Cut SkQP 2019-04-11

No-Try: true
Change-Id: Ie48ed9fe15a2a66298fa925a1212c3eeffdb1085
diff --git a/AUTHORS b/AUTHORS
index a0fd225..0e405b7 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -27,6 +27,7 @@
 Ion Rosca <rosca@adobe.com>
 Jacek Caban <cjacek@gmail.com>
 Jeff Muizelaar <jmuizelaar@mozilla.com>
+Jongdeok Kim <jongdeok.kim@navercorp.com>
 Lee Salzman <lsalzman@mozilla.com>
 Marcin Kazmierczak <mar.kazmierczak@gmail.com>
 Matthew Leibowitz <mattleibow@live.com>
@@ -54,3 +55,5 @@
 Kaloyan Donev <kdonev@gmail.com>
 Yong-Hwan Baek <meisterdevhwan@gmail.com>
 Alexander Khovansky <alx.khovansky@gmail.com>
+Zhuo Qingliang <zhuo.dev@gmail.com>
+Mainframe North <*@mainframe.co.uk>
diff --git a/BUILD.gn b/BUILD.gn
index 23f8ba8..9faf14d 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -12,11 +12,6 @@
   import("//build/vulkan/config.gni")
 }
 
-if (!defined(is_skia_standalone)) {
-  is_skia_standalone = false
-}
-is_skia_dev_build = is_skia_standalone && !is_official_build
-
 declare_args() {
   skia_enable_flutter_defines = false
 }
@@ -40,17 +35,16 @@
   skia_use_metal = false
   skia_use_libheif = is_skia_dev_build
   skia_use_x11 = is_linux
+  skia_use_xps = true
 
   skia_android_serial = ""
   skia_enable_ccpr = true
   skia_enable_nvpr = !skia_enable_flutter_defines
   skia_enable_discrete_gpu = true
-  skia_enable_gpu = true
   skia_enable_nima = false
   skia_enable_pdf = true
   skia_enable_spirv_validation = is_skia_dev_build && is_debug
   skia_enable_skpicture = true
-  skia_enable_tools = is_skia_dev_build
   skia_enable_vulkan_debug_layers = is_skia_dev_build && is_debug
   skia_qt_path = getenv("QT_PATH")
   skia_compile_processors = false
@@ -74,6 +68,16 @@
   skia_enable_fontmgr_custom_empty = is_fuchsia && skia_use_freetype
   skia_enable_fontmgr_android = skia_use_expat && skia_use_freetype
   skia_enable_fontmgr_fuchsia = is_fuchsia
+  skia_enable_fontmgr_win = is_win
+  skia_enable_fontmgr_win_gdi = is_win
+
+  if (is_mac) {
+    skia_gl_standard = "gl"
+  } else if (is_ios) {
+    skia_gl_standard = "gles"
+  } else {
+    skia_gl_standard = ""
+  }
 
   if (is_android) {
     skia_use_vulkan = defined(ndk_api) && ndk_api >= 24
@@ -93,9 +97,6 @@
   import(skia_settings)
 }
 
-# Our tools require static linking (they use non-exported symbols), and the GPU backend.
-skia_enable_tools = skia_enable_tools && !is_component_build && skia_enable_gpu
-
 skia_public_includes = [
   "include/android",
   "include/c",
@@ -140,6 +141,13 @@
   if (is_fuchsia) {
     defines += fuchsia_defines
   }
+  if (skia_gl_standard == "gles") {
+    defines += [ "SK_ASSUME_GL_ES=1" ]
+  } else if (skia_gl_standard == "gl") {
+    defines += [ "SK_ASSUME_GL=1" ]
+  } else if (skia_gl_standard == "webgl") {
+    defines += [ "SK_ASSUME_WEBGL=1" ]
+  }
 }
 
 # Skia internal APIs, used by Skia itself and a few test tools.
@@ -224,12 +232,14 @@
   visibility = [ ":*" ]
   if (invoker.enabled) {
     source_set(target_name) {
+      check_includes = false
       forward_variables_from(invoker, "*")
       configs += skia_library_configs
     }
   } else {
     # If not enabled, a phony empty target that swallows all otherwise unused variables.
     source_set(target_name) {
+      check_includes = false
       forward_variables_from(invoker,
                              "*",
                              [
@@ -347,6 +357,7 @@
       }
     }
     source_set(target_name) {
+      check_includes = false
       forward_variables_from(invoker,
                              "*",
                              [
@@ -411,29 +422,16 @@
   ]
 }
 
-optional("fontmgr_wasm") {
-  enabled = target_cpu == "wasm"
+optional("fontmgr_custom_empty") {
+  enabled = skia_enable_fontmgr_custom_empty
 
   deps = [
     ":typeface_freetype",
   ]
   sources = [
     "src/ports/SkFontMgr_custom.cpp",
-    "src/ports/SkFontMgr_custom.h",
-    "src/ports/SkFontMgr_custom_embedded.cpp",
-    "src/ports/SkFontMgr_custom_embedded_factory.cpp",
-  ]
-}
-
-optional("fontmgr_fuchsia") {
-  enabled = skia_enable_fontmgr_fuchsia
-
-  deps = [
-    "//garnet/public/fidl/fuchsia.fonts",
-  ]
-  sources = [
-    "src/ports/SkFontMgr_fuchsia.cpp",
-    "src/ports/SkFontMgr_fuchsia.h",
+    "src/ports/SkFontMgr_custom_empty.cpp",
+    "src/ports/SkFontMgr_custom_empty_factory.cpp",
   ]
 }
 
@@ -461,19 +459,53 @@
   ]
 }
 
-optional("fontmgr_custom_empty") {
-  enabled = skia_enable_fontmgr_custom_empty
+optional("fontmgr_fuchsia") {
+  enabled = skia_enable_fontmgr_fuchsia
+
+  deps = [
+    "//sdk/fidl/fuchsia.fonts",
+  ]
+  sources = [
+    "src/ports/SkFontMgr_fuchsia.cpp",
+    "src/ports/SkFontMgr_fuchsia.h",
+  ]
+}
+
+optional("fontmgr_wasm") {
+  enabled = target_cpu == "wasm"
 
   deps = [
     ":typeface_freetype",
   ]
   sources = [
     "src/ports/SkFontMgr_custom.cpp",
-    "src/ports/SkFontMgr_custom_empty.cpp",
-    "src/ports/SkFontMgr_custom_empty_factory.cpp",
+    "src/ports/SkFontMgr_custom.h",
+    "src/ports/SkFontMgr_custom_embedded.cpp",
+    "src/ports/SkFontMgr_custom_embedded_factory.cpp",
   ]
 }
 
+optional("fontmgr_win") {
+  enabled = skia_enable_fontmgr_win
+
+  sources = [
+    "src/fonts/SkFontMgr_indirect.cpp",
+    "src/ports/SkFontMgr_win_dw.cpp",
+    "src/ports/SkFontMgr_win_dw_factory.cpp",
+    "src/ports/SkScalerContext_win_dw.cpp",
+    "src/ports/SkTypeface_win_dw.cpp",
+  ]
+}
+
+optional("fontmgr_win_gdi") {
+  enabled = skia_enable_fontmgr_win_gdi
+
+  sources = [
+    "src/ports/SkFontHost_win.cpp",
+  ]
+  libs = [ "Gdi32.lib" ]
+}
+
 if (skia_lex) {
   executable("sksllex") {
     sources = [
@@ -592,7 +624,7 @@
   }
 }
 
-optional("gpu_for_real") {
+optional("gpu") {
   enabled = skia_enable_gpu
   deps = [
     ":compile_processors",
@@ -731,8 +763,10 @@
   }
   sources = skia_pdf_sources
   sources_when_disabled = [ "src/pdf/SkDocument_PDF_None.cpp" ]
-
-  if (skia_use_sfntly) {
+  if (skia_use_icu && skia_use_harfbuzz && skia_pdf_subset_harfbuzz) {
+    deps += [ "//third_party/harfbuzz" ]
+    defines = [ "SK_PDF_USE_HARFBUZZ_SUBSET" ]
+  } else if (skia_use_icu && skia_use_sfntly) {
     deps += [ "//third_party/sfntly" ]
     defines = [ "SK_PDF_USE_SFNTLY" ]
   }
@@ -866,19 +900,12 @@
   }
 }
 
-# Temporary fake targets so Flutter-in-Fuchsia can "depend" on them.
-# TODO: rename :gpu_for_real back to :gpu when cleaning up.
-source_set("effects") {
-}
-source_set("gpu") {
-}
-
 component("skia") {
   public_configs = [ ":skia_public" ]
   configs += skia_library_configs
 
   public_deps = [
-    ":gpu_for_real",
+    ":gpu",
     ":pdf",
     ":skcms",
   ]
@@ -896,6 +923,8 @@
     ":fontmgr_fontconfig",
     ":fontmgr_fuchsia",
     ":fontmgr_wasm",
+    ":fontmgr_win",
+    ":fontmgr_win_gdi",
     ":gif",
     ":heif",
     ":hsw",
@@ -921,10 +950,18 @@
   # We clear the filter here for clients who may have set up a global filter.
   set_sources_assignment_filter([])
 
+  public = skia_core_public
+  public += skia_utils_public
+  public += skia_effects_public
+  public += skia_effects_imagefilter_public
+  public += skia_xps_public
+
   sources = []
   sources += skia_core_sources
   sources += skia_utils_sources
-  sources += skia_xps_sources
+  if (skia_use_xps) {
+    sources += skia_xps_sources
+  }
   sources += skia_effects_sources
   sources += skia_effects_imagefilter_sources
   sources += [
@@ -965,6 +1002,7 @@
   defines = []
   if (!skia_enable_skpicture) {
     defines = [ "SK_DISABLE_SKPICTURE" ]
+    public -= skia_skpicture_public
     sources -= skia_skpicture_sources
     sources -= [ "//src/effects/imagefilters/SkPictureImageFilter.cpp" ]
     sources += [ "src/core/SkPicture_none.cpp" ]
@@ -974,22 +1012,15 @@
 
   if (is_win) {
     sources += [
-      "src/fonts/SkFontMgr_indirect.cpp",
       "src/ports/SkDebug_win.cpp",
-      "src/ports/SkFontHost_win.cpp",
-      "src/ports/SkFontMgr_win_dw.cpp",
-      "src/ports/SkFontMgr_win_dw_factory.cpp",
       "src/ports/SkImageEncoder_WIC.cpp",
       "src/ports/SkImageGeneratorWIC.cpp",
       "src/ports/SkOSFile_win.cpp",
       "src/ports/SkOSLibrary_win.cpp",
-      "src/ports/SkScalerContext_win_dw.cpp",
       "src/ports/SkTLS_win.cpp",
-      "src/ports/SkTypeface_win_dw.cpp",
     ]
     libs += [
       "FontSub.lib",
-      "Gdi32.lib",
       "Ole32.lib",
       "OleAut32.lib",
       "User32.lib",
@@ -1066,7 +1097,37 @@
   }
 }
 
+# DebugCanvas used in experimental/wasm-skp-debugger
+if (target_cpu == "wasm") {
+  static_library("debugcanvas") {
+    public_configs = [ ":skia_public" ]
+
+    include_dirs = [
+      "include/gpu",
+      "include/private",
+      "src/gpu",
+      "src/core",
+      "src/shaders",
+      "src/utils",
+      "tools",
+      "tools/debugger",
+    ]
+
+    sources = [
+      "tools/UrlDataManager.cpp",
+      "tools/debugger/DebugCanvas.cpp",
+      "tools/debugger/DrawCommand.cpp",
+      "tools/debugger/JsonWriteBuffer.cpp",
+    ]
+
+    deps = [
+      ":fontmgr_wasm",
+    ]
+  }
+}
+
 static_library("pathkit") {
+  check_includes = false
   public_configs = [ ":skia_public" ]
   configs += skia_library_configs
 
@@ -1090,6 +1151,7 @@
 
   sources = []
   sources += skia_pathops_sources
+  sources += skia_pathops_public
   sources += [
     "src/core/SkAnalyticEdge.cpp",
     "src/core/SkArenaAlloc.cpp",
@@ -1132,6 +1194,14 @@
   ]
 }
 
+group("modules") {
+  deps = [
+    "modules/particles",
+    "modules/skottie",
+    "modules/skshaper",
+  ]
+}
+
 # Targets guarded by skia_enable_tools may use //third_party freely.
 if (skia_enable_tools) {
   # Used by gn_to_bp.py to list our public include dirs.
@@ -1150,6 +1220,7 @@
     # TODO: would be cool to not hard-code these here, but how?
     module_public_includes = [
       "modules/sksg/include",
+      "modules/skshaper/include",
       "modules/skottie/include",
     ]
     args =
@@ -1164,6 +1235,7 @@
 
   if (target_cpu == "x64") {
     executable("fiddle") {
+      check_includes = false
       libs = []
       sources = [
         "tools/fiddle/draw.cpp",
@@ -1182,6 +1254,7 @@
         ":skia",
         ":skia.h",
         "modules/skottie",
+        "modules/skshaper",
       ]
     }
   }
@@ -1195,6 +1268,7 @@
       ":skia",
       ":skia.h",
       "modules/skottie",
+      "modules/skshaper",
     ]
 
     # We add this directory to simulate the client already have
@@ -1211,6 +1285,7 @@
     }
     source_set(target_name) {
       forward_variables_from(invoker, "*", [ "public_include_dirs" ])
+      check_includes = false
       public_configs = [
         ":" + target_name + "_config",
         ":skia_private",
@@ -1249,35 +1324,28 @@
         ]
       }
 
-      bundle_ios_data =
-          defined(invoker.bundle_ios_data) && invoker.bundle_ios_data
-
-      if (bundle_ios_data) {
-        has_skps =
-            "True" == exec_script("//gn/checkdir.py",
-                                  [ rebase_path("skps", root_build_dir) ],
-                                  "trim string")
-        bundle_data("${app_name}_bundle_resources") {
+      has_skps = "True" == exec_script("//gn/checkdir.py",
+                                       [ rebase_path("skps", root_build_dir) ],
+                                       "trim string")
+      bundle_data("${app_name}_bundle_resources") {
+        sources = [
+          "resources",
+        ]
+        outputs = [
+          # iOS reserves the folders 'Resources' and 'resources' so store one level deeper
+          "{{bundle_resources_dir}}/data/resources",
+        ]
+      }
+      if (has_skps) {
+        bundle_data("${app_name}_bundle_skps") {
           sources = [
-            "resources",
+            "skps",
           ]
           outputs = [
-            # iOS reserves the folders 'Resources' and 'resources' so store one level deeper
-            "{{bundle_resources_dir}}/data/resources",
+            # Store in same folder as resources
+            "{{bundle_resources_dir}}/data/skps",
           ]
         }
-
-        if (has_skps) {
-          bundle_data("${app_name}_bundle_skps") {
-            sources = [
-              "skps",
-            ]
-            outputs = [
-              # Store in same folder as resources
-              "{{bundle_resources_dir}}/data/skps",
-            ]
-          }
-        }
       }
 
       executable("${app_name}_generate_executable") {
@@ -1306,6 +1374,19 @@
         testonly = true
       }
 
+      bundle_data("${app_name}_bundle_symbols") {
+        public_deps = [
+          ":${app_name}_generate_executable",
+        ]
+        sources = [
+          "$gen_path/${app_name}.dSYM",
+        ]
+        outputs = [
+          "{{bundle_executable_dir}}/${app_name}.dSYM",
+        ]
+        testonly = true
+      }
+
       create_bundle("$app_name") {
         product_type = "com.apple.product-type.application"
         testonly = true
@@ -1313,17 +1394,15 @@
         bundle_root_dir = "${root_build_dir}/${target_name}.app"
         bundle_resources_dir = bundle_root_dir
         bundle_executable_dir = bundle_root_dir
-        bundle_plugins_dir = bundle_root_dir + "/Plugins"
 
         deps = [
           ":${app_name}_bundle_executable",
           ":${app_name}_bundle_info_plist",
+          ":${app_name}_bundle_resources",
+          ":${app_name}_bundle_symbols",
         ]
-        if (bundle_ios_data) {
-          deps += [ ":${app_name}_bundle_resources" ]
-          if (has_skps) {
-            deps += [ ":${app_name}_bundle_skps" ]
-          }
+        if (has_skps) {
+          deps += [ ":${app_name}_bundle_skps" ]
         }
 
         # should only code sign when running on a device, not the simulator
@@ -1353,6 +1432,7 @@
       } else {
         _executable = target_name
         executable(_executable) {
+          check_includes = false
           forward_variables_from(invoker, "*", [ "is_shared_library" ])
           configs += [ ":skia_private" ]
           testonly = true
@@ -1422,10 +1502,11 @@
       "tools/gpu/MemoryCache.h",
       "tools/gpu/ProxyUtils.cpp",
       "tools/gpu/TestContext.cpp",
+      "tools/gpu/YUVUtils.cpp",
+      "tools/gpu/YUVUtils.h",
       "tools/gpu/atlastext/GLTestAtlasTextRenderer.cpp",
       "tools/gpu/gl/GLTestContext.cpp",
       "tools/gpu/gl/command_buffer/GLTestContext_command_buffer.cpp",
-      "tools/gpu/gl/null/NullGLTestContext.cpp",
       "tools/gpu/mock/MockTestContext.cpp",
     ]
     libs = []
@@ -1474,14 +1555,14 @@
   test_lib("flags") {
     public_include_dirs = [ "tools/flags" ]
     sources = [
-      "tools/flags/SkCommandLineFlags.cpp",
+      "tools/flags/CommandLineFlags.cpp",
     ]
   }
-  test_lib("common_flags") {
+
+  test_lib("common_flags_config") {
     public_include_dirs = [ "tools/flags" ]
     sources = [
-      "tools/flags/SkCommonFlags.cpp",
-      "tools/flags/SkCommonFlagsConfig.cpp",
+      "tools/flags/CommonFlagsConfig.cpp",
     ]
     deps = [
       ":flags",
@@ -1490,6 +1571,51 @@
       ":gpu_tool_utils",
     ]
   }
+  test_lib("common_flags_gpu") {
+    public_include_dirs = [ "tools/flags" ]
+    sources = [
+      "tools/flags/CommonFlagsGpu.cpp",
+    ]
+    deps = [
+      ":flags",
+    ]
+    public_deps = [
+      ":gpu_tool_utils",
+    ]
+  }
+  test_lib("common_flags_images") {
+    public_include_dirs = [ "tools/flags" ]
+    sources = [
+      "tools/flags/CommonFlagsImages.cpp",
+    ]
+    deps = [
+      ":flags",
+    ]
+  }
+  test_lib("common_flags_aa") {
+    public_include_dirs = [ "tools/flags" ]
+    sources = [
+      "tools/flags/CommonFlagsAA.cpp",
+    ]
+    deps = [
+      ":flags",
+    ]
+  }
+
+  test_lib("trace") {
+    public_include_dirs = [ "tools/trace" ]
+    deps = [
+      ":flags",
+    ]
+    sources = [
+      "tools/trace/ChromeTracingTracer.cpp",
+      "tools/trace/ChromeTracingTracer.h",
+      "tools/trace/EventTracingPriv.cpp",
+      "tools/trace/EventTracingPriv.h",
+      "tools/trace/SkDebugfTracer.cpp",
+      "tools/trace/SkDebugfTracer.h",
+    ]
+  }
 
   test_lib("tool_utils") {
     public_include_dirs = [
@@ -1497,7 +1623,6 @@
       "tools/debugger",
       "tools/fonts",
       "tools/timer",
-      "tools/trace",
     ]
     sources = [
       "tools/AndroidSkDebugToStdOut.cpp",
@@ -1507,29 +1632,23 @@
       "tools/LsanSuppressions.cpp",
       "tools/ProcStats.cpp",
       "tools/Resources.cpp",
+      "tools/ToolUtils.cpp",
       "tools/UrlDataManager.cpp",
-      "tools/debugger/SkDebugCanvas.cpp",
-      "tools/debugger/SkDrawCommand.cpp",
-      "tools/debugger/SkJsonWriteBuffer.cpp",
-      "tools/fonts/SkRandomScalerContext.cpp",
-      "tools/fonts/SkTestEmptyTypeface.h",
-      "tools/fonts/SkTestFontMgr.cpp",
-      "tools/fonts/SkTestFontMgr.h",
-      "tools/fonts/SkTestSVGTypeface.cpp",
-      "tools/fonts/SkTestSVGTypeface.h",
-      "tools/fonts/SkTestTypeface.cpp",
-      "tools/fonts/SkTestTypeface.h",
-      "tools/fonts/sk_tool_utils_font.cpp",
+      "tools/debugger/DebugCanvas.cpp",
+      "tools/debugger/DrawCommand.cpp",
+      "tools/debugger/JsonWriteBuffer.cpp",
+      "tools/fonts/RandomScalerContext.cpp",
+      "tools/fonts/TestEmptyTypeface.h",
+      "tools/fonts/TestFontMgr.cpp",
+      "tools/fonts/TestFontMgr.h",
+      "tools/fonts/TestSVGTypeface.cpp",
+      "tools/fonts/TestSVGTypeface.h",
+      "tools/fonts/TestTypeface.cpp",
+      "tools/fonts/TestTypeface.h",
+      "tools/fonts/ToolUtilsFont.cpp",
       "tools/random_parse_path.cpp",
-      "tools/sk_tool_utils.cpp",
-      "tools/timer/SkAnimTimer.h",
+      "tools/timer/AnimTimer.h",
       "tools/timer/Timer.cpp",
-      "tools/trace/SkChromeTracingTracer.cpp",
-      "tools/trace/SkChromeTracingTracer.h",
-      "tools/trace/SkDebugfTracer.cpp",
-      "tools/trace/SkDebugfTracer.h",
-      "tools/trace/SkEventTracingPriv.cpp",
-      "tools/trace/SkEventTracingPriv.h",
     ]
     libs = []
     if (is_ios) {
@@ -1543,10 +1662,9 @@
     deps = [
       ":experimental_svg_model",
       ":flags",
-      "//third_party/libpng",
     ]
     public_deps = [
-      ":common_flags",
+      ":gpu_tool_utils",
     ]
   }
 
@@ -1561,6 +1679,7 @@
       "modules/skottie",
       "modules/skottie:gm",
       "modules/sksg",
+      "modules/skshaper",
     ]
     public_deps = [
       ":gpu_tool_utils",
@@ -1584,7 +1703,9 @@
       ":tool_utils",
       "modules/skottie:tests",
       "modules/sksg:tests",
+      "modules/skshaper",
       "//third_party/libpng",
+      "//third_party/libwebp",
       "//third_party/zlib",
     ]
     public_deps = [
@@ -1679,29 +1800,6 @@
     }
   }
 
-  test_app("bookmaker") {
-    sources = [
-      "tools/bookmaker/bmhParser.cpp",
-      "tools/bookmaker/bookmaker.cpp",
-      "tools/bookmaker/cataloger.cpp",
-      "tools/bookmaker/definition.cpp",
-      "tools/bookmaker/fiddleParser.cpp",
-      "tools/bookmaker/hackParser.cpp",
-      "tools/bookmaker/includeParser.cpp",
-      "tools/bookmaker/includeWriter.cpp",
-      "tools/bookmaker/mdOut.cpp",
-      "tools/bookmaker/parserCommon.cpp",
-      "tools/bookmaker/selfCheck.cpp",
-      "tools/bookmaker/spellCheck.cpp",
-      "tools/bookmaker/textParser.cpp",
-    ]
-    deps = [
-      ":flags",
-      ":skia",
-      ":tool_utils",
-    ]
-  }
-
   if (is_linux || is_mac) {
     test_app("skottie_tool") {
       deps = [
@@ -1722,7 +1820,9 @@
       deps = [
         ":experimental_svg_model",
         ":flags",
+        ":gpu_tool_utils",
         ":xml",
+        "modules/sksg",
         "modules/skshaper",
       ]
 
@@ -1749,6 +1849,37 @@
         ":skia",
       ]
     }
+    test_lib("hash_and_encode") {
+      public_include_dirs = [ "tools" ]
+      sources = [
+        "tools/HashAndEncode.cpp",
+        "tools/HashAndEncode.h",
+      ]
+      deps = [
+        ":flags",
+        ":skia",
+        "//third_party/libpng",
+      ]
+    }
+    test_app("fm") {
+      sources = [
+        "tools/fm/fm.cpp",
+      ]
+      deps = [
+        ":common_flags_aa",
+        ":common_flags_gpu",
+        ":experimental_svg_model",
+        ":flags",
+        ":gm",
+        ":gpu_tool_utils",
+        ":hash_and_encode",
+        ":skia",
+        ":tool_utils",
+        ":trace",
+        "modules/skottie",
+        "modules/skottie:utils",
+      ]
+    }
     test_app("dm") {
       sources = [
         "dm/DM.cpp",
@@ -1757,18 +1888,22 @@
         "dm/DMSrcSink.cpp",
       ]
       deps = [
-        ":common_flags",
+        ":common_flags_aa",
+        ":common_flags_config",
+        ":common_flags_gpu",
+        ":common_flags_images",
         ":experimental_svg_model",
         ":flags",
         ":gm",
         ":gpu_tool_utils",
+        ":hash_and_encode",
         ":skia",
         ":tests",
         ":tool_utils",
+        ":trace",
         "modules/skottie",
         "modules/skottie:utils",
         "modules/sksg",
-        "//third_party/libpng",
       ]
 
       # NIMA does not build on Windows clang
@@ -1795,13 +1930,17 @@
     ]
     deps = [
       ":bench",
-      ":common_flags",
+      ":common_flags_aa",
+      ":common_flags_config",
+      ":common_flags_gpu",
+      ":common_flags_images",
       ":experimental_svg_model",
       ":flags",
       ":gm",
       ":gpu_tool_utils",
       ":skia",
       ":tool_utils",
+      ":trace",
       "modules/sksg",
     ]
   }
@@ -1821,6 +1960,8 @@
       "tools/skpbench/skpbench.cpp",
     ]
     deps = [
+      ":common_flags_config",
+      ":common_flags_gpu",
       ":flags",
       ":gpu_tool_utils",
       ":skia",
@@ -1902,7 +2043,6 @@
         ":skia",
         ":tool_utils",
         "//third_party/libmicrohttpd",
-        "//third_party/libpng",
       ]
     }
   }
@@ -1912,6 +2052,7 @@
       "tools",
       "tools/debugger",
       "tools/fonts",
+      "src/sksl",
     ]
     sources = [
       "fuzz/Fuzz.cpp",
@@ -1935,18 +2076,21 @@
       "fuzz/oss_fuzz/FuzzPathDeserialize.cpp",
       "fuzz/oss_fuzz/FuzzRegionDeserialize.cpp",
       "fuzz/oss_fuzz/FuzzRegionSetPath.cpp",
+      "fuzz/oss_fuzz/FuzzSKSL2GLSL.cpp",
+      "fuzz/oss_fuzz/FuzzSKSL2Metal.cpp",
+      "fuzz/oss_fuzz/FuzzSKSL2Pipeline.cpp",
+      "fuzz/oss_fuzz/FuzzSKSL2SPIRV.cpp",
       "fuzz/oss_fuzz/FuzzTextBlobDeserialize.cpp",
       "tools/UrlDataManager.cpp",
-      "tools/debugger/SkDebugCanvas.cpp",
-      "tools/debugger/SkDrawCommand.cpp",
-      "tools/debugger/SkJsonWriteBuffer.cpp",
+      "tools/debugger/DebugCanvas.cpp",
+      "tools/debugger/DrawCommand.cpp",
+      "tools/debugger/JsonWriteBuffer.cpp",
     ]
     deps = [
       ":flags",
       ":gpu_tool_utils",
       ":skia",
       "modules/skottie:fuzz",
-      "//third_party/libpng",
     ]
   }
 
@@ -2159,9 +2303,20 @@
         libs += [ "X11-xcb" ]
       } else if (is_win) {
         sources += [ "tools/sk_app/win/VulkanWindowContext_win.cpp" ]
+      } else if (is_mac) {
+        sources += [ "tools/sk_app/mac/VulkanWindowContext_mac.mm" ]
+        libs += [ "MetalKit.framework" ]
       }
     }
 
+    if (skia_use_metal) {
+      sources += [ "tools/sk_app/MetalWindowContext.mm" ]
+      if (is_mac) {
+        sources += [ "tools/sk_app/mac/MetalWindowContext_mac.mm" ]
+      }
+      libs += [ "MetalKit.framework" ]
+    }
+
     deps = [
       ":gpu_tool_utils",
       ":skia",
@@ -2177,16 +2332,33 @@
     }
   }
 
+  if (!skia_use_vulkan && (is_mac || is_linux || is_win)) {
+    test_app("fiddle_examples") {
+      sources = [
+        "tools/fiddle/all_examples.cpp",
+        "tools/fiddle/examples.cpp",
+        "tools/fiddle/examples.h",
+      ]
+      include_dirs = [ "tools" ]
+      if (is_win) {
+        cflags = [ "/wd4756" ]  # Overflow in constant arithmetic
+      }
+      deps = [
+        ":skia",
+        ":skia.h",
+        "modules/skottie",
+        "modules/skshaper",
+      ]
+    }
+  }
   test_app("viewer") {
     is_shared_library = is_android
-    if (is_ios) {
-      bundle_ios_data = true
-    }
     sources = [
       "tools/viewer/BisectSlide.cpp",
       "tools/viewer/GMSlide.cpp",
       "tools/viewer/ImGuiLayer.cpp",
       "tools/viewer/ImageSlide.cpp",
+      "tools/viewer/ParticlesSlide.cpp",
       "tools/viewer/SKPSlide.cpp",
       "tools/viewer/SampleSlide.cpp",
       "tools/viewer/SkottieSlide.cpp",
@@ -2201,6 +2373,7 @@
 
     include_dirs = [ "experimental" ]
     deps = [
+      ":common_flags_gpu",
       ":experimental_svg_model",
       ":flags",
       ":gm",
@@ -2209,6 +2382,8 @@
       ":sk_app",
       ":skia",
       ":tool_utils",
+      ":trace",
+      "modules/particles",
       "modules/skottie",
       "modules/skottie:utils",
       "modules/sksg",
@@ -2233,6 +2408,8 @@
 
       # For internship expedience, yes, we're rebuilding Skia rather than depending on :skia.
       # At the moment there's no way to use Skia and Skottie/SkShaper unless they're in the same .so.
+      public = skia_core_public
+      public += skia_utils_public
       sources = []
       sources += skia_core_sources
       sources += skia_utils_sources
@@ -2349,9 +2526,9 @@
       }
       sources = [
         "tools/UrlDataManager.cpp",
-        "tools/debugger/SkDebugCanvas.cpp",
-        "tools/debugger/SkDrawCommand.cpp",
-        "tools/debugger/SkJsonWriteBuffer.cpp",
+        "tools/debugger/DebugCanvas.cpp",
+        "tools/debugger/DrawCommand.cpp",
+        "tools/debugger/JsonWriteBuffer.cpp",
         "tools/mdbviz/MainWindow.cpp",
         "tools/mdbviz/Model.cpp",
         "tools/mdbviz/main.cpp",
@@ -2377,7 +2554,6 @@
         ":generate_mocs",
         ":generate_resources",
         ":skia",
-        "//third_party/libpng",
       ]
     }
   }
@@ -2396,16 +2572,11 @@
   if (skia_use_opencl) {
     test_app("hello-opencl") {
       sources = [
-        "src/compute/common/cl/assert_cl.c",
-        "src/compute/common/cl/find_cl.c",
         "tools/hello-opencl.cpp",
       ]
-      include_dirs = [ "src/compute/common" ]
-      if (is_linux) {
-        libs = [ "OpenCL" ]
-      } else if (is_win) {
-        libs = [ "OpenCL.lib" ]
-      }
+      deps = [
+        "//third_party/opencl",
+      ]
     }
   }
 }
diff --git a/DEPS b/DEPS
index cdcd603..637885e 100644
--- a/DEPS
+++ b/DEPS
@@ -7,12 +7,12 @@
 deps = {
   "buildtools"                            : "https://chromium.googlesource.com/chromium/buildtools.git@505de88083136eefd056e5ee4ca0f01fe9b33de8",
   "common"                                : "https://skia.googlesource.com/common.git@9737551d7a52c3db3262db5856e6bcd62c462b92",
-  "third_party/externals/angle2"          : "https://chromium.googlesource.com/angle/angle.git@a54104803d7211d40720ddca28407137e5b70767",
+  "third_party/externals/angle2"          : "https://chromium.googlesource.com/angle/angle.git@e6b23e45b380bee1a2dfda06e4728d24d4d4ad8b",
   "third_party/externals/dng_sdk"         : "https://android.googlesource.com/platform/external/dng_sdk.git@96443b262250c390b0caefbf3eed8463ba35ecae",
   "third_party/externals/egl-registry"    : "https://skia.googlesource.com/external/github.com/KhronosGroup/EGL-Registry@a0bca08de07c7d7651047bedc0b653cfaaa4f2ae",
   "third_party/externals/expat"           : "https://android.googlesource.com/platform/external/expat.git@android-6.0.1_r55",
   "third_party/externals/freetype"        : "https://skia.googlesource.com/third_party/freetype2.git@7edc937fe679d14d66f55cf6f7fa607925d38f3c",
-  "third_party/externals/harfbuzz"        : "https://chromium.googlesource.com/external/github.com/harfbuzz/harfbuzz.git@1e06282105a2d579aab32094cc7abc10ed231978",
+  "third_party/externals/harfbuzz"        : "https://chromium.googlesource.com/external/github.com/harfbuzz/harfbuzz.git@4f37ab63de9705d7bf74ee75364747e41b7c06a1",
   "third_party/externals/icu"             : "https://chromium.googlesource.com/chromium/deps/icu.git@407b39301e71006b68bd38e770f35d32398a7b14",
   "third_party/externals/imgui"           : "https://skia.googlesource.com/external/github.com/ocornut/imgui.git@d38d7c6628bebd02692cfdd6fa76b4d992a35b75",
   "third_party/externals/libjpeg-turbo"   : "https://skia.googlesource.com/external/github.com/libjpeg-turbo/libjpeg-turbo.git@2.0.0",
@@ -21,21 +21,22 @@
   "third_party/externals/lua"             : "https://skia.googlesource.com/external/github.com/lua/lua.git@v5-3-4",
   "third_party/externals/microhttpd"      : "https://android.googlesource.com/platform/external/libmicrohttpd@748945ec6f1c67b7efc934ab0808e1d32f2fb98d",
   "third_party/externals/opencl-lib"      : "https://skia.googlesource.com/external/github.com/GPUOpen-Tools/common-lib-amd-APPSDK-3.0@4e6d30e406d2e5a65e1d65e404fe6df5f772a32b",
+  "third_party/externals/opencl-registry" : "https://skia.googlesource.com/external/github.com/KhronosGroup/OpenCL-Registry@932ed55c85f887041291cef8019e54280c033c35",
   "third_party/externals/opengl-registry" : "https://skia.googlesource.com/external/github.com/KhronosGroup/OpenGL-Registry@14b80ebeab022b2c78f84a573f01028c96075553",
   "third_party/externals/piex"            : "https://android.googlesource.com/platform/external/piex.git@bb217acdca1cc0c16b704669dd6f91a1b509c406",
   "third_party/externals/sdl"             : "https://skia.googlesource.com/third_party/sdl@5d7cfcca344034aff9327f77fc181ae3754e7a90",
   "third_party/externals/sfntly"          : "https://chromium.googlesource.com/external/github.com/googlei18n/sfntly.git@b55ff303ea2f9e26702b514cf6a3196a2e3e2974",
   "third_party/externals/spirv-headers"   : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Headers.git@661ad91124e6af2272afd00f804d8aa276e17107",
   "third_party/externals/spirv-tools"     : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Tools.git@e9e4393b1c5aad7553c05782acefbe32b42644bd",
-  "third_party/externals/swiftshader"     : "https://swiftshader.googlesource.com/SwiftShader@c81766320762e6a67dad91e6a41304f8713bc7d1",
+  "third_party/externals/swiftshader"     : "https://swiftshader.googlesource.com/SwiftShader@3954a0bafb8aba97b8fdc365a39161f896edc199",
   #"third_party/externals/v8"              : "https://chromium.googlesource.com/v8/v8.git@5f1ae66d5634e43563b2d25ea652dfb94c31a3b4",
-  "third_party/externals/wuffs"           : "https://skia.googlesource.com/external/github.com/google/wuffs.git@fda3c4c9863d9f9fcec58ae66508c4621fc71ea5",
+  "third_party/externals/wuffs"           : "https://skia.googlesource.com/external/github.com/google/wuffs.git@f58ffbc927899b9534a273d3057094ce6ac1ed61",
   "third_party/externals/zlib"            : "https://chromium.googlesource.com/chromium/src/third_party/zlib@47af7c547f8551bd25424e56354a2ae1e9062859",
   "third_party/externals/Nima-Cpp"        : "https://skia.googlesource.com/external/github.com/2d-inc/Nima-Cpp.git@4bd02269d7d1d2e650950411325eafa15defb084",
   "third_party/externals/Nima-Math-Cpp"   : "https://skia.googlesource.com/external/github.com/2d-inc/Nima-Math-Cpp.git@e0c12772093fa8860f55358274515b86885f0108",
 
   "../src": {
-    "url": "https://chromium.googlesource.com/chromium/src.git@c27b32b2fd5280299c9c81bf7665c45c8a3e6b0e",
+    "url": "https://chromium.googlesource.com/chromium/src.git@f8764798ff0e0e0f41052ed0a70637102a499801",
     "condition": "checkout_chromium",
   },
 }
diff --git a/Doxyfile b/Doxyfile
deleted file mode 100644
index 70f1980..0000000
--- a/Doxyfile
+++ /dev/null
@@ -1,59 +0,0 @@
-# Configuration used by
-# https://chromium.googlesource.com/chromium/tools/build/+/0f611b202b0e/scripts/slave/recipe_modules/skia/resources/generate_and_upload_doxygen.py
-# to generate our Doxygen docs, which are then uploaded to
-# http://skia-doc.commondatastorage.googleapis.com/doxygen/doxygen/html/index.html
-
-
-PROJECT_NAME = Skia
-PROJECT_BRIEF = 2D Graphics Library
-
-PROJECT_LOGO = https://skia.org/res/img/logo.png
-
-# These lines are overridden by
-# https://chromium.googlesource.com/chromium/tools/build/+/0f611b202b0e/scripts/slave/recipe_modules/skia/resources/generate_and_upload_doxygen.py
-# but they are needed in case someone wants to generate the doxygen manually
-# for some reason.
-OUTPUT_DIRECTORY = ../docs
-HTML_FOOTER = ../docs/static_footer.txt
-
-EXTRACT_ALL = NO
-INHERIT_DOCS = YES
-INLINE_INHERITED_MEMB = NO
-JAVADOC_AUTOBRIEF = YES
-TAB_SIZE = 4
-WARN_IF_UNDOCUMENTED = NO
-
-# This file only creates documentation for the most important parts of the
-# external-visible API.
-INPUT = include/core include/effects include/gpu
-EXTRACT_PRIVATE = YES
-EXTRACT_STATIC = YES
-
-HTML_DYNAMIC_SECTIONS = NO
-GENERATE_TREEVIEW = YES
-
-GENERATE_LATEX = NO
-
-# Good class diagrams require graphviz, but also more parameter tuning and
-# more build time than seems worthwhile.
-CLASS_DIAGRAMS = YES
-# HAVE_DOT = YES
-# CLASS_GRAPH = YES
-# COLLABORATION_GRAPH = YES
-# UML_LOOK = YES
-# GRAPHICAL_HIERARCHY = YES
-
-# Make SkDEBUGCODE disappear.
-ENABLE_PREPROCESSING = YES
-MACRO_EXPANSION = YES
-EXPAND_ONLY_PREDEF = YES
-EXPAND_AS_DEFINED = SkDEBUGCODE
-
-# experimental evil only! inflates build time by 10 minutes
-# SEARCH_INCLUDES = YES
-# INCLUDE_GRAPH = YES
-# INCLUDED_BY_GRAPH = YES
-# DIRECTORY_GRAPH = YES
-# INTERACTIVE_SVG = YES
-
-
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 0cf5acb..3f177e5 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -25,6 +25,8 @@
 
 # Please add the complete email address here (and not just 'xyz@' or 'xyz').
 PUBLIC_API_OWNERS = (
+    'mtklein@chromium.org',
+    'mtklein@google.com',
     'reed@chromium.org',
     'reed@google.com',
     'bsalomon@chromium.org',
@@ -43,7 +45,7 @@
 SERVICE_ACCOUNT_SUFFIX = [
     '@%s.iam.gserviceaccount.com' % project for project in [
         'skia-buildbots.google.com', 'skia-swarming-bots', 'skia-public',
-        'skia-corp.google.com']]
+        'skia-corp.google.com', 'chops-service-accounts']]
 
 
 def _CheckChangeHasEol(input_api, output_api, source_file_filter=None):
@@ -441,12 +443,6 @@
   This hook does the following:
   * Adds a link to preview docs changes if there are any docs changes in the CL.
   * Adds 'No-Try: true' if the CL contains only docs changes.
-  * Adds 'No-Tree-Checks: true' for non master branch changes since they do not
-    need to be gated on the master branch's tree.
-  * Adds 'No-Try: true' for non master branch changes since trybots do not yet
-    work on them.
-  * Adds 'No-Presubmit: true' for non master branch changes since those don't
-    run the presubmit checks.
   """
 
   results = []
@@ -496,30 +492,6 @@
               'Automatically added a link to preview the docs changes to the '
               'CL\'s description'))
 
-    # If the target ref is not master then add 'No-Tree-Checks: true' and
-    # 'No-Try: true' to the CL's description if it does not already exist there.
-    target_ref = cl.GetRemoteBranch()[1]
-    if target_ref != 'refs/remotes/origin/master':
-      if not _FooterExists(footers, 'No-Tree-Checks', 'true'):
-        new_description_lines.append('No-Tree-Checks: true')
-        results.append(
-            output_api.PresubmitNotifyResult(
-                'Branch changes do not need to rely on the master branch\'s '
-                'tree status. Automatically added \'No-Tree-Checks: true\' to '
-                'the CL\'s description'))
-      if not _FooterExists(footers, 'No-Try', 'true'):
-        new_description_lines.append('No-Try: true')
-        results.append(
-            output_api.PresubmitNotifyResult(
-                'Trybots do not yet work for non-master branches. '
-                'Automatically added \'No-Try: true\' to the CL\'s '
-                'description'))
-      if not _FooterExists(footers, 'No-Presubmit', 'true'):
-        new_description_lines.append('No-Presubmit: true')
-        results.append(
-            output_api.PresubmitNotifyResult(
-                'Branch changes do not run the presubmit checks.'))
-
     # If the description has changed update it.
     if new_description_lines != original_description_lines:
       # Add a new line separating the new contents from the old contents.
diff --git a/bench/AlternatingColorPatternBench.cpp b/bench/AlternatingColorPatternBench.cpp
index 57ff25a..b2735cf0 100644
--- a/bench/AlternatingColorPatternBench.cpp
+++ b/bench/AlternatingColorPatternBench.cpp
@@ -53,10 +53,10 @@
     SkPaint     paint;
 
     paint.setShader(SkGradientShader::MakeLinear(kPts0, kColors0, kPos, SK_ARRAY_COUNT(kColors0),
-                                                 SkShader::kClamp_TileMode));
+                                                 SkTileMode::kClamp));
     canvas.drawPaint(paint);
     paint.setShader(SkGradientShader::MakeLinear(kPts1, kColors1, kPos, SK_ARRAY_COUNT(kColors1),
-                                                 SkShader::kClamp_TileMode));
+                                                 SkTileMode::kClamp));
     canvas.drawPaint(paint);
 }
 
@@ -113,9 +113,7 @@
         int w = 40;
         int h = 40;
         makebm(&fBmp, w, h);
-        fBmShader = SkShader::MakeBitmapShader(fBmp,
-                                                 SkShader::kRepeat_TileMode,
-                                                 SkShader::kRepeat_TileMode);
+        fBmShader = fBmp.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat);
         int offset = 2;
         int count = 0;
         for (int j = 0; j < NY; ++j) {
diff --git a/bench/AndroidCodecBench.cpp b/bench/AndroidCodecBench.cpp
index 886e875..ba92a74 100644
--- a/bench/AndroidCodecBench.cpp
+++ b/bench/AndroidCodecBench.cpp
@@ -7,9 +7,9 @@
 
 #include "AndroidCodecBench.h"
 #include "CodecBenchPriv.h"
-#include "SkBitmap.h"
+#include "CommandLineFlags.h"
 #include "SkAndroidCodec.h"
-#include "SkCommandLineFlags.h"
+#include "SkBitmap.h"
 #include "SkOSFile.h"
 
 AndroidCodecBench::AndroidCodecBench(SkString baseName, SkData* encoded, int sampleSize)
diff --git a/bench/BigPathBench.cpp b/bench/BigPathBench.cpp
index a2f3ae0..6c49ae7 100644
--- a/bench/BigPathBench.cpp
+++ b/bench/BigPathBench.cpp
@@ -8,7 +8,7 @@
 #include "Benchmark.h"
 #include "SkCanvas.h"
 #include "SkPath.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 enum Align {
     kLeft_Align,
@@ -42,9 +42,7 @@
         return SkIPoint::Make(640, 100);
     }
 
-    void onDelayedSetup() override {
-        sk_tool_utils::make_big_path(fPath);
-    }
+    void onDelayedSetup() override { ToolUtils::make_big_path(fPath); }
 
     void onDraw(int loops, SkCanvas* canvas) override {
         SkPaint paint;
diff --git a/bench/BitmapBench.cpp b/bench/BitmapBench.cpp
index cc6f325..a44a897 100644
--- a/bench/BitmapBench.cpp
+++ b/bench/BitmapBench.cpp
@@ -12,7 +12,7 @@
 #include "SkPaint.h"
 #include "SkRandom.h"
 #include "SkString.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 /*  Variants for bitmaps
 
@@ -48,7 +48,8 @@
 protected:
     const char* onGetName() override {
         fName.set("bitmap");
-        fName.appendf("_%s%s", sk_tool_utils::colortype_name(fColorType),
+        fName.appendf("_%s%s",
+                      ToolUtils::colortype_name(fColorType),
                       kOpaque_SkAlphaType == fAlphaType ? "" : "_A");
         if (fDoScale) {
             fName.append("_scale");
diff --git a/bench/BlurRoundRectBench.cpp b/bench/BlurRoundRectBench.cpp
index 085a8eb..624c681 100644
--- a/bench/BlurRoundRectBench.cpp
+++ b/bench/BlurRoundRectBench.cpp
@@ -50,8 +50,7 @@
             SkPaint* paint = looperBuilder.addLayerOnTop(info);
             paint->setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle,
                                                         SkBlurMask::ConvertRadiusToSigma(0.5)));
-            paint->setColorFilter(SkColorFilter::MakeModeFilter(SK_ColorLTGRAY,
-                                                                SkBlendMode::kSrcIn));
+            paint->setColorFilter(SkColorFilters::Blend(SK_ColorLTGRAY, SkBlendMode::kSrcIn));
             paint->setColor(SK_ColorGRAY);
         }
         {
diff --git a/bench/ChecksumBench.cpp b/bench/ChecksumBench.cpp
index f84cd3d..685ac20 100644
--- a/bench/ChecksumBench.cpp
+++ b/bench/ChecksumBench.cpp
@@ -53,8 +53,7 @@
                 for (int i = 0; i < loops; i++) {
                     SkMD5 md5;
                     md5.write(fData, sizeof(fData));
-                    SkMD5::Digest digest;
-                    md5.finish(digest);
+                    (void)md5.finish();
                 }
             } break;
             case kHash_ChecksumType: {
diff --git a/bench/ClearBench.cpp b/bench/ClearBench.cpp
index 952e5dd..2fbfd8f 100644
--- a/bench/ClearBench.cpp
+++ b/bench/ClearBench.cpp
@@ -23,7 +23,7 @@
 static sk_sp<SkShader> make_shader() {
     static const SkPoint kPts[] = {{0, 0}, {10, 10}};
     static const SkColor kColors[] = {SK_ColorBLUE, SK_ColorWHITE};
-    return SkGradientShader::MakeLinear(kPts, kColors, nullptr, 2, SkShader::kClamp_TileMode);
+    return SkGradientShader::MakeLinear(kPts, kColors, nullptr, 2, SkTileMode::kClamp);
 }
 
 class ClearBench : public Benchmark {
diff --git a/bench/ClipMaskBench.cpp b/bench/ClipMaskBench.cpp
index b3b98f2..a6e4994 100644
--- a/bench/ClipMaskBench.cpp
+++ b/bench/ClipMaskBench.cpp
@@ -6,16 +6,16 @@
  */
 
 #include "Benchmark.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkColorSpace.h"
 #include "SkImage.h"
 #include "SkPictureRecorder.h"
 #include "SkString.h"
 #include "SkSurface.h"
+#include "ToolUtils.h"
 
 static void DrawMask(SkCanvas* canvas) {
-    sk_tool_utils::draw_checkerboard(canvas, SK_ColorTRANSPARENT, SK_ColorGREEN, 10);
+    ToolUtils::draw_checkerboard(canvas, SK_ColorTRANSPARENT, SK_ColorGREEN, 10);
 }
 
 class ClipMaskBench : public Benchmark {
diff --git a/bench/CodecBench.cpp b/bench/CodecBench.cpp
index 708ab60..2794c4a 100644
--- a/bench/CodecBench.cpp
+++ b/bench/CodecBench.cpp
@@ -7,13 +7,14 @@
 
 #include "CodecBench.h"
 #include "CodecBenchPriv.h"
+#include "CommandLineFlags.h"
 #include "SkBitmap.h"
 #include "SkCodec.h"
-#include "SkCommandLineFlags.h"
 #include "SkOSFile.h"
 
 // Actually zeroing the memory would throw off timing, so we just lie.
-DEFINE_bool(zero_init, false, "Pretend our destination is zero-intialized, simulating Android?");
+static DEFINE_bool(zero_init, false,
+                   "Pretend our destination is zero-intialized, simulating Android?");
 
 CodecBench::CodecBench(SkString baseName, SkData* encoded, SkColorType colorType,
         SkAlphaType alphaType)
diff --git a/bench/ColorCanvasDrawBitmapBench.cpp b/bench/ColorCanvasDrawBitmapBench.cpp
deleted file mode 100644
index a7877f4..0000000
--- a/bench/ColorCanvasDrawBitmapBench.cpp
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
-* Copyright 2017 Google Inc.
-*
-* Use of this source code is governed by a BSD-style license that can be
-* found in the LICENSE file.
-*/
-
-#include "Benchmark.h"
-#include "SkBitmap.h"
-#include "SkCanvas.h"
-#include "SkColorSpaceXformCanvas.h"
-#include "SkString.h"
-
-class ColorCanvasDrawBitmap : public Benchmark {
-public:
-    ColorCanvasDrawBitmap(sk_sp<SkColorSpace> src, sk_sp<SkColorSpace> dst, const char* name)
-        : fDst(dst)
-        , fName(SkStringPrintf("ColorCanvasDrawBitmap_%s", name))
-    {
-        fBitmap.allocPixels(SkImageInfo::MakeN32(100, 100, kOpaque_SkAlphaType, src));
-        fBitmap.eraseColor(SK_ColorBLUE);
-    }
-
-    const char* onGetName() override {
-        return fName.c_str();
-    }
-
-    SkIPoint onGetSize() override {
-        return SkIPoint::Make(100, 100);
-    }
-
-    bool isSuitableFor(Backend backend) override {
-        return kRaster_Backend == backend || kGPU_Backend == backend;
-    }
-
-    void onDraw(int n, SkCanvas* canvas) override {
-        // This simulates an Android app that draws bitmaps to a software canvas.
-        // Chrome and the Android framework both use SkImages exclusively.
-        std::unique_ptr<SkCanvas> colorCanvas = SkCreateColorSpaceXformCanvas(canvas, fDst);
-        for (int i = 0; i < n; i++) {
-            colorCanvas->drawBitmap(fBitmap, 0, 0, nullptr);
-        }
-    }
-
-private:
-    sk_sp<SkColorSpace> fDst;
-    SkString            fName;
-    SkBitmap            fBitmap;
-
-    typedef Benchmark INHERITED;
-};
-
-DEF_BENCH(return new ColorCanvasDrawBitmap(nullptr, SkColorSpace::MakeSRGB(), "null_to_sRGB");)
-DEF_BENCH(return new ColorCanvasDrawBitmap(SkColorSpace::MakeSRGB(), SkColorSpace::MakeSRGB(),
-        "sRGB_to_sRGB");)
-DEF_BENCH(return new ColorCanvasDrawBitmap(
-        SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kAdobeRGB),
-        SkColorSpace::MakeSRGB(), "AdobeRGB_to_sRGB");)
diff --git a/bench/ColorFilterBench.cpp b/bench/ColorFilterBench.cpp
index 54a04d2..46052b3 100644
--- a/bench/ColorFilterBench.cpp
+++ b/bench/ColorFilterBench.cpp
@@ -21,7 +21,7 @@
                             0, 1, 0, 0, amount255,
                             0, 0, 1, 0, amount255,
                             0, 0, 0, 1, 0 };
-    sk_sp<SkColorFilter> filter(SkColorFilter::MakeMatrixFilterRowMajor255(matrix));
+    sk_sp<SkColorFilter> filter(SkColorFilters::MatrixRowMajor255(matrix));
     return SkColorFilterImageFilter::Make(std::move(filter), std::move(input));
 }
 
@@ -32,12 +32,12 @@
     matrix[1] = matrix[6] = matrix[11] = 0.7152f;
     matrix[2] = matrix[7] = matrix[12] = 0.0722f;
     matrix[18] = 1.0f;
-    sk_sp<SkColorFilter> filter(SkColorFilter::MakeMatrixFilterRowMajor255(matrix));
+    sk_sp<SkColorFilter> filter(SkColorFilters::MatrixRowMajor255(matrix));
     return SkColorFilterImageFilter::Make(std::move(filter), std::move(input));
 }
 
 static sk_sp<SkImageFilter> make_mode_blue(sk_sp<SkImageFilter> input) {
-    sk_sp<SkColorFilter> filter(SkColorFilter::MakeModeFilter(SK_ColorBLUE, SkBlendMode::kSrcIn));
+    sk_sp<SkColorFilter> filter(SkColorFilters::Blend(SK_ColorBLUE, SkBlendMode::kSrcIn));
     return SkColorFilterImageFilter::Make(std::move(filter), std::move(input));
 }
 
diff --git a/bench/ColorSpaceXformBench.cpp b/bench/ColorSpaceXformBench.cpp
deleted file mode 100644
index b7b6b7d68..0000000
--- a/bench/ColorSpaceXformBench.cpp
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
-* Copyright 2017 Google Inc.
-*
-* Use of this source code is governed by a BSD-style license that can be
-* found in the LICENSE file.
-*/
-
-#include "Benchmark.h"
-#include "SkColor.h"
-#include "SkColorSpaceXformer.h"
-#include "SkColorSpaceXformSteps.h"
-#include "SkMakeUnique.h"
-#include "SkRandom.h"
-
-enum class Mode { steps, xformer };
-
-struct ColorSpaceXformBench : public Benchmark {
-    ColorSpaceXformBench(Mode mode) : fMode(mode) {}
-
-    const Mode fMode;
-
-    std::unique_ptr<SkColorSpaceXformSteps>  fSteps;
-    std::unique_ptr<SkColorSpaceXformer>     fXformer;
-
-    const char* onGetName() override {
-        switch (fMode) {
-            case Mode::steps  : return "ColorSpaceXformBench_steps";
-            case Mode::xformer: return "ColorSpaceXformBench_xformer";
-        }
-        return "";
-    }
-
-    bool isSuitableFor(Backend backend) override { return kNonRendering_Backend == backend; }
-
-    void onDelayedSetup() override {
-        sk_sp<SkColorSpace> src = SkColorSpace::MakeSRGB(),
-                            dst = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB,
-                                                        SkNamedGamut::kDCIP3);
-
-        fSteps = skstd::make_unique<SkColorSpaceXformSteps>(src.get(), kOpaque_SkAlphaType,
-                                                            dst.get(), kPremul_SkAlphaType);
-        fXformer = SkColorSpaceXformer::Make(dst);  // src is implicitly sRGB, what we want anyway
-    }
-
-    void onDraw(int n, SkCanvas* canvas) override {
-        volatile SkColor junk = 0;
-        SkRandom rand;
-
-        for (int i = 0; i < n; i++) {
-            SkColor src = rand.nextU(),
-                    dst;
-            switch (fMode) {
-                case Mode::steps: {
-                    SkColor4f rgba = SkColor4f::FromColor(src);
-                    fSteps->apply(rgba.vec());
-                    dst = rgba.toSkColor();
-                } break;
-
-                case Mode::xformer: {
-                    dst = fXformer->apply(src);
-                } break;
-            }
-
-            if (false && i == 0) {
-                SkDebugf("%x ~~> %x\n", src, dst);
-            }
-
-            junk ^= dst;
-        }
-    }
-};
-
-DEF_BENCH(return new ColorSpaceXformBench{Mode::steps  };)
-DEF_BENCH(return new ColorSpaceXformBench{Mode::xformer};)
diff --git a/bench/DashBench.cpp b/bench/DashBench.cpp
index ba1d66a..7c3df8a 100644
--- a/bench/DashBench.cpp
+++ b/bench/DashBench.cpp
@@ -144,8 +144,7 @@
     path->moveTo(0, -SK_Scalar1);
     for (int i = 1; i < n; i++) {
         rad += drad;
-        SkScalar cosV, sinV = SkScalarSinCos(rad, &cosV);
-        path->lineTo(cosV, sinV);
+        path->lineTo(SkScalarCos(rad), SkScalarSin(rad));
     }
     path->close();
 }
diff --git a/bench/DisplacementBench.cpp b/bench/DisplacementBench.cpp
index 37d53d1..97c008a 100644
--- a/bench/DisplacementBench.cpp
+++ b/bench/DisplacementBench.cpp
@@ -32,7 +32,7 @@
 
     void makeBitmap() {
         const int w = this->isSmall() ? FILTER_WIDTH_SMALL : FILTER_WIDTH_LARGE;
-        const int h = this->isSmall() ? FILTER_HEIGHT_LARGE : FILTER_HEIGHT_LARGE;
+        const int h = this->isSmall() ? FILTER_HEIGHT_SMALL : FILTER_HEIGHT_LARGE;
         fBitmap.allocN32Pixels(w, h);
         SkCanvas canvas(fBitmap);
         canvas.clear(0x00000000);
@@ -46,7 +46,7 @@
 
     void makeCheckerboard() {
         const int w = this->isSmall() ? FILTER_WIDTH_SMALL : FILTER_WIDTH_LARGE;
-        const int h = this->isSmall() ? FILTER_HEIGHT_LARGE : FILTER_HEIGHT_LARGE;
+        const int h = this->isSmall() ? FILTER_HEIGHT_SMALL : FILTER_HEIGHT_LARGE;
         auto surface(SkSurface::MakeRasterN32Premul(w, h));
         SkCanvas* canvas = surface->getCanvas();
         canvas->clear(0x00000000);
diff --git a/bench/GameBench.cpp b/bench/GameBench.cpp
index 4d34fc1..4096e0d 100644
--- a/bench/GameBench.cpp
+++ b/bench/GameBench.cpp
@@ -141,9 +141,7 @@
         SkPaint p2;         // for drawVertices path
         p2.setColor(0xFF000000);
         p2.setFilterQuality(kLow_SkFilterQuality);
-        p2.setShader(SkShader::MakeBitmapShader(fAtlas,
-                                                SkShader::kClamp_TileMode,
-                                                SkShader::kClamp_TileMode));
+        p2.setShader(fAtlas.makeShader());
 
         for (int i = 0; i < loops; ++i, ++fNumSaved) {
             if (0 == i % kNumBeforeClear) {
diff --git a/bench/GradientBench.cpp b/bench/GradientBench.cpp
index e6c02d3..4f28b6e 100644
--- a/bench/GradientBench.cpp
+++ b/bench/GradientBench.cpp
@@ -48,12 +48,12 @@
 
 /// Ignores scale
 static sk_sp<SkShader> MakeLinear(const SkPoint pts[2], const GradData& data,
-                                  SkShader::TileMode tm, float scale) {
+                                  SkTileMode tm, float scale) {
     return SkGradientShader::MakeLinear(pts, data.fColors, data.fPos, data.fCount, tm);
 }
 
 static sk_sp<SkShader> MakeRadial(const SkPoint pts[2], const GradData& data,
-                                  SkShader::TileMode tm, float scale) {
+                                  SkTileMode tm, float scale) {
     SkPoint center;
     center.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
@@ -63,7 +63,7 @@
 
 /// Ignores scale
 static sk_sp<SkShader> MakeSweep(const SkPoint pts[2], const GradData& data,
-                                 SkShader::TileMode tm, float scale) {
+                                 SkTileMode tm, float scale) {
     SkPoint center;
     center.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
@@ -72,7 +72,7 @@
 
 /// Ignores scale
 static sk_sp<SkShader> MakeConical(const SkPoint pts[2], const GradData& data,
-                                   SkShader::TileMode tm, float scale) {
+                                   SkTileMode tm, float scale) {
     SkPoint center0, center1;
     center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                 SkScalarAve(pts[0].fY, pts[1].fY));
@@ -85,7 +85,7 @@
 
 /// Ignores scale
 static sk_sp<SkShader> MakeConicalZeroRad(const SkPoint pts[2], const GradData& data,
-                                          SkShader::TileMode tm, float scale) {
+                                          SkTileMode tm, float scale) {
     SkPoint center0, center1;
     center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                 SkScalarAve(pts[0].fY, pts[1].fY));
@@ -98,7 +98,7 @@
 
 /// Ignores scale
 static sk_sp<SkShader> MakeConicalOutside(const SkPoint pts[2], const GradData& data,
-                                          SkShader::TileMode tm, float scale) {
+                                          SkTileMode tm, float scale) {
     SkPoint center0, center1;
     SkScalar radius0 = (pts[1].fX - pts[0].fX) / 10;
     SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
@@ -112,7 +112,7 @@
 
 /// Ignores scale
 static sk_sp<SkShader> MakeConicalOutsideZeroRad(const SkPoint pts[2], const GradData& data,
-                                                 SkShader::TileMode tm, float scale) {
+                                                 SkTileMode tm, float scale) {
     SkPoint center0, center1;
     SkScalar radius0 = (pts[1].fX - pts[0].fX) / 10;
     SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
@@ -125,7 +125,7 @@
 }
 
 typedef sk_sp<SkShader> (*GradMaker)(const SkPoint pts[2], const GradData& data,
-                                     SkShader::TileMode tm, float scale);
+                                     SkTileMode tm, float scale);
 
 static const struct {
     GradMaker   fMaker;
@@ -155,18 +155,18 @@
     kOval_GeomType
 };
 
-static const char* tilemodename(SkShader::TileMode tm) {
+static const char* tilemodename(SkTileMode tm) {
     switch (tm) {
-        case SkShader::kClamp_TileMode:
+        case SkTileMode::kClamp:
             return "clamp";
-        case SkShader::kRepeat_TileMode:
+        case SkTileMode::kRepeat:
             return "repeat";
-        case SkShader::kMirror_TileMode:
+        case SkTileMode::kMirror:
             return "mirror";
-        default:
-            SkDEBUGFAIL("unknown tilemode");
-            return "error";
+        case SkTileMode::kDecal:
+            return "decal";
     }
+    return "";
 }
 
 static const char* geomtypename(GeomType gt) {
@@ -187,7 +187,7 @@
 public:
     GradientBench(GradType gradType,
                   GradData data = gGradData[0],
-                  SkShader::TileMode tm = SkShader::kClamp_TileMode,
+                  SkTileMode tm = SkTileMode::kClamp,
                   GeomType geomType = kRect_GeomType,
                   float scale = 1.0f)
         : fGeomType(geomType) {
@@ -211,7 +211,7 @@
     GradientBench(GradType gradType, GradData data, bool dither)
         : fGeomType(kRect_GeomType) {
 
-        const char *tmname = tilemodename(SkShader::kClamp_TileMode);
+        const char *tmname = tilemodename(SkTileMode::kClamp);
         fName.printf("gradient_%s_%s", gGrads[gradType].fName, tmname);
         fName.append(data.fName);
 
@@ -220,7 +220,7 @@
         }
 
         this->setupPaint(&fPaint);
-        fPaint.setShader(MakeShader(gradType, data, SkShader::kClamp_TileMode, 1.0f));
+        fPaint.setShader(MakeShader(gradType, data, SkTileMode::kClamp, 1.0f));
         fPaint.setDither(dither);
     }
 
@@ -252,7 +252,7 @@
     typedef Benchmark INHERITED;
 
     sk_sp<SkShader> MakeShader(GradType gradType, GradData data,
-                               SkShader::TileMode tm, float scale) {
+                               SkTileMode tm, float scale) {
         const SkPoint pts[2] = {
             { 0, 0 },
             { SkIntToScalar(kSize), SkIntToScalar(kSize) }
@@ -272,26 +272,26 @@
 DEF_BENCH( return new GradientBench(kLinear_GradType, gGradData[1]); )
 DEF_BENCH( return new GradientBench(kLinear_GradType, gGradData[2]); )
 DEF_BENCH( return new GradientBench(kLinear_GradType, gGradData[4]); )
-DEF_BENCH( return new GradientBench(kLinear_GradType, gGradData[0], SkShader::kRepeat_TileMode); )
-DEF_BENCH( return new GradientBench(kLinear_GradType, gGradData[1], SkShader::kRepeat_TileMode); )
-DEF_BENCH( return new GradientBench(kLinear_GradType, gGradData[2], SkShader::kRepeat_TileMode); )
-DEF_BENCH( return new GradientBench(kLinear_GradType, gGradData[0], SkShader::kMirror_TileMode); )
-DEF_BENCH( return new GradientBench(kLinear_GradType, gGradData[1], SkShader::kMirror_TileMode); )
-DEF_BENCH( return new GradientBench(kLinear_GradType, gGradData[2], SkShader::kMirror_TileMode); )
+DEF_BENCH( return new GradientBench(kLinear_GradType, gGradData[0], SkTileMode::kRepeat); )
+DEF_BENCH( return new GradientBench(kLinear_GradType, gGradData[1], SkTileMode::kRepeat); )
+DEF_BENCH( return new GradientBench(kLinear_GradType, gGradData[2], SkTileMode::kRepeat); )
+DEF_BENCH( return new GradientBench(kLinear_GradType, gGradData[0], SkTileMode::kMirror); )
+DEF_BENCH( return new GradientBench(kLinear_GradType, gGradData[1], SkTileMode::kMirror); )
+DEF_BENCH( return new GradientBench(kLinear_GradType, gGradData[2], SkTileMode::kMirror); )
 
 DEF_BENCH( return new GradientBench(kRadial_GradType, gGradData[0]); )
 DEF_BENCH( return new GradientBench(kRadial_GradType, gGradData[1]); )
 DEF_BENCH( return new GradientBench(kRadial_GradType, gGradData[2]); )
 // Draw a radial gradient of radius 1/2 on a rectangle; half the lines should
 // be completely pinned, the other half should pe partially pinned
-DEF_BENCH( return new GradientBench(kRadial_GradType, gGradData[0], SkShader::kClamp_TileMode, kRect_GeomType, 0.5f); )
+DEF_BENCH( return new GradientBench(kRadial_GradType, gGradData[0], SkTileMode::kClamp, kRect_GeomType, 0.5f); )
 
 // Draw a radial gradient on a circle of equal size; all the lines should
 // hit the unpinned fast path (so long as GradientBench.W == H)
-DEF_BENCH( return new GradientBench(kRadial_GradType, gGradData[0], SkShader::kClamp_TileMode, kOval_GeomType); )
+DEF_BENCH( return new GradientBench(kRadial_GradType, gGradData[0], SkTileMode::kClamp, kOval_GeomType); )
 
-DEF_BENCH( return new GradientBench(kRadial_GradType, gGradData[0], SkShader::kMirror_TileMode); )
-DEF_BENCH( return new GradientBench(kRadial_GradType, gGradData[0], SkShader::kRepeat_TileMode); )
+DEF_BENCH( return new GradientBench(kRadial_GradType, gGradData[0], SkTileMode::kMirror); )
+DEF_BENCH( return new GradientBench(kRadial_GradType, gGradData[0], SkTileMode::kRepeat); )
 DEF_BENCH( return new GradientBench(kSweep_GradType); )
 DEF_BENCH( return new GradientBench(kSweep_GradType, gGradData[1]); )
 DEF_BENCH( return new GradientBench(kSweep_GradType, gGradData[2]); )
@@ -354,7 +354,7 @@
                 SK_ColorWHITE };
             paint.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr,
                                                          SK_ARRAY_COUNT(colors),
-                                                         SkShader::kClamp_TileMode));
+                                                         SkTileMode::kClamp));
             canvas->drawRect(r, paint);
         }
     }
diff --git a/bench/HardStopGradientBench_ScaleNumColors.cpp b/bench/HardStopGradientBench_ScaleNumColors.cpp
index cedbd72..b1f6267 100644
--- a/bench/HardStopGradientBench_ScaleNumColors.cpp
+++ b/bench/HardStopGradientBench_ScaleNumColors.cpp
@@ -14,23 +14,23 @@
 #include "SkColor.h"
 #include "SkPaint.h"
 
-static const char* get_tilemode_name(SkShader::TileMode tilemode) {
+static const char* get_tilemode_name(SkTileMode tilemode) {
     switch (tilemode) {
-        case SkShader::kClamp_TileMode:
+        case SkTileMode::kClamp:
             return "clamp";
-        case SkShader::kRepeat_TileMode:
+        case SkTileMode::kRepeat:
             return "repeat";
-        case SkShader::kMirror_TileMode:
+        case SkTileMode::kMirror:
             return "mirror";
-        default:
-            SkDEBUGFAIL("Unknown tilemode");
-            return "error";
+        case SkTileMode::kDecal:
+            return "decal";
     }
+    return "";
 }
 
 class HardStopGradientBench_ScaleNumColors : public Benchmark {
 public:
-    HardStopGradientBench_ScaleNumColors(SkShader::TileMode tilemode, int count) {
+    HardStopGradientBench_ScaleNumColors(SkTileMode tilemode, int count) {
         fName.printf("hardstop_scale_num_colors_%s_%03d_colors", get_tilemode_name(tilemode), count);
 
         fTileMode   = tilemode;
@@ -103,37 +103,37 @@
 private:
     static const int kSize = 500;
 
-    SkShader::TileMode  fTileMode;
-    SkString            fName;
-    int                 fColorCount;
-    SkPaint             fPaint;
+    SkTileMode  fTileMode;
+    SkString    fName;
+    int         fColorCount;
+    SkPaint     fPaint;
 
     typedef Benchmark INHERITED;
 };
 
 // Clamp
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kClamp_TileMode,   3);)
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kClamp_TileMode,   4);)
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kClamp_TileMode,   5);)
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kClamp_TileMode,  10);)
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kClamp_TileMode,  25);)
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kClamp_TileMode,  50);)
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kClamp_TileMode, 100);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kClamp,   3);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kClamp,   4);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kClamp,   5);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kClamp,  10);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kClamp,  25);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kClamp,  50);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kClamp, 100);)
 
 // Repeat
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kRepeat_TileMode,   3);)
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kRepeat_TileMode,   4);)
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kRepeat_TileMode,   5);)
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kRepeat_TileMode,  10);)
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kRepeat_TileMode,  25);)
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kRepeat_TileMode,  50);)
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kRepeat_TileMode, 100);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kRepeat,   3);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kRepeat,   4);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kRepeat,   5);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kRepeat,  10);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kRepeat,  25);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kRepeat,  50);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kRepeat, 100);)
 
 // Mirror
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kMirror_TileMode,   3);)
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kMirror_TileMode,   4);)
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kMirror_TileMode,   5);)
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kMirror_TileMode,  10);)
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kMirror_TileMode,  25);)
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kMirror_TileMode,  50);)
-DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkShader::kMirror_TileMode, 100);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kMirror,   3);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kMirror,   4);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kMirror,   5);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kMirror,  10);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kMirror,  25);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kMirror,  50);)
+DEF_BENCH(return new HardStopGradientBench_ScaleNumColors(SkTileMode::kMirror, 100);)
diff --git a/bench/HardStopGradientBench_ScaleNumHardStops.cpp b/bench/HardStopGradientBench_ScaleNumHardStops.cpp
index 843bb72..b1ea306 100644
--- a/bench/HardStopGradientBench_ScaleNumHardStops.cpp
+++ b/bench/HardStopGradientBench_ScaleNumHardStops.cpp
@@ -72,7 +72,7 @@
                                                       colors.get(),
                                                       positions.get(),
                                                       fColorCount,
-                                                      SkShader::kClamp_TileMode,
+                                                      SkTileMode::kClamp,
                                                       0,
                                                       nullptr));
     }
diff --git a/bench/HardStopGradientBench_SpecialHardStops.cpp b/bench/HardStopGradientBench_SpecialHardStops.cpp
index e987901..607080a 100644
--- a/bench/HardStopGradientBench_SpecialHardStops.cpp
+++ b/bench/HardStopGradientBench_SpecialHardStops.cpp
@@ -93,7 +93,7 @@
                                                       colors,
                                                       positions,
                                                       count,
-                                                      SkShader::kClamp_TileMode,
+                                                      SkTileMode::kClamp,
                                                       0,
                                                       nullptr));
     }
diff --git a/bench/ImageCacheBudgetBench.cpp b/bench/ImageCacheBudgetBench.cpp
index 518713d..fb714ba 100644
--- a/bench/ImageCacheBudgetBench.cpp
+++ b/bench/ImageCacheBudgetBench.cpp
@@ -6,10 +6,10 @@
  */
 
 #include "Benchmark.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkImage.h"
 #include "SkSurface.h"
+#include "ToolUtils.h"
 
 #include "GrContext.h"
 #include "GrContextPriv.h"
@@ -27,8 +27,8 @@
 
 static void make_images(sk_sp<SkImage> imgs[], int cnt) {
     for (int i = 0; i < cnt; ++i) {
-        SkBitmap bmp = sk_tool_utils::create_checkerboard_bitmap(kS, kS, SK_ColorBLACK,
-                                                                 SK_ColorCYAN, 10);
+        SkBitmap bmp =
+                ToolUtils::create_checkerboard_bitmap(kS, kS, SK_ColorBLACK, SK_ColorCYAN, 10);
         imgs[i] = SkImage::MakeFromBitmap(bmp);
     }
 }
diff --git a/bench/ImageFilterCollapse.cpp b/bench/ImageFilterCollapse.cpp
index ce27f06..3562766 100644
--- a/bench/ImageFilterCollapse.cpp
+++ b/bench/ImageFilterCollapse.cpp
@@ -62,7 +62,7 @@
             SK_ColorRED, 0, SK_ColorBLUE, SK_ColorWHITE
         };
         paint.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, SK_ARRAY_COUNT(colors),
-                                                     SkShader::kClamp_TileMode));
+                                                     SkTileMode::kClamp));
         canvas.drawPaint(paint);
     }
 };
@@ -103,7 +103,7 @@
                             0, 1, 0, 0, amount255,
                             0, 0, 1, 0, amount255,
                             0, 0, 0, 1, 0 };
-    return SkColorFilter::MakeMatrixFilterRowMajor255(matrix);
+    return SkColorFilters::MatrixRowMajor255(matrix);
 }
 
 static sk_sp<SkColorFilter> make_grayscale() {
@@ -113,7 +113,7 @@
     matrix[1] = matrix[6] = matrix[11] = 0.7152f;
     matrix[2] = matrix[7] = matrix[12] = 0.0722f;
     matrix[18] = 1.0f;
-    return SkColorFilter::MakeMatrixFilterRowMajor255(matrix);
+    return SkColorFilters::MatrixRowMajor255(matrix);
 }
 
 class MatrixCollapseBench: public BaseImageFilterCollapseBench {
diff --git a/bench/MagnifierBench.cpp b/bench/MagnifierBench.cpp
index 6403aa6..d345c63 100644
--- a/bench/MagnifierBench.cpp
+++ b/bench/MagnifierBench.cpp
@@ -53,7 +53,7 @@
 private:
     void make_checkerboard() {
         const int w = fIsSmall ? FILTER_WIDTH_SMALL : FILTER_WIDTH_LARGE;
-        const int h = fIsSmall ? FILTER_HEIGHT_LARGE : FILTER_HEIGHT_LARGE;
+        const int h = fIsSmall ? FILTER_HEIGHT_SMALL : FILTER_HEIGHT_LARGE;
         fCheckerboard.allocN32Pixels(w, h);
         SkCanvas canvas(fCheckerboard);
         canvas.clear(0x00000000);
diff --git a/bench/PDFBench.cpp b/bench/PDFBench.cpp
index 7720e21..c3521b3 100644
--- a/bench/PDFBench.cpp
+++ b/bench/PDFBench.cpp
@@ -210,7 +210,7 @@
         };
         fShader = SkGradientShader::MakeLinear(
                 pts, colors, nullptr, SK_ARRAY_COUNT(colors),
-                SkShader::kClamp_TileMode);
+                SkTileMode::kClamp);
     }
     void onDraw(int loops, SkCanvas*) final {
         SkASSERT(fShader);
@@ -243,6 +243,64 @@
     }
 };
 
+// Test for regression chromium:947381
+// with    5c83ae81aa :   2364.99 microsec
+// without 5c83ae81aa : 302821.78 microsec
+struct PDFClipPathBenchmark : public Benchmark {
+    SkPath fPath;
+    void onDelayedSetup() override {
+        SkBitmap bitmap;
+        bitmap.allocN32Pixels(256, 256);
+        bitmap.eraseColor(SK_ColorWHITE);
+        {
+            SkCanvas tmp(bitmap);
+            SkPaint paint;
+            paint.setAntiAlias(false);
+            paint.setStyle(SkPaint::kStroke_Style);
+            paint.setStrokeWidth(10);
+            for (int r : {20, 40, 60, 80, 100, 120}) {
+                tmp.drawCircle(128, 128, (float)r, paint);
+            }
+        }
+        fPath.reset();
+        for (int y = 0; y < 256; ++y) {
+            SkColor current = bitmap.getColor(0, y);
+            int start = 0;
+            for (int x = 0; x < 256; ++x) {
+                SkColor color = bitmap.getColor(x, y);
+                if (color == current) {
+                    continue;
+                }
+                if (color == SK_ColorBLACK) {
+                    start = x;
+                } else {
+                    fPath.addRect(SkRect::Make(SkIRect{start, y, x, y + 1}));
+                }
+                current = color;
+            }
+            if (current == SK_ColorBLACK) {
+                fPath.addRect(SkRect::Make(SkIRect{start, y, 256, y + 1}));
+            }
+        }
+    }
+    const char* onGetName() override { return "PDFClipPath"; }
+    bool isSuitableFor(Backend backend) override {
+        return backend == kNonRendering_Backend;
+    }
+    void onDraw(int loops, SkCanvas*) override {
+        while (loops-- > 0) {
+            SkNullWStream wStream;
+            SkPDFDocument doc(&wStream, SkPDF::Metadata());
+            SkCanvas* canvas = doc.beginPage(256, 256);
+            canvas->clipPath(fPath);
+            canvas->translate(4.0f/3, 4.0f/3);
+            canvas->clipPath(fPath);
+            canvas->clear(SK_ColorRED);
+            doc.endPage();
+        }
+    }
+};
+
 }  // namespace
 DEF_BENCH(return new PDFImageBench;)
 DEF_BENCH(return new PDFJpegImageBench;)
@@ -250,6 +308,7 @@
 DEF_BENCH(return new PDFColorComponentBench;)
 DEF_BENCH(return new PDFShaderBench;)
 DEF_BENCH(return new WritePDFTextBenchmark;)
+DEF_BENCH(return new PDFClipPathBenchmark;)
 
 #ifdef SK_PDF_ENABLE_SLOW_TESTS
 #include "SkExecutor.h"
diff --git a/bench/PatchBench.cpp b/bench/PatchBench.cpp
index 375560e..064db69 100644
--- a/bench/PatchBench.cpp
+++ b/bench/PatchBench.cpp
@@ -77,7 +77,7 @@
         const SkPoint pts[] = { { 200.f / 4.f, 0.f }, { 3.f * 200.f / 4, 200.f } };
 
         return SkGradientShader::MakeLinear(pts, colors, nullptr, SK_ARRAY_COUNT(colors),
-                                            SkShader::kMirror_TileMode);
+                                            SkTileMode::kMirror);
     }
 
 protected:
diff --git a/bench/PathTextBench.cpp b/bench/PathTextBench.cpp
index 3dc31c9..a9b845d 100644
--- a/bench/PathTextBench.cpp
+++ b/bench/PathTextBench.cpp
@@ -12,7 +12,7 @@
 #include "SkRandom.h"
 #include "SkStrike.h"
 #include "SkStrikeCache.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 static constexpr int kScreenWidth = 1500;
 static constexpr int kScreenHeight = 1500;
@@ -76,7 +76,7 @@
         }
 
         if (fClipped) {
-            fClipPath = sk_tool_utils::make_star(SkRect::MakeIWH(kScreenWidth,kScreenHeight), 11,3);
+            fClipPath = ToolUtils::make_star(SkRect::MakeIWH(kScreenWidth, kScreenHeight), 11, 3);
             fClipPath.setIsVolatile(fUncached);
         }
     }
diff --git a/bench/PolyUtilsBench.cpp b/bench/PolyUtilsBench.cpp
index a42ea70..5cbff93 100644
--- a/bench/PolyUtilsBench.cpp
+++ b/bench/PolyUtilsBench.cpp
@@ -115,11 +115,9 @@
         SkScalar rad = 0;
         const SkScalar drad = SK_ScalarPI / n;
         for (int i = 0; i < n; i++) {
-            SkScalar cosV, sinV = SkScalarSinCos(rad, &cosV);
-            *poly->push() = SkPoint::Make(c + cosV * r1, c + sinV * r1);
+            *poly->push() = SkPoint::Make(c + SkScalarCos(rad) * r1, c + SkScalarSin(rad) * r1);
             rad += drad;
-            sinV = SkScalarSinCos(rad, &cosV);
-            *poly->push() = SkPoint::Make(c + cosV * r2, c + sinV * r2);
+            *poly->push() = SkPoint::Make(c + SkScalarCos(rad) * r2, c + SkScalarSin(rad) * r2);
             rad += drad;
         }
     }
@@ -142,8 +140,7 @@
         SkScalar rad = 0;
         const SkScalar drad = 2 * SK_ScalarPI / n;
         for (int i = 0; i < n; i++) {
-            SkScalar cosV, sinV = SkScalarSinCos(rad, &cosV);
-            *poly->push() = SkPoint::Make(c + cosV * r, c + sinV * r);
+            *poly->push() = SkPoint::Make(c + SkScalarCos(rad) * r, c + SkScalarSin(rad) * r);
             rad += drad;
         }
     }
@@ -169,8 +166,7 @@
         *poly->push() = SkPoint::Make(c, c - r);
         for (int i = 1; i < n; i++) {
             rad += drad;
-            SkScalar cosV, sinV = SkScalarSinCos(rad, &cosV);
-            *poly->push() = SkPoint::Make(c + cosV * r, c + sinV * r);
+            *poly->push() = SkPoint::Make(c + SkScalarCos(rad) * r, c + SkScalarSin(rad) * r);
         }
     }
 private:
@@ -193,8 +189,7 @@
         SkScalar rad = 0;
         const SkScalar drad = 3 * SK_ScalarPI / (2*n);
         for (int i = 0; i < n; i++) {
-            SkScalar cosV, sinV = SkScalarSinCos(rad, &cosV);
-            *poly->push() = SkPoint::Make(c + cosV * r, c + sinV * r);
+            *poly->push() = SkPoint::Make(c + SkScalarCos(rad) * r, c + SkScalarSin(rad) * r);
             rad += drad;
         }
         // and the mouth
@@ -219,8 +214,7 @@
         SkScalar rad = 0;
         const SkScalar drad = 3 * SK_ScalarPI / (2*n);
         for (int i = 0; i < n; i++) {
-            SkScalar cosV, sinV = SkScalarSinCos(rad, &cosV);
-            *poly->push() = SkPoint::Make(c + cosV * r, c + sinV * r);
+            *poly->push() = SkPoint::Make(c + SkScalarCos(rad) * r, c + SkScalarSin(rad) * r);
             rad += drad;
         }
         // and the tip of the cone
diff --git a/bench/PremulAndUnpremulAlphaOpsBench.cpp b/bench/PremulAndUnpremulAlphaOpsBench.cpp
index 89c9bd5..22467ec 100644
--- a/bench/PremulAndUnpremulAlphaOpsBench.cpp
+++ b/bench/PremulAndUnpremulAlphaOpsBench.cpp
@@ -8,7 +8,7 @@
 #include "Benchmark.h"
 #include "SkCanvas.h"
 #include "SkString.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 class PremulAndUnpremulAlphaOpsBench : public Benchmark {
     enum {
@@ -20,7 +20,7 @@
 public:
     PremulAndUnpremulAlphaOpsBench(SkColorType ct) {
         fColorType = ct;
-        fName.printf("premul_and_unpremul_alpha_%s", sk_tool_utils::colortype_name(ct));
+        fName.printf("premul_and_unpremul_alpha_%s", ToolUtils::colortype_name(ct));
     }
 
 protected:
diff --git a/bench/ReadPixBench.cpp b/bench/ReadPixBench.cpp
index 1c1ce04..35e536d 100644
--- a/bench/ReadPixBench.cpp
+++ b/bench/ReadPixBench.cpp
@@ -8,61 +8,58 @@
 #include "Benchmark.h"
 #include "SkBitmap.h"
 #include "SkCanvas.h"
+#include "SkColorSpace.h"
 
-
-/**
- * This bench mark tests the use case where the user writes the a canvas
- * and then reads small chunks from it repeatedly. This can cause trouble
- * for the GPU as readbacks are very expensive.
- */
+// Time variants of read-pixels
+//  [ colortype ][ alphatype ][ colorspace ]
+//  Different combinations can trigger fast or slow paths in the impls
+//
 class ReadPixBench : public Benchmark {
 public:
-    ReadPixBench() {}
+    ReadPixBench(SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs)
+        : fCT(ct), fAT(at), fCS(cs)
+    {
+        fName.printf("readpix_%s_%s_%s",
+                     at == kPremul_SkAlphaType ? "pm" : "um",
+                     ct == kRGBA_8888_SkColorType ? "rgba" : "bgra",
+                     cs ? "srgb" : "null");
+    }
 
 protected:
     const char* onGetName() override {
-        return "readpix";
+        return fName.c_str();
     }
 
     void onDraw(int loops, SkCanvas* canvas) override {
-        canvas->clear(SK_ColorBLACK);
+        canvas->clear(0x80000000);
 
         SkISize size = canvas->getBaseLayerSize();
 
-        int offX = (size.width() - kWindowSize) / kNumStepsX;
-        int offY = (size.height() - kWindowSize) / kNumStepsY;
-
-        SkPaint paint;
-
-        paint.setColor(SK_ColorBLUE);
-
-        canvas->drawCircle(SkIntToScalar(size.width()/2),
-                           SkIntToScalar(size.height()/2),
-                           SkIntToScalar(size.width()/2),
-                           paint);
-
+        auto info = SkImageInfo::Make(size.width(), size.height(), fCT, fAT, fCS);
         SkBitmap bitmap;
-
-        bitmap.allocPixels(SkImageInfo::MakeN32Premul(kWindowSize, kWindowSize));
+        bitmap.allocPixels(info);
 
         for (int i = 0; i < loops; i++) {
-            for (int x = 0; x < kNumStepsX; ++x) {
-                for (int y = 0; y < kNumStepsY; ++y) {
-                    canvas->readPixels(bitmap.info(), bitmap.getPixels(), bitmap.rowBytes(),
-                                       x * offX, y * offY);
-                }
-            }
+            canvas->readPixels(bitmap.info(), bitmap.getPixels(), bitmap.rowBytes(), 0, 0);
         }
     }
 
 private:
-    static const int kNumStepsX = 30;
-    static const int kNumStepsY = 30;
-    static const int kWindowSize = 5;
-
+    SkColorType fCT;
+    SkAlphaType fAT;
+    sk_sp<SkColorSpace> fCS;
+    SkString fName;
     typedef Benchmark INHERITED;
 };
-DEF_BENCH( return new ReadPixBench(); )
+DEF_BENCH( return new ReadPixBench(kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr); )
+DEF_BENCH( return new ReadPixBench(kRGBA_8888_SkColorType, kUnpremul_SkAlphaType, nullptr); )
+DEF_BENCH( return new ReadPixBench(kRGBA_8888_SkColorType, kPremul_SkAlphaType, SkColorSpace::MakeSRGB()); )
+DEF_BENCH( return new ReadPixBench(kRGBA_8888_SkColorType, kUnpremul_SkAlphaType, SkColorSpace::MakeSRGB()); )
+
+DEF_BENCH( return new ReadPixBench(kBGRA_8888_SkColorType, kPremul_SkAlphaType, nullptr); )
+DEF_BENCH( return new ReadPixBench(kBGRA_8888_SkColorType, kUnpremul_SkAlphaType, nullptr); )
+DEF_BENCH( return new ReadPixBench(kBGRA_8888_SkColorType, kPremul_SkAlphaType, SkColorSpace::MakeSRGB()); )
+DEF_BENCH( return new ReadPixBench(kBGRA_8888_SkColorType, kUnpremul_SkAlphaType, SkColorSpace::MakeSRGB()); )
 
 ////////////////////////////////////////////////////////////////////////////////
 #include "SkBitmap.h"
diff --git a/bench/RectBench.cpp b/bench/RectBench.cpp
index f15c571..0c30dc0 100644
--- a/bench/RectBench.cpp
+++ b/bench/RectBench.cpp
@@ -6,16 +6,16 @@
  */
 
 #include "Benchmark.h"
+#include "CommandLineFlags.h"
 #include "SkBitmap.h"
 #include "SkCanvas.h"
-#include "SkCommandLineFlags.h"
 #include "SkGradientShader.h"
 #include "SkPaint.h"
 #include "SkRandom.h"
 #include "SkShader.h"
 #include "SkString.h"
 
-DEFINE_double(strokeWidth, -1.0, "If set, use this stroke width in RectBench.");
+static DEFINE_double(strokeWidth, -1.0, "If set, use this stroke width in RectBench.");
 
 class RectBench : public Benchmark {
 public:
@@ -171,7 +171,7 @@
         // Create the shader once, so that isn't included in the timing
         SkPoint pts[2] = { {0.f, 0.f}, {50.f, 50.f} };
         SkColor colors[] = { SK_ColorWHITE, SK_ColorBLUE };
-        fShader = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode);
+        fShader = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
     }
 
     void setupPaint(SkPaint* paint) override {
@@ -295,8 +295,7 @@
             srcBM.allocN32Pixels(10, 1);
             srcBM.eraseColor(0xFF00FF00);
 
-            paint.setShader(SkShader::MakeBitmapShader(srcBM, SkShader::kClamp_TileMode,
-                                                       SkShader::kClamp_TileMode));
+            paint.setShader(srcBM.makeShader());
         }
         for (int loop = 0; loop < loops; loop++) {
             for (size_t i = 0; i < sizes; i++) {
diff --git a/bench/RepeatTileBench.cpp b/bench/RepeatTileBench.cpp
index 83ba3f1..adec4f4 100644
--- a/bench/RepeatTileBench.cpp
+++ b/bench/RepeatTileBench.cpp
@@ -11,7 +11,7 @@
 #include "SkPaint.h"
 #include "SkShader.h"
 #include "SkString.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 static void draw_into_bitmap(const SkBitmap& bm) {
     const int w = bm.width();
@@ -44,7 +44,8 @@
 
         fBitmap.setInfo(SkImageInfo::Make(w, h, ct, at));
         fName.printf("repeatTile_%s_%c",
-                     sk_tool_utils::colortype_name(ct), kOpaque_SkAlphaType == at ? 'X' : 'A');
+                     ToolUtils::colortype_name(ct),
+                     kOpaque_SkAlphaType == at ? 'X' : 'A');
     }
 
 protected:
@@ -58,9 +59,7 @@
 
         draw_into_bitmap(fBitmap);
 
-        fPaint.setShader(SkShader::MakeBitmapShader(fBitmap,
-                                                    SkShader::kRepeat_TileMode,
-                                                    SkShader::kRepeat_TileMode));
+        fPaint.setShader(fBitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat));
     }
 
 
diff --git a/bench/RotatedRectBench.cpp b/bench/RotatedRectBench.cpp
index 7623715..db4a792 100644
--- a/bench/RotatedRectBench.cpp
+++ b/bench/RotatedRectBench.cpp
@@ -101,7 +101,7 @@
             SkPoint pts[2] = { {0.0f, 0.0f}, {kRectW, kRectH} };
             SkColor colors[] = { color, SK_ColorBLUE };
             paint.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, 2,
-                                                         SkShader::kClamp_TileMode));
+                                                         SkTileMode::kClamp));
         }
 
         SkMatrix rotate;
diff --git a/bench/SKPAnimationBench.cpp b/bench/SKPAnimationBench.cpp
index 9aa7ba3..efa2c76 100644
--- a/bench/SKPAnimationBench.cpp
+++ b/bench/SKPAnimationBench.cpp
@@ -6,7 +6,7 @@
  */
 
 #include "SKPAnimationBench.h"
-#include "SkCommandLineFlags.h"
+#include "CommandLineFlags.h"
 #include "SkMultiPictureDraw.h"
 #include "SkSurface.h"
 
diff --git a/bench/SKPBench.cpp b/bench/SKPBench.cpp
index 3a3bbaf..7617fe3 100644
--- a/bench/SKPBench.cpp
+++ b/bench/SKPBench.cpp
@@ -6,7 +6,7 @@
  */
 
 #include "SKPBench.h"
-#include "SkCommandLineFlags.h"
+#include "CommandLineFlags.h"
 #include "SkMultiPictureDraw.h"
 #include "SkSurface.h"
 
@@ -14,11 +14,11 @@
 #include "GrContextPriv.h"
 
 // These CPU tile sizes are not good per se, but they are similar to what Chrome uses.
-DEFINE_int32(CPUbenchTileW, 256, "Tile width  used for CPU SKP playback.");
-DEFINE_int32(CPUbenchTileH, 256, "Tile height used for CPU SKP playback.");
+static DEFINE_int(CPUbenchTileW, 256, "Tile width  used for CPU SKP playback.");
+static DEFINE_int(CPUbenchTileH, 256, "Tile height used for CPU SKP playback.");
 
-DEFINE_int32(GPUbenchTileW, 1600, "Tile width  used for GPU SKP playback.");
-DEFINE_int32(GPUbenchTileH, 512, "Tile height used for GPU SKP playback.");
+static DEFINE_int(GPUbenchTileW, 1600, "Tile width  used for GPU SKP playback.");
+static DEFINE_int(GPUbenchTileH, 512, "Tile height used for GPU SKP playback.");
 
 SKPBench::SKPBench(const char* name, const SkPicture* pic, const SkIRect& clip, SkScalar scale,
                    bool useMultiPictureDraw, bool doLooping)
@@ -152,20 +152,13 @@
 
 #include "GrGpu.h"
 static void draw_pic_for_stats(SkCanvas* canvas, GrContext* context, const SkPicture* picture,
-                               SkTArray<SkString>* keys, SkTArray<double>* values,
-                               const char* tag) {
+                               SkTArray<SkString>* keys, SkTArray<double>* values) {
     context->priv().resetGpuStats();
     canvas->drawPicture(picture);
     canvas->flush();
 
-    int offset = keys->count();
     context->priv().dumpGpuStatsKeyValuePairs(keys, values);
     context->priv().dumpCacheStatsKeyValuePairs(keys, values);
-
-    // append tag, but only to new tags
-    for (int i = offset; i < keys->count(); i++, offset++) {
-        (*keys)[i].appendf("_%s", tag);
-    }
 }
 
 void SKPBench::getGpuStats(SkCanvas* canvas, SkTArray<SkString>* keys, SkTArray<double>* values) {
@@ -180,8 +173,5 @@
     context->freeGpuResources();
     context->resetContext();
     context->priv().getGpu()->resetShaderCacheForTesting();
-    draw_pic_for_stats(canvas, context, fPic.get(), keys, values, "first_frame");
-
-    // draw second frame
-    draw_pic_for_stats(canvas, context, fPic.get(), keys, values, "second_frame");
+    draw_pic_for_stats(canvas, context, fPic.get(), keys, values);
 }
diff --git a/bench/ShaderMaskFilterBench.cpp b/bench/ShaderMaskFilterBench.cpp
index 4a2ec14..164d5dd 100644
--- a/bench/ShaderMaskFilterBench.cpp
+++ b/bench/ShaderMaskFilterBench.cpp
@@ -21,8 +21,7 @@
     auto surface = SkSurface::MakeRasterN32Premul(100, 100);
     surface->getCanvas()->drawCircle(50, 50, 50, p);
 
-    return surface->makeImageSnapshot()->makeShader(SkShader::kRepeat_TileMode,
-                                                    SkShader::kRepeat_TileMode);
+    return surface->makeImageSnapshot()->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat);
 }
 
 static sk_sp<SkShader> make_picture_shader() {
@@ -33,10 +32,7 @@
     SkPictureRecorder recorder;
     recorder.beginRecording(100, 100)->drawCircle(50, 50, 50, p);
 
-    return SkPictureShader::Make(recorder.finishRecordingAsPicture(),
-                                 SkShader::kRepeat_TileMode,
-                                 SkShader::kRepeat_TileMode,
-                                 nullptr, nullptr);
+    return recorder.finishRecordingAsPicture()->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat);
 }
 
 class ShaderMFBench final : public Benchmark {
diff --git a/bench/ShapesBench.cpp b/bench/ShapesBench.cpp
index a7d57eb..9c21f5c 100644
--- a/bench/ShapesBench.cpp
+++ b/bench/ShapesBench.cpp
@@ -6,11 +6,11 @@
  */
 
 #include "Benchmark.h"
+#include "CommandLineFlags.h"
 #include "SkCanvas.h"
-#include "SkCommandLineFlags.h"
 #include "SkPaint.h"
-#include "SkRandom.h"
 #include "SkRRect.h"
+#include "SkRandom.h"
 #include "SkString.h"
 
 #include <stdio.h>
@@ -20,13 +20,15 @@
 #define ENABLE_COMMAND_LINE_SHAPES_BENCH 0
 
 #if ENABLE_COMMAND_LINE_SHAPES_BENCH
-DEFINE_string(shapesType, "mixed", "Type of shape to use in ShapesBench. Must be one of: "
-                                   "rect, oval, rrect, mixed.");
-DEFINE_string(innerShapesType, "none", "Type of inner shape to use in ShapesBench. Must be one of: "
-                                       "none, rect, oval, rrect, mixed.");
-DEFINE_int32(numShapes, 10000, "Number of shapes to draw in ShapesBench.");
-DEFINE_string(shapesSize, "32x32", "Size of shapes to draw in ShapesBench.");
-DEFINE_bool(shapesPersp, false, "Use slight perspective tilt in ShapesBench?");
+static DEFINE_string(shapesType, "mixed",
+                     "Type of shape to use in ShapesBench. Must be one of: "
+                     "rect, oval, rrect, mixed.");
+static DEFINE_string(innerShapesType, "none",
+                     "Type of inner shape to use in ShapesBench. Must be one of: "
+                     "none, rect, oval, rrect, mixed.");
+static DEFINE_int(numShapes, 10000, "Number of shapes to draw in ShapesBench.");
+static DEFINE_string(shapesSize, "32x32", "Size of shapes to draw in ShapesBench.");
+static DEFINE_bool(shapesPersp, false, "Use slight perspective tilt in ShapesBench?");
 #endif
 
 /*
diff --git a/bench/SkGlyphCacheBench.cpp b/bench/SkGlyphCacheBench.cpp
index 27c6d26..fca0381 100644
--- a/bench/SkGlyphCacheBench.cpp
+++ b/bench/SkGlyphCacheBench.cpp
@@ -10,12 +10,11 @@
 
 #include "Benchmark.h"
 #include "SkCanvas.h"
-#include "SkStrikeCache.h"
 #include "SkGraphics.h"
+#include "SkStrikeCache.h"
 #include "SkTaskGroup.h"
 #include "SkTypeface.h"
-#include "sk_tool_utils.h"
-
+#include "ToolUtils.h"
 
 static void do_font_stuff(SkFont* font) {
     SkPaint defaultPaint;
@@ -58,7 +57,7 @@
         SkFont font;
         font.setEdging(SkFont::Edging::kAntiAlias);
         font.setSubpixel(true);
-        font.setTypeface(sk_tool_utils::create_portable_typeface("serif", SkFontStyle::Italic()));
+        font.setTypeface(ToolUtils::create_portable_typeface("serif", SkFontStyle::Italic()));
 
         for (int work = 0; work < loops; work++) {
             do_font_stuff(&font);
@@ -89,9 +88,9 @@
     void onDraw(int loops, SkCanvas*) override {
         size_t oldCacheLimitSize = SkGraphics::GetFontCacheLimit();
         SkGraphics::SetFontCacheLimit(fCacheSize);
-        sk_sp<SkTypeface> typefaces[] =
-            {sk_tool_utils::create_portable_typeface("serif", SkFontStyle::Italic()),
-             sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle::Italic())};
+        sk_sp<SkTypeface> typefaces[] = {
+                ToolUtils::create_portable_typeface("serif", SkFontStyle::Italic()),
+                ToolUtils::create_portable_typeface("sans-serif", SkFontStyle::Italic())};
 
         for (int work = 0; work < loops; work++) {
             SkTaskGroup().batch(16, [&](int threadIndex) {
diff --git a/bench/TextBlobBench.cpp b/bench/TextBlobBench.cpp
index 3f543362..a52b09c 100644
--- a/bench/TextBlobBench.cpp
+++ b/bench/TextBlobBench.cpp
@@ -17,7 +17,7 @@
 #include "SkTextBlob.h"
 #include "SkTypeface.h"
 
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 /*
  * A trivial test which benchmarks the performance of a textblob with a single run.
@@ -27,7 +27,7 @@
     SkTextBlobBench() {}
 
     void onDelayedSetup() override {
-        fFont.setTypeface(sk_tool_utils::create_portable_typeface("serif", SkFontStyle()));
+        fFont.setTypeface(ToolUtils::create_portable_typeface("serif", SkFontStyle()));
         fFont.setSubpixel(true);
 
         // This text seems representative in both length and letter frequency.
diff --git a/bench/TileBench.cpp b/bench/TileBench.cpp
index 4a69ccb..0679587 100644
--- a/bench/TileBench.cpp
+++ b/bench/TileBench.cpp
@@ -36,8 +36,8 @@
     static const int kHeight = 300;
 
 public:
-    ConstXTileBench(SkShader::TileMode xTile,
-                    SkShader::TileMode yTile,
+    ConstXTileBench(SkTileMode xTile,
+                    SkTileMode yTile,
                     bool doFilter,
                     bool doTrans,
                     bool doScale)
@@ -51,13 +51,13 @@
 
         create_gradient(&bm);
 
-        fPaint.setShader(SkShader::MakeBitmapShader(bm, xTile, yTile));
+        fPaint.setShader(bm.makeShader(xTile, yTile));
 
         fName.printf("constXTile_");
 
-        static const char* gTileModeStr[SkShader::kTileModeCount] = { "C", "R", "M" };
-        fName.append(gTileModeStr[xTile]);
-        fName.append(gTileModeStr[yTile]);
+        static const char* gTileModeStr[kSkTileModeCount] = { "C", "R", "M", "D" };
+        fName.append(gTileModeStr[(unsigned)xTile]);
+        fName.append(gTileModeStr[(unsigned)yTile]);
 
         if (doFilter) {
             fName.append("_filter");
@@ -114,18 +114,18 @@
 // Scaled benches are trending towards free.  Seems like caching.
 // TODO(mtklein, reed): fix and reenable
 
-//DEF_BENCH(return new ConstXTileBench(SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, false, false, true))
-DEF_BENCH(return new ConstXTileBench(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, false, false, false))
-//DEF_BENCH(return new ConstXTileBench(SkShader::kMirror_TileMode, SkShader::kMirror_TileMode, false, false, true))
+//DEF_BENCH(return new ConstXTileBench(SkTileMode::kRepeat, SkTileMode::kRepeat, false, false, true))
+DEF_BENCH(return new ConstXTileBench(SkTileMode::kClamp, SkTileMode::kClamp, false, false, false))
+//DEF_BENCH(return new ConstXTileBench(SkTileMode::kMirror, SkTileMode::kMirror, false, false, true))
 
-DEF_BENCH(return new ConstXTileBench(SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, true, false, false))
-//DEF_BENCH(return new ConstXTileBench(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, true, false, true))
-DEF_BENCH(return new ConstXTileBench(SkShader::kMirror_TileMode, SkShader::kMirror_TileMode, true, false, false))
+DEF_BENCH(return new ConstXTileBench(SkTileMode::kRepeat, SkTileMode::kRepeat, true, false, false))
+//DEF_BENCH(return new ConstXTileBench(SkTileMode::kClamp, SkTileMode::kClamp, true, false, true))
+DEF_BENCH(return new ConstXTileBench(SkTileMode::kMirror, SkTileMode::kMirror, true, false, false))
 
-//DEF_BENCH(return new ConstXTileBench(SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, false, true, true))
-DEF_BENCH(return new ConstXTileBench(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, false, true, false))
-//DEF_BENCH(return new ConstXTileBench(SkShader::kMirror_TileMode, SkShader::kMirror_TileMode, false, true, true))
+//DEF_BENCH(return new ConstXTileBench(SkTileMode::kRepeat, SkTileMode::kRepeat, false, true, true))
+DEF_BENCH(return new ConstXTileBench(SkTileMode::kClamp, SkTileMode::kClamp, false, true, false))
+//DEF_BENCH(return new ConstXTileBench(SkTileMode::kMirror, SkTileMode::kMirror, false, true, true))
 
-DEF_BENCH(return new ConstXTileBench(SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, true, true, false))
-//DEF_BENCH(return new ConstXTileBench(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, true, true, true))
-DEF_BENCH(return new ConstXTileBench(SkShader::kMirror_TileMode, SkShader::kMirror_TileMode, true, true, false))
+DEF_BENCH(return new ConstXTileBench(SkTileMode::kRepeat, SkTileMode::kRepeat, true, true, false))
+//DEF_BENCH(return new ConstXTileBench(SkTileMode::kClamp, SkTileMode::kClamp, true, true, true))
+DEF_BENCH(return new ConstXTileBench(SkTileMode::kMirror, SkTileMode::kMirror, true, true, false))
diff --git a/bench/TopoSortBench.cpp b/bench/TopoSortBench.cpp
index 123ff74..d0b6202 100644
--- a/bench/TopoSortBench.cpp
+++ b/bench/TopoSortBench.cpp
@@ -10,7 +10,7 @@
 #include "SkString.h"
 #include "SkTTopoSort.h"
 
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 class TopoSortBench : public Benchmark {
 public:
@@ -30,7 +30,7 @@
 
     // Delayed initialization only done if onDraw will be called.
     void onDelayedSetup() override {
-        sk_tool_utils::TopoTestNode::AllocNodes(&fGraph, kNumElements);
+        ToolUtils::TopoTestNode::AllocNodes(&fGraph, kNumElements);
 
         for (int i = kNumElements-1; i > 0; --i) {
             int numEdges = fRand.nextU() % (kMaxEdges+1);
@@ -49,9 +49,9 @@
                 fGraph[j]->reset();
             }
 
-            sk_tool_utils::TopoTestNode::Shuffle(&fGraph, &fRand);
+            ToolUtils::TopoTestNode::Shuffle(&fGraph, &fRand);
 
-            SkDEBUGCODE(bool actualResult =) SkTTopoSort<sk_tool_utils::TopoTestNode>(&fGraph);
+            SkDEBUGCODE(bool actualResult =) SkTTopoSort<ToolUtils::TopoTestNode>(&fGraph);
             SkASSERT(actualResult);
 
 #ifdef SK_DEBUG
@@ -66,7 +66,7 @@
     static const int kNumElements = 1000;
     static const int kMaxEdges = 5;
 
-    SkTArray<sk_sp<sk_tool_utils::TopoTestNode>> fGraph;
+    SkTArray<sk_sp<ToolUtils::TopoTestNode>> fGraph;
     SkRandom fRand;
 
     typedef Benchmark INHERITED;
diff --git a/bench/VertBench.cpp b/bench/VertBench.cpp
index 6e1613b..a49d3a8 100644
--- a/bench/VertBench.cpp
+++ b/bench/VertBench.cpp
@@ -91,7 +91,79 @@
 private:
     typedef Benchmark INHERITED;
 };
-
-///////////////////////////////////////////////////////////////////////////////
-
 DEF_BENCH(return new VertBench();)
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "Resources.h"
+#include "SkRandom.h"
+#include "SkRSXform.h"
+
+enum AtlasFlags {
+    kColors_Flag = 1 << 0,
+    kVerts_Flag  = 1 << 1,
+};
+
+class AtlasBench : public Benchmark {
+    unsigned fFlags;
+    SkString fName;
+    enum {
+        W = 640,
+        H = 480,
+        N = 10*1000,
+    };
+
+    sk_sp<SkImage>  fAtlas;
+    SkRSXform       fXforms[N];
+    SkRect          fRects[N];
+    SkColor         fColors[N];
+
+public:
+    AtlasBench(unsigned flags) : fFlags(flags) {
+        fName.printf("drawAtlas_%d", flags);
+    }
+    ~AtlasBench() override {}
+
+protected:
+    const char* onGetName() override { return fName.c_str(); }
+    void onDelayedSetup() override {
+        fAtlas = GetResourceAsImage("images/mandrill_256.png");
+        if (fAtlas) {
+            fAtlas = fAtlas->makeRasterImage();
+        }
+
+        const SkScalar imageW = fAtlas->width();
+        const SkScalar imageH = fAtlas->height();
+
+        SkRandom rand;
+        for (int i = 0; i < N; ++i) {
+            fRects[i] = SkRect::MakeXYWH(rand.nextF() * (imageW - 8),
+                                         rand.nextF() * (imageH - 8), 8, 8);
+            fColors[i] = rand.nextU();
+            fXforms[i] = SkRSXform::Make(1, 0, rand.nextF() * W, rand.nextF() * H);
+        }
+    }
+    void onDraw(int loops, SkCanvas* canvas) override {
+        const SkRect* cullRect = nullptr;
+        const SkPaint* paintPtr = nullptr;
+        const SkColor* colors = nullptr;
+        const SkImage* atlas = nullptr;
+        if (fFlags & kColors_Flag) {
+            colors = fColors;
+        }
+        if (fFlags & kVerts_Flag) {
+            atlas = fAtlas.get();
+        }
+        for (int i = 0; i < loops; i++) {
+            canvas->drawAtlas(atlas, fXforms, fRects, colors, N, SkBlendMode::kSrcOver,
+                              cullRect, paintPtr);
+        }
+    }
+private:
+    typedef Benchmark INHERITED;
+};
+//DEF_BENCH(return new AtlasBench(0);)
+//DEF_BENCH(return new AtlasBench(kColors_Flag);)
+DEF_BENCH(return new AtlasBench(kVerts_Flag);)
+DEF_BENCH(return new AtlasBench(kVerts_Flag | kColors_Flag);)
+
diff --git a/bench/VertexColorSpaceBench.cpp b/bench/VertexColorSpaceBench.cpp
index 0d395c5..b203d2f 100644
--- a/bench/VertexColorSpaceBench.cpp
+++ b/bench/VertexColorSpaceBench.cpp
@@ -151,7 +151,8 @@
         return FixedFunctionFlags::kNone;
     }
 
-    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*) override {
+    GrProcessorSet::Analysis finalize(
+            const GrCaps&, const GrAppliedClip*, GrFSAAType, GrClampType) override {
         return GrProcessorSet::EmptySetAnalysis();
     }
 
@@ -222,9 +223,12 @@
         GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangleStrip);
         mesh->setNonIndexedNonInstanced(kVertexCount);
         mesh->setVertexData(std::move(vertexBuffer), firstVertex);
-        auto pipe = target->makePipeline(0, GrProcessorSet::MakeEmptySet(),
-                                         target->detachAppliedClip());
-        target->draw(gp, pipe.fPipeline, pipe.fFixedDynamicState, mesh);
+        target->recordDraw(gp, mesh);
+    }
+
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        flushState->executeDrawsAndUploadsForMeshDrawOp(
+                this, chainBounds, GrProcessorSet::MakeEmptySet());
     }
 
     Mode fMode;
diff --git a/bench/WritePixelsBench.cpp b/bench/WritePixelsBench.cpp
index 4e2dbeb..39ceb1f 100644
--- a/bench/WritePixelsBench.cpp
+++ b/bench/WritePixelsBench.cpp
@@ -10,35 +10,21 @@
 #include "SkCanvas.h"
 #include "SkString.h"
 
+// Time variants of write-pixels
+//  [ colortype ][ alphatype ][ colorspace ]
+//  Different combinations can trigger fast or slow paths in the impls
+//
 class WritePixelsBench : public Benchmark {
 public:
-    WritePixelsBench(SkColorType ct, SkAlphaType at)
+    WritePixelsBench(SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs)
         : fColorType(ct)
         , fAlphaType(at)
-        , fName("writepix")
+        , fCS(cs)
     {
-        switch (ct) {
-            case kRGBA_8888_SkColorType:
-                fName.append("_RGBA");
-                break;
-            case kBGRA_8888_SkColorType:
-                fName.append("_BGRA");
-                break;
-            default:
-                SkASSERT(0);
-                break;
-        }
-        switch (at) {
-            case kPremul_SkAlphaType:
-                fName.append("_PM");
-                break;
-            case kUnpremul_SkAlphaType:
-                fName.append("_UPM");
-                break;
-            default:
-                SkASSERT(0);
-                break;
-        }
+        fName.printf("writepix_%s_%s_%s",
+                     at == kPremul_SkAlphaType ? "pm" : "um",
+                     ct == kRGBA_8888_SkColorType ? "rgba" : "bgra",
+                     cs ? "srgb" : "null");
     }
 
 protected:
@@ -49,13 +35,11 @@
     void onDraw(int loops, SkCanvas* canvas) override {
         SkISize size = canvas->getBaseLayerSize();
 
-        canvas->clear(0xFFFF0000);
-
+        SkImageInfo info = SkImageInfo::Make(size.width(), size.height(), fColorType, fAlphaType,
+                                             fCS);
         SkBitmap bmp;
-        bmp.allocN32Pixels(size.width(), size.height());
-        canvas->readPixels(bmp, 0, 0);
-
-        SkImageInfo info = SkImageInfo::Make(bmp.width(), bmp.height(), fColorType, fAlphaType);
+        bmp.allocPixels(info);
+        bmp.eraseColor(SK_ColorBLACK);
 
         for (int loop = 0; loop < loops; ++loop) {
             canvas->writePixels(info, bmp.getPixels(), bmp.rowBytes(), 0, 0);
@@ -65,6 +49,7 @@
 private:
     SkColorType fColorType;
     SkAlphaType fAlphaType;
+    sk_sp<SkColorSpace> fCS;
     SkString    fName;
 
     typedef Benchmark INHERITED;
@@ -72,5 +57,12 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
-DEF_BENCH(return new WritePixelsBench(kRGBA_8888_SkColorType, kPremul_SkAlphaType);)
-DEF_BENCH(return new WritePixelsBench(kRGBA_8888_SkColorType, kUnpremul_SkAlphaType);)
+DEF_BENCH(return new WritePixelsBench(kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr);)
+DEF_BENCH(return new WritePixelsBench(kRGBA_8888_SkColorType, kUnpremul_SkAlphaType, nullptr);)
+DEF_BENCH(return new WritePixelsBench(kRGBA_8888_SkColorType, kPremul_SkAlphaType, SkColorSpace::MakeSRGB());)
+DEF_BENCH(return new WritePixelsBench(kRGBA_8888_SkColorType, kUnpremul_SkAlphaType, SkColorSpace::MakeSRGB());)
+
+DEF_BENCH(return new WritePixelsBench(kBGRA_8888_SkColorType, kPremul_SkAlphaType, nullptr);)
+DEF_BENCH(return new WritePixelsBench(kBGRA_8888_SkColorType, kUnpremul_SkAlphaType, nullptr);)
+DEF_BENCH(return new WritePixelsBench(kBGRA_8888_SkColorType, kPremul_SkAlphaType, SkColorSpace::MakeSRGB());)
+DEF_BENCH(return new WritePixelsBench(kBGRA_8888_SkColorType, kUnpremul_SkAlphaType, SkColorSpace::MakeSRGB());)
diff --git a/bench/nanobench.cpp b/bench/nanobench.cpp
index b4d0035..bd61d34 100644
--- a/bench/nanobench.cpp
+++ b/bench/nanobench.cpp
@@ -14,7 +14,10 @@
 #include "BitmapRegionDecoderBench.h"
 #include "CodecBench.h"
 #include "CodecBenchPriv.h"
+#include "CommonFlags.h"
+#include "CommonFlagsConfig.h"
 #include "CrashHandler.h"
+#include "EventTracingPriv.h"
 #include "GMBench.h"
 #include "ProcStats.h"
 #include "RecordingBench.h"
@@ -28,19 +31,14 @@
 #include "SkCanvas.h"
 #include "SkCodec.h"
 #include "SkColorSpacePriv.h"
-#include "SkCommonFlags.h"
-#include "SkCommonFlagsConfig.h"
-#include "SkCommonFlagsGpu.h"
 #include "SkData.h"
 #include "SkDebugfTracer.h"
-#include "SkEventTracingPriv.h"
 #include "SkGraphics.h"
 #include "SkJSONWriter.h"
 #include "SkLeanWindows.h"
 #include "SkOSFile.h"
 #include "SkOSPath.h"
 #include "SkPictureRecorder.h"
-#include "SkScan.h"
 #include "SkString.h"
 #include "SkSurface.h"
 #include "SkTaskGroup.h"
@@ -103,39 +101,86 @@
     return str;
 }
 
-DEFINE_int32(loops, kDefaultLoops, loops_help_txt().c_str());
+static DEFINE_int(loops, kDefaultLoops, loops_help_txt().c_str());
 
-DEFINE_int32(samples, 10, "Number of samples to measure for each bench.");
-DEFINE_int32(ms, 0, "If >0, run each bench for this many ms instead of obeying --samples.");
-DEFINE_int32(overheadLoops, 100000, "Loops to estimate timer overhead.");
-DEFINE_double(overheadGoal, 0.0001,
+static DEFINE_int(samples, 10, "Number of samples to measure for each bench.");
+static DEFINE_int(ms, 0, "If >0, run each bench for this many ms instead of obeying --samples.");
+static DEFINE_int(overheadLoops, 100000, "Loops to estimate timer overhead.");
+static DEFINE_double(overheadGoal, 0.0001,
               "Loop until timer overhead is at most this fraction of our measurments.");
-DEFINE_double(gpuMs, 5, "Target bench time in millseconds for GPU.");
-DEFINE_int32(gpuFrameLag, 5, "If unknown, estimated maximum number of frames GPU allows to lag.");
+static DEFINE_double(gpuMs, 5, "Target bench time in millseconds for GPU.");
+static DEFINE_int(gpuFrameLag, 5,
+                    "If unknown, estimated maximum number of frames GPU allows to lag.");
 
-DEFINE_string(outResultsFile, "", "If given, write results here as JSON.");
-DEFINE_int32(maxCalibrationAttempts, 3,
+static DEFINE_string(outResultsFile, "", "If given, write results here as JSON.");
+static DEFINE_int(maxCalibrationAttempts, 3,
              "Try up to this many times to guess loops for a bench, or skip the bench.");
-DEFINE_int32(maxLoops, 1000000, "Never run a bench more times than this.");
-DEFINE_string(clip, "0,0,1000,1000", "Clip for SKPs.");
-DEFINE_string(scales, "1.0", "Space-separated scales for SKPs.");
-DEFINE_string(zoom, "1.0,0", "Comma-separated zoomMax,zoomPeriodMs factors for a periodic SKP zoom "
-                             "function that ping-pongs between 1.0 and zoomMax.");
-DEFINE_bool(bbh, true, "Build a BBH for SKPs?");
-DEFINE_bool(lite, false, "Use SkLiteRecorder in recording benchmarks?");
-DEFINE_bool(mpd, true, "Use MultiPictureDraw for the SKPs?");
-DEFINE_bool(loopSKP, true, "Loop SKPs like we do for micro benches?");
-DEFINE_int32(flushEvery, 10, "Flush --outResultsFile every Nth run.");
-DEFINE_bool(gpuStats, false, "Print GPU stats after each gpu benchmark?");
-DEFINE_bool(gpuStatsDump, false, "Dump GPU states after each benchmark to json");
-DEFINE_bool(keepAlive, false, "Print a message every so often so that we don't time out");
-DEFINE_bool(csv, false, "Print status in CSV format");
-DEFINE_string(sourceType, "",
+static DEFINE_int(maxLoops, 1000000, "Never run a bench more times than this.");
+static DEFINE_string(clip, "0,0,1000,1000", "Clip for SKPs.");
+static DEFINE_string(scales, "1.0", "Space-separated scales for SKPs.");
+static DEFINE_string(zoom, "1.0,0",
+                     "Comma-separated zoomMax,zoomPeriodMs factors for a periodic SKP zoom "
+                     "function that ping-pongs between 1.0 and zoomMax.");
+static DEFINE_bool(bbh, true, "Build a BBH for SKPs?");
+static DEFINE_bool(lite, false, "Use SkLiteRecorder in recording benchmarks?");
+static DEFINE_bool(mpd, true, "Use MultiPictureDraw for the SKPs?");
+static DEFINE_bool(loopSKP, true, "Loop SKPs like we do for micro benches?");
+static DEFINE_int(flushEvery, 10, "Flush --outResultsFile every Nth run.");
+static DEFINE_bool(gpuStats, false, "Print GPU stats after each gpu benchmark?");
+static DEFINE_bool(gpuStatsDump, false, "Dump GPU states after each benchmark to json");
+static DEFINE_bool(keepAlive, false, "Print a message every so often so that we don't time out");
+static DEFINE_bool(csv, false, "Print status in CSV format");
+static DEFINE_string(sourceType, "",
         "Apply usual --match rules to source type: bench, gm, skp, image, etc.");
-DEFINE_string(benchType,  "",
-        "Apply usual --match rules to bench type: micro, recording, piping, playback, skcodec, etc.");
+static DEFINE_string(benchType,  "",
+        "Apply usual --match rules to bench type: micro, recording, "
+        "piping, playback, skcodec, etc.");
 
-DEFINE_bool(forceRasterPipeline, false, "sets gSkForceRasterPipelineBlitter");
+static DEFINE_bool(forceRasterPipeline, false, "sets gSkForceRasterPipelineBlitter");
+
+static DEFINE_bool2(pre_log, p, false,
+                    "Log before running each test. May be incomprehensible when threading");
+
+static DEFINE_bool(cpu, true, "master switch for running CPU-bound work.");
+static DEFINE_bool(gpu, true, "master switch for running GPU-bound work.");
+static DEFINE_bool(dryRun, false,
+                   "just print the tests that would be run, without actually running them.");
+static DEFINE_string(images, "",
+                     "List of images and/or directories to decode. A directory with no images"
+                     " is treated as a fatal error.");
+static DEFINE_bool(simpleCodec, false,
+                   "Runs of a subset of the codec tests, always N32, Premul or Opaque");
+
+static DEFINE_string2(match, m, nullptr,
+               "[~][^]substring[$] [...] of name to run.\n"
+               "Multiple matches may be separated by spaces.\n"
+               "~ causes a matching name to always be skipped\n"
+               "^ requires the start of the name to match\n"
+               "$ requires the end of the name to match\n"
+               "^ and $ requires an exact match\n"
+               "If a name does not match any list entry,\n"
+               "it is skipped unless some list entry starts with ~");
+
+static DEFINE_bool2(quiet, q, false, "if true, don't print status updates.");
+static DEFINE_bool2(verbose, v, false, "enable verbose output from the test driver.");
+
+
+static DEFINE_string(skps, "skps", "Directory to read skps from.");
+static DEFINE_string(svgs, "", "Directory to read SVGs from, or a single SVG file.");
+
+static DEFINE_int_2(threads, j, -1,
+               "Run threadsafe tests on a threadpool with this many extra threads, "
+               "defaulting to one extra thread per core.");
+
+static DEFINE_string2(writePath, w, "", "If set, write bitmaps here as .pngs.");
+
+static DEFINE_string(key, "",
+                     "Space-separated key/value pairs to add to JSON identifying this builder.");
+static DEFINE_string(properties, "",
+                     "Space-separated key/value pairs to add to JSON identifying this run.");
+
+static DEFINE_bool(purgeBetweenBenches, false,
+                   "Call SkGraphics::PurgeAllCaches() between each benchmark?");
 
 static double now_ms() { return SkTime::GetNSecs() * 1e-6; }
 
@@ -572,8 +617,9 @@
     delete target;
 }
 
-static void collect_files(const SkCommandLineFlags::StringArray& paths, const char* ext,
-                          SkTArray<SkString>* list) {
+static void collect_files(const CommandLineFlags::StringArray& paths,
+                          const char*                          ext,
+                          SkTArray<SkString>*                  list) {
     for (int i = 0; i < paths.count(); ++i) {
         if (SkStrEndsWith(paths[i], ext)) {
             list->push_back(SkString(paths[i]));
@@ -648,7 +694,7 @@
     static sk_sp<SkPicture> ReadPicture(const char* path) {
         // Not strictly necessary, as it will be checked again later,
         // but helps to avoid a lot of pointless work if we're going to skip it.
-        if (SkCommandLineFlags::ShouldSkip(FLAGS_match, SkOSPath::Basename(path).c_str())) {
+        if (CommandLineFlags::ShouldSkip(FLAGS_match, SkOSPath::Basename(path).c_str())) {
             return nullptr;
         }
 
@@ -698,8 +744,8 @@
             if (!bench) {
                 return nullptr;
             }
-        } while(SkCommandLineFlags::ShouldSkip(FLAGS_sourceType, fSourceType) ||
-                SkCommandLineFlags::ShouldSkip(FLAGS_benchType,  fBenchType));
+        } while (CommandLineFlags::ShouldSkip(FLAGS_sourceType, fSourceType) ||
+                 CommandLineFlags::ShouldSkip(FLAGS_benchType, fBenchType));
         return bench.release();
     }
 
@@ -821,7 +867,7 @@
             fSourceType = "image";
             fBenchType = "skcodec";
             const SkString& path = fImages[fCurrentCodec];
-            if (SkCommandLineFlags::ShouldSkip(FLAGS_match, path.c_str())) {
+            if (CommandLineFlags::ShouldSkip(FLAGS_match, path.c_str())) {
                 continue;
             }
             sk_sp<SkData> encoded(SkData::MakeFromFileName(path.c_str()));
@@ -900,7 +946,7 @@
             fBenchType = "skandroidcodec";
 
             const SkString& path = fImages[fCurrentAndroidCodec];
-            if (SkCommandLineFlags::ShouldSkip(FLAGS_match, path.c_str())) {
+            if (CommandLineFlags::ShouldSkip(FLAGS_match, path.c_str())) {
                 continue;
             }
             sk_sp<SkData> encoded(SkData::MakeFromFileName(path.c_str()));
@@ -945,7 +991,7 @@
             fBenchType = "BRD";
 
             const SkString& path = fImages[fCurrentBRDImage];
-            if (SkCommandLineFlags::ShouldSkip(FLAGS_match, path.c_str())) {
+            if (CommandLineFlags::ShouldSkip(FLAGS_match, path.c_str())) {
                 continue;
             }
 
@@ -1099,7 +1145,7 @@
 }
 
 int main(int argc, char** argv) {
-    SkCommandLineFlags::Parse(argc, argv);
+    CommandLineFlags::Parse(argc, argv);
 
     initializeEventTracingForTools();
 
@@ -1112,10 +1158,6 @@
 
     SetCtxOptionsFromCommonFlags(&grContextOpts);
 
-    if (FLAGS_veryVerbose) {
-        FLAGS_verbose = true;
-    }
-
     if (kAutoTuneLoops != FLAGS_loops) {
         FLAGS_samples     = 1;
         FLAGS_gpuFrameLag = 0;
@@ -1185,15 +1227,8 @@
         start_keepalive();
     }
 
-    gSkUseAnalyticAA = FLAGS_analyticAA;
-    gSkUseDeltaAA = FLAGS_deltaAA;
+    SetAnalyticAAFromCommonFlags();
 
-    if (FLAGS_forceDeltaAA) {
-        gSkForceDeltaAA = true;
-    }
-    if (FLAGS_forceAnalyticAA) {
-        gSkForceAnalyticAA = true;
-    }
     if (FLAGS_forceRasterPipeline) {
         gSkForceRasterPipelineBlitter = true;
     }
@@ -1203,7 +1238,7 @@
     log.beginObject("results");
     while (Benchmark* b = benchStream.next()) {
         std::unique_ptr<Benchmark> bench(b);
-        if (SkCommandLineFlags::ShouldSkip(FLAGS_match, bench->getUniqueName())) {
+        if (CommandLineFlags::ShouldSkip(FLAGS_match, bench->getUniqueName())) {
             continue;
         }
 
@@ -1230,6 +1265,10 @@
                 }
             }
 
+            if (FLAGS_purgeBetweenBenches) {
+                SkGraphics::PurgeAllCaches();
+            }
+
             TRACE_EVENT2("skia", "Benchmark", "name", TRACE_STR_COPY(bench->getUniqueName()),
                                               "config", TRACE_STR_COPY(config));
 
diff --git a/bin/sync b/bin/sync
index 163584d..c75cdbd 100755
--- a/bin/sync
+++ b/bin/sync
@@ -1,53 +1,16 @@
 #!/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.
 
-# This script will update Skia's dependencies as necessary.
+"""
+Calls `GIT_SYNC_DEPS_QUIET=T tools/git-sync-deps`.
+"""
 
-# Depends on: Python and Git
-
-# To retreive and use all optional deps:
-#
-#   python bin/sync --deps=all
-
-import hashlib
 import os
 import subprocess
 import sys
 
-HASH_FILE = '.deps_sha1'
-DEPS_FLAG = '--deps='
-DEPS_FILE = 'DEPS'
-
-skia_opt_deps = [arg[len(DEPS_FLAG):] for arg in sys.argv[1:] if arg.startswith(DEPS_FLAG)]
-
-os.chdir(os.path.join(os.path.dirname(__file__), os.pardir))
-
-if not os.path.isfile(DEPS_FILE):
-  sys.stderr.write('DEPS file missing')
-  exit(1)
-
-deps_hasher = hashlib.sha1()
-with open(DEPS_FILE, 'r') as f:
-  deps_hasher.update(f.read())
-deps_hasher.update(repr(skia_opt_deps))
-deps_hash = deps_hasher.hexdigest()
-current_deps_hash = None
-if os.path.isfile(HASH_FILE):
-  with open(HASH_FILE, 'r') as f:
-    current_deps_hash = f.read().strip()
-
-if current_deps_hash != deps_hash:
-  if os.path.isfile(HASH_FILE):
-    os.remove(HASH_FILE)
-  command = [sys.executable, os.path.join('tools', 'git-sync-deps')]
-  command.extend(skia_opt_deps)
-  sys.stdout.write('%r\n' % command)
-  sys.stdout.flush()
-  subprocess.check_call(command)
-  # Only write hash after a successful sync.
-  with open(HASH_FILE, 'w') as o:
-    o.write(deps_hash)
+path = os.path.dirname(__file__) + '/../tools/git-sync-deps'
+subprocess.check_call([sys.executable, path] + sys.argv[:1],
+                      env=dict(os.environ, GIT_SYNC_DEPS_QUIET='T'))
diff --git a/dm/DM.cpp b/dm/DM.cpp
index bcd3272..3658912 100644
--- a/dm/DM.cpp
+++ b/dm/DM.cpp
@@ -5,26 +5,25 @@
  * found in the LICENSE file.
  */
 
+#include "ChromeTracingTracer.h"
+#include "CommonFlags.h"
+#include "CommonFlagsConfig.h"
 #include "DMJsonWriter.h"
 #include "DMSrcSink.h"
+#include "EventTracingPriv.h"
+#include "HashAndEncode.h"
 #include "ProcStats.h"
 #include "Resources.h"
 #include "SkBBHFactory.h"
 #include "SkChecksum.h"
-#include "SkChromeTracingTracer.h"
 #include "SkCodec.h"
 #include "SkColorPriv.h"
 #include "SkColorSpace.h"
 #include "SkColorSpacePriv.h"
-#include "SkCommonFlags.h"
-#include "SkCommonFlagsConfig.h"
-#include "SkCommonFlagsGpu.h"
 #include "SkData.h"
 #include "SkDebugfTracer.h"
 #include "SkDocument.h"
-#include "SkEventTracingPriv.h"
 #include "SkFontMgr.h"
-#include "SkFontMgrPriv.h"
 #include "SkGraphics.h"
 #include "SkHalf.h"
 #include "SkLeanWindows.h"
@@ -32,21 +31,16 @@
 #include "SkMutex.h"
 #include "SkOSFile.h"
 #include "SkOSPath.h"
-#include "SkPngEncoder.h"
-#include "SkScan.h"
 #include "SkSpinlock.h"
-#include "SkTestFontMgr.h"
 #include "SkTHash.h"
 #include "SkTaskGroup.h"
 #include "SkTypeface_win.h"
 #include "Test.h"
+#include "ToolUtils.h"
 #include "ios_utils.h"
-#include "sk_tool_utils.h"
 
 #include <vector>
 
-#include "png.h"
-
 #include <stdlib.h>
 
 #ifndef SK_BUILD_FOR_WIN
@@ -59,17 +53,17 @@
 
 extern bool gSkForceRasterPipelineBlitter;
 
-DEFINE_string(src, "tests gm skp image", "Source types to test.");
-DEFINE_bool(nameByHash, false,
-            "If true, write to FLAGS_writePath[0]/<hash>.png instead of "
-            "to FLAGS_writePath[0]/<config>/<sourceType>/<sourceOptions>/<name>.png");
-DEFINE_bool2(pathOpsExtended, x, false, "Run extended pathOps tests.");
-DEFINE_string(matrix, "1 0 0 1",
-              "2x2 scale+skew matrix to apply or upright when using "
-              "'matrix' or 'upright' in config.");
-DEFINE_bool(gpu_threading, false, "Allow GPU work to run on multiple threads?");
+static DEFINE_string(src, "tests gm skp image", "Source types to test.");
+static DEFINE_bool(nameByHash, false,
+                   "If true, write to FLAGS_writePath[0]/<hash>.png instead of "
+                   "to FLAGS_writePath[0]/<config>/<sourceType>/<sourceOptions>/<name>.png");
+static DEFINE_bool2(pathOpsExtended, x, false, "Run extended pathOps tests.");
+static DEFINE_string(matrix, "1 0 0 1",
+                    "2x2 scale+skew matrix to apply or upright when using "
+                    "'matrix' or 'upright' in config.");
+static DEFINE_bool(gpu_threading, false, "Allow GPU work to run on multiple threads?");
 
-DEFINE_string(blacklist, "",
+static DEFINE_string(blacklist, "",
         "Space-separated config/src/srcOptions/name quadruples to blacklist. "
         "'_' matches anything. '~' negates the match. E.g. \n"
         "'--blacklist gpu skp _ _' will blacklist all SKPs drawn into the gpu config.\n"
@@ -77,29 +71,79 @@
         "'--blacklist ~8888 svg _ svgparse_' blocks non-8888 SVGs that contain \"svgparse_\" in "
                                             "the name.");
 
-DEFINE_string2(readPath, r, "", "If set check for equality with golden results in this directory.");
+static DEFINE_string2(readPath, r, "",
+                      "If set check for equality with golden results in this directory.");
+DEFINE_string2(writePath, w, "", "If set, write bitmaps here as .pngs.");
 
-DEFINE_string(uninterestingHashesFile, "",
+
+static DEFINE_string(uninterestingHashesFile, "",
         "File containing a list of uninteresting hashes. If a result hashes to something in "
         "this list, no image is written for that result.");
 
-DEFINE_int32(shards, 1, "We're splitting source data into this many shards.");
-DEFINE_int32(shard,  0, "Which shard do I run?");
+static DEFINE_int(shards, 1, "We're splitting source data into this many shards.");
+static DEFINE_int(shard,  0, "Which shard do I run?");
 
-DEFINE_string(mskps, "", "Directory to read mskps from, or a single mskp file.");
-DEFINE_bool(forceRasterPipeline, false, "sets gSkForceRasterPipelineBlitter");
+static DEFINE_string(mskps, "", "Directory to read mskps from, or a single mskp file.");
+static DEFINE_bool(forceRasterPipeline, false, "sets gSkForceRasterPipelineBlitter");
 
-DEFINE_string(bisect, "",
+static DEFINE_string(bisect, "",
         "Pair of: SKP file to bisect, followed by an l/r bisect trail string (e.g., 'lrll'). The "
         "l/r trail specifies which half to keep at each step of a binary search through the SKP's "
         "paths. An empty string performs no bisect. Only the SkPaths are bisected; all other draws "
         "are thrown out. This is useful for finding a reduced repo case for path drawing bugs.");
 
-DEFINE_bool(ignoreSigInt, false, "ignore SIGINT signals during test execution");
+static DEFINE_bool(ignoreSigInt, false, "ignore SIGINT signals during test execution");
 
-DEFINE_string(dont_write, "", "File extensions to skip writing to --writePath.");  // See skia:6821
+static DEFINE_string(dont_write, "", "File extensions to skip writing to --writePath.");  // See skia:6821
 
-DEFINE_bool(gdi, false, "On Windows, use GDI instead of DirectWrite for font rendering.");
+static DEFINE_bool(checkF16, false, "Ensure that F16Norm pixels are clamped.");
+
+static DEFINE_string(colorImages, "",
+              "List of images and/or directories to decode with color correction. "
+              "A directory with no images is treated as a fatal error.");
+
+static DEFINE_bool2(veryVerbose, V, false, "tell individual tests to be verbose.");
+
+static DEFINE_bool(cpu, true, "master switch for running CPU-bound work.");
+static DEFINE_bool(gpu, true, "master switch for running GPU-bound work.");
+
+static DEFINE_bool(dryRun, false,
+                   "just print the tests that would be run, without actually running them.");
+
+static DEFINE_string(images, "",
+                     "List of images and/or directories to decode. A directory with no images"
+                     " is treated as a fatal error.");
+
+static DEFINE_bool(simpleCodec, false,
+                   "Runs of a subset of the codec tests, "
+                   "with no scaling or subsetting, always using the canvas color type.");
+
+static DEFINE_string2(match, m, nullptr,
+               "[~][^]substring[$] [...] of name to run.\n"
+               "Multiple matches may be separated by spaces.\n"
+               "~ causes a matching name to always be skipped\n"
+               "^ requires the start of the name to match\n"
+               "$ requires the end of the name to match\n"
+               "^ and $ requires an exact match\n"
+               "If a name does not match any list entry,\n"
+               "it is skipped unless some list entry starts with ~");
+
+static DEFINE_bool2(quiet, q, false, "if true, don't print status updates.");
+static DEFINE_bool2(verbose, v, false, "enable verbose output from the test driver.");
+
+static DEFINE_string(skps, "skps", "Directory to read skps from.");
+static DEFINE_string(lotties, "lotties", "Directory to read (Bodymovin) jsons from.");
+static DEFINE_string(svgs, "", "Directory to read SVGs from, or a single SVG file.");
+
+static DEFINE_int_2(threads, j, -1,
+               "Run threadsafe tests on a threadpool with this many extra threads, "
+               "defaulting to one extra thread per core.");
+
+static DEFINE_string(key, "",
+                     "Space-separated key/value pairs to add to JSON identifying this builder.");
+static DEFINE_string(properties, "",
+                     "Space-separated key/value pairs to add to JSON identifying this run.");
+
 
 using namespace DM;
 using sk_gpu_test::GrContextFactory;
@@ -108,6 +152,15 @@
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 
+static constexpr skcms_TransferFunction k2020_TF =
+    {2.22222f, 0.909672f, 0.0903276f, 0.222222f, 0.0812429f, 0, 0};
+
+static sk_sp<SkColorSpace> rec2020() {
+    return SkColorSpace::MakeRGB(k2020_TF, SkNamedGamut::kRec2020);
+}
+
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
 static FILE* gVLog;
 
 template <typename... Args>
@@ -149,6 +202,12 @@
     }
 };
 
+static void dump_json() {
+    if (!FLAGS_writePath.isEmpty()) {
+        JsonWriter::DumpJson(FLAGS_writePath[0], FLAGS_key, FLAGS_properties);
+    }
+}
+
 // We use a spinlock to make locking this in a signal handler _somewhat_ safe.
 static SkSpinlock        gMutex;
 static int               gPending;
@@ -172,7 +231,7 @@
     // We write out dm.json file and print out a progress update every once in a while.
     // Notice this also handles the final dm.json and progress update when pending == 0.
     if (pending % 500 == 0) {
-        JsonWriter::DumpJson();
+        dump_json();
 
         int curr = sk_tools::getCurrResidentSetSizeMB(),
             peak = sk_tools::getMaxResidentSetSizeMB();
@@ -280,8 +339,13 @@
     #endif
         fflush(stdout);
 
-        signal(sig, previous_handler[sig]);
-        raise(sig);
+        if (sig == SIGINT && FLAGS_ignoreSigInt) {
+            info("Ignoring signal %d because of --ignoreSigInt.\n"
+                 "This is probably a sign the bot is overloaded with work.\n", sig);
+        } else {
+            signal(sig, previous_handler[sig]);
+            raise(sig);
+        }
     }
 
     static void setup_crash_handler() {
@@ -289,10 +353,6 @@
         for (int sig : kSignals) {
             previous_handler[sig] = signal(sig, crash_handler);
         }
-
-        if (FLAGS_ignoreSigInt) {
-            signal(SIGINT, SIG_IGN);
-        }
     }
 #endif
 
@@ -387,9 +447,8 @@
 
 static void push_src(const char* tag, ImplicitString options, Src* s) {
     std::unique_ptr<Src> src(s);
-    if (in_shard() &&
-        FLAGS_src.contains(tag) &&
-        !SkCommandLineFlags::ShouldSkip(FLAGS_match, src->name().c_str())) {
+    if (in_shard() && FLAGS_src.contains(tag) &&
+        !CommandLineFlags::ShouldSkip(FLAGS_match, src->name().c_str())) {
         TaggedSrc& s = gSrcs.push_back();
         s.reset(src.release());
         s.tag = tag;
@@ -754,8 +813,9 @@
 }
 
 template <typename T>
-void gather_file_srcs(const SkCommandLineFlags::StringArray& flags, const char* ext,
-                      const char* src_name = nullptr) {
+void gather_file_srcs(const CommandLineFlags::StringArray& flags,
+                      const char*                          ext,
+                      const char*                          src_name = nullptr) {
     if (!src_name) {
         // With the exception of Lottie files, the source name is the extension.
         src_name = ext;
@@ -808,29 +868,13 @@
     }
 
     for (auto colorImage : colorImages) {
-        ColorCodecSrc* src = new ColorCodecSrc(colorImage, ColorCodecSrc::kBaseline_Mode,
-                                               kN32_SkColorType);
-        push_src("colorImage", "color_codec_baseline", src);
-
-        src = new ColorCodecSrc(colorImage, ColorCodecSrc::kDst_HPZR30w_Mode, kN32_SkColorType);
-        push_src("colorImage", "color_codec_HPZR30w", src);
-        // TODO (msarett):
-        // Should we test this Dst in F16 mode (even though the Dst gamma is 2.2 instead of sRGB)?
-
-        src = new ColorCodecSrc(colorImage, ColorCodecSrc::kDst_sRGB_Mode, kN32_SkColorType);
-        push_src("colorImage", "color_codec_sRGB_kN32", src);
-        src = new ColorCodecSrc(colorImage, ColorCodecSrc::kDst_sRGB_Mode, kRGBA_F16_SkColorType);
-        push_src("colorImage", "color_codec_sRGB_kF16", src);
+        push_src("colorImage", "decode_native", new ColorCodecSrc(colorImage, false));
+        push_src("colorImage", "decode_to_dst", new ColorCodecSrc(colorImage,  true));
     }
 
     return true;
 }
 
-static sk_sp<SkColorSpace> rec2020() {
-    return SkColorSpace::MakeRGB({2.22222f, 0.909672f, 0.0903276f, 0.222222f, 0.0812429f, 0, 0},
-                                 SkNamedGamut::kRec2020);
-}
-
 static void push_sink(const SkCommandLineConfig& config, Sink* s) {
     std::unique_ptr<Sink> sink(s);
 
@@ -858,6 +902,10 @@
     ts.tag = config.getTag();
 }
 
+static sk_sp<SkColorSpace> rgb_to_gbr() {
+    return SkColorSpace::MakeSRGB()->makeColorSpin();
+}
+
 static Sink* create_sink(const GrContextOptions& grCtxOptions, const SkCommandLineConfig* config) {
     if (FLAGS_gpu) {
         if (const SkCommandLineConfigGpu* gpuConfig = config->asConfigGpu()) {
@@ -881,7 +929,8 @@
                         contextType, contextOverrides, gpuConfig->getSurfType(),
                         gpuConfig->getSamples(), gpuConfig->getUseDIText(),
                         gpuConfig->getColorType(), gpuConfig->getAlphaType(),
-                        sk_ref_sp(gpuConfig->getColorSpace()), FLAGS_gpu_threading, grCtxOptions);
+                        sk_ref_sp(gpuConfig->getColorSpace()), FLAGS_gpu_threading, grCtxOptions,
+                        gpuConfig->getTestPersistentCache());
             } else {
                 return new GPUSink(contextType, contextOverrides, gpuConfig->getSurfType(),
                                    gpuConfig->getSamples(), gpuConfig->getUseDIText(),
@@ -928,6 +977,7 @@
         SINK(     "f16",  RasterSink,  kRGBA_F16_SkColorType, srgbLinear);
         SINK(    "srgb",  RasterSink, kRGBA_8888_SkColorType, srgb      );
         SINK(   "esrgb",  RasterSink,  kRGBA_F16_SkColorType, srgb      );
+        SINK(   "esgbr",  RasterSink,  kRGBA_F16_SkColorType, rgb_to_gbr());
         SINK(  "narrow",  RasterSink, kRGBA_8888_SkColorType, narrow    );
         SINK( "enarrow",  RasterSink,  kRGBA_F16_SkColorType, narrow    );
         SINK(      "p3",  RasterSink, kRGBA_8888_SkColorType, p3        );
@@ -935,21 +985,16 @@
         SINK( "rec2020",  RasterSink, kRGBA_8888_SkColorType, rec2020() );
         SINK("erec2020",  RasterSink,  kRGBA_F16_SkColorType, rec2020() );
 
+        SINK("f16norm",  RasterSink,  kRGBA_F16Norm_SkColorType, srgb);
+
         SINK(    "f32",  RasterSink,  kRGBA_F32_SkColorType, srgbLinear);
     }
 #undef SINK
     return nullptr;
 }
 
-static sk_sp<SkColorSpace> rgb_to_gbr() {
-    return SkColorSpace::MakeSRGB()->makeColorSpin();
-}
-
 static Sink* create_via(const SkString& tag, Sink* wrapped) {
 #define VIA(t, via, ...) if (tag.equals(t)) return new via(__VA_ARGS__)
-    VIA("gbr",       ViaCSXform,           wrapped, rgb_to_gbr(), true);
-    VIA("p3",        ViaCSXform,           wrapped,
-                     SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3), false);
     VIA("lite",      ViaLite,              wrapped);
 #ifdef TEST_VIA_SVG
     VIA("svg",       ViaSVG,               wrapped);
@@ -1018,49 +1063,6 @@
     return false;
 }
 
-static bool dump_png(SkBitmap bitmap, const char* path, const char* md5) {
-    SkPixmap pm;
-    if (!bitmap.peekPixels(&pm)) {
-        return false;  // Ought to never happen... we're already read-back at this point.
-    }
-    SkFILEWStream dst{path};
-
-    SkString description;
-    description.append("Key: ");
-    for (int i = 0; i < FLAGS_key.count(); i++) {
-        description.appendf("%s ", FLAGS_key[i]);
-    }
-    description.append("Properties: ");
-    for (int i = 0; i < FLAGS_properties.count(); i++) {
-        description.appendf("%s ", FLAGS_properties[i]);
-    }
-    description.appendf("MD5: %s", md5);
-
-    const char* comments[] = {
-        "Author",       "DM dump_png()",
-        "Description",  description.c_str(),
-    };
-    size_t lengths[] = {
-        strlen(comments[0])+1, strlen(comments[1])+1,
-        strlen(comments[2])+1, strlen(comments[3])+1,
-    };
-
-    // PNGs can't hold out-of-gamut values, so if we're likely to be holding them,
-    // convert to a wide gamut, giving us the best chance to have the PNG look like our colors.
-    SkBitmap wide;
-    if (pm.colorType() >= kRGBA_F16_SkColorType) {
-        wide.allocPixels(pm.info().makeColorSpace(rec2020()));
-        SkAssertResult(wide.writePixels(pm, 0,0));
-        SkAssertResult(wide.peekPixels(&pm));
-    }
-
-    SkPngEncoder::Options options;
-    options.fComments         = SkDataTable::MakeCopyArrays((const void**)comments, lengths, 4);
-    options.fFilterFlags      = SkPngEncoder::FilterFlag::kNone;
-    options.fZLibLevel        = 1;
-    return SkPngEncoder::Encode(&dst, pm, options);
-}
-
 static bool match(const char* needle, const char* haystack) {
     if ('~' == needle[0]) {
         return !match(needle + 1, haystack);
@@ -1132,6 +1134,8 @@
             gDefinitelyThreadSafeWork.add([task,name,bitmap,data]{
                 std::unique_ptr<SkStreamAsset> ownedData(data);
 
+                std::unique_ptr<HashAndEncode> hashAndEncode;
+
                 SkString md5;
                 if (!FLAGS_writePath.isEmpty() || !FLAGS_readPath.isEmpty()) {
                     SkMD5 hash;
@@ -1139,21 +1143,10 @@
                         hash.writeStream(data, data->getLength());
                         data->rewind();
                     } else {
-                        // If we're BGRA (Linux, Windows), swizzle over to RGBA (Mac, Android).
-                        // This helps eliminate multiple 0-pixel-diff hashes on gold.skia.org.
-                        // (Android's general slow speed breaks the tie arbitrarily in RGBA's favor.)
-                        // We might consider promoting 565 to RGBA too.
-                        if (bitmap.colorType() == kBGRA_8888_SkColorType) {
-                            SkBitmap swizzle;
-                            SkAssertResult(sk_tool_utils::copy_to(&swizzle, kRGBA_8888_SkColorType,
-                                                                  bitmap));
-                            hash.write(swizzle.getPixels(), swizzle.computeByteSize());
-                        } else {
-                            hash.write(bitmap.getPixels(), bitmap.computeByteSize());
-                        }
+                        hashAndEncode.reset(new HashAndEncode(bitmap));
+                        hashAndEncode->write(&hash);
                     }
-                    SkMD5::Digest digest;
-                    hash.finish(digest);
+                    SkMD5::Digest digest = hash.finish();
                     for (int i = 0; i < 16; i++) {
                         md5.appendf("%02x", digest.data[i]);
                     }
@@ -1175,10 +1168,26 @@
                     const char* ext = task.sink->fileExtension();
                     if (ext && !FLAGS_dont_write.contains(ext)) {
                         if (data->getLength()) {
-                            WriteToDisk(task, md5, ext, data, data->getLength(), nullptr);
+                            WriteToDisk(task, md5, ext, data, data->getLength(), nullptr, nullptr);
                             SkASSERT(bitmap.drawsNothing());
                         } else if (!bitmap.drawsNothing()) {
-                            WriteToDisk(task, md5, ext, nullptr, 0, &bitmap);
+                            WriteToDisk(task, md5, ext, nullptr, 0, &bitmap, hashAndEncode.get());
+                        }
+                    }
+                }
+
+                SkPixmap pm;
+                if (FLAGS_checkF16 && bitmap.colorType() == kRGBA_F16Norm_SkColorType &&
+                        bitmap.peekPixels(&pm)) {
+                    bool unclamped = false;
+                    for (int y = 0; y < pm.height() && !unclamped; ++y)
+                    for (int x = 0; x < pm.width() && !unclamped; ++x) {
+                        Sk4f rgba = SkHalfToFloat_finite_ftz(*pm.addr64(x, y));
+                        float a = rgba[3];
+                        if (a > 1.0f || (rgba < 0.0f).anyTrue() || (rgba > a).anyTrue()) {
+                            SkDebugf("[%s] F16Norm pixel [%d, %d] unclamped: (%g, %g, %g, %g)\n",
+                                     name.c_str(), x, y, rgba[0], rgba[1], rgba[2], rgba[3]);
+                            unclamped = true;
                         }
                     }
                 }
@@ -1187,14 +1196,66 @@
         done(task.sink.tag.c_str(), task.src.tag.c_str(), task.src.options.c_str(), name.c_str());
     }
 
+    static SkString identify_gamut(SkColorSpace* cs) {
+        if (!cs) {
+            return SkString("untagged");
+        }
+
+        skcms_Matrix3x3 gamut;
+        if (cs->toXYZD50(&gamut)) {
+            auto eq = [](skcms_Matrix3x3 x, skcms_Matrix3x3 y) {
+                for (int i = 0; i < 3; i++)
+                for (int j = 0; j < 3; j++) {
+                    if (x.vals[i][j] != y.vals[i][j]) { return false; }
+                }
+                return true;
+            };
+
+            if (eq(gamut, SkNamedGamut::kSRGB    )) { return SkString("sRGB"); }
+            if (eq(gamut, SkNamedGamut::kAdobeRGB)) { return SkString("Adobe"); }
+            if (eq(gamut, SkNamedGamut::kDCIP3   )) { return SkString("P3"); }
+            if (eq(gamut, SkNamedGamut::kRec2020 )) { return SkString("2020"); }
+            if (eq(gamut, SkNamedGamut::kXYZ     )) { return SkString("XYZ"); }
+            if (eq(gamut,     gNarrow_toXYZD50   )) { return SkString("narrow"); }
+            return SkString("other");
+        }
+        return SkString("non-XYZ");
+    }
+
+    static SkString identify_transfer_fn(SkColorSpace* cs) {
+        if (!cs) {
+            return SkString("untagged");
+        }
+
+        skcms_TransferFunction tf;
+        if (cs->isNumericalTransferFn(&tf)) {
+            auto eq = [](skcms_TransferFunction x, skcms_TransferFunction y) {
+                return x.g == y.g
+                    && x.a == y.a
+                    && x.b == y.b
+                    && x.c == y.c
+                    && x.d == y.d
+                    && x.e == y.e
+                    && x.f == y.f;
+            };
+
+            if (tf.a == 1 && tf.b == 0 && tf.c == 0 && tf.d == 0 && tf.e == 0 && tf.f == 0) {
+                return SkStringPrintf("gamma %.3g", tf.g);
+            }
+            if (eq(tf, SkNamedTransferFn::kSRGB)) { return SkString("sRGB"); }
+            if (eq(tf, k2020_TF                )) { return SkString("2020"); }
+            return SkStringPrintf("%.3g %.3g %.3g %.3g %.3g %.3g %.3g",
+                                  tf.g, tf.a, tf.b, tf.c, tf.d, tf.e, tf.f);
+        }
+        return SkString("non-numeric");
+    }
+
     static void WriteToDisk(const Task& task,
                             SkString md5,
                             const char* ext,
                             SkStream* data, size_t len,
-                            const SkBitmap* bitmap) {
-        bool gammaCorrect = bitmap &&
-                            bitmap->info().colorSpace() &&
-                            bitmap->info().colorSpace()->gammaIsLinear();
+                            const SkBitmap* bitmap,
+                            const HashAndEncode* hashAndEncode) {
 
         JsonWriter::BitmapResult result;
         result.name          = task.src->name();
@@ -1202,8 +1263,14 @@
         result.sourceType    = task.src.tag;
         result.sourceOptions = task.src.options;
         result.ext           = ext;
-        result.gammaCorrect  = gammaCorrect;
         result.md5           = md5;
+        if (bitmap) {
+            result.gamut         = identify_gamut            (bitmap->colorSpace());
+            result.transferFn    = identify_transfer_fn      (bitmap->colorSpace());
+            result.colorType     = ToolUtils::colortype_name (bitmap->colorType());
+            result.alphaType     = ToolUtils::alphatype_name (bitmap->alphaType());
+            result.colorDepth    = ToolUtils::colortype_depth(bitmap->colorType());
+        }
         JsonWriter::AddBitmapResult(result);
 
         // If an MD5 is uninteresting, we want it noted in the JSON file,
@@ -1213,8 +1280,9 @@
         }
 
         const char* dir = FLAGS_writePath[0];
+        SkString resources = GetResourcePath();
         if (0 == strcmp(dir, "@")) {  // Needed for iOS.
-            dir = FLAGS_resourcePath[0];
+            dir = resources.c_str();
         }
         sk_mkdir(dir);
 
@@ -1241,7 +1309,11 @@
         }
 
         if (bitmap) {
-            if (!dump_png(*bitmap, path.c_str(), result.md5.c_str())) {
+            SkASSERT(hashAndEncode);
+            if (!hashAndEncode->writePngTo(path.c_str(),
+                                           result.md5.c_str(),
+                                           FLAGS_key,
+                                           FLAGS_properties)) {
                 fail(SkStringPrintf("Can't encode PNG to %s.\n", path.c_str()));
                 return;
             }
@@ -1273,7 +1345,7 @@
         if (!in_shard()) {
             continue;
         }
-        if (SkCommandLineFlags::ShouldSkip(FLAGS_match, test.name)) {
+        if (CommandLineFlags::ShouldSkip(FLAGS_match, test.name)) {
             continue;
         }
         if (test.needsGpu && FLAGS_gpu) {
@@ -1288,7 +1360,6 @@
     struct : public skiatest::Reporter {
         void reportFailed(const skiatest::Failure& failure) override {
             fail(failure.toString());
-            JsonWriter::AddTestFailure(failure);
         }
         bool allowExtendedTest() const override {
             return FLAGS_pathOpsExtended;
@@ -1300,6 +1371,7 @@
         GrContextOptions options = grCtxOptions;
         test.modifyGrContextOptions(&options);
 
+        skiatest::ReporterContext ctx(&reporter, SkString(test.name));
         start("unit", "test", "", test.name);
         test.run(&reporter, options);
     }
@@ -1312,17 +1384,7 @@
 #if defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) && defined(SK_HAS_HEIF_LIBRARY)
     android::ProcessState::self()->startThreadPool();
 #endif
-    SkCommandLineFlags::Parse(argc, argv);
-
-    if (!FLAGS_nativeFonts) {
-        gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
-    }
-
-#if defined(SK_BUILD_FOR_WIN)
-    if (FLAGS_gdi) {
-        gSkFontMgr_DefaultFactory = &SkFontMgr_New_GDI;
-    }
-#endif
+    CommandLineFlags::Parse(argc, argv);
 
     initializeEventTracingForTools();
 
@@ -1332,15 +1394,9 @@
     setbuf(stdout, nullptr);
     setup_crash_handler();
 
-    gSkUseAnalyticAA = FLAGS_analyticAA;
-    gSkUseDeltaAA = FLAGS_deltaAA;
+    ToolUtils::SetDefaultFontMgr();
+    SetAnalyticAAFromCommonFlags();
 
-    if (FLAGS_forceAnalyticAA) {
-        gSkForceAnalyticAA = true;
-    }
-    if (FLAGS_forceDeltaAA) {
-        gSkForceDeltaAA = true;
-    }
     if (FLAGS_forceRasterPipeline) {
         gSkForceRasterPipelineBlitter = true;
     }
@@ -1357,7 +1413,8 @@
     GrContextOptions grCtxOptions;
     SetCtxOptionsFromCommonFlags(&grCtxOptions);
 
-    JsonWriter::DumpJson();  // It's handy for the bots to assume this is ~never missing.
+    dump_json();  // It's handy for the bots to assume this is ~never missing.
+
     SkAutoGraphics ag;
     SkTaskGroup::Enabler enabled(FLAGS_threads);
 
@@ -1425,7 +1482,7 @@
     // We'd better have run everything.
     SkASSERT(gPending == 0);
     // Make sure we've flushed all our results to disk.
-    JsonWriter::DumpJson();
+    dump_json();
 
     if (gFailures.count() > 0) {
         info("Failures:\n");
@@ -1433,6 +1490,11 @@
             info("\t%s\n", gFailures[i].c_str());
         }
         info("%d failures\n", gFailures.count());
+        // A non-zero return code does not make it to Swarming
+        // An abort does.
+#ifdef SK_BUILD_FOR_IOS
+        SK_ABORT("There were failures!");
+#endif
         return 1;
     }
 
diff --git a/dm/DMGpuTestProcs.cpp b/dm/DMGpuTestProcs.cpp
index f17c89a..2be645a 100644
--- a/dm/DMGpuTestProcs.cpp
+++ b/dm/DMGpuTestProcs.cpp
@@ -25,8 +25,8 @@
 bool IsRenderingGLContextType(sk_gpu_test::GrContextFactory::ContextType type) {
     return IsGLContextType(type) && GrContextFactory::IsRenderingContext(type);
 }
-bool IsNullGLContextType(sk_gpu_test::GrContextFactory::ContextType type) {
-    return type == GrContextFactory::kNullGL_ContextType;
+bool IsMockContextType(sk_gpu_test::GrContextFactory::ContextType type) {
+    return type == GrContextFactory::kMock_ContextType;
 }
 
 void RunWithGPUTestContexts(GrContextTestFn* test, GrContextTypeFilterFn* contextTypeFilter,
diff --git a/dm/DMJsonWriter.cpp b/dm/DMJsonWriter.cpp
index d3b9aa0..1879666 100644
--- a/dm/DMJsonWriter.cpp
+++ b/dm/DMJsonWriter.cpp
@@ -8,7 +8,6 @@
 #include "DMJsonWriter.h"
 
 #include "ProcStats.h"
-#include "SkCommonFlags.h"
 #include "SkData.h"
 #include "SkJSON.h"
 #include "SkJSONWriter.h"
@@ -28,33 +27,27 @@
     gBitmapResults.push_back(result);
 }
 
-SkTArray<skiatest::Failure> gFailures;
-SK_DECLARE_STATIC_MUTEX(gFailureLock);
-
-void JsonWriter::AddTestFailure(const skiatest::Failure& failure) {
-    SkAutoMutexAcquire lock(gFailureLock);
-    gFailures.push_back(failure);
-}
-
-void JsonWriter::DumpJson() {
-    if (FLAGS_writePath.isEmpty()) {
+void JsonWriter::DumpJson(const char* dir,
+                          CommandLineFlags::StringArray key,
+                          CommandLineFlags::StringArray properties) {
+    if (0 == strcmp(dir, "")) {
         return;
     }
 
-    SkString path = SkOSPath::Join(FLAGS_writePath[0], "dm.json");
-    sk_mkdir(FLAGS_writePath[0]);
+    SkString path = SkOSPath::Join(dir, "dm.json");
+    sk_mkdir(dir);
     SkFILEWStream stream(path.c_str());
     SkJSONWriter writer(&stream, SkJSONWriter::Mode::kPretty);
 
     writer.beginObject(); // root
 
-    for (int i = 1; i < FLAGS_properties.count(); i += 2) {
-        writer.appendString(FLAGS_properties[i-1], FLAGS_properties[i]);
+    for (int i = 1; i < properties.count(); i += 2) {
+        writer.appendString(properties[i-1], properties[i]);
     }
 
     writer.beginObject("key");
-    for (int i = 1; i < FLAGS_key.count(); i += 2) {
-        writer.appendString(FLAGS_key[i-1], FLAGS_key[i]);
+    for (int i = 1; i < key.count(); i += 2) {
+        writer.appendString(key[i-1], key[i]);
     }
     writer.endObject();
 
@@ -82,8 +75,12 @@
             writer.endObject(); // key
 
             writer.beginObject("options");
-            writer.appendString("ext"          , gBitmapResults[i].ext.c_str());
-            writer.appendString("gamma_correct", gBitmapResults[i].gammaCorrect ? "yes" : "no");
+            writer.appendString("ext"  ,       gBitmapResults[i].ext.c_str());
+            writer.appendString("gamut",       gBitmapResults[i].gamut.c_str());
+            writer.appendString("transfer_fn", gBitmapResults[i].transferFn.c_str());
+            writer.appendString("color_type",  gBitmapResults[i].colorType.c_str());
+            writer.appendString("alpha_type",  gBitmapResults[i].alphaType.c_str());
+            writer.appendString("color_depth", gBitmapResults[i].colorDepth.c_str());
             writer.endObject(); // options
 
             writer.appendString("md5", gBitmapResults[i].md5.c_str());
@@ -93,24 +90,6 @@
         writer.endArray(); // results
     }
 
-    {
-        SkAutoMutexAcquire lock(gFailureLock);
-        if (gFailures.count() > 0) {
-            writer.beginObject("test_results");
-            writer.beginArray("failures");
-            for (int i = 0; i < gFailures.count(); i++) {
-                writer.beginObject();
-                writer.appendString("file_name", gFailures[i].fileName);
-                writer.appendS32   ("line_no"  , gFailures[i].lineNo);
-                writer.appendString("condition", gFailures[i].condition);
-                writer.appendString("message"  , gFailures[i].message.c_str());
-                writer.endObject(); // 1 failure
-            }
-            writer.endArray(); // failures
-            writer.endObject(); // test_results
-        }
-    }
-
     writer.endObject(); // root
     writer.flush();
     stream.flush();
@@ -144,7 +123,11 @@
         br.config       = key["config"].as<StringValue>().begin();
         br.sourceType   = key["source_type"].as<StringValue>().begin();
         br.ext          = options["ext"].as<StringValue>().begin();
-        br.gammaCorrect = 0 == strcmp("yes", options["gamma_correct"].as<StringValue>().begin());
+        br.gamut        = options["gamut"].as<StringValue>().begin();
+        br.transferFn   = options["transfer_fn"].as<StringValue>().begin();
+        br.colorType    = options["color_type"].as<StringValue>().begin();
+        br.alphaType    = options["alpha_type"].as<StringValue>().begin();
+        br.colorDepth   = options["color_depth"].as<StringValue>().begin();
         br.md5          = (*r)["md5"].as<StringValue>().begin();
 
         if (const StringValue* so = key["source_options"]) {
diff --git a/dm/DMJsonWriter.h b/dm/DMJsonWriter.h
index 68e27a5..c8cd3fc 100644
--- a/dm/DMJsonWriter.h
+++ b/dm/DMJsonWriter.h
@@ -8,8 +8,8 @@
 #ifndef DMJsonWriter_DEFINED
 #define DMJsonWriter_DEFINED
 
+#include "CommandLineFlags.h"
 #include "SkString.h"
-#include "Test.h"
 
 namespace DM {
 
@@ -29,7 +29,11 @@
         SkString sourceOptions;   //      "image", "codec", "subset", "scanline"
         SkString md5;             // In ASCII, so 32 bytes long.
         SkString ext;             // Extension of file we wrote: "png", "pdf", ...
-        bool gammaCorrect;        // Old configs are not gamma correct, some new ones are.
+        SkString gamut;
+        SkString transferFn;
+        SkString colorType;
+        SkString alphaType;
+        SkString colorDepth;
     };
 
     /**
@@ -38,14 +42,11 @@
     static void AddBitmapResult(const BitmapResult&);
 
     /**
-     *  Add a Failure from a Test.
+     *  Write all collected results to the file dir/dm.json.
      */
-    static void AddTestFailure(const skiatest::Failure&);
-
-    /**
-     *  Write all collected results to the file FLAGS_writePath[0]/dm.json.
-     */
-    static void DumpJson();
+    static void DumpJson(const char* dir,
+                         CommandLineFlags::StringArray key,
+                         CommandLineFlags::StringArray properties);
 
     /**
      * Read JSON file at path written by DumpJson, calling callback for each
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index 17ec172..86a51ef 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -8,6 +8,7 @@
 #include "DMSrcSink.h"
 #include "DDLPromiseImageHelper.h"
 #include "DDLTileHelper.h"
+#include "DebugCanvas.h"
 #include "GrBackendSurface.h"
 #include "GrContextPriv.h"
 #include "GrGpu.h"
@@ -19,11 +20,7 @@
 #include "SkCodec.h"
 #include "SkCodecImageGenerator.h"
 #include "SkColorSpace.h"
-#include "SkColorSpaceXformCanvas.h"
-#include "SkCommonFlags.h"
-#include "SkCommonFlagsGpu.h"
 #include "SkData.h"
-#include "SkDebugCanvas.h"
 #include "SkDeferredDisplayListRecorder.h"
 #include "SkDocument.h"
 #include "SkExecutor.h"
@@ -41,10 +38,10 @@
 #include "SkOSFile.h"
 #include "SkOSPath.h"
 #include "SkOpts.h"
+#include "SkPDFDocument.h"
 #include "SkPictureCommon.h"
 #include "SkPictureData.h"
 #include "SkPictureRecorder.h"
-#include "SkPDFDocument.h"
 #include "SkRandom.h"
 #include "SkRecordDraw.h"
 #include "SkRecorder.h"
@@ -79,9 +76,11 @@
 
 #include "../third_party/skcms/skcms.h"
 
-DEFINE_bool(multiPage, false, "For document-type backends, render the source"
-            " into multiple pages");
-DEFINE_bool(RAW_threading, true, "Allow RAW decodes to run on multiple threads?");
+static DEFINE_bool(multiPage, false,
+                   "For document-type backends, render the source into multiple pages");
+static DEFINE_bool(RAW_threading, true, "Allow RAW decodes to run on multiple threads?");
+
+DECLARE_int(gpuThreads);
 
 using sk_gpu_test::GrContextFactory;
 
@@ -91,8 +90,12 @@
 
 Error GMSrc::draw(SkCanvas* canvas) const {
     std::unique_ptr<skiagm::GM> gm(fFactory(nullptr));
-    gm->draw(canvas);
-    return "";
+    SkString errorMsg;
+    skiagm::DrawResult drawResult = gm->draw(canvas, &errorMsg);
+    if (skiagm::DrawResult::kSkip == drawResult) {
+        return Error::Nonfatal(std::move(errorMsg));  // Cause this test to be skipped.
+    }
+    return errorMsg;
 }
 
 SkISize GMSrc::size() const {
@@ -374,7 +377,6 @@
     bitmap.installPixels(info, pixels, rowBytes);
     swap_rb_if_necessary(bitmap, dstColorType);
     canvas->drawBitmap(bitmap, left, top);
-    canvas->flush();
 }
 
 // For codec srcs, we want the "draw" step to be a memcpy.  Any interesting color space or
@@ -956,50 +958,15 @@
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 
-ColorCodecSrc::ColorCodecSrc(Path path, Mode mode, SkColorType colorType)
-    : fPath(path)
-    , fMode(mode)
-    , fColorType(colorType)
-{}
+ColorCodecSrc::ColorCodecSrc(Path path, bool decode_to_dst) : fPath(path)
+                                                            , fDecodeToDst(decode_to_dst) {}
 
 bool ColorCodecSrc::veto(SinkFlags flags) const {
     // Test to direct raster backends (8888 and 565).
     return flags.type != SinkFlags::kRaster || flags.approach != SinkFlags::kDirect;
 }
 
-void clamp_if_necessary(const SkBitmap& bitmap, SkColorType dstCT) {
-    if (kRGBA_F16_SkColorType != bitmap.colorType() || kRGBA_F16_SkColorType == dstCT) {
-        // No need to clamp if the dst is F16.  We will clamp when we encode to PNG.
-        return;
-    }
-
-    SkRasterPipeline_MemoryCtx ptr = { bitmap.getAddr(0,0), bitmap.rowBytesAsPixels() };
-
-    SkRasterPipeline_<256> p;
-    p.append(SkRasterPipeline::load_f16, &ptr);
-    p.append(SkRasterPipeline::clamp_0);
-    if (kPremul_SkAlphaType == bitmap.alphaType()) {
-        p.append(SkRasterPipeline::clamp_a);
-    } else {
-        p.append(SkRasterPipeline::clamp_1);
-    }
-    p.append(SkRasterPipeline::store_f16, &ptr);
-
-    p.run(0,0, bitmap.width(), bitmap.height());
-}
-
 Error ColorCodecSrc::draw(SkCanvas* canvas) const {
-    if (kRGB_565_SkColorType == canvas->imageInfo().colorType()) {
-        return Error::Nonfatal("No need to test color correction to 565 backend.");
-    }
-
-    bool runInLegacyMode = kBaseline_Mode == fMode;
-    if (runInLegacyMode && canvas->imageInfo().colorSpace()) {
-        return Error::Nonfatal("Skipping tests that are only interesting in legacy mode.");
-    } else if (!runInLegacyMode && !canvas->imageInfo().colorSpace()) {
-        return Error::Nonfatal("Skipping tests that are only interesting in srgb mode.");
-    }
-
     sk_sp<SkData> encoded(SkData::MakeFromFileName(fPath.c_str()));
     if (!encoded) {
         return SkStringPrintf("Couldn't read %s.", fPath.c_str());
@@ -1010,65 +977,30 @@
         return SkStringPrintf("Couldn't create codec for %s.", fPath.c_str());
     }
 
-    // Load the dst ICC profile.  This particular dst is fairly similar to Adobe RGB.
-    sk_sp<SkData> dstData = GetResourceAsData("icc_profiles/HP_ZR30w.icc");
-    if (!dstData) {
-        return "Cannot read monitor profile.  Is the resource path set correctly?";
-    }
-
-    sk_sp<SkColorSpace> dstSpace = nullptr;
-    if (kDst_sRGB_Mode == fMode) {
-        dstSpace = SkColorSpace::MakeSRGB();
-    } else if (kDst_HPZR30w_Mode == fMode) {
-        skcms_ICCProfile profile;
-        SkAssertResult(skcms_Parse(dstData->data(), dstData->size(), &profile));
-        dstSpace = SkColorSpace::Make(profile);
-        SkASSERT(dstSpace);
-    }
-
-    SkImageInfo decodeInfo = codec->getInfo().makeColorType(fColorType).makeColorSpace(dstSpace);
-    if (kUnpremul_SkAlphaType == decodeInfo.alphaType()) {
-        decodeInfo = decodeInfo.makeAlphaType(kPremul_SkAlphaType);
-    }
-
-    SkImageInfo bitmapInfo = decodeInfo;
-    set_bitmap_color_space(&bitmapInfo);
-    if (kRGBA_8888_SkColorType == decodeInfo.colorType() ||
-        kBGRA_8888_SkColorType == decodeInfo.colorType())
-    {
-        bitmapInfo = bitmapInfo.makeColorType(kN32_SkColorType);
+    SkImageInfo info = codec->getInfo();
+    if (fDecodeToDst) {
+        info = canvas->imageInfo().makeWH(info.width(),
+                                          info.height());
     }
 
     SkBitmap bitmap;
-    if (!bitmap.tryAllocPixels(bitmapInfo)) {
-        return SkStringPrintf("Image(%s) is too large (%d x %d)", fPath.c_str(),
-                              bitmapInfo.width(), bitmapInfo.height());
+    if (!bitmap.tryAllocPixels(info)) {
+        return SkStringPrintf("Image(%s) is too large (%d x %d)",
+                              fPath.c_str(), info.width(), info.height());
     }
 
-    size_t rowBytes = bitmap.rowBytes();
-    SkCodec::Result r = codec->getPixels(decodeInfo, bitmap.getPixels(), rowBytes);
-    switch (r) {
+    switch (auto r = codec->getPixels(info, bitmap.getPixels(), bitmap.rowBytes())) {
         case SkCodec::kSuccess:
         case SkCodec::kErrorInInput:
         case SkCodec::kIncompleteInput:
-            break;
+            canvas->drawBitmap(bitmap, 0,0);
+            return "";
+        case SkCodec::kInvalidConversion:
+            // TODO(mtklein): why are there formats we can't decode to?
+            return Error::Nonfatal("SkCodec can't decode to this format.");
         default:
             return SkStringPrintf("Couldn't getPixels %s. Error code %d", fPath.c_str(), r);
     }
-
-    switch (fMode) {
-        case kBaseline_Mode:
-        case kDst_sRGB_Mode:
-        case kDst_HPZR30w_Mode:
-            // We do not support drawing unclamped F16.
-            clamp_if_necessary(bitmap, canvas->imageInfo().colorType());
-            canvas->drawBitmap(bitmap, 0, 0);
-            break;
-        default:
-            SkASSERT(false);
-            return "Invalid fMode";
-    }
-    return "";
 }
 
 SkISize ColorCodecSrc::size() const {
@@ -1086,6 +1018,9 @@
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 
+static DEFINE_int(skpViewportSize, 1000,
+                  "Width & height of the viewport used to crop skp rendering.");
+
 SKPSrc::SKPSrc(Path path) : fPath(path) { }
 
 Error SKPSrc::draw(SkCanvas* canvas) const {
@@ -1390,7 +1325,15 @@
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 
-DEFINE_bool(gpuStats, false, "Append GPU stats to the log for each GPU task?");
+static DEFINE_bool(gpuStats, false, "Append GPU stats to the log for each GPU task?");
+static DEFINE_bool(preAbandonGpuContext, false,
+                   "Test abandoning the GrContext before running the test.");
+static DEFINE_bool(abandonGpuContext, false,
+                   "Test abandoning the GrContext after running each test.");
+static DEFINE_bool(releaseAndAbandonGpuContext, false,
+                   "Test releasing all gpu resources and abandoning the GrContext "
+                   "after running each test");
+static DEFINE_bool(drawOpClip, false, "Clip each GrDrawOp to its device bounds for testing.");
 
 GPUSink::GPUSink(GrContextFactory::ContextType ct,
                  GrContextFactory::ContextOverrides overrides,
@@ -1413,8 +1356,6 @@
         , fThreaded(threaded)
         , fBaseContextOptions(grCtxOptions) {}
 
-DEFINE_bool(drawOpClip, false, "Clip each GrDrawOp to its device bounds for testing.");
-
 Error GPUSink::draw(const Src& src, SkBitmap* dst, SkWStream* dstStream, SkString* log) const {
     return this->onDraw(src, dst, dstStream, log, fBaseContextOptions);
 }
@@ -1479,7 +1420,7 @@
     if (!err.isEmpty()) {
         return err;
     }
-    canvas->flush();
+    surface->flush();
     if (FLAGS_gpuStats) {
         canvas->getGrContext()->priv().dumpCacheStats(log);
         canvas->getGrContext()->priv().dumpGpuStats(log);
@@ -1569,9 +1510,11 @@
         SkAlphaType alphaType,
         sk_sp<SkColorSpace> colorSpace,
         bool threaded,
-        const GrContextOptions& grCtxOptions)
+        const GrContextOptions& grCtxOptions,
+        int cacheType)
         : INHERITED(ct, overrides, surfType, samples, diText, colorType, alphaType,
-                    std::move(colorSpace), threaded, grCtxOptions) {}
+                    std::move(colorSpace), threaded, grCtxOptions)
+        , fCacheType(cacheType) {}
 
 Error GPUPersistentCacheTestingSink::draw(const Src& src, SkBitmap* dst, SkWStream* wStream,
                                           SkString* log) const {
@@ -1580,6 +1523,7 @@
     sk_gpu_test::MemoryCache memoryCache;
     GrContextOptions contextOptions = this->baseContextOptions();
     contextOptions.fPersistentCache = &memoryCache;
+    contextOptions.fDisallowGLSLBinaryCaching = (fCacheType == 2);
 
     Error err = this->onDraw(src, dst, wStream, log, contextOptions);
     if (!err.isEmpty() || !dst) {
@@ -1696,7 +1640,7 @@
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 
 Error DebugSink::draw(const Src& src, SkBitmap*, SkWStream* dst, SkString*) const {
-    SkDebugCanvas debugCanvas(src.size().width(), src.size().height());
+    DebugCanvas debugCanvas(src.size().width(), src.size().height());
     Error err = src.draw(&debugCanvas);
     if (!err.isEmpty()) {
         return err;
@@ -1724,11 +1668,10 @@
                                         fPageIndex, pageCount));
         }
     }
-    std::unique_ptr<SkXMLWriter> xmlWriter(new SkXMLStreamWriter(dst));
     return src.draw(fPageIndex,
                     SkSVGCanvas::Make(SkRect::MakeWH(SkIntToScalar(src.size().width()),
                                                      SkIntToScalar(src.size().height())),
-                                      xmlWriter.get())
+                                      dst)
                             .get());
 #else
     (void)fPageIndex;
@@ -1780,7 +1723,7 @@
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 
-DEFINE_bool(check, true, "If true, have most Via- modes fail if they affect the output.");
+static DEFINE_bool(check, true, "If true, have most Via- modes fail if they affect the output.");
 
 // Is *bitmap identical to what you get drawing src into sink?
 static Error check_against_reference(const SkBitmap* bitmap, const Src& src, Sink* sink) {
@@ -1954,15 +1897,7 @@
     // this is our ultimate final drawing area/rect
     SkIRect viewport = SkIRect::MakeWH(size.fWidth, size.fHeight);
 
-    // When we're only doing one replay we exercise the code path that delays calling release
-    // until the SkImage is destroyed.
-    SkDeferredDisplayListRecorder::DelayReleaseCallback delayReleaseCallback;
-    if (fNumReplays > 1) {
-        delayReleaseCallback = SkDeferredDisplayListRecorder::DelayReleaseCallback::kNo;
-    } else {
-        delayReleaseCallback = SkDeferredDisplayListRecorder::DelayReleaseCallback::kYes;
-    }
-    DDLPromiseImageHelper promiseImageHelper(delayReleaseCallback);
+    DDLPromiseImageHelper promiseImageHelper;
     sk_sp<SkData> compressedPictureData = promiseImageHelper.deflateSKP(inputPicture.get());
     if (!compressedPictureData) {
         return SkStringPrintf("ViaDDL: Couldn't deflate SkPicture");
@@ -1975,10 +1910,7 @@
 
         // This is here bc this is the first point where we have access to the context
         promiseImageHelper.uploadAllToGPU(context);
-        // We draw N times, with a clear between. Between each run we invalidate and delete half of
-        // the textures backing promise images. So half the images exercise reusing a cached
-        // GrTexture and the other half exercise the case whem the client provides a different
-        // backing texture in fulfill.
+        // We draw N times, with a clear between.
         for (int replay = 0; replay < fNumReplays; ++replay) {
             if (replay > 0) {
                 // Clear the drawing of the previous replay
@@ -2000,11 +1932,6 @@
                 // This drops the promiseImageHelper's refs on all the promise images if we're in
                 // the last run.
                 promiseImageHelper.reset();
-            } else {
-                // This ought to ensure that all promise image textures from the last pass are
-                // released.
-                context->priv().getGpu()->testingOnly_flushGpuAndSync();
-                promiseImageHelper.replaceEveryOtherPromiseTexture(context);
             }
 
             // Fourth, synchronously render the display lists into the dest tiles
@@ -2097,59 +2024,4 @@
     return check_against_reference(bitmap, src, fSink.get());
 }
 
-/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-
-ViaCSXform::ViaCSXform(Sink* sink, sk_sp<SkColorSpace> cs, bool colorSpin)
-    : Via(sink)
-    , fCS(std::move(cs))
-    , fColorSpin(colorSpin) {}
-
-Error ViaCSXform::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
-    Error err = draw_to_canvas(fSink.get(), bitmap, stream, log, src.size(), [&](SkCanvas* canvas) {
-        {
-            SkAutoCanvasRestore acr(canvas, true);
-            auto proxy = SkCreateColorSpaceXformCanvas(canvas, fCS);
-            Error err = src.draw(proxy.get());
-            if (!err.isEmpty()) {
-                return err;
-            }
-        }
-
-        // Undo the color spin, so we can look at the pixels in Gold.
-        if (fColorSpin) {
-            SkBitmap pixels;
-            pixels.allocPixels(canvas->imageInfo());
-            canvas->readPixels(pixels, 0, 0);
-
-            SkPaint rotateColors;
-            SkScalar matrix[20] = { 0, 0, 1, 0, 0,   // B -> R
-                                    1, 0, 0, 0, 0,   // R -> G
-                                    0, 1, 0, 0, 0,   // G -> B
-                                    0, 0, 0, 1, 0 };
-            rotateColors.setBlendMode(SkBlendMode::kSrc);
-            rotateColors.setColorFilter(SkColorFilter::MakeMatrixFilterRowMajor255(matrix));
-            canvas->drawBitmap(pixels, 0, 0, &rotateColors);
-        }
-
-        return Error("");
-    });
-
-    if (!err.isEmpty()) {
-        return err;
-    }
-
-    if (bitmap && !fColorSpin) {
-        // It should be possible to do this without all the copies, but that (I think) requires
-        // adding API to SkBitmap.
-        SkAutoPixmapStorage pmap;
-        pmap.alloc(bitmap->info());
-        bitmap->readPixels(pmap);
-        pmap.setColorSpace(fCS);
-        bitmap->allocPixels(pmap.info());
-        bitmap->writePixels(pmap);
-    }
-
-    return "";
-}
-
 }  // namespace DM
diff --git a/dm/DMSrcSink.h b/dm/DMSrcSink.h
index 5875b15..54f7c4d 100644
--- a/dm/DMSrcSink.h
+++ b/dm/DMSrcSink.h
@@ -8,12 +8,12 @@
 #ifndef DMSrcSink_DEFINED
 #define DMSrcSink_DEFINED
 
+#include "CommonFlagsConfig.h"
 #include "SkBBHFactory.h"
 #include "SkBBoxHierarchy.h"
 #include "SkBitmap.h"
 #include "SkBitmapRegionDecoder.h"
 #include "SkCanvas.h"
-#include "SkCommonFlagsConfig.h"
 #include "SkData.h"
 #include "SkMultiPictureDocument.h"
 #include "SkPicture.h"
@@ -212,29 +212,15 @@
 
 class ColorCodecSrc : public Src {
 public:
-    enum Mode {
-        // Mimic legacy behavior and apply no color correction.
-        kBaseline_Mode,
-
-        // Color correct images into a specific dst color space.  If you happen to have this
-        // monitor, you're in luck!  The unmarked outputs of this test should display
-        // correctly on this monitor in the Chrome browser.  If not, it's useful to know
-        // that this monitor has a profile that is fairly similar to Adobe RGB.
-        kDst_HPZR30w_Mode,
-
-        kDst_sRGB_Mode,
-    };
-
-    ColorCodecSrc(Path, Mode, SkColorType);
+    ColorCodecSrc(Path, bool decode_to_dst);
 
     Error draw(SkCanvas*) const override;
     SkISize size() const override;
     Name name() const override;
     bool veto(SinkFlags) const override;
 private:
-    Path                    fPath;
-    Mode                    fMode;
-    SkColorType             fColorType;
+    Path fPath;
+    bool fDecodeToDst;
 };
 
 class SKPSrc : public Src {
@@ -408,7 +394,8 @@
                                   SkCommandLineConfigGpu::SurfType surfType, int samples,
                                   bool diText, SkColorType colorType, SkAlphaType alphaType,
                                   sk_sp<SkColorSpace> colorSpace, bool threaded,
-                                  const GrContextOptions& grCtxOptions);
+                                  const GrContextOptions& grCtxOptions,
+                                  int cacheType);
 
     Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
 
@@ -418,6 +405,8 @@
     }
 
 private:
+    int fCacheType;
+
     typedef GPUSink INHERITED;
 };
 
@@ -562,15 +551,6 @@
     Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
 };
 
-class ViaCSXform : public Via {
-public:
-    explicit ViaCSXform(Sink*, sk_sp<SkColorSpace>, bool colorSpin);
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
-private:
-    sk_sp<SkColorSpace> fCS;
-    bool                fColorSpin;
-};
-
 }  // namespace DM
 
 #endif//DMSrcSink_DEFINED
diff --git a/docker/README.md b/docker/README.md
index 24aa23a..aa310d5 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -69,3 +69,24 @@
     docker run -it cmake-release /bin/bash
     # Compile Skia in a local checkout with the local image
     docker run -v $SKIA_ROOT:/SRC -v /tmp/output:/OUT cmake-release /SRC/infra/docker/cmake/build_skia.sh
+
+binary-size
+------
+
+This image is used to build code size tree-maps of Skia
+
+It gets manually pushed anytime there's an update to the Dockerfile or relevant
+installed libraries. To push:
+
+    docker build -t binary-size ./binary-size/
+    docker tag binary-size gcr.io/skia-public/binary-size:v1
+    docker push gcr.io/skia-public/binary-size:v1
+
+For testing the image locally, the following flow can be helpful:
+
+    docker build -t binary-size ./binary-size/
+    # Run bash in it to poke around and make sure things are properly
+    # installed and configured.
+    docker run -it binary-size /bin/sh
+    # analyze exe "skottie_tool" in build directory out/Release
+    docker run -v $SKIA_ROOT/out/Release:/IN -v /tmp/output:/OUT binary-size /opt/binary_size/src/run_binary_size_analysis.py --library /IN/skottie_tool --destdir /OUT
\ No newline at end of file
diff --git a/docker/binary-size/Dockerfile b/docker/binary-size/Dockerfile
new file mode 100644
index 0000000..17601ad
--- /dev/null
+++ b/docker/binary-size/Dockerfile
@@ -0,0 +1,24 @@
+# Dockerfile that bundles chromium's binary_size tools
+# to build treemaps of code size for executables.
+FROM alpine:latest as dart-sdk-checkout
+
+RUN apk update && apk upgrade && \
+    apk add git
+
+WORKDIR /tmp/
+
+RUN git clone --depth 1 https://github.com/dart-lang/sdk.git
+
+#############################################################################
+# Multi-stage build part 2, in which we only have the python runtime and
+# and the scripts we need to analyze the binary.
+#############################################################################
+
+FROM alpine:latest as analyzer
+
+RUN apk update && apk upgrade && \
+    apk add python binutils
+
+WORKDIR /opt
+
+COPY --from=dart-sdk-checkout /tmp/sdk/runtime/third_party/ /opt/
diff --git a/docker/cloudbuild.yaml b/docker/cloudbuild.yaml
index e4a9e3a..448f30a 100644
--- a/docker/cloudbuild.yaml
+++ b/docker/cloudbuild.yaml
@@ -8,8 +8,17 @@
   #
   # Where COMMIT_SHA is updated to the last commit into Skia.
   #
-  # - name: 'gcr.io/cloud-builders/git'
-  #   args: ['clone', 'https://github.com/google/skia.git', '.']
+  #- name: 'gcr.io/cloud-builders/git'
+  #  args: ['clone', 'https://github.com/google/skia.git', '.']
+
+  - name: 'debian:testing-slim'
+    args: ['mkdir', '-p', '--mode=777', '/workspace/__doxygen_staging']
+
+  - name: 'gcr.io/skia-public/doxygen:testing-slim'
+    dir: './tools/doxygen'
+    args: ['doxygen', 'ProdDoxyfile']
+    timeout: 600s
+
   - name: 'gcr.io/cloud-builders/docker'
     args: ['build', '-t', 'gcr.io/$PROJECT_ID/skia-release:prod', './docker/skia-release']
     timeout: 7200s
@@ -34,6 +43,14 @@
     args: ['make', 'release_ci']
     timeout: 600s
 
+  - name: 'gcr.io/skia-public/infra:prod'
+    dir: '/home/skia/golib/src/go.skia.org/infra/api'
+    env:
+      - 'ROOT=/workspace/__api_staging'
+      - 'SKIP_BUILD=1'
+    args: ['make', 'release_ci']
+    timeout: 600s
+
   # We can't (easily) run docker inside of docker, which is how we get
   # the build artifacts out of skia-wasm-release when running locally.
   - name: 'gcr.io/cloud-builders/docker'
@@ -65,6 +82,17 @@
     timeout: 600s
 
   - name: 'gcr.io/cloud-builders/docker'
+    args: ['run', '--volume', '/workspace/wasm-products:/OUT',
+           '--volume', '/workspace/__particles_staging:/workspace/__particles_staging',
+           '--env', 'ROOT=/workspace/__particles_staging',
+           '--env', 'SKIP_BUILD=1',
+           '--workdir', '/home/skia/golib/src/go.skia.org/infra/particles',
+           'gcr.io/skia-public/infra:prod',
+           'make', 'release_ci']
+    dir: 'wasm-products'
+    timeout: 600s
+
+  - name: 'gcr.io/cloud-builders/docker'
     args: ['build', '-t', 'gcr.io/$PROJECT_ID/fiddler:$COMMIT_SHA', '/workspace/__fiddler_staging']
     timeout: 600s
 
@@ -73,17 +101,27 @@
     timeout: 600s
 
   - name: 'gcr.io/cloud-builders/docker'
+    args: ['build', '-t', 'gcr.io/$PROJECT_ID/particles:$COMMIT_SHA', '/workspace/__particles_staging']
+    timeout: 600s
+
+  - name: 'gcr.io/cloud-builders/docker'
     args: ['build', '-t', 'gcr.io/$PROJECT_ID/debugger:$COMMIT_SHA', '/workspace/__debugger_staging']
     timeout: 600s
 
   - name: 'gcr.io/cloud-builders/docker'
     args: ['build', '-t', 'gcr.io/$PROJECT_ID/jsfiddle:$COMMIT_SHA', '/workspace/__jsfiddle_staging']
     timeout: 600s
+
+  - name: 'gcr.io/cloud-builders/docker'
+    args: ['build', '-t', 'gcr.io/$PROJECT_ID/api:$COMMIT_SHA', '/workspace/__api_staging']
+    timeout: 600s
 images:
   - 'gcr.io/$PROJECT_ID/fiddler:$COMMIT_SHA'
   - 'gcr.io/$PROJECT_ID/skottie:$COMMIT_SHA'
+  - 'gcr.io/$PROJECT_ID/particles:$COMMIT_SHA'
   - 'gcr.io/$PROJECT_ID/debugger:$COMMIT_SHA'
   - 'gcr.io/$PROJECT_ID/jsfiddle:$COMMIT_SHA'
+  - 'gcr.io/$PROJECT_ID/api:$COMMIT_SHA'
   - 'gcr.io/$PROJECT_ID/skia-release:prod'
   - 'gcr.io/$PROJECT_ID/skia-wasm-release:prod'
 timeout: 7200s
diff --git a/docker/skia-wasm-release/Dockerfile b/docker/skia-wasm-release/Dockerfile
index 49cc27e..cd133e1 100644
--- a/docker/skia-wasm-release/Dockerfile
+++ b/docker/skia-wasm-release/Dockerfile
@@ -1,4 +1,4 @@
-# Dockerfile for building the WASM libraries used by jsfiddle.skia.org
+# Dockerfile for building the WASM libraries used by jsfiddle.skia.org and debugger.skia.org
 FROM gcr.io/skia-public/emsdk-release:prod as builder
 
 RUN cd /tmp \
@@ -15,7 +15,10 @@
 RUN /tmp/skia/skia/modules/pathkit/compile.sh
 
 # CanvasKit should be in /tmp/skia/skia/out/canvaskit_wasm
-RUN /tmp/skia/skia/experimental/canvaskit/compile.sh
+RUN /tmp/skia/skia/modules/canvaskit/compile.sh
+
+# Debugger should be in /tmp/skia/skia/out/debugger_wasm
+RUN /tmp/skia/skia/experimental/wasm-skp-debugger/compile.sh
 
 RUN cd /tmp/skia/skia && git rev-parse HEAD > /tmp/VERSION
 
@@ -36,3 +39,5 @@
 COPY --from=builder /tmp/skia/skia/out/pathkit/pathkit* /tmp/pathkit/
 
 COPY --from=builder /tmp/skia/skia/out/canvaskit_wasm/canvaskit* /tmp/canvaskit/
+
+COPY --from=builder /tmp/skia/skia/out/debugger_wasm/debugger* /tmp/debugger/
diff --git a/docs/SkAutoCanvasRestore_Reference.bmh b/docs/SkAutoCanvasRestore_Reference.bmh
deleted file mode 100644
index 30acdc4..0000000
--- a/docs/SkAutoCanvasRestore_Reference.bmh
+++ /dev/null
@@ -1,88 +0,0 @@
-#Topic Automatic_Canvas_Restore
-
-#Class SkAutoCanvasRestore
-
-#Code
-#Populate
-##
-
-Stack helper class calls SkCanvas::restoreToCount when SkAutoCanvasRestore
-goes out of scope. Use this to guarantee that the canvas is restored to a known
-state.
-
-#Method SkAutoCanvasRestore(SkCanvas* canvas, bool doSave)
-
-#Line # restores Canvas when out of scope ##
-#Populate
-
-#Example
-#Height 128
-    SkPaint p;
-    p.setAntiAlias(true);
-    p.setTextSize(64);
-    for (SkScalar sx : { -1, 1 } ) {
-        for (SkScalar sy : { -1, 1 } ) {
-            SkAutoCanvasRestore autoRestore(canvas, true);
-            SkMatrix m = SkMatrix::MakeAll(sx, 1, 96,    0, sy, 64,   0, 0, 1);
-            canvas->concat(m);
-            canvas->drawString("R", 0, 0, p);
-        }
-    }
-##
-
-#SeeAlso SkCanvas::save SkCanvas::restore
-
-##
-
-#Method ~SkAutoCanvasRestore()
-
-#Line # restores Canvas to saved state ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso SkCanvas::save SkCanvas::restore
-
-##
-
-#Method void restore()
-#In Member_Function
-#Line # restores Canvas to saved state ##
-#Populate
-
-#Example
-for (bool callRestore : { false, true } ) {
-    for (bool saveCanvas : {false, true} ) {
-        SkAutoCanvasRestore autoRestore(canvas, saveCanvas);
-        if (!saveCanvas) {
-            canvas->save();
-        }
-        SkDebugf("saveCanvas: %s  before restore: %d\n",
-               saveCanvas ? "true" : "false", canvas->getSaveCount());
-        if (callRestore) autoRestore.restore();
-        SkDebugf("saveCanvas: %s  after restore: %d\n",
-               saveCanvas ? "true" : "false", canvas->getSaveCount());
-    }
-}
-SkDebugf("final count: %d\n", canvas->getSaveCount());
-#StdOut
-saveCanvas: false  before restore: 2
-saveCanvas: false  after restore: 2
-saveCanvas: true  before restore: 2
-saveCanvas: true  after restore: 2
-saveCanvas: false  before restore: 2
-saveCanvas: false  after restore: 1
-saveCanvas: true  before restore: 2
-saveCanvas: true  after restore: 1
-final count: 1
-##
-##
-
-#SeeAlso SkCanvas::save SkCanvas::restore
-
-##
-
-#Class SkAutoCanvasRestore ##
-
-#Topic Automatic_Canvas_Restore ##
diff --git a/docs/SkBitmap_Reference.bmh b/docs/SkBitmap_Reference.bmh
deleted file mode 100644
index f0b50f9..0000000
--- a/docs/SkBitmap_Reference.bmh
+++ /dev/null
@@ -1,2662 +0,0 @@
-#Topic Bitmap
-#Alias Bitmaps ##
-#Alias Bitmap_Reference ##
-
-#Class SkBitmap
-#Line # two-dimensional raster pixel array ##
-
-#Code
-#Populate
-##
-
-Bitmap describes a two-dimensional raster pixel array. Bitmap is built on
-Image_Info, containing integer width and height, Color_Type and Alpha_Type
-describing the pixel format, and Color_Space describing the range of colors.
-Bitmap points to Pixel_Ref, which describes the physical array of pixels.
-Image_Info bounds may be located anywhere fully inside Pixel_Ref bounds.
-
-Bitmap can be drawn using Canvas. Bitmap can be a drawing destination for Canvas
-draw member functions. Bitmap flexibility as a pixel container limits some
-optimizations available to the target platform.
-
-If pixel array is primarily read-only, use Image for better performance.
-If pixel array is primarily written to, use Surface for better performance.
-
-Declaring SkBitmap const prevents altering Image_Info: the Bitmap height, width,
-and so on cannot change. It does not affect Pixel_Ref: a caller may write its
-pixels. Declaring SkBitmap const affects Bitmap configuration, not its contents.
-
-Bitmap is not thread safe. Each thread must have its own copy of Bitmap fields,
-although threads may share the underlying pixel array.
-
-#Subtopic Row_Bytes
-#Line # interval from one row to the next ##
-Bitmap pixels may be contiguous, or may have a gap at the end of each row.
-Row_Bytes is the interval from one row to the next. Row_Bytes may be specified;
-sometimes passing zero will compute the Row_Bytes from the row width and the
-number of bytes in a pixel. Row_Bytes may be larger than the row requires. This
-is useful to position one or more Bitmaps within a shared pixel array.
-##
-
-# ------------------------------------------------------------------------------
-
-#Class Allocator
-#Line # abstract subclass of HeapAllocator ##
-#Code
-#Populate
-##
-
-Abstract subclass of HeapAllocator.
-
-# ------------------------------------------------------------------------------
-
-#Method virtual bool allocPixelRef(SkBitmap* bitmap) = 0
-#Line # allocates pixel memory ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso HeapAllocator
-
-##
-
-#Class Allocator ##
-
-# ------------------------------------------------------------------------------
-
-#Class HeapAllocator
-#Line # allocates pixel memory from heap ##
-
-#Code
-#Populate
-##
-
-Subclass of SkBitmap::Allocator that returns a Pixel_Ref that allocates its pixel
-memory from the heap. This is the default SkBitmap::Allocator invoked by
-allocPixels.
-
-# ------------------------------------------------------------------------------
-
-#Method bool allocPixelRef(SkBitmap* bitmap) override
-#Line # allocates pixel memory ##
-#Populate
-
-#Example
-    SkBitmap bitmap;
-    bitmap.setInfo(SkImageInfo::MakeN32(16, 16, kPremul_SkAlphaType));
-    SkDebugf("pixel address = %p\n", bitmap.getPixels());
-    SkBitmap::HeapAllocator stdalloc;
-    if (!stdalloc.allocPixelRef(&bitmap)) {
-        SkDebugf("pixel allocation failed\n");
-    } else {
-        SkDebugf("pixel address = %p\n", bitmap.getPixels());
-    }
-#StdOut
-#Volatile
-pixel address = (nil)
-pixel address = 0x560ddd0ac670
-##
-##
-
-#SeeAlso SkBitmap::Allocator tryAllocPixels
-
-##
-
-#Class HeapAllocator ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkBitmap()
-
-#Line # constructs with default values ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    const char* alphas[] = {"Unknown", "Opaque", "Premul", "Unpremul"};
-    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
-                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16"};
-    SkBitmap bitmap;
-    for (int i = 0; i < 2; ++i) {
-       SkDebugf("width: %2d  height: %2d", bitmap.width(), bitmap.height());
-       SkDebugf("  color: k%s_SkColorType", colors[bitmap.colorType()]);
-       SkDebugf("  alpha: k%s_SkAlphaType\n", alphas[bitmap.alphaType()]);
-       bitmap.setInfo(SkImageInfo::Make(25, 35, kRGBA_8888_SkColorType, kOpaque_SkAlphaType),
-                      0);
-    }
-}
-#StdOut
-width:  0  height:  0  color: kUnknown_SkColorType  alpha: kUnknown_SkAlphaType
-width: 25  height: 35  color: kRGBA_8888_SkColorType  alpha: kOpaque_SkAlphaType
-##
-##
-
-#SeeAlso setInfo
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkBitmap(const SkBitmap& src)
-
-#Line # shares ownership of pixels ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkBitmap original;
-    if (original.tryAllocPixels(
-            SkImageInfo::Make(25, 35, kRGBA_8888_SkColorType, kOpaque_SkAlphaType))) {
-        SkDebugf("original has pixels before copy: %s\n", original.getPixels() ? "true" : "false");
-        SkBitmap copy(original);
-        SkDebugf("original has pixels after copy: %s\n", original.getPixels() ? "true" : "false");
-        SkDebugf("copy has pixels: %s\n", copy.getPixels() ? "true" : "false");
-    }
-}
-#StdOut
-original has pixels before copy: true
-original has pixels after copy: true
-copy has pixels: true
-##
-##
-
-#SeeAlso setInfo setPixelRef setPixels swap
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkBitmap(SkBitmap&& src)
-
-#Line # takes ownership of pixels ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkBitmap original;
-    if (original.tryAllocPixels(
-            SkImageInfo::Make(25, 35, kRGBA_8888_SkColorType, kOpaque_SkAlphaType))) {
-        SkDebugf("original has pixels before move: %s\n", original.getPixels() ? "true" : "false");
-        SkBitmap copy(std::move(original));
-        SkDebugf("original has pixels after move: %s\n", original.getPixels() ? "true" : "false");
-        SkDebugf("copy has pixels: %s\n", copy.getPixels() ? "true" : "false");
-    }
-}
-#StdOut
-original has pixels before move: true
-original has pixels after move: false
-copy has pixels: true
-##
-##
-
-#SeeAlso setInfo setPixelRef setPixels swap
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method ~SkBitmap()
-
-#Line # releases ownership of pixels ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso Pixel_Ref
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkBitmap& operator=(const SkBitmap& src)
-
-#Line # shares ownership of pixels ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkBitmap original;
-    if (original.tryAllocPixels(
-            SkImageInfo::Make(25, 35, kRGBA_8888_SkColorType, kOpaque_SkAlphaType))) {
-        SkDebugf("original has pixels before copy: %s\n", original.getPixels() ? "true" : "false");
-        SkBitmap copy = original;
-        SkDebugf("original has pixels after copy: %s\n", original.getPixels() ? "true" : "false");
-        SkDebugf("copy has pixels: %s\n", copy.getPixels() ? "true" : "false");
-    }
-}
-#StdOut
-original has pixels before copy: true
-original has pixels after copy: true
-copy has pixels: true
-##
-##
-
-#SeeAlso setInfo setPixelRef setPixels swap
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkBitmap& operator=(SkBitmap&& src)
-
-#Line # takes ownership of pixels ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkBitmap original;
-    if (original.tryAllocPixels(
-            SkImageInfo::Make(25, 35, kRGBA_8888_SkColorType, kOpaque_SkAlphaType))) {
-        SkDebugf("original has pixels before move: %s\n", original.getPixels() ? "true" : "false");
-        SkBitmap copy = std::move(original);
-        SkDebugf("original has pixels after move: %s\n", original.getPixels() ? "true" : "false");
-        SkDebugf("copy has pixels: %s\n", copy.getPixels() ? "true" : "false");
-    }
-}
-#StdOut
-original has pixels before move: true
-original has pixels after move: false
-copy has pixels: true
-##
-##
-
-#SeeAlso setInfo setPixelRef setPixels swap
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void swap(SkBitmap& other)
-#In Utility
-#Line # exchanges Bitmap pair ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    auto debugster = [](const char* prefix, const SkBitmap& b) -> void {
-        const char* alphas[] = {"Unknown", "Opaque", "Premul", "Unpremul"};
-        const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
-                                "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16"};
-        SkDebugf("%s width:%d height:%d colorType:k%s_SkColorType alphaType:k%s_SkAlphaType\n",
-                 prefix, b.width(), b.height(), colors[b.colorType()], alphas[b.alphaType()]);
-    };
-    SkBitmap one, two;
-    if (!one.tryAllocPixels(
-            SkImageInfo::Make(1, 1, kRGBA_8888_SkColorType, kOpaque_SkAlphaType))) {
-        return;
-    }
-    if (!two.tryAllocPixels(
-            SkImageInfo::Make(2, 2, kBGRA_8888_SkColorType, kPremul_SkAlphaType))) {
-        return;
-    }
-    for (int index = 0; index < 2; ++index) {
-       debugster("one", one);
-       debugster("two", two);
-       one.swap(two);
-    }
-}
-#StdOut
-one width:1 height:1 colorType:kRGBA_8888_SkColorType alphaType:kOpaque_SkAlphaType
-two width:2 height:2 colorType:kBGRA_8888_SkColorType alphaType:kPremul_SkAlphaType
-one width:2 height:2 colorType:kBGRA_8888_SkColorType alphaType:kPremul_SkAlphaType
-two width:1 height:1 colorType:kRGBA_8888_SkColorType alphaType:kOpaque_SkAlphaType
-##
-##
-
-#SeeAlso SkBitmap(SkBitmap&& src) operator=(SkBitmap&& src)
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Property
-#Line # metrics and attributes ##
-##
-
-#Method const SkPixmap& pixmap() const
-#In Property
-#Line # returns Pixmap ##
-#Populate
-
-#Example
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeN32Premul(10, 11));
-    SkCanvas offscreen(bitmap);
-    offscreen.clear(SK_ColorWHITE);
-    SkPaint paint;
-    offscreen.drawString("&", 0, 10, paint);
-    const SkPixmap& pixmap = bitmap.pixmap();
-    if (pixmap.addr()) {
-        SkPMColor pmWhite = *pixmap.addr32(0, 0);
-        for (int y = 0; y < pixmap.height(); ++y) {
-            for (int x = 0; x < pixmap.width(); ++x) {
-                SkDebugf("%c", *pixmap.addr32(x, y) == pmWhite ? '-' : 'x');
-            }
-            SkDebugf("\n");
-        }
-    }
-    #StdOut
-----------
----xx-----
---x--x----
---x-------
---xx------
---x-x---x-
--x---x--x-
--x----xx--
--xx---x---
---xxxx-xx-
-----------
-    #StdOut ##
-
-##
-
-#SeeAlso peekPixels installPixels readPixels writePixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method const SkImageInfo& info() const
-#In Property
-#Line # returns Image_Info ##
-#Populate
-
-#Example
-#Image 4
-void draw(SkCanvas* canvas) {
-    // SkBitmap source;  // pre-populated with soccer ball by fiddle.skia.org
-    const SkImageInfo& info = source.info();
-    const char* alphas[] = {"Unknown", "Opaque", "Premul", "Unpremul"};
-    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
-                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16"};
-    SkDebugf("width: %d height: %d color: %s alpha: %s\n", info.width(), info.height(),
-                colors[info.colorType()], alphas[info.alphaType()]);
-#StdOut
-width: 56 height: 56 color: BGRA_8888 alpha: Opaque
-##
-}
-##
-
-#SeeAlso Image_Info
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int width() const
-#In Property
-#Line # returns pixel column count ##
-Returns pixel count in each row. Should be equal or less than
-#Formula # rowBytes() / info().bytesPerPixel() ##.
-
-May be less than pixelRef().width(). Will not exceed pixelRef().width() less
-pixelRefOrigin().fX.
-
-#Return  pixel width in Image_Info ##
-
-#Example
-    SkImageInfo info = SkImageInfo::MakeA8(16, 32);
-    SkBitmap bitmap;
-    bitmap.setInfo(info);
-    SkDebugf("bitmap width: %d  info width: %d\n", bitmap.width(), info.width());
-#StdOut
-bitmap width: 16  info width: 16
-##
-##
-
-#SeeAlso height() SkPixelRef::width() SkImageInfo::width()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int height() const
-#In Property
-#Line # returns pixel row count ##
-#Populate
-
-#Example
-    SkImageInfo info = SkImageInfo::MakeA8(16, 32);
-    SkBitmap bitmap;
-    bitmap.setInfo(info);
-    SkDebugf("bitmap height: %d  info height: %d\n", bitmap.height(), info.height());
-#StdOut
-bitmap height: 32  info height: 32
-##
-##
-
-#SeeAlso width() SkPixelRef::height() SkImageInfo::height()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkColorType colorType() const
-#In Property
-#Line # returns Image_Info Color_Type ##
-Returns Color_Type, one of: #list_of_color_types#.
-
-#Return  Color_Type in Image_Info ##
-
-#Example
-    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
-                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16"};
-    SkBitmap bitmap;
-    bitmap.setInfo(SkImageInfo::MakeA8(16, 32));
-    SkDebugf("color type: k" "%s" "_SkColorType\n", colors[bitmap.colorType()]);
-#StdOut
-color type: kAlpha_8_SkColorType
-##
-##
-
-#SeeAlso alphaType() SkImageInfo::colorType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkAlphaType alphaType() const
-#In Property
-#Line # returns Image_Info Alpha_Type ##
-Returns Alpha_Type, one of: #list_of_alpha_types#.
-
-#Return  Alpha_Type in Image_Info ##
-
-#Example
-    const char* alphas[] = {"Unknown", "Opaque", "Premul", "Unpremul"};
-    SkPixmap pixmap(SkImageInfo::MakeA8(16, 32), nullptr, 64);
-    SkDebugf("alpha type: k" "%s" "_SkAlphaType\n", alphas[pixmap.alphaType()]);
-#StdOut
-alpha type: kPremul_SkAlphaType
-##
-##
-
-#SeeAlso colorType() SkImageInfo::alphaType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkColorSpace* colorSpace() const
-#In Property
-#Line # returns Image_Info Color_Space ##
-#Populate
-
-#Example
-#Description
-SkColorSpace::MakeSRGBLinear creates Color_Space with linear gamma
-and an sRGB gamut. This Color_Space gamma is not close to sRGB gamma.
-##
-    SkBitmap bitmap;
-    bitmap.setInfo(SkImageInfo::MakeN32(16, 32, kPremul_SkAlphaType,
-            SkColorSpace::MakeSRGBLinear()));
-    SkColorSpace* colorSpace = bitmap.colorSpace();
-    SkDebugf("gammaCloseToSRGB: %s  gammaIsLinear: %s  isSRGB: %s\n",
-            colorSpace->gammaCloseToSRGB() ? "true" : "false",
-            colorSpace->gammaIsLinear() ? "true" : "false",
-            colorSpace->isSRGB() ? "true" : "false");
-#StdOut
-gammaCloseToSRGB: false  gammaIsLinear: true  isSRGB: false
-##
-##
-
-#SeeAlso Color_Space SkImageInfo::colorSpace
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkColorSpace> refColorSpace() const
-#In Property
-#Line # returns Image_Info Color_Space ##
-#Populate
-
-#Example
-    SkBitmap bitmap1, bitmap2;
-    bitmap1.setInfo(SkImageInfo::MakeN32(16, 32, kPremul_SkAlphaType,
-            SkColorSpace::MakeSRGBLinear()));
-    bitmap2.setInfo(SkImageInfo::MakeN32(16, 32, kPremul_SkAlphaType,
-            bitmap1.refColorSpace()));
-    SkColorSpace* colorSpace = bitmap2.colorSpace();
-    SkDebugf("gammaCloseToSRGB: %s  gammaIsLinear: %s  isSRGB: %s\n",
-            colorSpace->gammaCloseToSRGB() ? "true" : "false",
-            colorSpace->gammaIsLinear() ? "true" : "false",
-            colorSpace->isSRGB() ? "true" : "false");
-#StdOut
-gammaCloseToSRGB: false  gammaIsLinear: true  isSRGB: false
-##
-##
-
-#SeeAlso Color_Space SkImageInfo::colorSpace
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int bytesPerPixel() const
-#In Property
-#Line # returns number of bytes in pixel based on Color_Type ##
-#Populate
-
-#Example
-    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
-                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16"};
-    SkImageInfo info = SkImageInfo::MakeA8(1, 1);
-    SkBitmap bitmap;
-    for (SkColorType colorType : { #list_of_color_types#
-                                 } ) {
-        bitmap.setInfo(info.makeColorType(colorType));
-        SkDebugf("color: k" "%s" "_SkColorType" "%*s" "bytesPerPixel: %d\n",
-                colors[colorType], 13 - strlen(colors[colorType]), " ",
-                bitmap.bytesPerPixel());
-    }
-#StdOut
-color: kUnknown_SkColorType      bytesPerPixel: 0
-color: kAlpha_8_SkColorType      bytesPerPixel: 1
-color: kRGB_565_SkColorType      bytesPerPixel: 2
-color: kARGB_4444_SkColorType    bytesPerPixel: 2
-color: kRGBA_8888_SkColorType    bytesPerPixel: 4
-color: kRGB_888x_SkColorType     bytesPerPixel: 4
-color: kBGRA_8888_SkColorType    bytesPerPixel: 4
-color: kRGBA_1010102_SkColorType bytesPerPixel: 4
-color: kRGB_101010x_SkColorType  bytesPerPixel: 4
-color: kGray_8_SkColorType       bytesPerPixel: 1
-color: kRGBA_F16_SkColorType     bytesPerPixel: 8
-##
-##
-
-#SeeAlso rowBytes rowBytesAsPixels width shiftPerPixel SkImageInfo::bytesPerPixel
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int rowBytesAsPixels() const
-#In Property
-#Line # returns interval between rows in pixels ##
-#Populate
-
-#Example
-    SkBitmap bitmap;
-    for (int rowBytes : { 4, 5, 6, 7, 8} ) {
-        bitmap.setInfo(SkImageInfo::MakeN32(1, 1, kPremul_SkAlphaType), rowBytes);
-        SkDebugf("rowBytes: %d rowBytesAsPixels: %d\n", rowBytes, bitmap.rowBytesAsPixels());
-    }
-#StdOut
-rowBytes: 4 rowBytesAsPixels: 1
-rowBytes: 5 rowBytesAsPixels: 1
-rowBytes: 6 rowBytesAsPixels: 1
-rowBytes: 7 rowBytesAsPixels: 1
-rowBytes: 8 rowBytesAsPixels: 2
-##
-##
-
-#SeeAlso rowBytes shiftPerPixel width bytesPerPixel
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int shiftPerPixel() const
-#In Property
-#Line # returns bit shift from pixels to bytes ##
-#Populate
-
-#Example
-    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
-                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16"};
-    SkImageInfo info = SkImageInfo::MakeA8(1, 1);
-    SkBitmap bitmap;
-    for (SkColorType colorType : { #list_of_color_types#
-                                 } ) {
-        bitmap.setInfo(info.makeColorType(colorType));
-        SkDebugf("color: k" "%s" "_SkColorType" "%*s" "shiftPerPixel: %d\n",
-                colors[colorType], 14 - strlen(colors[colorType]), " ",
-                bitmap.shiftPerPixel());
-    }
-#StdOut
-color: kUnknown_SkColorType       shiftPerPixel: 0
-color: kAlpha_8_SkColorType       shiftPerPixel: 0
-color: kRGB_565_SkColorType       shiftPerPixel: 1
-color: kARGB_4444_SkColorType     shiftPerPixel: 1
-color: kRGBA_8888_SkColorType     shiftPerPixel: 2
-color: kRGB_888x_SkColorType      shiftPerPixel: 2
-color: kBGRA_8888_SkColorType     shiftPerPixel: 2
-color: kRGBA_1010102_SkColorType  shiftPerPixel: 2
-color: kRGB_101010x_SkColorType   shiftPerPixel: 2
-color: kGray_8_SkColorType        shiftPerPixel: 0
-color: kRGBA_F16_SkColorType      shiftPerPixel: 3
-##
-##
-
-#SeeAlso rowBytes rowBytesAsPixels width bytesPerPixel
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool empty() const
-#In Property
-#Line # returns true if Image_Info has zero width() or height() ##
-#Populate
-
-#Example
-    SkBitmap bitmap;
-    for (int width : { 0, 2 } ) {
-        for (int height : { 0, 2 } ) {
-             bitmap.setInfo(SkImageInfo::MakeA8(width, height));
-             SkDebugf("width: %d height: %d empty: %s\n", width, height,
-                      bitmap.empty() ? "true" : "false");
-        }
-    }
-#StdOut
-width: 0 height: 0 empty: true
-width: 0 height: 2 empty: true
-width: 2 height: 0 empty: true
-width: 2 height: 2 empty: false
-##
-##
-
-#SeeAlso height() width() drawsNothing
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isNull() const
-#In Property
-#Line # returns true if Pixel_Ref is nullptr ##
-#Populate
-
-#Example
-    SkBitmap bitmap;
-    SkDebugf("empty bitmap does %shave pixels\n", bitmap.isNull() ? "not " : "");
-    bitmap.setInfo(SkImageInfo::MakeA8(8, 8));
-    SkDebugf("bitmap with dimensions does %shave pixels\n", bitmap.isNull() ? "not " : "");
-    bitmap.allocPixels();
-    SkDebugf("allocated bitmap does %shave pixels\n", bitmap.isNull() ? "not " : "");
-#StdOut
-empty bitmap does not have pixels
-bitmap with dimensions does not have pixels
-allocated bitmap does have pixels
-##
-##
-
-#SeeAlso empty() drawsNothing pixelRef
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool drawsNothing() const
-#In Property
-#Line # returns true if no width(), no height(), or no Pixel_Ref ##
-#Populate
-
-#Example
-    SkBitmap bitmap;
-    for (int w : { 0, 8 } ) {
-        for (bool allocate : { false, true} ) {
-            bitmap.setInfo(SkImageInfo::MakeA8(w, 8));
-            allocate ? bitmap.allocPixels() : (void) 0 ;
-            SkDebugf("empty:%s isNull:%s drawsNothing:%s\n", bitmap.empty() ? "true " : "false",
-                     bitmap.isNull() ? "true " : "false", bitmap.drawsNothing() ? "true" : "false");
-        }
-    }
-#StdOut
-empty:true  isNull:true  drawsNothing:true
-empty:true  isNull:false drawsNothing:true
-empty:false isNull:true  drawsNothing:true
-empty:false isNull:false drawsNothing:false
-##
-##
-
-#SeeAlso empty() isNull pixelRef
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t rowBytes() const
-#In Property
-#Line # returns interval between rows in bytes ##
-Returns row bytes, the interval from one pixel row to the next. Row bytes
-is at least as large as: #Formula # width() * info().bytesPerPixel() ##.
-
-Returns zero if colorType is kUnknown_SkColorType, or if row bytes supplied to
-setInfo is not large enough to hold a row of pixels.
-
-#Return  byte length of pixel row ##
-
-#Example
-   SkBitmap bitmap;
-   for (int rowBytes : { 2, 8 } ) {
-       bool result = bitmap.setInfo(SkImageInfo::MakeA8(4, 4), rowBytes);
-       SkDebugf("setInfo returned:%s rowBytes:%d\n", result ? "true " : "false", bitmap.rowBytes());
-    }
-#StdOut
-setInfo returned:false rowBytes:0
-setInfo returned:true  rowBytes:8
-##
-##
-
-#SeeAlso info() setInfo SkImageInfo::minRowBytes
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool setAlphaType(SkAlphaType alphaType)
-#In Set
-#Line # sets Alpha_Type of shared pixels ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    const char* colors[] = { "Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
-                             "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16" };
-    const char* alphas[] = {"Unknown ", "Opaque  ", "Premul  ", "Unpremul"};
-    SkBitmap bitmap;
-    SkAlphaType alphaTypes[] = { #list_of_alpha_types#
-                               };
-    SkDebugf("%18s%15s%17s%18s%19s\n", "Canonical", "Unknown", "Opaque", "Premul", "Unpremul");
-    for (SkColorType colorType : { #list_of_color_types#
-                                 } ) {
-        for (SkAlphaType canonicalAlphaType : alphaTypes) {
-            SkColorTypeValidateAlphaType(colorType, kUnknown_SkAlphaType, &canonicalAlphaType );
-            SkDebugf("%12s %9s  ", colors[(int) colorType], alphas[(int) canonicalAlphaType ]);
-            for (SkAlphaType alphaType : alphaTypes) {
-                bitmap.setInfo(SkImageInfo::Make(4, 4, colorType, canonicalAlphaType));
-                bool result = bitmap.setAlphaType(alphaType);
-                SkDebugf("%s %s    ", result ? "true " : "false", alphas[(int) bitmap.alphaType()]);
-            }
-            SkDebugf("\n");
-        }
-    }
-}
-##
-
-#SeeAlso Alpha_Type Color_Type Image_Info setInfo
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void* getPixels() const
-#In Property
-#Line # returns address of pixels ##
-#Populate
-
-#Example
-    SkBitmap bitmap;
-    bitmap.setInfo(SkImageInfo::MakeN32(4, 4, kPremul_SkAlphaType));
-    bitmap.allocPixels();
-    bitmap.eraseColor(0x00000000);
-    void* baseAddr = bitmap.getPixels();
-    *(SkPMColor*)baseAddr = 0xFFFFFFFF;
-    SkDebugf("bitmap.getColor(0, 1) %c= 0x00000000\n",
-              bitmap.getColor(0, 1)  == 0x00000000 ? '=' : '!');
-    SkDebugf("bitmap.getColor(0, 0) %c= 0xFFFFFFFF\n",
-              bitmap.getColor(0, 0)  == 0xFFFFFFFF ? '=' : '!');
-#StdOut
-bitmap.getColor(0, 1) == 0x00000000
-bitmap.getColor(0, 0) == 0xFFFFFFFF
-##
-##
-
-#SeeAlso isNull drawsNothing
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t computeByteSize() const
-#In Utility
-#Line # returns size required for pixels ##
-#Populate
-
-#Example
-    SkBitmap bitmap;
-    for (int width : { 1, 1000, 1000000 } ) {
-        for (int height: { 1, 1000, 1000000 } ) {
-            SkImageInfo imageInfo = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType);
-            bitmap.setInfo(imageInfo, width * 5);
-            SkDebugf("width: %7d height: %7d computeByteSize: %13lld\n", width, height,
-                     bitmap.computeByteSize());
-        }
-    }
-#StdOut
-width:       1 height:       1 computeByteSize:             4
-width:       1 height:    1000 computeByteSize:          4999
-width:       1 height: 1000000 computeByteSize:       4999999
-width:    1000 height:       1 computeByteSize:          4000
-width:    1000 height:    1000 computeByteSize:       4999000
-width:    1000 height: 1000000 computeByteSize:    4999999000
-width: 1000000 height:       1 computeByteSize:       4000000
-width: 1000000 height:    1000 computeByteSize:    4999000000
-width: 1000000 height: 1000000 computeByteSize: 4999999000000
-##
-##
-
-#SeeAlso SkImageInfo::computeByteSize
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isImmutable() const
-#In Property
-#Line # returns true if pixels will not change ##
-#Populate
-
-#Example
-    SkBitmap original;
-    SkImageInfo info = SkImageInfo::Make(25, 35, kRGBA_8888_SkColorType, kOpaque_SkAlphaType);
-    if (original.tryAllocPixels(info)) {
-        original.setImmutable();
-        SkBitmap copy;
-        original.extractSubset(&copy, {5, 10, 15, 20});
-        SkDebugf("original is " "%s" "immutable\n", original.isImmutable() ? "" : "not ");
-        SkDebugf("copy is " "%s" "immutable\n", copy.isImmutable() ? "" : "not ");
-    }
-#StdOut
-original is immutable
-copy is immutable
-##
-##
-
-#SeeAlso setImmutable SkPixelRef::isImmutable SkImage
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void setImmutable()
-#In Set
-#Line # marks that pixels will not change ##
-#Populate
-
-#Example
-#Description
-Triggers assert if SK_DEBUG is true, runs fine otherwise.
-##
-    SkBitmap bitmap;
-    bitmap.setInfo(SkImageInfo::MakeN32(4, 4, kPremul_SkAlphaType));
-    bitmap.allocPixels();
-    SkCanvas offscreen(bitmap);
-    SkDebugf("draw white\n");
-    offscreen.clear(SK_ColorWHITE);
-    bitmap.setImmutable();
-    SkDebugf("draw black\n");
-    offscreen.clear(SK_ColorBLACK);
-##
-
-#SeeAlso isImmutable SkPixelRef::setImmutable SkImage
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isOpaque() const
-#In Property
-#Line # returns true if Image_Info describes opaque pixels ##
-#Populate
-
-#Example
-#Description
-    isOpaque ignores whether all pixels are opaque or not.
-##
-    const int height = 2;
-    const int width = 2;
-    SkBitmap bitmap;
-    bitmap.setInfo(SkImageInfo::Make(width, height, kN32_SkColorType, kPremul_SkAlphaType));
-    for (int index = 0; index < 2; ++index) {
-        bitmap.allocPixels();
-        bitmap.eraseColor(0x00000000);
-        SkDebugf("isOpaque: %s\n", bitmap.isOpaque() ? "true" : "false");
-        bitmap.eraseColor(0xFFFFFFFF);
-        SkDebugf("isOpaque: %s\n", bitmap.isOpaque() ? "true" : "false");
-        bitmap.setInfo(bitmap.info().makeAlphaType(kOpaque_SkAlphaType));
-    }
-#StdOut
-isOpaque: false
-isOpaque: false
-isOpaque: true
-isOpaque: true
-##
-##
-
-#SeeAlso ComputeIsOpaque SkImageInfo::isOpaque
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isVolatile() const
-#In Property
-#Line # returns true if pixels should not be cached ##
-#Populate
-
-#Example
-    SkBitmap original;
-    SkImageInfo info = SkImageInfo::Make(25, 35, kRGBA_8888_SkColorType, kOpaque_SkAlphaType);
-    if (original.tryAllocPixels(info)) {
-        original.setIsVolatile(true);
-        SkBitmap copy;
-        original.extractSubset(&copy, {5, 10, 15, 20});
-        SkDebugf("original is " "%s" "volatile\n", original.isVolatile() ? "" : "not ");
-        SkDebugf("copy is " "%s" "volatile\n", copy.isImmutable() ? "" : "not ");
-    }
-#StdOut
-original is volatile
-copy is not volatile
-##
-##
-
-#SeeAlso setIsVolatile
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void setIsVolatile(bool isVolatile)
-#In Set
-#Line # marks if pixels should not be cached ##
-#Populate
-
-#Example
-#Height 20
-    SkBitmap bitmap;
-    bitmap.setInfo(SkImageInfo::Make(1, 1, kRGBA_8888_SkColorType, kOpaque_SkAlphaType));
-    bitmap.allocPixels();
-    bitmap.eraseColor(SK_ColorRED);
-    canvas->scale(16, 16);
-    canvas->drawBitmap(bitmap, 0, 0);
-    *(SkPMColor*) bitmap.getPixels() = SkPreMultiplyColor(SK_ColorBLUE);
-    canvas->drawBitmap(bitmap, 2, 0);
-    bitmap.setIsVolatile(true);
-    *(SkPMColor*) bitmap.getPixels() = SkPreMultiplyColor(SK_ColorGREEN);
-    canvas->drawBitmap(bitmap, 4, 0);
-##
-
-#SeeAlso isVolatile
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void reset()
-#In Constructors
-#Line # sets to default values, releases pixel ownership ##
-#Populate
-
-#Example
-    SkBitmap bitmap;
-    bitmap.setInfo(SkImageInfo::Make(1, 1, kRGBA_8888_SkColorType, kOpaque_SkAlphaType));
-    bitmap.allocPixels();
-    SkDebugf("width:%d height:%d isNull:%s\n", bitmap.width(), bitmap.height(),
-             bitmap.isNull() ? "true" : "false");
-    bitmap.reset();
-    SkDebugf("width:%d height:%d isNull:%s\n", bitmap.width(), bitmap.height(),
-             bitmap.isNull() ? "true" : "false");
-#StdOut
-width:1 height:1 isNull:false
-width:0 height:0 isNull:true
-##
-##
-
-#SeeAlso SkBitmap() SkAlphaType SkColorType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static bool ComputeIsOpaque(const SkBitmap& bm)
-#In Utility
-#Line # returns true if all pixels are opaque ##
-#Populate
-
-#Example
-    SkBitmap bitmap;
-    bitmap.setInfo(SkImageInfo::Make(2, 2, kN32_SkColorType, kPremul_SkAlphaType));
-    for (int index = 0; index < 2; ++index) {
-        bitmap.allocPixels();
-        bitmap.eraseColor(0x00000000);
-        SkDebugf("computeIsOpaque: %s\n", SkBitmap::ComputeIsOpaque(bitmap) ? "true" : "false");
-        bitmap.eraseColor(0xFFFFFFFF);
-        SkDebugf("computeIsOpaque: %s\n", SkBitmap::ComputeIsOpaque(bitmap) ? "true" : "false");
-        bitmap.setInfo(bitmap.info().makeAlphaType(kOpaque_SkAlphaType));
-    }
-#StdOut
-computeIsOpaque: false
-computeIsOpaque: true
-computeIsOpaque: false
-computeIsOpaque: true
-##
-##
-
-#SeeAlso isOpaque Color_Type Alpha
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void getBounds(SkRect* bounds) const
-#In Property
-#Line # returns width() and height() as Rectangle ##
-#Populate
-
-#Example
-#Height 160
-#Image 3
-    SkRect bounds;
-    source.getBounds(&bounds);
-    bounds.offset(100, 100);
-    SkPaint paint;
-    paint.setColor(SK_ColorGRAY);
-    canvas->scale(.25f, .25f);
-    canvas->drawRect(bounds, paint);
-    canvas->drawBitmap(source, 40, 40);
-##
-
-#SeeAlso bounds()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void getBounds(SkIRect* bounds) const
-#Populate
-
-#Example
-#Image 3
-    SkIRect bounds;
-    source.getBounds(&bounds);
-    bounds.inset(100, 100);
-    SkBitmap bitmap;
-    source.extractSubset(&bitmap, bounds);
-    canvas->scale(.5f, .5f);
-    canvas->drawBitmap(bitmap, 10, 10);
-##
-
-#SeeAlso bounds()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkIRect bounds() const
-#In Property
-#Line # returns width() and height() as Rectangle ##
-#Populate
-
-#Example
-#Height 64
-#Image 4
-    canvas->scale(.5f, .5f);
-    SkIRect bounds = source.bounds();
-    for (int x : { 0, bounds.width() } ) {
-        for (int y : { 0, bounds.height() } ) {
-            canvas->drawBitmap(source, x, y);
-        }
-    }
-##
-
-#SeeAlso getBounds
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkISize dimensions() const
-#In Property
-#Line # returns width() and height() ##
-#Populate
-
-#Example
-    SkBitmap bitmap;
-    bitmap.setInfo(SkImageInfo::MakeN32(33, 55, kOpaque_SkAlphaType));
-    SkISize dimensions = bitmap.dimensions();
-    SkRect bounds;
-    bitmap.getBounds(&bounds);
-    SkRect dimensionsAsBounds = SkRect::Make(dimensions);
-    SkDebugf("dimensionsAsBounds %c= bounds\n", dimensionsAsBounds == bounds ? '=' : '!');
-##
-
-#SeeAlso height width
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkIRect getSubset() const
-#In Property
-#Line # returns bounds offset by origin ##
-#Populate
-
-#Example
-#Image 3
-    SkIRect bounds;
-    source.getBounds(&bounds);
-    bounds.inset(100, 100);
-    SkBitmap subset;
-    source.extractSubset(&subset, bounds);
-    SkIRect r = source.getSubset();
-    SkDebugf("source: %d, %d, %d, %d\n", r.fLeft, r.fTop, r.fRight, r.fBottom);
-    r = subset.getSubset();
-    SkDebugf("subset: %d, %d, %d, %d\n", r.fLeft, r.fTop, r.fRight, r.fBottom);
-#StdOut
-source: 0, 0, 512, 512
-subset: 100, 100, 412, 412
-##
-##
-
-#SeeAlso extractSubset getBounds
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool setInfo(const SkImageInfo& imageInfo, size_t rowBytes = 0)
-#In Set
-#Line # sets height, width, Color_Type, and so on, releasing pixels ##
-Sets width, height, Alpha_Type, Color_Type, Color_Space, and optional
-rowBytes. Frees pixels, and returns true if successful.
-
-imageInfo.alphaType() may be altered to a value permitted by imageInfo.colorSpace().
-If imageInfo.colorType() is kUnknown_SkColorType, imageInfo.alphaType() is
-set to kUnknown_SkAlphaType.
-If imageInfo.colorType() is kAlpha_8_SkColorType and imageInfo.alphaType() is
-kUnpremul_SkAlphaType, imageInfo.alphaType() is replaced by kPremul_SkAlphaType.
-If imageInfo.colorType() is kRGB_565_SkColorType or kGray_8_SkColorType,
-imageInfo.alphaType() is set to kOpaque_SkAlphaType.
-If imageInfo.colorType() is kARGB_4444_SkColorType, kRGBA_8888_SkColorType,
-kBGRA_8888_SkColorType, or kRGBA_F16_SkColorType: imageInfo.alphaType() remains
-unchanged.
-
-rowBytes must equal or exceed imageInfo.minRowBytes(). If imageInfo.colorSpace() is
-kUnknown_SkColorType, rowBytes is ignored and treated as zero; for all other
-Color_Space values, rowBytes of zero is treated as imageInfo.minRowBytes().
-
-Calls reset() and returns false if:
-#List
-# rowBytes exceeds 31 bits ##
-# imageInfo.width() is negative ##
-# imageInfo.height() is negative ##
-# rowBytes is positive and less than imageInfo.width() times imageInfo.bytesPerPixel() ##
-##
-
-#Param imageInfo  contains width, height, Alpha_Type, Color_Type, Color_Space ##
-#Param rowBytes   imageInfo.minRowBytes() or larger; or zero ##
-
-#Return  true if Image_Info set successfully ##
-
-#Example
-#Height 96
-###^
-SkBitmap bitmap;
-bitmap.setInfo(SkImageInfo::MakeN32(44, 16, kOpaque_SkAlphaType));
-bitmap.allocPixels();
-bitmap.eraseColor(SK_ColorGREEN);
-SkCanvas offscreen(bitmap);
-SkPaint paint;
-offscreen.drawString("!@#$%", 0, 12, paint);
-canvas->scale(6, 6);
-canvas->drawBitmap(bitmap, 0, 0);
-^^^#
-##
-
-#SeeAlso Alpha_Type Color_Type Color_Space height rowBytes width
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Enum AllocFlags
-#Line # zero pixel memory ##
-#Code
-#Populate
-##
-
-AllocFlags provides the option to zero pixel memory when allocated.
-
-#Const kZeroPixels_AllocFlag 1
-#Line # zero pixel memory ##
-    Instructs tryAllocPixelsFlags and allocPixelsFlags to zero pixel memory.
-##
-
-#NoExample
-##
-
-#SeeAlso tryAllocPixelsFlags allocPixelsFlags erase eraseColor
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Allocate
-#Line # allocates storage for pixels ##
-##
-
-#Method bool tryAllocPixelsFlags(const SkImageInfo& info, uint32_t flags)
-#In Allocate
-#Line # allocates pixels from Image_Info with options if possible ##
-#Populate
-
-#Example
-    SkBitmap bitmap;
-    if (!bitmap.tryAllocPixelsFlags(SkImageInfo::MakeN32(10000, 10000, kOpaque_SkAlphaType),
-                                    SkBitmap::kZeroPixels_AllocFlag)) {
-        SkDebugf("bitmap allocation failed!\n");
-    } else {
-        SkDebugf("bitmap allocation succeeded!\n");
-    }
-#StdOut
-bitmap allocation succeeded!
-##
-##
-
-#SeeAlso allocPixelsFlags tryAllocPixels SkMallocPixelRef::MakeZeroed
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void allocPixelsFlags(const SkImageInfo& info, uint32_t flags)
-#In Allocate
-#Line # allocates pixels from Image_Info with options, or aborts ##
-#Populate
-
-#Example
-#Height 128
-#Description
-Text is drawn on a transparent background; drawing the bitmap a second time
-lets the first draw show through.
-##
-###^
-SkBitmap bitmap;
-bitmap.allocPixelsFlags(SkImageInfo::MakeN32(44, 16, kPremul_SkAlphaType),
-                        SkBitmap::kZeroPixels_AllocFlag);
-SkCanvas offscreen(bitmap);
-SkPaint paint;
-offscreen.drawString("!@#$%", 0, 12, paint);
-canvas->scale(6, 6);
-canvas->drawBitmap(bitmap, 0, 0);
-canvas->drawBitmap(bitmap, 8, 8);
-^^^#
-##
-
-#SeeAlso tryAllocPixelsFlags allocPixels SkMallocPixelRef::MakeZeroed
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool tryAllocPixels(const SkImageInfo& info, size_t rowBytes)
-#In Allocate
-#Line # allocates pixels from Image_Info if possible ##
-#Populate
-
-#Example
-#Image 3
-SkBitmap bitmap;
-SkImageInfo info = SkImageInfo::Make(64, 256, kGray_8_SkColorType, kOpaque_SkAlphaType);
-if (bitmap.tryAllocPixels(info, 0)) {
-    SkCanvas offscreen(bitmap);
-    offscreen.scale(.5f, .5f);
-    for (int x : { 0, 64, 128, 192 } ) {
-        offscreen.drawBitmap(source, -x, 0);
-        canvas->drawBitmap(bitmap, x, 0);
-    }
-}
-##
-
-#SeeAlso tryAllocPixelsFlags allocPixels SkMallocPixelRef::MakeAllocate
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void allocPixels(const SkImageInfo& info, size_t rowBytes)
-#In Allocate
-#Line # allocates pixels from Image_Info, or aborts ##
-#Populate
-
-#Example
-#Image 3
-SkBitmap bitmap;
-SkImageInfo info = SkImageInfo::Make(256, 64, kGray_8_SkColorType, kOpaque_SkAlphaType);
-bitmap.allocPixels(info, info.width() * info.bytesPerPixel() + 64);
-SkCanvas offscreen(bitmap);
-offscreen.scale(.5f, .5f);
-for (int y : { 0, 64, 128, 192 } ) {
-    offscreen.drawBitmap(source, 0, -y);
-    canvas->drawBitmap(bitmap, 0, y);
-}
-##
-
-#SeeAlso tryAllocPixels allocPixelsFlags SkMallocPixelRef::MakeAllocate
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool tryAllocPixels(const SkImageInfo& info)
-#Populate
-
-#Example
-#Image 3
-SkBitmap bitmap;
-if (bitmap.tryAllocPixels(SkImageInfo::Make(64, 64, kGray_8_SkColorType, kOpaque_SkAlphaType))) {
-    SkCanvas offscreen(bitmap);
-    offscreen.scale(.25f, .5f);
-    for (int y : { 0, 64, 128, 192 } ) {
-        offscreen.drawBitmap(source, -y, -y);
-        canvas->drawBitmap(bitmap, y, y);
-    }
-}
-##
-
-#SeeAlso tryAllocPixelsFlags allocPixels SkMallocPixelRef::MakeAllocate
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void allocPixels(const SkImageInfo& info)
-#Populate
-
-#Example
-#Image 4
-SkBitmap bitmap;
-bitmap.allocPixels(SkImageInfo::Make(64, 64, kGray_8_SkColorType, kOpaque_SkAlphaType));
-SkCanvas offscreen(bitmap);
-offscreen.scale(.5f, .5f);
-for (int y : { 0, 64, 128, 192 } ) {
-    offscreen.drawBitmap(source, -y, -y);
-    canvas->drawBitmap(bitmap, y, y);
-}
-##
-
-#SeeAlso tryAllocPixels allocPixelsFlags SkMallocPixelRef::MakeAllocate
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool tryAllocN32Pixels(int width, int height, bool isOpaque = false)
-#In Allocate
-#Line # allocates compatible ARGB pixels if possible ##
-#Populate
-
-#Example
-#Height 160
-    SkBitmap bitmap;
-    if (bitmap.tryAllocN32Pixels(80, 80)) {
-        bitmap.eraseColor(SK_ColorTRANSPARENT);
-        bitmap.erase(0x7f3f7fff, SkIRect::MakeWH(50, 30));
-        bitmap.erase(0x3f7fff3f, SkIRect::MakeXYWH(20, 10, 50, 30));
-        bitmap.erase(0x5fff3f7f, SkIRect::MakeXYWH(40, 20, 50, 30));
-        canvas->drawBitmap(bitmap, 0, 0);
-        for (int x : { 0, 30, 60, 90 } ) {
-            canvas->drawBitmap(bitmap, x, 70);
-        }
-    }
-##
-
-#SeeAlso tryAllocPixels allocN32Pixels SkMallocPixelRef::MakeAllocate
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void allocN32Pixels(int width, int height, bool isOpaque = false)
-#In Allocate
-#Line # allocates compatible ARGB pixels, or aborts ##
-#Populate
-
-#Example
-    SkRandom random;
-    SkBitmap bitmap;
-    bitmap.allocN32Pixels(64, 64);
-    bitmap.eraseColor(SK_ColorTRANSPARENT);
-    for (int y = 0; y < 256; y += 64) {
-        for (int x = 0; x < 256; x += 64) {
-            SkColor color = random.nextU();
-            uint32_t w = random.nextRangeU(4, 32);
-            uint32_t cx = random.nextRangeU(0, 64 - w);
-            uint32_t h = random.nextRangeU(4, 32);
-            uint32_t cy = random.nextRangeU(0, 64 - h);
-            bitmap.erase(color, SkIRect::MakeXYWH(cx, cy, w, h));
-            canvas->drawBitmap(bitmap, x, y);
-        }
-    }
-##
-
-#SeeAlso allocPixels tryAllocN32Pixels SkMallocPixelRef::MakeAllocate
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool installPixels(const SkImageInfo& info, void* pixels, size_t rowBytes,
-                       void (*releaseProc)(void* addr, void* context), void* context)
-#In Allocate
-#Line # creates Pixel_Ref, with optional release function ##
-#Populate
-
-#Example
-#Description
-releaseProc is called immediately because rowBytes is too small for Pixel_Ref.
-##
-#Function
-static void releaseProc(void* addr, void* ) {
-    SkDebugf("releaseProc called\n");
-    delete[] (uint32_t*) addr;
-}
-
-##
-
-void draw(SkCanvas* canvas) {
-   SkBitmap bitmap;
-   void* pixels = new uint32_t[8 * 8];
-   SkImageInfo info = SkImageInfo::MakeN32(8, 8, kOpaque_SkAlphaType);
-   SkDebugf("before installPixels\n");
-   bool installed = bitmap.installPixels(info, pixels, 16, releaseProc, nullptr);
-   SkDebugf("install " "%s" "successful\n", installed ? "" : "not ");
-}
-#StdOut
-before installPixels
-releaseProc called
-install not successful
-##
-##
-
-#SeeAlso allocPixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool installPixels(const SkImageInfo& info, void* pixels, size_t rowBytes)
-#Populate
-
-#Example
-#Bug 7079
-#Description
-GPU does not support kUnpremul_SkAlphaType, does not assert that it does not.
-##
-void draw(SkCanvas* canvas) {
-   SkRandom random;
-   SkBitmap bitmap;
-   const int width = 8;
-   const int height = 8;
-   uint32_t pixels[width * height];
-   for (unsigned x = 0; x < width * height; ++x) {
-       pixels[x] = random.nextU();
-   }
-   SkImageInfo info = SkImageInfo::MakeN32(width, height, kUnpremul_SkAlphaType);
-   if (bitmap.installPixels(info, pixels, info.minRowBytes())) {
-       canvas->scale(32, 32);
-       canvas->drawBitmap(bitmap, 0, 0);
-   }
-}
-##
-
-#SeeAlso allocPixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool installPixels(const SkPixmap& pixmap)
-#Populate
-
-#Example
-#Description
-Draw a five by five bitmap, and draw it again with a center white pixel.
-##
-#Height 64
-    uint8_t storage[][5] = {{ 0xCA, 0xDA, 0xCA, 0xC9, 0xA3 },
-                            { 0xAC, 0xA8, 0x89, 0x47, 0x87 },
-                            { 0x4B, 0x25, 0x25, 0x25, 0x46 },
-                            { 0x90, 0x81, 0x25, 0x41, 0x33 },
-                            { 0x75, 0x55, 0x44, 0x20, 0x00 }};
-    SkImageInfo imageInfo = SkImageInfo::Make(5, 5, kGray_8_SkColorType, kOpaque_SkAlphaType);
-    SkPixmap pixmap(imageInfo, storage[0], sizeof(storage) / 5);
-    SkBitmap bitmap;
-    bitmap.installPixels(pixmap);
-    canvas->scale(10, 10);
-    canvas->drawBitmap(bitmap, 0, 0);
-    *pixmap.writable_addr8(2, 2) = 0xFF;
-    bitmap.installPixels(pixmap);
-    canvas->drawBitmap(bitmap, 10, 0);
-##
-
-#SeeAlso allocPixels
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Pixels
-#Line # read and write pixel values ##
-##
-
-#Method void setPixels(void* pixels)
-#In Pixels
-#Line # sets Pixel_Ref without an offset ##
-#Populate
-
-#Example
-#Height 50
-    uint8_t set1[5] = { 0xCA, 0xDA, 0xCA, 0xC9, 0xA3 };
-    uint8_t set2[5] = { 0xAC, 0xA8, 0x89, 0x47, 0x87 };
-    SkBitmap bitmap;
-    bitmap.installPixels(SkImageInfo::Make(5, 1, kGray_8_SkColorType, kOpaque_SkAlphaType), set1, 5);
-    canvas->scale(10, 50);
-    canvas->drawBitmap(bitmap, 0, 0);
-    bitmap.setPixels(set2);
-    canvas->drawBitmap(bitmap, 10, 0);
-##
-
-#SeeAlso installPixels allocPixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool tryAllocPixels()
-#In Allocate
-#Populate
-
-#Example
-#Height 50
-#Description
-Bitmap hosts and draws gray values in set1. tryAllocPixels replaces Pixel_Ref
-and erases it to black, but does not alter set1. setPixels replaces black
-Pixel_Ref with set1.
-##
-    uint8_t set1[5] = { 0xCA, 0xDA, 0xCA, 0xC9, 0xA3 };
-    SkBitmap bitmap;
-    bitmap.installPixels(SkImageInfo::Make(5, 1, kGray_8_SkColorType, kOpaque_SkAlphaType), set1, 5);
-    canvas->scale(10, 50);
-    canvas->drawBitmap(bitmap, 0, 0);
-    if (bitmap.tryAllocPixels()) {
-        bitmap.eraseColor(SK_ColorBLACK);
-        canvas->drawBitmap(bitmap, 8, 0);
-        bitmap.setPixels(set1);
-        canvas->drawBitmap(bitmap, 16, 0);
-    }
-##
-
-#SeeAlso allocPixels installPixels setPixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void allocPixels()
-#In Allocate
-#Populate
-
-#Example
-#Height 50
-#Description
-Bitmap hosts and draws gray values in set1. allocPixels replaces Pixel_Ref
-and erases it to black, but does not alter set1. setPixels replaces black
-Pixel_Ref with set2.
-##
-    uint8_t set1[5] = { 0xCA, 0xDA, 0xCA, 0xC9, 0xA3 };
-    uint8_t set2[5] = { 0xAC, 0xA8, 0x89, 0x47, 0x87 };
-    SkBitmap bitmap;
-    bitmap.installPixels(SkImageInfo::Make(5, 1, kGray_8_SkColorType, kOpaque_SkAlphaType), set1, 5);
-    canvas->scale(10, 50);
-    canvas->drawBitmap(bitmap, 0, 0);
-    bitmap.allocPixels();
-    bitmap.eraseColor(SK_ColorBLACK);
-    canvas->drawBitmap(bitmap, 8, 0);
-    bitmap.setPixels(set2);
-    canvas->drawBitmap(bitmap, 16, 0);
-##
-
-#SeeAlso tryAllocPixels installPixels setPixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool tryAllocPixels(Allocator* allocator)
-#Populate
-
-#Example
-#Height 100
-#Description
-HeapAllocator limits the maximum size of Bitmap to two gigabytes. Using
-a custom allocator, this limitation may be relaxed. This example can be
-modified to allocate an eight gigabyte Bitmap on a 64-bit platform with
-sufficient memory.
-##
-#Function
-class LargePixelRef : public SkPixelRef {
-public:
-    LargePixelRef(const SkImageInfo& info, char* storage, size_t rowBytes)
-        : SkPixelRef(info.width(), info.height(), storage, rowBytes) {
-    }
-
-    ~LargePixelRef() override {
-        delete[] (char* ) this->pixels();
-    }
-};
-
-class LargeAllocator : public SkBitmap::Allocator {
-public:
-    bool allocPixelRef(SkBitmap* bitmap) override {
-        const SkImageInfo& info = bitmap->info();
-        uint64_t rowBytes = info.minRowBytes64();
-        uint64_t size = info.height() * rowBytes;
-        char* addr = new char[size];
-        if (nullptr == addr) {
-            return false;
-        }
-        sk_sp<SkPixelRef> pr = sk_sp<SkPixelRef>(new LargePixelRef(info, addr, rowBytes));
-        if (!pr) {
-            return false;
-        }
-        bitmap->setPixelRef(std::move(pr), 0, 0);
-        return true;
-    }
-};
-
-##
-
-void draw(SkCanvas* canvas) {
-   LargeAllocator largeAllocator;
-   SkBitmap bitmap;
-   int width = 100; // make this 20000
-   int height = 100; // and this 100000 to allocate 8 gigs on a 64-bit platform
-   bitmap.setInfo(SkImageInfo::MakeN32(width, height, kOpaque_SkAlphaType));
-   if (bitmap.tryAllocPixels(&largeAllocator)) {
-       bitmap.eraseColor(0xff55aa33);
-       canvas->drawBitmap(bitmap, 0, 0);
-   }
-}
-
-##
-
-#SeeAlso allocPixels Allocator Pixel_Ref
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void allocPixels(Allocator* allocator)
-#Populate
-
-#Example
-#Height 32
-#Function
-class TinyAllocator : public SkBitmap::Allocator {
-public:
-    bool allocPixelRef(SkBitmap* bitmap) override {
-        const SkImageInfo& info = bitmap->info();
-        if (info.height() * info.minRowBytes() > sizeof(storage)) {
-            return false;
-        }
-        sk_sp<SkPixelRef> pr = sk_sp<SkPixelRef>(
-                new SkPixelRef(info.width(), info.height(), storage, info.minRowBytes()));
-        bitmap->setPixelRef(std::move(pr), 0, 0);
-        return true;
-    }
-
-    char storage[16];
-};
-
-##
-
-void draw(SkCanvas* canvas) {
-   TinyAllocator tinyAllocator;
-   SkBitmap bitmap;
-   bitmap.setInfo(SkImageInfo::MakeN32(2, 2, kOpaque_SkAlphaType));
-   if (bitmap.tryAllocPixels(&tinyAllocator)) {
-       bitmap.eraseColor(0xff55aa33);
-       bitmap.erase(0xffaa3355, SkIRect::MakeXYWH(1, 1, 1, 1));
-       canvas->scale(16, 16);
-       canvas->drawBitmap(bitmap, 0, 0);
-   }
-}
-##
-
-#SeeAlso allocPixels Allocator Pixel_Ref
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPixelRef* pixelRef() const
-#In Property
-#Line # returns Pixel_Ref, or nullptr ##
-#Populate
-
-#Example
-#Image 3
-   SkBitmap subset;
-   source.extractSubset(&subset, SkIRect::MakeXYWH(32, 64, 128, 256));
-   SkDebugf("src ref %c= sub ref\n", source.pixelRef() == subset.pixelRef() ? '=' : '!');
-   SkDebugf("src pixels %c= sub pixels\n", source.getPixels() == subset.getPixels() ? '=' : '!');
-   SkDebugf("src addr %c= sub addr\n", source.getAddr(32, 64) == subset.getAddr(0, 0) ? '=' : '!');
-##
-
-#SeeAlso getPixels getAddr
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkIPoint pixelRefOrigin() const
-#In Property
-#Line # returns offset within Pixel_Ref ##
-#Populate
-
-#Example
-#Image 3
-   SkBitmap subset;
-   source.extractSubset(&subset, SkIRect::MakeXYWH(32, 64, 128, 256));
-   SkIPoint sourceOrigin = source.pixelRefOrigin();
-   SkIPoint subsetOrigin = subset.pixelRefOrigin();
-   SkDebugf("source origin: %d, %d\n", sourceOrigin.fX, sourceOrigin.fY);
-   SkDebugf("subset origin: %d, %d\n", subsetOrigin.fX, subsetOrigin.fY);
-#StdOut
-source origin: 0, 0
-subset origin: 32, 64
-##
-##
-
-#SeeAlso SkPixelRef getSubset setPixelRef
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Set
-#Line # updates values and attributes ##
-##
-
-#Method void setPixelRef(sk_sp<SkPixelRef> pixelRef, int dx, int dy)
-#In Set
-#Line # sets Pixel_Ref and offset ##
-#Populate
-
-#Example
-#Height 140
-#Image 5
-#Description
-Treating 32-bit data as 8-bit data is unlikely to produce useful results.
-##
-    SkBitmap bitmap;
-    bitmap.setInfo(SkImageInfo::Make(source.width() - 5, source.height() - 5,
-                   kGray_8_SkColorType, kOpaque_SkAlphaType), source.rowBytes());
-    bitmap.setPixelRef(sk_ref_sp(source.pixelRef()), 5, 5);
-    canvas->drawBitmap(bitmap, 10, 10);
-##
-
-#SeeAlso setInfo
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readyToDraw() const
-#In Utility
-#Line # returns true if address of pixels is not nullptr ##
-#Populate
-
-#Example
-#Image 5
-#Height 160
-    if (source.readyToDraw()) {
-        canvas->drawBitmap(source, 10, 10);
-    }
-##
-
-#SeeAlso getPixels drawsNothing
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method uint32_t getGenerationID() const
-#In Utility
-#Line # returns unique ID ##
-#Populate
-
-#Example
-    SkBitmap bitmap;
-    SkDebugf("empty id %u\n", bitmap.getGenerationID());
-    bitmap.allocPixels(SkImageInfo::MakeN32(64, 64, kOpaque_SkAlphaType));
-    SkDebugf("alloc id %u\n", bitmap.getGenerationID());
-    bitmap.eraseColor(SK_ColorRED);
-    SkDebugf("erase id %u\n", bitmap.getGenerationID());
-#StdOut
-#Volatile
-empty id 0
-alloc id 4
-erase id 6
-##
-##
-
-#SeeAlso notifyPixelsChanged Pixel_Ref
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void notifyPixelsChanged() const
-#In Pixels
-#Line # marks pixels as changed, altering the unique ID ##
-#Populate
-
-#Example
-#Height 20
-    SkBitmap bitmap;
-    bitmap.setInfo(SkImageInfo::Make(1, 1, kRGBA_8888_SkColorType, kOpaque_SkAlphaType));
-    bitmap.allocPixels();
-    bitmap.eraseColor(SK_ColorRED);
-    canvas->scale(16, 16);
-    canvas->drawBitmap(bitmap, 0, 0);
-    *(SkPMColor*) bitmap.getPixels() = SkPreMultiplyColor(SK_ColorBLUE);
-    canvas->drawBitmap(bitmap, 2, 0);
-    bitmap.notifyPixelsChanged();
-    *(SkPMColor*) bitmap.getPixels() = SkPreMultiplyColor(SK_ColorGREEN);
-    canvas->drawBitmap(bitmap, 4, 0);
-##
-
-#SeeAlso getGenerationID isVolatile Pixel_Ref
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Draw
-#Line # sets pixels to Color ##
-##
-
-#Method void eraseColor(SkColor c) const
-#In Draw
-#Line # writes Color to pixels ##
-#Populate
-
-#Example
-#Height 20
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeN32(1, 1, kOpaque_SkAlphaType));
-    bitmap.eraseColor(SK_ColorRED);
-    canvas->scale(16, 16);
-    canvas->drawBitmap(bitmap, 0, 0);
-##
-
-#SeeAlso eraseARGB erase
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void eraseARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b) const
-#In Draw
-#Line # writes Color to pixels ##
-#Populate
-
-#Example
-#Height 80
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeN32(1, 1, kPremul_SkAlphaType));
-    bitmap.eraseARGB(0x7f, 0xff, 0x7f, 0x3f);
-    canvas->scale(50, 50);
-    canvas->drawBitmap(bitmap, 0, 0);
-    canvas->drawBitmap(bitmap, .5f, .5f);
-##
-
-#SeeAlso eraseColor erase
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void erase(SkColor c, const SkIRect& area) const
-#In Draw
-#Line # writes Color to rectangle of pixels ##
-#Populate
-
-#Example
-#Height 70
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeN32(2, 2, kPremul_SkAlphaType));
-    bitmap.erase(0x7fff7f3f, SkIRect::MakeWH(1, 1));
-    bitmap.erase(0x7f7f3fff, SkIRect::MakeXYWH(0, 1, 1, 1));
-    bitmap.erase(0x7f3fff7f, SkIRect::MakeXYWH(1, 0, 1, 1));
-    bitmap.erase(0x7f1fbf5f, SkIRect::MakeXYWH(1, 1, 1, 1));
-    canvas->scale(25, 25);
-    canvas->drawBitmap(bitmap, 0, 0);
-    canvas->drawBitmap(bitmap, .5f, .5f);
-
-##
-
-#SeeAlso eraseColor eraseARGB SkCanvas::drawRect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkColor getColor(int x, int y) const
-#In Property
-#In Pixels
-#Line # returns one pixel as Unpremultiplied Color ##
-#Populate
-
-#Example
-    const int w = 4;
-    const int h = 4;
-    SkColor colors[][w] = {
-        { 0x00000000, 0x2a0e002a, 0x55380055, 0x7f7f007f },
-        { 0x2a000e2a, 0x551c1c55, 0x7f542a7f, 0xaaaa38aa },
-        { 0x55003855, 0x7f2a547f, 0xaa7171aa, 0xd4d48dd4 },
-        { 0x7f007f7f, 0xaa38aaaa, 0xd48dd4d4, 0xffffffff }
-    };
-    SkDebugf("Premultiplied:\n");
-    for (int y = 0; y < h; ++y) {
-        SkDebugf("(0, %d) ", y);
-        for (int x = 0; x < w; ++x) {
-            SkDebugf("0x%08x%c", colors[y][x], x == w - 1 ? '\n' : ' ');
-        }
-    }
-    SkPixmap pixmap(SkImageInfo::MakeN32(w, h, kPremul_SkAlphaType), colors, w * 4);
-    SkBitmap bitmap;
-    bitmap.installPixels(pixmap);
-    SkDebugf("Unpremultiplied:\n");
-    for (int y = 0; y < h; ++y) {
-        SkDebugf("(0, %d) ", y);
-        for (int x = 0; x < w; ++x) {
-            SkDebugf("0x%08x%c", bitmap.getColor(x, y), x == w - 1 ? '\n' : ' ');
-        }
-    }
-#StdOut
-Premultiplied:
-(0, 0) 0x00000000 0x2a0e002a 0x55380055 0x7f7f007f
-(0, 1) 0x2a000e2a 0x551c1c55 0x7f542a7f 0xaaaa38aa
-(0, 2) 0x55003855 0x7f2a547f 0xaa7171aa 0xd4d48dd4
-(0, 3) 0x7f007f7f 0xaa38aaaa 0xd48dd4d4 0xffffffff
-Unpremultiplied:
-(0, 0) 0x00000000 0x2a5500ff 0x55a800ff 0x7fff00ff
-(0, 1) 0x2a0055ff 0x555454ff 0x7fa954ff 0xaaff54ff
-(0, 2) 0x5500a8ff 0x7f54a9ff 0xaaaaaaff 0xd4ffaaff
-(0, 3) 0x7f00ffff 0xaa54ffff 0xd4aaffff 0xffffffff
-##
-##
-
-#SeeAlso getAlphaf getAddr readPixels
-
-##
-
-#Method float getAlphaf(int x, int y) const
-#In Property
-#Line # returns Alpha normalized from zero to one ##
-
-Looks up the pixel at (x,y) and return its alpha component, normalized to [0..1].
-This is roughly equivalent to #Formula # SkGetColorA(getColor()) ##, but can be more efficient
-(and more precise if the pixels store more than 8 bits per component).
-
-#Param x  column index, zero or greater, and less than width() ##
-#Param y  row index, zero or greater, and less than height() ##
-
-#Return   alpha converted to normalized float ##
-
-#NoExample
-##
-
-#SeeAlso getColor
-
-##
-
-
-# ------------------------------------------------------------------------------
-
-#Method void* getAddr(int x, int y) const
-#In Property
-#Line # returns readable pixel address as void pointer ##
-#Populate
-
-#Example
-#Image 3
-    char* row0 = (char* ) source.getAddr(0, 0);
-    char* row1 = (char* ) source.getAddr(0, 1);
-    SkDebugf("addr interval %c= rowBytes\n",
-             (size_t) (row1 - row0) == source.rowBytes() ? '=' : '!');
-#StdOut
-addr interval == rowBytes
-##
-##
-
-#SeeAlso getAddr8 getAddr16 getAddr32 readPixels SkPixmap::addr
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method uint32_t* getAddr32(int x, int y) const
-#In Property
-#Line # returns readable pixel address as 32-bit pointer ##
-Returns address at (x, y).
-
-Input is not validated. Triggers an assert() if built with SK_DEBUG defined and:
-#List
-# Pixel_Ref is nullptr ##
-# bytesPerPixel() is not four ##
-# x is negative, or not less than width() ##
-# y is negative, or not less than height() ##
-##
-
-#Param x  column index, zero or greater, and less than width() ##
-#Param y  row index, zero or greater, and less than height() ##
-
-#Return  unsigned 32-bit pointer to pixel at (x, y) ##
-
-#Example
-#Image 3
-    uint32_t* row0 = source.getAddr32(0, 0);
-    uint32_t* row1 = source.getAddr32(0, 1);
-    size_t interval = (row1 - row0) * source.bytesPerPixel();
-    SkDebugf("addr interval %c= rowBytes\n", interval == source.rowBytes() ? '=' : '!');
-#StdOut
-addr interval == rowBytes
-##
-##
-
-#SeeAlso getAddr8 getAddr16 getAddr readPixels SkPixmap::addr32
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method uint16_t* getAddr16(int x, int y) const
-#In Property
-#Line # returns readable pixel address as 16-bit pointer ##
-Returns address at (x, y).
-
-Input is not validated. Triggers an assert() if built with SK_DEBUG defined and:
-#List
-# Pixel_Ref is nullptr ##
-# bytesPerPixel() is not two ##
-# x is negative, or not less than width() ##
-# y is negative, or not less than height() ##
-##
-
-#Param x  column index, zero or greater, and less than width() ##
-#Param y  row index, zero or greater, and less than height() ##
-
-#Return  unsigned 16-bit pointer to pixel at (x, y)##
-
-#Example
-#Image 3
-    SkBitmap bitmap16;
-    SkImageInfo dstInfo = SkImageInfo::Make(source.width(), source.height(), kARGB_4444_SkColorType,
-                     kPremul_SkAlphaType);
-    bitmap16.allocPixels(dstInfo);
-    if (source.readPixels(dstInfo, bitmap16.getPixels(), bitmap16.rowBytes(), 0, 0)) {
-        uint16_t* row0 = bitmap16.getAddr16(0, 0);
-        uint16_t* row1 = bitmap16.getAddr16(0, 1);
-        size_t interval = (row1 - row0) * bitmap16.bytesPerPixel();
-        SkDebugf("addr interval %c= rowBytes\n", interval == bitmap16.rowBytes() ? '=' : '!');
-    }
-#StdOut
-addr interval == rowBytes
-##
-##
-
-#SeeAlso getAddr8 getAddr getAddr32 readPixels SkPixmap::addr16
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method uint8_t* getAddr8(int x, int y) const
-#In Property
-#Line # returns readable pixel address as 8-bit pointer ##
-Returns address at (x, y).
-
-Input is not validated. Triggers an assert() if built with SK_DEBUG defined and:
-#List
-# Pixel_Ref is nullptr ##
-# bytesPerPixel() is not one ##
-# x is negative, or not less than width() ##
-# y is negative, or not less than height() ##
-##
-
-#Param x  column index, zero or greater, and less than width() ##
-#Param y  row index, zero or greater, and less than height() ##
-
-#Return unsigned 8-bit pointer to pixel at (x, y) ##
-
-#Example
-   SkBitmap bitmap;
-   const int width = 8;
-   const int height = 8;
-   uint8_t pixels[height][width];
-   SkImageInfo info = SkImageInfo::Make(width, height, kGray_8_SkColorType, kOpaque_SkAlphaType);
-   if (bitmap.installPixels(info, pixels, info.minRowBytes())) {
-       SkDebugf("&pixels[4][2] %c= bitmap.getAddr8(2, 4)\n",
-                 &pixels[4][2]  == bitmap.getAddr8(2, 4) ? '=' : '!');
-   }
-#StdOut
-&pixels[4][2] == bitmap.getAddr8(2, 4)
-##
-##
-
-#SeeAlso getAddr getAddr16 getAddr32 readPixels SkPixmap::addr8
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool extractSubset(SkBitmap* dst, const SkIRect& subset) const
-#In Constructors
-#Line # creates Bitmap, sharing pixels if possible ##
-Shares Pixel_Ref with dst. Pixels are not copied; Bitmap and dst point
-to the same pixels; dst bounds() are set to the intersection of subset
-and the original bounds().
-
-subset may be larger than bounds(). Any area outside of bounds() is ignored.
-
-Any contents of dst are discarded. isVolatile setting is copied to dst.
-dst is set to colorType, alphaType, and colorSpace.
-
-Return false if:
-#List
-# dst is nullptr ##
-# Pixel_Ref is nullptr ##
-# subset does not intersect bounds() ##
-##
-
-#Param dst  Bitmap set to subset ##
-#Param subset  rectangle of pixels to reference ##
-
-#Return  true if dst is replaced by subset
-##
-
-#Example
-#Image 3
-    SkIRect bounds, s;
-    source.getBounds(&bounds);
-    SkDebugf("bounds: %d, %d, %d, %d\n", bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
-    SkBitmap subset;
-    for (int left: { -100, 0, 100, 1000 } ) {
-         for (int right: { 0, 100, 1000 } ) {
-             SkIRect b = SkIRect::MakeLTRB(left, 100, right, 200);
-             bool success = source.extractSubset(&subset, b);
-             SkDebugf("subset: %4d, %4d, %4d, %4d  ", b.fLeft, b.fTop, b.fRight, b.fBottom);
-             SkDebugf("success; %s", success ? "true" : "false");
-             if (success) {
-                 subset.getBounds(&s);
-                 SkDebugf("  subset: %d, %d, %d, %d", s.fLeft, s.fTop, s.fRight, s.fBottom);
-             }
-             SkDebugf("\n");
-         }
-    }
-#StdOut
-bounds: 0, 0, 512, 512
-subset: -100,  100,    0,  200  success; false
-subset: -100,  100,  100,  200  success; true  subset: 0, 0, 100, 100
-subset: -100,  100, 1000,  200  success; true  subset: 0, 0, 512, 100
-subset:    0,  100,    0,  200  success; false
-subset:    0,  100,  100,  200  success; true  subset: 0, 0, 100, 100
-subset:    0,  100, 1000,  200  success; true  subset: 0, 0, 512, 100
-subset:  100,  100,    0,  200  success; false
-subset:  100,  100,  100,  200  success; false
-subset:  100,  100, 1000,  200  success; true  subset: 0, 0, 412, 100
-subset: 1000,  100,    0,  200  success; false
-subset: 1000,  100,  100,  200  success; false
-subset: 1000,  100, 1000,  200  success; false
-##
-##
-
-#SeeAlso readPixels writePixels SkCanvas::drawBitmap
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes,
-                    int srcX, int srcY) const
-#In Pixels
-#Line # copies and converts pixels ##
-
-Copies a Rect of pixels from Bitmap to dstPixels. Copy starts at (srcX, srcY),
-and does not exceed Bitmap (width(), height()).
-
-dstInfo specifies width, height, Color_Type, Alpha_Type, and Color_Space of
-destination. dstRowBytes specifics the gap from one destination row to the next.
-Returns true if pixels are copied. Returns false if:
-#List
-# dstInfo has no address ##
-# dstRowBytes is less than dstInfo.minRowBytes() ##
-# Pixel_Ref is nullptr ##
-##
-
-Pixels are copied only if pixel conversion is possible. If Bitmap colorType is
-kGray_8_SkColorType, or kAlpha_8_SkColorType; dstInfo.colorType() must match.
-If Bitmap colorType is kGray_8_SkColorType, dstInfo.colorSpace() must match.
-If Bitmap alphaType is kOpaque_SkAlphaType, dstInfo.alphaType() must
-match. If Bitmap colorSpace is nullptr, dstInfo.colorSpace() must match. Returns
-false if pixel conversion is not possible.
-
-srcX and srcY may be negative to copy only top or left of source. Returns
-false if width() or height() is zero or negative.
-Returns false if #Formula # abs(srcX) >= Bitmap width() ##, or if #Formula # abs(srcY) >= Bitmap height() ##.
-
-#Param dstInfo  destination width, height, Color_Type, Alpha_Type, Color_Space ##
-#Param dstPixels  destination pixel storage ##
-#Param dstRowBytes  destination row length ##
-#Param srcX  column index whose absolute value is less than width() ##
-#Param srcY  row index whose absolute value is less than height() ##
-
-#Return  true if pixels are copied to dstPixels ##
-
-#Example
-#Height 128
-#Description
-Transferring the gradient from 8 bits per component to 4 bits per component
-creates visible banding.
-##
-    const int width = 256;
-    const int height = 64;
-    SkImageInfo srcInfo = SkImageInfo::MakeN32Premul(width, height);
-    SkColor  gradColors[] = { 0xFFAA3300, 0x7F881122 };
-    SkPoint  gradPoints[] = { { 0, 0 }, { 256, 0 } };
-    SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(gradPoints, gradColors, nullptr,
-                    SK_ARRAY_COUNT(gradColors), SkShader::kClamp_TileMode));
-    SkBitmap bitmap;
-    bitmap.allocPixels(srcInfo);
-    SkCanvas srcCanvas(bitmap);
-    srcCanvas.drawRect(SkRect::MakeWH(width, height), paint);
-    canvas->drawBitmap(bitmap, 0, 0);
-    SkImageInfo dstInfo = srcInfo.makeColorType(kARGB_4444_SkColorType);
-    std::vector<int16_t> dstPixels;
-    dstPixels.resize(height * width);
-    bitmap.readPixels(dstInfo, &dstPixels.front(), width * 2, 0, 0);
-    SkPixmap dstPixmap(dstInfo, &dstPixels.front(), width * 2);
-    bitmap.installPixels(dstPixmap);
-    canvas->drawBitmap(bitmap, 0, 64);
-##
-
-#SeeAlso writePixels SkPixmap::readPixels SkCanvas::readPixels SkImage::readPixels SkSurface::readPixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readPixels(const SkPixmap& dst, int srcX, int srcY) const
-
-Copies a Rect of pixels from Bitmap to dst. Copy starts at (srcX, srcY), and
-does not exceed Bitmap (width(), height()).
-
-dst specifies width, height, Color_Type, Alpha_Type, Color_Space, pixel storage,
-and row bytes of destination. dst.rowBytes() specifics the gap from one destination
-row to the next. Returns true if pixels are copied. Returns false if:
-#List
-# dst pixel storage equals nullptr ##
-# dst.rowBytes() is less than SkImageInfo::minRowBytes() ##
-# Pixel_Ref is nullptr ##
-##
-
-Pixels are copied only if pixel conversion is possible. If Bitmap colorType is
-kGray_8_SkColorType, or kAlpha_8_SkColorType; dst Color_Type must match.
-If Bitmap colorType is kGray_8_SkColorType, dst Color_Space must match.
-If Bitmap alphaType is kOpaque_SkAlphaType, dst Alpha_Type must
-match. If Bitmap colorSpace is nullptr, dst Color_Space must match. Returns
-false if pixel conversion is not possible.
-
-srcX and srcY may be negative to copy only top or left of source. Returns
-false if width() or height() is zero or negative.
-Returns false if #Formula # abs(srcX) >= Bitmap width() ##, or if #Formula # abs(srcY) >= Bitmap height() ##.
-
-#Param dst  destination Pixmap: Image_Info, pixels, row bytes ##
-#Param srcX  column index whose absolute value is less than width() ##
-#Param srcY  row index whose absolute value is less than height() ##
-
-#Return  true if pixels are copied to dst ##
-
-#Example
-#Image 3
-    std::vector<int32_t> srcPixels;
-    srcPixels.resize(source.height() * source.rowBytes());
-    for (int y = 0; y < 4; ++y) {
-        for (int x = 0; x < 4; ++x) {
-            SkPixmap pixmap(SkImageInfo::MakeN32Premul(source.width() / 4, source.height() / 4),
-                    &srcPixels.front() + x * source.height() * source.width() / 4 +
-                    y * source.width() / 4, source.rowBytes());
-            source.readPixels(pixmap, x * source.width() / 4, y * source.height() / 4);
-        }
-    }
-    canvas->scale(.5f, .5f);
-    SkBitmap bitmap;
-    bitmap.installPixels(SkImageInfo::MakeN32Premul(source.width(), source.height()),
-                             &srcPixels.front(), source.rowBytes());
-    canvas->drawBitmap(bitmap, 0, 0);
-##
-
-#SeeAlso writePixels SkPixmap::readPixels SkCanvas::readPixels SkImage::readPixels SkSurface::readPixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readPixels(const SkPixmap& dst) const
-
-Copies a Rect of pixels from Bitmap to dst. Copy starts at (0, 0), and
-does not exceed Bitmap (width(), height()).
-
-dst specifies width, height, Color_Type, Alpha_Type, Color_Space, pixel storage,
-and row bytes of destination. dst.rowBytes() specifics the gap from one destination
-row to the next. Returns true if pixels are copied. Returns false if:
-#List
-# dst pixel storage equals nullptr ##
-# dst.rowBytes() is less than SkImageInfo::minRowBytes() ##
-# Pixel_Ref is nullptr ##
-##
-
-Pixels are copied only if pixel conversion is possible. If Bitmap colorType is
-kGray_8_SkColorType, or kAlpha_8_SkColorType; dst Color_Type must match.
-If Bitmap colorType is kGray_8_SkColorType, dst Color_Space must match.
-If Bitmap alphaType is kOpaque_SkAlphaType, dst Alpha_Type must
-match. If Bitmap colorSpace is nullptr, dst Color_Space must match. Returns
-false if pixel conversion is not possible.
-
-#Param dst  destination Pixmap: Image_Info, pixels, row bytes ##
-
-#Return  true if pixels are copied to dst ##
-
-#Example
-#Height 128
-#Image 3
-    std::vector<int32_t> srcPixels;
-    srcPixels.resize(source.height() * source.width() * 8);
-    for (int i = 0;  i < 2; ++i) {
-    SkPixmap pixmap(SkImageInfo::Make(source.width() * 2, source.height(),
-                    i ? kRGBA_8888_SkColorType : kBGRA_8888_SkColorType, kPremul_SkAlphaType),
-                    &srcPixels.front() + i * source.width(), source.rowBytes() * 2);
-        source.readPixels(pixmap);
-    }
-    canvas->scale(.25f, .25f);
-    SkBitmap bitmap;
-    bitmap.installPixels(SkImageInfo::MakeN32Premul(source.width() * 2, source.height()),
-                         &srcPixels.front(), source.rowBytes() * 2);
-    canvas->drawBitmap(bitmap, 0, 0);
-##
-
-#SeeAlso writePixels SkPixmap::readPixels SkCanvas::readPixels SkImage::readPixels SkSurface::readPixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool writePixels(const SkPixmap& src, int dstX, int dstY)
-#In Pixels
-#Line # copies and converts pixels ##
-Copies a Rect of pixels from src. Copy starts at (dstX, dstY), and does not exceed
-(src.width(), src.height()).
-
-src specifies width, height, Color_Type, Alpha_Type, Color_Space, pixel storage,
-and row bytes of source. src.rowBytes() specifics the gap from one source
-row to the next. Returns true if pixels are copied. Returns false if:
-#List
-# src pixel storage equals nullptr ##
-# src.rowBytes() is less than SkImageInfo::minRowBytes() ##
-# Pixel_Ref is nullptr ##
-##
-
-Pixels are copied only if pixel conversion is possible. If Bitmap colorType is
-kGray_8_SkColorType, or kAlpha_8_SkColorType; src Color_Type must match.
-If Bitmap colorType is kGray_8_SkColorType, src Color_Space must match.
-If Bitmap alphaType is kOpaque_SkAlphaType, src Alpha_Type must
-match. If Bitmap colorSpace is nullptr, src Color_Space must match. Returns
-false if pixel conversion is not possible.
-
-dstX and dstY may be negative to copy only top or left of source. Returns
-false if width() or height() is zero or negative.
-Returns false if #Formula # abs(dstX) >= Bitmap width() ##, or if #Formula # abs(dstY) >= Bitmap height() ##.
-
-#Param src  source Pixmap: Image_Info, pixels, row bytes ##
-#Param dstX  column index whose absolute value is less than width() ##
-#Param dstY  row index whose absolute value is less than height() ##
-
-#Return  true if src pixels are copied to Bitmap ##
-
-#Example
-#Image 3
-    std::vector<int32_t> srcPixels;
-    int width = image->width();
-    int height = image->height();
-    srcPixels.resize(height * width  * 4);
-    SkPixmap pixmap(SkImageInfo::MakeN32Premul(width, height), (const void*) &srcPixels.front(),
-                    width * 4);
-    image->readPixels(pixmap, 0, 0);
-    canvas->scale(.5f, .5f);
-    width /= 4;
-    height /= 4;
-    for (int y = 0; y < 4; ++y) {
-        for (int x = 0; x < 4; ++x) {
-            SkBitmap bitmap;
-            bitmap.allocPixels(SkImageInfo::MakeN32Premul(width, height));
-            bitmap.writePixels(pixmap, -y * width, -x * height);
-            canvas->drawBitmap(bitmap, x * width, y * height);
-        }
-    }
-##
-
-#SeeAlso readPixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool writePixels(const SkPixmap& src)
-
-Copies a Rect of pixels from src. Copy starts at (0, 0), and does not exceed
-(src.width(), src.height()).
-
-src specifies width, height, Color_Type, Alpha_Type, Color_Space, pixel storage,
-and row bytes of source. src.rowBytes() specifics the gap from one source
-row to the next. Returns true if pixels are copied. Returns false if:
-#List
-# src pixel storage equals nullptr ##
-# src.rowBytes() is less than SkImageInfo::minRowBytes() ##
-# Pixel_Ref is nullptr ##
-##
-
-Pixels are copied only if pixel conversion is possible. If Bitmap colorType is
-kGray_8_SkColorType, or kAlpha_8_SkColorType; src Color_Type must match.
-If Bitmap colorType is kGray_8_SkColorType, src Color_Space must match.
-If Bitmap alphaType is kOpaque_SkAlphaType, src Alpha_Type must
-match. If Bitmap colorSpace is nullptr, src Color_Space must match. Returns
-false if pixel conversion is not possible.
-
-#Param src  source Pixmap: Image_Info, pixels, row bytes ##
-
-#Return  true if src pixels are copied to Bitmap ##
-
-#Example
-#Height 80
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeN32Premul(2, 2));
-    bitmap.eraseColor(SK_ColorGREEN);
-    SkPMColor color = 0xFF5599BB;
-    SkPixmap src(SkImageInfo::MakeN32Premul(1, 1), &color, 4);
-    bitmap.writePixels(src);
-    canvas->scale(40, 40);
-    canvas->drawBitmap(bitmap, 0, 0);
-##
-
-#SeeAlso readPixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool extractAlpha(SkBitmap* dst) const
-#In Constructors
-#Line # creates Bitmap containing Alpha of pixels ##
-#Populate
-
-#Example
-#Height 100
-    SkBitmap alpha, bitmap;
-    bitmap.allocN32Pixels(100, 100);
-    SkCanvas offscreen(bitmap);
-    offscreen.clear(0);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setColor(SK_ColorBLUE);
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setStrokeWidth(20);
-    offscreen.drawCircle(50, 50, 39, paint);
-    offscreen.flush();
-    bitmap.extractAlpha(&alpha);
-    paint.setColor(SK_ColorRED);
-    canvas->drawBitmap(bitmap, 0, 0, &paint);
-    canvas->drawBitmap(alpha, 100, 0, &paint);
-##
-
-#SeeAlso extractSubset
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool extractAlpha(SkBitmap* dst, const SkPaint* paint,
-                      SkIPoint* offset) const
-#Populate
-
-#Example
-#Height 160
-    auto radiusToSigma = [](SkScalar radius) -> SkScalar {
-         static const SkScalar kBLUR_SIGMA_SCALE = 0.57735f;
-         return radius > 0 ? kBLUR_SIGMA_SCALE * radius + 0.5f : 0.0f;
-    };
-    SkBitmap alpha, bitmap;
-    bitmap.allocN32Pixels(100, 100);
-    SkCanvas offscreen(bitmap);
-    offscreen.clear(0);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setColor(SK_ColorBLUE);
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setStrokeWidth(20);
-    offscreen.drawCircle(50, 50, 39, paint);
-    offscreen.flush();
-    paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, radiusToSigma(25)));
-    SkIPoint offset;
-    bitmap.extractAlpha(&alpha, &paint, &offset);
-    paint.setColor(SK_ColorRED);
-    canvas->drawBitmap(bitmap, 0, -offset.fY, &paint);
-    canvas->drawBitmap(alpha, 100 + offset.fX, 0, &paint);
-##
-
-#SeeAlso extractSubset
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool extractAlpha(SkBitmap* dst, const SkPaint* paint, Allocator* allocator,
-                      SkIPoint* offset) const
-#Populate
-
-#Example
-#Height 128
-    SkBitmap alpha, bitmap;
-    bitmap.allocN32Pixels(100, 100);
-    SkCanvas offscreen(bitmap);
-    offscreen.clear(0);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setColor(SK_ColorBLUE);
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setStrokeWidth(20);
-    offscreen.drawCircle(50, 50, 39, paint);
-    offscreen.flush();
-    paint.setMaskFilter(SkMaskFilter::MakeBlur(kOuter_SkBlurStyle, 3));
-    SkIPoint offset;
-    bitmap.extractAlpha(&alpha, &paint, nullptr, &offset);
-    paint.setColor(SK_ColorRED);
-    canvas->drawBitmap(bitmap, 0, -offset.fY, &paint);
-    canvas->drawBitmap(alpha, 100 + offset.fX, 0, &paint);
-##
-
-#SeeAlso extractSubset
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool peekPixels(SkPixmap* pixmap) const
-#In Pixels
-#Line # returns Pixmap if possible ##
-#Populate
-
-#Example
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeN32Premul(6, 11));
-    SkCanvas offscreen(bitmap);
-    offscreen.clear(SK_ColorWHITE);
-    SkPaint paint;
-    offscreen.drawString("?", 0, 10, paint);
-    SkPixmap pixmap;
-    if (bitmap.peekPixels(&pixmap)) {
-        const SkPMColor* pixels = pixmap.addr32();
-        SkPMColor pmWhite = pixels[0];
-        for (int y = 0; y < bitmap.height(); ++y) {
-            for (int x = 0; x < bitmap.width(); ++x) {
-                SkDebugf("%c", *pixels++ == pmWhite ? '-' : 'x');
-            }
-            SkDebugf("\n");
-        }
-    }
-    #StdOut
-------
--xxx--
-x---x-
-----x-
----x--
---x---
---x---
-------
---x---
---x---
-------
-    #StdOut ##
-##
-
-#SeeAlso pixmap installPixels readPixels writePixels
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Utility
-#Line # rarely called management functions ##
-##
-
-#Method void validate() const;
-#In Utility
-#Line # asserts if Bitmap is invalid (debug only) ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso SkImageInfo::validate
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Class SkBitmap ##
-
-#Topic Bitmap ##
diff --git a/docs/SkBlendMode_Overview.bmh b/docs/SkBlendMode_Overview.bmh
deleted file mode 100644
index 9f3aea8..0000000
--- a/docs/SkBlendMode_Overview.bmh
+++ /dev/null
@@ -1,89 +0,0 @@
-#Topic Blend_Mode_Overview
-
-Describes how destination pixel is replaced with a combination of itself and
-source pixel. Blend_Mode may use source, destination, or both. Blend_Mode may
-operate on each Color component independently, or may allow all source pixel
-components to contribute to one destination pixel component. 
-
-Blend_Mode does not use adjacent pixels to determine the outcome.
-
-Blend_Mode uses source and read destination Alpha to determine written
-destination Alpha; both source and destination Alpha may also affect written
-destination Color components.
-
-Regardless of how Alpha is encoded in source and destination pixel, nearly all
-Color_Types treat it as ranging from zero to one. And, nearly all Blend_Mode
-algorithms limit the output so that all results are also zero to one.
-
-Two exceptions are SkBlendMode::kPlus and kRGBA_F16_SkColorType. 
-
-SkBlendMode::kPlus permits computing Alpha and Color component values larger
-than one. For Color_Types other than kRGBA_F16_SkColorType, resulting Alpha
-and component values are clamped to one.
-
-kRGBA_F16_SkColorType permits values outside the zero to one range. It is up
-to the client to ensure that the result is within the range of zero to one,
-and therefore well-defined.
-
-#Subtopic Porter_Duff
-#Alias Porter_Duff ##
-#Line # classic color blend algorithms ##
-
-#A Compositing Digital Images # https://graphics.pixar.com/library/Compositing/paper.pdf ##
-describes Porter_Duff modes SkBlendMode::kClear through SkBlendMode::kXor.
-
-Drawing a bitmap with transparency using Porter_Duff compositing is free to clear
-the destination.
-
-#Illustration 1
-
-Draw geometry with transparency using Porter_Duff compositing does not combine
-transparent source pixels, leaving the destination outside the geometry untouched.
-
-#Illustration 2
-
-##
-
-#Subtopic Lighten_Darken
-#Line # color blends to lighten or darken result ##
-
-Modes SkBlendMode::kPlus and SkBlendMode::kScreen use
-simple arithmetic to lighten or darken the destination. Modes 
-SkBlendMode::kOverlay through SkBlendMode::kMultiply use more complicated
-algorithms to lighten or darken; sometimes one mode does both, as described by
-#A Blend Modes # https://en.wikipedia.org/wiki/Blend_modes ##
-.
-
-#Illustration
-
-##
-
-#Subtopic Modulate_Blend
-#Line # multiply color components ##
-
-SkBlendMode::kModulate is a mashup of SkBlendMode::kSrcATop and SkBlendMode::kMultiply.
-It multiplies all components, including Alpha; unlike SkBlendMode::kMultiply, if either
-source or destination is transparent, result is transparent. SkBlendMode::kModulate
-uses Premultiplied values to compute the product; SkBlendMode::kMultiply uses Unpremultiplied
-values to compute the product.
-
-#Illustration
-
-##
-
-#Subtopic Color_Blends
-#Line # non-separable blend modes ##
-
-Modes SkBlendMode::kHue, SkBlendMode::kSaturation, SkBlendMode::kColor, and
-SkBlendMode::kLuminosity convert source and destination pixels using all
-components color information, using 
-###$
-$A non-separable blend modes $ https://www.w3.org/TR/compositing-1/#blendingnonseparable $$
-$$$#
-.
-
-#Illustration
-
-##
-
-#Topic Blend_Mode_Overview ##
diff --git a/docs/SkBlendMode_Reference.bmh b/docs/SkBlendMode_Reference.bmh
deleted file mode 100644
index a02b012..0000000
--- a/docs/SkBlendMode_Reference.bmh
+++ /dev/null
@@ -1,901 +0,0 @@
-#Topic Blend_Mode
-
-#PhraseDef list_of_blend_modes
-SkBlendMode::kClear, SkBlendMode::kSrc, SkBlendMode::kDst, SkBlendMode::kSrcOver,
-SkBlendMode::kDstOver, SkBlendMode::kSrcIn, SkBlendMode::kDstIn,
-SkBlendMode::kSrcOut, SkBlendMode::kDstOut, SkBlendMode::kSrcATop,
-SkBlendMode::kDstATop, SkBlendMode::kXor, SkBlendMode::kPlus,
-SkBlendMode::kModulate, SkBlendMode::kScreen, SkBlendMode::kOverlay,
-SkBlendMode::kDarken, SkBlendMode::kLighten, SkBlendMode::kColorDodge,
-SkBlendMode::kColorBurn, SkBlendMode::kHardLight, SkBlendMode::kSoftLight,
-SkBlendMode::kDifference, SkBlendMode::kExclusion, SkBlendMode::kMultiply,
-SkBlendMode::kHue, SkBlendMode::kSaturation, SkBlendMode::kColor,
-SkBlendMode::kLuminosity
-##
-
-#Code
-#Populate
-##
-
-#EnumClass SkBlendMode
-#Line # algorithm combining source and destination pixels ##
-
-# ------------------------------------------------------------------------------
-
-#Const kClear 0
-#Line # replaces destination with zero: fully transparent ##
-#Details Clear
-Replaces destination with Alpha and Color components set to zero;
-a fully transparent pixel.
-##
-
-#Const kSrc 1
-#Line # replaces destination ##
-#Details Src
-Replaces destination with source. Destination alpha and color component values
-are ignored.
-##
-
-#Const kDst 2
-#Line # preserves destination ##
-#Details Dst
-Preserves destination, ignoring source. Drawing with Paint set to kDst has
-no effect.
-##
-
-#Const kSrcOver 3
-#Line # source over destination ##
-#Details Src_Over
-Replaces destination with source blended with destination. If source is opaque,
-replaces destination with source. Used as the default Blend_Mode for SkPaint.
-##
-
-#Const kDstOver 4
-#Line # destination over source ##
-#Details Dst_Over
-Replaces destination with destination blended with source. If destination is opaque,
-has no effect.
-##
-
-#Const kSrcIn 5
-#Line # source trimmed inside destination ##
-#Details Src_In
-Replaces destination with source using destination opacity.
-##
-
-#Const kDstIn 6
-#Line # destination trimmed by source ##
-#Details Dst_In
-Scales destination opacity by source opacity. 
-##
-
-#Const kSrcOut 7
-#Line # source trimmed outside destination ##
-#Details Src_Out
-Replaces destination with source using the inverse of destination opacity,
-drawing source fully where destination opacity is zero.
-##
-
-#Const kDstOut 8
-#Line # destination trimmed outside source ##
-#Details Dst_Out
-Replaces destination opacity with inverse of source opacity. If source is
-transparent, has no effect.
-##
-
-#Const kSrcATop 9
-#Line # source inside destination blended with destination ##
-#Details Src_Atop
-Blends destination with source using read destination opacity.
-##
-
-#Const kDstATop 10
-#Line # destination inside source blended with source ##
-#Details Dst_Atop
-Blends destination with source using source opacity.
-##
-
-#Const kXor 11
-#Line # each of source and destination trimmed outside the other ##
-#Details Xor
-Blends destination by exchanging transparency of the source and destination.
-##
-
-#Const kPlus 12
-#Line # sum of colors ##
-#Details Plus
-Replaces destination with source and destination added together.
-##
-
-#Const kModulate 13
-#Line # product of Premultiplied colors; darkens destination ##
-#Details Modulate
-Replaces destination with source and destination multiplied together.
-##
-
-#Const kScreen 14
-#Line # multiply inverse of pixels, inverting result; brightens destination ##
-#Details Screen
-Replaces destination with inverted source and destination multiplied together.
-##
-
-#Const kLastCoeffMode 14
-#Line # last Porter_Duff blend mode ##
-##
-
-#Const kOverlay 15
-#Line # multiply or screen, depending on destination ##
-#Details Overlay
-Replaces destination with multiply or screen, depending on destination.
-##
-
-#Const kDarken 16
-#Line # darker of source and destination ##
-#Details Darken
-Replaces destination with darker of source and destination.
-##
-
-#Const kLighten 17
-#Line # lighter of source and destination ##
-#Details Lighten
-Replaces destination with lighter of source and destination.
-##
-
-#Const kColorDodge 18
-#Line # brighten destination to reflect source ##
-#Details Color_Dodge
-Makes destination brighter to reflect source.
-##
-
-#Const kColorBurn 19
-#Line # darken destination to reflect source ##
-#Details Color_Burn
-Makes destination darker to reflect source.
-##
-
-#Const kHardLight 20
-#Line # multiply or screen, depending on source ##
-#Details Hard_Light
-Makes destination lighter or darker, depending on source.
-##
-
-#Const kSoftLight 21
-#Line # lighten or darken, depending on source ##
-#Details Soft_Light
-Makes destination lighter or darker, depending on source.
-##
-
-#Const kDifference 22
-#Line # subtract darker from lighter with higher contrast ##
-#Details Difference
-Subtracts darker from lighter with higher contrast.
-##
-
-#Const kExclusion 23
-#Line # subtract darker from lighter with lower contrast ##
-#Details Exclusion
-Subtracts darker from lighter with lower contrast.
-##
-
-#Const kMultiply 24
-#Line # multiply source with destination, darkening image ##
-#Details Multiply
-Multiplies source with destination, darkening image.
-##
-
-#Const kLastSeparableMode 24
-#Line # last blend mode operating separately on components ##
-Last blend mode operating separately on components.
-##
-
-#Const kHue 25
-#Line # hue of source with saturation and luminosity of destination ##
-#Details Hue
-Replaces hue of destination with hue of source, leaving saturation and luminosity
-unchanged.
-##
-
-#Const kSaturation 26
-#Line # saturation of source with hue and luminosity of destination ##
-#Details Saturation
-Replaces saturation of destination saturation hue of source, leaving hue and
-luminosity unchanged.
-##
-
-#Const kColor 27
-#Line # hue and saturation of source with luminosity of destination ##
-#Details Color
-Replaces hue and saturation of destination with hue and saturation of source,
-leaving luminosity unchanged.
-##
-
-#Const kLuminosity 28
-#Line # luminosity of source with hue and saturation of destination ##
-#Details Luminosity
-Replaces luminosity of destination with luminosity of source, leaving hue and
-saturation unchanged.
-##
-
-#Const kLastMode 28
-#Line # last valid value ##
-Used by tests to iterate through all valid values.
-##
-
-#NoExample
-##
-
-#SeeAlso SkCanvas::drawColor SkCanvas::drawVertices SkPaint SkShader::MakeCompose SkXfermodeImageFilter
-
-#Subtopic Clear
-#Line # makes destination pixels transparent ##
-SkBlendMode::kClear sets destination to: #Formula # [0, 0] ##. 
-Use SkBlendMode::kClear to initialize a buffer to fully transparent pixels when
-creating a mask with irregular edges.
-
-#Example
-#Description
-SK_ColorYELLOW is ignored because SkBlendMode::kClear ignores the source pixel
-value and the destination pixel value, always setting the destination to zero.
-##
-    canvas->saveLayer(nullptr, nullptr);
-    canvas->drawColor(SK_ColorYELLOW, SkBlendMode::kClear);
-    SkPaint paint;
-    for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN } ) {
-        SkColor colors[] = { color, SkColorSetA(color, 0) }; 
-        paint.setShader(SkGradientShader::MakeRadial({ 64, 64}, 100,
-                colors, nullptr, SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode));
-        canvas->drawCircle(64, 64, 100, paint);
-        canvas->translate(64, 64);
-    }
-    canvas->restore();
-##
-#SeeAlso SkCanvas::clear
-##
-
-#Subtopic Src
-#Line # replaces destination, ignoring Alpha ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component;
-SkBlendMode::kSrc sets destination to: #Formula # [Sa, Sc] ##. 
-Use SkBlendMode::kSrc to copy one buffer to another. All pixels are copied,
-regardless of source and destination Alpha values. As a parameter to
-SkCanvas::drawAtlas, selects sprites and ignores colors.
-#Example
-#Image 3
-#Description
-SkBlendMode::kSrc does not blend transparent pixels with existing background;
-it punches a transparent hole in the existing image.
-##
-    canvas->drawImage(image, 0, 0);
-    canvas->clipRect({50, 50, 200, 200});
-    SkPaint srcBlend;
-    srcBlend.setBlendMode(SkBlendMode::kSrc);
-    canvas->saveLayer(nullptr, &srcBlend);
-    canvas->drawColor(0);
-    SkPaint transRed;
-    transRed.setColor(SkColorSetA(SK_ColorRED, 127));
-    canvas->drawCircle(125, 125, 75, transRed);
-    canvas->restore();
-##
-#SeeAlso SkSurface::draw SkSurface::readPixels
-##
-
-#Subtopic Dst
-#Line # preserves destination, ignoring source ##
-Given: #Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
- SkBlendMode::kDst preserves destination set to: #Formula # [Da, Dc] ##.
-Setting Paint Blend_Mode to SkBlendMode::kDst causes drawing with
-Paint to have no effect. As a parameter to SkCanvas::drawAtlas,
-selects colors and ignores sprites.
-#Example
-#Image 3
-  SkRSXform xforms[] = { { .5f, 0, 0, 0 }, {0, .5f, 125, 128 } };
-  SkRect tex[] = { { 0, 0, 250, 250 }, { 0, 0, 250, 250 } };
-  SkColor colors[] = { 0x7f55aa00, 0x7f3333bf };
-  canvas->drawAtlas(image.get(), xforms, tex, colors, 2, SkBlendMode::kSrc, nullptr, nullptr);
-  canvas->translate(128, 0);
-  canvas->drawAtlas(image.get(), xforms, tex, colors, 2, SkBlendMode::kDst, nullptr, nullptr);
-##
-##
-
-#Subtopic Src_Over
-#Line # blends source with destination ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-SkBlendMode::kSrcOver replaces destination with: #Formula # [Sa + Da * (1 - Sa), Sc + Dc * (1 - Sa)] ##,
-drawing source over destination. SkBlendMode::kSrcOver is the default for Paint.
-
-SkBlendMode::kSrcOver cannot make destination more transparent; the result will
-be at least as opaque as the less transparent of source and original destination.
-#Example
-    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
-    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
-    SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    paint.setBlendMode(SkBlendMode::kDstIn);
-    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
-    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
-    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    canvas->clipRect( { 30, 30, 226, 226 } );
-    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kSrcOver);
-##
-##
-
-#Subtopic Dst_Over
-#Line # blends destination with source ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-SkBlendMode::kDstOver replaces destination with: #Formula # [Da + Sa * (1 - Da), Dc + Sc * (1 - Da)] ##,
-drawing destination over source. Has no effect destination if is opaque.
-#Example
-    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
-    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
-    SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    paint.setBlendMode(SkBlendMode::kDstIn);
-    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
-    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
-    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    canvas->clipRect( { 30, 30, 226, 226 } );
-    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kDstOver);
-##
-##
-
-#Subtopic Src_In
-#Line # source trimmed inside destination ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha;
-SkBlendMode::kSrcIn replaces destination with: #Formula # [Sa * Da, Sc * Da] ##,
-drawing source with destination opacity.
-#Example
-    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
-    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
-    SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    paint.setBlendMode(SkBlendMode::kDstIn);
-    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
-    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
-    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    canvas->clipRect( { 30, 30, 226, 226 } );
-    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kSrcIn);
-##
-##
-
-#Subtopic Dst_In
-#Line # destination trimmed by source ##
-Given: #Formula # Sa ## as source Alpha,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-SkBlendMode::kDstIn replaces destination with: #Formula # [Da * Sa, Dc * Sa] ##,
-scaling destination Alpha by source Alpha. Resulting
-destination is visible where source is visible.
-#Example
-    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
-    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
-    SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    paint.setBlendMode(SkBlendMode::kDstIn);
-    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
-    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
-    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    canvas->clipRect( { 30, 30, 226, 226 } );
-    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kDstIn);
-##
-##
-
-#Subtopic Src_Out
-#Line # source trimmed outside destination ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha;
-SkBlendMode::kSrcOut replaces destination with: #Formula # [Sa * (1 - Da), Sc * (1 - Da)] ##,
-drawing source fully where destination Alpha is zero. Is destination
-is opaque, has no effect.
-#Example
-    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
-    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
-    SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    paint.setBlendMode(SkBlendMode::kDstIn);
-    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
-    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
-    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    canvas->clipRect( { 30, 30, 226, 226 } );
-    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kSrcOut);
-##
-##
-
-#Subtopic Dst_Out
-#Line # destination trimmed outside source ##
-Given: #Formula # Sa ## as source Alpha,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-SkBlendMode::kDstOut replaces destination with: #Formula # [Da * (1 - Sa), Dc * (1 - Sa)] ##,
-scaling destination Alpha by source transparency. Resulting
-destination is visible where source is transparent. If source is transparent,
-has no effect.
-#Example
-    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
-    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
-    SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    paint.setBlendMode(SkBlendMode::kDstIn);
-    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
-    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
-    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    canvas->clipRect( { 30, 30, 226, 226 } );
-    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kDstOut);
-##
-##
-
-#Subtopic Src_Atop
-#Line # source inside destination over destination ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-SkBlendMode::kSrcATop replaces destination with: #Formula # [Da, Sc * Da + Dc * (1 - Sa)] ##,
-replacing opaque destination with opaque source. If source or destination
-is transparent, has no effect.
-#Example
-    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
-    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
-    SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    paint.setBlendMode(SkBlendMode::kDstIn);
-    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
-    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
-    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    canvas->clipRect( { 30, 30, 226, 226 } );
-    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kSrcATop);
-##
-##
-
-#Subtopic Dst_Atop
-#Line # destination inside source over source ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-SkBlendMode::kDstATop replaces destination with: #Formula # [Sa, Dc * Sa + Sc * (1 - Da)] ##,
-making destination transparent where source is transparent.
-#Example
-    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
-    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
-    SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    paint.setBlendMode(SkBlendMode::kDstATop);
-    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
-    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
-    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    canvas->clipRect( { 30, 30, 226, 226 } );
-    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kSrcATop);
-##
-##
-
-#Subtopic Xor
-#Line # each of source and destination trimmed outside the other ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-SkBlendMode::kXor replaces destination with:
-#Formula # [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + Dc * (1 - Sa)] ##,
-exchanging the transparency of the source and destination.
-#Example
-   SkPaint paint;
-   paint.setBlendMode(SkBlendMode::kXor);
-   for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN } ) {
-       SkColor colors[] = { color, SkColorSetA(color, 192), SkColorSetA(color, 128),
-                            SkColorSetA(color, 0) }; 
-       paint.setShader(SkGradientShader::MakeRadial({ 64, 64}, 100,
-               colors, nullptr, SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode));
-       canvas->drawCircle(64, 64, 100, paint);
-       canvas->translate(64, 64);
-   }
-##
-##
-
-#Subtopic Plus
-#Line # sum of colors ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-SkBlendMode::kPlus replaces destination with: #Formula # [Sa + Da, Sc + Dc] ##,
-summing the Alpha and Color components.
-#Example
-   canvas->drawColor(SK_ColorBLACK);
-   SkPaint paint;
-   paint.setBlendMode(SkBlendMode::kPlus);
-   for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN } ) {
-       SkColor colors[] = { color, SkColorSetA(color, 192), SkColorSetA(color, 128),
-                            SkColorSetA(color, 0) }; 
-       paint.setShader(SkGradientShader::MakeRadial({ 64, 64}, 100,
-               colors, nullptr, SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode));
-       canvas->drawCircle(64, 64, 100, paint);
-       canvas->translate(64, 64);
-   }
-##
-##
-
-#Subtopic Modulate
-#Line # product of Premultiplied colors; darkens destination ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-SkBlendMode::kModulate replaces destination with: #Formula # [Sa * Da, Sc * Dc] ##,
-scaling Alpha and Color components by the lesser of the values.
-SkBlendMode::kModulate differs from SkBlendMode::kMultiply in two ways.
-SkBlendMode::kModulate like SkBlendMode::kSrcATop alters the destination inside
-the destination area, as if the destination Alpha defined the boundaries of a
-soft clip. SkBlendMode::kMultiply like SkBlendMode::kSrcOver can alter the
-destination where the destination is transparent.
-SkBlendMode::kModulate computes the product of the source and destination using
-Premultiplied component values. SkBlendMode::kMultiply the product of the source
-and destination using Unpremultiplied component values.
-#Example
-#Description
-    If source and destination are opaque, SkBlendMode::kModulate and
-    SkBlendMode::kMultiply produce the same results.
-##
-    auto drawSquare = [=](int dx, int dy, SkBlendMode mode, const char* label) -> void {
-        const SkColor colors[] = { SK_ColorBLACK, SK_ColorWHITE };
-        const SkPoint horz[] = { { 0, 0 }, { 128, 0 } };
-        SkPaint paint;
-        paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
-                SkShader::kClamp_TileMode));
-        paint.setBlendMode(mode);
-        canvas->translate(dx, dy);
-        canvas->drawRect({0, 0, 128, 128}, paint);
-        paint.setBlendMode(SkBlendMode::kXor);
-        canvas->drawString(label, 40, 100, paint);
-    };
-    drawSquare(0, 0, SkBlendMode::kSrc, "destination");
-    drawSquare(128, 0, SkBlendMode::kSrc, "");
-    drawSquare(0, 128, SkBlendMode::kSrc, "");
-    canvas->translate(-128, -128);
-    canvas->rotate(90, 0, 128);
-    drawSquare(0, 0, SkBlendMode::kSrc, "source");
-    drawSquare(0, -128, SkBlendMode::kModulate, "modulate");
-    drawSquare(-128, 0, SkBlendMode::kMultiply, "multiply");
-##
-##
-
-#Subtopic Screen
-#Line # multiply inverse of pixels, inverting result; brightens destination ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-SkBlendMode::kScreen replaces destination with: #Formula # [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] ##.
-
-#Example
-    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
-    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
-    SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    paint.setBlendMode(SkBlendMode::kDstATop);
-    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
-    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
-    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    canvas->clipRect( { 30, 30, 226, 226 } );
-    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kScreen);
-##
-##
-
-#Subtopic Overlay
-#Line # multiply or screen, depending on destination ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-SkBlendMode::kOverlay replaces destination with:
-#Formula # [Sa + Da - Sa * Da, Sc * (1 - Da) + Dc * (1 - Sa) +
-    (2 * Dc <= Da ? 2 * Sc * Dc : Sa * Da - 2 * (Da - Dc) * (Sa - Sc))] ##.
-#Example
-    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
-    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
-    SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    paint.setBlendMode(SkBlendMode::kDstATop);
-    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
-    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
-    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    canvas->clipRect( { 30, 30, 226, 226 } );
-    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kOverlay);
-##
-##
-
-#Subtopic Darken
-#Line # darker of source and destination ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-SkBlendMode::kDarken replaces destination with:
-#Formula # [Sa + Da - Sa * Da,  Sc + Dc - max(Sc * Da, Dc * Sa)] ##.
-SkBlendMode::kDarken does not make an image darker; it replaces the destination
-component with source if source is darker. 
-#Example
-#Image 3
-    canvas->drawImage(image, 0, 0);
-    SkColor colors[] = { SK_ColorWHITE, SK_ColorBLACK };
-    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
-    SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
-            SkShader::kClamp_TileMode));
-    paint.setBlendMode(SkBlendMode::kDarken);
-    canvas->drawPaint(paint);
-##
-##
-
-#Subtopic Lighten
-#Line # lighter of source and destination ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-SkBlendMode::kLighten replaces destination with:
-#Formula # [Sa + Da - Sa * Da,  Sc + Dc - min(Sc * Da, Dc * Sa)] ##. 
-SkBlendMode::kDarken does not make an image lighter; it replaces the destination
-component with source if source is lighter. 
-#Example
-#Image 3
-    canvas->drawImage(image, 0, 0);
-    SkColor colors[] = { SK_ColorBLACK, SK_ColorWHITE };
-    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
-    SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
-            SkShader::kClamp_TileMode));
-    paint.setBlendMode(SkBlendMode::kLighten);
-    canvas->drawPaint(paint);
-##
-##
-
-#Subtopic Color_Dodge
-#Line # brighten destination to reflect source ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-SkBlendMode::kColorDodge replaces destination with:
-#Formula # [Sa + Da - Sa * Da, Dc == 0 ? Sc * (1 - Da) : Sc == Sa ? Sc + Da * (1 - Sa) :
-            Sa * min(Da, Dc * Sa / (Sa - Sc)) + Sc * (1 - Da) + Da * (1 - Sa)] ##,
-making destination brighter to reflect source.
-#Example
-#Image 3
-    canvas->drawImage(image, 0, 0);
-    canvas->clipRect({128, 0, 256, 256});
-    canvas->drawColor(SkColorSetARGB(0x80, 0x90, 0x90, 0x90), SkBlendMode::kColorDodge);
-##
-##
-
-#Subtopic Color_Burn
-#Line # darken destination to reflect source ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-SkBlendMode::kColorBurn replaces destination with:
-#Formula # [Sa + Da - Sa * Da, Dc == Da ? Dc + Sc * (1 - Da) : Sc == 0 ? Da * (1 - Sa) :
-            Sa * (Da - min(Da, (Da - Dc) * Sa / Sc)) + Sc * (1 - Da) + Da * (1 - Sa)] ##,
-making destination darker to reflect source.
-#Example
-#Image 3
-    canvas->drawImage(image, 0, 0);
-    canvas->clipRect({128, 0, 256, 256});
-    canvas->drawColor(SkColorSetARGB(0x80, 0x90, 0x90, 0x90), SkBlendMode::kColorBurn);
-##
-##
-
-#Subtopic Hard_Light
-#Line # multiply or screen, depending on source ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-SkBlendMode::kHardLight replaces destination with:
-#Formula # [Sa + Da - Sa * Da, Sc * (1 - Da) + Dc * (1 - Sa) +
-        2 * Sc <= Sa ? 2 * Sc * Dc : Sa * Da - 2 * (Da - Dc) * (Sa - Sc)] ##,
-making destination lighter or darker, depending on source.
-#Example
-#Image 3
-   canvas->drawImage(image, 0, 0);
-   const SkColor colors[] = { 0xFFFFFFFF, 0x00000000 };
-   SkPaint paint;
-   paint.setBlendMode(SkBlendMode::kHardLight);
-   paint.setShader(SkGradientShader::MakeRadial({ 128, 128}, 100, colors,
-        nullptr, SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode));
-   canvas->clipRect({0, 128, 256, 256});
-   canvas->drawPaint(paint);
-##
-##
-
-#Subtopic Soft_Light
-#Line # lighten or darken, depending on source ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-where #Formula # m = Da > 0 ? Dc / Da : 0 ##;
-SkBlendMode::kSoftLight replaces destination with: #Formula # [Sa + Da - Sa * Da, Sc / Da + Dc / Sa +
-    (2 * Sc <= Sa ? Dc * (Sa + (2 * Sc - Sa) * (1 - m)) : Dc * Sa + Da * (2 * Sc - Sa) *
-    (4 * Dc <= Da ? (16 * m * m  + 4 * m) * (m - 1) + 7 * m : sqrt(m) - m))] ##,
-making destination lighter or darker, depending on source.
-#Example
-#Image 3
-   const SkColor colors[] = { 0xFFFFFFFF, 0x3FFFFFFF };
-   SkPaint paint;
-   paint.setBlendMode(SkBlendMode::kSoftLight);
-   paint.setShader(SkGradientShader::MakeRadial({ 128, 128}, 100, colors,
-        nullptr, SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode));
-   canvas->drawImage(image, 0, 0);
-   canvas->drawCircle(128, 128, 100, paint);
-##
-##
-
-#Subtopic Difference
-#Line # subtract darker from lighter with higher contrast ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-SkBlendMode::kDifference replaces destination with:
-#Formula # [Sa + Da - Sa * Da, Sc + Dc - 2 * min(Sc * Da, Dc * Sa)] ##,
-replacing destination with lighter less darker.
-#Example
-#Image 5
-    canvas->drawImage(image, 0, 0);
-    canvas->drawImage(image, 128, 0);
-    canvas->drawImage(image, 0, 128);
-    canvas->drawImage(image, 128, 128);
-    SkPaint paint;
-    paint.setBlendMode(SkBlendMode::kDstATop);
-    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
-    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
-    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    canvas->clipRect( { 30, 30, 226, 226 } );
-    canvas->drawColor(0x80bb9977, SkBlendMode::kDifference);
-##
-##
-
-#Subtopic Exclusion
-#Line # subtract darker from lighter with lower contrast ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-SkBlendMode::kExclusion replaces destination with:
-#Formula # [Sa + Da - Sa * Da, Sc + Dc - 2 * Sc * Dc] ##,
-replacing destination with lighter less darker, ignoring Alpha.
-#Example
-#Image 5
-    canvas->drawImage(image, 0, 0);
-    canvas->drawImage(image, 128, 0);
-    canvas->drawImage(image, 0, 128);
-    canvas->drawImage(image, 128, 128);
-    SkPaint paint;
-    paint.setBlendMode(SkBlendMode::kDstATop);
-    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
-    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
-    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    canvas->clipRect( { 30, 30, 226, 226 } );
-    canvas->drawColor(0x80bb9977, SkBlendMode::kExclusion);
-##
-##
-
-#Subtopic Multiply
-#Line # multiply source with destination, darkening image ##
-Given: #Formula # Sa ## as source Alpha, #Formula # Sc ## as source Color component,
-#Formula # Da ## as destination Alpha, #Formula # Dc ## as destination Color component;
-SkBlendMode::kMultiply replaces destination with:
-#Formula # [Sa + Da - Sa * Da, Sc * (1 - Da) + Dc * (1 - Sa) + Sc * Dc] ##,
-the product of Unpremultiplied source and destination.
-SkBlendMode::kMultiply makes the image darker.
-#Example
-#Image 5
-    canvas->drawImage(image, 0, 0);
-    canvas->drawImage(image, 128, 0);
-    canvas->drawImage(image, 0, 128);
-    canvas->drawImage(image, 128, 128);
-    SkPaint paint;
-    paint.setBlendMode(SkBlendMode::kDstATop);
-    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
-    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
-    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
-            SkShader::kClamp_TileMode));
-    canvas->drawPaint(paint);
-    canvas->clipRect( { 30, 30, 226, 226 } );
-    canvas->drawColor(0x80bb9977, SkBlendMode::kMultiply);
-##
-##
-
-#Subtopic Hue
-#Line # hue of source with saturation and luminosity of destination ##
-Given: #Formula # Sa ## as source Alpha, #Formula # S ## as source Color,
-#Formula # Da ## as destination Alpha, #Formula # D ## as destination Color;
-SkBlendMode::kHue replaces destination with:
-#Formula # [Sa + Da - Sa * Da, SetLuminosity(SetSaturation(S, Saturation(D)), Luminosity(D))] ##,
-source hue, leaving destination luminosity and saturation unchanged.
-#Example
-#Image 3
-canvas->drawImage(image, 0, 0);
-canvas->drawColor(0xFF00FF00, SkBlendMode::kHue);
-##
-##
-
-#Subtopic Saturation
-#Line # saturation of source with hue and luminosity of destination ##
-Given: #Formula # Sa ## as source Alpha, #Formula # S ## as source Color,
-#Formula # Da ## as destination Alpha, #Formula # D ## as destination Color;
-SkBlendMode::kHue replaces destination with:
-#Formula # [Sa + Da - Sa * Da, SetLuminosity(SetSaturation(D, Saturation(S)), Luminosity(D))] ##,
-source hue, leaving destination luminosity and saturation unchanged.
-#Example
-#Image 3
-canvas->drawImage(image, 0, 0);
-canvas->drawColor(0xFF00FF00, SkBlendMode::kSaturation);
-##
-##
-
-#Subtopic Color
-#Line # hue and saturation of source with luminosity of destination ##
-Given: #Formula # Sa ## as source Alpha, #Formula # S ## as source Color,
-#Formula # Da ## as destination Alpha, #Formula # D ## as destination Color;
-SkBlendMode::kColor replaces destination with:
-#Formula # [Sa + Da - Sa * Da, SetLuminosity(S, Luminosity(D))] ##,
-source hue and saturation, leaving destination luminosity unchanged.
-#Example
-#Image 3
-canvas->drawImage(image, 0, 0);
-canvas->drawColor(0xFF00FF00, SkBlendMode::kColor);
-##
-##
-
-#Subtopic Luminosity
-#Line # luminosity of source with hue and saturation of destination ##
-Given: #Formula # Sa ## as source Alpha, #Formula # S ## as source Color,
-#Formula # Da ## as destination Alpha, #Formula # D ## as destination Color;
-SkBlendMode::kLuminosity replaces destination with:
-#Formula # [Sa + Da - Sa * Da, SetLuminosity(D, Luminosity(S))] ##,
-source luminosity, leaving destination hue and saturation unchanged.
-#Example
-#Image 3
-canvas->drawImage(image, 0, 0);
-canvas->drawColor(0xFF00FF00, SkBlendMode::kLuminosity);
-##
-##
-
-#EnumClass SkBlendMode ##
-
-# ------------------------------------------------------------------------------
-
-#Method const char* SkBlendMode_Name(SkBlendMode blendMode)
-#In Utility
-#Line # returns mode as C string ##
-#Populate
-
-#Example
-SkDebugf("default blend: SkBlendMode::k%s\n", SkBlendMode_Name(SkPaint().getBlendMode()));
-#StdOut
-default blend: SkBlendMode::kSrcOver
-##
-##
-
-#SeeAlso SkBlendMode
-
-#Method ##
-
-#Topic Blend_Mode ##
diff --git a/docs/SkCanvas_Reference.bmh b/docs/SkCanvas_Reference.bmh
deleted file mode 100644
index 93fa499..0000000
--- a/docs/SkCanvas_Reference.bmh
+++ /dev/null
@@ -1,4583 +0,0 @@
-#Topic Canvas
-#Alias Canvas_Reference ##
-
-#Class SkCanvas
-
-#Code
-#Populate
-##
-
-Canvas provides an interface for drawing, and how the drawing is clipped and transformed.
-Canvas contains a stack of Matrix and Clip values.
-
-Canvas and Paint together provide the state to draw into Surface or Device.
-Each Canvas draw call transforms the geometry of the object by the concatenation of all
-Matrix values in the stack. The transformed geometry is clipped by the intersection
-of all of Clip values in the stack. The Canvas draw calls use Paint to supply drawing
-state such as Color, Typeface, text size, stroke width, Shader and so on.
-
-To draw to a pixel-based destination, create Raster_Surface or GPU_Surface.
-Request Canvas from Surface to obtain the interface to draw.
-Canvas generated by Raster_Surface draws to memory visible to the CPU.
-Canvas generated by GPU_Surface uses Vulkan or OpenGL to draw to the GPU.
-
-To draw to a document, obtain Canvas from SVG_Canvas, Document_PDF, or Picture_Recorder.
-Document based Canvas and other Canvas subclasses reference Device describing the
-destination.
-
-Canvas can be constructed to draw to Bitmap without first creating Raster_Surface.
-This approach may be deprecated in the future.
-
-Canvas may be created directly when no Surface is required; some Canvas methods
-implicitly create Raster_Surface.
-
-# ------------------------------------------------------------------------------
-
-#Method static std::unique_ptr<SkCanvas> MakeRasterDirect(const SkImageInfo& info, void* pixels,
-                                                          size_t rowBytes,
-                                                          const SkSurfaceProps* props = nullptr)
-#In Constructors
-#Line # creates from SkImageInfo and Pixel_Storage ##
-#Populate
-
-#Example
-    #Description
-        Allocates a three by three bitmap, clears it to white, and draws a black pixel
-        in the center.
-    ##
-void draw(SkCanvas* ) {
-    SkImageInfo info = SkImageInfo::MakeN32Premul(3, 3);  // device aligned, 32 bpp, Premultiplied
-    const size_t minRowBytes = info.minRowBytes();  // bytes used by one bitmap row
-    const size_t size = info.computeMinByteSize();  // bytes used by all rows
-    SkAutoTMalloc<SkPMColor> storage(size);  // allocate storage for pixels
-    SkPMColor* pixels = storage.get();  // get pointer to allocated storage
-    // create a SkCanvas backed by a raster device, and delete it when the
-    // function goes out of scope.
-    std::unique_ptr<SkCanvas> canvas = SkCanvas::MakeRasterDirect(info, pixels, minRowBytes);
-    canvas->clear(SK_ColorWHITE);  // white is Unpremultiplied, in ARGB order
-    canvas->flush();  // ensure that pixels are cleared
-    SkPMColor pmWhite = pixels[0];  // the Premultiplied format may vary
-    SkPaint paint;  // by default, draws black
-    canvas->drawPoint(1, 1, paint);  // draw in the center
-    canvas->flush();  // ensure that point was drawn
-    for (int y = 0; y < info.height(); ++y) {
-        for (int x = 0; x < info.width(); ++x) {
-            SkDebugf("%c", *pixels++ == pmWhite ? '-' : 'x');
-        }
-        SkDebugf("\n");
-    }
-}
-    #StdOut
-        ---
-        -x-
-        ---
-    ##
-##
-
-#SeeAlso MakeRasterDirectN32 SkSurface::MakeRasterDirect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static std::unique_ptr<SkCanvas> MakeRasterDirectN32(int width, int height, SkPMColor* pixels,
-                                                         size_t rowBytes)
-#In Constructors
-#Line # creates from image data and Pixel_Storage ##
-#Populate
-
-#Example
-    #Description
-        Allocates a three by three bitmap, clears it to white, and draws a black pixel
-        in the center.
-    ##
-void draw(SkCanvas* ) {
-    const int width = 3;
-    const int height = 3;
-    SkPMColor pixels[height][width];  // allocate a 3 by 3 Premultiplied bitmap on the stack
-    // create a SkCanvas backed by a raster device, and delete it when the
-    // function goes out of scope.
-    std::unique_ptr<SkCanvas> canvas = SkCanvas::MakeRasterDirectN32(
-            width,
-            height,
-            pixels[0],  // top-left of the bitmap
-            sizeof(pixels[0]));  // byte width of the each row
-    // write a Premultiplied value for white into all pixels in the bitmap
-    canvas->clear(SK_ColorWHITE);
-    SkPMColor pmWhite = pixels[0][0];  // the Premultiplied format may vary
-    SkPaint paint;  // by default, draws black
-    canvas->drawPoint(1, 1, paint);  // draw in the center
-    canvas->flush();  // ensure that pixels is ready to be read
-    for (int y = 0; y < height; ++y) {
-        for (int x = 0; x < width; ++x) {
-            SkDebugf("%c", pixels[y][x] == pmWhite ? '-' : 'x');
-        }
-        SkDebugf("\n");
-    }
-}
-    #StdOut
-        ---
-        -x-
-        ---
-    ##
-##
-
-#SeeAlso MakeRasterDirect SkSurface::MakeRasterDirect SkImageInfo::MakeN32Premul
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkCanvas()
-
-#Line # creates with no Surface, no dimensions ##
-#Populate
-
-#Example
-
-#Description
-Passes a placeholder to a function that requires one.
-##
-
-#Function
-static void check_for_rotated_ctm(const SkCanvas* canvas) {
-    const SkMatrix& matrix = canvas->getTotalMatrix();
-    SkDebugf("rect stays rect is %s\n", matrix.rectStaysRect() ? "true" : "false");
-}
-
-##
-void draw(SkCanvas* canvas) {
-    check_for_rotated_ctm(canvas);
-    canvas->rotate(30);
-    check_for_rotated_ctm(canvas);
-
-    SkCanvas defaultCanvas;
-    check_for_rotated_ctm(&defaultCanvas);
-}
-
-    #StdOut
-        rect stays rect is true
-        rect stays rect is false
-        rect stays rect is true
-    ##
-##
-
-#SeeAlso MakeRasterDirect SkRasterHandleAllocator::MakeCanvas SkSurface::getCanvas SkCreateColorSpaceXformCanvas
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkCanvas(int width, int height, const SkSurfaceProps* props = nullptr)
-
-#Line # creates with no Surface, set dimensions, Surface_Properties ##
-#Populate
-
-#Example
-    SkCanvas canvas(10, 20);  // 10 units wide, 20 units high
-    canvas.clipRect(SkRect::MakeXYWH(30, 40, 5, 10));  // clip is outside canvas' device
-    SkDebugf("canvas %s empty\n", canvas.getDeviceClipBounds().isEmpty() ? "is" : "is not");
-
-    #StdOut
-        canvas is empty
-    ##
-##
-
-#SeeAlso MakeRasterDirect SkSurfaceProps SkPixelGeometry SkCreateColorSpaceXformCanvas
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method explicit SkCanvas(const SkBitmap& bitmap)
-
-#Line # uses existing Bitmap ##
-#Populate
-
-#Example
-#Description
-The actual output depends on the installed fonts.
-##
-    SkBitmap bitmap;
-    // create a bitmap 5 wide and 11 high
-    bitmap.allocPixels(SkImageInfo::MakeN32Premul(5, 11));
-    SkCanvas canvas(bitmap);
-    canvas.clear(SK_ColorWHITE);  // white is Unpremultiplied, in ARGB order
-    SkPixmap pixmap;  // provides guaranteed access to the drawn pixels
-    if (!canvas.peekPixels(&pixmap)) {
-        SkDebugf("peekPixels should never fail.\n");
-    }
-    const SkPMColor* pixels = pixmap.addr32();  // points to top-left of bitmap
-    SkPMColor pmWhite = pixels[0];  // the Premultiplied format may vary
-    SkPaint paint;  // by default, draws black, 12 point text
-    canvas.drawString("!", 1, 10, SkFont(), paint);  // 1 char at baseline (1, 10)
-    for (int y = 0; y < bitmap.height(); ++y) {
-        for (int x = 0; x < bitmap.width(); ++x) {
-            SkDebugf("%c", *pixels++ == pmWhite ? '-' : 'x');
-        }
-        SkDebugf("\n");
-    }
-
-    #StdOut
-    -----
-    ---x-
-    ---x-
-    ---x-
-    ---x-
-    ---x-
-    ---x-
-    -----
-    ---x-
-    ---x-
-    -----
-    #StdOut ##
-##
-
-#SeeAlso MakeRasterDirect SkRasterHandleAllocator::MakeCanvas SkSurface::getCanvas SkCreateColorSpaceXformCanvas
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkCanvas(const SkBitmap& bitmap, const SkSurfaceProps& props)
-
-#Line # uses existing Bitmap and Surface_Properties ##
-#Populate
-
-#Example
-#Description
-The actual output depends on the installed fonts.
-##
-    SkBitmap bitmap;
-    // create a bitmap 5 wide and 11 high
-    bitmap.allocPixels(SkImageInfo::MakeN32Premul(5, 11));
-    SkCanvas canvas(bitmap, SkSurfaceProps(0, kUnknown_SkPixelGeometry));
-    canvas.clear(SK_ColorWHITE);  // white is Unpremultiplied, in ARGB order
-    SkPixmap pixmap;  // provides guaranteed access to the drawn pixels
-    if (!canvas.peekPixels(&pixmap)) {
-        SkDebugf("peekPixels should never fail.\n");
-    }
-    const SkPMColor* pixels = pixmap.addr32();  // points to top-left of bitmap
-    SkPMColor pmWhite = pixels[0];  // the Premultiplied format may vary
-    SkPaint paint;  // by default, draws black, 12 point text
-    canvas.drawString("!", 1, 10, SkFont(), paint);  // 1 char at baseline (1, 10)
-    for (int y = 0; y < bitmap.height(); ++y) {
-        for (int x = 0; x < bitmap.width(); ++x) {
-            SkDebugf("%c", *pixels++ == pmWhite ? '-' : 'x');
-        }
-        SkDebugf("\n");
-    }
-
-    #StdOut
-    -----
-    ---x-
-    ---x-
-    ---x-
-    ---x-
-    ---x-
-    ---x-
-    -----
-    ---x-
-    ---x-
-    -----
-    #StdOut ##
-##
-
-#SeeAlso MakeRasterDirect SkRasterHandleAllocator::MakeCanvas SkSurface::getCanvas SkCreateColorSpaceXformCanvas
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual ~SkCanvas()
-
-#Line # draws saved Layers, frees resources ##
-#Populate
-
-#Example
-#Description
-Canvas Layer draws into bitmap. saveLayerAlpha sets up an additional
-drawing surface that blends with the bitmap. When Layer goes out of
-scope, Layer destructor is called. The saved Layer is restored, drawing
-transparent letters.
-##
-void draw(SkCanvas* canvas) {
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeN32Premul(200, 200));
-    {
-        SkCanvas offscreen(bitmap);
-        SkPaint paint;
-        SkFont font(nullptr, 100);
-        offscreen.drawString("ABC", 20, 160, font, paint);
-        SkRect layerBounds = SkRect::MakeXYWH(32, 32, 192, 192);
-        offscreen.saveLayerAlpha(&layerBounds, 128);
-        offscreen.clear(SK_ColorWHITE);
-        offscreen.drawString("DEF", 20, 160, font, paint);
-    }
-    canvas->drawBitmap(bitmap, 0, 0, nullptr);
-}
-##
-
-#SeeAlso State_Stack
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Property
-#Line # metrics and attributes ##
-##
-
-#Method SkImageInfo imageInfo() const
-#In Property
-#Line # returns Image_Info for Canvas ##
-#Populate
-
-#Example
-    SkCanvas emptyCanvas;
-    SkImageInfo canvasInfo = emptyCanvas.imageInfo();
-    SkImageInfo emptyInfo;
-    SkDebugf("emptyInfo %c= canvasInfo\n", emptyInfo == canvasInfo ? '=' : '!');
-
-    #StdOut
-        emptyInfo == canvasInfo
-    ##
-##
-
-#SeeAlso SkImageInfo MakeRasterDirect makeSurface
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool getProps(SkSurfaceProps* props) const
-#In Property
-#Line # copies Surface_Properties if available ##
-#Populate
-
-#Example
-    SkBitmap bitmap;
-    SkCanvas canvas(bitmap, SkSurfaceProps(0, kRGB_V_SkPixelGeometry));
-    SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
-    SkDebugf("isRGB:%d\n", SkPixelGeometryIsRGB(surfaceProps.pixelGeometry()));
-    if (!canvas.getProps(&surfaceProps)) {
-        SkDebugf("getProps failed unexpectedly.\n");
-    }
-    SkDebugf("isRGB:%d\n", SkPixelGeometryIsRGB(surfaceProps.pixelGeometry()));
-
-    #StdOut
-        isRGB:0
-        isRGB:1
-    #StdOut ##
-##
-
-#SeeAlso SkSurfaceProps makeSurface
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Utility
-#Line # rarely called management functions ##
-##
-
-#Method void flush()
-#In Utility
-#Line # triggers execution of all pending draw operations ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso peekPixels SkSurface::flush GrContext::flush GrContext::abandonContext
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual SkISize getBaseLayerSize() const
-#In Property
-#Line # returns size of base Layer in global coordinates ##
-#Populate
-
-#Example
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeN32Premul(20, 30));
-    SkCanvas canvas(bitmap, SkSurfaceProps(0, kUnknown_SkPixelGeometry));
-    canvas.clipRect(SkRect::MakeWH(10, 40));
-    SkIRect clipDeviceBounds = canvas.getDeviceClipBounds();
-    if (clipDeviceBounds.isEmpty()) {
-        SkDebugf("Empty clip bounds is unexpected!\n");
-    }
-    SkDebugf("clip=%d,%d\n", clipDeviceBounds.width(), clipDeviceBounds.height());
-    SkISize baseLayerSize = canvas.getBaseLayerSize();
-    SkDebugf("size=%d,%d\n", baseLayerSize.width(), baseLayerSize.height());
-
-    #StdOut
-        clip=10,30
-        size=20,30
-    ##
-##
-
-#ToDo is this the same as the width and height of surface? ##
-
-#SeeAlso getDeviceClipBounds
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkSurface> makeSurface(const SkImageInfo& info, const SkSurfaceProps* props = nullptr)
-#In Constructors
-#Line # creates Surface matching SkImageInfo and SkSurfaceProps ##
-#Populate
-
-#Example
-    sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(5, 6);
-    SkCanvas* smallCanvas = surface->getCanvas();
-    SkImageInfo imageInfo = SkImageInfo::MakeN32Premul(3, 4);
-    sk_sp<SkSurface> compatible = smallCanvas->makeSurface(imageInfo);
-    SkDebugf("compatible %c= nullptr\n", compatible == nullptr ? '=' : '!');
-    SkDebugf("size = %d, %d\n", compatible->width(), compatible->height());
-
-    #StdOut
-        compatible != nullptr
-        size = 3, 4
-    ##
-##
-
-#SeeAlso SkSurface SkSurface::makeSurface SkImageInfo SkSurfaceProps
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual GrContext* getGrContext()
-#In Property
-#Line # returns GPU_Context of the GPU_Surface ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    if (canvas->getGrContext()) {
-         canvas->clear(SK_ColorRED);
-    } else {
-         canvas->clear(SK_ColorBLUE);
-    }
-}
-##
-
-#ToDo fiddle should show both CPU and GPU out ##
-
-#SeeAlso GrContext
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void* accessTopLayerPixels(SkImageInfo* info, size_t* rowBytes, SkIPoint* origin = nullptr)
-#In Utility
-#In Property
-#Line # returns writable pixel access if available ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    if (canvas->accessTopLayerPixels(nullptr, nullptr)) {
-         canvas->clear(SK_ColorRED);
-    } else {
-         canvas->clear(SK_ColorBLUE);
-    }
-}
-##
-
-#Example
-#Description
-Draws "ABC" on the device. Then draws "DEF" in Layer, and reads
-Layer to add a large dotted "DEF". Finally blends Layer with the
-device.
-
-The Layer and blended result appear on the CPU and GPU but the large dotted
-"DEF" appear only on the CPU.
-##
-void draw(SkCanvas* canvas) {
-  SkPaint paint;
-  SkFont font(nullptr, 100);
-  canvas->drawString("ABC", 20, 160, font, paint);
-  SkRect layerBounds = SkRect::MakeXYWH(32, 32, 192, 192);
-  canvas->saveLayerAlpha(&layerBounds, 128);
-  canvas->clear(SK_ColorWHITE);
-  canvas->drawString("DEF", 20, 160, font, paint);
-  SkImageInfo imageInfo;
-  size_t rowBytes;
-  SkIPoint origin;
-  uint32_t* access = (uint32_t*) canvas->accessTopLayerPixels(&imageInfo, &rowBytes, &origin);
-  if (access) {
-    int h = imageInfo.height();
-    int v = imageInfo.width();
-    int rowWords = rowBytes / sizeof(uint32_t);
-    for (int y = 0; y < h; ++y) {
-        int newY = (y - h / 2) * 2 + h / 2;
-        if (newY < 0 || newY >= h) {
-            continue;
-        }
-        for (int x = 0; x < v; ++x) {
-            int newX = (x - v / 2) * 2 + v / 2;
-            if (newX < 0 || newX >= v) {
-                continue;
-            }
-            if (access[y * rowWords + x] == SK_ColorBLACK) {
-                access[newY * rowWords + newX] = SK_ColorGRAY;
-            }
-        }
-    }
-
-  }
-  canvas->restore();
-}
-##
-
-#ToDo there are no callers of this that I can find. Deprecate? ##
-#ToDo fiddle should show both CPU and GPU out ##
-
-#SeeAlso SkImageInfo SkPixmap
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkRasterHandleAllocator::Handle accessTopRasterHandle() const
-#In Utility
-#In Property
-#Line # returns context that tracks Clip and Matrix ##
-#Populate
-
-#Example
-#Description
-#ToDo ##
-##
-#Function
-    static void DeleteCallback(void*, void* context) {
-        delete (char*) context;
-    }
-
-    class CustomAllocator : public SkRasterHandleAllocator {
-    public:
-        bool allocHandle(const SkImageInfo& info, Rec* rec) override {
-            char* context = new char[4]{'s', 'k', 'i', 'a'};
-            rec->fReleaseProc = DeleteCallback;
-            rec->fReleaseCtx = context;
-            rec->fHandle = context;
-            rec->fPixels = context;
-            rec->fRowBytes = 4;
-            return true;
-        }
-
-        void updateHandle(Handle handle, const SkMatrix& ctm, const SkIRect& clip_bounds) override {
-            // apply canvas matrix and clip to custom environment
-        }
-    };
-
-##
-    void draw(SkCanvas* canvas) {
-        const SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
-        std::unique_ptr<SkCanvas> c2 =
-                SkRasterHandleAllocator::MakeCanvas(std::unique_ptr<CustomAllocator>(
-                new CustomAllocator()), info);
-        char* context = (char*) c2->accessTopRasterHandle();
-        SkDebugf("context = %.4s\n", context);
-
-    }
-    #StdOut
-        context = skia
-    ##
-    #ToDo skstd::make_unique could not be used because def is private -- note to fix in c++14? ##
-##
-
-#SeeAlso SkRasterHandleAllocator
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Pixels
-#Line # read and write pixel values ##
-##
-
-#Method bool peekPixels(SkPixmap* pixmap)
-#In Pixels
-#Line # returns if Canvas has direct access to its pixels ##
-#Populate
-
-#Example
-    SkPixmap pixmap;
-    if (canvas->peekPixels(&pixmap)) {
-        SkDebugf("width=%d height=%d\n", pixmap.bounds().width(), pixmap.bounds().height());
-    }
-    #StdOut
-        width=256 height=256
-    ##
-##
-
-#SeeAlso readPixels SkBitmap::peekPixels SkImage::peekPixels SkSurface::peekPixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes,
-                    int srcX, int srcY)
-#In Pixels
-#Line # copies and converts rectangle of pixels from Canvas ##
-
-Copies Rect of pixels from Canvas into dstPixels. Matrix and Clip are
-ignored.
-
-Source Rect corners are (srcX, srcY) and (imageInfo().width(), imageInfo().height()).
-Destination Rect corners are (0, 0) and (dstInfo.width(), dstInfo.height()).
-Copies each readable pixel intersecting both rectangles, without scaling,
-converting to dstInfo.colorType() and dstInfo.alphaType() if required.
-
-Pixels are readable when Device is raster, or backed by a GPU.
-Pixels are not readable when SkCanvas is returned by SkDocument::beginPage,
-returned by SkPictureRecorder::beginRecording, or Canvas is the base of a utility
-class like SkDebugCanvas.
-
-The destination pixel storage must be allocated by the caller.
-
-Pixel values are converted only if Color_Type and Alpha_Type
-do not match. Only pixels within both source and destination rectangles
-are copied. dstPixels contents outside Rect intersection are unchanged.
-
-Pass negative values for srcX or srcY to offset pixels across or down destination.
-
-Does not copy, and returns false if:
-
-#List
-# Source and destination rectangles do not intersect. ##
-# Canvas pixels could not be converted to dstInfo.colorType() or dstInfo.alphaType(). ##
-# Canvas pixels are not readable; for instance, Canvas is document-based. ##
-# dstRowBytes is too small to contain one row of pixels. ##
-##
-
-#Param dstInfo  width, height, Color_Type, and Alpha_Type of dstPixels ##
-#Param dstPixels  storage for pixels; dstInfo.height() times dstRowBytes, or larger ##
-#Param dstRowBytes  size of one destination row; dstInfo.width() times pixel size, or larger ##
-#Param srcX  offset into readable pixels on x-axis; may be negative ##
-#Param srcY  offset into readable pixels on y-axis; may be negative ##
-
-#Return  true if pixels were copied ##
-
-#Example
-#Width 64
-#Height 64
-#Description
-    A black circle drawn on a blue background provides an image to copy.
-    readPixels copies one quarter of the canvas into each of the four corners.
-    The copied quarter circles overdraw the original circle.
-##
-    canvas->clear(SK_ColorBLUE);
-    SkPaint paint;
-    canvas->drawCircle(32, 32, 28, paint);
-    SkImageInfo info = SkImageInfo::Make(64, 64, kBGRA_8888_SkColorType, kPremul_SkAlphaType);
-    sk_sp<SkData> data(SkData::MakeUninitialized(info.minRowBytes() * info.height()));
-    sk_bzero(data->writable_data(), info.minRowBytes() * info.height());
-    for (int x : { 32, -32 } ) {
-        for (int y : { 32, -32 } ) {
-            canvas->readPixels(info, data->writable_data(), info.minRowBytes(), x, y);
-        }
-    }
-    sk_sp<SkImage> image = SkImage::MakeRasterData(info, data, info.minRowBytes());
-    canvas->drawImage(image, 0, 0);
-##
-
-#Example
-#Description
-    Canvas returned by Raster_Surface has Premultiplied pixel values.
-    clear() takes Unpremultiplied input with Color_Alpha equal 0x80
-    and RGB equal 0x55, 0xAA, 0xFF. RGB is multiplied by Color_Alpha
-    to generate Premultiplied value 0x802B5580. readPixels converts pixel back
-    to Unpremultiplied value 0x8056A9FF, introducing error.
-##
-    canvas->clear(0x8055aaff);
-    for (SkAlphaType alphaType : { kPremul_SkAlphaType, kUnpremul_SkAlphaType } ) {
-        uint32_t pixel = 0;
-        SkImageInfo info = SkImageInfo::Make(1, 1, kBGRA_8888_SkColorType, alphaType);
-        if (canvas->readPixels(info, &pixel, 4, 0, 0)) {
-            SkDebugf("pixel = %08x\n", pixel);
-        }
-    }
-
-    #StdOut
-        pixel = 802b5580
-        pixel = 8056a9ff
-    ##
-##
-
-#SeeAlso peekPixels writePixels drawBitmap drawImage SkBitmap::readPixels SkPixmap::readPixels SkImage::readPixels SkSurface::readPixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readPixels(const SkPixmap& pixmap, int srcX, int srcY)
-
-Copies Rect of pixels from Canvas into pixmap. Matrix and Clip are
-ignored.
-
-Source Rect corners are (srcX, srcY) and (imageInfo().width(), imageInfo().height()).
-Destination Rect corners are (0, 0) and (pixmap.width(), pixmap.height()).
-Copies each readable pixel intersecting both rectangles, without scaling,
-converting to pixmap.colorType() and pixmap.alphaType() if required.
-
-Pixels are readable when Device is raster, or backed by a GPU.
-Pixels are not readable when SkCanvas is returned by SkDocument::beginPage,
-returned by SkPictureRecorder::beginRecording, or Canvas is the base of a utility
-class like SkDebugCanvas.
-
-Caller must allocate pixel storage in pixmap if needed.
-
-Pixel values are converted only if Color_Type and Alpha_Type
-do not match. Only pixels within both source and destination Rects
-are copied. pixmap pixels contents outside Rect intersection are unchanged.
-
-Pass negative values for srcX or srcY to offset pixels across or down pixmap.
-
-Does not copy, and returns false if:
-
-#List
-# Source and destination rectangles do not intersect. ##
-# Canvas pixels could not be converted to pixmap.colorType() or pixmap.alphaType(). ##
-# Canvas pixels are not readable; for instance, Canvas is document-based. ##
-# Pixmap pixels could not be allocated. ##
-# pixmap.rowBytes() is too small to contain one row of pixels. ##
-##
-
-#Param pixmap  storage for pixels copied from Canvas ##
-#Param srcX    offset into readable pixels on x-axis; may be negative ##
-#Param srcY    offset into readable pixels on y-axis; may be negative ##
-
-#Return  true if pixels were copied ##
-
-#Example
-    #Description
-        clear() takes Unpremultiplied input with Color_Alpha equal 0x80
-        and RGB equal 0x55, 0xAA, 0xFF. RGB is multiplied by Color_Alpha
-        to generate Premultiplied value 0x802B5580.
-    ##
-    void draw(SkCanvas* canvas) {
-        canvas->clear(0x8055aaff);
-        uint32_t pixels[1] = { 0 };
-        SkPixmap pixmap(SkImageInfo::MakeN32Premul(1, 1), pixels, 4);
-        canvas->readPixels(pixmap, 0, 0);
-        SkDebugf("pixel = %08x\n", pixels[0]);
-    }
-    #StdOut
-        pixel = 802b5580
-    ##
-##
-
-#SeeAlso peekPixels writePixels drawBitmap drawImage SkBitmap::readPixels SkPixmap::readPixels SkImage::readPixels SkSurface::readPixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readPixels(const SkBitmap& bitmap, int srcX, int srcY)
-
-Copies Rect of pixels from Canvas into bitmap. Matrix and Clip are
-ignored.
-
-Source Rect corners are (srcX, srcY) and (imageInfo().width(), imageInfo().height()).
-Destination Rect corners are (0, 0) and (bitmap.width(), bitmap.height()).
-Copies each readable pixel intersecting both rectangles, without scaling,
-converting to bitmap.colorType() and bitmap.alphaType() if required.
-
-Pixels are readable when Device is raster, or backed by a GPU.
-Pixels are not readable when SkCanvas is returned by SkDocument::beginPage,
-returned by SkPictureRecorder::beginRecording, or Canvas is the base of a utility
-class like SkDebugCanvas.
-
-Caller must allocate pixel storage in bitmap if needed.
-
-Bitmap values are converted only if Color_Type and Alpha_Type
-do not match. Only pixels within both source and destination rectangles
-are copied. Bitmap pixels outside Rect intersection are unchanged.
-
-Pass negative values for srcX or srcY to offset pixels across or down bitmap.
-
-Does not copy, and returns false if:
-
-#List
-# Source and destination rectangles do not intersect. ##
-# Canvas pixels could not be converted to bitmap.colorType() or bitmap.alphaType(). ##
-# Canvas pixels are not readable; for instance, Canvas is document-based. ##
-# bitmap pixels could not be allocated. ##
-# bitmap.rowBytes() is too small to contain one row of pixels. ##
-##
-
-#Param bitmap  storage for pixels copied from Canvas ##
-#Param srcX    offset into readable pixels on x-axis; may be negative ##
-#Param srcY    offset into readable pixels on y-axis; may be negative ##
-
-#Return  true if pixels were copied ##
-
-#Example
-    #Description
-        clear() takes Unpremultiplied input with Color_Alpha equal 0x80
-        and RGB equal 0x55, 0xAA, 0xFF. RGB is multiplied by Color_Alpha
-        to generate Premultiplied value 0x802B5580.
-    ##
-void draw(SkCanvas* canvas) {
-    canvas->clear(0x8055aaff);
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeN32Premul(1, 1));
-    canvas->readPixels(bitmap, 0, 0);
-    SkDebugf("pixel = %08x\n", bitmap.getAddr32(0, 0)[0]);
-}
-    #StdOut
-        pixel = 802b5580
-    ##
-##
-
-#SeeAlso peekPixels writePixels drawBitmap drawImage SkBitmap::readPixels SkPixmap::readPixels SkImage::readPixels SkSurface::readPixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool writePixels(const SkImageInfo& info, const void* pixels, size_t rowBytes, int x, int y)
-#In Pixels
-#Line # copies and converts rectangle of pixels to Canvas ##
-Copies Rect from pixels to Canvas. Matrix and Clip are ignored.
-Source Rect corners are (0, 0) and (info.width(), info.height()).
-Destination Rect corners are (x, y) and
-(imageInfo().width(), imageInfo().height()).
-
-Copies each readable pixel intersecting both rectangles, without scaling,
-converting to imageInfo().colorType() and imageInfo().alphaType() if required.
-
-Pixels are writable when Device is raster, or backed by a GPU.
-Pixels are not writable when SkCanvas is returned by SkDocument::beginPage,
-returned by SkPictureRecorder::beginRecording, or Canvas is the base of a utility
-class like SkDebugCanvas.
-
-Pixel values are converted only if Color_Type and Alpha_Type
-do not match. Only pixels within both source and destination rectangles
-are copied. Canvas pixels outside Rect intersection are unchanged.
-
-Pass negative values for x or y to offset pixels to the left or
-above Canvas pixels.
-
-Does not copy, and returns false if:
-
-#List
-# Source and destination rectangles do not intersect. ##
-# pixels could not be converted to Canvas imageInfo().colorType() or
-  imageInfo().alphaType(). ##
-# Canvas pixels are not writable; for instance, Canvas is document-based. ##
-# rowBytes is too small to contain one row of pixels. ##
-##
-
-#Param info  width, height, Color_Type, and Alpha_Type of pixels ##
-#Param pixels  pixels to copy, of size info.height() times rowBytes, or larger ##
-#Param rowBytes  size of one row of pixels; info.width() times pixel size, or larger ##
-#Param x  offset into Canvas writable pixels on x-axis; may be negative ##
-#Param y  offset into Canvas writable pixels on y-axis; may be negative ##
-
-#Return  true if pixels were written to Canvas ##
-
-#Example
-    SkImageInfo imageInfo = SkImageInfo::MakeN32(256, 1, kPremul_SkAlphaType);
-    for (int y = 0; y < 256; ++y) {
-        uint32_t pixels[256];
-        for (int x = 0; x < 256; ++x) {
-            pixels[x] = SkColorSetARGB(x, x + y, x, x - y);
-        }
-        canvas->writePixels(imageInfo, &pixels, sizeof(pixels), 0, y);
-    }
-##
-
-#SeeAlso readPixels drawBitmap drawImage SkBitmap::writePixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool writePixels(const SkBitmap& bitmap, int x, int y)
-
-Copies Rect from pixels to Canvas. Matrix and Clip are ignored.
-Source Rect corners are (0, 0) and (bitmap.width(), bitmap.height()).
-
-Destination Rect corners are (x, y) and
-(imageInfo().width(), imageInfo().height()).
-
-Copies each readable pixel intersecting both rectangles, without scaling,
-converting to imageInfo().colorType() and imageInfo().alphaType() if required.
-
-Pixels are writable when Device is raster, or backed by a GPU.
-Pixels are not writable when SkCanvas is returned by SkDocument::beginPage,
-returned by SkPictureRecorder::beginRecording, or Canvas is the base of a utility
-class like SkDebugCanvas.
-
-Pixel values are converted only if Color_Type and Alpha_Type
-do not match. Only pixels within both source and destination rectangles
-are copied. Canvas pixels outside Rect intersection are unchanged.
-
-Pass negative values for x or y to offset pixels to the left or
-above Canvas pixels.
-
-Does not copy, and returns false if:
-
-#List
-# Source and destination rectangles do not intersect. ##
-# bitmap does not have allocated pixels. ##
-# bitmap pixels could not be converted to Canvas imageInfo().colorType() or
-  imageInfo().alphaType(). ##
-# Canvas pixels are not writable; for instance, Canvas is document based. ##
-# bitmap pixels are inaccessible; for instance, bitmap wraps a texture. ##
-##
-
-#Param bitmap  contains pixels copied to Canvas ##
-#Param x       offset into Canvas writable pixels in x; may be negative ##
-#Param y       offset into Canvas writable pixels in y; may be negative ##
-
-#Return  true if pixels were written to Canvas ##
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkImageInfo imageInfo = SkImageInfo::MakeN32Premul(2, 2);
-    SkBitmap bitmap;
-    bitmap.setInfo(imageInfo);
-    uint32_t pixels[4];
-    bitmap.setPixels(pixels);
-    for (int y = 0; y < 256; y += 2) {
-        for (int x = 0; x < 256;  x += 2) {
-            pixels[0] = SkColorSetRGB(x, y, x | y);
-            pixels[1] = SkColorSetRGB(x ^ y, y, x);
-            pixels[2] = SkColorSetRGB(x, x & y, y);
-            pixels[3] = SkColorSetRGB(~x, ~y, x);
-            canvas->writePixels(bitmap, x, y);
-        }
-    }
-}
-##
-
-#SeeAlso readPixels drawBitmap drawImage SkBitmap::writePixels
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic State_Stack
-#Line # stack of state for hierarchical drawing ##
-
-Canvas maintains a stack of state that allows hierarchical drawing, commonly used
-to implement windows and views. The initial state has an identity matrix and and
-an infinite clip. Even with a wide-open clip, drawing is constrained by the
-bounds of the Canvas Surface or Device.
-
-Canvas savable state consists of Clip and Matrix.
-Clip describes the area that may be drawn to.
-Matrix transforms the geometry.
-
-save(), saveLayer, and saveLayerAlpha
-save state and return the depth of the stack.
-
-restore(), restoreToCount, and ~SkCanvas() revert state to its value when saved.
-
-Each state on the stack intersects Clip with the previous Clip,
-and concatenates Matrix with the previous Matrix.
-The intersected Clip makes the drawing area the same or smaller;
-the concatenated Matrix may move the origin and potentially scale or rotate
-the coordinate space.
-
-Canvas does not require balancing the state stack but it is a good idea
-to do so. Calling save() without restore() will eventually cause Skia to fail;
-mismatched save() and restore() create hard to find bugs.
-
-It is not possible to use state to draw outside of the clip defined by the
-previous state.
-
-#Example
-#Description
-Draw to ever smaller clips; then restore drawing to full canvas.
-Note that the second clipRect is not permitted to enlarge Clip.
-##
-#Height 160
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    canvas->save();                             // records stack depth to restore
-    canvas->clipRect(SkRect::MakeWH(100, 100)); // constrains drawing to clip
-    canvas->clear(SK_ColorRED);                 // draws to limit of clip
-    canvas->save();                             // records stack depth to restore
-    canvas->clipRect(SkRect::MakeWH(50, 150));  // Rect below 100 is ignored
-    canvas->clear(SK_ColorBLUE);                // draws to smaller clip
-    canvas->restore();                          // enlarges clip
-    canvas->drawLine(20, 20, 150, 150, paint);  // line below 100 is not drawn
-    canvas->restore();                          // enlarges clip
-    canvas->drawLine(150, 20, 50, 120, paint);  // line below 100 is drawn
-}
-##
-
-Each Clip uses the current Matrix for its coordinates.
-
-#Example
-#Description
-While clipRect is given the same rectangle twice, Matrix makes the second
-clipRect draw at half the size of the first.
-##
-#Height 128
-void draw(SkCanvas* canvas) {
-    canvas->clipRect(SkRect::MakeWH(100, 100));
-    canvas->clear(SK_ColorRED);
-    canvas->scale(.5, .5);
-    canvas->clipRect(SkRect::MakeWH(100, 100));
-    canvas->clear(SK_ColorBLUE);
-}
-##
-
-#SeeAlso save saveLayer saveLayerAlpha restore() restoreToCount
-
-#Method int save()
-
-#In State_Stack
-#Line # saves Clip and Matrix on stack ##
-#Populate
-
-#Example
-#Description
-The black square is translated 50 pixels down and to the right.
-Restoring Canvas state removes translate() from Canvas stack;
-the red square is not translated, and is drawn at the origin.
-##
-#Height 100
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkRect rect = { 0, 0, 25, 25 };
-    canvas->drawRect(rect, paint);
-    canvas->save();
-    canvas->translate(50, 50);
-    canvas->drawRect(rect, paint);
-    canvas->restore();
-    paint.setColor(SK_ColorRED);
-    canvas->drawRect(rect, paint);
-}
-##
-
-#SeeAlso saveLayer saveLayerAlpha restore restoreToCount
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void restore()
-
-#In State_Stack
-#Line # restores changes to Clip and Matrix, pops save stack ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkCanvas simple;
-    SkDebugf("depth = %d\n", simple.getSaveCount());
-    simple.restore();
-    SkDebugf("depth = %d\n", simple.getSaveCount());
-}
-##
-
-#SeeAlso save saveLayer saveLayerAlpha restoreToCount
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int getSaveCount() const
-
-#In State_Stack
-#Line # returns depth of stack containing Clip and Matrix ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkCanvas simple;
-    SkDebugf("depth = %d\n", simple.getSaveCount());
-    simple.save();
-    SkDebugf("depth = %d\n", simple.getSaveCount());
-    simple.restore();
-    SkDebugf("depth = %d\n", simple.getSaveCount());
-}
-#StdOut
-depth = 1
-depth = 2
-depth = 1
-##
-##
-
-#SeeAlso save restore restoreToCount
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void restoreToCount(int saveCount)
-
-#In State_Stack
-#Line # restores changes to Clip and Matrix to given depth ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkDebugf("depth = %d\n", canvas->getSaveCount());
-    canvas->save();
-    canvas->save();
-    SkDebugf("depth = %d\n", canvas->getSaveCount());
-    canvas->restoreToCount(0);
-    SkDebugf("depth = %d\n", canvas->getSaveCount());
-}
-#StdOut
-depth = 1
-depth = 3
-depth = 1
-##
-##
-
-#SeeAlso restore getSaveCount save
-
-##
-
-#Subtopic State_Stack ##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Layer
-#Substitute layer
-#Alias Layers
-#Substitute layers
-##
-#Line # temporary Bitmap to draw into ##
-
-Layer allocates a temporary Bitmap to draw into. When the drawing is
-complete, the Bitmap is drawn into the Canvas.
-
-Layer is saved in a stack along with other saved state. When state with a Layer
-is restored, the Bitmap is drawn into the previous Layer.
-
-Layer may be initialized with the contents of the previous Layer. When Layer is
-restored, its Bitmap can be modified by Paint passed to Layer to apply
-Color_Alpha, Color_Filter, Image_Filter, and Blend_Mode.
-
-#Method int saveLayer(const SkRect* bounds, const SkPaint* paint)
-
-#In Layer
-#Line # saves Clip and Matrix on stack; creates Layer ##
-#Populate
-
-#Example
-#Description
-Rectangles are blurred by Image_Filter when restore() draws Layer to main
-Canvas.
-##
-#Height 128
-#Function
-###$
-#include "SkBlurImageFilter.h"
-$$$#
-##
-
-void draw(SkCanvas* canvas) {
-    SkPaint paint, blur;
-    blur.setImageFilter(SkBlurImageFilter::Make(3, 3, nullptr));
-    canvas->saveLayer(nullptr, &blur);
-    SkRect rect = { 25, 25, 50, 50};
-    canvas->drawRect(rect, paint);
-    canvas->translate(50, 50);
-    paint.setColor(SK_ColorRED);
-    canvas->drawRect(rect, paint);
-    canvas->restore();
-}
-##
-
-#SeeAlso save restore saveLayer saveLayerAlpha SaveLayerRec
-
-##
-
-#Method int saveLayer(const SkRect& bounds, const SkPaint* paint)
-
-#In Layer
-#Populate
-
-#Example
-#Description
-Rectangles are blurred by Image_Filter when restore() draws Layer to main Canvas.
-The red rectangle is clipped; it does not fully fit on Layer.
-Image_Filter blurs past edge of Layer so red rectangle is blurred on all sides.
-##
-#Height 128
-#Function
-###$
-#include "SkBlurImageFilter.h"
-$$$#
-##
-
-void draw(SkCanvas* canvas) {
-    SkPaint paint, blur;
-    blur.setImageFilter(SkBlurImageFilter::Make(3, 3, nullptr));
-    canvas->saveLayer(SkRect::MakeWH(90, 90), &blur);
-    SkRect rect = { 25, 25, 50, 50};
-    canvas->drawRect(rect, paint);
-    canvas->translate(50, 50);
-    paint.setColor(SK_ColorRED);
-    canvas->drawRect(rect, paint);
-    canvas->restore();
-}
-##
-
-#SeeAlso save restore saveLayerAlpha SaveLayerRec
-
-##
-
-#Method int saveLayerAlpha(const SkRect* bounds, U8CPU alpha)
-
-#In Layer
-#Line # saves Clip and Matrix on stack; creates Layer; sets opacity ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setColor(SK_ColorRED);
-    canvas->drawCircle(50, 50, 50, paint);
-    canvas->saveLayerAlpha(nullptr, 128);
-    paint.setColor(SK_ColorBLUE);
-    canvas->drawCircle(100, 50, 50, paint);
-    paint.setColor(SK_ColorGREEN);
-    paint.setAlpha(128);
-    canvas->drawCircle(75, 90, 50, paint);
-    canvas->restore();
-##
-
-#SeeAlso save restore saveLayer SaveLayerRec
-
-##
-
-#Enum SaveLayerFlagsSet
-#Line # sets SaveLayerRec options ##
-#Code
-#Populate
-##
-
-
-#Typedef uint32_t SaveLayerFlags
-#Line # options for SaveLayerRec ##
-##
-
-SaveLayerFlags provides options that may be used in any combination in SaveLayerRec,
-defining how Layer allocated by saveLayer operates. It may be set to zero or
-kInitWithPrevious_SaveLayerFlag.
-
-#Const kInitWithPrevious_SaveLayerFlag 4
-#Line # initializes with previous contents ##
-  Initializes Layer with the contents of the previous Layer.
-##
-
-#Example
-#Height 160
-#Description
-Canvas Layer captures red and blue circles scaled up by four.
-scalePaint blends Layer back with transparency.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint redPaint, bluePaint, scalePaint;
-    redPaint.setColor(SK_ColorRED);
-    canvas->drawCircle(21, 21, 8, redPaint);
-    bluePaint.setColor(SK_ColorBLUE);
-    canvas->drawCircle(31, 21, 8, bluePaint);
-    SkMatrix matrix;
-    matrix.setScale(4, 4);
-    scalePaint.setAlpha(0x40);
-    scalePaint.setImageFilter(
-            SkImageFilter::MakeMatrixFilter(matrix, kNone_SkFilterQuality, nullptr));
-    SkCanvas::SaveLayerRec saveLayerRec(nullptr, &scalePaint,
-            SkCanvas::kInitWithPrevious_SaveLayerFlag);
-    canvas->saveLayer(saveLayerRec);
-    canvas->restore();
-}
-##
-
-#SeeAlso save restore saveLayer saveLayerAlpha SaveLayerRec
-
-#Enum ##
-
-#Subtopic SaveLayerRec
-#Line # contains the state used to create the Layer ##
-
-#Struct SaveLayerRec
-#Line # contains the state used to create the Layer ##
-
-#Code
-#Populate
-##
-
-SaveLayerRec contains the state used to create the Layer.
-
-#Member const SkRect*           fBounds
-#Line # hints at Layer size limit ##
-    fBounds is used as a hint to limit the size of Layer; may be nullptr.
-    fBounds suggests but does not define Layer size. To clip drawing to
-    a specific rectangle, use clipRect.
-##
-
-#Member const SkPaint*          fPaint
-#Line # modifies overlay ##
-    fPaint modifies how Layer overlays the prior Layer; may be nullptr.
-    Color_Alpha, Blend_Mode, Color_Filter, Draw_Looper, Image_Filter, and
-    Mask_Filter affect Layer draw.
-##
-
-#Member const SkImageFilter*    fBackdrop
-#Line # applies Image_Filter to prior Layer ##
-    fBackdrop applies Image_Filter to the prior Layer when copying to the Layer;
-    may be nullptr. Use kInitWithPrevious_SaveLayerFlag to copy the
-    prior Layer without an Image_Filter.
-##
-
-#Member const SkImage*          fClipMask
-#Line # clips Layer with Mask_Alpha ##
-  restore() clips Layer by the Color_Alpha channel of fClipMask when
-  Layer is copied to Device. fClipMask may be nullptr.    .
-##
-
-#Member const SkMatrix*         fClipMatrix
-#Line # transforms Mask_Alpha used to clip ##
-  fClipMatrix transforms fClipMask before it clips Layer. If
-  fClipMask describes a translucent gradient, it may be scaled and rotated
-  without introducing artifacts. fClipMatrix may be nullptr.
-##
-
-#Member SaveLayerFlags          fSaveLayerFlags
-#Line # creates with prior Layer contents ##
-    fSaveLayerFlags are used to create Layer without transparency,
-    and to create Layer with the ontents of the previous Layer.
-##
-
-#Example
-#Height 160
-#Description
-Canvas Layer captures a red Anti_Aliased circle and a blue Aliased circle scaled
-up by four. After drawing another red circle without scaling on top, the Layer is
-transferred to the main canvas.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint redPaint, bluePaint;
-    redPaint.setAntiAlias(true);
-    redPaint.setColor(SK_ColorRED);
-    canvas->drawCircle(21, 21, 8, redPaint);
-    bluePaint.setColor(SK_ColorBLUE);
-    canvas->drawCircle(31, 21, 8, bluePaint);
-    SkMatrix matrix;
-    matrix.setScale(4, 4);
-    auto scaler = SkImageFilter::MakeMatrixFilter(matrix, kNone_SkFilterQuality, nullptr);
-    SkCanvas::SaveLayerRec saveLayerRec(nullptr, nullptr, scaler.get(), 0);
-    canvas->saveLayer(saveLayerRec);
-    canvas->drawCircle(125, 85, 8, redPaint);
-    canvas->restore();
-}
-##
-
-#Subtopic Constructors
-##
-
-#Method SaveLayerRec()
-#Line # constructs SaveLayerRec ##
-#Populate
-
-#Example
-    SkCanvas::SaveLayerRec rec1;
-    rec1.fSaveLayerFlags = SkCanvas::kInitWithPrevious_SaveLayerFlag;
-    SkCanvas::SaveLayerRec rec2(nullptr, nullptr, SkCanvas::kInitWithPrevious_SaveLayerFlag);
-    SkDebugf("rec1 %c= rec2\n", rec1.fBounds == rec2.fBounds
-            && rec1.fPaint == rec2.fPaint
-            && rec1.fBackdrop == rec2.fBackdrop
-            && rec1.fSaveLayerFlags == rec2.fSaveLayerFlags ? '=' : '!');
-    #StdOut
-        rec1 == rec2
-    ##
-##
-
-#SeeAlso save restore saveLayer saveLayerAlpha
-
-##
-
-#Method SaveLayerRec(const SkRect* bounds, const SkPaint* paint, SaveLayerFlags saveLayerFlags = 0)
-#Populate
-
-#Example
-    SkCanvas::SaveLayerRec rec1;
-    SkCanvas::SaveLayerRec rec2(nullptr, nullptr);
-    SkDebugf("rec1 %c= rec2\n", rec1.fBounds == rec2.fBounds
-            && rec1.fPaint == rec2.fPaint
-            && rec1.fBackdrop == rec2.fBackdrop
-            && rec1.fSaveLayerFlags == rec2.fSaveLayerFlags ? '=' : '!');
-    #StdOut
-        rec1 == rec2
-    ##
-##
-
-#SeeAlso save restore saveLayer saveLayerAlpha
-
-##
-
-#Method SaveLayerRec(const SkRect* bounds, const SkPaint* paint, const SkImageFilter* backdrop,
-                     SaveLayerFlags saveLayerFlags)
-#Populate
-
-#Example
-    SkCanvas::SaveLayerRec rec1;
-    SkCanvas::SaveLayerRec rec2(nullptr, nullptr, nullptr, 0);
-    SkDebugf("rec1 %c= rec2\n", rec1.fBounds == rec2.fBounds
-            && rec1.fPaint == rec2.fPaint
-            && rec1.fBackdrop == rec2.fBackdrop
-            && rec1.fSaveLayerFlags == rec2.fSaveLayerFlags ? '=' : '!');
-    #StdOut
-        rec1 == rec2
-    ##
-##
-
-#SeeAlso save restore saveLayer saveLayerAlpha
-
-##
-
-#Struct ##
-
-#Subtopic ##
-
-#Method int saveLayer(const SaveLayerRec& layerRec)
-
-#In Layer
-#Populate
-
-#Example
-#Description
-The example draws an image, and saves it into a Layer with kInitWithPrevious_SaveLayerFlag.
-Next it punches a hole in Layer and restore with SkBlendMode::kPlus.
-Where Layer was cleared, the original image will draw unchanged.
-Outside of the circle the mandrill is brightened.
-##
-    #Image 3
-    // sk_sp<SkImage> image = GetResourceAsImage("images/mandrill_256.png");
-    canvas->drawImage(image, 0, 0, nullptr);
-    SkCanvas::SaveLayerRec rec;
-    SkPaint paint;
-    paint.setBlendMode(SkBlendMode::kPlus);
-    rec.fSaveLayerFlags = SkCanvas::kInitWithPrevious_SaveLayerFlag;
-    rec.fPaint = &paint;
-    canvas->saveLayer(rec);
-    paint.setBlendMode(SkBlendMode::kClear);
-    canvas->drawCircle(128, 128, 96, paint);
-    canvas->restore();
-##
-
-#ToDo above example needs to replace GetResourceAsImage with way to select image in fiddle ##
-
-#SeeAlso save restore saveLayer saveLayerAlpha
-
-##
-
-#Subtopic Layer ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Matrix
-#Line # coordinate transformation ##
-
-#Method void translate(SkScalar dx, SkScalar dy)
-
-#In Matrix
-#Line # translates Matrix ##
-#Populate
-
-#Example
-#Height 128
-#Description
-scale() followed by translate() produces different results from translate() followed
-by scale().
-
-The blue stroke follows translate of (50, 50); a black
-fill follows scale of (2, 1/2.f). After restoring the clip, which resets
-Matrix, a red frame follows the same scale of (2, 1/2.f); a gray fill
-follows translate of (50, 50).
-##
-void draw(SkCanvas* canvas) {
-    SkPaint filledPaint;
-    SkPaint outlinePaint;
-    outlinePaint.setStyle(SkPaint::kStroke_Style);
-    outlinePaint.setColor(SK_ColorBLUE);
-    canvas->save();
-    canvas->translate(50, 50);
-    canvas->drawCircle(28, 28, 15, outlinePaint);  // blue center: (50+28, 50+28)
-    canvas->scale(2, 1/2.f);
-    canvas->drawCircle(28, 28, 15, filledPaint);   // black center: (50+(28*2), 50+(28/2))
-    canvas->restore();
-    filledPaint.setColor(SK_ColorGRAY);
-    outlinePaint.setColor(SK_ColorRED);
-    canvas->scale(2, 1/2.f);
-    canvas->drawCircle(28, 28, 15, outlinePaint);  // red center: (28*2, 28/2)
-    canvas->translate(50, 50);
-    canvas->drawCircle(28, 28, 15, filledPaint);   // gray center: ((50+28)*2, (50+28)/2)
-}
-##
-
-#SeeAlso concat() scale() skew() rotate() setMatrix
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void scale(SkScalar sx, SkScalar sy)
-
-#In Matrix
-#Line # scales Matrix ##
-#Populate
-
-#Example
-#Height 160
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkRect rect = { 10, 20, 60, 120 };
-    canvas->translate(20, 20);
-    canvas->drawRect(rect, paint);
-    canvas->scale(2, .5f);
-    paint.setColor(SK_ColorGRAY);
-    canvas->drawRect(rect, paint);
-}
-##
-
-#SeeAlso concat() translate() skew() rotate() setMatrix
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void rotate(SkScalar degrees)
-
-#In Matrix
-#Line # rotates Matrix ##
-#Populate
-
-#Example
-#Description
-Draw clock hands at time 5:10. The hour hand and minute hand point up and
-are rotated clockwise.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->translate(128, 128);
-    canvas->drawCircle(0, 0, 60, paint);
-    canvas->save();
-    canvas->rotate(10 * 360 / 60);   // 10 minutes of 60 scaled to 360 degrees
-    canvas->drawLine(0, 0, 0, -50, paint);
-    canvas->restore();
-    canvas->rotate((5 + 10.f/60) * 360 / 12); // 5 and 10/60 hours of 12 scaled to 360 degrees
-    canvas->drawLine(0, 0, 0, -30, paint);
-}
-##
-
-#SeeAlso concat() translate() skew() scale() setMatrix
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void rotate(SkScalar degrees, SkScalar px, SkScalar py)
-
-#In Matrix
-#Populate
-
-#Example
-#Height 192
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkFont font(nullptr, 96);
-    canvas->drawString("A1", 130, 100, font, paint);
-    canvas->rotate(180, 130, 100);
-    canvas->drawString("A1", 130, 100, font, paint);
-}
-##
-
-#SeeAlso concat() translate() skew() scale() setMatrix
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void skew(SkScalar sx, SkScalar sy)
-
-#In Matrix
-#Line # skews Matrix ##
-#Populate
-
-#Example
-    #Description
-        Black text mimics an oblique text style by using a negative skew on x-axis
-        that shifts the geometry to the right as the y-axis values decrease.
-        Red text uses a positive skew on y-axis to shift the geometry down
-        as the x-axis values increase.
-        Blue text combines sx and sy skew to rotate and scale.
-    ##
-    SkPaint paint;
-    SkFont font(nullptr, 128);
-    canvas->translate(30, 130);
-    canvas->save();
-    canvas->skew(-.5, 0);
-    canvas->drawString("A1", 0, 0, font, paint);
-    canvas->restore();
-    canvas->save();
-    canvas->skew(0, .5);
-    paint.setColor(SK_ColorRED);
-    canvas->drawString("A1", 0, 0, font, paint);
-    canvas->restore();
-    canvas->skew(-.5, .5);
-    paint.setColor(SK_ColorBLUE);
-    canvas->drawString("A1", 0, 0, font, paint);
-##
-
-#SeeAlso concat() translate() rotate() scale() setMatrix
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void concat(const SkMatrix& matrix)
-
-#In Matrix
-#Line # multiplies Matrix by Matrix ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkFont font(nullptr, 80);
-    font.setScaleX(.3);
-    SkMatrix matrix;
-    SkRect rect[2] = {{ 10, 20, 90, 110 }, { 40, 130, 140, 180 }};
-    matrix.setRectToRect(rect[0], rect[1], SkMatrix::kFill_ScaleToFit);
-    canvas->drawRect(rect[0], paint);
-    canvas->drawRect(rect[1], paint);
-    paint.setColor(SK_ColorWHITE);
-    canvas->drawString("Here", rect[0].fLeft + 10, rect[0].fBottom - 10, font, paint);
-    canvas->concat(matrix);
-    canvas->drawString("There", rect[0].fLeft + 10, rect[0].fBottom - 10, font, paint);
-}
-##
-
-#SeeAlso translate() rotate() scale() skew() setMatrix
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void setMatrix(const SkMatrix& matrix)
-
-#In Matrix
-#Line # sets Matrix ##
-#Populate
-
-#Example
-#Height 128
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkFont font;
-    canvas->scale(4, 6);
-    canvas->drawString("truth", 2, 10, font, paint);
-    SkMatrix matrix;
-    matrix.setScale(2.8f, 6);
-    canvas->setMatrix(matrix);
-    canvas->drawString("consequences", 2, 20, font, paint);
-}
-##
-
-#SeeAlso resetMatrix concat() translate() rotate() scale() skew()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void resetMatrix()
-
-#In Matrix
-#Line # resets Matrix to identity ##
-#Populate
-
-#Example
-#Height 128
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkFont font;
-    canvas->scale(4, 6);
-    canvas->drawString("truth", 2, 10, font, paint);
-    canvas->resetMatrix();
-    canvas->scale(2.8f, 6);
-    canvas->drawString("consequences", 2, 20, font, paint);
-}
-##
-
-#SeeAlso setMatrix concat() translate() rotate() scale() skew()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method const SkMatrix& getTotalMatrix() const
-
-#In Matrix
-#Line # returns Matrix ##
-#Populate
-
-#Example
-    SkDebugf("isIdentity %s\n", canvas->getTotalMatrix().isIdentity() ? "true" : "false");
-    #StdOut
-        isIdentity true
-    ##
-##
-
-#SeeAlso setMatrix resetMatrix concat()
-
-##
-
-#Subtopic Matrix ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Clip
-#Line # stack of clipping Paths ##
-
-Clip is built from a stack of clipping paths. Each Path in the
-stack can be constructed from one or more Path_Contour elements. The
-Path_Contour may be composed of any number of Path_Verb segments. Each
-Path_Contour forms a closed area; Path_Fill_Type defines the area enclosed
-by Path_Contour.
-
-Clip stack of Path elements successfully restrict the Path area. Each
-Path is transformed by Matrix, then intersected with or subtracted from the
-prior Clip to form the replacement Clip. Use SkClipOp::kDifference
-to subtract Path from Clip; use SkClipOp::kIntersect to intersect Path
-with Clip.
-
-A clipping Path may be Anti_Aliased; if Path, after transformation, is
-composed of horizontal and vertical lines, clearing Anti_Alias allows whole pixels
-to either be inside or outside the clip. The fastest drawing has a Aliased,
-rectangular clip.
-
-If clipping Path has Anti_Alias set, clip may partially clip a pixel, requiring
-that drawing blend partially with the destination along the edge. A rotated
-rectangular Anti_Aliased clip looks smoother but draws slower.
-
-Clip can combine with Rect and Round_Rect primitives; like
-Path, these are transformed by Matrix before they are combined with Clip.
-
-Clip can combine with Region. Region is assumed to be in Device coordinates
-and is unaffected by Matrix.
-
-#Example
-#Height 90
-    #Description
-        Draw a red circle with an Aliased clip and an Anti_Aliased clip.
-        Use an image filter to zoom into the pixels drawn.
-        The edge of the Aliased clip fully draws pixels in the red circle.
-        The edge of the Anti_Aliased clip partially draws pixels in the red circle.
-    ##
-    SkPaint redPaint, scalePaint;
-    redPaint.setAntiAlias(true);
-    redPaint.setColor(SK_ColorRED);
-    canvas->save();
-    for (bool antialias : { false, true } ) {
-        canvas->save();
-        canvas->clipRect(SkRect::MakeWH(19.5f, 11.5f), antialias);
-        canvas->drawCircle(17, 11, 8, redPaint);
-        canvas->restore();
-        canvas->translate(16, 0);
-    }
-    canvas->restore();
-    SkMatrix matrix;
-    matrix.setScale(6, 6);
-    scalePaint.setImageFilter(
-            SkImageFilter::MakeMatrixFilter(matrix, kNone_SkFilterQuality, nullptr));
-    SkCanvas::SaveLayerRec saveLayerRec(
-            nullptr, &scalePaint, SkCanvas::kInitWithPrevious_SaveLayerFlag);
-    canvas->saveLayer(saveLayerRec);
-    canvas->restore();
-##
-
-#Method void clipRect(const SkRect& rect, SkClipOp op, bool doAntiAlias)
-
-#In Clip
-#Line # combines Clip with Rect ##
-#Populate
-
-#Example
-#Height 128
-void draw(SkCanvas* canvas) {
-    canvas->rotate(10);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    for (auto alias: { false, true } ) {
-        canvas->save();
-        canvas->clipRect(SkRect::MakeWH(90, 80), SkClipOp::kIntersect, alias);
-        canvas->drawCircle(100, 60, 60, paint);
-        canvas->restore();
-        canvas->translate(80, 0);
-    }
-}
-##
-
-#SeeAlso clipRRect clipPath clipRegion
-
-##
-
-#Method void clipRect(const SkRect& rect, SkClipOp op)
-
-#In Clip
-#Populate
-
-#Example
-#Height 192
-#Width 280
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    for (SkClipOp op: { SkClipOp::kIntersect, SkClipOp::kDifference } ) {
-        canvas->save();
-        canvas->clipRect(SkRect::MakeWH(90, 120), op, false);
-        canvas->drawCircle(100, 100, 60, paint);
-        canvas->restore();
-        canvas->translate(80, 0);
-    }
-}
-##
-
-#SeeAlso clipRRect clipPath clipRegion
-
-##
-
-#Method void clipRect(const SkRect& rect, bool doAntiAlias = false)
-
-#In Clip
-#Populate
-
-#Example
-#Height 133
-    #Description
-        A circle drawn in pieces looks uniform when drawn Aliased.
-        The same circle pieces blend with pixels more than once when Anti_Aliased,
-        visible as a thin pair of lines through the right circle.
-    ##
-void draw(SkCanvas* canvas) {
-    canvas->clear(SK_ColorWHITE);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setColor(0x8055aaff);
-    SkRect clipRect = { 0, 0, 87.4f, 87.4f };
-    for (auto alias: { false, true } ) {
-        canvas->save();
-        canvas->clipRect(clipRect, SkClipOp::kIntersect, alias);
-        canvas->drawCircle(67, 67, 60, paint);
-        canvas->restore();
-        canvas->save();
-        canvas->clipRect(clipRect, SkClipOp::kDifference, alias);
-        canvas->drawCircle(67, 67, 60, paint);
-        canvas->restore();
-        canvas->translate(120, 0);
-    }
-}
-##
-
-#SeeAlso clipRRect clipPath clipRegion
-
-##
-
-#Method void clipRRect(const SkRRect& rrect, SkClipOp op, bool doAntiAlias)
-
-#In Clip
-#Line # combines Clip with Round_Rect ##
-#Populate
-
-#Example
-#Height 128
-void draw(SkCanvas* canvas) {
-    canvas->clear(SK_ColorWHITE);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setColor(0x8055aaff);
-    SkRRect oval;
-    oval.setOval({10, 20, 90, 100});
-    canvas->clipRRect(oval, SkClipOp::kIntersect, true);
-    canvas->drawCircle(70, 100, 60, paint);
-}
-##
-
-#SeeAlso clipRect clipPath clipRegion
-
-##
-
-#Method void clipRRect(const SkRRect& rrect, SkClipOp op)
-
-#In Clip
-#Populate
-
-#Example
-#Height 128
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setColor(0x8055aaff);
-    auto oval = SkRRect::MakeOval({10, 20, 90, 100});
-    canvas->clipRRect(oval, SkClipOp::kIntersect);
-    canvas->drawCircle(70, 100, 60, paint);
-}
-##
-
-#SeeAlso clipRect clipPath clipRegion
-
-##
-
-#Method void clipRRect(const SkRRect& rrect, bool doAntiAlias = false)
-
-#In Clip
-#Populate
-
-#Example
-#Height 128
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    auto oval = SkRRect::MakeRectXY({10, 20, 90, 100}, 9, 13);
-    canvas->clipRRect(oval, true);
-    canvas->drawCircle(70, 100, 60, paint);
-}
-##
-
-#SeeAlso clipRect clipPath clipRegion
-
-##
-
-#Method void clipPath(const SkPath& path, SkClipOp op, bool doAntiAlias)
-
-#In Clip
-#Line # combines Clip with Path ##
-#Populate
-
-#Example
-#Description
-Top figure uses SkPath::kInverseWinding_FillType and SkClipOp::kDifference;
-area outside clip is subtracted from circle.
-
-Bottom figure uses SkPath::kWinding_FillType and SkClipOp::kIntersect;
-area inside clip is intersected with circle.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkPath path;
-    path.addRect({20, 30, 100, 110});
-    path.setFillType(SkPath::kInverseWinding_FillType);
-    canvas->save();
-    canvas->clipPath(path, SkClipOp::kDifference, false);
-    canvas->drawCircle(70, 100, 60, paint);
-    canvas->restore();
-    canvas->translate(100, 100);
-    path.setFillType(SkPath::kWinding_FillType);
-    canvas->clipPath(path, SkClipOp::kIntersect, false);
-    canvas->drawCircle(70, 100, 60, paint);
-}
-##
-
-#SeeAlso clipRect clipRRect clipRegion
-
-##
-
-#Method void clipPath(const SkPath& path, SkClipOp op)
-
-#In Clip
-#Populate
-
-#Example
-#Description
-Overlapping Rects form a clip. When clip Path_Fill_Type is set to
-SkPath::kWinding_FillType, the overlap is included. Set to
-SkPath::kEvenOdd_FillType, the overlap is excluded and forms a hole.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkPath path;
-    path.addRect({20, 15, 100, 95});
-    path.addRect({50, 65, 130, 135});
-    path.setFillType(SkPath::kWinding_FillType);
-    canvas->save();
-    canvas->clipPath(path, SkClipOp::kIntersect);
-    canvas->drawCircle(70, 85, 60, paint);
-    canvas->restore();
-    canvas->translate(100, 100);
-    path.setFillType(SkPath::kEvenOdd_FillType);
-    canvas->clipPath(path, SkClipOp::kIntersect);
-    canvas->drawCircle(70, 85, 60, paint);
-}
-##
-
-#SeeAlso clipRect clipRRect clipRegion
-
-##
-
-#Method void clipPath(const SkPath& path, bool doAntiAlias = false)
-
-#In Clip
-#Populate
-
-#Example
-#Height 212
-#Description
-Clip loops over itself covering its center twice. When clip Path_Fill_Type
-is set to SkPath::kWinding_FillType, the overlap is included. Set to
-SkPath::kEvenOdd_FillType, the overlap is excluded and forms a hole.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkPath path;
-    SkPoint poly[] = {{20, 20}, { 80, 20}, { 80,  80}, {40,  80},
-                      {40, 40}, {100, 40}, {100, 100}, {20, 100}};
-    path.addPoly(poly, SK_ARRAY_COUNT(poly), true);
-    path.setFillType(SkPath::kWinding_FillType);
-    canvas->save();
-    canvas->clipPath(path, SkClipOp::kIntersect);
-    canvas->drawCircle(50, 50, 45, paint);
-    canvas->restore();
-    canvas->translate(100, 100);
-    path.setFillType(SkPath::kEvenOdd_FillType);
-    canvas->clipPath(path, SkClipOp::kIntersect);
-    canvas->drawCircle(50, 50, 45, paint);
-}
-##
-
-#SeeAlso clipRect clipRRect clipRegion
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void clipRegion(const SkRegion& deviceRgn, SkClipOp op = SkClipOp::kIntersect)
-
-#In Clip
-#Line # combines Clip with Region ##
-#Populate
-
-#Example
-#Description
-    region is unaffected by canvas rotation; iRect is affected by canvas rotation.
-    Both clips are Aliased; this is not noticeable on Region clip because it
-    aligns to pixel boundaries.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkIRect iRect = {30, 40, 120, 130 };
-    SkRegion region(iRect);
-    canvas->rotate(10);
-    canvas->save();
-    canvas->clipRegion(region, SkClipOp::kIntersect);
-    canvas->drawCircle(50, 50, 45, paint);
-    canvas->restore();
-    canvas->translate(100, 100);
-    canvas->clipRect(SkRect::Make(iRect), SkClipOp::kIntersect);
-    canvas->drawCircle(50, 50, 45, paint);
-}
-##
-
-#SeeAlso clipRect clipRRect clipPath
-
-##
-
-#Method bool quickReject(const SkRect& rect) const
-
-#In Clip
-#Line # returns if Rect is outside Clip ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkRect testRect = {30, 30, 120, 129 };
-    SkRect clipRect = {30, 130, 120, 230 };
-    canvas->save();
-    canvas->clipRect(clipRect);
-    SkDebugf("quickReject %s\n", canvas->quickReject(testRect) ? "true" : "false");
-    canvas->restore();
-    canvas->rotate(10);
-    canvas->clipRect(clipRect);
-    SkDebugf("quickReject %s\n", canvas->quickReject(testRect) ? "true" : "false");
-}
-    #StdOut
-        quickReject true
-        quickReject false
-    ##
-##
-
-#SeeAlso getLocalClipBounds getTotalMatrix SkBitmap::drawsNothing
-
-##
-
-#Method bool quickReject(const SkPath& path) const
-
-#In Clip
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPoint testPoints[] = {{30,  30}, {120,  30}, {120, 129} };
-    SkPoint clipPoints[] = {{30, 130}, {120, 130}, {120, 230} };
-    SkPath testPath, clipPath;
-    testPath.addPoly(testPoints, SK_ARRAY_COUNT(testPoints), true);
-    clipPath.addPoly(clipPoints, SK_ARRAY_COUNT(clipPoints), true);
-    canvas->save();
-    canvas->clipPath(clipPath);
-    SkDebugf("quickReject %s\n", canvas->quickReject(testPath) ? "true" : "false");
-    canvas->restore();
-    canvas->rotate(10);
-    canvas->clipPath(clipPath);
-    SkDebugf("quickReject %s\n", canvas->quickReject(testPath) ? "true" : "false");
-    #StdOut
-        quickReject true
-        quickReject false
-    ##
-}
-##
-
-#SeeAlso getLocalClipBounds getTotalMatrix SkBitmap::drawsNothing
-
-##
-
-#Method SkRect getLocalClipBounds() const
-
-#In Clip
-#Line # returns Clip bounds in source coordinates ##
-#Populate
-
-#Example
-    #Description
-        Initial bounds is device bounds outset by 1 on all sides.
-        Clipped bounds is clipPath bounds outset by 1 on all sides.
-        Scaling the canvas by two on both axes scales the local bounds by 1/2
-        on both axes.
-    ##
-    SkCanvas local(256, 256);
-    canvas = &local;
-    SkRect bounds = canvas->getLocalClipBounds();
-    SkDebugf("left:%g  top:%g  right:%g  bottom:%g\n",
-            bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
-    SkPoint clipPoints[]  = {{30, 130}, {120, 130}, {120, 230} };
-    SkPath clipPath;
-    clipPath.addPoly(clipPoints, SK_ARRAY_COUNT(clipPoints), true);
-    canvas->clipPath(clipPath);
-    bounds = canvas->getLocalClipBounds();
-    SkDebugf("left:%g  top:%g  right:%g  bottom:%g\n",
-            bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
-    canvas->scale(2, 2);
-    bounds = canvas->getLocalClipBounds();
-    SkDebugf("left:%g  top:%g  right:%g  bottom:%g\n",
-            bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
-    #StdOut
-        left:-1  top:-1  right:257  bottom:257
-        left:29  top:129  right:121  bottom:231
-        left:14.5  top:64.5  right:60.5  bottom:115.5
-    ##
-##
-
-# local canvas in example works around bug in fiddle ##
-#Bug 6524
-#SeeAlso getDeviceClipBounds getBaseLayerSize quickReject
-
-##
-
-#Method bool getLocalClipBounds(SkRect* bounds) const
-
-#In Clip
-#Populate
-
-#Example
-    void draw(SkCanvas* canvas) {
-        SkCanvas local(256, 256);
-        canvas = &local;
-        SkRect bounds;
-        SkDebugf("local bounds empty = %s\n", canvas->getLocalClipBounds(&bounds)
-                 ? "false" : "true");
-        SkPath path;
-        canvas->clipPath(path);
-        SkDebugf("local bounds empty = %s\n", canvas->getLocalClipBounds(&bounds)
-                 ? "false" : "true");
-    }
-    #StdOut
-        local bounds empty = false
-        local bounds empty = true
-    ##
-##
-
-# local canvas in example works around bug in fiddle ##
-#Bug 6524
-#SeeAlso getDeviceClipBounds getBaseLayerSize quickReject
-
-##
-
-#Method SkIRect getDeviceClipBounds() const
-
-#In Clip
-#Line # returns IRect bounds of Clip ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    #Description
-        Initial bounds is device bounds, not outset.
-        Clipped bounds is clipPath bounds, not outset.
-        Scaling the canvas by 1/2 on both axes scales the device bounds by 1/2
-        on both axes.
-    ##
-    SkCanvas device(256, 256);
-    canvas = &device;
-    SkIRect bounds = canvas->getDeviceClipBounds();
-    SkDebugf("left:%d  top:%d  right:%d  bottom:%d\n",
-            bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
-    SkPoint clipPoints[]  = {{30, 130}, {120, 130}, {120, 230} };
-    SkPath clipPath;
-    clipPath.addPoly(clipPoints, SK_ARRAY_COUNT(clipPoints), true);
-    canvas->save();
-    canvas->clipPath(clipPath);
-    bounds = canvas->getDeviceClipBounds();
-    SkDebugf("left:%d  top:%d  right:%d  bottom:%d\n",
-            bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
-    canvas->restore();
-    canvas->scale(1.f/2, 1.f/2);
-    canvas->clipPath(clipPath);
-    bounds = canvas->getDeviceClipBounds();
-    SkDebugf("left:%d  top:%d  right:%d  bottom:%d\n",
-            bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
-    #StdOut
-        left:0  top:0  right:256  bottom:256
-        left:30  top:130  right:120  bottom:230
-        left:15  top:65  right:60  bottom:115
-    ##
-}
-##
-
-#ToDo some confusion on why with an identity Matrix local and device are different ##
-#SeeAlso getLocalClipBounds getBaseLayerSize quickReject
-
-# device canvas in example works around bug in fiddle ##
-#Bug 6524
-
-##
-
-#Method bool getDeviceClipBounds(SkIRect* bounds) const
-
-#In Clip
-#Populate
-
-#Example
-    void draw(SkCanvas* canvas) {
-        SkIRect bounds;
-        SkDebugf("device bounds empty = %s\n", canvas->getDeviceClipBounds(&bounds)
-                 ? "false" : "true");
-        SkPath path;
-        canvas->clipPath(path);
-        SkDebugf("device bounds empty = %s\n", canvas->getDeviceClipBounds(&bounds)
-                 ? "false" : "true");
-    }
-    #StdOut
-        device bounds empty = false
-        device bounds empty = true
-    ##
-##
-
-#SeeAlso getLocalClipBounds getBaseLayerSize quickReject
-
-##
-
-#Subtopic Clip ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Draw
-#Line # draws into Canvas ##
-##
-
-#Method void drawColor(SkColor color, SkBlendMode mode = SkBlendMode::kSrcOver)
-#In Draw
-#Line # fills Clip with Color and Blend_Mode ##
-#Populate
-
-#Example
-    canvas->drawColor(SK_ColorRED);
-    canvas->clipRect(SkRect::MakeWH(150, 150));
-    canvas->drawColor(SkColorSetARGB(0x80, 0x00, 0xFF, 0x00), SkBlendMode::kPlus);
-    canvas->clipRect(SkRect::MakeWH(75, 75));
-    canvas->drawColor(SkColorSetARGB(0x80, 0x00, 0x00, 0xFF), SkBlendMode::kPlus);
-##
-
-#SeeAlso clear SkBitmap::erase drawPaint
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void clear(SkColor color)
-#In Draw
-#Line # fills Clip with Color ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    canvas->save();
-    canvas->clipRect(SkRect::MakeWH(256, 128));
-    canvas->clear(SkColorSetARGB(0x80, 0xFF, 0x00, 0x00));
-    canvas->restore();
-    canvas->save();
-    canvas->clipRect(SkRect::MakeWH(150, 192));
-    canvas->clear(SkColorSetARGB(0x80, 0x00, 0xFF, 0x00));
-    canvas->restore();
-    canvas->clipRect(SkRect::MakeWH(75, 256));
-    canvas->clear(SkColorSetARGB(0x80, 0x00, 0x00, 0xFF));
-}
-##
-
-#SeeAlso drawColor SkBitmap::erase drawPaint
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void discard()
-#In Utility
-#Line # makes Canvas contents undefined ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso flush() GrContext::abandonContext
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawPaint(const SkPaint& paint)
-#In Draw
-#Line # fills Clip with Paint ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkColor     colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE };
-    SkScalar    pos[] = { 0, SK_Scalar1/2, SK_Scalar1 };
-    SkPaint     paint;
-    paint.setShader(SkGradientShader::MakeSweep(256, 256, colors, pos, SK_ARRAY_COUNT(colors)));
-    canvas->drawPaint(paint);
-}
-##
-
-#SeeAlso clear drawColor SkBitmap::erase
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Enum PointMode
-#Line # sets drawPoints options ##
-
-#Code
-#Populate
-##
-
-Selects if an array of points are drawn as discrete points, as lines, or as
-an open polygon.
-
-#Const kPoints_PointMode 0
-#Line # draw each point separately ##
-##
-
-#Const kLines_PointMode 1
-#Line # draw each pair of points as a line segment ##
-##
-
-#Const kPolygon_PointMode 2
-#Line # draw the array of points as a open polygon ##
-##
-
-#Example
-    #Description
-        The upper left corner shows three squares when drawn as points.
-        The upper right corner shows one line; when drawn as lines, two points are required per line.
-        The lower right corner shows two lines; when draw as polygon, no miter is drawn at the corner.
-        The lower left corner shows two lines with a miter when path contains polygon.
-    ##
-void draw(SkCanvas* canvas) {
-  SkPaint paint;
-  paint.setStyle(SkPaint::kStroke_Style);
-  paint.setStrokeWidth(10);
-  SkPoint points[] = {{64, 32}, {96, 96}, {32, 96}};
-  canvas->drawPoints(SkCanvas::kPoints_PointMode, 3, points, paint);
-  canvas->translate(128, 0);
-  canvas->drawPoints(SkCanvas::kLines_PointMode, 3, points, paint);
-  canvas->translate(0, 128);
-  canvas->drawPoints(SkCanvas::kPolygon_PointMode, 3, points, paint);
-  SkPath path;
-  path.addPoly(points, 3, false);
-  canvas->translate(-128, 0);
-  canvas->drawPath(path, paint);
-}
-##
-
-#SeeAlso drawLine drawPoint drawPath
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawPoints(PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint)
-#In Draw
-#Line # draws array as points, lines, polygon ##
-#Populate
-
-#Example
-#Height 200
-    #Description
-    #List
-    # The first column draws points. ##
-    # The second column draws points as lines. ##
-    # The third column draws points as a polygon. ##
-    # The fourth column draws points as a polygonal path. ##
-    # The first row uses a round cap and round join. ##
-    # The second row uses a square cap and a miter join. ##
-    # The third row uses a butt cap and a bevel join. ##
-    ##
-    The transparent color makes multiple line draws visible;
-    the path is drawn all at once.
-    ##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setStrokeWidth(10);
-    paint.setColor(0x80349a45);
-    const SkPoint points[] = {{32, 16}, {48, 48}, {16, 32}};
-    const SkPaint::Join join[] = { SkPaint::kRound_Join,
-                                   SkPaint::kMiter_Join,
-                                   SkPaint::kBevel_Join };
-    int joinIndex = 0;
-    SkPath path;
-    path.addPoly(points, 3, false);
-    for (const auto cap : { SkPaint::kRound_Cap, SkPaint::kSquare_Cap, SkPaint::kButt_Cap } ) {
-        paint.setStrokeCap(cap);
-        paint.setStrokeJoin(join[joinIndex++]);
-        for (const auto mode : { SkCanvas::kPoints_PointMode,
-                                 SkCanvas::kLines_PointMode,
-                                 SkCanvas::kPolygon_PointMode } ) {
-            canvas->drawPoints(mode, 3, points, paint);
-            canvas->translate(64, 0);
-        }
-        canvas->drawPath(path, paint);
-        canvas->translate(-192, 64);
-    }
-}
-##
-
-#SeeAlso drawLine drawPoint drawPath
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawPoint(SkScalar x, SkScalar y, const SkPaint& paint)
-#In Draw
-#Line # draws point at (x, y) position ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-  SkPaint paint;
-  paint.setAntiAlias(true);
-  paint.setColor(0x80349a45);
-  paint.setStyle(SkPaint::kStroke_Style);
-  paint.setStrokeWidth(100);
-  paint.setStrokeCap(SkPaint::kRound_Cap);
-  canvas->scale(1, 1.2f);
-  canvas->drawPoint(64, 96, paint);
-  canvas->scale(.6f, .8f);
-  paint.setColor(SK_ColorWHITE);
-  canvas->drawPoint(106, 120, paint);
-}
-##
-
-#SeeAlso drawPoints drawCircle drawRect drawLine drawPath
-
-##
-
-#Method void drawPoint(SkPoint p, const SkPaint& paint)
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-  SkPaint paint;
-  paint.setAntiAlias(true);
-  paint.setColor(0x80349a45);
-  paint.setStyle(SkPaint::kStroke_Style);
-  paint.setStrokeWidth(100);
-  paint.setStrokeCap(SkPaint::kSquare_Cap);
-  canvas->scale(1, 1.2f);
-  canvas->drawPoint({64, 96}, paint);
-  canvas->scale(.6f, .8f);
-  paint.setColor(SK_ColorWHITE);
-  canvas->drawPoint(106, 120, paint);
-}
-##
-
-#SeeAlso drawPoints drawCircle drawRect drawLine drawPath
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawLine(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1, const SkPaint& paint)
-#In Draw
-#Line # draws line segment between two points ##
-#Populate
-
-#Example
-  SkPaint paint;
-  paint.setAntiAlias(true);
-  paint.setColor(0xFF9a67be);
-  paint.setStrokeWidth(20);
-  canvas->skew(1, 0);
-  canvas->drawLine(32, 96, 32, 160, paint);
-  canvas->skew(-2, 0);
-  canvas->drawLine(288, 96, 288, 160, paint);
-##
-
-#SeeAlso drawPoint drawCircle drawRect drawPath
-
-##
-
-#Method void drawLine(SkPoint p0, SkPoint p1, const SkPaint& paint)
-#Populate
-
-#Example
-  SkPaint paint;
-  paint.setAntiAlias(true);
-  paint.setColor(0xFF9a67be);
-  paint.setStrokeWidth(20);
-  canvas->skew(1, 0);
-  canvas->drawLine({32, 96}, {32, 160}, paint);
-  canvas->skew(-2, 0);
-  canvas->drawLine({288, 96}, {288, 160}, paint);
-##
-
-#SeeAlso drawPoint drawCircle drawRect drawPath
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawRect(const SkRect& rect, const SkPaint& paint)
-#In Draw
-#Line # draws Rect using Clip, Matrix, and Paint ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPoint rectPts[] = { {64, 48}, {192, 160} };
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setStrokeWidth(20);
-    paint.setStrokeJoin(SkPaint::kRound_Join);
-    SkMatrix rotator;
-    rotator.setRotate(30, 128, 128);
-    for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorYELLOW, SK_ColorMAGENTA } ) {
-        paint.setColor(color);
-        SkRect rect;
-        rect.set(rectPts[0], rectPts[1]);
-        canvas->drawRect(rect, paint);
-        rotator.mapPoints(rectPts, 2);
-    }
-}
-##
-
-#SeeAlso drawIRect drawRRect drawRoundRect drawRegion drawPath drawLine
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawIRect(const SkIRect& rect, const SkPaint& paint)
-#In Draw
-#Line # draws IRect using Clip, Matrix, and Paint ##
-#Populate
-
-#Example
-    SkIRect rect = { 64, 48, 192, 160 };
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setStrokeWidth(20);
-    paint.setStrokeJoin(SkPaint::kRound_Join);
-    for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorYELLOW, SK_ColorMAGENTA } ) {
-        paint.setColor(color);
-        canvas->drawIRect(rect, paint);
-        canvas->rotate(30, 128, 128);
-    }
-##
-
-#SeeAlso drawRect drawRRect drawRoundRect drawRegion drawPath drawLine
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawRegion(const SkRegion& region, const SkPaint& paint)
-#In Draw
-#Line # draws Region using Clip, Matrix, and Paint ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkRegion region;
-    region.op( 10, 10, 50, 50, SkRegion::kUnion_Op);
-    region.op( 10, 50, 90, 90, SkRegion::kUnion_Op);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setStrokeWidth(20);
-    paint.setStrokeJoin(SkPaint::kRound_Join);
-    canvas->drawRegion(region, paint);
-}
-##
-
-#SeeAlso drawRect drawIRect drawPath
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawOval(const SkRect& oval, const SkPaint& paint)
-#In Draw
-#Line # draws Oval using Clip, Matrix, and Paint ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    canvas->clear(0xFF3f5f9f);
-    SkColor  kColor1 = SkColorSetARGB(0xff, 0xff, 0x7f, 0);
-    SkColor  g1Colors[] = { kColor1, SkColorSetA(kColor1, 0x20) };
-    SkPoint  g1Points[] = { { 0, 0 }, { 0, 100 } };
-    SkScalar pos[] = { 0.2f, 1.0f };
-    SkRect bounds = SkRect::MakeWH(80, 70);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setShader(SkGradientShader::MakeLinear(g1Points, g1Colors, pos, SK_ARRAY_COUNT(g1Colors),
-            SkShader::kClamp_TileMode));
-    canvas->drawOval(bounds , paint);
-}
-##
-
-#SeeAlso drawCircle drawPoint drawPath drawRRect drawRoundRect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawRRect(const SkRRect& rrect, const SkPaint& paint)
-#In Draw
-#Line # draws Round_Rect using Clip, Matrix, and Paint ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkRect outer = {30, 40, 210, 220};
-    SkRect radii = {30, 50, 70, 90 };
-    SkRRect rRect;
-    rRect.setNinePatch(outer, radii.fLeft, radii.fTop, radii.fRight, radii.fBottom);
-    canvas->drawRRect(rRect, paint);
-    paint.setColor(SK_ColorWHITE);
-    canvas->drawLine(outer.fLeft + radii.fLeft, outer.fTop,
-                     outer.fLeft + radii.fLeft, outer.fBottom, paint);
-    canvas->drawLine(outer.fRight - radii.fRight, outer.fTop,
-                     outer.fRight - radii.fRight, outer.fBottom, paint);
-    canvas->drawLine(outer.fLeft,  outer.fTop + radii.fTop,
-                     outer.fRight, outer.fTop + radii.fTop, paint);
-    canvas->drawLine(outer.fLeft,  outer.fBottom - radii.fBottom,
-                     outer.fRight, outer.fBottom - radii.fBottom, paint);
-}
-##
-
-#SeeAlso drawRect drawRoundRect drawDRRect drawCircle drawOval drawPath
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint)
-#In Draw
-#Line # draws double Round_Rect stroked or filled ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-   SkRRect outer = SkRRect::MakeRect({20, 40, 210, 200});
-   SkRRect inner = SkRRect::MakeOval({60, 70, 170, 160});
-   SkPaint paint;
-   canvas->drawDRRect(outer, inner, paint);
-}
-##
-
-#Example
-#Description
-    Outer Rect has no corner radii, but stroke join is rounded.
-    Inner Round_Rect has corner radii; outset stroke increases radii of corners.
-    Stroke join does not affect inner Round_Rect since it has no sharp corners.
-##
-void draw(SkCanvas* canvas) {
-   SkRRect outer = SkRRect::MakeRect({20, 40, 210, 200});
-   SkRRect inner = SkRRect::MakeRectXY({60, 70, 170, 160}, 10, 10);
-   SkPaint paint;
-   paint.setAntiAlias(true);
-   paint.setStyle(SkPaint::kStroke_Style);
-   paint.setStrokeWidth(20);
-   paint.setStrokeJoin(SkPaint::kRound_Join);
-   canvas->drawDRRect(outer, inner, paint);
-   paint.setStrokeWidth(1);
-   paint.setColor(SK_ColorWHITE);
-   canvas->drawDRRect(outer, inner, paint);
-}
-##
-
-#SeeAlso drawRect drawRoundRect drawRRect drawCircle drawOval drawPath
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawCircle(SkScalar cx, SkScalar cy, SkScalar radius, const SkPaint& paint)
-#In Draw
-#Line # draws Circle using Clip, Matrix, and Paint ##
-#Populate
-
-#Example
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        canvas->drawCircle(128, 128, 90, paint);
-        paint.setColor(SK_ColorWHITE);
-        canvas->drawCircle(86, 86, 20, paint);
-        canvas->drawCircle(160, 76, 20, paint);
-        canvas->drawCircle(140, 150, 35, paint);
-    }
-##
-
-#SeeAlso drawOval drawRRect drawRoundRect drawPath drawArc drawPoint drawLine
-
-##
-
-#Method void drawCircle(SkPoint center, SkScalar radius, const SkPaint& paint)
-#Populate
-
-#Example
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        canvas->drawCircle(128, 128, 90, paint);
-        paint.setColor(SK_ColorWHITE);
-        canvas->drawCircle({86, 86}, 20, paint);
-        canvas->drawCircle({160, 76}, 20, paint);
-        canvas->drawCircle({140, 150}, 35, paint);
-    }
-##
-
-#SeeAlso drawOval drawRRect drawRoundRect drawPath drawArc drawPoint drawLine
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
-                 bool useCenter, const SkPaint& paint)
-#In Draw
-#Line # draws Arc using Clip, Matrix, and Paint ##
-#Populate
-
-#Example
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        SkRect oval = { 4, 4, 60, 60};
-        for (auto useCenter : { false, true } ) {
-            for (auto style : { SkPaint::kFill_Style, SkPaint::kStroke_Style } ) {
-                paint.setStyle(style);
-                for (auto degrees : { 45, 90, 180, 360} ) {
-                    canvas->drawArc(oval, 0, degrees , useCenter, paint);
-                    canvas->translate(64, 0);
-                }
-                canvas->translate(-256, 64);
-            }
-        }
-    }
-##
-
-#Example
-#Height 64
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setStyle(SkPaint::kStroke_Style);
-        paint.setStrokeWidth(4);
-        SkRect oval = { 4, 4, 60, 60};
-        float intervals[] = { 5, 5 };
-        paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 2.5f));
-        for (auto degrees : { 270, 360, 540, 720 } ) {
-            canvas->drawArc(oval, 0, degrees, false, paint);
-            canvas->translate(64, 0);
-        }
-    }
-##
-
-#SeeAlso SkPath::arcTo drawCircle drawOval drawPath
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry, const SkPaint& paint)
-#In Draw
-#Line # draws Round_Rect using Clip, Matrix, and Paint ##
-#Populate
-
-#Example
-#Description
-    Top row has a zero radius a generates a rectangle.
-    Second row radii sum to less than sides.
-    Third row radii sum equals sides.
-    Fourth row radii sum exceeds sides; radii are scaled to fit.
-##
-    void draw(SkCanvas* canvas) {
-        SkVector radii[] = { {0, 20}, {10, 10}, {10, 20}, {10, 40} };
-        SkPaint paint;
-        paint.setStrokeWidth(15);
-        paint.setStrokeJoin(SkPaint::kRound_Join);
-        paint.setAntiAlias(true);
-        for (auto style : { SkPaint::kStroke_Style, SkPaint::kFill_Style  } ) {
-            paint.setStyle(style );
-            for (size_t i = 0; i < SK_ARRAY_COUNT(radii); ++i) {
-               canvas->drawRoundRect({10, 10, 60, 40}, radii[i].fX, radii[i].fY, paint);
-               canvas->translate(0, 60);
-            }
-            canvas->translate(80, -240);
-        }
-    }
-##
-
-#SeeAlso drawRRect drawRect drawDRRect drawPath drawCircle drawOval drawPoint
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawPath(const SkPath& path, const SkPaint& paint)
-#In Draw
-#Line # draws Path using Clip, Matrix, and Paint ##
-#Populate
-
-#Example
-#Description
-    Top rows draw stroked path with combinations of joins and caps. The open contour
-    is affected by caps; the closed contour is affected by joins.
-    Bottom row draws fill the same for open and closed contour.
-    First bottom column shows winding fills overlap.
-    Second bottom column shows even odd fills exclude overlap.
-    Third bottom column shows inverse winding fills area outside both contours.
-##
-void draw(SkCanvas* canvas) {
-    SkPath path;
-    path.moveTo(20, 20);
-    path.quadTo(60, 20, 60, 60);
-    path.close();
-    path.moveTo(60, 20);
-    path.quadTo(60, 60, 20, 60);
-    SkPaint paint;
-    paint.setStrokeWidth(10);
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    for (auto join: { SkPaint::kBevel_Join, SkPaint::kRound_Join, SkPaint::kMiter_Join } ) {
-        paint.setStrokeJoin(join);
-        for (auto cap: { SkPaint::kButt_Cap, SkPaint::kSquare_Cap, SkPaint::kRound_Cap  } ) {
-            paint.setStrokeCap(cap);
-            canvas->drawPath(path, paint);
-            canvas->translate(80, 0);
-        }
-        canvas->translate(-240, 60);
-    }
-    paint.setStyle(SkPaint::kFill_Style);
-    for (auto fill : { SkPath::kWinding_FillType,
-                       SkPath::kEvenOdd_FillType,
-                       SkPath::kInverseWinding_FillType } ) {
-        path.setFillType(fill);
-        canvas->save();
-        canvas->clipRect({0, 10, 80, 70});
-        canvas->drawPath(path, paint);
-        canvas->restore();
-        canvas->translate(80, 0);
-    }
-}
-##
-
-#SeeAlso SkPath drawLine drawArc drawRect drawPoints
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Draw_Image
-#Line # draws Image to Canvas ##
-
-drawImage, drawImageRect, and drawImageNine can be called with a bare pointer or
-a smart pointer as a convenience. The pairs of calls are otherwise identical.
-
-#Method void drawImage(const SkImage* image, SkScalar left, SkScalar top, const SkPaint* paint = nullptr)
-#In Draw_Image
-#In Draw
-#Line # draws Image at (x, y) position ##
-#Populate
-
-#Example
-#Height 64
-#Image 4
-void draw(SkCanvas* canvas) {
-   // sk_sp<SkImage> image;
-   SkImage* imagePtr = image.get();
-   canvas->drawImage(imagePtr, 0, 0);
-   SkPaint paint;
-   canvas->drawImage(imagePtr, 80, 0, &paint);
-   paint.setAlpha(0x80);
-   canvas->drawImage(imagePtr, 160, 0, &paint);
-}
-##
-
-#SeeAlso drawBitmap drawImageLattice drawImageNine drawImageRect SkPaint::setImageFilter
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawImage(const sk_sp<SkImage>& image, SkScalar left, SkScalar top,
-                   const SkPaint* paint = nullptr)
-#Populate
-
-#Example
-#Height 64
-#Image 4
-void draw(SkCanvas* canvas) {
-   // sk_sp<SkImage> image;
-   canvas->drawImage(image, 0, 0);
-   SkPaint paint;
-   canvas->drawImage(image, 80, 0, &paint);
-   paint.setAlpha(0x80);
-   canvas->drawImage(image, 160, 0, &paint);
-}
-##
-
-#SeeAlso drawBitmap drawImageLattice drawImageNine drawImageRect SkPaint::setImageFilter
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Enum SrcRectConstraint
-#Line # sets drawImageRect options ##
-
-#Code
-#Populate
-##
-
-SrcRectConstraint controls the behavior at the edge of source Rect,
-provided to drawImageRect, trading off speed for precision.
-
-Image_Filter in Paint may sample multiple pixels in the image. Source Rect
-restricts the bounds of pixels that may be read. Image_Filter may slow down if
-it cannot read outside the bounds, when sampling near the edge of source Rect.
-SrcRectConstraint specifies whether an Image_Filter is allowed to read pixels
-outside source Rect.
-
-#Const kStrict_SrcRectConstraint 0
-#Line # sample only inside bounds; slower ##
-    Requires Image_Filter to respect source Rect,
-    sampling only inside of its bounds, possibly with a performance penalty.
-##
-
-#Const kFast_SrcRectConstraint 1
-#Line # sample outside bounds; faster ##
-    Permits Image_Filter to sample outside of source Rect
-    by half the width of Image_Filter, permitting it to run faster but with
-    error at the image edges.
-##
-
-#Example
-#Height 64
-#Description
-    redBorder contains a black and white checkerboard bordered by red.
-    redBorder is drawn scaled by 16 on the left.
-    The middle and right bitmaps are filtered checkerboards.
-    Drawing the checkerboard with kStrict_SrcRectConstraint shows only a blur of black and white.
-    Drawing the checkerboard with kFast_SrcRectConstraint allows red to bleed in the corners.
-##
-void draw(SkCanvas* canvas) {
-    SkBitmap redBorder;
-    redBorder.allocPixels(SkImageInfo::MakeN32Premul(4, 4));
-    SkCanvas checkRed(redBorder);
-    checkRed.clear(SK_ColorRED);
-    uint32_t checkers[][2] = { { SK_ColorBLACK, SK_ColorWHITE },
-                               { SK_ColorWHITE, SK_ColorBLACK } };
-    checkRed.writePixels(
-            SkImageInfo::MakeN32Premul(2, 2), (void*) checkers, sizeof(checkers[0]), 1, 1);
-    canvas->scale(16, 16);
-    canvas->drawBitmap(redBorder, 0, 0, nullptr);
-    canvas->resetMatrix();
-    sk_sp<SkImage> image = SkImage::MakeFromBitmap(redBorder);
-    SkPaint lowPaint;
-    lowPaint.setFilterQuality(kLow_SkFilterQuality);
-    for (auto constraint : { SkCanvas::kStrict_SrcRectConstraint,
-                             SkCanvas::kFast_SrcRectConstraint } ) {
-        canvas->translate(80, 0);
-        canvas->drawImageRect(image.get(), SkRect::MakeLTRB(1, 1, 3, 3),
-                SkRect::MakeLTRB(16, 16, 48, 48), &lowPaint, constraint);
-    }
-}
-##
-
-#SeeAlso drawImageRect drawImage SkPaint::setImageFilter
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawImageRect(const SkImage* image, const SkRect& src, const SkRect& dst,
-                       const SkPaint* paint,
-                       SrcRectConstraint constraint = kStrict_SrcRectConstraint)
-#In Draw_Image
-#In Draw
-#Line # draws Image, source Rect to destination Rect ##
-#Populate
-
-#Example
-#Height 64
-#Description
-    The left bitmap draws with Paint default kNone_SkFilterQuality, and stays within
-    its bounds; there is no bleeding with kFast_SrcRectConstraint.
-    the middle and right bitmaps draw with kLow_SkFilterQuality; with
-    kStrict_SrcRectConstraint, the filter remains within the checkerboard, and
-    with kFast_SrcRectConstraint red bleeds on the edges.
-##
-void draw(SkCanvas* canvas) {
-    uint32_t pixels[][4] = {
-            { 0xFFFF0000, 0xFFFF0000, 0xFFFF0000, 0xFFFF0000 },
-            { 0xFFFF0000, 0xFF000000, 0xFFFFFFFF, 0xFFFF0000 },
-            { 0xFFFF0000, 0xFFFFFFFF, 0xFF000000, 0xFFFF0000 },
-            { 0xFFFF0000, 0xFFFF0000, 0xFFFF0000, 0xFFFF0000 } };
-    SkBitmap redBorder;
-    redBorder.installPixels(SkImageInfo::MakeN32Premul(4, 4),
-            (void*) pixels, sizeof(pixels[0]));
-    sk_sp<SkImage> image = SkImage::MakeFromBitmap(redBorder);
-    SkPaint lowPaint;
-    for (auto constraint : {
-            SkCanvas::kFast_SrcRectConstraint,
-            SkCanvas::kStrict_SrcRectConstraint,
-            SkCanvas::kFast_SrcRectConstraint } ) {
-        canvas->drawImageRect(image.get(), SkRect::MakeLTRB(1, 1, 3, 3),
-                SkRect::MakeLTRB(16, 16, 48, 48), &lowPaint, constraint);
-        lowPaint.setFilterQuality(kLow_SkFilterQuality);
-        canvas->translate(80, 0);
-    }
-}
-##
-
-#SeeAlso SrcRectConstraint drawImage drawImageLattice drawImageNine
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawImageRect(const SkImage* image, const SkIRect& isrc, const SkRect& dst,
-                       const SkPaint* paint, SrcRectConstraint constraint = kStrict_SrcRectConstraint)
-#In Draw_Image
-#In Draw
-#Populate
-
-#Example
-#Image 4
-void draw(SkCanvas* canvas) {
-    // sk_sp<SkImage> image;
-    for (auto i : { 1, 2, 4, 8 } ) {
-        canvas->drawImageRect(image.get(), SkIRect::MakeLTRB(0, 0, 100, 100),
-                SkRect::MakeXYWH(i * 20, i * 20, i * 20, i * 20), nullptr);
-    }
-}
-##
-
-#SeeAlso SrcRectConstraint drawImage drawImageLattice drawImageNine
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawImageRect(const SkImage* image, const SkRect& dst, const SkPaint* paint)
-#In Draw_Image
-#In Draw
-#Populate
-
-#Example
-#Image 4
-void draw(SkCanvas* canvas) {
-    // sk_sp<SkImage> image;
-    for (auto i : { 20, 40, 80, 160 } ) {
-        canvas->drawImageRect(image.get(), SkRect::MakeXYWH(i, i, i, i), nullptr);
-    }
-}
-##
-
-#SeeAlso SrcRectConstraint drawImage drawImageLattice drawImageNine
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawImageRect(const sk_sp<SkImage>& image, const SkRect& src, const SkRect& dst,
-                       const SkPaint* paint,
-                       SrcRectConstraint constraint = kStrict_SrcRectConstraint)
-#In Draw_Image
-#In Draw
-#Populate
-
-#Example
-#Height 64
-#Description
-    Canvas scales and translates; transformation from src to dst also scales.
-    The two matrices are concatenated to create the final transformation.
-##
-void draw(SkCanvas* canvas) {
-    uint32_t pixels[][2] = { { SK_ColorBLACK, SK_ColorWHITE },
-                             { SK_ColorWHITE, SK_ColorBLACK } };
-    SkBitmap bitmap;
-    bitmap.installPixels(SkImageInfo::MakeN32Premul(2, 2),
-            (void*) pixels, sizeof(pixels[0]));
-    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
-    SkPaint paint;
-    canvas->scale(4, 4);
-    for (auto alpha : { 50, 100, 150, 255 } ) {
-        paint.setAlpha(alpha);
-        canvas->drawImageRect(image, SkRect::MakeWH(2, 2), SkRect::MakeWH(8, 8), &paint);
-        canvas->translate(8, 0);
-    }
-}
-##
-
-#SeeAlso SrcRectConstraint drawImage drawImageLattice drawImageNine
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawImageRect(const sk_sp<SkImage>& image, const SkIRect& isrc, const SkRect& dst,
-                       const SkPaint* paint, SrcRectConstraint constraint = kStrict_SrcRectConstraint)
-#In Draw_Image
-#In Draw
-#Populate
-
-#Example
-#Height 64
-void draw(SkCanvas* canvas) {
-    uint32_t pixels[][2] = { { 0x00000000, 0x55555555},
-                             { 0xAAAAAAAA, 0xFFFFFFFF} };
-    SkBitmap bitmap;
-    bitmap.installPixels(SkImageInfo::MakeN32Premul(2, 2),
-            (void*) pixels, sizeof(pixels[0]));
-    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
-    SkPaint paint;
-    canvas->scale(4, 4);
-    for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN } ) {
-        paint.setColorFilter(SkColorFilter::MakeModeFilter(color, SkBlendMode::kPlus));
-        canvas->drawImageRect(image, SkIRect::MakeWH(2, 2), SkRect::MakeWH(8, 8), &paint);
-        canvas->translate(8, 0);
-    }
-}
-##
-
-#SeeAlso SrcRectConstraint drawImage drawImageLattice drawImageNine
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawImageRect(const sk_sp<SkImage>& image, const SkRect& dst, const SkPaint* paint)
-#In Draw_Image
-#In Draw
-#Populate
-
-#Example
-#Height 64
-void draw(SkCanvas* canvas) {
-    uint32_t pixels[][2] = { { 0x00000000, 0x55550000},
-                             { 0xAAAA0000, 0xFFFF0000} };
-    SkBitmap bitmap;
-    bitmap.installPixels(SkImageInfo::MakeN32Premul(2, 2),
-            (void*) pixels, sizeof(pixels[0]));
-    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
-    SkPaint paint;
-    canvas->scale(4, 4);
-    for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN } ) {
-        paint.setColorFilter(SkColorFilter::MakeModeFilter(color, SkBlendMode::kPlus));
-        canvas->drawImageRect(image, SkRect::MakeWH(8, 8), &paint);
-        canvas->translate(8, 0);
-    }
-}
-##
-
-#SeeAlso SrcRectConstraint drawImage drawImageLattice drawImageNine
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawImageNine(const SkImage* image, const SkIRect& center, const SkRect& dst,
-                       const SkPaint* paint = nullptr)
-#In Draw_Image
-#In Draw
-#Line # draws Nine_Patch Image ##
-
-Draws Image image stretched proportionally to fit into Rect dst.
-IRect center divides the image into nine sections: four sides, four corners, and
-the center. Corners are unmodified or scaled down proportionately if their sides
-are larger than dst; center and four sides are scaled to fit remaining space, if any.
-
-Additionally transform draw using Clip, Matrix, and optional Paint paint.
-
-#paint_as_used_by_draw_lattice_or_draw_nine(image)#
-
-If generated mask extends beyond image bounds, replicate image edge colors, just
-as Shader made from SkImage::makeShader with SkShader::kClamp_TileMode set
-replicates the image edge color when it samples outside of its bounds.
-
-#Param  image      Image containing pixels, dimensions, and format ##
-#Param  center     IRect edge of image corners and sides ##
-#Param  dst        destination Rect of image to draw to ##
-#Param  paint      Paint containing Blend_Mode, Color_Filter, Image_Filter,
-                   and so on; or nullptr
-##
-
-#Example
-#Height 128
-#Description
-    The leftmost image is smaller than center; only corners are drawn, all scaled to fit.
-    The second image equals the size of center; only corners are drawn without scaling.
-    The remaining images are larger than center. All corners draw without scaling.
-    The sides and center are scaled if needed to take up the remaining space.
-##
-void draw(SkCanvas* canvas) {
-    SkIRect center = { 20, 10, 50, 40 };
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeN32Premul(60, 60));
-    SkCanvas bitCanvas(bitmap);
-    SkPaint paint;
-    SkColor gray = 0xFF000000;
-    int left = 0;
-    for (auto right: { center.fLeft, center.fRight, bitmap.width() } ) {
-        int top = 0;
-        for (auto bottom: { center.fTop, center.fBottom, bitmap.height() } ) {
-            paint.setColor(gray);
-            bitCanvas.drawIRect(SkIRect::MakeLTRB(left, top, right, bottom), paint);
-            gray += 0x001f1f1f;
-            top = bottom;
-        }
-        left = right;
-    }
-    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
-    SkImage* imagePtr = image.get();
-    for (auto dest: { 20, 30, 40, 60, 90 } ) {
-        canvas->drawImageNine(imagePtr, center, SkRect::MakeWH(dest, dest), nullptr);
-        canvas->translate(dest + 4, 0);
-    }
-}
-##
-
-#SeeAlso drawImage drawBitmapNine drawImageLattice drawImageRect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawImageNine(const sk_sp<SkImage>& image, const SkIRect& center, const SkRect& dst,
-                       const SkPaint* paint = nullptr)
-#In Draw_Image
-#In Draw
-Draws Image image stretched proportionally to fit into Rect dst.
-IRect center divides the image into nine sections: four sides, four corners, and
-the center. Corners are not scaled, or scaled down proportionately if their sides
-are larger than dst; center and four sides are scaled to fit remaining space, if any.
-
-Additionally transform draw using Clip, Matrix, and optional Paint paint.
-
-#paint_as_used_by_draw_lattice_or_draw_nine(image)#
-
-If generated mask extends beyond image bounds, replicate image edge colors, just
-as Shader made from SkImage::makeShader with SkShader::kClamp_TileMode set
-replicates the image edge color when it samples outside of its bounds.
-
-#Param  image      Image containing pixels, dimensions, and format ##
-#Param  center     IRect edge of image corners and sides ##
-#Param  dst        destination Rect of image to draw to ##
-#Param  paint      Paint containing Blend_Mode, Color_Filter, Image_Filter,
-                   and so on; or nullptr
-##
-
-#Example
-#Height 128
-#Description
-    The two leftmost images has four corners and sides to the left and right of center.
-    The leftmost image scales the width of corners proportionately to fit.
-    The third and fourth image corners are not scaled; the sides and center are scaled to
-    fill the remaining space.
-    The rightmost image has four corners scaled vertically to fit, and uses sides above
-    and below center to fill the remaining space.
-##
-void draw(SkCanvas* canvas) {
-    SkIRect center = { 20, 10, 50, 40 };
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeN32Premul(60, 60));
-    SkCanvas bitCanvas(bitmap);
-    SkPaint paint;
-    SkColor gray = 0xFF000000;
-    int left = 0;
-    for (auto right: { center.fLeft, center.fRight, bitmap.width() } ) {
-        int top = 0;
-        for (auto bottom: { center.fTop, center.fBottom, bitmap.height() } ) {
-            paint.setColor(gray);
-            bitCanvas.drawIRect(SkIRect::MakeLTRB(left, top, right, bottom), paint);
-            gray += 0x001f1f1f;
-            top = bottom;
-        }
-        left = right;
-    }
-    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
-    for (auto dest: { 20, 30, 40, 60, 90 } ) {
-        canvas->drawImageNine(image, center, SkRect::MakeWH(dest, 110 - dest), nullptr);
-        canvas->translate(dest + 4, 0);
-    }
-}
-##
-
-#SeeAlso drawImage drawBitmapNine drawImageLattice drawImageRect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top,
-                    const SkPaint* paint = nullptr)
-#In Draw_Image
-#In Draw
-#Line # draws Bitmap at (x, y) position ##
-#Populate
-
-#Example
-#Height 64
-void draw(SkCanvas* canvas) {
-    uint8_t pixels[][8] = { { 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00},
-                            { 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00},
-                            { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00},
-                            { 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF},
-                            { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
-                            { 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00},
-                            { 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00},
-                            { 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF} };
-    SkBitmap bitmap;
-    bitmap.installPixels(SkImageInfo::MakeA8(8, 8),
-            (void*) pixels, sizeof(pixels[0]));
-    SkPaint paint;
-    canvas->scale(4, 4);
-    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xFF007F00} ) {
-        paint.setColor(color);
-        canvas->drawBitmap(bitmap, 0, 0, &paint);
-        canvas->translate(12, 0);
-    }
-}
-##
-
-#SeeAlso drawImage drawBitmapLattice drawBitmapNine drawBitmapRect SkBitmap::readPixels SkBitmap::writePixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawBitmapRect(const SkBitmap& bitmap, const SkRect& src, const SkRect& dst,
-                        const SkPaint* paint, SrcRectConstraint constraint = kStrict_SrcRectConstraint)
-#In Draw_Image
-#In Draw
-#Line # draws Bitmap, source Rect to destination Rect ##
-#Populate
-
-#Example
-#Height 64
-void draw(SkCanvas* canvas) {
-    uint8_t pixels[][8] = { { 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00},
-                            { 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00},
-                            { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00},
-                            { 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF},
-                            { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
-                            { 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00},
-                            { 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00},
-                            { 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00} };
-    SkBitmap bitmap;
-    bitmap.installPixels(SkImageInfo::MakeA8(8, 8),
-            (void*) pixels, sizeof(pixels[0]));
-    SkPaint paint;
-    paint.setMaskFilter(SkMaskFilter::MakeBlur(kSolid_SkBlurStyle, 6));
-    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xFF007F00} ) {
-        paint.setColor(color);
-        canvas->drawBitmapRect(bitmap, SkRect::MakeWH(8, 8), SkRect::MakeWH(32, 32), &paint);
-        canvas->translate(48, 0);
-    }
-}
-##
-
-#SeeAlso drawImageRect drawBitmap drawBitmapLattice drawBitmapNine
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawBitmapRect(const SkBitmap& bitmap, const SkIRect& isrc, const SkRect& dst,
-                        const SkPaint* paint, SrcRectConstraint constraint = kStrict_SrcRectConstraint)
-#In Draw_Image
-#In Draw
-#Populate
-
-#Example
-#Height 64
-void draw(SkCanvas* canvas) {
-    uint8_t pixels[][8] = { { 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00},
-                            { 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00},
-                            { 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF},
-                            { 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF},
-                            { 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF},
-                            { 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF},
-                            { 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00},
-                            { 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00} };
-    SkBitmap bitmap;
-    bitmap.installPixels(SkImageInfo::MakeA8(8, 8),
-            (void*) pixels, sizeof(pixels[0]));
-    SkPaint paint;
-    paint.setFilterQuality(kHigh_SkFilterQuality);
-    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xFF007F00, 0xFF7f007f} ) {
-        paint.setColor(color);
-        canvas->drawBitmapRect(bitmap, SkIRect::MakeWH(8, 8), SkRect::MakeWH(32, 32), &paint);
-        canvas->translate(48.25f, 0);
-    }
-}
-##
-
-#SeeAlso drawImageRect drawBitmap drawBitmapLattice drawBitmapNine
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawBitmapRect(const SkBitmap& bitmap, const SkRect& dst, const SkPaint* paint,
-                        SrcRectConstraint constraint = kStrict_SrcRectConstraint)
-#In Draw_Image
-#In Draw
-#Populate
-
-#Example
-#Height 64
-void draw(SkCanvas* canvas) {
-    uint32_t pixels[][2] = { { 0x00000000, 0x55550000},
-                             { 0xAAAA0000, 0xFFFF0000} };
-    SkBitmap bitmap;
-    bitmap.installPixels(SkImageInfo::MakeN32Premul(2, 2),
-            (void*) pixels, sizeof(pixels[0]));
-    SkPaint paint;
-    canvas->scale(4, 4);
-    for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN } ) {
-        paint.setColorFilter(SkColorFilter::MakeModeFilter(color, SkBlendMode::kPlus));
-        canvas->drawBitmapRect(bitmap, SkRect::MakeWH(8, 8), &paint);
-        canvas->translate(8, 0);
-    }
-}
-##
-
-#SeeAlso drawImageRect drawBitmap drawBitmapLattice drawBitmapNine
-
-##
-
-# ------------------------------------------------------------------------------
-
-#PhraseDef paint_as_used_by_draw_lattice_or_draw_nine(bitmap_or_image)
-If Paint paint is supplied, apply Color_Filter, Color_Alpha, Image_Filter,
-Blend_Mode, and Draw_Looper. If #bitmap_or_image# is kAlpha_8_SkColorType, apply Shader.
-If paint contains Mask_Filter, generate mask from #bitmap_or_image# bounds. If paint
-Filter_Quality set to kNone_SkFilterQuality, disable pixel filtering. For all
-other values of paint Filter_Quality, use kLow_SkFilterQuality to filter pixels.
-Any SkMaskFilter on paint is ignored as is paint Anti_Aliasing state.
-##
-
-#Method void drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, const SkRect& dst,
-                        const SkPaint* paint = nullptr)
-#In Draw_Image
-#In Draw
-#Line # draws Nine_Patch Bitmap ##
-
-Draws Bitmap bitmap stretched proportionally to fit into Rect dst.
-IRect center divides the bitmap into nine sections: four sides, four corners,
-and the center. Corners are not scaled, or scaled down proportionately if their
-sides are larger than dst; center and four sides are scaled to fit remaining
-space, if any.
-
-Additionally transform draw using Clip, Matrix, and optional Paint paint.
-
-#paint_as_used_by_draw_lattice_or_draw_nine(bitmap)#
-
-If generated mask extends beyond bitmap bounds, replicate bitmap edge colors,
-just as Shader made from SkShader::MakeBitmapShader with
-SkShader::kClamp_TileMode set replicates the bitmap edge color when it samples
-outside of its bounds.
-
-#Param  bitmap     Bitmap containing pixels, dimensions, and format ##
-#Param  center     IRect edge of image corners and sides ##
-#Param  dst        destination Rect of image to draw to ##
-#Param  paint    Paint containing Blend_Mode, Color_Filter, Image_Filter,
-                 and so on; or nullptr
-##
-
-#Example
-#Height 128
-#Description
-    The two leftmost bitmap draws has four corners and sides to the left and right of center.
-    The leftmost bitmap draw scales the width of corners proportionately to fit.
-    The third and fourth draw corners are not scaled; the sides and center are scaled to
-    fill the remaining space.
-    The rightmost bitmap draw has four corners scaled vertically to fit, and uses sides above
-    and below center to fill the remaining space.
-##
-void draw(SkCanvas* canvas) {
-    SkIRect center = { 20, 10, 50, 40 };
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeN32Premul(60, 60));
-    SkCanvas bitCanvas(bitmap);
-    SkPaint paint;
-    SkColor gray = 0xFF000000;
-    int left = 0;
-    for (auto right: { center.fLeft, center.fRight, bitmap.width() } ) {
-        int top = 0;
-        for (auto bottom: { center.fTop, center.fBottom, bitmap.height() } ) {
-            paint.setColor(gray);
-            bitCanvas.drawIRect(SkIRect::MakeLTRB(left, top, right, bottom), paint);
-            gray += 0x001f1f1f;
-            top = bottom;
-        }
-        left = right;
-    }
-    for (auto dest: { 20, 30, 40, 60, 90 } ) {
-        canvas->drawBitmapNine(bitmap, center, SkRect::MakeWH(dest, 110 - dest), nullptr);
-        canvas->translate(dest + 4, 0);
-    }
-}
-##
-
-#SeeAlso drawImageNine drawBitmap drawBitmapLattice drawBitmapRect
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Lattice
-#Line # divides Bitmap or Image into a rectangular grid ##
-
-#Struct Lattice
-#Line # divides Bitmap or Image into a rectangular grid ##
-
-#Code
-#Populate
-##
-
-Lattice divides Bitmap or Image into a rectangular grid.
-Grid entries on even columns and even rows are fixed; these entries are
-always drawn at their original size if the destination is large enough.
-If the destination side is too small to hold the fixed entries, all fixed
-entries are proportionately scaled down to fit.
-The grid entries not on even columns and rows are scaled to fit the
-remaining space, if any.
-
-    #Enum RectType
-    #Line # optional setting per rectangular grid entry ##
-        #Code
-        #Populate
-        ##
-
-        Optional setting per rectangular grid entry to make it transparent,
-        or to fill the grid entry with a color.
-
-        #Const kDefault 0
-        #Line # draws Bitmap into lattice rectangle ##
-        ##
-
-        #Const kTransparent 1
-        #Line # skips lattice rectangle by making it transparent ##
-        ##
-
-        #Const kFixedColor 2
-        #Line # draws one of fColors into lattice rectangle ##
-        ##
-    ##
-
-#Subtopic Members
-##
-
-    #Member const int*   fXDivs
-    #Line # x-axis values dividing bitmap ##
-        Array of x-axis values that divide the bitmap vertically.
-        Array entries must be unique, increasing, greater than or equal to
-        fBounds left edge, and less than fBounds right edge.
-        Set the first element to fBounds left to collapse the left column of
-        fixed grid entries.
-    ##
-
-    #Member const int*   fYDivs
-    #Line # y-axis values dividing bitmap ##
-        Array of y-axis values that divide the bitmap horizontally.
-        Array entries must be unique, increasing, greater than or equal to
-        fBounds top edge, and less than fBounds bottom edge.
-        Set the first element to fBounds top to collapse the top row of fixed
-        grid entries.
-    ##
-
-    #Member const RectType* fRectTypes
-    #Line # array of fill types ##
-        Optional array of fill types, one per rectangular grid entry:
-        array length must be #Formula # (fXCount + 1) * (fYCount + 1) ##.
-
-        Each RectType is one of: kDefault, kTransparent, kFixedColor.
-
-        Array entries correspond to the rectangular grid entries, ascending
-        left to right and then top to bottom.
-    ##
-
-    #Member int   fXCount
-    #Line # number of x-coordinates ##
-        Number of entries in fXDivs array; one less than the number of
-        horizontal divisions.
-    ##
-
-    #Member int   fYCount
-    #Line # number of y-coordinates ##
-        Number of entries in fYDivs array; one less than the number of vertical
-        divisions.
-    ##
-
-    #Member const SkIRect*   fBounds
-    #Line # source bounds to draw from ##
-       Optional subset IRect source to draw from.
-       If nullptr, source bounds is dimensions of Bitmap or Image.
-    ##
-
-    #Member const SkColor*   fColors
-    #Line # array of colors ##
-       Optional array of colors, one per rectangular grid entry.
-       Array length must be #Formula # (fXCount + 1) * (fYCount + 1) ##.
-
-       Array entries correspond to the rectangular grid entries, ascending
-       left to right, then top to bottom.
-    ##
-
-#Struct Lattice ##
-
-#Method void drawBitmapLattice(const SkBitmap& bitmap, const Lattice& lattice, const SkRect& dst,
-                           const SkPaint* paint = nullptr)
-#In Draw_Image
-#In Draw
-#Line # draws proportionally stretched Bitmap ##
-
-Draws Bitmap bitmap stretched proportionally to fit into Rect dst.
-
-Lattice lattice divides bitmap into a rectangular grid.
-Each intersection of an even-numbered row and column is fixed; like the corners
-of drawBitmapNine, fixed lattice elements never scale larger than their initial
-size and shrink proportionately when all fixed elements exceed the bitmap
-dimension. All other grid elements scale to fill the available space, if any.
-
-Additionally transform draw using Clip, Matrix, and optional Paint paint.
-
-#paint_as_used_by_draw_lattice_or_draw_nine(bitmap)#
-
-If generated mask extends beyond bitmap bounds, replicate bitmap edge colors,
-just as Shader made from SkShader::MakeBitmapShader with
-SkShader::kClamp_TileMode set replicates the bitmap edge color when it samples
-outside of its bounds.
-
-#Param  bitmap     Bitmap containing pixels, dimensions, and format ##
-#Param  lattice    division of bitmap into fixed and variable rectangles ##
-#Param  dst        destination Rect of image to draw to ##
-#Param  paint    Paint containing Blend_Mode, Color_Filter, Image_Filter,
-                 and so on; or nullptr
-##
-
-#Example
-#Height 128
-#Description
-    The two leftmost bitmap draws has four corners and sides to the left and right of center.
-    The leftmost bitmap draw scales the width of corners proportionately to fit.
-    The third and fourth draw corners are not scaled; the sides are scaled to
-    fill the remaining space; the center is transparent.
-    The rightmost bitmap draw has four corners scaled vertically to fit, and uses sides above
-    and below center to fill the remaining space.
-##
-void draw(SkCanvas* canvas) {
-    SkIRect center = { 20, 10, 50, 40 };
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeN32Premul(60, 60));
-    SkCanvas bitCanvas(bitmap);
-    SkPaint paint;
-    SkColor gray = 0xFF000000;
-    int left = 0;
-    for (auto right: { center.fLeft, center.fRight, bitmap.width() } ) {
-        int top = 0;
-        for (auto bottom: { center.fTop, center.fBottom, bitmap.height() } ) {
-            paint.setColor(gray);
-            bitCanvas.drawIRect(SkIRect::MakeLTRB(left, top, right, bottom), paint);
-            gray += 0x001f1f1f;
-            top = bottom;
-        }
-        left = right;
-    }
-    const int xDivs[] = { center.fLeft, center.fRight };
-    const int yDivs[] = { center.fTop, center.fBottom };
-    SkCanvas::Lattice::RectType fillTypes[3][3];
-    memset(fillTypes, 0, sizeof(fillTypes));
-    fillTypes[1][1] = SkCanvas::Lattice::kTransparent;
-    SkColor dummy[9];  // temporary pending bug fix
-    SkCanvas::Lattice lattice = { xDivs, yDivs, fillTypes[0], SK_ARRAY_COUNT(xDivs),
-         SK_ARRAY_COUNT(yDivs), nullptr, dummy };
-    for (auto dest: { 20, 30, 40, 60, 90 } ) {
-        canvas->drawBitmapLattice(bitmap, lattice, SkRect::MakeWH(dest, 110 - dest), nullptr);
-        canvas->translate(dest + 4, 0);
-    }
-}
-##
-
-#SeeAlso drawImageLattice drawBitmap drawBitmapNine Lattice
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawImageLattice(const SkImage* image, const Lattice& lattice, const SkRect& dst,
-                          const SkPaint* paint = nullptr)
-#In Draw_Image
-#In Draw
-#Line # draws proportionally stretched Image ##
-
-Draws Image image stretched proportionally to fit into Rect dst.
-
-Lattice lattice divides image into a rectangular grid.
-Each intersection of an even-numbered row and column is fixed; like the corners
-of drawBitmapNine, fixed lattice elements never scale larger than their initial
-size and shrink proportionately when all fixed elements exceed the bitmap
-dimension. All other grid elements scale to fill the available space, if any.
-
-Additionally transform draw using Clip, Matrix, and optional Paint paint.
-
-#paint_as_used_by_draw_lattice_or_draw_nine(image)#
-
-If generated mask extends beyond bitmap bounds, replicate bitmap edge colors,
-just as Shader made from SkShader::MakeBitmapShader with
-SkShader::kClamp_TileMode set replicates the bitmap edge color when it samples
-outside of its bounds.
-
-#Param  image      Image containing pixels, dimensions, and format ##
-#Param  lattice    division of bitmap into fixed and variable rectangles ##
-#Param  dst        destination Rect of image to draw to ##
-#Param  paint    Paint containing Blend_Mode, Color_Filter, Image_Filter,
-                 and so on; or nullptr
-##
-
-#Example
-#Height 128
-#Description
-    The leftmost image is smaller than center; only corners are drawn, all scaled to fit.
-    The second image equals the size of center; only corners are drawn without scaling.
-    The remaining images are larger than center. All corners draw without scaling. The sides
-    are scaled if needed to take up the remaining space; the center is transparent.
-##
-void draw(SkCanvas* canvas) {
-    SkIRect center = { 20, 10, 50, 40 };
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeN32Premul(60, 60));
-    SkCanvas bitCanvas(bitmap);
-    SkPaint paint;
-    SkColor gray = 0xFF000000;
-    int left = 0;
-    for (auto right: { center.fLeft, center.fRight, bitmap.width() } ) {
-        int top = 0;
-        for (auto bottom: { center.fTop, center.fBottom, bitmap.height() } ) {
-            paint.setColor(gray);
-            bitCanvas.drawIRect(SkIRect::MakeLTRB(left, top, right, bottom), paint);
-            gray += 0x001f1f1f;
-            top = bottom;
-        }
-        left = right;
-    }
-    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
-    SkImage* imagePtr = image.get();
-    for (auto dest: { 20, 30, 40, 60, 90 } ) {
-        canvas->drawImageNine(imagePtr, center, SkRect::MakeWH(dest, dest), nullptr);
-        canvas->translate(dest + 4, 0);
-    }
-}
-##
-
-#SeeAlso drawBitmapLattice drawImage drawImageNine Lattice
-
-##
-
-#Subtopic Lattice ##
-
-#Subtopic Draw_Image ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Draw_Text
-#Line # draws text into Canvas ##
-##
-
-#Method void drawSimpleText(const void* text, size_t byteLength, SkTextEncoding encoding,
-                            SkScalar x, SkScalar y, const SkFont& font, const SkPaint& paint)
-#In Draw_Text
-#In Draw
-#Line # draws text at (x, y), using font advance ##
-#Populate
-
-#Example
-#Height 200
-#Description
-The same text is drawn varying Paint_Text_Size and varying
-Matrix.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkFont font;
-    float textSizes[] = { 12, 18, 24, 36 };
-    for (auto size: textSizes ) {
-        font.setSize(size);
-        canvas->drawSimpleText("Aa", 2, kUTF8_SkTextEncoding, 10, 20, font, paint);
-        canvas->translate(0, size * 2);
-    }
-    font.setSize(12);
-    float yPos = 20;
-    for (auto size: textSizes ) {
-        float scale = size / 12.f;
-        canvas->resetMatrix();
-        canvas->translate(100, 0);
-        canvas->scale(scale, scale);
-        canvas->drawSimpleText("Aa", 2, kUTF8_SkTextEncoding,
-                               10 / scale, yPos / scale, font, paint);
-        yPos += size * 2;
-    }
-}
-##
-
-#SeeAlso drawString drawTextBlob
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint)
-#In Draw_Text
-#In Draw
-#Line # draws text with arrays of positions and Paint ##
-Draws Text_Blob blob at (x, y), using Clip, Matrix, and Paint paint.
-
-blob contains Glyphs, their positions, and paint attributes specific to text:
-#font_metrics#.
-
-Paint_Text_Encoding must be set to kGlyphID_SkTextEncoding.
-
-Elements of paint: Anti_Alias, Blend_Mode, Color including Color_Alpha,
-Color_Filter, Paint_Dither, Draw_Looper, Mask_Filter, Path_Effect, Shader, and
-Paint_Style; apply to blob. If Paint contains SkPaint::kStroke_Style:
-Paint_Miter_Limit, Paint_Stroke_Cap, Paint_Stroke_Join, and Paint_Stroke_Width;
-apply to Path created from blob.
-
-#Param  blob     Glyphs, positions, and their paints' text size, typeface, and so on ##
-#Param  x        horizontal offset applied to blob ##
-#Param  y        vertical offset applied to blob ##
-#Param  paint    blend, color, stroking, and so on, used to draw ##
-
-#Example
-#Height 120
-void draw(SkCanvas* canvas) {
-    SkTextBlobBuilder textBlobBuilder;
-    const char bunny[] = "/(^x^)\\";
-    const int len = sizeof(bunny) - 1;
-    uint16_t glyphs[len];
-    SkFont font;
-    font.textToGlyphs(bunny, len, SkTextEncoding::kUTF8, glyphs, len);
-    int runs[] = { 3, 1, 3 };
-    SkPoint textPos = { 20, 100 };
-    int glyphIndex = 0;
-    for (auto runLen : runs) {
-        font.setSize(1 == runLen ? 20 : 50);
-        const SkTextBlobBuilder::RunBuffer& run =
-                textBlobBuilder.allocRun(font, runLen, textPos.fX, textPos.fY);
-        memcpy(run.glyphs, &glyphs[glyphIndex], sizeof(glyphs[0]) * runLen);
-        font.setSize(1 == runLen ? 20 : 50);
-        textPos.fX += font.measureText(&glyphs[glyphIndex], sizeof(glyphs[0]) * runLen,
-                SkTextEncoding::kGlyphID);
-        glyphIndex += runLen;
-    }
-    sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
-    canvas->drawTextBlob(blob.get(), 0, 0, SkPaint());
-}
-##
-
-#SeeAlso drawText
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawTextBlob(const sk_sp<SkTextBlob>& blob, SkScalar x, SkScalar y, const SkPaint& paint)
-
-Draws Text_Blob blob at (x, y), using Clip, Matrix, and Paint paint.
-
-blob contains Glyphs, their positions, and paint attributes specific to text:
-#font_metrics#.
-
-Paint_Text_Encoding must be set to kGlyphID_SkTextEncoding.
-
-Elements of paint: Path_Effect, Mask_Filter, Shader, Color_Filter,
-Image_Filter, and Draw_Looper; apply to blob.
-
-#Param  blob     Glyphs, positions, and their paints' text size, typeface, and so on ##
-#Param  x        horizontal offset applied to blob ##
-#Param  y        vertical offset applied to blob ##
-#Param  paint    blend, color, stroking, and so on, used to draw ##
-
-#Example
-#Height 120
-#Description
-Paint attributes related to text, like text size, have no effect on paint passed to drawTextBlob.
-##
-    void draw(SkCanvas* canvas) {
-        SkTextBlobBuilder textBlobBuilder;
-        SkFont font;
-        font.setSize(50);
-        const SkTextBlobBuilder::RunBuffer& run =
-                textBlobBuilder.allocRun(font, 1, 20, 100);
-        run.glyphs[0] = 20;
-        sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
-        SkPaint paint;
-        paint.setTextSize(10);
-        paint.setColor(SK_ColorBLUE);
-        canvas->drawTextBlob(blob.get(), 0, 0, paint);
-    }
-##
-
-#SeeAlso drawText
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawPicture(const SkPicture* picture)
-#In Draw
-#Line # draws Picture using Clip and Matrix ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPictureRecorder recorder;
-    SkCanvas* recordingCanvas = recorder.beginRecording(50, 50);
-    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xff007f00 } ) {
-        SkPaint paint;
-        paint.setColor(color);
-        recordingCanvas->drawRect({10, 10, 30, 40}, paint);
-        recordingCanvas->translate(10, 10);
-        recordingCanvas->scale(1.2f, 1.4f);
-    }
-    sk_sp<SkPicture> playback = recorder.finishRecordingAsPicture();
-    canvas->drawPicture(playback);
-    canvas->scale(2, 2);
-    canvas->translate(50, 0);
-    canvas->drawPicture(playback);
-}
-##
-
-#SeeAlso drawDrawable SkPicture SkPicture::playback
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawPicture(const sk_sp<SkPicture>& picture)
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPictureRecorder recorder;
-    SkCanvas* recordingCanvas = recorder.beginRecording(50, 50);
-    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xff007f00 } ) {
-        SkPaint paint;
-        paint.setColor(color);
-        recordingCanvas->drawRect({10, 10, 30, 40}, paint);
-        recordingCanvas->translate(10, 10);
-        recordingCanvas->scale(1.2f, 1.4f);
-    }
-    sk_sp<SkPicture> playback = recorder.finishRecordingAsPicture();
-    canvas->drawPicture(playback);
-    canvas->scale(2, 2);
-    canvas->translate(50, 0);
-    canvas->drawPicture(playback);
-}
-##
-
-#SeeAlso drawDrawable SkPicture SkPicture::playback
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawPicture(const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint)
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkPictureRecorder recorder;
-    SkCanvas* recordingCanvas = recorder.beginRecording(50, 50);
-    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xff007f00 } ) {
-        paint.setColor(color);
-        recordingCanvas->drawRect({10, 10, 30, 40}, paint);
-        recordingCanvas->translate(10, 10);
-        recordingCanvas->scale(1.2f, 1.4f);
-    }
-    sk_sp<SkPicture> playback = recorder.finishRecordingAsPicture();
-    const SkPicture* playbackPtr = playback.get();
-    SkMatrix matrix;
-    matrix.reset();
-    for (auto alpha : { 70, 140, 210 } ) {
-    paint.setAlpha(alpha);
-    canvas->drawPicture(playbackPtr, &matrix, &paint);
-    matrix.preTranslate(70, 70);
-    }
-}
-##
-
-#SeeAlso drawDrawable SkPicture SkPicture::playback
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawPicture(const sk_sp<SkPicture>& picture, const SkMatrix* matrix, const SkPaint* paint)
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkPictureRecorder recorder;
-    SkCanvas* recordingCanvas = recorder.beginRecording(50, 50);
-    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xff007f00 } ) {
-        paint.setColor(color);
-        recordingCanvas->drawRect({10, 10, 30, 40}, paint);
-        recordingCanvas->translate(10, 10);
-        recordingCanvas->scale(1.2f, 1.4f);
-    }
-    sk_sp<SkPicture> playback = recorder.finishRecordingAsPicture();
-    SkMatrix matrix;
-    matrix.reset();
-    for (auto alpha : { 70, 140, 210 } ) {
-    paint.setAlpha(alpha);
-    canvas->drawPicture(playback, &matrix, &paint);
-    matrix.preTranslate(70, 70);
-    }
-}
-##
-
-#SeeAlso drawDrawable SkPicture SkPicture::playback
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawVertices(const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint)
-#In Draw
-#Line # draws Vertices, a triangle mesh ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkPoint points[] = { { 0, 0 }, { 250, 0 }, { 100, 100 }, { 0, 250 } };
-    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE, SK_ColorYELLOW, SK_ColorCYAN };
-    auto vertices = SkVertices::MakeCopy(SkVertices::kTriangleFan_VertexMode,
-            SK_ARRAY_COUNT(points), points, nullptr, colors);
-    canvas->drawVertices(vertices.get(), SkBlendMode::kSrc, paint);
-}
-##
-
-#SeeAlso drawPatch drawPicture
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawVertices(const sk_sp<SkVertices>& vertices, SkBlendMode mode, const SkPaint& paint)
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkPoint points[] = { { 0, 0 }, { 250, 0 }, { 100, 100 }, { 0, 250 } };
-    SkPoint texs[] = { { 0, 0 }, { 0, 250 }, { 250, 250 }, { 250, 0 } };
-    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE, SK_ColorYELLOW, SK_ColorCYAN };
-    paint.setShader(SkGradientShader::MakeLinear(points, colors, nullptr, 4,
-            SkShader::kClamp_TileMode));
-    auto vertices = SkVertices::MakeCopy(SkVertices::kTriangleFan_VertexMode,
-            SK_ARRAY_COUNT(points), points, texs, colors);
-    canvas->drawVertices(vertices, SkBlendMode::kDarken, paint);
-}
-##
-
-#SeeAlso drawPatch drawPicture
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawVertices(const SkVertices* vertices, const SkVertices::Bone bones[],
-                          int boneCount, SkBlendMode mode, const SkPaint& paint)
-#Populate
-
-#NoExample
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkPoint points[] = { { 0, 0 }, { 250, 0 }, { 100, 100 }, { 0, 250 } };
-    SkPoint texs[] = { { 0, 0 }, { 0, 250 }, { 250, 250 }, { 250, 0 } };
-    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE, SK_ColorYELLOW, SK_ColorCYAN };
-    SkVertices::BoneIndices boneIndices[] = { { 0, 0, 0, 0 },
-                                              { 1, 0, 0, 0 },
-                                              { 2, 0, 0, 0 },
-                                              { 3, 0, 0, 0 } };
-    SkVertices::BoneWeights boneWeights[] = { { 0.0f, 0.0f, 0.0f, 0.0f },
-                                              { 1.0f, 0.0f, 0.0f, 0.0f },
-                                              { 1.0f, 0.0f, 0.0f, 0.0f },
-                                              { 1.0f, 0.0f, 0.0f, 0.0f } };
-    SkVertices::Bone bones[] = { {{ 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }},
-                                 {{ 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 20.0f }},
-                                 {{ 1.0f, 0.0f, 0.0f, 1.0f, 50.0f, 50.0f }},
-                                 {{ 1.0f, 0.0f, 0.0f, 1.0f, 20.0f, 0.0f }} };
-    paint.setShader(SkGradientShader::MakeLinear(points, colors, nullptr, 4,
-            SkShader::kClamp_TileMode));
-    auto vertices = SkVertices::MakeCopy(SkVertices::kTriangleFan_VertexMode,
-            SK_ARRAY_COUNT(points), points, texs, colors, boneIndices, boneWeights);
-    canvas->drawVertices(vertices.get(), bones, SK_ARRAY_COUNT(bones), SkBlendMode::kDarken, paint);
-}
-##
-
-#SeeAlso drawPatch drawPicture
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawVertices(const sk_sp<SkVertices>& vertices, const SkVertices::Bone bones[],
-                          int boneCount, SkBlendMode mode, const SkPaint& paint)
-#Populate
-
-#NoExample
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkPoint points[] = { { 0, 0 }, { 250, 0 }, { 100, 100 }, { 0, 250 } };
-    SkPoint texs[] = { { 0, 0 }, { 0, 250 }, { 250, 250 }, { 250, 0 } };
-    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE, SK_ColorYELLOW, SK_ColorCYAN };
-    SkVertices::BoneIndices boneIndices[] = { { 0, 0, 0, 0 },
-                                              { 1, 0, 0, 0 },
-                                              { 2, 0, 0, 0 },
-                                              { 3, 0, 0, 0 } };
-    SkVertices::BoneWeights boneWeights[] = { { 0.0f, 0.0f, 0.0f, 0.0f },
-                                              { 1.0f, 0.0f, 0.0f, 0.0f },
-                                              { 1.0f, 0.0f, 0.0f, 0.0f },
-                                              { 1.0f, 0.0f, 0.0f, 0.0f } };
-    SkVertices::Bone bones[] = { {{ 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }},
-                                 {{ 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 20.0f }},
-                                 {{ 1.0f, 0.0f, 0.0f, 1.0f, 50.0f, 50.0f }},
-                                 {{ 1.0f, 0.0f, 0.0f, 1.0f, 20.0f, 0.0f }} };
-    paint.setShader(SkGradientShader::MakeLinear(points, colors, nullptr, 4,
-            SkShader::kClamp_TileMode));
-    auto vertices = SkVertices::MakeCopy(SkVertices::kTriangleFan_VertexMode,
-            SK_ARRAY_COUNT(points), points, texs, colors, boneIndices, boneWeights);
-    canvas->drawVertices(vertices, bones, SK_ARRAY_COUNT(bones), SkBlendMode::kDarken, paint);
-}
-##
-
-#SeeAlso drawPatch drawPicture
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawPatch(const SkPoint cubics[12], const SkColor colors[4],
-                   const SkPoint texCoords[4], SkBlendMode mode, const SkPaint& paint)
-#In Draw
-#Line # draws Coons_Patch ##
-#Populate
-
-#Example
-#Image 5
-void draw(SkCanvas* canvas) {
-    // SkBitmap source = cmbkygk;
-    SkPaint paint;
-    paint.setFilterQuality(kLow_SkFilterQuality);
-    paint.setAntiAlias(true);
-    SkPoint cubics[] = { { 3, 1 },    { 4, 2 }, { 5, 1 },    { 7, 3 },
-                      /* { 7, 3 }, */ { 6, 4 }, { 7, 5 },    { 5, 7 },
-                      /* { 5, 7 }, */ { 4, 6 }, { 3, 7 },    { 1, 5 },
-                      /* { 1, 5 }, */ { 2, 4 }, { 1, 3 }, /* { 3, 1 } */ };
-    SkColor colors[] = { 0xbfff0000, 0xbf0000ff, 0xbfff00ff, 0xbf00ffff };
-    SkPoint texCoords[] = { { -30, -30 }, { 162, -30}, { 162, 162}, { -30, 162} };
-    paint.setShader(SkShader::MakeBitmapShader(source, SkShader::kClamp_TileMode,
-                                                       SkShader::kClamp_TileMode, nullptr));
-    canvas->scale(15, 15);
-    for (auto blend : { SkBlendMode::kSrcOver, SkBlendMode::kModulate, SkBlendMode::kXor } ) {
-        canvas->drawPatch(cubics, colors, texCoords, blend, paint);
-        canvas->translate(4, 4);
-    }
-}
-##
-
-#ToDo can patch use image filter? ##
-#SeeAlso drawVertices drawPicture
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawPatch(const SkPoint cubics[12], const SkColor colors[4],
-                   const SkPoint texCoords[4], const SkPaint& paint)
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkPoint cubics[] = { { 3, 1 },    { 4, 2 }, { 5, 1 },    { 7, 3 },
-                      /* { 7, 3 }, */ { 6, 4 }, { 7, 5 },    { 5, 7 },
-                      /* { 5, 7 }, */ { 4, 6 }, { 3, 7 },    { 1, 5 },
-                      /* { 1, 5 }, */ { 2, 4 }, { 1, 3 }, /* { 3, 1 } */ };
-    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE, SK_ColorYELLOW, SK_ColorCYAN };
-    canvas->scale(30, 30);
-    canvas->drawPatch(cubics, colors, nullptr, paint);
-    SkPoint text[] = { {3,0.9f}, {4,2.5f}, {5,0.9f}, {7.5f,3.2f}, {5.5f,4.2f},
-            {7.5f,5.2f}, {5,7.5f}, {4,5.9f}, {3,7.5f}, {0.5f,5.2f}, {2.5f,4.2f},
-            {0.5f,3.2f} };
-
-    SkFont font(nullptr, 18.f / 30);
-    for (int i = 0; i< 10; ++i) {
-       char digit = '0' + i;
-       canvas->drawSimpleText(&digit, kUTF8_SkTextEncoding, 1, text[i].fX, text[i].fY, font, paint);
-    }
-    canvas->drawString("10", text[10].fX, text[10].fY, font, paint);
-    canvas->drawString("11", text[11].fX, text[11].fY, font, paint);
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, 12, cubics, paint);
-    canvas->drawLine(cubics[11].fX, cubics[11].fY, cubics[0].fX, cubics[0].fY, paint);
-}
-##
-
-#Example
-#Image 6
-void draw(SkCanvas* canvas) {
-    // SkBitmap source = checkerboard;
-    SkPaint paint;
-    paint.setFilterQuality(kLow_SkFilterQuality);
-    paint.setAntiAlias(true);
-    SkPoint cubics[] = { { 3, 1 },    { 4, 2 }, { 5, 1 },    { 7, 3 },
-                      /* { 7, 3 }, */ { 6, 4 }, { 7, 5 },    { 5, 7 },
-                      /* { 5, 7 }, */ { 4, 6 }, { 3, 7 },    { 1, 5 },
-                      /* { 1, 5 }, */ { 2, 4 }, { 1, 3 }, /* { 3, 1 } */ };
-    SkPoint texCoords[] = { { 0, 0 }, { 0, 62}, { 62, 62}, { 62, 0 } };
-    paint.setShader(SkShader::MakeBitmapShader(source, SkShader::kClamp_TileMode,
-                                                       SkShader::kClamp_TileMode, nullptr));
-    canvas->scale(30, 30);
-    canvas->drawPatch(cubics, nullptr, texCoords, paint);
-}
-##
-
-#ToDo can patch use image filter? ##
-#SeeAlso drawVertices drawPicture
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawAtlas(const SkImage* atlas, const SkRSXform xform[], const SkRect tex[],
-                   const SkColor colors[], int count, SkBlendMode mode, const SkRect* cullRect,
-                   const SkPaint* paint)
-#In Draw
-#Line # draws sprites using Clip, Matrix, and Paint ##
-#Populate
-
-#Example
-#Image 3
-void draw(SkCanvas* canvas) {
-  // SkBitmap source = mandrill;
-  SkRSXform xforms[] = { { .5f, 0, 0, 0 }, {0, .5f, 200, 100 } };
-  SkRect tex[] = { { 0, 0, 250, 250 }, { 0, 0, 250, 250 } };
-  SkColor colors[] = { 0x7f55aa00, 0x7f3333bf };
-  const SkImage* imagePtr = image.get();
-  canvas->drawAtlas(imagePtr, xforms, tex, colors, 2, SkBlendMode::kSrcOver, nullptr, nullptr);
-}
-##
-
-#SeeAlso drawBitmap drawImage
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawAtlas(const sk_sp<SkImage>& atlas, const SkRSXform xform[], const SkRect tex[],
-                   const SkColor colors[], int count, SkBlendMode mode, const SkRect* cullRect,
-                   const SkPaint* paint)
-#Populate
-
-#Example
-#Image 3
-void draw(SkCanvas* canvas) {
-  // SkBitmap source = mandrill;
-  SkRSXform xforms[] = { { .5f, 0, 0, 0 }, {0, .5f, 200, 100 } };
-  SkRect tex[] = { { 0, 0, 250, 250 }, { 0, 0, 250, 250 } };
-  SkColor colors[] = { 0x7f55aa00, 0x7f3333bf };
-  SkPaint paint;
-  paint.setAlpha(127);
-  canvas->drawAtlas(image, xforms, tex, colors, 2, SkBlendMode::kPlus, nullptr, &paint);
-}
-##
-
-#ToDo bug in example on cpu side, gpu looks ok ##
-
-#SeeAlso drawBitmap drawImage
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawAtlas(const SkImage* atlas, const SkRSXform xform[], const SkRect tex[], int count,
-                   const SkRect* cullRect, const SkPaint* paint)
-#Populate
-
-#Example
-#Image 3
-void draw(SkCanvas* canvas) {
-  // sk_sp<SkImage> image = mandrill;
-  SkRSXform xforms[] = { { .5f, 0, 0, 0 }, {0, .5f, 200, 100 } };
-  SkRect tex[] = { { 0, 0, 250, 250 }, { 0, 0, 250, 250 } };
-  const SkImage* imagePtr = image.get();
-  canvas->drawAtlas(imagePtr, xforms, tex, 2, nullptr, nullptr);
-}
-##
-
-#SeeAlso drawBitmap drawImage
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawAtlas(const sk_sp<SkImage>& atlas, const SkRSXform xform[], const SkRect tex[],
-                   int count, const SkRect* cullRect, const SkPaint* paint)
-#Populate
-
-#Example
-#Image 3
-void draw(SkCanvas* canvas) {
-  // sk_sp<SkImage> image = mandrill;
-  SkRSXform xforms[] = { { 1, 0, 0, 0 }, {0, 1, 300, 100 } };
-  SkRect tex[] = { { 0, 0, 200, 200 }, { 200, 0, 400, 200 } };
-  canvas->drawAtlas(image, xforms, tex, 2, nullptr, nullptr);
-}
-##
-
-#SeeAlso drawBitmap drawImage
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawDrawable(SkDrawable* drawable, const SkMatrix* matrix = nullptr)
-#In Draw
-#Line # draws Drawable, encapsulated drawing commands ##
-#Populate
-
-#Example
-#Height 100
-#Function
-struct MyDrawable : public SkDrawable {
-    SkRect onGetBounds() override { return SkRect::MakeWH(50, 100);  }
-
-    void onDraw(SkCanvas* canvas) override {
-       SkPath path;
-       path.conicTo(10, 90, 50, 90, 0.9f);
-       SkPaint paint;
-       paint.setColor(SK_ColorBLUE);
-       canvas->drawRect(path.getBounds(), paint);
-       paint.setAntiAlias(true);
-       paint.setColor(SK_ColorWHITE);
-       canvas->drawPath(path, paint);
-    }
-};
-
-#Function ##
-void draw(SkCanvas* canvas) {
-    sk_sp<SkDrawable> drawable(new MyDrawable);
-  SkMatrix matrix;
-  matrix.setTranslate(10, 10);
-  canvas->drawDrawable(drawable.get(), &matrix);
-}
-##
-
-#SeeAlso SkDrawable drawPicture
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawDrawable(SkDrawable* drawable, SkScalar x, SkScalar y)
-#Populate
-
-#Example
-#Height 100
-#Function
-struct MyDrawable : public SkDrawable {
-    SkRect onGetBounds() override { return SkRect::MakeWH(50, 100);  }
-
-    void onDraw(SkCanvas* canvas) override {
-       SkPath path;
-       path.conicTo(10, 90, 50, 90, 0.9f);
-       SkPaint paint;
-       paint.setColor(SK_ColorBLUE);
-       canvas->drawRect(path.getBounds(), paint);
-       paint.setAntiAlias(true);
-       paint.setColor(SK_ColorWHITE);
-       canvas->drawPath(path, paint);
-    }
-};
-
-#Function ##
-void draw(SkCanvas* canvas) {
-    sk_sp<SkDrawable> drawable(new MyDrawable);
-  canvas->drawDrawable(drawable.get(), 10, 10);
-}
-##
-
-#SeeAlso SkDrawable drawPicture
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawAnnotation(const SkRect& rect, const char key[], SkData* value)
-#In Draw
-#In Utility
-#Line # associates a Rect with a key-value pair ##
-#Populate
-
-#Example
-    #Height 1
-    const char text[] = "Click this link!";
-    SkRect bounds;
-    SkPaint paint;
-    SkFont font(nullptr, 40);
-    (void)font.measureText(text, strlen(text), SkTextEncoding::kUTF8, &bounds);
-    const char url[] = "https://www.google.com/";
-    sk_sp<SkData> urlData(SkData::MakeWithCString(url));
-    canvas->drawAnnotation(bounds, "url_key", urlData.get());
-##
-
-#SeeAlso SkPicture SkDocument
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawAnnotation(const SkRect& rect, const char key[], const sk_sp<SkData>& value)
-#Populate
-
-#Example
-#Height 1
-    const char text[] = "Click this link!";
-    SkRect bounds;
-    SkPaint paint;
-    SkFont font(nullptr, 40);
-    (void)font.measureText(text, strlen(text), SkTextEncoding::kUTF8, &bounds);
-    const char url[] = "https://www.google.com/";
-    sk_sp<SkData> urlData(SkData::MakeWithCString(url));
-    canvas->drawAnnotation(bounds, "url_key", urlData.get());
-##
-
-#SeeAlso SkPicture SkDocument
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual bool isClipEmpty() const
-#In Property
-#Line # returns if Clip is empty ##
-#Populate
-
-#Example
-    void draw(SkCanvas* canvas) {
-        SkDebugf("clip is%s empty\n", canvas->isClipEmpty() ? "" : " not");
-        SkPath path;
-        canvas->clipPath(path);
-        SkDebugf("clip is%s empty\n", canvas->isClipEmpty() ? "" : " not");
-    }
-    #StdOut
-        clip is not empty
-        clip is empty
-    ##
-##
-
-#SeeAlso isClipRect getLocalClipBounds getDeviceClipBounds
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual bool isClipRect() const
-#In Property
-#Line # returns if Clip is Rect and not empty ##
-#Populate
-
-#Example
-    void draw(SkCanvas* canvas) {
-        SkDebugf("clip is%s rect\n", canvas->isClipRect() ? "" : " not");
-        canvas->clipRect({0, 0, 0, 0});
-        SkDebugf("clip is%s rect\n", canvas->isClipRect() ? "" : " not");
-    }
-    #StdOut
-        clip is rect
-        clip is not rect
-    ##
-##
-
-#SeeAlso isClipEmpty getLocalClipBounds getDeviceClipBounds
-
-##
-
-#Class SkCanvas ##
-
-#Topic Canvas ##
diff --git a/docs/SkColor4f_Reference.bmh b/docs/SkColor4f_Reference.bmh
deleted file mode 100644
index fa5748b..0000000
--- a/docs/SkColor4f_Reference.bmh
+++ /dev/null
@@ -1,396 +0,0 @@
-#Topic Color4f
-#Alias Color4f_Reference ##
-
-#Struct SkPM4f
-##
-
-#Struct SkRGBA4f
-
-#Code
-#Populate
-##
-
-Each component is stored as a 32-bit single precision floating point float value.
-All values are allowed, but only the range from zero to one is meaningful.
-
-Components are independent of the others if defined with kUnpremul_SkAlphaType;
-fA Alpha is may be greater or smaller than fG green, fB blue, or fR red.
-SkColor4f is shorthand for Unpremultiplied SkRGBA4f.
-
-Components are connected if defined with kPremul_SkAlphaType; 
-fA Alpha is equal to or larger than fG green, fB blue, and fR red. The values
-stored in fG, fB, and fR combine the color component with the Alpha component.
-
-Values smaller than zero or larger than one are allowed. Values out of range
-may be used with Blend_Mode so that the final component is in range.
-
-#Member float  fR
-#Line # red component ##
-Single precision float for red ranges from no red (0.0) to full red (1.0).
-##
-
-#Member float  fG
-#Line # green component ##
-Single precision float for green ranges from no green (0.0) to full green (1.0).
-##
-
-#Member float  fB
-#Line # blue component ##
-Single precision float for blue ranges from no blue (0.0) to full blue (1.0).
-##
-
-#Member float  fA
-#Line # alpha component ##
-Single precision float for Alpha ranges from no Alpha (0.0) to full Alpha (1.0).
-##
-
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator==(const SkRGBA4f& other) const
-#Line # compares SkRGBA4f for equality ##
-
-Compares SkRGBA4f with other, and returns true if all components are equivalent.
-
-#Param other  SkRGBA4f to compare ##
-
-#Return true if SkRGBA4f equals other ##
-
-#Example
-    SkColor4f colorRed = { 1, 0, 0, 1 };

-    SkColor4f colorNamedRed = SkColor4f::FromColor(SK_ColorRED);

-    SkDebugf("colorRed %c= colorNamedRed", colorRed == colorNamedRed ? '=' : '!');

-#StdOut
-colorRed == colorNamedRed
-##
-##
-
-#SeeAlso operator!=(const SkRGBA4f& other) const
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator!=(const SkRGBA4f& other) const
-#Line # compares SkRGBA4f for inequality ##
-
-Compares SkRGBA4f with other, and returns true if all components are not
-equivalent.
-
-#Param other  SkRGBA4f to compare ##
-
-#Return true if SkRGBA4f is not equal to other ##
-
-#Example
-    SkColor4f colorGray = { .5, .5, .5, 1 };

-    SkColor4f colorNamedGray = SkColor4f::FromColor(SK_ColorGRAY);

-    SkDebugf("colorGray %c= colorNamedGray ", colorGray != colorNamedGray ? '!' : '=');

-#StdOut
-colorGray != colorNamedGray
-##
-##
-
-#SeeAlso operator==(const SkRGBA4f& other) const
-
-#Method ##
-
-#Method SkRGBA4f operator*(float scale) const
-#Line # multiplies components by scale ##
-
-Multiplies each component by scale. Does not pin the result.
-
-#Param scale  component multiplier ##
-
-#Return scaled color ##
-
-#NoExample
-##
-
-#SeeAlso SkBlendMode::kMultiply
-
-#Method ##
-
-#Method SkRGBA4f operator*(const SkRGBA4f& scale) const
-
-Multiplies each component by scale component. Does not pin the result.
-
-#Param scale  SkRGBA4f component multipliers ##
-
-#Return scaled color ##
-
-#NoExample
-##
-
-#SeeAlso SkBlendMode::kMultiply
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Property_Functions
-#Line # member values ##
-#Subtopic Property_Functions ##
-
-#Method const float* vec() const
-#In Property_Functions
-#Line # returns array of components ##
-
-Returns SkRGBA4f components as a read-only array.
-
-#Return components as read-only array ##
-
-#Example
-    SkColor4f color = SkColor4f::FromColor(0x884488CC);

-    SkDebugf("red=%g green=%g blue=%g alpha=%g\n", color.fR, color.fG, color.fB, color.fA);

-    const float* array = color.vec();

-    SkDebugf("[0]=%g [1]=%g [2]=%g [3]=%g\n", array[0], array[1], array[2], array[3]);

-#StdOut
-red=0.266667 green=0.533333 blue=0.8 alpha=0.533333

-[0]=0.266667 [1]=0.533333 [2]=0.8 [3]=0.533333
-##
-##
-
-#SeeAlso SkColor4f
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method float* vec()
-#In Property_Functions
-#Line # returns array of components ##
-
-Returns SkRGBA4f components as a writable array.
-
-#Return components as writable array ##
-
-#Example
-    SkColor4f color = SkColor4f::FromColor(0x884488CC);

-    SkDebugf("red=%g green=%g blue=%g alpha=%g\n", color.fR, color.fG, color.fB, color.fA);

-    float* array = color.vec();

-    array[3] = 1;

-    SkDebugf("[0]=%g [1]=%g [2]=%g [3]=%g\n", array[0], array[1], array[2], array[3]);
-#StdOut
-red=0.266667 green=0.533333 blue=0.8 alpha=0.533333

-[0]=0.266667 [1]=0.533333 [2]=0.8 [3]=1
-##
-##
-
-#SeeAlso SkColor4f
-
-#Method ##
-
-#Method float operator[](int index) const
-#Line # returns component by index ##
-
-Returns SkRGBA4f component by index, zero through three. index out of range
-triggers an assert in debug builds.
-
-#Param index component, zero through three ##
-#Return component by index ##
-
-#NoExample
-##
-
-#SeeAlso vec
-
-#Method ##
-
-#Method float& operator[](int index)
-#Line # returns writable component reference ##
-  
-Returns writable component reference by index, zero through three. index out of range
-triggers an assert in debug builds.
-
-#Param index component, zero through three ##
-#Return writable component reference by index ##
-
-#NoExample
-##
-
-#SeeAlso vec
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Utility_Functions
-#Line # less common functions ##
-#Subtopic Utility_Functions ##
-
-#Method bool isOpaque() const
-#In Utility_Functions
-#Line # returns if Alpha component is at maximum ##
-
-Returns true if Alpha component is one. Color has no transparency regardless of
-whether color is Premultiplied or Unpremultiplied. Triggers a debugging assert
-if Alpha not valid.
-
-#Return true if Alpha is one ##
-
-#NoExample
-##
-
-#SeeAlso vec SkColorGetA
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkRGBA4f FromColor(SkColor color)
-#In Utility_Functions
-#Line # sets components from Color ##
-#Populate
-
-#Example
-    uint8_t red = 77, green = 101, blue = 153, alpha = 43;

-    SkColor argb = SkColorSetARGB(alpha, red, green, blue);

-    SkColor4f color4f = SkColor4f::FromColor(argb);

-    SkDebugf("red=%g green=%g blue=%g alpha=%g\n", color4f.fR, color4f.fG, color4f.fB, color4f.fA);

-    SkColor fromColor4f = color4f.toSkColor();

-    SkDebugf("red=%d green=%d blue=%d alpha=%d\n", SkColorGetR(fromColor4f),

-             SkColorGetG(fromColor4f), SkColorGetB(fromColor4f), SkColorGetA(fromColor4f));
-#StdOut
-red=0.301961 green=0.396078 blue=0.6 alpha=0.168627

-red=77 green=101 blue=153 alpha=43
-##
-##
-
-#SeeAlso toSkColor
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkColor toSkColor() const
-#In Utility_Functions
-#Line # returns closest Color ##
-
-Converts to closest SkColor.
-
-#Return closest Color ##
-
-#Example
-    float red = 0.07, green = 0.13, blue = 0.32, alpha = 0.17;

-    SkColor4f color4f = { red, green, blue, alpha };

-    SkColor argb = color4f.toSkColor();

-    SkDebugf("red=%d green=%d blue=%d alpha=%d\n", SkColorGetR(argb),

-             SkColorGetG(argb), SkColorGetB(argb), SkColorGetA(argb));
-    SkColor4f fromSkColor = SkColor4f::FromColor(argb);

-    SkDebugf("red=%g green=%g blue=%g alpha=%g\n", fromSkColor.fR, fromSkColor.fG,

-                                                   fromSkColor.fB, fromSkColor.fA);

-#StdOut

-red=18 green=33 blue=82 alpha=43

-red=0.0705882 green=0.129412 blue=0.321569 alpha=0.168627

-##

-##
-
-#SeeAlso FromColor
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkRGBA4f FromPMColor(SkPMColor)
-#In Utility_Functions
-#Line # converts from Premultiplied Color ##
-
-Converts from Premultiplied integer components to Unpremultiplied float
-components.
-
-#Param SkPMColor  Premultiplied color ##
-
-#Return Unpremultiplied color ##
-
-#NoExample
-##
-
-#SeeAlso FromColor
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkRGBA4f<kPremul_SkAlphaType> premul() const
-#In Utility
-#Line # returns Premultiplied color ##
-
-Returns SkColor4f with all components premultiplied by Alpha.
-
-#Return Premultiplied color ##
-
-#NoExample
-##
-
-#SeeAlso unpremul
-
-#Method ##
-
-#Method SkRGBA4f<kUnpremul_SkAlphaType> unpremul() const
-#In Utility
-#Line # returns Unpremultiplied color ##
-
-Returns SkRGBA4f with all components independent of Alpha.
-
-#Return Unpremultiplied color ##
-
-#NoExample
-##
-
-#SeeAlso premul
-
-#Method ##
-
-#Method uint32_t toBytes_RGBA() const
-#In Utility
-#Line # returns kRGBA_8888_SkColorType color ##
-
-Produces bytes in RGBA order. Component values are not affected by color Alpha.
-
-#Return color ##
-
-#NoExample
-##
-
-#Method ##
-
-#Method static SkRGBA4f FromBytes_RGBA(uint32_t color)
-#In Utility
-#Line # sets kRGBA_8888_SkColorType color ##
-
-Returns from color kRGBA_8888_SkColorType order. Component values are
-not affected by color Alpha.
-
-#Param color  Premultiplied or Unpremultiplied ##
-#Return color ##
-
-#NoExample
-##
-
-#Method ##
-
-#Method SkRGBA4f makeOpaque() const
-#In Utility
-#Line # returns color without transparency ##
-
-Returns color with Alpha set to one.
-
-#Return color ##
-
-#NoExample
-##
-
-#Method ##
-
-#Struct ##
-
-#Typedef SkRGBA4f SkColor4f
-#Line # defines Unpremultiplied Color using floats ##
-
-#Code
-using SkColor4f = SkRGBA4f<kUnpremul_SkAlphaType>;
-##
-
-##
-
-#Topic Color4f ##
diff --git a/docs/SkColor_Reference.bmh b/docs/SkColor_Reference.bmh
deleted file mode 100644
index b32eebe..0000000
--- a/docs/SkColor_Reference.bmh
+++ /dev/null
@@ -1,805 +0,0 @@
-#Topic Color
-#Alias Color_Reference ##
-
-#Code
-#Populate
-##
-
-Color constants can be helpful to write code, documenting the meaning of values
-the represent transparency and color values. The use of Color constants is not
-required.
-
-#Subtopic Functions
-#Line # routines to read, write, and manipulate SkColor ##
-##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Alpha
-#Line # transparency of Color ##
-
-Alpha represents the transparency of Color. Color with Alpha of zero is fully
-transparent. Color with Alpha of 255 is fully opaque. Some, but not all pixel
-formats contain Alpha. Pixels with Alpha may store it as unsigned integers or
-floating point values. Unsigned integer Alpha ranges from zero, fully
-transparent, to all bits set, fully opaque. Floating point Alpha ranges from
-zero, fully transparent, to one, fully opaque.
-
-#Alias Alpha 
-#Substitute alpha
-##
-
-#Typedef uint8_t SkAlpha
-#Line # defines Alpha as eight bits ##
-
-#Code
-#Populate
-##
-
-8-bit type for an alpha value. 255 is 100% opaque, zero is 100% transparent.
-
-#Typedef ##
-
-#Subtopic ##
-
-# ------------------------------------------------------------------------------
-
-#Typedef uint32_t SkColor
-#Line # defines Color as 32 bits ##
-
-#Code
-#Populate
-##
-
-32-bit ARGB Color value, Unpremultiplied. Color components are always in
-a known order. This is different from SkPMColor, which has its bytes in a configuration
-dependent order, to match the format of kBGRA_8888_SkColorType bitmaps. SkColor
-is the type used to specify colors in SkPaint and in gradients.
-
-Color that is Premultiplied has the same component values as Color
-that is Unpremultiplied if Alpha is 255, fully opaque, although may have the
-component values in a different order.
-
-#SeeAlso SkPMColor
-
-#Typedef ##
-
-# ------------------------------------------------------------------------------
-
-#Method static constexpr inline SkColor SkColorSetARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b)
-#In Functions
-#Line # returns Color_Alpha and RGB combined ##
-
-Returns Color value from 8-bit component values. Asserts if SK_DEBUG is defined
-if a, r, g, or b exceed 255. Since Color is Unpremultiplied, a may be smaller
-than the largest of r, g, and b.
-
-#Param a    amount of Alpha, from fully transparent (0) to fully opaque (255) ##
-#Param r    amount of red, from no red (0) to full red (255) ##
-#Param g    amount of green, from no green (0) to full green (255) ##
-#Param b    amount of blue, from no blue (0) to full blue (255) ##
-
-#Return color and alpha, Unpremultiplied ##
-
-#Example
-    canvas->drawColor(SK_ColorRED);
-    canvas->clipRect(SkRect::MakeWH(150, 150));
-    canvas->drawColor(SkColorSetARGB(0x80, 0x00, 0xFF, 0x00));
-    canvas->clipRect(SkRect::MakeWH(75, 75));
-    canvas->drawColor(SkColorSetARGB(0x80, 0x00, 0x00, 0xFF));
-##
-
-#SeeAlso SkColorSetRGB SkPaint::setARGB SkPaint::setColor SkColorSetA
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Define SkColorSetRGB
-#Line # returns opaque Color ##
-
-#Code
-#Populate
-##
-
-Returns Color value from 8-bit component values, with Alpha set
-fully opaque to 255.
-
-#Param r    amount of red, from no red (0) to full red (255) ##
-#Param g    amount of green, from no green (0) to full green (255) ##
-#Param b    amount of blue, from no blue (0) to full blue (255) ##
-
-#Return     color with opaque alpha ##
-
-#Example
-    canvas->drawColor(SK_ColorRED);
-    canvas->clipRect(SkRect::MakeWH(150, 150));
-    canvas->drawColor(SkColorSetRGB(0x00, 0xFF, 0x00));
-    canvas->clipRect(SkRect::MakeWH(75, 75));
-    canvas->drawColor(SkColorSetRGB(0x00, 0x00, 0xFF));
-##
-
-#SeeAlso SkColorSetARGB 
-
-#Define ##
-
-# ------------------------------------------------------------------------------
-
-#Define SkColorGetA
-#Line # returns Alpha component ##
-
-#Code
-#Populate
-##
-
-Returns Alpha byte from Color value.
-
-#Param color SkColor, a 32-bit unsigned int, in 0xAARRGGBB format ##
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setColor(SK_ColorRED);
-    for (int alpha = 255; alpha >= 0; alpha -= 17) {
-        paint.setAlpha(alpha);
-        canvas->drawRect({5, 5, 100, 20}, paint);
-        SkAlpha alphaInPaint = SkColorGetA(paint.getColor());
-        canvas->drawString(std::to_string(alphaInPaint).c_str(), 110, 18, paint);
-        canvas->translate(0, 15);
-    }
-##
-
-#SeeAlso SkPaint::getAlpha
-
-#Define ##
-
-# ------------------------------------------------------------------------------
-
-#Define SkColorGetR
-#Line # returns red component ##
-
-#Code
-#Populate
-##
-
-Returns red component of Color, from zero to 255.
-
-#Param color SkColor, a 32-bit unsigned int, in 0xAARRGGBB format ##
-#Return red byte ##
-
-#Example
-#Image 3
-   canvas->drawBitmap(source, 0, 0);
-   SkPaint bgPaint;
-   bgPaint.setColor(0xafffffff);
-   canvas->drawRect({20, 50, 80, 70}, bgPaint);
-   uint8_t red = SkColorGetR(source.getColor(226, 128));
-   canvas->drawString(std::to_string(red).c_str(), 40, 65, SkPaint());
-   canvas->drawLine(80, 70, 226, 128, SkPaint());
-##
-
-#SeeAlso SkColorGetG SkColorGetB
-
-#Define ##
-
-# ------------------------------------------------------------------------------
-
-#Define SkColorGetG
-#Line # returns green component ##
-
-#Code
-#Populate
-##
-
-Returns green component of Color, from zero to 255.
-
-#Param color SkColor, a 32-bit unsigned int, in 0xAARRGGBB format ##
-#Return green byte ##
-
-#Example
-#Image 3
-   canvas->drawBitmap(source, 0, 0);
-   SkPaint bgPaint;
-   bgPaint.setColor(0xafffffff);
-   canvas->drawRect({20, 50, 80, 70}, bgPaint);
-   uint8_t green = SkColorGetG(source.getColor(57, 192));
-   canvas->drawString(std::to_string(green).c_str(), 40, 65, SkPaint());
-   canvas->drawLine(80, 70, 57, 192, SkPaint());
-##
-
-#SeeAlso SkColorGetR SkColorGetB
-
-#Define ##
-
-# ------------------------------------------------------------------------------
-
-#Define SkColorGetB
-#Line # returns blue component ##
-
-#Code
-#Populate
-##
-
-Returns blue component of Color, from zero to 255.
-
-#Param color SkColor, a 32-bit unsigned int, in 0xAARRGGBB format ##
-#Return blue byte ##
-
-#Example
-#Image 3
-   canvas->drawBitmap(source, 0, 0);
-   SkPaint bgPaint;
-   bgPaint.setColor(0xafffffff);
-   canvas->drawRect({20, 50, 80, 70}, bgPaint);
-   uint8_t blue = SkColorGetB(source.getColor(168, 170));
-   canvas->drawString(std::to_string(blue).c_str(), 40, 65, SkPaint());
-   canvas->drawLine(80, 70, 168, 170, SkPaint());
-##
-
-#SeeAlso SkColorGetR SkColorGetG
-
-#Define ##
-
-# ------------------------------------------------------------------------------
-
-#Method static constexpr inline SkColor SkColorSetA(SkColor c, U8CPU a)
-#In Functions
-#Line # returns Color with transparency ##
-
-Returns Unpremultiplied Color with red, blue, and green set from c; and alpha set
-from a. Alpha component of c is ignored and is replaced by a in result.
-
-#Param c  packed RGB, eight bits per component ##
-#Param a  Alpha: transparent at zero, fully opaque at 255 ##
-
-#Return Color with transparency ##
-
-#Example
-#Image 3
-   canvas->drawBitmap(source, 0, 0);
-   for (int y = 0; y < 256; y += 16) {
-      for (int x = 0; x < 256; x += 16) {
-        SkColor color = source.getColor(x + 8, y + 8);
-        SkPaint paint;
-        paint.setColor(SkColorSetA(color, x + y));
-        canvas->drawRect(SkRect::MakeXYWH(x, y, 16, 16), paint);
-     }
-   }
-##
-
-#SeeAlso SkColorSetARGB
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Alpha_Constants
-#In Constant
-#Line # constants for Alpha ##
-#Filter SK_Alpha
-
-#Code
-#Populate
-##
-
-Alpha constants are conveniences to represent fully transparent and fully
-opaque colors and masks. Their use is not required.
-
-#Const SK_AlphaTRANSPARENT 0x00
-#Line # fully transparent SkAlpha ##
-Represents fully transparent SkAlpha value. SkAlpha ranges from zero,
-fully transparent; to 255, fully opaque.
-##
-#Const SK_AlphaOPAQUE 0xFF
-#Line # fully opaque SkAlpha ##
-Represents fully opaque SkAlpha value. SkAlpha ranges from zero,
-fully transparent; to 255, fully opaque.
-##
-
-#Example
-#Image 1
-#Height 128
-#Description
-Color the parts of the bitmap red if they mostly contain transparent pixels.
-##
-    std::vector<int32_t> srcPixels;
-    srcPixels.resize(source.height() * source.rowBytes());
-    SkPixmap pixmap(SkImageInfo::MakeN32Premul(source.width(), source.height()),
-                    &srcPixels.front(), source.rowBytes());
-    source.readPixels(pixmap, 0, 0);
-    for (int y = 0; y < 16; ++y) {
-        for (int x = 0; x < 16; ++x) {
-            int32_t* blockStart = &srcPixels.front() + y * source.width() * 16 + x * 16;
-            size_t transparentCount = 0;
-            for (int fillY = 0; fillY < source.height() / 16; ++fillY) {
-                for (int fillX = 0; fillX < source.width() / 16; ++fillX) {
-                    const SkColor color = SkUnPreMultiply::PMColorToColor(blockStart[fillX]);
-                    transparentCount += SkColorGetA(color) == SK_AlphaTRANSPARENT;
-                }
-                blockStart += source.width();
-            }
-            if (transparentCount > 200) {
-                blockStart = &srcPixels.front() + y * source.width() * 16 + x * 16;
-                for (int fillY = 0; fillY < source.height() / 16; ++fillY) {
-                    for (int fillX = 0; fillX < source.width() / 16; ++fillX) {
-                        blockStart[fillX] = SK_ColorRED;
-                    }
-                    blockStart += source.width();
-                }
-            }
-        }
-    }
-    SkBitmap bitmap;
-    bitmap.installPixels(pixmap);
-    canvas->drawBitmap(bitmap, 0, 0);
-##
-
-
-# ------------------------------------------------------------------------------
-
-#Example
-#Image 1
-#Height 128
-#Description
-Color the parts of the bitmap green if they contain fully opaque pixels.
-##
-    std::vector<int32_t> srcPixels;
-    srcPixels.resize(source.height() * source.rowBytes());
-    SkPixmap pixmap(SkImageInfo::MakeN32Premul(source.width(), source.height()),
-                    &srcPixels.front(), source.rowBytes());
-    source.readPixels(pixmap, 0, 0);
-    for (int y = 0; y < source.height(); ++y) {
-        for (int x = 0; x < source.width(); ++x) {
-            SkPMColor pixel = srcPixels[y * source.width() + x];
-            const SkColor color = SkUnPreMultiply::PMColorToColor(pixel);
-            if (SkColorGetA(color) == SK_AlphaOPAQUE) {
-                srcPixels[y * source.width() + x] = SK_ColorGREEN;
-            }
-        }
-    }
-    SkBitmap bitmap;
-    bitmap.installPixels(pixmap);
-    canvas->drawBitmap(bitmap, 0, 0);
-##
-
-#SeeAlso SkAlpha SK_ColorTRANSPARENT SK_ColorBLACK
-
-#Subtopic Alpha_Constants ##
-
-#Subtopic Color_Constants
-#In Constant
-#Line # constants for Color ##
-#Filter SK_Color
-
-#Code
-#Populate
-##
-
-Color names are provided as conveniences, but are not otherwise special.
-The values chosen for names may not be the same as values used by
-SVG, HTML, CSS, or colors named by a platform.
-
-#Const SK_ColorTRANSPARENT 0x00000000
-#Line # transparent Color ##
-    Represents fully transparent SkColor. May be used to initialize a destination
-    containing a mask or a non-rectangular image.
-##
-#Const SK_ColorBLACK 0xFF000000
-#Line # black Color ##
-    Represents fully opaque black.
-##
-#Const SK_ColorDKGRAY 0xFF444444
-#Line # dark gray Color ##
-    Represents fully opaque dark gray.
-    Note that SVG_darkgray is equivalent to 0xFFA9A9A9.
-##
-#Const SK_ColorGRAY 0xFF888888
-#Line # gray Color ##
-    Represents fully opaque gray.
-    Note that HTML_Gray is equivalent to 0xFF808080.
-##
-#Const SK_ColorLTGRAY 0xFFCCCCCC
-#Line # light gray Color ##
-    Represents fully opaque light gray. HTML_Silver is equivalent to 0xFFC0C0C0.
-    Note that SVG_lightgray is equivalent to 0xFFD3D3D3.
-##
-#Const SK_ColorWHITE 0xFFFFFFFF
-#Line # white Color ##
-    Represents fully opaque white.
-##
-#Const SK_ColorRED 0xFFFF0000
-#Line # red Color ##
-    Represents fully opaque red.
-##
-#Const SK_ColorGREEN 0xFF00FF00
-#Line # green Color ##
-    Represents fully opaque green. HTML_Lime is equivalent.
-    Note that HTML_Green is equivalent to 0xFF008000.
-##
-#Const SK_ColorBLUE 0xFF0000FF
-#Line # blue Color ##
-    Represents fully opaque blue.
-##
-#Const SK_ColorYELLOW 0xFFFFFF00
-#Line # yellow Color ##
-    Represents fully opaque yellow.
-##
-#Const SK_ColorCYAN 0xFF00FFFF
-#Line # cyan Color ##
-    Represents fully opaque cyan. HTML_Aqua is equivalent.
-##
-#Const SK_ColorMAGENTA 0xFFFF00FF
-#Line # magenta Color ##
-    Represents fully opaque magenta. HTML_Fuchsia is equivalent.
-##
-
-#Example
-###$
-$Function
-#define SKIA_COLOR_PAIR(name) "SK_Color" #name, SK_Color##name
-$$
-void draw(SkCanvas* canvas) {
-    struct ColorCompare {
-        const char* fSVGName;
-        SkColor fSVGColor;
-        const char* fSkiaName;
-        SkColor fSkiaColor;
-    } colorCompare[] = {  // see https://www.w3.org/TR/SVG/types.html#ColorKeywords
-        {"black",     SkColorSetRGB(  0,   0,   0),    SKIA_COLOR_PAIR(BLACK) },
-        {"darkgray",  SkColorSetRGB(169, 169, 169),    SKIA_COLOR_PAIR(DKGRAY) },
-        {"gray",      SkColorSetRGB(128, 128, 128),    SKIA_COLOR_PAIR(GRAY) },
-        {"lightgray", SkColorSetRGB(211, 211, 211),    SKIA_COLOR_PAIR(LTGRAY) },
-        {"white",     SkColorSetRGB(255, 255, 255),    SKIA_COLOR_PAIR(WHITE) },
-        {"red",       SkColorSetRGB(255,   0,   0),    SKIA_COLOR_PAIR(RED) },
-        {"green",     SkColorSetRGB(  0, 128,   0),    SKIA_COLOR_PAIR(GREEN) },
-        {"blue",      SkColorSetRGB(  0,   0, 255),    SKIA_COLOR_PAIR(BLUE) },
-        {"yellow",    SkColorSetRGB(255, 255,   0),    SKIA_COLOR_PAIR(YELLOW) },
-        {"aqua",      SkColorSetRGB(  0, 255, 255),    SKIA_COLOR_PAIR(CYAN) },
-        {"fuchsia",   SkColorSetRGB(255,   0, 255),    SKIA_COLOR_PAIR(MAGENTA) },
-    };
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setTextSize(14);
-    for (auto compare : colorCompare) {
-        paint.setStyle(SkPaint::kFill_Style);
-        paint.setColor(compare.fSVGColor);
-        canvas->drawRect({5, 5, 15, 15}, paint);
-        paint.setColor(SK_ColorBLACK);
-        canvas->drawString(compare.fSVGName, 20, 16, paint);
-        paint.setColor(compare.fSkiaColor);
-        canvas->drawRect({105, 5, 115, 15}, paint);
-        paint.setColor(SK_ColorBLACK);
-        canvas->drawString(compare.fSkiaName, 120, 16, paint);
-        paint.setStyle(SkPaint::kStroke_Style);
-        canvas->drawRect({5, 5, 15, 15}, paint);
-        canvas->drawRect({105, 5, 115, 15}, paint);
-        canvas->translate(0, 20);
-    }
-}
-$$$#
-##
-
-#Example
-#Description
-SK_ColorTRANSPARENT sets Color Alpha and components to zero.
-##
-#Image 3
-    std::vector<uint32_t> srcPixels;
-    constexpr int width = 256;
-    constexpr int height = 256;
-    srcPixels.resize(width * height);
-    SkImageInfo imageInfo = SkImageInfo::MakeN32Premul(width, height);
-    SkPixmap pixmap(imageInfo, &srcPixels.front(), imageInfo.minRowBytes());
-    pixmap.erase(SK_ColorTRANSPARENT);
-    pixmap.erase(SK_ColorRED, { 24, 24, 192, 192 } );
-    pixmap.erase(SK_ColorTRANSPARENT, { 48, 48, 168, 168 } );
-    SkBitmap bitmap;
-    bitmap.installPixels(pixmap);
-    canvas->drawBitmap(bitmap, 0, 0);
-    canvas->drawBitmap(bitmap, 48, 48);
-##
-
-#Example
-#Description
-SK_ColorBLACK sets Color Alpha to one and components to zero.
-##
-    std::vector<uint32_t> srcPixels;
-    constexpr int width = 256;
-    constexpr int height = 256;
-    srcPixels.resize(width * height);
-    SkImageInfo imageInfo = SkImageInfo::MakeN32Premul(width, height);
-    SkPixmap pixmap(imageInfo, &srcPixels.front(), imageInfo.minRowBytes());
-    pixmap.erase(SK_ColorTRANSPARENT);
-    pixmap.erase(SK_ColorRED, { 24, 24, 192, 192 } );
-    pixmap.erase(SK_ColorBLACK, { 48, 48, 168, 168 } );
-    SkBitmap bitmap;
-    bitmap.installPixels(pixmap);
-    canvas->drawBitmap(bitmap, 0, 0);
-    canvas->drawBitmap(bitmap, 48, 48);
-##
-
-#Example
-#Description
-SK_ColorWHITE sets Color Alpha and components to one.
-##
-    std::vector<uint32_t> srcPixels;
-    constexpr int width = 256;
-    constexpr int height = 256;
-    srcPixels.resize(width * height);
-    SkImageInfo imageInfo = SkImageInfo::MakeN32Premul(width, height);
-    SkPixmap pixmap(imageInfo, &srcPixels.front(), imageInfo.minRowBytes());
-    pixmap.erase(SK_ColorTRANSPARENT);
-    pixmap.erase(SK_ColorRED, { 24, 24, 192, 192 } );
-    pixmap.erase(SK_ColorWHITE, { 48, 48, 168, 168 } );
-    SkBitmap bitmap;
-    bitmap.installPixels(pixmap);
-    canvas->drawBitmap(bitmap, 0, 0);
-    canvas->drawBitmap(bitmap, 48, 48);
-##
-
-#SeeAlso SK_ColorTRANSPARENT SkCanvas::clear SK_AlphaOPAQUE
-
-#Subtopic Color_Constants ##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic HSV
-#Line # hue saturation value color representation ##
-
-#Subtopic Hue
-Hue represents an angle, in degrees, on a color wheel. Hue has a positive value
-modulo 360, where zero degrees is red.
-##
-
-#Subtopic Saturation
-Saturation represents the intensity of the color. Saturation varies from zero,
-with no Hue contribution; to one, with full Hue contribution.
-##
-
-#Subtopic Value
-Value represents the lightness of the color. Value varies from zero, black; to
-one, full brightness.
-##
-
-#Method void SkRGBToHSV(U8CPU red, U8CPU green, U8CPU blue, SkScalar hsv[3])
-#In Functions
-#Line # converts RGB to HSV ##
-
-Converts RGB to its HSV components.
-hsv[0] contains HSV_Hue, a value from zero to less than 360.
-hsv[1] contains HSV_Saturation, a value from zero to one.
-hsv[2] contains HSV_Value, a value from zero to one.
-
-#Param red  red component value from zero to 255 ##
-#Param green  green component value from zero to 255 ##
-#Param blue  blue component value from zero to 255 ##
-#Param hsv  three element array which holds the resulting HSV components
-##
-
-#Example
-#Image 3
-   canvas->drawBitmap(source, 0, 0);
-   SkPaint bgPaint;
-   bgPaint.setColor(0xafffffff);
-   canvas->drawRect({20, 30, 110, 90}, bgPaint);
-   SkScalar hsv[3];
-   SkColor c = source.getColor(226, 128);
-   SkRGBToHSV(SkColorGetR(c), SkColorGetG(c), SkColorGetB(c), hsv);
-   canvas->drawString(("h: " + std::to_string(hsv[0]).substr(0, 6)).c_str(), 27, 45, SkPaint());
-   canvas->drawString(("s: " + std::to_string(hsv[1]).substr(0, 6)).c_str(), 27, 65, SkPaint());
-   canvas->drawString(("v: " + std::to_string(hsv[2]).substr(0, 6)).c_str(), 27, 85, SkPaint());
-   canvas->drawLine(110, 90, 226, 128, SkPaint());
-##
-
-#SeeAlso SkColorToHSV SkHSVToColor
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static void SkColorToHSV(SkColor color, SkScalar hsv[3])
-#In Functions
-#Line # converts RGB to HSV ##
-
-Converts ARGB to its HSV components. Alpha in ARGB is ignored.
-hsv[0] contains HSV_Hue, and is assigned a value from zero to less than 360.
-hsv[1] contains HSV_Saturation, a value from zero to one.
-hsv[2] contains HSV_Value, a value from zero to one.
-
-#Param color  ARGB color to convert
-##
-#Param hsv  three element array which holds the resulting HSV components
-##
-
-#Example
-#Image 3
-   canvas->drawBitmap(source, 0, 0);
-   for (int y = 0; y < 256; ++y) {
-      for (int x = 0; x < 256; ++x) {
-        SkScalar hsv[3];
-        SkColorToHSV(source.getColor(x, y), hsv);
-        hsv[1] = 1 - hsv[1];
-        SkPaint paint;
-        paint.setColor(SkHSVToColor(hsv));
-        canvas->drawRect(SkRect::MakeXYWH(x, y, 1, 1), paint);
-     }
-   }
-##
-
-#SeeAlso SkRGBToHSV SkHSVToColor
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkColor SkHSVToColor(U8CPU alpha, const SkScalar hsv[3])
-#In Functions
-#Line # converts HSV with Alpha to RGB ##
-
-Converts HSV components to an ARGB color. Alpha is passed through unchanged.
-hsv[0] represents HSV_Hue, an angle from zero to less than 360.
-hsv[1] represents HSV_Saturation, and varies from zero to one.
-hsv[2] represents HSV_Value, and varies from zero to one.
-
-Out of range hsv values are pinned.
-
-#Param alpha  Alpha component of the returned ARGB color
-##
-#Param hsv  three element array which holds the input HSV components
-##
-
-#Return  ARGB equivalent to HSV
-##
-
-#Example
-#Image 3
-   canvas->drawBitmap(source, 0, 0);
-   for (int y = 0; y < 256; ++y) {
-      for (int x = 0; x < 256; ++x) {
-        SkColor color = source.getColor(x, y);
-        SkScalar hsv[3];
-        SkColorToHSV(color, hsv);
-        hsv[0] = hsv[0] + 90 >= 360 ? hsv[0] - 270 : hsv[0] + 90;
-        SkPaint paint;
-        paint.setColor(SkHSVToColor(x + y, hsv));
-        canvas->drawRect(SkRect::MakeXYWH(x, y, 1, 1), paint);
-     }
-   }
-##
-
-#SeeAlso SkColorToHSV SkRGBToHSV
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkColor SkHSVToColor(const SkScalar hsv[3])
-#In Functions
-#Line # converts HSV to RGB ##
-
-Converts HSV components to an ARGB color. Alpha is set to 255.
-hsv[0] represents HSV_Hue, an angle from zero to less than 360.
-hsv[1] represents HSV_Saturation, and varies from zero to one.
-hsv[2] represents HSV_Value, and varies from zero to one.
-
-Out of range hsv values are pinned.
-
-#Param hsv  three element array which holds the input HSV components
-##
-
-#Return  RGB equivalent to HSV
-##
-
-#Example
-#Image 3
-   canvas->drawBitmap(source, 0, 0);
-   for (int y = 0; y < 256; ++y) {
-      for (int x = 0; x < 256; ++x) {
-        SkColor color = source.getColor(x, y);
-        SkScalar hsv[3];
-        SkColorToHSV(color, hsv);
-        hsv[0] = hsv[0] + 90 >= 360 ? hsv[0] - 270 : hsv[0] + 90;
-        SkPaint paint;
-        paint.setColor(SkHSVToColor(hsv));
-        canvas->drawRect(SkRect::MakeXYWH(x, y, 1, 1), paint);
-     }
-   }
-##
-
-#SeeAlso SkColorToHSV SkRGBToHSV
-
-#Method ##
-
-#Subtopic HSV ##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic PM_Color
-#Line # color components premultiplied by Alpha ##
-
-#Typedef uint32_t SkPMColor
-#Line # defines Premultiplied Color as 32 bits ##
-
-#Code
-#Populate
-##
-
-32-bit ARGB color value, Premultiplied. The byte order for this value is
-configuration dependent, matching the format of kBGRA_8888_SkColorType bitmaps.
-This is different from SkColor, which is Unpremultiplied, and is always in the
-same byte order.
-
-#Typedef ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPMColor SkPreMultiplyARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b)
-#In Functions
-#Line # converts Unpremultiplied ARGB to Premultiplied PM_Color ##
-
-Returns a SkPMColor value from Unpremultiplied 8-bit component values.
-
-#Param a    amount of Alpha, from fully transparent (0) to fully opaque (255) ##
-#Param r    amount of red, from no red (0) to full red (255) ##
-#Param g    amount of green, from no green (0) to full green (255) ##
-#Param b    amount of blue, from no blue (0) to full blue (255) ##
-
-#Return Premultiplied Color ##
-
-#Example
-#Height 128
-#Width 300
-    SkPMColor premultiplied = SkPreMultiplyARGB(160, 128, 160, 192);
-    canvas->drawString("Unpremultiplied:", 20, 20, SkPaint());
-    canvas->drawString("alpha=160 red=128 green=160 blue=192", 20, 40, SkPaint());
-    canvas->drawString("Premultiplied:", 20, 80, SkPaint());
-    std::string str = "alpha=" + std::to_string(SkColorGetA(premultiplied));
-    str += " red=" + std::to_string(SkColorGetR(premultiplied));
-    str += " green=" + std::to_string(SkColorGetG(premultiplied));
-    str += " blue=" + std::to_string(SkColorGetB(premultiplied));
-    canvas->drawString(str.c_str(), 20, 100, SkPaint());
-##
-
-#SeeAlso SkPreMultiplyColor
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPMColor SkPreMultiplyColor(SkColor c)
-#In Functions
-#Line # converts Unpremultiplied Color to Premultiplied PM_Color ##
-
-Returns PM_Color closest to Color c. Multiplies c RGB components by the c Alpha,
-and arranges the bytes to match the format of kN32_SkColorType.
-
-#Param c  Unpremultiplied ARGB Color  ##
-
-#Return Premultiplied Color ##
-
-#Example
-#Height 128
-#Width 300
-    SkColor unpremultiplied = SkColorSetARGB(160, 128, 160, 192);
-    SkPMColor premultiplied = SkPreMultiplyColor(unpremultiplied);
-    canvas->drawString("Unpremultiplied:", 20, 20, SkPaint());
-    std::string str = "alpha=" + std::to_string(SkColorGetA(unpremultiplied));
-    str += " red=" + std::to_string(SkColorGetR(unpremultiplied));
-    str += " green=" + std::to_string(SkColorGetG(unpremultiplied));
-    str += " blue=" + std::to_string(SkColorGetB(unpremultiplied));
-    canvas->drawString(str.c_str(), 20, 40, SkPaint());
-    canvas->drawString("Premultiplied:", 20, 80, SkPaint());
-    str = "alpha=" + std::to_string(SkColorGetA(premultiplied));
-    str += " red=" + std::to_string(SkColorGetR(premultiplied));
-    str += " green=" + std::to_string(SkColorGetG(premultiplied));
-    str += " blue=" + std::to_string(SkColorGetB(premultiplied));
-    canvas->drawString(str.c_str(), 20, 100, SkPaint());
-##
-
-#SeeAlso SkPreMultiplyARGB
-
-#Method ##
-
-#Subtopic PM_Color ##
-
-#Topic Color ##
diff --git a/docs/SkDynamicMemoryWStream_Reference.bmh b/docs/SkDynamicMemoryWStream_Reference.bmh
deleted file mode 100644
index 13c1ec8..0000000
--- a/docs/SkDynamicMemoryWStream_Reference.bmh
+++ /dev/null
@@ -1,244 +0,0 @@
-#Topic DynamicMemoryWStream
-#Alias DynamicMemoryWStream_Reference ##
-
-#Class SkDynamicMemoryWStream
-
-#Code
-#Populate
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkDynamicMemoryWStream()
-#In Constructors
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method ~SkDynamicMemoryWStream() override
-#In Constructors
-#Line # incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool write(const void* buffer, size_t size) override
-#In incomplete
-#Line # incomplete ##
-
-#Param buffer  incomplete ##
-#Param size  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t bytesWritten() const override
-#In incomplete
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool read(void* buffer, size_t offset, size_t size)
-#In incomplete
-#Line # incomplete ##
-
-#Param buffer  incomplete ##
-#Param offset  incomplete ##
-#Param size  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void copyTo(void* dst) const
-#In incomplete
-#Line # incomplete ##
-
-Copies bytes read to dst.
-
-#Param dst  incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool writeToStream(SkWStream* dst) const
-#In incomplete
-#Line # incomplete ##
-
-#Param dst  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void copyToAndReset(void* dst)
-#In incomplete
-#Line # incomplete ##
-
-Copies bytes read to dst, and resets stream to start.
-Internally, frees memory as it is copied, reducing total memory
-use on large streams.
-
-#Param dst  incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool writeToAndReset(SkWStream* dst)
-#In incomplete
-#Line # incomplete ##
-
-Writes bytes read to dst, and resets stream to start.
-Internally, frees memory as it is copied, reducing total memory
-use on large streams.
-
-Stream is reset: data memory is released and stream length is set to zero;
-regardless of whether the write was successful.
-
-#Param dst  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkData> detachAsData()
-#In incomplete
-#Line # incomplete ##
-
-Return the contents as SkData, and then reset the stream.
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method std::unique_ptr<SkStreamAsset> detachAsStream()
-#In incomplete
-#Line # incomplete ##
-
-Reset, returning a reader stream with the current content.
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void reset()
-#In incomplete
-#Line # incomplete ##
-
-Reset the stream to its original, empty, state.
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void padToAlign4()
-#In incomplete
-#Line # incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-#Class SkDynamicMemoryWStream ##
-
-#Topic DynamicMemoryWStream ##
diff --git a/docs/SkFILEStream_Reference.bmh b/docs/SkFILEStream_Reference.bmh
deleted file mode 100644
index f847547..0000000
--- a/docs/SkFILEStream_Reference.bmh
+++ /dev/null
@@ -1,276 +0,0 @@
-#Topic FILEStream
-#Alias FILEStream_Reference ##
-
-#Class SkFILEStream
-
-A stream that wraps a C FILE* file stream. */
-
-#Code
-#Populate
-##
-
-# ------------------------------------------------------------------------------
-
-#Method explicit SkFILEStream(const char path[] = nullptr)
-#In Constructors
-#Line # incomplete ##
-
-Initializes Stream by reading data contained by path.
-File descriptor is opened here and is closed when SkFILEStream
-destructor is invoked.
-
-#Param path  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method explicit SkFILEStream(FILE* file)
-#In Constructors
-#Line # incomplete ##
-
-Initialize the stream with an existing C_FILE stream.
-The current position of the C_FILE stream will be considered the
-beginning of the SkFILEStream.
-The C_FILE stream is closed when SkFILEStream destructor is invoked.
-
-#Param file  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method ~SkFILEStream() override
-#In Constructors
-#Line # incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static std::unique_ptr<SkFILEStream> Make(const char path[])
-#In incomplete
-#Line # incomplete ##
-
-#Param path  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isValid() const
-#In incomplete
-#Line # incomplete ##
-
-Returns true if the current path could be opened.
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void close()
-#In incomplete
-#Line # incomplete ##
-
-Close this SkFILEStream.
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t read(void* buffer, size_t size) override
-#In incomplete
-#Line # incomplete ##
-
-#Param buffer  incomplete ##
-#Param size  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isAtEnd() const override
-#In incomplete
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool rewind() override
-#In incomplete
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method std::unique_ptr<SkStreamAsset> duplicate() const
-#In incomplete
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t getPosition() const override
-#In incomplete
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool seek(size_t position) override
-#In incomplete
-#Line # incomplete ##
-
-#Param position  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool move(long offset) override
-#In incomplete
-#Line # incomplete ##
-
-#Param offset  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method std::unique_ptr<SkStreamAsset> fork() const
-#In incomplete
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t getLength() const override
-#In incomplete
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-#Class SkFILEStream ##
-
-#Topic FILEStream ##
diff --git a/docs/SkFILEWStream_Reference.bmh b/docs/SkFILEWStream_Reference.bmh
deleted file mode 100644
index d7a1fed..0000000
--- a/docs/SkFILEWStream_Reference.bmh
+++ /dev/null
@@ -1,127 +0,0 @@
-#Topic FILEWStream
-#Alias FILEWStream_Reference ##
-
-#Class SkFILEWStream
-
-////////////////////////////////////////////////////////////////////////////////////////////
-
-#Code
-#Populate
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkFILEWStream(const char path[])
-#In Constructors
-#Line # incomplete ##
-
-#Param path  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method ~SkFILEWStream() override
-#In Constructors
-#Line # incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isValid() const
-#In incomplete
-#Line # incomplete ##
-
-Returns true if the current path could be opened.
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool write(const void* buffer, size_t size) override
-#In incomplete
-#Line # incomplete ##
-
-#Param buffer  incomplete ##
-#Param size  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void flush() override
-#In incomplete
-#Line # incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void fsync()
-#In incomplete
-#Line # incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t bytesWritten() const override
-#In incomplete
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-#Class SkFILEWStream ##
-
-#Topic FILEWStream ##
diff --git a/docs/SkFont_Reference.bmh b/docs/SkFont_Reference.bmh
deleted file mode 100644
index 6e4b64a..0000000
--- a/docs/SkFont_Reference.bmh
+++ /dev/null
@@ -1,995 +0,0 @@
-#Topic Font
-#Alias Font_Reference ##
-
-#Code
-#Populate
-##
-
-#PhraseDef font_metrics
-Typeface, Font_Size, Font_Scale_X,
-Font_Skew_X, Font_Hinting, Paint_Anti_Alias, Font_Embolden, Font_Force_Hinting,
-Font_Embedded_Bitmaps, Font_Hinting_Spacing, Font_Anti_Alias, Font_Linear,
-and Font_Subpixel
-##
-
-#Subtopic Advance
-# incomplete, should probably be in overview, not reference
-##
-#Subtopic Engine
-# incomplete, should probably be in overview, not reference
-##
-
-#Subtopic Size
-#Line # overall height in points ##
-Font_Size adjusts the overall text size in points.
-Font_Size can be set to any positive value or zero.
-Font_Size defaults to 12.
-Font_Size
-##
-
-#Subtopic Scale_X
-#Line # text horizontal scale ##
-Font_Scale_X adjusts the text horizontal scale.
-Text scaling approximates condensed and expanded type faces when the actual face
-is not available.
-Font_Scale_X can be set to any value.
-Font_Scale_X defaults to 1.
-##
-
-#Subtopic Skew_X
-#Line # text horizontal slant ##
-Font_Skew_X adjusts the text horizontal slant.
-Text skewing approximates italic and oblique type faces when the actual face
-is not available.
-Font_Skew_X can be set to any value.
-Font_Skew_X defaults to 0.
-##
-
-#Subtopic Embolden
-#Line # approximate font styles ##
-
-Font_Embolden approximates the bold font style accompanying a normal font when a bold font face
-is not available. Skia does not provide font substitution; it is up to the client to find the
-bold font face using the platform Font_Manager.
-
-Use Font_Skew_X to approximate an italic font style when the italic font face
-is not available.
-
-A FreeType based port may define SK_USE_FREETYPE_EMBOLDEN at compile time to direct
-the font engine to create the bold Glyphs. Otherwise, the extra bold is computed
-by increasing the stroke width and setting the SkPaint::Style to
-SkPaint::kStrokeAndFill_Style as needed.
-
-Font_Embolden is disabled by default.
-#Subtopic Embolden ##
-
-#Subtopic Hinting_Spacing
-#Line # glyph spacing affected by hinting ##
-
-If Hinting is set to SkFontHinting::kFull, Hinting_Spacing adjusts the character
-spacing by the difference of the hinted and unhinted Left_Side_Bearing and
-Right_Side_Bearing. Hinting_Spacing only applies to platforms that use
-FreeType as their Font_Engine.
-
-Hinting_Spacing is not related to text kerning, where the space between
-a specific pair of characters is adjusted using data in the font kerning tables.
-#Subtopic Hinting_Spacing ##
-
-#Subtopic Linear
-#Line # selects text rendering as Glyph or Path ##
-Font_Linear selects whether text is rendered as a Glyph or as a Path.
-If Font_Linear is set, it has the same effect as setting Hinting to SkFontHinting::kNormal.
-If Font_Linear is clear, it is the same as setting Hinting to SkFontHinting::kNone.
-#Subtopic Linear ##
-
-#Subtopic Subpixel
-#Line # uses pixel transparency to represent fractional offset ##
-#Substitute sub-pixel
-Font_Subpixel uses the pixel transparency to represent a fractional offset.
-As the opaqueness of the color increases, the edge of the glyph appears to move
-towards the outside of the pixel.
-#Subtopic Subpixel ##
-
-#Subtopic Anti_Alias
-#Line # text relying on the order of RGB stripes ##
-When set, Anti_Alias positions glyphs within a pixel, using alpha and
-possibly RGB striping. It can take advantage of the organization of RGB stripes
-that create a color, and relies on the small size of the stripe and visual perception
-to make the color fringing imperceptible.
-
-Anti_Alias can be enabled on devices that orient stripes horizontally
-or vertically, and that order the color components as RGB or BGR. Internally, the
-glyph cache may store multiple copies of the same glyph with different sub-pixel
-positions, requiring more memory.
-#Subtopic Anti_Alias ##
-
-#Subtopic Force_Hinting
-#Line # always adjust glyph paths ##
-
-If Hinting is set to SkFontHinting::kNormal or SkFontHinting::kFull, Force_Hinting
-instructs the Font_Manager to always hint Glyphs.
-Force_Hinting has no effect if Hinting is set to SkFontHinting::kNone or
-SkFontHinting::kSlight.
-
-Force_Hinting only affects platforms that use FreeType as the Font_Manager.
-#Subtopic Force_Hinting ##
-
-#Subtopic Embedded_Bitmaps
-#Line # custom sized bitmap Glyphs ##
-Embedded_Bitmaps allows selecting custom sized bitmap Glyphs.
-Embedded_Bitmaps when set chooses an embedded bitmap glyph over an outline contained
-in a font if the platform supports this option.
-
-FreeType selects the bitmap glyph if available when Embedded_Bitmaps is set, and selects
-the outline glyph if Embedded_Bitmaps is clear.
-Windows may select the bitmap glyph but is not required to do so.
-OS_X and iOS do not support this option.
-##
-
-# ------------------------------------------------------------------------------
-
-#Class SkFont
-
-SkFont controls options applied when drawing and measuring text.
-
-#Code
-#Populate
-##
-
-# ------------------------------------------------------------------------------
-
-#EnumClass Edging
-
-#Code
-#Populate
-##
-
-Whether edge pixels draw opaque or with partial transparency.
-
-#Const kAlias  0 # incomplete; replace '0' with member value
-#Line # no transparent pixels on glyph edges ##
-# incomplete; add description or delete
-##
-#Const kAntiAlias  0 # incomplete; replace '0' with member value
-#Line # may have transparent pixels on glyph edges ##
-# incomplete; add description or delete
-##
-#Const kSubpixelAntiAlias  0 # incomplete; replace '0' with member value
-#Line # glyph positioned in pixel using transparency ##
-# incomplete; add description or delete
-##
-
-# incomplete; add description or delete
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#EnumClass ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkFont()
-#In Constructor
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkFont(sk_sp<SkTypeface> typeface, SkScalar size)
-#In Constructor
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method explicit SkFont(sk_sp<SkTypeface> typeface)
-#In Constructor
-#Line # incomplete ##
-#Populate
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkFont(sk_sp<SkTypeface> typeface, SkScalar size, SkScalar scaleX, SkScalar skewX)
-#In Constructor
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator==(const SkFont& font) const
-#In Operator
-#Line # compares fonts for equality ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator!=(const SkFont& font) const
-#In Operator
-#Line # compares fonts for inequality ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isForceAutoHinting() const
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isEmbeddedBitmaps() const
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isSubpixel() const
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isLinearMetrics() const
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isEmbolden() const
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setForceAutoHinting(bool forceAutoHinting)
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setEmbeddedBitmaps(bool embeddedBitmaps)
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setSubpixel(bool subpixel)
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setLinearMetrics(bool linearMetrics)
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setEmbolden(bool embolden)
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method Edging getEdging() const
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setEdging(Edging edging)
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setHinting(SkFontHinting hintingLevel)
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkFontHinting getHinting() const
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkFont makeWithSize(SkScalar size) const
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkTypeface* getTypeface() const
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkTypeface* getTypefaceOrDefault() const
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar    getSize() const
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar    getScaleX() const
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar    getSkewX() const
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkTypeface> refTypeface() const
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkTypeface> refTypefaceOrDefault() const
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setTypeface(sk_sp<SkTypeface> tf)
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setSize(SkScalar textSize)
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setScaleX(SkScalar scaleX)
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setSkewX(SkScalar skewX)
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method int textToGlyphs(const void* text, size_t byteLength, SkTextEncoding encoding,
-                     SkGlyphID glyphs[], int maxGlyphCount) const
-#In Utility
-#Line # converts text into glyph indices ##
-#Populate
-
-#Example
-    #Height 64
-    void draw(SkCanvas* canvas) {
-        SkFont font;
-        const uint8_t utf8[] = { 0x24, 0xC2, 0xA2, 0xE2, 0x82, 0xAC, 0xC2, 0xA5, 0xC2, 0xA3 };
-        std::vector<SkGlyphID> glyphs;
-        int count = font.textToGlyphs(utf8, sizeof(utf8), SkTextEncoding::kUTF8, nullptr, 0);
-        glyphs.resize(count);
-        (void) font.textToGlyphs(utf8, sizeof(utf8), SkTextEncoding::kUTF8, &glyphs.front(),
-                count);
-        font.setSize(32);
-        canvas->drawSimpleText(&glyphs.front(), glyphs.size() * sizeof(SkGlyphID),
-                SkTextEncoding::kGlyphID, 10, 40, font, SkPaint());
-    }
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkGlyphID unicharToGlyph(SkUnichar uni) const
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method int countText(const void* text, size_t byteLength, SkTextEncoding encoding) const
-#In Utility
-#Line # returns number of Glyphs in text ##
-#Populate
-
-#Example
-    SkFont font;
-    const uint8_t utf8[] = { 0x24, 0xC2, 0xA2, 0xE2, 0x82, 0xAC, 0xC2, 0xA5, 0xC2, 0xA3 };
-    SkDebugf("count = %d\n", font.countText(utf8, sizeof(utf8), SkTextEncoding::kUTF8));
-
-    #StdOut
-        count = 5
-    ##
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar measureText(const void* text, size_t byteLength, SkTextEncoding encoding,
-                         SkRect* bounds = nullptr) const
-#In incomplete
-#Line # returns advance width and bounds of text ##
-#Populate
-
-#Example
-    SkFont font;
-    SkDebugf("default width = %g\n", font.measureText("!", 1, SkTextEncoding::kUTF8));
-    font.setSize(font.getSize() * 2);
-    SkDebugf("double width = %g\n", font.measureText("!", 1, SkTextEncoding::kUTF8));
-
-    #StdOut
-        default width = 5
-        double width = 10
-    ##
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-
-#Method SkScalar measureText(const void* text, size_t byteLength, SkTextEncoding encoding,
-                         SkRect* bounds, const SkPaint* paint) const
-#In incomplete
-#Populate
-
-#Example
-    #Height 64
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        SkFont font(nullptr, 50);
-        const char str[] = "ay^jZ";
-        const int count = sizeof(str) - 1;
-        canvas->drawSimpleText(str, count, SkTextEncoding::kUTF8, 25, 50, font, paint);
-        SkRect bounds;
-        font.measureText(str, count, SkTextEncoding::kUTF8, &bounds, nullptr);
-        canvas->translate(25, 50);
-        paint.setStyle(SkPaint::kStroke_Style);
-        canvas->drawRect(bounds, paint);
-    }
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-#Method void getWidths(const uint16_t glyphs[], int count, SkScalar widths[]) const
-#In incomplete
-#Line # returns advance and bounds for each glyph in text ##
-#Populate
-#Example
-// incomplete
-##
-#SeeAlso incomplete
-#Method ##
-
-#Method void getWidthsBounds(const uint16_t glyphs[], int count, SkScalar widths[], SkRect bounds[],
-                         const SkPaint* paint) const
-#In incomplete
-#Populate
-#Example
-    #Height 160
-    #Description
-    Bounds of Glyphs increase for stroked text, but text advance remains the same.
-    The underlines show the text advance, spaced to keep them distinct.
-    ##
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        SkFont font(nullptr, 50);
-        const char str[] = "abc";
-        const int bytes = sizeof(str) - 1;
-        int count = font.textToGlyphs(str, bytes, SkTextEncoding::kUTF8, nullptr, 0);
-        std::vector<SkGlyphID> glyphs;
-        std::vector<SkScalar> widths;
-        std::vector<SkRect> bounds;
-        glyphs.resize(count);
-        (void) font.textToGlyphs(str, bytes, SkTextEncoding::kUTF8, &glyphs.front(), count);
-        widths.resize(count);
-        bounds.resize(count);
-        for (int loop = 0; loop < 2; ++loop) {
-            (void) font.getWidthsBounds(&glyphs.front(), count, &widths.front(), &bounds.front(),
-                    &paint);
-            SkPoint loc = { 25, 50 };
-            canvas->drawSimpleText(str, bytes, SkTextEncoding::kUTF8, loc.fX, loc.fY, font, paint);
-            paint.setStyle(SkPaint::kStroke_Style);
-            paint.setStrokeWidth(0);
-            SkScalar advanceY = loc.fY + 10;
-            for (int index = 0; index < count; ++index) {
-                bounds[index].offset(loc.fX, loc.fY);
-                canvas->drawRect(bounds[index], paint);
-                canvas->drawLine(loc.fX, advanceY, loc.fX + widths[index], advanceY, paint);
-                loc.fX += widths[index];
-                advanceY += 5;
-            }
-            canvas->translate(0, 80);
-            paint.setStrokeWidth(3);
-        }
-    }
-##
-#SeeAlso incomplete
-#Method ##
-
-#Method void getBounds(const uint16_t glyphs[], int count, SkRect bounds[],
-                   const SkPaint* paint) const
-#In incomplete
-#Populate
-#Example
-// incomplete
-##
-#SeeAlso incomplete
-#Method ##
-
-#Method void getPos(const uint16_t glyphs[], int count, SkPoint pos[], SkPoint origin = {0, 0}) const
-#In incomplete
-#Populate
-#Example
-// incomplete
-##
-#SeeAlso incomplete
-#Method ##
-
-#Method void getXPos(const uint16_t glyphs[], int count, SkScalar xpos[], SkScalar origin = 0) const
-#In incomplete
-#Populate
-#Example
-// incomplete
-##
-#SeeAlso incomplete
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool getPath(uint16_t glyphID, SkPath* path) const
-#In incomplete
-#Line # returns Path equivalent to text ##
-#Populate
-
-#Example
-    #Description
-    Text is added to Path, offset, and subtracted from Path, then added at
-    the offset location. The result is rendered with one draw call.
-    ##
-    #Height 128
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        SkFont font(nullptr, 80);
-        SkPath onePath, path, path2;
-        const char str[] = "ABC";
-        const int bytes = sizeof(str) - 1;
-        int count = font.textToGlyphs(str, bytes, SkTextEncoding::kUTF8, nullptr, 0);
-        std::vector<SkGlyphID> glyphs;
-        glyphs.resize(count);
-        (void) font.textToGlyphs(str, bytes, SkTextEncoding::kUTF8, &glyphs.front(), count);
-        int xPos = 20;
-        for (auto oneGlyph : glyphs) {
-            font.getPath(oneGlyph, &onePath);
-            path.addPath(onePath, xPos, 60);
-            xPos += 60;
-        }
-        path.offset(20, 20, &path2);
-        Op(path, path2, SkPathOp::kDifference_SkPathOp, &path);
-        path.addPath(path2);
-        paint.setStyle(SkPaint::kStroke_Style);
-        canvas->drawPath(path, paint);
-    }
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void getPaths(const uint16_t glyphIDs[], int count,
-                  void (*glyphPathProc)(const SkPath* pathOrNull, const SkMatrix& mx, void* ctx),
-                  void* ctx) const
-#In incomplete
-#Line # incomplete ##
-
-#Populate
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar getMetrics(SkFontMetrics* metrics) const
-#In incomplete
-#Line # returns Typeface metrics scaled by text size ##
-#Populate
-
-#Example
-    #Height 128
-    void draw(SkCanvas* canvas) {
-        SkFont font(nullptr, 32);
-        SkScalar lineHeight = font.getMetrics(nullptr);
-        SkPaint paint;
-        canvas->drawSimpleText("line 1", 6, SkTextEncoding::kUTF8, 10, 40, font, paint);
-        canvas->drawSimpleText("line 2", 6, SkTextEncoding::kUTF8, 10, 40 + lineHeight, font, paint);
-    }
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar getSpacing() const
-#In incomplete
-#Line # returns recommended spacing between lines ##
-
-#Populate
-
-#Example
-        SkFont font;
-        for (SkScalar textSize : { 12, 18, 24, 32 } ) {
-            font.setSize(textSize);
-            SkDebugf("textSize: %g spacing: %g\n", textSize, font.getSpacing());
-        }
-
-        #StdOut
-            textSize: 12 spacing: 13.9688
-            textSize: 18 spacing: 20.9531
-            textSize: 24 spacing: 27.9375
-            textSize: 32 spacing: 37.25
-        ##
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Class SkFont ##
-
-#Topic Font ##
diff --git a/docs/SkIPoint_Reference.bmh b/docs/SkIPoint_Reference.bmh
deleted file mode 100644
index 6b0351c..0000000
--- a/docs/SkIPoint_Reference.bmh
+++ /dev/null
@@ -1,402 +0,0 @@
-#Topic IPoint
-#Alias IPoints ##
-#Alias IPoint_Reference ##
-
-#Struct SkIPoint
-
-#Code
-#Populate
-##
-
-SkIPoint holds two 32-bit integer coordinates.
-
-#Member int32_t  fX
-#Line # x-axis value ##
-x-axis value used by IPoint.
-##
-
-#Member int32_t  fY
-#Line # y-axis value ##
-y-axis value used by IPoint.
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static constexpr SkIPoint Make(int32_t x, int32_t y)
-
-#In Constructors
-#Line # constructs from integer inputs ##
-#Populate
-
-#Example
-SkIPoint pt1 = {45, 66};
-SkIPoint pt2 = SkIPoint::Make(45, 66);
-SkDebugf("pt1 %c= pt2\n", pt1 == pt2 ? '=' : '!');
-#StdOut
-pt1 == pt2
-##
-##
-
-#SeeAlso set() SkPoint::iset() SkPoint::Make
-
-#Method ##
-
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Property
-#Line # member values ##
-#Subtopic Property ##
-
-#Method int32_t x() const
-#In Property
-#Line # returns fX ##
-#Populate
-
-#Example
-SkIPoint pt1 = {45, 66};
-SkDebugf("pt1.fX %c= pt1.x()\n", pt1.fX == pt1.x() ? '=' : '!');
-#StdOut
-pt1.fX == pt1.x()
-##
-##
-
-#SeeAlso y() SkPoint::x()
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method int32_t y() const
-#In Property
-#Line # returns fY ##
-#Populate
-
-#Example
-SkIPoint pt1 = {45, 66};
-SkDebugf("pt1.fY %c= pt1.y()\n", pt1.fY == pt1.y() ? '=' : '!');
-#StdOut
-pt1.fY == pt1.y()
-##
-##
-
-#SeeAlso x() SkPoint::y()
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isZero() const
-#In Property
-#Line # returns true if both members equal zero ##
-#Populate
-
-#Example
-SkIPoint pt = { 0, -0};
-SkDebugf("pt.isZero() == %s\n", pt.isZero() ? "true" : "false");
-#StdOut
-pt.isZero() == true
-##
-##
-
-#SeeAlso SkPoint::isZero
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Set
-#Line # replaces all values ##
-##
-
-#Method void set(int32_t x, int32_t y)
-#In Set
-#Line # sets to integer input ##
-#Populate
-
-#Example
-SkIPoint pt1, pt2 = { SK_MinS32, SK_MaxS32 };
-pt1.set(SK_MinS32, SK_MaxS32);
-SkDebugf("pt1 %c= pt2\n", pt1 == pt2 ? '=' : '!');
-#StdOut
-pt1 == pt2
-##
-##
-
-#SeeAlso Make
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkIPoint operator-() const
-
-#Line # reverses sign of IPoint ##
-#Populate
-
-#Example
-SkIPoint test[] = { {0, -0}, {-1, -2},
-                   { SK_MaxS32, SK_MinS32 },
-                   { SK_NaN32, SK_NaN32 } };
-for (const SkIPoint& pt : test) {
-    SkIPoint negPt = -pt;
-    SkDebugf("pt: %d, %d  negate: %d, %d\n", pt.fX, pt.fY, negPt.fX, negPt.fY);
-}
-#StdOut
-pt: 0, 0  negate: 0, 0
-pt: -1, -2  negate: 1, 2
-pt: 2147483647, -2147483647  negate: -2147483647, 2147483647
-pt: -2147483648, -2147483648  negate: -2147483648, -2147483648
-##
-##
-
-#SeeAlso operator-(const SkIPoint& a, const SkIPoint& b) operator-=(const SkIVector& v) SkPoint::operator-() const
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void operator+=(const SkIVector& v)
-
-#Line # adds IVector to IPoint ##
-Offsets IPoint by IVector v. Sets IPoint to #Formula # (fX + v.fX, fY + v.fY) ##.
-
-#Param v  IVector to add ##
-
-#Example
-#Height 64
-    auto draw_lines = [=](const SkIPoint pts[], size_t count, SkPaint& paint) -> void {
-        for (size_t i = 0; i < count - 1; ++i) {
-            SkPoint p0, p1;
-            p0.iset(pts[i]);
-            p1.iset(pts[i + 1]);
-            canvas->drawLine(p0, p1, paint);
-        }
-    };
-    SkIPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 } };
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->scale(30, 15);
-    draw_lines(points, SK_ARRAY_COUNT(points), paint);
-    points[1] += {1, 1};
-    points[2] += {-1, -1};
-    paint.setColor(SK_ColorRED);
-    draw_lines(points, SK_ARRAY_COUNT(points), paint);
-##
-
-#SeeAlso operator+(const SkIPoint& a, const SkIVector& b) SkPoint::operator+=(const SkVector& v)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void operator-=(const SkIVector& v)
-
-#Line # subtracts IVector from IPoint ##
-Subtracts IVector v from IPoint. Sets IPoint to: #Formula # (fX - v.fX, fY - v.fY) ##.
-
-#Param v  IVector to subtract ##
-
-#Example
-#Height 64
-    auto draw_lines = [=](const SkIPoint pts[], size_t count, SkPaint& paint) -> void {
-        for (size_t i = 0; i < count - 1; ++i) {
-            SkPoint p0, p1;
-            p0.iset(pts[i]);
-            p1.iset(pts[i + 1]);
-            canvas->drawLine(p0, p1, paint);
-        }
-    };
-    SkIPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 } };
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->scale(30, 15);
-    draw_lines(points, SK_ARRAY_COUNT(points), paint);
-    points[1] -= {1, 1};
-    points[2] -= {-1, -1};
-    paint.setColor(SK_ColorRED);
-    draw_lines(points, SK_ARRAY_COUNT(points), paint);
-##
-
-#SeeAlso operator-(const SkIPoint& a, const SkIPoint& b) SkPoint::operator-=(const SkVector& v)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool equals(int32_t x, int32_t y) const
-#In Operators
-#Line # returns true if members are equal ##
-#Populate
-
-#Example
-SkIPoint test[] = { {0, -0}, {-1, -2}, {SK_MaxS32, -1}, {SK_NaN32, -1} };
-for (const SkIPoint& pt : test) {
-    SkDebugf("pt: %d, %d  %c= pt\n", pt.fX, pt.fY, pt.equals(pt.fX, pt.fY) ? '=' : '!');
-}
-#StdOut
-pt: 0, 0  == pt
-pt: -1, -2  == pt
-pt: 2147483647, -1  == pt
-pt: -2147483648, -1  == pt
-##
-##
-
-#SeeAlso operator==(const SkIPoint& a, const SkIPoint& b)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator==(const SkIPoint& a, const SkIPoint& b)
-
-#Line # returns true if IPoints are equal ##
-#Populate
-
-#Example
-SkIPoint test[] = { {0, -0}, {-1, -2}, {SK_MaxS32, -1}, {SK_NaN32, -1} };
-for (const SkIPoint& pt : test) {
-    SkDebugf("pt: %d, %d  %c= pt\n", pt.fX, pt.fY, pt == pt ? '=' : '!');
-}
-#StdOut
-pt: 0, 0  == pt
-pt: -1, -2  == pt
-pt: 2147483647, -1  == pt
-pt: -2147483648, -1  == pt
-##
-##
-
-#SeeAlso equals() operator!=(const SkIPoint& a, const SkIPoint& b)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator!=(const SkIPoint& a, const SkIPoint& b)
-
-#Line # returns true if IPoints are unequal ##
-#Populate
-
-#Example
-SkIPoint test[] = { {0, -0}, {-1, -2}, {SK_MaxS32, -1}, {SK_NaN32, -1} };
-for (const SkIPoint& pt : test) {
-    SkDebugf("pt: %d, %d  %c= pt\n", pt.fX, pt.fY, pt != pt ? '!' : '=');
-}
-#StdOut
-pt: 0, 0  == pt
-pt: -1, -2  == pt
-pt: 2147483647, -1  == pt
-pt: -2147483648, -1  == pt
-##
-##
-
-#SeeAlso operator==(const SkIPoint& a, const SkIPoint& b) equals()
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkIVector operator-(const SkIPoint& a, const SkIPoint& b)
-
-#Line # returns IVector between IPoints ##
-Returns IVector from b to a; computed as #Formula # (a.fX - b.fX, a.fY - b.fY) ##.
-
-Can also be used to subtract IVector from IVector, returning IVector.
-
-#Param a  IPoint or IVector to subtract from ##
-#Param b  IVector to subtract ##
-
-#Return IVector from b to a ##
-
-#Example
-#Height 64
-    auto draw_lines = [=](const SkIPoint pts[], size_t count, SkPaint& paint) -> void {
-        for (size_t i = 0; i < count - 1; ++i) {
-            SkPoint p0, p1;
-            p0.iset(pts[i]);
-            p1.iset(pts[i + 1]);
-            canvas->drawLine(p0, p1, paint);
-        }
-    };
-    SkIPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 } };
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->scale(30, 15);
-    draw_lines(points, SK_ARRAY_COUNT(points), paint);
-    points[1] += points[0] - points[3];
-    points[2] -= points[1] - points[0];
-    paint.setColor(SK_ColorRED);
-    draw_lines(points, SK_ARRAY_COUNT(points), paint);
-##
-
-#SeeAlso operator-=(const SkIVector& v)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkIPoint operator+(const SkIPoint& a, const SkIVector& b)
-
-#Line # returns IPoint offset by IVector ##
-Returns IPoint resulting from IPoint a offset by IVector b, computed as:
-#Formula # (a.fX + b.fX, a.fY + b.fY) ##.
-
-Can also be used to offset IPoint b by IVector a, returning IPoint.
-Can also be used to add IVector to IVector, returning IVector.
-
-#Param a  IPoint or IVector to add to ##
-#Param b  IPoint or IVector to add ##
-
-#Return IPoint equal to a offset by b ##
-
-#Example
-#Height 128
-    auto draw_lines = [=](const SkIPoint pts[], size_t count, SkPaint& paint) -> void {
-        for (size_t i = 0; i < count - 1; ++i) {
-            SkPoint p0, p1;
-            p0.iset(pts[i]);
-            p1.iset(pts[i + 1]);
-            canvas->drawLine(p0, p1, paint);
-        }
-    };
-    SkIPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 } };
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->scale(30, 15);
-    draw_lines(points, SK_ARRAY_COUNT(points), paint);
-    SkIPoint mod = {4, 1};
-    for (auto& point : points) {
-        point = point + mod;
-        mod.fX -= 1;
-        mod.fY += 1;
-    }
-    paint.setColor(SK_ColorRED);
-    draw_lines(points, SK_ARRAY_COUNT(points), paint);
-##
-
-#SeeAlso operator+=(const SkIVector& v)
-
-#Method ##
-
-#Struct SkIPoint ##
-
-
-#Subtopic IVector
-#Line # alias for IPoint ##
-    #Alias IVector ##
-    #Alias IVectors ##
-    #Typedef SkIPoint SkIVector
-    #Line # alias for IPoint ##
-    #Code
-    typedef SkIPoint SkIVector;
-    ##
-    SkIVector provides an alternative name for SkIPoint. SkIVector and SkIPoint
-    can be used interchangeably for all purposes.
-    #Typedef ##
-##
-
-#Topic IPoint ##
diff --git a/docs/SkIRect_Reference.bmh b/docs/SkIRect_Reference.bmh
deleted file mode 100644
index b308f53..0000000
--- a/docs/SkIRect_Reference.bmh
+++ /dev/null
@@ -1,1292 +0,0 @@
-#Topic IRect
-#Alias IRects ##
-#Alias IRect_Reference ##
-
-#Struct SkIRect
-
-#Code
-#Populate
-##
-
-SkIRect holds four 32-bit integer coordinates describing the upper and
-lower bounds of a rectangle. SkIRect may be created from outer bounds or
-from position, width, and height. SkIRect describes an area; if its right
-is less than or equal to its left, or if its bottom is less than or equal to
-its top, it is considered empty.
-
-#Member int32_t  fLeft
-#Line # smaller x-axis bounds ##
-May contain any value. The smaller of the horizontal values when sorted.
-When equal to or greater than fRight, IRect is empty.
-##
-
-#Member int32_t  fTop
-#Line # smaller y-axis bounds ##
-May contain any value. The smaller of the horizontal values when sorted.
-When equal to or greater than fBottom, IRect is empty.
-##
-
-#Member int32_t  fRight
-#Line # larger x-axis bounds ##
-May contain any value. The larger of the vertical values when sorted.
-When equal to or less than fLeft, IRect is empty.
-##
-
-#Member int32_t  fBottom
-#Line # larger y-axis bounds ##
-May contain any value. The larger of the vertical values when sorted.
-When equal to or less than fTop, IRect is empty.
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static constexpr SkIRect MakeEmpty()
-
-#In Constructors
-#Line # returns bounds of (0, 0, 0, 0) ##
-#Populate
-
-#Example
-    SkIRect rect = SkIRect::MakeEmpty();
-    SkDebugf("MakeEmpty isEmpty: %s\n", rect.isEmpty() ? "true" : "false");
-    rect.offset(10, 10);
-    SkDebugf("offset rect isEmpty: %s\n", rect.isEmpty() ? "true" : "false");
-    rect.inset(10, 10);
-    SkDebugf("inset rect isEmpty: %s\n", rect.isEmpty() ? "true" : "false");
-    rect.outset(20, 20);
-    SkDebugf("outset rect isEmpty: %s\n", rect.isEmpty() ? "true" : "false");
-#StdOut
-MakeEmpty isEmpty: true
-offset rect isEmpty: true
-inset rect isEmpty: true
-outset rect isEmpty: false
-##
-##
-
-#SeeAlso EmptyIRect isEmpty setEmpty SkRect::MakeEmpty
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static constexpr SkIRect MakeWH(int32_t w, int32_t h)
-
-#In Constructors
-#Line # constructs from int input returning (0, 0, width, height) ##
-#Populate
-
-#Example
-    SkIRect rect1 = SkIRect::MakeWH(25, 35);
-    SkIRect rect2 = SkIRect::MakeSize({25, 35});
-    SkIRect rect3 = SkIRect::MakeXYWH(0, 0, 25, 35);
-    SkIRect rect4 = SkIRect::MakeLTRB(0, 0, 25, 35);
-    SkDebugf("all %s" "equal\n", rect1 == rect2 && rect2 == rect3 && rect3 == rect4 ?
-             "" : "not ");
-#StdOut
-all equal
-##
-##
-
-#SeeAlso MakeSize MakeXYWH SkRect::MakeWH SkRect::MakeIWH
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static constexpr SkIRect MakeSize(const SkISize& size)
-
-#In Constructors
-#Line # constructs from ISize returning (0, 0, width, height) ##
-#Populate
-
-#Example
-    SkSize size = {25.5f, 35.5f};
-    SkIRect rect = SkIRect::MakeSize(size.toRound());
-    SkDebugf("round width: %d  height: %d\n", rect.width(), rect.height());
-    rect = SkIRect::MakeSize(size.toFloor());
-    SkDebugf("floor width: %d  height: %d\n", rect.width(), rect.height());
-#StdOut
-round width: 26  height: 36
-floor width: 25  height: 35
-##
-##
-
-#SeeAlso MakeWH MakeXYWH SkRect::Make SkRect::MakeIWH
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static constexpr SkIRect MakeLTRB(int32_t l, int32_t t, int32_t r, int32_t b)
-
-#In Constructors
-#Line # constructs from int left, top, right, bottom ##
-#Populate
-
-#Example
-    SkIRect rect = SkIRect::MakeLTRB(5, 35, 15, 25);
-    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-    rect.sort();
-    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-#StdOut
-rect: 5, 35, 15, 25  isEmpty: true
-rect: 5, 25, 15, 35  isEmpty: false
-##
-##
-
-#SeeAlso MakeXYWH SkRect::MakeLTRB
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static constexpr SkIRect MakeXYWH(int32_t x, int32_t y, int32_t w, int32_t h)
-
-#In Constructors
-#Line # constructs from int input returning (x, y, width, height) ##
-
-#Populate
-
-#Example
-    SkIRect rect = SkIRect::MakeXYWH(5, 35, -15, 25);
-    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-    rect.sort();
-    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-#StdOut
-rect: 5, 35, -10, 60  isEmpty: true
-rect: -10, 35, 5, 60  isEmpty: false
-##
-##
-
-#SeeAlso MakeLTRB SkRect::MakeXYWH
-
-##
-
-#Subtopic Property
-#Line # member values, center, validity ##
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int32_t left() const
-
-#In Property
-#Line # returns smaller bounds in x, if sorted ##
-#Populate
-
-#Example
-    SkIRect unsorted = { 15, 5, 10, 25 };
-    SkDebugf("unsorted.fLeft: %d unsorted.left(): %d\n", unsorted.fLeft, unsorted.left());
-    SkIRect sorted = unsorted.makeSorted();
-    SkDebugf("sorted.fLeft: %d sorted.left(): %d\n", sorted.fLeft, sorted.left());
-#StdOut
-unsorted.fLeft: 15 unsorted.left(): 15
-sorted.fLeft: 10 sorted.left(): 10
-##
-##
-
-#SeeAlso fLeft x() SkRect::left()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int32_t top() const
-
-#In Property
-#Line # returns smaller bounds in y, if sorted ##
-#Populate
-
-#Example
-    SkIRect unsorted = { 15, 25, 10, 5 };
-    SkDebugf("unsorted.fTop: %d unsorted.top(): %d\n", unsorted.fTop, unsorted.top());
-    SkIRect sorted = unsorted.makeSorted();
-    SkDebugf("sorted.fTop: %d sorted.top(): %d\n", sorted.fTop, sorted.top());
-#StdOut
-unsorted.fTop: 25 unsorted.top(): 25
-sorted.fTop: 5 sorted.top(): 5
-##
-##
-
-#SeeAlso fTop y() SkRect::top()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int32_t right() const
-
-#In Property
-#Line # returns larger bounds in x, if sorted ##
-#Populate
-
-#Example
-    SkIRect unsorted = { 15, 25, 10, 5 };
-    SkDebugf("unsorted.fRight: %d unsorted.right(): %d\n", unsorted.fRight, unsorted.right());
-    SkIRect sorted = unsorted.makeSorted();
-    SkDebugf("sorted.fRight: %d sorted.right(): %d\n", sorted.fRight, sorted.right());
-#StdOut
-unsorted.fRight: 10 unsorted.right(): 10
-sorted.fRight: 15 sorted.right(): 15
-##
-##
-
-#SeeAlso fRight SkRect::right()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int32_t bottom() const
-
-#In Property
-#Line # returns larger bounds in y, if sorted ##
-#Populate
-
-#Example
-    SkIRect unsorted = { 15, 25, 10, 5 };
-    SkDebugf("unsorted.fBottom: %d unsorted.bottom(): %d\n", unsorted.fBottom, unsorted.bottom());
-    SkIRect sorted = unsorted.makeSorted();
-    SkDebugf("sorted.fBottom: %d sorted.bottom(): %d\n", sorted.fBottom, sorted.bottom());
-#StdOut
-unsorted.fBottom: 5 unsorted.bottom(): 5
-sorted.fBottom: 25 sorted.bottom(): 25
-##
-##
-
-#SeeAlso fBottom SkRect::bottom()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int32_t x() const
-
-#In Property
-#Line # returns bounds left ##
-#Populate
-
-#Example
-    SkIRect unsorted = { 15, 5, 10, 25 };
-    SkDebugf("unsorted.fLeft: %d unsorted.x(): %d\n", unsorted.fLeft, unsorted.x());
-    SkIRect sorted = unsorted.makeSorted();
-    SkDebugf("sorted.fLeft: %d sorted.x(): %d\n", sorted.fLeft, sorted.x());
-#StdOut
-unsorted.fLeft: 15 unsorted.x(): 15
-sorted.fLeft: 10 sorted.x(): 10
-##
-##
-
-#SeeAlso fLeft left() y() SkRect::x()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int32_t y() const
-
-#In Property
-#Line # returns bounds top ##
-#Populate
-
-#Example
-    SkIRect unsorted = { 15, 25, 10, 5 };
-    SkDebugf("unsorted.fTop: %d unsorted.y(): %d\n", unsorted.fTop, unsorted.y());
-    SkIRect sorted = unsorted.makeSorted();
-    SkDebugf("sorted.fTop: %d sorted.y(): %d\n", sorted.fTop, sorted.y());
-#StdOut
-unsorted.fTop: 25 unsorted.y(): 25
-sorted.fTop: 5 sorted.y(): 5
-##
-##
-
-#SeeAlso fTop top() x() SkRect::y()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int32_t width() const
-
-#In Property
-#Line # returns span in x ##
-#Populate
-
-#Example
-    SkIRect unsorted = { 15, 25, 10, 5 };
-    SkDebugf("unsorted width: %d\n", unsorted.width());
-    SkIRect large = { -2147483647, 1, 2147483644, 2 };
-    SkDebugf("large width: %d\n", large.width());
-#StdOut
-unsorted width: -5
-large width: -5
-##
-##
-
-#SeeAlso height() width64() height64() SkRect::width()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int64_t width64() const
-
-#In Property
-#Line # returns span in y as int64_t ##
-#Populate
-
-#Example
-SkIRect large = { -2147483647, 1, 2147483644, 2 };
-SkDebugf("width: %d width64: %lld\n", large.width(), large.width64());
-#StdOut
-width: -5 width64: 4294967291
-##
-##
-
-#SeeAlso width() height() height64() SkRect::width()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int32_t height() const
-
-#In Property
-#Line # returns span in y ##
-#Populate
-
-#Example
-    SkIRect unsorted = { 15, 25, 10, 20 };
-    SkDebugf("unsorted height: %d\n", unsorted.height());
-    SkIRect large = { 1, -2147483647, 2, 2147483644 };
-    SkDebugf("large height: %d\n", large.height());
-#StdOut
-unsorted height: -5
-large height: -5
-##
-##
-
-#SeeAlso width() SkRect::height()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int64_t height64() const
-
-#In Property
-#Line # returns span in y as int64_t ##
-#Populate
-
-#Example
-SkIRect large = { 1, -2147483647, 2, 2147483644 };
-SkDebugf("height: %d height64: %lld\n", large.height(), large.height64());
-#StdOut
-height: -5 height64: 4294967291
-##
-##
-
-#SeeAlso width() height() width64() SkRect::height()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkISize size() const
-
-#In Property
-#Line # returns ISize (width, height) ##
-#Populate
-
-#Example
-    auto debugster = [](const char* prefix, const SkIRect& rect) -> void {
-        SkISize size = rect.size();
-        SkDebugf("%s ", prefix);
-        SkDebugf("rect: %d, %d, %d, %d  ", rect.left(), rect.top(), rect.right(), rect.bottom());
-        SkDebugf("size: %d, %d\n", size.width(), size.height());
-    };
-    SkIRect rect = {20, 30, 40, 50};
-    debugster("original", rect);
-    rect.offset(20, 20);
-    debugster("  offset", rect);
-    rect.outset(20, 20);
-    debugster("  outset", rect);
-#StdOut
-original rect: 20, 30, 40, 50  size: 20, 20
-  offset rect: 40, 50, 60, 70  size: 20, 20
-  outset rect: 20, 30, 80, 90  size: 60, 60
-##
-##
-
-#SeeAlso height() width() MakeSize
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isEmpty() const
-
-#In Property
-#Line # returns true if width or height are zero or negative or they exceed int32_t ##
-#Populate
-
-#Example
-    SkIRect tests[] = {{20, 40, 10, 50}, {20, 40, 20, 50}};
-    for (auto rect : tests) {
-        SkDebugf("rect: {%d, %d, %d, %d} is" "%s empty\n", rect.left(), rect.top(), rect.right(),
-                 rect.bottom(), rect.isEmpty() ? "" : " not");
-        rect.sort();
-        SkDebugf("sorted: {%d, %d, %d, %d} is" "%s empty\n", rect.left(), rect.top(), rect.right(),
-                 rect.bottom(), rect.isEmpty() ? "" : " not");
-    }
-#StdOut
-rect: {20, 40, 10, 50} is empty
-sorted: {10, 40, 20, 50} is not empty
-rect: {20, 40, 20, 50} is empty
-sorted: {20, 40, 20, 50} is empty
-##
-##
-
-#SeeAlso EmptyIRect MakeEmpty sort SkRect::isEmpty
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isEmpty64() const
-
-#In Property
-#Line # returns true if width or height are zero or negative ##
-#Populate
-
-#Example
-SkIRect tests[] = {{20, 40, 10, 50}, {20, 40, 20, 50}};
-for (auto rect : tests) {
-    SkDebugf("rect: {%d, %d, %d, %d} is" "%s empty\n", rect.left(), rect.top(), rect.right(),
-            rect.bottom(), rect.isEmpty64() ? "" : " not");
-    rect.sort();
-    SkDebugf("sorted: {%d, %d, %d, %d} is" "%s empty\n", rect.left(), rect.top(), rect.right(),
-            rect.bottom(), rect.isEmpty64() ? "" : " not");
-}
-#StdOut
-rect: {20, 40, 10, 50} is empty
-sorted: {10, 40, 20, 50} is not empty
-rect: {20, 40, 20, 50} is empty
-sorted: {20, 40, 20, 50} is empty
-##
-##
-
-#SeeAlso EmptyIRect MakeEmpty sort SkRect::isEmpty
-
-##
-
-#Subtopic Operators
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator==(const SkIRect& a, const SkIRect& b)
-
-#In Operators
-#Line # returns true if members are equal ##
-#Populate
-
-#Example
-    SkIRect test = {0, 0, 2, 2};
-    SkIRect sorted = test.makeSorted();
-    SkDebugf("test %c= sorted\n", test == sorted ? '=' : '!');
-#StdOut
-test == sorted
-##
-##
-
-#SeeAlso operator!=(const SkIRect& a, const SkIRect& b)
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator!=(const SkIRect& a, const SkIRect& b)
-
-#In Operators
-#Line # returns true if members are unequal ##
-#Populate
-
-#Example
-    SkIRect test = {2, 2, 0, 0};
-    SkIRect sorted = test.makeSorted();
-    SkDebugf("test %c= sorted\n", test != sorted ? '!' : '=');
-#StdOut
-test != sorted
-##
-##
-
-#SeeAlso operator==(const SkIRect& a, const SkIRect& b)
-
-##
-
-#Subtopic Operators ##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Set
-#Line # replaces all values ##
-##
-
-#Method void setEmpty()
-
-#In Set
-#Line # sets to (0, 0, 0, 0) ##
-#Populate
-
-#Example
-    SkIRect rect = {3, 4, 1, 2};
-    for (int i = 0; i < 2; ++i) {
-    SkDebugf("rect: {%d, %d, %d, %d} is %s" "empty\n", rect.fLeft, rect.fTop,
-             rect.fRight, rect.fBottom, rect.isEmpty() ? "" : "not ");
-    rect.setEmpty();
-    }
-#StdOut
-rect: {3, 4, 1, 2} is empty
-rect: {0, 0, 0, 0} is empty
-##
-##
-
-#SeeAlso MakeEmpty SkRect::setEmpty
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void set(int32_t left, int32_t top, int32_t right, int32_t bottom)
-
-#In Set
-#Line # sets to (left, top, right, bottom) ##
-#Populate
-
-#Example
-    SkIRect rect1 = {3, 4, 1, 2};
-    SkDebugf("rect1: {%d, %d, %d, %d}\n", rect1.fLeft, rect1.fTop, rect1.fRight, rect1.fBottom);
-    SkIRect rect2;
-    rect2.set(3, 4, 1, 2);
-    SkDebugf("rect2: {%d, %d, %d, %d}\n", rect2.fLeft, rect2.fTop, rect2.fRight, rect2.fBottom);
-#StdOut
-rect1: {3, 4, 1, 2}
-rect2: {3, 4, 1, 2}
-##
-##
-
-#SeeAlso setLTRB setXYWH SkRect::set
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void setLTRB(int32_t left, int32_t top, int32_t right, int32_t bottom)
-
-#In Set
-#Line # sets to SkScalar input (left, top, right, bottom) ##
-#Populate
-
-#Example
-    SkIRect rect1 = {3, 4, 1, 2};
-    SkDebugf("rect1: {%d, %d, %d, %d}\n", rect1.fLeft, rect1.fTop, rect1.fRight, rect1.fBottom);
-    SkIRect rect2;
-    rect2.setLTRB(3, 4, 1, 2);
-    SkDebugf("rect2: {%d, %d, %d, %d}\n", rect2.fLeft, rect2.fTop, rect2.fRight, rect2.fBottom);
-#StdOut
-rect1: {3, 4, 1, 2}
-rect2: {3, 4, 1, 2}
-##
-##
-
-#SeeAlso set setXYWH SkRect::setLTRB
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void setXYWH(int32_t x, int32_t y, int32_t width, int32_t height)
-
-#In Set
-#Line # sets to (x, y, width, height) ##
-Sets IRect to: #Formula # (x, y, x + width, y + height) ##.
-Does not validate input; width or height may be negative.
-
-#Param x  stored in fLeft ##
-#Param y  stored in fTop ##
-#Param width  added to x and stored in fRight ##
-#Param height  added to y and stored in fBottom ##
-
-#Example
-    SkIRect rect;
-    rect.setXYWH(5, 35, -15, 25);
-    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-    rect.sort();
-    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-#StdOut
-rect: 5, 35, -10, 60  isEmpty: true
-rect: -10, 35, 5, 60  isEmpty: false
-##
-##
-
-#SeeAlso MakeXYWH setLTRB set SkRect::setXYWH
-
-##
-
-#Subtopic Inset_Outset_Offset
-#Line # moves sides ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkIRect makeOffset(int32_t dx, int32_t dy) const
-
-#In Inset_Outset_Offset
-#Line # constructs from translated sides ##
-#Populate
-
-#Example
-    SkIRect rect = { 10, 50, 20, 60 };
-    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-    rect = rect.makeOffset(15, 32);
-    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-#StdOut
-rect: 10, 50, 20, 60  isEmpty: false
-rect: 25, 82, 35, 92  isEmpty: false
-##
-##
-
-#SeeAlso offset() makeInset makeOutset SkRect::makeOffset
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkIRect makeInset(int32_t dx, int32_t dy) const
-
-#In Inset_Outset_Offset
-#Line # constructs from sides moved symmetrically about the center ##
-#Populate
-
-#Example
-    SkIRect rect = { 10, 50, 20, 60 };
-    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-    rect = rect.makeInset(15, 32);
-    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-#StdOut
-rect: 10, 50, 20, 60  isEmpty: false
-rect: 25, 82, 5, 28  isEmpty: true
-##
-##
-
-#SeeAlso inset() makeOffset makeOutset SkRect::makeInset
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkIRect makeOutset(int32_t dx, int32_t dy) const
-
-#In Inset_Outset_Offset
-#Line # constructs from sides moved symmetrically about the center ##
-#Populate
-
-#Example
-    SkIRect rect = { 10, 50, 20, 60 };
-    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-    rect = rect.makeOutset(15, 32);
-    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-#StdOut
-rect: 10, 50, 20, 60  isEmpty: false
-rect: -5, 18, 35, 92  isEmpty: false
-##
-##
-
-#SeeAlso outset() makeOffset makeInset SkRect::makeOutset
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void offset(int32_t dx, int32_t dy)
-
-#In Inset_Outset_Offset
-#Line # translates sides without changing width and height ##
-#Populate
-
-#Example
-    SkIRect rect = { 10, 14, 50, 73 };
-    rect.offset(5, 13);
-    SkDebugf("rect: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-#StdOut
-rect: 15, 27, 55, 86
-##
-##
-
-#SeeAlso offsetTo makeOffset SkRect::offset
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void offset(const SkIPoint& delta)
-
-#In Inset_Outset_Offset
-#Populate
-
-#Example
-    SkIRect rect = { 10, 14, 50, 73 };
-    rect.offset({5, 13});
-    SkDebugf("rect: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-#StdOut
-rect: 15, 27, 55, 86
-##
-##
-
-#SeeAlso offsetTo makeOffset SkRect::offset
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void offsetTo(int32_t newX, int32_t newY)
-
-#In Inset_Outset_Offset
-#Line # translates to (x, y) without changing width and height ##
-#Populate
-
-#Example
-    SkIRect rect = { 10, 14, 50, 73 };
-    rect.offsetTo(15, 27);
-    SkDebugf("rect: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-#StdOut
-rect: 15, 27, 55, 86
-##
-##
-
-#SeeAlso offset makeOffset setXYWH SkRect::offsetTo
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void inset(int32_t dx, int32_t dy)
-
-#In Inset_Outset_Offset
-#Line # moves the sides symmetrically about the center ##
-#Populate
-
-#Example
-    SkIRect rect = { 10, 14, 50, 73 };
-    rect.inset(5, 13);
-    SkDebugf("rect: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-#StdOut
-rect: 15, 27, 45, 60
-##
-##
-
-#SeeAlso outset makeInset SkRect::inset
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void outset(int32_t dx, int32_t dy)
-
-#In Inset_Outset_Offset
-#Line # moves the sides symmetrically about the center ##
-#Populate
-
-#Example
-    SkIRect rect = { 10, 14, 50, 73 };
-    rect.outset(5, 13);
-    SkDebugf("rect: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-#StdOut
-rect: 5, 1, 55, 86
-##
-##
-
-#SeeAlso inset makeOutset SkRect::outset
-
-##
-
-#Subtopic Inset_Outset_Offset ##
-
-#Subtopic Intersection
-#Line # sets to shared bounds ##
-
-IRects intersect when they enclose a common area. To intersect, each of the pair
-must describe area; fLeft is less than fRight, and fTop is less than fBottom;
-SkIRect::isEmpty() returns false. The intersection of IRect pair can be described by:
-#Formula # (max(a.fLeft, b.fLeft), max(a.fTop, b.fTop),
-            min(a.fRight, b.fRight), min(a.fBottom, b.fBottom)) ##.
-
-The intersection is only meaningful if the resulting IRect is not empty and
-describes an area: fLeft is less than fRight, and fTop is less than fBottom.
-
-# ------------------------------------------------------------------------------
-
-#Method void adjust(int32_t dL, int32_t dT, int32_t dR, int32_t dB)
-
-#In Inset_Outset_Offset
-#Line # moves the sides independently relative to their original locations ##
-#Populate
-
-#Example
-    SkIRect rect = { 8, 11, 19, 22 };
-    rect.adjust(2, -1, 1, -2);
-    SkDebugf("rect: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-#StdOut
-rect: 10, 10, 20, 20
-##
-##
-
-#SeeAlso inset outset
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool contains(int32_t x, int32_t y) const
-
-#In Intersection
-#Line # returns true if IPoint (x, y) is equal or inside ##
-Returns true if: #Formula # fLeft <= x < fRight && fTop <= y < fBottom ##.
-Returns false if IRect is empty.
-
-Considers input to describe constructed IRect: #Formula # (x, y, x + 1, y + 1) ## and 
-returns true if constructed area is completely enclosed by IRect area.
-
-#Param x  test IPoint x-coordinate ##
-#Param y  test IPoint y-coordinate ##
-
-#Return true if (x, y) is inside IRect ##
-
-#Example
-    SkIRect rect = { 30, 50, 40, 60 };
-    SkIPoint pts[] = { { 30, 50}, { 40, 50}, { 30, 60} };
-    for (auto pt : pts) {
-        SkDebugf("rect: (%d, %d, %d, %d) %s (%d, %d)\n",
-                 rect.left(), rect.top(), rect.right(), rect.bottom(),
-                 rect.contains(pt.x(), pt.y()) ? "contains" : "does not contain", pt.x(), pt.y());
-    }
-#StdOut
-rect: (30, 50, 40, 60) contains (30, 50)
-rect: (30, 50, 40, 60) does not contain (40, 50)
-rect: (30, 50, 40, 60) does not contain (30, 60)
-##
-##
-
-#SeeAlso containsNoEmptyCheck SkRect::contains
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool contains(int32_t left, int32_t top, int32_t right, int32_t bottom) const
-
-#In Intersection
-#Populate
-
-#Example
-    SkIRect rect = { 30, 50, 40, 60 };
-    SkIRect tests[] = { { 30, 50, 31, 51}, { 39, 49, 40, 50}, { 29, 59, 30, 60} };
-    for (auto contained : tests) {
-        bool success = rect.contains(
-                       contained.left(), contained.top(), contained.right(), contained.bottom());
-        SkDebugf("rect: (%d, %d, %d, %d) %s (%d, %d, %d, %d)\n",
-                 rect.left(), rect.top(), rect.right(), rect.bottom(),
-                 success ? "contains" : "does not contain",
-                 contained.left(), contained.top(), contained.right(), contained.bottom());
-    }
-#StdOut
-rect: (30, 50, 40, 60) contains (30, 50, 31, 51)
-rect: (30, 50, 40, 60) does not contain (39, 49, 40, 50)
-rect: (30, 50, 40, 60) does not contain (29, 59, 30, 60)
-##
-##
-
-#SeeAlso containsNoEmptyCheck SkRect::contains
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool contains(const SkIRect& r) const
-
-#In Intersection
-#Populate
-
-#Example
-    SkIRect rect = { 30, 50, 40, 60 };
-    SkIRect tests[] = { { 30, 50, 31, 51}, { 39, 49, 40, 50}, { 29, 59, 30, 60} };
-    for (auto contained : tests) {
-        SkDebugf("rect: (%d, %d, %d, %d) %s (%d, %d, %d, %d)\n",
-                 rect.left(), rect.top(), rect.right(), rect.bottom(),
-                 rect.contains(contained) ? "contains" : "does not contain",
-                 contained.left(), contained.top(), contained.right(), contained.bottom());
-    }
-#StdOut
-rect: (30, 50, 40, 60) contains (30, 50, 31, 51)
-rect: (30, 50, 40, 60) does not contain (39, 49, 40, 50)
-rect: (30, 50, 40, 60) does not contain (29, 59, 30, 60)
-##
-##
-
-#SeeAlso containsNoEmptyCheck SkRect::contains
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool contains(const SkRect& r) const
-
-#In Intersection
-#Populate
-
-#Example
-    SkIRect rect = { 30, 50, 40, 60 };
-    SkRect tests[] = { { 30, 50, 31, 51}, { 39, 49, 40, 50}, { 29, 59, 30, 60} };
-    for (auto contained : tests) {
-        SkDebugf("rect: (%d, %d, %d, %d) %s (%g, %g, %g, %g)\n",
-                 rect.left(), rect.top(), rect.right(), rect.bottom(),
-                 rect.contains(contained) ? "contains" : "does not contain",
-                 contained.left(), contained.top(), contained.right(), contained.bottom());
-    }
-#StdOut
-rect: (30, 50, 40, 60) contains (30, 50, 31, 51)
-rect: (30, 50, 40, 60) does not contain (39, 49, 40, 50)
-rect: (30, 50, 40, 60) does not contain (29, 59, 30, 60)
-##
-##
-
-#SeeAlso containsNoEmptyCheck SkRect::contains
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool containsNoEmptyCheck(int32_t left, int32_t top,
-                              int32_t right, int32_t bottom) const
-#In Intersection
-#Line # returns true if contains unsorted IRect ##
-#Populate
-
-#Example
-    SkIRect rect = { 30, 50, 40, 60 };
-    SkIRect tests[] = { { 30, 50, 31, 51}, { 39, 49, 40, 50}, { 29, 59, 30, 60} };
-    for (auto contained : tests) {
-        bool success = rect.containsNoEmptyCheck(
-                 contained.left(), contained.top(), contained.right(), contained.bottom());
-        SkDebugf("rect: (%d, %d, %d, %d) %s (%d, %d, %d, %d)\n",
-                 rect.left(), rect.top(), rect.right(), rect.bottom(),
-                 success ? "contains" : "does not contain",
-                 contained.left(), contained.top(), contained.right(), contained.bottom());
-    }
-#StdOut
-rect: (30, 50, 40, 60) contains (30, 50, 31, 51)
-rect: (30, 50, 40, 60) does not contain (39, 49, 40, 50)
-rect: (30, 50, 40, 60) does not contain (29, 59, 30, 60)
-##
-##
-
-#SeeAlso contains SkRect::contains
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool containsNoEmptyCheck(const SkIRect& r) const
-
-#In Intersection
-#Populate
-
-#Example
-    SkIRect rect = { 30, 50, 40, 60 };
-    SkIRect tests[] = { { 30, 50, 31, 51}, { 39, 49, 40, 50}, { 29, 59, 30, 60} };
-    for (auto contained : tests) {
-        SkDebugf("rect: (%d, %d, %d, %d) %s (%d, %d, %d, %d)\n",
-                 rect.left(), rect.top(), rect.right(), rect.bottom(),
-                 rect.containsNoEmptyCheck(contained) ? "contains" : "does not contain",
-                 contained.left(), contained.top(), contained.right(), contained.bottom());
-    }
-#StdOut
-rect: (30, 50, 40, 60) contains (30, 50, 31, 51)
-rect: (30, 50, 40, 60) does not contain (39, 49, 40, 50)
-rect: (30, 50, 40, 60) does not contain (29, 59, 30, 60)
-##
-##
-
-#SeeAlso contains SkRect::contains
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool intersect(const SkIRect& r)
-
-#In Intersection
-#Line # sets to shared area; returns true if not empty ##
-#Populate
-
-#Example
-#Description
-Two SkDebugf calls are required. If the calls are combined, their arguments
-may not be evaluated in left to right order: the printed intersection may
-be before or after the call to intersect.
-##
-    SkIRect leftRect =  { 10, 40, 50, 80 };
-    SkIRect rightRect = { 30, 60, 70, 90 };
-    SkDebugf("%s intersection: ", leftRect.intersect(rightRect) ? "" : "no ");
-    SkDebugf("%d, %d, %d, %d\n", leftRect.left(), leftRect.top(),
-                                 leftRect.right(), leftRect.bottom());
-#StdOut
- intersection: 30, 60, 50, 80
-##
-##
-
-#SeeAlso Intersects intersectNoEmptyCheck join SkRect::intersect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool intersect(const SkIRect& a, const SkIRect& b)
-
-#In Intersection
-#Populate
-
-#Example
-    SkIRect result;
-    bool intersected = result.intersect({ 10, 40, 50, 80 }, { 30, 60, 70, 90 });
-    SkDebugf("%s intersection: %d, %d, %d, %d\n", intersected ? "" : "no ",
-             result.left(), result.top(), result.right(), result.bottom());
-#StdOut
- intersection: 30, 60, 50, 80
-##
-##
-
-#SeeAlso Intersects intersectNoEmptyCheck join SkRect::intersect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool intersectNoEmptyCheck(const SkIRect& a, const SkIRect& b)
-
-#In Intersection
-#Line # sets to shared area; returns true if not empty skips empty check ##
-#Populate
-
-#Example
-    SkIRect result;
-    if (result.intersectNoEmptyCheck({ 10, 40, 50, 80 }, { 30, 60, 70, 90 })) {
-        SkDebugf("intersection: %d, %d, %d, %d\n",
-                 result.left(), result.top(), result.right(), result.bottom());
-    }
-#StdOut
- intersection: 30, 60, 50, 80
-##
-##
-
-#SeeAlso Intersects intersect join SkRect::intersect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool intersect(int32_t left, int32_t top, int32_t right, int32_t bottom)
-
-#In Intersection
-#Populate
-
-#Example
-#Description
-Two SkDebugf calls are required. If the calls are combined, their arguments
-may not be evaluated in left to right order: the printed intersection may
-be before or after the call to intersect.
-##
-    SkIRect leftRect =  { 10, 40, 50, 80 };
-    SkDebugf("%s intersection: ", leftRect.intersect(30, 60, 70, 90) ? "" : "no ");
-    SkDebugf("%d, %d, %d, %d\n", leftRect.left(), leftRect.top(),
-                                 leftRect.right(), leftRect.bottom());
-#StdOut
- intersection: 30, 60, 50, 80
-##
-##
-
-#SeeAlso intersectNoEmptyCheck Intersects join SkRect::intersect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static bool Intersects(const SkIRect& a, const SkIRect& b)
-
-#In Intersection
-#Line # returns true if areas overlap ##
-#Populate
-
-#Example
-    SkDebugf("%s intersection", SkIRect::Intersects({10, 40, 50, 80}, {30, 60, 70, 90}) ? "" : "no ");
-#StdOut
- intersection
-##
-##
-
-#SeeAlso IntersectsNoEmptyCheck intersect SkRect::intersect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static bool IntersectsNoEmptyCheck(const SkIRect& a, const SkIRect& b)
-
-#In Intersection
-#Line # returns true if areas overlap skips empty check ##
-#Populate
-
-#Example
-    SkDebugf("%s intersection", SkIRect::IntersectsNoEmptyCheck(
-            {10, 40, 50, 80}, {30, 60, 70, 90}) ? "" : "no ");
-#StdOut
- intersection
-##
-##
-
-#SeeAlso Intersects intersect SkRect::intersect
-
-##
-
-#Subtopic Intersection ##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Join
-#Line # sets to union of bounds ##
-##
-
-#Method void join(int32_t left, int32_t top, int32_t right, int32_t bottom)
-
-#In Join
-#Line # sets to union of bounds ##
-#Populate
-
-#Example
-    SkIRect rect = { 10, 20, 15, 25};
-    rect.join(50, 60, 55, 65);
-    SkDebugf("join: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-#StdOut
- join: 10, 20, 55, 65
-##
-##
-
-#SeeAlso set SkRect::join
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void join(const SkIRect& r)
-
-#In Join
-#Populate
-
-#Example
-    SkIRect rect = { 10, 20, 15, 25};
-    rect.join({50, 60, 55, 65});
-    SkDebugf("join: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-#StdOut
- join: 10, 20, 55, 65
-##
-##
-
-#SeeAlso set SkRect::join
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Sorting
-#Line # orders sides ##
-##
-
-#Method void sort()
-
-#In Sorting
-#Line # orders sides from smaller to larger ##
-#Populate
-
-#Example
-    SkIRect rect = { 30, 50, 20, 10 };
-    SkDebugf("rect: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-    rect.sort();
-    SkDebugf("sorted: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-#StdOut
-rect: 30, 50, 20, 10
-sorted: 20, 10, 30, 50
-##
-##
-
-#SeeAlso makeSorted SkRect::sort
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkIRect makeSorted() const
-
-#In Sorting
-#In Constructors
-#Line # constructs IRect, ordering sides from smaller to larger ##
-#Populate
-
-#Example
-    SkIRect rect = { 30, 50, 20, 10 };
-    SkDebugf("rect: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-    SkIRect sort = rect.makeSorted();
-    SkDebugf("sorted: %d, %d, %d, %d\n", sort.fLeft, sort.fTop, sort.fRight, sort.fBottom);
-#StdOut
-rect: 30, 50, 20, 10
-sorted: 20, 10, 30, 50
-##
-##
-
-#SeeAlso sort SkRect::makeSorted
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static const SkIRect& EmptyIRect()
-
-#In Constructors
-#Line # returns immutable bounds of (0, 0, 0, 0) ##
-#Populate
-
-#Example
-    const SkIRect& rect = SkIRect::EmptyIRect();
-    SkDebugf("rect: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-#StdOut
-rect: 0, 0, 0, 0
-##
-##
-
-#SeeAlso MakeEmpty
-
-##
-
-#Struct SkIRect ##
-
-#Topic IRect ##
diff --git a/docs/SkImageInfo_Reference.bmh b/docs/SkImageInfo_Reference.bmh
deleted file mode 100644
index 6297712..0000000
--- a/docs/SkImageInfo_Reference.bmh
+++ /dev/null
@@ -1,2113 +0,0 @@
-#Topic Image_Info
-#Alias Image_Info_Reference ##
-
-#Code
-#Populate
-##
-
-Image_Info specifies the dimensions and encoding of the pixels in a Bitmap.
-The dimensions are integral width and height. The encoding is how pixel
-bits describe Color_Alpha, transparency; Color components red, blue,
-and green; and Color_Space, the range and linearity of colors.
-
-Image_Info describes an uncompressed raster pixels. In contrast, Image
-additionally describes compressed pixels like PNG, and Surface describes
-destinations on the GPU. Image and Surface may be specified by Image_Info,
-but Image and Surface may not contain Image_Info.
-
-# ------------------------------------------------------------------------------
-#Subtopic Alpha_Type
-#Line # encoding for pixel transparency ##
-#Alias Alpha_Type ##
-#Alias Alpha_Types ##
-
-#PhraseDef list_of_alpha_types
-kUnknown_SkAlphaType, kOpaque_SkAlphaType, kPremul_SkAlphaType,
-kUnpremul_SkAlphaType
-##
-
-#Enum SkAlphaType
-#Line # encoding for pixel transparency ##
-
-#Code
-#Populate
-##
-
-Describes how to interpret the alpha component of a pixel. A pixel may
-be opaque, or Color_Alpha, describing multiple levels of transparency.
-
-In simple blending, Color_Alpha weights the draw color and the destination
-color to create a new color. If alpha describes a weight from zero to one,
-new color is set to: #Formula # draw color * alpha + destination color * (1 - alpha) ##.
-
-In practice alpha is encoded in two or more bits, where 1.0 equals all bits set.
-
-RGB may have Color_Alpha included in each component value; the stored
-value is the original RGB multiplied by Color_Alpha. Premultiplied color
-components improve performance.
-
-#Const kUnknown_SkAlphaType 0
-#Line # uninitialized ##
-    Alpha_Type is uninitialized.
-##
-#Const kOpaque_SkAlphaType 1
-#Line # pixel is opaque ##
-#Details Opaque
-    Pixels are opaque. The Color_Type must have no explicit alpha
-    component, or all alpha components must be set to their maximum value.
-##
-#Const kPremul_SkAlphaType 2
-#Line # pixel components are Premultiplied by Alpha ##
-#Details Premul
-    Pixels have Alpha Premultiplied into color components.
-    Surface pixels must be Premultiplied.
-##
-#Const kUnpremul_SkAlphaType 3
-#Line # pixel components are independent of Alpha ##
-#Details Unpremul
-    Pixel color component values are independent of alpha value.
-    Images generated from encoded data like PNG do not Premultiply pixel color
-    components. kUnpremul_SkAlphaType is supported for Image pixels, but not for
-    Surface pixels.
-##
-#Const kLastEnum_SkAlphaType 3
-#Line # last valid value ##
-    Used by tests to iterate through all valid values.
-##
-
-#NoExample
-##
-
-#SeeAlso SkColorType SkColorSpace
-
-#Enum SkAlphaType ##
-
-#Subtopic Opaque
-#Line # hints all pixels are opaque ##
-Use kOpaque_SkAlphaType as a hint to optimize drawing when Alpha component
-of all pixel is set to its maximum value of 1.0; all alpha component bits are set.
-If Image_Info is set to kOpaque_SkAlphaType but all alpha values are not 1.0,
-results are undefined.
-
-#Example
-#Height 64
-#Description
-SkPreMultiplyARGB parameter a is set to 255, its maximum value, and is interpreted
-as Color_Alpha of 1.0. kOpaque_SkAlphaType may be set to improve performance.
-If SkPreMultiplyARGB parameter a is set to a value smaller than 255,
-kPremul_SkAlphaType must be used instead to avoid undefined results.
-The four displayed values are the original component values, though not necessarily
-in the same order.
-##
-    SkPMColor color = SkPreMultiplyARGB(255, 50, 100, 150);
-    SkString s;
-    s.printf("%u %u %u %u", SkColorGetA(color), SkColorGetR(color),
-                            SkColorGetG(color), SkColorGetB(color));
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    canvas->drawString(s, 10, 62, paint);
-    canvas->scale(50, 50);
-    SkBitmap bitmap;
-    SkImageInfo imageInfo = SkImageInfo::Make(1, 1, kN32_SkColorType, kOpaque_SkAlphaType);
-    if (bitmap.installPixels(imageInfo, (void*) &color, imageInfo.minRowBytes())) {
-        canvas->drawBitmap(bitmap, 0, 0);
-    }
-##
-
-#Subtopic Opaque ##
-
-#Subtopic Premul
-#Line # stores components scaled by Alpha ##
-Use kPremul_SkAlphaType when stored color components are the original color
-multiplied by the alpha component. The alpha component range of 0.0 to 1.0 is
-achieved by dividing the integer bit value by the maximum bit value.
-
-#Code
-stored color = original color * alpha / max alpha
-##
-
-The color component must be equal to or smaller than the alpha component,
-or the results are undefined.
-
-#Example
-#Description
-SkPreMultiplyARGB parameter a is set to 150, less than its maximum value, and is
-interpreted as Color_Alpha of about 0.6. kPremul_SkAlphaType must be set, since
-SkPreMultiplyARGB parameter a is set to a value smaller than 255,
-to avoid undefined results.
-The four displayed values reflect that the alpha component has been multiplied
-by the original color.
-##
-#Height 64
-    SkPMColor color = SkPreMultiplyARGB(150, 50, 100, 150);
-    SkString s;
-    s.printf("%u %u %u %u", SkColorGetA(color), SkColorGetR(color),
-                            SkColorGetG(color), SkColorGetB(color));
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    canvas->drawString(s, 10, 62, paint);
-    canvas->scale(50, 50);
-    SkBitmap bitmap;
-    SkImageInfo imageInfo = SkImageInfo::Make(1, 1, kN32_SkColorType, kPremul_SkAlphaType);
-    if (bitmap.installPixels(imageInfo, (void*) &color, imageInfo.minRowBytes())) {
-        canvas->drawBitmap(bitmap, 0, 0);
-    }
-##
-
-#Subtopic Premul ##
-
-#Subtopic Unpremul
-#Line # stores components without Alpha scaling ##
-Use kUnpremul_SkAlphaType if stored color components are not divided by the
-alpha component. Some drawing destinations may not support
-kUnpremul_SkAlphaType.
-
-#Bug 7079
-#Example
-#Height 64
-#Description
-SkColorSetARGB parameter a is set to 150, less than its maximum value, and is
-interpreted as Color_Alpha of about 0.6. color is not Premultiplied;
-color components may have values greater than color alpha.
-The four displayed values are the original component values, though not necessarily
-in the same order.
-##
-    SkColor color = SkColorSetARGB(150, 50, 100, 255);
-    SkString s;
-    s.printf("%u %u %u %u", SkColorGetA(color), SkColorGetR(color),
-                            SkColorGetG(color), SkColorGetB(color));
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    canvas->drawString(s, 10, 62, paint);
-    canvas->scale(50, 50);
-    SkBitmap bitmap;
-    SkImageInfo imageInfo = SkImageInfo::Make(1, 1, kN32_SkColorType, kUnpremul_SkAlphaType);
-    if (bitmap.installPixels(imageInfo, (void*) &color, imageInfo.minRowBytes())) {
-        canvas->drawBitmap(bitmap, 0, 0);
-    }
-##
-
-#Subtopic Unpremul ##
-
-#Method static bool SkAlphaTypeIsOpaque(SkAlphaType at)
-#In Property
-#Line # returns if Alpha_Type equals kOpaque_SkAlphaType ##
-#Populate
-
-#NoExample
-##
-##
-
-#Subtopic Alpha_Type ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Color_Type
-#Line # encoding for pixel color ##
-#Alias Color_Type ##
-#Alias Color_Types ##
-
-#PhraseDef list_of_color_types
-kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType,
-kARGB_4444_SkColorType, kRGBA_8888_SkColorType, kRGB_888x_SkColorType,
-kBGRA_8888_SkColorType, kRGBA_1010102_SkColorType, kRGB_101010x_SkColorType,
-kGray_8_SkColorType, kRGBA_F16_SkColorType
-##
-
-#Enum SkColorType
-#Line # encoding for pixel color ##
-
-#Code
-#Populate
-##
-
-Describes how pixel bits encode color. A pixel may be an alpha mask, a
-grayscale, RGB, or ARGB.
-
-kN32_SkColorType selects the native 32-bit ARGB format. On Little_Endian
-processors, pixels containing 8-bit ARGB components pack into 32-bit
-kBGRA_8888_SkColorType. On Big_Endian processors, pixels pack into 32-bit
-kRGBA_8888_SkColorType.
-
-#Const kUnknown_SkColorType 0
-#Line # uninitialized ##
-    Color_Type is set to kUnknown_SkColorType by default. If set,
-    encoding format and size is unknown.
-##
-
-#Const kAlpha_8_SkColorType 1
-#Line # pixel with Alpha in 8-bit byte ##
-#Details Alpha_8
-    Stores 8-bit byte pixel encoding that represents transparency. Value of zero
-    is completely transparent; a value of 255 is completely opaque.
-##
-
-#Const kRGB_565_SkColorType 2
-#Line # pixel with 5 bits red, 6 bits green, 5 bits blue, in 16-bit word ##
-#Details RGB_565
-    Stores 16-bit word pixel encoding that contains five bits of blue,
-    six bits of green, and five bits of red.
-##
-
-#Const kARGB_4444_SkColorType 3
-#Line # pixel with 4 bits for alpha, red, green, blue; in 16-bit word ##
-#Details ARGB_4444
-    Stores 16-bit word pixel encoding that contains four bits of alpha,
-    four bits of blue, four bits of green, and four bits of red.
-##
-
-#Const kRGBA_8888_SkColorType 4
-#Line # pixel with 8 bits for red, green, blue, alpha; in 32-bit word ##
-#Details RGBA_8888
-    Stores 32-bit word pixel encoding that contains eight bits of red,
-    eight bits of green, eight bits of blue, and eight bits of alpha.
-##
-
-#Const kRGB_888x_SkColorType 5
-#Line # pixel with 8 bits each for red, green, blue; in 32-bit word ##
-#Details RGB_888
-    Stores 32-bit word pixel encoding that contains eight bits of red,
-    eight bits of green, eight bits of blue, and eight unused bits.
-##
-
-#Const kBGRA_8888_SkColorType 6
-#Line # pixel with 8 bits for blue, green, red, alpha; in 32-bit word ##
-#Details BGRA_8888
-    Stores 32-bit word pixel encoding that contains eight bits of blue,
-    eight bits of green, eight bits of red, and eight bits of alpha.
-##
-
-#Const kRGBA_1010102_SkColorType 7
-#Line # 10 bits for red, green, blue; 2 bits for alpha; in 32-bit word ##
-#Details RGBA_1010102
-    Stores 32-bit word pixel encoding that contains ten bits of red,
-    ten bits of green, ten bits of blue, and two bits of alpha.
-##
-
-#Const kRGB_101010x_SkColorType 8
-#Line # pixel with 10 bits each for red, green, blue; in 32-bit word ##
-#Details RGB_101010
-    Stores 32-bit word pixel encoding that contains ten bits of red,
-    ten bits of green, ten bits of blue, and two unused bits.
-##
-
-#Const kGray_8_SkColorType 9
-#Line # pixel with grayscale level in 8-bit byte ##
-#Details Gray_8
-    Stores 8-bit byte pixel encoding that equivalent to equal values for red,
-    blue, and green, representing colors from black to white.
-##
-
-#Const kRGBA_F16_SkColorType 10
-#Line # pixel with half floats for red, green, blue, alpha; in 64-bit word ##
-#Details RGBA_F16
-    Stores 64-bit word pixel encoding that contains 16 bits of blue,
-    16 bits of green, 16 bits of red, and 16 bits of alpha. Each component
-    is encoded as a half float.
-##
-
-#Const kRGBA_F32_SkColorType 11
-#Line # pixel using C float for red, green, blue, alpha; in 128-bit word ##
-#Details RGBA_F32
-    Stores 128-bit word pixel encoding that contains 32 bits of blue,
-    32 bits of green, 32 bits of red, and 32 bits of alpha. Each component
-    is encoded as a single precision float.
-##
-
-#Const kLastEnum_SkColorType 11
-#NoJustify
-#Line # last valid value ##
-    Used by tests to iterate through all valid values.
-##
-
-#Const kN32_SkColorType 4 or 6
-#Alias Native_Color_Type ##
-#NoJustify
-#Line # native ARGB 32-bit encoding ##
-    Encodes ARGB as either kRGBA_8888_SkColorType or
-    kBGRA_8888_SkColorType, whichever is native to the platform.
-##
-
-#NoExample
-##
-
-#SeeAlso SkAlphaType SkColorSpace
-
-#Enum SkColorType ##
-
-#Subtopic Alpha_8
-#Line # encodes transparency only ##
-    Alpha pixels encode transparency without color information. Value of zero is
-    completely transparent; a value of 255 is completely opaque. Bitmap
-    pixels do not visibly draw, because its pixels have no color information.
-    When SkColorType is set to kAlpha_8_SkColorType, the paired SkAlphaType is
-    ignored.
-
-    #Example
-        #Description
-        Alpha pixels can modify another draw. orangePaint fills the bounds of bitmap,
-        with its transparency set to alpha8 pixel value.
-        ##
-        #Height 64
-        canvas->scale(16, 16);
-        SkBitmap bitmap;
-        SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kAlpha_8_SkColorType, kOpaque_SkAlphaType);
-        bitmap.allocPixels(imageInfo);
-        SkCanvas offscreen(bitmap);
-        offscreen.clear(SK_ColorGREEN);
-        SkPaint orangePaint;
-        orangePaint.setARGB(0xFF, 0xFF, 0xA5, 0x00);
-        canvas->drawBitmap(bitmap, 0, 0, &orangePaint);
-        uint8_t alpha8[] = { 0xFF, 0xBB, 0x77, 0x33 };
-        SkPixmap alphaPixmap(imageInfo, &alpha8, imageInfo.minRowBytes());
-        if (bitmap.writePixels(alphaPixmap, 0, 0)) {
-            canvas->drawBitmap(bitmap, 2, 2, &orangePaint);
-        }
-    ##
-    #SeeAlso Alpha Gray_8
-##
-
-#Subtopic RGB_565
-#Line # encodes RGB in 16 bits ##
-    kRGB_565_SkColorType encodes RGB to fit in a 16-bit word. Red and blue
-    components use five bits describing 32 levels. Green components, more sensitive
-    to the eye, use six bits describing 64 levels. kRGB_565_SkColorType has no
-    bits for Alpha.
-    
-    Pixels are fully opaque as if its Color_Alpha was set to one, and should
-    always be paired with kOpaque_SkAlphaType.
-
-    #Illustration
-
-    #Example
-    #Height 96
-        canvas->scale(16, 16);
-        SkBitmap bitmap;
-        SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kRGB_565_SkColorType, kOpaque_SkAlphaType);
-        bitmap.allocPixels(imageInfo);
-        SkCanvas offscreen(bitmap);
-        offscreen.clear(SK_ColorGREEN);
-        canvas->drawBitmap(bitmap, 0, 0);
-        auto pack565 = [](unsigned r, unsigned g, unsigned b) -> uint16_t {
-            return (b << 0) | (g << 5) | (r << 11);
-        };
-        uint16_t red565[] =  { pack565(0x1F, 0x00, 0x00), pack565(0x17, 0x00, 0x00),
-                               pack565(0x0F, 0x00, 0x00), pack565(0x07, 0x00, 0x00) };
-        uint16_t blue565[] = { pack565(0x00, 0x00, 0x1F), pack565(0x00, 0x00, 0x17),
-                               pack565(0x00, 0x00, 0x0F), pack565(0x00, 0x00, 0x07) };
-        SkPixmap redPixmap(imageInfo, &red565, imageInfo.minRowBytes());
-        if (bitmap.writePixels(redPixmap, 0, 0)) {
-            canvas->drawBitmap(bitmap, 2, 2);
-        }
-        SkPixmap bluePixmap(imageInfo, &blue565, imageInfo.minRowBytes());
-        if (bitmap.writePixels(bluePixmap, 0, 0)) {
-            canvas->drawBitmap(bitmap, 4, 4);
-        }
-    ##
-    #SeeAlso ARGB_4444 RGBA_8888
-##
-
-#Subtopic ARGB_4444
-#Line # encodes ARGB in 16 bits ##
-    kARGB_4444_SkColorType encodes ARGB to fit in 16-bit word. Each
-    component: alpha, blue, green, and red; use four bits, describing 16 levels.
-    Note that kARGB_4444_SkColorType is misnamed; the acronym does not
-    describe the actual component order.
-
-    #Illustration
-
-    If paired with kPremul_SkAlphaType: blue, green, and red components are
-    Premultiplied by the alpha value. If blue, green, or red is greater than alpha,
-    the drawn result is undefined.
-
-    If paired with kUnpremul_SkAlphaType: alpha, blue, green, and red components
-    may have any value. There may be a performance penalty with Unpremultiplied
-    pixels.
-
-    If paired with kOpaque_SkAlphaType: all alpha component values are at the maximum;
-    blue, green, and red components are fully opaque. If any alpha component is
-    less than 15, the drawn result is undefined.
-
-    #Bug 7648
-
-    #Example
-    #Height 96
-        canvas->scale(16, 16);
-        SkBitmap bitmap;
-        SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kARGB_4444_SkColorType, kPremul_SkAlphaType);
-        bitmap.allocPixels(imageInfo);
-        SkCanvas offscreen(bitmap);
-        offscreen.clear(SK_ColorGREEN);
-        canvas->drawBitmap(bitmap, 0, 0);
-        auto pack4444 = [](unsigned a, unsigned r, unsigned g, unsigned b) -> uint16_t {
-            return (a << 0) | (b << 4) | (g << 8) | (r << 12);
-        };
-        uint16_t red4444[] =  { pack4444(0xF, 0xF, 0x0, 0x0), pack4444(0xF, 0xb, 0x0, 0x0),
-                                pack4444(0xF, 0x7, 0x0, 0x0), pack4444(0xF, 0x3, 0x0, 0x0) };
-        uint16_t blue4444[] = { pack4444(0xF, 0x0, 0x0, 0xF), pack4444(0xF, 0x0, 0x0, 0xb),
-                                pack4444(0xF, 0x0, 0x0, 0x7), pack4444(0xF, 0x0, 0x0, 0x3) };
-        SkPixmap redPixmap(imageInfo, &red4444, imageInfo.minRowBytes());
-        if (bitmap.writePixels(redPixmap, 0, 0)) {
-            canvas->drawBitmap(bitmap, 2, 2);
-        }
-        SkPixmap bluePixmap(imageInfo, &blue4444, imageInfo.minRowBytes());
-        if (bitmap.writePixels(bluePixmap, 0, 0)) {
-            canvas->drawBitmap(bitmap, 4, 4);
-        }
-    ##
-    #SeeAlso RGBA_8888
-##
-
-#Subtopic RGBA_8888
-#Line # encodes ARGB Big_Endian in 32 bits ##
-    kRGBA_8888_SkColorType encodes ARGB into a 32-bit word. Each component:
-    red, green, blue, alpha; use eight bits, describing 256 levels.
-
-    #Illustration
-
-    If paired with kPremul_SkAlphaType: red, green, and blue components are
-    Premultiplied by the alpha value. If red, green, or blue is greater than alpha,
-    the drawn result is undefined.
-
-    If paired with kUnpremul_SkAlphaType: alpha, red, green, and blue components
-    may have any value. There may be a performance penalty with Unpremultiplied
-    pixels.
-
-    If paired with kOpaque_SkAlphaType: all alpha component values are at the maximum;
-    red, green, and blue components are fully opaque. If any alpha component is
-    less than 255, the drawn result is undefined.
-
-    On Big_Endian platforms, kRGBA_8888_SkColorType is the native Color_Type, and
-    will have the best performance. Use kN32_SkColorType to choose the best
-    Color_Type for the platform at compile time.
-
-    #Example
-    #Height 96
-        canvas->scale(16, 16);
-        SkBitmap bitmap;
-        SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
-        bitmap.allocPixels(imageInfo);
-        SkCanvas offscreen(bitmap);
-        offscreen.clear(SK_ColorGREEN);
-        canvas->drawBitmap(bitmap, 0, 0);
-        auto pack8888 = [](unsigned a, unsigned r, unsigned g, unsigned b) -> uint32_t {
-            return (r << 0) | (g << 8) | (b << 16) | (a << 24);
-        };
-        uint32_t red8888[] = { pack8888(0xFF, 0xFF, 0x0, 0x0), pack8888(0xFF, 0xbb, 0x0, 0x0),
-                               pack8888(0xFF, 0x77, 0x0, 0x0), pack8888(0xFF, 0x33, 0x0, 0x0) };
-        uint32_t blue8888[] = { pack8888(0xFF, 0x0, 0x0, 0x0FF), pack8888(0xFF, 0x0, 0x0, 0x0bb),
-                                pack8888(0xFF, 0x0, 0x0, 0x077), pack8888(0xFF, 0x0, 0x0, 0x033) };
-        SkPixmap redPixmap(imageInfo, &red8888, imageInfo.minRowBytes());
-        if (bitmap.writePixels(redPixmap, 0, 0)) {
-            canvas->drawBitmap(bitmap, 2, 2);
-        }
-        SkPixmap bluePixmap(imageInfo, &blue8888, imageInfo.minRowBytes());
-        if (bitmap.writePixels(bluePixmap, 0, 0)) {
-            canvas->drawBitmap(bitmap, 4, 4);
-        }
-    ##
-    #SeeAlso RGB_888 BGRA_8888
-##
-
-#Subtopic RGB_888
-#Line # encodes RGB in 32 bits ##
-    kRGB_888x_SkColorType encodes RGB into a 32-bit word. Each component:
-    red, green, blue; use eight bits, describing 256 levels. Eight bits are
-    unused. Pixels described by kRGB_888x_SkColorType are fully opaque as if 
-    their Color_Alpha was set to one, and should always be paired with
-    kOpaque_SkAlphaType.
-
-    #Illustration
-
-    #Example
-    #Bug 7645
-    #Height 96
-    #Platform cpu
-        canvas->scale(16, 16);
-        SkBitmap bitmap;
-        SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kRGB_888x_SkColorType, kOpaque_SkAlphaType);
-        bitmap.allocPixels(imageInfo);
-        SkCanvas offscreen(bitmap);
-        offscreen.clear(SK_ColorGREEN);
-        canvas->drawBitmap(bitmap, 0, 0);
-        auto pack888 = [](unsigned r, unsigned g, unsigned b) -> uint32_t {
-            return (r << 0) | (g << 8) | (b << 16);
-        };
-        uint32_t red888[] =  { pack888(0xFF, 0x00, 0x00), pack888(0xbb, 0x00, 0x00),
-            pack888(0x77, 0x00, 0x00), pack888(0x33, 0x00, 0x00) };
-        uint32_t blue888[] = { pack888(0x00, 0x00, 0xFF), pack888(0x00, 0x00, 0xbb),
-            pack888(0x00, 0x00, 0x77), pack888(0x00, 0x00, 0x33) };
-        if (bitmap.installPixels(imageInfo, (void*) red888, imageInfo.minRowBytes())) {
-            canvas->drawBitmap(bitmap, 2, 2);
-        }
-        if (bitmap.installPixels(imageInfo, (void*) blue888, imageInfo.minRowBytes())) {
-            canvas->drawBitmap(bitmap, 4, 4);
-        }
-    ##
-    #SeeAlso RGBA_8888 BGRA_8888
-##
-
-#Subtopic BGRA_8888
-#Line # encodes ARGB Little_Endian in 32 bits ##
-    kBGRA_8888_SkColorType encodes ARGB into a 32-bit word. Each component:
-    blue, green, red, and alpha; use eight bits, describing 256 levels.
-
-    #Illustration
-
-    If paired with kPremul_SkAlphaType: blue, green, and red components are
-    Premultiplied by the alpha value. If blue, green, or red is greater than alpha,
-    the drawn result is undefined.
-
-    If paired with kUnpremul_SkAlphaType: blue, green, red, and alpha components
-    may have any value. There may be a performance penalty with Unpremultiplied
-    pixels.
-
-    If paired with kOpaque_SkAlphaType: all alpha component values are at the maximum;
-    blue, green, and red components are fully opaque. If any alpha component is
-    less than 255, the drawn result is undefined.
-
-    On Little_Endian platforms, kBGRA_8888_SkColorType is the native Color_Type,
-    and will have the best performance. Use kN32_SkColorType to choose the best
-    Color_Type for the platform at compile time.
-
-    #Example
-    #Height 96
-        canvas->scale(16, 16);
-        SkBitmap bitmap;
-        SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kBGRA_8888_SkColorType, kPremul_SkAlphaType);
-        bitmap.allocPixels(imageInfo);
-        SkCanvas offscreen(bitmap);
-        offscreen.clear(SK_ColorGREEN);
-        canvas->drawBitmap(bitmap, 0, 0);
-        auto pack8888 = [](unsigned a, unsigned r, unsigned g, unsigned b) -> uint32_t {
-            return (b << 0) | (g << 8) | (r << 16) | (a << 24);
-        };
-        uint32_t red8888[] = { pack8888(0xFF, 0xFF, 0x0, 0x0), pack8888(0xFF, 0xbb, 0x0, 0x0),
-                               pack8888(0xFF, 0x99, 0x0, 0x0), pack8888(0xFF, 0x55, 0x0, 0x0) };
-        uint32_t blue8888[] = { pack8888(0xFF, 0x0, 0x0, 0x0FF), pack8888(0xFF, 0x0, 0x0, 0x0bb),
-                                pack8888(0xFF, 0x0, 0x0, 0x099), pack8888(0xFF, 0x0, 0x0, 0x055) };
-        SkPixmap redPixmap(imageInfo, &red8888, imageInfo.minRowBytes());
-        if (bitmap.writePixels(redPixmap, 0, 0)) {
-            canvas->drawBitmap(bitmap, 2, 2);
-        }
-        SkPixmap bluePixmap(imageInfo, &blue8888, imageInfo.minRowBytes());
-        if (bitmap.writePixels(bluePixmap, 0, 0)) {
-            canvas->drawBitmap(bitmap, 4, 4);
-        }
-    ##
-    #SeeAlso RGBA_8888
-##
-
-#Subtopic RGBA_1010102
-#Line # encodes ARGB ten bits per color component ##
-    kRGBA_1010102_SkColorType encodes ARGB into a 32-bit word. Each
-    Color component: red, green, and blue; use ten bits, describing 1024 levels.
-    Two bits contain alpha, describing four levels. Possible alpha
-    values are zero: fully transparent; one: 33% opaque; two: 67% opaque;
-    three: fully opaque.
-
-    At present, Color in Paint does not provide enough precision to
-    draw all colors possible to a kRGBA_1010102_SkColorType Surface.
-
-    #Illustration
-
-    If paired with kPremul_SkAlphaType: red, green, and blue components are
-    Premultiplied by the alpha value. If red, green, or blue is greater than the
-    alpha replicated to ten bits, the drawn result is undefined.
-
-    If paired with kUnpremul_SkAlphaType: alpha, red, green, and blue components
-    may have any value. There may be a performance penalty with Unpremultiplied
-    pixels.
-
-    If paired with kOpaque_SkAlphaType: all alpha component values are at the maximum;
-    red, green, and blue components are fully opaque. If any alpha component is
-    less than three, the drawn result is undefined.
-
-    #Example
-    #Bug 7645
-    #Height 96
-    #Platform cpu
-        canvas->scale(16, 16);
-        SkBitmap bitmap;
-        SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kRGBA_1010102_SkColorType, kOpaque_SkAlphaType);
-        bitmap.allocPixels(imageInfo);
-        SkCanvas offscreen(bitmap);
-        offscreen.clear(SK_ColorGREEN);
-        canvas->drawBitmap(bitmap, 0, 0);
-        auto pack1010102 = [](unsigned r, unsigned g, unsigned b, unsigned a) -> uint32_t {
-            return (r << 0) | (g << 10) | (b << 20) | (a << 30);
-        };
-        uint32_t redBits[] =  { pack1010102(0x3FF, 0x000, 0x000, 0x3),
-                                pack1010102(0x2ff, 0x000, 0x000, 0x3),
-                                pack1010102(0x1ff, 0x000, 0x000, 0x3),
-                                pack1010102(0x0ff, 0x000, 0x000, 0x3) };
-        uint32_t blueBits[] = { pack1010102(0x000, 0x000, 0x3FF, 0x3),
-                                pack1010102(0x000, 0x000, 0x2ff, 0x3),
-                                pack1010102(0x000, 0x000, 0x1ff, 0x3),
-                                pack1010102(0x000, 0x000, 0x0ff, 0x3) };
-        if (bitmap.installPixels(imageInfo, (void*) redBits, imageInfo.minRowBytes())) {
-            canvas->drawBitmap(bitmap, 2, 2);
-        }
-        SkPixmap bluePixmap(imageInfo, &blueBits, imageInfo.minRowBytes());
-        if (bitmap.installPixels(imageInfo, (void*) blueBits, imageInfo.minRowBytes())) {
-            canvas->drawBitmap(bitmap, 4, 4);
-        }
-    ##
-    #SeeAlso RGB_101010 RGBA_8888
-##
-
-#Subtopic RGB_101010
-#Line # encodes RGB ten bits per color component ##
-    kRGB_101010x_SkColorType encodes RGB into a 32-bit word. Each
-    Color component: red, green, and blue; use ten bits, describing 1024 levels.
-    Two bits are unused. Pixels described by kRGB_101010x_SkColorType are fully
-    opaque as if its Color_Alpha was set to one, and should always be paired
-    with kOpaque_SkAlphaType.
-
-    At present, Color in Paint does not provide enough precision to
-    draw all colors possible to a kRGB_101010x_SkColorType Surface.
-
-    #Illustration
-
-    #Example
-    #Bug 7645
-    #Height 96
-    #Platform cpu
-        canvas->scale(16, 16);
-        SkBitmap bitmap;
-        SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kRGB_101010x_SkColorType, kOpaque_SkAlphaType);
-        bitmap.allocPixels(imageInfo);
-        SkCanvas offscreen(bitmap);
-        offscreen.clear(SK_ColorGREEN);
-        canvas->drawBitmap(bitmap, 0, 0);
-        auto pack101010x = [](unsigned r, unsigned g, unsigned b) -> uint32_t {
-            return (r << 0) | (g << 10) | (b << 20);
-        };
-        uint32_t redBits[] =  { pack101010x(0x3FF, 0x000, 0x000), pack101010x(0x2ff, 0x000, 0x000),
-        pack101010x(0x1ff, 0x000, 0x000), pack101010x(0x0ff, 0x000, 0x000) };
-        uint32_t blueBits[] = { pack101010x(0x000, 0x000, 0x3FF), pack101010x(0x000, 0x000, 0x2ff),
-        pack101010x(0x000, 0x000, 0x1ff), pack101010x(0x000, 0x000, 0x0ff) };
-        if (bitmap.installPixels(imageInfo, (void*) redBits, imageInfo.minRowBytes())) {
-            canvas->drawBitmap(bitmap, 2, 2);
-        }
-        SkPixmap bluePixmap(imageInfo, &blueBits, imageInfo.minRowBytes());
-        if (bitmap.installPixels(imageInfo, (void*) blueBits, imageInfo.minRowBytes())) {
-            canvas->drawBitmap(bitmap, 4, 4);
-        }
-    ##
-    #SeeAlso RGBA_1010102
-##
-
-#Subtopic Gray_8
-#Line # encodes level of grayscale in 8 bits ##
-    kGray_8_SkColorType encodes grayscale level in eight bits that is equivalent
-    to equal values for red, blue, and green, representing colors from black to
-    white.  Pixels described by kGray_8_SkColorType are fully
-    opaque as if its Color_Alpha was set to one, and should always be paired with
-    kOpaque_SkAlphaType.
-
-    #Example
-    #Height 64
-        canvas->scale(16, 16);
-        SkBitmap bitmap;
-        SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kGray_8_SkColorType, kOpaque_SkAlphaType);
-        bitmap.allocPixels(imageInfo);
-        SkCanvas offscreen(bitmap);
-        offscreen.clear(SK_ColorGREEN);
-        canvas->drawBitmap(bitmap, 0, 0);
-        uint8_t gray8[] = { 0xFF, 0xBB, 0x77, 0x33 };
-        SkPixmap grayPixmap(imageInfo, &gray8, imageInfo.minRowBytes());
-        if (bitmap.writePixels(grayPixmap, 0, 0)) {
-            canvas->drawBitmap(bitmap, 2, 2);
-        }
-    ##
-    #SeeAlso Alpha_8
-##
-
-#Subtopic RGBA_F16
-#Line # encodes ARGB as half floats ##
-    kRGBA_F16_SkColorType encodes ARGB into a 64-bit word. Each component:
-    blue, green, red, and alpha; use 16 bits, describing a floating point value,
-    from -65500 to 65000 with 3.31 decimal digits of precision.
-
-    At present, Color in Paint does not provide enough precision or range to
-    draw all colors possible to a kRGBA_F16_SkColorType Surface.
-
-    Each component encodes a floating point value using
-    #A Half floats # https://www.khronos.org/opengl/wiki/Small_Float_Formats ##
-    . Meaningful colors are represented by the range 0.0 to 1.0, although smaller
-    and larger values may be useful when used in combination with Transfer_Mode.
-
-    #Illustration
-
-    If paired with kPremul_SkAlphaType: blue, green, and red components are
-    Premultiplied by the alpha value. If blue, green, or red is greater than alpha,
-    the drawn result is undefined.
-
-    If paired with kUnpremul_SkAlphaType: blue, green, red, and alpha components
-    may have any value. There may be a performance penalty with Unpremultiplied
-    pixels.
-
-    If paired with kOpaque_SkAlphaType: all alpha component values are at the maximum;
-    blue, green, and red components are fully opaque. If any alpha component is
-    less than one, the drawn result is undefined.
-
-    #ToDo
-    FloatToHalf should be replaced with SkFloatToHalf if/when that's made public
-    ##
-
-    #Example
-    #Height 96
-    #Function
-    union FloatUIntUnion {
-        uint32_t fUInt;
-        float    fFloat;
-    };
-
-    uint16_t FloatToHalf(float f) {
-        static const FloatUIntUnion magic = { 15 << 23 };
-        static const uint32_t round_mask = ~0xfffu;
-        FloatUIntUnion floatUnion;
-        floatUnion.fFloat = f;
-        uint32_t sign = floatUnion.fUInt & 0x80000000u;
-        floatUnion.fUInt ^= sign;
-        floatUnion.fUInt &= round_mask;
-        floatUnion.fFloat *= magic.fFloat;
-        floatUnion.fUInt -= round_mask;
-        return (floatUnion.fUInt >> 13) | (sign >> 16);
-    }
-    ##
-
-    void draw(SkCanvas* canvas) {
-        canvas->scale(16, 16);
-        SkBitmap bitmap;
-        SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kRGBA_F16_SkColorType, kPremul_SkAlphaType);
-        bitmap.allocPixels(imageInfo);
-        SkCanvas offscreen(bitmap);
-        offscreen.clear(SK_ColorGREEN);
-        canvas->drawBitmap(bitmap, 0, 0);
-        auto H = [](float c) -> uint16_t {
-            return FloatToHalf(c);
-        };
-                                 //     R        G        B        A
-        uint16_t red_f16[][4] =  { { H(1.0), H(0.0), H(0.0), H(1.0) },
-                                   { H(.75), H(0.0), H(0.0), H(1.0) },
-                                   { H(.50), H(0.0), H(0.0), H(1.0) },
-                                   { H(.25), H(0.0), H(0.0), H(1.0) } };
-        uint16_t blue_f16[][4] = { { H(0.0), H(0.0), H(1.0), H(1.0) },
-                                   { H(0.0), H(0.0), H(.75), H(1.0) },
-                                   { H(0.0), H(0.0), H(.50), H(1.0) },
-                                   { H(0.0), H(0.0), H(.25), H(1.0) } };
-        SkPixmap redPixmap(imageInfo, red_f16, imageInfo.minRowBytes());
-        if (bitmap.writePixels(redPixmap, 0, 0)) {
-            canvas->drawBitmap(bitmap, 2, 2);
-        }
-        SkPixmap bluePixmap(imageInfo, blue_f16, imageInfo.minRowBytes());
-        if (bitmap.writePixels(bluePixmap, 0, 0)) {
-            canvas->drawBitmap(bitmap, 4, 4);
-        }
-    }
-    ##
-    #SeeAlso SkColor4f
-##
-
-#Subtopic RGBA_F32
-#Line # encodes ARGB as single precision floats ##
-    kRGBA_F32_SkColorType encodes ARGB into a 128-bit word. Each component:
-    blue, green, red, and alpha; use 32 bits, describing a floating point value,
-    from -3.402823e+38 to 3.402823e+38 with 7.225 decimal digits of precision.
-
-    At present, Color in Paint does not provide enough precision or range to
-    draw all colors possible to a kRGBA_F32_SkColorType Surface.
-
-    Each component encodes a floating point value using
-    #A single-precision floats # https://en.wikipedia.org/wiki/Single-precision_floating-point_format ##
-    . Meaningful colors are represented by the range 0.0 to 1.0, although smaller
-    and larger values may be useful when used in combination with Transfer_Mode.
-
-    #Illustration
-
-    If paired with kPremul_SkAlphaType: blue, green, and red components are
-    Premultiplied by the alpha value. If blue, green, or red is greater than alpha,
-    the drawn result is undefined.
-
-    If paired with kUnpremul_SkAlphaType: blue, green, red, and alpha components
-    may have any value. There may be a performance penalty with Unpremultiplied
-    pixels.
-
-    If paired with kOpaque_SkAlphaType: all alpha component values are at the maximum;
-    blue, green, and red components are fully opaque. If any alpha component is
-    less than one, the drawn result is undefined.
-
-    #NoExample
-        canvas->scale(16, 16);
-        SkBitmap bitmap;
-        SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kRGBA_F32_SkColorType, kPremul_SkAlphaType);
-        bitmap.allocPixels(imageInfo);
-        SkCanvas offscreen(bitmap);
-        offscreen.clear(SK_ColorGREEN);
-        canvas->drawBitmap(bitmap, 0, 0);
-                             //    R    G    B    A
-        float red_f32[][4] =  { { 1.0, 0.0, 0.0, 1.0 },
-                                { .75, 0.0, 0.0, 1.0 },
-                                { .50, 0.0, 0.0, 1.0 },
-                                { .25, 0.0, 0.0, 1.0 } };
-        float blue_f32[][4] = { { 0.0, 0.0, 1.0, 1.0 },
-                                { 0.0, 0.0, .75, 1.0 },
-                                { 0.0, 0.0, .50, 1.0 },
-                                { 0.0, 0.0, .25, 1.0 } };
-        SkPixmap redPixmap(imageInfo, red_f32, imageInfo.minRowBytes());
-        if (bitmap.writePixels(redPixmap, 0, 0)) {
-            canvas->drawBitmap(bitmap, 2, 2);
-        }
-        SkPixmap bluePixmap(imageInfo, blue_f32, imageInfo.minRowBytes());
-        if (bitmap.writePixels(bluePixmap, 0, 0)) {
-            canvas->drawBitmap(bitmap, 4, 4);
-        }
-    ##
-    #SeeAlso SkColor4f
-##
-
-
-#Subtopic Color_Type ##
-
-# ------------------------------------------------------------------------------
-
-#Method int SkColorTypeBytesPerPixel(SkColorType ct)
-#In Property
-#Line # returns Color_Type byte size ##
-#Populate
-
-#Example
-#Height 192
-    const char* colors[] = { "Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
-                             "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16" };
-    SkPaint paint;
-    paint.setTypeface(SkTypeface::MakeFromName("monospace", SkFontStyle()));
-    paint.setAntiAlias(true);
-    paint.setTextSize(10);
-    int y = 15;
-    canvas->drawString("    colorType  bytes", 10, y, paint);
-    for (SkColorType colorType : { #list_of_color_types#
-                                 } ) {
-        int result = SkColorTypeBytesPerPixel(colorType);
-        SkString string;
-        string.printf("%13s %4d", colors[(int) colorType], result);
-        canvas->drawString(string, 10, y += 14, paint);
-    }
-##
-#SeeAlso SkImageInfo::bytesPerPixel
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool SkColorTypeIsAlwaysOpaque(SkColorType ct)
-#In Property
-#Line # returns if Color_Type includes Color_Alpha ##
-#Populate
-
-#Example
-#Height 192
-    const char* colors[] = { "Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
-                             "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16" };
-    SkPaint paint;
-    paint.setTypeface(SkTypeface::MakeFromName("monospace", SkFontStyle()));
-    paint.setAntiAlias(true);
-    paint.setTextSize(10);
-    int y = 15;
-    canvas->drawString("    colorType  bytes", 10, y, paint);
-    for (SkColorType colorType : { #list_of_color_types#
-                                 } ) {
-        bool result = SkColorTypeIsAlwaysOpaque(colorType);
-        SkString string;
-        string.printf("%13s %6s", colors[(int) colorType], result ? "true" : "false");
-        canvas->drawString(string, 10, y += 14, paint);
-    }
-##
-#SeeAlso SkColorTypeValidateAlphaType
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool SkColorTypeValidateAlphaType(SkColorType colorType, SkAlphaType alphaType,
-                                         SkAlphaType* canonical = nullptr)
-#In Property
-#Line # returns if Alpha_Type is valid ##
-#Populate
-
-#Example
-#Height 640
-    const char* colors[] = { "Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
-                             "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16" };
-    const char* alphas[] = {"Unknown ", "Opaque  ", "Premul  ", "Unpremul"};
-    SkAlphaType alphaTypes[] = { #list_of_alpha_types#
-                               };
-    SkPaint paint;
-    paint.setTypeface(SkTypeface::MakeFromName("monospace", SkFontStyle()));
-    paint.setAntiAlias(true);
-    paint.setTextSize(10);
-    int y = 15;
-    canvas->drawString("   colorType   alphaType  canonical", 10, y, paint);
-    for (SkColorType colorType : { #list_of_color_types#
-                                 } ) {
-        for (SkAlphaType alphaType : alphaTypes) {
-            SkAlphaType canonicalAlphaType  = kUnknown_SkAlphaType;
-            bool result = SkColorTypeValidateAlphaType(colorType, alphaType, &canonicalAlphaType);
-            SkString string;
-            string.printf("%13s %10s %10s", colors[(int) colorType], alphas[(int) alphaType],
-                     result ? alphas[(int) canonicalAlphaType] : "------  ");
-            canvas->drawString(string, 10, y += 14, paint);
-        }
-    }
-##
-#SeeAlso SkColorTypeIsAlwaysOpaque
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic YUV_ColorSpace
-#Line # color range of YUV pixels ##
-#Alias YUV_ColorSpace ##
-
-#Enum SkYUVColorSpace
-#Line # color range of YUV pixels ##
-
-#Code
-#Populate
-##
-
-Describes color range of YUV pixels. The color mapping from YUV to RGB varies
-depending on the source. YUV pixels may be generated by JPEG images, standard
-video streams, or high definition video streams. Each has its own mapping from
-YUV and RGB.
-
-JPEG YUV values encode the full range of 0 to 255 for all three components.
-Video YUV values range from 16 to 235 for all three components. Details of
-encoding and conversion to RGB are described in
-#A YCbCr color space # https://en.wikipedia.org/wiki/YCbCr ##
-.
-
-#Const kJPEG_SkYUVColorSpace 0
-#Line # describes full range ##
-Describes standard JPEG color space;
-#A CCIR 601 # https://en.wikipedia.org/wiki/Rec._601 ##
-with full range of 0 to 255 for components.
-##
-#Const kRec601_SkYUVColorSpace 1
-#Line # describes SDTV range ##
-Describes standard used by standard definition television;
-#A CCIR 601 # https://en.wikipedia.org/wiki/Rec._601 ##
-with studio range of 16 to 235 range for components.
-##
-#Const kRec709_SkYUVColorSpace 2
-#Line # describes HDTV range ##
-Describes standard used by high definition television;
-#A Rec. 709 # https://en.wikipedia.org/wiki/Rec._709 ##
-with studio range of 16 to 235 range for components.
-##
-#Const kLastEnum_SkYUVColorSpace 2
-#Line # last valid value ##
-    Used by tests to iterate through all valid values.
-##
-
-#NoExample
-##
-
-#SeeAlso SkImage::MakeFromYUVTexturesCopy SkImage::MakeFromNV12TexturesCopy
-
-#Enum SkYUVColorSpace ##
-#Subtopic YUV_ColorSpace ##
-
-# ------------------------------------------------------------------------------
-
-#Struct SkImageInfo
-
-#Code
-#Populate
-##
-
-Describes pixel dimensions and encoding. Bitmap, Image, Pixmap, and Surface
-can be created from Image_Info. Image_Info can be retrieved from Bitmap and
-Pixmap, but not from Image and Surface. For example, Image and Surface
-implementations may defer pixel depth, so may not completely specify Image_Info.
-
-Image_Info contains dimensions, the pixel integral width and height. It encodes
-how pixel bits describe Color_Alpha, transparency; Color components red, blue,
-and green; and Color_Space, the range and linearity of colors.
-
-# ------------------------------------------------------------------------------
-
-#Method SkImageInfo()
-
-#In Constructors
-#Line # creates with zeroed dimensions, kUnknown_SkColorType, kUnknown_SkAlphaType ##
-#Populate
-
-#Example
-#Height 32
-#Description
-An empty Image_Info may be passed to SkCanvas::accessTopLayerPixels as storage
-for the Canvas actual Image_Info.
-##
-  SkImageInfo imageInfo;
-  size_t rowBytes;
-  SkIPoint origin;
-  (void) canvas->accessTopLayerPixels(&imageInfo, &rowBytes, &origin);
-  const char* alphaType[] = { "Unknown", "Opaque", "Premul", "Unpremul" };
-  SkString string;
-  string.printf("k%s_SkAlphaType", alphaType[(int) imageInfo.alphaType()]);
-  SkPaint paint;
-  canvas->drawString(string, 20, 20, paint);
-##
-
-#SeeAlso Make MakeN32 MakeS32 MakeA8
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at,
-                            sk_sp<SkColorSpace> cs = nullptr)
-#In Constructors
-#Line # creates Image_Info from dimensions, Color_Type, Alpha_Type, Color_Space ##
-#Populate
-
-#Example
-#Height 48
-    uint8_t storage[][5] = {{ 0xCA, 0xDA, 0xCA, 0xC9, 0xA3 },
-                            { 0xAC, 0xA8, 0x89, 0xA7, 0x87 },
-                            { 0x9B, 0xB5, 0xE5, 0x95, 0x46 },
-                            { 0x90, 0x81, 0xC5, 0x71, 0x33 },
-                            { 0x75, 0x55, 0x44, 0x40, 0x30 }};
-    SkImageInfo imageInfo = SkImageInfo::Make(5, 5, kGray_8_SkColorType, kOpaque_SkAlphaType);
-    SkPixmap pixmap(imageInfo, storage[0], sizeof(storage) / 5);
-    SkBitmap bitmap;
-    bitmap.installPixels(pixmap);
-    canvas->scale(8, 8);
-    canvas->drawBitmap(bitmap, 0, 0);
-##
-
-#SeeAlso MakeN32 MakeN32Premul MakeS32 MakeA8
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkImageInfo MakeN32(int width, int height, SkAlphaType at,
-                               sk_sp<SkColorSpace> cs = nullptr)
-#In Constructors
-#Line # creates Image_Info with Native_Color_Type ##
-#Populate
-
-#Example
-#Height 128
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeN32(16, 16, kPremul_SkAlphaType));
-    SkCanvas offscreen(bitmap);
-    offscreen.clear(SK_ColorWHITE);
-    SkPaint paint;
-    offscreen.drawString("g", 0, 10, paint);
-    canvas->scale(8, 8);
-    canvas->drawBitmap(bitmap, 0, 0);
-##
-
-#SeeAlso Make MakeN32Premul MakeS32 MakeA8
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkImageInfo MakeS32(int width, int height, SkAlphaType at)
-
-#In Constructors
-#Line # creates Image_Info with Native_Color_Type, sRGB Color_Space ##
-#Populate
-
-#Example
-#Set sRGB
-#Height 128
-#Description
-Top gradient is drawn to offScreen without Color_Space. It is darker than middle
-gradient, drawn to offScreen with sRGB Color_Space. Bottom gradient shares bits
-with middle, but does not specify the Color_Space in noColorSpaceBitmap. A source
-without Color_Space is treated as sRGB; the bottom gradient is identical to the
-middle gradient.
-##
-    const int width = 256;
-    const int height = 32;
-    SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
-    SkColor  gradColors[] = { 0xFFAA0055, 0xFF11CC88 };
-    SkPoint  gradPoints[] = { { 0, 0 }, { width, 0 } };
-    SkPaint gradPaint;
-    gradPaint.setShader(SkGradientShader::MakeLinear(gradPoints, gradColors, nullptr,
-                    SK_ARRAY_COUNT(gradColors), SkShader::kClamp_TileMode));
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType));
-    SkCanvas offScreen(bitmap);
-    offScreen.drawRect(SkRect::MakeWH(width, height), gradPaint);
-    canvas->drawBitmap(bitmap, 0, 0);
-    bitmap.allocPixels(SkImageInfo::MakeS32(width, height, kPremul_SkAlphaType));
-    SkCanvas sRGBOffscreen(bitmap);
-    sRGBOffscreen.drawRect(SkRect::MakeWH(width, height), gradPaint);
-    canvas->drawBitmap(bitmap, 0, 48);
-    SkBitmap noColorSpaceBitmap;
-    noColorSpaceBitmap.setInfo(SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType));
-    noColorSpaceBitmap.setPixels(bitmap.getAddr(0, 0));
-    canvas->drawBitmap(noColorSpaceBitmap, 0, 96);
-##
-
-#SeeAlso Make MakeN32 MakeN32Premul MakeA8
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkImageInfo MakeN32Premul(int width, int height, sk_sp<SkColorSpace> cs = nullptr)
-
-#In Constructors
-#Line # creates Image_Info with Native_Color_Type, kPremul_SkAlphaType ##
-#Populate
-
-#Example
-#Height 128
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeN32Premul(18, 18));
-    SkCanvas offscreen(bitmap);
-    offscreen.clear(SK_ColorWHITE);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setTextSize(15);
-    offscreen.drawString("\xF0\x9F\x98\xB8", 1, 15, paint);
-    canvas->scale(6, 6);
-    canvas->drawBitmap(bitmap, 0, 0);
-##
-
-#SeeAlso MakeN32 MakeS32 MakeA8 Make
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkImageInfo MakeN32Premul(const SkISize& size)
-
-#In Constructors
-#Populate
-
-#Example
-#Height 128
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeN32Premul({18, 18}));
-    SkCanvas offscreen(bitmap);
-    offscreen.clear(SK_ColorWHITE);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setTextSize(15);
-    offscreen.drawString("\xF0\x9F\x98\xB9", 1, 15, paint);
-    canvas->scale(6, 6);
-    canvas->drawBitmap(bitmap, 0, 0);
-##
-
-#SeeAlso MakeN32 MakeS32 MakeA8 Make
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkImageInfo MakeA8(int width, int height)
-
-#In Constructors
-#Line # creates Image_Info with kAlpha_8_SkColorType, kPremul_SkAlphaType ##
-#Populate
-
-#Example
-#Height 64
-    uint8_t pixels[][8] = { { 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00},
-                            { 0x00, 0x7f, 0xff, 0x3f, 0x3f, 0x7f, 0x3f, 0x00},
-                            { 0x3f, 0xff, 0x7f, 0x00, 0x7f, 0xff, 0x7f, 0x00},
-                            { 0x00, 0x3f, 0x00, 0x00, 0x3f, 0x7f, 0x3f, 0x00},
-                            { 0x3f, 0x7f, 0x7f, 0x3f, 0x00, 0x00, 0x00, 0x00},
-                            { 0x7f, 0xff, 0xff, 0x7f, 0x00, 0x3f, 0x7f, 0x3f},
-                            { 0x7f, 0xff, 0xff, 0x7f, 0x00, 0x7f, 0xff, 0x7f},
-                            { 0x3f, 0x7f, 0x7f, 0x3f, 0x00, 0x3f, 0x7f, 0x3f} };
-    SkBitmap bitmap;
-    bitmap.installPixels(SkImageInfo::MakeA8(8, 8),
-            (void*) pixels, sizeof(pixels[0]));
-    SkPaint paint;
-    canvas->scale(4, 4);
-    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xFF007F00} ) {
-        paint.setColor(color);
-        canvas->drawBitmap(bitmap, 0, 0, &paint);
-        canvas->translate(12, 0);
-    }
-##
-
-#SeeAlso MakeN32 MakeS32 Make
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkImageInfo MakeUnknown(int width, int height)
-
-#In Constructors
-#Line # creates Image_Info with kUnknown_SkColorType, kUnknown_SkAlphaType ##
-#Populate
-
-#Example
-#Height 32
-#Width 384
-SkImageInfo info;  // default constructor
-SkString string;
-string.printf("SkImageInfo() %c= SkImageInfo::MakeUnknown(0, 0)",
-              info == SkImageInfo::MakeUnknown(0, 0) ? '=' : '!');
-SkPaint paint;
-canvas->drawString(string, 0, 12, paint);
-##
-
-#SeeAlso SkImageInfo() MakeN32 MakeS32 Make
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkImageInfo MakeUnknown()
-
-#In Constructors
-#Populate
-
-#Example
-#Height 32
-#Width 384
-SkImageInfo info;  // default constructor
-SkString string;
-string.printf("SkImageInfo() %c= SkImageInfo::MakeUnknown()",
-              info == SkImageInfo::MakeUnknown() ? '=' : '!');
-SkPaint paint;
-canvas->drawString(string, 0, 12, paint);
-##
-
-#SeeAlso SkImageInfo() MakeN32 MakeS32 Make
-
-#Method ##
-
-
-# ------------------------------------------------------------------------------
-#Subtopic Property
-#Line # metrics and attributes ##
-##
-
-#Method int width() const
-#In Property
-#Line # returns pixel column count ##
-#Populate
-
-#Example
-#Image 4
-#Height 96
-   canvas->translate(10, 10);
-   canvas->drawBitmap(source, 0, 0);
-   SkImageInfo imageInfo = source.info();
-   canvas->translate(0, imageInfo.height());
-   SkPaint paint;
-   canvas->drawLine(0, 10, imageInfo.width(), 10, paint);
-   canvas->drawString("width", imageInfo.width() / 2 - 15, 25, paint);
-##
-
-#SeeAlso height SkBitmap::width SkPixelRef::width SkImage::width SkSurface::width
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method int height() const
-#In Property
-#Line # returns pixel row count ##
-#Populate
-
-#Example
-#Image 4
-#Height 96
-   canvas->translate(10, 20);
-   canvas->drawBitmap(source, 0, 0);
-   SkImageInfo imageInfo = source.info();
-   SkPaint paint;
-   canvas->drawLine(imageInfo.width() + 10, 0, imageInfo.width() + 10, imageInfo.height(), paint);
-   canvas->drawString("height", imageInfo.width() + 15, imageInfo.height() / 2, paint);
-##
-
-#SeeAlso width SkBitmap::height SkPixelRef::height SkImage::height SkSurface::height
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkColorType colorType() const
-#In Property
-#Line # returns Color_Type ##
-Returns Color_Type, one of: #list_of_color_types#.
-
-#Return Color_Type ##
-
-#Example
-    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
-                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16"};
-    SkImageInfo info = SkImageInfo::MakeA8(16, 32);
-    SkDebugf("color type: k" "%s" "_SkColorType\n", colors[info.colorType()]);
-#StdOut
-color type: kAlpha_8_SkColorType
-##
-##
-
-#SeeAlso alphaType SkPixmap::colorType SkBitmap::colorType
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkAlphaType alphaType() const
-#In Property
-#Line # returns Alpha_Type ##
-Returns Alpha_Type, one of: #list_of_alpha_types#.
-
-#Return Alpha_Type ##
-
-#Example
-    const char* alphas[] = {"Unknown", "Opaque", "Premul", "Unpremul"};
-    SkImageInfo info = SkImageInfo::MakeA8(16, 32);
-    SkDebugf("alpha type: k" "%s" "_SkAlphaType\n", alphas[info.alphaType()]);
-#StdOut
-alpha type: kPremul_SkAlphaType
-##
-##
-
-#SeeAlso colorType SkPixmap::alphaType SkBitmap::alphaType
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkColorSpace* colorSpace() const
-#In Property
-#Line # returns Color_Space ##
-#Populate
-
-#Example
-#Description
-SkColorSpace::MakeSRGBLinear creates Color_Space with linear gamma
-and an sRGB gamut. This Color_Space gamma is not close to sRGB gamma.
-##
-    SkImageInfo info = SkImageInfo::MakeN32(16, 32, kPremul_SkAlphaType,
-            SkColorSpace::MakeSRGBLinear());
-    SkColorSpace* colorSpace = info.colorSpace();
-    SkDebugf("gammaCloseToSRGB: %s  gammaIsLinear: %s  isSRGB: %s\n",
-            colorSpace->gammaCloseToSRGB() ? "true" : "false",
-            colorSpace->gammaIsLinear() ? "true" : "false",
-            colorSpace->isSRGB() ? "true" : "false");
-#StdOut
-gammaCloseToSRGB: false  gammaIsLinear: true  isSRGB: false
-##
-##
-
-#SeeAlso Color_Space SkPixmap::colorSpace SkBitmap::colorSpace
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkColorSpace> refColorSpace() const
-#In Property
-#Line # returns Color_Space ##
-#Populate
-
-#Example
-    SkImageInfo info1 = SkImageInfo::MakeN32(16, 32, kPremul_SkAlphaType,
-            SkColorSpace::MakeSRGBLinear());
-    SkImageInfo info2 = SkImageInfo::MakeN32(16, 32, kPremul_SkAlphaType,
-            info1.refColorSpace());
-    SkColorSpace* colorSpace = info2.colorSpace();
-    SkDebugf("gammaCloseToSRGB: %s  gammaIsLinear: %s  isSRGB: %s\n",
-            colorSpace->gammaCloseToSRGB() ? "true" : "false",
-            colorSpace->gammaIsLinear() ? "true" : "false",
-            colorSpace->isSRGB() ? "true" : "false");
-##
-
-#SeeAlso Color_Space SkBitmap::refColorSpace
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isEmpty() const
-#In Property
-#Line # returns if dimensions contain pixels ##
-#Populate
-
-#Example
-    for (int width : { 0, 2 } ) {
-        for (int height : { 0, 2 } ) {
-             SkImageInfo imageInfo= SkImageInfo::MakeA8(width, height);
-             SkDebugf("width: %d height: %d empty: %s\n", width, height,
-                      imageInfo.isEmpty() ? "true" : "false");
-        }
-    }
-#StdOut
-width: 0 height: 0 empty: true
-width: 0 height: 2 empty: true
-width: 2 height: 0 empty: true
-width: 2 height: 2 empty: false
-##
-##
-
-#SeeAlso dimensions bounds SkBitmap::empty SkPixmap::bounds
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isOpaque() const
-#In Property
-#Line # returns if Alpha_Type is kOpaque_SkAlphaType ##
-#Populate
-
-#Example
-    const int height = 2;
-    const int width = 2;
-    SkBitmap bitmap;
-    SkImageInfo imageInfo = SkImageInfo::Make(width, height, kN32_SkColorType, kPremul_SkAlphaType);
-    bitmap.setInfo(imageInfo);
-    for (int index = 0; index < 2; ++index) {
-        bitmap.allocPixels();
-        bitmap.eraseColor(0x00000000);
-        SkDebugf("isOpaque: %s\n", imageInfo.isOpaque() ? "true" : "false");
-        bitmap.eraseColor(0xFFFFFFFF);
-        SkDebugf("isOpaque: %s\n", imageInfo.isOpaque() ? "true" : "false");
-        imageInfo = imageInfo.makeAlphaType(kOpaque_SkAlphaType);
-        bitmap.setInfo(imageInfo);
-    }
-#StdOut
-isOpaque: false
-isOpaque: false
-isOpaque: true
-isOpaque: true
-##
-##
-
-#SeeAlso Color_Alpha SkColorTypeValidateAlphaType SkBitmap::isOpaque SkImage::isOpaque SkPixmap::isOpaque
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkISize dimensions() const
-#In Property
-#Line # returns width() and height() ##
-#Populate
-
-#Example
-    const int height = 2;
-    const int width = 2;
-    SkImageInfo imageInfo = SkImageInfo::Make(width, height, kN32_SkColorType, kPremul_SkAlphaType);
-    SkISize dimensions = imageInfo.dimensions();
-    SkIRect bounds = imageInfo.bounds();
-    SkIRect dimensionsAsBounds = SkIRect::MakeSize(dimensions);
-    SkDebugf("dimensionsAsBounds %c= bounds\n", dimensionsAsBounds == bounds ? '=' : '!');
-#StdOut
-dimensionsAsBounds == bounds
-##
-##
-
-#SeeAlso width height bounds SkBitmap::dimensions
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkIRect bounds() const
-#In Property
-#Line # returns width() and height() as Rectangle ##
-#Populate
-
-#Example
-#Height 64
-#Image 4
-    canvas->scale(.5f, .5f);
-    SkImageInfo imageInfo = source.info();
-    SkIRect bounds = imageInfo.bounds();
-    for (int x : { 0, bounds.width() } ) {
-        for (int y : { 0, bounds.height() } ) {
-            canvas->drawBitmap(source, x, y);
-        }
-    }
-##
-
-#SeeAlso width height dimensions
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool gammaCloseToSRGB() const
-#In Property
-#Line # returns if Color_Space gamma is approximately the same as sRGB ##
-
-Returns true if associated Color_Space is not nullptr, and Color_Space gamma
-is approximately the same as sRGB.
-This includes the
-###$
-$A sRGB transfer function $ https://en.wikipedia.org/wiki/SRGB#The_sRGB_transfer_function_(%22gamma%22) $$
-$$$#
-as well as a gamma curve described by a 2.2 exponent.
-
-#Return true if Color_Space gamma is approximately the same as sRGB ##
-
-#Example
-#Height 144
-    const int width = 256;
-    const int height = 64;
-    auto drawLabel = [=](const char* what, bool closeToSRGB) -> void {
-        SkString string;
-        string.printf("%s gamma is %s" "close to sRGB", what, closeToSRGB ? "" : "not ");
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        canvas->drawString(string, 20, 56, paint);
-    };
-    SkColor  gradColors[] = { 0xFFFF7F00, 0xFF00FF7F,  0xFF0000FF, 0xFF7F7FFF };
-    SkPoint  gradPoints[] = { { 0, 0 }, { width, 0 }, { width * 2, 0 }, { width * 3, 0 } };
-    SkPaint gradPaint;
-    gradPaint.setShader(SkGradientShader::MakeLinear(gradPoints, gradColors, nullptr,
-                    SK_ARRAY_COUNT(gradColors), SkShader::kClamp_TileMode));
-    canvas->drawRect(SkRect::MakeWH(width, height), gradPaint);
-    drawLabel("canvas", canvas->imageInfo().gammaCloseToSRGB());
-    SkBitmap bitmap;
-    SkImageInfo offscreenInfo = SkImageInfo::MakeS32(width, height, kPremul_SkAlphaType);
-    bitmap.allocPixels(offscreenInfo);
-    SkCanvas sRGBOffscreen(bitmap);
-    sRGBOffscreen.drawRect(SkRect::MakeWH(width, height), gradPaint);
-    canvas->translate(0, 80);
-    canvas->drawBitmap(bitmap, 0, 0);
-    drawLabel("offscreen", offscreenInfo.gammaCloseToSRGB());
-##
-
-#SeeAlso SkColorSpace::gammaCloseToSRGB
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkImageInfo makeWH(int newWidth, int newHeight) const
-#In Constructors
-#Line # creates Image_Info with changed dimensions ##
-#Populate
-
-#Example
-#Height 144
-#Image 3
-    SkImageInfo canvasImageInfo = canvas->imageInfo();
-    SkRect canvasBounds = SkRect::Make(canvasImageInfo.bounds());
-    canvas->drawBitmapRect(source, source.bounds(), canvasBounds, nullptr);
-    SkImageInfo insetImageInfo =
-              canvasImageInfo.makeWH(canvasBounds.width() / 2, canvasBounds.height() / 2);
-    SkBitmap inset;
-    inset.allocPixels(insetImageInfo);
-    SkCanvas offscreen(inset);
-    offscreen.drawBitmapRect(source, source.bounds(), SkRect::Make(inset.bounds()), nullptr);
-    canvas->drawBitmap(inset, canvasBounds.width() / 4, canvasBounds.height() / 4);
-##
-
-#SeeAlso Make makeAlphaType makeColorSpace makeColorType
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkImageInfo makeAlphaType(SkAlphaType newAlphaType) const
-#In Constructors
-#Line # creates Image_Info with changed Alpha_Type ##
-#Populate
-
-#Example
-#Image 3
-    const int width = 256;
-    const int height = 128;
-    SkColor pixels[height][width];
-    for (int y = 0; y < height; ++y) {
-        for (int x = 0; x < width; ++x) {
-            int red = SkScalarRoundToInt(255 * SkScalarAbs(SkScalarSin((x * 4 + y) * 0.03f)));
-            int blue = SkScalarRoundToInt(255 * SkScalarAbs(SkScalarCos((x * 3 + y) * 0.04f)));
-            int green = SkScalarRoundToInt(255 * SkScalarAbs(SkScalarSin((x * 2 + y) * 0.05f)));
-            int alpha = SkScalarRoundToInt(255 * SkScalarAbs(SkScalarCos((x * 1 + y) * 0.006f)));
-            pixels[y][x] =
-                SkColorSetARGB(alpha, red * alpha / 255, green * alpha / 255, blue * alpha / 255);
-        }
-    }
-    SkBitmap bitmap;
-    SkImageInfo info = SkImageInfo::Make(width, height, kBGRA_8888_SkColorType, kPremul_SkAlphaType);
-    bitmap.installPixels(info, (void*) pixels, sizeof(SkColor) * width);
-    canvas->drawBitmap(source, 0, 0);
-    canvas->drawBitmap(bitmap, 0, 0);
-    SkImageInfo unpremulInfo = info.makeAlphaType(kUnpremul_SkAlphaType);
-    bitmap.installPixels(unpremulInfo, (void*) pixels, sizeof(SkColor) * width);
-    canvas->drawBitmap(bitmap, 0, 128);
-##
-
-#SeeAlso Make MakeA8 makeColorType makeColorSpace
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkImageInfo makeColorType(SkColorType newColorType) const
-#In Constructors
-#Line # creates Image_Info with changed Color_Type ##
-#Populate
-
-#Example
-    const int width = 256;
-    const int height = 128;
-    SkColor pixels[height][width];
-    for (int y = 0; y < height; ++y) {
-        for (int x = 0; x < width; ++x) {
-            int red = SkScalarRoundToInt(255 * SkScalarAbs(SkScalarSin((x * 4 + y) * 0.03f)));
-            int blue = SkScalarRoundToInt(255 * SkScalarAbs(SkScalarCos((x * 3 + y) * 0.04f)));
-            int green = SkScalarRoundToInt(255 * SkScalarAbs(SkScalarSin((x * 2 + y) * 0.05f)));
-            int alpha = SkScalarRoundToInt(255 * SkScalarAbs(SkScalarCos((x * 1 + y) * 0.006f)));
-            pixels[y][x] =
-                SkColorSetARGB(alpha, red * alpha / 255, green * alpha / 255, blue * alpha / 255);
-        }
-    }
-    SkBitmap bitmap;
-    SkImageInfo info = SkImageInfo::Make(width, height, kBGRA_8888_SkColorType, kPremul_SkAlphaType);
-    bitmap.installPixels(info, (void*) pixels, sizeof(SkColor) * width);
-    canvas->drawBitmap(source, 0, 0);
-    canvas->drawBitmap(bitmap, 0, 0);
-    SkImageInfo rgbaInfo = info.makeColorType(kRGBA_8888_SkColorType);
-    bitmap.installPixels(rgbaInfo, (void*) pixels, sizeof(SkColor) * width);
-    canvas->drawBitmap(bitmap, 0, 128);
-##
-
-#SeeAlso Make makeAlphaType makeColorSpace
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkImageInfo makeColorSpace(sk_sp<SkColorSpace> cs) const
-#In Constructors
-#Line # creates Image_Info with changed Color_Space ##
-#Populate
-
-#Example
-#Height 224
-    const int width = 256;
-    const int height = 64;
-    auto drawLabel = [=](const char* what, bool closeToSRGB) -> void {
-        SkString string;
-        string.printf("%s gamma is %s" "close to sRGB", what, closeToSRGB ? "" : "not ");
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        canvas->drawString(string, 20, 56, paint);
-    };
-    SkColor  gradColors[] = { 0xFFFF7F00, 0xFF00FF7F,  0xFF0000FF, 0xFF7F7FFF };
-    SkPoint  gradPoints[] = { { 0, 0 }, { width, 0 }, { width * 2, 0 }, { width * 3, 0 } };
-    SkPaint gradPaint;
-    gradPaint.setShader(SkGradientShader::MakeLinear(gradPoints, gradColors, nullptr,
-            SK_ARRAY_COUNT(gradColors), SkShader::kClamp_TileMode));
-    canvas->drawRect(SkRect::MakeWH(width, height), gradPaint);
-    drawLabel("canvas", canvas->imageInfo().gammaCloseToSRGB());
-    SkBitmap bitmap;
-    SkImageInfo offscreenInfo = SkImageInfo::MakeS32(width, height, kPremul_SkAlphaType);
-    bitmap.allocPixels(offscreenInfo);
-    SkCanvas sRGBOffscreen(bitmap);
-    sRGBOffscreen.drawRect(SkRect::MakeWH(width, height), gradPaint);
-    canvas->translate(0, 80);
-    canvas->drawBitmap(bitmap, 0, 0);
-    drawLabel("offscreen", offscreenInfo.gammaCloseToSRGB());
-    SkImageInfo linearGamma =
-            offscreenInfo.makeColorSpace(offscreenInfo.colorSpace()->makeLinearGamma());
-    bitmap.allocPixels(linearGamma);
-    SkCanvas lgOffscreen(bitmap);
-    lgOffscreen.drawRect(SkRect::MakeWH(width, height), gradPaint);
-    canvas->translate(0, 80);
-    canvas->drawBitmap(bitmap, 0, 0);
-    drawLabel("linear", linearGamma.gammaCloseToSRGB());
-##
-
-#SeeAlso Make MakeS32 makeAlphaType makeColorType
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method int bytesPerPixel() const
-#In Property
-#Line # returns number of bytes in pixel based on Color_Type ##
-#Populate
-
-#Example
-    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
-                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16"};
-    for (SkColorType colorType : { #list_of_color_types#
-                                 } ) {
-        SkImageInfo info = SkImageInfo::Make(1, 1, colorType, kOpaque_SkAlphaType);
-        SkDebugf("color: k" "%s" "_SkColorType" "%*s" "bytesPerPixel: %d\n",
-                colors[colorType], 13 - strlen(colors[colorType]), " ",
-                info.bytesPerPixel());
-    }
-#StdOut
-color: kUnknown_SkColorType      bytesPerPixel: 0
-color: kAlpha_8_SkColorType      bytesPerPixel: 1
-color: kRGB_565_SkColorType      bytesPerPixel: 2
-color: kARGB_4444_SkColorType    bytesPerPixel: 2
-color: kRGBA_8888_SkColorType    bytesPerPixel: 4
-color: kRGB_888x_SkColorType     bytesPerPixel: 4
-color: kBGRA_8888_SkColorType    bytesPerPixel: 4
-color: kRGBA_1010102_SkColorType bytesPerPixel: 4
-color: kRGB_101010x_SkColorType  bytesPerPixel: 4
-color: kGray_8_SkColorType       bytesPerPixel: 1
-color: kRGBA_F16_SkColorType     bytesPerPixel: 8
-##
-##
-
-#SeeAlso width shiftPerPixel SkBitmap::bytesPerPixel
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method int shiftPerPixel() const
-#In Property
-#Line # returns bit shift from pixels to bytes ##
-#Populate
-
-#Example
-    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
-                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16"};
-    for (SkColorType colorType : { #list_of_color_types#
-                                 } ) {
-        SkImageInfo info = SkImageInfo::Make(1, 1, colorType, kOpaque_SkAlphaType);
-        SkDebugf("color: k" "%s" "_SkColorType" "%*s" "shiftPerPixel: %d\n",
-                colors[colorType], 14 - strlen(colors[colorType]), " ",
-                info.shiftPerPixel());
-    }
-#StdOut
-color: kUnknown_SkColorType       shiftPerPixel: 0
-color: kAlpha_8_SkColorType       shiftPerPixel: 0
-color: kRGB_565_SkColorType       shiftPerPixel: 1
-color: kARGB_4444_SkColorType     shiftPerPixel: 1
-color: kRGBA_8888_SkColorType     shiftPerPixel: 2
-color: kRGB_888x_SkColorType      shiftPerPixel: 2
-color: kBGRA_8888_SkColorType     shiftPerPixel: 2
-color: kRGBA_1010102_SkColorType  shiftPerPixel: 2
-color: kRGB_101010x_SkColorType   shiftPerPixel: 2
-color: kGray_8_SkColorType        shiftPerPixel: 0
-color: kRGBA_F16_SkColorType      shiftPerPixel: 3
-##
-##
-
-#SeeAlso bytesPerPixel minRowBytes SkBitmap::shiftPerPixel SkPixmap::shiftPerPixel
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method uint64_t minRowBytes64() const
-#In Property
-#Line # returns width() times bytesPerPixel in 64 bits ##
-#Populate
-
-#Example
-    for (int shift = 24; shift < 32; ++shift) {
-        int width = 1 << shift;
-        SkImageInfo imageInfo =
-                SkImageInfo::Make(width, 1, kRGBA_F16_SkColorType, kPremul_SkAlphaType);
-        uint64_t minRowBytes = imageInfo.minRowBytes64();
-        bool widthTooLarge = (uint64_t) (int32_t) minRowBytes != minRowBytes;
-        SkDebugf("RGBA_F16 width %d (0x%08x) %s\n",
-                width, width, widthTooLarge ? "too large" : "OK");
-    }
-#StdOut
-RGBA_F16 width 16777216 (0x01000000) OK
-RGBA_F16 width 33554432 (0x02000000) OK
-RGBA_F16 width 67108864 (0x04000000) OK
-RGBA_F16 width 134217728 (0x08000000) OK
-RGBA_F16 width 268435456 (0x10000000) too large
-RGBA_F16 width 536870912 (0x20000000) too large
-RGBA_F16 width 1073741824 (0x40000000) too large
-RGBA_F16 width -2147483648 (0x80000000) too large
-##
-##
-
-#SeeAlso minRowBytes computeByteSize computeMinByteSize validRowBytes
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t minRowBytes() const
-#In Property
-#Line # returns width() times bytesPerPixel in 32 bits ##
-#Populate
-
-#Example
-    for (int shift = 24; shift < 32; ++shift) {
-        int width = 1 << shift;
-        SkImageInfo imageInfo =
-                SkImageInfo::Make(width, 1, kRGBA_F16_SkColorType, kPremul_SkAlphaType);
-        size_t minRowBytes = imageInfo.minRowBytes();
-        bool widthTooLarge = !minRowBytes;
-        SkDebugf("RGBA_F16 width %d (0x%08x) %s\n",
-                width, width, widthTooLarge ? "too large" : "OK");
-    }
-#StdOut
-RGBA_F16 width 16777216 (0x01000000) OK
-RGBA_F16 width 33554432 (0x02000000) OK
-RGBA_F16 width 67108864 (0x04000000) OK
-RGBA_F16 width 134217728 (0x08000000) OK
-RGBA_F16 width 268435456 (0x10000000) too large
-RGBA_F16 width 536870912 (0x20000000) too large
-RGBA_F16 width 1073741824 (0x40000000) too large
-RGBA_F16 width -2147483648 (0x80000000) too large
-##
-##
-
-#SeeAlso minRowBytes64 computeByteSize computeMinByteSize validRowBytes
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t computeOffset(int x, int y, size_t rowBytes) const
-#In Utility
-#Line # returns byte offset within pixel array ##
-#Populate
-
-#Example
-#Height 128
-    uint8_t pixels[][12] = { { 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00},
-                             { 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00},
-                             { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00},
-                             { 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF},
-                             { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
-                             { 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00},
-                             { 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00},
-                             { 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00} };
-    SkImageInfo imageInfo = SkImageInfo::MakeA8(8, 8);
-    SkBitmap bitmap;
-    bitmap.installPixels(imageInfo, (void*) pixels, sizeof(pixels[0]));
-    SkPaint paint;
-    paint.setColor(SK_ColorRED);
-    canvas->drawBitmapRect(bitmap, SkRect::MakeWH(8, 8), SkRect::MakeWH(32, 32), &paint);
-    size_t offset = imageInfo.computeOffset(2, 3, sizeof(pixels[0]));
-    pixels[0][offset] = 0x7F;
-    offset = imageInfo.computeOffset(5, 3, sizeof(pixels[0]));
-    pixels[0][offset] = 0x7F;
-    bitmap.installPixels(imageInfo, (void*) pixels, sizeof(pixels[0]));
-    canvas->drawBitmapRect(bitmap, SkRect::MakeWH(8, 8), SkRect::MakeWH(128, 128), &paint);
-##
-
-#SeeAlso height width minRowBytes computeByteSize
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator==(const SkImageInfo& other) const
-#Line # compares Image_Info for equality ##
-#Populate
-
-#Example
-    SkImageInfo info1 = SkImageInfo::Make(10, 20, kGray_8_SkColorType, kPremul_SkAlphaType);
-    SkImageInfo info2 = SkImageInfo::Make(20, 10, kAlpha_8_SkColorType, kUnpremul_SkAlphaType);
-    SkDebugf("info1 %c= info2\n", info1 == info2 ? '=' : '!');
-    info2 = info2.makeWH(10, 20);
-    SkDebugf("info1 %c= info2\n", info1 == info2 ? '=' : '!');
-    info2 = info2.makeColorType(kGray_8_SkColorType);
-    SkDebugf("info1 %c= info2\n", info1 == info2 ? '=' : '!');
-    info2 = info2.makeAlphaType(kPremul_SkAlphaType);
-    SkDebugf("info1 %c= info2\n", info1 == info2 ? '=' : '!');
-#StdOut
-info1 != info2
-info1 != info2
-info1 != info2
-info1 == info2
-##
-##
-
-#SeeAlso operator!=(const SkImageInfo& other) const SkColorSpace::Equals
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator!=(const SkImageInfo& other) const
-#Line # compares Image_Info for inequality ##
-#Populate
-
-#Example
-    SkImageInfo info1 = SkImageInfo::Make(10, 20, kGray_8_SkColorType, kPremul_SkAlphaType);
-    SkImageInfo info2 = SkImageInfo::Make(20, 10, kAlpha_8_SkColorType, kUnpremul_SkAlphaType);
-    SkDebugf("info1 %c= info2\n", info1 != info2 ? '!' : '=');
-    info2 = info2.makeWH(10, 20);
-    SkDebugf("info1 %c= info2\n", info1 != info2 ? '!' : '=');
-    info2 = info2.makeColorType(kGray_8_SkColorType);
-    SkDebugf("info1 %c= info2\n", info1 != info2 ? '!' : '=');
-    info2 = info2.makeAlphaType(kPremul_SkAlphaType);
-    SkDebugf("info1 %c= info2\n", info1 != info2 ? '!' : '=');
-#StdOut
-info1 != info2
-info1 != info2
-info1 != info2
-info1 == info2
-##
-##
-
-#SeeAlso operator==(const SkImageInfo& other) const SkColorSpace::Equals
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t computeByteSize(size_t rowBytes) const
-#In Utility
-#Line # returns memory required by pixel buffer with given row bytes ##
-#Populate
-
-#Example
-#Height 130
-    SkImageInfo info = SkImageInfo::MakeN32Premul(2, 2);
-    const size_t size = info.computeByteSize(100000);
-    SkAutoTMalloc<SkPMColor> storage(size);
-    SkPMColor* pixels = storage.get();
-    SkBitmap bitmap;
-    bitmap.setInfo(info);
-    bitmap.setPixels(pixels);
-    bitmap.eraseColor(SK_ColorRED);
-    canvas->scale(50, 50);
-    canvas->rotate(8);
-    canvas->drawBitmap(bitmap, 2, 0);
-##
-
-#SeeAlso computeMinByteSize validRowBytes
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t computeMinByteSize() const
-#In Utility
-#Line # returns least memory required by pixel buffer ##
-#Populate
-
-#Example
-#Height 130
-    SkImageInfo info = SkImageInfo::MakeN32Premul(2, 2);
-    const size_t size = info.computeMinByteSize();
-    SkAutoTMalloc<SkPMColor> storage(size);
-    SkPMColor* pixels = storage.get();
-    SkBitmap bitmap;
-    bitmap.setInfo(info);
-    bitmap.setPixels(pixels);
-    bitmap.eraseColor(SK_ColorRED);
-    canvas->scale(50, 50);
-    canvas->rotate(8);
-    canvas->drawBitmap(bitmap, 2, 0);
-##
-
-#SeeAlso computeByteSize validRowBytes
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static bool ByteSizeOverflowed(size_t byteSize)
-#In Utility
-#Line # checks result of computeByteSize and computeMinByteSize ##
-#Populate
-
-#Example
-    SkImageInfo info = SkImageInfo::MakeN32Premul(2, 1000000000);
-    for (size_t rowBytes = 100000000; rowBytes < 10000000000000LL; rowBytes *= 10) {
-        const size_t size = info.computeByteSize(rowBytes);
-        SkDebugf("rowBytes:%llu size:%llu overflowed:%s\n", rowBytes, size,
-                 SkImageInfo::ByteSizeOverflowed(size) ? "true" : "false");
-    }
-#StdOut
-rowBytes:100000000 size:99999999900000008 overflowed:false
-rowBytes:1000000000 size:999999999000000008 overflowed:false
-rowBytes:10000000000 size:9999999990000000008 overflowed:false
-rowBytes:100000000000 size:18446744073709551615 overflowed:true
-rowBytes:1000000000000 size:18446744073709551615 overflowed:true
-##
-##
-
-#SeeAlso computeByteSize computeMinByteSize validRowBytes
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool validRowBytes(size_t rowBytes) const
-#In Utility
-#Line # checks if row bytes is large enough to contain pixel row ##
-#Populate
-
-#Example
-    SkImageInfo info = SkImageInfo::MakeN32Premul(16, 8);
-    for (size_t rowBytes = 60; rowBytes < 72; rowBytes += sizeof(SkPMColor)) {
-        SkDebugf("validRowBytes(%llu): %s\n", rowBytes, info.validRowBytes(rowBytes) ?
-                 "true" : "false");
-    }
-#StdOut
-validRowBytes(60): false
-validRowBytes(64): true
-validRowBytes(68): true
-##
-##
-
-#SeeAlso ByteSizeOverflowed computeByteSize computeMinByteSize
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void reset()
-#In Constructors
-#Line # sets zero dimensions, kUnknown_SkColorType, kUnknown_SkAlphaType ##
-#Populate
-
-#Example
-    SkImageInfo info = SkImageInfo::MakeN32Premul(16, 8);
-    SkImageInfo copy = info;
-    SkDebugf("info %c= copy\n", info == copy ? '=' : '!');
-    copy.reset();
-    SkDebugf("info %c= reset copy\n", info == copy ? '=' : '!');
-    SkDebugf("SkImageInfo() %c= reset copy\n", SkImageInfo() == copy ? '=' : '!');
-#StdOut
-info == copy
-info != reset copy
-SkImageInfo() == reset copy
-##
-##
-
-#SeeAlso SkImageInfo()
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Utility
-#Line # rarely called management functions ##
-##
-
-#Method void validate() const
-#In Utility
-#Line # asserts if Image_Info is invalid (debug only) ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso validRowBytes SkBitmap::validate
-
-#Method ##
-
-#Struct SkImageInfo ##
-
-#Topic Image_Info ##
diff --git a/docs/SkImage_Reference.bmh b/docs/SkImage_Reference.bmh
deleted file mode 100644
index 7c7ed8e..0000000
--- a/docs/SkImage_Reference.bmh
+++ /dev/null
@@ -1,1789 +0,0 @@
-#Topic Image
-#Alias Image_Reference ##
-
-#Class SkImage
-
-#Code
-#Populate
-##
-
-Image describes a two dimensional array of pixels to draw. The pixels may be
-decoded in a Raster_Bitmap, encoded in a Picture or compressed data stream,
-or located in GPU memory as a GPU_Texture.
-
-Image cannot be modified after it is created. Image may allocate additional
-storage as needed; for instance, an encoded Image may decode when drawn.
-
-Image width and height are greater than zero. Creating an Image with zero width
-or height returns Image equal to nullptr.
-
-Image may be created from Bitmap, Pixmap, Surface, Picture, encoded streams,
-GPU_Texture, YUV_ColorSpace data, or hardware buffer. Encoded streams supported
-include BMP, GIF, HEIF, ICO, JPEG, PNG, WBMP, WebP. Supported encoding details
-vary with platform.
-
-#Subtopic Raster_Image
-#Alias Raster_Image ##
-#Line # pixels decoded in Raster_Bitmap ##
-Raster_Image pixels are decoded in a Raster_Bitmap. These pixels may be read
-directly and in most cases written to, although edited pixels may not be drawn
-if Image has been copied internally.
-##
-
-#Subtopic Texture_Image
-#Line # pixels located on GPU ##
-Texture_Image are located on GPU and pixels are not accessible. Texture_Image
-are allocated optimally for best performance. Raster_Image may
-be drawn to GPU_Surface, but pixels are uploaded from CPU to GPU downgrading
-performance.
-##
-
-#Subtopic Lazy_Image
-#Line # deferred pixel buffer ##
-Lazy_Image defer allocating buffer for Image pixels and decoding stream until
-Image is drawn. Lazy_Image caches result if possible to speed up repeated
-drawing.
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkImage> MakeRasterCopy(const SkPixmap& pixmap)
-#In Constructors
-#Line # creates Image from Pixmap and copied pixels ##
-#Populate
-
-#Example
-#Height 50
-#Description
-Draw a five by five bitmap, and draw a copy in an Image. Editing the pixmap
-alters the bitmap draw, but does not alter the Image draw since the Image
-contains a copy of the pixels.
-##
-    uint8_t storage[][5] = {{ 0xCA, 0xDA, 0xCA, 0xC9, 0xA3 },
-                            { 0xAC, 0xA8, 0x89, 0xA7, 0x87 },
-                            { 0x9B, 0xB5, 0xE5, 0x95, 0x46 },
-                            { 0x90, 0x81, 0xC5, 0x71, 0x33 },
-                            { 0x75, 0x55, 0x44, 0x40, 0x30 }};
-    SkImageInfo imageInfo = SkImageInfo::Make(5, 5, kGray_8_SkColorType, kOpaque_SkAlphaType);
-    SkPixmap pixmap(imageInfo, storage[0], sizeof(storage) / 5);
-    SkBitmap bitmap;
-    bitmap.installPixels(pixmap);
-    sk_sp<SkImage> image = SkImage::MakeRasterCopy(pixmap);
-    *pixmap.writable_addr8(2, 2) = 0x00;
-    canvas->scale(10, 10);
-    canvas->drawBitmap(bitmap, 0, 0);
-    canvas->drawImage(image, 10, 0);
-##
-
-#SeeAlso MakeRasterData MakeFromGenerator
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkImage> MakeRasterData(const SkImageInfo& info, sk_sp<SkData> pixels, size_t rowBytes)
-#In Constructors
-#Line # creates Image from Image_Info and shared pixels ##
-#Populate
-
-#Example
-#Image 3
-    size_t rowBytes = image->width() * SkColorTypeBytesPerPixel(kRGBA_8888_SkColorType);
-    sk_sp<SkData> data = SkData::MakeUninitialized(rowBytes * image->height());
-    SkImageInfo dstInfo = SkImageInfo::MakeN32(image->width(), image->height(),
-                                               kPremul_SkAlphaType);
-    image->readPixels(dstInfo, data->writable_data(), rowBytes, 0, 0, SkImage::kAllow_CachingHint);
-    sk_sp<SkImage> raw = SkImage::MakeRasterData(dstInfo.makeColorType(kRGBA_8888_SkColorType),
-                                                 data, rowBytes);
-    canvas->drawImage(image, 0, 0);
-    canvas->drawImage(raw.get(), 128, 0);
-##
-
-#SeeAlso MakeRasterCopy MakeFromGenerator
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Typedef void* ReleaseContext
-#Line # parameter type for MakeFromRaster ##
-
-#Code
-#Populate
-##
-
-Caller data passed to RasterReleaseProc; may be nullptr.
-
-#SeeAlso MakeFromRaster RasterReleaseProc
-
-##
-
-#Typedef void (*RasterReleaseProc)(const void* pixels, ReleaseContext)
-#Line #  parameter type for MakeFromRaster ##
-
-#Code
-#Populate
-##
-
-Function called when Image no longer shares pixels. ReleaseContext is
-provided by caller when Image is created, and may be nullptr.
-
-#SeeAlso ReleaseContext MakeFromRaster
-
-##
-
-#Method static sk_sp<SkImage> MakeFromRaster(const SkPixmap& pixmap,
-                                         RasterReleaseProc rasterReleaseProc,
-                                         ReleaseContext releaseContext)
-#In Constructors
-#Line # creates Image from Pixmap, with release ##
-#Populate
-
-#Example
-#Function
-static void releaseProc(const void* pixels, SkImage::ReleaseContext context) {
-     int* countPtr = static_cast<int*>(context);
-     *countPtr += 1;
-}
-##
-
-void draw(SkCanvas* canvas) {
-    SkColor color = 0;
-    SkPixmap pixmap(SkImageInfo::MakeN32(1, 1, kPremul_SkAlphaType), &color, 4);
-    int releaseCount = 0;
-    sk_sp<SkImage> image(SkImage::MakeFromRaster(pixmap, releaseProc, &releaseCount));
-    SkDebugf("before reset: %d\n", releaseCount);
-    image.reset();
-    SkDebugf("after reset: %d\n", releaseCount);
-}
-#StdOut
-before reset: 0
-after reset: 1
-##
-##
-
-#SeeAlso MakeRasterCopy MakeRasterData MakeFromGenerator RasterReleaseProc ReleaseContext
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkImage> MakeFromBitmap(const SkBitmap& bitmap)
-#In Constructors
-#Line # creates Image from Bitmap, sharing or copying pixels ##
-#Populate
-
-#Example
-#Description
-The first Bitmap is shared; writing to the pixel memory changes the first
-Image.
-The second Bitmap is marked immutable, and is copied; writing to the pixel
-memory does not alter the second Image.
-##
-#Height 50
-    uint8_t storage[][5] = {{ 0xCA, 0xDA, 0xCA, 0xC9, 0xA3 },
-                            { 0xAC, 0xA8, 0x89, 0xA7, 0x87 },
-                            { 0x9B, 0xB5, 0xE5, 0x95, 0x46 },
-                            { 0x90, 0x81, 0xC5, 0x71, 0x33 },
-                            { 0x75, 0x55, 0x44, 0x40, 0x30 }};
-    SkImageInfo imageInfo = SkImageInfo::Make(5, 5, kGray_8_SkColorType, kOpaque_SkAlphaType);
-    SkPixmap pixmap(imageInfo, storage[0], sizeof(storage) / 5);
-    SkBitmap bitmap;
-    bitmap.installPixels(pixmap);
-    sk_sp<SkImage> image1 = SkImage::MakeFromBitmap(bitmap);
-    bitmap.setImmutable();
-    sk_sp<SkImage> image2 = SkImage::MakeFromBitmap(bitmap);
-    *pixmap.writable_addr8(2, 2) = 0x00;
-    canvas->scale(10, 10);
-    canvas->drawImage(image1, 0, 0);
-    canvas->drawImage(image2, 10, 0);
-##
-
-#SeeAlso MakeFromRaster MakeRasterCopy MakeFromGenerator MakeRasterData
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkImage> MakeFromGenerator(std::unique_ptr<SkImageGenerator> imageGenerator,
-                                            const SkIRect* subset = nullptr)
-#In Constructors
-#Line # creates Image from a stream of data ##
-#Populate
-
-#Example
-#Height 128
-#Description
-The generator returning Picture cannot be shared; std::move transfers ownership to generated Image.
-##
-    SkPictureRecorder recorder;
-    recorder.beginRecording(100, 100)->drawColor(SK_ColorRED);
-    auto picture = recorder.finishRecordingAsPicture();
-    auto gen = SkImageGenerator::MakeFromPicture({100, 100}, picture, nullptr, nullptr,
-                                                 SkImage::BitDepth::kU8, SkColorSpace::MakeSRGB());
-    sk_sp<SkImage> image = SkImage::MakeFromGenerator(std::move(gen));
-    canvas->drawImage(image, 0, 0);
-##
-
-#SeeAlso MakeFromEncoded
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkImage> MakeFromEncoded(sk_sp<SkData> encoded, const SkIRect* subset = nullptr)
-#In Constructors
-#Line # creates Image from encoded data ##
-#Populate
-
-#Example
-#Image 3
-int x = 0;
-for (int quality : { 100, 50, 10, 1} ) {
-    sk_sp<SkData> encodedData = image->encodeToData(SkEncodedImageFormat::kJPEG, quality);
-    sk_sp<SkImage> image = SkImage::MakeFromEncoded(encodedData);
-    canvas->drawImage(image, x, 0);
-    x += 64;
-}
-##
-
-#SeeAlso MakeFromGenerator
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Enum CompressionType
-#Line # option for MakeFromCompressed ##
-#Code
-#Populate
-##
-
-Used to inform MakeFromCompressed which compression method was used on the
-input data.
-
-#Const kETC1_CompressionType 0
-#Line # compressed data uses ETC1 compression ##
-##
-
-#NoExample
-##
-
-#SeeAlso MakeFromCompressed
-
-#Enum ##
-
-#Method static sk_sp<SkImage> MakeFromCompressed(GrContext* context, sk_sp<SkData> data, int width, int height, CompressionType type)
-#In Constructors
-#Line # creates a GPU-backed Image from compressed data ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso MakeFromTexture CompressionType
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Typedef void (*TextureReleaseProc)(ReleaseContext releaseContext)
-#Line # parameter type for MakeFromTexture ##
-
-#Code
-#Populate
-##
-
-User function called when supplied texture may be deleted.
-#SeeAlso MakeFromTexture
-##
-
-#Method static sk_sp<SkImage> MakeFromTexture(GrContext* context,
-                                          const GrBackendTexture& backendTexture,
-                                          GrSurfaceOrigin origin,
-                                          SkColorType colorType,
-                                          SkAlphaType alphaType,
-                                          sk_sp<SkColorSpace> colorSpace)
-#In Constructors
-#Line # creates Image from GPU_Texture ##
-#Populate
-
-#Example
-#Image 3
-#Platform gpu
-#Height 128
-#Description
-A back-end texture has been created and uploaded to the GPU outside of this example.
-##
-GrContext* context = canvas->getGrContext();
-if (!context) {
-   return;
-}
-canvas->scale(.25f, .25f);
-int x = 0;
-for (auto origin : { kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin } ) {
-    sk_sp<SkImage> image = SkImage::MakeFromTexture(context, backEndTexture,
-           origin, kRGBA_8888_SkColorType, kOpaque_SkAlphaType, nullptr);
-    canvas->drawImage(image, x, 0);
-x += 512;
-}
-##
-
-#SeeAlso MakeFromAdoptedTexture SkSurface::MakeFromBackendTexture
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkImage> MakeFromTexture(GrContext* context,
-                                          const GrBackendTexture& backendTexture,
-                                          GrSurfaceOrigin origin,
-                                          SkColorType colorType,
-                                          SkAlphaType alphaType,
-                                          sk_sp<SkColorSpace> colorSpace,
-                                          TextureReleaseProc textureReleaseProc,
-                                          ReleaseContext releaseContext)
-#Populate
-
-#Example
-#Description
-textureReleaseProc may be called at some later point in time. In this example,
-textureReleaseProc has no effect on the drawing.
-##
-#Platform gpu
-#Image 4
-GrContext* context = canvas->getGrContext();
-if (!context) {
-   return;
-}
-auto debugster = [](SkImage::ReleaseContext releaseContext) -> void {
-   *((int *) releaseContext) += 128;
-};
-int x = 0, y = 0;
-for (auto origin : { kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin } ) {
-    sk_sp<SkImage> image = SkImage::MakeFromTexture(context, backEndTexture,
-           origin, kRGBA_8888_SkColorType, kOpaque_SkAlphaType, nullptr, debugster, &x);
-    canvas->drawImage(image, x, y);
-    y += 128;
-}
-##
-
-#SeeAlso MakeFromAdoptedTexture SkSurface::MakeFromBackendTexture
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkImage> MakeCrossContextFromEncoded(GrContext* context, sk_sp<SkData> data,
-                                                      bool buildMips,
-                                                      SkColorSpace* dstColorSpace,
-                                                      bool limitToMaxTextureSize = false)
-#In Constructors
-#Line # creates Image from encoded data, and uploads to GPU ##
-#Populate
-
-#Example
-#Image 4
-#Height 64
-GrContext* context = canvas->getGrContext();
-sk_sp<SkData> encodedData = image->encodeToData(SkEncodedImageFormat::kJPEG, 100);
-sk_sp<SkImage> image = SkImage::MakeCrossContextFromEncoded(context,
-                                                            encodedData, false, nullptr);
-canvas->drawImage(image, 0, 0);
-##
-
-#SeeAlso MakeCrossContextFromPixmap
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkImage> MakeCrossContextFromPixmap(GrContext* context, const SkPixmap& pixmap,
-                                                      bool buildMips,
-                                                      SkColorSpace* dstColorSpace,
-                                                      bool limitToMaxTextureSize = false)
-#In Constructors
-#Line # creates Image from Pixmap, and uploads to GPU ##
-#Populate
-
-#Example
-#Image 4
-#Height 64
-GrContext* context = canvas->getGrContext();
-SkPixmap pixmap;
-if (source.peekPixels(&pixmap)) {
-    sk_sp<SkImage> image = SkImage::MakeCrossContextFromPixmap(context, pixmap,
-                                                               false, nullptr);
-    canvas->drawImage(image, 0, 0);
-}
-##
-
-#SeeAlso MakeCrossContextFromEncoded
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkImage> MakeFromAdoptedTexture(GrContext* context,
-                                                 const GrBackendTexture& backendTexture,
-                                                 GrSurfaceOrigin surfaceOrigin,
-                                                 SkColorType colorType,
-                                                 SkAlphaType alphaType = kPremul_SkAlphaType,
-                                                 sk_sp<SkColorSpace> colorSpace = nullptr)
-#In Constructors
-#Line # creates Image from GPU_Texture, managed internally ##
-#Populate
-
-#Example
-#Image 5
-#Platform gpu
-   if (!canvas->getGrContext()) {
-       return;
-   }
-   canvas->scale(.5f, .5f);
-   canvas->clear(0x7f3f5f7f);
-   int x = 0, y = 0;
-   for (auto origin : { kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin } ) {
-       for (auto alpha : { kOpaque_SkAlphaType, kPremul_SkAlphaType, kUnpremul_SkAlphaType } ) {
-           sk_sp<SkImage> image = SkImage::MakeFromAdoptedTexture(canvas->getGrContext(),
-                                                                  backEndTexture, origin,
-                                                                  kRGBA_8888_SkColorType, alpha);
-           canvas->drawImage(image, x, y);
-           x += 160;
-       }
-       x -= 160 * 3;
-       y += 256;
-   }
-##
-
-#SeeAlso MakeFromTexture MakeFromYUVTexturesCopy
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkImage> MakeFromYUVATexturesCopy(GrContext* context,
-                                                   SkYUVColorSpace yuvColorSpace,
-                                                   const GrBackendTexture yuvaTextures[],
-                                                   const SkYUVAIndex yuvaIndices[4],
-                                                   SkISize imageSize,
-                                                   GrSurfaceOrigin imageOrigin,
-                                                   sk_sp<SkColorSpace> imageColorSpace = nullptr)
-#In Constructor
-#Line # creates Image from YUV_ColorSpace data ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso MakeFromYUVATexturesCopyWithExternalBackend MakeFromYUVATextures
-
-#Method ##
-
-#Method static sk_sp<SkImage> MakeFromYUVATextures(GrContext* context,
-                                               SkYUVColorSpace yuvColorSpace,
-                                               const GrBackendTexture yuvaTextures[],
-                                               const SkYUVAIndex yuvaIndices[4],
-                                               SkISize imageSize,
-                                               GrSurfaceOrigin imageOrigin,
-                                               sk_sp<SkColorSpace> imageColorSpace = nullptr);
-
-#In Constructor
-#Line # creates Image from YUV_ColorSpace data ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso MakeFromYUVATexturesCopy MakeFromYUVATexturesCopyWithExternalBackend
-
-#Method ##
-
-#Method static sk_sp<SkImage> MakeFromYUVAPixmaps(
-                                               GrContext* context,
-                                               SkYUVColorSpace yuvColorSpace,
-                                               const SkPixmap yuvaPixmaps[],
-                                               const SkYUVAIndex yuvaIndices[4],
-                                               SkISize imageSize,
-                                               GrSurfaceOrigin imageOrigin,
-                                               bool buildMips,
-                                               bool limitToMaxTextureSize = false,
-                                               sk_sp<SkColorSpace> imageColorSpace = nullptr);
-
-#In Constructor
-#Line # creates Image from YUV_ColorSpace data ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso MakeFromYUVATextures
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkImage> MakeFromYUVATexturesCopyWithExternalBackend(
-            GrContext* context,
-            SkYUVColorSpace yuvColorSpace,
-            const GrBackendTexture yuvaTextures[],
-            const SkYUVAIndex yuvaIndices[4],
-            SkISize imageSize,
-            GrSurfaceOrigin imageOrigin,
-            const GrBackendTexture& backendTexture,
-            sk_sp<SkColorSpace> imageColorSpace = nullptr)
-#In Constructor
-#Line # creates Image from planar YUV_ColorSpace, stored in texture ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso MakeFromYUVATexturesCopy MakeFromYUVATextures
-
-#Method ##
-
-#Method static sk_sp<SkImage> MakeFromYUVTexturesCopy(GrContext* context, SkYUVColorSpace yuvColorSpace,
-                                                  const GrBackendTexture yuvTextures[3],
-                                                  GrSurfaceOrigin imageOrigin,
-                                                  sk_sp<SkColorSpace> imageColorSpace = nullptr)
-#In Constructors
-#Line # creates Image from YUV_ColorSpace data in three planes ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso MakeFromYUVTexturesCopyWithExternalBackend MakeFromNV12TexturesCopy MakeFromYUVATexturesCopy
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkImage> MakeFromYUVTexturesCopyWithExternalBackend(
-        GrContext* context, SkYUVColorSpace yuvColorSpace,
-        const GrBackendTexture yuvTextures[3], GrSurfaceOrigin imageOrigin,
-        const GrBackendTexture& backendTexture, sk_sp<SkColorSpace> imageColorSpace = nullptr);
-#In Constructors
-#Line # creates Image from planar YUV_ColorSpace, stored in texture ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso MakeFromYUVTexturesCopy MakeFromNV12TexturesCopy MakeFromYUVATexturesCopyWithExternalBackend
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkImage> MakeFromNV12TexturesCopy(GrContext* context,
-                                                   SkYUVColorSpace yuvColorSpace,
-                                                   const GrBackendTexture nv12Textures[2],
-                                                   GrSurfaceOrigin imageOrigin,
-                                                   sk_sp<SkColorSpace> imageColorSpace = nullptr)
-#In Constructors
-#Line # creates Image from YUV_ColorSpace data in three planes ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso MakeFromNV12TexturesCopyWithExternalBackend MakeFromYUVTexturesCopy MakeFromYUVATexturesCopy
-
-#Method ##
-
-#Method static sk_sp<SkImage> MakeFromNV12TexturesCopyWithExternalBackend(
-            GrContext* context,
-            SkYUVColorSpace yuvColorSpace,
-            const GrBackendTexture nv12Textures[2],
-            GrSurfaceOrigin imageOrigin,
-            const GrBackendTexture& backendTexture,
-            sk_sp<SkColorSpace> imageColorSpace = nullptr);
-#In Constructors
-#Line # creates Image from planar YUV_ColorSpace, stored in texture ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso MakeFromNV12TexturesCopy MakeFromYUVTexturesCopy MakeFromYUVATexturesCopyWithExternalBackend
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-# currently uncalled by any test or client ##
-#Bug 7424
-
-#EnumClass BitDepth
-#Line # options for MakeFromPicture ##
-#Code
-#Populate
-##
-
-#Const kU8 0
-#Line # uses 8-bit unsigned int per Color component ##
-Use 8 bits per ARGB component using unsigned integer format.
-##
-#Const kF16 1
-#Line # uses 16-bit float per Color component ##
-Use 16 bits per ARGB component using half-precision floating point format.
-##
-
-#NoExample
-##
-
-#SeeAlso MakeFromPicture
-
-#EnumClass ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkImage> MakeFromPicture(sk_sp<SkPicture> picture, const SkISize& dimensions,
-                                          const SkMatrix* matrix, const SkPaint* paint,
-                                          BitDepth bitDepth,
-                                          sk_sp<SkColorSpace> colorSpace)
-#In Constructors
-#Line # creates Image from Picture ##
-#Populate
-
-#Example
-    SkPaint paint;
-    SkPictureRecorder recorder;
-    SkCanvas* recordingCanvas = recorder.beginRecording(50, 50);
-    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xff007f00 } ) {
-        paint.setColor(color);
-        recordingCanvas->drawRect({10, 10, 30, 40}, paint);
-        recordingCanvas->translate(10, 10);
-        recordingCanvas->scale(1.2f, 1.4f);
-    }
-    sk_sp<SkPicture> playback = recorder.finishRecordingAsPicture();
-    int x = 0, y = 0;
-    for (auto alpha : { 70, 140, 210 } ) {
-        paint.setAlpha(alpha);
-        auto srgbColorSpace = SkColorSpace::MakeSRGB();
-        sk_sp<SkImage> image = SkImage::MakeFromPicture(playback, {50, 50}, nullptr, &paint,
-                                                        SkImage::BitDepth::kU8, srgbColorSpace);
-        canvas->drawImage(image, x, y);
-        x += 70; y += 70;
-    }
-##
-
-#SeeAlso SkCanvas::drawPicture
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkImage> MakeFromAHardwareBuffer(
-        AHardwareBuffer* hardwareBuffer,
-        SkAlphaType alphaType = kPremul_SkAlphaType,
-        sk_sp<SkColorSpace> colorSpace = nullptr,
-        GrSurfaceOrigin surfaceOrigin = kTopLeft_GrSurfaceOrigin)
-#In Constructors
-#Line # creates Image from Android hardware buffer ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso MakeFromRaster
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Property
-#Line # values and attributes ##
-##
-
-#Method int width() const
-#In Property
-#Line # returns pixel column count ##
-#Populate
-
-#Example
-#Image 4
-#Height 96
-   canvas->translate(10, 10);
-   canvas->drawImage(image, 0, 0);
-   canvas->translate(0, image->height());
-   SkPaint paint;
-   canvas->drawLine(0, 10, image->width(), 10, paint);
-   canvas->drawString("width", image->width() / 2 - 15, 25, paint);
-##
-
-#SeeAlso dimensions() height()
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method int height() const
-#In Property
-#Line # returns pixel row count ##
-#Populate
-
-#Example
-#Image 4
-#Height 96
-   canvas->translate(10, 10);
-   canvas->drawImage(image, 0, 0);
-   canvas->translate(image->width(), 0);
-   SkPaint paint;
-   canvas->drawLine(10, 0, 10, image->height(), paint);
-   canvas->drawString("height", 34, image->height() / 2, paint);
-##
-
-#SeeAlso dimensions() width()
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkISize dimensions() const
-#In Property
-#Line # returns width() and height() ##
-#Populate
-
-#Example
-#Image 4
-    SkISize dimensions = image->dimensions();
-    SkIRect bounds = image->bounds();
-    SkIRect dimensionsAsBounds = SkIRect::MakeSize(dimensions);
-    SkDebugf("dimensionsAsBounds %c= bounds\n", dimensionsAsBounds == bounds ? '=' : '!');
-#StdOut
-dimensionsAsBounds == bounds
-##
-##
-
-#SeeAlso height() width() bounds()
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkIRect bounds() const
-#In Property
-#Line # returns width() and height() as Rectangle ##
-#Populate
-
-#Example
-#Height 128
-#Image 4
-    SkIRect bounds = image->bounds();
-    for (int x : { 0, bounds.width() } ) {
-        for (int y : { 0, bounds.height() } ) {
-            canvas->drawImage(image, x, y);
-        }
-    }
-##
-
-#SeeAlso dimensions()
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method uint32_t uniqueID() const
-#In Property
-#Line # returns identifier for Image ##
-#Populate
-
-#Example
-#Image 5
-#Height 156
- sk_sp<SkImage> subset = image->makeSubset({10, 20, 90, 100});
- canvas->drawImage(image, 0, 0);
- canvas->drawImage(subset, 128, 0);
- SkPaint paint;
- SkString s;
- s.printf("original id: %d", image->uniqueID());
- canvas->drawString(s, 20, image->height() + 20, paint);
- s.printf("subset id: %d", subset->uniqueID());
- canvas->drawString(s, 148, subset->height() + 20, paint);
-##
-
-#SeeAlso isLazyGenerated
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkAlphaType alphaType() const
-#In Property
-#Line # returns Alpha_Type ##
-Returns Alpha_Type, one of: #list_of_alpha_types#.
-
-Alpha_Type returned was a parameter to an Image constructor,
-or was parsed from encoded data.
-
-#Return Alpha_Type in Image ##
-
-#Example
-#Image 4
-#Height 96
-  const char* alphaTypeStr[] = { "Unknown", "Opaque", "Premul", "Unpremul" };
-  SkAlphaType alphaType = image->alphaType();
-  canvas->drawImage(image, 16, 0);
-  canvas->drawString(alphaTypeStr[(int) alphaType], 20, image->height() + 20, SkPaint());
-##
-
-#SeeAlso SkImageInfo::alphaType
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkColorType colorType() const
-#In Property
-#Line # returns Color_Type ##
-#Populate
-
-#Example
-#Image 4
-#Height 96
-    const char* colors[] = { "Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
-                             "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16" };
-    SkColorType colorType = image->colorType();
-    canvas->drawImage(image, 16, 0);
-    canvas->drawString(colors[(int) colorType], 20, image->height() + 20, SkPaint());
-##
-
-#SeeAlso SkImageInfo::colorType
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkColorSpace* colorSpace() const
-#In Property
-#Line # returns Color_Space ##
-#Populate
-
-#Example
-#Image 3
-#Set sRGB
-    SkPixmap pixmap;
-    source.peekPixels(&pixmap);
-    canvas->scale(.25f, .25f);
-    int y = 0;
-    for (auto gamma : { SkNamedTransferFn::kLinear,
-                        SkNamedTransferFn::kSRGB } ) {
-        int x = 0;
-        sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeRGB(gamma, SkNamedGamut::kSRGB);
-        for (int index = 0; index < 2; ++index) {
-            pixmap.setColorSpace(colorSpace);
-            sk_sp<SkImage> image = SkImage::MakeRasterCopy(pixmap);
-            canvas->drawImage(image, x, y);
-            colorSpace = image->colorSpace()->makeColorSpin();
-            x += 512;
-        }
-        y += 512;
-    }
-##
-
-#SeeAlso refColorSpace makeColorSpace
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkColorSpace> refColorSpace() const
-#In Property
-#Line # returns Image_Info Color_Space ##
-#Populate
-
-#Example
-#Image 3
-#Set sRGB
-    SkPixmap pixmap;
-    source.peekPixels(&pixmap);
-    canvas->scale(.25f, .25f);
-    int y = 0;
-    for (auto gamma : { SkNamedTransferFn::kLinear,
-                        SkNamedTransferFn::kSRGB } ) {
-        int x = 0;
-        sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeRGB(gamma, SkNamedGamut::kSRGB);
-        for (int index = 0; index < 2; ++index) {
-            pixmap.setColorSpace(colorSpace);
-            sk_sp<SkImage> image = SkImage::MakeRasterCopy(pixmap);
-            canvas->drawImage(image, x, y);
-            colorSpace = image->refColorSpace()->makeColorSpin();
-            x += 512;
-        }
-        y += 512;
-    }
-##
-
-#SeeAlso colorSpace makeColorSpace
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isAlphaOnly() const
-#In Property
-#Line # returns if pixels represent a transparency mask ##
-#Populate
-
-#Example
-    uint8_t pmColors = 0;
-    sk_sp<SkImage> image = SkImage::MakeRasterCopy({SkImageInfo::MakeA8(1, 1), &pmColors, 1});
-    SkDebugf("alphaOnly = %s\n", image->isAlphaOnly() ? "true" : "false");
-#StdOut
-alphaOnly = true
-##
-##
-
-#SeeAlso alphaType isOpaque
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isOpaque() const
-#In Property
-#Line # returns if Alpha_Type is kOpaque_SkAlphaType ##
-#Populate
-
-#Example
-    auto check_isopaque = [](const SkImageInfo& imageInfo) -> void {
-        auto surface(SkSurface::MakeRaster(imageInfo));
-        auto image(surface->makeImageSnapshot());
-        SkDebugf("isOpaque = %s\n", image->isOpaque() ? "true" : "false");
-    };
-
-    check_isopaque(SkImageInfo::MakeN32Premul(5, 5));
-    check_isopaque(SkImageInfo::MakeN32(5, 5, kOpaque_SkAlphaType));
-#StdOut
-isOpaque = false
-isOpaque = true
-##
-##
-
-#SeeAlso alphaType isAlphaOnly
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkShader> makeShader(SkShader::TileMode tileMode1, SkShader::TileMode tileMode2,
-                               const SkMatrix* localMatrix = nullptr) const
-#In Constructors
-#Line # creates Shader, Paint element that can tile Image ##
-#Populate
-
-#Example
-#Image 4
-SkMatrix matrix;
-matrix.setRotate(45);
-SkPaint paint;
-paint.setShader(image->makeShader(SkShader::kRepeat_TileMode, SkShader::kMirror_TileMode,
-                                  &matrix));
-canvas->drawPaint(paint);
-##
-
-#SeeAlso scalePixels
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkShader> makeShader(const SkMatrix* localMatrix = nullptr) const
-#Populate
-
-#Example
-#Image 5
-SkMatrix matrix;
-matrix.setRotate(45);
-matrix.postTranslate(125, 30);
-SkPaint paint;
-paint.setShader(image->makeShader(&matrix));
-canvas->drawPaint(paint);
-##
-
-#SeeAlso scalePixels
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Pixels
-#Line # read and write pixel values ##
-##
-
-#Method bool peekPixels(SkPixmap* pixmap) const
-#In Pixels
-#Line # returns Pixmap if possible ##
-#Populate
-
-#Example
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeN32Premul(12, 11));
-    SkCanvas offscreen(bitmap);
-    offscreen.clear(SK_ColorWHITE);
-    SkPaint paint;
-    offscreen.drawString("%", 1, 10, paint);
-    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
-    SkPixmap pixmap;
-    if (image->peekPixels(&pixmap)) {
-        const SkPMColor* pixels = pixmap.addr32();
-        SkPMColor pmWhite = pixels[0];
-        for (int y = 0; y < image->height(); ++y) {
-            for (int x = 0; x < image->width(); ++x) {
-                SkDebugf("%c", *pixels++ == pmWhite ? '-' : 'x');
-            }
-            SkDebugf("\n");
-        }
-    }
-#StdOut
-------------
---xx----x---
--x--x--x----
--x--x--x----
--x--x-x-----
---xx-xx-xx--
------x-x--x-
-----x--x--x-
-----x--x--x-
----x----xx--
-------------
-##
-##
-
-#SeeAlso readPixels
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isTextureBacked() const
-#In Property
-#Line # returns if Image was created from GPU_Texture ##
-#Populate
-
-#Example
-#Image 5
-#Platform gpu
-auto drawImage = [=](sk_sp<SkImage> image, const char* label) -> void {
-    if (nullptr == image) {
-        return;
-    }
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    canvas->drawImage(image, 0, 0);
-    canvas->drawString(label, 30, image->height() / 4, paint);
-    canvas->drawString(image->isTextureBacked() ? "is GPU texture" : "not GPU texture",
-                       20, image->height() * 3 / 4, paint);
-};
-sk_sp<SkImage> bitmapImage(SkImage::MakeFromBitmap(source));
-sk_sp<SkImage> textureImage(SkImage::MakeFromTexture(canvas->getGrContext(), backEndTexture,
-                            kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType,
-                            kOpaque_SkAlphaType, nullptr));
-drawImage(image, "image");
-canvas->translate(image->width(), 0);
-drawImage(bitmapImage, "source");
-canvas->translate(-image->width(), image->height());
-drawImage(textureImage, "backEndTexture");
-##
-
-#SeeAlso MakeFromTexture isValid
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isValid(GrContext* context) const
-#In Property
-#Line # returns if Image can draw to Raster_Surface or GPU_Context ##
-#Populate
-
-#Example
-#Image 5
-#Platform gpu
-auto drawImage = [=](sk_sp<SkImage> image, const char* label) -> void {
-    if (nullptr == image) {
-        return;
-    }
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    canvas->drawImage(image, 0, 0);
-    canvas->drawString(label, image->width() / 2, image->height() / 4, paint);
-    if (canvas->getGrContext()) {
-        canvas->drawString(image->isValid(canvas->getGrContext()) ? "is valid on GPU" :
-                "not valid on GPU", 20, image->height() * 5 / 8, paint);
-    }
-    canvas->drawString(image->isValid(nullptr) ? "is valid on CPU" :
-            "not valid on CPU", 20, image->height() * 7 / 8, paint);
-};
-sk_sp<SkImage> bitmapImage(SkImage::MakeFromBitmap(source));
-sk_sp<SkImage> textureImage(SkImage::MakeFromTexture(canvas->getGrContext(), backEndTexture,
-                            kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType,
-                            kOpaque_SkAlphaType, nullptr));
-drawImage(image, "image");
-canvas->translate(image->width(), 0);
-drawImage(bitmapImage, "source");
-canvas->translate(-image->width(), image->height());
-drawImage(textureImage, "backEndTexture");
-##
-
-#SeeAlso isTextureBacked isLazyGenerated
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method GrBackendTexture getBackendTexture(bool flushPendingGrContextIO,
-                                           GrSurfaceOrigin* origin = nullptr) const
-#In Property
-#Line # returns GPU reference to Image as texture ##
-#Populate
-
-#Example
-#Image 3
-#Platform gpu
-    GrContext* grContext = canvas->getGrContext();
-    if (!grContext) {
-        canvas->drawString("GPU only!", 20, 40, SkPaint());
-        return;
-    }
-    sk_sp<SkImage> imageFromBackend = SkImage::MakeFromAdoptedTexture(grContext, backEndTexture,
-            kBottomLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, kOpaque_SkAlphaType);
-    GrBackendTexture textureFromImage = imageFromBackend->getBackendTexture(false);
-    if (!textureFromImage.isValid()) {
-        return;
-    }
-    sk_sp<SkImage> imageFromTexture = SkImage::MakeFromAdoptedTexture(grContext, textureFromImage,
-            kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, kOpaque_SkAlphaType);
-    canvas->drawImage(imageFromTexture, 0, 0);
-    canvas->drawImage(imageFromBackend, 128, 128);
-##
-
-#SeeAlso MakeFromTexture isTextureBacked
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Enum CachingHint
-#Line # options for readPixels and scalePixels ##
-#Code
-#Populate
-##
-
-CachingHint selects whether Skia may internally cache Bitmaps generated by
-decoding Image, or by copying Image from GPU to CPU. The default behavior
-allows caching Bitmaps.
-
-Choose kDisallow_CachingHint if Image pixels are to be used only once, or
-if Image pixels reside in a cache outside of Skia, or to reduce memory pressure.
-
-Choosing kAllow_CachingHint does not ensure that pixels will be cached.
-Image pixels may not be cached if memory requirements are too large or
-pixels are not accessible.
-
-#Const kAllow_CachingHint 0
-#Line # allows internally caching decoded and copied pixels ##
-##
-#Const kDisallow_CachingHint 1
-#Line # disallows internally caching decoded and copied pixels ##
-##
-
-#NoExample
-##
-
-#SeeAlso readPixels scalePixels
-
-#Enum ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes,
-                    int srcX, int srcY, CachingHint cachingHint = kAllow_CachingHint) const
-#In Pixels
-#Line # copies and converts pixels ##
-
-Copies Rect of pixels from Image to dstPixels. Copy starts at offset (srcX, srcY),
-and does not exceed Image (width(), height()).
-
-dstInfo specifies width, height, Color_Type, Alpha_Type, and Color_Space of
-destination. dstRowBytes specifics the gap from one destination row to the next.
-Returns true if pixels are copied. Returns false if:
-#List
-# dstInfo has no address ##
-# dstRowBytes is less than dstInfo.minRowBytes() ##
-# Pixel_Ref is nullptr ##
-##
-
-Pixels are copied only if pixel conversion is possible. If Image Color_Type is
-kGray_8_SkColorType, or kAlpha_8_SkColorType; dstInfo.colorType() must match.
-If Image Color_Type is kGray_8_SkColorType, dstInfo.colorSpace() must match.
-If Image Alpha_Type is kOpaque_SkAlphaType, dstInfo.alphaType() must
-match. If Image Color_Space is nullptr, dstInfo.colorSpace() must match. Returns
-false if pixel conversion is not possible.
-
-srcX and srcY may be negative to copy only top or left of source. Returns
-false if width() or height() is zero or negative.
-Returns false if #Formula # abs(srcX) >= Image width() ##, or if #Formula # abs(srcY) >= Image height() ##.
-
-If cachingHint is kAllow_CachingHint, pixels may be retained locally.
-If cachingHint is kDisallow_CachingHint, pixels are not added to the local cache.
-
-#Param dstInfo  destination width, height, Color_Type, Alpha_Type, Color_Space ##
-#Param dstPixels  destination pixel storage ##
-#Param dstRowBytes  destination row length ##
-#Param srcX  column index whose absolute value is less than width() ##
-#Param srcY  row index whose absolute value is less than height() ##
-#Param cachingHint  one of: kAllow_CachingHint, kDisallow_CachingHint ##
-
-#Return true if pixels are copied to dstPixels ##
-
-#Example
-#Image 3
-    canvas->scale(.5f, .5f);
-    const int width = 32;
-    const int height = 32;
-    std::vector<int32_t> dstPixels;
-    dstPixels.resize(height * width * 4);
-    SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
-    for (int y = 0; y < 512; y += height ) {
-        for (int x = 0; x < 512; x += width ) {
-            if (image->readPixels(info, &dstPixels.front(), width * 4, x, y)) {
-                SkPixmap dstPixmap(info, &dstPixels.front(), width * 4);
-                SkBitmap bitmap;
-                bitmap.installPixels(dstPixmap);
-                canvas->drawBitmap(bitmap, 0, 0);
-            }
-            canvas->translate(48, 0);
-        }
-        canvas->translate(-16 * 48, 48);
-    }
-##
-
-#SeeAlso scalePixels SkBitmap::readPixels SkPixmap::readPixels SkCanvas::readPixels SkSurface::readPixels
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readPixels(const SkPixmap& dst, int srcX, int srcY,
-                    CachingHint cachingHint = kAllow_CachingHint) const
-
-Copies a Rect of pixels from Image to dst. Copy starts at (srcX, srcY), and
-does not exceed Image (width(), height()).
-
-dst specifies width, height, Color_Type, Alpha_Type, Color_Space, pixel storage,
-and row bytes of destination. dst.rowBytes() specifics the gap from one destination
-row to the next. Returns true if pixels are copied. Returns false if:
-#List
-# dst pixel storage equals nullptr ##
-# dst.rowBytes() is less than SkImageInfo::minRowBytes ##
-# Pixel_Ref is nullptr ##
-##
-
-Pixels are copied only if pixel conversion is possible. If Image Color_Type is
-kGray_8_SkColorType, or kAlpha_8_SkColorType; dst.colorType() must match.
-If Image Color_Type is kGray_8_SkColorType, dst.colorSpace() must match.
-If Image Alpha_Type is kOpaque_SkAlphaType, dst.alphaType() must
-match. If Image Color_Space is nullptr, dst.colorSpace() must match. Returns
-false if pixel conversion is not possible.
-
-srcX and srcY may be negative to copy only top or left of source. Returns
-false if width() or height() is zero or negative.
-Returns false if #Formula # abs(srcX) >= Image width() ##, or if #Formula # abs(srcY) >= Image height() ##.
-
-If cachingHint is kAllow_CachingHint, pixels may be retained locally.
-If cachingHint is kDisallow_CachingHint, pixels are not added to the local cache.
-
-#Param dst  destination Pixmap: Image_Info, pixels, row bytes ##
-#Param srcX  column index whose absolute value is less than width() ##
-#Param srcY  row index whose absolute value is less than height() ##
-#Param cachingHint  one of: kAllow_CachingHint, kDisallow_CachingHint ##
-
-#Return true if pixels are copied to dst ##
-
-#Example
-#Image 3
-    std::vector<int32_t> srcPixels;
-    int rowBytes = image->width() * 4;
-    int quarterWidth = image->width() / 4;
-    int quarterHeight = image->height() / 4;
-    srcPixels.resize(image->height() * rowBytes);
-    for (int y = 0; y < 4; ++y) {
-        for (int x = 0; x < 4; ++x) {
-            SkPixmap pixmap(SkImageInfo::MakeN32Premul(quarterWidth, quarterHeight),
-                    &srcPixels.front() + x * image->height() * quarterWidth +
-                    y * quarterWidth, rowBytes);
-            image->readPixels(pixmap, x * quarterWidth, y * quarterHeight);
-        }
-    }
-    canvas->scale(.5f, .5f);
-    SkBitmap bitmap;
-    bitmap.installPixels(SkImageInfo::MakeN32Premul(image->width(), image->height()),
-                             &srcPixels.front(), rowBytes);
-    canvas->drawBitmap(bitmap, 0, 0);
-##
-
-#SeeAlso scalePixels SkBitmap::readPixels SkPixmap::readPixels SkCanvas::readPixels SkSurface::readPixels
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool scalePixels(const SkPixmap& dst, SkFilterQuality filterQuality,
-                     CachingHint cachingHint = kAllow_CachingHint) const
-#In Pixels
-#Line # scales and converts one Image to another ##
-#Populate
-
-#Example
-#Image 3
-#Height 128
-    std::vector<int32_t> srcPixels;
-    int quarterWidth = image->width() / 16;
-    int rowBytes = quarterWidth * 4;
-    int quarterHeight = image->height() / 16;
-    srcPixels.resize(quarterHeight * rowBytes);
-    SkPixmap pixmap(SkImageInfo::MakeN32Premul(quarterWidth, quarterHeight),
-                    &srcPixels.front(), rowBytes);
-    canvas->scale(4, 4);
-    SkFilterQuality qualities[] = { kNone_SkFilterQuality, kLow_SkFilterQuality,
-                     kMedium_SkFilterQuality, kHigh_SkFilterQuality };
-    for (unsigned index = 0; index < SK_ARRAY_COUNT(qualities); ++index) {
-        image->scalePixels(pixmap, qualities[index]);
-        sk_sp<SkImage> filtered = SkImage::MakeFromRaster(pixmap, nullptr, nullptr);
-        canvas->drawImage(filtered, 16 * index, 0);
-    }
-##
-
-#SeeAlso SkCanvas::drawImage readPixels SkPixmap::scalePixels
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkData> encodeToData(SkEncodedImageFormat encodedImageFormat, int quality) const
-#In Utility
-#Line # returns encoded Image as SkData ##
-#Populate
-
-#Example
-#Image 3
-    canvas->scale(4, 4);
-    SkIRect subset = {0, 0, 16, 64};
-    int x = 0;
-    for (int quality : { 0, 10, 50, 100 } ) {
-        sk_sp<SkData> data(image->encodeToData(SkEncodedImageFormat::kJPEG, quality));
-        sk_sp<SkImage> filtered = SkImage::MakeFromEncoded(data, &subset);
-        canvas->drawImage(filtered, x, 0);
-        x += 16;
-    }
-##
-
-#SeeAlso refEncodedData MakeFromEncoded
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkData> encodeToData() const
-#Populate
-
-#Example
-#Image 3
-    canvas->scale(4, 4);
-    SkIRect subset = {136, 32, 200, 96};
-    sk_sp<SkData> data(image->encodeToData());
-    sk_sp<SkImage> eye = SkImage::MakeFromEncoded(data, &subset);
-    canvas->drawImage(eye, 0, 0);
-##
-
-#SeeAlso refEncodedData MakeFromEncoded
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkData> refEncodedData() const
-#In Utility
-#Line # returns Image encoded in SkData if present ##
-#Populate
-
-#Example
-#Image 3
-#Platform gpu
-    struct {
-        const char* name;
-        sk_sp<SkImage> image;
-    } tests[] = { { "image", image }, { "bitmap", SkImage::MakeFromBitmap(source) },
-          { "texture", SkImage::MakeFromTexture(canvas->getGrContext(), backEndTexture,
-                            kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType,
-                            kOpaque_SkAlphaType, nullptr) } };
-    SkString string;
-    SkPaint paint;
-    for (const auto& test : tests ) {
-        if (!test.image) {
-            string.printf("no %s", test.name);
-        } else {
-            string.printf("%s" "encoded %s", test.image->refEncodedData() ? "" : "no ", test.name);
-        }
-        canvas->drawString(string, 10, 20, paint);
-        canvas->translate(0, 20);
-    }
-##
-
-#SeeAlso encodeToData MakeFromEncoded
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Utility
-#Line # rarely called management functions ##
-##
-
-#Method sk_sp<SkImage> makeSubset(const SkIRect& subset) const
-#In Constructors
-#Line # creates Image containing part of original ##
-#Populate
-
-#Example
-#Image 3
-    canvas->scale(.5f, .5f);
-    const int width = 64;
-    const int height = 64;
-    for (int y = 0; y < 512; y += height ) {
-        for (int x = 0; x < 512; x += width ) {
-            sk_sp<SkImage> subset(image->makeSubset({x, y, x + width, y + height}));
-            canvas->drawImage(subset, x * 3 / 2, y * 3 / 2);
-        }
-    }
-##
-
-#SeeAlso MakeFromEncoded
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkImage> makeTextureImage(GrContext* context, SkColorSpace* dstColorSpace,
-                                        GrMipMapped mipMapped = GrMipMapped::kNo) const
-#In Constructors
-#Line # creates Image matching Color_Space if possible ##
-#Populate
-
-#Example
-#Platform gpu
-#Image 5
-    auto drawImage = [=](sk_sp<SkImage> image, GrContext* context, const char* label) -> void {
-        if (nullptr == image || nullptr == context) {
-            return;
-        }
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        sk_sp<SkImage> texture(image->makeTextureImage(context, nullptr));
-        canvas->drawImage(texture, 0, 0);
-        canvas->drawString(label, 20, texture->height() / 4, paint);
-    };
-    sk_sp<SkImage> bitmapImage(SkImage::MakeFromBitmap(source));
-    GrContext* context = canvas->getGrContext();
-    sk_sp<SkImage> textureImage(SkImage::MakeFromTexture(context, backEndTexture,
-                                kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType,
-                                kOpaque_SkAlphaType, nullptr));
-    drawImage(image, context, "image");
-    canvas->translate(image->width(), 0);
-    drawImage(bitmapImage, context, "source");
-    canvas->translate(-image->width(), image->height());
-    drawImage(textureImage, context, "backEndTexture");
-##
-
-#SeeAlso MakeFromTexture
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkImage> makeNonTextureImage() const
-#In Constructors
-#Line # creates Image without dependency on GPU_Texture ##
-#Populate
-
-#Example
-#Image 5
-#Platform gpu
-    auto drawImage = [=](sk_sp<SkImage> image, const char* label) -> void {
-        if (nullptr == image) {
-            return;
-        }
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        sk_sp<SkImage> nonTexture(image->makeNonTextureImage());
-        canvas->drawImage(nonTexture, 0, 0);
-        canvas->drawString(label, 20, nonTexture->height() / 4, paint);
-    };
-    sk_sp<SkImage> bitmapImage(SkImage::MakeFromBitmap(source));
-    sk_sp<SkImage> textureImage(SkImage::MakeFromTexture(canvas->getGrContext(), backEndTexture,
-                                kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType,
-                                kOpaque_SkAlphaType, nullptr));
-    drawImage(image, "image");
-    canvas->translate(image->width(), 0);
-    drawImage(bitmapImage, "source");
-    canvas->translate(-image->width(), image->height());
-    drawImage(textureImage, "backEndTexture");
-##
-
-#SeeAlso makeTextureImage makeRasterImage MakeBackendTextureFromSkImage
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkImage> makeRasterImage() const
-#In Constructors
-#Line # creates Image compatible with Raster_Surface if possible ##
-#Populate
-
-#Example
-#Image 5
-#Platform gpu
-    auto drawImage = [=](sk_sp<SkImage> image, const char* label) -> void {
-        if (nullptr == image) {
-            return;
-        }
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        sk_sp<SkImage> raster(image->makeRasterImage());
-        canvas->drawImage(raster, 0, 0);
-        canvas->drawString(label, 20, raster->height() / 4, paint);
-    };
-    sk_sp<SkImage> bitmapImage(SkImage::MakeFromBitmap(source));
-    sk_sp<SkImage> textureImage(SkImage::MakeFromTexture(canvas->getGrContext(), backEndTexture,
-                                kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType,
-                                kOpaque_SkAlphaType, nullptr));
-    drawImage(image, "image");
-    canvas->translate(image->width(), 0);
-    drawImage(bitmapImage, "source");
-    canvas->translate(-image->width(), image->height());
-    drawImage(textureImage, "backEndTexture");
-##
-
-#SeeAlso isTextureBacked isLazyGenerated MakeFromRaster
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkImage> makeWithFilter(GrContext* context,
-                                  const SkImageFilter* filter, const SkIRect& subset,
-                                  const SkIRect& clipBounds, SkIRect* outSubset,
-                                  SkIPoint* offset) const
-#In Constructors
-#Line # creates filtered, clipped Image ##
-#Populate
-
-#Example
-#Description
-In each frame of the animation, filtered Image is drawn in a different location.
-By translating canvas by returned offset, Image appears stationary.
-##
-#Image 5
-#Platform gpu
-#Duration 1
-    sk_sp<SkImageFilter> shadowFilter = SkDropShadowImageFilter::Make(
-                -10.0f * frame, 5.0f * frame, 3.0f, 3.0f, SK_ColorBLUE,
-                SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode,
-                nullptr);
-    sk_sp<SkImageFilter> offsetFilter = SkOffsetImageFilter::Make(40, 40, shadowFilter, nullptr);
-    SkIRect subset = image->bounds();
-    SkIRect clipBounds = image->bounds();
-    clipBounds.outset(60, 60);
-    SkIRect outSubset;
-    SkIPoint offset;
-    sk_sp<SkImage> filtered(image->makeWithFilter(canvas->getGrContext(),
-                            offsetFilter.get(), subset, clipBounds,
-                            &outSubset, &offset));
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawLine(0, 0, offset.fX, offset.fY, paint);
-    canvas->translate(offset.fX, offset.fY);
-    canvas->drawImage(filtered, 0, 0);
-    canvas->drawRect(SkRect::Make(outSubset), paint);
-##
-
-#SeeAlso makeShader SkPaint::setImageFilter
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkImage> makeWithFilter(const SkImageFilter* filter, const SkIRect& subset,
-                                  const SkIRect& clipBounds, SkIRect* outSubset,
-                                  SkIPoint* offset) const
-#In  Constructors
-#Line # creates filtered, clipped Image ##
-#Populate
-#NoExample
-##
-#SeeAlso makeShader SkPaint::setImageFilter
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Typedef std::function<void(GrBackendTexture)> BackendTextureReleaseProc
-#Line # parameter type for MakeBackendTextureFromSkImage ##
-
-#Code
-#Populate
-##
-
-Defines a callback function, taking one parameter of type GrBackendTexture with
-no return value. Function is called when back-end texture is to be released.
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static bool MakeBackendTextureFromSkImage(GrContext* context,
-                                              sk_sp<SkImage> image,
-                                              GrBackendTexture* backendTexture,
-                                              BackendTextureReleaseProc* backendTextureReleaseProc)
-#In Constructors
-#Line # creates GPU_Texture from Image ##
-#Populate
-
-#Example
-#Platform gpu
-#Height 64
-#Function
-static sk_sp<SkImage> create_gpu_image(GrContext* grContext) {
-    const SkImageInfo info = SkImageInfo::MakeN32(20, 20, kOpaque_SkAlphaType);
-    auto surface(SkSurface::MakeRenderTarget(grContext, SkBudgeted::kNo, info));
-    SkCanvas* canvas = surface->getCanvas();
-    canvas->clear(SK_ColorWHITE);
-    SkPaint paint;
-    paint.setColor(SK_ColorBLACK);
-    canvas->drawRect(SkRect::MakeXYWH(5, 5, 10, 10), paint);
-    return surface->makeImageSnapshot();
-}
-##
-
-void draw(SkCanvas* canvas) {
-    GrContext* grContext = canvas->getGrContext();
-    if (!grContext) {
-        return;
-    }
-    sk_sp<SkImage> backEndImage = create_gpu_image(grContext);
-    canvas->drawImage(backEndImage, 0, 0);
-    GrBackendTexture texture;
-    SkImage::BackendTextureReleaseProc proc;
-    if (!SkImage::MakeBackendTextureFromSkImage(grContext, std::move(backEndImage),
-            &texture, &proc)) {
-        return;
-    }
-    sk_sp<SkImage> i2 = SkImage::MakeFromTexture(grContext, texture, kTopLeft_GrSurfaceOrigin,
-            kN32_SkColorType, kOpaque_SkAlphaType, nullptr);
-    canvas->drawImage(i2, 30, 30);
-}
-##
-
-#SeeAlso MakeFromTexture makeTextureImage
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isLazyGenerated() const
-#In Property
-#Line # returns if Image is created as needed ##
-#Populate
-
-#Example
-#Height 80
-#Function
-class TestImageGenerator : public SkImageGenerator {
-public:
-    TestImageGenerator() : SkImageGenerator(SkImageInfo::MakeN32Premul(10, 10)) {}
-    ~TestImageGenerator() override {}
-protected:
-    bool onGetPixels(const SkImageInfo& info, void* pixelPtr, size_t rowBytes,
-                     const Options& options) override {
-        SkPMColor* pixels = static_cast<SkPMColor*>(pixelPtr);
-        for (int y = 0; y < info.height(); ++y) {
-            for (int x = 0; x < info.width(); ++x) {
-                pixels[y * info.width() + x] = 0xff223344 + y * 0x000C0811;
-            }
-        }
-        return true;
-    }
-};
-##
-void draw(SkCanvas* canvas) {
-    auto gen = std::unique_ptr<TestImageGenerator>(new TestImageGenerator());
-    sk_sp<SkImage> image(SkImage::MakeFromGenerator(std::move(gen)));
-    SkString lazy(image->isLazyGenerated() ? "is lazy" : "not lazy");
-    canvas->scale(8, 8);
-    canvas->drawImage(image, 0, 0, nullptr);
-    SkPaint paint;
-    paint.setTextSize(4);
-    canvas->drawString(lazy, 2, 5, paint);
-}
-##
-
-#Example
-#Image 5
-#Platform gpu
-void draw(SkCanvas* canvas) {
-    auto drawImage = [=](sk_sp<SkImage> image, const char* label) -> void {
-        if (nullptr == image) {
-            return;
-        }
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        canvas->drawImage(image, 0, 0);
-        canvas->drawString(label, 30, image->height() / 4, paint);
-        canvas->drawString(
-                image->isLazyGenerated() ? "is lazily generated" : "not lazily generated",
-                20, image->height() * 3 / 4, paint);
-    };
-    sk_sp<SkImage> bitmapImage(SkImage::MakeFromBitmap(source));
-    sk_sp<SkImage> textureImage(SkImage::MakeFromTexture(canvas->getGrContext(), backEndTexture,
-                                kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType,
-                                kOpaque_SkAlphaType, nullptr));
-    drawImage(image, "image");
-    canvas->translate(image->width(), 0);
-    drawImage(bitmapImage, "source");
-    canvas->translate(-image->width(), image->height());
-    drawImage(textureImage, "backEndTexture");
-}
-##
-
-#SeeAlso isTextureBacked makeNonTextureImage
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkImage> makeColorSpace(sk_sp<SkColorSpace> target) const
-#In Constructors
-#Line # creates Image matching Color_Space if possible ##
-#Populate
-
-#Example
-#Image 5
-#Set sRGB
-    sk_sp<SkColorSpace> normalColorSpace = SkColorSpace::MakeRGB(
-             SkNamedTransferFn::kSRGB, SkNamedGamut::kSRGB);
-    sk_sp<SkColorSpace> wackyColorSpace = normalColorSpace->makeColorSpin();
-    for (auto colorSpace : { normalColorSpace, wackyColorSpace  } ) {
-        sk_sp<SkImage> colorSpaced = image->makeColorSpace(colorSpace);
-        canvas->drawImage(colorSpaced, 0, 0);
-        canvas->translate(128, 0);
-    }
-##
-
-#SeeAlso MakeFromPicture MakeFromTexture
-
-#Method ##
-
-#Class SkImage ##
-
-#Topic Image ##
diff --git a/docs/SkMatrix_Reference.bmh b/docs/SkMatrix_Reference.bmh
deleted file mode 100644
index f624c5a..0000000
--- a/docs/SkMatrix_Reference.bmh
+++ /dev/null
@@ -1,3052 +0,0 @@
-#Topic Matrix
-#Alias Matrices ##
-#Alias Matrix_Reference ##
-
-#Class SkMatrix
-
-#Code
-#Populate
-##
-
-Matrix holds a 3 by 3 matrix for transforming coordinates. This allows mapping
-Points and Vectors with translation, scaling, skewing, rotation, and
-perspective.
-
-Matrix elements are in row major order. Matrix does not have a constructor,
-so it must be explicitly initialized. setIdentity initializes Matrix
-so it has no effect. setTranslate, setScale, setSkew, setRotate, set9 and setAll
-initializes all Matrix elements with the corresponding mapping.
-
-Matrix includes a hidden variable that classifies the type of matrix to
-improve performance. Matrix is not thread safe unless getType is called first.
-
-# ------------------------------------------------------------------------------
-
-#Method static SkMatrix MakeScale(SkScalar sx, SkScalar sy)
-#In Constructors
-#Line # constructs from scale on x-axis and y-axis ##
-#Populate
-
-#Example
-#Image 4
-canvas->concat(SkMatrix::MakeScale(4, 3));
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso setScale postScale preScale
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkMatrix MakeScale(SkScalar scale)
-#Populate
-
-#Example
-#Image 4
-canvas->concat(SkMatrix::MakeScale(4));
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso setScale postScale preScale
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkMatrix MakeTrans(SkScalar dx, SkScalar dy)
-#In Constructors
-#Line # constructs from translate on x-axis and y-axis ##
-#Populate
-
-#Example
-#Image 4
-SkMatrix matrix = SkMatrix::MakeTrans(64, 48);
-for (int i = 0; i < 4; ++i) {
-    canvas->drawBitmap(source, 0, 0);
-    canvas->concat(matrix);
-}
-##
-
-#SeeAlso setTranslate postTranslate preTranslate
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkMatrix MakeAll(SkScalar scaleX, SkScalar skewX,  SkScalar transX,
-                                                      SkScalar skewY,  SkScalar scaleY, SkScalar transY,
-                                                      SkScalar pers0, SkScalar pers1, SkScalar pers2)
-#In Constructors
-#Line # constructs all nine values ##
-#Populate
-
-#Example
-    SkPaint p;
-    p.setAntiAlias(true);
-    p.setTextSize(64);
-    for (SkScalar sx : { -1, 1 } ) {
-        for (SkScalar sy : { -1, 1 } ) {
-            SkAutoCanvasRestore autoRestore(canvas, true);
-            SkMatrix m = SkMatrix::MakeAll(sx, 1, 128,    0, sy, 128,   0, 0, 1);
-            canvas->concat(m);
-            canvas->drawString("K", 0, 0, p);
-        }
-    }
-##
-
-#SeeAlso setAll set9 postConcat preConcat
-
-##
-
-
-# ------------------------------------------------------------------------------
-
-#Enum TypeMask
-#Line # bit field for Matrix complexity ##
-#Code
-#Populate
-##
-
-Enumeration of bit fields for mask returned by getType.
-Used to identify the complexity of Matrix, to optimize performance.
-
-#Const kIdentity_Mask 0
-#Line # identity Matrix; all bits clear ##
-all bits clear if Matrix is identity
-##
-#Const kTranslate_Mask 1
-#Line # translation Matrix ##
-set if Matrix has translation
-##
-#Const kScale_Mask 2
-#Line # scale Matrix ##
-set if Matrix scales x-axis or y-axis
-##
-#Const kAffine_Mask 4
-#Line # skew or rotate Matrix ##
-set if Matrix skews or rotates
-##
-#Const kPerspective_Mask 8
-#Line # perspective Matrix ##
-set if Matrix has perspective
-##
-
-#Example
-    auto debugster = [](const char* prefix, const SkMatrix& matrix) -> void {
-        SkString typeMask;
-        typeMask += SkMatrix::kIdentity_Mask == matrix.getType() ? "kIdentity_Mask " : "";
-        typeMask += SkMatrix::kTranslate_Mask & matrix.getType() ? "kTranslate_Mask " : "";
-        typeMask += SkMatrix::kScale_Mask & matrix.getType() ? "kScale_Mask " : "";
-        typeMask += SkMatrix::kAffine_Mask & matrix.getType() ? "kAffine_Mask " : "";
-        typeMask += SkMatrix::kPerspective_Mask & matrix.getType() ? "kPerspective_Mask" : "";
-        SkDebugf("after %s: %s\n", prefix, typeMask.c_str());
-    };
-SkMatrix matrix;
-matrix.reset();
-debugster("reset", matrix);
-matrix.postTranslate(1, 0);
-debugster("postTranslate", matrix);
-matrix.postScale(2, 1);
-debugster("postScale", matrix);
-matrix.postRotate(45);
-debugster("postScale", matrix);
-SkPoint polys[][4] = {{{0, 0}, {0, 1}, {1, 1}, {1, 0}}, {{0, 0}, {0, 1}, {2, 1}, {1, 0}}};
-matrix.setPolyToPoly(polys[0], polys[1], 4);
-debugster("setPolyToPoly", matrix);
-#StdOut
-after reset: kIdentity_Mask
-after postTranslate: kTranslate_Mask
-after postScale: kTranslate_Mask kScale_Mask
-after postScale: kTranslate_Mask kScale_Mask kAffine_Mask
-after setPolyToPoly: kTranslate_Mask kScale_Mask kAffine_Mask kPerspective_Mask
-##
-##
-
-#SeeAlso getType
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Property
-#Line # values and attributes ##
-##
-
-#Method TypeMask getType() const
-#In Property
-#Line # returns transform complexity ##
-#Populate
-
-#Example
-SkMatrix matrix;
-matrix.setAll(1, 0, 0,   0, 1, 0,    0, 0, 1);
-SkDebugf("identity flags hex: %0x decimal: %d\n", matrix.getType(), matrix.getType());
-matrix.setAll(1, 0, 0,   0, 1, 0,    0, 0, .5f);
-SkDebugf("set all  flags hex: %0x decimal: %d\n", matrix.getType(), matrix.getType());
-#StdOut
-identity flags hex: 0 decimal: 0
-set all  flags hex: f decimal: 15
-##
-##
-
-#SeeAlso TypeMask
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isIdentity() const
-#In Property
-#Line # returns if matrix equals the identity Matrix  ##
-#Populate
-
-#Example
-SkMatrix matrix;
-matrix.setAll(1, 0, 0,   0, 1, 0,    0, 0, 1);
-SkDebugf("is identity: %s\n", matrix.isIdentity() ? "true" : "false");
-matrix.setAll(1, 0, 0,   0, 1, 0,    0, 0, 2);
-SkDebugf("is identity: %s\n", matrix.isIdentity() ? "true" : "false");
-#StdOut
-is identity: true
-is identity: false
-##
-##
-
-#SeeAlso reset() setIdentity getType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isScaleTranslate() const
-#In Property
-#Line # returns if transform is limited to scale and translate ##
-#Populate
-
-#Example
-SkMatrix matrix;
-for (SkScalar scaleX : { 1, 2 } ) {
-    for (SkScalar translateX : { 0, 20 } ) {
-        matrix.setAll(scaleX, 0, translateX,   0, 1, 0,    0, 0, 1);
-        SkDebugf("is scale-translate: %s\n", matrix.isScaleTranslate() ? "true" : "false");
-    }
-}
-#StdOut
-is scale-translate: true
-is scale-translate: true
-is scale-translate: true
-is scale-translate: true
-##
-##
-
-#SeeAlso setScale isTranslate setTranslate getType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isTranslate() const
-#In Property
-#Line # returns if transform is limited to translate ##
-#Populate
-
-#Example
-SkMatrix matrix;
-for (SkScalar scaleX : { 1, 2 } ) {
-    for (SkScalar translateX : { 0, 20 } ) {
-        matrix.setAll(scaleX, 0, translateX,   0, 1, 0,    0, 0, 1);
-        SkDebugf("is translate: %s\n", matrix.isTranslate() ? "true" : "false");
-    }
-}
-#StdOut
-is translate: true
-is translate: true
-is translate: false
-is translate: false
-##
-##
-
-#SeeAlso setTranslate getType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool rectStaysRect() const
-#In Property
-#Line # returns if mapped Rect can be represented by another Rect ##
-#Populate
-
-#Example
-SkMatrix matrix;
-for (SkScalar angle: { 0, 90, 180, 270 } ) {
-    matrix.setRotate(angle);
-    SkDebugf("rectStaysRect: %s\n", matrix.rectStaysRect() ? "true" : "false");
-}
-#StdOut
-rectStaysRect: true
-rectStaysRect: true
-rectStaysRect: true
-rectStaysRect: true
-##
-##
-
-#SeeAlso preservesAxisAlignment preservesRightAngles
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool preservesAxisAlignment() const
-#In Property
-#Line # returns if mapping restricts to 90 degree multiples and mirroring ##
-#Populate
-
-#Example
-SkMatrix matrix;
-for (SkScalar angle: { 0, 90, 180, 270 } ) {
-    matrix.setRotate(angle);
-    SkDebugf("preservesAxisAlignment: %s\n", matrix.preservesAxisAlignment() ? "true" : "false");
-}
-#StdOut
-preservesAxisAlignment: true
-preservesAxisAlignment: true
-preservesAxisAlignment: true
-preservesAxisAlignment: true
-##
-##
-
-#SeeAlso rectStaysRect preservesRightAngles
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool hasPerspective() const
-#In Property
-#Line # returns if transform includes perspective ##
-#Populate
-
-#Example
-#Image 4
-SkMatrix matrix;
-SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
-SkRect::Make(source.bounds()).toQuad(bitmapBounds);
-matrix.setPolyToPoly(bitmapBounds, perspect, 4);
-canvas->concat(matrix);
-SkString string;
-string.printf("hasPerspective %s", matrix.hasPerspective() ? "true" : "false");
-canvas->drawBitmap(source, 0, 0);
-SkPaint paint;
-paint.setAntiAlias(true);
-paint.setTextSize(48);
-canvas->drawString(string, 0, source.bounds().height() + 48, paint);
-##
-
-#SeeAlso setAll set9 MakeAll
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isSimilarity(SkScalar tol = SK_ScalarNearlyZero) const
-#In Property
-#Line # returns if transform is limited to square scale and rotation ##
-#Populate
-
-#Example
-#Description
-String is drawn four times through but only two are visible. Drawing the pair
-with isSimilarity false reveals the pair not visible through the matrix.
-##
-    SkPaint p;
-    p.setAntiAlias(true);
-    SkMatrix m;
-    int below = 175;
-    for (SkScalar sx : { -1, 1 } ) {
-        for (SkScalar sy : { -1, 1 } ) {
-            m.setAll(sx, 1, 128,    1, sy, 32,   0, 0, 1);
-            bool isSimilarity = m.isSimilarity();
-            SkString str;
-            str.printf("sx: %g sy: %g sim: %s", sx, sy, isSimilarity ? "true" : "false");
-            {
-                SkAutoCanvasRestore autoRestore(canvas, true);
-                canvas->concat(m);
-                canvas->drawString(str, 0, 0, p);
-            }
-            if (!isSimilarity) {
-                canvas->drawString(str, 40, below, p);
-                below += 20;
-            }
-        }
-    }
-##
-
-#SeeAlso isScaleTranslate preservesRightAngles rectStaysRect isFixedStepInX
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool preservesRightAngles(SkScalar tol = SK_ScalarNearlyZero) const
-#In Property
-#Line # returns if mapped 90 angle remains 90 degrees ##
-#Populate
-
-#Example
-#Height 128
-#Description
-Equal scale is both similar and preserves right angles.
-Unequal scale is not similar but preserves right angles.
-Skews are not similar and do not preserve right angles.
-##
-SkPaint p;
-p.setAntiAlias(true);
-SkMatrix m;
-int pos = 0;
-for (SkScalar sx : { 1, 2 } ) {
-    for (SkScalar kx : { 0, 1 } ) {
-        m.setAll(sx, kx, 16,    0, 1, 32,   0, 0, 1);
-        bool isSimilarity = m.isSimilarity();
-        bool preservesRightAngles = m.preservesRightAngles();
-        SkString str;
-        str.printf("sx: %g kx: %g %s %s", sx, kx, isSimilarity ? "sim" : "",
-                    preservesRightAngles ? "right" : "");
-        SkAutoCanvasRestore autoRestore(canvas, true);
-        canvas->concat(m);
-        canvas->drawString(str, 0, pos, p);
-        pos += 20;
-    }
-}
-##
-
-#SeeAlso isScaleTranslate isSimilarity rectStaysRect isFixedStepInX
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic MemberIndex
-#In Constant
-#Line # member indices ##
-#Filter kM
-#Code
-#Populate
-##
-
-Matrix organizes its values in row order. These members correspond to
-each value in Matrix.
-
-#Const kMScaleX 0
-#Line # horizontal scale factor ##
-##
-#Const kMSkewX 1
-#Line # horizontal skew factor ##
-##
-#Const kMTransX 2
-#Line # horizontal translation ##
-##
-#Const kMSkewY 3
-#Line # vertical skew factor ##
-##
-#Const kMScaleY 4
-#Line # vertical scale factor ##
-##
-#Const kMTransY 5
-#Line # vertical translation ##
-##
-#Const kMPersp0 6
-#Line # input x perspective factor ##
-##
-#Const kMPersp1 7
-#Line # input y perspective factor ##
-##
-#Const kMPersp2 8
-#Line # perspective bias ##
-##
-
-#Example
-SkPaint black;
-black.setAntiAlias(true);
-black.setTextSize(48);
-SkPaint gray = black;
-gray.setColor(0xFF9f9f9f);
-SkScalar offset[] = { 1.5f, 1.5f, 20,   1.5f, 1.5f, 20,   .03f, .01f, 2 };
-for (int i : { SkMatrix::kMScaleX, SkMatrix::kMSkewX,  SkMatrix::kMTransX,
-               SkMatrix::kMSkewY,  SkMatrix::kMScaleY, SkMatrix::kMTransY,
-               SkMatrix::kMPersp0, SkMatrix::kMPersp1, SkMatrix::kMPersp2 } ) {
-    SkMatrix m;
-    m.setIdentity();
-    m.set(i, offset[i]);
-    SkAutoCanvasRestore autoRestore(canvas, true);
-    canvas->translate(22 + (i % 3) * 88, 44 + (i / 3) * 88);
-    canvas->drawString("&", 0, 0, gray);
-    canvas->concat(m);
-    canvas->drawString("&", 0, 0, black);
-}
-##
-
-#SeeAlso get() set()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic AffineIndex
-#In Constant
-#Line # affine member indices ##
-#Filter KA
-
-#Code
-#Populate
-##
-
-Affine arrays are in column major order to match the matrix used by
-PDF and XPS.
-
-#Const kAScaleX 0
-#Line # horizontal scale factor ##
-##
-#Const kASkewY 1
-#Line # vertical skew factor ##
-##
-#Const kASkewX 2
-#Line # horizontal skew factor ##
-##
-#Const kAScaleY 3
-#Line # vertical scale factor ##
-##
-#Const kATransX 4
-#Line # horizontal translation ##
-##
-#Const kATransY 5
-#Line # vertical translation ##
-##
-
-#NoExample
-##
-
-#SeeAlso SetAffineIdentity asAffine setAffine
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar operator[](int index) const
-
-#Line # returns Matrix value ##
-#Populate
-
-#Example
-SkMatrix matrix;
-matrix.setScale(42, 24);
-SkDebugf("matrix[SkMatrix::kMScaleX] %c= 42\n", matrix[SkMatrix::kMScaleX] == 42 ? '=' : '!');
-SkDebugf("matrix[SkMatrix::kMScaleY] %c= 24\n", matrix[SkMatrix::kMScaleY] == 24 ? '=' : '!');
-#StdOut
-matrix[SkMatrix::kMScaleX] == 42
-matrix[SkMatrix::kMScaleY] == 24
-##
-##
-
-#SeeAlso get set
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar get(int index) const
-#In Property
-#Line # returns one of nine Matrix values ##
-#Populate
-
-#Example
-SkMatrix matrix;
-matrix.setSkew(42, 24);
-SkDebugf("matrix.get(SkMatrix::kMSkewX) %c= 42\n",
-         matrix.get(SkMatrix::kMSkewX) == 42 ? '=' : '!');
-SkDebugf("matrix.get(SkMatrix::kMSkewY) %c= 24\n",
-         matrix.get(SkMatrix::kMSkewY) == 24 ? '=' : '!');
-#StdOut
-matrix.get(SkMatrix::kMSkewX) == 42
-matrix.get(SkMatrix::kMSkewY) == 24
-##
-##
-
-#SeeAlso operator[](int index) set
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar getScaleX() const
-#In Property
-#Line # returns horizontal scale factor ##
-#Populate
-
-#Example
-SkMatrix matrix;
-matrix.setScale(42, 24);
-SkDebugf("matrix.getScaleX() %c= 42\n", matrix.getScaleX() == 42 ? '=' : '!');
-#StdOut
-matrix.getScaleX() == 42
-##
-##
-
-#SeeAlso get getScaleY setScaleX setScale
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar getScaleY() const
-#In Property
-#Line # returns vertical scale factor ##
-#Populate
-
-#Example
-SkMatrix matrix;
-matrix.setScale(42, 24);
-SkDebugf("matrix.getScaleY() %c= 24\n", matrix.getScaleY() == 24 ? '=' : '!');
-#StdOut
-matrix.getScaleY() == 24
-##
-##
-
-#SeeAlso get getScaleX setScaleY setScale
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar getSkewY() const
-#In Property
-#Line # returns vertical skew factor ##
-#Populate
-
-#Example
-SkMatrix matrix;
-matrix.setSkew(42, 24);
-SkDebugf("matrix.getSkewY() %c= 24\n", matrix.getSkewY() == 24 ? '=' : '!');
-#StdOut
-matrix.getSkewY() == 24
-##
-##
-
-#SeeAlso get getSkewX setSkewY setSkew
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar getSkewX() const
-#In Property
-#Line # returns horizontal skew factor ##
-#Populate
-
-#Example
-SkMatrix matrix;
-matrix.setSkew(42, 24);
-SkDebugf("matrix.getSkewX() %c= 42\n", matrix.getSkewX() == 42 ? '=' : '!');
-#StdOut
-matrix.getSkewX() == 42
-##
-##
-
-#SeeAlso get getSkewY setSkewX setSkew
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar getTranslateX() const
-#In Property
-#Line # returns horizontal translation ##
-#Populate
-
-#Example
-SkMatrix matrix;
-matrix.setTranslate(42, 24);
-SkDebugf("matrix.getTranslateX() %c= 42\n", matrix.getTranslateX() == 42 ? '=' : '!');
-#StdOut
-matrix.getTranslateX() == 42
-##
-##
-
-#SeeAlso get getTranslateY setTranslateX setTranslate
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar getTranslateY() const
-#In Property
-#Line # returns vertical translation ##
-#Populate
-
-#Example
-SkMatrix matrix;
-matrix.setTranslate(42, 24);
-SkDebugf("matrix.getTranslateY() %c= 24\n", matrix.getTranslateY() == 24 ? '=' : '!');
-#StdOut
-matrix.getTranslateY() == 24
-##
-##
-
-#SeeAlso get getTranslateX setTranslateY setTranslate
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar getPerspX() const
-#In Property
-#Line # returns input x perspective factor ##
-#Populate
-
-#Example
-    SkMatrix m;
-    m.setIdentity();
-    m.set(SkMatrix::kMPersp0, -0.004f);
-    SkAutoCanvasRestore autoRestore(canvas, true);
-    canvas->translate(22, 144);
-    SkPaint black;
-    black.setAntiAlias(true);
-    black.setTextSize(24);
-    SkPaint gray = black;
-    gray.setColor(0xFF9f9f9f);
-    SkString string;
-    string.appendScalar(m.getPerspX());
-    canvas->drawString(string, 0, -72, gray);
-    canvas->concat(m);
-    canvas->drawString(string, 0, 0, black);
-##
-
-#SeeAlso kMPersp0 getPerspY
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar getPerspY() const
-#In Property
-#Line # returns input y perspective factor ##
-#Populate
-
-#Example
-    SkMatrix m;
-    m.setIdentity();
-    m.set(SkMatrix::kMPersp1, -0.004f);
-    SkAutoCanvasRestore autoRestore(canvas, true);
-    canvas->translate(22, 144);
-    SkPaint black;
-    black.setAntiAlias(true);
-    black.setTextSize(24);
-    SkPaint gray = black;
-    gray.setColor(0xFF9f9f9f);
-    SkString string;
-    string.appendScalar(m.getPerspY());
-    canvas->drawString(string, 0, -72, gray);
-    canvas->concat(m);
-    canvas->drawString(string, 0, 0, black);
-##
-
-#SeeAlso kMPersp1 getPerspX
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar& operator[](int index)
-
-#Line # returns writable reference to Matrix value ##
-#Populate
-
-#Example
-SkMatrix matrix;
-matrix.setIdentity();
-SkDebugf("with identity matrix: x = %g\n", matrix.mapXY(24, 42).fX);
-SkScalar& skewRef = matrix[SkMatrix::kMSkewX];
-skewRef = 0;
-SkDebugf("after skew x mod:     x = %g\n", matrix.mapXY(24, 42).fX);
-skewRef = 1;
-SkDebugf("after 2nd skew x mod: x = %g\n", matrix.mapXY(24, 42).fX);
-matrix.dirtyMatrixTypeCache();
-SkDebugf("after dirty cache:    x = %g\n", matrix.mapXY(24, 42).fX);
-#StdOut
-with identity matrix: x = 24
-after skew x mod:     x = 24
-after 2nd skew x mod: x = 24
-after dirty cache:    x = 66
-##
-##
-
-#SeeAlso get dirtyMatrixTypeCache set
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Set
-#Line # sets one or more matrix values ##
-##
-
-#Method void set(int index, SkScalar value)
-#In Set
-#Line # sets one value ##
-#Populate
-
-#Example
-SkMatrix matrix;
-matrix.setIdentity();
-SkDebugf("with identity matrix: x = %g\n", matrix.mapXY(24, 42).fX);
-matrix.set(SkMatrix::kMSkewX, 0);
-SkDebugf("after skew x mod:     x = %g\n", matrix.mapXY(24, 42).fX);
-matrix.set(SkMatrix::kMSkewX, 1);
-SkDebugf("after 2nd skew x mod: x = %g\n", matrix.mapXY(24, 42).fX);
-#StdOut
-with identity matrix: x = 24
-after skew x mod:     x = 24
-after 2nd skew x mod: x = 66
-##
-##
-
-#SeeAlso operator[] get
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setScaleX(SkScalar v)
-#In Set
-#Line # sets horizontal scale factor ##
-#Populate
-
-#Example
-#Height 64
-SkPaint paint;
-paint.setAntiAlias(true);
-paint.setTextSize(24);
-canvas->drawString("normal", 12, 24, paint);
-SkMatrix matrix;
-matrix.setIdentity();
-matrix.setScaleX(3);
-canvas->concat(matrix);
-canvas->drawString("x scale", 0, 48, paint);
-##
-
-#SeeAlso set setScale setScaleY
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setScaleY(SkScalar v)
-#In Set
-#Line # sets vertical scale factor ##
-#Populate
-
-#Example
-#Height 192
-SkPaint paint;
-paint.setAntiAlias(true);
-paint.setTextSize(24);
-canvas->drawString("normal", 12, 24, paint);
-SkMatrix matrix;
-matrix.setIdentity();
-matrix.setScaleY(3);
-canvas->concat(matrix);
-canvas->drawString("y scale", 12, 48, paint);
-##
-
-#SeeAlso set setScale setScaleX
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setSkewY(SkScalar v)
-#In Set
-#Line # sets vertical skew factor ##
-#Populate
-
-#Example
-#Height 96
-SkPaint paint;
-paint.setAntiAlias(true);
-paint.setTextSize(24);
-canvas->drawString("normal", 12, 24, paint);
-SkMatrix matrix;
-matrix.setIdentity();
-matrix.setSkewY(.3f);
-canvas->concat(matrix);
-canvas->drawString("y skew", 12, 48, paint);
-##
-
-#SeeAlso set setSkew setSkewX
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setSkewX(SkScalar v)
-#In Set
-#Line # sets horizontal skew factor ##
-#Populate
-
-#Example
-#Height 64
-SkPaint paint;
-paint.setAntiAlias(true);
-paint.setTextSize(24);
-canvas->drawString("normal", 12, 24, paint);
-SkMatrix matrix;
-matrix.setIdentity();
-matrix.setSkewX(-.7f);
-canvas->concat(matrix);
-canvas->drawString("x skew", 36, 48, paint);
-##
-
-#SeeAlso set setSkew setSkewX
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setTranslateX(SkScalar v)
-#In Set
-#Line # sets horizontal translation ##
-#Populate
-
-#Example
-#Height 48
-SkPaint paint;
-paint.setAntiAlias(true);
-paint.setTextSize(24);
-canvas->drawString("normal", 8, 24, paint);
-SkMatrix matrix;
-matrix.setIdentity();
-matrix.setTranslateX(96);
-canvas->concat(matrix);
-canvas->drawString("x translate", 8, 24, paint);
-##
-
-#SeeAlso set setTranslate setTranslateY
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setTranslateY(SkScalar v)
-#In Set
-#Line # sets vertical translation ##
-#Populate
-
-#Example
-#Height 64
-SkPaint paint;
-paint.setAntiAlias(true);
-paint.setTextSize(24);
-canvas->drawString("normal", 8, 24, paint);
-SkMatrix matrix;
-matrix.setIdentity();
-matrix.setTranslateY(24);
-canvas->concat(matrix);
-canvas->drawString("y translate", 8, 24, paint);
-##
-
-#SeeAlso set setTranslate setTranslateX
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setPerspX(SkScalar v)
-#In Set
-#Line # sets input x perspective factor ##
-#Populate
-
-#Example
-#Image 4
-for (SkScalar perspX : { -.003f, 0.f, .003f, .012f } ) {
-    SkMatrix matrix;
-    matrix.setIdentity();
-    matrix.setPerspX(perspX);
-    canvas->save();
-    canvas->concat(matrix);
-    canvas->drawBitmap(source, 0, 0);
-    canvas->restore();
-    canvas->translate(64, 64);
-}
-##
-
-#SeeAlso getPerspX set setAll set9 MakeAll
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setPerspY(SkScalar v)
-#In Set
-#Line # sets input y perspective factor ##
-#Populate
-
-#Example
-#Image 4
-for (SkScalar perspX : { -.003f, 0.f, .003f, .012f } ) {
-    SkMatrix matrix;
-    matrix.setIdentity();
-    matrix.setPerspY(perspX);
-    canvas->save();
-    canvas->concat(matrix);
-    canvas->drawBitmap(source, 0, 0);
-    canvas->restore();
-    canvas->translate(64, 64);
-}
-##
-
-#SeeAlso getPerspY set setAll set9 MakeAll
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setAll(SkScalar scaleX, SkScalar skewX,  SkScalar transX,
-                SkScalar skewY,  SkScalar scaleY, SkScalar transY,
-                SkScalar persp0, SkScalar persp1, SkScalar persp2)
-#In Set
-#Line # sets all values from parameters ##
-#Populate
-
-#Example
-#Height 128
-    SkPaint p;
-    p.setAntiAlias(true);
-    p.setTextSize(64);
-    SkMatrix m;
-    for (SkScalar sx : { -1, 1 } ) {
-        for (SkScalar sy : { -1, 1 } ) {
-            SkAutoCanvasRestore autoRestore(canvas, true);
-            m.setAll(sx, 1, 128,    0, sy, 64,   0, 0, 1);
-            canvas->concat(m);
-            canvas->drawString("K", 0, 0, p);
-        }
-    }
-##
-
-#SeeAlso set9 MakeAll
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void get9(SkScalar buffer[9]) const
-#In Property
-#Line # returns all nine Matrix values ##
-#Populate
-
-#Example
-SkMatrix matrix = SkMatrix::MakeRectToRect({0, 0, 1, 1}, {3, 4, 7, 9},
-                                           SkMatrix::kFill_ScaleToFit);
-SkScalar b[9];
-matrix.get9(b);
-SkDebugf("{%g, %g, %g},\n{%g, %g, %g},\n{%g, %g, %g}\n", b[0], b[1], b[2],
-         b[3], b[4], b[5], b[6], b[7], b[8]);
-#StdOut
-{4, 0, 3},
-{0, 5, 4},
-{0, 0, 1}
-##
-##
-
-#SeeAlso set9
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void set9(const SkScalar buffer[9])
-#In Set
-#In Constructors
-#Line # sets all values from Scalar array ##
-#Populate
-
-#Example
-#Image 4
-SkMatrix m;
-SkScalar buffer[9] = {4, 0, 3,    0, 5, 4,     0, 0, 1};
-m.set9(buffer);
-canvas->concat(m);
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso setAll get9 MakeAll
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void reset()
-#In Constructors
-#In Set
-#Line # sets Matrix to identity ##
-#Populate
-
-#Example
-SkMatrix m;
-m.reset();
-SkDebugf("m.isIdentity(): %s\n", m.isIdentity() ? "true" : "false");
-#StdOut
-m.isIdentity(): true
-##
-##
-
-#SeeAlso isIdentity setIdentity
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setIdentity()
-#In Constructors
-#In Set
-#Line # sets Matrix to identity ##
-#Populate
-
-#Example
-SkMatrix m;
-m.setIdentity();
-SkDebugf("m.isIdentity(): %s\n", m.isIdentity() ? "true" : "false");
-#StdOut
-m.isIdentity(): true
-##
-##
-
-#SeeAlso isIdentity reset
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setTranslate(SkScalar dx, SkScalar dy)
-#In Constructors
-#In Set
-#Line # sets to translate on x-axis and y-axis ##
-#Populate
-
-#Example
-#Height 64
-SkPaint paint;
-paint.setAntiAlias(true);
-paint.setTextSize(24);
-canvas->drawString("normal", 8, 24, paint);
-SkMatrix matrix;
-matrix.setTranslate(96, 24);
-canvas->concat(matrix);
-canvas->drawString("translate", 8, 24, paint);
-##
-
-#SeeAlso setTranslateX setTranslateY
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setTranslate(const SkVector& v)
-#Populate
-
-#Example
-#Height 64
-SkPaint paint;
-paint.setAntiAlias(true);
-paint.setTextSize(24);
-canvas->drawString("normal", 8, 24, paint);
-SkMatrix matrix;
-matrix.setTranslate({96, 24});
-canvas->concat(matrix);
-canvas->drawString("translate", 8, 24, paint);
-##
-
-#SeeAlso setTranslateX setTranslateY MakeTrans
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py)
-#In Constructors
-#In Set
-#Line # sets to scale about a point ##
-#Populate
-
-#Example
-#Height 128
-    SkPaint p;
-    p.setAntiAlias(true);
-    p.setTextSize(64);
-    SkMatrix m;
-    for (SkScalar sx : { -1, 1 } ) {
-        for (SkScalar sy : { -1, 1 } ) {
-            SkAutoCanvasRestore autoRestore(canvas, true);
-            m.setScale(sx, sy, 128, 64);
-            canvas->concat(m);
-            canvas->drawString("%", 128, 64, p);
-        }
-    }
-##
-
-#SeeAlso setScaleX setScaleY MakeScale preScale postScale
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setScale(SkScalar sx, SkScalar sy)
-#Populate
-
-#Example
-#Height 128
-    SkPaint p;
-    p.setAntiAlias(true);
-    p.setTextSize(64);
-    SkMatrix m;
-    for (SkScalar sx : { -1, 1 } ) {
-        for (SkScalar sy : { -1, 1 } ) {
-            SkAutoCanvasRestore autoRestore(canvas, true);
-            m.setScale(sx, sy);
-            m.postTranslate(128, 64);
-            canvas->concat(m);
-            canvas->drawString("@", 0, 0, p);
-        }
-    }
-##
-
-#SeeAlso setScaleX setScaleY MakeScale preScale postScale
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setRotate(SkScalar degrees, SkScalar px, SkScalar py)
-#In Constructors
-#In Set
-#Line # sets to rotate about a point ##
-#Populate
-
-#Example
-#Height 128
-    SkPaint paint;
-    paint.setColor(SK_ColorGRAY);
-    paint.setAntiAlias(true);
-    SkRect rect = {20, 20, 100, 100};
-    canvas->drawRect(rect, paint);
-    paint.setColor(SK_ColorRED);
-    SkMatrix matrix;
-    matrix.setRotate(25, rect.centerX(), rect.centerY());
-    canvas->concat(matrix);
-    canvas->drawRect(rect, paint);
-##
-
-#SeeAlso setSinCos preRotate postRotate
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setRotate(SkScalar degrees)
-#Populate
-
-#Example
-#Height 128
-    SkPaint paint;
-    paint.setColor(SK_ColorGRAY);
-    paint.setAntiAlias(true);
-    SkRect rect = {20, 20, 100, 100};
-    canvas->drawRect(rect, paint);
-    paint.setColor(SK_ColorRED);
-    SkMatrix matrix;
-    matrix.setRotate(25);
-    canvas->translate(rect.centerX(), rect.centerY());
-    canvas->concat(matrix);
-    canvas->translate(-rect.centerX(), -rect.centerY());
-    canvas->drawRect(rect, paint);
-##
-
-#SeeAlso setSinCos preRotate postRotate
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setSinCos(SkScalar sinValue, SkScalar cosValue,
-                   SkScalar px, SkScalar py)
-#In Constructors
-#In Set
-#Line # sets to rotate and scale about a point ##
-#Populate
-
-#Example
-#Height 128
-    SkPaint paint;
-    paint.setColor(SK_ColorGRAY);
-    paint.setAntiAlias(true);
-    SkRect rect = {20, 20, 100, 100};
-    canvas->drawRect(rect, paint);
-    paint.setColor(SK_ColorRED);
-    SkMatrix matrix;
-    matrix.setSinCos(.25f, .85f, rect.centerX(), rect.centerY());
-    canvas->concat(matrix);
-    canvas->drawRect(rect, paint);
-##
-
-#SeeAlso setRotate setScale setRSXform
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setSinCos(SkScalar sinValue, SkScalar cosValue)
-#Populate
-
-#Example
-#Description
-Canvas needs offset after applying Matrix to pivot about Rect center.
-##
-#Height 128
-    SkPaint paint;
-    paint.setColor(SK_ColorGRAY);
-    paint.setAntiAlias(true);
-    SkRect rect = {20, 20, 100, 100};
-    canvas->drawRect(rect, paint);
-    paint.setColor(SK_ColorRED);
-    SkMatrix matrix;
-    matrix.setSinCos(.25f, .85f);
-    matrix.postTranslate(rect.centerX(), rect.centerY());
-    canvas->concat(matrix);
-    canvas->translate(-rect.centerX(), -rect.centerY());
-    canvas->drawRect(rect, paint);
-##
-
-#SeeAlso setRotate setScale setRSXform
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkMatrix& setRSXform(const SkRSXform& rsxForm)
-#In Constructors
-#In Set
-#Line # sets to rotate, scale, and translate ##
-#Populate
-
-#Example
-#Description
-Canvas needs offset after applying Matrix to pivot about Rect center.
-##
-#Height 128
-    SkPaint paint;
-    paint.setColor(SK_ColorGRAY);
-    paint.setAntiAlias(true);
-    SkRect rect = {20, 20, 100, 100};
-    canvas->drawRect(rect, paint);
-    paint.setColor(SK_ColorRED);
-    SkMatrix matrix;
-    matrix.setRSXform(SkRSXform::Make(.85f, .25f, rect.centerX(), rect.centerY()));
-    canvas->concat(matrix);
-    canvas->translate(-rect.centerX(), -rect.centerY());
-    canvas->drawRect(rect, paint);
-##
-
-#SeeAlso setSinCos setScale setTranslate
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setSkew(SkScalar kx, SkScalar ky, SkScalar px, SkScalar py)
-#In Constructors
-#In Set
-#Line # sets to skew about a point ##
-#Populate
-
-#Example
-    SkPaint p;
-    p.setAntiAlias(true);
-    p.setTextSize(48);
-    SkMatrix m;
-    for (SkScalar sx : { -1, 0, 1 } ) {
-        for (SkScalar sy : { -1, 0, 1 } ) {
-            SkAutoCanvasRestore autoRestore(canvas, true);
-            m.setSkew(sx, sy, 96 + 64 * sx, 128 + 48 * sy);
-            canvas->concat(m);
-            canvas->drawString("K", 96 + 64 * sx, 128 + 48 * sy, p);
-        }
-    }
-##
-
-#SeeAlso setSkewX setSkewY preSkew postSkew
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setSkew(SkScalar kx, SkScalar ky)
-#Populate
-
-#Example
-    SkPaint p;
-    p.setAntiAlias(true);
-    p.setTextSize(48);
-    SkMatrix m;
-    for (SkScalar sx : { -1, 0, 1 } ) {
-        for (SkScalar sy : { -1, 0, 1 } ) {
-            SkAutoCanvasRestore autoRestore(canvas, true);
-            m.setSkew(sx, sy);
-            m.postTranslate(96 + 64 * sx, 128 + 48 * sy);
-            canvas->concat(m);
-            canvas->drawString("K", 0, 0, p);
-        }
-    }
-##
-
-#SeeAlso setSkewX setSkewY preSkew postSkew
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setConcat(const SkMatrix& a, const SkMatrix& b)
-#In Constructors
-#In Set
-#Line # sets to Matrix parameter multiplied by Matrix parameter ##
-#Populate
-
-#Example
-#Image 3
-#Description
-setPolyToPoly creates perspective matrices, one the inverse of the other.
-Multiplying the matrix by its inverse turns into an identity matrix.
-##
-SkMatrix matrix, matrix2;
-SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
-SkRect::Make(source.bounds()).toQuad(bitmapBounds);
-matrix.setPolyToPoly(bitmapBounds, perspect, 4);
-matrix2.setPolyToPoly(perspect, bitmapBounds, 4);
-matrix.setConcat(matrix, matrix2);
-canvas->concat(matrix);
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso Concat preConcat postConcat SkCanvas::concat
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void preTranslate(SkScalar dx, SkScalar dy)
-#In Set
-#In Operators
-#Line # pre-multiplies Matrix by translation ##
-#Populate
-
-#Example
-#Height 160
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkRect rect = {20, 20, 100, 100};
-    for (int i = 0; i < 2; ++i ) {
-        SkMatrix matrix;
-        i == 0 ? matrix.reset(): matrix.setRotate(25, rect.centerX(), 320);
-        {
-            SkAutoCanvasRestore acr(canvas, true);
-            canvas->concat(matrix);
-            paint.setColor(SK_ColorGRAY);
-            canvas->drawRect(rect, paint);
-        }
-        paint.setColor(SK_ColorRED);
-        for (int j = 0; j < 2; ++j ) {
-            SkAutoCanvasRestore acr(canvas, true);
-            matrix.preTranslate(40, 40);
-            canvas->concat(matrix);
-            canvas->drawCircle(0, 0, 3, paint);
-        }
-    }
-##
-
-#SeeAlso postTranslate setTranslate MakeTrans
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void preScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py)
-#In Set
-#In Operators
-#Line # pre-multiplies Matrix by scale ##
-#Populate
-
-#Example
-#Image 3
-SkMatrix matrix;
-SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
-SkRect::Make(source.bounds()).toQuad(bitmapBounds);
-matrix.setPolyToPoly(bitmapBounds, perspect, 4);
-matrix.preScale(.75f, 1.5f, source.width() / 2, source.height() / 2);
-canvas->concat(matrix);
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso postScale setScale MakeScale
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void preScale(SkScalar sx, SkScalar sy)
-#In Set
-#In Operators
-#Populate
-
-#Example
-#Image 3
-SkMatrix matrix;
-SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
-SkRect::Make(source.bounds()).toQuad(bitmapBounds);
-matrix.setPolyToPoly(bitmapBounds, perspect, 4);
-matrix.preScale(.75f, 1.5f);
-canvas->concat(matrix);
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso postScale setScale MakeScale
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void preRotate(SkScalar degrees, SkScalar px, SkScalar py)
-#In Set
-#In Operators
-#Line # pre-multiplies Matrix by rotation ##
-#Populate
-
-#Example
-#Image 3
-SkMatrix matrix;
-SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
-SkRect::Make(source.bounds()).toQuad(bitmapBounds);
-matrix.setPolyToPoly(bitmapBounds, perspect, 4);
-matrix.preRotate(45, source.width() / 2, source.height() / 2);
-canvas->concat(matrix);
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso postRotate setRotate
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void preRotate(SkScalar degrees)
-#Populate
-
-#Example
-#Image 3
-SkMatrix matrix;
-SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
-SkRect::Make(source.bounds()).toQuad(bitmapBounds);
-matrix.setPolyToPoly(bitmapBounds, perspect, 4);
-matrix.preRotate(45);
-canvas->concat(matrix);
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso postRotate setRotate
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void preSkew(SkScalar kx, SkScalar ky, SkScalar px, SkScalar py)
-#In Set
-#In Operators
-#Line # pre-multiplies Matrix by skew ##
-#Populate
-
-#Example
-#Image 3
-SkMatrix matrix;
-SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
-SkRect::Make(source.bounds()).toQuad(bitmapBounds);
-matrix.setPolyToPoly(bitmapBounds, perspect, 4);
-matrix.preSkew(.5f, 0, source.width() / 2, source.height() / 2);
-canvas->concat(matrix);
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso postSkew setSkew
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void preSkew(SkScalar kx, SkScalar ky)
-#Populate
-
-#Example
-#Image 3
-SkMatrix matrix;
-SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
-SkRect::Make(source.bounds()).toQuad(bitmapBounds);
-matrix.setPolyToPoly(bitmapBounds, perspect, 4);
-matrix.preSkew(.5f, 0);
-canvas->concat(matrix);
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso postSkew setSkew
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void preConcat(const SkMatrix& other)
-#In Set
-#In Operators
-#Line # pre-multiplies Matrix by Matrix parameter ##
-#Populate
-
-#Example
-#Image 3
-#Description
-setPolyToPoly creates perspective matrices, one the inverse of the other.
-Multiplying the matrix by its inverse turns into an identity matrix.
-##
-SkMatrix matrix, matrix2;
-SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
-SkRect::Make(source.bounds()).toQuad(bitmapBounds);
-matrix.setPolyToPoly(bitmapBounds, perspect, 4);
-matrix2.setPolyToPoly(perspect, bitmapBounds, 4);
-matrix.preConcat(matrix2);
-canvas->concat(matrix);
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso postConcat setConcat Concat
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void postTranslate(SkScalar dx, SkScalar dy)
-#In Set
-#In Operators
-#Line # post-multiplies Matrix by translation ##
-#Populate
-
-#Example
-#Height 160
-#Description
-Compare with preTranslate example.
-##
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkRect rect = {20, 20, 100, 100};
-    for (int i = 0; i < 2; ++i ) {
-        SkMatrix matrix;
-        i == 0 ? matrix.reset(): matrix.setRotate(25, rect.centerX(), 320);
-        {
-            SkAutoCanvasRestore acr(canvas, true);
-            canvas->concat(matrix);
-            paint.setColor(SK_ColorGRAY);
-            canvas->drawRect(rect, paint);
-        }
-        paint.setColor(SK_ColorRED);
-        for (int j = 0; j < 2; ++j ) {
-            SkAutoCanvasRestore acr(canvas, true);
-            matrix.postTranslate(40, 40);
-            canvas->concat(matrix);
-            canvas->drawCircle(0, 0, 3, paint);
-        }
-    }
-##
-
-#SeeAlso preTranslate setTranslate MakeTrans
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void postScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py)
-#In Set
-#In Operators
-#Line # post-multiplies Matrix by scale ##
-#Populate
-
-#Example
-#Image 3
-SkMatrix matrix;
-SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
-SkRect::Make(source.bounds()).toQuad(bitmapBounds);
-matrix.setPolyToPoly(bitmapBounds, perspect, 4);
-matrix.postScale(.75f, 1.5f, source.width() / 2, source.height() / 2);
-canvas->concat(matrix);
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso preScale setScale MakeScale
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void postScale(SkScalar sx, SkScalar sy)
-#Populate
-
-#Example
-#Image 3
-SkMatrix matrix;
-SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
-SkRect::Make(source.bounds()).toQuad(bitmapBounds);
-matrix.setPolyToPoly(bitmapBounds, perspect, 4);
-matrix.postScale(.75f, 1.5f);
-canvas->concat(matrix);
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso preScale setScale MakeScale
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool postIDiv(int divx, int divy)
-#In Set
-#In Operators
-#Line # post-multiplies Matrix by inverse scale ##
-Sets Matrix to Matrix constructed from scaling by (1/divx, 1/divy),
-multiplied by Matrix.
-
-Returns false if either divx or divy is zero.
-
-Given:
-
-#Code
-#Literal
-         | J K L |                   | sx  0  0 |
-Matrix = | M N O |,  I(divx, divy) = |  0 sy  0 |
-         | P Q R |                   |  0  0  1 |
-##
-
-where
-
-#Code
-#Literal
-sx = 1 / divx
-sy = 1 / divy
-##
-
-sets Matrix to:
-
-#Code
-#Literal
-                         | sx  0  0 | | J K L |   | sx*J sx*K sx*L |
-I(divx, divy) * Matrix = |  0 sy  0 | | M N O | = | sy*M sy*N sy*O |
-                         |  0  0  1 | | P Q R |   |    P    Q    R |
-##
-
-#Param divx  integer divisor for inverse scale on x-axis ##
-#Param divy  integer divisor for inverse scale on y-axis ##
-
-#Return  true on successful scale ##
-
-#Example
-#Image 3
-SkMatrix matrix;
-SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
-SkRect::Make(source.bounds()).toQuad(bitmapBounds);
-matrix.setPolyToPoly(bitmapBounds, perspect, 4);
-matrix.postIDiv(1, 2);
-canvas->concat(matrix);
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso postScale MakeScale
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void postRotate(SkScalar degrees, SkScalar px, SkScalar py)
-#In Set
-#In Operators
-#Line # post-multiplies Matrix by rotation ##
-#Populate
-
-#Example
-#Image 3
-SkMatrix matrix;
-SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
-SkRect::Make(source.bounds()).toQuad(bitmapBounds);
-matrix.setPolyToPoly(bitmapBounds, perspect, 4);
-matrix.postRotate(45, source.width() / 2, source.height() / 2);
-canvas->concat(matrix);
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso preRotate setRotate
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void postRotate(SkScalar degrees)
-#Populate
-
-#Example
-#Image 3
-SkMatrix matrix;
-SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
-SkRect::Make(source.bounds()).toQuad(bitmapBounds);
-matrix.setPolyToPoly(bitmapBounds, perspect, 4);
-matrix.postRotate(45);
-canvas->concat(matrix);
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso preRotate setRotate
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void postSkew(SkScalar kx, SkScalar ky, SkScalar px, SkScalar py)
-#In Set
-#In Operators
-#Line # post-multiplies Matrix by skew ##
-#Populate
-
-#Example
-#Image 3
-SkMatrix matrix;
-SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
-SkRect::Make(source.bounds()).toQuad(bitmapBounds);
-matrix.setPolyToPoly(bitmapBounds, perspect, 4);
-matrix.postSkew(.5f, 0, source.width() / 2, source.height() / 2);
-canvas->concat(matrix);
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso preSkew setSkew
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void postSkew(SkScalar kx, SkScalar ky)
-#Populate
-
-#Example
-#Image 3
-SkMatrix matrix;
-SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
-SkRect::Make(source.bounds()).toQuad(bitmapBounds);
-matrix.setPolyToPoly(bitmapBounds, perspect, 4);
-matrix.postSkew(.5f, 0);
-canvas->concat(matrix);
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso preSkew setSkew
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void postConcat(const SkMatrix& other)
-#In Set
-#In Operators
-#Line # post-multiplies Matrix by Matrix parameter ##
-#Populate
-
-#Example
-#Image 3
-#Height 64
-SkMatrix matrix;
-SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
-SkRect::Make(source.bounds()).toQuad(bitmapBounds);
-matrix.setPolyToPoly(bitmapBounds, perspect, 4);
-matrix.postConcat(matrix);
-canvas->concat(matrix);
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso preConcat setConcat Concat
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Enum ScaleToFit
-#Line # options to map Rects ##
-#Code
-    enum ScaleToFit {
-        kFill_ScaleToFit,
-        kStart_ScaleToFit,
-        kCenter_ScaleToFit,
-        kEnd_ScaleToFit,
-    };
-##
-
-ScaleToFit describes how Matrix is constructed to map one Rect to another.
-ScaleToFit may allow Matrix to have unequal horizontal and vertical scaling,
-or may restrict Matrix to square scaling. If restricted, ScaleToFit specifies
-how Matrix maps to the side or center of the destination Rect.
-
-#Const kFill_ScaleToFit 0
-#Line # scales about x-axis and y-axis to fill destination Rect ##
-    Computes Matrix that scales about x-axis and y-axis independently, so that
-    source Rect is mapped to completely fill destination Rect. The aspect ratio
-    of source Rect may change.
-##
-#Const kStart_ScaleToFit 1
-#Line # scales and aligns to left and top ##
-    Computes Matrix that maintains source Rect aspect ratio, mapping source Rect
-    width or height to destination Rect. Aligns mapping to left and top edges
-    of destination Rect.
-##
-#Const kCenter_ScaleToFit 2
-#Line # scales and aligns to center ##
-    Computes Matrix that maintains source Rect aspect ratio, mapping source Rect
-    width or height to destination Rect. Aligns mapping to center of destination
-    Rect.
-##
-#Const kEnd_ScaleToFit 3
-#Line # scales and aligns to right and bottom ##
-    Computes Matrix that maintains source Rect aspect ratio, mapping source Rect
-    width or height to destination Rect. Aligns mapping to right and bottom
-    edges of destination Rect.
-##
-
-#Example
-   const char* labels[] = { "Fill", "Start", "Center", "End" };
-   SkRect rects[] = {{5, 5, 59, 59}, {5, 74, 59, 108}, {10, 123, 44, 172}, {10, 187, 54, 231}};
-   SkRect bounds;
-   source.getBounds(&bounds);
-   SkPaint paint;
-   paint.setAntiAlias(true);
-   for (auto fit : { SkMatrix::kFill_ScaleToFit, SkMatrix::kStart_ScaleToFit,
-                     SkMatrix::kCenter_ScaleToFit, SkMatrix::kEnd_ScaleToFit } ) {
-       for (auto rect : rects ) {
-           canvas->drawRect(rect, paint);
-           SkMatrix matrix;
-           if (!matrix.setRectToRect(bounds, rect, fit)) {
-               continue;
-           }
-           SkAutoCanvasRestore acr(canvas, true);
-           canvas->concat(matrix);
-           canvas->drawBitmap(source, 0, 0);
-       }
-       canvas->drawString(labels[fit], 10, 255, paint);
-       canvas->translate(64, 0);
-   }
-##
-
-#SeeAlso setRectToRect MakeRectToRect setPolyToPoly
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool setRectToRect(const SkRect& src, const SkRect& dst, ScaleToFit stf)
-#In Set
-#Line # sets to map one Rect to another ##
-#Populate
-
-#Example
-    const SkRect srcs[] = { {0, 0, 0, 0}, {1, 2, 3, 4} };
-    const SkRect dsts[] = { {0, 0, 0, 0}, {5, 6, 8, 9} };
-    for (auto src : srcs) {
-        for (auto dst : dsts) {
-             SkMatrix matrix;
-             matrix.setAll(-1, -1, -1, -1, -1, -1, -1, -1, -1);
-             bool success = matrix.setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
-             SkDebugf("src: %g, %g, %g, %g  dst: %g, %g, %g, %g  success: %s\n",
-                      src.fLeft, src.fTop, src.fRight, src.fBottom,
-                      dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, success ? "true" : "false");
-             matrix.dump();
-        }
-    }
-#StdOut
-src: 0, 0, 0, 0  dst: 0, 0, 0, 0  success: false
-[  1.0000   0.0000   0.0000][  0.0000   1.0000   0.0000][  0.0000   0.0000   1.0000]
-src: 0, 0, 0, 0  dst: 5, 6, 8, 9  success: false
-[  1.0000   0.0000   0.0000][  0.0000   1.0000   0.0000][  0.0000   0.0000   1.0000]
-src: 1, 2, 3, 4  dst: 0, 0, 0, 0  success: true
-[  0.0000   0.0000   0.0000][  0.0000   0.0000   0.0000][  0.0000   0.0000   1.0000]
-src: 1, 2, 3, 4  dst: 5, 6, 8, 9  success: true
-[  1.5000   0.0000   3.5000][  0.0000   1.5000   3.0000][  0.0000   0.0000   1.0000]
-##
-##
-
-#SeeAlso MakeRectToRect ScaleToFit setPolyToPoly SkRect::isEmpty
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkMatrix MakeRectToRect(const SkRect& src, const SkRect& dst, ScaleToFit stf)
-#In Constructors
-#Line # constructs from source Rect to destination Rect ##
-#Populate
-
-#Example
-    const SkRect srcs[] = { {0, 0, 0, 0}, {1, 2, 3, 4} };
-    const SkRect dsts[] = { {0, 0, 0, 0}, {5, 6, 8, 9} };
-    for (auto src : srcs) {
-        for (auto dst : dsts) {
-             SkMatrix matrix = SkMatrix::MakeRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
-             SkDebugf("src: %g, %g, %g, %g  dst: %g, %g, %g, %g\n",
-                      src.fLeft, src.fTop, src.fRight, src.fBottom,
-                      dst.fLeft, dst.fTop, dst.fRight, dst.fBottom);
-             matrix.dump();
-        }
-    }
-#StdOut
-src: 0, 0, 0, 0  dst: 0, 0, 0, 0
-[  1.0000   0.0000   0.0000][  0.0000   1.0000   0.0000][  0.0000   0.0000   1.0000]
-src: 0, 0, 0, 0  dst: 5, 6, 8, 9
-[  1.0000   0.0000   0.0000][  0.0000   1.0000   0.0000][  0.0000   0.0000   1.0000]
-src: 1, 2, 3, 4  dst: 0, 0, 0, 0
-[  0.0000   0.0000   0.0000][  0.0000   0.0000   0.0000][  0.0000   0.0000   1.0000]
-src: 1, 2, 3, 4  dst: 5, 6, 8, 9
-[  1.5000   0.0000   3.5000][  0.0000   1.5000   3.0000][  0.0000   0.0000   1.0000]
-##
-##
-
-#SeeAlso setRectToRect ScaleToFit setPolyToPoly SkRect::isEmpty
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool setPolyToPoly(const SkPoint src[], const SkPoint dst[], int count)
-#In Set
-#Line # sets to map one to four points to an equal array of points ##
-#Populate
-
-#Example
-    const SkPoint src[] = { { 0, 0}, {30,   0}, {30, -30}, { 0, -30} };
-    const SkPoint dst[] = { {50, 0}, {80, -10}, {90, -30}, {60, -40} };
-    SkPaint blackPaint;
-    blackPaint.setAntiAlias(true);
-    blackPaint.setTextSize(42);
-    SkPaint redPaint = blackPaint;
-    redPaint.setColor(SK_ColorRED);
-    for (int count : { 1, 2, 3, 4 } ) {
-        canvas->translate(35, 55);
-        for (int index = 0; index < count; ++index) {
-            canvas->drawCircle(src[index], 3, blackPaint);
-            canvas->drawCircle(dst[index], 3, blackPaint);
-            if (index > 0) {
-                canvas->drawLine(src[index], src[index - 1], blackPaint);
-                canvas->drawLine(dst[index], dst[index - 1], blackPaint);
-            }
-        }
-        SkMatrix matrix;
-        matrix.setPolyToPoly(src, dst, count);
-        canvas->drawString("A", src[0].fX, src[0].fY, redPaint);
-        SkAutoCanvasRestore acr(canvas, true);
-        canvas->concat(matrix);
-        canvas->drawString("A", src[0].fX, src[0].fY, redPaint);
-    }
-##
-
-#SeeAlso setRectToRect MakeRectToRect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool invert(SkMatrix* inverse) const
-#In Operators
-#Line # returns inverse, if possible ##
-#Populate
-
-#Example
-#Height 128
-    const SkPoint src[] = { { 10, 120}, {120, 120}, {120, 10}, {  10, 10} };
-    const SkPoint dst[] = { {150, 120}, {200, 100}, {240, 30}, { 130, 40} };
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkMatrix matrix;
-    matrix.setPolyToPoly(src, dst, 4);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, 4, src, paint);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, 4, dst, paint);
-    paint.setColor(SK_ColorBLUE);
-    paint.setStrokeWidth(3);
-    paint.setStrokeCap(SkPaint::kRound_Cap);
-    canvas->drawPoints(SkCanvas::kPoints_PointMode, 4, dst, paint);
-    if (matrix.invert(&matrix)) {
-        canvas->concat(matrix);
-        canvas->drawPoints(SkCanvas::kPoints_PointMode, 4, dst, paint);
-    }
-##
-
-#SeeAlso Concat
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static void SetAffineIdentity(SkScalar affine[6])
-#In Constructors
-#Line # sets 3x2 array to identity ##
-#Populate
-
-#Example
-    SkScalar affine[6];
-    SkMatrix::SetAffineIdentity(affine);
-    const char* names[] = { "ScaleX", "SkewY", "SkewX", "ScaleY", "TransX", "TransY" };
-    for (int i = 0; i < 6; ++i) {
-        SkDebugf("%s: %g ", names[i], affine[i]);
-    }
-    SkDebugf("\n");
-#StdOut
-ScaleX: 1 SkewY: 0 SkewX: 0 ScaleY: 1 TransX: 0 TransY: 0
-##
-##
-
-#SeeAlso setAffine asAffine
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool asAffine(SkScalar affine[6]) const
-#In Constructors
-#Line # copies to 3x2 array ##
-#Populate
-
-#Example
-SkMatrix matrix;
-matrix.setAll(2, 3, 4, 5, 6, 7, 0, 0, 1);
-SkScalar affine[6];
-if (matrix.asAffine(affine)) {
-    const char* names[] = { "ScaleX", "SkewY", "SkewX", "ScaleY", "TransX", "TransY" };
-    for (int i = 0; i < 6; ++i) {
-        SkDebugf("%s: %g ", names[i], affine[i]);
-    }
-    SkDebugf("\n");
-}
-#StdOut
-ScaleX: 2 SkewY: 5 SkewX: 3 ScaleY: 6 TransX: 4 TransY: 7
-##
-##
-
-#SeeAlso setAffine SetAffineIdentity
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void setAffine(const SkScalar affine[6])
-#In Constructors
-#In Set
-#Line # sets left two columns ##
-#Populate
-
-#Example
-SkMatrix matrix;
-matrix.setAll(2, 3, 4, 5, 6, 7, 0, 0, 1);
-SkScalar affine[6];
-if (matrix.asAffine(affine)) {
-    const char* names[] = { "ScaleX", "SkewY", "SkewX", "ScaleY", "TransX", "TransY" };
-    for (int i = 0; i < 6; ++i) {
-        SkDebugf("%s: %g ", names[i], affine[i]);
-    }
-    SkDebugf("\n");
-    matrix.reset();
-    matrix.setAffine(affine);
-    matrix.dump();
-}
-#StdOut
-ScaleX: 2 SkewY: 5 SkewX: 3 ScaleY: 6 TransX: 4 TransY: 7
-[  2.0000   3.0000   4.0000][  5.0000   6.0000   7.0000][  0.0000   0.0000   1.0000]
-##
-##
-
-#SeeAlso asAffine SetAffineIdentity
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Transform
-#Line # map points with Matrix ##
-##
-
-#Method void mapPoints(SkPoint dst[], const SkPoint src[], int count) const
-#In Transform
-#Line # maps Point array ##
-#Populate
-
-#Example
-    SkMatrix matrix;
-    matrix.reset();
-    const int count = 4;
-    SkPoint src[count];
-    matrix.mapRectToQuad(src, {40, 70, 180, 220} );
-    SkPaint paint;
-    paint.setARGB(77, 23, 99, 154);
-    for (int i = 0; i < 5; ++i) {
-        SkPoint dst[count];
-        matrix.mapPoints(dst, src, count);
-        canvas->drawPoints(SkCanvas::kPolygon_PointMode, count, dst, paint);
-        matrix.preRotate(35, 128, 128);
-    }
-##
-
-#SeeAlso mapXY mapHomogeneousPoints mapVectors
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void mapPoints(SkPoint pts[], int count) const
-#Populate
-
-#Example
-    SkMatrix matrix;
-    matrix.setRotate(35, 128, 128);
-    const int count = 4;
-    SkPoint pts[count];
-    matrix.mapRectToQuad(pts, {40, 70, 180, 220} );
-    SkPaint paint;
-    paint.setARGB(77, 23, 99, 154);
-    for (int i = 0; i < 5; ++i) {
-        canvas->drawPoints(SkCanvas::kPolygon_PointMode, count, pts, paint);
-        matrix.mapPoints(pts, count);
-    }
-##
-
-#SeeAlso mapXY mapHomogeneousPoints mapVectors
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void mapHomogeneousPoints(SkPoint3 dst[], const SkPoint3 src[], int count) const
-#In Transform
-#Line # maps Point3 array ##
-#Populate
-
-#Example
-    SkPoint3 src[] = {{3, 3, 1}, {8, 2, 2}, {5, 0, 4}, {0, 1, 3},
-                      {3, 7, 1}, {8, 6, 2}, {5, 4, 4}, {0, 5, 3}};
-    int lines[] = { 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 };
-    constexpr int count = SK_ARRAY_COUNT(src);
-    auto debugster = [=](SkPoint3 src[]) -> void {
-    for (size_t i = 0; i < SK_ARRAY_COUNT(lines); i += 2) {
-        const SkPoint3& s = src[lines[i]];
-        const SkPoint3& e = src[lines[i + 1]];
-        SkPaint paint;
-        paint.setARGB(77, 23, 99, 154);
-        canvas->drawLine(s.fX / s.fZ, s.fY / s.fZ, e.fX / e.fZ, e.fY / e.fZ, paint);
-    }
-    };
-    canvas->save();
-    canvas->translate(5, 5);
-    canvas->scale(15, 15);
-    debugster(src);
-    canvas->restore();
-    canvas->translate(128, 128);
-    SkMatrix matrix;
-    matrix.setAll(15, 0, 0, 0, 15, 0, -0.08, 0.04, 1);
-    matrix.mapHomogeneousPoints(src, src, count);
-    debugster(src);
-##
-
-#SeeAlso mapPoints mapXY mapVectors
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void mapXY(SkScalar x, SkScalar y, SkPoint* result) const
-#In Transform
-#Line # maps Point ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkMatrix matrix;
-    matrix.setRotate(60, 128, 128);
-    SkPoint lines[] = {{50, 50}, {150, 50}, {150, 150}};
-    for (size_t i = 0; i < SK_ARRAY_COUNT(lines); ++i) {
-        SkPoint pt;
-        matrix.mapXY(lines[i].fX, lines[i].fY, &pt);
-        canvas->drawCircle(pt.fX, pt.fY, 3, paint);
-    }
-    canvas->concat(matrix);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(lines), lines, paint);
-##
-
-#SeeAlso mapPoints mapVectors
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPoint mapXY(SkScalar x, SkScalar y) const
-#Populate
-
-#Example
-#Image 4
-SkMatrix matrix;
-SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {30, 206}};
-SkRect::Make(source.bounds()).toQuad(bitmapBounds);
-matrix.setPolyToPoly(bitmapBounds, perspect, 4);
-SkPaint paint;
-paint.setAntiAlias(true);
-paint.setStrokeWidth(3);
-for (int x : { 0, source.width() } ) {
-    for (int y : { 0, source.height() } ) {
-        canvas->drawPoint(matrix.mapXY(x, y), paint);
-    }
-}
-canvas->concat(matrix);
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso mapPoints mapVectors
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void mapVectors(SkVector dst[], const SkVector src[], int count) const
-#In Transform
-#Line # maps Vector array ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkMatrix matrix;
-    matrix.reset();
-    const SkVector radii[] = {{8, 4}, {9, 1}, {6, 2}, {7, 3}};
-    for (int i = 0; i < 4; ++i) {
-        SkVector rScaled[4];
-        matrix.preScale(1.5f, 2.f);
-        matrix.mapVectors(rScaled, radii, SK_ARRAY_COUNT(radii));
-        SkRRect rrect;
-        rrect.setRectRadii({20, 20, 180, 70}, rScaled);
-        canvas->drawRRect(rrect, paint);
-        canvas->translate(0, 60);
-    }
-##
-
-#SeeAlso mapVector mapPoints mapXY
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void mapVectors(SkVector vecs[], int count) const
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkMatrix matrix;
-    matrix.setScale(2, 3);
-    SkVector radii[] = {{7, 7}, {3, 3}, {2, 2}, {4, 0}};
-    for (int i = 0; i < 4; ++i) {
-        SkRRect rrect;
-        rrect.setRectRadii({20, 20, 180, 70}, radii);
-        canvas->drawRRect(rrect, paint);
-        canvas->translate(0, 60);
-        matrix.mapVectors(radii, SK_ARRAY_COUNT(radii));
-    }
-##
-
-#SeeAlso mapVector mapPoints mapXY
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void mapVector(SkScalar dx, SkScalar dy, SkVector* result) const
-#In Transform
-#Line # maps Vector ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setColor(SK_ColorGREEN);
-    paint.setAntiAlias(true);
-    paint.setTextSize(48);
-    SkMatrix matrix;
-    matrix.setRotate(90);
-    SkVector offset = { 7, 7 };
-    for (int i = 0; i < 4; ++i) {
-        paint.setImageFilter(SkDropShadowImageFilter::Make(offset.fX, offset.fY, 3, 3,
-              SK_ColorBLUE, SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode, nullptr));
-        matrix.mapVector(offset.fX, offset.fY, &offset);
-        canvas->translate(0, 60);
-        canvas->drawString("Text", 50, 0, paint);
-    }
-##
-
-#SeeAlso mapVectors mapPoints mapXY
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkVector mapVector(SkScalar dx, SkScalar dy) const
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setColor(SK_ColorGREEN);
-    paint.setAntiAlias(true);
-    paint.setTextSize(48);
-    SkMatrix matrix;
-    matrix.setRotate(90);
-    SkVector offset = { 7, 7 };
-    for (int i = 0; i < 4; ++i) {
-        paint.setImageFilter(SkDropShadowImageFilter::Make(offset.fX, offset.fY, 3, 3,
-              SK_ColorBLUE, SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode, nullptr));
-        offset = matrix.mapVector(offset.fX, offset.fY);
-        canvas->translate(0, 60);
-        canvas->drawString("Text", 50, 0, paint);
-    }
-##
-
-#SeeAlso mapVectors mapPoints mapXY
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool mapRect(SkRect* dst, const SkRect& src) const
-#In Transform
-#Line # returns bounds of mapped Rect ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkMatrix matrix;
-    matrix.setRotate(45, 128, 128);
-    SkRect rotatedBounds, bounds = {40, 50, 190, 200};
-    matrix.mapRect(&rotatedBounds, bounds );
-    paint.setColor(SK_ColorGRAY);
-    canvas->drawRect(rotatedBounds, paint);
-    canvas->concat(matrix);
-    paint.setColor(SK_ColorRED);
-    canvas->drawRect(bounds, paint);
-##
-
-#SeeAlso mapPoints rectStaysRect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool mapRect(SkRect* rect) const
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkMatrix matrix;
-    matrix.setRotate(45, 128, 128);
-    SkRect bounds = {40, 50, 190, 200};
-    matrix.mapRect(&bounds);
-    paint.setColor(SK_ColorGRAY);
-    canvas->drawRect(bounds, paint);
-    canvas->concat(matrix);
-    paint.setColor(SK_ColorRED);
-    canvas->drawRect({40, 50, 190, 200}, paint);
-##
-
-#SeeAlso mapRectScaleTranslate mapPoints rectStaysRect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkRect mapRect(const SkRect& src) const
-#Populate
-
-#Example
-  SkRect rect{110, 50, 180, 100};
-  SkMatrix matrix;
-  matrix.setRotate(50, 28, 28);
-  SkRect mapped = matrix.mapRect(rect);
-  SkPaint paint;
-  paint.setAntiAlias(true);
-  paint.setStyle(SkPaint::kStroke_Style);
-  canvas->drawRect(rect, paint);
-  canvas->drawRect(mapped, paint);
-  canvas->concat(matrix);
-  canvas->drawRect(rect, paint);
-##
-
-#SeeAlso mapRectToQuad mapRectScaleTranslate
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void mapRectToQuad(SkPoint dst[4], const SkRect& rect) const
-#In Transform
-#Line # maps Rect to Point array ##
-#Populate
-
-#Example
-#Height 192
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkMatrix matrix;
-    matrix.setRotate(60, 128, 128);
-    SkRect rect = {50, 50, 150, 150};
-    SkPoint pts[4];
-    matrix.mapRectToQuad(pts, rect);
-    for (int i = 0; i < 4; ++i) {
-        canvas->drawCircle(pts[i].fX, pts[i].fY, 3, paint);
-    }
-    canvas->concat(matrix);
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawRect(rect, paint);
-##
-
-#SeeAlso mapRect mapRectScaleTranslate
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void mapRectScaleTranslate(SkRect* dst, const SkRect& src) const
-#In Transform
-#Line # returns bounds of mapped Rect ##
-#Populate
-
-#Example
-    SkPaint paint;
-    SkMatrix matrix;
-    SkRect rect = {100, 50, 150, 180};
-    matrix.setScale(2, .5f, rect.centerX(), rect.centerY());
-    SkRect rotated;
-    matrix.mapRectScaleTranslate(&rotated, rect);
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawRect(rect, paint);
-    paint.setColor(SK_ColorRED);
-    canvas->drawRect(rotated, paint);
-##
-
-#SeeAlso mapRect mapRectToQuad isScaleTranslate rectStaysRect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar mapRadius(SkScalar radius) const
-#In Transform
-#Line # returns mean radius of mapped Circle ##
-#Populate
-
-#Example
-#Description
-The area enclosed by a square with sides equal to mappedRadius is the same as
-the area enclosed by the ellipse major and minor axes.
-##
-  SkPaint paint;
-  paint.setAntiAlias(true);
-  SkMatrix matrix;
-  const SkPoint center = {108, 93};
-  matrix.setScale(2, .5f, center.fX, center.fY);
-  matrix.postRotate(45, center.fX, center.fY);
-  const SkScalar circleRadius = 50;
-  SkScalar mappedRadius = matrix.mapRadius(circleRadius);
-  SkVector minorAxis, majorAxis;
-  matrix.mapVector(0, circleRadius, &minorAxis);
-  matrix.mapVector(circleRadius, 0, &majorAxis);
-  SkString mappedArea;
-  mappedArea.printf("area = %g", mappedRadius * mappedRadius);
-  canvas->drawString(mappedArea, 145, 250, paint);
-  canvas->drawString("mappedRadius", center.fX + mappedRadius + 3, center.fY, paint);
-  paint.setColor(SK_ColorRED);
-  SkString axArea;
-  axArea.printf("area = %g", majorAxis.length() * minorAxis.length());
-  paint.setStyle(SkPaint::kFill_Style);
-  canvas->drawString(axArea, 15, 250, paint);
-  paint.setStyle(SkPaint::kStroke_Style);
-  canvas->drawRect({10, 200, 10 + majorAxis.length(), 200 + minorAxis.length()}, paint);
-  paint.setColor(SK_ColorBLACK);
-  canvas->drawLine(center.fX, center.fY, center.fX + mappedRadius, center.fY, paint);
-  canvas->drawLine(center.fX, center.fY, center.fX, center.fY + mappedRadius, paint);
-  canvas->drawRect({140, 180, 140 + mappedRadius, 180 + mappedRadius}, paint);
-  canvas->concat(matrix);
-  canvas->drawCircle(center.fX, center.fY, circleRadius, paint);
-  paint.setColor(SK_ColorRED);
-  canvas->drawLine(center.fX, center.fY, center.fX + circleRadius, center.fY, paint);
-  canvas->drawLine(center.fX, center.fY, center.fX, center.fY + circleRadius, paint);
-##
-
-#SeeAlso mapVector
-
-##
-
-# ------------------------------------------------------------------------------
-#Method bool isFixedStepInX() const
-#In Property
-#Line # returns if transformation supports fixed step on x-axis ##
-#Populate
-
-#Example
-    SkMatrix matrix;
-    for (SkScalar px : { 0.0f, 0.1f } ) {
-        for (SkScalar py : { 0.0f, 0.1f } ) {
-            for (SkScalar sy : { 1, 2 } ) {
-                matrix.setAll(1, 0, 0,   0, sy, 0,   px, py, 1);
-                matrix.dump();
-                SkDebugf("isFixedStepInX: %s\n", matrix.isFixedStepInX() ? "true" : "false");
-            }
-        }
-    }
-#StdOut
-[  1.0000   0.0000   0.0000][  0.0000   1.0000   0.0000][  0.0000   0.0000   1.0000]
-isFixedStepInX: true
-[  1.0000   0.0000   0.0000][  0.0000   2.0000   0.0000][  0.0000   0.0000   1.0000]
-isFixedStepInX: true
-[  1.0000   0.0000   0.0000][  0.0000   1.0000   0.0000][  0.0000   0.1000   1.0000]
-isFixedStepInX: true
-[  1.0000   0.0000   0.0000][  0.0000   2.0000   0.0000][  0.0000   0.1000   1.0000]
-isFixedStepInX: true
-[  1.0000   0.0000   0.0000][  0.0000   1.0000   0.0000][  0.1000   0.0000   1.0000]
-isFixedStepInX: false
-[  1.0000   0.0000   0.0000][  0.0000   2.0000   0.0000][  0.1000   0.0000   1.0000]
-isFixedStepInX: false
-[  1.0000   0.0000   0.0000][  0.0000   1.0000   0.0000][  0.1000   0.1000   1.0000]
-isFixedStepInX: false
-[  1.0000   0.0000   0.0000][  0.0000   2.0000   0.0000][  0.1000   0.1000   1.0000]
-isFixedStepInX: false
-##
-##
-
-#SeeAlso fixedStepInX getType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkVector fixedStepInX(SkScalar y) const
-#In Property
-#Line # returns step on x-axis for a position on y-axis ##
-#Populate
-
-#Example
-#Image 3
-    SkMatrix matrix;
-    const SkPoint center = { 128, 128 };
-    matrix.setScale(20, 25, center.fX, center.fY);
-    matrix.postRotate(75, center.fX, center.fY);
-    {
-       SkAutoCanvasRestore acr(canvas, true);
-       canvas->concat(matrix);
-       canvas->drawBitmap(source, 0, 0);
-    }
-    if (matrix.isFixedStepInX()) {
-       SkPaint paint;
-       paint.setAntiAlias(true);
-       SkVector step = matrix.fixedStepInX(128);
-       SkVector end = center + step;
-       canvas->drawLine(center, end, paint);
-       SkVector arrow = { step.fX + step.fY, step.fY - step.fX};
-       arrow = arrow * .25f;
-       canvas->drawLine(end, end - arrow, paint);
-       canvas->drawLine(end, {end.fX + arrow.fY, end.fY - arrow.fX}, paint);
-    }
-##
-
-#SeeAlso isFixedStepInX getType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool cheapEqualTo(const SkMatrix& m) const
-#In Operators
-#Line # compares Matrix pair using memcmp() ##
-#Populate
-
-#Example
-    auto debugster = [](const char* prefix, const SkMatrix& a, const SkMatrix& b) -> void {
-        SkDebugf("%s: a %c= b a.cheapEqualTo(b): %s\n", prefix,
-                 a == b ? '=' : '!', a.cheapEqualTo(b) ? "true" : "false");
-    };
-    SkMatrix a, b;
-    a.setAll(1, 0, 0,   0, 1, 0,  0, 0, 1);
-    b.setIdentity();
-    debugster("identity", a, b);
-    a.setAll(1, -0.0f, 0,   0, 1, 0,  0, 0, 1);
-    debugster("neg zero", a, b);
-    a.setAll(1, SK_ScalarNaN, 0,   0, 1, 0,  0, 0, 1);
-    debugster(" one NaN", a, b);
-    b.setAll(1, SK_ScalarNaN, 0,   0, 1, 0,  0, 0, 1);
-    debugster("both NaN", a, b);
-#StdOut
-identity: a == b a.cheapEqualTo(b): true
-neg zero: a == b a.cheapEqualTo(b): false
- one NaN: a != b a.cheapEqualTo(b): false
-both NaN: a != b a.cheapEqualTo(b): true
-##
-##
-
-#SeeAlso operator==(const SkMatrix& a, const SkMatrix& b)
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator==(const SkMatrix& a, const SkMatrix& b)
-
-#Line # returns true if members are equal ##
-#Populate
-
-#Example
-    auto debugster = [](const char* prefix, const SkMatrix& a, const SkMatrix& b) -> void {
-        SkDebugf("%s: a %c= b a.cheapEqualTo(b): %s\n", prefix,
-                 a == b ? '=' : '!', a.cheapEqualTo(b) ? "true" : "false");
-    };
-    SkMatrix a, b;
-    a.setAll(1, 0, 0,   0, 1, 0,  0, 0, 1);
-    b.setScale(2, 4);
-    b.postScale(0.5f, 0.25f);
-    debugster("identity", a, b);
-#StdOut
-identity: a == b a.cheapEqualTo(b): true
-##
-##
-
-#SeeAlso  cheapEqualTo operator!=(const SkMatrix& a, const SkMatrix& b)
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator!=(const SkMatrix& a, const SkMatrix& b)
-
-#Line # returns true if members are unequal ##
-#Populate
-
-#Example
-    auto debugster = [](const char* prefix, const SkMatrix& a, const SkMatrix& b) -> void {
-        SkDebugf("%s: a %c= b a.cheapEqualTo(b): %s\n", prefix,
-                 a != b ? '!' : '=', a.cheapEqualTo(b) ? "true" : "false");
-    };
-    SkMatrix a, b;
-    a.setAll(1, 0, 0,   0, 1, 0,  1, 0, 1);
-    if (a.invert(&b)) {
-        debugster("identity", a, b);
-    }
-##
-
-#SeeAlso cheapEqualTo operator==(const SkMatrix& a, const SkMatrix& b)
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Utility
-#Line # rarely called management functions ##
-##
-
-#Method void dump() const
-#In Utility
-#Line # sends text representation using floats to standard output ##
-#Populate
-
-#Example
-    SkMatrix matrix;
-    matrix.setRotate(45);
-    matrix.dump();
-    SkMatrix nearlyEqual;
-    nearlyEqual.setAll(0.7071f, -0.7071f, 0,   0.7071f, 0.7071f, 0,   0, 0, 1);
-    nearlyEqual.dump();
-    SkDebugf("matrix %c= nearlyEqual\n", matrix == nearlyEqual ? '=' : '!');
-#StdOut
-[  0.7071  -0.7071   0.0000][  0.7071   0.7071   0.0000][  0.0000   0.0000   1.0000]
-[  0.7071  -0.7071   0.0000][  0.7071   0.7071   0.0000][  0.0000   0.0000   1.0000]
-matrix != nearlyEqual
-##
-##
-
-#SeeAlso SkPath::dump
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar getMinScale() const
-#In Property
-#Line # returns minimum scaling, if possible ##
-#Populate
-
-#Example
-    SkMatrix matrix;
-    matrix.setScale(42, 24);
-    SkDebugf("matrix.getMinScale() %g\n", matrix.getMinScale());
-#StdOut
-matrix.getMinScale() 24
-##
-##
-
-#SeeAlso getMaxScale getMinMaxScales
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar getMaxScale() const
-#In Property
-#Line # returns maximum scaling, if possible ##
-#Populate
-
-#Example
-    SkMatrix matrix;
-    matrix.setScale(42, 24);
-    SkDebugf("matrix.getMaxScale() %g\n", matrix.getMaxScale());
-#StdOut
-matrix.getMaxScale() 42
-##
-##
-
-#SeeAlso getMinScale getMinMaxScales
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool getMinMaxScales(SkScalar scaleFactors[2]) const
-#In Property
-#Line # returns minimum and maximum scaling, if possible ##
-#Populate
-
-#Example
-    SkMatrix matrix;
-    matrix.setAll(1, 0, 0,  0, 1, 0,   0, 0, 0);
-    if (matrix.invert(&matrix)) {
-        SkScalar factor[2] = {2, 2};
-        bool result = matrix.getMinMaxScales(factor);
-        SkDebugf("matrix.getMinMaxScales() %s %g %g\n",
-                result ? "true" : "false", factor[0], factor[1]);
-    }
-#StdOut
-matrix.getMinMaxScales() false 2 2
-##
-##
-
-#SeeAlso getMinScale getMaxScale
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool decomposeScale(SkSize* scale, SkMatrix* remaining = nullptr) const
-#In Property
-#Line # separates scale if possible ##
-Decomposes Matrix into scale components and whatever remains. Returns false if
-Matrix could not be decomposed.
-
-Sets scale to portion of Matrix that scale axes. Sets remaining to Matrix
-with scaling factored out. remaining may be passed as nullptr
-to determine if Matrix can be decomposed without computing remainder.
-
-Returns true if scale components are found. scale and remaining are
-unchanged if Matrix contains perspective; scale factors are not finite, or
-are nearly zero.
-
-On success: #Formula # Matrix = scale * Remaining ##.
-
-#Param scale  axes scaling factors; may be nullptr ##
-#Param remaining  Matrix without scaling; may be nullptr ##
-
-#Return  true if scale can be computed ##
-
-#Example
-    SkMatrix matrix;
-    matrix.setRotate(90 * SK_Scalar1);
-    matrix.postScale(1.f / 4, 1.f / 2);
-    matrix.dump();
-    SkSize scale = {SK_ScalarNaN, SK_ScalarNaN};
-    SkMatrix remaining;
-    remaining.reset();
-    bool success = matrix.decomposeScale(&scale, &remaining);
-    SkDebugf("success: %s  ", success ? "true" : "false");
-    SkDebugf("scale: %g, %g\n", scale.width(), scale.height());
-    remaining.dump();
-    SkMatrix scaleMatrix = SkMatrix::MakeScale(scale.width(), scale.height());
-    SkMatrix combined = SkMatrix::Concat(scaleMatrix, remaining);
-    combined.dump();
-#StdOut
-[  0.0000  -0.2500   0.0000][  0.5000   0.0000   0.0000][  0.0000   0.0000   1.0000]
-success: true  scale: 0.5, 0.25
-[  0.0000  -0.5000   0.0000][  2.0000   0.0000   0.0000][  0.0000   0.0000   1.0000]
-[  0.0000  -0.2500   0.0000][  0.5000   0.0000   0.0000][  0.0000   0.0000   1.0000]
-##
-##
-
-#SeeAlso setScale MakeScale
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static const SkMatrix& I()
-#In Constructors
-#Line # returns a reference to a const identity Matrix ##
-#Populate
-
-#Example
-    SkMatrix m1, m2, m3;
-    m1.reset();
-    m2.setIdentity();
-    m3 = SkMatrix::I();
-    SkDebugf("m1 %c= m2\n", m1 == m2 ? '=' : '!');
-    SkDebugf("m2 %c= m3\n", m1 == m2 ? '=' : '!');
-#StdOut
-m1 == m2
-m2 == m3
-##
-##
-
-#SeeAlso reset() setIdentity
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static const SkMatrix& InvalidMatrix()
-#In Constructors
-#Line # returns a reference to a const invalid Matrix ##
-#Populate
-
-#Example
-    SkDebugf("scaleX %g\n", SkMatrix::InvalidMatrix().getScaleX());
-#StdOut
-scaleX 3.40282e+38
-##
-##
-
-#SeeAlso getType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkMatrix Concat(const SkMatrix& a, const SkMatrix& b)
-#In Operators
-#Line # returns the concatenation of Matrix pair ##
-#Populate
-
-#Example
-#Height 64
-#Image 4
-#Description
-setPolyToPoly creates perspective matrices, one the inverse of the other.
-Multiplying the matrix by its inverse turns into an identity matrix.
-##
-SkMatrix matrix, matrix2;
-SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
-SkRect::Make(source.bounds()).toQuad(bitmapBounds);
-matrix.setPolyToPoly(bitmapBounds, perspect, 4);
-matrix2.setPolyToPoly(perspect, bitmapBounds, 4);
-SkMatrix concat = SkMatrix::Concat(matrix, matrix2);
-canvas->concat(concat);
-canvas->drawBitmap(source, 0, 0);
-##
-
-#SeeAlso preConcat postConcat
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void dirtyMatrixTypeCache()
-#In Utility
-#Line # sets internal cache to unknown state ##
-#Populate
-
-#Example
-SkMatrix matrix;
-matrix.setIdentity();
-SkDebugf("with identity matrix: x = %g\n", matrix.mapXY(24, 42).fX);
-SkScalar& skewRef = matrix[SkMatrix::kMSkewX];
-skewRef = 0;
-SkDebugf("after skew x mod:     x = %g\n", matrix.mapXY(24, 42).fX);
-skewRef = 1;
-SkDebugf("after 2nd skew x mod: x = %g\n", matrix.mapXY(24, 42).fX);
-matrix.dirtyMatrixTypeCache();
-SkDebugf("after dirty cache:    x = %g\n", matrix.mapXY(24, 42).fX);
-#StdOut
-with identity matrix: x = 24
-after skew x mod:     x = 24
-after 2nd skew x mod: x = 24
-after dirty cache:    x = 66
-##
-##
-
-#SeeAlso operator[](int index) getType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void setScaleTranslate(SkScalar sx, SkScalar sy, SkScalar tx, SkScalar ty)
-#In Constructors
-#In Set
-#Line # sets to scale and translate ##
-#Populate
-
-#Example
-SkMatrix matrix;
-matrix.setScaleTranslate(1, 2, 3, 4);
-matrix.dump();
-#StdOut
-[  1.0000   0.0000   3.0000][  0.0000   2.0000   4.0000][  0.0000   0.0000   1.0000]
-##
-##
-
-#SeeAlso setScale preTranslate postTranslate
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isFinite() const
-#In Property
-#Line # returns if all Matrix values are not infinity, NaN ##
-#Populate
-
-#Example
-SkMatrix matrix = SkMatrix::MakeTrans(SK_ScalarNaN, 0);
-matrix.dump();
-SkDebugf("matrix is finite: %s\n", matrix.isFinite() ? "true" : "false");
-SkDebugf("matrix %c= matrix\n", matrix == matrix ? '=' : '!');
-#StdOut
-[  1.0000   0.0000      nan][  0.0000   1.0000   0.0000][  0.0000   0.0000   1.0000]
-matrix is finite: false
-matrix != matrix
-##
-##
-
-#SeeAlso operator==
-
-##
-
-#Class SkMatrix ##
-
-#Topic Matrix ##
diff --git a/docs/SkMemoryStream_Reference.bmh b/docs/SkMemoryStream_Reference.bmh
deleted file mode 100644
index b7e86b0..0000000
--- a/docs/SkMemoryStream_Reference.bmh
+++ /dev/null
@@ -1,424 +0,0 @@
-#Topic MemoryStream
-#Alias MemoryStream_Reference ##
-
-#Class SkMemoryStream
-
-#Code
-#Populate
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkMemoryStream()
-#In Constructors
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkMemoryStream(size_t length)
-#In Constructors
-#Line # incomplete ##
-
-Allocates memory for stream data. Call getMemoryBase() to access allocated
-memory.
-
-#Param length  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkMemoryStream(const void* data, size_t length, bool copyData = false)
-#In Constructors
-#Line # incomplete ##
-
-If copyData is true, the stream makes a private copy of the data.
-
-#Param data  incomplete ##
-#Param length  incomplete ##
-#Param copyData  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkMemoryStream(sk_sp<SkData> data)
-#In Constructors
-#Line # incomplete ##
-
-Creates the stream to read from the specified data
-
-#Param data  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static std::unique_ptr<SkMemoryStream> MakeCopy(const void* data, size_t length)
-#In incomplete
-#Line # incomplete ##
-
-Returns a stream with a copy of the input data.
-
-#Param data  incomplete ##
-#Param length  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static std::unique_ptr<SkMemoryStream> MakeDirect(const void* data, size_t length)
-#In incomplete
-#Line # incomplete ##
-
-Returns a stream with a bare pointer reference to the input data.
-
-#Param data  incomplete ##
-#Param length  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static std::unique_ptr<SkMemoryStream> Make(sk_sp<SkData> data)
-#In incomplete
-#Line # incomplete ##
-
-Returns a stream with a shared reference to the input data.
-
-#Param data  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual void setMemory(const void* data, size_t length,
-                           bool copyData = false)
-#In incomplete
-#Line # incomplete ##
-
-Resets the stream to the specified data and length,
-just like the constructor.
-if copyData is true, the stream makes a private copy of the data
-
-#Param data  incomplete ##
-#Param length  incomplete ##
-#Param copyData  incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkData> asData() const
-#In incomplete
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setData(sk_sp<SkData> data)
-#In incomplete
-#Line # incomplete ##
-
-#Param data  incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void skipToAlign4()
-#In incomplete
-#Line # incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method const void* getAtPos()
-#In incomplete
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t read(void* buffer, size_t size) override
-#In incomplete
-#Line # incomplete ##
-
-#Param buffer  incomplete ##
-#Param size  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isAtEnd() const override
-#In incomplete
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t peek(void* buffer, size_t size) const override
-#In incomplete
-#Line # incomplete ##
-
-#Param buffer  incomplete ##
-#Param size  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool rewind() override
-#In incomplete
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method std::unique_ptr<SkMemoryStream> duplicate() const
-#In incomplete
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t getPosition() const override
-#In incomplete
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool seek(size_t position) override
-#In incomplete
-#Line # incomplete ##
-
-#Param position  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool move(long offset) override
-#In incomplete
-#Line # incomplete ##
-
-#Param offset  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method std::unique_ptr<SkMemoryStream> fork() const
-#In incomplete
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t getLength() const override
-#In incomplete
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method const void* getMemoryBase() override
-#In incomplete
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-#Class SkMemoryStream ##
-
-#Topic MemoryStream ##
diff --git a/docs/SkPaint_Reference.bmh b/docs/SkPaint_Reference.bmh
deleted file mode 100644
index 93f3faf..0000000
--- a/docs/SkPaint_Reference.bmh
+++ /dev/null
@@ -1,2503 +0,0 @@
-#Topic Paint
-#Alias Paint_Reference ##
-
-#Class SkPaint
-
-#Code
-#Populate
-##
-
-Paint controls options applied when drawing and measuring. Paint collects all
-options outside of the Canvas_Clip and Canvas_Matrix.
-
-Various options apply to text, strokes and fills, and images.
-
-Some options may not be implemented on all platforms; in these cases, setting
-the option has no effect. Some options are conveniences that duplicate Canvas
-functionality; for instance, text size is identical to matrix scale.
-
-Paint options are rarely exclusive; each option modifies a stage of the drawing
-pipeline and multiple pipeline stages may be affected by a single Paint.
-
-Paint collects effects and filters that describe single-pass and multiple-pass
-algorithms that alter the drawing geometry, color, and transparency. For instance,
-Paint does not directly implement dashing or blur, but contains the objects that do so.
-
-The objects contained by Paint are opaque, and cannot be edited outside of the Paint
-to affect it. The implementation is free to defer computations associated with the
-Paint, or ignore them altogether. For instance, some GPU implementations draw all
-Path geometries with Anti_Aliasing, regardless of how SkPaint::kAntiAlias_Flag
-is set in Paint.
-
-Paint describes a single color, a single font, a single image quality, and so on.
-Multiple colors are drawn either by using multiple paints or with objects like
-Shader attached to Paint.
-
-#Method SkPaint()
-#Line # constructs with default values ##
-Constructs Paint with default values.
-
-#Table
-#Legend
-# attribute              # default value            ##
-#Legend ##
-# Anti_Alias             # false                    ##
-# Blend_Mode             # SkBlendMode::kSrcOver    ##
-# Color                  # SK_ColorBLACK            ##
-# Color_Alpha            # 255                      ##
-# Color_Filter           # nullptr                  ##
-# Dither                 # false                    ##
-# Draw_Looper            # nullptr                  ##
-# Filter_Quality         # kNone_SkFilterQuality    ##
-# Image_Filter           # nullptr                  ##
-# Miter_Limit            # 4                        ##
-# Mask_Filter            # nullptr                  ##
-# Path_Effect            # nullptr                  ##
-# Shader                 # nullptr                  ##
-# Style                  # kFill_Style              ##
-# Stroke_Cap             # kButt_Cap                ##
-# Stroke_Join            # kMiter_Join              ##
-# Stroke_Width           # 0                        ##
-#Table ##
-
-The miter limit may be overridden at compile time by defining
-paint default values. The overrides may be included in "SkUserConfig.h" or predefined by the
-build system.
-
-#Return  default initialized Paint ##
-
-#Example
-#ToDo mark this as no output ##
-#Height 1
-###$  $ redefine markup character so preprocessor commands appear normally
-#ifndef SkUserConfig_DEFINED
-#define SkUserConfig_DEFINED
-
-#define SkPaintDefaults_Flags      0x01   // always enable antialiasing
-#define SkPaintDefaults_TextSize   24.f   // double default font size
-#define SkPaintDefaults_Hinting    3      // use full hinting
-#define SkPaintDefaults_MiterLimit 10.f   // use HTML Canvas miter limit setting
-
-#endif
-$$$#  # restore original markup character
-##
-
-
-##
-
-#Method SkPaint(const SkPaint& paint)
-#Line # makes a shallow copy ##
-#Populate
-
-#Example
-#ToDo why is this double-spaced on Fiddle? ##
-    SkPaint paint1;
-    paint1.setColor(SK_ColorRED);
-    SkPaint paint2(paint1);
-    paint2.setColor(SK_ColorBLUE);
-    SkDebugf("SK_ColorRED %c= paint1.getColor()\n", SK_ColorRED == paint1.getColor() ? '=' : '!');
-    SkDebugf("SK_ColorBLUE %c= paint2.getColor()\n", SK_ColorBLUE == paint2.getColor() ? '=' : '!');
-
-    #StdOut
-        SK_ColorRED == paint1.getColor()
-        SK_ColorBLUE == paint2.getColor()
-    ##
-##
-
-##
-
-#Method SkPaint(SkPaint&& paint)
-#Line # moves paint without copying it ##
-#Populate
-
-#Example
-        SkPaint paint;
-        float intervals[] = { 5, 5 };
-        paint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 2.5f));
-        SkPaint dashed(std::move(paint));
-        SkDebugf("path effect unique: %s\n", dashed.getPathEffect()->unique() ? "true" : "false");
-
-        #StdOut
-            path effect unique: true
-        ##
-    ##
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void reset()
-#In Constructors
-#Line # sets to default values ##
-#Populate
-
-#Example
-    SkPaint paint1, paint2;
-    paint1.setColor(SK_ColorRED);
-    paint1.reset();
-    SkDebugf("paint1 %c= paint2", paint1 == paint2 ? '=' : '!');
-
-    #StdOut
-        paint1 == paint2
-    ##
-##
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method ~SkPaint()
-#Line # decreases Reference_Count of owned objects ##
-#Populate
-
-#NoExample
-##
-
-##
-
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Management
-#Line # paint copying, moving, comparing ##
-##
-
-#Method SkPaint& operator=(const SkPaint& paint)
-#In Management
-#Line # makes a shallow copy ##
-#Populate
-
-#Example
-    SkPaint paint1, paint2;
-    paint1.setColor(SK_ColorRED);
-    paint2 = paint1;
-    SkDebugf("SK_ColorRED %c= paint1.getColor()\n", SK_ColorRED == paint1.getColor() ? '=' : '!');
-    SkDebugf("SK_ColorRED %c= paint2.getColor()\n", SK_ColorRED == paint2.getColor() ? '=' : '!');
-
-    #StdOut
-        SK_ColorRED == paint1.getColor()
-        SK_ColorRED == paint2.getColor()
-    ##
-##
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPaint& operator=(SkPaint&& paint)
-#In Management
-#Line # moves paint without copying it ##
-#Populate
-
-#Example
-    SkPaint paint1, paint2;
-    paint1.setColor(SK_ColorRED);
-    paint2 = std::move(paint1);
-    SkDebugf("SK_ColorRED == paint2.getColor()\n", SK_ColorRED == paint2.getColor() ? '=' : '!');
-
-    #StdOut
-        SK_ColorRED == paint2.getColor()
-    ##
-##
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator==(const SkPaint& a, const SkPaint& b)
-#Line # compares paints for equality ##
-#Populate
-
-#Example
-        SkPaint paint1, paint2;
-        paint1.setColor(SK_ColorRED);
-        paint2.setColor(0xFFFF0000);
-        SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
-        float intervals[] = { 5, 5 };
-        paint1.setPathEffect(SkDashPathEffect::Make(intervals, 2, 2.5f));
-        paint2.setPathEffect(SkDashPathEffect::Make(intervals, 2, 2.5f));
-        SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
-
-        #StdOut
-            paint1 == paint2
-            paint1 != paint2
-        ##
-    ##
-#SeeAlso operator!=(const SkPaint& a, const SkPaint& b)
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator!=(const SkPaint& a, const SkPaint& b)
-#Line # compares paints for inequality ##
-#Populate
-
-#Example
-    SkPaint paint1, paint2;
-    paint1.setColor(SK_ColorRED);
-    paint2.setColor(0xFFFF0000);
-    SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
-    SkDebugf("paint1 %c= paint2\n", paint1 != paint2 ? '!' : '=');
-
-    #StdOut
-        paint1 == paint2
-        paint1 == paint2
-    ##
-##
-#SeeAlso operator==(const SkPaint& a, const SkPaint& b)
-##
-
-# ------------------------------------------------------------------------------
-
-#Method uint32_t getHash() const
-#In Management
-#Line # returns a shallow hash for equality checks ##
-#Populate
-
-#Example
-    SkPaint paint1, paint2;
-    paint1.setColor(SK_ColorRED);
-    paint2.setColor(0xFFFF0000);
-    SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
-    SkDebugf("paint1.getHash() %c= paint2.getHash()\n",
-             paint1.getHash() == paint2.getHash() ? '=' : '!');
-
-    #StdOut
-        paint1 == paint2
-        paint1.getHash() == paint2.getHash()
-    ##
-##
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Anti_Alias
-#Alias Anti_Alias
-#Substitute anti-alias
-##
-#Alias Anti_Aliased
-#Substitute anti-aliased
-##
-#Alias Anti_Aliasing
-#Substitute anti-aliasing
-##
-#In Related_Function
-#Line # approximating coverage with transparency ##
-
-Anti_Alias drawing approximates partial pixel coverage with transparency.
-If kAntiAlias_Flag is clear, pixel centers contained by the shape edge are drawn opaque.
-If kAntiAlias_Flag is set, pixels are drawn with Color_Alpha equal to their coverage.
-
-The rule for Aliased pixels is inconsistent across platforms. A shape edge
-passing through the pixel center may, but is not required to, draw the pixel.
-
-Raster_Engine draws Aliased pixels whose centers are on or to the right of the start of an
-active Path edge, and whose center is to the left of the end of the active Path edge.
-
-#ToDo  add illustration of raster pixels ##
-
-A platform may only support Anti_Aliased drawing. Some GPU-backed platforms use
-Supersampling to Anti_Alias all drawing, and have no mechanism to selectively
-Alias.
-
-The amount of coverage computed for Anti_Aliased pixels also varies across platforms.
-
-Anti_Alias is disabled by default.
-Anti_Alias can be enabled by default by setting SkPaintDefaults_Flags to kAntiAlias_Flag
-at compile time.
-
-#Example
-    #Width 512
-    #Description
-        A red line is drawn with transparency on the edges to make it look smoother.
-        A blue line draws only where the pixel centers are contained.
-        The lines are drawn into Bitmap, then drawn magnified to make the
-        Aliasing easier to see.
-    ##
-
-    void draw(SkCanvas* canvas) {
-        SkBitmap bitmap;
-        bitmap.allocN32Pixels(50, 50);
-        SkCanvas offscreen(bitmap);
-        SkPaint paint;
-        paint.setStyle(SkPaint::kStroke_Style);
-        paint.setStrokeWidth(10);
-        for (bool antialias : { false, true }) {
-            paint.setColor(antialias ? SK_ColorRED : SK_ColorBLUE);
-            paint.setAntiAlias(antialias);
-            bitmap.eraseColor(0);
-            offscreen.drawLine(5, 5, 15, 30, paint);
-            canvas->drawLine(5, 5, 15, 30, paint);
-            canvas->save();
-            canvas->scale(10, 10);
-            canvas->drawBitmap(bitmap, antialias ? 12 : 0, 0);
-            canvas->restore();
-            canvas->translate(15, 0);
-        }
-    }
-##
-#Subtopic Anti_Alias ##
-
-#Method bool isAntiAlias() const
-#In Anti_alias
-#Line # returns true if Anti_Alias is set ##
-#Populate
-
-#Example
-        SkPaint paint;
-        SkDebugf("paint.isAntiAlias() %c= !!(paint.getFlags() & SkPaint::kAntiAlias_Flag)\n",
-                paint.isAntiAlias() == !!(paint.getFlags() & SkPaint::kAntiAlias_Flag) ? '=' : '!');
-        paint.setAntiAlias(true);
-        SkDebugf("paint.isAntiAlias() %c= !!(paint.getFlags() & SkPaint::kAntiAlias_Flag)\n",
-                paint.isAntiAlias() == !!(paint.getFlags() & SkPaint::kAntiAlias_Flag) ? '=' : '!');
-
-    #StdOut
-        paint.isAntiAlias() == !!(paint.getFlags() & SkPaint::kAntiAlias_Flag)
-        paint.isAntiAlias() == !!(paint.getFlags() & SkPaint::kAntiAlias_Flag)
-    ##
-    ##
-##
-
-#Method void setAntiAlias(bool aa)
-#In Anti_alias
-#Line # sets or clears Anti_Alias ##
-#Populate
-
-#Example
-        SkPaint paint1, paint2;
-        paint1.setAntiAlias(true);
-        paint2.setFlags(paint2.getFlags() | SkPaint::kAntiAlias_Flag);
-        SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
-
-        #StdOut
-            paint1 == paint2
-        ##
-    ##
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Dither
-#Line # distributing color error ##
-
-Dither increases fidelity by adjusting the color of adjacent pixels.
-This can help to smooth color transitions and reducing banding in gradients.
-Dithering lessens visible banding from kRGB_565_SkColorType
-and kRGBA_8888_SkColorType gradients,
-and improves rendering into a kRGB_565_SkColorType Surface.
-
-Dithering is always enabled for linear gradients drawing into
-kRGB_565_SkColorType Surface and kRGBA_8888_SkColorType Surface.
-Dither cannot be enabled for kAlpha_8_SkColorType Surface and
-kRGBA_F16_SkColorType Surface.
-
-Dither is disabled by default.
-Dither can be enabled by default by setting SkPaintDefaults_Flags to kDither_Flag
-at compile time.
-
-Some platform implementations may ignore dithering. Set #Formula # SK_IGNORE_GPU_DITHER ##
-to ignore Dither on GPU_Surface.
-
-#Example
-#Description
-Dithering in the bottom half more closely approximates the requested color by
-alternating nearby colors from pixel to pixel.
-##
-void draw(SkCanvas* canvas) {
-    SkBitmap bm16;
-    bm16.allocPixels(SkImageInfo::Make(32, 32, kRGB_565_SkColorType, kOpaque_SkAlphaType));
-    SkCanvas c16(bm16);
-    SkPaint colorPaint;
-    for (auto dither : { false, true } ) {
-        colorPaint.setDither(dither);
-        for (auto colors : { 0xFF333333, 0xFF666666, 0xFF999999, 0xFFCCCCCC } ) {
-            for (auto mask : { 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFFFFFFFF } ) {
-                 colorPaint.setColor(colors & mask);
-                 c16.drawRect({0, 0, 8, 4}, colorPaint);
-                 c16.translate(8, 0);
-            }
-            c16.translate(-32, 4);
-        }
-    }
-    canvas->scale(8, 8);
-    canvas->drawBitmap(bm16, 0, 0);
-}
-##
-
-#Example
-#Description
-Dithering introduces subtle adjustments to color to smooth gradients.
-Drawing the gradient repeatedly with SkBlendMode::kPlus exaggerates the
-dither, making it easier to see.
-##
-void draw(SkCanvas* canvas) {
-    canvas->clear(0);
-    SkBitmap bm32;
-    bm32.allocPixels(SkImageInfo::Make(20, 10, kN32_SkColorType, kPremul_SkAlphaType));
-    SkCanvas c32(bm32);
-    SkPoint points[] = {{0, 0}, {20, 0}};
-    SkColor colors[] = {0xFF334455, 0xFF662211 };
-    SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(
-                     points, colors, nullptr, SK_ARRAY_COUNT(colors),
-                     SkShader::kClamp_TileMode, 0, nullptr));
-    paint.setDither(true);
-    c32.drawPaint(paint);
-    canvas->scale(12, 12);
-    canvas->drawBitmap(bm32, 0, 0);
-    paint.setBlendMode(SkBlendMode::kPlus);
-    canvas->drawBitmap(bm32, 0, 11, &paint);
-    canvas->drawBitmap(bm32, 0, 11, &paint);
-    canvas->drawBitmap(bm32, 0, 11, &paint);
-}
-##
-#SeeAlso Gradient kRGB_565_SkColorType
-#Subtopic Dither ##
-
-#Method bool isDither() const
-#In Dither
-#Line # returns true if Dither is set ##
-#Populate
-
-#Example
-        SkPaint paint;
-        SkDebugf("paint.isDither() %c= !!(paint.getFlags() & SkPaint::kDither_Flag)\n",
-                paint.isDither() == !!(paint.getFlags() & SkPaint::kDither_Flag) ? '=' : '!');
-        paint.setDither(true);
-        SkDebugf("paint.isDither() %c= !!(paint.getFlags() & SkPaint::kDither_Flag)\n",
-                paint.isDither() == !!(paint.getFlags() & SkPaint::kDither_Flag) ? '=' : '!');
-
-    #StdOut
-        paint.isDither() == !!(paint.getFlags() & SkPaint::kDither_Flag)
-        paint.isDither() == !!(paint.getFlags() & SkPaint::kDither_Flag)
-    ##
-    ##
-
-##
-
-#Method void setDither(bool dither)
-#In Dither
-#Line # sets or clears Dither ##
-#Populate
-
-#Example
-        SkPaint paint1, paint2;
-        paint1.setDither(true);
-        paint2.setFlags(paint2.getFlags() | SkPaint::kDither_Flag);
-        SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
-
-        #StdOut
-            paint1 == paint2
-        ##
-    ##
-
-    #SeeAlso kRGB_565_SkColorType
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Device_Text
-#Line # increase precision of glyph position ##
-
-Font_Anti_Alias and Font_Subpixel increase the precision of glyph position.
-
-When set, Flags kLCDRenderText_Flag takes advantage of the organization of RGB stripes that
-create a color, and relies
-on the small size of the stripe and visual perception to make the color fringing imperceptible.
-Font_Anti_Alias can be enabled on devices that orient stripes horizontally or vertically, and that order
-the color components as RGB or BGR.
-
-Flags kSubpixelText_Flag uses the pixel transparency to represent a fractional offset.
-As the opaqueness
-of the color increases, the edge of the glyph appears to move towards the outside of the pixel.
-
-Either or both techniques can be enabled.
-kLCDRenderText_Flag and kSubpixelText_Flag are clear by default.
-Font_Anti_Alias or Font_Subpixel can be enabled by default by setting SkPaintDefaults_Flags to
-kLCDRenderText_Flag or kSubpixelText_Flag (or both) at compile time.
-
-#Example
-    #Description
-        Four commas are drawn normally and with combinations of Font_Anti_Alias and Font_Subpixel.
-        When Font_Subpixel is disabled, the comma Glyphs are identical, but not evenly spaced.
-        When Font_Subpixel is enabled, the comma Glyphs are unique, but appear evenly spaced.
-    ##
-
-    SkBitmap bitmap;
-    bitmap.allocN32Pixels(24, 33);
-    SkCanvas offscreen(bitmap);
-    offscreen.clear(SK_ColorWHITE);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setTextSize(20);
-    for (bool lcd : { false, true }) {
-        paint.setLCDRenderText(lcd);
-        for (bool subpixel : { false, true }) {
-            paint.setSubpixelText(subpixel);
-            offscreen.drawString(",,,,", 0, 4, paint);
-            offscreen.translate(0, 7);
-        }
-    }
-    canvas->drawBitmap(bitmap, 4, 12);
-    canvas->scale(9, 9);
-    canvas->drawBitmap(bitmap, 4, -1);
-##
-#Subtopic Device_Text ##
-
-#Subtopic Linear_Text
-#Line # selects text rendering as Glyph or Path ##
-
-Font_Linear selects whether text is rendered as a Glyph or as a Path.
-If Font_Linear is set, it has the same effect as setting Hinting to SkFontHinting::kNormal.
-If Font_Linear is clear, it is the same as setting Hinting to SkFontHinting::kNone.
-#Subtopic Linear_Text ##
-
-#Subtopic Subpixel_Text
-#Line # uses pixel transparency to represent fractional offset ##
-
-Flags kSubpixelText_Flag uses the pixel transparency to represent a fractional offset.
-As the opaqueness
-of the color increases, the edge of the glyph appears to move towards the outside of the pixel.
-#Subtopic Subpixel_Text ##
-
-#Subtopic LCD_Text
-#Line # text relying on the order of RGB stripes ##
-
-When set, Font_Anti_Alias takes advantage of the organization of RGB stripes that
-create a color, and relies
-on the small size of the stripe and visual perception to make the color fringing imperceptible.
-Font_Anti_Alias can be enabled on devices that orient stripes horizontally or vertically, and that order
-the color components as RGB or BGR.
-#Subtopic LCD_Text ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Embedded_Bitmaps
-#Line # custom sized bitmap Glyphs ##
-
-Font_Embedded_Bitmaps allows selecting custom sized bitmap Glyphs.
-Flags kEmbeddedBitmapText_Flag when set chooses an embedded bitmap glyph over an outline contained
-in a font if the platform supports this option.
-
-FreeType selects the bitmap glyph if available when kEmbeddedBitmapText_Flag is set, and selects
-the outline glyph if kEmbeddedBitmapText_Flag is clear.
-Windows may select the bitmap glyph but is not required to do so.
-OS_X and iOS do not support this option.
-
-Font_Embedded_Bitmaps is disabled by default.
-Font_Embedded_Bitmaps can be enabled by default by setting SkPaintDefaults_Flags to
-kEmbeddedBitmapText_Flag at compile time.
-
-#Example
-    #ToDo image will only output on Ubuntu ... how to handle that in fiddle? ##
-    #Platform !fiddle
-    #Description
-        The "hintgasp" TrueType font in the Skia resources/fonts directory
-        includes an embedded bitmap Glyph at odd font sizes. This example works
-        on platforms that use FreeType as their Font_Engine.
-        Windows may, but is not required to, return a bitmap glyph if
-        kEmbeddedBitmapText_Flag is set.
-    ##
-    #Image  embeddedbitmap.png
-
-    SkBitmap bitmap;
-    bitmap.allocN32Pixels(30, 15);
-    bitmap.eraseColor(0);
-    SkCanvas offscreen(bitmap);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setTextSize(13);
-    paint.setTypeface(MakeResourceAsTypeface("fonts/hintgasp.ttf"));
-    for (bool embedded : { false, true}) {
-        paint.setEmbeddedBitmapText(embedded);
-        offscreen.drawString("A", embedded ? 5 : 15, 15, paint);
-    }
-    canvas->drawBitmap(bitmap, 0, 0);
-    canvas->scale(10, 10);
-    canvas->drawBitmap(bitmap, -2, 1);
-##
-#Subtopic Embedded_Bitmaps ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Automatic_Hinting
-#Line # always adjust glyph paths ##
-
-If Hinting is set to SkFontHinting::kNormal or SkFontHinting::kFull, Font_Force_Hinting
-instructs the Font_Manager to always hint Glyphs.
-Font_Force_Hinting has no effect if Hinting is set to SkFontHinting::kNone or
-SkFontHinting::kSlight.
-
-Font_Force_Hinting only affects platforms that use FreeType as the Font_Manager.
-#Subtopic Automatic_Hinting ##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Fake_Bold
-#Line # approximate font styles ##
-
-Font_Embolden approximates the bold font style accompanying a normal font when a bold font face
-is not available. Skia does not provide font substitution; it is up to the client to find the
-bold font face using the platform Font_Manager.
-
-Use Text_Skew_X to approximate an italic font style when the italic font face
-is not available.
-
-A FreeType based port may define SK_USE_FREETYPE_EMBOLDEN at compile time to direct
-the font engine to create the bold Glyphs. Otherwise, the extra bold is computed
-by increasing the stroke width and setting the Style to kStrokeAndFill_Style as needed.
-
-Font_Embolden is disabled by default.
-
-#Example
-#Height 128
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setTextSize(40);
-    canvas->drawString("OjYy_-", 10, 35, paint);
-    paint.setFakeBoldText(true);
-    canvas->drawString("OjYy_-", 10, 75, paint);
-    // create a custom fake bold by varying the stroke width
-    paint.setFakeBoldText(false);
-    paint.setStyle(SkPaint::kStrokeAndFill_Style);
-    paint.setStrokeWidth(40.f / 48);
-    canvas->drawString("OjYy_-", 10, 115, paint);
-}
-##
-#Subtopic Fake_Bold ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Filter_Quality_Methods
-#Line # get and set Filter_Quality ##
-
-Filter_Quality trades speed for image filtering when the image is scaled.
-A lower Filter_Quality draws faster, but has less fidelity.
-A higher Filter_Quality draws slower, but looks better.
-If the image is drawn without scaling, the Filter_Quality choice will not result
-in a noticeable difference.
-
-Filter_Quality is used in Paint passed as a parameter to
-#List
-# SkCanvas::drawBitmap ##
-# SkCanvas::drawBitmapRect ##
-# SkCanvas::drawImage ##
-# SkCanvas::drawImageRect ##
-  #ToDo probably more... ##
-#List ##
-and when Paint has a Shader specialization that uses Image or Bitmap.
-
-Filter_Quality is kNone_SkFilterQuality by default.
-
-#Example
-#Image 3
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    canvas->scale(.2f, .2f);
-    for (SkFilterQuality q : { kNone_SkFilterQuality, kLow_SkFilterQuality,
-                               kMedium_SkFilterQuality, kHigh_SkFilterQuality } ) {
-        paint.setFilterQuality(q);
-        canvas->drawImage(image.get(), 0, 0, &paint);
-        canvas->translate(550, 0);
-        if (kLow_SkFilterQuality == q) canvas->translate(-1100, 550);
-    }
-}
-##
-#Subtopic Filter_Quality_Methods ##
-
-#Method SkFilterQuality getFilterQuality() const
-#In Filter_Quality_Methods
-#Line # returns Filter_Quality, image filtering level ##
-#Populate
-
-#Example
-    SkPaint paint;
-    SkDebugf("kNone_SkFilterQuality %c= paint.getFilterQuality()\n",
-            kNone_SkFilterQuality == paint.getFilterQuality() ? '=' : '!');
-
-    #StdOut
-        kNone_SkFilterQuality == paint.getFilterQuality()
-    ##
-##
-
-##
-
-
-#Method void setFilterQuality(SkFilterQuality quality)
-#In Filter_Quality_Methods
-#Line # sets Filter_Quality, the image filtering level ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setFilterQuality(kHigh_SkFilterQuality);
-    SkDebugf("kHigh_SkFilterQuality %c= paint.getFilterQuality()\n",
-            kHigh_SkFilterQuality == paint.getFilterQuality() ? '=' : '!');
-
-    #StdOut
-        kHigh_SkFilterQuality == paint.getFilterQuality()
-    ##
-##
-
-#SeeAlso SkFilterQuality Image_Scaling
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Color_Methods
-#Line # get and set Color ##
-
-#Table
-#Legend
-# name                  # description                                          ##
-#Legend ##
-# getColor              # returns Color_Alpha and RGB, one drawing color ##
-# setColor              # sets Color_Alpha and RGB, one drawing color    ##
-#Table ##
-
-Color specifies the red, blue, green, and Color_Alpha
-values used to draw a filled or stroked shape in a 32-bit value. Each component
-occupies 8-bits, ranging from zero: no contribution; to 255: full intensity.
-All values in any combination are valid.
-
-Color is not Premultiplied; Color_Alpha sets the transparency independent of
-RGB: red, blue, and green.
-
-The bit positions of Color_Alpha and RGB are independent of the bit
-positions on the output device, which may have more or fewer bits, and may have
-a different arrangement.
-
-#Table
-#Legend
-# bit positions # Color_Alpha # red # blue # green ##
-#Legend ##
-#               # 31 - 24     # 23 - 16       # 15 - 8         # 7 - 0           ##
-#Table ##
-
-#Example
-#Height 128
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setColor(0x8000FF00);  // transparent green
-        canvas->drawCircle(50, 50, 40, paint);
-        paint.setARGB(128, 255, 0, 0); // transparent red
-        canvas->drawCircle(80, 50, 40, paint);
-        paint.setColor(SK_ColorBLUE);
-        paint.setAlpha(0x80);
-        canvas->drawCircle(65, 65, 40, paint);
-    }
-##
-#Subtopic Color_Methods ##
-
-#Method SkColor getColor() const
-#In Color_Methods
-#Line # returns Color_Alpha and RGB, one drawing color ##
-#Populate
-
-#Example
-        SkPaint paint;
-        paint.setColor(SK_ColorYELLOW);
-        SkColor y = paint.getColor();
-        SkDebugf("Yellow is %d%% red, %d%% green, and %d%% blue.\n", (int) (SkColorGetR(y) / 2.55f),
-                (int) (SkColorGetG(y) / 2.55f), (int) (SkColorGetB(y) / 2.55f));
-
-        #StdOut
-            Yellow is 100% red, 100% green, and 0% blue.
-        ##
-    ##
-
-    #SeeAlso getColor4f SkColor
-
-##
-
-#Method SkColor4f getColor4f() const
-#In Color_Methods
-#Line # returns Color_Alpha and RGB, one drawing color ##
-#Populate
-
-#Example
-        SkPaint paint;
-        paint.setColor(SK_ColorYELLOW);
-        SkColor4f y = paint.getColor4f();
-        SkDebugf("Yellow is %d%% red, %d%% green, and %d%% blue.\n", (int) (y.fR * 100),
-                (int) (y.fG * 100), (int) (y.fB * 100));
-
-        #StdOut
-            Yellow is 100% red, 100% green, and 0% blue.
-        ##
-    ##
-
-    #SeeAlso getColor SkColor
-##
-
-
-#Method void setColor(SkColor color)
-#In Color_Methods
-#Line # sets Color_Alpha and RGB, one drawing color ##
-#Populate
-
-#Example
-        SkPaint green1, green2;
-        unsigned a = 255;
-        unsigned r = 0;
-        unsigned g = 255;
-        unsigned b = 0;
-        green1.setColor((a << 24) + (r << 16) + (g << 8) + (b << 0));
-        green2.setColor(0xFF00FF00);
-        SkDebugf("green1 %c= green2\n", green1 == green2 ? '=' : '!');
-
-        #StdOut
-            green1 == green2
-        ##
-    ##
-
-    #SeeAlso SkColor setColor4f setARGB SkColorSetARGB
-
-##
-
-#Method void setColor4f(const SkColor4f& color, SkColorSpace* colorSpace)
-#In Color_Methods
-#Line # sets Color_Alpha and RGB, one drawing color ##
-#Populate
-
-#Example
-        SkPaint green1, green2;
-        green1.setColor4f({0, 1, 0, 1}, nullptr);  // R=0 G=1 B=0 A=1
-        green2.setColor(0xFF00FF00); // A=255 R=0 G=255 B=0
-        SkDebugf("green1 %c= green2\n", green1 == green2 ? '=' : '!');
-
-        #StdOut
-            green1 == green2
-        ##
-    ##
-
-    #SeeAlso SkColor setColor setARGB SkColorSetARGB
-
-##
-
-#Subtopic Alpha_Methods
-#Line # get and set Alpha ##
-
-Color_Alpha sets the transparency independent of RGB: red, blue, and green.
-#Subtopic Alpha_Methods ##
-
-#Method uint8_t getAlpha() const
-#In Alpha_Methods
-#Line # returns Color_Alpha, color opacity ##
-#Populate
-
-#Example
-        SkPaint paint;
-        SkDebugf("255 %c= paint.getAlpha()\n", 255 == paint.getAlpha() ? '=' : '!');
-
-        #StdOut
-            255 == paint.getAlpha()
-        ##
-    ##
-
-##
-
-#Method void setAlpha(U8CPU a)
-#In Alpha_Methods
-#Line # sets Color_Alpha, color opacity ##
-#Populate
-
-#Example
-        SkPaint paint;
-        paint.setColor(0x00112233);
-        paint.setAlpha(0x44);
-        SkDebugf("0x44112233 %c= paint.getColor()\n", 0x44112233 == paint.getColor() ? '=' : '!');
-
-        #StdOut
-            0x44112233 == paint.getColor()
-        ##
-    ##
-
-##
-
-#Method void setARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b)
-#In Color_Methods
-#Line # sets color by component ##
-#Populate
-
-#Example
-        SkPaint transRed1, transRed2;
-        transRed1.setARGB(255 / 2, 255, 0, 0);
-        transRed2.setColor(SkColorSetARGB(255 / 2, 255, 0, 0));
-        SkDebugf("transRed1 %c= transRed2", transRed1 == transRed2 ? '=' : '!');
-
-        #StdOut
-            transRed1 == transRed2
-        ##
-    ##
-
-    #SeeAlso setColor SkColorSetARGB
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Style
-#Line # geometry filling, stroking ##
-
-Style specifies if the geometry is filled, stroked, or both filled and stroked.
-Some shapes ignore Style and are always drawn filled or stroked.
-
-#Subtopic Fill
-Set Style to kFill_Style to fill the shape.
-The fill covers the area inside the geometry for most shapes.
-#Subtopic Fill ##
-
-#Subtopic Stroke
-Set Style to kStroke_Style to stroke the shape.
-
-The stroke covers the area described by following the shape edge with a pen or brush of
-Stroke_Width. The area covered where the shape starts and stops is described by Stroke_Cap.
-The area covered where the shape turns a corner is described by Stroke_Join.
-The stroke is centered on the shape; it extends equally on either side of the shape edge.
-#Subtopic Stroke ##
-
-As Stroke_Width gets smaller, the drawn path frame is thinner. Stroke_Width less than one
-may have gaps, and if kAntiAlias_Flag is set, Color_Alpha will increase to visually decrease coverage.
-
-#SeeAlso Path_Fill_Type Path_Effect Style_Fill Style_Stroke
-#Subtopic Style ##
-
-#Subtopic Hairline
-Stroke_Width of zero has a special meaning and switches drawing to use Hairline.
-Hairline draws the thinnest continuous frame. If kAntiAlias_Flag is clear, adjacent pixels
-flow horizontally, vertically,or diagonally.
-
-#ToDo  what is the description of Anti_Aliased hairlines? ##
-
-Path drawing with Hairline may hit the same pixel more than once. For instance, Path containing
-two lines in one Path_Contour will draw the corner point once, but may both lines may draw the adjacent
-pixel. If kAntiAlias_Flag is set, transparency is applied twice, resulting in a darker pixel. Some
-GPU-backed implementations apply transparency at a later drawing stage, avoiding double hit pixels
-while stroking.
-
-#SeeAlso Path_Fill_Type Path_Effect Style_Fill Style_Stroke
-#Subtopic Hairline ##
-
-#Enum Style
-#Line # stroke, fill, or both ##
-
-#Code
-#Populate
-##
-
-#Code
-#In Constant
-#Filter kStyle
-#Populate
-##
-
-Set Style to fill, stroke, or both fill and stroke geometry.
-The stroke and fill
-share all paint attributes; for instance, they are drawn with the same color.
-
-Use kStrokeAndFill_Style to avoid hitting the same pixels twice with a stroke draw and
-a fill draw.
-
-#Const  kFill_Style 0
-#Line # set to fill geometry ##
-    Applies to Rect, Region, Round_Rect, Circles, Ovals, Path, and Text.
-    Bitmap, Image, Patches, Region, Sprites, and Vertices are painted as if
-    kFill_Style is set, and ignore the set Style.
-    The Path_Fill_Type specifies additional rules to fill the area outside the path edge,
-    and to create an unfilled hole inside the shape.
-    Style is set to kFill_Style by default.
-##
-
-#Const kStroke_Style 1
-#Line # set to stroke geometry ##
-    Applies to Rect, Region, Round_Rect, Arcs, Circles, Ovals, Path, and Text.
-    Arcs, Lines, and points, are always drawn as if kStroke_Style is set,
-    and ignore the set Style.
-    The stroke construction is unaffected by the Path_Fill_Type.
-##
-
-#Const kStrokeAndFill_Style 2
-#Line # sets to stroke and fill geometry ##
-    Applies to Rect, Region, Round_Rect, Circles, Ovals, Path, and Text.
-    Path is treated as if it is set to SkPath::kWinding_FillType,
-    and the set Path_Fill_Type is ignored.
-##
-
-#Const kStyleCount 3
-#Line # number of different Style values defined ##
-May be used to verify that Style is a legal value.
-##
-
-#Enum Style ##
-
-#Method Style getStyle() const
-#In Style
-#Line # returns Style: stroke, fill, or both ##
-#Populate
-
-#Example
-        SkPaint paint;
-        SkDebugf("SkPaint::kFill_Style %c= paint.getStyle()\n",
-                SkPaint::kFill_Style == paint.getStyle() ? '=' : '!');
-
-        #StdOut
-            SkPaint::kFill_Style == paint.getStyle()
-        ##
-    ##
-
-#SeeAlso Style setStyle
-##
-
-#Method void setStyle(Style style)
-#In Style
-#Line # sets Style: stroke, fill, or both ##
-#Populate
-
-#Example
-        void draw(SkCanvas* canvas) {
-            SkPaint paint;
-            paint.setStrokeWidth(5);
-            SkRegion region;
-            region.op(140, 10, 160, 30, SkRegion::kUnion_Op);
-            region.op(170, 40, 190, 60, SkRegion::kUnion_Op);
-            SkBitmap bitmap;
-            bitmap.setInfo(SkImageInfo::MakeA8(50, 50), 50);
-            uint8_t pixels[50][50];
-            for (int x = 0; x < 50; ++x) {
-                for (int y = 0; y < 50; ++y) {
-                    pixels[y][x] = (x + y) % 5 ? 0xFF : 0x00;
-                }
-            }
-            bitmap.setPixels(pixels);
-            for (auto style : { SkPaint::kFill_Style,
-                                SkPaint::kStroke_Style,
-                                SkPaint::kStrokeAndFill_Style }) {
-                paint.setStyle(style);
-                canvas->drawLine(10, 10, 60, 60, paint);
-                canvas->drawRect({80, 10, 130, 60}, paint);
-                canvas->drawRegion(region, paint);
-                canvas->drawBitmap(bitmap, 200, 10, &paint);
-                canvas->translate(0, 80);
-            }
-        }
-    ##
-
-#SeeAlso Style getStyle
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Stroke_Width
-#Line # thickness perpendicular to geometry ##
-
-Stroke_Width sets the width for stroking. The width is the thickness
-of the stroke perpendicular to the path direction when the paint style is
-set to kStroke_Style or kStrokeAndFill_Style.
-
-When width is greater than zero, the stroke encompasses as many pixels partially
-or fully as needed. When the width equals zero, the paint enables hairlines;
-the stroke is always one pixel wide.
-
-The stroke dimensions are scaled by the canvas matrix, but Hairline stroke
-remains one pixel wide regardless of scaling.
-
-The default width for the paint is zero.
-
-#Example
-#Height 170
-    #Platform raster gpu
-    #Description
-        The pixels hit to represent thin lines vary with the angle of the
-        line and the platform implementation.
-    ##
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        for (bool antialias : { false, true }) {
-            paint.setAntiAlias(antialias);
-            for (int width = 0; width <= 4; ++width) {
-                SkScalar offset = antialias * 100 + width * 20;
-                paint.setStrokeWidth(width * 0.25f);
-                canvas->drawLine(10 + offset,  10, 20 + offset,  60, paint);
-                canvas->drawLine(10 + offset, 110, 60 + offset, 160, paint);
-            }
-        }
-    }
-##
-
-#Method SkScalar getStrokeWidth() const
-
-#In Stroke_Width
-#Line # returns thickness of the stroke ##
-#Populate
-
-#Example
-        SkPaint paint;
-        SkDebugf("0 %c= paint.getStrokeWidth()\n", 0 == paint.getStrokeWidth() ? '=' : '!');
-
-        #StdOut
-            0 == paint.getStrokeWidth()
-        ##
-    ##
-
-##
-
-#Method void setStrokeWidth(SkScalar width)
-
-#In Stroke_Width
-#Line # sets thickness of the stroke ##
-#Populate
-
-#Example
-        SkPaint paint;
-        paint.setStrokeWidth(5);
-        paint.setStrokeWidth(-1);
-        SkDebugf("5 %c= paint.getStrokeWidth()\n", 5 == paint.getStrokeWidth() ? '=' : '!');
-
-        #StdOut
-            5 == paint.getStrokeWidth()
-        ##
-    ##
-
-##
-
-#Subtopic Stroke_Width ##
-# ------------------------------------------------------------------------------
-#Subtopic Miter_Limit
-#Line # maximum length of stroked corners ##
-
-Miter_Limit specifies the maximum miter length,
-relative to the stroke width.
-
-Miter_Limit is used when the Stroke_Join
-is set to kMiter_Join, and the Style is either kStroke_Style
-or kStrokeAndFill_Style.
-
-If the miter at a corner exceeds this limit, kMiter_Join
-is replaced with kBevel_Join.
-
-Miter_Limit can be computed from the corner angle using:
-#Formula # miter limit = 1 / sin ( angle / 2 ) ##.
-
-Miter_Limit default value is 4.
-The default may be changed at compile time by setting SkPaintDefaults_MiterLimit
-in "SkUserConfig.h" or as a define supplied by the build environment.
-
-Here are some miter limits and the angles that triggers them.
-#Table
-#Legend
-    # miter limit    # angle in degrees ##
-#Legend ##
-    # 10             # 11.48            ##
-    # 9              # 12.76            ##
-    # 8              # 14.36            ##
-    # 7              # 16.43            ##
-    # 6              # 19.19            ##
-    # 5              # 23.07            ##
-    # 4              # 28.96            ##
-    # 3              # 38.94            ##
-    # 2              # 60               ##
-    # 1              # 180              ##
-#Table ##
-
-#Example
-    #Height 170
-    #Width 384
-    #Description
-        This example draws a stroked corner and the miter length beneath.
-        When the miter limit is decreased slightly, the miter join is replaced
-        by a bevel join.
-    ##
-    void draw(SkCanvas* canvas) {
-        SkPoint pts[] = {{ 10, 50 }, { 110, 80 }, { 10, 110 }};
-        SkVector v[] = { pts[0] - pts[1], pts[2] - pts[1] };
-        SkScalar angle1 = SkScalarATan2(v[0].fY, v[0].fX);
-        SkScalar angle2 = SkScalarATan2(v[1].fY, v[1].fX);
-        const SkScalar strokeWidth = 20;
-        SkScalar miterLimit = 1 / SkScalarSin((angle2 - angle1) / 2);
-        SkScalar miterLength = strokeWidth * miterLimit;
-        SkPath path;
-        path.moveTo(pts[0]);
-        path.lineTo(pts[1]);
-        path.lineTo(pts[2]);
-        SkPaint paint;  // set to default kMiter_Join
-        paint.setAntiAlias(true);
-        paint.setStyle(SkPaint::kStroke_Style);
-        paint.setStrokeMiter(miterLimit);
-        paint.setStrokeWidth(strokeWidth);
-        canvas->drawPath(path, paint);
-        paint.setStrokeWidth(1);
-        canvas->drawLine(pts[1].fX - miterLength / 2, pts[1].fY + 50,
-                         pts[1].fX + miterLength / 2, pts[1].fY + 50, paint);
-        canvas->translate(200, 0);
-        miterLimit *= 0.99f;
-        paint.setStrokeMiter(miterLimit);
-        paint.setStrokeWidth(strokeWidth);
-        canvas->drawPath(path, paint);
-        paint.setStrokeWidth(1);
-        canvas->drawLine(pts[1].fX - miterLength / 2, pts[1].fY + 50,
-                         pts[1].fX + miterLength / 2, pts[1].fY + 50, paint);
-    }
-##
-
-#Method SkScalar getStrokeMiter() const
-
-#In Miter_Limit
-#Line # returns Miter_Limit, angles with sharp corners ##
-#Populate
-
-#Example
-        SkPaint paint;
-        SkDebugf("default miter limit == %g\n", paint.getStrokeMiter());
-
-        #StdOut
-        default miter limit == 4
-        ##
-    ##
-
-    #SeeAlso Miter_Limit setStrokeMiter Join
-
-##
-
-#Method void setStrokeMiter(SkScalar miter)
-
-#In Miter_Limit
-#Line # sets Miter_Limit, angles with sharp corners ##
-#Populate
-
-#Example
-        SkPaint paint;
-        paint.setStrokeMiter(8);
-        paint.setStrokeMiter(-1);
-        SkDebugf("default miter limit == %g\n", paint.getStrokeMiter());
-
-        #StdOut
-        default miter limit == 8
-        ##
-    ##
-
-    #SeeAlso Miter_Limit getStrokeMiter Join
-
-##
-
-#Subtopic Miter_Limit ##
-# ------------------------------------------------------------------------------
-#Subtopic Stroke_Cap
-#Line # decorations at ends of open strokes ##
-#Subtopic Stroke_Cap ##
-
-#Enum Cap
-#Line # start and end geometry on stroked shapes ##
-
-#Code
-#Populate
-##
-
-#Code
-#In Constant
-#Filter kCap
-#Populate
-##
-
-Stroke_Cap draws at the beginning and end of an open Path_Contour.
-
-    #Const kButt_Cap 0
-    #Line # no stroke extension ##
-        Does not extend the stroke past the beginning or the end.
-    ##
-    #Const kRound_Cap 1
-    #Line # adds circle ##
-        Adds a circle with a diameter equal to Stroke_Width at the beginning
-        and end.
-    ##
-    #Const kSquare_Cap 2
-    #Line # adds square ##
-        Adds a square with sides equal to Stroke_Width at the beginning
-        and end. The square sides are parallel to the initial and final direction
-        of the stroke.
-    ##
-    #Const kLast_Cap 2
-    #Line # largest Stroke_Cap value ##
-        Equivalent to the largest value for Stroke_Cap.
-    ##
-    #Const kDefault_Cap 0
-    #Line # equivalent to kButt_Cap ##
-        Stroke_Cap is set to kButt_Cap by default.
-    ##
-
-    #Const kCapCount 3
-    #Line # number of different Stroke_Cap values defined ##
-        May be used to verify that Stroke_Cap is a legal value.
-    ##
-#Enum ##
-
-Stroke describes the area covered by a pen of Stroke_Width as it
-follows the Path_Contour, moving parallel to the contour direction.
-
-If the Path_Contour is not terminated by SkPath::kClose_Verb, the contour has a
-visible beginning and end.
-
-Path_Contour may start and end at the same point; defining Zero_Length_Contour.
-
-kButt_Cap and Zero_Length_Contour is not drawn.
-kRound_Cap and Zero_Length_Contour draws a circle of diameter Stroke_Width
-at the contour point.
-kSquare_Cap and Zero_Length_Contour draws an upright square with a side of
-Stroke_Width at the contour point.
-
-Stroke_Cap is kButt_Cap by default.
-
-#Example
-#Height 200
-    SkPaint paint;
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setStrokeWidth(20);
-    SkPath path;
-    path.moveTo(30, 30);
-    path.lineTo(30, 30);
-    path.moveTo(70, 30);
-    path.lineTo(90, 40);
-    for (SkPaint::Cap c : { SkPaint::kButt_Cap, SkPaint::kRound_Cap, SkPaint::kSquare_Cap } ) {
-        paint.setStrokeCap(c);
-        canvas->drawPath(path, paint);
-        canvas->translate(0, 70);
-    }
-##
-
-#Method Cap getStrokeCap() const
-
-#In Stroke_Cap
-#Line # returns Cap, the area drawn at path ends ##
-#Populate
-
-#Example
-        SkPaint paint;
-        SkDebugf("kButt_Cap %c= default stroke cap\n",
-                SkPaint::kButt_Cap == paint.getStrokeCap() ? '=' : '!');
-
-        #StdOut
-            kButt_Cap == default stroke cap
-        ##
-    ##
-
-    #SeeAlso Stroke_Cap setStrokeCap
-##
-
-#Method void setStrokeCap(Cap cap)
-
-#In Stroke_Cap
-#Line # sets Cap, the area drawn at path ends ##
-#Populate
-
-#Example
-        SkPaint paint;
-        paint.setStrokeCap(SkPaint::kRound_Cap);
-        paint.setStrokeCap((SkPaint::Cap) SkPaint::kCapCount);
-        SkDebugf("kRound_Cap %c= paint.getStrokeCap()\n",
-                SkPaint::kRound_Cap == paint.getStrokeCap() ? '=' : '!');
-
-        #StdOut
-            kRound_Cap == paint.getStrokeCap()
-        ##
-    ##
-
-    #SeeAlso Stroke_Cap getStrokeCap
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Stroke_Join
-#Line # decoration at corners of strokes ##
-#Subtopic Stroke_Join ##
-
-Stroke_Join draws at the sharp corners of an open or closed Path_Contour.
-
-Stroke describes the area covered by a pen of Stroke_Width as it
-follows the Path_Contour, moving parallel to the contour direction.
-
-If the contour direction changes abruptly, because the tangent direction leading
-to the end of a curve within the contour does not match the tangent direction of
-the following curve, the pair of curves meet at Stroke_Join.
-
-#Example
-#Height 200
-    SkPaint paint;
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setStrokeWidth(20);
-    SkPath path;
-    path.moveTo(30, 20);
-    path.lineTo(40, 40);
-    path.conicTo(70, 20, 100, 20, .707f);
-    for (SkPaint::Join j : { SkPaint::kMiter_Join, SkPaint::kRound_Join, SkPaint::kBevel_Join } ) {
-        paint.setStrokeJoin(j);
-        canvas->drawPath(path, paint);
-        canvas->translate(0, 70);
-    }
-##
-
-#Enum Join
-#Line # corner geometry on stroked shapes ##
-#Code
-#Populate
-##
-
-#Code
-#In Constant
-#Filter kJoin
-#Populate
-##
-
-Join specifies how corners are drawn when a shape is stroked. Join
-affects the four corners of a stroked rectangle, and the connected segments in a
-stroked path.
-
-Choose miter join to draw sharp corners. Choose round join to draw a circle with a
-radius equal to the stroke width on top of the corner. Choose bevel join to minimally
-connect the thick strokes.
-
-The fill path constructed to describe the stroked path respects the join setting but may
-not contain the actual join. For instance, a fill path constructed with round joins does
-not necessarily include circles at each connected segment.
-
-#Const kMiter_Join 0
-#Line # extends to Miter_Limit ##
-    Extends the outside corner to the extent allowed by Miter_Limit.
-    If the extension exceeds Miter_Limit, kBevel_Join is used instead.
-##
-
-#Const kRound_Join 1
-#Line # adds circle ##
-    Adds a circle with a diameter of Stroke_Width at the sharp corner.
-##
-
-#Const kBevel_Join 2
-#Line # connects outside edges ##
-    Connects the outside edges of the sharp corner.
-##
-
-#Const kLast_Join 2
-#Line # equivalent to the largest value for Stroke_Join ##
-##
-
-#Const kDefault_Join 1
-#Line # equivalent to kMiter_Join ##
-    Stroke_Join is set to kMiter_Join by default.
-##
-
-#Const kJoinCount 3
-#Line # number of different Stroke_Join values defined ##
-    May be used to verify that Stroke_Join is a legal value.
-##
-
-#Example
-#Width 462
-void draw(SkCanvas* canvas) {
-    SkPath path;
-    path.moveTo(10, 50);
-    path.quadTo(35, 110, 60, 210);
-    path.quadTo(105, 110, 130, 10);
-    SkPaint paint;  // set to default kMiter_Join
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setStrokeWidth(20);
-    canvas->drawPath(path, paint);
-    canvas->translate(150, 0);
-    paint.setStrokeJoin(SkPaint::kBevel_Join);
-    canvas->drawPath(path, paint);
-    canvas->translate(150, 0);
-    paint.setStrokeJoin(SkPaint::kRound_Join);
-    canvas->drawPath(path, paint);
-}
-##
-
-#SeeAlso setStrokeJoin getStrokeJoin setStrokeMiter getStrokeMiter
-
-#Enum ##
-
-#Method Join getStrokeJoin() const
-
-#In Stroke_Join
-#Line # returns Join, geometry on path corners ##
-#Populate
-
-#Example
-        SkPaint paint;
-        SkDebugf("kMiter_Join %c= default stroke join\n",
-                SkPaint::kMiter_Join == paint.getStrokeJoin() ? '=' : '!');
-
-        #StdOut
-            kMiter_Join == default stroke join
-        ##
-    ##
-
-    #SeeAlso Stroke_Join setStrokeJoin
-##
-
-#Method void setStrokeJoin(Join join)
-
-#In Stroke_Join
-#Line # sets Join, geometry on path corners ##
-#Populate
-
-#Example
-        SkPaint paint;
-        paint.setStrokeJoin(SkPaint::kMiter_Join);
-        paint.setStrokeJoin((SkPaint::Join) SkPaint::kJoinCount);
-        SkDebugf("kMiter_Join %c= paint.getStrokeJoin()\n",
-                SkPaint::kMiter_Join == paint.getStrokeJoin() ? '=' : '!');
-
-        #StdOut
-            kMiter_Join == paint.getStrokeJoin()
-        ##
-    ##
-
-    #SeeAlso Stroke_Join getStrokeJoin
-##
-
-#SeeAlso Miter_Limit
-
-# ------------------------------------------------------------------------------
-#Subtopic Fill_Path
-#Line # make Path from Path_Effect, stroking ##
-
-Fill_Path creates a Path by applying the Path_Effect, followed by the Style_Stroke.
-
-If Paint contains Path_Effect, Path_Effect operates on the source Path; the result
-replaces the destination Path. Otherwise, the source Path is replaces the
-destination Path.
-
-Fill Path can request the Path_Effect to restrict to a culling rectangle, but
-the Path_Effect is not required to do so.
-
-If Style is kStroke_Style or kStrokeAndFill_Style,
-and Stroke_Width is greater than zero, the Stroke_Width, Stroke_Cap, Stroke_Join,
-and Miter_Limit operate on the destination Path, replacing it.
-
-Fill Path can specify the precision used by Stroke_Width to approximate the stroke geometry.
-
-If the Style is kStroke_Style and the Stroke_Width is zero, getFillPath
-returns false since Hairline has no filled equivalent.
-
-#SeeAlso Style_Stroke Stroke_Width Path_Effect
-
-#Subtopic Fill_Path ##
-
-#Method bool getFillPath(const SkPath& src, SkPath* dst, const SkRect* cullRect,
-                     SkScalar resScale = 1) const
-#In Fill_Path
-#Line # returns fill path equivalent to stroke ##
-#Populate
-
-#Example
-    #Height 192
-    #Description
-    A very small Quad stroke is turned into a filled path with increasing levels of precision.
-    At the lowest precision, the Quad stroke is approximated by a rectangle.
-    At the highest precision, the filled path has high fidelity compared to the original stroke.
-    ##
-        void draw(SkCanvas* canvas) {
-            SkPaint strokePaint;
-            strokePaint.setAntiAlias(true);
-            strokePaint.setStyle(SkPaint::kStroke_Style);
-            strokePaint.setStrokeWidth(.1f);
-            SkPath strokePath;
-            strokePath.moveTo(.08f, .08f);
-            strokePath.quadTo(.09f, .08f, .17f, .17f);
-            SkPath fillPath;
-            SkPaint outlinePaint(strokePaint);
-            outlinePaint.setStrokeWidth(2);
-            SkMatrix scale = SkMatrix::MakeScale(300, 300);
-            for (SkScalar precision : { 0.01f, .1f, 1.f, 10.f, 100.f } ) {
-                strokePaint.getFillPath(strokePath, &fillPath, nullptr, precision);
-                fillPath.transform(scale);
-                canvas->drawPath(fillPath, outlinePaint);
-                canvas->translate(60, 0);
-                if (1.f == precision) canvas->translate(-180, 100);
-            }
-            strokePath.transform(scale);
-            strokePaint.setStrokeWidth(30);
-            canvas->drawPath(strokePath, strokePaint);
-        }
-    ##
-
-##
-
-#Method bool getFillPath(const SkPath& src, SkPath* dst) const
-
-#In Fill_Path
-#Populate
-
-#Example
-    #Height 128
-        void draw(SkCanvas* canvas) {
-            SkPaint paint;
-            paint.setStyle(SkPaint::kStroke_Style);
-            paint.setStrokeWidth(10);
-            SkPath strokePath;
-            strokePath.moveTo(20, 20);
-            strokePath.lineTo(100, 100);
-            canvas->drawPath(strokePath, paint);
-            SkPath fillPath;
-            paint.getFillPath(strokePath, &fillPath);
-            paint.setStrokeWidth(2);
-            canvas->translate(40, 0);
-            canvas->drawPath(fillPath, paint);
-        }
-    ##
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Shader_Methods
-#Line # get and set Shader ##
-
-Shader defines the colors used when drawing a shape.
-Shader may be an image, a gradient, or a computed fill.
-If Paint has no Shader, then Color fills the shape.
-
-Shader is modulated by Color_Alpha component of Color.
-If Shader object defines only Color_Alpha, then Color modulated by Color_Alpha describes
-the fill.
-
-The drawn transparency can be modified without altering Shader, by changing Color_Alpha.
-
-#Example
-void draw(SkCanvas* canvas) {
-   SkPaint paint;
-   SkPoint center = { 50, 50 };
-   SkScalar radius = 50;
-   const SkColor colors[] = { 0xFFFFFFFF, 0xFF000000 };
-   paint.setShader(SkGradientShader::MakeRadial(center, radius, colors,
-        nullptr, SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode));
-   for (SkScalar a : { 0.3f, 0.6f, 1.0f } ) {
-       paint.setAlpha((int) (a * 255));
-       canvas->drawCircle(center.fX, center.fY, radius, paint);
-       canvas->translate(70, 70);
-   }
-}
-##
-
-If Shader generates only Color_Alpha then all components of Color modulate the output.
-
-#Example
-void draw(SkCanvas* canvas) {
-   SkPaint paint;
-   SkBitmap bitmap;
-   bitmap.setInfo(SkImageInfo::MakeA8(5, 1), 5);  // bitmap only contains alpha
-   uint8_t pixels[5] = { 0x22, 0x55, 0x88, 0xBB, 0xFF };
-   bitmap.setPixels(pixels);
-   paint.setShader(SkShader::MakeBitmapShader(bitmap,
-            SkShader::kMirror_TileMode, SkShader::kMirror_TileMode));
-   for (SkColor c : { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN } ) {
-       paint.setColor(c);  // all components in color affect shader
-       canvas->drawCircle(50, 50, 50, paint);
-       canvas->translate(70, 70);
-   }
-}
-##
-
-#Method SkShader* getShader() const
-
-#In Shader_Methods
-#Line # returns Shader, multiple drawing colors; gradients ##
-#Populate
-
-#Example
-        void draw(SkCanvas* canvas) {
-           SkPaint paint;
-           SkDebugf("nullptr %c= shader\n", paint.getShader() ? '!' : '=');
-           paint.setShader(SkShader::MakeEmptyShader());
-           SkDebugf("nullptr %c= shader\n", paint.getShader() ? '!' : '=');
-        }
-
-        #StdOut
-            nullptr == shader
-            nullptr != shader
-        ##
-    ##
-
-##
-
-#Method sk_sp<SkShader> refShader() const
-
-#In Shader_Methods
-#Line # references Shader, multiple drawing colors; gradients ##
-#Populate
-
-#Example
-        void draw(SkCanvas* canvas) {
-           SkPaint paint1, paint2;
-           paint1.setShader(SkShader::MakeEmptyShader());
-           SkDebugf("shader unique: %s\n", paint1.getShader()->unique() ? "true" : "false");
-           paint2.setShader(paint1.refShader());
-           SkDebugf("shader unique: %s\n", paint1.getShader()->unique() ? "true" : "false");
-        }
-
-        #StdOut
-            shader unique: true
-            shader unique: false
-        ##
-    ##
-
-##
-
-#Method void setShader(sk_sp<SkShader> shader)
-
-#In Shader_Methods
-#Line # sets Shader, multiple drawing colors; gradients ##
-#Populate
-
-#Example
-    #Height 64
-        void draw(SkCanvas* canvas) {
-            SkPaint paint;
-            paint.setColor(SK_ColorBLUE);
-            paint.setShader(SkShader::MakeColorShader(SK_ColorRED));
-            canvas->drawRect(SkRect::MakeWH(40, 40), paint);
-            paint.setShader(nullptr);
-            canvas->translate(50, 0);
-            canvas->drawRect(SkRect::MakeWH(40, 40), paint);
-        }
-    ##
-
-##
-
-#Subtopic Shader_Methods ##
-# ------------------------------------------------------------------------------
-#Subtopic Color_Filter_Methods
-#Line # get and set Color_Filter ##
-
-Color_Filter alters the color used when drawing a shape.
-Color_Filter may apply Blend_Mode, transform the color through a matrix, or composite multiple filters.
-If Paint has no Color_Filter, the color is unaltered.
-
-The drawn transparency can be modified without altering Color_Filter, by changing Color_Alpha.
-
-#Example
-#Height 128
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setColorFilter(SkColorMatrixFilter::MakeLightingFilter(0xFFFFFF, 0xFF0000));
-    for (SkColor c : { SK_ColorBLACK, SK_ColorGREEN } ) {
-        paint.setColor(c);
-        canvas->drawRect(SkRect::MakeXYWH(10, 10, 50, 50), paint);
-        paint.setAlpha(0x80);
-        canvas->drawRect(SkRect::MakeXYWH(60, 60, 50, 50), paint);
-        canvas->translate(100, 0);
-    }
-}
-##
-
-#Method SkColorFilter* getColorFilter() const
-
-#In Color_Filter_Methods
-#Line # returns Color_Filter, how colors are altered ##
-#Populate
-
-#Example
-        void draw(SkCanvas* canvas) {
-           SkPaint paint;
-           SkDebugf("nullptr %c= color filter\n", paint.getColorFilter() ? '!' : '=');
-           paint.setColorFilter(SkColorFilter::MakeModeFilter(SK_ColorLTGRAY, SkBlendMode::kSrcIn));
-           SkDebugf("nullptr %c= color filter\n", paint.getColorFilter() ? '!' : '=');
-        }
-
-        #StdOut
-            nullptr == color filter
-            nullptr != color filter
-        ##
-    ##
-##
-
-#Method sk_sp<SkColorFilter> refColorFilter() const
-
-#In Color_Filter_Methods
-#Line # references Color_Filter, how colors are altered ##
-#Populate
-
-#Example
-    void draw(SkCanvas* canvas) {
-        SkPaint paint1, paint2;
-        paint1.setColorFilter(SkColorFilter::MakeModeFilter(0xFFFF0000, SkBlendMode::kSrcATop));
-        SkDebugf("color filter unique: %s\n", paint1.getColorFilter()->unique() ? "true" : "false");
-        paint2.setColorFilter(paint1.refColorFilter());
-        SkDebugf("color filter unique: %s\n", paint1.getColorFilter()->unique() ? "true" : "false");
-    }
-
-        #StdOut
-            color filter unique: true
-            color filter unique: false
-        ##
-    ##
-##
-
-#Method void setColorFilter(sk_sp<SkColorFilter> colorFilter)
-
-#In Color_Filter_Methods
-#Line # sets Color_Filter, alters color ##
-#Populate
-
-#Example
-    #Height 64
-        void draw(SkCanvas* canvas) {
-           SkPaint paint;
-           paint.setColorFilter(SkColorFilter::MakeModeFilter(SK_ColorLTGRAY, SkBlendMode::kSrcIn));
-           canvas->drawRect(SkRect::MakeWH(50, 50), paint);
-           paint.setColorFilter(nullptr);
-           canvas->translate(70, 0);
-           canvas->drawRect(SkRect::MakeWH(50, 50), paint);
-        }
-    ##
-
-##
-
-#Subtopic Color_Filter_Methods ##
-# ------------------------------------------------------------------------------
-#Subtopic Blend_Mode_Methods
-#Line # get and set Blend_Mode ##
-
-Blend_Mode describes how Color combines with the destination color.
-The default setting, SkBlendMode::kSrcOver, draws the source color
-over the destination color.
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPaint normal, blender;
-    normal.setColor(0xFF58a889);
-    blender.setColor(0xFF8958a8);
-    canvas->clear(0);
-    for (SkBlendMode m : { SkBlendMode::kSrcOver, SkBlendMode::kSrcIn, SkBlendMode::kSrcOut } ) {
-        normal.setBlendMode(SkBlendMode::kSrcOver);
-        canvas->drawOval(SkRect::MakeXYWH(30, 30, 30, 80), normal);
-        blender.setBlendMode(m);
-        canvas->drawOval(SkRect::MakeXYWH(10, 50, 80, 30), blender);
-        canvas->translate(70, 70);
-    }
-}
-##
-
-#SeeAlso Blend_Mode
-
-#Method SkBlendMode getBlendMode() const
-
-#In Blend_Mode_Methods
-#Line # returns Blend_Mode, how colors combine with Device ##
-#Populate
-
-#Example
-        void draw(SkCanvas* canvas) {
-           SkPaint paint;
-           SkDebugf("kSrcOver %c= getBlendMode\n",
-                    SkBlendMode::kSrcOver == paint.getBlendMode() ? '=' : '!');
-           paint.setBlendMode(SkBlendMode::kSrc);
-           SkDebugf("kSrcOver %c= getBlendMode\n",
-                    SkBlendMode::kSrcOver == paint.getBlendMode() ? '=' : '!');
-        }
-
-        #StdOut
-            kSrcOver == getBlendMode
-            kSrcOver != getBlendMode
-        ##
-    ##
-
-##
-
-#Method bool isSrcOver() const
-
-#In Blend_Mode_Methods
-#Line # returns true if Blend_Mode is SkBlendMode::kSrcOver ##
-#Populate
-
-#Example
-        void draw(SkCanvas* canvas) {
-           SkPaint paint;
-           SkDebugf("isSrcOver %c= true\n", paint.isSrcOver() ? '=' : '!');
-           paint.setBlendMode(SkBlendMode::kSrc);
-           SkDebugf("isSrcOver %c= true\n", paint.isSrcOver() ? '=' : '!');
-        }
-
-        #StdOut
-            isSrcOver == true
-            isSrcOver != true
-        ##
-    ##
-
-##
-
-#Method void setBlendMode(SkBlendMode mode)
-
-#In Blend_Mode_Methods
-#Line # sets Blend_Mode, how colors combine with destination ##
-#Populate
-
-#Example
-        void draw(SkCanvas* canvas) {
-           SkPaint paint;
-           SkDebugf("isSrcOver %c= true\n", paint.isSrcOver() ? '=' : '!');
-           paint.setBlendMode(SkBlendMode::kSrc);
-           SkDebugf("isSrcOver %c= true\n", paint.isSrcOver() ? '=' : '!');
-        }
-
-        #StdOut
-            isSrcOver == true
-            isSrcOver != true
-        ##
-    ##
-
-##
-
-#Subtopic Blend_Mode_Methods ##
-# ------------------------------------------------------------------------------
-#Subtopic Path_Effect_Methods
-#Line # get and set Path_Effect ##
-
-Path_Effect modifies the path geometry before drawing it.
-Path_Effect may implement dashing, custom fill effects and custom stroke effects.
-If Paint has no Path_Effect, the path geometry is unaltered when filled or stroked.
-
-#Example
-#Height 160
-        void draw(SkCanvas* canvas) {
-            SkPaint paint;
-            paint.setStyle(SkPaint::kStroke_Style);
-            paint.setStrokeWidth(16);
-            SkScalar intervals[] = {30, 10};
-            paint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 1));
-            canvas->drawRoundRect({20, 20, 120, 120}, 20, 20, paint);
-        }
-##
-
-#SeeAlso Path_Effect
-
-#Method SkPathEffect* getPathEffect() const
-
-#In Path_Effect_Methods
-#Line # returns Path_Effect, modifications to path geometry; dashing ##
-#Populate
-
-#Example
-        void draw(SkCanvas* canvas) {
-           SkPaint paint;
-           SkDebugf("nullptr %c= path effect\n", paint.getPathEffect() ? '!' : '=');
-           paint.setPathEffect(SkCornerPathEffect::Make(10));
-           SkDebugf("nullptr %c= path effect\n", paint.getPathEffect() ? '!' : '=');
-        }
-
-        #StdOut
-            nullptr == path effect
-            nullptr != path effect
-        ##
-    ##
-
-##
-
-
-#Method sk_sp<SkPathEffect> refPathEffect() const
-
-#In Path_Effect_Methods
-#Line # references Path_Effect, modifications to path geometry; dashing ##
-#Populate
-
-#Example
-    void draw(SkCanvas* canvas) {
-        SkPaint paint1, paint2;
-        SkScalar intervals[] = {1, 2};
-        paint1.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 10));
-        SkDebugf("path effect unique: %s\n", paint1.getPathEffect()->unique() ? "true" : "false");
-        paint2.setPathEffect(paint1.refPathEffect());
-        SkDebugf("path effect unique: %s\n", paint1.getPathEffect()->unique() ? "true" : "false");
-    }
-
-        #StdOut
-            path effect unique: true
-            path effect unique: false
-        ##
-    ##
-
-##
-
-
-#Method void setPathEffect(sk_sp<SkPathEffect> pathEffect)
-
-#In Path_Effect_Methods
-#Line # sets Path_Effect, modifications to path geometry; dashing ##
-#Populate
-
-#Example
-        void draw(SkCanvas* canvas) {
-            SkPaint paint;
-            paint.setPathEffect(SkDiscretePathEffect::Make(3, 5));
-            canvas->drawRect(SkRect::MakeXYWH(40, 40, 175, 175), paint);
-        }
-    ##
-
-##
-
-#Subtopic Path_Effect_Methods ##
-# ------------------------------------------------------------------------------
-#Subtopic Mask_Filter_Methods
-#Line # get and set Mask_Filter ##
-
-Mask_Filter uses coverage of the shape drawn to create Mask_Alpha.
-Mask_Filter takes a Mask, and returns a Mask.
-
-Mask_Filter may change the geometry and transparency of the shape, such as
-creating a blur effect. Set Mask_Filter to nullptr to prevent Mask_Filter from
-modifying the draw.
-
-#Example
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setMaskFilter(SkMaskFilter::MakeBlur(kSolid_SkBlurStyle, 3));
-        canvas->drawRect(SkRect::MakeXYWH(40, 40, 175, 175), paint);
-    }
-##
-
-#Method SkMaskFilter* getMaskFilter() const
-
-#In Mask_Filter_Methods
-#Line # returns Mask_Filter, alterations to Mask_Alpha ##
-#Populate
-
-#Example
-        void draw(SkCanvas* canvas) {
-           SkPaint paint;
-           SkDebugf("nullptr %c= mask filter\n", paint.getMaskFilter() ? '!' : '=');
-           paint.setMaskFilter(SkMaskFilter::MakeBlur(kOuter_SkBlurStyle, 3));
-           SkDebugf("nullptr %c= mask filter\n", paint.getMaskFilter() ? '!' : '=');
-        }
-
-        #StdOut
-            nullptr == mask filter
-            nullptr != mask filter
-        ##
-    ##
-
-##
-
-#Method sk_sp<SkMaskFilter> refMaskFilter() const
-
-#In Mask_Filter_Methods
-#Line # references Mask_Filter, alterations to Mask_Alpha ##
-#Populate
-
-#Example
-    void draw(SkCanvas* canvas) {
-        SkPaint paint1, paint2;
-        paint1.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 1));
-        SkDebugf("mask filter unique: %s\n", paint1.getMaskFilter()->unique() ? "true" : "false");
-        paint2.setMaskFilter(paint1.refMaskFilter());
-        SkDebugf("mask filter unique: %s\n", paint1.getMaskFilter()->unique() ? "true" : "false");
-    }
-
-        #StdOut
-            mask filter unique: true
-            mask filter unique: false
-        ##
-    ##
-
-##
-
-#Method void setMaskFilter(sk_sp<SkMaskFilter> maskFilter)
-
-#In Mask_Filter_Methods
-#Line # sets Mask_Filter, alterations to Mask_Alpha ##
-#Populate
-
-#Example
-        void draw(SkCanvas* canvas) {
-            SkPaint paint;
-            paint.setStyle(SkPaint::kStroke_Style);
-            paint.setStrokeWidth(10);
-            paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 10));
-            canvas->drawRect(SkRect::MakeXYWH(40, 40, 175, 175), paint);
-        }
-    ##
-
-##
-
-#Subtopic Mask_Filter_Methods ##
-# ------------------------------------------------------------------------------
-#Subtopic Typeface_Methods
-#Line # get and set Typeface ##
-
-Typeface identifies the font used when drawing and measuring text.
-Typeface may be specified by name, from a file, or from a data stream.
-The default Typeface defers to the platform-specific default font
-implementation.
-
-#Example
-#Height 100
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setTypeface(SkTypeface::MakeFromName(nullptr, SkFontStyle()));
-        paint.setAntiAlias(true);
-        paint.setTextSize(36);
-        canvas->drawString("A Big Hello!", 10, 40, paint);
-        paint.setTypeface(nullptr);
-        paint.setFakeBoldText(true);
-        canvas->drawString("A Big Hello!", 10, 80, paint);
-    }
-##
-
-#Subtopic Typeface_Methods ##
-# ------------------------------------------------------------------------------
-#Subtopic Image_Filter_Methods
-#Line # get and set Image_Filter ##
-
-Image_Filter operates on the pixel representation of the shape, as modified by Paint
-with Blend_Mode set to SkBlendMode::kSrcOver. Image_Filter creates a new bitmap,
-which is drawn to the device using the set Blend_Mode.
-
-Image_Filter is higher level than Mask_Filter; for instance, an Image_Filter
-can operate on all channels of Color, while Mask_Filter generates Alpha only.
-Image_Filter operates independently of and can be used in combination with
-Mask_Filter.
-
-#Example
-    #ToDo explain why the two draws are so different ##
-    #Function
-    ###$
-    #include "SkBlurImageFilter.h"
-    $$$#
-    ##
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setStyle(SkPaint::kStroke_Style);
-        paint.setStrokeWidth(2);
-        SkRegion region;
-        region.op( 10, 10, 50, 50, SkRegion::kUnion_Op);
-        region.op( 10, 50, 90, 90, SkRegion::kUnion_Op);
-        paint.setImageFilter(SkBlurImageFilter::Make(5.0f, 5.0f, nullptr));
-        canvas->drawRegion(region, paint);
-        paint.setImageFilter(nullptr);
-        paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 5));
-        canvas->translate(100, 100);
-        canvas->drawRegion(region, paint);
-    }
-##
-
-#Method SkImageFilter* getImageFilter() const
-
-#In Image_Filter_Methods
-#Line # returns Image_Filter, alter pixels; blur ##
-#Populate
-
-#Example
-        #Function
-        ###$
-        #include "SkBlurImageFilter.h"
-        $$$#
-        ##
-        void draw(SkCanvas* canvas) {
-           SkPaint paint;
-           SkDebugf("nullptr %c= image filter\n", paint.getImageFilter() ? '!' : '=');
-           paint.setImageFilter(SkBlurImageFilter::Make(kOuter_SkBlurStyle, 3, nullptr, nullptr));
-           SkDebugf("nullptr %c= image filter\n", paint.getImageFilter() ? '!' : '=');
-        }
-
-        #StdOut
-            nullptr == image filter
-            nullptr != image filter
-        ##
-    ##
-
-##
-
-#Method sk_sp<SkImageFilter> refImageFilter() const
-
-#In Image_Filter_Methods
-#Line # references Image_Filter, alter pixels; blur ##
-#Populate
-
-#Example
-    void draw(SkCanvas* canvas) {
-        SkPaint paint1, paint2;
-        paint1.setImageFilter(SkOffsetImageFilter::Make(25, 25, nullptr));
-        SkDebugf("image filter unique: %s\n", paint1.getImageFilter()->unique() ? "true" : "false");
-        paint2.setImageFilter(paint1.refImageFilter());
-        SkDebugf("image filter unique: %s\n", paint1.getImageFilter()->unique() ? "true" : "false");
-    }
-
-        #StdOut
-            image filter unique: true
-            image filter unique: false
-        ##
-    ##
-
-##
-
-#Method void setImageFilter(sk_sp<SkImageFilter> imageFilter)
-
-#In Image_Filter_Methods
-#Line # sets Image_Filter, alter pixels; blur ##
-#Populate
-
-#Example
-    #Height 160
-    void draw(SkCanvas* canvas) {
-        SkBitmap bitmap;
-        bitmap.allocN32Pixels(100, 100);
-        SkCanvas offscreen(bitmap);
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setColor(SK_ColorWHITE);
-        paint.setTextSize(96);
-        offscreen.clear(0);
-        offscreen.drawString("e", 20, 70, paint);
-        paint.setImageFilter(
-               SkLightingImageFilter::MakePointLitDiffuse(SkPoint3::Make(80, 100, 10),
-               SK_ColorWHITE, 1, 2, nullptr, nullptr));
-        canvas->drawBitmap(bitmap, 0, 0, &paint);
-    }
-    ##
-
-##
-
-#Subtopic Image_Filter_Methods ##
-# ------------------------------------------------------------------------------
-#Subtopic Draw_Looper_Methods
-#Line # get and set Draw_Looper ##
-
-Draw_Looper sets a modifier that communicates state from one Draw_Layer
-to another to construct the draw.
-
-Draw_Looper draws one or more times, modifying the canvas and paint each time.
-Draw_Looper may be used to draw multiple colors or create a colored shadow.
-Set Draw_Looper to nullptr to prevent Draw_Looper from modifying the draw.
-
-#Example
-#Height 128
-    void draw(SkCanvas* canvas) {
-        SkLayerDrawLooper::LayerInfo info;
-        info.fPaintBits = (SkLayerDrawLooper::BitFlags) SkLayerDrawLooper::kColorFilter_Bit;
-        info.fColorMode = SkBlendMode::kSrc;
-        SkLayerDrawLooper::Builder looperBuilder;
-        SkPaint* loopPaint = looperBuilder.addLayer(info);
-        loopPaint->setColor(SK_ColorRED);
-        info.fOffset.set(20, 20);
-        loopPaint = looperBuilder.addLayer(info);
-        loopPaint->setColor(SK_ColorBLUE);
-        SkPaint paint;
-        paint.setDrawLooper(looperBuilder.detach());
-        canvas->drawCircle(50, 50, 50, paint);
-    }
-
-##
-
-#Method SkDrawLooper* getDrawLooper() const
-
-#In Draw_Looper_Methods
-#Line # returns Draw_Looper, multiple layers ##
-#Populate
-
-#Example
-        void draw(SkCanvas* canvas) {
-           SkPaint paint;
-           SkDebugf("nullptr %c= draw looper\n", paint.getDrawLooper() ? '!' : '=');
-           SkLayerDrawLooper::Builder looperBuilder;
-           paint.setDrawLooper(looperBuilder.detach());
-           SkDebugf("nullptr %c= draw looper\n", paint.getDrawLooper() ? '!' : '=');
-        }
-
-        #StdOut
-            nullptr == draw looper
-            nullptr != draw looper
-        ##
-    ##
-
-##
-
-#Method sk_sp<SkDrawLooper> refDrawLooper() const
-
-#In Draw_Looper_Methods
-#Line # references Draw_Looper, multiple layers ##
-#Populate
-
-#Example
-    void draw(SkCanvas* canvas) {
-        SkPaint paint1, paint2;
-        SkLayerDrawLooper::Builder looperBuilder;
-        paint1.setDrawLooper(looperBuilder.detach());
-        SkDebugf("draw looper unique: %s\n", paint1.getDrawLooper()->unique() ? "true" : "false");
-        paint2.setDrawLooper(paint1.refDrawLooper());
-        SkDebugf("draw looper unique: %s\n", paint1.getDrawLooper()->unique() ? "true" : "false");
-    }
-
-        #StdOut
-            draw looper unique: true
-            draw looper unique: false
-        ##
-    ##
-
-##
-
-#Method void setDrawLooper(sk_sp<SkDrawLooper> drawLooper)
-#In Draw_Looper_Methods
-#Line # sets Draw_Looper, multiple layers ##
-#Populate
-
-#Example
-    #Height 128
-        void draw(SkCanvas* canvas) {
-            SkPaint paint;
-            paint.setDrawLooper(SkBlurDrawLooper::Make(0x7FFF0000, 4, -5, -10));
-            paint.setStyle(SkPaint::kStroke_Style);
-            paint.setStrokeWidth(10);
-            paint.setAntiAlias(true);
-            paint.setColor(0x7f0000ff);
-            canvas->drawCircle(70, 70, 50, paint);
-        }
-    ##
-
-##
-
-#Subtopic Draw_Looper_Methods ##
-
-#Subtopic Text_Size
-#Line # overall height in points ##
-
-Text_Size adjusts the overall text size in points.
-Text_Size can be set to any positive value or zero.
-Text_Size defaults to 12.
-Set SkPaintDefaults_TextSize at compile time to change the default setting.
-
-#Example
-#Height 135
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        canvas->drawString("12 point", 10, 20, paint);
-        paint.setTextSize(24);
-        canvas->drawString("24 point", 10, 60, paint);
-        paint.setTextSize(48);
-        canvas->drawString("48 point", 10, 120, paint);
-    }
-##
-
-#Subtopic Text_Size ##
-# ------------------------------------------------------------------------------
-#Subtopic Text_Scale_X
-#Line # text horizontal scale ##
-
-Text_Scale_X adjusts the text horizontal scale.
-Text scaling approximates condensed and expanded type faces when the actual face
-is not available.
-Text_Scale_X can be set to any value.
-Text_Scale_X defaults to 1.
-
-#Example
-#Height 128
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setTextSize(24);
-        paint.setTextScaleX(.8f);
-        canvas->drawString("narrow", 10, 20, paint);
-        paint.setTextScaleX(1);
-        canvas->drawString("normal", 10, 60, paint);
-        paint.setTextScaleX(1.2f);
-        canvas->drawString("wide", 10, 100, paint);
-    }
-##
-
-#Subtopic Text_Scale_X ##
-
-#Subtopic Text_Skew_X
-#Line # text horizontal slant ##
-
-
-Text_Skew_X adjusts the text horizontal slant.
-Text skewing approximates italic and oblique type faces when the actual face
-is not available.
-Text_Skew_X can be set to any value.
-Text_Skew_X defaults to 0.
-
-#Example
-#Height 128
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setTextSize(24);
-        paint.setTextSkewX(-.25f);
-        canvas->drawString("right-leaning", 10, 100, paint);
-        paint.setTextSkewX(0);
-        canvas->drawString("normal", 10, 60, paint);
-        paint.setTextSkewX(.25f);
-        canvas->drawString("left-leaning", 10, 20, paint);
-    }
-##
-
-#Subtopic Text_Skew_X ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Text_Encoding
-#Line # text encoded as characters or Glyphs ##
-
-#Example
-#Height 128
-#Description
-First line is encoded in UTF-8.
-Second line is encoded in UTF-16.
-Third line is encoded in UTF-32.
-Fourth line has 16-bit glyph indices.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    const char hello8[] = "Hello" "\xE2" "\x98" "\xBA";
-    const uint16_t hello16[] = { 'H', 'e', 'l', 'l', 'o', 0x263A };
-    const uint32_t hello32[] = { 'H', 'e', 'l', 'l', 'o', 0x263A };
-    paint.setTextSize(24);
-    canvas->drawText(hello8, sizeof(hello8) - 1, 10, 30, paint);
-    paint.setTextEncoding(SkTextEncoding::kUTF16);
-    canvas->drawText(hello16, sizeof(hello16), 10, 60, paint);
-    paint.setTextEncoding(SkTextEncoding::kUTF32);
-    canvas->drawText(hello32, sizeof(hello32), 10, 90, paint);
-    uint16_t glyphs[SK_ARRAY_COUNT(hello32)];
-    SkFont font;
-    font.textToGlyphs(hello32, sizeof(hello32), SkTextEncoding::kUTF32,
-            glyphs, SK_ARRAY_COUNT(hello32));
-    paint.setTextEncoding(kGlyphID_SkTextEncoding);
-    canvas->drawText(glyphs, sizeof(glyphs), 10, 120, paint);
-}
-##
-
-#Subtopic Text_Encoding ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool nothingToDraw() const
-#In Utility
-#Line # returns true if Paint prevents all drawing ##
-#Populate
-
-#Example
-        void draw(SkCanvas* canvas) {
-            auto debugster = [](const char* prefix, const SkPaint& p) -> void {
-                SkDebugf("%s nothing to draw: %s\n", prefix,
-                         p.nothingToDraw() ? "true" : "false");
-            };
-            SkPaint paint;
-            debugster("initial", paint);
-            paint.setBlendMode(SkBlendMode::kDst);
-            debugster("blend dst", paint);
-            paint.setBlendMode(SkBlendMode::kSrcOver);
-            debugster("blend src over", paint);
-            paint.setAlpha(0);
-            debugster("alpha 0", paint);
-        }
-
-        #StdOut
-            initial nothing to draw: false
-            blend dst nothing to draw: true
-            blend src over nothing to draw: false
-            alpha 0 nothing to draw: true
-        #StdOut  ##
-    ##
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Utility
-#Line # rarely called management functions ##
-##
-
-# ------------------------------------------------------------------------------
-
-#Class SkPaint ##
-
-#Topic Paint ##
diff --git a/docs/SkPath_Overview.bmh b/docs/SkPath_Overview.bmh
deleted file mode 100644
index 70ab8bd..0000000
--- a/docs/SkPath_Overview.bmh
+++ /dev/null
@@ -1,169 +0,0 @@
-#Topic Path_Overview
-
-Path contains Lines and Curves which can be stroked or filled. Contour is
-composed of a series of connected Lines and Curves. Path may contain zero,
-one, or more Contours.
-Each Line and Curve are described by Verb, Points, and optional Path_Conic_Weight.
-
-Each pair of connected Lines and Curves share common Point; for instance, Path
-containing two connected Lines are described the Path_Verb sequence:
-SkPath::kMove_Verb, SkPath::kLine_Verb, SkPath::kLine_Verb; and a Point sequence
-with three entries, sharing
-the middle entry as the end of the first Line and the start of the second Line.
-
-Path components Arc, Rect, Round_Rect, Circle, and Oval are composed of
-Lines and Curves with as many Verbs and Points required
-for an exact description. Once added to Path, these components may lose their
-identity; although Path can be inspected to determine if it describes a single
-Rect, Oval, Round_Rect, and so on.
-
-#Example
-#Height 192
-#Description
-Path contains three Contours: Line, Circle, and Quad. Line is stroked but
-not filled. Circle is stroked and filled; Circle stroke forms a loop. Quad
-is stroked and filled, but since it is not closed, Quad does not stroke a loop.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkPath path;
-    path.moveTo(124, 108);
-    path.lineTo(172, 24);
-    path.addCircle(50, 50, 30);
-    path.moveTo(36, 148);
-    path.quadTo(66, 188, 120, 136);
-    canvas->drawPath(path, paint);
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setColor(SK_ColorBLUE);
-    paint.setStrokeWidth(3);
-    canvas->drawPath(path, paint);
-}
-##
-
-Path contains a Path_Fill_Type which determines whether overlapping Contours
-form fills or holes. Path_Fill_Type also determines whether area inside or outside
-Lines and Curves is filled.
-
-#Example
-#Height 192
-#Description
-Path is drawn filled, then stroked, then stroked and filled.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkPath path;
-    path.moveTo(36, 48);
-    path.quadTo(66, 88, 120, 36);
-    canvas->drawPath(path, paint);
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setColor(SK_ColorBLUE);
-    paint.setStrokeWidth(8);
-    canvas->translate(0, 50);
-    canvas->drawPath(path, paint);
-    paint.setStyle(SkPaint::kStrokeAndFill_Style);
-    paint.setColor(SK_ColorRED);
-    canvas->translate(0, 50);
-    canvas->drawPath(path, paint);
-}
-##
-
-Path contents are never shared. Copying Path by value effectively creates
-a new Path independent of the original. Internally, the copy does not duplicate
-its contents until it is edited, to reduce memory use and improve performance.
-
-#Subtopic Contour
-#Alias Path_Contour ##
-#Alias Contour ##
-#Alias Contours ##
-#Line # loop of lines and curves ##
-
-Contour contains one or more Verbs, and as many Points as
-are required to satisfy Path_Verb_Array. First Path_Verb in Path is always
-SkPath::kMove_Verb; each SkPath::kMove_Verb that follows starts a new Contour.
-
-#Example
-#Description
-Each SkPath::moveTo starts a new Contour, and content after SkPath::close()
-also starts a new Contour. Since SkPath::conicTo is not preceded by
-SkPath::moveTo, the first Point of the third Contour starts at the last Point
-of the second Contour.
-##
-#Height 192
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    canvas->drawString("1st contour", 150, 100, paint);
-    canvas->drawString("2nd contour", 130, 160, paint);
-    canvas->drawString("3rd contour", 40, 30, paint);
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkPath path;
-    path.moveTo(124, 108);
-    path.lineTo(172, 24);
-    path.moveTo(36, 148);
-    path.quadTo(66, 188, 120, 136);
-    path.close();
-    path.conicTo(70, 20, 110, 40, 0.6f);
-    canvas->drawPath(path, paint);
-##
-
-If final Path_Verb in Contour is SkPath::kClose_Verb, Line connects Path_Last_Point in
-Contour with first Point. A closed Contour, stroked, draws
-Paint_Stroke_Join at Path_Last_Point and first Point. Without SkPath::kClose_Verb
-as final Verb, Path_Last_Point and first Point are not connected; Contour
-remains open. An open Contour, stroked, draws Paint_Stroke_Cap at
-Path_Last_Point and first Point.
-
-#Example
-#Height 160
-#Description
-Path is drawn stroked, with an open Contour and a closed Contour.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setStrokeWidth(8);
-    SkPath path;
-    path.moveTo(36, 48);
-    path.quadTo(66, 88, 120, 36);
-    canvas->drawPath(path, paint);
-    path.close();
-    canvas->translate(0, 50);
-    canvas->drawPath(path, paint);
-}
-##
-
-#Subtopic Zero_Length
-#Alias Zero_Length_Contour ##
-#Line # consideration when contour has no length ##
-Contour length is distance traveled from first Point to Path_Last_Point,
-plus, if Contour is closed, distance from Path_Last_Point to first Point.
-Even if Contour length is zero, stroked Lines are drawn if Paint_Stroke_Cap
-makes them visible.
-
-#Example
-#Height 64
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setStrokeWidth(8);
-    paint.setStrokeCap(SkPaint::kRound_Cap);
-    SkPath path;
-    path.moveTo(36, 48);
-    path.lineTo(36, 48);
-    canvas->drawPath(path, paint);
-    path.reset();
-    paint.setStrokeCap(SkPaint::kSquare_Cap);
-    path.moveTo(56, 48);
-    path.close();
-    canvas->drawPath(path, paint);
-##
-
-#Subtopic Zero_Length ##
-
-#Subtopic Contour ##
-
-# ------------------------------------------------------------------------------
-
-#Topic Path_Overview ##
diff --git a/docs/SkPath_Reference.bmh b/docs/SkPath_Reference.bmh
deleted file mode 100644
index 11d0b53..0000000
--- a/docs/SkPath_Reference.bmh
+++ /dev/null
@@ -1,4847 +0,0 @@
-#Topic Path
-#Alias Path_Reference ##
-#Alias Paths ##
-
-#Class SkPath
-
-#Code
-#Populate
-##
-
-Paths contain geometry. Paths may be empty, or contain one or more Verbs that
-outline a figure. Path always starts with a move verb to a Cartesian_Coordinate,
-and may be followed by additional verbs that add lines or curves.
-Adding a close verb makes the geometry into a continuous loop, a closed contour.
-Paths may contain any number of contours, each beginning with a move verb.
-
-Path contours may contain only a move verb, or may also contain lines,
-Quadratic_Beziers, Conics, and Cubic_Beziers. Path contours may be open or
-closed.
-
-When used to draw a filled area, Path describes whether the fill is inside or
-outside the geometry. Path also describes the winding rule used to fill
-overlapping contours.
-
-Internally, Path lazily computes metrics likes bounds and convexity. Call
-SkPath::updateBoundsCache to make Path thread safe.
-
-#Subtopic Verb
-#Alias Verbs ##
-#Line # line and curve type ##
-#Enum Verb
-#Line # controls how Path Points are interpreted ##
-
-#Code
-    enum Verb {
-        kMove_Verb,
-        kLine_Verb,
-        kQuad_Verb,
-        kConic_Verb,
-        kCubic_Verb,
-        kClose_Verb,
-        kDone_Verb,
-    };
-##
-
-Verb instructs Path how to interpret one or more Point and optional Conic_Weight;
-manage Contour, and terminate Path.
-
-#Const kMove_Verb 0
-#Line # starts new Contour at next Point ##
-    Consecutive kMove_Verb are preserved but all but the last kMove_Verb is
-    ignored. kMove_Verb after other Verbs implicitly closes the previous Contour
-    if SkPaint::kFill_Style is set when drawn; otherwise, stroke is drawn open.
-    kMove_Verb as the last Verb is preserved but ignored.
-##
-#Const kLine_Verb 1
-#Line # adds Line from Last_Point to next Point ##
-    Line is a straight segment from Point to Point. Consecutive kLine_Verb
-    extend Contour. kLine_Verb at same position as prior kMove_Verb is
-    preserved, and draws Point if SkPaint::kStroke_Style is set, and
-    SkPaint::Cap is SkPaint::kSquare_Cap or SkPaint::kRound_Cap. kLine_Verb
-    at same position as prior line or curve Verb is preserved but is ignored.
-##
-#Const kQuad_Verb 2
-#Line # adds Quad from Last_Point ##
-    Adds Quad from Last_Point, using control Point, and end Point.
-    Quad is a parabolic section within tangents from Last_Point to control Point,
-    and control Point to end Point.
-##
-#Const kConic_Verb 3
-#Line # adds Conic from Last_Point ##
-    Adds Conic from Last_Point, using control Point, end Point, and Conic_Weight.
-    Conic is a elliptical, parabolic, or hyperbolic section within tangents
-    from Last_Point to control Point, and control Point to end Point, constrained
-    by Conic_Weight. Conic_Weight less than one is elliptical; equal to one is
-    parabolic (and identical to Quad); greater than one hyperbolic.
-##
-#Const kCubic_Verb 4
-#Line # adds Cubic from Last_Point ##
-    Adds Cubic from Last_Point, using two control Points, and end Point.
-    Cubic is a third-order Bezier_Curve section within tangents from Last_Point
-    to first control Point, and from second control Point to end Point.
-##
-#Const kClose_Verb 5
-#Line # closes Contour ##
-    Closes Contour, connecting Last_Point to kMove_Verb Point. Consecutive
-    kClose_Verb are preserved but only first has an effect. kClose_Verb after
-    kMove_Verb has no effect.
-##
-#Const kDone_Verb 6
-#Line # terminates Path ##
-    Not in Verb_Array, but returned by Path iterator.
-##
-
-Each Verb has zero or more Points stored in Path.
-Path iterator returns complete curve descriptions, duplicating shared Points
-for consecutive entries.
-
-#Table
-#Legend
-# Verb        # Allocated Points # Iterated Points # Weights ##
-##
-# kMove_Verb  # 1                # 1               # 0       ##
-# kLine_Verb  # 1                # 2               # 0       ##
-# kQuad_Verb  # 2                # 3               # 0       ##
-# kConic_Verb # 2                # 3               # 1       ##
-# kCubic_Verb # 3                # 4               # 0       ##
-# kClose_Verb # 0                # 1               # 0       ##
-# kDone_Verb  # --               # 0               # 0       ##
-##
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPath path;
-    path.lineTo(20, 20);
-    path.quadTo(-10, -10, 30, 30);
-    path.close();
-    path.cubicTo(1, 2, 3, 4, 5, 6);
-    path.conicTo(0, 0, 0, 0, 2);
-    uint8_t verbs[7];
-    int count = path.getVerbs(verbs, (int) SK_ARRAY_COUNT(verbs));
-    const char* verbStr[] = { "Move", "Line", "Quad", "Conic", "Cubic", "Close" };
-    SkDebugf("verb count: %d\nverbs: ", count);
-    for (int i = 0; i < count; ++i) {
-        SkDebugf("k%s_Verb ", verbStr[verbs[i]]);
-    }
-    SkDebugf("\n");
-}
-#StdOut
-verb count: 7
-verbs: kMove_Verb kLine_Verb kQuad_Verb kClose_Verb kMove_Verb kCubic_Verb kConic_Verb
-##
-##
-
-#Enum Verb ##
-#Subtopic Verb ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Direction
-#Line # contour orientation, clockwise or counterclockwise ##
-#Alias Directions ##
-
-#Enum Direction
-#Line # sets Contour clockwise or counterclockwise ##
-
-#Code
-    enum Direction : int {
-        kCW_Direction,
-        kCCW_Direction,
-    };
-##
-
-Direction describes whether Contour is clockwise or counterclockwise.
-When Path contains multiple overlapping Contours, Direction together with
-Fill_Type determines whether overlaps are filled or form holes.
-
-Direction also determines how Contour is measured. For instance, dashing
-measures along Path to determine where to start and stop stroke; Direction
-will change dashed results as it steps clockwise or counterclockwise.
-
-Closed Contours like Rect, Round_Rect, Circle, and Oval added with
-kCW_Direction travel clockwise; the same added with kCCW_Direction
-travel counterclockwise.
-
-#Const kCW_Direction 0
-#Line # contour travels clockwise ##
-##
-#Const kCCW_Direction 1
-#Line # contour travels counterclockwise ##
-##
-
-
-#Example
-#Height 100
-void draw(SkCanvas* canvas) {
-    const SkPoint arrow[] = { {40, -5}, {45, 0}, {40, 5} };
-    const SkRect rect = {10, 10, 90, 90};
-    SkPaint rectPaint;
-    rectPaint.setAntiAlias(true);
-    SkPaint textPaint(rectPaint);
-    rectPaint.setStyle(SkPaint::kStroke_Style);
-    SkPaint arrowPaint(rectPaint);
-    SkPath arrowPath;
-    arrowPath.addPoly(arrow, SK_ARRAY_COUNT(arrow), true);
-    arrowPaint.setPathEffect(SkPath1DPathEffect::Make(arrowPath, 320, 0,
-                             SkPath1DPathEffect::kRotate_Style));
-    for (auto direction : { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {
-        canvas->drawRect(rect, rectPaint);
-        for (unsigned start : { 0, 1, 2, 3 } ) {
-           SkPath path;
-           path.addRect(rect, direction, start);
-           canvas->drawPath(path, arrowPaint);
-       }
-       canvas->drawString(SkPath::kCW_Direction == direction ? "CW" : "CCW",  rect.centerX(),
-            rect.centerY(), textPaint);
-       canvas->translate(120, 0);
-    }
-}
-##
-
-#SeeAlso arcTo rArcTo isRect isNestedFillRects addRect addOval
-
-#Enum Direction ##
-#Subtopic Direction ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath()
-#In Constructors
-#Line # constructs with default values ##
-#Populate
-
-#Example
-    SkPath path;
-    SkDebugf("path is " "%s" "empty", path.isEmpty() ? "" : "not ");
-#StdOut
-path is empty
-##
-##
-
-#SeeAlso reset rewind
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath(const SkPath& path)
-#In Constructors
-#Line # makes a shallow copy ##
-#Populate
-
-#Example
-#Description
-    Modifying one path does not effect another, even if they started as copies
-    of each other.
-##
-    SkPath path;
-    path.lineTo(20, 20);
-    SkPath path2(path);
-    path2.close();
-    SkDebugf("path verbs: %d\n", path.countVerbs());
-    SkDebugf("path2 verbs: %d\n", path2.countVerbs());
-    path.reset();
-    SkDebugf("after reset\n" "path verbs: %d\n", path.countVerbs());
-    SkDebugf("path2 verbs: %d\n", path2.countVerbs());
-#StdOut
-path verbs: 2
-path2 verbs: 3
-after reset
-path verbs: 0
-path2 verbs: 3
-##
-##
-
-#SeeAlso operator=(const SkPath& path)
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method ~SkPath()
-
-#Line # decreases Reference_Count of owned objects ##
-#Populate
-
-#Example
-#Description
-delete calls Path destructor, but copy of original in path2 is unaffected.
-##
-void draw(SkCanvas* canvas) {
-    SkPath* path = new SkPath();
-    path->lineTo(20, 20);
-    SkPath path2(*path);
-    delete path;
-    SkDebugf("path2 is " "%s" "empty", path2.isEmpty() ? "" : "not ");
-}
-##
-
-#SeeAlso SkPath() SkPath(const SkPath& path) operator=(const SkPath& path)
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& operator=(const SkPath& path)
-
-#Line # makes a shallow copy ##
-#Populate
-
-#Example
-SkPath path1;
-path1.addRect({10, 20, 30, 40});
-SkPath path2 = path1;
-const SkRect& b1 = path1.getBounds();
-SkDebugf("path1 bounds = %g, %g, %g, %g\n", b1.fLeft, b1.fTop, b1.fRight, b1.fBottom);
-const SkRect& b2 = path2.getBounds();
-SkDebugf("path2 bounds = %g, %g, %g, %g\n", b2.fLeft, b2.fTop, b2.fRight, b2.fBottom);
-#StdOut
-path1 bounds = 10, 20, 30, 40
-path2 bounds = 10, 20, 30, 40
-#StdOut ##
-##
-
-#SeeAlso swap SkPath(const SkPath& path)
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator==(const SkPath& a, const SkPath& b)
-
-#Line # compares Paths for equality ##
-#Populate
-
-#Example
-#Description
-rewind() removes Verb_Array but leaves storage; since storage is not compared,
-Path pair are equivalent.
-##
-void draw(SkCanvas* canvas) {
-    auto debugster = [](const char* prefix, const SkPath& a, const SkPath& b) -> void {
-                SkDebugf("%s one %c= two\n", prefix, a == b ? '=' : '!');
-    };
-    SkPath one;
-    SkPath two;
-    debugster("empty", one, two);
-    one.moveTo(0, 0);
-    debugster("moveTo", one, two);
-    one.rewind();
-    debugster("rewind", one, two);
-    one.moveTo(0, 0);
-    one.reset();
-    debugster("reset", one, two);
-}
-#StdOut
-empty one == two
-moveTo one != two
-rewind one == two
-reset one == two
-##
-##
-
-#SeeAlso operator!=(const SkPath& a, const SkPath& b) operator=(const SkPath& path)
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator!=(const SkPath& a, const SkPath& b)
-
-#Line # compares paths for inequality ##
-#Populate
-
-#Example
-#Description
-Path pair are equal though their convexity is not equal.
-##
-void draw(SkCanvas* canvas) {
-    auto debugster = [](const char* prefix, const SkPath& a, const SkPath& b) -> void {
-                SkDebugf("%s one %c= two\n", prefix, a != b ? '!' : '=');
-    };
-    SkPath one;
-    SkPath two;
-    debugster("empty", one, two);
-    one.addRect({10, 20, 30, 40});
-    two.addRect({10, 20, 30, 40});
-    debugster("add rect", one, two);
-    one.setConvexity(SkPath::kConcave_Convexity);
-    debugster("setConvexity", one, two);
-    SkDebugf("convexity %c=\n", one.getConvexity() == two.getConvexity() ? '=' : '!');
-}
-#StdOut
-empty one == two
-add rect one == two
-setConvexity one == two
-convexity !=
-##
-##
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Property
-#Line # metrics and attributes ##
-##
-
-#Method bool isInterpolatable(const SkPath& compare) const
-#In Property
-#In Interpolate
-#Line # returns if pair contains equal counts of Verb_Array and Weights ##
-#Populate
-
-#Example
-    SkPath path, path2;
-    path.moveTo(20, 20);
-    path.lineTo(40, 40);
-    path.lineTo(20, 20);
-    path.lineTo(40, 40);
-    path.close();
-    path2.addRect({20, 20, 40, 40});
-    SkDebugf("paths are " "%s" "interpolatable", path.isInterpolatable(path2) ? "" : "not ");
-#StdOut
-paths are interpolatable
-##
-##
-
-#SeeAlso isInterpolatable
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Interpolate
-#Line # weighted average of Path pair ##
-##
-
-#Method bool interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const
-#In Interpolate
-#Line # interpolates between Path pair ##
-Interpolates between Paths with Point_Array of equal size.
-Copy Verb_Array and Weights to out, and set out Point_Array to a weighted
-average of this Point_Array and ending Point_Array, using the formula:
-#Formula # (Path Point * weight) + ending Point * (1 - weight) ##.
-
-weight is most useful when between zero (ending Point_Array) and
-one (this Point_Array); will work with values outside of this
-range.
-
-interpolate() returns false and leaves out unchanged if Point_Array is not
-the same size as ending Point_Array. Call isInterpolatable to check Path
-compatibility prior to calling interpolate().
-
-#Param ending  Point_Array averaged with this Point_Array ##
-#Param weight  contribution of this Point_Array, and
-               one minus contribution of ending Point_Array
-##
-#Param out     Path replaced by interpolated averages ##
-
-#Return  true if Paths contain same number of Points ##
-
-#Example
-#Height 60
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkPath path, path2;
-    path.moveTo(20, 20);
-    path.lineTo(40, 40);
-    path.lineTo(20, 40);
-    path.lineTo(40, 20);
-    path.close();
-    path2.addRect({20, 20, 40, 40});
-    for (SkScalar i = 0; i <= 1; i += 1.f / 6) {
-      SkPath interp;
-      path.interpolate(path2, i, &interp);
-      canvas->drawPath(interp, paint);
-      canvas->translate(30, 0);
-    }
-}
-##
-
-#SeeAlso isInterpolatable
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Fill_Type
-#Line # fill rule, normal and inverted ##
-
-#Enum FillType
-#Line # sets winding rule and inverse fill ##
-
-#Code
-    enum FillType {
-        kWinding_FillType,
-        kEvenOdd_FillType,
-        kInverseWinding_FillType,
-        kInverseEvenOdd_FillType,
-    };
-##
-
-Fill_Type selects the rule used to fill Path. Path set to kWinding_FillType
-fills if the sum of Contour edges is not zero, where clockwise edges add one, and
-counterclockwise edges subtract one. Path set to kEvenOdd_FillType fills if the
-number of Contour edges is odd. Each Fill_Type has an inverse variant that
-reverses the rule:
-kInverseWinding_FillType fills where the sum of Contour edges is zero;
-kInverseEvenOdd_FillType fills where the number of Contour edges is even.
-
-#Example
-#Height 100
-#Description
-The top row has two clockwise rectangles. The second row has one clockwise and
-one counterclockwise rectangle. The even-odd variants draw the same. The
-winding variants draw the top rectangle overlap, which has a winding of 2, the
-same as the outer parts of the top rectangles, which have a winding of 1.
-##
-void draw(SkCanvas* canvas) {
-   SkPath path;
-   path.addRect({10, 10, 30, 30}, SkPath::kCW_Direction);
-   path.addRect({20, 20, 40, 40}, SkPath::kCW_Direction);
-   path.addRect({10, 60, 30, 80}, SkPath::kCW_Direction);
-   path.addRect({20, 70, 40, 90}, SkPath::kCCW_Direction);
-   SkPaint strokePaint;
-   strokePaint.setStyle(SkPaint::kStroke_Style);
-   SkRect clipRect = {0, 0, 51, 100};
-   canvas->drawPath(path, strokePaint);
-   SkPaint fillPaint;
-   for (auto fillType : { SkPath::kWinding_FillType, SkPath::kEvenOdd_FillType,
-                      SkPath::kInverseWinding_FillType, SkPath::kInverseEvenOdd_FillType } ) {
-        canvas->translate(51, 0);
-        canvas->save();
-        canvas->clipRect(clipRect);
-        path.setFillType(fillType);
-        canvas->drawPath(path, fillPaint);
-        canvas->restore();
-    }
-}
-##
-
-#Const kWinding_FillType 0
-#Line # is enclosed by a non-zero sum of Contour Directions ##
-##
-#Const kEvenOdd_FillType 1
-#Line # is enclosed by an odd number of Contours ##
-##
-#Const kInverseWinding_FillType 2
-#Line # is enclosed by a zero sum of Contour Directions ##
-##
-#Const kInverseEvenOdd_FillType 3
-#Line # is enclosed by an even number of Contours ##
-##
-
-#Example
-#Height 230
-void draw(SkCanvas* canvas) {
-   SkPath path;
-   path.addRect({20, 10, 80, 70}, SkPath::kCW_Direction);
-   path.addRect({40, 30, 100, 90}, SkPath::kCW_Direction);
-   SkPaint strokePaint;
-   strokePaint.setStyle(SkPaint::kStroke_Style);
-   SkRect clipRect = {0, 0, 128, 128};
-   canvas->drawPath(path, strokePaint);
-   canvas->drawLine({0, 50}, {120, 50}, strokePaint);
-   SkPaint textPaint;
-   textPaint.setAntiAlias(true);
-   SkScalar textHPos[] = { 10, 30, 60, 90, 110 };
-   canvas->drawPosTextH("01210", 5, textHPos, 48, textPaint);
-   textPaint.setTextSize(18);
-   canvas->translate(0, 128);
-   canvas->scale(.5f, .5f);
-   canvas->drawString("inverse", 384, 150, textPaint);
-   SkPaint fillPaint;
-   for (auto fillType : { SkPath::kWinding_FillType, SkPath::kEvenOdd_FillType,
-                      SkPath::kInverseWinding_FillType, SkPath::kInverseEvenOdd_FillType } ) {
-        canvas->save();
-        canvas->clipRect(clipRect);
-        path.setFillType(fillType);
-        canvas->drawPath(path, fillPaint);
-        canvas->restore();
-        canvas->drawString(fillType & 1 ? "even-odd" : "winding", 64, 170, textPaint);
-        canvas->translate(128, 0);
-    }
-}
-##
-
-#SeeAlso SkPaint::Style Direction getFillType setFillType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method FillType getFillType() const
-
-#In Fill_Type
-#Line # returns Fill_Type: winding, even-odd, inverse ##
-#Populate
-
-#Example
-    SkPath path;
-    SkDebugf("default path fill type is %s\n",
-            path.getFillType() == SkPath::kWinding_FillType ? "kWinding_FillType" :
-            path.getFillType() == SkPath::kEvenOdd_FillType ? "kEvenOdd_FillType" :
-            path.getFillType() == SkPath::kInverseWinding_FillType ? "kInverseWinding_FillType" :
-                                                                     "kInverseEvenOdd_FillType");
-#StdOut
-default path fill type is kWinding_FillType
-##
-##
-
-#SeeAlso FillType setFillType isInverseFillType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void setFillType(FillType ft)
-
-#In Fill_Type
-#Line # sets Fill_Type: winding, even-odd, inverse ##
-#Populate
-
-#Example
-#Description
-If empty Path is set to inverse FillType, it fills all pixels.
-##
-#Height 64
-     SkPath path;
-     path.setFillType(SkPath::kInverseWinding_FillType);
-     SkPaint paint;
-     paint.setColor(SK_ColorBLUE);
-     canvas->drawPath(path, paint);
-##
-
-#SeeAlso FillType getFillType toggleInverseFillType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isInverseFillType() const
-
-#In Fill_Type
-#Line # returns if Fill_Type fills outside geometry ##
-#Populate
-
-#Example
-    SkPath path;
-    SkDebugf("default path fill type is inverse: %s\n",
-            path.isInverseFillType() ? "true" : "false");
-#StdOut
-default path fill type is inverse: false
-##
-##
-
-#SeeAlso FillType getFillType setFillType toggleInverseFillType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void toggleInverseFillType()
-
-#In Fill_Type
-#Line # toggles Fill_Type between inside and outside geometry ##
-Replaces FillType with its inverse. The inverse of FillType describes the area
-unmodified by the original FillType.
-
-#Table
-#Legend
-# FillType                 # toggled FillType         ##
-##
-# kWinding_FillType        # kInverseWinding_FillType ##
-# kEvenOdd_FillType        # kInverseEvenOdd_FillType ##
-# kInverseWinding_FillType # kWinding_FillType        ##
-# kInverseEvenOdd_FillType # kEvenOdd_FillType        ##
-##
-
-#Example
-#Description
-Path drawn normally and through its inverse touches every pixel once.
-##
-#Height 100
-SkPath path;
-SkPaint paint;
-paint.setColor(SK_ColorRED);
-paint.setTextSize(80);
-paint.getTextPath("ABC", 3, 20, 80, &path);
-canvas->drawPath(path, paint);
-path.toggleInverseFillType();
-paint.setColor(SK_ColorGREEN);
-canvas->drawPath(path, paint);
-##
-
-#SeeAlso FillType getFillType setFillType isInverseFillType
-
-##
-
-#Subtopic Fill_Type ##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Convexity
-#Line # if Path is concave or convex ##
-
-#Enum Convexity
-#Line # returns if Path is convex or concave ##
-
-#Code
-    enum Convexity : uint8_t {
-        kUnknown_Convexity,
-        kConvex_Convexity,
-        kConcave_Convexity,
-    };
-##
-
-Path is convex if it contains one Contour and Contour loops no more than
-360 degrees, and Contour angles all have same Direction. Convex Path
-may have better performance and require fewer resources on GPU_Surface.
-
-Path is concave when either at least one Direction change is clockwise and
-another is counterclockwise, or the sum of the changes in Direction is not 360
-degrees.
-
-Initially Path Convexity is kUnknown_Convexity. Path Convexity is computed
-if needed by destination Surface.
-
-#Const kUnknown_Convexity 0
-#Line # indicates Convexity has not been determined ##
-##
-#Const kConvex_Convexity 1
-#Line # one Contour made of a simple geometry without indentations ##
-##
-#Const kConcave_Convexity 2
-#Line # more than one Contour, or a geometry with indentations ##
-##
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkPoint quad[] = {{70, 70}, {20, 20}, {120, 20}, {120, 120}};
-    const char* labels[] = { "unknown", "convex", "concave" };
-    for (SkScalar x : { 40, 100 } ) {
-        SkPath path;
-        quad[0].fX = x;
-        path.addPoly(quad, SK_ARRAY_COUNT(quad), true);
-        canvas->drawPath(path, paint);
-        canvas->drawString(labels[(int) path.getConvexity()], 30, 100, paint);
-        canvas->translate(100, 100);
-    }
-}
-##
-
-#SeeAlso Contour Direction getConvexity getConvexityOrUnknown setConvexity isConvex
-
-#Enum Convexity ##
-
-#Method Convexity getConvexity() const
-
-#In Convexity
-#Line # returns geometry convexity, computing if necessary ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    auto debugster = [](const char* prefix, const SkPath& path) -> void {
-        SkDebugf("%s path convexity is %s\n", prefix,
-                SkPath::kUnknown_Convexity == path.getConvexity() ? "unknown" :
-                SkPath::kConvex_Convexity == path.getConvexity() ? "convex" : "concave"); };
-    SkPath path;
-    debugster("initial", path);
-    path.lineTo(50, 0);
-    debugster("first line", path);
-    path.lineTo(50, 50);
-    debugster("second line", path);
-    path.lineTo(100, 50);
-    debugster("third line", path);
-}
-##
-
-#SeeAlso Convexity Contour Direction getConvexityOrUnknown setConvexity isConvex
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method Convexity getConvexityOrUnknown() const
-
-#In Convexity
-#Line # returns geometry convexity if known ##
-#Populate
-
-#Example
-#Description
-Convexity is unknown unless getConvexity is called without a subsequent call
-that alters the path.
-##
-void draw(SkCanvas* canvas) {
-    auto debugster = [](const char* prefix, const SkPath& path) -> void {
-        SkDebugf("%s path convexity is %s\n", prefix,
-            SkPath::kUnknown_Convexity == path.getConvexityOrUnknown() ? "unknown" :
-            SkPath::kConvex_Convexity == path.getConvexityOrUnknown() ? "convex" : "concave"); };
-    SkPath path;
-    debugster("initial", path);
-    path.lineTo(50, 0);
-    debugster("first line", path);
-    path.getConvexity();
-    path.lineTo(50, 50);
-    debugster("second line", path);
-    path.lineTo(100, 50);
-    path.getConvexity();
-    debugster("third line", path);
-}
-##
-
-#SeeAlso Convexity Contour Direction getConvexity setConvexity isConvex
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void setConvexity(Convexity convexity)
-
-#In Convexity
-#Line # sets if geometry is convex to avoid future computation ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    auto debugster = [](const char* prefix, const SkPath& path) -> void {
-        SkDebugf("%s path convexity is %s\n", prefix,
-                SkPath::kUnknown_Convexity == path.getConvexity() ? "unknown" :
-                SkPath::kConvex_Convexity == path.getConvexity() ? "convex" : "concave"); };
-        SkPoint quad[] = {{70, 70}, {20, 20}, {120, 20}, {120, 120}};
-        SkPath path;
-        path.addPoly(quad, SK_ARRAY_COUNT(quad), true);
-        debugster("initial", path);
-        path.setConvexity(SkPath::kConcave_Convexity);
-        debugster("after forcing concave", path);
-        path.setConvexity(SkPath::kUnknown_Convexity);
-        debugster("after forcing unknown", path);
-}
-##
-
-#SeeAlso Convexity Contour Direction getConvexity getConvexityOrUnknown isConvex
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isConvex() const
-
-#In Convexity
-#Line # returns if geometry is convex ##
-#Populate
-
-#Example
-#Description
-Concave shape is erroneously considered convex after a forced call to
-setConvexity.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkPoint quad[] = {{70, 70}, {20, 20}, {120, 20}, {120, 120}};
-    for (SkScalar x : { 40, 100 } ) {
-        SkPath path;
-        quad[0].fX = x;
-        path.addPoly(quad, SK_ARRAY_COUNT(quad), true);
-        path.setConvexity(SkPath::kConvex_Convexity);
-        canvas->drawPath(path, paint);
-        canvas->drawString(path.isConvex() ? "convex" : "not convex", 30, 100, paint);
-        canvas->translate(100, 100);
-    }
-}
-##
-
-#SeeAlso Convexity Contour Direction getConvexity getConvexityOrUnknown setConvexity
-
-##
-
-#Subtopic Convexity ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isOval(SkRect* bounds) const
-#In Property
-#Line # returns if describes Oval ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkPath path;
-    path.addOval({20, 20, 220, 220});
-    SkRect bounds;
-    if (path.isOval(&bounds)) {
-        paint.setColor(0xFF9FBFFF);
-        canvas->drawRect(bounds, paint);
-    }
-    paint.setColor(0x3f000000);
-    canvas->drawPath(path, paint);
-}
-##
-
-#SeeAlso Oval addCircle addOval
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isRRect(SkRRect* rrect) const
-#In Property
-#Line # returns if describes Round_Rect ##
-#Populate
-
-#Example
-#Description
-Draw rounded rectangle and its bounds.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkPath path;
-    path.addRRect(SkRRect::MakeRectXY({20, 20, 220, 220}, 30, 50));
-    SkRRect rrect;
-    if (path.isRRect(&rrect)) {
-        const SkRect& bounds = rrect.rect();
-        paint.setColor(0xFF9FBFFF);
-        canvas->drawRect(bounds, paint);
-    }
-    paint.setColor(0x3f000000);
-    canvas->drawPath(path, paint);
-}
-##
-
-#SeeAlso Round_Rect addRoundRect addRRect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& reset()
-#In Constructors
-#Line # removes Verb_Array, Point_Array, and Weights; frees memory ##
-#Populate
-
-#Example
-   SkPath path1, path2;
-   path1.setFillType(SkPath::kInverseWinding_FillType);
-   path1.addRect({10, 20, 30, 40});
-   SkDebugf("path1 %c= path2\n", path1 == path2 ? '=' : '!');
-   path1.reset();
-   SkDebugf("path1 %c= path2\n", path1 == path2 ? '=' : '!');
-##
-
-#SeeAlso rewind()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& rewind()
-#In Constructors
-#Line # removes Verb_Array, Point_Array, and Weights, keeping memory ##
-#Populate
-
-#Example
-#Description
-Although path1 retains its internal storage, it is indistinguishable from
-a newly initialized path.
-##
-   SkPath path1, path2;
-   path1.setFillType(SkPath::kInverseWinding_FillType);
-   path1.addRect({10, 20, 30, 40});
-   SkDebugf("path1 %c= path2\n", path1 == path2 ? '=' : '!');
-   path1.rewind();
-   SkDebugf("path1 %c= path2\n", path1 == path2 ? '=' : '!');
-##
-
-#SeeAlso reset()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isEmpty() const
-#In Property
-#Line # returns if verb count is zero ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    auto debugster = [](const char* prefix, const SkPath& path) -> void {
-        SkDebugf("%s path is %s" "empty\n", prefix, path.isEmpty() ? "" : "not ");
-    };
-    SkPath path;
-    debugster("initial", path);
-    path.moveTo(0, 0);
-    debugster("after moveTo", path);
-    path.rewind();
-    debugster("after rewind", path);
-    path.lineTo(0, 0);
-    debugster("after lineTo", path);
-    path.reset();
-    debugster("after reset", path);
-}
-#StdOut
-initial path is empty
-after moveTo path is not empty
-after rewind path is empty
-after lineTo path is not empty
-after reset path is empty
-##
-##
-
-#SeeAlso SkPath() reset() rewind()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isLastContourClosed() const
-#In Property
-#Line # returns if final Contour forms a loop ##
-#Populate
-
-#Example
-#Description
-close() has no effect if Path is empty; isLastContourClosed() returns
-false until Path has geometry followed by close().
-##
-void draw(SkCanvas* canvas) {
-    auto debugster = [](const char* prefix, const SkPath& path) -> void {
-        SkDebugf("%s last contour is %s" "closed\n", prefix,
-                 path.isLastContourClosed() ? "" : "not ");
-    };
-    SkPath path;
-    debugster("initial", path);
-    path.close();
-    debugster("after close", path);
-    path.lineTo(0, 0);
-    debugster("after lineTo", path);
-    path.close();
-    debugster("after close", path);
-}
-#StdOut
-initial last contour is not closed
-after close last contour is not closed
-after lineTo last contour is not closed
-after close last contour is closed
-##
-##
-
-#SeeAlso close()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isFinite() const
-#In Property
-#Line # returns if all Point values are finite ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    auto debugster = [](const char* prefix, const SkPath& path) -> void {
-        SkDebugf("%s path is %s" "finite\n", prefix, path.isFinite() ? "" : "not ");
-    };
-    SkPath path;
-    debugster("initial", path);
-    path.lineTo(SK_ScalarMax, SK_ScalarMax);
-    debugster("after line", path);
-    SkMatrix matrix;
-    matrix.setScale(2, 2);
-    path.transform(matrix);
-    debugster("after scale", path);
-}
-#StdOut
-initial path is finite
-after line path is finite
-after scale path is not finite
-##
-##
-
-#SeeAlso SkScalar
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isVolatile() const
-#In Property
-#In Volatile
-#Line # returns if Device should not cache ##
-#Populate
-
-#Example
-    SkPath path;
-    SkDebugf("volatile by default is %s\n", path.isVolatile() ? "true" : "false");
-#StdOut
-volatile by default is false
-##
-##
-
-#SeeAlso setIsVolatile
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Volatile
-#Line # caching attribute ##
-##
-
-#Method void setIsVolatile(bool isVolatile)
-#In Volatile
-#Line # sets if Device should not cache ##
-#Populate
-
-#Example
-#Height 50
-#Width 50
-    SkPaint paint;
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkPath path;
-    path.setIsVolatile(true);
-    path.lineTo(40, 40);
-    canvas->drawPath(path, paint);
-    path.rewind();
-    path.moveTo(0, 40);
-    path.lineTo(40, 0);
-    canvas->drawPath(path, paint);
-##
-
-#ToDo tie example to bench to show how volatile affects speed or dm to show resource usage ##
-
-#SeeAlso isVolatile
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static bool IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact)
-#In Property
-#Line # returns if Line is very small ##
-#Populate
-
-#Example
-#Description
-As single precision floats, 100 and 100.000001 have the same bit representation,
-and are exactly equal. 100 and 100.0001 have different bit representations, and
-are not exactly equal, but are nearly equal.
-##
-void draw(SkCanvas* canvas) {
-    SkPoint points[] = { {100, 100}, {100.000001f, 100.000001f}, {100.0001f, 100.0001f} };
-    for (size_t i = 0; i < SK_ARRAY_COUNT(points) - 1; ++i) {
-        for (bool exact : { false, true } ) {
-            SkDebugf("line from (%1.8g,%1.8g) to (%1.8g,%1.8g) is %s" "degenerate, %s\n",
-                    points[i].fX, points[i].fY, points[i + 1].fX, points[i + 1].fY,
-                    SkPath::IsLineDegenerate(points[i], points[i + 1], exact)
-                    ? "" : "not ", exact ? "exactly" : "nearly");
-        }
-    }
-}
-#StdOut
-line from (100,100) to (100,100) is degenerate, nearly
-line from (100,100) to (100,100) is degenerate, exactly
-line from (100,100) to (100.0001,100.0001) is degenerate, nearly
-line from (100,100) to (100.0001,100.0001) is not degenerate, exactly
-#StdOut ##
-##
-
-#SeeAlso IsQuadDegenerate IsCubicDegenerate
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static bool IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
-                                 const SkPoint& p3, bool exact)
-#In Property
-#Line # returns if Quad is very small ##
-#Populate
-
-#Example
-#Description
-As single precision floats: 100, 100.00001, and 100.00002 have different bit representations
-but nearly the same value. Translating all three by 1000 gives them the same bit representation;
-the fractional portion of the number can not be represented by the float and is lost.
-##
-void draw(SkCanvas* canvas) {
-    auto debugster = [](const SkPath& path, bool exact) -> void {
-        SkDebugf("quad (%1.8g,%1.8g), (%1.8g,%1.8g), (%1.8g,%1.8g) is %s" "degenerate, %s\n",
-            path.getPoint(0).fX, path.getPoint(0).fY, path.getPoint(1).fX,
-            path.getPoint(1).fY, path.getPoint(2).fX, path.getPoint(2).fY,
-            SkPath::IsQuadDegenerate(path.getPoint(0), path.getPoint(1), path.getPoint(2), exact) ?
-            "" : "not ", exact ? "exactly" : "nearly");
-    };
-    SkPath path, offset;
-    path.moveTo({100, 100});
-    path.quadTo({100.00001f, 100.00001f}, {100.00002f, 100.00002f});
-    offset.addPath(path, 1000, 1000);
-    for (bool exact : { false, true } ) {
-        debugster(path, exact);
-        debugster(offset, exact);
-    }
-}
-#StdOut
-quad (100,100), (100.00001,100.00001), (100.00002,100.00002) is degenerate, nearly
-quad (1100,1100), (1100,1100), (1100,1100) is degenerate, nearly
-quad (100,100), (100.00001,100.00001), (100.00002,100.00002) is not degenerate, exactly
-quad (1100,1100), (1100,1100), (1100,1100) is degenerate, exactly
-#StdOut ##
-##
-
-#SeeAlso IsLineDegenerate IsCubicDegenerate
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static bool IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
-                                  const SkPoint& p3, const SkPoint& p4, bool exact)
-#In Property
-#Line # returns if Cubic is very small ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPoint points[] = {{1, 0}, {0, 0}, {0, 0}, {0, 0}};
-    SkScalar step = 1;
-    SkScalar prior, length = 0, degenerate = 0;
-    do {
-        prior = points[0].fX;
-        step /= 2;
-        if (SkPath::IsCubicDegenerate(points[0], points[1], points[2], points[3], false)) {
-            degenerate = prior;
-            points[0].fX += step;
-        } else {
-            length = prior;
-            points[0].fX -= step;
-        }
-    } while (prior != points[0].fX);
-    SkDebugf("%1.8g is degenerate\n", degenerate);
-    SkDebugf("%1.8g is length\n", length);
-}
-#StdOut
-0.00024414062 is degenerate
-0.00024414065 is length
-#StdOut ##
-##
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isLine(SkPoint line[2]) const
-#In Property
-#Line # returns if describes Line ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    auto debugster = [](const char* prefix, const SkPath& path) -> void {
-        SkPoint line[2];
-        if (path.isLine(line)) {
-            SkDebugf("%s is line (%1.8g,%1.8g) (%1.8g,%1.8g)\n", prefix,
-                 line[0].fX, line[0].fY, line[1].fX, line[1].fY);
-        } else {
-            SkDebugf("%s is not line\n", prefix);
-        }
-    };
-    SkPath path;
-    debugster("empty", path);
-    path.lineTo(0, 0);
-    debugster("zero line", path);
-    path.rewind();
-    path.moveTo(10, 10);
-    path.lineTo(20, 20);
-    debugster("line", path);
-    path.moveTo(20, 20);
-    debugster("second move", path);
-}
-#StdOut
-empty is not line
-zero line is line (0,0) (0,0)
-line is line (10,10) (20,20)
-second move is not line
-##
-##
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Point_Array
-#Line # end points and control points for lines and curves ##
-#Substitute SkPoint array
-
-Point_Array contains Points satisfying the allocated Points for
-each Verb in Verb_Array. For instance, Path containing one Contour with Line
-and Quad is described by Verb_Array: kMove_Verb, kLine_Verb, kQuad_Verb; and
-one Point for move, one Point for Line, two Points for Quad; totaling four Points.
-
-Point_Array may be read directly from Path with getPoints, or inspected with
-getPoint, with Iter, or with RawIter.
-
-#Method int getPoints(SkPoint points[], int max) const
-
-#In Point_Array
-#Line # returns Point_Array ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    auto debugster = [](const char* prefix, const SkPath& path, SkPoint* points, int max) -> void {
-         int count = path.getPoints(points, max);
-         SkDebugf("%s point count: %d  ", prefix, count);
-         for (int i = 0; i < SkTMin(count, max) && points; ++i) {
-             SkDebugf("(%1.8g,%1.8g) ", points[i].fX, points[i].fY);
-         }
-         SkDebugf("\n");
-    };
-    SkPath path;
-    path.lineTo(20, 20);
-    path.lineTo(-10, -10);
-    SkPoint points[3];
-    debugster("no points",  path, nullptr, 0);
-    debugster("zero max",  path, points, 0);
-    debugster("too small",  path, points, 2);
-    debugster("just right",  path, points, path.countPoints());
-}
-#StdOut
-no points point count: 3
-zero max point count: 3
-too small point count: 3  (0,0) (20,20)
-just right point count: 3  (0,0) (20,20) (-10,-10)
-##
-##
-
-#SeeAlso countPoints getPoint
-##
-
-#Method int countPoints() const
-
-#In Point_Array
-#Line # returns Point_Array length ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    auto debugster = [](const char* prefix, const SkPath& path) -> void {
-         SkDebugf("%s point count: %d\n", prefix, path.countPoints());
-    };
-    SkPath path;
-    debugster("empty", path);
-    path.lineTo(0, 0);
-    debugster("zero line", path);
-    path.rewind();
-    path.moveTo(10, 10);
-    path.lineTo(20, 20);
-    debugster("line", path);
-    path.moveTo(20, 20);
-    debugster("second move", path);
-}
-#StdOut
-empty point count: 0
-zero line point count: 2
-line point count: 2
-second move point count: 3
-##
-##
-
-#SeeAlso getPoints
-##
-
-#Method SkPoint getPoint(int index) const
-
-#In Point_Array
-#Line # returns entry from Point_Array ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPath path;
-    path.lineTo(20, 20);
-    path.offset(-10, -10);
-    for (int i= 0; i < path.countPoints(); ++i) {
-         SkDebugf("point %d: (%1.8g,%1.8g)\n", i, path.getPoint(i).fX, path.getPoint(i).fY);
-    }
-}
-#StdOut
-point 0: (-10,-10)
-point 1: (10,10)
-##
-##
-
-#SeeAlso countPoints getPoints
-##
-
-
-#Subtopic Point_Array ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Verb_Array
-#Line # line and curve type for points ##
-
-Verb_Array always starts with kMove_Verb.
-If kClose_Verb is not the last entry, it is always followed by kMove_Verb;
-the quantity of kMove_Verb equals the Contour count.
-Verb_Array does not include or count kDone_Verb; it is a convenience
-returned when iterating through Verb_Array.
-
-Verb_Array may be read directly from Path with getVerbs, or inspected with Iter,
-or with RawIter.
-
-#Method int countVerbs() const
-
-#In Verb_Array
-#Line # returns Verb_Array length ##
-#Populate
-
-#Example
-SkPath path;
-SkDebugf("empty verb count: %d\n", path.countVerbs());
-path.addRoundRect({10, 20, 30, 40}, 5, 5);
-SkDebugf("round rect verb count: %d\n", path.countVerbs());
-#StdOut
-empty verb count: 0
-round rect verb count: 10
-##
-##
-
-#SeeAlso getVerbs Iter RawIter
-
-##
-
-#Method int getVerbs(uint8_t verbs[], int max) const
-
-#In Verb_Array
-#Line # returns Verb_Array ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    auto debugster = [](const char* prefix, const SkPath& path, uint8_t* verbs, int max) -> void {
-         int count = path.getVerbs(verbs, max);
-         SkDebugf("%s verb count: %d  ", prefix, count);
-         const char* verbStr[] = { "move", "line", "quad", "conic", "cubic", "close" };
-         for (int i = 0; i < SkTMin(count, max) && verbs; ++i) {
-             SkDebugf("%s ", verbStr[verbs[i]]);
-         }
-         SkDebugf("\n");
-    };
-    SkPath path;
-    path.lineTo(20, 20);
-    path.lineTo(-10, -10);
-    uint8_t verbs[3];
-    debugster("no verbs",  path, nullptr, 0);
-    debugster("zero max",  path, verbs, 0);
-    debugster("too small",  path, verbs, 2);
-    debugster("just right",  path, verbs, path.countVerbs());
-}
-#StdOut
-no verbs verb count: 3
-zero max verb count: 3
-too small verb count: 3  move line
-just right verb count: 3  move line line
-##
-##
-
-#SeeAlso countVerbs getPoints Iter RawIter
-##
-
-#Subtopic Verb_Array ##
-
-# ------------------------------------------------------------------------------
-
-#Method void swap(SkPath& other)
-#In Operators
-#Line # exchanges Path pair ##
-#Populate
-
-#Example
-SkPath path1, path2;
-path1.addRect({10, 20, 30, 40});
-path1.swap(path2);
-const SkRect& b1 = path1.getBounds();
-SkDebugf("path1 bounds = %g, %g, %g, %g\n", b1.fLeft, b1.fTop, b1.fRight, b1.fBottom);
-const SkRect& b2 = path2.getBounds();
-SkDebugf("path2 bounds = %g, %g, %g, %g\n", b2.fLeft, b2.fTop, b2.fRight, b2.fBottom);
-#StdOut
-path1 bounds = 0, 0, 0, 0
-path2 bounds = 10, 20, 30, 40
-#StdOut ##
-##
-
-#SeeAlso operator=(const SkPath& path)
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method const SkRect& getBounds() const
-#In Property
-#Line # returns maximum and minimum of Point_Array ##
-#Populate
-
-#Example
-#Description
-Bounds of upright Circle can be predicted from center and radius.
-Bounds of rotated Circle includes control Points outside of filled area.
-##
-    auto debugster = [](const char* prefix, const SkPath& path) -> void {
-            const SkRect& bounds = path.getBounds();
-            SkDebugf("%s bounds = %g, %g, %g, %g\n", prefix,
-                     bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
-    };
-    SkPath path;
-    debugster("empty", path);
-    path.addCircle(50, 45, 25);
-    debugster("circle", path);
-    SkMatrix matrix;
-    matrix.setRotate(45, 50, 45);
-    path.transform(matrix);
-    debugster("rotated circle", path);
-#StdOut
-empty bounds = 0, 0, 0, 0
-circle bounds = 25, 20, 75, 70
-rotated circle bounds = 14.6447, 9.64466, 85.3553, 80.3553
-##
-##
-
-#SeeAlso computeTightBounds updateBoundsCache
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Utility
-#Line # rarely called management functions ##
-##
-
-#Method void updateBoundsCache() const
-#In Utility
-#Line # refreshes result of getBounds ##
-#Populate
-
-#Example
-    double times[2] = { 0, 0 };
-    for (int i = 0; i < 10000; ++i) {
-      SkPath path;
-      for (int j = 1; j < 100; ++ j) {
-        path.addCircle(50 + j, 45 + j, 25 + j);
-      }
-      if (1 & i) {
-        path.updateBoundsCache();
-      }
-      double start = SkTime::GetNSecs();
-      (void) path.getBounds();
-      times[1 & i] += SkTime::GetNSecs() - start;
-    }
-    SkDebugf("uncached avg: %g ms\n", times[0] * 1e-6);
-    SkDebugf("cached avg: %g ms\n", times[1] * 1e-6);
-#StdOut
-#Volatile
-uncached avg: 0.18048 ms
-cached avg: 0.182784 ms
-##
-##
-
-#SeeAlso getBounds
-#ToDo the results don't make sense, need to profile to figure this out ##
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkRect computeTightBounds() const
-#In Property
-#Line # returns extent of geometry ##
-#Populate
-
-#Example
-    auto debugster = [](const char* prefix, const SkPath& path) -> void {
-            const SkRect& bounds = path.computeTightBounds();
-            SkDebugf("%s bounds = %g, %g, %g, %g\n", prefix,
-                     bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
-    };
-    SkPath path;
-    debugster("empty", path);
-    path.addCircle(50, 45, 25);
-    debugster("circle", path);
-    SkMatrix matrix;
-    matrix.setRotate(45, 50, 45);
-    path.transform(matrix);
-    debugster("rotated circle", path);
-#StdOut
-empty bounds = 0, 0, 0, 0
-circle bounds = 25, 20, 75, 70
-rotated circle bounds = 25, 20, 75, 70
-##
-##
-
-#SeeAlso getBounds
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool conservativelyContainsRect(const SkRect& rect) const
-#In Property
-#Line # returns true if Rect may be inside ##
-#Populate
-
-#Example
-#Height 140
-#Description
-Rect is drawn in blue if it is contained by red Path.
-##
-void draw(SkCanvas* canvas) {
-    SkPath path;
-    path.addRoundRect({10, 20, 54, 120}, 10, 20);
-    SkRect tests[] = {
-      { 10, 40, 54, 80 },
-      { 25, 20, 39, 120 },
-      { 15, 25, 49, 115 },
-      { 13, 27, 51, 113 },
-    };
-    for (unsigned i = 0; i < SK_ARRAY_COUNT(tests); ++i) {
-      SkPaint paint;
-      paint.setColor(SK_ColorRED);
-      canvas->drawPath(path, paint);
-      bool rectInPath = path.conservativelyContainsRect(tests[i]);
-      paint.setColor(rectInPath ? SK_ColorBLUE : SK_ColorBLACK);
-      canvas->drawRect(tests[i], paint);
-      canvas->translate(64, 0);
-    }
-}
-##
-
-#SeeAlso contains Op Rect Convexity
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void incReserve(int extraPtCount)
-#In Utility
-#Line # reserves space for additional data ##
-#Populate
-
-#Example
-#Height 192
-void draw(SkCanvas* canvas) {
-    auto addPoly = [](int sides, SkScalar size, SkPath* path) -> void {
-        path->moveTo(size, 0);
-        for (int i = 1; i < sides; i++) {
-            SkScalar c, s = SkScalarSinCos(SK_ScalarPI * 2 * i / sides, &c);
-            path->lineTo(c * size, s * size);
-        }
-        path->close();
-    };
-    SkPath path;
-    path.incReserve(3 + 4 + 5 + 6 + 7 + 8 + 9);
-    for (int sides = 3; sides < 10; ++sides) {
-       addPoly(sides, sides, &path);
-    }
-    SkMatrix matrix;
-    matrix.setScale(10, 10, -10, -10);
-    path.transform(matrix);
-    SkPaint paint;
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawPath(path, paint);
-}
-##
-
-#SeeAlso Point_Array
-
-##
-
-#Method void shrinkToFit()
-#In Utility
-#Line # removes unused reserved space ##
-#Populate
-
-#NoExample
-#Height 192
-void draw(SkCanvas* canvas) {
-    SkPath skinnyPath;
-    skinnyPath.lineTo(100, 100);
-    skinnyPath.shrinkToFit();
-
-    SkDebugf("no wasted Path capacity in my house!\n");
-}
-##
-
-#SeeAlso incReserve
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Build
-#Line # adds points and verbs to path ##
-##
-
-#Method SkPath& moveTo(SkScalar x, SkScalar y)
-#In Build
-#Line # starts Contour ##
-#Populate
-
-#Example
-    #Width 140
-    #Height 100
-    void draw(SkCanvas* canvas) {
-        SkRect rect = { 20, 20, 120, 80 };
-        SkPath path;
-        path.addRect(rect);
-        path.moveTo(rect.fLeft, rect.fTop);
-        path.lineTo(rect.fRight, rect.fBottom);
-        path.moveTo(rect.fLeft, rect.fBottom);
-        path.lineTo(rect.fRight, rect.fTop);
-        SkPaint paint;
-        paint.setStyle(SkPaint::kStroke_Style);
-        canvas->drawPath(path, paint);
-    }
-##
-
-#SeeAlso Contour lineTo rMoveTo quadTo conicTo cubicTo close()
-
-##
-
-#Method SkPath& moveTo(const SkPoint& p)
-#Populate
-
-#Example
-    #Width 128
-    #Height 128
-void draw(SkCanvas* canvas) {
-    SkPoint data[][3] = {{{30,40},{60,60},{90,30}}, {{30,120},{60,100},{90,120}},
-                         {{60,100},{60,40},{70,30}}, {{60,40},{50,20},{70,30}}};
-    SkPath path;
-    for (unsigned i = 0; i < SK_ARRAY_COUNT(data); ++i) {
-        path.moveTo(data[i][0]);
-        path.lineTo(data[i][1]);
-        path.lineTo(data[i][2]);
-    }
-    SkPaint paint;
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawPath(path, paint);
-}
-##
-
-#SeeAlso Contour lineTo rMoveTo quadTo conicTo cubicTo close()
-
-##
-
-#Method SkPath& rMoveTo(SkScalar dx, SkScalar dy)
-#In Build
-#Line # starts Contour relative to Last_Point ##
-#Populate
-
-#Example
-    #Height 100
-    SkPath path;
-    path.addRect({20, 20, 80, 80}, SkPath::kCW_Direction, 2);
-    path.rMoveTo(25, 2);
-    SkVector arrow[] = {{0, -4}, {-20, 0}, {0, -3}, {-5, 5}, {5, 5}, {0, -3}, {20, 0}};
-    for (unsigned i = 0; i < SK_ARRAY_COUNT(arrow); ++i) {
-        path.rLineTo(arrow[i].fX, arrow[i].fY);
-    }
-    SkPaint paint;
-    canvas->drawPath(path, paint);
-    SkPoint lastPt;
-    path.getLastPt(&lastPt);
-    canvas->drawString("start", lastPt.fX, lastPt.fY, paint);
-##
-
-#SeeAlso Contour lineTo moveTo quadTo conicTo cubicTo close()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& lineTo(SkScalar x, SkScalar y)
-#In Build
-#Line # appends Line ##
-#Populate
-
-#Example
-#Height 100
-###$
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setTextSize(72);
-    canvas->drawString("#", 120, 80, paint);
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setStrokeWidth(5);
-    SkPath path;
-    SkPoint hash[] = {{58, 28}, {43, 80}, {37, 45}, {85, 45}};
-    SkVector offsets[] = {{0, 0}, {17, 0}, {0, 0}, {-5, 17}};
-    unsigned o = 0;
-    for (unsigned i = 0; i < SK_ARRAY_COUNT(hash); i += 2) {
-        for (unsigned j = 0; j < 2; o++, j++) {
-            path.moveTo(hash[i].fX + offsets[o].fX, hash[i].fY + offsets[o].fY);
-            path.lineTo(hash[i + 1].fX + offsets[o].fX, hash[i + 1].fY + offsets[o].fY);
-        }
-    }
-    canvas->drawPath(path, paint);
-}
-$$$#
-##
-
-#SeeAlso Contour moveTo rLineTo addRect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& lineTo(const SkPoint& p)
-#Populate
-
-#Example
-#Height 100
-    SkPath path;
-    SkVector oxo[] = {{25, 25}, {35, 35}, {25, 35}, {35, 25},
-                      {40, 20}, {40, 80}, {60, 20}, {60, 80},
-                      {20, 40}, {80, 40}, {20, 60}, {80, 60}};
-    for (unsigned i = 0; i < SK_ARRAY_COUNT(oxo); i += 2) {
-        path.moveTo(oxo[i]);
-        path.lineTo(oxo[i + 1]);
-    }
-    SkPaint paint;
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawPath(path, paint);
-##
-
-#SeeAlso Contour moveTo rLineTo addRect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& rLineTo(SkScalar dx, SkScalar dy)
-#In Build
-#Line # appends Line relative to Last_Point ##
-#Populate
-
-#Example
-#Height 128
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkPath path;
-    path.moveTo(10, 98);
-    SkScalar x = 0, y = 0;
-    for (int i = 10; i < 100; i += 5) {
-        x += i * ((i & 2) - 1);
-        y += i * (((i + 1) & 2) - 1);
-        path.rLineTo(x, y);
-
-    }
-    canvas->drawPath(path, paint);
-}
-##
-
-#SeeAlso Contour moveTo lineTo addRect
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Quad
-#Alias Quad ##
-#Alias Quads ##
-#Alias Quadratic_Bezier ##
-#Alias Quadratic_Beziers ##
-#Line # curve described by second-order polynomial ##
-
-Quad describes a Quadratic_Bezier, a second-order curve identical to a section
-of a parabola. Quad begins at a start Point, curves towards a control Point,
-and then curves to an end Point.
-
-#Example
-#Height 110
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkPoint quadPts[] = {{20, 90}, {120, 10}, {220, 90}};
-    canvas->drawLine(quadPts[0], quadPts[1], paint);
-    canvas->drawLine(quadPts[1], quadPts[2], paint);
-    SkPath path;
-    path.moveTo(quadPts[0]);
-    path.quadTo(quadPts[1], quadPts[2]);
-    paint.setStrokeWidth(3);
-    canvas->drawPath(path, paint);
-}
-##
-
-Quad is a special case of Conic where Conic_Weight is set to one.
-
-Quad is always contained by the triangle connecting its three Points. Quad
-begins tangent to the line between start Point and control Point, and ends
-tangent to the line between control Point and end Point.
-
-#Example
-#Height 160
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkPoint quadPts[] = {{20, 150}, {120, 10}, {220, 150}};
-    SkColor colors[] = { 0xff88ff00, 0xff0088bb, 0xff6600cc, 0xffbb3377 };
-    for (unsigned i = 0; i < SK_ARRAY_COUNT(colors); ++i) {
-        paint.setColor(0x7fffffff & colors[i]);
-        paint.setStrokeWidth(1);
-        canvas->drawLine(quadPts[0], quadPts[1], paint);
-        canvas->drawLine(quadPts[1], quadPts[2], paint);
-        SkPath path;
-        path.moveTo(quadPts[0]);
-        path.quadTo(quadPts[1], quadPts[2]);
-        paint.setStrokeWidth(3);
-        paint.setColor(colors[i]);
-        canvas->drawPath(path, paint);
-        quadPts[1].fY += 30;
-   }
-}
-##
-
-#Method SkPath& quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2)
-
-#In Quad
-#Line # appends Quad ##
-#Populate
-
-#Example
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setStyle(SkPaint::kStroke_Style);
-        SkPath path;
-        path.moveTo(0, -10);
-        for (int i = 0; i < 128; i += 16) {
-            path.quadTo( 10 + i, -10 - i,  10 + i,       0);
-            path.quadTo( 14 + i,  14 + i,       0,  14 + i);
-            path.quadTo(-18 - i,  18 + i, -18 - i,       0);
-            path.quadTo(-22 - i, -22 - i,       0, -22 - i);
-        }
-        path.offset(128, 128);
-        canvas->drawPath(path, paint);
-    }
-    ##
-
-    #SeeAlso Contour moveTo conicTo rQuadTo
-
-##
-
-#Method SkPath& quadTo(const SkPoint& p1, const SkPoint& p2)
-#In Build
-#In Quad
-#Populate
-
-#Example
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setStyle(SkPaint::kStroke_Style);
-        paint.setAntiAlias(true);
-        SkPath path;
-        SkPoint pts[] = {{128, 10}, {10, 214}, {236, 214}};
-        path.moveTo(pts[1]);
-        for (int i = 0; i < 3; ++i) {
-            path.quadTo(pts[i % 3],  pts[(i + 2) % 3]);
-        }
-        canvas->drawPath(path, paint);
-    }
-    ##
-
-    #SeeAlso Contour moveTo conicTo rQuadTo
-
-##
-
-#Method SkPath& rQuadTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2)
-#In Build
-#In Quad
-#Line # appends Quad relative to Last_Point ##
-#Populate
-
-#Example
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        SkPath path;
-        path.moveTo(128, 20);
-        path.rQuadTo(-6, 10, -7, 10);
-        for (int i = 1; i < 32; i += 4) {
-           path.rQuadTo(10 + i, 10 + i, 10 + i * 4, 10);
-           path.rQuadTo(-10 - i, 10 + i, -10 - (i + 2) * 4, 10);
-        }
-        path.quadTo(92, 220, 128, 215);
-        canvas->drawPath(path, paint);
-    }
-    ##
-
-    #SeeAlso Contour moveTo conicTo quadTo
-
-##
-
-#Subtopic Quad ##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Conic
-#Alias Conic ##
-#Alias Conics ##
-#Line # conic section defined by three points and a weight ##
-
-Conic describes a conical section: a piece of an ellipse, or a piece of a
-parabola, or a piece of a hyperbola. Conic begins at a start Point,
-curves towards a control Point, and then curves to an end Point. The influence
-of the control Point is determined by Conic_Weight.
-
-Each Conic in Path adds two Points and one Conic_Weight. Conic_Weights in Path
-may be inspected with Iter, or with RawIter.
-
-#Subtopic Weight
-#Alias Conic_Weights ##
-#Alias Weights ##
-#Line # strength of Conic control Point ##
-
-Weight determines both the strength of the control Point and the type of Conic.
-Weight varies from zero to infinity. At zero, Weight causes the control Point to
-have no effect; Conic is identical to a line segment from start Point to end
-point. If Weight is less than one, Conic follows an elliptical arc.
-If Weight is exactly one, then Conic is identical to Quad; Conic follows a
-parabolic arc. If Weight is greater than one, Conic follows a hyperbolic
-arc. If Weight is infinity, Conic is identical to two line segments, connecting
-start Point to control Point, and control Point to end Point.
-
-#Example
-#Description
-When Conic_Weight is one, Quad is added to path; the two are identical.
-##
-void draw(SkCanvas* canvas) {
-    const char* verbNames[] = { "move", "line", "quad", "conic", "cubic", "close", "done" };
-    const int pointCount[]  = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };
-    SkPath path;
-    path.conicTo(20, 30, 50, 60, 1);
-    SkPath::Iter iter(path, false);
-    SkPath::Verb verb;
-    do {
-       SkPoint points[4];
-       verb = iter.next(points);
-       SkDebugf("%s ", verbNames[(int) verb]);
-       for (int i = 0; i < pointCount[(int) verb]; ++i) {
-            SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);
-       }
-       if (SkPath::kConic_Verb == verb) {
-           SkDebugf("weight = %g", iter.conicWeight());
-       }
-       SkDebugf("\n");
-    } while (SkPath::kDone_Verb != verb);
-}
-#StdOut
-move {0, 0},
-quad {0, 0}, {20, 30}, {50, 60},
-done
-##
-##
-
-If weight is less than one, Conic is an elliptical segment.
-
-#Example
-#Description
-A 90 degree circular arc has the weight #Formula # 1 / sqrt(2) ##.
-##
-void draw(SkCanvas* canvas) {
-    const char* verbNames[] = { "move", "line", "quad", "conic", "cubic", "close", "done" };
-    const int pointCount[]  = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };
-    SkPath path;
-    path.arcTo(20, 0, 20, 20, 20);
-    SkPath::Iter iter(path, false);
-    SkPath::Verb verb;
-    do {
-       SkPoint points[4];
-       verb = iter.next(points);
-       SkDebugf("%s ", verbNames[(int) verb]);
-       for (int i = 0; i < pointCount[(int) verb]; ++i) {
-            SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);
-       }
-       if (SkPath::kConic_Verb == verb) {
-           SkDebugf("weight = %g", iter.conicWeight());
-       }
-       SkDebugf("\n");
-    } while (SkPath::kDone_Verb != verb);
-}
-#StdOut
-move {0, 0},
-conic {0, 0}, {20, 0}, {20, 20}, weight = 0.707107
-done
-##
-##
-
-If weight is greater than one, Conic is a hyperbolic segment. As weight gets large,
-a hyperbolic segment can be approximated by straight lines connecting the
-control Point with the end Points.
-
-#Example
-void draw(SkCanvas* canvas) {
-    const char* verbNames[] = { "move", "line", "quad", "conic", "cubic", "close", "done" };
-    const int pointCount[]  = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };
-    SkPath path;
-    path.conicTo(20, 0, 20, 20, SK_ScalarInfinity);
-    SkPath::Iter iter(path, false);
-    SkPath::Verb verb;
-    do {
-       SkPoint points[4];
-       verb = iter.next(points);
-       SkDebugf("%s ", verbNames[(int) verb]);
-       for (int i = 0; i < pointCount[(int) verb]; ++i) {
-            SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);
-       }
-       if (SkPath::kConic_Verb == verb) {
-           SkDebugf("weight = %g", iter.conicWeight());
-       }
-       SkDebugf("\n");
-    } while (SkPath::kDone_Verb != verb);
-}
-#StdOut
-move {0, 0},
-line {0, 0}, {20, 0},
-line {20, 0}, {20, 20},
-done
-##
-##
-
-#Subtopic Weight ##
-
-#Method SkPath& conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
-                 SkScalar w)
-#In Conic
-#In Build
-#Line # appends Conic ##
-#Populate
-
-#Example
-    #Height 160
-    #Description
-    As weight increases, curve is pulled towards control point.
-    The bottom two curves are elliptical; the next is parabolic; the
-    top curve is hyperbolic.
-    ##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkPoint conicPts[] = {{20, 150}, {120, 10}, {220, 150}};
-    canvas->drawLine(conicPts[0], conicPts[1], paint);
-    canvas->drawLine(conicPts[1], conicPts[2], paint);
-    SkColor colors[] = { 0xff88ff00, 0xff0088bb, 0xff6600cc, 0xffbb3377 };
-    paint.setStrokeWidth(3);
-    SkScalar weight = 0.5f;
-    for (unsigned i = 0; i < SK_ARRAY_COUNT(colors); ++i) {
-        SkPath path;
-        path.moveTo(conicPts[0]);
-        path.conicTo(conicPts[1], conicPts[2], weight);
-        paint.setColor(colors[i]);
-        canvas->drawPath(path, paint);
-        weight += 0.25f;
-   }
-}
-    ##
-
-    #SeeAlso rConicTo arcTo addArc quadTo
-
-##
-
-#Method SkPath& conicTo(const SkPoint& p1, const SkPoint& p2, SkScalar w)
-#In Build
-#In Conic
-#Populate
-
-#Example
-    #Height 128
-    #Description
-    Conics and arcs use identical representations. As the arc sweep increases
-    the Conic_Weight also increases, but remains smaller than one.
-    ##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkRect oval = {0, 20, 120, 140};
-    SkPath path;
-    for (int i = 0; i < 4; ++i) {
-        path.moveTo(oval.centerX(), oval.fTop);
-        path.arcTo(oval, -90, 90 - 20 * i, false);
-        oval.inset(15, 15);
-    }
-    path.offset(100, 0);
-    SkScalar conicWeights[] = { 0.707107f, 0.819152f, 0.906308f, 0.965926f };
-    SkPoint conicPts[][3] = { { {40, 20}, {100, 20}, {100, 80} },
-                              { {40, 35}, {71.509f, 35}, {82.286f, 64.6091f} },
-                              { {40, 50}, {53.9892f, 50}, {62.981f, 60.7164f} },
-                              { {40, 65}, {44.0192f, 65}, {47.5f, 67.0096f} } };
-    for (int i = 0; i < 4; ++i) {
-         path.moveTo(conicPts[i][0]);
-         path.conicTo(conicPts[i][1], conicPts[i][2], conicWeights[i]);
-    }
-    canvas->drawPath(path, paint);
-}
-    ##
-
-    #SeeAlso rConicTo arcTo addArc quadTo
-
-##
-
-#Method SkPath& rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
-                  SkScalar w)
-#In Build
-#In Conic
-#Line # appends Conic relative to Last_Point ##
-#Populate
-
-#Example
-    #Height 140
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setStyle(SkPaint::kStroke_Style);
-        SkPath path;
-        path.moveTo(20, 80);
-        path.rConicTo( 60,   0,  60,  60, 0.707107f);
-        path.rConicTo(  0, -60,  60, -60, 0.707107f);
-        path.rConicTo(-60,   0, -60, -60, 0.707107f);
-        path.rConicTo(  0,  60, -60,  60, 0.707107f);
-        canvas->drawPath(path, paint);
-    }
-    ##
-
-    #SeeAlso conicTo arcTo addArc quadTo
-
-##
-
-#Subtopic Conic ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Cubic
-#Alias Cubic ##
-#Alias Cubics ##
-#Alias Cubic_Bezier ##
-#Alias Cubic_Beziers ##
-#Line # curve described by third-order polynomial ##
-
-Cubic describes a Bezier_Curve segment described by a third-order polynomial.
-Cubic begins at a start Point, curving towards the first control Point;
-and curves from the end Point towards the second control Point.
-
-#Example
-#Height 160
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkPoint cubicPts[] = {{20, 150}, {90, 10}, {160, 150}, {230, 10}};
-    SkColor colors[] = { 0xff88ff00, 0xff0088bb, 0xff6600cc, 0xffbb3377 };
-    for (unsigned i = 0; i < SK_ARRAY_COUNT(colors); ++i) {
-        paint.setColor(0x7fffffff & colors[i]);
-        paint.setStrokeWidth(1);
-        for (unsigned j = 0; j < 3; ++j) {
-            canvas->drawLine(cubicPts[j], cubicPts[j + 1], paint);
-        }
-        SkPath path;
-        path.moveTo(cubicPts[0]);
-        path.cubicTo(cubicPts[1], cubicPts[2], cubicPts[3]);
-        paint.setStrokeWidth(3);
-        paint.setColor(colors[i]);
-        canvas->drawPath(path, paint);
-        cubicPts[1].fY += 30;
-        cubicPts[2].fX += 30;
-   }
-}
-##
-
-#Method SkPath& cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
-                 SkScalar x3, SkScalar y3)
-#In Build
-#In Cubic
-#Line # appends Cubic ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkPath path;
-    path.moveTo(0, -10);
-    for (int i = 0; i < 128; i += 16) {
-        SkScalar c = i * 0.5f;
-        path.cubicTo( 10 + c, -10 - i,  10 + i, -10 - c,  10 + i,       0);
-        path.cubicTo( 14 + i,  14 + c,  14 + c,  14 + i,       0,  14 + i);
-        path.cubicTo(-18 - c,  18 + i, -18 - i,  18 + c, -18 - i,       0);
-        path.cubicTo(-22 - i, -22 - c, -22 - c, -22 - i,       0, -22 - i);
-    }
-    path.offset(128, 128);
-    canvas->drawPath(path, paint);
-}
-##
-
-#SeeAlso Contour moveTo rCubicTo quadTo
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& cubicTo(const SkPoint& p1, const SkPoint& p2, const SkPoint& p3)
-
-#In Build
-#In Cubic
-#Populate
-
-#Example
-#Height 84
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkPoint pts[] = { {20, 20}, {300, 80}, {-140, 90}, {220, 10} };
-    SkPath path;
-    path.moveTo(pts[0]);
-    path.cubicTo(pts[1], pts[2], pts[3]);
-    canvas->drawPath(path, paint);
-##
-
-#SeeAlso Contour moveTo rCubicTo quadTo
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& rCubicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
-                  SkScalar dx3, SkScalar dy3)
-#In Build
-#In Cubic
-#Line # appends Cubic relative to Last_Point ##
-#Populate
-
-#Example
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setStyle(SkPaint::kStroke_Style);
-        SkPath path;
-        path.moveTo(24, 108);
-        for (int i = 0; i < 16; i++) {
-           SkScalar sx, sy;
-           sx = SkScalarSinCos(i * SK_ScalarPI / 8, &sy);
-           path.rCubicTo(40 * sx, 4 * sy, 4 * sx, 40 * sy, 40 * sx, 40 * sy);
-        }
-        canvas->drawPath(path, paint);
-    }
-##
-
-#SeeAlso Contour moveTo cubicTo quadTo
-
-##
-
-#Subtopic Cubic ##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Arc
-#Line # part of Oval or Circle ##
-Arc can be constructed in a number of ways. Arc may be described by part of Oval and angles,
-by start point and end point, and by radius and tangent lines. Each construction has advantages,
-and some constructions correspond to Arc drawing in graphics standards.
-
-All Arc draws are implemented by one or more Conic draws. When Conic_Weight is less than one,
-Conic describes an Arc of some Oval or Circle.
-
-arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool forceMoveTo)
-describes Arc as a piece of Oval, beginning at start angle, sweeping clockwise or counterclockwise,
-which may continue Contour or start a new one. This construction is similar to PostScript and
-HTML_Canvas arcs. Variation addArc always starts new Contour. SkCanvas::drawArc draws without
-requiring Path.
-
-arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius)
-describes Arc as tangent to the line segment from last Point added to Path to (x1, y1); and tangent
-to the line segment from (x1, y1) to (x2, y2). This construction is similar to PostScript and
-HTML_Canvas arcs.
-
-arcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, ArcSize largeArc, Direction sweep,
-      SkScalar x, SkScalar y)
-describes Arc as part of Oval with radii (rx, ry), beginning at
-last Point added to Path and ending at (x, y). More than one Arc satisfies this criteria,
-so additional values choose a single solution. This construction is similar to SVG arcs.
-
-conicTo describes Arc of less than 180 degrees as a pair of tangent lines and Conic_Weight.
-conicTo can represent any Arc with a sweep less than 180 degrees at any rotation. All arcTo
-constructions are converted to Conic data when added to Path.
-
-#ToDo  example is spaced correctly on fiddle but spacing is too wide on pc
-##
-
-#Illustration
-
-#List
-# <sup>1</sup> arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool forceMoveTo) ##
-# <sup>2</sup> parameter adds move to first point  ##
-# <sup>3</sup> start angle must be multiple of 90 degrees ##
-# <sup>4</sup> arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) ##
-# <sup>5</sup> arcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, ArcSize largeArc,
-               Direction sweep, SkScalar x, SkScalar y) ##
-#List ##
-
-#Example
-#Height 128
-void draw(SkCanvas* canvas) {
-    SkRect oval = {8, 8, 56, 56};
-    SkPaint ovalPaint;
-    ovalPaint.setAntiAlias(true);
-    SkPaint textPaint(ovalPaint);
-    ovalPaint.setStyle(SkPaint::kStroke_Style);
-    SkPaint arcPaint(ovalPaint);
-    arcPaint.setStrokeWidth(5);
-    arcPaint.setColor(SK_ColorBLUE);
-    canvas->translate(-64, 0);
-    for (char arcStyle = '1'; arcStyle <= '6'; ++arcStyle) {
-        '4' == arcStyle ? canvas->translate(-96, 55) : canvas->translate(64, 0);
-        canvas->drawText(&arcStyle, 1, 30, 36, textPaint);
-        canvas->drawOval(oval, ovalPaint);
-        SkPath path;
-        path.moveTo({56, 32});
-        switch (arcStyle) {
-            case '1':
-                path.arcTo(oval, 0, 90, false);
-                break;
-            case '2':
-                canvas->drawArc(oval, 0, 90, false, arcPaint);
-                continue;
-            case '3':
-                path.addArc(oval, 0, 90);
-                break;
-            case '4':
-                path.arcTo({56, 56}, {32, 56}, 24);
-                break;
-            case '5':
-                path.arcTo({24, 24}, 0, SkPath::kSmall_ArcSize, SkPath::kCW_Direction, {32, 56});
-                break;
-            case '6':
-                path.conicTo({56, 56}, {32, 56}, SK_ScalarRoot2Over2);
-                break;
-         }
-         canvas->drawPath(path, arcPaint);
-     }
-}
-#Example ##
-
-In the example above:
-#List
-# 1 describes an arc from an oval, a starting angle, and a sweep angle. ##
-# 2 is similar to 1, but does not require building a path to draw. ##
-# 3 is similar to 1, but always begins new Contour. ##
-# 4 describes an arc from a pair of tangent lines and a radius. ##
-# 5 describes an arc from Oval center, arc start Point and arc end Point. ##
-# 6 describes an arc from a pair of tangent lines and a Conic_Weight. ##
-#List ##
-
-#Method SkPath& arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool forceMoveTo)
-#In Build
-#In Arc
-#Line # appends Arc ##
-#Populate
-
-#Example
-#Height 200
-#Description
-arcTo continues a previous contour when forceMoveTo is false and when Path
-is not empty.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkPath path;
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setStrokeWidth(4);
-    path.moveTo(0, 0);
-    path.arcTo({20, 20, 120, 120}, -90, 90, false);
-    canvas->drawPath(path, paint);
-    path.rewind();
-    path.arcTo({120, 20, 220, 120}, -90, 90, false);
-    canvas->drawPath(path, paint);
-    path.rewind();
-    path.moveTo(0, 0);
-    path.arcTo({20, 120, 120, 220}, -90, 90, true);
-    canvas->drawPath(path, paint);
-}
-##
-
-#SeeAlso addArc SkCanvas::drawArc conicTo
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius)
-#In Build
-#In Arc
-#Populate
-
-#Example
-#Height 226
-void draw(SkCanvas* canvas) {
-    SkPaint tangentPaint;
-    tangentPaint.setAntiAlias(true);
-    SkPaint textPaint(tangentPaint);
-    tangentPaint.setStyle(SkPaint::kStroke_Style);
-    tangentPaint.setColor(SK_ColorGRAY);
-    SkPaint arcPaint(tangentPaint);
-    arcPaint.setStrokeWidth(5);
-    arcPaint.setColor(SK_ColorBLUE);
-    SkPath path;
-    SkPoint pts[] = { {56, 20}, {200, 20}, {90, 190} };
-    SkScalar radius = 50;
-    path.moveTo(pts[0]);
-    path.arcTo(pts[1], pts[2], radius);
-    canvas->drawLine(pts[0], pts[1], tangentPaint);
-    canvas->drawLine(pts[1], pts[2], tangentPaint);
-    SkPoint lastPt;
-    (void) path.getLastPt(&lastPt);
-    SkVector radial = pts[2] - pts[1];
-    radial.setLength(radius);
-    SkPoint center = { lastPt.fX - radial.fY, lastPt.fY + radial.fX };
-    canvas->drawCircle(center, radius, tangentPaint);
-    canvas->drawLine(lastPt, center, tangentPaint);
-    radial = pts[1] - pts[0];
-    radial.setLength(radius);
-    SkPoint arcStart = { center.fX + radial.fY, center.fY - radial.fX };
-    canvas->drawLine(center, arcStart, tangentPaint);
-    canvas->drawPath(path, arcPaint);
-    canvas->drawString("(x0, y0)", pts[0].fX - 5, pts[0].fY, textPaint);
-    canvas->drawString("(x1, y1)", pts[1].fX + 5, pts[1].fY, textPaint);
-    canvas->drawString("(x2, y2)", pts[2].fX, pts[2].fY + 15, textPaint);
-    canvas->drawString("radius", center.fX + 15, center.fY + 25, textPaint);
-    canvas->drawString("radius", center.fX - 3, center.fY - 16, textPaint);
-}
-##
-
-#Example
-#Height 128
-void draw(SkCanvas* canvas) {
-    SkPaint tangentPaint;
-    tangentPaint.setAntiAlias(true);
-    SkPaint textPaint(tangentPaint);
-    tangentPaint.setStyle(SkPaint::kStroke_Style);
-    tangentPaint.setColor(SK_ColorGRAY);
-    SkPaint arcPaint(tangentPaint);
-    arcPaint.setStrokeWidth(5);
-    arcPaint.setColor(SK_ColorBLUE);
-    SkPath path;
-    SkPoint pts[] = { {156, 20}, {200, 20}, {170, 50} };
-    SkScalar radius = 50;
-    path.moveTo(pts[0]);
-    path.arcTo(pts[1], pts[2], radius);
-    canvas->drawLine(pts[0], pts[1], tangentPaint);
-    canvas->drawLine(pts[1], pts[2], tangentPaint);
-    SkPoint lastPt;
-    (void) path.getLastPt(&lastPt);
-    SkVector radial = pts[2] - pts[1];
-    radial.setLength(radius);
-    SkPoint center = { lastPt.fX - radial.fY, lastPt.fY + radial.fX };
-    canvas->drawLine(lastPt, center, tangentPaint);
-    radial = pts[1] - pts[0];
-    radial.setLength(radius);
-    SkPoint arcStart = { center.fX + radial.fY, center.fY - radial.fX };
-    canvas->drawLine(center, arcStart, tangentPaint);
-    canvas->drawPath(path, arcPaint);
-    canvas->drawString("(x0, y0)", pts[0].fX, pts[0].fY - 7, textPaint);
-    canvas->drawString("(x1, y1)", pts[1].fX + 5, pts[1].fY, textPaint);
-    canvas->drawString("(x2, y2)", pts[2].fX, pts[2].fY + 15, textPaint);
-    canvas->drawString("radius", center.fX + 15, center.fY + 25, textPaint);
-    canvas->drawString("radius", center.fX - 5, center.fY - 20, textPaint);
-}
-##
-
-#Example
-#Description
-arcTo is represented by Line and circular Conic in Path.
-##
-void draw(SkCanvas* canvas) {
-    SkPath path;
-    path.moveTo({156, 20});
-    path.arcTo(200, 20, 170, 50, 50);
-    SkPath::Iter iter(path, false);
-    SkPoint p[4];
-    SkPath::Verb verb;
-    while (SkPath::kDone_Verb != (verb = iter.next(p))) {
-        switch (verb) {
-            case SkPath::kMove_Verb:
-                SkDebugf("move to (%g,%g)\n", p[0].fX, p[0].fY);
-                break;
-            case SkPath::kLine_Verb:
-                SkDebugf("line (%g,%g),(%g,%g)\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY);
-                break;
-            case SkPath::kConic_Verb:
-                SkDebugf("conic (%g,%g),(%g,%g),(%g,%g) weight %g\n",
-                         p[0].fX, p[0].fY, p[1].fX, p[1].fY, p[2].fX, p[2].fY, iter.conicWeight());
-                break;
-            default:
-                SkDebugf("unexpected verb\n");
-        }
-    }
-}
-#StdOut
-move to (156,20)
-line (156,20),(79.2893,20)
-conic (79.2893,20),(200,20),(114.645,105.355) weight 0.382683
-##
-##
-
-#SeeAlso conicTo
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& arcTo(const SkPoint p1, const SkPoint p2, SkScalar radius)
-#In Build
-#In Arc
-#Populate
-
-#Example
-#Description
-Because tangent lines are parallel, arcTo appends line from last Path Point to
-p1, but does not append a circular Conic.
-##
-void draw(SkCanvas* canvas) {
-    SkPath path;
-    path.moveTo({156, 20});
-    path.arcTo({200, 20}, {170, 20}, 50);
-    SkPath::Iter iter(path, false);
-    SkPoint p[4];
-    SkPath::Verb verb;
-    while (SkPath::kDone_Verb != (verb = iter.next(p))) {
-        switch (verb) {
-            case SkPath::kMove_Verb:
-                SkDebugf("move to (%g,%g)\n", p[0].fX, p[0].fY);
-                break;
-            case SkPath::kLine_Verb:
-                SkDebugf("line (%g,%g),(%g,%g)\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY);
-                break;
-            case SkPath::kConic_Verb:
-                SkDebugf("conic (%g,%g),(%g,%g),(%g,%g) weight %g\n",
-                          p[0].fX, p[0].fY, p[1].fX, p[1].fY, p[2].fX, p[2].fY, iter.conicWeight());
-                break;
-            default:
-                SkDebugf("unexpected verb\n");
-        }
-    }
-}
-#StdOut
-move to (156,20)
-line (156,20),(200,20)
-##
-##
-
-#SeeAlso conicTo
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Enum ArcSize
-#Line # used by arcTo variation ##
-
-#Code
-    enum ArcSize {
-        kSmall_ArcSize,
-        kLarge_ArcSize,
-    };
-##
-
-Four axis-aligned Ovals with the same height and width intersect a pair of Points.
-ArcSize and Direction select one of the four Ovals, by choosing the larger or smaller
-arc between the Points; and by choosing the arc Direction, clockwise
-or counterclockwise.
-
-#Const kSmall_ArcSize 0
-#Line # smaller of Arc pair ##
-##
-#Const kLarge_ArcSize 1
-#Line # larger of Arc pair ##
-##
-
-#Example
-#Height 160
-#Description
-Arc begins at top of Oval pair and ends at bottom. Arc can take four routes to get there.
-Two routes are large, and two routes are counterclockwise. The one route both large
-and counterclockwise is blue.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    for (auto sweep: { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {
-        for (auto arcSize : { SkPath::kSmall_ArcSize, SkPath::kLarge_ArcSize } ) {
-            SkPath path;
-            path.moveTo({120, 50});
-            path.arcTo(70, 40, 30, arcSize, sweep, 156, 100);
-            if (SkPath::kCCW_Direction == sweep && SkPath::kLarge_ArcSize == arcSize) {
-                paint.setColor(SK_ColorBLUE);
-                paint.setStrokeWidth(3);
-            }
-            canvas->drawPath(path, paint);
-         }
-    }
-}
-##
-
-#SeeAlso arcTo Direction
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& arcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, ArcSize largeArc,
-               Direction sweep, SkScalar x, SkScalar y)
-#In Build
-#In Arc
-#Populate
-
-#Example
-#Height 160
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    for (auto sweep: { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {
-        for (auto arcSize : { SkPath::kSmall_ArcSize, SkPath::kLarge_ArcSize } ) {
-            SkPath path;
-            path.moveTo({120, 50});
-            path.arcTo(70, 40, 30, arcSize, sweep, 120.1, 50);
-            if (SkPath::kCCW_Direction == sweep && SkPath::kLarge_ArcSize == arcSize) {
-                paint.setColor(SK_ColorBLUE);
-                paint.setStrokeWidth(3);
-            }
-            canvas->drawPath(path, paint);
-         }
-    }
-}
-##
-
-#SeeAlso rArcTo ArcSize Direction
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& arcTo(const SkPoint r, SkScalar xAxisRotate, ArcSize largeArc, Direction sweep,
-               const SkPoint xy)
-#In Build
-#In Arc
-#Populate
-
-#Example
-#Height 108
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkPath path;
-    const SkPoint starts[] = {{20, 20}, {120, 20}, {70, 60}};
-    for (auto start : starts) {
-        path.moveTo(start.fX, start.fY);
-        path.rArcTo(20, 20, 0, SkPath::kSmall_ArcSize, SkPath::kCCW_Direction, 60, 0);
-    }
-    canvas->drawPath(path, paint);
-}
-##
-
-#SeeAlso rArcTo ArcSize Direction
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, ArcSize largeArc,
-                Direction sweep, SkScalar dx, SkScalar dy)
-#In Build
-#In Arc
-#Line # appends Arc relative to Last_Point ##
-
-Appends Arc to Path, relative to last Path Point. Arc is implemented by one or
-more Conic, weighted to describe part of Oval with radii (rx, ry) rotated by
-xAxisRotate degrees. Arc curves from last Path Point to relative end Point
-(dx, dy), choosing one of four possible routes: clockwise or
-counterclockwise, and smaller or larger. If Path is empty, the start Arc Point
-is (0, 0).
-
-Arc sweep is always less than 360 degrees. arcTo appends Line to end Point
-if either radii are zero, or if last Path Point equals end Point.
-arcTo scales radii (rx, ry) to fit last Path Point and end Point if both are
-greater than zero but too small to describe an arc.
-
-arcTo appends up to four Conic curves.
-arcTo implements the functionality of SVG_Arc, although SVG "sweep-flag" value is
-opposite the integer value of sweep; SVG "sweep-flag" uses 1 for clockwise, while
-kCW_Direction cast to int is zero.
-
-#Param rx           radius before x-axis rotation ##
-#Param ry           radius before x-axis rotation ##
-#Param xAxisRotate  x-axis rotation in degrees; positive values are clockwise ##
-#Param largeArc     chooses smaller or larger Arc ##
-#Param sweep        chooses clockwise or counterclockwise Arc ##
-#Param dx           x-axis offset end of Arc from last Path Point ##
-#Param dy           y-axis offset end of Arc from last Path Point ##
-
-#Return reference to Path ##
-
-#Example
-#Height 108
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    SkPath path;
-    const SkPoint starts[] = {{20, 20}, {120, 20}, {70, 60}};
-    for (auto start : starts) {
-        path.moveTo(start.fX, start.fY);
-        path.rArcTo(20, 20, 0, SkPath::kSmall_ArcSize, SkPath::kCCW_Direction, 60, 0);
-    }
-    canvas->drawPath(path, paint);
-}
-##
-
-#SeeAlso arcTo ArcSize Direction
-
-##
-
-#Subtopic Arc ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& close()
-#In Build
-#Line # makes last Contour a loop ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setStrokeWidth(15);
-    paint.setStrokeCap(SkPaint::kRound_Cap);
-    SkPath path;
-    const SkPoint points[] = {{20, 20}, {70, 20}, {40, 90}};
-    path.addPoly(points, SK_ARRAY_COUNT(points), false);
-    for (int loop = 0; loop < 2; ++loop) {
-        for (auto style : {SkPaint::kStroke_Style, SkPaint::kFill_Style,
-                SkPaint::kStrokeAndFill_Style} ) {
-            paint.setStyle(style);
-            canvas->drawPath(path, paint);
-            canvas->translate(85, 0);
-        }
-        path.close();
-        canvas->translate(-255, 128);
-    }
-}
-##
-
-#SeeAlso
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static bool IsInverseFillType(FillType fill)
-#In Property
-#Line # returns if Fill_Type represents outside geometry ##
-Returns true if fill is inverted and Path with fill represents area outside
-of its geometric bounds.
-
-#Table
-#Legend
-# FillType                 # is inverse ##
-##
-# kWinding_FillType        # false      ##
-# kEvenOdd_FillType        # false      ##
-# kInverseWinding_FillType # true       ##
-# kInverseEvenOdd_FillType # true       ##
-##
-
-#Param fill  one of: kWinding_FillType, kEvenOdd_FillType,
-             kInverseWinding_FillType, kInverseEvenOdd_FillType
-##
-
-#Return  true if Path fills outside its bounds ##
-
-#Example
-#Function
-###$
-#define nameValue(fill) { SkPath::fill, #fill }
-
-$$$#
-##
-void draw(SkCanvas* canvas) {
-    struct {
-        SkPath::FillType fill;
-        const char* name;
-    } fills[] = {
-        nameValue(kWinding_FillType),
-        nameValue(kEvenOdd_FillType),
-        nameValue(kInverseWinding_FillType),
-        nameValue(kInverseEvenOdd_FillType),
-    };
-    for (auto fill: fills ) {
-        SkDebugf("IsInverseFillType(%s) == %s\n", fill.name, SkPath::IsInverseFillType(fill.fill) ?
-                 "true" : "false");
-    }
-}
-#StdOut
-IsInverseFillType(kWinding_FillType) == false
-IsInverseFillType(kEvenOdd_FillType) == false
-IsInverseFillType(kInverseWinding_FillType) == true
-IsInverseFillType(kInverseEvenOdd_FillType) == true
-##
-##
-
-#SeeAlso FillType getFillType setFillType ConvertToNonInverseFillType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static FillType ConvertToNonInverseFillType(FillType fill)
-#In Utility
-#Line # returns Fill_Type representing inside geometry ##
-Returns equivalent Fill_Type representing Path fill inside its bounds.
-
-#Table
-#Legend
-# FillType                 # inside FillType   ##
-##
-# kWinding_FillType        # kWinding_FillType ##
-# kEvenOdd_FillType        # kEvenOdd_FillType ##
-# kInverseWinding_FillType # kWinding_FillType ##
-# kInverseEvenOdd_FillType # kEvenOdd_FillType ##
-##
-
-#Param fill  one of: kWinding_FillType, kEvenOdd_FillType,
-             kInverseWinding_FillType, kInverseEvenOdd_FillType
-##
-
-#Return  fill, or kWinding_FillType or kEvenOdd_FillType if fill is inverted ##
-
-#Example
-#Function
-###$
-#define nameValue(fill) { SkPath::fill, #fill }
-
-$$$#
-##
-void draw(SkCanvas* canvas) {
-    struct {
-        SkPath::FillType fill;
-        const char* name;
-    } fills[] = {
-        nameValue(kWinding_FillType),
-        nameValue(kEvenOdd_FillType),
-        nameValue(kInverseWinding_FillType),
-        nameValue(kInverseEvenOdd_FillType),
-    };
-    for (unsigned i = 0; i < SK_ARRAY_COUNT(fills); ++i) {
-        if (fills[i].fill != (SkPath::FillType) i) {
-            SkDebugf("fills array order does not match FillType enum order");
-            break;
-        }
-        SkDebugf("ConvertToNonInverseFillType(%s) == %s\n", fills[i].name,
-                fills[(int) SkPath::ConvertToNonInverseFillType(fills[i].fill)].name);
-    }
-}
-#StdOut
-ConvertToNonInverseFillType(kWinding_FillType) == kWinding_FillType
-ConvertToNonInverseFillType(kEvenOdd_FillType) == kEvenOdd_FillType
-ConvertToNonInverseFillType(kInverseWinding_FillType) == kWinding_FillType
-ConvertToNonInverseFillType(kInverseEvenOdd_FillType) == kEvenOdd_FillType
-##
-##
-
-#SeeAlso FillType getFillType setFillType IsInverseFillType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static int ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
-                                   SkScalar w, SkPoint pts[], int pow2)
-#In Utility
-#Line # approximates Conic with Quad array ##
-
-Approximates Conic with Quad array. Conic is constructed from start Point p0,
-control Point p1, end Point p2, and weight w.
-Quad array is stored in pts; this storage is supplied by caller.
-Maximum Quad count is 2 to the pow2.
-Every third point in array shares last Point of previous Quad and first Point of
-next Quad. Maximum pts storage size is given by:
-#Formula # (1 + 2 * (1 << pow2)) * sizeof(SkPoint) ##.
-
-Returns Quad count used the approximation, which may be smaller
-than the number requested.
-
-Conic_Weight determines the amount of influence Conic control point has on the curve.
-w less than one represents an elliptical section. w greater than one represents
-a hyperbolic section. w equal to one represents a parabolic section.
-
-Two Quad curves are sufficient to approximate an elliptical Conic with a sweep
-of up to 90 degrees; in this case, set pow2 to one.
-
-#Param p0    Conic start Point ##
-#Param p1    Conic control Point ##
-#Param p2    Conic end Point ##
-#Param w     Conic weight ##
-#Param pts   storage for Quad array ##
-#Param pow2  Quad count, as power of two, normally 0 to 5 (1 to 32 Quad curves) ##
-
-#Return  number of Quad curves written to pts ##
-
-#Example
-#Description
-A pair of Quad curves are drawn in red on top of the elliptical Conic curve in black.
-The middle curve is nearly circular. The top-right curve is parabolic, which can
-be drawn exactly with a single Quad.
-##
-void draw(SkCanvas* canvas) {
-      SkPaint conicPaint;
-      conicPaint.setAntiAlias(true);
-      conicPaint.setStyle(SkPaint::kStroke_Style);
-      SkPaint quadPaint(conicPaint);
-      quadPaint.setColor(SK_ColorRED);
-      SkPoint conic[] = { {20, 170}, {80, 170}, {80, 230} };
-      for (auto weight : { .25f, .5f, .707f, .85f, 1.f } ) {
-          SkPoint quads[5];
-          SkPath::ConvertConicToQuads(conic[0], conic[1], conic[2], weight, quads, 1);
-          SkPath path;
-          path.moveTo(conic[0]);
-          path.conicTo(conic[1], conic[2], weight);
-          canvas->drawPath(path, conicPaint);
-          path.rewind();
-          path.moveTo(quads[0]);
-          path.quadTo(quads[1], quads[2]);
-          path.quadTo(quads[3], quads[4]);
-          canvas->drawPath(path, quadPaint);
-          canvas->translate(50, -50);
-      }
-}
-##
-
-#SeeAlso Conic Quad
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isRect(SkRect* rect, bool* isClosed = nullptr, Direction* direction = nullptr) const
-#In Property
-#Line # returns if describes Rect ##
-#Populate
-
-#Example
-#Description
-After addRect, isRect returns true. Following moveTo permits isRect to return true, but
-following lineTo does not. addPoly returns true even though rect is not closed, and one
-side of rect is made up of consecutive line segments.
-##
-void draw(SkCanvas* canvas) {
-    auto debugster = [](const char* prefix, const SkPath& path) -> void {
-        SkRect rect;
-        SkPath::Direction direction;
-        bool isClosed;
-        path.isRect(&rect, &isClosed, &direction) ?
-                SkDebugf("%s is rect (%g, %g, %g, %g); is %s" "closed; direction %s\n", prefix,
-                         rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, isClosed ? "" : "not ",
-                         SkPath::kCW_Direction == direction ? "CW" : "CCW") :
-                SkDebugf("%s is not rect\n", prefix);
-    };
-    SkPath path;
-    debugster("empty", path);
-    path.addRect({10, 20, 30, 40});
-    debugster("addRect", path);
-    path.moveTo(60, 70);
-    debugster("moveTo", path);
-    path.lineTo(60, 70);
-    debugster("lineTo", path);
-    path.reset();
-    const SkPoint pts[] = { {0, 0}, {0, 80}, {80, 80}, {80, 0}, {40, 0}, {20, 0} };
-    path.addPoly(pts, SK_ARRAY_COUNT(pts), false);
-    debugster("addPoly", path);
-}
-#StdOut
-empty is not rect
-addRect is rect (10, 20, 30, 40); is closed; direction CW
-moveTo is rect (10, 20, 30, 40); is closed; direction CW
-lineTo is not rect
-addPoly is rect (0, 0, 80, 80); is not closed; direction CCW
-##
-##
-
-#SeeAlso computeTightBounds conservativelyContainsRect getBounds isConvex isLastContourClosed isNestedFillRects
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isNestedFillRects(SkRect rect[2], Direction dirs[2] = nullptr) const
-#In Property
-#Line # returns if describes Rect pair, one inside the other ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setStrokeWidth(5);
-    SkPath path;
-    path.addRect({10, 20, 30, 40});
-    paint.getFillPath(path, &path);
-    SkRect rects[2];
-    SkPath::Direction directions[2];
-    if (path.isNestedFillRects(rects, directions)) {
-        for (int i = 0; i < 2; ++i) {
-            SkDebugf("%s (%g, %g, %g, %g); direction %s\n", i ? "inner" : "outer",
-                     rects[i].fLeft, rects[i].fTop, rects[i].fRight, rects[i].fBottom,
-                     SkPath::kCW_Direction == directions[i] ? "CW" : "CCW");
-        }
-    } else {
-        SkDebugf("is not nested rectangles\n");
-    }
-}
-#StdOut
-outer (7.5, 17.5, 32.5, 42.5); direction CW
-inner (12.5, 22.5, 27.5, 37.5); direction CCW
-##
-##
-
-#SeeAlso computeTightBounds conservativelyContainsRect getBounds isConvex isLastContourClosed isRect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& addRect(const SkRect& rect, Direction dir = kCW_Direction)
-#In Build
-#Line # adds one Contour containing Rect ##
-#Populate
-
-#Example
-#Description
-The left Rect dashes starting at the top-left corner, to the right.
-The right Rect dashes starting at the top-left corner, towards the bottom.
-##
-#Height 128
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setStrokeWidth(15);
-    paint.setStrokeCap(SkPaint::kSquare_Cap);
-    float intervals[] = { 5, 21.75f };
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0));
-    SkPath path;
-    path.addRect({20, 20, 100, 100}, SkPath::kCW_Direction);
-    canvas->drawPath(path, paint);
-    path.rewind();
-    path.addRect({140, 20, 220, 100}, SkPath::kCCW_Direction);
-    canvas->drawPath(path, paint);
-}
-##
-
-#SeeAlso SkCanvas::drawRect Direction
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& addRect(const SkRect& rect, Direction dir, unsigned start)
-
-Adds Rect to Path, appending kMove_Verb, three kLine_Verb, and kClose_Verb.
-If dir is kCW_Direction, Rect corners are added clockwise; if dir is
-kCCW_Direction, Rect corners are added counterclockwise.
-start determines the first corner added.
-
-#Table
-#Legend
-# start # first corner ##
-#Legend ##
-# 0     # top-left ##
-# 1     # top-right ##
-# 2     # bottom-right ##
-# 3     # bottom-left ##
-#Table ##
-
-#Param rect   Rect to add as a closed contour ##
-#Param dir    Direction to wind added contour ##
-#Param start  initial corner of Rect to add ##
-
-#Return reference to Path ##
-
-#Example
-#Height 128
-#Description
-The arrow is just after the initial corner and points towards the next
-corner appended to Path.
-##
-void draw(SkCanvas* canvas) {
-    const SkPoint arrow[] = { {5, -5}, {15, -5}, {20, 0}, {15, 5}, {5, 5}, {10, 0} };
-    const SkRect rect = {10, 10, 54, 54};
-    SkPaint rectPaint;
-    rectPaint.setAntiAlias(true);
-    rectPaint.setStyle(SkPaint::kStroke_Style);
-    SkPaint arrowPaint(rectPaint);
-    SkPath arrowPath;
-    arrowPath.addPoly(arrow, SK_ARRAY_COUNT(arrow), true);
-    arrowPaint.setPathEffect(SkPath1DPathEffect::Make(arrowPath, 176, 0,
-                             SkPath1DPathEffect::kRotate_Style));
-    for (auto direction : { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {
-        for (unsigned start : { 0, 1, 2, 3 } ) {
-           SkPath path;
-           path.addRect(rect, direction, start);
-           canvas->drawPath(path, rectPaint);
-           canvas->drawPath(path, arrowPaint);
-           canvas->translate(64, 0);
-       }
-       canvas->translate(-256, 64);
-    }
-}
-##
-
-#SeeAlso SkCanvas::drawRect Direction
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& addRect(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom,
-                 Direction dir = kCW_Direction)
-#Populate
-
-#Example
-#Description
-The left Rect dashes start at the top-left corner, and continue to the right.
-The right Rect dashes start at the top-left corner, and continue down.
-##
-#Height 128
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setStrokeWidth(15);
-    paint.setStrokeCap(SkPaint::kSquare_Cap);
-    float intervals[] = { 5, 21.75f };
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0));
-    for (auto direction : { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {
-        SkPath path;
-        path.addRect(20, 20, 100, 100, direction);
-        canvas->drawPath(path, paint);
-        canvas->translate(128, 0);
-    }
-}
-##
-
-#SeeAlso SkCanvas::drawRect Direction
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& addOval(const SkRect& oval, Direction dir = kCW_Direction)
-#In Build
-#Line # adds one Contour containing Oval ##
-#Populate
-
-#Example
-#Height 120
-    SkPaint paint;
-    SkPath oval;
-    oval.addOval({20, 20, 160, 80});
-    canvas->drawPath(oval, paint);
-##
-
-#SeeAlso SkCanvas::drawOval Direction Oval
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& addOval(const SkRect& oval, Direction dir, unsigned start)
-
-Adds Oval to Path, appending kMove_Verb, four kConic_Verb, and kClose_Verb.
-Oval is upright ellipse bounded by Rect oval with radii equal to half oval width
-and half oval height. Oval begins at start and continues
-clockwise if dir is kCW_Direction, counterclockwise if dir is kCCW_Direction.
-
-#Table
-#Legend
-# start # Point                        ##
-#Legend ##
-# 0     # oval.centerX(), oval.fTop    ##
-# 1     # oval.fRight, oval.centerY()  ##
-# 2     # oval.centerX(), oval.fBottom ##
-# 3     # oval.fLeft, oval.centerY()   ##
-#Table ##
-
-#Param oval   bounds of ellipse added ##
-#Param dir    Direction to wind ellipse ##
-#Param start  index of initial point of ellipse ##
-
-#Return reference to Path ##
-
-#Example
-#Height 160
-void draw(SkCanvas* canvas) {
-    const SkPoint arrow[] = { {0, -5}, {10, 0}, {0, 5} };
-    const SkRect rect = {10, 10, 54, 54};
-    SkPaint ovalPaint;
-    ovalPaint.setAntiAlias(true);
-    SkPaint textPaint(ovalPaint);
-    ovalPaint.setStyle(SkPaint::kStroke_Style);
-    SkPaint arrowPaint(ovalPaint);
-    SkPath arrowPath;
-    arrowPath.addPoly(arrow, SK_ARRAY_COUNT(arrow), true);
-    arrowPaint.setPathEffect(SkPath1DPathEffect::Make(arrowPath, 176, 0,
-                             SkPath1DPathEffect::kRotate_Style));
-    for (auto direction : { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {
-        for (unsigned start : { 0, 1, 2, 3 } ) {
-           SkPath path;
-           path.addOval(rect, direction, start);
-           canvas->drawPath(path, ovalPaint);
-           canvas->drawPath(path, arrowPaint);
-           canvas->drawText(&"0123"[start], 1, rect.centerX(), rect.centerY() + 5, textPaint);
-           canvas->translate(64, 0);
-       }
-       canvas->translate(-256, 72);
-       canvas->drawString(SkPath::kCW_Direction == direction ? "clockwise" : "counterclockwise",
-                          128, 0, textPaint);
-    }
-}
-##
-
-#SeeAlso SkCanvas::drawOval Direction Oval
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& addCircle(SkScalar x, SkScalar y, SkScalar radius,
-                   Direction dir = kCW_Direction)
-#In Build
-#Line # adds one Contour containing Circle ##
-
-Adds Circle centered at (x, y) of size radius to Path, appending kMove_Verb,
-four kConic_Verb, and kClose_Verb. Circle begins at: #Formula # (x + radius, y) ##, continuing
-clockwise if dir is kCW_Direction, and counterclockwise if dir is kCCW_Direction.
-
-Has no effect if radius is zero or negative.
-
-#Param x       center of Circle ##
-#Param y       center of Circle  ##
-#Param radius  distance from center to edge ##
-#Param dir     Direction to wind Circle ##
-
-#Return reference to Path ##
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setStrokeWidth(10);
-    for (int size = 10; size < 300; size += 20) {
-        SkPath path;
-        path.addCircle(128, 128, size, SkPath::kCW_Direction);
-        canvas->drawPath(path, paint);
-    }
-}
-##
-
-#SeeAlso SkCanvas::drawCircle Direction Circle
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle)
-#In Build
-#Line # adds one Contour containing Arc ##
-#Populate
-
-#Example
-#Description
-The middle row of the left and right columns draw differently from the entries
-above and below because sweepAngle is outside of the range of +/-360,
-and startAngle modulo 90 is not zero.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    for (auto start : { 0, 90, 135, 180, 270 } ) {
-        for (auto sweep : { -450.f, -180.f, -90.f, 90.f, 180.f, 360.1f } ) {
-            SkPath path;
-            path.addArc({10, 10, 35, 45}, start, sweep);
-            canvas->drawPath(path, paint);
-            canvas->translate(252 / 6, 0);
-        }
-        canvas->translate(-252, 255 / 5);
-    }
-}
-##
-
-#SeeAlso Arc arcTo SkCanvas::drawArc
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
-                      Direction dir = kCW_Direction)
-#In Build
-#Line # adds one Contour containing Round_Rect with common corner radii ##
-#Populate
-
-#Example
-#Description
-If either radius is zero, path contains Rect and is drawn red.
-If sides are only radii, path contains Oval and is drawn blue.
-All remaining path draws are convex, and are drawn in gray; no
-paths constructed from addRoundRect are concave, so none are
-drawn in green.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    for (auto xradius : { 0, 7, 13, 20 } ) {
-        for (auto yradius : { 0, 9, 18, 40 } ) {
-            SkPath path;
-            path.addRoundRect({10, 10, 36, 46}, xradius, yradius);
-            paint.setColor(path.isRect(nullptr) ? SK_ColorRED : path.isOval(nullptr) ?
-                           SK_ColorBLUE : path.isConvex() ? SK_ColorGRAY : SK_ColorGREEN);
-            canvas->drawPath(path, paint);
-            canvas->translate(64, 0);
-        }
-        canvas->translate(-256, 64);
-    }
-}
-##
-
-#SeeAlso addRRect SkCanvas::drawRoundRect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& addRoundRect(const SkRect& rect, const SkScalar radii[],
-                      Direction dir = kCW_Direction)
-
-Appends Round_Rect to Path, creating a new closed Contour. Round_Rect has bounds
-equal to rect; each corner is 90 degrees of an ellipse with radii from the
-array.
-
-#Table
-#Legend
-# radii index # location                        ##
-#Legend ##
-# 0           # x-axis radius of top-left corner     ##
-# 1           # y-axis radius of top-left corner     ##
-# 2           # x-axis radius of top-right corner    ##
-# 3           # y-axis radius of top-right corner    ##
-# 4           # x-axis radius of bottom-right corner ##
-# 5           # y-axis radius of bottom-right corner ##
-# 6           # x-axis radius of bottom-left corner  ##
-# 7           # y-axis radius of bottom-left corner  ##
-#Table ##
-
-If dir is kCW_Direction, Round_Rect starts at top-left of the lower-left corner
-and winds clockwise. If dir is kCCW_Direction, Round_Rect starts at the
-bottom-left of the upper-left corner and winds counterclockwise.
-
-If both radii on any side of rect exceed its length, all radii are scaled
-uniformly until the corners fit. If either radius of a corner is less than or
-equal to zero, both are treated as zero.
-
-After appending, Path may be empty, or may contain: Rect, Oval, or Round_Rect.
-
-#Param rect   bounds of Round_Rect ##
-#Param radii  array of 8 SkScalar values, a radius pair for each corner ##
-#Param dir    Direction to wind Round_Rect ##
-
-#Return reference to Path ##
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkScalar radii[] = { 80, 100, 0, 0, 40, 60, 0, 0 };
-    SkPath path;
-    SkMatrix rotate90;
-    rotate90.setRotate(90, 128, 128);
-    for (int i = 0; i < 4; ++i) {
-        path.addRoundRect({10, 10, 110, 110}, radii);
-        path.transform(rotate90);
-    }
-    canvas->drawPath(path, paint);
-}
-##
-
-#SeeAlso addRRect SkCanvas::drawRoundRect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& addRRect(const SkRRect& rrect, Direction dir = kCW_Direction)
-#In Build
-#Line # adds one Contour containing Round_Rect ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkRRect rrect;
-    SkVector radii[] = {{50, 50}, {0, 0}, {0, 0}, {50, 50}};
-    rrect.setRectRadii({10, 10, 110, 110}, radii);
-    SkPath path;
-    SkMatrix rotate90;
-    rotate90.setRotate(90, 128, 128);
-    for (int i = 0; i < 4; ++i) {
-        path.addRRect(rrect);
-        path.transform(rotate90);
-    }
-    canvas->drawPath(path, paint);
-}
-##
-
-#SeeAlso addRoundRect SkCanvas::drawRRect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& addRRect(const SkRRect& rrect, Direction dir, unsigned start)
-
-Adds rrect to Path, creating a new closed Contour. If dir is kCW_Direction, rrect
-winds clockwise; if dir is kCCW_Direction, rrect winds counterclockwise.
-start determines the first point of rrect to add.
-
-#Table
-#Legend
-# start       # location                    ##
-#Legend ##
-# 0           # right of top-left corner    ##
-# 1           # left of top-right corner    ##
-# 2           # bottom of top-right corner  ##
-# 3           # top of bottom-right corner  ##
-# 4           # left of bottom-right corner ##
-# 5           # right of bottom-left corner ##
-# 6           # top of bottom-left corner   ##
-# 7           # bottom of top-left corner   ##
-#Table ##
-
-After appending, Path may be empty, or may contain: Rect, Oval, or Round_Rect.
-
-#Param rrect  bounds and radii of rounded rectangle ##
-#Param dir    Direction to wind Round_Rect ##
-#Param start  index of initial point of Round_Rect ##
-
-#Return reference to Path ##
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkRRect rrect;
-    rrect.setRectXY({40, 40, 215, 215}, 50, 50);
-    SkPath path;
-    path.addRRect(rrect);
-    canvas->drawPath(path, paint);
-    for (int start = 0; start < 8; ++start) {
-        SkPath textPath;
-        textPath.addRRect(rrect, SkPath::kCW_Direction, start);
-        SkPathMeasure pathMeasure(textPath, false);
-        SkPoint position;
-        SkVector tangent;
-        if (!pathMeasure.getPosTan(0, &position, &tangent)) {
-            continue;
-        }
-        SkRSXform rsxForm = SkRSXform::Make(tangent.fX, tangent.fY,
-               position.fX + tangent.fY * 5, position.fY - tangent.fX * 5);
-        canvas->drawTextRSXform(&"01234567"[start], 1, &rsxForm, nullptr, paint);
-    }
-}
-##
-
-#SeeAlso addRoundRect SkCanvas::drawRRect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& addPoly(const SkPoint pts[], int count, bool close)
-#In Build
-#Line # adds one Contour containing connected lines ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setStrokeWidth(15);
-    paint.setStrokeCap(SkPaint::kRound_Cap);
-    const SkPoint points[] = {{20, 20}, {70, 20}, {40, 90}};
-    for (bool close : { false, true } ) {
-        SkPath path;
-        path.addPoly(points, SK_ARRAY_COUNT(points), close);
-        for (auto style : {SkPaint::kStroke_Style, SkPaint::kFill_Style,
-                SkPaint::kStrokeAndFill_Style} ) {
-            paint.setStyle(style);
-            canvas->drawPath(path, paint);
-            canvas->translate(85, 0);
-        }
-        canvas->translate(-255, 128);
-    }
-}
-##
-
-#SeeAlso SkCanvas::drawPoints
-
-##
-
-#Method SkPath& addPoly(const std::initializer_list<SkPoint>& list, bool close)
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setStrokeWidth(15);
-    paint.setStrokeCap(SkPaint::kRound_Cap);
-    for (bool close : { false, true } ) {
-        SkPath path;
-        path.addPoly({{20, 20}, {70, 20}, {40, 90}}, close);
-        for (auto style : {SkPaint::kStroke_Style, SkPaint::kFill_Style,
-                SkPaint::kStrokeAndFill_Style} ) {
-            paint.setStyle(style);
-            canvas->drawPath(path, paint);
-            canvas->translate(85, 0);
-        }
-        canvas->translate(-255, 128);
-    }
-}
-##
-
-#SeeAlso SkCanvas::drawPoints
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Enum AddPathMode
-#Line # sets addPath options ##
-
-#Code
-    enum AddPathMode {
-        kAppend_AddPathMode,
-        kExtend_AddPathMode,
-    };
-##
-
-AddPathMode chooses how addPath appends. Adding one Path to another can extend
-the last Contour or start a new Contour.
-
-#Const kAppend_AddPathMode
-#Line # appended to destination unaltered ##
-    Path Verbs, Points, and Conic_Weights are appended to destination unaltered.
-    Since Path Verb_Array begins with kMove_Verb if src is not empty, this
-    starts a new Contour.
-##
-#Const kExtend_AddPathMode
-#Line # add line if prior Contour is not closed ##
-    If destination is closed or empty, start a new Contour. If destination
-    is not empty, add Line from Last_Point to added Path first Point. Skip added
-    Path initial kMove_Verb, then append remaining Verbs, Points, and Conic_Weights.
-##
-
-#Example
-#Description
-test is built from path, open on the top row, and closed on the bottom row.
-The left column uses kAppend_AddPathMode; the right uses kExtend_AddPathMode.
-The top right composition is made up of one contour; the other three have two.
-##
-#Height 180
-    SkPath path, path2;
-    path.moveTo(20, 20);
-    path.lineTo(20, 40);
-    path.lineTo(40, 20);
-    path2.moveTo(60, 60);
-    path2.lineTo(80, 60);
-    path2.lineTo(80, 40);
-    SkPaint paint;
-    paint.setStyle(SkPaint::kStroke_Style);
-    for (int i = 0; i < 2; i++) {
-        for (auto addPathMode : { SkPath::kAppend_AddPathMode, SkPath::kExtend_AddPathMode } ) {
-            SkPath test(path);
-            test.addPath(path2, addPathMode);
-            canvas->drawPath(test, paint);
-            canvas->translate(100, 0);
-        }
-        canvas->translate(-200, 100);
-        path.close();
-    }
-##
-
-#SeeAlso addPath reverseAddPath
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& addPath(const SkPath& src, SkScalar dx, SkScalar dy,
-                 AddPathMode mode = kAppend_AddPathMode)
-#In Build
-#Line # adds contents of Path ##
-#Populate
-
-#Example
-#Height 180
-    SkPaint paint;
-    paint.setTextSize(128);
-    paint.setFakeBoldText(true);
-    SkPath dest, text;
-    paint.getTextPath("O", 1, 50, 120, &text);
-    for (int i = 0; i < 3; i++) {
-        dest.addPath(text, i * 20, i * 20);
-    }
-    Simplify(dest, &dest);
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setStrokeWidth(3);
-    canvas->drawPath(dest, paint);
-##
-
-#SeeAlso AddPathMode offset reverseAddPath
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& addPath(const SkPath& src, AddPathMode mode = kAppend_AddPathMode)
-#Populate
-
-#Example
-#Height 80
-    SkPaint paint;
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkPath dest, path;
-    path.addOval({-80, 20, 0, 60}, SkPath::kCW_Direction, 1);
-    for (int i = 0; i < 2; i++) {
-        dest.addPath(path, SkPath::kExtend_AddPathMode);
-        dest.offset(100, 0);
-    }
-    canvas->drawPath(dest, paint);
-##
-
-#SeeAlso AddPathMode reverseAddPath
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& addPath(const SkPath& src, const SkMatrix& matrix, AddPathMode mode = kAppend_AddPathMode)
-#Populate
-
-#Example
-#Height 160
-    SkPaint paint;
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkPath dest, path;
-    path.addOval({20, 20, 200, 120}, SkPath::kCW_Direction, 1);
-    for (int i = 0; i < 6; i++) {
-        SkMatrix matrix;
-        matrix.reset();
-        matrix.setPerspX(i / 400.f);
-        dest.addPath(path, matrix);
-    }
-    canvas->drawPath(dest, paint);
-##
-
-#SeeAlso AddPathMode transform offset reverseAddPath
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPath& reverseAddPath(const SkPath& src)
-#In Build
-#Line # adds contents of Path back to front ##
-#Populate
-
-#Example
-#Height 200
-    SkPath path;
-    path.moveTo(20, 20);
-    path.lineTo(20, 40);
-    path.lineTo(40, 20);
-    SkPaint paint;
-    paint.setStyle(SkPaint::kStroke_Style);
-    for (int i = 0; i < 2; i++) {
-        SkPath path2;
-        path2.moveTo(60, 60);
-        path2.lineTo(80, 60);
-        path2.lineTo(80, 40);
-        for (int j = 0; j < 2; j++) {
-            SkPath test(path);
-            test.reverseAddPath(path2);
-            canvas->drawPath(test, paint);
-            canvas->translate(100, 0);
-            path2.close();
-        }
-        canvas->translate(-200, 100);
-        path.close();
-    }
-##
-
-#SeeAlso AddPathMode transform offset addPath
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void offset(SkScalar dx, SkScalar dy, SkPath* dst) const
-#In Transform
-#Line # translates Point_Array ##
-#Populate
-
-#Example
-#Height 60
-    SkPath pattern;
-    pattern.moveTo(20, 20);
-    pattern.lineTo(20, 40);
-    pattern.lineTo(40, 20);
-    SkPaint paint;
-    paint.setStyle(SkPaint::kStroke_Style);
-    for (int i = 0; i < 10; i++) {
-        SkPath path;
-        pattern.offset(20 * i, 0, &path);
-        canvas->drawPath(path, paint);
-    }
-##
-
-#SeeAlso addPath transform
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Transform
-#Line # modify all points ##
-##
-
-#Method void offset(SkScalar dx, SkScalar dy)
-#In Transform
-#Populate
-
-#Example
-#Height 60
-    SkPath path;
-    path.moveTo(20, 20);
-    path.lineTo(20, 40);
-    path.lineTo(40, 20);
-    SkPaint paint;
-    paint.setStyle(SkPaint::kStroke_Style);
-    for (int i = 0; i < 10; i++) {
-        canvas->drawPath(path, paint);
-        path.offset(20, 0);
-    }
-##
-
-#SeeAlso addPath transform SkCanvas::translate()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void transform(const SkMatrix& matrix, SkPath* dst) const
-#In Transform
-#Line # applies Matrix to Point_Array and Weights ##
-#Populate
-
-#Example
-#Height 200
-    SkPath pattern;
-    pattern.moveTo(100, 100);
-    pattern.lineTo(100, 20);
-    pattern.lineTo(20, 100);
-    SkPaint paint;
-    paint.setStyle(SkPaint::kStroke_Style);
-    for (int i = 0; i < 10; i++) {
-        SkPath path;
-        SkMatrix matrix;
-        matrix.setRotate(36 * i, 100, 100);
-        pattern.transform(matrix, &path);
-        canvas->drawPath(path, paint);
-    }
-##
-
-#SeeAlso addPath offset SkCanvas::concat() SkMatrix
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void transform(const SkMatrix& matrix)
-#Populate
-
-#Example
-#Height 200
-    SkPath path;
-    path.moveTo(100, 100);
-    path.quadTo(100, 20, 20, 100);
-    SkPaint paint;
-    paint.setStyle(SkPaint::kStroke_Style);
-    for (int i = 0; i < 10; i++) {
-        SkMatrix matrix;
-        matrix.setRotate(36, 100, 100);
-        path.transform(matrix);
-        canvas->drawPath(path, paint);
-    }
-##
-
-#SeeAlso addPath offset SkCanvas::concat() SkMatrix
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Last_Point
-#Line # final Point in Contour ##
-
-Path is defined cumulatively, often by adding a segment to the end of last
-Contour. Last_Point of Contour is shared as first Point of added Line or Curve.
-Last_Point can be read and written directly with getLastPt and setLastPt.
-
-#Method bool getLastPt(SkPoint* lastPt) const
-#In Property
-#In Last_Point
-#Line # returns Last_Point ##
-#Populate
-
-#Example
-        SkPath path;
-        path.moveTo(100, 100);
-        path.quadTo(100, 20, 20, 100);
-        SkMatrix matrix;
-        matrix.setRotate(36, 100, 100);
-        path.transform(matrix);
-        SkPoint last;
-        path.getLastPt(&last);
-        SkDebugf("last point: %g, %g\n", last.fX, last.fY);
-    #StdOut
-    last point: 35.2786, 52.9772
-    ##
-    ##
-
-    #SeeAlso setLastPt
-
-##
-
-#Method void setLastPt(SkScalar x, SkScalar y)
-#In Utility
-#In Last_Point
-#Line # replaces Last_Point ##
-#Populate
-
-#Example
-    #Height 128
-        SkPaint paint;
-        paint.setTextSize(128);
-        SkPath path;
-        paint.getTextPath("@", 1, 60, 100, &path);
-        path.setLastPt(20, 120);
-        canvas->drawPath(path, paint);
-    ##
-
-    #SeeAlso getLastPt
-
-##
-
-#Method void setLastPt(const SkPoint& p)
-#Populate
-
-#Example
-    #Height 128
-        SkPaint paint;
-        paint.setTextSize(128);
-        SkPath path, path2;
-        paint.getTextPath("A", 1, 60, 100, &path);
-        paint.getTextPath("Z", 1, 60, 100, &path2);
-        SkPoint pt, pt2;
-        path.getLastPt(&pt);
-        path2.getLastPt(&pt2);
-        path.setLastPt(pt2);
-        path2.setLastPt(pt);
-        canvas->drawPath(path, paint);
-        canvas->drawPath(path2, paint);
-    ##
-
-    #SeeAlso getLastPt
-
-##
-
-#Subtopic Last_Point ##
-
-# ------------------------------------------------------------------------------
-
-#Enum SegmentMask
-#Line # returns Verb types in Path ##
-
-#Code
-    enum SegmentMask {
-        kLine_SegmentMask = 1 << 0,
-        kQuad_SegmentMask = 1 << 1,
-        kConic_SegmentMask = 1 << 2,
-        kCubic_SegmentMask = 1 << 3,
-    };
-##
-
-SegmentMask constants correspond to each drawing Verb type in Path; for
-instance, if Path only contains Lines, only the kLine_SegmentMask bit is set.
-
-#Bug 6785
-#Const kLine_SegmentMask 1
-#Line # contains one or more Lines ##
-Set if Verb_Array contains kLine_Verb.
-##
-#Const kQuad_SegmentMask 2
-#Line # contains one or more Quads ##
-Set if Verb_Array contains kQuad_Verb. Note that conicTo may add a Quad.
-##
-#Const kConic_SegmentMask 4
-#Line # contains one or more Conics ##
-Set if Verb_Array contains kConic_Verb.
-##
-#Const kCubic_SegmentMask 8
-#Line # contains one or more Cubics ##
-Set if Verb_Array contains kCubic_Verb.
-##
-
-#Example
-#Description
-When conicTo has a weight of one, Quad is added to Path.
-##
-    SkPath path;
-    path.conicTo(10, 10, 20, 30, 1);
-    SkDebugf("Path kConic_SegmentMask is %s\n", path.getSegmentMasks() &
-          SkPath::kConic_SegmentMask ? "set" : "clear");
-    SkDebugf("Path kQuad_SegmentMask is %s\n", path.getSegmentMasks() &
-          SkPath::kQuad_SegmentMask ? "set" : "clear");
-#StdOut
-Path kConic_SegmentMask is clear
-Path kQuad_SegmentMask is set
-##
-##
-
-#SeeAlso getSegmentMasks Verb
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method uint32_t getSegmentMasks() const
-#In Utility
-#In Property
-#Line # returns types in Verb_Array ##
-#Populate
-
-#Example
-SkPath path;
-path.quadTo(20, 30, 40, 50);
-path.close();
-const char* masks[] = { "line", "quad", "conic", "cubic" };
-int index = 0;
-for (auto mask : { SkPath::kLine_SegmentMask, SkPath::kQuad_SegmentMask,
-        SkPath::kConic_SegmentMask, SkPath::kCubic_SegmentMask } ) {
-    if (mask & path.getSegmentMasks()) {
-       SkDebugf("mask %s set\n", masks[index]);
-    }
-    ++index;
-}
-#StdOut
-mask quad set
-##
-##
-
-#SeeAlso getSegmentMasks Verb
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool contains(SkScalar x, SkScalar y) const
-#In Property
-#Line # returns if Point is in fill area ##
-Returns true if the point (x, y) is contained by Path, taking into
-account FillType.
-
-#Table
-#Legend
-# FillType                 # contains() returns true if Point is enclosed by ##
-##
-# kWinding_FillType        # a non-zero sum of Contour Directions. ##
-# kEvenOdd_FillType        # an odd number of Contours.            ##
-# kInverseWinding_FillType # a zero sum of Contour Directions.     ##
-# kInverseEvenOdd_FillType # and even number of Contours.          ##
-##
-
-#Param x  x-axis value of containment test ##
-#Param y  y-axis value of containment test ##
-
-#Return  true if Point is in Path ##
-
-#Example
-SkPath path;
-SkPaint paint;
-paint.setTextSize(256);
-paint.getTextPath("&", 1, 30, 220, &path);
-for (int y = 2; y < 256; y += 9) {
-   for (int x = 2; x < 256; x += 9) {
-       int coverage = 0;
-       for (int iy = -4; iy <= 4; iy += 2) {
-           for (int ix = -4; ix <= 4; ix += 2) {
-               coverage += path.contains(x + ix, y + iy);
-           }
-       }
-       paint.setColor(SkColorSetARGB(0x5f, 0xff * coverage / 25, 0, 0xff * (25 - coverage) / 25));
-       canvas->drawCircle(x, y, 8, paint);
-   }
-}
-##
-
-#SeeAlso conservativelyContainsRect Fill_Type Op
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void dump(SkWStream* stream, bool forceClose, bool dumpAsHex) const
-#In Utility
-#Line # sends text representation to stream ##
-#Populate
-
-#Example
-   SkPath path;
-   path.quadTo(20, 30, 40, 50);
-   for (bool forceClose : { false, true } ) {
-       for (bool dumpAsHex : { false, true } ) {
-           path.dump(nullptr, forceClose, dumpAsHex);
-           SkDebugf("\n");
-       }
-   }
-#StdOut
-path.setFillType(SkPath::kWinding_FillType);
-path.moveTo(0, 0);
-path.quadTo(20, 30, 40, 50);
-
-path.setFillType(SkPath::kWinding_FillType);
-path.moveTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000));  // 0, 0
-path.quadTo(SkBits2Float(0x41a00000), SkBits2Float(0x41f00000), SkBits2Float(0x42200000), SkBits2Float(0x42480000));  // 20, 30, 40, 50
-
-path.setFillType(SkPath::kWinding_FillType);
-path.moveTo(0, 0);
-path.quadTo(20, 30, 40, 50);
-path.lineTo(0, 0);
-path.close();
-
-path.setFillType(SkPath::kWinding_FillType);
-path.moveTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000));  // 0, 0
-path.quadTo(SkBits2Float(0x41a00000), SkBits2Float(0x41f00000), SkBits2Float(0x42200000), SkBits2Float(0x42480000));  // 20, 30, 40, 50
-path.lineTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000));  // 0, 0
-path.close();
-##
-##
-
-#SeeAlso dumpHex SkRect::dump() SkRRect::dump() SkPathMeasure::dump()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void dump() const
-#Populate
-
-#Example
-SkPath path, copy;
-path.lineTo(6.f / 7, 2.f / 3);
-path.dump();
-copy.setFillType(SkPath::kWinding_FillType);
-copy.moveTo(0, 0);
-copy.lineTo(0.857143f, 0.666667f);
-SkDebugf("path is " "%s" "equal to copy\n", path == copy ? "" : "not ");
-#StdOut
-path.setFillType(SkPath::kWinding_FillType);
-path.moveTo(0, 0);
-path.lineTo(0.857143f, 0.666667f);
-path is not equal to copy
-##
-##
-
-#SeeAlso dumpHex SkRect::dump() SkRRect::dump() SkPathMeasure::dump() writeToMemory
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void dumpHex() const
-#In Utility
-#Line # sends text representation using hexadecimal to standard output ##
-Writes text representation of Path to standard output. The representation may be
-directly compiled as C++ code. Floating point values are written
-in hexadecimal to preserve their exact bit pattern. The output reconstructs the
-original Path.
-
-Use instead of dump() when submitting
-#A bug reports against Skia # https://bug.skia.org ##
-.
-
-#Example
-SkPath path, copy;
-path.lineTo(6.f / 7, 2.f / 3);
-path.dumpHex();
-copy.setFillType(SkPath::kWinding_FillType);
-copy.moveTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000));  // 0, 0
-copy.lineTo(SkBits2Float(0x3f5b6db7), SkBits2Float(0x3f2aaaab));  // 0.857143f, 0.666667f
-SkDebugf("path is " "%s" "equal to copy\n", path == copy ? "" : "not ");
-#StdOut
-path.setFillType(SkPath::kWinding_FillType);
-path.moveTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000));  // 0, 0
-path.lineTo(SkBits2Float(0x3f5b6db7), SkBits2Float(0x3f2aaaab));  // 0.857143f, 0.666667f
-path is equal to copy
-##
-##
-
-#SeeAlso dump SkRect::dumpHex SkRRect::dumpHex writeToMemory
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t writeToMemory(void* buffer) const
-#In Utility
-#Line # copies data to buffer ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPath path, copy;
-    path.lineTo(6.f / 7, 2.f / 3);
-    size_t size = path.writeToMemory(nullptr);
-    SkTDArray<char> storage;
-    storage.setCount(size);
-    path.writeToMemory(storage.begin());
-    copy.readFromMemory(storage.begin(), size);
-    SkDebugf("path is " "%s" "equal to copy\n", path == copy ? "" : "not ");
-}
-#StdOut
-path is equal to copy
-##
-##
-
-#SeeAlso serialize readFromMemory dump dumpHex
-
-##
-
-#Method sk_sp<SkData> serialize() const
-#In Utility
-#Line # copies data to buffer ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPath path, copy;
-    path.lineTo(6.f / 7, 2.f / 3);
-    sk_sp<SkData> data = path.serialize();
-    copy.readFromMemory(data->data(), data->size());
-    SkDebugf("path is " "%s" "equal to copy\n", path == copy ? "" : "not ");
-}
-#StdOut
-path is equal to copy
-##
-##
-
-#SeeAlso writeToMemory readFromMemory dump dumpHex
-##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t readFromMemory(const void* buffer, size_t length)
-#In Utility
-#Line # initializes from buffer ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPath path, copy;
-    path.lineTo(6.f / 7, 2.f / 3);
-    size_t size = path.writeToMemory(nullptr);
-    SkTDArray<char> storage;
-    storage.setCount(size);
-    path.writeToMemory(storage.begin());
-    size_t wrongSize = size - 4;
-    size_t bytesRead = copy.readFromMemory(storage.begin(), wrongSize);
-    SkDebugf("length = %u; returned by readFromMemory = %u\n", wrongSize, bytesRead);
-    size_t largerSize = size + 4;
-    bytesRead = copy.readFromMemory(storage.begin(), largerSize);
-    SkDebugf("length = %u; returned by readFromMemory = %u\n", largerSize, bytesRead);
-}
-#StdOut
-length = 32; returned by readFromMemory = 0
-length = 40; returned by readFromMemory = 36
-##
-##
-
-#SeeAlso writeToMemory
-
-##
-
-# ------------------------------------------------------------------------------
-#Subtopic Generation_ID
-#Alias Generation_IDs ##
-#Line # value reflecting contents change ##
-Generation_ID provides a quick way to check if Verb_Array, Point_Array, or
-Conic_Weight has changed. Generation_ID is not a hash; identical Paths will
-not necessarily have matching Generation_IDs.
-
-Empty Paths have a Generation_ID of one.
-
-#Method uint32_t getGenerationID() const
-
-#In Generation_ID
-#Line # returns unique ID ##
-#Populate
-
-#Example
-SkPath path;
-SkDebugf("empty genID = %u\n", path.getGenerationID());
-path.lineTo(1, 2);
-SkDebugf("1st lineTo genID = %u\n", path.getGenerationID());
-path.rewind();
-SkDebugf("empty genID = %u\n", path.getGenerationID());
-path.lineTo(1, 2);
-SkDebugf("2nd lineTo genID = %u\n", path.getGenerationID());
-#StdOut
-empty genID = 1
-1st lineTo genID = 2
-empty genID = 1
-2nd lineTo genID = 3
-##
-##
-
-#SeeAlso operator==(const SkPath& a, const SkPath& b)
-
-##
-
-#Subtopic ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isValid() const
-#In Property
-#In Utility
-#Line # returns if data is internally consistent ##
-#Populate
-
-#NoExample
-    ##
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Class Iter
-#Line # data iterator ##
-
-#Code
-#Populate
-##
-
-Iterates through Verb_Array, and associated Point_Array and Conic_Weight.
-Provides options to treat open Contours as closed, and to ignore
-degenerate data.
-
-#Example
-#Height 128
-#Description
-Ignoring the actual Verbs and replacing them with Quads rounds the
-path of the glyph.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setTextSize(256);
-    SkPath asterisk, path;
-    paint.getTextPath("*", 1, 50, 192, &asterisk);
-    SkPath::Iter iter(asterisk, true);
-    SkPoint start[4], pts[4];
-    iter.next(start);  // skip moveTo
-    iter.next(start);  // first quadTo
-    path.moveTo((start[0] + start[1]) * 0.5f);
-    while (SkPath::kClose_Verb != iter.next(pts)) {
-        path.quadTo(pts[0], (pts[0] + pts[1]) * 0.5f);
-    }
-    path.quadTo(start[0], (start[0] + start[1]) * 0.5f);
-    canvas->drawPath(path, paint);
-}
-##
-
-#SeeAlso RawIter
-
-#Method Iter()
-#Line # constructs Path iterator ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPath::Iter iter;
-    SkPoint points[4];
-    SkDebugf("iter is " "%s" "done\n", SkPath::kDone_Verb == iter.next(points) ? "" : "not ");
-    SkPath path;
-    iter.setPath(path, false);
-    SkDebugf("iter is " "%s" "done\n", SkPath::kDone_Verb == iter.next(points) ? "" : "not ");
-}
-#StdOut
-iter is done
-iter is done
-##
-##
-
-#SeeAlso setPath
-
-##
-
-#Method Iter(const SkPath& path, bool forceClose)
-#Line # constructs Path iterator ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    auto debugster = [](const char* prefix, SkPath::Iter& iter) -> void {
-        SkDebugf("%s:\n", prefix);
-        const char* verbStr[] =  { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };
-        const int pointCount[] = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };
-        SkPath::Verb verb;
-        do {
-           SkPoint points[4];
-           verb = iter.next(points);
-           SkDebugf("k%s_Verb ", verbStr[(int) verb]);
-           for (int i = 0; i < pointCount[(int) verb]; ++i) {
-                SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);
-           }
-           if (SkPath::kConic_Verb == verb) {
-               SkDebugf("weight = %g", iter.conicWeight());
-           }
-           SkDebugf("\n");
-        } while (SkPath::kDone_Verb != verb);
-        SkDebugf("\n");
-    };
-
-    SkPath path;
-    path.quadTo(10, 20, 30, 40);
-    SkPath::Iter openIter(path, false);
-    debugster("open", openIter);
-    SkPath::Iter closedIter(path, true);
-    debugster("closed", closedIter);
-}
-#StdOut
-open:
-kMove_Verb {0, 0},
-kQuad_Verb {0, 0}, {10, 20}, {30, 40},
-kDone_Verb
-
-closed:
-kMove_Verb {0, 0},
-kQuad_Verb {0, 0}, {10, 20}, {30, 40},
-kLine_Verb {30, 40}, {0, 0},
-kClose_Verb {0, 0},
-kDone_Verb
-##
-##
-
-#SeeAlso setPath
-
-##
-
-#Method void setPath(const SkPath& path, bool forceClose)
-#Line # resets Iter to Path ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    auto debugster = [](const char* prefix, SkPath::Iter& iter) -> void {
-        SkDebugf("%s:\n", prefix);
-        const char* verbStr[] =  { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };
-        const int pointCount[] = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };
-        SkPath::Verb verb;
-        do {
-           SkPoint points[4];
-           verb = iter.next(points);
-           SkDebugf("k%s_Verb ", verbStr[(int) verb]);
-           for (int i = 0; i < pointCount[(int) verb]; ++i) {
-                SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);
-           }
-           if (SkPath::kConic_Verb == verb) {
-               SkDebugf("weight = %g", iter.conicWeight());
-           }
-           SkDebugf("\n");
-        } while (SkPath::kDone_Verb != verb);
-        SkDebugf("\n");
-    };
-
-    SkPath path;
-    path.quadTo(10, 20, 30, 40);
-    SkPath::Iter iter(path, false);
-    debugster("quad open", iter);
-    SkPath path2;
-    path2.conicTo(1, 2, 3, 4, .5f);
-    iter.setPath(path2, true);
-    debugster("conic closed", iter);
-}
-#StdOut
-quad open:
-kMove_Verb {0, 0},
-kQuad_Verb {0, 0}, {10, 20}, {30, 40},
-kDone_Verb
-
-conic closed:
-kMove_Verb {0, 0},
-kConic_Verb {0, 0}, {1, 2}, {3, 4}, weight = 0.5
-kLine_Verb {3, 4}, {0, 0},
-kClose_Verb {0, 0},
-kDone_Verb
-##
-##
-
-#SeeAlso Iter(const SkPath& path, bool forceClose)
-
-##
-
-#Method Verb next(SkPoint pts[4], bool doConsumeDegenerates = true, bool exact = false)
-#Line # returns next Verb ##
-#Populate
-
-#Example
-#Description
-skip degenerate skips the first in a kMove_Verb pair, the kMove_Verb
-followed by the kClose_Verb, the zero length Line and the very small Line.
-
-skip degenerate if exact skips the same as skip degenerate, but shows
-the very small Line.
-
-skip none shows all of the Verbs and Points in Path.
-##
-void draw(SkCanvas* canvas) {
-    auto debugster = [](const char* prefix, const SkPath& path, bool degen, bool exact) -> void {
-        SkPath::Iter iter(path, false);
-        SkDebugf("%s:\n", prefix);
-        const char* verbStr[] =  { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };
-        const int pointCount[] = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };
-        SkPath::Verb verb;
-        do {
-           SkPoint points[4];
-           verb = iter.next(points, degen, exact);
-           SkDebugf("k%s_Verb ", verbStr[(int) verb]);
-           for (int i = 0; i < pointCount[(int) verb]; ++i) {
-                SkDebugf("{%1.8g, %1.8g}, ", points[i].fX, points[i].fY);
-           }
-           SkDebugf("\n");
-        } while (SkPath::kDone_Verb != verb);
-        SkDebugf("\n");
-    };
-
-    SkPath path;
-    path.moveTo(10, 10);
-    path.moveTo(20, 20);
-    path.quadTo(10, 20, 30, 40);
-    path.moveTo(1, 1);
-    path.close();
-    path.moveTo(30, 30);
-    path.lineTo(30, 30);
-    path.moveTo(30, 30);
-    path.lineTo(30.00001f, 30);
-    debugster("skip degenerate", path, true, false);
-    debugster("skip degenerate if exact", path, true, true);
-    debugster("skip none", path, false, false);
-}
-#StdOut
-skip degenerate:
-kMove_Verb {20, 20},
-kQuad_Verb {20, 20}, {10, 20}, {30, 40},
-kDone_Verb
-
-skip degenerate if exact:
-kMove_Verb {20, 20},
-kQuad_Verb {20, 20}, {10, 20}, {30, 40},
-kMove_Verb {30, 30},
-kLine_Verb {30, 30}, {30.00001, 30},
-kDone_Verb
-
-skip none:
-kMove_Verb {10, 10},
-kMove_Verb {20, 20},
-kQuad_Verb {20, 20}, {10, 20}, {30, 40},
-kMove_Verb {1, 1},
-kClose_Verb {1, 1},
-kMove_Verb {30, 30},
-kLine_Verb {30, 30}, {30, 30},
-kMove_Verb {30, 30},
-kLine_Verb {30, 30}, {30.00001, 30},
-kDone_Verb
-##
-##
-
-#SeeAlso Verb IsLineDegenerate IsCubicDegenerate IsQuadDegenerate
-
-##
-
-#Method SkScalar conicWeight() const
-#Line # returns Conic_Weight ##
-#Populate
-
-#Example
-    void draw(SkCanvas* canvas) {
-       SkPath path;
-       path.conicTo(1, 2, 3, 4, .5f);
-       SkPath::Iter iter(path, false);
-       SkPoint p[4];
-       SkDebugf("first verb is " "%s" "move\n", SkPath::kMove_Verb == iter.next(p) ? "" : "not ");
-       SkDebugf("next verb is " "%s" "conic\n", SkPath::kConic_Verb == iter.next(p) ? "" : "not ");
-       SkDebugf("conic points: {%g,%g}, {%g,%g}, {%g,%g}\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY,
-                    p[2].fX, p[2].fY);
-       SkDebugf("conic weight: %g\n", iter.conicWeight());
-    }
-    #StdOut
-first verb is move
-next verb is conic
-conic points: {0,0}, {1,2}, {3,4}
-conic weight: 0.5
-    ##
-    ##
-
-    #SeeAlso Conic_Weight
-
-##
-
-#Method bool isCloseLine() const
-#Line # returns if Line was generated by kClose_Verb ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-   SkPath path;
-   path.moveTo(6, 7);
-   path.conicTo(1, 2, 3, 4, .5f);
-   path.close();
-   SkPath::Iter iter(path, false);
-   SkPoint p[4];
-   SkDebugf("1st verb is " "%s" "move\n", SkPath::kMove_Verb == iter.next(p) ? "" : "not ");
-   SkDebugf("moveTo point: {%g,%g}\n", p[0].fX, p[0].fY);
-   SkDebugf("2nd verb is " "%s" "conic\n", SkPath::kConic_Verb == iter.next(p) ? "" : "not ");
-   SkDebugf("3rd verb is " "%s" "line\n", SkPath::kLine_Verb == iter.next(p) ? "" : "not ");
-   SkDebugf("line points: {%g,%g}, {%g,%g}\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY);
-   SkDebugf("line " "%s" "generated by close\n", iter.isCloseLine() ? "" : "not ");
-   SkDebugf("4th verb is " "%s" "close\n", SkPath::kClose_Verb == iter.next(p) ? "" : "not ");
-}
-    #StdOut
-1st verb is move
-moveTo point: {6,7}
-2nd verb is conic
-3rd verb is line
-line points: {3,4}, {6,7}
-line generated by close
-4th verb is close
-    ##
-    ##
-
-    #SeeAlso close()
-##
-
-#Method bool isClosedContour() const
-#Line # returns if Contour has kClose_Verb ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-   for (bool forceClose : { false, true } ) {
-       SkPath path;
-       path.conicTo(1, 2, 3, 4, .5f);
-       SkPath::Iter iter(path, forceClose);
-       SkDebugf("without close(), forceClose is %s: isClosedContour returns %s\n",
-           forceClose ? "true " : "false", iter.isClosedContour() ? "true" : "false");
-       path.close();
-       iter.setPath(path, forceClose);
-       SkDebugf("with close(),    forceClose is %s: isClosedContour returns %s\n",
-           forceClose ? "true " : "false", iter.isClosedContour() ? "true" : "false");
-    }
-}
-#StdOut
-without close(), forceClose is false: isClosedContour returns false
-with close(),    forceClose is false: isClosedContour returns true
-without close(), forceClose is true : isClosedContour returns true
-with close(),    forceClose is true : isClosedContour returns true
-##
-##
-
-#SeeAlso Iter(const SkPath& path, bool forceClose)
-
-##
-
-#Class Iter ##
-
-#Class RawIter
-#Line # raw data iterator ##
-
-#Code
-#Populate
-##
-
-Iterates through Verb_Array, and associated Point_Array and Conic_Weight.
-Verb_Array, Point_Array, and Conic_Weight are returned unaltered.
-
-
-    #Method RawIter()
-    #Line # constructs empty Path iterator ##
-#Populate
-
-#NoExample
-        ##
-    ##
-
-    #Method RawIter(const SkPath& path)
-    #Line # constructs with Path to iterate over ##
-#Populate
-
-#NoExample
-        ##
-    ##
-
-    #Method void setPath(const SkPath& path)
-    #Line # sets Path to iterate over ##
-#Populate
-
-#NoExample
-        ##
-   ##
-
-    #Method Verb next(SkPoint pts[4])
-    #Line # returns next Verb and associated Points ##
-#Populate
-
-#Example
-        void draw(SkCanvas* canvas) {
-            SkPath path;
-            path.moveTo(50, 60);
-            path.quadTo(10, 20, 30, 40);
-            path.close();
-            path.lineTo(30, 30);
-            path.conicTo(1, 2, 3, 4, .5f);
-            path.cubicTo(-1, -2, -3, -4, -5, -6);
-            SkPath::RawIter iter(path);
-            const char* verbStr[] =  { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };
-            const int pointCount[] = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };
-            SkPath::Verb verb;
-            do {
-                SkPoint points[4];
-                verb = iter.next(points);
-                SkDebugf("k%s_Verb ", verbStr[(int) verb]);
-                for (int i = 0; i < pointCount[(int) verb]; ++i) {
-                    SkDebugf("{%1.8g, %1.8g}, ", points[i].fX, points[i].fY);
-                }
-                if (SkPath::kConic_Verb == verb) {
-                    SkDebugf("weight = %g", iter.conicWeight());
-                }
-                SkDebugf("\n");
-            } while (SkPath::kDone_Verb != verb);
-        }
-    #StdOut
-        kMove_Verb {50, 60},
-        kQuad_Verb {50, 60}, {10, 20}, {30, 40},
-        kClose_Verb {50, 60},
-        kMove_Verb {50, 60},
-        kLine_Verb {50, 60}, {30, 30},
-        kConic_Verb {30, 30}, {1, 2}, {3, 4}, weight = 0.5
-        kCubic_Verb {3, 4}, {-1, -2}, {-3, -4}, {-5, -6},
-        kDone_Verb
-    ##
-    ##
-
-    #SeeAlso peek()
-
-    ##
-
-    #Method Verb peek() const
-    #Line # returns next Verb ##
-    #Populate
-
-        #Example
-            SkPath path;
-            path.quadTo(10, 20, 30, 40);
-            path.conicTo(1, 2, 3, 4, .5f);
-            path.cubicTo(1, 2, 3, 4, .5, 6);
-            SkPath::RawIter iter(path);
-            SkPath::Verb verb, peek = iter.peek();
-            const char* verbStr[] =  { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };
-            do {
-                SkPoint points[4];
-                verb = iter.next(points);
-                SkDebugf("peek %s %c= verb %s\n", verbStr[peek], peek == verb ? '=' : '!', verbStr[verb]);
-                peek = iter.peek();
-            } while (SkPath::kDone_Verb != verb);
-            SkDebugf("peek %s %c= verb %s\n", verbStr[peek], peek == verb ? '=' : '!', verbStr[verb]);
-            #StdOut
-                #Volatile
-                peek Move == verb Move
-                peek Quad == verb Quad
-                peek Conic == verb Conic
-                peek Cubic == verb Cubic
-                peek Done == verb Done
-                peek Done == verb Done
-            ##
-        ##
-
-        #Bug 6832
-        # StdOut is not really volatile, it just produces the wrong result.
-        # A simple fix changes the output of hairlines and needs to be
-        # investigated to see if the change is correct or not.
-        # see change 21340 (abandoned for now)
-
-        #SeeAlso next
-
-    ##
-
-    #Method SkScalar conicWeight() const
-    #Line # returns Conic_Weight ##
-#Populate
-
-#Example
-    void draw(SkCanvas* canvas) {
-       SkPath path;
-       path.conicTo(1, 2, 3, 4, .5f);
-       SkPath::RawIter iter(path);
-       SkPoint p[4];
-       SkDebugf("first verb is " "%s" "move\n", SkPath::kMove_Verb == iter.next(p) ? "" : "not ");
-       SkDebugf("next verb is " "%s" "conic\n", SkPath::kConic_Verb == iter.next(p) ? "" : "not ");
-       SkDebugf("conic points: {%g,%g}, {%g,%g}, {%g,%g}\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY,
-                    p[2].fX, p[2].fY);
-       SkDebugf("conic weight: %g\n", iter.conicWeight());
-    }
-    #StdOut
-        first verb is move
-        next verb is conic
-        conic points: {0,0}, {1,2}, {3,4}
-        conic weight: 0.5
-    ##
-    ##
-
-    #SeeAlso Conic_Weight
-
-    ##
-
-#Class RawIter ##
-
-#Class SkPath ##
-
-#Topic Path ##
diff --git a/docs/SkPicture_Reference.bmh b/docs/SkPicture_Reference.bmh
deleted file mode 100644
index 16c8926..0000000
--- a/docs/SkPicture_Reference.bmh
+++ /dev/null
@@ -1,402 +0,0 @@
-#Topic Picture
-#Alias Pictures ##
-#Alias Picture_Reference ##
-
-#Class SkPicture
-
-#Code
-#Populate
-##
-
-Picture records drawing commands made to Canvas. The command stream may be
-played in whole or in part at a later time.
-
-Picture is an abstract class. Picture may be generated by Picture_Recorder
-or Drawable, or from Picture previously saved to Data or Stream. 
-
-Picture may contain any Canvas drawing command, as well as one or more
-Canvas_Matrix or Canvas_Clip. Picture has a cull Rect, which is used as
-a bounding box hint. To limit Picture bounds, use Canvas_Clip when
-recording or drawing Picture.
-
-# ------------------------------------------------------------------------------
-
-#Class AbortCallback
-#Line # utility to stop picture playback ##
-
-#Code
-    class AbortCallback {
-    public:
-        AbortCallback() {}
-        virtual ~AbortCallback() {}
-        virtual bool abort() = 0;
-    };
-##
-
-AbortCallback is an abstract class. An implementation of AbortCallback may
-passed as a parameter to SkPicture::playback, to stop it before all drawing
-commands have been processed.
-
-If AbortCallback::abort returns true, SkPicture::playback is interrupted.
-
-# ------------------------------------------------------------------------------
-
-#Method AbortCallback()
-#In Constructors
-#Line # defines default constructor ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso playback
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual ~AbortCallback()
-#In Constructors
-#Line # defines default destructor ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso playback
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual bool abort() = 0
-#In Utility
-#Line # aborts playback by callback ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso playback
-
-#Method ##
-
-#Example
-#Description
-JustOneDraw allows the black rectangle to draw but stops playback before the
-white rectangle appears.
-##
-#Function
-class JustOneDraw : public SkPicture::AbortCallback {
-public:
-    bool abort() override { return fCalls++ > 0; }
-private:
-    int fCalls = 0;
-};
-##
-
-void draw(SkCanvas* canvas) {
-    SkPictureRecorder recorder;
-    SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
-    SkPaint paint;
-    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
-    paint.setColor(SK_ColorWHITE);
-    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
-    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
-    JustOneDraw callback;
-    picture->playback(canvas, &callback);
-}
-
-##
-
-#Class AbortCallback ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkPicture> MakeFromStream(SkStream* stream,
-                                           const SkDeserialProcs* procs = nullptr)
-#In Constructors
-#Line # constructs Picture from stream ##
-#Populate
-
-#Example
-    SkPictureRecorder recorder;
-    SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
-    SkPaint paint;
-    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
-    paint.setColor(SK_ColorWHITE);
-    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
-    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
-    SkDynamicMemoryWStream writableStream;
-    picture->serialize(&writableStream);
-    std::unique_ptr<SkStreamAsset> readableStream = writableStream.detachAsStream();
-    sk_sp<SkPicture> copy = SkPicture::MakeFromStream(readableStream.get());
-    copy->playback(canvas);
-##
-
-#SeeAlso MakeFromData SkPictureRecorder
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkPicture> MakeFromData(const SkData* data,
-                                         const SkDeserialProcs* procs = nullptr)
-#In Constructors
-#Line # constructs Picture from data ##
-#Populate
-
-#Example
-    SkPictureRecorder recorder;
-    SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
-    SkPaint paint;
-    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
-    paint.setColor(SK_ColorWHITE);
-    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
-    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
-    SkDynamicMemoryWStream writableStream;
-    picture->serialize(&writableStream);
-    sk_sp<SkData> readableData = writableStream.detachAsData();
-    sk_sp<SkPicture> copy = SkPicture::MakeFromData(readableData.get());
-    copy->playback(canvas);
-##
-
-#SeeAlso MakeFromStream SkPictureRecorder
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkPicture> MakeFromData(const void* data, size_t size,
-                                         const SkDeserialProcs* procs = nullptr)
-#Populate
-
-#Example
-    SkPictureRecorder recorder;
-    SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
-    SkPaint paint;
-    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
-    paint.setColor(SK_ColorWHITE);
-    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
-    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
-    SkDynamicMemoryWStream writableStream;
-    picture->serialize(&writableStream);
-    sk_sp<SkData> readableData = writableStream.detachAsData();
-    sk_sp<SkPicture> copy = SkPicture::MakeFromData(readableData->data(), readableData->size());
-    copy->playback(canvas);
-##
-
-#SeeAlso MakeFromStream SkPictureRecorder
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual void playback(SkCanvas* canvas, AbortCallback* callback = nullptr) const = 0
-#In Action
-#Line # replays drawing commands on canvas ##
-#Populate
-
-#Example
-    SkPictureRecorder recorder;
-    SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
-    SkPaint paint;
-    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
-    paint.setColor(SK_ColorWHITE);
-    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
-    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
-    picture->playback(canvas);
-##
-
-#SeeAlso SkCanvas::drawPicture
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual SkRect cullRect() const = 0
-#In Property
-#Line # returns bounds used to record Picture ##
-#Populate
-
-#Example
-#Description
-Picture recorded bounds are smaller than contents; contents outside recorded
-bounds may be drawn, and are drawn in this example.
-##
-    SkPictureRecorder recorder;
-    SkCanvas* pictureCanvas = recorder.beginRecording({64, 64, 192, 192});
-    SkPaint paint;
-    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
-    paint.setColor(SK_ColorWHITE);
-    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
-    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
-    picture->playback(canvas);
-    paint.setBlendMode(SkBlendMode::kModulate);
-    paint.setColor(0x40404040);
-    canvas->drawRect(picture->cullRect(), paint);
-##
-
-#SeeAlso SkCanvas::clipRect
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method uint32_t uniqueID() const
-#In Property
-#Line # returns identifier for Picture ##
-#Populate
-
-#Example
-    SkPictureRecorder recorder;
-    recorder.beginRecording({0, 0, 0, 0});
-    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
-    SkDebugf("empty picture id = %d\n", picture->uniqueID());
-    sk_sp<SkPicture> placeholder = SkPicture::MakePlaceholder({0, 0, 0, 0});
-    SkDebugf("placeholder id = %d\n", placeholder->uniqueID());
-#StdOut
-empty picture id = 1
-placeholder id = 2
-##
-##
-
-#SeeAlso SkRefCnt
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkData> serialize(const SkSerialProcs* procs = nullptr) const
-#In Utility
-#Line # writes Picture to Data ##
-#Populate
-
-#Example
-    SkPictureRecorder recorder;
-    SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
-    SkPaint paint;
-    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
-    paint.setColor(SK_ColorWHITE);
-    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
-    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
-    sk_sp<SkData> readableData = picture->serialize();
-    sk_sp<SkPicture> copy = SkPicture::MakeFromData(readableData->data(), readableData->size());
-    copy->playback(canvas);
-##
-
-#SeeAlso MakeFromData SkData SkSerialProcs
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void serialize(SkWStream* stream, const SkSerialProcs* procs = nullptr) const
-#Populate
-
-#Example
-    SkPictureRecorder recorder;
-    SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
-    SkPaint paint;
-    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
-    paint.setColor(SK_ColorWHITE);
-    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
-    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
-    SkDynamicMemoryWStream writableStream;
-    picture->serialize(&writableStream);
-    sk_sp<SkData> readableData = writableStream.detachAsData();
-    sk_sp<SkPicture> copy = SkPicture::MakeFromData(readableData->data(), readableData->size());
-    copy->playback(canvas);
-##
-
-#SeeAlso MakeFromStream SkWStream SkSerialProcs
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkPicture> MakePlaceholder(SkRect cull)
-#In Constructors
-#Line # constructs placeholder with unique identifier ##
-#Populate
-
-#Example
-#Function
-class MyCanvas : public SkCanvas {
-public:
-    MyCanvas(SkCanvas* c) : canvas(c) {}
-        void onDrawPicture(const SkPicture* picture, const SkMatrix* ,
-                               const SkPaint* ) override {
-        const SkRect rect = picture->cullRect();
-        SkPaint redPaint;
-        redPaint.setColor(SK_ColorRED);
-        canvas->drawRect(rect, redPaint);
-   }
-
-   SkCanvas* canvas;
-};
-##
-SkPictureRecorder recorder;
-SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
-sk_sp<SkPicture> placeholder = SkPicture::MakePlaceholder({10, 40, 80, 110});
-pictureCanvas->drawPicture(placeholder);
-sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
-MyCanvas myCanvas(canvas);
-myCanvas.drawPicture(picture);
-##
-
-#SeeAlso MakeFromStream MakeFromData uniqueID
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual int approximateOpCount() const = 0
-#In Property
-#Line # returns approximate operation count ##
-#Populate
-
-#Example
-    SkPictureRecorder recorder;
-    SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
-    SkPaint paint;
-    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
-    paint.setColor(SK_ColorWHITE);
-    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
-    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
-    picture->playback(canvas);
-    std::string opCount = "approximate op count: " + std::to_string(picture->approximateOpCount());
-    canvas->drawString(opCount.c_str(), 50, 220, SkPaint());
-##
-
-#SeeAlso approximateBytesUsed
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual size_t approximateBytesUsed() const = 0
-#In Property
-#Line # returns approximate size ##
-#Populate
-
-#Example
-    SkPictureRecorder recorder;
-    SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
-    SkPaint paint;
-    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
-    paint.setColor(SK_ColorWHITE);
-    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
-    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
-    picture->playback(canvas);
-    std::string opCount = "approximate bytes used: " + std::to_string(picture->approximateBytesUsed());
-    canvas->drawString(opCount.c_str(), 20, 220, SkPaint());
-##
-
-#SeeAlso approximateOpCount
-
-#Method ##
-
-#Class SkPicture ##
-
-#Topic Picture ##
diff --git a/docs/SkPixmap_Reference.bmh b/docs/SkPixmap_Reference.bmh
deleted file mode 100644
index 1515b8e..0000000
--- a/docs/SkPixmap_Reference.bmh
+++ /dev/null
@@ -1,1514 +0,0 @@
-#Topic Pixmap
-#Alias Pixmap_Reference ##
-
-#Class SkPixmap
-
-#Code
-#Populate
-##
-
-Pixmap provides a utility to pair SkImageInfo with pixels and row bytes.
-Pixmap is a low level class which provides convenience functions to access
-raster destinations. Canvas can not draw Pixmap, nor does Pixmap provide
-a direct drawing destination.
-
-Use Bitmap to draw pixels referenced by Pixmap; use Surface to draw into
-pixels referenced by Pixmap.
-
-Pixmap does not try to manage the lifetime of the pixel memory. Use Pixel_Ref
-to manage pixel memory; Pixel_Ref is safe across threads.
-
-
-#Subtopic Initialization
-#Line # sets fields for use ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPixmap()
-
-#In Initialization
-#Line # constructs with default values ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    const char* alphas[] = {"Unknown", "Opaque", "Premul", "Unpremul"};
-    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
-                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16"};
-    SkPixmap pixmap;
-    for (int i = 0; i < 2; ++i) {
-       SkDebugf("width: %2d  height: %2d", pixmap.width(), pixmap.height());
-       SkDebugf("  color: k%s_SkColorType", colors[pixmap.colorType()]);
-       SkDebugf("  alpha: k%s_SkAlphaType\n", alphas[pixmap.alphaType()]);
-       pixmap.reset(SkImageInfo::Make(25, 35, kRGBA_8888_SkColorType, kOpaque_SkAlphaType),
-                    nullptr, 0);
-    }
-}
-#StdOut
-width:  0  height:  0  color: kUnknown_SkColorType  alpha: kUnknown_SkAlphaType
-width: 25  height: 35  color: kRGBA_8888_SkColorType  alpha: kOpaque_SkAlphaType
-##
-##
-
-#SeeAlso SkPixmap(const SkImageInfo& info, const void* addr, size_t rowBytes) reset() SkAlphaType SkColorType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPixmap(const SkImageInfo& info, const void* addr, size_t rowBytes)
-
-#In Initialization
-#Line # constructs from Image_Info, pixels ##
-#Populate
-
-#Example
-#Image 3
-#Description
-SkImage::MakeRasterCopy takes const SkPixmap& as an argument. The example
-constructs a SkPixmap from the brace-delimited parameters.
-##
-    SkDebugf("image alpha only = %s\n", image->isAlphaOnly() ? "true" : "false");
-    SkPMColor pmColors = 0;
-    sk_sp<SkImage> copy = SkImage::MakeRasterCopy({SkImageInfo::MakeA8(1, 1),
-                                                  (uint8_t*)&pmColors,
-                                                  1});
-    SkDebugf("copy alpha only = %s\n", copy->isAlphaOnly() ? "true" : "false");
-#StdOut
-image alpha only = false
-copy alpha only = true
-##
-##
-
-#SeeAlso SkPixmap() reset() SkAlphaType SkColorType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void reset()
-
-#In Initialization
-#Line # reuses existing Pixmap with replacement values ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    const char* alphas[] = {"Unknown", "Opaque", "Premul", "Unpremul"};
-    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
-                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16"};
-    SkPixmap pixmap(SkImageInfo::Make(25, 35, kRGBA_8888_SkColorType, kOpaque_SkAlphaType),
-                    nullptr, 0);
-    for (int i = 0; i < 2; ++i) {
-       SkDebugf("width: %2d  height: %2d", pixmap.width(), pixmap.height());
-       SkDebugf("  color: k%s_SkColorType", colors[pixmap.colorType()]);
-       SkDebugf("  alpha: k%s_SkAlphaType\n", alphas[pixmap.alphaType()]);
-       pixmap.reset();
-    }
-}
-#StdOut
-width: 25  height: 35  color: kRGBA_8888_SkColorType  alpha: kOpaque_SkAlphaType
-width:  0  height:  0  color: kUnknown_SkColorType  alpha: kUnknown_SkAlphaType
-##
-##
-
-#SeeAlso SkPixmap() SkAlphaType SkColorType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void reset(const SkImageInfo& info, const void* addr, size_t rowBytes)
-
-#In Initialization
-#Populate
-
-#Example
-#Image 4
-#Height 64
-void draw(SkCanvas* canvas) {
-    std::vector<int32_t> pixels;
-    pixels.resize(image->height() * image->width() * 4);
-    SkPixmap pixmap(SkImageInfo::Make(image->width(), image->height(), kN32_SkColorType,
-            image->alphaType()), (const void*) &pixels.front(), image->width() * 4);
-    image->readPixels(pixmap, 0, 0);
-    int x = 0;
-    for (auto colorType : { kRGBA_8888_SkColorType, kBGRA_8888_SkColorType } ) {
-        pixmap.reset(SkImageInfo::Make(image->width(), image->height(), colorType,
-                image->alphaType()), (const void*) &pixels.front(), image->width() * 4);
-        SkBitmap bitmap;
-        bitmap.installPixels(pixmap);
-        canvas->drawBitmap(bitmap, x, 0);
-        x += 128;
-    }
-}
-##
-
-#SeeAlso SkPixmap(const SkImageInfo& info, const void* addr, size_t rowBytes) reset() SkAlphaType SkColorType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void setColorSpace(sk_sp<SkColorSpace> colorSpace)
-
-#In Initialization
-#Line # sets Image_Info Color_Space ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    SkPixmap pixmap;
-    sk_sp<SkColorSpace> colorSpace1 = SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear,
-                                                            SkNamedGamut::kRec2020);
-    SkDebugf("is %sunique\n", colorSpace1->unique() ? "" : "not ");
-    pixmap.setColorSpace(colorSpace1);
-    SkDebugf("is %sunique\n", colorSpace1->unique() ? "" : "not ");
-}
-#StdOut
-is unique
-is not unique
-##
-##
-
-#SeeAlso Color_Space SkImageInfo::makeColorSpace
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool extractSubset(SkPixmap* subset, const SkIRect& area) const
-
-#In Initialization
-#Line # sets pointer to portion of original ##
-#Populate
-
-#Example
-#Image 3
-#Height 128
-void draw(SkCanvas* canvas) {
-    std::vector<int32_t> pixels;
-    pixels.resize(image->height() * image->width() * 4);
-    SkPixmap pixmap(SkImageInfo::Make(image->width(), image->height(), kN32_SkColorType,
-            image->alphaType()), (const void*) &pixels.front(), image->width() * 4);
-    image->readPixels(pixmap, 0, 0);
-    SkPixmap inset;
-    if (pixmap.extractSubset(&inset, {128, 128, 512, 512})) {
-        SkBitmap bitmap;
-        bitmap.installPixels(inset);
-        canvas->drawBitmap(bitmap, 0, 0);
-    }
-}
-##
-
-#SeeAlso reset() SkIRect::intersect
-
-##
-
-#Subtopic Initialization ##
-
-#Subtopic Image_Info_Access
-#Line # returns all or part of Image_Info ##
-
-# ------------------------------------------------------------------------------
-
-#Method const SkImageInfo& info() const
-
-#In Image_Info_Access
-#Line # returns Image_Info ##
-#Populate
-
-#Example
-#Image 3
-    std::vector<int32_t> pixels;
-    pixels.resize(image->height() * image->width() * 4);
-    SkPixmap pixmap(SkImageInfo::Make(image->width(), image->height(), kN32_SkColorType,
-            image->alphaType()), (const void*) &pixels.front(), image->width() * 4);
-    image->readPixels(pixmap, 0, 0);
-    SkPixmap inset;
-    if (pixmap.extractSubset(&inset, {128, 128, 512, 512})) {
-        const SkImageInfo& info = inset.info();
-        const char* alphas[] = {"Unknown", "Opaque", "Premul", "Unpremul"};
-        const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888",
-                "RGB_888x", "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16"};
-        SkDebugf("width: %d height: %d color: %s alpha: %s\n", info.width(), info.height(),
-                 colors[info.colorType()], alphas[info.alphaType()]);
-    }
-#StdOut
-width: 384 height: 384 color: BGRA_8888 alpha: Opaque
-##
-##
-
-#SeeAlso Image_Info
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t rowBytes() const
-
-#In Image_Info_Access
-#Line # returns interval between rows in bytes ##
-Returns row bytes, the interval from one pixel row to the next. Row bytes
-is at least as large as: #Formula # width() * info().bytesPerPixel() ##.
-
-Returns zero if colorType is kUnknown_SkColorType.
-It is up to the Bitmap creator to ensure that row bytes is a useful value.
-
-#Return  byte length of pixel row ##
-
-#Example
-SkPixmap badPixmap = {SkImageInfo::MakeA8(4, 4), nullptr, 2};
-SkPixmap okPixmap = {SkImageInfo::MakeA8(4, 4), nullptr, 8};
-for (auto& pixmap : { badPixmap, okPixmap } ) {
-    SkDebugf("rowBytes: %d minRowBytes: %d\n", pixmap.rowBytes(),
-       pixmap.info().minRowBytes());
-}
-#StdOut
-rowBytes: 2 minRowBytes: 4
-rowBytes: 8 minRowBytes: 4
-##
-##
-
-#SeeAlso addr() info() SkImageInfo::minRowBytes
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method const void* addr() const
-
-#In Image_Info_Access
-#Line # returns readable pixel address as void pointer ##
-#Populate
-
-#Example
-#Image 3
-    std::vector<int32_t> pixels;
-    pixels.resize(image->height() * image->width() * 4);
-    SkPixmap pixmap(SkImageInfo::Make(image->width(), image->height(), kN32_SkColorType,
-            image->alphaType()), (const void*) &pixels.front(), image->width() * 4);
-    image->readPixels(pixmap, 0, 0);
-    SkDebugf("pixels address: 0x%llx\n", pixmap.addr());
-    SkPixmap inset;
-    if (pixmap.extractSubset(&inset, {128, 128, 512, 512})) {
-         SkDebugf("inset address:  0x%llx\n", inset.addr());
-    }
-#StdOut
-#Volatile
-pixels address: 0x7f2a440bb010
-inset address:  0x7f2a440fb210
-##
-##
-
-#SeeAlso addr(int x, int y) addr8 addr16 addr32 addr64 info() rowBytes()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int width() const
-
-#In Image_Info_Access
-#Line # returns pixel column count ##
-Returns pixel count in each pixel row. Should be equal or less than:
-
-#Formula # rowBytes() / info().bytesPerPixel() ##.
-
-#Return  pixel width in Image_Info ##
-
-#Example
-    SkImageInfo info = SkImageInfo::MakeA8(16, 32);
-    SkPixmap pixmap(info, nullptr, 64);
-    SkDebugf("pixmap width: %d  info width: %d\n", pixmap.width(), info.width());
-#StdOut
-pixmap width: 16  info width: 16
-##
-##
-
-#SeeAlso height() SkImageInfo::width()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int height() const
-
-#In Image_Info_Access
-#Line # returns pixel row count ##
-#Populate
-
-#Example
-    SkPixmap pixmap(SkImageInfo::MakeA8(16, 32), nullptr, 64);
-    SkDebugf("pixmap height: %d  info height: %d\n", pixmap.height(), pixmap.info().height());
-#StdOut
-pixmap height: 32  info height: 32
-##
-##
-
-#SeeAlso width SkImageInfo::height
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkColorType colorType() const
-
-#In Image_Info_Access
-#Line # returns Image_Info Color_Type ##
-Returns Color_Type, one of: #list_of_color_types#.
-
-#Return  Color_Type in Image_Info ##
-
-#Example
-    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
-                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16"};
-    SkPixmap pixmap(SkImageInfo::MakeA8(16, 32), nullptr, 64);
-    SkDebugf("color type: k" "%s" "_SkColorType\n", colors[pixmap.colorType()]);
-#StdOut
-color type: kAlpha_8_SkColorType
-##
-##
-
-#SeeAlso alphaType() SkImageInfo::colorType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkAlphaType alphaType() const
-
-#In Image_Info_Access
-#Line # returns Image_Info Alpha_Type ##
-Returns Alpha_Type, one of: #list_of_alpha_types#.
-
-#Return  Alpha_Type in Image_Info ##
-
-#Example
-    const char* alphas[] = {"Unknown", "Opaque", "Premul", "Unpremul"};
-    SkPixmap pixmap(SkImageInfo::MakeA8(16, 32), nullptr, 64);
-    SkDebugf("alpha type: k" "%s" "_SkAlphaType\n", alphas[pixmap.alphaType()]);
-#StdOut
-alpha type: kPremul_SkAlphaType
-##
-##
-
-#SeeAlso colorType() SkImageInfo::alphaType
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkColorSpace* colorSpace() const
-
-#In Image_Info_Access
-#Line # returns Image_Info Color_Space ##
-#Populate
-
-#Example
-#Description
-SkColorSpace::MakeSRGBLinear creates Color_Space with linear gamma
-and an sRGB gamut. This Color_Space gamma is not close to sRGB gamma.
-##
-    SkPixmap pixmap(SkImageInfo::MakeN32(16, 32, kPremul_SkAlphaType,
-            SkColorSpace::MakeSRGBLinear()), nullptr, 64);
-    SkColorSpace* colorSpace = pixmap.colorSpace();
-    SkDebugf("gammaCloseToSRGB: %s  gammaIsLinear: %s  isSRGB: %s\n",
-            colorSpace->gammaCloseToSRGB() ? "true" : "false",
-            colorSpace->gammaIsLinear() ? "true" : "false",
-            colorSpace->isSRGB() ? "true" : "false");
-#StdOut
-gammaCloseToSRGB: false  gammaIsLinear: true  isSRGB: false
-##
-##
-
-#SeeAlso Color_Space SkImageInfo::colorSpace
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isOpaque() const
-
-#In Image_Info_Access
-#Line # returns true if Image_Info describes opaque pixels ##
-#Populate
-
-#Example
-#Description
-    isOpaque ignores whether all pixels are opaque or not.
-##
-    std::vector<uint32_t> pixels;
-    const int height = 2;
-    const int width = 2;
-    pixels.resize(height * width * 4);
-    SkPixmap pixmap(SkImageInfo::Make(width, height, kN32_SkColorType,
-            kPremul_SkAlphaType), (const void*) &pixels.front(), width * 4);
-    for (int index = 0; index < 2; ++index) {
-        pixmap.erase(0x00000000);
-        SkDebugf("isOpaque: %s\n", pixmap.isOpaque() ? "true" : "false");
-        pixmap.erase(0xFFFFFFFF);
-        SkDebugf("isOpaque: %s\n", pixmap.isOpaque() ? "true" : "false");
-        pixmap.reset(pixmap.info().makeAlphaType(kOpaque_SkAlphaType),
-                     (const void*) &pixels.front(), width * 4);
-    }
-#StdOut
-isOpaque: false
-isOpaque: false
-isOpaque: true
-isOpaque: true
-##
-##
-
-#SeeAlso computeIsOpaque SkImageInfo::isOpaque
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkIRect bounds() const
-
-#In Image_Info_Access
-#Line # returns width and height as Rectangle ##
-#Populate
-
-#Example
-    for (int width : { 0, 2 } ) {
-        for (int height : { 0, 2 } ) {
-             SkPixmap pixmap(SkImageInfo::MakeA8(width, height), nullptr, width);
-             SkDebugf("width: %d height: %d empty: %s\n", width, height,
-                      pixmap.bounds().isEmpty() ? "true" : "false");
-        }
-    }
-#StdOut
-width: 0 height: 0 empty: true
-width: 0 height: 2 empty: true
-width: 2 height: 0 empty: true
-width: 2 height: 2 empty: false
-##
-##
-
-#SeeAlso height() width() IRect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int rowBytesAsPixels() const
-
-#In Image_Info_Access
-#Line # returns interval between rows in pixels ##
-#Populate
-
-#Example
-    for (int rowBytes : { 4, 5, 6, 7, 8} ) {
-        SkPixmap pixmap(SkImageInfo::MakeN32(1, 1, kPremul_SkAlphaType), nullptr, rowBytes);
-        SkDebugf("rowBytes: %d rowBytesAsPixels: %d\n", rowBytes, pixmap.rowBytesAsPixels());
-    }
-#StdOut
-rowBytes: 4 rowBytesAsPixels: 1
-rowBytes: 5 rowBytesAsPixels: 1
-rowBytes: 6 rowBytesAsPixels: 1
-rowBytes: 7 rowBytesAsPixels: 1
-rowBytes: 8 rowBytesAsPixels: 2
-##
-##
-
-#SeeAlso rowBytes shiftPerPixel width SkImageInfo::bytesPerPixel
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method int shiftPerPixel() const
-
-#In Image_Info_Access
-#Line # returns bit shift from pixels to bytes ##
-#Populate
-
-#Example
-    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
-                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16"};
-    SkImageInfo info = SkImageInfo::MakeA8(1, 1);
-    for (SkColorType colorType : { kUnknown_SkColorType,   kAlpha_8_SkColorType,
-                                   kRGB_565_SkColorType,   kARGB_4444_SkColorType,
-                                   kRGBA_8888_SkColorType, kBGRA_8888_SkColorType,
-                                   kGray_8_SkColorType,    kRGBA_F16_SkColorType } ) {
-        SkPixmap pixmap(info.makeColorType(colorType), nullptr, 4);
-        SkDebugf("color: k" "%s" "_SkColorType" "%*s" "bytesPerPixel: %d shiftPerPixel: %d\n",
-                colors[colorType], 10 - strlen(colors[colorType]), " ",
-                pixmap.info().bytesPerPixel(), pixmap.shiftPerPixel());
-    }
-#StdOut
-color: kUnknown_SkColorType   bytesPerPixel: 0 shiftPerPixel: 0
-color: kAlpha_8_SkColorType   bytesPerPixel: 1 shiftPerPixel: 0
-color: kRGB_565_SkColorType   bytesPerPixel: 2 shiftPerPixel: 1
-color: kARGB_4444_SkColorType bytesPerPixel: 2 shiftPerPixel: 1
-color: kRGBA_8888_SkColorType bytesPerPixel: 4 shiftPerPixel: 2
-color: kBGRA_8888_SkColorType bytesPerPixel: 4 shiftPerPixel: 2
-color: kGray_8_SkColorType    bytesPerPixel: 1 shiftPerPixel: 0
-color: kRGBA_F16_SkColorType  bytesPerPixel: 8 shiftPerPixel: 3
-##
-##
-
-#SeeAlso rowBytes rowBytesAsPixels width SkImageInfo::bytesPerPixel
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t computeByteSize() const
-
-#In Image_Info_Access
-#Line # returns size required for pixels ##
-#Populate
-
-#Example
-    SkPixmap pixmap;
-    for (int width : { 1, 1000, 1000000 } ) {
-        for (int height: { 1, 1000, 1000000 } ) {
-            SkImageInfo imageInfo = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType);
-            pixmap.reset(imageInfo, nullptr, width * 5);
-            SkDebugf("width: %7d height: %7d computeByteSize: %13lld\n", width, height,
-                     pixmap.computeByteSize());
-        }
-    }
-#StdOut
-width:       1 height:       1 computeByteSize:             4
-width:       1 height:    1000 computeByteSize:          4999
-width:       1 height: 1000000 computeByteSize:       4999999
-width:    1000 height:       1 computeByteSize:          4000
-width:    1000 height:    1000 computeByteSize:       4999000
-width:    1000 height: 1000000 computeByteSize:    4999999000
-width: 1000000 height:       1 computeByteSize:       4000000
-width: 1000000 height:    1000 computeByteSize:    4999000000
-width: 1000000 height: 1000000 computeByteSize: 4999999000000
-##
-##
-
-#SeeAlso SkImageInfo::computeByteSize
-
-##
-
-#Subtopic Image_Info_Access ##
-
-#Subtopic Reader
-#Line # examine pixel value ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool computeIsOpaque() const
-
-#In Reader
-#Line # returns true if all pixels are opaque ##
-#Populate
-
-#Example
-    std::vector<uint32_t> pixels;
-    const int height = 2;
-    const int width = 2;
-    pixels.resize(height * width * 4);
-    SkPixmap pixmap(SkImageInfo::Make(width, height, kN32_SkColorType,
-            kPremul_SkAlphaType), (const void*) &pixels.front(), width * 4);
-    for (int index = 0; index < 2; ++index) {
-        pixmap.erase(0x00000000);
-        SkDebugf("computeIsOpaque: %s\n", pixmap.computeIsOpaque() ? "true" : "false");
-        pixmap.erase(0xFFFFFFFF);
-        SkDebugf("computeIsOpaque: %s\n", pixmap.computeIsOpaque() ? "true" : "false");
-        pixmap.reset(pixmap.info().makeAlphaType(kOpaque_SkAlphaType),
-                     (const void*) &pixels.front(), width * 4);
-    }
-#StdOut
-computeIsOpaque: false
-computeIsOpaque: true
-computeIsOpaque: false
-computeIsOpaque: true
-##
-##
-
-#SeeAlso isOpaque Color_Type Alpha
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkColor getColor(int x, int y) const
-
-#In Reader
-#Line # returns one pixel as Unpremultiplied Color ##
-#Populate
-
-#Example
-    const int w = 4;
-    const int h = 4;
-    std::vector<SkPMColor> storage;
-    storage.resize(w * h);
-    SkDebugf("Premultiplied:\n");
-    for (int y = 0; y < h; ++y) {
-        SkDebugf("(0, %d) ", y);
-        for (int x = 0; x < w; ++x) {
-            int a = 0xFF * (x + y) / (w - 1 + h - 1);
-            storage[x + y * w] = SkPackARGB32(a, a * x / (w - 1), a * y / (h - 1), a);
-            SkDebugf("0x%08x%c", storage[x + y * w], x == w - 1 ? '\n' : ' ');
-        }
-    }
-    SkPixmap pixmap(SkImageInfo::MakeN32(w, h, kPremul_SkAlphaType), &storage.front(), w * 4);
-    SkDebugf("Unpremultiplied:\n");
-    for (int y = 0; y < h; ++y) {
-        SkDebugf("(0, %d) ", y);
-        for (int x = 0; x < w; ++x) {
-            SkDebugf("0x%08x%c", pixmap.getColor(x, y), x == w - 1 ? '\n' : ' ');
-        }
-    }
-#StdOut
-Premultiplied:
-(0, 0) 0x00000000 0x2a0e002a 0x55380055 0x7f7f007f
-(0, 1) 0x2a000e2a 0x551c1c55 0x7f542a7f 0xaaaa38aa
-(0, 2) 0x55003855 0x7f2a547f 0xaa7171aa 0xd4d48dd4
-(0, 3) 0x7f007f7f 0xaa38aaaa 0xd48dd4d4 0xffffffff
-Unpremultiplied:
-(0, 0) 0x00000000 0x2a5500ff 0x55a800ff 0x7fff00ff
-(0, 1) 0x2a0055ff 0x555454ff 0x7fa954ff 0xaaff54ff
-(0, 2) 0x5500a8ff 0x7f54a9ff 0xaaaaaaff 0xd4ffaaff
-(0, 3) 0x7f00ffff 0xaa54ffff 0xd4aaffff 0xffffffff
-##
-##
-
-#SeeAlso getAlphaf addr() readPixels
-
-##
-
-#Method float getAlphaf(int x, int y) const
-#In Property
-#Line # returns Alpha normalized from zero to one ##
-
-Looks up the pixel at (x,y) and return its alpha component, normalized to [0..1].
-This is roughly equivalent to #Formula # SkGetColorA(getColor()) ##, but can be more efficient
-(and more precise if the pixels store more than 8 bits per component).
-
-#Param x  column index, zero or greater, and less than width() ##
-#Param y  row index, zero or greater, and less than height() ##
-
-#Return   alpha converted to normalized float ##
-
-#NoExample
-##
-
-#SeeAlso getColor
-
-##
-
-#Subtopic Reader ##
-
-#Subtopic Readable_Address
-#Line # returns read only pixels ##
-
-# ------------------------------------------------------------------------------
-
-#Method const void* addr(int x, int y) const
-
-#In Readable_Address
-#Populate
-
-#Example
-    const int w = 4;
-    const int h = 4;
-    std::vector<SkPMColor> storage;
-    storage.resize(w * h);
-    SkPixmap pixmap(SkImageInfo::MakeN32(w, h, kPremul_SkAlphaType), &storage.front(), w * 4);
-    SkDebugf("pixmap.addr(1, 2) %c= &storage[1 + 2 * w]\n",
-              pixmap.addr(1, 2)  == &storage[1 + 2 * w] ? '=' : '!');
-#StdOut
-pixmap.addr(1, 2) == &storage[1 + 2 * w]
-##
-##
-
-#SeeAlso addr8 addr16 addr32 addr64 addrF16 getColor writable_addr SkBitmap::getAddr
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method const uint8_t* addr8() const
-
-#In Readable_Address
-#Line # returns readable pixel address as 8-bit pointer ##
-#Populate
-
-#Example
-    const int w = 4;
-    const int h = 4;
-    uint8_t storage[w * h];
-    SkPixmap pixmap(SkImageInfo::Make(w, h, kGray_8_SkColorType, kPremul_SkAlphaType),
-                    storage, w * sizeof(storage[0]));
-    SkDebugf("pixmap.addr8() %c= storage\n",
-              pixmap.addr8()  == storage ? '=' : '!');
-#StdOut
-pixmap.addr8() == storage
-##
-##
-
-#SeeAlso addr() addr16 addr32 addr64 addrF16 getColor writable_addr writable_addr8
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method const uint16_t* addr16() const
-
-#In Readable_Address
-#Line # returns readable pixel address as 16-bit pointer ##
-#Populate
-
-#Example
-    const int w = 4;
-    const int h = 4;
-    uint16_t storage[w * h];
-    SkPixmap pixmap(SkImageInfo::Make(w, h, kARGB_4444_SkColorType, kPremul_SkAlphaType),
-                    storage, w * sizeof(storage[0]));
-    SkDebugf("pixmap.addr16() %c= storage\n",
-              pixmap.addr16()  == storage ? '=' : '!');
-#StdOut
-pixmap.addr16() == storage
-##
-##
-
-#SeeAlso addr() addr8 addr32 addr64 addrF16 getColor writable_addr writable_addr16
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method const uint32_t* addr32() const
-
-#In Readable_Address
-#Line # returns readable pixel address as 32-bit pointer ##
-#Populate
-
-#Example
-    const int w = 4;
-    const int h = 4;
-    uint32_t storage[w * h];
-    SkPixmap pixmap(SkImageInfo::MakeN32(w, h, kPremul_SkAlphaType),
-                    storage, w * sizeof(storage[0]));
-    SkDebugf("pixmap.addr32() %c= storage\n",
-              pixmap.addr32()  == storage ? '=' : '!');
-#StdOut
-pixmap.addr32() == storage
-##
-##
-
-#SeeAlso addr() addr8 addr16 addr64 addrF16 getColor writable_addr writable_addr32
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method const uint64_t* addr64() const
-
-#In Readable_Address
-#Line # returns readable pixel address as 64-bit pointer ##
-#Populate
-
-#Example
-    const int w = 4;
-    const int h = 4;
-    uint64_t storage[w * h];
-    SkPixmap pixmap(SkImageInfo::Make(w, h, kRGBA_F16_SkColorType, kPremul_SkAlphaType),
-                    storage, w * sizeof(storage[0]));
-    SkDebugf("pixmap.addr64() %c= storage\n",
-              pixmap.addr64()  == storage ? '=' : '!');
-#StdOut
-pixmap.addr64() == storage
-##
-##
-
-#SeeAlso addr() addr8 addr16 addr32 addrF16 getColor writable_addr writable_addr64
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method const uint16_t* addrF16() const
-
-#In Readable_Address
-#Line # returns readable pixel component address as 16-bit pointer ##
-#Populate
-
-#Example
-    const int w = 4;
-    const int h = 4;
-    uint16_t storage[w * h * 4];
-    SkPixmap pixmap(SkImageInfo::Make(w, h, kRGBA_F16_SkColorType, kPremul_SkAlphaType),
-                    storage, w * 4 * sizeof(storage[0]));
-    SkDebugf("pixmap.addrF16() %c= storage\n",
-              pixmap.addrF16()  == storage ? '=' : '!');
-#StdOut
-pixmap.addrF16() == storage
-##
-##
-
-#SeeAlso addr() addr8 addr16 addr32 addr64 getColor writable_addr writable_addrF16
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method const uint8_t* addr8(int x, int y) const
-
-#In Readable_Address
-#Populate
-
-#Example
-    const int w = 4;
-    const int h = 4;
-    uint8_t storage[w * h];
-    SkPixmap pixmap(SkImageInfo::Make(w, h, kGray_8_SkColorType, kPremul_SkAlphaType),
-                    storage, w * sizeof(storage[0]));
-    SkDebugf("pixmap.addr8(1, 2) %c= &storage[1 + 2 * w]\n",
-              pixmap.addr8(1, 2)  == &storage[1 + 2 * w] ? '=' : '!');
-#StdOut
-pixmap.addr8(1, 2) == &storage[1 + 2 * w]
-##
-##
-
-#SeeAlso addr() addr16 addr32 addr64 addrF16 getColor writable_addr writable_addr8
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method const uint16_t* addr16(int x, int y) const
-
-#In Readable_Address
-#Populate
-
-#Example
-    const int w = 4;
-    const int h = 4;
-    uint16_t storage[w * h];
-    SkPixmap pixmap(SkImageInfo::Make(w, h, kARGB_4444_SkColorType, kPremul_SkAlphaType),
-                    storage, w * sizeof(storage[0]));
-    SkDebugf("pixmap.addr16(1, 2) %c= &storage[1 + 2 * w]\n",
-              pixmap.addr16(1, 2)  == &storage[1 + 2 * w] ? '=' : '!');
-#StdOut
-pixmap.addr16(1, 2) == &storage[1 + 2 * w]
-##
-##
-
-#SeeAlso addr() addr8 addr32 addr64 addrF16 getColor writable_addr writable_addr16
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method const uint32_t* addr32(int x, int y) const
-
-#In Readable_Address
-#Populate
-
-#Example
-    const int w = 4;
-    const int h = 4;
-    uint32_t storage[w * h];
-    SkPixmap pixmap(SkImageInfo::Make(w, h, kRGBA_8888_SkColorType, kPremul_SkAlphaType),
-                    storage, w * sizeof(storage[0]));
-    SkDebugf("pixmap.addr32(1, 2) %c= &storage[1 + 2 * w]\n",
-              pixmap.addr32(1, 2)  == &storage[1 + 2 * w] ? '=' : '!');
-#StdOut
-pixmap.addr32(1, 2) == &storage[1 + 2 * w]
-##
-##
-
-#SeeAlso addr() addr8 addr16 addr64 addrF16 getColor writable_addr writable_addr64
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method const uint64_t* addr64(int x, int y) const
-
-#In Readable_Address
-#Populate
-
-#Example
-    const int w = 4;
-    const int h = 4;
-    uint64_t storage[w * h];
-    SkPixmap pixmap(SkImageInfo::Make(w, h, kRGBA_F16_SkColorType, kPremul_SkAlphaType),
-                    storage, w * sizeof(storage[0]));
-    SkDebugf("pixmap.addr64(1, 2) %c= &storage[1 + 2 * w]\n",
-              pixmap.addr64(1, 2)  == &storage[1 + 2 * w] ? '=' : '!');
-#StdOut
-pixmap.addr64(1, 2) == &storage[1 + 2 * w]
-##
-##
-
-#SeeAlso addr() addr8 addr16 addr32 addrF16 getColor writable_addr writable_addr64
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method const uint16_t* addrF16(int x, int y) const
-
-#In Readable_Address
-#Populate
-
-#Example
-    const int w = 4;
-    const int h = 4;
-    const int wordsPerPixel = 4;
-    const int rowWords = w * wordsPerPixel;
-    uint16_t storage[rowWords * h];
-    SkPixmap pixmap(SkImageInfo::Make(w, h, kRGBA_F16_SkColorType, kPremul_SkAlphaType),
-                    storage, rowWords * sizeof(storage[0]));
-    SkDebugf("pixmap.addrF16(1, 2) %c= &storage[1 * wordsPerPixel + 2 * rowWords]\n",
-              pixmap.addrF16(1, 2)  == &storage[1 * wordsPerPixel + 2 * rowWords] ? '=' : '!');
-#StdOut
-pixmap.addrF16(1, 2) == &storage[1 * wordsPerPixel + 2 * rowWords]
-##
-##
-
-#SeeAlso addr() addr8 addr16 addr32 addr64 getColor writable_addr writable_addrF16
-
-##
-
-#Subtopic Readable_Address ##
-
-#Subtopic Writable_Address
-#Line # returns writable pixels ##
-
-# ------------------------------------------------------------------------------
-
-#Method void* writable_addr() const
-
-#In Writable_Address
-#Line # returns writable pixel address as void pointer ##
-#Populate
-
-#Example
-    const int w = 4;
-    const int h = 4;
-    SkPMColor storage[w * h * 4];
-    SkPixmap pixmap(SkImageInfo::MakeN32(w, h, kPremul_SkAlphaType), storage, w * 4);
-    SkDebugf("pixmap.writable_addr() %c= (void *)storage\n",
-              pixmap.writable_addr()  == (void *)storage ? '=' : '!');
-    pixmap.erase(0x00000000);
-    *(SkPMColor*)pixmap.writable_addr() = 0xFFFFFFFF;
-    SkDebugf("pixmap.getColor(0, 1) %c= 0x00000000\n",
-              pixmap.getColor(0, 1)  == 0x00000000 ? '=' : '!');
-    SkDebugf("pixmap.getColor(0, 0) %c= 0xFFFFFFFF\n",
-              pixmap.getColor(0, 0)  == 0xFFFFFFFF ? '=' : '!');
-#StdOut
-pixmap.writable_addr() == (void *)storage
-pixmap.getColor(0, 1) == 0x00000000
-pixmap.getColor(0, 0) == 0xFFFFFFFF
-##
-##
-
-#SeeAlso writable_addr8 writable_addr16 writable_addr32 writable_addr64 writable_addrF16 addr()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void* writable_addr(int x, int y) const
-
-#In Writable_Address
-#Populate
-
-#Example
-    const int w = 4;
-    const int h = 4;
-    SkPMColor storage[w * h * 4];
-    SkPixmap pixmap(SkImageInfo::MakeN32(w, h, kPremul_SkAlphaType), storage, w * 4);
-    SkDebugf("pixmap.writable_addr() %c= (void *)storage\n",
-              pixmap.writable_addr()  == (void *)storage ? '=' : '!');
-    pixmap.erase(0x00000000);
-    *(SkPMColor*)pixmap.writable_addr(1, 2) = 0xFFFFFFFF;
-    SkDebugf("pixmap.getColor(0, 0) %c= 0x00000000\n",
-              pixmap.getColor(0, 0)  == 0x00000000 ? '=' : '!');
-    SkDebugf("pixmap.getColor(1, 2) %c= 0xFFFFFFFF\n",
-              pixmap.getColor(1, 2)  == 0xFFFFFFFF ? '=' : '!');
-#StdOut
-pixmap.writable_addr() == (void *)storage
-pixmap.getColor(0, 0) == 0x00000000
-pixmap.getColor(1, 2) == 0xFFFFFFFF
-##
-##
-
-#SeeAlso writable_addr8 writable_addr16 writable_addr32 writable_addr64 writable_addrF16 addr()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method uint8_t* writable_addr8(int x, int y) const
-
-#In Writable_Address
-#Line # returns writable pixel address as 8-bit pointer ##
-#Populate
-
-#Example
-#Height 64
-#Description
-Altering pixels after drawing Bitmap is not guaranteed to affect subsequent
-drawing on all platforms. Adding a second SkBitmap::installPixels after editing
-pixel memory is safer.
-##
-void draw(SkCanvas* canvas) {
-    uint8_t storage[][5] = {{ 0,   0,  64,   0,  0},
-                            { 0, 128, 255, 128,  0},
-                            {64, 255, 255, 255, 64},
-                            { 0, 128, 255, 128,  0},
-                            { 0,   0,  64,   0,  0}};
-    SkImageInfo imageInfo = SkImageInfo::Make(5, 5, kGray_8_SkColorType, kPremul_SkAlphaType);
-    SkPixmap pixmap(imageInfo, storage[0], 5);
-    SkBitmap bitmap;
-    bitmap.installPixels(pixmap);
-    canvas->scale(10, 10);
-    canvas->drawBitmap(bitmap, 0, 0);
-    *pixmap.writable_addr8(2, 2) = 0;
-//  bitmap.installPixels(pixmap);      // uncomment to fix on GPU
-    canvas->drawBitmap(bitmap, 10, 0);
-}
-##
-
-#SeeAlso writable_addr writable_addr16 writable_addr32 writable_addr64 writable_addrF16 addr() addr8
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method uint16_t* writable_addr16(int x, int y) const
-
-#In Writable_Address
-#Line # returns writable pixel address as 16-bit pointer ##
-#Populate
-
-#Example
-#Description
-Draw a five by five bitmap, and draw it again with a center black pixel.
-The low nibble of the 16-bit word is Alpha.
-##
-#Height 64
-    uint16_t storage[][5] = {{ 0xCABF, 0xDABE, 0xCA9D, 0xC96C, 0xA39B },
-                             { 0xACEE, 0xA87C, 0x893A, 0x4779, 0x8708 },
-                             { 0x4B7C, 0x255B, 0x2559, 0x2557, 0x4656 },
-                             { 0x9099, 0x8128, 0x2557, 0x4124, 0x3323 },
-                             { 0x7547, 0x5505, 0x4434, 0x2012, 0x0000 }};
-    SkImageInfo imageInfo = SkImageInfo::Make(5, 5, kARGB_4444_SkColorType, kPremul_SkAlphaType);
-    SkPixmap pixmap(imageInfo, storage[0], sizeof(storage) / 5);
-    SkBitmap bitmap;
-    bitmap.installPixels(pixmap);
-    canvas->scale(10, 10);
-    canvas->drawBitmap(bitmap, 0, 0);
-    *pixmap.writable_addr16(2, 2) = 0x000F;
-    bitmap.installPixels(pixmap);
-    canvas->drawBitmap(bitmap, 10, 0);
-##
-
-#SeeAlso writable_addr writable_addr8 writable_addr32 writable_addr64 writable_addrF16 addr() addr16
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method uint32_t* writable_addr32(int x, int y) const
-
-#In Writable_Address
-#Line # returns writable pixel address as 32-bit pointer ##
-#Populate
-
-#Example
-#Image 4
-#Height 72
-    std::vector<int32_t> pixels;
-    pixels.resize(image->height() * image->width() * 4);
-    SkPixmap pixmap(SkImageInfo::Make(image->width(), image->height(), kN32_SkColorType,
-            image->alphaType()), (const void*) &pixels.front(), image->width() * 4);
-    image->readPixels(pixmap, 0, 0);
-    for (int y = 0; y < pixmap.height() / 2; ++y) {
-        for (int x = 0; x < pixmap.width(); ++x) {
-            if ((x & 4) == (y & 4)) {
-                *pixmap.writable_addr32(x, y) =
-                        *pixmap.writable_addr32(pixmap.width() - x, pixmap.height() - y);
-            }
-        }
-    }
-    SkBitmap bitmap;
-    bitmap.installPixels(pixmap);
-    canvas->drawBitmap(bitmap, 0, 0);
-##
-
-#SeeAlso writable_addr writable_addr8 writable_addr16 writable_addr64 writable_addrF16 addr() addr32
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method uint64_t* writable_addr64(int x, int y) const
-
-#In Writable_Address
-#Line # returns writable pixel address as 64-bit pointer ##
-#Populate
-
-#Example
-#Height 40
-    SkImageInfo info = SkImageInfo::Make(3, 3, kRGBA_F16_SkColorType, kPremul_SkAlphaType);
-    uint64_t storage[9];
-    SkPixmap pixmap(info, storage, 3 * sizeof(uint64_t));
-    SkColor4f c4 { 1, 0.45f, 0.25f, 0.65f };
-    pixmap.erase(c4);
-    SkBitmap bitmap;
-    canvas->scale(10, 10);
-    bitmap.installPixels(pixmap);
-    canvas->drawBitmap(bitmap, 0, 0);
-    *pixmap.writable_addr64(1, 1) |= 0x00ff000000000000LL;
-    bitmap.installPixels(pixmap);
-    canvas->drawBitmap(bitmap, 10, 0);
-##
-
-#SeeAlso writable_addr writable_addr8 writable_addr16 writable_addr32 writable_addrF16 addr() addr64
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method uint16_t* writable_addrF16(int x, int y) const
-
-#In Writable_Address
-#Line # returns writable pixel component address as 16-bit pointer ##
-#Populate
-
-#Example
-#Height 64
-#Description
-Left bitmap is drawn with two pixels defined in half float format. Right bitmap
-is drawn after overwriting bottom half float color with top half float color.
-##
-    SkImageInfo info = SkImageInfo::Make(1, 2, kRGBA_F16_SkColorType, kPremul_SkAlphaType);
-    uint16_t storage[2][4];
-    SkPixmap pixmap(info, storage[0], sizeof(uint64_t));
-    SkIRect topPixelBounds = {0, 0, 1, 1};
-    pixmap.erase({ 0.65f, 0.45f, 0.25f, 1 }, &topPixelBounds);
-    SkIRect bottomPixelBounds = {0, 1, 1, 2};
-    pixmap.erase({ 0.25f, 0.65f, 0.45f, 1 }, &bottomPixelBounds);
-    SkBitmap bitmap;
-    canvas->scale(20, 20);
-    bitmap.installPixels(pixmap);
-    canvas->drawBitmap(bitmap, 0, 0);
-    uint16_t* pixel2 = pixmap.writable_addrF16(0, 1);
-    for (int i = 0; i < 4; ++i) {
-        pixel2[i] = storage[0][i];
-    }
-    bitmap.installPixels(pixmap);
-    canvas->drawBitmap(bitmap, 4, 0);
-##
-
-#SeeAlso writable_addr writable_addr8 writable_addr16 writable_addr32 writable_addr64 addr() addrF16
-
-##
-
-#Subtopic Writable_Address ##
-
-#Subtopic Pixels
-#Line # read and write pixel values ##
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes) const
-#In Pixels
-#Line # copies and converts pixels ##
-#Populate
-
-#Example
-#Height 128
-#Description
-Transferring the gradient from 8 bits per component to 4 bits per component
-creates visible banding.
-##
-    std::vector<int32_t> pixels;
-    const int width = 256;
-    const int height = 64;
-    pixels.resize(height * width * 4);
-    SkImageInfo srcInfo = SkImageInfo::MakeN32Premul(width, height);
-    SkPixmap srcPixmap(srcInfo, (const void*) &pixels.front(), width * 4);
-    SkColor  gradColors[] = { 0xFFAA3300, 0x7F881122 };
-    SkPoint  gradPoints[] = { { 0, 0 }, { 256, 0 } };
-    SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(gradPoints, gradColors, nullptr,
-                    SK_ARRAY_COUNT(gradColors), SkShader::kClamp_TileMode));
-    SkBitmap bitmap;
-    bitmap.installPixels(srcPixmap);
-    SkCanvas srcCanvas(bitmap);
-    srcCanvas.drawRect(SkRect::MakeWH(width, height), paint);
-    canvas->drawBitmap(bitmap, 0, 0);
-    std::vector<int32_t> dstPixels;
-    dstPixels.resize(height * width * 2);
-    SkImageInfo dstInfo = srcInfo.makeColorType(kARGB_4444_SkColorType);
-    srcPixmap.readPixels(dstInfo, &dstPixels.front(), width * 2);
-    SkPixmap dstPixmap(dstInfo, &dstPixels.front(), width * 2);
-    bitmap.installPixels(dstPixmap);
-    canvas->drawBitmap(bitmap, 0, 128);
-##
-
-#SeeAlso erase SkBitmap::readPixels SkCanvas::drawBitmap SkCanvas::readPixels SkImage::readPixels SkSurface::readPixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, int srcX,
-                    int srcY) const
-
-Copies a Rect of pixels to dstPixels. Copy starts at (srcX, srcY), and does not
-exceed Pixmap (width(), height()).
-
-dstInfo specifies width, height, Color_Type, Alpha_Type, and
-Color_Space of destination. dstRowBytes specifics the gap from one destination
-row to the next. Returns true if pixels are copied. Returns false if
-dstInfo has no address, or dstRowBytes is less than dstInfo.minRowBytes().
-
-Pixels are copied only if pixel conversion is possible. If Pixmap colorType is
-kGray_8_SkColorType, or kAlpha_8_SkColorType; dstInfo.colorType() must match.
-If Pixmap colorType is kGray_8_SkColorType, dstInfo.colorSpace() must match.
-If Pixmap alphaType is kOpaque_SkAlphaType, dstInfo.alphaType() must
-match. If Pixmap colorSpace is nullptr, dstInfo.colorSpace() must match. Returns
-false if pixel conversion is not possible.
-
-srcX and srcY may be negative to copy only top or left of source. Returns
-false if Pixmap width() or height() is zero or negative. Returns false if:
-
-#Formula # abs(srcX) >= Pixmap width() ##, or if #Formula # abs(srcY) >= Pixmap height() ##.
-
-#Param dstInfo  destination width, height, Color_Type, Alpha_Type, Color_Space ##
-#Param dstPixels  destination pixel storage ##
-#Param dstRowBytes  destination row length ##
-#Param srcX  column index whose absolute value is less than width() ##
-#Param srcY  row index whose absolute value is less than height() ##
-
-#Return  true if pixels are copied to dstPixels ##
-
-#Example
-#Image 3
-void draw(SkCanvas* canvas) {
-    SkImageInfo info = SkImageInfo::MakeN32Premul(image->width(), image->height());
-    std::vector<int32_t> srcPixels;
-    const int rowBytes = image->width() * 4;
-    srcPixels.resize(image->height() * rowBytes);
-    SkPixmap pixmap(info, (const void*) &srcPixels.front(), rowBytes);
-    image->readPixels(pixmap, 0, 0);
-    for (int offset : { 32, 64, 96 } ) {
-        std::vector<int32_t> dstPixels;
-        dstPixels.resize(image->height() * rowBytes);
-        pixmap.readPixels(info, &dstPixels.front(), rowBytes, offset, 0);
-        SkBitmap bitmap;
-        SkPixmap dstmap(info, &dstPixels.front(), rowBytes);
-        bitmap.installPixels(dstmap);
-        canvas->translate(32, 32);
-        canvas->drawBitmap(bitmap, 0, 0);
-    }
-}
-##
-
-#SeeAlso erase SkBitmap::readPixels SkCanvas::drawBitmap SkCanvas::readPixels SkImage::readPixels SkSurface::readPixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readPixels(const SkPixmap& dst, int srcX, int srcY) const
-
-Copies a Rect of pixels to dst. Copy starts at (srcX, srcY), and does not
-exceed Pixmap (width(), height()). dst specifies width, height, Color_Type,
-Alpha_Type, and Color_Space of destination.  Returns true if pixels are copied.
-Returns false if dst.addr() equals nullptr, or dst.rowBytes() is less than
-dst SkImageInfo::minRowBytes.
-
-Pixels are copied only if pixel conversion is possible. If Pixmap colorType is
-kGray_8_SkColorType, or kAlpha_8_SkColorType; dst.info().colorType() must match.
-If Pixmap colorType is kGray_8_SkColorType, dst.info().colorSpace() must match.
-If Pixmap alphaType is kOpaque_SkAlphaType, dst.info().alphaType() must
-match. If Pixmap colorSpace is nullptr, dst.info().colorSpace() must match. Returns
-false if pixel conversion is not possible.
-
-srcX and srcY may be negative to copy only top or left of source. Returns
-false Pixmap width() or height() is zero or negative. Returns false if:
-
-#Formula # abs(srcX) >= Pixmap width() ##, or if #Formula # abs(srcY) >= Pixmap height() ##.
-
-#Param dst  Image_Info and pixel address to write to ##
-#Param srcX  column index whose absolute value is less than width() ##
-#Param srcY  row index whose absolute value is less than height() ##
-
-#Return  true if pixels are copied to dst ##
-
-#Example
-#Image 3
-void draw(SkCanvas* canvas) {
-    SkImageInfo info = SkImageInfo::MakeN32Premul(image->width(), image->height());
-    std::vector<int32_t> srcPixels;
-    const int rowBytes = image->width() * 4;
-    srcPixels.resize(image->height() * rowBytes);
-    SkPixmap pixmap(info, (const void*) &srcPixels.front(), rowBytes);
-    image->readPixels(pixmap, 0, 0);
-    for (int offset : { 32, 64, 96 } ) {
-        std::vector<int32_t> dstPixels;
-        dstPixels.resize(image->height() * rowBytes);
-        SkPixmap dstmap(info, &dstPixels.front(), rowBytes);
-        pixmap.readPixels(dstmap, offset, 0);
-        SkBitmap bitmap;
-        bitmap.installPixels(dstmap);
-        canvas->translate(32, 32);
-        canvas->drawBitmap(bitmap, 0, 0);
-    }
-}
-##
-
-#SeeAlso erase SkBitmap::readPixels SkCanvas::drawBitmap SkCanvas::readPixels SkImage::readPixels SkSurface::readPixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readPixels(const SkPixmap& dst) const
-#Populate
-
-#Example
-#Image 3
-void draw(SkCanvas* canvas) {
-    SkImageInfo info = SkImageInfo::MakeN32Premul(image->width(), image->height());
-    std::vector<int32_t> srcPixels;
-    const int rowBytes = image->width() * 4;
-    srcPixels.resize(image->height() * rowBytes);
-    SkPixmap pixmap(info, (const void*) &srcPixels.front(), rowBytes);
-    image->readPixels(pixmap, 0, 0);
-    for (int index = 0; index < 3; ++index ) {
-        std::vector<int32_t> dstPixels;
-        dstPixels.resize(image->height() * rowBytes);
-        SkPixmap dstmap(info, &dstPixels.front(), rowBytes);
-        pixmap.readPixels(dstmap);
-        SkBitmap bitmap;
-        bitmap.installPixels(dstmap);
-        canvas->translate(32, 32);
-        canvas->drawBitmap(bitmap, 0, 0);
-    }
-}
-##
-
-#SeeAlso erase SkBitmap::readPixels SkCanvas::drawBitmap SkCanvas::readPixels SkImage::readPixels SkSurface::readPixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool scalePixels(const SkPixmap& dst, SkFilterQuality filterQuality) const
-
-#In Pixels
-#Line # scales and converts pixels ##
-#Populate
-
-#Example
-#Image 3
-void draw(SkCanvas* canvas) {
-    SkImageInfo info = SkImageInfo::MakeN32Premul(image->width(), image->height());
-    std::vector<int32_t> srcPixels;
-    int rowBytes = image->width() * 4;
-    srcPixels.resize(image->height() * rowBytes);
-    SkPixmap pixmap(info, (const void*) &srcPixels.front(), rowBytes);
-    image->readPixels(pixmap, 0, 0);
-    for (int offset : { 32, 64, 96 } ) {
-        info = SkImageInfo::MakeN32Premul(image->width() + offset, image->height());
-        rowBytes = info.width() * 4;
-        std::vector<int32_t> dstPixels;
-        dstPixels.resize(image->height() * rowBytes);
-        SkPixmap dstmap(info, &dstPixels.front(), rowBytes);
-        pixmap.scalePixels(dstmap, kMedium_SkFilterQuality);
-        SkBitmap bitmap;
-        bitmap.installPixels(dstmap);
-        canvas->translate(32, 32);
-        canvas->drawBitmap(bitmap, 0, 0);
-    }
-}
-##
-
-#SeeAlso SkCanvas::drawBitmap SkImage::scalePixels
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool erase(SkColor color, const SkIRect& subset) const
-
-#In Pixels
-#Line # writes Color to pixels ##
-#Populate
-
-#Example
-#Height 50
-    uint32_t storage[2];
-    SkImageInfo info = SkImageInfo::MakeN32Premul(1, 2);
-    SkPixmap pixmap(info, storage, info.minRowBytes());
-    pixmap.erase(SK_ColorBLUE, {0, 0, 1, 1});
-    pixmap.erase(SK_ColorRED, {0, 1, 1, 2});
-    SkBitmap bitmap;
-    canvas->scale(20, 20);
-    bitmap.installPixels(pixmap);
-    canvas->drawBitmap(bitmap, 0, 0);
-##
-
-#SeeAlso SkBitmap::erase SkCanvas::clear SkCanvas::drawColor
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool erase(SkColor color) const
-#Populate
-
-#Example
-#Height 50
-    uint32_t storage[2];
-    SkImageInfo info = SkImageInfo::MakeN32Premul(1, 2);
-    SkPixmap pixmap(info, storage, info.minRowBytes());
-    pixmap.erase(SK_ColorBLUE);
-    SkBitmap bitmap;
-    canvas->scale(20, 20);
-    bitmap.installPixels(pixmap);
-    canvas->drawBitmap(bitmap, 0, 0);
-##
-
-#SeeAlso SkBitmap::erase SkCanvas::clear SkCanvas::drawColor
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool erase(const SkColor4f& color, const SkIRect* subset = nullptr) const
-#Populate
-
-#Example
-#Height 50
-    uint32_t storage[2];
-    SkImageInfo info = SkImageInfo::MakeN32Premul(1, 2);
-    SkPixmap pixmap(info, storage, info.minRowBytes());
-    SkIRect topPixelBounds = {0, 0, 1, 1};
-    pixmap.erase({ 0.65f, 0.45f, 0.25f, 1 }, &topPixelBounds);
-    SkIRect bottomPixelBounds = {0, 1, 1, 2};
-    pixmap.erase({ 0.25f, 0.65f, 0.45f, 1 }, &bottomPixelBounds);
-    SkBitmap bitmap;
-    canvas->scale(20, 20);
-    bitmap.installPixels(pixmap);
-    canvas->drawBitmap(bitmap, 0, 0);
-##
-
-#SeeAlso SkBitmap::erase SkCanvas::clear SkCanvas::drawColor
-
-##
-
-#Class SkPixmap ##
-
-#Topic Pixmap ##
diff --git a/docs/SkPoint_Reference.bmh b/docs/SkPoint_Reference.bmh
deleted file mode 100644
index 1a1f045..0000000
--- a/docs/SkPoint_Reference.bmh
+++ /dev/null
@@ -1,1071 +0,0 @@
-#Topic Point
-#Alias Points ##
-#Alias Point_Reference ##
-
-#Struct SkPoint
-
-#Code
-#Populate
-##
-
-SkPoint holds two 32-bit floating point coordinates.
-
-#Member SkScalar  fX
-#Line # x-axis value ##
-x-axis value used by both Point and Vector. May contain any value, including
-infinities and NaN.
-##
-
-#Member SkScalar  fY
-#Line # y-axis value ##
-y-axis value used by both Point and Vector. May contain any value, including
-infinities and NaN.
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static constexpr SkPoint Make(SkScalar x, SkScalar y)
-#In Constructors
-#Line # constructs from SkScalar inputs ##
-#Populate
-
-#Example
-SkPoint pt1 = {45, 66};
-SkPoint pt2 = SkPoint::Make(45, 66);
-SkVector v1 = {45, 66};
-SkVector v2 = SkPoint::Make(45, 66);
-SkDebugf("all %s" "equal\n", pt1 == pt2 && pt2 == v1 && v1 == v2 ? "" : "not ");
-#StdOut
-all equal
-##
-##
-
-#SeeAlso set() iset() SkIPoint::Make
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Property
-#Line # member values ##
-##
-
-#Method SkScalar x() const
-#In Property
-#Line # returns fX ##
-#Populate
-
-#Example
-SkPoint pt1 = {45, 66};
-SkDebugf("pt1.fX %c= pt1.x()\n", pt1.fX == pt1.x() ? '=' : '!');
-#StdOut
-pt1.fX == pt1.x()
-##
-##
-
-#SeeAlso y() SkIPoint::x()
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar y() const
-#In Property
-#Line # returns fY ##
-#Populate
-
-#Example
-SkPoint pt1 = {45, 66};
-SkDebugf("pt1.fY %c= pt1.y()\n", pt1.fY == pt1.y() ? '=' : '!');
-#StdOut
-pt1.fY == pt1.y()
-##
-##
-
-#SeeAlso x() SkIPoint::y()
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isZero() const
-#In Property
-#Line # returns true if both members equal zero ##
-#Populate
-
-#Example
-SkPoint pt = { 0.f, -0.f};
-SkDebugf("pt.fX=%c%g pt.fY=%c%g\n", std::signbit(pt.fX) ? '-' : '+', fabsf(pt.fX),
-                                    std::signbit(pt.fY) ? '-' : '+', fabsf(pt.fY));
-SkDebugf("pt.isZero() == %s\n", pt.isZero() ? "true" : "false");
-#StdOut
-pt.fX=+0 pt.fY=-0
-pt.isZero() == true
-##
-##
-
-#SeeAlso isFinite SkIPoint::isZero
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Set
-#Line # replaces all values ##
-##
-
-#Method void set(SkScalar x, SkScalar y)
-#In Set
-#Line # sets to SkScalar input ##
-#Populate
-
-#Example
-SkPoint pt1, pt2 = { SK_ScalarPI, SK_ScalarSqrt2 };
-pt1.set(SK_ScalarPI, SK_ScalarSqrt2);
-SkDebugf("pt1 %c= pt2\n", pt1 == pt2 ? '=' : '!');
-#StdOut
-pt1 == pt2
-##
-##
-
-#SeeAlso iset() Make
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void iset(int32_t x, int32_t y)
-#In Set
-#Line # sets to integer input ##
-#Populate
-
-#Example
-SkPoint pt1, pt2 = { SK_MinS16, SK_MaxS16 };
-pt1.iset(SK_MinS16, SK_MaxS16);
-SkDebugf("pt1 %c= pt2\n", pt1 == pt2 ? '=' : '!');
-##
-
-#SeeAlso set Make SkIPoint::set
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void iset(const SkIPoint& p)
-#Populate
-
-#Example
-SkIPoint iPt = { SK_MinS32, SK_MaxS32 };
-SkPoint fPt;
-fPt.iset(iPt);
-SkDebugf("iPt: %d, %d\n", iPt.fX, iPt.fY);
-SkDebugf("fPt: %g, %g\n", fPt.fX, fPt.fY);
-#StdOut
-iPt: -2147483647, 2147483647
-fPt: -2.14748e+09, 2.14748e+09
-##
-##
-
-#SeeAlso set Make SkIPoint::set
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setAbs(const SkPoint& pt)
-#In Set
-#Line # sets sign of both members to positive ##
-#Populate
-
-#Example
-SkPoint test[] = { {0.f, -0.f}, {-1, -2},
-                   { SK_ScalarInfinity, SK_ScalarNegativeInfinity },
-                   { SK_ScalarNaN, -SK_ScalarNaN } };
-for (const SkPoint& pt : test) {
-    SkPoint absPt;
-    absPt.setAbs(pt);
-    SkDebugf("pt: %g, %g  abs: %g, %g\n", pt.fX, pt.fY, absPt.fX, absPt.fY);
-}
-#StdOut
-pt: 0, -0  abs: 0, 0
-pt: -1, -2  abs: 1, 2
-pt: inf, -inf  abs: inf, inf
-pt: nan, -nan  abs: nan, nan
-##
-##
-
-#SeeAlso set Make negate
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Offset
-#Line # moves sides ##
-##
-
-#Method static void Offset(SkPoint points[], int count, const SkVector& offset)
-#In Offset
-#Line # translates Point array ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 },
-                         { 6, 4 }, { 7, 5 }, { 5, 7 },
-                         { 4, 6 }, { 3, 7 }, { 1, 5 },
-                         { 2, 4 }, { 1, 3 }, { 3, 1 } };
-    canvas->scale(30, 15);
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
-    SkPoint::Offset(points, SK_ARRAY_COUNT(points), { 1, 9 } );
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
-##
-
-#SeeAlso offset operator+=(const SkVector& v)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static void Offset(SkPoint points[], int count, SkScalar dx, SkScalar dy)
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 },
-                         { 6, 4 }, { 7, 5 }, { 5, 7 },
-                         { 4, 6 }, { 3, 7 }, { 1, 5 },
-                         { 2, 4 }, { 1, 3 }, { 3, 1 } };
-    canvas->scale(30, 15);
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
-    SkPoint::Offset(points, SK_ARRAY_COUNT(points), 1, 9);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
-##
-
-#SeeAlso offset operator+=(const SkVector& v)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void offset(SkScalar dx, SkScalar dy)
-#In Offset
-#Line # translates Point ##
-#Populate
-
-#Example
-#Height 128
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 },
-                         { 6, 4 }, { 7, 5 }, { 5, 7 },
-                         { 4, 6 }, { 3, 7 }, { 1, 5 },
-                         { 2, 4 }, { 1, 3 }, { 3, 1 } };
-    canvas->scale(30, 15);
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
-    points[1].offset(1, 1);
-    paint.setColor(SK_ColorRED);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
-##
-
-#SeeAlso Offset operator+=(const SkVector& v)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar length() const
-#In Property
-#Line # returns straight-line distance to origin ##
-#Populate
-
-#Example
-#Height 192
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    const SkPoint points[] = { { 90, 30 }, { 120, 150 }, { 150, 30 }, { 210, 90 } };
-    const SkPoint origin = {30, 140};
-    for (auto point : points) {
-        canvas->drawLine(origin, point, paint);
-        SkAutoCanvasRestore acr(canvas, true);
-        SkScalar angle = SkScalarATan2((point.fY - origin.fY), point.fX - origin.fX);
-        canvas->rotate(angle * 180 / SK_ScalarPI, origin.fX, origin.fY);
-        SkString length("length = ");
-        length.appendScalar(point.length());
-        canvas->drawString(length, origin.fX + 25, origin.fY - 4, paint);
-    }
-##
-
-#SeeAlso distanceToOrigin Length setLength Distance
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar distanceToOrigin() const
-#In Property
-#Line # returns straight-line distance to origin ##
-#Populate
-
-#Example
-#Height 192
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    const SkPoint points[] = { { 60, -110 }, { 90, 10 }, { 120, -110 }, { 180, -50 } };
-    const SkPoint origin = {0, 0};
-    canvas->translate(30, 140);
-    for (auto point : points) {
-        canvas->drawLine(origin, point, paint);
-        SkAutoCanvasRestore acr(canvas, true);
-        SkScalar angle = SkScalarATan2((point.fY - origin.fY), point.fX - origin.fX);
-        canvas->rotate(angle * 180 / SK_ScalarPI, origin.fX, origin.fY);
-        SkString distance("distance = ");
-        distance.appendScalar(point.distanceToOrigin());
-        canvas->drawString(distance, origin.fX + 25, origin.fY - 4, paint);
-    }
-##
-
-#SeeAlso length Length setLength Distance
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool normalize()
-#In Set
-#Line # sets length to one, preserving direction ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    const SkPoint lines[][2] = { {{  30, 110 }, { 190,  30 }},
-                                 {{ 120, 140 }, {  30, 220 }}};
-    for (auto line : lines) {
-        canvas->drawLine(line[0], line[1], paint);
-        SkVector vector = line[1] - line[0];
-        if (vector.normalize()) {
-            SkVector rotate90 = { -vector.fY, vector.fX };
-            rotate90 *= 10.f;
-            canvas->drawLine(line[0] - rotate90, line[0] + rotate90, paint);
-            canvas->drawLine(line[1] - rotate90, line[1] + rotate90, paint);
-        }
-    }
-##
-
-#SeeAlso Normalize setLength length Length
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool setNormalize(SkScalar x, SkScalar y)
-#In Set
-#Line # sets length to one, in direction of (x, y) ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    const SkPoint points[] = { { 60, -110 }, { 90, 10 }, { 120, -110 }, { 180, -50 } };
-    const SkPoint origin = {0, 0};
-    canvas->translate(30, 140);
-    for (auto point : points) {
-        paint.setStrokeWidth(1);
-        paint.setColor(SK_ColorBLACK);
-        canvas->drawLine(origin, point, paint);
-        SkVector normal;
-        normal.setNormalize(point.fX, point.fY);
-        normal *= 100;
-        paint.setStrokeWidth(10);
-        paint.setColor(0x3f4512bf);
-        canvas->drawLine(origin, normal, paint);
-    }
-##
-
-#SeeAlso normalize setLength
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool setLength(SkScalar length)
-#In Set
-#Line # sets straight-line distance to origin ##
-#Populate
-
-#Example
-#Height 160
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    const SkPoint points[] = { { 60, -110 }, { 90, 10 }, { 120, -110 }, { 180, -50 } };
-    const SkPoint origin = {0, 0};
-    canvas->translate(30, 140);
-    for (auto point : points) {
-        paint.setStrokeWidth(1);
-        paint.setColor(SK_ColorBLACK);
-        canvas->drawLine(origin, point, paint);
-        SkVector normal = point;
-        normal.setLength(100);
-        paint.setStrokeWidth(10);
-        paint.setColor(0x3f45bf12);
-        canvas->drawLine(origin, normal, paint);
-    }
-##
-
-#SeeAlso length Length setNormalize setAbs
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool setLength(SkScalar x, SkScalar y, SkScalar length)
-#Populate
-
-#Example
-#Height 160
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    const SkPoint points[] = { { 60, -110 }, { 90, 10 }, { 120, -110 }, { 180, -50 } };
-    const SkPoint origin = {0, 0};
-    canvas->translate(30, 140);
-    for (auto point : points) {
-        paint.setStrokeWidth(1);
-        paint.setColor(SK_ColorBLACK);
-        canvas->drawLine(origin, point, paint);
-        SkVector normal;
-        normal.setLength(point.fX, point.fY, 100);
-        paint.setStrokeWidth(10);
-        paint.setColor(0x3fbf4512);
-        canvas->drawLine(origin, normal, paint);
-    }
-##
-
-#SeeAlso length Length setNormalize setAbs
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void scale(SkScalar scale, SkPoint* dst) const
-#In Offset
-#In Operators
-#Line # multiplies Point by scale factor ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkPoint point = {40, -15}, scaled;
-    SkPoint origin = {30, 110};
-    for (auto scale : {1, 2, 3, 5}) {
-        paint.setStrokeWidth(scale * 5);
-        paint.setARGB(0x7f, 0x9f, 0xbf, 0x33 * scale);
-        point.scale(scale, &scaled);
-        canvas->drawLine(origin, origin + scaled, paint);
-    }
-##
-
-#SeeAlso operator*(SkScalar scale) const operator*=(SkScalar scale) setLength
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void scale(SkScalar value)
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkPoint point = {40, -15};
-    SkPoint origin = {30, 110};
-    for (auto scale : {1, 2, 3, 5}) {
-        paint.setStrokeWidth(scale * 5);
-        paint.setARGB(0x7f, 0x9f, 0xbf, 0x33 * scale);
-        point.scale(scale);
-        canvas->drawLine(origin, origin + point, paint);
-    }
-##
-
-#SeeAlso operator*(SkScalar scale) const operator*=(SkScalar scale) setLength
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void negate()
-#In Operators
-#Line # reverses the sign of both members ##
-#Populate
-
-#Example
-SkPoint test[] = { {0.f, -0.f}, {-1, -2},
-                   { SK_ScalarInfinity, SK_ScalarNegativeInfinity },
-                   { SK_ScalarNaN, -SK_ScalarNaN } };
-for (const SkPoint& pt : test) {
-    SkPoint negPt = pt;
-    negPt.negate();
-    SkDebugf("pt: %g, %g  negate: %g, %g\n", pt.fX, pt.fY, negPt.fX, negPt.fY);
-}
-#StdOut
-pt: 0, -0  negate: -0, 0
-pt: -1, -2  negate: 1, 2
-pt: inf, -inf  negate: -inf, inf
-pt: nan, -nan  negate: -nan, nan
-##
-##
-
-#SeeAlso operator-() const setAbs
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPoint operator-() const
-
-#Line # reverses sign of Point ##
-#Populate
-
-#Example
-SkPoint test[] = { {0.f, -0.f}, {-1, -2},
-                   { SK_ScalarInfinity, SK_ScalarNegativeInfinity },
-                   { SK_ScalarNaN, -SK_ScalarNaN } };
-for (const SkPoint& pt : test) {
-    SkPoint negPt = -pt;
-    SkDebugf("pt: %g, %g  negate: %g, %g\n", pt.fX, pt.fY, negPt.fX, negPt.fY);
-}
-#StdOut
-pt: 0, -0  negate: -0, 0
-pt: -1, -2  negate: 1, 2
-pt: inf, -inf  negate: -inf, inf
-pt: nan, -nan  negate: -nan, nan
-##
-##
-
-#SeeAlso negate operator-(const SkPoint& a, const SkPoint& b) operator-=(const SkVector& v) SkIPoint::operator-() const
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void operator+=(const SkVector& v)
-
-#Line # adds Vector to Point ##
-Adds Vector v to Point. Sets Point to: #Formula # (fX + v.fX, fY + v.fY) ##.
-
-#Param v  Vector to add ##
-
-#Example
-#Height 128
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 },
-                         { 6, 4 }, { 7, 5 }, { 5, 7 },
-                         { 4, 6 }, { 3, 7 }, { 1, 5 },
-                         { 2, 4 }, { 1, 3 }, { 3, 1 } };
-    canvas->scale(30, 15);
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
-    points[1] += {1, 1};
-    points[2] += {-1, -1};
-    paint.setColor(SK_ColorRED);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
-##
-
-#SeeAlso offset() operator+(const SkPoint& a, const SkVector& b) SkIPoint::operator+=(const SkIVector& v)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void operator-=(const SkVector& v)
-
-#Line # subtracts Vector from Point ##
-Subtracts Vector v from Point. Sets Point to: #Formula # (fX - v.fX, fY - v.fY) ##.
-
-#Param v  Vector to subtract ##
-
-#Example
-#Height 128
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 },
-                         { 6, 4 }, { 7, 5 }, { 5, 7 },
-                         { 4, 6 }, { 3, 7 }, { 1, 5 },
-                         { 2, 4 }, { 1, 3 }, { 3, 1 } };
-    canvas->scale(30, 15);
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
-    points[1] -= {1, 1};
-    points[2] -= {-1, -1};
-    paint.setColor(SK_ColorRED);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
-##
-
-#SeeAlso offset() operator-(const SkPoint& a, const SkPoint& b) SkIPoint::operator-=(const SkIVector& v)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPoint operator*(SkScalar scale) const
-
-#Line # returns Point multiplied by scale ##
-#Populate
-
-#Example
-#Height 128
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 },
-                         { 6, 4 }, { 7, 5 }, { 5, 7 },
-                         { 4, 6 }, { 3, 7 }, { 1, 5 },
-                         { 2, 4 }, { 1, 3 }, { 3, 1 } };
-    canvas->scale(15, 10);
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
-    for (auto& point : points) {
-        point = point * 1.5f;
-    }
-    paint.setColor(SK_ColorRED);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
-##
-
-#SeeAlso operator*=(SkScalar scale) scale() setLength setNormalize
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPoint& operator*=(SkScalar scale)
-
-#Line # multiplies Point by scale factor ##
-Multiplies Point by scale. Sets Point to: #Formula # (fX * scale, fY * scale) ##.
-
-#Param scale  Scalar to multiply by ##
-
-#Return reference to Point ##
-
-#Example
-#Height 128
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 },
-                         { 6, 4 }, { 7, 5 }, { 5, 7 },
-                         { 4, 6 }, { 3, 7 }, { 1, 5 },
-                         { 2, 4 }, { 1, 3 }, { 3, 1 } };
-    canvas->scale(15, 10);
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
-    for (auto& point : points) {
-        point *= 2;
-    }
-    paint.setColor(SK_ColorRED);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
-##
-
-#SeeAlso operator*(SkScalar scale) const scale() setLength setNormalize
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isFinite() const
-#In Property
-#Line # returns true if no member is infinite or NaN ##
-#Populate
-
-#Example
-SkPoint test[] = { {0, -0.f}, {-1, -2}, {SK_ScalarInfinity, 1}, {SK_ScalarNaN, -1} };
-for (const SkPoint& pt : test) {
-    SkDebugf("pt: %g, %g  finite: %s\n", pt.fX, pt.fY, pt.isFinite() ? "true" : "false");
-}
-#StdOut
-pt: 0, -0  finite: true
-pt: -1, -2  finite: true
-pt: inf, 1  finite: false
-pt: nan, -1  finite: false
-##
-##
-
-#SeeAlso SkRect::isFinite SkPath::isFinite
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool equals(SkScalar x, SkScalar y) const
-#In Operators
-#Line # returns true if Points are equal ##
-#Populate
-
-#Example
-SkPoint test[] = { {0, -0.f}, {-1, -2}, {SK_ScalarInfinity, 1}, {SK_ScalarNaN, -1} };
-for (const SkPoint& pt : test) {
-    SkDebugf("pt: %g, %g  %c= pt\n", pt.fX, pt.fY, pt.equals(pt.fX, pt.fY) ? '=' : '!');
-}
-#StdOut
-pt: 0, -0  == pt
-pt: -1, -2  == pt
-pt: inf, 1  == pt
-pt: nan, -1  != pt
-##
-##
-
-#SeeAlso operator==(const SkPoint& a, const SkPoint& b)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator==(const SkPoint& a, const SkPoint& b)
-
-#Line # returns true if Point are equal ##
-#Populate
-
-#Example
-SkPoint test[] = { {0, -0.f}, {-1, -2}, {SK_ScalarInfinity, 1}, {SK_ScalarNaN, -1} };
-for (const SkPoint& pt : test) {
-    SkDebugf("pt: %g, %g  %c= pt\n", pt.fX, pt.fY, pt == pt ? '=' : '!');
-}
-#StdOut
-pt: 0, -0  == pt
-pt: -1, -2  == pt
-pt: inf, 1  == pt
-pt: nan, -1  != pt
-##
-##
-
-#SeeAlso equals() operator!=(const SkPoint& a, const SkPoint& b)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator!=(const SkPoint& a, const SkPoint& b)
-
-#Line # returns true if Point are unequal ##
-#Populate
-
-#Example
-SkPoint test[] = { {0, -0.f}, {-1, -2}, {SK_ScalarInfinity, 1}, {SK_ScalarNaN, -1} };
-for (const SkPoint& pt : test) {
-    SkDebugf("pt: %g, %g  %c= pt\n", pt.fX, pt.fY, pt != pt ? '!' : '=');
-}
-#StdOut
-pt: 0, -0  == pt
-pt: -1, -2  == pt
-pt: inf, 1  == pt
-pt: nan, -1  != pt
-##
-##
-
-#SeeAlso operator==(const SkPoint& a, const SkPoint& b) equals()
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkVector operator-(const SkPoint& a, const SkPoint& b)
-
-#Line # returns Vector between Points ##
-Returns Vector from b to a, computed as #Formula # (a.fX - b.fX, a.fY - b.fY) ##.
-
-Can also be used to subtract Vector from Point, returning Point.
-Can also be used to subtract Vector from Vector, returning Vector.
-
-#Param a  Point to subtract from ##
-#Param b  Point to subtract ##
-
-#Return Vector from b to a ##
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 },
-                         { 6, 4 }, { 7, 5 }, { 5, 7 },
-                         { 4, 6 }, { 3, 7 }, { 1, 5 },
-                         { 2, 4 }, { 1, 3 }, { 3, 1 } };
-    canvas->scale(30, 15);
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
-    points[1] += points[0] - points[2];
-    points[2] -= points[3] - points[5];
-    paint.setColor(SK_ColorRED);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
-##
-
-#SeeAlso operator-=(const SkVector& v) offset()
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkPoint operator+(const SkPoint& a, const SkVector& b)
-
-#Line # returns Point offset by Vector ##
-Returns Point resulting from Point a offset by Vector b, computed as:
-#Formula # (a.fX + b.fX, a.fY + b.fY) ##.
-
-Can also be used to offset Point b by Vector a, returning Point.
-Can also be used to add Vector to Vector, returning Vector.
-
-#Param a  Point or Vector to add to ##
-#Param b  Point or Vector to add ##
-
-#Return Point equal to a offset by b ##
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 },
-                         { 6, 4 }, { 7, 5 }, { 5, 7 },
-                         { 4, 6 }, { 3, 7 }, { 1, 5 },
-                         { 2, 4 }, { 1, 3 }, { 3, 1 } };
-    canvas->scale(30, 15);
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
-    SkVector mod = {1, 1};
-    for (auto& point : points) {
-        point = point + mod;
-        mod.fX *= 1.1f;
-        mod.fY += .2f;
-    }
-    paint.setColor(SK_ColorRED);
-    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
-##
-
-#SeeAlso operator+=(const SkVector& v) offset()
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkScalar Length(SkScalar x, SkScalar y)
-#In Property
-#Line # returns straight-line distance to origin ##
-#Populate
-
-#Example
-#Height 192
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    const SkPoint points[] = { { 90, 30 }, { 120, 150 }, { 150, 30 }, { 210, 90 } };
-    const SkPoint origin = {30, 140};
-    for (auto point : points) {
-        canvas->drawLine(origin, point, paint);
-        SkAutoCanvasRestore acr(canvas, true);
-        SkScalar angle = SkScalarATan2((point.fY - origin.fY), point.fX - origin.fX);
-        canvas->rotate(angle * 180 / SK_ScalarPI, origin.fX, origin.fY);
-        SkString length("length = ");
-        length.appendScalar(SkPoint::Length(point.fX, point.fY));
-        canvas->drawString(length, origin.fX + 25, origin.fY - 4, paint);
-    }
-##
-
-#SeeAlso length() Distance setLength
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkScalar Normalize(SkVector* vec)
-#In Offset
-#Line # sets length to one, and returns prior length ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    const SkPoint lines[][2] = { {{  30, 110 }, { 190,  30 }},
-                                 {{  30, 220 }, { 120, 140 }}};
-    for (auto line : lines) {
-        canvas->drawLine(line[0], line[1], paint);
-        SkVector vector = line[1] - line[0];
-        SkScalar priorLength = SkPoint::Normalize(&vector);
-        SkVector rotate90 = { -vector.fY, vector.fX };
-        rotate90 *= 10.f;
-        canvas->drawLine(line[0] - rotate90, line[0] + rotate90, paint);
-        canvas->drawLine(line[1] - rotate90, line[1] + rotate90, paint);
-        SkString length("length = ");
-        length.appendScalar(priorLength);
-        canvas->drawString(length, line[0].fX + 25, line[0].fY - 4, paint);
-    }
-##
-
-#SeeAlso normalize() setLength Length
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkScalar Distance(const SkPoint& a, const SkPoint& b)
-#In Property
-#Line # returns straight-line distance between points ##
-#Populate
-
-#Example
-#Height 192
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    const SkPoint lines[][2] = {{{-10, -10}, {90, 30}}, {{0, 0}, {150, 30}}, {{10, 25}, {120, 150}}};
-    const SkPoint origin = {30, 160};
-    for (auto line : lines) {
-        SkPoint a = origin + line[0];
-        const SkPoint& b = line[1];
-        canvas->drawLine(a, b, paint);
-        SkAutoCanvasRestore acr(canvas, true);
-        SkScalar angle = SkScalarATan2((b.fY - a.fY), b.fX - a.fX);
-        canvas->rotate(angle * 180 / SK_ScalarPI, a.fX, a.fY);
-        SkString distance("distance = ");
-        distance.appendScalar(SkPoint::Distance(a, b));
-        canvas->drawString(distance, a.fX + 25, a.fY - 4, paint);
-    }
-##
-
-#SeeAlso length() setLength
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkScalar DotProduct(const SkVector& a, const SkVector& b)
-#In Operators
-#Line # returns dot product ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkVector vectors[][2] = {{{50, 2}, {-14, 20}}, {{0, 50}, {-50, 0}}, {{-20, 25}, {25, -20}},
-                             {{-20, -24}, {-24, -20}}};
-    SkPoint center[] = {{32, 32}, {160, 32}, {32, 160}, {160, 160}};
-    paint.setStrokeWidth(2);
-    for (size_t i = 0; i < 4; ++i) {
-        canvas->drawLine(center[i], center[i] + vectors[i][0], paint);
-        canvas->drawLine(center[i], center[i] + vectors[i][1], paint);
-        SkString str;
-        str.printf("dot = %g", SkPoint::DotProduct(vectors[i][0], vectors[i][1]));
-        canvas->drawString(str, center[i].fX, center[i].fY, paint);
-    }
-##
-
-#SeeAlso dot CrossProduct
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkScalar CrossProduct(const SkVector& a, const SkVector& b)
-#In Operators
-#Line # returns cross product ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkVector vectors[][2] = {{{50, 2}, {-14, 20}}, {{0, 50}, {-50, 0}}, {{-20, 25}, {25, -20}},
-                             {{-20, -24}, {-24, -20}}};
-    SkPoint center[] = {{32, 32}, {160, 32}, {32, 160}, {160, 160}};
-    paint.setStrokeWidth(2);
-    for (size_t i = 0; i < 4; ++i) {
-        paint.setColor(SK_ColorRED);
-        canvas->drawLine(center[i], center[i] + vectors[i][0], paint);
-        paint.setColor(SK_ColorBLUE);
-        canvas->drawLine(center[i], center[i] + vectors[i][1], paint);
-        SkString str;
-        SkScalar cross = SkPoint::CrossProduct(vectors[i][1], vectors[i][0]);
-        str.printf("cross = %g", cross);
-        paint.setColor(cross >= 0 ? SK_ColorRED : SK_ColorBLUE);
-        canvas->drawString(str, center[i].fX, center[i].fY, paint);
-    }
-##
-
-#SeeAlso cross DotProduct
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar cross(const SkVector& vec) const
-#In Operators
-#Line # returns cross product ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkVector vectors[][2] = {{{50, 2}, {-14, 20}}, {{0, 50}, {-50, 0}}, {{-20, 25}, {25, -20}},
-                             {{-20, -24}, {-24, -20}}};
-    SkPoint center[] = {{32, 32}, {160, 32}, {32, 160}, {160, 160}};
-    paint.setStrokeWidth(2);
-    for (size_t i = 0; i < 4; ++i) {
-        paint.setColor(SK_ColorRED);
-        canvas->drawLine(center[i], center[i] + vectors[i][0], paint);
-        paint.setColor(SK_ColorBLUE);
-        canvas->drawLine(center[i], center[i] + vectors[i][1], paint);
-        SkString str;
-        SkScalar cross = vectors[i][0].cross(vectors[i][1]);
-        str.printf("cross = %g", cross);
-        paint.setColor(cross >= 0 ? SK_ColorRED : SK_ColorBLUE);
-        canvas->drawString(str, center[i].fX, center[i].fY, paint);
-    }
-##
-
-#SeeAlso CrossProduct dot
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar dot(const SkVector& vec) const
-#In Operators
-#Line # returns dot product ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkVector vectors[][2] = {{{50, 2}, {-14, 20}}, {{0, 50}, {-50, 0}}, {{-20, 25}, {25, -20}},
-                             {{-20, -24}, {-24, -20}}};
-    SkPoint center[] = {{32, 32}, {160, 32}, {32, 160}, {160, 160}};
-    paint.setStrokeWidth(2);
-    for (size_t i = 0; i < 4; ++i) {
-        canvas->drawLine(center[i], center[i] + vectors[i][0], paint);
-        canvas->drawLine(center[i], center[i] + vectors[i][1], paint);
-        SkString str;
-        str.printf("dot = %g", vectors[i][0].dot(vectors[i][1]));
-        canvas->drawString(str, center[i].fX, center[i].fY, paint);
-    }
-##
-
-#SeeAlso DotProduct cross
-
-#Method ##
-
-#Struct SkPoint ##
-
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Vector
-#Line # alias for Point ##
-    #Alias Vector ##
-    #Alias Vectors ##
-    #Typedef SkPoint SkVector
-    #Line # alias for Point ##
-    #Code
-    typedef SkPoint SkVector;
-    ##
-    SkVector provides an alternative name for SkPoint. SkVector and SkPoint can
-    be used interchangeably for all purposes.
-    #Typedef ##
-##
-
-#Topic Point ##
diff --git a/docs/SkRRect_Reference.bmh b/docs/SkRRect_Reference.bmh
deleted file mode 100644
index 2136984..0000000
--- a/docs/SkRRect_Reference.bmh
+++ /dev/null
@@ -1,1392 +0,0 @@
-#Topic RRect
-#Alias Round_Rect ##
-#Alias RRect_Reference ##
-
-#Class SkRRect
-
-#Code
-class SkRRect {
-public:
-    SkRRect() = default;
-    SkRRect(const SkRRect& rrect) = default;
-    SkRRect& operator=(const SkRRect& rrect) = default;
-
-    enum Type {
-        kEmpty_Type,
-        kRect_Type,
-        kOval_Type,
-        kSimple_Type,
-        kNinePatch_Type,
-        kComplex_Type,
-        kLastType       = kComplex_Type,
-    };
-
-    Type getType() const;
-    Type type() const;
-    bool isEmpty() const;
-    bool isRect() const;
-    bool isOval() const;
-    bool isSimple() const;
-    bool isNinePatch() const;
-    bool isComplex() const;
-    SkScalar width() const;
-    SkScalar height() const;
-    SkVector getSimpleRadii() const;
-    void setEmpty();
-    void setRect(const SkRect& rect);
-    static SkRRect MakeEmpty();
-    static SkRRect MakeRect(const SkRect& r);
-    static SkRRect MakeOval(const SkRect& oval);
-    static SkRRect MakeRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad);
-    void setOval(const SkRect& oval);
-    void setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad);
-    void setNinePatch(const SkRect& rect, SkScalar leftRad, SkScalar topRad,
-                      SkScalar rightRad, SkScalar bottomRad);
-    void setRectRadii(const SkRect& rect, const SkVector radii[4]);
-
-    enum Corner {
-        kUpperLeft_Corner,
-        kUpperRight_Corner,
-        kLowerRight_Corner,
-        kLowerLeft_Corner,
-    };
-
-    const SkRect& rect() const;
-    SkVector radii(Corner corner) const;
-    const SkRect& getBounds() const;
-    friend bool operator==(const SkRRect& a, const SkRRect& b);
-    friend bool operator!=(const SkRRect& a, const SkRRect& b);
-    void inset(SkScalar dx, SkScalar dy, SkRRect* dst) const;
-    void inset(SkScalar dx, SkScalar dy);
-    void outset(SkScalar dx, SkScalar dy, SkRRect* dst) const;
-    void outset(SkScalar dx, SkScalar dy);
-    void offset(SkScalar dx, SkScalar dy);
-    SkRRect makeOffset(SkScalar dx, SkScalar dy) const;
-    bool contains(const SkRect& rect) const;
-    bool isValid() const;
-
-    static constexpr size_t kSizeInMemory = 12 * sizeof(SkScalar);
-
-    size_t writeToMemory(void* buffer) const;
-    size_t readFromMemory(const void* buffer, size_t length);
-    bool transform(const SkMatrix& matrix, SkRRect* dst) const;
-    void dump(bool asHex) const;
-    void dump() const;
-    void dumpHex() const;
-};
-##
-
-SkRRect describes a rounded rectangle with a bounds and a pair of radii for each corner.
-The bounds and radii can be set so that SkRRect describes: a rectangle with sharp corners;
-a Circle; an Oval; or a rectangle with one or more rounded corners.
-
-SkRRect allows implementing CSS properties that describe rounded corners.
-SkRRect may have up to eight different radii, one for each axis on each of its four
-corners.
-
-SkRRect may modify the provided parameters when initializing bounds and radii.
-If either axis radii is zero or less: radii are stored as zero; corner is square.
-If corner curves overlap, radii are proportionally reduced to fit within bounds.
-
-# ------------------------------------------------------------------------------
-
-#Method SkRRect()
-#In Constructors
-#Line # creates with zeroed bounds and corner radii ##
-#Populate
-
-#Example
-#Height 60
-    SkRRect rrect;
-    SkPaint p;
-    p.setStyle(SkPaint::kStroke_Style);
-    p.setStrokeWidth(10);
-    canvas->drawRRect(rrect, p);
-    rrect.setRect({10, 10, 100, 50});
-    canvas->drawRRect(rrect, p);
-##
-
-#SeeAlso setEmpty isEmpty
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkRRect(const SkRRect& rrect)
-#In Constructors
-#Line # copies bounds and corner radii ##
-#Populate
-
-#Example
-#Height 60
-    SkRRect rrect = SkRRect::MakeRect({10, 10, 100, 50});
-    SkRRect rrect2(rrect);
-    rrect2.inset(20, 20);
-    SkPaint p;
-    p.setStyle(SkPaint::kStroke_Style);
-    p.setStrokeWidth(10);
-    canvas->drawRRect(rrect, p);
-    canvas->drawRRect(rrect2, p);
-##
-
-#SeeAlso operator=(const SkRRect& rrect) MakeRect
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkRRect& operator=(const SkRRect& rrect)
-#In Operators
-#Line # copies bounds and corner radii ##
-#Populate
-
-#Example
-#Height 110
-    SkRRect rrect = SkRRect::MakeRect({40, 40, 100, 70});
-    SkRRect rrect2 = rrect;
-    rrect2.inset(-20, -20);
-    SkPaint p;
-    p.setStyle(SkPaint::kStroke_Style);
-    p.setStrokeWidth(10);
-    canvas->drawRRect(rrect, p);
-    canvas->drawRRect(rrect2, p);
-##
-
-#SeeAlso SkRRect(const SkRRect& rrect) MakeRect
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Type
-#Line # specialization of Round_Rect geometry ##
-
-#PhraseDef list_of_rrect_types
-kEmpty_Type, kRect_Type, kOval_Type, kSimple_Type, kNinePatch_Type,
-kComplex_Type
-##
-
-#Enum Type
-#Line # specialization of Round_Rect geometry ##
-
-#Code
-    enum Type {
-        kEmpty_Type,
-        kRect_Type,
-        kOval_Type,
-        kSimple_Type,
-        kNinePatch_Type,
-        kComplex_Type,
-        kLastType = kComplex_Type,
-    };
-##
-
-Type describes possible specializations of Round_Rect. Each Type is
-exclusive; a Round_Rect may only have one type. 
-
-Type members become progressively less restrictive; larger values of
-Type have more degrees of freedom than smaller values.
-
-#Const kEmpty_Type 0
-#Line # zero width or height ##
-Round_Rect has zero width or height. All radii are zero.
-##
-#Const kRect_Type 1
-#Line # non-zero width and height, and zeroed radii ##
-Round_Rect has width and height. All radii are zero.
-##
-#Const kOval_Type 2
-#Line # non-zero width and height filled with radii ##
-Round_Rect has width and height. All four x-radii are equal,
-and at least half the width. All four y-radii are equal,
-and at least half the height.
-##
-#Const kSimple_Type 3
-#Line # non-zero width and height with equal radii ##
-Round_Rect has width and height. All four x-radii are equal and
-greater than zero, and all four y-radii are equal and greater than
-zero. Either x-radii are less than half the width, or y-radii is
-less than half the height, or both.
-##
-#Const kNinePatch_Type 4
-#Line # non-zero width and height with axis-aligned radii ##
-Round_Rect has width and height. Left x-radii are equal, top
-y-radii are equal, right x-radii are equal, and bottom y-radii
-are equal. The radii do not describe Rect, Oval, or simple type.
-
-The centers of the corner ellipses form an axis-aligned rectangle
-that divides the Round_Rect into nine rectangular patches; an
-interior rectangle, four edges, and four corners.
-##
-#Const kComplex_Type 5
-#Line # non-zero width and height with arbitrary radii ##
-both radii are non-zero.
-##
-#Const kLastType 5
-#Line # largest Type value ##
-##
-
-#Example
-#Height 128
-    struct Radii { SkVector data[4]; };
-    auto drawRRectType = [=](const SkRect& rect, const Radii& radii) {
-        SkRRect rrect;
-        rrect.setRectRadii(rect, radii.data);
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        const char* typeStr[] = { "empty", "rect", "oval", "simple", "nine patch", "complex" };
-        canvas->drawString(typeStr[(int) rrect.type()], rect.centerX(), rect.bottom() + 20, paint);
-        paint.setStyle(SkPaint::kStroke_Style);       
-        canvas->drawRRect(rrect, paint);
-    };
-    drawRRectType({ 45,  30,  45,  30}, {{{ 5,  5}, { 5,  5}, { 5,  5}, { 5,  5}}});
-    drawRRectType({ 90,  10, 140,  30}, {{{ 0,  0}, { 0,  0}, { 0,  0}, { 0,  0}}});
-    drawRRectType({160,  10, 210,  30}, {{{25, 10}, {25, 10}, {25, 10}, {25, 10}}});
-    drawRRectType({ 20,  80,  70, 100}, {{{ 5,  5}, { 5,  5}, { 5,  5}, { 5,  5}}});
-    drawRRectType({ 90,  80, 140, 100}, {{{ 5,  5}, {10,  5}, {10,  5}, { 5,  5}}});
-    drawRRectType({160,  80, 210, 100}, {{{ 5,  5}, {10,  5}, { 5,  5}, { 5,  5}}});
-##
-
-#SeeAlso Rect Path
-
-#Enum ##
-
-# ------------------------------------------------------------------------------
-
-#Method Type getType() const
-#In Property
-#Line # returns Type ##
-
-Returns Type, one of: #list_of_rrect_types#.
-
-#Return Type ##
-
-#Example
-#Height 100
-#Description 
-rrect2 is not a Rect; inset() has made it empty.
-##
-    SkRRect rrect = SkRRect::MakeRect({10, 10, 100, 50});
-    SkRRect rrect2(rrect);
-    rrect2.inset(20, 20);
-    SkPaint p;
-    p.setStyle(SkPaint::kStroke_Style);
-    p.setStrokeWidth(10);
-    std::string str("Type ");
-    str += SkRRect::kRect_Type == rrect2.getType() ? "=" : "!"; 
-    str += "= SkRRect::kRect_Type";
-    canvas->drawString(str.c_str(), 20, 80, SkPaint());
-    canvas->drawRRect(rrect2, p);
-##
-
-#SeeAlso Type type
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method Type type() const
-#In Property
-#Line # returns Type ##
-
-Returns Type, one of: #list_of_rrect_types#.
-
-#Return Type ##
-
-#Example
-#Height 100
-#Description 
-inset() has made rrect2 empty.
-##
-    SkRRect rrect = SkRRect::MakeRect({10, 10, 100, 50});
-    SkRRect rrect2(rrect);
-    rrect2.inset(20, 20);
-    SkPaint p;
-    p.setStyle(SkPaint::kStroke_Style);
-    p.setStrokeWidth(10);
-    std::string str("Type ");
-    str += SkRRect::kEmpty_Type == rrect2.type() ? "=" : "!"; 
-    str += "= SkRRect::kEmpty_Type";
-    canvas->drawString(str.c_str(), 20, 80, SkPaint());
-    canvas->drawRRect(rrect2, p);
-##
-
-#SeeAlso Type getType
-
-#Method ##
-
-#Subtopic Type ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isEmpty() const
-#In Property
-#Line # returns true if width or height are zero ##
-#Populate
-
-#Example
-#Height 100
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setTextSize(16);
-    SkRRect rrect = SkRRect::MakeRectXY({30, 10, 100, 60}, 10, 5);
-    canvas->drawRRect(rrect, paint);
-    canvas->drawString(rrect.isEmpty() ? "empty" : "not empty", 64, 90, paint);
-    rrect.inset(40, 0);
-    canvas->translate(128, 0);
-    canvas->drawRRect(rrect, paint);
-    canvas->drawString(rrect.isEmpty() ? "empty" : "not empty", 64, 90, paint);
-##
-
-#SeeAlso SkRect::isEmpty height width
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isRect() const
-#In Property
-#Line # returns true if not empty, and one radius at each corner is zero ##
-#Populate
-
-#Example
-#Height 100
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setTextSize(16);
-    SkRRect rrect = SkRRect::MakeRect({30, 10, 100, 60});
-    canvas->drawRRect(rrect, paint);
-    canvas->drawString(rrect.isRect() ? "rect" : "not rect", 64, 90, paint);
-    SkVector radii[] = {{10, 10}, {0, 0}, {0, 0}, {0, 0}};
-    rrect.setRectRadii(rrect.getBounds(), radii);
-    canvas->translate(128, 0);
-    canvas->drawRRect(rrect, paint);
-    canvas->drawString(rrect.isRect() ? "rect" : "not rect", 64, 90, paint);
-##
-
-#SeeAlso isEmpty radii
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isOval() const
-#In Property
-#Line # returns true if not empty, axes radii are equal, radii fill bounds ##
-#Populate
-
-#Example
-#Height 100
-#Description
-The first radii are scaled down proportionately until both x-axis and y-axis fit
-within the bounds. After scaling, x-axis radius is smaller than half the width;
-left Round_Rect is not an oval. The second radii are equal to half the
-dimensions; right Round_Rect is an oval.
-##
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setTextSize(16);
-    SkRRect rrect = SkRRect::MakeRectXY({30, 10, 100, 60}, 40, 30);
-    canvas->drawRRect(rrect, paint);
-    canvas->drawString(rrect.isOval() ? "oval" : "not oval", 64, 90, paint);
-    rrect.setRectXY(rrect.getBounds(), 35, 25);
-    canvas->translate(128, 0);
-    canvas->drawRRect(rrect, paint);
-    canvas->drawString(rrect.isOval() ? "oval" : "not oval", 64, 90, paint);
-##
-
-#SeeAlso isEmpty isSimple SkCanvas::drawOval
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isSimple() const
-#In Property
-#Line # returns true if not empty, Rect or Oval; and axes radii are equal ##
-#Populate
-
-#Example
-#Height 100
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setTextSize(16);
-    SkVector radii[] = {{40, 30}, {40, 30}, {40, 30}, {40, 30}};
-    SkRRect rrect;
-    rrect.setRectRadii({30, 10, 100, 60}, radii);
-    canvas->drawRRect(rrect, paint);
-    canvas->drawString(rrect.isSimple() ? "simple" : "not simple", 64, 90, paint);
-    radii[0].fX = 35;
-    rrect.setRectRadii(rrect.getBounds(), radii);
-    canvas->translate(128, 0);
-    canvas->drawRRect(rrect, paint);
-    canvas->drawString(rrect.isSimple() ? "simple" : "not simple", 64, 90, paint);
-##
-
-#SeeAlso isEmpty isRect isOval isNinePatch
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isNinePatch() const
-#In Property
-#Line # returns true if not empty, Rect, Oval or simple; and radii are axis-aligned ##
-#Populate
-
-#Example
-#Height 100
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setTextSize(16);
-    SkVector radii[] = {{20, 30}, {40, 30}, {40, 30}, {20, 30}};
-    SkRRect rrect;
-    rrect.setRectRadii({30, 10, 100, 60}, radii);
-    canvas->drawRRect(rrect, paint);
-    canvas->drawString(rrect.isNinePatch() ? "9 patch" : "not 9 patch", 64, 90, paint);
-    radii[0].fX = 35;
-    rrect.setRectRadii(rrect.getBounds(), radii);
-    canvas->translate(128, 0);
-    canvas->drawRRect(rrect, paint);
-    canvas->drawString(rrect.isNinePatch() ? "9 patch" : "not 9 patch", 64, 90, paint);
-##
-
-#SeeAlso isEmpty isRect isOval isSimple isComplex
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isComplex() const
-#In Property
-#Line # returns true if not empty, Rect, Oval, simple, or nine-patch ##
-#Populate
-
-#Example
-#Height 100
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setTextSize(16);
-    SkVector radii[] = {{25, 30}, {40, 30}, {40, 30}, {20, 30}};
-    SkRRect rrect;
-    rrect.setRectRadii({30, 10, 100, 60}, radii);
-    canvas->drawRRect(rrect, paint);
-    canvas->drawString(rrect.isComplex() ? "complex" : "not complex", 64, 90, paint);
-    radii[0].fX = 20;
-    rrect.setRectRadii(rrect.getBounds(), radii);
-    canvas->translate(128, 0);
-    canvas->drawRRect(rrect, paint);
-    canvas->drawString(rrect.isComplex() ? "complex" : "not complex", 64, 90, paint);
-##
-
-#SeeAlso isEmpty isRect isOval isSimple isNinePatch
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar width() const
-#In Property
-#Line # returns span in x-axis ##
-#Populate
-
-#Example
-#Description
-SkRRect::MakeRect sorts its input, so width() is always zero or larger.
-##
-    SkRRect unsorted = SkRRect::MakeRect({ 15, 25, 10, 5 });
-    SkDebugf("unsorted width: %g\n", unsorted.width());
-    SkRRect large = SkRRect::MakeRect({ -FLT_MAX, 1, FLT_MAX, 2 });
-    SkDebugf("large width: %.0f\n", large.width());
-#StdOut
-unsorted width: 5
-large width: inf
-##
-##
-
-#SeeAlso SkRect::width height getBounds
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar height() const
-#In Property
-#Line # returns span in y-axis ##
-#Populate
-
-#Example
-#Description
-SkRRect::MakeRect sorts its input, so height() is always zero or larger.
-##
-    SkRRect unsorted = SkRRect::MakeRect({ 15, 25, 10, 20 });
-    SkDebugf("unsorted height: %g\n", unsorted.height());
-    SkRRect large = SkRRect::MakeRect({ 1, -FLT_MAX, 2, FLT_MAX });
-    SkDebugf("large height: %.0f\n", large.height());
-#StdOut
-unsorted height: 5
-large height: inf
-##
-##
-
-#SeeAlso SkRect::height width getBounds
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkVector getSimpleRadii() const
-#In Property
-#Line # returns corner radii for simple types ##
-#Populate
-
-#Example
-#Height 100
-    auto drawDetails = [=](const SkRRect& rrect) {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setTextSize(12);
-        canvas->drawRRect(rrect, paint);
-        SkVector corner = rrect.getSimpleRadii();
-        std::string label = "corner: " + std::to_string(corner.fX).substr(0, 3) + ", " +
-                        std::to_string(corner.fY).substr(0, 3);
-        canvas->drawString(label.c_str(), 64, 90, paint);
-        canvas->translate(128, 0);
-    };
-    SkRRect rrect = SkRRect::MakeRect({30, 10, 100, 60});
-    drawDetails(rrect);
-    rrect.setRectXY(rrect.getBounds(), 5, 8);
-    drawDetails(rrect);
-##
-
-#SeeAlso radii getBounds getType isSimple
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setEmpty()
-#In Set
-#Line # zeroes width, height, and corner radii ##
-#Populate
-
-#Example
-#Height 80
-#Description
-Nothing blue is drawn because Round_Rect is set to empty.
-##
-    SkPaint paint;
-    SkRRect rrect = SkRRect::MakeRect({30, 10, 100, 60});
-    canvas->drawRRect(rrect, paint);
-    rrect.setEmpty();
-    paint.setColor(SK_ColorBLUE);
-    canvas->drawRRect(rrect, paint);
-##
-
-#SeeAlso MakeEmpty setRect
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setRect(const SkRect& rect)
-#In Set
-#Line # sets Round_Rect bounds with zeroed corners ##
-#Populate
-
-#Example
-#Height 90
-    SkPaint paint;
-    SkRRect rrect = SkRRect::MakeRect({30, 10, 100, 60});
-    canvas->drawRRect(rrect, paint);
-    rrect.setRect({60, 30, 120, 80});
-    paint.setColor(SK_ColorBLUE);
-    canvas->drawRRect(rrect, paint);
-##
-
-#SeeAlso MakeRect setRectXY
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkRRect MakeEmpty()
-#In Constructors
-#Line # creates with zeroed bounds and corner radii ##
-#Populate
-
-#Example
-#Height 90
-    SkRRect rrect = SkRRect::MakeEmpty();
-    SkRRect rrect2(rrect);
-    rrect2.inset(-20, -20);
-    SkPaint p;
-    p.setStyle(SkPaint::kStroke_Style);
-    p.setStrokeWidth(10);
-    std::string str("Type ");
-    str += SkRRect::kEmpty_Type == rrect2.type() ? "=" : "!"; 
-    str += "= SkRRect::kEmpty_Type";
-    canvas->drawString(str.c_str(), 20, 80, SkPaint());
-    canvas->drawRRect(rrect2, p);
-##
-
-#SeeAlso SkRRect() SkRect::MakeEmpty
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkRRect MakeRect(const SkRect& r)
-#In Constructors
-#Line # copies bounds and zeroes corner radii ##
-#Populate
-
-#Example
-#Height 70
-    SkPaint paint;
-    SkRRect rrect = SkRRect::MakeRect({30, 10, 100, 60});
-    canvas->drawRRect(rrect, paint);
-    rrect.setOval(rrect.getBounds());
-    paint.setColor(SK_ColorBLUE);
-    canvas->drawRRect(rrect, paint);
-##
-
-#SeeAlso setRect MakeOval MakeRectXY
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkRRect MakeOval(const SkRect& oval)
-#In Constructors
-#Line # creates Oval to fit bounds ##
-#Populate
-
-#Example
-#Height 70
-    SkPaint paint;
-    SkRRect rrect = SkRRect::MakeOval({30, 10, 100, 60});
-    canvas->drawRRect(rrect, paint);
-    rrect.setRect(rrect.getBounds());
-    paint.setColor(SK_ColorBLUE);
-    paint.setBlendMode(SkBlendMode::kDifference);
-    canvas->drawRRect(rrect, paint);
-##
-
-#SeeAlso setOval MakeRect MakeRectXY
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkRRect MakeRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad)
-#In Constructors
-#Line # creates rounded rectangle ##
-#Populate
-
-#Example
-#Height 70
-    SkPaint paint;
-    SkRRect rrect = SkRRect::MakeRectXY({30, 10, 100, 60}, 20, 20);
-    canvas->drawRRect(rrect, paint);
-    rrect.setRect(rrect.getBounds());
-    paint.setColor(SK_ColorBLUE);
-    paint.setBlendMode(SkBlendMode::kModulate);
-    canvas->drawRRect(rrect, paint);
-##
-
-#SeeAlso setRectXY
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setOval(const SkRect& oval)
-#In Set
-#Line # replaces with Oval to fit bounds ##
-#Populate
-
-#Example
-#Height 70
-    SkPaint paint;
-    SkRRect rrect = SkRRect::MakeRectXY({30, 10, 100, 60}, 20, 20);
-    canvas->drawRRect(rrect, paint);
-    rrect.setOval(rrect.getBounds());
-    paint.setColor(SK_ColorWHITE);
-    paint.setBlendMode(SkBlendMode::kExclusion);
-    canvas->drawRRect(rrect, paint);
-##
-
-#SeeAlso MakeOval
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad)
-#In Set
-#Line # replaces with rounded rectangle ##
-#Populate
-
-#Example
-#Height 70
-    SkPaint paint;
-    SkRRect rrect = SkRRect::MakeRectXY({30, 10, 100, 60}, 20, 20);
-    canvas->drawRRect(rrect, paint);
-    rrect.setRectXY(rrect.getBounds(), 5, 5);
-    paint.setColor(SK_ColorWHITE);
-    paint.setBlendMode(SkBlendMode::kExclusion);
-    canvas->drawRRect(rrect, paint);
-##
-
-#SeeAlso MakeRectXY SkPath::addRoundRect
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setNinePatch(const SkRect& rect, SkScalar leftRad, SkScalar topRad,
-                      SkScalar rightRad, SkScalar bottomRad)
-#In Set
-#Line # replaces with rounded rectangle ##
-#Populate
-
-#Example
-#Height 70
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkRRect rrect;
-    rrect.setNinePatch({30, 10, 100, 60}, 10, 20, 20, 10);
-    canvas->drawRRect(rrect, paint);
-    paint.setColor(SK_ColorWHITE);
-    const SkRect r = rrect.getBounds();
-    canvas->drawLine(r.fLeft, r.fTop + rrect.radii(SkRRect::kUpperLeft_Corner).fY,
-                     r.fRight, r.fTop + rrect.radii(SkRRect::kUpperRight_Corner).fY, paint);
-    canvas->drawLine(r.fLeft, r.fBottom - rrect.radii(SkRRect::kLowerLeft_Corner).fY,
-                     r.fRight, r.fBottom - rrect.radii(SkRRect::kLowerRight_Corner).fY, paint);
-    canvas->drawLine(r.fLeft + rrect.radii(SkRRect::kUpperLeft_Corner).fX, r.fTop,
-                     r.fLeft + rrect.radii(SkRRect::kLowerLeft_Corner).fX, r.fBottom, paint);
-    canvas->drawLine(r.fRight - rrect.radii(SkRRect::kUpperRight_Corner).fX, r.fTop,
-                     r.fRight - rrect.radii(SkRRect::kLowerRight_Corner).fX, r.fBottom, paint);
-##
-
-#SeeAlso setRectRadii
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setRectRadii(const SkRect& rect, const SkVector radii[4])
-#In Set
-#Line # replaces with rounded rectangle ##
-#Populate
-
-#Example
-#Height 128
-    SkPaint paint;
-    paint.setStrokeWidth(15);
-    paint.setStrokeCap(SkPaint::kSquare_Cap);
-    paint.setAntiAlias(true);
-    float intervals[] = { 5, 21.75f };
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0));
-    SkPath path;
-    SkRRect rrect;
-    SkVector corners[] = {{15, 17}, {17, 19}, {19, 15}, {15, 15}};
-    rrect.setRectRadii({20, 20, 100, 100}, corners);
-    path.addRRect(rrect, SkPath::kCW_Direction);
-    canvas->drawPath(path, paint);
-    path.rewind();
-    path.addRRect(rrect, SkPath::kCCW_Direction, 1);
-    canvas->translate(120, 0);
-    canvas->drawPath(path, paint);
-##
-
-#SeeAlso setNinePatch SkPath::addRoundRect
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Enum Corner
-#Line # corner radii order ##
-
-#Code
-    enum Corner {
-        kUpperLeft_Corner,
-        kUpperRight_Corner,
-        kLowerRight_Corner,
-        kLowerLeft_Corner,
-    };
-##
-
-The radii are stored: top-left, top-right, bottom-right, bottom-left.
-
-#Const kUpperLeft_Corner 0
-#Line # index of top-left corner radii ##
-##
-#Const kUpperRight_Corner 1
-#Line # index of top-right corner radii ##
-##
-#Const kLowerRight_Corner 2
-#Line # index of bottom-right corner radii ##
-##
-#Const kLowerLeft_Corner 3
-#Line # index of bottom-left corner radii ##
-##
-
-#Example
-#Height 70
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkRRect rrect;
-    SkVector corners[] = {{25, 17}, {17, 19}, {19, 15}, {15, 15}};
-    rrect.setRectRadii({30, 10, 100, 60}, corners);
-    canvas->drawRRect(rrect, paint);
-    paint.setColor(SK_ColorWHITE);
-    const SkRect r = rrect.getBounds();
-    canvas->drawLine(r.fLeft, r.fTop + rrect.radii(SkRRect::kUpperLeft_Corner).fY,
-                     r.fRight, r.fTop + rrect.radii(SkRRect::kUpperRight_Corner).fY, paint);
-    canvas->drawLine(r.fLeft, r.fBottom - rrect.radii(SkRRect::kLowerLeft_Corner).fY,
-                     r.fRight, r.fBottom - rrect.radii(SkRRect::kLowerRight_Corner).fY, paint);
-    canvas->drawLine(r.fLeft + rrect.radii(SkRRect::kUpperLeft_Corner).fX, r.fTop,
-                     r.fLeft + rrect.radii(SkRRect::kLowerLeft_Corner).fX, r.fBottom, paint);
-    canvas->drawLine(r.fRight - rrect.radii(SkRRect::kUpperRight_Corner).fX, r.fTop,
-                     r.fRight - rrect.radii(SkRRect::kLowerRight_Corner).fX, r.fBottom, paint);
-##
-
-#SeeAlso radii
-
-#Enum ##
-
-# ------------------------------------------------------------------------------
-
-#Method const SkRect& rect() const
-#In Property
-#Line # returns bounds ##
-#Populate
-
-#Example
-    for (SkScalar left : { SK_ScalarNaN, SK_ScalarInfinity, 100.f, 50.f, 25.f} ) {
-        SkRRect rrect1 = SkRRect::MakeRectXY({left, 20, 60, 220}, 50, 200);
-        SkDebugf("left bounds: (%g) %g\n", left, rrect1.rect().fLeft);
-    }
-#StdOut
-left bounds: (nan) 0
-left bounds: (inf) 0
-left bounds: (100) 60
-left bounds: (50) 50
-left bounds: (25) 25
-##
-##
-
-#SeeAlso getBounds
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkVector radii(Corner corner) const
-#In Property
-#Line # returns x-axis and y-axis radii for one corner ##
-#Populate
-
-#Example
-#Description
-Finite values are scaled proportionately to fit; other values are set to zero.
-Scaled values cannot be larger than 25, half the bounding Round_Rect width.
-Small scaled values are halved to scale in proportion to the y-axis corner
-radius, which is twice the bounds height.
-##
-    for (SkScalar radiusX : { SK_ScalarNaN, SK_ScalarInfinity, 100.f, 50.f, 25.f} ) {
-        SkRRect rrect1 = SkRRect::MakeRectXY({10, 20, 60, 220}, radiusX, 200);
-        SkDebugf("left corner: (%g) %g\n", radiusX, rrect1.radii(SkRRect::kUpperLeft_Corner).fX);
-    }
-#StdOut
-left corner: (nan) 0
-left corner: (inf) 0
-left corner: (100) 25
-left corner: (50) 25
-left corner: (25) 12.5
-##
-##
-
-#SeeAlso Corner
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method const SkRect& getBounds() const
-#In Property
-#Line # returns bounds ##
-#Populate
-
-#Example
-#Height 120
-    SkPaint paint;
-    SkRRect rrect = SkRRect::MakeRectXY({20, 20, 220, 100}, 15, 15);
-    canvas->drawRRect(rrect, paint);
-    paint.setColor(SK_ColorWHITE);
-    rrect = SkRRect::MakeOval(rrect.getBounds());
-    canvas->drawRRect(rrect, paint);
-##
-
-#SeeAlso rect
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator==(const SkRRect& a, const SkRRect& b)
-#In Operators
-#Line # returns true if members are equal ##
-#Populate
-
-#Example
-    SkRRect rrect1 = SkRRect::MakeRectXY({10, 20, 60, 220}, 50, 200);
-    SkRRect rrect2 = SkRRect::MakeRectXY(rrect1.rect(), 25, 100);
-    SkRRect rrect3 = SkRRect::MakeOval(rrect1.rect());
-    canvas->drawRRect(rrect1, SkPaint());
-    std::string str = "rrect1 " + std::string(rrect1 == rrect2 ? "=" : "!") + "= rrect2";
-    canvas->drawString(str.c_str(), 10, 240, SkPaint());
-    canvas->translate(70, 0);
-    canvas->drawRRect(rrect2, SkPaint());
-    canvas->translate(70, 0);
-    canvas->drawRRect(rrect3, SkPaint());
-    str = "rrect2 " + std::string(rrect2 == rrect3 ? "=" : "!") + "= rrect3";
-    canvas->drawString(str.c_str(), -20, 240, SkPaint());
-##
-
-#SeeAlso operator!=(const SkRRect& a, const SkRRect& b)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator!=(const SkRRect& a, const SkRRect& b)
-#In Operators
-#Line # returns true if members are unequal ##
-#Populate
-
-#Example
-    SkRRect rrect1 = SkRRect::MakeRectXY({10, 20, 60, 220}, 50, 100);
-    SkRRect rrect2 = SkRRect::MakeRectXY(rrect1.rect(), 50, 50);
-    SkRRect rrect3 = SkRRect::MakeOval(rrect1.rect());
-    canvas->drawRRect(rrect1, SkPaint());
-    std::string str = "rrect1 " + std::string(rrect1 == rrect2 ? "=" : "!") + "= rrect2";
-    canvas->drawString(str.c_str(), 10, 240, SkPaint());
-    canvas->translate(70, 0);
-    canvas->drawRRect(rrect2, SkPaint());
-    canvas->translate(70, 0);
-    canvas->drawRRect(rrect3, SkPaint());
-    str = "rrect2 " + std::string(rrect2 == rrect3 ? "=" : "!") + "= rrect3";
-    canvas->drawString(str.c_str(), -20, 240, SkPaint());
-##
-
-#SeeAlso operator==(const SkRRect& a, const SkRRect& b)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void inset(SkScalar dx, SkScalar dy, SkRRect* dst) const
-#In Inset_Outset_Offset
-#Line # insets bounds and radii ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkRRect rrect = SkRRect::MakeRectXY({100, 20, 140, 220}, 50, 100);
-    for (int index = 0; index < 25; ++index) {
-       canvas->drawRRect(rrect, paint);
-       rrect.inset(-3, 3, &rrect);
-    }
-##
-
-#SeeAlso outset offset makeOffset
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void inset(SkScalar dx, SkScalar dy)
-#In Inset_Outset_Offset
-#Line # insets bounds and radii ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkRRect rrect = SkRRect::MakeRectXY({10, 20, 180, 220}, 50, 100);
-    for (int index = 0; index < 25; ++index) {
-       canvas->drawRRect(rrect, paint);
-       rrect.inset(3, 3);
-    }
-##
-
-#SeeAlso outset offset makeOffset
-
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void outset(SkScalar dx, SkScalar dy, SkRRect* dst) const
-#In Inset_Outset_Offset
-#Line # outsets bounds and radii ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkRRect rrect = SkRRect::MakeRectXY({100, 20, 140, 220}, 50, 100);
-    for (int index = 0; index < 25; ++index) {
-       canvas->drawRRect(rrect, paint);
-       rrect.outset(-3, 3, &rrect);
-    }
-##
-
-#SeeAlso inset offset makeOffset
-
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void outset(SkScalar dx, SkScalar dy)
-#In Inset_Outset_Offset
-#Line # outsets bounds and radii ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkRRect rrect = SkRRect::MakeRectXY({100, 20, 140, 220}, 50, 100);
-    for (int index = 0; index < 25; ++index) {
-       canvas->drawRRect(rrect, paint);
-       rrect.outset(3, 3);
-    }
-##
-
-#SeeAlso inset offset makeOffset
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void offset(SkScalar dx, SkScalar dy)
-#In Inset_Outset_Offset
-#Line # offsets bounds and radii ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkRRect rrect = SkRRect::MakeRectXY({100, 20, 140, 220}, 50, 100);
-    for (int index = 0; index < 25; ++index) {
-       canvas->drawRRect(rrect, paint);
-       rrect.offset(3, 3);
-    }
-##
-
-#SeeAlso makeOffset inset outset
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkRRect makeOffset(SkScalar dx, SkScalar dy) const
-#In Inset_Outset_Offset
-#Line # offsets bounds and radii ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setStyle(SkPaint::kStroke_Style);
-    SkRRect rrect = SkRRect::MakeRectXY({100, 20, 140, 220}, 50, 100);
-    for (int index = 0; index < 25; ++index) {
-       canvas->drawRRect(rrect, paint);
-       rrect = rrect.makeOffset(-3, 3);
-    }
-##
-
-#SeeAlso offset inset outset
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool contains(const SkRect& rect) const
-#In Intersection
-#Line # returns true if Rect is inside ##
-#Populate
-
-#Example
-#Height 110
-    SkRect test = {10, 10, 110, 80};
-    SkRRect rrect = SkRRect::MakeRect(test);
-    SkRRect oval = SkRRect::MakeOval(test);
-    test.inset(10, 10);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    canvas->drawString(rrect.contains(test) ? "contains" : "does not contain", 55, 100, paint);
-    canvas->drawString(oval.contains(test) ? "contains" : "does not contain", 185, 100, paint);    
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawRRect(rrect, paint);
-    canvas->drawRect(test, paint);
-    canvas->translate(120, 0);
-    canvas->drawRRect(oval, paint);
-    canvas->drawRect(test, paint);
-##
-
-#SeeAlso SkRect::contains
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isValid() const
-#In Utility
-#Line # returns if type() matches bounds and radii  ##
-#Populate
-
-#Example
-#Height 110
-    SkRRect rrect = SkRRect::MakeRect({10, 10, 110, 80});
-    SkRRect corrupt = rrect;
-    *((float*) &corrupt) = 120;
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    canvas->drawString(rrect.isValid() ? "is valid" : "is corrupted", 55, 100, paint);
-    canvas->drawString(corrupt.isValid() ? "is valid" : "is corrupted", 185, 100, paint);    
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawRRect(rrect, paint);
-    canvas->translate(120, 0);
-    canvas->drawRRect(corrupt, paint);
-##
-
-#SeeAlso Type getType
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Const kSizeInMemory 48
-#Line # storage space for Round_Rect ##
-
-Space required to write the contents of SkRRect into a buffer; always a multiple of four.
-
-#Const ##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t writeToMemory(void* buffer) const
-#In Utility
-#Line # writes Round_Rect to buffer ##
-#Populate
-
-#Example
-#Height 110
-    SkRRect rrect = SkRRect::MakeRect({10, 10, 110, 80});
-    char storage[SkRRect::kSizeInMemory];
-    rrect.writeToMemory(storage);
-    SkRRect copy;
-    copy.readFromMemory(storage, sizeof(storage));
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    canvas->drawString("rrect", 55, 100, paint);
-    canvas->drawString("copy", 185, 100, paint);    
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawRRect(rrect, paint);
-    canvas->translate(120, 0);
-    canvas->drawRRect(copy, paint);
-##
-
-#SeeAlso readFromMemory
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t readFromMemory(const void* buffer, size_t length)
-#In Utility
-#Line # reads Round_Rect from buffer ##
-#Populate
-
-#Example
-#Height 110
-    SkVector radii[] = {{5, 5},  {10, 10}, {15, 15}, {5, 5}};
-    SkRRect rrect;
-    rrect.setRectRadii({10, 10, 110, 80}, radii);
-    char storage[SkRRect::kSizeInMemory];
-    rrect.writeToMemory(storage);
-    SkRRect copy;
-    copy.readFromMemory(storage, sizeof(storage));
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    canvas->drawString("rrect", 55, 100, paint);
-    canvas->drawString("copy", 185, 100, paint);    
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawRRect(rrect, paint);
-    canvas->translate(120, 0);
-    canvas->drawRRect(copy, paint);
-##
-
-#SeeAlso writeToMemory
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool transform(const SkMatrix& matrix, SkRRect* dst) const
-#In Inset_Outset_Offset
-#Line # scales and offsets into copy ##
-#Populate
-
-#Example
-#Height 110
-    SkVector radii[] = {{5, 5},  {10, 10}, {15, 15}, {5, 5}};
-    SkRRect rrect;
-    rrect.setRectRadii({10, 10, 110, 80}, radii);
-    SkRRect transformed;
-    SkMatrix matrix = SkMatrix::MakeRectToRect(rrect.rect(), {140, 30, 220, 80},
-                                               SkMatrix::kCenter_ScaleToFit);
-    bool success = rrect.transform(matrix, &transformed);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    canvas->drawString("rrect", 55, 100, paint);
-    canvas->drawString(success ? "transformed" : "transform failed", 185, 100, paint);    
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawRRect(rrect, paint);
-    canvas->drawRRect(transformed, paint);
-##
-
-#SeeAlso SkPath::transform
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void dump(bool asHex) const
-#In Utility
-#Line # sends text representation to standard output ##
-#Populate
-
-#Example
-SkRRect rrect = SkRRect::MakeRect({6.f / 7, 2.f / 3, 6.f / 7, 2.f / 3});
-for (bool dumpAsHex : { false, true } ) {
-    rrect.dump(dumpAsHex);
-}
-#StdOut
-SkRect::MakeLTRB(0.857143f, 0.666667f, 0.857143f, 0.666667f);
-const SkPoint corners[] = {
-    { 0, 0 },
-    { 0, 0 },
-    { 0, 0 },
-    { 0, 0 },
-};
-SkRect::MakeLTRB(SkBits2Float(0x3f5b6db7), /* 0.857143 */
-                 SkBits2Float(0x3f2aaaab), /* 0.666667 */
-                 SkBits2Float(0x3f5b6db7), /* 0.857143 */
-                 SkBits2Float(0x3f2aaaab)  /* 0.666667 */);
-const SkPoint corners[] = {
-    { SkBits2Float(0x00000000), SkBits2Float(0x00000000) }, /* 0.000000 0.000000 */
-    { SkBits2Float(0x00000000), SkBits2Float(0x00000000) }, /* 0.000000 0.000000 */
-    { SkBits2Float(0x00000000), SkBits2Float(0x00000000) }, /* 0.000000 0.000000 */
-    { SkBits2Float(0x00000000), SkBits2Float(0x00000000) }, /* 0.000000 0.000000 */
-};
-##
-##
-
-#SeeAlso dumpHex SkRect::dump SkPath::dump SkPathMeasure::dump
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void dump() const
-#In Utility
-#Line # sends text representation using floats to standard output ##
-#Populate
-
-#Example
-SkRRect rrect = SkRRect::MakeRect({6.f / 7, 2.f / 3, 6.f / 7, 2.f / 3});
-rrect.dump();
-SkRect bounds = SkRect::MakeLTRB(0.857143f, 0.666667f, 0.857143f, 0.666667f);
-const SkPoint corners[] = {
-    { 0, 0 },
-    { 0, 0 },
-    { 0, 0 },
-    { 0, 0 },
-};
-SkRRect copy;
-copy.setRectRadii(bounds, corners);
-SkDebugf("rrect is " "%s" "equal to copy\n", rrect == copy ? "" : "not ");
-#StdOut
-SkRect::MakeLTRB(0.857143f, 0.666667f, 0.857143f, 0.666667f);
-const SkPoint corners[] = {
-    { 0, 0 },
-    { 0, 0 },
-    { 0, 0 },
-    { 0, 0 },
-};
-rrect is not equal to copy
-##
-##
-
-#SeeAlso dumpHex SkRect::dump SkPath::dump SkPathMeasure::dump
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void dumpHex() const
-#In Utility
-#Line # sends text representation using hexadecimal to standard output ##
-#Populate
-
-#Example
-SkRRect rrect = SkRRect::MakeRect({6.f / 7, 2.f / 3, 6.f / 7, 2.f / 3});
-rrect.dumpHex();
-SkRect bounds = SkRect::MakeLTRB(SkBits2Float(0x3f5b6db7), /* 0.857143 */
-                 SkBits2Float(0x3f2aaaab), /* 0.666667 */
-                 SkBits2Float(0x3f5b6db7), /* 0.857143 */
-                 SkBits2Float(0x3f2aaaab)  /* 0.666667 */);
-const SkPoint corners[] = {
-    { SkBits2Float(0x00000000), SkBits2Float(0x00000000) }, /* 0.000000 0.000000 */
-    { SkBits2Float(0x00000000), SkBits2Float(0x00000000) }, /* 0.000000 0.000000 */
-    { SkBits2Float(0x00000000), SkBits2Float(0x00000000) }, /* 0.000000 0.000000 */
-    { SkBits2Float(0x00000000), SkBits2Float(0x00000000) }, /* 0.000000 0.000000 */
-};
-SkRRect copy;
-copy.setRectRadii(bounds, corners);
-SkDebugf("rrect is " "%s" "equal to copy\n", rrect == copy ? "" : "not ");
-#StdOut
-SkRect::MakeLTRB(SkBits2Float(0x3f5b6db7), /* 0.857143 */
-                 SkBits2Float(0x3f2aaaab), /* 0.666667 */
-                 SkBits2Float(0x3f5b6db7), /* 0.857143 */
-                 SkBits2Float(0x3f2aaaab)  /* 0.666667 */);
-const SkPoint corners[] = {
-    { SkBits2Float(0x00000000), SkBits2Float(0x00000000) }, /* 0.000000 0.000000 */
-    { SkBits2Float(0x00000000), SkBits2Float(0x00000000) }, /* 0.000000 0.000000 */
-    { SkBits2Float(0x00000000), SkBits2Float(0x00000000) }, /* 0.000000 0.000000 */
-    { SkBits2Float(0x00000000), SkBits2Float(0x00000000) }, /* 0.000000 0.000000 */
-};
-rrect is equal to copy
-##
-##
-
-#SeeAlso dump SkRect::dumpHex SkPath::dumpHex
-
-#Method ##
-
-#Class SkRRect ##
-
-#Topic RRect ##
diff --git a/docs/SkRect_Reference.bmh b/docs/SkRect_Reference.bmh
deleted file mode 100644
index fa34825..0000000
--- a/docs/SkRect_Reference.bmh
+++ /dev/null
@@ -1,1858 +0,0 @@
-#Topic Rect
-#Alias Rects ##
-#Alias Rect_Reference ##
-
-#Struct SkRect
-
-#Code
-#Populate
-##
-
-SkRect holds four SkScalar coordinates describing the upper and
-lower bounds of a rectangle. SkRect may be created from outer bounds or
-from position, width, and height. SkRect describes an area; if its right
-is less than or equal to its left, or if its bottom is less than or equal to
-its top, it is considered empty.
-
-# move to topic about MakeIWH and friends
-SkRect can be constructed from int values to avoid compiler warnings that
-integer input cannot convert to SkScalar without loss of precision.
-
-#Member SkScalar  fLeft
-#Line # smaller x-axis bounds ##
-May contain any value, including infinities and NaN. The smaller of the
-horizontal values when sorted. When equal to or greater than fRight, Rect is empty.
-##
-
-#Member SkScalar  fTop
-#Line # smaller y-axis bounds ##
-May contain any value, including infinities and NaN. The smaller of the
-vertical values when sorted. When equal to or greater than fBottom, Rect is empty.
-##
-
-#Member SkScalar  fRight
-#Line # larger x-axis bounds ##
-May contain any value, including infinities and NaN. The larger of the
-horizontal values when sorted. When equal to or less than fLeft, Rect is empty.
-##
-
-#Member SkScalar  fBottom
-#Line # larger y-axis bounds ##
-May contain any value, including infinities and NaN. The larger of the
-vertical values when sorted. When equal to or less than fTop, Rect is empty.
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static constexpr SkRect MakeEmpty()
-
-#In Constructors
-#Line # constructs from bounds of (0, 0, 0, 0) ##
-#Populate
-
-#Example
-    SkRect rect = SkRect::MakeEmpty();
-    SkDebugf("MakeEmpty isEmpty: %s\n", rect.isEmpty() ? "true" : "false");
-    rect.offset(10, 10);
-    SkDebugf("offset rect isEmpty: %s\n", rect.isEmpty() ? "true" : "false");
-    rect.inset(10, 10);
-    SkDebugf("inset rect isEmpty: %s\n", rect.isEmpty() ? "true" : "false");
-    rect.outset(20, 20);
-    SkDebugf("outset rect isEmpty: %s\n", rect.isEmpty() ? "true" : "false");
-#StdOut
-MakeEmpty isEmpty: true
-offset rect isEmpty: true
-inset rect isEmpty: true
-outset rect isEmpty: false
-##
-##
-
-#SeeAlso isEmpty setEmpty SkIRect::MakeEmpty
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static constexpr SkRect MakeWH(SkScalar w, SkScalar h)
-
-#In Constructors
-#Line # constructs from SkScalar input returning (0, 0, width, height) ##
-#Populate
-
-#Example
-    SkRect rect1 = SkRect::MakeWH(25, 35);
-    SkRect rect2 = SkRect::MakeIWH(25, 35);
-    SkRect rect3 = SkRect::MakeXYWH(0, 0, 25, 35);
-    SkRect rect4 = SkRect::MakeLTRB(0, 0, 25, 35);
-    SkDebugf("all %s" "equal\n", rect1 == rect2 && rect2 == rect3 && rect3 == rect4 ?
-             "" : "not ");
-#StdOut
-all equal
-##
-##
-
-#SeeAlso MakeSize MakeXYWH MakeIWH setWH SkIRect::MakeWH
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkRect MakeIWH(int w, int h)
-
-#In Constructors
-#Line # constructs from int input returning (0, 0, width, height) ##
-#Populate
-
-#Example
-    SkIRect i_rect = SkIRect::MakeWH(25, 35);
-    SkRect  f_rect = SkRect::MakeIWH(25, 35);
-    SkDebugf("i_rect width: %d f_rect width:%g\n", i_rect.width(), f_rect.width());
-    i_rect = SkIRect::MakeWH(125000111, 0);
-    f_rect = SkRect::MakeIWH(125000111, 0);
-    SkDebugf("i_rect width: %d f_rect width:%.0f\n", i_rect.width(), f_rect.width());
-#StdOut
-i_rect width: 25 f_rect width:25
-i_rect width: 125000111 f_rect width:125000112
-##
-##
-
-#SeeAlso MakeXYWH MakeWH isetWH SkIRect::MakeWH
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static constexpr SkRect MakeSize(const SkSize& size)
-
-#In Constructors
-#Line # constructs from Size returning (0, 0, width, height) ##
-#Populate
-
-#Example
-    SkSize size = {25.5f, 35.5f};
-    SkRect rect = SkRect::MakeSize(size);
-    SkDebugf("rect width: %g  height: %g\n", rect.width(), rect.height());
-    SkISize floor = size.toFloor();
-    rect = SkRect::MakeSize(SkSize::Make(floor));
-    SkDebugf("floor width: %g  height: %g\n", rect.width(), rect.height());
-#StdOut
-rect width: 25.5  height: 35.5
-floor width: 25  height: 35
-##
-##
-
-#SeeAlso MakeWH MakeXYWH MakeIWH setWH SkIRect::MakeWH
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static constexpr SkRect MakeLTRB(SkScalar l, SkScalar t, SkScalar r,
-                                                           SkScalar b)
-#In Constructors
-#Line # constructs from SkScalar left, top, right, bottom ##
-#Populate
-
-#Example
-    SkRect rect = SkRect::MakeLTRB(5, 35, 15, 25);
-    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-    rect.sort();
-    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-#StdOut
-rect: 5, 35, 15, 25  isEmpty: true
-rect: 5, 25, 15, 35  isEmpty: false
-##
-##
-
-#SeeAlso MakeXYWH SkIRect::MakeLTRB
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static constexpr SkRect MakeXYWH(SkScalar x, SkScalar y, SkScalar w, SkScalar h)
-
-#In Constructors
-#Line # constructs from SkScalar input returning (x, y, width, height) ##
-Returns constructed Rect set to #Formula # (x, y, x + w, y + h) ##.
-Does not validate input; w or h may be negative.
-
-#Param x  stored in fLeft ##
-#Param y  stored in fTop ##
-#Param w  added to x and stored in fRight ##
-#Param h  added to y and stored in fBottom ##
-
-#Return bounds at (x, y) with width w and height h ##
-
-#Example
-    SkRect rect = SkRect::MakeXYWH(5, 35, -15, 25);
-    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-    rect.sort();
-    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-#StdOut
-rect: 5, 35, -10, 60  isEmpty: true
-rect: -10, 35, 5, 60  isEmpty: false
-##
-##
-
-#SeeAlso MakeLTRB SkIRect::MakeXYWH
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkRect Make(const SkISize& size)
-
-#In Constructors
-#Line # constructs from ISize returning (0, 0, width, height) ##
-#Populate
-
-#Example
-    SkRect rect1 = SkRect::MakeSize({2, 35});
-    SkRect rect2 = SkRect::MakeIWH(2, 35);
-    SkDebugf("rect1 %c= rect2\n", rect1 == rect2 ? '=' : '!');
-#StdOut
-rect1 == rect2
-##
-##
-
-#SeeAlso MakeWH MakeXYWH SkRect::MakeIWH SkIRect::MakeSize
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static SkRect Make(const SkIRect& irect)
-
-#In Constructors
-#Populate
-
-#Example
-    SkIRect i_rect1 = {2, 35, 22, 53};
-    SkRect f_rect = SkRect::Make(i_rect1);
-    f_rect.offset(0.49f, 0.49f);
-    SkIRect i_rect2;
-    f_rect.round(&i_rect2);
-    SkDebugf("i_rect1 %c= i_rect2\n", i_rect1 == i_rect2? '=' : '!');
-##
-
-#SeeAlso MakeLTRB
-
-##
-
-#Subtopic Property
-#Line # member values, center, validity ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isEmpty() const
-
-#In Property
-#Line # returns true if width or height are zero or negative ##
-#Populate
-
-#Example
-    SkRect tests[] = {{20, 40, 10, 50}, {20, 40, 20, 50}};
-    for (auto rect : tests) {
-        SkDebugf("rect: {%g, %g, %g, %g} is" "%s empty\n", rect.left(), rect.top(), rect.right(),
-                 rect.bottom(), rect.isEmpty() ? "" : " not");
-        rect.sort();
-        SkDebugf("sorted: {%g, %g, %g, %g} is" "%s empty\n", rect.left(), rect.top(), rect.right(),
-                 rect.bottom(), rect.isEmpty() ? "" : " not");
-    }
-#StdOut
-rect: {20, 40, 10, 50} is empty
-sorted: {10, 40, 20, 50} is not empty
-rect: {20, 40, 20, 50} is empty
-sorted: {20, 40, 20, 50} is empty
-##
-##
-
-#SeeAlso MakeEmpty sort SkIRect::isEmpty
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isSorted() const
-
-#In Property
-#Line # returns true if width or height are zero or positive ##
-#Populate
-
-#Example
-    SkRect tests[] = {{20, 40, 10, 50}, {20, 40, 20, 50}};
-    for (auto rect : tests) {
-        SkDebugf("rect: {%g, %g, %g, %g} is" "%s sorted\n", rect.left(), rect.top(), rect.right(),
-                 rect.bottom(), rect.isSorted() ? "" : " not");
-        rect.sort();
-        SkDebugf("sorted: {%g, %g, %g, %g} is" "%s sorted\n", rect.left(), rect.top(), rect.right(),
-                 rect.bottom(), rect.isSorted() ? "" : " not");
-    }
-#StdOut
-rect: {20, 40, 10, 50} is not sorted
-sorted: {10, 40, 20, 50} is sorted
-rect: {20, 40, 20, 50} is sorted
-sorted: {20, 40, 20, 50} is sorted
-##
-##
-
-#SeeAlso sort makeSorted isEmpty
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isFinite() const
-
-#In Property
-#Line # returns true if no member is infinite or NaN ##
-#Populate
-
-#Example
-SkRect largest = { SK_ScalarMin, SK_ScalarMin, SK_ScalarMax, SK_ScalarMax };
-    SkDebugf("largest is finite: %s\n", largest.isFinite() ? "true" : "false");
-    SkDebugf("large width %g\n", largest.width());
-    SkRect widest = SkRect::MakeWH(largest.width(), largest.height());
-    SkDebugf("widest is finite: %s\n", widest.isFinite() ? "true" : "false");
-#StdOut
-largest is finite: true
-large width inf
-widest is finite: false
-##
-##
-
-#SeeAlso SkScalarIsFinite SkScalarIsNaN
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar    x() const
-
-#In Property
-#Line # returns bounds left ##
-#Populate
-
-#Example
-    SkRect unsorted = { 15, 5, 10, 25 };
-    SkDebugf("unsorted.fLeft: %g unsorted.x(): %g\n", unsorted.fLeft, unsorted.x());
-    SkRect sorted = unsorted.makeSorted();
-    SkDebugf("sorted.fLeft: %g sorted.x(): %g\n", sorted.fLeft, sorted.x());
-#StdOut
-unsorted.fLeft: 15 unsorted.x(): 15
-sorted.fLeft: 10 sorted.x(): 10
-##
-##
-
-#SeeAlso fLeft left() y() SkIRect::x()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar    y() const
-
-#In Property
-#Line # returns bounds top ##
-#Populate
-
-#Example
-    SkRect unsorted = { 15, 25, 10, 5 };
-    SkDebugf("unsorted.fTop: %g unsorted.y(): %g\n", unsorted.fTop, unsorted.y());
-    SkRect sorted = unsorted.makeSorted();
-    SkDebugf("sorted.fTop: %g sorted.y(): %g\n", sorted.fTop, sorted.y());
-#StdOut
-unsorted.fTop: 25 unsorted.y(): 25
-sorted.fTop: 5 sorted.y(): 5
-##
-##
-
-#SeeAlso fTop top() x() SkIRect::y()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar    left() const
-
-#In Property
-#Line # returns smaller bounds in x, if sorted ##
-#Populate
-
-#Example
-    SkRect unsorted = { 15, 5, 10, 25 };
-    SkDebugf("unsorted.fLeft: %g unsorted.left(): %g\n", unsorted.fLeft, unsorted.left());
-    SkRect sorted = unsorted.makeSorted();
-    SkDebugf("sorted.fLeft: %g sorted.left(): %g\n", sorted.fLeft, sorted.left());
-#StdOut
-unsorted.fLeft: 15 unsorted.left(): 15
-sorted.fLeft: 10 sorted.left(): 10
-##
-##
-
-#SeeAlso fLeft x() SkIRect::left()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar    top() const
-
-#In Property
-#Line # returns smaller bounds in y, if sorted ##
-#Populate
-
-#Example
-    SkRect unsorted = { 15, 25, 10, 5 };
-    SkDebugf("unsorted.fTop: %g unsorted.top(): %g\n", unsorted.fTop, unsorted.top());
-    SkRect sorted = unsorted.makeSorted();
-    SkDebugf("sorted.fTop: %g sorted.top(): %g\n", sorted.fTop, sorted.top());
-#StdOut
-unsorted.fTop: 25 unsorted.top(): 25
-sorted.fTop: 5 sorted.top(): 5
-##
-##
-
-#SeeAlso fTop y() SkIRect::top()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar    right() const
-
-#In Property
-#Line # returns larger bounds in x, if sorted ##
-#Populate
-
-#Example
-    SkRect unsorted = { 15, 25, 10, 5 };
-    SkDebugf("unsorted.fRight: %g unsorted.right(): %g\n", unsorted.fRight, unsorted.right());
-    SkRect sorted = unsorted.makeSorted();
-    SkDebugf("sorted.fRight: %g sorted.right(): %g\n", sorted.fRight, sorted.right());
-#StdOut
-unsorted.fRight: 10 unsorted.right(): 10
-sorted.fRight: 15 sorted.right(): 15
-##
-##
-
-#SeeAlso fRight SkIRect::right()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar    bottom() const
-
-#In Property
-#Line # returns larger bounds in y, if sorted ##
-#Populate
-
-#Example
-    SkRect unsorted = { 15, 25, 10, 5 };
-    SkDebugf("unsorted.fBottom: %g unsorted.bottom(): %g\n", unsorted.fBottom, unsorted.bottom());
-    SkRect sorted = unsorted.makeSorted();
-    SkDebugf("sorted.fBottom: %g sorted.bottom(): %g\n", sorted.fBottom, sorted.bottom());
-#StdOut
-unsorted.fBottom: 5 unsorted.bottom(): 5
-sorted.fBottom: 25 sorted.bottom(): 25
-##
-##
-
-#SeeAlso fBottom SkIRect::bottom()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar    width() const
-
-#In Property
-#Line # returns span in x ##
-#Populate
-
-#Example
-#Description
-Compare with SkIRect::width() example.
-##
-    SkRect unsorted = { 15, 25, 10, 5 };
-    SkDebugf("unsorted width: %g\n", unsorted.width());
-    SkRect large = { -2147483647.f, 1, 2147483644.f, 2 };
-    SkDebugf("large width: %.0f\n", large.width());
-#StdOut
-unsorted width: -5
-large width: 4294967296
-##
-##
-
-#SeeAlso height() SkIRect::width()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar    height() const
-
-#In Property
-#Line # returns span in y ##
-#Populate
-
-#Example
-#Description
-Compare with SkIRect::height() example.
-##
-    SkRect unsorted = { 15, 25, 10, 20 };
-    SkDebugf("unsorted height: %g\n", unsorted.height());
-    SkRect large = { 1, -2147483647.f, 2, 2147483644.f };
-    SkDebugf("large height: %.0f\n", large.height());
-#StdOut
-unsorted height: -5
-large height: 4294967296
-##
-##
-
-#SeeAlso width() SkIRect::height()
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar    centerX() const
-
-#In Property
-#Line # returns midpoint in x ##
-#Populate
-
-#Example
-    SkRect tests[] = {{20, 30, 41, 51}, {-20, -30, -41, -51}};
-    for (auto rect : tests) {
-        SkDebugf("left: %3g right: %3g centerX: %3g\n", rect.left(), rect.right(), rect.centerX());
-        rect.sort();
-        SkDebugf("left: %3g right: %3g centerX: %3g\n", rect.left(), rect.right(), rect.centerX());
-    }
-#StdOut
-left:  20 right:  41 centerX: 30.5
-left:  20 right:  41 centerX: 30.5
-left: -20 right: -41 centerX: -30.5
-left: -41 right: -20 centerX: -30.5
-##
-##
-
-#SeeAlso centerY
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkScalar    centerY() const
-
-#In Property
-#Line # returns midpoint in y ##
-#Populate
-
-#Example
-   SkRect rect = { 2e+38, 2e+38, 3e+38, 3e+38 };
-   SkDebugf("left: %g right: %g centerX: %g ", rect.left(), rect.right(), rect.centerX());
-   SkDebugf("safe mid x: %g\n", rect.left() / 2 + rect.right() / 2);
-#StdOut
-left: 2e+38 right: 3e+38 centerX: 2.5e+38 safe mid x: 2.5e+38
-##
-##
-
-#SeeAlso centerX
-
-##
-
-#Subtopic Property ##
-
-#Subtopic Operators
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator==(const SkRect& a, const SkRect& b)
-
-#In Operators
-#Line # returns true if members are equal ##
-#Populate
-
-#Example
-    auto debugster = [](const SkRect& test) -> void {
-        SkRect negZero = {-0.0f, -0.0f, 2, 2};
-        SkDebugf("{%g, %g, %g, %g} %c= {%g, %g, %g, %g} %s numerically equal\n",
-                 test.fLeft, test.fTop, test.fRight, test.fBottom,
-                 negZero.fLeft, negZero.fTop, negZero.fRight, negZero.fBottom,
-                 test == negZero ? '=' : '!',
-                 test.fLeft == negZero.fLeft && test.fTop == negZero.fTop &&
-                 test.fRight == negZero.fRight && test.fBottom == negZero.fBottom ?
-                 "and are" : "yet are not");
-    };
-    SkRect tests[] = {{0, 0, 2, 2}, {-0, -0, 2, 2}, {0.0f, 0.0f, 2, 2}};
-    SkDebugf("tests are %s" "equal\n", tests[0] == tests[1] && tests[1] == tests[2] ? "" : "not ");
-    for (auto rect : tests) {
-        debugster(rect);
-    }
-#StdOut
-tests are equal
-{0, 0, 2, 2} == {-0, -0, 2, 2} and are numerically equal
-{0, 0, 2, 2} == {-0, -0, 2, 2} and are numerically equal
-{0, 0, 2, 2} == {-0, -0, 2, 2} and are numerically equal
-##
-##
-
-#SeeAlso operator!=(const SkRect& a, const SkRect& b)
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator!=(const SkRect& a, const SkRect& b)
-
-#In Operators
-#Line # returns true if members are unequal ##
-#Populate
-
-#Example
-    SkRect test = {0, 0, 2, SK_ScalarNaN};
-    SkDebugf("test with NaN is %s" "equal to itself\n", test == test ? "" : "not ");
-#StdOut
-test with NaN is not equal to itself
-##
-##
-
-#SeeAlso operator==(const SkRect& a, const SkRect& b)
-
-##
-
-#Subtopic Operators ##
-
-#Subtopic As_Points
-#Line # conversion to and from Points ##
-
-# ------------------------------------------------------------------------------
-
-#Method void toQuad(SkPoint quad[4]) const
-
-#In As_Points
-#Line # returns four corners as Point ##
-#Populate
-
-#Example
-    SkRect rect = {1, 2, 3, 4};
-    SkPoint corners[4];
-    rect.toQuad(corners);
-    SkDebugf("rect: {%g, %g, %g, %g}\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-    SkDebugf("corners:");
-    for (auto corner : corners) {
-        SkDebugf(" {%g, %g}", corner.fX, corner.fY);
-    }
-    SkDebugf("\n");
-#StdOut
-rect: {1, 2, 3, 4}
-corners: {1, 2} {3, 2} {3, 4} {1, 4}
-##
-##
-
-#SeeAlso SkPath::addRect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void setBounds(const SkPoint pts[], int count)
-
-#In As_Points
-#Line # sets to upper and lower limits of Point array ##
-#Populate
-
-#Example
-   SkPoint points[] = {{3, 4}, {1, 2}, {5, 6}, {SK_ScalarNaN, 8}};
-   for (int count = 0; count <= (int) SK_ARRAY_COUNT(points); ++count) {
-       SkRect rect;
-       rect.setBounds(points, count);
-       if (count > 0) {
-           SkDebugf("added: %3g, %g ", points[count - 1].fX,  points[count - 1].fY);
-       } else {
-           SkDebugf("%14s", " ");
-       }
-       SkDebugf("count: %d rect: %g, %g, %g, %g\n", count,
-               rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-   }
-#StdOut
-              count: 0 rect: 0, 0, 0, 0
-added:   3, 4 count: 1 rect: 3, 4, 3, 4
-added:   1, 2 count: 2 rect: 1, 2, 3, 4
-added:   5, 6 count: 3 rect: 1, 2, 5, 6
-added: nan, 8 count: 4 rect: 0, 0, 0, 0
-##
-##
-
-#SeeAlso set setBoundsCheck SkPath::addPoly
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool setBoundsCheck(const SkPoint pts[], int count)
-
-#In As_Points
-#Line # sets to upper and lower limits of Point array ##
-#Populate
-
-#Example
-   SkPoint points[] = {{3, 4}, {1, 2}, {5, 6}, {SK_ScalarNaN, 8}};
-   for (int count = 0; count <= (int) SK_ARRAY_COUNT(points); ++count) {
-       SkRect rect;
-       bool success = rect.setBoundsCheck(points, count);
-       if (count > 0) {
-           SkDebugf("added: %3g, %g ", points[count - 1].fX,  points[count - 1].fY);
-       } else {
-           SkDebugf("%14s", " ");
-       }
-       SkDebugf("count: %d rect: %g, %g, %g, %g success: %s\n", count,
-               rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, success ? "true" : "false");
-   }
-#StdOut
-              count: 0 rect: 0, 0, 0, 0 success: true
-added:   3, 4 count: 1 rect: 3, 4, 3, 4 success: true
-added:   1, 2 count: 2 rect: 1, 2, 3, 4 success: true
-added:   5, 6 count: 3 rect: 1, 2, 5, 6 success: true
-added: nan, 8 count: 4 rect: 0, 0, 0, 0 success: false
-##
-##
-
-#SeeAlso set setBounds SkPath::addPoly
-
-##
-
-#Subtopic As_Points ##
-
-#Subtopic Set
-#Line # replaces all values ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setBoundsNoCheck(const SkPoint pts[], int count)
-#In Set
-#Line # sets to upper and lower limits of Point array ##
-#Populate
-
-#Example
-   SkPoint points[] = {{3, 4}, {1, 2}, {SK_ScalarInfinity, 6}, {SK_ScalarNaN, 8}};
-   for (int count = 0; count <= (int) SK_ARRAY_COUNT(points); ++count) {
-       SkRect rect;
-       rect.setBoundsNoCheck(points, count);
-       if (count > 0) {
-           SkDebugf("added: %3g, %g ", points[count - 1].fX,  points[count - 1].fY);
-       } else {
-           SkDebugf("%14s", " ");
-       }
-       SkDebugf("count: %d rect: %g, %g, %g, %g\n", count,
-               rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-   }
-##
-
-#SeeAlso setBoundsCheck
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void setEmpty()
-
-#In Set
-#Line # sets to (0, 0, 0, 0) ##
-#Populate
-
-#Example
-    SkRect rect = {3, 4, 1, 2};
-    for (int i = 0; i < 2; ++i) {
-    SkDebugf("rect: {%g, %g, %g, %g} is %s" "empty\n", rect.fLeft, rect.fTop,
-             rect.fRight, rect.fBottom, rect.isEmpty() ? "" : "not ");
-    rect.setEmpty();
-    }
-#StdOut
-rect: {3, 4, 1, 2} is empty
-rect: {0, 0, 0, 0} is empty
-##
-##
-
-#SeeAlso MakeEmpty SkIRect::setEmpty
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void set(const SkIRect& src)
-
-#In Set
-#Line # sets to SkScalar input (left, top, right, bottom) and others ##
-#Populate
-
-#Example
-    SkIRect i_rect = {3, 4, 1, 2};
-    SkDebugf("i_rect: {%d, %d, %d, %d}\n", i_rect.fLeft, i_rect.fTop, i_rect.fRight, i_rect.fBottom);
-    SkRect f_rect;
-    f_rect.set(i_rect);
-    SkDebugf("f_rect: {%g, %g, %g, %g}\n", f_rect.fLeft, f_rect.fTop, f_rect.fRight, f_rect.fBottom);
-#StdOut
-i_rect: {3, 4, 1, 2}
-f_rect: {3, 4, 1, 2}
-##
-##
-
-#SeeAlso  setLTRB SkIntToScalar
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void set(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom)
-
-#In Set
-#Populate
-
-#Example
-    SkRect rect1 = {3, 4, 1, 2};
-    SkDebugf("rect1: {%g, %g, %g, %g}\n", rect1.fLeft, rect1.fTop, rect1.fRight, rect1.fBottom);
-    SkRect rect2;
-    rect2.set(3, 4, 1, 2);
-    SkDebugf("rect2: {%g, %g, %g, %g}\n", rect2.fLeft, rect2.fTop, rect2.fRight, rect2.fBottom);
-#StdOut
-rect1: {3, 4, 1, 2}
-rect2: {3, 4, 1, 2}
-##
-##
-
-#SeeAlso setLTRB setXYWH SkIRect::set
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void setLTRB(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom)
-
-#In Set
-#Line # sets to SkScalar input (left, top, right, bottom) ##
-#Populate
-
-#Example
-    SkRect rect1 = {3, 4, 1, 2};
-    SkDebugf("rect1: {%g, %g, %g, %g}\n", rect1.fLeft, rect1.fTop, rect1.fRight, rect1.fBottom);
-    SkRect rect2;
-    rect2.setLTRB(3, 4, 1, 2);
-    SkDebugf("rect2: {%g, %g, %g, %g}\n", rect2.fLeft, rect2.fTop, rect2.fRight, rect2.fBottom);
-#StdOut
-rect1: {3, 4, 1, 2}
-rect2: {3, 4, 1, 2}
-##
-##
-
-#SeeAlso set setXYWH SkIRect::set
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void set(const SkPoint pts[], int count)
-
-#In Set
-#Populate
-
-#Example
-   SkPoint points[] = {{3, 4}, {1, 2}, {5, 6}, {SK_ScalarNaN, 8}};
-   for (int count = 0; count <= (int) SK_ARRAY_COUNT(points); ++count) {
-       SkRect rect;
-       rect.set(points, count);
-       if (count > 0) {
-           SkDebugf("added: %3g, %g ", points[count - 1].fX,  points[count - 1].fY);
-       } else {
-           SkDebugf("%14s", " ");
-       }
-       SkDebugf("count: %d rect: %g, %g, %g, %g\n", count,
-               rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-   }
-#StdOut
-              count: 0 rect: 0, 0, 0, 0
-added:   3, 4 count: 1 rect: 3, 4, 3, 4
-added:   1, 2 count: 2 rect: 1, 2, 3, 4
-added:   5, 6 count: 3 rect: 1, 2, 5, 6
-added: nan, 8 count: 4 rect: 0, 0, 0, 0
-##
-##
-
-#SeeAlso setBounds setBoundsCheck SkPath::addPoly
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void set(const SkPoint& p0, const SkPoint& p1)
-
-#In Set
-#Populate
-
-#Example
-#Description
-p0 and p1 may be swapped and have the same effect unless one contains NaN.
-##
-   SkPoint point1 = {SK_ScalarNaN, 8};
-   SkPoint point2 = {3, 4};
-   SkRect rect;
-   rect.set(point1, point2);
-   SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-   rect.set(point2, point1);
-   SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-##
-
-#SeeAlso setBounds setBoundsCheck
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void setXYWH(SkScalar x, SkScalar y, SkScalar width, SkScalar height)
-
-#In Set
-#Line # sets to SkScalar input (x, y, width, height) ##
-Sets Rect to #Formula # (x, y, x + width, y + height) ##.
-Does not validate input; width or height may be negative.
-
-#Param x  stored in fLeft ##
-#Param y  stored in fTop ##
-#Param width  added to x and stored in fRight ##
-#Param height  added to y and stored in fBottom ##
-
-#Example
-    SkRect rect;
-    rect.setXYWH(5, 35, -15, 25);
-    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-    rect.sort();
-    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-#StdOut
-rect: 5, 35, -10, 60  isEmpty: true
-rect: -10, 35, 5, 60  isEmpty: false
-##
-##
-
-#SeeAlso MakeXYWH setLTRB set SkIRect::setXYWH
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void setWH(SkScalar width, SkScalar height)
-
-#In Set
-#Line # sets to SkScalar input (0, 0, width, height) ##
-#Populate
-
-#Example
-    SkRect rect;
-    rect.setWH(-15, 25);
-    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-    rect.sort();
-    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-#StdOut
-rect: 0, 0, -15, 25  isEmpty: true
-rect: -15, 0, 0, 25  isEmpty: false
-##
-##
-
-#SeeAlso MakeWH setXYWH isetWH
-
-##
-
-#Subtopic Set ##
-
-#Subtopic From_Integers
-#Line # sets Scalar values from integer input ##
-
-# ------------------------------------------------------------------------------
-
-#Method void iset(int left, int top, int right, int bottom)
-
-#In From_Integers
-#Line # sets to int input (left, top, right, bottom) ##
-#Populate
-
-#Example
-    SkRect rect1 = {3, 4, 1, 2};
-    SkDebugf("rect1: {%g, %g, %g, %g}\n", rect1.fLeft, rect1.fTop, rect1.fRight, rect1.fBottom);
-    SkRect rect2;
-    rect2.iset(3, 4, 1, 2);
-    SkDebugf("rect2: {%g, %g, %g, %g}\n", rect2.fLeft, rect2.fTop, rect2.fRight, rect2.fBottom);
-#StdOut
-rect1: {3, 4, 1, 2}
-rect2: {3, 4, 1, 2}
-##
-##
-
-#SeeAlso set setLTRB SkIRect::set SkIntToScalar
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void isetWH(int width, int height)
-
-#In From_Integers
-#Line # sets to int input (0, 0, width, height) ##
-#Populate
-
-#Example
-    SkRect rect1 = {0, 0, 1, 2};
-    SkDebugf("rect1: {%g, %g, %g, %g}\n", rect1.fLeft, rect1.fTop, rect1.fRight, rect1.fBottom);
-    SkRect rect2;
-    rect2.isetWH(1, 2);
-    SkDebugf("rect2: {%g, %g, %g, %g}\n", rect2.fLeft, rect2.fTop, rect2.fRight, rect2.fBottom);
-#StdOut
-rect1: {0, 0, 1, 2}
-rect2: {0, 0, 1, 2}
-##
-##
-
-#SeeAlso MakeWH MakeXYWH iset() SkIRect:MakeWH
-
-##
-
-#Subtopic From_Integers ##
-
-#Subtopic Inset_Outset_Offset
-#Line # moves sides ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkRect makeOffset(SkScalar dx, SkScalar dy) const
-
-#In Inset_Outset_Offset
-#Line # constructs from translated sides ##
-#Populate
-
-#Example
-    SkRect rect = { 10, 50, 20, 60 };
-    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-    rect = rect.makeOffset(15, 32);
-    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-#StdOut
-rect: 10, 50, 20, 60  isEmpty: false
-rect: 25, 82, 35, 92  isEmpty: false
-##
-##
-
-#SeeAlso offset() makeInset makeOutset SkIRect::makeOffset
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkRect makeInset(SkScalar dx, SkScalar dy) const
-
-#In Inset_Outset_Offset
-#Line # constructs from sides moved symmetrically about the center ##
-#Populate
-
-#Example
-    SkRect rect = { 10, 50, 20, 60 };
-    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-    rect = rect.makeInset(15, 32);
-    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-#StdOut
-rect: 10, 50, 20, 60  isEmpty: false
-rect: 25, 82, 5, 28  isEmpty: true
-##
-##
-
-#SeeAlso inset() makeOffset makeOutset SkIRect::makeInset
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method SkRect makeOutset(SkScalar dx, SkScalar dy) const
-
-#In Inset_Outset_Offset
-#Line # constructs from sides moved symmetrically about the center ##
-#Populate
-
-#Example
-    SkRect rect = { 10, 50, 20, 60 };
-    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-    rect = rect.makeOutset(15, 32);
-    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
-              rect.bottom(), rect.isEmpty() ? "true" : "false");
-#StdOut
-rect: 10, 50, 20, 60  isEmpty: false
-rect: -5, 18, 35, 92  isEmpty: false
-##
-##
-
-#SeeAlso outset() makeOffset makeInset SkIRect::makeOutset
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void offset(SkScalar dx, SkScalar dy)
-
-#In Inset_Outset_Offset
-#Line # translates sides without changing width and height ##
-#Populate
-
-#Example
-    SkRect rect = { 10, 14, 50, 73 };
-    rect.offset(5, 13);
-    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-#StdOut
-rect: 15, 27, 55, 86
-##
-##
-
-#SeeAlso offsetTo makeOffset SkIRect::offset
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void offset(const SkPoint& delta)
-
-#In Inset_Outset_Offset
-#Populate
-
-#Example
-    SkRect rect = { 10, 14, 50, 73 };
-    rect.offset({5, 13});
-    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-#StdOut
-rect: 15, 27, 55, 86
-##
-##
-
-#SeeAlso offsetTo makeOffset SkIRect::offset
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void offsetTo(SkScalar newX, SkScalar newY)
-
-#In Inset_Outset_Offset
-#Line # translates to (x, y) without changing width and height ##
-#Populate
-
-#Example
-    SkRect rect = { 10, 14, 50, 73 };
-    rect.offsetTo(15, 27);
-    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-#StdOut
-rect: 15, 27, 55, 86
-##
-##
-
-#SeeAlso offset makeOffset setXYWH SkIRect::offsetTo
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void inset(SkScalar dx, SkScalar dy)
-
-#In Inset_Outset_Offset
-#Line # moves the sides symmetrically about the center ##
-#Populate
-
-#Example
-    SkRect rect = { 10, 14, 50, 73 };
-    rect.inset(5, 13);
-    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-#StdOut
-rect: 15, 27, 45, 60
-##
-##
-
-#SeeAlso outset makeInset SkIRect::inset
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void outset(SkScalar dx, SkScalar dy)
-
-#In Inset_Outset_Offset
-#Line # moves the sides symmetrically about the center ##
-#Populate
-
-#Example
-    SkRect rect = { 10, 14, 50, 73 };
-    rect.outset(5, 13);
-    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-#StdOut
-rect: 5, 1, 55, 86
-##
-##
-
-#SeeAlso inset makeOutset SkIRect::outset
-
-##
-
-#Subtopic Inset_Outset_Offset ##
-
-#Subtopic Intersection
-#Line # sets to shared bounds ##
-
-Rects intersect when they enclose a common area. To intersect, each of the pair
-must describe area; fLeft is less than fRight, and fTop is less than fBottom;
-isEmpty() returns false. The intersection of Rect pair can be described by:
-#Formula # (max(a.fLeft, b.fLeft), max(a.fTop, b.fTop),
-            min(a.fRight, b.fRight), min(a.fBottom, b.fBottom)) ##.
-
-The intersection is only meaningful if the resulting Rect is not empty and
-describes an area: fLeft is less than fRight, and fTop is less than fBottom.
-
-# ------------------------------------------------------------------------------
-
-#Method    bool contains(SkScalar x, SkScalar y) const
-
-#In Intersection
-#Line # returns true if points are equal or inside ##
-#Populate
-
-#Example
-    SkRect rect = { 30, 50, 40, 60 };
-    SkPoint tests[] = { { 30, 50 }, { 39, 49 }, { 29, 59 } };
-    for (auto contained : tests) {
-        SkDebugf("rect: (%g, %g, %g, %g) %s (%g, %g)\n",
-                 rect.left(), rect.top(), rect.right(), rect.bottom(),
-                 rect.contains(contained.x(), contained.y()) ? "contains" : "does not contain",
-                 contained.x(), contained.y());
-    }
-#StdOut
-rect: (30, 50, 40, 60) contains (30, 50)
-rect: (30, 50, 40, 60) does not contain (39, 49)
-rect: (30, 50, 40, 60) does not contain (29, 59)
-##
-##
-
-#SeeAlso SkIRect::contains SkRRect::contains
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method    bool contains(const SkRect& r) const
-
-#In Intersection
-#Populate
-
-#Example
-    SkRect rect = { 30, 50, 40, 60 };
-    SkRect tests[] = { { 30, 50, 31, 51}, { 39, 49, 40, 50}, { 29, 59, 30, 60} };
-    for (auto contained : tests) {
-        SkDebugf("rect: (%g, %g, %g, %g) %s (%g, %g, %g, %g)\n",
-                 rect.left(), rect.top(), rect.right(), rect.bottom(),
-                 rect.contains(contained) ? "contains" : "does not contain",
-                 contained.left(), contained.top(), contained.right(), contained.bottom());
-    }
-#StdOut
-rect: (30, 50, 40, 60) contains (30, 50, 31, 51)
-rect: (30, 50, 40, 60) does not contain (39, 49, 40, 50)
-rect: (30, 50, 40, 60) does not contain (29, 59, 30, 60)
-##
-##
-
-#SeeAlso SkIRect::contains
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method    bool contains(const SkIRect& r) const
-
-#In Intersection
-#Populate
-
-#Example
-    SkRect rect = { 30, 50, 40, 60 };
-    SkIRect tests[] = { { 30, 50, 31, 51}, { 39, 49, 40, 50}, { 29, 59, 30, 60} };
-    for (auto contained : tests) {
-        SkDebugf("rect: (%g, %g, %g, %g) %s (%d, %d, %d, %d)\n",
-                 rect.left(), rect.top(), rect.right(), rect.bottom(),
-                 rect.contains(contained) ? "contains" : "does not contain",
-                 contained.left(), contained.top(), contained.right(), contained.bottom());
-    }
-#StdOut
-rect: (30, 50, 40, 60) contains (30, 50, 31, 51)
-rect: (30, 50, 40, 60) does not contain (39, 49, 40, 50)
-rect: (30, 50, 40, 60) does not contain (29, 59, 30, 60)
-##
-##
-
-#SeeAlso SkIRect::contains
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool intersect(const SkRect& r)
-
-#In Intersection
-#Line # sets to shared area; returns true if not empty ##
-#Populate
-
-#Example
-#Description
-Two SkDebugf calls are required. If the calls are combined, their arguments
-may not be evaluated in left to right order: the printed intersection may
-be before or after the call to intersect.
-##
-    SkRect leftRect =  { 10, 40, 50, 80 };
-    SkRect rightRect = { 30, 60, 70, 90 };
-    SkDebugf("%s intersection: ", leftRect.intersect(rightRect) ? "" : "no ");
-    SkDebugf("%g, %g, %g, %g\n", leftRect.left(), leftRect.top(),
-                                 leftRect.right(), leftRect.bottom());
-#StdOut
- intersection: 30, 60, 50, 80
-##
-##
-
-#SeeAlso intersects Intersects join SkIRect::intersect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool intersect(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom)
-
-#In Intersection
-#Populate
-
-#Example
-#Description
-Two SkDebugf calls are required. If the calls are combined, their arguments
-may not be evaluated in left to right order: the printed intersection may
-be before or after the call to intersect.
-##
-    SkRect leftRect =  { 10, 40, 50, 80 };
-    SkDebugf("%s intersection: ", leftRect.intersect(30, 60, 70, 90) ? "" : "no ");
-    SkDebugf("%g, %g, %g, %g\n", leftRect.left(), leftRect.top(),
-                                 leftRect.right(), leftRect.bottom());
-#StdOut
- intersection: 30, 60, 50, 80
-##
-##
-
-#SeeAlso intersects Intersects join SkIRect::intersect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool intersect(const SkRect& a, const SkRect& b)
-
-#In Intersection
-#Populate
-
-#Example
-    SkRect result;
-    bool intersected = result.intersect({ 10, 40, 50, 80 }, { 30, 60, 70, 90 });
-    SkDebugf("%s intersection: %g, %g, %g, %g\n", intersected ? "" : "no ",
-             result.left(), result.top(), result.right(), result.bottom());
-#StdOut
- intersection: 30, 60, 50, 80
-##
-##
-
-#SeeAlso intersects Intersects join SkIRect::intersect
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method    bool intersects(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom) const
-
-#In Intersection
-#Line # returns true if areas overlap ##
-#Populate
-
-#Example
-    SkRect rect = { 10, 40, 50, 80 };
-    SkDebugf("%s intersection", rect.intersects(30, 60, 70, 90) ? "" : "no ");
-#StdOut
- intersection
-##
-##
-
-#SeeAlso intersect Intersects SkIRect::Intersects
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method    bool intersects(const SkRect& r) const
-
-#In Intersection
-#Populate
-
-#Example
-    SkRect rect = { 10, 40, 50, 80 };
-    SkDebugf("%s intersection", rect.intersects({30, 60, 70, 90}) ? "" : "no ");
-#StdOut
- intersection
-##
-##
-
-#SeeAlso intersect Intersects SkIRect::Intersects
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method    static bool Intersects(const SkRect& a, const SkRect& b)
-
-#In Intersection
-#Line # returns true if areas overlap ##
-#Populate
-
-#Example
-    SkDebugf("%s intersection", SkRect::Intersects({10, 40, 50, 80}, {30, 60, 70, 90}) ? "" : "no ");
-#StdOut
- intersection
-##
-##
-
-#SeeAlso intersect intersects SkIRect::Intersects
-
-##
-
-#Subtopic Intersection ##
-
-#Subtopic Join
-#Line # sets to union of bounds ##
-
-# ------------------------------------------------------------------------------
-
-#Method    void join(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom)
-
-#In Join
-#Line # sets to union of bounds ##
-#Populate
-
-#Example
-    SkRect rect = { 10, 20, 15, 25};
-    rect.join(50, 60, 55, 65);
-    SkDebugf("join: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-#StdOut
- join: 10, 20, 55, 65
-##
-##
-
-#SeeAlso joinNonEmptyArg joinPossiblyEmptyRect SkIRect::join
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method    void join(const SkRect& r)
-
-#In Join
-#Populate
-
-#Example
-    SkRect rect = { 10, 20, 15, 25};
-    rect.join({50, 60, 55, 65});
-    SkDebugf("join: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-#StdOut
- join: 10, 20, 55, 65
-##
-##
-
-#SeeAlso joinNonEmptyArg joinPossiblyEmptyRect SkIRect::join
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method    void joinNonEmptyArg(const SkRect& r)
-
-#In Join
-#Line # sets to union of bounds, asserting that argument is not empty ##
-#Populate
-
-#Example
-#Description
-Since Rect is not sorted, first result is copy of toJoin.
-##
-    SkRect rect = { 10, 100, 15, 0};
-    SkRect sorted = rect.makeSorted();
-    SkRect toJoin = { 50, 60, 55, 65 };
-    rect.joinNonEmptyArg(toJoin);
-    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-    sorted.joinNonEmptyArg(toJoin);
-    SkDebugf("sorted: %g, %g, %g, %g\n", sorted.fLeft, sorted.fTop, sorted.fRight, sorted.fBottom);
-#StdOut
-rect: 50, 60, 55, 65
-sorted: 10, 0, 55, 100
-##
-##
-
-#SeeAlso join joinPossiblyEmptyRect SkIRect::join
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method    void joinPossiblyEmptyRect(const SkRect& r)
-
-#In Join
-#Line # sets to union of bounds; skips empty check for both ##
-#Populate
-
-#Example
-#Description
-Since Rect is not sorted, first result is not useful.
-##
-    SkRect rect = { 10, 100, 15, 0};
-    SkRect sorted = rect.makeSorted();
-    SkRect toJoin = { 50, 60, 55, 65 };
-    rect.joinPossiblyEmptyRect(toJoin);
-    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-    sorted.joinPossiblyEmptyRect(toJoin);
-    SkDebugf("sorted: %g, %g, %g, %g\n", sorted.fLeft, sorted.fTop, sorted.fRight, sorted.fBottom);
-#StdOut
-rect: 10, 60, 55, 65
-sorted: 10, 0, 55, 100
-##
-##
-
-#SeeAlso joinNonEmptyArg join SkIRect::join
-
-##
-
-#Subtopic Join ##
-
-#Subtopic Rounding
-#Line # adjust to integer bounds ##
-
-#Method    void round(SkIRect* dst) const
-
-#In Rounding
-#Line # sets members to nearest integer value ##
-Sets IRect by adding 0.5 and discarding the fractional portion of Rect
-members, using #Formula # (SkScalarRoundToInt(fLeft), SkScalarRoundToInt(fTop),
-                           SkScalarRoundToInt(fRight), SkScalarRoundToInt(fBottom)) ##.
-
-#Param dst  storage for IRect ##
-
-#Example
-    SkRect rect = { 30.5f, 50.5f, 40.5f, 60.5f };
-    SkIRect round;
-    rect.round(&round);
-    SkDebugf("round: %d, %d, %d, %d\n", round.fLeft, round.fTop, round.fRight, round.fBottom);
-#StdOut
-round: 31, 51, 41, 61
-##
-##
-
-#SeeAlso roundIn roundOut SkScalarRoundToInt
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method    void roundOut(SkIRect* dst) const
-
-#In Rounding
-#Line # sets members to nearest integer value away from opposite ##
-Sets IRect by discarding the fractional portion of fLeft and fTop; and rounding
-up fRight and fBottom, using 
-#Formula # (SkScalarFloorToInt(fLeft), SkScalarFloorToInt(fTop),
-            SkScalarCeilToInt(fRight), SkScalarCeilToInt(fBottom)) ##.
-
-#Param dst  storage for IRect ##
-
-#Example
-    SkRect rect = { 30.5f, 50.5f, 40.5f, 60.5f };
-    SkIRect round;
-    rect.roundOut(&round);
-    SkDebugf("round: %d, %d, %d, %d\n", round.fLeft, round.fTop, round.fRight, round.fBottom);
-#StdOut
-round: 30, 50, 41, 61
-##
-##
-
-#SeeAlso roundIn round SkScalarRoundToInt
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method    void roundOut(SkRect* dst) const
-
-#In Rounding
-Sets Rect by discarding the fractional portion of fLeft and fTop; and rounding 
-up fRight and fBottom, using 
-#Formula # (SkScalarFloorToInt(fLeft), SkScalarFloorToInt(fTop),
-            SkScalarCeilToInt(fRight), SkScalarCeilToInt(fBottom)) ##.
-
-#Param dst  storage for Rect ##
-
-#Example
-    SkRect rect = { 30.5f, 50.5f, 40.5f, 60.5f };
-    SkRect round;
-    rect.roundOut(&round);
-    SkDebugf("round: %g, %g, %g, %g\n", round.fLeft, round.fTop, round.fRight, round.fBottom);
-#StdOut
-round: 30, 50, 41, 61
-##
-##
-
-#SeeAlso roundIn round SkScalarRoundToInt
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method    void roundIn(SkIRect* dst) const
-
-#In Rounding
-#Line # sets members to nearest integer value towards opposite ##
-Sets Rect by rounding up fLeft and fTop; and discarding the fractional portion
-of fRight and fBottom, using
-#Formula # (SkScalarCeilToInt(fLeft), SkScalarCeilToInt(fTop),
-            SkScalarFloorToInt(fRight), SkScalarFloorToInt(fBottom)) ##.
-
-#Param dst  storage for IRect ##
-
-#Example
-    SkRect rect = { 30.5f, 50.5f, 40.5f, 60.5f };
-    SkIRect round;
-    rect.roundIn(&round);
-    SkDebugf("round: %d, %d, %d, %d\n", round.fLeft, round.fTop, round.fRight, round.fBottom);
-#StdOut
-round: 31, 51, 40, 60
-##
-##
-
-#SeeAlso roundOut round SkScalarRoundToInt
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method    SkIRect round() const
-
-#In Rounding
-Returns IRect by adding 0.5 and discarding the fractional portion of Rect
-members, using #Formula # (SkScalarRoundToInt(fLeft), SkScalarRoundToInt(fTop),
-                           SkScalarRoundToInt(fRight), SkScalarRoundToInt(fBottom)) ##.
-
-#Return  rounded IRect ##
-
-#Example
-    SkRect rect = { 30.5f, 50.5f, 40.5f, 60.5f };
-    SkIRect round = rect.round();
-    SkDebugf("round: %d, %d, %d, %d\n", round.fLeft, round.fTop, round.fRight, round.fBottom);
-#StdOut
-round: 31, 51, 41, 61
-##
-##
-
-#SeeAlso roundOut roundIn SkScalarRoundToInt
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method    SkIRect roundOut() const
-
-#In Rounding
-Sets IRect by discarding the fractional portion of fLeft and fTop; and rounding
-up fRight and fBottom, using
-#Formula # (SkScalarFloorToInt(fLeft), SkScalarFloorToInt(fTop),
-            SkScalarCeilToInt(fRight), SkScalarCeilToInt(fBottom)) ##.
-
-#Return  rounded IRect ##
-
-#Example
-    SkRect rect = { 30.5f, 50.5f, 40.5f, 60.5f };
-    SkIRect round = rect.roundOut();
-    SkDebugf("round: %d, %d, %d, %d\n", round.fLeft, round.fTop, round.fRight, round.fBottom);
-#StdOut
-round: 30, 50, 41, 61
-##
-##
-
-#SeeAlso round roundIn SkScalarRoundToInt
-
-##
-
-#Subtopic Rounding ##
-
-#Subtopic Sorting
-#Line # orders sides ##
-
-# ------------------------------------------------------------------------------
-
-#Method    void sort()
-
-#In Sorting
-#Line # orders sides from smaller to larger ##
-#Populate
-
-#Example
-    SkRect rect = { 30.5f, 50.5f, 20.5f, 10.5f };
-    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-    rect.sort();
-    SkDebugf("sorted: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-#StdOut
-rect: 30.5, 50.5, 20.5, 10.5
-sorted: 20.5, 10.5, 30.5, 50.5
-##
-##
-
-#SeeAlso makeSorted SkIRect::sort isSorted
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method    SkRect makeSorted() const
-
-#In Sorting
-#In Constructors
-#Line # constructs Rect, ordering sides from smaller to larger ##
-#Populate
-
-#Example
-    SkRect rect = { 30.5f, 50.5f, 20.5f, 10.5f };
-    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-    SkRect sort = rect.makeSorted();
-    SkDebugf("sorted: %g, %g, %g, %g\n", sort.fLeft, sort.fTop, sort.fRight, sort.fBottom);
-#StdOut
-rect: 30.5, 50.5, 20.5, 10.5
-sorted: 20.5, 10.5, 30.5, 50.5
-##
-##
-
-#SeeAlso sort SkIRect::makeSorted isSorted
-
-##
-
-#Subtopic Sorting ##
-
-# ------------------------------------------------------------------------------
-
-#Method    const SkScalar* asScalars() const
-#In Property
-#Line # returns pointer to members as array ##
-#Populate
-
-#Example
-   SkRect rect = {7, 11, 13, 17};
-SkDebugf("rect.asScalars() %c= &rect.fLeft\n", rect.asScalars() == &rect.fLeft? '=' : '!');
-#StdOut
-rect.asScalars() == &rect.fLeft
-##
-##
-
-#SeeAlso toQuad
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method    void dump(bool asHex) const
-#In Property
-#Line # sends text representation to standard output using floats ##
-#Populate
-
-#Example
-   SkRect rect = {20, 30, 40, 50};
-    for (bool dumpAsHex : { false, true } ) {
-        rect.dump(dumpAsHex);
-        SkDebugf("\n");
-    }
-#StdOut
-SkRect::MakeLTRB(20, 30, 40, 50);
-
-SkRect::MakeLTRB(SkBits2Float(0x41a00000), /* 20.000000 */
-                 SkBits2Float(0x41f00000), /* 30.000000 */
-                 SkBits2Float(0x42200000), /* 40.000000 */
-                 SkBits2Float(0x42480000)  /* 50.000000 */);
-##
-##
-
-#SeeAlso dumpHex
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method    void dump() const
-#Populate
-
-#Example
-SkRect rect = {6.f / 7, 2.f / 3, 26.f / 10, 42.f / 6};
-rect.dump();
-SkRect copy = SkRect::MakeLTRB(0.857143f, 0.666667f, 2.6f, 7);
-SkDebugf("rect is " "%s" "equal to copy\n", rect == copy ? "" : "not ");
-#StdOut
-SkRect::MakeLTRB(0.857143f, 0.666667f, 2.6f, 7);
-rect is not equal to copy
-##
-##
-
-#SeeAlso dumpHex
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method    void dumpHex() const
-#In Property
-#Line # sends text representation to standard output using hexadecimal ##
-Writes text representation of Rect to standard output. The representation may be
-directly compiled as C++ code. Floating point values are written
-in hexadecimal to preserve their exact bit pattern. The output reconstructs the
-original Rect.
-
-Use instead of dump() when submitting
-#A bug reports against Skia # https://bug.skia.org ##
-.
-
-#Example
-   SkRect rect = {6.f / 7, 2.f / 3, 26.f / 10, 42.f / 6};
-rect.dumpHex();
-SkRect copy = SkRect::MakeLTRB(SkBits2Float(0x3f5b6db7), /* 0.857143 */
-                 SkBits2Float(0x3f2aaaab), /* 0.666667 */
-                 SkBits2Float(0x40266666), /* 2.600000 */
-                 SkBits2Float(0x40e00000)  /* 7.000000 */);
-SkDebugf("rect is " "%s" "equal to copy\n", rect == copy ? "" : "not ");
-#StdOut
-SkRect::MakeLTRB(SkBits2Float(0x3f5b6db7), /* 0.857143 */
-                 SkBits2Float(0x3f2aaaab), /* 0.666667 */
-                 SkBits2Float(0x40266666), /* 2.600000 */
-                 SkBits2Float(0x40e00000)  /* 7.000000 */);
-rect is equal to copy
-##
-##
-
-#SeeAlso dump
-
-##
-
-#Struct SkRect ##
-
-#Topic Rect ##
diff --git a/docs/SkRegion_Reference.bmh b/docs/SkRegion_Reference.bmh
deleted file mode 100644
index 5a35fb7..0000000
--- a/docs/SkRegion_Reference.bmh
+++ /dev/null
@@ -1,1583 +0,0 @@
-#Topic Region
-#Alias Region_Reference ##
-#Alias Regions ##
-
-Region is a compressed one bit mask. Region describes an aliased clipping area
-on integer boundaries. Region can also describe an array of integer rectangles.
-
-Canvas uses Region to reduce the current clip. Region may be drawn to Canvas;
-Paint determines if Region is filled or stroked, its Color, and so on.
-
-Region may be constructed from IRect array or Path. Diagonal lines and curves
-in Path become integer rectangle edges. Regions operators compute union,
-intersection, difference, and so on. Canvas allows only intersection and
-difference; successive clips can only reduce available Canvas area.
-
-#PhraseDef list_of_op_types
-kDifference_Op, kIntersect_Op, kUnion_Op, kXOR_Op, kReverseDifference_Op,
-kReplace_Op
-##
-
-#Class SkRegion
-
-#Code
-#Populate
-##
-
-SkRegion describes the set of pixels used to clip Canvas. SkRegion is compact,
-efficiently storing a single integer rectangle, or a run length encoded array
-of rectangles. SkRegion may reduce the current Canvas_Clip, or may be drawn as
-one or more integer rectangles. SkRegion iterator returns the scan lines or
-rectangles contained by it, optionally intersecting a bounding rectangle.
-
-# ------------------------------------------------------------------------------
-
-#Class Iterator
-#Line # iterator returning IRect  ##
-
-#Code
-    class Iterator {
-    public:
-        Iterator();
-        Iterator(const SkRegion& region);
-        bool rewind();
-        void reset(const SkRegion& region);
-        bool done() const;
-        void next();
-        const SkIRect& rect();
-        const SkRegion* rgn();
-    };
-##
-
-Returns sequence of rectangles, sorted along y-axis, then x-axis, that make
-up Region.
-
-# ------------------------------------------------------------------------------
-
-#Method Iterator()
-#Line # constructs Region iterator ##
-#Populate
-
-#Example
-    SkRegion::Iterator iter;
-    SkRegion region;
-    region.setRect({1, 2, 3, 4});
-    iter.reset(region);
-    auto r = iter.rect();
-    SkDebugf("rect={%d,%d,%d,%d}\n", r.fLeft, r.fTop, r.fRight, r.fBottom);
-#StdOut
-rect={1,2,3,4}
-##
-##
-
-#SeeAlso reset SkRegion
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method Iterator(const SkRegion& region)
-#Line # constructs Region iterator ##
-#Populate
-
-#Example
-    SkRegion region;
-    region.setRect({1, 2, 3, 4});
-    SkRegion::Iterator iter(region);
-    auto r = iter.rect();
-    SkDebugf("rect={%d,%d,%d,%d}\n", r.fLeft, r.fTop, r.fRight, r.fBottom);
-#StdOut
-rect={1,2,3,4}
-##
-##
-
-#SeeAlso reset SkRegion Cliperator Spanerator
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool rewind()
-#Line # points Iterator to start ##
-#Populate
-
-#Example
-#Bug 8186
-    auto debugster = [](const char* label, SkRegion::Iterator& iter, bool addRewind) -> void {
-        if (addRewind) {
-            bool success = iter.rewind();
-            SkDebugf("%14s rewind success=%s\n", label, success ? "true" : "false");
-        }
-        auto r = iter.rect();
-        SkDebugf("%14s rect={%d,%d,%d,%d}\n", label, r.fLeft, r.fTop, r.fRight, r.fBottom);
-    };
-    SkRegion::Iterator iter;
-    debugster("empty iter", iter, true);
-    SkRegion region;
-    iter.reset(region);
-    debugster("empty region", iter, true);
-    region.setRect({1, 2, 3, 4});
-    iter.reset(region);
-    debugster("after set rect", iter, false);
-    debugster("after rewind", iter, true);
-#StdOut
-#Volatile
-    empty iter rewind success=false
-    empty iter rect={0,0,0,0}
-  empty region rewind success=true
-  empty region rect={0,0,0,0}
-after set rect rect={1,2,3,4}
-  after rewind rewind success=true
-  after rewind rect={1,2,3,4}
-##
-##
-
-#SeeAlso reset
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void reset(const SkRegion& region)
-#Line # sets Region to iterate ##
-#Populate
-
-#Example
-    auto debugster = [](const char* label, SkRegion::Iterator& iter) -> void {
-        SkDebugf("%14s: done=%s\n", label, iter.done() ? "true" : "false");
-    };
-    SkRegion region;
-    SkRegion::Iterator iter(region);
-    debugster("empty region", iter);
-    region.setRect({1, 2, 3, 4});
-    debugster("after set rect", iter);
-    iter.reset(region);
-    debugster("after reset", iter);
-#StdOut
-  empty region: done=true
-after set rect: done=true
-   after reset: done=false
-##
-##
-
-#SeeAlso rewind
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool done() const
-#Line # returns if data parsing is complete ##
-#Populate
-
-#Example
-    SkRegion region;
-    SkRegion::Iterator iter(region);
-    SkDebugf("done=%s\n", iter.done() ? "true" : "false"); 
-    region.setRect({1, 2, 3, 4});
-    iter.rewind();
-    SkDebugf("done=%s\n", iter.done() ? "true" : "false"); 
-#StdOut
-done=true
-done=false
-##
-##
-
-#SeeAlso next rect
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void next()
-#Line # advances to next IRect ##
-#Populate
-
-#Example
-    SkRegion region;
-    SkIRect rects[] = {{1, 2, 3, 4}, {5, 6, 7, 8}};
-    region.setRects(rects, SK_ARRAY_COUNT(rects));
-    SkRegion::Iterator iter(region);
-    do {
-        auto r2 = iter.rect();
-        SkDebugf("rect={%d,%d,%d,%d}\n", r2.fLeft, r2.fTop, r2.fRight, r2.fBottom);
-        iter.next();
-    } while (!iter.done());
-#StdOut
-rect={1,2,3,4}
-rect={5,6,7,8}
-##
-##
-
-#SeeAlso done rect
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method const SkIRect& rect() const
-#Line # returns part of Region as IRect ##
-#Populate
-
-#Example
-#Bug 8186
-    SkRegion region;
-    SkRegion::Iterator iter(region);
-    auto r1 = iter.rect();
-    SkDebugf("rect={%d,%d,%d,%d}\n", r1.fLeft, r1.fTop, r1.fRight, r1.fBottom); 
-    region.setRect({1, 2, 3, 4});
-    iter.rewind();
-    auto r2 = iter.rect();
-    SkDebugf("rect={%d,%d,%d,%d}\n", r2.fLeft, r2.fTop, r2.fRight, r2.fBottom); 
-#StdOut
-#Volatile
-rect={0,0,0,0}
-rect={1,2,3,4}
-##
-##
-
-#SeeAlso next done
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method const SkRegion* rgn() const
-#Line # returns original Region ##
-#Populate
-
-#Example
-    SkRegion region;
-    SkIRect rects[] = {{1, 2, 3, 4}, {3, 4, 5, 6}};
-    region.setRects(rects, SK_ARRAY_COUNT(rects));
-    SkRegion::Iterator iter(region);
-    auto r = iter.rect();
-    SkDebugf("rect={%d,%d,%d,%d}\n", r.fLeft, r.fTop, r.fRight, r.fBottom);
-    auto b = iter.rgn()->getBounds();
-    SkDebugf("bounds={%d,%d,%d,%d}\n", b.fLeft, b.fTop, b.fRight, b.fBottom);
-##
-
-#SeeAlso Iterator reset
-
-#Method ##
-
-#Class Iterator ##
-
-# ------------------------------------------------------------------------------
-
-#Class Cliperator
-#Line # iterator returning IRect within clip ##
-
-#Code
-    class Cliperator {
-    public:
-        Cliperator(const SkRegion& region, const SkIRect& clip);
-        bool done();
-        void next();
-        const SkIRect& rect() const;
-    };
-##
-
-Returns the sequence of rectangles, sorted along y-axis, then x-axis, that make
-up Region intersected with the specified clip rectangle.
-
-# ------------------------------------------------------------------------------
-
-#Method Cliperator(const SkRegion& region, const SkIRect& clip)
-#Line # constructs Region iterator with clip ##
-#Populate
-
-#Example
-    SkRegion region;
-    region.setRect({1, 2, 3, 4});
-    SkRegion::Cliperator clipper(region, {0, 0, 2, 3});
-    auto r = clipper.rect();
-    SkDebugf("rect={%d,%d,%d,%d}\n", r.fLeft, r.fTop, r.fRight, r.fBottom);
-#StdOut
-rect={1,2,2,3}
-##
-##
-
-#SeeAlso SkRegion Iterator Spanerator
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool done()
-#Line # returns if data parsing is complete ##
-#Populate
-
-#Example
-    auto debugster = [](const char* label, SkRegion& region) -> void {
-        SkRegion::Cliperator clipper(region, {0, 0, 5, 5});
-        SkDebugf("%14s done=%s\n", label, clipper.done() ? "true" : "false"); 
-    };
-    SkRegion region;
-    debugster("empty region", region);
-    region.setRect({1, 2, 3, 4});
-    debugster("after add rect", region);
-#StdOut
-  empty region done=true
-after add rect done=false
-##
-##
-
-#SeeAlso next rect
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void  next()
-#Line # advances to next IRect within clip ##
-#Populate
-
-#Example
-    SkRegion region;
-    SkIRect rects[] = {{1, 2, 3, 4}, {5, 6, 7, 8}};
-    region.setRects(rects, SK_ARRAY_COUNT(rects));
-    SkRegion::Cliperator clipper(region, {0, 3, 8, 7});
-    do {
-        auto r2 = clipper.rect();
-        SkDebugf("rect={%d,%d,%d,%d}\n", r2.fLeft, r2.fTop, r2.fRight, r2.fBottom);
-        clipper.next();
-    } while (!clipper.done());
-#StdOut
-rect={1,3,3,4}
-rect={5,6,7,7}
-##
-##
-
-#SeeAlso done
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method const SkIRect& rect() const
-#Line # returns part of Region as IRect intersected with clip ##
-#Populate
-
-#Example
-#Bug 8186
-    auto debugster = [](const char* label, SkRegion& region) -> void {
-        SkRegion::Cliperator clipper(region, {0, 0, 5, 3});
-        auto r = clipper.rect();
-        SkDebugf("%14s rect={%d,%d,%d,%d}\n", label, r.fLeft, r.fTop, r.fRight, r.fBottom); 
-    };
-    SkRegion region;
-    debugster("empty region", region);
-    region.setRect({1, 2, 3, 4});
-    debugster("after set rect", region);
-#StdOut
-#Volatile
-  empty region rect={1094713344,1065353216,0,-1}
-after set rect rect={1,2,3,3}
-##
-##
-
-#SeeAlso next done
-
-#Method ##
-
-#Class Cliperator ##
-
-# ------------------------------------------------------------------------------
-
-#Class Spanerator
-#Line # horizontal line segment iterator ##
-
-#Code
-    class Spanerator {
-    public:
-        Spanerator(const SkRegion& region, int y, int left, int right);
-        bool next(int* left, int* right);
-    };
-##
-
-Returns the line segment ends within Region that intersect a horizontal line.
-
-# ------------------------------------------------------------------------------
-
-#Method Spanerator(const SkRegion& region, int y, int left, int right)
-#Line # constructs Region iterator on scan line ##
-#Populate
-
-#Example
-    SkRegion region;
-    region.setRect({1, 2, 3, 4});
-    SkRegion::Spanerator spanner(region, 3, 2, 4);
-    int left, right;
-    bool result = spanner.next(&left, &right);
-    SkDebugf("result=%s left=%d right=%d\n", result ? "true" : "false", left, right);
-##
-
-#SeeAlso SkRegion Iterator Cliperator
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool next(int* left, int* right)
-#Line # advances to next span on horizontal line ##
-#Populate
-
-#Example
-    auto debugster = [](const char* label, SkRegion& region) -> void {
-        SkRegion::Spanerator spanner(region, 3, 2, 4);
-        int left, right;
-        bool result = spanner.next(&left, &right);
-        SkDebugf("%14s: result=%s", label, result ? "true" : "false");
-        if (result) SkDebugf(" left=%d right=%d", left, right);
-        SkDebugf("\n");
-    };
-    SkRegion region;
-    debugster("empty region", region);
-    region.setRect({1, 2, 3, 4});
-    debugster("after set rect", region);
-#StdOut
-  empty region: result=false
-after set rect: result=true left=2 right=3
-##
-##
-
-#SeeAlso done
-
-#Method ##
-
-#Class Spanerator ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkRegion()
-#In Constructors
-#Line # constructs with default values ##
-#Populate
-
-#Example
-SkRegion region;
-SkIRect r = region.getBounds();
-SkDebugf("region bounds: {%d, %d, %d, %d}\n", r.fLeft, r.fTop, r.fRight, r.fBottom);
-#StdOut
-region bounds: {0, 0, 0, 0}
-##
-##
-
-#SeeAlso setEmpty
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkRegion(const SkRegion& region)
-#In Constructors
-#Line # makes a shallow copy ##
-#Populate
-
-#Example
-    auto debugster = [](const char* label, SkRegion& region) -> void {
-        auto r = region.getBounds();
-        SkDebugf("%14s: {%d,%d,%d,%d}\n", label, r.fLeft, r.fTop, r.fRight, r.fBottom);
-    };
-    SkRegion region({1, 2, 3, 4});
-    SkRegion region2(region);
-    debugster("region bounds", region);
-    debugster("region2 bounds", region2);
-    region.setEmpty();
-    SkDebugf("    after region set empty:\n");
-    debugster("region bounds", region);
-    debugster("region2 bounds", region2);
-#StdOut
- region bounds: {1,2,3,4}
-region2 bounds: {1,2,3,4}
-    after region set empty:
- region bounds: {0,0,0,0}
-region2 bounds: {1,2,3,4}
-##
-##
-
-#SeeAlso setRegion operator=(const SkRegion& region)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method explicit SkRegion(const SkIRect& rect)
-#In Constructors
-#Line # constructs Region matching IRect ##
-#Populate
-
-#Example
-    SkRegion region({1, 2, 3, 4});
-    SkRegion region2;
-    region2.setRect({1, 2, 3, 4});
-    SkDebugf("region %c= region2\n", region == region2 ? '=' : '!');
-##
-
-#SeeAlso setRect setRegion
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method ~SkRegion()
-#In Constructors
-#Line # decreases Reference_Count of owned objects ##
-#Populate
-
-#Example
-#Description
-delete calls Region destructor, but copy of original in region2 is unaffected.
-##
-    SkRegion* region = new SkRegion({1, 2, 3, 4});
-    SkRegion region2(*region);
-    delete region;
-    auto r = region2.getBounds();
-    SkDebugf("region2 bounds: {%d,%d,%d,%d}\n", r.fLeft, r.fTop, r.fRight, r.fBottom);
-#StdOut
-region2 bounds: {1,2,3,4}
-##
-##
-
-#SeeAlso SkRegion() SkRegion(const SkRegion& region) SkRegion(const SkIRect& rect) operator=(const SkRegion& region)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkRegion& operator=(const SkRegion& region)
-#In Operators
-#Line # makes a shallow copy ##
-#Populate
-
-#Example
-    auto debugster = [](const char* label, SkRegion& region) -> void {
-        auto r = region.getBounds();
-        SkDebugf("%14s: {%d,%d,%d,%d}\n", label, r.fLeft, r.fTop, r.fRight, r.fBottom);
-    };
-    SkRegion region1({1, 2, 3, 4});
-    SkRegion region2 = region1;
-    debugster("region1 bounds", region1);
-    debugster("region2 bounds", region2);
-#StdOut
-region1 bounds: {1,2,3,4}
-region2 bounds: {1,2,3,4}
-##
-##
-
-#SeeAlso set swap SkRegion(const SkRegion& region)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator==(const SkRegion& other) const
-#In Operators
-#Line # compares Regions for equality ##
-#Populate
-
-#Example
-    auto debugster = [](const char* prefix, const SkRegion& a, const SkRegion& b) -> void {
-                SkDebugf("%s one %c= two\n", prefix, a == b ? '=' : '!');
-    };
-    SkRegion one;
-    SkRegion two;
-    debugster("empty", one, two);
-    one.setRect({1, 2, 3, 4});
-    debugster("set rect", one, two);
-    one.setEmpty();
-    debugster("set empty", one, two);
-#StdOut
-empty one == two
-set rect one != two
-set empty one == two
-##
-##
-
-#SeeAlso operator!=(const SkRegion& other) const operator=(const SkRegion& region)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool operator!=(const SkRegion& other) const
-#In Operators
-#Line # compares Regions for inequality ##
-#Populate
-
-#Example
-    auto debugster = [](const char* prefix, const SkRegion& a, const SkRegion& b) -> void {
-                SkDebugf("%s one %c= two\n", prefix, a != b ? '!' : '=');
-    };
-    SkRegion one;
-    SkRegion two;
-    debugster("empty", one, two);
-    one.setRect({1, 2, 3, 4});
-    two.setRect({1, 2, 3, 3});
-    debugster("set rect", one, two);
-    two.op({1, 3, 3, 4}, SkRegion::kUnion_Op);
-    debugster("union rect", one, two);
-#StdOut
-empty one == two
-set rect one != two
-union rect one == two
-##
-##
-
-#SeeAlso operator==(const SkRegion& other) const operator=(const SkRegion& region)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool set(const SkRegion& src)
-#In Constructors
-#Line # makes a shallow copy ##
-#Populate
-
-#Example
-    auto debugster = [](const char* label, SkRegion& region) -> void {
-        auto r = region.getBounds();
-        SkDebugf("%14s: {%d,%d,%d,%d}\n", label, r.fLeft, r.fTop, r.fRight, r.fBottom);
-    };
-    SkRegion region1({1, 2, 3, 4});
-    SkRegion region2;
-    region2.set(region1);
-    debugster("region1 bounds", region1);
-    debugster("region2 bounds", region2);
-#StdOut
-region1 bounds: {1,2,3,4}
-region2 bounds: {1,2,3,4}
-##
-##
-
-#SeeAlso operator=(const SkRegion& region) swap SkRegion(const SkRegion& region)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void swap(SkRegion& other)
-#In Operators
-#Line # exchanges Region pair ##
-#Populate
-
-#Example
-    auto debugster = [](const char* label, SkRegion& region) -> void {
-        auto r = region.getBounds();
-        SkDebugf("%14s: {%d,%d,%d,%d}\n", label, r.fLeft, r.fTop, r.fRight, r.fBottom);
-    };
-    SkRegion region1({1, 2, 3, 4});
-    SkRegion region2;
-    region1.swap(region2);
-    debugster("region1 bounds", region1);
-    debugster("region2 bounds", region2);
-#StdOut
-region1 bounds: {0,0,0,0}
-region2 bounds: {1,2,3,4}
-##
-##
-
-#SeeAlso operator=(const SkRegion& region) set SkRegion(const SkRegion& region)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isEmpty() const
-#In Property
-#Line # returns if bounds has no width or height ##
-#Populate
-
-#Example
-    auto debugster = [](const char* label, SkRegion& region) -> void {
-        SkDebugf("%14s: region is %s" "empty\n", label, region.isEmpty() ? "" : "not ");
-    };
-    SkRegion region;
-    debugster("initial", region);
-    region.setRect({1, 2, 3, 4});
-    debugster("set rect", region);
-    region.setEmpty();
-    debugster("set empty", region);
-#StdOut
-       initial: region is empty
-      set rect: region is not empty
-     set empty: region is empty
-##
-##
-
-#SeeAlso isRect isComplex operator==(const SkRegion& other) const
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isRect() const
-#In Property
-#Line # returns if Region contains one IRect ##
-#Populate
-
-#Example
-    auto debugster = [](const char* label, const SkRegion& region) -> void {
-                SkDebugf("%s: region is %s" "rect\n", label, region.isRect() ? "" : "not ");
-    };
-    SkRegion region;
-    debugster("initial", region);
-    region.setRect({1, 2, 3, 4});
-    debugster("set rect", region);
-    region.setEmpty();
-    debugster("set empty", region);
-#StdOut
-initial: region is not rect
-set rect: region is rect
-set empty: region is not rect
-##
-##
-
-#SeeAlso isEmpty isComplex
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool isComplex() const
-#In Property
-#Line # returns true if Region contains more than one IRect ##
-#Populate
-
-#Example
-    auto debugster = [](const char* label, const SkRegion& region) -> void {
-                SkDebugf("%s: region is %s" "complex\n", label, region.isComplex() ? "" : "not ");
-    };
-    SkRegion region;
-    debugster("initial", region);
-    region.setRect({1, 2, 3, 4});
-    debugster("set rect", region);
-    region.op({2, 3, 4, 5}, SkRegion::kUnion_Op);
-    debugster("op rect", region);
-#StdOut
-initial: region is not complex
-set rect: region is not complex
-op rect: region is complex
-##
-##
-
-#SeeAlso isEmpty isRect
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method const SkIRect& getBounds() const
-#In Property
-#Line # returns maximum and minimum of IRect array ##
-#Populate
-
-#Example
-    SkRegion region({1, 2, 3, 4});
-    region.op({2, 3, 4, 5}, SkRegion::kUnion_Op);
-    auto r = region.getBounds();
-    SkDebugf("bounds: {%d,%d,%d,%d}\n", r.fLeft, r.fTop, r.fRight, r.fBottom);
-#StdOut
-bounds: {1,2,4,5}
-##
-##
-
-#SeeAlso isEmpty isRect
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method int computeRegionComplexity() const
-#In Property
-#Line # returns relative complexity ##
-#Populate
-
-#Example
-    auto debugster = [](const char* label, const SkRegion& region) -> void {
-                SkDebugf("%s: region complexity %d\n", label, region.computeRegionComplexity());
-    };
-    SkRegion region;
-    debugster("initial", region);
-    region.setRect({1, 2, 3, 4});
-    debugster("set rect", region);
-    region.op({2, 3, 4, 5}, SkRegion::kUnion_Op);
-    debugster("op rect", region);
-#StdOut
-initial: region complexity 0
-set rect: region complexity 1
-op rect: region complexity 3
-##
-##
-
-#SeeAlso isRect isComplex
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool getBoundaryPath(SkPath* path) const
-#In Property
-#Line # appends Region outline to Path ##
-#Populate
-
-#Example
-#Height 100
-    SkRegion region;
-    region.setRect({10, 20, 90, 60});
-    region.op({30, 40, 60, 80}, SkRegion::kXOR_Op);
-    canvas->drawRegion(region, SkPaint());
-    SkPath path;
-    region.getBoundaryPath(&path);
-    path.offset(100, 0);
-    canvas->drawPath(path, SkPaint());
-##
-
-#SeeAlso isEmpty isComplex
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool setEmpty()
-#In Constructors
-#Line # constructs with default values ##
-#Populate
-
-#Example
-    auto debugster = [](const char* label, SkRegion& region) -> void {
-        auto r = region.getBounds();
-        SkDebugf("%14s: {%d,%d,%d,%d}\n", label, r.fLeft, r.fTop, r.fRight, r.fBottom);
-    };
-    SkRegion region({1, 2, 3, 4});
-    debugster("region bounds", region);
-    region.setEmpty();
-    SkDebugf("    after region set empty:\n");
-    debugster("region bounds", region);
-#StdOut
- region bounds: {1,2,3,4}
-    after region set empty:
- region bounds: {0,0,0,0}
-##
-##
-
-#SeeAlso SkRegion()
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool setRect(const SkIRect& rect)
-#In Constructors
-#Line # constructs Region matching IRect ##
-#Populate
-
-#Example
-    SkRegion region({1, 2, 3, 4});
-    SkDebugf("region is %s" "empty\n", region.isEmpty() ? "" : "not ");
-    bool setEmpty = region.setRect({1, 2, 1, 4});
-    SkDebugf("region is %s" "empty\n", region.isEmpty() ? "" : "not ");
-    SkDebugf("setEmpty: %s\n", setEmpty ? "true" : "false");
-#StdOut
-region is not empty
-region is empty
-setEmpty: false
-##
-##
-
-#SeeAlso SkRegion(const SkIRect& rect)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool setRect(int32_t left, int32_t top, int32_t right, int32_t bottom)
-#In Constructors
-#Line # constructs Region matching bounds ##
-#Populate
-
-#Example
-    auto debugster = [](const char* label, bool success, SkRegion& region) -> void {
-        auto r = region.getBounds();
-        SkDebugf("%14s: success:%s {%d,%d,%d,%d}\n", label, success ? "true" : "false",
-                 r.fLeft, r.fTop, r.fRight, r.fBottom);
-    };
-    SkRegion region;
-    bool success = region.setRect(1, 2, 3, 4);
-    debugster("set to: 1,2,3,4", success, region);
-    success = region.setRect(3, 2, 1, 4);
-    debugster("set to: 3,2,1,4", success, region);
-#StdOut
-set to: 1,2,3,4: success:true {1,2,3,4}
-set to: 3,2,1,4: success:false {0,0,0,0}
-##
-##
-
-#SeeAlso SkRegion(const SkIRect& rect)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool setRects(const SkIRect rects[], int count)
-#In Constructors
-#Line # sets IRect array ##
-#Populate
-
-#Example
-#Height 70
-    SkIRect rects[] = { {10, 10, 40, 40}, {20, 20, 50, 50}, {30, 30, 60, 60} };
-    SkRegion region;
-    region.setRects(rects, SK_ARRAY_COUNT(rects));
-    canvas->drawRegion(region, SkPaint());
-    region.setEmpty();
-    for (auto add : rects) {
-        region.op(add, SkRegion::kUnion_Op);
-    }
-    region.translate(100, 0);
-    canvas->drawRegion(region, SkPaint());
-##
-
-#SeeAlso setRect op
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool setRegion(const SkRegion& region)
-#In Constructors
-#Line # copies Region ##
-#Populate
-
-#Example
-    auto debugster = [](const char* label, SkRegion& region) -> void {
-        auto r = region.getBounds();
-        SkDebugf("%14s: {%d,%d,%d,%d}\n", label, r.fLeft, r.fTop, r.fRight, r.fBottom);
-    };
-    SkRegion region({1, 2, 3, 4});
-    SkRegion region2;
-    region2.setRegion(region);
-    debugster("region bounds", region);
-    debugster("region2 bounds", region2);
-    region2.setEmpty();
-    SkDebugf("    after region set empty:\n");
-    debugster("region bounds", region);
-    debugster("region2 bounds", region2);
-#StdOut
- region bounds: {1,2,3,4}
-region2 bounds: {1,2,3,4}
-    after region set empty:
- region bounds: {1,2,3,4}
-region2 bounds: {0,0,0,0}
-##
-##
-
-#SeeAlso SkRegion(const SkRegion& region)
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool setPath(const SkPath& path, const SkRegion& clip)
-#In Constructors
-#Line # constructs Region from clipped Path ##
-#Populate
-
-#Example
-#Height 120
-    SkPaint paint;
-    paint.setTextSize(128);
-    SkPath textPath;
-    paint.getTextPath("Q", 1, 0, 110, &textPath);
-    SkIRect clipRect = {20, 20, 100, 120};
-    SkRegion clipRegion(clipRect);
-    SkRegion region;
-    region.setPath(textPath, clipRegion);
-    canvas->drawRegion(region, SkPaint());
-    clipRect.offset(100, 0);
-    textPath.offset(100, 0);
-    canvas->clipRect(SkRect::Make(clipRect), false);
-    canvas->drawPath(textPath, SkPaint());
-##
-
-#SeeAlso setRects op
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool intersects(const SkIRect& rect) const
-#In Intersection
-#Line # returns true if areas overlap ##
-#Populate
-
-#Example
-#Duration 4
-#Height 128
-    SkPaint paint;
-    paint.setTextSize(128);
-    SkPath textPath;
-    paint.getTextPath("W", 1, 20, 110, &textPath);
-    SkRegion region;
-    region.setPath(textPath, SkRegion({0, 0, 256, 256}));
-    canvas->drawRegion(region, SkPaint());
-    SkIRect iRect = SkIRect::MakeXYWH(frame * 160, 55, 10, 10);
-    paint.setColor(region.intersects(iRect) ? SK_ColorBLUE : SK_ColorRED);
-    canvas->drawRect(SkRect::Make(iRect), paint);
-##
-
-#SeeAlso contains SkRect::intersects
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool intersects(const SkRegion& other) const
-#Populate
-
-#Example
-#Duration 4
-#Height 128
-    SkPaint paint;
-    paint.setTextSize(128);
-    SkPath hPath, dotPath;
-    paint.getTextPath("H", 1, 40, 110, &hPath);
-    paint.getTextPath(",", 1, frame * 180, 95, &dotPath);
-    SkRegion hRegion, dotRegion;
-    hRegion.setPath(hPath, SkRegion({0, 0, 256, 256}));
-    dotRegion.setPath(dotPath, SkRegion({0, 0, 256, 256}));
-    canvas->drawRegion(hRegion, paint);
-    paint.setColor(hRegion.intersects(dotRegion) ? SK_ColorBLUE : SK_ColorRED);
-    canvas->drawRegion(dotRegion, paint);
-##
-
-#SeeAlso contains SkRect::intersects
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool contains(int32_t x, int32_t y) const
-#In Intersection
-#Line # returns true if points are equal or inside ##
-#Populate
-
-#Example
-#Height 128
-    SkPaint paint;
-    paint.setTextSize(128);
-    SkPath xPath;
-    paint.getTextPath("X", 1, 20, 110, &xPath);
-    SkRegion xRegion;
-    xRegion.setPath(xPath, SkRegion({0, 0, 256, 256}));
-    canvas->drawRegion(xRegion, paint);
-    for (int y = 0; y < 128; y += 8) {
-        for (int x = 0; x < 128; x += 8) {
-           paint.setColor(xRegion.contains(x, y) ? SK_ColorWHITE : SK_ColorRED);
-           canvas->drawPoint(x, y, paint);
-        }
-    }
-##
-
-#SeeAlso intersects SkRect::contains
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool contains(const SkIRect& other) const
-#Populate
-
-#Example
-#Height 128
-#Duration 4
-    SkPaint paint;
-    paint.setTextSize(128);
-    SkPath xPath;
-    paint.getTextPath("X", 1, 20, 110, &xPath);
-    SkRegion xRegion;
-    SkIRect drawBounds = {0, 0, 128, 128};
-    xRegion.setPath(xPath, SkRegion(drawBounds));
-    xRegion.op(drawBounds, SkRegion::kReverseDifference_Op);
-    canvas->drawRegion(xRegion, paint);
-    SkIRect test = SkIRect::MakeXYWH(frame* 128, 64, 5, 5);
-    if (xRegion.contains(test)) {
-        paint.setColor(SK_ColorYELLOW);
-        canvas->drawRect(SkRect::Make(test), paint);
-    }
-##
-
-#SeeAlso intersects SkRect::contains
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool contains(const SkRegion& other) const
-#Populate
-
-#Example
-#Height 128
-#Duration 4
-    SkPaint paint;
-    paint.setTextSize(128);
-    SkPath xPath, testPath;
-    paint.getTextPath("X", 1, 20, 110, &xPath);
-    paint.getTextPath("`", 1, frame * 150 - 40, 150, &testPath);
-    SkRegion xRegion, testRegion;
-    SkIRect drawBounds = {0, 0, 128, 128};
-    xRegion.setPath(xPath, SkRegion(drawBounds));
-    testRegion.setPath(testPath, SkRegion(drawBounds));
-    xRegion.op(drawBounds, SkRegion::kReverseDifference_Op);
-    canvas->drawRegion(xRegion, paint);
-    if (xRegion.contains(testRegion)) {
-        paint.setColor(SK_ColorYELLOW);
-        canvas->drawRegion(testRegion, paint);
-    }
-##
-
-#SeeAlso intersects SkRect::contains
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool quickContains(const SkIRect& r) const
-#In Intersection
-#Line # returns true quickly if points are equal or inside ##
-#Populate
-
-#Example
-    SkRegion region({1, 2, 3, 4});
-    SkIRect test = {2, 2, 3, 3};
-    SkDebugf("quickContains 1: %s\n", region.quickContains(test) ? "true" : "false");
-    region.op({1, 4, 3, 6}, SkRegion::kUnion_Op);
-    SkDebugf("quickContains 2: %s\n", region.quickContains(test) ? "true" : "false");
-    region.op({1, 7, 3, 8}, SkRegion::kUnion_Op);
-    SkDebugf("quickContains 3: %s\n", region.quickContains(test) ? "true" : "false");
-#StdOut
-quickContains 1: true
-quickContains 2: true
-quickContains 3: false
-##
-##
-
-#SeeAlso contains quickReject intersects
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool quickContains(int32_t left, int32_t top, int32_t right,
-                       int32_t bottom) const
-#Populate
-
-#Example
-    auto debugster = [](const char* label, SkRegion& region) -> void {
-        SkDebugf("%s: %s\n", label, region.quickContains(2, 2, 3, 3) ? "true" : "false");
-    };
-    SkRegion region({1, 2, 3, 4});
-    debugster("quickContains 1", region);
-    region.op({1, 4, 3, 6}, SkRegion::kUnion_Op);
-    debugster("quickContains 2", region);
-    region.op({1, 7, 3, 8}, SkRegion::kUnion_Op);
-    debugster("quickContains 3", region);
-#StdOut
-quickContains 1: true
-quickContains 2: true
-quickContains 3: false
-##
-##
-
-#SeeAlso contains quickReject intersects
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool quickReject(const SkIRect& rect) const
-#In Intersection
-#Line # returns true quickly if points are outside ##
-#Populate
-
-#Example
-    SkRegion region({1, 2, 3, 4});
-    SkIRect test = {4, 2, 5, 3};
-    SkDebugf("quickReject 1: %s\n", region.quickReject(test) ? "true" : "false");
-    region.op({1, 4, 3, 6}, SkRegion::kUnion_Op);
-    SkDebugf("quickReject 2: %s\n", region.quickReject(test) ? "true" : "false");
-    region.op({4, 7, 5, 8}, SkRegion::kUnion_Op);
-    SkDebugf("quickReject 3: %s\n", region.quickReject(test) ? "true" : "false");
-#StdOut
-quickReject 1: true
-quickReject 2: true
-quickReject 3: false
-##
-##
-
-#SeeAlso quickContains contains intersects
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool quickReject(const SkRegion& rgn) const
-#Populate
-
-#Example
-    SkRegion region({1, 2, 3, 4});
-    SkRegion test;
-    SkIRect rects[] = {{4, 2, 5, 3}, {7, 2, 8, 3}};
-    test.setRects(rects, SK_ARRAY_COUNT(rects));
-    SkDebugf("quickReject 1: %s\n", region.quickReject(test) ? "true" : "false");
-    region.op({1, 4, 3, 6}, SkRegion::kUnion_Op);
-    SkDebugf("quickReject 2: %s\n", region.quickReject(test) ? "true" : "false");
-    region.op({4, 7, 5, 8}, SkRegion::kUnion_Op);
-    SkDebugf("quickReject 3: %s\n", region.quickReject(test) ? "true" : "false");
-#StdOut
-quickReject 1: true
-quickReject 2: true
-quickReject 3: false
-##
-##
-
-#SeeAlso quickContains contains intersects
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void translate(int dx, int dy)
-#In Transform
-#Line # translates IPoints in Region ##
-#Populate
-
-#Example
-#Height 90
-    SkRegion test;
-    SkIRect rects[] = {{40, 20, 50, 30}, {70, 40, 80, 50}, { 60, 10, 70, 20}};
-    test.setRects(rects, SK_ARRAY_COUNT(rects));
-    SkPaint paint;
-    for (auto color :  { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN, SK_ColorMAGENTA } ) {
-        paint.setColor(color);
-        canvas->drawRegion(test, paint);
-        test.translate(10, 10);
-    }
-##
-
-#SeeAlso SkCanvas::translate SkIRect::offset SkPath::offset
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void translate(int dx, int dy, SkRegion* dst) const
-#Populate
-
-#Example
-    SkRegion test;
-    SkIRect rects[] = {{40, 20, 50, 30}, {70, 40, 80, 50}, { 60, 10, 70, 20}};
-    test.setRects(rects, SK_ARRAY_COUNT(rects));
-    SkPaint paint;
-    for (auto color :  { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN, SK_ColorMAGENTA } ) {
-        paint.setColor(color);
-        canvas->drawRegion(test, paint);
-        SkRegion second;
-        test.translate(10, test.getBounds().fBottom, &second);
-        test.op(second, SkRegion::kXOR_Op);
-        test.translate(30, 0);
-    }
-##
-
-#SeeAlso SkCanvas::translate SkIRect::offset SkPath::offset
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Enum Op
-#Line # binary operator combining Regions ##
-
-#Code
-    enum Op {
-        kDifference_Op,
-        kIntersect_Op,
-        kUnion_Op,
-        kXOR_Op,
-        kReverseDifference_Op,
-        kReplace_Op,
-        kLastOp = kReplace_Op,
-    };
-##
-
-The logical operations that can be performed when combining two Regions.
-
-#Const kDifference_Op 0
-#Line # target minus operand ##
-Subtracts operand Region from target Region.
-##
-#Const kIntersect_Op 1
-#Line # target intersected with operand ##
-Intersects operand Region and target Region.
-##
-#Const kUnion_Op 2
-#Line # target unioned with operand ##
-Unions operand Region and target Region.
-##
-#Const kXOR_Op 3
-#Line # target exclusive or with operand ##
-Replaces target Region with area exclusive to both Regions.
-##
-#Const kReverseDifference_Op 4
-#Line # operand minus target ##
-Subtracts target Region from operand Region.
-##
-#Const kReplace_Op 5
-#Line # replace target with operand ##
-Replaces target Region with operand Region.
-##
-#Const kLastOp 5
-#Line # last operator ##
-##
-
-#Example
-    SkRegion operand({35, 35, 85, 85});
-    const char* labels[] = {"difference", "intersect", "union", "xor", "reverse diff", "replace"};
-    int index = 0;
-    SkPaint paint;
-    for (auto op : { SkRegion::kDifference_Op, SkRegion::kIntersect_Op, SkRegion::kUnion_Op,
-                     SkRegion::kXOR_Op, SkRegion::kReverseDifference_Op, SkRegion::kReplace_Op } ) {
-        SkRegion target({10, 10, 60, 60});
-        target.op(operand, op);
-        canvas->drawRegion(target, paint);
-        canvas->drawString(labels[index++], 40, 100, paint);
-        canvas->translate(80, 0);
-        if (SkRegion::kUnion_Op == op) {
-            canvas->translate(-240, 120);
-        }
-    }
-##
-
-#SeeAlso SkPathOp
-
-#Enum ##
-
-# ------------------------------------------------------------------------------
-
-#Const kOpCnt 6
-#Line # number of operators defined ##
-May be used to verify that Op is a legal value.
-#Const ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool op(const SkIRect& rect, Op op)
-#In Transform
-#Line # applies binary operator ##
-#Populate
-
-#Example
-#Height 128
-    SkPaint paint;
-    paint.setTextSize(128);
-    SkPath xPath;
-    paint.getTextPath("X", 1, 20, 110, &xPath);
-    SkRegion xRegion;
-    SkIRect drawBounds = {0, 0, 128, 128};
-    xRegion.setPath(xPath, SkRegion(drawBounds));
-    xRegion.op(drawBounds, SkRegion::kReverseDifference_Op);
-    canvas->drawRegion(xRegion, paint);
-##
-
-#SeeAlso setRects Op
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool op(int left, int top, int right, int bottom, Op op)
-#Populate
-
-#Example
-#Duration 4
-#Height 128
-    SkPaint paint;
-    paint.setTextSize(128);
-    SkPath xPath;
-    paint.getTextPath("X", 1, 20, 110, &xPath);
-    SkRegion xRegion;
-    SkIRect drawBounds = {0, 0, 128, 128};
-    xRegion.setPath(xPath, SkRegion(drawBounds));
-    xRegion.op(drawBounds.fLeft + frame * drawBounds.width(), drawBounds.fTop,
-               drawBounds.fRight, drawBounds.fBottom, SkRegion::kReverseDifference_Op);
-    canvas->drawRegion(xRegion, paint);
-##
-
-#SeeAlso setRects Op
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool op(const SkRegion& rgn, Op op)
-#Populate
-
-#Example
-#Duration 4
-#Height 128
-    SkPaint paint;
-    paint.setTextSize(128);
-    SkPath xPath, opPath;
-    paint.getTextPath("X", 1, 20, 110, &xPath);
-    opPath.addCircle(64, 64, frame * 64);
-    SkRegion xRegion, opRegion;
-    SkIRect drawBounds = {0, 0, 128, 128};
-    opRegion.setPath(opPath, SkRegion(drawBounds));
-    xRegion.setPath(xPath, SkRegion(drawBounds));
-    xRegion.op(opRegion, SkRegion::kReverseDifference_Op);
-    canvas->drawRegion(xRegion, paint);
-##
-
-#SeeAlso setRects Op
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool op(const SkIRect& rect, const SkRegion& rgn, Op op)
-#Populate
-
-#Example
-#Duration 4
-#Height 128
-    SkPaint paint;
-    paint.setTextSize(128);
-    SkPath xPath, opPath;
-    paint.getTextPath("X", 1, 20, 110, &xPath);
-    opPath.addCircle(64, 64, frame * 64);
-    SkRegion xRegion, opRegion, rectRegion;
-    SkIRect drawBounds = {0, 0, 128, 128};
-    opRegion.setPath(opPath, SkRegion(drawBounds));
-    xRegion.setPath(xPath, SkRegion(drawBounds));
-    drawBounds.inset(frame * drawBounds.width() / 2, 0);    
-    rectRegion.op(drawBounds, opRegion, SkRegion::kIntersect_Op);
-    xRegion.op(rectRegion, SkRegion::kReverseDifference_Op);
-    canvas->drawRegion(xRegion, paint);
-##
-
-#SeeAlso setRects Op
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool op(const SkRegion& rgn, const SkIRect& rect, Op op)
-#Populate
-
-#Example
-#Duration 4
-#Height 128
-    SkPaint paint;
-    paint.setTextSize(128);
-    SkPath xPath, opPath;
-    paint.getTextPath("X", 1, 20, 110, &xPath);
-    opPath.addCircle(64, 64, frame * 64);
-    SkRegion xRegion, opRegion, rectRegion;
-    SkIRect drawBounds = {0, 0, 128, 128};
-    opRegion.setPath(opPath, SkRegion(drawBounds));
-    xRegion.setPath(xPath, SkRegion(drawBounds));
-    drawBounds.inset(frame * drawBounds.width() / 2, 0);    
-    rectRegion.op(opRegion, drawBounds, SkRegion::kUnion_Op);
-    xRegion.op(rectRegion, SkRegion::kReverseDifference_Op);
-    canvas->drawRegion(xRegion, paint);
-##
-
-#SeeAlso setRects Op
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool op(const SkRegion& rgna, const SkRegion& rgnb, Op op)
-#Populate
-
-#Example
-#Duration 4
-#Height 128
-    SkPaint paint;
-    paint.setTextSize(128);
-    SkPath xPath, opPath;
-    paint.getTextPath("X", 1, 20, 110, &xPath);
-    xPath.setFillType(SkPath::kInverseWinding_FillType);
-    opPath.addCircle(64, 64, frame * 64);
-    opPath.setFillType(SkPath::kInverseWinding_FillType);
-    SkRegion xRegion, opRegion, rectRegion;
-    SkIRect drawBounds = {0, 0, 128, 128};
-    opRegion.setPath(opPath, SkRegion(drawBounds));
-    xRegion.setPath(xPath, SkRegion(drawBounds));
-    drawBounds.inset(frame * drawBounds.width() / 2, 0);
-    rectRegion.setRect(drawBounds);    
-    rectRegion.op(xRegion, SkRegion::kIntersect_Op);
-    xRegion.op(rectRegion, opRegion, SkRegion::kReverseDifference_Op);
-    canvas->drawRegion(xRegion, paint);
-##
-
-#SeeAlso setRects Op
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t writeToMemory(void* buffer) const
-#In Utility
-#Line # writes to buffer ##
-#Populate
-
-#Example
-#Height 128
-    SkPaint paint;
-    paint.setTextSize(128);
-    SkPath xPath;
-    paint.getTextPath("X", 1, 20, 110, &xPath);
-    SkIRect drawBounds = {0, 0, 128, 128};
-    SkRegion xRegion;
-    xRegion.setPath(xPath, SkRegion(drawBounds));
-    size_t size = xRegion.writeToMemory(nullptr);
-    sk_sp<SkData> data = SkData::MakeUninitialized(size);
-    xRegion.writeToMemory(data->writable_data());
-    SkRegion copy;
-    copy.readFromMemory(data->data(), data->size());
-    canvas->drawRegion(copy, paint);
-##
-
-#SeeAlso readFromMemory
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t readFromMemory(const void* buffer, size_t length)
-#In Utility
-#Line # reads from buffer ##
-#Populate
-
-#Example
-#Height 100
-    SkRegion region({20, 20, 80, 80});
-    size_t size = region.writeToMemory(nullptr);
-    sk_sp<SkData> data = SkData::MakeUninitialized(size);
-    region.writeToMemory(data->writable_data());
-    SkRegion copy;
-    copy.readFromMemory(data->data(), data->size());
-    canvas->drawRegion(copy, SkPaint());
-##
-
-#SeeAlso writeToMemory
-
-#Method ##
-
-#Class SkRegion ##
-
-#Topic Region ##
diff --git a/docs/SkStream_Reference.bmh b/docs/SkStream_Reference.bmh
deleted file mode 100644
index c94b6f9..0000000
--- a/docs/SkStream_Reference.bmh
+++ /dev/null
@@ -1,520 +0,0 @@
-#Topic Stream
-#Alias Stream_Reference ##
-
-#Class SkStream
-
-#Code
-#Populate
-##
-
-SkStream describes an abstract class that provides readable serial access to 
-data. Subclass SkFILEStream stores readable data in a file. Subclass 
-SkMemoryStream stores readable data in memory.
-
-SkStream data is immutable; data access is synchronous. Reading Stream data
-returns only as many bytes as were available when Stream was created. Unlike
-traditional streams, additional data will not become available at a later time
-or on a subsequent read request.
-
-# ------------------------------------------------------------------------------
-
-#Method virtual ~SkStream()
-#In Constructors
-#Line # incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkStream()
-#In Constructors
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static std::unique_ptr<SkStreamAsset> MakeFromFile(const char path[])
-#In incomplete
-#Line # incomplete ##
-
-Attempts to open the specified file as a stream, returns nullptr on failure.
-
-#Param path  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual size_t read(void* buffer, size_t size) = 0
-#In incomplete
-#Line # incomplete ##
-
-Reads or skips size number of bytes.
-If buffer is nullptr, skip size bytes, return how many were skipped.
-If buffer is not nullptr, copy size bytes into buffer, return how many were copied.
-
-#Param buffer  when nullptr skip size bytes, otherwise copy size bytes into buffer
-##
-#Param size  the number of bytes to skip or copy
-##
-
-#Return  the number of bytes actually read.
-##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t skip(size_t size)
-#In incomplete
-#Line # incomplete ##
-
-Skip size number of bytes. #Param size  incomplete ##
-
-#Return  the actual number bytes that could be skipped
-##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual size_t peek(void* buffer, size_t size) const
-#In incomplete
-#Line # incomplete ##
-
-Attempt to peek at size bytes.
-If this stream supports peeking, copy min(size, peekable bytes) into
-buffer, and return the number of bytes copied.
-If the stream does not support peeking, or cannot peek any bytes,
-return 0 and leave buffer unchanged.
-The stream is guaranteed to be in the same visible state after this
-call, regardless of success or failure.
-
-#Param buffer  must not be nullptr, and must be at least size bytes. Destination
-        to copy bytes
-##
-#Param size  number of bytes to copy
-##
-
-#Return  number of bytes peeked/copied.
-##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual bool isAtEnd() const = 0
-#In incomplete
-#Line # incomplete ##
-
-Returns true when all the bytes in the stream have been read.
-This may return true early (when there are no more bytes to be read)
-or late (after the first unsuccessful read).
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readS8(int8_t* i)
-#In incomplete
-#Line # incomplete ##
-
-#Param i  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readS16(int16_t* i)
-#In incomplete
-#Line # incomplete ##
-
-#Param i  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readS32(int32_t* i)
-#In incomplete
-#Line # incomplete ##
-
-#Param i  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readU8(uint8_t* i)
-#In incomplete
-#Line # incomplete ##
-
-#Param i  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readU16(uint16_t* i)
-#In incomplete
-#Line # incomplete ##
-
-#Param i  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readU32(uint32_t* i)
-#In incomplete
-#Line # incomplete ##
-
-#Param i  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readBool(bool* b)
-#In incomplete
-#Line # incomplete ##
-
-#Param b  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readScalar(SkScalar* s)
-#In incomplete
-#Line # incomplete ##
-
-#Param s  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readPackedUInt(size_t* u)
-#In incomplete
-#Line # incomplete ##
-
-#Param u  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual bool rewind()
-#In incomplete
-#Line # incomplete ##
-
-Rewinds to the beginning of the stream. Returns true if the stream is known
-to be at the beginning after this call returns.
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method std::unique_ptr<SkStream> duplicate() const
-#In incomplete
-#Line # incomplete ##
-
-Duplicates this stream. If this cannot be done, returns nullptr.
-The returned stream will be positioned at the beginning of its data.
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method std::unique_ptr<SkStream> fork() const
-#In incomplete
-#Line # incomplete ##
-
-Duplicates this stream. If this cannot be done, returns nullptr.
-The returned stream will be positioned the same as this stream.
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual bool hasPosition() const
-#In incomplete
-#Line # incomplete ##
-
-Returns true if this stream can report its current position.
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual size_t getPosition() const
-#In incomplete
-#Line # incomplete ##
-
-Returns the current position in the stream. If this cannot be done, returns 0.
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual bool seek(size_t position)
-#In incomplete
-#Line # incomplete ##
-
-#Param position incomplete ##
-
-Seeks to an absolute position in the stream. If this cannot be done, returns false.
-If an attempt is made to seek past the end of the stream, the position will be set
-to the end of the stream.
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual bool move(long offset)
-#In incomplete
-#Line # incomplete ##
-
-#Param offset incomplete ##
-
-Seeks to an relative offset in the stream. If this cannot be done, returns false.
-If an attempt is made to move to a position outside the stream, the position will be set
-to the closest point within the stream (beginning or end).
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual bool hasLength() const
-#In incomplete
-#Line # incomplete ##
-
-Returns true if this stream can report its total length.
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual size_t getLength() const
-#In incomplete
-#Line # incomplete ##
-
-Returns the total length of the stream. If this cannot be done, returns 0.
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual const void* getMemoryBase()
-#In incomplete
-#Line # incomplete ##
-
-Returns the starting address for the data. If this cannot be done, returns nullptr.
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-#Class SkStream ##
-
-#Topic Stream ##
diff --git a/docs/SkSurface_Reference.bmh b/docs/SkSurface_Reference.bmh
deleted file mode 100644
index d551144..0000000
--- a/docs/SkSurface_Reference.bmh
+++ /dev/null
@@ -1,1302 +0,0 @@
-#Topic Surface
-#Alias Surface_Reference ##
-
-#Class SkSurface
-
-#Code
-#Populate
-##
-
-SkSurface is responsible for managing the pixels that a canvas draws into. The pixels can be
-allocated either in CPU memory, if a raster surface; or on the GPU, for a GrRenderTarget surface.
-SkSurface takes care of allocating a SkCanvas that will draw into the surface. Call
-surface->getCanvas() to use that canvas. The caller should not delete the returned canvas;
-it is owned by surface.
-
-SkSurface always has non-zero dimensions. If there is a request for a new surface, and either
-of the requested dimensions are zero, then nullptr will be returned.
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkSurface> MakeRasterDirect(const SkImageInfo& imageInfo, void* pixels,
-                                             size_t rowBytes,
-                                             const SkSurfaceProps* surfaceProps = nullptr)
-#In Constructors
-#Line # creates Surface from SkImageInfo and Pixel_Storage ##
-#Populate
-
-#Example
-void draw(SkCanvas* ) {
-    SkImageInfo info = SkImageInfo::MakeN32Premul(3, 3);
-    const size_t size = info.computeMinByteSize();
-    SkAutoTMalloc<SkPMColor> storage(size);
-    SkPMColor* pixels = storage.get();
-    sk_sp<SkSurface> surface(SkSurface::MakeRasterDirect(info, pixels, info.minRowBytes()));
-    SkCanvas* canvas = surface->getCanvas();
-    canvas->clear(SK_ColorWHITE);
-    SkPMColor pmWhite = pixels[0];
-    SkPaint paint;
-    canvas->drawPoint(1, 1, paint);
-    canvas->flush();  // ensure that point was drawn
-    for (int y = 0; y < info.height(); ++y) {
-        for (int x = 0; x < info.width(); ++x) {
-            SkDebugf("%c", *pixels++ == pmWhite ? '-' : 'x');
-        }
-        SkDebugf("\n");
-    }
-}
-    #StdOut
-        ---
-        -x-
-        ---
-    ##
-##
-
-#SeeAlso MakeRasterDirectReleaseProc MakeRaster MakeRasterN32Premul SkCanvas::MakeRasterDirect
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkSurface> MakeRasterDirectReleaseProc(const SkImageInfo& imageInfo, void* pixels,
-                                    size_t rowBytes,
-                                    void (*releaseProc)(void* pixels, void* context),
-                                    void* context, const SkSurfaceProps* surfaceProps = nullptr)
-#In Constructors
-#Line # creates Surface from SkImageInfo and Pixel_Storage ##
-#Populate
-
-#Example
-#Function
-static void release_direct_surface_storage(void* pixels, void* context) {
-    if (pixels == context) {
-        SkDebugf("expected release context\n");
-    }
-    sk_free(pixels);
-}
-
-##
-void draw(SkCanvas* ) {
-    SkImageInfo info = SkImageInfo::MakeN32Premul(3, 3);
-    const size_t rowBytes = info.minRowBytes();
-    void* pixels = sk_malloc_throw(info.computeByteSize(rowBytes));
-    sk_sp<SkSurface> surface(SkSurface::MakeRasterDirectReleaseProc(info, pixels, rowBytes,
-            release_direct_surface_storage, pixels));
-    SkCanvas* canvas = surface->getCanvas();
-    canvas->clear(SK_ColorWHITE);
-    SkPMColor* colorPtr = (SkPMColor*) pixels;
-    SkPMColor pmWhite = colorPtr[0];
-    SkPaint paint;
-    canvas->drawPoint(1, 1, paint);
-    canvas->flush();  // ensure that point was drawn
-    for (int y = 0; y < info.height(); ++y) {
-        for (int x = 0; x < info.width(); ++x) {
-            SkDebugf("%c", *colorPtr++ == pmWhite ? '-' : 'x');
-        }
-        SkDebugf("\n");
-    }
-}
-#StdOut
----
--x-
----
-expected release context
-##
-##
-
-#SeeAlso MakeRasterDirect MakeRasterN32Premul MakeRaster
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkSurface> MakeRaster(const SkImageInfo& imageInfo, size_t rowBytes,
-                                       const SkSurfaceProps* surfaceProps)
-#In Constructors
-#Line # creates Surface from SkImageInfo ##
-#Populate
-
-#Example
-void draw(SkCanvas* ) {
-    SkImageInfo info = SkImageInfo::MakeN32Premul(3, 3);
-    const size_t rowBytes = 64;
-    sk_sp<SkSurface> surface(SkSurface::MakeRaster(info, rowBytes, nullptr));
-    SkCanvas* canvas = surface->getCanvas();
-    canvas->clear(SK_ColorWHITE);
-    SkPixmap pixmap;
-    if (surface->peekPixels(&pixmap)) {
-        const uint32_t* colorPtr = pixmap.addr32();
-        SkPMColor pmWhite = colorPtr[0];
-        SkPaint paint;
-        canvas->drawPoint(1, 1, paint);
-        canvas->flush();  // ensure that point was drawn
-        for (int y = 0; y < info.height(); ++y) {
-            for (int x = 0; x < info.width(); ++x) {
-                SkDebugf("%c", colorPtr[x] == pmWhite ? '-' : 'x');
-            }
-            colorPtr += rowBytes / sizeof(colorPtr[0]);
-            SkDebugf("\n");
-        }
-    }
-}
-#StdOut
----
--x-
----
-##
-##
-
-#SeeAlso MakeRasterDirect MakeRasterN32Premul MakeRasterDirectReleaseProc
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkSurface> MakeRaster(const SkImageInfo& imageInfo,
-                                       const SkSurfaceProps* props = nullptr)
-#Populate
-
-#Example
-void draw(SkCanvas* ) {
-    SkImageInfo info = SkImageInfo::MakeN32Premul(3, 3);
-    sk_sp<SkSurface> surface(SkSurface::MakeRaster(info));
-    SkCanvas* canvas = surface->getCanvas();
-    canvas->clear(SK_ColorWHITE);
-    SkPixmap pixmap;
-    if (surface->peekPixels(&pixmap)) {
-        const uint32_t* colorPtr = pixmap.addr32();
-        SkPMColor pmWhite = colorPtr[0];
-        SkPaint paint;
-        canvas->drawPoint(1, 1, paint);
-        canvas->flush();  // ensure that point was drawn
-        for (int y = 0; y < info.height(); ++y) {
-            for (int x = 0; x < info.width(); ++x) {
-                SkDebugf("%c", colorPtr[x] == pmWhite ? '-' : 'x');
-            }
-            colorPtr += info.width();
-            SkDebugf("\n");
-        }
-    }
-}
-##
-
-#SeeAlso MakeRasterDirect MakeRasterN32Premul MakeRasterDirectReleaseProc
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkSurface> MakeRasterN32Premul(int width, int height,
-                                                const SkSurfaceProps* surfaceProps = nullptr)
-#In Constructors
-#Line # creates Surface from width, height matching output ##
-#Populate
-
-#Example
-void draw(SkCanvas* ) {
-    sk_sp<SkSurface> surface(SkSurface::MakeRasterN32Premul(3, 3));
-    SkCanvas* canvas = surface->getCanvas();
-    canvas->clear(SK_ColorWHITE);
-    SkPixmap pixmap;
-    if (surface->peekPixels(&pixmap)) {
-        const uint32_t* colorPtr = pixmap.addr32();
-        SkPMColor pmWhite = colorPtr[0];
-        SkPaint paint;
-        canvas->drawPoint(1, 1, paint);
-        canvas->flush();  // ensure that point was drawn
-        for (int y = 0; y < surface->height(); ++y) {
-            for (int x = 0; x < surface->width(); ++x) {
-                SkDebugf("%c", colorPtr[x] == pmWhite ? '-' : 'x');
-            }
-            colorPtr += surface->width();
-            SkDebugf("\n");
-        }
-    }
-}
-#StdOut
----
--x-
----
-##
-##
-
-#SeeAlso MakeRasterDirect MakeRasterN32Premul MakeRasterDirectReleaseProc
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkSurface> MakeFromBackendTexture(GrContext* context,
-                                                   const GrBackendTexture& backendTexture,
-                                                   GrSurfaceOrigin origin, int sampleCnt,
-                                                   SkColorType colorType,
-                                                   sk_sp<SkColorSpace> colorSpace,
-                                                   const SkSurfaceProps* surfaceProps)
-#In Constructors
-#Line # creates Surface from GPU texture ##
-#Populate
-
-#Example
-#Platform gpu cpu
-#Image 3
-    SkPaint paint;
-    paint.setTextSize(32);
-    GrContext* context = canvas->getGrContext();
-    if (!context) {
-         canvas->drawString("GPU only!", 20, 40, paint);
-         return;
-    }
-    sk_sp<SkSurface> gpuSurface = SkSurface::MakeFromBackendTexture(context,
-            backEndTexture, kTopLeft_GrSurfaceOrigin, 0,
-            kRGBA_8888_SkColorType, nullptr, nullptr);
-    auto surfaceCanvas = gpuSurface->getCanvas();
-    surfaceCanvas->drawString("GPU rocks!", 20, 40, paint);
-    sk_sp<SkImage> image(gpuSurface->makeImageSnapshot());
-    canvas->drawImage(image, 0, 0);
-##
-
-#SeeAlso GrBackendTexture MakeFromBackendRenderTarget MakeRenderTarget
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkSurface> MakeFromBackendRenderTarget(GrContext* context,
-                                                const GrBackendRenderTarget& backendRenderTarget,
-                                                GrSurfaceOrigin origin,
-                                                SkColorType colorType,
-                                                sk_sp<SkColorSpace> colorSpace,
-                                                const SkSurfaceProps* surfaceProps)
-#In Constructors
-#Line # creates Surface from GPU render target ##
-#Populate
-
-#Example
-#ToDo  remove !fiddle below once backEndTextureRenderTarget is available ##
-#Platform !fiddle gpu
-    SkPaint paint;
-    paint.setTextSize(32);
-    GrContext* context = canvas->getGrContext();
-    if (!context) {
-         canvas->drawString("GPU only!", 20, 40, paint);
-         return;
-    }
-    sk_sp<SkSurface> gpuSurface = SkSurface::MakeFromBackendRenderTarget(context,
-            backEndRenderTarget, kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType,
-            nullptr, nullptr);
-    auto surfaceCanvas = gpuSurface->getCanvas();
-    surfaceCanvas->drawString("GPU rocks!", 20, 40, paint);
-    sk_sp<SkImage> image(gpuSurface->makeImageSnapshot());
-    canvas->drawImage(image, 0, 0);
-##
-
-#SeeAlso MakeFromBackendTexture MakeRenderTarget
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkSurface> MakeFromBackendTextureAsRenderTarget(GrContext* context,
-                                                            const GrBackendTexture& backendTexture,
-                                                            GrSurfaceOrigin origin,
-                                                            int sampleCnt,
-                                                            SkColorType colorType,
-                                                            sk_sp<SkColorSpace> colorSpace,
-                                                            const SkSurfaceProps* surfaceProps)
-#In Constructors
-#Line # creates Surface from GPU back-end render target ##
-#Populate
-
-#Example
-#ToDo example is bogus; gpuSurface should not make image ##
-#Platform gpu
-#Image 3
-    SkPaint paint;
-    paint.setTextSize(32);
-    GrContext* context = canvas->getGrContext();
-    if (!context) {
-         canvas->drawString("GPU only!", 20, 40, paint);
-         return;
-    }
-    sk_sp<SkSurface> gpuSurface = SkSurface::MakeFromBackendTextureAsRenderTarget(
-            context, backEndTexture, kTopLeft_GrSurfaceOrigin, 0,
-            kRGBA_8888_SkColorType, nullptr, nullptr);
-    auto surfaceCanvas = gpuSurface->getCanvas();
-    surfaceCanvas->drawString("GPU rocks!", 20, 40, paint);
-    sk_sp<SkImage> image(gpuSurface->makeImageSnapshot());
-    canvas->drawImage(image, 0, 0);
-##
-
-#SeeAlso MakeFromBackendRenderTarget MakeRenderTarget
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkSurface> MakeRenderTarget(GrContext* context, SkBudgeted budgeted,
-                                             const SkImageInfo& imageInfo,
-                                             int sampleCount, GrSurfaceOrigin surfaceOrigin,
-                                             const SkSurfaceProps* surfaceProps,
-                                             bool shouldCreateWithMips = false)
-#In Constructors
-#Line # creates Surface pointing to new GPU memory buffer ##
-#Populate
-
-#Example
-#Platform gpu
-#Height 64
-    SkPaint paint;
-    paint.setTextSize(32);
-    GrContext* context = canvas->getGrContext();
-    if (!context) {
-         canvas->drawString("GPU only!", 20, 40, paint);
-         return;
-    }
-    SkImageInfo info = SkImageInfo::MakeN32(256, 64, kOpaque_SkAlphaType);
-    for (auto surfaceOrigin : { kTopLeft_GrSurfaceOrigin, kBottomLeft_GrSurfaceOrigin } ) {
-        auto gpuSurface(SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 0,
-               surfaceOrigin, nullptr));
-        auto surfaceCanvas = gpuSurface->getCanvas();
-        surfaceCanvas->clear(SK_ColorWHITE);
-        surfaceCanvas->drawString("GPU rocks!", 20, 40, paint);
-        sk_sp<SkImage> image(gpuSurface->makeImageSnapshot());
-        canvas->drawImage(image, 0, 0);
-       canvas->translate(0, 128);
-    }
-##
-
-#SeeAlso MakeFromBackendRenderTarget MakeFromBackendTextureAsRenderTarget
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkSurface> MakeRenderTarget(GrContext* context, SkBudgeted budgeted,
-                                             const SkImageInfo& imageInfo, int sampleCount,
-                                             const SkSurfaceProps* props)
-#Populate
-
-#Example
-#Platform cpu gpu
-#Description
-LCD text takes advantage of raster striping to improve resolution. Only one of
-the four combinations is correct, depending on whether monitor LCD striping is
-horizontal or vertical, and whether the order of the stripes is red blue green
-or red green blue.
-##
-void draw(SkCanvas* canvas) {
-    auto test_draw = [](SkCanvas* surfaceCanvas) -> void {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setLCDRenderText(true);
-        paint.setColor(0xFFBBBBBB);
-        surfaceCanvas->drawRect(SkRect::MakeWH(128, 64), paint);
-        paint.setColor(SK_ColorWHITE);
-        paint.setTextSize(32);
-        surfaceCanvas->drawString("Pest", 0, 25, paint);
-    };
-    GrContext* context = canvas->getGrContext();
-    SkImageInfo info = SkImageInfo::MakeN32(128, 64, kOpaque_SkAlphaType);
-    int y = 0;
-    for (auto geometry : { kRGB_H_SkPixelGeometry, kBGR_H_SkPixelGeometry,
-                           kRGB_V_SkPixelGeometry, kBGR_V_SkPixelGeometry } ) {
-        SkSurfaceProps props(0, geometry);
-        sk_sp<SkSurface> surface = context ? SkSurface::MakeRenderTarget(
-                context, SkBudgeted::kNo, info, 0, &props) : SkSurface::MakeRaster(info, &props);
-        test_draw(surface->getCanvas());
-        surface->draw(canvas, 0, y, nullptr);
-        sk_sp<SkImage> image(surface->makeImageSnapshot());
-        SkAutoCanvasRestore acr(canvas, true);
-        canvas->scale(8, 8);
-        canvas->drawImage(image, 12, y / 8);
-        y += 64;
-    }
-}
-##
-
-#SeeAlso MakeFromBackendRenderTarget MakeFromBackendTextureAsRenderTarget
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkSurface> MakeRenderTarget(GrContext* context, SkBudgeted budgeted,
-                                             const SkImageInfo& imageInfo)
-#Populate
-
-#Example
-#Platform gpu
-    SkPaint paint;
-    paint.setTextSize(32);
-    GrContext* context = canvas->getGrContext();
-    if (!context) {
-         canvas->drawString("GPU only!", 20, 40, paint);
-         return;
-    }
-    SkImageInfo info = SkImageInfo::MakeN32(256, 64, kOpaque_SkAlphaType);
-    auto gpuSurface(SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info));
-    auto surfaceCanvas = gpuSurface->getCanvas();
-    surfaceCanvas->clear(SK_ColorWHITE);
-    surfaceCanvas->drawString("GPU rocks!", 20, 40, paint);
-    sk_sp<SkImage> image(gpuSurface->makeImageSnapshot());
-    canvas->drawImage(image, 0, 0);
-##
-
-#SeeAlso MakeFromBackendRenderTarget MakeFromBackendTextureAsRenderTarget
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkSurface> MakeRenderTarget(GrContext* context,
-                                             const SkSurfaceCharacterization& characterization,
-                                             SkBudgeted budgeted)
-#Populate
-
-#NoExample
-##
-
-#SeeAlso MakeFromBackendRenderTarget MakeFromBackendTextureAsRenderTarget
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkSurface> MakeNull(int width, int height)
-
-#In Constructors
-#Line # creates Surface without backing pixels ##
-#Populate
-
-#Example
-    SkDebugf("SkSurface::MakeNull(0, 0) %c= nullptr\n", SkSurface::MakeNull(0, 0) == nullptr ?
-             '=' : '!');
-    const int w = 37;
-    const int h = 1000;
-    auto surf = SkSurface::MakeNull(w, h);
-    auto nullCanvas = surf->getCanvas();
-    nullCanvas->drawPaint(SkPaint());   // does not crash, nothing draws
-    SkDebugf("surf->makeImageSnapshot() %c= nullptr\n", surf->makeImageSnapshot() == nullptr ?
-            '=' : '!');
-#StdOut
-SkSurface::MakeNull(0, 0) == nullptr
-surf->makeImageSnapshot() == nullptr
-##
-##
-
-#SeeAlso MakeRaster MakeRenderTarget
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Property
-#Line # member values ##
-##
-
-#Method int width() const
-
-#In Property
-#Line # returns pixel column count ##
-#Populate
-
-#Example
-    const int width = 37;
-    const int height = 1000;
-    auto surf = SkSurface::MakeNull(width, height);
-    auto nullCanvas = surf->getCanvas();
-    SkDebugf("surface width=%d  canvas width=%d\n", surf->width(),
-             nullCanvas->getBaseLayerSize().fWidth);
-#StdOut
-surface width=37  canvas width=37
-##
-##
-
-#SeeAlso height()
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method int height() const
-#In Property
-#Line # returns pixel row count ##
-#Populate
-
-#Example
-    const int width = 37;
-    const int height = 1000;
-    auto surf = SkSurface::MakeNull(width, height);
-    auto nullCanvas = surf->getCanvas();
-    SkDebugf("surface height=%d  canvas height=%d\n", surf->height(),
-             nullCanvas->getBaseLayerSize().fHeight);
-#StdOut
-surface height=1000  canvas height=1000
-##
-##
-
-#SeeAlso width()
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method uint32_t generationID()
-#In Property
-#Line # returns unique ID ##
-#Populate
-
-#Example
-    auto surface = SkSurface::MakeRasterN32Premul(1, 1);
-    for (int i = 0; i < 3; ++i) {
-        SkDebugf("surface generationID: %d\n", surface->generationID());
-        if (0 == i) {
-            surface->getCanvas()->drawColor(SK_ColorBLACK);
-        } else {
-            surface->notifyContentWillChange(SkSurface::kDiscard_ContentChangeMode);
-        }
-    }
-#StdOut
-surface generationID: 1
-surface generationID: 2
-surface generationID: 3
-##
-##
-
-#SeeAlso notifyContentWillChange ContentChangeMode getCanvas
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Enum ContentChangeMode
-#Line # parameter options for notifyContentWillChange ##
-#Code
-    enum ContentChangeMode {
-        kDiscard_ContentChangeMode,
-        kRetain_ContentChangeMode,
-    };
-##
-
-ContentChangeMode members are parameters to notifyContentWillChange.
-
-#Const kDiscard_ContentChangeMode
-#Line # discards surface on change ##
-Pass to notifyContentWillChange to discard surface contents when
-the surface is cleared or overwritten.
-##
-#Const kRetain_ContentChangeMode
-#Line # preserves surface on change ##
-Pass to notifyContentWillChange when to preserve surface contents.
-If a snapshot has been generated, this copies the Surface contents.
-##
-
-#SeeAlso notifyContentWillChange generationID
-
-#Enum ##
-
-# ------------------------------------------------------------------------------
-
-#ToDo not crazy about misc catagory -- hopefully will become clear with time
-##
-
-#Subtopic Miscellaneous
-#Line # other functions ##
-##
-
-#Method void notifyContentWillChange(ContentChangeMode mode)
-#In Miscellaneous
-#Line # notifies that contents will be changed outside of Skia ##
-#Populate
-
-#Example
-    auto surface = SkSurface::MakeRasterN32Premul(1, 1);
-    for (int i = 0; i < 3; ++i) {
-        SkDebugf("surface generationID: %d\n", surface->generationID());
-        if (0 == i) {
-            surface->getCanvas()->drawColor(SK_ColorBLACK);
-        } else {
-            surface->notifyContentWillChange(SkSurface::kDiscard_ContentChangeMode);
-        }
-    }
-##
-
-#SeeAlso ContentChangeMode generationID
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Enum BackendHandleAccess
-#Line # options to read and write back-end object ##
-#Code
-    enum BackendHandleAccess {
-        kFlushRead_BackendHandleAccess,
-        kFlushWrite_BackendHandleAccess,
-        kDiscardWrite_BackendHandleAccess,
-    };
-
-    static const BackendHandleAccess kFlushRead_TextureHandleAccess =
-            kFlushRead_BackendHandleAccess;
-    static const BackendHandleAccess kFlushWrite_TextureHandleAccess =
-            kFlushWrite_BackendHandleAccess;
-    static const BackendHandleAccess kDiscardWrite_TextureHandleAccess =
-            kDiscardWrite_BackendHandleAccess;
-##
-
-#Const kFlushRead_BackendHandleAccess 0
-#Line # back-end object is readable ##
-Caller may read from the back-end object.
-##
-#Const kFlushWrite_BackendHandleAccess 1
-#Line # back-end object is writable ##
-Caller may write to the back-end object.
-##
-#Const kDiscardWrite_BackendHandleAccess 2
-#Line # back-end object must be overwritten ##
-Caller must overwrite the entire back-end object.
-##
-
-#NoExample
-// todo: need to update example to use GrBackendTexture instead of GrBackendObject
-#Platform gpu
-    SkPaint paint;
-    paint.setTextSize(32);
-    GrContext* context = canvas->getGrContext();
-    if (!context) {
-         canvas->drawString("GPU only!", 20, 40, paint);
-         return;
-    }
-    sk_sp<SkSurface> gpuSurface = SkSurface::MakeRenderTarget(
-            context, SkBudgeted::kYes, SkImageInfo::MakeN32Premul(10, 10));
-    int y = 20;
-    SkString str;
-    paint.setTextSize(16);
-    for (auto access : { SkSurface::kFlushRead_BackendHandleAccess,
-                         SkSurface::kFlushWrite_BackendHandleAccess,
-                         SkSurface::kDiscardWrite_BackendHandleAccess } ) {
-        sk_sp<SkImage> image(gpuSurface->makeImageSnapshot());
-        str.printf("uniqueID=%d", image->uniqueID());
-        canvas->drawString(str, 20, y += 20, paint);
-        GrBackendTexture backendTex = gpuSurface->getBackendTexture(access);
-        str.printf("backendTex is %svalid", backendTex.isValid() ? '' : 'not ');
-        canvas->drawString(str, 20, y += 20, paint);
-    }
-    sk_sp<SkImage> image(gpuSurface->makeImageSnapshot());
-    str.printf("final image uniqueID=%d", image->uniqueID());
-    canvas->drawString(str, 20, y += 20, paint);
-##
-
-#SeeAlso getBackendTexture getBackendRenderTarget
-
-#Enum ##
-
-# ------------------------------------------------------------------------------
-
-#Method GrBackendTexture getBackendTexture(BackendHandleAccess backendHandleAccess)
-#In Property
-#Line # returns the GPU reference to texture ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso GrBackendTexture BackendHandleAccess getBackendRenderTarget
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method GrBackendRenderTarget getBackendRenderTarget(BackendHandleAccess backendHandleAccess)
-#In Property
-#Line # returns the GPU reference to render target ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso GrBackendRenderTarget BackendHandleAccess getBackendTexture
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkCanvas* getCanvas()
-#In Property
-#Line # returns Canvas that draws into Surface ##
-#Populate
-
-#Example
-#Height 64
-    sk_sp<SkSurface> surface(SkSurface::MakeRasterN32Premul(64, 64));
-    SkCanvas* surfaceCanvas = surface->getCanvas();
-    surfaceCanvas->clear(SK_ColorBLUE);
-    SkPaint paint;
-    paint.setTextSize(40);
-    surfaceCanvas->drawString("\xF0\x9F\x98\x81", 12, 45, paint);
-    surface->draw(canvas, 0, 0, nullptr);
-##
-
-#SeeAlso makeSurface makeImageSnapshot draw
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkSurface> makeSurface(const SkImageInfo& imageInfo)
-#In Constructors
-#Line # creates a compatible Surface ##
-#Populate
-
-#Example
-#Height 96
-    sk_sp<SkSurface> big(SkSurface::MakeRasterN32Premul(64, 64));
-    sk_sp<SkSurface> lil(big->makeSurface(SkImageInfo::MakeN32(32, 32, kPremul_SkAlphaType)));
-    big->getCanvas()->clear(SK_ColorRED);
-    lil->getCanvas()->clear(SK_ColorBLACK);
-    SkPixmap pixmap;
-    if (big->peekPixels(&pixmap)) {
-        SkBitmap bigBits;
-        bigBits.installPixels(pixmap);
-        canvas->drawBitmap(bigBits, 0, 0);
-    }
-    if (lil->peekPixels(&pixmap)) {
-        SkBitmap lilBits;
-        lilBits.installPixels(pixmap);
-        canvas->drawBitmap(lilBits, 64, 64);
-    }
-##
-
-#SeeAlso makeImageSnapshot getCanvas draw
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkImage> makeImageSnapshot()
-#In Constructors
-#Line # creates Image capturing Surface contents ##
-#Populate
-
-#Example
-#Height 64
-    sk_sp<SkSurface> big(SkSurface::MakeRasterN32Premul(64, 64));
-    sk_sp<SkSurface> lil(big->makeSurface(SkImageInfo::MakeN32(32, 32, kPremul_SkAlphaType)));
-    big->getCanvas()->clear(SK_ColorRED);
-    lil->getCanvas()->clear(SK_ColorBLACK);
-    sk_sp<SkImage> early(big->makeImageSnapshot());
-    lil->draw(big->getCanvas(), 16, 16, nullptr);
-    sk_sp<SkImage> later(big->makeImageSnapshot());
-    canvas->drawImage(early, 0, 0);
-    canvas->drawImage(later, 128, 0);
-##
-
-#SeeAlso draw getCanvas
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkImage> makeImageSnapshot(const SkIRect& bounds)
-#In Constructors
-#Line # creates Image capturing subset of Surface contents ##
-#Populate
-
-#Example
-#Height 64
-    sk_sp<SkSurface> big(SkSurface::MakeRasterN32Premul(64, 64));
-    sk_sp<SkSurface> lil(big->makeSurface(SkImageInfo::MakeN32(32, 32, kPremul_SkAlphaType)));
-    big->getCanvas()->clear(SK_ColorRED);
-    lil->getCanvas()->clear(SK_ColorBLACK);
-    sk_sp<SkImage> early(big->makeImageSnapshot());
-    lil->draw(big->getCanvas(), 16, 16, nullptr);
-    sk_sp<SkImage> later(big->makeImageSnapshot({0, 0, 16, 16}));
-    canvas->drawImage(early, 0, 0);
-    canvas->drawImage(later, 0, 0);
-##
-
-#SeeAlso draw getCanvas
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Pixels
-#Line # functions with pixel access ##
-##
-
-#Method void draw(SkCanvas* canvas, SkScalar x, SkScalar y, const SkPaint* paint)
-#In Pixels
-#Line # draws Surface contents to canvas ##
-#Populate
-
-#Example
-#Height 64
-    sk_sp<SkSurface> big(SkSurface::MakeRasterN32Premul(64, 64));
-    sk_sp<SkSurface> lil(big->makeSurface(SkImageInfo::MakeN32(32, 32, kPremul_SkAlphaType)));
-    big->getCanvas()->clear(SK_ColorRED);
-    lil->getCanvas()->clear(SK_ColorBLACK);
-    lil->draw(big->getCanvas(), 16, 16, nullptr);
-    SkPixmap pixmap;
-    if (big->peekPixels(&pixmap)) {
-        SkBitmap bigBits;
-        bigBits.installPixels(pixmap);
-        canvas->drawBitmap(bigBits, 0, 0);
-    }
-##
-
-#SeeAlso makeImageSnapshot getCanvas
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool peekPixels(SkPixmap* pixmap)
-#In Pixels
-#Line # copies Surface parameters to Pixmap ##
-#Populate
-
-#Example
-#Height 64
-    sk_sp<SkSurface> surf(SkSurface::MakeRasterN32Premul(64, 64));
-    auto surfCanvas = surf->getCanvas();
-    surfCanvas->clear(SK_ColorRED);
-    SkPaint paint;
-    paint.setTextSize(40);
-    surfCanvas->drawString("&", 16, 48, paint);
-    SkPixmap pixmap;
-    if (surf->peekPixels(&pixmap)) {
-        SkBitmap surfBits;
-        surfBits.installPixels(pixmap);
-        canvas->drawBitmap(surfBits, 0, 0);
-    }
-##
-
-#SeeAlso readPixels writePixels
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readPixels(const SkPixmap& dst, int srcX, int srcY)
-#In Pixels
-#Line # copies Rect of pixels ##
-Copies Rect of pixels to dst.
-
-Source Rect corners are (srcX, srcY) and Surface (width(), height()).
-Destination Rect corners are (0, 0) and (dst.width(), dst.height()).
-Copies each readable pixel intersecting both rectangles, without scaling,
-converting to dst.colorType() and dst.alphaType() if required.
-
-Pixels are readable when Surface is raster, or backed by a GPU.
-
-The destination pixel storage must be allocated by the caller.
-
-Pixel values are converted only if Color_Type and Alpha_Type
-do not match. Only pixels within both source and destination rectangles
-are copied. dst contents outside Rect intersection are unchanged.
-
-Pass negative values for srcX or srcY to offset pixels across or down destination.
-
-Does not copy, and returns false if:
-
-#List
-# Source and destination rectangles do not intersect. ##
-# Pixmap pixels could not be allocated. ##
-# dst.rowBytes() is too small to contain one row of pixels. ##
-##
-
-#Param dst  storage for pixels copied from Surface ##
-#Param srcX  offset into readable pixels on x-axis; may be negative ##
-#Param srcY  offset into readable pixels on y-axis; may be negative ##
-
-#Return true if pixels were copied ##
-
-#Example
-#Height 32
-    sk_sp<SkSurface> surf(SkSurface::MakeRasterN32Premul(64, 64));
-    auto surfCanvas = surf->getCanvas();
-    surfCanvas->clear(SK_ColorRED);
-    SkPaint paint;
-    paint.setTextSize(40);
-    surfCanvas->drawString("&", 0, 32, paint);
-    std::vector<SkPMColor> storage;
-    storage.resize(surf->width() * surf->height());
-    SkPixmap pixmap(SkImageInfo::MakeN32Premul(32, 32), &storage.front(),
-                    surf->width() * sizeof(storage[0]));
-    if (surf->readPixels(pixmap, 0, 0)) {
-        SkBitmap surfBits;
-        surfBits.installPixels(pixmap);
-        canvas->drawBitmap(surfBits, 0, 0);
-    }
-##
-
-#SeeAlso peekPixels writePixels
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes,
-                    int srcX, int srcY)
-
-Copies Rect of pixels from Canvas into dstPixels.
-
-Source Rect corners are (srcX, srcY) and Surface (width(), height()).
-Destination Rect corners are (0, 0) and (dstInfo.width(), dstInfo.height()).
-Copies each readable pixel intersecting both rectangles, without scaling,
-converting to dstInfo.colorType() and dstInfo.alphaType() if required.
-
-Pixels are readable when Surface is raster, or backed by a GPU.
-
-The destination pixel storage must be allocated by the caller.
-
-Pixel values are converted only if Color_Type and Alpha_Type
-do not match. Only pixels within both source and destination rectangles
-are copied. dstPixels contents outside Rect intersection are unchanged.
-
-Pass negative values for srcX or srcY to offset pixels across or down destination.
-
-Does not copy, and returns false if:
-
-#List
-# Source and destination rectangles do not intersect. ##
-# Surface pixels could not be converted to dstInfo.colorType() or dstInfo.alphaType(). ##
-# dstRowBytes is too small to contain one row of pixels. ##
-##
-
-#Param dstInfo  width, height, Color_Type, and Alpha_Type of dstPixels ##
-#Param dstPixels  storage for pixels; dstInfo.height() times dstRowBytes, or larger ##
-#Param dstRowBytes  size of one destination row; dstInfo.width() times pixel size, or larger ##
-#Param srcX  offset into readable pixels on x-axis; may be negative ##
-#Param srcY  offset into readable pixels on y-axis; may be negative ##
-
-#Return true if pixels were copied ##
-
-#Example
-#Height 64
-#Description
-    A black oval drawn on a red background provides an image to copy.
-    readPixels copies one quarter of the Surface into each of the four corners.
-    The copied quarter ovals overdraw the original oval.
-##
-    sk_sp<SkSurface> surf(SkSurface::MakeRasterN32Premul(64, 64));
-    auto surfCanvas = surf->getCanvas();
-    surfCanvas->clear(SK_ColorRED);
-    SkPaint paint;
-    surfCanvas->drawOval({4, 8, 58, 54}, paint);
-    SkImageInfo info = SkImageInfo::Make(64, 64, kBGRA_8888_SkColorType, kPremul_SkAlphaType);
-    sk_sp<SkData> data(SkData::MakeUninitialized(info.minRowBytes() * info.height()));
-    sk_bzero(data->writable_data(), info.minRowBytes() * info.height());
-    for (int x : { 32, -32 } ) {
-        for (int y : { 32, -32 } ) {
-            surf->readPixels(info, data->writable_data(), info.minRowBytes(), x, y);
-        }
-    }
-    sk_sp<SkImage> image = SkImage::MakeRasterData(info, data, info.minRowBytes());
-    canvas->drawImage(image, 0, 0);
-##
-
-#SeeAlso peekPixels writePixels
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool readPixels(const SkBitmap& dst, int srcX, int srcY)
-
-Copies Rect of pixels from Surface into bitmap.
-
-Source Rect corners are (srcX, srcY) and Surface (width(), height()).
-Destination Rect corners are (0, 0) and (bitmap.width(), bitmap.height()).
-Copies each readable pixel intersecting both rectangles, without scaling,
-converting to dst.colorType() and dst.alphaType() if required.
-
-Pixels are readable when Surface is raster, or backed by a GPU.
-
-The destination pixel storage must be allocated by the caller.
-
-Pixel values are converted only if Color_Type and Alpha_Type
-do not match. Only pixels within both source and destination rectangles
-are copied. dst contents outside Rect intersection are unchanged.
-
-Pass negative values for srcX or srcY to offset pixels across or down destination.
-
-Does not copy, and returns false if:
-
-#List
-# Source and destination rectangles do not intersect. ##
-# Surface pixels could not be converted to dst.colorType() or dst.alphaType(). ##
-# dst pixels could not be allocated. ##
-# dst.rowBytes() is too small to contain one row of pixels. ##
-##
-
-#Param dst  storage for pixels copied from Surface ##
-#Param srcX  offset into readable pixels on x-axis; may be negative ##
-#Param srcY  offset into readable pixels on y-axis; may be negative ##
-
-#Return true if pixels were copied ##
-
-#Example
-    sk_sp<SkSurface> surf(SkSurface::MakeRasterN32Premul(64, 64));
-    auto surfCanvas = surf->getCanvas();
-    surfCanvas->clear(SK_ColorGREEN);
-    SkPaint paint;
-    surfCanvas->drawOval({2, 10, 58, 54}, paint);
-    SkImageInfo info = SkImageInfo::Make(64, 64, kBGRA_8888_SkColorType, kPremul_SkAlphaType);
-    SkBitmap bitmap;
-    bitmap.setInfo(info);
-    bitmap.allocPixels();
-    for (int x : { 32, -32 } ) {
-        for (int y : { 32, -32 } ) {
-            surf->readPixels(bitmap, x, y);
-        }
-    }
-    canvas->drawBitmap(bitmap, 0, 0);
-##
-
-#SeeAlso peekPixels writePixels
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void writePixels(const SkPixmap& src, int dstX, int dstY)
-#In Pixels
-#Line # copies Rect of pixels ##
-Copies Rect of pixels from the src Pixmap to the Surface.
-
-Source Rect corners are (0, 0) and (src.width(), src.height()).
-Destination Rect corners are (dstX, dstY) and
-#Formula # (dstX + Surface width(), dstY + Surface height()) ##.
-
-Copies each readable pixel intersecting both rectangles, without scaling,
-converting to Surface SkColorType and SkAlphaType if required.
-
-#Param src  storage for pixels to copy to Surface ##
-#Param dstX x-axis position relative to Surface to begin copy; may be negative ##
-#Param dstY y-axis position relative to Surface to begin copy; may be negative ##
-
-#Example
-#Image 4
-#Height 96
-    sk_sp<SkSurface> surf(SkSurface::MakeRasterN32Premul(64, 64));
-    auto surfCanvas = surf->getCanvas();
-    surfCanvas->clear(SK_ColorRED);
-    SkPaint paint;
-    paint.setTextSize(40);
-    surfCanvas->drawString("&", 16, 40, paint);
-    SkPixmap pixmap;
-    if (surf->peekPixels(&pixmap)) {
-        surf->writePixels(pixmap, 25, 25);
-        sk_sp<SkImage> image(surf->makeImageSnapshot());
-        canvas->drawImage(image, 0, 0);
-    }
-##
-
-#SeeAlso readPixels peekPixels
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method void writePixels(const SkBitmap& src, int dstX, int dstY)
-
-Copies Rect of pixels from the src Bitmap to the Surface.
-
-Source Rect corners are (0, 0) and (src.width(), src.height()).
-Destination Rect corners are (dstX, dstY) and
-#Formula # (dstX + Surface width(), dstY + Surface height()) ##.
-
-Copies each readable pixel intersecting both rectangles, without scaling,
-converting to Surface SkColorType and SkAlphaType if required.
-
-#Param src  storage for pixels to copy to Surface ##
-#Param dstX x-axis position relative to Surface to begin copy; may be negative ##
-#Param dstY y-axis position relative to Surface to begin copy; may be negative ##
-
-#Example
-#Image 4
-#Height 96
-    sk_sp<SkSurface> surf(SkSurface::MakeRasterN32Premul(64, 64));
-    auto surfCanvas = surf->getCanvas();
-    surfCanvas->clear(SK_ColorGREEN);
-    surf->writePixels(source, 25, 25);
-    sk_sp<SkImage> image(surf->makeImageSnapshot());
-    canvas->drawImage(image, 0, 0);
-##
-
-#SeeAlso readPixels peekPixels
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method const SkSurfaceProps& props() const
-#In Property
-#Line # returns Surface_Properties ##
-#Populate
-
-#Example
-    const char* names[] = { "Unknown", "RGB_H", "BGR_H", "RGB_V", "BGR_V" };
-    sk_sp<SkSurface> surf(SkSurface::MakeRasterN32Premul(64, 64));
-    SkDebugf("surf.props(): k%s_SkPixelGeometry\n", names[surf->props().pixelGeometry()]);
-#StdOut
-surf.props(): kRGB_H_SkPixelGeometry
-##
-##
-
-#SeeAlso SkSurfaceProps
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-#Subtopic Utility
-#Line # rarely called management functions ##
-##
-
-#Method void flush()
-#In Utility
-#Line # resolves pending I/O ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso GrBackendSemaphore
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method GrSemaphoresSubmitted flushAndSignalSemaphores(int numSemaphores,
-                                                   GrBackendSemaphore signalSemaphores[])
-#In Utility
-#Line # resolves pending I/O, and signal ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso wait GrBackendSemaphore
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool wait(int numSemaphores, const GrBackendSemaphore* waitSemaphores)
-#In Utility
-#Line # pauses commands until signaled ##
-#Populate
-
-#NoExample
-#ToDo this is copy and paste silliness masquerading as an example. Probably need gpu
-      globals and definitely need gpu expertise to make a real example out of this
-      also, note need to replace GrBackendObject with GrBackendTexture
- ##
-#Platform gpu
-#Height 64
-    SkPaint paint;
-    paint.setTextSize(32);
-    GrContext* context = canvas->getGrContext();
-    if (!context) {
-         canvas->drawString("GPU only!", 20, 40, paint);
-         return;
-    }
-    GrBackendSemaphore semaphore;
-    sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(
-            context, SkBudgeted::kYes, SkImageInfo::MakeN32Premul(64, 64));
-    surface->flushAndSignalSemaphores(1, &semaphore);
-    sk_sp<SkImage> image = surface->makeImageSnapshot();
-    GrBackendTexture backendTex = image->getBackendTexture(false); // unused
-    SkASSERT(backendTex.isValid());
-    const SkImageInfo childImageInfo = SkImageInfo::Make(64, 64,
-              kRGBA_8888_SkColorType, kPremul_SkAlphaType);
-    sk_sp<SkSurface> childSurface(SkSurface::MakeRenderTarget(context, SkBudgeted::kNo,
-              childImageInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr));
-    GrBackendTexture backendTexture;
-    sk_sp<SkImage> childImage = SkImage::MakeFromTexture(context,
-              backendTexture,   // undefined
-              kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, nullptr);
-    SkCanvas* childCanvas = childSurface->getCanvas();
-    childCanvas->clear(SK_ColorRED);
-    childSurface->wait(1, &semaphore);
-    childCanvas->drawImage(childImage, 32, 0);
-    childSurface->draw(canvas, 0, 0, nullptr);
-##
-
-#SeeAlso flushAndSignalSemaphores GrBackendSemaphore
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool characterize(SkSurfaceCharacterization* characterization) const
-#In Utility
-#Line # sets Surface_Characterization for threaded GPU processing ##
-#Populate
-
-#Example
-#Platform gpu
-#Height 64
-    SkPaint paint;
-    paint.setTextSize(32);
-    GrContext* context = canvas->getGrContext();
-    if (!context) {
-         canvas->drawString("GPU only!", 20, 40, paint);
-         return;
-    }
-    sk_sp<SkSurface> gpuSurface = SkSurface::MakeRenderTarget(
-            context, SkBudgeted::kYes, SkImageInfo::MakeN32Premul(64, 64));
-    SkSurfaceCharacterization characterization;
-    if (!gpuSurface->characterize(&characterization)) {
-         canvas->drawString("characterization unsupported", 20, 40, paint);
-         return;
-    }
-    // start of threadable work
-    SkDeferredDisplayListRecorder recorder(characterization);
-    SkCanvas* subCanvas = recorder.getCanvas();
-    subCanvas->clear(SK_ColorGREEN);
-    std::unique_ptr<SkDeferredDisplayList> displayList = recorder.detach();
-    // end of threadable work
-    gpuSurface->draw(displayList.get());
-    sk_sp<SkImage> img = gpuSurface->makeImageSnapshot();
-    canvas->drawImage(std::move(img), 0, 0);
-##
-
-#SeeAlso draw() SkSurfaceCharacterization SkDeferredDisplayList
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool draw(SkDeferredDisplayList* deferredDisplayList)
-#Populate
-
-#Example
-#Height 64
-#Platform gpu cpu
-    SkPaint paint;
-    paint.setTextSize(16);
-    sk_sp<SkSurface> gpuSurface = SkSurface::MakeRasterN32Premul(64, 64);
-    SkSurfaceCharacterization characterization;
-    if (!gpuSurface->characterize(&characterization)) {
-         canvas->drawString("characterization unsupported", 20, 40, paint);
-         return;
-    }
-    // start of threadable work
-    SkDeferredDisplayListRecorder recorder(characterization);
-    SkCanvas* subCanvas = recorder.getCanvas();
-    subCanvas->clear(SK_ColorGREEN);
-    std::unique_ptr<SkDeferredDisplayList> displayList = recorder.detach();
-    // end of threadable work
-    gpuSurface->draw(displayList.get());
-    sk_sp<SkImage> img = gpuSurface->makeImageSnapshot();
-    canvas->drawImage(std::move(img), 0, 0);
-##
-
-#SeeAlso characterize() SkSurfaceCharacterization SkDeferredDisplayList
-
-#Method ##
-
-#Class SkSurface ##
-
-#Topic Surface ##
diff --git a/docs/SkTextBlobBuilder_Reference.bmh b/docs/SkTextBlobBuilder_Reference.bmh
deleted file mode 100644
index 489142e..0000000
--- a/docs/SkTextBlobBuilder_Reference.bmh
+++ /dev/null
@@ -1,202 +0,0 @@
-#Topic Text_Blob_Builder
-#Alias Text_Blob_Builder_Reference ##
-
-#Class SkTextBlobBuilder
-
-#Code
-#Populate
-##
-
-Helper class for constructing SkTextBlob.
-
-# ------------------------------------------------------------------------------
-
-#Struct RunBuffer
-#Line # storage for Glyphs and Glyph positions ##
-
-#Code
-    struct RunBuffer {
-        SkGlyphID* glyphs;
-        SkScalar* pos;
-        char* utf8text;
-        uint32_t* clusters;
-    };
-##
-
-RunBuffer supplies storage for Glyphs and positions within a run.
-
-A run is a sequence of Glyphs sharing Font_Metrics and positioning.
-Each run may position its Glyphs in one of three ways:
-by specifying where the first Glyph is drawn, and allowing Font_Metrics to
-determine the advance to subsequent Glyphs; by specifying a baseline, and
-the position on that baseline for each Glyph in run; or by providing Point
-array, one per Glyph.
-
-#Member SkGlyphID* glyphs
-#Line # storage for Glyphs in run ##
-    glyphs points to memory for one or more Glyphs. glyphs memory must be
-    written to by the caller.
-##
-
-#Member SkScalar* pos
-#Line # storage for positions in run ##
-    pos points to memory for Glyph positions. Depending on how RunBuffer
-    is allocated, pos may point to zero bytes per Glyph, one Scalar per Glyph,
-    or one Point per Glyph.
-##
-
-#Member char* utf8text
-#Line # reserved for future use ##
-    Reserved for future use. utf8text should not be read or written.
-##
-
-#Member uint32_t* clusters
-#Line # reserved for future use ##
-    Reserved for future use. clusters should not be read or written.
-##
-
-#SeeAlso allocRun allocRunPos allocRunPosH
-
-#Struct RunBuffer ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkTextBlobBuilder()
-#In Constructors
-#Line # constructs with default values ##
-#Populate
-
-#Example
-    SkTextBlobBuilder builder;
-    sk_sp<SkTextBlob> blob = builder.make();
-    SkDebugf("blob " "%s" " nullptr", blob == nullptr ? "equals" : "does not equal");
-#StdOut
-blob equals nullptr
-##
-##
-
-#SeeAlso make SkTextBlob::MakeFromText
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method ~SkTextBlobBuilder()
-#In Constructors
-#Line # deletes storage ##
-#Populate
-
-#NoExample
-##
-
-#SeeAlso SkTextBlobBuilder()
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkTextBlob> make()
-#In Constructors
-#Line # constructs Text_Blob from bulider ##
-#Populate
-
-#Example
-    SkTextBlobBuilder builder;
-    sk_sp<SkTextBlob> blob = builder.make();
-    SkDebugf("blob " "%s" " nullptr\n", blob == nullptr ? "equals" : "does not equal");
-    SkPaint paint;
-    paint.setTextEncoding(kGlyphID_SkTextEncoding);
-    SkFont font;
-    paint.textToGlyphs("x", 1, builder.allocRun(font, 1, 20, 20).glyphs);
-    blob = builder.make();
-    SkDebugf("blob " "%s" " nullptr\n", blob == nullptr ? "equals" : "does not equal");
-    blob = builder.make();
-    SkDebugf("blob " "%s" " nullptr\n", blob == nullptr ? "equals" : "does not equal");
-#StdOut
-blob equals nullptr
-blob does not equal nullptr
-blob equals nullptr
-##
-##
-
-#SeeAlso SkTextBlob::MakeFromText
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method const RunBuffer& allocRun(const SkFont& font, int count, SkScalar x, SkScalar y,
-const SkRect* bounds = nullptr)
-#In Allocator
-#Line # returns writable glyph buffer at Point ##
-
-#Populate
-
-#Example
-#Height 60
-    SkTextBlobBuilder builder;
-    SkFont font;
-    SkPaint paint;
-    const SkTextBlobBuilder::RunBuffer& run = builder.allocRun(font, 5, 20, 20);
-    paint.textToGlyphs("hello", 5, run.glyphs);
-    canvas->drawRect({20, 20, 30, 30}, paint);
-    canvas->drawTextBlob(builder.make(), 20, 20, paint);
-##
-
-#SeeAlso allocRunPosH allocRunPos
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method const RunBuffer& allocRunPosH(const SkFont& font, int count, SkScalar y,
-                                      const SkRect* bounds = nullptr)
-#In Allocator
-#Line # returns writable glyph and x-axis position buffers ##
-
-#Populate
-
-#Example
-#Height 60
-    SkTextBlobBuilder builder;
-    SkPaint paint;
-    SkFont font;
-    const SkTextBlobBuilder::RunBuffer& run = builder.allocRunPosH(font, 5, 20);
-    paint.textToGlyphs("hello", 5, run.glyphs);
-    SkScalar positions[] = {0, 10, 20, 40, 80};
-    memcpy(run.pos, positions, sizeof(positions));
-    canvas->drawTextBlob(builder.make(), 20, 20, paint);
-##
-
-#SeeAlso allocRunPos allocRun
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method const RunBuffer& allocRunPos(const SkFont& font, int count,
-                                     const SkRect* bounds = nullptr)
-#In Allocator
-#Line # returns writable glyph and Point buffers ##
-
-#Populate
-
-#Example
-#Height 90
-    SkTextBlobBuilder builder;
-    SkPaint paint;
-    SkFont font;
-    const SkTextBlobBuilder::RunBuffer& run = builder.allocRunPos(font, 5);
-    paint.textToGlyphs("hello", 5, run.glyphs);
-    SkPoint positions[] = {{0, 0}, {10, 10}, {20, 20}, {40, 40}, {80, 80}};
-    memcpy(run.pos, positions, sizeof(positions));
-    canvas->drawTextBlob(builder.make(), 20, 20, paint);
-##
-
-#SeeAlso allocRunPosH allocRun
-
-#Method ##
-
-#Class SkTextBlobBuilder ##
-
-#Topic Text_Blob_Builder ##
diff --git a/docs/SkTextBlob_Reference.bmh b/docs/SkTextBlob_Reference.bmh
deleted file mode 100644
index fc01ab1..0000000
--- a/docs/SkTextBlob_Reference.bmh
+++ /dev/null
@@ -1,305 +0,0 @@
-#Topic Text_Blob
-#Alias Text_Blob_Reference ##
-
-#Class SkTextBlob
-
-#Code
-#Populate
-##
-
-SkTextBlob combines multiple text runs into an immutable container. Each text
-run consists of Glyphs, Paint, and position. Only parts of Paint related to
-fonts and text rendering are used by run.
-
-# ------------------------------------------------------------------------------
-
-#Method const SkRect& bounds() const
-#In Property
-#Line # returns conservative bounding box ##
-#Populate
-
-#Example
-#Height 70
-    SkTextBlobBuilder textBlobBuilder;
-    const char bunny[] = "/(^x^)\\";
-    const int len = sizeof(bunny) - 1;
-    uint16_t glyphs[len];
-    SkFont font;
-    font.textToGlyphs(bunny, len, SkTextEncoding::kUTF8, glyphs, sizeof(glyphs));
-    int runs[] = { 3, 1, 3 };
-    SkPoint textPos = { 20, 50 };
-    int glyphIndex = 0;
-    for (auto runLen : runs) {
-        font.setSize(1 == runLen ? 20 : 50);
-        const SkTextBlobBuilder::RunBuffer& run =
-                textBlobBuilder.allocRun(font, runLen, textPos.fX, textPos.fY);
-        memcpy(run.glyphs, &glyphs[glyphIndex], sizeof(glyphs[0]) * runLen);
-        textPos.fX += font.measureText(&glyphs[glyphIndex], sizeof(glyphs[0]) * runLen, 
-                SkTextEncoding::kGlyphID);
-        glyphIndex += runLen;
-    }
-    sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
-    SkPaint paint;
-    canvas->drawTextBlob(blob.get(), 0, 0, paint);
-    paint.setStyle(SkPaint::kStroke_Style);
-    canvas->drawRect(blob->bounds(), paint);
-##
-
-#SeeAlso SkPath::getBounds
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method uint32_t uniqueID() const
-#In Property
-#Line # returns identifier for Text_Blob ##
-#Populate
-
-#Example
-for (int index = 0; index < 2; ++index) {
-    SkTextBlobBuilder textBlobBuilder;
-    const char bunny[] = "/(^x^)\\";
-    const int len = sizeof(bunny) - 1;
-    uint16_t glyphs[len];
-    SkFont font;
-    font.textToGlyphs(bunny, len, SkTextEncoding::kUTF8, glyphs, sizeof(glyphs));
-    font.setScaleX(0.5);
-    int runs[] = { 3, 1, 3 };
-    SkPoint textPos = { 20, 50 };
-    int glyphIndex = 0;
-    for (auto runLen : runs) {
-        font.setSize(1 == runLen ? 20 : 50);
-        const SkTextBlobBuilder::RunBuffer& run =
-                textBlobBuilder.allocRun(font, runLen, textPos.fX, textPos.fY);
-        memcpy(run.glyphs, &glyphs[glyphIndex], sizeof(glyphs[0]) * runLen);
-        textPos.fX += font.measureText(&glyphs[glyphIndex], sizeof(glyphs[0]) * runLen,
-                SkTextEncoding::kGlyphID);
-        glyphIndex += runLen;
-    }
-    sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
-    SkPaint paint;
-    canvas->drawTextBlob(blob.get(), 0, 0, paint);
-    std::string id = "unique ID:" + std::to_string(blob->uniqueID());
-    canvas->drawString(id.c_str(), 30, blob->bounds().fBottom + 15, paint);
-    canvas->translate(blob->bounds().fRight + 10, 0);
-}
-##
-
-#SeeAlso SkRefCnt
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Subtopic Text_Intercepts
-#Line # advanced underline, strike through ##
-
-Text_Intercepts describe the intersection of drawn text Glyphs with a pair
-of lines parallel to the text advance. Text_Intercepts permits creating a
-underline that skips Descenders.
-
-#Method int getIntercepts(const SkScalar bounds[2], SkScalar intervals[],
-                      const SkPaint* paint = nullptr) const;
-#In Text_Intercepts
-#Line # returns where lines intersect Text_Blob; underlines ##
-#Populate
-
-#Example
-#Height 143
-    void draw(SkCanvas* canvas) {
-        SkFont font;
-        font.setSize(120);
-        SkPoint textPos = { 20, 110 };
-        int len = 3;
-        SkTextBlobBuilder textBlobBuilder;
-        const SkTextBlobBuilder::RunBuffer& run =
-                textBlobBuilder.allocRun(font, len, textPos.fX, textPos.fY);
-        run.glyphs[0] = 10;
-        run.glyphs[1] = 20;
-        run.glyphs[2] = 30;
-        sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
-        SkPaint paint;
-        SkScalar bounds[] = { 116, 134 };
-        int count = blob->getIntercepts(bounds, nullptr);
-        std::vector<SkScalar> intervals;
-        intervals.resize(count);
-        (void) paint.getTextBlobIntercepts(blob.get(), bounds, &intervals.front());
-        canvas->drawTextBlob(blob.get(), 0, 0, paint);
-        paint.setColor(0xFFFF7777);
-        SkScalar x = textPos.fX;
-        for (int i = 0; i < count; i+= 2) {
-            canvas->drawRect({x, bounds[0], intervals[i], bounds[1]}, paint);
-            x = intervals[i + 1];
-        }
-        canvas->drawRect({intervals[count - 1], bounds[0], 180, bounds[1]}, paint);
-    }
-##
-
-#Method ##
-
-#Subtopic Text_Intercepts ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkTextBlob> MakeFromText(const void* text, size_t byteLength, const SkFont& font,
-                                          SkTextEncoding encoding = kUTF8_SkTextEncoding)
-#In Constructors
-#Line # constructs Text_Blob with one run ##
-
-Creates Text_Blob with a single run. text meaning depends on Text_Encoding;
-by default, text is encoded as UTF-8.
-
-font contains attributes used to define the run text: #font_metrics#.
-
-#Param text character code points or Glyphs drawn ##
-#Param  byteLength   byte length of text array ##
-#Param  font    text size, typeface, text scale, and so on, used to draw ##
-#Param  encoding  one of: kUTF8_SkTextEncoding, kUTF16_SkTextEncoding,
-                          kUTF32_SkTextEncoding, kGlyphID_SkTextEncoding
-##
-
-#Return Text_Blob constructed from one run ##
-
-#Example
-#Height 24
-    SkFont font;
-    font.setSize(24);
-    SkPaint canvasPaint;
-    canvasPaint.setColor(SK_ColorBLUE); // respected
-    canvasPaint.setTextSize(2); // ignored
-    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromText("Hello World", 11, font);
-    canvas->drawTextBlob(blob, 20, 20, canvasPaint);
-##
-
-#SeeAlso MakeFromString SkTextBlobBuilder
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkTextBlob> MakeFromString(const char* string, const SkFont& font,
-             SkTextEncoding encoding = kUTF8_SkTextEncoding)
-#In Constructors
-#Line # constructs Text_Blob with one run ##
-
-Creates Text_Blob with a single run. string meaning depends on Text_Encoding;
-by default, string is encoded as UTF-8.
-
-font contains Font_Metrics used to define the run text: #font_metrics#.
-
-#Param string character code points or Glyphs drawn ##
-#Param  font    text size, typeface, text scale, and so on, used to draw ##
-#Param  encoding  one of: kUTF8_SkTextEncoding, kUTF16_SkTextEncoding,
-                          kUTF32_SkTextEncoding, kGlyphID_SkTextEncoding
-##
-
-#Return Text_Blob constructed from one run ##
-
-#Example
-#Height 24
-    SkFont font;
-    font.setSize(24);
-    SkPaint canvasPaint;
-    canvasPaint.setColor(SK_ColorBLUE); // respected
-    canvasPaint.setTextSize(2); // ignored
-    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromString("Hello World", font);
-    canvas->drawTextBlob(blob, 20, 20, canvasPaint);
-##
-
-#SeeAlso MakeFromText SkTextBlobBuilder
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method size_t serialize(const SkSerialProcs& procs, void* memory, size_t memory_size) const
-#In Utility
-#Line # writes Text_Blob to memory ##
-#Populate
-
-#Example
-#Height 64
-###$
-$Function
-#include "SkSerialProcs.h"
-$$
-$$$#
-    SkFont blobFont;
-    blobFont.setSize(24);
-    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromText("Hello World", 11, blobFont);
-    char storage[2048];
-    size_t used = blob->serialize(SkSerialProcs(), storage, sizeof(storage));
-    sk_sp<SkTextBlob> copy = SkTextBlob::Deserialize(storage, used, SkDeserialProcs());
-    canvas->drawTextBlob(copy, 20, 20, SkPaint());
-    std::string usage = "size=" + std::to_string(sizeof(storage)) + " used=" + std::to_string(used);
-    canvas->drawString(usage.c_str(), 20, 40, SkPaint());
-##
-
-#SeeAlso Deserialize SkSerialProcs
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method sk_sp<SkData> serialize(const SkSerialProcs& procs) const
-#In Utility
-#Line # writes Text_Blob to Data ##
-#Populate
-
-#Example
-#Height 24
-###$
-$Function
-#include "SkSerialProcs.h"
-$$
-$$$#
-    SkFont blobFont;
-    blobFont.setSize(24);
-    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromText("Hello World", 11, blobFont);
-    sk_sp<SkData> data = blob->serialize(SkSerialProcs());
-    sk_sp<SkTextBlob> copy = SkTextBlob::Deserialize(data->data(), data->size(), SkDeserialProcs());
-    canvas->drawTextBlob(copy, 20, 20, SkPaint());
-##
-
-#SeeAlso Deserialize SkData SkSerialProcs
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static sk_sp<SkTextBlob> Deserialize(const void* data, size_t size, const SkDeserialProcs& procs)
-#In Constructors
-#Line # constructs Text_Blob from memory ##
-#Populate
-
-#Example
-#Height 24
-#Description
-Text "Hacker" replaces "World!", but does not update its metrics.
-When drawn, "Hacker" uses the spacing computed for "World!".
-##
-###$
-$Function
-#include "SkSerialProcs.h"
-$$
-$$$#
-    SkFont blobFont;
-    blobFont.setSize(24);
-    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromText("Hello World!", 12, blobFont);
-    sk_sp<SkData> data = blob->serialize(SkSerialProcs());
-    uint16_t glyphs[6];
-    SkPaint blobPaint;
-    blobPaint.textToGlyphs("Hacker", 6, glyphs);
-    memcpy((char*)data->writable_data() + 0x54, glyphs, sizeof(glyphs));
-    sk_sp<SkTextBlob> copy = SkTextBlob::Deserialize(data->data(), data->size(), SkDeserialProcs());
-    canvas->drawTextBlob(copy, 20, 20, SkPaint());
-##
-
-#SeeAlso serialize SkDeserialProcs
-
-#Method ##
-
-#Class SkTextBlob ##
-
-#Topic Text_Blob ##
diff --git a/docs/SkWStream_Reference.bmh b/docs/SkWStream_Reference.bmh
deleted file mode 100644
index 40c142b..0000000
--- a/docs/SkWStream_Reference.bmh
+++ /dev/null
@@ -1,354 +0,0 @@
-#Topic WStream
-#Alias WStream_Reference ##
-
-#Class SkWStream
-
-#Code
-#Populate
-##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual ~SkWStream()
-#In Constructors
-#Line # incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method SkWStream()
-#In Constructors
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual bool write(const void* buffer, size_t size) = 0
-#In incomplete
-#Line # incomplete ##
-
-Called to write bytes to a SkWStream. Returns true on success
-
-#Param buffer  the address of at least size bytes to be written to the stream
-##
-#Param size  number of bytes in buffer to write to the stream
-##
-
-#Return  true on success
-##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual void flush()
-#In incomplete
-#Line # incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method virtual size_t bytesWritten() const = 0
-#In incomplete
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool write8(U8CPU value)
-#In incomplete
-#Line # incomplete ##
-
-Helpers.
-
-#Param value  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool write16(U16CPU value)
-#In incomplete
-#Line # incomplete ##
-
-#Param value  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool write32(uint32_t v)
-#In incomplete
-#Line # incomplete ##
-
-#Param v  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool writeText(const char text[])
-#In incomplete
-#Line # incomplete ##
-
-#Param text  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool newline()
-#In incomplete
-#Line # incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool writeDecAsText(int32_t)
-#In incomplete
-#Line # incomplete ##
-
-#Param t  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool writeBigDecAsText(int64_t, int minDigits = 0)
-#In incomplete
-#Line # incomplete ##
-
-#Param t  incomplete ##
-#Param minDigits  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool writeHexAsText(uint32_t, int minDigits = 0)
-#In incomplete
-#Line # incomplete ##
-
-#Param t  incomplete ##
-#Param minDigits  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool writeScalarAsText(SkScalar)
-#In incomplete
-#Line # incomplete ##
-
-#Param SkScalar  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool writeBool(bool v)
-#In incomplete
-#Line # incomplete ##
-
-#Param v  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool writeScalar(SkScalar)
-#In incomplete
-#Line # incomplete ##
-
-#Param SkScalar  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool writePackedUInt(size_t)
-#In incomplete
-#Line # incomplete ##
-
-#Param t  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method bool writeStream(SkStream* input, size_t length)
-#In incomplete
-#Line # incomplete ##
-
-#Param input  incomplete ##
-#Param length  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-# ------------------------------------------------------------------------------
-
-#Method static int SizeOfPackedUInt(size_t value)
-#In incomplete
-#Line # incomplete ##
-
-This returns the number of bytes in the stream required to store
-'value'.
-
-#Param value  incomplete ##
-
-#Return incomplete ##
-
-#Example
-// incomplete
-##
-
-#SeeAlso incomplete
-
-#Method ##
-
-#Class SkWStream ##
-
-#Topic WStream ##
diff --git a/docs/catalogHeader.txt b/docs/catalogHeader.txt
deleted file mode 100644
index 45a40d3..0000000
--- a/docs/catalogHeader.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-<!DOCTYPE html>
-
-<html lang="en" xmlns="https://www.w3.org/1999/xhtml">
-<head>
-    <meta charset="utf-8" />
-    <title></title>
-
-<script type="text/javascript">
-
diff --git a/docs/catalogTrailer.txt b/docs/catalogTrailer.txt
deleted file mode 100644
index 064fdd5..0000000
--- a/docs/catalogTrailer.txt
+++ /dev/null
@@ -1,217 +0,0 @@
-
-var canvas
-var context
-var getFromWeb = true
-var mouseDown = false
-var scale = .6
-var outset = 15
-var columnWidth = 256
-var maxHeight = 256
-var lastLink = null
-var lastLinkStr = null
-var labelback = {}
-var loadedImages = 0
-var images = {}
-var imagesLength = 0;
-
-function recContains(rec, value) {
-    if (!value.length)
-        return 0
-    var lc = value.toLowerCase()
-    if (rec.name.toLowerCase().indexOf(lc) >= 0)
-        return 1
-    if (rec.code.toLowerCase().indexOf(lc) >= 0)
-        return 2
-    return 3
-}
-
-function setLink(recstr) {
-    var under
-    var link = recstr
-    if (!link.startsWith("Sk")) {
-        under = link.indexOf('_')
-        link = link.substring(under + 1)
-    }
-    under = link.lastIndexOf('_')
-    var len = link.length
-    if (under == len - 2) {
-        var letter = link[len - 1]
-        if ('a' <= letter && letter <= 'z') {
-            link = link.substr(0, len - 2)
-        } else if ('0' <= letter && letter <= '9') {
-            link = link.substr(0, len - 2)
-        }
-    }
-    lastLinkStr = link
-}
-
-function showLink() {
-    var link = lastLink.file + '#' + lastLinkStr
-    context.save()
-    context.font = "normal 16px Arial";
-    labelback.w = Math.max(labelback.w, context.measureText(link).width + 8)
-    context.beginPath()
-    context.rect(labelback.x, labelback.y, labelback.w, labelback.h)
-    context.fillStyle = "rgba(232,180,220, 1)"
-    context.fill()
-    context.fillStyle = "rgba(64,32,48, 1)"
-    context.fillText(link, labelback.x + 4, labelback.y + 16)
-    context.restore()
-}
-
-function imageIterator(callout, state) {
-    var row = outset + 30
-    var column = outset
-    for (var recstr in pngs) {
-        var rec = pngs[recstr]
-        var contains = recContains(rec, input.value)
-        if (3 == contains)
-            continue;
-        var height = rec.height < maxHeight ? rec.height : maxHeight
-        if (callout(state, column, row, height, contains, recstr))
-            break;
-        row += height + outset
-        if (row >= canvas.height / scale) {
-            row = 0
-            column += columnWidth + outset
-            if (column >= canvas.width / scale) {
-                break
-            }
-        }
-    }
-}
-
-function handleMouseOver(event) {
-    var callout = function(state, column, row, height, contains, recstr) {
-        if (state.x >= column && state.x <= column + columnWidth &&
-                state.y >= row && state.y <= row + height) {
-            document.body.style.cursor = "pointer"
-            lastLink = pngs[recstr]
-            setLink(recstr)
-            showLink()
-            return true
-        }
-        return false
-    }
-    var state = {
-        x: (event.clientX - 5) / scale,
-        y: (event.clientY - 7) / scale
-    }
-    document.body.style.cursor = ""
-    lastLink = null
-    imageIterator(callout, state)
-}
-
-function handleMouseClick() {
-    if (null != lastLink) {
-        var link = 'https://skia.org/user/api/' + lastLink.file + '#' + lastLinkStr
-        window.location = link
-    }
-}
-
-function doKeyPress(evt) {
-    idiv.style.height = 20
-    input.focus()
-}
-
-function drawImage(hash, x, y, w, h, contains) {
-    context.save()
-    context.transform(scale, 0, 0, scale, 0, 0)
-    context.save()
-    context.beginPath()
-    context.rect(x, y, w, h)
-    context.clip()
-    context.drawImage(images[hash], x, y)
-    context.restore()
-    context.beginPath()
-    context.rect(x, y, w, h)
-    context.strokeStyle = 1 == contains ? "red" : "black"
-    context.stroke()
-    context.restore()
-}
-
-function draw() {
-    var callout = function(state, column, row, height, contains, recstr) {
-        drawImage(pngs[recstr].hash, column, row, columnWidth, height, contains)
-        return false
-    }
-    imageIterator(callout, null)
-}
-
-function sleep(ms) {
-    return new Promise(resolve => setTimeout(resolve, ms));
-}
-
-async function redraw() {
-    context.strokeStyle = "white"
-    context.beginPath()
-    context.fillStyle = "white"
-    context.rect(0, 30, canvas.width, canvas.height)
-    context.fill()
-    context.rect((256 + outset) * scale, 0, canvas.width, 30)
-    context.fill()
-    for (var image in images) {
-        image.drawn = false
-    }
-    do {
-        draw();
-        if (loadedImages >= imagesLength)
-            break;
-        console.debug(" loadedImages:" + loadedImages + " imagesLength:" + imagesLength)
-        await sleep(1000);
-    } while (true)
-}
-
-function resize() {
-    setSize()
-    redraw()
-}
-
-function setSize() {
-    canvas.width = window.innerWidth - 20
-    canvas.height = window.innerHeight - 20
-    labelback.x = 0
-    labelback.y = canvas.height - 20
-    labelback.w = 0
-    labelback.h = 20
-}
-
-function loadImages() {
-    for (var recstr in pngs) {
-        var rec = pngs[recstr]
-        var image = new Image()
-        images[rec.hash] = image
-        if (getFromWeb)
-            image.src = 'https://fiddle.skia.org/i/'
-        image.src += rec.hash + '_raster.png'
-        image.onload = function () {
-            loadedImages += 1
-        }
-        imagesLength += 1;
-    }
-}
-
-function start() {
-    loadImages()
-    window.addEventListener('keypress', doKeyPress, true);
-    window.addEventListener('keydown', doKeyPress, true);
-    canvas = document.getElementById('canvas')
-    context = canvas.getContext('2d')
-    resize()
-}
-
-</script>
-</head>
-
-<body onLoad="start()" onresize="resize()">
-<div style="height:0" id="idiv">
-<input type="text" id="input" onkeypress="redraw()" onkeydown="redraw()"/>
-</div>
-<canvas id="canvas" width="750" height="500"
-onmousedown="mouseDown = true"
-onmouseup="mouseDown = false"
-onmousemove="handleMouseOver(event)"
-onclick="handleMouseClick()"
-></canvas >
-</body>
-</html>
diff --git a/docs/examples/Alpha_Constants_a.cpp b/docs/examples/Alpha_Constants_a.cpp
new file mode 100644
index 0000000..8988038
--- /dev/null
+++ b/docs/examples/Alpha_Constants_a.cpp
@@ -0,0 +1,38 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bc9c7ea424d10bbcd1e5a88770d4794e
+REG_FIDDLE(Alpha_Constants_a, 256, 128, false, 1) {
+void draw(SkCanvas* canvas) {
+    std::vector<int32_t> srcPixels;
+    srcPixels.resize(source.height() * source.rowBytes());
+    SkPixmap pixmap(SkImageInfo::MakeN32Premul(source.width(), source.height()),
+                    &srcPixels.front(), source.rowBytes());
+    source.readPixels(pixmap, 0, 0);
+    for (int y = 0; y < 16; ++y) {
+        for (int x = 0; x < 16; ++x) {
+            int32_t* blockStart = &srcPixels.front() + y * source.width() * 16 + x * 16;
+            size_t transparentCount = 0;
+            for (int fillY = 0; fillY < source.height() / 16; ++fillY) {
+                for (int fillX = 0; fillX < source.width() / 16; ++fillX) {
+                    const SkColor color = SkUnPreMultiply::PMColorToColor(blockStart[fillX]);
+                    transparentCount += SkColorGetA(color) == SK_AlphaTRANSPARENT;
+                }
+                blockStart += source.width();
+            }
+            if (transparentCount > 200) {
+                blockStart = &srcPixels.front() + y * source.width() * 16 + x * 16;
+                for (int fillY = 0; fillY < source.height() / 16; ++fillY) {
+                    for (int fillX = 0; fillX < source.width() / 16; ++fillX) {
+                        blockStart[fillX] = SK_ColorRED;
+                    }
+                    blockStart += source.width();
+                }
+            }
+        }
+    }
+    SkBitmap bitmap;
+    bitmap.installPixels(pixmap);
+    canvas->drawBitmap(bitmap, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Alpha_Constants_b.cpp b/docs/examples/Alpha_Constants_b.cpp
new file mode 100644
index 0000000..8de18c3
--- /dev/null
+++ b/docs/examples/Alpha_Constants_b.cpp
@@ -0,0 +1,25 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0424f67ebc2858e8fd04ae3367b115ff
+REG_FIDDLE(Alpha_Constants_b, 256, 128, false, 1) {
+void draw(SkCanvas* canvas) {
+    std::vector<int32_t> srcPixels;
+    srcPixels.resize(source.height() * source.rowBytes());
+    SkPixmap pixmap(SkImageInfo::MakeN32Premul(source.width(), source.height()),
+                    &srcPixels.front(), source.rowBytes());
+    source.readPixels(pixmap, 0, 0);
+    for (int y = 0; y < source.height(); ++y) {
+        for (int x = 0; x < source.width(); ++x) {
+            SkPMColor pixel = srcPixels[y * source.width() + x];
+            const SkColor color = SkUnPreMultiply::PMColorToColor(pixel);
+            if (SkColorGetA(color) == SK_AlphaOPAQUE) {
+                srcPixels[y * source.width() + x] = SK_ColorGREEN;
+            }
+        }
+    }
+    SkBitmap bitmap;
+    bitmap.installPixels(pixmap);
+    canvas->drawBitmap(bitmap, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Alpha_Type_Opaque.cpp b/docs/examples/Alpha_Type_Opaque.cpp
new file mode 100644
index 0000000..92fb99f
--- /dev/null
+++ b/docs/examples/Alpha_Type_Opaque.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=79146a1a41d58d22582fdc567c6ffe4e
+REG_FIDDLE(Alpha_Type_Opaque, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPMColor color = SkPreMultiplyARGB(255, 50, 100, 150);
+    SkString s;
+    s.printf("%u %u %u %u", SkColorGetA(color), SkColorGetR(color),
+                            SkColorGetG(color), SkColorGetB(color));
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    canvas->drawString(s, 10, 62, paint);
+    canvas->scale(50, 50);
+    SkBitmap bitmap;
+    SkImageInfo imageInfo = SkImageInfo::Make(1, 1, kN32_SkColorType, kOpaque_SkAlphaType);
+    if (bitmap.installPixels(imageInfo, (void*) &color, imageInfo.minRowBytes())) {
+        canvas->drawBitmap(bitmap, 0, 0);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Alpha_Type_Premul.cpp b/docs/examples/Alpha_Type_Premul.cpp
new file mode 100644
index 0000000..ae3bb5f
--- /dev/null
+++ b/docs/examples/Alpha_Type_Premul.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ad696b39c915803d566e96896ec3a36c
+REG_FIDDLE(Alpha_Type_Premul, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPMColor color = SkPreMultiplyARGB(150, 50, 100, 150);
+    SkString s;
+    s.printf("%u %u %u %u", SkColorGetA(color), SkColorGetR(color),
+                            SkColorGetG(color), SkColorGetB(color));
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    canvas->drawString(s, 10, 62, paint);
+    canvas->scale(50, 50);
+    SkBitmap bitmap;
+    SkImageInfo imageInfo = SkImageInfo::Make(1, 1, kN32_SkColorType, kPremul_SkAlphaType);
+    if (bitmap.installPixels(imageInfo, (void*) &color, imageInfo.minRowBytes())) {
+        canvas->drawBitmap(bitmap, 0, 0);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Alpha_Type_Unpremul.cpp b/docs/examples/Alpha_Type_Unpremul.cpp
new file mode 100644
index 0000000..37a4f60
--- /dev/null
+++ b/docs/examples/Alpha_Type_Unpremul.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b8216a9e5ff5bc61a0e46eba7d36307b
+REG_FIDDLE(Alpha_Type_Unpremul, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkColor color = SkColorSetARGB(150, 50, 100, 255);
+    SkString s;
+    s.printf("%u %u %u %u", SkColorGetA(color), SkColorGetR(color),
+                            SkColorGetG(color), SkColorGetB(color));
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    canvas->drawString(s, 10, 62, paint);
+    canvas->scale(50, 50);
+    SkBitmap bitmap;
+    SkImageInfo imageInfo = SkImageInfo::Make(1, 1, kN32_SkColorType, kUnpremul_SkAlphaType);
+    if (bitmap.installPixels(imageInfo, (void*) &color, imageInfo.minRowBytes())) {
+        canvas->drawBitmap(bitmap, 0, 0);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Anti_Alias.cpp b/docs/examples/Anti_Alias.cpp
new file mode 100644
index 0000000..16387d9
--- /dev/null
+++ b/docs/examples/Anti_Alias.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a6575a49467ce8d28bb01cc7638fa04d
+REG_FIDDLE(Anti_Alias, 512, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.allocN32Pixels(50, 50);
+    SkCanvas offscreen(bitmap);
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(10);
+    for (bool antialias : { false, true }) {
+        paint.setColor(antialias ? SK_ColorRED : SK_ColorBLUE);
+        paint.setAntiAlias(antialias);
+        bitmap.eraseColor(0);
+        offscreen.drawLine(5, 5, 15, 30, paint);
+        canvas->drawLine(5, 5, 15, 30, paint);
+        canvas->save();
+        canvas->scale(10, 10);
+        canvas->drawBitmap(bitmap, antialias ? 12 : 0, 0);
+        canvas->restore();
+        canvas->translate(15, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Arc.cpp b/docs/examples/Arc.cpp
new file mode 100644
index 0000000..16c077d
--- /dev/null
+++ b/docs/examples/Arc.cpp
@@ -0,0 +1,47 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5acc77eba0cb4d00bbf3a8f4db0c0aee
+REG_FIDDLE(Arc, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect oval = {8, 8, 56, 56};
+    SkPaint ovalPaint;
+    ovalPaint.setAntiAlias(true);
+    SkPaint textPaint(ovalPaint);
+    ovalPaint.setStyle(SkPaint::kStroke_Style);
+    SkPaint arcPaint(ovalPaint);
+    arcPaint.setStrokeWidth(5);
+    arcPaint.setColor(SK_ColorBLUE);
+    canvas->translate(-64, 0);
+    for (char arcStyle = '1'; arcStyle <= '6'; ++arcStyle) {
+        '4' == arcStyle ? canvas->translate(-96, 55) : canvas->translate(64, 0);
+        canvas->drawText(&arcStyle, 1, 30, 36, textPaint);
+        canvas->drawOval(oval, ovalPaint);
+        SkPath path;
+        path.moveTo({56, 32});
+        switch (arcStyle) {
+            case '1':
+                path.arcTo(oval, 0, 90, false);
+                break;
+            case '2':
+                canvas->drawArc(oval, 0, 90, false, arcPaint);
+                continue;
+            case '3':
+                path.addArc(oval, 0, 90);
+                break;
+            case '4':
+                path.arcTo({56, 56}, {32, 56}, 24);
+                break;
+            case '5':
+                path.arcTo({24, 24}, 0, SkPath::kSmall_ArcSize, SkPath::kCW_Direction, {32, 56});
+                break;
+            case '6':
+                path.conicTo({56, 56}, {32, 56}, SK_ScalarRoot2Over2);
+                break;
+         }
+         canvas->drawPath(path, arcPaint);
+     }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/AutoCanvasRestore_SkCanvas_star.cpp b/docs/examples/AutoCanvasRestore_SkCanvas_star.cpp
new file mode 100644
index 0000000..a810cd7
--- /dev/null
+++ b/docs/examples/AutoCanvasRestore_SkCanvas_star.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=466ef576b88e29d7252422db7adeed1c
+REG_FIDDLE(AutoCanvasRestore_SkCanvas_star, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkFont font(nullptr, 64);
+    for (SkScalar sx : { -1, 1 } ) {
+        for (SkScalar sy : { -1, 1 } ) {
+            SkAutoCanvasRestore autoRestore(canvas, true);
+            SkMatrix m = SkMatrix::MakeAll(sx, 1, 96,    0, sy, 64,   0, 0, 1);
+            canvas->concat(m);
+            canvas->drawString("R", 0, 0, font, paint);
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/AutoCanvasRestore_restore.cpp b/docs/examples/AutoCanvasRestore_restore.cpp
new file mode 100644
index 0000000..216d8f4
--- /dev/null
+++ b/docs/examples/AutoCanvasRestore_restore.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9f459b218ec079c1ada23f4412968f9a
+REG_FIDDLE(AutoCanvasRestore_restore, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    for (bool callRestore : { false, true } ) {
+        for (bool saveCanvas : {false, true} ) {
+            SkAutoCanvasRestore autoRestore(canvas, saveCanvas);
+            if (!saveCanvas) {
+                canvas->save();
+            }
+            SkDebugf("saveCanvas: %s  before restore: %d\n",
+                   saveCanvas ? "true" : "false", canvas->getSaveCount());
+            if (callRestore) autoRestore.restore();
+            SkDebugf("saveCanvas: %s  after restore: %d\n",
+                   saveCanvas ? "true" : "false", canvas->getSaveCount());
+        }
+    }
+    SkDebugf("final count: %d\n", canvas->getSaveCount());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_012.cpp b/docs/examples/Bitmap_012.cpp
new file mode 100644
index 0000000..d45b507
--- /dev/null
+++ b/docs/examples/Bitmap_012.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=070b1a60232be499eb10c6ea62371804
+REG_FIDDLE(Bitmap_012, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const char* alphas[] = {"Unknown", "Opaque", "Premul", "Unpremul"};
+    SkPixmap pixmap(SkImageInfo::MakeA8(16, 32), nullptr, 64);
+    SkDebugf("alpha type: k" "%s" "_SkAlphaType\n", alphas[pixmap.alphaType()]);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_ComputeIsOpaque.cpp b/docs/examples/Bitmap_ComputeIsOpaque.cpp
new file mode 100644
index 0000000..d92fba1
--- /dev/null
+++ b/docs/examples/Bitmap_ComputeIsOpaque.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9df1baa17658fbd0c419780f26fd854f
+REG_FIDDLE(Bitmap_ComputeIsOpaque, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.setInfo(SkImageInfo::Make(2, 2, kN32_SkColorType, kPremul_SkAlphaType));
+    for (int index = 0; index < 2; ++index) {
+        bitmap.allocPixels();
+        bitmap.eraseColor(0x00000000);
+        SkDebugf("computeIsOpaque: %s\n", SkBitmap::ComputeIsOpaque(bitmap) ? "true" : "false");
+        bitmap.eraseColor(0xFFFFFFFF);
+        SkDebugf("computeIsOpaque: %s\n", SkBitmap::ComputeIsOpaque(bitmap) ? "true" : "false");
+        bitmap.setInfo(bitmap.info().makeAlphaType(kOpaque_SkAlphaType));
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_HeapAllocator_allocPixelRef.cpp b/docs/examples/Bitmap_HeapAllocator_allocPixelRef.cpp
new file mode 100644
index 0000000..4f3a110
--- /dev/null
+++ b/docs/examples/Bitmap_HeapAllocator_allocPixelRef.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=fe79a9c1ec350264eb9c7b2509dd3638
+REG_FIDDLE(Bitmap_HeapAllocator_allocPixelRef, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.setInfo(SkImageInfo::MakeN32(16, 16, kPremul_SkAlphaType));
+    SkDebugf("pixel address = %p\n", bitmap.getPixels());
+    SkBitmap::HeapAllocator stdalloc;
+    if (!stdalloc.allocPixelRef(&bitmap)) {
+        SkDebugf("pixel allocation failed\n");
+    } else {
+        SkDebugf("pixel address = %p\n", bitmap.getPixels());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_allocN32Pixels.cpp b/docs/examples/Bitmap_allocN32Pixels.cpp
new file mode 100644
index 0000000..f569401
--- /dev/null
+++ b/docs/examples/Bitmap_allocN32Pixels.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c717491f9251604724c9cbde7088ec20
+REG_FIDDLE(Bitmap_allocN32Pixels, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRandom random;
+    SkBitmap bitmap;
+    bitmap.allocN32Pixels(64, 64);
+    bitmap.eraseColor(SK_ColorTRANSPARENT);
+    for (int y = 0; y < 256; y += 64) {
+        for (int x = 0; x < 256; x += 64) {
+            SkColor color = random.nextU();
+            uint32_t w = random.nextRangeU(4, 32);
+            uint32_t cx = random.nextRangeU(0, 64 - w);
+            uint32_t h = random.nextRangeU(4, 32);
+            uint32_t cy = random.nextRangeU(0, 64 - h);
+            bitmap.erase(color, SkIRect::MakeXYWH(cx, cy, w, h));
+            canvas->drawBitmap(bitmap, x, y);
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_allocPixels.cpp b/docs/examples/Bitmap_allocPixels.cpp
new file mode 100644
index 0000000..806edd7
--- /dev/null
+++ b/docs/examples/Bitmap_allocPixels.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=555c0f62f96602a9dcd459badcd005e0
+REG_FIDDLE(Bitmap_allocPixels, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    SkImageInfo info = SkImageInfo::Make(256, 64, kGray_8_SkColorType, kOpaque_SkAlphaType);
+    bitmap.allocPixels(info, info.width() * info.bytesPerPixel() + 64);
+    SkCanvas offscreen(bitmap);
+    offscreen.scale(.5f, .5f);
+    for (int y : { 0, 64, 128, 192 } ) {
+        offscreen.drawBitmap(source, 0, -y);
+        canvas->drawBitmap(bitmap, 0, y);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_allocPixelsFlags.cpp b/docs/examples/Bitmap_allocPixelsFlags.cpp
new file mode 100644
index 0000000..1cdc39b
--- /dev/null
+++ b/docs/examples/Bitmap_allocPixelsFlags.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=737e721c7d9e0f367d25521a1b823b9d
+REG_FIDDLE(Bitmap_allocPixelsFlags, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.allocPixelsFlags(SkImageInfo::MakeN32(44, 16, kPremul_SkAlphaType),
+                            SkBitmap::kZeroPixels_AllocFlag);
+    SkCanvas offscreen(bitmap);
+    SkPaint paint;
+    SkFont font;
+    offscreen.drawString("!@#$%", 0, 12, font, paint);
+    canvas->scale(6, 6);
+    canvas->drawBitmap(bitmap, 0, 0);
+    canvas->drawBitmap(bitmap, 8, 8);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_allocPixels_2.cpp b/docs/examples/Bitmap_allocPixels_2.cpp
new file mode 100644
index 0000000..947774a
--- /dev/null
+++ b/docs/examples/Bitmap_allocPixels_2.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=91f474a11a2112cd5c88c40a9015048d
+REG_FIDDLE(Bitmap_allocPixels_2, 256, 256, false, 4) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::Make(64, 64, kGray_8_SkColorType, kOpaque_SkAlphaType));
+    SkCanvas offscreen(bitmap);
+    offscreen.scale(.5f, .5f);
+    for (int y : { 0, 64, 128, 192 } ) {
+        offscreen.drawBitmap(source, -y, -y);
+        canvas->drawBitmap(bitmap, y, y);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_allocPixels_3.cpp b/docs/examples/Bitmap_allocPixels_3.cpp
new file mode 100644
index 0000000..eae1055
--- /dev/null
+++ b/docs/examples/Bitmap_allocPixels_3.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1219b38c788bf270fb20f8cd2d78cff8
+REG_FIDDLE(Bitmap_allocPixels_3, 256, 50, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint8_t set1[5] = { 0xCA, 0xDA, 0xCA, 0xC9, 0xA3 };
+    uint8_t set2[5] = { 0xAC, 0xA8, 0x89, 0x47, 0x87 };
+    SkBitmap bitmap;
+    bitmap.installPixels(SkImageInfo::Make(5, 1, kGray_8_SkColorType, kOpaque_SkAlphaType), set1, 5);
+    canvas->scale(10, 50);
+    canvas->drawBitmap(bitmap, 0, 0);
+    bitmap.allocPixels();
+    bitmap.eraseColor(SK_ColorBLACK);
+    canvas->drawBitmap(bitmap, 8, 0);
+    bitmap.setPixels(set2);
+    canvas->drawBitmap(bitmap, 16, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_allocPixels_4.cpp b/docs/examples/Bitmap_allocPixels_4.cpp
new file mode 100644
index 0000000..8ac116c
--- /dev/null
+++ b/docs/examples/Bitmap_allocPixels_4.cpp
@@ -0,0 +1,32 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1b2800d23c9ea249b45c2c21a34b6d14
+REG_FIDDLE(Bitmap_allocPixels_4, 256, 32, false, 0) {
+class TinyAllocator : public SkBitmap::Allocator {
+public:
+    bool allocPixelRef(SkBitmap* bitmap) override {
+        const SkImageInfo& info = bitmap->info();
+        if (info.height() * info.minRowBytes() > sizeof(storage)) {
+            return false;
+        }
+        sk_sp<SkPixelRef> pr = sk_sp<SkPixelRef>(
+                new SkPixelRef(info.width(), info.height(), storage, info.minRowBytes()));
+        bitmap->setPixelRef(std::move(pr), 0, 0);
+        return true;
+    }
+    char storage[16];
+};
+
+void draw(SkCanvas* canvas) {
+   TinyAllocator tinyAllocator;
+   SkBitmap bitmap;
+   bitmap.setInfo(SkImageInfo::MakeN32(2, 2, kOpaque_SkAlphaType));
+   if (bitmap.tryAllocPixels(&tinyAllocator)) {
+       bitmap.eraseColor(0xff55aa33);
+       bitmap.erase(0xffaa3355, SkIRect::MakeXYWH(1, 1, 1, 1));
+       canvas->scale(16, 16);
+       canvas->drawBitmap(bitmap, 0, 0);
+   }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_bounds.cpp b/docs/examples/Bitmap_bounds.cpp
new file mode 100644
index 0000000..9b66dde
--- /dev/null
+++ b/docs/examples/Bitmap_bounds.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3e9126152ff1cc592aef6facbcb5fc96
+REG_FIDDLE(Bitmap_bounds, 256, 64, false, 4) {
+void draw(SkCanvas* canvas) {
+    canvas->scale(.5f, .5f);
+    SkIRect bounds = source.bounds();
+    for (int x : { 0, bounds.width() } ) {
+        for (int y : { 0, bounds.height() } ) {
+            canvas->drawBitmap(source, x, y);
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_bytesPerPixel.cpp b/docs/examples/Bitmap_bytesPerPixel.cpp
new file mode 100644
index 0000000..a3663b1
--- /dev/null
+++ b/docs/examples/Bitmap_bytesPerPixel.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2a688e6f0a516c0d44a826381e9d637f
+REG_FIDDLE(Bitmap_bytesPerPixel, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
+                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16Norm",
+                            "RGBA_F16"};
+    SkImageInfo info = SkImageInfo::MakeA8(1, 1);
+    SkBitmap bitmap;
+    for (SkColorType colorType : {
+    kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType,
+    kARGB_4444_SkColorType, kRGBA_8888_SkColorType, kRGB_888x_SkColorType,
+    kBGRA_8888_SkColorType, kRGBA_1010102_SkColorType, kRGB_101010x_SkColorType,
+    kGray_8_SkColorType, kRGBA_F16_SkColorType
+                                 } ) {
+        bitmap.setInfo(info.makeColorType(colorType));
+        SkDebugf("color: k" "%s" "_SkColorType" "%*s" "bytesPerPixel: %d\n",
+                colors[colorType], 13 - strlen(colors[colorType]), " ",
+                bitmap.bytesPerPixel());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_colorSpace.cpp b/docs/examples/Bitmap_colorSpace.cpp
new file mode 100644
index 0000000..632e50d
--- /dev/null
+++ b/docs/examples/Bitmap_colorSpace.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=817f95879fadba44baf87ea60e9b595a
+REG_FIDDLE(Bitmap_colorSpace, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.setInfo(SkImageInfo::MakeN32(16, 32, kPremul_SkAlphaType,
+            SkColorSpace::MakeSRGBLinear()));
+    SkColorSpace* colorSpace = bitmap.colorSpace();
+    SkDebugf("gammaCloseToSRGB: %s  gammaIsLinear: %s  isSRGB: %s\n",
+            colorSpace->gammaCloseToSRGB() ? "true" : "false",
+            colorSpace->gammaIsLinear() ? "true" : "false",
+            colorSpace->isSRGB() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_colorType.cpp b/docs/examples/Bitmap_colorType.cpp
new file mode 100644
index 0000000..cbc225f
--- /dev/null
+++ b/docs/examples/Bitmap_colorType.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ceb77fab7326b57822a147b04aa0960e
+REG_FIDDLE(Bitmap_colorType, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
+                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16Norm",
+                            "RGBA_F16"};
+    SkBitmap bitmap;
+    bitmap.setInfo(SkImageInfo::MakeA8(16, 32));
+    SkDebugf("color type: k" "%s" "_SkColorType\n", colors[bitmap.colorType()]);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_computeByteSize.cpp b/docs/examples/Bitmap_computeByteSize.cpp
new file mode 100644
index 0000000..84850bc
--- /dev/null
+++ b/docs/examples/Bitmap_computeByteSize.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=165c8f208829fc0908e8a50da60c0076
+REG_FIDDLE(Bitmap_computeByteSize, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    for (int width : { 1, 1000, 1000000 } ) {
+        for (int height: { 1, 1000, 1000000 } ) {
+            SkImageInfo imageInfo = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType);
+            bitmap.setInfo(imageInfo, width * 5);
+            SkDebugf("width: %7d height: %7d computeByteSize: %13lld\n", width, height,
+                     bitmap.computeByteSize());
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_copy_const_SkBitmap.cpp b/docs/examples/Bitmap_copy_const_SkBitmap.cpp
new file mode 100644
index 0000000..77350dc
--- /dev/null
+++ b/docs/examples/Bitmap_copy_const_SkBitmap.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bbbae7a181bfd128a4484e8e9f454db1
+REG_FIDDLE(Bitmap_copy_const_SkBitmap, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap original;
+    if (original.tryAllocPixels(
+            SkImageInfo::Make(25, 35, kRGBA_8888_SkColorType, kOpaque_SkAlphaType))) {
+        SkDebugf("original has pixels before copy: %s\n", original.getPixels() ? "true" : "false");
+        SkBitmap copy(original);
+        SkDebugf("original has pixels after copy: %s\n", original.getPixels() ? "true" : "false");
+        SkDebugf("copy has pixels: %s\n", copy.getPixels() ? "true" : "false");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_copy_operator.cpp b/docs/examples/Bitmap_copy_operator.cpp
new file mode 100644
index 0000000..c998dad
--- /dev/null
+++ b/docs/examples/Bitmap_copy_operator.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=45279c519ae808f78bd30e9d84bdfdde
+REG_FIDDLE(Bitmap_copy_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap original;
+    if (original.tryAllocPixels(
+            SkImageInfo::Make(25, 35, kRGBA_8888_SkColorType, kOpaque_SkAlphaType))) {
+        SkDebugf("original has pixels before copy: %s\n", original.getPixels() ? "true" : "false");
+        SkBitmap copy = original;
+        SkDebugf("original has pixels after copy: %s\n", original.getPixels() ? "true" : "false");
+        SkDebugf("copy has pixels: %s\n", copy.getPixels() ? "true" : "false");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_dimensions.cpp b/docs/examples/Bitmap_dimensions.cpp
new file mode 100644
index 0000000..2027a8d
--- /dev/null
+++ b/docs/examples/Bitmap_dimensions.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=647056bcc12c27fb4413f212f33a2898
+REG_FIDDLE(Bitmap_dimensions, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.setInfo(SkImageInfo::MakeN32(33, 55, kOpaque_SkAlphaType));
+    SkISize dimensions = bitmap.dimensions();
+    SkRect bounds;
+    bitmap.getBounds(&bounds);
+    SkRect dimensionsAsBounds = SkRect::Make(dimensions);
+    SkDebugf("dimensionsAsBounds %c= bounds\n", dimensionsAsBounds == bounds ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_drawsNothing.cpp b/docs/examples/Bitmap_drawsNothing.cpp
new file mode 100644
index 0000000..c113dcf
--- /dev/null
+++ b/docs/examples/Bitmap_drawsNothing.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=daacf43394ce4045a362a48b5774deed
+REG_FIDDLE(Bitmap_drawsNothing, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    for (int w : { 0, 8 } ) {
+        for (bool allocate : { false, true} ) {
+            bitmap.setInfo(SkImageInfo::MakeA8(w, 8));
+            allocate ? bitmap.allocPixels() : (void) 0 ;
+            SkDebugf("empty:%s isNull:%s drawsNothing:%s\n", bitmap.empty() ? "true " : "false",
+                     bitmap.isNull() ? "true " : "false", bitmap.drawsNothing() ? "true" : "false");
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_empty.cpp b/docs/examples/Bitmap_empty.cpp
new file mode 100644
index 0000000..6964d1d
--- /dev/null
+++ b/docs/examples/Bitmap_empty.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a3762c2722b56ba55e42689c527f146c
+REG_FIDDLE(Bitmap_empty, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    for (int width : { 0, 2 } ) {
+        for (int height : { 0, 2 } ) {
+             bitmap.setInfo(SkImageInfo::MakeA8(width, height));
+             SkDebugf("width: %d height: %d empty: %s\n", width, height,
+                      bitmap.empty() ? "true" : "false");
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_empty_constructor.cpp b/docs/examples/Bitmap_empty_constructor.cpp
new file mode 100644
index 0000000..c7a5c8c
--- /dev/null
+++ b/docs/examples/Bitmap_empty_constructor.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6739d14ec0d6a373f2fcadc6b3077fd4
+REG_FIDDLE(Bitmap_empty_constructor, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const char* alphas[] = {"Unknown", "Opaque", "Premul", "Unpremul"};
+    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
+                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16Norm",
+                            "RGBA_F16"};
+    SkBitmap bitmap;
+    for (int i = 0; i < 2; ++i) {
+       SkDebugf("width: %2d  height: %2d", bitmap.width(), bitmap.height());
+       SkDebugf("  color: k%s_SkColorType", colors[bitmap.colorType()]);
+       SkDebugf("  alpha: k%s_SkAlphaType\n", alphas[bitmap.alphaType()]);
+       bitmap.setInfo(SkImageInfo::Make(25, 35, kRGBA_8888_SkColorType, kOpaque_SkAlphaType),
+                      0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_erase.cpp b/docs/examples/Bitmap_erase.cpp
new file mode 100644
index 0000000..28a0abc2
--- /dev/null
+++ b/docs/examples/Bitmap_erase.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2c5c4230ccd2861a5d15b7cd2764ab6e
+REG_FIDDLE(Bitmap_erase, 256, 70, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32(2, 2, kPremul_SkAlphaType));
+    bitmap.erase(0x7fff7f3f, SkIRect::MakeWH(1, 1));
+    bitmap.erase(0x7f7f3fff, SkIRect::MakeXYWH(0, 1, 1, 1));
+    bitmap.erase(0x7f3fff7f, SkIRect::MakeXYWH(1, 0, 1, 1));
+    bitmap.erase(0x7f1fbf5f, SkIRect::MakeXYWH(1, 1, 1, 1));
+    canvas->scale(25, 25);
+    canvas->drawBitmap(bitmap, 0, 0);
+    canvas->drawBitmap(bitmap, .5f, .5f);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_eraseARGB.cpp b/docs/examples/Bitmap_eraseARGB.cpp
new file mode 100644
index 0000000..ac46cd4
--- /dev/null
+++ b/docs/examples/Bitmap_eraseARGB.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=67277b0a1003f340473a35982533561c
+REG_FIDDLE(Bitmap_eraseARGB, 256, 80, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32(1, 1, kPremul_SkAlphaType));
+    bitmap.eraseARGB(0x7f, 0xff, 0x7f, 0x3f);
+    canvas->scale(50, 50);
+    canvas->drawBitmap(bitmap, 0, 0);
+    canvas->drawBitmap(bitmap, .5f, .5f);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_eraseColor.cpp b/docs/examples/Bitmap_eraseColor.cpp
new file mode 100644
index 0000000..3d1462a
--- /dev/null
+++ b/docs/examples/Bitmap_eraseColor.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=418928dbfffa9eb00c8225530f44baf5
+REG_FIDDLE(Bitmap_eraseColor, 256, 20, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32(1, 1, kOpaque_SkAlphaType));
+    bitmap.eraseColor(SK_ColorRED);
+    canvas->scale(16, 16);
+    canvas->drawBitmap(bitmap, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_extractAlpha.cpp b/docs/examples/Bitmap_extractAlpha.cpp
new file mode 100644
index 0000000..805540a
--- /dev/null
+++ b/docs/examples/Bitmap_extractAlpha.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ab6577df079e6c70511cf2bfc6447b44
+REG_FIDDLE(Bitmap_extractAlpha, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap alpha, bitmap;
+    bitmap.allocN32Pixels(100, 100);
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(0);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setColor(SK_ColorBLUE);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(20);
+    offscreen.drawCircle(50, 50, 39, paint);
+    offscreen.flush();
+    bitmap.extractAlpha(&alpha);
+    paint.setColor(SK_ColorRED);
+    canvas->drawBitmap(bitmap, 0, 0, &paint);
+    canvas->drawBitmap(alpha, 100, 0, &paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_extractAlpha_2.cpp b/docs/examples/Bitmap_extractAlpha_2.cpp
new file mode 100644
index 0000000..d4a0459
--- /dev/null
+++ b/docs/examples/Bitmap_extractAlpha_2.cpp
@@ -0,0 +1,29 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=092739b4cd5d732a27c07ced8ef45f01
+REG_FIDDLE(Bitmap_extractAlpha_2, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    auto radiusToSigma = [](SkScalar radius) -> SkScalar {
+         static const SkScalar kBLUR_SIGMA_SCALE = 0.57735f;
+         return radius > 0 ? kBLUR_SIGMA_SCALE * radius + 0.5f : 0.0f;
+    };
+    SkBitmap alpha, bitmap;
+    bitmap.allocN32Pixels(100, 100);
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(0);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setColor(SK_ColorBLUE);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(20);
+    offscreen.drawCircle(50, 50, 39, paint);
+    offscreen.flush();
+    paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, radiusToSigma(25)));
+    SkIPoint offset;
+    bitmap.extractAlpha(&alpha, &paint, &offset);
+    paint.setColor(SK_ColorRED);
+    canvas->drawBitmap(bitmap, 0, -offset.fY, &paint);
+    canvas->drawBitmap(alpha, 100 + offset.fX, 0, &paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_extractAlpha_3.cpp b/docs/examples/Bitmap_extractAlpha_3.cpp
new file mode 100644
index 0000000..c6b1b61
--- /dev/null
+++ b/docs/examples/Bitmap_extractAlpha_3.cpp
@@ -0,0 +1,25 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=cd7543fa8c9f3cede46dc2d72eb8c4bd
+REG_FIDDLE(Bitmap_extractAlpha_3, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap alpha, bitmap;
+    bitmap.allocN32Pixels(100, 100);
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(0);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setColor(SK_ColorBLUE);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(20);
+    offscreen.drawCircle(50, 50, 39, paint);
+    offscreen.flush();
+    paint.setMaskFilter(SkMaskFilter::MakeBlur(kOuter_SkBlurStyle, 3));
+    SkIPoint offset;
+    bitmap.extractAlpha(&alpha, &paint, nullptr, &offset);
+    paint.setColor(SK_ColorRED);
+    canvas->drawBitmap(bitmap, 0, -offset.fY, &paint);
+    canvas->drawBitmap(alpha, 100 + offset.fX, 0, &paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_extractSubset.cpp b/docs/examples/Bitmap_extractSubset.cpp
new file mode 100644
index 0000000..70dd398
--- /dev/null
+++ b/docs/examples/Bitmap_extractSubset.cpp
@@ -0,0 +1,25 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=304148c50c91490bfd58e9222342419c
+REG_FIDDLE(Bitmap_extractSubset, 256, 256, true, 3) {
+void draw(SkCanvas* canvas) {
+    SkIRect bounds, s;
+    source.getBounds(&bounds);
+    SkDebugf("bounds: %d, %d, %d, %d\n", bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
+    SkBitmap subset;
+    for (int left: { -100, 0, 100, 1000 } ) {
+         for (int right: { 0, 100, 1000 } ) {
+             SkIRect b = SkIRect::MakeLTRB(left, 100, right, 200);
+             bool success = source.extractSubset(&subset, b);
+             SkDebugf("subset: %4d, %4d, %4d, %4d  ", b.fLeft, b.fTop, b.fRight, b.fBottom);
+             SkDebugf("success; %s", success ? "true" : "false");
+             if (success) {
+                 subset.getBounds(&s);
+                 SkDebugf("  subset: %d, %d, %d, %d", s.fLeft, s.fTop, s.fRight, s.fBottom);
+             }
+             SkDebugf("\n");
+         }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_getAddr.cpp b/docs/examples/Bitmap_getAddr.cpp
new file mode 100644
index 0000000..1443209
--- /dev/null
+++ b/docs/examples/Bitmap_getAddr.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ffcefb2344cd38c3b99f69cfe6d64a17
+REG_FIDDLE(Bitmap_getAddr, 256, 256, true, 3) {
+void draw(SkCanvas* canvas) {
+    char* row0 = (char* ) source.getAddr(0, 0);
+    char* row1 = (char* ) source.getAddr(0, 1);
+    SkDebugf("addr interval %c= rowBytes\n",
+             (size_t) (row1 - row0) == source.rowBytes() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_getAddr16.cpp b/docs/examples/Bitmap_getAddr16.cpp
new file mode 100644
index 0000000..207d0a1
--- /dev/null
+++ b/docs/examples/Bitmap_getAddr16.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=53e00899ef2e00e2096daf7a07d9b059
+REG_FIDDLE(Bitmap_getAddr16, 256, 256, true, 3) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap16;
+    SkImageInfo dstInfo = SkImageInfo::Make(source.width(), source.height(), kARGB_4444_SkColorType,
+                     kPremul_SkAlphaType);
+    bitmap16.allocPixels(dstInfo);
+    if (source.readPixels(dstInfo, bitmap16.getPixels(), bitmap16.rowBytes(), 0, 0)) {
+        uint16_t* row0 = bitmap16.getAddr16(0, 0);
+        uint16_t* row1 = bitmap16.getAddr16(0, 1);
+        size_t interval = (row1 - row0) * bitmap16.bytesPerPixel();
+        SkDebugf("addr interval %c= rowBytes\n", interval == bitmap16.rowBytes() ? '=' : '!');
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_getAddr32.cpp b/docs/examples/Bitmap_getAddr32.cpp
new file mode 100644
index 0000000..77d64dc
--- /dev/null
+++ b/docs/examples/Bitmap_getAddr32.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=837a2bcc9fb9ce617a3420956cefc64a
+REG_FIDDLE(Bitmap_getAddr32, 256, 256, true, 3) {
+void draw(SkCanvas* canvas) {
+    uint32_t* row0 = source.getAddr32(0, 0);
+    uint32_t* row1 = source.getAddr32(0, 1);
+    size_t interval = (row1 - row0) * source.bytesPerPixel();
+    SkDebugf("addr interval %c= rowBytes\n", interval == source.rowBytes() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_getAddr8.cpp b/docs/examples/Bitmap_getAddr8.cpp
new file mode 100644
index 0000000..c1b188c
--- /dev/null
+++ b/docs/examples/Bitmap_getAddr8.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=cb9a08e8ff779b6a1cf8bb54f3883aaf
+REG_FIDDLE(Bitmap_getAddr8, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    const int width = 8;
+    const int height = 8;
+    uint8_t pixels[height][width];
+    SkImageInfo info = SkImageInfo::Make(width, height, kGray_8_SkColorType, kOpaque_SkAlphaType);
+    if (bitmap.installPixels(info, pixels, info.minRowBytes())) {
+        SkDebugf("&pixels[4][2] %c= bitmap.getAddr8(2, 4)\n",
+                  &pixels[4][2]  == bitmap.getAddr8(2, 4) ? '=' : '!');
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_getBounds.cpp b/docs/examples/Bitmap_getBounds.cpp
new file mode 100644
index 0000000..4f37db7
--- /dev/null
+++ b/docs/examples/Bitmap_getBounds.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2431ebc7e7d1e91e6d9daafd0f7a478f
+REG_FIDDLE(Bitmap_getBounds, 256, 160, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkRect bounds;
+    source.getBounds(&bounds);
+    bounds.offset(100, 100);
+    SkPaint paint;
+    paint.setColor(SK_ColorGRAY);
+    canvas->scale(.25f, .25f);
+    canvas->drawRect(bounds, paint);
+    canvas->drawBitmap(source, 40, 40);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_getBounds_2.cpp b/docs/examples/Bitmap_getBounds_2.cpp
new file mode 100644
index 0000000..e66603d
--- /dev/null
+++ b/docs/examples/Bitmap_getBounds_2.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0c45da35172bc0a529b2faecddae62a2
+REG_FIDDLE(Bitmap_getBounds_2, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkIRect bounds;
+    source.getBounds(&bounds);
+    bounds.inset(100, 100);
+    SkBitmap bitmap;
+    source.extractSubset(&bitmap, bounds);
+    canvas->scale(.5f, .5f);
+    canvas->drawBitmap(bitmap, 10, 10);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_getColor.cpp b/docs/examples/Bitmap_getColor.cpp
new file mode 100644
index 0000000..2ae727b
--- /dev/null
+++ b/docs/examples/Bitmap_getColor.cpp
@@ -0,0 +1,33 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=193d1f6d8a43b7a8e9f27ba21de38617
+REG_FIDDLE(Bitmap_getColor, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int w = 4;
+    const int h = 4;
+    SkColor colors[][w] = {
+        { 0x00000000, 0x2a0e002a, 0x55380055, 0x7f7f007f },
+        { 0x2a000e2a, 0x551c1c55, 0x7f542a7f, 0xaaaa38aa },
+        { 0x55003855, 0x7f2a547f, 0xaa7171aa, 0xd4d48dd4 },
+        { 0x7f007f7f, 0xaa38aaaa, 0xd48dd4d4, 0xffffffff }
+    };
+    SkDebugf("Premultiplied:\n");
+    for (int y = 0; y < h; ++y) {
+        SkDebugf("(0, %d) ", y);
+        for (int x = 0; x < w; ++x) {
+            SkDebugf("0x%08x%c", colors[y][x], x == w - 1 ? '\n' : ' ');
+        }
+    }
+    SkPixmap pixmap(SkImageInfo::MakeN32(w, h, kPremul_SkAlphaType), colors, w * 4);
+    SkBitmap bitmap;
+    bitmap.installPixels(pixmap);
+    SkDebugf("Unpremultiplied:\n");
+    for (int y = 0; y < h; ++y) {
+        SkDebugf("(0, %d) ", y);
+        for (int x = 0; x < w; ++x) {
+            SkDebugf("0x%08x%c", bitmap.getColor(x, y), x == w - 1 ? '\n' : ' ');
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_getGenerationID.cpp b/docs/examples/Bitmap_getGenerationID.cpp
new file mode 100644
index 0000000..674a352
--- /dev/null
+++ b/docs/examples/Bitmap_getGenerationID.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=db9dd91e0207c3941c09538555817b4b
+REG_FIDDLE(Bitmap_getGenerationID, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    SkDebugf("empty id %u\n", bitmap.getGenerationID());
+    bitmap.allocPixels(SkImageInfo::MakeN32(64, 64, kOpaque_SkAlphaType));
+    SkDebugf("alloc id %u\n", bitmap.getGenerationID());
+    bitmap.eraseColor(SK_ColorRED);
+    SkDebugf("erase id %u\n", bitmap.getGenerationID());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_getPixels.cpp b/docs/examples/Bitmap_getPixels.cpp
new file mode 100644
index 0000000..25c041b
--- /dev/null
+++ b/docs/examples/Bitmap_getPixels.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e006bb05cf74ec8d2b3d6adeb5dba11b
+REG_FIDDLE(Bitmap_getPixels, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.setInfo(SkImageInfo::MakeN32(4, 4, kPremul_SkAlphaType));
+    bitmap.allocPixels();
+    bitmap.eraseColor(0x00000000);
+    void* baseAddr = bitmap.getPixels();
+    *(SkPMColor*)baseAddr = 0xFFFFFFFF;
+    SkDebugf("bitmap.getColor(0, 1) %c= 0x00000000\n",
+              bitmap.getColor(0, 1)  == 0x00000000 ? '=' : '!');
+    SkDebugf("bitmap.getColor(0, 0) %c= 0xFFFFFFFF\n",
+              bitmap.getColor(0, 0)  == 0xFFFFFFFF ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_getSubset.cpp b/docs/examples/Bitmap_getSubset.cpp
new file mode 100644
index 0000000..18a69b1
--- /dev/null
+++ b/docs/examples/Bitmap_getSubset.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d6dd0b425aa550f21b938a18c2e1a981
+REG_FIDDLE(Bitmap_getSubset, 256, 256, true, 3) {
+void draw(SkCanvas* canvas) {
+    SkIRect bounds;
+    source.getBounds(&bounds);
+    bounds.inset(100, 100);
+    SkBitmap subset;
+    source.extractSubset(&subset, bounds);
+    SkIRect r = source.getSubset();
+    SkDebugf("source: %d, %d, %d, %d\n", r.fLeft, r.fTop, r.fRight, r.fBottom);
+    r = subset.getSubset();
+    SkDebugf("subset: %d, %d, %d, %d\n", r.fLeft, r.fTop, r.fRight, r.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_height.cpp b/docs/examples/Bitmap_height.cpp
new file mode 100644
index 0000000..69aef2f
--- /dev/null
+++ b/docs/examples/Bitmap_height.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c79a196278c58b34cd5f551b0124ecc9
+REG_FIDDLE(Bitmap_height, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo info = SkImageInfo::MakeA8(16, 32);
+    SkBitmap bitmap;
+    bitmap.setInfo(info);
+    SkDebugf("bitmap height: %d  info height: %d\n", bitmap.height(), info.height());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_info.cpp b/docs/examples/Bitmap_info.cpp
new file mode 100644
index 0000000..b792f16
--- /dev/null
+++ b/docs/examples/Bitmap_info.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ec47c4dc23e2925ad565eaba55a91553
+REG_FIDDLE(Bitmap_info, 256, 256, true, 4) {
+void draw(SkCanvas* canvas) {
+    // SkBitmap source;  // pre-populated with soccer ball by fiddle.skia.org
+    const SkImageInfo& info = source.info();
+    const char* alphas[] = {"Unknown", "Opaque", "Premul", "Unpremul"};
+    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
+                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16Norm",
+                            "RGBA_F16"};
+    SkDebugf("width: %d height: %d color: %s alpha: %s\n", info.width(), info.height(),
+                colors[info.colorType()], alphas[info.alphaType()]);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_installPixels.cpp b/docs/examples/Bitmap_installPixels.cpp
new file mode 100644
index 0000000..2cb5bca
--- /dev/null
+++ b/docs/examples/Bitmap_installPixels.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8c4f7bf73fffa1a812ee8e88e44e639c
+REG_FIDDLE(Bitmap_installPixels, 256, 256, true, 0) {
+static void releaseProc(void* addr, void* ) {
+    SkDebugf("releaseProc called\n");
+    delete[] (uint32_t*) addr;
+}
+
+void draw(SkCanvas* canvas) {
+   SkBitmap bitmap;
+   void* pixels = new uint32_t[8 * 8];
+   SkImageInfo info = SkImageInfo::MakeN32(8, 8, kOpaque_SkAlphaType);
+   SkDebugf("before installPixels\n");
+   bool installed = bitmap.installPixels(info, pixels, 16, releaseProc, nullptr);
+   SkDebugf("install " "%s" "successful\n", installed ? "" : "not ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_installPixels_2.cpp b/docs/examples/Bitmap_installPixels_2.cpp
new file mode 100644
index 0000000..21e3d53
--- /dev/null
+++ b/docs/examples/Bitmap_installPixels_2.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a7e04447b2081010c50d7920e80a6bb2
+REG_FIDDLE(Bitmap_installPixels_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+   SkRandom random;
+   SkBitmap bitmap;
+   const int width = 8;
+   const int height = 8;
+   uint32_t pixels[width * height];
+   for (unsigned x = 0; x < width * height; ++x) {
+       pixels[x] = random.nextU();
+   }
+   SkImageInfo info = SkImageInfo::MakeN32(width, height, kUnpremul_SkAlphaType);
+   if (bitmap.installPixels(info, pixels, info.minRowBytes())) {
+       canvas->scale(32, 32);
+       canvas->drawBitmap(bitmap, 0, 0);
+   }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_installPixels_3.cpp b/docs/examples/Bitmap_installPixels_3.cpp
new file mode 100644
index 0000000..6ae41d0
--- /dev/null
+++ b/docs/examples/Bitmap_installPixels_3.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6e2a8c9358b34aebd2ec586815fe9d3a
+REG_FIDDLE(Bitmap_installPixels_3, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint8_t storage[][5] = {{ 0xCA, 0xDA, 0xCA, 0xC9, 0xA3 },
+                            { 0xAC, 0xA8, 0x89, 0x47, 0x87 },
+                            { 0x4B, 0x25, 0x25, 0x25, 0x46 },
+                            { 0x90, 0x81, 0x25, 0x41, 0x33 },
+                            { 0x75, 0x55, 0x44, 0x20, 0x00 }};
+    SkImageInfo imageInfo = SkImageInfo::Make(5, 5, kGray_8_SkColorType, kOpaque_SkAlphaType);
+    SkPixmap pixmap(imageInfo, storage[0], sizeof(storage) / 5);
+    SkBitmap bitmap;
+    bitmap.installPixels(pixmap);
+    canvas->scale(10, 10);
+    canvas->drawBitmap(bitmap, 0, 0);
+    *pixmap.writable_addr8(2, 2) = 0xFF;
+    bitmap.installPixels(pixmap);
+    canvas->drawBitmap(bitmap, 10, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_isImmutable.cpp b/docs/examples/Bitmap_isImmutable.cpp
new file mode 100644
index 0000000..2b238c9
--- /dev/null
+++ b/docs/examples/Bitmap_isImmutable.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=db61fdcd382342ee88ea1b4f27c27b95
+REG_FIDDLE(Bitmap_isImmutable, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap original;
+    SkImageInfo info = SkImageInfo::Make(25, 35, kRGBA_8888_SkColorType, kOpaque_SkAlphaType);
+    if (original.tryAllocPixels(info)) {
+        original.setImmutable();
+        SkBitmap copy;
+        original.extractSubset(&copy, {5, 10, 15, 20});
+        SkDebugf("original is " "%s" "immutable\n", original.isImmutable() ? "" : "not ");
+        SkDebugf("copy is " "%s" "immutable\n", copy.isImmutable() ? "" : "not ");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_isNull.cpp b/docs/examples/Bitmap_isNull.cpp
new file mode 100644
index 0000000..9d6c3eb
--- /dev/null
+++ b/docs/examples/Bitmap_isNull.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=211ec89418011aa6e54aa2cc9567e003
+REG_FIDDLE(Bitmap_isNull, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    SkDebugf("empty bitmap does %shave pixels\n", bitmap.isNull() ? "not " : "");
+    bitmap.setInfo(SkImageInfo::MakeA8(8, 8));
+    SkDebugf("bitmap with dimensions does %shave pixels\n", bitmap.isNull() ? "not " : "");
+    bitmap.allocPixels();
+    SkDebugf("allocated bitmap does %shave pixels\n", bitmap.isNull() ? "not " : "");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_isOpaque.cpp b/docs/examples/Bitmap_isOpaque.cpp
new file mode 100644
index 0000000..796937f
--- /dev/null
+++ b/docs/examples/Bitmap_isOpaque.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5e76b68bb46d54315eb0c12d83bd6949
+REG_FIDDLE(Bitmap_isOpaque, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int height = 2;
+    const int width = 2;
+    SkBitmap bitmap;
+    bitmap.setInfo(SkImageInfo::Make(width, height, kN32_SkColorType, kPremul_SkAlphaType));
+    for (int index = 0; index < 2; ++index) {
+        bitmap.allocPixels();
+        bitmap.eraseColor(0x00000000);
+        SkDebugf("isOpaque: %s\n", bitmap.isOpaque() ? "true" : "false");
+        bitmap.eraseColor(0xFFFFFFFF);
+        SkDebugf("isOpaque: %s\n", bitmap.isOpaque() ? "true" : "false");
+        bitmap.setInfo(bitmap.info().makeAlphaType(kOpaque_SkAlphaType));
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_isVolatile.cpp b/docs/examples/Bitmap_isVolatile.cpp
new file mode 100644
index 0000000..7f2a2ed
--- /dev/null
+++ b/docs/examples/Bitmap_isVolatile.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=23c4543ac6cdd0e8fe762816a0dc2e03
+REG_FIDDLE(Bitmap_isVolatile, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap original;
+    SkImageInfo info = SkImageInfo::Make(25, 35, kRGBA_8888_SkColorType, kOpaque_SkAlphaType);
+    if (original.tryAllocPixels(info)) {
+        original.setIsVolatile(true);
+        SkBitmap copy;
+        original.extractSubset(&copy, {5, 10, 15, 20});
+        SkDebugf("original is " "%s" "volatile\n", original.isVolatile() ? "" : "not ");
+        SkDebugf("copy is " "%s" "volatile\n", copy.isImmutable() ? "" : "not ");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_move_SkBitmap.cpp b/docs/examples/Bitmap_move_SkBitmap.cpp
new file mode 100644
index 0000000..2362eef
--- /dev/null
+++ b/docs/examples/Bitmap_move_SkBitmap.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=40afd4f1fa69e02d69d92b38252088ef
+REG_FIDDLE(Bitmap_move_SkBitmap, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap original;
+    if (original.tryAllocPixels(
+            SkImageInfo::Make(25, 35, kRGBA_8888_SkColorType, kOpaque_SkAlphaType))) {
+        SkDebugf("original has pixels before move: %s\n", original.getPixels() ? "true" : "false");
+        SkBitmap copy(std::move(original));
+        // NOLINTNEXTLINE(bugprone-use-after-move)
+        SkDebugf("original has pixels after move: %s\n", original.getPixels() ? "true" : "false");
+        SkDebugf("copy has pixels: %s\n", copy.getPixels() ? "true" : "false");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_move_operator.cpp b/docs/examples/Bitmap_move_operator.cpp
new file mode 100644
index 0000000..0a12712
--- /dev/null
+++ b/docs/examples/Bitmap_move_operator.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=35ea3fed27d8db22dc00f48670de64de
+REG_FIDDLE(Bitmap_move_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap original;
+    if (original.tryAllocPixels(
+            SkImageInfo::Make(25, 35, kRGBA_8888_SkColorType, kOpaque_SkAlphaType))) {
+        SkDebugf("original has pixels before move: %s\n", original.getPixels() ? "true" : "false");
+        SkBitmap copy = std::move(original);
+        // NOLINTNEXTLINE(bugprone-use-after-move)
+        SkDebugf("original has pixels after move: %s\n", original.getPixels() ? "true" : "false");
+        SkDebugf("copy has pixels: %s\n", copy.getPixels() ? "true" : "false");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_notifyPixelsChanged.cpp b/docs/examples/Bitmap_notifyPixelsChanged.cpp
new file mode 100644
index 0000000..c70a567
--- /dev/null
+++ b/docs/examples/Bitmap_notifyPixelsChanged.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8f463ed17b0ed4fb9c503a0ec71706f9
+REG_FIDDLE(Bitmap_notifyPixelsChanged, 256, 20, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.setInfo(SkImageInfo::Make(1, 1, kRGBA_8888_SkColorType, kOpaque_SkAlphaType));
+    bitmap.allocPixels();
+    bitmap.eraseColor(SK_ColorRED);
+    canvas->scale(16, 16);
+    canvas->drawBitmap(bitmap, 0, 0);
+    *(SkPMColor*) bitmap.getPixels() = SkPreMultiplyColor(SK_ColorBLUE);
+    canvas->drawBitmap(bitmap, 2, 0);
+    bitmap.notifyPixelsChanged();
+    *(SkPMColor*) bitmap.getPixels() = SkPreMultiplyColor(SK_ColorGREEN);
+    canvas->drawBitmap(bitmap, 4, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_peekPixels.cpp b/docs/examples/Bitmap_peekPixels.cpp
new file mode 100644
index 0000000..957e9b9
--- /dev/null
+++ b/docs/examples/Bitmap_peekPixels.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0cc2c6a0dffa61a88711534bd3d43b40
+REG_FIDDLE(Bitmap_peekPixels, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(6, 11));
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(SK_ColorWHITE);
+    SkPaint paint;
+    SkFont font;
+    offscreen.drawString("?", 0, 10, font, paint);
+    SkPixmap pixmap;
+    if (bitmap.peekPixels(&pixmap)) {
+        const SkPMColor* pixels = pixmap.addr32();
+        SkPMColor pmWhite = pixels[0];
+        for (int y = 0; y < bitmap.height(); ++y) {
+            for (int x = 0; x < bitmap.width(); ++x) {
+                SkDebugf("%c", *pixels++ == pmWhite ? '-' : 'x');
+            }
+            SkDebugf("\n");
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_pixelRef.cpp b/docs/examples/Bitmap_pixelRef.cpp
new file mode 100644
index 0000000..cdf53a3
--- /dev/null
+++ b/docs/examples/Bitmap_pixelRef.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5db2d30870a7cc45f28e22578d1880c3
+REG_FIDDLE(Bitmap_pixelRef, 256, 256, true, 3) {
+void draw(SkCanvas* canvas) {
+    SkBitmap subset;
+    source.extractSubset(&subset, SkIRect::MakeXYWH(32, 64, 128, 256));
+    SkDebugf("src ref %c= sub ref\n", source.pixelRef() == subset.pixelRef() ? '=' : '!');
+    SkDebugf("src pixels %c= sub pixels\n", source.getPixels() == subset.getPixels() ? '=' : '!');
+    SkDebugf("src addr %c= sub addr\n", source.getAddr(32, 64) == subset.getAddr(0, 0) ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_pixelRefOrigin.cpp b/docs/examples/Bitmap_pixelRefOrigin.cpp
new file mode 100644
index 0000000..ff0d154
--- /dev/null
+++ b/docs/examples/Bitmap_pixelRefOrigin.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6d31686c6c0829c70f284ae716526d6a
+REG_FIDDLE(Bitmap_pixelRefOrigin, 256, 256, true, 3) {
+void draw(SkCanvas* canvas) {
+    SkBitmap subset;
+    source.extractSubset(&subset, SkIRect::MakeXYWH(32, 64, 128, 256));
+    SkIPoint sourceOrigin = source.pixelRefOrigin();
+    SkIPoint subsetOrigin = subset.pixelRefOrigin();
+    SkDebugf("source origin: %d, %d\n", sourceOrigin.fX, sourceOrigin.fY);
+    SkDebugf("subset origin: %d, %d\n", subsetOrigin.fX, subsetOrigin.fY);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_pixmap.cpp b/docs/examples/Bitmap_pixmap.cpp
new file mode 100644
index 0000000..b9e113c
--- /dev/null
+++ b/docs/examples/Bitmap_pixmap.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7f972d742dd78d2500034d8867e9ef2f
+REG_FIDDLE(Bitmap_pixmap, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(10, 11));
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(SK_ColorWHITE);
+    SkPaint paint;
+    SkFont font;
+    font.setEdging(SkFont::Edging::kAlias);
+    offscreen.drawString("&", 0, 10, font, paint);
+    const SkPixmap& pixmap = bitmap.pixmap();
+    if (pixmap.addr()) {
+        SkPMColor pmWhite = *pixmap.addr32(0, 0);
+        for (int y = 0; y < pixmap.height(); ++y) {
+            for (int x = 0; x < pixmap.width(); ++x) {
+                SkDebugf("%c", *pixmap.addr32(x, y) == pmWhite ? '-' : 'x');
+            }
+            SkDebugf("\n");
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_readPixels.cpp b/docs/examples/Bitmap_readPixels.cpp
new file mode 100644
index 0000000..98dd888
--- /dev/null
+++ b/docs/examples/Bitmap_readPixels.cpp
@@ -0,0 +1,28 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b2cbbbbcffb618865d8aae3bc04b2a62
+REG_FIDDLE(Bitmap_readPixels, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    const int width = 256;
+    const int height = 64;
+    SkImageInfo srcInfo = SkImageInfo::MakeN32Premul(width, height);
+    SkColor  gradColors[] = { 0xFFAA3300, 0x7F881122 };
+    SkPoint  gradPoints[] = { { 0, 0 }, { 256, 0 } };
+    SkPaint paint;
+    paint.setShader(SkGradientShader::MakeLinear(gradPoints, gradColors, nullptr,
+                    SK_ARRAY_COUNT(gradColors), SkTileMode::kClamp));
+    SkBitmap bitmap;
+    bitmap.allocPixels(srcInfo);
+    SkCanvas srcCanvas(bitmap);
+    srcCanvas.drawRect(SkRect::MakeWH(width, height), paint);
+    canvas->drawBitmap(bitmap, 0, 0);
+    SkImageInfo dstInfo = srcInfo.makeColorType(kARGB_4444_SkColorType);
+    std::vector<int16_t> dstPixels;
+    dstPixels.resize(height * width);
+    bitmap.readPixels(dstInfo, &dstPixels.front(), width * 2, 0, 0);
+    SkPixmap dstPixmap(dstInfo, &dstPixels.front(), width * 2);
+    bitmap.installPixels(dstPixmap);
+    canvas->drawBitmap(bitmap, 0, 64);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_readPixels_2.cpp b/docs/examples/Bitmap_readPixels_2.cpp
new file mode 100644
index 0000000..b40ce0d
--- /dev/null
+++ b/docs/examples/Bitmap_readPixels_2.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e9f70cbc9827097449a386ec7a8a8188
+REG_FIDDLE(Bitmap_readPixels_2, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    std::vector<int32_t> srcPixels;
+    srcPixels.resize(source.height() * source.rowBytes());
+    for (int y = 0; y < 4; ++y) {
+        for (int x = 0; x < 4; ++x) {
+            SkPixmap pixmap(SkImageInfo::MakeN32Premul(source.width() / 4, source.height() / 4),
+                    &srcPixels.front() + x * source.height() * source.width() / 4 +
+                    y * source.width() / 4, source.rowBytes());
+            source.readPixels(pixmap, x * source.width() / 4, y * source.height() / 4);
+        }
+    }
+    canvas->scale(.5f, .5f);
+    SkBitmap bitmap;
+    bitmap.installPixels(SkImageInfo::MakeN32Premul(source.width(), source.height()),
+                             &srcPixels.front(), source.rowBytes());
+    canvas->drawBitmap(bitmap, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_readPixels_3.cpp b/docs/examples/Bitmap_readPixels_3.cpp
new file mode 100644
index 0000000..a284926
--- /dev/null
+++ b/docs/examples/Bitmap_readPixels_3.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4590fbf052659d6e629fbfd827081ae5
+REG_FIDDLE(Bitmap_readPixels_3, 256, 128, false, 3) {
+void draw(SkCanvas* canvas) {
+    std::vector<int32_t> srcPixels;
+    srcPixels.resize(source.height() * source.width() * 8);
+    for (int i = 0;  i < 2; ++i) {
+    SkPixmap pixmap(SkImageInfo::Make(source.width() * 2, source.height(),
+                    i ? kRGBA_8888_SkColorType : kBGRA_8888_SkColorType, kPremul_SkAlphaType),
+                    &srcPixels.front() + i * source.width(), source.rowBytes() * 2);
+        source.readPixels(pixmap);
+    }
+    canvas->scale(.25f, .25f);
+    SkBitmap bitmap;
+    bitmap.installPixels(SkImageInfo::MakeN32Premul(source.width() * 2, source.height()),
+                         &srcPixels.front(), source.rowBytes() * 2);
+    canvas->drawBitmap(bitmap, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_readyToDraw.cpp b/docs/examples/Bitmap_readyToDraw.cpp
new file mode 100644
index 0000000..04277d0
--- /dev/null
+++ b/docs/examples/Bitmap_readyToDraw.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e89c78ca992e2e789ed50944fe68f920
+REG_FIDDLE(Bitmap_readyToDraw, 256, 160, false, 5) {
+void draw(SkCanvas* canvas) {
+    if (source.readyToDraw()) {
+        canvas->drawBitmap(source, 10, 10);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_refColorSpace.cpp b/docs/examples/Bitmap_refColorSpace.cpp
new file mode 100644
index 0000000..930dd48
--- /dev/null
+++ b/docs/examples/Bitmap_refColorSpace.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=cb028b7931da85b949ad0953b9711f9f
+REG_FIDDLE(Bitmap_refColorSpace, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap1, bitmap2;
+    bitmap1.setInfo(SkImageInfo::MakeN32(16, 32, kPremul_SkAlphaType,
+            SkColorSpace::MakeSRGBLinear()));
+    bitmap2.setInfo(SkImageInfo::MakeN32(16, 32, kPremul_SkAlphaType,
+            bitmap1.refColorSpace()));
+    SkColorSpace* colorSpace = bitmap2.colorSpace();
+    SkDebugf("gammaCloseToSRGB: %s  gammaIsLinear: %s  isSRGB: %s\n",
+            colorSpace->gammaCloseToSRGB() ? "true" : "false",
+            colorSpace->gammaIsLinear() ? "true" : "false",
+            colorSpace->isSRGB() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_reset.cpp b/docs/examples/Bitmap_reset.cpp
new file mode 100644
index 0000000..623cd26
--- /dev/null
+++ b/docs/examples/Bitmap_reset.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=52ccaeda67924373c5b55a2b89fe314d
+REG_FIDDLE(Bitmap_reset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.setInfo(SkImageInfo::Make(1, 1, kRGBA_8888_SkColorType, kOpaque_SkAlphaType));
+    bitmap.allocPixels();
+    SkDebugf("width:%d height:%d isNull:%s\n", bitmap.width(), bitmap.height(),
+             bitmap.isNull() ? "true" : "false");
+    bitmap.reset();
+    SkDebugf("width:%d height:%d isNull:%s\n", bitmap.width(), bitmap.height(),
+             bitmap.isNull() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_rowBytes.cpp b/docs/examples/Bitmap_rowBytes.cpp
new file mode 100644
index 0000000..2ac1b5c
--- /dev/null
+++ b/docs/examples/Bitmap_rowBytes.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a654fd0b73f424859ae6c95e03f55099
+REG_FIDDLE(Bitmap_rowBytes, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    for (int rowBytes : { 2, 8 } ) {
+        bool result = bitmap.setInfo(SkImageInfo::MakeA8(4, 4), rowBytes);
+        SkDebugf("setInfo returned:%s rowBytes:%d\n", result ? "true " : "false", bitmap.rowBytes());
+     }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_rowBytesAsPixels.cpp b/docs/examples/Bitmap_rowBytesAsPixels.cpp
new file mode 100644
index 0000000..1c78217
--- /dev/null
+++ b/docs/examples/Bitmap_rowBytesAsPixels.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=03a9e08082a23a98de17c3e24871d61a
+REG_FIDDLE(Bitmap_rowBytesAsPixels, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    for (int rowBytes : { 4, 5, 6, 7, 8} ) {
+        bitmap.setInfo(SkImageInfo::MakeN32(1, 1, kPremul_SkAlphaType), rowBytes);
+        SkDebugf("rowBytes: %d rowBytesAsPixels: %d\n", rowBytes, bitmap.rowBytesAsPixels());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_setAlphaType.cpp b/docs/examples/Bitmap_setAlphaType.cpp
new file mode 100644
index 0000000..ff2b142
--- /dev/null
+++ b/docs/examples/Bitmap_setAlphaType.cpp
@@ -0,0 +1,34 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=af3adcbea7b58bf90298ca5e0ea93030
+REG_FIDDLE(Bitmap_setAlphaType, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const char* colors[] = { "Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
+                             "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16Norm",
+                             "RGBA_F16"};
+    const char* alphas[] = {"Unknown ", "Opaque  ", "Premul  ", "Unpremul"};
+    SkBitmap bitmap;
+    SkAlphaType alphaTypes[] = { kUnknown_SkAlphaType, kOpaque_SkAlphaType, kPremul_SkAlphaType,
+    kUnpremul_SkAlphaType
+                               };
+    SkDebugf("%18s%15s%17s%18s%19s\n", "Canonical", "Unknown", "Opaque", "Premul", "Unpremul");
+    for (SkColorType colorType : {
+    kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType,
+    kARGB_4444_SkColorType, kRGBA_8888_SkColorType, kRGB_888x_SkColorType,
+    kBGRA_8888_SkColorType, kRGBA_1010102_SkColorType, kRGB_101010x_SkColorType,
+    kGray_8_SkColorType, kRGBA_F16_SkColorType
+                                 } ) {
+        for (SkAlphaType canonicalAlphaType : alphaTypes) {
+            SkColorTypeValidateAlphaType(colorType, kUnknown_SkAlphaType, &canonicalAlphaType );
+            SkDebugf("%12s %9s  ", colors[(int) colorType], alphas[(int) canonicalAlphaType ]);
+            for (SkAlphaType alphaType : alphaTypes) {
+                bitmap.setInfo(SkImageInfo::Make(4, 4, colorType, canonicalAlphaType));
+                bool result = bitmap.setAlphaType(alphaType);
+                SkDebugf("%s %s    ", result ? "true " : "false", alphas[(int) bitmap.alphaType()]);
+            }
+            SkDebugf("\n");
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_setImmutable.cpp b/docs/examples/Bitmap_setImmutable.cpp
new file mode 100644
index 0000000..4bf741f
--- /dev/null
+++ b/docs/examples/Bitmap_setImmutable.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9210060d1f4ca46e1375496237902ef3
+REG_FIDDLE(Bitmap_setImmutable, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.setInfo(SkImageInfo::MakeN32(4, 4, kPremul_SkAlphaType));
+    bitmap.allocPixels();
+    SkCanvas offscreen(bitmap);
+    SkDebugf("draw white\n");
+    offscreen.clear(SK_ColorWHITE);
+    bitmap.setImmutable();
+    SkDebugf("draw black\n");
+    // Triggers assert if SK_DEBUG is true, runs fine otherwise.
+    // offscreen.clear(SK_ColorBLACK);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_setInfo.cpp b/docs/examples/Bitmap_setInfo.cpp
new file mode 100644
index 0000000..8ebae8f
--- /dev/null
+++ b/docs/examples/Bitmap_setInfo.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=599ab64d0aea005498176249bbfb64eb
+REG_FIDDLE(Bitmap_setInfo, 256, 96, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.setInfo(SkImageInfo::MakeN32(44, 16, kOpaque_SkAlphaType));
+    bitmap.allocPixels();
+    bitmap.eraseColor(SK_ColorGREEN);
+    SkCanvas offscreen(bitmap);
+    SkPaint paint;
+    SkFont font;
+    offscreen.drawString("!@#$%", 0, 12, font, paint);
+    canvas->scale(6, 6);
+    canvas->drawBitmap(bitmap, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_setIsVolatile.cpp b/docs/examples/Bitmap_setIsVolatile.cpp
new file mode 100644
index 0000000..0a9b9f6
--- /dev/null
+++ b/docs/examples/Bitmap_setIsVolatile.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e8627a4df659b896402f89a91326618f
+REG_FIDDLE(Bitmap_setIsVolatile, 256, 20, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.setInfo(SkImageInfo::Make(1, 1, kRGBA_8888_SkColorType, kOpaque_SkAlphaType));
+    bitmap.allocPixels();
+    bitmap.eraseColor(SK_ColorRED);
+    canvas->scale(16, 16);
+    canvas->drawBitmap(bitmap, 0, 0);
+    *(SkPMColor*) bitmap.getPixels() = SkPreMultiplyColor(SK_ColorBLUE);
+    canvas->drawBitmap(bitmap, 2, 0);
+    bitmap.setIsVolatile(true);
+    *(SkPMColor*) bitmap.getPixels() = SkPreMultiplyColor(SK_ColorGREEN);
+    canvas->drawBitmap(bitmap, 4, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_setPixelRef.cpp b/docs/examples/Bitmap_setPixelRef.cpp
new file mode 100644
index 0000000..a8fb7c6
--- /dev/null
+++ b/docs/examples/Bitmap_setPixelRef.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f98cc0451c6e77a8833d261c9a484c5f
+REG_FIDDLE(Bitmap_setPixelRef, 256, 140, false, 5) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.setInfo(SkImageInfo::Make(source.width() - 5, source.height() - 5,
+                   kGray_8_SkColorType, kOpaque_SkAlphaType), source.rowBytes());
+    bitmap.setPixelRef(sk_ref_sp(source.pixelRef()), 5, 5);
+    canvas->drawBitmap(bitmap, 10, 10);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_setPixels.cpp b/docs/examples/Bitmap_setPixels.cpp
new file mode 100644
index 0000000..5f72c28
--- /dev/null
+++ b/docs/examples/Bitmap_setPixels.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f0db16e06c9a1436917c8179f8c1718f
+REG_FIDDLE(Bitmap_setPixels, 256, 50, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint8_t set1[5] = { 0xCA, 0xDA, 0xCA, 0xC9, 0xA3 };
+    uint8_t set2[5] = { 0xAC, 0xA8, 0x89, 0x47, 0x87 };
+    SkBitmap bitmap;
+    bitmap.installPixels(SkImageInfo::Make(5, 1, kGray_8_SkColorType, kOpaque_SkAlphaType), set1, 5);
+    canvas->scale(10, 50);
+    canvas->drawBitmap(bitmap, 0, 0);
+    bitmap.setPixels(set2);
+    canvas->drawBitmap(bitmap, 10, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_shiftPerPixel.cpp b/docs/examples/Bitmap_shiftPerPixel.cpp
new file mode 100644
index 0000000..8c10e1b
--- /dev/null
+++ b/docs/examples/Bitmap_shiftPerPixel.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=56ede4b7d45c15d5936f81ac3d74f070
+REG_FIDDLE(Bitmap_shiftPerPixel, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
+                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16Norm",
+                            "RGBA_F16"};
+    SkImageInfo info = SkImageInfo::MakeA8(1, 1);
+    SkBitmap bitmap;
+    for (SkColorType colorType : {
+    kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType,
+    kARGB_4444_SkColorType, kRGBA_8888_SkColorType, kRGB_888x_SkColorType,
+    kBGRA_8888_SkColorType, kRGBA_1010102_SkColorType, kRGB_101010x_SkColorType,
+    kGray_8_SkColorType, kRGBA_F16_SkColorType
+                                 } ) {
+        bitmap.setInfo(info.makeColorType(colorType));
+        SkDebugf("color: k" "%s" "_SkColorType" "%*s" "shiftPerPixel: %d\n",
+                colors[colorType], 14 - strlen(colors[colorType]), " ",
+                bitmap.shiftPerPixel());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_swap.cpp b/docs/examples/Bitmap_swap.cpp
new file mode 100644
index 0000000..5ae88f2
--- /dev/null
+++ b/docs/examples/Bitmap_swap.cpp
@@ -0,0 +1,30 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=de9be45255e48fae445c916a41063abc
+REG_FIDDLE(Bitmap_swap, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkBitmap& b) -> void {
+        const char* alphas[] = {"Unknown", "Opaque", "Premul", "Unpremul"};
+        const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
+                                "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16Norm",
+                                "RGBA_F16"};
+        SkDebugf("%s width:%d height:%d colorType:k%s_SkColorType alphaType:k%s_SkAlphaType\n",
+                 prefix, b.width(), b.height(), colors[b.colorType()], alphas[b.alphaType()]);
+    };
+    SkBitmap one, two;
+    if (!one.tryAllocPixels(
+            SkImageInfo::Make(1, 1, kRGBA_8888_SkColorType, kOpaque_SkAlphaType))) {
+        return;
+    }
+    if (!two.tryAllocPixels(
+            SkImageInfo::Make(2, 2, kBGRA_8888_SkColorType, kPremul_SkAlphaType))) {
+        return;
+    }
+    for (int index = 0; index < 2; ++index) {
+       debugster("one", one);
+       debugster("two", two);
+       one.swap(two);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_tryAllocN32Pixels.cpp b/docs/examples/Bitmap_tryAllocN32Pixels.cpp
new file mode 100644
index 0000000..af93741
--- /dev/null
+++ b/docs/examples/Bitmap_tryAllocN32Pixels.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a2b1e0910f37066f15ae56368775a6d8
+REG_FIDDLE(Bitmap_tryAllocN32Pixels, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    if (bitmap.tryAllocN32Pixels(80, 80)) {
+        bitmap.eraseColor(SK_ColorTRANSPARENT);
+        bitmap.erase(0x7f3f7fff, SkIRect::MakeWH(50, 30));
+        bitmap.erase(0x3f7fff3f, SkIRect::MakeXYWH(20, 10, 50, 30));
+        bitmap.erase(0x5fff3f7f, SkIRect::MakeXYWH(40, 20, 50, 30));
+        canvas->drawBitmap(bitmap, 0, 0);
+        for (int x : { 0, 30, 60, 90 } ) {
+            canvas->drawBitmap(bitmap, x, 70);
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_tryAllocPixels.cpp b/docs/examples/Bitmap_tryAllocPixels.cpp
new file mode 100644
index 0000000..c831d89
--- /dev/null
+++ b/docs/examples/Bitmap_tryAllocPixels.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=34479d5aa23ce9f5e334b0786c9edb22
+REG_FIDDLE(Bitmap_tryAllocPixels, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    SkImageInfo info = SkImageInfo::Make(64, 256, kGray_8_SkColorType, kOpaque_SkAlphaType);
+    if (bitmap.tryAllocPixels(info, 0)) {
+        SkCanvas offscreen(bitmap);
+        offscreen.scale(.5f, .5f);
+        for (int x : { 0, 64, 128, 192 } ) {
+            offscreen.drawBitmap(source, -x, 0);
+            canvas->drawBitmap(bitmap, x, 0);
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_tryAllocPixelsFlags.cpp b/docs/examples/Bitmap_tryAllocPixelsFlags.cpp
new file mode 100644
index 0000000..4cc5e9c
--- /dev/null
+++ b/docs/examples/Bitmap_tryAllocPixelsFlags.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f1d1880d38e0aea4cefd3e11745e8a09
+REG_FIDDLE(Bitmap_tryAllocPixelsFlags, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    if (!bitmap.tryAllocPixelsFlags(SkImageInfo::MakeN32(10000, 10000, kOpaque_SkAlphaType),
+                                    SkBitmap::kZeroPixels_AllocFlag)) {
+        SkDebugf("bitmap allocation failed!\n");
+    } else {
+        SkDebugf("bitmap allocation succeeded!\n");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_tryAllocPixels_2.cpp b/docs/examples/Bitmap_tryAllocPixels_2.cpp
new file mode 100644
index 0000000..0732ba4
--- /dev/null
+++ b/docs/examples/Bitmap_tryAllocPixels_2.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7ef3d043c4c5885649e591dd7dca92ff
+REG_FIDDLE(Bitmap_tryAllocPixels_2, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    if (bitmap.tryAllocPixels(SkImageInfo::Make(64, 64, kGray_8_SkColorType, kOpaque_SkAlphaType))) {
+        SkCanvas offscreen(bitmap);
+        offscreen.scale(.25f, .5f);
+        for (int y : { 0, 64, 128, 192 } ) {
+            offscreen.drawBitmap(source, -y, -y);
+            canvas->drawBitmap(bitmap, y, y);
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_tryAllocPixels_3.cpp b/docs/examples/Bitmap_tryAllocPixels_3.cpp
new file mode 100644
index 0000000..85d22e2
--- /dev/null
+++ b/docs/examples/Bitmap_tryAllocPixels_3.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=720e4c053fae9e929ab6518b47e49370
+REG_FIDDLE(Bitmap_tryAllocPixels_3, 256, 50, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint8_t set1[5] = { 0xCA, 0xDA, 0xCA, 0xC9, 0xA3 };
+    SkBitmap bitmap;
+    bitmap.installPixels(SkImageInfo::Make(5, 1, kGray_8_SkColorType, kOpaque_SkAlphaType), set1, 5);
+    canvas->scale(10, 50);
+    canvas->drawBitmap(bitmap, 0, 0);
+    if (bitmap.tryAllocPixels()) {
+        bitmap.eraseColor(SK_ColorBLACK);
+        canvas->drawBitmap(bitmap, 8, 0);
+        bitmap.setPixels(set1);
+        canvas->drawBitmap(bitmap, 16, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_tryAllocPixels_4.cpp b/docs/examples/Bitmap_tryAllocPixels_4.cpp
new file mode 100644
index 0000000..e8155ba
--- /dev/null
+++ b/docs/examples/Bitmap_tryAllocPixels_4.cpp
@@ -0,0 +1,45 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=eb6f861ca1839146d26e40d56c2a001c
+REG_FIDDLE(Bitmap_tryAllocPixels_4, 256, 100, false, 0) {
+class LargePixelRef : public SkPixelRef {
+public:
+    LargePixelRef(const SkImageInfo& info, char* storage, size_t rowBytes)
+        : SkPixelRef(info.width(), info.height(), storage, rowBytes) {
+    }
+    ~LargePixelRef() override {
+        delete[] (char* ) this->pixels();
+    }
+};
+class LargeAllocator : public SkBitmap::Allocator {
+public:
+    bool allocPixelRef(SkBitmap* bitmap) override {
+        const SkImageInfo& info = bitmap->info();
+        uint64_t rowBytes = info.minRowBytes64();
+        uint64_t size = info.height() * rowBytes;
+        char* addr = new char[size];
+        if (nullptr == addr) {
+            return false;
+        }
+        sk_sp<SkPixelRef> pr = sk_sp<SkPixelRef>(new LargePixelRef(info, addr, rowBytes));
+        if (!pr) {
+            return false;
+        }
+        bitmap->setPixelRef(std::move(pr), 0, 0);
+        return true;
+    }
+};
+
+void draw(SkCanvas* canvas) {
+   LargeAllocator largeAllocator;
+   SkBitmap bitmap;
+   int width = 100; // make this 20000
+   int height = 100; // and this 100000 to allocate 8 gigs on a 64-bit platform
+   bitmap.setInfo(SkImageInfo::MakeN32(width, height, kOpaque_SkAlphaType));
+   if (bitmap.tryAllocPixels(&largeAllocator)) {
+       bitmap.eraseColor(0xff55aa33);
+       canvas->drawBitmap(bitmap, 0, 0);
+   }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_width.cpp b/docs/examples/Bitmap_width.cpp
new file mode 100644
index 0000000..c8330be
--- /dev/null
+++ b/docs/examples/Bitmap_width.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d06880c42f8bb3b4c3b67bd988046049
+REG_FIDDLE(Bitmap_width, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo info = SkImageInfo::MakeA8(16, 32);
+    SkBitmap bitmap;
+    bitmap.setInfo(info);
+    SkDebugf("bitmap width: %d  info width: %d\n", bitmap.width(), info.width());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_writePixels.cpp b/docs/examples/Bitmap_writePixels.cpp
new file mode 100644
index 0000000..0caa52b
--- /dev/null
+++ b/docs/examples/Bitmap_writePixels.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9b3133a6673d2514d166398adbe1f9f4
+REG_FIDDLE(Bitmap_writePixels, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    std::vector<int32_t> srcPixels;
+    int width = image->width();
+    int height = image->height();
+    srcPixels.resize(height * width  * 4);
+    SkPixmap pixmap(SkImageInfo::MakeN32Premul(width, height), (const void*) &srcPixels.front(),
+                    width * 4);
+    image->readPixels(pixmap, 0, 0);
+    canvas->scale(.5f, .5f);
+    width /= 4;
+    height /= 4;
+    for (int y = 0; y < 4; ++y) {
+        for (int x = 0; x < 4; ++x) {
+            SkBitmap bitmap;
+            bitmap.allocPixels(SkImageInfo::MakeN32Premul(width, height));
+            bitmap.writePixels(pixmap, -y * width, -x * height);
+            canvas->drawBitmap(bitmap, x * width, y * height);
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Bitmap_writePixels_2.cpp b/docs/examples/Bitmap_writePixels_2.cpp
new file mode 100644
index 0000000..29fcf94
--- /dev/null
+++ b/docs/examples/Bitmap_writePixels_2.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=faa5dfa466f6e16c07c124d971f32679
+REG_FIDDLE(Bitmap_writePixels_2, 256, 80, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(2, 2));
+    bitmap.eraseColor(SK_ColorGREEN);
+    SkPMColor color = 0xFF5599BB;
+    SkPixmap src(SkImageInfo::MakeN32Premul(1, 1), &color, 4);
+    bitmap.writePixels(src);
+    canvas->scale(40, 40);
+    canvas->drawBitmap(bitmap, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/BlendMode_Name.cpp b/docs/examples/BlendMode_Name.cpp
new file mode 100644
index 0000000..a12dd83
--- /dev/null
+++ b/docs/examples/BlendMode_Name.cpp
@@ -0,0 +1,9 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3996f4994bf4e90b4cd86524c1f9f1a6
+REG_FIDDLE(BlendMode_Name, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkDebugf("default blend: SkBlendMode::k%s\n", SkBlendMode_Name(SkPaint().getBlendMode()));
+}
+}  // END FIDDLE
diff --git a/docs/examples/Blend_Mode_Methods.cpp b/docs/examples/Blend_Mode_Methods.cpp
new file mode 100644
index 0000000..a0ba2ce
--- /dev/null
+++ b/docs/examples/Blend_Mode_Methods.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=73092d4d06faecea3c204d852a4dd8a8
+REG_FIDDLE(Blend_Mode_Methods, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint normal, blender;
+    normal.setColor(0xFF58a889);
+    blender.setColor(0xFF8958a8);
+    canvas->clear(0);
+    for (SkBlendMode m : { SkBlendMode::kSrcOver, SkBlendMode::kSrcIn, SkBlendMode::kSrcOut } ) {
+        normal.setBlendMode(SkBlendMode::kSrcOver);
+        canvas->drawOval(SkRect::MakeXYWH(30, 30, 30, 80), normal);
+        blender.setBlendMode(m);
+        canvas->drawOval(SkRect::MakeXYWH(10, 50, 80, 30), blender);
+        canvas->translate(70, 70);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_129.cpp b/docs/examples/Canvas_129.cpp
new file mode 100644
index 0000000..ba9c70a
--- /dev/null
+++ b/docs/examples/Canvas_129.cpp
@@ -0,0 +1,18 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=00b430bd80d740e19c6d020a940f56d5
+REG_FIDDLE(Canvas_129, 256, 1, false, 0) {
+void draw(SkCanvas* canvas) {
+    const char text[] = "Click this link!";
+    SkRect bounds;
+    SkPaint paint;
+    paint.setTextSize(40);
+    (void)paint.measureText(text, strlen(text), &bounds);
+    const char url[] = "https://www.google.com/";
+    sk_sp<SkData> urlData(SkData::MakeWithCString(url));
+    canvas->drawAnnotation(bounds, "url_key", urlData.get());
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Canvas_MakeRasterDirect.cpp b/docs/examples/Canvas_MakeRasterDirect.cpp
new file mode 100644
index 0000000..8b4dbd3
--- /dev/null
+++ b/docs/examples/Canvas_MakeRasterDirect.cpp
@@ -0,0 +1,28 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=525285073aae7e53eb8f454a398f880c
+REG_FIDDLE(Canvas_MakeRasterDirect, 256, 256, true, 0) {
+void draw(SkCanvas* ) {
+    SkImageInfo info = SkImageInfo::MakeN32Premul(3, 3);  // device aligned, 32 bpp, Premultiplied
+    const size_t minRowBytes = info.minRowBytes();  // bytes used by one bitmap row
+    const size_t size = info.computeMinByteSize();  // bytes used by all rows
+    SkAutoTMalloc<SkPMColor> storage(size);  // allocate storage for pixels
+    SkPMColor* pixels = storage.get();  // get pointer to allocated storage
+    // create a SkCanvas backed by a raster device, and delete it when the
+    // function goes out of scope.
+    std::unique_ptr<SkCanvas> canvas = SkCanvas::MakeRasterDirect(info, pixels, minRowBytes);
+    canvas->clear(SK_ColorWHITE);  // white is Unpremultiplied, in ARGB order
+    canvas->flush();  // ensure that pixels are cleared
+    SkPMColor pmWhite = pixels[0];  // the Premultiplied format may vary
+    SkPaint paint;  // by default, draws black
+    canvas->drawPoint(1, 1, paint);  // draw in the center
+    canvas->flush();  // ensure that point was drawn
+    for (int y = 0; y < info.height(); ++y) {
+        for (int x = 0; x < info.width(); ++x) {
+            SkDebugf("%c", *pixels++ == pmWhite ? '-' : 'x');
+        }
+        SkDebugf("\n");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_MakeRasterDirectN32.cpp b/docs/examples/Canvas_MakeRasterDirectN32.cpp
new file mode 100644
index 0000000..27313fe
--- /dev/null
+++ b/docs/examples/Canvas_MakeRasterDirectN32.cpp
@@ -0,0 +1,30 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=87f55e62ec4c3535e1a5d0f1415b20c6
+REG_FIDDLE(Canvas_MakeRasterDirectN32, 256, 256, true, 0) {
+void draw(SkCanvas* ) {
+    const int width = 3;
+    const int height = 3;
+    SkPMColor pixels[height][width];  // allocate a 3 by 3 Premultiplied bitmap on the stack
+    // create a SkCanvas backed by a raster device, and delete it when the
+    // function goes out of scope.
+    std::unique_ptr<SkCanvas> canvas = SkCanvas::MakeRasterDirectN32(
+            width,
+            height,
+            pixels[0],  // top-left of the bitmap
+            sizeof(pixels[0]));  // byte width of the each row
+    // write a Premultiplied value for white into all pixels in the bitmap
+    canvas->clear(SK_ColorWHITE);
+    SkPMColor pmWhite = pixels[0][0];  // the Premultiplied format may vary
+    SkPaint paint;  // by default, draws black
+    canvas->drawPoint(1, 1, paint);  // draw in the center
+    canvas->flush();  // ensure that pixels is ready to be read
+    for (int y = 0; y < height; ++y) {
+        for (int x = 0; x < width; ++x) {
+            SkDebugf("%c", pixels[y][x] == pmWhite ? '-' : 'x');
+        }
+        SkDebugf("\n");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_PointMode.cpp b/docs/examples/Canvas_PointMode.cpp
new file mode 100644
index 0000000..398fafa
--- /dev/null
+++ b/docs/examples/Canvas_PointMode.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=292b4b2008961b6f612434d3121fc4ce
+REG_FIDDLE(Canvas_PointMode, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+  SkPaint paint;
+  paint.setStyle(SkPaint::kStroke_Style);
+  paint.setStrokeWidth(10);
+  SkPoint points[] = {{64, 32}, {96, 96}, {32, 96}};
+  canvas->drawPoints(SkCanvas::kPoints_PointMode, 3, points, paint);
+  canvas->translate(128, 0);
+  canvas->drawPoints(SkCanvas::kLines_PointMode, 3, points, paint);
+  canvas->translate(0, 128);
+  canvas->drawPoints(SkCanvas::kPolygon_PointMode, 3, points, paint);
+  SkPath path;
+  path.addPoly(points, 3, false);
+  canvas->translate(-128, 0);
+  canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_SaveLayerRec.cpp b/docs/examples/Canvas_SaveLayerRec.cpp
new file mode 100644
index 0000000..b842348
--- /dev/null
+++ b/docs/examples/Canvas_SaveLayerRec.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ee8c0b120234e27364f8c9a786cf8f89
+REG_FIDDLE(Canvas_SaveLayerRec, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint redPaint, bluePaint;
+    redPaint.setAntiAlias(true);
+    redPaint.setColor(SK_ColorRED);
+    canvas->drawCircle(21, 21, 8, redPaint);
+    bluePaint.setColor(SK_ColorBLUE);
+    canvas->drawCircle(31, 21, 8, bluePaint);
+    SkMatrix matrix;
+    matrix.setScale(4, 4);
+    auto scaler = SkImageFilter::MakeMatrixFilter(matrix, kNone_SkFilterQuality, nullptr);
+    SkCanvas::SaveLayerRec saveLayerRec(nullptr, nullptr, scaler.get(), 0);
+    canvas->saveLayer(saveLayerRec);
+    canvas->drawCircle(125, 85, 8, redPaint);
+    canvas->restore();
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_SaveLayerRec_SaveLayerRec.cpp b/docs/examples/Canvas_SaveLayerRec_SaveLayerRec.cpp
new file mode 100644
index 0000000..b80834c
--- /dev/null
+++ b/docs/examples/Canvas_SaveLayerRec_SaveLayerRec.cpp
@@ -0,0 +1,17 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b5cea1eed80a0eb04ddbab3f36dff73f
+REG_FIDDLE(Canvas_SaveLayerRec_SaveLayerRec, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkCanvas::SaveLayerRec rec1;
+    rec1.fSaveLayerFlags = SkCanvas::kPreserveLCDText_SaveLayerFlag;
+    SkCanvas::SaveLayerRec rec2(nullptr, nullptr, SkCanvas::kPreserveLCDText_SaveLayerFlag);
+    SkDebugf("rec1 %c= rec2\n", rec1.fBounds == rec2.fBounds
+            && rec1.fPaint == rec2.fPaint
+            && rec1.fBackdrop == rec2.fBackdrop
+            && rec1.fSaveLayerFlags == rec2.fSaveLayerFlags ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Canvas_SaveLayerRec_const_SkRect_star_const_SkPaint_star.cpp b/docs/examples/Canvas_SaveLayerRec_const_SkRect_star_const_SkPaint_star.cpp
new file mode 100644
index 0000000..9da2199
--- /dev/null
+++ b/docs/examples/Canvas_SaveLayerRec_const_SkRect_star_const_SkPaint_star.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=027f920259888fc19591ea9a90d92873
+REG_FIDDLE(Canvas_SaveLayerRec_const_SkRect_star_const_SkPaint_star, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkCanvas::SaveLayerRec rec1;
+    SkCanvas::SaveLayerRec rec2(nullptr, nullptr);
+    SkDebugf("rec1 %c= rec2\n", rec1.fBounds == rec2.fBounds
+            && rec1.fPaint == rec2.fPaint
+            && rec1.fBackdrop == rec2.fBackdrop
+            && rec1.fSaveLayerFlags == rec2.fSaveLayerFlags ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_SaveLayerRec_const_SkRect_star_const_SkPaint_star_const_SkImageFilter_star.cpp b/docs/examples/Canvas_SaveLayerRec_const_SkRect_star_const_SkPaint_star_const_SkImageFilter_star.cpp
new file mode 100644
index 0000000..bb014d7
--- /dev/null
+++ b/docs/examples/Canvas_SaveLayerRec_const_SkRect_star_const_SkPaint_star_const_SkImageFilter_star.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9b7fa2fe855642ffff6538829db15328
+REG_FIDDLE(Canvas_SaveLayerRec_const_SkRect_star_const_SkPaint_star_const_SkImageFilter_star, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkCanvas::SaveLayerRec rec1;
+    SkCanvas::SaveLayerRec rec2(nullptr, nullptr, nullptr, 0);
+    SkDebugf("rec1 %c= rec2\n", rec1.fBounds == rec2.fBounds
+            && rec1.fPaint == rec2.fPaint
+            && rec1.fBackdrop == rec2.fBackdrop
+            && rec1.fSaveLayerFlags == rec2.fSaveLayerFlags ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_SrcRectConstraint.cpp b/docs/examples/Canvas_SrcRectConstraint.cpp
new file mode 100644
index 0000000..1218216
--- /dev/null
+++ b/docs/examples/Canvas_SrcRectConstraint.cpp
@@ -0,0 +1,28 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5df49d1f4da37275a1f10ef7f1a749f0
+REG_FIDDLE(Canvas_SrcRectConstraint, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap redBorder;
+    redBorder.allocPixels(SkImageInfo::MakeN32Premul(4, 4));
+    SkCanvas checkRed(redBorder);
+    checkRed.clear(SK_ColorRED);
+    uint32_t checkers[][2] = { { SK_ColorBLACK, SK_ColorWHITE },
+                               { SK_ColorWHITE, SK_ColorBLACK } };
+    checkRed.writePixels(
+            SkImageInfo::MakeN32Premul(2, 2), (void*) checkers, sizeof(checkers[0]), 1, 1);
+    canvas->scale(16, 16);
+    canvas->drawBitmap(redBorder, 0, 0, nullptr);
+    canvas->resetMatrix();
+    sk_sp<SkImage> image = SkImage::MakeFromBitmap(redBorder);
+    SkPaint lowPaint;
+    lowPaint.setFilterQuality(kLow_SkFilterQuality);
+    for (auto constraint : { SkCanvas::kStrict_SrcRectConstraint,
+                             SkCanvas::kFast_SrcRectConstraint } ) {
+        canvas->translate(80, 0);
+        canvas->drawImageRect(image.get(), SkRect::MakeLTRB(1, 1, 3, 3),
+                SkRect::MakeLTRB(16, 16, 48, 48), &lowPaint, constraint);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_accessTopLayerPixels_a.cpp b/docs/examples/Canvas_accessTopLayerPixels_a.cpp
new file mode 100644
index 0000000..3266ae1
--- /dev/null
+++ b/docs/examples/Canvas_accessTopLayerPixels_a.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=38d0d6ca9bea146d31bcbec197856359
+REG_FIDDLE(Canvas_accessTopLayerPixels_a, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    if (canvas->accessTopLayerPixels(nullptr, nullptr)) {
+         canvas->clear(SK_ColorRED);
+    } else {
+         canvas->clear(SK_ColorBLUE);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_accessTopLayerPixels_b.cpp b/docs/examples/Canvas_accessTopLayerPixels_b.cpp
new file mode 100644
index 0000000..e23f316
--- /dev/null
+++ b/docs/examples/Canvas_accessTopLayerPixels_b.cpp
@@ -0,0 +1,40 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a7ac9c21bbabcdeeca00f72a61cd0f3e
+REG_FIDDLE(Canvas_accessTopLayerPixels_b, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+  SkPaint paint;
+  SkFont font(nullptr, 100);
+  canvas->drawString("ABC", 20, 160, font, paint);
+  SkRect layerBounds = SkRect::MakeXYWH(32, 32, 192, 192);
+  canvas->saveLayerAlpha(&layerBounds, 128);
+  canvas->clear(SK_ColorWHITE);
+  canvas->drawString("DEF", 20, 160, font, paint);
+  SkImageInfo imageInfo;
+  size_t rowBytes;
+  SkIPoint origin;
+  uint32_t* access = (uint32_t*) canvas->accessTopLayerPixels(&imageInfo, &rowBytes, &origin);
+  if (access) {
+    int h = imageInfo.height();
+    int v = imageInfo.width();
+    int rowWords = rowBytes / sizeof(uint32_t);
+    for (int y = 0; y < h; ++y) {
+        int newY = (y - h / 2) * 2 + h / 2;
+        if (newY < 0 || newY >= h) {
+            continue;
+        }
+        for (int x = 0; x < v; ++x) {
+            int newX = (x - v / 2) * 2 + v / 2;
+            if (newX < 0 || newX >= v) {
+                continue;
+            }
+            if (access[y * rowWords + x] == SK_ColorBLACK) {
+                access[newY * rowWords + newX] = SK_ColorGRAY;
+            }
+        }
+    }
+  }
+  canvas->restore();
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_accessTopRasterHandle.cpp b/docs/examples/Canvas_accessTopRasterHandle.cpp
new file mode 100644
index 0000000..93ab5fc
--- /dev/null
+++ b/docs/examples/Canvas_accessTopRasterHandle.cpp
@@ -0,0 +1,33 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4486d0c0b22ad2931db130f42da4c80c
+REG_FIDDLE(Canvas_accessTopRasterHandle, 256, 256, true, 0) {
+static void DeleteCallback(void*, void* context) {
+    delete (char*) context;
+}
+class CustomAllocator : public SkRasterHandleAllocator {
+public:
+    bool allocHandle(const SkImageInfo& info, Rec* rec) override {
+        char* context = new char[4]{'s', 'k', 'i', 'a'};
+        rec->fReleaseProc = DeleteCallback;
+        rec->fReleaseCtx = context;
+        rec->fHandle = context;
+        rec->fPixels = context;
+        rec->fRowBytes = 4;
+        return true;
+    }
+    void updateHandle(Handle handle, const SkMatrix& ctm, const SkIRect& clip_bounds) override {
+        // apply canvas matrix and clip to custom environment
+    }
+};
+
+void draw(SkCanvas* canvas) {
+    const SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
+    std::unique_ptr<SkCanvas> c2 =
+            SkRasterHandleAllocator::MakeCanvas(std::unique_ptr<CustomAllocator>(
+            new CustomAllocator()), info);
+    char* context = (char*) c2->accessTopRasterHandle();
+    SkDebugf("context = %.4s\n", context);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_clear.cpp b/docs/examples/Canvas_clear.cpp
new file mode 100644
index 0000000..733c24e
--- /dev/null
+++ b/docs/examples/Canvas_clear.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8c4499e322f10153dcd9b0b9806233b9
+REG_FIDDLE(Canvas_clear, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->save();
+    canvas->clipRect(SkRect::MakeWH(256, 128));
+    canvas->clear(SkColorSetARGB(0x80, 0xFF, 0x00, 0x00));
+    canvas->restore();
+    canvas->save();
+    canvas->clipRect(SkRect::MakeWH(150, 192));
+    canvas->clear(SkColorSetARGB(0x80, 0x00, 0xFF, 0x00));
+    canvas->restore();
+    canvas->clipRect(SkRect::MakeWH(75, 256));
+    canvas->clear(SkColorSetARGB(0x80, 0x00, 0x00, 0xFF));
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_clipPath.cpp b/docs/examples/Canvas_clipPath.cpp
new file mode 100644
index 0000000..ab43bb0
--- /dev/null
+++ b/docs/examples/Canvas_clipPath.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ee47ae6b813bfaa55e1a7b7c053ed60d
+REG_FIDDLE(Canvas_clipPath, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkPath path;
+    path.addRect({20, 30, 100, 110});
+    path.setFillType(SkPath::kInverseWinding_FillType);
+    canvas->save();
+    canvas->clipPath(path, SkClipOp::kDifference, false);
+    canvas->drawCircle(70, 100, 60, paint);
+    canvas->restore();
+    canvas->translate(100, 100);
+    path.setFillType(SkPath::kWinding_FillType);
+    canvas->clipPath(path, SkClipOp::kIntersect, false);
+    canvas->drawCircle(70, 100, 60, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_clipPath_2.cpp b/docs/examples/Canvas_clipPath_2.cpp
new file mode 100644
index 0000000..e74ca27
--- /dev/null
+++ b/docs/examples/Canvas_clipPath_2.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7856755c1bf8431c286c734b353345ad
+REG_FIDDLE(Canvas_clipPath_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkPath path;
+    path.addRect({20, 15, 100, 95});
+    path.addRect({50, 65, 130, 135});
+    path.setFillType(SkPath::kWinding_FillType);
+    canvas->save();
+    canvas->clipPath(path, SkClipOp::kIntersect);
+    canvas->drawCircle(70, 85, 60, paint);
+    canvas->restore();
+    canvas->translate(100, 100);
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    canvas->clipPath(path, SkClipOp::kIntersect);
+    canvas->drawCircle(70, 85, 60, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_clipPath_3.cpp b/docs/examples/Canvas_clipPath_3.cpp
new file mode 100644
index 0000000..8952c6a
--- /dev/null
+++ b/docs/examples/Canvas_clipPath_3.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=187a7ae77a8176e417181411988534b6
+REG_FIDDLE(Canvas_clipPath_3, 256, 212, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkPath path;
+    SkPoint poly[] = {{20, 20}, { 80, 20}, { 80,  80}, {40,  80},
+                      {40, 40}, {100, 40}, {100, 100}, {20, 100}};
+    path.addPoly(poly, SK_ARRAY_COUNT(poly), true);
+    path.setFillType(SkPath::kWinding_FillType);
+    canvas->save();
+    canvas->clipPath(path, SkClipOp::kIntersect);
+    canvas->drawCircle(50, 50, 45, paint);
+    canvas->restore();
+    canvas->translate(100, 100);
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    canvas->clipPath(path, SkClipOp::kIntersect);
+    canvas->drawCircle(50, 50, 45, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_clipRRect.cpp b/docs/examples/Canvas_clipRRect.cpp
new file mode 100644
index 0000000..2a09fe2
--- /dev/null
+++ b/docs/examples/Canvas_clipRRect.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=182ef48ab5e04ba3578496fda8d9fa36
+REG_FIDDLE(Canvas_clipRRect, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->clear(SK_ColorWHITE);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setColor(0x8055aaff);
+    SkRRect oval;
+    oval.setOval({10, 20, 90, 100});
+    canvas->clipRRect(oval, SkClipOp::kIntersect, true);
+    canvas->drawCircle(70, 100, 60, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_clipRRect_2.cpp b/docs/examples/Canvas_clipRRect_2.cpp
new file mode 100644
index 0000000..6af4930
--- /dev/null
+++ b/docs/examples/Canvas_clipRRect_2.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ef6ae2eaae6761130ce38065d0364abd
+REG_FIDDLE(Canvas_clipRRect_2, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setColor(0x8055aaff);
+    auto oval = SkRRect::MakeOval({10, 20, 90, 100});
+    canvas->clipRRect(oval, SkClipOp::kIntersect);
+    canvas->drawCircle(70, 100, 60, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_clipRRect_3.cpp b/docs/examples/Canvas_clipRRect_3.cpp
new file mode 100644
index 0000000..8688c24
--- /dev/null
+++ b/docs/examples/Canvas_clipRRect_3.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f583114580b2176fe3e75b0994476a84
+REG_FIDDLE(Canvas_clipRRect_3, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    auto oval = SkRRect::MakeRectXY({10, 20, 90, 100}, 9, 13);
+    canvas->clipRRect(oval, true);
+    canvas->drawCircle(70, 100, 60, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_clipRect.cpp b/docs/examples/Canvas_clipRect.cpp
new file mode 100644
index 0000000..0397fcb
--- /dev/null
+++ b/docs/examples/Canvas_clipRect.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6a614faa0fbcf19958b5559c19b02d0f
+REG_FIDDLE(Canvas_clipRect, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->rotate(10);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    for (auto alias: { false, true } ) {
+        canvas->save();
+        canvas->clipRect(SkRect::MakeWH(90, 80), SkClipOp::kIntersect, alias);
+        canvas->drawCircle(100, 60, 60, paint);
+        canvas->restore();
+        canvas->translate(80, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_clipRect_2.cpp b/docs/examples/Canvas_clipRect_2.cpp
new file mode 100644
index 0000000..490c640
--- /dev/null
+++ b/docs/examples/Canvas_clipRect_2.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=13bbc5fa5597a6cd4d704b419dbc66d9
+REG_FIDDLE(Canvas_clipRect_2, 280, 192, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    for (SkClipOp op: { SkClipOp::kIntersect, SkClipOp::kDifference } ) {
+        canvas->save();
+        canvas->clipRect(SkRect::MakeWH(90, 120), op, false);
+        canvas->drawCircle(100, 100, 60, paint);
+        canvas->restore();
+        canvas->translate(80, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_clipRect_3.cpp b/docs/examples/Canvas_clipRect_3.cpp
new file mode 100644
index 0000000..fe34c28
--- /dev/null
+++ b/docs/examples/Canvas_clipRect_3.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1d4e0632c97e42692775d834fe10aa99
+REG_FIDDLE(Canvas_clipRect_3, 256, 133, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->clear(SK_ColorWHITE);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setColor(0x8055aaff);
+    SkRect clipRect = { 0, 0, 87.4f, 87.4f };
+    for (auto alias: { false, true } ) {
+        canvas->save();
+        canvas->clipRect(clipRect, SkClipOp::kIntersect, alias);
+        canvas->drawCircle(67, 67, 60, paint);
+        canvas->restore();
+        canvas->save();
+        canvas->clipRect(clipRect, SkClipOp::kDifference, alias);
+        canvas->drawCircle(67, 67, 60, paint);
+        canvas->restore();
+        canvas->translate(120, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_clipRegion.cpp b/docs/examples/Canvas_clipRegion.cpp
new file mode 100644
index 0000000..8b90021
--- /dev/null
+++ b/docs/examples/Canvas_clipRegion.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7bb57c0e456c5fda2c2cca4abb68b19e
+REG_FIDDLE(Canvas_clipRegion, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkIRect iRect = {30, 40, 120, 130 };
+    SkRegion region(iRect);
+    canvas->rotate(10);
+    canvas->save();
+    canvas->clipRegion(region, SkClipOp::kIntersect);
+    canvas->drawCircle(50, 50, 45, paint);
+    canvas->restore();
+    canvas->translate(100, 100);
+    canvas->clipRect(SkRect::Make(iRect), SkClipOp::kIntersect);
+    canvas->drawCircle(50, 50, 45, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_concat.cpp b/docs/examples/Canvas_concat.cpp
new file mode 100644
index 0000000..d687832
--- /dev/null
+++ b/docs/examples/Canvas_concat.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8f6818b25a92a88638ad99b2dd293f61
+REG_FIDDLE(Canvas_concat, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkFont font(nullptr, 80);
+    font.setScaleX(.3f);
+    SkMatrix matrix;
+    SkRect rect[2] = {{ 10, 20, 90, 110 }, { 40, 130, 140, 180 }};
+    matrix.setRectToRect(rect[0], rect[1], SkMatrix::kFill_ScaleToFit);
+    canvas->drawRect(rect[0], paint);
+    canvas->drawRect(rect[1], paint);
+    paint.setColor(SK_ColorWHITE);
+    canvas->drawString("Here", rect[0].fLeft + 10, rect[0].fBottom - 10, font, paint);
+    canvas->concat(matrix);
+    canvas->drawString("There", rect[0].fLeft + 10, rect[0].fBottom - 10, font, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_const_SkBitmap_const_SkSurfaceProps.cpp b/docs/examples/Canvas_const_SkBitmap_const_SkSurfaceProps.cpp
new file mode 100644
index 0000000..7e4da71
--- /dev/null
+++ b/docs/examples/Canvas_const_SkBitmap_const_SkSurfaceProps.cpp
@@ -0,0 +1,28 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c26cfae4c42cb445240335cc12a50235
+REG_FIDDLE(Canvas_const_SkBitmap_const_SkSurfaceProps, 256, 256, true, 0) {
+void draw(SkCanvas* ) {
+    SkBitmap bitmap;
+    // create a bitmap 5 wide and 11 high
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(5, 11));
+    SkCanvas canvas(bitmap, SkSurfaceProps(0, kUnknown_SkPixelGeometry));
+    canvas.clear(SK_ColorWHITE);  // white is Unpremultiplied, in ARGB order
+    SkPixmap pixmap;  // provides guaranteed access to the drawn pixels
+    if (!canvas.peekPixels(&pixmap)) {
+        SkDebugf("peekPixels should never fail.\n");
+    }
+    const SkPMColor* pixels = pixmap.addr32();  // points to top-left of bitmap
+    SkPMColor pmWhite = pixels[0];  // the Premultiplied format may vary
+    SkPaint paint;  // by default, draws black, 12 point text
+    SkFont font;
+    canvas.drawString("!", 1, 10, font, paint);  // 1 char at baseline (1, 10)
+    for (int y = 0; y < bitmap.height(); ++y) {
+        for (int x = 0; x < bitmap.width(); ++x) {
+            SkDebugf("%c", *pixels++ == pmWhite ? '-' : 'x');
+        }
+        SkDebugf("\n");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_copy_const_SkBitmap.cpp b/docs/examples/Canvas_copy_const_SkBitmap.cpp
new file mode 100644
index 0000000..7812e41
--- /dev/null
+++ b/docs/examples/Canvas_copy_const_SkBitmap.cpp
@@ -0,0 +1,28 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=dd92db963af190e849894038f39b598a
+REG_FIDDLE(Canvas_copy_const_SkBitmap, 256, 256, true, 0) {
+void draw(SkCanvas* ) {
+    SkBitmap bitmap;
+    // create a bitmap 5 wide and 11 high
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(5, 11));
+    SkCanvas canvas(bitmap);
+    canvas.clear(SK_ColorWHITE);  // white is Unpremultiplied, in ARGB order
+    SkPixmap pixmap;  // provides guaranteed access to the drawn pixels
+    if (!canvas.peekPixels(&pixmap)) {
+        SkDebugf("peekPixels should never fail.\n");
+    }
+    const SkPMColor* pixels = pixmap.addr32();  // points to top-left of bitmap
+    SkPMColor pmWhite = pixels[0];  // the Premultiplied format may vary
+    SkPaint paint;  // by default, draws black, 12 point text
+    SkFont font;
+    canvas.drawString("!", 1, 10, font, paint);  // 1 char at baseline (1, 10)
+    for (int y = 0; y < bitmap.height(); ++y) {
+        for (int x = 0; x < bitmap.width(); ++x) {
+            SkDebugf("%c", *pixels++ == pmWhite ? '-' : 'x');
+        }
+        SkDebugf("\n");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_destructor.cpp b/docs/examples/Canvas_destructor.cpp
new file mode 100644
index 0000000..4b6dc87
--- /dev/null
+++ b/docs/examples/Canvas_destructor.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b7bc91ff16c9b9351b2a127f35394b82
+REG_FIDDLE(Canvas_destructor, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(200, 200));
+    {
+        SkCanvas offscreen(bitmap);
+        SkPaint paint;
+        SkFont font(nullptr, 100);
+        offscreen.drawString("ABC", 20, 160, font, paint);
+        SkRect layerBounds = SkRect::MakeXYWH(32, 32, 192, 192);
+        offscreen.saveLayerAlpha(&layerBounds, 128);
+        offscreen.clear(SK_ColorWHITE);
+        offscreen.drawString("DEF", 20, 160, font, paint);
+    }
+    canvas->drawBitmap(bitmap, 0, 0, nullptr);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawAnnotation_2.cpp b/docs/examples/Canvas_drawAnnotation_2.cpp
new file mode 100644
index 0000000..6c58391
--- /dev/null
+++ b/docs/examples/Canvas_drawAnnotation_2.cpp
@@ -0,0 +1,18 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=00b430bd80d740e19c6d020a940f56d5
+REG_FIDDLE(Canvas_drawAnnotation_2, 256, 1, false, 0) {
+void draw(SkCanvas* canvas) {
+    const char text[] = "Click this link!";
+    SkRect bounds;
+    SkPaint paint;
+    paint.setTextSize(40);
+    (void)paint.measureText(text, strlen(text), &bounds);
+    const char url[] = "https://www.google.com/";
+    sk_sp<SkData> urlData(SkData::MakeWithCString(url));
+    canvas->drawAnnotation(bounds, "url_key", urlData.get());
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Canvas_drawArc_a.cpp b/docs/examples/Canvas_drawArc_a.cpp
new file mode 100644
index 0000000..a969e7b
--- /dev/null
+++ b/docs/examples/Canvas_drawArc_a.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=11f0fbe7b30d776913c2e7c92c02ff57
+REG_FIDDLE(Canvas_drawArc_a, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkRect oval = { 4, 4, 60, 60};
+    for (auto useCenter : { false, true } ) {
+        for (auto style : { SkPaint::kFill_Style, SkPaint::kStroke_Style } ) {
+            paint.setStyle(style);
+            for (auto degrees : { 45, 90, 180, 360} ) {
+                canvas->drawArc(oval, 0, degrees , useCenter, paint);
+                canvas->translate(64, 0);
+            }
+            canvas->translate(-256, 64);
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawArc_b.cpp b/docs/examples/Canvas_drawArc_b.cpp
new file mode 100644
index 0000000..bcd3968
--- /dev/null
+++ b/docs/examples/Canvas_drawArc_b.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e91dbe45974489b8962c815017b7914f
+REG_FIDDLE(Canvas_drawArc_b, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(4);
+    SkRect oval = { 4, 4, 60, 60};
+    float intervals[] = { 5, 5 };
+    paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 2.5f));
+    for (auto degrees : { 270, 360, 540, 720 } ) {
+        canvas->drawArc(oval, 0, degrees, false, paint);
+        canvas->translate(64, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawAtlas.cpp b/docs/examples/Canvas_drawAtlas.cpp
new file mode 100644
index 0000000..dcb64b1
--- /dev/null
+++ b/docs/examples/Canvas_drawAtlas.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1df575f9b8132306ce0552a2554ed132
+REG_FIDDLE(Canvas_drawAtlas, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+  // SkBitmap source = mandrill;
+  SkRSXform xforms[] = { { .5f, 0, 0, 0 }, {0, .5f, 200, 100 } };
+  SkRect tex[] = { { 0, 0, 250, 250 }, { 0, 0, 250, 250 } };
+  SkColor colors[] = { 0x7f55aa00, 0x7f3333bf };
+  const SkImage* imagePtr = image.get();
+  canvas->drawAtlas(imagePtr, xforms, tex, colors, 2, SkBlendMode::kSrcOver, nullptr, nullptr);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawAtlas_2.cpp b/docs/examples/Canvas_drawAtlas_2.cpp
new file mode 100644
index 0000000..ccae9f3
--- /dev/null
+++ b/docs/examples/Canvas_drawAtlas_2.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0e66a8f230a8d531bcef9f5ebdc5aac1
+REG_FIDDLE(Canvas_drawAtlas_2, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+  // SkBitmap source = mandrill;
+  SkRSXform xforms[] = { { .5f, 0, 0, 0 }, {0, .5f, 200, 100 } };
+  SkRect tex[] = { { 0, 0, 250, 250 }, { 0, 0, 250, 250 } };
+  SkColor colors[] = { 0x7f55aa00, 0x7f3333bf };
+  SkPaint paint;
+  paint.setAlpha(127);
+  canvas->drawAtlas(image, xforms, tex, colors, 2, SkBlendMode::kPlus, nullptr, &paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawAtlas_3.cpp b/docs/examples/Canvas_drawAtlas_3.cpp
new file mode 100644
index 0000000..0cd31af
--- /dev/null
+++ b/docs/examples/Canvas_drawAtlas_3.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8dc0d0fdeab20bbc21cac6874ddbefcd
+REG_FIDDLE(Canvas_drawAtlas_3, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+  // sk_sp<SkImage> image = mandrill;
+  SkRSXform xforms[] = { { .5f, 0, 0, 0 }, {0, .5f, 200, 100 } };
+  SkRect tex[] = { { 0, 0, 250, 250 }, { 0, 0, 250, 250 } };
+  const SkImage* imagePtr = image.get();
+  canvas->drawAtlas(imagePtr, xforms, tex, 2, nullptr, nullptr);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawAtlas_4.cpp b/docs/examples/Canvas_drawAtlas_4.cpp
new file mode 100644
index 0000000..33ce86e
--- /dev/null
+++ b/docs/examples/Canvas_drawAtlas_4.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c093c2b14bd3e6171ede7cd4049d9b57
+REG_FIDDLE(Canvas_drawAtlas_4, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+  // sk_sp<SkImage> image = mandrill;
+  SkRSXform xforms[] = { { 1, 0, 0, 0 }, {0, 1, 300, 100 } };
+  SkRect tex[] = { { 0, 0, 200, 200 }, { 200, 0, 400, 200 } };
+  canvas->drawAtlas(image, xforms, tex, 2, nullptr, nullptr);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawBitmap.cpp b/docs/examples/Canvas_drawBitmap.cpp
new file mode 100644
index 0000000..b21c09e
--- /dev/null
+++ b/docs/examples/Canvas_drawBitmap.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4a521be1f850058541e136a808c65e78
+REG_FIDDLE(Canvas_drawBitmap, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint8_t pixels[][8] = { { 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00},
+                            { 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00},
+                            { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00},
+                            { 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF},
+                            { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+                            { 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00},
+                            { 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00},
+                            { 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF} };
+    SkBitmap bitmap;
+    bitmap.installPixels(SkImageInfo::MakeA8(8, 8),
+            (void*) pixels, sizeof(pixels[0]));
+    SkPaint paint;
+    canvas->scale(4, 4);
+    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xFF007F00} ) {
+        paint.setColor(color);
+        canvas->drawBitmap(bitmap, 0, 0, &paint);
+        canvas->translate(12, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawBitmapLattice.cpp b/docs/examples/Canvas_drawBitmapLattice.cpp
new file mode 100644
index 0000000..6ab2254
--- /dev/null
+++ b/docs/examples/Canvas_drawBitmapLattice.cpp
@@ -0,0 +1,37 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c5bfa944e17ba4a4400dc799f032069c
+REG_FIDDLE(Canvas_drawBitmapLattice, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect center = { 20, 10, 50, 40 };
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(60, 60));
+    SkCanvas bitCanvas(bitmap);
+    SkPaint paint;
+    SkColor gray = 0xFF000000;
+    int left = 0;
+    for (auto right: { center.fLeft, center.fRight, bitmap.width() } ) {
+        int top = 0;
+        for (auto bottom: { center.fTop, center.fBottom, bitmap.height() } ) {
+            paint.setColor(gray);
+            bitCanvas.drawIRect(SkIRect::MakeLTRB(left, top, right, bottom), paint);
+            gray += 0x001f1f1f;
+            top = bottom;
+        }
+        left = right;
+    }
+    const int xDivs[] = { center.fLeft, center.fRight };
+    const int yDivs[] = { center.fTop, center.fBottom };
+    SkCanvas::Lattice::RectType fillTypes[3][3];
+    memset(fillTypes, 0, sizeof(fillTypes));
+    fillTypes[1][1] = SkCanvas::Lattice::kTransparent;
+    SkColor dummy[9];  // temporary pending bug fix
+    SkCanvas::Lattice lattice = { xDivs, yDivs, fillTypes[0], SK_ARRAY_COUNT(xDivs),
+         SK_ARRAY_COUNT(yDivs), nullptr, dummy };
+    for (auto dest: { 20, 30, 40, 60, 90 } ) {
+        canvas->drawBitmapLattice(bitmap, lattice, SkRect::MakeWH(dest, 110 - dest), nullptr);
+        canvas->translate(dest + 4, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawBitmapNine.cpp b/docs/examples/Canvas_drawBitmapNine.cpp
new file mode 100644
index 0000000..e6d7bc3
--- /dev/null
+++ b/docs/examples/Canvas_drawBitmapNine.cpp
@@ -0,0 +1,29 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e99e7be0d8f67dfacbecf85df585433d
+REG_FIDDLE(Canvas_drawBitmapNine, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect center = { 20, 10, 50, 40 };
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(60, 60));
+    SkCanvas bitCanvas(bitmap);
+    SkPaint paint;
+    SkColor gray = 0xFF000000;
+    int left = 0;
+    for (auto right: { center.fLeft, center.fRight, bitmap.width() } ) {
+        int top = 0;
+        for (auto bottom: { center.fTop, center.fBottom, bitmap.height() } ) {
+            paint.setColor(gray);
+            bitCanvas.drawIRect(SkIRect::MakeLTRB(left, top, right, bottom), paint);
+            gray += 0x001f1f1f;
+            top = bottom;
+        }
+        left = right;
+    }
+    for (auto dest: { 20, 30, 40, 60, 90 } ) {
+        canvas->drawBitmapNine(bitmap, center, SkRect::MakeWH(dest, 110 - dest), nullptr);
+        canvas->translate(dest + 4, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawBitmapRect.cpp b/docs/examples/Canvas_drawBitmapRect.cpp
new file mode 100644
index 0000000..ae78fa9
--- /dev/null
+++ b/docs/examples/Canvas_drawBitmapRect.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7d04932f2a259cc70d6e45cd25a6feb6
+REG_FIDDLE(Canvas_drawBitmapRect, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint8_t pixels[][8] = { { 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00},
+                            { 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00},
+                            { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00},
+                            { 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF},
+                            { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+                            { 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00},
+                            { 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00},
+                            { 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00} };
+    SkBitmap bitmap;
+    bitmap.installPixels(SkImageInfo::MakeA8(8, 8),
+            (void*) pixels, sizeof(pixels[0]));
+    SkPaint paint;
+    paint.setMaskFilter(SkMaskFilter::MakeBlur(kSolid_SkBlurStyle, 6));
+    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xFF007F00} ) {
+        paint.setColor(color);
+        canvas->drawBitmapRect(bitmap, SkRect::MakeWH(8, 8), SkRect::MakeWH(32, 32), &paint);
+        canvas->translate(48, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawBitmapRect_2.cpp b/docs/examples/Canvas_drawBitmapRect_2.cpp
new file mode 100644
index 0000000..1eda940
--- /dev/null
+++ b/docs/examples/Canvas_drawBitmapRect_2.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0a3c6d2459566e58cee7d4910655ee21
+REG_FIDDLE(Canvas_drawBitmapRect_2, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint8_t pixels[][8] = { { 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00},
+                            { 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00},
+                            { 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF},
+                            { 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF},
+                            { 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF},
+                            { 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF},
+                            { 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00},
+                            { 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00} };
+    SkBitmap bitmap;
+    bitmap.installPixels(SkImageInfo::MakeA8(8, 8),
+            (void*) pixels, sizeof(pixels[0]));
+    SkPaint paint;
+    paint.setFilterQuality(kHigh_SkFilterQuality);
+    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xFF007F00, 0xFF7f007f} ) {
+        paint.setColor(color);
+        canvas->drawBitmapRect(bitmap, SkIRect::MakeWH(8, 8), SkRect::MakeWH(32, 32), &paint);
+        canvas->translate(48.25f, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawBitmapRect_3.cpp b/docs/examples/Canvas_drawBitmapRect_3.cpp
new file mode 100644
index 0000000..30d9bca
--- /dev/null
+++ b/docs/examples/Canvas_drawBitmapRect_3.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bdbeac3c97f60a63987b1cc8e1f1e91e
+REG_FIDDLE(Canvas_drawBitmapRect_3, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint32_t pixels[][2] = { { 0x00000000, 0x55550000},
+                             { 0xAAAA0000, 0xFFFF0000} };
+    SkBitmap bitmap;
+    bitmap.installPixels(SkImageInfo::MakeN32Premul(2, 2),
+            (void*) pixels, sizeof(pixels[0]));
+    SkPaint paint;
+    canvas->scale(4, 4);
+    for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN } ) {
+        paint.setColorFilter(SkColorFilters::Blend(color, SkBlendMode::kPlus));
+        canvas->drawBitmapRect(bitmap, SkRect::MakeWH(8, 8), &paint);
+        canvas->translate(8, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawCircle.cpp b/docs/examples/Canvas_drawCircle.cpp
new file mode 100644
index 0000000..da5c462
--- /dev/null
+++ b/docs/examples/Canvas_drawCircle.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=841229e25ca9dfb68bd0dc4dfff356eb
+REG_FIDDLE(Canvas_drawCircle, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    canvas->drawCircle(128, 128, 90, paint);
+    paint.setColor(SK_ColorWHITE);
+    canvas->drawCircle(86, 86, 20, paint);
+    canvas->drawCircle(160, 76, 20, paint);
+    canvas->drawCircle(140, 150, 35, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawCircle_2.cpp b/docs/examples/Canvas_drawCircle_2.cpp
new file mode 100644
index 0000000..1e4b4ce
--- /dev/null
+++ b/docs/examples/Canvas_drawCircle_2.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9303ffae45ddd0b0a1f93d816a1762f4
+REG_FIDDLE(Canvas_drawCircle_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    canvas->drawCircle(128, 128, 90, paint);
+    paint.setColor(SK_ColorWHITE);
+    canvas->drawCircle({86, 86}, 20, paint);
+    canvas->drawCircle({160, 76}, 20, paint);
+    canvas->drawCircle({140, 150}, 35, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawColor.cpp b/docs/examples/Canvas_drawColor.cpp
new file mode 100644
index 0000000..a6dfc48
--- /dev/null
+++ b/docs/examples/Canvas_drawColor.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9cf94fead1e6b17d836c704b4eac269a
+REG_FIDDLE(Canvas_drawColor, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->drawColor(SK_ColorRED);
+    canvas->clipRect(SkRect::MakeWH(150, 150));
+    canvas->drawColor(SkColorSetARGB(0x80, 0x00, 0xFF, 0x00), SkBlendMode::kPlus);
+    canvas->clipRect(SkRect::MakeWH(75, 75));
+    canvas->drawColor(SkColorSetARGB(0x80, 0x00, 0x00, 0xFF), SkBlendMode::kPlus);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawDRRect_a.cpp b/docs/examples/Canvas_drawDRRect_a.cpp
new file mode 100644
index 0000000..0079023
--- /dev/null
+++ b/docs/examples/Canvas_drawDRRect_a.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=02e33141f13da2f19aef7feb7117b541
+REG_FIDDLE(Canvas_drawDRRect_a, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+   SkRRect outer = SkRRect::MakeRect({20, 40, 210, 200});
+   SkRRect inner = SkRRect::MakeOval({60, 70, 170, 160});
+   SkPaint paint;
+   canvas->drawDRRect(outer, inner, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawDRRect_b.cpp b/docs/examples/Canvas_drawDRRect_b.cpp
new file mode 100644
index 0000000..33306e5
--- /dev/null
+++ b/docs/examples/Canvas_drawDRRect_b.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=30823cb4edf884d330285ea161664931
+REG_FIDDLE(Canvas_drawDRRect_b, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+   SkRRect outer = SkRRect::MakeRect({20, 40, 210, 200});
+   SkRRect inner = SkRRect::MakeRectXY({60, 70, 170, 160}, 10, 10);
+   SkPaint paint;
+   paint.setAntiAlias(true);
+   paint.setStyle(SkPaint::kStroke_Style);
+   paint.setStrokeWidth(20);
+   paint.setStrokeJoin(SkPaint::kRound_Join);
+   canvas->drawDRRect(outer, inner, paint);
+   paint.setStrokeWidth(1);
+   paint.setColor(SK_ColorWHITE);
+   canvas->drawDRRect(outer, inner, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawDrawable.cpp b/docs/examples/Canvas_drawDrawable.cpp
new file mode 100644
index 0000000..4e05c27
--- /dev/null
+++ b/docs/examples/Canvas_drawDrawable.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3a4dfcd08838866b5cfc0d82489195ba
+REG_FIDDLE(Canvas_drawDrawable, 256, 100, false, 0) {
+struct MyDrawable : public SkDrawable {
+    SkRect onGetBounds() override { return SkRect::MakeWH(50, 100);  }
+    void onDraw(SkCanvas* canvas) override {
+       SkPath path;
+       path.conicTo(10, 90, 50, 90, 0.9f);
+       SkPaint paint;
+       paint.setColor(SK_ColorBLUE);
+       canvas->drawRect(path.getBounds(), paint);
+       paint.setAntiAlias(true);
+       paint.setColor(SK_ColorWHITE);
+       canvas->drawPath(path, paint);
+    }
+};
+
+void draw(SkCanvas* canvas) {
+    sk_sp<SkDrawable> drawable(new MyDrawable);
+  SkMatrix matrix;
+  matrix.setTranslate(10, 10);
+  canvas->drawDrawable(drawable.get(), &matrix);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawDrawable_2.cpp b/docs/examples/Canvas_drawDrawable_2.cpp
new file mode 100644
index 0000000..b450b65
--- /dev/null
+++ b/docs/examples/Canvas_drawDrawable_2.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1bdc07ad3b154c89b771722c2fcaee3f
+REG_FIDDLE(Canvas_drawDrawable_2, 256, 100, false, 0) {
+struct MyDrawable : public SkDrawable {
+    SkRect onGetBounds() override { return SkRect::MakeWH(50, 100);  }
+    void onDraw(SkCanvas* canvas) override {
+       SkPath path;
+       path.conicTo(10, 90, 50, 90, 0.9f);
+       SkPaint paint;
+       paint.setColor(SK_ColorBLUE);
+       canvas->drawRect(path.getBounds(), paint);
+       paint.setAntiAlias(true);
+       paint.setColor(SK_ColorWHITE);
+       canvas->drawPath(path, paint);
+    }
+};
+
+void draw(SkCanvas* canvas) {
+    sk_sp<SkDrawable> drawable(new MyDrawable);
+  canvas->drawDrawable(drawable.get(), 10, 10);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawIRect.cpp b/docs/examples/Canvas_drawIRect.cpp
new file mode 100644
index 0000000..2d7a57c
--- /dev/null
+++ b/docs/examples/Canvas_drawIRect.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d3d8ca584134560750b1efa4a4c6e138
+REG_FIDDLE(Canvas_drawIRect, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 64, 48, 192, 160 };
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(20);
+    paint.setStrokeJoin(SkPaint::kRound_Join);
+    for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorYELLOW, SK_ColorMAGENTA } ) {
+        paint.setColor(color);
+        canvas->drawIRect(rect, paint);
+        canvas->rotate(30, 128, 128);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawImage.cpp b/docs/examples/Canvas_drawImage.cpp
new file mode 100644
index 0000000..f11c80c
--- /dev/null
+++ b/docs/examples/Canvas_drawImage.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=185746dc0faa6f1df30c4afe098646ff
+REG_FIDDLE(Canvas_drawImage, 256, 64, false, 4) {
+void draw(SkCanvas* canvas) {
+   // sk_sp<SkImage> image;
+   SkImage* imagePtr = image.get();
+   canvas->drawImage(imagePtr, 0, 0);
+   SkPaint paint;
+   canvas->drawImage(imagePtr, 80, 0, &paint);
+   paint.setAlpha(0x80);
+   canvas->drawImage(imagePtr, 160, 0, &paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawImageNine.cpp b/docs/examples/Canvas_drawImageNine.cpp
new file mode 100644
index 0000000..e3a365e
--- /dev/null
+++ b/docs/examples/Canvas_drawImageNine.cpp
@@ -0,0 +1,31 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4f153cf1d0dbe1a95acf5badeec14dae
+REG_FIDDLE(Canvas_drawImageNine, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect center = { 20, 10, 50, 40 };
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(60, 60));
+    SkCanvas bitCanvas(bitmap);
+    SkPaint paint;
+    SkColor gray = 0xFF000000;
+    int left = 0;
+    for (auto right: { center.fLeft, center.fRight, bitmap.width() } ) {
+        int top = 0;
+        for (auto bottom: { center.fTop, center.fBottom, bitmap.height() } ) {
+            paint.setColor(gray);
+            bitCanvas.drawIRect(SkIRect::MakeLTRB(left, top, right, bottom), paint);
+            gray += 0x001f1f1f;
+            top = bottom;
+        }
+        left = right;
+    }
+    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
+    SkImage* imagePtr = image.get();
+    for (auto dest: { 20, 30, 40, 60, 90 } ) {
+        canvas->drawImageNine(imagePtr, center, SkRect::MakeWH(dest, dest), nullptr);
+        canvas->translate(dest + 4, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawImageNine_2.cpp b/docs/examples/Canvas_drawImageNine_2.cpp
new file mode 100644
index 0000000..eaa9f0d
--- /dev/null
+++ b/docs/examples/Canvas_drawImageNine_2.cpp
@@ -0,0 +1,30 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d597d9af8d17fd93e634dd12017058e2
+REG_FIDDLE(Canvas_drawImageNine_2, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect center = { 20, 10, 50, 40 };
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(60, 60));
+    SkCanvas bitCanvas(bitmap);
+    SkPaint paint;
+    SkColor gray = 0xFF000000;
+    int left = 0;
+    for (auto right: { center.fLeft, center.fRight, bitmap.width() } ) {
+        int top = 0;
+        for (auto bottom: { center.fTop, center.fBottom, bitmap.height() } ) {
+            paint.setColor(gray);
+            bitCanvas.drawIRect(SkIRect::MakeLTRB(left, top, right, bottom), paint);
+            gray += 0x001f1f1f;
+            top = bottom;
+        }
+        left = right;
+    }
+    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
+    for (auto dest: { 20, 30, 40, 60, 90 } ) {
+        canvas->drawImageNine(image, center, SkRect::MakeWH(dest, 110 - dest), nullptr);
+        canvas->translate(dest + 4, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawImageRect.cpp b/docs/examples/Canvas_drawImageRect.cpp
new file mode 100644
index 0000000..f2648bd
--- /dev/null
+++ b/docs/examples/Canvas_drawImageRect.cpp
@@ -0,0 +1,27 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bfd18e9cac896cdf94c9f154ccf94be8
+REG_FIDDLE(Canvas_drawImageRect, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint32_t pixels[][4] = {
+            { 0xFFFF0000, 0xFFFF0000, 0xFFFF0000, 0xFFFF0000 },
+            { 0xFFFF0000, 0xFF000000, 0xFFFFFFFF, 0xFFFF0000 },
+            { 0xFFFF0000, 0xFFFFFFFF, 0xFF000000, 0xFFFF0000 },
+            { 0xFFFF0000, 0xFFFF0000, 0xFFFF0000, 0xFFFF0000 } };
+    SkBitmap redBorder;
+    redBorder.installPixels(SkImageInfo::MakeN32Premul(4, 4),
+            (void*) pixels, sizeof(pixels[0]));
+    sk_sp<SkImage> image = SkImage::MakeFromBitmap(redBorder);
+    SkPaint lowPaint;
+    for (auto constraint : {
+            SkCanvas::kFast_SrcRectConstraint,
+            SkCanvas::kStrict_SrcRectConstraint,
+            SkCanvas::kFast_SrcRectConstraint } ) {
+        canvas->drawImageRect(image.get(), SkRect::MakeLTRB(1, 1, 3, 3),
+                SkRect::MakeLTRB(16, 16, 48, 48), &lowPaint, constraint);
+        lowPaint.setFilterQuality(kLow_SkFilterQuality);
+        canvas->translate(80, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawImageRect_2.cpp b/docs/examples/Canvas_drawImageRect_2.cpp
new file mode 100644
index 0000000..f7daec0
--- /dev/null
+++ b/docs/examples/Canvas_drawImageRect_2.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7f92cd5c9b9f4b1ac3cd933b08037bfe
+REG_FIDDLE(Canvas_drawImageRect_2, 256, 256, false, 4) {
+void draw(SkCanvas* canvas) {
+    // sk_sp<SkImage> image;
+    for (auto i : { 1, 2, 4, 8 } ) {
+        canvas->drawImageRect(image.get(), SkIRect::MakeLTRB(0, 0, 100, 100),
+                SkRect::MakeXYWH(i * 20, i * 20, i * 20, i * 20), nullptr);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawImageRect_3.cpp b/docs/examples/Canvas_drawImageRect_3.cpp
new file mode 100644
index 0000000..904b6c7
--- /dev/null
+++ b/docs/examples/Canvas_drawImageRect_3.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3cf8fb639fef99993cafc064d550c739
+REG_FIDDLE(Canvas_drawImageRect_3, 256, 256, false, 4) {
+void draw(SkCanvas* canvas) {
+    // sk_sp<SkImage> image;
+    for (auto i : { 20, 40, 80, 160 } ) {
+        canvas->drawImageRect(image.get(), SkRect::MakeXYWH(i, i, i, i), nullptr);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawImageRect_4.cpp b/docs/examples/Canvas_drawImageRect_4.cpp
new file mode 100644
index 0000000..38c8cd4
--- /dev/null
+++ b/docs/examples/Canvas_drawImageRect_4.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d4b35a9d24c32c042bd1f529b8de3c0d
+REG_FIDDLE(Canvas_drawImageRect_4, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint32_t pixels[][2] = { { SK_ColorBLACK, SK_ColorWHITE },
+                             { SK_ColorWHITE, SK_ColorBLACK } };
+    SkBitmap bitmap;
+    bitmap.installPixels(SkImageInfo::MakeN32Premul(2, 2),
+            (void*) pixels, sizeof(pixels[0]));
+    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
+    SkPaint paint;
+    canvas->scale(4, 4);
+    for (auto alpha : { 50, 100, 150, 255 } ) {
+        paint.setAlpha(alpha);
+        canvas->drawImageRect(image, SkRect::MakeWH(2, 2), SkRect::MakeWH(8, 8), &paint);
+        canvas->translate(8, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawImageRect_5.cpp b/docs/examples/Canvas_drawImageRect_5.cpp
new file mode 100644
index 0000000..223bdcb
--- /dev/null
+++ b/docs/examples/Canvas_drawImageRect_5.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d307e7e1237f39fb54d80723e5449857
+REG_FIDDLE(Canvas_drawImageRect_5, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint32_t pixels[][2] = { { 0x00000000, 0x55555555},
+                             { 0xAAAAAAAA, 0xFFFFFFFF} };
+    SkBitmap bitmap;
+    bitmap.installPixels(SkImageInfo::MakeN32Premul(2, 2),
+            (void*) pixels, sizeof(pixels[0]));
+    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
+    SkPaint paint;
+    canvas->scale(4, 4);
+    for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN } ) {
+        paint.setColorFilter(SkColorFilters::Blend(color, SkBlendMode::kPlus));
+        canvas->drawImageRect(image, SkIRect::MakeWH(2, 2), SkRect::MakeWH(8, 8), &paint);
+        canvas->translate(8, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawImageRect_6.cpp b/docs/examples/Canvas_drawImageRect_6.cpp
new file mode 100644
index 0000000..1d3fcb9
--- /dev/null
+++ b/docs/examples/Canvas_drawImageRect_6.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3a47ef94cb70144455f80333d8653e6c
+REG_FIDDLE(Canvas_drawImageRect_6, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint32_t pixels[][2] = { { 0x00000000, 0x55550000},
+                             { 0xAAAA0000, 0xFFFF0000} };
+    SkBitmap bitmap;
+    bitmap.installPixels(SkImageInfo::MakeN32Premul(2, 2),
+            (void*) pixels, sizeof(pixels[0]));
+    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
+    SkPaint paint;
+    canvas->scale(4, 4);
+    for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN } ) {
+        paint.setColorFilter(SkColorFilters::Blend(color, SkBlendMode::kPlus));
+        canvas->drawImageRect(image, SkRect::MakeWH(8, 8), &paint);
+        canvas->translate(8, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawImage_2.cpp b/docs/examples/Canvas_drawImage_2.cpp
new file mode 100644
index 0000000..63c7514
--- /dev/null
+++ b/docs/examples/Canvas_drawImage_2.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a4e877e891b1be5faa2b7fd07f673a10
+REG_FIDDLE(Canvas_drawImage_2, 256, 64, false, 4) {
+void draw(SkCanvas* canvas) {
+   // sk_sp<SkImage> image;
+   canvas->drawImage(image, 0, 0);
+   SkPaint paint;
+   canvas->drawImage(image, 80, 0, &paint);
+   paint.setAlpha(0x80);
+   canvas->drawImage(image, 160, 0, &paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawLine.cpp b/docs/examples/Canvas_drawLine.cpp
new file mode 100644
index 0000000..72d2ed7
--- /dev/null
+++ b/docs/examples/Canvas_drawLine.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d10ee4a265f278d02afe11ad889b293b
+REG_FIDDLE(Canvas_drawLine, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setColor(0xFF9a67be);
+    paint.setStrokeWidth(20);
+    canvas->skew(1, 0);
+    canvas->drawLine(32, 96, 32, 160, paint);
+    canvas->skew(-2, 0);
+    canvas->drawLine(288, 96, 288, 160, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawLine_2.cpp b/docs/examples/Canvas_drawLine_2.cpp
new file mode 100644
index 0000000..1c47f59
--- /dev/null
+++ b/docs/examples/Canvas_drawLine_2.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f8525816cb596dde1a3855446792c8e0
+REG_FIDDLE(Canvas_drawLine_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setColor(0xFF9a67be);
+    paint.setStrokeWidth(20);
+    canvas->skew(1, 0);
+    canvas->drawLine({32, 96}, {32, 160}, paint);
+    canvas->skew(-2, 0);
+    canvas->drawLine({288, 96}, {288, 160}, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawOval.cpp b/docs/examples/Canvas_drawOval.cpp
new file mode 100644
index 0000000..3e254ca
--- /dev/null
+++ b/docs/examples/Canvas_drawOval.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8b6b86f8a022811cd29a9c6ab771df12
+REG_FIDDLE(Canvas_drawOval, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->clear(0xFF3f5f9f);
+    SkColor  kColor1 = SkColorSetARGB(0xff, 0xff, 0x7f, 0);
+    SkColor  g1Colors[] = { kColor1, SkColorSetA(kColor1, 0x20) };
+    SkPoint  g1Points[] = { { 0, 0 }, { 0, 100 } };
+    SkScalar pos[] = { 0.2f, 1.0f };
+    SkRect bounds = SkRect::MakeWH(80, 70);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setShader(SkGradientShader::MakeLinear(g1Points, g1Colors, pos, SK_ARRAY_COUNT(g1Colors),
+            SkTileMode::kClamp));
+    canvas->drawOval(bounds , paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawPaint.cpp b/docs/examples/Canvas_drawPaint.cpp
new file mode 100644
index 0000000..2968ceb
--- /dev/null
+++ b/docs/examples/Canvas_drawPaint.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1cd076b9b1a7c976cdca72b93c4f42dd
+REG_FIDDLE(Canvas_drawPaint, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkColor     colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE };
+    SkScalar    pos[] = { 0, SK_Scalar1/2, SK_Scalar1 };
+    SkPaint     paint;
+    paint.setShader(SkGradientShader::MakeSweep(256, 256, colors, pos, SK_ARRAY_COUNT(colors)));
+    canvas->drawPaint(paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawPatch.cpp b/docs/examples/Canvas_drawPatch.cpp
new file mode 100644
index 0000000..83c3a81
--- /dev/null
+++ b/docs/examples/Canvas_drawPatch.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=accb545d67984ced168f5be6ab824795
+REG_FIDDLE(Canvas_drawPatch, 256, 256, false, 5) {
+void draw(SkCanvas* canvas) {
+    // SkBitmap source = cmbkygk;
+    SkPaint paint;
+    paint.setFilterQuality(kLow_SkFilterQuality);
+    paint.setAntiAlias(true);
+    SkPoint cubics[] = { { 3, 1 },    { 4, 2 }, { 5, 1 },    { 7, 3 },
+                      /* { 7, 3 }, */ { 6, 4 }, { 7, 5 },    { 5, 7 },
+                      /* { 5, 7 }, */ { 4, 6 }, { 3, 7 },    { 1, 5 },
+                      /* { 1, 5 }, */ { 2, 4 }, { 1, 3 }, /* { 3, 1 } */ };
+    SkColor colors[] = { 0xbfff0000, 0xbf0000ff, 0xbfff00ff, 0xbf00ffff };
+    SkPoint texCoords[] = { { -30, -30 }, { 162, -30}, { 162, 162}, { -30, 162} };
+    paint.setShader(source.makeShader());
+    canvas->scale(15, 15);
+    for (auto blend : { SkBlendMode::kSrcOver, SkBlendMode::kModulate, SkBlendMode::kXor } ) {
+        canvas->drawPatch(cubics, colors, texCoords, blend, paint);
+        canvas->translate(4, 4);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawPatch_2_a.cpp b/docs/examples/Canvas_drawPatch_2_a.cpp
new file mode 100644
index 0000000..f7a1cc1
--- /dev/null
+++ b/docs/examples/Canvas_drawPatch_2_a.cpp
@@ -0,0 +1,32 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4e8b7409531c9211a2afcf632005a38c
+REG_FIDDLE(Canvas_drawPatch_2_a, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkPoint cubics[] = { { 3, 1 },    { 4, 2 }, { 5, 1 },    { 7, 3 },
+                      /* { 7, 3 }, */ { 6, 4 }, { 7, 5 },    { 5, 7 },
+                      /* { 5, 7 }, */ { 4, 6 }, { 3, 7 },    { 1, 5 },
+                      /* { 1, 5 }, */ { 2, 4 }, { 1, 3 }, /* { 3, 1 } */ };
+    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE, SK_ColorYELLOW, SK_ColorCYAN };
+    canvas->scale(30, 30);
+    canvas->drawPatch(cubics, colors, nullptr, paint);
+    SkPoint text[] = { {3,0.9f}, {4,2.5f}, {5,0.9f}, {7.5f,3.2f}, {5.5f,4.2f},
+            {7.5f,5.2f}, {5,7.5f}, {4,5.9f}, {3,7.5f}, {0.5f,5.2f}, {2.5f,4.2f},
+            {0.5f,3.2f} };
+    paint.setTextSize(18.f / 30);
+    for (int i = 0; i< 10; ++i) {
+       char digit = '0' + i;
+       canvas->drawText(&digit, 1, text[i].fX, text[i].fY, paint);
+    }
+    canvas->drawString("10", text[10].fX, text[10].fY, paint);
+    canvas->drawString("11", text[11].fX, text[11].fY, paint);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, 12, cubics, paint);
+    canvas->drawLine(cubics[11].fX, cubics[11].fY, cubics[0].fX, cubics[0].fY, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Canvas_drawPatch_2_b.cpp b/docs/examples/Canvas_drawPatch_2_b.cpp
new file mode 100644
index 0000000..bb8175a
--- /dev/null
+++ b/docs/examples/Canvas_drawPatch_2_b.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3412c2a16cb529af0e04878d264451f2
+REG_FIDDLE(Canvas_drawPatch_2_b, 256, 256, false, 6) {
+void draw(SkCanvas* canvas) {
+    // SkBitmap source = checkerboard;
+    SkPaint paint;
+    paint.setFilterQuality(kLow_SkFilterQuality);
+    paint.setAntiAlias(true);
+    SkPoint cubics[] = { { 3, 1 },    { 4, 2 }, { 5, 1 },    { 7, 3 },
+                      /* { 7, 3 }, */ { 6, 4 }, { 7, 5 },    { 5, 7 },
+                      /* { 5, 7 }, */ { 4, 6 }, { 3, 7 },    { 1, 5 },
+                      /* { 1, 5 }, */ { 2, 4 }, { 1, 3 }, /* { 3, 1 } */ };
+    SkPoint texCoords[] = { { 0, 0 }, { 0, 62}, { 62, 62}, { 62, 0 } };
+    paint.setShader(source.makeShader());
+    canvas->scale(30, 30);
+    canvas->drawPatch(cubics, nullptr, texCoords, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawPath.cpp b/docs/examples/Canvas_drawPath.cpp
new file mode 100644
index 0000000..6d07815
--- /dev/null
+++ b/docs/examples/Canvas_drawPath.cpp
@@ -0,0 +1,38 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=fe2294131f422b8d6752f6a880f98ad9
+REG_FIDDLE(Canvas_drawPath, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.moveTo(20, 20);
+    path.quadTo(60, 20, 60, 60);
+    path.close();
+    path.moveTo(60, 20);
+    path.quadTo(60, 60, 20, 60);
+    SkPaint paint;
+    paint.setStrokeWidth(10);
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    for (auto join: { SkPaint::kBevel_Join, SkPaint::kRound_Join, SkPaint::kMiter_Join } ) {
+        paint.setStrokeJoin(join);
+        for (auto cap: { SkPaint::kButt_Cap, SkPaint::kSquare_Cap, SkPaint::kRound_Cap  } ) {
+            paint.setStrokeCap(cap);
+            canvas->drawPath(path, paint);
+            canvas->translate(80, 0);
+        }
+        canvas->translate(-240, 60);
+    }
+    paint.setStyle(SkPaint::kFill_Style);
+    for (auto fill : { SkPath::kWinding_FillType,
+                       SkPath::kEvenOdd_FillType,
+                       SkPath::kInverseWinding_FillType } ) {
+        path.setFillType(fill);
+        canvas->save();
+        canvas->clipRect({0, 10, 80, 70});
+        canvas->drawPath(path, paint);
+        canvas->restore();
+        canvas->translate(80, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawPicture_2.cpp b/docs/examples/Canvas_drawPicture_2.cpp
new file mode 100644
index 0000000..97e58c1
--- /dev/null
+++ b/docs/examples/Canvas_drawPicture_2.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=83918a23fcffd47f59a1ef662c85a24c
+REG_FIDDLE(Canvas_drawPicture_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPictureRecorder recorder;
+    SkCanvas* recordingCanvas = recorder.beginRecording(50, 50);
+    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xff007f00 } ) {
+        SkPaint paint;
+        paint.setColor(color);
+        recordingCanvas->drawRect({10, 10, 30, 40}, paint);
+        recordingCanvas->translate(10, 10);
+        recordingCanvas->scale(1.2f, 1.4f);
+    }
+    sk_sp<SkPicture> playback = recorder.finishRecordingAsPicture();
+    canvas->drawPicture(playback);
+    canvas->scale(2, 2);
+    canvas->translate(50, 0);
+    canvas->drawPicture(playback);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawPicture_3.cpp b/docs/examples/Canvas_drawPicture_3.cpp
new file mode 100644
index 0000000..df76070
--- /dev/null
+++ b/docs/examples/Canvas_drawPicture_3.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=759e4e5bac680838added8f70884dcdc
+REG_FIDDLE(Canvas_drawPicture_3, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkPictureRecorder recorder;
+    SkCanvas* recordingCanvas = recorder.beginRecording(50, 50);
+    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xff007f00 } ) {
+        paint.setColor(color);
+        recordingCanvas->drawRect({10, 10, 30, 40}, paint);
+        recordingCanvas->translate(10, 10);
+        recordingCanvas->scale(1.2f, 1.4f);
+    }
+    sk_sp<SkPicture> playback = recorder.finishRecordingAsPicture();
+    const SkPicture* playbackPtr = playback.get();
+    SkMatrix matrix;
+    matrix.reset();
+    for (auto alpha : { 70, 140, 210 } ) {
+    paint.setAlpha(alpha);
+    canvas->drawPicture(playbackPtr, &matrix, &paint);
+    matrix.preTranslate(70, 70);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawPicture_4.cpp b/docs/examples/Canvas_drawPicture_4.cpp
new file mode 100644
index 0000000..f1520d5
--- /dev/null
+++ b/docs/examples/Canvas_drawPicture_4.cpp
@@ -0,0 +1,25 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c4ff59439dd2fc871925d4eeb0c84ca1
+REG_FIDDLE(Canvas_drawPicture_4, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkPictureRecorder recorder;
+    SkCanvas* recordingCanvas = recorder.beginRecording(50, 50);
+    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xff007f00 } ) {
+        paint.setColor(color);
+        recordingCanvas->drawRect({10, 10, 30, 40}, paint);
+        recordingCanvas->translate(10, 10);
+        recordingCanvas->scale(1.2f, 1.4f);
+    }
+    sk_sp<SkPicture> playback = recorder.finishRecordingAsPicture();
+    SkMatrix matrix;
+    matrix.reset();
+    for (auto alpha : { 70, 140, 210 } ) {
+    paint.setAlpha(alpha);
+    canvas->drawPicture(playback, &matrix, &paint);
+    matrix.preTranslate(70, 70);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawPoint.cpp b/docs/examples/Canvas_drawPoint.cpp
new file mode 100644
index 0000000..47c50b5
--- /dev/null
+++ b/docs/examples/Canvas_drawPoint.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3476b553e7b547b604a3f6969f02d933
+REG_FIDDLE(Canvas_drawPoint, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+  SkPaint paint;
+  paint.setAntiAlias(true);
+  paint.setColor(0x80349a45);
+  paint.setStyle(SkPaint::kStroke_Style);
+  paint.setStrokeWidth(100);
+  paint.setStrokeCap(SkPaint::kRound_Cap);
+  canvas->scale(1, 1.2f);
+  canvas->drawPoint(64, 96, paint);
+  canvas->scale(.6f, .8f);
+  paint.setColor(SK_ColorWHITE);
+  canvas->drawPoint(106, 120, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawPoint_2.cpp b/docs/examples/Canvas_drawPoint_2.cpp
new file mode 100644
index 0000000..8fa5e74
--- /dev/null
+++ b/docs/examples/Canvas_drawPoint_2.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1a0a839061c69d870acca2bcfbdf1a41
+REG_FIDDLE(Canvas_drawPoint_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+  SkPaint paint;
+  paint.setAntiAlias(true);
+  paint.setColor(0x80349a45);
+  paint.setStyle(SkPaint::kStroke_Style);
+  paint.setStrokeWidth(100);
+  paint.setStrokeCap(SkPaint::kSquare_Cap);
+  canvas->scale(1, 1.2f);
+  canvas->drawPoint({64, 96}, paint);
+  canvas->scale(.6f, .8f);
+  paint.setColor(SK_ColorWHITE);
+  canvas->drawPoint(106, 120, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawPoints.cpp b/docs/examples/Canvas_drawPoints.cpp
new file mode 100644
index 0000000..5de4426
--- /dev/null
+++ b/docs/examples/Canvas_drawPoints.cpp
@@ -0,0 +1,32 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=635d54b4716e226e93dfbc21ad40e77d
+REG_FIDDLE(Canvas_drawPoints, 256, 200, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(10);
+    paint.setColor(0x80349a45);
+    const SkPoint points[] = {{32, 16}, {48, 48}, {16, 32}};
+    const SkPaint::Join join[] = { SkPaint::kRound_Join,
+                                   SkPaint::kMiter_Join,
+                                   SkPaint::kBevel_Join };
+    int joinIndex = 0;
+    SkPath path;
+    path.addPoly(points, 3, false);
+    for (const auto cap : { SkPaint::kRound_Cap, SkPaint::kSquare_Cap, SkPaint::kButt_Cap } ) {
+        paint.setStrokeCap(cap);
+        paint.setStrokeJoin(join[joinIndex++]);
+        for (const auto mode : { SkCanvas::kPoints_PointMode,
+                                 SkCanvas::kLines_PointMode,
+                                 SkCanvas::kPolygon_PointMode } ) {
+            canvas->drawPoints(mode, 3, points, paint);
+            canvas->translate(64, 0);
+        }
+        canvas->drawPath(path, paint);
+        canvas->translate(-192, 64);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawPosText.cpp b/docs/examples/Canvas_drawPosText.cpp
new file mode 100644
index 0000000..0ca581e
--- /dev/null
+++ b/docs/examples/Canvas_drawPosText.cpp
@@ -0,0 +1,16 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bf0b2402533a23b6392e0676b7a8414c
+REG_FIDDLE(Canvas_drawPosText, 256, 120, false, 0) {
+void draw(SkCanvas* canvas) {
+  const char hello[] = "HeLLo!";
+  const SkPoint pos[] = { {40, 100}, {82, 95}, {115, 110}, {130, 95}, {145, 85},
+    {172, 100} };
+  SkPaint paint;
+  paint.setTextSize(60);
+  canvas->drawPosText(hello, strlen(hello), pos, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Canvas_drawPosTextH.cpp b/docs/examples/Canvas_drawPosTextH.cpp
new file mode 100644
index 0000000..e72adce
--- /dev/null
+++ b/docs/examples/Canvas_drawPosTextH.cpp
@@ -0,0 +1,13 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=95c6a7ef82993a8d2add676080e9438a
+REG_FIDDLE(Canvas_drawPosTextH, 256, 40, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkScalar xpos[] = { 20, 40, 80, 160 };
+    SkPaint paint;
+    canvas->drawPosTextH("XXXX", 4, xpos, 20, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Canvas_drawRRect.cpp b/docs/examples/Canvas_drawRRect.cpp
new file mode 100644
index 0000000..99d6143
--- /dev/null
+++ b/docs/examples/Canvas_drawRRect.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=90fed1bb11efb43aada94113338c63d8
+REG_FIDDLE(Canvas_drawRRect, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkRect outer = {30, 40, 210, 220};
+    SkRect radii = {30, 50, 70, 90 };
+    SkRRect rRect;
+    rRect.setNinePatch(outer, radii.fLeft, radii.fTop, radii.fRight, radii.fBottom);
+    canvas->drawRRect(rRect, paint);
+    paint.setColor(SK_ColorWHITE);
+    canvas->drawLine(outer.fLeft + radii.fLeft, outer.fTop,
+                     outer.fLeft + radii.fLeft, outer.fBottom, paint);
+    canvas->drawLine(outer.fRight - radii.fRight, outer.fTop,
+                     outer.fRight - radii.fRight, outer.fBottom, paint);
+    canvas->drawLine(outer.fLeft,  outer.fTop + radii.fTop,
+                     outer.fRight, outer.fTop + radii.fTop, paint);
+    canvas->drawLine(outer.fLeft,  outer.fBottom - radii.fBottom,
+                     outer.fRight, outer.fBottom - radii.fBottom, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawRect.cpp b/docs/examples/Canvas_drawRect.cpp
new file mode 100644
index 0000000..afb88f5
--- /dev/null
+++ b/docs/examples/Canvas_drawRect.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=871b0da9b4a23de11ae7a772ce14aed3
+REG_FIDDLE(Canvas_drawRect, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint rectPts[] = { {64, 48}, {192, 160} };
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(20);
+    paint.setStrokeJoin(SkPaint::kRound_Join);
+    SkMatrix rotator;
+    rotator.setRotate(30, 128, 128);
+    for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorYELLOW, SK_ColorMAGENTA } ) {
+        paint.setColor(color);
+        SkRect rect;
+        rect.set(rectPts[0], rectPts[1]);
+        canvas->drawRect(rect, paint);
+        rotator.mapPoints(rectPts, 2);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawRegion.cpp b/docs/examples/Canvas_drawRegion.cpp
new file mode 100644
index 0000000..a312ff2
--- /dev/null
+++ b/docs/examples/Canvas_drawRegion.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=80309e0deca0f8add616cec7bec634ca
+REG_FIDDLE(Canvas_drawRegion, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion region;
+    region.op( 10, 10, 50, 50, SkRegion::kUnion_Op);
+    region.op( 10, 50, 90, 90, SkRegion::kUnion_Op);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(20);
+    paint.setStrokeJoin(SkPaint::kRound_Join);
+    canvas->drawRegion(region, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawRoundRect.cpp b/docs/examples/Canvas_drawRoundRect.cpp
new file mode 100644
index 0000000..edbd598
--- /dev/null
+++ b/docs/examples/Canvas_drawRoundRect.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=199fe818c09026c114e165bff166a39f
+REG_FIDDLE(Canvas_drawRoundRect, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkVector radii[] = { {0, 20}, {10, 10}, {10, 20}, {10, 40} };
+    SkPaint paint;
+    paint.setStrokeWidth(15);
+    paint.setStrokeJoin(SkPaint::kRound_Join);
+    paint.setAntiAlias(true);
+    for (auto style : { SkPaint::kStroke_Style, SkPaint::kFill_Style  } ) {
+        paint.setStyle(style );
+        for (size_t i = 0; i < SK_ARRAY_COUNT(radii); ++i) {
+           canvas->drawRoundRect({10, 10, 60, 40}, radii[i].fX, radii[i].fY, paint);
+           canvas->translate(0, 60);
+        }
+        canvas->translate(80, -240);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawString.cpp b/docs/examples/Canvas_drawString.cpp
new file mode 100644
index 0000000..510f4aa
--- /dev/null
+++ b/docs/examples/Canvas_drawString.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=85442cf8d0bce6b5a777853bc36a4dc4
+REG_FIDDLE(Canvas_drawString, 256, 48, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkFont font;
+    canvas->drawString("a small hello", 20, 20, font, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawString_2.cpp b/docs/examples/Canvas_drawString_2.cpp
new file mode 100644
index 0000000..f09a6c3
--- /dev/null
+++ b/docs/examples/Canvas_drawString_2.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=435178c09feb3bfec5e35d983609a013
+REG_FIDDLE(Canvas_drawString_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkString string("a small hello");
+    canvas->drawString(string, 20, 20, SkFont(), paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawText.cpp b/docs/examples/Canvas_drawText.cpp
new file mode 100644
index 0000000..46d7665
--- /dev/null
+++ b/docs/examples/Canvas_drawText.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=55f5e59350622c5e2834d1c85789f732
+REG_FIDDLE(Canvas_drawText, 256, 200, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkFont font;
+    float textSizes[] = { 12, 18, 24, 36 };
+    for (auto size: textSizes ) {
+        font.setSize(size);
+        canvas->drawString("Aa", 10, 20, font, paint);
+        canvas->translate(0, size * 2);
+    }
+    font = SkFont();
+    float yPos = 20;
+    for (auto size: textSizes ) {
+        float scale = size / 12.f;
+        canvas->resetMatrix();
+        canvas->translate(100, 0);
+        canvas->scale(scale, scale);
+        canvas->drawString("Aa", 10 / scale, yPos / scale, font, paint);
+        yPos += size * 2;
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawTextBlob.cpp b/docs/examples/Canvas_drawTextBlob.cpp
new file mode 100644
index 0000000..50588ce
--- /dev/null
+++ b/docs/examples/Canvas_drawTextBlob.cpp
@@ -0,0 +1,33 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=005502b502c1282cb8d306d6c8d998fb
+REG_FIDDLE(Canvas_drawTextBlob, 256, 120, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkTextBlobBuilder textBlobBuilder;
+    const char bunny[] = "/(^x^)\\";
+    const int len = sizeof(bunny) - 1;
+    uint16_t glyphs[len];
+    SkPaint paint;
+    paint.textToGlyphs(bunny, len, glyphs);
+    paint.setTextEncoding(kGlyphID_SkTextEncoding);
+    SkFont font;
+    int runs[] = { 3, 1, 3 };
+    SkPoint textPos = { 20, 100 };
+    int glyphIndex = 0;
+    for (auto runLen : runs) {
+        font.setSize(1 == runLen ? 20 : 50);
+        const SkTextBlobBuilder::RunBuffer& run =
+                textBlobBuilder.allocRun(font, runLen, textPos.fX, textPos.fY);
+        memcpy(run.glyphs, &glyphs[glyphIndex], sizeof(glyphs[0]) * runLen);
+        paint.setTextSize(1 == runLen ? 20 : 50);
+        textPos.fX += paint.measureText(&glyphs[glyphIndex], sizeof(glyphs[0]) * runLen, nullptr);
+        glyphIndex += runLen;
+    }
+    sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
+    paint.reset();
+    canvas->drawTextBlob(blob.get(), 0, 0, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Canvas_drawTextBlob_2.cpp b/docs/examples/Canvas_drawTextBlob_2.cpp
new file mode 100644
index 0000000..c8fe857
--- /dev/null
+++ b/docs/examples/Canvas_drawTextBlob_2.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1cae21e7b63b24de3eca0bbd9be1936b
+REG_FIDDLE(Canvas_drawTextBlob_2, 256, 120, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkTextBlobBuilder textBlobBuilder;
+    SkFont font;
+    font.setSize(50);
+    const SkTextBlobBuilder::RunBuffer& run =
+            textBlobBuilder.allocRun(font, 1, 20, 100);
+    run.glyphs[0] = 20;
+    sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
+    SkPaint paint;
+    paint.setColor(SK_ColorBLUE);
+    canvas->drawTextBlob(blob.get(), 0, 0, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawTextRSXform.cpp b/docs/examples/Canvas_drawTextRSXform.cpp
new file mode 100644
index 0000000..d1e1d06
--- /dev/null
+++ b/docs/examples/Canvas_drawTextRSXform.cpp
@@ -0,0 +1,25 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=935c8f8b9782d297a73d7186f6ef7945
+REG_FIDDLE(Canvas_drawTextRSXform, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    const int iterations = 26;
+    SkRSXform transforms[iterations];
+    char alphabet[iterations];
+    SkScalar angle = 0;
+    SkScalar scale = 1;
+    for (size_t i = 0; i < SK_ARRAY_COUNT(transforms); ++i) {
+        const SkScalar s = SkScalarSin(angle) * scale;
+        const SkScalar c = SkScalarCos(angle) * scale;
+        transforms[i] = SkRSXform::Make(-c, -s, -s * 16, c * 16);
+        angle += .45f;
+        scale += .2f;
+        alphabet[i] = 'A' + i;
+    }
+    SkPaint paint;
+    SkFont font(nullptr, 20);
+    auto spiral = SkTextBlob::MakeFromRSXform(alphabet, sizeof(alphabet), transforms, font);
+    canvas->drawTextBlob(spiral, 110, 138, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawVertices.cpp b/docs/examples/Canvas_drawVertices.cpp
new file mode 100644
index 0000000..d79d85c
--- /dev/null
+++ b/docs/examples/Canvas_drawVertices.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f48b22eaad1bb7adcc3faaa321754af6
+REG_FIDDLE(Canvas_drawVertices, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkPoint points[] = { { 0, 0 }, { 250, 0 }, { 100, 100 }, { 0, 250 } };
+    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE, SK_ColorYELLOW, SK_ColorCYAN };
+    auto vertices = SkVertices::MakeCopy(SkVertices::kTriangleFan_VertexMode,
+            SK_ARRAY_COUNT(points), points, nullptr, colors);
+    canvas->drawVertices(vertices.get(), SkBlendMode::kSrc, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_drawVertices_2.cpp b/docs/examples/Canvas_drawVertices_2.cpp
new file mode 100644
index 0000000..9a5a841
--- /dev/null
+++ b/docs/examples/Canvas_drawVertices_2.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e8bdae9bea3227758989028424fcac3d
+REG_FIDDLE(Canvas_drawVertices_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkPoint points[] = { { 0, 0 }, { 250, 0 }, { 100, 100 }, { 0, 250 } };
+    SkPoint texs[] = { { 0, 0 }, { 0, 250 }, { 250, 250 }, { 250, 0 } };
+    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE, SK_ColorYELLOW, SK_ColorCYAN };
+    paint.setShader(SkGradientShader::MakeLinear(points, colors, nullptr, 4, SkTileMode::kClamp));
+    auto vertices = SkVertices::MakeCopy(SkVertices::kTriangleFan_VertexMode,
+            SK_ARRAY_COUNT(points), points, texs, colors);
+    canvas->drawVertices(vertices, SkBlendMode::kDarken, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_empty_constructor.cpp b/docs/examples/Canvas_empty_constructor.cpp
new file mode 100644
index 0000000..9cf17fa
--- /dev/null
+++ b/docs/examples/Canvas_empty_constructor.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4a00e6589e862fde5be532f4b6e316ce
+REG_FIDDLE(Canvas_empty_constructor, 256, 256, true, 0) {
+static void check_for_rotated_ctm(const SkCanvas* canvas) {
+    const SkMatrix& matrix = canvas->getTotalMatrix();
+    SkDebugf("rect stays rect is %s\n", matrix.rectStaysRect() ? "true" : "false");
+}
+
+void draw(SkCanvas* canvas) {
+    check_for_rotated_ctm(canvas);
+    canvas->rotate(30);
+    check_for_rotated_ctm(canvas);
+    SkCanvas defaultCanvas;
+    check_for_rotated_ctm(&defaultCanvas);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_getBaseLayerSize.cpp b/docs/examples/Canvas_getBaseLayerSize.cpp
new file mode 100644
index 0000000..400033d
--- /dev/null
+++ b/docs/examples/Canvas_getBaseLayerSize.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=374e245d91cd729eca48fd20e631fdf3
+REG_FIDDLE(Canvas_getBaseLayerSize, 256, 256, true, 0) {
+void draw(SkCanvas* ) {
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(20, 30));
+    SkCanvas canvas(bitmap, SkSurfaceProps(0, kUnknown_SkPixelGeometry));
+    canvas.clipRect(SkRect::MakeWH(10, 40));
+    SkIRect clipDeviceBounds = canvas.getDeviceClipBounds();
+    if (clipDeviceBounds.isEmpty()) {
+        SkDebugf("Empty clip bounds is unexpected!\n");
+    }
+    SkDebugf("clip=%d,%d\n", clipDeviceBounds.width(), clipDeviceBounds.height());
+    SkISize baseLayerSize = canvas.getBaseLayerSize();
+    SkDebugf("size=%d,%d\n", baseLayerSize.width(), baseLayerSize.height());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_getDeviceClipBounds.cpp b/docs/examples/Canvas_getDeviceClipBounds.cpp
new file mode 100644
index 0000000..6783ef5
--- /dev/null
+++ b/docs/examples/Canvas_getDeviceClipBounds.cpp
@@ -0,0 +1,27 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=556832ac5711af662a98c21c547185e9
+REG_FIDDLE(Canvas_getDeviceClipBounds, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkCanvas device(256, 256);
+    canvas = &device;
+    SkIRect bounds = canvas->getDeviceClipBounds();
+    SkDebugf("left:%d  top:%d  right:%d  bottom:%d\n",
+            bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
+    SkPoint clipPoints[]  = {{30, 130}, {120, 130}, {120, 230} };
+    SkPath clipPath;
+    clipPath.addPoly(clipPoints, SK_ARRAY_COUNT(clipPoints), true);
+    canvas->save();
+    canvas->clipPath(clipPath);
+    bounds = canvas->getDeviceClipBounds();
+    SkDebugf("left:%d  top:%d  right:%d  bottom:%d\n",
+            bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
+    canvas->restore();
+    canvas->scale(1.f/2, 1.f/2);
+    canvas->clipPath(clipPath);
+    bounds = canvas->getDeviceClipBounds();
+    SkDebugf("left:%d  top:%d  right:%d  bottom:%d\n",
+            bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_getDeviceClipBounds_2.cpp b/docs/examples/Canvas_getDeviceClipBounds_2.cpp
new file mode 100644
index 0000000..5fd67c6
--- /dev/null
+++ b/docs/examples/Canvas_getDeviceClipBounds_2.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6abb99f849a1f0e33e1dedc00d1c4f7a
+REG_FIDDLE(Canvas_getDeviceClipBounds_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect bounds;
+    SkDebugf("device bounds empty = %s\n", canvas->getDeviceClipBounds(&bounds)
+             ? "false" : "true");
+    SkPath path;
+    canvas->clipPath(path);
+    SkDebugf("device bounds empty = %s\n", canvas->getDeviceClipBounds(&bounds)
+             ? "false" : "true");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_getGrContext.cpp b/docs/examples/Canvas_getGrContext.cpp
new file mode 100644
index 0000000..c993b15
--- /dev/null
+++ b/docs/examples/Canvas_getGrContext.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c4ea949e5fa5a0630dcb6b0204bd498f
+REG_FIDDLE(Canvas_getGrContext, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    if (canvas->getGrContext()) {
+         canvas->clear(SK_ColorRED);
+    } else {
+         canvas->clear(SK_ColorBLUE);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_getLocalClipBounds.cpp b/docs/examples/Canvas_getLocalClipBounds.cpp
new file mode 100644
index 0000000..922aa0f
--- /dev/null
+++ b/docs/examples/Canvas_getLocalClipBounds.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7f60cb030d3f9b2473adbe3e34b19d91
+REG_FIDDLE(Canvas_getLocalClipBounds, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkCanvas local(256, 256);
+    canvas = &local;
+    SkRect bounds = canvas->getLocalClipBounds();
+    SkDebugf("left:%g  top:%g  right:%g  bottom:%g\n",
+            bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
+    SkPoint clipPoints[]  = {{30, 130}, {120, 130}, {120, 230} };
+    SkPath clipPath;
+    clipPath.addPoly(clipPoints, SK_ARRAY_COUNT(clipPoints), true);
+    canvas->clipPath(clipPath);
+    bounds = canvas->getLocalClipBounds();
+    SkDebugf("left:%g  top:%g  right:%g  bottom:%g\n",
+            bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
+    canvas->scale(2, 2);
+    bounds = canvas->getLocalClipBounds();
+    SkDebugf("left:%g  top:%g  right:%g  bottom:%g\n",
+            bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_getLocalClipBounds_2.cpp b/docs/examples/Canvas_getLocalClipBounds_2.cpp
new file mode 100644
index 0000000..1cd37c0
--- /dev/null
+++ b/docs/examples/Canvas_getLocalClipBounds_2.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=85496614e90c66b020f8a70db8d06f4a
+REG_FIDDLE(Canvas_getLocalClipBounds_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkCanvas local(256, 256);
+    canvas = &local;
+    SkRect bounds;
+    SkDebugf("local bounds empty = %s\n", canvas->getLocalClipBounds(&bounds)
+             ? "false" : "true");
+    SkPath path;
+    canvas->clipPath(path);
+    SkDebugf("local bounds empty = %s\n", canvas->getLocalClipBounds(&bounds)
+             ? "false" : "true");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_getProps.cpp b/docs/examples/Canvas_getProps.cpp
new file mode 100644
index 0000000..85b9256
--- /dev/null
+++ b/docs/examples/Canvas_getProps.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0fbf2dedc2619bbfbf173c9e3bc1a508
+REG_FIDDLE(Canvas_getProps, 256, 256, true, 0) {
+void draw(SkCanvas* ) {
+    SkBitmap bitmap;
+    bitmap.allocN32Pixels(1, 1);
+    SkCanvas canvas(bitmap, SkSurfaceProps(0, kRGB_V_SkPixelGeometry));
+    SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
+    SkDebugf("isRGB:%d\n", SkPixelGeometryIsRGB(surfaceProps.pixelGeometry()));
+    if (!canvas.getProps(&surfaceProps)) {
+        SkDebugf("getProps failed unexpectedly.\n");
+    }
+    SkDebugf("isRGB:%d\n", SkPixelGeometryIsRGB(surfaceProps.pixelGeometry()));
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_getSaveCount.cpp b/docs/examples/Canvas_getSaveCount.cpp
new file mode 100644
index 0000000..a8f6891
--- /dev/null
+++ b/docs/examples/Canvas_getSaveCount.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=005f2b207e078baac596681924fe591e
+REG_FIDDLE(Canvas_getSaveCount, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkCanvas simple;
+    SkDebugf("depth = %d\n", simple.getSaveCount());
+    simple.save();
+    SkDebugf("depth = %d\n", simple.getSaveCount());
+    simple.restore();
+    SkDebugf("depth = %d\n", simple.getSaveCount());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_getTotalMatrix.cpp b/docs/examples/Canvas_getTotalMatrix.cpp
new file mode 100644
index 0000000..965c08e
--- /dev/null
+++ b/docs/examples/Canvas_getTotalMatrix.cpp
@@ -0,0 +1,9 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c0d5fa544759704768f47cac91ae3832
+REG_FIDDLE(Canvas_getTotalMatrix, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkDebugf("isIdentity %s\n", canvas->getTotalMatrix().isIdentity() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_imageInfo.cpp b/docs/examples/Canvas_imageInfo.cpp
new file mode 100644
index 0000000..35070ae
--- /dev/null
+++ b/docs/examples/Canvas_imageInfo.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d93389d971f8084c4ccc7a66e4e157ee
+REG_FIDDLE(Canvas_imageInfo, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkCanvas emptyCanvas;
+    SkImageInfo canvasInfo = emptyCanvas.imageInfo();
+    SkImageInfo emptyInfo;
+    SkDebugf("emptyInfo %c= canvasInfo\n", emptyInfo == canvasInfo ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_int_int_const_SkSurfaceProps_star.cpp b/docs/examples/Canvas_int_int_const_SkSurfaceProps_star.cpp
new file mode 100644
index 0000000..ffc6756
--- /dev/null
+++ b/docs/examples/Canvas_int_int_const_SkSurfaceProps_star.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ce6a5ef2df447970b4453489d9d67930
+REG_FIDDLE(Canvas_int_int_const_SkSurfaceProps_star, 256, 256, true, 0) {
+void draw(SkCanvas* ) {
+    SkCanvas canvas(10, 20);  // 10 units wide, 20 units high
+    canvas.clipRect(SkRect::MakeXYWH(30, 40, 5, 10));  // clip is outside canvas' device
+    SkDebugf("canvas %s empty\n", canvas.getDeviceClipBounds().isEmpty() ? "is" : "is not");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_isClipEmpty.cpp b/docs/examples/Canvas_isClipEmpty.cpp
new file mode 100644
index 0000000..af2b185
--- /dev/null
+++ b/docs/examples/Canvas_isClipEmpty.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f106f146a58c8604308d4d8d7086d2f5
+REG_FIDDLE(Canvas_isClipEmpty, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkDebugf("clip is%s empty\n", canvas->isClipEmpty() ? "" : " not");
+    SkPath path;
+    canvas->clipPath(path);
+    SkDebugf("clip is%s empty\n", canvas->isClipEmpty() ? "" : " not");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_isClipRect.cpp b/docs/examples/Canvas_isClipRect.cpp
new file mode 100644
index 0000000..21fc246
--- /dev/null
+++ b/docs/examples/Canvas_isClipRect.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9894bfb476c78a8f6c8f49fbbca3d50d
+REG_FIDDLE(Canvas_isClipRect, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkDebugf("clip is%s rect\n", canvas->isClipRect() ? "" : " not");
+    canvas->clipRect({0, 0, 0, 0});
+    SkDebugf("clip is%s rect\n", canvas->isClipRect() ? "" : " not");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_kInitWithPrevious_SaveLayerFlag.cpp b/docs/examples/Canvas_kInitWithPrevious_SaveLayerFlag.cpp
new file mode 100644
index 0000000..9955295
--- /dev/null
+++ b/docs/examples/Canvas_kInitWithPrevious_SaveLayerFlag.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=05db6a937225e8e31ae3481173d25dae
+REG_FIDDLE(Canvas_kInitWithPrevious_SaveLayerFlag, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint redPaint, bluePaint, scalePaint;
+    redPaint.setColor(SK_ColorRED);
+    canvas->drawCircle(21, 21, 8, redPaint);
+    bluePaint.setColor(SK_ColorBLUE);
+    canvas->drawCircle(31, 21, 8, bluePaint);
+    SkMatrix matrix;
+    matrix.setScale(4, 4);
+    scalePaint.setAlpha(0x40);
+    scalePaint.setImageFilter(
+            SkImageFilter::MakeMatrixFilter(matrix, kNone_SkFilterQuality, nullptr));
+    SkCanvas::SaveLayerRec saveLayerRec(nullptr, &scalePaint,
+            SkCanvas::kInitWithPrevious_SaveLayerFlag);
+    canvas->saveLayer(saveLayerRec);
+    canvas->restore();
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_makeSurface.cpp b/docs/examples/Canvas_makeSurface.cpp
new file mode 100644
index 0000000..ddf56f5
--- /dev/null
+++ b/docs/examples/Canvas_makeSurface.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1ce28351444b41ab2b8e3128a4b9b9c2
+REG_FIDDLE(Canvas_makeSurface, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(5, 6);
+    SkCanvas* smallCanvas = surface->getCanvas();
+    SkImageInfo imageInfo = SkImageInfo::MakeN32Premul(3, 4);
+    sk_sp<SkSurface> compatible = smallCanvas->makeSurface(imageInfo);
+    SkDebugf("compatible %c= nullptr\n", compatible == nullptr ? '=' : '!');
+    SkDebugf("size = %d, %d\n", compatible->width(), compatible->height());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_peekPixels.cpp b/docs/examples/Canvas_peekPixels.cpp
new file mode 100644
index 0000000..d57ff7f
--- /dev/null
+++ b/docs/examples/Canvas_peekPixels.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e9411d676d1fa13b46331abe9e14ad3e
+REG_FIDDLE(Canvas_peekPixels, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPixmap pixmap;
+    if (canvas->peekPixels(&pixmap)) {
+        SkDebugf("width=%d height=%d\n", pixmap.bounds().width(), pixmap.bounds().height());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_quickReject.cpp b/docs/examples/Canvas_quickReject.cpp
new file mode 100644
index 0000000..f863ba9
--- /dev/null
+++ b/docs/examples/Canvas_quickReject.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=cfe4016241074477809dd45435be9cf4
+REG_FIDDLE(Canvas_quickReject, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect testRect = {30, 30, 120, 129 };
+    SkRect clipRect = {30, 130, 120, 230 };
+    canvas->save();
+    canvas->clipRect(clipRect);
+    SkDebugf("quickReject %s\n", canvas->quickReject(testRect) ? "true" : "false");
+    canvas->restore();
+    canvas->rotate(10);
+    canvas->clipRect(clipRect);
+    SkDebugf("quickReject %s\n", canvas->quickReject(testRect) ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_quickReject_2.cpp b/docs/examples/Canvas_quickReject_2.cpp
new file mode 100644
index 0000000..3f69c03
--- /dev/null
+++ b/docs/examples/Canvas_quickReject_2.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=56dcd14f943aea6f7d7aafe0de7e6c25
+REG_FIDDLE(Canvas_quickReject_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint testPoints[] = {{30,  30}, {120,  30}, {120, 129} };
+    SkPoint clipPoints[] = {{30, 130}, {120, 130}, {120, 230} };
+    SkPath testPath, clipPath;
+    testPath.addPoly(testPoints, SK_ARRAY_COUNT(testPoints), true);
+    clipPath.addPoly(clipPoints, SK_ARRAY_COUNT(clipPoints), true);
+    canvas->save();
+    canvas->clipPath(clipPath);
+    SkDebugf("quickReject %s\n", canvas->quickReject(testPath) ? "true" : "false");
+    canvas->restore();
+    canvas->rotate(10);
+    canvas->clipPath(clipPath);
+    SkDebugf("quickReject %s\n", canvas->quickReject(testPath) ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_readPixels_2.cpp b/docs/examples/Canvas_readPixels_2.cpp
new file mode 100644
index 0000000..877473f
--- /dev/null
+++ b/docs/examples/Canvas_readPixels_2.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=85f199032943b6483722c34a91c4e20f
+REG_FIDDLE(Canvas_readPixels_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->clear(0x8055aaff);
+    uint32_t pixels[1] = { 0 };
+    SkPixmap pixmap(SkImageInfo::MakeN32Premul(1, 1), pixels, 4);
+    canvas->readPixels(pixmap, 0, 0);
+    SkDebugf("pixel = %08x\n", pixels[0]);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_readPixels_3.cpp b/docs/examples/Canvas_readPixels_3.cpp
new file mode 100644
index 0000000..0c96279
--- /dev/null
+++ b/docs/examples/Canvas_readPixels_3.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=af6dec8ef974aa67bf102f29915bcd6a
+REG_FIDDLE(Canvas_readPixels_3, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->clear(0x8055aaff);
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(1, 1));
+    canvas->readPixels(bitmap, 0, 0);
+    SkDebugf("pixel = %08x\n", bitmap.getAddr32(0, 0)[0]);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_readPixels_a.cpp b/docs/examples/Canvas_readPixels_a.cpp
new file mode 100644
index 0000000..74110d0
--- /dev/null
+++ b/docs/examples/Canvas_readPixels_a.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=102d014d7f753db2a9b9ee08893aaf11
+REG_FIDDLE(Canvas_readPixels_a, 64, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->clear(SK_ColorBLUE);
+    SkPaint paint;
+    canvas->drawCircle(32, 32, 28, paint);
+    SkImageInfo info = SkImageInfo::Make(64, 64, kBGRA_8888_SkColorType, kPremul_SkAlphaType);
+    sk_sp<SkData> data(SkData::MakeUninitialized(info.minRowBytes() * info.height()));
+    sk_bzero(data->writable_data(), info.minRowBytes() * info.height());
+    for (int x : { 32, -32 } ) {
+        for (int y : { 32, -32 } ) {
+            canvas->readPixels(info, data->writable_data(), info.minRowBytes(), x, y);
+        }
+    }
+    sk_sp<SkImage> image = SkImage::MakeRasterData(info, data, info.minRowBytes());
+    canvas->drawImage(image, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_readPixels_b.cpp b/docs/examples/Canvas_readPixels_b.cpp
new file mode 100644
index 0000000..a1ddd35
--- /dev/null
+++ b/docs/examples/Canvas_readPixels_b.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=481e990e923a0ed34654f4361b94f096
+REG_FIDDLE(Canvas_readPixels_b, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->clear(0x8055aaff);
+    for (SkAlphaType alphaType : { kPremul_SkAlphaType, kUnpremul_SkAlphaType } ) {
+        uint32_t pixel = 0;
+        SkImageInfo info = SkImageInfo::Make(1, 1, kBGRA_8888_SkColorType, alphaType);
+        if (canvas->readPixels(info, &pixel, 4, 0, 0)) {
+            SkDebugf("pixel = %08x\n", pixel);
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_resetMatrix.cpp b/docs/examples/Canvas_resetMatrix.cpp
new file mode 100644
index 0000000..a69ce0c
--- /dev/null
+++ b/docs/examples/Canvas_resetMatrix.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=412afffdf4682baa503a4e2e99201967
+REG_FIDDLE(Canvas_resetMatrix, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkFont font;
+    canvas->scale(4, 6);
+    canvas->drawString("truth", 2, 10, font, paint);
+    canvas->resetMatrix();
+    canvas->scale(2.8f, 6);
+    canvas->drawString("consequences", 2, 20, font, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_restore.cpp b/docs/examples/Canvas_restore.cpp
new file mode 100644
index 0000000..3ed4a4f
--- /dev/null
+++ b/docs/examples/Canvas_restore.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e78471212a67f2f4fd39496e17a30d17
+REG_FIDDLE(Canvas_restore, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkCanvas simple;
+    SkDebugf("depth = %d\n", simple.getSaveCount());
+    simple.restore();
+    SkDebugf("depth = %d\n", simple.getSaveCount());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_restoreToCount.cpp b/docs/examples/Canvas_restoreToCount.cpp
new file mode 100644
index 0000000..7112c00
--- /dev/null
+++ b/docs/examples/Canvas_restoreToCount.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9ed0d56436e114c7097fd49eed1aea47
+REG_FIDDLE(Canvas_restoreToCount, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkDebugf("depth = %d\n", canvas->getSaveCount());
+    canvas->save();
+    canvas->save();
+    SkDebugf("depth = %d\n", canvas->getSaveCount());
+    canvas->restoreToCount(0);
+    SkDebugf("depth = %d\n", canvas->getSaveCount());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_rotate.cpp b/docs/examples/Canvas_rotate.cpp
new file mode 100644
index 0000000..428a6b9
--- /dev/null
+++ b/docs/examples/Canvas_rotate.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=963789ac8498d4e505748ab3b15cdaa5
+REG_FIDDLE(Canvas_rotate, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->translate(128, 128);
+    canvas->drawCircle(0, 0, 60, paint);
+    canvas->save();
+    canvas->rotate(10 * 360 / 60);   // 10 minutes of 60 scaled to 360 degrees
+    canvas->drawLine(0, 0, 0, -50, paint);
+    canvas->restore();
+    canvas->rotate((5 + 10.f/60) * 360 / 12); // 5 and 10/60 hours of 12 scaled to 360 degrees
+    canvas->drawLine(0, 0, 0, -30, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_rotate_2.cpp b/docs/examples/Canvas_rotate_2.cpp
new file mode 100644
index 0000000..2c96215
--- /dev/null
+++ b/docs/examples/Canvas_rotate_2.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bcf5baea1c66a957d5ffd7b54bbbfeff
+REG_FIDDLE(Canvas_rotate_2, 256, 192, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkFont font(nullptr, 96);
+    canvas->drawString("A1", 130, 100, font, paint);
+    canvas->rotate(180, 130, 100);
+    canvas->drawString("A1", 130, 100, font, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_save.cpp b/docs/examples/Canvas_save.cpp
new file mode 100644
index 0000000..660117b
--- /dev/null
+++ b/docs/examples/Canvas_save.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e477dce358a9ba3b0aa1bf33b8a376de
+REG_FIDDLE(Canvas_save, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkRect rect = { 0, 0, 25, 25 };
+    canvas->drawRect(rect, paint);
+    canvas->save();
+    canvas->translate(50, 50);
+    canvas->drawRect(rect, paint);
+    canvas->restore();
+    paint.setColor(SK_ColorRED);
+    canvas->drawRect(rect, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_saveLayer.cpp b/docs/examples/Canvas_saveLayer.cpp
new file mode 100644
index 0000000..3f4616d
--- /dev/null
+++ b/docs/examples/Canvas_saveLayer.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=42318b18d403e17e07a541652da91ee2
+REG_FIDDLE(Canvas_saveLayer, 256, 128, false, 0) {
+#include "SkBlurImageFilter.h"
+
+void draw(SkCanvas* canvas) {
+    SkPaint paint, blur;
+    blur.setImageFilter(SkBlurImageFilter::Make(3, 3, nullptr));
+    canvas->saveLayer(nullptr, &blur);
+    SkRect rect = { 25, 25, 50, 50};
+    canvas->drawRect(rect, paint);
+    canvas->translate(50, 50);
+    paint.setColor(SK_ColorRED);
+    canvas->drawRect(rect, paint);
+    canvas->restore();
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_saveLayerAlpha.cpp b/docs/examples/Canvas_saveLayerAlpha.cpp
new file mode 100644
index 0000000..ab563a4
--- /dev/null
+++ b/docs/examples/Canvas_saveLayerAlpha.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8ab88d86fb438856cc48d6e2f08a6e24
+REG_FIDDLE(Canvas_saveLayerAlpha, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setColor(SK_ColorRED);
+    canvas->drawCircle(50, 50, 50, paint);
+    canvas->saveLayerAlpha(nullptr, 128);
+    paint.setColor(SK_ColorBLUE);
+    canvas->drawCircle(100, 50, 50, paint);
+    paint.setColor(SK_ColorGREEN);
+    paint.setAlpha(128);
+    canvas->drawCircle(75, 90, 50, paint);
+    canvas->restore();
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_saveLayerPreserveLCDTextRequests.cpp b/docs/examples/Canvas_saveLayerPreserveLCDTextRequests.cpp
new file mode 100644
index 0000000..32d11e3
--- /dev/null
+++ b/docs/examples/Canvas_saveLayerPreserveLCDTextRequests.cpp
@@ -0,0 +1,28 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8460bf8b013f46c67e0bd96e13451aff
+REG_FIDDLE(Canvas_saveLayerPreserveLCDTextRequests, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setLCDRenderText(true);
+    paint.setTextSize(20);
+    for (auto preserve : { false, true } ) {
+        preserve ? canvas->saveLayerPreserveLCDTextRequests(nullptr, nullptr)
+                 : canvas->saveLayer(nullptr, nullptr);
+        SkPaint p;
+        p.setColor(SK_ColorWHITE);
+        // Comment out the next line to draw on a non-opaque background.
+        canvas->drawRect(SkRect::MakeLTRB(25, 40, 200, 70), p);
+        canvas->drawString("Hamburgefons", 30, 60, paint);
+        p.setColor(0xFFCCCCCC);
+        canvas->drawRect(SkRect::MakeLTRB(25, 70, 200, 100), p);
+        canvas->drawString("Hamburgefons", 30, 90, paint);
+        canvas->restore();
+        canvas->translate(0, 80);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Canvas_saveLayer_2.cpp b/docs/examples/Canvas_saveLayer_2.cpp
new file mode 100644
index 0000000..a6ae460
--- /dev/null
+++ b/docs/examples/Canvas_saveLayer_2.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a17aec3aa4909527be039e26a7eda694
+REG_FIDDLE(Canvas_saveLayer_2, 256, 128, false, 0) {
+#include "SkBlurImageFilter.h"
+
+void draw(SkCanvas* canvas) {
+    SkPaint paint, blur;
+    blur.setImageFilter(SkBlurImageFilter::Make(3, 3, nullptr));
+    canvas->saveLayer(SkRect::MakeWH(90, 90), &blur);
+    SkRect rect = { 25, 25, 50, 50};
+    canvas->drawRect(rect, paint);
+    canvas->translate(50, 50);
+    paint.setColor(SK_ColorRED);
+    canvas->drawRect(rect, paint);
+    canvas->restore();
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_saveLayer_3.cpp b/docs/examples/Canvas_saveLayer_3.cpp
new file mode 100644
index 0000000..16ae0de
--- /dev/null
+++ b/docs/examples/Canvas_saveLayer_3.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7d3751e82d1b6ec328ffa3d6f48ca831
+REG_FIDDLE(Canvas_saveLayer_3, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    // sk_sp<SkImage> image = GetResourceAsImage("images/mandrill_256.png");
+    canvas->drawImage(image, 0, 0, nullptr);
+    SkCanvas::SaveLayerRec rec;
+    SkPaint paint;
+    paint.setBlendMode(SkBlendMode::kPlus);
+    rec.fSaveLayerFlags = SkCanvas::kInitWithPrevious_SaveLayerFlag;
+    rec.fPaint = &paint;
+    canvas->saveLayer(rec);
+    paint.setBlendMode(SkBlendMode::kClear);
+    canvas->drawCircle(128, 128, 96, paint);
+    canvas->restore();
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_scale.cpp b/docs/examples/Canvas_scale.cpp
new file mode 100644
index 0000000..8bf1a51
--- /dev/null
+++ b/docs/examples/Canvas_scale.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7d0d801ef13c6c6da51e840c22ac15b0
+REG_FIDDLE(Canvas_scale, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkRect rect = { 10, 20, 60, 120 };
+    canvas->translate(20, 20);
+    canvas->drawRect(rect, paint);
+    canvas->scale(2, .5f);
+    paint.setColor(SK_ColorGRAY);
+    canvas->drawRect(rect, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_setMatrix.cpp b/docs/examples/Canvas_setMatrix.cpp
new file mode 100644
index 0000000..2e3cf69
--- /dev/null
+++ b/docs/examples/Canvas_setMatrix.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=24b9cf7e6f9a08394e1e07413bd8733a
+REG_FIDDLE(Canvas_setMatrix, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkFont font;
+    canvas->scale(4, 6);
+    canvas->drawString("truth", 2, 10, font, paint);
+    SkMatrix matrix;
+    matrix.setScale(2.8f, 6);
+    canvas->setMatrix(matrix);
+    canvas->drawString("consequences", 2, 20, font, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_skew.cpp b/docs/examples/Canvas_skew.cpp
new file mode 100644
index 0000000..486dd5f
--- /dev/null
+++ b/docs/examples/Canvas_skew.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2e2acc21d7774df7e0940a30ad2ca99e
+REG_FIDDLE(Canvas_skew, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkFont font(nullptr, 128);
+    canvas->translate(30, 130);
+    canvas->save();
+    canvas->skew(-.5, 0);
+    canvas->drawString("A1", 0, 0, font, paint);
+    canvas->restore();
+    canvas->save();
+    canvas->skew(0, .5);
+    paint.setColor(SK_ColorRED);
+    canvas->drawString("A1", 0, 0, font, paint);
+    canvas->restore();
+    canvas->skew(-.5, .5);
+    paint.setColor(SK_ColorBLUE);
+    canvas->drawString("A1", 0, 0, font, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_translate.cpp b/docs/examples/Canvas_translate.cpp
new file mode 100644
index 0000000..e6dfd47
--- /dev/null
+++ b/docs/examples/Canvas_translate.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=eb93d5fa66a5f7a10f4f9210494d7222
+REG_FIDDLE(Canvas_translate, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint filledPaint;
+    SkPaint outlinePaint;
+    outlinePaint.setStyle(SkPaint::kStroke_Style);
+    outlinePaint.setColor(SK_ColorBLUE);
+    canvas->save();
+    canvas->translate(50, 50);
+    canvas->drawCircle(28, 28, 15, outlinePaint);  // blue center: (50+28, 50+28)
+    canvas->scale(2, 1/2.f);
+    canvas->drawCircle(28, 28, 15, filledPaint);   // black center: (50+(28*2), 50+(28/2))
+    canvas->restore();
+    filledPaint.setColor(SK_ColorGRAY);
+    outlinePaint.setColor(SK_ColorRED);
+    canvas->scale(2, 1/2.f);
+    canvas->drawCircle(28, 28, 15, outlinePaint);  // red center: (28*2, 28/2)
+    canvas->translate(50, 50);
+    canvas->drawCircle(28, 28, 15, filledPaint);   // gray center: ((50+28)*2, (50+28)/2)
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_writePixels.cpp b/docs/examples/Canvas_writePixels.cpp
new file mode 100644
index 0000000..75f22de
--- /dev/null
+++ b/docs/examples/Canvas_writePixels.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=29b98ebf58aa9fd1edfaabf9f4490b3a
+REG_FIDDLE(Canvas_writePixels, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo imageInfo = SkImageInfo::MakeN32(256, 1, kPremul_SkAlphaType);
+    for (int y = 0; y < 256; ++y) {
+        uint32_t pixels[256];
+        for (int x = 0; x < 256; ++x) {
+            pixels[x] = SkColorSetARGB(x, (x + y) % 256, x, (x - y) & 0xFF);
+        }
+        canvas->writePixels(imageInfo, &pixels, sizeof(pixels), 0, y);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Canvas_writePixels_2.cpp b/docs/examples/Canvas_writePixels_2.cpp
new file mode 100644
index 0000000..83d3f51
--- /dev/null
+++ b/docs/examples/Canvas_writePixels_2.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8b128e067881f9251357653692fa28da
+REG_FIDDLE(Canvas_writePixels_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo imageInfo = SkImageInfo::MakeN32Premul(2, 2);
+    SkBitmap bitmap;
+    bitmap.setInfo(imageInfo);
+    uint32_t pixels[4];
+    bitmap.setPixels(pixels);
+    for (int y = 0; y < 256; y += 2) {
+        for (int x = 0; x < 256;  x += 2) {
+            pixels[0] = SkColorSetRGB(x, y, x | y);
+            pixels[1] = SkColorSetRGB(x ^ y, y, x);
+            pixels[2] = SkColorSetRGB(x, x & y, y);
+            pixels[3] = SkColorSetRGB((~x) & 0xFF, (~y) & 0xFF, x);
+            canvas->writePixels(bitmap, x, y);
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Clear.cpp b/docs/examples/Clear.cpp
new file mode 100644
index 0000000..45df306
--- /dev/null
+++ b/docs/examples/Clear.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a9b56a26ca469bab9ab10e16f62fb2e2
+REG_FIDDLE(Clear, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->saveLayer(nullptr, nullptr);
+    canvas->drawColor(SK_ColorYELLOW, SkBlendMode::kClear);
+    SkPaint paint;
+    for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN } ) {
+        SkColor colors[] = { color, SkColorSetA(color, 0) };
+        paint.setShader(SkGradientShader::MakeRadial({ 64, 64}, 100,
+                colors, nullptr, SK_ARRAY_COUNT(colors), SkTileMode::kClamp));
+        canvas->drawCircle(64, 64, 100, paint);
+        canvas->translate(64, 64);
+    }
+    canvas->restore();
+}
+}  // END FIDDLE
diff --git a/docs/examples/Clip.cpp b/docs/examples/Clip.cpp
new file mode 100644
index 0000000..1c1b9af
--- /dev/null
+++ b/docs/examples/Clip.cpp
@@ -0,0 +1,28 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=862cc026601a41a58df49c0b9f0d7777
+REG_FIDDLE(Clip, 256, 90, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint redPaint, scalePaint;
+    redPaint.setAntiAlias(true);
+    redPaint.setColor(SK_ColorRED);
+    canvas->save();
+    for (bool antialias : { false, true } ) {
+        canvas->save();
+        canvas->clipRect(SkRect::MakeWH(19.5f, 11.5f), antialias);
+        canvas->drawCircle(17, 11, 8, redPaint);
+        canvas->restore();
+        canvas->translate(16, 0);
+    }
+    canvas->restore();
+    SkMatrix matrix;
+    matrix.setScale(6, 6);
+    scalePaint.setImageFilter(
+            SkImageFilter::MakeMatrixFilter(matrix, kNone_SkFilterQuality, nullptr));
+    SkCanvas::SaveLayerRec saveLayerRec(
+            nullptr, &scalePaint, SkCanvas::kInitWithPrevious_SaveLayerFlag);
+    canvas->saveLayer(saveLayerRec);
+    canvas->restore();
+}
+}  // END FIDDLE
diff --git a/docs/examples/Color.cpp b/docs/examples/Color.cpp
new file mode 100644
index 0000000..6584833
--- /dev/null
+++ b/docs/examples/Color.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5d7c6e23a34ca9bf3ba8cda4cdc94cc4
+REG_FIDDLE(Color, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->drawImage(image, 0, 0);
+    canvas->drawColor(0xFF00FF00, SkBlendMode::kColor);
+}
+}  // END FIDDLE
diff --git a/docs/examples/ColorGetA.cpp b/docs/examples/ColorGetA.cpp
new file mode 100644
index 0000000..230df7e
--- /dev/null
+++ b/docs/examples/ColorGetA.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=896ce0316b489608a95af5439ca2aab1
+REG_FIDDLE(ColorGetA, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setColor(SK_ColorRED);
+    for (int alpha = 255; alpha >= 0; alpha -= 17) {
+        paint.setAlpha(alpha);
+        canvas->drawRect({5, 5, 100, 20}, paint);
+        SkAlpha alphaInPaint = SkColorGetA(paint.getColor());
+        canvas->drawString(std::to_string(alphaInPaint).c_str(), 110, 18, SkFont(), paint);
+        canvas->translate(0, 15);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/ColorGetB.cpp b/docs/examples/ColorGetB.cpp
new file mode 100644
index 0000000..ef7641d
--- /dev/null
+++ b/docs/examples/ColorGetB.cpp
@@ -0,0 +1,17 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9ee27675284faea375611dc88123a2c5
+REG_FIDDLE(ColorGetB, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->drawBitmap(source, 0, 0);
+    SkPaint bgPaint;
+    bgPaint.setColor(0xafffffff);
+    canvas->drawRect({20, 50, 80, 70}, bgPaint);
+    uint8_t blue = SkColorGetB(source.getColor(168, 170));
+    canvas->drawString(std::to_string(blue).c_str(), 40, 65, SkPaint());
+    canvas->drawLine(80, 70, 168, 170, SkPaint());
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/ColorGetG.cpp b/docs/examples/ColorGetG.cpp
new file mode 100644
index 0000000..45c26f4
--- /dev/null
+++ b/docs/examples/ColorGetG.cpp
@@ -0,0 +1,17 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=535d38b2c019299d915170f7b03d5fea
+REG_FIDDLE(ColorGetG, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->drawBitmap(source, 0, 0);
+    SkPaint bgPaint;
+    bgPaint.setColor(0xafffffff);
+    canvas->drawRect({20, 50, 80, 70}, bgPaint);
+    uint8_t green = SkColorGetG(source.getColor(57, 192));
+    canvas->drawString(std::to_string(green).c_str(), 40, 65, SkPaint());
+    canvas->drawLine(80, 70, 57, 192, SkPaint());
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/ColorGetR.cpp b/docs/examples/ColorGetR.cpp
new file mode 100644
index 0000000..d2d3299
--- /dev/null
+++ b/docs/examples/ColorGetR.cpp
@@ -0,0 +1,17 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d6da38577f189eaa6d9df75f6c3ed252
+REG_FIDDLE(ColorGetR, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->drawBitmap(source, 0, 0);
+    SkPaint bgPaint;
+    bgPaint.setColor(0xafffffff);
+    canvas->drawRect({20, 50, 80, 70}, bgPaint);
+    uint8_t red = SkColorGetR(source.getColor(226, 128));
+    canvas->drawString(std::to_string(red).c_str(), 40, 65, SkPaint());
+    canvas->drawLine(80, 70, 226, 128, SkPaint());
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/ColorSetA.cpp b/docs/examples/ColorSetA.cpp
new file mode 100644
index 0000000..496d5c7
--- /dev/null
+++ b/docs/examples/ColorSetA.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=18f6f376f771f5ffa56d5e5b2ebd20fb
+REG_FIDDLE(ColorSetA, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->drawBitmap(source, 0, 0);
+    for (int y = 0; y < 256; y += 16) {
+       for (int x = 0; x < 256; x += 16) {
+         SkColor color = source.getColor(x + 8, y + 8);
+         SkPaint paint;
+         paint.setColor(SkColorSetA(color, x + y));
+         canvas->drawRect(SkRect::MakeXYWH(x, y, 16, 16), paint);
+      }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/ColorSetARGB.cpp b/docs/examples/ColorSetARGB.cpp
new file mode 100644
index 0000000..f0f60a0
--- /dev/null
+++ b/docs/examples/ColorSetARGB.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=35888f0869e01a6e03b5b93bba563734
+REG_FIDDLE(ColorSetARGB, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->drawColor(SK_ColorRED);
+    canvas->clipRect(SkRect::MakeWH(150, 150));
+    canvas->drawColor(SkColorSetARGB(0x80, 0x00, 0xFF, 0x00));
+    canvas->clipRect(SkRect::MakeWH(75, 75));
+    canvas->drawColor(SkColorSetARGB(0x80, 0x00, 0x00, 0xFF));
+}
+}  // END FIDDLE
diff --git a/docs/examples/ColorSetRGB.cpp b/docs/examples/ColorSetRGB.cpp
new file mode 100644
index 0000000..c68ed6a
--- /dev/null
+++ b/docs/examples/ColorSetRGB.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=dad12dd912197cd5edd789ac0801bf8a
+REG_FIDDLE(ColorSetRGB, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->drawColor(SK_ColorRED);
+    canvas->clipRect(SkRect::MakeWH(150, 150));
+    canvas->drawColor(SkColorSetRGB(0x00, 0xFF, 0x00));
+    canvas->clipRect(SkRect::MakeWH(75, 75));
+    canvas->drawColor(SkColorSetRGB(0x00, 0x00, 0xFF));
+}
+}  // END FIDDLE
diff --git a/docs/examples/ColorToHSV.cpp b/docs/examples/ColorToHSV.cpp
new file mode 100644
index 0000000..4475463
--- /dev/null
+++ b/docs/examples/ColorToHSV.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1e0370f12c8aab5b84f9e824074f1e5a
+REG_FIDDLE(ColorToHSV, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->drawBitmap(source, 0, 0);
+    for (int y = 0; y < 256; ++y) {
+       for (int x = 0; x < 256; ++x) {
+         SkScalar hsv[3];
+         SkColorToHSV(source.getColor(x, y), hsv);
+         hsv[1] = 1 - hsv[1];
+         SkPaint paint;
+         paint.setColor(SkHSVToColor(hsv));
+         canvas->drawRect(SkRect::MakeXYWH(x, y, 1, 1), paint);
+      }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/ColorTypeBytesPerPixel.cpp b/docs/examples/ColorTypeBytesPerPixel.cpp
new file mode 100644
index 0000000..dc3c71b
--- /dev/null
+++ b/docs/examples/ColorTypeBytesPerPixel.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=09ef49d07cb7005ba3e34d5ea53896f5
+REG_FIDDLE(ColorTypeBytesPerPixel, 256, 192, false, 0) {
+void draw(SkCanvas* canvas) {
+    const char* colors[] = { "Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
+                             "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16Norm",
+                             "RGBA_F16" };
+    SkPaint paint;
+    SkFont font(SkTypeface::MakeFromName("monospace", SkFontStyle()), 10);
+    int y = 15;
+    canvas->drawString("    colorType  bytes", 10, y, font, paint);
+    for (SkColorType colorType : {
+    kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType,
+    kARGB_4444_SkColorType, kRGBA_8888_SkColorType, kRGB_888x_SkColorType,
+    kBGRA_8888_SkColorType, kRGBA_1010102_SkColorType, kRGB_101010x_SkColorType,
+    kGray_8_SkColorType, kRGBA_F16_SkColorType
+                                 } ) {
+        int result = SkColorTypeBytesPerPixel(colorType);
+        SkString string;
+        string.printf("%13s %4d", colors[(int) colorType], result);
+        canvas->drawString(string, 10, y += 14, font, paint);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/ColorTypeIsAlwaysOpaque.cpp b/docs/examples/ColorTypeIsAlwaysOpaque.cpp
new file mode 100644
index 0000000..322e118
--- /dev/null
+++ b/docs/examples/ColorTypeIsAlwaysOpaque.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9b3eb5aaa0dfea9feee54e7650fa5446
+REG_FIDDLE(ColorTypeIsAlwaysOpaque, 256, 192, false, 0) {
+void draw(SkCanvas* canvas) {
+    const char* colors[] = { "Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
+                             "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16Norm",
+                             "RGBA_F16" };
+    SkPaint paint;
+    SkFont font(SkTypeface::MakeFromName("monospace", SkFontStyle()), 10);
+    int y = 15;
+    canvas->drawString("    colorType  bytes", 10, y, font, paint);
+    for (SkColorType colorType : {
+    kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType,
+    kARGB_4444_SkColorType, kRGBA_8888_SkColorType, kRGB_888x_SkColorType,
+    kBGRA_8888_SkColorType, kRGBA_1010102_SkColorType, kRGB_101010x_SkColorType,
+    kGray_8_SkColorType, kRGBA_F16_SkColorType
+                                 } ) {
+        bool result = SkColorTypeIsAlwaysOpaque(colorType);
+        SkString string;
+        string.printf("%13s %6s", colors[(int) colorType], result ? "true" : "false");
+        canvas->drawString(string, 10, y += 14, font, paint);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/ColorTypeValidateAlphaType.cpp b/docs/examples/ColorTypeValidateAlphaType.cpp
new file mode 100644
index 0000000..d85ef88
--- /dev/null
+++ b/docs/examples/ColorTypeValidateAlphaType.cpp
@@ -0,0 +1,34 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=befac1c29ed21507d367e4d824383a04
+REG_FIDDLE(ColorTypeValidateAlphaType, 256, 640, false, 0) {
+void draw(SkCanvas* canvas) {
+    const char* colors[] = { "Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
+                             "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16Norm",
+                             "RGBA_F16" };
+    const char* alphas[] = {"Unknown ", "Opaque  ", "Premul  ", "Unpremul"};
+    SkAlphaType alphaTypes[] = { kUnknown_SkAlphaType, kOpaque_SkAlphaType, kPremul_SkAlphaType,
+    kUnpremul_SkAlphaType
+                               };
+    SkPaint paint;
+    SkFont font(SkTypeface::MakeFromName("monospace", SkFontStyle()), 10);
+    int y = 15;
+    canvas->drawString("   colorType   alphaType  canonical", 10, y, font, paint);
+    for (SkColorType colorType : {
+    kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType,
+    kARGB_4444_SkColorType, kRGBA_8888_SkColorType, kRGB_888x_SkColorType,
+    kBGRA_8888_SkColorType, kRGBA_1010102_SkColorType, kRGB_101010x_SkColorType,
+    kGray_8_SkColorType, kRGBA_F16_SkColorType
+                                 } ) {
+        for (SkAlphaType alphaType : alphaTypes) {
+            SkAlphaType canonicalAlphaType  = kUnknown_SkAlphaType;
+            bool result = SkColorTypeValidateAlphaType(colorType, alphaType, &canonicalAlphaType);
+            SkString string;
+            string.printf("%13s %10s %10s", colors[(int) colorType], alphas[(int) alphaType],
+                     result ? alphas[(int) canonicalAlphaType] : "------  ");
+            canvas->drawString(string, 10, y += 14, font, paint);
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Color_Burn.cpp b/docs/examples/Color_Burn.cpp
new file mode 100644
index 0000000..74a37d6
--- /dev/null
+++ b/docs/examples/Color_Burn.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3eeef529375d8083ae0d615789d55e89
+REG_FIDDLE(Color_Burn, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->drawImage(image, 0, 0);
+    canvas->clipRect({128, 0, 256, 256});
+    canvas->drawColor(SkColorSetARGB(0x80, 0x90, 0x90, 0x90), SkBlendMode::kColorBurn);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Color_Constants_a.cpp b/docs/examples/Color_Constants_a.cpp
new file mode 100644
index 0000000..e46a9f2
--- /dev/null
+++ b/docs/examples/Color_Constants_a.cpp
@@ -0,0 +1,48 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1c2e38321464818847f953ddd45cb5a1
+REG_FIDDLE(Color_Constants_a, 256, 256, false, 0) {
+#define SKIA_COLOR_PAIR(name) "SK_Color" #name, SK_Color##name
+
+void draw(SkCanvas* canvas) {
+    struct ColorCompare {
+        const char* fSVGName;
+        SkColor fSVGColor;
+        const char* fSkiaName;
+        SkColor fSkiaColor;
+    } colorCompare[] = {  // see https://www.w3.org/TR/SVG/types.html#ColorKeywords
+        {"black",     SkColorSetRGB(  0,   0,   0),    SKIA_COLOR_PAIR(BLACK) },
+        {"darkgray",  SkColorSetRGB(169, 169, 169),    SKIA_COLOR_PAIR(DKGRAY) },
+        {"gray",      SkColorSetRGB(128, 128, 128),    SKIA_COLOR_PAIR(GRAY) },
+        {"lightgray", SkColorSetRGB(211, 211, 211),    SKIA_COLOR_PAIR(LTGRAY) },
+        {"white",     SkColorSetRGB(255, 255, 255),    SKIA_COLOR_PAIR(WHITE) },
+        {"red",       SkColorSetRGB(255,   0,   0),    SKIA_COLOR_PAIR(RED) },
+        {"green",     SkColorSetRGB(  0, 128,   0),    SKIA_COLOR_PAIR(GREEN) },
+        {"blue",      SkColorSetRGB(  0,   0, 255),    SKIA_COLOR_PAIR(BLUE) },
+        {"yellow",    SkColorSetRGB(255, 255,   0),    SKIA_COLOR_PAIR(YELLOW) },
+        {"aqua",      SkColorSetRGB(  0, 255, 255),    SKIA_COLOR_PAIR(CYAN) },
+        {"fuchsia",   SkColorSetRGB(255,   0, 255),    SKIA_COLOR_PAIR(MAGENTA) },
+    };
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(14);
+    for (auto compare : colorCompare) {
+        paint.setStyle(SkPaint::kFill_Style);
+        paint.setColor(compare.fSVGColor);
+        canvas->drawRect({5, 5, 15, 15}, paint);
+        paint.setColor(SK_ColorBLACK);
+        canvas->drawString(compare.fSVGName, 20, 16, paint);
+        paint.setColor(compare.fSkiaColor);
+        canvas->drawRect({105, 5, 115, 15}, paint);
+        paint.setColor(SK_ColorBLACK);
+        canvas->drawString(compare.fSkiaName, 120, 16, paint);
+        paint.setStyle(SkPaint::kStroke_Style);
+        canvas->drawRect({5, 5, 15, 15}, paint);
+        canvas->drawRect({105, 5, 115, 15}, paint);
+        canvas->translate(0, 20);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Color_Constants_b.cpp b/docs/examples/Color_Constants_b.cpp
new file mode 100644
index 0000000..8b9ba8a
--- /dev/null
+++ b/docs/examples/Color_Constants_b.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9ca1e2a5b9b4c92ecf4409d0813867d6
+REG_FIDDLE(Color_Constants_b, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    std::vector<uint32_t> srcPixels;
+    constexpr int width = 256;
+    constexpr int height = 256;
+    srcPixels.resize(width * height);
+    SkImageInfo imageInfo = SkImageInfo::MakeN32Premul(width, height);
+    SkPixmap pixmap(imageInfo, &srcPixels.front(), imageInfo.minRowBytes());
+    pixmap.erase(SK_ColorTRANSPARENT);
+    pixmap.erase(SK_ColorRED, { 24, 24, 192, 192 } );
+    pixmap.erase(SK_ColorTRANSPARENT, { 48, 48, 168, 168 } );
+    SkBitmap bitmap;
+    bitmap.installPixels(pixmap);
+    canvas->drawBitmap(bitmap, 0, 0);
+    canvas->drawBitmap(bitmap, 48, 48);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Color_Constants_c.cpp b/docs/examples/Color_Constants_c.cpp
new file mode 100644
index 0000000..c3c937f
--- /dev/null
+++ b/docs/examples/Color_Constants_c.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6971489f28291f08e429cc6ccc73b09b
+REG_FIDDLE(Color_Constants_c, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    std::vector<uint32_t> srcPixels;
+    constexpr int width = 256;
+    constexpr int height = 256;
+    srcPixels.resize(width * height);
+    SkImageInfo imageInfo = SkImageInfo::MakeN32Premul(width, height);
+    SkPixmap pixmap(imageInfo, &srcPixels.front(), imageInfo.minRowBytes());
+    pixmap.erase(SK_ColorTRANSPARENT);
+    pixmap.erase(SK_ColorRED, { 24, 24, 192, 192 } );
+    pixmap.erase(SK_ColorBLACK, { 48, 48, 168, 168 } );
+    SkBitmap bitmap;
+    bitmap.installPixels(pixmap);
+    canvas->drawBitmap(bitmap, 0, 0);
+    canvas->drawBitmap(bitmap, 48, 48);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Color_Constants_d.cpp b/docs/examples/Color_Constants_d.cpp
new file mode 100644
index 0000000..c3aa498
--- /dev/null
+++ b/docs/examples/Color_Constants_d.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=fce650f997e802d4e55edf62b8437a2d
+REG_FIDDLE(Color_Constants_d, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    std::vector<uint32_t> srcPixels;
+    constexpr int width = 256;
+    constexpr int height = 256;
+    srcPixels.resize(width * height);
+    SkImageInfo imageInfo = SkImageInfo::MakeN32Premul(width, height);
+    SkPixmap pixmap(imageInfo, &srcPixels.front(), imageInfo.minRowBytes());
+    pixmap.erase(SK_ColorTRANSPARENT);
+    pixmap.erase(SK_ColorRED, { 24, 24, 192, 192 } );
+    pixmap.erase(SK_ColorWHITE, { 48, 48, 168, 168 } );
+    SkBitmap bitmap;
+    bitmap.installPixels(pixmap);
+    canvas->drawBitmap(bitmap, 0, 0);
+    canvas->drawBitmap(bitmap, 48, 48);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Color_Dodge.cpp b/docs/examples/Color_Dodge.cpp
new file mode 100644
index 0000000..0d12c1f
--- /dev/null
+++ b/docs/examples/Color_Dodge.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=280ad6267a7d2d77b6d2c4531c6fc0bf
+REG_FIDDLE(Color_Dodge, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->drawImage(image, 0, 0);
+    canvas->clipRect({128, 0, 256, 256});
+    canvas->drawColor(SkColorSetARGB(0x80, 0x90, 0x90, 0x90), SkBlendMode::kColorDodge);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Color_Filter_Methods.cpp b/docs/examples/Color_Filter_Methods.cpp
new file mode 100644
index 0000000..96bf2dc
--- /dev/null
+++ b/docs/examples/Color_Filter_Methods.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5abde56ca2f89a18b8e231abd1b57c56
+REG_FIDDLE(Color_Filter_Methods, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setColorFilter(SkColorMatrixFilter::MakeLightingFilter(0xFFFFFF, 0xFF0000));
+    for (SkColor c : { SK_ColorBLACK, SK_ColorGREEN } ) {
+        paint.setColor(c);
+        canvas->drawRect(SkRect::MakeXYWH(10, 10, 50, 50), paint);
+        paint.setAlpha(0x80);
+        canvas->drawRect(SkRect::MakeXYWH(60, 60, 50, 50), paint);
+        canvas->translate(100, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Color_Methods.cpp b/docs/examples/Color_Methods.cpp
new file mode 100644
index 0000000..5545839
--- /dev/null
+++ b/docs/examples/Color_Methods.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=214b559d75c65a7bef6ef4be1f860053
+REG_FIDDLE(Color_Methods, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setColor(0x8000FF00);  // transparent green
+    canvas->drawCircle(50, 50, 40, paint);
+    paint.setARGB(128, 255, 0, 0); // transparent red
+    canvas->drawCircle(80, 50, 40, paint);
+    paint.setColor(SK_ColorBLUE);
+    paint.setAlpha(0x80);
+    canvas->drawCircle(65, 65, 40, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Color_Type_ARGB_4444.cpp b/docs/examples/Color_Type_ARGB_4444.cpp
new file mode 100644
index 0000000..3c4f3f3
--- /dev/null
+++ b/docs/examples/Color_Type_ARGB_4444.cpp
@@ -0,0 +1,30 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=33a360c3404ac21db801943336843d8e
+REG_FIDDLE(Color_Type_ARGB_4444, 256, 96, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->scale(16, 16);
+    SkBitmap bitmap;
+    SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kARGB_4444_SkColorType, kPremul_SkAlphaType);
+    bitmap.allocPixels(imageInfo);
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(SK_ColorGREEN);
+    canvas->drawBitmap(bitmap, 0, 0);
+    auto pack4444 = [](unsigned a, unsigned r, unsigned g, unsigned b) -> uint16_t {
+        return (a << 0) | (b << 4) | (g << 8) | (r << 12);
+    };
+    uint16_t red4444[] =  { pack4444(0xF, 0xF, 0x0, 0x0), pack4444(0xF, 0xb, 0x0, 0x0),
+                            pack4444(0xF, 0x7, 0x0, 0x0), pack4444(0xF, 0x3, 0x0, 0x0) };
+    uint16_t blue4444[] = { pack4444(0xF, 0x0, 0x0, 0xF), pack4444(0xF, 0x0, 0x0, 0xb),
+                            pack4444(0xF, 0x0, 0x0, 0x7), pack4444(0xF, 0x0, 0x0, 0x3) };
+    SkPixmap redPixmap(imageInfo, &red4444, imageInfo.minRowBytes());
+    if (bitmap.writePixels(redPixmap, 0, 0)) {
+        canvas->drawBitmap(bitmap, 2, 2);
+    }
+    SkPixmap bluePixmap(imageInfo, &blue4444, imageInfo.minRowBytes());
+    if (bitmap.writePixels(bluePixmap, 0, 0)) {
+        canvas->drawBitmap(bitmap, 4, 4);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Color_Type_Alpha_8.cpp b/docs/examples/Color_Type_Alpha_8.cpp
new file mode 100644
index 0000000..49b5c22
--- /dev/null
+++ b/docs/examples/Color_Type_Alpha_8.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=21ae21e4ce53d2018e042dd457997300
+REG_FIDDLE(Color_Type_Alpha_8, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->scale(16, 16);
+    SkBitmap bitmap;
+    SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kAlpha_8_SkColorType, kOpaque_SkAlphaType);
+    bitmap.allocPixels(imageInfo);
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(SK_ColorGREEN);
+    SkPaint orangePaint;
+    orangePaint.setARGB(0xFF, 0xFF, 0xA5, 0x00);
+    canvas->drawBitmap(bitmap, 0, 0, &orangePaint);
+    uint8_t alpha8[] = { 0xFF, 0xBB, 0x77, 0x33 };
+    SkPixmap alphaPixmap(imageInfo, &alpha8, imageInfo.minRowBytes());
+    if (bitmap.writePixels(alphaPixmap, 0, 0)) {
+        canvas->drawBitmap(bitmap, 2, 2, &orangePaint);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Color_Type_BGRA_8888.cpp b/docs/examples/Color_Type_BGRA_8888.cpp
new file mode 100644
index 0000000..94ff42e
--- /dev/null
+++ b/docs/examples/Color_Type_BGRA_8888.cpp
@@ -0,0 +1,30 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=945ce5344fce5470f8604b2e06e9f9ae
+REG_FIDDLE(Color_Type_BGRA_8888, 256, 96, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->scale(16, 16);
+    SkBitmap bitmap;
+    SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kBGRA_8888_SkColorType, kPremul_SkAlphaType);
+    bitmap.allocPixels(imageInfo);
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(SK_ColorGREEN);
+    canvas->drawBitmap(bitmap, 0, 0);
+    auto pack8888 = [](unsigned a, unsigned r, unsigned g, unsigned b) -> uint32_t {
+        return (b << 0) | (g << 8) | (r << 16) | (a << 24);
+    };
+    uint32_t red8888[] = { pack8888(0xFF, 0xFF, 0x0, 0x0), pack8888(0xFF, 0xbb, 0x0, 0x0),
+                           pack8888(0xFF, 0x99, 0x0, 0x0), pack8888(0xFF, 0x55, 0x0, 0x0) };
+    uint32_t blue8888[] = { pack8888(0xFF, 0x0, 0x0, 0x0FF), pack8888(0xFF, 0x0, 0x0, 0x0bb),
+                            pack8888(0xFF, 0x0, 0x0, 0x099), pack8888(0xFF, 0x0, 0x0, 0x055) };
+    SkPixmap redPixmap(imageInfo, &red8888, imageInfo.minRowBytes());
+    if (bitmap.writePixels(redPixmap, 0, 0)) {
+        canvas->drawBitmap(bitmap, 2, 2);
+    }
+    SkPixmap bluePixmap(imageInfo, &blue8888, imageInfo.minRowBytes());
+    if (bitmap.writePixels(bluePixmap, 0, 0)) {
+        canvas->drawBitmap(bitmap, 4, 4);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Color_Type_Gray_8.cpp b/docs/examples/Color_Type_Gray_8.cpp
new file mode 100644
index 0000000..3f493be
--- /dev/null
+++ b/docs/examples/Color_Type_Gray_8.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=93da0eb0b6722a4f33dc7dae094abf0b
+REG_FIDDLE(Color_Type_Gray_8, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->scale(16, 16);
+    SkBitmap bitmap;
+    SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kGray_8_SkColorType, kOpaque_SkAlphaType);
+    bitmap.allocPixels(imageInfo);
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(SK_ColorGREEN);
+    canvas->drawBitmap(bitmap, 0, 0);
+    uint8_t gray8[] = { 0xFF, 0xBB, 0x77, 0x33 };
+    SkPixmap grayPixmap(imageInfo, &gray8, imageInfo.minRowBytes());
+    if (bitmap.writePixels(grayPixmap, 0, 0)) {
+        canvas->drawBitmap(bitmap, 2, 2);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Color_Type_RGBA_1010102.cpp b/docs/examples/Color_Type_RGBA_1010102.cpp
new file mode 100644
index 0000000..4244d6f
--- /dev/null
+++ b/docs/examples/Color_Type_RGBA_1010102.cpp
@@ -0,0 +1,33 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1282dc1127ce1b0061544619ae4de0f0
+REG_FIDDLE(Color_Type_RGBA_1010102, 256, 96, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->scale(16, 16);
+    SkBitmap bitmap;
+    SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kRGBA_1010102_SkColorType, kOpaque_SkAlphaType);
+    bitmap.allocPixels(imageInfo);
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(SK_ColorGREEN);
+    canvas->drawBitmap(bitmap, 0, 0);
+    auto pack1010102 = [](unsigned r, unsigned g, unsigned b, unsigned a) -> uint32_t {
+        return (r << 0) | (g << 10) | (b << 20) | (a << 30);
+    };
+    uint32_t redBits[] =  { pack1010102(0x3FF, 0x000, 0x000, 0x3),
+                            pack1010102(0x2ff, 0x000, 0x000, 0x3),
+                            pack1010102(0x1ff, 0x000, 0x000, 0x3),
+                            pack1010102(0x0ff, 0x000, 0x000, 0x3) };
+    uint32_t blueBits[] = { pack1010102(0x000, 0x000, 0x3FF, 0x3),
+                            pack1010102(0x000, 0x000, 0x2ff, 0x3),
+                            pack1010102(0x000, 0x000, 0x1ff, 0x3),
+                            pack1010102(0x000, 0x000, 0x0ff, 0x3) };
+    if (bitmap.installPixels(imageInfo, (void*) redBits, imageInfo.minRowBytes())) {
+        canvas->drawBitmap(bitmap, 2, 2);
+    }
+    SkPixmap bluePixmap(imageInfo, &blueBits, imageInfo.minRowBytes());
+    if (bitmap.installPixels(imageInfo, (void*) blueBits, imageInfo.minRowBytes())) {
+        canvas->drawBitmap(bitmap, 4, 4);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Color_Type_RGBA_8888.cpp b/docs/examples/Color_Type_RGBA_8888.cpp
new file mode 100644
index 0000000..d598cd8
--- /dev/null
+++ b/docs/examples/Color_Type_RGBA_8888.cpp
@@ -0,0 +1,30 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=947922a19d59893fe7f9d9ee1954379b
+REG_FIDDLE(Color_Type_RGBA_8888, 256, 96, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->scale(16, 16);
+    SkBitmap bitmap;
+    SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+    bitmap.allocPixels(imageInfo);
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(SK_ColorGREEN);
+    canvas->drawBitmap(bitmap, 0, 0);
+    auto pack8888 = [](unsigned a, unsigned r, unsigned g, unsigned b) -> uint32_t {
+        return (r << 0) | (g << 8) | (b << 16) | (a << 24);
+    };
+    uint32_t red8888[] = { pack8888(0xFF, 0xFF, 0x0, 0x0), pack8888(0xFF, 0xbb, 0x0, 0x0),
+                           pack8888(0xFF, 0x77, 0x0, 0x0), pack8888(0xFF, 0x33, 0x0, 0x0) };
+    uint32_t blue8888[] = { pack8888(0xFF, 0x0, 0x0, 0x0FF), pack8888(0xFF, 0x0, 0x0, 0x0bb),
+                            pack8888(0xFF, 0x0, 0x0, 0x077), pack8888(0xFF, 0x0, 0x0, 0x033) };
+    SkPixmap redPixmap(imageInfo, &red8888, imageInfo.minRowBytes());
+    if (bitmap.writePixels(redPixmap, 0, 0)) {
+        canvas->drawBitmap(bitmap, 2, 2);
+    }
+    SkPixmap bluePixmap(imageInfo, &blue8888, imageInfo.minRowBytes());
+    if (bitmap.writePixels(bluePixmap, 0, 0)) {
+        canvas->drawBitmap(bitmap, 4, 4);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Color_Type_RGBA_F16.cpp b/docs/examples/Color_Type_RGBA_F16.cpp
new file mode 100644
index 0000000..e89aa62
--- /dev/null
+++ b/docs/examples/Color_Type_RGBA_F16.cpp
@@ -0,0 +1,52 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=dd81527bbdf5eaae7dd21ac04ab84f9e
+REG_FIDDLE(Color_Type_RGBA_F16, 256, 96, false, 0) {
+union FloatUIntUnion {
+    uint32_t fUInt;
+    float    fFloat;
+};
+uint16_t FloatToHalf(float f) {
+    static const FloatUIntUnion magic = { 15 << 23 };
+    static const uint32_t round_mask = ~0xfffu;
+    FloatUIntUnion floatUnion;
+    floatUnion.fFloat = f;
+    uint32_t sign = floatUnion.fUInt & 0x80000000u;
+    floatUnion.fUInt ^= sign;
+    floatUnion.fUInt &= round_mask;
+    floatUnion.fFloat *= magic.fFloat;
+    floatUnion.fUInt -= round_mask;
+    return (floatUnion.fUInt >> 13) | (sign >> 16);
+}
+
+void draw(SkCanvas* canvas) {
+    canvas->scale(16, 16);
+    SkBitmap bitmap;
+    SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kRGBA_F16_SkColorType, kPremul_SkAlphaType);
+    bitmap.allocPixels(imageInfo);
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(SK_ColorGREEN);
+    canvas->drawBitmap(bitmap, 0, 0);
+    auto H = [](float c) -> uint16_t {
+        return FloatToHalf(c);
+    };
+                             //     R        G        B        A
+    uint16_t red_f16[][4] =  { { H(1.0), H(0.0), H(0.0), H(1.0) },
+                               { H(.75), H(0.0), H(0.0), H(1.0) },
+                               { H(.50), H(0.0), H(0.0), H(1.0) },
+                               { H(.25), H(0.0), H(0.0), H(1.0) } };
+    uint16_t blue_f16[][4] = { { H(0.0), H(0.0), H(1.0), H(1.0) },
+                               { H(0.0), H(0.0), H(.75), H(1.0) },
+                               { H(0.0), H(0.0), H(.50), H(1.0) },
+                               { H(0.0), H(0.0), H(.25), H(1.0) } };
+    SkPixmap redPixmap(imageInfo, red_f16, imageInfo.minRowBytes());
+    if (bitmap.writePixels(redPixmap, 0, 0)) {
+        canvas->drawBitmap(bitmap, 2, 2);
+    }
+    SkPixmap bluePixmap(imageInfo, blue_f16, imageInfo.minRowBytes());
+    if (bitmap.writePixels(bluePixmap, 0, 0)) {
+        canvas->drawBitmap(bitmap, 4, 4);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Color_Type_RGB_101010.cpp b/docs/examples/Color_Type_RGB_101010.cpp
new file mode 100644
index 0000000..deea7bb
--- /dev/null
+++ b/docs/examples/Color_Type_RGB_101010.cpp
@@ -0,0 +1,29 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=92f81aa0459230459600a01e79ccff29
+REG_FIDDLE(Color_Type_RGB_101010, 256, 96, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->scale(16, 16);
+    SkBitmap bitmap;
+    SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kRGB_101010x_SkColorType, kOpaque_SkAlphaType);
+    bitmap.allocPixels(imageInfo);
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(SK_ColorGREEN);
+    canvas->drawBitmap(bitmap, 0, 0);
+    auto pack101010x = [](unsigned r, unsigned g, unsigned b) -> uint32_t {
+        return (r << 0) | (g << 10) | (b << 20);
+    };
+    uint32_t redBits[] =  { pack101010x(0x3FF, 0x000, 0x000), pack101010x(0x2ff, 0x000, 0x000),
+    pack101010x(0x1ff, 0x000, 0x000), pack101010x(0x0ff, 0x000, 0x000) };
+    uint32_t blueBits[] = { pack101010x(0x000, 0x000, 0x3FF), pack101010x(0x000, 0x000, 0x2ff),
+    pack101010x(0x000, 0x000, 0x1ff), pack101010x(0x000, 0x000, 0x0ff) };
+    if (bitmap.installPixels(imageInfo, (void*) redBits, imageInfo.minRowBytes())) {
+        canvas->drawBitmap(bitmap, 2, 2);
+    }
+    SkPixmap bluePixmap(imageInfo, &blueBits, imageInfo.minRowBytes());
+    if (bitmap.installPixels(imageInfo, (void*) blueBits, imageInfo.minRowBytes())) {
+        canvas->drawBitmap(bitmap, 4, 4);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Color_Type_RGB_565.cpp b/docs/examples/Color_Type_RGB_565.cpp
new file mode 100644
index 0000000..8cbcfdf
--- /dev/null
+++ b/docs/examples/Color_Type_RGB_565.cpp
@@ -0,0 +1,30 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7e7c46bb4572e21e13529ff364eb0a9c
+REG_FIDDLE(Color_Type_RGB_565, 256, 96, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->scale(16, 16);
+    SkBitmap bitmap;
+    SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kRGB_565_SkColorType, kOpaque_SkAlphaType);
+    bitmap.allocPixels(imageInfo);
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(SK_ColorGREEN);
+    canvas->drawBitmap(bitmap, 0, 0);
+    auto pack565 = [](unsigned r, unsigned g, unsigned b) -> uint16_t {
+        return (b << 0) | (g << 5) | (r << 11);
+    };
+    uint16_t red565[] =  { pack565(0x1F, 0x00, 0x00), pack565(0x17, 0x00, 0x00),
+                           pack565(0x0F, 0x00, 0x00), pack565(0x07, 0x00, 0x00) };
+    uint16_t blue565[] = { pack565(0x00, 0x00, 0x1F), pack565(0x00, 0x00, 0x17),
+                           pack565(0x00, 0x00, 0x0F), pack565(0x00, 0x00, 0x07) };
+    SkPixmap redPixmap(imageInfo, &red565, imageInfo.minRowBytes());
+    if (bitmap.writePixels(redPixmap, 0, 0)) {
+        canvas->drawBitmap(bitmap, 2, 2);
+    }
+    SkPixmap bluePixmap(imageInfo, &blue565, imageInfo.minRowBytes());
+    if (bitmap.writePixels(bluePixmap, 0, 0)) {
+        canvas->drawBitmap(bitmap, 4, 4);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Color_Type_RGB_888.cpp b/docs/examples/Color_Type_RGB_888.cpp
new file mode 100644
index 0000000..04adc46
--- /dev/null
+++ b/docs/examples/Color_Type_RGB_888.cpp
@@ -0,0 +1,28 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4260d6cc15db2c60c07f6fdc8d9ae425
+REG_FIDDLE(Color_Type_RGB_888, 256, 96, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->scale(16, 16);
+    SkBitmap bitmap;
+    SkImageInfo imageInfo = SkImageInfo::Make(2, 2, kRGB_888x_SkColorType, kOpaque_SkAlphaType);
+    bitmap.allocPixels(imageInfo);
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(SK_ColorGREEN);
+    canvas->drawBitmap(bitmap, 0, 0);
+    auto pack888 = [](unsigned r, unsigned g, unsigned b) -> uint32_t {
+        return (r << 0) | (g << 8) | (b << 16);
+    };
+    uint32_t red888[] =  { pack888(0xFF, 0x00, 0x00), pack888(0xbb, 0x00, 0x00),
+        pack888(0x77, 0x00, 0x00), pack888(0x33, 0x00, 0x00) };
+    uint32_t blue888[] = { pack888(0x00, 0x00, 0xFF), pack888(0x00, 0x00, 0xbb),
+        pack888(0x00, 0x00, 0x77), pack888(0x00, 0x00, 0x33) };
+    if (bitmap.installPixels(imageInfo, (void*) red888, imageInfo.minRowBytes())) {
+        canvas->drawBitmap(bitmap, 2, 2);
+    }
+    if (bitmap.installPixels(imageInfo, (void*) blue888, imageInfo.minRowBytes())) {
+        canvas->drawBitmap(bitmap, 4, 4);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Conic_Weight_a.cpp b/docs/examples/Conic_Weight_a.cpp
new file mode 100644
index 0000000..142d2f3
--- /dev/null
+++ b/docs/examples/Conic_Weight_a.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2aadded3d20dfef34d1c8abe28c7bc8d
+REG_FIDDLE(Conic_Weight_a, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const char* verbNames[] = { "move", "line", "quad", "conic", "cubic", "close", "done" };
+    const int pointCount[]  = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };
+    SkPath path;
+    path.conicTo(20, 30, 50, 60, 1);
+    SkPath::Iter iter(path, false);
+    SkPath::Verb verb;
+    do {
+       SkPoint points[4];
+       verb = iter.next(points);
+       SkDebugf("%s ", verbNames[(int) verb]);
+       for (int i = 0; i < pointCount[(int) verb]; ++i) {
+            SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);
+       }
+       if (SkPath::kConic_Verb == verb) {
+           SkDebugf("weight = %g", iter.conicWeight());
+       }
+       SkDebugf("\n");
+    } while (SkPath::kDone_Verb != verb);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Conic_Weight_b.cpp b/docs/examples/Conic_Weight_b.cpp
new file mode 100644
index 0000000..9785ae2
--- /dev/null
+++ b/docs/examples/Conic_Weight_b.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e88f554efacfa9f75f270fb1c0add5b4
+REG_FIDDLE(Conic_Weight_b, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const char* verbNames[] = { "move", "line", "quad", "conic", "cubic", "close", "done" };
+    const int pointCount[]  = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };
+    SkPath path;
+    path.arcTo(20, 0, 20, 20, 20);
+    SkPath::Iter iter(path, false);
+    SkPath::Verb verb;
+    do {
+       SkPoint points[4];
+       verb = iter.next(points);
+       SkDebugf("%s ", verbNames[(int) verb]);
+       for (int i = 0; i < pointCount[(int) verb]; ++i) {
+            SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);
+       }
+       if (SkPath::kConic_Verb == verb) {
+           SkDebugf("weight = %g", iter.conicWeight());
+       }
+       SkDebugf("\n");
+    } while (SkPath::kDone_Verb != verb);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Conic_Weight_c.cpp b/docs/examples/Conic_Weight_c.cpp
new file mode 100644
index 0000000..fa5959e
--- /dev/null
+++ b/docs/examples/Conic_Weight_c.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6fb11419e99297fe2fe666c296117fb9
+REG_FIDDLE(Conic_Weight_c, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const char* verbNames[] = { "move", "line", "quad", "conic", "cubic", "close", "done" };
+    const int pointCount[]  = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };
+    SkPath path;
+    path.conicTo(20, 0, 20, 20, SK_ScalarInfinity);
+    SkPath::Iter iter(path, false);
+    SkPath::Verb verb;
+    do {
+       SkPoint points[4];
+       verb = iter.next(points);
+       SkDebugf("%s ", verbNames[(int) verb]);
+       for (int i = 0; i < pointCount[(int) verb]; ++i) {
+            SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);
+       }
+       if (SkPath::kConic_Verb == verb) {
+           SkDebugf("weight = %g", iter.conicWeight());
+       }
+       SkDebugf("\n");
+    } while (SkPath::kDone_Verb != verb);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Cubic.cpp b/docs/examples/Cubic.cpp
new file mode 100644
index 0000000..5e69b01
--- /dev/null
+++ b/docs/examples/Cubic.cpp
@@ -0,0 +1,28 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=466445ed991d86de08587066392d654a
+REG_FIDDLE(Cubic, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkPoint cubicPts[] = {{20, 150}, {90, 10}, {160, 150}, {230, 10}};
+    SkColor colors[] = { 0xff88ff00, 0xff0088bb, 0xff6600cc, 0xffbb3377 };
+    for (unsigned i = 0; i < SK_ARRAY_COUNT(colors); ++i) {
+        paint.setColor(0x7fffffff & colors[i]);
+        paint.setStrokeWidth(1);
+        for (unsigned j = 0; j < 3; ++j) {
+            canvas->drawLine(cubicPts[j], cubicPts[j + 1], paint);
+        }
+        SkPath path;
+        path.moveTo(cubicPts[0]);
+        path.cubicTo(cubicPts[1], cubicPts[2], cubicPts[3]);
+        paint.setStrokeWidth(3);
+        paint.setColor(colors[i]);
+        canvas->drawPath(path, paint);
+        cubicPts[1].fY += 30;
+        cubicPts[2].fX += 30;
+   }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Darken.cpp b/docs/examples/Darken.cpp
new file mode 100644
index 0000000..be738c9
--- /dev/null
+++ b/docs/examples/Darken.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=23c974d2759f523ca2f4a78ae86855c3
+REG_FIDDLE(Darken, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->drawImage(image, 0, 0);
+    SkColor colors[] = { SK_ColorWHITE, SK_ColorBLACK };
+    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
+    SkPaint paint;
+    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
+            SkTileMode::kClamp));
+    paint.setBlendMode(SkBlendMode::kDarken);
+    canvas->drawPaint(paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Device_Text.cpp b/docs/examples/Device_Text.cpp
new file mode 100644
index 0000000..5a3c0f0
--- /dev/null
+++ b/docs/examples/Device_Text.cpp
@@ -0,0 +1,28 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4606ae1be792d6bc46d496432f050ee9
+REG_FIDDLE(Device_Text, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.allocN32Pixels(24, 33);
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(SK_ColorWHITE);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(20);
+    for (bool lcd : { false, true }) {
+        paint.setLCDRenderText(lcd);
+        for (bool subpixel : { false, true }) {
+            paint.setSubpixelText(subpixel);
+            offscreen.drawString(",,,,", 0, 4, paint);
+            offscreen.translate(0, 7);
+        }
+    }
+    canvas->drawBitmap(bitmap, 4, 12);
+    canvas->scale(9, 9);
+    canvas->drawBitmap(bitmap, 4, -1);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Difference.cpp b/docs/examples/Difference.cpp
new file mode 100644
index 0000000..8ad5dcc
--- /dev/null
+++ b/docs/examples/Difference.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=52d2c8d1b9b428de4477b4caa1543a3d
+REG_FIDDLE(Difference, 256, 256, false, 5) {
+void draw(SkCanvas* canvas) {
+    canvas->drawImage(image, 0, 0);
+    canvas->drawImage(image, 128, 0);
+    canvas->drawImage(image, 0, 128);
+    canvas->drawImage(image, 128, 128);
+    SkPaint paint;
+    paint.setBlendMode(SkBlendMode::kDstATop);
+    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
+    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
+    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    canvas->clipRect( { 30, 30, 226, 226 } );
+    canvas->drawColor(0x80bb9977, SkBlendMode::kDifference);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Dither_a.cpp b/docs/examples/Dither_a.cpp
new file mode 100644
index 0000000..2587848
--- /dev/null
+++ b/docs/examples/Dither_a.cpp
@@ -0,0 +1,25 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8b26507690b71462f44642b911890bbf
+REG_FIDDLE(Dither_a, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bm16;
+    bm16.allocPixels(SkImageInfo::Make(32, 32, kRGB_565_SkColorType, kOpaque_SkAlphaType));
+    SkCanvas c16(bm16);
+    SkPaint colorPaint;
+    for (auto dither : { false, true } ) {
+        colorPaint.setDither(dither);
+        for (auto colors : { 0xFF333333, 0xFF666666, 0xFF999999, 0xFFCCCCCC } ) {
+            for (auto mask : { 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFFFFFFFF } ) {
+                 colorPaint.setColor(colors & mask);
+                 c16.drawRect({0, 0, 8, 4}, colorPaint);
+                 c16.translate(8, 0);
+            }
+            c16.translate(-32, 4);
+        }
+    }
+    canvas->scale(8, 8);
+    canvas->drawBitmap(bm16, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Dither_b.cpp b/docs/examples/Dither_b.cpp
new file mode 100644
index 0000000..0300b3d
--- /dev/null
+++ b/docs/examples/Dither_b.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=76d4d4a7931a48495e4d5f54e073be53
+REG_FIDDLE(Dither_b, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->clear(0);
+    SkBitmap bm32;
+    bm32.allocPixels(SkImageInfo::Make(20, 10, kN32_SkColorType, kPremul_SkAlphaType));
+    SkCanvas c32(bm32);
+    SkPoint points[] = {{0, 0}, {20, 0}};
+    SkColor colors[] = {0xFF334455, 0xFF662211 };
+    SkPaint paint;
+    paint.setShader(SkGradientShader::MakeLinear(
+                     points, colors, nullptr, SK_ARRAY_COUNT(colors),
+                     SkTileMode::kClamp));
+    paint.setDither(true);
+    c32.drawPaint(paint);
+    canvas->scale(12, 12);
+    canvas->drawBitmap(bm32, 0, 0);
+    paint.setBlendMode(SkBlendMode::kPlus);
+    canvas->drawBitmap(bm32, 0, 11, &paint);
+    canvas->drawBitmap(bm32, 0, 11, &paint);
+    canvas->drawBitmap(bm32, 0, 11, &paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Draw_Looper_Methods.cpp b/docs/examples/Draw_Looper_Methods.cpp
new file mode 100644
index 0000000..aca8e4b
--- /dev/null
+++ b/docs/examples/Draw_Looper_Methods.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=84ec12a36e50df5ac565cc7a75ffbe9f
+REG_FIDDLE(Draw_Looper_Methods, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkLayerDrawLooper::LayerInfo info;
+    info.fPaintBits = (SkLayerDrawLooper::BitFlags) SkLayerDrawLooper::kColorFilter_Bit;
+    info.fColorMode = SkBlendMode::kSrc;
+    SkLayerDrawLooper::Builder looperBuilder;
+    SkPaint* loopPaint = looperBuilder.addLayer(info);
+    loopPaint->setColor(SK_ColorRED);
+    info.fOffset.set(20, 20);
+    loopPaint = looperBuilder.addLayer(info);
+    loopPaint->setColor(SK_ColorBLUE);
+    SkPaint paint;
+    paint.setDrawLooper(looperBuilder.detach());
+    canvas->drawCircle(50, 50, 50, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Dst.cpp b/docs/examples/Dst.cpp
new file mode 100644
index 0000000..2b5899a
--- /dev/null
+++ b/docs/examples/Dst.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=35915a2273be1076f00f2e47998ce808
+REG_FIDDLE(Dst, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkRSXform xforms[] = { { .5f, 0, 0, 0 }, {0, .5f, 125, 128 } };
+    SkRect tex[] = { { 0, 0, 250, 250 }, { 0, 0, 250, 250 } };
+    SkColor colors[] = { 0x7f55aa00, 0x7f3333bf };
+    canvas->drawAtlas(image.get(), xforms, tex, colors, 2, SkBlendMode::kSrc, nullptr, nullptr);
+    canvas->translate(128, 0);
+    canvas->drawAtlas(image.get(), xforms, tex, colors, 2, SkBlendMode::kDst, nullptr, nullptr);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Dst_Atop.cpp b/docs/examples/Dst_Atop.cpp
new file mode 100644
index 0000000..e6d82b1
--- /dev/null
+++ b/docs/examples/Dst_Atop.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1955856d45773a4fd914fcc1f813222f
+REG_FIDDLE(Dst_Atop, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
+    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
+    SkPaint paint;
+    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    paint.setBlendMode(SkBlendMode::kDstATop);
+    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
+    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
+    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    canvas->clipRect( { 30, 30, 226, 226 } );
+    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kSrcATop);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Dst_In.cpp b/docs/examples/Dst_In.cpp
new file mode 100644
index 0000000..c4f0abd
--- /dev/null
+++ b/docs/examples/Dst_In.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a5eeba05ccf6097a5d110a9d64f97c25
+REG_FIDDLE(Dst_In, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
+    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
+    SkPaint paint;
+    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    paint.setBlendMode(SkBlendMode::kDstIn);
+    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
+    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
+    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    canvas->clipRect( { 30, 30, 226, 226 } );
+    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kDstIn);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Dst_Out.cpp b/docs/examples/Dst_Out.cpp
new file mode 100644
index 0000000..7378f73
--- /dev/null
+++ b/docs/examples/Dst_Out.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b9a894c9accfc5d94081bbd77d5d790a
+REG_FIDDLE(Dst_Out, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
+    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
+    SkPaint paint;
+    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    paint.setBlendMode(SkBlendMode::kDstIn);
+    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
+    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
+    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    canvas->clipRect( { 30, 30, 226, 226 } );
+    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kDstOut);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Dst_Over.cpp b/docs/examples/Dst_Over.cpp
new file mode 100644
index 0000000..d8e7d86
--- /dev/null
+++ b/docs/examples/Dst_Over.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=10dbb4d97902956ef5f5f8562f65119e
+REG_FIDDLE(Dst_Over, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
+    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
+    SkPaint paint;
+    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    paint.setBlendMode(SkBlendMode::kDstIn);
+    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
+    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
+    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    canvas->clipRect( { 30, 30, 226, 226 } );
+    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kDstOver);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Exclusion.cpp b/docs/examples/Exclusion.cpp
new file mode 100644
index 0000000..30a7e4f
--- /dev/null
+++ b/docs/examples/Exclusion.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a544ee1c67c7c557a9e54d5e99f94bb6
+REG_FIDDLE(Exclusion, 256, 256, false, 5) {
+void draw(SkCanvas* canvas) {
+    canvas->drawImage(image, 0, 0);
+    canvas->drawImage(image, 128, 0);
+    canvas->drawImage(image, 0, 128);
+    canvas->drawImage(image, 128, 128);
+    SkPaint paint;
+    paint.setBlendMode(SkBlendMode::kDstATop);
+    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
+    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
+    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    canvas->clipRect( { 30, 30, 226, 226 } );
+    canvas->drawColor(0x80bb9977, SkBlendMode::kExclusion);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Fake_Bold.cpp b/docs/examples/Fake_Bold.cpp
new file mode 100644
index 0000000..37b0e51
--- /dev/null
+++ b/docs/examples/Fake_Bold.cpp
@@ -0,0 +1,21 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e811f4829a2daaaeaad3795504a7e02a
+REG_FIDDLE(Fake_Bold, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(40);
+    canvas->drawString("OjYy_-", 10, 35, paint);
+    paint.setFakeBoldText(true);
+    canvas->drawString("OjYy_-", 10, 75, paint);
+    // create a custom fake bold by varying the stroke width
+    paint.setFakeBoldText(false);
+    paint.setStyle(SkPaint::kStrokeAndFill_Style);
+    paint.setStrokeWidth(40.f / 48);
+    canvas->drawString("OjYy_-", 10, 115, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Filter_Quality_Methods.cpp b/docs/examples/Filter_Quality_Methods.cpp
new file mode 100644
index 0000000..c6c80df
--- /dev/null
+++ b/docs/examples/Filter_Quality_Methods.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=69369cff2f5b145a6f616092513266a0
+REG_FIDDLE(Filter_Quality_Methods, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    canvas->scale(.2f, .2f);
+    for (SkFilterQuality q : { kNone_SkFilterQuality, kLow_SkFilterQuality,
+                               kMedium_SkFilterQuality, kHigh_SkFilterQuality } ) {
+        paint.setFilterQuality(q);
+        canvas->drawImage(image.get(), 0, 0, &paint);
+        canvas->translate(550, 0);
+        if (kLow_SkFilterQuality == q) canvas->translate(-1100, 550);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Font_breakText.cpp b/docs/examples/Font_breakText.cpp
new file mode 100644
index 0000000..d2223e6
--- /dev/null
+++ b/docs/examples/Font_breakText.cpp
@@ -0,0 +1,24 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3cad18678254526be66ef162eecd1d23
+REG_FIDDLE(Font_breakText, 280, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(50);
+    const char str[] = "Breakfast";
+    const int count = sizeof(str) - 1;
+    canvas->drawText(str, count, 25, 50, paint);
+    SkScalar measuredWidth;
+    SkFont font;
+    font.setSize(50);
+    int partialBytes = font.breakText(str, count, kUTF8_SkTextEncoding,
+            100, &measuredWidth);
+    canvas->drawText(str, partialBytes, 25, 100, paint);
+    canvas->drawLine(25, 60, 25 + 100, 60, paint);
+    canvas->drawLine(25, 110, 25 + measuredWidth, 110, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/HSVToColor.cpp b/docs/examples/HSVToColor.cpp
new file mode 100644
index 0000000..cf53bc2
--- /dev/null
+++ b/docs/examples/HSVToColor.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=311a59931ac340b90f202cd6ac399a0a
+REG_FIDDLE(HSVToColor, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->drawBitmap(source, 0, 0);
+    for (int y = 0; y < 256; ++y) {
+       for (int x = 0; x < 256; ++x) {
+         SkColor color = source.getColor(x, y);
+         SkScalar hsv[3];
+         SkColorToHSV(color, hsv);
+         hsv[0] = hsv[0] + 90 >= 360 ? hsv[0] - 270 : hsv[0] + 90;
+         SkPaint paint;
+         paint.setColor(SkHSVToColor((x + y) % 256, hsv));
+         canvas->drawRect(SkRect::MakeXYWH(x, y, 1, 1), paint);
+      }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/HSVToColor_2.cpp b/docs/examples/HSVToColor_2.cpp
new file mode 100644
index 0000000..3ecd8c2
--- /dev/null
+++ b/docs/examples/HSVToColor_2.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d355a17547908cdbc2c38720974b5d11
+REG_FIDDLE(HSVToColor_2, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->drawBitmap(source, 0, 0);
+    for (int y = 0; y < 256; ++y) {
+       for (int x = 0; x < 256; ++x) {
+         SkColor color = source.getColor(x, y);
+         SkScalar hsv[3];
+         SkColorToHSV(color, hsv);
+         hsv[0] = hsv[0] + 90 >= 360 ? hsv[0] - 270 : hsv[0] + 90;
+         SkPaint paint;
+         paint.setColor(SkHSVToColor(hsv));
+         canvas->drawRect(SkRect::MakeXYWH(x, y, 1, 1), paint);
+      }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Hard_Light.cpp b/docs/examples/Hard_Light.cpp
new file mode 100644
index 0000000..eb6f307
--- /dev/null
+++ b/docs/examples/Hard_Light.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ac2fe555e2196e15863ea4ce74db3d54
+REG_FIDDLE(Hard_Light, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->drawImage(image, 0, 0);
+    const SkColor colors[] = { 0xFFFFFFFF, 0x00000000 };
+    SkPaint paint;
+    paint.setBlendMode(SkBlendMode::kHardLight);
+    paint.setShader(SkGradientShader::MakeRadial({ 128, 128}, 100, colors,
+         nullptr, SK_ARRAY_COUNT(colors), SkTileMode::kClamp));
+    canvas->clipRect({0, 128, 256, 256});
+    canvas->drawPaint(paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Hue.cpp b/docs/examples/Hue.cpp
new file mode 100644
index 0000000..39556ca
--- /dev/null
+++ b/docs/examples/Hue.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=41e45570d682397d3b8ff2f51bd9c574
+REG_FIDDLE(Hue, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->drawImage(image, 0, 0);
+    canvas->drawColor(0xFF00FF00, SkBlendMode::kHue);
+}
+}  // END FIDDLE
diff --git a/docs/examples/IPoint_Make.cpp b/docs/examples/IPoint_Make.cpp
new file mode 100644
index 0000000..a1c3cc0
--- /dev/null
+++ b/docs/examples/IPoint_Make.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e5cf5159525bd3140f288a95fe641fae
+REG_FIDDLE(IPoint_Make, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIPoint pt1 = {45, 66};
+    SkIPoint pt2 = SkIPoint::Make(45, 66);
+    SkDebugf("pt1 %c= pt2\n", pt1 == pt2 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/IPoint_add_operator.cpp b/docs/examples/IPoint_add_operator.cpp
new file mode 100644
index 0000000..2cd012d
--- /dev/null
+++ b/docs/examples/IPoint_add_operator.cpp
@@ -0,0 +1,30 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=63f4cba971c6d8434595906f865b5a29
+REG_FIDDLE(IPoint_add_operator, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    auto draw_lines = [=](const SkIPoint pts[], size_t count, SkPaint& paint) -> void {
+        for (size_t i = 0; i < count - 1; ++i) {
+            SkPoint p0, p1;
+            p0.iset(pts[i]);
+            p1.iset(pts[i + 1]);
+            canvas->drawLine(p0, p1, paint);
+        }
+    };
+    SkIPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 } };
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->scale(30, 15);
+    draw_lines(points, SK_ARRAY_COUNT(points), paint);
+    SkIPoint mod = {4, 1};
+    for (auto& point : points) {
+        point = point + mod;
+        mod.fX -= 1;
+        mod.fY += 1;
+    }
+    paint.setColor(SK_ColorRED);
+    draw_lines(points, SK_ARRAY_COUNT(points), paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/IPoint_addto_operator.cpp b/docs/examples/IPoint_addto_operator.cpp
new file mode 100644
index 0000000..5e77b08
--- /dev/null
+++ b/docs/examples/IPoint_addto_operator.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4eb2d95c9e9a66f05296e345bb68bd51
+REG_FIDDLE(IPoint_addto_operator, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    auto draw_lines = [=](const SkIPoint pts[], size_t count, SkPaint& paint) -> void {
+        for (size_t i = 0; i < count - 1; ++i) {
+            SkPoint p0, p1;
+            p0.iset(pts[i]);
+            p1.iset(pts[i + 1]);
+            canvas->drawLine(p0, p1, paint);
+        }
+    };
+    SkIPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 } };
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->scale(30, 15);
+    draw_lines(points, SK_ARRAY_COUNT(points), paint);
+    points[1] += {1, 1};
+    points[2] += {-1, -1};
+    paint.setColor(SK_ColorRED);
+    draw_lines(points, SK_ARRAY_COUNT(points), paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/IPoint_equal_operator.cpp b/docs/examples/IPoint_equal_operator.cpp
new file mode 100644
index 0000000..92f6218
--- /dev/null
+++ b/docs/examples/IPoint_equal_operator.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=37ffe2817d720f99e6c252332ce70460
+REG_FIDDLE(IPoint_equal_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIPoint test[] = { {0, -0}, {-1, -2}, {SK_MaxS32, -1}, {SK_NaN32, -1} };
+    for (const SkIPoint& pt : test) {
+        SkDebugf("pt: %d, %d  %c= pt\n", pt.fX, pt.fY, pt == pt ? '=' : '!');
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/IPoint_equals.cpp b/docs/examples/IPoint_equals.cpp
new file mode 100644
index 0000000..082994d
--- /dev/null
+++ b/docs/examples/IPoint_equals.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=64f575d36439d5b69aaed14ffeff1cc4
+REG_FIDDLE(IPoint_equals, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIPoint test[] = { {0, -0}, {-1, -2}, {SK_MaxS32, -1}, {SK_NaN32, -1} };
+    for (const SkIPoint& pt : test) {
+        SkDebugf("pt: %d, %d  %c= pt\n", pt.fX, pt.fY, pt.equals(pt.fX, pt.fY) ? '=' : '!');
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/IPoint_isZero.cpp b/docs/examples/IPoint_isZero.cpp
new file mode 100644
index 0000000..d0ccb30
--- /dev/null
+++ b/docs/examples/IPoint_isZero.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=658c1df611b4577cc7e0bb384e95737e
+REG_FIDDLE(IPoint_isZero, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIPoint pt = { 0, -0};
+    SkDebugf("pt.isZero() == %s\n", pt.isZero() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/IPoint_minus_operator.cpp b/docs/examples/IPoint_minus_operator.cpp
new file mode 100644
index 0000000..6de0b24
--- /dev/null
+++ b/docs/examples/IPoint_minus_operator.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b30d4780475d113a7fed3637af7f0db1
+REG_FIDDLE(IPoint_minus_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIPoint test[] = { {0, -0}, {-1, -2},
+                       { SK_MaxS32, SK_MinS32 },
+                       { SK_NaN32, SK_NaN32 } };
+    for (const SkIPoint& pt : test) {
+        SkIPoint negPt = -pt;
+        SkDebugf("pt: %d, %d  negate: %d, %d\n", pt.fX, pt.fY, negPt.fX, negPt.fY);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/IPoint_notequal_operator.cpp b/docs/examples/IPoint_notequal_operator.cpp
new file mode 100644
index 0000000..22994811
--- /dev/null
+++ b/docs/examples/IPoint_notequal_operator.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=dd89dc48dff69b53d99530b120f204bc
+REG_FIDDLE(IPoint_notequal_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIPoint test[] = { {0, -0}, {-1, -2}, {SK_MaxS32, -1}, {SK_NaN32, -1} };
+    for (const SkIPoint& pt : test) {
+        SkDebugf("pt: %d, %d  %c= pt\n", pt.fX, pt.fY, pt != pt ? '!' : '=');
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/IPoint_set.cpp b/docs/examples/IPoint_set.cpp
new file mode 100644
index 0000000..caf853a
--- /dev/null
+++ b/docs/examples/IPoint_set.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=165418b5718d79d8f1682a8a0ee32ba0
+REG_FIDDLE(IPoint_set, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIPoint pt1, pt2 = { SK_MinS32, SK_MaxS32 };
+    pt1.set(SK_MinS32, SK_MaxS32);
+    SkDebugf("pt1 %c= pt2\n", pt1 == pt2 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/IPoint_subtract_operator.cpp b/docs/examples/IPoint_subtract_operator.cpp
new file mode 100644
index 0000000..92282e6
--- /dev/null
+++ b/docs/examples/IPoint_subtract_operator.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e626e26bf557857b824aa7d03f723e0f
+REG_FIDDLE(IPoint_subtract_operator, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    auto draw_lines = [=](const SkIPoint pts[], size_t count, SkPaint& paint) -> void {
+        for (size_t i = 0; i < count - 1; ++i) {
+            SkPoint p0, p1;
+            p0.iset(pts[i]);
+            p1.iset(pts[i + 1]);
+            canvas->drawLine(p0, p1, paint);
+        }
+    };
+    SkIPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 } };
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->scale(30, 15);
+    draw_lines(points, SK_ARRAY_COUNT(points), paint);
+    points[1] += points[0] - points[3];
+    points[2] -= points[1] - points[0];
+    paint.setColor(SK_ColorRED);
+    draw_lines(points, SK_ARRAY_COUNT(points), paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/IPoint_subtractfrom_operator.cpp b/docs/examples/IPoint_subtractfrom_operator.cpp
new file mode 100644
index 0000000..11d9280
--- /dev/null
+++ b/docs/examples/IPoint_subtractfrom_operator.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a01e533dc7ab34ed728dc4e7a5f1f0ee
+REG_FIDDLE(IPoint_subtractfrom_operator, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    auto draw_lines = [=](const SkIPoint pts[], size_t count, SkPaint& paint) -> void {
+        for (size_t i = 0; i < count - 1; ++i) {
+            SkPoint p0, p1;
+            p0.iset(pts[i]);
+            p1.iset(pts[i + 1]);
+            canvas->drawLine(p0, p1, paint);
+        }
+    };
+    SkIPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 } };
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->scale(30, 15);
+    draw_lines(points, SK_ARRAY_COUNT(points), paint);
+    points[1] -= {1, 1};
+    points[2] -= {-1, -1};
+    paint.setColor(SK_ColorRED);
+    draw_lines(points, SK_ARRAY_COUNT(points), paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/IPoint_x.cpp b/docs/examples/IPoint_x.cpp
new file mode 100644
index 0000000..c6529f4
--- /dev/null
+++ b/docs/examples/IPoint_x.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=eed4185294f8a8216fc354e6ee6b2e3a
+REG_FIDDLE(IPoint_x, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIPoint pt1 = {45, 66};
+    SkDebugf("pt1.fX %c= pt1.x()\n", pt1.fX == pt1.x() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/IPoint_y.cpp b/docs/examples/IPoint_y.cpp
new file mode 100644
index 0000000..28a8dbb
--- /dev/null
+++ b/docs/examples/IPoint_y.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=35c41b8ba7cebf8c9a7a8494e610e14d
+REG_FIDDLE(IPoint_y, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIPoint pt1 = {45, 66};
+    SkDebugf("pt1.fY %c= pt1.y()\n", pt1.fY == pt1.y() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_EmptyIRect.cpp b/docs/examples/IRect_EmptyIRect.cpp
new file mode 100644
index 0000000..b59a35a
--- /dev/null
+++ b/docs/examples/IRect_EmptyIRect.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=65e0b9b52e907902630577941fb3ed6d
+REG_FIDDLE(IRect_EmptyIRect, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const SkIRect& rect = SkIRect::EmptyIRect();
+    SkDebugf("rect: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_Intersects.cpp b/docs/examples/IRect_Intersects.cpp
new file mode 100644
index 0000000..51ccbbf
--- /dev/null
+++ b/docs/examples/IRect_Intersects.cpp
@@ -0,0 +1,9 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0c67cf8981389efc7108369fb9b7976b
+REG_FIDDLE(IRect_Intersects, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkDebugf("%s intersection", SkIRect::Intersects({10, 40, 50, 80}, {30, 60, 70, 90}) ? "" : "no ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_IntersectsNoEmptyCheck.cpp b/docs/examples/IRect_IntersectsNoEmptyCheck.cpp
new file mode 100644
index 0000000..8e69061
--- /dev/null
+++ b/docs/examples/IRect_IntersectsNoEmptyCheck.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=dba234d15162fb5b26e1a96529ca6a2a
+REG_FIDDLE(IRect_IntersectsNoEmptyCheck, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkDebugf("%s intersection", SkIRect::IntersectsNoEmptyCheck(
+            {10, 40, 50, 80}, {30, 60, 70, 90}) ? "" : "no ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_MakeEmpty.cpp b/docs/examples/IRect_MakeEmpty.cpp
new file mode 100644
index 0000000..877063e
--- /dev/null
+++ b/docs/examples/IRect_MakeEmpty.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0ade3971c1d2616564992e286966ec8a
+REG_FIDDLE(IRect_MakeEmpty, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = SkIRect::MakeEmpty();
+    SkDebugf("MakeEmpty isEmpty: %s\n", rect.isEmpty() ? "true" : "false");
+    rect.offset(10, 10);
+    SkDebugf("offset rect isEmpty: %s\n", rect.isEmpty() ? "true" : "false");
+    rect.inset(10, 10);
+    SkDebugf("inset rect isEmpty: %s\n", rect.isEmpty() ? "true" : "false");
+    rect.outset(20, 20);
+    SkDebugf("outset rect isEmpty: %s\n", rect.isEmpty() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_MakeLTRB.cpp b/docs/examples/IRect_MakeLTRB.cpp
new file mode 100644
index 0000000..417081f
--- /dev/null
+++ b/docs/examples/IRect_MakeLTRB.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ec1473b700c594f2df9749a12a06b89b
+REG_FIDDLE(IRect_MakeLTRB, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = SkIRect::MakeLTRB(5, 35, 15, 25);
+    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+    rect.sort();
+    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_MakeSize.cpp b/docs/examples/IRect_MakeSize.cpp
new file mode 100644
index 0000000..12b4efd
--- /dev/null
+++ b/docs/examples/IRect_MakeSize.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c6586ff8d24869c780169b0d19c75df6
+REG_FIDDLE(IRect_MakeSize, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkSize size = {25.5f, 35.5f};
+    SkIRect rect = SkIRect::MakeSize(size.toRound());
+    SkDebugf("round width: %d  height: %d\n", rect.width(), rect.height());
+    rect = SkIRect::MakeSize(size.toFloor());
+    SkDebugf("floor width: %d  height: %d\n", rect.width(), rect.height());
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_MakeWH.cpp b/docs/examples/IRect_MakeWH.cpp
new file mode 100644
index 0000000..9e44136
--- /dev/null
+++ b/docs/examples/IRect_MakeWH.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e36827a1a6ae2b1c26e7a8a08f325a07
+REG_FIDDLE(IRect_MakeWH, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect1 = SkIRect::MakeWH(25, 35);
+    SkIRect rect2 = SkIRect::MakeSize({25, 35});
+    SkIRect rect3 = SkIRect::MakeXYWH(0, 0, 25, 35);
+    SkIRect rect4 = SkIRect::MakeLTRB(0, 0, 25, 35);
+    SkDebugf("all %s" "equal\n", rect1 == rect2 && rect2 == rect3 && rect3 == rect4 ?
+             "" : "not ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_MakeXYWH.cpp b/docs/examples/IRect_MakeXYWH.cpp
new file mode 100644
index 0000000..45cf195
--- /dev/null
+++ b/docs/examples/IRect_MakeXYWH.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=598ee14350bd1d961cae6b36fa3df17e
+REG_FIDDLE(IRect_MakeXYWH, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = SkIRect::MakeXYWH(5, 35, -15, 25);
+    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+    rect.sort();
+    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_adjust.cpp b/docs/examples/IRect_adjust.cpp
new file mode 100644
index 0000000..e624c52
--- /dev/null
+++ b/docs/examples/IRect_adjust.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8dc91284493dd012cca3d0ce4c66bda4
+REG_FIDDLE(IRect_adjust, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 8, 11, 19, 22 };
+    rect.adjust(2, -1, 1, -2);
+    SkDebugf("rect: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_bottom.cpp b/docs/examples/IRect_bottom.cpp
new file mode 100644
index 0000000..ef7ed72
--- /dev/null
+++ b/docs/examples/IRect_bottom.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c32afebc296054a181621648a184b8e3
+REG_FIDDLE(IRect_bottom, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect unsorted = { 15, 25, 10, 5 };
+    SkDebugf("unsorted.fBottom: %d unsorted.bottom(): %d\n", unsorted.fBottom, unsorted.bottom());
+    SkIRect sorted = unsorted.makeSorted();
+    SkDebugf("sorted.fBottom: %d sorted.bottom(): %d\n", sorted.fBottom, sorted.bottom());
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_contains.cpp b/docs/examples/IRect_contains.cpp
new file mode 100644
index 0000000..885c8e6
--- /dev/null
+++ b/docs/examples/IRect_contains.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a7958a4e0668f5cf805a8e78eb57f51d
+REG_FIDDLE(IRect_contains, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 30, 50, 40, 60 };
+    SkIPoint pts[] = { { 30, 50}, { 40, 50}, { 30, 60} };
+    for (auto pt : pts) {
+        SkDebugf("rect: (%d, %d, %d, %d) %s (%d, %d)\n",
+                 rect.left(), rect.top(), rect.right(), rect.bottom(),
+                 rect.contains(pt.x(), pt.y()) ? "contains" : "does not contain", pt.x(), pt.y());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_containsNoEmptyCheck.cpp b/docs/examples/IRect_containsNoEmptyCheck.cpp
new file mode 100644
index 0000000..223ad75
--- /dev/null
+++ b/docs/examples/IRect_containsNoEmptyCheck.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=fef2a36bee224e92500199fa9d3cbb8b
+REG_FIDDLE(IRect_containsNoEmptyCheck, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 30, 50, 40, 60 };
+    SkIRect tests[] = { { 30, 50, 31, 51}, { 39, 49, 40, 50}, { 29, 59, 30, 60} };
+    for (auto contained : tests) {
+        bool success = rect.containsNoEmptyCheck(
+                 contained.left(), contained.top(), contained.right(), contained.bottom());
+        SkDebugf("rect: (%d, %d, %d, %d) %s (%d, %d, %d, %d)\n",
+                 rect.left(), rect.top(), rect.right(), rect.bottom(),
+                 success ? "contains" : "does not contain",
+                 contained.left(), contained.top(), contained.right(), contained.bottom());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_containsNoEmptyCheck_2.cpp b/docs/examples/IRect_containsNoEmptyCheck_2.cpp
new file mode 100644
index 0000000..0da1a3a
--- /dev/null
+++ b/docs/examples/IRect_containsNoEmptyCheck_2.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8f91f58001d9c10420eb146fbc169af4
+REG_FIDDLE(IRect_containsNoEmptyCheck_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 30, 50, 40, 60 };
+    SkIRect tests[] = { { 30, 50, 31, 51}, { 39, 49, 40, 50}, { 29, 59, 30, 60} };
+    for (auto contained : tests) {
+        SkDebugf("rect: (%d, %d, %d, %d) %s (%d, %d, %d, %d)\n",
+                 rect.left(), rect.top(), rect.right(), rect.bottom(),
+                 rect.containsNoEmptyCheck(contained) ? "contains" : "does not contain",
+                 contained.left(), contained.top(), contained.right(), contained.bottom());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_contains_2.cpp b/docs/examples/IRect_contains_2.cpp
new file mode 100644
index 0000000..359b994
--- /dev/null
+++ b/docs/examples/IRect_contains_2.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=eae55f284818d9965ec5834747d14a48
+REG_FIDDLE(IRect_contains_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 30, 50, 40, 60 };
+    SkIRect tests[] = { { 30, 50, 31, 51}, { 39, 49, 40, 50}, { 29, 59, 30, 60} };
+    for (auto contained : tests) {
+        bool success = rect.contains(
+                       contained.left(), contained.top(), contained.right(), contained.bottom());
+        SkDebugf("rect: (%d, %d, %d, %d) %s (%d, %d, %d, %d)\n",
+                 rect.left(), rect.top(), rect.right(), rect.bottom(),
+                 success ? "contains" : "does not contain",
+                 contained.left(), contained.top(), contained.right(), contained.bottom());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_contains_3.cpp b/docs/examples/IRect_contains_3.cpp
new file mode 100644
index 0000000..800c435
--- /dev/null
+++ b/docs/examples/IRect_contains_3.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ee0185db622602b4eb19583c2f42c734
+REG_FIDDLE(IRect_contains_3, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 30, 50, 40, 60 };
+    SkIRect tests[] = { { 30, 50, 31, 51}, { 39, 49, 40, 50}, { 29, 59, 30, 60} };
+    for (auto contained : tests) {
+        SkDebugf("rect: (%d, %d, %d, %d) %s (%d, %d, %d, %d)\n",
+                 rect.left(), rect.top(), rect.right(), rect.bottom(),
+                 rect.contains(contained) ? "contains" : "does not contain",
+                 contained.left(), contained.top(), contained.right(), contained.bottom());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_contains_4.cpp b/docs/examples/IRect_contains_4.cpp
new file mode 100644
index 0000000..0a6d3f9
--- /dev/null
+++ b/docs/examples/IRect_contains_4.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=acbd79ffb304f332e4b38ef18e19663e
+REG_FIDDLE(IRect_contains_4, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 30, 50, 40, 60 };
+    SkRect tests[] = { { 30, 50, 31, 51}, { 39, 49, 40, 50}, { 29, 59, 30, 60} };
+    for (auto contained : tests) {
+        SkDebugf("rect: (%d, %d, %d, %d) %s (%g, %g, %g, %g)\n",
+                 rect.left(), rect.top(), rect.right(), rect.bottom(),
+                 rect.contains(contained) ? "contains" : "does not contain",
+                 contained.left(), contained.top(), contained.right(), contained.bottom());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_equal_operator.cpp b/docs/examples/IRect_equal_operator.cpp
new file mode 100644
index 0000000..2e2168e
--- /dev/null
+++ b/docs/examples/IRect_equal_operator.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bd8f028d9051062816c9116fea4237b2
+REG_FIDDLE(IRect_equal_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect test = {0, 0, 2, 2};
+    SkIRect sorted = test.makeSorted();
+    SkDebugf("test %c= sorted\n", test == sorted ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_height.cpp b/docs/examples/IRect_height.cpp
new file mode 100644
index 0000000..bedecb2
--- /dev/null
+++ b/docs/examples/IRect_height.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0175bae87fafcd9433ae661574695586
+REG_FIDDLE(IRect_height, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect unsorted = { 15, 25, 10, 20 };
+    SkDebugf("unsorted height: %d\n", unsorted.height());
+    SkIRect large = { 1, -2147483647, 2, 2147483644 };
+    SkDebugf("large height: %d\n", large.height());
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_height64.cpp b/docs/examples/IRect_height64.cpp
new file mode 100644
index 0000000..eab2d3c
--- /dev/null
+++ b/docs/examples/IRect_height64.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=02dd98716e54bbd8c2f0ff23b7ef98cf
+REG_FIDDLE(IRect_height64, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect large = { 1, -2147483647, 2, 2147483644 };
+    SkDebugf("height: %d height64: %lld\n", large.height(), large.height64());
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_inset.cpp b/docs/examples/IRect_inset.cpp
new file mode 100644
index 0000000..45f2f92
--- /dev/null
+++ b/docs/examples/IRect_inset.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9debaded1aa8bdf5077a4de0b3015b8f
+REG_FIDDLE(IRect_inset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 10, 14, 50, 73 };
+    rect.inset(5, 13);
+    SkDebugf("rect: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_intersect.cpp b/docs/examples/IRect_intersect.cpp
new file mode 100644
index 0000000..ae09f44
--- /dev/null
+++ b/docs/examples/IRect_intersect.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ea233f5d5d1ae0e76fc6f2eb371c927a
+REG_FIDDLE(IRect_intersect, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect leftRect =  { 10, 40, 50, 80 };
+    SkIRect rightRect = { 30, 60, 70, 90 };
+    SkDebugf("%s intersection: ", leftRect.intersect(rightRect) ? "" : "no ");
+    SkDebugf("%d, %d, %d, %d\n", leftRect.left(), leftRect.top(),
+                                 leftRect.right(), leftRect.bottom());
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_intersectNoEmptyCheck.cpp b/docs/examples/IRect_intersectNoEmptyCheck.cpp
new file mode 100644
index 0000000..896c817
--- /dev/null
+++ b/docs/examples/IRect_intersectNoEmptyCheck.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d35fbc9fdea71df8b8a12fd3da50d11c
+REG_FIDDLE(IRect_intersectNoEmptyCheck, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect result;
+    if (result.intersectNoEmptyCheck({ 10, 40, 50, 80 }, { 30, 60, 70, 90 })) {
+        SkDebugf("intersection: %d, %d, %d, %d\n",
+                 result.left(), result.top(), result.right(), result.bottom());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_intersect_2.cpp b/docs/examples/IRect_intersect_2.cpp
new file mode 100644
index 0000000..41506eb
--- /dev/null
+++ b/docs/examples/IRect_intersect_2.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b2db0573aacf99ca52776c5522459d02
+REG_FIDDLE(IRect_intersect_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect result;
+    bool intersected = result.intersect({ 10, 40, 50, 80 }, { 30, 60, 70, 90 });
+    SkDebugf("%s intersection: %d, %d, %d, %d\n", intersected ? "" : "no ",
+             result.left(), result.top(), result.right(), result.bottom());
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_intersect_3.cpp b/docs/examples/IRect_intersect_3.cpp
new file mode 100644
index 0000000..78ac267
--- /dev/null
+++ b/docs/examples/IRect_intersect_3.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=200422990eded2f754ab9893118f2645
+REG_FIDDLE(IRect_intersect_3, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect leftRect =  { 10, 40, 50, 80 };
+    SkDebugf("%s intersection: ", leftRect.intersect(30, 60, 70, 90) ? "" : "no ");
+    SkDebugf("%d, %d, %d, %d\n", leftRect.left(), leftRect.top(),
+                                 leftRect.right(), leftRect.bottom());
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_isEmpty.cpp b/docs/examples/IRect_isEmpty.cpp
new file mode 100644
index 0000000..27dd212
--- /dev/null
+++ b/docs/examples/IRect_isEmpty.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=edaad064b6de249b7a7c768dfa000adc
+REG_FIDDLE(IRect_isEmpty, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect tests[] = {{20, 40, 10, 50}, {20, 40, 20, 50}};
+    for (auto rect : tests) {
+        SkDebugf("rect: {%d, %d, %d, %d} is" "%s empty\n", rect.left(), rect.top(), rect.right(),
+                 rect.bottom(), rect.isEmpty() ? "" : " not");
+        rect.sort();
+        SkDebugf("sorted: {%d, %d, %d, %d} is" "%s empty\n", rect.left(), rect.top(), rect.right(),
+                 rect.bottom(), rect.isEmpty() ? "" : " not");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_isEmpty64.cpp b/docs/examples/IRect_isEmpty64.cpp
new file mode 100644
index 0000000..02e7972
--- /dev/null
+++ b/docs/examples/IRect_isEmpty64.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=eb905faa1084ccab3ad0605df4c27ea4
+REG_FIDDLE(IRect_isEmpty64, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect tests[] = {{20, 40, 10, 50}, {20, 40, 20, 50}};
+    for (auto rect : tests) {
+        SkDebugf("rect: {%d, %d, %d, %d} is" "%s empty\n", rect.left(), rect.top(), rect.right(),
+                rect.bottom(), rect.isEmpty64() ? "" : " not");
+        rect.sort();
+        SkDebugf("sorted: {%d, %d, %d, %d} is" "%s empty\n", rect.left(), rect.top(), rect.right(),
+                rect.bottom(), rect.isEmpty64() ? "" : " not");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_join.cpp b/docs/examples/IRect_join.cpp
new file mode 100644
index 0000000..60e8a3f
--- /dev/null
+++ b/docs/examples/IRect_join.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c00ef06289d21db70340e465690e0e08
+REG_FIDDLE(IRect_join, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 10, 20, 15, 25};
+    rect.join(50, 60, 55, 65);
+    SkDebugf("join: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_join_2.cpp b/docs/examples/IRect_join_2.cpp
new file mode 100644
index 0000000..052b541
--- /dev/null
+++ b/docs/examples/IRect_join_2.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=75fd81c1d3512e63890d085593018876
+REG_FIDDLE(IRect_join_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 10, 20, 15, 25};
+    rect.join({50, 60, 55, 65});
+    SkDebugf("join: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_left.cpp b/docs/examples/IRect_left.cpp
new file mode 100644
index 0000000..325ba21
--- /dev/null
+++ b/docs/examples/IRect_left.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=caf38ea4431bc246ba198f6a8c2b0f01
+REG_FIDDLE(IRect_left, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect unsorted = { 15, 5, 10, 25 };
+    SkDebugf("unsorted.fLeft: %d unsorted.left(): %d\n", unsorted.fLeft, unsorted.left());
+    SkIRect sorted = unsorted.makeSorted();
+    SkDebugf("sorted.fLeft: %d sorted.left(): %d\n", sorted.fLeft, sorted.left());
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_makeInset.cpp b/docs/examples/IRect_makeInset.cpp
new file mode 100644
index 0000000..f15b0db
--- /dev/null
+++ b/docs/examples/IRect_makeInset.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1db94b2c76e0a7a71856532335fa56b6
+REG_FIDDLE(IRect_makeInset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 10, 50, 20, 60 };
+    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+    rect = rect.makeInset(15, 32);
+    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_makeOffset.cpp b/docs/examples/IRect_makeOffset.cpp
new file mode 100644
index 0000000..5d1bd3e
--- /dev/null
+++ b/docs/examples/IRect_makeOffset.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=737c747df07ddf392c05970440de0927
+REG_FIDDLE(IRect_makeOffset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 10, 50, 20, 60 };
+    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+    rect = rect.makeOffset(15, 32);
+    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_makeOutset.cpp b/docs/examples/IRect_makeOutset.cpp
new file mode 100644
index 0000000..c585202
--- /dev/null
+++ b/docs/examples/IRect_makeOutset.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=240e2953e3455c08f6d89255feff8416
+REG_FIDDLE(IRect_makeOutset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 10, 50, 20, 60 };
+    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+    rect = rect.makeOutset(15, 32);
+    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_makeSorted.cpp b/docs/examples/IRect_makeSorted.cpp
new file mode 100644
index 0000000..c8cc096
--- /dev/null
+++ b/docs/examples/IRect_makeSorted.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=de89926c374aa16427916900b89a3441
+REG_FIDDLE(IRect_makeSorted, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 30, 50, 20, 10 };
+    SkDebugf("rect: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+    SkIRect sort = rect.makeSorted();
+    SkDebugf("sorted: %d, %d, %d, %d\n", sort.fLeft, sort.fTop, sort.fRight, sort.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_notequal_operator.cpp b/docs/examples/IRect_notequal_operator.cpp
new file mode 100644
index 0000000..25b6bea
--- /dev/null
+++ b/docs/examples/IRect_notequal_operator.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6c4acd8aa203f632b7d85cae672abf4d
+REG_FIDDLE(IRect_notequal_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect test = {2, 2, 0, 0};
+    SkIRect sorted = test.makeSorted();
+    SkDebugf("test %c= sorted\n", test != sorted ? '!' : '=');
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_offset.cpp b/docs/examples/IRect_offset.cpp
new file mode 100644
index 0000000..d261351
--- /dev/null
+++ b/docs/examples/IRect_offset.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=77e633b2174ffae923c038b303418b50
+REG_FIDDLE(IRect_offset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 10, 14, 50, 73 };
+    rect.offset(5, 13);
+    SkDebugf("rect: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_offsetTo.cpp b/docs/examples/IRect_offsetTo.cpp
new file mode 100644
index 0000000..22ec0bf
--- /dev/null
+++ b/docs/examples/IRect_offsetTo.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a2734ff23b35653956a3002e5c29ff91
+REG_FIDDLE(IRect_offsetTo, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 10, 14, 50, 73 };
+    rect.offsetTo(15, 27);
+    SkDebugf("rect: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_offset_2.cpp b/docs/examples/IRect_offset_2.cpp
new file mode 100644
index 0000000..0742206
--- /dev/null
+++ b/docs/examples/IRect_offset_2.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=31a4c575499e76def651eb65994876f0
+REG_FIDDLE(IRect_offset_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 10, 14, 50, 73 };
+    rect.offset({5, 13});
+    SkDebugf("rect: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_outset.cpp b/docs/examples/IRect_outset.cpp
new file mode 100644
index 0000000..de08251
--- /dev/null
+++ b/docs/examples/IRect_outset.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3fc62ca29428195f33a3a02b3eb74e4f
+REG_FIDDLE(IRect_outset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 10, 14, 50, 73 };
+    rect.outset(5, 13);
+    SkDebugf("rect: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_right.cpp b/docs/examples/IRect_right.cpp
new file mode 100644
index 0000000..36fa738
--- /dev/null
+++ b/docs/examples/IRect_right.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=97e210976f1ee0387b30c70635cf114f
+REG_FIDDLE(IRect_right, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect unsorted = { 15, 25, 10, 5 };
+    SkDebugf("unsorted.fRight: %d unsorted.right(): %d\n", unsorted.fRight, unsorted.right());
+    SkIRect sorted = unsorted.makeSorted();
+    SkDebugf("sorted.fRight: %d sorted.right(): %d\n", sorted.fRight, sorted.right());
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_set.cpp b/docs/examples/IRect_set.cpp
new file mode 100644
index 0000000..ba9b815
--- /dev/null
+++ b/docs/examples/IRect_set.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1912c37076b7f3bf6aebfa167e971bec
+REG_FIDDLE(IRect_set, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect1 = {3, 4, 1, 2};
+    SkDebugf("rect1: {%d, %d, %d, %d}\n", rect1.fLeft, rect1.fTop, rect1.fRight, rect1.fBottom);
+    SkIRect rect2;
+    rect2.set(3, 4, 1, 2);
+    SkDebugf("rect2: {%d, %d, %d, %d}\n", rect2.fLeft, rect2.fTop, rect2.fRight, rect2.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_setEmpty.cpp b/docs/examples/IRect_setEmpty.cpp
new file mode 100644
index 0000000..ddbafdd
--- /dev/null
+++ b/docs/examples/IRect_setEmpty.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=94039c3cc9e911c8ab2993d56fd06210
+REG_FIDDLE(IRect_setEmpty, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = {3, 4, 1, 2};
+    for (int i = 0; i < 2; ++i) {
+    SkDebugf("rect: {%d, %d, %d, %d} is %s" "empty\n", rect.fLeft, rect.fTop,
+             rect.fRight, rect.fBottom, rect.isEmpty() ? "" : "not ");
+    rect.setEmpty();
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_setLTRB.cpp b/docs/examples/IRect_setLTRB.cpp
new file mode 100644
index 0000000..c55bec5
--- /dev/null
+++ b/docs/examples/IRect_setLTRB.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ead6bdcf2ae77ec19a1c5a96f5b31af8
+REG_FIDDLE(IRect_setLTRB, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect1 = {3, 4, 1, 2};
+    SkDebugf("rect1: {%d, %d, %d, %d}\n", rect1.fLeft, rect1.fTop, rect1.fRight, rect1.fBottom);
+    SkIRect rect2;
+    rect2.setLTRB(3, 4, 1, 2);
+    SkDebugf("rect2: {%d, %d, %d, %d}\n", rect2.fLeft, rect2.fTop, rect2.fRight, rect2.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_setXYWH.cpp b/docs/examples/IRect_setXYWH.cpp
new file mode 100644
index 0000000..ccd6ceb
--- /dev/null
+++ b/docs/examples/IRect_setXYWH.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0e1db8c86678c004e504f47641b44b17
+REG_FIDDLE(IRect_setXYWH, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect;
+    rect.setXYWH(5, 35, -15, 25);
+    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+    rect.sort();
+    SkDebugf("rect: %d, %d, %d, %d  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_size.cpp b/docs/examples/IRect_size.cpp
new file mode 100644
index 0000000..3954609
--- /dev/null
+++ b/docs/examples/IRect_size.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8b3224641cb3053a7b8a5798b6cd1cf6
+REG_FIDDLE(IRect_size, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkIRect& rect) -> void {
+        SkISize size = rect.size();
+        SkDebugf("%s ", prefix);
+        SkDebugf("rect: %d, %d, %d, %d  ", rect.left(), rect.top(), rect.right(), rect.bottom());
+        SkDebugf("size: %d, %d\n", size.width(), size.height());
+    };
+    SkIRect rect = {20, 30, 40, 50};
+    debugster("original", rect);
+    rect.offset(20, 20);
+    debugster("  offset", rect);
+    rect.outset(20, 20);
+    debugster("  outset", rect);
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_sort.cpp b/docs/examples/IRect_sort.cpp
new file mode 100644
index 0000000..6c5c515
--- /dev/null
+++ b/docs/examples/IRect_sort.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=fa12547fcfd4c1aef3db1a1f6aae0fe4
+REG_FIDDLE(IRect_sort, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rect = { 30, 50, 20, 10 };
+    SkDebugf("rect: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+    rect.sort();
+    SkDebugf("sorted: %d, %d, %d, %d\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_top.cpp b/docs/examples/IRect_top.cpp
new file mode 100644
index 0000000..fe75a08
--- /dev/null
+++ b/docs/examples/IRect_top.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=cbec1ae6530e95943775450b1d11f19e
+REG_FIDDLE(IRect_top, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect unsorted = { 15, 25, 10, 5 };
+    SkDebugf("unsorted.fTop: %d unsorted.top(): %d\n", unsorted.fTop, unsorted.top());
+    SkIRect sorted = unsorted.makeSorted();
+    SkDebugf("sorted.fTop: %d sorted.top(): %d\n", sorted.fTop, sorted.top());
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_width.cpp b/docs/examples/IRect_width.cpp
new file mode 100644
index 0000000..820e5cb
--- /dev/null
+++ b/docs/examples/IRect_width.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4acfbe051805940210c8916a94794142
+REG_FIDDLE(IRect_width, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect unsorted = { 15, 25, 10, 5 };
+    SkDebugf("unsorted width: %d\n", unsorted.width());
+    SkIRect large = { -2147483647, 1, 2147483644, 2 };
+    SkDebugf("large width: %d\n", large.width());
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_width64.cpp b/docs/examples/IRect_width64.cpp
new file mode 100644
index 0000000..c027438a
--- /dev/null
+++ b/docs/examples/IRect_width64.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=63977f97999bbd6eecfdcc7575d75492
+REG_FIDDLE(IRect_width64, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect large = { -2147483647, 1, 2147483644, 2 };
+    SkDebugf("width: %d width64: %lld\n", large.width(), large.width64());
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_x.cpp b/docs/examples/IRect_x.cpp
new file mode 100644
index 0000000..106842c
--- /dev/null
+++ b/docs/examples/IRect_x.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2a59cbfd1330a0db520d6ebb2b7c68c7
+REG_FIDDLE(IRect_x, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect unsorted = { 15, 5, 10, 25 };
+    SkDebugf("unsorted.fLeft: %d unsorted.x(): %d\n", unsorted.fLeft, unsorted.x());
+    SkIRect sorted = unsorted.makeSorted();
+    SkDebugf("sorted.fLeft: %d sorted.x(): %d\n", sorted.fLeft, sorted.x());
+}
+}  // END FIDDLE
diff --git a/docs/examples/IRect_y.cpp b/docs/examples/IRect_y.cpp
new file mode 100644
index 0000000..710623a2
--- /dev/null
+++ b/docs/examples/IRect_y.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6ea461e71f7fc80605818fbf493caa63
+REG_FIDDLE(IRect_y, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect unsorted = { 15, 25, 10, 5 };
+    SkDebugf("unsorted.fTop: %d unsorted.y(): %d\n", unsorted.fTop, unsorted.y());
+    SkIRect sorted = unsorted.makeSorted();
+    SkDebugf("sorted.fTop: %d sorted.y(): %d\n", sorted.fTop, sorted.y());
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_ByteSizeOverflowed.cpp b/docs/examples/ImageInfo_ByteSizeOverflowed.cpp
new file mode 100644
index 0000000..d993c71
--- /dev/null
+++ b/docs/examples/ImageInfo_ByteSizeOverflowed.cpp
@@ -0,0 +1,18 @@
+#if 0  // disabled
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6a63dfdd62ab77ff57783af8c33d7b78
+REG_FIDDLE(ImageInfo_ByteSizeOverflowed, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    if (sizeof(size_t) == 8) {
+        SkImageInfo info = SkImageInfo::MakeN32Premul(2, 1000000000);
+        for (size_t rowBytes = 100000000; rowBytes < 10000000000000LL; rowBytes *= 10) {
+            const size_t size = info.computeByteSize(rowBytes);
+            SkDebugf("rowBytes:%llu size:%llu overflowed:%s\n", rowBytes, size,
+                     SkImageInfo::ByteSizeOverflowed(size) ? "true" : "false");
+        }
+    }
+}
+}  // END FIDDLE
+#endif  // disabled
diff --git a/docs/examples/ImageInfo_Make.cpp b/docs/examples/ImageInfo_Make.cpp
new file mode 100644
index 0000000..4fe45a6
--- /dev/null
+++ b/docs/examples/ImageInfo_Make.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9f47f9c2a99473f5b1113db48096d586
+REG_FIDDLE(ImageInfo_Make, 256, 48, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint8_t storage[][5] = {{ 0xCA, 0xDA, 0xCA, 0xC9, 0xA3 },
+                            { 0xAC, 0xA8, 0x89, 0xA7, 0x87 },
+                            { 0x9B, 0xB5, 0xE5, 0x95, 0x46 },
+                            { 0x90, 0x81, 0xC5, 0x71, 0x33 },
+                            { 0x75, 0x55, 0x44, 0x40, 0x30 }};
+    SkImageInfo imageInfo = SkImageInfo::Make(5, 5, kGray_8_SkColorType, kOpaque_SkAlphaType);
+    SkPixmap pixmap(imageInfo, storage[0], sizeof(storage) / 5);
+    SkBitmap bitmap;
+    bitmap.installPixels(pixmap);
+    canvas->scale(8, 8);
+    canvas->drawBitmap(bitmap, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_MakeA8.cpp b/docs/examples/ImageInfo_MakeA8.cpp
new file mode 100644
index 0000000..2dd462b
--- /dev/null
+++ b/docs/examples/ImageInfo_MakeA8.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=547388991687b8e10d482d8b1c82777d
+REG_FIDDLE(ImageInfo_MakeA8, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint8_t pixels[][8] = { { 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00},
+                            { 0x00, 0x7f, 0xff, 0x3f, 0x3f, 0x7f, 0x3f, 0x00},
+                            { 0x3f, 0xff, 0x7f, 0x00, 0x7f, 0xff, 0x7f, 0x00},
+                            { 0x00, 0x3f, 0x00, 0x00, 0x3f, 0x7f, 0x3f, 0x00},
+                            { 0x3f, 0x7f, 0x7f, 0x3f, 0x00, 0x00, 0x00, 0x00},
+                            { 0x7f, 0xff, 0xff, 0x7f, 0x00, 0x3f, 0x7f, 0x3f},
+                            { 0x7f, 0xff, 0xff, 0x7f, 0x00, 0x7f, 0xff, 0x7f},
+                            { 0x3f, 0x7f, 0x7f, 0x3f, 0x00, 0x3f, 0x7f, 0x3f} };
+    SkBitmap bitmap;
+    bitmap.installPixels(SkImageInfo::MakeA8(8, 8),
+            (void*) pixels, sizeof(pixels[0]));
+    SkPaint paint;
+    canvas->scale(4, 4);
+    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xFF007F00} ) {
+        paint.setColor(color);
+        canvas->drawBitmap(bitmap, 0, 0, &paint);
+        canvas->translate(12, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_MakeN32.cpp b/docs/examples/ImageInfo_MakeN32.cpp
new file mode 100644
index 0000000..c7c7647
--- /dev/null
+++ b/docs/examples/ImageInfo_MakeN32.cpp
@@ -0,0 +1,18 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=78cea0c4cac205b61ad6f6c982cbd888
+REG_FIDDLE(ImageInfo_MakeN32, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32(16, 16, kPremul_SkAlphaType));
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(SK_ColorWHITE);
+    SkPaint paint;
+    offscreen.drawString("g", 0, 10, paint);
+    canvas->scale(8, 8);
+    canvas->drawBitmap(bitmap, 0, 0);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/ImageInfo_MakeN32Premul.cpp b/docs/examples/ImageInfo_MakeN32Premul.cpp
new file mode 100644
index 0000000..12bae3e
--- /dev/null
+++ b/docs/examples/ImageInfo_MakeN32Premul.cpp
@@ -0,0 +1,20 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=525650a67e19fdd8ca9f72b7eda65174
+REG_FIDDLE(ImageInfo_MakeN32Premul, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(18, 18));
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(SK_ColorWHITE);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(15);
+    offscreen.drawString("\xF0\x9F\x98\xB8", 1, 15, paint);
+    canvas->scale(6, 6);
+    canvas->drawBitmap(bitmap, 0, 0);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/ImageInfo_MakeN32Premul_2.cpp b/docs/examples/ImageInfo_MakeN32Premul_2.cpp
new file mode 100644
index 0000000..369aee2
--- /dev/null
+++ b/docs/examples/ImageInfo_MakeN32Premul_2.cpp
@@ -0,0 +1,20 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b9026d7f39029756bd7cab9542c64f4e
+REG_FIDDLE(ImageInfo_MakeN32Premul_2, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul({18, 18}));
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(SK_ColorWHITE);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(15);
+    offscreen.drawString("\xF0\x9F\x98\xB9", 1, 15, paint);
+    canvas->scale(6, 6);
+    canvas->drawBitmap(bitmap, 0, 0);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/ImageInfo_MakeS32.cpp b/docs/examples/ImageInfo_MakeS32.cpp
new file mode 100644
index 0000000..1033014
--- /dev/null
+++ b/docs/examples/ImageInfo_MakeS32.cpp
@@ -0,0 +1,29 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=de418ccb42471d1589508ef3955f8c53
+REG_FIDDLE(ImageInfo_MakeS32, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    const int width = 256;
+    const int height = 32;
+    SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
+    SkColor  gradColors[] = { 0xFFAA0055, 0xFF11CC88 };
+    SkPoint  gradPoints[] = { { 0, 0 }, { width, 0 } };
+    SkPaint gradPaint;
+    gradPaint.setShader(SkGradientShader::MakeLinear(gradPoints, gradColors, nullptr,
+                    SK_ARRAY_COUNT(gradColors), SkTileMode::kClamp));
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType));
+    SkCanvas offScreen(bitmap);
+    offScreen.drawRect(SkRect::MakeWH(width, height), gradPaint);
+    canvas->drawBitmap(bitmap, 0, 0);
+    bitmap.allocPixels(SkImageInfo::MakeS32(width, height, kPremul_SkAlphaType));
+    SkCanvas sRGBOffscreen(bitmap);
+    sRGBOffscreen.drawRect(SkRect::MakeWH(width, height), gradPaint);
+    canvas->drawBitmap(bitmap, 0, 48);
+    SkBitmap noColorSpaceBitmap;
+    noColorSpaceBitmap.setInfo(SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType));
+    noColorSpaceBitmap.setPixels(bitmap.getAddr(0, 0));
+    canvas->drawBitmap(noColorSpaceBitmap, 0, 96);
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_MakeUnknown.cpp b/docs/examples/ImageInfo_MakeUnknown.cpp
new file mode 100644
index 0000000..6db4bff
--- /dev/null
+++ b/docs/examples/ImageInfo_MakeUnknown.cpp
@@ -0,0 +1,16 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=75f13a78b28b08c72baf32b7d868de1c
+REG_FIDDLE(ImageInfo_MakeUnknown, 384, 32, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo info;  // default constructor
+    SkString string;
+    string.printf("SkImageInfo() %c= SkImageInfo::MakeUnknown(0, 0)",
+                  info == SkImageInfo::MakeUnknown(0, 0) ? '=' : '!');
+    SkPaint paint;
+    canvas->drawString(string, 0, 12, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/ImageInfo_MakeUnknown_2.cpp b/docs/examples/ImageInfo_MakeUnknown_2.cpp
new file mode 100644
index 0000000..df6c4ff
--- /dev/null
+++ b/docs/examples/ImageInfo_MakeUnknown_2.cpp
@@ -0,0 +1,16 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a1af7696ae0cdd6f379546dd1f211b7a
+REG_FIDDLE(ImageInfo_MakeUnknown_2, 384, 32, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo info;  // default constructor
+    SkString string;
+    string.printf("SkImageInfo() %c= SkImageInfo::MakeUnknown()",
+                  info == SkImageInfo::MakeUnknown() ? '=' : '!');
+    SkPaint paint;
+    canvas->drawString(string, 0, 12, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/ImageInfo_alphaType.cpp b/docs/examples/ImageInfo_alphaType.cpp
new file mode 100644
index 0000000..f7c140f
--- /dev/null
+++ b/docs/examples/ImageInfo_alphaType.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5c1d2499a4056b6cff38c1cf924158a1
+REG_FIDDLE(ImageInfo_alphaType, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const char* alphas[] = {"Unknown", "Opaque", "Premul", "Unpremul"};
+    SkImageInfo info = SkImageInfo::MakeA8(16, 32);
+    SkDebugf("alpha type: k" "%s" "_SkAlphaType\n", alphas[info.alphaType()]);
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_bounds.cpp b/docs/examples/ImageInfo_bounds.cpp
new file mode 100644
index 0000000..2e6a382
--- /dev/null
+++ b/docs/examples/ImageInfo_bounds.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a818be8945cd0c18f99ffe53e90afa48
+REG_FIDDLE(ImageInfo_bounds, 256, 64, false, 4) {
+void draw(SkCanvas* canvas) {
+    canvas->scale(.5f, .5f);
+    SkImageInfo imageInfo = source.info();
+    SkIRect bounds = imageInfo.bounds();
+    for (int x : { 0, bounds.width() } ) {
+        for (int y : { 0, bounds.height() } ) {
+            canvas->drawBitmap(source, x, y);
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_bytesPerPixel.cpp b/docs/examples/ImageInfo_bytesPerPixel.cpp
new file mode 100644
index 0000000..f22eef5
--- /dev/null
+++ b/docs/examples/ImageInfo_bytesPerPixel.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9b6de4a07b2316228e9340e5a3b82134
+REG_FIDDLE(ImageInfo_bytesPerPixel, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
+                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16Norm",
+                            "RGBA_F16"};
+    for (SkColorType colorType : {
+    kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType,
+    kARGB_4444_SkColorType, kRGBA_8888_SkColorType, kRGB_888x_SkColorType,
+    kBGRA_8888_SkColorType, kRGBA_1010102_SkColorType, kRGB_101010x_SkColorType,
+    kGray_8_SkColorType, kRGBA_F16_SkColorType
+                                 } ) {
+        SkImageInfo info = SkImageInfo::Make(1, 1, colorType, kOpaque_SkAlphaType);
+        SkDebugf("color: k" "%s" "_SkColorType" "%*s" "bytesPerPixel: %d\n",
+                colors[colorType], 13 - strlen(colors[colorType]), " ",
+                info.bytesPerPixel());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_colorSpace.cpp b/docs/examples/ImageInfo_colorSpace.cpp
new file mode 100644
index 0000000..bcb1e8b
--- /dev/null
+++ b/docs/examples/ImageInfo_colorSpace.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5602b816d7cf75e3851274ef36a4c10f
+REG_FIDDLE(ImageInfo_colorSpace, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo info = SkImageInfo::MakeN32(16, 32, kPremul_SkAlphaType,
+            SkColorSpace::MakeSRGBLinear());
+    SkColorSpace* colorSpace = info.colorSpace();
+    SkDebugf("gammaCloseToSRGB: %s  gammaIsLinear: %s  isSRGB: %s\n",
+            colorSpace->gammaCloseToSRGB() ? "true" : "false",
+            colorSpace->gammaIsLinear() ? "true" : "false",
+            colorSpace->isSRGB() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_colorType.cpp b/docs/examples/ImageInfo_colorType.cpp
new file mode 100644
index 0000000..753e089
--- /dev/null
+++ b/docs/examples/ImageInfo_colorType.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=06ecc3ce7f35cc7f930cbc2a662e3105
+REG_FIDDLE(ImageInfo_colorType, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
+                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16Norm",
+                            "RGBA_F16"};
+    SkImageInfo info = SkImageInfo::MakeA8(16, 32);
+    SkDebugf("color type: k" "%s" "_SkColorType\n", colors[info.colorType()]);
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_computeByteSize.cpp b/docs/examples/ImageInfo_computeByteSize.cpp
new file mode 100644
index 0000000..3f9a9d4
--- /dev/null
+++ b/docs/examples/ImageInfo_computeByteSize.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9def507d2295f7051effd0c83bb04436
+REG_FIDDLE(ImageInfo_computeByteSize, 256, 130, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo info = SkImageInfo::MakeN32Premul(2, 2);
+    const size_t size = info.computeByteSize(100000);
+    SkAutoTMalloc<SkPMColor> storage(size);
+    SkPMColor* pixels = storage.get();
+    SkBitmap bitmap;
+    bitmap.setInfo(info);
+    bitmap.setPixels(pixels);
+    bitmap.eraseColor(SK_ColorRED);
+    canvas->scale(50, 50);
+    canvas->rotate(8);
+    canvas->drawBitmap(bitmap, 2, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_computeMinByteSize.cpp b/docs/examples/ImageInfo_computeMinByteSize.cpp
new file mode 100644
index 0000000..b5a0931
--- /dev/null
+++ b/docs/examples/ImageInfo_computeMinByteSize.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=fc18640fdde437cb35338aed7c68d399
+REG_FIDDLE(ImageInfo_computeMinByteSize, 256, 130, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo info = SkImageInfo::MakeN32Premul(2, 2);
+    const size_t size = info.computeMinByteSize();
+    SkAutoTMalloc<SkPMColor> storage(size);
+    SkPMColor* pixels = storage.get();
+    SkBitmap bitmap;
+    bitmap.setInfo(info);
+    bitmap.setPixels(pixels);
+    bitmap.eraseColor(SK_ColorRED);
+    canvas->scale(50, 50);
+    canvas->rotate(8);
+    canvas->drawBitmap(bitmap, 2, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_computeOffset.cpp b/docs/examples/ImageInfo_computeOffset.cpp
new file mode 100644
index 0000000..918c350
--- /dev/null
+++ b/docs/examples/ImageInfo_computeOffset.cpp
@@ -0,0 +1,28 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=818e4e1191e39d2a642902cbf253b399
+REG_FIDDLE(ImageInfo_computeOffset, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint8_t pixels[][12] = { { 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00},
+                             { 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00},
+                             { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00},
+                             { 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF},
+                             { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+                             { 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00},
+                             { 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00},
+                             { 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00} };
+    SkImageInfo imageInfo = SkImageInfo::MakeA8(8, 8);
+    SkBitmap bitmap;
+    bitmap.installPixels(imageInfo, (void*) pixels, sizeof(pixels[0]));
+    SkPaint paint;
+    paint.setColor(SK_ColorRED);
+    canvas->drawBitmapRect(bitmap, SkRect::MakeWH(8, 8), SkRect::MakeWH(32, 32), &paint);
+    size_t offset = imageInfo.computeOffset(2, 3, sizeof(pixels[0]));
+    pixels[0][offset] = 0x7F;
+    offset = imageInfo.computeOffset(5, 3, sizeof(pixels[0]));
+    pixels[0][offset] = 0x7F;
+    bitmap.installPixels(imageInfo, (void*) pixels, sizeof(pixels[0]));
+    canvas->drawBitmapRect(bitmap, SkRect::MakeWH(8, 8), SkRect::MakeWH(128, 128), &paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_dimensions.cpp b/docs/examples/ImageInfo_dimensions.cpp
new file mode 100644
index 0000000..d9cf3eb
--- /dev/null
+++ b/docs/examples/ImageInfo_dimensions.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d5547cd2b302822aa85b7b0ae3f48458
+REG_FIDDLE(ImageInfo_dimensions, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int height = 2;
+    const int width = 2;
+    SkImageInfo imageInfo = SkImageInfo::Make(width, height, kN32_SkColorType, kPremul_SkAlphaType);
+    SkISize dimensions = imageInfo.dimensions();
+    SkIRect bounds = imageInfo.bounds();
+    SkIRect dimensionsAsBounds = SkIRect::MakeSize(dimensions);
+    SkDebugf("dimensionsAsBounds %c= bounds\n", dimensionsAsBounds == bounds ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_empty_constructor.cpp b/docs/examples/ImageInfo_empty_constructor.cpp
new file mode 100644
index 0000000..afdcb4f
--- /dev/null
+++ b/docs/examples/ImageInfo_empty_constructor.cpp
@@ -0,0 +1,19 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f206f698e7a8db3d84334c26b1a702dc
+REG_FIDDLE(ImageInfo_empty_constructor, 256, 32, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo imageInfo;
+    size_t rowBytes;
+    SkIPoint origin;
+    (void) canvas->accessTopLayerPixels(&imageInfo, &rowBytes, &origin);
+    const char* alphaType[] = { "Unknown", "Opaque", "Premul", "Unpremul" };
+    SkString string;
+    string.printf("k%s_SkAlphaType", alphaType[(int) imageInfo.alphaType()]);
+    SkPaint paint;
+    canvas->drawString(string, 20, 20, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/ImageInfo_equal1_operator.cpp b/docs/examples/ImageInfo_equal1_operator.cpp
new file mode 100644
index 0000000..f796732
--- /dev/null
+++ b/docs/examples/ImageInfo_equal1_operator.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=53c212c4f2449df0b0eedbc6227b6ab7
+REG_FIDDLE(ImageInfo_equal1_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo info1 = SkImageInfo::Make(10, 20, kGray_8_SkColorType, kPremul_SkAlphaType);
+    SkImageInfo info2 = SkImageInfo::Make(20, 10, kAlpha_8_SkColorType, kUnpremul_SkAlphaType);
+    SkDebugf("info1 %c= info2\n", info1 == info2 ? '=' : '!');
+    info2 = info2.makeWH(10, 20);
+    SkDebugf("info1 %c= info2\n", info1 == info2 ? '=' : '!');
+    info2 = info2.makeColorType(kGray_8_SkColorType);
+    SkDebugf("info1 %c= info2\n", info1 == info2 ? '=' : '!');
+    info2 = info2.makeAlphaType(kPremul_SkAlphaType);
+    SkDebugf("info1 %c= info2\n", info1 == info2 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_gammaCloseToSRGB.cpp b/docs/examples/ImageInfo_gammaCloseToSRGB.cpp
new file mode 100644
index 0000000..831f254
--- /dev/null
+++ b/docs/examples/ImageInfo_gammaCloseToSRGB.cpp
@@ -0,0 +1,34 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=22df72732e898a11773fbfe07388a546
+REG_FIDDLE(ImageInfo_gammaCloseToSRGB, 256, 144, false, 0) {
+void draw(SkCanvas* canvas) {
+    const int width = 256;
+    const int height = 64;
+    auto drawLabel = [=](const char* what, bool closeToSRGB) -> void {
+        SkString string;
+        string.printf("%s gamma is %s" "close to sRGB", what, closeToSRGB ? "" : "not ");
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        canvas->drawString(string, 20, 56, paint);
+    };
+    SkColor  gradColors[] = { 0xFFFF7F00, 0xFF00FF7F,  0xFF0000FF, 0xFF7F7FFF };
+    SkPoint  gradPoints[] = { { 0, 0 }, { width, 0 }, { width * 2, 0 }, { width * 3, 0 } };
+    SkPaint gradPaint;
+    gradPaint.setShader(SkGradientShader::MakeLinear(gradPoints, gradColors, nullptr,
+                    SK_ARRAY_COUNT(gradColors), SkShader::kClamp_TileMode));
+    canvas->drawRect(SkRect::MakeWH(width, height), gradPaint);
+    drawLabel("canvas", canvas->imageInfo().gammaCloseToSRGB());
+    SkBitmap bitmap;
+    SkImageInfo offscreenInfo = SkImageInfo::MakeS32(width, height, kPremul_SkAlphaType);
+    bitmap.allocPixels(offscreenInfo);
+    SkCanvas sRGBOffscreen(bitmap);
+    sRGBOffscreen.drawRect(SkRect::MakeWH(width, height), gradPaint);
+    canvas->translate(0, 80);
+    canvas->drawBitmap(bitmap, 0, 0);
+    drawLabel("offscreen", offscreenInfo.gammaCloseToSRGB());
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/ImageInfo_height.cpp b/docs/examples/ImageInfo_height.cpp
new file mode 100644
index 0000000..1b28ef7
--- /dev/null
+++ b/docs/examples/ImageInfo_height.cpp
@@ -0,0 +1,16 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=72c35baaeddca1d912edf93d19429c8e
+REG_FIDDLE(ImageInfo_height, 256, 96, false, 4) {
+void draw(SkCanvas* canvas) {
+    canvas->translate(10, 20);
+    canvas->drawBitmap(source, 0, 0);
+    SkImageInfo imageInfo = source.info();
+    SkPaint paint;
+    canvas->drawLine(imageInfo.width() + 10, 0, imageInfo.width() + 10, imageInfo.height(), paint);
+    canvas->drawString("height", imageInfo.width() + 15, imageInfo.height() / 2, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/ImageInfo_isEmpty.cpp b/docs/examples/ImageInfo_isEmpty.cpp
new file mode 100644
index 0000000..f4180ab
--- /dev/null
+++ b/docs/examples/ImageInfo_isEmpty.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b8757200da5be0b43763cf79feb681a7
+REG_FIDDLE(ImageInfo_isEmpty, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    for (int width : { 0, 2 } ) {
+        for (int height : { 0, 2 } ) {
+             SkImageInfo imageInfo= SkImageInfo::MakeA8(width, height);
+             SkDebugf("width: %d height: %d empty: %s\n", width, height,
+                      imageInfo.isEmpty() ? "true" : "false");
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_isOpaque.cpp b/docs/examples/ImageInfo_isOpaque.cpp
new file mode 100644
index 0000000..fb66406
--- /dev/null
+++ b/docs/examples/ImageInfo_isOpaque.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e9bd4f02b6cfb3ac864cb7fee7d7299c
+REG_FIDDLE(ImageInfo_isOpaque, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int height = 2;
+    const int width = 2;
+    SkBitmap bitmap;
+    SkImageInfo imageInfo = SkImageInfo::Make(width, height, kN32_SkColorType, kPremul_SkAlphaType);
+    bitmap.setInfo(imageInfo);
+    for (int index = 0; index < 2; ++index) {
+        bitmap.allocPixels();
+        bitmap.eraseColor(0x00000000);
+        SkDebugf("isOpaque: %s\n", imageInfo.isOpaque() ? "true" : "false");
+        bitmap.eraseColor(0xFFFFFFFF);
+        SkDebugf("isOpaque: %s\n", imageInfo.isOpaque() ? "true" : "false");
+        imageInfo = imageInfo.makeAlphaType(kOpaque_SkAlphaType);
+        bitmap.setInfo(imageInfo);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_makeAlphaType.cpp b/docs/examples/ImageInfo_makeAlphaType.cpp
new file mode 100644
index 0000000..7dc4f87
--- /dev/null
+++ b/docs/examples/ImageInfo_makeAlphaType.cpp
@@ -0,0 +1,29 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e72db006f1bea26feceaef8727ff9818
+REG_FIDDLE(ImageInfo_makeAlphaType, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    const int width = 256;
+    const int height = 128;
+    SkColor pixels[height][width];
+    for (int y = 0; y < height; ++y) {
+        for (int x = 0; x < width; ++x) {
+            int red = SkScalarRoundToInt(255 * SkScalarAbs(SkScalarSin((x * 4 + y) * 0.03f)));
+            int blue = SkScalarRoundToInt(255 * SkScalarAbs(SkScalarCos((x * 3 + y) * 0.04f)));
+            int green = SkScalarRoundToInt(255 * SkScalarAbs(SkScalarSin((x * 2 + y) * 0.05f)));
+            int alpha = SkScalarRoundToInt(255 * SkScalarAbs(SkScalarCos((x * 1 + y) * 0.006f)));
+            pixels[y][x] =
+                SkColorSetARGB(alpha, red * alpha / 255, green * alpha / 255, blue * alpha / 255);
+        }
+    }
+    SkBitmap bitmap;
+    SkImageInfo info = SkImageInfo::Make(width, height, kBGRA_8888_SkColorType, kPremul_SkAlphaType);
+    bitmap.installPixels(info, (void*) pixels, sizeof(SkColor) * width);
+    canvas->drawBitmap(source, 0, 0);
+    canvas->drawBitmap(bitmap, 0, 0);
+    SkImageInfo unpremulInfo = info.makeAlphaType(kUnpremul_SkAlphaType);
+    bitmap.installPixels(unpremulInfo, (void*) pixels, sizeof(SkColor) * width);
+    canvas->drawBitmap(bitmap, 0, 128);
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_makeColorSpace.cpp b/docs/examples/ImageInfo_makeColorSpace.cpp
new file mode 100644
index 0000000..ad4b1e0
--- /dev/null
+++ b/docs/examples/ImageInfo_makeColorSpace.cpp
@@ -0,0 +1,42 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=fe3c5a755d3dde29bba058a583f18901
+REG_FIDDLE(ImageInfo_makeColorSpace, 256, 224, false, 0) {
+void draw(SkCanvas* canvas) {
+    const int width = 256;
+    const int height = 64;
+    auto drawLabel = [=](const char* what, bool closeToSRGB) -> void {
+        SkString string;
+        string.printf("%s gamma is %s" "close to sRGB", what, closeToSRGB ? "" : "not ");
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        canvas->drawString(string, 20, 56, paint);
+    };
+    SkColor  gradColors[] = { 0xFFFF7F00, 0xFF00FF7F,  0xFF0000FF, 0xFF7F7FFF };
+    SkPoint  gradPoints[] = { { 0, 0 }, { width, 0 }, { width * 2, 0 }, { width * 3, 0 } };
+    SkPaint gradPaint;
+    gradPaint.setShader(SkGradientShader::MakeLinear(gradPoints, gradColors, nullptr,
+            SK_ARRAY_COUNT(gradColors), SkShader::kClamp_TileMode));
+    canvas->drawRect(SkRect::MakeWH(width, height), gradPaint);
+    drawLabel("canvas", canvas->imageInfo().gammaCloseToSRGB());
+    SkBitmap bitmap;
+    SkImageInfo offscreenInfo = SkImageInfo::MakeS32(width, height, kPremul_SkAlphaType);
+    bitmap.allocPixels(offscreenInfo);
+    SkCanvas sRGBOffscreen(bitmap);
+    sRGBOffscreen.drawRect(SkRect::MakeWH(width, height), gradPaint);
+    canvas->translate(0, 80);
+    canvas->drawBitmap(bitmap, 0, 0);
+    drawLabel("offscreen", offscreenInfo.gammaCloseToSRGB());
+    SkImageInfo linearGamma =
+            offscreenInfo.makeColorSpace(offscreenInfo.colorSpace()->makeLinearGamma());
+    bitmap.allocPixels(linearGamma);
+    SkCanvas lgOffscreen(bitmap);
+    lgOffscreen.drawRect(SkRect::MakeWH(width, height), gradPaint);
+    canvas->translate(0, 80);
+    canvas->drawBitmap(bitmap, 0, 0);
+    drawLabel("linear", linearGamma.gammaCloseToSRGB());
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/ImageInfo_makeColorType.cpp b/docs/examples/ImageInfo_makeColorType.cpp
new file mode 100644
index 0000000..a93dd18
--- /dev/null
+++ b/docs/examples/ImageInfo_makeColorType.cpp
@@ -0,0 +1,29 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3ac267b08b12dc83c95f91d8dd5d70ee
+REG_FIDDLE(ImageInfo_makeColorType, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    const int width = 256;
+    const int height = 128;
+    SkColor pixels[height][width];
+    for (int y = 0; y < height; ++y) {
+        for (int x = 0; x < width; ++x) {
+            int red = SkScalarRoundToInt(255 * SkScalarAbs(SkScalarSin((x * 4 + y) * 0.03f)));
+            int blue = SkScalarRoundToInt(255 * SkScalarAbs(SkScalarCos((x * 3 + y) * 0.04f)));
+            int green = SkScalarRoundToInt(255 * SkScalarAbs(SkScalarSin((x * 2 + y) * 0.05f)));
+            int alpha = SkScalarRoundToInt(255 * SkScalarAbs(SkScalarCos((x * 1 + y) * 0.006f)));
+            pixels[y][x] =
+                SkColorSetARGB(alpha, red * alpha / 255, green * alpha / 255, blue * alpha / 255);
+        }
+    }
+    SkBitmap bitmap;
+    SkImageInfo info = SkImageInfo::Make(width, height, kBGRA_8888_SkColorType, kPremul_SkAlphaType);
+    bitmap.installPixels(info, (void*) pixels, sizeof(SkColor) * width);
+    canvas->drawBitmap(source, 0, 0);
+    canvas->drawBitmap(bitmap, 0, 0);
+    SkImageInfo rgbaInfo = info.makeColorType(kRGBA_8888_SkColorType);
+    bitmap.installPixels(rgbaInfo, (void*) pixels, sizeof(SkColor) * width);
+    canvas->drawBitmap(bitmap, 0, 128);
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_makeWH.cpp b/docs/examples/ImageInfo_makeWH.cpp
new file mode 100644
index 0000000..68ab049
--- /dev/null
+++ b/docs/examples/ImageInfo_makeWH.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=cd203a3f9c5fb68272f21f302dd54fbc
+REG_FIDDLE(ImageInfo_makeWH, 256, 144, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo canvasImageInfo = canvas->imageInfo();
+    SkRect canvasBounds = SkRect::Make(canvasImageInfo.bounds());
+    canvas->drawBitmapRect(source, source.bounds(), canvasBounds, nullptr);
+    SkImageInfo insetImageInfo =
+              canvasImageInfo.makeWH(canvasBounds.width() / 2, canvasBounds.height() / 2);
+    SkBitmap inset;
+    inset.allocPixels(insetImageInfo);
+    SkCanvas offscreen(inset);
+    offscreen.drawBitmapRect(source, source.bounds(), SkRect::Make(inset.bounds()), nullptr);
+    canvas->drawBitmap(inset, canvasBounds.width() / 4, canvasBounds.height() / 4);
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_minRowBytes.cpp b/docs/examples/ImageInfo_minRowBytes.cpp
new file mode 100644
index 0000000..6f6839c
--- /dev/null
+++ b/docs/examples/ImageInfo_minRowBytes.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=897230ecfb36095486beca324fd369f9
+REG_FIDDLE(ImageInfo_minRowBytes, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    for (int shift = 24; shift < 31; ++shift) {
+        int width = 1 << shift;
+        SkImageInfo imageInfo =
+                SkImageInfo::Make(width, 1, kRGBA_F16_SkColorType, kPremul_SkAlphaType);
+        size_t minRowBytes = imageInfo.minRowBytes();
+        bool widthTooLarge = !minRowBytes;
+        SkDebugf("RGBA_F16 width %d (0x%08x) %s\n",
+                width, width, widthTooLarge ? "too large" : "OK");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_minRowBytes64.cpp b/docs/examples/ImageInfo_minRowBytes64.cpp
new file mode 100644
index 0000000..81769ab
--- /dev/null
+++ b/docs/examples/ImageInfo_minRowBytes64.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4b5d3904476726a39f1c3e276d6b6ba7
+REG_FIDDLE(ImageInfo_minRowBytes64, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    for (int shift = 24; shift < 31; ++shift) {
+        int width = 1 << shift;
+        SkImageInfo imageInfo =
+                SkImageInfo::Make(width, 1, kRGBA_F16_SkColorType, kPremul_SkAlphaType);
+        uint64_t minRowBytes = imageInfo.minRowBytes64();
+        bool widthTooLarge = (uint64_t) (int32_t) minRowBytes != minRowBytes;
+        SkDebugf("RGBA_F16 width %d (0x%08x) %s\n",
+                width, width, widthTooLarge ? "too large" : "OK");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_notequal1_operator.cpp b/docs/examples/ImageInfo_notequal1_operator.cpp
new file mode 100644
index 0000000..2a09e30
--- /dev/null
+++ b/docs/examples/ImageInfo_notequal1_operator.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8c039fde0a476ac1aa62bf9de5d61c77
+REG_FIDDLE(ImageInfo_notequal1_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo info1 = SkImageInfo::Make(10, 20, kGray_8_SkColorType, kPremul_SkAlphaType);
+    SkImageInfo info2 = SkImageInfo::Make(20, 10, kAlpha_8_SkColorType, kUnpremul_SkAlphaType);
+    SkDebugf("info1 %c= info2\n", info1 != info2 ? '!' : '=');
+    info2 = info2.makeWH(10, 20);
+    SkDebugf("info1 %c= info2\n", info1 != info2 ? '!' : '=');
+    info2 = info2.makeColorType(kGray_8_SkColorType);
+    SkDebugf("info1 %c= info2\n", info1 != info2 ? '!' : '=');
+    info2 = info2.makeAlphaType(kPremul_SkAlphaType);
+    SkDebugf("info1 %c= info2\n", info1 != info2 ? '!' : '=');
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_refColorSpace.cpp b/docs/examples/ImageInfo_refColorSpace.cpp
new file mode 100644
index 0000000..c575e11
--- /dev/null
+++ b/docs/examples/ImageInfo_refColorSpace.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=33f65524736736fd91802b4198ba6fa8
+REG_FIDDLE(ImageInfo_refColorSpace, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo info1 = SkImageInfo::MakeN32(16, 32, kPremul_SkAlphaType,
+            SkColorSpace::MakeSRGBLinear());
+    SkImageInfo info2 = SkImageInfo::MakeN32(16, 32, kPremul_SkAlphaType,
+            info1.refColorSpace());
+    SkColorSpace* colorSpace = info2.colorSpace();
+    SkDebugf("gammaCloseToSRGB: %s  gammaIsLinear: %s  isSRGB: %s\n",
+            colorSpace->gammaCloseToSRGB() ? "true" : "false",
+            colorSpace->gammaIsLinear() ? "true" : "false",
+            colorSpace->isSRGB() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_reset.cpp b/docs/examples/ImageInfo_reset.cpp
new file mode 100644
index 0000000..d0bbc66
--- /dev/null
+++ b/docs/examples/ImageInfo_reset.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ab7e73786805c936de386b6c1ebe1f13
+REG_FIDDLE(ImageInfo_reset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo info = SkImageInfo::MakeN32Premul(16, 8);
+    SkImageInfo copy = info;
+    SkDebugf("info %c= copy\n", info == copy ? '=' : '!');
+    copy.reset();
+    SkDebugf("info %c= reset copy\n", info == copy ? '=' : '!');
+    SkDebugf("SkImageInfo() %c= reset copy\n", SkImageInfo() == copy ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_shiftPerPixel.cpp b/docs/examples/ImageInfo_shiftPerPixel.cpp
new file mode 100644
index 0000000..74cf3e3
--- /dev/null
+++ b/docs/examples/ImageInfo_shiftPerPixel.cpp
@@ -0,0 +1,37 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e47b911f94fc629f756a829e523a2a89
+REG_FIDDLE(ImageInfo_shiftPerPixel, 256, 256, true, 0) {
+const char* color_type(SkColorType ct) {
+    switch (ct) {
+        case kUnknown_SkColorType:      return "Unknown";
+        case kAlpha_8_SkColorType:      return "Alpha_8";
+        case kRGB_565_SkColorType:      return "RGB_565";
+        case kARGB_4444_SkColorType:    return "ARGB_4444";
+        case kRGBA_8888_SkColorType:    return "RGBA_8888";
+        case kRGB_888x_SkColorType:     return "RGB_888x";
+        case kBGRA_8888_SkColorType:    return "BGRA_8888";
+        case kRGBA_1010102_SkColorType: return "RGBA_1010102";
+        case kRGB_101010x_SkColorType:  return "RGB_101010x";
+        case kGray_8_SkColorType:       return "Gray_8";
+        case kRGBA_F16Norm_SkColorType: return "RGBA_F16Norm";
+        case kRGBA_F16_SkColorType:     return "RGBA_F16";
+        case kRGBA_F32_SkColorType:     return "RGBA_F32";
+        default: SkASSERT(false); return nullptr;
+    }
+}
+void draw(SkCanvas* canvas) {
+    for (SkColorType colorType : {
+    kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType,
+    kARGB_4444_SkColorType, kRGBA_8888_SkColorType, kRGB_888x_SkColorType,
+    kBGRA_8888_SkColorType, kRGBA_1010102_SkColorType, kRGB_101010x_SkColorType,
+    kGray_8_SkColorType, kRGBA_F16_SkColorType
+                                 } ) {
+        SkImageInfo info = SkImageInfo::Make(1, 1, colorType, kOpaque_SkAlphaType);
+        SkDebugf("color: k" "%s" "_SkColorType" "%*s" "shiftPerPixel: %d\n",
+                color_type(colorType), 14 - strlen(color_type(colorType)), " ",
+                info.shiftPerPixel());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_validRowBytes.cpp b/docs/examples/ImageInfo_validRowBytes.cpp
new file mode 100644
index 0000000..e145e56
--- /dev/null
+++ b/docs/examples/ImageInfo_validRowBytes.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c6b0f6a3f493cb08d9abcdefe12de245
+REG_FIDDLE(ImageInfo_validRowBytes, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo info = SkImageInfo::MakeN32Premul(16, 8);
+    for (size_t rowBytes = 60; rowBytes < 72; rowBytes += sizeof(SkPMColor)) {
+        SkDebugf("validRowBytes(%llu): %s\n", rowBytes, info.validRowBytes(rowBytes) ?
+                 "true" : "false");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/ImageInfo_width.cpp b/docs/examples/ImageInfo_width.cpp
new file mode 100644
index 0000000..4f8fe39
--- /dev/null
+++ b/docs/examples/ImageInfo_width.cpp
@@ -0,0 +1,17 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e2491817695290d0218be77f091b8460
+REG_FIDDLE(ImageInfo_width, 256, 96, false, 4) {
+void draw(SkCanvas* canvas) {
+    canvas->translate(10, 10);
+    canvas->drawBitmap(source, 0, 0);
+    SkImageInfo imageInfo = source.info();
+    canvas->translate(0, imageInfo.height());
+    SkPaint paint;
+    canvas->drawLine(0, 10, imageInfo.width(), 10, paint);
+    canvas->drawString("width", imageInfo.width() / 2 - 15, 25, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Image_Filter_Methods.cpp b/docs/examples/Image_Filter_Methods.cpp
new file mode 100644
index 0000000..ec17e4b
--- /dev/null
+++ b/docs/examples/Image_Filter_Methods.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ece04ee3d3761e3425f37c8f06f054c1
+REG_FIDDLE(Image_Filter_Methods, 256, 256, false, 0) {
+#include "SkBlurImageFilter.h"
+
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(2);
+    SkRegion region;
+    region.op( 10, 10, 50, 50, SkRegion::kUnion_Op);
+    region.op( 10, 50, 90, 90, SkRegion::kUnion_Op);
+    paint.setImageFilter(SkBlurImageFilter::Make(5.0f, 5.0f, nullptr));
+    canvas->drawRegion(region, paint);
+    paint.setImageFilter(nullptr);
+    paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 5));
+    canvas->translate(100, 100);
+    canvas->drawRegion(region, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_MakeBackendTextureFromSkImage.cpp b/docs/examples/Image_MakeBackendTextureFromSkImage.cpp
new file mode 100644
index 0000000..b40ae5e
--- /dev/null
+++ b/docs/examples/Image_MakeBackendTextureFromSkImage.cpp
@@ -0,0 +1,34 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=06aeb3cf63ffccf7b49fe556e5def351
+REG_FIDDLE(Image_MakeBackendTextureFromSkImage, 256, 64, false, 0) {
+static sk_sp<SkImage> create_gpu_image(GrContext* grContext) {
+    const SkImageInfo info = SkImageInfo::MakeN32(20, 20, kOpaque_SkAlphaType);
+    auto surface(SkSurface::MakeRenderTarget(grContext, SkBudgeted::kNo, info));
+    SkCanvas* canvas = surface->getCanvas();
+    canvas->clear(SK_ColorWHITE);
+    SkPaint paint;
+    paint.setColor(SK_ColorBLACK);
+    canvas->drawRect(SkRect::MakeXYWH(5, 5, 10, 10), paint);
+    return surface->makeImageSnapshot();
+}
+
+void draw(SkCanvas* canvas) {
+    GrContext* grContext = canvas->getGrContext();
+    if (!grContext) {
+        return;
+    }
+    sk_sp<SkImage> backEndImage = create_gpu_image(grContext);
+    canvas->drawImage(backEndImage, 0, 0);
+    GrBackendTexture texture;
+    SkImage::BackendTextureReleaseProc proc;
+    if (!SkImage::MakeBackendTextureFromSkImage(grContext, std::move(backEndImage),
+            &texture, &proc)) {
+        return;
+    }
+    sk_sp<SkImage> i2 = SkImage::MakeFromTexture(grContext, texture, kTopLeft_GrSurfaceOrigin,
+            kN32_SkColorType, kOpaque_SkAlphaType, nullptr);
+    canvas->drawImage(i2, 30, 30);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_MakeCrossContextFromEncoded.cpp b/docs/examples/Image_MakeCrossContextFromEncoded.cpp
new file mode 100644
index 0000000..bed9eaf
--- /dev/null
+++ b/docs/examples/Image_MakeCrossContextFromEncoded.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=069c7b116479e3ca46f953f07dcbdd36
+REG_FIDDLE(Image_MakeCrossContextFromEncoded, 256, 64, false, 4) {
+void draw(SkCanvas* canvas) {
+    GrContext* context = canvas->getGrContext();
+    sk_sp<SkData> encodedData = image->encodeToData(SkEncodedImageFormat::kJPEG, 100);
+    sk_sp<SkImage> image = SkImage::MakeCrossContextFromEncoded(context,
+                                                                encodedData, false, nullptr);
+    canvas->drawImage(image, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_MakeCrossContextFromPixmap.cpp b/docs/examples/Image_MakeCrossContextFromPixmap.cpp
new file mode 100644
index 0000000..edbecdd
--- /dev/null
+++ b/docs/examples/Image_MakeCrossContextFromPixmap.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=45bca8747b8f49b5be34b520897ef048
+REG_FIDDLE(Image_MakeCrossContextFromPixmap, 256, 64, false, 4) {
+void draw(SkCanvas* canvas) {
+    GrContext* context = canvas->getGrContext();
+    SkPixmap pixmap;
+    if (source.peekPixels(&pixmap)) {
+        sk_sp<SkImage> image = SkImage::MakeCrossContextFromPixmap(context, pixmap,
+                                                                   false, nullptr);
+        canvas->drawImage(image, 0, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_MakeFromAdoptedTexture.cpp b/docs/examples/Image_MakeFromAdoptedTexture.cpp
new file mode 100644
index 0000000..6c6a5c0
--- /dev/null
+++ b/docs/examples/Image_MakeFromAdoptedTexture.cpp
@@ -0,0 +1,25 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b034517e39394b7543f06ec885e36d7d
+REG_FIDDLE(Image_MakeFromAdoptedTexture, 256, 256, false, 5) {
+void draw(SkCanvas* canvas) {
+    if (!canvas->getGrContext()) {
+        return;
+    }
+    canvas->scale(.5f, .5f);
+    canvas->clear(0x7f3f5f7f);
+    int x = 0, y = 0;
+    for (auto origin : { kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin } ) {
+        for (auto alpha : { kOpaque_SkAlphaType, kPremul_SkAlphaType, kUnpremul_SkAlphaType } ) {
+            sk_sp<SkImage> image = SkImage::MakeFromAdoptedTexture(canvas->getGrContext(),
+                                                                   backEndTexture, origin,
+                                                                   kRGBA_8888_SkColorType, alpha);
+            canvas->drawImage(image, x, y);
+            x += 160;
+        }
+        x -= 160 * 3;
+        y += 256;
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_MakeFromBitmap.cpp b/docs/examples/Image_MakeFromBitmap.cpp
new file mode 100644
index 0000000..83d9d61
--- /dev/null
+++ b/docs/examples/Image_MakeFromBitmap.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=cf2cf53321e4e6a77c2841bfbc0ef707
+REG_FIDDLE(Image_MakeFromBitmap, 256, 50, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint8_t storage[][5] = {{ 0xCA, 0xDA, 0xCA, 0xC9, 0xA3 },
+                            { 0xAC, 0xA8, 0x89, 0xA7, 0x87 },
+                            { 0x9B, 0xB5, 0xE5, 0x95, 0x46 },
+                            { 0x90, 0x81, 0xC5, 0x71, 0x33 },
+                            { 0x75, 0x55, 0x44, 0x40, 0x30 }};
+    SkImageInfo imageInfo = SkImageInfo::Make(5, 5, kGray_8_SkColorType, kOpaque_SkAlphaType);
+    SkPixmap pixmap(imageInfo, storage[0], sizeof(storage) / 5);
+    SkBitmap bitmap;
+    bitmap.installPixels(pixmap);
+    sk_sp<SkImage> image1 = SkImage::MakeFromBitmap(bitmap);
+    bitmap.setImmutable();
+    sk_sp<SkImage> image2 = SkImage::MakeFromBitmap(bitmap);
+    *pixmap.writable_addr8(2, 2) = 0x00;
+    canvas->scale(10, 10);
+    canvas->drawImage(image1, 0, 0);
+    canvas->drawImage(image2, 10, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_MakeFromEncoded.cpp b/docs/examples/Image_MakeFromEncoded.cpp
new file mode 100644
index 0000000..7c8f0ca
--- /dev/null
+++ b/docs/examples/Image_MakeFromEncoded.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=894f732ed6409b1f392bc5481421d0e9
+REG_FIDDLE(Image_MakeFromEncoded, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    int x = 0;
+    for (int quality : { 100, 50, 10, 1} ) {
+        sk_sp<SkData> encodedData = image->encodeToData(SkEncodedImageFormat::kJPEG, quality);
+        sk_sp<SkImage> image = SkImage::MakeFromEncoded(encodedData);
+        canvas->drawImage(image, x, 0);
+        x += 64;
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_MakeFromGenerator.cpp b/docs/examples/Image_MakeFromGenerator.cpp
new file mode 100644
index 0000000..f8908ea
--- /dev/null
+++ b/docs/examples/Image_MakeFromGenerator.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c2fec0746f88ca34d7dce59dd9bdef9e
+REG_FIDDLE(Image_MakeFromGenerator, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPictureRecorder recorder;
+    recorder.beginRecording(100, 100)->drawColor(SK_ColorRED);
+    auto picture = recorder.finishRecordingAsPicture();
+    auto gen = SkImageGenerator::MakeFromPicture({100, 100}, picture, nullptr, nullptr,
+                                                 SkImage::BitDepth::kU8, SkColorSpace::MakeSRGB());
+    sk_sp<SkImage> image = SkImage::MakeFromGenerator(std::move(gen));
+    canvas->drawImage(image, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_MakeFromPicture.cpp b/docs/examples/Image_MakeFromPicture.cpp
new file mode 100644
index 0000000..c39cfc5
--- /dev/null
+++ b/docs/examples/Image_MakeFromPicture.cpp
@@ -0,0 +1,27 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4aa2879b9e44dfd6648995326d2c4dcf
+REG_FIDDLE(Image_MakeFromPicture, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkPictureRecorder recorder;
+    SkCanvas* recordingCanvas = recorder.beginRecording(50, 50);
+    for (auto color : { SK_ColorRED, SK_ColorBLUE, 0xff007f00 } ) {
+        paint.setColor(color);
+        recordingCanvas->drawRect({10, 10, 30, 40}, paint);
+        recordingCanvas->translate(10, 10);
+        recordingCanvas->scale(1.2f, 1.4f);
+    }
+    sk_sp<SkPicture> playback = recorder.finishRecordingAsPicture();
+    int x = 0, y = 0;
+    for (auto alpha : { 70, 140, 210 } ) {
+        paint.setAlpha(alpha);
+        auto srgbColorSpace = SkColorSpace::MakeSRGB();
+        sk_sp<SkImage> image = SkImage::MakeFromPicture(playback, {50, 50}, nullptr, &paint,
+                                                        SkImage::BitDepth::kU8, srgbColorSpace);
+        canvas->drawImage(image, x, y);
+        x += 70; y += 70;
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_MakeFromRaster.cpp b/docs/examples/Image_MakeFromRaster.cpp
new file mode 100644
index 0000000..23e905f
--- /dev/null
+++ b/docs/examples/Image_MakeFromRaster.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=275356b65d18c8868f4434137350cddc
+REG_FIDDLE(Image_MakeFromRaster, 256, 256, true, 0) {
+static void releaseProc(const void* pixels, SkImage::ReleaseContext context) {
+     int* countPtr = static_cast<int*>(context);
+     *countPtr += 1;
+}
+
+void draw(SkCanvas* canvas) {
+    SkColor color = 0;
+    SkPixmap pixmap(SkImageInfo::MakeN32(1, 1, kPremul_SkAlphaType), &color, 4);
+    int releaseCount = 0;
+    sk_sp<SkImage> image(SkImage::MakeFromRaster(pixmap, releaseProc, &releaseCount));
+    SkDebugf("before reset: %d\n", releaseCount);
+    image.reset();
+    SkDebugf("after reset: %d\n", releaseCount);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_MakeFromTexture.cpp b/docs/examples/Image_MakeFromTexture.cpp
new file mode 100644
index 0000000..064736e
--- /dev/null
+++ b/docs/examples/Image_MakeFromTexture.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=94e9296c53bad074bf2a48ff885dac13
+REG_FIDDLE(Image_MakeFromTexture, 256, 128, false, 3) {
+void draw(SkCanvas* canvas) {
+    GrContext* context = canvas->getGrContext();
+    if (!context) {
+       return;
+    }
+    canvas->scale(.25f, .25f);
+    int x = 0;
+    for (auto origin : { kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin } ) {
+        sk_sp<SkImage> image = SkImage::MakeFromTexture(context, backEndTexture,
+               origin, kRGBA_8888_SkColorType, kOpaque_SkAlphaType, nullptr);
+        canvas->drawImage(image, x, 0);
+    x += 512;
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_MakeFromTexture_2.cpp b/docs/examples/Image_MakeFromTexture_2.cpp
new file mode 100644
index 0000000..8cf3324
--- /dev/null
+++ b/docs/examples/Image_MakeFromTexture_2.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2b1e46354d823dbb53fa6af570135329
+REG_FIDDLE(Image_MakeFromTexture_2, 256, 256, false, 4) {
+void draw(SkCanvas* canvas) {
+    GrContext* context = canvas->getGrContext();
+    if (!context) {
+       return;
+    }
+    auto debugster = [](SkImage::ReleaseContext releaseContext) -> void {
+       *((int *) releaseContext) += 128;
+    };
+    int x = 0, y = 0;
+    for (auto origin : { kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin } ) {
+        sk_sp<SkImage> image = SkImage::MakeFromTexture(context, backEndTexture,
+               origin, kRGBA_8888_SkColorType, kOpaque_SkAlphaType, nullptr, debugster, &x);
+        canvas->drawImage(image, x, y);
+        y += 128;
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_MakeRasterCopy.cpp b/docs/examples/Image_MakeRasterCopy.cpp
new file mode 100644
index 0000000..2a1c005
--- /dev/null
+++ b/docs/examples/Image_MakeRasterCopy.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=513afec5795a9504ebf6af5373d16b6b
+REG_FIDDLE(Image_MakeRasterCopy, 256, 50, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint8_t storage[][5] = {{ 0xCA, 0xDA, 0xCA, 0xC9, 0xA3 },
+                            { 0xAC, 0xA8, 0x89, 0xA7, 0x87 },
+                            { 0x9B, 0xB5, 0xE5, 0x95, 0x46 },
+                            { 0x90, 0x81, 0xC5, 0x71, 0x33 },
+                            { 0x75, 0x55, 0x44, 0x40, 0x30 }};
+    SkImageInfo imageInfo = SkImageInfo::Make(5, 5, kGray_8_SkColorType, kOpaque_SkAlphaType);
+    SkPixmap pixmap(imageInfo, storage[0], sizeof(storage) / 5);
+    SkBitmap bitmap;
+    bitmap.installPixels(pixmap);
+    sk_sp<SkImage> image = SkImage::MakeRasterCopy(pixmap);
+    *pixmap.writable_addr8(2, 2) = 0x00;
+    canvas->scale(10, 10);
+    canvas->drawBitmap(bitmap, 0, 0);
+    canvas->drawImage(image, 10, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_MakeRasterData.cpp b/docs/examples/Image_MakeRasterData.cpp
new file mode 100644
index 0000000..286df83
--- /dev/null
+++ b/docs/examples/Image_MakeRasterData.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=22e7ce79ab2fe94252d23319f2258127
+REG_FIDDLE(Image_MakeRasterData, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    size_t rowBytes = image->width() * SkColorTypeBytesPerPixel(kRGBA_8888_SkColorType);
+    sk_sp<SkData> data = SkData::MakeUninitialized(rowBytes * image->height());
+    SkImageInfo dstInfo = SkImageInfo::MakeN32(image->width(), image->height(),
+                                               kPremul_SkAlphaType);
+    image->readPixels(dstInfo, data->writable_data(), rowBytes, 0, 0, SkImage::kAllow_CachingHint);
+    sk_sp<SkImage> raw = SkImage::MakeRasterData(dstInfo.makeColorType(kRGBA_8888_SkColorType),
+                                                 data, rowBytes);
+    canvas->drawImage(image, 0, 0);
+    canvas->drawImage(raw.get(), 128, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_alphaType.cpp b/docs/examples/Image_alphaType.cpp
new file mode 100644
index 0000000..f662615
--- /dev/null
+++ b/docs/examples/Image_alphaType.cpp
@@ -0,0 +1,14 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1b9f1f05026ceb14ccb6926a13cdaa83
+REG_FIDDLE(Image_alphaType, 256, 96, false, 4) {
+void draw(SkCanvas* canvas) {
+    const char* alphaTypeStr[] = { "Unknown", "Opaque", "Premul", "Unpremul" };
+    SkAlphaType alphaType = image->alphaType();
+    canvas->drawImage(image, 16, 0);
+    canvas->drawString(alphaTypeStr[(int) alphaType], 20, image->height() + 20, SkPaint());
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Image_bounds.cpp b/docs/examples/Image_bounds.cpp
new file mode 100644
index 0000000..ddd72c2
--- /dev/null
+++ b/docs/examples/Image_bounds.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c204b38b3fc08914b0a634aa4eaec894
+REG_FIDDLE(Image_bounds, 256, 128, false, 4) {
+void draw(SkCanvas* canvas) {
+    SkIRect bounds = image->bounds();
+    for (int x : { 0, bounds.width() } ) {
+        for (int y : { 0, bounds.height() } ) {
+            canvas->drawImage(image, x, y);
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_colorSpace.cpp b/docs/examples/Image_colorSpace.cpp
new file mode 100644
index 0000000..4d40af0
--- /dev/null
+++ b/docs/examples/Image_colorSpace.cpp
@@ -0,0 +1,27 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4468d573f42af6f5e234be10a5453bb2
+REG_FIDDLE(Image_colorSpace, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkPixmap pixmap;
+    source.peekPixels(&pixmap);
+    canvas->scale(.25f, .25f);
+    int y = 0;
+    for (auto gamma : { SkColorSpace::kLinear_RenderTargetGamma,
+                        SkColorSpace::kSRGB_RenderTargetGamma } ) {
+        int x = 0;
+        sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeRGB(gamma, SkColorSpace::kSRGB_Gamut);
+        for (int index = 0; index < 2; ++index) {
+            pixmap.setColorSpace(colorSpace);
+            sk_sp<SkImage> image = SkImage::MakeRasterCopy(pixmap);
+            canvas->drawImage(image, x, y);
+            colorSpace = image->colorSpace()->makeColorSpin();
+            x += 512;
+        }
+        y += 512;
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Image_colorType.cpp b/docs/examples/Image_colorType.cpp
new file mode 100644
index 0000000..395d749
--- /dev/null
+++ b/docs/examples/Image_colorType.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=50396fad4a128f58e400ca00fe09711f
+REG_FIDDLE(Image_colorType, 256, 96, false, 4) {
+void draw(SkCanvas* canvas) {
+    const char* colors[] = { "Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
+                             "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16Norm",
+                             "RGBA_F16" };
+    SkColorType colorType = image->colorType();
+    canvas->drawImage(image, 16, 0);
+    canvas->drawString(colors[(int) colorType], 20, image->height() + 20, SkFont(), SkPaint());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_dimensions.cpp b/docs/examples/Image_dimensions.cpp
new file mode 100644
index 0000000..06d29ed
--- /dev/null
+++ b/docs/examples/Image_dimensions.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=96b4bc43b3667df9ba9e2dafb770d33c
+REG_FIDDLE(Image_dimensions, 256, 256, true, 4) {
+void draw(SkCanvas* canvas) {
+    SkISize dimensions = image->dimensions();
+    SkIRect bounds = image->bounds();
+    SkIRect dimensionsAsBounds = SkIRect::MakeSize(dimensions);
+    SkDebugf("dimensionsAsBounds %c= bounds\n", dimensionsAsBounds == bounds ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_encodeToData.cpp b/docs/examples/Image_encodeToData.cpp
new file mode 100644
index 0000000..e4992f1
--- /dev/null
+++ b/docs/examples/Image_encodeToData.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7a3bf8851bb7160e4e49c48f8c09639d
+REG_FIDDLE(Image_encodeToData, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->scale(4, 4);
+    SkIRect subset = {0, 0, 16, 64};
+    int x = 0;
+    for (int quality : { 0, 10, 50, 100 } ) {
+        sk_sp<SkData> data(image->encodeToData(SkEncodedImageFormat::kJPEG, quality));
+        sk_sp<SkImage> filtered = SkImage::MakeFromEncoded(data, &subset);
+        canvas->drawImage(filtered, x, 0);
+        x += 16;
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_encodeToData_2.cpp b/docs/examples/Image_encodeToData_2.cpp
new file mode 100644
index 0000000..e97babc
--- /dev/null
+++ b/docs/examples/Image_encodeToData_2.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=30cee813f6aa476b0a9c8a24283e53a3
+REG_FIDDLE(Image_encodeToData_2, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->scale(4, 4);
+    SkIRect subset = {136, 32, 200, 96};
+    sk_sp<SkData> data(image->encodeToData());
+    sk_sp<SkImage> eye = SkImage::MakeFromEncoded(data, &subset);
+    canvas->drawImage(eye, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_getBackendTexture.cpp b/docs/examples/Image_getBackendTexture.cpp
new file mode 100644
index 0000000..35cf7c2
--- /dev/null
+++ b/docs/examples/Image_getBackendTexture.cpp
@@ -0,0 +1,25 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d093aad721261f421c4bef4a296aab48
+REG_FIDDLE(Image_getBackendTexture, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    GrContext* grContext = canvas->getGrContext();
+    if (!grContext) {
+        canvas->drawString("GPU only!", 20, 40, SkPaint());
+        return;
+    }
+    sk_sp<SkImage> imageFromBackend = SkImage::MakeFromAdoptedTexture(grContext, backEndTexture,
+            kBottomLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, kOpaque_SkAlphaType);
+    GrBackendTexture textureFromImage = imageFromBackend->getBackendTexture(false);
+    if (!textureFromImage.isValid()) {
+        return;
+    }
+    sk_sp<SkImage> imageFromTexture = SkImage::MakeFromAdoptedTexture(grContext, textureFromImage,
+            kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, kOpaque_SkAlphaType);
+    canvas->drawImage(imageFromTexture, 0, 0);
+    canvas->drawImage(imageFromBackend, 128, 128);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Image_height.cpp b/docs/examples/Image_height.cpp
new file mode 100644
index 0000000..5564045
--- /dev/null
+++ b/docs/examples/Image_height.cpp
@@ -0,0 +1,16 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a4f53a0b6ac85e7bc3887245b728530d
+REG_FIDDLE(Image_height, 256, 96, false, 4) {
+void draw(SkCanvas* canvas) {
+    canvas->translate(10, 10);
+    canvas->drawImage(image, 0, 0);
+    canvas->translate(image->width(), 0);
+    SkPaint paint;
+    canvas->drawLine(10, 0, 10, image->height(), paint);
+    canvas->drawString("height", 34, image->height() / 2, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Image_isAlphaOnly.cpp b/docs/examples/Image_isAlphaOnly.cpp
new file mode 100644
index 0000000..3f69c62
--- /dev/null
+++ b/docs/examples/Image_isAlphaOnly.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=50762c73b8ea91959c5a7b68fbf1062d
+REG_FIDDLE(Image_isAlphaOnly, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    uint8_t pmColors = 0;
+    sk_sp<SkImage> image = SkImage::MakeRasterCopy({SkImageInfo::MakeA8(1, 1), &pmColors, 1});
+    SkDebugf("alphaOnly = %s\n", image->isAlphaOnly() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_isLazyGenerated_a.cpp b/docs/examples/Image_isLazyGenerated_a.cpp
new file mode 100644
index 0000000..bb70305
--- /dev/null
+++ b/docs/examples/Image_isLazyGenerated_a.cpp
@@ -0,0 +1,35 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a8b8bd4bfe968e2c63085f867665227f
+REG_FIDDLE(Image_isLazyGenerated_a, 256, 80, false, 0) {
+class TestImageGenerator : public SkImageGenerator {
+public:
+    TestImageGenerator() : SkImageGenerator(SkImageInfo::MakeN32Premul(10, 10)) {}
+    ~TestImageGenerator() override {}
+protected:
+    bool onGetPixels(const SkImageInfo& info, void* pixelPtr, size_t rowBytes,
+                     const Options& options) override {
+        SkPMColor* pixels = static_cast<SkPMColor*>(pixelPtr);
+        for (int y = 0; y < info.height(); ++y) {
+            for (int x = 0; x < info.width(); ++x) {
+                pixels[y * info.width() + x] = 0xff223344 + y * 0x000C0811;
+            }
+        }
+        return true;
+    }
+};
+
+void draw(SkCanvas* canvas) {
+    auto gen = std::unique_ptr<TestImageGenerator>(new TestImageGenerator());
+    sk_sp<SkImage> image(SkImage::MakeFromGenerator(std::move(gen)));
+    SkString lazy(image->isLazyGenerated() ? "is lazy" : "not lazy");
+    canvas->scale(8, 8);
+    canvas->drawImage(image, 0, 0, nullptr);
+    SkPaint paint;
+    paint.setTextSize(4);
+    canvas->drawString(lazy, 2, 5, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Image_isLazyGenerated_b.cpp b/docs/examples/Image_isLazyGenerated_b.cpp
new file mode 100644
index 0000000..faf5119
--- /dev/null
+++ b/docs/examples/Image_isLazyGenerated_b.cpp
@@ -0,0 +1,31 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f031c2a53f6a57833dc0127e674553da
+REG_FIDDLE(Image_isLazyGenerated_b, 256, 256, false, 5) {
+void draw(SkCanvas* canvas) {
+    auto drawImage = [=](sk_sp<SkImage> image, const char* label) -> void {
+        if (nullptr == image) {
+            return;
+        }
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        canvas->drawImage(image, 0, 0);
+        canvas->drawString(label, 30, image->height() / 4, paint);
+        canvas->drawString(
+                image->isLazyGenerated() ? "is lazily generated" : "not lazily generated",
+                20, image->height() * 3 / 4, paint);
+    };
+    sk_sp<SkImage> bitmapImage(SkImage::MakeFromBitmap(source));
+    sk_sp<SkImage> textureImage(SkImage::MakeFromTexture(canvas->getGrContext(), backEndTexture,
+                                kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType,
+                                kOpaque_SkAlphaType, nullptr));
+    drawImage(image, "image");
+    canvas->translate(image->width(), 0);
+    drawImage(bitmapImage, "source");
+    canvas->translate(-image->width(), image->height());
+    drawImage(textureImage, "backEndTexture");
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Image_isOpaque.cpp b/docs/examples/Image_isOpaque.cpp
new file mode 100644
index 0000000..1261582c
--- /dev/null
+++ b/docs/examples/Image_isOpaque.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e3340460003b74ee286d625e68589d65
+REG_FIDDLE(Image_isOpaque, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto check_isopaque = [](const SkImageInfo& imageInfo) -> void {
+        auto surface(SkSurface::MakeRaster(imageInfo));
+        auto image(surface->makeImageSnapshot());
+        SkDebugf("isOpaque = %s\n", image->isOpaque() ? "true" : "false");
+    };
+    check_isopaque(SkImageInfo::MakeN32Premul(5, 5));
+    check_isopaque(SkImageInfo::MakeN32(5, 5, kOpaque_SkAlphaType));
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_isTextureBacked.cpp b/docs/examples/Image_isTextureBacked.cpp
new file mode 100644
index 0000000..9726f57
--- /dev/null
+++ b/docs/examples/Image_isTextureBacked.cpp
@@ -0,0 +1,30 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9cf5c62a3d2243e6577ae563f360ea9d
+REG_FIDDLE(Image_isTextureBacked, 256, 256, false, 5) {
+void draw(SkCanvas* canvas) {
+    auto drawImage = [=](sk_sp<SkImage> image, const char* label) -> void {
+        if (nullptr == image) {
+            return;
+        }
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        canvas->drawImage(image, 0, 0);
+        canvas->drawString(label, 30, image->height() / 4, paint);
+        canvas->drawString(image->isTextureBacked() ? "is GPU texture" : "not GPU texture",
+                           20, image->height() * 3 / 4, paint);
+    };
+    sk_sp<SkImage> bitmapImage(SkImage::MakeFromBitmap(source));
+    sk_sp<SkImage> textureImage(SkImage::MakeFromTexture(canvas->getGrContext(), backEndTexture,
+                                kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType,
+                                kOpaque_SkAlphaType, nullptr));
+    drawImage(image, "image");
+    canvas->translate(image->width(), 0);
+    drawImage(bitmapImage, "source");
+    canvas->translate(-image->width(), image->height());
+    drawImage(textureImage, "backEndTexture");
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Image_isValid.cpp b/docs/examples/Image_isValid.cpp
new file mode 100644
index 0000000..a4b2f1f
--- /dev/null
+++ b/docs/examples/Image_isValid.cpp
@@ -0,0 +1,34 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=afc62f38aebc56af8e425297ec67dd37
+REG_FIDDLE(Image_isValid, 256, 256, false, 5) {
+void draw(SkCanvas* canvas) {
+    auto drawImage = [=](sk_sp<SkImage> image, const char* label) -> void {
+        if (nullptr == image) {
+            return;
+        }
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        canvas->drawImage(image, 0, 0);
+        canvas->drawString(label, image->width() / 2, image->height() / 4, paint);
+        if (canvas->getGrContext()) {
+            canvas->drawString(image->isValid(canvas->getGrContext()) ? "is valid on GPU" :
+                    "not valid on GPU", 20, image->height() * 5 / 8, paint);
+        }
+        canvas->drawString(image->isValid(nullptr) ? "is valid on CPU" :
+                "not valid on CPU", 20, image->height() * 7 / 8, paint);
+    };
+    sk_sp<SkImage> bitmapImage(SkImage::MakeFromBitmap(source));
+    sk_sp<SkImage> textureImage(SkImage::MakeFromTexture(canvas->getGrContext(), backEndTexture,
+                                kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType,
+                                kOpaque_SkAlphaType, nullptr));
+    drawImage(image, "image");
+    canvas->translate(image->width(), 0);
+    drawImage(bitmapImage, "source");
+    canvas->translate(-image->width(), image->height());
+    drawImage(textureImage, "backEndTexture");
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Image_makeColorSpace.cpp b/docs/examples/Image_makeColorSpace.cpp
new file mode 100644
index 0000000..1ff619f
--- /dev/null
+++ b/docs/examples/Image_makeColorSpace.cpp
@@ -0,0 +1,18 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=dbf5f75c1275a3013672f896767140fb
+REG_FIDDLE(Image_makeColorSpace, 256, 256, false, 5) {
+void draw(SkCanvas* canvas) {
+    sk_sp<SkColorSpace> normalColorSpace = SkColorSpace::MakeRGB(
+             SkColorSpace::kSRGB_RenderTargetGamma, SkColorSpace::kSRGB_Gamut);
+    sk_sp<SkColorSpace> wackyColorSpace = normalColorSpace->makeColorSpin();
+    for (auto colorSpace : { normalColorSpace, wackyColorSpace  } ) {
+        sk_sp<SkImage> colorSpaced = image->makeColorSpace(colorSpace);
+        canvas->drawImage(colorSpaced, 0, 0);
+        canvas->translate(128, 0);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Image_makeNonTextureImage.cpp b/docs/examples/Image_makeNonTextureImage.cpp
new file mode 100644
index 0000000..0f91222
--- /dev/null
+++ b/docs/examples/Image_makeNonTextureImage.cpp
@@ -0,0 +1,29 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ecdbaff44a02c310ef672b7d393c6dea
+REG_FIDDLE(Image_makeNonTextureImage, 256, 256, false, 5) {
+void draw(SkCanvas* canvas) {
+    auto drawImage = [=](sk_sp<SkImage> image, const char* label) -> void {
+        if (nullptr == image) {
+            return;
+        }
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        sk_sp<SkImage> nonTexture(image->makeNonTextureImage());
+        canvas->drawImage(nonTexture, 0, 0);
+        canvas->drawString(label, 20, nonTexture->height() / 4, paint);
+    };
+    sk_sp<SkImage> bitmapImage(SkImage::MakeFromBitmap(source));
+    sk_sp<SkImage> textureImage(SkImage::MakeFromTexture(canvas->getGrContext(), backEndTexture,
+                                kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType,
+                                kOpaque_SkAlphaType, nullptr));
+    drawImage(image, "image");
+    canvas->translate(image->width(), 0);
+    drawImage(bitmapImage, "source");
+    canvas->translate(-image->width(), image->height());
+    drawImage(textureImage, "backEndTexture");
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Image_makeRasterImage.cpp b/docs/examples/Image_makeRasterImage.cpp
new file mode 100644
index 0000000..fa522a1
--- /dev/null
+++ b/docs/examples/Image_makeRasterImage.cpp
@@ -0,0 +1,29 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=aed5f399915d40bb5d133ab586e5bac3
+REG_FIDDLE(Image_makeRasterImage, 256, 256, false, 5) {
+void draw(SkCanvas* canvas) {
+    auto drawImage = [=](sk_sp<SkImage> image, const char* label) -> void {
+        if (nullptr == image) {
+            return;
+        }
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        sk_sp<SkImage> raster(image->makeRasterImage());
+        canvas->drawImage(raster, 0, 0);
+        canvas->drawString(label, 20, raster->height() / 4, paint);
+    };
+    sk_sp<SkImage> bitmapImage(SkImage::MakeFromBitmap(source));
+    sk_sp<SkImage> textureImage(SkImage::MakeFromTexture(canvas->getGrContext(), backEndTexture,
+                                kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType,
+                                kOpaque_SkAlphaType, nullptr));
+    drawImage(image, "image");
+    canvas->translate(image->width(), 0);
+    drawImage(bitmapImage, "source");
+    canvas->translate(-image->width(), image->height());
+    drawImage(textureImage, "backEndTexture");
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Image_makeShader.cpp b/docs/examples/Image_makeShader.cpp
new file mode 100644
index 0000000..0f4611d
--- /dev/null
+++ b/docs/examples/Image_makeShader.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1c6de6fe72b00b5be970f5f718363449
+REG_FIDDLE(Image_makeShader, 256, 256, false, 4) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setRotate(45);
+    SkPaint paint;
+    paint.setShader(image->makeShader(SkTileMode::kRepeat, SkTileMode::kMirror, &matrix));
+    canvas->drawPaint(paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_makeShader_2.cpp b/docs/examples/Image_makeShader_2.cpp
new file mode 100644
index 0000000..6559726
--- /dev/null
+++ b/docs/examples/Image_makeShader_2.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=10172fca71b9dbdcade772513ffeb27e
+REG_FIDDLE(Image_makeShader_2, 256, 256, false, 5) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setRotate(45);
+    matrix.postTranslate(125, 30);
+    SkPaint paint;
+    paint.setShader(image->makeShader(&matrix));
+    canvas->drawPaint(paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_makeSubset.cpp b/docs/examples/Image_makeSubset.cpp
new file mode 100644
index 0000000..6faa552
--- /dev/null
+++ b/docs/examples/Image_makeSubset.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=889e495ce3e3b3bacc96e8230932331c
+REG_FIDDLE(Image_makeSubset, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->scale(.5f, .5f);
+    const int width = 64;
+    const int height = 64;
+    for (int y = 0; y < 512; y += height ) {
+        for (int x = 0; x < 512; x += width ) {
+            sk_sp<SkImage> subset(image->makeSubset({x, y, x + width, y + height}));
+            canvas->drawImage(subset, x * 3 / 2, y * 3 / 2);
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_makeTextureImage.cpp b/docs/examples/Image_makeTextureImage.cpp
new file mode 100644
index 0000000..c5bcb5e
--- /dev/null
+++ b/docs/examples/Image_makeTextureImage.cpp
@@ -0,0 +1,30 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=eeec9e07e604b44d0208899a2fe5bef5
+REG_FIDDLE(Image_makeTextureImage, 256, 256, false, 5) {
+void draw(SkCanvas* canvas) {
+    auto drawImage = [=](sk_sp<SkImage> image, GrContext* context, const char* label) -> void {
+        if (nullptr == image || nullptr == context) {
+            return;
+        }
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        sk_sp<SkImage> texture(image->makeTextureImage(context, nullptr));
+        canvas->drawImage(texture, 0, 0);
+        canvas->drawString(label, 20, texture->height() / 4, paint);
+    };
+    sk_sp<SkImage> bitmapImage(SkImage::MakeFromBitmap(source));
+    GrContext* context = canvas->getGrContext();
+    sk_sp<SkImage> textureImage(SkImage::MakeFromTexture(context, backEndTexture,
+                                kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType,
+                                kOpaque_SkAlphaType, nullptr));
+    drawImage(image, context, "image");
+    canvas->translate(image->width(), 0);
+    drawImage(bitmapImage, context, "source");
+    canvas->translate(-image->width(), image->height());
+    drawImage(textureImage, context, "backEndTexture");
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Image_makeWithFilter.cpp b/docs/examples/Image_makeWithFilter.cpp
new file mode 100644
index 0000000..f9e7299
--- /dev/null
+++ b/docs/examples/Image_makeWithFilter.cpp
@@ -0,0 +1,27 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=85a76163138a2720ac003691d6363938
+REG_FIDDLE(Image_makeWithFilter, 256, 256, false, 5) {
+void draw(SkCanvas* canvas) {
+    sk_sp<SkImageFilter> shadowFilter = SkDropShadowImageFilter::Make(
+                -10.0f * frame, 5.0f * frame, 3.0f, 3.0f, SK_ColorBLUE,
+                SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode,
+                nullptr);
+    sk_sp<SkImageFilter> offsetFilter = SkOffsetImageFilter::Make(40, 40, shadowFilter, nullptr);
+    SkIRect subset = image->bounds();
+    SkIRect clipBounds = image->bounds();
+    clipBounds.outset(60, 60);
+    SkIRect outSubset;
+    SkIPoint offset;
+    sk_sp<SkImage> filtered(image->makeWithFilter(offsetFilter.get(), subset, clipBounds,
+                            &outSubset, &offset));
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawLine(0, 0, offset.fX, offset.fY, paint);
+    canvas->translate(offset.fX, offset.fY);
+    canvas->drawImage(filtered, 0, 0);
+    canvas->drawRect(SkRect::Make(outSubset), paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_peekPixels.cpp b/docs/examples/Image_peekPixels.cpp
new file mode 100644
index 0000000..44bf916
--- /dev/null
+++ b/docs/examples/Image_peekPixels.cpp
@@ -0,0 +1,28 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=900c0eab8dfdecd8301ed5be95887f8e
+REG_FIDDLE(Image_peekPixels, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeN32Premul(12, 11));
+    SkCanvas offscreen(bitmap);
+    offscreen.clear(SK_ColorWHITE);
+    SkPaint paint;
+    offscreen.drawString("%", 1, 10, paint);
+    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
+    SkPixmap pixmap;
+    if (image->peekPixels(&pixmap)) {
+        const SkPMColor* pixels = pixmap.addr32();
+        SkPMColor pmWhite = pixels[0];
+        for (int y = 0; y < image->height(); ++y) {
+            for (int x = 0; x < image->width(); ++x) {
+                SkDebugf("%c", *pixels++ == pmWhite ? '-' : 'x');
+            }
+            SkDebugf("\n");
+        }
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Image_readPixels.cpp b/docs/examples/Image_readPixels.cpp
new file mode 100644
index 0000000..c41c861
--- /dev/null
+++ b/docs/examples/Image_readPixels.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8aa8ca63dff4641dfc6ea8a3c555d59c
+REG_FIDDLE(Image_readPixels, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->scale(.5f, .5f);
+    const int width = 32;
+    const int height = 32;
+    std::vector<int32_t> dstPixels;
+    dstPixels.resize(height * width * 4);
+    SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
+    for (int y = 0; y < 512; y += height ) {
+        for (int x = 0; x < 512; x += width ) {
+            if (image->readPixels(info, &dstPixels.front(), width * 4, x, y)) {
+                SkPixmap dstPixmap(info, &dstPixels.front(), width * 4);
+                SkBitmap bitmap;
+                bitmap.installPixels(dstPixmap);
+                canvas->drawBitmap(bitmap, 0, 0);
+            }
+            canvas->translate(48, 0);
+        }
+        canvas->translate(-16 * 48, 48);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_readPixels_2.cpp b/docs/examples/Image_readPixels_2.cpp
new file mode 100644
index 0000000..cfc38a1
--- /dev/null
+++ b/docs/examples/Image_readPixels_2.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b77a73c4baa63a4a8e2a4fdd96144d0b
+REG_FIDDLE(Image_readPixels_2, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    std::vector<int32_t> srcPixels;
+    int rowBytes = image->width() * 4;
+    int quarterWidth = image->width() / 4;
+    int quarterHeight = image->height() / 4;
+    srcPixels.resize(image->height() * rowBytes);
+    for (int y = 0; y < 4; ++y) {
+        for (int x = 0; x < 4; ++x) {
+            SkPixmap pixmap(SkImageInfo::MakeN32Premul(quarterWidth, quarterHeight),
+                    &srcPixels.front() + x * image->height() * quarterWidth +
+                    y * quarterWidth, rowBytes);
+            image->readPixels(pixmap, x * quarterWidth, y * quarterHeight);
+        }
+    }
+    canvas->scale(.5f, .5f);
+    SkBitmap bitmap;
+    bitmap.installPixels(SkImageInfo::MakeN32Premul(image->width(), image->height()),
+                             &srcPixels.front(), rowBytes);
+    canvas->drawBitmap(bitmap, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_refColorSpace.cpp b/docs/examples/Image_refColorSpace.cpp
new file mode 100644
index 0000000..b8a9061
--- /dev/null
+++ b/docs/examples/Image_refColorSpace.cpp
@@ -0,0 +1,27 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=59b2078ebfbda8736a57c0486ae33332
+REG_FIDDLE(Image_refColorSpace, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkPixmap pixmap;
+    source.peekPixels(&pixmap);
+    canvas->scale(.25f, .25f);
+    int y = 0;
+    for (auto gamma : { SkColorSpace::kLinear_RenderTargetGamma,
+                        SkColorSpace::kSRGB_RenderTargetGamma } ) {
+        int x = 0;
+        sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeRGB(gamma, SkColorSpace::kSRGB_Gamut);
+        for (int index = 0; index < 2; ++index) {
+            pixmap.setColorSpace(colorSpace);
+            sk_sp<SkImage> image = SkImage::MakeRasterCopy(pixmap);
+            canvas->drawImage(image, x, y);
+            colorSpace = image->refColorSpace()->makeColorSpin();
+            x += 512;
+        }
+        y += 512;
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Image_refEncodedData.cpp b/docs/examples/Image_refEncodedData.cpp
new file mode 100644
index 0000000..df3d537
--- /dev/null
+++ b/docs/examples/Image_refEncodedData.cpp
@@ -0,0 +1,28 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=80856fe921ce36f8d5a32d8672bccbfc
+REG_FIDDLE(Image_refEncodedData, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    struct {
+        const char* name;
+        sk_sp<SkImage> image;
+    } tests[] = { { "image", image }, { "bitmap", SkImage::MakeFromBitmap(source) },
+          { "texture", SkImage::MakeFromTexture(canvas->getGrContext(), backEndTexture,
+                            kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType,
+                            kOpaque_SkAlphaType, nullptr) } };
+    SkString string;
+    SkPaint paint;
+    for (const auto& test : tests ) {
+        if (!test.image) {
+            string.printf("no %s", test.name);
+        } else {
+            string.printf("%s" "encoded %s", test.image->refEncodedData() ? "" : "no ", test.name);
+        }
+        canvas->drawString(string, 10, 20, paint);
+        canvas->translate(0, 20);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Image_scalePixels.cpp b/docs/examples/Image_scalePixels.cpp
new file mode 100644
index 0000000..ab4b0fb
--- /dev/null
+++ b/docs/examples/Image_scalePixels.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5949c9a63610cae30019e5b1899ee38f
+REG_FIDDLE(Image_scalePixels, 256, 128, false, 3) {
+void draw(SkCanvas* canvas) {
+    std::vector<int32_t> srcPixels;
+    int quarterWidth = image->width() / 16;
+    int rowBytes = quarterWidth * 4;
+    int quarterHeight = image->height() / 16;
+    srcPixels.resize(quarterHeight * rowBytes);
+    SkPixmap pixmap(SkImageInfo::MakeN32Premul(quarterWidth, quarterHeight),
+                    &srcPixels.front(), rowBytes);
+    canvas->scale(4, 4);
+    SkFilterQuality qualities[] = { kNone_SkFilterQuality, kLow_SkFilterQuality,
+                     kMedium_SkFilterQuality, kHigh_SkFilterQuality };
+    for (unsigned index = 0; index < SK_ARRAY_COUNT(qualities); ++index) {
+        image->scalePixels(pixmap, qualities[index]);
+        sk_sp<SkImage> filtered = SkImage::MakeFromRaster(pixmap, nullptr, nullptr);
+        canvas->drawImage(filtered, 16 * index, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Image_uniqueID.cpp b/docs/examples/Image_uniqueID.cpp
new file mode 100644
index 0000000..f63aba5
--- /dev/null
+++ b/docs/examples/Image_uniqueID.cpp
@@ -0,0 +1,19 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d70194c9c51e700335f95de91846d023
+REG_FIDDLE(Image_uniqueID, 256, 156, false, 5) {
+void draw(SkCanvas* canvas) {
+    sk_sp<SkImage> subset = image->makeSubset({10, 20, 90, 100});
+    canvas->drawImage(image, 0, 0);
+    canvas->drawImage(subset, 128, 0);
+    SkPaint paint;
+    SkString s;
+    s.printf("original id: %d", image->uniqueID());
+    canvas->drawString(s, 20, image->height() + 20, paint);
+    s.printf("subset id: %d", subset->uniqueID());
+    canvas->drawString(s, 148, subset->height() + 20, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Image_width.cpp b/docs/examples/Image_width.cpp
new file mode 100644
index 0000000..22fb5bb
--- /dev/null
+++ b/docs/examples/Image_width.cpp
@@ -0,0 +1,16 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9aec65fc252ffc9982fa8867433eca18
+REG_FIDDLE(Image_width, 256, 96, false, 4) {
+void draw(SkCanvas* canvas) {
+    canvas->translate(10, 10);
+    canvas->drawImage(image, 0, 0);
+    canvas->translate(0, image->height());
+    SkPaint paint;
+    canvas->drawLine(0, 10, image->width(), 10, paint);
+    canvas->drawString("width", image->width() / 2 - 15, 25, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Lighten.cpp b/docs/examples/Lighten.cpp
new file mode 100644
index 0000000..f02a2ea
--- /dev/null
+++ b/docs/examples/Lighten.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=95cb08b8c8db3af3b2c9ad56ae7d6bc1
+REG_FIDDLE(Lighten, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->drawImage(image, 0, 0);
+    SkColor colors[] = { SK_ColorBLACK, SK_ColorWHITE };
+    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
+    SkPaint paint;
+    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
+            SkTileMode::kClamp));
+    paint.setBlendMode(SkBlendMode::kLighten);
+    canvas->drawPaint(paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Luminosity.cpp b/docs/examples/Luminosity.cpp
new file mode 100644
index 0000000..e3fad9c
--- /dev/null
+++ b/docs/examples/Luminosity.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7d42fe34ae20dd9e12c39dc3950e9989
+REG_FIDDLE(Luminosity, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->drawImage(image, 0, 0);
+    canvas->drawColor(0xFF00FF00, SkBlendMode::kLuminosity);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Mask_Filter_Methods.cpp b/docs/examples/Mask_Filter_Methods.cpp
new file mode 100644
index 0000000..8702c6a
--- /dev/null
+++ b/docs/examples/Mask_Filter_Methods.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=55d7b9d482ac8e17a6153f555a8adb8d
+REG_FIDDLE(Mask_Filter_Methods, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setMaskFilter(SkMaskFilter::MakeBlur(kSolid_SkBlurStyle, 3));
+    canvas->drawRect(SkRect::MakeXYWH(40, 40, 175, 175), paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_063.cpp b/docs/examples/Matrix_063.cpp
new file mode 100644
index 0000000..ef25b47
--- /dev/null
+++ b/docs/examples/Matrix_063.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e6ad0bd2999613d9e4758b661d45070c
+REG_FIDDLE(Matrix_063, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
+    SkRect::Make(source.bounds()).toQuad(bitmapBounds);
+    matrix.setPolyToPoly(bitmapBounds, perspect, 4);
+    matrix.postIDiv(1, 2);
+    canvas->concat(matrix);
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_Concat.cpp b/docs/examples/Matrix_Concat.cpp
new file mode 100644
index 0000000..ca11a31
--- /dev/null
+++ b/docs/examples/Matrix_Concat.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6b4562c7052da94f3d5b2412dca41946
+REG_FIDDLE(Matrix_Concat, 256, 64, false, 4) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix, matrix2;
+    SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
+    SkRect::Make(source.bounds()).toQuad(bitmapBounds);
+    matrix.setPolyToPoly(bitmapBounds, perspect, 4);
+    matrix2.setPolyToPoly(perspect, bitmapBounds, 4);
+    SkMatrix concat = SkMatrix::Concat(matrix, matrix2);
+    canvas->concat(concat);
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_I.cpp b/docs/examples/Matrix_I.cpp
new file mode 100644
index 0000000..d379219
--- /dev/null
+++ b/docs/examples/Matrix_I.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d961d91020f19037204a8c3fd8cb1060
+REG_FIDDLE(Matrix_I, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix m1, m2, m3;
+    m1.reset();
+    m2.setIdentity();
+    m3 = SkMatrix::I();
+    SkDebugf("m1 %c= m2\n", m1 == m2 ? '=' : '!');
+    SkDebugf("m2 %c= m3\n", m1 == m2 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_InvalidMatrix.cpp b/docs/examples/Matrix_InvalidMatrix.cpp
new file mode 100644
index 0000000..29cbfd1
--- /dev/null
+++ b/docs/examples/Matrix_InvalidMatrix.cpp
@@ -0,0 +1,9 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=af0b72360c1c7a25b4754bfa47011dd5
+REG_FIDDLE(Matrix_InvalidMatrix, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkDebugf("scaleX %g\n", SkMatrix::InvalidMatrix().getScaleX());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_MakeAll.cpp b/docs/examples/Matrix_MakeAll.cpp
new file mode 100644
index 0000000..bf68a0a
--- /dev/null
+++ b/docs/examples/Matrix_MakeAll.cpp
@@ -0,0 +1,21 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6bad83b64de9266e323c29d550e04188
+REG_FIDDLE(Matrix_MakeAll, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint p;
+    p.setAntiAlias(true);
+    p.setTextSize(64);
+    for (SkScalar sx : { -1, 1 } ) {
+        for (SkScalar sy : { -1, 1 } ) {
+            SkAutoCanvasRestore autoRestore(canvas, true);
+            SkMatrix m = SkMatrix::MakeAll(sx, 1, 128,    0, sy, 128,   0, 0, 1);
+            canvas->concat(m);
+            canvas->drawString("K", 0, 0, p);
+        }
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_MakeRectToRect.cpp b/docs/examples/Matrix_MakeRectToRect.cpp
new file mode 100644
index 0000000..4adb674
--- /dev/null
+++ b/docs/examples/Matrix_MakeRectToRect.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a1d6a6721b39350f81021f71a1b93208
+REG_FIDDLE(Matrix_MakeRectToRect, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const SkRect srcs[] = { {0, 0, 0, 0}, {1, 2, 3, 4} };
+    const SkRect dsts[] = { {0, 0, 0, 0}, {5, 6, 8, 9} };
+    for (auto src : srcs) {
+        for (auto dst : dsts) {
+             SkMatrix matrix = SkMatrix::MakeRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
+             SkDebugf("src: %g, %g, %g, %g  dst: %g, %g, %g, %g\n",
+                      src.fLeft, src.fTop, src.fRight, src.fBottom,
+                      dst.fLeft, dst.fTop, dst.fRight, dst.fBottom);
+             matrix.dump();
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_MakeScale.cpp b/docs/examples/Matrix_MakeScale.cpp
new file mode 100644
index 0000000..cd6f014
--- /dev/null
+++ b/docs/examples/Matrix_MakeScale.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7ff17718111df6d6f95381d8a8f1b389
+REG_FIDDLE(Matrix_MakeScale, 256, 256, false, 4) {
+void draw(SkCanvas* canvas) {
+    canvas->concat(SkMatrix::MakeScale(4, 3));
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_MakeScale_2.cpp b/docs/examples/Matrix_MakeScale_2.cpp
new file mode 100644
index 0000000..b2cb0b6
--- /dev/null
+++ b/docs/examples/Matrix_MakeScale_2.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2956aeb50fa862cdb13995e1e56a4bc8
+REG_FIDDLE(Matrix_MakeScale_2, 256, 256, false, 4) {
+void draw(SkCanvas* canvas) {
+    canvas->concat(SkMatrix::MakeScale(4));
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_MakeTrans.cpp b/docs/examples/Matrix_MakeTrans.cpp
new file mode 100644
index 0000000..4a7bc72
--- /dev/null
+++ b/docs/examples/Matrix_MakeTrans.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b2479df0d9cf296ff64ac31e36684557
+REG_FIDDLE(Matrix_MakeTrans, 256, 256, false, 4) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix = SkMatrix::MakeTrans(64, 48);
+    for (int i = 0; i < 4; ++i) {
+        canvas->drawBitmap(source, 0, 0);
+        canvas->concat(matrix);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_ScaleToFit.cpp b/docs/examples/Matrix_ScaleToFit.cpp
new file mode 100644
index 0000000..ad8dced
--- /dev/null
+++ b/docs/examples/Matrix_ScaleToFit.cpp
@@ -0,0 +1,31 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=17c3070b31b700ea8f52e48af9a66b6e
+REG_FIDDLE(Matrix_ScaleToFit, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    const char* labels[] = { "Fill", "Start", "Center", "End" };
+    SkRect rects[] = {{5, 5, 59, 59}, {5, 74, 59, 108}, {10, 123, 44, 172}, {10, 187, 54, 231}};
+    SkRect bounds;
+    source.getBounds(&bounds);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    for (auto fit : { SkMatrix::kFill_ScaleToFit, SkMatrix::kStart_ScaleToFit,
+                      SkMatrix::kCenter_ScaleToFit, SkMatrix::kEnd_ScaleToFit } ) {
+        for (auto rect : rects ) {
+            canvas->drawRect(rect, paint);
+            SkMatrix matrix;
+            if (!matrix.setRectToRect(bounds, rect, fit)) {
+                continue;
+            }
+            SkAutoCanvasRestore acr(canvas, true);
+            canvas->concat(matrix);
+            canvas->drawBitmap(source, 0, 0);
+        }
+        canvas->drawString(labels[fit], 10, 255, paint);
+        canvas->translate(64, 0);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_SetAffineIdentity.cpp b/docs/examples/Matrix_SetAffineIdentity.cpp
new file mode 100644
index 0000000..81a7378
--- /dev/null
+++ b/docs/examples/Matrix_SetAffineIdentity.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e10adbd0bcc940c5d4d872db0e78e892
+REG_FIDDLE(Matrix_SetAffineIdentity, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkScalar affine[6];
+    SkMatrix::SetAffineIdentity(affine);
+    const char* names[] = { "ScaleX", "SkewY", "SkewX", "ScaleY", "TransX", "TransY" };
+    for (int i = 0; i < 6; ++i) {
+        SkDebugf("%s: %g ", names[i], affine[i]);
+    }
+    SkDebugf("\n");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_TypeMask.cpp b/docs/examples/Matrix_TypeMask.cpp
new file mode 100644
index 0000000..35ea2e2
--- /dev/null
+++ b/docs/examples/Matrix_TypeMask.cpp
@@ -0,0 +1,29 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ba19b36df8cd78586f3dff54e2d4c093
+REG_FIDDLE(Matrix_TypeMask, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkMatrix& matrix) -> void {
+        SkString typeMask;
+        typeMask += SkMatrix::kIdentity_Mask == matrix.getType() ? "kIdentity_Mask " : "";
+        typeMask += SkMatrix::kTranslate_Mask & matrix.getType() ? "kTranslate_Mask " : "";
+        typeMask += SkMatrix::kScale_Mask & matrix.getType() ? "kScale_Mask " : "";
+        typeMask += SkMatrix::kAffine_Mask & matrix.getType() ? "kAffine_Mask " : "";
+        typeMask += SkMatrix::kPerspective_Mask & matrix.getType() ? "kPerspective_Mask" : "";
+        SkDebugf("after %s: %s\n", prefix, typeMask.c_str());
+    };
+SkMatrix matrix;
+matrix.reset();
+debugster("reset", matrix);
+matrix.postTranslate(1, 0);
+debugster("postTranslate", matrix);
+matrix.postScale(2, 1);
+debugster("postScale", matrix);
+matrix.postRotate(45);
+debugster("postScale", matrix);
+SkPoint polys[][4] = {{{0, 0}, {0, 1}, {1, 1}, {1, 0}}, {{0, 0}, {0, 1}, {2, 1}, {1, 0}}};
+matrix.setPolyToPoly(polys[0], polys[1], 4);
+debugster("setPolyToPoly", matrix);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_array_operator.cpp b/docs/examples/Matrix_array_operator.cpp
new file mode 100644
index 0000000..449cb64
--- /dev/null
+++ b/docs/examples/Matrix_array_operator.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e8740493abdf0c6341762db9cee56b89
+REG_FIDDLE(Matrix_array_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setScale(42, 24);
+    SkDebugf("matrix[SkMatrix::kMScaleX] %c= 42\n", matrix[SkMatrix::kMScaleX] == 42 ? '=' : '!');
+    SkDebugf("matrix[SkMatrix::kMScaleY] %c= 24\n", matrix[SkMatrix::kMScaleY] == 24 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_asAffine.cpp b/docs/examples/Matrix_asAffine.cpp
new file mode 100644
index 0000000..ae57aee
--- /dev/null
+++ b/docs/examples/Matrix_asAffine.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3325bdf82bd86d9fbc4199f248afa82c
+REG_FIDDLE(Matrix_asAffine, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setAll(2, 3, 4, 5, 6, 7, 0, 0, 1);
+    SkScalar affine[6];
+    if (matrix.asAffine(affine)) {
+        const char* names[] = { "ScaleX", "SkewY", "SkewX", "ScaleY", "TransX", "TransY" };
+        for (int i = 0; i < 6; ++i) {
+            SkDebugf("%s: %g ", names[i], affine[i]);
+        }
+        SkDebugf("\n");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_cheapEqualTo.cpp b/docs/examples/Matrix_cheapEqualTo.cpp
new file mode 100644
index 0000000..80ceaeb
--- /dev/null
+++ b/docs/examples/Matrix_cheapEqualTo.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=39016b3cfc6bbabb09348a53822ce508
+REG_FIDDLE(Matrix_cheapEqualTo, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkMatrix& a, const SkMatrix& b) -> void {
+        SkDebugf("%s: a %c= b a.cheapEqualTo(b): %s\n", prefix,
+                 a == b ? '=' : '!', a.cheapEqualTo(b) ? "true" : "false");
+    };
+    SkMatrix a, b;
+    a.setAll(1, 0, 0,   0, 1, 0,  0, 0, 1);
+    b.setIdentity();
+    debugster("identity", a, b);
+    a.setAll(1, -0.0f, 0,   0, 1, 0,  0, 0, 1);
+    debugster("neg zero", a, b);
+    a.setAll(1, SK_ScalarNaN, 0,   0, 1, 0,  0, 0, 1);
+    debugster(" one NaN", a, b);
+    b.setAll(1, SK_ScalarNaN, 0,   0, 1, 0,  0, 0, 1);
+    debugster("both NaN", a, b);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_decomposeScale.cpp b/docs/examples/Matrix_decomposeScale.cpp
new file mode 100644
index 0000000..7f7ca5b
--- /dev/null
+++ b/docs/examples/Matrix_decomposeScale.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=139b874da0a3ede1f3df88119085c0aa
+REG_FIDDLE(Matrix_decomposeScale, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setRotate(90 * SK_Scalar1);
+    matrix.postScale(1.f / 4, 1.f / 2);
+    matrix.dump();
+    SkSize scale = {SK_ScalarNaN, SK_ScalarNaN};
+    SkMatrix remaining;
+    remaining.reset();
+    bool success = matrix.decomposeScale(&scale, &remaining);
+    SkDebugf("success: %s  ", success ? "true" : "false");
+    SkDebugf("scale: %g, %g\n", scale.width(), scale.height());
+    remaining.dump();
+    SkMatrix scaleMatrix = SkMatrix::MakeScale(scale.width(), scale.height());
+    SkMatrix combined = SkMatrix::Concat(scaleMatrix, remaining);
+    combined.dump();
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_dirtyMatrixTypeCache.cpp b/docs/examples/Matrix_dirtyMatrixTypeCache.cpp
new file mode 100644
index 0000000..f536170
--- /dev/null
+++ b/docs/examples/Matrix_dirtyMatrixTypeCache.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f4365ef332f51f7fd25040e0771ba9a2
+REG_FIDDLE(Matrix_dirtyMatrixTypeCache, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setIdentity();
+    SkDebugf("with identity matrix: x = %g\n", matrix.mapXY(24, 42).fX);
+    SkScalar& skewRef = matrix[SkMatrix::kMSkewX];
+    skewRef = 0;
+    SkDebugf("after skew x mod:     x = %g\n", matrix.mapXY(24, 42).fX);
+    skewRef = 1;
+    SkDebugf("after 2nd skew x mod: x = %g\n", matrix.mapXY(24, 42).fX);
+    matrix.dirtyMatrixTypeCache();
+    SkDebugf("after dirty cache:    x = %g\n", matrix.mapXY(24, 42).fX);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_dump.cpp b/docs/examples/Matrix_dump.cpp
new file mode 100644
index 0000000..77a1ef8
--- /dev/null
+++ b/docs/examples/Matrix_dump.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8d72a4818e5a9188348f6c08ab5d8a40
+REG_FIDDLE(Matrix_dump, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setRotate(45);
+    matrix.dump();
+    SkMatrix nearlyEqual;
+    nearlyEqual.setAll(0.7071f, -0.7071f, 0,   0.7071f, 0.7071f, 0,   0, 0, 1);
+    nearlyEqual.dump();
+    SkDebugf("matrix %c= nearlyEqual\n", matrix == nearlyEqual ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_equal_operator.cpp b/docs/examples/Matrix_equal_operator.cpp
new file mode 100644
index 0000000..6890ee3
--- /dev/null
+++ b/docs/examples/Matrix_equal_operator.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3902859150b0f0c4aeb1f25d00434baa
+REG_FIDDLE(Matrix_equal_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkMatrix& a, const SkMatrix& b) -> void {
+        SkDebugf("%s: a %c= b a.cheapEqualTo(b): %s\n", prefix,
+                 a == b ? '=' : '!', a.cheapEqualTo(b) ? "true" : "false");
+    };
+    SkMatrix a, b;
+    a.setAll(1, 0, 0,   0, 1, 0,  0, 0, 1);
+    b.setScale(2, 4);
+    b.postScale(0.5f, 0.25f);
+    debugster("identity", a, b);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_fixedStepInX.cpp b/docs/examples/Matrix_fixedStepInX.cpp
new file mode 100644
index 0000000..be68cb5
--- /dev/null
+++ b/docs/examples/Matrix_fixedStepInX.cpp
@@ -0,0 +1,28 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=fad6b92b21b1e1deeae61978cec2d232
+REG_FIDDLE(Matrix_fixedStepInX, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    const SkPoint center = { 128, 128 };
+    matrix.setScale(20, 25, center.fX, center.fY);
+    matrix.postRotate(75, center.fX, center.fY);
+    {
+       SkAutoCanvasRestore acr(canvas, true);
+       canvas->concat(matrix);
+       canvas->drawBitmap(source, 0, 0);
+    }
+    if (matrix.isFixedStepInX()) {
+       SkPaint paint;
+       paint.setAntiAlias(true);
+       SkVector step = matrix.fixedStepInX(128);
+       SkVector end = center + step;
+       canvas->drawLine(center, end, paint);
+       SkVector arrow = { step.fX + step.fY, step.fY - step.fX};
+       arrow = arrow * .25f;
+       canvas->drawLine(end, end - arrow, paint);
+       canvas->drawLine(end, {end.fX + arrow.fY, end.fY - arrow.fX}, paint);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_get.cpp b/docs/examples/Matrix_get.cpp
new file mode 100644
index 0000000..d6fd779
--- /dev/null
+++ b/docs/examples/Matrix_get.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f5ed382bd04fa7d50b2398cce2fca23a
+REG_FIDDLE(Matrix_get, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setSkew(42, 24);
+    SkDebugf("matrix.get(SkMatrix::kMSkewX) %c= 42\n",
+             matrix.get(SkMatrix::kMSkewX) == 42 ? '=' : '!');
+    SkDebugf("matrix.get(SkMatrix::kMSkewY) %c= 24\n",
+             matrix.get(SkMatrix::kMSkewY) == 24 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_get9.cpp b/docs/examples/Matrix_get9.cpp
new file mode 100644
index 0000000..95df291
--- /dev/null
+++ b/docs/examples/Matrix_get9.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=379fc375e011050b54ed9df83c0996a7
+REG_FIDDLE(Matrix_get9, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix = SkMatrix::MakeRectToRect({0, 0, 1, 1}, {3, 4, 7, 9},
+                                               SkMatrix::kFill_ScaleToFit);
+    SkScalar b[9];
+    matrix.get9(b);
+    SkDebugf("{%g, %g, %g},\n{%g, %g, %g},\n{%g, %g, %g}\n", b[0], b[1], b[2],
+             b[3], b[4], b[5], b[6], b[7], b[8]);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_getMaxScale.cpp b/docs/examples/Matrix_getMaxScale.cpp
new file mode 100644
index 0000000..fc4cc5f
--- /dev/null
+++ b/docs/examples/Matrix_getMaxScale.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3fee4364929899649cf9efc37897e964
+REG_FIDDLE(Matrix_getMaxScale, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setScale(42, 24);
+    SkDebugf("matrix.getMaxScale() %g\n", matrix.getMaxScale());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_getMinMaxScales.cpp b/docs/examples/Matrix_getMinMaxScales.cpp
new file mode 100644
index 0000000..5f2e5fd
--- /dev/null
+++ b/docs/examples/Matrix_getMinMaxScales.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=13adba0ecf5f82247cf051b4fa4d8a9c
+REG_FIDDLE(Matrix_getMinMaxScales, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setAll(1, 0, 0,  0, 1, 0,   0, 0, 0);
+    if (matrix.invert(&matrix)) {
+        SkScalar factor[2] = {2, 2};
+        bool result = matrix.getMinMaxScales(factor);
+        SkDebugf("matrix.getMinMaxScales() %s %g %g\n",
+                result ? "true" : "false", factor[0], factor[1]);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_getMinScale.cpp b/docs/examples/Matrix_getMinScale.cpp
new file mode 100644
index 0000000..35397d3
--- /dev/null
+++ b/docs/examples/Matrix_getMinScale.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1d6f67904c88a806c3731879e9af4ae5
+REG_FIDDLE(Matrix_getMinScale, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setScale(42, 24);
+    SkDebugf("matrix.getMinScale() %g\n", matrix.getMinScale());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_getPerspX.cpp b/docs/examples/Matrix_getPerspX.cpp
new file mode 100644
index 0000000..fcd7d8a
--- /dev/null
+++ b/docs/examples/Matrix_getPerspX.cpp
@@ -0,0 +1,25 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a0f5bf4b55e8c33bfda29bf67e34306f
+REG_FIDDLE(Matrix_getPerspX, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix m;
+    m.setIdentity();
+    m.set(SkMatrix::kMPersp0, -0.004f);
+    SkAutoCanvasRestore autoRestore(canvas, true);
+    canvas->translate(22, 144);
+    SkPaint black;
+    black.setAntiAlias(true);
+    black.setTextSize(24);
+    SkPaint gray = black;
+    gray.setColor(0xFF9f9f9f);
+    SkString string;
+    string.appendScalar(m.getPerspX());
+    canvas->drawString(string, 0, -72, gray);
+    canvas->concat(m);
+    canvas->drawString(string, 0, 0, black);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_getPerspY.cpp b/docs/examples/Matrix_getPerspY.cpp
new file mode 100644
index 0000000..135ce15
--- /dev/null
+++ b/docs/examples/Matrix_getPerspY.cpp
@@ -0,0 +1,25 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=424a00a73675dbd99ad20feb0267442b
+REG_FIDDLE(Matrix_getPerspY, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix m;
+    m.setIdentity();
+    m.set(SkMatrix::kMPersp1, -0.004f);
+    SkAutoCanvasRestore autoRestore(canvas, true);
+    canvas->translate(22, 144);
+    SkPaint black;
+    black.setAntiAlias(true);
+    black.setTextSize(24);
+    SkPaint gray = black;
+    gray.setColor(0xFF9f9f9f);
+    SkString string;
+    string.appendScalar(m.getPerspY());
+    canvas->drawString(string, 0, -72, gray);
+    canvas->concat(m);
+    canvas->drawString(string, 0, 0, black);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_getScaleX.cpp b/docs/examples/Matrix_getScaleX.cpp
new file mode 100644
index 0000000..6bb19e3
--- /dev/null
+++ b/docs/examples/Matrix_getScaleX.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ab746d9be63975041ae8e50cba84dc3d
+REG_FIDDLE(Matrix_getScaleX, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setScale(42, 24);
+    SkDebugf("matrix.getScaleX() %c= 42\n", matrix.getScaleX() == 42 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_getScaleY.cpp b/docs/examples/Matrix_getScaleY.cpp
new file mode 100644
index 0000000..64941bf
--- /dev/null
+++ b/docs/examples/Matrix_getScaleY.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=708b1a548a2f8661b2ab570782fbc751
+REG_FIDDLE(Matrix_getScaleY, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setScale(42, 24);
+    SkDebugf("matrix.getScaleY() %c= 24\n", matrix.getScaleY() == 24 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_getSkewX.cpp b/docs/examples/Matrix_getSkewX.cpp
new file mode 100644
index 0000000..f96209f
--- /dev/null
+++ b/docs/examples/Matrix_getSkewX.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=df3a5d3c688e7597eae1e4e07bf91ae6
+REG_FIDDLE(Matrix_getSkewX, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setSkew(42, 24);
+    SkDebugf("matrix.getSkewX() %c= 42\n", matrix.getSkewX() == 42 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_getSkewY.cpp b/docs/examples/Matrix_getSkewY.cpp
new file mode 100644
index 0000000..e3dc404
--- /dev/null
+++ b/docs/examples/Matrix_getSkewY.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6be5704506d029ffc91ba03b1d3e674b
+REG_FIDDLE(Matrix_getSkewY, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setSkew(42, 24);
+    SkDebugf("matrix.getSkewY() %c= 24\n", matrix.getSkewY() == 24 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_getTranslateX.cpp b/docs/examples/Matrix_getTranslateX.cpp
new file mode 100644
index 0000000..a239fec
--- /dev/null
+++ b/docs/examples/Matrix_getTranslateX.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6236f7f2b91aff977a66ba2ee2558ca4
+REG_FIDDLE(Matrix_getTranslateX, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setTranslate(42, 24);
+    SkDebugf("matrix.getTranslateX() %c= 42\n", matrix.getTranslateX() == 42 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_getTranslateY.cpp b/docs/examples/Matrix_getTranslateY.cpp
new file mode 100644
index 0000000..105f21f
--- /dev/null
+++ b/docs/examples/Matrix_getTranslateY.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=08464e32d22421d2b254c71a84545ef5
+REG_FIDDLE(Matrix_getTranslateY, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setTranslate(42, 24);
+    SkDebugf("matrix.getTranslateY() %c= 24\n", matrix.getTranslateY() == 24 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_getType.cpp b/docs/examples/Matrix_getType.cpp
new file mode 100644
index 0000000..77c427e
--- /dev/null
+++ b/docs/examples/Matrix_getType.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8e45fe2dd52731bb2d4318686257e1d7
+REG_FIDDLE(Matrix_getType, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setAll(1, 0, 0,   0, 1, 0,    0, 0, 1);
+    SkDebugf("identity flags hex: %0x decimal: %d\n", matrix.getType(), matrix.getType());
+    matrix.setAll(1, 0, 0,   0, 1, 0,    0, 0, .5f);
+    SkDebugf("set all  flags hex: %0x decimal: %d\n", matrix.getType(), matrix.getType());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_hasPerspective.cpp b/docs/examples/Matrix_hasPerspective.cpp
new file mode 100644
index 0000000..6a167be
--- /dev/null
+++ b/docs/examples/Matrix_hasPerspective.cpp
@@ -0,0 +1,22 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=688123908c733169bbbfaf11f41ecff6
+REG_FIDDLE(Matrix_hasPerspective, 256, 256, false, 4) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
+    SkRect::Make(source.bounds()).toQuad(bitmapBounds);
+    matrix.setPolyToPoly(bitmapBounds, perspect, 4);
+    canvas->concat(matrix);
+    SkString string;
+    string.printf("hasPerspective %s", matrix.hasPerspective() ? "true" : "false");
+    canvas->drawBitmap(source, 0, 0);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(48);
+    canvas->drawString(string, 0, source.bounds().height() + 48, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_invert.cpp b/docs/examples/Matrix_invert.cpp
new file mode 100644
index 0000000..b682e66
--- /dev/null
+++ b/docs/examples/Matrix_invert.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0e03cd9f154603ed4b21ca56d69dae44
+REG_FIDDLE(Matrix_invert, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    const SkPoint src[] = { { 10, 120}, {120, 120}, {120, 10}, {  10, 10} };
+    const SkPoint dst[] = { {150, 120}, {200, 100}, {240, 30}, { 130, 40} };
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkMatrix matrix;
+    matrix.setPolyToPoly(src, dst, 4);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, 4, src, paint);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, 4, dst, paint);
+    paint.setColor(SK_ColorBLUE);
+    paint.setStrokeWidth(3);
+    paint.setStrokeCap(SkPaint::kRound_Cap);
+    canvas->drawPoints(SkCanvas::kPoints_PointMode, 4, dst, paint);
+    if (matrix.invert(&matrix)) {
+        canvas->concat(matrix);
+        canvas->drawPoints(SkCanvas::kPoints_PointMode, 4, dst, paint);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_isFinite.cpp b/docs/examples/Matrix_isFinite.cpp
new file mode 100644
index 0000000..6cc8dd1
--- /dev/null
+++ b/docs/examples/Matrix_isFinite.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bc6c6f6a5df770287120d87f81b922eb
+REG_FIDDLE(Matrix_isFinite, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix = SkMatrix::MakeTrans(SK_ScalarNaN, 0);
+    matrix.dump();
+    SkDebugf("matrix is finite: %s\n", matrix.isFinite() ? "true" : "false");
+    SkDebugf("matrix %c= matrix\n", matrix == matrix ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_isFixedStepInX.cpp b/docs/examples/Matrix_isFixedStepInX.cpp
new file mode 100644
index 0000000..7d746a5
--- /dev/null
+++ b/docs/examples/Matrix_isFixedStepInX.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ab57b232acef69f26de9cb23d23c8a1a
+REG_FIDDLE(Matrix_isFixedStepInX, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    for (SkScalar px : { 0.0f, 0.1f } ) {
+        for (SkScalar py : { 0.0f, 0.1f } ) {
+            for (SkScalar sy : { 1, 2 } ) {
+                matrix.setAll(1, 0, 0,   0, sy, 0,   px, py, 1);
+                matrix.dump();
+                SkDebugf("isFixedStepInX: %s\n", matrix.isFixedStepInX() ? "true" : "false");
+            }
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_isIdentity.cpp b/docs/examples/Matrix_isIdentity.cpp
new file mode 100644
index 0000000..a838050
--- /dev/null
+++ b/docs/examples/Matrix_isIdentity.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=780ab376325b3cfa889ea26c0769ec11
+REG_FIDDLE(Matrix_isIdentity, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setAll(1, 0, 0,   0, 1, 0,    0, 0, 1);
+    SkDebugf("is identity: %s\n", matrix.isIdentity() ? "true" : "false");
+    matrix.setAll(1, 0, 0,   0, 1, 0,    0, 0, 2);
+    SkDebugf("is identity: %s\n", matrix.isIdentity() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_isScaleTranslate.cpp b/docs/examples/Matrix_isScaleTranslate.cpp
new file mode 100644
index 0000000..3f5168f
--- /dev/null
+++ b/docs/examples/Matrix_isScaleTranslate.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6287e29674a487eb94174992d45b9a34
+REG_FIDDLE(Matrix_isScaleTranslate, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    for (SkScalar scaleX : { 1, 2 } ) {
+        for (SkScalar translateX : { 0, 20 } ) {
+            matrix.setAll(scaleX, 0, translateX,   0, 1, 0,    0, 0, 1);
+            SkDebugf("is scale-translate: %s\n", matrix.isScaleTranslate() ? "true" : "false");
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_isSimilarity.cpp b/docs/examples/Matrix_isSimilarity.cpp
new file mode 100644
index 0000000..cc96df0
--- /dev/null
+++ b/docs/examples/Matrix_isSimilarity.cpp
@@ -0,0 +1,31 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8b37f4ae7fec1756433c0f984175fb14
+REG_FIDDLE(Matrix_isSimilarity, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint p;
+    p.setAntiAlias(true);
+    SkMatrix m;
+    int below = 175;
+    for (SkScalar sx : { -1, 1 } ) {
+        for (SkScalar sy : { -1, 1 } ) {
+            m.setAll(sx, 1, 128,    1, sy, 32,   0, 0, 1);
+            bool isSimilarity = m.isSimilarity();
+            SkString str;
+            str.printf("sx: %g sy: %g sim: %s", sx, sy, isSimilarity ? "true" : "false");
+            {
+                SkAutoCanvasRestore autoRestore(canvas, true);
+                canvas->concat(m);
+                canvas->drawString(str, 0, 0, p);
+            }
+            if (!isSimilarity) {
+                canvas->drawString(str, 40, below, p);
+                below += 20;
+            }
+        }
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_isTranslate.cpp b/docs/examples/Matrix_isTranslate.cpp
new file mode 100644
index 0000000..bca84ce
--- /dev/null
+++ b/docs/examples/Matrix_isTranslate.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=73ac71a8a30841873577c11c6c9b38ee
+REG_FIDDLE(Matrix_isTranslate, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    for (SkScalar scaleX : { 1, 2 } ) {
+        for (SkScalar translateX : { 0, 20 } ) {
+            matrix.setAll(scaleX, 0, translateX,   0, 1, 0,    0, 0, 1);
+            SkDebugf("is translate: %s\n", matrix.isTranslate() ? "true" : "false");
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_mapHomogeneousPoints.cpp b/docs/examples/Matrix_mapHomogeneousPoints.cpp
new file mode 100644
index 0000000..bd10507
--- /dev/null
+++ b/docs/examples/Matrix_mapHomogeneousPoints.cpp
@@ -0,0 +1,31 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d56f93e4bc763c7ba4914321ed07a8b5
+REG_FIDDLE(Matrix_mapHomogeneousPoints, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint3 src[] = {{3, 3, 1}, {8, 2, 2}, {5, 0, 4}, {0, 1, 3},
+                      {3, 7, 1}, {8, 6, 2}, {5, 4, 4}, {0, 5, 3}};
+    int lines[] = { 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 };
+    constexpr int count = SK_ARRAY_COUNT(src);
+    auto debugster = [=](SkPoint3 src[]) -> void {
+    for (size_t i = 0; i < SK_ARRAY_COUNT(lines); i += 2) {
+        const SkPoint3& s = src[lines[i]];
+        const SkPoint3& e = src[lines[i + 1]];
+        SkPaint paint;
+        paint.setARGB(77, 23, 99, 154);
+        canvas->drawLine(s.fX / s.fZ, s.fY / s.fZ, e.fX / e.fZ, e.fY / e.fZ, paint);
+    }
+    };
+    canvas->save();
+    canvas->translate(5, 5);
+    canvas->scale(15, 15);
+    debugster(src);
+    canvas->restore();
+    canvas->translate(128, 128);
+    SkMatrix matrix;
+    matrix.setAll(15, 0, 0, 0, 15, 0, -0.08f, 0.04f, 1);
+    matrix.mapHomogeneousPoints(src, src, count);
+    debugster(src);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_mapPoints.cpp b/docs/examples/Matrix_mapPoints.cpp
new file mode 100644
index 0000000..c38d271
--- /dev/null
+++ b/docs/examples/Matrix_mapPoints.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f99dcb00296d0c56b6c0e178e94b3534
+REG_FIDDLE(Matrix_mapPoints, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.reset();
+    const int count = 4;
+    SkPoint src[count];
+    matrix.mapRectToQuad(src, {40, 70, 180, 220} );
+    SkPaint paint;
+    paint.setARGB(77, 23, 99, 154);
+    for (int i = 0; i < 5; ++i) {
+        SkPoint dst[count];
+        matrix.mapPoints(dst, src, count);
+        canvas->drawPoints(SkCanvas::kPolygon_PointMode, count, dst, paint);
+        matrix.preRotate(35, 128, 128);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_mapPoints_2.cpp b/docs/examples/Matrix_mapPoints_2.cpp
new file mode 100644
index 0000000..a295976
--- /dev/null
+++ b/docs/examples/Matrix_mapPoints_2.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=428ca171ae3bd0d3f992458ac598b97b
+REG_FIDDLE(Matrix_mapPoints_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setRotate(35, 128, 128);
+    const int count = 4;
+    SkPoint pts[count];
+    matrix.mapRectToQuad(pts, {40, 70, 180, 220} );
+    SkPaint paint;
+    paint.setARGB(77, 23, 99, 154);
+    for (int i = 0; i < 5; ++i) {
+        canvas->drawPoints(SkCanvas::kPolygon_PointMode, count, pts, paint);
+        matrix.mapPoints(pts, count);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_mapRadius.cpp b/docs/examples/Matrix_mapRadius.cpp
new file mode 100644
index 0000000..06904493
--- /dev/null
+++ b/docs/examples/Matrix_mapRadius.cpp
@@ -0,0 +1,41 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6d6f2082fcf59d9f02bfb1758b87db69
+REG_FIDDLE(Matrix_mapRadius, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkMatrix matrix;
+    const SkPoint center = {108, 93};
+    matrix.setScale(2, .5f, center.fX, center.fY);
+    matrix.postRotate(45, center.fX, center.fY);
+    const SkScalar circleRadius = 50;
+    SkScalar mappedRadius = matrix.mapRadius(circleRadius);
+    SkVector minorAxis, majorAxis;
+    matrix.mapVector(0, circleRadius, &minorAxis);
+    matrix.mapVector(circleRadius, 0, &majorAxis);
+    SkString mappedArea;
+    mappedArea.printf("area = %g", mappedRadius * mappedRadius);
+    canvas->drawString(mappedArea, 145, 250, paint);
+    canvas->drawString("mappedRadius", center.fX + mappedRadius + 3, center.fY, paint);
+    paint.setColor(SK_ColorRED);
+    SkString axArea;
+    axArea.printf("area = %g", majorAxis.length() * minorAxis.length());
+    paint.setStyle(SkPaint::kFill_Style);
+    canvas->drawString(axArea, 15, 250, paint);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawRect({10, 200, 10 + majorAxis.length(), 200 + minorAxis.length()}, paint);
+    paint.setColor(SK_ColorBLACK);
+    canvas->drawLine(center.fX, center.fY, center.fX + mappedRadius, center.fY, paint);
+    canvas->drawLine(center.fX, center.fY, center.fX, center.fY + mappedRadius, paint);
+    canvas->drawRect({140, 180, 140 + mappedRadius, 180 + mappedRadius}, paint);
+    canvas->concat(matrix);
+    canvas->drawCircle(center.fX, center.fY, circleRadius, paint);
+    paint.setColor(SK_ColorRED);
+    canvas->drawLine(center.fX, center.fY, center.fX + circleRadius, center.fY, paint);
+    canvas->drawLine(center.fX, center.fY, center.fX, center.fY + circleRadius, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_mapRect.cpp b/docs/examples/Matrix_mapRect.cpp
new file mode 100644
index 0000000..3f27d98
--- /dev/null
+++ b/docs/examples/Matrix_mapRect.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=dbcf928b035a31ca69c99392e2e2cca9
+REG_FIDDLE(Matrix_mapRect, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkMatrix matrix;
+    matrix.setRotate(45, 128, 128);
+    SkRect rotatedBounds, bounds = {40, 50, 190, 200};
+    matrix.mapRect(&rotatedBounds, bounds );
+    paint.setColor(SK_ColorGRAY);
+    canvas->drawRect(rotatedBounds, paint);
+    canvas->concat(matrix);
+    paint.setColor(SK_ColorRED);
+    canvas->drawRect(bounds, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_mapRectScaleTranslate.cpp b/docs/examples/Matrix_mapRectScaleTranslate.cpp
new file mode 100644
index 0000000..685af6c
--- /dev/null
+++ b/docs/examples/Matrix_mapRectScaleTranslate.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=62bc26989c2b4c2a54d516596a71dd97
+REG_FIDDLE(Matrix_mapRectScaleTranslate, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkMatrix matrix;
+    SkRect rect = {100, 50, 150, 180};
+    matrix.setScale(2, .5f, rect.centerX(), rect.centerY());
+    SkRect rotated;
+    matrix.mapRectScaleTranslate(&rotated, rect);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawRect(rect, paint);
+    paint.setColor(SK_ColorRED);
+    canvas->drawRect(rotated, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_mapRectToQuad.cpp b/docs/examples/Matrix_mapRectToQuad.cpp
new file mode 100644
index 0000000..b388cfb
--- /dev/null
+++ b/docs/examples/Matrix_mapRectToQuad.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c69cd2a590b5733c3cbc92cb9ceed3f5
+REG_FIDDLE(Matrix_mapRectToQuad, 256, 192, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkMatrix matrix;
+    matrix.setRotate(60, 128, 128);
+    SkRect rect = {50, 50, 150, 150};
+    SkPoint pts[4];
+    matrix.mapRectToQuad(pts, rect);
+    for (int i = 0; i < 4; ++i) {
+        canvas->drawCircle(pts[i].fX, pts[i].fY, 3, paint);
+    }
+    canvas->concat(matrix);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawRect(rect, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_mapRect_2.cpp b/docs/examples/Matrix_mapRect_2.cpp
new file mode 100644
index 0000000..c713935
--- /dev/null
+++ b/docs/examples/Matrix_mapRect_2.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5fafd0bd23d1ed37425b970b4a3c6cc9
+REG_FIDDLE(Matrix_mapRect_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkMatrix matrix;
+    matrix.setRotate(45, 128, 128);
+    SkRect bounds = {40, 50, 190, 200};
+    matrix.mapRect(&bounds);
+    paint.setColor(SK_ColorGRAY);
+    canvas->drawRect(bounds, paint);
+    canvas->concat(matrix);
+    paint.setColor(SK_ColorRED);
+    canvas->drawRect({40, 50, 190, 200}, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_mapRect_3.cpp b/docs/examples/Matrix_mapRect_3.cpp
new file mode 100644
index 0000000..57d5f25
--- /dev/null
+++ b/docs/examples/Matrix_mapRect_3.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3b7b1f884437ab450f986234e4aec27f
+REG_FIDDLE(Matrix_mapRect_3, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect{110, 50, 180, 100};
+    SkMatrix matrix;
+    matrix.setRotate(50, 28, 28);
+    SkRect mapped = matrix.mapRect(rect);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawRect(rect, paint);
+    canvas->drawRect(mapped, paint);
+    canvas->concat(matrix);
+    canvas->drawRect(rect, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_mapVector.cpp b/docs/examples/Matrix_mapVector.cpp
new file mode 100644
index 0000000..2425d64
--- /dev/null
+++ b/docs/examples/Matrix_mapVector.cpp
@@ -0,0 +1,24 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=aed143fc6cd0bce4ed029b98d1e61f2d
+REG_FIDDLE(Matrix_mapVector, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setColor(SK_ColorGREEN);
+    paint.setAntiAlias(true);
+    paint.setTextSize(48);
+    SkMatrix matrix;
+    matrix.setRotate(90);
+    SkVector offset = { 7, 7 };
+    for (int i = 0; i < 4; ++i) {
+        paint.setImageFilter(SkDropShadowImageFilter::Make(offset.fX, offset.fY, 3, 3,
+              SK_ColorBLUE, SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode, nullptr));
+        matrix.mapVector(offset.fX, offset.fY, &offset);
+        canvas->translate(0, 60);
+        canvas->drawString("Text", 50, 0, paint);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_mapVector_2.cpp b/docs/examples/Matrix_mapVector_2.cpp
new file mode 100644
index 0000000..1ca8e7b
--- /dev/null
+++ b/docs/examples/Matrix_mapVector_2.cpp
@@ -0,0 +1,24 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8bf1518db3f369696cd3065b541a8bd7
+REG_FIDDLE(Matrix_mapVector_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setColor(SK_ColorGREEN);
+    paint.setAntiAlias(true);
+    paint.setTextSize(48);
+    SkMatrix matrix;
+    matrix.setRotate(90);
+    SkVector offset = { 7, 7 };
+    for (int i = 0; i < 4; ++i) {
+        paint.setImageFilter(SkDropShadowImageFilter::Make(offset.fX, offset.fY, 3, 3,
+              SK_ColorBLUE, SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode, nullptr));
+        offset = matrix.mapVector(offset.fX, offset.fY);
+        canvas->translate(0, 60);
+        canvas->drawString("Text", 50, 0, paint);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_mapVectors.cpp b/docs/examples/Matrix_mapVectors.cpp
new file mode 100644
index 0000000..78ba1ce
--- /dev/null
+++ b/docs/examples/Matrix_mapVectors.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=918a9778c3d7d5cb306692784399f6dc
+REG_FIDDLE(Matrix_mapVectors, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkMatrix matrix;
+    matrix.reset();
+    const SkVector radii[] = {{8, 4}, {9, 1}, {6, 2}, {7, 3}};
+    for (int i = 0; i < 4; ++i) {
+        SkVector rScaled[4];
+        matrix.preScale(1.5f, 2.f);
+        matrix.mapVectors(rScaled, radii, SK_ARRAY_COUNT(radii));
+        SkRRect rrect;
+        rrect.setRectRadii({20, 20, 180, 70}, rScaled);
+        canvas->drawRRect(rrect, paint);
+        canvas->translate(0, 60);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_mapVectors_2.cpp b/docs/examples/Matrix_mapVectors_2.cpp
new file mode 100644
index 0000000..503cd46
--- /dev/null
+++ b/docs/examples/Matrix_mapVectors_2.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5754501a00a1323e76353fb53153e939
+REG_FIDDLE(Matrix_mapVectors_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkMatrix matrix;
+    matrix.setScale(2, 3);
+    SkVector radii[] = {{7, 7}, {3, 3}, {2, 2}, {4, 0}};
+    for (int i = 0; i < 4; ++i) {
+        SkRRect rrect;
+        rrect.setRectRadii({20, 20, 180, 70}, radii);
+        canvas->drawRRect(rrect, paint);
+        canvas->translate(0, 60);
+        matrix.mapVectors(radii, SK_ARRAY_COUNT(radii));
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_mapXY.cpp b/docs/examples/Matrix_mapXY.cpp
new file mode 100644
index 0000000..76097f4
--- /dev/null
+++ b/docs/examples/Matrix_mapXY.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9e50185d502dc6903783679a84106089
+REG_FIDDLE(Matrix_mapXY, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkMatrix matrix;
+    matrix.setRotate(60, 128, 128);
+    SkPoint lines[] = {{50, 50}, {150, 50}, {150, 150}};
+    for (size_t i = 0; i < SK_ARRAY_COUNT(lines); ++i) {
+        SkPoint pt;
+        matrix.mapXY(lines[i].fX, lines[i].fY, &pt);
+        canvas->drawCircle(pt.fX, pt.fY, 3, paint);
+    }
+    canvas->concat(matrix);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(lines), lines, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_mapXY_2.cpp b/docs/examples/Matrix_mapXY_2.cpp
new file mode 100644
index 0000000..2173bd4
--- /dev/null
+++ b/docs/examples/Matrix_mapXY_2.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b1ead09c67a177ab8eace12b061610a7
+REG_FIDDLE(Matrix_mapXY_2, 256, 256, false, 4) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {30, 206}};
+    SkRect::Make(source.bounds()).toQuad(bitmapBounds);
+    matrix.setPolyToPoly(bitmapBounds, perspect, 4);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStrokeWidth(3);
+    for (int x : { 0, source.width() } ) {
+        for (int y : { 0, source.height() } ) {
+            canvas->drawPoint(matrix.mapXY(x, y), paint);
+        }
+    }
+    canvas->concat(matrix);
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_notequal_operator.cpp b/docs/examples/Matrix_notequal_operator.cpp
new file mode 100644
index 0000000..f242bd1
--- /dev/null
+++ b/docs/examples/Matrix_notequal_operator.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=088ab41f877599f980a99523749b0afd
+REG_FIDDLE(Matrix_notequal_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkMatrix& a, const SkMatrix& b) -> void {
+        SkDebugf("%s: a %c= b a.cheapEqualTo(b): %s\n", prefix,
+                 a != b ? '!' : '=', a.cheapEqualTo(b) ? "true" : "false");
+    };
+    SkMatrix a, b;
+    a.setAll(1, 0, 0,   0, 1, 0,  1, 0, 1);
+    if (a.invert(&b)) {
+        debugster("identity", a, b);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_postConcat.cpp b/docs/examples/Matrix_postConcat.cpp
new file mode 100644
index 0000000..efb8f9a
--- /dev/null
+++ b/docs/examples/Matrix_postConcat.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e4226c55d9bdbc119264bd372b2b9835
+REG_FIDDLE(Matrix_postConcat, 256, 64, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
+    SkRect::Make(source.bounds()).toQuad(bitmapBounds);
+    matrix.setPolyToPoly(bitmapBounds, perspect, 4);
+    matrix.postConcat(matrix);
+    canvas->concat(matrix);
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_postRotate.cpp b/docs/examples/Matrix_postRotate.cpp
new file mode 100644
index 0000000..e3a75c8
--- /dev/null
+++ b/docs/examples/Matrix_postRotate.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e09194ee48a81e7b375ade473d340f0d
+REG_FIDDLE(Matrix_postRotate, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
+    SkRect::Make(source.bounds()).toQuad(bitmapBounds);
+    matrix.setPolyToPoly(bitmapBounds, perspect, 4);
+    matrix.postRotate(45, source.width() / 2, source.height() / 2);
+    canvas->concat(matrix);
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_postRotate_2.cpp b/docs/examples/Matrix_postRotate_2.cpp
new file mode 100644
index 0000000..0c30702
--- /dev/null
+++ b/docs/examples/Matrix_postRotate_2.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=52e4c53e26971af5576b30de60fa70c2
+REG_FIDDLE(Matrix_postRotate_2, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
+    SkRect::Make(source.bounds()).toQuad(bitmapBounds);
+    matrix.setPolyToPoly(bitmapBounds, perspect, 4);
+    matrix.postRotate(45);
+    canvas->concat(matrix);
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_postScale.cpp b/docs/examples/Matrix_postScale.cpp
new file mode 100644
index 0000000..9ade784
--- /dev/null
+++ b/docs/examples/Matrix_postScale.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ed3aa18ba0ea95c85cc49aa3829fe384
+REG_FIDDLE(Matrix_postScale, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
+    SkRect::Make(source.bounds()).toQuad(bitmapBounds);
+    matrix.setPolyToPoly(bitmapBounds, perspect, 4);
+    matrix.postScale(.75f, 1.5f, source.width() / 2, source.height() / 2);
+    canvas->concat(matrix);
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_postScale_2.cpp b/docs/examples/Matrix_postScale_2.cpp
new file mode 100644
index 0000000..70b6054
--- /dev/null
+++ b/docs/examples/Matrix_postScale_2.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1931017698766a67d3a26423453b8095
+REG_FIDDLE(Matrix_postScale_2, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
+    SkRect::Make(source.bounds()).toQuad(bitmapBounds);
+    matrix.setPolyToPoly(bitmapBounds, perspect, 4);
+    matrix.postScale(.75f, 1.5f);
+    canvas->concat(matrix);
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_postSkew.cpp b/docs/examples/Matrix_postSkew.cpp
new file mode 100644
index 0000000..077f650
--- /dev/null
+++ b/docs/examples/Matrix_postSkew.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8c34ae3a2b7e2742bb969819737365ec
+REG_FIDDLE(Matrix_postSkew, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
+    SkRect::Make(source.bounds()).toQuad(bitmapBounds);
+    matrix.setPolyToPoly(bitmapBounds, perspect, 4);
+    matrix.postSkew(.5f, 0, source.width() / 2, source.height() / 2);
+    canvas->concat(matrix);
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_postSkew_2.cpp b/docs/examples/Matrix_postSkew_2.cpp
new file mode 100644
index 0000000..149d505
--- /dev/null
+++ b/docs/examples/Matrix_postSkew_2.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3aa2603225dff72ac53dd359f897f494
+REG_FIDDLE(Matrix_postSkew_2, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
+    SkRect::Make(source.bounds()).toQuad(bitmapBounds);
+    matrix.setPolyToPoly(bitmapBounds, perspect, 4);
+    matrix.postSkew(.5f, 0);
+    canvas->concat(matrix);
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_postTranslate.cpp b/docs/examples/Matrix_postTranslate.cpp
new file mode 100644
index 0000000..a26177b
--- /dev/null
+++ b/docs/examples/Matrix_postTranslate.cpp
@@ -0,0 +1,28 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f5144ef4bd7cea294fad2f756ed335af
+REG_FIDDLE(Matrix_postTranslate, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkRect rect = {20, 20, 100, 100};
+    for (int i = 0; i < 2; ++i ) {
+        SkMatrix matrix;
+        i == 0 ? matrix.reset(): matrix.setRotate(25, rect.centerX(), 320);
+        {
+            SkAutoCanvasRestore acr(canvas, true);
+            canvas->concat(matrix);
+            paint.setColor(SK_ColorGRAY);
+            canvas->drawRect(rect, paint);
+        }
+        paint.setColor(SK_ColorRED);
+        for (int j = 0; j < 2; ++j ) {
+            SkAutoCanvasRestore acr(canvas, true);
+            matrix.postTranslate(40, 40);
+            canvas->concat(matrix);
+            canvas->drawCircle(0, 0, 3, paint);
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_preConcat.cpp b/docs/examples/Matrix_preConcat.cpp
new file mode 100644
index 0000000..dada66d
--- /dev/null
+++ b/docs/examples/Matrix_preConcat.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b07e62298e7b0ab5683db199faffceb2
+REG_FIDDLE(Matrix_preConcat, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix, matrix2;
+    SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
+    SkRect::Make(source.bounds()).toQuad(bitmapBounds);
+    matrix.setPolyToPoly(bitmapBounds, perspect, 4);
+    matrix2.setPolyToPoly(perspect, bitmapBounds, 4);
+    matrix.preConcat(matrix2);
+    canvas->concat(matrix);
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_preRotate.cpp b/docs/examples/Matrix_preRotate.cpp
new file mode 100644
index 0000000..f456313
--- /dev/null
+++ b/docs/examples/Matrix_preRotate.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a70bb18d67c06a20ab514e7a47924e5a
+REG_FIDDLE(Matrix_preRotate, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
+    SkRect::Make(source.bounds()).toQuad(bitmapBounds);
+    matrix.setPolyToPoly(bitmapBounds, perspect, 4);
+    matrix.preRotate(45, source.width() / 2, source.height() / 2);
+    canvas->concat(matrix);
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_preRotate_2.cpp b/docs/examples/Matrix_preRotate_2.cpp
new file mode 100644
index 0000000..c134411
--- /dev/null
+++ b/docs/examples/Matrix_preRotate_2.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5acd49bd931c79a808dd6c7cc0e92f72
+REG_FIDDLE(Matrix_preRotate_2, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
+    SkRect::Make(source.bounds()).toQuad(bitmapBounds);
+    matrix.setPolyToPoly(bitmapBounds, perspect, 4);
+    matrix.preRotate(45);
+    canvas->concat(matrix);
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_preScale.cpp b/docs/examples/Matrix_preScale.cpp
new file mode 100644
index 0000000..ace6b20
--- /dev/null
+++ b/docs/examples/Matrix_preScale.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2531f8d1e05d7b6dc22f3efcd2fb84e4
+REG_FIDDLE(Matrix_preScale, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
+    SkRect::Make(source.bounds()).toQuad(bitmapBounds);
+    matrix.setPolyToPoly(bitmapBounds, perspect, 4);
+    matrix.preScale(.75f, 1.5f, source.width() / 2, source.height() / 2);
+    canvas->concat(matrix);
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_preScale_2.cpp b/docs/examples/Matrix_preScale_2.cpp
new file mode 100644
index 0000000..ec86d64
--- /dev/null
+++ b/docs/examples/Matrix_preScale_2.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3edbdea8e43d06086abf33ec4a9b415b
+REG_FIDDLE(Matrix_preScale_2, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
+    SkRect::Make(source.bounds()).toQuad(bitmapBounds);
+    matrix.setPolyToPoly(bitmapBounds, perspect, 4);
+    matrix.preScale(.75f, 1.5f);
+    canvas->concat(matrix);
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_preSkew.cpp b/docs/examples/Matrix_preSkew.cpp
new file mode 100644
index 0000000..1dd38f9
--- /dev/null
+++ b/docs/examples/Matrix_preSkew.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=199a18ad61d702664ce6df1d7037aa48
+REG_FIDDLE(Matrix_preSkew, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
+    SkRect::Make(source.bounds()).toQuad(bitmapBounds);
+    matrix.setPolyToPoly(bitmapBounds, perspect, 4);
+    matrix.preSkew(.5f, 0, source.width() / 2, source.height() / 2);
+    canvas->concat(matrix);
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_preSkew_2.cpp b/docs/examples/Matrix_preSkew_2.cpp
new file mode 100644
index 0000000..1987ea2
--- /dev/null
+++ b/docs/examples/Matrix_preSkew_2.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e100c543869fe8fd516ba69de79444ba
+REG_FIDDLE(Matrix_preSkew_2, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
+    SkRect::Make(source.bounds()).toQuad(bitmapBounds);
+    matrix.setPolyToPoly(bitmapBounds, perspect, 4);
+    matrix.preSkew(.5f, 0);
+    canvas->concat(matrix);
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_preTranslate.cpp b/docs/examples/Matrix_preTranslate.cpp
new file mode 100644
index 0000000..2d6902e
--- /dev/null
+++ b/docs/examples/Matrix_preTranslate.cpp
@@ -0,0 +1,28 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f75a9b629aa6c51ed888f8799b5ba5f7
+REG_FIDDLE(Matrix_preTranslate, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkRect rect = {20, 20, 100, 100};
+    for (int i = 0; i < 2; ++i ) {
+        SkMatrix matrix;
+        i == 0 ? matrix.reset(): matrix.setRotate(25, rect.centerX(), 320);
+        {
+            SkAutoCanvasRestore acr(canvas, true);
+            canvas->concat(matrix);
+            paint.setColor(SK_ColorGRAY);
+            canvas->drawRect(rect, paint);
+        }
+        paint.setColor(SK_ColorRED);
+        for (int j = 0; j < 2; ++j ) {
+            SkAutoCanvasRestore acr(canvas, true);
+            matrix.preTranslate(40, 40);
+            canvas->concat(matrix);
+            canvas->drawCircle(0, 0, 3, paint);
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_preservesAxisAlignment.cpp b/docs/examples/Matrix_preservesAxisAlignment.cpp
new file mode 100644
index 0000000..a31fd79
--- /dev/null
+++ b/docs/examples/Matrix_preservesAxisAlignment.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7a234c96608fb7cb8135b9940b0b15f7
+REG_FIDDLE(Matrix_preservesAxisAlignment, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    for (SkScalar angle: { 0, 90, 180, 270 } ) {
+        matrix.setRotate(angle);
+        SkDebugf("preservesAxisAlignment: %s\n", matrix.preservesAxisAlignment() ? "true" : "false");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_preservesRightAngles.cpp b/docs/examples/Matrix_preservesRightAngles.cpp
new file mode 100644
index 0000000..0c71d38
--- /dev/null
+++ b/docs/examples/Matrix_preservesRightAngles.cpp
@@ -0,0 +1,28 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b9becf0dc24a9f00726e24a81fb72f16
+REG_FIDDLE(Matrix_preservesRightAngles, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint p;
+    p.setAntiAlias(true);
+    SkMatrix m;
+    int pos = 0;
+    for (SkScalar sx : { 1, 2 } ) {
+        for (SkScalar kx : { 0, 1 } ) {
+            m.setAll(sx, kx, 16,    0, 1, 32,   0, 0, 1);
+            bool isSimilarity = m.isSimilarity();
+            bool preservesRightAngles = m.preservesRightAngles();
+            SkString str;
+            str.printf("sx: %g kx: %g %s %s", sx, kx, isSimilarity ? "sim" : "",
+                        preservesRightAngles ? "right" : "");
+            SkAutoCanvasRestore autoRestore(canvas, true);
+            canvas->concat(m);
+            canvas->drawString(str, 0, pos, p);
+            pos += 20;
+        }
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_rectStaysRect.cpp b/docs/examples/Matrix_rectStaysRect.cpp
new file mode 100644
index 0000000..b558738
--- /dev/null
+++ b/docs/examples/Matrix_rectStaysRect.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ce5319c036c9b5086da8a0009fe409f8
+REG_FIDDLE(Matrix_rectStaysRect, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    for (SkScalar angle: { 0, 90, 180, 270 } ) {
+        matrix.setRotate(angle);
+        SkDebugf("rectStaysRect: %s\n", matrix.rectStaysRect() ? "true" : "false");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_reset.cpp b/docs/examples/Matrix_reset.cpp
new file mode 100644
index 0000000..2ef3676
--- /dev/null
+++ b/docs/examples/Matrix_reset.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ca94f7922bc37ef03bbc51ad70536fcf
+REG_FIDDLE(Matrix_reset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix m;
+    m.reset();
+    SkDebugf("m.isIdentity(): %s\n", m.isIdentity() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_set.cpp b/docs/examples/Matrix_set.cpp
new file mode 100644
index 0000000..aa4bb2a
--- /dev/null
+++ b/docs/examples/Matrix_set.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1d400a92ca826cc89bcb88ea051f28c8
+REG_FIDDLE(Matrix_set, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setIdentity();
+    SkDebugf("with identity matrix: x = %g\n", matrix.mapXY(24, 42).fX);
+    matrix.set(SkMatrix::kMSkewX, 0);
+    SkDebugf("after skew x mod:     x = %g\n", matrix.mapXY(24, 42).fX);
+    matrix.set(SkMatrix::kMSkewX, 1);
+    SkDebugf("after 2nd skew x mod: x = %g\n", matrix.mapXY(24, 42).fX);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_set9.cpp b/docs/examples/Matrix_set9.cpp
new file mode 100644
index 0000000..b020a7c
--- /dev/null
+++ b/docs/examples/Matrix_set9.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ec5de0d23e5fe28ba7628625d1402e85
+REG_FIDDLE(Matrix_set9, 256, 256, false, 4) {
+void draw(SkCanvas* canvas) {
+    SkMatrix m;
+    SkScalar buffer[9] = {4, 0, 3,    0, 5, 4,     0, 0, 1};
+    m.set9(buffer);
+    canvas->concat(m);
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_setAffine.cpp b/docs/examples/Matrix_setAffine.cpp
new file mode 100644
index 0000000..aaa50e1
--- /dev/null
+++ b/docs/examples/Matrix_setAffine.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f5b6d371c4d65e5b5ac6eebdd4b237d8
+REG_FIDDLE(Matrix_setAffine, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setAll(2, 3, 4, 5, 6, 7, 0, 0, 1);
+    SkScalar affine[6];
+    if (matrix.asAffine(affine)) {
+        const char* names[] = { "ScaleX", "SkewY", "SkewX", "ScaleY", "TransX", "TransY" };
+        for (int i = 0; i < 6; ++i) {
+            SkDebugf("%s: %g ", names[i], affine[i]);
+        }
+        SkDebugf("\n");
+        matrix.reset();
+        matrix.setAffine(affine);
+        matrix.dump();
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_setAll.cpp b/docs/examples/Matrix_setAll.cpp
new file mode 100644
index 0000000..46559b3
--- /dev/null
+++ b/docs/examples/Matrix_setAll.cpp
@@ -0,0 +1,22 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=95ccfc2a89ce593e6b7a9f992a844bc0
+REG_FIDDLE(Matrix_setAll, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint p;
+    p.setAntiAlias(true);
+    p.setTextSize(64);
+    SkMatrix m;
+    for (SkScalar sx : { -1, 1 } ) {
+        for (SkScalar sy : { -1, 1 } ) {
+            SkAutoCanvasRestore autoRestore(canvas, true);
+            m.setAll(sx, 1, 128,    0, sy, 64,   0, 0, 1);
+            canvas->concat(m);
+            canvas->drawString("K", 0, 0, p);
+        }
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_setConcat.cpp b/docs/examples/Matrix_setConcat.cpp
new file mode 100644
index 0000000..779b60d
--- /dev/null
+++ b/docs/examples/Matrix_setConcat.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0381a10ac69bdefdf9d15b47cbb9fefe
+REG_FIDDLE(Matrix_setConcat, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix, matrix2;
+    SkPoint bitmapBounds[4], perspect[4] = {{50, 10}, {180, 40}, {236, 176}, {10, 206}};
+    SkRect::Make(source.bounds()).toQuad(bitmapBounds);
+    matrix.setPolyToPoly(bitmapBounds, perspect, 4);
+    matrix2.setPolyToPoly(perspect, bitmapBounds, 4);
+    matrix.setConcat(matrix, matrix2);
+    canvas->concat(matrix);
+    canvas->drawBitmap(source, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_setIdentity.cpp b/docs/examples/Matrix_setIdentity.cpp
new file mode 100644
index 0000000..0c69d88
--- /dev/null
+++ b/docs/examples/Matrix_setIdentity.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3979c865bb482e6ef1fafc71e56bbb91
+REG_FIDDLE(Matrix_setIdentity, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix m;
+    m.setIdentity();
+    SkDebugf("m.isIdentity(): %s\n", m.isIdentity() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_setPerspX.cpp b/docs/examples/Matrix_setPerspX.cpp
new file mode 100644
index 0000000..f1b352f
--- /dev/null
+++ b/docs/examples/Matrix_setPerspX.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=830a9e4e4bb93d25afd83b2fea63929e
+REG_FIDDLE(Matrix_setPerspX, 256, 256, false, 4) {
+void draw(SkCanvas* canvas) {
+    for (SkScalar perspX : { -.003f, 0.f, .003f, .012f } ) {
+        SkMatrix matrix;
+        matrix.setIdentity();
+        matrix.setPerspX(perspX);
+        canvas->save();
+        canvas->concat(matrix);
+        canvas->drawBitmap(source, 0, 0);
+        canvas->restore();
+        canvas->translate(64, 64);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_setPerspY.cpp b/docs/examples/Matrix_setPerspY.cpp
new file mode 100644
index 0000000..b0d74c6
--- /dev/null
+++ b/docs/examples/Matrix_setPerspY.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=aeb258b7922c1a11b698b00f562182ec
+REG_FIDDLE(Matrix_setPerspY, 256, 256, false, 4) {
+void draw(SkCanvas* canvas) {
+    for (SkScalar perspX : { -.003f, 0.f, .003f, .012f } ) {
+        SkMatrix matrix;
+        matrix.setIdentity();
+        matrix.setPerspY(perspX);
+        canvas->save();
+        canvas->concat(matrix);
+        canvas->drawBitmap(source, 0, 0);
+        canvas->restore();
+        canvas->translate(64, 64);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_setPolyToPoly.cpp b/docs/examples/Matrix_setPolyToPoly.cpp
new file mode 100644
index 0000000..66f28db
--- /dev/null
+++ b/docs/examples/Matrix_setPolyToPoly.cpp
@@ -0,0 +1,34 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c851d1313e8909aaea4f0591699fdb7b
+REG_FIDDLE(Matrix_setPolyToPoly, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    const SkPoint src[] = { { 0, 0}, {30,   0}, {30, -30}, { 0, -30} };
+    const SkPoint dst[] = { {50, 0}, {80, -10}, {90, -30}, {60, -40} };
+    SkPaint blackPaint;
+    blackPaint.setAntiAlias(true);
+    blackPaint.setTextSize(42);
+    SkPaint redPaint = blackPaint;
+    redPaint.setColor(SK_ColorRED);
+    for (int count : { 1, 2, 3, 4 } ) {
+        canvas->translate(35, 55);
+        for (int index = 0; index < count; ++index) {
+            canvas->drawCircle(src[index], 3, blackPaint);
+            canvas->drawCircle(dst[index], 3, blackPaint);
+            if (index > 0) {
+                canvas->drawLine(src[index], src[index - 1], blackPaint);
+                canvas->drawLine(dst[index], dst[index - 1], blackPaint);
+            }
+        }
+        SkMatrix matrix;
+        matrix.setPolyToPoly(src, dst, count);
+        canvas->drawString("A", src[0].fX, src[0].fY, redPaint);
+        SkAutoCanvasRestore acr(canvas, true);
+        canvas->concat(matrix);
+        canvas->drawString("A", src[0].fX, src[0].fY, redPaint);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_setRSXform.cpp b/docs/examples/Matrix_setRSXform.cpp
new file mode 100644
index 0000000..2168953
--- /dev/null
+++ b/docs/examples/Matrix_setRSXform.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c3f5faddca466f78278b32b88fd5f5eb
+REG_FIDDLE(Matrix_setRSXform, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setColor(SK_ColorGRAY);
+    paint.setAntiAlias(true);
+    SkRect rect = {20, 20, 100, 100};
+    canvas->drawRect(rect, paint);
+    paint.setColor(SK_ColorRED);
+    SkMatrix matrix;
+    matrix.setRSXform(SkRSXform::Make(.85f, .25f, rect.centerX(), rect.centerY()));
+    canvas->concat(matrix);
+    canvas->translate(-rect.centerX(), -rect.centerY());
+    canvas->drawRect(rect, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_setRectToRect.cpp b/docs/examples/Matrix_setRectToRect.cpp
new file mode 100644
index 0000000..0fcb13a
--- /dev/null
+++ b/docs/examples/Matrix_setRectToRect.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=69cdea599dcaaec35efcb24403f4287b
+REG_FIDDLE(Matrix_setRectToRect, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const SkRect srcs[] = { {0, 0, 0, 0}, {1, 2, 3, 4} };
+    const SkRect dsts[] = { {0, 0, 0, 0}, {5, 6, 8, 9} };
+    for (auto src : srcs) {
+        for (auto dst : dsts) {
+             SkMatrix matrix;
+             matrix.setAll(-1, -1, -1, -1, -1, -1, -1, -1, -1);
+             bool success = matrix.setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
+             SkDebugf("src: %g, %g, %g, %g  dst: %g, %g, %g, %g  success: %s\n",
+                      src.fLeft, src.fTop, src.fRight, src.fBottom,
+                      dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, success ? "true" : "false");
+             matrix.dump();
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_setRotate.cpp b/docs/examples/Matrix_setRotate.cpp
new file mode 100644
index 0000000..c5456f0
--- /dev/null
+++ b/docs/examples/Matrix_setRotate.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8c28db3add9cd0177225088f6df6bbb5
+REG_FIDDLE(Matrix_setRotate, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setColor(SK_ColorGRAY);
+    paint.setAntiAlias(true);
+    SkRect rect = {20, 20, 100, 100};
+    canvas->drawRect(rect, paint);
+    paint.setColor(SK_ColorRED);
+    SkMatrix matrix;
+    matrix.setRotate(25, rect.centerX(), rect.centerY());
+    canvas->concat(matrix);
+    canvas->drawRect(rect, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_setRotate_2.cpp b/docs/examples/Matrix_setRotate_2.cpp
new file mode 100644
index 0000000..7c18b64
--- /dev/null
+++ b/docs/examples/Matrix_setRotate_2.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=93efb9d191bf1b9710c173513e014d6c
+REG_FIDDLE(Matrix_setRotate_2, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setColor(SK_ColorGRAY);
+    paint.setAntiAlias(true);
+    SkRect rect = {20, 20, 100, 100};
+    canvas->drawRect(rect, paint);
+    paint.setColor(SK_ColorRED);
+    SkMatrix matrix;
+    matrix.setRotate(25);
+    canvas->translate(rect.centerX(), rect.centerY());
+    canvas->concat(matrix);
+    canvas->translate(-rect.centerX(), -rect.centerY());
+    canvas->drawRect(rect, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_setScale.cpp b/docs/examples/Matrix_setScale.cpp
new file mode 100644
index 0000000..d01f269
--- /dev/null
+++ b/docs/examples/Matrix_setScale.cpp
@@ -0,0 +1,22 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4565a0792058178c88e0a129a87272d6
+REG_FIDDLE(Matrix_setScale, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint p;
+    p.setAntiAlias(true);
+    p.setTextSize(64);
+    SkMatrix m;
+    for (SkScalar sx : { -1, 1 } ) {
+        for (SkScalar sy : { -1, 1 } ) {
+            SkAutoCanvasRestore autoRestore(canvas, true);
+            m.setScale(sx, sy, 128, 64);
+            canvas->concat(m);
+            canvas->drawString("%", 128, 64, p);
+        }
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_setScaleTranslate.cpp b/docs/examples/Matrix_setScaleTranslate.cpp
new file mode 100644
index 0000000..ddb9f0c
--- /dev/null
+++ b/docs/examples/Matrix_setScaleTranslate.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=fed43797f13796529cb6731385d6f8f3
+REG_FIDDLE(Matrix_setScaleTranslate, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkMatrix matrix;
+    matrix.setScaleTranslate(1, 2, 3, 4);
+    matrix.dump();
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_setScaleX.cpp b/docs/examples/Matrix_setScaleX.cpp
new file mode 100644
index 0000000..00075fe
--- /dev/null
+++ b/docs/examples/Matrix_setScaleX.cpp
@@ -0,0 +1,19 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a39dfed98c3c3c3a56be9ad59fe4e21e
+REG_FIDDLE(Matrix_setScaleX, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(24);
+    canvas->drawString("normal", 12, 24, paint);
+    SkMatrix matrix;
+    matrix.setIdentity();
+    matrix.setScaleX(3);
+    canvas->concat(matrix);
+    canvas->drawString("x scale", 0, 48, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_setScaleY.cpp b/docs/examples/Matrix_setScaleY.cpp
new file mode 100644
index 0000000..ca03684
--- /dev/null
+++ b/docs/examples/Matrix_setScaleY.cpp
@@ -0,0 +1,19 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f040c6dd85a02e94eaca00d5c2832604
+REG_FIDDLE(Matrix_setScaleY, 256, 192, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(24);
+    canvas->drawString("normal", 12, 24, paint);
+    SkMatrix matrix;
+    matrix.setIdentity();
+    matrix.setScaleY(3);
+    canvas->concat(matrix);
+    canvas->drawString("y scale", 12, 48, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_setScale_2.cpp b/docs/examples/Matrix_setScale_2.cpp
new file mode 100644
index 0000000..cd49107
--- /dev/null
+++ b/docs/examples/Matrix_setScale_2.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1579d0cc109c26e69f66f73abd35fb0e
+REG_FIDDLE(Matrix_setScale_2, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint p;
+    p.setAntiAlias(true);
+    p.setTextSize(64);
+    SkMatrix m;
+    for (SkScalar sx : { -1, 1 } ) {
+        for (SkScalar sy : { -1, 1 } ) {
+            SkAutoCanvasRestore autoRestore(canvas, true);
+            m.setScale(sx, sy);
+            m.postTranslate(128, 64);
+            canvas->concat(m);
+            canvas->drawString("@", 0, 0, p);
+        }
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_setSinCos.cpp b/docs/examples/Matrix_setSinCos.cpp
new file mode 100644
index 0000000..67de59d
--- /dev/null
+++ b/docs/examples/Matrix_setSinCos.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=187e1d9228e2e4341ef820bd77b6fda9
+REG_FIDDLE(Matrix_setSinCos, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setColor(SK_ColorGRAY);
+    paint.setAntiAlias(true);
+    SkRect rect = {20, 20, 100, 100};
+    canvas->drawRect(rect, paint);
+    paint.setColor(SK_ColorRED);
+    SkMatrix matrix;
+    matrix.setSinCos(.25f, .85f, rect.centerX(), rect.centerY());
+    canvas->concat(matrix);
+    canvas->drawRect(rect, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_setSinCos_2.cpp b/docs/examples/Matrix_setSinCos_2.cpp
new file mode 100644
index 0000000..e5eea65
--- /dev/null
+++ b/docs/examples/Matrix_setSinCos_2.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e37a94a53c959951b059fcd624639ef6
+REG_FIDDLE(Matrix_setSinCos_2, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setColor(SK_ColorGRAY);
+    paint.setAntiAlias(true);
+    SkRect rect = {20, 20, 100, 100};
+    canvas->drawRect(rect, paint);
+    paint.setColor(SK_ColorRED);
+    SkMatrix matrix;
+    matrix.setSinCos(.25f, .85f);
+    matrix.postTranslate(rect.centerX(), rect.centerY());
+    canvas->concat(matrix);
+    canvas->translate(-rect.centerX(), -rect.centerY());
+    canvas->drawRect(rect, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Matrix_setSkew.cpp b/docs/examples/Matrix_setSkew.cpp
new file mode 100644
index 0000000..2942ab8
--- /dev/null
+++ b/docs/examples/Matrix_setSkew.cpp
@@ -0,0 +1,22 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=55e0431adc6c5b1987ebb8123cc10342
+REG_FIDDLE(Matrix_setSkew, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint p;
+    p.setAntiAlias(true);
+    p.setTextSize(48);
+    SkMatrix m;
+    for (SkScalar sx : { -1, 0, 1 } ) {
+        for (SkScalar sy : { -1, 0, 1 } ) {
+            SkAutoCanvasRestore autoRestore(canvas, true);
+            m.setSkew(sx, sy, 96 + 64 * sx, 128 + 48 * sy);
+            canvas->concat(m);
+            canvas->drawString("K", 96 + 64 * sx, 128 + 48 * sy, p);
+        }
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_setSkewX.cpp b/docs/examples/Matrix_setSkewX.cpp
new file mode 100644
index 0000000..ad540e8
--- /dev/null
+++ b/docs/examples/Matrix_setSkewX.cpp
@@ -0,0 +1,19 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c7177a6fbc1545be95a5ebca87e0cd0d
+REG_FIDDLE(Matrix_setSkewX, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(24);
+    canvas->drawString("normal", 12, 24, paint);
+    SkMatrix matrix;
+    matrix.setIdentity();
+    matrix.setSkewX(-.7f);
+    canvas->concat(matrix);
+    canvas->drawString("x skew", 36, 48, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_setSkewY.cpp b/docs/examples/Matrix_setSkewY.cpp
new file mode 100644
index 0000000..44d3822
--- /dev/null
+++ b/docs/examples/Matrix_setSkewY.cpp
@@ -0,0 +1,19 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b418d15df9829aefcc6aca93a37428bb
+REG_FIDDLE(Matrix_setSkewY, 256, 96, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(24);
+    canvas->drawString("normal", 12, 24, paint);
+    SkMatrix matrix;
+    matrix.setIdentity();
+    matrix.setSkewY(.3f);
+    canvas->concat(matrix);
+    canvas->drawString("y skew", 12, 48, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_setSkew_2.cpp b/docs/examples/Matrix_setSkew_2.cpp
new file mode 100644
index 0000000..146fd32
--- /dev/null
+++ b/docs/examples/Matrix_setSkew_2.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=05be7844e9afdd7b9bfc31c5423a70a2
+REG_FIDDLE(Matrix_setSkew_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint p;
+    p.setAntiAlias(true);
+    p.setTextSize(48);
+    SkMatrix m;
+    for (SkScalar sx : { -1, 0, 1 } ) {
+        for (SkScalar sy : { -1, 0, 1 } ) {
+            SkAutoCanvasRestore autoRestore(canvas, true);
+            m.setSkew(sx, sy);
+            m.postTranslate(96 + 64 * sx, 128 + 48 * sy);
+            canvas->concat(m);
+            canvas->drawString("K", 0, 0, p);
+        }
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_setTranslate.cpp b/docs/examples/Matrix_setTranslate.cpp
new file mode 100644
index 0000000..5a63ae0
--- /dev/null
+++ b/docs/examples/Matrix_setTranslate.cpp
@@ -0,0 +1,18 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=63ca62985741b1bccb5e8b9cf734874e
+REG_FIDDLE(Matrix_setTranslate, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(24);
+    canvas->drawString("normal", 8, 24, paint);
+    SkMatrix matrix;
+    matrix.setTranslate(96, 24);
+    canvas->concat(matrix);
+    canvas->drawString("translate", 8, 24, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_setTranslateX.cpp b/docs/examples/Matrix_setTranslateX.cpp
new file mode 100644
index 0000000..4b88683
--- /dev/null
+++ b/docs/examples/Matrix_setTranslateX.cpp
@@ -0,0 +1,19 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a18bc2e3607ac3a8e438bcb61fb13130
+REG_FIDDLE(Matrix_setTranslateX, 256, 48, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(24);
+    canvas->drawString("normal", 8, 24, paint);
+    SkMatrix matrix;
+    matrix.setIdentity();
+    matrix.setTranslateX(96);
+    canvas->concat(matrix);
+    canvas->drawString("x translate", 8, 24, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_setTranslateY.cpp b/docs/examples/Matrix_setTranslateY.cpp
new file mode 100644
index 0000000..f00035c
--- /dev/null
+++ b/docs/examples/Matrix_setTranslateY.cpp
@@ -0,0 +1,19 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=34e3c70a72b836abf7f4858d35eecc98
+REG_FIDDLE(Matrix_setTranslateY, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(24);
+    canvas->drawString("normal", 8, 24, paint);
+    SkMatrix matrix;
+    matrix.setIdentity();
+    matrix.setTranslateY(24);
+    canvas->concat(matrix);
+    canvas->drawString("y translate", 8, 24, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Matrix_setTranslate_2.cpp b/docs/examples/Matrix_setTranslate_2.cpp
new file mode 100644
index 0000000..3be2426
--- /dev/null
+++ b/docs/examples/Matrix_setTranslate_2.cpp
@@ -0,0 +1,18 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ccfc734aff2ddea0b097c83f5621de5e
+REG_FIDDLE(Matrix_setTranslate_2, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(24);
+    canvas->drawString("normal", 8, 24, paint);
+    SkMatrix matrix;
+    matrix.setTranslate({96, 24});
+    canvas->concat(matrix);
+    canvas->drawString("translate", 8, 24, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/MemberIndex.cpp b/docs/examples/MemberIndex.cpp
new file mode 100644
index 0000000..c75f2f4
--- /dev/null
+++ b/docs/examples/MemberIndex.cpp
@@ -0,0 +1,28 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3bbf75f4748420810aa2586e3c8548d9
+REG_FIDDLE(MemberIndex, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint black;
+    black.setAntiAlias(true);
+    black.setTextSize(48);
+    SkPaint gray = black;
+    gray.setColor(0xFF9f9f9f);
+    SkScalar offset[] = { 1.5f, 1.5f, 20,   1.5f, 1.5f, 20,   .03f, .01f, 2 };
+    for (int i : { SkMatrix::kMScaleX, SkMatrix::kMSkewX,  SkMatrix::kMTransX,
+                   SkMatrix::kMSkewY,  SkMatrix::kMScaleY, SkMatrix::kMTransY,
+                   SkMatrix::kMPersp0, SkMatrix::kMPersp1, SkMatrix::kMPersp2 } ) {
+        SkMatrix m;
+        m.setIdentity();
+        m.set(i, offset[i]);
+        SkAutoCanvasRestore autoRestore(canvas, true);
+        canvas->translate(22 + (i % 3) * 88, 44 + (i / 3) * 88);
+        canvas->drawString("&", 0, 0, gray);
+        canvas->concat(m);
+        canvas->drawString("&", 0, 0, black);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Miter_Limit.cpp b/docs/examples/Miter_Limit.cpp
new file mode 100644
index 0000000..88c1229
--- /dev/null
+++ b/docs/examples/Miter_Limit.cpp
@@ -0,0 +1,36 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5de2de0f00354e59074a9bb1a42d5a63
+REG_FIDDLE(Miter_Limit, 384, 170, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint pts[] = {{ 10, 50 }, { 110, 80 }, { 10, 110 }};
+    SkVector v[] = { pts[0] - pts[1], pts[2] - pts[1] };
+    SkScalar angle1 = SkScalarATan2(v[0].fY, v[0].fX);
+    SkScalar angle2 = SkScalarATan2(v[1].fY, v[1].fX);
+    const SkScalar strokeWidth = 20;
+    SkScalar miterLimit = 1 / SkScalarSin((angle2 - angle1) / 2);
+    SkScalar miterLength = strokeWidth * miterLimit;
+    SkPath path;
+    path.moveTo(pts[0]);
+    path.lineTo(pts[1]);
+    path.lineTo(pts[2]);
+    SkPaint paint;  // set to default kMiter_Join
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeMiter(miterLimit);
+    paint.setStrokeWidth(strokeWidth);
+    canvas->drawPath(path, paint);
+    paint.setStrokeWidth(1);
+    canvas->drawLine(pts[1].fX - miterLength / 2, pts[1].fY + 50,
+                     pts[1].fX + miterLength / 2, pts[1].fY + 50, paint);
+    canvas->translate(200, 0);
+    miterLimit *= 0.99f;
+    paint.setStrokeMiter(miterLimit);
+    paint.setStrokeWidth(strokeWidth);
+    canvas->drawPath(path, paint);
+    paint.setStrokeWidth(1);
+    canvas->drawLine(pts[1].fX - miterLength / 2, pts[1].fY + 50,
+                     pts[1].fX + miterLength / 2, pts[1].fY + 50, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Modulate.cpp b/docs/examples/Modulate.cpp
new file mode 100644
index 0000000..7290577
--- /dev/null
+++ b/docs/examples/Modulate.cpp
@@ -0,0 +1,29 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3fdac2b2f48bd227d2e74234c260bc8e
+REG_FIDDLE(Modulate, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    auto drawSquare = [=](int dx, int dy, SkBlendMode mode, const char* label) -> void {
+        const SkColor colors[] = { SK_ColorBLACK, SK_ColorWHITE };
+        const SkPoint horz[] = { { 0, 0 }, { 128, 0 } };
+        SkPaint paint;
+        paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
+                SkTileMode::kClamp));
+        paint.setBlendMode(mode);
+        canvas->translate(dx, dy);
+        canvas->drawRect({0, 0, 128, 128}, paint);
+        paint.setBlendMode(SkBlendMode::kXor);
+        SkFont font;
+        canvas->drawString(label, 40, 100, font, paint);
+    };
+    drawSquare(0, 0, SkBlendMode::kSrc, "destination");
+    drawSquare(128, 0, SkBlendMode::kSrc, "");
+    drawSquare(0, 128, SkBlendMode::kSrc, "");
+    canvas->translate(-128, -128);
+    canvas->rotate(90, 0, 128);
+    drawSquare(0, 0, SkBlendMode::kSrc, "source");
+    drawSquare(0, -128, SkBlendMode::kModulate, "modulate");
+    drawSquare(-128, 0, SkBlendMode::kMultiply, "multiply");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Multiply.cpp b/docs/examples/Multiply.cpp
new file mode 100644
index 0000000..5a9a1b6
--- /dev/null
+++ b/docs/examples/Multiply.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=eb29c896f008dfbef09e16b85114fc3a
+REG_FIDDLE(Multiply, 256, 256, false, 5) {
+void draw(SkCanvas* canvas) {
+    canvas->drawImage(image, 0, 0);
+    canvas->drawImage(image, 128, 0);
+    canvas->drawImage(image, 0, 128);
+    canvas->drawImage(image, 128, 128);
+    SkPaint paint;
+    paint.setBlendMode(SkBlendMode::kDstATop);
+    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
+    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
+    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    canvas->clipRect( { 30, 30, 226, 226 } );
+    canvas->drawColor(0x80bb9977, SkBlendMode::kMultiply);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Overlay.cpp b/docs/examples/Overlay.cpp
new file mode 100644
index 0000000..95c77ac
--- /dev/null
+++ b/docs/examples/Overlay.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=03bf042201de02d6d131938ccd3172eb
+REG_FIDDLE(Overlay, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
+    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
+    SkPaint paint;
+    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    paint.setBlendMode(SkBlendMode::kDstATop);
+    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
+    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
+    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    canvas->clipRect( { 30, 30, 226, 226 } );
+    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kOverlay);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_053.cpp b/docs/examples/Paint_053.cpp
new file mode 100644
index 0000000..132e754
--- /dev/null
+++ b/docs/examples/Paint_053.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2bffb6384cc20077e632e7d01da045ca
+REG_FIDDLE(Paint_053, 256, 200, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(20);
+    SkPath path;
+    path.moveTo(30, 30);
+    path.lineTo(30, 30);
+    path.moveTo(70, 30);
+    path.lineTo(90, 40);
+    for (SkPaint::Cap c : { SkPaint::kButt_Cap, SkPaint::kRound_Cap, SkPaint::kSquare_Cap } ) {
+        paint.setStrokeCap(c);
+        canvas->drawPath(path, paint);
+        canvas->translate(0, 70);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_057.cpp b/docs/examples/Paint_057.cpp
new file mode 100644
index 0000000..d7ede5e
--- /dev/null
+++ b/docs/examples/Paint_057.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3b1aebacc21c1836a52876b9b0b3905e
+REG_FIDDLE(Paint_057, 462, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.moveTo(10, 50);
+    path.quadTo(35, 110, 60, 210);
+    path.quadTo(105, 110, 130, 10);
+    SkPaint paint;  // set to default kMiter_Join
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(20);
+    canvas->drawPath(path, paint);
+    canvas->translate(150, 0);
+    paint.setStrokeJoin(SkPaint::kBevel_Join);
+    canvas->drawPath(path, paint);
+    canvas->translate(150, 0);
+    paint.setStrokeJoin(SkPaint::kRound_Join);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_containsText.cpp b/docs/examples/Paint_containsText.cpp
new file mode 100644
index 0000000..c81b316
--- /dev/null
+++ b/docs/examples/Paint_containsText.cpp
@@ -0,0 +1,21 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6a68cb3c8b81a5976c81ee004f559247
+REG_FIDDLE(Paint_containsText, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    const uint16_t goodGlyph = 511;
+    const uint16_t zeroGlyph = 0;
+    const uint16_t badGlyph = 65535; // larger than glyph count in font
+    paint.setTextEncoding(kGlyphID_SkTextEncoding);
+    SkDebugf("0x%04x %c= has glyph\n", goodGlyph,
+            paint.containsText(&goodGlyph, 2) ? '=' : '!');
+    SkDebugf("0x%04x %c= has glyph\n", zeroGlyph,
+            paint.containsText(&zeroGlyph, 2) ? '=' : '!');
+    SkDebugf("0x%04x %c= has glyph\n", badGlyph,
+            paint.containsText(&badGlyph, 2) ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_copy_const_SkPaint.cpp b/docs/examples/Paint_copy_const_SkPaint.cpp
new file mode 100644
index 0000000..2319e87
--- /dev/null
+++ b/docs/examples/Paint_copy_const_SkPaint.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b99971ad0ef243d617925289d963b62d
+REG_FIDDLE(Paint_copy_const_SkPaint, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint1;
+    paint1.setColor(SK_ColorRED);
+    SkPaint paint2(paint1);
+    paint2.setColor(SK_ColorBLUE);
+    SkDebugf("SK_ColorRED %c= paint1.getColor()\n", SK_ColorRED == paint1.getColor() ? '=' : '!');
+    SkDebugf("SK_ColorBLUE %c= paint2.getColor()\n", SK_ColorBLUE == paint2.getColor() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_copy_operator.cpp b/docs/examples/Paint_copy_operator.cpp
new file mode 100644
index 0000000..fe1b399
--- /dev/null
+++ b/docs/examples/Paint_copy_operator.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b476a9088f80dece176ed577807d3992
+REG_FIDDLE(Paint_copy_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint1, paint2;
+    paint1.setColor(SK_ColorRED);
+    paint2 = paint1;
+    SkDebugf("SK_ColorRED %c= paint1.getColor()\n", SK_ColorRED == paint1.getColor() ? '=' : '!');
+    SkDebugf("SK_ColorRED %c= paint2.getColor()\n", SK_ColorRED == paint2.getColor() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_countText.cpp b/docs/examples/Paint_countText.cpp
new file mode 100644
index 0000000..b648b21
--- /dev/null
+++ b/docs/examples/Paint_countText.cpp
@@ -0,0 +1,13 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=85436c71aab5410767fc688ab0573e09
+REG_FIDDLE(Paint_countText, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    const uint8_t utf8[] = { 0x24, 0xC2, 0xA2, 0xE2, 0x82, 0xAC, 0xC2, 0xA5, 0xC2, 0xA3 };
+    SkDebugf("count = %d\n", paint.countText(utf8, sizeof(utf8)));
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_empty_constructor.cpp b/docs/examples/Paint_empty_constructor.cpp
new file mode 100644
index 0000000..c4fc87d
--- /dev/null
+++ b/docs/examples/Paint_empty_constructor.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c4b2186d85c142a481298f7144295ffd
+REG_FIDDLE(Paint_empty_constructor, 256, 1, false, 0) {
+void draw(SkCanvas* canvas) {
+    #ifndef SkUserConfig_DEFINED
+    #define SkUserConfig_DEFINED
+    #define SkPaintDefaults_Flags      0x01   // always enable antialiasing
+    #define SkPaintDefaults_TextSize   24.f   // double default font size
+    #define SkPaintDefaults_Hinting    3      // use full hinting
+    #define SkPaintDefaults_MiterLimit 10.f   // use HTML Canvas miter limit setting
+    #endif
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_equal_operator.cpp b/docs/examples/Paint_equal_operator.cpp
new file mode 100644
index 0000000..23df823
--- /dev/null
+++ b/docs/examples/Paint_equal_operator.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7481a948e34672720337a631830586dd
+REG_FIDDLE(Paint_equal_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint1, paint2;
+    paint1.setColor(SK_ColorRED);
+    paint2.setColor(0xFFFF0000);
+    SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+    float intervals[] = { 5, 5 };
+    paint1.setPathEffect(SkDashPathEffect::Make(intervals, 2, 2.5f));
+    paint2.setPathEffect(SkDashPathEffect::Make(intervals, 2, 2.5f));
+    SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getAlpha.cpp b/docs/examples/Paint_getAlpha.cpp
new file mode 100644
index 0000000..14f07b5
--- /dev/null
+++ b/docs/examples/Paint_getAlpha.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9a85bb62fe3d877b18fb7f952c4fa7f7
+REG_FIDDLE(Paint_getAlpha, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("255 %c= paint.getAlpha()\n", 255 == paint.getAlpha() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getBlendMode.cpp b/docs/examples/Paint_getBlendMode.cpp
new file mode 100644
index 0000000..f87a4bc
--- /dev/null
+++ b/docs/examples/Paint_getBlendMode.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a1e059c8f6740fa2044cc64152b39dda
+REG_FIDDLE(Paint_getBlendMode, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+   SkPaint paint;
+   SkDebugf("kSrcOver %c= getBlendMode\n",
+            SkBlendMode::kSrcOver == paint.getBlendMode() ? '=' : '!');
+   paint.setBlendMode(SkBlendMode::kSrc);
+   SkDebugf("kSrcOver %c= getBlendMode\n",
+            SkBlendMode::kSrcOver == paint.getBlendMode() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getColor.cpp b/docs/examples/Paint_getColor.cpp
new file mode 100644
index 0000000..831b4a1
--- /dev/null
+++ b/docs/examples/Paint_getColor.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=72d41f890203109a41f589a7403acae9
+REG_FIDDLE(Paint_getColor, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setColor(SK_ColorYELLOW);
+    SkColor y = paint.getColor();
+    SkDebugf("Yellow is %d%% red, %d%% green, and %d%% blue.\n", (int) (SkColorGetR(y) / 2.55f),
+            (int) (SkColorGetG(y) / 2.55f), (int) (SkColorGetB(y) / 2.55f));
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getColor4f.cpp b/docs/examples/Paint_getColor4f.cpp
new file mode 100644
index 0000000..c7e4fa9
--- /dev/null
+++ b/docs/examples/Paint_getColor4f.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8512ea2176f36e8f1aeef311ff228790
+REG_FIDDLE(Paint_getColor4f, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setColor(SK_ColorYELLOW);
+    SkColor4f y = paint.getColor4f();
+    SkDebugf("Yellow is %d%% red, %d%% green, and %d%% blue.\n", (int) (y.fR * 100),
+            (int) (y.fG * 100), (int) (y.fB * 100));
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getColorFilter.cpp b/docs/examples/Paint_getColorFilter.cpp
new file mode 100644
index 0000000..13d0532
--- /dev/null
+++ b/docs/examples/Paint_getColorFilter.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=093bdc627d6b59002670fd290931f6c9
+REG_FIDDLE(Paint_getColorFilter, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+   SkPaint paint;
+   SkDebugf("nullptr %c= color filter\n", paint.getColorFilter() ? '!' : '=');
+   paint.setColorFilter(SkColorFilters::Blend(SK_ColorLTGRAY, SkBlendMode::kSrcIn));
+   SkDebugf("nullptr %c= color filter\n", paint.getColorFilter() ? '!' : '=');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getDrawLooper.cpp b/docs/examples/Paint_getDrawLooper.cpp
new file mode 100644
index 0000000..fb578c4
--- /dev/null
+++ b/docs/examples/Paint_getDrawLooper.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=af4c5acc7a91e7f23c2af48018903ad4
+REG_FIDDLE(Paint_getDrawLooper, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+   SkPaint paint;
+   SkDebugf("nullptr %c= draw looper\n", paint.getDrawLooper() ? '!' : '=');
+   SkLayerDrawLooper::Builder looperBuilder;
+   paint.setDrawLooper(looperBuilder.detach());
+   SkDebugf("nullptr %c= draw looper\n", paint.getDrawLooper() ? '!' : '=');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getFillPath.cpp b/docs/examples/Paint_getFillPath.cpp
new file mode 100644
index 0000000..b6f6f30
--- /dev/null
+++ b/docs/examples/Paint_getFillPath.cpp
@@ -0,0 +1,29 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=cedd6233848198e1fca4d1e14816baaf
+REG_FIDDLE(Paint_getFillPath, 256, 192, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint strokePaint;
+    strokePaint.setAntiAlias(true);
+    strokePaint.setStyle(SkPaint::kStroke_Style);
+    strokePaint.setStrokeWidth(.1f);
+    SkPath strokePath;
+    strokePath.moveTo(.08f, .08f);
+    strokePath.quadTo(.09f, .08f, .17f, .17f);
+    SkPath fillPath;
+    SkPaint outlinePaint(strokePaint);
+    outlinePaint.setStrokeWidth(2);
+    SkMatrix scale = SkMatrix::MakeScale(300, 300);
+    for (SkScalar precision : { 0.01f, .1f, 1.f, 10.f, 100.f } ) {
+        strokePaint.getFillPath(strokePath, &fillPath, nullptr, precision);
+        fillPath.transform(scale);
+        canvas->drawPath(fillPath, outlinePaint);
+        canvas->translate(60, 0);
+        if (1.f == precision) canvas->translate(-180, 100);
+    }
+    strokePath.transform(scale);
+    strokePaint.setStrokeWidth(30);
+    canvas->drawPath(strokePath, strokePaint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getFillPath_2.cpp b/docs/examples/Paint_getFillPath_2.cpp
new file mode 100644
index 0000000..093d0cf
--- /dev/null
+++ b/docs/examples/Paint_getFillPath_2.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e6d8ca0cc17e0b475bd54dd995825468
+REG_FIDDLE(Paint_getFillPath_2, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(10);
+    SkPath strokePath;
+    strokePath.moveTo(20, 20);
+    strokePath.lineTo(100, 100);
+    canvas->drawPath(strokePath, paint);
+    SkPath fillPath;
+    paint.getFillPath(strokePath, &fillPath);
+    paint.setStrokeWidth(2);
+    canvas->translate(40, 0);
+    canvas->drawPath(fillPath, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getFilterQuality.cpp b/docs/examples/Paint_getFilterQuality.cpp
new file mode 100644
index 0000000..990ac83
--- /dev/null
+++ b/docs/examples/Paint_getFilterQuality.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d4ca1f23809b6835c4ba46ea98a86900
+REG_FIDDLE(Paint_getFilterQuality, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("kNone_SkFilterQuality %c= paint.getFilterQuality()\n",
+            kNone_SkFilterQuality == paint.getFilterQuality() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getFlags.cpp b/docs/examples/Paint_getFlags.cpp
new file mode 100644
index 0000000..9929196
--- /dev/null
+++ b/docs/examples/Paint_getFlags.cpp
@@ -0,0 +1,14 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8a3f8c309533388b01aa66e1267f322d
+REG_FIDDLE(Paint_getFlags, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkDebugf("(SkPaint::kAntiAlias_Flag & paint.getFlags()) %c= 0\n",
+        SkPaint::kAntiAlias_Flag & paint.getFlags() ? '!' : '=');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_getFontMetrics.cpp b/docs/examples/Paint_getFontMetrics.cpp
new file mode 100644
index 0000000..5cf535c
--- /dev/null
+++ b/docs/examples/Paint_getFontMetrics.cpp
@@ -0,0 +1,15 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=59d9b8249afa1c2af6186711250ce240
+REG_FIDDLE(Paint_getFontMetrics, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(32);
+    SkScalar lineHeight = paint.getFontMetrics(nullptr);
+    canvas->drawString("line 1", 10, 40, paint);
+    canvas->drawString("line 2", 10, 40 + lineHeight, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_getFontSpacing.cpp b/docs/examples/Paint_getFontSpacing.cpp
new file mode 100644
index 0000000..d46225e
--- /dev/null
+++ b/docs/examples/Paint_getFontSpacing.cpp
@@ -0,0 +1,15 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=424741e26e1b174e43087d67422ce14f
+REG_FIDDLE(Paint_getFontSpacing, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    for (SkScalar textSize : { 12, 18, 24, 32 } ) {
+        paint.setTextSize(textSize);
+        SkDebugf("textSize: %g fontSpacing: %g\n", textSize, paint.getFontSpacing());
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_getHash.cpp b/docs/examples/Paint_getHash.cpp
new file mode 100644
index 0000000..ccc5d6d
--- /dev/null
+++ b/docs/examples/Paint_getHash.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7f7e1b701361912b344f90ae6b530393
+REG_FIDDLE(Paint_getHash, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint1, paint2;
+    paint1.setColor(SK_ColorRED);
+    paint2.setColor(0xFFFF0000);
+    SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+    SkDebugf("paint1.getHash() %c= paint2.getHash()\n",
+             paint1.getHash() == paint2.getHash() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getHinting.cpp b/docs/examples/Paint_getHinting.cpp
new file mode 100644
index 0000000..d67a632
--- /dev/null
+++ b/docs/examples/Paint_getHinting.cpp
@@ -0,0 +1,13 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b56b70c7ea2453c41bfa58b626953bed
+REG_FIDDLE(Paint_getHinting, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("SkFontHinting::kNormal %c= paint.getHinting()\n",
+            SkFontHinting::kNormal == paint.getHinting() ? '=' : ':');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_getImageFilter.cpp b/docs/examples/Paint_getImageFilter.cpp
new file mode 100644
index 0000000..1112d28
--- /dev/null
+++ b/docs/examples/Paint_getImageFilter.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c11f8eaa1dd149bc18db21e23ce26904
+REG_FIDDLE(Paint_getImageFilter, 256, 256, true, 0) {
+#include "SkBlurImageFilter.h"
+
+void draw(SkCanvas* canvas) {
+   SkPaint paint;
+   SkDebugf("nullptr %c= image filter\n", paint.getImageFilter() ? '!' : '=');
+   paint.setImageFilter(SkBlurImageFilter::Make(kOuter_SkBlurStyle, 3, nullptr, nullptr));
+   SkDebugf("nullptr %c= image filter\n", paint.getImageFilter() ? '!' : '=');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getMaskFilter.cpp b/docs/examples/Paint_getMaskFilter.cpp
new file mode 100644
index 0000000..97f3fef
--- /dev/null
+++ b/docs/examples/Paint_getMaskFilter.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5ac4b31371726da87bb7390b385e9fee
+REG_FIDDLE(Paint_getMaskFilter, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+   SkPaint paint;
+   SkDebugf("nullptr %c= mask filter\n", paint.getMaskFilter() ? '!' : '=');
+   paint.setMaskFilter(SkMaskFilter::MakeBlur(kOuter_SkBlurStyle, 3));
+   SkDebugf("nullptr %c= mask filter\n", paint.getMaskFilter() ? '!' : '=');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getPathEffect.cpp b/docs/examples/Paint_getPathEffect.cpp
new file mode 100644
index 0000000..d5f1acd
--- /dev/null
+++ b/docs/examples/Paint_getPathEffect.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=211a1b14bfa6c4332082c8eab4fbc5fd
+REG_FIDDLE(Paint_getPathEffect, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+   SkPaint paint;
+   SkDebugf("nullptr %c= path effect\n", paint.getPathEffect() ? '!' : '=');
+   paint.setPathEffect(SkCornerPathEffect::Make(10));
+   SkDebugf("nullptr %c= path effect\n", paint.getPathEffect() ? '!' : '=');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getPosTextPath.cpp b/docs/examples/Paint_getPosTextPath.cpp
new file mode 100644
index 0000000..df7a539
--- /dev/null
+++ b/docs/examples/Paint_getPosTextPath.cpp
@@ -0,0 +1,18 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7f27c93472aa99a7542fb3493076f072
+REG_FIDDLE(Paint_getPosTextPath, 256, 85, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(80);
+    SkPath path, path2;
+    SkPoint pos[] = {{20, 60}, {30, 70}, {40, 80}};
+    paint.getPosTextPath("ABC", 3, pos, &path);
+    Simplify(path, &path);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_getShader.cpp b/docs/examples/Paint_getShader.cpp
new file mode 100644
index 0000000..1e5f161
--- /dev/null
+++ b/docs/examples/Paint_getShader.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=09f15b9fd88882850da2d235eb86292f
+REG_FIDDLE(Paint_getShader, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+   SkPaint paint;
+   SkDebugf("nullptr %c= shader\n", paint.getShader() ? '!' : '=');
+   paint.setShader(SkShaders::Empty());
+   SkDebugf("nullptr %c= shader\n", paint.getShader() ? '!' : '=');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getStrokeCap.cpp b/docs/examples/Paint_getStrokeCap.cpp
new file mode 100644
index 0000000..37823b3
--- /dev/null
+++ b/docs/examples/Paint_getStrokeCap.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=aabf9baee8e026fae36fca30e955512b
+REG_FIDDLE(Paint_getStrokeCap, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("kButt_Cap %c= default stroke cap\n",
+            SkPaint::kButt_Cap == paint.getStrokeCap() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getStrokeJoin.cpp b/docs/examples/Paint_getStrokeJoin.cpp
new file mode 100644
index 0000000..9f925f7
--- /dev/null
+++ b/docs/examples/Paint_getStrokeJoin.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=31bf751d0a8ddf176b871810820d8199
+REG_FIDDLE(Paint_getStrokeJoin, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("kMiter_Join %c= default stroke join\n",
+            SkPaint::kMiter_Join == paint.getStrokeJoin() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getStrokeMiter.cpp b/docs/examples/Paint_getStrokeMiter.cpp
new file mode 100644
index 0000000..77c09bd
--- /dev/null
+++ b/docs/examples/Paint_getStrokeMiter.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=50da74a43b725f07a914df588c867d36
+REG_FIDDLE(Paint_getStrokeMiter, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("default miter limit == %g\n", paint.getStrokeMiter());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getStrokeWidth.cpp b/docs/examples/Paint_getStrokeWidth.cpp
new file mode 100644
index 0000000..a8c672e
--- /dev/null
+++ b/docs/examples/Paint_getStrokeWidth.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=99aa73f64df8bbf06e656cd891a81b9e
+REG_FIDDLE(Paint_getStrokeWidth, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("0 %c= paint.getStrokeWidth()\n", 0 == paint.getStrokeWidth() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getStyle.cpp b/docs/examples/Paint_getStyle.cpp
new file mode 100644
index 0000000..9fcaa43
--- /dev/null
+++ b/docs/examples/Paint_getStyle.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1c5e18c3c0102d2dac86a78ba8c8ce01
+REG_FIDDLE(Paint_getStyle, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("SkPaint::kFill_Style %c= paint.getStyle()\n",
+            SkPaint::kFill_Style == paint.getStyle() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_getTextEncoding.cpp b/docs/examples/Paint_getTextEncoding.cpp
new file mode 100644
index 0000000..6bae086
--- /dev/null
+++ b/docs/examples/Paint_getTextEncoding.cpp
@@ -0,0 +1,16 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0d21e968e9a4c78c902ae3ef494941a0
+REG_FIDDLE(Paint_getTextEncoding, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("kUTF8_SkTextEncoding %c= text encoding\n",
+            kUTF8_SkTextEncoding == paint.getTextEncoding() ? '=' : '!');
+    paint.setTextEncoding(kGlyphID_SkTextEncoding);
+    SkDebugf("kGlyphID_SkTextEncoding %c= text encoding\n",
+            kGlyphID_SkTextEncoding == paint.getTextEncoding() ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_getTextPath.cpp b/docs/examples/Paint_getTextPath.cpp
new file mode 100644
index 0000000..d03146b
--- /dev/null
+++ b/docs/examples/Paint_getTextPath.cpp
@@ -0,0 +1,19 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7c9e6a399f898d68026c1f0865e6f73e
+REG_FIDDLE(Paint_getTextPath, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(80);
+    SkPath path, path2;
+    paint.getTextPath("ABC", 3, 20, 80, &path);
+    path.offset(20, 20, &path2);
+    Op(path, path2, SkPathOp::kDifference_SkPathOp, &path);
+    path.addPath(path2);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_getTextScaleX.cpp b/docs/examples/Paint_getTextScaleX.cpp
new file mode 100644
index 0000000..c8b4d94
--- /dev/null
+++ b/docs/examples/Paint_getTextScaleX.cpp
@@ -0,0 +1,12 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5dc8e58f6910cb8e4de9ed60f888188b
+REG_FIDDLE(Paint_getTextScaleX, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("1 %c= default text scale x\n", 1 == paint.getTextScaleX() ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_getTextSize.cpp b/docs/examples/Paint_getTextSize.cpp
new file mode 100644
index 0000000..9a85a17
--- /dev/null
+++ b/docs/examples/Paint_getTextSize.cpp
@@ -0,0 +1,12 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=983e2a71ba72d4ba8c945420040b8f1c
+REG_FIDDLE(Paint_getTextSize, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("12 %c= default text size\n", 12 == paint.getTextSize() ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_getTextSkewX.cpp b/docs/examples/Paint_getTextSkewX.cpp
new file mode 100644
index 0000000..8e0b72d
--- /dev/null
+++ b/docs/examples/Paint_getTextSkewX.cpp
@@ -0,0 +1,12 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=11c10f466dae0d1639dbb9f6a0040218
+REG_FIDDLE(Paint_getTextSkewX, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("0 %c= default text skew x\n", 0 == paint.getTextSkewX() ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_getTextWidths.cpp b/docs/examples/Paint_getTextWidths.cpp
new file mode 100644
index 0000000..43e3d15
--- /dev/null
+++ b/docs/examples/Paint_getTextWidths.cpp
@@ -0,0 +1,37 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6b9e101f49e9c2c28755c5bdcef64dfb
+REG_FIDDLE(Paint_getTextWidths, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(50);
+    const char str[] = "abc";
+    const int bytes = sizeof(str) - 1;
+    int count = paint.getTextWidths(str, bytes, nullptr);
+    std::vector<SkScalar> widths;
+    std::vector<SkRect> bounds;
+    widths.resize(count);
+    bounds.resize(count);
+    for (int loop = 0; loop < 2; ++loop) {
+        (void) paint.getTextWidths(str, count, &widths.front(), &bounds.front());
+        SkPoint loc = { 25, 50 };
+        canvas->drawText(str, bytes, loc.fX, loc.fY, paint);
+        paint.setStyle(SkPaint::kStroke_Style);
+        paint.setStrokeWidth(0);
+        SkScalar advanceY = loc.fY + 10;
+        for (int index = 0; index < count; ++index) {
+            bounds[index].offset(loc.fX, loc.fY);
+            canvas->drawRect(bounds[index], paint);
+            canvas->drawLine(loc.fX, advanceY, loc.fX + widths[index], advanceY, paint);
+            loc.fX += widths[index];
+            advanceY += 5;
+        }
+        canvas->translate(0, 80);
+        paint.setStrokeWidth(3);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_getTypeface.cpp b/docs/examples/Paint_getTypeface.cpp
new file mode 100644
index 0000000..6491632
--- /dev/null
+++ b/docs/examples/Paint_getTypeface.cpp
@@ -0,0 +1,14 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5ce718e5a184baaac80e7098d7dad67b
+REG_FIDDLE(Paint_getTypeface, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+   SkPaint paint;
+   SkDebugf("nullptr %c= typeface\n", paint.getTypeface() ? '!' : '=');
+   paint.setTypeface(SkTypeface::MakeFromName("monospace", SkFontStyle()));
+   SkDebugf("nullptr %c= typeface\n", paint.getTypeface() ? '!' : '=');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_isAntiAlias.cpp b/docs/examples/Paint_isAntiAlias.cpp
new file mode 100644
index 0000000..0c9ecd7
--- /dev/null
+++ b/docs/examples/Paint_isAntiAlias.cpp
@@ -0,0 +1,16 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d7d5f4f7da7acd5104a652f490c6f7b8
+REG_FIDDLE(Paint_isAntiAlias, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("paint.isAntiAlias() %c= !!(paint.getFlags() & SkPaint::kAntiAlias_Flag)\n",
+            paint.isAntiAlias() == !!(paint.getFlags() & SkPaint::kAntiAlias_Flag) ? '=' : '!');
+    paint.setAntiAlias(true);
+    SkDebugf("paint.isAntiAlias() %c= !!(paint.getFlags() & SkPaint::kAntiAlias_Flag)\n",
+            paint.isAntiAlias() == !!(paint.getFlags() & SkPaint::kAntiAlias_Flag) ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_isAutohinted.cpp b/docs/examples/Paint_isAutohinted.cpp
new file mode 100644
index 0000000..682e886
--- /dev/null
+++ b/docs/examples/Paint_isAutohinted.cpp
@@ -0,0 +1,18 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=aa4781afbe3b90e7ef56a287e5b9ce1e
+REG_FIDDLE(Paint_isAutohinted, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    for (auto forceAutoHinting : { false, true} ) {
+    paint.setAutohinted(forceAutoHinting);
+    SkDebugf("paint.isAutohinted() %c="
+            " !!(paint.getFlags() & SkPaint::kAutoHinting_Flag)\n",
+            paint.isAutohinted() ==
+            !!(paint.getFlags() & SkPaint::kAutoHinting_Flag) ? '=' : '!');
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_isDither.cpp b/docs/examples/Paint_isDither.cpp
new file mode 100644
index 0000000..8de4a6c
--- /dev/null
+++ b/docs/examples/Paint_isDither.cpp
@@ -0,0 +1,16 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f4ce93f6c5e7335436a985377fd980c0
+REG_FIDDLE(Paint_isDither, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("paint.isDither() %c= !!(paint.getFlags() & SkPaint::kDither_Flag)\n",
+            paint.isDither() == !!(paint.getFlags() & SkPaint::kDither_Flag) ? '=' : '!');
+    paint.setDither(true);
+    SkDebugf("paint.isDither() %c= !!(paint.getFlags() & SkPaint::kDither_Flag)\n",
+            paint.isDither() == !!(paint.getFlags() & SkPaint::kDither_Flag) ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_isEmbeddedBitmapText.cpp b/docs/examples/Paint_isEmbeddedBitmapText.cpp
new file mode 100644
index 0000000..9390151
--- /dev/null
+++ b/docs/examples/Paint_isEmbeddedBitmapText.cpp
@@ -0,0 +1,20 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=eba10b27b790e87183ae451b3fc5c4b1
+REG_FIDDLE(Paint_isEmbeddedBitmapText, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("paint.isEmbeddedBitmapText() %c="
+            " !!(paint.getFlags() & SkPaint::kEmbeddedBitmapText_Flag)\n",
+            paint.isEmbeddedBitmapText() ==
+            !!(paint.getFlags() & SkPaint::kEmbeddedBitmapText_Flag) ? '=' : '!');
+    paint.setEmbeddedBitmapText(true);
+    SkDebugf("paint.isEmbeddedBitmapText() %c="
+            " !!(paint.getFlags() & SkPaint::kEmbeddedBitmapText_Flag)\n",
+            paint.isEmbeddedBitmapText() ==
+            !!(paint.getFlags() & SkPaint::kEmbeddedBitmapText_Flag) ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_isFakeBoldText.cpp b/docs/examples/Paint_isFakeBoldText.cpp
new file mode 100644
index 0000000..a851e44
--- /dev/null
+++ b/docs/examples/Paint_isFakeBoldText.cpp
@@ -0,0 +1,16 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f54d1f85b16073b80b9eef2e1a1d151d
+REG_FIDDLE(Paint_isFakeBoldText, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("paint.isFakeBoldText() %c= !!(paint.getFlags() & SkPaint::kFakeBoldText_Flag)\n",
+        paint.isFakeBoldText() == !!(paint.getFlags() & SkPaint::kFakeBoldText_Flag) ? '=' : '!');
+    paint.setFakeBoldText(true);
+    SkDebugf("paint.isFakeBoldText() %c= !!(paint.getFlags() & SkPaint::kFakeBoldText_Flag)\n",
+        paint.isFakeBoldText() == !!(paint.getFlags() & SkPaint::kFakeBoldText_Flag) ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_isLCDRenderText.cpp b/docs/examples/Paint_isLCDRenderText.cpp
new file mode 100644
index 0000000..a139270
--- /dev/null
+++ b/docs/examples/Paint_isLCDRenderText.cpp
@@ -0,0 +1,16 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=68e1fd95dd2fd06a333899d2bd2396b9
+REG_FIDDLE(Paint_isLCDRenderText, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("paint.isLCDRenderText() %c= !!(paint.getFlags() & SkPaint::kLCDRenderText_Flag)\n",
+        paint.isLCDRenderText() == !!(paint.getFlags() & SkPaint::kLCDRenderText_Flag) ? '=' : '!');
+    paint.setLCDRenderText(true);
+    SkDebugf("paint.isLCDRenderText() %c= !!(paint.getFlags() & SkPaint::kLCDRenderText_Flag)\n",
+        paint.isLCDRenderText() == !!(paint.getFlags() & SkPaint::kLCDRenderText_Flag) ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_isLinearText.cpp b/docs/examples/Paint_isLinearText.cpp
new file mode 100644
index 0000000..25c0432
--- /dev/null
+++ b/docs/examples/Paint_isLinearText.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2890ad644f980637837e6fcb386fb462
+REG_FIDDLE(Paint_isLinearText, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    const char testStr[] = "xxxx xxxx";
+    for (auto linearText : { false, true } ) {
+        paint.setLinearText(linearText);
+        paint.setTextSize(24);
+        canvas->drawString(paint.isLinearText() ? "linear" : "hinted", 128, 30, paint);
+        for (SkScalar textSize = 8; textSize < 30; textSize *= 1.22f) {
+            paint.setTextSize(textSize);
+            canvas->translate(0, textSize);
+            canvas->drawString(testStr, 10, 0, paint);
+        }
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_isSubpixelText.cpp b/docs/examples/Paint_isSubpixelText.cpp
new file mode 100644
index 0000000..a55546c
--- /dev/null
+++ b/docs/examples/Paint_isSubpixelText.cpp
@@ -0,0 +1,16 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=abe9afc0932e2199324ae6cbb396e67c
+REG_FIDDLE(Paint_isSubpixelText, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("paint.isSubpixelText() %c= !!(paint.getFlags() & SkPaint::kSubpixelText_Flag)\n",
+        paint.isSubpixelText() == !!(paint.getFlags() & SkPaint::kSubpixelText_Flag) ? '=' : '!');
+    paint.setSubpixelText(true);
+    SkDebugf("paint.isSubpixelText() %c= !!(paint.getFlags() & SkPaint::kSubpixelText_Flag)\n",
+        paint.isSubpixelText() == !!(paint.getFlags() & SkPaint::kSubpixelText_Flag) ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_measureText.cpp b/docs/examples/Paint_measureText.cpp
new file mode 100644
index 0000000..73850d4
--- /dev/null
+++ b/docs/examples/Paint_measureText.cpp
@@ -0,0 +1,21 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=06084f609184470135a9cd9ebc5af149
+REG_FIDDLE(Paint_measureText, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(50);
+    const char str[] = "ay^jZ";
+    const int count = sizeof(str) - 1;
+    canvas->drawText(str, count, 25, 50, paint);
+    SkRect bounds;
+    paint.measureText(str, count, &bounds);
+    canvas->translate(25, 50);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawRect(bounds, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_measureText_2.cpp b/docs/examples/Paint_measureText_2.cpp
new file mode 100644
index 0000000..c2b56d6
--- /dev/null
+++ b/docs/examples/Paint_measureText_2.cpp
@@ -0,0 +1,14 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f1139a5ddd17fd47c2f45f6e642cac76
+REG_FIDDLE(Paint_measureText_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("default width = %g\n", paint.measureText("!", 1));
+    paint.setTextSize(paint.getTextSize() * 2);
+    SkDebugf("double width = %g\n", paint.measureText("!", 1));
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_move_SkPaint.cpp b/docs/examples/Paint_move_SkPaint.cpp
new file mode 100644
index 0000000..556b28c
--- /dev/null
+++ b/docs/examples/Paint_move_SkPaint.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8ed1488a503cd5282b86a51614aa90b1
+REG_FIDDLE(Paint_move_SkPaint, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    float intervals[] = { 5, 5 };
+    paint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 2.5f));
+    SkPaint dashed(std::move(paint));
+    SkDebugf("path effect unique: %s\n", dashed.getPathEffect()->unique() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_move_operator.cpp b/docs/examples/Paint_move_operator.cpp
new file mode 100644
index 0000000..4b6894f
--- /dev/null
+++ b/docs/examples/Paint_move_operator.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9fb7459b097d713f5f1fe5675afe14f5
+REG_FIDDLE(Paint_move_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint1, paint2;
+    paint1.setColor(SK_ColorRED);
+    paint2 = std::move(paint1);
+    SkDebugf("SK_ColorRED == paint2.getColor()\n", SK_ColorRED == paint2.getColor() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_notequal_operator.cpp b/docs/examples/Paint_notequal_operator.cpp
new file mode 100644
index 0000000..73a289a
--- /dev/null
+++ b/docs/examples/Paint_notequal_operator.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b6c8484b1187f555b435ad5369833be4
+REG_FIDDLE(Paint_notequal_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint1, paint2;
+    paint1.setColor(SK_ColorRED);
+    paint2.setColor(0xFFFF0000);
+    SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+    SkDebugf("paint1 %c= paint2\n", paint1 != paint2 ? '!' : '=');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_nothingToDraw.cpp b/docs/examples/Paint_nothingToDraw.cpp
new file mode 100644
index 0000000..f20f41c
--- /dev/null
+++ b/docs/examples/Paint_nothingToDraw.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2973b05bfbb6b4c29332c8ac4fcf3995
+REG_FIDDLE(Paint_nothingToDraw, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkPaint& p) -> void {
+        SkDebugf("%s nothing to draw: %s\n", prefix,
+                 p.nothingToDraw() ? "true" : "false");
+    };
+    SkPaint paint;
+    debugster("initial", paint);
+    paint.setBlendMode(SkBlendMode::kDst);
+    debugster("blend dst", paint);
+    paint.setBlendMode(SkBlendMode::kSrcOver);
+    debugster("blend src over", paint);
+    paint.setAlpha(0);
+    debugster("alpha 0", paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_refColorFilter.cpp b/docs/examples/Paint_refColorFilter.cpp
new file mode 100644
index 0000000..4e9672c
--- /dev/null
+++ b/docs/examples/Paint_refColorFilter.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b588c95fa4c86ddbc4b0546762f08297
+REG_FIDDLE(Paint_refColorFilter, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint1, paint2;
+    paint1.setColorFilter(SkColorFilters::Blend(0xFFFF0000, SkBlendMode::kSrcATop));
+    SkDebugf("color filter unique: %s\n", paint1.getColorFilter()->unique() ? "true" : "false");
+    paint2.setColorFilter(paint1.refColorFilter());
+    SkDebugf("color filter unique: %s\n", paint1.getColorFilter()->unique() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_refDrawLooper.cpp b/docs/examples/Paint_refDrawLooper.cpp
new file mode 100644
index 0000000..71bf029
--- /dev/null
+++ b/docs/examples/Paint_refDrawLooper.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2a3782c33f04ed17a725d0e449c6f7c3
+REG_FIDDLE(Paint_refDrawLooper, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint1, paint2;
+    SkLayerDrawLooper::Builder looperBuilder;
+    paint1.setDrawLooper(looperBuilder.detach());
+    SkDebugf("draw looper unique: %s\n", paint1.getDrawLooper()->unique() ? "true" : "false");
+    paint2.setDrawLooper(paint1.refDrawLooper());
+    SkDebugf("draw looper unique: %s\n", paint1.getDrawLooper()->unique() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_refImageFilter.cpp b/docs/examples/Paint_refImageFilter.cpp
new file mode 100644
index 0000000..c73aec3
--- /dev/null
+++ b/docs/examples/Paint_refImageFilter.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=13f09088b569251547107d14ae989dc1
+REG_FIDDLE(Paint_refImageFilter, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint1, paint2;
+    paint1.setImageFilter(SkOffsetImageFilter::Make(25, 25, nullptr));
+    SkDebugf("image filter unique: %s\n", paint1.getImageFilter()->unique() ? "true" : "false");
+    paint2.setImageFilter(paint1.refImageFilter());
+    SkDebugf("image filter unique: %s\n", paint1.getImageFilter()->unique() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_refMaskFilter.cpp b/docs/examples/Paint_refMaskFilter.cpp
new file mode 100644
index 0000000..371370b
--- /dev/null
+++ b/docs/examples/Paint_refMaskFilter.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=084b0dc3cebd78718c651d58f257f799
+REG_FIDDLE(Paint_refMaskFilter, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint1, paint2;
+    paint1.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 1));
+    SkDebugf("mask filter unique: %s\n", paint1.getMaskFilter()->unique() ? "true" : "false");
+    paint2.setMaskFilter(paint1.refMaskFilter());
+    SkDebugf("mask filter unique: %s\n", paint1.getMaskFilter()->unique() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_refPathEffect.cpp b/docs/examples/Paint_refPathEffect.cpp
new file mode 100644
index 0000000..0f85d939
--- /dev/null
+++ b/docs/examples/Paint_refPathEffect.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f56039b94c702c2704c8c5100e623aca
+REG_FIDDLE(Paint_refPathEffect, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint1, paint2;
+    SkScalar intervals[] = {1, 2};
+    paint1.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 10));
+    SkDebugf("path effect unique: %s\n", paint1.getPathEffect()->unique() ? "true" : "false");
+    paint2.setPathEffect(paint1.refPathEffect());
+    SkDebugf("path effect unique: %s\n", paint1.getPathEffect()->unique() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_refShader.cpp b/docs/examples/Paint_refShader.cpp
new file mode 100644
index 0000000..e57c295
--- /dev/null
+++ b/docs/examples/Paint_refShader.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=53da0295972a418cbc9607bbb17feaa8
+REG_FIDDLE(Paint_refShader, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+   SkPaint paint1, paint2;
+   paint1.setShader(SkShaders::Empty());
+   SkDebugf("shader unique: %s\n", paint1.getShader()->unique() ? "true" : "false");
+   paint2.setShader(paint1.refShader());
+   SkDebugf("shader unique: %s\n", paint1.getShader()->unique() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_refTypeface.cpp b/docs/examples/Paint_refTypeface.cpp
new file mode 100644
index 0000000..8f114ff
--- /dev/null
+++ b/docs/examples/Paint_refTypeface.cpp
@@ -0,0 +1,19 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8b5aa7e555a0dc31be69db7cadf471a1
+REG_FIDDLE(Paint_refTypeface, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+   SkPaint paint1, paint2;
+   paint1.setTypeface(SkTypeface::MakeFromName("monospace",
+            SkFontStyle(SkFontStyle::kNormal_Weight, SkFontStyle::kNormal_Width,
+            SkFontStyle::kItalic_Slant)));
+   SkDebugf("typeface1 %c= typeface2\n",
+            paint1.getTypeface() == paint2.getTypeface() ? '=' : '!');
+   paint2.setTypeface(paint1.refTypeface());
+   SkDebugf("typeface1 %c= typeface2\n",
+            paint1.getTypeface() == paint2.getTypeface() ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_reset.cpp b/docs/examples/Paint_reset.cpp
new file mode 100644
index 0000000..c641983
--- /dev/null
+++ b/docs/examples/Paint_reset.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ef269937ade7e7353635121d9a64f9f7
+REG_FIDDLE(Paint_reset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint1, paint2;
+    paint1.setColor(SK_ColorRED);
+    paint1.reset();
+    SkDebugf("paint1 %c= paint2", paint1 == paint2 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_setARGB.cpp b/docs/examples/Paint_setARGB.cpp
new file mode 100644
index 0000000..703f0ff
--- /dev/null
+++ b/docs/examples/Paint_setARGB.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=cb62e4755789ed32f7120dc55984959d
+REG_FIDDLE(Paint_setARGB, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint transRed1, transRed2;
+    transRed1.setARGB(255 / 2, 255, 0, 0);
+    transRed2.setColor(SkColorSetARGB(255 / 2, 255, 0, 0));
+    SkDebugf("transRed1 %c= transRed2", transRed1 == transRed2 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_setAlpha.cpp b/docs/examples/Paint_setAlpha.cpp
new file mode 100644
index 0000000..fb4782e
--- /dev/null
+++ b/docs/examples/Paint_setAlpha.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6ddc0360512dfb9947e75c17e6a8103d
+REG_FIDDLE(Paint_setAlpha, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setColor(0x00112233);
+    paint.setAlpha(0x44);
+    SkDebugf("0x44112233 %c= paint.getColor()\n", 0x44112233 == paint.getColor() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_setAntiAlias.cpp b/docs/examples/Paint_setAntiAlias.cpp
new file mode 100644
index 0000000..3519e7e
--- /dev/null
+++ b/docs/examples/Paint_setAntiAlias.cpp
@@ -0,0 +1,14 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c2ff148374d01cbef845b223e725905c
+REG_FIDDLE(Paint_setAntiAlias, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint1, paint2;
+    paint1.setAntiAlias(true);
+    paint2.setFlags(paint2.getFlags() | SkPaint::kAntiAlias_Flag);
+    SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_setAutohinted.cpp b/docs/examples/Paint_setAutohinted.cpp
new file mode 100644
index 0000000..d55f15d
--- /dev/null
+++ b/docs/examples/Paint_setAutohinted.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4e185306d7de9390fe8445eed0139309
+REG_FIDDLE(Paint_setAutohinted, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    const char testStr[] = "xxxx xxxx";
+        for (auto forceAutoHinting : { false, true} ) {
+        paint.setAutohinted(forceAutoHinting);
+        paint.setTextSize(24);
+        canvas->drawString(paint.isAutohinted() ? "auto-hinted" : "default", 108, 30, paint);
+        for (SkScalar textSize = 8; textSize < 30; textSize *= 1.22f) {
+            paint.setTextSize(textSize);
+            canvas->translate(0, textSize);
+            canvas->drawString(testStr, 10, 0, paint);
+        }
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_setBlendMode.cpp b/docs/examples/Paint_setBlendMode.cpp
new file mode 100644
index 0000000..dbaebf8
--- /dev/null
+++ b/docs/examples/Paint_setBlendMode.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=257c9473db7a2b3a0fb2b9e2431e59a6
+REG_FIDDLE(Paint_setBlendMode, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+   SkPaint paint;
+   SkDebugf("isSrcOver %c= true\n", paint.isSrcOver() ? '=' : '!');
+   paint.setBlendMode(SkBlendMode::kSrc);
+   SkDebugf("isSrcOver %c= true\n", paint.isSrcOver() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_setColor.cpp b/docs/examples/Paint_setColor.cpp
new file mode 100644
index 0000000..f0d770a
--- /dev/null
+++ b/docs/examples/Paint_setColor.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6e70f18300bd676a3c056ceb6b62f8df
+REG_FIDDLE(Paint_setColor, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint green1, green2;
+    unsigned a = 255;
+    unsigned r = 0;
+    unsigned g = 255;
+    unsigned b = 0;
+    green1.setColor((a << 24) + (r << 16) + (g << 8) + (b << 0));
+    green2.setColor(0xFF00FF00);
+    SkDebugf("green1 %c= green2\n", green1 == green2 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_setColor4f.cpp b/docs/examples/Paint_setColor4f.cpp
new file mode 100644
index 0000000..601d3bb
--- /dev/null
+++ b/docs/examples/Paint_setColor4f.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=fa60859e3d03bdc117a05b32e093a8f1
+REG_FIDDLE(Paint_setColor4f, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint green1, green2;
+    green1.setColor4f({0, 1, 0, 1}, nullptr);  // R=0 G=1 B=0 A=1
+    green2.setColor(0xFF00FF00); // A=255 R=0 G=255 B=0
+    SkDebugf("green1 %c= green2\n", green1 == green2 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_setColorFilter.cpp b/docs/examples/Paint_setColorFilter.cpp
new file mode 100644
index 0000000..71d22d5
--- /dev/null
+++ b/docs/examples/Paint_setColorFilter.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c7b786dc9b3501cd0eaba47494b6fa31
+REG_FIDDLE(Paint_setColorFilter, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+   SkPaint paint;
+   paint.setColorFilter(SkColorFilters::Blend(SK_ColorLTGRAY, SkBlendMode::kSrcIn));
+   canvas->drawRect(SkRect::MakeWH(50, 50), paint);
+   paint.setColorFilter(nullptr);
+   canvas->translate(70, 0);
+   canvas->drawRect(SkRect::MakeWH(50, 50), paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_setDither.cpp b/docs/examples/Paint_setDither.cpp
new file mode 100644
index 0000000..093a506
--- /dev/null
+++ b/docs/examples/Paint_setDither.cpp
@@ -0,0 +1,14 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=69b7162e8324d9239dd02dd9ada2bdff
+REG_FIDDLE(Paint_setDither, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint1, paint2;
+    paint1.setDither(true);
+    paint2.setFlags(paint2.getFlags() | SkPaint::kDither_Flag);
+    SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_setDrawLooper.cpp b/docs/examples/Paint_setDrawLooper.cpp
new file mode 100644
index 0000000..612312c
--- /dev/null
+++ b/docs/examples/Paint_setDrawLooper.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bf10f838b330f0a3a3266d42ea68a638
+REG_FIDDLE(Paint_setDrawLooper, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setDrawLooper(SkBlurDrawLooper::Make(0x7FFF0000, 4, -5, -10));
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(10);
+    paint.setAntiAlias(true);
+    paint.setColor(0x7f0000ff);
+    canvas->drawCircle(70, 70, 50, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_setEmbeddedBitmapText.cpp b/docs/examples/Paint_setEmbeddedBitmapText.cpp
new file mode 100644
index 0000000..35683b5
--- /dev/null
+++ b/docs/examples/Paint_setEmbeddedBitmapText.cpp
@@ -0,0 +1,14 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=246dffdd93a484ba4ad7ecf71198a5d4
+REG_FIDDLE(Paint_setEmbeddedBitmapText, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint1, paint2;
+    paint1.setEmbeddedBitmapText(true);
+    paint2.setFlags(paint2.getFlags() | SkPaint::kEmbeddedBitmapText_Flag);
+    SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_setFakeBoldText.cpp b/docs/examples/Paint_setFakeBoldText.cpp
new file mode 100644
index 0000000..3015573
--- /dev/null
+++ b/docs/examples/Paint_setFakeBoldText.cpp
@@ -0,0 +1,14 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=594d47858eb11028cb626515a520910a
+REG_FIDDLE(Paint_setFakeBoldText, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint1, paint2;
+    paint1.setFakeBoldText(true);
+    paint2.setFlags(paint2.getFlags() | SkPaint::kFakeBoldText_Flag);
+    SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_setFilterQuality.cpp b/docs/examples/Paint_setFilterQuality.cpp
new file mode 100644
index 0000000..0946595
--- /dev/null
+++ b/docs/examples/Paint_setFilterQuality.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e4288fabf24ee60b645e8bb6ea0afadf
+REG_FIDDLE(Paint_setFilterQuality, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setFilterQuality(kHigh_SkFilterQuality);
+    SkDebugf("kHigh_SkFilterQuality %c= paint.getFilterQuality()\n",
+            kHigh_SkFilterQuality == paint.getFilterQuality() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_setFlags.cpp b/docs/examples/Paint_setFlags.cpp
new file mode 100644
index 0000000..db4b227
--- /dev/null
+++ b/docs/examples/Paint_setFlags.cpp
@@ -0,0 +1,14 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=54baed3f6bc4b9c31ba664e27767fdc7
+REG_FIDDLE(Paint_setFlags, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setFlags((uint32_t) (SkPaint::kAntiAlias_Flag | SkPaint::kDither_Flag));
+    SkDebugf("paint.isAntiAlias()\n", paint.isAntiAlias() ? '!' : '=');
+    SkDebugf("paint.isDither()\n", paint.isDither() ? '!' : '=');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_setHinting.cpp b/docs/examples/Paint_setHinting.cpp
new file mode 100644
index 0000000..0c8c521
--- /dev/null
+++ b/docs/examples/Paint_setHinting.cpp
@@ -0,0 +1,13 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bb179ec5698ec1398ff18f3657ab73f7
+REG_FIDDLE(Paint_setHinting, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint1, paint2;
+    paint2.setHinting(SkFontHinting::kNormal);
+    SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : ':');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_setImageFilter.cpp b/docs/examples/Paint_setImageFilter.cpp
new file mode 100644
index 0000000..5d0ce21
--- /dev/null
+++ b/docs/examples/Paint_setImageFilter.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6679d6e4ec632715ee03e68391bd7f9a
+REG_FIDDLE(Paint_setImageFilter, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkBitmap bitmap;
+    bitmap.allocN32Pixels(100, 100);
+    SkCanvas offscreen(bitmap);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setColor(SK_ColorWHITE);
+    paint.setTextSize(96);
+    offscreen.clear(0);
+    offscreen.drawString("e", 20, 70, paint);
+    paint.setImageFilter(
+           SkLightingImageFilter::MakePointLitDiffuse(SkPoint3::Make(80, 100, 10),
+           SK_ColorWHITE, 1, 2, nullptr, nullptr));
+    canvas->drawBitmap(bitmap, 0, 0, &paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_setLCDRenderText.cpp b/docs/examples/Paint_setLCDRenderText.cpp
new file mode 100644
index 0000000..cbbbacf
--- /dev/null
+++ b/docs/examples/Paint_setLCDRenderText.cpp
@@ -0,0 +1,14 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=50dedf8450159571a3edaf4f0050defe
+REG_FIDDLE(Paint_setLCDRenderText, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint1, paint2;
+    paint1.setLCDRenderText(true);
+    paint2.setFlags(paint2.getFlags() | SkPaint::kLCDRenderText_Flag);
+    SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_setLinearText.cpp b/docs/examples/Paint_setLinearText.cpp
new file mode 100644
index 0000000..18469fe
--- /dev/null
+++ b/docs/examples/Paint_setLinearText.cpp
@@ -0,0 +1,24 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c93bb912f3bddfb4d96d3ad70ada552b
+REG_FIDDLE(Paint_setLinearText, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    const char testStr[] = "abcd efgh";
+    for (int textSize : { 12, 24 } ) {
+        paint.setTextSize(textSize);
+        for (auto linearText : { false, true } ) {
+            paint.setLinearText(linearText);
+            SkString width;
+            width.appendScalar(paint.measureText(testStr, SK_ARRAY_COUNT(testStr), nullptr));
+            canvas->translate(0, textSize + 4);
+            canvas->drawString(testStr, 10, 0, paint);
+            canvas->drawString(width, 128, 0, paint);
+        }
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_setMaskFilter.cpp b/docs/examples/Paint_setMaskFilter.cpp
new file mode 100644
index 0000000..a53bf5f
--- /dev/null
+++ b/docs/examples/Paint_setMaskFilter.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a993831c40f3e134f809134e3b74e4a6
+REG_FIDDLE(Paint_setMaskFilter, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(10);
+    paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 10));
+    canvas->drawRect(SkRect::MakeXYWH(40, 40, 175, 175), paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_setPathEffect.cpp b/docs/examples/Paint_setPathEffect.cpp
new file mode 100644
index 0000000..d3071c1
--- /dev/null
+++ b/docs/examples/Paint_setPathEffect.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=52dd55074ca0b7d520d04e750ca2a0d7
+REG_FIDDLE(Paint_setPathEffect, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setPathEffect(SkDiscretePathEffect::Make(3, 5));
+    canvas->drawRect(SkRect::MakeXYWH(40, 40, 175, 175), paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_setShader.cpp b/docs/examples/Paint_setShader.cpp
new file mode 100644
index 0000000..0c6f41b
--- /dev/null
+++ b/docs/examples/Paint_setShader.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=77e64d5bae9b1ba037fd99252bb4aa58
+REG_FIDDLE(Paint_setShader, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setColor(SK_ColorBLUE);
+    paint.setShader(SkShaders::Color(SK_ColorRED));
+    canvas->drawRect(SkRect::MakeWH(40, 40), paint);
+    paint.setShader(nullptr);
+    canvas->translate(50, 0);
+    canvas->drawRect(SkRect::MakeWH(40, 40), paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_setStrokeCap_a.cpp b/docs/examples/Paint_setStrokeCap_a.cpp
new file mode 100644
index 0000000..385d045
--- /dev/null
+++ b/docs/examples/Paint_setStrokeCap_a.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=de83fbd848a4625345b4b87a6e55d98a
+REG_FIDDLE(Paint_setStrokeCap_a, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStrokeCap(SkPaint::kRound_Cap);
+    paint.setStrokeCap((SkPaint::Cap) SkPaint::kCapCount);
+    SkDebugf("kRound_Cap %c= paint.getStrokeCap()\n",
+            SkPaint::kRound_Cap == paint.getStrokeCap() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_setStrokeCap_b.cpp b/docs/examples/Paint_setStrokeCap_b.cpp
new file mode 100644
index 0000000..8dd4c53
--- /dev/null
+++ b/docs/examples/Paint_setStrokeCap_b.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=917c44b504d3f9308571fd3835d90a0d
+REG_FIDDLE(Paint_setStrokeCap_b, 256, 200, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(20);
+    SkPath path;
+    path.moveTo(30, 20);
+    path.lineTo(40, 40);
+    path.conicTo(70, 20, 100, 20, .707f);
+    for (SkPaint::Join j : { SkPaint::kMiter_Join, SkPaint::kRound_Join, SkPaint::kBevel_Join } ) {
+        paint.setStrokeJoin(j);
+        canvas->drawPath(path, paint);
+        canvas->translate(0, 70);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_setStrokeJoin.cpp b/docs/examples/Paint_setStrokeJoin.cpp
new file mode 100644
index 0000000..151239f
--- /dev/null
+++ b/docs/examples/Paint_setStrokeJoin.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=48d963ad4286eddf680f9c511eb6da91
+REG_FIDDLE(Paint_setStrokeJoin, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStrokeJoin(SkPaint::kMiter_Join);
+    paint.setStrokeJoin((SkPaint::Join) SkPaint::kJoinCount);
+    SkDebugf("kMiter_Join %c= paint.getStrokeJoin()\n",
+            SkPaint::kMiter_Join == paint.getStrokeJoin() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_setStrokeMiter.cpp b/docs/examples/Paint_setStrokeMiter.cpp
new file mode 100644
index 0000000..4fed994
--- /dev/null
+++ b/docs/examples/Paint_setStrokeMiter.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=700b284dbc97785c6a9c9636088713ad
+REG_FIDDLE(Paint_setStrokeMiter, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStrokeMiter(8);
+    paint.setStrokeMiter(-1);
+    SkDebugf("default miter limit == %g\n", paint.getStrokeMiter());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_setStrokeWidth.cpp b/docs/examples/Paint_setStrokeWidth.cpp
new file mode 100644
index 0000000..de99d49
--- /dev/null
+++ b/docs/examples/Paint_setStrokeWidth.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0c4446c0870b5c7b5a2efe77ff92afb8
+REG_FIDDLE(Paint_setStrokeWidth, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStrokeWidth(5);
+    paint.setStrokeWidth(-1);
+    SkDebugf("5 %c= paint.getStrokeWidth()\n", 5 == paint.getStrokeWidth() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_setStyle.cpp b/docs/examples/Paint_setStyle.cpp
new file mode 100644
index 0000000..05c24a9
--- /dev/null
+++ b/docs/examples/Paint_setStyle.cpp
@@ -0,0 +1,32 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c7bb6248e4735b8d1a32d02fba40d344
+REG_FIDDLE(Paint_setStyle, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStrokeWidth(5);
+    SkRegion region;
+    region.op(140, 10, 160, 30, SkRegion::kUnion_Op);
+    region.op(170, 40, 190, 60, SkRegion::kUnion_Op);
+    SkBitmap bitmap;
+    bitmap.setInfo(SkImageInfo::MakeA8(50, 50), 50);
+    uint8_t pixels[50][50];
+    for (int x = 0; x < 50; ++x) {
+        for (int y = 0; y < 50; ++y) {
+            pixels[y][x] = (x + y) % 5 ? 0xFF : 0x00;
+        }
+    }
+    bitmap.setPixels(pixels);
+    for (auto style : { SkPaint::kFill_Style,
+                        SkPaint::kStroke_Style,
+                        SkPaint::kStrokeAndFill_Style }) {
+        paint.setStyle(style);
+        canvas->drawLine(10, 10, 60, 60, paint);
+        canvas->drawRect({80, 10, 130, 60}, paint);
+        canvas->drawRegion(region, paint);
+        canvas->drawBitmap(bitmap, 200, 10, &paint);
+        canvas->translate(0, 80);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Paint_setSubpixelText.cpp b/docs/examples/Paint_setSubpixelText.cpp
new file mode 100644
index 0000000..28e324b
--- /dev/null
+++ b/docs/examples/Paint_setSubpixelText.cpp
@@ -0,0 +1,14 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a77bbc1a4e3be9a8ab0f842f877c5ee4
+REG_FIDDLE(Paint_setSubpixelText, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint1, paint2;
+    paint1.setSubpixelText(true);
+    paint2.setFlags(paint2.getFlags() | SkPaint::kSubpixelText_Flag);
+    SkDebugf("paint1 %c= paint2\n", paint1 == paint2 ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_setTextEncoding.cpp b/docs/examples/Paint_setTextEncoding.cpp
new file mode 100644
index 0000000..0cba688
--- /dev/null
+++ b/docs/examples/Paint_setTextEncoding.cpp
@@ -0,0 +1,13 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a5d1ba0dbf42afb797ffdb07647b5cb9
+REG_FIDDLE(Paint_setTextEncoding, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextEncoding((SkTextEncoding) 4);
+    SkDebugf("4 %c= text encoding\n", (SkTextEncoding) 4 == paint.getTextEncoding() ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_setTextScaleX.cpp b/docs/examples/Paint_setTextScaleX.cpp
new file mode 100644
index 0000000..41af1f2
--- /dev/null
+++ b/docs/examples/Paint_setTextScaleX.cpp
@@ -0,0 +1,13 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a75bbdb8bb866b125c4c1dd5e967d470
+REG_FIDDLE(Paint_setTextScaleX, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextScaleX(0.f / 0.f);
+    SkDebugf("text scale %s-a-number\n", SkScalarIsNaN(paint.getTextScaleX()) ? "not" : "is");
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_setTextSize.cpp b/docs/examples/Paint_setTextSize.cpp
new file mode 100644
index 0000000..31fa3ca
--- /dev/null
+++ b/docs/examples/Paint_setTextSize.cpp
@@ -0,0 +1,14 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6510c9e2f57b83c47e67829e7a68d493
+REG_FIDDLE(Paint_setTextSize, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkDebugf("12 %c= text size\n", 12 == paint.getTextSize() ? '=' : '!');
+    paint.setTextSize(-20);
+    SkDebugf("12 %c= text size\n", 12 == paint.getTextSize() ? '=' : '!');
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_setTextSkewX.cpp b/docs/examples/Paint_setTextSkewX.cpp
new file mode 100644
index 0000000..25e5a0b
--- /dev/null
+++ b/docs/examples/Paint_setTextSkewX.cpp
@@ -0,0 +1,13 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6bd705a6e0c5f8ee24f302fe531bfabc
+REG_FIDDLE(Paint_setTextSkewX, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextScaleX(1.f / 0.f);
+    SkDebugf("text scale %s-finite\n", SkScalarIsFinite(paint.getTextScaleX()) ? "is" : "not");
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_setTypeface.cpp b/docs/examples/Paint_setTypeface.cpp
new file mode 100644
index 0000000..70ab45b
--- /dev/null
+++ b/docs/examples/Paint_setTypeface.cpp
@@ -0,0 +1,15 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0e6fbb7773cd925b274552f4cd1abef2
+REG_FIDDLE(Paint_setTypeface, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTypeface(SkTypeface::MakeFromName("monospace", SkFontStyle()));
+    canvas->drawString("hamburgerfons", 10, 30, paint);
+    paint.setTypeface(nullptr);
+    canvas->drawString("hamburgerfons", 10, 50, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Paint_textToGlyphs.cpp b/docs/examples/Paint_textToGlyphs.cpp
new file mode 100644
index 0000000..ca0bab1
--- /dev/null
+++ b/docs/examples/Paint_textToGlyphs.cpp
@@ -0,0 +1,19 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d11136d8a74f63009da2a7f550710823
+REG_FIDDLE(Paint_textToGlyphs, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    const uint8_t utf8[] = { 0x24, 0xC2, 0xA2, 0xE2, 0x82, 0xAC, 0xC2, 0xA5, 0xC2, 0xA3 };
+    std::vector<SkGlyphID> glyphs;
+    int count = paint.textToGlyphs(utf8, sizeof(utf8), nullptr);
+    glyphs.resize(count);
+    (void) paint.textToGlyphs(utf8, sizeof(utf8), &glyphs.front());
+    paint.setTextEncoding(kGlyphID_SkTextEncoding);
+    paint.setTextSize(32);
+    canvas->drawText(&glyphs.front(), glyphs.size() * sizeof(SkGlyphID), 10, 40, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Path_AddPathMode.cpp b/docs/examples/Path_AddPathMode.cpp
new file mode 100644
index 0000000..4ac6c91
--- /dev/null
+++ b/docs/examples/Path_AddPathMode.cpp
@@ -0,0 +1,27 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=801b02e74c64aafdb734f2e5cf3e5ab0
+REG_FIDDLE(Path_AddPathMode, 256, 180, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path, path2;
+    path.moveTo(20, 20);
+    path.lineTo(20, 40);
+    path.lineTo(40, 20);
+    path2.moveTo(60, 60);
+    path2.lineTo(80, 60);
+    path2.lineTo(80, 40);
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    for (int i = 0; i < 2; i++) {
+        for (auto addPathMode : { SkPath::kAppend_AddPathMode, SkPath::kExtend_AddPathMode } ) {
+            SkPath test(path);
+            test.addPath(path2, addPathMode);
+            canvas->drawPath(test, paint);
+            canvas->translate(100, 0);
+        }
+        canvas->translate(-200, 100);
+        path.close();
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_ArcSize.cpp b/docs/examples/Path_ArcSize.cpp
new file mode 100644
index 0000000..5c70ead
--- /dev/null
+++ b/docs/examples/Path_ArcSize.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8e40c546eecd9cc213200717240898ba
+REG_FIDDLE(Path_ArcSize, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    for (auto sweep: { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {
+        for (auto arcSize : { SkPath::kSmall_ArcSize, SkPath::kLarge_ArcSize } ) {
+            SkPath path;
+            path.moveTo({120, 50});
+            path.arcTo(70, 40, 30, arcSize, sweep, 156, 100);
+            if (SkPath::kCCW_Direction == sweep && SkPath::kLarge_ArcSize == arcSize) {
+                paint.setColor(SK_ColorBLUE);
+                paint.setStrokeWidth(3);
+            }
+            canvas->drawPath(path, paint);
+         }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_ConvertConicToQuads.cpp b/docs/examples/Path_ConvertConicToQuads.cpp
new file mode 100644
index 0000000..46f6a88
--- /dev/null
+++ b/docs/examples/Path_ConvertConicToQuads.cpp
@@ -0,0 +1,28 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3ba94448a4ba48f926e643baeb5b1016
+REG_FIDDLE(Path_ConvertConicToQuads, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+      SkPaint conicPaint;
+      conicPaint.setAntiAlias(true);
+      conicPaint.setStyle(SkPaint::kStroke_Style);
+      SkPaint quadPaint(conicPaint);
+      quadPaint.setColor(SK_ColorRED);
+      SkPoint conic[] = { {20, 170}, {80, 170}, {80, 230} };
+      for (auto weight : { .25f, .5f, .707f, .85f, 1.f } ) {
+          SkPoint quads[5];
+          SkPath::ConvertConicToQuads(conic[0], conic[1], conic[2], weight, quads, 1);
+          SkPath path;
+          path.moveTo(conic[0]);
+          path.conicTo(conic[1], conic[2], weight);
+          canvas->drawPath(path, conicPaint);
+          path.rewind();
+          path.moveTo(quads[0]);
+          path.quadTo(quads[1], quads[2]);
+          path.quadTo(quads[3], quads[4]);
+          canvas->drawPath(path, quadPaint);
+          canvas->translate(50, -50);
+      }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_ConvertToNonInverseFillType.cpp b/docs/examples/Path_ConvertToNonInverseFillType.cpp
new file mode 100644
index 0000000..ef98ee0
--- /dev/null
+++ b/docs/examples/Path_ConvertToNonInverseFillType.cpp
@@ -0,0 +1,27 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=319f6b124458dcc0f9ce4d7bbde65810
+REG_FIDDLE(Path_ConvertToNonInverseFillType, 256, 256, true, 0) {
+#define nameValue(fill) { SkPath::fill, #fill }
+
+void draw(SkCanvas* canvas) {
+    struct {
+        SkPath::FillType fill;
+        const char* name;
+    } fills[] = {
+        nameValue(kWinding_FillType),
+        nameValue(kEvenOdd_FillType),
+        nameValue(kInverseWinding_FillType),
+        nameValue(kInverseEvenOdd_FillType),
+    };
+    for (unsigned i = 0; i < SK_ARRAY_COUNT(fills); ++i) {
+        if (fills[i].fill != (SkPath::FillType) i) {
+            SkDebugf("fills array order does not match FillType enum order");
+            break;
+        }
+        SkDebugf("ConvertToNonInverseFillType(%s) == %s\n", fills[i].name,
+                fills[(int) SkPath::ConvertToNonInverseFillType(fills[i].fill)].name);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_Convexity.cpp b/docs/examples/Path_Convexity.cpp
new file mode 100644
index 0000000..2c7bd7d
--- /dev/null
+++ b/docs/examples/Path_Convexity.cpp
@@ -0,0 +1,21 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ac49e8b810bd6ed5d84b4f5a3b40a0ec
+REG_FIDDLE(Path_Convexity, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkPoint quad[] = {{70, 70}, {20, 20}, {120, 20}, {120, 120}};
+    const char* labels[] = { "unknown", "convex", "concave" };
+    for (SkScalar x : { 40, 100 } ) {
+        SkPath path;
+        quad[0].fX = x;
+        path.addPoly(quad, SK_ARRAY_COUNT(quad), true);
+        canvas->drawPath(path, paint);
+        canvas->drawString(labels[(int) path.getConvexity()], 30, 100, paint);
+        canvas->translate(100, 100);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Path_Direction.cpp b/docs/examples/Path_Direction.cpp
new file mode 100644
index 0000000..a6ec07d
--- /dev/null
+++ b/docs/examples/Path_Direction.cpp
@@ -0,0 +1,32 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4bbae00b40ed2cfcd0007921ad693a7b
+REG_FIDDLE(Path_Direction, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    const SkPoint arrow[] = { {40, -5}, {45, 0}, {40, 5} };
+    const SkRect rect = {10, 10, 90, 90};
+    SkPaint rectPaint;
+    rectPaint.setAntiAlias(true);
+    SkPaint textPaint(rectPaint);
+    rectPaint.setStyle(SkPaint::kStroke_Style);
+    SkPaint arrowPaint(rectPaint);
+    SkPath arrowPath;
+    arrowPath.addPoly(arrow, SK_ARRAY_COUNT(arrow), true);
+    arrowPaint.setPathEffect(SkPath1DPathEffect::Make(arrowPath, 320, 0,
+                             SkPath1DPathEffect::kRotate_Style));
+    for (auto direction : { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {
+        canvas->drawRect(rect, rectPaint);
+        for (unsigned start : { 0, 1, 2, 3 } ) {
+           SkPath path;
+           path.addRect(rect, direction, start);
+           canvas->drawPath(path, arrowPaint);
+       }
+       canvas->drawString(SkPath::kCW_Direction == direction ? "CW" : "CCW",  rect.centerX(),
+            rect.centerY(), textPaint);
+       canvas->translate(120, 0);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Path_Effect_Methods.cpp b/docs/examples/Path_Effect_Methods.cpp
new file mode 100644
index 0000000..ed0b396
--- /dev/null
+++ b/docs/examples/Path_Effect_Methods.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8cf5684b187d60f09e11c4a48993ea39
+REG_FIDDLE(Path_Effect_Methods, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(16);
+    SkScalar intervals[] = {30, 10};
+    paint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 1));
+    canvas->drawRoundRect({20, 20, 120, 120}, 20, 20, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_FillType_a.cpp b/docs/examples/Path_FillType_a.cpp
new file mode 100644
index 0000000..08dadad
--- /dev/null
+++ b/docs/examples/Path_FillType_a.cpp
@@ -0,0 +1,27 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=71fc6c069c377d808799f2453edabaf5
+REG_FIDDLE(Path_FillType_a, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+   SkPath path;
+   path.addRect({10, 10, 30, 30}, SkPath::kCW_Direction);
+   path.addRect({20, 20, 40, 40}, SkPath::kCW_Direction);
+   path.addRect({10, 60, 30, 80}, SkPath::kCW_Direction);
+   path.addRect({20, 70, 40, 90}, SkPath::kCCW_Direction);
+   SkPaint strokePaint;
+   strokePaint.setStyle(SkPaint::kStroke_Style);
+   SkRect clipRect = {0, 0, 51, 100};
+   canvas->drawPath(path, strokePaint);
+   SkPaint fillPaint;
+   for (auto fillType : { SkPath::kWinding_FillType, SkPath::kEvenOdd_FillType,
+                      SkPath::kInverseWinding_FillType, SkPath::kInverseEvenOdd_FillType } ) {
+        canvas->translate(51, 0);
+        canvas->save();
+        canvas->clipRect(clipRect);
+        path.setFillType(fillType);
+        canvas->drawPath(path, fillPaint);
+        canvas->restore();
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_FillType_b.cpp b/docs/examples/Path_FillType_b.cpp
new file mode 100644
index 0000000..f6d64b3
--- /dev/null
+++ b/docs/examples/Path_FillType_b.cpp
@@ -0,0 +1,37 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d2c33dc791cd165dcc2423226ba5b095
+REG_FIDDLE(Path_FillType_b, 256, 230, false, 0) {
+void draw(SkCanvas* canvas) {
+   SkPath path;
+   path.addRect({20, 10, 80, 70}, SkPath::kCW_Direction);
+   path.addRect({40, 30, 100, 90}, SkPath::kCW_Direction);
+   SkPaint strokePaint;
+   strokePaint.setStyle(SkPaint::kStroke_Style);
+   SkRect clipRect = {0, 0, 128, 128};
+   canvas->drawPath(path, strokePaint);
+   canvas->drawLine({0, 50}, {120, 50}, strokePaint);
+   SkPaint textPaint;
+   textPaint.setAntiAlias(true);
+   SkScalar textHPos[] = { 10, 30, 60, 90, 110 };
+   canvas->drawPosTextH("01210", 5, textHPos, 48, textPaint);
+   textPaint.setTextSize(18);
+   canvas->translate(0, 128);
+   canvas->scale(.5f, .5f);
+   canvas->drawString("inverse", 384, 150, textPaint);
+   SkPaint fillPaint;
+   for (auto fillType : { SkPath::kWinding_FillType, SkPath::kEvenOdd_FillType,
+                      SkPath::kInverseWinding_FillType, SkPath::kInverseEvenOdd_FillType } ) {
+        canvas->save();
+        canvas->clipRect(clipRect);
+        path.setFillType(fillType);
+        canvas->drawPath(path, fillPaint);
+        canvas->restore();
+        canvas->drawString(fillType & 1 ? "even-odd" : "winding", 64, 170, textPaint);
+        canvas->translate(128, 0);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Path_IsCubicDegenerate.cpp b/docs/examples/Path_IsCubicDegenerate.cpp
new file mode 100644
index 0000000..6393337
--- /dev/null
+++ b/docs/examples/Path_IsCubicDegenerate.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6b97099acdae80b16df0c4241f593991
+REG_FIDDLE(Path_IsCubicDegenerate, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint points[] = {{1, 0}, {0, 0}, {0, 0}, {0, 0}};
+    SkScalar step = 1;
+    SkScalar prior, length = 0, degenerate = 0;
+    do {
+        prior = points[0].fX;
+        step /= 2;
+        if (SkPath::IsCubicDegenerate(points[0], points[1], points[2], points[3], false)) {
+            degenerate = prior;
+            points[0].fX += step;
+        } else {
+            length = prior;
+            points[0].fX -= step;
+        }
+    } while (prior != points[0].fX);
+    SkDebugf("%1.8g is degenerate\n", degenerate);
+    SkDebugf("%1.8g is length\n", length);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_IsInverseFillType.cpp b/docs/examples/Path_IsInverseFillType.cpp
new file mode 100644
index 0000000..087baa1
--- /dev/null
+++ b/docs/examples/Path_IsInverseFillType.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1453856a9d0c73e8192bf298c4143563
+REG_FIDDLE(Path_IsInverseFillType, 256, 256, true, 0) {
+#define nameValue(fill) { SkPath::fill, #fill }
+
+void draw(SkCanvas* canvas) {
+    struct {
+        SkPath::FillType fill;
+        const char* name;
+    } fills[] = {
+        nameValue(kWinding_FillType),
+        nameValue(kEvenOdd_FillType),
+        nameValue(kInverseWinding_FillType),
+        nameValue(kInverseEvenOdd_FillType),
+    };
+    for (auto fill: fills ) {
+        SkDebugf("IsInverseFillType(%s) == %s\n", fill.name, SkPath::IsInverseFillType(fill.fill) ?
+                 "true" : "false");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_IsLineDegenerate.cpp b/docs/examples/Path_IsLineDegenerate.cpp
new file mode 100644
index 0000000..0591b91
--- /dev/null
+++ b/docs/examples/Path_IsLineDegenerate.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=97a031f9186ade586928563840ce9116
+REG_FIDDLE(Path_IsLineDegenerate, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint points[] = { {100, 100}, {100.000001f, 100.000001f}, {100.0001f, 100.0001f} };
+    for (size_t i = 0; i < SK_ARRAY_COUNT(points) - 1; ++i) {
+        for (bool exact : { false, true } ) {
+            SkDebugf("line from (%1.8g,%1.8g) to (%1.8g,%1.8g) is %s" "degenerate, %s\n",
+                    points[i].fX, points[i].fY, points[i + 1].fX, points[i + 1].fY,
+                    SkPath::IsLineDegenerate(points[i], points[i + 1], exact)
+                    ? "" : "not ", exact ? "exactly" : "nearly");
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_IsQuadDegenerate.cpp b/docs/examples/Path_IsQuadDegenerate.cpp
new file mode 100644
index 0000000..9f5f070
--- /dev/null
+++ b/docs/examples/Path_IsQuadDegenerate.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a2b255a7dac1926cc3a247d318d63c62
+REG_FIDDLE(Path_IsQuadDegenerate, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const SkPath& path, bool exact) -> void {
+        SkDebugf("quad (%1.8g,%1.8g), (%1.8g,%1.8g), (%1.8g,%1.8g) is %s" "degenerate, %s\n",
+            path.getPoint(0).fX, path.getPoint(0).fY, path.getPoint(1).fX,
+            path.getPoint(1).fY, path.getPoint(2).fX, path.getPoint(2).fY,
+            SkPath::IsQuadDegenerate(path.getPoint(0), path.getPoint(1), path.getPoint(2), exact) ?
+            "" : "not ", exact ? "exactly" : "nearly");
+    };
+    SkPath path, offset;
+    path.moveTo({100, 100});
+    path.quadTo({100.00001f, 100.00001f}, {100.00002f, 100.00002f});
+    offset.addPath(path, 1000, 1000);
+    for (bool exact : { false, true } ) {
+        debugster(path, exact);
+        debugster(offset, exact);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_Iter.cpp b/docs/examples/Path_Iter.cpp
new file mode 100644
index 0000000..5eb9d52
--- /dev/null
+++ b/docs/examples/Path_Iter.cpp
@@ -0,0 +1,25 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2f53df9201769ab7e7c0e164a1334309
+REG_FIDDLE(Path_Iter, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(256);
+    SkPath asterisk, path;
+    paint.getTextPath("*", 1, 50, 192, &asterisk);
+    SkPath::Iter iter(asterisk, true);
+    SkPoint start[4], pts[4];
+    iter.next(start);  // skip moveTo
+    iter.next(start);  // first quadTo
+    path.moveTo((start[0] + start[1]) * 0.5f);
+    while (SkPath::kClose_Verb != iter.next(pts)) {
+        path.quadTo(pts[0], (pts[0] + pts[1]) * 0.5f);
+    }
+    path.quadTo(start[0], (start[0] + start[1]) * 0.5f);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Path_Iter_Iter.cpp b/docs/examples/Path_Iter_Iter.cpp
new file mode 100644
index 0000000..c21e1a9
--- /dev/null
+++ b/docs/examples/Path_Iter_Iter.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=01648775cb9b354b2f1836dad82a25ab
+REG_FIDDLE(Path_Iter_Iter, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath::Iter iter;
+    SkPoint points[4];
+    SkDebugf("iter is " "%s" "done\n", SkPath::kDone_Verb == iter.next(points) ? "" : "not ");
+    SkPath path;
+    iter.setPath(path, false);
+    SkDebugf("iter is " "%s" "done\n", SkPath::kDone_Verb == iter.next(points) ? "" : "not ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_Iter_conicWeight.cpp b/docs/examples/Path_Iter_conicWeight.cpp
new file mode 100644
index 0000000..72670d3
--- /dev/null
+++ b/docs/examples/Path_Iter_conicWeight.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7cdea37741d50f0594c6244eb07fd175
+REG_FIDDLE(Path_Iter_conicWeight, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+   SkPath path;
+   path.conicTo(1, 2, 3, 4, .5f);
+   SkPath::Iter iter(path, false);
+   SkPoint p[4];
+   SkDebugf("first verb is " "%s" "move\n", SkPath::kMove_Verb == iter.next(p) ? "" : "not ");
+   SkDebugf("next verb is " "%s" "conic\n", SkPath::kConic_Verb == iter.next(p) ? "" : "not ");
+   SkDebugf("conic points: {%g,%g}, {%g,%g}, {%g,%g}\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY,
+                p[2].fX, p[2].fY);
+   SkDebugf("conic weight: %g\n", iter.conicWeight());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_Iter_const_SkPath.cpp b/docs/examples/Path_Iter_const_SkPath.cpp
new file mode 100644
index 0000000..ede39cf
--- /dev/null
+++ b/docs/examples/Path_Iter_const_SkPath.cpp
@@ -0,0 +1,33 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=13044dbf68885c0f15322c0633b633a3
+REG_FIDDLE(Path_Iter_const_SkPath, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, SkPath::Iter& iter) -> void {
+        SkDebugf("%s:\n", prefix);
+        const char* verbStr[] =  { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };
+        const int pointCount[] = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };
+        SkPath::Verb verb;
+        do {
+           SkPoint points[4];
+           verb = iter.next(points);
+           SkDebugf("k%s_Verb ", verbStr[(int) verb]);
+           for (int i = 0; i < pointCount[(int) verb]; ++i) {
+                SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);
+           }
+           if (SkPath::kConic_Verb == verb) {
+               SkDebugf("weight = %g", iter.conicWeight());
+           }
+           SkDebugf("\n");
+        } while (SkPath::kDone_Verb != verb);
+        SkDebugf("\n");
+    };
+    SkPath path;
+    path.quadTo(10, 20, 30, 40);
+    SkPath::Iter openIter(path, false);
+    debugster("open", openIter);
+    SkPath::Iter closedIter(path, true);
+    debugster("closed", closedIter);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_Iter_isCloseLine.cpp b/docs/examples/Path_Iter_isCloseLine.cpp
new file mode 100644
index 0000000..6097cf6
--- /dev/null
+++ b/docs/examples/Path_Iter_isCloseLine.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7000b501f49341629bfdd9f80e686103
+REG_FIDDLE(Path_Iter_isCloseLine, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+   SkPath path;
+   path.moveTo(6, 7);
+   path.conicTo(1, 2, 3, 4, .5f);
+   path.close();
+   SkPath::Iter iter(path, false);
+   SkPoint p[4];
+   SkDebugf("1st verb is " "%s" "move\n", SkPath::kMove_Verb == iter.next(p) ? "" : "not ");
+   SkDebugf("moveTo point: {%g,%g}\n", p[0].fX, p[0].fY);
+   SkDebugf("2nd verb is " "%s" "conic\n", SkPath::kConic_Verb == iter.next(p) ? "" : "not ");
+   SkDebugf("3rd verb is " "%s" "line\n", SkPath::kLine_Verb == iter.next(p) ? "" : "not ");
+   SkDebugf("line points: {%g,%g}, {%g,%g}\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY);
+   SkDebugf("line " "%s" "generated by close\n", iter.isCloseLine() ? "" : "not ");
+   SkDebugf("4th verb is " "%s" "close\n", SkPath::kClose_Verb == iter.next(p) ? "" : "not ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_Iter_isClosedContour.cpp b/docs/examples/Path_Iter_isClosedContour.cpp
new file mode 100644
index 0000000..e8706b0
--- /dev/null
+++ b/docs/examples/Path_Iter_isClosedContour.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b0d48a6e949db1cb545216ae9c3c3c70
+REG_FIDDLE(Path_Iter_isClosedContour, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+   for (bool forceClose : { false, true } ) {
+       SkPath path;
+       path.conicTo(1, 2, 3, 4, .5f);
+       SkPath::Iter iter(path, forceClose);
+       SkDebugf("without close(), forceClose is %s: isClosedContour returns %s\n",
+           forceClose ? "true " : "false", iter.isClosedContour() ? "true" : "false");
+       path.close();
+       iter.setPath(path, forceClose);
+       SkDebugf("with close(),    forceClose is %s: isClosedContour returns %s\n",
+           forceClose ? "true " : "false", iter.isClosedContour() ? "true" : "false");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_Iter_next.cpp b/docs/examples/Path_Iter_next.cpp
new file mode 100644
index 0000000..48e1c3a
--- /dev/null
+++ b/docs/examples/Path_Iter_next.cpp
@@ -0,0 +1,38 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=00ae8984856486bdb626d0ed6587855a
+REG_FIDDLE(Path_Iter_next, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkPath& path, bool degen, bool exact) -> void {
+        SkPath::Iter iter(path, false);
+        SkDebugf("%s:\n", prefix);
+        const char* verbStr[] =  { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };
+        const int pointCount[] = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };
+        SkPath::Verb verb;
+        do {
+           SkPoint points[4];
+           verb = iter.next(points, degen, exact);
+           SkDebugf("k%s_Verb ", verbStr[(int) verb]);
+           for (int i = 0; i < pointCount[(int) verb]; ++i) {
+                SkDebugf("{%1.8g, %1.8g}, ", points[i].fX, points[i].fY);
+           }
+           SkDebugf("\n");
+        } while (SkPath::kDone_Verb != verb);
+        SkDebugf("\n");
+    };
+    SkPath path;
+    path.moveTo(10, 10);
+    path.moveTo(20, 20);
+    path.quadTo(10, 20, 30, 40);
+    path.moveTo(1, 1);
+    path.close();
+    path.moveTo(30, 30);
+    path.lineTo(30, 30);
+    path.moveTo(30, 30);
+    path.lineTo(30.00001f, 30);
+    debugster("skip degenerate", path, true, false);
+    debugster("skip degenerate if exact", path, true, true);
+    debugster("skip none", path, false, false);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_Iter_setPath.cpp b/docs/examples/Path_Iter_setPath.cpp
new file mode 100644
index 0000000..1db1f3b
--- /dev/null
+++ b/docs/examples/Path_Iter_setPath.cpp
@@ -0,0 +1,35 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6c9688008cea8937ad5cc188b38ecf16
+REG_FIDDLE(Path_Iter_setPath, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, SkPath::Iter& iter) -> void {
+        SkDebugf("%s:\n", prefix);
+        const char* verbStr[] =  { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };
+        const int pointCount[] = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };
+        SkPath::Verb verb;
+        do {
+           SkPoint points[4];
+           verb = iter.next(points);
+           SkDebugf("k%s_Verb ", verbStr[(int) verb]);
+           for (int i = 0; i < pointCount[(int) verb]; ++i) {
+                SkDebugf("{%g, %g}, ", points[i].fX, points[i].fY);
+           }
+           if (SkPath::kConic_Verb == verb) {
+               SkDebugf("weight = %g", iter.conicWeight());
+           }
+           SkDebugf("\n");
+        } while (SkPath::kDone_Verb != verb);
+        SkDebugf("\n");
+    };
+    SkPath path;
+    path.quadTo(10, 20, 30, 40);
+    SkPath::Iter iter(path, false);
+    debugster("quad open", iter);
+    SkPath path2;
+    path2.conicTo(1, 2, 3, 4, .5f);
+    iter.setPath(path2, true);
+    debugster("conic closed", iter);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_RawIter_conicWeight.cpp b/docs/examples/Path_RawIter_conicWeight.cpp
new file mode 100644
index 0000000..7d8daea
--- /dev/null
+++ b/docs/examples/Path_RawIter_conicWeight.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=69f360a0ba8f40c51ef4cd9f972c5893
+REG_FIDDLE(Path_RawIter_conicWeight, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+   SkPath path;
+   path.conicTo(1, 2, 3, 4, .5f);
+   SkPath::RawIter iter(path);
+   SkPoint p[4];
+   SkDebugf("first verb is " "%s" "move\n", SkPath::kMove_Verb == iter.next(p) ? "" : "not ");
+   SkDebugf("next verb is " "%s" "conic\n", SkPath::kConic_Verb == iter.next(p) ? "" : "not ");
+   SkDebugf("conic points: {%g,%g}, {%g,%g}, {%g,%g}\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY,
+                p[2].fX, p[2].fY);
+   SkDebugf("conic weight: %g\n", iter.conicWeight());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_RawIter_next.cpp b/docs/examples/Path_RawIter_next.cpp
new file mode 100644
index 0000000..ce7f968
--- /dev/null
+++ b/docs/examples/Path_RawIter_next.cpp
@@ -0,0 +1,31 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=944a80c7ff8c04e1fecc4aec4a47ea60
+REG_FIDDLE(Path_RawIter_next, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.moveTo(50, 60);
+    path.quadTo(10, 20, 30, 40);
+    path.close();
+    path.lineTo(30, 30);
+    path.conicTo(1, 2, 3, 4, .5f);
+    path.cubicTo(-1, -2, -3, -4, -5, -6);
+    SkPath::RawIter iter(path);
+    const char* verbStr[] =  { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };
+    const int pointCount[] = {     1 ,     2 ,     3 ,      3 ,      4 ,      1 ,     0  };
+    SkPath::Verb verb;
+    do {
+        SkPoint points[4];
+        verb = iter.next(points);
+        SkDebugf("k%s_Verb ", verbStr[(int) verb]);
+        for (int i = 0; i < pointCount[(int) verb]; ++i) {
+            SkDebugf("{%1.8g, %1.8g}, ", points[i].fX, points[i].fY);
+        }
+        if (SkPath::kConic_Verb == verb) {
+            SkDebugf("weight = %g", iter.conicWeight());
+        }
+        SkDebugf("\n");
+    } while (SkPath::kDone_Verb != verb);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_RawIter_peek.cpp b/docs/examples/Path_RawIter_peek.cpp
new file mode 100644
index 0000000..57f648a
--- /dev/null
+++ b/docs/examples/Path_RawIter_peek.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=eb5fa5bea23059ce538e883502f828f5
+REG_FIDDLE(Path_RawIter_peek, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.quadTo(10, 20, 30, 40);
+    path.conicTo(1, 2, 3, 4, .5f);
+    path.cubicTo(1, 2, 3, 4, .5, 6);
+    SkPath::RawIter iter(path);
+    SkPath::Verb verb, peek = iter.peek();
+    const char* verbStr[] =  { "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done" };
+    do {
+        SkPoint points[4];
+        verb = iter.next(points);
+        SkDebugf("peek %s %c= verb %s\n", verbStr[peek], peek == verb ? '=' : '!', verbStr[verb]);
+        peek = iter.peek();
+    } while (SkPath::kDone_Verb != verb);
+    SkDebugf("peek %s %c= verb %s\n", verbStr[peek], peek == verb ? '=' : '!', verbStr[verb]);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_SegmentMask.cpp b/docs/examples/Path_SegmentMask.cpp
new file mode 100644
index 0000000..68f244f
--- /dev/null
+++ b/docs/examples/Path_SegmentMask.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a61e5758574e28190ec4ed8c4ae7e7fa
+REG_FIDDLE(Path_SegmentMask, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.conicTo(10, 10, 20, 30, 1);
+    SkDebugf("Path kConic_SegmentMask is %s\n", path.getSegmentMasks() &
+          SkPath::kConic_SegmentMask ? "set" : "clear");
+    SkDebugf("Path kQuad_SegmentMask is %s\n", path.getSegmentMasks() &
+          SkPath::kQuad_SegmentMask ? "set" : "clear");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_Verb.cpp b/docs/examples/Path_Verb.cpp
new file mode 100644
index 0000000..2af35b5
--- /dev/null
+++ b/docs/examples/Path_Verb.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=799096fdc1298aa815934a74e76570ca
+REG_FIDDLE(Path_Verb, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.lineTo(20, 20);
+    path.quadTo(-10, -10, 30, 30);
+    path.close();
+    path.cubicTo(1, 2, 3, 4, 5, 6);
+    path.conicTo(0, 0, 0, 0, 2);
+    uint8_t verbs[7];
+    int count = path.getVerbs(verbs, (int) SK_ARRAY_COUNT(verbs));
+    const char* verbStr[] = { "Move", "Line", "Quad", "Conic", "Cubic", "Close" };
+    SkDebugf("verb count: %d\nverbs: ", count);
+    for (int i = 0; i < count; ++i) {
+        SkDebugf("k%s_Verb ", verbStr[verbs[i]]);
+    }
+    SkDebugf("\n");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_addArc.cpp b/docs/examples/Path_addArc.cpp
new file mode 100644
index 0000000..30801eb
--- /dev/null
+++ b/docs/examples/Path_addArc.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9cf5122475624e4cf39f06c698f80b1a
+REG_FIDDLE(Path_addArc, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    for (auto start : { 0, 90, 135, 180, 270 } ) {
+        for (auto sweep : { -450.f, -180.f, -90.f, 90.f, 180.f, 360.1f } ) {
+            SkPath path;
+            path.addArc({10, 10, 35, 45}, start, sweep);
+            canvas->drawPath(path, paint);
+            canvas->translate(252 / 6, 0);
+        }
+        canvas->translate(-252, 255 / 5);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_addCircle.cpp b/docs/examples/Path_addCircle.cpp
new file mode 100644
index 0000000..2c92cdf
--- /dev/null
+++ b/docs/examples/Path_addCircle.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bd5286cb9a5e5c32cd980f72b8f400fb
+REG_FIDDLE(Path_addCircle, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(10);
+    for (int size = 10; size < 300; size += 20) {
+        SkPath path;
+        path.addCircle(128, 128, size, SkPath::kCW_Direction);
+        canvas->drawPath(path, paint);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_addOval.cpp b/docs/examples/Path_addOval.cpp
new file mode 100644
index 0000000..e1e2ee6
--- /dev/null
+++ b/docs/examples/Path_addOval.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=cac84cf68e63a453c2a8b64c91537704
+REG_FIDDLE(Path_addOval, 256, 120, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkPath oval;
+    oval.addOval({20, 20, 160, 80});
+    canvas->drawPath(oval, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_addOval_2.cpp b/docs/examples/Path_addOval_2.cpp
new file mode 100644
index 0000000..ae9de3f
--- /dev/null
+++ b/docs/examples/Path_addOval_2.cpp
@@ -0,0 +1,34 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f1122d6fffddac0167e96fab4b9a862f
+REG_FIDDLE(Path_addOval_2, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    const SkPoint arrow[] = { {0, -5}, {10, 0}, {0, 5} };
+    const SkRect rect = {10, 10, 54, 54};
+    SkPaint ovalPaint;
+    ovalPaint.setAntiAlias(true);
+    SkPaint textPaint(ovalPaint);
+    ovalPaint.setStyle(SkPaint::kStroke_Style);
+    SkPaint arrowPaint(ovalPaint);
+    SkPath arrowPath;
+    arrowPath.addPoly(arrow, SK_ARRAY_COUNT(arrow), true);
+    arrowPaint.setPathEffect(SkPath1DPathEffect::Make(arrowPath, 176, 0,
+                             SkPath1DPathEffect::kRotate_Style));
+    for (auto direction : { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {
+        for (unsigned start : { 0, 1, 2, 3 } ) {
+           SkPath path;
+           path.addOval(rect, direction, start);
+           canvas->drawPath(path, ovalPaint);
+           canvas->drawPath(path, arrowPaint);
+           canvas->drawText(&"0123"[start], 1, rect.centerX(), rect.centerY() + 5, textPaint);
+           canvas->translate(64, 0);
+       }
+       canvas->translate(-256, 72);
+       canvas->drawString(SkPath::kCW_Direction == direction ? "clockwise" : "counterclockwise",
+                          128, 0, textPaint);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Path_addPath.cpp b/docs/examples/Path_addPath.cpp
new file mode 100644
index 0000000..b9a37f7
--- /dev/null
+++ b/docs/examples/Path_addPath.cpp
@@ -0,0 +1,22 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c416bddfe286628974e1c7f0fd66f3f4
+REG_FIDDLE(Path_addPath, 256, 180, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(128);
+    paint.setFakeBoldText(true);
+    SkPath dest, text;
+    paint.getTextPath("O", 1, 50, 120, &text);
+    for (int i = 0; i < 3; i++) {
+        dest.addPath(text, i * 20, i * 20);
+    }
+    Simplify(dest, &dest);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(3);
+    canvas->drawPath(dest, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Path_addPath_2.cpp b/docs/examples/Path_addPath_2.cpp
new file mode 100644
index 0000000..138764b
--- /dev/null
+++ b/docs/examples/Path_addPath_2.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=84b2d1c0fc29f1b35e855b6fc6672f9e
+REG_FIDDLE(Path_addPath_2, 256, 80, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkPath dest, path;
+    path.addOval({-80, 20, 0, 60}, SkPath::kCW_Direction, 1);
+    for (int i = 0; i < 2; i++) {
+        dest.addPath(path, SkPath::kExtend_AddPathMode);
+        dest.offset(100, 0);
+    }
+    canvas->drawPath(dest, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_addPath_3.cpp b/docs/examples/Path_addPath_3.cpp
new file mode 100644
index 0000000..d0ebe97
--- /dev/null
+++ b/docs/examples/Path_addPath_3.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3a90a91030f7289d5df0671d342dbbad
+REG_FIDDLE(Path_addPath_3, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkPath dest, path;
+    path.addOval({20, 20, 200, 120}, SkPath::kCW_Direction, 1);
+    for (int i = 0; i < 6; i++) {
+        SkMatrix matrix;
+        matrix.reset();
+        matrix.setPerspX(i / 400.f);
+        dest.addPath(path, matrix);
+    }
+    canvas->drawPath(dest, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_addPoly.cpp b/docs/examples/Path_addPoly.cpp
new file mode 100644
index 0000000..a897e28
--- /dev/null
+++ b/docs/examples/Path_addPoly.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=182b3999772f330f3b0b891b492634ae
+REG_FIDDLE(Path_addPoly, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStrokeWidth(15);
+    paint.setStrokeCap(SkPaint::kRound_Cap);
+    const SkPoint points[] = {{20, 20}, {70, 20}, {40, 90}};
+    for (bool close : { false, true } ) {
+        SkPath path;
+        path.addPoly(points, SK_ARRAY_COUNT(points), close);
+        for (auto style : {SkPaint::kStroke_Style, SkPaint::kFill_Style,
+                SkPaint::kStrokeAndFill_Style} ) {
+            paint.setStyle(style);
+            canvas->drawPath(path, paint);
+            canvas->translate(85, 0);
+        }
+        canvas->translate(-255, 128);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_addPoly_2.cpp b/docs/examples/Path_addPoly_2.cpp
new file mode 100644
index 0000000..3a23491
--- /dev/null
+++ b/docs/examples/Path_addPoly_2.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1a6b69acad5ceafede3c5984ec6634cb
+REG_FIDDLE(Path_addPoly_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStrokeWidth(15);
+    paint.setStrokeCap(SkPaint::kRound_Cap);
+    for (bool close : { false, true } ) {
+        SkPath path;
+        path.addPoly({{20, 20}, {70, 20}, {40, 90}}, close);
+        for (auto style : {SkPaint::kStroke_Style, SkPaint::kFill_Style,
+                SkPaint::kStrokeAndFill_Style} ) {
+            paint.setStyle(style);
+            canvas->drawPath(path, paint);
+            canvas->translate(85, 0);
+        }
+        canvas->translate(-255, 128);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_addRRect.cpp b/docs/examples/Path_addRRect.cpp
new file mode 100644
index 0000000..eb854dc
--- /dev/null
+++ b/docs/examples/Path_addRRect.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d9ecd58081b5bc77a157636fcb345dc6
+REG_FIDDLE(Path_addRRect, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkRRect rrect;
+    SkVector radii[] = {{50, 50}, {0, 0}, {0, 0}, {50, 50}};
+    rrect.setRectRadii({10, 10, 110, 110}, radii);
+    SkPath path;
+    SkMatrix rotate90;
+    rotate90.setRotate(90, 128, 128);
+    for (int i = 0; i < 4; ++i) {
+        path.addRRect(rrect);
+        path.transform(rotate90);
+    }
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_addRRect_2.cpp b/docs/examples/Path_addRRect_2.cpp
new file mode 100644
index 0000000..ff518a8
--- /dev/null
+++ b/docs/examples/Path_addRRect_2.cpp
@@ -0,0 +1,30 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=888edd4c4a91ca62ceb01bce8ab675b2
+REG_FIDDLE(Path_addRRect_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkRRect rrect;
+    rrect.setRectXY({40, 40, 215, 215}, 50, 50);
+    SkPath path;
+    path.addRRect(rrect);
+    canvas->drawPath(path, paint);
+    for (int start = 0; start < 8; ++start) {
+        SkPath textPath;
+        textPath.addRRect(rrect, SkPath::kCW_Direction, start);
+        SkPathMeasure pathMeasure(textPath, false);
+        SkPoint position;
+        SkVector tangent;
+        if (!pathMeasure.getPosTan(0, &position, &tangent)) {
+            continue;
+        }
+        SkRSXform rsxForm = SkRSXform::Make(tangent.fX, tangent.fY,
+               position.fX + tangent.fY * 5, position.fY - tangent.fX * 5);
+        SkFont font(nullptr, 12);
+        auto labels = SkTextBlob::MakeFromRSXform(&"01234567"[start], 1, &rsxForm, font);
+        canvas->drawTextBlob(labels, 0, 0, paint);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_addRect.cpp b/docs/examples/Path_addRect.cpp
new file mode 100644
index 0000000..7b37848
--- /dev/null
+++ b/docs/examples/Path_addRect.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0f841e4eaebb613b5069800567917c2d
+REG_FIDDLE(Path_addRect, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStrokeWidth(15);
+    paint.setStrokeCap(SkPaint::kSquare_Cap);
+    float intervals[] = { 5, 21.75f };
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0));
+    SkPath path;
+    path.addRect({20, 20, 100, 100}, SkPath::kCW_Direction);
+    canvas->drawPath(path, paint);
+    path.rewind();
+    path.addRect({140, 20, 220, 100}, SkPath::kCCW_Direction);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_addRect_2.cpp b/docs/examples/Path_addRect_2.cpp
new file mode 100644
index 0000000..1a68fb6
--- /dev/null
+++ b/docs/examples/Path_addRect_2.cpp
@@ -0,0 +1,28 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9202430b3f4f5275af8eec5cc9d7baa8
+REG_FIDDLE(Path_addRect_2, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    const SkPoint arrow[] = { {5, -5}, {15, -5}, {20, 0}, {15, 5}, {5, 5}, {10, 0} };
+    const SkRect rect = {10, 10, 54, 54};
+    SkPaint rectPaint;
+    rectPaint.setAntiAlias(true);
+    rectPaint.setStyle(SkPaint::kStroke_Style);
+    SkPaint arrowPaint(rectPaint);
+    SkPath arrowPath;
+    arrowPath.addPoly(arrow, SK_ARRAY_COUNT(arrow), true);
+    arrowPaint.setPathEffect(SkPath1DPathEffect::Make(arrowPath, 176, 0,
+                             SkPath1DPathEffect::kRotate_Style));
+    for (auto direction : { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {
+        for (unsigned start : { 0, 1, 2, 3 } ) {
+           SkPath path;
+           path.addRect(rect, direction, start);
+           canvas->drawPath(path, rectPaint);
+           canvas->drawPath(path, arrowPaint);
+           canvas->translate(64, 0);
+       }
+       canvas->translate(-256, 64);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_addRect_3.cpp b/docs/examples/Path_addRect_3.cpp
new file mode 100644
index 0000000..674cd7c
--- /dev/null
+++ b/docs/examples/Path_addRect_3.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3837827310e8b88b8c2e128ef9fbbd65
+REG_FIDDLE(Path_addRect_3, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStrokeWidth(15);
+    paint.setStrokeCap(SkPaint::kSquare_Cap);
+    float intervals[] = { 5, 21.75f };
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0));
+    for (auto direction : { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {
+        SkPath path;
+        path.addRect(20, 20, 100, 100, direction);
+        canvas->drawPath(path, paint);
+        canvas->translate(128, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_addRoundRect.cpp b/docs/examples/Path_addRoundRect.cpp
new file mode 100644
index 0000000..7d78cf8
--- /dev/null
+++ b/docs/examples/Path_addRoundRect.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=24736f685f265cf533f1700c042db353
+REG_FIDDLE(Path_addRoundRect, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    for (auto xradius : { 0, 7, 13, 20 } ) {
+        for (auto yradius : { 0, 9, 18, 40 } ) {
+            SkPath path;
+            path.addRoundRect({10, 10, 36, 46}, xradius, yradius);
+            paint.setColor(path.isRect(nullptr) ? SK_ColorRED : path.isOval(nullptr) ?
+                           SK_ColorBLUE : path.isConvex() ? SK_ColorGRAY : SK_ColorGREEN);
+            canvas->drawPath(path, paint);
+            canvas->translate(64, 0);
+        }
+        canvas->translate(-256, 64);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_addRoundRect_2.cpp b/docs/examples/Path_addRoundRect_2.cpp
new file mode 100644
index 0000000..6dcb54a
--- /dev/null
+++ b/docs/examples/Path_addRoundRect_2.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c43d70606b4ee464d2befbcf448c5e73
+REG_FIDDLE(Path_addRoundRect_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkScalar radii[] = { 80, 100, 0, 0, 40, 60, 0, 0 };
+    SkPath path;
+    SkMatrix rotate90;
+    rotate90.setRotate(90, 128, 128);
+    for (int i = 0; i < 4; ++i) {
+        path.addRoundRect({10, 10, 110, 110}, radii);
+        path.transform(rotate90);
+    }
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_arcTo.cpp b/docs/examples/Path_arcTo.cpp
new file mode 100644
index 0000000..c93e81d
--- /dev/null
+++ b/docs/examples/Path_arcTo.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5f02890edaa10cb5e1a4243a82b6a382
+REG_FIDDLE(Path_arcTo, 256, 200, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkPath path;
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(4);
+    path.moveTo(0, 0);
+    path.arcTo({20, 20, 120, 120}, -90, 90, false);
+    canvas->drawPath(path, paint);
+    path.rewind();
+    path.arcTo({120, 20, 220, 120}, -90, 90, false);
+    canvas->drawPath(path, paint);
+    path.rewind();
+    path.moveTo(0, 0);
+    path.arcTo({20, 120, 120, 220}, -90, 90, true);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_arcTo_2_a.cpp b/docs/examples/Path_arcTo_2_a.cpp
new file mode 100644
index 0000000..57163a7
--- /dev/null
+++ b/docs/examples/Path_arcTo_2_a.cpp
@@ -0,0 +1,42 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=386000684073fccabc224d7d6dc81cd9
+REG_FIDDLE(Path_arcTo_2_a, 256, 226, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint tangentPaint;
+    tangentPaint.setAntiAlias(true);
+    SkPaint textPaint(tangentPaint);
+    tangentPaint.setStyle(SkPaint::kStroke_Style);
+    tangentPaint.setColor(SK_ColorGRAY);
+    SkPaint arcPaint(tangentPaint);
+    arcPaint.setStrokeWidth(5);
+    arcPaint.setColor(SK_ColorBLUE);
+    SkPath path;
+    SkPoint pts[] = { {56, 20}, {200, 20}, {90, 190} };
+    SkScalar radius = 50;
+    path.moveTo(pts[0]);
+    path.arcTo(pts[1], pts[2], radius);
+    canvas->drawLine(pts[0], pts[1], tangentPaint);
+    canvas->drawLine(pts[1], pts[2], tangentPaint);
+    SkPoint lastPt;
+    (void) path.getLastPt(&lastPt);
+    SkVector radial = pts[2] - pts[1];
+    radial.setLength(radius);
+    SkPoint center = { lastPt.fX - radial.fY, lastPt.fY + radial.fX };
+    canvas->drawCircle(center, radius, tangentPaint);
+    canvas->drawLine(lastPt, center, tangentPaint);
+    radial = pts[1] - pts[0];
+    radial.setLength(radius);
+    SkPoint arcStart = { center.fX + radial.fY, center.fY - radial.fX };
+    canvas->drawLine(center, arcStart, tangentPaint);
+    canvas->drawPath(path, arcPaint);
+    canvas->drawString("(x0, y0)", pts[0].fX - 5, pts[0].fY, textPaint);
+    canvas->drawString("(x1, y1)", pts[1].fX + 5, pts[1].fY, textPaint);
+    canvas->drawString("(x2, y2)", pts[2].fX, pts[2].fY + 15, textPaint);
+    canvas->drawString("radius", center.fX + 15, center.fY + 25, textPaint);
+    canvas->drawString("radius", center.fX - 3, center.fY - 16, textPaint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Path_arcTo_2_b.cpp b/docs/examples/Path_arcTo_2_b.cpp
new file mode 100644
index 0000000..145bb17
--- /dev/null
+++ b/docs/examples/Path_arcTo_2_b.cpp
@@ -0,0 +1,41 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=78f3c65fa900610bb52518989b547095
+REG_FIDDLE(Path_arcTo_2_b, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint tangentPaint;
+    tangentPaint.setAntiAlias(true);
+    SkPaint textPaint(tangentPaint);
+    tangentPaint.setStyle(SkPaint::kStroke_Style);
+    tangentPaint.setColor(SK_ColorGRAY);
+    SkPaint arcPaint(tangentPaint);
+    arcPaint.setStrokeWidth(5);
+    arcPaint.setColor(SK_ColorBLUE);
+    SkPath path;
+    SkPoint pts[] = { {156, 20}, {200, 20}, {170, 50} };
+    SkScalar radius = 50;
+    path.moveTo(pts[0]);
+    path.arcTo(pts[1], pts[2], radius);
+    canvas->drawLine(pts[0], pts[1], tangentPaint);
+    canvas->drawLine(pts[1], pts[2], tangentPaint);
+    SkPoint lastPt;
+    (void) path.getLastPt(&lastPt);
+    SkVector radial = pts[2] - pts[1];
+    radial.setLength(radius);
+    SkPoint center = { lastPt.fX - radial.fY, lastPt.fY + radial.fX };
+    canvas->drawLine(lastPt, center, tangentPaint);
+    radial = pts[1] - pts[0];
+    radial.setLength(radius);
+    SkPoint arcStart = { center.fX + radial.fY, center.fY - radial.fX };
+    canvas->drawLine(center, arcStart, tangentPaint);
+    canvas->drawPath(path, arcPaint);
+    canvas->drawString("(x0, y0)", pts[0].fX, pts[0].fY - 7, textPaint);
+    canvas->drawString("(x1, y1)", pts[1].fX + 5, pts[1].fY, textPaint);
+    canvas->drawString("(x2, y2)", pts[2].fX, pts[2].fY + 15, textPaint);
+    canvas->drawString("radius", center.fX + 15, center.fY + 25, textPaint);
+    canvas->drawString("radius", center.fX - 5, center.fY - 20, textPaint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Path_arcTo_2_c.cpp b/docs/examples/Path_arcTo_2_c.cpp
new file mode 100644
index 0000000..2f81a2f
--- /dev/null
+++ b/docs/examples/Path_arcTo_2_c.cpp
@@ -0,0 +1,30 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=498360fa0a201cc5db04b1c27256358f
+REG_FIDDLE(Path_arcTo_2_c, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.moveTo({156, 20});
+    path.arcTo(200, 20, 170, 50, 50);
+    SkPath::Iter iter(path, false);
+    SkPoint p[4];
+    SkPath::Verb verb;
+    while (SkPath::kDone_Verb != (verb = iter.next(p))) {
+        switch (verb) {
+            case SkPath::kMove_Verb:
+                SkDebugf("move to (%g,%g)\n", p[0].fX, p[0].fY);
+                break;
+            case SkPath::kLine_Verb:
+                SkDebugf("line (%g,%g),(%g,%g)\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY);
+                break;
+            case SkPath::kConic_Verb:
+                SkDebugf("conic (%g,%g),(%g,%g),(%g,%g) weight %g\n",
+                         p[0].fX, p[0].fY, p[1].fX, p[1].fY, p[2].fX, p[2].fY, iter.conicWeight());
+                break;
+            default:
+                SkDebugf("unexpected verb\n");
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_arcTo_3.cpp b/docs/examples/Path_arcTo_3.cpp
new file mode 100644
index 0000000..0816c00
--- /dev/null
+++ b/docs/examples/Path_arcTo_3.cpp
@@ -0,0 +1,30 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0c056264a361579c18e5d02d3172d4d4
+REG_FIDDLE(Path_arcTo_3, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.moveTo({156, 20});
+    path.arcTo({200, 20}, {170, 20}, 50);
+    SkPath::Iter iter(path, false);
+    SkPoint p[4];
+    SkPath::Verb verb;
+    while (SkPath::kDone_Verb != (verb = iter.next(p))) {
+        switch (verb) {
+            case SkPath::kMove_Verb:
+                SkDebugf("move to (%g,%g)\n", p[0].fX, p[0].fY);
+                break;
+            case SkPath::kLine_Verb:
+                SkDebugf("line (%g,%g),(%g,%g)\n", p[0].fX, p[0].fY, p[1].fX, p[1].fY);
+                break;
+            case SkPath::kConic_Verb:
+                SkDebugf("conic (%g,%g),(%g,%g),(%g,%g) weight %g\n",
+                          p[0].fX, p[0].fY, p[1].fX, p[1].fY, p[2].fX, p[2].fY, iter.conicWeight());
+                break;
+            default:
+                SkDebugf("unexpected verb\n");
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_arcTo_4.cpp b/docs/examples/Path_arcTo_4.cpp
new file mode 100644
index 0000000..043e72a
--- /dev/null
+++ b/docs/examples/Path_arcTo_4.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6b6ea44f659b27918f3a6fa621bf6173
+REG_FIDDLE(Path_arcTo_4, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    for (auto sweep: { SkPath::kCW_Direction, SkPath::kCCW_Direction } ) {
+        for (auto arcSize : { SkPath::kSmall_ArcSize, SkPath::kLarge_ArcSize } ) {
+            SkPath path;
+            path.moveTo({120, 50});
+            path.arcTo(70, 40, 30, arcSize, sweep, 120.1f, 50);
+            if (SkPath::kCCW_Direction == sweep && SkPath::kLarge_ArcSize == arcSize) {
+                paint.setColor(SK_ColorBLUE);
+                paint.setStrokeWidth(3);
+            }
+            canvas->drawPath(path, paint);
+         }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_close.cpp b/docs/examples/Path_close.cpp
new file mode 100644
index 0000000..74681ab
--- /dev/null
+++ b/docs/examples/Path_close.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9235f6309271d6420fa5c45dc28664c5
+REG_FIDDLE(Path_close, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStrokeWidth(15);
+    paint.setStrokeCap(SkPaint::kRound_Cap);
+    SkPath path;
+    const SkPoint points[] = {{20, 20}, {70, 20}, {40, 90}};
+    path.addPoly(points, SK_ARRAY_COUNT(points), false);
+    for (int loop = 0; loop < 2; ++loop) {
+        for (auto style : {SkPaint::kStroke_Style, SkPaint::kFill_Style,
+                SkPaint::kStrokeAndFill_Style} ) {
+            paint.setStyle(style);
+            canvas->drawPath(path, paint);
+            canvas->translate(85, 0);
+        }
+        path.close();
+        canvas->translate(-255, 128);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_computeTightBounds.cpp b/docs/examples/Path_computeTightBounds.cpp
new file mode 100644
index 0000000..4a54c00
--- /dev/null
+++ b/docs/examples/Path_computeTightBounds.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9a39c56e95b19a657133b7ad1fe0cf03
+REG_FIDDLE(Path_computeTightBounds, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkPath& path) -> void {
+            const SkRect& bounds = path.computeTightBounds();
+            SkDebugf("%s bounds = %g, %g, %g, %g\n", prefix,
+                     bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
+    };
+    SkPath path;
+    debugster("empty", path);
+    path.addCircle(50, 45, 25);
+    debugster("circle", path);
+    SkMatrix matrix;
+    matrix.setRotate(45, 50, 45);
+    path.transform(matrix);
+    debugster("rotated circle", path);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_conicTo.cpp b/docs/examples/Path_conicTo.cpp
new file mode 100644
index 0000000..901d184
--- /dev/null
+++ b/docs/examples/Path_conicTo.cpp
@@ -0,0 +1,25 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=358d9b6060b528b0923c007420f09c13
+REG_FIDDLE(Path_conicTo, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkPoint conicPts[] = {{20, 150}, {120, 10}, {220, 150}};
+    canvas->drawLine(conicPts[0], conicPts[1], paint);
+    canvas->drawLine(conicPts[1], conicPts[2], paint);
+    SkColor colors[] = { 0xff88ff00, 0xff0088bb, 0xff6600cc, 0xffbb3377 };
+    paint.setStrokeWidth(3);
+    SkScalar weight = 0.5f;
+    for (unsigned i = 0; i < SK_ARRAY_COUNT(colors); ++i) {
+        SkPath path;
+        path.moveTo(conicPts[0]);
+        path.conicTo(conicPts[1], conicPts[2], weight);
+        paint.setColor(colors[i]);
+        canvas->drawPath(path, paint);
+        weight += 0.25f;
+   }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_conicTo_2.cpp b/docs/examples/Path_conicTo_2.cpp
new file mode 100644
index 0000000..4c5f5fb
--- /dev/null
+++ b/docs/examples/Path_conicTo_2.cpp
@@ -0,0 +1,29 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=22d25e03b19d5bae92118877e462361b
+REG_FIDDLE(Path_conicTo_2, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkRect oval = {0, 20, 120, 140};
+    SkPath path;
+    for (int i = 0; i < 4; ++i) {
+        path.moveTo(oval.centerX(), oval.fTop);
+        path.arcTo(oval, -90, 90 - 20 * i, false);
+        oval.inset(15, 15);
+    }
+    path.offset(100, 0);
+    SkScalar conicWeights[] = { 0.707107f, 0.819152f, 0.906308f, 0.965926f };
+    SkPoint conicPts[][3] = { { {40, 20}, {100, 20}, {100, 80} },
+                              { {40, 35}, {71.509f, 35}, {82.286f, 64.6091f} },
+                              { {40, 50}, {53.9892f, 50}, {62.981f, 60.7164f} },
+                              { {40, 65}, {44.0192f, 65}, {47.5f, 67.0096f} } };
+    for (int i = 0; i < 4; ++i) {
+         path.moveTo(conicPts[i][0]);
+         path.conicTo(conicPts[i][1], conicPts[i][2], conicWeights[i]);
+    }
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_conservativelyContainsRect.cpp b/docs/examples/Path_conservativelyContainsRect.cpp
new file mode 100644
index 0000000..549f9ee
--- /dev/null
+++ b/docs/examples/Path_conservativelyContainsRect.cpp
@@ -0,0 +1,25 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=41638d13e40fa449ece354dde5fb1941
+REG_FIDDLE(Path_conservativelyContainsRect, 256, 140, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.addRoundRect({10, 20, 54, 120}, 10, 20);
+    SkRect tests[] = {
+      { 10, 40, 54, 80 },
+      { 25, 20, 39, 120 },
+      { 15, 25, 49, 115 },
+      { 13, 27, 51, 113 },
+    };
+    for (unsigned i = 0; i < SK_ARRAY_COUNT(tests); ++i) {
+      SkPaint paint;
+      paint.setColor(SK_ColorRED);
+      canvas->drawPath(path, paint);
+      bool rectInPath = path.conservativelyContainsRect(tests[i]);
+      paint.setColor(rectInPath ? SK_ColorBLUE : SK_ColorBLACK);
+      canvas->drawRect(tests[i], paint);
+      canvas->translate(64, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_contains.cpp b/docs/examples/Path_contains.cpp
new file mode 100644
index 0000000..6ef4673
--- /dev/null
+++ b/docs/examples/Path_contains.cpp
@@ -0,0 +1,26 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c0216b3f7ebd80b9589ae5728f08fc80
+REG_FIDDLE(Path_contains, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    SkPaint paint;
+    paint.setTextSize(256);
+    paint.getTextPath("&", 1, 30, 220, &path);
+    for (int y = 2; y < 256; y += 9) {
+       for (int x = 2; x < 256; x += 9) {
+           int coverage = 0;
+           for (int iy = -4; iy <= 4; iy += 2) {
+               for (int ix = -4; ix <= 4; ix += 2) {
+                   coverage += path.contains(x + ix, y + iy);
+               }
+           }
+           paint.setColor(SkColorSetARGB(0x5f, 0xff * coverage / 25, 0, 0xff * (25 - coverage) / 25));
+           canvas->drawCircle(x, y, 8, paint);
+       }
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Path_copy_const_SkPath.cpp b/docs/examples/Path_copy_const_SkPath.cpp
new file mode 100644
index 0000000..5442aee
--- /dev/null
+++ b/docs/examples/Path_copy_const_SkPath.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=647312aacd946c8a6eabaca797140432
+REG_FIDDLE(Path_copy_const_SkPath, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.lineTo(20, 20);
+    SkPath path2(path);
+    path2.close();
+    SkDebugf("path verbs: %d\n", path.countVerbs());
+    SkDebugf("path2 verbs: %d\n", path2.countVerbs());
+    path.reset();
+    SkDebugf("after reset\n" "path verbs: %d\n", path.countVerbs());
+    SkDebugf("path2 verbs: %d\n", path2.countVerbs());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_copy_operator.cpp b/docs/examples/Path_copy_operator.cpp
new file mode 100644
index 0000000..e17c609
--- /dev/null
+++ b/docs/examples/Path_copy_operator.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bba288f5f77fc8e37e89d2ec08e0ac60
+REG_FIDDLE(Path_copy_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path1;
+    path1.addRect({10, 20, 30, 40});
+    SkPath path2 = path1;
+    const SkRect& b1 = path1.getBounds();
+    SkDebugf("path1 bounds = %g, %g, %g, %g\n", b1.fLeft, b1.fTop, b1.fRight, b1.fBottom);
+    const SkRect& b2 = path2.getBounds();
+    SkDebugf("path2 bounds = %g, %g, %g, %g\n", b2.fLeft, b2.fTop, b2.fRight, b2.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_countPoints.cpp b/docs/examples/Path_countPoints.cpp
new file mode 100644
index 0000000..2f792c5
--- /dev/null
+++ b/docs/examples/Path_countPoints.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bca6379ccef62cb081b10db7381deb27
+REG_FIDDLE(Path_countPoints, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkPath& path) -> void {
+         SkDebugf("%s point count: %d\n", prefix, path.countPoints());
+    };
+    SkPath path;
+    debugster("empty", path);
+    path.lineTo(0, 0);
+    debugster("zero line", path);
+    path.rewind();
+    path.moveTo(10, 10);
+    path.lineTo(20, 20);
+    debugster("line", path);
+    path.moveTo(20, 20);
+    debugster("second move", path);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_countVerbs.cpp b/docs/examples/Path_countVerbs.cpp
new file mode 100644
index 0000000..67af271
--- /dev/null
+++ b/docs/examples/Path_countVerbs.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=af0c66aea3ef81b709664c7007f48aae
+REG_FIDDLE(Path_countVerbs, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    SkDebugf("empty verb count: %d\n", path.countVerbs());
+    path.addRoundRect({10, 20, 30, 40}, 5, 5);
+    SkDebugf("round rect verb count: %d\n", path.countVerbs());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_cubicTo.cpp b/docs/examples/Path_cubicTo.cpp
new file mode 100644
index 0000000..1ac2569
--- /dev/null
+++ b/docs/examples/Path_cubicTo.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3e476378e3e0550ab134bbaf61112d98
+REG_FIDDLE(Path_cubicTo, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkPath path;
+    path.moveTo(0, -10);
+    for (int i = 0; i < 128; i += 16) {
+        SkScalar c = i * 0.5f;
+        path.cubicTo( 10 + c, -10 - i,  10 + i, -10 - c,  10 + i,       0);
+        path.cubicTo( 14 + i,  14 + c,  14 + c,  14 + i,       0,  14 + i);
+        path.cubicTo(-18 - c,  18 + i, -18 - i,  18 + c, -18 - i,       0);
+        path.cubicTo(-22 - i, -22 - c, -22 - c, -22 - i,       0, -22 - i);
+    }
+    path.offset(128, 128);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_cubicTo_2.cpp b/docs/examples/Path_cubicTo_2.cpp
new file mode 100644
index 0000000..3ea9538
--- /dev/null
+++ b/docs/examples/Path_cubicTo_2.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d38aaf12c6ff5b8d901a2201bcee5476
+REG_FIDDLE(Path_cubicTo_2, 256, 84, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkPoint pts[] = { {20, 20}, {300, 80}, {-140, 90}, {220, 10} };
+    SkPath path;
+    path.moveTo(pts[0]);
+    path.cubicTo(pts[1], pts[2], pts[3]);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_destructor.cpp b/docs/examples/Path_destructor.cpp
new file mode 100644
index 0000000..5209407
--- /dev/null
+++ b/docs/examples/Path_destructor.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=01ad6be9b7d15a2217daea273eb3d466
+REG_FIDDLE(Path_destructor, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath* path = new SkPath();
+    path->lineTo(20, 20);
+    SkPath path2(*path);
+    delete path;
+    SkDebugf("path2 is " "%s" "empty", path2.isEmpty() ? "" : "not ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_dump.cpp b/docs/examples/Path_dump.cpp
new file mode 100644
index 0000000..1f12b4d
--- /dev/null
+++ b/docs/examples/Path_dump.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8036d764452a62f9953af50846f0f3c0
+REG_FIDDLE(Path_dump, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.quadTo(20, 30, 40, 50);
+    for (bool forceClose : { false, true } ) {
+        for (bool dumpAsHex : { false, true } ) {
+            path.dump(nullptr, forceClose, dumpAsHex);
+            SkDebugf("\n");
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_dumpHex.cpp b/docs/examples/Path_dumpHex.cpp
new file mode 100644
index 0000000..12d0f6a
--- /dev/null
+++ b/docs/examples/Path_dumpHex.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=72a92fe058e8b3be6c8a30fad7fd1266
+REG_FIDDLE(Path_dumpHex, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path, copy;
+    path.lineTo(6.f / 7, 2.f / 3);
+    path.dumpHex();
+    copy.setFillType(SkPath::kWinding_FillType);
+    copy.moveTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000));  // 0, 0
+    copy.lineTo(SkBits2Float(0x3f5b6db7), SkBits2Float(0x3f2aaaab));  // 0.857143f, 0.666667f
+    SkDebugf("path is " "%s" "equal to copy\n", path == copy ? "" : "not ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_dump_2.cpp b/docs/examples/Path_dump_2.cpp
new file mode 100644
index 0000000..c81618f
--- /dev/null
+++ b/docs/examples/Path_dump_2.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=92e0032f85181795d1f8b5a2c8e4e4b7
+REG_FIDDLE(Path_dump_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path, copy;
+    path.lineTo(6.f / 7, 2.f / 3);
+    path.dump();
+    copy.setFillType(SkPath::kWinding_FillType);
+    copy.moveTo(0, 0);
+    copy.lineTo(0.857143f, 0.666667f);
+    SkDebugf("path is " "%s" "equal to copy\n", path == copy ? "" : "not ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_empty_constructor.cpp b/docs/examples/Path_empty_constructor.cpp
new file mode 100644
index 0000000..47a6a93
--- /dev/null
+++ b/docs/examples/Path_empty_constructor.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0a0026fca638d1cd75c0ab884e3ee1c6
+REG_FIDDLE(Path_empty_constructor, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    SkDebugf("path is " "%s" "empty", path.isEmpty() ? "" : "not ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_equal_operator.cpp b/docs/examples/Path_equal_operator.cpp
new file mode 100644
index 0000000..b7e59ee
--- /dev/null
+++ b/docs/examples/Path_equal_operator.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=31883f51bb357f2ac5990d88f8b82e02
+REG_FIDDLE(Path_equal_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkPath& a, const SkPath& b) -> void {
+                SkDebugf("%s one %c= two\n", prefix, a == b ? '=' : '!');
+    };
+    SkPath one;
+    SkPath two;
+    debugster("empty", one, two);
+    one.moveTo(0, 0);
+    debugster("moveTo", one, two);
+    one.rewind();
+    debugster("rewind", one, two);
+    one.moveTo(0, 0);
+    one.reset();
+    debugster("reset", one, two);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_getBounds.cpp b/docs/examples/Path_getBounds.cpp
new file mode 100644
index 0000000..98eb8e2
--- /dev/null
+++ b/docs/examples/Path_getBounds.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=45c0fc3acb74fab99d544b80eadd10ad
+REG_FIDDLE(Path_getBounds, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkPath& path) -> void {
+            const SkRect& bounds = path.getBounds();
+            SkDebugf("%s bounds = %g, %g, %g, %g\n", prefix,
+                     bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
+    };
+    SkPath path;
+    debugster("empty", path);
+    path.addCircle(50, 45, 25);
+    debugster("circle", path);
+    SkMatrix matrix;
+    matrix.setRotate(45, 50, 45);
+    path.transform(matrix);
+    debugster("rotated circle", path);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_getConvexity.cpp b/docs/examples/Path_getConvexity.cpp
new file mode 100644
index 0000000..b67337b
--- /dev/null
+++ b/docs/examples/Path_getConvexity.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a8f36f2fa90003e3691fd0da0bb0c243
+REG_FIDDLE(Path_getConvexity, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkPath& path) -> void {
+        SkDebugf("%s path convexity is %s\n", prefix,
+                SkPath::kUnknown_Convexity == path.getConvexity() ? "unknown" :
+                SkPath::kConvex_Convexity == path.getConvexity() ? "convex" : "concave"); };
+    SkPath path;
+    debugster("initial", path);
+    path.lineTo(50, 0);
+    debugster("first line", path);
+    path.lineTo(50, 50);
+    debugster("second line", path);
+    path.lineTo(100, 50);
+    debugster("third line", path);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_getConvexityOrUnknown.cpp b/docs/examples/Path_getConvexityOrUnknown.cpp
new file mode 100644
index 0000000..52bdb0c
--- /dev/null
+++ b/docs/examples/Path_getConvexityOrUnknown.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=111c59e9afadb940ab8f41bdc25378a4
+REG_FIDDLE(Path_getConvexityOrUnknown, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkPath& path) -> void {
+        SkDebugf("%s path convexity is %s\n", prefix,
+            SkPath::kUnknown_Convexity == path.getConvexityOrUnknown() ? "unknown" :
+            SkPath::kConvex_Convexity == path.getConvexityOrUnknown() ? "convex" : "concave"); };
+    SkPath path;
+    debugster("initial", path);
+    path.lineTo(50, 0);
+    debugster("first line", path);
+    path.getConvexity();
+    path.lineTo(50, 50);
+    debugster("second line", path);
+    path.lineTo(100, 50);
+    path.getConvexity();
+    debugster("third line", path);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_getFillType.cpp b/docs/examples/Path_getFillType.cpp
new file mode 100644
index 0000000..54d3c30
--- /dev/null
+++ b/docs/examples/Path_getFillType.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=019af90e778914e8a109d6305ede4fc4
+REG_FIDDLE(Path_getFillType, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    SkDebugf("default path fill type is %s\n",
+            path.getFillType() == SkPath::kWinding_FillType ? "kWinding_FillType" :
+            path.getFillType() == SkPath::kEvenOdd_FillType ? "kEvenOdd_FillType" :
+            path.getFillType() == SkPath::kInverseWinding_FillType ? "kInverseWinding_FillType" :
+                                                                     "kInverseEvenOdd_FillType");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_getGenerationID.cpp b/docs/examples/Path_getGenerationID.cpp
new file mode 100644
index 0000000..a7b2d05
--- /dev/null
+++ b/docs/examples/Path_getGenerationID.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a0f166715d6479f91258d854e63e586d
+REG_FIDDLE(Path_getGenerationID, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    SkDebugf("empty genID = %u\n", path.getGenerationID());
+    path.lineTo(1, 2);
+    SkDebugf("1st lineTo genID = %u\n", path.getGenerationID());
+    path.rewind();
+    SkDebugf("empty genID = %u\n", path.getGenerationID());
+    path.lineTo(1, 2);
+    SkDebugf("2nd lineTo genID = %u\n", path.getGenerationID());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_getLastPt.cpp b/docs/examples/Path_getLastPt.cpp
new file mode 100644
index 0000000..7ed4ce1
--- /dev/null
+++ b/docs/examples/Path_getLastPt.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=df8160dd7ac8aa4b40fce7286fe49952
+REG_FIDDLE(Path_getLastPt, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.moveTo(100, 100);
+    path.quadTo(100, 20, 20, 100);
+    SkMatrix matrix;
+    matrix.setRotate(36, 100, 100);
+    path.transform(matrix);
+    SkPoint last;
+    path.getLastPt(&last);
+    SkDebugf("last point: %g, %g\n", last.fX, last.fY);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_getPoint.cpp b/docs/examples/Path_getPoint.cpp
new file mode 100644
index 0000000..8a39cc1
--- /dev/null
+++ b/docs/examples/Path_getPoint.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=42885f1df13de109adccc5d531f62111
+REG_FIDDLE(Path_getPoint, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.lineTo(20, 20);
+    path.offset(-10, -10);
+    for (int i= 0; i < path.countPoints(); ++i) {
+         SkDebugf("point %d: (%1.8g,%1.8g)\n", i, path.getPoint(i).fX, path.getPoint(i).fY);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_getPoints.cpp b/docs/examples/Path_getPoints.cpp
new file mode 100644
index 0000000..74d51a2
--- /dev/null
+++ b/docs/examples/Path_getPoints.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9bc86efda08cbcd9c6f7c5f220294a24
+REG_FIDDLE(Path_getPoints, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkPath& path, SkPoint* points, int max) -> void {
+         int count = path.getPoints(points, max);
+         SkDebugf("%s point count: %d  ", prefix, count);
+         for (int i = 0; i < SkTMin(count, max) && points; ++i) {
+             SkDebugf("(%1.8g,%1.8g) ", points[i].fX, points[i].fY);
+         }
+         SkDebugf("\n");
+    };
+    SkPath path;
+    path.lineTo(20, 20);
+    path.lineTo(-10, -10);
+    SkPoint points[3];
+    debugster("no points",  path, nullptr, 0);
+    debugster("zero max",  path, points, 0);
+    debugster("too small",  path, points, 2);
+    debugster("just right",  path, points, path.countPoints());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_getSegmentMasks.cpp b/docs/examples/Path_getSegmentMasks.cpp
new file mode 100644
index 0000000..4751235
--- /dev/null
+++ b/docs/examples/Path_getSegmentMasks.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=657a3f3e11acafea92b84d6bb0c13633
+REG_FIDDLE(Path_getSegmentMasks, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.quadTo(20, 30, 40, 50);
+    path.close();
+    const char* masks[] = { "line", "quad", "conic", "cubic" };
+    int index = 0;
+    for (auto mask : { SkPath::kLine_SegmentMask, SkPath::kQuad_SegmentMask,
+            SkPath::kConic_SegmentMask, SkPath::kCubic_SegmentMask } ) {
+        if (mask & path.getSegmentMasks()) {
+           SkDebugf("mask %s set\n", masks[index]);
+        }
+        ++index;
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_getVerbs.cpp b/docs/examples/Path_getVerbs.cpp
new file mode 100644
index 0000000..3e3f9a5
--- /dev/null
+++ b/docs/examples/Path_getVerbs.cpp
@@ -0,0 +1,25 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2ec66880966a6133ddd9331ce7323438
+REG_FIDDLE(Path_getVerbs, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkPath& path, uint8_t* verbs, int max) -> void {
+         int count = path.getVerbs(verbs, max);
+         SkDebugf("%s verb count: %d  ", prefix, count);
+         const char* verbStr[] = { "move", "line", "quad", "conic", "cubic", "close" };
+         for (int i = 0; i < SkTMin(count, max) && verbs; ++i) {
+             SkDebugf("%s ", verbStr[verbs[i]]);
+         }
+         SkDebugf("\n");
+    };
+    SkPath path;
+    path.lineTo(20, 20);
+    path.lineTo(-10, -10);
+    uint8_t verbs[3];
+    debugster("no verbs",  path, nullptr, 0);
+    debugster("zero max",  path, verbs, 0);
+    debugster("too small",  path, verbs, 2);
+    debugster("just right",  path, verbs, path.countVerbs());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_incReserve.cpp b/docs/examples/Path_incReserve.cpp
new file mode 100644
index 0000000..483cb7c
--- /dev/null
+++ b/docs/examples/Path_incReserve.cpp
@@ -0,0 +1,27 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f2260f2a170a54aef5bafe5b91c121b3
+REG_FIDDLE(Path_incReserve, 256, 192, false, 0) {
+void draw(SkCanvas* canvas) {
+    auto addPoly = [](int sides, SkScalar size, SkPath* path) -> void {
+        path->moveTo(size, 0);
+        for (int i = 1; i < sides; i++) {
+            SkScalar rad = SK_ScalarPI * 2 * i / sides;
+            path->lineTo(SkScalarCos(rad) * size, SkScalarSin(rad) * size);
+        }
+        path->close();
+    };
+    SkPath path;
+    path.incReserve(3 + 4 + 5 + 6 + 7 + 8 + 9);
+    for (int sides = 3; sides < 10; ++sides) {
+       addPoly(sides, sides, &path);
+    }
+    SkMatrix matrix;
+    matrix.setScale(10, 10, -10, -10);
+    path.transform(matrix);
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_interpolate.cpp b/docs/examples/Path_interpolate.cpp
new file mode 100644
index 0000000..a210601
--- /dev/null
+++ b/docs/examples/Path_interpolate.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=404f11c5c9c9ca8a64822d484552a473
+REG_FIDDLE(Path_interpolate, 256, 60, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkPath path, path2;
+    path.moveTo(20, 20);
+    path.lineTo(40, 40);
+    path.lineTo(20, 40);
+    path.lineTo(40, 20);
+    path.close();
+    path2.addRect({20, 20, 40, 40});
+    for (SkScalar i = 0; i <= 1; i += 1.f / 6) {
+      SkPath interp;
+      path.interpolate(path2, i, &interp);
+      canvas->drawPath(interp, paint);
+      canvas->translate(30, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_isConvex.cpp b/docs/examples/Path_isConvex.cpp
new file mode 100644
index 0000000..19d7ee1
--- /dev/null
+++ b/docs/examples/Path_isConvex.cpp
@@ -0,0 +1,21 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d8be8b6e59de244e4cbf58ec9554557b
+REG_FIDDLE(Path_isConvex, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkPoint quad[] = {{70, 70}, {20, 20}, {120, 20}, {120, 120}};
+    for (SkScalar x : { 40, 100 } ) {
+        SkPath path;
+        quad[0].fX = x;
+        path.addPoly(quad, SK_ARRAY_COUNT(quad), true);
+        path.setConvexity(SkPath::kConvex_Convexity);
+        canvas->drawPath(path, paint);
+        canvas->drawString(path.isConvex() ? "convex" : "not convex", 30, 100, paint);
+        canvas->translate(100, 100);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Path_isEmpty.cpp b/docs/examples/Path_isEmpty.cpp
new file mode 100644
index 0000000..e965c0b
--- /dev/null
+++ b/docs/examples/Path_isEmpty.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0b34e6d55d11586744adeb889d2a12f4
+REG_FIDDLE(Path_isEmpty, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkPath& path) -> void {
+        SkDebugf("%s path is %s" "empty\n", prefix, path.isEmpty() ? "" : "not ");
+    };
+    SkPath path;
+    debugster("initial", path);
+    path.moveTo(0, 0);
+    debugster("after moveTo", path);
+    path.rewind();
+    debugster("after rewind", path);
+    path.lineTo(0, 0);
+    debugster("after lineTo", path);
+    path.reset();
+    debugster("after reset", path);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_isFinite.cpp b/docs/examples/Path_isFinite.cpp
new file mode 100644
index 0000000..2fe07b1
--- /dev/null
+++ b/docs/examples/Path_isFinite.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=dd4e4dd2aaa8039b2430729c6b3af817
+REG_FIDDLE(Path_isFinite, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkPath& path) -> void {
+        SkDebugf("%s path is %s" "finite\n", prefix, path.isFinite() ? "" : "not ");
+    };
+    SkPath path;
+    debugster("initial", path);
+    path.lineTo(SK_ScalarMax, SK_ScalarMax);
+    debugster("after line", path);
+    SkMatrix matrix;
+    matrix.setScale(2, 2);
+    path.transform(matrix);
+    debugster("after scale", path);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_isInterpolatable.cpp b/docs/examples/Path_isInterpolatable.cpp
new file mode 100644
index 0000000..9e72687
--- /dev/null
+++ b/docs/examples/Path_isInterpolatable.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c81fc7dfaf785c3fb77209c7f2ebe5b8
+REG_FIDDLE(Path_isInterpolatable, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path, path2;
+    path.moveTo(20, 20);
+    path.lineTo(40, 40);
+    path.lineTo(20, 20);
+    path.lineTo(40, 40);
+    path.close();
+    path2.addRect({20, 20, 40, 40});
+    SkDebugf("paths are " "%s" "interpolatable", path.isInterpolatable(path2) ? "" : "not ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_isInverseFillType_2.cpp b/docs/examples/Path_isInverseFillType_2.cpp
new file mode 100644
index 0000000..ce6af38
--- /dev/null
+++ b/docs/examples/Path_isInverseFillType_2.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2a2d39f5da611545caa18bbcea873ab2
+REG_FIDDLE(Path_isInverseFillType_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    SkDebugf("default path fill type is inverse: %s\n",
+            path.isInverseFillType() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_isLastContourClosed.cpp b/docs/examples/Path_isLastContourClosed.cpp
new file mode 100644
index 0000000..7692daa
--- /dev/null
+++ b/docs/examples/Path_isLastContourClosed.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=03b740ab94b9017800a52e30b5e7fee7
+REG_FIDDLE(Path_isLastContourClosed, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkPath& path) -> void {
+        SkDebugf("%s last contour is %s" "closed\n", prefix,
+                 path.isLastContourClosed() ? "" : "not ");
+    };
+    SkPath path;
+    debugster("initial", path);
+    path.close();
+    debugster("after close", path);
+    path.lineTo(0, 0);
+    debugster("after lineTo", path);
+    path.close();
+    debugster("after close", path);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_isLine.cpp b/docs/examples/Path_isLine.cpp
new file mode 100644
index 0000000..00e4fa1
--- /dev/null
+++ b/docs/examples/Path_isLine.cpp
@@ -0,0 +1,27 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1ad07d56e4258e041606d50cad969392
+REG_FIDDLE(Path_isLine, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkPath& path) -> void {
+        SkPoint line[2];
+        if (path.isLine(line)) {
+            SkDebugf("%s is line (%1.8g,%1.8g) (%1.8g,%1.8g)\n", prefix,
+                 line[0].fX, line[0].fY, line[1].fX, line[1].fY);
+        } else {
+            SkDebugf("%s is not line\n", prefix);
+        }
+    };
+    SkPath path;
+    debugster("empty", path);
+    path.lineTo(0, 0);
+    debugster("zero line", path);
+    path.rewind();
+    path.moveTo(10, 10);
+    path.lineTo(20, 20);
+    debugster("line", path);
+    path.moveTo(20, 20);
+    debugster("second move", path);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_isNestedFillRects.cpp b/docs/examples/Path_isNestedFillRects.cpp
new file mode 100644
index 0000000..c7cc163
--- /dev/null
+++ b/docs/examples/Path_isNestedFillRects.cpp
@@ -0,0 +1,25 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=77e4394caf9fa083c19c21c2462efe14
+REG_FIDDLE(Path_isNestedFillRects, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(5);
+    SkPath path;
+    path.addRect({10, 20, 30, 40});
+    paint.getFillPath(path, &path);
+    SkRect rects[2];
+    SkPath::Direction directions[2];
+    if (path.isNestedFillRects(rects, directions)) {
+        for (int i = 0; i < 2; ++i) {
+            SkDebugf("%s (%g, %g, %g, %g); direction %s\n", i ? "inner" : "outer",
+                     rects[i].fLeft, rects[i].fTop, rects[i].fRight, rects[i].fBottom,
+                     SkPath::kCW_Direction == directions[i] ? "CW" : "CCW");
+        }
+    } else {
+        SkDebugf("is not nested rectangles\n");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_isOval.cpp b/docs/examples/Path_isOval.cpp
new file mode 100644
index 0000000..1beeaa4
--- /dev/null
+++ b/docs/examples/Path_isOval.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a51256952b183ee0f7004f2c87cbbf5b
+REG_FIDDLE(Path_isOval, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkPath path;
+    path.addOval({20, 20, 220, 220});
+    SkRect bounds;
+    if (path.isOval(&bounds)) {
+        paint.setColor(0xFF9FBFFF);
+        canvas->drawRect(bounds, paint);
+    }
+    paint.setColor(0x3f000000);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_isRRect.cpp b/docs/examples/Path_isRRect.cpp
new file mode 100644
index 0000000..dca8763
--- /dev/null
+++ b/docs/examples/Path_isRRect.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2aa939b90d96aff436b145a96305132c
+REG_FIDDLE(Path_isRRect, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkPath path;
+    path.addRRect(SkRRect::MakeRectXY({20, 20, 220, 220}, 30, 50));
+    SkRRect rrect;
+    if (path.isRRect(&rrect)) {
+        const SkRect& bounds = rrect.rect();
+        paint.setColor(0xFF9FBFFF);
+        canvas->drawRect(bounds, paint);
+    }
+    paint.setColor(0x3f000000);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_isRect.cpp b/docs/examples/Path_isRect.cpp
new file mode 100644
index 0000000..3e954fa
--- /dev/null
+++ b/docs/examples/Path_isRect.cpp
@@ -0,0 +1,30 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=81a2aac1b8f0ff3d4c8d35ccb9149b16
+REG_FIDDLE(Path_isRect, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkPath& path) -> void {
+        SkRect rect;
+        SkPath::Direction direction;
+        bool isClosed;
+        path.isRect(&rect, &isClosed, &direction) ?
+                SkDebugf("%s is rect (%g, %g, %g, %g); is %s" "closed; direction %s\n", prefix,
+                         rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, isClosed ? "" : "not ",
+                         SkPath::kCW_Direction == direction ? "CW" : "CCW") :
+                SkDebugf("%s is not rect\n", prefix);
+    };
+    SkPath path;
+    debugster("empty", path);
+    path.addRect({10, 20, 30, 40});
+    debugster("addRect", path);
+    path.moveTo(60, 70);
+    debugster("moveTo", path);
+    path.lineTo(60, 70);
+    debugster("lineTo", path);
+    path.reset();
+    const SkPoint pts[] = { {0, 0}, {0, 80}, {80, 80}, {80, 0}, {40, 0}, {20, 0} };
+    path.addPoly(pts, SK_ARRAY_COUNT(pts), false);
+    debugster("addPoly", path);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_isVolatile.cpp b/docs/examples/Path_isVolatile.cpp
new file mode 100644
index 0000000..e1aedbb
--- /dev/null
+++ b/docs/examples/Path_isVolatile.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c722ebe8ac991d77757799ce29e509e1
+REG_FIDDLE(Path_isVolatile, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    SkDebugf("volatile by default is %s\n", path.isVolatile() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_lineTo.cpp b/docs/examples/Path_lineTo.cpp
new file mode 100644
index 0000000..2a39a5d
--- /dev/null
+++ b/docs/examples/Path_lineTo.cpp
@@ -0,0 +1,27 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e311cdd451edacec33b50cc22a4dd5dc
+REG_FIDDLE(Path_lineTo, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(72);
+    canvas->drawString("#", 120, 80, paint);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(5);
+    SkPath path;
+    SkPoint hash[] = {{58, 28}, {43, 80}, {37, 45}, {85, 45}};
+    SkVector offsets[] = {{0, 0}, {17, 0}, {0, 0}, {-5, 17}};
+    unsigned o = 0;
+    for (unsigned i = 0; i < SK_ARRAY_COUNT(hash); i += 2) {
+        for (unsigned j = 0; j < 2; o++, j++) {
+            path.moveTo(hash[i].fX + offsets[o].fX, hash[i].fY + offsets[o].fY);
+            path.lineTo(hash[i + 1].fX + offsets[o].fX, hash[i + 1].fY + offsets[o].fY);
+        }
+    }
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Path_lineTo_2.cpp b/docs/examples/Path_lineTo_2.cpp
new file mode 100644
index 0000000..4df672b
--- /dev/null
+++ b/docs/examples/Path_lineTo_2.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=41001546a7f7927d08e5a818bcc304f5
+REG_FIDDLE(Path_lineTo_2, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    SkVector oxo[] = {{25, 25}, {35, 35}, {25, 35}, {35, 25},
+                      {40, 20}, {40, 80}, {60, 20}, {60, 80},
+                      {20, 40}, {80, 40}, {20, 60}, {80, 60}};
+    for (unsigned i = 0; i < SK_ARRAY_COUNT(oxo); i += 2) {
+        path.moveTo(oxo[i]);
+        path.lineTo(oxo[i + 1]);
+    }
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_moveTo.cpp b/docs/examples/Path_moveTo.cpp
new file mode 100644
index 0000000..28ad8b6
--- /dev/null
+++ b/docs/examples/Path_moveTo.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=84101d341e934a535a41ad6cf42218ce
+REG_FIDDLE(Path_moveTo, 140, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 20, 20, 120, 80 };
+    SkPath path;
+    path.addRect(rect);
+    path.moveTo(rect.fLeft, rect.fTop);
+    path.lineTo(rect.fRight, rect.fBottom);
+    path.moveTo(rect.fLeft, rect.fBottom);
+    path.lineTo(rect.fRight, rect.fTop);
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_moveTo_2.cpp b/docs/examples/Path_moveTo_2.cpp
new file mode 100644
index 0000000..4684ffd
--- /dev/null
+++ b/docs/examples/Path_moveTo_2.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=cb8d37990f6e7df3bcc85e7240c81274
+REG_FIDDLE(Path_moveTo_2, 128, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint data[][3] = {{{30,40},{60,60},{90,30}}, {{30,120},{60,100},{90,120}},
+                         {{60,100},{60,40},{70,30}}, {{60,40},{50,20},{70,30}}};
+    SkPath path;
+    for (unsigned i = 0; i < SK_ARRAY_COUNT(data); ++i) {
+        path.moveTo(data[i][0]);
+        path.lineTo(data[i][1]);
+        path.lineTo(data[i][2]);
+    }
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_notequal_operator.cpp b/docs/examples/Path_notequal_operator.cpp
new file mode 100644
index 0000000..f0d5c78
--- /dev/null
+++ b/docs/examples/Path_notequal_operator.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bbbda1cc818d96c9c0d2a06c0c48902b
+REG_FIDDLE(Path_notequal_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkPath& a, const SkPath& b) -> void {
+                SkDebugf("%s one %c= two\n", prefix, a != b ? '!' : '=');
+    };
+    SkPath one;
+    SkPath two;
+    debugster("empty", one, two);
+    one.addRect({10, 20, 30, 40});
+    two.addRect({10, 20, 30, 40});
+    debugster("add rect", one, two);
+    one.setConvexity(SkPath::kConcave_Convexity);
+    debugster("setConvexity", one, two);
+    SkDebugf("convexity %c=\n", one.getConvexity() == two.getConvexity() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_offset.cpp b/docs/examples/Path_offset.cpp
new file mode 100644
index 0000000..26ae4c2
--- /dev/null
+++ b/docs/examples/Path_offset.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1d1892196ba5bda257df4f3351abd084
+REG_FIDDLE(Path_offset, 256, 60, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath pattern;
+    pattern.moveTo(20, 20);
+    pattern.lineTo(20, 40);
+    pattern.lineTo(40, 20);
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    for (int i = 0; i < 10; i++) {
+        SkPath path;
+        pattern.offset(20 * i, 0, &path);
+        canvas->drawPath(path, paint);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_offset_2.cpp b/docs/examples/Path_offset_2.cpp
new file mode 100644
index 0000000..6966ed4
--- /dev/null
+++ b/docs/examples/Path_offset_2.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5188d77585715db30bef228f2dfbcccd
+REG_FIDDLE(Path_offset_2, 256, 60, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.moveTo(20, 20);
+    path.lineTo(20, 40);
+    path.lineTo(40, 20);
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    for (int i = 0; i < 10; i++) {
+        canvas->drawPath(path, paint);
+        path.offset(20, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_quadTo.cpp b/docs/examples/Path_quadTo.cpp
new file mode 100644
index 0000000..2731218
--- /dev/null
+++ b/docs/examples/Path_quadTo.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=60ee3eb747474f5781b0f0dd3a17a866
+REG_FIDDLE(Path_quadTo, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkPath path;
+    path.moveTo(0, -10);
+    for (int i = 0; i < 128; i += 16) {
+        path.quadTo( 10 + i, -10 - i,  10 + i,       0);
+        path.quadTo( 14 + i,  14 + i,       0,  14 + i);
+        path.quadTo(-18 - i,  18 + i, -18 - i,       0);
+        path.quadTo(-22 - i, -22 - i,       0, -22 - i);
+    }
+    path.offset(128, 128);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_quadTo_2.cpp b/docs/examples/Path_quadTo_2.cpp
new file mode 100644
index 0000000..5ae718c
--- /dev/null
+++ b/docs/examples/Path_quadTo_2.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=82621c4df8da1e589d9e627494067826
+REG_FIDDLE(Path_quadTo_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setAntiAlias(true);
+    SkPath path;
+    SkPoint pts[] = {{128, 10}, {10, 214}, {236, 214}};
+    path.moveTo(pts[1]);
+    for (int i = 0; i < 3; ++i) {
+        path.quadTo(pts[i % 3],  pts[(i + 2) % 3]);
+    }
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_rArcTo.cpp b/docs/examples/Path_rArcTo.cpp
new file mode 100644
index 0000000..89fe283
--- /dev/null
+++ b/docs/examples/Path_rArcTo.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3f76a1007416181a4848c1a87fc81dbd
+REG_FIDDLE(Path_rArcTo, 256, 108, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkPath path;
+    const SkPoint starts[] = {{20, 20}, {120, 20}, {70, 60}};
+    for (auto start : starts) {
+        path.moveTo(start.fX, start.fY);
+        path.rArcTo(20, 20, 0, SkPath::kSmall_ArcSize, SkPath::kCCW_Direction, 60, 0);
+    }
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_rConicTo.cpp b/docs/examples/Path_rConicTo.cpp
new file mode 100644
index 0000000..e1a36a5
--- /dev/null
+++ b/docs/examples/Path_rConicTo.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3d52763e7c0e20c0b1d484a0afa622d2
+REG_FIDDLE(Path_rConicTo, 256, 140, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkPath path;
+    path.moveTo(20, 80);
+    path.rConicTo( 60,   0,  60,  60, 0.707107f);
+    path.rConicTo(  0, -60,  60, -60, 0.707107f);
+    path.rConicTo(-60,   0, -60, -60, 0.707107f);
+    path.rConicTo(  0,  60, -60,  60, 0.707107f);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_rCubicTo.cpp b/docs/examples/Path_rCubicTo.cpp
new file mode 100644
index 0000000..ae095e9
--- /dev/null
+++ b/docs/examples/Path_rCubicTo.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=19f0cfc7eeba8937fe19446ec0b5f932
+REG_FIDDLE(Path_rCubicTo, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkPath path;
+    path.moveTo(24, 108);
+    for (int i = 0; i < 16; i++) {
+        SkScalar sx = SkScalarSin(i * SK_ScalarPI / 8),
+                 sy = SkScalarCos(i * SK_ScalarPI / 8);
+       path.rCubicTo(40 * sx, 4 * sy, 4 * sx, 40 * sy, 40 * sx, 40 * sy);
+    }
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_rLineTo.cpp b/docs/examples/Path_rLineTo.cpp
new file mode 100644
index 0000000..2e093fe
--- /dev/null
+++ b/docs/examples/Path_rLineTo.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6e0be0766b8ca320da51640326e608b3
+REG_FIDDLE(Path_rLineTo, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkPath path;
+    path.moveTo(10, 98);
+    SkScalar x = 0, y = 0;
+    for (int i = 10; i < 100; i += 5) {
+        x += i * ((i & 2) - 1);
+        y += i * (((i + 1) & 2) - 1);
+        path.rLineTo(x, y);
+    }
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_rMoveTo.cpp b/docs/examples/Path_rMoveTo.cpp
new file mode 100644
index 0000000..c8a739e
--- /dev/null
+++ b/docs/examples/Path_rMoveTo.cpp
@@ -0,0 +1,22 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=63e32dec4b2d8440b427f368bf8313a4
+REG_FIDDLE(Path_rMoveTo, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.addRect({20, 20, 80, 80}, SkPath::kCW_Direction, 2);
+    path.rMoveTo(25, 2);
+    SkVector arrow[] = {{0, -4}, {-20, 0}, {0, -3}, {-5, 5}, {5, 5}, {0, -3}, {20, 0}};
+    for (unsigned i = 0; i < SK_ARRAY_COUNT(arrow); ++i) {
+        path.rLineTo(arrow[i].fX, arrow[i].fY);
+    }
+    SkPaint paint;
+    canvas->drawPath(path, paint);
+    SkPoint lastPt;
+    path.getLastPt(&lastPt);
+    canvas->drawString("start", lastPt.fX, lastPt.fY, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Path_rQuadTo.cpp b/docs/examples/Path_rQuadTo.cpp
new file mode 100644
index 0000000..f686481
--- /dev/null
+++ b/docs/examples/Path_rQuadTo.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1c1f4cdef1c572c9aa8fdf3e461191d0
+REG_FIDDLE(Path_rQuadTo, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkPath path;
+    path.moveTo(128, 20);
+    path.rQuadTo(-6, 10, -7, 10);
+    for (int i = 1; i < 32; i += 4) {
+       path.rQuadTo(10 + i, 10 + i, 10 + i * 4, 10);
+       path.rQuadTo(-10 - i, 10 + i, -10 - (i + 2) * 4, 10);
+    }
+    path.quadTo(92, 220, 128, 215);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_readFromMemory.cpp b/docs/examples/Path_readFromMemory.cpp
new file mode 100644
index 0000000..40e6a38
--- /dev/null
+++ b/docs/examples/Path_readFromMemory.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9c6edd836c573a0fd232d2b8aa11a678
+REG_FIDDLE(Path_readFromMemory, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path, copy;
+    path.lineTo(6.f / 7, 2.f / 3);
+    size_t size = path.writeToMemory(nullptr);
+    SkTDArray<char> storage;
+    storage.setCount(size);
+    path.writeToMemory(storage.begin());
+    size_t wrongSize = size - 4;
+    size_t bytesRead = copy.readFromMemory(storage.begin(), wrongSize);
+    SkDebugf("length = %u; returned by readFromMemory = %u\n", wrongSize, bytesRead);
+    size_t largerSize = size + 4;
+    bytesRead = copy.readFromMemory(storage.begin(), largerSize);
+    SkDebugf("length = %u; returned by readFromMemory = %u\n", largerSize, bytesRead);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_reset.cpp b/docs/examples/Path_reset.cpp
new file mode 100644
index 0000000..75a3c97
--- /dev/null
+++ b/docs/examples/Path_reset.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8cdca35d2964bbbecb93d79a13f71c65
+REG_FIDDLE(Path_reset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path1, path2;
+    path1.setFillType(SkPath::kInverseWinding_FillType);
+    path1.addRect({10, 20, 30, 40});
+    SkDebugf("path1 %c= path2\n", path1 == path2 ? '=' : '!');
+    path1.reset();
+    SkDebugf("path1 %c= path2\n", path1 == path2 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_reverseAddPath.cpp b/docs/examples/Path_reverseAddPath.cpp
new file mode 100644
index 0000000..39ec7192
--- /dev/null
+++ b/docs/examples/Path_reverseAddPath.cpp
@@ -0,0 +1,29 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5e8513f073db09acde3ff616f6426e3d
+REG_FIDDLE(Path_reverseAddPath, 256, 200, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.moveTo(20, 20);
+    path.lineTo(20, 40);
+    path.lineTo(40, 20);
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    for (int i = 0; i < 2; i++) {
+        SkPath path2;
+        path2.moveTo(60, 60);
+        path2.lineTo(80, 60);
+        path2.lineTo(80, 40);
+        for (int j = 0; j < 2; j++) {
+            SkPath test(path);
+            test.reverseAddPath(path2);
+            canvas->drawPath(test, paint);
+            canvas->translate(100, 0);
+            path2.close();
+        }
+        canvas->translate(-200, 100);
+        path.close();
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_rewind.cpp b/docs/examples/Path_rewind.cpp
new file mode 100644
index 0000000..9990765
--- /dev/null
+++ b/docs/examples/Path_rewind.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f1fedbb89da9c2a33a91805175663012
+REG_FIDDLE(Path_rewind, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path1, path2;
+    path1.setFillType(SkPath::kInverseWinding_FillType);
+    path1.addRect({10, 20, 30, 40});
+    SkDebugf("path1 %c= path2\n", path1 == path2 ? '=' : '!');
+    path1.rewind();
+    SkDebugf("path1 %c= path2\n", path1 == path2 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_serialize.cpp b/docs/examples/Path_serialize.cpp
new file mode 100644
index 0000000..f185c2f
--- /dev/null
+++ b/docs/examples/Path_serialize.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2c6aff73608cd198659db6d1eeaaae4f
+REG_FIDDLE(Path_serialize, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path, copy;
+    path.lineTo(6.f / 7, 2.f / 3);
+    sk_sp<SkData> data = path.serialize();
+    copy.readFromMemory(data->data(), data->size());
+    SkDebugf("path is " "%s" "equal to copy\n", path == copy ? "" : "not ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_setConvexity.cpp b/docs/examples/Path_setConvexity.cpp
new file mode 100644
index 0000000..b45a8cd
--- /dev/null
+++ b/docs/examples/Path_setConvexity.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=875e32b4b1cb48d739325705fc0fa42c
+REG_FIDDLE(Path_setConvexity, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkPath& path) -> void {
+        SkDebugf("%s path convexity is %s\n", prefix,
+                SkPath::kUnknown_Convexity == path.getConvexity() ? "unknown" :
+                SkPath::kConvex_Convexity == path.getConvexity() ? "convex" : "concave"); };
+        SkPoint quad[] = {{70, 70}, {20, 20}, {120, 20}, {120, 120}};
+        SkPath path;
+        path.addPoly(quad, SK_ARRAY_COUNT(quad), true);
+        debugster("initial", path);
+        path.setConvexity(SkPath::kConcave_Convexity);
+        debugster("after forcing concave", path);
+        path.setConvexity(SkPath::kUnknown_Convexity);
+        debugster("after forcing unknown", path);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_setFillType.cpp b/docs/examples/Path_setFillType.cpp
new file mode 100644
index 0000000..778921c
--- /dev/null
+++ b/docs/examples/Path_setFillType.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b4a91cd7f50b2a0a0d1bec6d0ac823d2
+REG_FIDDLE(Path_setFillType, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.setFillType(SkPath::kInverseWinding_FillType);
+    SkPaint paint;
+    paint.setColor(SK_ColorBLUE);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_setIsVolatile.cpp b/docs/examples/Path_setIsVolatile.cpp
new file mode 100644
index 0000000..5d02a02
--- /dev/null
+++ b/docs/examples/Path_setIsVolatile.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2049ff5141f0c80aac497618622b28af
+REG_FIDDLE(Path_setIsVolatile, 50, 50, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkPath path;
+    path.setIsVolatile(true);
+    path.lineTo(40, 40);
+    canvas->drawPath(path, paint);
+    path.rewind();
+    path.moveTo(0, 40);
+    path.lineTo(40, 0);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_setLastPt.cpp b/docs/examples/Path_setLastPt.cpp
new file mode 100644
index 0000000..726e704
--- /dev/null
+++ b/docs/examples/Path_setLastPt.cpp
@@ -0,0 +1,16 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=542c5afaea5f57baa11d0561dd402e18
+REG_FIDDLE(Path_setLastPt, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(128);
+    SkPath path;
+    paint.getTextPath("@", 1, 60, 100, &path);
+    path.setLastPt(20, 120);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Path_setLastPt_2.cpp b/docs/examples/Path_setLastPt_2.cpp
new file mode 100644
index 0000000..d95e74d
--- /dev/null
+++ b/docs/examples/Path_setLastPt_2.cpp
@@ -0,0 +1,22 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6fa5e8f9513b3225e106778592e27e94
+REG_FIDDLE(Path_setLastPt_2, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(128);
+    SkPath path, path2;
+    paint.getTextPath("A", 1, 60, 100, &path);
+    paint.getTextPath("Z", 1, 60, 100, &path2);
+    SkPoint pt, pt2;
+    path.getLastPt(&pt);
+    path2.getLastPt(&pt2);
+    path.setLastPt(pt2);
+    path2.setLastPt(pt);
+    canvas->drawPath(path, paint);
+    canvas->drawPath(path2, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Path_swap.cpp b/docs/examples/Path_swap.cpp
new file mode 100644
index 0000000..4c111a7
--- /dev/null
+++ b/docs/examples/Path_swap.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4c5ebee2b5039e5faefa07ae63a15467
+REG_FIDDLE(Path_swap, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path1, path2;
+    path1.addRect({10, 20, 30, 40});
+    path1.swap(path2);
+    const SkRect& b1 = path1.getBounds();
+    SkDebugf("path1 bounds = %g, %g, %g, %g\n", b1.fLeft, b1.fTop, b1.fRight, b1.fBottom);
+    const SkRect& b2 = path2.getBounds();
+    SkDebugf("path2 bounds = %g, %g, %g, %g\n", b2.fLeft, b2.fTop, b2.fRight, b2.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_toggleInverseFillType.cpp b/docs/examples/Path_toggleInverseFillType.cpp
new file mode 100644
index 0000000..2b747e7
--- /dev/null
+++ b/docs/examples/Path_toggleInverseFillType.cpp
@@ -0,0 +1,19 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=400facce23d417bc5043c5f58404afbd
+REG_FIDDLE(Path_toggleInverseFillType, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    SkPaint paint;
+    paint.setColor(SK_ColorRED);
+    paint.setTextSize(80);
+    paint.getTextPath("ABC", 3, 20, 80, &path);
+    canvas->drawPath(path, paint);
+    path.toggleInverseFillType();
+    paint.setColor(SK_ColorGREEN);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Path_transform.cpp b/docs/examples/Path_transform.cpp
new file mode 100644
index 0000000..1cc5fd3
--- /dev/null
+++ b/docs/examples/Path_transform.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=99761add116ce3b0730557224c1b0105
+REG_FIDDLE(Path_transform, 256, 200, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath pattern;
+    pattern.moveTo(100, 100);
+    pattern.lineTo(100, 20);
+    pattern.lineTo(20, 100);
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    for (int i = 0; i < 10; i++) {
+        SkPath path;
+        SkMatrix matrix;
+        matrix.setRotate(36 * i, 100, 100);
+        pattern.transform(matrix, &path);
+        canvas->drawPath(path, paint);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_transform_2.cpp b/docs/examples/Path_transform_2.cpp
new file mode 100644
index 0000000..b60ecb5
--- /dev/null
+++ b/docs/examples/Path_transform_2.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c40979a3b92a30cfb7bae36abcc1d805
+REG_FIDDLE(Path_transform_2, 256, 200, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path;
+    path.moveTo(100, 100);
+    path.quadTo(100, 20, 20, 100);
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    for (int i = 0; i < 10; i++) {
+        SkMatrix matrix;
+        matrix.setRotate(36, 100, 100);
+        path.transform(matrix);
+        canvas->drawPath(path, paint);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_updateBoundsCache.cpp b/docs/examples/Path_updateBoundsCache.cpp
new file mode 100644
index 0000000..9ee6e90
--- /dev/null
+++ b/docs/examples/Path_updateBoundsCache.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bb761cd858e6d0ca05627262cd22ff5e
+REG_FIDDLE(Path_updateBoundsCache, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    double times[2] = { 0, 0 };
+    int N = 100;
+    for (int i = 0; i < N; ++i) {
+      SkPath path;
+      for (int j = 1; j < 100; ++ j) {
+        path.addCircle(50 + j, 45 + j, 25 + j);
+      }
+      if (1 & i) {
+        path.updateBoundsCache();
+      }
+      double start = SkTime::GetNSecs();
+      (void) path.getBounds();
+      times[1 & i] += SkTime::GetNSecs() - start;
+    }
+    SkDebugf("uncached avg: %g ms\n", times[0] / (double)N);
+    SkDebugf("cached avg: %g ms\n", times[1] / (double)N);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Path_writeToMemory.cpp b/docs/examples/Path_writeToMemory.cpp
new file mode 100644
index 0000000..7b0bc87
--- /dev/null
+++ b/docs/examples/Path_writeToMemory.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e5f16eda6a1c2d759556285f72598445
+REG_FIDDLE(Path_writeToMemory, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPath path, copy;
+    path.lineTo(6.f / 7, 2.f / 3);
+    size_t size = path.writeToMemory(nullptr);
+    SkTDArray<char> storage;
+    storage.setCount(size);
+    path.writeToMemory(storage.begin());
+    copy.readFromMemory(storage.begin(), size);
+    SkDebugf("path is " "%s" "equal to copy\n", path == copy ? "" : "not ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Picture_008.cpp b/docs/examples/Picture_008.cpp
new file mode 100644
index 0000000..822e7f8
--- /dev/null
+++ b/docs/examples/Picture_008.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=30b9f1b310187db6aff720a5d67591e2
+REG_FIDDLE(Picture_008, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPictureRecorder recorder;
+    SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
+    SkPaint paint;
+    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
+    paint.setColor(SK_ColorWHITE);
+    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
+    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
+    SkDynamicMemoryWStream writableStream;
+    picture->serialize(&writableStream);
+    sk_sp<SkData> readableData = writableStream.detachAsData();
+    sk_sp<SkPicture> copy = SkPicture::MakeFromData(readableData->data(), readableData->size());
+    copy->playback(canvas);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Picture_AbortCallback_abort.cpp b/docs/examples/Picture_AbortCallback_abort.cpp
new file mode 100644
index 0000000..d43383b
--- /dev/null
+++ b/docs/examples/Picture_AbortCallback_abort.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=56ed920dadbf2b2967ac45fb5a9bded6
+REG_FIDDLE(Picture_AbortCallback_abort, 256, 256, false, 0) {
+class JustOneDraw : public SkPicture::AbortCallback {
+public:
+    bool abort() override { return fCalls++ > 0; }
+private:
+    int fCalls = 0;
+};
+
+void draw(SkCanvas* canvas) {
+    SkPictureRecorder recorder;
+    SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
+    SkPaint paint;
+    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
+    paint.setColor(SK_ColorWHITE);
+    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
+    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
+    JustOneDraw callback;
+    picture->playback(canvas, &callback);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Picture_MakeFromData.cpp b/docs/examples/Picture_MakeFromData.cpp
new file mode 100644
index 0000000..8eb53e8
--- /dev/null
+++ b/docs/examples/Picture_MakeFromData.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=58b44bf47d8816782066618700afdecb
+REG_FIDDLE(Picture_MakeFromData, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPictureRecorder recorder;
+    SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
+    SkPaint paint;
+    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
+    paint.setColor(SK_ColorWHITE);
+    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
+    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
+    SkDynamicMemoryWStream writableStream;
+    picture->serialize(&writableStream);
+    sk_sp<SkData> readableData = writableStream.detachAsData();
+    sk_sp<SkPicture> copy = SkPicture::MakeFromData(readableData.get());
+    copy->playback(canvas);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Picture_MakeFromStream.cpp b/docs/examples/Picture_MakeFromStream.cpp
new file mode 100644
index 0000000..0308e0f
--- /dev/null
+++ b/docs/examples/Picture_MakeFromStream.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=404fb42560a289c2004cad1caf3b96de
+REG_FIDDLE(Picture_MakeFromStream, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPictureRecorder recorder;
+    SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
+    SkPaint paint;
+    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
+    paint.setColor(SK_ColorWHITE);
+    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
+    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
+    SkDynamicMemoryWStream writableStream;
+    picture->serialize(&writableStream);
+    std::unique_ptr<SkStreamAsset> readableStream = writableStream.detachAsStream();
+    sk_sp<SkPicture> copy = SkPicture::MakeFromStream(readableStream.get());
+    copy->playback(canvas);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Picture_MakePlaceholder.cpp b/docs/examples/Picture_MakePlaceholder.cpp
new file mode 100644
index 0000000..8247af5
--- /dev/null
+++ b/docs/examples/Picture_MakePlaceholder.cpp
@@ -0,0 +1,28 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0d2cbf82f490ffb180e0b4531afa232c
+REG_FIDDLE(Picture_MakePlaceholder, 256, 256, false, 0) {
+class MyCanvas : public SkCanvas {
+public:
+    MyCanvas(SkCanvas* c) : canvas(c) {}
+        void onDrawPicture(const SkPicture* picture, const SkMatrix* ,
+                               const SkPaint* ) override {
+        const SkRect rect = picture->cullRect();
+        SkPaint redPaint;
+        redPaint.setColor(SK_ColorRED);
+        canvas->drawRect(rect, redPaint);
+   }
+   SkCanvas* canvas;
+};
+
+void draw(SkCanvas* canvas) {
+    SkPictureRecorder recorder;
+    SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
+    sk_sp<SkPicture> placeholder = SkPicture::MakePlaceholder({10, 40, 80, 110});
+    pictureCanvas->drawPicture(placeholder);
+    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
+    MyCanvas myCanvas(canvas);
+    myCanvas.drawPicture(picture);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Picture_approximateBytesUsed.cpp b/docs/examples/Picture_approximateBytesUsed.cpp
new file mode 100644
index 0000000..a54d2cd
--- /dev/null
+++ b/docs/examples/Picture_approximateBytesUsed.cpp
@@ -0,0 +1,20 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ececbda21218bd732394a305dba393a2
+REG_FIDDLE(Picture_approximateBytesUsed, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPictureRecorder recorder;
+    SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
+    SkPaint paint;
+    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
+    paint.setColor(SK_ColorWHITE);
+    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
+    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
+    picture->playback(canvas);
+    std::string opCount = "approximate bytes used: " + std::to_string(picture->approximateBytesUsed());
+    canvas->drawString(opCount.c_str(), 20, 220, SkPaint());
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Picture_approximateOpCount.cpp b/docs/examples/Picture_approximateOpCount.cpp
new file mode 100644
index 0000000..10ad4dd
--- /dev/null
+++ b/docs/examples/Picture_approximateOpCount.cpp
@@ -0,0 +1,20 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4b3d879118ef770d1f11a23c6493b2c4
+REG_FIDDLE(Picture_approximateOpCount, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPictureRecorder recorder;
+    SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
+    SkPaint paint;
+    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
+    paint.setColor(SK_ColorWHITE);
+    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
+    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
+    picture->playback(canvas);
+    std::string opCount = "approximate op count: " + std::to_string(picture->approximateOpCount());
+    canvas->drawString(opCount.c_str(), 50, 220, SkPaint());
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Picture_cullRect.cpp b/docs/examples/Picture_cullRect.cpp
new file mode 100644
index 0000000..d56b55d
--- /dev/null
+++ b/docs/examples/Picture_cullRect.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=15bb9a9596b40c5e2045f76e8c1dcf8e
+REG_FIDDLE(Picture_cullRect, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPictureRecorder recorder;
+    SkCanvas* pictureCanvas = recorder.beginRecording({64, 64, 192, 192});
+    SkPaint paint;
+    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
+    paint.setColor(SK_ColorWHITE);
+    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
+    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
+    picture->playback(canvas);
+    paint.setBlendMode(SkBlendMode::kModulate);
+    paint.setColor(0x40404040);
+    canvas->drawRect(picture->cullRect(), paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Picture_playback.cpp b/docs/examples/Picture_playback.cpp
new file mode 100644
index 0000000..028b231
--- /dev/null
+++ b/docs/examples/Picture_playback.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6b0ffb03ba05f526b345dc65a1c73fe4
+REG_FIDDLE(Picture_playback, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPictureRecorder recorder;
+    SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
+    SkPaint paint;
+    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
+    paint.setColor(SK_ColorWHITE);
+    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
+    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
+    picture->playback(canvas);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Picture_serialize.cpp b/docs/examples/Picture_serialize.cpp
new file mode 100644
index 0000000..128ebcf
--- /dev/null
+++ b/docs/examples/Picture_serialize.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=dacdebe1355c884ebd3c2ea038cc7a20
+REG_FIDDLE(Picture_serialize, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPictureRecorder recorder;
+    SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
+    SkPaint paint;
+    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
+    paint.setColor(SK_ColorWHITE);
+    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
+    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
+    sk_sp<SkData> readableData = picture->serialize();
+    sk_sp<SkPicture> copy = SkPicture::MakeFromData(readableData->data(), readableData->size());
+    copy->playback(canvas);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Picture_serialize_2.cpp b/docs/examples/Picture_serialize_2.cpp
new file mode 100644
index 0000000..829c19d
--- /dev/null
+++ b/docs/examples/Picture_serialize_2.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=30b9f1b310187db6aff720a5d67591e2
+REG_FIDDLE(Picture_serialize_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPictureRecorder recorder;
+    SkCanvas* pictureCanvas = recorder.beginRecording({0, 0, 256, 256});
+    SkPaint paint;
+    pictureCanvas->drawRect(SkRect::MakeWH(200, 200), paint);
+    paint.setColor(SK_ColorWHITE);
+    pictureCanvas->drawRect(SkRect::MakeLTRB(20, 20, 180, 180), paint);
+    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
+    SkDynamicMemoryWStream writableStream;
+    picture->serialize(&writableStream);
+    sk_sp<SkData> readableData = writableStream.detachAsData();
+    sk_sp<SkPicture> copy = SkPicture::MakeFromData(readableData->data(), readableData->size());
+    copy->playback(canvas);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Picture_uniqueID.cpp b/docs/examples/Picture_uniqueID.cpp
new file mode 100644
index 0000000..e69a311
--- /dev/null
+++ b/docs/examples/Picture_uniqueID.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8e4257245c988c600410fe4fd7293f07
+REG_FIDDLE(Picture_uniqueID, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPictureRecorder recorder;
+    recorder.beginRecording({0, 0, 0, 0});
+    sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
+    SkDebugf("empty picture id = %d\n", picture->uniqueID());
+    sk_sp<SkPicture> placeholder = SkPicture::MakePlaceholder({0, 0, 0, 0});
+    SkDebugf("placeholder id = %d\n", placeholder->uniqueID());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_addr.cpp b/docs/examples/Pixmap_addr.cpp
new file mode 100644
index 0000000..9c58513
--- /dev/null
+++ b/docs/examples/Pixmap_addr.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=17bcabaaee2dbb7beba562e9ca50b55e
+REG_FIDDLE(Pixmap_addr, 256, 256, true, 3) {
+void draw(SkCanvas* canvas) {
+    std::vector<int32_t> pixels;
+    pixels.resize(image->height() * image->width() * 4);
+    SkPixmap pixmap(SkImageInfo::Make(image->width(), image->height(), kN32_SkColorType,
+            image->alphaType()), (const void*) &pixels.front(), image->width() * 4);
+    image->readPixels(pixmap, 0, 0);
+    SkDebugf("pixels address: 0x%llx\n", pixmap.addr());
+    SkPixmap inset;
+    if (pixmap.extractSubset(&inset, {128, 128, 512, 512})) {
+         SkDebugf("inset address:  0x%llx\n", inset.addr());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_addr16.cpp b/docs/examples/Pixmap_addr16.cpp
new file mode 100644
index 0000000..954ac8a
--- /dev/null
+++ b/docs/examples/Pixmap_addr16.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9b16012d265c954c6de13f3fc960da52
+REG_FIDDLE(Pixmap_addr16, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int w = 4;
+    const int h = 4;
+    uint16_t storage[w * h];
+    SkPixmap pixmap(SkImageInfo::Make(w, h, kARGB_4444_SkColorType, kPremul_SkAlphaType),
+                    storage, w * sizeof(storage[0]));
+    SkDebugf("pixmap.addr16() %c= storage\n",
+              pixmap.addr16()  == storage ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_addr16_2.cpp b/docs/examples/Pixmap_addr16_2.cpp
new file mode 100644
index 0000000..8ea4730
--- /dev/null
+++ b/docs/examples/Pixmap_addr16_2.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2c0c88a546d4ef093ab63ff72dac00b9
+REG_FIDDLE(Pixmap_addr16_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int w = 4;
+    const int h = 4;
+    uint16_t storage[w * h];
+    SkPixmap pixmap(SkImageInfo::Make(w, h, kARGB_4444_SkColorType, kPremul_SkAlphaType),
+                    storage, w * sizeof(storage[0]));
+    SkDebugf("pixmap.addr16(1, 2) %c= &storage[1 + 2 * w]\n",
+              pixmap.addr16(1, 2)  == &storage[1 + 2 * w] ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_addr32.cpp b/docs/examples/Pixmap_addr32.cpp
new file mode 100644
index 0000000..837fa48
--- /dev/null
+++ b/docs/examples/Pixmap_addr32.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6b90c7ae9f254fe4ea9ef638f893a3e6
+REG_FIDDLE(Pixmap_addr32, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int w = 4;
+    const int h = 4;
+    uint32_t storage[w * h];
+    SkPixmap pixmap(SkImageInfo::MakeN32(w, h, kPremul_SkAlphaType),
+                    storage, w * sizeof(storage[0]));
+    SkDebugf("pixmap.addr32() %c= storage\n",
+              pixmap.addr32()  == storage ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_addr32_2.cpp b/docs/examples/Pixmap_addr32_2.cpp
new file mode 100644
index 0000000..53cec5d
--- /dev/null
+++ b/docs/examples/Pixmap_addr32_2.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=12f8b5ce9fb25604f33df336677f5d62
+REG_FIDDLE(Pixmap_addr32_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int w = 4;
+    const int h = 4;
+    uint32_t storage[w * h];
+    SkPixmap pixmap(SkImageInfo::Make(w, h, kRGBA_8888_SkColorType, kPremul_SkAlphaType),
+                    storage, w * sizeof(storage[0]));
+    SkDebugf("pixmap.addr32(1, 2) %c= &storage[1 + 2 * w]\n",
+              pixmap.addr32(1, 2)  == &storage[1 + 2 * w] ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_addr64.cpp b/docs/examples/Pixmap_addr64.cpp
new file mode 100644
index 0000000..702c755
--- /dev/null
+++ b/docs/examples/Pixmap_addr64.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0d17085a4698a8a2e2235fad9041b4b4
+REG_FIDDLE(Pixmap_addr64, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int w = 4;
+    const int h = 4;
+    uint64_t storage[w * h];
+    SkPixmap pixmap(SkImageInfo::Make(w, h, kRGBA_F16_SkColorType, kPremul_SkAlphaType),
+                    storage, w * sizeof(storage[0]));
+    SkDebugf("pixmap.addr64() %c= storage\n",
+              pixmap.addr64()  == storage ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_addr64_2.cpp b/docs/examples/Pixmap_addr64_2.cpp
new file mode 100644
index 0000000..dee9270
--- /dev/null
+++ b/docs/examples/Pixmap_addr64_2.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5449f65fd7673273b0b57807fd3117ff
+REG_FIDDLE(Pixmap_addr64_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int w = 4;
+    const int h = 4;
+    uint64_t storage[w * h];
+    SkPixmap pixmap(SkImageInfo::Make(w, h, kRGBA_F16_SkColorType, kPremul_SkAlphaType),
+                    storage, w * sizeof(storage[0]));
+    SkDebugf("pixmap.addr64(1, 2) %c= &storage[1 + 2 * w]\n",
+              pixmap.addr64(1, 2)  == &storage[1 + 2 * w] ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_addr8.cpp b/docs/examples/Pixmap_addr8.cpp
new file mode 100644
index 0000000..9fd3ea9
--- /dev/null
+++ b/docs/examples/Pixmap_addr8.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9adda80b2dd1b08ec5ccf66da7c8bd91
+REG_FIDDLE(Pixmap_addr8, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int w = 4;
+    const int h = 4;
+    uint8_t storage[w * h];
+    SkPixmap pixmap(SkImageInfo::Make(w, h, kGray_8_SkColorType, kPremul_SkAlphaType),
+                    storage, w * sizeof(storage[0]));
+    SkDebugf("pixmap.addr8() %c= storage\n",
+              pixmap.addr8()  == storage ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_addr8_2.cpp b/docs/examples/Pixmap_addr8_2.cpp
new file mode 100644
index 0000000..a07dac4
--- /dev/null
+++ b/docs/examples/Pixmap_addr8_2.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5b986272268ef2c52045c1856f8b6107
+REG_FIDDLE(Pixmap_addr8_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int w = 4;
+    const int h = 4;
+    uint8_t storage[w * h];
+    SkPixmap pixmap(SkImageInfo::Make(w, h, kGray_8_SkColorType, kPremul_SkAlphaType),
+                    storage, w * sizeof(storage[0]));
+    SkDebugf("pixmap.addr8(1, 2) %c= &storage[1 + 2 * w]\n",
+              pixmap.addr8(1, 2)  == &storage[1 + 2 * w] ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_addrF16.cpp b/docs/examples/Pixmap_addrF16.cpp
new file mode 100644
index 0000000..713b5a3
--- /dev/null
+++ b/docs/examples/Pixmap_addrF16.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=54e8525a592f05623c33b375aebc90c1
+REG_FIDDLE(Pixmap_addrF16, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int w = 4;
+    const int h = 4;
+    uint16_t storage[w * h * 4];
+    SkPixmap pixmap(SkImageInfo::Make(w, h, kRGBA_F16_SkColorType, kPremul_SkAlphaType),
+                    storage, w * 4 * sizeof(storage[0]));
+    SkDebugf("pixmap.addrF16() %c= storage\n",
+              pixmap.addrF16()  == storage ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_addrF16_2.cpp b/docs/examples/Pixmap_addrF16_2.cpp
new file mode 100644
index 0000000..65119a7
--- /dev/null
+++ b/docs/examples/Pixmap_addrF16_2.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f6076cad455bc80af5d06eb121d3b6f2
+REG_FIDDLE(Pixmap_addrF16_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int w = 4;
+    const int h = 4;
+    const int wordsPerPixel = 4;
+    const int rowWords = w * wordsPerPixel;
+    uint16_t storage[rowWords * h];
+    SkPixmap pixmap(SkImageInfo::Make(w, h, kRGBA_F16_SkColorType, kPremul_SkAlphaType),
+                    storage, rowWords * sizeof(storage[0]));
+    SkDebugf("pixmap.addrF16(1, 2) %c= &storage[1 * wordsPerPixel + 2 * rowWords]\n",
+              pixmap.addrF16(1, 2)  == &storage[1 * wordsPerPixel + 2 * rowWords] ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_addr_2.cpp b/docs/examples/Pixmap_addr_2.cpp
new file mode 100644
index 0000000..f3a8494
--- /dev/null
+++ b/docs/examples/Pixmap_addr_2.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6e6e29e860eafed77308c973400cc84d
+REG_FIDDLE(Pixmap_addr_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int w = 4;
+    const int h = 4;
+    std::vector<SkPMColor> storage;
+    storage.resize(w * h);
+    SkPixmap pixmap(SkImageInfo::MakeN32(w, h, kPremul_SkAlphaType), &storage.front(), w * 4);
+    SkDebugf("pixmap.addr(1, 2) %c= &storage[1 + 2 * w]\n",
+              pixmap.addr(1, 2)  == &storage[1 + 2 * w] ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_alphaType.cpp b/docs/examples/Pixmap_alphaType.cpp
new file mode 100644
index 0000000..d506d97
--- /dev/null
+++ b/docs/examples/Pixmap_alphaType.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=070b1a60232be499eb10c6ea62371804
+REG_FIDDLE(Pixmap_alphaType, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const char* alphas[] = {"Unknown", "Opaque", "Premul", "Unpremul"};
+    SkPixmap pixmap(SkImageInfo::MakeA8(16, 32), nullptr, 64);
+    SkDebugf("alpha type: k" "%s" "_SkAlphaType\n", alphas[pixmap.alphaType()]);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_bounds.cpp b/docs/examples/Pixmap_bounds.cpp
new file mode 100644
index 0000000..9d0bb5b
--- /dev/null
+++ b/docs/examples/Pixmap_bounds.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=79750fb1d898a4e5c8c828b7bc9acec5
+REG_FIDDLE(Pixmap_bounds, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    for (int width : { 0, 2 } ) {
+        for (int height : { 0, 2 } ) {
+             SkPixmap pixmap(SkImageInfo::MakeA8(width, height), nullptr, width);
+             SkDebugf("width: %d height: %d empty: %s\n", width, height,
+                      pixmap.bounds().isEmpty() ? "true" : "false");
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_colorSpace.cpp b/docs/examples/Pixmap_colorSpace.cpp
new file mode 100644
index 0000000..a1b3fbf
--- /dev/null
+++ b/docs/examples/Pixmap_colorSpace.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3421bb20a302d563832ba7bb45e0cc58
+REG_FIDDLE(Pixmap_colorSpace, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPixmap pixmap(SkImageInfo::MakeN32(16, 32, kPremul_SkAlphaType,
+            SkColorSpace::MakeSRGBLinear()), nullptr, 64);
+    SkColorSpace* colorSpace = pixmap.colorSpace();
+    SkDebugf("gammaCloseToSRGB: %s  gammaIsLinear: %s  isSRGB: %s\n",
+            colorSpace->gammaCloseToSRGB() ? "true" : "false",
+            colorSpace->gammaIsLinear() ? "true" : "false",
+            colorSpace->isSRGB() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_colorType.cpp b/docs/examples/Pixmap_colorType.cpp
new file mode 100644
index 0000000..3aca89a
--- /dev/null
+++ b/docs/examples/Pixmap_colorType.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0ab5c7af272685f2ce177cc79e6b9457
+REG_FIDDLE(Pixmap_colorType, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
+                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16Norm",
+                            "RGBA_F16"};
+    SkPixmap pixmap(SkImageInfo::MakeA8(16, 32), nullptr, 64);
+    SkDebugf("color type: k" "%s" "_SkColorType\n", colors[pixmap.colorType()]);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_computeByteSize.cpp b/docs/examples/Pixmap_computeByteSize.cpp
new file mode 100644
index 0000000..447fabb
--- /dev/null
+++ b/docs/examples/Pixmap_computeByteSize.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=410d14ddc45d272598c5a4e52bb047de
+REG_FIDDLE(Pixmap_computeByteSize, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPixmap pixmap;
+    for (int width : { 1, 1000, 1000000 } ) {
+        for (int height: { 1, 1000, 1000000 } ) {
+            SkImageInfo imageInfo = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType);
+            pixmap.reset(imageInfo, nullptr, width * 5);
+            SkDebugf("width: %7d height: %7d computeByteSize: %13lld\n", width, height,
+                     pixmap.computeByteSize());
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_computeIsOpaque.cpp b/docs/examples/Pixmap_computeIsOpaque.cpp
new file mode 100644
index 0000000..7eb76be
--- /dev/null
+++ b/docs/examples/Pixmap_computeIsOpaque.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6ef37d5be03d0bfaec992dbb5a94c66f
+REG_FIDDLE(Pixmap_computeIsOpaque, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    std::vector<uint32_t> pixels;
+    const int height = 2;
+    const int width = 2;
+    pixels.resize(height * width * 4);
+    SkPixmap pixmap(SkImageInfo::Make(width, height, kN32_SkColorType,
+            kPremul_SkAlphaType), (const void*) &pixels.front(), width * 4);
+    for (int index = 0; index < 2; ++index) {
+        pixmap.erase(0x00000000);
+        SkDebugf("computeIsOpaque: %s\n", pixmap.computeIsOpaque() ? "true" : "false");
+        pixmap.erase(0xFFFFFFFF);
+        SkDebugf("computeIsOpaque: %s\n", pixmap.computeIsOpaque() ? "true" : "false");
+        pixmap.reset(pixmap.info().makeAlphaType(kOpaque_SkAlphaType),
+                     (const void*) &pixels.front(), width * 4);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_const_SkImageInfo_const_star.cpp b/docs/examples/Pixmap_const_SkImageInfo_const_star.cpp
new file mode 100644
index 0000000..b742c72
--- /dev/null
+++ b/docs/examples/Pixmap_const_SkImageInfo_const_star.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9a00774be57d7308313b3a9073e6e696
+REG_FIDDLE(Pixmap_const_SkImageInfo_const_star, 256, 256, true, 3) {
+void draw(SkCanvas* canvas) {
+    SkDebugf("image alpha only = %s\n", image->isAlphaOnly() ? "true" : "false");
+    SkPMColor pmColors = 0;
+    sk_sp<SkImage> copy = SkImage::MakeRasterCopy({SkImageInfo::MakeA8(1, 1),
+                                                  (uint8_t*)&pmColors,
+                                                  1});
+    SkDebugf("copy alpha only = %s\n", copy->isAlphaOnly() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_empty_constructor.cpp b/docs/examples/Pixmap_empty_constructor.cpp
new file mode 100644
index 0000000..146f312
--- /dev/null
+++ b/docs/examples/Pixmap_empty_constructor.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9547e74a9d37553a667b913ffd1312dd
+REG_FIDDLE(Pixmap_empty_constructor, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const char* alphas[] = {"Unknown", "Opaque", "Premul", "Unpremul"};
+    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
+                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16Norm",
+                            "RGBA_F16"};
+    SkPixmap pixmap;
+    for (int i = 0; i < 2; ++i) {
+       SkDebugf("width: %2d  height: %2d", pixmap.width(), pixmap.height());
+       SkDebugf("  color: k%s_SkColorType", colors[pixmap.colorType()]);
+       SkDebugf("  alpha: k%s_SkAlphaType\n", alphas[pixmap.alphaType()]);
+       pixmap.reset(SkImageInfo::Make(25, 35, kRGBA_8888_SkColorType, kOpaque_SkAlphaType),
+                    nullptr, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_erase.cpp b/docs/examples/Pixmap_erase.cpp
new file mode 100644
index 0000000..e6299e3
--- /dev/null
+++ b/docs/examples/Pixmap_erase.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a0cdbafed4786788cc90681e7b294234
+REG_FIDDLE(Pixmap_erase, 256, 50, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint32_t storage[2];
+    SkImageInfo info = SkImageInfo::MakeN32Premul(1, 2);
+    SkPixmap pixmap(info, storage, info.minRowBytes());
+    pixmap.erase(SK_ColorBLUE, {0, 0, 1, 1});
+    pixmap.erase(SK_ColorRED, {0, 1, 1, 2});
+    SkBitmap bitmap;
+    canvas->scale(20, 20);
+    bitmap.installPixels(pixmap);
+    canvas->drawBitmap(bitmap, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_erase_2.cpp b/docs/examples/Pixmap_erase_2.cpp
new file mode 100644
index 0000000..8c3a120
--- /dev/null
+++ b/docs/examples/Pixmap_erase_2.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=838202e0d49cad2eb3eeb495834f6d63
+REG_FIDDLE(Pixmap_erase_2, 256, 50, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint32_t storage[2];
+    SkImageInfo info = SkImageInfo::MakeN32Premul(1, 2);
+    SkPixmap pixmap(info, storage, info.minRowBytes());
+    pixmap.erase(SK_ColorBLUE);
+    SkBitmap bitmap;
+    canvas->scale(20, 20);
+    bitmap.installPixels(pixmap);
+    canvas->drawBitmap(bitmap, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_erase_3.cpp b/docs/examples/Pixmap_erase_3.cpp
new file mode 100644
index 0000000..d0fcb1e
--- /dev/null
+++ b/docs/examples/Pixmap_erase_3.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f884f3f46a565f052a5e252ae2f36e9b
+REG_FIDDLE(Pixmap_erase_3, 256, 50, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint32_t storage[2];
+    SkImageInfo info = SkImageInfo::MakeN32Premul(1, 2);
+    SkPixmap pixmap(info, storage, info.minRowBytes());
+    SkIRect topPixelBounds = {0, 0, 1, 1};
+    pixmap.erase({ 0.65f, 0.45f, 0.25f, 1 }, &topPixelBounds);
+    SkIRect bottomPixelBounds = {0, 1, 1, 2};
+    pixmap.erase({ 0.25f, 0.65f, 0.45f, 1 }, &bottomPixelBounds);
+    SkBitmap bitmap;
+    canvas->scale(20, 20);
+    bitmap.installPixels(pixmap);
+    canvas->drawBitmap(bitmap, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_extractSubset.cpp b/docs/examples/Pixmap_extractSubset.cpp
new file mode 100644
index 0000000..946d110
--- /dev/null
+++ b/docs/examples/Pixmap_extractSubset.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=febdbfac6cf4cde69837643be2e1f6dd
+REG_FIDDLE(Pixmap_extractSubset, 256, 128, false, 3) {
+void draw(SkCanvas* canvas) {
+    std::vector<int32_t> pixels;
+    pixels.resize(image->height() * image->width() * 4);
+    SkPixmap pixmap(SkImageInfo::Make(image->width(), image->height(), kN32_SkColorType,
+            image->alphaType()), (const void*) &pixels.front(), image->width() * 4);
+    image->readPixels(pixmap, 0, 0);
+    SkPixmap inset;
+    if (pixmap.extractSubset(&inset, {128, 128, 512, 512})) {
+        SkBitmap bitmap;
+        bitmap.installPixels(inset);
+        canvas->drawBitmap(bitmap, 0, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_getColor.cpp b/docs/examples/Pixmap_getColor.cpp
new file mode 100644
index 0000000..e5276ef
--- /dev/null
+++ b/docs/examples/Pixmap_getColor.cpp
@@ -0,0 +1,29 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=94ad244056dc80ecd87daae004266334
+REG_FIDDLE(Pixmap_getColor, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int w = 4;
+    const int h = 4;
+    std::vector<SkPMColor> storage;
+    storage.resize(w * h);
+    SkDebugf("Premultiplied:\n");
+    for (int y = 0; y < h; ++y) {
+        SkDebugf("(0, %d) ", y);
+        for (int x = 0; x < w; ++x) {
+            int a = 0xFF * (x + y) / (w - 1 + h - 1);
+            storage[x + y * w] = SkPackARGB32(a, a * x / (w - 1), a * y / (h - 1), a);
+            SkDebugf("0x%08x%c", storage[x + y * w], x == w - 1 ? '\n' : ' ');
+        }
+    }
+    SkPixmap pixmap(SkImageInfo::MakeN32(w, h, kPremul_SkAlphaType), &storage.front(), w * 4);
+    SkDebugf("Unpremultiplied:\n");
+    for (int y = 0; y < h; ++y) {
+        SkDebugf("(0, %d) ", y);
+        for (int x = 0; x < w; ++x) {
+            SkDebugf("0x%08x%c", pixmap.getColor(x, y), x == w - 1 ? '\n' : ' ');
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_height.cpp b/docs/examples/Pixmap_height.cpp
new file mode 100644
index 0000000..d436d2d
--- /dev/null
+++ b/docs/examples/Pixmap_height.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4a996d32122f469d51ddd0186efb48cc
+REG_FIDDLE(Pixmap_height, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPixmap pixmap(SkImageInfo::MakeA8(16, 32), nullptr, 64);
+    SkDebugf("pixmap height: %d  info height: %d\n", pixmap.height(), pixmap.info().height());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_info.cpp b/docs/examples/Pixmap_info.cpp
new file mode 100644
index 0000000..a17a520
--- /dev/null
+++ b/docs/examples/Pixmap_info.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6e0f558bf7fabc655041116288559134
+REG_FIDDLE(Pixmap_info, 256, 256, true, 3) {
+void draw(SkCanvas* canvas) {
+    std::vector<int32_t> pixels;
+    pixels.resize(image->height() * image->width() * 4);
+    SkPixmap pixmap(SkImageInfo::Make(image->width(), image->height(), kN32_SkColorType,
+            image->alphaType()), (const void*) &pixels.front(), image->width() * 4);
+    image->readPixels(pixmap, 0, 0);
+    SkPixmap inset;
+    if (pixmap.extractSubset(&inset, {128, 128, 512, 512})) {
+        const SkImageInfo& info = inset.info();
+        const char* alphas[] = {"Unknown", "Opaque", "Premul", "Unpremul"};
+        const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888",
+                "RGB_888x", "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16Norm",
+                            "RGBA_F16"};
+        SkDebugf("width: %d height: %d color: %s alpha: %s\n", info.width(), info.height(),
+                 colors[info.colorType()], alphas[info.alphaType()]);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_isOpaque.cpp b/docs/examples/Pixmap_isOpaque.cpp
new file mode 100644
index 0000000..2c399a8
--- /dev/null
+++ b/docs/examples/Pixmap_isOpaque.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=efd083f121e888a523455ea8a49e50d1
+REG_FIDDLE(Pixmap_isOpaque, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    std::vector<uint32_t> pixels;
+    const int height = 2;
+    const int width = 2;
+    pixels.resize(height * width * 4);
+    SkPixmap pixmap(SkImageInfo::Make(width, height, kN32_SkColorType,
+            kPremul_SkAlphaType), (const void*) &pixels.front(), width * 4);
+    for (int index = 0; index < 2; ++index) {
+        pixmap.erase(0x00000000);
+        SkDebugf("isOpaque: %s\n", pixmap.isOpaque() ? "true" : "false");
+        pixmap.erase(0xFFFFFFFF);
+        SkDebugf("isOpaque: %s\n", pixmap.isOpaque() ? "true" : "false");
+        pixmap.reset(pixmap.info().makeAlphaType(kOpaque_SkAlphaType),
+                     (const void*) &pixels.front(), width * 4);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_readPixels.cpp b/docs/examples/Pixmap_readPixels.cpp
new file mode 100644
index 0000000..23c7c5e
--- /dev/null
+++ b/docs/examples/Pixmap_readPixels.cpp
@@ -0,0 +1,31 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=df4e355c4845350daede833b4fd21ec1
+REG_FIDDLE(Pixmap_readPixels, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    std::vector<int32_t> pixels;
+    const int width = 256;
+    const int height = 64;
+    pixels.resize(height * width * 4);
+    SkImageInfo srcInfo = SkImageInfo::MakeN32Premul(width, height);
+    SkPixmap srcPixmap(srcInfo, (const void*) &pixels.front(), width * 4);
+    SkColor  gradColors[] = { 0xFFAA3300, 0x7F881122 };
+    SkPoint  gradPoints[] = { { 0, 0 }, { 256, 0 } };
+    SkPaint paint;
+    paint.setShader(SkGradientShader::MakeLinear(gradPoints, gradColors, nullptr,
+                    SK_ARRAY_COUNT(gradColors), SkTileMode::kClamp));
+    SkBitmap bitmap;
+    bitmap.installPixels(srcPixmap);
+    SkCanvas srcCanvas(bitmap);
+    srcCanvas.drawRect(SkRect::MakeWH(width, height), paint);
+    canvas->drawBitmap(bitmap, 0, 0);
+    std::vector<int32_t> dstPixels;
+    dstPixels.resize(height * width * 2);
+    SkImageInfo dstInfo = srcInfo.makeColorType(kARGB_4444_SkColorType);
+    srcPixmap.readPixels(dstInfo, &dstPixels.front(), width * 2);
+    SkPixmap dstPixmap(dstInfo, &dstPixels.front(), width * 2);
+    bitmap.installPixels(dstPixmap);
+    canvas->drawBitmap(bitmap, 0, 128);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_readPixels_2.cpp b/docs/examples/Pixmap_readPixels_2.cpp
new file mode 100644
index 0000000..f29f1a6
--- /dev/null
+++ b/docs/examples/Pixmap_readPixels_2.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=094ca0bd37588cc7be241bb387a3e17b
+REG_FIDDLE(Pixmap_readPixels_2, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo info = SkImageInfo::MakeN32Premul(image->width(), image->height());
+    std::vector<int32_t> srcPixels;
+    const int rowBytes = image->width() * 4;
+    srcPixels.resize(image->height() * rowBytes);
+    SkPixmap pixmap(info, (const void*) &srcPixels.front(), rowBytes);
+    image->readPixels(pixmap, 0, 0);
+    for (int offset : { 32, 64, 96 } ) {
+        std::vector<int32_t> dstPixels;
+        dstPixels.resize(image->height() * rowBytes);
+        pixmap.readPixels(info, &dstPixels.front(), rowBytes, offset, 0);
+        SkBitmap bitmap;
+        SkPixmap dstmap(info, &dstPixels.front(), rowBytes);
+        bitmap.installPixels(dstmap);
+        canvas->translate(32, 32);
+        canvas->drawBitmap(bitmap, 0, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_readPixels_3.cpp b/docs/examples/Pixmap_readPixels_3.cpp
new file mode 100644
index 0000000..9698bde
--- /dev/null
+++ b/docs/examples/Pixmap_readPixels_3.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6ec7f7b2cc163cd29f627eef6d4b061c
+REG_FIDDLE(Pixmap_readPixels_3, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo info = SkImageInfo::MakeN32Premul(image->width(), image->height());
+    std::vector<int32_t> srcPixels;
+    const int rowBytes = image->width() * 4;
+    srcPixels.resize(image->height() * rowBytes);
+    SkPixmap pixmap(info, (const void*) &srcPixels.front(), rowBytes);
+    image->readPixels(pixmap, 0, 0);
+    for (int offset : { 32, 64, 96 } ) {
+        std::vector<int32_t> dstPixels;
+        dstPixels.resize(image->height() * rowBytes);
+        SkPixmap dstmap(info, &dstPixels.front(), rowBytes);
+        pixmap.readPixels(dstmap, offset, 0);
+        SkBitmap bitmap;
+        bitmap.installPixels(dstmap);
+        canvas->translate(32, 32);
+        canvas->drawBitmap(bitmap, 0, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_readPixels_4.cpp b/docs/examples/Pixmap_readPixels_4.cpp
new file mode 100644
index 0000000..d6615f1
--- /dev/null
+++ b/docs/examples/Pixmap_readPixels_4.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e18549b5ee1039cb61b0bb38c2104fc9
+REG_FIDDLE(Pixmap_readPixels_4, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo info = SkImageInfo::MakeN32Premul(image->width(), image->height());
+    std::vector<int32_t> srcPixels;
+    const int rowBytes = image->width() * 4;
+    srcPixels.resize(image->height() * rowBytes);
+    SkPixmap pixmap(info, (const void*) &srcPixels.front(), rowBytes);
+    image->readPixels(pixmap, 0, 0);
+    for (int index = 0; index < 3; ++index ) {
+        std::vector<int32_t> dstPixels;
+        dstPixels.resize(image->height() * rowBytes);
+        SkPixmap dstmap(info, &dstPixels.front(), rowBytes);
+        pixmap.readPixels(dstmap);
+        SkBitmap bitmap;
+        bitmap.installPixels(dstmap);
+        canvas->translate(32, 32);
+        canvas->drawBitmap(bitmap, 0, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_reset.cpp b/docs/examples/Pixmap_reset.cpp
new file mode 100644
index 0000000..91e77f4
--- /dev/null
+++ b/docs/examples/Pixmap_reset.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d9eb583c39f4f0baea79896b89245c98
+REG_FIDDLE(Pixmap_reset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const char* alphas[] = {"Unknown", "Opaque", "Premul", "Unpremul"};
+    const char* colors[] = {"Unknown", "Alpha_8", "RGB_565", "ARGB_4444", "RGBA_8888", "RGB_888x",
+                            "BGRA_8888", "RGBA_1010102", "RGB_101010x", "Gray_8", "RGBA_F16Norm",
+                            "RGBA_F16"};
+    SkPixmap pixmap(SkImageInfo::Make(25, 35, kRGBA_8888_SkColorType, kOpaque_SkAlphaType),
+                    nullptr, 0);
+    for (int i = 0; i < 2; ++i) {
+       SkDebugf("width: %2d  height: %2d", pixmap.width(), pixmap.height());
+       SkDebugf("  color: k%s_SkColorType", colors[pixmap.colorType()]);
+       SkDebugf("  alpha: k%s_SkAlphaType\n", alphas[pixmap.alphaType()]);
+       pixmap.reset();
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_reset_2.cpp b/docs/examples/Pixmap_reset_2.cpp
new file mode 100644
index 0000000..4036640
--- /dev/null
+++ b/docs/examples/Pixmap_reset_2.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9a392b753167cfa849cebeefd5a6e07d
+REG_FIDDLE(Pixmap_reset_2, 256, 64, false, 4) {
+void draw(SkCanvas* canvas) {
+    std::vector<int32_t> pixels;
+    pixels.resize(image->height() * image->width() * 4);
+    SkPixmap pixmap(SkImageInfo::Make(image->width(), image->height(), kN32_SkColorType,
+            image->alphaType()), (const void*) &pixels.front(), image->width() * 4);
+    image->readPixels(pixmap, 0, 0);
+    int x = 0;
+    for (auto colorType : { kRGBA_8888_SkColorType, kBGRA_8888_SkColorType } ) {
+        pixmap.reset(SkImageInfo::Make(image->width(), image->height(), colorType,
+                image->alphaType()), (const void*) &pixels.front(), image->width() * 4);
+        SkBitmap bitmap;
+        bitmap.installPixels(pixmap);
+        canvas->drawBitmap(bitmap, x, 0);
+        x += 128;
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_rowBytes.cpp b/docs/examples/Pixmap_rowBytes.cpp
new file mode 100644
index 0000000..97cb73d
--- /dev/null
+++ b/docs/examples/Pixmap_rowBytes.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=19ac8bb81854680bd408fec8cb797d5c
+REG_FIDDLE(Pixmap_rowBytes, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPixmap badPixmap = {SkImageInfo::MakeA8(4, 4), nullptr, 2};
+    SkPixmap okPixmap = {SkImageInfo::MakeA8(4, 4), nullptr, 8};
+    for (auto& pixmap : { badPixmap, okPixmap } ) {
+        SkDebugf("rowBytes: %d minRowBytes: %d\n", pixmap.rowBytes(),
+           pixmap.info().minRowBytes());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_rowBytesAsPixels.cpp b/docs/examples/Pixmap_rowBytesAsPixels.cpp
new file mode 100644
index 0000000..89e42d5
--- /dev/null
+++ b/docs/examples/Pixmap_rowBytesAsPixels.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6231bb212d0c231b5bc44eac626fbcb5
+REG_FIDDLE(Pixmap_rowBytesAsPixels, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    for (int rowBytes : { 4, 5, 6, 7, 8} ) {
+        SkPixmap pixmap(SkImageInfo::MakeN32(1, 1, kPremul_SkAlphaType), nullptr, rowBytes);
+        SkDebugf("rowBytes: %d rowBytesAsPixels: %d\n", rowBytes, pixmap.rowBytesAsPixels());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_scalePixels.cpp b/docs/examples/Pixmap_scalePixels.cpp
new file mode 100644
index 0000000..5ecaba1
--- /dev/null
+++ b/docs/examples/Pixmap_scalePixels.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8e3c8a9c1d0d2e9b8bf66e24d274f792
+REG_FIDDLE(Pixmap_scalePixels, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo info = SkImageInfo::MakeN32Premul(image->width(), image->height());
+    std::vector<int32_t> srcPixels;
+    int rowBytes = image->width() * 4;
+    srcPixels.resize(image->height() * rowBytes);
+    SkPixmap pixmap(info, (const void*) &srcPixels.front(), rowBytes);
+    image->readPixels(pixmap, 0, 0);
+    for (int offset : { 32, 64, 96 } ) {
+        info = SkImageInfo::MakeN32Premul(image->width() + offset, image->height());
+        rowBytes = info.width() * 4;
+        std::vector<int32_t> dstPixels;
+        dstPixels.resize(image->height() * rowBytes);
+        SkPixmap dstmap(info, &dstPixels.front(), rowBytes);
+        pixmap.scalePixels(dstmap, kMedium_SkFilterQuality);
+        SkBitmap bitmap;
+        bitmap.installPixels(dstmap);
+        canvas->translate(32, 32);
+        canvas->drawBitmap(bitmap, 0, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_setColorSpace.cpp b/docs/examples/Pixmap_setColorSpace.cpp
new file mode 100644
index 0000000..e6822d6
--- /dev/null
+++ b/docs/examples/Pixmap_setColorSpace.cpp
@@ -0,0 +1,16 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=30d70aec4de17c831dba71e03dc9664a
+REG_FIDDLE(Pixmap_setColorSpace, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPixmap pixmap;
+    sk_sp<SkColorSpace> colorSpace1 = SkColorSpace::MakeRGB(SkColorSpace::kLinear_RenderTargetGamma,
+                                                            SkColorSpace::kRec2020_Gamut);
+    SkDebugf("is %sunique\n", colorSpace1->unique() ? "" : "not ");
+    pixmap.setColorSpace(colorSpace1);
+    SkDebugf("is %sunique\n", colorSpace1->unique() ? "" : "not ");
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Pixmap_shiftPerPixel.cpp b/docs/examples/Pixmap_shiftPerPixel.cpp
new file mode 100644
index 0000000..1538cc7
--- /dev/null
+++ b/docs/examples/Pixmap_shiftPerPixel.cpp
@@ -0,0 +1,36 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bf31ee140e2c163c3957276e6d4c4f0c
+REG_FIDDLE(Pixmap_shiftPerPixel, 256, 256, true, 0) {
+const char* color_type(SkColorType ct) {
+    switch (ct) {
+        case kUnknown_SkColorType:      return "Unknown";
+        case kAlpha_8_SkColorType:      return "Alpha_8";
+        case kRGB_565_SkColorType:      return "RGB_565";
+        case kARGB_4444_SkColorType:    return "ARGB_4444";
+        case kRGBA_8888_SkColorType:    return "RGBA_8888";
+        case kRGB_888x_SkColorType:     return "RGB_888x";
+        case kBGRA_8888_SkColorType:    return "BGRA_8888";
+        case kRGBA_1010102_SkColorType: return "RGBA_1010102";
+        case kRGB_101010x_SkColorType:  return "RGB_101010x";
+        case kGray_8_SkColorType:       return "Gray_8";
+        case kRGBA_F16Norm_SkColorType: return "RGBA_F16Norm";
+        case kRGBA_F16_SkColorType:     return "RGBA_F16";
+        case kRGBA_F32_SkColorType:     return "RGBA_F32";
+        default: SkASSERT(false); return nullptr;
+    }
+}
+void draw(SkCanvas* canvas) {
+    SkImageInfo info = SkImageInfo::MakeA8(1, 1);
+    for (SkColorType colorType : { kUnknown_SkColorType,   kAlpha_8_SkColorType,
+                                   kRGB_565_SkColorType,   kARGB_4444_SkColorType,
+                                   kRGBA_8888_SkColorType, kBGRA_8888_SkColorType,
+                                   kGray_8_SkColorType,    kRGBA_F16_SkColorType } ) {
+        SkPixmap pixmap(info.makeColorType(colorType), nullptr, 4);
+        SkDebugf("color: k" "%s" "_SkColorType" "%*s" "bytesPerPixel: %d shiftPerPixel: %d\n",
+                color_type(colorType), 10 - strlen(color_type(colorType)), " ",
+                pixmap.info().bytesPerPixel(), pixmap.shiftPerPixel());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_width.cpp b/docs/examples/Pixmap_width.cpp
new file mode 100644
index 0000000..63f3426
--- /dev/null
+++ b/docs/examples/Pixmap_width.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f68617b7153a20b2ed3d7f9ed5c6e5e4
+REG_FIDDLE(Pixmap_width, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo info = SkImageInfo::MakeA8(16, 32);
+    SkPixmap pixmap(info, nullptr, 64);
+    SkDebugf("pixmap width: %d  info width: %d\n", pixmap.width(), info.width());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_writable_addr.cpp b/docs/examples/Pixmap_writable_addr.cpp
new file mode 100644
index 0000000..d0601a1
--- /dev/null
+++ b/docs/examples/Pixmap_writable_addr.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=74ef460f89ed5904334d0f8883e781c4
+REG_FIDDLE(Pixmap_writable_addr, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int w = 4;
+    const int h = 4;
+    SkPMColor storage[w * h * 4];
+    SkPixmap pixmap(SkImageInfo::MakeN32(w, h, kPremul_SkAlphaType), storage, w * 4);
+    SkDebugf("pixmap.writable_addr() %c= (void *)storage\n",
+              pixmap.writable_addr()  == (void *)storage ? '=' : '!');
+    pixmap.erase(0x00000000);
+    *(SkPMColor*)pixmap.writable_addr() = 0xFFFFFFFF;
+    SkDebugf("pixmap.getColor(0, 1) %c= 0x00000000\n",
+              pixmap.getColor(0, 1)  == 0x00000000 ? '=' : '!');
+    SkDebugf("pixmap.getColor(0, 0) %c= 0xFFFFFFFF\n",
+              pixmap.getColor(0, 0)  == 0xFFFFFFFF ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_writable_addr16.cpp b/docs/examples/Pixmap_writable_addr16.cpp
new file mode 100644
index 0000000..d44d930
--- /dev/null
+++ b/docs/examples/Pixmap_writable_addr16.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6da54774f6432b46b47ea9013c15f280
+REG_FIDDLE(Pixmap_writable_addr16, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint16_t storage[][5] = {{ 0xCABF, 0xDABE, 0xCA9D, 0xC96C, 0xA39B },
+                             { 0xACEE, 0xA87C, 0x893A, 0x4779, 0x8708 },
+                             { 0x4B7C, 0x255B, 0x2559, 0x2557, 0x4656 },
+                             { 0x9099, 0x8128, 0x2557, 0x4124, 0x3323 },
+                             { 0x7547, 0x5505, 0x4434, 0x2012, 0x0000 }};
+    SkImageInfo imageInfo = SkImageInfo::Make(5, 5, kARGB_4444_SkColorType, kPremul_SkAlphaType);
+    SkPixmap pixmap(imageInfo, storage[0], sizeof(storage) / 5);
+    SkBitmap bitmap;
+    bitmap.installPixels(pixmap);
+    canvas->scale(10, 10);
+    canvas->drawBitmap(bitmap, 0, 0);
+    *pixmap.writable_addr16(2, 2) = 0x000F;
+    bitmap.installPixels(pixmap);
+    canvas->drawBitmap(bitmap, 10, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_writable_addr32.cpp b/docs/examples/Pixmap_writable_addr32.cpp
new file mode 100644
index 0000000..54bde5e
--- /dev/null
+++ b/docs/examples/Pixmap_writable_addr32.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=086866243bf9e4c14c3b215a2aa69ad9
+REG_FIDDLE(Pixmap_writable_addr32, 256, 72, false, 4) {
+void draw(SkCanvas* canvas) {
+    std::vector<int32_t> pixels;
+    pixels.resize(image->height() * image->width() * 4);
+    SkPixmap pixmap(SkImageInfo::Make(image->width(), image->height(), kN32_SkColorType,
+            image->alphaType()), (const void*) &pixels.front(), image->width() * 4);
+    image->readPixels(pixmap, 0, 0);
+    for (int y = 0; y < pixmap.height() / 2; ++y) {
+        for (int x = 0; x < pixmap.width(); ++x) {
+            if ((x & 4) == (y & 4)) {
+                *pixmap.writable_addr32(x, y) =
+                        *pixmap.writable_addr32(pixmap.width() - x - 1, pixmap.height() - y - 1);
+            }
+        }
+    }
+    SkBitmap bitmap;
+    bitmap.installPixels(pixmap);
+    canvas->drawBitmap(bitmap, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_writable_addr64.cpp b/docs/examples/Pixmap_writable_addr64.cpp
new file mode 100644
index 0000000..b5e775a
--- /dev/null
+++ b/docs/examples/Pixmap_writable_addr64.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=de14d8d30e4a7b6462103d0e0dd96b0b
+REG_FIDDLE(Pixmap_writable_addr64, 256, 40, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo info = SkImageInfo::Make(3, 3, kRGBA_F16_SkColorType, kPremul_SkAlphaType);
+    uint64_t storage[9];
+    SkPixmap pixmap(info, storage, 3 * sizeof(uint64_t));
+    SkColor4f c4 { 1, 0.45f, 0.25f, 0.65f };
+    pixmap.erase(c4);
+    SkBitmap bitmap;
+    canvas->scale(10, 10);
+    bitmap.installPixels(pixmap);
+    canvas->drawBitmap(bitmap, 0, 0);
+    *pixmap.writable_addr64(1, 1) |= 0x00ff000000000000LL;
+    bitmap.installPixels(pixmap);
+    canvas->drawBitmap(bitmap, 10, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_writable_addr8.cpp b/docs/examples/Pixmap_writable_addr8.cpp
new file mode 100644
index 0000000..c2ecd52
--- /dev/null
+++ b/docs/examples/Pixmap_writable_addr8.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=809284db136748208b3efc31cd89de29
+REG_FIDDLE(Pixmap_writable_addr8, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    uint8_t storage[][5] = {{ 0,   0,  64,   0,  0},
+                            { 0, 128, 255, 128,  0},
+                            {64, 255, 255, 255, 64},
+                            { 0, 128, 255, 128,  0},
+                            { 0,   0,  64,   0,  0}};
+    SkImageInfo imageInfo = SkImageInfo::Make(5, 5, kGray_8_SkColorType, kPremul_SkAlphaType);
+    SkPixmap pixmap(imageInfo, storage[0], 5);
+    SkBitmap bitmap;
+    bitmap.installPixels(pixmap);
+    canvas->scale(10, 10);
+    canvas->drawBitmap(bitmap, 0, 0);
+    *pixmap.writable_addr8(2, 2) = 0;
+//  bitmap.installPixels(pixmap);      // uncomment to fix on GPU
+    canvas->drawBitmap(bitmap, 10, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_writable_addrF16.cpp b/docs/examples/Pixmap_writable_addrF16.cpp
new file mode 100644
index 0000000..c3cf286
--- /dev/null
+++ b/docs/examples/Pixmap_writable_addrF16.cpp
@@ -0,0 +1,25 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7822d78f5cacf5c04267cbbc6c6d0b80
+REG_FIDDLE(Pixmap_writable_addrF16, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkImageInfo info = SkImageInfo::Make(1, 2, kRGBA_F16_SkColorType, kPremul_SkAlphaType);
+    uint16_t storage[2][4];
+    SkPixmap pixmap(info, storage[0], sizeof(uint64_t));
+    SkIRect topPixelBounds = {0, 0, 1, 1};
+    pixmap.erase({ 0.65f, 0.45f, 0.25f, 1 }, &topPixelBounds);
+    SkIRect bottomPixelBounds = {0, 1, 1, 2};
+    pixmap.erase({ 0.25f, 0.65f, 0.45f, 1 }, &bottomPixelBounds);
+    SkBitmap bitmap;
+    canvas->scale(20, 20);
+    bitmap.installPixels(pixmap);
+    canvas->drawBitmap(bitmap, 0, 0);
+    uint16_t* pixel2 = pixmap.writable_addrF16(0, 1);
+    for (int i = 0; i < 4; ++i) {
+        pixel2[i] = storage[0][i];
+    }
+    bitmap.installPixels(pixmap);
+    canvas->drawBitmap(bitmap, 4, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Pixmap_writable_addr_2.cpp b/docs/examples/Pixmap_writable_addr_2.cpp
new file mode 100644
index 0000000..13c5759
--- /dev/null
+++ b/docs/examples/Pixmap_writable_addr_2.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=559eaca89c765bc8466ea1ba3331d4db
+REG_FIDDLE(Pixmap_writable_addr_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int w = 4;
+    const int h = 4;
+    SkPMColor storage[w * h * 4];
+    SkPixmap pixmap(SkImageInfo::MakeN32(w, h, kPremul_SkAlphaType), storage, w * 4);
+    SkDebugf("pixmap.writable_addr() %c= (void *)storage\n",
+              pixmap.writable_addr()  == (void *)storage ? '=' : '!');
+    pixmap.erase(0x00000000);
+    *(SkPMColor*)pixmap.writable_addr(1, 2) = 0xFFFFFFFF;
+    SkDebugf("pixmap.getColor(0, 0) %c= 0x00000000\n",
+              pixmap.getColor(0, 0)  == 0x00000000 ? '=' : '!');
+    SkDebugf("pixmap.getColor(1, 2) %c= 0xFFFFFFFF\n",
+              pixmap.getColor(1, 2)  == 0xFFFFFFFF ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Plus.cpp b/docs/examples/Plus.cpp
new file mode 100644
index 0000000..9289c80
--- /dev/null
+++ b/docs/examples/Plus.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=05383441e510d54008402e128fc8ad2b
+REG_FIDDLE(Plus, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->drawColor(SK_ColorBLACK);
+    SkPaint paint;
+    paint.setBlendMode(SkBlendMode::kPlus);
+    for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN } ) {
+        SkColor colors[] = { color, SkColorSetA(color, 192), SkColorSetA(color, 128),
+                             SkColorSetA(color, 0) };
+        paint.setShader(SkGradientShader::MakeRadial({ 64, 64}, 100,
+                colors, nullptr, SK_ARRAY_COUNT(colors), SkTileMode::kClamp));
+        canvas->drawCircle(64, 64, 100, paint);
+        canvas->translate(64, 64);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_CrossProduct.cpp b/docs/examples/Point_CrossProduct.cpp
new file mode 100644
index 0000000..7ab7281
--- /dev/null
+++ b/docs/examples/Point_CrossProduct.cpp
@@ -0,0 +1,27 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8b8a4cd8a29d22bb9c5e63b70357bd65
+REG_FIDDLE(Point_CrossProduct, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkVector vectors[][2] = {{{50, 2}, {-14, 20}}, {{0, 50}, {-50, 0}}, {{-20, 25}, {25, -20}},
+                             {{-20, -24}, {-24, -20}}};
+    SkPoint center[] = {{32, 32}, {160, 32}, {32, 160}, {160, 160}};
+    paint.setStrokeWidth(2);
+    for (size_t i = 0; i < 4; ++i) {
+        paint.setColor(SK_ColorRED);
+        canvas->drawLine(center[i], center[i] + vectors[i][0], paint);
+        paint.setColor(SK_ColorBLUE);
+        canvas->drawLine(center[i], center[i] + vectors[i][1], paint);
+        SkString str;
+        SkScalar cross = SkPoint::CrossProduct(vectors[i][1], vectors[i][0]);
+        str.printf("cross = %g", cross);
+        paint.setColor(cross >= 0 ? SK_ColorRED : SK_ColorBLUE);
+        canvas->drawString(str, center[i].fX, center[i].fY, paint);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Point_Distance.cpp b/docs/examples/Point_Distance.cpp
new file mode 100644
index 0000000..ce8a31d
--- /dev/null
+++ b/docs/examples/Point_Distance.cpp
@@ -0,0 +1,25 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9e0a2de2eb94dba4521d733e73f2bda5
+REG_FIDDLE(Point_Distance, 256, 192, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    const SkPoint lines[][2] = {{{-10, -10}, {90, 30}}, {{0, 0}, {150, 30}}, {{10, 25}, {120, 150}}};
+    const SkPoint origin = {30, 160};
+    for (auto line : lines) {
+        SkPoint a = origin + line[0];
+        const SkPoint& b = line[1];
+        canvas->drawLine(a, b, paint);
+        SkAutoCanvasRestore acr(canvas, true);
+        SkScalar angle = SkScalarATan2((b.fY - a.fY), b.fX - a.fX);
+        canvas->rotate(angle * 180 / SK_ScalarPI, a.fX, a.fY);
+        SkString distance("distance = ");
+        distance.appendScalar(SkPoint::Distance(a, b));
+        canvas->drawString(distance, a.fX + 25, a.fY - 4, paint);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Point_DotProduct.cpp b/docs/examples/Point_DotProduct.cpp
new file mode 100644
index 0000000..d49e10f
--- /dev/null
+++ b/docs/examples/Point_DotProduct.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=496db0131a003162faba7d7f98b30340
+REG_FIDDLE(Point_DotProduct, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkVector vectors[][2] = {{{50, 2}, {-14, 20}}, {{0, 50}, {-50, 0}}, {{-20, 25}, {25, -20}},
+                             {{-20, -24}, {-24, -20}}};
+    SkPoint center[] = {{32, 32}, {160, 32}, {32, 160}, {160, 160}};
+    paint.setStrokeWidth(2);
+    for (size_t i = 0; i < 4; ++i) {
+        canvas->drawLine(center[i], center[i] + vectors[i][0], paint);
+        canvas->drawLine(center[i], center[i] + vectors[i][1], paint);
+        SkString str;
+        str.printf("dot = %g", SkPoint::DotProduct(vectors[i][0], vectors[i][1]));
+        canvas->drawString(str, center[i].fX, center[i].fY, paint);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Point_Length.cpp b/docs/examples/Point_Length.cpp
new file mode 100644
index 0000000..5f5d622
--- /dev/null
+++ b/docs/examples/Point_Length.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c98773d8b4509969d78cb8121e4b77f6
+REG_FIDDLE(Point_Length, 256, 192, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    const SkPoint points[] = { { 90, 30 }, { 120, 150 }, { 150, 30 }, { 210, 90 } };
+    const SkPoint origin = {30, 140};
+    for (auto point : points) {
+        canvas->drawLine(origin, point, paint);
+        SkAutoCanvasRestore acr(canvas, true);
+        SkScalar angle = SkScalarATan2((point.fY - origin.fY), point.fX - origin.fX);
+        canvas->rotate(angle * 180 / SK_ScalarPI, origin.fX, origin.fY);
+        SkString length("length = ");
+        length.appendScalar(SkPoint::Length(point.fX, point.fY));
+        canvas->drawString(length, origin.fX + 25, origin.fY - 4, paint);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Point_Make.cpp b/docs/examples/Point_Make.cpp
new file mode 100644
index 0000000..5a041b0
--- /dev/null
+++ b/docs/examples/Point_Make.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d266e70977847001f7c42f8a2513bee7
+REG_FIDDLE(Point_Make, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint pt1 = {45, 66};
+    SkPoint pt2 = SkPoint::Make(45, 66);
+    SkVector v1 = {45, 66};
+    SkVector v2 = SkPoint::Make(45, 66);
+    SkDebugf("all %s" "equal\n", pt1 == pt2 && pt2 == v1 && v1 == v2 ? "" : "not ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_Normalize.cpp b/docs/examples/Point_Normalize.cpp
new file mode 100644
index 0000000..31b14d4
--- /dev/null
+++ b/docs/examples/Point_Normalize.cpp
@@ -0,0 +1,26 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=60a08f3ce75374fc815384616d114df7
+REG_FIDDLE(Point_Normalize, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    const SkPoint lines[][2] = { {{  30, 110 }, { 190,  30 }},
+                                 {{  30, 220 }, { 120, 140 }}};
+    for (auto line : lines) {
+        canvas->drawLine(line[0], line[1], paint);
+        SkVector vector = line[1] - line[0];
+        SkScalar priorLength = SkPoint::Normalize(&vector);
+        SkVector rotate90 = { -vector.fY, vector.fX };
+        rotate90 *= 10.f;
+        canvas->drawLine(line[0] - rotate90, line[0] + rotate90, paint);
+        canvas->drawLine(line[1] - rotate90, line[1] + rotate90, paint);
+        SkString length("length = ");
+        length.appendScalar(priorLength);
+        canvas->drawString(length, line[0].fX + 25, line[0].fY - 4, paint);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Point_Offset.cpp b/docs/examples/Point_Offset.cpp
new file mode 100644
index 0000000..f748144
--- /dev/null
+++ b/docs/examples/Point_Offset.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f0f24726df78a5d797bcf311e694a0a3
+REG_FIDDLE(Point_Offset, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 },
+                         { 6, 4 }, { 7, 5 }, { 5, 7 },
+                         { 4, 6 }, { 3, 7 }, { 1, 5 },
+                         { 2, 4 }, { 1, 3 }, { 3, 1 } };
+    canvas->scale(30, 15);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
+    SkPoint::Offset(points, SK_ARRAY_COUNT(points), { 1, 9 } );
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_Offset_2.cpp b/docs/examples/Point_Offset_2.cpp
new file mode 100644
index 0000000..e301ea8
--- /dev/null
+++ b/docs/examples/Point_Offset_2.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=532849faa838de885b86d3ebffae3712
+REG_FIDDLE(Point_Offset_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 },
+                         { 6, 4 }, { 7, 5 }, { 5, 7 },
+                         { 4, 6 }, { 3, 7 }, { 1, 5 },
+                         { 2, 4 }, { 1, 3 }, { 3, 1 } };
+    canvas->scale(30, 15);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
+    SkPoint::Offset(points, SK_ARRAY_COUNT(points), 1, 9);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_add_operator.cpp b/docs/examples/Point_add_operator.cpp
new file mode 100644
index 0000000..8b4eee0
--- /dev/null
+++ b/docs/examples/Point_add_operator.cpp
@@ -0,0 +1,25 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=911a84253dfec4dabf94dbe3c71766f0
+REG_FIDDLE(Point_add_operator, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 },
+                         { 6, 4 }, { 7, 5 }, { 5, 7 },
+                         { 4, 6 }, { 3, 7 }, { 1, 5 },
+                         { 2, 4 }, { 1, 3 }, { 3, 1 } };
+    canvas->scale(30, 15);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
+    SkVector mod = {1, 1};
+    for (auto& point : points) {
+        point = point + mod;
+        mod.fX *= 1.1f;
+        mod.fY += .2f;
+    }
+    paint.setColor(SK_ColorRED);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_addto_operator.cpp b/docs/examples/Point_addto_operator.cpp
new file mode 100644
index 0000000..c9ec913
--- /dev/null
+++ b/docs/examples/Point_addto_operator.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8b4e79109e2381345258cb744881b20c
+REG_FIDDLE(Point_addto_operator, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 },
+                         { 6, 4 }, { 7, 5 }, { 5, 7 },
+                         { 4, 6 }, { 3, 7 }, { 1, 5 },
+                         { 2, 4 }, { 1, 3 }, { 3, 1 } };
+    canvas->scale(30, 15);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
+    points[1] += {1, 1};
+    points[2] += {-1, -1};
+    paint.setColor(SK_ColorRED);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_cross.cpp b/docs/examples/Point_cross.cpp
new file mode 100644
index 0000000..4dd8fd7
--- /dev/null
+++ b/docs/examples/Point_cross.cpp
@@ -0,0 +1,27 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0bc7b3997357e499817278b78bdfbf1d
+REG_FIDDLE(Point_cross, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkVector vectors[][2] = {{{50, 2}, {-14, 20}}, {{0, 50}, {-50, 0}}, {{-20, 25}, {25, -20}},
+                             {{-20, -24}, {-24, -20}}};
+    SkPoint center[] = {{32, 32}, {160, 32}, {32, 160}, {160, 160}};
+    paint.setStrokeWidth(2);
+    for (size_t i = 0; i < 4; ++i) {
+        paint.setColor(SK_ColorRED);
+        canvas->drawLine(center[i], center[i] + vectors[i][0], paint);
+        paint.setColor(SK_ColorBLUE);
+        canvas->drawLine(center[i], center[i] + vectors[i][1], paint);
+        SkString str;
+        SkScalar cross = vectors[i][0].cross(vectors[i][1]);
+        str.printf("cross = %g", cross);
+        paint.setColor(cross >= 0 ? SK_ColorRED : SK_ColorBLUE);
+        canvas->drawString(str, center[i].fX, center[i].fY, paint);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Point_distanceToOrigin.cpp b/docs/examples/Point_distanceToOrigin.cpp
new file mode 100644
index 0000000..4611095
--- /dev/null
+++ b/docs/examples/Point_distanceToOrigin.cpp
@@ -0,0 +1,24 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=812cf26d91b1cdcd2c6b9438a8172518
+REG_FIDDLE(Point_distanceToOrigin, 256, 192, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    const SkPoint points[] = { { 60, -110 }, { 90, 10 }, { 120, -110 }, { 180, -50 } };
+    const SkPoint origin = {0, 0};
+    canvas->translate(30, 140);
+    for (auto point : points) {
+        canvas->drawLine(origin, point, paint);
+        SkAutoCanvasRestore acr(canvas, true);
+        SkScalar angle = SkScalarATan2((point.fY - origin.fY), point.fX - origin.fX);
+        canvas->rotate(angle * 180 / SK_ScalarPI, origin.fX, origin.fY);
+        SkString distance("distance = ");
+        distance.appendScalar(point.distanceToOrigin());
+        canvas->drawString(distance, origin.fX + 25, origin.fY - 4, paint);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Point_dot.cpp b/docs/examples/Point_dot.cpp
new file mode 100644
index 0000000..fb436c4
--- /dev/null
+++ b/docs/examples/Point_dot.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=56d01ccfedd71d3c504b09afa2875d38
+REG_FIDDLE(Point_dot, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkVector vectors[][2] = {{{50, 2}, {-14, 20}}, {{0, 50}, {-50, 0}}, {{-20, 25}, {25, -20}},
+                             {{-20, -24}, {-24, -20}}};
+    SkPoint center[] = {{32, 32}, {160, 32}, {32, 160}, {160, 160}};
+    paint.setStrokeWidth(2);
+    for (size_t i = 0; i < 4; ++i) {
+        canvas->drawLine(center[i], center[i] + vectors[i][0], paint);
+        canvas->drawLine(center[i], center[i] + vectors[i][1], paint);
+        SkString str;
+        str.printf("dot = %g", vectors[i][0].dot(vectors[i][1]));
+        canvas->drawString(str, center[i].fX, center[i].fY, paint);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Point_equal_operator.cpp b/docs/examples/Point_equal_operator.cpp
new file mode 100644
index 0000000..66af868
--- /dev/null
+++ b/docs/examples/Point_equal_operator.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=741f793334a48a35dadf4310d7ea52cb
+REG_FIDDLE(Point_equal_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint test[] = { {0, -0.f}, {-1, -2}, {SK_ScalarInfinity, 1}, {SK_ScalarNaN, -1} };
+    for (const SkPoint& pt : test) {
+        SkDebugf("pt: %g, %g  %c= pt\n", pt.fX, pt.fY, pt == pt ? '=' : '!');
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_equals.cpp b/docs/examples/Point_equals.cpp
new file mode 100644
index 0000000..1bb0220
--- /dev/null
+++ b/docs/examples/Point_equals.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4cecb878c8b66beffda051f26c00f817
+REG_FIDDLE(Point_equals, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint test[] = { {0, -0.f}, {-1, -2}, {SK_ScalarInfinity, 1}, {SK_ScalarNaN, -1} };
+    for (const SkPoint& pt : test) {
+        SkDebugf("pt: %g, %g  %c= pt\n", pt.fX, pt.fY, pt.equals(pt.fX, pt.fY) ? '=' : '!');
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_isFinite.cpp b/docs/examples/Point_isFinite.cpp
new file mode 100644
index 0000000..09e62a7
--- /dev/null
+++ b/docs/examples/Point_isFinite.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=937cc166cc0e220f33fb82501141d0b3
+REG_FIDDLE(Point_isFinite, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint test[] = { {0, -0.f}, {-1, -2}, {SK_ScalarInfinity, 1}, {SK_ScalarNaN, -1} };
+    for (const SkPoint& pt : test) {
+        SkDebugf("pt: %g, %g  finite: %s\n", pt.fX, pt.fY, pt.isFinite() ? "true" : "false");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_isZero.cpp b/docs/examples/Point_isZero.cpp
new file mode 100644
index 0000000..4721014
--- /dev/null
+++ b/docs/examples/Point_isZero.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=81b9665110b88ef6bcbc20464aed7da1
+REG_FIDDLE(Point_isZero, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint pt = { 0.f, -0.f};
+    SkDebugf("pt.fX=%c%g pt.fY=%c%g\n", std::signbit(pt.fX) ? '-' : '+', fabsf(pt.fX),
+                                        std::signbit(pt.fY) ? '-' : '+', fabsf(pt.fY));
+    SkDebugf("pt.isZero() == %s\n", pt.isZero() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_iset.cpp b/docs/examples/Point_iset.cpp
new file mode 100644
index 0000000..00769968
--- /dev/null
+++ b/docs/examples/Point_iset.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0d9e8ed734981b5b113f22c7bfde5357
+REG_FIDDLE(Point_iset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint pt1, pt2 = { SK_MinS16, SK_MaxS16 };
+    pt1.iset(SK_MinS16, SK_MaxS16);
+    SkDebugf("pt1 %c= pt2\n", pt1 == pt2 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_iset_2.cpp b/docs/examples/Point_iset_2.cpp
new file mode 100644
index 0000000..acb1d00
--- /dev/null
+++ b/docs/examples/Point_iset_2.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=12b7164a769e232bb772f19c59600ee7
+REG_FIDDLE(Point_iset_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIPoint iPt = { SK_MinS32, SK_MaxS32 };
+    SkPoint fPt;
+    fPt.iset(iPt);
+    SkDebugf("iPt: %d, %d\n", iPt.fX, iPt.fY);
+    SkDebugf("fPt: %g, %g\n", fPt.fX, fPt.fY);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_length_2.cpp b/docs/examples/Point_length_2.cpp
new file mode 100644
index 0000000..8812004
--- /dev/null
+++ b/docs/examples/Point_length_2.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8363ab179447ee4b827679e20d3d81eb
+REG_FIDDLE(Point_length_2, 256, 192, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    const SkPoint points[] = { { 90, 30 }, { 120, 150 }, { 150, 30 }, { 210, 90 } };
+    const SkPoint origin = {30, 140};
+    for (auto point : points) {
+        canvas->drawLine(origin, point, paint);
+        SkAutoCanvasRestore acr(canvas, true);
+        SkScalar angle = SkScalarATan2((point.fY - origin.fY), point.fX - origin.fX);
+        canvas->rotate(angle * 180 / SK_ScalarPI, origin.fX, origin.fY);
+        SkString length("length = ");
+        length.appendScalar(point.length());
+        canvas->drawString(length, origin.fX + 25, origin.fY - 4, paint);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Point_minus_operator.cpp b/docs/examples/Point_minus_operator.cpp
new file mode 100644
index 0000000..dd58a7b
--- /dev/null
+++ b/docs/examples/Point_minus_operator.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9baf247cfcd8272c0ddf6ce93f676b37
+REG_FIDDLE(Point_minus_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint test[] = { {0.f, -0.f}, {-1, -2},
+                       { SK_ScalarInfinity, SK_ScalarNegativeInfinity },
+                       { SK_ScalarNaN, -SK_ScalarNaN } };
+    for (const SkPoint& pt : test) {
+        SkPoint negPt = -pt;
+        SkDebugf("pt: %g, %g  negate: %g, %g\n", pt.fX, pt.fY, negPt.fX, negPt.fY);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_multiply_operator.cpp b/docs/examples/Point_multiply_operator.cpp
new file mode 100644
index 0000000..e7c8bb3
--- /dev/null
+++ b/docs/examples/Point_multiply_operator.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=35b3bc675779de043706ae4817ee950c
+REG_FIDDLE(Point_multiply_operator, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 },
+                         { 6, 4 }, { 7, 5 }, { 5, 7 },
+                         { 4, 6 }, { 3, 7 }, { 1, 5 },
+                         { 2, 4 }, { 1, 3 }, { 3, 1 } };
+    canvas->scale(15, 10);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
+    for (auto& point : points) {
+        point = point * 1.5f;
+    }
+    paint.setColor(SK_ColorRED);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_multiplyby_operator.cpp b/docs/examples/Point_multiplyby_operator.cpp
new file mode 100644
index 0000000..06866ee
--- /dev/null
+++ b/docs/examples/Point_multiplyby_operator.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3ce3db36235d80dbac4d39504cf756da
+REG_FIDDLE(Point_multiplyby_operator, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 },
+                         { 6, 4 }, { 7, 5 }, { 5, 7 },
+                         { 4, 6 }, { 3, 7 }, { 1, 5 },
+                         { 2, 4 }, { 1, 3 }, { 3, 1 } };
+    canvas->scale(15, 10);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
+    for (auto& point : points) {
+        point *= 2;
+    }
+    paint.setColor(SK_ColorRED);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_negate.cpp b/docs/examples/Point_negate.cpp
new file mode 100644
index 0000000..0d6ed75
--- /dev/null
+++ b/docs/examples/Point_negate.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=312c0c8065ab5d0adfda80cccf2d11e6
+REG_FIDDLE(Point_negate, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint test[] = { {0.f, -0.f}, {-1, -2},
+                       { SK_ScalarInfinity, SK_ScalarNegativeInfinity },
+                       { SK_ScalarNaN, -SK_ScalarNaN } };
+    for (const SkPoint& pt : test) {
+        SkPoint negPt = pt;
+        negPt.negate();
+        SkDebugf("pt: %g, %g  negate: %g, %g\n", pt.fX, pt.fY, negPt.fX, negPt.fY);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_normalize_2.cpp b/docs/examples/Point_normalize_2.cpp
new file mode 100644
index 0000000..dafcd88
--- /dev/null
+++ b/docs/examples/Point_normalize_2.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d84fce292d86c7d9ef37ae2d179c03c7
+REG_FIDDLE(Point_normalize_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    const SkPoint lines[][2] = { {{  30, 110 }, { 190,  30 }},
+                                 {{ 120, 140 }, {  30, 220 }}};
+    for (auto line : lines) {
+        canvas->drawLine(line[0], line[1], paint);
+        SkVector vector = line[1] - line[0];
+        if (vector.normalize()) {
+            SkVector rotate90 = { -vector.fY, vector.fX };
+            rotate90 *= 10.f;
+            canvas->drawLine(line[0] - rotate90, line[0] + rotate90, paint);
+            canvas->drawLine(line[1] - rotate90, line[1] + rotate90, paint);
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_notequal_operator.cpp b/docs/examples/Point_notequal_operator.cpp
new file mode 100644
index 0000000..0704fa6
--- /dev/null
+++ b/docs/examples/Point_notequal_operator.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8fe8572685eaa617f25a5a6767a874dc
+REG_FIDDLE(Point_notequal_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint test[] = { {0, -0.f}, {-1, -2}, {SK_ScalarInfinity, 1}, {SK_ScalarNaN, -1} };
+    for (const SkPoint& pt : test) {
+        SkDebugf("pt: %g, %g  %c= pt\n", pt.fX, pt.fY, pt != pt ? '!' : '=');
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_offset_3.cpp b/docs/examples/Point_offset_3.cpp
new file mode 100644
index 0000000..0a9c4e6
--- /dev/null
+++ b/docs/examples/Point_offset_3.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=02750ceaa874f956e6e6544ef6b858ee
+REG_FIDDLE(Point_offset_3, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 },
+                         { 6, 4 }, { 7, 5 }, { 5, 7 },
+                         { 4, 6 }, { 3, 7 }, { 1, 5 },
+                         { 2, 4 }, { 1, 3 }, { 3, 1 } };
+    canvas->scale(30, 15);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
+    points[1].offset(1, 1);
+    paint.setColor(SK_ColorRED);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_scale.cpp b/docs/examples/Point_scale.cpp
new file mode 100644
index 0000000..8e7bbc9
--- /dev/null
+++ b/docs/examples/Point_scale.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=972e4e230806281adb928e068bcd8551
+REG_FIDDLE(Point_scale, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkPoint point = {40, -15}, scaled;
+    SkPoint origin = {30, 110};
+    for (auto scale : {1, 2, 3, 5}) {
+        paint.setStrokeWidth(scale * 5);
+        paint.setARGB(0x7f, 0x9f, 0xbf, 0x33 * scale);
+        point.scale(scale, &scaled);
+        canvas->drawLine(origin, origin + scaled, paint);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_scale_2.cpp b/docs/examples/Point_scale_2.cpp
new file mode 100644
index 0000000..d857d0c
--- /dev/null
+++ b/docs/examples/Point_scale_2.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1060a4f27d8ef29519e6ac006ce90f2b
+REG_FIDDLE(Point_scale_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkPoint point = {40, -15};
+    SkPoint origin = {30, 110};
+    for (auto scale : {1, 2, 3, 5}) {
+        paint.setStrokeWidth(scale * 5);
+        paint.setARGB(0x7f, 0x9f, 0xbf, 0x33 * scale);
+        point.scale(scale);
+        canvas->drawLine(origin, origin + point, paint);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_set.cpp b/docs/examples/Point_set.cpp
new file mode 100644
index 0000000..d2724a7
--- /dev/null
+++ b/docs/examples/Point_set.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d08d1e7dafcad4342d1619fdbb2f5781
+REG_FIDDLE(Point_set, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint pt1, pt2 = { SK_ScalarPI, SK_ScalarSqrt2 };
+    pt1.set(SK_ScalarPI, SK_ScalarSqrt2);
+    SkDebugf("pt1 %c= pt2\n", pt1 == pt2 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_setAbs.cpp b/docs/examples/Point_setAbs.cpp
new file mode 100644
index 0000000..893f919
--- /dev/null
+++ b/docs/examples/Point_setAbs.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7f70860e820b67a347cff03c00488426
+REG_FIDDLE(Point_setAbs, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint test[] = { {0.f, -0.f}, {-1, -2},
+                       { SK_ScalarInfinity, SK_ScalarNegativeInfinity },
+                       { SK_ScalarNaN, -SK_ScalarNaN } };
+    for (const SkPoint& pt : test) {
+        SkPoint absPt;
+        absPt.setAbs(pt);
+        SkDebugf("pt: %g, %g  abs: %g, %g\n", pt.fX, pt.fY, absPt.fX, absPt.fY);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_setLength.cpp b/docs/examples/Point_setLength.cpp
new file mode 100644
index 0000000..2fb4517
--- /dev/null
+++ b/docs/examples/Point_setLength.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=cbe7db206ece825aa3b9b7c3256aeaf0
+REG_FIDDLE(Point_setLength, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    const SkPoint points[] = { { 60, -110 }, { 90, 10 }, { 120, -110 }, { 180, -50 } };
+    const SkPoint origin = {0, 0};
+    canvas->translate(30, 140);
+    for (auto point : points) {
+        paint.setStrokeWidth(1);
+        paint.setColor(SK_ColorBLACK);
+        canvas->drawLine(origin, point, paint);
+        SkVector normal = point;
+        normal.setLength(100);
+        paint.setStrokeWidth(10);
+        paint.setColor(0x3f45bf12);
+        canvas->drawLine(origin, normal, paint);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_setLength_2.cpp b/docs/examples/Point_setLength_2.cpp
new file mode 100644
index 0000000..d17bb18
--- /dev/null
+++ b/docs/examples/Point_setLength_2.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3cc0662b6fbbee1fe3442a0acfece22c
+REG_FIDDLE(Point_setLength_2, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    const SkPoint points[] = { { 60, -110 }, { 90, 10 }, { 120, -110 }, { 180, -50 } };
+    const SkPoint origin = {0, 0};
+    canvas->translate(30, 140);
+    for (auto point : points) {
+        paint.setStrokeWidth(1);
+        paint.setColor(SK_ColorBLACK);
+        canvas->drawLine(origin, point, paint);
+        SkVector normal;
+        normal.setLength(point.fX, point.fY, 100);
+        paint.setStrokeWidth(10);
+        paint.setColor(0x3fbf4512);
+        canvas->drawLine(origin, normal, paint);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_setNormalize.cpp b/docs/examples/Point_setNormalize.cpp
new file mode 100644
index 0000000..abb42fd
--- /dev/null
+++ b/docs/examples/Point_setNormalize.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3e4f147d143a388802484bf0d26534c2
+REG_FIDDLE(Point_setNormalize, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    const SkPoint points[] = { { 60, -110 }, { 90, 10 }, { 120, -110 }, { 180, -50 } };
+    const SkPoint origin = {0, 0};
+    canvas->translate(30, 140);
+    for (auto point : points) {
+        paint.setStrokeWidth(1);
+        paint.setColor(SK_ColorBLACK);
+        canvas->drawLine(origin, point, paint);
+        SkVector normal;
+        normal.setNormalize(point.fX, point.fY);
+        normal *= 100;
+        paint.setStrokeWidth(10);
+        paint.setColor(0x3f4512bf);
+        canvas->drawLine(origin, normal, paint);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_subtract_operator.cpp b/docs/examples/Point_subtract_operator.cpp
new file mode 100644
index 0000000..1685e6a
--- /dev/null
+++ b/docs/examples/Point_subtract_operator.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b6c4943ecd0b2dccf9d220b8944009e0
+REG_FIDDLE(Point_subtract_operator, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 },
+                         { 6, 4 }, { 7, 5 }, { 5, 7 },
+                         { 4, 6 }, { 3, 7 }, { 1, 5 },
+                         { 2, 4 }, { 1, 3 }, { 3, 1 } };
+    canvas->scale(30, 15);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
+    points[1] += points[0] - points[2];
+    points[2] -= points[3] - points[5];
+    paint.setColor(SK_ColorRED);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_subtractfrom_operator.cpp b/docs/examples/Point_subtractfrom_operator.cpp
new file mode 100644
index 0000000..22d40dd
--- /dev/null
+++ b/docs/examples/Point_subtractfrom_operator.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=86c0399704d8dff4091bf87b8d87d40b
+REG_FIDDLE(Point_subtractfrom_operator, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkPoint points[] = { { 3, 1 }, { 4, 2 }, { 5, 1 }, { 7, 3 },
+                         { 6, 4 }, { 7, 5 }, { 5, 7 },
+                         { 4, 6 }, { 3, 7 }, { 1, 5 },
+                         { 2, 4 }, { 1, 3 }, { 3, 1 } };
+    canvas->scale(30, 15);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
+    points[1] -= {1, 1};
+    points[2] -= {-1, -1};
+    paint.setColor(SK_ColorRED);
+    canvas->drawPoints(SkCanvas::kPolygon_PointMode, SK_ARRAY_COUNT(points), points, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_x.cpp b/docs/examples/Point_x.cpp
new file mode 100644
index 0000000..1ae5251
--- /dev/null
+++ b/docs/examples/Point_x.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9f3fe446b800ae1d940785d438634941
+REG_FIDDLE(Point_x, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint pt1 = {45, 66};
+    SkDebugf("pt1.fX %c= pt1.x()\n", pt1.fX == pt1.x() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Point_y.cpp b/docs/examples/Point_y.cpp
new file mode 100644
index 0000000..60675f3
--- /dev/null
+++ b/docs/examples/Point_y.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4c962850c2dbea4d2325df469400680e
+REG_FIDDLE(Point_y, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint pt1 = {45, 66};
+    SkDebugf("pt1.fY %c= pt1.y()\n", pt1.fY == pt1.y() ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/PreMultiplyARGB.cpp b/docs/examples/PreMultiplyARGB.cpp
new file mode 100644
index 0000000..8462943
--- /dev/null
+++ b/docs/examples/PreMultiplyARGB.cpp
@@ -0,0 +1,19 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=756345484fd48ca0ea7b6cec350f73b8
+REG_FIDDLE(PreMultiplyARGB, 300, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPMColor premultiplied = SkPreMultiplyARGB(160, 128, 160, 192);
+    canvas->drawString("Unpremultiplied:", 20, 20, SkPaint());
+    canvas->drawString("alpha=160 red=128 green=160 blue=192", 20, 40, SkPaint());
+    canvas->drawString("Premultiplied:", 20, 80, SkPaint());
+    std::string str = "alpha=" + std::to_string(SkColorGetA(premultiplied));
+    str += " red=" + std::to_string(SkColorGetR(premultiplied));
+    str += " green=" + std::to_string(SkColorGetG(premultiplied));
+    str += " blue=" + std::to_string(SkColorGetB(premultiplied));
+    canvas->drawString(str.c_str(), 20, 100, SkPaint());
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/PreMultiplyColor.cpp b/docs/examples/PreMultiplyColor.cpp
new file mode 100644
index 0000000..000ff6f
--- /dev/null
+++ b/docs/examples/PreMultiplyColor.cpp
@@ -0,0 +1,24 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0bcc0f86a2aefc899f3500503dce6968
+REG_FIDDLE(PreMultiplyColor, 300, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkColor unpremultiplied = SkColorSetARGB(160, 128, 160, 192);
+    SkPMColor premultiplied = SkPreMultiplyColor(unpremultiplied);
+    canvas->drawString("Unpremultiplied:", 20, 20, SkPaint());
+    std::string str = "alpha=" + std::to_string(SkColorGetA(unpremultiplied));
+    str += " red=" + std::to_string(SkColorGetR(unpremultiplied));
+    str += " green=" + std::to_string(SkColorGetG(unpremultiplied));
+    str += " blue=" + std::to_string(SkColorGetB(unpremultiplied));
+    canvas->drawString(str.c_str(), 20, 40, SkPaint());
+    canvas->drawString("Premultiplied:", 20, 80, SkPaint());
+    str = "alpha=" + std::to_string(SkColorGetA(premultiplied));
+    str += " red=" + std::to_string(SkColorGetR(premultiplied));
+    str += " green=" + std::to_string(SkColorGetG(premultiplied));
+    str += " blue=" + std::to_string(SkColorGetB(premultiplied));
+    canvas->drawString(str.c_str(), 20, 100, SkPaint());
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Quad_a.cpp b/docs/examples/Quad_a.cpp
new file mode 100644
index 0000000..2aef63d
--- /dev/null
+++ b/docs/examples/Quad_a.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=78ad51fa1cd33eb84a6f99061e56e067
+REG_FIDDLE(Quad_a, 256, 110, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkPoint quadPts[] = {{20, 90}, {120, 10}, {220, 90}};
+    canvas->drawLine(quadPts[0], quadPts[1], paint);
+    canvas->drawLine(quadPts[1], quadPts[2], paint);
+    SkPath path;
+    path.moveTo(quadPts[0]);
+    path.quadTo(quadPts[1], quadPts[2]);
+    paint.setStrokeWidth(3);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Quad_b.cpp b/docs/examples/Quad_b.cpp
new file mode 100644
index 0000000..8c7e317
--- /dev/null
+++ b/docs/examples/Quad_b.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4082f66a42df11bb20462b232b156bb6
+REG_FIDDLE(Quad_b, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkPoint quadPts[] = {{20, 150}, {120, 10}, {220, 150}};
+    SkColor colors[] = { 0xff88ff00, 0xff0088bb, 0xff6600cc, 0xffbb3377 };
+    for (unsigned i = 0; i < SK_ARRAY_COUNT(colors); ++i) {
+        paint.setColor(0x7fffffff & colors[i]);
+        paint.setStrokeWidth(1);
+        canvas->drawLine(quadPts[0], quadPts[1], paint);
+        canvas->drawLine(quadPts[1], quadPts[2], paint);
+        SkPath path;
+        path.moveTo(quadPts[0]);
+        path.quadTo(quadPts[1], quadPts[2]);
+        paint.setStrokeWidth(3);
+        paint.setColor(colors[i]);
+        canvas->drawPath(path, paint);
+        quadPts[1].fY += 30;
+   }
+}
+}  // END FIDDLE
diff --git a/docs/examples/RGBA4f_FromColor.cpp b/docs/examples/RGBA4f_FromColor.cpp
new file mode 100644
index 0000000..412c19e
--- /dev/null
+++ b/docs/examples/RGBA4f_FromColor.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=33b029064e8d1928e42a587c953d0e4e
+REG_FIDDLE(RGBA4f_FromColor, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    uint8_t red = 77, green = 101, blue = 153, alpha = 43;
+    SkColor argb = SkColorSetARGB(alpha, red, green, blue);
+    SkColor4f color4f = SkColor4f::FromColor(argb);
+    SkDebugf("red=%g green=%g blue=%g alpha=%g\n", color4f.fR, color4f.fG, color4f.fB, color4f.fA);
+    SkColor fromColor4f = color4f.toSkColor();
+    SkDebugf("red=%d green=%d blue=%d alpha=%d\n", SkColorGetR(fromColor4f),
+             SkColorGetG(fromColor4f), SkColorGetB(fromColor4f), SkColorGetA(fromColor4f));
+}
+}  // END FIDDLE
diff --git a/docs/examples/RGBA4f_equal1_operator.cpp b/docs/examples/RGBA4f_equal1_operator.cpp
new file mode 100644
index 0000000..46d4d13
--- /dev/null
+++ b/docs/examples/RGBA4f_equal1_operator.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e5b34bcb7f80f2ed890cdacaa059db0d
+REG_FIDDLE(RGBA4f_equal1_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkColor4f colorRed = { 1, 0, 0, 1 };
+    SkColor4f colorNamedRed = SkColor4f::FromColor(SK_ColorRED);
+    SkDebugf("colorRed %c= colorNamedRed", colorRed == colorNamedRed ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/RGBA4f_notequal1_operator.cpp b/docs/examples/RGBA4f_notequal1_operator.cpp
new file mode 100644
index 0000000..311ddec
--- /dev/null
+++ b/docs/examples/RGBA4f_notequal1_operator.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=82f1a9b4c2b27aa547061786d1f33dab
+REG_FIDDLE(RGBA4f_notequal1_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkColor4f colorGray = { .5, .5, .5, 1 };
+    SkColor4f colorNamedGray = SkColor4f::FromColor(SK_ColorGRAY);
+    SkDebugf("colorGray %c= colorNamedGray ", colorGray != colorNamedGray ? '!' : '=');
+}
+}  // END FIDDLE
diff --git a/docs/examples/RGBA4f_toSkColor.cpp b/docs/examples/RGBA4f_toSkColor.cpp
new file mode 100644
index 0000000..b78b323
--- /dev/null
+++ b/docs/examples/RGBA4f_toSkColor.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=edc5fd18d961f7607d2bcbf7f7d427e5
+REG_FIDDLE(RGBA4f_toSkColor, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    float red = 0.07f, green = 0.13f, blue = 0.32f, alpha = 0.17f;
+    SkColor4f color4f = { red, green, blue, alpha };
+    SkColor argb = color4f.toSkColor();
+    SkDebugf("red=%d green=%d blue=%d alpha=%d\n", SkColorGetR(argb),
+             SkColorGetG(argb), SkColorGetB(argb), SkColorGetA(argb));
+    SkColor4f fromSkColor = SkColor4f::FromColor(argb);
+    SkDebugf("red=%g green=%g blue=%g alpha=%g\n", fromSkColor.fR, fromSkColor.fG,
+                                                   fromSkColor.fB, fromSkColor.fA);
+}
+}  // END FIDDLE
diff --git a/docs/examples/RGBA4f_vec.cpp b/docs/examples/RGBA4f_vec.cpp
new file mode 100644
index 0000000..28d66fa
--- /dev/null
+++ b/docs/examples/RGBA4f_vec.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=229057023515224358a36acf15508cf6
+REG_FIDDLE(RGBA4f_vec, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkColor4f color = SkColor4f::FromColor(0x884488CC);
+    SkDebugf("red=%g green=%g blue=%g alpha=%g\n", color.fR, color.fG, color.fB, color.fA);
+    const float* array = color.vec();
+    SkDebugf("[0]=%g [1]=%g [2]=%g [3]=%g\n", array[0], array[1], array[2], array[3]);
+}
+}  // END FIDDLE
diff --git a/docs/examples/RGBA4f_vec_2.cpp b/docs/examples/RGBA4f_vec_2.cpp
new file mode 100644
index 0000000..366e262
--- /dev/null
+++ b/docs/examples/RGBA4f_vec_2.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7420bf0a7cae5c6577c4c4a4613e7e7e
+REG_FIDDLE(RGBA4f_vec_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkColor4f color = SkColor4f::FromColor(0x884488CC);
+    SkDebugf("red=%g green=%g blue=%g alpha=%g\n", color.fR, color.fG, color.fB, color.fA);
+    float* array = color.vec();
+    array[3] = 1;
+    SkDebugf("[0]=%g [1]=%g [2]=%g [3]=%g\n", array[0], array[1], array[2], array[3]);
+}
+}  // END FIDDLE
diff --git a/docs/examples/RGBToHSV.cpp b/docs/examples/RGBToHSV.cpp
new file mode 100644
index 0000000..f081c0d
--- /dev/null
+++ b/docs/examples/RGBToHSV.cpp
@@ -0,0 +1,21 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4fb2da4a3d9b14ca4ac24eefb0f5126a
+REG_FIDDLE(RGBToHSV, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->drawBitmap(source, 0, 0);
+    SkPaint bgPaint;
+    bgPaint.setColor(0xafffffff);
+    canvas->drawRect({20, 30, 110, 90}, bgPaint);
+    SkScalar hsv[3];
+    SkColor c = source.getColor(226, 128);
+    SkRGBToHSV(SkColorGetR(c), SkColorGetG(c), SkColorGetB(c), hsv);
+    canvas->drawString(("h: " + std::to_string(hsv[0]).substr(0, 6)).c_str(), 27, 45, SkPaint());
+    canvas->drawString(("s: " + std::to_string(hsv[1]).substr(0, 6)).c_str(), 27, 65, SkPaint());
+    canvas->drawString(("v: " + std::to_string(hsv[2]).substr(0, 6)).c_str(), 27, 85, SkPaint());
+    canvas->drawLine(110, 90, 226, 128, SkPaint());
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/RRect_Corner.cpp b/docs/examples/RRect_Corner.cpp
new file mode 100644
index 0000000..91805e8
--- /dev/null
+++ b/docs/examples/RRect_Corner.cpp
@@ -0,0 +1,24 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9205393f30b156e1507e88aa27f1dd91
+REG_FIDDLE(RRect_Corner, 256, 70, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkRRect rrect;
+    SkVector corners[] = {{25, 17}, {17, 19}, {19, 15}, {15, 15}};
+    rrect.setRectRadii({30, 10, 100, 60}, corners);
+    canvas->drawRRect(rrect, paint);
+    paint.setColor(SK_ColorWHITE);
+    const SkRect r = rrect.getBounds();
+    canvas->drawLine(r.fLeft, r.fTop + rrect.radii(SkRRect::kUpperLeft_Corner).fY,
+                     r.fRight, r.fTop + rrect.radii(SkRRect::kUpperRight_Corner).fY, paint);
+    canvas->drawLine(r.fLeft, r.fBottom - rrect.radii(SkRRect::kLowerLeft_Corner).fY,
+                     r.fRight, r.fBottom - rrect.radii(SkRRect::kLowerRight_Corner).fY, paint);
+    canvas->drawLine(r.fLeft + rrect.radii(SkRRect::kUpperLeft_Corner).fX, r.fTop,
+                     r.fLeft + rrect.radii(SkRRect::kLowerLeft_Corner).fX, r.fBottom, paint);
+    canvas->drawLine(r.fRight - rrect.radii(SkRRect::kUpperRight_Corner).fX, r.fTop,
+                     r.fRight - rrect.radii(SkRRect::kLowerRight_Corner).fX, r.fBottom, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_MakeEmpty.cpp b/docs/examples/RRect_MakeEmpty.cpp
new file mode 100644
index 0000000..479349f
--- /dev/null
+++ b/docs/examples/RRect_MakeEmpty.cpp
@@ -0,0 +1,21 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c6c6be3b3c137226adbb5b5af9203d27
+REG_FIDDLE(RRect_MakeEmpty, 256, 90, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRRect rrect = SkRRect::MakeEmpty();
+    SkRRect rrect2(rrect);
+    rrect2.inset(-20, -20);
+    SkPaint p;
+    p.setStyle(SkPaint::kStroke_Style);
+    p.setStrokeWidth(10);
+    std::string str("Type ");
+    str += SkRRect::kEmpty_Type == rrect2.type() ? "=" : "!";
+    str += "= SkRRect::kEmpty_Type";
+    canvas->drawString(str.c_str(), 20, 80, SkPaint());
+    canvas->drawRRect(rrect2, p);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/RRect_MakeOval.cpp b/docs/examples/RRect_MakeOval.cpp
new file mode 100644
index 0000000..039e8f7
--- /dev/null
+++ b/docs/examples/RRect_MakeOval.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0b99ee38fd154f769f6031242e02fa7a
+REG_FIDDLE(RRect_MakeOval, 256, 70, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkRRect rrect = SkRRect::MakeOval({30, 10, 100, 60});
+    canvas->drawRRect(rrect, paint);
+    rrect.setRect(rrect.getBounds());
+    paint.setColor(SK_ColorBLUE);
+    paint.setBlendMode(SkBlendMode::kDifference);
+    canvas->drawRRect(rrect, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_MakeRect.cpp b/docs/examples/RRect_MakeRect.cpp
new file mode 100644
index 0000000..0fbc081
--- /dev/null
+++ b/docs/examples/RRect_MakeRect.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5295b07fe4d2cdcd077979a9e19854d9
+REG_FIDDLE(RRect_MakeRect, 256, 70, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkRRect rrect = SkRRect::MakeRect({30, 10, 100, 60});
+    canvas->drawRRect(rrect, paint);
+    rrect.setOval(rrect.getBounds());
+    paint.setColor(SK_ColorBLUE);
+    canvas->drawRRect(rrect, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_MakeRectXY.cpp b/docs/examples/RRect_MakeRectXY.cpp
new file mode 100644
index 0000000..22e6fad
--- /dev/null
+++ b/docs/examples/RRect_MakeRectXY.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2b24a1247637cbc94f8b3c77d37ed3e2
+REG_FIDDLE(RRect_MakeRectXY, 256, 70, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkRRect rrect = SkRRect::MakeRectXY({30, 10, 100, 60}, 20, 20);
+    canvas->drawRRect(rrect, paint);
+    rrect.setRect(rrect.getBounds());
+    paint.setColor(SK_ColorBLUE);
+    paint.setBlendMode(SkBlendMode::kModulate);
+    canvas->drawRRect(rrect, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_Type.cpp b/docs/examples/RRect_Type.cpp
new file mode 100644
index 0000000..10c6611
--- /dev/null
+++ b/docs/examples/RRect_Type.cpp
@@ -0,0 +1,27 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a4233634c75b72fc7a2815ddb69bd669
+REG_FIDDLE(RRect_Type, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    struct Radii { SkVector data[4]; };
+    auto drawRRectType = [=](const SkRect& rect, const Radii& radii) {
+        SkRRect rrect;
+        rrect.setRectRadii(rect, radii.data);
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        const char* typeStr[] = { "empty", "rect", "oval", "simple", "nine patch", "complex" };
+        canvas->drawString(typeStr[(int) rrect.type()], rect.centerX(), rect.bottom() + 20, paint);
+        paint.setStyle(SkPaint::kStroke_Style);
+        canvas->drawRRect(rrect, paint);
+    };
+    drawRRectType({ 45,  30,  45,  30}, {{{ 5,  5}, { 5,  5}, { 5,  5}, { 5,  5}}});
+    drawRRectType({ 90,  10, 140,  30}, {{{ 0,  0}, { 0,  0}, { 0,  0}, { 0,  0}}});
+    drawRRectType({160,  10, 210,  30}, {{{25, 10}, {25, 10}, {25, 10}, {25, 10}}});
+    drawRRectType({ 20,  80,  70, 100}, {{{ 5,  5}, { 5,  5}, { 5,  5}, { 5,  5}}});
+    drawRRectType({ 90,  80, 140, 100}, {{{ 5,  5}, {10,  5}, {10,  5}, { 5,  5}}});
+    drawRRectType({160,  80, 210, 100}, {{{ 5,  5}, {10,  5}, { 5,  5}, { 5,  5}}});
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/RRect_contains.cpp b/docs/examples/RRect_contains.cpp
new file mode 100644
index 0000000..20f9be3
--- /dev/null
+++ b/docs/examples/RRect_contains.cpp
@@ -0,0 +1,24 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0bb057140e4119234bdd2e8dd2f0fa19
+REG_FIDDLE(RRect_contains, 256, 110, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect test = {10, 10, 110, 80};
+    SkRRect rrect = SkRRect::MakeRect(test);
+    SkRRect oval = SkRRect::MakeOval(test);
+    test.inset(10, 10);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    canvas->drawString(rrect.contains(test) ? "contains" : "does not contain", 55, 100, paint);
+    canvas->drawString(oval.contains(test) ? "contains" : "does not contain", 185, 100, paint);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawRRect(rrect, paint);
+    canvas->drawRect(test, paint);
+    canvas->translate(120, 0);
+    canvas->drawRRect(oval, paint);
+    canvas->drawRect(test, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/RRect_copy_const_SkRRect.cpp b/docs/examples/RRect_copy_const_SkRRect.cpp
new file mode 100644
index 0000000..9d85a1c
--- /dev/null
+++ b/docs/examples/RRect_copy_const_SkRRect.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ad8f5d49edfcee60eddfe2a955b6c5f5
+REG_FIDDLE(RRect_copy_const_SkRRect, 256, 60, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRRect rrect = SkRRect::MakeRect({10, 10, 100, 50});
+    SkRRect rrect2(rrect);
+    rrect2.inset(20, 20);
+    SkPaint p;
+    p.setStyle(SkPaint::kStroke_Style);
+    p.setStrokeWidth(10);
+    canvas->drawRRect(rrect, p);
+    canvas->drawRRect(rrect2, p);
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_copy_operator.cpp b/docs/examples/RRect_copy_operator.cpp
new file mode 100644
index 0000000..96d882a
--- /dev/null
+++ b/docs/examples/RRect_copy_operator.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=52926c98c1cca00606d3ea99f23fea3d
+REG_FIDDLE(RRect_copy_operator, 256, 110, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRRect rrect = SkRRect::MakeRect({40, 40, 100, 70});
+    SkRRect rrect2 = rrect;
+    rrect2.inset(-20, -20);
+    SkPaint p;
+    p.setStyle(SkPaint::kStroke_Style);
+    p.setStrokeWidth(10);
+    canvas->drawRRect(rrect, p);
+    canvas->drawRRect(rrect2, p);
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_dump.cpp b/docs/examples/RRect_dump.cpp
new file mode 100644
index 0000000..29eb737
--- /dev/null
+++ b/docs/examples/RRect_dump.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=265b8d23288dc8026ff788e809360af7
+REG_FIDDLE(RRect_dump, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRRect rrect = SkRRect::MakeRect({6.f / 7, 2.f / 3, 6.f / 7, 2.f / 3});
+    for (bool dumpAsHex : { false, true } ) {
+        rrect.dump(dumpAsHex);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_dumpHex.cpp b/docs/examples/RRect_dumpHex.cpp
new file mode 100644
index 0000000..1044d26
--- /dev/null
+++ b/docs/examples/RRect_dumpHex.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c73f5e2644d949b859f05bd367883454
+REG_FIDDLE(RRect_dumpHex, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRRect rrect = SkRRect::MakeRect({6.f / 7, 2.f / 3, 6.f / 7, 2.f / 3});
+    rrect.dumpHex();
+    SkRect bounds = SkRect::MakeLTRB(SkBits2Float(0x3f5b6db7), /* 0.857143 */
+                     SkBits2Float(0x3f2aaaab), /* 0.666667 */
+                     SkBits2Float(0x3f5b6db7), /* 0.857143 */
+                     SkBits2Float(0x3f2aaaab)  /* 0.666667 */);
+    const SkPoint corners[] = {
+        { SkBits2Float(0x00000000), SkBits2Float(0x00000000) }, /* 0.000000 0.000000 */
+        { SkBits2Float(0x00000000), SkBits2Float(0x00000000) }, /* 0.000000 0.000000 */
+        { SkBits2Float(0x00000000), SkBits2Float(0x00000000) }, /* 0.000000 0.000000 */
+        { SkBits2Float(0x00000000), SkBits2Float(0x00000000) }, /* 0.000000 0.000000 */
+    };
+    SkRRect copy;
+    copy.setRectRadii(bounds, corners);
+    SkDebugf("rrect is " "%s" "equal to copy\n", rrect == copy ? "" : "not ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_dump_2.cpp b/docs/examples/RRect_dump_2.cpp
new file mode 100644
index 0000000..366f87a
--- /dev/null
+++ b/docs/examples/RRect_dump_2.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f850423c7c0c4f803d479ecd92221059
+REG_FIDDLE(RRect_dump_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRRect rrect = SkRRect::MakeRect({6.f / 7, 2.f / 3, 6.f / 7, 2.f / 3});
+    rrect.dump();
+    SkRect bounds = SkRect::MakeLTRB(0.857143f, 0.666667f, 0.857143f, 0.666667f);
+    const SkPoint corners[] = {
+        { 0, 0 },
+        { 0, 0 },
+        { 0, 0 },
+        { 0, 0 },
+    };
+    SkRRect copy;
+    copy.setRectRadii(bounds, corners);
+    SkDebugf("rrect is " "%s" "equal to copy\n", rrect == copy ? "" : "not ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_empty_constructor.cpp b/docs/examples/RRect_empty_constructor.cpp
new file mode 100644
index 0000000..b42ba63
--- /dev/null
+++ b/docs/examples/RRect_empty_constructor.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=471e7aad0feaf9ec3a21757a317a64f5
+REG_FIDDLE(RRect_empty_constructor, 256, 60, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRRect rrect;
+    SkPaint p;
+    p.setStyle(SkPaint::kStroke_Style);
+    p.setStrokeWidth(10);
+    canvas->drawRRect(rrect, p);
+    rrect.setRect({10, 10, 100, 50});
+    canvas->drawRRect(rrect, p);
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_equal_operator.cpp b/docs/examples/RRect_equal_operator.cpp
new file mode 100644
index 0000000..e967053
--- /dev/null
+++ b/docs/examples/RRect_equal_operator.cpp
@@ -0,0 +1,22 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=df181af37f1d2b06f0f45af73df7b47d
+REG_FIDDLE(RRect_equal_operator, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRRect rrect1 = SkRRect::MakeRectXY({10, 20, 60, 220}, 50, 200);
+    SkRRect rrect2 = SkRRect::MakeRectXY(rrect1.rect(), 25, 100);
+    SkRRect rrect3 = SkRRect::MakeOval(rrect1.rect());
+    canvas->drawRRect(rrect1, SkPaint());
+    std::string str = "rrect1 " + std::string(rrect1 == rrect2 ? "=" : "!") + "= rrect2";
+    canvas->drawString(str.c_str(), 10, 240, SkPaint());
+    canvas->translate(70, 0);
+    canvas->drawRRect(rrect2, SkPaint());
+    canvas->translate(70, 0);
+    canvas->drawRRect(rrect3, SkPaint());
+    str = "rrect2 " + std::string(rrect2 == rrect3 ? "=" : "!") + "= rrect3";
+    canvas->drawString(str.c_str(), -20, 240, SkPaint());
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/RRect_getBounds.cpp b/docs/examples/RRect_getBounds.cpp
new file mode 100644
index 0000000..40c5cab
--- /dev/null
+++ b/docs/examples/RRect_getBounds.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4577e2dcb086b241bb43d8b89ee0b0dd
+REG_FIDDLE(RRect_getBounds, 256, 120, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkRRect rrect = SkRRect::MakeRectXY({20, 20, 220, 100}, 15, 15);
+    canvas->drawRRect(rrect, paint);
+    paint.setColor(SK_ColorWHITE);
+    rrect = SkRRect::MakeOval(rrect.getBounds());
+    canvas->drawRRect(rrect, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_getSimpleRadii.cpp b/docs/examples/RRect_getSimpleRadii.cpp
new file mode 100644
index 0000000..2236d2c
--- /dev/null
+++ b/docs/examples/RRect_getSimpleRadii.cpp
@@ -0,0 +1,25 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=81345f7619a072bb2b0cf59810fe86d0
+REG_FIDDLE(RRect_getSimpleRadii, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    auto drawDetails = [=](const SkRRect& rrect) {
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setTextSize(12);
+        canvas->drawRRect(rrect, paint);
+        SkVector corner = rrect.getSimpleRadii();
+        std::string label = "corner: " + std::to_string(corner.fX).substr(0, 3) + ", " +
+                        std::to_string(corner.fY).substr(0, 3);
+        canvas->drawString(label.c_str(), 64, 90, paint);
+        canvas->translate(128, 0);
+    };
+    SkRRect rrect = SkRRect::MakeRect({30, 10, 100, 60});
+    drawDetails(rrect);
+    rrect.setRectXY(rrect.getBounds(), 5, 8);
+    drawDetails(rrect);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/RRect_getType.cpp b/docs/examples/RRect_getType.cpp
new file mode 100644
index 0000000..20403b9
--- /dev/null
+++ b/docs/examples/RRect_getType.cpp
@@ -0,0 +1,21 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ace8f4aebf90527d43e4b7291375c9ad
+REG_FIDDLE(RRect_getType, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRRect rrect = SkRRect::MakeRect({10, 10, 100, 50});
+    SkRRect rrect2(rrect);
+    rrect2.inset(20, 20);
+    SkPaint p;
+    p.setStyle(SkPaint::kStroke_Style);
+    p.setStrokeWidth(10);
+    std::string str("Type ");
+    str += SkRRect::kRect_Type == rrect2.getType() ? "=" : "!";
+    str += "= SkRRect::kRect_Type";
+    canvas->drawString(str.c_str(), 20, 80, SkPaint());
+    canvas->drawRRect(rrect2, p);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/RRect_height.cpp b/docs/examples/RRect_height.cpp
new file mode 100644
index 0000000..8673a10
--- /dev/null
+++ b/docs/examples/RRect_height.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5a3eb1755164a7becec33cec6e6eca31
+REG_FIDDLE(RRect_height, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRRect unsorted = SkRRect::MakeRect({ 15, 25, 10, 20 });
+    SkDebugf("unsorted height: %g\n", unsorted.height());
+    SkRRect large = SkRRect::MakeRect({ 1, -FLT_MAX, 2, FLT_MAX });
+    SkDebugf("large height: %.0f\n", large.height());
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_inset.cpp b/docs/examples/RRect_inset.cpp
new file mode 100644
index 0000000..53a125c
--- /dev/null
+++ b/docs/examples/RRect_inset.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f02f0110d5605dac6d14dcb8d1d8cb6e
+REG_FIDDLE(RRect_inset, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkRRect rrect = SkRRect::MakeRectXY({100, 20, 140, 220}, 50, 100);
+    for (int index = 0; index < 25; ++index) {
+       canvas->drawRRect(rrect, paint);
+       rrect.inset(-3, 3, &rrect);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_inset_2.cpp b/docs/examples/RRect_inset_2.cpp
new file mode 100644
index 0000000..0b497b7
--- /dev/null
+++ b/docs/examples/RRect_inset_2.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=da61054322550a2d5ac15114da23bd23
+REG_FIDDLE(RRect_inset_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkRRect rrect = SkRRect::MakeRectXY({10, 20, 180, 220}, 50, 100);
+    for (int index = 0; index < 25; ++index) {
+       canvas->drawRRect(rrect, paint);
+       rrect.inset(3, 3);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_isComplex.cpp b/docs/examples/RRect_isComplex.cpp
new file mode 100644
index 0000000..2a4e477
--- /dev/null
+++ b/docs/examples/RRect_isComplex.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b62c183dc435d1fc091111fb2f3c3f8e
+REG_FIDDLE(RRect_isComplex, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(16);
+    SkVector radii[] = {{25, 30}, {40, 30}, {40, 30}, {20, 30}};
+    SkRRect rrect;
+    rrect.setRectRadii({30, 10, 100, 60}, radii);
+    canvas->drawRRect(rrect, paint);
+    canvas->drawString(rrect.isComplex() ? "complex" : "not complex", 64, 90, paint);
+    radii[0].fX = 20;
+    rrect.setRectRadii(rrect.getBounds(), radii);
+    canvas->translate(128, 0);
+    canvas->drawRRect(rrect, paint);
+    canvas->drawString(rrect.isComplex() ? "complex" : "not complex", 64, 90, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/RRect_isEmpty.cpp b/docs/examples/RRect_isEmpty.cpp
new file mode 100644
index 0000000..489c684
--- /dev/null
+++ b/docs/examples/RRect_isEmpty.cpp
@@ -0,0 +1,20 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=099d79ecfbdfb0a19c10deb7201859c3
+REG_FIDDLE(RRect_isEmpty, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(16);
+    SkRRect rrect = SkRRect::MakeRectXY({30, 10, 100, 60}, 10, 5);
+    canvas->drawRRect(rrect, paint);
+    canvas->drawString(rrect.isEmpty() ? "empty" : "not empty", 64, 90, paint);
+    rrect.inset(40, 0);
+    canvas->translate(128, 0);
+    canvas->drawRRect(rrect, paint);
+    canvas->drawString(rrect.isEmpty() ? "empty" : "not empty", 64, 90, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/RRect_isNinePatch.cpp b/docs/examples/RRect_isNinePatch.cpp
new file mode 100644
index 0000000..dae70a9
--- /dev/null
+++ b/docs/examples/RRect_isNinePatch.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=429f6dfd4cf6287df3c3c77fa7681c99
+REG_FIDDLE(RRect_isNinePatch, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(16);
+    SkVector radii[] = {{20, 30}, {40, 30}, {40, 30}, {20, 30}};
+    SkRRect rrect;
+    rrect.setRectRadii({30, 10, 100, 60}, radii);
+    canvas->drawRRect(rrect, paint);
+    canvas->drawString(rrect.isNinePatch() ? "9 patch" : "not 9 patch", 64, 90, paint);
+    radii[0].fX = 35;
+    rrect.setRectRadii(rrect.getBounds(), radii);
+    canvas->translate(128, 0);
+    canvas->drawRRect(rrect, paint);
+    canvas->drawString(rrect.isNinePatch() ? "9 patch" : "not 9 patch", 64, 90, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/RRect_isOval.cpp b/docs/examples/RRect_isOval.cpp
new file mode 100644
index 0000000..b377cd6
--- /dev/null
+++ b/docs/examples/RRect_isOval.cpp
@@ -0,0 +1,20 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4dfdb28d8343958425f2c1323fe8170d
+REG_FIDDLE(RRect_isOval, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(16);
+    SkRRect rrect = SkRRect::MakeRectXY({30, 10, 100, 60}, 40, 30);
+    canvas->drawRRect(rrect, paint);
+    canvas->drawString(rrect.isOval() ? "oval" : "not oval", 64, 90, paint);
+    rrect.setRectXY(rrect.getBounds(), 35, 25);
+    canvas->translate(128, 0);
+    canvas->drawRRect(rrect, paint);
+    canvas->drawString(rrect.isOval() ? "oval" : "not oval", 64, 90, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/RRect_isRect.cpp b/docs/examples/RRect_isRect.cpp
new file mode 100644
index 0000000..8d54f95
--- /dev/null
+++ b/docs/examples/RRect_isRect.cpp
@@ -0,0 +1,21 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bc931c9a6eb8ffe7ea8d3fb47e07a475
+REG_FIDDLE(RRect_isRect, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(16);
+    SkRRect rrect = SkRRect::MakeRect({30, 10, 100, 60});
+    canvas->drawRRect(rrect, paint);
+    canvas->drawString(rrect.isRect() ? "rect" : "not rect", 64, 90, paint);
+    SkVector radii[] = {{10, 10}, {0, 0}, {0, 0}, {0, 0}};
+    rrect.setRectRadii(rrect.getBounds(), radii);
+    canvas->translate(128, 0);
+    canvas->drawRRect(rrect, paint);
+    canvas->drawString(rrect.isRect() ? "rect" : "not rect", 64, 90, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/RRect_isSimple.cpp b/docs/examples/RRect_isSimple.cpp
new file mode 100644
index 0000000..da8443c
--- /dev/null
+++ b/docs/examples/RRect_isSimple.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f6959ea422a7c6e98ddfad216a52c707
+REG_FIDDLE(RRect_isSimple, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(16);
+    SkVector radii[] = {{40, 30}, {40, 30}, {40, 30}, {40, 30}};
+    SkRRect rrect;
+    rrect.setRectRadii({30, 10, 100, 60}, radii);
+    canvas->drawRRect(rrect, paint);
+    canvas->drawString(rrect.isSimple() ? "simple" : "not simple", 64, 90, paint);
+    radii[0].fX = 35;
+    rrect.setRectRadii(rrect.getBounds(), radii);
+    canvas->translate(128, 0);
+    canvas->drawRRect(rrect, paint);
+    canvas->drawString(rrect.isSimple() ? "simple" : "not simple", 64, 90, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/RRect_isValid.cpp b/docs/examples/RRect_isValid.cpp
new file mode 100644
index 0000000..c8d3833
--- /dev/null
+++ b/docs/examples/RRect_isValid.cpp
@@ -0,0 +1,21 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8cc1f21c98c0416f7724ad218f557a00
+REG_FIDDLE(RRect_isValid, 256, 110, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRRect rrect = SkRRect::MakeRect({10, 10, 110, 80});
+    SkRRect corrupt = rrect;
+    *((float*) &corrupt) = 120;
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    canvas->drawString(rrect.isValid() ? "is valid" : "is corrupted", 55, 100, paint);
+    canvas->drawString(corrupt.isValid() ? "is valid" : "is corrupted", 185, 100, paint);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawRRect(rrect, paint);
+    canvas->translate(120, 0);
+    canvas->drawRRect(corrupt, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/RRect_makeOffset.cpp b/docs/examples/RRect_makeOffset.cpp
new file mode 100644
index 0000000..0fb62d4
--- /dev/null
+++ b/docs/examples/RRect_makeOffset.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c433aa41eaf5e419e3349fb970a08151
+REG_FIDDLE(RRect_makeOffset, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkRRect rrect = SkRRect::MakeRectXY({100, 20, 140, 220}, 50, 100);
+    for (int index = 0; index < 25; ++index) {
+       canvas->drawRRect(rrect, paint);
+       rrect = rrect.makeOffset(-3, 3);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_notequal_operator.cpp b/docs/examples/RRect_notequal_operator.cpp
new file mode 100644
index 0000000..8af064d
--- /dev/null
+++ b/docs/examples/RRect_notequal_operator.cpp
@@ -0,0 +1,22 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=505e47b3e6474ebdecdc04c3c2af2c34
+REG_FIDDLE(RRect_notequal_operator, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRRect rrect1 = SkRRect::MakeRectXY({10, 20, 60, 220}, 50, 100);
+    SkRRect rrect2 = SkRRect::MakeRectXY(rrect1.rect(), 50, 50);
+    SkRRect rrect3 = SkRRect::MakeOval(rrect1.rect());
+    canvas->drawRRect(rrect1, SkPaint());
+    std::string str = "rrect1 " + std::string(rrect1 == rrect2 ? "=" : "!") + "= rrect2";
+    canvas->drawString(str.c_str(), 10, 240, SkPaint());
+    canvas->translate(70, 0);
+    canvas->drawRRect(rrect2, SkPaint());
+    canvas->translate(70, 0);
+    canvas->drawRRect(rrect3, SkPaint());
+    str = "rrect2 " + std::string(rrect2 == rrect3 ? "=" : "!") + "= rrect3";
+    canvas->drawString(str.c_str(), -20, 240, SkPaint());
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/RRect_offset.cpp b/docs/examples/RRect_offset.cpp
new file mode 100644
index 0000000..fb27e38
--- /dev/null
+++ b/docs/examples/RRect_offset.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a45cdd46ef2fe0df62d84d41713e82e2
+REG_FIDDLE(RRect_offset, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkRRect rrect = SkRRect::MakeRectXY({100, 20, 140, 220}, 50, 100);
+    for (int index = 0; index < 25; ++index) {
+       canvas->drawRRect(rrect, paint);
+       rrect.offset(3, 3);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_outset.cpp b/docs/examples/RRect_outset.cpp
new file mode 100644
index 0000000..34b8437
--- /dev/null
+++ b/docs/examples/RRect_outset.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4d69b6d9c7726c47c42827d79fc7899c
+REG_FIDDLE(RRect_outset, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkRRect rrect = SkRRect::MakeRectXY({100, 20, 140, 220}, 50, 100);
+    for (int index = 0; index < 25; ++index) {
+       canvas->drawRRect(rrect, paint);
+       rrect.outset(-3, 3, &rrect);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_outset_2.cpp b/docs/examples/RRect_outset_2.cpp
new file mode 100644
index 0000000..cb8593b
--- /dev/null
+++ b/docs/examples/RRect_outset_2.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4391cced86653dcd0f84439a5c0bb3f2
+REG_FIDDLE(RRect_outset_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    SkRRect rrect = SkRRect::MakeRectXY({100, 20, 140, 220}, 50, 100);
+    for (int index = 0; index < 25; ++index) {
+       canvas->drawRRect(rrect, paint);
+       rrect.outset(3, 3);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_radii.cpp b/docs/examples/RRect_radii.cpp
new file mode 100644
index 0000000..8bd8006
--- /dev/null
+++ b/docs/examples/RRect_radii.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8d5c88478528584913867ada423e0d59
+REG_FIDDLE(RRect_radii, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    for (SkScalar radiusX : { SK_ScalarNaN, SK_ScalarInfinity, 100.f, 50.f, 25.f} ) {
+        SkRRect rrect1 = SkRRect::MakeRectXY({10, 20, 60, 220}, radiusX, 200);
+        SkDebugf("left corner: (%g) %g\n", radiusX, rrect1.radii(SkRRect::kUpperLeft_Corner).fX);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_readFromMemory.cpp b/docs/examples/RRect_readFromMemory.cpp
new file mode 100644
index 0000000..b8038df
--- /dev/null
+++ b/docs/examples/RRect_readFromMemory.cpp
@@ -0,0 +1,25 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=50969745cf2b23544362f4cff5592b75
+REG_FIDDLE(RRect_readFromMemory, 256, 110, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkVector radii[] = {{5, 5},  {10, 10}, {15, 15}, {5, 5}};
+    SkRRect rrect;
+    rrect.setRectRadii({10, 10, 110, 80}, radii);
+    char storage[SkRRect::kSizeInMemory];
+    rrect.writeToMemory(storage);
+    SkRRect copy;
+    copy.readFromMemory(storage, sizeof(storage));
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    canvas->drawString("rrect", 55, 100, paint);
+    canvas->drawString("copy", 185, 100, paint);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawRRect(rrect, paint);
+    canvas->translate(120, 0);
+    canvas->drawRRect(copy, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/RRect_rect.cpp b/docs/examples/RRect_rect.cpp
new file mode 100644
index 0000000..0156465
--- /dev/null
+++ b/docs/examples/RRect_rect.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6831adf4c536047f4709c686feb10c48
+REG_FIDDLE(RRect_rect, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    for (SkScalar left : { SK_ScalarNaN, SK_ScalarInfinity, 100.f, 50.f, 25.f} ) {
+        SkRRect rrect1 = SkRRect::MakeRectXY({left, 20, 60, 220}, 50, 200);
+        SkDebugf("left bounds: (%g) %g\n", left, rrect1.rect().fLeft);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_setEmpty.cpp b/docs/examples/RRect_setEmpty.cpp
new file mode 100644
index 0000000..f013d57
--- /dev/null
+++ b/docs/examples/RRect_setEmpty.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=44e9a9c2c5ef1af2a616086ff46a9037
+REG_FIDDLE(RRect_setEmpty, 256, 80, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkRRect rrect = SkRRect::MakeRect({30, 10, 100, 60});
+    canvas->drawRRect(rrect, paint);
+    rrect.setEmpty();
+    paint.setColor(SK_ColorBLUE);
+    canvas->drawRRect(rrect, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_setNinePatch.cpp b/docs/examples/RRect_setNinePatch.cpp
new file mode 100644
index 0000000..acd850a
--- /dev/null
+++ b/docs/examples/RRect_setNinePatch.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c4620df2eaba447b581688d3100053b1
+REG_FIDDLE(RRect_setNinePatch, 256, 70, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    SkRRect rrect;
+    rrect.setNinePatch({30, 10, 100, 60}, 10, 20, 20, 10);
+    canvas->drawRRect(rrect, paint);
+    paint.setColor(SK_ColorWHITE);
+    const SkRect r = rrect.getBounds();
+    canvas->drawLine(r.fLeft, r.fTop + rrect.radii(SkRRect::kUpperLeft_Corner).fY,
+                     r.fRight, r.fTop + rrect.radii(SkRRect::kUpperRight_Corner).fY, paint);
+    canvas->drawLine(r.fLeft, r.fBottom - rrect.radii(SkRRect::kLowerLeft_Corner).fY,
+                     r.fRight, r.fBottom - rrect.radii(SkRRect::kLowerRight_Corner).fY, paint);
+    canvas->drawLine(r.fLeft + rrect.radii(SkRRect::kUpperLeft_Corner).fX, r.fTop,
+                     r.fLeft + rrect.radii(SkRRect::kLowerLeft_Corner).fX, r.fBottom, paint);
+    canvas->drawLine(r.fRight - rrect.radii(SkRRect::kUpperRight_Corner).fX, r.fTop,
+                     r.fRight - rrect.radii(SkRRect::kLowerRight_Corner).fX, r.fBottom, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_setOval.cpp b/docs/examples/RRect_setOval.cpp
new file mode 100644
index 0000000..9072602
--- /dev/null
+++ b/docs/examples/RRect_setOval.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=cf418af29cbab6243ac16aacd1217ffe
+REG_FIDDLE(RRect_setOval, 256, 70, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkRRect rrect = SkRRect::MakeRectXY({30, 10, 100, 60}, 20, 20);
+    canvas->drawRRect(rrect, paint);
+    rrect.setOval(rrect.getBounds());
+    paint.setColor(SK_ColorWHITE);
+    paint.setBlendMode(SkBlendMode::kExclusion);
+    canvas->drawRRect(rrect, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_setRect.cpp b/docs/examples/RRect_setRect.cpp
new file mode 100644
index 0000000..94e57fc
--- /dev/null
+++ b/docs/examples/RRect_setRect.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3afc3ac9bebd1d7387822cc608571e82
+REG_FIDDLE(RRect_setRect, 256, 90, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkRRect rrect = SkRRect::MakeRect({30, 10, 100, 60});
+    canvas->drawRRect(rrect, paint);
+    rrect.setRect({60, 30, 120, 80});
+    paint.setColor(SK_ColorBLUE);
+    canvas->drawRRect(rrect, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_setRectRadii.cpp b/docs/examples/RRect_setRectRadii.cpp
new file mode 100644
index 0000000..1dab17d
--- /dev/null
+++ b/docs/examples/RRect_setRectRadii.cpp
@@ -0,0 +1,25 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=340d6c51efaa1f7f3d0dcaf8b0e90696
+REG_FIDDLE(RRect_setRectRadii, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStrokeWidth(15);
+    paint.setStrokeCap(SkPaint::kSquare_Cap);
+    paint.setAntiAlias(true);
+    float intervals[] = { 5, 21.75f };
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0));
+    SkPath path;
+    SkRRect rrect;
+    SkVector corners[] = {{15, 17}, {17, 19}, {19, 15}, {15, 15}};
+    rrect.setRectRadii({20, 20, 100, 100}, corners);
+    path.addRRect(rrect, SkPath::kCW_Direction);
+    canvas->drawPath(path, paint);
+    path.rewind();
+    path.addRRect(rrect, SkPath::kCCW_Direction, 1);
+    canvas->translate(120, 0);
+    canvas->drawPath(path, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_setRectXY.cpp b/docs/examples/RRect_setRectXY.cpp
new file mode 100644
index 0000000..ebe1d53
--- /dev/null
+++ b/docs/examples/RRect_setRectXY.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6ac569e40fb68c758319e85428b9ae95
+REG_FIDDLE(RRect_setRectXY, 256, 70, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    SkRRect rrect = SkRRect::MakeRectXY({30, 10, 100, 60}, 20, 20);
+    canvas->drawRRect(rrect, paint);
+    rrect.setRectXY(rrect.getBounds(), 5, 5);
+    paint.setColor(SK_ColorWHITE);
+    paint.setBlendMode(SkBlendMode::kExclusion);
+    canvas->drawRRect(rrect, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_transform.cpp b/docs/examples/RRect_transform.cpp
new file mode 100644
index 0000000..d666de8
--- /dev/null
+++ b/docs/examples/RRect_transform.cpp
@@ -0,0 +1,24 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=68a5d24f22e2d798608fce8a20e47fd0
+REG_FIDDLE(RRect_transform, 256, 110, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkVector radii[] = {{5, 5},  {10, 10}, {15, 15}, {5, 5}};
+    SkRRect rrect;
+    rrect.setRectRadii({10, 10, 110, 80}, radii);
+    SkRRect transformed;
+    SkMatrix matrix = SkMatrix::MakeRectToRect(rrect.rect(), {140, 30, 220, 80},
+                                               SkMatrix::kCenter_ScaleToFit);
+    bool success = rrect.transform(matrix, &transformed);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    canvas->drawString("rrect", 55, 100, paint);
+    canvas->drawString(success ? "transformed" : "transform failed", 185, 100, paint);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawRRect(rrect, paint);
+    canvas->drawRRect(transformed, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/RRect_type_2.cpp b/docs/examples/RRect_type_2.cpp
new file mode 100644
index 0000000..1e913dd
--- /dev/null
+++ b/docs/examples/RRect_type_2.cpp
@@ -0,0 +1,21 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1080805c8449406a4e26d694bc56d2dc
+REG_FIDDLE(RRect_type_2, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRRect rrect = SkRRect::MakeRect({10, 10, 100, 50});
+    SkRRect rrect2(rrect);
+    rrect2.inset(20, 20);
+    SkPaint p;
+    p.setStyle(SkPaint::kStroke_Style);
+    p.setStrokeWidth(10);
+    std::string str("Type ");
+    str += SkRRect::kEmpty_Type == rrect2.type() ? "=" : "!";
+    str += "= SkRRect::kEmpty_Type";
+    canvas->drawString(str.c_str(), 20, 80, SkPaint());
+    canvas->drawRRect(rrect2, p);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/RRect_width.cpp b/docs/examples/RRect_width.cpp
new file mode 100644
index 0000000..032da29
--- /dev/null
+++ b/docs/examples/RRect_width.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c675a480b41dee157f84fa2550a2a53c
+REG_FIDDLE(RRect_width, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRRect unsorted = SkRRect::MakeRect({ 15, 25, 10, 5 });
+    SkDebugf("unsorted width: %g\n", unsorted.width());
+    SkRRect large = SkRRect::MakeRect({ -FLT_MAX, 1, FLT_MAX, 2 });
+    SkDebugf("large width: %.0f\n", large.width());
+}
+}  // END FIDDLE
diff --git a/docs/examples/RRect_writeToMemory.cpp b/docs/examples/RRect_writeToMemory.cpp
new file mode 100644
index 0000000..582affc
--- /dev/null
+++ b/docs/examples/RRect_writeToMemory.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d6f5a3d21727ddc15e10ef4d5103ff91
+REG_FIDDLE(RRect_writeToMemory, 256, 110, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRRect rrect = SkRRect::MakeRect({10, 10, 110, 80});
+    char storage[SkRRect::kSizeInMemory];
+    rrect.writeToMemory(storage);
+    SkRRect copy;
+    copy.readFromMemory(storage, sizeof(storage));
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    canvas->drawString("rrect", 55, 100, paint);
+    canvas->drawString("copy", 185, 100, paint);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawRRect(rrect, paint);
+    canvas->translate(120, 0);
+    canvas->drawRRect(copy, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Rect_Intersects.cpp b/docs/examples/Rect_Intersects.cpp
new file mode 100644
index 0000000..6c4350e
--- /dev/null
+++ b/docs/examples/Rect_Intersects.cpp
@@ -0,0 +1,9 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=795061764b10c9e05efb466c9cb60644
+REG_FIDDLE(Rect_Intersects, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkDebugf("%s intersection", SkRect::Intersects({10, 40, 50, 80}, {30, 60, 70, 90}) ? "" : "no ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_Make.cpp b/docs/examples/Rect_Make.cpp
new file mode 100644
index 0000000..702d8b4
--- /dev/null
+++ b/docs/examples/Rect_Make.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e866f5e4f6ac52e89acadf48e54ac8e0
+REG_FIDDLE(Rect_Make, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect1 = SkRect::MakeSize({2, 35});
+    SkRect rect2 = SkRect::MakeIWH(2, 35);
+    SkDebugf("rect1 %c= rect2\n", rect1 == rect2 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_MakeEmpty.cpp b/docs/examples/Rect_MakeEmpty.cpp
new file mode 100644
index 0000000..e39e1f1
--- /dev/null
+++ b/docs/examples/Rect_MakeEmpty.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2e262d0ac4b8ef51695e0525fc3ecdf6
+REG_FIDDLE(Rect_MakeEmpty, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = SkRect::MakeEmpty();
+    SkDebugf("MakeEmpty isEmpty: %s\n", rect.isEmpty() ? "true" : "false");
+    rect.offset(10, 10);
+    SkDebugf("offset rect isEmpty: %s\n", rect.isEmpty() ? "true" : "false");
+    rect.inset(10, 10);
+    SkDebugf("inset rect isEmpty: %s\n", rect.isEmpty() ? "true" : "false");
+    rect.outset(20, 20);
+    SkDebugf("outset rect isEmpty: %s\n", rect.isEmpty() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_MakeIWH.cpp b/docs/examples/Rect_MakeIWH.cpp
new file mode 100644
index 0000000..cd1b826
--- /dev/null
+++ b/docs/examples/Rect_MakeIWH.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=faa660ac19eaddc3f3eab57a0bddfdcb
+REG_FIDDLE(Rect_MakeIWH, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect i_rect = SkIRect::MakeWH(25, 35);
+    SkRect  f_rect = SkRect::MakeIWH(25, 35);
+    SkDebugf("i_rect width: %d f_rect width:%g\n", i_rect.width(), f_rect.width());
+    i_rect = SkIRect::MakeWH(125000111, 0);
+    f_rect = SkRect::MakeIWH(125000111, 0);
+    SkDebugf("i_rect width: %d f_rect width:%.0f\n", i_rect.width(), f_rect.width());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_MakeLTRB.cpp b/docs/examples/Rect_MakeLTRB.cpp
new file mode 100644
index 0000000..af37ee9
--- /dev/null
+++ b/docs/examples/Rect_MakeLTRB.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=158b8dd9d02d65a5ae5ab7d1595a5b4c
+REG_FIDDLE(Rect_MakeLTRB, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = SkRect::MakeLTRB(5, 35, 15, 25);
+    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+    rect.sort();
+    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_MakeSize.cpp b/docs/examples/Rect_MakeSize.cpp
new file mode 100644
index 0000000..25c1271
--- /dev/null
+++ b/docs/examples/Rect_MakeSize.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ab2c1a55016c8de9172b77fdf69e00a2
+REG_FIDDLE(Rect_MakeSize, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkSize size = {25.5f, 35.5f};
+    SkRect rect = SkRect::MakeSize(size);
+    SkDebugf("rect width: %g  height: %g\n", rect.width(), rect.height());
+    SkISize floor = size.toFloor();
+    rect = SkRect::MakeSize(SkSize::Make(floor));
+    SkDebugf("floor width: %g  height: %g\n", rect.width(), rect.height());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_MakeWH.cpp b/docs/examples/Rect_MakeWH.cpp
new file mode 100644
index 0000000..3c89c1c
--- /dev/null
+++ b/docs/examples/Rect_MakeWH.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8009d30f431e01f8aea4808e9017d9bf
+REG_FIDDLE(Rect_MakeWH, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect1 = SkRect::MakeWH(25, 35);
+    SkRect rect2 = SkRect::MakeIWH(25, 35);
+    SkRect rect3 = SkRect::MakeXYWH(0, 0, 25, 35);
+    SkRect rect4 = SkRect::MakeLTRB(0, 0, 25, 35);
+    SkDebugf("all %s" "equal\n", rect1 == rect2 && rect2 == rect3 && rect3 == rect4 ?
+             "" : "not ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_MakeXYWH.cpp b/docs/examples/Rect_MakeXYWH.cpp
new file mode 100644
index 0000000..3fc0588
--- /dev/null
+++ b/docs/examples/Rect_MakeXYWH.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=38e464dba13be11ac21e210fbf3b5afc
+REG_FIDDLE(Rect_MakeXYWH, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = SkRect::MakeXYWH(5, 35, -15, 25);
+    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+    rect.sort();
+    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_Make_2.cpp b/docs/examples/Rect_Make_2.cpp
new file mode 100644
index 0000000..d1fbda1
--- /dev/null
+++ b/docs/examples/Rect_Make_2.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=dd801faa1e60a0fe9e0657674461e063
+REG_FIDDLE(Rect_Make_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect i_rect1 = {2, 35, 22, 53};
+    SkRect f_rect = SkRect::Make(i_rect1);
+    f_rect.offset(0.49f, 0.49f);
+    SkIRect i_rect2;
+    f_rect.round(&i_rect2);
+    SkDebugf("i_rect1 %c= i_rect2\n", i_rect1 == i_rect2? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_asScalars.cpp b/docs/examples/Rect_asScalars.cpp
new file mode 100644
index 0000000..8ede918
--- /dev/null
+++ b/docs/examples/Rect_asScalars.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e1ea5f949d80276f3637931eae93a07c
+REG_FIDDLE(Rect_asScalars, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = {7, 11, 13, 17};
+ SkDebugf("rect.asScalars() %c= &rect.fLeft\n", rect.asScalars() == &rect.fLeft? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_bottom.cpp b/docs/examples/Rect_bottom.cpp
new file mode 100644
index 0000000..0f562bf
--- /dev/null
+++ b/docs/examples/Rect_bottom.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a98993a66616ae406d8bdc54adfb1411
+REG_FIDDLE(Rect_bottom, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect unsorted = { 15, 25, 10, 5 };
+    SkDebugf("unsorted.fBottom: %g unsorted.bottom(): %g\n", unsorted.fBottom, unsorted.bottom());
+    SkRect sorted = unsorted.makeSorted();
+    SkDebugf("sorted.fBottom: %g sorted.bottom(): %g\n", sorted.fBottom, sorted.bottom());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_centerX.cpp b/docs/examples/Rect_centerX.cpp
new file mode 100644
index 0000000..bd7f9e1
--- /dev/null
+++ b/docs/examples/Rect_centerX.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d8439ba8d23a424fa032fb97147fd2d2
+REG_FIDDLE(Rect_centerX, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect tests[] = {{20, 30, 41, 51}, {-20, -30, -41, -51}};
+    for (auto rect : tests) {
+        SkDebugf("left: %3g right: %3g centerX: %3g\n", rect.left(), rect.right(), rect.centerX());
+        rect.sort();
+        SkDebugf("left: %3g right: %3g centerX: %3g\n", rect.left(), rect.right(), rect.centerX());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_centerY.cpp b/docs/examples/Rect_centerY.cpp
new file mode 100644
index 0000000..18ab4f8
--- /dev/null
+++ b/docs/examples/Rect_centerY.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ebeeafafeb8fe39d5ffc9115b02c2340
+REG_FIDDLE(Rect_centerY, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 2e+38f, 2e+38f, 3e+38f, 3e+38f };
+    SkDebugf("left: %g right: %g centerX: %g ", rect.left(), rect.right(), rect.centerX());
+    SkDebugf("safe mid x: %g\n", rect.left() / 2 + rect.right() / 2);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_contains.cpp b/docs/examples/Rect_contains.cpp
new file mode 100644
index 0000000..2d9348b
--- /dev/null
+++ b/docs/examples/Rect_contains.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=85be528a78945a6dc4f7dccb80a80746
+REG_FIDDLE(Rect_contains, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 30, 50, 40, 60 };
+    SkPoint tests[] = { { 30, 50 }, { 39, 49 }, { 29, 59 } };
+    for (auto contained : tests) {
+        SkDebugf("rect: (%g, %g, %g, %g) %s (%g, %g)\n",
+                 rect.left(), rect.top(), rect.right(), rect.bottom(),
+                 rect.contains(contained.x(), contained.y()) ? "contains" : "does not contain",
+                 contained.x(), contained.y());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_contains_2.cpp b/docs/examples/Rect_contains_2.cpp
new file mode 100644
index 0000000..d61d8b4
--- /dev/null
+++ b/docs/examples/Rect_contains_2.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=92f9e6aa5bb76791139a24cf7d8df99e
+REG_FIDDLE(Rect_contains_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 30, 50, 40, 60 };
+    SkRect tests[] = { { 30, 50, 31, 51}, { 39, 49, 40, 50}, { 29, 59, 30, 60} };
+    for (auto contained : tests) {
+        SkDebugf("rect: (%g, %g, %g, %g) %s (%g, %g, %g, %g)\n",
+                 rect.left(), rect.top(), rect.right(), rect.bottom(),
+                 rect.contains(contained) ? "contains" : "does not contain",
+                 contained.left(), contained.top(), contained.right(), contained.bottom());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_contains_3.cpp b/docs/examples/Rect_contains_3.cpp
new file mode 100644
index 0000000..423b68a
--- /dev/null
+++ b/docs/examples/Rect_contains_3.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=dd58b699551dd44026a2c6386be27d88
+REG_FIDDLE(Rect_contains_3, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 30, 50, 40, 60 };
+    SkIRect tests[] = { { 30, 50, 31, 51}, { 39, 49, 40, 50}, { 29, 59, 30, 60} };
+    for (auto contained : tests) {
+        SkDebugf("rect: (%g, %g, %g, %g) %s (%d, %d, %d, %d)\n",
+                 rect.left(), rect.top(), rect.right(), rect.bottom(),
+                 rect.contains(contained) ? "contains" : "does not contain",
+                 contained.left(), contained.top(), contained.right(), contained.bottom());
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_dump.cpp b/docs/examples/Rect_dump.cpp
new file mode 100644
index 0000000..30d4f04
--- /dev/null
+++ b/docs/examples/Rect_dump.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=cea049ffff702a5923da41fe0ae0763b
+REG_FIDDLE(Rect_dump, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = {20, 30, 40, 50};
+     for (bool dumpAsHex : { false, true } ) {
+         rect.dump(dumpAsHex);
+         SkDebugf("\n");
+     }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_dumpHex.cpp b/docs/examples/Rect_dumpHex.cpp
new file mode 100644
index 0000000..771c32d
--- /dev/null
+++ b/docs/examples/Rect_dumpHex.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=824b5a3fcfd46a7e1c5f9e3c16e6bb39
+REG_FIDDLE(Rect_dumpHex, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = {6.f / 7, 2.f / 3, 26.f / 10, 42.f / 6};
+ rect.dumpHex();
+ SkRect copy = SkRect::MakeLTRB(SkBits2Float(0x3f5b6db7), /* 0.857143 */
+                  SkBits2Float(0x3f2aaaab), /* 0.666667 */
+                  SkBits2Float(0x40266666), /* 2.600000 */
+                  SkBits2Float(0x40e00000)  /* 7.000000 */);
+ SkDebugf("rect is " "%s" "equal to copy\n", rect == copy ? "" : "not ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_dump_2.cpp b/docs/examples/Rect_dump_2.cpp
new file mode 100644
index 0000000..220736e
--- /dev/null
+++ b/docs/examples/Rect_dump_2.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9fb76971b1a104a2a59816e0392267a7
+REG_FIDDLE(Rect_dump_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = {6.f / 7, 2.f / 3, 26.f / 10, 42.f / 6};
+    rect.dump();
+    SkRect copy = SkRect::MakeLTRB(0.857143f, 0.666667f, 2.6f, 7);
+    SkDebugf("rect is " "%s" "equal to copy\n", rect == copy ? "" : "not ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_equal_operator.cpp b/docs/examples/Rect_equal_operator.cpp
new file mode 100644
index 0000000..16b4b86
--- /dev/null
+++ b/docs/examples/Rect_equal_operator.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c6c5b40cad7c3a839fdf576b380391a6
+REG_FIDDLE(Rect_equal_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const SkRect& test) -> void {
+        SkRect negZero = {-0.0f, -0.0f, 2, 2};
+        SkDebugf("{%g, %g, %g, %g} %c= {%g, %g, %g, %g} %s numerically equal\n",
+                 test.fLeft, test.fTop, test.fRight, test.fBottom,
+                 negZero.fLeft, negZero.fTop, negZero.fRight, negZero.fBottom,
+                 test == negZero ? '=' : '!',
+                 test.fLeft == negZero.fLeft && test.fTop == negZero.fTop &&
+                 test.fRight == negZero.fRight && test.fBottom == negZero.fBottom ?
+                 "and are" : "yet are not");
+    };
+    SkRect tests[] = {{0, 0, 2, 2}, {-0, -0, 2, 2}, {0.0f, 0.0f, 2, 2}};
+    SkDebugf("tests are %s" "equal\n", tests[0] == tests[1] && tests[1] == tests[2] ? "" : "not ");
+    for (auto rect : tests) {
+        debugster(rect);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_height.cpp b/docs/examples/Rect_height.cpp
new file mode 100644
index 0000000..d0827ec
--- /dev/null
+++ b/docs/examples/Rect_height.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=39429e45f05240218ecd511443ab3e44
+REG_FIDDLE(Rect_height, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect unsorted = { 15, 25, 10, 20 };
+    SkDebugf("unsorted height: %g\n", unsorted.height());
+    SkRect large = { 1, -2147483647.f, 2, 2147483644.f };
+    SkDebugf("large height: %.0f\n", large.height());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_inset.cpp b/docs/examples/Rect_inset.cpp
new file mode 100644
index 0000000..964d81e
--- /dev/null
+++ b/docs/examples/Rect_inset.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=dae21340941dc6e4d048816dfd9f204c
+REG_FIDDLE(Rect_inset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 10, 14, 50, 73 };
+    rect.inset(5, 13);
+    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_intersect.cpp b/docs/examples/Rect_intersect.cpp
new file mode 100644
index 0000000..e9d7f91
--- /dev/null
+++ b/docs/examples/Rect_intersect.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5d0b12e0ef6f1c181dddded4274230ca
+REG_FIDDLE(Rect_intersect, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect leftRect =  { 10, 40, 50, 80 };
+    SkRect rightRect = { 30, 60, 70, 90 };
+    SkDebugf("%s intersection: ", leftRect.intersect(rightRect) ? "" : "no ");
+    SkDebugf("%g, %g, %g, %g\n", leftRect.left(), leftRect.top(),
+                                 leftRect.right(), leftRect.bottom());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_intersect_2.cpp b/docs/examples/Rect_intersect_2.cpp
new file mode 100644
index 0000000..e79b33a
--- /dev/null
+++ b/docs/examples/Rect_intersect_2.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5002f65a72def2787086a33131933e70
+REG_FIDDLE(Rect_intersect_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect leftRect =  { 10, 40, 50, 80 };
+    SkDebugf("%s intersection: ", leftRect.intersect(30, 60, 70, 90) ? "" : "no ");
+    SkDebugf("%g, %g, %g, %g\n", leftRect.left(), leftRect.top(),
+                                 leftRect.right(), leftRect.bottom());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_intersect_3.cpp b/docs/examples/Rect_intersect_3.cpp
new file mode 100644
index 0000000..76751b0
--- /dev/null
+++ b/docs/examples/Rect_intersect_3.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d610437a65dd3e952719efe605cbd0c7
+REG_FIDDLE(Rect_intersect_3, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect result;
+    bool intersected = result.intersect({ 10, 40, 50, 80 }, { 30, 60, 70, 90 });
+    SkDebugf("%s intersection: %g, %g, %g, %g\n", intersected ? "" : "no ",
+             result.left(), result.top(), result.right(), result.bottom());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_intersects_2.cpp b/docs/examples/Rect_intersects_2.cpp
new file mode 100644
index 0000000..3e629ff
--- /dev/null
+++ b/docs/examples/Rect_intersects_2.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ca37b4231b21eb8296cb19ba9e0c781b
+REG_FIDDLE(Rect_intersects_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 10, 40, 50, 80 };
+    SkDebugf("%s intersection", rect.intersects({30, 60, 70, 90}) ? "" : "no ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_intersects_3.cpp b/docs/examples/Rect_intersects_3.cpp
new file mode 100644
index 0000000..e845bff
--- /dev/null
+++ b/docs/examples/Rect_intersects_3.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=7145dc17ebce4f54e892102f6c98e811
+REG_FIDDLE(Rect_intersects_3, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 10, 40, 50, 80 };
+    SkDebugf("%s intersection", rect.intersects(30, 60, 70, 90) ? "" : "no ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_isEmpty.cpp b/docs/examples/Rect_isEmpty.cpp
new file mode 100644
index 0000000..863911c
--- /dev/null
+++ b/docs/examples/Rect_isEmpty.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1d7b924d6ca2a6aef09684a8a632439c
+REG_FIDDLE(Rect_isEmpty, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect tests[] = {{20, 40, 10, 50}, {20, 40, 20, 50}};
+    for (auto rect : tests) {
+        SkDebugf("rect: {%g, %g, %g, %g} is" "%s empty\n", rect.left(), rect.top(), rect.right(),
+                 rect.bottom(), rect.isEmpty() ? "" : " not");
+        rect.sort();
+        SkDebugf("sorted: {%g, %g, %g, %g} is" "%s empty\n", rect.left(), rect.top(), rect.right(),
+                 rect.bottom(), rect.isEmpty() ? "" : " not");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_isFinite.cpp b/docs/examples/Rect_isFinite.cpp
new file mode 100644
index 0000000..266857d
--- /dev/null
+++ b/docs/examples/Rect_isFinite.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=443fe5f8296d4cdb19cc9862a9cf77a4
+REG_FIDDLE(Rect_isFinite, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect largest = { SK_ScalarMin, SK_ScalarMin, SK_ScalarMax, SK_ScalarMax };
+        SkDebugf("largest is finite: %s\n", largest.isFinite() ? "true" : "false");
+        SkDebugf("large width %g\n", largest.width());
+        SkRect widest = SkRect::MakeWH(largest.width(), largest.height());
+        SkDebugf("widest is finite: %s\n", widest.isFinite() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_isSorted.cpp b/docs/examples/Rect_isSorted.cpp
new file mode 100644
index 0000000..4438f07
--- /dev/null
+++ b/docs/examples/Rect_isSorted.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c7065a83b220a96f903dbbb65906fe7b
+REG_FIDDLE(Rect_isSorted, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect tests[] = {{20, 40, 10, 50}, {20, 40, 20, 50}};
+    for (auto rect : tests) {
+        SkDebugf("rect: {%g, %g, %g, %g} is" "%s sorted\n", rect.left(), rect.top(), rect.right(),
+                 rect.bottom(), rect.isSorted() ? "" : " not");
+        rect.sort();
+        SkDebugf("sorted: {%g, %g, %g, %g} is" "%s sorted\n", rect.left(), rect.top(), rect.right(),
+                 rect.bottom(), rect.isSorted() ? "" : " not");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_iset.cpp b/docs/examples/Rect_iset.cpp
new file mode 100644
index 0000000..932bec1
--- /dev/null
+++ b/docs/examples/Rect_iset.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=18532f1aa90b76364fb8d7ea072f1892
+REG_FIDDLE(Rect_iset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect1 = {3, 4, 1, 2};
+    SkDebugf("rect1: {%g, %g, %g, %g}\n", rect1.fLeft, rect1.fTop, rect1.fRight, rect1.fBottom);
+    SkRect rect2;
+    rect2.iset(3, 4, 1, 2);
+    SkDebugf("rect2: {%g, %g, %g, %g}\n", rect2.fLeft, rect2.fTop, rect2.fRight, rect2.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_isetWH.cpp b/docs/examples/Rect_isetWH.cpp
new file mode 100644
index 0000000..366533a
--- /dev/null
+++ b/docs/examples/Rect_isetWH.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ee6000080fc7123214ea404018cf9176
+REG_FIDDLE(Rect_isetWH, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect1 = {0, 0, 1, 2};
+    SkDebugf("rect1: {%g, %g, %g, %g}\n", rect1.fLeft, rect1.fTop, rect1.fRight, rect1.fBottom);
+    SkRect rect2;
+    rect2.isetWH(1, 2);
+    SkDebugf("rect2: {%g, %g, %g, %g}\n", rect2.fLeft, rect2.fTop, rect2.fRight, rect2.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_join.cpp b/docs/examples/Rect_join.cpp
new file mode 100644
index 0000000..d6750f2
--- /dev/null
+++ b/docs/examples/Rect_join.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=afa9c6b4d05bb669db07fe0b7b97e6aa
+REG_FIDDLE(Rect_join, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 10, 20, 15, 25};
+    rect.join(50, 60, 55, 65);
+    SkDebugf("join: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_joinNonEmptyArg.cpp b/docs/examples/Rect_joinNonEmptyArg.cpp
new file mode 100644
index 0000000..443d1ed
--- /dev/null
+++ b/docs/examples/Rect_joinNonEmptyArg.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=88439de2aa0911262c60c0eb506396cb
+REG_FIDDLE(Rect_joinNonEmptyArg, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 10, 100, 15, 0};
+    SkRect sorted = rect.makeSorted();
+    SkRect toJoin = { 50, 60, 55, 65 };
+    rect.joinNonEmptyArg(toJoin);
+    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+    sorted.joinNonEmptyArg(toJoin);
+    SkDebugf("sorted: %g, %g, %g, %g\n", sorted.fLeft, sorted.fTop, sorted.fRight, sorted.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_joinPossiblyEmptyRect.cpp b/docs/examples/Rect_joinPossiblyEmptyRect.cpp
new file mode 100644
index 0000000..1ca5b13
--- /dev/null
+++ b/docs/examples/Rect_joinPossiblyEmptyRect.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a476548d0001296afd8e58c1eba1b70b
+REG_FIDDLE(Rect_joinPossiblyEmptyRect, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 10, 100, 15, 0};
+    SkRect sorted = rect.makeSorted();
+    SkRect toJoin = { 50, 60, 55, 65 };
+    rect.joinPossiblyEmptyRect(toJoin);
+    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+    sorted.joinPossiblyEmptyRect(toJoin);
+    SkDebugf("sorted: %g, %g, %g, %g\n", sorted.fLeft, sorted.fTop, sorted.fRight, sorted.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_join_2.cpp b/docs/examples/Rect_join_2.cpp
new file mode 100644
index 0000000..8052701
--- /dev/null
+++ b/docs/examples/Rect_join_2.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=26500032494cf93c5fa3423110fe82af
+REG_FIDDLE(Rect_join_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 10, 20, 15, 25};
+    rect.join({50, 60, 55, 65});
+    SkDebugf("join: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_left.cpp b/docs/examples/Rect_left.cpp
new file mode 100644
index 0000000..27a2408
--- /dev/null
+++ b/docs/examples/Rect_left.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=900dc96c3549795a87036d6458c4fde6
+REG_FIDDLE(Rect_left, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect unsorted = { 15, 5, 10, 25 };
+    SkDebugf("unsorted.fLeft: %g unsorted.left(): %g\n", unsorted.fLeft, unsorted.left());
+    SkRect sorted = unsorted.makeSorted();
+    SkDebugf("sorted.fLeft: %g sorted.left(): %g\n", sorted.fLeft, sorted.left());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_makeInset.cpp b/docs/examples/Rect_makeInset.cpp
new file mode 100644
index 0000000..c2a0065
--- /dev/null
+++ b/docs/examples/Rect_makeInset.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b8d32ab2f7ea3d4d5fb5a4ea2156f1c5
+REG_FIDDLE(Rect_makeInset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 10, 50, 20, 60 };
+    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+    rect = rect.makeInset(15, 32);
+    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_makeOffset.cpp b/docs/examples/Rect_makeOffset.cpp
new file mode 100644
index 0000000..6160828
--- /dev/null
+++ b/docs/examples/Rect_makeOffset.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=98841ab0a932f99cccd8e6a34d94ba05
+REG_FIDDLE(Rect_makeOffset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 10, 50, 20, 60 };
+    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+    rect = rect.makeOffset(15, 32);
+    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_makeOutset.cpp b/docs/examples/Rect_makeOutset.cpp
new file mode 100644
index 0000000..41c4d67
--- /dev/null
+++ b/docs/examples/Rect_makeOutset.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=87176fc60914cbca9c6a20998a033c24
+REG_FIDDLE(Rect_makeOutset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 10, 50, 20, 60 };
+    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+    rect = rect.makeOutset(15, 32);
+    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_makeSorted.cpp b/docs/examples/Rect_makeSorted.cpp
new file mode 100644
index 0000000..891c4fa
--- /dev/null
+++ b/docs/examples/Rect_makeSorted.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f59567042b87f6b26f9bfeeb04468032
+REG_FIDDLE(Rect_makeSorted, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 30.5f, 50.5f, 20.5f, 10.5f };
+    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+    SkRect sort = rect.makeSorted();
+    SkDebugf("sorted: %g, %g, %g, %g\n", sort.fLeft, sort.fTop, sort.fRight, sort.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_notequal_operator.cpp b/docs/examples/Rect_notequal_operator.cpp
new file mode 100644
index 0000000..2c0e454
--- /dev/null
+++ b/docs/examples/Rect_notequal_operator.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=286072f8c27ff15be9eb945fa38dc9f7
+REG_FIDDLE(Rect_notequal_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect test = {0, 0, 2, SK_ScalarNaN};
+    SkDebugf("test with NaN is %s" "equal to itself\n", test == test ? "" : "not ");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_offset.cpp b/docs/examples/Rect_offset.cpp
new file mode 100644
index 0000000..06fdcd8
--- /dev/null
+++ b/docs/examples/Rect_offset.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=04eb33f0fd376f2942ca5f1c7f6cbcfc
+REG_FIDDLE(Rect_offset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 10, 14, 50, 73 };
+    rect.offset(5, 13);
+    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_offsetTo.cpp b/docs/examples/Rect_offsetTo.cpp
new file mode 100644
index 0000000..21e1f24
--- /dev/null
+++ b/docs/examples/Rect_offsetTo.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bedb04b7b3e1af3e8039f9cffe66989e
+REG_FIDDLE(Rect_offsetTo, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 10, 14, 50, 73 };
+    rect.offsetTo(15, 27);
+    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_offset_2.cpp b/docs/examples/Rect_offset_2.cpp
new file mode 100644
index 0000000..4f9c783
--- /dev/null
+++ b/docs/examples/Rect_offset_2.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b24cf65561c98c1858a06c39f10fb797
+REG_FIDDLE(Rect_offset_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 10, 14, 50, 73 };
+    rect.offset({5, 13});
+    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_outset.cpp b/docs/examples/Rect_outset.cpp
new file mode 100644
index 0000000..46da1c4
--- /dev/null
+++ b/docs/examples/Rect_outset.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=861f873ba660af8c8bf8b0b83d829cf4
+REG_FIDDLE(Rect_outset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 10, 14, 50, 73 };
+    rect.outset(5, 13);
+    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_right.cpp b/docs/examples/Rect_right.cpp
new file mode 100644
index 0000000..96c37ba
--- /dev/null
+++ b/docs/examples/Rect_right.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ca3de7e5e292b3ad3633b1c39a31d3ab
+REG_FIDDLE(Rect_right, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect unsorted = { 15, 25, 10, 5 };
+    SkDebugf("unsorted.fRight: %g unsorted.right(): %g\n", unsorted.fRight, unsorted.right());
+    SkRect sorted = unsorted.makeSorted();
+    SkDebugf("sorted.fRight: %g sorted.right(): %g\n", sorted.fRight, sorted.right());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_round.cpp b/docs/examples/Rect_round.cpp
new file mode 100644
index 0000000..7d41206
--- /dev/null
+++ b/docs/examples/Rect_round.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8b9e5a9af0a9b878f76919534d88f41e
+REG_FIDDLE(Rect_round, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 30.5f, 50.5f, 40.5f, 60.5f };
+    SkIRect round;
+    rect.round(&round);
+    SkDebugf("round: %d, %d, %d, %d\n", round.fLeft, round.fTop, round.fRight, round.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_roundIn.cpp b/docs/examples/Rect_roundIn.cpp
new file mode 100644
index 0000000..3eca5fa
--- /dev/null
+++ b/docs/examples/Rect_roundIn.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=abb337da8fc1891f016c61258681c64c
+REG_FIDDLE(Rect_roundIn, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 30.5f, 50.5f, 40.5f, 60.5f };
+    SkIRect round;
+    rect.roundIn(&round);
+    SkDebugf("round: %d, %d, %d, %d\n", round.fLeft, round.fTop, round.fRight, round.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_roundOut.cpp b/docs/examples/Rect_roundOut.cpp
new file mode 100644
index 0000000..cb03091
--- /dev/null
+++ b/docs/examples/Rect_roundOut.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0bd13d7e6426ae7a3befa2ab151ac5fc
+REG_FIDDLE(Rect_roundOut, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 30.5f, 50.5f, 40.5f, 60.5f };
+    SkIRect round;
+    rect.roundOut(&round);
+    SkDebugf("round: %d, %d, %d, %d\n", round.fLeft, round.fTop, round.fRight, round.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_roundOut_2.cpp b/docs/examples/Rect_roundOut_2.cpp
new file mode 100644
index 0000000..515e695
--- /dev/null
+++ b/docs/examples/Rect_roundOut_2.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e09a6a12869a8ac21e9c2af98a5bb686
+REG_FIDDLE(Rect_roundOut_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 30.5f, 50.5f, 40.5f, 60.5f };
+    SkRect round;
+    rect.roundOut(&round);
+    SkDebugf("round: %g, %g, %g, %g\n", round.fLeft, round.fTop, round.fRight, round.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_roundOut_3.cpp b/docs/examples/Rect_roundOut_3.cpp
new file mode 100644
index 0000000..3a9aab7
--- /dev/null
+++ b/docs/examples/Rect_roundOut_3.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=05f0f65ae148f192656cd87df90f1d57
+REG_FIDDLE(Rect_roundOut_3, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 30.5f, 50.5f, 40.5f, 60.5f };
+    SkIRect round = rect.roundOut();
+    SkDebugf("round: %d, %d, %d, %d\n", round.fLeft, round.fTop, round.fRight, round.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_round_2.cpp b/docs/examples/Rect_round_2.cpp
new file mode 100644
index 0000000..2c27c9a
--- /dev/null
+++ b/docs/examples/Rect_round_2.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ef7ae1dd522c235b0afe41b55a624f46
+REG_FIDDLE(Rect_round_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 30.5f, 50.5f, 40.5f, 60.5f };
+    SkIRect round = rect.round();
+    SkDebugf("round: %d, %d, %d, %d\n", round.fLeft, round.fTop, round.fRight, round.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_set.cpp b/docs/examples/Rect_set.cpp
new file mode 100644
index 0000000..241fcff
--- /dev/null
+++ b/docs/examples/Rect_set.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a10ad8d97062bc3f40942f47e5108917
+REG_FIDDLE(Rect_set, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect i_rect = {3, 4, 1, 2};
+    SkDebugf("i_rect: {%d, %d, %d, %d}\n", i_rect.fLeft, i_rect.fTop, i_rect.fRight, i_rect.fBottom);
+    SkRect f_rect;
+    f_rect.set(i_rect);
+    SkDebugf("f_rect: {%g, %g, %g, %g}\n", f_rect.fLeft, f_rect.fTop, f_rect.fRight, f_rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_setBounds.cpp b/docs/examples/Rect_setBounds.cpp
new file mode 100644
index 0000000..95bdec4d
--- /dev/null
+++ b/docs/examples/Rect_setBounds.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=cf0da15f48aa54fd1889e7f913601710
+REG_FIDDLE(Rect_setBounds, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint points[] = {{3, 4}, {1, 2}, {5, 6}, {SK_ScalarNaN, 8}};
+    for (int count = 0; count <= (int) SK_ARRAY_COUNT(points); ++count) {
+        SkRect rect;
+        rect.setBounds(points, count);
+        if (count > 0) {
+            SkDebugf("added: %3g, %g ", points[count - 1].fX,  points[count - 1].fY);
+        } else {
+            SkDebugf("%14s", " ");
+        }
+        SkDebugf("count: %d rect: %g, %g, %g, %g\n", count,
+                rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_setBoundsCheck.cpp b/docs/examples/Rect_setBoundsCheck.cpp
new file mode 100644
index 0000000..6c15913
--- /dev/null
+++ b/docs/examples/Rect_setBoundsCheck.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=83d879b92683b15f9daaf0c9e71c5b35
+REG_FIDDLE(Rect_setBoundsCheck, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint points[] = {{3, 4}, {1, 2}, {5, 6}, {SK_ScalarNaN, 8}};
+    for (int count = 0; count <= (int) SK_ARRAY_COUNT(points); ++count) {
+        SkRect rect;
+        bool success = rect.setBoundsCheck(points, count);
+        if (count > 0) {
+            SkDebugf("added: %3g, %g ", points[count - 1].fX,  points[count - 1].fY);
+        } else {
+            SkDebugf("%14s", " ");
+        }
+        SkDebugf("count: %d rect: %g, %g, %g, %g success: %s\n", count,
+                rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, success ? "true" : "false");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_setBoundsNoCheck.cpp b/docs/examples/Rect_setBoundsNoCheck.cpp
new file mode 100644
index 0000000..1a36e80
--- /dev/null
+++ b/docs/examples/Rect_setBoundsNoCheck.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=be10cb1411dbcf7e38e0198e8a9b8b0e
+REG_FIDDLE(Rect_setBoundsNoCheck, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint points[] = {{3, 4}, {1, 2}, {SK_ScalarInfinity, 6}, {SK_ScalarNaN, 8}};
+    for (int count = 0; count <= (int) SK_ARRAY_COUNT(points); ++count) {
+        SkRect rect;
+        rect.setBoundsNoCheck(points, count);
+        if (count > 0) {
+            SkDebugf("added: %3g, %g ", points[count - 1].fX,  points[count - 1].fY);
+        } else {
+            SkDebugf("%14s", " ");
+        }
+        SkDebugf("count: %d rect: %g, %g, %g, %g\n", count,
+                rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_setEmpty.cpp b/docs/examples/Rect_setEmpty.cpp
new file mode 100644
index 0000000..48a6013
--- /dev/null
+++ b/docs/examples/Rect_setEmpty.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2cf67542d45ef5d7a7efb673b651ff54
+REG_FIDDLE(Rect_setEmpty, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = {3, 4, 1, 2};
+    for (int i = 0; i < 2; ++i) {
+    SkDebugf("rect: {%g, %g, %g, %g} is %s" "empty\n", rect.fLeft, rect.fTop,
+             rect.fRight, rect.fBottom, rect.isEmpty() ? "" : "not ");
+    rect.setEmpty();
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_setLTRB.cpp b/docs/examples/Rect_setLTRB.cpp
new file mode 100644
index 0000000..ee9a668
--- /dev/null
+++ b/docs/examples/Rect_setLTRB.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=70692838793454c8e045d6eaf7edcbff
+REG_FIDDLE(Rect_setLTRB, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect1 = {3, 4, 1, 2};
+    SkDebugf("rect1: {%g, %g, %g, %g}\n", rect1.fLeft, rect1.fTop, rect1.fRight, rect1.fBottom);
+    SkRect rect2;
+    rect2.setLTRB(3, 4, 1, 2);
+    SkDebugf("rect2: {%g, %g, %g, %g}\n", rect2.fLeft, rect2.fTop, rect2.fRight, rect2.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_setWH.cpp b/docs/examples/Rect_setWH.cpp
new file mode 100644
index 0000000..9eac42f
--- /dev/null
+++ b/docs/examples/Rect_setWH.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9cb5fee17802fa49341f3707bdf5d235
+REG_FIDDLE(Rect_setWH, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect;
+    rect.setWH(-15, 25);
+    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+    rect.sort();
+    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_setXYWH.cpp b/docs/examples/Rect_setXYWH.cpp
new file mode 100644
index 0000000..c6c7137
--- /dev/null
+++ b/docs/examples/Rect_setXYWH.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=373cce4c61b9da0384b735b838765163
+REG_FIDDLE(Rect_setXYWH, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect;
+    rect.setXYWH(5, 35, -15, 25);
+    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+    rect.sort();
+    SkDebugf("rect: %g, %g, %g, %g  isEmpty: %s\n", rect.left(), rect.top(), rect.right(),
+              rect.bottom(), rect.isEmpty() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_set_2.cpp b/docs/examples/Rect_set_2.cpp
new file mode 100644
index 0000000..80a47c7
--- /dev/null
+++ b/docs/examples/Rect_set_2.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9b29ea460d69b4d47323fd9e3e17721e
+REG_FIDDLE(Rect_set_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect1 = {3, 4, 1, 2};
+    SkDebugf("rect1: {%g, %g, %g, %g}\n", rect1.fLeft, rect1.fTop, rect1.fRight, rect1.fBottom);
+    SkRect rect2;
+    rect2.set(3, 4, 1, 2);
+    SkDebugf("rect2: {%g, %g, %g, %g}\n", rect2.fLeft, rect2.fTop, rect2.fRight, rect2.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_set_3.cpp b/docs/examples/Rect_set_3.cpp
new file mode 100644
index 0000000..24351b9
--- /dev/null
+++ b/docs/examples/Rect_set_3.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=94295fa5197e21256171b99b4023dd48
+REG_FIDDLE(Rect_set_3, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint points[] = {{3, 4}, {1, 2}, {5, 6}, {SK_ScalarNaN, 8}};
+    for (int count = 0; count <= (int) SK_ARRAY_COUNT(points); ++count) {
+        SkRect rect;
+        rect.set(points, count);
+        if (count > 0) {
+            SkDebugf("added: %3g, %g ", points[count - 1].fX,  points[count - 1].fY);
+        } else {
+            SkDebugf("%14s", " ");
+        }
+        SkDebugf("count: %d rect: %g, %g, %g, %g\n", count,
+                rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_set_4.cpp b/docs/examples/Rect_set_4.cpp
new file mode 100644
index 0000000..228df0f
--- /dev/null
+++ b/docs/examples/Rect_set_4.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ee72450381f768f3869153cdbeccdc3e
+REG_FIDDLE(Rect_set_4, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkPoint point1 = {SK_ScalarNaN, 8};
+    SkPoint point2 = {3, 4};
+    SkRect rect;
+    rect.set(point1, point2);
+    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+    rect.set(point2, point1);
+    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_sort.cpp b/docs/examples/Rect_sort.cpp
new file mode 100644
index 0000000..8b19973
--- /dev/null
+++ b/docs/examples/Rect_sort.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e624fe398e3d770b573c09fc74c0c400
+REG_FIDDLE(Rect_sort, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = { 30.5f, 50.5f, 20.5f, 10.5f };
+    SkDebugf("rect: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+    rect.sort();
+    SkDebugf("sorted: %g, %g, %g, %g\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_toQuad.cpp b/docs/examples/Rect_toQuad.cpp
new file mode 100644
index 0000000..bfc98b6
--- /dev/null
+++ b/docs/examples/Rect_toQuad.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=59a6e7d202ac17ab80ec21b233e51f59
+REG_FIDDLE(Rect_toQuad, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect rect = {1, 2, 3, 4};
+    SkPoint corners[4];
+    rect.toQuad(corners);
+    SkDebugf("rect: {%g, %g, %g, %g}\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+    SkDebugf("corners:");
+    for (auto corner : corners) {
+        SkDebugf(" {%g, %g}", corner.fX, corner.fY);
+    }
+    SkDebugf("\n");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_top.cpp b/docs/examples/Rect_top.cpp
new file mode 100644
index 0000000..2ac0be3
--- /dev/null
+++ b/docs/examples/Rect_top.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3cfc24b011aef1ca8ccb57c05711620c
+REG_FIDDLE(Rect_top, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect unsorted = { 15, 25, 10, 5 };
+    SkDebugf("unsorted.fTop: %g unsorted.top(): %g\n", unsorted.fTop, unsorted.top());
+    SkRect sorted = unsorted.makeSorted();
+    SkDebugf("sorted.fTop: %g sorted.top(): %g\n", sorted.fTop, sorted.top());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_width.cpp b/docs/examples/Rect_width.cpp
new file mode 100644
index 0000000..90c2e35
--- /dev/null
+++ b/docs/examples/Rect_width.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=11f8f0efe6291019fee0ac17844f6c1a
+REG_FIDDLE(Rect_width, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect unsorted = { 15, 25, 10, 5 };
+    SkDebugf("unsorted width: %g\n", unsorted.width());
+    SkRect large = { -2147483647.f, 1, 2147483644.f, 2 };
+    SkDebugf("large width: %.0f\n", large.width());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_x.cpp b/docs/examples/Rect_x.cpp
new file mode 100644
index 0000000..d73b058
--- /dev/null
+++ b/docs/examples/Rect_x.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=23c77a35ac54a439a2989f840aa5cb99
+REG_FIDDLE(Rect_x, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect unsorted = { 15, 5, 10, 25 };
+    SkDebugf("unsorted.fLeft: %g unsorted.x(): %g\n", unsorted.fLeft, unsorted.x());
+    SkRect sorted = unsorted.makeSorted();
+    SkDebugf("sorted.fLeft: %g sorted.x(): %g\n", sorted.fLeft, sorted.x());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Rect_y.cpp b/docs/examples/Rect_y.cpp
new file mode 100644
index 0000000..adf2cfb
--- /dev/null
+++ b/docs/examples/Rect_y.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c653d9017983d2a047b1fee6a481d82b
+REG_FIDDLE(Rect_y, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRect unsorted = { 15, 25, 10, 5 };
+    SkDebugf("unsorted.fTop: %g unsorted.y(): %g\n", unsorted.fTop, unsorted.y());
+    SkRect sorted = unsorted.makeSorted();
+    SkDebugf("sorted.fTop: %g sorted.y(): %g\n", sorted.fTop, sorted.y());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_Cliperator_const_SkRegion_const_SkIRect.cpp b/docs/examples/Region_Cliperator_const_SkRegion_const_SkIRect.cpp
new file mode 100644
index 0000000..a29d8bd
--- /dev/null
+++ b/docs/examples/Region_Cliperator_const_SkRegion_const_SkIRect.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3831fb6006a7e0ad5d140c266c22be78
+REG_FIDDLE(Region_Cliperator_const_SkRegion_const_SkIRect, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion region;
+    region.setRect({1, 2, 3, 4});
+    SkRegion::Cliperator clipper(region, {0, 0, 2, 3});
+    auto r = clipper.rect();
+    SkDebugf("rect={%d,%d,%d,%d}\n", r.fLeft, r.fTop, r.fRight, r.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_Cliperator_done.cpp b/docs/examples/Region_Cliperator_done.cpp
new file mode 100644
index 0000000..9a0897e
--- /dev/null
+++ b/docs/examples/Region_Cliperator_done.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6cca7b96836266800d852664a1366453
+REG_FIDDLE(Region_Cliperator_done, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* label, SkRegion& region) -> void {
+        SkRegion::Cliperator clipper(region, {0, 0, 5, 5});
+        SkDebugf("%14s done=%s\n", label, clipper.done() ? "true" : "false");
+    };
+    SkRegion region;
+    debugster("empty region", region);
+    region.setRect({1, 2, 3, 4});
+    debugster("after add rect", region);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_Cliperator_next.cpp b/docs/examples/Region_Cliperator_next.cpp
new file mode 100644
index 0000000..fd744f6
--- /dev/null
+++ b/docs/examples/Region_Cliperator_next.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3bbcc7eec19c808a8167bbcc987199f8
+REG_FIDDLE(Region_Cliperator_next, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion region;
+    SkIRect rects[] = {{1, 2, 3, 4}, {5, 6, 7, 8}};
+    region.setRects(rects, SK_ARRAY_COUNT(rects));
+    SkRegion::Cliperator clipper(region, {0, 3, 8, 7});
+    do {
+        auto r2 = clipper.rect();
+        SkDebugf("rect={%d,%d,%d,%d}\n", r2.fLeft, r2.fTop, r2.fRight, r2.fBottom);
+        clipper.next();
+    } while (!clipper.done());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_Cliperator_rect.cpp b/docs/examples/Region_Cliperator_rect.cpp
new file mode 100644
index 0000000..28cf7e2
--- /dev/null
+++ b/docs/examples/Region_Cliperator_rect.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=05791751f00b4c2426093fa143b43bc7
+REG_FIDDLE(Region_Cliperator_rect, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* label, SkRegion& region) -> void {
+        SkRegion::Cliperator clipper(region, {0, 0, 5, 3});
+        auto r = clipper.rect();
+        SkDebugf("%14s rect={%d,%d,%d,%d}\n", label, r.fLeft, r.fTop, r.fRight, r.fBottom);
+    };
+    SkRegion region;
+    debugster("empty region", region);
+    region.setRect({1, 2, 3, 4});
+    debugster("after set rect", region);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_Iterator_Iterator.cpp b/docs/examples/Region_Iterator_Iterator.cpp
new file mode 100644
index 0000000..94fd808
--- /dev/null
+++ b/docs/examples/Region_Iterator_Iterator.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a2db43ee3cbf6893e9b23927fb44298a
+REG_FIDDLE(Region_Iterator_Iterator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion::Iterator iter;
+    SkRegion region;
+    region.setRect({1, 2, 3, 4});
+    iter.reset(region);
+    auto r = iter.rect();
+    SkDebugf("rect={%d,%d,%d,%d}\n", r.fLeft, r.fTop, r.fRight, r.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_Iterator_copy_const_SkRegion.cpp b/docs/examples/Region_Iterator_copy_const_SkRegion.cpp
new file mode 100644
index 0000000..9574357
--- /dev/null
+++ b/docs/examples/Region_Iterator_copy_const_SkRegion.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e317ceca48a6a7504219af58f35d2c95
+REG_FIDDLE(Region_Iterator_copy_const_SkRegion, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion region;
+    region.setRect({1, 2, 3, 4});
+    SkRegion::Iterator iter(region);
+    auto r = iter.rect();
+    SkDebugf("rect={%d,%d,%d,%d}\n", r.fLeft, r.fTop, r.fRight, r.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_Iterator_done.cpp b/docs/examples/Region_Iterator_done.cpp
new file mode 100644
index 0000000..f7b7448
--- /dev/null
+++ b/docs/examples/Region_Iterator_done.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=814efa7d7f4ae52dfc861a937c1b5c25
+REG_FIDDLE(Region_Iterator_done, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion region;
+    SkRegion::Iterator iter(region);
+    SkDebugf("done=%s\n", iter.done() ? "true" : "false");
+    region.setRect({1, 2, 3, 4});
+    iter.rewind();
+    SkDebugf("done=%s\n", iter.done() ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_Iterator_next.cpp b/docs/examples/Region_Iterator_next.cpp
new file mode 100644
index 0000000..cbb1772
--- /dev/null
+++ b/docs/examples/Region_Iterator_next.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=771236c2eadfc2fcd02a3e61a0875d39
+REG_FIDDLE(Region_Iterator_next, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion region;
+    SkIRect rects[] = {{1, 2, 3, 4}, {5, 6, 7, 8}};
+    region.setRects(rects, SK_ARRAY_COUNT(rects));
+    SkRegion::Iterator iter(region);
+    do {
+        auto r2 = iter.rect();
+        SkDebugf("rect={%d,%d,%d,%d}\n", r2.fLeft, r2.fTop, r2.fRight, r2.fBottom);
+        iter.next();
+    } while (!iter.done());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_Iterator_rect.cpp b/docs/examples/Region_Iterator_rect.cpp
new file mode 100644
index 0000000..5c86b67
--- /dev/null
+++ b/docs/examples/Region_Iterator_rect.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0e7c58ab5d3bcfb36b1f8464cf6c7d89
+REG_FIDDLE(Region_Iterator_rect, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion region;
+    SkRegion::Iterator iter(region);
+    auto r1 = iter.rect();
+    SkDebugf("rect={%d,%d,%d,%d}\n", r1.fLeft, r1.fTop, r1.fRight, r1.fBottom);
+    region.setRect({1, 2, 3, 4});
+    iter.rewind();
+    auto r2 = iter.rect();
+    SkDebugf("rect={%d,%d,%d,%d}\n", r2.fLeft, r2.fTop, r2.fRight, r2.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_Iterator_reset.cpp b/docs/examples/Region_Iterator_reset.cpp
new file mode 100644
index 0000000..506f58b
--- /dev/null
+++ b/docs/examples/Region_Iterator_reset.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d153f87bd518a4ab947b7e407ea1db79
+REG_FIDDLE(Region_Iterator_reset, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* label, SkRegion::Iterator& iter) -> void {
+        SkDebugf("%14s: done=%s\n", label, iter.done() ? "true" : "false");
+    };
+    SkRegion region;
+    SkRegion::Iterator iter(region);
+    debugster("empty region", iter);
+    region.setRect({1, 2, 3, 4});
+    debugster("after set rect", iter);
+    iter.reset(region);
+    debugster("after reset", iter);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_Iterator_rewind.cpp b/docs/examples/Region_Iterator_rewind.cpp
new file mode 100644
index 0000000..3c7e9ca
--- /dev/null
+++ b/docs/examples/Region_Iterator_rewind.cpp
@@ -0,0 +1,25 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=32d51e959d6cc720a74ec4822511e2cd
+REG_FIDDLE(Region_Iterator_rewind, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* label, SkRegion::Iterator& iter, bool addRewind) -> void {
+        if (addRewind) {
+            bool success = iter.rewind();
+            SkDebugf("%14s rewind success=%s\n", label, success ? "true" : "false");
+        }
+        auto r = iter.rect();
+        SkDebugf("%14s rect={%d,%d,%d,%d}\n", label, r.fLeft, r.fTop, r.fRight, r.fBottom);
+    };
+    SkRegion::Iterator iter;
+    debugster("empty iter", iter, true);
+    SkRegion region;
+    iter.reset(region);
+    debugster("empty region", iter, true);
+    region.setRect({1, 2, 3, 4});
+    iter.reset(region);
+    debugster("after set rect", iter, false);
+    debugster("after rewind", iter, true);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_Iterator_rgn.cpp b/docs/examples/Region_Iterator_rgn.cpp
new file mode 100644
index 0000000..b2ef13c
--- /dev/null
+++ b/docs/examples/Region_Iterator_rgn.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bbc3c454a21186e2a16e843a5b061c44
+REG_FIDDLE(Region_Iterator_rgn, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion region;
+    SkIRect rects[] = {{1, 2, 3, 4}, {3, 4, 5, 6}};
+    region.setRects(rects, SK_ARRAY_COUNT(rects));
+    SkRegion::Iterator iter(region);
+    auto r = iter.rect();
+    SkDebugf("rect={%d,%d,%d,%d}\n", r.fLeft, r.fTop, r.fRight, r.fBottom);
+    auto b = iter.rgn()->getBounds();
+    SkDebugf("bounds={%d,%d,%d,%d}\n", b.fLeft, b.fTop, b.fRight, b.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_Op.cpp b/docs/examples/Region_Op.cpp
new file mode 100644
index 0000000..eed0861
--- /dev/null
+++ b/docs/examples/Region_Op.cpp
@@ -0,0 +1,25 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=026dd8b180fe8e43f477fce43e9217b3
+REG_FIDDLE(Region_Op, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion operand({35, 35, 85, 85});
+    const char* labels[] = {"difference", "intersect", "union", "xor", "reverse diff", "replace"};
+    int index = 0;
+    SkPaint paint;
+    for (auto op : { SkRegion::kDifference_Op, SkRegion::kIntersect_Op, SkRegion::kUnion_Op,
+                     SkRegion::kXOR_Op, SkRegion::kReverseDifference_Op, SkRegion::kReplace_Op } ) {
+        SkRegion target({10, 10, 60, 60});
+        target.op(operand, op);
+        canvas->drawRegion(target, paint);
+        canvas->drawString(labels[index++], 40, 100, paint);
+        canvas->translate(80, 0);
+        if (SkRegion::kUnion_Op == op) {
+            canvas->translate(-240, 120);
+        }
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Region_Spanerator_const_SkRegion_int_int_int.cpp b/docs/examples/Region_Spanerator_const_SkRegion_int_int_int.cpp
new file mode 100644
index 0000000..0b0cf03
--- /dev/null
+++ b/docs/examples/Region_Spanerator_const_SkRegion_int_int_int.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3073b3f8ea7252871b6156ff674dc385
+REG_FIDDLE(Region_Spanerator_const_SkRegion_int_int_int, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion region;
+    region.setRect({1, 2, 3, 4});
+    SkRegion::Spanerator spanner(region, 3, 2, 4);
+    int left, right;
+    bool result = spanner.next(&left, &right);
+    SkDebugf("result=%s left=%d right=%d\n", result ? "true" : "false", left, right);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_Spanerator_next.cpp b/docs/examples/Region_Spanerator_next.cpp
new file mode 100644
index 0000000..f7405c7
--- /dev/null
+++ b/docs/examples/Region_Spanerator_next.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=03d02180fee5f64ec4a3347e118fb2ec
+REG_FIDDLE(Region_Spanerator_next, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* label, SkRegion& region) -> void {
+        SkRegion::Spanerator spanner(region, 3, 2, 4);
+        int left, right;
+        bool result = spanner.next(&left, &right);
+        SkDebugf("%14s: result=%s", label, result ? "true" : "false");
+        if (result) SkDebugf(" left=%d right=%d", left, right);
+        SkDebugf("\n");
+    };
+    SkRegion region;
+    debugster("empty region", region);
+    region.setRect({1, 2, 3, 4});
+    debugster("after set rect", region);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_computeRegionComplexity.cpp b/docs/examples/Region_computeRegionComplexity.cpp
new file mode 100644
index 0000000..419ce6b
--- /dev/null
+++ b/docs/examples/Region_computeRegionComplexity.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c4984fefdcecdd1090be160f80939d87
+REG_FIDDLE(Region_computeRegionComplexity, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* label, const SkRegion& region) -> void {
+                SkDebugf("%s: region complexity %d\n", label, region.computeRegionComplexity());
+    };
+    SkRegion region;
+    debugster("initial", region);
+    region.setRect({1, 2, 3, 4});
+    debugster("set rect", region);
+    region.op({2, 3, 4, 5}, SkRegion::kUnion_Op);
+    debugster("op rect", region);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_contains.cpp b/docs/examples/Region_contains.cpp
new file mode 100644
index 0000000..20fb879
--- /dev/null
+++ b/docs/examples/Region_contains.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e3899c2715c332bfc7648d5f2b9eefc6
+REG_FIDDLE(Region_contains, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(128);
+    SkPath xPath;
+    paint.getTextPath("X", 1, 20, 110, &xPath);
+    SkRegion xRegion;
+    xRegion.setPath(xPath, SkRegion({0, 0, 256, 256}));
+    canvas->drawRegion(xRegion, paint);
+    for (int y = 0; y < 128; y += 8) {
+        for (int x = 0; x < 128; x += 8) {
+           paint.setColor(xRegion.contains(x, y) ? SK_ColorWHITE : SK_ColorRED);
+           canvas->drawPoint(x, y, paint);
+        }
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Region_contains_2.cpp b/docs/examples/Region_contains_2.cpp
new file mode 100644
index 0000000..a7700f3
--- /dev/null
+++ b/docs/examples/Region_contains_2.cpp
@@ -0,0 +1,24 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=100b4cbd5dd7406804e40035833a433c
+REG_FIDDLE(Region_contains_2, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(128);
+    SkPath xPath;
+    paint.getTextPath("X", 1, 20, 110, &xPath);
+    SkRegion xRegion;
+    SkIRect drawBounds = {0, 0, 128, 128};
+    xRegion.setPath(xPath, SkRegion(drawBounds));
+    xRegion.op(drawBounds, SkRegion::kReverseDifference_Op);
+    canvas->drawRegion(xRegion, paint);
+    SkIRect test = SkIRect::MakeXYWH(frame* 128, 64, 5, 5);
+    if (xRegion.contains(test)) {
+        paint.setColor(SK_ColorYELLOW);
+        canvas->drawRect(SkRect::Make(test), paint);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Region_contains_3.cpp b/docs/examples/Region_contains_3.cpp
new file mode 100644
index 0000000..05785e7
--- /dev/null
+++ b/docs/examples/Region_contains_3.cpp
@@ -0,0 +1,25 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=46de22da2f3e08a8d7f064634fc1c7b5
+REG_FIDDLE(Region_contains_3, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(128);
+    SkPath xPath, testPath;
+    paint.getTextPath("X", 1, 20, 110, &xPath);
+    paint.getTextPath("`", 1, frame * 150 - 40, 150, &testPath);
+    SkRegion xRegion, testRegion;
+    SkIRect drawBounds = {0, 0, 128, 128};
+    xRegion.setPath(xPath, SkRegion(drawBounds));
+    testRegion.setPath(testPath, SkRegion(drawBounds));
+    xRegion.op(drawBounds, SkRegion::kReverseDifference_Op);
+    canvas->drawRegion(xRegion, paint);
+    if (xRegion.contains(testRegion)) {
+        paint.setColor(SK_ColorYELLOW);
+        canvas->drawRegion(testRegion, paint);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Region_copy_const_SkIRect.cpp b/docs/examples/Region_copy_const_SkIRect.cpp
new file mode 100644
index 0000000..e8660b3
--- /dev/null
+++ b/docs/examples/Region_copy_const_SkIRect.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5253910233f7961c30b4c18ab911e917
+REG_FIDDLE(Region_copy_const_SkIRect, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion region({1, 2, 3, 4});
+    SkRegion region2;
+    region2.setRect({1, 2, 3, 4});
+    SkDebugf("region %c= region2\n", region == region2 ? '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_copy_const_SkRegion.cpp b/docs/examples/Region_copy_const_SkRegion.cpp
new file mode 100644
index 0000000..c1277b1
--- /dev/null
+++ b/docs/examples/Region_copy_const_SkRegion.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3daa83fca809b9ec6560d2ef9e2da5e6
+REG_FIDDLE(Region_copy_const_SkRegion, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* label, SkRegion& region) -> void {
+        auto r = region.getBounds();
+        SkDebugf("%14s: {%d,%d,%d,%d}\n", label, r.fLeft, r.fTop, r.fRight, r.fBottom);
+    };
+    SkRegion region({1, 2, 3, 4});
+    SkRegion region2(region);
+    debugster("region bounds", region);
+    debugster("region2 bounds", region2);
+    region.setEmpty();
+    SkDebugf("    after region set empty:\n");
+    debugster("region bounds", region);
+    debugster("region2 bounds", region2);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_copy_operator.cpp b/docs/examples/Region_copy_operator.cpp
new file mode 100644
index 0000000..474a40b
--- /dev/null
+++ b/docs/examples/Region_copy_operator.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e8513f6394c24efaa301d41921c5241a
+REG_FIDDLE(Region_copy_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* label, SkRegion& region) -> void {
+        auto r = region.getBounds();
+        SkDebugf("%14s: {%d,%d,%d,%d}\n", label, r.fLeft, r.fTop, r.fRight, r.fBottom);
+    };
+    SkRegion region1({1, 2, 3, 4});
+    SkRegion region2 = region1;
+    debugster("region1 bounds", region1);
+    debugster("region2 bounds", region2);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_destructor.cpp b/docs/examples/Region_destructor.cpp
new file mode 100644
index 0000000..ac27b42
--- /dev/null
+++ b/docs/examples/Region_destructor.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=985ff654a6b67288d322c748132a088e
+REG_FIDDLE(Region_destructor, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion* region = new SkRegion({1, 2, 3, 4});
+    SkRegion region2(*region);
+    delete region;
+    auto r = region2.getBounds();
+    SkDebugf("region2 bounds: {%d,%d,%d,%d}\n", r.fLeft, r.fTop, r.fRight, r.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_empty_constructor.cpp b/docs/examples/Region_empty_constructor.cpp
new file mode 100644
index 0000000..5fd6cad
--- /dev/null
+++ b/docs/examples/Region_empty_constructor.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4549dcda3e0f9a41b3daee0ed37deca8
+REG_FIDDLE(Region_empty_constructor, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion region;
+    SkIRect r = region.getBounds();
+    SkDebugf("region bounds: {%d, %d, %d, %d}\n", r.fLeft, r.fTop, r.fRight, r.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_equal1_operator.cpp b/docs/examples/Region_equal1_operator.cpp
new file mode 100644
index 0000000..3aa6d8a
--- /dev/null
+++ b/docs/examples/Region_equal1_operator.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d7f4fdc8bc63ca8410ed166ecef0aef3
+REG_FIDDLE(Region_equal1_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkRegion& a, const SkRegion& b) -> void {
+                SkDebugf("%s one %c= two\n", prefix, a == b ? '=' : '!');
+    };
+    SkRegion one;
+    SkRegion two;
+    debugster("empty", one, two);
+    one.setRect({1, 2, 3, 4});
+    debugster("set rect", one, two);
+    one.setEmpty();
+    debugster("set empty", one, two);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_getBoundaryPath.cpp b/docs/examples/Region_getBoundaryPath.cpp
new file mode 100644
index 0000000..bb29500
--- /dev/null
+++ b/docs/examples/Region_getBoundaryPath.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6631d36406efa3b3e27960c876421a7f
+REG_FIDDLE(Region_getBoundaryPath, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion region;
+    region.setRect({10, 20, 90, 60});
+    region.op({30, 40, 60, 80}, SkRegion::kXOR_Op);
+    canvas->drawRegion(region, SkPaint());
+    SkPath path;
+    region.getBoundaryPath(&path);
+    path.offset(100, 0);
+    canvas->drawPath(path, SkPaint());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_getBounds.cpp b/docs/examples/Region_getBounds.cpp
new file mode 100644
index 0000000..034b505
--- /dev/null
+++ b/docs/examples/Region_getBounds.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=651632582d385d2531e7aa551c31e331
+REG_FIDDLE(Region_getBounds, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion region({1, 2, 3, 4});
+    region.op({2, 3, 4, 5}, SkRegion::kUnion_Op);
+    auto r = region.getBounds();
+    SkDebugf("bounds: {%d,%d,%d,%d}\n", r.fLeft, r.fTop, r.fRight, r.fBottom);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_intersects.cpp b/docs/examples/Region_intersects.cpp
new file mode 100644
index 0000000..d3964e8
--- /dev/null
+++ b/docs/examples/Region_intersects.cpp
@@ -0,0 +1,20 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=42bde0ef8c2ee372751428cd6e21c1ca
+REG_FIDDLE(Region_intersects, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(128);
+    SkPath textPath;
+    paint.getTextPath("W", 1, 20, 110, &textPath);
+    SkRegion region;
+    region.setPath(textPath, SkRegion({0, 0, 256, 256}));
+    canvas->drawRegion(region, SkPaint());
+    SkIRect iRect = SkIRect::MakeXYWH(frame * 160, 55, 10, 10);
+    paint.setColor(region.intersects(iRect) ? SK_ColorBLUE : SK_ColorRED);
+    canvas->drawRect(SkRect::Make(iRect), paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Region_intersects_2.cpp b/docs/examples/Region_intersects_2.cpp
new file mode 100644
index 0000000..7df98c2
--- /dev/null
+++ b/docs/examples/Region_intersects_2.cpp
@@ -0,0 +1,21 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4263d79ac0e7df02e90948fdde9fa965
+REG_FIDDLE(Region_intersects_2, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(128);
+    SkPath hPath, dotPath;
+    paint.getTextPath("H", 1, 40, 110, &hPath);
+    paint.getTextPath(",", 1, frame * 180, 95, &dotPath);
+    SkRegion hRegion, dotRegion;
+    hRegion.setPath(hPath, SkRegion({0, 0, 256, 256}));
+    dotRegion.setPath(dotPath, SkRegion({0, 0, 256, 256}));
+    canvas->drawRegion(hRegion, paint);
+    paint.setColor(hRegion.intersects(dotRegion) ? SK_ColorBLUE : SK_ColorRED);
+    canvas->drawRegion(dotRegion, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Region_isComplex.cpp b/docs/examples/Region_isComplex.cpp
new file mode 100644
index 0000000..b17d47f
--- /dev/null
+++ b/docs/examples/Region_isComplex.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1fbd76d75ca2d280e81856311de4e54e
+REG_FIDDLE(Region_isComplex, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* label, const SkRegion& region) -> void {
+                SkDebugf("%s: region is %s" "complex\n", label, region.isComplex() ? "" : "not ");
+    };
+    SkRegion region;
+    debugster("initial", region);
+    region.setRect({1, 2, 3, 4});
+    debugster("set rect", region);
+    region.op({2, 3, 4, 5}, SkRegion::kUnion_Op);
+    debugster("op rect", region);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_isEmpty.cpp b/docs/examples/Region_isEmpty.cpp
new file mode 100644
index 0000000..dc02155
--- /dev/null
+++ b/docs/examples/Region_isEmpty.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=10ef0de39e8553dd97cf8668ce185070
+REG_FIDDLE(Region_isEmpty, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* label, SkRegion& region) -> void {
+        SkDebugf("%14s: region is %s" "empty\n", label, region.isEmpty() ? "" : "not ");
+    };
+    SkRegion region;
+    debugster("initial", region);
+    region.setRect({1, 2, 3, 4});
+    debugster("set rect", region);
+    region.setEmpty();
+    debugster("set empty", region);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_isRect.cpp b/docs/examples/Region_isRect.cpp
new file mode 100644
index 0000000..571598e
--- /dev/null
+++ b/docs/examples/Region_isRect.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b6adbdddf7fe45a1098121c4e5fd57ea
+REG_FIDDLE(Region_isRect, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* label, const SkRegion& region) -> void {
+                SkDebugf("%s: region is %s" "rect\n", label, region.isRect() ? "" : "not ");
+    };
+    SkRegion region;
+    debugster("initial", region);
+    region.setRect({1, 2, 3, 4});
+    debugster("set rect", region);
+    region.setEmpty();
+    debugster("set empty", region);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_notequal1_operator.cpp b/docs/examples/Region_notequal1_operator.cpp
new file mode 100644
index 0000000..a0602d6
--- /dev/null
+++ b/docs/examples/Region_notequal1_operator.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3357caa9d8d810f200cbccb668182496
+REG_FIDDLE(Region_notequal1_operator, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* prefix, const SkRegion& a, const SkRegion& b) -> void {
+                SkDebugf("%s one %c= two\n", prefix, a != b ? '!' : '=');
+    };
+    SkRegion one;
+    SkRegion two;
+    debugster("empty", one, two);
+    one.setRect({1, 2, 3, 4});
+    two.setRect({1, 2, 3, 3});
+    debugster("set rect", one, two);
+    two.op({1, 3, 3, 4}, SkRegion::kUnion_Op);
+    debugster("union rect", one, two);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_op_1.cpp b/docs/examples/Region_op_1.cpp
new file mode 100644
index 0000000..b5c56b6
--- /dev/null
+++ b/docs/examples/Region_op_1.cpp
@@ -0,0 +1,19 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1790b2e054c536a54601138365700ac3
+REG_FIDDLE(Region_op_1, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(128);
+    SkPath xPath;
+    paint.getTextPath("X", 1, 20, 110, &xPath);
+    SkRegion xRegion;
+    SkIRect drawBounds = {0, 0, 128, 128};
+    xRegion.setPath(xPath, SkRegion(drawBounds));
+    xRegion.op(drawBounds, SkRegion::kReverseDifference_Op);
+    canvas->drawRegion(xRegion, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Region_op_2.cpp b/docs/examples/Region_op_2.cpp
new file mode 100644
index 0000000..3cca6f6
--- /dev/null
+++ b/docs/examples/Region_op_2.cpp
@@ -0,0 +1,20 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2e3497890d523235f96680716c321098
+REG_FIDDLE(Region_op_2, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(128);
+    SkPath xPath;
+    paint.getTextPath("X", 1, 20, 110, &xPath);
+    SkRegion xRegion;
+    SkIRect drawBounds = {0, 0, 128, 128};
+    xRegion.setPath(xPath, SkRegion(drawBounds));
+    xRegion.op(drawBounds.fLeft + frame * drawBounds.width(), drawBounds.fTop,
+               drawBounds.fRight, drawBounds.fBottom, SkRegion::kReverseDifference_Op);
+    canvas->drawRegion(xRegion, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Region_op_3.cpp b/docs/examples/Region_op_3.cpp
new file mode 100644
index 0000000..48ccba6
--- /dev/null
+++ b/docs/examples/Region_op_3.cpp
@@ -0,0 +1,21 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=65f4eccea3514ed7e37b5067e15efddb
+REG_FIDDLE(Region_op_3, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(128);
+    SkPath xPath, opPath;
+    paint.getTextPath("X", 1, 20, 110, &xPath);
+    opPath.addCircle(64, 64, frame * 64);
+    SkRegion xRegion, opRegion;
+    SkIRect drawBounds = {0, 0, 128, 128};
+    opRegion.setPath(opPath, SkRegion(drawBounds));
+    xRegion.setPath(xPath, SkRegion(drawBounds));
+    xRegion.op(opRegion, SkRegion::kReverseDifference_Op);
+    canvas->drawRegion(xRegion, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Region_op_4.cpp b/docs/examples/Region_op_4.cpp
new file mode 100644
index 0000000..e69b77f
--- /dev/null
+++ b/docs/examples/Region_op_4.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3f964be1e1fd2fbb977b655d3a928f0a
+REG_FIDDLE(Region_op_4, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(128);
+    SkPath xPath, opPath;
+    paint.getTextPath("X", 1, 20, 110, &xPath);
+    opPath.addCircle(64, 64, frame * 64);
+    SkRegion xRegion, opRegion, rectRegion;
+    SkIRect drawBounds = {0, 0, 128, 128};
+    opRegion.setPath(opPath, SkRegion(drawBounds));
+    xRegion.setPath(xPath, SkRegion(drawBounds));
+    drawBounds.inset(frame * drawBounds.width() / 2, 0);
+    rectRegion.op(drawBounds, opRegion, SkRegion::kIntersect_Op);
+    xRegion.op(rectRegion, SkRegion::kReverseDifference_Op);
+    canvas->drawRegion(xRegion, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Region_op_5.cpp b/docs/examples/Region_op_5.cpp
new file mode 100644
index 0000000..4188927
--- /dev/null
+++ b/docs/examples/Region_op_5.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e623208dd44f0b24499ac5f1593d1b39
+REG_FIDDLE(Region_op_5, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(128);
+    SkPath xPath, opPath;
+    paint.getTextPath("X", 1, 20, 110, &xPath);
+    opPath.addCircle(64, 64, frame * 64);
+    SkRegion xRegion, opRegion, rectRegion;
+    SkIRect drawBounds = {0, 0, 128, 128};
+    opRegion.setPath(opPath, SkRegion(drawBounds));
+    xRegion.setPath(xPath, SkRegion(drawBounds));
+    drawBounds.inset(frame * drawBounds.width() / 2, 0);
+    rectRegion.op(opRegion, drawBounds, SkRegion::kUnion_Op);
+    xRegion.op(rectRegion, SkRegion::kReverseDifference_Op);
+    canvas->drawRegion(xRegion, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Region_op_6.cpp b/docs/examples/Region_op_6.cpp
new file mode 100644
index 0000000..3e5cee5
--- /dev/null
+++ b/docs/examples/Region_op_6.cpp
@@ -0,0 +1,26 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=13de1a6fcb2302a2a30278cb88d3e17d
+REG_FIDDLE(Region_op_6, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(128);
+    SkPath xPath, opPath;
+    paint.getTextPath("X", 1, 20, 110, &xPath);
+    xPath.setFillType(SkPath::kInverseWinding_FillType);
+    opPath.addCircle(64, 64, frame * 64);
+    opPath.setFillType(SkPath::kInverseWinding_FillType);
+    SkRegion xRegion, opRegion, rectRegion;
+    SkIRect drawBounds = {0, 0, 128, 128};
+    opRegion.setPath(opPath, SkRegion(drawBounds));
+    xRegion.setPath(xPath, SkRegion(drawBounds));
+    drawBounds.inset(frame * drawBounds.width() / 2, 0);
+    rectRegion.setRect(drawBounds);
+    rectRegion.op(xRegion, SkRegion::kIntersect_Op);
+    xRegion.op(rectRegion, opRegion, SkRegion::kReverseDifference_Op);
+    canvas->drawRegion(xRegion, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Region_quickContains.cpp b/docs/examples/Region_quickContains.cpp
new file mode 100644
index 0000000..9168fb4
--- /dev/null
+++ b/docs/examples/Region_quickContains.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d8e5eac373e2e7cfc1b8cd0229647ba6
+REG_FIDDLE(Region_quickContains, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion region({1, 2, 3, 4});
+    SkIRect test = {2, 2, 3, 3};
+    SkDebugf("quickContains 1: %s\n", region.quickContains(test) ? "true" : "false");
+    region.op({1, 4, 3, 6}, SkRegion::kUnion_Op);
+    SkDebugf("quickContains 2: %s\n", region.quickContains(test) ? "true" : "false");
+    region.op({1, 7, 3, 8}, SkRegion::kUnion_Op);
+    SkDebugf("quickContains 3: %s\n", region.quickContains(test) ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_quickContains_2.cpp b/docs/examples/Region_quickContains_2.cpp
new file mode 100644
index 0000000..b3e033f
--- /dev/null
+++ b/docs/examples/Region_quickContains_2.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=eb6d290887e1a3a0b051b4d7b012f5e1
+REG_FIDDLE(Region_quickContains_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* label, SkRegion& region) -> void {
+        SkDebugf("%s: %s\n", label, region.quickContains(2, 2, 3, 3) ? "true" : "false");
+    };
+    SkRegion region({1, 2, 3, 4});
+    debugster("quickContains 1", region);
+    region.op({1, 4, 3, 6}, SkRegion::kUnion_Op);
+    debugster("quickContains 2", region);
+    region.op({1, 7, 3, 8}, SkRegion::kUnion_Op);
+    debugster("quickContains 3", region);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_quickReject.cpp b/docs/examples/Region_quickReject.cpp
new file mode 100644
index 0000000..0f52ea7
--- /dev/null
+++ b/docs/examples/Region_quickReject.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=71ac24b7d91ac5ca7c14b43930d5f85d
+REG_FIDDLE(Region_quickReject, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion region({1, 2, 3, 4});
+    SkIRect test = {4, 2, 5, 3};
+    SkDebugf("quickReject 1: %s\n", region.quickReject(test) ? "true" : "false");
+    region.op({1, 4, 3, 6}, SkRegion::kUnion_Op);
+    SkDebugf("quickReject 2: %s\n", region.quickReject(test) ? "true" : "false");
+    region.op({4, 7, 5, 8}, SkRegion::kUnion_Op);
+    SkDebugf("quickReject 3: %s\n", region.quickReject(test) ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_quickReject_2.cpp b/docs/examples/Region_quickReject_2.cpp
new file mode 100644
index 0000000..d1a23d2
--- /dev/null
+++ b/docs/examples/Region_quickReject_2.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=def7dba38947c33b203e4f9db6c88be3
+REG_FIDDLE(Region_quickReject_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion region({1, 2, 3, 4});
+    SkRegion test;
+    SkIRect rects[] = {{4, 2, 5, 3}, {7, 2, 8, 3}};
+    test.setRects(rects, SK_ARRAY_COUNT(rects));
+    SkDebugf("quickReject 1: %s\n", region.quickReject(test) ? "true" : "false");
+    region.op({1, 4, 3, 6}, SkRegion::kUnion_Op);
+    SkDebugf("quickReject 2: %s\n", region.quickReject(test) ? "true" : "false");
+    region.op({4, 7, 5, 8}, SkRegion::kUnion_Op);
+    SkDebugf("quickReject 3: %s\n", region.quickReject(test) ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_readFromMemory.cpp b/docs/examples/Region_readFromMemory.cpp
new file mode 100644
index 0000000..c043b7e
--- /dev/null
+++ b/docs/examples/Region_readFromMemory.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1ede346c430ef23df0eaaf0773dd6a15
+REG_FIDDLE(Region_readFromMemory, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion region({20, 20, 80, 80});
+    size_t size = region.writeToMemory(nullptr);
+    sk_sp<SkData> data = SkData::MakeUninitialized(size);
+    region.writeToMemory(data->writable_data());
+    SkRegion copy;
+    copy.readFromMemory(data->data(), data->size());
+    canvas->drawRegion(copy, SkPaint());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_set.cpp b/docs/examples/Region_set.cpp
new file mode 100644
index 0000000..8270db0
--- /dev/null
+++ b/docs/examples/Region_set.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b3538117c7ae2cb7de3b42ca45fe1b13
+REG_FIDDLE(Region_set, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* label, SkRegion& region) -> void {
+        auto r = region.getBounds();
+        SkDebugf("%14s: {%d,%d,%d,%d}\n", label, r.fLeft, r.fTop, r.fRight, r.fBottom);
+    };
+    SkRegion region1({1, 2, 3, 4});
+    SkRegion region2;
+    region2.set(region1);
+    debugster("region1 bounds", region1);
+    debugster("region2 bounds", region2);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_setEmpty.cpp b/docs/examples/Region_setEmpty.cpp
new file mode 100644
index 0000000..08cc4ca
--- /dev/null
+++ b/docs/examples/Region_setEmpty.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1314f7250963775c5ee89cc5981eee24
+REG_FIDDLE(Region_setEmpty, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* label, SkRegion& region) -> void {
+        auto r = region.getBounds();
+        SkDebugf("%14s: {%d,%d,%d,%d}\n", label, r.fLeft, r.fTop, r.fRight, r.fBottom);
+    };
+    SkRegion region({1, 2, 3, 4});
+    debugster("region bounds", region);
+    region.setEmpty();
+    SkDebugf("    after region set empty:\n");
+    debugster("region bounds", region);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_setPath.cpp b/docs/examples/Region_setPath.cpp
new file mode 100644
index 0000000..b9342b5
--- /dev/null
+++ b/docs/examples/Region_setPath.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=45b9ea2247b9ca7f10aa22ea29a426f4
+REG_FIDDLE(Region_setPath, 256, 120, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(128);
+    SkPath textPath;
+    paint.getTextPath("Q", 1, 0, 110, &textPath);
+    SkIRect clipRect = {20, 20, 100, 120};
+    SkRegion clipRegion(clipRect);
+    SkRegion region;
+    region.setPath(textPath, clipRegion);
+    canvas->drawRegion(region, SkPaint());
+    clipRect.offset(100, 0);
+    textPath.offset(100, 0);
+    canvas->clipRect(SkRect::Make(clipRect), false);
+    canvas->drawPath(textPath, SkPaint());
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Region_setRect.cpp b/docs/examples/Region_setRect.cpp
new file mode 100644
index 0000000..b25e03f
--- /dev/null
+++ b/docs/examples/Region_setRect.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e12575ffcd262f2364e0e6bece98a825
+REG_FIDDLE(Region_setRect, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion region({1, 2, 3, 4});
+    SkDebugf("region is %s" "empty\n", region.isEmpty() ? "" : "not ");
+    bool setEmpty = region.setRect({1, 2, 1, 4});
+    SkDebugf("region is %s" "empty\n", region.isEmpty() ? "" : "not ");
+    SkDebugf("setEmpty: %s\n", setEmpty ? "true" : "false");
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_setRect_2.cpp b/docs/examples/Region_setRect_2.cpp
new file mode 100644
index 0000000..c16548b
--- /dev/null
+++ b/docs/examples/Region_setRect_2.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5b31a1b077818a8150ad50f3b19e7bfe
+REG_FIDDLE(Region_setRect_2, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* label, bool success, SkRegion& region) -> void {
+        auto r = region.getBounds();
+        SkDebugf("%14s: success:%s {%d,%d,%d,%d}\n", label, success ? "true" : "false",
+                 r.fLeft, r.fTop, r.fRight, r.fBottom);
+    };
+    SkRegion region;
+    bool success = region.setRect(1, 2, 3, 4);
+    debugster("set to: 1,2,3,4", success, region);
+    success = region.setRect(3, 2, 1, 4);
+    debugster("set to: 3,2,1,4", success, region);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_setRects.cpp b/docs/examples/Region_setRects.cpp
new file mode 100644
index 0000000..2ba2cdf
--- /dev/null
+++ b/docs/examples/Region_setRects.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=fc793a14ed76c096a68a755c963c1ee0
+REG_FIDDLE(Region_setRects, 256, 70, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkIRect rects[] = { {10, 10, 40, 40}, {20, 20, 50, 50}, {30, 30, 60, 60} };
+    SkRegion region;
+    region.setRects(rects, SK_ARRAY_COUNT(rects));
+    canvas->drawRegion(region, SkPaint());
+    region.setEmpty();
+    for (auto add : rects) {
+        region.op(add, SkRegion::kUnion_Op);
+    }
+    region.translate(100, 0);
+    canvas->drawRegion(region, SkPaint());
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_setRegion.cpp b/docs/examples/Region_setRegion.cpp
new file mode 100644
index 0000000..ae90894
--- /dev/null
+++ b/docs/examples/Region_setRegion.cpp
@@ -0,0 +1,21 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5d75d22bd155576838155762ab040751
+REG_FIDDLE(Region_setRegion, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* label, SkRegion& region) -> void {
+        auto r = region.getBounds();
+        SkDebugf("%14s: {%d,%d,%d,%d}\n", label, r.fLeft, r.fTop, r.fRight, r.fBottom);
+    };
+    SkRegion region({1, 2, 3, 4});
+    SkRegion region2;
+    region2.setRegion(region);
+    debugster("region bounds", region);
+    debugster("region2 bounds", region2);
+    region2.setEmpty();
+    SkDebugf("    after region set empty:\n");
+    debugster("region bounds", region);
+    debugster("region2 bounds", region2);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_swap.cpp b/docs/examples/Region_swap.cpp
new file mode 100644
index 0000000..668814c
--- /dev/null
+++ b/docs/examples/Region_swap.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ae67b7b4c198b46c58e48f5af061c8f1
+REG_FIDDLE(Region_swap, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto debugster = [](const char* label, SkRegion& region) -> void {
+        auto r = region.getBounds();
+        SkDebugf("%14s: {%d,%d,%d,%d}\n", label, r.fLeft, r.fTop, r.fRight, r.fBottom);
+    };
+    SkRegion region1({1, 2, 3, 4});
+    SkRegion region2;
+    region1.swap(region2);
+    debugster("region1 bounds", region1);
+    debugster("region2 bounds", region2);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_translate.cpp b/docs/examples/Region_translate.cpp
new file mode 100644
index 0000000..d2e97a1
--- /dev/null
+++ b/docs/examples/Region_translate.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=4e5b9e53aa1b200fed3ee6596ca01f0e
+REG_FIDDLE(Region_translate, 256, 90, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion test;
+    SkIRect rects[] = {{40, 20, 50, 30}, {70, 40, 80, 50}, { 60, 10, 70, 20}};
+    test.setRects(rects, SK_ARRAY_COUNT(rects));
+    SkPaint paint;
+    for (auto color :  { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN, SK_ColorMAGENTA } ) {
+        paint.setColor(color);
+        canvas->drawRegion(test, paint);
+        test.translate(10, 10);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_translate_2.cpp b/docs/examples/Region_translate_2.cpp
new file mode 100644
index 0000000..8151d3d
--- /dev/null
+++ b/docs/examples/Region_translate_2.cpp
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=024200960eb52fee1f471514607e6001
+REG_FIDDLE(Region_translate_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkRegion test;
+    SkIRect rects[] = {{40, 20, 50, 30}, {70, 40, 80, 50}, { 60, 10, 70, 20}};
+    test.setRects(rects, SK_ARRAY_COUNT(rects));
+    SkPaint paint;
+    for (auto color :  { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN, SK_ColorMAGENTA } ) {
+        paint.setColor(color);
+        canvas->drawRegion(test, paint);
+        SkRegion second;
+        test.translate(10, test.getBounds().fBottom, &second);
+        test.op(second, SkRegion::kXOR_Op);
+        test.translate(30, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Region_writeToMemory.cpp b/docs/examples/Region_writeToMemory.cpp
new file mode 100644
index 0000000..1fe05d2
--- /dev/null
+++ b/docs/examples/Region_writeToMemory.cpp
@@ -0,0 +1,23 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1419d2a8c22c355ab46240865d056ee5
+REG_FIDDLE(Region_writeToMemory, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(128);
+    SkPath xPath;
+    paint.getTextPath("X", 1, 20, 110, &xPath);
+    SkIRect drawBounds = {0, 0, 128, 128};
+    SkRegion xRegion;
+    xRegion.setPath(xPath, SkRegion(drawBounds));
+    size_t size = xRegion.writeToMemory(nullptr);
+    sk_sp<SkData> data = SkData::MakeUninitialized(size);
+    xRegion.writeToMemory(data->writable_data());
+    SkRegion copy;
+    copy.readFromMemory(data->data(), data->size());
+    canvas->drawRegion(copy, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Saturation.cpp b/docs/examples/Saturation.cpp
new file mode 100644
index 0000000..c3bbf8c
--- /dev/null
+++ b/docs/examples/Saturation.cpp
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a48698975d236573cef512f94a7e360b
+REG_FIDDLE(Saturation, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->drawImage(image, 0, 0);
+    canvas->drawColor(0xFF00FF00, SkBlendMode::kSaturation);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Screen.cpp b/docs/examples/Screen.cpp
new file mode 100644
index 0000000..edbb97d
--- /dev/null
+++ b/docs/examples/Screen.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b7b42965927788d853f449f08ddf46de
+REG_FIDDLE(Screen, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
+    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
+    SkPaint paint;
+    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    paint.setBlendMode(SkBlendMode::kDstATop);
+    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
+    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
+    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    canvas->clipRect( { 30, 30, 226, 226 } );
+    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kScreen);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Shader_Methods_a.cpp b/docs/examples/Shader_Methods_a.cpp
new file mode 100644
index 0000000..70ee642
--- /dev/null
+++ b/docs/examples/Shader_Methods_a.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c015dc2010c15e1c00b4f7330232b0f7
+REG_FIDDLE(Shader_Methods_a, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+   SkPaint paint;
+   SkPoint center = { 50, 50 };
+   SkScalar radius = 50;
+   const SkColor colors[] = { 0xFFFFFFFF, 0xFF000000 };
+   paint.setShader(SkGradientShader::MakeRadial(center, radius, colors,
+        nullptr, SK_ARRAY_COUNT(colors), SkTileMode::kClamp));
+   for (SkScalar a : { 0.3f, 0.6f, 1.0f } ) {
+       paint.setAlpha((int) (a * 255));
+       canvas->drawCircle(center.fX, center.fY, radius, paint);
+       canvas->translate(70, 70);
+   }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Shader_Methods_b.cpp b/docs/examples/Shader_Methods_b.cpp
new file mode 100644
index 0000000..f04ed31
--- /dev/null
+++ b/docs/examples/Shader_Methods_b.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=fe80fd80b98a20823db7fb9a077243c7
+REG_FIDDLE(Shader_Methods_b, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+   SkPaint paint;
+   SkBitmap bitmap;
+   bitmap.setInfo(SkImageInfo::MakeA8(5, 1), 5);  // bitmap only contains alpha
+   uint8_t pixels[5] = { 0x22, 0x55, 0x88, 0xBB, 0xFF };
+   bitmap.setPixels(pixels);
+   paint.setShader(bitmap.makeShader(SkTileMode::kMirror, SkTileMode::kMirror));
+   for (SkColor c : { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN } ) {
+       paint.setColor(c);  // all components in color affect shader
+       canvas->drawCircle(50, 50, 50, paint);
+       canvas->translate(70, 70);
+   }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Soft_Light.cpp b/docs/examples/Soft_Light.cpp
new file mode 100644
index 0000000..a97b75a
--- /dev/null
+++ b/docs/examples/Soft_Light.cpp
@@ -0,0 +1,15 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ac93f30dff13f8a8bb31398de370863b
+REG_FIDDLE(Soft_Light, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    const SkColor colors[] = { 0xFFFFFFFF, 0x3FFFFFFF };
+    SkPaint paint;
+    paint.setBlendMode(SkBlendMode::kSoftLight);
+    paint.setShader(SkGradientShader::MakeRadial({ 128, 128}, 100, colors,
+         nullptr, SK_ARRAY_COUNT(colors), SkTileMode::kClamp));
+    canvas->drawImage(image, 0, 0);
+    canvas->drawCircle(128, 128, 100, paint);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Src.cpp b/docs/examples/Src.cpp
new file mode 100644
index 0000000..50d26f3
--- /dev/null
+++ b/docs/examples/Src.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0fc85dd916cc1a5896d36c80b9847391
+REG_FIDDLE(Src, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    canvas->drawImage(image, 0, 0);
+    canvas->clipRect({50, 50, 200, 200});
+    SkPaint srcBlend;
+    srcBlend.setBlendMode(SkBlendMode::kSrc);
+    canvas->saveLayer(nullptr, &srcBlend);
+    canvas->drawColor(0);
+    SkPaint transRed;
+    transRed.setColor(SkColorSetA(SK_ColorRED, 127));
+    canvas->drawCircle(125, 125, 75, transRed);
+    canvas->restore();
+}
+}  // END FIDDLE
diff --git a/docs/examples/Src_Atop.cpp b/docs/examples/Src_Atop.cpp
new file mode 100644
index 0000000..4560f95
--- /dev/null
+++ b/docs/examples/Src_Atop.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a13148977bfc985934a92752c83a2041
+REG_FIDDLE(Src_Atop, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
+    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
+    SkPaint paint;
+    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    paint.setBlendMode(SkBlendMode::kDstIn);
+    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
+    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
+    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    canvas->clipRect( { 30, 30, 226, 226 } );
+    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kSrcATop);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Src_In.cpp b/docs/examples/Src_In.cpp
new file mode 100644
index 0000000..2dbbe79
--- /dev/null
+++ b/docs/examples/Src_In.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b0833c18fe8b0eeaab9bd6d2160d272f
+REG_FIDDLE(Src_In, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
+    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
+    SkPaint paint;
+    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    paint.setBlendMode(SkBlendMode::kDstIn);
+    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
+    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
+    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    canvas->clipRect( { 30, 30, 226, 226 } );
+    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kSrcIn);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Src_Out.cpp b/docs/examples/Src_Out.cpp
new file mode 100644
index 0000000..f7cdb0a
--- /dev/null
+++ b/docs/examples/Src_Out.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=ccc1e74226e0c9eacbc21f1eed017b84
+REG_FIDDLE(Src_Out, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
+    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
+    SkPaint paint;
+    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    paint.setBlendMode(SkBlendMode::kDstIn);
+    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
+    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
+    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    canvas->clipRect( { 30, 30, 226, 226 } );
+    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kSrcOut);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Src_Over.cpp b/docs/examples/Src_Over.cpp
new file mode 100644
index 0000000..e0c9fb3
--- /dev/null
+++ b/docs/examples/Src_Over.cpp
@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2ea9c149964a06cdb4929158cb4f15f8
+REG_FIDDLE(Src_Over, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
+    SkPoint horz[] = { { 0, 0 }, { 256, 0 } };
+    SkPaint paint;
+    paint.setShader(SkGradientShader::MakeLinear(horz, colors, nullptr, SK_ARRAY_COUNT(colors),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    paint.setBlendMode(SkBlendMode::kDstIn);
+    SkColor alphas[] = { SK_ColorBLACK, SK_ColorTRANSPARENT };
+    SkPoint vert[] = { { 0, 0 }, { 0, 256 } };
+    paint.setShader(SkGradientShader::MakeLinear(vert, alphas, nullptr, SK_ARRAY_COUNT(alphas),
+            SkTileMode::kClamp));
+    canvas->drawPaint(paint);
+    canvas->clipRect( { 30, 30, 226, 226 } );
+    canvas->drawColor(SkColorSetA(SK_ColorGREEN, 128), SkBlendMode::kSrcOver);
+}
+}  // END FIDDLE
diff --git a/docs/examples/State_Stack_a.cpp b/docs/examples/State_Stack_a.cpp
new file mode 100644
index 0000000..f1be0f6
--- /dev/null
+++ b/docs/examples/State_Stack_a.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bb1dbfdca3aedf716beb6f07e2aab065
+REG_FIDDLE(State_Stack_a, 256, 160, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    canvas->save();                             // records stack depth to restore
+    canvas->clipRect(SkRect::MakeWH(100, 100)); // constrains drawing to clip
+    canvas->clear(SK_ColorRED);                 // draws to limit of clip
+    canvas->save();                             // records stack depth to restore
+    canvas->clipRect(SkRect::MakeWH(50, 150));  // Rect below 100 is ignored
+    canvas->clear(SK_ColorBLUE);                // draws to smaller clip
+    canvas->restore();                          // enlarges clip
+    canvas->drawLine(20, 20, 150, 150, paint);  // line below 100 is not drawn
+    canvas->restore();                          // enlarges clip
+    canvas->drawLine(150, 20, 50, 120, paint);  // line below 100 is drawn
+}
+}  // END FIDDLE
diff --git a/docs/examples/State_Stack_b.cpp b/docs/examples/State_Stack_b.cpp
new file mode 100644
index 0000000..83710fa
--- /dev/null
+++ b/docs/examples/State_Stack_b.cpp
@@ -0,0 +1,13 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9f563a2d60aa31d4b26742e5aa17aa4e
+REG_FIDDLE(State_Stack_b, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    canvas->clipRect(SkRect::MakeWH(100, 100));
+    canvas->clear(SK_ColorRED);
+    canvas->scale(.5, .5);
+    canvas->clipRect(SkRect::MakeWH(100, 100));
+    canvas->clear(SK_ColorBLUE);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Stroke_Width.cpp b/docs/examples/Stroke_Width.cpp
new file mode 100644
index 0000000..f16d343
--- /dev/null
+++ b/docs/examples/Stroke_Width.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5112c7209a19e035c61cef33a624a652
+REG_FIDDLE(Stroke_Width, 256, 170, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    for (bool antialias : { false, true }) {
+        paint.setAntiAlias(antialias);
+        for (int width = 0; width <= 4; ++width) {
+            SkScalar offset = antialias * 100 + width * 20;
+            paint.setStrokeWidth(width * 0.25f);
+            canvas->drawLine(10 + offset,  10, 20 + offset,  60, paint);
+            canvas->drawLine(10 + offset, 110, 60 + offset, 160, paint);
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Surface_MakeFromBackendTexture.cpp b/docs/examples/Surface_MakeFromBackendTexture.cpp
new file mode 100644
index 0000000..7a841dd
--- /dev/null
+++ b/docs/examples/Surface_MakeFromBackendTexture.cpp
@@ -0,0 +1,24 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d3aec071998f871809f515e58abb1b0e
+REG_FIDDLE(Surface_MakeFromBackendTexture, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(32);
+    GrContext* context = canvas->getGrContext();
+    if (!context) {
+         canvas->drawString("GPU only!", 20, 40, paint);
+         return;
+    }
+    sk_sp<SkSurface> gpuSurface = SkSurface::MakeFromBackendTexture(context,
+            backEndTexture, kTopLeft_GrSurfaceOrigin, 0,
+            kRGBA_8888_SkColorType, nullptr, nullptr);
+    auto surfaceCanvas = gpuSurface->getCanvas();
+    surfaceCanvas->drawString("GPU rocks!", 20, 40, paint);
+    sk_sp<SkImage> image(gpuSurface->makeImageSnapshot());
+    canvas->drawImage(image, 0, 0);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Surface_MakeFromBackendTextureAsRenderTarget.cpp b/docs/examples/Surface_MakeFromBackendTextureAsRenderTarget.cpp
new file mode 100644
index 0000000..9f22e1f
--- /dev/null
+++ b/docs/examples/Surface_MakeFromBackendTextureAsRenderTarget.cpp
@@ -0,0 +1,24 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5e87093b9cbe95124ae14cbe77091eb7
+REG_FIDDLE(Surface_MakeFromBackendTextureAsRenderTarget, 256, 256, false, 3) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(32);
+    GrContext* context = canvas->getGrContext();
+    if (!context) {
+         canvas->drawString("GPU only!", 20, 40, paint);
+         return;
+    }
+    sk_sp<SkSurface> gpuSurface = SkSurface::MakeFromBackendTextureAsRenderTarget(
+            context, backEndTexture, kTopLeft_GrSurfaceOrigin, 0,
+            kRGBA_8888_SkColorType, nullptr, nullptr);
+    auto surfaceCanvas = gpuSurface->getCanvas();
+    surfaceCanvas->drawString("GPU rocks!", 20, 40, paint);
+    sk_sp<SkImage> image(gpuSurface->makeImageSnapshot());
+    canvas->drawImage(image, 0, 0);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Surface_MakeNull.cpp b/docs/examples/Surface_MakeNull.cpp
new file mode 100644
index 0000000..1052240
--- /dev/null
+++ b/docs/examples/Surface_MakeNull.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=99a54b814ccab7d2b1143c88581649ff
+REG_FIDDLE(Surface_MakeNull, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkDebugf("SkSurface::MakeNull(0, 0) %c= nullptr\n", SkSurface::MakeNull(0, 0) == nullptr ?
+             '=' : '!');
+    const int w = 37;
+    const int h = 1000;
+    auto surf = SkSurface::MakeNull(w, h);
+    auto nullCanvas = surf->getCanvas();
+    nullCanvas->drawPaint(SkPaint());   // does not crash, nothing draws
+    SkDebugf("surf->makeImageSnapshot() %c= nullptr\n", surf->makeImageSnapshot() == nullptr ?
+            '=' : '!');
+}
+}  // END FIDDLE
diff --git a/docs/examples/Surface_MakeRaster.cpp b/docs/examples/Surface_MakeRaster.cpp
new file mode 100644
index 0000000..34b3906
--- /dev/null
+++ b/docs/examples/Surface_MakeRaster.cpp
@@ -0,0 +1,28 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a803910ada4f8733f0b62456afead55f
+REG_FIDDLE(Surface_MakeRaster, 256, 256, true, 0) {
+void draw(SkCanvas* ) {
+    SkImageInfo info = SkImageInfo::MakeN32Premul(3, 3);
+    const size_t rowBytes = 64;
+    sk_sp<SkSurface> surface(SkSurface::MakeRaster(info, rowBytes, nullptr));
+    SkCanvas* canvas = surface->getCanvas();
+    canvas->clear(SK_ColorWHITE);
+    SkPixmap pixmap;
+    if (surface->peekPixels(&pixmap)) {
+        const uint32_t* colorPtr = pixmap.addr32();
+        SkPMColor pmWhite = colorPtr[0];
+        SkPaint paint;
+        canvas->drawPoint(1, 1, paint);
+        canvas->flush();  // ensure that point was drawn
+        for (int y = 0; y < info.height(); ++y) {
+            for (int x = 0; x < info.width(); ++x) {
+                SkDebugf("%c", colorPtr[x] == pmWhite ? '-' : 'x');
+            }
+            colorPtr += rowBytes / sizeof(colorPtr[0]);
+            SkDebugf("\n");
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Surface_MakeRasterDirect.cpp b/docs/examples/Surface_MakeRasterDirect.cpp
new file mode 100644
index 0000000..484c275
--- /dev/null
+++ b/docs/examples/Surface_MakeRasterDirect.cpp
@@ -0,0 +1,25 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=3f5aeb870104187643197354a7f1d27a
+REG_FIDDLE(Surface_MakeRasterDirect, 256, 256, true, 0) {
+void draw(SkCanvas* ) {
+    SkImageInfo info = SkImageInfo::MakeN32Premul(3, 3);
+    const size_t size = info.computeMinByteSize();
+    SkAutoTMalloc<SkPMColor> storage(size);
+    SkPMColor* pixels = storage.get();
+    sk_sp<SkSurface> surface(SkSurface::MakeRasterDirect(info, pixels, info.minRowBytes()));
+    SkCanvas* canvas = surface->getCanvas();
+    canvas->clear(SK_ColorWHITE);
+    SkPMColor pmWhite = pixels[0];
+    SkPaint paint;
+    canvas->drawPoint(1, 1, paint);
+    canvas->flush();  // ensure that point was drawn
+    for (int y = 0; y < info.height(); ++y) {
+        for (int x = 0; x < info.width(); ++x) {
+            SkDebugf("%c", *pixels++ == pmWhite ? '-' : 'x');
+        }
+        SkDebugf("\n");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Surface_MakeRasterDirectReleaseProc.cpp b/docs/examples/Surface_MakeRasterDirectReleaseProc.cpp
new file mode 100644
index 0000000..d6f5223
--- /dev/null
+++ b/docs/examples/Surface_MakeRasterDirectReleaseProc.cpp
@@ -0,0 +1,33 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8e6530b26ab4096a9a91cfaadda1c568
+REG_FIDDLE(Surface_MakeRasterDirectReleaseProc, 256, 256, true, 0) {
+static void release_direct_surface_storage(void* pixels, void* context) {
+    if (pixels == context) {
+        SkDebugf("expected release context\n");
+    }
+    sk_free(pixels);
+}
+
+void draw(SkCanvas* ) {
+    SkImageInfo info = SkImageInfo::MakeN32Premul(3, 3);
+    const size_t rowBytes = info.minRowBytes();
+    void* pixels = sk_malloc_throw(info.computeByteSize(rowBytes));
+    sk_sp<SkSurface> surface(SkSurface::MakeRasterDirectReleaseProc(info, pixels, rowBytes,
+            release_direct_surface_storage, pixels));
+    SkCanvas* canvas = surface->getCanvas();
+    canvas->clear(SK_ColorWHITE);
+    SkPMColor* colorPtr = (SkPMColor*) pixels;
+    SkPMColor pmWhite = colorPtr[0];
+    SkPaint paint;
+    canvas->drawPoint(1, 1, paint);
+    canvas->flush();  // ensure that point was drawn
+    for (int y = 0; y < info.height(); ++y) {
+        for (int x = 0; x < info.width(); ++x) {
+            SkDebugf("%c", *colorPtr++ == pmWhite ? '-' : 'x');
+        }
+        SkDebugf("\n");
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Surface_MakeRasterN32Premul.cpp b/docs/examples/Surface_MakeRasterN32Premul.cpp
new file mode 100644
index 0000000..9361cf1
--- /dev/null
+++ b/docs/examples/Surface_MakeRasterN32Premul.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b932a2bd68455fb0af2e7a1ed19e36b3
+REG_FIDDLE(Surface_MakeRasterN32Premul, 256, 256, true, 0) {
+void draw(SkCanvas* ) {
+    sk_sp<SkSurface> surface(SkSurface::MakeRasterN32Premul(3, 3));
+    SkCanvas* canvas = surface->getCanvas();
+    canvas->clear(SK_ColorWHITE);
+    SkPixmap pixmap;
+    if (surface->peekPixels(&pixmap)) {
+        const uint32_t* colorPtr = pixmap.addr32();
+        SkPMColor pmWhite = colorPtr[0];
+        SkPaint paint;
+        canvas->drawPoint(1, 1, paint);
+        canvas->flush();  // ensure that point was drawn
+        for (int y = 0; y < surface->height(); ++y) {
+            for (int x = 0; x < surface->width(); ++x) {
+                SkDebugf("%c", colorPtr[x] == pmWhite ? '-' : 'x');
+            }
+            colorPtr += surface->width();
+            SkDebugf("\n");
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Surface_MakeRaster_2.cpp b/docs/examples/Surface_MakeRaster_2.cpp
new file mode 100644
index 0000000..704dc04
--- /dev/null
+++ b/docs/examples/Surface_MakeRaster_2.cpp
@@ -0,0 +1,27 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c6197d204ef9e4ccfb583242651fb2a7
+REG_FIDDLE(Surface_MakeRaster_2, 256, 256, true, 0) {
+void draw(SkCanvas* ) {
+    SkImageInfo info = SkImageInfo::MakeN32Premul(3, 3);
+    sk_sp<SkSurface> surface(SkSurface::MakeRaster(info));
+    SkCanvas* canvas = surface->getCanvas();
+    canvas->clear(SK_ColorWHITE);
+    SkPixmap pixmap;
+    if (surface->peekPixels(&pixmap)) {
+        const uint32_t* colorPtr = pixmap.addr32();
+        SkPMColor pmWhite = colorPtr[0];
+        SkPaint paint;
+        canvas->drawPoint(1, 1, paint);
+        canvas->flush();  // ensure that point was drawn
+        for (int y = 0; y < info.height(); ++y) {
+            for (int x = 0; x < info.width(); ++x) {
+                SkDebugf("%c", colorPtr[x] == pmWhite ? '-' : 'x');
+            }
+            colorPtr += info.width();
+            SkDebugf("\n");
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Surface_MakeRenderTarget.cpp b/docs/examples/Surface_MakeRenderTarget.cpp
new file mode 100644
index 0000000..0404e63
--- /dev/null
+++ b/docs/examples/Surface_MakeRenderTarget.cpp
@@ -0,0 +1,28 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=67b6609471a3f1ed0f4b1657004cdecb
+REG_FIDDLE(Surface_MakeRenderTarget, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(32);
+    GrContext* context = canvas->getGrContext();
+    if (!context) {
+         canvas->drawString("GPU only!", 20, 40, paint);
+         return;
+    }
+    SkImageInfo info = SkImageInfo::MakeN32(256, 64, kOpaque_SkAlphaType);
+    for (auto surfaceOrigin : { kTopLeft_GrSurfaceOrigin, kBottomLeft_GrSurfaceOrigin } ) {
+        auto gpuSurface(SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 0,
+               surfaceOrigin, nullptr));
+        auto surfaceCanvas = gpuSurface->getCanvas();
+        surfaceCanvas->clear(SK_ColorWHITE);
+        surfaceCanvas->drawString("GPU rocks!", 20, 40, paint);
+        sk_sp<SkImage> image(gpuSurface->makeImageSnapshot());
+        canvas->drawImage(image, 0, 0);
+       canvas->translate(0, 128);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Surface_MakeRenderTarget_2.cpp b/docs/examples/Surface_MakeRenderTarget_2.cpp
new file mode 100644
index 0000000..a200639
--- /dev/null
+++ b/docs/examples/Surface_MakeRenderTarget_2.cpp
@@ -0,0 +1,36 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=640321e8ecfb3f9329f3bc6e1f02485f
+REG_FIDDLE(Surface_MakeRenderTarget_2, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    auto test_draw = [](SkCanvas* surfaceCanvas) -> void {
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setLCDRenderText(true);
+        paint.setColor(0xFFBBBBBB);
+        surfaceCanvas->drawRect(SkRect::MakeWH(128, 64), paint);
+        paint.setColor(SK_ColorWHITE);
+        paint.setTextSize(32);
+        surfaceCanvas->drawString("Pest", 0, 25, paint);
+    };
+    GrContext* context = canvas->getGrContext();
+    SkImageInfo info = SkImageInfo::MakeN32(128, 64, kOpaque_SkAlphaType);
+    int y = 0;
+    for (auto geometry : { kRGB_H_SkPixelGeometry, kBGR_H_SkPixelGeometry,
+                           kRGB_V_SkPixelGeometry, kBGR_V_SkPixelGeometry } ) {
+        SkSurfaceProps props(0, geometry);
+        sk_sp<SkSurface> surface = context ? SkSurface::MakeRenderTarget(
+                context, SkBudgeted::kNo, info, 0, &props) : SkSurface::MakeRaster(info, &props);
+        test_draw(surface->getCanvas());
+        surface->draw(canvas, 0, y, nullptr);
+        sk_sp<SkImage> image(surface->makeImageSnapshot());
+        SkAutoCanvasRestore acr(canvas, true);
+        canvas->scale(8, 8);
+        canvas->drawImage(image, 12, y / 8);
+        y += 64;
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Surface_MakeRenderTarget_3.cpp b/docs/examples/Surface_MakeRenderTarget_3.cpp
new file mode 100644
index 0000000..e6096c7
--- /dev/null
+++ b/docs/examples/Surface_MakeRenderTarget_3.cpp
@@ -0,0 +1,24 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=5c7629c15e9ac93f098335e72560fa2e
+REG_FIDDLE(Surface_MakeRenderTarget_3, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(32);
+    GrContext* context = canvas->getGrContext();
+    if (!context) {
+         canvas->drawString("GPU only!", 20, 40, paint);
+         return;
+    }
+    SkImageInfo info = SkImageInfo::MakeN32(256, 64, kOpaque_SkAlphaType);
+    auto gpuSurface(SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info));
+    auto surfaceCanvas = gpuSurface->getCanvas();
+    surfaceCanvas->clear(SK_ColorWHITE);
+    surfaceCanvas->drawString("GPU rocks!", 20, 40, paint);
+    sk_sp<SkImage> image(gpuSurface->makeImageSnapshot());
+    canvas->drawImage(image, 0, 0);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Surface_characterize.cpp b/docs/examples/Surface_characterize.cpp
new file mode 100644
index 0000000..5b9a267
--- /dev/null
+++ b/docs/examples/Surface_characterize.cpp
@@ -0,0 +1,33 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6de6f3ef699a72ff26da1b26b23a3316
+REG_FIDDLE(Surface_characterize, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(32);
+    GrContext* context = canvas->getGrContext();
+    if (!context) {
+         canvas->drawString("GPU only!", 20, 40, paint);
+         return;
+    }
+    sk_sp<SkSurface> gpuSurface = SkSurface::MakeRenderTarget(
+            context, SkBudgeted::kYes, SkImageInfo::MakeN32Premul(64, 64));
+    SkSurfaceCharacterization characterization;
+    if (!gpuSurface->characterize(&characterization)) {
+         canvas->drawString("characterization unsupported", 20, 40, paint);
+         return;
+    }
+    // start of threadable work
+    SkDeferredDisplayListRecorder recorder(characterization);
+    SkCanvas* subCanvas = recorder.getCanvas();
+    subCanvas->clear(SK_ColorGREEN);
+    std::unique_ptr<SkDeferredDisplayList> displayList = recorder.detach();
+    // end of threadable work
+    gpuSurface->draw(displayList.get());
+    sk_sp<SkImage> img = gpuSurface->makeImageSnapshot();
+    canvas->drawImage(std::move(img), 0, 0);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Surface_draw.cpp b/docs/examples/Surface_draw.cpp
new file mode 100644
index 0000000..ce6dd8f
--- /dev/null
+++ b/docs/examples/Surface_draw.cpp
@@ -0,0 +1,19 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=0de693f4d8dd898a60be8cfba23952be
+REG_FIDDLE(Surface_draw, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    sk_sp<SkSurface> big(SkSurface::MakeRasterN32Premul(64, 64));
+    sk_sp<SkSurface> lil(big->makeSurface(SkImageInfo::MakeN32(32, 32, kPremul_SkAlphaType)));
+    big->getCanvas()->clear(SK_ColorRED);
+    lil->getCanvas()->clear(SK_ColorBLACK);
+    lil->draw(big->getCanvas(), 16, 16, nullptr);
+    SkPixmap pixmap;
+    if (big->peekPixels(&pixmap)) {
+        SkBitmap bigBits;
+        bigBits.installPixels(pixmap);
+        canvas->drawBitmap(bigBits, 0, 0);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Surface_draw_2.cpp b/docs/examples/Surface_draw_2.cpp
new file mode 100644
index 0000000..5a2bb18
--- /dev/null
+++ b/docs/examples/Surface_draw_2.cpp
@@ -0,0 +1,27 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=46d9bacf593deaaeabd74ff42f2571a0
+REG_FIDDLE(Surface_draw_2, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTextSize(16);
+    sk_sp<SkSurface> gpuSurface = SkSurface::MakeRasterN32Premul(64, 64);
+    SkSurfaceCharacterization characterization;
+    if (!gpuSurface->characterize(&characterization)) {
+         canvas->drawString("characterization unsupported", 20, 40, paint);
+         return;
+    }
+    // start of threadable work
+    SkDeferredDisplayListRecorder recorder(characterization);
+    SkCanvas* subCanvas = recorder.getCanvas();
+    subCanvas->clear(SK_ColorGREEN);
+    std::unique_ptr<SkDeferredDisplayList> displayList = recorder.detach();
+    // end of threadable work
+    gpuSurface->draw(displayList.get());
+    sk_sp<SkImage> img = gpuSurface->makeImageSnapshot();
+    canvas->drawImage(std::move(img), 0, 0);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Surface_getCanvas.cpp b/docs/examples/Surface_getCanvas.cpp
new file mode 100644
index 0000000..65b8cc4
--- /dev/null
+++ b/docs/examples/Surface_getCanvas.cpp
@@ -0,0 +1,17 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=33d0c5ad5a4810e533ae1010e29f8b75
+REG_FIDDLE(Surface_getCanvas, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    sk_sp<SkSurface> surface(SkSurface::MakeRasterN32Premul(64, 64));
+    SkCanvas* surfaceCanvas = surface->getCanvas();
+    surfaceCanvas->clear(SK_ColorBLUE);
+    SkPaint paint;
+    paint.setTextSize(40);
+    surfaceCanvas->drawString("\xF0\x9F\x98\x81", 12, 45, paint);
+    surface->draw(canvas, 0, 0, nullptr);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Surface_height.cpp b/docs/examples/Surface_height.cpp
new file mode 100644
index 0000000..5fed7b3
--- /dev/null
+++ b/docs/examples/Surface_height.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=20571cc23e3146deaa09046b64cc0aef
+REG_FIDDLE(Surface_height, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int width = 37;
+    const int height = 1000;
+    auto surf = SkSurface::MakeNull(width, height);
+    auto nullCanvas = surf->getCanvas();
+    SkDebugf("surface height=%d  canvas height=%d\n", surf->height(),
+             nullCanvas->getBaseLayerSize().fHeight);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Surface_makeImageSnapshot.cpp b/docs/examples/Surface_makeImageSnapshot.cpp
new file mode 100644
index 0000000..5b1a31d
--- /dev/null
+++ b/docs/examples/Surface_makeImageSnapshot.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=46f1fa0d95e590a64bed0140407ce5f7
+REG_FIDDLE(Surface_makeImageSnapshot, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    sk_sp<SkSurface> big(SkSurface::MakeRasterN32Premul(64, 64));
+    sk_sp<SkSurface> lil(big->makeSurface(SkImageInfo::MakeN32(32, 32, kPremul_SkAlphaType)));
+    big->getCanvas()->clear(SK_ColorRED);
+    lil->getCanvas()->clear(SK_ColorBLACK);
+    sk_sp<SkImage> early(big->makeImageSnapshot());
+    lil->draw(big->getCanvas(), 16, 16, nullptr);
+    sk_sp<SkImage> later(big->makeImageSnapshot());
+    canvas->drawImage(early, 0, 0);
+    canvas->drawImage(later, 128, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Surface_makeImageSnapshot_2.cpp b/docs/examples/Surface_makeImageSnapshot_2.cpp
new file mode 100644
index 0000000..e762a4a
--- /dev/null
+++ b/docs/examples/Surface_makeImageSnapshot_2.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=b18b8ab693b09eb70a1d22ab63790cc7
+REG_FIDDLE(Surface_makeImageSnapshot_2, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    sk_sp<SkSurface> big(SkSurface::MakeRasterN32Premul(64, 64));
+    sk_sp<SkSurface> lil(big->makeSurface(SkImageInfo::MakeN32(32, 32, kPremul_SkAlphaType)));
+    big->getCanvas()->clear(SK_ColorRED);
+    lil->getCanvas()->clear(SK_ColorBLACK);
+    sk_sp<SkImage> early(big->makeImageSnapshot());
+    lil->draw(big->getCanvas(), 16, 16, nullptr);
+    sk_sp<SkImage> later(big->makeImageSnapshot({0, 0, 16, 16}));
+    canvas->drawImage(early, 0, 0);
+    canvas->drawImage(later, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Surface_makeSurface.cpp b/docs/examples/Surface_makeSurface.cpp
new file mode 100644
index 0000000..f60a0f6
--- /dev/null
+++ b/docs/examples/Surface_makeSurface.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a9889b519a26896b900da0444e423c61
+REG_FIDDLE(Surface_makeSurface, 256, 96, false, 0) {
+void draw(SkCanvas* canvas) {
+    sk_sp<SkSurface> big(SkSurface::MakeRasterN32Premul(64, 64));
+    sk_sp<SkSurface> lil(big->makeSurface(SkImageInfo::MakeN32(32, 32, kPremul_SkAlphaType)));
+    big->getCanvas()->clear(SK_ColorRED);
+    lil->getCanvas()->clear(SK_ColorBLACK);
+    SkPixmap pixmap;
+    if (big->peekPixels(&pixmap)) {
+        SkBitmap bigBits;
+        bigBits.installPixels(pixmap);
+        canvas->drawBitmap(bigBits, 0, 0);
+    }
+    if (lil->peekPixels(&pixmap)) {
+        SkBitmap lilBits;
+        lilBits.installPixels(pixmap);
+        canvas->drawBitmap(lilBits, 64, 64);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Surface_notifyContentWillChange.cpp b/docs/examples/Surface_notifyContentWillChange.cpp
new file mode 100644
index 0000000..c4ff78e
--- /dev/null
+++ b/docs/examples/Surface_notifyContentWillChange.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=be9574c4a14f891e1abb4ec2b1e51d6c
+REG_FIDDLE(Surface_notifyContentWillChange, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+    for (int i = 0; i < 3; ++i) {
+        SkDebugf("surface generationID: %d\n", surface->generationID());
+        if (0 == i) {
+            surface->getCanvas()->drawColor(SK_ColorBLACK);
+        } else {
+            surface->notifyContentWillChange(SkSurface::kDiscard_ContentChangeMode);
+        }
+    }
+}
+}  // END FIDDLE
diff --git a/docs/examples/Surface_peekPixels.cpp b/docs/examples/Surface_peekPixels.cpp
new file mode 100644
index 0000000..a771339
--- /dev/null
+++ b/docs/examples/Surface_peekPixels.cpp
@@ -0,0 +1,22 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=8c6184f22cfe068f021704cf92a147a1
+REG_FIDDLE(Surface_peekPixels, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    sk_sp<SkSurface> surf(SkSurface::MakeRasterN32Premul(64, 64));
+    auto surfCanvas = surf->getCanvas();
+    surfCanvas->clear(SK_ColorRED);
+    SkPaint paint;
+    paint.setTextSize(40);
+    surfCanvas->drawString("&", 16, 48, paint);
+    SkPixmap pixmap;
+    if (surf->peekPixels(&pixmap)) {
+        SkBitmap surfBits;
+        surfBits.installPixels(pixmap);
+        canvas->drawBitmap(surfBits, 0, 0);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Surface_props.cpp b/docs/examples/Surface_props.cpp
new file mode 100644
index 0000000..799c8d9
--- /dev/null
+++ b/docs/examples/Surface_props.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=13cf9e7b2894ae6e98c1fd719040bf01
+REG_FIDDLE(Surface_props, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const char* names[] = { "Unknown", "RGB_H", "BGR_H", "RGB_V", "BGR_V" };
+    sk_sp<SkSurface> surf(SkSurface::MakeRasterN32Premul(64, 64));
+    SkDebugf("surf.props(): k%s_SkPixelGeometry\n", names[surf->props().pixelGeometry()]);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Surface_readPixels.cpp b/docs/examples/Surface_readPixels.cpp
new file mode 100644
index 0000000..0e876b0
--- /dev/null
+++ b/docs/examples/Surface_readPixels.cpp
@@ -0,0 +1,25 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=9f454fb93bca6482598d198b4121f0a6
+REG_FIDDLE(Surface_readPixels, 256, 32, false, 0) {
+void draw(SkCanvas* canvas) {
+    sk_sp<SkSurface> surf(SkSurface::MakeRasterN32Premul(64, 64));
+    auto surfCanvas = surf->getCanvas();
+    surfCanvas->clear(SK_ColorRED);
+    SkPaint paint;
+    paint.setTextSize(40);
+    surfCanvas->drawString("&", 0, 32, paint);
+    std::vector<SkPMColor> storage;
+    storage.resize(surf->width() * surf->height());
+    SkPixmap pixmap(SkImageInfo::MakeN32Premul(32, 32), &storage.front(),
+                    surf->width() * sizeof(storage[0]));
+    if (surf->readPixels(pixmap, 0, 0)) {
+        SkBitmap surfBits;
+        surfBits.installPixels(pixmap);
+        canvas->drawBitmap(surfBits, 0, 0);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Surface_readPixels_2.cpp b/docs/examples/Surface_readPixels_2.cpp
new file mode 100644
index 0000000..37ca8d3
--- /dev/null
+++ b/docs/examples/Surface_readPixels_2.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=484d60dab5d846bf28c7a4d48892324a
+REG_FIDDLE(Surface_readPixels_2, 256, 64, false, 0) {
+void draw(SkCanvas* canvas) {
+    sk_sp<SkSurface> surf(SkSurface::MakeRasterN32Premul(64, 64));
+    auto surfCanvas = surf->getCanvas();
+    surfCanvas->clear(SK_ColorRED);
+    SkPaint paint;
+    surfCanvas->drawOval({4, 8, 58, 54}, paint);
+    SkImageInfo info = SkImageInfo::Make(64, 64, kBGRA_8888_SkColorType, kPremul_SkAlphaType);
+    sk_sp<SkData> data(SkData::MakeUninitialized(info.minRowBytes() * info.height()));
+    sk_bzero(data->writable_data(), info.minRowBytes() * info.height());
+    for (int x : { 32, -32 } ) {
+        for (int y : { 32, -32 } ) {
+            surf->readPixels(info, data->writable_data(), info.minRowBytes(), x, y);
+        }
+    }
+    sk_sp<SkImage> image = SkImage::MakeRasterData(info, data, info.minRowBytes());
+    canvas->drawImage(image, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Surface_readPixels_3.cpp b/docs/examples/Surface_readPixels_3.cpp
new file mode 100644
index 0000000..f22f3fd
--- /dev/null
+++ b/docs/examples/Surface_readPixels_3.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=2d991a231e49d1de13eeb2ba9b440e01
+REG_FIDDLE(Surface_readPixels_3, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    sk_sp<SkSurface> surf(SkSurface::MakeRasterN32Premul(64, 64));
+    auto surfCanvas = surf->getCanvas();
+    surfCanvas->clear(SK_ColorGREEN);
+    SkPaint paint;
+    surfCanvas->drawOval({2, 10, 58, 54}, paint);
+    SkImageInfo info = SkImageInfo::Make(64, 64, kBGRA_8888_SkColorType, kPremul_SkAlphaType);
+    SkBitmap bitmap;
+    bitmap.setInfo(info);
+    bitmap.allocPixels();
+    for (int x : { 32, -32 } ) {
+        for (int y : { 32, -32 } ) {
+            surf->readPixels(bitmap, x, y);
+        }
+    }
+    canvas->drawBitmap(bitmap, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Surface_width.cpp b/docs/examples/Surface_width.cpp
new file mode 100644
index 0000000..442f3e1
--- /dev/null
+++ b/docs/examples/Surface_width.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=df066b56dd97c7c589fd2bb6a2539de8
+REG_FIDDLE(Surface_width, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    const int width = 37;
+    const int height = 1000;
+    auto surf = SkSurface::MakeNull(width, height);
+    auto nullCanvas = surf->getCanvas();
+    SkDebugf("surface width=%d  canvas width=%d\n", surf->width(),
+             nullCanvas->getBaseLayerSize().fWidth);
+}
+}  // END FIDDLE
diff --git a/docs/examples/Surface_writePixels.cpp b/docs/examples/Surface_writePixels.cpp
new file mode 100644
index 0000000..7bc325c
--- /dev/null
+++ b/docs/examples/Surface_writePixels.cpp
@@ -0,0 +1,22 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=760793bcf0ef193fa61ea03e6e8fc825
+REG_FIDDLE(Surface_writePixels, 256, 96, false, 4) {
+void draw(SkCanvas* canvas) {
+    sk_sp<SkSurface> surf(SkSurface::MakeRasterN32Premul(64, 64));
+    auto surfCanvas = surf->getCanvas();
+    surfCanvas->clear(SK_ColorRED);
+    SkPaint paint;
+    paint.setTextSize(40);
+    surfCanvas->drawString("&", 16, 40, paint);
+    SkPixmap pixmap;
+    if (surf->peekPixels(&pixmap)) {
+        surf->writePixels(pixmap, 25, 25);
+        sk_sp<SkImage> image(surf->makeImageSnapshot());
+        canvas->drawImage(image, 0, 0);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Surface_writePixels_2.cpp b/docs/examples/Surface_writePixels_2.cpp
new file mode 100644
index 0000000..e13297a
--- /dev/null
+++ b/docs/examples/Surface_writePixels_2.cpp
@@ -0,0 +1,14 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d77790dd3bc9f678fa4f582347fb8fba
+REG_FIDDLE(Surface_writePixels_2, 256, 96, false, 4) {
+void draw(SkCanvas* canvas) {
+    sk_sp<SkSurface> surf(SkSurface::MakeRasterN32Premul(64, 64));
+    auto surfCanvas = surf->getCanvas();
+    surfCanvas->clear(SK_ColorGREEN);
+    surf->writePixels(source, 25, 25);
+    sk_sp<SkImage> image(surf->makeImageSnapshot());
+    canvas->drawImage(image, 0, 0);
+}
+}  // END FIDDLE
diff --git a/docs/examples/TextBlobBuilder_allocRun.cpp b/docs/examples/TextBlobBuilder_allocRun.cpp
new file mode 100644
index 0000000..2cf581e
--- /dev/null
+++ b/docs/examples/TextBlobBuilder_allocRun.cpp
@@ -0,0 +1,17 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=f0e584aec20eaee7a5bfed62aa885eee
+REG_FIDDLE(TextBlobBuilder_allocRun, 256, 60, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkTextBlobBuilder builder;
+    SkFont font;
+    SkPaint paint;
+    const SkTextBlobBuilder::RunBuffer& run = builder.allocRun(font, 5, 20, 20);
+    paint.textToGlyphs("hello", 5, run.glyphs);
+    canvas->drawRect({20, 20, 30, 30}, paint);
+    canvas->drawTextBlob(builder.make(), 20, 20, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/TextBlobBuilder_allocRunPos.cpp b/docs/examples/TextBlobBuilder_allocRunPos.cpp
new file mode 100644
index 0000000..ca166d7
--- /dev/null
+++ b/docs/examples/TextBlobBuilder_allocRunPos.cpp
@@ -0,0 +1,18 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=da4fcb4a972b500996be9aff6c6c40e1
+REG_FIDDLE(TextBlobBuilder_allocRunPos, 256, 90, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkTextBlobBuilder builder;
+    SkPaint paint;
+    SkFont font;
+    const SkTextBlobBuilder::RunBuffer& run = builder.allocRunPos(font, 5);
+    paint.textToGlyphs("hello", 5, run.glyphs);
+    SkPoint positions[] = {{0, 0}, {10, 10}, {20, 20}, {40, 40}, {80, 80}};
+    memcpy(run.pos, positions, sizeof(positions));
+    canvas->drawTextBlob(builder.make(), 20, 20, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/TextBlobBuilder_allocRunPosH.cpp b/docs/examples/TextBlobBuilder_allocRunPosH.cpp
new file mode 100644
index 0000000..e73c2d9
--- /dev/null
+++ b/docs/examples/TextBlobBuilder_allocRunPosH.cpp
@@ -0,0 +1,18 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=c77ac50f506106fdfef94d20bc1a6934
+REG_FIDDLE(TextBlobBuilder_allocRunPosH, 256, 60, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkTextBlobBuilder builder;
+    SkPaint paint;
+    SkFont font;
+    const SkTextBlobBuilder::RunBuffer& run = builder.allocRunPosH(font, 5, 20);
+    paint.textToGlyphs("hello", 5, run.glyphs);
+    SkScalar positions[] = {0, 10, 20, 40, 80};
+    memcpy(run.pos, positions, sizeof(positions));
+    canvas->drawTextBlob(builder.make(), 20, 20, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/TextBlobBuilder_empty_constructor.cpp b/docs/examples/TextBlobBuilder_empty_constructor.cpp
new file mode 100644
index 0000000..f387a91
--- /dev/null
+++ b/docs/examples/TextBlobBuilder_empty_constructor.cpp
@@ -0,0 +1,11 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d9dbabfe24aad92ee3c8144513e90d81
+REG_FIDDLE(TextBlobBuilder_empty_constructor, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkTextBlobBuilder builder;
+    sk_sp<SkTextBlob> blob = builder.make();
+    SkDebugf("blob " "%s" " nullptr", blob == nullptr ? "equals" : "does not equal");
+}
+}  // END FIDDLE
diff --git a/docs/examples/TextBlobBuilder_make.cpp b/docs/examples/TextBlobBuilder_make.cpp
new file mode 100644
index 0000000..1f27cf0
--- /dev/null
+++ b/docs/examples/TextBlobBuilder_make.cpp
@@ -0,0 +1,21 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=34c37c0212cc0aef670d96945d08fe24
+REG_FIDDLE(TextBlobBuilder_make, 256, 256, true, 0) {
+void draw(SkCanvas* canvas) {
+    SkTextBlobBuilder builder;
+    sk_sp<SkTextBlob> blob = builder.make();
+    SkDebugf("blob " "%s" " nullptr\n", blob == nullptr ? "equals" : "does not equal");
+    SkPaint paint;
+    paint.setTextEncoding(kGlyphID_SkTextEncoding);
+    SkFont font;
+    paint.textToGlyphs("x", 1, builder.allocRun(font, 1, 20, 20).glyphs);
+    blob = builder.make();
+    SkDebugf("blob " "%s" " nullptr\n", blob == nullptr ? "equals" : "does not equal");
+    blob = builder.make();
+    SkDebugf("blob " "%s" " nullptr\n", blob == nullptr ? "equals" : "does not equal");
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/TextBlob_Deserialize.cpp b/docs/examples/TextBlob_Deserialize.cpp
new file mode 100644
index 0000000..72a4dda
--- /dev/null
+++ b/docs/examples/TextBlob_Deserialize.cpp
@@ -0,0 +1,22 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=68b6d0208eb0b4de67fc152381af7a58
+REG_FIDDLE(TextBlob_Deserialize, 256, 24, false, 0) {
+#include "SkSerialProcs.h"
+
+void draw(SkCanvas* canvas) {
+    SkFont blobFont;
+    blobFont.setSize(24);
+    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromText("Hello World!", 12, blobFont);
+    sk_sp<SkData> data = blob->serialize(SkSerialProcs());
+    uint16_t glyphs[6];
+    SkPaint blobPaint;
+    blobPaint.textToGlyphs("Hacker", 6, glyphs);
+    memcpy((char*)data->writable_data() + 0x54, glyphs, sizeof(glyphs));
+    sk_sp<SkTextBlob> copy = SkTextBlob::Deserialize(data->data(), data->size(), SkDeserialProcs());
+    canvas->drawTextBlob(copy, 20, 20, SkPaint());
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/TextBlob_MakeFromString.cpp b/docs/examples/TextBlob_MakeFromString.cpp
new file mode 100644
index 0000000..66ba3eb
--- /dev/null
+++ b/docs/examples/TextBlob_MakeFromString.cpp
@@ -0,0 +1,17 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=a5af182e793eed3f2bb3b0efc2cf4852
+REG_FIDDLE(TextBlob_MakeFromString, 256, 24, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkFont font;
+    font.setSize(24);
+    SkPaint canvasPaint;
+    canvasPaint.setColor(SK_ColorBLUE); // respected
+    canvasPaint.setTextSize(2); // ignored
+    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromString("Hello World", font);
+    canvas->drawTextBlob(blob, 20, 20, canvasPaint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/TextBlob_MakeFromText.cpp b/docs/examples/TextBlob_MakeFromText.cpp
new file mode 100644
index 0000000..51ebd7e
--- /dev/null
+++ b/docs/examples/TextBlob_MakeFromText.cpp
@@ -0,0 +1,17 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=bec2252bc36dc8fd023015629d60c405
+REG_FIDDLE(TextBlob_MakeFromText, 256, 24, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkFont font;
+    font.setSize(24);
+    SkPaint canvasPaint;
+    canvasPaint.setColor(SK_ColorBLUE); // respected
+    canvasPaint.setTextSize(2); // ignored
+    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromText("Hello World", 11, font);
+    canvas->drawTextBlob(blob, 20, 20, canvasPaint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/TextBlob_bounds.cpp b/docs/examples/TextBlob_bounds.cpp
new file mode 100644
index 0000000..5723a0e
--- /dev/null
+++ b/docs/examples/TextBlob_bounds.cpp
@@ -0,0 +1,34 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=fb8b2502bbe52d2029aecdf569dd9fdb
+REG_FIDDLE(TextBlob_bounds, 256, 70, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkTextBlobBuilder textBlobBuilder;
+    const char bunny[] = "/(^x^)\\";
+    const int len = sizeof(bunny) - 1;
+    uint16_t glyphs[len];
+    SkPaint paint;
+    paint.textToGlyphs(bunny, len, glyphs);
+    paint.setTextEncoding(kGlyphID_SkTextEncoding);
+    SkFont font;
+    int runs[] = { 3, 1, 3 };
+    SkPoint textPos = { 20, 50 };
+    int glyphIndex = 0;
+    for (auto runLen : runs) {
+        font.setSize(1 == runLen ? 20 : 50);
+        paint.setTextSize(1 == runLen ? 20 : 50);
+        const SkTextBlobBuilder::RunBuffer& run =
+                textBlobBuilder.allocRun(font, runLen, textPos.fX, textPos.fY);
+        memcpy(run.glyphs, &glyphs[glyphIndex], sizeof(glyphs[0]) * runLen);
+        textPos.fX += paint.measureText(&glyphs[glyphIndex], sizeof(glyphs[0]) * runLen, nullptr);
+        glyphIndex += runLen;
+    }
+    sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
+    canvas->drawTextBlob(blob.get(), 0, 0, paint);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawRect(blob->bounds(), paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/TextBlob_getIntercepts.cpp b/docs/examples/TextBlob_getIntercepts.cpp
new file mode 100644
index 0000000..293c58b
--- /dev/null
+++ b/docs/examples/TextBlob_getIntercepts.cpp
@@ -0,0 +1,35 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=e9d4eb8ece521b1329e7433d4b243fdf
+REG_FIDDLE(TextBlob_getIntercepts, 256, 143, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkFont font;
+    font.setSize(120);
+    SkPoint textPos = { 20, 110 };
+    int len = 3;
+    SkTextBlobBuilder textBlobBuilder;
+    const SkTextBlobBuilder::RunBuffer& run =
+            textBlobBuilder.allocRun(font, len, textPos.fX, textPos.fY);
+    run.glyphs[0] = 10;
+    run.glyphs[1] = 20;
+    run.glyphs[2] = 30;
+    sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
+    SkPaint paint;
+    SkScalar bounds[] = { 116, 134 };
+    int count = blob->getIntercepts(bounds, nullptr);
+    std::vector<SkScalar> intervals;
+    intervals.resize(count);
+    (void) paint.getTextBlobIntercepts(blob.get(), bounds, &intervals.front());
+    canvas->drawTextBlob(blob.get(), 0, 0, paint);
+    paint.setColor(0xFFFF7777);
+    SkScalar x = textPos.fX;
+    for (int i = 0; i < count; i+= 2) {
+        canvas->drawRect({x, bounds[0], intervals[i], bounds[1]}, paint);
+        x = intervals[i + 1];
+    }
+    canvas->drawRect({intervals[count - 1], bounds[0], 180, bounds[1]}, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/TextBlob_serialize.cpp b/docs/examples/TextBlob_serialize.cpp
new file mode 100644
index 0000000..c0b2d08
--- /dev/null
+++ b/docs/examples/TextBlob_serialize.cpp
@@ -0,0 +1,21 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=90ce8c327d407b1faac73baa2ebd0378
+REG_FIDDLE(TextBlob_serialize, 256, 64, false, 0) {
+#include "SkSerialProcs.h"
+
+void draw(SkCanvas* canvas) {
+    SkFont blobFont;
+    blobFont.setSize(24);
+    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromText("Hello World", 11, blobFont);
+    char storage[2048];
+    size_t used = blob->serialize(SkSerialProcs(), storage, sizeof(storage));
+    sk_sp<SkTextBlob> copy = SkTextBlob::Deserialize(storage, used, SkDeserialProcs());
+    canvas->drawTextBlob(copy, 20, 20, SkPaint());
+    std::string usage = "size=" + std::to_string(sizeof(storage)) + " used=" + std::to_string(used);
+    canvas->drawString(usage.c_str(), 20, 40, SkPaint());
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/TextBlob_serialize_2.cpp b/docs/examples/TextBlob_serialize_2.cpp
new file mode 100644
index 0000000..459a06a
--- /dev/null
+++ b/docs/examples/TextBlob_serialize_2.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=464201a828f7e94fc01cd57facfcd2f4
+REG_FIDDLE(TextBlob_serialize_2, 256, 24, false, 0) {
+#include "SkSerialProcs.h"
+
+void draw(SkCanvas* canvas) {
+    SkFont blobFont;
+    blobFont.setSize(24);
+    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromText("Hello World", 11, blobFont);
+    sk_sp<SkData> data = blob->serialize(SkSerialProcs());
+    sk_sp<SkTextBlob> copy = SkTextBlob::Deserialize(data->data(), data->size(), SkDeserialProcs());
+    canvas->drawTextBlob(copy, 20, 20, SkPaint());
+}
+}  // END FIDDLE
diff --git a/docs/examples/TextBlob_uniqueID.cpp b/docs/examples/TextBlob_uniqueID.cpp
new file mode 100644
index 0000000..7489067
--- /dev/null
+++ b/docs/examples/TextBlob_uniqueID.cpp
@@ -0,0 +1,40 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=6e12cceca981ddabc0fc18c380543f34
+REG_FIDDLE(TextBlob_uniqueID, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    for (int index = 0; index < 2; ++index) {
+        SkTextBlobBuilder textBlobBuilder;
+        const char bunny[] = "/(^x^)\\";
+        const int len = sizeof(bunny) - 1;
+        uint16_t glyphs[len];
+        SkPaint paint;
+        paint.textToGlyphs(bunny, len, glyphs);
+        paint.setTextEncoding(kGlyphID_SkTextEncoding);
+        paint.setTextScaleX(0.5);
+        SkFont font;
+        font.setScaleX(0.5);
+        int runs[] = { 3, 1, 3 };
+        SkPoint textPos = { 20, 50 };
+        int glyphIndex = 0;
+        for (auto runLen : runs) {
+            font.setSize(1 == runLen ? 20 : 50);
+            paint.setTextSize(1 == runLen ? 20 : 50);
+            const SkTextBlobBuilder::RunBuffer& run =
+                    textBlobBuilder.allocRun(font, runLen, textPos.fX, textPos.fY);
+            memcpy(run.glyphs, &glyphs[glyphIndex], sizeof(glyphs[0]) * runLen);
+            textPos.fX += paint.measureText(&glyphs[glyphIndex], sizeof(glyphs[0]) * runLen, nullptr);
+            glyphIndex += runLen;
+        }
+        sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
+        paint.reset();
+        canvas->drawTextBlob(blob.get(), 0, 0, paint);
+        std::string id = "unique ID:" + std::to_string(blob->uniqueID());
+        canvas->drawString(id.c_str(), 30, blob->bounds().fBottom + 15, paint);
+        canvas->translate(blob->bounds().fRight + 10, 0);
+    }
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Text_Encoding.cpp b/docs/examples/Text_Encoding.cpp
new file mode 100644
index 0000000..ef4b438
--- /dev/null
+++ b/docs/examples/Text_Encoding.cpp
@@ -0,0 +1,24 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=767fa4e7b6300e16a419f9881f0f9d3d
+REG_FIDDLE(Text_Encoding, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    const char hello8[] = "Hello" "\xE2" "\x98" "\xBA";
+    const uint16_t hello16[] = { 'H', 'e', 'l', 'l', 'o', 0x263A };
+    const uint32_t hello32[] = { 'H', 'e', 'l', 'l', 'o', 0x263A };
+    paint.setTextSize(24);
+    canvas->drawText(hello8, sizeof(hello8) - 1, 10, 30, paint);
+    paint.setTextEncoding(kUTF16_SkTextEncoding);
+    canvas->drawText(hello16, sizeof(hello16), 10, 60, paint);
+    paint.setTextEncoding(kUTF32_SkTextEncoding);
+    canvas->drawText(hello32, sizeof(hello32), 10, 90, paint);
+    uint16_t glyphs[SK_ARRAY_COUNT(hello32)];
+    paint.textToGlyphs(hello32, sizeof(hello32), glyphs);
+    paint.setTextEncoding(kGlyphID_SkTextEncoding);
+    canvas->drawText(glyphs, sizeof(glyphs), 10, 120, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Text_Scale_X.cpp b/docs/examples/Text_Scale_X.cpp
new file mode 100644
index 0000000..b103bc4
--- /dev/null
+++ b/docs/examples/Text_Scale_X.cpp
@@ -0,0 +1,19 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=d13d787c1e36f515319fc998411c1d91
+REG_FIDDLE(Text_Scale_X, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(24);
+    paint.setTextScaleX(.8f);
+    canvas->drawString("narrow", 10, 20, paint);
+    paint.setTextScaleX(1);
+    canvas->drawString("normal", 10, 60, paint);
+    paint.setTextScaleX(1.2f);
+    canvas->drawString("wide", 10, 100, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Text_Size.cpp b/docs/examples/Text_Size.cpp
new file mode 100644
index 0000000..4fa50ac
--- /dev/null
+++ b/docs/examples/Text_Size.cpp
@@ -0,0 +1,16 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=91c9a3e498bb9412e4522a95d076ed5f
+REG_FIDDLE(Text_Size, 256, 135, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    canvas->drawString("12 point", 10, 20, paint);
+    paint.setTextSize(24);
+    canvas->drawString("24 point", 10, 60, paint);
+    paint.setTextSize(48);
+    canvas->drawString("48 point", 10, 120, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Text_Skew_X.cpp b/docs/examples/Text_Skew_X.cpp
new file mode 100644
index 0000000..a16ab1a
--- /dev/null
+++ b/docs/examples/Text_Skew_X.cpp
@@ -0,0 +1,19 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=aff208b0aab265f273045b27e683c17c
+REG_FIDDLE(Text_Skew_X, 256, 128, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTextSize(24);
+    paint.setTextSkewX(-.25f);
+    canvas->drawString("right-leaning", 10, 100, paint);
+    paint.setTextSkewX(0);
+    canvas->drawString("normal", 10, 60, paint);
+    paint.setTextSkewX(.25f);
+    canvas->drawString("left-leaning", 10, 20, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Typeface_Methods.cpp b/docs/examples/Typeface_Methods.cpp
new file mode 100644
index 0000000..7ec9530
--- /dev/null
+++ b/docs/examples/Typeface_Methods.cpp
@@ -0,0 +1,18 @@
+#if 0  // Disabled until updated to use current API.
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=1a7a5062725139760962582f599f1b97
+REG_FIDDLE(Typeface_Methods, 256, 100, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setTypeface(SkTypeface::MakeFromName(nullptr, SkFontStyle()));
+    paint.setAntiAlias(true);
+    paint.setTextSize(36);
+    canvas->drawString("A Big Hello!", 10, 40, paint);
+    paint.setTypeface(nullptr);
+    paint.setFakeBoldText(true);
+    canvas->drawString("A Big Hello!", 10, 80, paint);
+}
+}  // END FIDDLE
+#endif  // Disabled until updated to use current API.
diff --git a/docs/examples/Xor.cpp b/docs/examples/Xor.cpp
new file mode 100644
index 0000000..9a9af87
--- /dev/null
+++ b/docs/examples/Xor.cpp
@@ -0,0 +1,18 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "fiddle/examples.h"
+// HASH=29db2c7493d9098b8a086ddbe30dd6d6
+REG_FIDDLE(Xor, 256, 256, false, 0) {
+void draw(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setBlendMode(SkBlendMode::kXor);
+    for (auto color : { SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN } ) {
+        SkColor colors[] = { color, SkColorSetA(color, 192), SkColorSetA(color, 128),
+                             SkColorSetA(color, 0) };
+        paint.setShader(SkGradientShader::MakeRadial({ 64, 64}, 100,
+                colors, nullptr, SK_ARRAY_COUNT(colors), SkTileMode::kClamp));
+        canvas->drawCircle(64, 64, 100, paint);
+        canvas->translate(64, 64);
+    }
+}
+}  // END FIDDLE
diff --git a/docs/illustrations.bmh b/docs/illustrations.bmh
deleted file mode 100644
index d82d250..0000000
--- a/docs/illustrations.bmh
+++ /dev/null
@@ -1,901 +0,0 @@
-#Topic Illustrations
-
-#Subtopic Image_Info_Color_Type_RGB_565
-#Example
-#Width 415
-#Height 250
-#Function
-###$
-#include "SkTextUtils.h"
-$$$#
-##
-
-void draw(SkCanvas* canvas) {
-    canvas->scale(1.25f, 1.25f);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkFont font(nullptr, 10);
-    SkTextUtils::DrawString(canvas, "16-bit word", 5 + 20 * 8, 20, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 1.5f, 137, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 6.5f, 187, font, paint, SkTextUtils::kCenter_Align);
-    auto drawBoxText = [=](SkScalar e[], const char* s[], int count, int n, SkScalar yPos) -> void {
-        SkPaint p(paint);
-        p.setColor(SK_ColorRED);
-        SkScalar xPos = 15;
-        int width = n % 32 + 1;
-        int lastN = n > 32 ? 32 : 0;
-        for (; n >= lastN; --n) {
-            for (int i = 0; i <= count; ++i) {
-                int a = width - e[i];
-                if (a == n || a == n + 1 || a == n - 32 || a == n - 31) {
-                    char num[3] = {(char) ('0' + n / 10), (char) ('0' + n % 10), '\0'};
-                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, font, p, SkTextUtils::kCenter_Align);
-                    break;
-                }
-            }
-            xPos += 20;
-        }
-        p.setColor(SK_ColorBLACK);
-        for (int i = 0; i < count; ++i) {
-            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, font, p, SkTextUtils::kCenter_Align);
-        }
-        p.setStyle(SkPaint::kStroke_Style);
-        for (int i = 0; i <= count; ++i) {
-            canvas->drawLine(5 + e[i] * 20, yPos, 5 + e[i] * 20, yPos + 15, p);
-        }
-        for (int i = 0; i < 2; ++i) {
-            canvas->drawLine(5 + e[0] * 20, yPos + i * 15, 5 + e[count] * 20, yPos + i * 15, p);
-        }
-    };
-    SkScalar edges[] = { 0, 5, 11, 16,
-                         0, 3, 8,
-                         0, 5, 8 };
-    const char* labels[] = { "red", "green", "blue" };
-    drawBoxText(&edges[0], &labels[0], 3, 15, 45);
-    drawBoxText(&edges[4], &labels[1], 2, 7, 110);
-    drawBoxText(&edges[7], &labels[0], 2, 7, 160);
-}
-##
-##
-
-#Subtopic Image_Info_Color_Type_ARGB_4444
-#Example
-#Width 415
-#Height 250
-#Function
-###$
-#include "SkTextUtils.h"
-$$$#
-##
-
-void draw(SkCanvas* canvas) {
-    canvas->scale(1.25f, 1.25f);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkFont font(nullptr, 10);
-    SkTextUtils::DrawString(canvas, "16-bit word", 5 + 20 * 8, 20, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, font, paint, SkTextUtils::kCenter_Align);
-    auto drawBoxText = [=](SkScalar e[], const char* s[], int count, int n, SkScalar yPos) -> void {
-        SkPaint p(paint);
-        p.setColor(SK_ColorRED);
-        SkScalar xPos = 15;
-        int width = n % 32 + 1;
-        int lastN = n > 32 ? 32 : 0;
-        for (; n >= lastN; --n) {
-            for (int i = 0; i <= count; ++i) {
-                int a = width - e[i];
-                if (a == n || a == n + 1 || a == n - 32 || a == n - 31) {
-                    char num[3] = {(char) ('0' + n / 10), (char) ('0' + n % 10), '\0'};
-                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, font, p, SkTextUtils::kCenter_Align);
-                    break;
-                }
-            }
-            xPos += 20;
-        }
-        p.setColor(SK_ColorBLACK);
-        for (int i = 0; i < count; ++i) {
-            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, font, p, SkTextUtils::kCenter_Align);
-        }
-        p.setStyle(SkPaint::kStroke_Style);
-        for (int i = 0; i <= count; ++i) {
-            canvas->drawLine(5 + e[i] * 20, yPos, 5 + e[i] * 20, yPos + 15, p);
-        }
-        for (int i = 0; i < 2; ++i) {
-            canvas->drawLine(5 + e[0] * 20, yPos + i * 15, 5 + e[count] * 20, yPos + i * 15, p);
-        }
-    };
-    SkScalar edges[] = { 0, 4, 8, 12, 16 };
-    const char* labels[] = { "red", "green", "blue", "alpha" };
-    drawBoxText(&edges[0], &labels[0], 4, 15, 45);
-    drawBoxText(&edges[0], &labels[2], 2, 7, 110);
-    drawBoxText(&edges[0], &labels[0], 2, 7, 160);
-}
-##
-##
-
-#Subtopic Image_Info_Color_Type_RGBA_8888
-#Example
-#Width 812
-#Height 365
-#Function
-###$
-#include "SkTextUtils.h"
-$$$#
-##
-
-void draw(SkCanvas* canvas) {
-    canvas->scale(1.25f, 1.25f);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkFont font(nullptr, 10);
-    SkTextUtils::DrawString(canvas, "32-bit word", 5 + 20 * 16, 20, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, font, paint, SkTextUtils::kCenter_Align);
-    auto drawBoxText = [=](SkScalar e[], const char* s[], int count, int n, SkScalar yPos) -> void {
-        SkPaint p(paint);
-        p.setColor(SK_ColorRED);
-        SkScalar xPos = 15;
-        int width = n % 32 + 1;
-        int lastN = n > 32 ? 32 : 0;
-        for (; n >= lastN; --n) {
-            for (int i = 0; i <= count; ++i) {
-                int a = width - e[i];
-                if (a == n || a == n + 1 || a == n - 32 || a == n - 31) {
-                    char num[3] = {(char) ('0' + n / 10), (char) ('0' + n % 10), '\0'};
-                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, font, p, SkTextUtils::kCenter_Align);
-                    break;
-                }
-            }
-            xPos += 20;
-        }
-        p.setColor(SK_ColorBLACK);
-        for (int i = 0; i < count; ++i) {
-            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, font, p, SkTextUtils::kCenter_Align);
-        }
-        p.setStyle(SkPaint::kStroke_Style);
-        for (int i = 0; i <= count; ++i) {
-            canvas->drawLine(5 + e[i] * 20, yPos, 5 + e[i] * 20, yPos + 15, p);
-        }
-        for (int i = 0; i < 2; ++i) {
-            canvas->drawLine(5 + e[0] * 20, yPos + i * 15, 5 + e[count] * 20, yPos + i * 15, p);
-        }
-    };
-    SkScalar edges[] = { 0, 8, 16, 24, 32 };
-    const char* labels[] = { "alpha", "blue", "green", "red" };
-    drawBoxText(edges, &labels[0], 4, 31, 45);
-    drawBoxText(edges, &labels[3], 1, 7, 110);
-    drawBoxText(edges, &labels[2], 1, 7, 160);
-    drawBoxText(edges, &labels[1], 1, 7, 210);
-    drawBoxText(edges, &labels[0], 1, 7, 260);
-}
-##
-##
-
-#Subtopic Image_Info_Color_Type_RGB_888
-#Example
-#Width 812
-#Height 365
-#Function
-###$
-#include "SkTextUtils.h"
-$$$#
-##
-
-void draw(SkCanvas* canvas) {
-    canvas->scale(1.25f, 1.25f);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkFont font(nullptr, 10);
-    SkTextUtils::DrawString(canvas, "32-bit word", 5 + 20 * 16, 20, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, font, paint, SkTextUtils::kCenter_Align);
-    auto drawBoxText = [=](SkScalar e[], const char* s[], int count, int n, SkScalar yPos) -> void {
-        SkPaint p(paint);
-        p.setColor(SK_ColorRED);
-        SkScalar xPos = 15;
-        int width = n % 32 + 1;
-        int lastN = n > 32 ? 32 : 0;
-        for (; n >= lastN; --n) {
-            for (int i = 0; i <= count; ++i) {
-                int a = width - e[i];
-                if (a == n || a == n + 1 || a == n - 32 || a == n - 31) {
-                    char num[3] = {(char) ('0' + n / 10), (char) ('0' + n % 10), '\0'};
-                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, font, p, SkTextUtils::kCenter_Align);
-                    break;
-                }
-            }
-            xPos += 20;
-        }
-        p.setColor(SK_ColorBLACK);
-        for (int i = 0; i < count; ++i) {
-            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, font, p, SkTextUtils::kCenter_Align);
-        }
-        p.setStyle(SkPaint::kStroke_Style);
-        for (int i = 0; i <= count; ++i) {
-            canvas->drawLine(5 + e[i] * 20, yPos, 5 + e[i] * 20, yPos + 15, p);
-        }
-        for (int i = 0; i < 2; ++i) {
-            canvas->drawLine(5 + e[0] * 20, yPos + i * 15, 5 + e[count] * 20, yPos + i * 15, p);
-        }
-    };
-    SkScalar edges[] = { 0, 8, 16, 24, 32 };
-    const char* labels[] = { "(unused)", "blue", "green", "red" };
-    drawBoxText(edges, &labels[0], 4, 31, 45);
-    drawBoxText(edges, &labels[3], 1, 7, 110);
-    drawBoxText(edges, &labels[2], 1, 7, 160);
-    drawBoxText(edges, &labels[1], 1, 7, 210);
-    drawBoxText(edges, &labels[0], 1, 7, 260);
-}
-##
-##
-
-#Subtopic Image_Info_Color_Type_BGRA_8888
-#Example
-#Width 812
-#Height 365
-#Function
-###$
-#include "SkTextUtils.h"
-$$$#
-##
-
-void draw(SkCanvas* canvas) {
-    canvas->scale(1.25f, 1.25f);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkFont font(nullptr, 10);
-    SkTextUtils::DrawString(canvas, "32-bit word", 5 + 20 * 16, 20, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, font, paint, SkTextUtils::kCenter_Align);
-    auto drawBoxText = [=](SkScalar e[], const char* s[], int count, int n, SkScalar yPos) -> void {
-        SkPaint p(paint);
-        p.setColor(SK_ColorRED);
-        SkScalar xPos = 15;
-        int width = n % 32 + 1;
-        int lastN = n > 32 ? 32 : 0;
-        for (; n >= lastN; --n) {
-            for (int i = 0; i <= count; ++i) {
-                int a = width - e[i];
-                if (a == n || a == n + 1 || a == n - 32 || a == n - 31) {
-                    char num[3] = {(char) ('0' + n / 10), (char) ('0' + n % 10), '\0'};
-                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, font, p, SkTextUtils::kCenter_Align);
-                    break;
-                }
-            }
-            xPos += 20;
-        }
-        p.setColor(SK_ColorBLACK);
-        for (int i = 0; i < count; ++i) {
-            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, font, p, SkTextUtils::kCenter_Align);
-        }
-        p.setStyle(SkPaint::kStroke_Style);
-        for (int i = 0; i <= count; ++i) {
-            canvas->drawLine(5 + e[i] * 20, yPos, 5 + e[i] * 20, yPos + 15, p);
-        }
-        for (int i = 0; i < 2; ++i) {
-            canvas->drawLine(5 + e[0] * 20, yPos + i * 15, 5 + e[count] * 20, yPos + i * 15, p);
-        }
-    };
-    SkScalar edges[] = { 0, 8, 16, 24, 32 };
-    const char* labels[] = { "alpha", "red", "green", "blue" };
-    drawBoxText(edges, &labels[0], 4, 31, 45);
-    drawBoxText(edges, &labels[3], 1, 7, 110);
-    drawBoxText(edges, &labels[2], 1, 7, 160);
-    drawBoxText(edges, &labels[1], 1, 7, 210);
-    drawBoxText(edges, &labels[0], 1, 7, 260);
-}
-##
-##
-
-#Subtopic Image_Info_Color_Type_RGBA_1010102
-#Example
-#Width 812
-#Height 380
-#Function
-###$
-#include "SkTextUtils.h"
-$$$#
-##
-
-void draw(SkCanvas* canvas) {
-    canvas->scale(1.25f, 1.25f);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkFont font(nullptr, 10);
-    SkTextUtils::DrawString(canvas, "32-bit word", 5 + 20 * 16, 20, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 4, 137, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 3, 187, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 7, 187, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 2, 237, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 6, 237, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 5, 287, font, paint, SkTextUtils::kCenter_Align);
-    auto drawBoxText = [=](SkScalar e[], const char* s[], int count, int n, SkScalar yPos) -> void {
-        SkPaint p(paint);
-        p.setColor(SK_ColorRED);
-        SkScalar xPos = 15;
-        int width = n % 32 + 1;
-        int lastN = n > 32 ? 32 : 0;
-        for (; n >= lastN; --n) {
-            for (int i = 0; i <= count; ++i) {
-                int a = width - e[i];
-                if (a == n || a == n + 1 || a == n - 32 || a == n - 31) {
-                    char num[3] = {(char) ('0' + n / 10), (char) ('0' + n % 10), '\0'};
-                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, font, p, SkTextUtils::kCenter_Align);
-                    break;
-                }
-            }
-            xPos += 20;
-        }
-        p.setColor(SK_ColorBLACK);
-        for (int i = 0; i < count; ++i) {
-            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, font, p, SkTextUtils::kCenter_Align);
-        }
-        p.setStyle(SkPaint::kStroke_Style);
-        for (int i = 0; i <= count; ++i) {
-            canvas->drawLine(5 + e[i] * 20, yPos, 5 + e[i] * 20, yPos + 15, p);
-        }
-        for (int i = 0; i < 2; ++i) {
-            canvas->drawLine(5 + e[0] * 20, yPos + i * 15, 5 + e[count] * 20, yPos + i * 15, p);
-        }
-    };
-    SkScalar edges[] = { 0, 2, 12, 22, 32,
-                         0, 8,
-                         0, 6, 8,
-                         0, 4, 8,
-                         0, 2, 8
-                        };
-    const char* labels[] = { "alpha", "blue", "green", "red" };
-    drawBoxText(&edges[0], &labels[0], 4, 31, 45);
-    drawBoxText(&edges[5], &labels[3], 1, 7, 110);
-    drawBoxText(&edges[7], &labels[2], 2, 7, 160);
-    drawBoxText(&edges[10], &labels[1], 2, 7, 210);
-    drawBoxText(&edges[13], &labels[0], 2, 7, 260);
-}
-##
-##
-
-#Subtopic Image_Info_Color_Type_RGB_101010
-#Example
-#Width 812
-#Height 380
-#Function
-###$
-#include "SkTextUtils.h"
-$$$#
-##
-
-void draw(SkCanvas* canvas) {
-    canvas->scale(1.25f, 1.25f);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkFont font(nullptr, 10);
-    SkTextUtils::DrawString(canvas, "32-bit word", 5 + 20 * 16, 20, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 4, 137, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 3, 187, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 7, 187, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 2, 237, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 6, 237, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 5, 287, font, paint, SkTextUtils::kCenter_Align);
-    auto drawBoxText = [=](SkScalar e[], const char* s[], int count, int n, SkScalar yPos) -> void {
-        SkPaint p(paint);
-        p.setColor(SK_ColorRED);
-        SkScalar xPos = 15;
-        int width = n % 32 + 1;
-        int lastN = n > 32 ? 32 : 0;
-        for (; n >= lastN; --n) {
-            for (int i = 0; i <= count; ++i) {
-                int a = width - e[i];
-                if (a == n || a == n + 1 || a == n - 32 || a == n - 31) {
-                    char num[3] = {(char) ('0' + n / 10), (char) ('0' + n % 10), '\0'};
-                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, font, p, SkTextUtils::kCenter_Align);
-                    break;
-                }
-            }
-            xPos += 20;
-        }
-        p.setColor(SK_ColorBLACK);
-        for (int i = 0; i < count; ++i) {
-            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, font, p, SkTextUtils::kCenter_Align);
-        }
-        p.setStyle(SkPaint::kStroke_Style);
-        for (int i = 0; i <= count; ++i) {
-            canvas->drawLine(5 + e[i] * 20, yPos, 5 + e[i] * 20, yPos + 15, p);
-        }
-        for (int i = 0; i < 2; ++i) {
-            canvas->drawLine(5 + e[0] * 20, yPos + i * 15, 5 + e[count] * 20, yPos + i * 15, p);
-        }
-    };
-    SkScalar edges[] = { 0, 2, 12, 22, 32,
-                         0, 8,
-                         0, 6, 8,
-                         0, 4, 8,
-                         0, 2, 8
-                        };
-    const char* labels[] = { "unused", "blue", "green", "red" };
-    drawBoxText(&edges[0], &labels[0], 4, 31, 45);
-    drawBoxText(&edges[5], &labels[3], 1, 7, 110);
-    drawBoxText(&edges[7], &labels[2], 2, 7, 160);
-    drawBoxText(&edges[10], &labels[1], 2, 7, 210);
-    drawBoxText(&edges[13], &labels[0], 2, 7, 260);
-}
-##
-##
-
-#Subtopic Image_Info_Color_Type_RGBA_F16
-#Example
-#Width 812
-#Height 685
-#Function
-###$
-#include "SkTextUtils.h"
-$$$#
-##
-
-void draw(SkCanvas* canvas) {
-    canvas->scale(1.25f, 1.25f);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkFont font(nullptr, 10);
-    SkTextUtils::DrawString(canvas, "64-bit word", 5 + 20 * 16, 20, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 135, font, paint, SkTextUtils::kCenter_Align);
-    for (int i = 0; i < 4; ++i) {
-        SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 4, 187 + i * 100, font, paint, SkTextUtils::kCenter_Align);
-        SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 4, 237 + i * 100, font, paint, SkTextUtils::kCenter_Align);
-    }
-    auto drawBoxText = [=](SkScalar e[], const char* s[], int count, int n, SkScalar yPos) -> void {
-        SkPaint p(paint);
-        p.setColor(SK_ColorRED);
-        SkScalar xPos = 15;
-        int width = n % 32 + 1;
-        int lastN = n > 32 ? 32 : 0;
-        for (; n >= lastN; --n) {
-            for (int i = 0; i <= count; ++i) {
-                int a = width - e[i];
-                if (a == n || a == n + 1 || a == n - 32 || a == n - 31) {
-                    char num[3] = {(char) ('0' + n / 10), (char) ('0' + n % 10), '\0'};
-                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, font, p, SkTextUtils::kCenter_Align);
-                    break;
-                }
-            }
-            xPos += 20;
-        }
-        p.setColor(SK_ColorBLACK);
-        for (int i = 0; i < count; ++i) {
-            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, font, p, SkTextUtils::kCenter_Align);
-        }
-        p.setStyle(SkPaint::kStroke_Style);
-        for (int i = 0; i <= count; ++i) {
-            canvas->drawLine(5 + e[i] * 20, yPos, 5 + e[i] * 20, yPos + 15, p);
-        }
-        for (int i = 0; i < 2; ++i) {
-            canvas->drawLine(5 + e[0] * 20, yPos + i * 15, 5 + e[count] * 20, yPos + i * 15, p);
-        }
-    };
-    SkScalar edges[] = { 0, 16, 32,
-                         0, 8
-                       };
-    const char* labels[] = { "alpha", "blue", "green", "red" };
-    drawBoxText(&edges[0], &labels[0], 2, 63, 45);
-    drawBoxText(&edges[0], &labels[2], 2, 31, 95);
-    drawBoxText(&edges[3], &labels[3], 1, 7, 160);
-    drawBoxText(&edges[3], &labels[3], 1, 7, 210);
-    drawBoxText(&edges[3], &labels[2], 1, 7, 260);
-    drawBoxText(&edges[3], &labels[2], 1, 7, 310);
-    drawBoxText(&edges[3], &labels[1], 1, 7, 360);
-    drawBoxText(&edges[3], &labels[1], 1, 7, 410);
-    drawBoxText(&edges[3], &labels[0], 1, 7, 460);
-    drawBoxText(&edges[3], &labels[0], 1, 7, 510);
-}
-##
-##
-
-#Subtopic Image_Info_Color_Type_RGBA_F32
-#Example
-#Width 812
-#Height 685
-#Function
-###$
-#include "SkTextUtils.h"
-$$$#
-##
-
-void draw(SkCanvas* canvas) {
-    canvas->scale(1.25f, 1.25f);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    SkFont font(nullptr, 10);
-    SkTextUtils::DrawString(canvas, "128-bit word", 5 + 20 * 16, 20, font, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 135, font, paint, SkTextUtils::kCenter_Align);
-    for (int i = 0; i < 4; ++i) {
-        SkTextUtils::DrawString(canvas, "(low bits)", 5 + 10 * 4, 187 + i * 100, font, paint, SkTextUtils::kCenter_Align);
-        SkTextUtils::DrawString(canvas, "(high bits)", 105 + 10 * 4, 237 + i * 100, font, paint, SkTextUtils::kCenter_Align);
-    }
-    auto drawBoxText = [=](SkScalar e[], const char* s[], const char* nums[] , 
-             int count, int n, SkScalar yPos) -> void {
-        SkPaint p(paint);
-        p.setColor(SK_ColorRED);
-        SkScalar xPos = 15;
-        int stringIndex = 0;
-        for (int i = n; i >= 0; --i) {
-            if (0 == i || n == i || 32 == i || 31 == i) {
-                int x = xPos;
-                if (2 == count) {
-                    x += stringIndex * 12 + (stringIndex ? 8 : 0);
-                }
-                SkTextUtils::DrawString(canvas, nums[stringIndex], x, yPos - 5, font, p, SkTextUtils::kCenter_Align);
-                if (1 == count) {
-                    SkTextUtils::DrawString(canvas, nums[stringIndex], xPos + 100, yPos - 5, font, p, SkTextUtils::kCenter_Align);
-                }
-                ++stringIndex;
-            }
-            xPos += 9;
-        }
-        p.setColor(SK_ColorBLACK);
-        for (int i = 0; i < count; ++i) {
-            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 5, yPos + 10, font, p, SkTextUtils::kCenter_Align);
-            if (1 == count) {
-                SkTextUtils::DrawString(canvas, s[i], 105 + (e[i] + e[i + 1]) * 5, yPos + 10, font, p, SkTextUtils::kCenter_Align);
-            }
-        }
-        p.setStyle(SkPaint::kStroke_Style);
-        for (int i = 0; i <= count; ++i) {
-            canvas->drawLine(5 + e[i] * 10, yPos, 5 + e[i] * 10, yPos + 15, p);
-            if (1 == count) {
-                canvas->drawLine(105 + e[i] * 10, yPos, 105 + e[i] * 10, yPos + 15, p);
-            }
-        }
-        for (int i = 0; i < 2; ++i) {
-            canvas->drawLine(5 + e[0] * 10, yPos + i * 15,
-                             5 + e[count] * 10, yPos + i * 15, p);
-            if (1 == count) {
-                canvas->drawLine(105 + e[0] * 10, yPos + i * 15,
-                                 105 + e[count] * 10, yPos + i * 15, p);
-            }
-        }
-    };
-    SkScalar edges[] = { 0, 32, 64,
-                         0, 8
-                       };
-    const char* labels[] = { "alpha", "blue", "green", "red" };
-    const char* nums128[] = { "127", "96", "95", "64"};
-    const char* nums64[] = { "63", "32", "31", "0"};
-    const char* nums8[] = { "7", "0"};
-    drawBoxText(&edges[0], &labels[0], nums128, 2, 63, 45);
-    drawBoxText(&edges[0], &labels[2], nums64, 2, 63, 95);
-    drawBoxText(&edges[3], &labels[3], nums8, 1, 7, 160);
-    drawBoxText(&edges[3], &labels[3], nums8, 1, 7, 210);
-    drawBoxText(&edges[3], &labels[2], nums8, 1, 7, 260);
-    drawBoxText(&edges[3], &labels[2], nums8, 1, 7, 310);
-    drawBoxText(&edges[3], &labels[1], nums8, 1, 7, 360);
-    drawBoxText(&edges[3], &labels[1], nums8, 1, 7, 410);
-    drawBoxText(&edges[3], &labels[0], nums8, 1, 7, 460);
-    drawBoxText(&edges[3], &labels[0], nums8, 1, 7, 510);
-}
-##
-##
-
-#Subtopic Blend_Mode_Overview_Porter_Duff
-#Example
-#Width 480
-#Height 330
-#Function
-###$
-#include "SkTextUtils.h"
-$$$#
-##
-
-void draw(SkCanvas* canvas) {
-    SkPaint srcPaint;
-    srcPaint.setAntiAlias(true);
-    SkPaint labelPaint = srcPaint;
-    SkFont labelFont(nullptr, 16);
-    SkPaint dstPaint = srcPaint;
-    dstPaint.setColor(0xFF606080);
-    SkFont dstFont(SkTypeface::MakeFromName("Roboto", SkFontStyle::Bold()), 80);
-
-    SkBitmap srcBits;
-    srcBits.allocN32Pixels(80, 84);
-    SkCanvas srcCanvas(srcBits);
-    srcPaint.setColor(0xFFcc6633);
-    SkPath srcPath;
-    const SkPoint points[] = {{20, 20}, {80, 45}, {45, 80}};
-    srcPath.addPoly(points, SK_ARRAY_COUNT(points), true);
-    srcBits.eraseColor(0);
-    srcCanvas.drawPath(srcPath, srcPaint);
-
-    canvas->drawColor(0, SkBlendMode::kClear);
-    for (auto blend : { SkBlendMode::kSrc, SkBlendMode::kSrcATop, SkBlendMode::kSrcOver,
-                        SkBlendMode::kSrcIn, SkBlendMode::kSrcOut,
-                        SkBlendMode::kDst, SkBlendMode::kDstATop, SkBlendMode::kDstOver,
-                        SkBlendMode::kDstIn, SkBlendMode::kDstOut,
-                        SkBlendMode::kClear, SkBlendMode::kXor } ) {
-        SkTextUtils::DrawString(canvas, "&", 50, 80, dstFont, dstPaint, SkTextUtils::kCenter_Align);
-        srcPaint.setBlendMode(blend);
-        canvas->drawBitmap(srcBits, 0, 0, &srcPaint);
-        SkTextUtils::DrawString(canvas, SkBlendMode_Name(blend), 50, 100, labelFont, labelPaint, SkTextUtils::kCenter_Align);
-        canvas->translate(80, 0);
-        if (SkBlendMode::kSrcOut == blend || SkBlendMode::kDstOut == blend) {
-            canvas->translate(-80 * 5, 100);
-        }
-    }
-}
-##
-##
-
-#Subtopic Blend_Mode_Overview_Porter_Duff_2
-#Example
-#Width 480
-#Height 330
-#Function
-###$
-#include "SkTextUtils.h"
-$$$#
-##
-
-void draw(SkCanvas* canvas) {
-    SkPaint srcPaint;
-    srcPaint.setAntiAlias(true);
-    SkPaint labelPaint = srcPaint;
-    SkFont labelFont(nullptr, 16);
-    SkPaint dstPaint = srcPaint;
-    dstPaint.setColor(0xFF606080);
-    SkFont dstFont(SkTypeface::MakeFromName("Roboto", SkFontStyle::Bold()), 80);
-
-    srcPaint.setColor(0xFFcc6633);
-    SkPath srcPath;
-    const SkPoint points[] = {{20, 20}, {80, 45}, {45, 80}};
-    srcPath.addPoly(points, SK_ARRAY_COUNT(points), true);
-    canvas->drawColor(0, SkBlendMode::kClear);
-
-    SkBitmap dstBits;
-    dstBits.allocN32Pixels(80, 80);
-    SkCanvas dstCanvas(dstBits);
-    for (auto blend : { SkBlendMode::kSrc, SkBlendMode::kSrcATop, SkBlendMode::kSrcOver,
-                        SkBlendMode::kSrcIn, SkBlendMode::kSrcOut,
-                        SkBlendMode::kDst, SkBlendMode::kDstATop, SkBlendMode::kDstOver,
-                        SkBlendMode::kDstIn, SkBlendMode::kDstOut,
-                        SkBlendMode::kClear, SkBlendMode::kXor } ) {
-        SkTextUtils::DrawString(canvas, "&", 50, 80, dstFont, dstPaint, SkTextUtils::kCenter_Align);
-        srcPaint.setBlendMode(blend);
-        canvas->drawPath(srcPath, srcPaint);
-        SkTextUtils::DrawString(canvas, SkBlendMode_Name(blend), 50, 100, labelFont, labelPaint, SkTextUtils::kCenter_Align);
-        canvas->translate(80, 0);
-        if (SkBlendMode::kSrcOut == blend || SkBlendMode::kDstOut == blend) {
-            canvas->translate(-80 * 5, 100);
-        }
-    }
-}
-##
-##
-
-#Subtopic Blend_Mode_Overview_Lighten_Darken
-#Example
-#Width 480
-#Height 330
-#Function
-###$
-#include "SkTextUtils.h"
-$$$#
-##
-
-void draw(SkCanvas* canvas) {
-    SkPaint srcPaint;
-    srcPaint.setAntiAlias(true);
-    SkPaint labelPaint = srcPaint;
-    SkFont labelFont(nullptr, 16);
-    SkPaint dstPaint = srcPaint;
-    dstPaint.setColor(0xFF606080);
-    SkFont dstFont(SkTypeface::MakeFromName("Roboto", SkFontStyle::Bold()), 80);
-
-    srcPaint.setColor(0xFFcc6633);
-    SkPath srcPath;
-    const SkPoint points[] = {{20, 20}, {80, 45}, {45, 80}};
-    srcPath.addPoly(points, SK_ARRAY_COUNT(points), true);
-    canvas->drawColor(0, SkBlendMode::kClear);
-    for (auto blend : { SkBlendMode::kPlus, SkBlendMode::kScreen, SkBlendMode::kOverlay,
-            SkBlendMode::kDarken, SkBlendMode::kLighten, SkBlendMode::kColorDodge,
-            SkBlendMode::kColorBurn, SkBlendMode::kHardLight, SkBlendMode::kSoftLight,
-            SkBlendMode::kDifference, SkBlendMode::kExclusion, SkBlendMode::kMultiply } ) {
-        SkTextUtils::DrawString(canvas, "&", 50, 80, dstFont, dstPaint, SkTextUtils::kCenter_Align);
-        srcPaint.setBlendMode(blend);
-        canvas->drawPath(srcPath, srcPaint);
-        SkTextUtils::DrawString(canvas, SkBlendMode_Name(blend), 50, 100, labelFont, labelPaint,
-                SkTextUtils::kCenter_Align);
-        canvas->translate(90, 0);
-        if (SkBlendMode::kLighten == blend || SkBlendMode::kDifference == blend) {
-            canvas->translate(-90 * 5, 100);
-        }
-    }
-}
-##
-##
-
-#Subtopic Blend_Mode_Overview_Color_Blends
-#Example
-#Width 480
-#Height 110
-#Function
-###$
-#include "SkTextUtils.h"
-$$$#
-##
-
-void draw(SkCanvas* canvas) {
-    SkPaint srcPaint;
-    srcPaint.setAntiAlias(true);
-    SkPaint labelPaint = srcPaint;
-    SkFont labelFont(nullptr, 16);
-    SkPaint dstPaint = labelPaint;
-    dstPaint.setColor(0xFF606080);
-    SkFont dstFont(SkTypeface::MakeFromName("Roboto", SkFontStyle::Bold()), 80);
-
-    srcPaint.setColor(0xFFcc6633);
-    SkPath srcPath;
-    const SkPoint points[] = {{20, 20}, {80, 45}, {45, 80}};
-    srcPath.addPoly(points, SK_ARRAY_COUNT(points), true);
-    canvas->drawColor(0, SkBlendMode::kClear);
-    for (auto blend : { SkBlendMode::kHue, SkBlendMode::kSaturation, SkBlendMode::kColor,
-                        SkBlendMode::kLuminosity } ) {
-        SkTextUtils::DrawString(canvas, "&", 50, 80, dstFont, dstPaint, SkTextUtils::kCenter_Align);
-        srcPaint.setBlendMode(blend);
-        canvas->drawPath(srcPath, srcPaint);
-        SkTextUtils::DrawString(canvas, SkBlendMode_Name(blend), 50, 100, labelFont, labelPaint,
-                SkTextUtils::kCenter_Align);
-        canvas->translate(90, 0);
-    }
-}
-##
-##
-
-#Subtopic Blend_Mode_Overview_Modulate_Blend
-#Example
-#Width 480
-#Height 110
-#Function
-###$
-#include "SkTextUtils.h"
-$$$#
-##
-
-void draw(SkCanvas* canvas) {
-    SkPaint srcPaint;
-    srcPaint.setAntiAlias(true);
-    SkPaint labelPaint = srcPaint;
-    SkFont labelFont(nullptr, 16);
-    SkPaint dstPaint = srcPaint;
-    dstPaint.setColor(0xFF606080);
-    SkFont dstFont(SkTypeface::MakeFromName("Roboto", SkFontStyle::Bold()), 80);
-
-    SkBitmap srcBits;
-    srcBits.allocN32Pixels(80, 84);
-    SkCanvas srcCanvas(srcBits);
-    srcPaint.setColor(0xFFcc6633);
-    SkPath srcPath;
-    const SkPoint points[] = {{20, 20}, {80, 45}, {45, 80}};
-    srcPath.addPoly(points, SK_ARRAY_COUNT(points), true);
-    srcBits.eraseColor(0);
-    srcCanvas.drawPath(srcPath, srcPaint);
-
-    canvas->drawColor(0, SkBlendMode::kClear);
-    srcPaint.setBlendMode(SkBlendMode::kModulate);
-    for (auto step: { 1, 2 } ) {
-        SkTextUtils::DrawString(canvas, "&", 50, 80, dstFont, dstPaint, SkTextUtils::kCenter_Align);
-        if (1 == step) {
-            canvas->drawBitmap(srcBits, 0, 0, &srcPaint);
-            SkTextUtils::DrawString(canvas, "Bitmap", 50, 18, labelFont, labelPaint, SkTextUtils::kCenter_Align);
-        } else {
-            canvas->drawPath(srcPath, srcPaint);
-            SkTextUtils::DrawString(canvas, "Geometry", 50, 18, labelFont, labelPaint, SkTextUtils::kCenter_Align);
-        }
-        SkTextUtils::DrawString(canvas, SkBlendMode_Name(SkBlendMode::kModulate), 50, 100, labelFont, labelPaint,
-                SkTextUtils::kCenter_Align);
-        canvas->translate(120, 0);
-    }
-}
-##
-##
-
-#Subtopic Path_Arc
-#Example
-#Height 300
-#Width 600
-#Function
-###$
-
-struct data {
-   const char* name;
-   char super;
-   int yn[10];
-};
-
-const data dataSet[] = {
-{ "arcTo sweep",    '1', {1,  3, 1, 0, 0, 0, 0, 1, 0, 0 }},
-{ "drawArc",         0,  {1, -1, 1, 1, 1, 1, 1, 0, 0, 0 }},
-{ "addArc",          0,  {1,  1, 1, 4, 0, 1, 1, 1, 0, 0 }},
-{ "arcTo tangents", '4', {0,  0, 0, 0, 0, 0, 0, 1, 1, 0 }},
-{ "arcTo radii",    '5', {1,  0, 1, 0, 0, 0, 0, 1, 1, 0 }},
-{ "conicTo",         0,  {1,  1, 0, 0, 0, 0, 0, 1, 1, 1 }}
-};
-
-#define __degree_symbol__ "\xC2" "\xB0"
-
-const char* headers[] = {
-    "Oval part",
-    "force moveTo",
-    "can draw 180" __degree_symbol__,
-    "can draw 360" __degree_symbol__,
-    "can draw greater than 360" __degree_symbol__,
-    "ignored if radius is zero",
-    "ignored if sweep is zero",
-    "requires Path",
-    "describes rotation",
-    "describes perspective",
-};
-
-const char* yna[] = {
-     "n/a",
-     "no",
-     "yes"
-};
-$$$#
-##
-void draw(SkCanvas* canvas) {
-    SkPaint lp;

-    lp.setAntiAlias(true);

-    SkPaint tp(lp);

-    SkPaint sp(tp);

-    SkFont bf;

-    bf.setEmbolden(true);

-    SkFont sf(nullptr, 10);

-    lp.setColor(SK_ColorGRAY);

-    canvas->translate(0, 32);

-    const int tl = 115;

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

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

-       if (0 == col) {

-          continue;

-       }

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

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

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

-       v.normalize();

-       SkMatrix matrix;

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

-       canvas->save();

-       canvas->concat(matrix);

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

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

-       canvas->restore();

-    }

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

-        if (0 == row) {

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

-        } else {

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

-        }

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

-            break;

-        }

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

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

-        if (dataSet[row].super) {

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

-                    SkTextEncoding::kUTF8);

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

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

-        }

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

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

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

-            if (val > 1) {

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

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

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

-            }

-        }

-    }
-}
-#Example ##
-##
-
-#Topic Illustrations ##
diff --git a/docs/markup.bmh b/docs/markup.bmh
deleted file mode 100644
index 7e4d98b..0000000
--- a/docs/markup.bmh
+++ /dev/null
@@ -1,88 +0,0 @@
-#Topic Bookmaker_Markup
-
-# redefine markup character so examples below will not be parsed
-###$
-
-Text, except for the single markup character, requires no annotation.
-
-# comments are preceded by a hash symbol and whitespace
-# comments may terminated by linefeed or double hash ## <- end of comment
-
-Keywords are preceded by a single hash symbol without whitespace.
-#Keyword
-
-Keywords are terminated by double hash and may be labeled
-##            <- end of #keyword
-
-#Keyword
-#Keyword ##   <- alternate labeled end of #Keyword
-
-Tables use single hash symbols to delimit columns, and double to end row.
-#Table
-#Legend
-# first column in table # next column in table ##
-##            <- end of #Legend
-# a row                 # another row ##
-# another row           # another row ##
-#Table ##     <- or, just ##
-
-$Table
-$Legend
-$ first column in table $ next column in table $$
-$$
-$ a row                 $ another row $$
-$ another row           $ another row $$
-$Table $$
-
-The markup character is initially # at the start of any .bmh file
-###x          <- redefine the markup character as 'x'
-xxx#          <- restore the default markup character
-
-  anchor, ala HTML
-  anchors may start anywhere in the line
-#A text #_reference ##
-
-  class description
-#Class SkClassName
-description
-methods
-##
-
-  if the example is not named, it inherits the name of its container
-#Example
-    #Description
-    ##
-    #Image
-    #Width
-    #Height
-        code...
-    #StdOut
-        expected example output
-    ##
-##
-
-#Enum __required_reference
-description
-#Code
-##
-#Example
-##
-#Enum ##
-
-  method description
-  the _method_reference must be unique within the class
-#Method type name(params..)
-description
-#Param name  description ##
-#Return return ##
-#Example
-##
-#SeeAlso ##
-##
-
-#ToDo  description ##
-
-$ restore markup character
-$$$#
-
-##
diff --git a/docs/overview.bmh b/docs/overview.bmh
deleted file mode 100644
index c6b0d1f..0000000
--- a/docs/overview.bmh
+++ /dev/null
@@ -1,8 +0,0 @@
-overview
-
---------------------------------------------------------------------------------
-
-Skia draws 2D primitives, paths and bitmaps, using the styles in the SkPaint, to
-the device contained by the SkCanvas.
-
---------------------------------------------------------------------------------
diff --git a/docs/spelling.txt b/docs/spelling.txt
deleted file mode 100644
index 52f20bd..0000000
--- a/docs/spelling.txt
+++ /dev/null
@@ -1,257 +0,0 @@
-a able abort aborted aborts about above abruptly absolute abstract abut access accessed accessible

-accompanying account accounted achieved acronym across active actual actually add added adding

-additional additionally address addressable adds adjacent adjusted adjusting adjustment

-adjustments adjusts advance advances advantage advantages affect affected affecting affects

-after again agree agrees algorithm algorithms align aligned alignment aligns alike all allocate

-allocated allocates allocating allocation allocations allow allowed allowing allows 

-along also alter altered altering alternating alternative alters although altogether

-always among amount an anchor and angle angles animating animation annotate annotation annotations

-another answer anticipating any anywhere appear appears append appended appending appends

-applied applies apply applying approach approximate approximated approximately approximates

-approximation are area argument arguments arithmetic arms arrangement arranges array arrays

-arrow artifacts as ascending ascent aspect assert asserts assigned assigning assignment

-associate associated associates associating assumed asynchronous asynchronously at

-attach attached attempt attribute attributes auxiliary available average averaged averages

-avoid avoiding away axes axis axis-aligned

-

-back back-end backed background backing balance balancing banding bare base based baseline

-be bearing because become becomes been before begin beginning begins behaves behavior being below

-beneath best better between bevel beveled beyond binary bit bit-field bits black bleed bleeding

-bleeds blend blended blending blends blob blobs blue blur blurred blurs bold bone bordered both

-bottom bottom-left bottom-right boundaries bounded bounding bounds box brace-delimited

-breaking brightened brighter brightness brush buffer bug bugs build builder building builds

-built but butt by byte bytes 

-

-cache cached caches caching calculation call callback called caller calling calls can 

-cannot canonical cap capabilities capacity caps captured captures capturing care case cases cast

-casts cause causes center centered centers change changed changes changing channel channels

-char character characteristics characters check checked checkerboard checkerboards 

-checking checks choice choose chooses choosing chosen circular clamped class classifies

-clean cleans clear cleared clearing clears client clip clipped clipping clips clock

-clockwise close closed closely closes closest clusters code codec codes collapse collects colored

-colors column columns combination combinations combine combined combines combining comma

-command commands commas common commonly communicates compact compare compared compares comparison

-compatibility compatible compile compiled compiler complete completely completes complex 

-complexity complicated component components composed composite compositing composition

-compressed computation computations compute computed computes computing concatenated

-concatenates concatenation concave concert condensed condition configuration conical

-connect connected connecting connects consecutive conservative conservatively consider 

-consideration considered considers consistent consists const constant constants constrained

-constraint

-construct constructed constructing construction constructions constructor constructs

-consumption contain contained container containers containing containment contains content contents

-context contexts contextual contiguous continue continues continuing continuous contrast

-contribute contributing contribution control controls convenience conveniences conversion

-convert converted converting converts convex convexity coordinate coordinates copied copies

-copy copying corner corners correct correctly correspond corresponding corresponds corrupt

-corrupting

-cosine could count counterclockwise counts coverage covered covering covers crash

-create created creates creating creator criteria critical cross cull culled culling

-cumulatively current curvature curving custom cyan

-

-dark darken darkening darker dashed dashes dashing debug decimal decimated declaring

-decode decoded decoders decodes decoding 

-decomposed decomposes decomposing decrease decreased decreases decreasing decrements default

-defaults defer deferred

-defers define defined defines defining definition deform deformations degenerate degree

-degrees delete deleted deletes deleting dependent depending depends deprecated depth

-descent describe described describes describing description descriptions descriptor

-designate desired destination destinations destruct destructed details detected determine 

-determined determines devices diagonal diagonally diameter did differ difference different

-differently differing differs digits dilates dimension dimensional dimensionless

-dimensions direct direction directly directory disable disabled disabling discard

-discarded discarding discards discrete display displayed displays distance distinct distribute

-distributed dither dithering divide divided divides dividing division divisions divisor do 

-document-based documentation documenting does done dot dotted double down downgrading

-downscale downward draw drawing drawn draws due duplicate duplicates duplicating during dynamically 

-

-each early easier edge edges edited editing effect effectively effects efficient

-efficiently eight either element elements eliminate ellipse ellipses elliptical else embedded

-empty emulates enabled enables encapsulating enclose enclosed enclosing encode encoded encoder

-encoders encodes encoding encompasses end ending ends engine engines enlarge enough

-ensure ensuring entire entries entry enumeration environment equal equaling equality equally equals

-equivalent equivalents erases erroneously error evaluated even even-numbered even-odd

-evenly eventually ever every exact exactly exaggerates examined example exceed exceeds

-exceptions excess exchanged exchanges exchanging exclude excluded exclusive executing

-execution exhausted existing expanded expansion expected expecting explicit explicitly exponent

-exposed expression extend extended extends extension extensive extent extra extract eye 

-

-face faces factor factored factors fail failing fails failure fake fall false far fast faster

-fastest

-favor feature features fewer fidelity field fields figure file fill filled filling fills

-filter filtered filtering filters final finalized finally find fine finite first fit fits 

-fitted five fix

-fixed flag flags flattening flexibility float floating floats flow flush flushed flushes

-followed following follows fonts for force forced form format formats formed former forming

-forms formula found four fourth fractional fragment frame frames framework free freed

-freedom frees fringing from front full fully function functionality functions further future

-fuzzy

-

-gamma gamut gap gaps general generate generated generates generator generic geometric geometrically

-geometries geometry get gets gigabyte gigabytes given gives global globally go goes

-good gradient gradients graphics gray gray-level greater greatest green grid grows guarantee

-guaranteed guard

-

-had hairline hairlines half half-precision halved hand handle handling hands happens hard hardware

-has hash

-hashes have heap height help helper helpers helpful here hexadecimal hidden hierarchical

-high higher highest hint hinted hinting hints hit hitting hold holding holds hole holes

-horizontal horizontally host hosts hour how however hue hyperbola hyperbolic 

-

-idea identical identically identifier identifies identify identifying identity if

-ignore ignored ignores ignoring illegal image-generator images immediate immediately

-immutable imperceptible implement implementation implementations implemented implementing

-implements implicitly improve improves improving in inaccessible include included includes

-including incompatible incomplete inconsistent incorrect incorrectly increase increased increases

-increasing increment incremented increments indefinitely independent independently index indicated

-indicates

-indicating indices indistinguishable individual individually inefficient infinite infinities

-infinity

-influence info inform information initial initialize initialized initializes initializing

-initially inline inner input insert inserts inset insets inside inspected installed instance

-instantaneous instantiated instantiation instantiations instead instruct instructs integer integers

-integral intensity intercepted intercepts interchangeably interest interface interior

-interleaved interleaving 

-intermediate internal internally interpolate interpolated interpolates interpolation interpret

-interpreted interrupted interruption intersect intersected intersecting intersection

-intersections intersects interval into introduces introducing invalid invalidate inverse

-inversely inverted investigated invoked irregular is issues issuing it italic items iterate iterated

-iterates iterating iteration iterator its itself 

-

-join joins just 

-

-keep keeping key-value known 

-

-large largely larger largest last late later layout lazily leading least leave leaves

-leaving left left-aligned left-bottom left-top leftmost legacy legal length lengthens

-lengths less lessens lesser lets letters level levels lifetime light lighten lighter lightness

-lightweight like likes limit limitation limited limits linear linearity list loaded local locally

-located locates location locations logical longer look looks lookup loop loops lose loss

-lost low lower lower-case lower-left lowest luminosity 

-

-made magenta magnified magnitude magnitudes main maintained maintains major make makes making manage

-managed manager manages managing mandrill manipulating many map mapped mapping mappings

-maps mark marked marking marks mashup mask masked masks match matches matching mathematically

-maximum may maybe mean

-meaning meaningful means measurable measure measured measures measuring mechanism meet

-member members memory mesh met method methods metric metrics middle midpoint might

-mimics minimally minimum minor minus minute mirrors mismatched misnamed missing

-miter mode modes modification modifications modified modifier modifies modify

-modifying modulate modulated modulo monitor monitors more most mostly move moved

-moves moving multiple multiple-pass multiplied multiplier multipliers multiplies multiply

-multiplying must muted 

-

-name named names narrower narrowing narrows native near nearby nearly necessarily necessary

-need needed needs negative nested never new newly next nibble nine no non-homogeneous

-non-monochrome non-rectangular

-non-square non-volatile non-zero none nor normal normalize normalized normally not

-note nothing noticeable notifies now null null-terminated number numbers numeric numerically 

-

-object objects oblique obtain occupies odd of off offers offset offsets often on

-on-demand once one only onto opacity opaque opaqueness open opened operand operate operates

-operating operation operations operator operators opposite optimal optimally optimizations

-optimize optimized option optional optionally options or order ordered organization

-organizes orient orientation origin original origins other others otherwise out outcome outer

-outline outlines output outset outsets outside outstanding over overall overdraw

-overflow overflows overhead overlap overlapping overlaps overlaying overlays overridden

-override overrides overriding overwrite overwriting overwritten own owned owner owners ownership

-owning 

-

-pack packed packing padding painted paints pair paired pairs parabola parabolic parallel

-parameter parameters parsed parsing part partial partially parts pass passed passes passing past

-pattern patterns peek peeked peeking pen penalty pending per perception perform performance

-performed performing performs permit permits permitted permitting

-perpendicular perspective perspective-scale

-perspective-x perspective-y physical piece pieces pin pinned pins pipeline pivot

-pixel-based pixels place placeholder placement places plane planes platform platform-specific

-platforms play playback played plus pointer pointers pointing polygon polygonal polynomial

-pop port portion position positioned positioning positions positive positives possible 

-possibly potentially power practice preceded preceding precise precision predefined predictable

-predicted prepare present preserve preserved preserves preserving pressure prevent

-prevents previous previously primarily primitives printed prior private process processed

-processes processing processors produce produces product progressively promoted promoting

-properties proportion proportional proportionally proportionately provide provided

-provides providing pt public pulled punches purposes put 

-

-quadratic quality quantity quarter quick quickly 

-

-race radii radius range ranges ranging rarely raster rather ratio raw read read-only

-readable reader reading reads reallocation really reassign receive receiver receives receiving

-reciprocal recognized recommended recompute recomputed reconstruct reconstruction

-reconstructs recorded recording records recreates rectangle rectangles rectangular red reduce 

-reduced reducing reference referenced references referred referring refers reflect

-reflection regardless related relative relaxed release released releases releasing

-relies rely remain remainder remaining remains remove removed removes removing render

-rendered rendering repeated repeatedly replace replaced replacement replaces replacing replays

-replicate replicated replicates report reports represent representation representations

-representative represented representing represents request requested requests require 

-required requirements requires requiring resemble reserve reserved

-reset resets reside residing resolution

-resolves resource resources respect respected respects

-responsible restore restored restores restoring

-restrict restricted restriction restrictive restricts result resulting results retain

-retained retains retrieve retrieved retrieves retroactive return returned returning returns

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

-rotate rotate-x rotate-y rotated rotates rotating rotation roughly round rounded

-rounding rounds route routes routines row rows rule rules run runs 

-

-safe safely safer same sample sampled samples sampling sanitized satisfies satisfy satisfying

-saturation savable save saved saves scale scale-x scale-y scaled scales scaling scan

-scene scope screen second second-order section sections see seek seeks segment segments select

-selecting selectively selector selects semaphore semaphores sensitive sent separate separately 

-sequence serial serialized series service set sets setting settings shadow shadows

-shallow shape shapes shaping share shareable shared shares sharing sharp shear shift

-shifts short shorter shorthand should show shows shrink shrinks side sides sign signal

-signaled signed signs similar similarity simple simplifies

-simplify simply since single single-pass singly

-six sized sizes skew skewing skews skip skipped skips slant slightly slow slower slowest

-small smaller smallest smart smooth smoother snapshot so soft sole solely solid solution some

-sometimes soon sort sorted sorts source space spaced spacing span spanned spans special 

-specialization specializations specialized specializes specific specification specifics specified

-specifies specify specifying speed speeds spirit square squared squares stack stage stages

-standard standards stands start started starting starts state states stationary stay

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

-straight-line streams strength stretched strictly strikeout strings stripe stripes 

-striping stroke stroked strokes stroking struct studio style stylistic subclass

-submitting subsequent subsequently subset substitution subtle subtract subtracted subtracts

-succeed succeeded succeeds success successful successfully successive such sufficient

-suggests sum summing supplied supplies supply supplying support supported supports

-suppress surrogate swapped swaps sweep sweeping sweeps switches symbol symmetrically

-synchronous system 

-

-tables take taken takes taking taller tangent tangents target targets techniques 

-television temporary

-ten terminate terminated test tested tests textual textures than that the their them then

-there therefore these they thick thickness thin thinner thinnest third third-order this

-those though thought thread threads three three-dimensional through thus tight tile

-tiles tiling time times

-to together toggled too top top-left top-right total totaling touches towards tracked tracks 

-trades trading traditional transferred transferring transfers transform transformation

-transformations

-transformed transforming transforms transition transitions translate translated translates

-translating translation translucent transparency transparent travel traveled treat treated

-treating treats triangle trigger triggered triggers trimmed trivial true try turned turns twice two

-two-dimensional type types typically typographic 

-

-unaffected unaltered unchanged unchanging uncompressed undefined under underline

-underlines underlying unequal unfilled uniform uniformly uninitialized union unions unique

-unit unknown unless unlike unlikely unmodified unrelated unsigned unsorted unsuccessful until

-untouched unused up update updates upload uploaded upper upper-case upper-left upright upward

-usage use used useful user uses using utility 

-

-valid validate validated validity value values variable variant variants variation 

-varies various vary

-varying verb verify version vertical vertically very via video views virtual visible visibly visual

-visually volatile 

-

-wait waited waiting warning warnings was way ways wedge weight weighted well well-defined

-were whatever wheel when where whether which whichever while white whole whose wide

-wide-open widens wider width widths will wind winding windows winds with within without

-word words work works world would wrap wrapped wraps writable write writes writing 

-written wrong 

-

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

-

-y-axis y-coordinate y-radii yellow

-

-z-axis zero zeroed zeroes zoom 

diff --git a/docs/status.json b/docs/status.json
deleted file mode 100644
index 8c3ec23..0000000
--- a/docs/status.json
+++ /dev/null
@@ -1,72 +0,0 @@
-{
-    "Completed": {
-        "include": {
-            "core": [
-                "SkBitmap.h",
-                "SkBlendMode.h",
-                "SkCanvas.h",
-                "SkColor.h",
-                "SkFont.h",
-                "SkImage.h",
-                "SkImageInfo.h",
-                "SkMatrix.h",
-                "SkPaint.h",
-                "SkPath.h",
-                "SkPicture.h",
-                "SkPixmap.h",
-                "SkPoint.h",
-                "SkRRect.h",
-                "SkRect.h",
-                "SkRegion.h",
-                "SkSurface.h",
-                "SkTextBlob.h"
-            ]
-        },
-        "docs": [
-            "SkAutoCanvasRestore_Reference.bmh",
-            "SkBlendMode_Reference.bmh",
-            "SkCanvas_Reference.bmh",
-            "SkColor_Reference.bmh",
-            "SkFont_Reference.bmh",
-            "SkPaint_Reference.bmh",
-            "SkPoint_Reference.bmh",
-            "SkIRect_Reference.bmh",
-            "SkImage_Reference.bmh",
-            "SkImageInfo_Reference.bmh",
-            "SkPath_Reference.bmh",
-            "SkPicture_Reference.bmh",
-            "SkRRect_Reference.bmh",
-            "SkRect_Reference.bmh",
-            "SkRegion_Reference.bmh",
-            "SkBitmap_Reference.bmh",
-            "SkIPoint_Reference.bmh",
-            "SkMatrix_Reference.bmh",
-            "SkPixmap_Reference.bmh",
-            "SkSurface_Reference.bmh",
-            "SkTextBlobBuilder_Reference.bmh",
-            "SkTextBlob_Reference.bmh",
-            "illustrations.bmh",
-            "undocumented.bmh"
-        ]
-    },
-    "InProgress": {
-        "include": {
-            "core": [
-                "SkStream.h"
-            ]
-        },
-        "docs": [
-            "SkBlendMode_Overview.bmh",
-            "SkColor4f_Reference.bmh",
-            "SkDynamicMemoryWStream_Reference.bmh",
-            "SkFILEStream_Reference.bmh",
-            "SkFILEWStream_Reference.bmh",
-            "SkMemoryStream_Reference.bmh",
-            "SkPath_Overview.bmh",
-            "SkStream_Reference.bmh",
-            "SkWStream_Reference.bmh",
-            "overview.bmh",
-            "usingBookmaker.bmh"
-        ]
-    }
-}
diff --git a/docs/undocumented.bmh b/docs/undocumented.bmh
deleted file mode 100644
index 223898e..0000000
--- a/docs/undocumented.bmh
+++ /dev/null
@@ -1,1071 +0,0 @@
-# external references that will be documented eventually ...
-#External
- DirectWrite TrueType Windows Linux Android iOS __ANDROID_API__ AHardwareBuffer
- FreeType FreeType-based Harfbuzz
- LCD RGB sRGB BGR RGBA ARGB YUV YUVA HSV SDTV HDTV
- Unicode Unicode5 UTF-8 UTF-16 UTF-32 ASCII Unichar
- API
- BMP GIF HEIF ICO JPEG PNG WBMP WebP
- CPU
- GPU GPU-backed OpenGL Vulkan VkDevice I/O MSAA
- PDF XPS
- RFC
- NaN NaNs
- bool U16CPU int int8_t int16_t int32_t int64_t uint8_t uint16_t uint32_t uint64_t size_t
- nullptr
- malloc() calloc() assert() std::move()
- C C++ SIZE_MAX C_FILE FILE
- CSS HTML
-
- affine destructor multitexturing representable subclasses descender descenders grayscale kerning
- unhinted no-parameter
- 
- Sa Da Sc Dc 0xAARRGGBB # temporary until figure out what to do
-
- SkUserConfig.h  # not external, but still thinking about how markup refers to this
- SkXXX.h        # ditto
- SkXXX_Reference # ditto
- Skia           # ditto
- SK_ABORT       # ditto
- SK_DEBUG       # ditto
- SK_RELEASE     # ditto
- SK_USE_FREETYPE_EMBOLDEN # ditto
- SK_BUILD_FOR_ANDROID_FRAMEWORK # ditto
- SK_BUILD_FOR_MAC # ditto
- SK_BUILD_FOR_IOS # ditto
- SK_SUPPORT_GPU # ditto
- SK_HAS_JPEG_LIBRARY # ditto
- SK_HAS_PNG_LIBRARY # ditto
- SK_HAS_WEBP_LIBRARY # ditto
- SK_IGNORE_GPU_DITHER # ditto
- SK_PMCOLOR_BYTE_ORDER # ditto
-
-# FreeType related
-FT_LOAD_TARGET_LIGHT
-FT_LOAD_TARGET_NORMAL
-FT_LOAD_TARGET_LCD
-FT_LOAD_TARGET_LCD_V
-FT_LOAD_NO_HINTING
-FT_Load_Glyph
-
-#External ##
-
-# this jargon requires a substitute to space the phrase.
-#Topic Little_Endian
-#Substitute little endian
-##
-
-#Topic Big_Endian
-#Substitute big endian
-##
-
-#Topic YUV_Component_Y
-#Substitute YUV component y
-##
-
-#Topic YUV_Component_U
-#Substitute YUV component u
-##
-
-#Topic YUV_Component_V
-#Substitute YUV component v
-##
-
-#Topic UV_Mapping
-#Substitute UV mapping
-##
-
-#Topic Multi_Sample_Anti_Aliasing
-#Substitute multi-sample anti-aliasing
-##
-
-#Topic GPU_Share_Group
-#Substitute GPU share group
-##
-
-#Topic Bezier_Curve
-#Substitute Bezier cruve
-##
-
-#Topic Coons_Patch
-#Substitute Coons patch
-##
-
-#Topic Cartesian_Coordinate
-#Substitute Cartesian coordinate
-##
-
-#Topic Euclidean_Distance
-#Substitute Euclidean distance
-##
-
-#Topic Euclidean_Space
-#Substitute Euclidean space
-##
-
-#Topic HTML_Gray
-#Substitute HTML gray
-##
-
-#Topic HTML_Silver
-#Substitute HTML silver
-##
-
-#Topic HTML_Lime
-#Substitute HTML lime
-##
-
-#Topic HTML_Green
-#Substitute HTML green
-##
-
-#Topic HTML_Aqua
-#Substitute HTML aqua
-##
-
-#Topic HTML_Fuchsia
-#Substitute HTML fuchsia
-##
-
-#Topic SVG_lightgray
-#Substitute SVG light gray
-##
-
-#Topic SVG_darkgray
-#Substitute SVG dark gray
-##
-
-# start of topics to be documented in the future
-
-#Topic Alias
-#Alias Aliased ##
-#Alias Aliasing ##
-##
-
-#Topic Arc
-#Alias Arcs ##
-#Topic ##
-
-#Topic Backend_Semaphore
-#Alias Backend_Semaphores ##
-#Class GrBackendSemaphore
-##
-##
-
-#Topic BBH_Factory
-#Class SkBBHFactory
-##
-##
-
-
-#Topic Circle
-#Alias Circles ##
-#Topic ##
-
-#Topic Clip_Op
-#EnumClass SkClipOp
-    #Const kDifference 0
-    ##
-    #Const kIntersect 1
-    ##
-##
-##
-
-#Topic Color_Filter
-#Class SkColorFilter
-#Class ##
-##
-
-#Topic Color_Space
-#Class SkColorSpace
-    #Method static sk_sp<SkColorSpace> MakeSRGBLinear()
-    ##
-    #Method bool gammaCloseToSRGB() const
-    ##
-    #Method static bool Equals(const SkColorSpace* src, const SkColorSpace* dst)
-    ##
-##
-##
-
-#Topic Create_Color_Space_Xform_Canvas
-#Method std::unique_ptr<SkCanvas> SkCreateColorSpaceXformCanvas(SkCanvas* target,
-                                                                sk_sp<SkColorSpace> targetCS)
-##
-##
-
-#Topic Core_Graphics
-#Substitute Core Graphics
-##
-
-#Topic Core_Text
-#Substitute Core Text
-##
-
-#Topic Curve
-#Alias Curves ##
-##
-
-#Topic Data
-#Class SkData
-##
-##
-
-#Topic Debug_Canvas
-#Class SkDebugCanvas
-##
-##
-
-#Topic Debugging
-#Method void SkDebugf(const char format[], ...)
-##
-##
-
-#Topic Deferred_Display_List
-#Class SkDeferredDisplayList
-##
-#Subtopic Recorder
-#Class SkDeferredDisplayListRecorder
-##
-##
-##
-
-#Topic Deserial_Procs
-#Struct SkDeserialProcs
-#Member SkDeserialPictureProc   fPictureProc
-##
-#Member void*   fPictureCtx
-##
-#Member SkDeserialTypefaceProc   fTypefaceProc
-##
-#Member void*   fTypefaceCtx
-##
-##
-##
-
-#Topic Device
-#Class SkBaseDevice
-##
-##
-
-#Topic Document
-#Class SkDocument
-    #Method SkCanvas* beginPage(SkScalar width, SkScalar height,
-                        const SkRect* content = NULL)
-    ##
-##
-#Subtopic PDF
-##
-##
-
-#Topic Draw_Layer
-##
-
-#Topic Draw_Looper
-#Class SkDrawLooper
-#Class ##
-##
-
-#Topic Drawable
-#Class SkDrawable
-    #Method void draw(SkCanvas*, const SkMatrix* = NULL)
-    ##
-##
-##
-
-# to be in topic Encoded_Image_Format
-#EnumClass SkEncodedImageFormat
-    #Const kUnknown 0
-    ##
-    #Const kBMP 1
-    ##
-    #Const kGIF 2
-    ##
-    #Const kICO 3
-    ##
-    #Const kJPEG 4
-    ##
-    #Const kPNG 5
-    ##
-    #Const kWBMP 6
-    ##
-    #Const kWEBP 7
-    ##
-    #Const kPKM 8
-    ##
-    #Const kKTX 9
-    ##
-    #Const kASTC 10
-    ##
-    #Const kDNG 11
-    ##
-    #Const kHEIF 12
-    ##
-##
-# end of topic Encoded_Image_Format
-
-#Topic Filter_Quality
-#Enum SkFilterQuality
-    #Const kNone_SkFilterQuality 0
-    ##
-    #Const kLow_SkFilterQuality 1
-    ##
-    #Const kMedium_SkFilterQuality 2
-    ##
-    #Const kHigh_SkFilterQuality 3
-    ##
-#Enum ##
-#Subtopic Nearest_Neighbor
-#Substitute nearest neighbor filter
-##
-#Subtopic Bilerp
-#Substitute bilerp filter
-##
-#Subtopic MipMap
-#Substitute mip-map filter
-##
-#Subtopic BiCubic
-#Substitute bicubic filter
-##
-#Topic ##
-
-#Topic Text_Encoding
-#Enum SkTextEncoding
-#Const kUTF8_SkTextEncoding 0
-##
-#Const kUTF16_SkTextEncoding 1
-##
-#Const kUTF32_SkTextEncoding 2
-##
-#Const kGlyphID_SkTextEncoding 3
-##
-TextEncoding determines whether text specifies character codes and their encoded
-size, or glyph indices. Characters are encoded as specified by the
-#A Unicode standard # https://unicode.org/standard/standard.html ##
-.
-
-Character codes encoded size are specified by UTF-8, UTF-16, or UTF-32.
-All character code formats are able to represent all of Unicode, differing only
-in the total storage required.
-
-#A UTF-8 (RFC 3629) # https://tools.ietf.org/html/rfc3629 ##
-encodes each character as one or more 8-bit bytes.
-
-#A UTF-16 (RFC 2781) # https://tools.ietf.org/html/rfc2781 ##
-encodes each character as one or two 16-bit words.
-
-#A UTF-32 # https://www.unicode.org/versions/Unicode5.0.0/ch03.pdf ##
-encodes each character as one 32-bit word.
-
-Font_Manager uses font data to convert character code points into glyph indices.
-A glyph index is a 16-bit word.
-#Enum SkTextEncoding ##
-#Topic Text_Encoding ##
-
-#Topic Font_Hinting
-#Line # glyph outline adjustment ##
-#EnumClass SkFontHinting
-#Const kNone 0
-#Line # glyph outlines unchanged ##
-    Leaves glyph outlines unchanged from their native representation.
-    With FreeType, this is equivalent to the FT_LOAD_NO_HINTING
-    bit-field constant supplied to FT_Load_Glyph, which indicates that the vector
-    outline being loaded should not be fitted to the pixel grid but simply scaled
-    to 26.6 fractional pixels.
-##
-#Const kSlight 1
-#Line # minimal modification to improve contrast ##
-    Modifies glyph outlines minimally to improve contrast.
-    With FreeType, this is equivalent in spirit to the
-    FT_LOAD_TARGET_LIGHT value supplied to FT_Load_Glyph. It chooses a
-    lighter hinting algorithm for non-monochrome modes.
-    Generated Glyphs may be fuzzy but better resemble their original shape.
-##
-#Const kNormal 2
-#Line # glyph outlines modified to improve contrast ##
-    Modifies glyph outlines to improve contrast. This is the default.
-    With FreeType, this supplies FT_LOAD_TARGET_NORMAL to FT_Load_Glyph,
-    choosing the default hinting algorithm, which is optimized for standard
-    gray-level rendering.
-##
-#Const kFull 3
-#Line # modifies glyph outlines for maximum contrast ##
-    Modifies glyph outlines for maximum contrast. With FreeType, this selects
-    FT_LOAD_TARGET_LCD or FT_LOAD_TARGET_LCD_V if kLCDRenderText_Flag is set.
-    FT_LOAD_TARGET_LCD is a variant of FT_LOAD_TARGET_NORMAL optimized for
-    horizontally decimated LCD displays; FT_LOAD_TARGET_LCD_V is a
-    variant of FT_LOAD_TARGET_NORMAL optimized for vertically decimated LCD displays.
-##
-#EnumClass SkFontHinting ##
-#Topic Font_Hinting ##
-
-#Topic Font_Metrics
-#Struct SkFontMetrics
-#Line # values computed by Font_Manager using Typeface ##
-
-    SkFontMetrics is filled out by SkPaint::getFontMetrics. SkFontMetrics contents
-    reflect the values
-    computed by Font_Manager using Typeface. Values are set to zero if they are
-    not available.
-
-    All vertical values are relative to the baseline, on a y-axis pointing down.
-    Zero is on the baseline, negative values are above the baseline, and positive
-    values are below the baseline.
-
-    fUnderlineThickness and fUnderlinePosition have a bit set in fFlags if their values
-    are valid, since their value may be zero.
-
-    fStrikeoutThickness and fStrikeoutPosition have a bit set in fFlags if their values
-    are valid, since their value may be zero.
-
-    #Enum FontMetricsFlags
-    #Line # valid Font_Metrics ##
-
-    FontMetricsFlags are set in fFlags when underline and strikeout metrics are valid;
-    the underline or strikeout metric may be valid and zero.
-    Fonts with embedded bitmaps may not have valid underline or strikeout metrics.
-
-        #Const kUnderlineThicknessIsValid_Flag 0x0001
-        #Line # set if fUnderlineThickness is valid ##
-        ##
-        #Const kUnderlinePositionIsValid_Flag  0x0002
-        #Line # set if fUnderlinePosition is valid ##
-        ##
-        #Const kStrikeoutThicknessIsValid_Flag 0x0004
-        #Line # set if fStrikeoutThickness is valid ##
-        ##
-        #Const kStrikeoutPositionIsValid_Flag  0x0008
-        #Line # set if fStrikeoutPosition is valid ##
-        ##
-
-    #Enum ##
-
-    #Member uint32_t    fFlags
-    #Line # is set to FontMetricsFlags when metrics are valid ##
-    ##
-
-    #Member SkScalar    fTop
-    #Line # extent above baseline ##
-        Greatest extent above the baseline for any glyph.
-        Typically less than zero.
-    ##
-
-    #Member SkScalar    fAscent
-    #Line # distance to reserve above baseline ##
-        Recommended distance above the baseline to reserve for a line of text.
-        Typically less than zero.
-    ##
-
-    #Member SkScalar    fDescent
-    #Line # distance to reserve below baseline ##
-        Recommended distance below the baseline to reserve for a line of text.
-        Typically greater than zero.
-    ##
-
-    #Member SkScalar    fBottom
-    #Line # extent below baseline ##
-        Greatest extent below the baseline for any glyph.
-        Typically greater than zero.
-    ##
-
-    #Member SkScalar    fLeading
-    #Line # distance to add between lines ##
-        Recommended distance to add between lines of text.
-        Typically greater than or equal to zero.
-    ##
-
-    #Member SkScalar    fAvgCharWidth
-    #Line # average character width ##
-        Average character width, if it is available.
-        Zero if no average width is stored in the font.
-    ##
-
-    #Member SkScalar    fMaxCharWidth
-    #Line # maximum character width ##
-    ##
-
-    #Member SkScalar    fXMin
-    #Line # minimum x ##
-        Minimum bounding box x-axis value for all Glyphs.
-        Typically less than zero.
-    ##
-
-    #Member SkScalar    fXMax
-    #Line # maximum x ##
-        Maximum bounding box x-axis value for all Glyphs.
-        Typically greater than zero.
-    ##
-
-    #Member SkScalar    fXHeight
-    #Line # height of lower-case 'x' ##
-        May be zero if no lower-case height is stored in the font.
-    ##
-
-    #Member SkScalar    fCapHeight
-    #Line # height of an upper-case letter ##
-        May be zero if no upper-case height is stored in the font.
-    ##
-
-    #Member SkScalar    fUnderlineThickness
-    #Line # underline thickness ##
-        If the metric is valid, the kUnderlineThicknessIsValid_Flag is set in fFlags.
-        If kUnderlineThicknessIsValid_Flag is clear, fUnderlineThickness is zero.
-    ##
-
-    #Member SkScalar    fUnderlinePosition
-    #Line # underline position relative to baseline ##
-       Position of the top of the underline stroke relative to the baseline.
-       Typically positive when valid.
-
-       If the metric is valid, the kUnderlinePositionIsValid_Flag is set in fFlags.
-       If kUnderlinePositionIsValid_Flag is clear, fUnderlinePosition is zero.
-    ##
-
-    #Member SkScalar    fStrikeoutThickness
-    #Line # strikeout thickness ##
-
-        If the metric is valid, the kStrikeoutThicknessIsValid_Flag is set in fFlags.
-        If kStrikeoutThicknessIsValid_Flag is clear, fStrikeoutThickness is zero.
-    ##
-
-    #Member SkScalar    fStrikeoutPosition
-    #Line # strikeout position relative to baseline ##
-        Position of the bottom of the strikeout stroke relative to the baseline.
-        Typically negative when valid.
-
-        If the metric is valid, the kStrikeoutPositionIsValid_Flag is set in fFlags.
-        If kStrikeoutPositionIsValid_Flag is clear, fStrikeoutPosition is zero.
-    ##
-
-    #Method bool hasUnderlineThickness(SkScalar* thickness) const
-    #Line # returns underline thickness if set ##
-
-#NoExample
-        ##
-    ##
-
-    #Method bool hasUnderlinePosition(SkScalar* position) const
-    #Line # returns underline position if set ##
-
-#NoExample
-        ##
-    ##
-
-    #Method bool hasStrikeoutThickness(SkScalar* thickness) const
-    #Line # returns strikeout thickness if set ##
-
-#NoExample
-        ##
-    ##
-
-    #Method bool hasStrikeoutPosition(SkScalar* position) const
-    #Line # returns strikeout position if set ##
-
-#NoExample
-        ##
-    ##
-
-#Struct ##
-#Topic ##
-
-#Topic Font_Manager
-#Topic ##
-
-#Topic Glyph
-#Subtopic ID
-##
-#Alias Glyphs ##
-##
-
-#Topic GPU_Context
-#Substitute GPU context
-#Class GrContext
-#Method void abandonContext()
-##
-#Method void flush()
-##
-##
-##
-
-#Topic GPU_Surface
-#Substitute GPU surface
-##
-
-#Topic GPU_Texture
-#Substitute GPU texture
-##
-
-#Topic HTML_Canvas
-    #Substitute HTML Canvas
-    #Subtopic ArcTo
-        #Substitute HTML Canvas arcTo
-    ##
-##
-
-#Topic Image_Filter
-#Class SkImageFilter
-#Class ##
-#Topic ##
-
-#Class SkImageGenerator
-##
-
-#Topic Image_Scaling
-##
-
-#Topic ISize
-#Struct SkISize
-#Method int32_t width() const
-##
-#Method int32_t height() const
-##
-##
-##
-
-#Topic Left_Side_Bearing
-##
-
-#Topic Line
-#Alias Lines ##
-#Topic ##
-
-# to be in Topic Malloc_Pixel_Ref
-#Class SkMallocPixelRef
-    #Method static sk_sp<SkPixelRef> MakeZeroed(const SkImageInfo&, size_t rowBytes)
-    ##
-    #Method static sk_sp<SkPixelRef> MakeAllocate(const SkImageInfo&, size_t rowBytes)
-    ##
-##
-# end of Topic Malloc_Pixel_Ref
-
-#Struct SkMask
-##
-
-#Topic Mask_Alpha
-#Topic ##
-
-#Topic Mask_Filter
-#Class SkMaskFilter
-#Class ##
-#Topic ##
-
-# to be in Topic Math
-    #Method Luminosity(dstColor)
-    # this permits using Luminosity() in documentation
-    ##
-    #Method Saturation(dstColor)
-    # this permits using Saturation() in documentation
-    ##
-    #Method SetLuminosity(srcSaturation, dstLuminosity)
-    # this permits using SetLuminosity() in documentation
-    ##
-    #Method SetSaturation(srcSaturation, dstSaturation)
-    # this permits using SetSaturation() in documentation
-    ##
-    #Method SkIntToScalar(x)
-    ##
-    #Method SkScalarRoundToInt(x)
-    ##
-    #Method SkScalarFloorToInt(x)
-    ##
-    #Method SkScalarCeilToInt(x)
-    ##
-    #Method SkScalarFloorToScalar(x)
-    ##
-    #Method SkScalarCeilToScalar(x)
-    ##
-    #Method SkScalarIsFinite(x)
-    ##
-    #Method SkScalarIsNaN(x)
-    ##
-    #Method template <typename D, typename S> inline bool SkTFitsIn(S s)
-    ##
-    #Method float abs(float x)
-    # this permits using abs(x) in documentation
-    ##
-    #Method float max(float x)
-    # this permits using max(x) in documentation
-    ##
-    #Method float min(float x)
-    # this permits using max(x) in documentation
-    ##
-    #Method float sizeof(float x)
-    # this permits using sizeof(x) in documentation
-    ##
-    #Method float sqrt(float x)
-    # this permits using sqrt(x) in documentation
-    ##
-    #Method static inline bool sk_64_isS32(int64_t value)
-    ##
-# end of Topic Math
-
-#Topic Mip_Map
-#Substitute mip map
-##
-
-#Topic Nine_Patch
-##
-
-# to be in Topic Number_Types ?
-    #Typedef uint16_t SkGlyphID
-    #Typedef ##
-    #Topic Scalar
-    #Alias Scalars ##
-    #Typedef float SkScalar
-    #Typedef ##
-    ##
-    #Const SK_MinS32FitsInFloat
-    to be written
-    ##
-    #Const SK_MaxS32FitsInFloat
-    to be written
-    ##
-    #Const SK_ScalarMin
-    to be written
-    ##
-    #Const SK_ScalarMax
-    to be written
-    ##
-    #Const SK_ScalarInfinity
-    to be written
-    ##
-    #Const SK_ScalarNegativeInfinity
-    to be written
-    ##
-    #Const SK_ScalarNaN
-    to be written
-    ##
-    #Const SK_ScalarNearlyZero
-    to be written
-    ##
-    #Const SK_MinS32
-    to be written
-    ##
-    #Const SK_MaxS32
-    to be written
-    ##
-    #Typedef int32_t SkUnichar
-    #Typedef ##
-    #Typedef unsigned U8CPU
-    #Typedef ##
-# end of Topic Number_Types
-
-#Topic OS_X
-#Substitute OS X
-##
-
-#Topic Oval
-#Alias Ovals ##
-#Topic ##
-
-#Topic Paint_Defaults
-#Const SkPaintDefaults_Flags 0
-##
-#Const SkPaintDefaults_Hinting 2
-##
-#Const SkPaintDefaults_TextSize 12
-##
-#Const SkPaintDefaults_MiterLimit 4
-##
-#Topic ##
-
-#Topic Patch
-#Alias Patches ##
-#Topic ##
-
-#Topic Path_Effect
-    #Class SkPathEffect
-    #Class ##
-#Topic ##
-
-# to be in Topic Path_Measure
-    #Class SkPathMeasure
-            #Method void dump() const
-            ##
-    ##
-# end of Topic Path_Measure
-
-#Topic PathOps
-    #Enum SkPathOp
-    ##
-    #Method bool Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result)
-    ##
-#Topic ##
-
-#Topic Picture_Recorder
-    #Class SkPictureRecorder
-        #Method SkCanvas* beginRecording(const SkRect& bounds,
-                             SkBBHFactory* bbhFactory = NULL,
-                             uint32_t recordFlags = 0)
-        ##
-    ##
-##
-
-#Topic Pixel
-#Subtopic Storage
-##
-##
-
-#Topic Pixel_Ref
-#Class SkPixelRef
-    #Method int width() const
-    ##
-    #Method int height() const
-    ##
-    #Method bool isImmutable() const
-    ##
-    #Method void setImmutable()
-    ##
-##
-##
-
-#Enum SkPixelGeometry
-##
-
-#Topic Point3
-#Struct SkPoint3
-##
-#Topic ##
-
-#Topic PostScript
-#Substitute PostScript
-#Subtopic Arct
-#Substitute PostScript arct
-##
-##
-
-#Topic Premultiply
-#Alias Premultiplied ##
-#Alias Premultiplies ##
-##
-
-#Topic Raster_Engine
-##
-
-#Topic Raster_Handle_Allocator
-#Class SkRasterHandleAllocator
-    #Typedef void* Handle
-    ##
-    #Struct Rec
-    ##
-    #Method static std::unique_ptr<SkCanvas> MakeCanvas(std::unique_ptr<SkRasterHandleAllocator>, const SkImageInfo&, const Rec* rec = nullptr)
-    ##
-##
-##
-
-#Topic Raster_Bitmap
-##
-
-#Topic Raster_Surface
-##
-
-# to be in Topic Rasterizer
-#Class SkRasterizer
-#Class ##
-# end of Topic Rasterizer
-
-#Topic Reference_Count
-#Class SkRefCnt
-#Class ##
-
-#Template SkNVRefCnt
-##
-##
-
-#Topic Smart_Pointer
-#Class sk_sp
-#Class ##
-#Topic ##
-
-#Topic Render_Target
-#Class GrRenderTarget
-##
-##
-
-#Topic Right_Side_Bearing
-##
-
-#Topic RSXform
-#Struct SkRSXform
-#Member SkScalar fSCos
-##
-#Member SkScalar fSSin
-##
-#Member SkScalar fTx
-##
-#Member SkScalar fTy
-##
-##
-##
-
-#Topic Serial_Procs
-#Struct SkSerialProcs
-#Member SkSerialPictureProc   fPictureProc
-##
-#Member void*   fPictureCtx
-##
-#Member SkSerialTypefaceProc   fTypefaceProc
-##
-#Member void*   fTypefaceCtx
-##
-##
-##
-
-#Topic Shader
-#Class SkShader
-    #Enum TileMode
-        #Const kClamp_TileMode 0
-        ##
-        #Const kRepeat_TileMode 1
-        ##
-        #Const kMirror_TileMode 2
-        ##
-    ##
-    #Method static sk_sp<SkShader> MakeBitmapShader(const SkBitmap& src, TileMode tmx, TileMode tmy,
-                                            const SkMatrix* localMatrix = nullptr)
-    ##
-    #Method static sk_sp<SkShader> MakeCompose(sk_sp<SkShader> dst, sk_sp<SkShader> src,
-                                       SkBlendMode mode, float lerp = 1)
-    ##
-#Class ##
-#Topic ##
-
-#Topic Size
-#Struct SkSize
-#Method int32_t width() const
-##
-#Method int32_t height() const
-##
-##
-##
-
-#Topic Sprite
-#Alias Sprites ##
-#Topic ##
-
-#Topic Stream_Asset
-#Class SkStreamAsset
-##
-#Topic ##
-
-#Topic String
-#Class SkString
-#Class ##
-#Topic ##
-
-#Topic Supersampling
-##
-
-#Topic Surface_Characterization
-#Class SkSurfaceCharacterization
-##
-##
-
-#Topic Surface_Properties
-    #Class SkSurfaceProps
-        #Subtopic Legacy_Font_Host
-        #Enum InitType
-            #Const kLegacyFontHost_InitType 0
-            ##
-        ##
-        #Topic ##
-    ##
-##
-
-#Topic SVG
-#Subtopic Canvas
-##
-#Subtopic Arc
-##
-#Subtopic Sweep_Flag
-#Substitute SVG sweep-flag
-##
-##
-
-#Topic Text
-#Topic ##
-
-#Topic Texture
-#Class GrBackendTexture
-    #Method bool isValid() const
-    ##
-##
-##
-
-#Topic YUV_Planes
-#Class SkYUVAIndex
-##
-##
-
-#Topic RenderTarget
-#Class GrBackendRenderTarget
-    #Method bool isValid() const
-    ##
-##
-##
-
-#Topic Transfer_Mode
-##
-
-#Topic Typeface
-#Subtopic ID
-#Typedef uint32_t SkFontID
-##
-##
-#Class SkTypeface
-    #Method SkFontID uniqueID() const
-    ##
-#Class ##
-#Topic ##
-
-# to be defined in types
-    #Typedef intptr_t GrBackendObject
-    #Typedef ##
-
-    #EnumClass GrMipMapped
-    #Const kNo 0
-    ##
-    #Const kYes 1
-    ##
-    #EnumClass ##
-
-    #Enum GrSurfaceOrigin
-    #Const kBottomLeft_GrSurfaceOrigin 0
-    ##
-    #Const kTopLeft_GrSurfaceOrigin 1
-    ##
-    #Enum ##
-
-    #EnumClass SkBudgeted
-    #Const kNo 0
-    ##
-    #Const kYes 1
-    ##
-    #EnumClass ##
-
-    #EnumClass GrSemaphoresSubmitted
-    #Const kNo 0
-    ##
-    #Const kYes 1
-    ##
-    #EnumClass ##
-# end of defined in types
-
-#Topic Unpremultiply
-#Alias Unpremultiplied ##
-##
-
-#Topic Vertices
-#Class SkVertices
-#Class Bone
-##
-##
-#Subtopic Colors
-##
-#Subtopic Texs
-##
-#Topic ##
-
-#Topic Xfermode_Image_Filter
-#Class SkXfermodeImageFilter
-##
-##
diff --git a/docs/usingBookmaker.bmh b/docs/usingBookmaker.bmh
deleted file mode 100644
index a3413b20..0000000
--- a/docs/usingBookmaker.bmh
+++ /dev/null
@@ -1,344 +0,0 @@
-#External
-SkXXX.md 
-SkXXX.bmh
-docs/SkIRect_Reference.bmh
-docs/SkSurface_Reference.bmh
-docs/status.json
-include/core/SkSurface.h
-SkSurface.h
-fiddle.json
-fiddleout.json
-status.json
-CL
-Go
-Visual_Studio
-Completed InProgress
-skia.org
-##
-
-#Topic Bookmaker
-
-Bookmaker generates markdown files to view documentation on skia.org, and generates includes for use in C++.
-Bookmaker reads canonical documentation from files suffixed with bmh in the docs directory. These bmh
-files describe how public interfaces work, and generate Skia fiddle examples to illustrate them.
-
-The docs files must be manually edited to stay current with Skia as it evolves.
-
-#Subtopic Installing
-
-Install
-#A Go # https://golang.org/doc/install ##
- if needed.
-Check the version. The results should be 1.10 or greater.
-
-#Code
-$ go version
-##
-
-Get the fiddle command line interface tool.
-By default this will appear in your home directory.
-
-#Code
-$ go get go.skia.org/infra/fiddlek/go/fiddlecli
-##
-
-Check the version. The command should work and the result should be 1.0 or greater.
-
-#Code
-$ ~/go/bin/fiddlecli --version
-##
-
-If fiddlecli is already installed but out of date, update with:
-
-#Code
-$ go get -u go.skia.org/infra/fiddlek/go/fiddlecli
-##
-
-Build Bookmaker.
-
-#Code
-$ ninja -C out/skia bookmaker
-##
-
-#Subtopic Installing ##
-
-#Subtopic Running
-
-Bookmaker extracts examples, generates example hashes with fiddle, and generates web markdown
-and c++ includes.
-
-#Code
-$ ./out/skia/bookmaker -E && ~/go/bin/fiddlecli --quiet && ./out/skia/bookmaker
-##
-
-A successful run generates:
-
-#Code
-cross-check...................
-##
-
-#Subtopic Running ##
-
-#Subtopic Broken_Build
-
-The bots 
-#A Housekeeper-PerCommit-Bookmaker # https://status.skia.org/repo/skia?filter=search&search_value=Housekeeper-PerCommit-Bookmaker ##
-and
-#A Housekeeper-Nightly-Bookmaker # https://status.skia.org/repo/skia?filter=search&search_value=Housekeeper-Nightly-Bookmaker ##
-verify that Bookmaker data in docs builds without error and is consistent with include files it documents.
-
-Possible failures include:
-#List
-# Public interface in include directory does not match documented interface in docs directory. ##
-# Example in bookmaker bmh file does not compile, or does not produce expected output. ##
-# Undocumented but referenced interface is missing from undocumented bookmaker file in docs directory. ##
-##
-
-Editing comments in includes or editing private interfaces will not break the bots.
-Bookmaker detects that comments edited in includes do not match comments in docs; it will generate an updated include in the
-directory where it is run.
-
-If 
-#A Housekeeper-PerCommit-Bookmaker # https://status.skia.org/repo/skia?filter=search&search_value=Housekeeper-PerCommit-Bookmaker ##
-bot is red, the error is usually related to an edit to an include which has not been reflected in docs.
-
-To fix this, edit the docs file corresponding to the changed include file.
-
-For instance, if the change was made to SkIRect, edit docs/SkIRect_Reference.bmh.
-Checking in the edited docs/SkIRect_Reference.bmh will fix the bot.
-
-If the interface is deprecated, private, or experimental, documentation is not
-required. Put the word "Deprecated", "Private", or "Experimental"; upper or lower
-case, in a comment just before the symbol to be ignored.
-
-If
-#A Housekeeper-Nightly-Bookmaker # https://status.skia.org/repo/skia?filter=search&search_value=Housekeeper-Nightly-Bookmaker ##
- bot is red, one of several things may have gone wrong:
-
-#List
-# A change to include broke documentation examples. ##
-# Something changed the examples that output text. ##
-# Some interface was added, deleted, edited. ##
-# Documentation is malformed. ##
-##
-
-The bot output describes what changed, and includes the file and line
-where the error occurred.
-
-To regenerate the documentation, follow the Installing and Regenerate steps below.
-
-#Subtopic Broken_Build ##
-
-#Subtopic Editing_Comments
-
-Edit docs instead of include/core files to update comments if possible.
-
-The Bookmaker bots do not complain if the docs file does not match the
-corresponding include comments. Running Bookmaker include generation will
-report when docs and includes comments do not match.
-
-For instance, if include/core/SkSurface.h comments do not match
-docs/SkSurface_Reference.bmh, running:
-
-#Code
-$ ./out/dir/bookmaker -b docs -i include/core/SkSurface.h -p
-##
-
-generates
-
-#Code
-wrote updated SkSurface.h
-##
-
-The updated SkSurface.h is written to the root to avoid subsequent runs of
-Bookmaker from recompiling. if SkSurface.h was not changed, it is not written,
-and Bookmaker will not generate any output.
-
-#Subtopic Editing_Comments ##
-
-#Subtopic Broken_Example
-
-An example may cause Bookmaker or a bot running Bookmaker to fail if it fails to compile.
-
-Fix the example by pasting it into #A Skia Fiddle # https://fiddle.skia.org ##
-and editing it until it runs successfully.
-
-If the example cannot be fixed, it can be commented out by changing
-###$
-$Code
-$Literal
-#Example
-$Code $$
-to
-$Code
-#NoExample
-$Code $$
-$$$#
-. The disabled example can contain additional markup, which will be ignored.
-
-#Subtopic Broken_Example ##
-
-#Subtopic Regenerate
-
-Complete rebuilding of all bookmaker output looks like:
-
-#Code
-$ ./out/dir/bookmaker -a docs/status.json -e fiddle.json
-$ ~/go/bin/fiddlecli --input fiddle.json --output fiddleout.json
-$ ./out/dir/bookmaker -a docs/status.json -f fiddleout.json -r site/user/api -c
-$ ./out/dir/bookmaker -a docs/status.json -f fiddleout.json -r site/user/api
-$ ./out/dir/bookmaker -a docs/status.json -x
-$ ./out/dir/bookmaker -a docs/status.json -p
-##
-
-#Subtopic Regenerate ##
-
-#Subtopic New_Documentation
-
-Generate an starter Bookmaker file from an existing include.
-
-#Code
-$ ./out/dir/bookmaker -i include/core/SkXXX.h -t docs
-##
-
-If a method or function has an unnamed parameter, bookmaker generates an error:
-
-#Code
-###$
-C:/puregit/include/core/SkPixmap.h(208): error: #Method missing param name
-bool erase(const SkColor4f&, const SkIRect* subset = nullptr) const
-           ^
-$$$#
-##
-
-All parameters require names to allow markdown and doxygen documents to refer to
-them. After naming all parameters, check in the include before continuing.
-
-A successful run generates
-#Code
-docs/SkXXX_Reference.bmh
-##
-.
-
-Next, use your favorite editor to fill out
-#Code
-docs/SkXXX_Reference.bmh
-##
-.
-
-##
-
-#Subtopic Style
-
-Documentation consists of cross references, descriptions, and examples.
-All structs, classes, enums, their members and methods, functions, and so on,
-require descriptions. Most also require examples.
-
-All methods and functions should include examples if practical.
-It's difficult to think of a meaningful example for class destructors.
-In cases like these, change the placeholder:
-
-###$
-$Code
-#Example
-$$
-
-to:
-
-$Code
-#NoExample
-$$
-$$$#
-
-Descriptions start with an active verb. Descriptions are complete, punctuated
-sentences unless they describe parameters or return values. Parameters and
-returned values are described by phrases that start lower case and do not
-include trailing punctuation.
-
-Descriptions are not self-referential; they do not include the thing they
-describe. Descriptions may contain upper case or camel case references to
-definitions but otherwise should be free of jargon.
-
-Descriptions may contain code and formulas, each bracketed by markup.
-
-Similar items may be grouped into topics. Topics may include subtopics.
-
-Each document begins with one or more indices that include the contents of
-that file. A class reference includes an index listing contained topics,
-a separate listing for constructors, one for methods, and so on.
-
-Class methods contain a description, any parameters, any return value,
-an example, and any cross references.
-
-Each method must contain either one or more examples or markup indicating
-that there is no example.
-
-After editing is complete, searching for "incomplete" should fail,
-assuming "incomplete" is not the perfect word to use in a description or
-example!
-
-#Subtopic Style ##
-
-#Subtopic Adding_Documentation
-
-Generate fiddle.json from all examples, including the ones you just wrote.
-Error checking is syntatic: starting keywords are closed, keywords have the
-correct parents.
-If you run Bookmaker inside Visual_Studio, you can click on errors and it
-will take you to the source line in question.
-
-#Code
-$ ./out/dir/bookmaker -e fiddle.json -b docs
-##
-
-Once complete, run fiddlecli to generate the example hashes.
-Errors are contained by the output but aren't reported yet.
-
-#Code
-$ $GOPATH/bin/fiddlecli --input fiddle.json --output fiddleout.json
-##
-
-Generate SkXXX.md from SkXXX.bmh and fiddleout.json.
-Error checking includes: undefined references, fiddle compiler errors,
-missing or mismatched printf output.
-Again, you can click on any errors inside Visual_Studio.
-
-#Code
-$ ./out/dir/bookmaker -r site/user/api -b docs -f fiddleout.json
-##
-
-The original include may have changed since you started creating the markdown.
-Check to see if it is up to date.
-This reports if a method no longer exists or its parameters have changed.
-
-#Code
-$ ./out/dir/bookmaker -x -b docs/SkXXX.bmh -i include/core/SkXXX.h
-##
-
-Generate an updated include header. Run:
-
-#Code
-$ ./out/dir/bookmaker -p -b docs -i include/core/SkXXX.h
-##
-
-to write the updated SkXXX.h to the current directory.
-
-Once adding the file is complete, add the file to status.json in the
-Completed section. You may add it to the InProgress section during
-development, or leave status.json unchanged.
-
-If the new file has been added to status.json, you can run
-any of the above commands with -a docs/status.json in place of
--b docs or -i includes.
-
-#Subtopic Adding_Documentation ##
-
-#Subtopic Bugs
-
-Bookmaker bugs are tracked
-#A here # https://bug.skia.org/6898 ##
-.
-
-##
-
-#Topic Bookmaker ##
diff --git a/example/HelloWorld.cpp b/example/HelloWorld.cpp
index 812c631..6d88af9 100644
--- a/example/HelloWorld.cpp
+++ b/example/HelloWorld.cpp
@@ -7,11 +7,11 @@
 
 #include "HelloWorld.h"
 
-#include "GrContext.h"
 #include "SkCanvas.h"
 #include "SkFont.h"
 #include "SkGradientShader.h"
 #include "SkGraphics.h"
+#include "SkSurface.h"
 
 using namespace sk_app;
 
@@ -54,7 +54,9 @@
     fWindow->inval();
 }
 
-void HelloWorld::onPaint(SkCanvas* canvas) {
+void HelloWorld::onPaint(SkSurface* surface) {
+    auto canvas = surface->getCanvas();
+
     // Clear background
     canvas->clear(SK_ColorWHITE);
 
@@ -70,7 +72,7 @@
         SkPoint linearPoints[] = { { 0, 0 }, { 300, 300 } };
         SkColor linearColors[] = { SK_ColorGREEN, SK_ColorBLACK };
         paint.setShader(SkGradientShader::MakeLinear(linearPoints, linearColors, nullptr, 2,
-                                                     SkShader::kMirror_TileMode));
+                                                     SkTileMode::kMirror));
         paint.setAntiAlias(true);
 
         canvas->drawCircle(200, 200, 64, paint);
diff --git a/example/HelloWorld.h b/example/HelloWorld.h
index 28715fe..d052fe0 100644
--- a/example/HelloWorld.h
+++ b/example/HelloWorld.h
@@ -21,7 +21,7 @@
     void onIdle() override;
 
     void onBackendCreated() override;
-    void onPaint(SkCanvas* canvas) override;
+    void onPaint(SkSurface*) override;
     bool onChar(SkUnichar c, uint32_t modifiers) override;
 
 private:
diff --git a/experimental/canvaskit/CHANGELOG.md b/experimental/canvaskit/CHANGELOG.md
deleted file mode 100644
index 5fd1a52..0000000
--- a/experimental/canvaskit/CHANGELOG.md
+++ /dev/null
@@ -1,67 +0,0 @@
-# CanvasKit Changelog
-All notable changes to this project will be documented in this file.
-
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-
-## [Unreleased]
-
-### Added
- - `SkPath.addRoundRect`, `SkPath.reset`, `SkPath.rewind` exposed.
- - `SkCanvas.drawArc`, `SkCanvas.drawLine`, `SkCanvas.drawOval`, `SkCanvas.drawRoundRect` exposed.
- - Can import/export a SkPath to an array of commands. See `CanvasKit.MakePathFromCmds` and
-   `SkPath.toCmds`.
- - `SkCanvas.drawTextBlob()` and `SkCanvas.SkTextBlob.MakeFromText()` to draw text to a canvas.
- - `CanvasKit.TextEncoding` enum. For use with `SkTextBlob`.
-
-### Changed
- - `SkCanvas.drawText()` now requires an `SkFont` object.
-
-### Removed
- -  `SkPaint.setTextSize()`, `SkPaint.getTextSize()`, `SkPaint.setTypeface()`
-   which should be replaced by using `SkFont`.
-
-
-### Fixed
- - Potential bug in `ready()` if already loaded.
-
-## [0.3.1] - 2019-01-04
-### Added
- - `SkFont` now exposed.
- - `MakeCanvasSurface` can now take a canvas element directly.
- - `MakeWebGLCanvasSurface` can now take a WebGL context as an integer and use it directly.
-
-### Changed
- - `CanvasKitInit(...).then()` is no longer the recommended way to initialize things.
-It will be removed in 0.4.0. Use `CanvasKitInit(...).ready()`, which returns a real Promise.
-
-### Removed
-- `SkPaint.measureText` - use `SkFont.measureText` instead.
-
-## [0.3.0] - 2018-12-18
-
-### Added
-- Add Canvas2D JS layer. This mirrors the HTML Canvas API. This may be omitted at compile time
-    it by adding `no_canvas` to the `compile.sh` invocation.
-- `CanvasKit.FontMgr.DefaultRef()` and `fontmgr.MakeTypefaceFromData` to load fonts.
-- Exposed `SkPath.setVolatile`. Some animations see performance improvements by setting
-their paths' volatility to true.
-
-### Fixed
-- `SkPath.addRect` now correctly draws counter-clockwise vs clockwise.
-
-### Changed
-- `CanvasKit.MakeImageShader` no longer takes encoded bytes, but an `SkImage`, created from
-    `CanvasKit.MakeImageFromEncoded`. Additionally, the optional parameters `clampIfUnpremul`
-    and `localMatrix` have been exposed.
-- `SkPath.arcTo` now takes `startAngle`, `sweepAngle`, `forceMoveTo` as additional parameters.
-- `SkPath.stroke` has a new option `precision`  It defaults to 1.0.
-- CanvasKit comes with one font (NotoMono) instead of the Skia TestTypeface. Clients are encouraged
-  to use the new `fontmgr.MakeTypefaceFromData` for more font variety.
-
-### Removed
-- `CanvasKit.initFonts()` - no longer needed.
-
-
-## [0.2.1] - 2018-11-20
-Beginning of Changelog history
\ No newline at end of file
diff --git a/experimental/canvaskit/Makefile b/experimental/canvaskit/Makefile
deleted file mode 100644
index 0e65549..0000000
--- a/experimental/canvaskit/Makefile
+++ /dev/null
@@ -1,63 +0,0 @@
-clean:
-	rm -rf ../../out/canvaskit_wasm
-	rm -rf ./canvaskit/bin
-	$(MAKE) release
-
-release:
-	# Does an incremental build where possible.
-	./compile.sh
-	mkdir -p ./canvaskit/bin
-	cp ../../out/canvaskit_wasm/canvaskit.js   ./canvaskit/bin
-	cp ../../out/canvaskit_wasm/canvaskit.wasm ./canvaskit/bin
-
-release_cpu:
-	# Does an incremental build where possible.
-	./compile.sh cpu_only
-	mkdir -p ./canvaskit/bin
-	cp ../../out/canvaskit_wasm/canvaskit.js   ./canvaskit/bin
-	cp ../../out/canvaskit_wasm/canvaskit.wasm ./canvaskit/bin
-
-debug:
-	# Does an incremental build where possible.
-	./compile.sh debug
-	mkdir -p ./canvaskit/bin
-	cp ../../out/canvaskit_wasm_debug/canvaskit.js   ./canvaskit/bin
-	cp ../../out/canvaskit_wasm_debug/canvaskit.wasm ./canvaskit/bin
-	cp ../../out/canvaskit_wasm_debug/canvaskit.wasm.map ./canvaskit/bin
-
-debug_cpu:
-	# Does an incremental build where possible.
-	./compile.sh debug cpu_only
-	mkdir -p ./canvaskit/bin
-	cp ../../out/canvaskit_wasm_debug/canvaskit.js   ./canvaskit/bin
-	cp ../../out/canvaskit_wasm_debug/canvaskit.wasm ./canvaskit/bin
-	cp ../../out/canvaskit_wasm_debug/canvaskit.wasm.map ./canvaskit/bin
-
-profile:
-	./compile.sh profiling
-	mkdir -p ./canvaskit/bin
-	cp ../../out/canvaskit_wasm_profile/canvaskit.js       ./canvaskit/bin
-	cp ../../out/canvaskit_wasm_profile/canvaskit.wasm     ./canvaskit/bin
-	cp ../../out/canvaskit_wasm_profile/canvaskit.wasm.map ./canvaskit/bin
-
-profile_cpu:
-	./compile.sh profiling cpu_only
-	mkdir -p ./canvaskit/bin
-	cp ../../out/canvaskit_wasm_profile/canvaskit.js       ./canvaskit/bin
-	cp ../../out/canvaskit_wasm_profile/canvaskit.wasm     ./canvaskit/bin
-	cp ../../out/canvaskit_wasm_profile/canvaskit.wasm.map ./canvaskit/bin
-
-local-example:
-	rm -rf node_modules/canvaskit
-	mkdir -p node_modules
-	ln -s -T ../canvaskit node_modules/canvaskit
-	echo "Go check out http://localhost:8000/canvaskit/example.html"
-	python serve.py
-
-test-continuous:
-	echo "Assuming npm install has been run by user"
-	echo "Also assuming make debug or release has also been run by a user (if needed)"
-	npx karma start ./karma.conf.js --no-single-run --watch-poll
-
-node-example:
-	node ./canvaskit/node.example.js --expose-wasm
diff --git a/experimental/canvaskit/WasmAliases.h b/experimental/canvaskit/WasmAliases.h
deleted file mode 100644
index eff6e43..0000000
--- a/experimental/canvaskit/WasmAliases.h
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef WasmAliases_DEFINED
-#define WasmAliases_DEFINED
-
-#include <emscripten.h>
-#include <emscripten/bind.h>
-
-using namespace emscripten;
-
-// Self-documenting types
-using JSArray = emscripten::val;
-using JSColor = int32_t;
-using JSObject = emscripten::val;
-using JSString = emscripten::val;
-using SkPathOrNull = emscripten::val;
-using Uint8Array = emscripten::val;
-
-#endif
diff --git a/experimental/canvaskit/canvaskit/README.md b/experimental/canvaskit/canvaskit/README.md
deleted file mode 100644
index 70e4737..0000000
--- a/experimental/canvaskit/canvaskit/README.md
+++ /dev/null
@@ -1,106 +0,0 @@
-A WASM version of Skia's Canvas API.
-
-See https://skia.org/user/modules/canvaskit for more background information.
-
-# Getting Started
-
-## Browser
-To use the library, run `npm install canvaskit-wasm` and then simply include it:
-
-    <script src="/node_modules/canvaskit-wasm/bin/canvaskit.js"></script>
-    CanvasKitInit({
-        locateFile: (file) => '/node_modules/canvaskit-wasm/bin/'+file,
-    }).ready().then((CanvasKit) => {
-        // Code goes here using CanvasKit
-    });
-
-As with all npm packages, there's a freely available CDN via unpkg.com:
-
-    <script src="https://unpkg.com/canvaskit-wasm@0.3.0/bin/canvaskit.js"></script>
-    CanvasKitInit({
-         locateFile: (file) => 'https://unpkg.com/canvaskit-wasm@0.3.0/bin/'+file,
-    }).ready().then(...)
-
-## Node
-To use CanvasKit in Node, it's similar to the browser:
-
-    const CanvasKitInit = require('/node_modules/canvaskit-wasm/bin/canvaskit.js');
-    CanvasKitInit({
-        locateFile: (file) => __dirname + '/bin/'+file,
-    }).ready().then((CanvasKit) => {
-        // Code goes here using CanvasKit
-    });
-
-With node, you also need to supply the `--expose-wasm` flag.
-
-## WebPack
-
-WebPack's support for WASM is still somewhat experimental, but CanvasKit can be
-used with a few configuration changes.
-
-In the JS code, use require():
-
-    const CanvasKitInit = require('canvaskit-wasm/bin/canvaskit.js')
-    CanvasKitInit().ready().then((CanvasKit) => {
-        // Code goes here using CanvasKit
-    });
-
-Since WebPack does not expose the entire `/node_modules/` directory, but instead
-packages only the needed pieces, we have to copy canvaskit.wasm into the build directory.
-One such solution is to use [CopyWebpackPlugin](https://github.com/webpack-contrib/copy-webpack-plugin).
-For example, add the following plugin:
-
-    config.plugins.push(
-        new CopyWebpackPlugin([
-            { from: 'node_modules/canvaskit-wasm/bin/canvaskit.wasm' }
-        ])
-    );
-
-If webpack gives an error similar to:
-
-    ERROR in ./node_modules/canvaskit-wasm/bin/canvaskit.js
-    Module not found: Error: Can't resolve 'fs' in '...'
-
-Then, add the following configuration change to the node section of the config:
-
-    config.node = {
-        fs: 'empty'
-    };
-
-
-# Using the CanvasKit API
-
-See `example.html` and `node.example.js` for demos of how to use the API.
-
-More detailed docs will be coming soon.
-
-## Drop-in Canvas2D replacement
-For environments where an HTML canvas is not available (e.g. Node, headless servers),
-CanvasKit has an optional API (included by default) that mirrors the HTML canvas.
-
-    let skcanvas = CanvasKit.MakeCanvas(600, 600);
-
-    let ctx = skcanvas.getContext('2d');
-    let rgradient = ctx.createRadialGradient(200, 300, 10, 100, 100, 300);
-
-    // Add three color stops
-    rgradient.addColorStop(0, 'red');
-    rgradient.addColorStop(0.7, 'white');
-    rgradient.addColorStop(1, 'blue');
-
-    ctx.fillStyle = rgradient;
-    ctx.globalAlpha = 0.7;
-    ctx.fillRect(0, 0, 600, 600);
-
-    let imgData = skcanvas.toDataURL();
-    // imgData is now a base64 encoded image.
-
-See more examples in `example.html` and `node.example.js`.
-
-
-# Filing bugs
-
-Please file bugs at [skbug.com](skbug.com).
-It may be convenient to use [our online fiddle](jsfiddle.skia.org/canvaskit) to demonstrate any issues encountered.
-
-See CONTRIBUTING.md for more information on sending pull requests.
\ No newline at end of file
diff --git a/experimental/canvaskit/canvaskit/example.html b/experimental/canvaskit/canvaskit/example.html
deleted file mode 100644
index 0007881..0000000
--- a/experimental/canvaskit/canvaskit/example.html
+++ /dev/null
@@ -1,1081 +0,0 @@
-<!DOCTYPE html>
-<title>CanvasKit (Skia via Web Assembly)</title>
-<meta charset="utf-8" />
-<meta http-equiv="X-UA-Compatible" content="IE=edge">
-<meta name="viewport" content="width=device-width, initial-scale=1.0">
-
-<style>
-  svg, canvas, img {
-    border: 1px dashed #AAA;
-  }
-
-  #patheffect,#paths,#sk_drinks,#sk_party, #sk_legos, #sk_onboarding,
-  img, canvas {
-    width: 300px;
-    height: 300px;
-  }
-
-</style>
-
-<h2>Drop in replacement for HTML Canvas (e.g. node.js)</h2>
-<img id=api1 width=300 height=300>
-<canvas id=api1_c width=300 height=300></canvas>
-<img id=api2 width=300 height=300>
-<canvas id=api2_c width=300 height=300></canvas>
-<img id=api3 width=300 height=300>
-<canvas id=api3_c width=300 height=300></canvas>
-<img id=api4 width=300 height=300>
-<canvas id=api4_c width=300 height=300></canvas>
-<img id=api5 width=300 height=300>
-<canvas id=api5_c width=300 height=300></canvas>
-<img id=api6 width=300 height=300>
-<canvas id=api6_c width=300 height=300></canvas>
-<img id=api7 width=300 height=300>
-<canvas id=api7_c width=300 height=300></canvas>
-<img id=api8 width=300 height=300>
-<canvas id=api8_c width=300 height=300></canvas>
-
-<h2> CanvasKit draws Paths to the browser</h2>
-<canvas id=vertex1 width=300 height=300></canvas>
-<canvas id=vertex2 width=300 height=300></canvas>
-<canvas id=gradient1 width=300 height=300></canvas>
-<canvas id=patheffect width=300 height=300></canvas>
-<canvas id=paths width=200 height=200></canvas>
-<canvas id=ink width=300 height=300></canvas>
-
-<h2> Skottie </h2>
-<canvas id=sk_legos width=300 height=300></canvas>
-<canvas id=sk_drinks width=500 height=500></canvas>
-<canvas id=sk_party width=500 height=500></canvas>
-<canvas id=sk_onboarding width=500 height=500></canvas>
-
-<script type="text/javascript" src="/node_modules/canvaskit/bin/canvaskit.js"></script>
-
-<script type="text/javascript" charset="utf-8">
-
-  var CanvasKit = null;
-  var legoJSON = null;
-  var drinksJSON = null;
-  var confettiJSON = null;
-  var onboardingJSON = null;
-  var fullBounds = {fLeft: 0, fTop: 0, fRight: 500, fBottom: 500};
-
-  var robotoData = null;
-
-  var bonesImageData = null;
-  CanvasKitInit({
-    locateFile: (file) => '/node_modules/canvaskit/bin/'+file,
-  }).ready().then((CK) => {
-    CanvasKit = CK;
-    DrawingExample(CanvasKit, robotoData);
-    PathExample(CanvasKit);
-    InkExample(CanvasKit);
-    // Set bounds to fix the 4:3 resolution of the legos
-    SkottieExample(CanvasKit, 'sk_legos', legoJSON,
-                  {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
-    // Re-size to fit
-    SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds);
-    SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds);
-    SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds);
-
-    CanvasAPI1(CanvasKit);
-    CanvasAPI2(CanvasKit);
-    CanvasAPI3(CanvasKit);
-    CanvasAPI4(CanvasKit);
-    CanvasAPI5(CanvasKit);
-    CanvasAPI6(CanvasKit);
-    CanvasAPI7(CanvasKit);
-    CanvasAPI8(CanvasKit)
-
-    VertexAPI1(CanvasKit);
-    VertexAPI2(CanvasKit, bonesImageData);
-
-    GradiantAPI1(CanvasKit);
-  });
-
-  fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => {
-    resp.text().then((str) => {
-      legoJSON = str;
-      SkottieExample(CanvasKit, 'sk_legos', legoJSON,
-                    {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
-    });
-  });
-
-  fetch('https://storage.googleapis.com/skia-cdn/misc/drinks.json').then((resp) => {
-    resp.text().then((str) => {
-      drinksJSON = str;
-      SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds);
-    });
-  });
-
-  fetch('https://storage.googleapis.com/skia-cdn/misc/confetti.json').then((resp) => {
-    resp.text().then((str) => {
-      confettiJSON = str;
-      SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds);
-    });
-  });
-
-  fetch('https://storage.googleapis.com/skia-cdn/misc/onboarding.json').then((resp) => {
-    resp.text().then((str) => {
-      onboardingJSON = str;
-      SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds);
-    });
-  });
-
-  fetch('https://storage.googleapis.com/skia-cdn/misc/bones.jpg').then((resp) => {
-    resp.arrayBuffer().then((buffer) => {
-      bonesImageData = buffer;
-      VertexAPI2(CanvasKit, bonesImageData);
-    });
-  });
-
-  fetch('./Roboto-Regular.woff').then((resp) => {
-    resp.arrayBuffer().then((buffer) => {
-      robotoData = buffer;
-      DrawingExample(CanvasKit, robotoData)
-    });
-  });
-
-  function DrawingExample(CanvasKit, robotoData) {
-    if (!robotoData || !CanvasKit) {
-      return;
-    }
-    const surface = CanvasKit.MakeCanvasSurface('patheffect');
-    if (!surface) {
-      console.error('Could not make surface');
-      return;
-    }
-    const context = CanvasKit.currentContext(); // only needed for WebGL; no-op for CPU
-
-    const canvas = surface.getCanvas();
-
-    const paint = new CanvasKit.SkPaint();
-
-    const fontMgr = CanvasKit.SkFontMgr.RefDefault();
-    let roboto = fontMgr.MakeTypefaceFromData(robotoData);
-
-    const textPaint = new CanvasKit.SkPaint();
-    textPaint.setColor(CanvasKit.RED);
-    textPaint.setAntiAlias(true);
-
-    const textFont = new CanvasKit.SkFont(roboto, 30);
-
-    let i = 0;
-
-    let X = 128;
-    let Y = 128;
-
-    function drawFrame() {
-      const path = starPath(CanvasKit, X, Y);
-      // Some animations see performance improvements by marking their
-      // paths as volatile.
-      path.setIsVolatile(true);
-      CanvasKit.setCurrentContext(context);
-      const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], i/5);
-      i++;
-
-      paint.setPathEffect(dpe);
-      paint.setStyle(CanvasKit.PaintStyle.Stroke);
-      paint.setStrokeWidth(5.0 + -3 * Math.cos(i/30));
-      paint.setAntiAlias(true);
-      paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
-
-      canvas.clear(CanvasKit.TRANSPARENT);
-
-      canvas.drawPath(path, paint);
-      canvas.drawText('Try Clicking!', 10, 280, textFont, textPaint);
-
-      surface.flush();
-
-      dpe.delete();
-      path.delete();
-      window.requestAnimationFrame(drawFrame);
-    }
-    window.requestAnimationFrame(drawFrame);
-
-    // Make animation interactive
-    let interact = (e) => {
-      if (!e.pressure) {
-        return;
-      }
-      X = e.offsetX;
-      Y = e.offsetY;
-    };
-    document.getElementById('patheffect').addEventListener('pointermove', interact);
-    document.getElementById('patheffect').addEventListener('pointerdown', interact);
-    preventScrolling(document.getElementById('patheffect'));
-    // A client would need to delete this if it didn't go on for ever.
-    // paint.delete();
-    // textPaint.delete();
-    // textFont.delete();
-  }
-
-  function PathExample(CanvasKit) {
-    const surface = CanvasKit.MakeCanvasSurface('paths');
-    if (!surface) {
-      console.error('Could not make surface');
-      return;
-    }
-    const context = CanvasKit.currentContext();
-
-    const canvas = surface.getCanvas();
-
-    function drawFrame() {
-      CanvasKit.setCurrentContext(context);
-      const paint = new CanvasKit.SkPaint();
-      paint.setStrokeWidth(1.0);
-      paint.setAntiAlias(true);
-      paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
-      paint.setStyle(CanvasKit.PaintStyle.Stroke);
-
-      const path = new CanvasKit.SkPath();
-      path.moveTo(20, 5);
-      path.lineTo(30, 20);
-      path.lineTo(40, 10);
-      path.lineTo(50, 20);
-      path.lineTo(60, 0);
-      path.lineTo(20, 5);
-
-      path.moveTo(20, 80);
-      path.cubicTo(90, 10, 160, 150, 190, 10);
-
-      path.moveTo(36, 148);
-      path.quadTo(66, 188, 120, 136);
-      path.lineTo(36, 148);
-
-      path.moveTo(150, 180);
-      path.arcTo(150, 100, 50, 200, 20);
-      path.lineTo(160, 160);
-
-      path.moveTo(20, 120);
-      path.lineTo(20, 120);
-
-      canvas.drawPath(path, paint);
-
-      let rrect = new CanvasKit.SkPath()
-                               .addRoundRect(100, 10, 140, 62,
-                                             10, 4, true);
-
-      canvas.drawPath(rrect, paint);
-
-      surface.flush();
-
-      path.delete();
-      rrect.delete();
-      paint.delete();
-      // Intentionally just draw frame once
-    }
-    window.requestAnimationFrame(drawFrame);
-  }
-
-  function preventScrolling(canvas) {
-    canvas.addEventListener('touchmove', (e) => {
-      // Prevents touch events in the canvas from scrolling the canvas.
-      e.preventDefault();
-      e.stopPropagation();
-    });
-  }
-
-  function InkExample(CanvasKit) {
-    const surface = CanvasKit.MakeCanvasSurface('ink');
-    if (!surface) {
-      console.error('Could not make surface');
-      return;
-    }
-    const context = CanvasKit.currentContext();
-
-    const canvas = surface.getCanvas();
-
-    let paint = new CanvasKit.SkPaint();
-    paint.setAntiAlias(true);
-    paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
-    paint.setStyle(CanvasKit.PaintStyle.Stroke);
-    paint.setStrokeWidth(4.0);
-    paint.setPathEffect(CanvasKit.MakeSkCornerPathEffect(50));
-
-    // Draw I N K
-    let path = new CanvasKit.SkPath();
-    path.moveTo(80, 30);
-    path.lineTo(80, 80);
-
-    path.moveTo(100, 80);
-    path.lineTo(100, 15);
-    path.lineTo(130, 95);
-    path.lineTo(130, 30);
-
-    path.moveTo(150, 30);
-    path.lineTo(150, 80);
-    path.moveTo(170, 30);
-    path.lineTo(150, 55);
-    path.lineTo(170, 80);
-
-    let paths = [path];
-    let paints = [paint];
-
-    function drawFrame() {
-      CanvasKit.setCurrentContext(context);
-      canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
-
-      for (let i = 0; i < paints.length && i < paths.length; i++) {
-        canvas.drawPath(paths[i], paints[i]);
-      }
-      surface.flush();
-
-      window.requestAnimationFrame(drawFrame);
-    }
-
-    let hold = false;
-    let interact = (e) => {
-      let type = e.type;
-      if (type === 'lostpointercapture' || type === 'pointerup' || !e.pressure ) {
-        hold = false;
-        return;
-      }
-      if (hold) {
-        path.lineTo(e.offsetX, e.offsetY);
-      } else {
-        paint = paint.copy();
-        paint.setColor(CanvasKit.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() + .2));
-        paints.push(paint);
-        path = new CanvasKit.SkPath();
-        paths.push(path);
-        path.moveTo(e.offsetX, e.offsetY);
-      }
-      hold = true;
-    };
-    document.getElementById('ink').addEventListener('pointermove', interact);
-    document.getElementById('ink').addEventListener('pointerdown', interact);
-    document.getElementById('ink').addEventListener('lostpointercapture', interact);
-    document.getElementById('ink').addEventListener('pointerup', interact);
-    preventScrolling(document.getElementById('ink'));
-    window.requestAnimationFrame(drawFrame);
-  }
-
-  function starPath(CanvasKit, X=128, Y=128, R=116) {
-    let p = new CanvasKit.SkPath();
-    p.moveTo(X + R, Y);
-    for (let i = 1; i < 8; i++) {
-      let a = 2.6927937 * i;
-      p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
-    }
-    return p;
-  }
-
-  function fps(frameTimes) {
-    let total = 0;
-    for (let ft of frameTimes) {
-      total += ft;
-    }
-    return frameTimes.length / total;
-  }
-
-  function SkottieExample(CanvasKit, id, jsonStr, bounds) {
-    if (!CanvasKit || !jsonStr) {
-      return;
-    }
-    const animation = CanvasKit.MakeManagedAnimation(jsonStr);
-    const duration = animation.duration() * 1000;
-    const size = animation.size();
-    let c = document.getElementById(id);
-    bounds = bounds || {fLeft: 0, fTop: 0, fRight: size.w, fBottom: size.h};
-
-    // Basic managed animation test.
-    if (id === 'sk_drinks') {
-      animation.setColor('BACKGROUND_FILL', CanvasKit.Color(0, 163, 199, 1.0));
-    }
-
-    const surface = CanvasKit.MakeCanvasSurface(id);
-    if (!surface) {
-      console.error('Could not make surface');
-      return;
-    }
-    const context = CanvasKit.currentContext();
-    const canvas = surface.getCanvas();
-
-    let firstFrame = Date.now();
-
-    function drawFrame() {
-      let seek = ((Date.now() - firstFrame) / duration) % 1.0;
-      CanvasKit.setCurrentContext(context);
-      animation.seek(seek);
-      canvas.clear(CanvasKit.WHITE);
-      animation.render(canvas, bounds);
-      surface.flush();
-      window.requestAnimationFrame(drawFrame);
-    }
-    window.requestAnimationFrame(drawFrame);
-
-    //animation.delete();
-    return surface;
-  }
-
-  function CanvasAPI1(CanvasKit) {
-    let skcanvas = CanvasKit.MakeCanvas(300, 300);
-    let realCanvas = document.getElementById('api1_c');
-
-    let skPromise   = fetch('./test.png')
-                        // if clients want to use a Blob, they are responsible
-                        // for reading it themselves.
-                        .then((response) => response.arrayBuffer())
-                        .then((buffer) => {
-                          skcanvas._img = skcanvas.decodeImage(buffer);
-                        });
-    let realPromise = fetch('./test.png')
-                        .then((response) => response.blob())
-                        .then((blob) => createImageBitmap(blob))
-                        .then((bitmap) => {
-                          realCanvas._img = bitmap;
-                        });
-
-    let realFontLoaded = new FontFace('Bungee', 'url(/tests/assets/Bungee-Regular.ttf)', {
-      'family': 'Bungee',
-      'style': 'normal',
-      'weight': '400',
-    }).load().then((font) => {
-      document.fonts.add(font);
-    });
-
-    let skFontLoaded = fetch('/tests/assets/Bungee-Regular.ttf').then(
-                             (response) => response.arrayBuffer()).then(
-                             (buffer) => {
-                                // loadFont is synchronous
-                                skcanvas.loadFont(buffer, {
-                                  'family': 'Bungee',
-                                  'style': 'normal',
-                                  'weight': '400',
-                                });
-                              });
-
-    Promise.all([realPromise, skPromise, realFontLoaded, skFontLoaded]).then(() => {
-      for (let canvas of [skcanvas, realCanvas]) {
-        let ctx = canvas.getContext('2d');
-        ctx.fillStyle = '#EEE';
-        ctx.fillRect(0, 0, 300, 300);
-        ctx.fillStyle = 'black';
-        ctx.font = '26px Bungee';
-        ctx.rotate(.1);
-        let text = ctx.measureText('Awesome');
-        ctx.fillText('Awesome ', 25, 100);
-        ctx.strokeText('Groovy!', 35+text.width, 100);
-
-        // Draw line under Awesome
-        ctx.strokeStyle = 'rgba(125,0,0,0.5)';
-        ctx.beginPath();
-        ctx.lineWidth = 6;
-        ctx.moveTo(25, 105);
-        ctx.lineTo(25 + text.width, 105);
-        ctx.stroke();
-
-        // squished vertically
-        ctx.globalAlpha = 0.7
-        ctx.imageSmoothingQuality = 'medium';
-        ctx.drawImage(canvas._img, 150, 150, 150, 100);
-        ctx.rotate(-.2);
-        ctx.imageSmoothingEnabled = false;
-        ctx.drawImage(canvas._img, 100, 150, 400, 350, 10, 200, 150, 100);
-
-        let idata = ctx.getImageData(80, 220, 40, 45);
-        ctx.putImageData(idata, 250, 10);
-        ctx.putImageData(idata, 200, 10, 20, 10, 20, 30);
-        ctx.resetTransform();
-        ctx.strokeStyle = 'black';
-        ctx.lineWidth = 1;
-        ctx.strokeRect(200, 10, 40, 45);
-
-        idata = ctx.createImageData(10, 20);
-        ctx.putImageData(idata, 10, 10);
-      }
-
-      document.getElementById('api1').src = skcanvas.toDataURL();
-      skcanvas.dispose();
-    });
-
-  }
-
-  function CanvasAPI2(CanvasKit) {
-    let skcanvas = CanvasKit.MakeCanvas(300, 300);
-    let realCanvas = document.getElementById('api2_c');
-    realCanvas.width = 300;
-    realCanvas.height = 300;
-
-    // svg data for a clock
-    skcanvas._path = skcanvas.makePath2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
-    realCanvas._path = new Path2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
-
-    for (let canvas of [skcanvas, realCanvas]) {
-      let ctx = canvas.getContext('2d');
-      ctx.scale(1.5, 1.5);
-      ctx.moveTo(20, 5);
-      ctx.lineTo(30, 20);
-      ctx.lineTo(40, 10);
-      ctx.lineTo(50, 20);
-      ctx.lineTo(60, 0);
-      ctx.lineTo(20, 5);
-
-      ctx.moveTo(20, 80);
-      ctx.bezierCurveTo(90, 10, 160, 150, 190, 10);
-
-      ctx.moveTo(36, 148);
-      ctx.quadraticCurveTo(66, 188, 120, 136);
-      ctx.lineTo(36, 148);
-
-      ctx.rect(5, 170, 20, 25);
-
-      ctx.moveTo(150, 180);
-      ctx.arcTo(150, 100, 50, 200, 20);
-      ctx.lineTo(160, 160);
-
-      ctx.moveTo(20, 120);
-      ctx.arc(20, 120, 18, 0, 1.75 * Math.PI);
-      ctx.lineTo(20, 120);
-
-      ctx.moveTo(150, 5);
-      ctx.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI);
-
-      ctx.lineWidth = 4/3;
-      ctx.stroke();
-
-      // make a clock
-      ctx.stroke(canvas._path);
-
-      // Test edgecases and draw direction
-      ctx.beginPath();
-      ctx.arc(50, 100, 10, Math.PI, -Math.PI/2);
-      ctx.stroke();
-      ctx.beginPath();
-      ctx.arc(75, 100, 10, Math.PI, -Math.PI/2, true);
-      ctx.stroke();
-      ctx.beginPath();
-      ctx.arc(100, 100, 10, Math.PI, 100.1 * Math.PI, true);
-      ctx.stroke();
-      ctx.beginPath();
-      ctx.arc(125, 100, 10, Math.PI, 100.1 * Math.PI, false);
-      ctx.stroke();
-      ctx.beginPath();
-      ctx.ellipse(155, 100, 10, 15, Math.PI/8, 100.1 * Math.PI, Math.PI, true);
-      ctx.stroke();
-      ctx.beginPath();
-      ctx.ellipse(180, 100, 10, 15, Math.PI/8, Math.PI, 100.1 * Math.PI, true);
-      ctx.stroke();
-    }
-    document.getElementById('api2').src = skcanvas.toDataURL();
-    skcanvas.dispose();
-  }
-
-  function CanvasAPI3(CanvasKit) {
-    let skcanvas = CanvasKit.MakeCanvas(300, 300);
-    let realCanvas = document.getElementById('api3_c');
-    realCanvas.width = 300;
-    realCanvas.height = 300;
-
-    for (let canvas of [skcanvas, realCanvas]) {
-      let ctx = canvas.getContext('2d');
-      ctx.rect(10, 10, 20, 20);
-
-      ctx.scale(2.0, 4.0);
-      ctx.rect(30, 10, 20, 20);
-      ctx.resetTransform();
-
-      ctx.rotate(Math.PI / 3);
-      ctx.rect(50, 10, 20, 20);
-      ctx.resetTransform();
-
-      ctx.translate(30, -2);
-      ctx.rect(70, 10, 20, 20);
-      ctx.resetTransform();
-
-      ctx.translate(60, 0);
-      ctx.rotate(Math.PI / 6);
-      ctx.transform(1.5, 0, 0, 0.5, 0, 0, 0); // effectively scale
-      ctx.rect(90, 10, 20, 20);
-      ctx.resetTransform();
-
-      ctx.save();
-      ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
-      ctx.rect(110, 10, 20, 20);
-      ctx.lineTo(110, 0);
-      ctx.restore();
-      ctx.lineTo(220, 120);
-
-      ctx.scale(3.0, 3.0);
-      ctx.font = '6pt Noto Mono';
-      ctx.fillText('This text should be huge', 10, 80);
-      ctx.resetTransform();
-
-      ctx.strokeStyle = 'black';
-      ctx.lineWidth = 2;
-      ctx.stroke();
-
-      ctx.beginPath();
-      ctx.moveTo(250, 30);
-      ctx.lineTo(250, 80);
-      ctx.scale(3.0, 3.0);
-      ctx.lineTo(280/3, 90/3);
-      ctx.closePath();
-      ctx.strokeStyle = 'black';
-      ctx.lineWidth = 5;
-      ctx.stroke();
-
-    }
-    document.getElementById('api3').src = skcanvas.toDataURL();
-    skcanvas.dispose();
-  }
-
-  function CanvasAPI4(CanvasKit) {
-    let skcanvas = CanvasKit.MakeCanvas(300, 300);
-    let realCanvas = document.getElementById('api4_c');
-    realCanvas.width = 300;
-    realCanvas.height = 300;
-
-    for (let canvas of [skcanvas, realCanvas]) {
-      let ctx = canvas.getContext('2d');
-
-      ctx.strokeStyle = '#000';
-      ctx.fillStyle = '#CCC';
-      ctx.shadowColor = 'rebeccapurple';
-      ctx.shadowBlur = 1;
-      ctx.shadowOffsetX = 3;
-      ctx.shadowOffsetY = -8;
-      ctx.rect(10, 10, 30, 30);
-
-      ctx.save();
-      ctx.strokeStyle = '#C00';
-      ctx.fillStyle = '#00C';
-      ctx.shadowBlur = 0;
-      ctx.shadowColor = 'transparent';
-
-      ctx.stroke();
-
-      ctx.restore();
-      ctx.fill();
-
-      ctx.beginPath();
-      ctx.moveTo(36, 148);
-      ctx.quadraticCurveTo(66, 188, 120, 136);
-      ctx.closePath();
-      ctx.stroke();
-
-      ctx.beginPath();
-      ctx.shadowColor = '#993366AA';
-      ctx.shadowOffsetX = 8;
-      ctx.shadowBlur = 5;
-      ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
-      ctx.rect(110, 10, 20, 20);
-      ctx.lineTo(110, 0);
-      ctx.resetTransform();
-      ctx.lineTo(220, 120);
-      ctx.stroke();
-
-      ctx.fillStyle = 'green';
-      ctx.font = '16pt Noto Mono';
-      ctx.fillText('This should be shadowed', 20, 80);
-
-      ctx.beginPath();
-      ctx.lineWidth = 6;
-      ctx.ellipse(10, 290, 30, 30, 0, 0, Math.PI * 2);
-      ctx.scale(2, 1);
-      ctx.moveTo(10, 290)
-      ctx.ellipse(10, 290, 30, 60, 0, 0, Math.PI * 2);
-      ctx.resetTransform();
-      ctx.scale(3, 1);
-      ctx.moveTo(10, 290)
-      ctx.ellipse(10, 290, 30, 90, 0, 0, Math.PI * 2);
-      ctx.stroke();
-    }
-    document.getElementById('api4').src = skcanvas.toDataURL();
-    skcanvas.dispose();
-  }
-
-  function CanvasAPI5(CanvasKit) {
-    let skcanvas = CanvasKit.MakeCanvas(600, 600);
-    let realCanvas = document.getElementById('api5_c');
-    realCanvas.width = 600;
-    realCanvas.height = 600;
-
-    for (let canvas of [skcanvas, realCanvas]) {
-      let ctx = canvas.getContext('2d');
-      ctx.scale(1.1, 1.1);
-      ctx.translate(10, 10);
-      // Shouldn't impact the fillRect calls
-      ctx.setLineDash([5, 3]);
-
-      ctx.fillStyle = 'rgba(200, 0, 100, 0.81)';
-      ctx.fillRect(20, 30, 100, 100);
-
-      ctx.globalAlpha = 0.81;
-      ctx.fillStyle = 'rgba(200, 0, 100, 1.0)';
-      ctx.fillRect(120, 30, 100, 100);
-      // This shouldn't do anything
-      ctx.globalAlpha = 0.1;
-
-      ctx.fillStyle = 'rgba(200, 0, 100, 0.9)';
-      ctx.globalAlpha = 0.9;
-      // Intentional no-op to check ordering
-      ctx.clearRect(220, 30, 100, 100);
-      ctx.fillRect(220, 30, 100, 100);
-
-      ctx.fillRect(320, 30, 100, 100);
-      ctx.clearRect(330, 40, 80, 80);
-
-      ctx.strokeStyle = 'blue';
-      ctx.lineWidth = 3;
-      ctx.setLineDash([5, 3]);
-      ctx.strokeRect(20, 150, 100, 100);
-      ctx.setLineDash([50, 30]);
-      ctx.strokeRect(125, 150, 100, 100);
-      ctx.lineDashOffset = 25;
-      ctx.strokeRect(230, 150, 100, 100);
-      ctx.setLineDash([2, 5, 9]);
-      ctx.strokeRect(335, 150, 100, 100);
-
-      ctx.setLineDash([5, 2]);
-      ctx.moveTo(336, 400);
-      ctx.quadraticCurveTo(366, 488, 120, 450);
-      ctx.lineTo(300, 400);
-      ctx.stroke();
-
-      ctx.font = '36pt Noto Mono';
-      ctx.strokeText('Dashed', 20, 350);
-      ctx.fillText('Not Dashed', 20, 400);
-
-    }
-    document.getElementById('api5').src = skcanvas.toDataURL();
-    skcanvas.dispose();
-  }
-
-  function CanvasAPI6(CanvasKit) {
-    let skcanvas = CanvasKit.MakeCanvas(600, 600);
-    let realCanvas = document.getElementById('api6_c');
-    realCanvas.width = 600;
-    realCanvas.height = 600;
-
-    for (let canvas of [skcanvas, realCanvas]) {
-      let ctx = canvas.getContext('2d');
-
-      let rgradient = ctx.createRadialGradient(200, 300, 10, 100, 100, 300);
-
-      // Add three color stops
-      rgradient.addColorStop(0, 'red');
-      rgradient.addColorStop(0.7, 'white');
-      rgradient.addColorStop(1, 'blue');
-
-      ctx.fillStyle = rgradient;
-      ctx.globalAlpha = 0.7;
-      ctx.fillRect(0, 0, 600, 600);
-      ctx.globalAlpha = 0.95;
-
-      ctx.beginPath();
-      ctx.arc(300, 100, 90, 0, Math.PI*1.66);
-      ctx.closePath();
-      ctx.strokeStyle = 'yellow';
-      ctx.lineWidth = 5;
-      ctx.stroke();
-      ctx.save();
-      ctx.clip();
-
-      let lgradient = ctx.createLinearGradient(200, 20, 420, 40);
-
-      // Add three color stops
-      lgradient.addColorStop(0, 'green');
-      lgradient.addColorStop(0.5, 'cyan');
-      lgradient.addColorStop(1, 'orange');
-
-      ctx.fillStyle = lgradient;
-
-      ctx.fillRect(200, 30, 200, 300);
-
-      ctx.restore();
-      ctx.fillRect(550, 550, 40, 40);
-
-    }
-    document.getElementById('api6').src = skcanvas.toDataURL();
-    skcanvas.dispose();
-  }
-
-  function CanvasAPI7(CanvasKit) {
-    let skcanvas = CanvasKit.MakeCanvas(300, 300);
-    let realCanvas = document.getElementById('api7_c');
-
-    let skPromise   = fetch('./test.png')
-                        // if clients want to use a Blob, they are responsible
-                        // for reading it themselves.
-                        .then((response) => response.arrayBuffer())
-                        .then((buffer) => {
-                          skcanvas._img = skcanvas.decodeImage(buffer);
-                        });
-    let realPromise = fetch('./test.png')
-                        .then((response) => response.blob())
-                        .then((blob) => createImageBitmap(blob))
-                        .then((bitmap) => {
-                          realCanvas._img = bitmap;
-                        });
-
-
-    Promise.all([realPromise, skPromise]).then(() => {
-      for (let canvas of [skcanvas, realCanvas]) {
-        let ctx = canvas.getContext('2d');
-        ctx.fillStyle = '#EEE';
-        ctx.fillRect(0, 0, 300, 300);
-        ctx.lineWidth = 20;
-        ctx.scale(0.1, 0.2);
-
-        let pattern = ctx.createPattern(canvas._img, 'repeat');
-        ctx.fillStyle = pattern;
-        ctx.fillRect(0, 0, 1500, 750);
-
-        pattern = ctx.createPattern(canvas._img, 'repeat-x');
-        ctx.fillStyle = pattern;
-        ctx.fillRect(1500, 0, 3000, 750);
-
-        ctx.globalAlpha = 0.7
-        pattern = ctx.createPattern(canvas._img, 'repeat-y');
-        ctx.fillStyle = pattern;
-        ctx.fillRect(0, 750, 1500, 1500);
-        ctx.strokeRect(0, 750, 1500, 1500);
-
-        pattern = ctx.createPattern(canvas._img, 'no-repeat');
-        ctx.fillStyle = pattern;
-        pattern.setTransform({a: 1, b: -.1, c:.1, d: 0.5, e: 1800, f:800});
-        ctx.fillRect(0, 0, 3000, 1500);
-      }
-
-      document.getElementById('api7').src = skcanvas.toDataURL();
-      skcanvas.dispose();
-    });
-  }
-
-  function CanvasAPI8(CanvasKit) {
-    let skcanvas = CanvasKit.MakeCanvas(300, 300);
-    let realCanvas = document.getElementById('api8_c');
-
-    function drawPoint(ctx, x, y, color) {
-      ctx.fillStyle = color;
-      ctx.fillRect(x, y, 1, 1);
-    }
-    const IN = 'purple';
-    const OUT = 'orange';
-    const SCALE = 4;
-
-    const pts = [[3, 3], [4, 4], [5, 5], [10, 10], [8, 10], [6, 10],
-                 [6.5, 9], [15, 10], [17, 10], [17, 11], [24, 24],
-                 [25, 25], [26, 26], [27, 27]];
-
-    const tests = [
-      {
-        xOffset: 0,
-        yOffset: 0,
-        fillType: 'nonzero',
-        strokeWidth: 0,
-        testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'nonzero'),
-      },
-      {
-        xOffset: 30,
-        yOffset: 0,
-        fillType: 'evenodd',
-        strokeWidth: 0,
-        testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'evenodd'),
-      },
-      {
-        xOffset: 0,
-        yOffset: 30,
-        fillType: null,
-        strokeWidth: 1,
-        testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
-      },
-      {
-        xOffset: 30,
-        yOffset: 30,
-        fillType: null,
-        strokeWidth: 2,
-        testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
-      },
-    ];
-
-    for (let canvas of [skcanvas, realCanvas]) {
-      let ctx = canvas.getContext('2d');
-      ctx.font = '11px Noto Mono';
-      // Draw some visual aids
-      ctx.fillText('path-nonzero', 30, 15);
-      ctx.fillText('path-evenodd', 150, 15);
-      ctx.fillText('stroke-1px-wide', 30, 130);
-      ctx.fillText('stroke-2px-wide', 150, 130);
-      ctx.fillText('purple is IN, orange is OUT', 10, 280);
-
-      // Scale up to make single pixels easier to see
-      ctx.scale(SCALE, SCALE);
-      for (let test of tests) {
-        ctx.beginPath();
-        let xOffset = test.xOffset;
-        let yOffset = test.yOffset;
-
-        ctx.fillStyle = '#AAA';
-        ctx.lineWidth = test.strokeWidth;
-        ctx.rect(5+xOffset, 5+yOffset, 20, 20);
-        ctx.arc(15+xOffset, 15+yOffset, 8, 0, Math.PI*2, false);
-        if (test.fillType) {
-          ctx.fill(test.fillType);
-        } else {
-          ctx.stroke();
-        }
-
-        for (let pt of pts) {
-          let [x, y] = pt;
-          x += xOffset;
-          y += yOffset;
-          // naively apply transform when querying because the points queried
-          // ignore the CTM.
-          if (test.testFn(ctx, x, y)) {
-            drawPoint(ctx, x, y, IN);
-          } else {
-            drawPoint(ctx, x, y, OUT);
-          }
-        }
-      }
-    }
-
-    document.getElementById('api8').src = skcanvas.toDataURL();
-    skcanvas.dispose();
-  }
-
-  function VertexAPI1(CanvasKit) {
-    const surface = CanvasKit.MakeCanvasSurface('vertex1');
-    if (!surface) {
-      console.error('Could not make surface');
-      return;
-    }
-    const context = CanvasKit.currentContext();
-    const canvas = surface.getCanvas();
-    let paint = new CanvasKit.SkPaint();
-
-    // See https://fiddle.skia.org/c/f48b22eaad1bb7adcc3faaa321754af6
-    // for original c++ version.
-    let points = [[ 0, 0 ], [ 250, 0 ], [ 100, 100 ], [ 0, 250 ]];
-    let colors = [CanvasKit.RED, CanvasKit.BLUE,
-                  CanvasKit.YELLOW, CanvasKit.CYAN];
-    let vertices = CanvasKit.MakeSkVertices(CanvasKit.VertexMode.TriangleFan,
-                                            points, null, colors);
-
-    canvas.drawVertices(vertices, CanvasKit.BlendMode.Src, paint);
-    surface.flush();
-
-    vertices.delete();
-
-    // See https://fiddle.skia.org/c/e8bdae9bea3227758989028424fcac3d
-    // for original c++ version.
-    points   = [[ 300, 300 ], [ 50, 300 ], [ 200, 200 ], [ 300, 50 ]];
-    let texs = [[   0,   0 ], [  0, 250 ], [ 250, 250 ], [ 250,  0 ]];
-    vertices = CanvasKit.MakeSkVertices(CanvasKit.VertexMode.TriangleFan,
-                                            points, texs, colors);
-
-    let shader = CanvasKit.MakeLinearGradientShader([0, 0], [250, 0],
-            colors, null, CanvasKit.TileMode.Clamp);
-    paint.setShader(shader);
-
-    canvas.drawVertices(vertices, CanvasKit.BlendMode.Darken, paint);
-    surface.flush();
-
-    shader.delete();
-    paint.delete();
-    surface.delete();
-  }
-
-  // bonesImageDatae is passed in as raw, encoded bytes.
-  function VertexAPI2(CanvasKit, bonesImageData) {
-    if (!CanvasKit || !bonesImageData) {
-      return;
-    }
-    const surface = CanvasKit.MakeCanvasSurface('vertex2');
-    if (!surface) {
-      console.error('Could not make surface');
-      return;
-    }
-    const context = CanvasKit.currentContext();
-    const canvas = surface.getCanvas();
-    let paint = new CanvasKit.SkPaint();
-    let bonesImage = CanvasKit.MakeImageFromEncoded(bonesImageData);
-
-    let shader = CanvasKit.MakeImageShader(bonesImage,
-                    CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp);
-
-    // comment this out to see just the triangles move.
-    paint.setShader(shader);
-
-    // points is the destination location on the canvas  We want the output
-    // to be a 280x280 box (to start).
-    let points   = [[ 0, 0 ],  [ 280, 0 ], [ 280, 280 ], [ 0, 280 ]];
-    // texs is the coordinates of the source in the texture
-    // (provided by the image shader). The image is 334x226 px big.
-    let texs     = [[ 0, 0 ],  [ 334, 0 ], [ 334, 226 ], [ 0, 226 ]];
-    let boneidxs = [[1,0,0,0], [2,0,0,0],  [3,0,0,0],    [2,3,0,0]];
-    let bonewts  = [[1,0,0,0], [1,0,0,0],  [1,0,0,0],    [.5,.5,0,0]];
-    let vertices = CanvasKit.MakeSkVertices(CanvasKit.VertexMode.TriangleFan,
-                                            points, texs, null, boneidxs, bonewts);
-
-    function drawFrame() {
-      let now = Date.now();
-      let bones = [
-        [[1,0, // world bone (move 10px down and to the right to center)
-          0,1,
-          10,10]],
-        [[1,0, // identity bone (bone for vertices that are static)
-          0,1,
-          0,0]],
-        [[1,0, // ossilate in x bone
-          0,1,
-          10*Math.sin(now/500),0]],
-        [[1,0, // ossilate in y bone
-          0,1,
-          0,30*Math.cos(now/500)]],
-      ];
-      let tVerts = vertices.applyBones(bones);
-      CanvasKit.setCurrentContext(context);
-      //canvas.clear(CanvasKit.TRANSPARENT);
-      canvas.drawVertices(tVerts, CanvasKit.BlendMode.Src, paint);
-      surface.flush();
-
-      tVerts.delete();
-      window.requestAnimationFrame(drawFrame);
-    }
-    window.requestAnimationFrame(drawFrame);
-    //tVerts.delete();
-    //vertices.delete();
-
-    // bonesImage && bonesImage.delete();
-    //shader && shader.delete();
-    //paint.delete();
-    //surface.delete();
-
-  }
-
-  function GradiantAPI1(CanvasKit) {
-    const surface = CanvasKit.MakeCanvasSurface('gradient1');
-    if (!surface) {
-      console.error('Could not make surface');
-      return;
-    }
-    const context = CanvasKit.currentContext();
-    const canvas = surface.getCanvas();
-    let paint = new CanvasKit.SkPaint();
-
-    // See https://fiddle.skia.org/c/f48b22eaad1bb7adcc3faaa321754af6
-    // for original c++ version.
-    let points = [[ 0, 0 ], [ 250, 0 ], [ 100, 100 ], [ 0, 250 ]];
-    let colors = [CanvasKit.BLUE, CanvasKit.YELLOW, CanvasKit.RED];
-    let pos =    [0, .7, 1.0];
-    let transform = [2, 0, 0,
-                     0, 2, 0,
-                     0, 0, 1]
-    let shader = CanvasKit.MakeRadialGradientShader([150,150], 130, colors,
-                              pos, CanvasKit.TileMode.Mirror, transform);
-
-    paint.setShader(shader);
-    const textFont = new CanvasKit.SkFont(null, 75);
-    const textBlob = CanvasKit.SkTextBlob.MakeFromText('Radial', textFont);
-
-    canvas.drawTextBlob(textBlob, 10, 200, paint);
-    paint.delete()
-    textFont.delete();
-    textBlob.delete();
-    surface.flush();
-  }
-</script>
diff --git a/experimental/canvaskit/canvaskit/node.example.js b/experimental/canvaskit/canvaskit/node.example.js
deleted file mode 100644
index 65fbe79..0000000
--- a/experimental/canvaskit/canvaskit/node.example.js
+++ /dev/null
@@ -1,114 +0,0 @@
-const CanvasKitInit = require('./bin/canvaskit.js');
-const fs = require('fs');
-const path = require('path');
-
-CanvasKitInit({
-  locateFile: (file) => __dirname + '/bin/'+file,
-}).ready().then((CanvasKit) => {
-  let canvas = CanvasKit.MakeCanvas(300, 300);
-
-  let img = fs.readFileSync(path.join(__dirname, 'test.png'));
-  img = canvas.decodeImage(img);
-
-  let fontData = fs.readFileSync(path.join(__dirname, './Roboto-Regular.woff'));
-  canvas.loadFont(fontData, {
-                                'family': 'Roboto',
-                                'style': 'normal',
-                                'weight': '400',
-                              });
-
-  let ctx = canvas.getContext('2d');
-  ctx.font = '30px Roboto';
-  ctx.rotate(.1);
-  let text = ctx.measureText('Awesome');
-  ctx.fillText('Awesome ', 50, 100);
-  ctx.strokeText('Groovy!', 60+text.width, 100);
-
-  // Draw line under Awesome
-  ctx.strokeStyle = 'rgba(125,0,0,0.5)';
-  ctx.beginPath();
-  ctx.lineWidth = 6;
-  ctx.lineTo(50, 102);
-  ctx.lineTo(50 + text.width, 102);
-  ctx.stroke();
-
-  // squished vertically
-  ctx.globalAlpha = 0.7
-  ctx.imageSmoothingQuality = 'medium';
-  ctx.drawImage(img, 150, 150, 150, 100);
-  ctx.rotate(-.2);
-  ctx.imageSmoothingEnabled = false;
-  ctx.drawImage(img, 100, 150, 400, 350, 10, 200, 150, 100);
-
-  console.log('<img src="' + canvas.toDataURL() + '" />');
-
-  fancyAPI(CanvasKit);
-});
-
-function fancyAPI(CanvasKit) {
-  let surface = CanvasKit.MakeSurface(300, 300);
-  const canvas = surface.getCanvas();
-
-  const paint = new CanvasKit.SkPaint();
-
-  const fontMgr = CanvasKit.SkFontMgr.RefDefault();
-  let robotoData = fs.readFileSync(path.join(__dirname, './Roboto-Regular.woff'));
-  const roboto = fontMgr.MakeTypefaceFromData(robotoData);
-
-  const textPaint = new CanvasKit.SkPaint();
-  textPaint.setColor(CanvasKit.Color(40, 0, 0));
-  textPaint.setAntiAlias(true);
-
-  const textFont = new CanvasKit.SkFont(roboto, 30);
-
-  const skpath = starPath(CanvasKit);
-  const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], 1);
-
-  paint.setPathEffect(dpe);
-  paint.setStyle(CanvasKit.PaintStyle.Stroke);
-  paint.setStrokeWidth(5.0);
-  paint.setAntiAlias(true);
-  paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
-
-  canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
-
-  canvas.drawPath(skpath, paint);
-  canvas.drawText('Try Clicking!', 10, 280, textFont, textPaint);
-
-  surface.flush();
-
-  const img = surface.makeImageSnapshot()
-  if (!img) {
-    console.error('no snapshot');
-    return;
-  }
-  const png = img.encodeToData()
-  if (!png) {
-    console.error('encoding failure');
-    return
-  }
-  const pngBytes = CanvasKit.getSkDataBytes(png);
-  // See https://stackoverflow.com/a/12713326
-  let b64encoded = Buffer.from(pngBytes).toString('base64');
-  console.log(`<img src="data:image/png;base64,${b64encoded}" />`);
-
-  // These delete calls free up memeory in the C++ WASM memory block.
-  dpe.delete();
-  skpath.delete();
-  textPaint.delete();
-  paint.delete();
-  roboto.delete();
-  textFont.delete();
-
-  surface.dispose();
-}
-
-function starPath(CanvasKit, X=128, Y=128, R=116) {
-  let p = new CanvasKit.SkPath();
-  p.moveTo(X + R, Y);
-  for (let i = 1; i < 8; i++) {
-    let a = 2.6927937 * i;
-    p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
-  }
-  return p;
-}
\ No newline at end of file
diff --git a/experimental/canvaskit/canvaskit/package.json b/experimental/canvaskit/canvaskit/package.json
deleted file mode 100644
index 91c17dd..0000000
--- a/experimental/canvaskit/canvaskit/package.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
-  "name": "canvaskit-wasm",
-  "version": "0.3.1",
-  "description": "A WASM version of Skia's Canvas API",
-  "main": "bin/canvaskit.js",
-  "homepage": "https://github.com/google/skia/tree/master/experimental/canvaskit",
-  "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1"
-  },
-  "license": "BSD-3-Clause"
-}
diff --git a/experimental/canvaskit/canvaskit_bindings.cpp b/experimental/canvaskit/canvaskit_bindings.cpp
deleted file mode 100644
index 27edd7de..0000000
--- a/experimental/canvaskit/canvaskit_bindings.cpp
+++ /dev/null
@@ -1,1124 +0,0 @@
-/*
- * Copyright 2018 Google LLC
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#if SK_SUPPORT_GPU
-#include "GrBackendSurface.h"
-#include "GrContext.h"
-#include "GrGLInterface.h"
-#include "GrGLTypes.h"
-#endif
-
-#include "SkBlendMode.h"
-#include "SkBlurTypes.h"
-#include "SkCanvas.h"
-#include "SkColor.h"
-#include "SkCornerPathEffect.h"
-#include "SkDashPathEffect.h"
-#include "SkData.h"
-#include "SkDiscretePathEffect.h"
-#include "SkEncodedImageFormat.h"
-#include "SkFilterQuality.h"
-#include "SkFont.h"
-#include "SkFontMgr.h"
-#include "SkFontMgrPriv.h"
-#include "SkFontTypes.h"
-#include "SkGradientShader.h"
-#include "SkImage.h"
-#include "SkImageInfo.h"
-#include "SkImageShader.h"
-#include "SkMakeUnique.h"
-#include "SkMaskFilter.h"
-#include "SkPaint.h"
-#include "SkParsePath.h"
-#include "SkPath.h"
-#include "SkPathEffect.h"
-#include "SkPathOps.h"
-#include "SkScalar.h"
-#include "SkShader.h"
-#include "SkShadowUtils.h"
-#include "SkString.h"
-#include "SkStrokeRec.h"
-#include "SkSurface.h"
-#include "SkSurfaceProps.h"
-#include "SkTextBlob.h"
-#include "SkTrimPathEffect.h"
-#include "SkTypeface.h"
-#include "SkTypes.h"
-#include "SkVertices.h"
-
-#include <iostream>
-#include <string>
-
-#include "WasmAliases.h"
-#include <emscripten.h>
-#include <emscripten/bind.h>
-
-#if SK_SUPPORT_GPU
-#include <GL/gl.h>
-#include <emscripten/html5.h>
-#endif
-
-// Aliases for less typing
-using BoneIndices = SkVertices::BoneIndices;
-using BoneWeights = SkVertices::BoneWeights;
-using Bone        = SkVertices::Bone;
-
-#if SK_SUPPORT_GPU
-// Wraps the WebGL context in an SkSurface and returns it.
-// This function based on the work of
-// https://github.com/Zubnix/skia-wasm-port/, used under the terms of the MIT license.
-sk_sp<SkSurface> getWebGLSurface(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context, int width, int height) {
-    EMSCRIPTEN_RESULT r = emscripten_webgl_make_context_current(context);
-    if (r < 0) {
-        printf("failed to make webgl context current %d\n", r);
-        return nullptr;
-    }
-
-    glClearColor(0, 0, 0, 0);
-    glClearStencil(0);
-    glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
-
-    // setup GrContext
-    auto interface = GrGLMakeNativeInterface();
-
-    // setup contexts
-    sk_sp<GrContext> grContext(GrContext::MakeGL(interface));
-
-    // Wrap the frame buffer object attached to the screen in a Skia render target so Skia can
-    // render to it
-    GrGLint buffer;
-    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &buffer);
-    GrGLFramebufferInfo info;
-    info.fFBOID = (GrGLuint) buffer;
-    SkColorType colorType;
-
-    info.fFormat = GL_RGBA8;
-    colorType = kRGBA_8888_SkColorType;
-
-    GrBackendRenderTarget target(width, height, 0, 8, info);
-
-    sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(grContext.get(), target,
-                                                                    kBottomLeft_GrSurfaceOrigin,
-                                                                    colorType, nullptr, nullptr));
-    return surface;
-}
-#endif
-
-struct SimpleMatrix {
-    SkScalar scaleX, skewX,  transX;
-    SkScalar skewY,  scaleY, transY;
-    SkScalar pers0,  pers1,  pers2;
-};
-
-SkMatrix toSkMatrix(const SimpleMatrix& sm) {
-    return SkMatrix::MakeAll(sm.scaleX, sm.skewX , sm.transX,
-                             sm.skewY , sm.scaleY, sm.transY,
-                             sm.pers0 , sm.pers1 , sm.pers2);
-}
-
-SimpleMatrix toSimpleSkMatrix(const SkMatrix& sm) {
-    SimpleMatrix m {sm[0], sm[1], sm[2],
-                    sm[3], sm[4], sm[5],
-                    sm[6], sm[7], sm[8]};
-    return m;
-}
-
-struct SimpleImageInfo {
-    int width;
-    int height;
-    SkColorType colorType;
-    SkAlphaType alphaType;
-    // TODO color spaces?
-};
-
-SkImageInfo toSkImageInfo(const SimpleImageInfo& sii) {
-    return SkImageInfo::Make(sii.width, sii.height, sii.colorType, sii.alphaType);
-}
-
-//========================================================================================
-// Path things
-//========================================================================================
-
-// All these Apply* methods are simple wrappers to avoid returning an object.
-// The default WASM bindings produce code that will leak if a return value
-// isn't assigned to a JS variable and has delete() called on it.
-// These Apply methods, combined with the smarter binding code allow for chainable
-// commands that don't leak if the return value is ignored (i.e. when used intuitively).
-
-void ApplyAddArc(SkPath& orig, const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
-    orig.addArc(oval, startAngle, sweepAngle);
-}
-
-void ApplyAddPath(SkPath& orig, const SkPath& newPath,
-                   SkScalar scaleX, SkScalar skewX,  SkScalar transX,
-                   SkScalar skewY,  SkScalar scaleY, SkScalar transY,
-                   SkScalar pers0, SkScalar pers1, SkScalar pers2,
-                   bool extendPath) {
-    SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
-                                   skewY , scaleY, transY,
-                                   pers0 , pers1 , pers2);
-    orig.addPath(newPath, m, extendPath ? SkPath::kExtend_AddPathMode :
-                                          SkPath::kAppend_AddPathMode);
-}
-
-void ApplyAddRect(SkPath& path, SkScalar left, SkScalar top,
-                  SkScalar right, SkScalar bottom, bool ccw) {
-    path.addRect(left, top, right, bottom,
-                 ccw ? SkPath::Direction::kCCW_Direction :
-                 SkPath::Direction::kCW_Direction);
-}
-
-void ApplyAddRoundRect(SkPath& path, SkScalar left, SkScalar top,
-                  SkScalar right, SkScalar bottom, uintptr_t /* SkScalar*  */ rPtr,
-                  bool ccw) {
-    // See comment below for uintptr_t explanation
-    const SkScalar* radii = reinterpret_cast<const SkScalar*>(rPtr);
-    path.addRoundRect(SkRect::MakeLTRB(left, top, right, bottom), radii,
-                      ccw ? SkPath::Direction::kCCW_Direction : SkPath::Direction::kCW_Direction);
-}
-
-
-void ApplyArcTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
-                SkScalar radius) {
-    p.arcTo(x1, y1, x2, y2, radius);
-}
-
-void ApplyArcToAngle(SkPath& p, SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool forceMoveTo) {
-    p.arcTo(oval, startAngle, sweepAngle, forceMoveTo);
-}
-
-void ApplyClose(SkPath& p) {
-    p.close();
-}
-
-void ApplyConicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
-                  SkScalar w) {
-    p.conicTo(x1, y1, x2, y2, w);
-}
-
-void ApplyCubicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
-                  SkScalar x3, SkScalar y3) {
-    p.cubicTo(x1, y1, x2, y2, x3, y3);
-}
-
-void ApplyLineTo(SkPath& p, SkScalar x, SkScalar y) {
-    p.lineTo(x, y);
-}
-
-void ApplyMoveTo(SkPath& p, SkScalar x, SkScalar y) {
-    p.moveTo(x, y);
-}
-
-void ApplyReset(SkPath& p) {
-    p.reset();
-}
-
-void ApplyRewind(SkPath& p) {
-    p.rewind();
-}
-
-void ApplyQuadTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
-    p.quadTo(x1, y1, x2, y2);
-}
-
-void ApplyTransform(SkPath& orig,
-                    SkScalar scaleX, SkScalar skewX,  SkScalar transX,
-                    SkScalar skewY,  SkScalar scaleY, SkScalar transY,
-                    SkScalar pers0, SkScalar pers1, SkScalar pers2) {
-    SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
-                                   skewY , scaleY, transY,
-                                   pers0 , pers1 , pers2);
-    orig.transform(m);
-}
-
-bool EMSCRIPTEN_KEEPALIVE ApplySimplify(SkPath& path) {
-    return Simplify(path, &path);
-}
-
-bool EMSCRIPTEN_KEEPALIVE ApplyPathOp(SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
-    return Op(pathOne, pathTwo, op, &pathOne);
-}
-
-JSString EMSCRIPTEN_KEEPALIVE ToSVGString(const SkPath& path) {
-    SkString s;
-    SkParsePath::ToSVGString(path, &s);
-    return emscripten::val(s.c_str());
-}
-
-SkPathOrNull EMSCRIPTEN_KEEPALIVE MakePathFromSVGString(std::string str) {
-    SkPath path;
-    if (SkParsePath::FromSVGString(str.c_str(), &path)) {
-        return emscripten::val(path);
-    }
-    return emscripten::val::null();
-}
-
-SkPathOrNull EMSCRIPTEN_KEEPALIVE MakePathFromOp(const SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
-    SkPath out;
-    if (Op(pathOne, pathTwo, op, &out)) {
-        return emscripten::val(out);
-    }
-    return emscripten::val::null();
-}
-
-SkPath EMSCRIPTEN_KEEPALIVE CopyPath(const SkPath& a) {
-    SkPath copy(a);
-    return copy;
-}
-
-bool EMSCRIPTEN_KEEPALIVE Equals(const SkPath& a, const SkPath& b) {
-    return a == b;
-}
-
-// =================================================================================
-// Creating/Exporting Paths with cmd arrays
-// =================================================================================
-
-static const int MOVE = 0;
-static const int LINE = 1;
-static const int QUAD = 2;
-static const int CONIC = 3;
-static const int CUBIC = 4;
-static const int CLOSE = 5;
-
-template <typename VisitFunc>
-void VisitPath(const SkPath& p, VisitFunc&& f) {
-    SkPath::RawIter iter(p);
-    SkPoint pts[4];
-    SkPath::Verb verb;
-    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
-        f(verb, pts, iter);
-    }
-}
-
-JSArray EMSCRIPTEN_KEEPALIVE ToCmds(const SkPath& path) {
-    JSArray cmds = emscripten::val::array();
-
-    VisitPath(path, [&cmds](SkPath::Verb verb, const SkPoint pts[4], SkPath::RawIter iter) {
-        JSArray cmd = emscripten::val::array();
-        switch (verb) {
-        case SkPath::kMove_Verb:
-            cmd.call<void>("push", MOVE, pts[0].x(), pts[0].y());
-            break;
-        case SkPath::kLine_Verb:
-            cmd.call<void>("push", LINE, pts[1].x(), pts[1].y());
-            break;
-        case SkPath::kQuad_Verb:
-            cmd.call<void>("push", QUAD, pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
-            break;
-        case SkPath::kConic_Verb:
-            cmd.call<void>("push", CONIC,
-                           pts[1].x(), pts[1].y(),
-                           pts[2].x(), pts[2].y(), iter.conicWeight());
-            break;
-        case SkPath::kCubic_Verb:
-            cmd.call<void>("push", CUBIC,
-                           pts[1].x(), pts[1].y(),
-                           pts[2].x(), pts[2].y(),
-                           pts[3].x(), pts[3].y());
-            break;
-        case SkPath::kClose_Verb:
-            cmd.call<void>("push", CLOSE);
-            break;
-        case SkPath::kDone_Verb:
-            SkASSERT(false);
-            break;
-        }
-        cmds.call<void>("push", cmd);
-    });
-    return cmds;
-}
-
-// This type signature is a mess, but it's necessary. See, we can't use "bind" (EMSCRIPTEN_BINDINGS)
-// and pointers to primitive types (Only bound types like SkPoint). We could if we used
-// cwrap (see https://becominghuman.ai/passing-and-returning-webassembly-array-parameters-a0f572c65d97)
-// but that requires us to stick to C code and, AFAIK, doesn't allow us to return nice things like
-// SkPath or SkOpBuilder.
-//
-// So, basically, if we are using C++ and EMSCRIPTEN_BINDINGS, we can't have primative pointers
-// in our function type signatures. (this gives an error message like "Cannot call foo due to unbound
-// types Pi, Pf").  But, we can just pretend they are numbers and cast them to be pointers and
-// the compiler is happy.
-SkPathOrNull EMSCRIPTEN_KEEPALIVE MakePathFromCmds(uintptr_t /* float* */ cptr, int numCmds) {
-    const auto* cmds = reinterpret_cast<const float*>(cptr);
-    SkPath path;
-    float x1, y1, x2, y2, x3, y3;
-
-    // if there are not enough arguments, bail with the path we've constructed so far.
-    #define CHECK_NUM_ARGS(n) \
-        if ((i + n) > numCmds) { \
-            SkDebugf("Not enough args to match the verbs. Saw %d commands\n", numCmds); \
-            return emscripten::val::null(); \
-        }
-
-    for(int i = 0; i < numCmds;){
-         switch (sk_float_floor2int(cmds[i++])) {
-            case MOVE:
-                CHECK_NUM_ARGS(2);
-                x1 = cmds[i++], y1 = cmds[i++];
-                path.moveTo(x1, y1);
-                break;
-            case LINE:
-                CHECK_NUM_ARGS(2);
-                x1 = cmds[i++], y1 = cmds[i++];
-                path.lineTo(x1, y1);
-                break;
-            case QUAD:
-                CHECK_NUM_ARGS(4);
-                x1 = cmds[i++], y1 = cmds[i++];
-                x2 = cmds[i++], y2 = cmds[i++];
-                path.quadTo(x1, y1, x2, y2);
-                break;
-            case CONIC:
-                CHECK_NUM_ARGS(5);
-                x1 = cmds[i++], y1 = cmds[i++];
-                x2 = cmds[i++], y2 = cmds[i++];
-                x3 = cmds[i++]; // weight
-                path.conicTo(x1, y1, x2, y2, x3);
-                break;
-            case CUBIC:
-                CHECK_NUM_ARGS(6);
-                x1 = cmds[i++], y1 = cmds[i++];
-                x2 = cmds[i++], y2 = cmds[i++];
-                x3 = cmds[i++], y3 = cmds[i++];
-                path.cubicTo(x1, y1, x2, y2, x3, y3);
-                break;
-            case CLOSE:
-                path.close();
-                break;
-            default:
-                SkDebugf("  path: UNKNOWN command %f, aborting dump...\n", cmds[i-1]);
-                return emscripten::val::null();
-        }
-    }
-
-    #undef CHECK_NUM_ARGS
-
-    return emscripten::val(path);
-}
-
-//========================================================================================
-// Path Effects
-//========================================================================================
-
-bool ApplyDash(SkPath& path, SkScalar on, SkScalar off, SkScalar phase) {
-    SkScalar intervals[] = { on, off };
-    auto pe = SkDashPathEffect::Make(intervals, 2, phase);
-    if (!pe) {
-        SkDebugf("Invalid args to dash()\n");
-        return false;
-    }
-    SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
-    if (pe->filterPath(&path, path, &rec, nullptr)) {
-        return true;
-    }
-    SkDebugf("Could not make dashed path\n");
-    return false;
-}
-
-bool ApplyTrim(SkPath& path, SkScalar startT, SkScalar stopT, bool isComplement) {
-    auto mode = isComplement ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal;
-    auto pe = SkTrimPathEffect::Make(startT, stopT, mode);
-    if (!pe) {
-        SkDebugf("Invalid args to trim(): startT and stopT must be in [0,1]\n");
-        return false;
-    }
-    SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
-    if (pe->filterPath(&path, path, &rec, nullptr)) {
-        return true;
-    }
-    SkDebugf("Could not trim path\n");
-    return false;
-}
-
-struct StrokeOpts {
-    // Default values are set in interface.js which allows clients
-    // to set any number of them. Otherwise, the binding code complains if
-    // any are omitted.
-    SkScalar width;
-    SkScalar miter_limit;
-    SkPaint::Join join;
-    SkPaint::Cap cap;
-    float precision;
-};
-
-bool ApplyStroke(SkPath& path, StrokeOpts opts) {
-    SkPaint p;
-    p.setStyle(SkPaint::kStroke_Style);
-    p.setStrokeCap(opts.cap);
-    p.setStrokeJoin(opts.join);
-    p.setStrokeWidth(opts.width);
-    p.setStrokeMiter(opts.miter_limit);
-
-    return p.getFillPath(path, &path, nullptr, opts.precision);
-}
-
-// to map from raw memory to a uint8array
-Uint8Array getSkDataBytes(const SkData *data) {
-    return Uint8Array(typed_memory_view(data->size(), data->bytes()));
-}
-
-// These objects have private destructors / delete mthods - I don't think
-// we need to do anything other than tell emscripten to do nothing.
-namespace emscripten {
-    namespace internal {
-        template<typename ClassType>
-        void raw_destructor(ClassType *);
-
-        template<>
-        void raw_destructor<SkData>(SkData *ptr) {
-        }
-
-        template<>
-        void raw_destructor<SkTypeface>(SkTypeface *ptr) {
-        }
-
-        template<>
-        void raw_destructor<SkVertices>(SkVertices *ptr) {
-        }
-
-        template<>
-        void raw_destructor<SkTextBlob>(SkTextBlob *ptr) {
-        }
-    }
-}
-
-// Some timesignatures below have uintptr_t instead of a pointer to a primative
-// type (e.g. SkScalar). This is necessary because we can't use "bind" (EMSCRIPTEN_BINDINGS)
-// and pointers to primitive types (Only bound types like SkPoint). We could if we used
-// cwrap (see https://becominghuman.ai/passing-and-returning-webassembly-array-parameters-a0f572c65d97)
-// but that requires us to stick to C code and, AFAIK, doesn't allow us to return nice things like
-// SkPath or SkCanvas.
-//
-// So, basically, if we are using C++ and EMSCRIPTEN_BINDINGS, we can't have primative pointers
-// in our function type signatures. (this gives an error message like "Cannot call foo due to unbound
-// types Pi, Pf").  But, we can just pretend they are numbers and cast them to be pointers and
-// the compiler is happy.
-EMSCRIPTEN_BINDINGS(Skia) {
-#if SK_SUPPORT_GPU
-    function("_getWebGLSurface", &getWebGLSurface, allow_raw_pointers());
-    function("currentContext", &emscripten_webgl_get_current_context);
-    function("setCurrentContext", &emscripten_webgl_make_context_current);
-    constant("gpu", true);
-#endif
-    function("_decodeImage", optional_override([](uintptr_t /* uint8_t*  */ iptr,
-                                                  size_t length)->sk_sp<SkImage> {
-        uint8_t* imgData = reinterpret_cast<uint8_t*>(iptr);
-        sk_sp<SkData> bytes = SkData::MakeWithoutCopy(imgData, length);
-        return SkImage::MakeFromEncoded(bytes);
-    }), allow_raw_pointers());
-    function("_getRasterDirectSurface", optional_override([](const SimpleImageInfo ii,
-                                                             uintptr_t /* uint8_t*  */ pPtr,
-                                                             size_t rowBytes)->sk_sp<SkSurface> {
-        uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
-        SkImageInfo imageInfo = toSkImageInfo(ii);
-        return SkSurface::MakeRasterDirect(imageInfo, pixels, rowBytes, nullptr);
-    }), allow_raw_pointers());
-    function("_getRasterN32PremulSurface", optional_override([](int width, int height)->sk_sp<SkSurface> {
-        return SkSurface::MakeRasterN32Premul(width, height, nullptr);
-    }), allow_raw_pointers());
-
-    function("getSkDataBytes", &getSkDataBytes, allow_raw_pointers());
-    function("MakeSkCornerPathEffect", &SkCornerPathEffect::Make, allow_raw_pointers());
-    function("MakeSkDiscretePathEffect", &SkDiscretePathEffect::Make, allow_raw_pointers());
-    function("MakeBlurMaskFilter", optional_override([](SkBlurStyle style, SkScalar sigma, bool respectCTM)->sk_sp<SkMaskFilter> {
-        // Adds a little helper because emscripten doesn't expose default params.
-        return SkMaskFilter::MakeBlur(style, sigma, respectCTM);
-    }), allow_raw_pointers());
-    function("_MakePathFromCmds", &MakePathFromCmds);
-    function("MakePathFromOp", &MakePathFromOp);
-    function("MakePathFromSVGString", &MakePathFromSVGString);
-
-    // These won't be called directly, there's a JS helper to deal with typed arrays.
-    function("_MakeSkDashPathEffect", optional_override([](uintptr_t /* float* */ cptr, int count, SkScalar phase)->sk_sp<SkPathEffect> {
-        // See comment above for uintptr_t explanation
-        const float* intervals = reinterpret_cast<const float*>(cptr);
-        return SkDashPathEffect::Make(intervals, count, phase);
-    }), allow_raw_pointers());
-    function("_MakeImage", optional_override([](SimpleImageInfo ii,
-                                                uintptr_t /* uint8_t*  */ pPtr, int plen,
-                                                size_t rowBytes)->sk_sp<SkImage> {
-        // See comment above for uintptr_t explanation
-        uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
-        SkImageInfo info = toSkImageInfo(ii);
-        sk_sp<SkData> pixelData = SkData::MakeFromMalloc(pixels, plen);
-
-        return SkImage::MakeRasterData(info, pixelData, rowBytes);
-    }), allow_raw_pointers());
-    // Allow localMatrix to be optional, so we have 2 declarations of these shaders
-    function("_MakeImageShader", optional_override([](sk_sp<SkImage> img,
-                                SkShader::TileMode tx, SkShader::TileMode ty,
-                                bool clampAsIfUnpremul)->sk_sp<SkShader> {
-        return SkImageShader::Make(img, tx, ty, nullptr, clampAsIfUnpremul);
-    }), allow_raw_pointers());
-    function("_MakeImageShader", optional_override([](sk_sp<SkImage> img,
-                                SkShader::TileMode tx, SkShader::TileMode ty,
-                                bool clampAsIfUnpremul, const SimpleMatrix& lm)->sk_sp<SkShader> {
-        SkMatrix localMatrix = toSkMatrix(lm);
-
-        return SkImageShader::Make(img, tx, ty, &localMatrix, clampAsIfUnpremul);
-    }), allow_raw_pointers());
-    function("_MakeLinearGradientShader", optional_override([](SkPoint start, SkPoint end,
-                                uintptr_t /* SkColor*  */ cPtr, uintptr_t /* SkScalar*  */ pPtr,
-                                int count, SkShader::TileMode mode, uint32_t flags)->sk_sp<SkShader> {
-        SkPoint points[] = { start, end };
-        // See comment above for uintptr_t explanation
-        const SkColor*  colors    = reinterpret_cast<const SkColor*> (cPtr);
-        const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
-
-        return SkGradientShader::MakeLinear(points, colors, positions, count,
-                                            mode, flags, nullptr);
-    }), allow_raw_pointers());
-    function("_MakeLinearGradientShader", optional_override([](SkPoint start, SkPoint end,
-                                uintptr_t /* SkColor*  */ cPtr, uintptr_t /* SkScalar*  */ pPtr,
-                                int count, SkShader::TileMode mode, uint32_t flags,
-                                const SimpleMatrix& lm)->sk_sp<SkShader> {
-        SkPoint points[] = { start, end };
-        // See comment above for uintptr_t explanation
-        const SkColor*  colors    = reinterpret_cast<const SkColor*> (cPtr);
-        const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
-
-        SkMatrix localMatrix = toSkMatrix(lm);
-
-        return SkGradientShader::MakeLinear(points, colors, positions, count,
-                                            mode, flags, &localMatrix);
-    }), allow_raw_pointers());
-    function("_MakeRadialGradientShader", optional_override([](SkPoint center, SkScalar radius,
-                                uintptr_t /* SkColor*  */ cPtr, uintptr_t /* SkScalar*  */ pPtr,
-                                int count, SkShader::TileMode mode, uint32_t flags)->sk_sp<SkShader> {
-        // See comment above for uintptr_t explanation
-        const SkColor*  colors    = reinterpret_cast<const SkColor*> (cPtr);
-        const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
-
-        return SkGradientShader::MakeRadial(center, radius, colors, positions, count,
-                                            mode, flags, nullptr);
-    }), allow_raw_pointers());
-    function("_MakeRadialGradientShader", optional_override([](SkPoint center, SkScalar radius,
-                                uintptr_t /* SkColor*  */ cPtr, uintptr_t /* SkScalar*  */ pPtr,
-                                int count, SkShader::TileMode mode, uint32_t flags,
-                                const SimpleMatrix& lm)->sk_sp<SkShader> {
-        // See comment above for uintptr_t explanation
-        const SkColor*  colors    = reinterpret_cast<const SkColor*> (cPtr);
-        const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
-
-        SkMatrix localMatrix = toSkMatrix(lm);
-        return SkGradientShader::MakeRadial(center, radius, colors, positions, count,
-                                            mode, flags, &localMatrix);
-    }), allow_raw_pointers());
-    function("_MakeTwoPointConicalGradientShader", optional_override([](
-                SkPoint start, SkScalar startRadius,
-                SkPoint end, SkScalar endRadius,
-                uintptr_t /* SkColor*  */ cPtr, uintptr_t /* SkScalar*  */ pPtr,
-                int count, SkShader::TileMode mode, uint32_t flags)->sk_sp<SkShader> {
-        // See comment above for uintptr_t explanation
-        const SkColor*  colors    = reinterpret_cast<const SkColor*> (cPtr);
-        const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
-
-        return SkGradientShader::MakeTwoPointConical(start, startRadius, end, endRadius,
-                                                     colors, positions, count, mode,
-                                                     flags, nullptr);
-    }), allow_raw_pointers());
-    function("_MakeTwoPointConicalGradientShader", optional_override([](
-                SkPoint start, SkScalar startRadius,
-                SkPoint end, SkScalar endRadius,
-                uintptr_t /* SkColor*  */ cPtr, uintptr_t /* SkScalar*  */ pPtr,
-                int count, SkShader::TileMode mode, uint32_t flags,
-                const SimpleMatrix& lm)->sk_sp<SkShader> {
-        // See comment above for uintptr_t explanation
-        const SkColor*  colors    = reinterpret_cast<const SkColor*> (cPtr);
-        const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
-
-        SkMatrix localMatrix = toSkMatrix(lm);
-        return SkGradientShader::MakeTwoPointConical(start, startRadius, end, endRadius,
-                                                     colors, positions, count, mode,
-                                                     flags, &localMatrix);
-    }), allow_raw_pointers());
-
-    function("_MakeSkVertices", optional_override([](SkVertices::VertexMode mode, int vertexCount,
-                                uintptr_t /* SkPoint*     */ pPtr,  uintptr_t /* SkPoint*     */ tPtr,
-                                uintptr_t /* SkColor*     */ cPtr,
-                                uintptr_t /* BoneIndices* */ biPtr, uintptr_t /* BoneWeights* */ bwPtr,
-                                int indexCount,                     uintptr_t /* uint16_t  *  */ iPtr)->sk_sp<SkVertices> {
-        // See comment above for uintptr_t explanation
-        const SkPoint* positions       = reinterpret_cast<const SkPoint*>(pPtr);
-        const SkPoint* texs            = reinterpret_cast<const SkPoint*>(tPtr);
-        const SkColor* colors          = reinterpret_cast<const SkColor*>(cPtr);
-        const BoneIndices* boneIndices = reinterpret_cast<const BoneIndices*>(biPtr);
-        const BoneWeights* boneWeights = reinterpret_cast<const BoneWeights*>(bwPtr);
-        const uint16_t* indices        = reinterpret_cast<const uint16_t*>(iPtr);
-
-        return SkVertices::MakeCopy(mode, vertexCount, positions, texs, colors,
-                                    boneIndices, boneWeights, indexCount, indices);
-    }), allow_raw_pointers());
-
-    class_<SkCanvas>("SkCanvas")
-        .constructor<>()
-        .function("clear", optional_override([](SkCanvas& self, JSColor color)->void {
-            // JS side gives us a signed int instead of an unsigned int for color
-            // Add a optional_override to change it out.
-            self.clear(SkColor(color));
-        }))
-        .function("clipPath", select_overload<void (const SkPath&, SkClipOp, bool)>(&SkCanvas::clipPath))
-        .function("clipRect", select_overload<void (const SkRect&, SkClipOp, bool)>(&SkCanvas::clipRect))
-        .function("concat", optional_override([](SkCanvas& self, const SimpleMatrix& m) {
-            self.concat(toSkMatrix(m));
-        }))
-        .function("drawArc", &SkCanvas::drawArc)
-        .function("drawImage", select_overload<void (const sk_sp<SkImage>&, SkScalar, SkScalar, const SkPaint*)>(&SkCanvas::drawImage), allow_raw_pointers())
-        .function("drawImageRect", optional_override([](SkCanvas& self, const sk_sp<SkImage>& image,
-                                                        SkRect src, SkRect dst,
-                                                        const SkPaint* paint, bool fastSample)->void {
-            self.drawImageRect(image, src, dst, paint,
-                               fastSample ? SkCanvas::kFast_SrcRectConstraint :
-                                            SkCanvas::kStrict_SrcRectConstraint);
-        }), allow_raw_pointers())
-        .function("drawLine", select_overload<void (SkScalar, SkScalar, SkScalar, SkScalar, const SkPaint&)>(&SkCanvas::drawLine))
-        .function("drawOval", &SkCanvas::drawOval)
-        .function("drawPaint", &SkCanvas::drawPaint)
-        .function("drawPath", &SkCanvas::drawPath)
-        .function("drawRect", &SkCanvas::drawRect)
-        .function("drawRoundRect", &SkCanvas::drawRoundRect)
-        .function("drawShadow", optional_override([](SkCanvas& self, const SkPath& path,
-                                                     const SkPoint3& zPlaneParams,
-                                                     const SkPoint3& lightPos, SkScalar lightRadius,
-                                                     JSColor ambientColor, JSColor spotColor,
-                                                     uint32_t flags) {
-            SkShadowUtils::DrawShadow(&self, path, zPlaneParams, lightPos, lightRadius,
-                                      SkColor(ambientColor), SkColor(spotColor), flags);
-        }))
-        .function("_drawSimpleText", optional_override([](SkCanvas& self, uintptr_t /* char* */ sptr,
-                                                          size_t len, SkScalar x, SkScalar y, const SkFont& font,
-                                                          const SkPaint& paint) {
-            // See comment above for uintptr_t explanation
-            const char* str = reinterpret_cast<const char*>(sptr);
-
-            self.drawSimpleText(str, len, SkTextEncoding::kUTF8, x, y, font, paint);
-        }))
-        .function("drawTextBlob", select_overload<void (const sk_sp<SkTextBlob>&, SkScalar, SkScalar, const SkPaint&)>(&SkCanvas::drawTextBlob))
-        .function("drawVertices", select_overload<void (const sk_sp<SkVertices>&, SkBlendMode, const SkPaint&)>(&SkCanvas::drawVertices))
-        .function("flush", &SkCanvas::flush)
-        .function("getTotalMatrix", optional_override([](const SkCanvas& self)->SimpleMatrix {
-            SkMatrix m = self.getTotalMatrix();
-            return toSimpleSkMatrix(m);
-        }))
-        .function("_readPixels", optional_override([](SkCanvas& self, SimpleImageInfo di,
-                                                      uintptr_t /* uint8_t* */ pPtr,
-                                                      size_t dstRowBytes, int srcX, int srcY) {
-            uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
-            SkImageInfo dstInfo = toSkImageInfo(di);
-
-            return self.readPixels(dstInfo, pixels, dstRowBytes, srcX, srcY);
-        }))
-        .function("restore", &SkCanvas::restore)
-        .function("rotate", select_overload<void (SkScalar, SkScalar, SkScalar)>(&SkCanvas::rotate))
-        .function("save", &SkCanvas::save)
-        .function("scale", &SkCanvas::scale)
-        .function("skew", &SkCanvas::skew)
-        .function("translate", &SkCanvas::translate)
-        .function("_writePixels", optional_override([](SkCanvas& self, SimpleImageInfo di,
-                                                       uintptr_t /* uint8_t* */ pPtr,
-                                                       size_t srcRowBytes, int dstX, int dstY) {
-            uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
-            SkImageInfo dstInfo = toSkImageInfo(di);
-
-            return self.writePixels(dstInfo, pixels, srcRowBytes, dstX, dstY);
-        }))
-        ;
-
-    class_<SkData>("SkData")
-        .smart_ptr<sk_sp<SkData>>("sk_sp<SkData>>")
-        .function("size", &SkData::size);
-
-    class_<SkFont>("SkFont")
-        .constructor<>()
-        .constructor<sk_sp<SkTypeface>>()
-        .constructor<sk_sp<SkTypeface>, SkScalar>()
-        .constructor<sk_sp<SkTypeface>, SkScalar, SkScalar, SkScalar>()
-        .function("getScaleX", &SkFont::getScaleX)
-        .function("getSize", &SkFont::getSize)
-        .function("getSkewX", &SkFont::getSkewX)
-        .function("getTypeface", &SkFont::getTypeface, allow_raw_pointers())
-        .function("measureText", optional_override([](SkFont& self, std::string text) {
-            // TODO(kjlubick): This does not work well for non-ascii
-            // Need to maybe add a helper in interface.js that supports UTF-8
-            // Otherwise, go with std::wstring and set UTF-32 encoding.
-            return self.measureText(text.c_str(), text.length(), SkTextEncoding::kUTF8);
-        }))
-        .function("setScaleX", &SkFont::setScaleX)
-        .function("setSize", &SkFont::setSize)
-        .function("setSkewX", &SkFont::setSkewX)
-        .function("setTypeface", &SkFont::setTypeface, allow_raw_pointers());
-
-    class_<SkFontMgr>("SkFontMgr")
-        .smart_ptr<sk_sp<SkFontMgr>>("sk_sp<SkFontMgr>")
-        .class_function("RefDefault", &SkFontMgr::RefDefault)
-#ifdef SK_DEBUG
-        .function("dumpFamilies", optional_override([](SkFontMgr& self) {
-            int numFam = self.countFamilies();
-            SkDebugf("There are %d font families\n");
-            for (int i = 0 ; i< numFam; i++) {
-                SkString s;
-                self.getFamilyName(i, &s);
-                SkDebugf("\t%s", s.c_str());
-            }
-        }))
-#endif
-        .function("countFamilies", &SkFontMgr::countFamilies)
-        .function("_makeTypefaceFromData", optional_override([](SkFontMgr& self,
-                                                uintptr_t /* uint8_t*  */ fPtr,
-                                                int flen)->sk_sp<SkTypeface> {
-        // See comment above for uintptr_t explanation
-        uint8_t* font = reinterpret_cast<uint8_t*>(fPtr);
-        sk_sp<SkData> fontData = SkData::MakeFromMalloc(font, flen);
-
-        return self.makeFromData(fontData);
-    }), allow_raw_pointers());
-
-    class_<SkImage>("SkImage")
-        .smart_ptr<sk_sp<SkImage>>("sk_sp<SkImage>")
-        .function("height", &SkImage::height)
-        .function("width", &SkImage::width)
-        .function("_encodeToData", select_overload<sk_sp<SkData>()const>(&SkImage::encodeToData))
-        .function("_encodeToDataWithFormat", select_overload<sk_sp<SkData>(SkEncodedImageFormat encodedImageFormat, int quality)const>(&SkImage::encodeToData));
-
-    class_<SkMaskFilter>("SkMaskFilter")
-        .smart_ptr<sk_sp<SkMaskFilter>>("sk_sp<SkMaskFilter>");
-
-    class_<SkPaint>("SkPaint")
-        .constructor<>()
-        .function("copy", optional_override([](const SkPaint& self)->SkPaint {
-            SkPaint p(self);
-            return p;
-        }))
-        .function("getBlendMode", &SkPaint::getBlendMode)
-        .function("getColor", optional_override([](SkPaint& self)->JSColor {
-            // JS side gives us a signed int instead of an unsigned int for color
-            // Add a optional_override to change it out.
-            return JSColor(self.getColor());
-        }))
-        .function("getFilterQuality", &SkPaint::getFilterQuality)
-        .function("getStrokeCap", &SkPaint::getStrokeCap)
-        .function("getStrokeJoin", &SkPaint::getStrokeJoin)
-        .function("getStrokeMiter", &SkPaint::getStrokeMiter)
-        .function("getStrokeWidth", &SkPaint::getStrokeWidth)
-        .function("setAntiAlias", &SkPaint::setAntiAlias)
-        .function("setBlendMode", &SkPaint::setBlendMode)
-        .function("setColor", optional_override([](SkPaint& self, JSColor color)->void {
-            // JS side gives us a signed int instead of an unsigned int for color
-            // Add a optional_override to change it out.
-            self.setColor(SkColor(color));
-        }))
-        .function("setFilterQuality", &SkPaint::setFilterQuality)
-        .function("setMaskFilter", &SkPaint::setMaskFilter)
-        .function("setPathEffect", &SkPaint::setPathEffect)
-        .function("setShader", &SkPaint::setShader)
-        .function("setStrokeCap", &SkPaint::setStrokeCap)
-        .function("setStrokeJoin", &SkPaint::setStrokeJoin)
-        .function("setStrokeMiter", &SkPaint::setStrokeMiter)
-        .function("setStrokeWidth", &SkPaint::setStrokeWidth)
-        .function("setStyle", &SkPaint::setStyle);
-
-    class_<SkPathEffect>("SkPathEffect")
-        .smart_ptr<sk_sp<SkPathEffect>>("sk_sp<SkPathEffect>");
-
-    class_<SkPath>("SkPath")
-        .constructor<>()
-        .constructor<const SkPath&>()
-        .function("_addArc", &ApplyAddArc)
-        // interface.js has 3 overloads of addPath
-        .function("_addPath", &ApplyAddPath)
-        // interface.js has 4 overloads of addRect
-        .function("_addRect", &ApplyAddRect)
-        // interface.js has 4 overloads of addRoundRect
-        .function("_addRoundRect", &ApplyAddRoundRect)
-        .function("_arcTo", &ApplyArcTo)
-        .function("_arcTo", &ApplyArcToAngle)
-        .function("_close", &ApplyClose)
-        .function("_conicTo", &ApplyConicTo)
-        .function("countPoints", &SkPath::countPoints)
-        .function("contains", &SkPath::contains)
-        .function("_cubicTo", &ApplyCubicTo)
-        .function("getPoint", &SkPath::getPoint)
-        .function("isEmpty",  &SkPath::isEmpty)
-        .function("isVolatile", &SkPath::isVolatile)
-        .function("_lineTo", &ApplyLineTo)
-        .function("_moveTo", &ApplyMoveTo)
-        .function("reset", &ApplyReset)
-        .function("rewind", &ApplyRewind)
-        .function("_quadTo", &ApplyQuadTo)
-        .function("setIsVolatile", &SkPath::setIsVolatile)
-        .function("_transform", select_overload<void(SkPath&, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&ApplyTransform))
-
-        // PathEffects
-        .function("_dash", &ApplyDash)
-        .function("_trim", &ApplyTrim)
-        .function("_stroke", &ApplyStroke)
-
-        // PathOps
-        .function("_simplify", &ApplySimplify)
-        .function("_op", &ApplyPathOp)
-
-        // Exporting
-        .function("toSVGString", &ToSVGString)
-        .function("toCmds", &ToCmds)
-
-        .function("setFillType", &SkPath::setFillType)
-        .function("getFillType", &SkPath::getFillType)
-        .function("getBounds", &SkPath::getBounds)
-        .function("computeTightBounds", &SkPath::computeTightBounds)
-        .function("equals", &Equals)
-        .function("copy", &CopyPath)
-#ifdef SK_DEBUG
-        .function("dump", select_overload<void() const>(&SkPath::dump))
-        .function("dumpHex", select_overload<void() const>(&SkPath::dumpHex))
-#endif
-        ;
-
-    class_<SkShader>("SkShader")
-        .smart_ptr<sk_sp<SkShader>>("sk_sp<SkShader>");
-
-    class_<SkSurface>("SkSurface")
-        .smart_ptr<sk_sp<SkSurface>>("sk_sp<SkSurface>")
-        .function("width", &SkSurface::width)
-        .function("height", &SkSurface::height)
-        .function("_flush", &SkSurface::flush)
-        .function("makeImageSnapshot", select_overload<sk_sp<SkImage>()>(&SkSurface::makeImageSnapshot))
-        .function("makeImageSnapshot", select_overload<sk_sp<SkImage>(const SkIRect& bounds)>(&SkSurface::makeImageSnapshot))
-        .function("getCanvas", &SkSurface::getCanvas, allow_raw_pointers());
-
-    class_<SkTextBlob>("SkTextBlob")
-        .smart_ptr<sk_sp<SkTextBlob>>("sk_sp<SkTextBlob>>")
-        .class_function("_MakeFromText",optional_override([](uintptr_t /* char* */ sptr,
-                                                             size_t len, const SkFont& font,
-                                                             SkTextEncoding encoding)->sk_sp<SkTextBlob> {
-            // See comment above for uintptr_t explanation
-            const char* str = reinterpret_cast<const char*>(sptr);
-            return SkTextBlob::MakeFromText(str, len, font, encoding);
-        }), allow_raw_pointers());
-
-
-    class_<SkTypeface>("SkTypeface")
-        .smart_ptr<sk_sp<SkTypeface>>("sk_sp<SkTypeface>");
-
-    class_<SkVertices>("SkVertices")
-        .smart_ptr<sk_sp<SkVertices>>("sk_sp<SkVertices>")
-        .function("_applyBones", optional_override([](SkVertices& self, uintptr_t /* Bone* */ bptr, int boneCount)->sk_sp<SkVertices> {
-            // See comment above for uintptr_t explanation
-            const Bone* bones = reinterpret_cast<const Bone*>(bptr);
-            return self.applyBones(bones, boneCount);
-        }))
-        .function("bounds", &SkVertices::bounds)
-        .function("mode", &SkVertices::mode)
-        .function("uniqueID", &SkVertices::uniqueID)
-#ifdef SK_DEBUG
-        .function("dumpPositions", optional_override([](SkVertices& self)->void {
-            auto pos = self.positions();
-            for(int i = 0; i< self.vertexCount(); i++) {
-                SkDebugf("position[%d] = (%f, %f)\n", i, pos[i].x(), pos[i].y());
-            }
-        }))
-#endif
-        .function("vertexCount", &SkVertices::vertexCount);
-
-    enum_<SkAlphaType>("AlphaType")
-        .value("Opaque",   SkAlphaType::kOpaque_SkAlphaType)
-        .value("Premul",   SkAlphaType::kPremul_SkAlphaType)
-        .value("Unpremul", SkAlphaType::kUnpremul_SkAlphaType);
-
-    enum_<SkBlendMode>("BlendMode")
-        .value("Clear",      SkBlendMode::kClear)
-        .value("Src",        SkBlendMode::kSrc)
-        .value("Dst",        SkBlendMode::kDst)
-        .value("SrcOver",    SkBlendMode::kSrcOver)
-        .value("DstOver",    SkBlendMode::kDstOver)
-        .value("SrcIn",      SkBlendMode::kSrcIn)
-        .value("DstIn",      SkBlendMode::kDstIn)
-        .value("SrcOut",     SkBlendMode::kSrcOut)
-        .value("DstOut",     SkBlendMode::kDstOut)
-        .value("SrcATop",    SkBlendMode::kSrcATop)
-        .value("DstATop",    SkBlendMode::kDstATop)
-        .value("Xor",        SkBlendMode::kXor)
-        .value("Plus",       SkBlendMode::kPlus)
-        .value("Modulate",   SkBlendMode::kModulate)
-        .value("Screen",     SkBlendMode::kScreen)
-        .value("Overlay",    SkBlendMode::kOverlay)
-        .value("Darken",     SkBlendMode::kDarken)
-        .value("Lighten",    SkBlendMode::kLighten)
-        .value("ColorDodge", SkBlendMode::kColorDodge)
-        .value("ColorBurn",  SkBlendMode::kColorBurn)
-        .value("HardLight",  SkBlendMode::kHardLight)
-        .value("SoftLight",  SkBlendMode::kSoftLight)
-        .value("Difference", SkBlendMode::kDifference)
-        .value("Exclusion",  SkBlendMode::kExclusion)
-        .value("Multiply",   SkBlendMode::kMultiply)
-        .value("Hue",        SkBlendMode::kHue)
-        .value("Saturation", SkBlendMode::kSaturation)
-        .value("Color",      SkBlendMode::kColor)
-        .value("Luminosity", SkBlendMode::kLuminosity);
-
-    enum_<SkBlurStyle>("BlurStyle")
-        .value("Normal", SkBlurStyle::kNormal_SkBlurStyle)
-        .value("Solid",  SkBlurStyle::kSolid_SkBlurStyle)
-        .value("Outer",  SkBlurStyle::kOuter_SkBlurStyle)
-        .value("Inner",  SkBlurStyle::kInner_SkBlurStyle);
-
-    enum_<SkClipOp>("ClipOp")
-        .value("Difference", SkClipOp::kDifference)
-        .value("Intersect",  SkClipOp::kIntersect);
-
-    enum_<SkColorType>("ColorType")
-        .value("Alpha_8", SkColorType::kAlpha_8_SkColorType)
-        .value("RGB_565", SkColorType::kRGB_565_SkColorType)
-        .value("ARGB_4444", SkColorType::kARGB_4444_SkColorType)
-        .value("RGBA_8888", SkColorType::kRGBA_8888_SkColorType)
-        .value("RGB_888x", SkColorType::kRGB_888x_SkColorType)
-        .value("BGRA_8888", SkColorType::kBGRA_8888_SkColorType)
-        .value("RGBA_1010102", SkColorType::kRGBA_1010102_SkColorType)
-        .value("RGB_101010x", SkColorType::kRGB_101010x_SkColorType)
-        .value("Gray_8", SkColorType::kGray_8_SkColorType)
-        .value("RGBA_F16", SkColorType::kRGBA_F16_SkColorType)
-        .value("RGBA_F32", SkColorType::kRGBA_F32_SkColorType);
-
-    enum_<SkPath::FillType>("FillType")
-        .value("Winding",           SkPath::FillType::kWinding_FillType)
-        .value("EvenOdd",           SkPath::FillType::kEvenOdd_FillType)
-        .value("InverseWinding",    SkPath::FillType::kInverseWinding_FillType)
-        .value("InverseEvenOdd",    SkPath::FillType::kInverseEvenOdd_FillType);
-
-    enum_<SkFilterQuality>("FilterQuality")
-        .value("None",   SkFilterQuality::kNone_SkFilterQuality)
-        .value("Low",    SkFilterQuality::kLow_SkFilterQuality)
-        .value("Medium", SkFilterQuality::kMedium_SkFilterQuality)
-        .value("High",   SkFilterQuality::kHigh_SkFilterQuality);
-
-    enum_<SkEncodedImageFormat>("ImageFormat")
-        .value("PNG",  SkEncodedImageFormat::kPNG)
-        .value("JPEG", SkEncodedImageFormat::kJPEG);
-
-    enum_<SkPaint::Style>("PaintStyle")
-        .value("Fill",            SkPaint::Style::kFill_Style)
-        .value("Stroke",          SkPaint::Style::kStroke_Style)
-        .value("StrokeAndFill",   SkPaint::Style::kStrokeAndFill_Style);
-
-    enum_<SkPathOp>("PathOp")
-        .value("Difference",         SkPathOp::kDifference_SkPathOp)
-        .value("Intersect",          SkPathOp::kIntersect_SkPathOp)
-        .value("Union",              SkPathOp::kUnion_SkPathOp)
-        .value("XOR",                SkPathOp::kXOR_SkPathOp)
-        .value("ReverseDifference",  SkPathOp::kReverseDifference_SkPathOp);
-
-    enum_<SkPaint::Cap>("StrokeCap")
-        .value("Butt",   SkPaint::Cap::kButt_Cap)
-        .value("Round",  SkPaint::Cap::kRound_Cap)
-        .value("Square", SkPaint::Cap::kSquare_Cap);
-
-    enum_<SkPaint::Join>("StrokeJoin")
-        .value("Miter", SkPaint::Join::kMiter_Join)
-        .value("Round", SkPaint::Join::kRound_Join)
-        .value("Bevel", SkPaint::Join::kBevel_Join);
-
-    enum_<SkTextEncoding>("TextEncoding")
-        .value("UTF8",    SkTextEncoding::kUTF8)
-        .value("UTF16",   SkTextEncoding::kUTF16)
-        .value("UTF32",   SkTextEncoding::kUTF32)
-        .value("GlyphID", SkTextEncoding::kGlyphID);
-
-    enum_<SkShader::TileMode>("TileMode")
-        .value("Clamp",    SkShader::TileMode::kClamp_TileMode)
-        .value("Repeat",   SkShader::TileMode::kRepeat_TileMode)
-        .value("Mirror",   SkShader::TileMode::kMirror_TileMode)
-        // Decal mode only works in the SW backend, not WebGl (yet).
-        .value("Decal",    SkShader::TileMode::kDecal_TileMode);
-
-    enum_<SkVertices::VertexMode>("VertexMode")
-        .value("Triangles",       SkVertices::VertexMode::kTriangles_VertexMode)
-        .value("TrianglesStrip",  SkVertices::VertexMode::kTriangleStrip_VertexMode)
-        .value("TriangleFan",     SkVertices::VertexMode::kTriangleFan_VertexMode);
-
-
-    // A value object is much simpler than a class - it is returned as a JS
-    // object and does not require delete().
-    // https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#value-types
-    value_object<SkRect>("SkRect")
-        .field("fLeft",   &SkRect::fLeft)
-        .field("fTop",    &SkRect::fTop)
-        .field("fRight",  &SkRect::fRight)
-        .field("fBottom", &SkRect::fBottom);
-
-    value_object<SkIRect>("SkIRect")
-        .field("fLeft",   &SkIRect::fLeft)
-        .field("fTop",    &SkIRect::fTop)
-        .field("fRight",  &SkIRect::fRight)
-        .field("fBottom", &SkIRect::fBottom);
-
-    value_object<SimpleImageInfo>("SkImageInfo")
-        .field("width",     &SimpleImageInfo::width)
-        .field("height",    &SimpleImageInfo::height)
-        .field("colorType", &SimpleImageInfo::colorType)
-        .field("alphaType", &SimpleImageInfo::alphaType);
-
-    // SkPoints can be represented by [x, y]
-    value_array<SkPoint>("SkPoint")
-        .element(&SkPoint::fX)
-        .element(&SkPoint::fY);
-
-    // SkPoint3s can be represented by [x, y, z]
-    value_array<SkPoint3>("SkPoint3")
-        .element(&SkPoint3::fX)
-        .element(&SkPoint3::fY)
-        .element(&SkPoint3::fZ);
-
-    // {"w": Number, "h", Number}
-    value_object<SkSize>("SkSize")
-        .field("w",   &SkSize::fWidth)
-        .field("h",   &SkSize::fHeight);
-
-    value_object<SkISize>("SkISize")
-        .field("w",   &SkISize::fWidth)
-        .field("h",   &SkISize::fHeight);
-
-    value_object<StrokeOpts>("StrokeOpts")
-        .field("width",       &StrokeOpts::width)
-        .field("miter_limit", &StrokeOpts::miter_limit)
-        .field("join",        &StrokeOpts::join)
-        .field("cap",         &StrokeOpts::cap)
-        .field("precision",   &StrokeOpts::precision);
-
-    // Allows clients to supply a 1D array of 9 elements and the bindings
-    // will automatically turn it into a 3x3 2D matrix.
-    // e.g. path.transform([0,1,2,3,4,5,6,7,8])
-    // This is likely simpler for the client than exposing SkMatrix
-    // directly and requiring them to do a lot of .delete().
-    value_array<SimpleMatrix>("SkMatrix")
-        .element(&SimpleMatrix::scaleX)
-        .element(&SimpleMatrix::skewX)
-        .element(&SimpleMatrix::transX)
-
-        .element(&SimpleMatrix::skewY)
-        .element(&SimpleMatrix::scaleY)
-        .element(&SimpleMatrix::transY)
-
-        .element(&SimpleMatrix::pers0)
-        .element(&SimpleMatrix::pers1)
-        .element(&SimpleMatrix::pers2);
-
-    constant("TRANSPARENT", (JSColor) SK_ColorTRANSPARENT);
-    constant("RED",         (JSColor) SK_ColorRED);
-    constant("BLUE",        (JSColor) SK_ColorBLUE);
-    constant("YELLOW",      (JSColor) SK_ColorYELLOW);
-    constant("CYAN",        (JSColor) SK_ColorCYAN);
-    constant("BLACK",       (JSColor) SK_ColorBLACK);
-    constant("WHITE",       (JSColor) SK_ColorWHITE);
-    // TODO(?)
-
-    constant("MOVE_VERB",  MOVE);
-    constant("LINE_VERB",  LINE);
-    constant("QUAD_VERB",  QUAD);
-    constant("CONIC_VERB", CONIC);
-    constant("CUBIC_VERB", CUBIC);
-    constant("CLOSE_VERB", CLOSE);
-}
diff --git a/experimental/canvaskit/compile.sh b/experimental/canvaskit/compile.sh
deleted file mode 100755
index 2c56793..0000000
--- a/experimental/canvaskit/compile.sh
+++ /dev/null
@@ -1,231 +0,0 @@
-#!/bin/bash
-# Copyright 2018 Google LLC
-#
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-set -ex
-
-BASE_DIR=`cd $(dirname ${BASH_SOURCE[0]}) && pwd`
-# This expects the environment variable EMSDK to be set
-if [[ ! -d $EMSDK ]]; then
-  echo "Be sure to set the EMSDK environment variable."
-  exit 1
-fi
-
-# Navigate to SKIA_HOME from where this file is located.
-pushd $BASE_DIR/../..
-
-source $EMSDK/emsdk_env.sh
-EMCC=`which emcc`
-EMCXX=`which em++`
-
-RELEASE_CONF="-Oz --closure 1 --llvm-lto 3 -DSK_RELEASE --pre-js $BASE_DIR/release.js \
-              -DGR_GL_CHECK_ALLOC_WITH_GET_ERROR=0"
-EXTRA_CFLAGS="\"-DSK_RELEASE\", \"-DGR_GL_CHECK_ALLOC_WITH_GET_ERROR=0\","
-if [[ $@ == *debug* ]]; then
-  echo "Building a Debug build"
-  EXTRA_CFLAGS="\"-DSK_DEBUG\""
-  RELEASE_CONF="-O0 --js-opts 0 -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=1 -s GL_ASSERTIONS=1 -g4 \
-                --source-map-base /node_modules/canvaskit/bin/ -DSK_DEBUG --pre-js $BASE_DIR/debug.js"
-  BUILD_DIR=${BUILD_DIR:="out/canvaskit_wasm_debug"}
-elif [[ $@ == *profiling* ]]; then
-  echo "Building a build for profiling"
-  RELEASE_CONF="-O3 --source-map-base /node_modules/canvaskit/bin/ --profiling -g4 -DSK_RELEASE \
-                --pre-js $BASE_DIR/release.js -DGR_GL_CHECK_ALLOC_WITH_GET_ERROR=0"
-  BUILD_DIR=${BUILD_DIR:="out/canvaskit_wasm_profile"}
-else
-  BUILD_DIR=${BUILD_DIR:="out/canvaskit_wasm"}
-fi
-
-mkdir -p $BUILD_DIR
-
-GN_GPU="skia_enable_gpu=true"
-GN_GPU_FLAGS="\"-DIS_WEBGL=1\", \"-DSK_DISABLE_LEGACY_SHADERCONTEXT\","
-WASM_GPU="-lEGL -lGLESv2 -DSK_SUPPORT_GPU=1 \
-          -DSK_DISABLE_LEGACY_SHADERCONTEXT --pre-js $BASE_DIR/cpu.js --pre-js $BASE_DIR/gpu.js"
-if [[ $@ == *cpu* ]]; then
-  echo "Using the CPU backend instead of the GPU backend"
-  GN_GPU="skia_enable_gpu=false"
-  GN_GPU_FLAGS=""
-  WASM_GPU="-DSK_SUPPORT_GPU=0 --pre-js $BASE_DIR/cpu.js"
-fi
-
-WASM_SKOTTIE=" \
-  $BASE_DIR/skottie_bindings.cpp \
-  modules/skottie/src/Skottie.cpp \
-  modules/skottie/src/SkottieAdapter.cpp \
-  modules/skottie/src/SkottieAnimator.cpp \
-  modules/skottie/src/SkottieJson.cpp \
-  modules/skottie/src/SkottieLayer.cpp \
-  modules/skottie/src/SkottieLayerEffect.cpp \
-  modules/skottie/src/SkottiePrecompLayer.cpp \
-  modules/skottie/src/SkottieProperty.cpp \
-  modules/skottie/src/SkottieShapeLayer.cpp \
-  modules/skottie/src/SkottieTextLayer.cpp \
-  modules/skottie/src/SkottieValue.cpp \
-  modules/sksg/src/*.cpp \
-  modules/skshaper/src/SkShaper.cpp \
-  modules/skshaper/src/SkShaper_primitive.cpp \
-  src/core/SkCubicMap.cpp \
-  src/core/SkTime.cpp \
-  src/pathops/SkOpBuilder.cpp \
-  src/utils/SkJSON.cpp \
-  src/utils/SkParse.cpp "
-if [[ $@ == *no_skottie* ]]; then
-  echo "Omitting Skottie"
-  WASM_SKOTTIE=""
-fi
-
-WASM_MANAGED_SKOTTIE="\
-  -DSK_INCLUDE_MANAGED_SKOTTIE=1 \
-  modules/skottie/utils/SkottieUtils.cpp"
-if [[ $@ == *no_managed_skottie* ]]; then
-  echo "Omitting managed Skottie"
-  WASM_MANAGED_SKOTTIE="-DSK_INCLUDE_MANAGED_SKOTTIE=0"
-fi
-
-HTML_CANVAS_API="--pre-js $BASE_DIR/htmlcanvas/preamble.js \
---pre-js $BASE_DIR/htmlcanvas/util.js \
---pre-js $BASE_DIR/htmlcanvas/color.js \
---pre-js $BASE_DIR/htmlcanvas/font.js \
---pre-js $BASE_DIR/htmlcanvas/canvas2dcontext.js \
---pre-js $BASE_DIR/htmlcanvas/htmlcanvas.js \
---pre-js $BASE_DIR/htmlcanvas/imagedata.js \
---pre-js $BASE_DIR/htmlcanvas/lineargradient.js \
---pre-js $BASE_DIR/htmlcanvas/path2d.js \
---pre-js $BASE_DIR/htmlcanvas/pattern.js \
---pre-js $BASE_DIR/htmlcanvas/radialgradient.js \
---pre-js $BASE_DIR/htmlcanvas/postamble.js "
-if [[ $@ == *no_canvas* ]]; then
-  echo "Omitting bindings for HTML Canvas API"
-  HTML_CANVAS_API=""
-fi
-
-BUILTIN_FONT="$BASE_DIR/fonts/NotoMono-Regular.ttf.cpp"
-if [[ $@ == *no_font* ]]; then
-  echo "Omitting the built-in font(s)"
-  BUILTIN_FONT=""
-else
-  # Generate the font's binary file (which is covered by .gitignore)
-  python tools/embed_resources.py \
-      --name SK_EMBEDDED_FONTS \
-      --input $BASE_DIR/fonts/NotoMono-Regular.ttf \
-      --output $BASE_DIR/fonts/NotoMono-Regular.ttf.cpp \
-      --align 4
-fi
-
-# Turn off exiting while we check for ninja (which may not be on PATH)
-set +e
-NINJA=`which ninja`
-if [[ -z $NINJA ]]; then
-  git clone "https://chromium.googlesource.com/chromium/tools/depot_tools.git" --depth 1 $BUILD_DIR/depot_tools
-  NINJA=$BUILD_DIR/depot_tools/ninja
-fi
-# Re-enable error checking
-set -e
-
-echo "Compiling bitcode"
-
-# Inspired by https://github.com/Zubnix/skia-wasm-port/blob/master/build_bindings.sh
-./bin/gn gen ${BUILD_DIR} \
-  --args="cc=\"${EMCC}\" \
-  cxx=\"${EMCXX}\" \
-  extra_cflags_cc=[\"-frtti\"] \
-  extra_cflags=[\"-s\",\"USE_FREETYPE=1\",\"-s\",\"USE_LIBPNG=1\", \"-s\", \"WARN_UNALIGNED=1\",
-    \"-DSKNX_NO_SIMD\", \"-DSK_DISABLE_AAA\", \"-DSK_DISABLE_DAA\", \"-DSK_DISABLE_READBUFFER\",
-    \"-DSK_DISABLE_EFFECT_DESERIALIZATION\",
-    ${GN_GPU_FLAGS}
-    ${EXTRA_CFLAGS}
-  ] \
-  is_debug=false \
-  is_official_build=true \
-  is_component_build=false \
-  target_cpu=\"wasm\" \
-  \
-  skia_use_angle = false \
-  skia_use_dng_sdk=false \
-  skia_use_egl=true \
-  skia_use_expat=false \
-  skia_use_fontconfig=false \
-  skia_use_freetype=true \
-  skia_use_icu=false \
-  skia_use_libheif=false \
-  skia_use_system_libjpeg_turbo = false \
-  skia_use_libjpeg_turbo=true \
-  skia_use_libpng=true \
-  skia_use_libwebp=false \
-  skia_use_lua=false \
-  skia_use_piex=false \
-  skia_use_vulkan=false \
-  skia_use_zlib=true \
-  \
-  skia_enable_ccpr=false \
-  skia_enable_nvpr=false \
-  skia_enable_skpicture=false \
-  ${GN_GPU} \
-  skia_enable_fontmgr_empty=false \
-  skia_enable_pdf=false"
-
-${NINJA} -C ${BUILD_DIR} libskia.a
-
-export EMCC_CLOSURE_ARGS="--externs $BASE_DIR/externs.js "
-
-echo "Generating final wasm"
-
-# Skottie doesn't end up in libskia and is currently not its own library
-# so we just hack in the .cpp files we need for now.
-# Emscripten prefers that libskia.a goes last in order, otherwise, it
-# may drop symbols that it incorrectly thinks aren't used. One day,
-# Emscripten will use LLD, which may relax this requirement.
-${EMCXX} \
-    $RELEASE_CONF \
-    -Iexperimental \
-    -Iinclude/c \
-    -Iinclude/codec \
-    -Iinclude/config \
-    -Iinclude/core \
-    -Iinclude/effects \
-    -Iinclude/gpu \
-    -Iinclude/gpu/gl \
-    -Iinclude/pathops \
-    -Iinclude/private \
-    -Iinclude/utils/ \
-    -Imodules/skottie/include \
-    -Imodules/skottie/utils \
-    -Imodules/sksg/include \
-    -Imodules/skshaper/include \
-    -Isrc/core/ \
-    -Isrc/gpu/ \
-    -Isrc/sfnt/ \
-    -Isrc/shaders/ \
-    -Isrc/utils/ \
-    -Itools \
-    -Itools/fonts \
-    -DSK_DISABLE_READBUFFER \
-    -DSK_DISABLE_AAA \
-    -DSK_DISABLE_DAA \
-    $WASM_GPU \
-    -std=c++14 \
-    --bind \
-    --pre-js $BASE_DIR/helper.js \
-    --pre-js $BASE_DIR/interface.js \
-    --post-js $BASE_DIR/ready.js \
-    $HTML_CANVAS_API \
-    $BUILTIN_FONT \
-    $BASE_DIR/canvaskit_bindings.cpp \
-    $WASM_SKOTTIE \
-    $WASM_MANAGED_SKOTTIE \
-    $BUILD_DIR/libskia.a \
-    -s ALLOW_MEMORY_GROWTH=1 \
-    -s EXPORT_NAME="CanvasKitInit" \
-    -s FORCE_FILESYSTEM=0 \
-    -s MODULARIZE=1 \
-    -s NO_EXIT_RUNTIME=1 \
-    -s STRICT=1 \
-    -s TOTAL_MEMORY=32MB \
-    -s USE_FREETYPE=1 \
-    -s USE_LIBPNG=1 \
-    -s WARN_UNALIGNED=1 \
-    -s WASM=1 \
-    -o $BUILD_DIR/canvaskit.js
diff --git a/experimental/canvaskit/externs.js b/experimental/canvaskit/externs.js
deleted file mode 100644
index eda3fad..0000000
--- a/experimental/canvaskit/externs.js
+++ /dev/null
@@ -1,581 +0,0 @@
-/*
- * This externs file prevents the Closure JS compiler from minifying away
- * names of objects created by Emscripten.
- * Basically, by defining empty objects and functions here, Closure will
- * know not to rename them.  This is needed because of our pre-js files,
- * that is, the JS we hand-write to bundle into the output. That JS will be
- * hit by the closure compiler and thus needs to know about what functions
- * have special names and should not be minified.
- *
- * Emscripten does not support automatically generating an externs file, so we
- * do it by hand. The general process is to write some JS code, and then put any
- * calls to CanvasKit or related things in here. Running ./compile.sh and then
- * looking at the minified results or running the Release trybot should
- * verify nothing was missed. Optionally, looking directly at the minified
- * pathkit.js can be useful when developing locally.
- *
- * Docs:
- *   https://github.com/cljsjs/packages/wiki/Creating-Externs
- *   https://github.com/google/closure-compiler/wiki/Types-in-the-Closure-Type-System
- *
- * Example externs:
- *   https://github.com/google/closure-compiler/tree/master/externs
- */
-
-var CanvasKit = {
-	// public API (i.e. things we declare in the pre-js file)
-	Color: function() {},
-	/** @return {CanvasKit.SkRect} */
-	LTRBRect: function() {},
-	/** @return {CanvasKit.SkRect} */
-	XYWHRect: function() {},
-	/** @return {ImageData} */
-	ImageData: function() {},
-	MakeBlurMaskFilter: function() {},
-	MakeCanvas: function() {},
-	MakeCanvasSurface: function() {},
-	MakeImageShader: function() {},
-	/** @return {CanvasKit.SkImage} */
-	MakeImageFromEncoded: function() {},
-	/** @return {LinearCanvasGradient} */
-	MakeLinearGradientShader: function() {},
-	MakePathFromCmds: function() {},
-	MakePathFromOp: function() {},
-	MakePathFromSVGString: function() {},
-	MakeRadialGradientShader: function() {},
-	MakeSWCanvasSurface: function() {},
-	MakeSkDashPathEffect: function() {},
-	MakeSkVertices: function() {},
-	MakeSurface: function() {},
-	/** @return {RadialCanvasGradient} */
-	MakeTwoPointConicalGradientShader: function() {},
-	MakeWebGLCanvasSurface: function() {},
-	currentContext: function() {},
-	getColorComponents: function() {},
-	getSkDataBytes: function() {},
-	multiplyByAlpha: function() {},
-	setCurrentContext: function() {},
-
-	// private API (i.e. things declared in the bindings that we use
-	// in the pre-js file)
-	_MakeImage: function() {},
-	_MakeImageShader: function() {},
-	_MakeLinearGradientShader: function() {},
-	_MakePathFromCmds: function() {},
-	_MakeRadialGradientShader: function() {},
-	_MakeSkDashPathEffect: function() {},
-	_MakeSkVertices: function() {},
-	_MakeTwoPointConicalGradientShader: function() {},
-	_decodeImage: function() {},
-	_getRasterDirectSurface: function() {},
-	_getRasterN32PremulSurface: function() {},
-	_getWebGLSurface: function() {},
-
-	// The testing object is meant to expose internal functions
-	// for more fine-grained testing, e.g. parseColor
-	_testing: {},
-
-	// Objects and properties on CanvasKit
-
-	SkCanvas: {
-		// public API (from C++ bindings)
-		clear: function() {},
-		clipPath: function() {},
-		clipRect: function() {},
-		concat: function() {},
-		drawArc: function() {},
-		drawImage: function() {},
-		drawImageRect: function() {},
-		drawLine: function() {},
-		drawOval: function() {},
-		drawPaint: function() {},
-		drawPath: function() {},
-		drawRect: function() {},
-		drawRoundRect: function() {},
-		drawShadow: function() {},
-		drawText: function() {},
-		drawTextBlob: function() {},
-		drawVertices: function() {},
-		flush: function() {},
-		getTotalMatrix: function() {},
-		restore: function() {},
-		rotate: function() {},
-		save: function() {},
-		scale: function() {},
-		skew: function() {},
-		translate: function() {},
-
-		// private API
-		_drawSimpleText: function() {},
-		_readPixels: function() {},
-		_writePixels: function() {},
-		delete: function() {},
-	},
-
-	SkFont: {
-		// public API (from C++ bindings)
-		getScaleX: function() {},
-		getSize: function() {},
-		getSkewX: function() {},
-		getTypeface: function() {},
-		measureText: function() {},
-		setScaleX: function() {},
-		setSize: function() {},
-		setSkewX: function() {},
-		setTypeface: function() {},
-	},
-
-	SkFontMgr: {
-		// public API (from C++ bindings)
-		RefDefault: function() {},
-		countFamilies: function() {},
-
-		// private API
-		_makeTypefaceFromData: function() {},
-	},
-
-	SkImage: {
-		// public API (from C++ bindings)
-		height: function() {},
-		width: function() {},
-		// private API
-		_encodeToData: function() {},
-		_encodeToDataWithFormat: function() {},
-	},
-
-	SkMatrix: {
-		identity: function() {},
-		mapPoints: function() {},
-		multiply: function() {},
-		rotated: function() {},
-		scaled: function() {},
-		skewed: function() {},
-		translated: function() {},
-	},
-
-	SkPaint: {
-		// public API (from C++ bindings)
-		/** @return {CanvasKit.SkPaint} */
-		copy: function() {},
-		getBlendMode: function() {},
-		getColor: function() {},
-		getFilterQuality: function() {},
-		getStrokeCap: function() {},
-		getStrokeJoin: function() {},
-		getStrokeMiter: function() {},
-		getStrokeWidth: function() {},
-		setAntiAlias: function() {},
-		setBlendMode: function() {},
-		setColor: function() {},
-		setFilterQuality: function() {},
-		setMaskFilter: function() {},
-		setPathEffect: function() {},
-		setShader: function() {},
-		setStrokeCap: function() {},
-		setStrokeJoin: function() {},
-		setStrokeMiter: function() {},
-		setStrokeWidth: function() {},
-		setStyle: function() {},
-
-		//private API
-		delete: function() {},
-	},
-
-	SkPath: {
-		// public API (from C++ bindings)
-		computeTightBounds: function() {},
-		contains: function() {},
-		/** @return {CanvasKit.SkPath} */
-		copy: function() {},
-		countPoints: function() {},
-		equals: function() {},
-		getBounds: function() {},
-		getFillType: function() {},
-		getPoint: function() {},
-		isEmpty: function() {},
-		isVolatile: function() {},
-		reset: function() {},
-		rewind: function() {},
-		setFillType: function() {},
-		setIsVolatile: function() {},
-		toSVGString: function() {},
-
-		// private API
-		_addArc: function() {},
-		_addPath: function() {},
-		_addRect: function() {},
-		_addRoundRect: function() {},
-		_arc: function() {},
-		_arcTo: function() {},
-		_close: function() {},
-		_conicTo: function() {},
-		_cubicTo: function() {},
-		_dash: function() {},
-		_lineTo: function() {},
-		_moveTo: function() {},
-		_op: function() {},
-		_quadTo: function() {},
-		_rect: function() {},
-		_simplify: function() {},
-		_stroke: function() {},
-		_transform: function() {},
-		_trim: function() {},
-		delete: function() {},
-		dump: function() {},
-		dumpHex: function() {},
-	},
-
-	SkRect: {
-		fLeft: {},
-		fTop: {},
-		fRight: {},
-		fBottom: {},
-	},
-
-	SkSurface: {
-		// public API (from C++ bindings)
-		/** @return {CanvasKit.SkCanvas} */
-		getCanvas: function() {},
-		/** @return {CanvasKit.SkImage} */
-		makeImageSnapshot: function() {},
-
-		// private API
-		_flush: function() {},
-		_getRasterN32PremulSurface: function() {},
-		delete: function() {},
-	},
-
-	SkTextBlob: {
-		MakeFromText: function() {},
-		_MakeFromText: function() {},
-	},
-
-	SkVertices: {
-		// public API (from C++ bindings)
-		bounds: function() {},
-		mode: function() {},
-		uniqueID: function() {},
-		vertexCount: function() {},
-
-		// private API
-		/** @return {CanvasKit.SkVertices} */
-		_applyBones: function() {},
-	},
-
-	// Constants and Enums
-	gpu: {},
-	skottie: {},
-
-	TRANSPARENT: {},
-	RED: {},
-	BLUE: {},
-	YELLOW: {},
-	CYAN: {},
-	BLACK: {},
-	WHITE: {},
-
-	MOVE_VERB: {},
-	LINE_VERB: {},
-	QUAD_VERB: {},
-	CONIC_VERB: {},
-	CUBIC_VERB: {},
-	CLOSE_VERB: {},
-
-	AlphaType: {
-		Opaque: {},
-		Premul: {},
-		Unpremul: {},
-	},
-
-	BlendMode: {
-		Clear: {},
-		Src: {},
-		Dst: {},
-		SrcOver: {},
-		DstOver: {},
-		SrcIn: {},
-		DstIn: {},
-		SrcOut: {},
-		DstOut: {},
-		SrcATop: {},
-		DstATop: {},
-		Xor: {},
-		Plus: {},
-		Modulate: {},
-		Screen: {},
-		Overlay: {},
-		Darken: {},
-		Lighten: {},
-		ColorDodge: {},
-		ColorBurn: {},
-		HardLight: {},
-		SoftLight: {},
-		Difference: {},
-		Exclusion: {},
-		Multiply: {},
-		Hue: {},
-		Saturation: {},
-		Color: {},
-		Luminosity: {},
-	},
-
-	BlurStyle: {
-		Normal: {},
-		Solid: {},
-		Outer: {},
-		Inner: {},
-	},
-
-	ClipOp: {
-		Difference: {},
-		Intersect: {},
-	},
-
-	ColorType: {
-		Alpha_8: {},
-		RGB_565: {},
-		ARGB_4444: {},
-		RGBA_8888: {},
-		RGB_888x: {},
-		BGRA_8888: {},
-		RGBA_1010102: {},
-		RGB_101010x: {},
-		Gray_8: {},
-		RGBA_F16: {},
-		RGBA_F32: {},
-	},
-
-	FillType: {
-		Winding: {},
-		EvenOdd: {},
-		InverseWinding: {},
-		InverseEvenOdd: {},
-	},
-
-	FilterQuality: {
-		None: {},
-		Low: {},
-		Medium: {},
-		High: {},
-	},
-
-	ImageFormat: {
-		PNG: {},
-		JPEG: {},
-	},
-
-	PaintStyle: {
-		Fill: {},
-		Stroke: {},
-		StrokeAndFill: {},
-	},
-
-	PathOp: {
-		Difference: {},
-		Intersect: {},
-		Union: {},
-		XOR: {},
-		ReverseDifference: {},
-	},
-
-	StrokeCap: {
-		Butt: {},
-		Round: {},
-		Square: {},
-	},
-
-	StrokeJoin: {
-		Miter: {},
-		Round: {},
-		Bevel: {},
-	},
-
-	TextEncoding: {
-		UTF8: {},
-		UTF16: {},
-		UTF32: {},
-		GlyphID: {},
-	},
-
-	TileMode: {
-		Clamp: {},
-		Repeat: {},
-		Mirror: {},
-		Decal: {},
-	},
-
-	VertexMode: {
-		Triangles: {},
-		TrianglesStrip: {},
-		TriangleFan: {},
-	},
-
-	// Things Enscriptem adds for us
-
-	/** Represents the heap of the WASM code
-	 * @type {ArrayBuffer}
-	 */
-	buffer: {},
-	/**
-	 * @type {Float32Array}
-	 */
-	HEAPF32: {},
-	/**
-	 * @type {Uint8Array}
-	 */
-	HEAPU8: {},
-	/**
-	 * @type {Uint16Array}
-	 */
-	HEAPU16: {},
-	/**
-	 * @type {Int32Array}
-	 */
-	HEAP32: {},
-
-	_malloc: function() {},
-	_free: function() {},
-	onRuntimeInitialized: function() {},
-};
-
-// Public API things that are newly declared in the JS should go here.
-// It's not enough to declare them above, because closure can still erase them
-// unless they go on the prototype.
-CanvasKit.SkPath.prototype.addArc = function() {};
-CanvasKit.SkPath.prototype.addPath = function() {};
-CanvasKit.SkPath.prototype.addRect = function() {};
-CanvasKit.SkPath.prototype.addRoundRect = function() {};
-CanvasKit.SkPath.prototype.arc = function() {};
-CanvasKit.SkPath.prototype.arcTo = function() {};
-CanvasKit.SkPath.prototype.close = function() {};
-CanvasKit.SkPath.prototype.conicTo = function() {};
-CanvasKit.SkPath.prototype.cubicTo = function() {};
-CanvasKit.SkPath.prototype.dash = function() {};
-CanvasKit.SkPath.prototype.lineTo = function() {};
-CanvasKit.SkPath.prototype.moveTo = function() {};
-CanvasKit.SkPath.prototype.op = function() {};
-CanvasKit.SkPath.prototype.quadTo = function() {};
-CanvasKit.SkPath.prototype.rect = function() {};
-CanvasKit.SkPath.prototype.simplify = function() {};
-CanvasKit.SkPath.prototype.stroke = function() {};
-CanvasKit.SkPath.prototype.transform = function() {};
-CanvasKit.SkPath.prototype.trim = function() {};
-
-CanvasKit.SkSurface.prototype.flush = function() {};
-CanvasKit.SkSurface.prototype.dispose = function() {};
-
-/** @return {CanvasKit.SkVertices} */
-CanvasKit.SkVertices.prototype.applyBones = function() {};
-
-CanvasKit.SkImage.prototype.encodeToData = function() {};
-
-CanvasKit.SkCanvas.prototype.drawText = function() {};
-/** @return {Uint8Array} */
-CanvasKit.SkCanvas.prototype.readPixels = function() {};
-CanvasKit.SkCanvas.prototype.writePixels = function() {};
-
-CanvasKit.SkFontMgr.prototype.MakeTypefaceFromData = function() {};
-
-// Define StrokeOpts object
-var StrokeOpts = {};
-StrokeOpts.prototype.width;
-StrokeOpts.prototype.miter_limit;
-StrokeOpts.prototype.cap;
-StrokeOpts.prototype.join;
-StrokeOpts.prototype.precision;
-
-// Define everything created in the canvas2d spec here
-var HTMLCanvas = {};
-HTMLCanvas.prototype.decodeImage = function() {};
-HTMLCanvas.prototype.dispose = function() {};
-HTMLCanvas.prototype.getContext = function() {};
-HTMLCanvas.prototype.loadFont = function() {};
-HTMLCanvas.prototype.makePath2D = function() {};
-HTMLCanvas.prototype.toDataURL = function() {};
-
-var CanvasRenderingContext2D = {};
-CanvasRenderingContext2D.prototype.addHitRegion = function() {};
-CanvasRenderingContext2D.prototype.arc = function() {};
-CanvasRenderingContext2D.prototype.arcTo = function() {};
-CanvasRenderingContext2D.prototype.beginPath = function() {};
-CanvasRenderingContext2D.prototype.bezierCurveTo = function() {};
-CanvasRenderingContext2D.prototype.clearHitRegions = function() {};
-CanvasRenderingContext2D.prototype.clearRect = function() {};
-CanvasRenderingContext2D.prototype.clip = function() {};
-CanvasRenderingContext2D.prototype.closePath = function() {};
-CanvasRenderingContext2D.prototype.createImageData = function() {};
-CanvasRenderingContext2D.prototype.createLinearGradient = function() {};
-CanvasRenderingContext2D.prototype.createPattern = function() {};
-CanvasRenderingContext2D.prototype.createRadialGradient = function() {};
-CanvasRenderingContext2D.prototype.drawFocusIfNeeded = function() {};
-CanvasRenderingContext2D.prototype.drawImage = function() {};
-CanvasRenderingContext2D.prototype.ellipse = function() {};
-CanvasRenderingContext2D.prototype.fill = function() {};
-CanvasRenderingContext2D.prototype.fillRect = function() {};
-CanvasRenderingContext2D.prototype.fillText = function() {};
-CanvasRenderingContext2D.prototype.getImageData = function() {};
-CanvasRenderingContext2D.prototype.getLineDash = function() {};
-CanvasRenderingContext2D.prototype.isPointInPath = function() {};
-CanvasRenderingContext2D.prototype.isPointInStroke = function() {};
-CanvasRenderingContext2D.prototype.lineTo = function() {};
-CanvasRenderingContext2D.prototype.measureText = function() {};
-CanvasRenderingContext2D.prototype.moveTo = function() {};
-CanvasRenderingContext2D.prototype.putImageData = function() {};
-CanvasRenderingContext2D.prototype.quadraticCurveTo = function() {};
-CanvasRenderingContext2D.prototype.rect = function() {};
-CanvasRenderingContext2D.prototype.removeHitRegion = function() {};
-CanvasRenderingContext2D.prototype.resetTransform = function() {};
-CanvasRenderingContext2D.prototype.restore = function() {};
-CanvasRenderingContext2D.prototype.rotate = function() {};
-CanvasRenderingContext2D.prototype.save = function() {};
-CanvasRenderingContext2D.prototype.scale = function() {};
-CanvasRenderingContext2D.prototype.scrollPathIntoView = function() {};
-CanvasRenderingContext2D.prototype.setLineDash = function() {};
-CanvasRenderingContext2D.prototype.setTransform = function() {};
-CanvasRenderingContext2D.prototype.stroke = function() {};
-CanvasRenderingContext2D.prototype.strokeRect = function() {};
-CanvasRenderingContext2D.prototype.strokeText = function() {};
-CanvasRenderingContext2D.prototype.transform = function() {};
-CanvasRenderingContext2D.prototype.translate = function() {};
-
-var Path2D = {};
-Path2D.prototype.addPath = function() {};
-Path2D.prototype.arc = function() {};
-Path2D.prototype.arcTo = function() {};
-Path2D.prototype.bezierCurveTo = function() {};
-Path2D.prototype.closePath = function() {};
-Path2D.prototype.ellipse = function() {};
-Path2D.prototype.lineTo = function() {};
-Path2D.prototype.moveTo = function() {};
-Path2D.prototype.quadraticCurveTo = function() {};
-Path2D.prototype.rect = function() {};
-
-var LinearCanvasGradient = {};
-LinearCanvasGradient.prototype.addColorStop = function() {};
-var RadialCanvasGradient = {};
-RadialCanvasGradient.prototype.addColorStop = function() {};
-var CanvasPattern = {};
-CanvasPattern.prototype.setTransform = function() {};
-
-var ImageData = {
-	/**
-	 * @type {Uint8ClampedArray}
-	 */
-	data: {},
-	height: {},
-	width: {},
-};
-
-var DOMMatrix = {
-	a: {},
-	b: {},
-	c: {},
-	d: {},
-	e: {},
-	f: {},
-};
-
-// Not sure why this is needed - might be a bug in emsdk that this isn't properly declared.
-function loadWebAssemblyModule() {};
-
-var DOMMatrix = {};
\ No newline at end of file
diff --git a/experimental/canvaskit/fonts/README.md b/experimental/canvaskit/fonts/README.md
deleted file mode 100644
index 6e2addb..0000000
--- a/experimental/canvaskit/fonts/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-To generate the files in here:
-
-    python tools/embed_resources.py --name SK_EMBEDDED_FONTS --input ~/Downloads/NotoMono-Regular.ttf --output experimental/canvaskit/NotoMono-Regular.ttf.cpp --align 4
\ No newline at end of file
diff --git a/experimental/canvaskit/gpu.js b/experimental/canvaskit/gpu.js
deleted file mode 100644
index 6dca353..0000000
--- a/experimental/canvaskit/gpu.js
+++ /dev/null
@@ -1,87 +0,0 @@
-// Adds compile-time JS functions to augment the CanvasKit interface.
-// Specifically, anything that should only be on the GPU version of canvaskit.
-(function(CanvasKit){
-    CanvasKit._extraInitializations = CanvasKit._extraInitializations || [];
-    CanvasKit._extraInitializations.push(function() {
-      function get(obj, attr, defaultValue) {
-        if (obj && obj.hasOwnProperty(attr)) {
-          return obj[attr];
-        }
-        return defaultValue;
-      }
-
-      function makeWebGLContext(canvas, attrs) {
-        // These defaults come from the emscripten _emscripten_webgl_create_context
-        var contextAttributes = {
-          'alpha': get(attrs, 'alpha', 1),
-          'depth': get(attrs, 'depth', 1),
-          'stencil': get(attrs, 'stencil', 0),
-          'antialias': get(attrs, 'antialias', 1),
-          'premultipliedAlpha': get(attrs, 'premultipliedAlpha', 1),
-          'preserveDrawingBuffer': get(attrs, 'preserveDrawingBuffer', 0),
-          'preferLowPowerToHighPerformance': get(attrs, 'preferLowPowerToHighPerformance', 0),
-          'failIfMajorPerformanceCaveat': get(attrs, 'failIfMajorPerformanceCaveat', 0),
-          'majorVersion': get(attrs, 'majorVersion', 1),
-          'minorVersion': get(attrs, 'minorVersion', 0),
-          'enableExtensionsByDefault': get(attrs, 'enableExtensionsByDefault', 1),
-          'explicitSwapControl': get(attrs, 'explicitSwapControl', 0),
-          'renderViaOffscreenBackBuffer': get(attrs, 'renderViaOffscreenBackBuffer', 0),
-        };
-        if (!canvas) {
-          SkDebug('null canvas passed into makeWebGLContext');
-          return 0;
-        }
-        // This check is from the emscripten version
-        if (contextAttributes['explicitSwapControl']) {
-          SkDebug('explicitSwapControl is not supported');
-          return 0;
-        }
-        return GL.createContext(canvas, contextAttributes);
-      }
-
-      // arg can be of types:
-      //  - String - in which case it is interpreted as an id of a
-      //          canvas element.
-      //  - HTMLCanvasElement - in which the provided canvas element will
-      //          be used directly.
-      //  - int - in which case it will be used as a WebGLContext. Only 1.0
-      //          contexts are known to work for now.
-      // Width and height can be provided to override those on the canvas
-      // element, or specify a height for when a context is provided.
-      CanvasKit.MakeWebGLCanvasSurface = function(arg, width, height) {
-        var ctx = arg;
-        // ctx is only > 0 if it's an int, and thus a valid context
-        if (!(ctx > 0)) {
-          var canvas = arg;
-          if (canvas.tagName !== 'CANVAS') {
-            canvas = document.getElementById(arg);
-            if (!canvas) {
-              throw 'Canvas with id ' + arg + ' was not found';
-            }
-          }
-          // we are ok with all the defaults
-          ctx = makeWebGLContext(canvas);
-        }
-
-        if (!ctx || ctx < 0) {
-          throw 'failed to create webgl context: err ' + ctx;
-        }
-
-        if (!canvas && (!width || !height)) {
-          throw 'height and width must be provided with context';
-        }
-
-        // Maybe better to use clientWidth/height.  See:
-        // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
-        var surface = this._getWebGLSurface(ctx, width || canvas.width,
-                                            height || canvas.height);
-        if (!surface) {
-          SkDebug('falling back from GPU implementation to a SW based one');
-          return CanvasKit.MakeSWCanvasSurface(arg);
-        }
-        return surface;
-      };
-      // Default to trying WebGL first.
-      CanvasKit.MakeCanvasSurface = CanvasKit.MakeWebGLCanvasSurface;
-    });
-}(Module)); // When this file is loaded in, the high level object is "Module";
diff --git a/experimental/canvaskit/helper.js b/experimental/canvaskit/helper.js
deleted file mode 100644
index e49d2b7..0000000
--- a/experimental/canvaskit/helper.js
+++ /dev/null
@@ -1,54 +0,0 @@
-
-// Adds any extra JS functions/helpers we want to add to CanvasKit.
-// Wrapped in a function to avoid leaking global variables.
-(function(CanvasKit){
-
-  function clamp(c) {
-    return Math.round(Math.max(0, Math.min(c || 0, 255)));
-  }
-
-  // Colors are just a 32 bit number with 8 bits each of a, r, g, b
-  // The API is the same as CSS's representation of color rgba(), that is
-  // r,g,b are 0-255, and a is 0.0 to 1.0.
-  // if a is omitted, it will be assumed to be 1.0
-  CanvasKit.Color = function(r, g, b, a) {
-    if (a === undefined) {
-        a = 1;
-    }
-    return (clamp(a*255) << 24) | (clamp(r) << 16) | (clamp(g) << 8) | (clamp(b) << 0);
-  }
-
-  // returns [r, g, b, a] from a color
-  // where a is scaled between 0 and 1.0
-  CanvasKit.getColorComponents = function(color) {
-    return [
-       (color >> 16) & 0xFF,
-       (color >>  8) & 0xFF,
-       (color >>  0) & 0xFF,
-      ((color >> 24) & 0xFF) / 255,
-    ]
-  }
-
-  CanvasKit.multiplyByAlpha = function(color, alpha) {
-    if (alpha === 1) {
-      return color;
-    }
-    // extract as int from 0 to 255
-    var a = (color >> 24) & 0xFF;
-    a *= alpha;
-    // mask off the old alpha
-    color &= 0xFFFFFF;
-    return clamp(a) << 24 | color;
-
-  }
-}(Module)); // When this file is loaded in, the high level object is "Module";
-
-// See https://stackoverflow.com/a/31090240
-// This contraption keeps closure from minifying away the check
-// if btoa is defined *and* prevents runtime "btoa" or "window" is not defined.
-// Defined outside any scopes to make it available in all files.
-var isNode = !(new Function("try {return this===window;}catch(e){ return false;}")());
-
-function almostEqual(floata, floatb) {
-  return Math.abs(floata - floatb) < 0.00001;
-}
\ No newline at end of file
diff --git a/experimental/canvaskit/htmlcanvas/color.js b/experimental/canvaskit/htmlcanvas/color.js
deleted file mode 100644
index afbb8a1..0000000
--- a/experimental/canvaskit/htmlcanvas/color.js
+++ /dev/null
@@ -1,97 +0,0 @@
-// Functions dealing with parsing/stringifying color go here.
-
-// Create the following with
-// node ./htmlcanvas/_namedcolors.js --expose-wasm
-// JS/closure doesn't have a constexpr like thing which
-// would really help here. Since we don't, we pre-compute
-// the map, which saves (a tiny amount of) startup time
-// and (a small amount of) code size.
-/* @dict */
-var colorMap = {'aliceblue':-984833,'antiquewhite':-332841,'aqua':-16711681,'aquamarine':-8388652,'azure':-983041,'beige':-657956,'bisque':-6972,'black':-16777216,'blanchedalmond':-5171,'blue':-16776961,'blueviolet':-7722014,'brown':-5952982,'burlywood':-2180985,'cadetblue':-10510688,'chartreuse':-8388864,'chocolate':-2987746,'coral':-32944,'cornflowerblue':-10185235,'cornsilk':-1828,'crimson':-2354116,'cyan':-16711681,'darkblue':-16777077,'darkcyan':-16741493,'darkgoldenrod':-4684277,'darkgray':-5658199,'darkgreen':-16751616,'darkgrey':-5658199,'darkkhaki':-4343957,'darkmagenta':-7667573,'darkolivegreen':-11179217,'darkorange':-29696,'darkorchid':-6737204,'darkred':-7667712,'darksalmon':-1468806,'darkseagreen':-7357297,'darkslateblue':-12042869,'darkslategray':-13676721,'darkslategrey':-13676721,'darkturquoise':-16724271,'darkviolet':-7077677,'deeppink':-60269,'deepskyblue':-16728065,'dimgray':-9868951,'dimgrey':-9868951,'dodgerblue':-14774017,'firebrick':-5103070,'floralwhite':-1296,'forestgreen':-14513374,'fuchsia':-65281,'gainsboro':-2302756,'ghostwhite':-460545,'gold':-10496,'goldenrod':-2448096,'gray':-8355712,'green':-16744448,'greenyellow':-5374161,'grey':-8355712,'honeydew':-983056,'hotpink':-38476,'indianred':-3318692,'indigo':-11861886,'ivory':-16,'khaki':-989556,'lavender':-1644806,'lavenderblush':-3851,'lawngreen':-8586240,'lemonchiffon':-1331,'lightblue':-5383962,'lightcoral':-1015680,'lightcyan':-2031617,'lightgoldenrodyellow':-329006,'lightgray':-2894893,'lightgreen':-7278960,'lightgrey':-2894893,'lightpink':-18751,'lightsalmon':-24454,'lightseagreen':-14634326,'lightskyblue':-7876870,'lightslategray':-8943463,'lightslategrey':-8943463,'lightsteelblue':-5192482,'lightyellow':-32,'lime':-16711936,'limegreen':-13447886,'linen':-331546,'magenta':-65281,'maroon':-8388608,'mediumaquamarine':-10039894,'mediumblue':-16777011,'mediumorchid':-4565549,'mediumpurple':-7114533,'mediumseagreen':-12799119,'mediumslateblue':-8689426,'mediumspringgreen':-16713062,'mediumturquoise':-12004916,'mediumvioletred':-3730043,'midnightblue':-15132304,'mintcream':-655366,'mistyrose':-6943,'moccasin':-6987,'navajowhite':-8531,'navy':-16777088,'oldlace':-133658,'olive':-8355840,'olivedrab':-9728477,'orange':-23296,'orangered':-47872,'orchid':-2461482,'palegoldenrod':-1120086,'palegreen':-6751336,'paleturquoise':-5247250,'palevioletred':-2396013,'papayawhip':-4139,'peachpuff':-9543,'peru':-3308225,'pink':-16181,'plum':-2252579,'powderblue':-5185306,'purple':-8388480,'rebeccapurple':-10079335,'red':-65536,'rosybrown':-4419697,'royalblue':-12490271,'saddlebrown':-7650029,'salmon':-360334,'sandybrown':-744352,'seagreen':-13726889,'seashell':-2578,'sienna':-6270419,'silver':-4144960,'skyblue':-7876885,'slateblue':-9807155,'slategray':-9404272,'slategrey':-9404272,'snow':-1286,'springgreen':-16711809,'steelblue':-12156236,'tan':-2968436,'teal':-16744320,'thistle':-2572328,'transparent':0,'tomato':-40121,'turquoise':-12525360,'violet':-1146130,'wheat':-663885,'white':-1,'whitesmoke':-657931,'yellow':-256,'yellowgreen':-6632142};
-
-function colorToString(skcolor) {
-  // https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color
-  var components = CanvasKit.getColorComponents(skcolor);
-  var r = components[0];
-  var g = components[1];
-  var b = components[2];
-  var a = components[3];
-  if (a === 1.0) {
-    // hex
-    r = r.toString(16).toLowerCase();
-    g = g.toString(16).toLowerCase();
-    b = b.toString(16).toLowerCase();
-    r = (r.length === 1 ? '0'+r: r);
-    g = (g.length === 1 ? '0'+g: g);
-    b = (b.length === 1 ? '0'+b: b);
-    return '#'+r+g+b;
-  } else {
-    a = (a === 0 || a === 1) ? a : a.toFixed(8);
-    return 'rgba('+r+', '+g+', '+b+', '+a+')';
-  }
-}
-
-function valueOrPercent(aStr) {
-  var a = parseFloat(aStr) || 1;
-  if (aStr && aStr.indexOf('%') !== -1) {
-    return a / 100;
-  }
-  return a;
-}
-
-function parseColor(colorStr) {
-  colorStr = colorStr.toLowerCase();
-  // See https://drafts.csswg.org/css-color/#typedef-hex-color
-  if (colorStr.startsWith('#')) {
-    var r, g, b, a = 255;
-    switch (colorStr.length) {
-      case 9: // 8 hex chars #RRGGBBAA
-        a = parseInt(colorStr.slice(7, 9), 16);
-      case 7: // 6 hex chars #RRGGBB
-        r = parseInt(colorStr.slice(1, 3), 16);
-        g = parseInt(colorStr.slice(3, 5), 16);
-        b = parseInt(colorStr.slice(5, 7), 16);
-        break;
-      case 5: // 4 hex chars #RGBA
-        // multiplying by 17 is the same effect as
-        // appending another character of the same value
-        // e.g. e => ee == 14 => 238
-        a = parseInt(colorStr.slice(4, 5), 16) * 17;
-      case 4: // 6 hex chars #RGB
-        r = parseInt(colorStr.slice(1, 2), 16) * 17;
-        g = parseInt(colorStr.slice(2, 3), 16) * 17;
-        b = parseInt(colorStr.slice(3, 4), 16) * 17;
-        break;
-    }
-    return CanvasKit.Color(r, g, b, a/255);
-
-  } else if (colorStr.startsWith('rgba')) {
-    // Trim off rgba( and the closing )
-    colorStr = colorStr.slice(5, -1);
-    var nums = colorStr.split(',');
-    return CanvasKit.Color(+nums[0], +nums[1], +nums[2],
-                           valueOrPercent(nums[3]));
-  } else if (colorStr.startsWith('rgb')) {
-    // Trim off rgba( and the closing )
-    colorStr = colorStr.slice(4, -1);
-    var nums = colorStr.split(',');
-    // rgb can take 3 or 4 arguments
-    return CanvasKit.Color(+nums[0], +nums[1], +nums[2],
-                           valueOrPercent(nums[3]));
-  } else if (colorStr.startsWith('gray(')) {
-    // TODO
-  } else if (colorStr.startsWith('hsl')) {
-    // TODO
-  } else {
-    // Try for named color
-    var nc = colorMap[colorStr];
-    if (nc !== undefined) {
-      return nc;
-    }
-  }
-  SkDebug('unrecognized color ' + colorStr);
-  return CanvasKit.BLACK;
-}
-
-CanvasKit._testing['parseColor'] = parseColor;
-CanvasKit._testing['colorToString'] = colorToString;
diff --git a/experimental/canvaskit/htmlcanvas/pattern.js b/experimental/canvaskit/htmlcanvas/pattern.js
deleted file mode 100644
index 6691987..0000000
--- a/experimental/canvaskit/htmlcanvas/pattern.js
+++ /dev/null
@@ -1,69 +0,0 @@
-function CanvasPattern(image, repetition) {
-  this._shader = null;
-  // image should be an SkImage returned from HTMLCanvas.decodeImage()
-  this._image = image;
-  this._transform = CanvasKit.SkMatrix.identity();
-
-  if (repetition === '') {
-    repetition = 'repeat';
-  }
-  switch(repetition) {
-    case 'repeat-x':
-      this._tileX = CanvasKit.TileMode.Repeat;
-      // Skia's 'clamp' mode repeats the last row/column
-      // which looks very very strange.
-      // Decal mode does just transparent copying, which
-      // is exactly what the spec wants.
-      this._tileY = CanvasKit.TileMode.Decal;
-      break;
-    case 'repeat-y':
-      this._tileX = CanvasKit.TileMode.Decal;
-      this._tileY = CanvasKit.TileMode.Repeat;
-      break;
-    case 'repeat':
-      this._tileX = CanvasKit.TileMode.Repeat;
-      this._tileY = CanvasKit.TileMode.Repeat;
-      break;
-    case 'no-repeat':
-      this._tileX = CanvasKit.TileMode.Decal;
-      this._tileY = CanvasKit.TileMode.Decal;
-      break;
-    default:
-      throw 'invalid repetition mode ' + repetition;
-  }
-
-  // Takes a DOMMatrix like object. e.g. the identity would be:
-  // {a:1, b: 0, c: 0, d: 1, e: 0, f: 0}
-  // @param {DOMMatrix} m
-  this.setTransform = function(m) {
-    var t = [m.a, m.c, m.e,
-             m.b, m.d, m.f,
-               0,   0,   1];
-    if (allAreFinite(t)) {
-      this._transform = t;
-    }
-  }
-
-  this._copy = function() {
-    var cp = new CanvasPattern()
-    cp._tileX = this._tileX;
-    cp._tileY = this._tileY;
-    return cp;
-  }
-
-  this._dispose = function() {
-    if (this._shader) {
-      this._shader.delete();
-      this._shader = null;
-    }
-  }
-
-  this._getShader = function(currentTransform) {
-    // Ignore currentTransform since it will be applied later
-    this._dispose();
-    this._shader = CanvasKit.MakeImageShader(this._image, this._tileX, this._tileY,
-                                             false, this._transform);
-    return this._shader;
-  }
-
-}
\ No newline at end of file
diff --git a/experimental/canvaskit/htmlcanvas/preamble.js b/experimental/canvaskit/htmlcanvas/preamble.js
deleted file mode 100644
index 4ee1fe1..0000000
--- a/experimental/canvaskit/htmlcanvas/preamble.js
+++ /dev/null
@@ -1,17 +0,0 @@
-// Adds compile-time JS functions to augment the CanvasKit interface.
-// Specifically, the code that emulates the HTML Canvas interface
-// (which is called HTMLCanvas or similar to avoid confusion with
-// SkCanvas).
-(function(CanvasKit) {
-
-  // This allows us to expose internal functions (e.g. color
-  // parsing) for unit-testing, even in the minified version.
-  // Our tests are not minified like CanvasKit is, so the names
-  // would get lost otherwise.
-  CanvasKit._testing = {};
-
-// This intentionally dangles because we want all the htmlcanvas
-// JS code to be in the same scope, but JS doesn't support
-// namespaces like C++ does. Thus, we simply include this
-// preamble.js file, all the source .js files and then postamble.js
-// to bundle everything in the same scope.
\ No newline at end of file
diff --git a/experimental/canvaskit/interface.js b/experimental/canvaskit/interface.js
deleted file mode 100644
index fe0d6be..0000000
--- a/experimental/canvaskit/interface.js
+++ /dev/null
@@ -1,802 +0,0 @@
-// Adds JS functions to augment the CanvasKit interface.
-// For example, if there is a wrapper around the C++ call or logic to allow
-// chaining, it should go here.
-(function(CanvasKit) {
-  // CanvasKit.onRuntimeInitialized is called after the WASM library has loaded.
-  // Anything that modifies an exposed class (e.g. SkPath) should be set
-  // after onRuntimeInitialized, otherwise, it can happen outside of that scope.
-  CanvasKit.onRuntimeInitialized = function() {
-    // All calls to 'this' need to go in externs.js so closure doesn't minify them away.
-
-
-    // Add some helpers for matrices. This is ported from SkMatrix.cpp
-    // to save complexity and overhead of going back and forth between
-    // C++ and JS layers.
-    // I would have liked to use something like DOMMatrix, except it
-    // isn't widely supported (would need polyfills) and it doesn't
-    // have a mapPoints() function (which could maybe be tacked on here).
-    // If DOMMatrix catches on, it would be worth re-considering this usage.
-    CanvasKit.SkMatrix = {};
-    function sdot(a, b, c, d, e, f) {
-      e = e || 0;
-      f = f || 0;
-      return a * b + c * d + e * f;
-    }
-
-    CanvasKit.SkMatrix.identity = function() {
-      return [
-        1, 0, 0,
-        0, 1, 0,
-        0, 0, 1,
-      ];
-    };
-
-    // Return the inverse (if it exists) of this matrix.
-    // Otherwise, return the identity.
-    CanvasKit.SkMatrix.invert = function(m) {
-      var det = m[0]*m[4]*m[8] + m[1]*m[5]*m[6] + m[2]*m[3]*m[7]
-              - m[2]*m[4]*m[6] - m[1]*m[3]*m[8] - m[0]*m[5]*m[7];
-      if (!det) {
-        SkDebug('Warning, uninvertible matrix');
-        return CanvasKit.SkMatrix.identity();
-      }
-      return [
-        (m[4]*m[8] - m[5]*m[7])/det, (m[2]*m[7] - m[1]*m[8])/det, (m[1]*m[5] - m[2]*m[4])/det,
-        (m[5]*m[6] - m[3]*m[8])/det, (m[0]*m[8] - m[2]*m[6])/det, (m[2]*m[3] - m[0]*m[5])/det,
-        (m[3]*m[7] - m[4]*m[6])/det, (m[1]*m[6] - m[0]*m[7])/det, (m[0]*m[4] - m[1]*m[3])/det,
-      ];
-    };
-
-    // Maps the given points according to the passed in matrix.
-    // Results are done in place.
-    // See SkMatrix.h::mapPoints for the docs on the math.
-    CanvasKit.SkMatrix.mapPoints = function(matrix, ptArr) {
-      if (ptArr.length % 2) {
-        throw 'mapPoints requires an even length arr';
-      }
-      for (var i = 0; i < ptArr.length; i+=2) {
-        var x = ptArr[i], y = ptArr[i+1];
-        // Gx+Hy+I
-        var denom  = matrix[6]*x + matrix[7]*y + matrix[8];
-        // Ax+By+C
-        var xTrans = matrix[0]*x + matrix[1]*y + matrix[2];
-        // Dx+Ey+F
-        var yTrans = matrix[3]*x + matrix[4]*y + matrix[5];
-        ptArr[i]   = xTrans/denom;
-        ptArr[i+1] = yTrans/denom;
-      }
-      return ptArr;
-    };
-
-    CanvasKit.SkMatrix.multiply = function(m1, m2) {
-      var result = [0,0,0, 0,0,0, 0,0,0];
-      for (var r = 0; r < 3; r++) {
-        for (var c = 0; c < 3; c++) {
-          // m1 and m2 are 1D arrays pretending to be 2D arrays
-          result[3*r + c] = sdot(m1[3*r + 0], m2[3*0 + c],
-                                 m1[3*r + 1], m2[3*1 + c],
-                                 m1[3*r + 2], m2[3*2 + c]);
-        }
-      }
-      return result;
-    }
-
-    // Return a matrix representing a rotation by n radians.
-    // px, py optionally say which point the rotation should be around
-    // with the default being (0, 0);
-    CanvasKit.SkMatrix.rotated = function(radians, px, py) {
-      px = px || 0;
-      py = py || 0;
-      var sinV = Math.sin(radians);
-      var cosV = Math.cos(radians);
-      return [
-        cosV, -sinV, sdot( sinV, py, 1 - cosV, px),
-        sinV,  cosV, sdot(-sinV, px, 1 - cosV, py),
-        0,        0,                             1,
-      ];
-    };
-
-    CanvasKit.SkMatrix.scaled = function(sx, sy, px, py) {
-      px = px || 0;
-      py = py || 0;
-      return [
-        sx, 0, px - sx * px,
-        0, sy, py - sy * py,
-        0,  0,            1,
-      ];
-    };
-
-    CanvasKit.SkMatrix.skewed = function(kx, ky, px, py) {
-      px = px || 0;
-      py = py || 0;
-      return [
-        1, kx, -kx * px,
-        ky, 1, -ky * py,
-        0,  0,        1,
-      ];
-    };
-
-    CanvasKit.SkMatrix.translated = function(dx, dy) {
-      return [
-        1, 0, dx,
-        0, 1, dy,
-        0, 0,  1,
-      ];
-    };
-
-    CanvasKit.SkPath.prototype.addArc = function(oval, startAngle, sweepAngle) {
-      // see arc() for the HTMLCanvas version
-      // note input angles are degrees.
-      this._addArc(oval, startAngle, sweepAngle);
-      return this;
-    };
-
-    CanvasKit.SkPath.prototype.addPath = function() {
-      // Takes 1, 2, 7, or 10 required args, where the first arg is always the path.
-      // The last arg is optional and chooses between add or extend mode.
-      // The options for the remaining args are:
-      //   - an array of 6 or 9 parameters (perspective is optional)
-      //   - the 9 parameters of a full matrix or
-      //     the 6 non-perspective params of a matrix.
-      var args = Array.prototype.slice.call(arguments);
-      var path = args[0];
-      var extend = false;
-      if (typeof args[args.length-1] === "boolean") {
-        extend = args.pop();
-      }
-      if (args.length === 1) {
-        // Add path, unchanged.  Use identity matrix
-        this._addPath(path, 1, 0, 0,
-                            0, 1, 0,
-                            0, 0, 1,
-                            extend);
-      } else if (args.length === 2) {
-        // User provided the 9 params of a full matrix as an array.
-        var a = args[1];
-        this._addPath(path, a[0],      a[1],      a[2],
-                            a[3],      a[4],      a[5],
-                            a[6] || 0, a[7] || 0, a[8] || 1,
-                            extend);
-      } else if (args.length === 7 || args.length === 10) {
-        // User provided the 9 params of a (full) matrix directly.
-        // (or just the 6 non perspective ones)
-        // These are in the same order as what Skia expects.
-        var a = args;
-        this._addPath(path, a[1],      a[2],      a[3],
-                            a[4],      a[5],      a[6],
-                            a[7] || 0, a[8] || 0, a[9] || 1,
-                            extend);
-      } else {
-        SkDebug('addPath expected to take 1, 2, 7, or 10 required args. Got ' + args.length);
-        return null;
-      }
-      return this;
-    };
-
-    CanvasKit.SkPath.prototype.addRect = function() {
-      // Takes 1, 2, 4 or 5 args
-      //  - SkRect
-      //  - SkRect, isCCW
-      //  - left, top, right, bottom
-      //  - left, top, right, bottom, isCCW
-      if (arguments.length === 1 || arguments.length === 2) {
-        var r = arguments[0];
-        var ccw = arguments[1] || false;
-        this._addRect(r.fLeft, r.fTop, r.fRight, r.fBottom, ccw);
-      } else if (arguments.length === 4 || arguments.length === 5) {
-        var a = arguments;
-        this._addRect(a[0], a[1], a[2], a[3], a[4] || false);
-      } else {
-        SkDebug('addRect expected to take 1, 2, 4, or 5 args. Got ' + arguments.length);
-        return null;
-      }
-      return this;
-    };
-
-    CanvasKit.SkPath.prototype.addRoundRect = function() {
-      // Takes 3, 4, 6 or 7 args
-      //  - SkRect, radii, ccw
-      //  - SkRect, rx, ry, ccw
-      //  - left, top, right, bottom, radii, ccw
-      //  - left, top, right, bottom, rx, ry, ccw
-      var args = arguments;
-      if (args.length === 3 || args.length === 6) {
-        var radii = args[args.length-2];
-      } else if (args.length === 6 || args.length === 7){
-        // duplicate the given (rx, ry) pairs for each corner.
-        var rx = args[args.length-3];
-        var ry = args[args.length-2];
-        var radii = [rx, ry, rx, ry, rx, ry, rx, ry];
-      } else {
-        SkDebug('addRoundRect expected to take 3, 4, 6, or 7 args. Got ' + args.length);
-        return null;
-      }
-      if (radii.length !== 8) {
-        SkDebug('addRoundRect needs 8 radii provided. Got ' + radii.length);
-        return null;
-      }
-      var rptr = copy1dArray(radii, CanvasKit.HEAPF32);
-      if (args.length === 3 || args.length === 4) {
-        var r = args[0];
-        var ccw = args[args.length - 1];
-        this._addRoundRect(r.fLeft, r.fTop, r.fRight, r.fBottom, rptr, ccw);
-      } else if (args.length === 6 || args.length === 7) {
-        var a = args;
-        this._addRoundRect(a[0], a[1], a[2], a[3], rptr, ccw);
-      }
-      CanvasKit._free(rptr);
-      return this;
-    };
-
-    CanvasKit.SkPath.prototype.arc = function(x, y, radius, startAngle, endAngle, ccw) {
-      // emulates the HTMLCanvas behavior.  See addArc() for the SkPath version.
-      // Note input angles are radians.
-      var bounds = CanvasKit.LTRBRect(x-radius, y-radius, x+radius, y+radius);
-      var sweep = radiansToDegrees(endAngle - startAngle) - (360 * !!ccw);
-      var temp = new CanvasKit.SkPath();
-      temp.addArc(bounds, radiansToDegrees(startAngle), sweep);
-      this.addPath(temp, true);
-      temp.delete();
-      return this;
-    };
-
-    CanvasKit.SkPath.prototype.arcTo = function() {
-      // takes 4, 5 or 7 args
-      // - 5 x1, y1, x2, y2, radius
-      // - 4 oval (as Rect), startAngle, sweepAngle, forceMoveTo
-      // - 7 x1, y1, x2, y2, startAngle, sweepAngle, forceMoveTo
-      var args = arguments;
-      if (args.length === 5) {
-        this._arcTo(args[0], args[1], args[2], args[3], args[4]);
-      } else if (args.length === 4) {
-        this._arcTo(args[0], args[1], args[2], args[3]);
-      } else if (args.length === 7) {
-        this._arcTo(CanvasKit.LTRBRect(args[0], args[1], args[2], args[3]),
-                    args[4], args[5], args[6]);
-      } else {
-        throw 'Invalid args for arcTo. Expected 4, 5, or 7, got '+ args.length;
-      }
-
-      return this;
-    };
-
-    CanvasKit.SkPath.prototype.close = function() {
-      this._close();
-      return this;
-    };
-
-    CanvasKit.SkPath.prototype.conicTo = function(x1, y1, x2, y2, w) {
-      this._conicTo(x1, y1, x2, y2, w);
-      return this;
-    };
-
-    CanvasKit.SkPath.prototype.cubicTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
-      this._cubicTo(cp1x, cp1y, cp2x, cp2y, x, y);
-      return this;
-    };
-
-    CanvasKit.SkPath.prototype.dash = function(on, off, phase) {
-      if (this._dash(on, off, phase)) {
-        return this;
-      }
-      return null;
-    };
-
-    CanvasKit.SkPath.prototype.lineTo = function(x, y) {
-      this._lineTo(x, y);
-      return this;
-    };
-
-    CanvasKit.SkPath.prototype.moveTo = function(x, y) {
-      this._moveTo(x, y);
-      return this;
-    };
-
-    CanvasKit.SkPath.prototype.op = function(otherPath, op) {
-      if (this._op(otherPath, op)) {
-        return this;
-      }
-      return null;
-    };
-
-    CanvasKit.SkPath.prototype.quadTo = function(cpx, cpy, x, y) {
-      this._quadTo(cpx, cpy, x, y);
-      return this;
-    };
-
-    CanvasKit.SkPath.prototype.simplify = function() {
-      if (this._simplify()) {
-        return this;
-      }
-      return null;
-    };
-
-    CanvasKit.SkPath.prototype.stroke = function(opts) {
-      // Fill out any missing values with the default values.
-      /**
-       * See externs.js for this definition
-       * @type {StrokeOpts}
-       */
-      opts = opts || {};
-      opts.width = opts.width || 1;
-      opts.miter_limit = opts.miter_limit || 4;
-      opts.cap = opts.cap || CanvasKit.StrokeCap.Butt;
-      opts.join = opts.join || CanvasKit.StrokeJoin.Miter;
-      opts.precision = opts.precision || 1;
-      if (this._stroke(opts)) {
-        return this;
-      }
-      return null;
-    };
-
-    CanvasKit.SkPath.prototype.transform = function() {
-      // Takes 1 or 9 args
-      if (arguments.length === 1) {
-        // argument 1 should be a 6 or 9 element array.
-        var a = arguments[0];
-        this._transform(a[0], a[1], a[2],
-                        a[3], a[4], a[5],
-                        a[6] || 0, a[7] || 0, a[8] || 1);
-      } else if (arguments.length === 6 || arguments.length === 9) {
-        // these arguments are the 6 or 9 members of the matrix
-        var a = arguments;
-        this._transform(a[0], a[1], a[2],
-                        a[3], a[4], a[5],
-                        a[6] || 0, a[7] || 0, a[8] || 1);
-      } else {
-        throw 'transform expected to take 1 or 9 arguments. Got ' + arguments.length;
-      }
-      return this;
-    };
-    // isComplement is optional, defaults to false
-    CanvasKit.SkPath.prototype.trim = function(startT, stopT, isComplement) {
-      if (this._trim(startT, stopT, !!isComplement)) {
-        return this;
-      }
-      return null;
-    };
-
-    // bones should be a 3d array.
-    // Each bone is a 3x2 transformation matrix in column major order:
-    // | scaleX   skewX transX |
-    // |  skewY  scaleY transY |
-    // and bones is an array of those matrices.
-    // Returns a copy of this (SkVertices) with the bones applied.
-    CanvasKit.SkVertices.prototype.applyBones = function(bones) {
-      var bPtr = copy3dArray(bones, CanvasKit.HEAPF32);
-      var vert = this._applyBones(bPtr, bones.length);
-      CanvasKit._free(bPtr);
-      return vert;
-    }
-
-    CanvasKit.SkImage.prototype.encodeToData = function() {
-      if (!arguments.length) {
-        return this._encodeToData();
-      }
-
-      if (arguments.length === 2) {
-        var a = arguments;
-        return this._encodeToDataWithFormat(a[0], a[1]);
-      }
-
-      throw 'encodeToData expected to take 0 or 2 arguments. Got ' + arguments.length;
-    }
-
-    CanvasKit.SkCanvas.prototype.drawText = function(str, x, y, font, paint) {
-      // lengthBytesUTF8 and stringToUTF8Array are defined in the emscripten
-      // JS.  See https://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#stringToUTF8
-      // Add 1 for null terminator
-      var strLen = lengthBytesUTF8(str) + 1;
-      var strPtr = CanvasKit._malloc(strLen);
-      // Add 1 for the null terminator.
-      stringToUTF8(str, strPtr, strLen);
-      this._drawSimpleText(strPtr, strLen, x, y, font, paint);
-    }
-
-    // returns Uint8Array
-    CanvasKit.SkCanvas.prototype.readPixels = function(x, y, w, h, alphaType,
-                                                       colorType, dstRowBytes) {
-      // supply defaults (which are compatible with HTMLCanvas's getImageData)
-      alphaType = alphaType || CanvasKit.AlphaType.Unpremul;
-      colorType = colorType || CanvasKit.ColorType.RGBA_8888;
-      dstRowBytes = dstRowBytes || (4 * w);
-
-      var len = h * dstRowBytes
-      var pptr = CanvasKit._malloc(len);
-      var ok = this._readPixels({
-        'width': w,
-        'height': h,
-        'colorType': colorType,
-        'alphaType': alphaType,
-      }, pptr, dstRowBytes, x, y);
-      if (!ok) {
-        CanvasKit._free(pptr);
-        return null;
-      }
-
-      // The first typed array is just a view into memory. Because we will
-      // be free-ing that, we call slice to make a persistent copy.
-      var pixels = new Uint8Array(CanvasKit.HEAPU8.buffer, pptr, len).slice();
-      CanvasKit._free(pptr);
-      return pixels;
-    }
-
-    // pixels is a TypedArray. No matter the input size, it will be treated as
-    // a Uint8Array (essentially, a byte array).
-    CanvasKit.SkCanvas.prototype.writePixels = function(pixels, srcWidth, srcHeight,
-                                                        destX, destY, alphaType, colorType) {
-      if (pixels.byteLength % (srcWidth * srcHeight)) {
-        throw 'pixels length must be a multiple of the srcWidth * srcHeight';
-      }
-      var bytesPerPixel = pixels.byteLength / (srcWidth * srcHeight);
-      // supply defaults (which are compatible with HTMLCanvas's putImageData)
-      alphaType = alphaType || CanvasKit.AlphaType.Unpremul;
-      colorType = colorType || CanvasKit.ColorType.RGBA_8888;
-      var srcRowBytes = bytesPerPixel * srcWidth;
-
-      var pptr = CanvasKit._malloc(pixels.byteLength);
-      CanvasKit.HEAPU8.set(pixels, pptr);
-
-      var ok = this._writePixels({
-        'width': srcWidth,
-        'height': srcHeight,
-        'colorType': colorType,
-        'alphaType': alphaType,
-      }, pptr, srcRowBytes, destX, destY);
-
-      CanvasKit._free(pptr);
-      return ok;
-    }
-
-    // fontData should be an arrayBuffer
-    CanvasKit.SkFontMgr.prototype.MakeTypefaceFromData = function(fontData) {
-      var data = new Uint8Array(fontData);
-
-      var fptr = CanvasKit._malloc(data.byteLength);
-      CanvasKit.HEAPU8.set(data, fptr);
-      var font = this._makeTypefaceFromData(fptr, data.byteLength);
-      if (!font) {
-        SkDebug('Could not decode font data');
-        // We do not need to free the data since the C++ will do that for us
-        // when the font is deleted (or fails to decode);
-        return null;
-      }
-      return font;
-    }
-
-    CanvasKit.SkTextBlob.MakeFromText = function(str, font) {
-      // lengthBytesUTF8 and stringToUTF8Array are defined in the emscripten
-      // JS.  See https://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#stringToUTF8
-      // Add 1 for null terminator
-      var strLen = lengthBytesUTF8(str) + 1;
-      var strPtr = CanvasKit._malloc(strLen);
-      // Add 1 for the null terminator.
-      stringToUTF8(str, strPtr, strLen);
-
-      var blob = CanvasKit.SkTextBlob._MakeFromText(strPtr, strLen - 1, font, CanvasKit.TextEncoding.UTF8);
-      if (!blob) {
-        SkDebug('Could not make textblob from string "' + str + '"');
-        return null;
-      }
-
-      var origDelete = blob.delete.bind(blob);
-      blob.delete = function() {
-        CanvasKit._free(strPtr);
-        origDelete();
-      }
-      return blob;
-    }
-
-    // Run through the JS files that are added at compile time.
-    if (CanvasKit._extraInitializations) {
-      CanvasKit._extraInitializations.forEach(function(init) {
-        init();
-      });
-    }
-  } // end CanvasKit.onRuntimeInitialized, that is, anything changing prototypes or dynamic.
-
-  CanvasKit.LTRBRect = function(l, t, r, b) {
-    return {
-      fLeft: l,
-      fTop: t,
-      fRight: r,
-      fBottom: b,
-    };
-  }
-
-  CanvasKit.XYWHRect = function(x, y, w, h) {
-    return {
-      fLeft: x,
-      fTop: y,
-      fRight: x+w,
-      fBottom: y+h,
-    };
-  }
-
-  var nullptr = 0; // emscripten doesn't like to take null as uintptr_t
-
-  // arr can be a normal JS array or a TypedArray
-  // dest is something like CanvasKit.HEAPF32
-  function copy1dArray(arr, dest) {
-    if (!arr || !arr.length) {
-      return nullptr;
-    }
-    var ptr = CanvasKit._malloc(arr.length * dest.BYTES_PER_ELEMENT);
-    // In c++ terms, the WASM heap is a uint8_t*, a long buffer/array of single
-    // byte elements. When we run _malloc, we always get an offset/pointer into
-    // that block of memory.
-    // CanvasKit exposes some different views to make it easier to work with
-    // different types. HEAPF32 for example, exposes it as a float*
-    // However, to make the ptr line up, we have to do some pointer arithmetic.
-    // Concretely, we need to convert ptr to go from an index into a 1-byte-wide
-    // buffer to an index into a 4-byte-wide buffer (in the case of HEAPF32)
-    // and thus we divide ptr by 4.
-    dest.set(arr, ptr / dest.BYTES_PER_ELEMENT);
-    return ptr;
-  }
-
-  // arr should be a non-jagged 2d JS array (TypeyArrays can't be nested
-  //     inside themselves.)
-  // dest is something like CanvasKit.HEAPF32
-  function copy2dArray(arr, dest) {
-    if (!arr || !arr.length) {
-      return nullptr;
-    }
-    var ptr = CanvasKit._malloc(arr.length * arr[0].length * dest.BYTES_PER_ELEMENT);
-    var idx = 0;
-    var adjustedPtr = ptr / dest.BYTES_PER_ELEMENT;
-    for (var r = 0; r < arr.length; r++) {
-      for (var c = 0; c < arr[0].length; c++) {
-        dest[adjustedPtr + idx] = arr[r][c];
-        idx++;
-      }
-    }
-    return ptr;
-  }
-
-  // arr should be a non-jagged 3d JS array (TypeyArrays can't be nested
-  //     inside themselves.)
-  // dest is something like CanvasKit.HEAPF32
-  function copy3dArray(arr, dest) {
-    if (!arr || !arr.length || !arr[0].length) {
-      return nullptr;
-    }
-    var ptr = CanvasKit._malloc(arr.length * arr[0].length * arr[0][0].length * dest.BYTES_PER_ELEMENT);
-    var idx = 0;
-    var adjustedPtr = ptr / dest.BYTES_PER_ELEMENT;
-    for (var x = 0; x < arr.length; x++) {
-      for (var y = 0; y < arr[0].length; y++) {
-        for (var z = 0; z < arr[0][0].length; z++) {
-          dest[adjustedPtr + idx] = arr[x][y][z];
-          idx++;
-        }
-      }
-    }
-    return ptr;
-  }
-
-  // Caching the Float32Arrays can save having to reallocate them
-  // over and over again.
-  var Float32ArrayCache = {};
-
-  // Takes a 2D array of commands and puts them into the WASM heap
-  // as a 1D array. This allows them to referenced from the C++ code.
-  // Returns a 2 element array, with the first item being essentially a
-  // pointer to the array and the second item being the length of
-  // the new 1D array.
-  //
-  // Example usage:
-  // let cmds = [
-  //   [CanvasKit.MOVE_VERB, 0, 10],
-  //   [CanvasKit.LINE_VERB, 30, 40],
-  //   [CanvasKit.QUAD_VERB, 20, 50, 45, 60],
-  // ];
-  function loadCmdsTypedArray(arr) {
-    var len = 0;
-    for (var r = 0; r < arr.length; r++) {
-      len += arr[r].length;
-    }
-
-    var ta;
-    if (Float32ArrayCache[len]) {
-      ta = Float32ArrayCache[len];
-    } else {
-      ta = new Float32Array(len);
-      Float32ArrayCache[len] = ta;
-    }
-    // Flatten into a 1d array
-    var i = 0;
-    for (var r = 0; r < arr.length; r++) {
-      for (var c = 0; c < arr[r].length; c++) {
-        var item = arr[r][c];
-        ta[i] = item;
-        i++;
-      }
-    }
-
-    var ptr = copy1dArray(ta, CanvasKit.HEAPF32);
-    return [ptr, len];
-  }
-
-  CanvasKit.MakePathFromCmds = function(cmds) {
-    var ptrLen = loadCmdsTypedArray(cmds);
-    var path = CanvasKit._MakePathFromCmds(ptrLen[0], ptrLen[1]);
-    CanvasKit._free(ptrLen[0]);
-    return path;
-  }
-
-  CanvasKit.MakeSkDashPathEffect = function(intervals, phase) {
-    if (!phase) {
-      phase = 0;
-    }
-    if (!intervals.length || intervals.length % 2 === 1) {
-      throw 'Intervals array must have even length';
-    }
-    var ptr = copy1dArray(intervals, CanvasKit.HEAPF32);
-    var dpe = CanvasKit._MakeSkDashPathEffect(ptr, intervals.length, phase);
-    CanvasKit._free(ptr);
-    return dpe;
-  }
-
-  // data is a TypedArray or ArrayBuffer e.g. from fetch().then(resp.arrayBuffer())
-  CanvasKit.MakeImageFromEncoded = function(data) {
-    data = new Uint8Array(data);
-
-    var iptr = CanvasKit._malloc(data.byteLength);
-    CanvasKit.HEAPU8.set(data, iptr);
-    var img = CanvasKit._decodeImage(iptr, data.byteLength);
-    if (!img) {
-      SkDebug('Could not decode image');
-      CanvasKit._free(iptr);
-      return null;
-    }
-    var realDelete = img.delete.bind(img);
-    img.delete = function() {
-      CanvasKit._free(iptr);
-      realDelete();
-    }
-    return img;
-  }
-
-  // imgData is an Encoded SkImage, e.g. from MakeImageFromEncoded
-  CanvasKit.MakeImageShader = function(img, xTileMode, yTileMode, clampUnpremul, localMatrix) {
-    if (!img) {
-      return null;
-    }
-    clampUnpremul = clampUnpremul || false;
-    if (localMatrix) {
-      // Add perspective args if not provided.
-      if (localMatrix.length === 6) {
-        localMatrix.push(0, 0, 1);
-      }
-      return CanvasKit._MakeImageShader(img, xTileMode, yTileMode, clampUnpremul, localMatrix);
-    } else {
-      return CanvasKit._MakeImageShader(img, xTileMode, yTileMode, clampUnpremul);
-    }
-  }
-
-  // pixels is a Uint8Array
-  CanvasKit.MakeImage = function(pixels, width, height, alphaType, colorType) {
-    var bytesPerPixel = pixels.byteLength / (width * height);
-    var info = {
-      'width': width,
-      'height': height,
-      'alphaType': alphaType,
-      'colorType': colorType,
-    };
-    var pptr = CanvasKit._malloc(pixels.byteLength);
-    CanvasKit.HEAPU8.set(pixels, pptr);
-    // No need to _free iptr, Image takes it with SkData::MakeFromMalloc
-
-    return CanvasKit._MakeImage(info, pptr, pixels.byteLength, width * bytesPerPixel);
-  }
-
-  CanvasKit.MakeLinearGradientShader = function(start, end, colors, pos, mode, localMatrix, flags) {
-    var colorPtr = copy1dArray(colors, CanvasKit.HEAP32);
-    var posPtr =   copy1dArray(pos,    CanvasKit.HEAPF32);
-    flags = flags || 0;
-
-    if (localMatrix) {
-      // Add perspective args if not provided.
-      if (localMatrix.length === 6) {
-        localMatrix.push(0, 0, 1);
-      }
-      var lgs = CanvasKit._MakeLinearGradientShader(start, end, colorPtr, posPtr,
-                                                    colors.length, mode, flags, localMatrix);
-    } else {
-      var lgs = CanvasKit._MakeLinearGradientShader(start, end, colorPtr, posPtr,
-                                                    colors.length, mode, flags);
-    }
-
-    CanvasKit._free(colorPtr);
-    CanvasKit._free(posPtr);
-    return lgs;
-  }
-
-  CanvasKit.MakeRadialGradientShader = function(center, radius, colors, pos, mode, localMatrix, flags) {
-    var colorPtr = copy1dArray(colors, CanvasKit.HEAP32);
-    var posPtr =   copy1dArray(pos,    CanvasKit.HEAPF32);
-    flags = flags || 0;
-
-    if (localMatrix) {
-      // Add perspective args if not provided.
-      if (localMatrix.length === 6) {
-        localMatrix.push(0, 0, 1);
-      }
-      var rgs = CanvasKit._MakeRadialGradientShader(center, radius, colorPtr, posPtr,
-                                                    colors.length, mode, flags, localMatrix);
-    } else {
-      var rgs = CanvasKit._MakeRadialGradientShader(center, radius, colorPtr, posPtr,
-                                                    colors.length, mode, flags);
-    }
-
-    CanvasKit._free(colorPtr);
-    CanvasKit._free(posPtr);
-    return rgs;
-  }
-
-  CanvasKit.MakeTwoPointConicalGradientShader = function(start, startRadius, end, endRadius,
-                                                         colors, pos, mode, localMatrix, flags) {
-    var colorPtr = copy1dArray(colors, CanvasKit.HEAP32);
-    var posPtr =   copy1dArray(pos,    CanvasKit.HEAPF32);
-    flags = flags || 0;
-
-    if (localMatrix) {
-      // Add perspective args if not provided.
-      if (localMatrix.length === 6) {
-        localMatrix.push(0, 0, 1);
-      }
-      var rgs = CanvasKit._MakeTwoPointConicalGradientShader(
-                          start, startRadius, end, endRadius,
-                          colorPtr, posPtr, colors.length, mode, flags, localMatrix);
-    } else {
-      var rgs = CanvasKit._MakeTwoPointConicalGradientShader(
-                          start, startRadius, end, endRadius,
-                          colorPtr, posPtr, colors.length, mode, flags);
-    }
-
-    CanvasKit._free(colorPtr);
-    CanvasKit._free(posPtr);
-    return rgs;
-  }
-
-  CanvasKit.MakeSkVertices = function(mode, positions, textureCoordinates, colors,
-                                      boneIndices, boneWeights, indices) {
-    var positionPtr = copy2dArray(positions,          CanvasKit.HEAPF32);
-    var texPtr =      copy2dArray(textureCoordinates, CanvasKit.HEAPF32);
-    // Since we write the colors to memory as signed integers (JSColor), we can
-    // read them out on the other side as unsigned ints (SkColor) just fine
-    // - it's effectively casting.
-    var colorPtr =    copy1dArray(colors,             CanvasKit.HEAP32);
-
-    var boneIdxPtr =  copy2dArray(boneIndices,        CanvasKit.HEAP32);
-    var boneWtPtr  =  copy2dArray(boneWeights,        CanvasKit.HEAPF32);
-    var idxPtr =      copy1dArray(indices,            CanvasKit.HEAPU16);
-
-    var idxCount = (indices && indices.length) || 0;
-    // _MakeVertices will copy all the values in, so we are free to release
-    // the memory after.
-    var vertices = CanvasKit._MakeSkVertices(mode, positions.length, positionPtr,
-                                             texPtr, colorPtr, boneIdxPtr, boneWtPtr,
-                                             idxCount, idxPtr);
-    positionPtr && CanvasKit._free(positionPtr);
-    texPtr && CanvasKit._free(texPtr);
-    colorPtr && CanvasKit._free(colorPtr);
-    idxPtr && CanvasKit._free(idxPtr);
-    boneIdxPtr && CanvasKit._free(boneIdxPtr);
-    boneWtPtr && CanvasKit._free(boneWtPtr);
-    return vertices;
-  }
-
-}(Module)); // When this file is loaded in, the high level object is "Module";
-
-// Intentionally added outside the scope to allow usage in canvas2d.js and other
-// pre-js files. These names are unlikely to cause emscripten collisions.
-function radiansToDegrees(rad) {
-  return (rad / Math.PI) * 180;
-}
-
-function degreesToRadians(deg) {
-  return (deg / 180) * Math.PI;
-}
-
diff --git a/experimental/canvaskit/karma.bench.conf.js b/experimental/canvaskit/karma.bench.conf.js
deleted file mode 100644
index f02aa1f..0000000
--- a/experimental/canvaskit/karma.bench.conf.js
+++ /dev/null
@@ -1,90 +0,0 @@
-const isDocker = require('is-docker')();
-
-module.exports = function(config) {
-  // Set the default values to be what are needed when testing the
-  // WebAssembly build locally.
-  let cfg = {
-    // frameworks to use
-    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
-    frameworks: ['jasmine'],
-
-    // list of files / patterns to load in the browser
-    files: [
-      { pattern: 'canvaskit/bin/canvaskit.wasm', included:false, served:true},
-      { pattern: 'perf/assets/*', included:false, served:true},
-      '../../modules/pathkit/perf/perfReporter.js',
-      'canvaskit/bin/canvaskit.js',
-      'perf/*.bench.js'
-    ],
-
-    proxies: {
-      '/canvaskit/': '/base/canvaskit/bin/',
-      '/assets/': '/base/perf/assets/'
-    },
-
-    // test results reporter to use
-    // possible values: 'dots', 'progress'
-    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
-    reporters: ['progress'],
-
-    // web server port
-    port: 4444,
-
-    // enable / disable colors in the output (reporters and logs)
-    colors: true,
-
-    // level of logging
-    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
-    logLevel: config.LOG_INFO,
-
-    // enable / disable watching file and executing tests whenever any file changes
-    autoWatch: true,
-
-    browserDisconnectTimeout: 15000,
-    browserNoActivityTimeout: 15000,
-
-    // start these browsers
-    browsers: ['Chrome'],
-
-    // Continuous Integration mode
-    // if true, Karma captures browsers, runs the tests and exits
-    singleRun: false,
-
-    // Concurrency level
-    // how many browser should be started simultaneous
-    concurrency: Infinity,
-  };
-
-  if (isDocker) {
-    // See https://hackernoon.com/running-karma-tests-with-headless-chrome-inside-docker-ae4aceb06ed3
-    cfg.browsers = ['ChromeHeadlessNoSandbox'],
-    cfg.customLaunchers = {
-        ChromeHeadlessNoSandbox: {
-            base: 'ChromeHeadless',
-            flags: [
-            // Without this flag, we see an error:
-            // Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permitted
-                '--no-sandbox'
-            ],
-        },
-    };
-  }
-
-  if (process.env.ASM_JS) {
-    console.log('asm.js is under test');
-    cfg.files = [
-      { pattern: 'npm-asmjs/bin/pathkit.js.mem', included:false, served:true},
-      'perf/perfReporter.js',
-      'npm-asmjs/bin/pathkit.js',
-      'perf/*.bench.js'
-    ];
-
-    cfg.proxies = {
-      '/pathkit/': '/base/npm-asmjs/bin/'
-    };
-  } else {
-    console.log('wasm is under test');
-  }
-
-  config.set(cfg);
-}
diff --git a/experimental/canvaskit/karma.conf.js b/experimental/canvaskit/karma.conf.js
deleted file mode 100644
index c324460..0000000
--- a/experimental/canvaskit/karma.conf.js
+++ /dev/null
@@ -1,74 +0,0 @@
-const isDocker = require('is-docker')();
-
-module.exports = function(config) {
-  // Set the default values to be what are needed when testing the
-  // WebAssembly build locally.
-  let cfg = {
-    // frameworks to use
-    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
-    frameworks: ['jasmine'],
-
-    // list of files / patterns to load in the browser
-    files: [
-      { pattern: 'canvaskit/bin/canvaskit.wasm', included:false, served:true},
-      { pattern: 'tests/assets/*', included:false, served:true},
-      '../../modules/pathkit/tests/testReporter.js',
-      'canvaskit/bin/canvaskit.js',
-      'tests/*.spec.js'
-    ],
-
-    proxies: {
-      '/assets/': '/base/tests/assets/',
-      '/canvaskit/': '/base/canvaskit/bin/',
-    },
-
-    // test results reporter to use
-    // possible values: 'dots', 'progress'
-    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
-    reporters: ['progress'],
-
-    // web server port
-    port: 4444,
-
-    // enable / disable colors in the output (reporters and logs)
-    colors: true,
-
-    // level of logging
-    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
-    logLevel: config.LOG_INFO,
-
-    // enable / disable watching file and executing tests whenever any file changes
-    autoWatch: true,
-
-    browserDisconnectTimeout: 15000,
-    browserNoActivityTimeout: 15000,
-
-    // start these browsers
-    browsers: ['Chrome'],
-
-    // Continuous Integration mode
-    // if true, Karma captures browsers, runs the tests and exits
-    singleRun: false,
-
-    // Concurrency level
-    // how many browser should be started simultaneous
-    concurrency: Infinity,
-  };
-
-  if (isDocker) {
-    // See https://hackernoon.com/running-karma-tests-with-headless-chrome-inside-docker-ae4aceb06ed3
-    cfg.browsers = ['ChromeHeadlessNoSandbox'],
-    cfg.customLaunchers = {
-        ChromeHeadlessNoSandbox: {
-            base: 'ChromeHeadless',
-            flags: [
-            // Without this flag, we see an error:
-            // Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permitted
-                '--no-sandbox'
-            ],
-        },
-    };
-  }
-
-  config.set(cfg);
-}
diff --git a/experimental/canvaskit/perf/animation.bench.js b/experimental/canvaskit/perf/animation.bench.js
deleted file mode 100644
index f3f463e..0000000
--- a/experimental/canvaskit/perf/animation.bench.js
+++ /dev/null
@@ -1,167 +0,0 @@
-// The increased timeout is especially needed with larger binaries
-// like in the debug/gpu build
-jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
-
-describe('CanvasKit\'s Animation', function() {
-    // Note, don't try to print the CanvasKit object - it can cause Karma/Jasmine to lock up.
-    var CanvasKit = null;
-    const LoadCanvasKit = new Promise(function(resolve, reject) {
-        if (CanvasKit) {
-            resolve();
-        } else {
-            CanvasKitInit({
-                locateFile: (file) => '/canvaskit/'+file,
-            }).ready().then((_CanvasKit) => {
-                CanvasKit = _CanvasKit;
-                resolve();
-            });
-        }
-    });
-
-    const LOTTIE_ANIMATIONS = ['lego_loader', 'drinks', 'confetti', 'onboarding'];
-
-    let container = document.createElement('div');
-    document.body.appendChild(container);
-
-
-    beforeEach(function() {
-        container.innerHTML = `
-            <canvas width=600 height=600 id=test></canvas>`;
-    });
-
-    afterEach(function() {
-        container.innerHTML = '';
-    });
-
-    function fetchAndText(url) {
-        return new Promise(function(resolve, reject) {
-            fetch(url).then((resp) => {
-                    resp.text().then((str) => {
-                        expect(str).toBeTruthy();
-                        resolve(str);
-                    });
-                }).catch(reject);
-        });
-    }
-
-    LOTTIE_ANIMATIONS.forEach((animStr) => {
-        let promises = [fetchAndText(`/assets/${animStr}.json`), LoadCanvasKit];
-
-        it(`animation loading for ${animStr}`, function(done) {
-            let jsonStr = '';
-            function setup(ctx) {
-                expect(jsonStr).toBeTruthy();
-            }
-
-            function test(ctx) {
-                const animation = CanvasKit.MakeAnimation(jsonStr);
-                animation.delete();
-            }
-
-            function teardown(ctx) {}
-
-            Promise.all(promises).then((responses) => {
-                // The result from the first promise, that is, the JSON string
-                // fetched by fetchAndText
-                jsonStr = responses[0];
-                benchmarkAndReport(`${animStr}_animation_load`, setup, test, teardown).then(() => {
-                    done();
-                }).catch(reportError(done));
-            });
-        });
-
-        it(`animation frames in order for ${animStr}`, function(done) {
-            let jsonStr = '';
-            function setup(ctx) {
-                expect(jsonStr).toBeTruthy();
-                ctx.animation = CanvasKit.MakeAnimation(jsonStr);
-                expect(ctx.animation).toBeTruthy();
-                ctx.timer = 0;
-            }
-
-            function test(ctx) {
-                ctx.animation.seek(ctx.timer);
-                ctx.timer += 0.01;
-                if (ctx.timer > 1.0) {
-                    ctx.timer = 0;
-                }
-            }
-
-            function teardown(ctx) {
-                ctx.animation.delete();
-            }
-
-            Promise.all(promises).then((responses) => {
-                // The result from the first promise, that is, the JSON string
-                // fetched by fetchAndText
-                jsonStr = responses[0];
-                benchmarkAndReport(`${animStr}_animation_in_order`, setup, test, teardown).then(() => {
-                    done();
-                }).catch(reportError(done));
-            });
-        });
-
-        it(`animation frames in random order for ${animStr}`, function(done) {
-            let jsonStr = '';
-            function setup(ctx) {
-                expect(jsonStr).toBeTruthy();
-                ctx.animation = CanvasKit.MakeAnimation(jsonStr);
-                expect(ctx.animation).toBeTruthy();
-            }
-
-            function test(ctx) {
-                ctx.animation.seek(Math.random());
-            }
-
-            function teardown(ctx) {
-                ctx.animation.delete();
-            }
-
-            Promise.all(promises).then((responses) => {
-                // The result from the first promise, that is, the JSON string
-                // fetched by fetchAndText
-                jsonStr = responses[0];
-                benchmarkAndReport(`${animStr}_animation_random_order`, setup, test, teardown).then(() => {
-                    done();
-                }).catch(reportError(done));
-            });
-        });
-
-        it(`renders to an HTML canvas ${animStr}`, function(done) {
-            let jsonStr = '';
-            function setup(ctx) {
-                expect(jsonStr).toBeTruthy();
-                ctx.animation = CanvasKit.MakeAnimation(jsonStr);
-                expect(ctx.animation).toBeTruthy();
-                ctx.animation.seek(0.5);
-                ctx.surface = CanvasKit.MakeCanvasSurface('test');
-                ctx.canvas = ctx.surface.getCanvas();
-                ctx.clear = CanvasKit.Color(255, 255, 255, 0.0); // transparent
-            }
-
-            function test(ctx) {
-                // This emulates what would need to be done do accurately
-                // draw one frame.
-                ctx.canvas.clear(ctx.clear);
-                ctx.animation.render(ctx.canvas);
-                ctx.surface.flush();
-            }
-
-            function teardown(ctx) {
-                ctx.animation.delete();
-                ctx.surface.dispose(); // ctx.canvas will also be cleaned up
-            }
-
-            Promise.all(promises).then((responses) => {
-                // The result from the first promise, that is, the JSON string
-                // fetched by fetchAndText
-                jsonStr = responses[0];
-                benchmarkAndReport(`${animStr}_animation_render_flush`, setup, test, teardown).then(() => {
-                    done();
-                }).catch(reportError(done));
-            });
-        });
-
-    });
-
-});
diff --git a/experimental/canvaskit/ready.js b/experimental/canvaskit/ready.js
deleted file mode 100644
index 66f2a0e..0000000
--- a/experimental/canvaskit/ready.js
+++ /dev/null
@@ -1,18 +0,0 @@
-// See https://github.com/kripken/emscripten/issues/5820#issuecomment-385722568
-// for context on why the .then() that comes with Module breaks things (e.g. infinite loops)
-// and why the below fixes it.
-Module['ready'] = function() {
-  return new Promise(function (resolve, reject) {
-    delete Module['then'];
-    Module['onAbort'] = reject;
-    if (runtimeInitialized) {
-      resolve(Module);
-    } else {
-      addOnPostRun(function() {
-        resolve(Module);
-      });
-    }
-  });
-}
-// TODO(kjlubick): Shut .then() entirely off in 0.4.0 by uncommenting below.
-// delete Module['then'];
\ No newline at end of file
diff --git a/experimental/canvaskit/skottie_bindings.cpp b/experimental/canvaskit/skottie_bindings.cpp
deleted file mode 100644
index 66be7e0..0000000
--- a/experimental/canvaskit/skottie_bindings.cpp
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkCanvas.h"
-#include "SkMakeUnique.h"
-#include "SkTypes.h"
-#include "Skottie.h"
-
-#include <string>
-
-#include <emscripten.h>
-#include <emscripten/bind.h>
-#include "WasmAliases.h"
-
-#if SK_INCLUDE_MANAGED_SKOTTIE
-#include "SkottieProperty.h"
-#include "SkottieUtils.h"
-#endif // SK_INCLUDE_MANAGED_SKOTTIE
-
-using namespace emscripten;
-
-#if SK_INCLUDE_MANAGED_SKOTTIE
-namespace {
-
-class ManagedAnimation final : public SkRefCnt {
-public:
-    static sk_sp<ManagedAnimation> Make(const std::string& json) {
-        auto mgr = skstd::make_unique<skottie_utils::CustomPropertyManager>();
-        auto animation = skottie::Animation::Builder()
-                            .setMarkerObserver(mgr->getMarkerObserver())
-                            .setPropertyObserver(mgr->getPropertyObserver())
-                            .make(json.c_str(), json.size());
-
-        return animation
-            ? sk_sp<ManagedAnimation>(new ManagedAnimation(std::move(animation), std::move(mgr)))
-            : nullptr;
-    }
-
-    // skottie::Animation API
-    void render(SkCanvas* canvas) const { fAnimation->render(canvas, nullptr); }
-    void render(SkCanvas* canvas, const SkRect& dst) const { fAnimation->render(canvas, &dst); }
-    void seek(SkScalar t) { fAnimation->seek(t); }
-    SkScalar duration() const { return fAnimation->duration(); }
-    const SkSize&      size() const { return fAnimation->size(); }
-    std::string version() const { return std::string(fAnimation->version().c_str()); }
-
-    // CustomPropertyManager API
-    JSArray getColorProps() const {
-        JSArray props = emscripten::val::array();
-
-        for (const auto& cp : fPropMgr->getColorProps()) {
-            JSObject prop = emscripten::val::object();
-            prop.set("key", cp);
-            prop.set("value", fPropMgr->getColor(cp));
-            props.call<void>("push", prop);
-        }
-
-        return props;
-    }
-
-    JSArray getOpacityProps() const {
-        JSArray props = emscripten::val::array();
-
-        for (const auto& op : fPropMgr->getOpacityProps()) {
-            JSObject prop = emscripten::val::object();
-            prop.set("key", op);
-            prop.set("value", fPropMgr->getOpacity(op));
-            props.call<void>("push", prop);
-        }
-
-        return props;
-    }
-
-    bool setColor(const std::string& key, JSColor c) {
-        return fPropMgr->setColor(key, static_cast<SkColor>(c));
-    }
-
-    bool setOpacity(const std::string& key, float o) {
-        return fPropMgr->setOpacity(key, o);
-    }
-
-    JSArray getMarkers() const {
-        JSArray markers = emscripten::val::array();
-        for (const auto& m : fPropMgr->markers()) {
-            JSObject marker = emscripten::val::object();
-            marker.set("name", m.name);
-            marker.set("t0"  , m.t0);
-            marker.set("t1"  , m.t1);
-            markers.call<void>("push", marker);
-        }
-        return markers;
-    }
-
-private:
-    ManagedAnimation(sk_sp<skottie::Animation> animation,
-                     std::unique_ptr<skottie_utils::CustomPropertyManager> propMgr)
-        : fAnimation(std::move(animation))
-        , fPropMgr(std::move(propMgr)) {}
-
-    sk_sp<skottie::Animation>                             fAnimation;
-    std::unique_ptr<skottie_utils::CustomPropertyManager> fPropMgr;
-};
-
-} // anonymous ns
-#endif // SK_INCLUDE_MANAGED_SKOTTIE
-
-EMSCRIPTEN_BINDINGS(Skottie) {
-    // Animation things (may eventually go in own library)
-    class_<skottie::Animation>("Animation")
-        .smart_ptr<sk_sp<skottie::Animation>>("sk_sp<Animation>")
-        .function("version", optional_override([](skottie::Animation& self)->std::string {
-            return std::string(self.version().c_str());
-        }))
-        .function("size", &skottie::Animation::size)
-        .function("duration", &skottie::Animation::duration)
-        .function("seek", &skottie::Animation::seek)
-        .function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas)->void {
-            self.render(canvas, nullptr);
-        }), allow_raw_pointers())
-        .function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas,
-                                                 const SkRect r)->void {
-            self.render(canvas, &r);
-        }), allow_raw_pointers());
-
-    function("MakeAnimation", optional_override([](std::string json)->sk_sp<skottie::Animation> {
-        return skottie::Animation::Make(json.c_str(), json.length());
-    }));
-    constant("skottie", true);
-
-#if SK_INCLUDE_MANAGED_SKOTTIE
-    class_<ManagedAnimation>("ManagedAnimation")
-        .smart_ptr<sk_sp<ManagedAnimation>>("sk_sp<ManagedAnimation>")
-        .function("version"   , &ManagedAnimation::version)
-        .function("size"      , &ManagedAnimation::size)
-        .function("duration"  , &ManagedAnimation::duration)
-        .function("seek"      , &ManagedAnimation::seek)
-        .function("render"    , select_overload<void(SkCanvas*) const>(&ManagedAnimation::render), allow_raw_pointers())
-        .function("render"    , select_overload<void(SkCanvas*, const SkRect&) const>
-                                    (&ManagedAnimation::render), allow_raw_pointers())
-        .function("setColor"  , &ManagedAnimation::setColor)
-        .function("setOpacity", &ManagedAnimation::setOpacity)
-        .function("getMarkers", &ManagedAnimation::getMarkers)
-        .function("getColorProps"  , &ManagedAnimation::getColorProps)
-        .function("getOpacityProps", &ManagedAnimation::getOpacityProps);
-
-    function("MakeManagedAnimation", &ManagedAnimation::Make);
-    constant("managed_skottie", true);
-#endif // SK_INCLUDE_MANAGED_SKOTTIE
-}
diff --git a/experimental/canvaskit/tests/canvas2d.spec.js b/experimental/canvaskit/tests/canvas2d.spec.js
deleted file mode 100644
index 7e6e43a..0000000
--- a/experimental/canvaskit/tests/canvas2d.spec.js
+++ /dev/null
@@ -1,893 +0,0 @@
-// The increased timeout is especially needed with larger binaries
-// like in the debug/gpu build
-jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
-
-describe('CanvasKit\'s Canvas 2d Behavior', function() {
-    // Note, don't try to print the CanvasKit object - it can cause Karma/Jasmine to lock up.
-    var CanvasKit = null;
-    const LoadCanvasKit = new Promise(function(resolve, reject) {
-        if (CanvasKit) {
-            resolve();
-        } else {
-            CanvasKitInit({
-                locateFile: (file) => '/canvaskit/'+file,
-            }).ready().then((_CanvasKit) => {
-                CanvasKit = _CanvasKit;
-                resolve();
-            });
-        }
-    });
-
-    let container = document.createElement('div');
-    document.body.appendChild(container);
-    const CANVAS_WIDTH = 600;
-    const CANVAS_HEIGHT = 600;
-
-    beforeEach(function() {
-        container.innerHTML = `
-            <canvas width=600 height=600 id=test></canvas>`;
-    });
-
-    afterEach(function() {
-        container.innerHTML = '';
-    });
-
-    describe('color strings', function() {
-        function hex(s) {
-            return parseInt(s, 16);
-        }
-
-        it('parses hex color strings', function(done) {
-            LoadCanvasKit.then(catchException(done, () => {
-                const parseColor = CanvasKit._testing.parseColor;
-                expect(parseColor('#FED')).toEqual(
-                    CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), 1));
-                expect(parseColor('#FEDC')).toEqual(
-                    CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), hex('CC')/255));
-                expect(parseColor('#fed')).toEqual(
-                    CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), 1));
-                expect(parseColor('#fedc')).toEqual(
-                    CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), hex('CC')/255));
-                done();
-            }));
-        });
-        it('parses rgba color strings', function(done) {
-            LoadCanvasKit.then(catchException(done, () => {
-                const parseColor = CanvasKit._testing.parseColor;
-                expect(parseColor('rgba(117, 33, 64, 0.75)')).toEqual(
-                    CanvasKit.Color(117, 33, 64, 0.75));
-                expect(parseColor('rgb(117, 33, 64, 0.75)')).toEqual(
-                    CanvasKit.Color(117, 33, 64, 0.75));
-                expect(parseColor('rgba(117,33,64)')).toEqual(
-                    CanvasKit.Color(117, 33, 64, 1.0));
-                expect(parseColor('rgb(117,33, 64)')).toEqual(
-                    CanvasKit.Color(117, 33, 64, 1.0));
-                done();
-            }));
-        });
-        it('parses named color strings', function(done) {
-            LoadCanvasKit.then(catchException(done, () => {
-                const parseColor = CanvasKit._testing.parseColor;
-                expect(parseColor('grey')).toEqual(
-                    CanvasKit.Color(128, 128, 128, 1.0));
-                expect(parseColor('blanchedalmond')).toEqual(
-                    CanvasKit.Color(255, 235, 205, 1.0));
-                expect(parseColor('transparent')).toEqual(
-                    CanvasKit.Color(0, 0, 0, 0));
-                done();
-            }));
-        });
-
-        it('properly produces color strings', function(done) {
-            LoadCanvasKit.then(catchException(done, () => {
-                const colorToString = CanvasKit._testing.colorToString;
-
-                expect(colorToString(CanvasKit.Color(102, 51, 153, 1.0))).toEqual('#663399');
-
-                expect(colorToString(CanvasKit.Color(255, 235, 205, 0.5))).toEqual(
-                                               'rgba(255, 235, 205, 0.50196078)');
-
-                done();
-            }));
-        });
-
-        it('can multiply colors by alpha', function(done) {
-            LoadCanvasKit.then(catchException(done, () => {
-                const multiplyByAlpha = CanvasKit.multiplyByAlpha;
-
-                const testCases = [
-                    {
-                        inColor:  CanvasKit.Color(102, 51, 153, 1.0),
-                        inAlpha:  1.0,
-                        outColor: CanvasKit.Color(102, 51, 153, 1.0),
-                    },
-                    {
-                        inColor:  CanvasKit.Color(102, 51, 153, 1.0),
-                        inAlpha:  0.8,
-                        outColor: CanvasKit.Color(102, 51, 153, 0.8),
-                    },
-                    {
-                        inColor:  CanvasKit.Color(102, 51, 153, 0.8),
-                        inAlpha:  0.7,
-                        outColor: CanvasKit.Color(102, 51, 153, 0.56),
-                    },
-                    {
-                        inColor:  CanvasKit.Color(102, 51, 153, 0.8),
-                        inAlpha:  1000,
-                        outColor: CanvasKit.Color(102, 51, 153, 1.0),
-                    },
-                ];
-
-                for (let tc of testCases) {
-                    // Print out the test case if the two don't match.
-                    expect(multiplyByAlpha(tc.inColor, tc.inAlpha))
-                          .toBe(tc.outColor, JSON.stringify(tc));
-                }
-
-                done();
-            }));
-        });
-    }); // end describe('color string parsing')
-
-    describe('fonts', function() {
-        it('can parse font sizes', function(done) {
-            LoadCanvasKit.then(catchException(done, () => {
-                const parseFontString = CanvasKit._testing.parseFontString;
-
-                const tests = [{
-                        'input': '10px monospace',
-                        'output': {
-                            'style': '',
-                            'variant': '',
-                            'weight': '',
-                            'sizePx': 10,
-                            'family': 'monospace',
-                        }
-                    },
-                    {
-                        'input': '15pt Arial',
-                        'output': {
-                            'style': '',
-                            'variant': '',
-                            'weight': '',
-                            'sizePx': 20,
-                            'family': 'Arial',
-                        }
-                    },
-                    {
-                        'input': '1.5in Arial, san-serif ',
-                        'output': {
-                            'style': '',
-                            'variant': '',
-                            'weight': '',
-                            'sizePx': 144,
-                            'family': 'Arial, san-serif',
-                        }
-                    },
-                    {
-                        'input': '1.5em SuperFont',
-                        'output': {
-                            'style': '',
-                            'variant': '',
-                            'weight': '',
-                            'sizePx': 24,
-                            'family': 'SuperFont',
-                        }
-                    },
-                ];
-
-                for (let i = 0; i < tests.length; i++) {
-                    expect(parseFontString(tests[i].input)).toEqual(tests[i].output);
-                }
-
-                done();
-            }));
-        });
-
-        it('can parse font attributes', function(done) {
-            LoadCanvasKit.then(catchException(done, () => {
-                const parseFontString = CanvasKit._testing.parseFontString;
-
-                const tests = [{
-                        'input': 'bold 10px monospace',
-                        'output': {
-                            'style': '',
-                            'variant': '',
-                            'weight': 'bold',
-                            'sizePx': 10,
-                            'family': 'monospace',
-                        }
-                    },
-                    {
-                        'input': 'italic bold 10px monospace',
-                        'output': {
-                            'style': 'italic',
-                            'variant': '',
-                            'weight': 'bold',
-                            'sizePx': 10,
-                            'family': 'monospace',
-                        }
-                    },
-                    {
-                        'input': 'italic small-caps bold 10px monospace',
-                        'output': {
-                            'style': 'italic',
-                            'variant': 'small-caps',
-                            'weight': 'bold',
-                            'sizePx': 10,
-                            'family': 'monospace',
-                        }
-                    },
-                    {
-                        'input': 'small-caps bold 10px monospace',
-                        'output': {
-                            'style': '',
-                            'variant': 'small-caps',
-                            'weight': 'bold',
-                            'sizePx': 10,
-                            'family': 'monospace',
-                        }
-                    },
-                    {
-                        'input': 'italic 10px monospace',
-                        'output': {
-                            'style': 'italic',
-                            'variant': '',
-                            'weight': '',
-                            'sizePx': 10,
-                            'family': 'monospace',
-                        }
-                    },
-                    {
-                        'input': 'small-caps 10px monospace',
-                        'output': {
-                            'style': '',
-                            'variant': 'small-caps',
-                            'weight': '',
-                            'sizePx': 10,
-                            'family': 'monospace',
-                        }
-                    },
-                    {
-                        'input': 'normal bold 10px monospace',
-                        'output': {
-                            'style': 'normal',
-                            'variant': '',
-                            'weight': 'bold',
-                            'sizePx': 10,
-                            'family': 'monospace',
-                        }
-                    },
-                ];
-
-                for (let i = 0; i < tests.length; i++) {
-                    expect(parseFontString(tests[i].input)).toEqual(tests[i].output);
-                }
-
-                done();
-            }));
-        });
-    });
-
-    function multipleCanvasTest(testname, done, test) {
-        const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
-        skcanvas._config = 'software_canvas';
-        const realCanvas = document.getElementById('test');
-        realCanvas._config = 'html_canvas';
-        realCanvas.width = CANVAS_WIDTH;
-        realCanvas.height = CANVAS_HEIGHT;
-
-        let promises = [];
-
-        for (let canvas of [skcanvas, realCanvas]) {
-            test(canvas);
-            // canvas has .toDataURL (even though skcanvas is not a real Canvas)
-            // so this will work.
-            promises.push(reportCanvas(canvas, testname, canvas._config));
-        }
-        Promise.all(promises).then(() => {
-            skcanvas.dispose();
-            done();
-        }).catch(reportError(done));
-    }
-
-    describe('CanvasContext2D API', function() {
-        it('supports all the line types', function(done) {
-            LoadCanvasKit.then(catchException(done, () => {
-                multipleCanvasTest('all_line_drawing_operations', done, (canvas) => {
-                    let ctx = canvas.getContext('2d');
-                    ctx.scale(3.0, 3.0);
-                    ctx.moveTo(20, 5);
-                    ctx.lineTo(30, 20);
-                    ctx.lineTo(40, 10);
-                    ctx.lineTo(50, 20);
-                    ctx.lineTo(60, 0);
-                    ctx.lineTo(20, 5);
-
-                    ctx.moveTo(20, 80);
-                    ctx.bezierCurveTo(90, 10, 160, 150, 190, 10);
-
-                    ctx.moveTo(36, 148);
-                    ctx.quadraticCurveTo(66, 188, 120, 136);
-                    ctx.lineTo(36, 148);
-
-                    ctx.rect(5, 170, 20, 25);
-
-                    ctx.moveTo(150, 180);
-                    ctx.arcTo(150, 100, 50, 200, 20);
-                    ctx.lineTo(160, 160);
-
-                    ctx.moveTo(20, 120);
-                    ctx.arc(20, 120, 18, 0, 1.75 * Math.PI);
-                    ctx.lineTo(20, 120);
-
-                    ctx.moveTo(150, 5);
-                    ctx.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI)
-
-                    ctx.lineWidth = 2;
-                    ctx.stroke();
-
-                    // Test edgecases and draw direction
-                    ctx.beginPath();
-                    ctx.arc(50, 100, 10, Math.PI, -Math.PI/2);
-                    ctx.stroke();
-                    ctx.beginPath();
-                    ctx.arc(75, 100, 10, Math.PI, -Math.PI/2, true);
-                    ctx.stroke();
-                    ctx.beginPath();
-                    ctx.arc(100, 100, 10, Math.PI, 100.1 * Math.PI, true);
-                    ctx.stroke();
-                    ctx.beginPath();
-                    ctx.arc(125, 100, 10, Math.PI, 100.1 * Math.PI, false);
-                    ctx.stroke();
-                    ctx.beginPath();
-                    ctx.ellipse(155, 100, 10, 15, Math.PI/8, 100.1 * Math.PI, Math.PI, true);
-                    ctx.stroke();
-                    ctx.beginPath();
-                    ctx.ellipse(180, 100, 10, 15, Math.PI/8, Math.PI, 100.1 * Math.PI, true);
-                    ctx.stroke();
-                });
-            }));
-        });
-
-        it('handles all the transforms as specified', function(done) {
-            LoadCanvasKit.then(catchException(done, () => {
-                multipleCanvasTest('all_matrix_operations', done, (canvas) => {
-                    let ctx = canvas.getContext('2d');
-                    ctx.rect(10, 10, 20, 20);
-
-                    ctx.scale(2.0, 4.0);
-                    ctx.rect(30, 10, 20, 20);
-                    ctx.resetTransform();
-
-                    ctx.rotate(Math.PI / 3);
-                    ctx.rect(50, 10, 20, 20);
-                    ctx.resetTransform();
-
-                    ctx.translate(30, -2);
-                    ctx.rect(70, 10, 20, 20);
-                    ctx.resetTransform();
-
-                    ctx.translate(60, 0);
-                    ctx.rotate(Math.PI / 6);
-                    ctx.transform(1.5, 0, 0, 0.5, 0, 0, 0); // effectively scale
-                    ctx.rect(90, 10, 20, 20);
-                    ctx.resetTransform();
-
-                    ctx.save();
-                    ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
-                    ctx.rect(110, 10, 20, 20);
-                    ctx.lineTo(110, 0);
-                    ctx.restore();
-                    ctx.lineTo(220, 120);
-
-                    ctx.scale(3.0, 3.0);
-                    ctx.font = '6pt Noto Mono';
-                    ctx.fillText('This text should be huge', 10, 80);
-                    ctx.resetTransform();
-
-                    ctx.strokeStyle = 'black';
-                    ctx.lineWidth = 2;
-                    ctx.stroke();
-
-                    ctx.beginPath();
-                    ctx.moveTo(250, 30);
-                    ctx.lineTo(250, 80);
-                    ctx.scale(3.0, 3.0);
-                    ctx.lineTo(280/3, 90/3);
-                    ctx.closePath();
-                    ctx.strokeStyle = 'black';
-                    ctx.lineWidth = 5;
-                    ctx.stroke();
-                });
-            }));
-        });
-
-        it('properly saves and restores states, even when drawing shadows', function(done) {
-            LoadCanvasKit.then(catchException(done, () => {
-                multipleCanvasTest('shadows_and_save_restore', done, (canvas) => {
-                    let ctx = canvas.getContext('2d');
-                    ctx.strokeStyle = '#000';
-                    ctx.fillStyle = '#CCC';
-                    ctx.shadowColor = 'rebeccapurple';
-                    ctx.shadowBlur = 1;
-                    ctx.shadowOffsetX = 3;
-                    ctx.shadowOffsetY = -8;
-                    ctx.rect(10, 10, 30, 30);
-
-                    ctx.save();
-                    ctx.strokeStyle = '#C00';
-                    ctx.fillStyle = '#00C';
-                    ctx.shadowBlur = 0;
-                    ctx.shadowColor = 'transparent';
-
-                    ctx.stroke();
-
-                    ctx.restore();
-                    ctx.fill();
-
-                    ctx.beginPath();
-                    ctx.moveTo(36, 148);
-                    ctx.quadraticCurveTo(66, 188, 120, 136);
-                    ctx.closePath();
-                    ctx.stroke();
-
-                    ctx.beginPath();
-                    ctx.shadowColor = '#993366AA';
-                    ctx.shadowOffsetX = 8;
-                    ctx.shadowBlur = 5;
-                    ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
-                    ctx.rect(110, 10, 20, 20);
-                    ctx.lineTo(110, 0);
-                    ctx.resetTransform();
-                    ctx.lineTo(220, 120);
-                    ctx.stroke();
-
-                    ctx.fillStyle = 'green';
-                    ctx.font = '16pt Noto Mono';
-                    ctx.fillText('This should be shadowed', 20, 80);
-
-                    ctx.beginPath();
-                    ctx.lineWidth = 6;
-                    ctx.ellipse(10, 290, 30, 30, 0, 0, Math.PI * 2);
-                    ctx.scale(2, 1);
-                    ctx.moveTo(10, 290)
-                    ctx.ellipse(10, 290, 30, 60, 0, 0, Math.PI * 2);
-                    ctx.resetTransform();
-                    ctx.shadowColor = '#993366AA';
-                    ctx.scale(3, 1);
-                    ctx.moveTo(10, 290)
-                    ctx.ellipse(10, 290, 30, 90, 0, 0, Math.PI * 2);
-                    ctx.stroke();
-                });
-            }));
-        });
-
-        it('fills/strokes rects and supports some global settings', function(done) {
-            LoadCanvasKit.then(catchException(done, () => {
-                multipleCanvasTest('global_dashed_rects', done, (canvas) => {
-                    let ctx = canvas.getContext('2d');
-                    ctx.scale(1.1, 1.1);
-                    ctx.translate(10, 10);
-                    // Shouldn't impact the fillRect calls
-                    ctx.setLineDash([5, 3]);
-
-                    ctx.fillStyle = 'rgba(200, 0, 100, 0.81)';
-                    ctx.fillRect(20, 30, 100, 100);
-
-                    ctx.globalAlpha = 0.81;
-                    ctx.fillStyle = 'rgba(200, 0, 100, 1.0)';
-                    ctx.fillRect(120, 30, 100, 100);
-                    // This shouldn't do anything
-                    ctx.globalAlpha = 0.1;
-
-                    ctx.fillStyle = 'rgba(200, 0, 100, 0.9)';
-                    ctx.globalAlpha = 0.9;
-                    // Intentional no-op to check ordering
-                    ctx.clearRect(220, 30, 100, 100);
-                    ctx.fillRect(220, 30, 100, 100);
-
-                    ctx.fillRect(320, 30, 100, 100);
-                    ctx.clearRect(330, 40, 80, 80);
-
-                    ctx.strokeStyle = 'blue';
-                    ctx.lineWidth = 3;
-                    ctx.setLineDash([5, 3]);
-                    ctx.strokeRect(20, 150, 100, 100);
-                    ctx.setLineDash([50, 30]);
-                    ctx.strokeRect(125, 150, 100, 100);
-                    ctx.lineDashOffset = 25;
-                    ctx.strokeRect(230, 150, 100, 100);
-                    ctx.setLineDash([2, 5, 9]);
-                    ctx.strokeRect(335, 150, 100, 100);
-
-                    ctx.setLineDash([5, 2]);
-                    ctx.moveTo(336, 400);
-                    ctx.quadraticCurveTo(366, 488, 120, 450);
-                    ctx.lineTo(300, 400);
-                    ctx.stroke();
-
-                    ctx.font = '36pt Noto Mono';
-                    ctx.strokeText('Dashed', 20, 350);
-                    ctx.fillText('Not Dashed', 20, 400);
-                });
-            }));
-        });
-
-        it('supports gradients, which respect clip/save/restore', function(done) {
-            LoadCanvasKit.then(catchException(done, () => {
-                multipleCanvasTest('gradients_clip', done, (canvas) => {
-                    let ctx = canvas.getContext('2d');
-
-                    var rgradient = ctx.createRadialGradient(200, 300, 10, 100, 100, 300);
-
-                    rgradient.addColorStop(0, 'red');
-                    rgradient.addColorStop(.7, 'white');
-                    rgradient.addColorStop(1, 'blue');
-
-                    ctx.fillStyle = rgradient;
-                    ctx.globalAlpha = 0.7;
-                    ctx.fillRect(0,0,600,600);
-                    ctx.globalAlpha = 0.95;
-
-                    ctx.beginPath();
-                    ctx.arc(300, 100, 90, 0, Math.PI*1.66);
-                    ctx.closePath();
-                    ctx.strokeStyle = 'yellow';
-                    ctx.lineWidth = 5;
-                    ctx.stroke();
-                    ctx.save();
-                    ctx.clip();
-
-                    var lgradient = ctx.createLinearGradient(200, 20, 420, 40);
-
-                    lgradient.addColorStop(0, 'green');
-                    lgradient.addColorStop(.5, 'cyan');
-                    lgradient.addColorStop(1, 'orange');
-
-                    ctx.fillStyle = lgradient;
-
-                    ctx.fillRect(200, 30, 200, 300);
-
-                    ctx.restore();
-                    ctx.fillRect(550, 550, 40, 40);
-                });
-            }));
-        });
-
-        it('can draw png images', function(done) {
-            let skImageData = null;
-            let htmlImage = null;
-            let skPromise = fetch('/assets/mandrill_512.png')
-                .then((response) => response.arrayBuffer())
-                .then((buffer) => {
-                    skImageData = buffer;
-
-                });
-            let realPromise = fetch('/assets/mandrill_512.png')
-                .then((response) => response.blob())
-                .then((blob) => createImageBitmap(blob))
-                .then((bitmap) => {
-                    htmlImage = bitmap;
-                });
-            LoadCanvasKit.then(catchException(done, () => {
-                Promise.all([realPromise, skPromise]).then(() => {
-                    multipleCanvasTest('draw_image', done, (canvas) => {
-                        let ctx = canvas.getContext('2d');
-                        let img = htmlImage;
-                        if (canvas._config == 'software_canvas') {
-                            img = canvas.decodeImage(skImageData);
-                        }
-                        ctx.drawImage(img, 30, -200);
-
-                        ctx.globalAlpha = 0.7
-                        ctx.rotate(.1);
-                        ctx.imageSmoothingQuality = 'medium';
-                        ctx.drawImage(img, 200, 350, 150, 100);
-                        ctx.rotate(-.2);
-                        ctx.imageSmoothingEnabled = false;
-                        ctx.drawImage(img, 100, 150, 400, 350, 10, 400, 150, 100);
-                    });
-                });
-            }));
-        });
-
-        it('can get and put pixels', function(done) {
-            LoadCanvasKit.then(catchException(done, () => {
-                multipleCanvasTest('get_put_imagedata', done, (canvas) => {
-                    let ctx = canvas.getContext('2d');
-                    // Make a gradient so we see if the pixels copying worked
-                    let grad = ctx.createLinearGradient(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
-                    grad.addColorStop(0, 'yellow');
-                    grad.addColorStop(1, 'red');
-                    ctx.fillStyle = grad;
-                    ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
-
-                    let iData = ctx.getImageData(400, 100, 200, 150);
-                    expect(iData.width).toBe(200);
-                    expect(iData.height).toBe(150);
-                    expect(iData.data.byteLength).toBe(200*150*4);
-                    ctx.putImageData(iData, 10, 10);
-                    ctx.putImageData(iData, 350, 350, 100, 75, 45, 40);
-                    ctx.strokeRect(350, 350, 200, 150);
-
-                    let box = ctx.createImageData(20, 40);
-                    ctx.putImageData(box, 10, 300);
-                    let biggerBox = ctx.createImageData(iData);
-                    ctx.putImageData(biggerBox, 10, 350);
-                    expect(biggerBox.width).toBe(iData.width);
-                    expect(biggerBox.height).toBe(iData.height);
-                });
-            }));
-        });
-
-        it('can make patterns', function(done) {
-            let skImageData = null;
-            let htmlImage = null;
-            let skPromise = fetch('/assets/mandrill_512.png')
-                .then((response) => response.arrayBuffer())
-                .then((buffer) => {
-                    skImageData = buffer;
-
-                });
-            let realPromise = fetch('/assets/mandrill_512.png')
-                .then((response) => response.blob())
-                .then((blob) => createImageBitmap(blob))
-                .then((bitmap) => {
-                    htmlImage = bitmap;
-                });
-            LoadCanvasKit.then(catchException(done, () => {
-                Promise.all([realPromise, skPromise]).then(() => {
-                    multipleCanvasTest('draw_patterns', done, (canvas) => {
-                        let ctx = canvas.getContext('2d');
-                        let img = htmlImage;
-                        if (canvas._config == 'software_canvas') {
-                            img = canvas.decodeImage(skImageData);
-                        }
-                        ctx.fillStyle = '#EEE';
-                        ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
-                        ctx.lineWidth = 20;
-                        ctx.scale(0.2, 0.4);
-
-                        let pattern = ctx.createPattern(img, 'repeat');
-                        ctx.fillStyle = pattern;
-                        ctx.fillRect(0, 0, 1500, 750);
-
-                        pattern = ctx.createPattern(img, 'repeat-x');
-                        ctx.fillStyle = pattern;
-                        ctx.fillRect(1500, 0, 3000, 750);
-
-                        ctx.globalAlpha = 0.7
-                        pattern = ctx.createPattern(img, 'repeat-y');
-                        ctx.fillStyle = pattern;
-                        ctx.fillRect(0, 750, 1500, 1500);
-                        ctx.strokeRect(0, 750, 1500, 1500);
-
-                        pattern = ctx.createPattern(img, 'no-repeat');
-                        ctx.fillStyle = pattern;
-                        pattern.setTransform({a: 1, b: -.1, c:.1, d: 0.5, e: 1800, f:800});
-                        ctx.fillRect(0, 0, 3000, 1500);
-                    });
-                });
-            }));
-        });
-
-        it('can get and put pixels', function(done) {
-            LoadCanvasKit.then(catchException(done, () => {
-                function drawPoint(ctx, x, y, color) {
-                    ctx.fillStyle = color;
-                    ctx.fillRect(x, y, 1, 1);
-                }
-                const IN = 'purple';
-                const OUT = 'orange';
-                const SCALE = 8;
-
-                // Check to see if these points are in or out on each of the
-                // test configurations.
-                const pts = [[3, 3], [4, 4], [5, 5], [10, 10], [8, 10], [6, 10],
-                             [6.5, 9], [15, 10], [17, 10], [17, 11], [24, 24],
-                             [25, 25], [26, 26], [27, 27]];
-                const tests = [
-                    {
-                        xOffset: 0,
-                        yOffset: 0,
-                        fillType: 'nonzero',
-                        strokeWidth: 0,
-                        testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'nonzero'),
-                    },
-                    {
-                        xOffset: 30,
-                        yOffset: 0,
-                        fillType: 'evenodd',
-                        strokeWidth: 0,
-                        testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'evenodd'),
-                    },
-                    {
-                        xOffset: 0,
-                        yOffset: 30,
-                        fillType: null,
-                        strokeWidth: 1,
-                        testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
-                    },
-                    {
-                        xOffset: 30,
-                        yOffset: 30,
-                        fillType: null,
-                        strokeWidth: 2,
-                        testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
-                    },
-                ];
-                multipleCanvasTest('points_in_path_stroke', done, (canvas) => {
-                    let ctx = canvas.getContext('2d');
-                    ctx.font = '20px Noto Mono';
-                    // Draw some visual aids
-                    ctx.fillText('path-nonzero', 60, 30);
-                    ctx.fillText('path-evenodd', 300, 30);
-                    ctx.fillText('stroke-1px-wide', 60, 260);
-                    ctx.fillText('stroke-2px-wide', 300, 260);
-                    ctx.fillText('purple is IN, orange is OUT', 20, 560);
-
-                    // Scale up to make single pixels easier to see
-                    ctx.scale(SCALE, SCALE);
-                    for (let test of tests) {
-                        ctx.beginPath();
-                        let xOffset = test.xOffset;
-                        let yOffset = test.yOffset;
-
-                        ctx.fillStyle = '#AAA';
-                        ctx.lineWidth = test.strokeWidth;
-                        ctx.rect(5+xOffset, 5+yOffset, 20, 20);
-                        ctx.arc(15+xOffset, 15+yOffset, 8, 0, Math.PI*2, false);
-                        if (test.fillType) {
-                            ctx.fill(test.fillType);
-                        } else {
-                            ctx.stroke();
-                        }
-
-                        for (let pt of pts) {
-                            let [x, y] = pt;
-                            x += xOffset;
-                            y += yOffset;
-                            // naively apply transform when querying because the points queried
-                            // ignore the CTM.
-                            if (test.testFn(ctx, x, y)) {
-                              drawPoint(ctx, x, y, IN);
-                            } else {
-                              drawPoint(ctx, x, y, OUT);
-                            }
-                        }
-                    }
-                });
-            }));
-        });
-
-        it('can load custom fonts', function(done) {
-            let realFontLoaded = new FontFace('BungeeNonSystem', 'url(/assets/Bungee-Regular.ttf)', {
-                'family': 'BungeeNonSystem', //Make sure the canvas does not use the system font
-                'style': 'normal',
-                'weight': '400',
-            }).load().then((font) => {
-                document.fonts.add(font);
-            });
-
-            let fontBuffer = null;
-
-            let skFontLoaded = fetch('/assets/Bungee-Regular.ttf').then(
-                (response) => response.arrayBuffer()).then(
-                (buffer) => {
-                    fontBuffer = buffer;
-                });
-
-            LoadCanvasKit.then(catchException(done, () => {
-                Promise.all([realFontLoaded, skFontLoaded]).then(() => {
-                    multipleCanvasTest('custom_font', done, (canvas) => {
-                        if (canvas.loadFont) {
-                            canvas.loadFont(fontBuffer, {
-                                'family': 'BungeeNonSystem',
-                                'style': 'normal',
-                                'weight': '400',
-                            });
-                        }
-                        let ctx = canvas.getContext('2d');
-
-                        ctx.font = '20px monospace';
-                        ctx.fillText('20 px monospace', 10, 30);
-
-                        ctx.font = '2.0em BungeeNonSystem';
-                        ctx.fillText('2.0em Bungee filled', 10, 80);
-                        ctx.strokeText('2.0em Bungee stroked', 10, 130);
-
-                        ctx.font = '40pt monospace';
-                        ctx.strokeText('40pt monospace', 10, 200);
-
-                        // bold wasn't defined, so should fallback to just the 400 weight
-                        ctx.font = 'bold 45px BungeeNonSystem';
-                        ctx.fillText('45px Bungee filled', 10, 260);
-                    });
-                });
-            }));
-        });
-        it('can read default properties', function(done) {
-            LoadCanvasKit.then(catchException(done, () => {
-                const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
-                const realCanvas = document.getElementById('test');
-                realCanvas.width = CANVAS_WIDTH;
-                realCanvas.height = CANVAS_HEIGHT;
-
-                const skcontext = skcanvas.getContext('2d');
-                const realContext = realCanvas.getContext('2d');
-                // The skia canvas only comes with a monospace font by default
-                // Set the html canvas to be monospace too.
-                realContext.font = '10px monospace';
-
-                const toTest = ['font', 'lineWidth', 'strokeStyle', 'lineCap',
-                                'lineJoin', 'miterLimit', 'shadowOffsetY',
-                                'shadowBlur', 'shadowColor', 'shadowOffsetX',
-                                'globalAlpha', 'globalCompositeOperation',
-                                'lineDashOffset', 'imageSmoothingEnabled',
-                                'imageFilterQuality'];
-
-                // Compare all the default values of the properties of skcanvas
-                // to the default values on the properties of a real canvas.
-                for(let attr of toTest) {
-                    expect(skcontext[attr]).toBe(realContext[attr], attr);
-                }
-
-                skcanvas.dispose();
-                done();
-            }));
-        });
-    }); // end describe('CanvasContext2D API')
-
-    describe('Path2D API', function() {
-        it('supports all the line types', function(done) {
-            LoadCanvasKit.then(catchException(done, () => {
-                multipleCanvasTest('path2d_line_drawing_operations', done, (canvas) => {
-                    let ctx = canvas.getContext('2d');
-                    var clock;
-                    var path;
-                    if (canvas.makePath2D) {
-                        clock = canvas.makePath2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
-                        path = canvas.makePath2D();
-                    } else {
-                        clock = new Path2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z')
-                        path = new Path2D();
-                    }
-                    path.moveTo(20, 5);
-                    path.lineTo(30, 20);
-                    path.lineTo(40, 10);
-                    path.lineTo(50, 20);
-                    path.lineTo(60, 0);
-                    path.lineTo(20, 5);
-
-                    path.moveTo(20, 80);
-                    path.bezierCurveTo(90, 10, 160, 150, 190, 10);
-
-                    path.moveTo(36, 148);
-                    path.quadraticCurveTo(66, 188, 120, 136);
-                    path.lineTo(36, 148);
-
-                    path.rect(5, 170, 20, 25);
-
-                    path.moveTo(150, 180);
-                    path.arcTo(150, 100, 50, 200, 20);
-                    path.lineTo(160, 160);
-
-                    path.moveTo(20, 120);
-                    path.arc(20, 120, 18, 0, 1.75 * Math.PI);
-                    path.lineTo(20, 120);
-
-                    path.moveTo(150, 5);
-                    path.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI)
-
-                    ctx.lineWidth = 2;
-                    ctx.scale(3.0, 3.0);
-                    ctx.stroke(path);
-                    ctx.stroke(clock);
-                });
-            }));
-        });
-    }); // end describe('Path2D API')
-
-
-});
diff --git a/experimental/canvaskit/tests/path.spec.js b/experimental/canvaskit/tests/path.spec.js
deleted file mode 100644
index 4a3e455..0000000
--- a/experimental/canvaskit/tests/path.spec.js
+++ /dev/null
@@ -1,240 +0,0 @@
-// The increased timeout is especially needed with larger binaries
-// like in the debug/gpu build
-jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
-
-describe('CanvasKit\'s Path Behavior', function() {
-    // Note, don't try to print the CanvasKit object - it can cause Karma/Jasmine to lock up.
-    var CanvasKit = null;
-    const LoadCanvasKit = new Promise(function(resolve, reject) {
-        if (CanvasKit) {
-            resolve();
-        } else {
-            CanvasKitInit({
-                locateFile: (file) => '/canvaskit/'+file,
-            }).ready().then((_CanvasKit) => {
-                CanvasKit = _CanvasKit;
-                resolve();
-            });
-        }
-    });
-
-    let container = document.createElement('div');
-    document.body.appendChild(container);
-    const CANVAS_WIDTH = 600;
-    const CANVAS_HEIGHT = 600;
-
-    beforeEach(function() {
-        container.innerHTML = `
-            <canvas width=600 height=600 id=test></canvas>
-            <canvas width=600 height=600 id=report></canvas>`;
-    });
-
-    afterEach(function() {
-        container.innerHTML = '';
-    });
-
-    function reportSurface(surface, testname, done) {
-        // In docker, the webgl canvas is blank, but the surface has the pixel
-        // data. So, we copy it out and draw it to a normal canvas to take a picture.
-        // To be consistent across CPU and GPU, we just do it for all configurations
-        // (even though the CPU canvas shows up after flush just fine).
-        let pixels = surface.getCanvas().readPixels(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
-        pixels = new Uint8ClampedArray(pixels.buffer);
-        var imageData = new ImageData(pixels, CANVAS_WIDTH, CANVAS_HEIGHT);
-
-        let reportingCanvas =  document.getElementById('report');
-        reportingCanvas.getContext('2d').putImageData(imageData, 0, 0);
-        reportCanvas(reportingCanvas, testname).then(() => {
-            done();
-        }).catch(reportError(done));
-    }
-
-    it('can draw a path', function(done) {
-        LoadCanvasKit.then(catchException(done, () => {
-            // This is taken from example.html
-            const surface = CanvasKit.MakeCanvasSurface('test');
-            expect(surface).toBeTruthy('Could not make surface')
-            if (!surface) {
-                done();
-                return;
-            }
-            const canvas = surface.getCanvas();
-            const paint = new CanvasKit.SkPaint();
-            paint.setStrokeWidth(1.0);
-            paint.setAntiAlias(true);
-            paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
-            paint.setStyle(CanvasKit.PaintStyle.Stroke);
-
-            const path = new CanvasKit.SkPath();
-            path.moveTo(20, 5);
-            path.lineTo(30, 20);
-            path.lineTo(40, 10);
-            path.lineTo(50, 20);
-            path.lineTo(60, 0);
-            path.lineTo(20, 5);
-
-            path.moveTo(20, 80);
-            path.cubicTo(90, 10, 160, 150, 190, 10);
-
-            path.moveTo(36, 148);
-            path.quadTo(66, 188, 120, 136);
-            path.lineTo(36, 148);
-
-            path.moveTo(150, 180);
-            path.arcTo(150, 100, 50, 200, 20);
-            path.lineTo(160, 160);
-
-            path.moveTo(20, 120);
-            path.lineTo(20, 120);
-
-            path.transform([2, 0, 0,
-                            0, 2, 0,
-                            0, 0, 1 ])
-
-            canvas.drawPath(path, paint);
-
-            let rrect = new CanvasKit.SkPath()
-                               .addRoundRect(100, 10, 140, 62,
-                                             10, 4, true);
-
-            canvas.drawPath(rrect, paint);
-            rrect.delete();
-
-            surface.flush();
-
-            path.delete();
-            paint.delete();
-
-            reportSurface(surface, 'path_api_example', done);
-        }));
-        // See PathKit for more tests, since they share implementation
-    });
-
-    it('can draw directly to a canvas', function(done) {
-        LoadCanvasKit.then(catchException(done, () => {
-            // This is taken from example.html
-            const surface = CanvasKit.MakeCanvasSurface('test');
-            expect(surface).toBeTruthy('Could not make surface')
-            if (!surface) {
-                done();
-                return;
-            }
-            const canvas = surface.getCanvas();
-            const paint = new CanvasKit.SkPaint();
-            paint.setStrokeWidth(2.0);
-            paint.setAntiAlias(true);
-            paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
-            paint.setStyle(CanvasKit.PaintStyle.Stroke);
-
-            canvas.drawLine(3, 10, 30, 15, paint);
-            canvas.drawRoundRect(CanvasKit.LTRBRect(5, 35, 45, 80), 15, 10, paint);
-
-            canvas.drawOval(CanvasKit.LTRBRect(5, 35, 45, 80), paint);
-
-            canvas.drawArc(CanvasKit.LTRBRect(55, 35, 95, 80), 15, 270, true, paint);
-
-            const font = new CanvasKit.SkFont(null, 20);
-            canvas.drawText('this is ascii text', 5, 100, font, paint);
-
-            const blob = CanvasKit.SkTextBlob.MakeFromText('Unicode chars 💩 é É ص', font);
-            canvas.drawTextBlob(blob, 5, 130, paint);
-
-            surface.flush();
-            font.delete();
-            blob.delete();
-            paint.delete();
-
-            reportSurface(surface, 'canvas_api_example', done);
-        }));
-        // See canvas2d for more API tests
-    });
-
-    function starPath(CanvasKit, X=128, Y=128, R=116) {
-        let p = new CanvasKit.SkPath();
-        p.moveTo(X + R, Y);
-        for (let i = 1; i < 8; i++) {
-          let a = 2.6927937 * i;
-          p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
-        }
-        return p;
-      }
-
-    it('can apply an effect and draw text', function(done) {
-        LoadCanvasKit.then(catchException(done, () => {
-            const surface = CanvasKit.MakeCanvasSurface('test');
-            expect(surface).toBeTruthy('Could not make surface')
-            if (!surface) {
-                done();
-                return;
-            }
-            const canvas = surface.getCanvas();
-            const path = starPath(CanvasKit);
-
-            const paint = new CanvasKit.SkPaint();
-
-            const textPaint = new CanvasKit.SkPaint();
-            textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
-            textPaint.setAntiAlias(true);
-
-            const textFont = new CanvasKit.SkFont(null, 30);
-
-            const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], 1);
-
-            paint.setPathEffect(dpe);
-            paint.setStyle(CanvasKit.PaintStyle.Stroke);
-            paint.setStrokeWidth(5.0);
-            paint.setAntiAlias(true);
-            paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
-
-            canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
-
-            canvas.drawPath(path, paint);
-            canvas.drawText('This is text', 10, 280, textFont, textPaint);
-            surface.flush();
-            dpe.delete();
-            path.delete();
-
-            reportSurface(surface, 'effect_and_text_example', done);
-        }));
-    });
-
-    it('can create a path from an SVG string', function(done) {
-        LoadCanvasKit.then(catchException(done, () => {
-            //.This is a parallelagram from
-            // https://upload.wikimedia.org/wikipedia/commons/e/e7/Simple_parallelogram.svg
-            let path = CanvasKit.MakePathFromSVGString('M 205,5 L 795,5 L 595,295 L 5,295 L 205,5 z');
-
-            let cmds = path.toCmds();
-            expect(cmds).toBeTruthy();
-            // 1 move, 4 lines, 1 close
-            // each element in cmds is an array, with index 0 being the verb, and the rest being args
-            expect(cmds.length).toBe(6);
-            expect(cmds).toEqual([[CanvasKit.MOVE_VERB, 205, 5],
-                                  [CanvasKit.LINE_VERB, 795, 5],
-                                  [CanvasKit.LINE_VERB, 595, 295],
-                                  [CanvasKit.LINE_VERB, 5, 295],
-                                  [CanvasKit.LINE_VERB, 205, 5],
-                                  [CanvasKit.CLOSE_VERB]]);
-            path.delete();
-            done();
-        }));
-    });
-
-     it('can create an SVG string from a path', function(done) {
-        LoadCanvasKit.then(catchException(done, () => {
-            let cmds = [[CanvasKit.MOVE_VERB, 205, 5],
-                       [CanvasKit.LINE_VERB, 795, 5],
-                       [CanvasKit.LINE_VERB, 595, 295],
-                       [CanvasKit.LINE_VERB, 5, 295],
-                       [CanvasKit.LINE_VERB, 205, 5],
-                       [CanvasKit.CLOSE_VERB]];
-            let path = CanvasKit.MakePathFromCmds(cmds);
-
-            let svgStr = path.toSVGString();
-            // We output it in terse form, which is different than Wikipedia's version
-            expect(svgStr).toEqual('M205 5L795 5L595 295L5 295L205 5Z');
-            path.delete();
-            done();
-        }));
-    });
-});
diff --git a/experimental/svg/model/SkSVGGradient.cpp b/experimental/svg/model/SkSVGGradient.cpp
index 5718aac..a591522 100644
--- a/experimental/svg/model/SkSVGGradient.cpp
+++ b/experimental/svg/model/SkSVGGradient.cpp
@@ -86,13 +86,13 @@
     //       * href attribute inheritance (not just color stops)
     //       * objectBoundingBox units support
 
-    static_assert(static_cast<SkShader::TileMode>(SkSVGSpreadMethod::Type::kPad) ==
-                  SkShader::kClamp_TileMode, "SkSVGSpreadMethod::Type is out of sync");
-    static_assert(static_cast<SkShader::TileMode>(SkSVGSpreadMethod::Type::kRepeat) ==
-                  SkShader::kRepeat_TileMode, "SkSVGSpreadMethod::Type is out of sync");
-    static_assert(static_cast<SkShader::TileMode>(SkSVGSpreadMethod::Type::kReflect) ==
-                  SkShader::kMirror_TileMode, "SkSVGSpreadMethod::Type is out of sync");
-    const auto tileMode = static_cast<SkShader::TileMode>(fSpreadMethod.type());
+    static_assert(static_cast<SkTileMode>(SkSVGSpreadMethod::Type::kPad) ==
+                  SkTileMode::kClamp, "SkSVGSpreadMethod::Type is out of sync");
+    static_assert(static_cast<SkTileMode>(SkSVGSpreadMethod::Type::kRepeat) ==
+                  SkTileMode::kRepeat, "SkSVGSpreadMethod::Type is out of sync");
+    static_assert(static_cast<SkTileMode>(SkSVGSpreadMethod::Type::kReflect) ==
+                  SkTileMode::kMirror, "SkSVGSpreadMethod::Type is out of sync");
+    const auto tileMode = static_cast<SkTileMode>(fSpreadMethod.type());
 
     paint->setShader(this->onMakeShader(ctx, colors.begin(), pos.begin(), colors.count(), tileMode,
                                         fGradientTransform.value()));
diff --git a/experimental/svg/model/SkSVGGradient.h b/experimental/svg/model/SkSVGGradient.h
index 75ffba9..1c6efa1 100644
--- a/experimental/svg/model/SkSVGGradient.h
+++ b/experimental/svg/model/SkSVGGradient.h
@@ -32,7 +32,7 @@
 
     virtual sk_sp<SkShader> onMakeShader(const SkSVGRenderContext&,
                                          const SkColor*, const SkScalar*, int count,
-                                         SkShader::TileMode, const SkMatrix& localMatrix) const = 0;
+                                         SkTileMode, const SkMatrix& localMatrix) const = 0;
 
 private:
     using StopPositionArray = SkSTArray<2, SkScalar, true>;
diff --git a/experimental/svg/model/SkSVGLinearGradient.cpp b/experimental/svg/model/SkSVGLinearGradient.cpp
index 14939d1..63aeebb 100644
--- a/experimental/svg/model/SkSVGLinearGradient.cpp
+++ b/experimental/svg/model/SkSVGLinearGradient.cpp
@@ -57,7 +57,7 @@
 
 sk_sp<SkShader> SkSVGLinearGradient::onMakeShader(const SkSVGRenderContext& ctx,
                                                   const SkColor* colors, const SkScalar* pos,
-                                                  int count, SkShader::TileMode tm,
+                                                  int count, SkTileMode tm,
                                                   const SkMatrix& localMatrix) const {
     const auto& lctx = ctx.lengthContext();
     const auto x1 = lctx.resolve(fX1, SkSVGLengthContext::LengthType::kHorizontal);
diff --git a/experimental/svg/model/SkSVGLinearGradient.h b/experimental/svg/model/SkSVGLinearGradient.h
index e878baf..923c79b 100644
--- a/experimental/svg/model/SkSVGLinearGradient.h
+++ b/experimental/svg/model/SkSVGLinearGradient.h
@@ -28,7 +28,7 @@
 
     sk_sp<SkShader> onMakeShader(const SkSVGRenderContext&,
                                  const SkColor*, const SkScalar*, int count,
-                                 SkShader::TileMode, const SkMatrix&) const override;
+                                 SkTileMode, const SkMatrix&) const override;
 private:
     SkSVGLinearGradient();
 
diff --git a/experimental/svg/model/SkSVGPattern.cpp b/experimental/svg/model/SkSVGPattern.cpp
index ef334d7..ffed054 100644
--- a/experimental/svg/model/SkSVGPattern.cpp
+++ b/experimental/svg/model/SkSVGPattern.cpp
@@ -161,9 +161,9 @@
     // Cannot call into INHERITED:: because SkSVGHiddenContainer skips rendering.
     contentNode->SkSVGContainer::onRender(recordingContext);
 
-    paint->setShader(SkShader::MakePictureShader(recorder.finishRecordingAsPicture(),
-                                                 SkShader::kRepeat_TileMode,
-                                                 SkShader::kRepeat_TileMode,
+    paint->setShader(recorder.finishRecordingAsPicture()->makeShader(
+                                                 SkTileMode::kRepeat,
+                                                 SkTileMode::kRepeat,
                                                  patternTransform,
                                                  &tile));
     return true;
diff --git a/experimental/svg/model/SkSVGRadialGradient.cpp b/experimental/svg/model/SkSVGRadialGradient.cpp
index c719693..b1011a2 100644
--- a/experimental/svg/model/SkSVGRadialGradient.cpp
+++ b/experimental/svg/model/SkSVGRadialGradient.cpp
@@ -66,7 +66,7 @@
 
 sk_sp<SkShader> SkSVGRadialGradient::onMakeShader(const SkSVGRenderContext& ctx,
                                                   const SkColor* colors, const SkScalar* pos,
-                                                  int count, SkShader::TileMode tm,
+                                                  int count, SkTileMode tm,
                                                   const SkMatrix& m) const {
     const auto&  lctx = ctx.lengthContext();
     const auto      r = lctx.resolve(fR , SkSVGLengthContext::LengthType::kOther);
diff --git a/experimental/svg/model/SkSVGRadialGradient.h b/experimental/svg/model/SkSVGRadialGradient.h
index f277b65..e71947a 100644
--- a/experimental/svg/model/SkSVGRadialGradient.h
+++ b/experimental/svg/model/SkSVGRadialGradient.h
@@ -29,7 +29,7 @@
 
     sk_sp<SkShader> onMakeShader(const SkSVGRenderContext&,
                                  const SkColor*, const SkScalar*, int count,
-                                 SkShader::TileMode, const SkMatrix&) const override;
+                                 SkTileMode, const SkMatrix&) const override;
 private:
     SkSVGRadialGradient();
 
diff --git a/experimental/wasm-skp-debugger/.gitignore b/experimental/wasm-skp-debugger/.gitignore
new file mode 100644
index 0000000..907d56f
--- /dev/null
+++ b/experimental/wasm-skp-debugger/.gitignore
@@ -0,0 +1,3 @@
+package-lock.json
+fonts/*.cpp
+debugger/bin/*
\ No newline at end of file
diff --git a/experimental/wasm-skp-debugger/Makefile b/experimental/wasm-skp-debugger/Makefile
new file mode 100644
index 0000000..a9f9e7a
--- /dev/null
+++ b/experimental/wasm-skp-debugger/Makefile
@@ -0,0 +1,34 @@
+release:
+	# Does an incremental build where possible.
+	./compile.sh
+	mkdir -p ./debugger/bin
+	cp ../../out/debugger_wasm/debugger.js   ./debugger/bin
+	cp ../../out/debugger_wasm/debugger.wasm ./debugger/bin
+
+debug:
+	# Does an incremental build where possible.
+	./compile.sh debug
+	mkdir -p ./debugger/bin
+	cp ../../out/debugger_wasm_debug/debugger.js   ./debugger/bin
+	cp ../../out/debugger_wasm_debug/debugger.wasm ./debugger/bin
+
+move-assets:
+	# assume the developer also has a checkout of buildbot here and wants to test the newly
+	# built wasm debugger with the interface.
+	cp ./debugger/bin/debugger.js   ~/go/src/go.skia.org/infra/debugger-assets/res/js
+	cp ./debugger/bin/debugger.wasm ~/go/src/go.skia.org/infra/debugger-assets/res
+
+# A barebones local example of the wasm module included in a page.
+# for the real interface see
+# https://github.com/google/skia-buildbot/blob/master/debugger-assets/res/imp/wasm-app.html
+local-example:
+	rm -rf node_modules/debugger
+	mkdir -p node_modules
+	ln -s -T ../debugger node_modules/debugger
+	echo "Go check out http://localhost:8000/debugger/index.html"
+	python serve.py
+
+test-continuous:
+	echo "Assuming npm install has been run by user"
+	echo "Also assuming make debug or release has also been run by a user (if needed)"
+	npx karma start ./karma.conf.js --no-single-run --watch-poll
diff --git a/experimental/wasm-skp-debugger/compile.sh b/experimental/wasm-skp-debugger/compile.sh
new file mode 100755
index 0000000..cb287c9
--- /dev/null
+++ b/experimental/wasm-skp-debugger/compile.sh
@@ -0,0 +1,162 @@
+#!/bin/bash
+# Copyright 2019 Google LLC
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+set -ex
+
+BASE_DIR=`cd $(dirname ${BASH_SOURCE[0]}) && pwd`
+# This expects the environment variable EMSDK to be set
+if [[ ! -d $EMSDK ]]; then
+  echo "Be sure to set the EMSDK environment variable."
+  exit 1
+fi
+
+# Navigate to SKIA_HOME from where this file is located.
+pushd $BASE_DIR/../..
+
+source $EMSDK/emsdk_env.sh
+EMCC=`which emcc`
+EMCXX=`which em++`
+
+if [[ $@ == *debug* ]]; then
+  echo "Building a Debug build"
+  EXTRA_CFLAGS="\"-DSK_DEBUG\","
+  RELEASE_CONF="-O0 --js-opts 0 -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=1 -s GL_ASSERTIONS=1 -g4 \
+                --source-map-base /node_modules/debugger/bin/ -DSK_DEBUG"
+  BUILD_DIR=${BUILD_DIR:="out/debugger_wasm_debug"}
+else
+  echo "Building a Release build"
+  EXTRA_CFLAGS="\"-DSK_RELEASE\", \"-DGR_GL_CHECK_ALLOC_WITH_GET_ERROR=0\","
+  RELEASE_CONF="-Oz --closure 1 --llvm-lto 3 -DSK_RELEASE -DGR_GL_CHECK_ALLOC_WITH_GET_ERROR=0"
+  BUILD_DIR=${BUILD_DIR:="out/debugger_wasm"}
+fi
+
+mkdir -p $BUILD_DIR
+
+BUILTIN_FONT="$BASE_DIR/fonts/NotoMono-Regular.ttf.cpp"
+# Generate the font's binary file (which is covered by .gitignore)
+python tools/embed_resources.py \
+    --name SK_EMBEDDED_FONTS \
+    --input $BASE_DIR/fonts/NotoMono-Regular.ttf \
+    --output $BASE_DIR/fonts/NotoMono-Regular.ttf.cpp \
+    --align 4
+
+GN_GPU_FLAGS="\"-DSK_DISABLE_LEGACY_SHADERCONTEXT\","
+WASM_GPU="-lEGL -lGLESv2 -DSK_SUPPORT_GPU=1 \
+          -DSK_DISABLE_LEGACY_SHADERCONTEXT --pre-js $BASE_DIR/cpu.js --pre-js $BASE_DIR/gpu.js"
+
+# Turn off exiting while we check for ninja (which may not be on PATH)
+set +e
+NINJA=`which ninja`
+if [[ -z $NINJA ]]; then
+  git clone "https://chromium.googlesource.com/chromium/tools/depot_tools.git" --depth 1 $BUILD_DIR/depot_tools
+  NINJA=$BUILD_DIR/depot_tools/ninja
+fi
+# Re-enable error checking
+set -e
+
+./bin/fetch-gn
+
+echo "Compiling bitcode"
+
+./bin/gn gen ${BUILD_DIR} \
+  --args="cc=\"${EMCC}\" \
+  cxx=\"${EMCXX}\" \
+  extra_cflags_cc=[\"-frtti\"] \
+  extra_cflags=[\"-s\",\"USE_FREETYPE=1\",\"-s\",\"USE_LIBPNG=1\", \"-s\", \"WARN_UNALIGNED=1\",
+    \"-DSKNX_NO_SIMD\", \"-DSK_DISABLE_AAA\",
+    ${GN_GPU_FLAGS}
+    ${EXTRA_CFLAGS}
+  ] \
+  is_debug=false \
+  is_official_build=true \
+  is_component_build=false \
+  target_cpu=\"wasm\" \
+  \
+  skia_use_angle = false \
+  skia_use_dng_sdk=false \
+  skia_use_egl=true \
+  skia_use_expat=false \
+  skia_use_fontconfig=false \
+  skia_use_freetype=true \
+  skia_use_libheif=false \
+  skia_use_libjpeg_turbo=true \
+  skia_use_libpng=true \
+  skia_use_libwebp=true \
+  skia_use_wuffs=true \
+  skia_use_lua=false \
+  skia_use_piex=false \
+  skia_use_system_libpng=true \
+  skia_use_system_freetype2=true \
+  skia_use_system_libjpeg_turbo = false \
+  skia_use_system_libwebp=false \
+  skia_use_vulkan=false \
+  skia_use_zlib=true \
+  skia_enable_gpu=true \
+  skia_enable_tools=false \
+  skia_enable_skshaper=false \
+  skia_enable_ccpr=false \
+  skia_enable_nvpr=false \
+  skia_enable_skpicture=true \
+  skia_enable_fontmgr_empty=false \
+  skia_enable_pdf=false"
+
+# Build all the libs, we'll link the appropriate ones down below
+${NINJA} -C ${BUILD_DIR} libskia.a libdebugcanvas.a
+
+export EMCC_CLOSURE_ARGS="--externs $BASE_DIR/externs.js "
+
+echo "Generating final debugger wasm and javascript"
+
+# Emscripten prefers that the .a files go last in order, otherwise, it
+# may drop symbols that it incorrectly thinks aren't used. One day,
+# Emscripten will use LLD, which may relax this requirement.
+${EMCXX} \
+    $RELEASE_CONF \
+    -Iexperimental \
+    -Iinclude/c \
+    -Iinclude/codec \
+    -Iinclude/config \
+    -Iinclude/core \
+    -Iinclude/effects \
+    -Iinclude/gpu \
+    -Iinclude/gpu/gl \
+    -Iinclude/pathops \
+    -Iinclude/private \
+    -Iinclude/utils/ \
+    -Isrc/core/ \
+    -Isrc/gpu/ \
+    -Isrc/sfnt/ \
+    -Isrc/shaders/ \
+    -Isrc/utils/ \
+    -Ithird_party/icu \
+    -Itools \
+    -Itools/debugger \
+    -DSK_DISABLE_AAA \
+    -std=c++17 \
+    $WASM_GPU \
+    --pre-js $BASE_DIR/helper.js \
+    --post-js $BASE_DIR/ready.js \
+    --bind \
+    $BASE_DIR/fonts/NotoMono-Regular.ttf.cpp \
+    $BASE_DIR/debugger_bindings.cpp \
+    $BUILD_DIR/libdebugcanvas.a \
+    $BUILD_DIR/libskia.a \
+    -s ALLOW_MEMORY_GROWTH=1 \
+    -s EXPORT_NAME="DebuggerInit" \
+    -s FORCE_FILESYSTEM=0 \
+    -s MODULARIZE=1 \
+    -s NO_EXIT_RUNTIME=1 \
+    -s STRICT=1 \
+    -s TOTAL_MEMORY=128MB \
+    -s USE_FREETYPE=1 \
+    -s USE_LIBPNG=1 \
+    -s WARN_UNALIGNED=1 \
+    -s WASM=1 \
+    -s USE_WEBGL2=1 \
+    -o $BUILD_DIR/debugger.js
+
+# TODO(nifong): write unit tests
diff --git a/experimental/wasm-skp-debugger/cpu.js b/experimental/wasm-skp-debugger/cpu.js
new file mode 100644
index 0000000..91536a5
--- /dev/null
+++ b/experimental/wasm-skp-debugger/cpu.js
@@ -0,0 +1,85 @@
+// Adds compile-time JS functions to handle creation and flushing of wasm's offscreen buffer
+// to a visible element on the page.
+(function(DebuggerView){
+    // Takes a canvas element
+    DebuggerView.MakeSWCanvasSurface = function(canvas) {
+      // Set the canvas element to have a 2d non-gpu context. (constant until element is destroyed)
+      // We don't need the context in this scope, we just want the side effect.
+      canvas.getContext('2d', {
+          alpha: true,
+          depth: false
+        });
+      // Maybe better to use clientWidth/height.  See:
+      // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
+      var surface = DebuggerView.MakeSurface(canvas.width, canvas.height);
+      if (surface) {
+        surface._canvas = canvas;
+      }
+      console.log('Made HTML Canvas Surface');
+      return surface;
+    };
+
+    // Don't over-write the MakeCanvasSurface set by gpu.js if it exists.
+    if (!DebuggerView.MakeCanvasSurface) {
+      DebuggerView.MakeCanvasSurface = DebuggerView.MakeSWCanvasSurface;
+    }
+
+    DebuggerView.MakeSurface = function(width, height) {
+      var bufferLen = width * height * 4; // 4 bytes per pixel
+      // Allocate the buffer of pixels to be drawn into.
+      var pixelPtr = DebuggerView._malloc(bufferLen);
+      var imageInfo = {
+        'width':  width,
+        'height': height,
+        // RGBA 8888 is the only pixel format we can show on an HTML canvas
+        'colorType': DebuggerView.ColorType.RGBA_8888,
+        // We are sending these pixels directly into the HTML canvas,
+        // (and those pixels are un-premultiplied, i.e. straight r,g,b,a)
+        'alphaType': DebuggerView.AlphaType.Unpremul,
+      }
+      var surface = this._getRasterDirectSurface(imageInfo, pixelPtr, width * 4);
+      if (surface) {
+        surface._canvas = null;
+        surface._width = width;
+        surface._height = height;
+        surface._bufferLen = bufferLen;
+
+        surface._pixelPtr = pixelPtr;
+        // rasterDirectSurface does not initialize the pixels, so we clear them
+        // to transparent black.
+        surface.getCanvas().clear(DebuggerView.TRANSPARENT);
+      }
+      return surface;
+    };
+
+
+    DebuggerView.onRuntimeInitialized = function() {
+
+      DebuggerView.SkSurface.prototype.flush = function() {
+        this._flush();
+        // Do we have an HTML canvas to write the pixels to?
+        // We will not if this a GPU build or a raster surface, for example.
+        if (this._canvas) {
+          var pixels = new Uint8ClampedArray(DebuggerView.buffer, this._pixelPtr, this._bufferLen);
+          var imageData = new ImageData(pixels, this._width, this._height);
+          this._canvas.getContext('2d').putImageData(imageData, 0, 0);
+        }
+      };
+
+      // Call dispose() instead of delete to clean up the underlying memory
+      DebuggerView.SkSurface.prototype.dispose = function() {
+        if (this._pixelPtr) {
+          DebuggerView._free(this._pixelPtr);
+        }
+        this.delete();
+      }
+    }
+
+    DebuggerView.currentContext = DebuggerView.currentContext || function() {
+      // no op if this is a cpu-only build.
+    };
+
+    DebuggerView.setCurrentContext = DebuggerView.setCurrentContext || function() {
+       // no op if this is a cpu-only build.
+    };
+}(Module)); // When this file is loaded in, the high level object is "Module";
diff --git a/experimental/wasm-skp-debugger/debugger/index.html b/experimental/wasm-skp-debugger/debugger/index.html
new file mode 100644
index 0000000..872bb9d
--- /dev/null
+++ b/experimental/wasm-skp-debugger/debugger/index.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Debugger Demo</title>
+  <script src="bin/debugger.js"></script>
+  <script>
+let index = 0;
+let surface;
+
+DebuggerInit({
+  locateFile: (file) => '/node_modules/debugger/bin/'+file,
+}).ready().then((Debugger) => {
+  const player = new Debugger.SkpDebugPlayer();
+
+  // Define an event handler for the file input dialog
+  function readSkpFile(e){
+    // Did the change event result in the file-input element specifing a file? (user might have cancelled the dialog)
+    const file = e.target.files[0];
+    if (!file) {
+      return;
+    }
+    // create a callback for when the file finishes being read.
+    const reader = new FileReader();
+    reader.onload = function(e) {
+      // Convert e.target.result (an ArrayBuffer) into a typedarray,
+      // otherwise fileMem.set() below seems to have no effect.
+      const fileContents = new Uint8Array(e.target.result);
+      const size = fileContents.byteLength;
+      // Allocate memory in wasm to hold the skp file selected by the user.
+      const fileMemPtr = Debugger._malloc(size);
+      // Make a typed array view of that memory
+      let fileMem = new Uint8Array(Debugger.buffer, fileMemPtr, size);
+      // Copy the file into it
+      fileMem.set(fileContents);
+      // Hand off pointer to wasm
+      player.loadSkp(fileMemPtr, size);
+      // From the loaded commands, Debugger now knows the bounds.
+      let bounds = player.getBounds();
+      // Resize our canvas to match
+      canvas = document.getElementById('debugger_view');
+      canvas.width = bounds.fRight - bounds.fLeft;
+      canvas.height = bounds.fBottom - bounds.fTop;
+      //Assume GPU selected initially
+      surface = Debugger.MakeWebGLCanvasSurface(canvas);
+
+      document.getElementById('command-count').innerHTML = player.getSize();
+      player.setClipVizColor(0);
+    };
+    reader.readAsArrayBuffer(file);
+  }
+
+  function playFile() {
+    interval = parseFloat(document.getElementById('interval').value);
+    let intervalID = setInterval(function() {
+        if (index < 789){
+          player.drawTo(surface, index);
+          surface.flush();
+          index++;
+        }
+    }, interval);
+  }
+
+  // Discard canvas when switching between cpu/gpu backend because it's bound to a context.
+  function replaceCanvas() {
+      canvas = document.getElementById('debugger_view');
+      let newCanvas = canvas.cloneNode(true);
+      let parent = canvas.parentNode;
+      parent.replaceChild(newCanvas, canvas);
+  }
+
+  document.getElementById('file-input')
+    .addEventListener('change', readSkpFile);
+
+  document.getElementById('play_button')
+    .addEventListener('click', playFile);
+
+  document.getElementById('overdraw')
+    .addEventListener('change', function(e) {
+      player.setOverdrawVis(e.target.checked);
+    });
+
+  document.getElementById('gpu_op_bounds')
+    .addEventListener('change', function(e) {
+      player.setGpuOpBounds(e.target.checked);
+    });
+
+  document.getElementById('clip_viz_color')
+    .addEventListener('change', function(e) {
+      // Remove the '#' from the hex color string.
+      // prepend an alpha value (0x50 or about 30%)
+      // then convert to an integer.
+      colorInt = parseInt("50"+e.target.value.substring(1),16);
+      player.setClipVizColor(colorInt);
+    });
+
+  document.getElementById('disable_clip_viz')
+    .addEventListener('click', function(e) {
+      // Setting the clip viz to transparent black makes it invisible.
+      player.setClipVizColor(0);
+    });
+
+  document.getElementById('get_json_command_list')
+    .addEventListener('click', function(e) {
+      result = player.jsonCommandList(surface);
+      console.log('getjsoncommandlist result '+result.substring(0,100)+'...');
+      commands = JSON.parse(result);
+      console.log('parsed json');
+    });
+
+  document.getElementById('backend_gpu')
+    .addEventListener('change', function(e) {
+      if (e.target.checked) {
+        replaceCanvas();
+        surface = Debugger.MakeWebGLCanvasSurface(document.getElementById('debugger_view'));
+      }
+    });
+
+  document.getElementById('backend_cpu')
+    .addEventListener('change', function(e) {
+      if (e.target.checked) {
+        replaceCanvas();
+        surface = Debugger.MakeSWCanvasSurface(document.getElementById('debugger_view'));
+      }
+    });
+
+});
+  </script>
+  </head>
+  <body>
+    <canvas id=debugger_view width=400 height=400></canvas>
+    <div style="float:right">
+      <input type="radio" name="backend" value="CPU" id="backend_cpu"> CPU
+      <input type="radio" name="backend" value="WebGL" id="backend_gpu" checked> WebGL<br>
+      <input type="file" id="file-input" /> | <span id="command-count">0</span> commands<br>
+      <input type="button" id="play_button" value="Play" />
+      command interval in ms
+      <input type="text" id="interval" value="20" /><br>
+      <input type="checkbox" id="overdraw" /> Overdraw vis<br>
+      <input type="checkbox" id="gpu_op_bounds" /> GPU Op bounds<br>
+      <input type="color" id="clip_viz_color" />Clip visualization color
+      <input type="button" id="disable_clip_viz" value="Disable" /><br>
+      <input type="button" id="get_json_command_list" value="Get JSON Command List" /><br>
+    <div>
+    <div style="float:clear"></div>
+  </body>
+</html>
diff --git a/experimental/wasm-skp-debugger/debugger/sample.skp b/experimental/wasm-skp-debugger/debugger/sample.skp
new file mode 100644
index 0000000..3053116
--- /dev/null
+++ b/experimental/wasm-skp-debugger/debugger/sample.skp
Binary files differ
diff --git a/experimental/wasm-skp-debugger/debugger_bindings.cpp b/experimental/wasm-skp-debugger/debugger_bindings.cpp
new file mode 100644
index 0000000..a07cc4d
--- /dev/null
+++ b/experimental/wasm-skp-debugger/debugger_bindings.cpp
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "DebugCanvas.h"
+#include "SkJSONWriter.h"
+#include "SkPicture.h"
+#include "SkSurface.h"
+#include "UrlDataManager.h"
+
+#include <emscripten.h>
+#include <emscripten/bind.h>
+
+#if SK_SUPPORT_GPU
+#include "GrBackendSurface.h"
+#include "GrContext.h"
+#include "GrGLInterface.h"
+#include "GrGLTypes.h"
+
+#include <GL/gl.h>
+#include <emscripten/html5.h>
+#endif
+
+using JSColor = int32_t;
+
+struct SimpleImageInfo {
+  int width;
+  int height;
+  SkColorType colorType;
+  SkAlphaType alphaType;
+};
+
+SkImageInfo toSkImageInfo(const SimpleImageInfo& sii) {
+  return SkImageInfo::Make(sii.width, sii.height, sii.colorType, sii.alphaType);
+}
+
+class SkpDebugPlayer {
+  public:
+    SkpDebugPlayer() {}
+
+    /* loadSkp deserializes a skp file that has been copied into the shared WASM memory.
+     * cptr - a pointer to the data to deserialize.
+     * length - length of the data in bytes.
+     * The caller must allocate the memory with M._malloc where M is the wasm module in javascript
+     * and copy the data into M.buffer at the pointer returned by malloc.
+     *
+     * uintptr_t is used here because emscripten will not allow binding of functions with pointers
+     * to primitive types. We can instead pass a number and cast it to whatever kind of
+     * pointer we're expecting.
+     */
+    void loadSkp(uintptr_t cptr, int length) {
+      const auto* data = reinterpret_cast<const uint8_t*>(cptr);
+      // note overloaded = operator that actually does a move
+      fPicture = SkPicture::MakeFromData(data, length);
+      if (!fPicture) {
+        SkDebugf("Unable to parse SKP file.\n");
+        return;
+      }
+      SkDebugf("Parsed SKP file.\n");
+      // Make debug canvas using bounds from SkPicture
+      fBounds = fPicture->cullRect().roundOut();
+      fDebugCanvas.reset(new DebugCanvas(fBounds));
+      SkDebugf("DebugCanvas created.\n");
+
+      // Only draw picture to the debug canvas once.
+      fDebugCanvas->drawPicture(fPicture);
+      SkDebugf("Added picture with %d commands.\n", fDebugCanvas->getSize());
+    }
+
+    /* drawTo asks the debug canvas to draw from the beginning of the picture
+     * to the given command and flush the canvas.
+     */
+    void drawTo(SkSurface* surface, int32_t index) {
+      fDebugCanvas->drawTo(surface->getCanvas(), index);
+      surface->getCanvas()->flush();
+    }
+
+    const SkIRect& getBounds() { return fBounds; }
+
+    void setOverdrawVis(bool on) { fDebugCanvas->setOverdrawViz(on); }
+    void setGpuOpBounds(bool on) {
+      fDebugCanvas->setDrawGpuOpBounds(on);
+    }
+    void setClipVizColor(JSColor color) {
+      fDebugCanvas->setClipVizColor(SkColor(color));
+    }
+    int getSize() const { return fDebugCanvas->getSize(); }
+    void deleteCommand(int index) { fDebugCanvas->deleteDrawCommandAt(index); }
+    void setCommandVisibility(int index, bool visible) {
+      fDebugCanvas->toggleCommand(index, visible);
+    }
+
+    // Return the command list in JSON representation as a string
+    std::string jsonCommandList(sk_sp<SkSurface> surface) {
+      SkDynamicMemoryWStream stream;
+      SkJSONWriter writer(&stream, SkJSONWriter::Mode::kFast);
+      // Note that the root url provided here may need to change in the production deployment.
+      // this will be prepended to any links that are created in the json command list.
+      UrlDataManager udm(SkString("/"));
+      writer.beginObject(); // root
+      fDebugCanvas->toJSON(writer, udm, getSize(), surface->getCanvas());
+      writer.endObject(); // root
+      writer.flush();
+      auto skdata = stream.detachAsData();
+      // Convert skdata to string_view, which accepts a length
+      std::string_view data_view(reinterpret_cast<const char*>(skdata->data()), skdata->size());
+      // and string_view to string, which emscripten understands.
+      return std::string(data_view);
+    }
+
+    // Gets the clip and matrix of the last command drawn
+    std::string lastCommandInfo() {
+      SkMatrix vm = fDebugCanvas->getCurrentMatrix();
+      SkIRect clip = fDebugCanvas->getCurrentClip();
+
+      SkDynamicMemoryWStream stream;
+      SkJSONWriter writer(&stream, SkJSONWriter::Mode::kFast);
+      UrlDataManager udm(SkString("/"));
+      writer.beginObject(); // root
+
+      writer.appendName("ViewMatrix");
+      DrawCommand::MakeJsonMatrix(writer, vm);
+      writer.appendName("ClipRect");
+      DrawCommand::MakeJsonIRect(writer, clip);
+
+      writer.endObject(); // root
+      writer.flush();
+      auto skdata = stream.detachAsData();
+      // Convert skdata to string_view, which accepts a length
+      std::string_view data_view(reinterpret_cast<const char*>(skdata->data()), skdata->size());
+      // and string_view to string, which emscripten understands.
+      return std::string(data_view);
+    }
+
+  private:
+      std::unique_ptr<DebugCanvas> fDebugCanvas;
+      sk_sp<SkPicture>             fPicture;
+      SkIRect                      fBounds;
+};
+
+#if SK_SUPPORT_GPU
+sk_sp<GrContext> MakeGrContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context)
+{
+    EMSCRIPTEN_RESULT r = emscripten_webgl_make_context_current(context);
+    if (r < 0) {
+        SkDebugf("failed to make webgl context current %d\n", r);
+        return nullptr;
+    }
+    // setup GrContext
+    auto interface = GrGLMakeNativeInterface();
+    // setup contexts
+    sk_sp<GrContext> grContext(GrContext::MakeGL(interface));
+    return grContext;
+}
+
+sk_sp<SkSurface> MakeOnScreenGLSurface(sk_sp<GrContext> grContext, int width, int height) {
+    glClearColor(0, 0, 0, 0);
+    glClearStencil(0);
+    glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+
+    // Wrap the frame buffer object attached to the screen in a Skia render
+    // target so Skia can render to it
+    GrGLint buffer;
+    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &buffer);
+    GrGLFramebufferInfo info;
+    info.fFBOID = (GrGLuint) buffer;
+    SkColorType colorType;
+
+    info.fFormat = GL_RGBA8;
+    colorType = kRGBA_8888_SkColorType;
+
+    GrBackendRenderTarget target(width, height, 0, 8, info);
+
+    sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(grContext.get(), target,
+                                                                    kBottomLeft_GrSurfaceOrigin,
+                                                                    colorType, nullptr, nullptr));
+    return surface;
+}
+
+sk_sp<SkSurface> MakeRenderTarget(sk_sp<GrContext> grContext, int width, int height) {
+    SkImageInfo info = SkImageInfo::MakeN32(width, height, SkAlphaType::kPremul_SkAlphaType);
+
+    sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(grContext.get(),
+                             SkBudgeted::kYes,
+                             info, 0,
+                             kBottomLeft_GrSurfaceOrigin,
+                             nullptr, true));
+    return surface;
+}
+
+sk_sp<SkSurface> MakeRenderTarget(sk_sp<GrContext> grContext, SimpleImageInfo sii) {
+    sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(grContext.get(),
+                             SkBudgeted::kYes,
+                             toSkImageInfo(sii), 0,
+                             kBottomLeft_GrSurfaceOrigin,
+                             nullptr, true));
+    return surface;
+}
+#endif
+
+using namespace emscripten;
+EMSCRIPTEN_BINDINGS(my_module) {
+
+  // The main class that the JavaScript in index.html uses
+  class_<SkpDebugPlayer>("SkpDebugPlayer")
+    .constructor<>()
+    .function("loadSkp",              &SkpDebugPlayer::loadSkp, allow_raw_pointers())
+    .function("drawTo",               &SkpDebugPlayer::drawTo, allow_raw_pointers())
+    .function("getBounds",            &SkpDebugPlayer::getBounds)
+    .function("setOverdrawVis",       &SkpDebugPlayer::setOverdrawVis)
+    .function("setClipVizColor",      &SkpDebugPlayer::setClipVizColor)
+    .function("getSize",              &SkpDebugPlayer::getSize)
+    .function("deleteCommand",        &SkpDebugPlayer::deleteCommand)
+    .function("setCommandVisibility", &SkpDebugPlayer::setCommandVisibility)
+    .function("setGpuOpBounds",       &SkpDebugPlayer::setGpuOpBounds)
+    .function("jsonCommandList",      &SkpDebugPlayer::jsonCommandList, allow_raw_pointers())
+    .function("lastCommandInfo",      &SkpDebugPlayer::lastCommandInfo);
+
+  // Structs used as arguments or returns to the functions above
+  value_object<SkIRect>("SkIRect")
+      .field("fLeft",   &SkIRect::fLeft)
+      .field("fTop",    &SkIRect::fTop)
+      .field("fRight",  &SkIRect::fRight)
+      .field("fBottom", &SkIRect::fBottom);
+
+  // Symbols needed by cpu.js to perform surface creation and flushing.
+  enum_<SkColorType>("ColorType")
+    .value("RGBA_8888", SkColorType::kRGBA_8888_SkColorType);
+  enum_<SkAlphaType>("AlphaType")
+      .value("Opaque",   SkAlphaType::kOpaque_SkAlphaType)
+      .value("Premul",   SkAlphaType::kPremul_SkAlphaType)
+      .value("Unpremul", SkAlphaType::kUnpremul_SkAlphaType);
+  value_object<SimpleImageInfo>("SkImageInfo")
+    .field("width",     &SimpleImageInfo::width)
+    .field("height",    &SimpleImageInfo::height)
+    .field("colorType", &SimpleImageInfo::colorType)
+    .field("alphaType", &SimpleImageInfo::alphaType);
+  constant("TRANSPARENT", (JSColor) SK_ColorTRANSPARENT);
+  function("_getRasterDirectSurface", optional_override([](const SimpleImageInfo ii,
+                                                           uintptr_t /* uint8_t*  */ pPtr,
+                                                           size_t rowBytes)->sk_sp<SkSurface> {
+    uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
+    SkImageInfo imageInfo = toSkImageInfo(ii);
+    SkDebugf("Made raster direct surface.\n");
+    return SkSurface::MakeRasterDirect(imageInfo, pixels, rowBytes, nullptr);
+  }), allow_raw_pointers());
+  class_<SkSurface>("SkSurface")
+    .smart_ptr<sk_sp<SkSurface>>("sk_sp<SkSurface>")
+    .function("width", &SkSurface::width)
+    .function("height", &SkSurface::height)
+    .function("_flush", select_overload<void()>(&SkSurface::flush))
+    .function("getCanvas", &SkSurface::getCanvas, allow_raw_pointers());
+  class_<SkCanvas>("SkCanvas")
+    .function("clear", optional_override([](SkCanvas& self, JSColor color)->void {
+      // JS side gives us a signed int instead of an unsigned int for color
+      // Add a optional_override to change it out.
+      self.clear(SkColor(color));
+    }));
+
+  #if SK_SUPPORT_GPU
+    class_<GrContext>("GrContext")
+        .smart_ptr<sk_sp<GrContext>>("sk_sp<GrContext>");
+    function("currentContext", &emscripten_webgl_get_current_context);
+    function("setCurrentContext", &emscripten_webgl_make_context_current);
+    function("MakeGrContext", &MakeGrContext);
+    function("MakeOnScreenGLSurface", &MakeOnScreenGLSurface);
+    function("MakeRenderTarget", select_overload<sk_sp<SkSurface>(
+      sk_sp<GrContext>, int, int)>(&MakeRenderTarget));
+    function("MakeRenderTarget", select_overload<sk_sp<SkSurface>(
+      sk_sp<GrContext>, SimpleImageInfo)>(&MakeRenderTarget));
+    constant("gpu", true);
+  #endif
+}
diff --git a/experimental/wasm-skp-debugger/externs.js b/experimental/wasm-skp-debugger/externs.js
new file mode 100644
index 0000000..b99e4f5
--- /dev/null
+++ b/experimental/wasm-skp-debugger/externs.js
@@ -0,0 +1,56 @@
+// TODO(nifong): Complete this before turning on clojure optimizations in compile.sh
+
+var DebuggerView = {
+	MakeSWCanvasSurface: function() {},
+	_getRasterDirectSurface: function() {},
+	_malloc: function() {},
+	_free: function() {},
+	onRuntimeInitialized: function() {},
+	SkpFilePlayer: function() {},
+	MakeWebGLCanvasSurface: function() {},
+	MakeGrContext: function() {},
+	MakeOnScreenGLSurface: function() {},
+	MakeCanvasSurface: function() {},
+
+	ColorType: {
+		RGBA_8888: {},
+	},
+
+	AlphaType: {
+		Unpremul: {},
+	},
+
+	TRANSPARENT: {},
+
+	SkSurface: {
+		// public API (from C++ bindings)
+		/** @return {DebuggerView.SkCanvas} */
+		getCanvas: function() {},
+
+		// private API
+		_flush: function() {},
+		delete: function() {},
+	},
+
+	SkpDebugPlayer: {
+		SkpDebugPlayer: function() {},
+		loadSkp: function() {},
+		drawTo: function() {},
+		getBounds: function() {},
+		setOverdrawVis: function() {},
+		setGpuOpBounds: function() {},
+		setClipVizColor: function() {},
+		getSize: function() {},
+		deleteCommand: function() {},
+		setCommandVisibility: function() {},
+		jsonCommandList: function() {},
+		lastCommandInfo: function() {},
+	},
+};
+
+// Public API things that are newly declared in the JS should go here.
+// It's not enough to declare them above, because closure can still erase them
+// unless they go on the prototype.
+
+DebuggerView.SkSurface.prototype.flush = function() {};
+DebuggerView.SkSurface.prototype.dispose = function() {};
\ No newline at end of file
diff --git a/experimental/canvaskit/fonts/NotoMono-Regular.ttf b/experimental/wasm-skp-debugger/fonts/NotoMono-Regular.ttf
similarity index 100%
copy from experimental/canvaskit/fonts/NotoMono-Regular.ttf
copy to experimental/wasm-skp-debugger/fonts/NotoMono-Regular.ttf
Binary files differ
diff --git a/experimental/wasm-skp-debugger/gpu.js b/experimental/wasm-skp-debugger/gpu.js
new file mode 100644
index 0000000..5549c9c
--- /dev/null
+++ b/experimental/wasm-skp-debugger/gpu.js
@@ -0,0 +1,74 @@
+// Adds compile-time JS functions to augment the DebuggerView interface.
+// Specifically, anything that should only be on the GPU version of DebuggerView.
+(function(DebuggerView){
+    function makeWebGLContext(canvas, attrs) {
+      // These defaults come from the emscripten _emscripten_webgl_create_context
+      // TODO(nifong): All these settings appear to be ignored. investigate.
+      var contextAttributes = {
+        alpha: 1,
+        depth: 1,
+        stencil: 0,
+        antialias: 1,
+        premultipliedAlpha: 1,
+        // for the zoom to be able to access the pixels. Incurs performance penalty
+        preserveDrawingBuffer: 1,
+        preferLowPowerToHighPerformance: 0,
+        failIfMajorPerformanceCaveat: 0,
+        majorVersion: 1,
+        minorVersion: 0,
+        enableExtensionsByDefault: 1,
+        explicitSwapControl: 0,
+        renderViaOffscreenBackBuffer: 0,
+      };
+      if (!canvas) {
+        console.log('null canvas passed into makeWebGLContext');
+        return 0;
+      }
+      // This check is from the emscripten version
+      if (contextAttributes['explicitSwapControl']) {
+        console.log('explicitSwapControl is not supported');
+        return 0;
+      }
+      // GL is an enscripten provided helper
+      // See https://github.com/emscripten-core/emscripten/blob/incoming/src/library_webgl.js
+      var context = GL.createContext(canvas, contextAttributes);
+      if (!context) {
+        console.log('Could not get a WebGL context from the canvas element.');
+      }
+      console.log('Made Web Gl Canvas Surface');
+      return context
+    }
+
+    DebuggerView.GetWebGLContext = function(canvas, attrs) {
+      return makeWebGLContext(canvas, attrs);
+    };
+
+    // canvas - a canvas element to use for this surface.
+    DebuggerView.MakeWebGLCanvasSurface = function(canvas) {
+      // we are ok with all the defaults
+      var ctx = DebuggerView.GetWebGLContext(canvas);
+
+      if (!ctx || ctx < 0) {
+        throw 'failed to create webgl context: err ' + ctx;
+      }
+
+      var grcontext = this.MakeGrContext(ctx);
+      if (!grcontext) {
+        throw (
+          'failed to create grcontext. Open GL driver may not support all needed functions: err '
+          + grcontext);
+      }
+
+      // Maybe better to use clientWidth/height.  See:
+      // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
+      var surface = this.MakeOnScreenGLSurface(grcontext, canvas.width, canvas.height);
+      if (!surface) {
+        // Don't fall back silently in the debugger, the user explicitly controls which backend he
+        // wants via the UI. Calling function may catch this and show the user an error.
+        throw ('Failed to create OpenGL surface. GPU Backend unavailable.');
+      }
+      return surface;
+    };
+    // Default to trying WebGL first.
+    DebuggerView.MakeCanvasSurface = DebuggerView.MakeWebGLCanvasSurface;
+}(Module)); // When this file is loaded in, the high level object is "Module";
diff --git a/experimental/wasm-skp-debugger/helper.js b/experimental/wasm-skp-debugger/helper.js
new file mode 100644
index 0000000..1370f05
--- /dev/null
+++ b/experimental/wasm-skp-debugger/helper.js
@@ -0,0 +1,24 @@
+// Adds compile-time JS functions to augment the DebuggerView interface.
+(function(DebuggerView){
+
+    DebuggerView.SkpFilePlayer = function(file_arraybuf) {
+    // Create the instance of SkpDebugPlayer
+    var player = new this.SkpDebugPlayer();
+    // Convert file (an ArrayBuffer) into a typedarray,
+    // otherwise fileMem.set() below seems to have no effect.
+    var fileContents = new Uint8Array(file_arraybuf);
+    var size = fileContents.byteLength;
+    // Allocate memory in wasm to hold the skp file selected by the user.
+    var fileMemPtr = this._malloc(size);
+    // Make a typed array view of that memory
+    var fileMem = new Uint8Array(this.buffer, fileMemPtr, size);
+    // Copy the file into it
+    fileMem.set(fileContents);
+    // Hand off pointer to wasm
+    player.loadSkp(fileMemPtr, size);
+    // Free the memory that was used to hold the file, since it is now represented as an SkPicture
+    this._free(fileMemPtr)
+    return player;
+  }
+
+}(Module)); // When this file is loaded in, the high level object is "Module";
diff --git a/experimental/wasm-skp-debugger/karma.conf.js b/experimental/wasm-skp-debugger/karma.conf.js
new file mode 100644
index 0000000..8378be4
--- /dev/null
+++ b/experimental/wasm-skp-debugger/karma.conf.js
@@ -0,0 +1,73 @@
+const isDocker = require('is-docker')();
+
+module.exports = function(config) {
+  // Set the default values to be what are needed when testing the
+  // WebAssembly build locally.
+  let cfg = {
+    // frameworks to use
+    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+    frameworks: ['jasmine'],
+
+    // list of files / patterns to load in the browser
+    files: [
+      { pattern: 'debugger/bin/debugger.wasm', included:false, served:true},
+      { pattern: 'debugger/sample.skp', included:false, served:true},
+      '../../modules/pathkit/tests/testReporter.js',
+      'debugger/bin/debugger.js',
+      'tests/*.spec.js'
+    ],
+
+    proxies: {
+      '/debugger/': '/base/debugger/',
+    },
+
+    // test results reporter to use
+    // possible values: 'dots', 'progress'
+    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+    reporters: ['progress'],
+
+    // web server port
+    port: 4444,
+
+    // enable / disable colors in the output (reporters and logs)
+    colors: true,
+
+    // level of logging
+    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+    logLevel: config.LOG_INFO,
+
+    // enable / disable watching file and executing tests whenever any file changes
+    autoWatch: true,
+
+    browserDisconnectTimeout: 15000,
+    browserNoActivityTimeout: 15000,
+
+    // start these browsers
+    browsers: ['Chrome'],
+
+    // Continuous Integration mode
+    // if true, Karma captures browsers, runs the tests and exits
+    singleRun: false,
+
+    // Concurrency level
+    // how many browser should be started simultaneous
+    concurrency: Infinity,
+  };
+
+  if (isDocker) {
+    // See https://hackernoon.com/running-karma-tests-with-headless-chrome-inside-docker-ae4aceb06ed3
+    cfg.browsers = ['ChromeHeadlessNoSandbox'],
+    cfg.customLaunchers = {
+        ChromeHeadlessNoSandbox: {
+            base: 'ChromeHeadless',
+            flags: [
+            // Without this flag, we see an error:
+            // Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permitted
+                '--no-sandbox'
+            ],
+        },
+    };
+  }
+
+  config.set(cfg);
+}
diff --git a/experimental/wasm-skp-debugger/package.json b/experimental/wasm-skp-debugger/package.json
new file mode 100644
index 0000000..be52f2e
--- /dev/null
+++ b/experimental/wasm-skp-debugger/package.json
@@ -0,0 +1,21 @@
+{
+  "name": "debugger-local",
+  "version": "0.0.0",
+  "description": "private",
+  "private": true,
+  "main": "index.js",
+  "dependencies": {},
+  "devDependencies": {
+    "is-docker": "~1.1.0",
+    "jasmine-core": "~3.1.0",
+    "karma": "~3.0.0",
+    "karma-chrome-launcher": "~2.2.0",
+    "karma-jasmine": "~1.1.2",
+    "requirejs": "~2.3.5"
+  },
+  "scripts": {
+    "test": "make test-continuous"
+  },
+  "author": "",
+  "license": "BSD-3-Clause"
+}
diff --git a/experimental/wasm-skp-debugger/ready.js b/experimental/wasm-skp-debugger/ready.js
new file mode 100644
index 0000000..60f2486
--- /dev/null
+++ b/experimental/wasm-skp-debugger/ready.js
@@ -0,0 +1,16 @@
+// See https://github.com/kripken/emscripten/issues/5820#issuecomment-385722568
+// for context on why the .then() that comes with Module breaks things (e.g. infinite loops)
+// and why the below fixes it.
+Module['ready'] = function() {
+  return new Promise(function (resolve, reject) {
+    Module['onAbort'] = reject;
+    if (runtimeInitialized) {
+      resolve(Module);
+    } else {
+      addOnPostRun(function() {
+        resolve(Module);
+      });
+    }
+  });
+}
+delete Module['then'];
\ No newline at end of file
diff --git a/experimental/canvaskit/serve.py b/experimental/wasm-skp-debugger/serve.py
similarity index 100%
rename from experimental/canvaskit/serve.py
rename to experimental/wasm-skp-debugger/serve.py
diff --git a/experimental/wasm-skp-debugger/tests/startup.spec.js b/experimental/wasm-skp-debugger/tests/startup.spec.js
new file mode 100644
index 0000000..0f61dba
--- /dev/null
+++ b/experimental/wasm-skp-debugger/tests/startup.spec.js
@@ -0,0 +1,60 @@
+// The increased timeout is especially needed with larger binaries
+// like in the debug/gpu build
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
+
+describe('Debugger\'s Startup Behavior', function() {
+    // Note, don't try to print the CanvasKit object - it can cause Karma/Jasmine to lock up.
+    var Debugger = null;
+    const LoadDebugger = new Promise(function(resolve, reject) {
+        if (Debugger) {
+            resolve();
+        } else {
+            DebuggerInit({
+                locateFile: (file) => '/debugger/bin/'+file,
+            }).ready().then((_Debugger) => {
+                Debugger = _Debugger;
+                resolve();
+            });
+        }
+    });
+
+    let container = document.createElement('div');
+    document.body.appendChild(container);
+    container.innerHTML = `<canvas id=debugger_view width=720 height=1280></canvas>`;
+
+    it('can load and draw a skp file', function(done) {
+        LoadDebugger.then(catchException(done, () => {
+            const surface = Debugger.MakeSWCanvasSurface(document.getElementById('debugger_view'));
+            const player = new Debugger.SkpDebugPlayer();
+
+            fetch('/debugger/sample.skp').then(function(response) {
+                // Load test file
+                if (!response.ok) {
+                  throw new Error("HTTP error, status = " + response.status);
+                }
+                response.arrayBuffer().then(function(buffer) {
+                    let fileContents = new Uint8Array(buffer);
+                    console.log('fetched /debugger/sample.skp');
+                    const size = fileContents.byteLength;
+                    expect(size).toEqual(662976);
+
+                    // Allocate memory in wasm to hold the skp file selected by the user.
+                    const fileMemPtr = Debugger._malloc(size);
+                    // Make a typed array view of that memory
+                    let fileMem = new Uint8Array(Debugger.buffer, fileMemPtr, size);
+                    // Copy the file into it
+                    fileMem.set(fileContents);
+                    // Hand off pointer to wasm
+                    player.loadSkp(fileMemPtr, size);
+                    // Draw picture
+                    player.drawTo(surface, 789); // number of commands in sample file
+                    surface.flush();
+
+                    console.log('drew picture to canvas element');
+                    surface.dispose();
+                    done();
+                });
+              });
+        }));
+    });
+});
diff --git a/fuzz/FuzzCanvas.cpp b/fuzz/FuzzCanvas.cpp
index 7fac837..6134ff1 100644
--- a/fuzz/FuzzCanvas.cpp
+++ b/fuzz/FuzzCanvas.cpp
@@ -9,9 +9,9 @@
 #include "FuzzCommon.h"
 
 // CORE
+#include "DebugCanvas.h"
 #include "SkCanvas.h"
 #include "SkColorFilter.h"
-#include "SkDebugCanvas.h"
 #include "SkFontMgr.h"
 #include "SkImageFilter.h"
 #include "SkMaskFilter.h"
@@ -63,7 +63,7 @@
 #include "SkXfermodeImageFilter.h"
 
 // SRC
-#include "SkCommandLineFlags.h"
+#include "CommandLineFlags.h"
 #include "SkUTF.h"
 
 #if SK_SUPPORT_GPU
@@ -79,7 +79,7 @@
 #include <iostream>
 #include <utility>
 
-DEFINE_bool2(gpuInfo, g, false, "Display GPU information on relevant targets.");
+static DEFINE_bool2(gpuInfo, g, false, "Display GPU information on relevant targets.");
 
 // TODO:
 //   SkTextBlob with Unicode
@@ -115,7 +115,7 @@
             SkBlendMode mode;
             fuzz->next(&color);
             fuzz->nextRange(&mode, 0, SkBlendMode::kLastMode);
-            return SkColorFilter::MakeModeFilter(color, mode);
+            return SkColorFilters::Blend(color, mode);
         }
         case 2: {
             sk_sp<SkColorFilter> outer = make_fuzz_colorfilter(fuzz, depth - 1);
@@ -129,7 +129,7 @@
         case 3: {
             SkScalar array[20];
             fuzz->nextN(array, SK_ARRAY_COUNT(array));
-            return SkColorFilter::MakeMatrixFilterRowMajor255(array);
+            return SkColorFilters::MatrixRowMajor255(array);
         }
         case 4: {
             SkColor mul, add;
@@ -190,7 +190,7 @@
     sk_sp<SkColorFilter> colorFilter(nullptr);
     SkBitmap bitmap;
     sk_sp<SkImage> img;
-    SkShader::TileMode tmX, tmY;
+    SkTileMode tmX, tmY;
     bool useMatrix;
     SkColor color;
     SkMatrix matrix;
@@ -204,14 +204,14 @@
         case 0:
             return nullptr;
         case 1:
-            return SkShader::MakeEmptyShader();
+            return SkShaders::Empty();
         case 2:
             fuzz->next(&color);
-            return SkShader::MakeColorShader(color);
+            return SkShaders::Color(color);
         case 3:
             img = make_fuzz_image(fuzz);
-            fuzz->nextRange(&tmX, 0, SkShader::TileMode::kLast_TileMode);
-            fuzz->nextRange(&tmY, 0, SkShader::TileMode::kLast_TileMode);
+            fuzz->nextRange(&tmX, 0, SkTileMode::kLastTileMode);
+            fuzz->nextRange(&tmY, 0, SkTileMode::kLastTileMode);
             fuzz->next(&useMatrix);
             if (useMatrix) {
                 FuzzNiceMatrix(fuzz, &matrix);
@@ -219,13 +219,13 @@
             return img->makeShader(tmX, tmY, useMatrix ? &matrix : nullptr);
         case 4:
             bitmap = make_fuzz_bitmap(fuzz);
-            fuzz->nextRange(&tmX, 0, SkShader::TileMode::kLast_TileMode);
-            fuzz->nextRange(&tmY, 0, SkShader::TileMode::kLast_TileMode);
+            fuzz->nextRange(&tmX, 0, SkTileMode::kLastTileMode);
+            fuzz->nextRange(&tmY, 0, SkTileMode::kLastTileMode);
             fuzz->next(&useMatrix);
             if (useMatrix) {
                 FuzzNiceMatrix(fuzz, &matrix);
             }
-            return SkShader::MakeBitmapShader(bitmap, tmX, tmY, useMatrix ? &matrix : nullptr);
+            return bitmap.makeShader(tmX, tmY, useMatrix ? &matrix : nullptr);
         case 5:
             shader1 = make_fuzz_shader(fuzz, depth - 1);  // limit recursion.
             FuzzNiceMatrix(fuzz, &matrix);
@@ -238,13 +238,13 @@
             shader1 = make_fuzz_shader(fuzz, depth - 1);  // limit recursion.
             shader2 = make_fuzz_shader(fuzz, depth - 1);
             fuzz->nextRange(&blendMode, 0, SkBlendMode::kLastMode);
-            return SkShader::MakeComposeShader(std::move(shader1), std::move(shader2), blendMode);
+            return SkShaders::Blend(blendMode, std::move(shader1), std::move(shader2));
         case 8: {
             auto pic = make_fuzz_picture(fuzz, depth - 1);
             bool useTile;
             SkRect tile;
-            fuzz->nextRange(&tmX, 0, SkShader::TileMode::kLast_TileMode);
-            fuzz->nextRange(&tmY, 0, SkShader::TileMode::kLast_TileMode);
+            fuzz->nextRange(&tmX, 0, SkTileMode::kLastTileMode);
+            fuzz->nextRange(&tmY, 0, SkTileMode::kLastTileMode);
             fuzz->next(&useMatrix, &useTile);
             if (useMatrix) {
                 FuzzNiceMatrix(fuzz, &matrix);
@@ -252,9 +252,7 @@
             if (useTile) {
                 fuzz->next(&tile);
             }
-            return SkShader::MakePictureShader(std::move(pic), tmX, tmY,
-                                               useMatrix ? &matrix : nullptr,
-                                               useTile ? &tile : nullptr);
+            return pic->makeShader(tmX, tmY, useMatrix ? &matrix : nullptr, useTile ? &tile : nullptr);
         }
         // EFFECTS:
         case 9:
@@ -270,7 +268,7 @@
             fuzz->nextN(pts, 2);
             fuzz->nextRange(&colorCount, 2, kMaxColors);
             fuzz->nextN(colors, colorCount);
-            fuzz->nextRange(&tmX, 0, SkShader::TileMode::kLast_TileMode);
+            fuzz->nextRange(&tmX, 0, SkTileMode::kLastTileMode);
             fuzz->next(&useMatrix, &usePos);
             if (useMatrix) {
                 FuzzNiceMatrix(fuzz, &matrix);
@@ -289,7 +287,7 @@
             bool usePos;
             SkColor colors[kMaxColors];
             SkScalar pos[kMaxColors];
-            fuzz->nextRange(&tmX, 0, SkShader::TileMode::kLast_TileMode);
+            fuzz->nextRange(&tmX, 0, SkTileMode::kLastTileMode);
             fuzz->next(&useMatrix, &usePos, &center, &radius);
             fuzz->nextRange(&colorCount, 2, kMaxColors);
             fuzz->nextN(colors, colorCount);
@@ -310,7 +308,7 @@
             bool usePos;
             SkColor colors[kMaxColors];
             SkScalar pos[kMaxColors];
-            fuzz->nextRange(&tmX, 0, SkShader::TileMode::kLast_TileMode);
+            fuzz->nextRange(&tmX, 0, SkTileMode::kLastTileMode);
             fuzz->next(&useMatrix, &usePos, &startRadius, &endRadius, &start, &end);
             fuzz->nextRange(&colorCount, 2, kMaxColors);
             fuzz->nextN(colors, colorCount);
@@ -1018,20 +1016,11 @@
     return textBlobBuilder.make();
 }
 
-extern std::atomic<bool> gSkUseDeltaAA;
-extern std::atomic<bool> gSkForceDeltaAA;
-
 static void fuzz_canvas(Fuzz* fuzz, SkCanvas* canvas, int depth = 9) {
     if (!fuzz || !canvas || depth <= 0) {
         return;
     }
     SkAutoCanvasRestore autoCanvasRestore(canvas, false);
-    bool useDAA;
-    fuzz->next(&useDAA);
-    if (useDAA) {
-        gSkForceDeltaAA = true;
-        gSkUseDeltaAA = true;
-    }
     unsigned N;
     fuzz->nextRange(&N, 0, 2000);
     for (unsigned i = 0; i < N; ++i) {
@@ -1608,6 +1597,12 @@
 #define SK_ADD_RANDOM_BIT_FLIPS
 
 DEF_FUZZ(SerializedImageFilter, fuzz) {
+    SkBitmap bitmap;
+    if (!bitmap.tryAllocN32Pixels(256, 256)) {
+        SkDEBUGF("Could not allocate 256x256 bitmap in SerializedImageFilter");
+        return;
+    }
+
     auto filter = make_fuzz_imageFilter(fuzz, 20);
     if (!filter) {
         return;
@@ -1657,9 +1652,10 @@
 
     SkPaint paint;
     paint.setImageFilter(deserializedFil);
-    SkBitmap bitmap;
+
     SkCanvas canvas(bitmap);
-    canvas.saveLayer(SkRect::MakeWH(500, 500), &paint);
+    canvas.saveLayer(SkRect::MakeWH(256, 256), &paint);
+    canvas.restore();
 }
 
 #if SK_SUPPORT_GPU
@@ -1700,13 +1696,6 @@
     fuzz_ganesh(fuzz, context);
 }
 
-// This target is deprecated, NullGLContext is not well maintained.
-// Please use MockGPUCanvas instead.
-DEF_FUZZ(NullGLCanvas, fuzz) {
-    sk_gpu_test::GrContextFactory f;
-    fuzz_ganesh(fuzz, f.get(sk_gpu_test::GrContextFactory::kNullGL_ContextType));
-}
-
 DEF_FUZZ(MockGPUCanvas, fuzz) {
     sk_gpu_test::GrContextFactory f;
     fuzz_ganesh(fuzz, f.get(sk_gpu_test::GrContextFactory::kMock_ContextType));
@@ -1722,7 +1711,7 @@
 
 // not a "real" thing to fuzz, used to debug errors found while fuzzing.
 DEF_FUZZ(_DumpCanvas, fuzz) {
-    SkDebugCanvas debugCanvas(kCanvasSize.width(), kCanvasSize.height());
+    DebugCanvas debugCanvas(kCanvasSize.width(), kCanvasSize.height());
     fuzz_canvas(fuzz, &debugCanvas);
     std::unique_ptr<SkCanvas> nullCanvas = SkMakeNullCanvas();
     UrlDataManager dataManager(SkString("data"));
diff --git a/fuzz/FuzzGradients.cpp b/fuzz/FuzzGradients.cpp
index f9d0742..92ff366 100644
--- a/fuzz/FuzzGradients.cpp
+++ b/fuzz/FuzzGradients.cpp
@@ -5,9 +5,9 @@
  * found in the LICENSE file.
  */
 
+#include "CommandLineFlags.h"
 #include "Fuzz.h"
 #include "SkCanvas.h"
-#include "SkCommonFlags.h"
 #include "SkGradientShader.h"
 #include "SkSurface.h"
 #include "SkTLazy.h"
@@ -15,6 +15,8 @@
 #include <algorithm>
 #include <vector>
 
+static DEFINE_bool2(verbose, v, false, "log verbose linear gradient description");
+
 const int MAX_COUNT = 400;
 
 void makeMatrix(Fuzz* fuzz, SkMatrix* m) {
@@ -24,7 +26,7 @@
 }
 
 void initGradientParams(Fuzz* fuzz, std::vector<SkColor>* colors,
-                        std::vector<SkScalar>* pos, SkShader::TileMode* mode) {
+                        std::vector<SkScalar>* pos, SkTileMode* mode) {
     int count;
     fuzz->nextRange(&count, 0, MAX_COUNT);
 
@@ -32,7 +34,7 @@
     // smaller, which leads to more efficient fuzzing.
     uint8_t m;
     fuzz->nextRange(&m, 0, 2);
-    *mode = static_cast<SkShader::TileMode>(m);
+    *mode = static_cast<SkTileMode>(m);
 
     colors->clear();
     pos   ->clear();
@@ -66,7 +68,7 @@
 static void logLinearGradient(const SkPoint pts[2],
                               const std::vector<SkColor>& colors,
                               const std::vector<SkScalar> pos,
-                              SkShader::TileMode mode,
+                              SkTileMode mode,
                               uint32_t flags,
                               const SkMatrix* localMatrix,
                               const SkMatrix* globalMatrix) {
@@ -94,10 +96,10 @@
     SkDebugf("]\n");
 
     static const char* gModeName[] = {
-        "kClamp_TileMode", "kRepeat_TileMode", "kMirror_TileMode"
+        "kClamp_TileMode", "kRepeat_TileMode", "kMirror_TileMode", "kDecal_TileMode"
     };
-    SkASSERT(mode < SK_ARRAY_COUNT(gModeName));
-    SkDebugf("  mode:\t\t%s\n", gModeName[mode]);
+    SkASSERT((unsigned)mode < SK_ARRAY_COUNT(gModeName));
+    SkDebugf("  mode:\t\t%s\n", gModeName[(unsigned)mode]);
     SkDebugf("  flags:\t0x%x\n", flags);
     logOptionalMatrix("local matrix", localMatrix);
     logOptionalMatrix("global matrix", globalMatrix);
@@ -111,7 +113,7 @@
 
     std::vector<SkColor> colors;
     std::vector<SkScalar> pos;
-    SkShader::TileMode mode;
+    SkTileMode mode;
     initGradientParams(fuzz, &colors, &pos, &mode);
 
     SkPaint p;
@@ -149,7 +151,7 @@
 
     std::vector<SkColor> colors;
     std::vector<SkScalar> pos;
-    SkShader::TileMode mode;
+    SkTileMode mode;
     initGradientParams(fuzz, &colors, &pos, &mode);
 
     SkPaint p;
@@ -187,7 +189,7 @@
 
     std::vector<SkColor> colors;
     std::vector<SkScalar> pos;
-    SkShader::TileMode mode;
+    SkTileMode mode;
     initGradientParams(fuzz, &colors, &pos, &mode);
 
     SkPaint p;
@@ -221,7 +223,7 @@
 
     std::vector<SkColor> colors;
     std::vector<SkScalar> pos;
-    SkShader::TileMode mode;
+    SkTileMode mode;
     initGradientParams(fuzz, &colors, &pos, &mode);
 
     SkPaint p;
diff --git a/fuzz/FuzzMain.cpp b/fuzz/FuzzMain.cpp
index 0ba3fc9..1047e5f 100644
--- a/fuzz/FuzzMain.cpp
+++ b/fuzz/FuzzMain.cpp
@@ -5,10 +5,10 @@
  * found in the LICENSE file.
  */
 
+#include "CommandLineFlags.h"
 #include "Fuzz.h"
 #include "SkCanvas.h"
 #include "SkCodec.h"
-#include "SkCommandLineFlags.h"
 #include "SkData.h"
 #include "SkImage.h"
 #include "SkImageEncoder.h"
@@ -23,24 +23,20 @@
 #include "SkSurface.h"
 #include "SkTextBlob.h"
 
-#if SK_SUPPORT_GPU
-#include "SkSLCompiler.h"
-#endif
-
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 #include <iostream>
 #include <map>
 #include <regex>
 #include <signal.h>
 
-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(dump, d, "", "If not empty, dump 'image*' or 'skp' types as a "
-        "PNG with this name.");
+static 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.");
+static DEFINE_string2(name, n, "", "If --type is 'api', fuzz the API with this name.");
+static DEFINE_string2(dump, d, "", "If not empty, dump 'image*' or 'skp' types as a "
+                                   "PNG with this name.");
 DEFINE_bool2(verbose, v, false, "Print more information while fuzzing.");
 
 // This cannot be inlined in DEFINE_string2 due to interleaved ifdefs
@@ -60,12 +56,15 @@
                                          "region_set_path\n"
                                          "skp\n"
                                          "sksl2glsl\n"
+                                         "sksl2metal\n"
+                                         "sksl2pipeline\n"
+                                         "sksl2spirv\n"
 #if defined(SK_ENABLE_SKOTTIE)
                                          "skottie_json\n"
 #endif
                                          "textblob";
 
-DEFINE_string2(type, t, "", g_type_message);
+static DEFINE_string2(type, t, "", g_type_message);
 
 static int fuzz_file(SkString path, SkString type);
 static uint8_t calculate_option(SkData*);
@@ -84,24 +83,25 @@
 static void fuzz_region_deserialize(sk_sp<SkData>);
 static void fuzz_region_set_path(sk_sp<SkData>);
 static void fuzz_skp(sk_sp<SkData>);
+static void fuzz_sksl2glsl(sk_sp<SkData>);
+static void fuzz_sksl2metal(sk_sp<SkData>);
+static void fuzz_sksl2spirv(sk_sp<SkData>);
+static void fuzz_sksl2pipeline(sk_sp<SkData>);
 static void fuzz_textblob_deserialize(sk_sp<SkData>);
 
 static void print_api_names();
 
-#if SK_SUPPORT_GPU
-static void fuzz_sksl2glsl(sk_sp<SkData>);
-#endif
-
 #if defined(SK_ENABLE_SKOTTIE)
 static void fuzz_skottie_json(sk_sp<SkData>);
 #endif
 
 int main(int argc, char** argv) {
-    SkCommandLineFlags::SetUsage("Usage: fuzz -t <type> -b <path/to/file> [-n api-to-fuzz]\n"
-                                 "       fuzz -b <path/to/file>\n"
-                                 "--help lists the valid types. If type is not specified,\n"
-                                 "fuzz will make a guess based on the name of the file.\n");
-    SkCommandLineFlags::Parse(argc, argv);
+    CommandLineFlags::SetUsage(
+            "Usage: fuzz -t <type> -b <path/to/file> [-n api-to-fuzz]\n"
+            "       fuzz -b <path/to/file>\n"
+            "--help lists the valid types. If type is not specified,\n"
+            "fuzz will make a guess based on the name of the file.\n");
+    CommandLineFlags::Parse(argc, argv);
 
     SkString path = SkString(FLAGS_bytes.isEmpty() ? argv[0] : FLAGS_bytes[0]);
     SkString type = SkString(FLAGS_type.isEmpty() ? "" : FLAGS_type[0]);
@@ -207,18 +207,28 @@
         fuzz_skp(bytes);
         return 0;
     }
-    if (type.equals("textblob")) {
-        fuzz_textblob_deserialize(bytes);
-        return 0;
-    }
-#if SK_SUPPORT_GPU
     if (type.equals("sksl2glsl")) {
         fuzz_sksl2glsl(bytes);
         return 0;
     }
-#endif
+    if (type.equals("sksl2metal")) {
+        fuzz_sksl2metal(bytes);
+        return 0;
+    }
+    if (type.equals("sksl2spirv")) {
+        fuzz_sksl2spirv(bytes);
+        return 0;
+    }
+    if (type.equals("sksl2pipeline")) {
+        fuzz_sksl2pipeline(bytes);
+        return 0;
+    }
+    if (type.equals("textblob")) {
+        fuzz_textblob_deserialize(bytes);
+        return 0;
+    }
     SkDebugf("Unknown type %s\n", type.c_str());
-    SkCommandLineFlags::PrintUsage();
+    CommandLineFlags::PrintUsage();
     return 1;
 }
 
@@ -250,6 +260,10 @@
     {"region_deserialize", "region_deserialize"},
     {"region_set_path", "region_set_path"},
     {"skjson", "json"},
+    {"sksl2glsl", "sksl2glsl"},
+    {"sksl2metal", "sksl2metal"},
+    {"sksl2spirv", "sksl2spirv"},
+    {"sksl2pipeline", "sksl2pipeline"},
 #if defined(SK_ENABLE_SKOTTIE)
     {"skottie_json", "skottie_json"},
 #endif
@@ -340,7 +354,7 @@
 
 static void dump_png(SkBitmap bitmap) {
     if (!FLAGS_dump.isEmpty()) {
-        sk_tool_utils::EncodeImageToFile(FLAGS_dump[0], bitmap, SkEncodedImageFormat::kPNG, 100);
+        ToolUtils::EncodeImageToFile(FLAGS_dump[0], bitmap, SkEncodedImageFormat::kPNG, 100);
         SkDebugf("Dumped to %s\n", FLAGS_dump[0]);
     }
 }
@@ -706,20 +720,42 @@
     SkDebugf("[terminated] filter_fuzz didn't crash!\n");
 }
 
-#if SK_SUPPORT_GPU
+bool FuzzSKSL2GLSL(sk_sp<SkData> bytes);
+
 static void fuzz_sksl2glsl(sk_sp<SkData> bytes) {
-    SkSL::Compiler compiler;
-    SkSL::String output;
-    SkSL::Program::Settings settings;
-    sk_sp<GrShaderCaps> caps = SkSL::ShaderCapsFactory::Default();
-    settings.fCaps = caps.get();
-    std::unique_ptr<SkSL::Program> program = compiler.convertProgram(SkSL::Program::kFragment_Kind,
-                                                          SkSL::String((const char*) bytes->data()),
-                                                          settings);
-    if (!program || !compiler.toGLSL(*program, &output)) {
-        SkDebugf("[terminated] Couldn't compile input.\n");
-        return;
+    if (FuzzSKSL2GLSL(bytes)) {
+        SkDebugf("[terminated] Success! Compiled input to GLSL.\n");
+    } else {
+        SkDebugf("[terminated] Could not compile input to GLSL.\n");
     }
-    SkDebugf("[terminated] Success! Compiled input.\n");
 }
-#endif
+
+bool FuzzSKSL2SPIRV(sk_sp<SkData> bytes);
+
+static void fuzz_sksl2spirv(sk_sp<SkData> bytes) {
+    if (FuzzSKSL2SPIRV(bytes)) {
+        SkDebugf("[terminated] Success! Compiled input to SPIRV.\n");
+    } else {
+        SkDebugf("[terminated] Could not compile input to SPIRV.\n");
+    }
+}
+
+bool FuzzSKSL2Metal(sk_sp<SkData> bytes);
+
+static void fuzz_sksl2metal(sk_sp<SkData> bytes) {
+    if (FuzzSKSL2Metal(bytes)) {
+        SkDebugf("[terminated] Success! Compiled input to Metal.\n");
+    } else {
+        SkDebugf("[terminated] Could not compile input to Metal.\n");
+    }
+}
+
+bool FuzzSKSL2Pipeline(sk_sp<SkData> bytes);
+
+static void fuzz_sksl2pipeline(sk_sp<SkData> bytes) {
+    if (FuzzSKSL2Pipeline(bytes)) {
+        SkDebugf("[terminated] Success! Compiled input to pipeline stage.\n");
+    } else {
+        SkDebugf("[terminated] Could not compile input to pipeline stage.\n");
+    }
+}
diff --git a/fuzz/oss_fuzz/FuzzGradients.cpp b/fuzz/oss_fuzz/FuzzGradients.cpp
index 0694802..238abde 100644
--- a/fuzz/oss_fuzz/FuzzGradients.cpp
+++ b/fuzz/oss_fuzz/FuzzGradients.cpp
@@ -9,7 +9,6 @@
 
 void fuzz_Gradients(Fuzz* f);
 
-bool FLAGS_verbose = false;
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
     auto fuzz = Fuzz(SkData::MakeWithoutCopy(data, size));
     fuzz_Gradients(&fuzz);
diff --git a/fuzz/oss_fuzz/FuzzImageFilterDeserialize.cpp b/fuzz/oss_fuzz/FuzzImageFilterDeserialize.cpp
index 7ae7870..cb3e332 100644
--- a/fuzz/oss_fuzz/FuzzImageFilterDeserialize.cpp
+++ b/fuzz/oss_fuzz/FuzzImageFilterDeserialize.cpp
@@ -12,7 +12,7 @@
 #include "SkFontMgrPriv.h"
 #include "SkImageFilter.h"
 #include "SkPaint.h"
-#include "SkTestFontMgr.h"
+#include "TestFontMgr.h"
 
 void FuzzImageFilterDeserialize(sk_sp<SkData> bytes) {
     const int BitmapSize = 24;
@@ -41,7 +41,7 @@
 
 #if defined(IS_FUZZING_WITH_LIBFUZZER)
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
-    gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
+    gSkFontMgr_DefaultFactory = &ToolUtils::MakePortableFontMgr;
     auto bytes = SkData::MakeWithoutCopy(data, size);
     FuzzImageFilterDeserialize(bytes);
     return 0;
diff --git a/fuzz/oss_fuzz/FuzzMockGPUCanvas.cpp b/fuzz/oss_fuzz/FuzzMockGPUCanvas.cpp
index 5a6f389..e65467a 100644
--- a/fuzz/oss_fuzz/FuzzMockGPUCanvas.cpp
+++ b/fuzz/oss_fuzz/FuzzMockGPUCanvas.cpp
@@ -6,8 +6,8 @@
  */
 
 #include "../Fuzz.h"
-#include "SkTestFontMgr.h"
 #include "SkFontMgrPriv.h"
+#include "TestFontMgr.h"
 
 void fuzz_MockGPUCanvas(Fuzz* f);
 
@@ -20,7 +20,7 @@
     }
 
     int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
-        gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
+        gSkFontMgr_DefaultFactory = &ToolUtils::MakePortableFontMgr;
         auto fuzz = Fuzz(SkData::MakeWithoutCopy(data, size));
         fuzz_MockGPUCanvas(&fuzz);
         return 0;
diff --git a/fuzz/oss_fuzz/FuzzNullCanvas.cpp b/fuzz/oss_fuzz/FuzzNullCanvas.cpp
index 2363af8..a90f55a 100644
--- a/fuzz/oss_fuzz/FuzzNullCanvas.cpp
+++ b/fuzz/oss_fuzz/FuzzNullCanvas.cpp
@@ -6,13 +6,13 @@
  */
 
 #include "../Fuzz.h"
-#include "SkTestFontMgr.h"
 #include "SkFontMgrPriv.h"
+#include "TestFontMgr.h"
 
 void fuzz_NullCanvas(Fuzz* f);
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
-    gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
+    gSkFontMgr_DefaultFactory = &ToolUtils::MakePortableFontMgr;
     auto fuzz = Fuzz(SkData::MakeWithoutCopy(data, size));
     fuzz_NullCanvas(&fuzz);
     return 0;
diff --git a/fuzz/oss_fuzz/FuzzRasterN32Canvas.cpp b/fuzz/oss_fuzz/FuzzRasterN32Canvas.cpp
index 8038cf5..ed16131 100644
--- a/fuzz/oss_fuzz/FuzzRasterN32Canvas.cpp
+++ b/fuzz/oss_fuzz/FuzzRasterN32Canvas.cpp
@@ -6,13 +6,13 @@
  */
 
 #include "../Fuzz.h"
-#include "SkTestFontMgr.h"
 #include "SkFontMgrPriv.h"
+#include "TestFontMgr.h"
 
 void fuzz_RasterN32Canvas(Fuzz* f);
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
-    gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
+    gSkFontMgr_DefaultFactory = &ToolUtils::MakePortableFontMgr;
     auto fuzz = Fuzz(SkData::MakeWithoutCopy(data, size));
     fuzz_RasterN32Canvas(&fuzz);
     return 0;
diff --git a/fuzz/oss_fuzz/FuzzSKSL2GLSL.cpp b/fuzz/oss_fuzz/FuzzSKSL2GLSL.cpp
new file mode 100644
index 0000000..9beca53
--- /dev/null
+++ b/fuzz/oss_fuzz/FuzzSKSL2GLSL.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 Google, LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrShaderCaps.h"
+#include "SkSLCompiler.h"
+
+#include "../Fuzz.h"
+
+bool FuzzSKSL2GLSL(sk_sp<SkData> bytes) {
+    SkSL::Compiler compiler;
+    SkSL::String output;
+    SkSL::Program::Settings settings;
+    sk_sp<GrShaderCaps> caps = SkSL::ShaderCapsFactory::Default();
+    settings.fCaps = caps.get();
+    std::unique_ptr<SkSL::Program> program = compiler.convertProgram(
+                                                    SkSL::Program::kFragment_Kind,
+                                                    SkSL::String((const char*) bytes->data(),
+                                                                 bytes->size()),
+                                                    settings);
+    if (!program || !compiler.toGLSL(*program, &output)) {
+        return false;
+    }
+    return true;
+}
+
+#if defined(IS_FUZZING_WITH_LIBFUZZER)
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    auto bytes = SkData::MakeWithoutCopy(data, size);
+    FuzzSKSL2GLSL(bytes);
+    return 0;
+}
+#endif
diff --git a/fuzz/oss_fuzz/FuzzSKSL2Metal.cpp b/fuzz/oss_fuzz/FuzzSKSL2Metal.cpp
new file mode 100644
index 0000000..0f4ed1e
--- /dev/null
+++ b/fuzz/oss_fuzz/FuzzSKSL2Metal.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 Google, LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrShaderCaps.h"
+#include "SkSLCompiler.h"
+
+#include "../Fuzz.h"
+
+bool FuzzSKSL2Metal(sk_sp<SkData> bytes) {
+    SkSL::Compiler compiler;
+    SkSL::String output;
+    SkSL::Program::Settings settings;
+    sk_sp<GrShaderCaps> caps = SkSL::ShaderCapsFactory::Default();
+    settings.fCaps = caps.get();
+    std::unique_ptr<SkSL::Program> program = compiler.convertProgram(
+                                                    SkSL::Program::kFragment_Kind,
+                                                    SkSL::String((const char*) bytes->data(),
+                                                                 bytes->size()),
+                                                    settings);
+    if (!program || !compiler.toMetal(*program, &output)) {
+        return false;
+    }
+    return true;
+}
+
+#if defined(IS_FUZZING_WITH_LIBFUZZER)
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    auto bytes = SkData::MakeWithoutCopy(data, size);
+    FuzzSKSL2Metal(bytes);
+    return 0;
+}
+#endif
diff --git a/fuzz/oss_fuzz/FuzzSKSL2Pipeline.cpp b/fuzz/oss_fuzz/FuzzSKSL2Pipeline.cpp
new file mode 100644
index 0000000..cc980f7
--- /dev/null
+++ b/fuzz/oss_fuzz/FuzzSKSL2Pipeline.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019 Google, LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrShaderCaps.h"
+#include "SkSLCompiler.h"
+
+#include "../Fuzz.h"
+
+bool FuzzSKSL2Pipeline(sk_sp<SkData> bytes) {
+    SkSL::Compiler compiler;
+    SkSL::String output;
+    SkSL::Program::Settings settings;
+    sk_sp<GrShaderCaps> caps = SkSL::ShaderCapsFactory::Default();
+    settings.fCaps = caps.get();
+    std::unique_ptr<SkSL::Program> program = compiler.convertProgram(
+                                                    SkSL::Program::kPipelineStage_Kind,
+                                                    SkSL::String((const char*) bytes->data(),
+                                                                 bytes->size()),
+                                                    settings);
+    std::vector<SkSL::Compiler::FormatArg> formatArgs;
+    if (!program || !compiler.toPipelineStage(*program, &output, &formatArgs)) {
+        return false;
+    }
+    return true;
+}
+
+#if defined(IS_FUZZING_WITH_LIBFUZZER)
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    auto bytes = SkData::MakeWithoutCopy(data, size);
+    FuzzSKSL2Pipeline(bytes);
+    return 0;
+}
+#endif
diff --git a/fuzz/oss_fuzz/FuzzSKSL2SPIRV.cpp b/fuzz/oss_fuzz/FuzzSKSL2SPIRV.cpp
new file mode 100644
index 0000000..59c4c71
--- /dev/null
+++ b/fuzz/oss_fuzz/FuzzSKSL2SPIRV.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 Google, LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrShaderCaps.h"
+#include "SkSLCompiler.h"
+
+#include "../Fuzz.h"
+
+bool FuzzSKSL2SPIRV(sk_sp<SkData> bytes) {
+    SkSL::Compiler compiler;
+    SkSL::String output;
+    SkSL::Program::Settings settings;
+    sk_sp<GrShaderCaps> caps = SkSL::ShaderCapsFactory::Default();
+    settings.fCaps = caps.get();
+    std::unique_ptr<SkSL::Program> program = compiler.convertProgram(
+                                                    SkSL::Program::kFragment_Kind,
+                                                    SkSL::String((const char*) bytes->data(),
+                                                                 bytes->size()),
+                                                    settings);
+    if (!program || !compiler.toSPIRV(*program, &output)) {
+        return false;
+    }
+    return true;
+}
+
+#if defined(IS_FUZZING_WITH_LIBFUZZER)
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    auto bytes = SkData::MakeWithoutCopy(data, size);
+    FuzzSKSL2SPIRV(bytes);
+    return 0;
+}
+#endif
diff --git a/fuzz/oss_fuzz/FuzzTextBlobDeserialize.cpp b/fuzz/oss_fuzz/FuzzTextBlobDeserialize.cpp
index 6b82c79..6cd0ca9 100644
--- a/fuzz/oss_fuzz/FuzzTextBlobDeserialize.cpp
+++ b/fuzz/oss_fuzz/FuzzTextBlobDeserialize.cpp
@@ -10,8 +10,8 @@
 #include "SkPaint.h"
 #include "SkReadBuffer.h"
 #include "SkSurface.h"
-#include "SkTestFontMgr.h"
 #include "SkTextBlobPriv.h"
+#include "TestFontMgr.h"
 
 void FuzzTextBlobDeserialize(SkReadBuffer& buf) {
     auto tb = SkTextBlobPriv::MakeFromBuffer(buf);
@@ -29,7 +29,7 @@
 
 #if defined(IS_FUZZING_WITH_LIBFUZZER)
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
-    gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
+    gSkFontMgr_DefaultFactory = &ToolUtils::MakePortableFontMgr;
     SkReadBuffer buf(data, size);
     FuzzTextBlobDeserialize(buf);
     return 0;
diff --git a/gm/3dgm.cpp b/gm/3dgm.cpp
index 9184a76..e530d09 100644
--- a/gm/3dgm.cpp
+++ b/gm/3dgm.cpp
@@ -14,7 +14,7 @@
 
 #ifdef SK_ENABLE_SKOTTIE
 
-#include "SkAnimTimer.h"
+#include "AnimTimer.h"
 #include "Resources.h"
 #include "SkStream.h"
 #include "Skottie.h"
@@ -101,10 +101,10 @@
         proc(0xC0FF0000, m4 * tmp);
     }
 
-    void onDraw(SkCanvas* canvas) override {
+    DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
         if (!fAnim) {
-            DrawFailureMessage(canvas, "No animation.");
-            return;
+            *errorMsg = "No animation.";
+            return DrawResult::kFail;
         }
         SkMatrix44  camera,
                     perspective,
@@ -175,13 +175,14 @@
 
         fAnim->seek(fAnimT);
         draw_skia(canvas, mv, viewport, fAnim.get());
+        return DrawResult::kOk;
     }
 
     SkISize onISize() override { return { 1024, 768 }; }
 
     SkString onShortName() override { return SkString("3dgm"); }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         if (!fAnim) {
             return false;
         }
diff --git a/gm/aaa.cpp b/gm/aaa.cpp
index e503eaf..2ed5dc8 100644
--- a/gm/aaa.cpp
+++ b/gm/aaa.cpp
@@ -100,9 +100,7 @@
         path.moveTo(C + R, C);
         for (int i = 1; i < 8; ++i) {
             SkScalar a = 2.6927937f * i;
-            SkScalar cosine;
-            SkScalar sine = SkScalarSinCos(a, &cosine);
-            path.lineTo(C + R * cosine, C + R * sine);
+            path.lineTo(C + R * SkScalarCos(a), C + R * SkScalarSin(a));
         }
         canvas->drawPath(path, p);
         canvas->restore();
diff --git a/gm/aaclip.cpp b/gm/aaclip.cpp
index e475c74..0260f27 100644
--- a/gm/aaclip.cpp
+++ b/gm/aaclip.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvasPriv.h"
-#include "SkPath.h"
 #include "SkMakeUnique.h"
+#include "SkPath.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static void do_draw(SkCanvas* canvas, const SkRect& r) {
     SkPaint paint;
@@ -257,7 +257,7 @@
 
         SkRect r = SkRect::MakeXYWH(0, H/4, W, H/2);
         SkPaint paint;
-        paint.setColor(sk_tool_utils::color_to_565(0xFF8888FF));
+        paint.setColor(ToolUtils::color_to_565(0xFF8888FF));
 
         canvas->drawRect(r, paint);
         this->doDraw(canvas, path);
diff --git a/gm/aarectmodes.cpp b/gm/aarectmodes.cpp
index b9fa25d..9a39d043 100644
--- a/gm/aarectmodes.cpp
+++ b/gm/aarectmodes.cpp
@@ -111,8 +111,7 @@
                                                              0xCF, 0xCE);
 
     const SkMatrix m = SkMatrix::MakeScale(SkIntToScalar(6), SkIntToScalar(6));
-    return SkShader::MakeBitmapShader(bm, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode,
-                                      &m);
+    return bm.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &m);
 }
 
 DEF_SIMPLE_GM(aarectmodes, canvas, 640, 480) {
diff --git a/gm/aaxfermodes.cpp b/gm/aaxfermodes.cpp
index cbf8db4..58b4c44 100644
--- a/gm/aaxfermodes.cpp
+++ b/gm/aaxfermodes.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkColorPriv.h"
 #include "SkPath.h"
 #include "SkShader.h"
 #include "SkTextUtils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 enum {
     kXfermodeCount = (int)SkBlendMode::kLastMode + 1 + 1,   // extra for arith
@@ -68,7 +68,7 @@
     }
 
     void onOnceBeforeDraw() override {
-        fLabelFont.setTypeface(sk_tool_utils::create_portable_typeface());
+        fLabelFont.setTypeface(ToolUtils::create_portable_typeface());
         fLabelFont.setSize(5 * kShapeSize/8);
         fLabelFont.setSubpixel(true);
 
@@ -141,8 +141,7 @@
                             canvas->save();
                             canvas->clipRect(clipRect);
                             if (kCheckerboard_Pass == drawingPass) {
-                                sk_tool_utils::draw_checkerboard(canvas, 0xffffffff, 0xffc6c3c6,
-                                        10);
+                                ToolUtils::draw_checkerboard(canvas, 0xffffffff, 0xffc6c3c6, 10);
                             } else {
                                 SkASSERT(kBackground_Pass == drawingPass);
                                 canvas->drawColor(kBGColor, SkBlendMode::kSrc);
diff --git a/gm/addarc.cpp b/gm/addarc.cpp
index cfb8fed..c9b7633 100644
--- a/gm/addarc.cpp
+++ b/gm/addarc.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
-#include "SkAnimTimer.h"
+#include "AnimTimer.h"
 #include "SkCanvas.h"
 #include "SkPathMeasure.h"
 #include "SkRandom.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 class AddArcGM : public skiagm::GM {
 public:
@@ -37,7 +37,7 @@
 
         SkScalar sign = 1;
         while (r.width() > paint.getStrokeWidth() * 3) {
-            paint.setColor(sk_tool_utils::color_to_565(rand.nextU() | (0xFF << 24)));
+            paint.setColor(ToolUtils::color_to_565(rand.nextU() | (0xFF << 24)));
             SkScalar startAngle = rand.nextUScalar1() * 360;
 
             SkScalar speed = SkScalarSqrt(16 / r.width()) * 0.5f;
@@ -52,7 +52,7 @@
         }
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         fRotate = timer.scaled(1, 360);
         return true;
     }
@@ -131,14 +131,14 @@
             SkAutoCanvasRestore acr(canvas, true);
             canvas->rotate(fRotate * sign);
 
-            paint.setColor(sk_tool_utils::color_to_565(rand.nextU() | (0xFF << 24)));
+            paint.setColor(ToolUtils::color_to_565(rand.nextU() | (0xFF << 24)));
             canvas->drawOval(r, paint);
             r.inset(delta, delta);
             sign = -sign;
         }
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         fRotate = timer.scaled(60, 360);
         return true;
     }
@@ -184,14 +184,14 @@
         while (r.width() > strokeWidth * 2) {
             SkAutoCanvasRestore acr(canvas, true);
             canvas->rotate(fRotate * sign);
-            paint.setColor(sk_tool_utils::color_to_565(rand.nextU() | (0xFF << 24)));
+            paint.setColor(ToolUtils::color_to_565(rand.nextU() | (0xFF << 24)));
             canvas->drawOval(r, paint);
             r.inset(delta, delta);
             sign = -sign;
         }
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         fRotate = timer.scaled(60, 360);
         return true;
     }
diff --git a/gm/all_bitmap_configs.cpp b/gm/all_bitmap_configs.cpp
index db3297b..4435942 100644
--- a/gm/all_bitmap_configs.cpp
+++ b/gm/all_bitmap_configs.cpp
@@ -5,10 +5,10 @@
  * found in the LICENSE file.
  */
 
-#include "SkSurface.h"
 #include "Resources.h"
+#include "SkSurface.h"
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkColorPriv.h"
 #include "SkFont.h"
@@ -23,7 +23,7 @@
     }
 
     SkBitmap copy;
-    sk_tool_utils::copy_to(&copy, colorType, *srcPtr);
+    ToolUtils::copy_to(&copy, colorType, *srcPtr);
     copy.setImmutable();
     return copy;
 }
@@ -83,7 +83,7 @@
 
     SkFont font;
     font.setEdging(SkFont::Edging::kAlias);
-    font.setTypeface(sk_tool_utils::create_portable_typeface(nullptr, SkFontStyle::Bold()));
+    font.setTypeface(ToolUtils::create_portable_typeface(nullptr, SkFontStyle::Bold()));
     font.setSize(0.28125f * SCALE);
     draw_center_letter('K', font, SK_ColorBLACK, Z, Z, canvas);
     draw_center_letter('R', font, SK_ColorRED, Z, D, canvas);
@@ -121,9 +121,9 @@
     p.setColor(SK_ColorBLACK);
     p.setAntiAlias(true);
 
-    SkFont font(sk_tool_utils::create_portable_typeface());
+    SkFont font(ToolUtils::create_portable_typeface());
 
-    sk_tool_utils::draw_checkerboard(canvas, SK_ColorLTGRAY, SK_ColorWHITE, 8);
+    ToolUtils::draw_checkerboard(canvas, SK_ColorLTGRAY, SK_ColorWHITE, 8);
 
     SkBitmap bitmap;
     if (GetResourceAsBitmap("images/color_wheel.png", &bitmap)) {
@@ -164,14 +164,13 @@
     n32bitmap.eraseColor(SK_ColorTRANSPARENT);
     SkCanvas n32canvas(n32bitmap);
     color_wheel_native(&n32canvas);
-    n32canvas.flush();
     #if SK_PMCOLOR_BYTE_ORDER(B,G,R,A)
         const SkColorType ct = kRGBA_8888_SkColorType;
     #elif SK_PMCOLOR_BYTE_ORDER(R,G,B,A)
         const SkColorType ct = kBGRA_8888_SkColorType;
     #endif
     static_assert(ct != kN32_SkColorType, "BRGA!=RGBA");
-    SkAssertResult(sk_tool_utils::copy_to(&notN32bitmap, ct, n32bitmap));
+    SkAssertResult(ToolUtils::copy_to(&notN32bitmap, ct, n32bitmap));
     SkASSERT(notN32bitmap.colorType() == ct);
     return SkImage::MakeFromBitmap(notN32bitmap);
 }
@@ -179,7 +178,7 @@
 DEF_SIMPLE_GM(not_native32_bitmap_config, canvas, SCALE, SCALE) {
     sk_sp<SkImage> notN32image(make_not_native32_color_wheel());
     SkASSERT(notN32image);
-    sk_tool_utils::draw_checkerboard(canvas, SK_ColorLTGRAY, SK_ColorWHITE, 8);
+    ToolUtils::draw_checkerboard(canvas, SK_ColorLTGRAY, SK_ColorWHITE, 8);
     canvas->drawImage(notN32image.get(), 0.0f, 0.0f);
 }
 
@@ -229,7 +228,7 @@
 }
 
 DEF_SIMPLE_GM(all_variants_8888, canvas, 4 * SCALE + 30, 2 * SCALE + 10) {
-    sk_tool_utils::draw_checkerboard(canvas, SK_ColorLTGRAY, SK_ColorWHITE, 8);
+    ToolUtils::draw_checkerboard(canvas, SK_ColorLTGRAY, SK_ColorWHITE, 8);
 
     sk_sp<SkColorSpace> colorSpaces[] {
         SkColorSpace::MakeSRGB(),
diff --git a/gm/alpha_image.cpp b/gm/alpha_image.cpp
index 06fb3f6..8765726 100644
--- a/gm/alpha_image.cpp
+++ b/gm/alpha_image.cpp
@@ -31,7 +31,7 @@
         0, 1, 0,   0,   0,
         0, 0, 0.5, 0.5, 0,
         0, 0, 0.5, 0.5, 0}; // mix G and A.
-    return SkColorFilter::MakeMatrixFilterRowMajor255(colorMatrix);
+    return SkColorFilters::MatrixRowMajor255(colorMatrix);
 }
 
 DEF_SIMPLE_GM(alpha_image, canvas, 256, 256) {
@@ -43,7 +43,7 @@
     canvas->drawImage(image.get(), 16, 16, &paint);
 
     paint.setColorFilter(nullptr);
-    paint.setShader(SkShader::MakeColorShader(SK_ColorCYAN));
+    paint.setShader(SkShaders::Color(SK_ColorCYAN));
     canvas->drawImage(image.get(), 144, 16, &paint);
 
     paint.setColorFilter(make_color_filter());
diff --git a/gm/alphagradients.cpp b/gm/alphagradients.cpp
index fa9d3e2..f739f02 100644
--- a/gm/alphagradients.cpp
+++ b/gm/alphagradients.cpp
@@ -29,7 +29,7 @@
         SkPaint paint;
         uint32_t flags = doPreMul ? SkGradientShader::kInterpolateColorsInPremul_Flag : 0;
         paint.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, 2,
-                                                     SkShader::kClamp_TileMode, flags, nullptr));
+                                                     SkTileMode::kClamp, flags, nullptr));
         canvas->drawRect(r, paint);
 
         paint.setShader(nullptr);
diff --git a/gm/analytic_gradients.cpp b/gm/analytic_gradients.cpp
index df84b19..aeb17f3 100644
--- a/gm/analytic_gradients.cpp
+++ b/gm/analytic_gradients.cpp
@@ -162,7 +162,7 @@
                                 colors.get(),
                                 positions.get(),
                                 colorCount,
-                                SkShader::kClamp_TileMode,
+                                SkTileMode::kClamp,
                                 0,
                                 nullptr);
 
diff --git a/gm/androidblendmodes.cpp b/gm/androidblendmodes.cpp
index 1f1500f..0665ddf 100644
--- a/gm/androidblendmodes.cpp
+++ b/gm/androidblendmodes.cpp
@@ -5,10 +5,10 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkBitmap.h"
 #include "SkTextUtils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -36,7 +36,7 @@
             tmp.clear(SK_ColorTRANSPARENT);
             SkPaint p;
             p.setAntiAlias(true);
-            p.setColor(sk_tool_utils::color_to_565(kBlue));
+            p.setColor(ToolUtils::color_to_565(kBlue));
             tmp.drawRect(SkRect::MakeLTRB(16, 96, 160, 240), p);
         }
 
@@ -46,7 +46,7 @@
             tmp.clear(SK_ColorTRANSPARENT);
             SkPaint p;
             p.setAntiAlias(true);
-            p.setColor(sk_tool_utils::color_to_565(kRed));
+            p.setColor(ToolUtils::color_to_565(kRed));
             tmp.drawCircle(160, 95, 80, p);
         }
     }
@@ -65,12 +65,9 @@
     }
 
     void onDraw(SkCanvas* canvas) override {
-        SkFont font(sk_tool_utils::create_portable_typeface());
+        SkFont font(ToolUtils::create_portable_typeface());
 
-        sk_tool_utils::draw_checkerboard(canvas,
-                                         kWhite,
-                                         kGrey,
-                                         32);
+        ToolUtils::draw_checkerboard(canvas, kWhite, kGrey, 32);
 
         int xOffset = 0, yOffset = 0;
 
diff --git a/gm/animatedGif.cpp b/gm/animatedGif.cpp
index 7074fa8..77ef54b 100644
--- a/gm/animatedGif.cpp
+++ b/gm/animatedGif.cpp
@@ -5,21 +5,21 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
-#include "SkAnimTimer.h"
+#include "AnimTimer.h"
+#include "CommandLineFlags.h"
+#include "Resources.h"
 #include "SkCanvas.h"
 #include "SkCodec.h"
 #include "SkColor.h"
-#include "SkCommandLineFlags.h"
 #include "SkFont.h"
 #include "SkPaint.h"
 #include "SkString.h"
-#include "Resources.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #include <vector>
 
-DEFINE_string(animatedGif, "images/test640x479.gif", "Animated gif in resources folder");
+static DEFINE_string(animatedGif, "images/test640x479.gif", "Animated gif in resources folder");
 
 class AnimatedGifGM : public skiagm::GM {
 private:
@@ -49,7 +49,7 @@
                 SkBitmap& requiredBitmap = fFrames[requiredFrame];
                 // For simplicity, do not try to cache old frames
                 if (requiredBitmap.getPixels() &&
-                        sk_tool_utils::copy_to(&bm, requiredBitmap.colorType(), requiredBitmap)) {
+                    ToolUtils::copy_to(&bm, requiredBitmap.colorType(), requiredBitmap)) {
                     opts.fPriorFrame = requiredFrame;
                 }
             }
@@ -112,10 +112,10 @@
         return true;
     }
 
-    void onDraw(SkCanvas* canvas) override {
+    DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
         if (!this->initCodec()) {
-            DrawFailureMessage(canvas, "Could not create codec from %s", FLAGS_animatedGif[0]);
-            return;
+            errorMsg->printf("Could not create codec from %s", FLAGS_animatedGif[0]);
+            return DrawResult::kFail;
         }
 
         canvas->save();
@@ -128,9 +128,10 @@
         SkAutoCanvasRestore acr(canvas, true);
         canvas->translate(0, SkIntToScalar(fCodec->getInfo().height()));
         this->drawFrame(canvas, fFrame);
+        return DrawResult::kOk;
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         if (!fCodec || fTotalFrames == 1) {
             return false;
         }
@@ -213,7 +214,7 @@
         }
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         if (fBaseMSec == 0) {
             fBaseMSec = timer.msec();
         }
diff --git a/gm/animatedimageblurs.cpp b/gm/animatedimageblurs.cpp
index 1c2d173..e08f1d0 100644
--- a/gm/animatedimageblurs.cpp
+++ b/gm/animatedimageblurs.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "SkAnimTimer.h"
+#include "AnimTimer.h"
 #include "SkBlurImageFilter.h"
-#include "SkRandom.h"
 #include "SkRRect.h"
+#include "SkRandom.h"
+#include "gm.h"
 
 static const SkScalar kBlurMax = 7.0f;
 static const int kNumNodes = 30;
@@ -59,7 +59,7 @@
         }
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         if (0.0f != fLastTime) {
             for (int i = 0; i < kNumNodes; ++i) {
                 fNodes[i].update(timer, fLastTime);
@@ -96,8 +96,7 @@
             fSpeed = rand->nextRangeF(20.0f, 60.0f);
         }
 
-        void update(const SkAnimTimer& timer, SkScalar lastTime) {
-
+        void update(const AnimTimer& timer, SkScalar lastTime) {
             SkScalar deltaTime = timer.secs() - lastTime;
 
             fPos.fX += deltaTime * fSpeed * fDir.fX;
diff --git a/gm/anisotropic.cpp b/gm/anisotropic.cpp
index 7a874b2..ff88121 100644
--- a/gm/anisotropic.cpp
+++ b/gm/anisotropic.cpp
@@ -44,7 +44,8 @@
 
         canvas.translate(kImageSize/2.0f, kImageSize/2.0f);
         for (int i = 0; i < kNumLines; ++i, angle += kAngleStep) {
-            sin = SkScalarSinCos(angle, &cos);
+            sin = SkScalarSin(angle);
+            cos = SkScalarCos(angle);
             canvas.drawLine(cos * kInnerOffset, sin * kInnerOffset,
                             cos * kImageSize/2, sin * kImageSize/2, p);
         }
diff --git a/gm/arithmode.cpp b/gm/arithmode.cpp
index adf71ce..7ab5044 100644
--- a/gm/arithmode.cpp
+++ b/gm/arithmode.cpp
@@ -6,8 +6,6 @@
  */
 
 #include <SkFont.h>
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkArithmeticImageFilter.h"
 #include "SkCanvas.h"
 #include "SkColorPriv.h"
@@ -16,6 +14,8 @@
 #include "SkImageSource.h"
 #include "SkShader.h"
 #include "SkSurface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #define WW  100
 #define HH  32
@@ -31,7 +31,7 @@
         SK_ColorRED, SK_ColorMAGENTA, SK_ColorWHITE,
     };
     paint.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, SK_ARRAY_COUNT(colors),
-                                                 SkShader::kClamp_TileMode));
+                                                 SkTileMode::kClamp));
     canvas->drawPaint(paint);
     return surface->makeImageSnapshot();
 }
@@ -47,13 +47,13 @@
         SK_ColorGRAY,
     };
     paint.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, SK_ARRAY_COUNT(colors),
-                                                 SkShader::kClamp_TileMode));
+                                                 SkTileMode::kClamp));
     canvas->drawPaint(paint);
     return surface->makeImageSnapshot();
 }
 
 static void show_k_text(SkCanvas* canvas, SkScalar x, SkScalar y, const SkScalar k[]) {
-    SkFont font(sk_tool_utils::create_portable_typeface(), 24);
+    SkFont font(ToolUtils::create_portable_typeface(), 24);
     font.setEdging(SkFont::Edging::kAntiAlias);
     SkPaint paint;
     paint.setAntiAlias(true);
@@ -149,7 +149,7 @@
                 canvas->translate(gap, 0);
 
                 // Label
-                SkFont font(sk_tool_utils::create_portable_typeface(), 24);
+                SkFont   font(ToolUtils::create_portable_typeface(), 24);
                 SkString str(enforcePMColor ? "enforcePM" : "no enforcePM");
                 canvas->drawString(str, 0, font.getSize(), font, SkPaint());
             }
diff --git a/gm/atlastext.cpp b/gm/atlastext.cpp
index a7e95b4..328f398 100644
--- a/gm/atlastext.cpp
+++ b/gm/atlastext.cpp
@@ -18,10 +18,10 @@
 #include "SkFont.h"
 #include "SkTypeface.h"
 #include "SkUTF.h"
+#include "ToolUtils.h"
 #include "gpu/TestContext.h"
 #include "gpu/atlastext/GLTestAtlasTextRenderer.h"
 #include "gpu/atlastext/TestAtlasTextRenderer.h"
-#include "sk_tool_utils.h"
 
 // GM that draws text using the Atlas Text interface offscreen and then blits that to the canvas.
 
@@ -54,7 +54,7 @@
     return positions[cnt - 1].fX + widths[cnt - 1] - positions[0].fX;
 }
 
-class AtlasTextGM : public skiagm::GM {
+class AtlasTextGM : public skiagm::GpuGM {
 public:
     AtlasTextGM() = default;
 
@@ -76,26 +76,32 @@
 
         fTarget = SkAtlasTextTarget::Make(fContext, kSize, kSize, targetHandle);
 
-        fTypefaces[0] = sk_tool_utils::create_portable_typeface("serif", SkFontStyle::Italic());
-        fTypefaces[1] =
-                sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle::Italic());
-        fTypefaces[2] = sk_tool_utils::create_portable_typeface("serif", SkFontStyle::Normal());
-        fTypefaces[3] =
-                sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle::Normal());
-        fTypefaces[4] = sk_tool_utils::create_portable_typeface("serif", SkFontStyle::Bold());
-        fTypefaces[5] = sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle::Bold());
+        fTypefaces[0] = ToolUtils::create_portable_typeface("serif", SkFontStyle::Italic());
+        fTypefaces[1] = ToolUtils::create_portable_typeface("sans-serif", SkFontStyle::Italic());
+        fTypefaces[2] = ToolUtils::create_portable_typeface("serif", SkFontStyle::Normal());
+        fTypefaces[3] = ToolUtils::create_portable_typeface("sans-serif", SkFontStyle::Normal());
+        fTypefaces[4] = ToolUtils::create_portable_typeface("serif", SkFontStyle::Bold());
+        fTypefaces[5] = ToolUtils::create_portable_typeface("sans-serif", SkFontStyle::Bold());
     }
 
-    void onDraw(SkCanvas* canvas) override {
-        if (!fRenderer || !fTarget || !fTarget->handle()) {
-            DrawFailureMessage(canvas, "No renderer and/or target.");
-            return;
+    DrawResult onDraw(GrContext*,
+                      GrRenderTargetContext*,
+                      SkCanvas* canvas,
+                      SkString* errorMsg) override {
+        if (!fRenderer) {
+            *errorMsg = "No renderer... probably not supported.";
+            return DrawResult::kSkip;
+        }
+        if (!fTarget || !fTarget->handle()) {
+            *errorMsg = "No target... we can't continue.";
+            return DrawResult::kFail;
         }
         fRenderer->clearTarget(fTarget->handle(), 0xFF808080);
         auto bmp = this->drawText();
         SkPaint paint;
         paint.setBlendMode(SkBlendMode::kSrc);
         canvas->drawBitmap(bmp, 0, 0);
+        return DrawResult::kOk;
     }
 
 private:
diff --git a/gm/backdrop.cpp b/gm/backdrop.cpp
new file mode 100644
index 0000000..0b248b2
--- /dev/null
+++ b/gm/backdrop.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkBlurImageFilter.h"
+#include "SkGradientShader.h"
+#include "SkLiteDL.h"
+#include "SkLiteRecorder.h"
+#include "SkPictureRecorder.h"
+
+// Make a noisy (with hard-edges) background, so we can see the effect of the blur
+//
+static sk_sp<SkShader> make_shader(SkScalar cx, SkScalar cy, SkScalar rad) {
+    const SkColor colors[] = {
+        SK_ColorRED, SK_ColorRED, SK_ColorBLUE, SK_ColorBLUE, SK_ColorGREEN, SK_ColorGREEN,
+        SK_ColorRED, SK_ColorRED, SK_ColorBLUE, SK_ColorBLUE, SK_ColorGREEN, SK_ColorGREEN,
+    };
+    constexpr int count = SK_ARRAY_COUNT(colors);
+    SkScalar pos[count] = { 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6 };
+    for (int i = 0; i < count; ++i) {
+        pos[i] *= 1.0f/6;
+    }
+    return SkGradientShader::MakeSweep(cx, cy, colors, pos, count);
+}
+
+static void do_draw(SkCanvas* canvas, bool useClip, bool useHintRect) {
+    SkAutoCanvasRestore acr(canvas, true);
+    canvas->clipRect({0, 0, 256, 256});
+
+    const SkScalar cx = 128, cy = 128, rad = 100;
+    SkPaint p;
+    p.setShader(make_shader(cx, cy, rad));
+    p.setAntiAlias(true);
+    canvas->drawCircle(cx, cy, rad, p);
+
+    // now setup a saveLayer that will pull in the backdrop and blur it
+    //
+    const SkRect r = {cx-50, cy-50, cx+50, cy+50};
+    const SkRect* drawrptr = useHintRect ? &r : nullptr;
+    const SkScalar sigma = 10;
+    if (useClip) {
+        canvas->clipRect(r);
+    }
+    auto blur = SkBlurImageFilter::Make(sigma, sigma, nullptr);
+    auto rec = SkCanvas::SaveLayerRec(drawrptr, nullptr, blur.get(), 0);
+    canvas->saveLayer(rec);
+        // draw something inside, just to demonstrate that we don't blur the new contents,
+        // just the backdrop.
+        p.setColor(SK_ColorYELLOW);
+        p.setShader(nullptr);
+        canvas->drawCircle(cx, cy, 30, p);
+    canvas->restore();
+}
+
+/*
+ *  Draws a 3x4 grid of sweep circles.
+ *  - for a given row, each col should be identical (canvas, picture, litedl)
+ *  - row:0     no-hint-rect    no-clip-rect        expect big blur (except inner circle)
+ *  - row:1     no-hint-rect    clip-rect           expect small blur (except inner circle)
+ *  - row:2     hint-rect       no-clip-rect        expect big blur (except inner circle)
+ *  - row:3     hint-rect       clip-rect           expect small blur (except inner circle)
+ *
+ *  The test is that backdrop effects should be independent of the hint-rect, but should
+ *  respect the clip-rect.
+ */
+DEF_SIMPLE_GM(backdrop_hintrect_clipping, canvas, 768, 1024) {
+    for (bool useHintRect : {false, true}) {
+        for (bool useClip : {false, true}) {
+            SkAutoCanvasRestore acr(canvas, true);
+
+            do_draw(canvas, useClip, useHintRect);
+
+            SkPictureRecorder rec;
+            do_draw(rec.beginRecording(256, 256), useClip, useHintRect);
+            canvas->translate(256, 0);
+            canvas->drawPicture(rec.finishRecordingAsPicture());
+
+            SkLiteDL dl;
+            SkLiteRecorder lite;
+            lite.reset(&dl, {0, 0, 256, 256});
+            do_draw(&lite, useClip, useHintRect);
+            canvas->translate(256, 0);
+            dl.draw(canvas);
+
+            acr.restore();
+            canvas->translate(0, 256);
+        }
+    }
+}
diff --git a/gm/badpaint.cpp b/gm/badpaint.cpp
index 85eaa1e..c0e5317 100644
--- a/gm/badpaint.cpp
+++ b/gm/badpaint.cpp
@@ -9,7 +9,6 @@
 #include "SkCanvas.h"
 #include "SkShader.h"
 
-
 /** This GM draws with invalid paints. It should draw nothing other than the background. */
 class BadPaintGM : public skiagm::GM {
  public:
@@ -32,13 +31,11 @@
 
         // Empty bitmap.
         fPaints.push_back().setColor(SK_ColorGREEN);
-        fPaints.back().setShader(SkShader::MakeBitmapShader(emptyBmp, SkShader::kClamp_TileMode,
-                                                            SkShader::kClamp_TileMode));
+        fPaints.back().setShader(emptyBmp.makeShader());
 
         // Non-invertible local matrix.
         fPaints.push_back().setColor(SK_ColorGREEN);
-        fPaints.back().setShader(SkShader::MakeBitmapShader(blueBmp, SkShader::kClamp_TileMode,
-                                                            SkShader::kClamp_TileMode, &badMatrix));
+        fPaints.back().setShader(blueBmp.makeShader(&badMatrix));
     }
 
     void onDraw(SkCanvas* canvas) override {
diff --git a/gm/beziereffects.cpp b/gm/beziereffects.cpp
index 2210c7b..2e0e52c0 100644
--- a/gm/beziereffects.cpp
+++ b/gm/beziereffects.cpp
@@ -7,13 +7,16 @@
 
 // This test only works with the GPU backend.
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "GrContext.h"
+#include "GrContextPriv.h"
 #include "GrMemoryPool.h"
 #include "GrOpFlushState.h"
 #include "GrPathUtils.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContextPriv.h"
 #include "SkColorPriv.h"
 #include "SkGeometry.h"
@@ -28,9 +31,11 @@
 public:
     FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
-        return fProcessorSet.finalize(fColor, GrProcessorAnalysisCoverage::kSingleChannel, clip,
-                                      false, caps, &fColor);
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
+        return fProcessorSet.finalize(
+                fColor, GrProcessorAnalysisCoverage::kSingleChannel, clip,
+                &GrUserStencilSettings::kUnused, fsaaType, caps, clampType, &fColor);
     }
 
     void visitProxies(const VisitProxyFunc& func, VisitorType) const override {
@@ -48,8 +53,9 @@
         this->setBounds(rect, HasAABloat::kYes, IsZeroArea::kNo);
     }
 
-    Target::PipelineAndFixedDynamicState makePipeline(Target* target) {
-        return target->makePipeline(0, std::move(fProcessorSet), target->detachAppliedClip());
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        flushState->executeDrawsAndUploadsForMeshDrawOp(
+                this, chainBounds, std::move(fProcessorSet));
     }
 
     sk_sp<const GrGeometryProcessor> gp() const { return fGeometryProcessor; }
@@ -76,7 +82,7 @@
 
     const char* name() const override { return "BezierConicTestOp"; }
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           sk_sp<const GrGeometryProcessor> gp,
                                           const SkRect& rect,
                                           const SkPMColor4f& color,
@@ -112,8 +118,8 @@
             SkPoint3 pt3 = {verts[v].fPosition.x(), verts[v].fPosition.y(), 1.f};
             fKLM.mapHomogeneousPoints((SkPoint3* ) verts[v].fKLM, &pt3, 1);
         }
-        auto pipe = this->makePipeline(target);
-        helper.recordDraw(target, this->gp(), pipe.fPipeline, pipe.fFixedDynamicState);
+
+        helper.recordDraw(target, this->gp());
     }
 
     SkMatrix fKLM;
@@ -319,8 +325,7 @@
         SkRect rect = this->rect();
         SkPointPriv::SetRectTriStrip(&verts[0].fPosition, rect, sizeof(Vertex));
         fDevToUV.apply(verts, 4, sizeof(Vertex), sizeof(SkPoint));
-        auto pipe = this->makePipeline(target);
-        helper.recordDraw(target, this->gp(), pipe.fPipeline, pipe.fFixedDynamicState);
+        helper.recordDraw(target, this->gp());
     }
 
     GrPathUtils::QuadUVMatrix fDevToUV;
diff --git a/gm/beziers.cpp b/gm/beziers.cpp
index 598ed62..f964217 100644
--- a/gm/beziers.cpp
+++ b/gm/beziers.cpp
@@ -30,7 +30,7 @@
     SkScalar width = rand.nextRangeScalar(1, 5);
     width *= width;
     paint->setStrokeWidth(width);
-    paint->setAlpha(0xFF);
+    paint->setAlphaf(1.0f);
 }
 
 static void rnd_cubic(SkPath* p, SkPaint* paint, SkRandom& rand) {
@@ -50,7 +50,7 @@
     SkScalar width = rand.nextRangeScalar(1, 5);
     width *= width;
     paint->setStrokeWidth(width);
-    paint->setAlpha(0xFF);
+    paint->setAlphaf(1.0f);
 }
 
 class BeziersGM : public skiagm::GM {
diff --git a/gm/bigmatrix.cpp b/gm/bigmatrix.cpp
index 273b049..e54e04b 100644
--- a/gm/bigmatrix.cpp
+++ b/gm/bigmatrix.cpp
@@ -5,62 +5,58 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkColorPriv.h"
 #include "SkPath.h"
 #include "SkShader.h"
 
-DEF_SIMPLE_GM_BG(bigmatrix, canvas, 50, 50,
-                 sk_tool_utils::color_to_565(0xFF66AA99)) {
-        SkMatrix m;
-        m.reset();
-        m.setRotate(33 * SK_Scalar1);
-        m.postScale(3000 * SK_Scalar1, 3000 * SK_Scalar1);
-        m.postTranslate(6000 * SK_Scalar1, -5000 * SK_Scalar1);
-        canvas->concat(m);
+DEF_SIMPLE_GM_BG(bigmatrix, canvas, 50, 50, ToolUtils::color_to_565(0xFF66AA99)) {
+    SkMatrix m;
+    m.reset();
+    m.setRotate(33 * SK_Scalar1);
+    m.postScale(3000 * SK_Scalar1, 3000 * SK_Scalar1);
+    m.postTranslate(6000 * SK_Scalar1, -5000 * SK_Scalar1);
+    canvas->concat(m);
 
-        SkPaint paint;
-        paint.setColor(SK_ColorRED);
-        paint.setAntiAlias(true);
+    SkPaint paint;
+    paint.setColor(SK_ColorRED);
+    paint.setAntiAlias(true);
 
-        bool success = m.invert(&m);
-        SkASSERT(success);
-        (void) success; // silence compiler :(
+    bool success = m.invert(&m);
+    SkASSERT(success);
+    (void)success;  // silence compiler :(
 
-        SkPath path;
+    SkPath path;
 
-        SkPoint pt = {10 * SK_Scalar1, 10 * SK_Scalar1};
-        SkScalar small = 1 / (500 * SK_Scalar1);
+    SkPoint  pt    = {10 * SK_Scalar1, 10 * SK_Scalar1};
+    SkScalar small = 1 / (500 * SK_Scalar1);
 
-        m.mapPoints(&pt, 1);
-        path.addCircle(pt.fX, pt.fY, small);
-        canvas->drawPath(path, paint);
+    m.mapPoints(&pt, 1);
+    path.addCircle(pt.fX, pt.fY, small);
+    canvas->drawPath(path, paint);
 
-        pt.set(30 * SK_Scalar1, 10 * SK_Scalar1);
-        m.mapPoints(&pt, 1);
-        SkRect rect = {pt.fX - small, pt.fY - small,
-                       pt.fX + small, pt.fY + small};
-        canvas->drawRect(rect, paint);
+    pt.set(30 * SK_Scalar1, 10 * SK_Scalar1);
+    m.mapPoints(&pt, 1);
+    SkRect rect = {pt.fX - small, pt.fY - small, pt.fX + small, pt.fY + small};
+    canvas->drawRect(rect, paint);
 
-        SkBitmap bmp;
-        bmp.allocN32Pixels(2, 2);
-        uint32_t* pixels = reinterpret_cast<uint32_t*>(bmp.getPixels());
-        pixels[0] = SkPackARGB32(0xFF, 0xFF, 0x00, 0x00);
-        pixels[1] = SkPackARGB32(0xFF, 0x00, 0xFF, 0x00);
-        pixels[2] = SkPackARGB32(0x80, 0x00, 0x00, 0x00);
-        pixels[3] = SkPackARGB32(0xFF, 0x00, 0x00, 0xFF);
-        pt.set(30 * SK_Scalar1, 30 * SK_Scalar1);
-        m.mapPoints(&pt, 1);
-        SkMatrix s;
-        s.reset();
-        s.setScale(SK_Scalar1 / 1000, SK_Scalar1 / 1000);
-        paint.setShader(SkShader::MakeBitmapShader(bmp, SkShader::kRepeat_TileMode,
-                                                   SkShader::kRepeat_TileMode, &s));
-        paint.setAntiAlias(false);
-        paint.setFilterQuality(kLow_SkFilterQuality);
-        rect.setLTRB(pt.fX - small, pt.fY - small,
-                     pt.fX + small, pt.fY + small);
-        canvas->drawRect(rect, paint);
+    SkBitmap bmp;
+    bmp.allocN32Pixels(2, 2);
+    uint32_t* pixels = reinterpret_cast<uint32_t*>(bmp.getPixels());
+    pixels[0]        = SkPackARGB32(0xFF, 0xFF, 0x00, 0x00);
+    pixels[1]        = SkPackARGB32(0xFF, 0x00, 0xFF, 0x00);
+    pixels[2]        = SkPackARGB32(0x80, 0x00, 0x00, 0x00);
+    pixels[3]        = SkPackARGB32(0xFF, 0x00, 0x00, 0xFF);
+    pt.set(30 * SK_Scalar1, 30 * SK_Scalar1);
+    m.mapPoints(&pt, 1);
+    SkMatrix s;
+    s.reset();
+    s.setScale(SK_Scalar1 / 1000, SK_Scalar1 / 1000);
+    paint.setShader(bmp.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &s));
+    paint.setAntiAlias(false);
+    paint.setFilterQuality(kLow_SkFilterQuality);
+    rect.setLTRB(pt.fX - small, pt.fY - small, pt.fX + small, pt.fY + small);
+    canvas->drawRect(rect, paint);
 }
diff --git a/gm/bigrrectaaeffect.cpp b/gm/bigrrectaaeffect.cpp
index 613cf2d..86e3daa 100644
--- a/gm/bigrrectaaeffect.cpp
+++ b/gm/bigrrectaaeffect.cpp
@@ -5,13 +5,13 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "GrCaps.h"
 #include "GrContext.h"
 #include "GrRenderTargetContextPriv.h"
 #include "SkRRect.h"
+#include "ToolUtils.h"
 #include "effects/GrRRectEffect.h"
+#include "gm.h"
 #include "ops/GrDrawOp.h"
 #include "ops/GrFillRectOp.h"
 
@@ -24,7 +24,7 @@
     BigRRectAAEffectGM(const SkRRect& rrect, const char* name)
         : fRRect(rrect)
         , fName(name) {
-        this->setBGColor(sk_tool_utils::color_to_565(SK_ColorBLUE));
+        this->setBGColor(ToolUtils::color_to_565(SK_ColorBLUE));
         // Each test case draws the rrect with gaps around it.
         fTestWidth = SkScalarCeilToInt(rrect.width()) + 2 * kGap;
         fTestHeight = SkScalarCeilToInt(rrect.height()) + 2 * kGap;
diff --git a/gm/bigtext.cpp b/gm/bigtext.cpp
index 839609e..f30c7b9 100644
--- a/gm/bigtext.cpp
+++ b/gm/bigtext.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkFont.h"
 #include "SkPath.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 /**
  *  Skia may draw from outlines when the size is very large, so we exercise that
@@ -33,7 +33,7 @@
     void onDraw(SkCanvas* canvas) override {
         SkPaint paint;
         paint.setAntiAlias(true);
-        SkFont font(sk_tool_utils::create_portable_typeface(), 1500);
+        SkFont font(ToolUtils::create_portable_typeface(), 1500);
 
         SkRect r;
         (void)font.measureText("/", 1, kUTF8_SkTextEncoding, &r);
diff --git a/gm/bitmapcopy.cpp b/gm/bitmapcopy.cpp
index 547ebe0..9c4fbb7 100644
--- a/gm/bitmapcopy.cpp
+++ b/gm/bitmapcopy.cpp
@@ -5,9 +5,9 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkFont.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -23,6 +23,7 @@
         case kRGBA_1010102_SkColorType: return "1010102";
         case kRGB_101010x_SkColorType:  return "101010x";
         case kGray_8_SkColorType:       return "G8";
+        case kRGBA_F16Norm_SkColorType: return "F16Norm";
         case kRGBA_F16_SkColorType:     return "F16";
         case kRGBA_F32_SkColorType:     return "F32";
     }
@@ -81,13 +82,13 @@
         draw_checks(&canvasTmp, 40, 40);
 
         for (unsigned i = 0; i < NUM_CONFIGS; ++i) {
-            sk_tool_utils::copy_to(&fDst[i], gColorTypes[i], src);
+            ToolUtils::copy_to(&fDst[i], gColorTypes[i], src);
         }
 
         canvas->clear(0xFFDDDDDD);
         paint.setAntiAlias(true);
 
-        SkFont font(sk_tool_utils::create_portable_typeface());
+        SkFont font(ToolUtils::create_portable_typeface());
 
         SkScalar width = SkIntToScalar(40);
         SkScalar height = SkIntToScalar(40);
diff --git a/gm/bitmapfilters.cpp b/gm/bitmapfilters.cpp
index 98038ca..2ae7fc9 100644
--- a/gm/bitmapfilters.cpp
+++ b/gm/bitmapfilters.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 static void make_bm(SkBitmap* bm) {
     const SkColor colors[4] = {
@@ -49,8 +49,8 @@
     SkScalar x = 0;
     const int scale = 32;
 
-    SkFont font(sk_tool_utils::create_portable_typeface());
-    const char* name = sk_tool_utils::colortype_name(bm.colorType());
+    SkFont      font(ToolUtils::create_portable_typeface());
+    const char* name = ToolUtils::colortype_name(bm.colorType());
     canvas->drawString(name, x, SkIntToScalar(bm.height())*scale*5/8,
                        font, paint);
     canvas->translate(SkIntToScalar(48), 0);
@@ -59,7 +59,7 @@
 
     x += draw_set(canvas, bm, 0, &paint);
     paint.reset();
-    paint.setAlpha(0x80);
+    paint.setAlphaf(0.5f);
     draw_set(canvas, bm, x, &paint);
     return x * scale / 3;
 }
@@ -67,8 +67,8 @@
 class FilterGM : public skiagm::GM {
     void onOnceBeforeDraw() override {
         make_bm(&fBM32);
-        sk_tool_utils::copy_to(&fBM4444, kARGB_4444_SkColorType, fBM32);
-        sk_tool_utils::copy_to(&fBM16, kRGB_565_SkColorType, fBM32);
+        ToolUtils::copy_to(&fBM4444, kARGB_4444_SkColorType, fBM32);
+        ToolUtils::copy_to(&fBM16, kRGB_565_SkColorType, fBM32);
     }
 
 public:
@@ -120,7 +120,6 @@
         paint.setStrokeWidth(20);
 
         canvas.drawCircle(50, 50, 39, paint);
-        canvas.flush();
 
         fBitmap.extractAlpha(&fAlpha);
     }
diff --git a/gm/bitmapimage.cpp b/gm/bitmapimage.cpp
index 2dc692c..92883da 100644
--- a/gm/bitmapimage.cpp
+++ b/gm/bitmapimage.cpp
@@ -26,14 +26,14 @@
         return SkISize::Make(2*kSize, 2*kSize);
     }
 
-    void onDraw(SkCanvas* canvas) override {
+    DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
         // Create image.
         const char* path = "images/mandrill_512_q075.jpg";
         sk_sp<SkImage> image = GetResourceAsImage(path);
         if (!image) {
-            DrawFailureMessage(canvas, "Couldn't load images/mandrill_512_q075.jpg. "
-                                       "Did you forget to set the resource path?");
-            return;
+            *errorMsg = "Couldn't load images/mandrill_512_q075.jpg. "
+                        "Did you forget to set the resource path?";
+            return DrawResult::kFail;
         }
 
         // Create matching bitmap.
@@ -63,6 +63,7 @@
         srgbCanvas.translate(SkScalar(kSize), 0.0f);
         srgbCanvas.drawBitmap(bitmap, 0.0f, 0.0f, nullptr);
         canvas->drawBitmap(srgbBMCanvas, 0.0f, 0.0f, nullptr);
+        return DrawResult::kOk;
     }
 
 private:
diff --git a/gm/bitmaprect.cpp b/gm/bitmaprect.cpp
index 190fb0d..67fdd53 100644
--- a/gm/bitmaprect.cpp
+++ b/gm/bitmaprect.cpp
@@ -23,8 +23,7 @@
     paint.setAntiAlias(true);
     const SkPoint pts[] = { { 0, 0 }, { 64, 64 } };
     const SkColor colors[] = { SK_ColorWHITE, SK_ColorBLUE };
-    paint.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, 2,
-                                                 SkShader::kClamp_TileMode));
+    paint.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp));
     canvas.drawCircle(32, 32, 32, paint);
 }
 
diff --git a/gm/bitmapshader.cpp b/gm/bitmapshader.cpp
index c6dba20..7143160 100644
--- a/gm/bitmapshader.cpp
+++ b/gm/bitmapshader.cpp
@@ -67,11 +67,11 @@
             }
 
             canvas->save();
-            paint.setShader(SkShader::MakeBitmapShader(fBitmap, SkShader::kClamp_TileMode,
-                                                       SkShader::kClamp_TileMode, &s));
+            paint.setShader(fBitmap.makeShader(&s));
 
             // draw the shader with a bitmap mask
             canvas->drawBitmap(fMask, 0, 0, &paint);
+            // no blue circle expected (the bitmap shader's coordinates are aligned to CTM still)
             canvas->drawBitmap(fMask, 30, 0, &paint);
 
             canvas->translate(0, 25);
@@ -89,8 +89,7 @@
 
             canvas->translate(0, 25);
 
-            paint.setShader(SkShader::MakeBitmapShader(fMask, SkShader::kRepeat_TileMode,
-                                                       SkShader::kRepeat_TileMode, &s));
+            paint.setShader(fMask.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &s));
             paint.setColor(SK_ColorRED);
 
             // draw the mask using the shader and a color
@@ -128,8 +127,7 @@
     }
     bitmap.setPixels(pixels);
 
-    paint.setShader(SkShader::MakeBitmapShader(bitmap,
-             SkShader::kMirror_TileMode, SkShader::kMirror_TileMode));
+    paint.setShader(bitmap.makeShader(SkTileMode::kMirror, SkTileMode::kMirror));
     paint.setColor(SK_ColorRED);
     paint.setAntiAlias(true);
     canvas->drawCircle(50, 50, 50, paint);
diff --git a/gm/bleed.cpp b/gm/bleed.cpp
index f7d582f..485a3d8 100644
--- a/gm/bleed.cpp
+++ b/gm/bleed.cpp
@@ -5,7 +5,6 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
 #include "SkBlurMask.h"
 #include "SkCanvas.h"
 #include "SkGradientShader.h"
@@ -13,7 +12,8 @@
 #include "SkMaskFilter.h"
 #include "SkTDArray.h"
 #include "SkUTF.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #include "GrContext.h"
 #include "GrContextOptions.h"
@@ -157,7 +157,7 @@
 static sk_sp<SkShader> make_shader() {
     constexpr SkPoint pts[] = { {0, 0}, {20, 20} };
     constexpr SkColor colors[] = { SK_ColorGREEN, SK_ColorYELLOW };
-    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kMirror_TileMode);
+    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kMirror);
 }
 
 static sk_sp<SkShader> make_null_shader() { return nullptr; }
@@ -457,7 +457,7 @@
     // produce different mipmap filtering when we have an odd sized texture.
     const int N = 10 + 2 + 8 + 2 + 10;
     SkImageInfo info = SkImageInfo::MakeN32Premul(N, N);
-    auto surface = sk_tool_utils::makeSurface(canvas, info);
+    auto        surface = ToolUtils::makeSurface(canvas, info);
     SkCanvas* c = surface->getCanvas();
     SkRect r = SkRect::MakeIWH(info.width(), info.height());
     SkPaint paint;
@@ -489,7 +489,7 @@
         canvas->save();
         for (auto quality : qualities) {
             paint.setFilterQuality(quality);
-            auto surf = sk_tool_utils::makeSurface(canvas, SkImageInfo::MakeN32Premul(1, 1));
+            auto surf = ToolUtils::makeSurface(canvas, SkImageInfo::MakeN32Premul(1, 1));
             surf->getCanvas()->drawImageRect(img, src, SkRect::MakeWH(1, 1), &paint, constraint);
             // now blow up the 1 pixel result
             canvas->drawImageRect(surf->makeImageSnapshot(), SkRect::MakeWH(100, 100), nullptr);
diff --git a/gm/blend.cpp b/gm/blend.cpp
index 28ec6ff..aaaeeeb 100644
--- a/gm/blend.cpp
+++ b/gm/blend.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 DEF_SIMPLE_GM(blend, canvas, 300, 100) {
     SkPaint p;
@@ -30,8 +30,8 @@
         p.setColor(SK_ColorRED);
         canvas->drawRect(SkRect::MakeXYWH(2,0,1,1), p);
         canvas->saveLayerAlpha(nullptr, 0xFC);
-            p.setColor(sk_tool_utils::color_to_565(0xFF208000));
-            canvas->drawRect(SkRect::MakeXYWH(2,0,1,1), p);
+        p.setColor(ToolUtils::color_to_565(0xFF208000));
+        canvas->drawRect(SkRect::MakeXYWH(2, 0, 1, 1), p);
         canvas->restore();
     canvas->restore();
 }
diff --git a/gm/blurcircles2.cpp b/gm/blurcircles2.cpp
index c2739a9..b28c932 100644
--- a/gm/blurcircles2.cpp
+++ b/gm/blurcircles2.cpp
@@ -5,15 +5,15 @@
 * found in the LICENSE file.
 */
 
-#include "gm.h"
-#include "SkAnimTimer.h"
+#include "AnimTimer.h"
 #include "SkBlurMask.h"
 #include "SkBlurMaskFilter.h"
 #include "SkCanvas.h"
 #include "SkPaint.h"
 #include "SkPath.h"
-#include "SkString.h"
 #include "SkRandom.h"
+#include "SkString.h"
+#include "gm.h"
 
 /**
  * In GM mode this draws an array of circles with different radii and different blur radii. Below
@@ -28,11 +28,13 @@
 class BlurCircles2GM : public skiagm::GM {
 public:
     BlurCircles2GM() {
-        fAnimRadius = SkAnimTimer::PingPong(0, kRadiusPingPoingPeriod, kRadiusPingPoingShift,
-                                            kMinRadius, kMaxRadius);
-        fAnimBlurRadius = SkAnimTimer::PingPong(0, kBlurRadiusPingPoingPeriod,
-                                                kBlurRadiusPingPoingShift, kMinBlurRadius,
-                                                kMaxBlurRadius);
+        fAnimRadius = AnimTimer::PingPong(
+                0, kRadiusPingPoingPeriod, kRadiusPingPoingShift, kMinRadius, kMaxRadius);
+        fAnimBlurRadius = AnimTimer::PingPong(0,
+                                              kBlurRadiusPingPoingPeriod,
+                                              kBlurRadiusPingPoingShift,
+                                              kMinBlurRadius,
+                                              kMaxBlurRadius);
     }
 
 protected:
@@ -133,7 +135,7 @@
         }
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         fAnimRadius = timer.pingPong(kRadiusPingPoingPeriod, kRadiusPingPoingShift, kMinRadius,
                                      kMaxRadius);
         fAnimBlurRadius = timer.pingPong(kBlurRadiusPingPoingPeriod, kBlurRadiusPingPoingShift,
diff --git a/gm/blurignorexform.cpp b/gm/blurignorexform.cpp
index 28fc19d..9a68daf 100644
--- a/gm/blurignorexform.cpp
+++ b/gm/blurignorexform.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkBlurMask.h"
 #include "SkCanvas.h"
@@ -95,7 +95,7 @@
 
     void drawOverlay(SkCanvas* canvas) {
         canvas->translate(10, 0);
-        SkFont font(sk_tool_utils::create_portable_typeface());
+        SkFont font(ToolUtils::create_portable_typeface());
         canvas->save();
         for (int i = 0; i < kNumBlurs; ++i) {
             canvas->drawString(kBlurFlags[i].fName, 100, 0, font, SkPaint());
diff --git a/gm/blurimagevmask.cpp b/gm/blurimagevmask.cpp
index 0056a05..78524ca 100644
--- a/gm/blurimagevmask.cpp
+++ b/gm/blurimagevmask.cpp
@@ -7,16 +7,15 @@
 
 #include "SkBlurImageFilter.h"
 #include "SkMaskFilter.h"
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
-
 
 DEF_SIMPLE_GM(blurimagevmask, canvas, 700, 1200) {
     SkPaint paint;
     paint.setAntiAlias(true);
     paint.setColor(SK_ColorBLACK);
 
-    SkFont font(sk_tool_utils::create_portable_typeface(), 25);
+    SkFont font(ToolUtils::create_portable_typeface(), 25);
 
     const double sigmas[] = {3.0, 8.0, 16.0, 24.0, 32.0};
 
@@ -52,12 +51,11 @@
 }
 
 #include "Resources.h"
-DEF_SIMPLE_GM(blur_image, canvas, 500, 500) {
+DEF_SIMPLE_GM_CAN_FAIL(blur_image, canvas, errorMsg, 500, 500) {
     auto image = GetResourceAsImage("images/mandrill_128.png");
     if (!image) {
-        skiagm::GM::DrawFailureMessage(canvas, "Could not load mandrill_128.png. "
-                                               "Did you forget to set the resourcePath?");
-        return;
+        *errorMsg = "Could not load mandrill_128.png. Did you forget to set the resourcePath?";
+        return skiagm::DrawResult::kFail;
     }
 
     SkPaint paint;
@@ -69,4 +67,5 @@
     canvas->drawImage(image, 10, 10, &paint);
     canvas->scale(1.01f, 1.01f);
     canvas->drawImage(image, 10 + image->width() + 10.f, 10, &paint);
+    return skiagm::DrawResult::kOk;
 }
diff --git a/gm/blurrect.cpp b/gm/blurrect.cpp
index c2724bd..2000f08 100644
--- a/gm/blurrect.cpp
+++ b/gm/blurrect.cpp
@@ -64,7 +64,7 @@
         { 0, 0 },
         { SkIntToScalar(100), SkIntToScalar(100) }
     };
-    SkShader::TileMode tm = SkShader::kClamp_TileMode;
+    SkTileMode tm = SkTileMode::kClamp;
     const SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, };
     const SkScalar pos[] = { SK_Scalar1/4, SK_Scalar1*3/4 };
     SkMatrix scale;
diff --git a/gm/blurredclippedcircle.cpp b/gm/blurredclippedcircle.cpp
index 65e62b7..318e4a9 100644
--- a/gm/blurredclippedcircle.cpp
+++ b/gm/blurredclippedcircle.cpp
@@ -67,9 +67,8 @@
                     paint.setMaskFilter(SkMaskFilter::MakeBlur(
                                             kNormal_SkBlurStyle,
                                             1.366025f));
-                    paint.setColorFilter(SkColorFilter::MakeModeFilter(
-                                             SK_ColorRED,
-                                             SkBlendMode::kSrcIn));
+                    paint.setColorFilter(SkColorFilters::Blend(SK_ColorRED,
+                                                                   SkBlendMode::kSrcIn));
                     paint.setAntiAlias(true);
 
                     canvas->drawRRect(rr, paint);
diff --git a/gm/blurroundrect.cpp b/gm/blurroundrect.cpp
index b7d0800..05db485 100644
--- a/gm/blurroundrect.cpp
+++ b/gm/blurroundrect.cpp
@@ -57,9 +57,7 @@
             paint->setMaskFilter(SkMaskFilter::MakeBlur(
                     kNormal_SkBlurStyle,
                     SkBlurMask::ConvertRadiusToSigma(SK_ScalarHalf)));
-            paint->setColorFilter(SkColorFilter::MakeModeFilter(
-                    SK_ColorLTGRAY,
-                    SkBlendMode::kSrcIn));
+            paint->setColorFilter(SkColorFilters::Blend(SK_ColorLTGRAY, SkBlendMode::kSrcIn));
             paint->setColor(SK_ColorGRAY);
         }
         {
@@ -93,7 +91,7 @@
         { 0, 0 },
         { SkIntToScalar(100), SkIntToScalar(100) }
     };
-    SkShader::TileMode tm = SkShader::kClamp_TileMode;
+    SkTileMode tm = SkTileMode::kClamp;
     const SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, };
     const SkScalar pos[] = { SK_Scalar1/4, SK_Scalar1*3/4 };
     SkMatrix scale;
diff --git a/gm/blurs.cpp b/gm/blurs.cpp
index 6892ad3..23ef092 100644
--- a/gm/blurs.cpp
+++ b/gm/blurs.cpp
@@ -5,13 +5,13 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "Resources.h"
 #include "SkBlurMask.h"
 #include "SkImage.h"
 #include "SkMaskFilter.h"
 #include "SkPath.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 DEF_SIMPLE_GM_BG(blurs, canvas, 700, 500, 0xFFDDDDDD) {
     SkBlurStyle NONE = SkBlurStyle(-999);
@@ -46,7 +46,7 @@
     }
     // draw text
     {
-        SkFont font(sk_tool_utils::create_portable_typeface(), 25);
+        SkFont font(ToolUtils::create_portable_typeface(), 25);
         paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle,
                                    SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(4))));
         SkScalar x = SkIntToScalar(70);
diff --git a/gm/bmpfilterqualityrepeat.cpp b/gm/bmpfilterqualityrepeat.cpp
index ba41c3a..611420c 100644
--- a/gm/bmpfilterqualityrepeat.cpp
+++ b/gm/bmpfilterqualityrepeat.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkShader.h"
 
@@ -15,7 +15,7 @@
 // the bmp filter respects the repeat mode at the tile seams.
 class BmpFilterQualityRepeat : public skiagm::GM {
 public:
-    BmpFilterQualityRepeat() { this->setBGColor(sk_tool_utils::color_to_565(0xFFCCBBAA)); }
+    BmpFilterQualityRepeat() { this->setBGColor(ToolUtils::color_to_565(0xFFCCBBAA)); }
 
 protected:
 
@@ -26,11 +26,11 @@
         colorBmp.allocN32Pixels(20, 20, true);
         colorBmp.eraseColor(0xFFFF0000);
         canvas.drawBitmap(colorBmp, 0, 0);
-        colorBmp.eraseColor(sk_tool_utils::color_to_565(0xFF008200));
+        colorBmp.eraseColor(ToolUtils::color_to_565(0xFF008200));
         canvas.drawBitmap(colorBmp, 20, 0);
-        colorBmp.eraseColor(sk_tool_utils::color_to_565(0xFFFF9000));
+        colorBmp.eraseColor(ToolUtils::color_to_565(0xFFFF9000));
         canvas.drawBitmap(colorBmp, 0, 20);
-        colorBmp.eraseColor(sk_tool_utils::color_to_565(0xFF2000FF));
+        colorBmp.eraseColor(ToolUtils::color_to_565(0xFF2000FF));
         canvas.drawBitmap(colorBmp, 20, 20);
     }
 
@@ -68,13 +68,13 @@
 
         SkPaint bmpPaint(textPaint);
 
-        SkFont font(sk_tool_utils::create_portable_typeface());
+        SkFont font(ToolUtils::create_portable_typeface());
 
         SkAutoCanvasRestore acr(canvas, true);
 
         for (size_t q = 0; q < SK_ARRAY_COUNT(kQualities); ++q) {
-            constexpr SkShader::TileMode kTM = SkShader::kRepeat_TileMode;
-            bmpPaint.setShader(SkShader::MakeBitmapShader(fBmp, kTM, kTM, &lm));
+            constexpr SkTileMode kTM = SkTileMode::kRepeat;
+            bmpPaint.setShader(fBmp.makeShader(kTM, kTM, &lm));
             bmpPaint.setFilterQuality(kQualities[q].fQuality);
             canvas->drawRect(rect, bmpPaint);
             canvas->drawString(kQualities[q].fName, 20, 40, font, textPaint);
diff --git a/gm/bug6643.cpp b/gm/bug6643.cpp
index abe79fa..c30d898 100644
--- a/gm/bug6643.cpp
+++ b/gm/bug6643.cpp
@@ -21,9 +21,8 @@
     SkPictureRecorder recorder;
     recorder.beginRecording(200, 200)->drawPaint(p);
 
-    p.setShader(SkShader::MakePictureShader(recorder.finishRecordingAsPicture(),
-                                            SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode,
-                                            nullptr, nullptr));
+    p.setShader(recorder.finishRecordingAsPicture()->makeShader(
+                                            SkTileMode::kRepeat, SkTileMode::kRepeat));
     canvas->drawColor(SK_ColorWHITE);
     canvas->drawPaint(p);
 }
diff --git a/gm/bug6783.cpp b/gm/bug6783.cpp
index 5b54e1c..ba8b77b 100644
--- a/gm/bug6783.cpp
+++ b/gm/bug6783.cpp
@@ -42,6 +42,6 @@
     p.setFilterQuality(kLow_SkFilterQuality);
 
     // It's only important to repeat or mirror in x to show off the bug.
-    p.setShader(img->makeShader(SkShader::kRepeat_TileMode, SkShader::kClamp_TileMode, &m));
+    p.setShader(img->makeShader(SkTileMode::kRepeat, SkTileMode::kClamp, &m));
     canvas->drawPaint(p);
 }
diff --git a/gm/circles.cpp b/gm/circles.cpp
index 9127c0a..01bf851 100644
--- a/gm/circles.cpp
+++ b/gm/circles.cpp
@@ -66,7 +66,7 @@
         SkColor colors[] = { SK_ColorBLUE, SK_ColorRED, SK_ColorGREEN };
         SkScalar pos[] = { 0, SK_ScalarHalf, SK_Scalar1 };
         p.setShader(SkGradientShader::MakeRadial(center, 20, colors, pos, SK_ARRAY_COUNT(colors),
-                                                 SkShader::kClamp_TileMode));
+                                                 SkTileMode::kClamp));
         fPaints.push_back(p);
         }
 
diff --git a/gm/clip_error.cpp b/gm/clip_error.cpp
index 7e9c4ce..43c00ec 100644
--- a/gm/clip_error.cpp
+++ b/gm/clip_error.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkBlurMask.h"
 #include "SkCanvas.h"
 #include "SkMaskFilter.h"
 #include "SkTextBlob.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #define WIDTH 800
 #define HEIGHT 800
@@ -42,7 +42,7 @@
         SkPaint paint;
         paint.setAntiAlias(true);
 
-        SkFont font(sk_tool_utils::create_portable_typeface(), 256);
+        SkFont font(ToolUtils::create_portable_typeface(), 256);
 
         // setup up maskfilter
         const SkScalar kSigma = SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(50));
diff --git a/gm/clippedbitmapshaders.cpp b/gm/clippedbitmapshaders.cpp
index b421800..88499b1 100644
--- a/gm/clippedbitmapshaders.cpp
+++ b/gm/clippedbitmapshaders.cpp
@@ -42,28 +42,29 @@
 
 class ClippedBitmapShadersGM : public GM {
 public:
-    ClippedBitmapShadersGM(SkShader::TileMode mode, bool hq=false)
+    ClippedBitmapShadersGM(SkTileMode mode, bool hq=false)
     : fMode(mode), fHQ(hq) {
     }
 
 protected:
-    SkShader::TileMode fMode;
+    SkTileMode fMode;
     bool fHQ;
 
     virtual SkString onShortName() {
         SkString descriptor;
         switch (fMode) {
-            case SkShader::kRepeat_TileMode:
+            case SkTileMode::kRepeat:
                 descriptor = "tile";
             break;
-            case SkShader::kMirror_TileMode:
+            case SkTileMode::kMirror:
                 descriptor = "mirror";
             break;
-            case SkShader::kClamp_TileMode:
+            case SkTileMode::kClamp:
                 descriptor = "clamp";
             break;
-            default:
-                SkASSERT(false);
+            case SkTileMode::kDecal:
+                descriptor = "decal";
+                break;
         }
         descriptor.prepend("clipped-bitmap-shaders-");
         if (fHQ) {
@@ -83,7 +84,7 @@
         s.setScale(8, 8);
         s.postTranslate(SLIDE_SIZE / 2, SLIDE_SIZE / 2);
         SkPaint paint;
-        paint.setShader(SkShader::MakeBitmapShader(bmp, fMode, fMode, &s));
+        paint.setShader(bmp.makeShader(fMode, fMode, &s));
 
         if (fHQ) {
             paint.setFilterQuality(kHigh_SkFilterQuality);
@@ -113,13 +114,13 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
-DEF_GM( return new ClippedBitmapShadersGM(SkShader::kRepeat_TileMode); )
-DEF_GM( return new ClippedBitmapShadersGM(SkShader::kMirror_TileMode); )
-DEF_GM( return new ClippedBitmapShadersGM(SkShader::kClamp_TileMode); )
+DEF_GM( return new ClippedBitmapShadersGM(SkTileMode::kRepeat); )
+DEF_GM( return new ClippedBitmapShadersGM(SkTileMode::kMirror); )
+DEF_GM( return new ClippedBitmapShadersGM(SkTileMode::kClamp); )
 
-DEF_GM( return new ClippedBitmapShadersGM(SkShader::kRepeat_TileMode, true); )
-DEF_GM( return new ClippedBitmapShadersGM(SkShader::kMirror_TileMode, true); )
-DEF_GM( return new ClippedBitmapShadersGM(SkShader::kClamp_TileMode, true); )
+DEF_GM( return new ClippedBitmapShadersGM(SkTileMode::kRepeat, true); )
+DEF_GM( return new ClippedBitmapShadersGM(SkTileMode::kMirror, true); )
+DEF_GM( return new ClippedBitmapShadersGM(SkTileMode::kClamp, true); )
 
 
 }
diff --git a/gm/clockwise.cpp b/gm/clockwise.cpp
index 269a5fb..7ad9fee 100644
--- a/gm/clockwise.cpp
+++ b/gm/clockwise.cpp
@@ -9,9 +9,12 @@
 
 #include "GrClip.h"
 #include "GrContext.h"
+#include "GrContextPriv.h"
 #include "GrGpuCommandBuffer.h"
 #include "GrMemoryPool.h"
 #include "GrOpFlushState.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrRenderTargetContextPriv.h"
 #include "GrRenderTarget.h"
@@ -91,7 +94,8 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context, bool readSkFragCoord, int y = 0) {
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
+                                          bool readSkFragCoord, int y = 0) {
         GrOpMemoryPool* pool = context->priv().opMemoryPool();
         return pool->allocate<ClockwiseTestOp>(readSkFragCoord, y);
     }
@@ -104,7 +108,8 @@
 
     const char* name() const override { return "ClockwiseTestOp"; }
     FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
-    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*) override {
+    GrProcessorSet::Analysis finalize(
+            const GrCaps&, const GrAppliedClip*, GrFSAAType, GrClampType) override {
         return GrProcessorSet::EmptySetAnalysis();
     }
     void onPrepare(GrOpFlushState*) override {}
@@ -116,8 +121,7 @@
             {100, fY+100},
         };
         sk_sp<const GrBuffer> vertexBuffer(flushState->resourceProvider()->createBuffer(
-                sizeof(vertices), GrGpuBufferType::kVertex, kStatic_GrAccessPattern,
-                GrResourceProvider::Flags::kNone, vertices));
+                sizeof(vertices), GrGpuBufferType::kVertex, kStatic_GrAccessPattern, vertices));
         if (!vertexBuffer) {
             return;
         }
@@ -155,8 +159,9 @@
         topLeftRTC->priv().testingOnly_addDrawOp(ClockwiseTestOp::Make(ctx, false, 0));
         topLeftRTC->priv().testingOnly_addDrawOp(ClockwiseTestOp::Make(ctx, true, 100));
         rtc->drawTexture(GrNoClip(), sk_ref_sp(topLeftRTC->asTextureProxy()),
-                         GrSamplerState::Filter::kNearest, SK_PMColor4fWHITE, {0, 0, 100, 200},
-                         {100, 0, 200, 200}, GrQuadAAFlags::kNone,
+                         GrSamplerState::Filter::kNearest, SkBlendMode::kSrcOver,
+                         SK_PMColor4fWHITE, {0, 0, 100, 200},
+                         {100, 0, 200, 200}, GrAA::kNo, GrQuadAAFlags::kNone,
                          SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint, SkMatrix::I(),
                          nullptr);
     }
@@ -171,8 +176,9 @@
         topLeftRTC->priv().testingOnly_addDrawOp(ClockwiseTestOp::Make(ctx, false, 0));
         topLeftRTC->priv().testingOnly_addDrawOp(ClockwiseTestOp::Make(ctx, true, 100));
         rtc->drawTexture(GrNoClip(), sk_ref_sp(topLeftRTC->asTextureProxy()),
-                         GrSamplerState::Filter::kNearest, SK_PMColor4fWHITE, {0, 0, 100, 200},
-                         {200, 0, 300, 200}, GrQuadAAFlags::kNone,
+                         GrSamplerState::Filter::kNearest, SkBlendMode::kSrcOver,
+                         SK_PMColor4fWHITE, {0, 0, 100, 200},
+                         {200, 0, 300, 200}, GrAA::kNo, GrQuadAAFlags::kNone,
                          SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint, SkMatrix::I(),
                          nullptr);
     }
diff --git a/gm/color4f.cpp b/gm/color4f.cpp
index 792f7d5..e9f3680 100644
--- a/gm/color4f.cpp
+++ b/gm/color4f.cpp
@@ -15,11 +15,11 @@
 #include "SkGradientShader.h"
 
 static sk_sp<SkShader> make_opaque_color() {
-    return SkShader::MakeColorShader(0xFFFF0000);
+    return SkShaders::Color(0xFFFF0000);
 }
 
 static sk_sp<SkShader> make_alpha_color() {
-    return SkShader::MakeColorShader(0x80FF0000);
+    return SkShaders::Color(0x80FF0000);
 }
 
 static sk_sp<SkColorFilter> make_cf_null() {
@@ -29,23 +29,23 @@
 static sk_sp<SkColorFilter> make_cf0() {
     SkColorMatrix cm;
     cm.setSaturation(0.75f);
-    return SkColorFilter::MakeMatrixFilterRowMajor255(cm.fMat);
+    return SkColorFilters::MatrixRowMajor255(cm.fMat);
 }
 
 static sk_sp<SkColorFilter> make_cf1() {
     SkColorMatrix cm;
     cm.setSaturation(0.75f);
-    auto a = SkColorFilter::MakeMatrixFilterRowMajor255(cm.fMat);
+    auto a = SkColorFilters::MatrixRowMajor255(cm.fMat);
     // CreateComposedFilter will try to concat these two matrices, resulting in a single
     // filter (which is good for speed). For this test, we want to force a real compose of
     // these two, so our inner filter has a scale-up, which disables the optimization of
     // combining the two matrices.
     cm.setScale(1.1f, 0.9f, 1);
-    return a->makeComposed(SkColorFilter::MakeMatrixFilterRowMajor255(cm.fMat));
+    return a->makeComposed(SkColorFilters::MatrixRowMajor255(cm.fMat));
 }
 
 static sk_sp<SkColorFilter> make_cf2() {
-    return SkColorFilter::MakeModeFilter(0x8044CC88, SkBlendMode::kSrcATop);
+    return SkColorFilters::Blend(0x8044CC88, SkBlendMode::kSrcATop);
 }
 
 static void draw_into_canvas(SkCanvas* canvas) {
@@ -108,9 +108,9 @@
 
     for (const auto& c4 : colors) {
         sk_sp<SkShader> shaders[] {
-            SkShader::MakeColorShader(c4, nullptr),
-            SkShader::MakeColorShader(c4, srgb),
-            SkShader::MakeColorShader(c4, spin),
+            SkShaders::Color(c4, nullptr),
+            SkShaders::Color(c4, srgb),
+            SkShaders::Color(c4, spin),
         };
 
         canvas->save();
diff --git a/gm/coloremoji.cpp b/gm/coloremoji.cpp
index 115e5dd..b34af0f 100644
--- a/gm/coloremoji.cpp
+++ b/gm/coloremoji.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "Resources.h"
 #include "SkBlurImageFilter.h"
@@ -27,7 +27,7 @@
     constexpr SkScalar    kPos[] = { 0, SK_Scalar1/2, SK_Scalar1 };
     constexpr SkColor kColors[] = {0x80F00080, 0xF0F08000, 0x800080F0 };
     return SkGradientShader::MakeLinear(kPts, kColors, kPos, SK_ARRAY_COUNT(kColors),
-                                        SkShader::kClamp_TileMode);
+                                        SkTileMode::kClamp);
 }
 
 static sk_sp<SkImageFilter> make_grayscale(sk_sp<SkImageFilter> input) {
@@ -37,7 +37,7 @@
     matrix[1] = matrix[6] = matrix[11] = 0.7152f;
     matrix[2] = matrix[7] = matrix[12] = 0.0722f;
     matrix[18] = 1.0f;
-    sk_sp<SkColorFilter> filter(SkColorFilter::MakeMatrixFilterRowMajor255(matrix));
+    sk_sp<SkColorFilter> filter(SkColorFilters::MatrixRowMajor255(matrix));
     return SkColorFilterImageFilter::Make(std::move(filter), std::move(input));
 }
 
@@ -62,14 +62,12 @@
         const char* text;
     } emojiFont;
     virtual void onOnceBeforeDraw() override {
-        emojiFont.typeface = sk_tool_utils::emoji_typeface();
-        emojiFont.text = sk_tool_utils::emoji_sample_text();
+        emojiFont.typeface = ToolUtils::emoji_typeface();
+        emojiFont.text     = ToolUtils::emoji_sample_text();
     }
 
     SkString onShortName() override {
-        SkString name("coloremoji");
-        name.append(sk_tool_utils::platform_font_manager());
-        return name;
+        return SkString("coloremoji");
     }
 
     SkISize onISize() override { return SkISize::Make(650, 1200); }
@@ -123,7 +121,7 @@
                                 shaderPaint.setColorFilter(make_color_filter());
                             }
                             if (alpha) {
-                                shaderPaint.setAlpha(0x80);
+                                shaderPaint.setAlphaf(0.5f);
                             }
                             shaderFont.setSize(30);
                             shaderFont.getMetrics(&metrics);
@@ -170,7 +168,7 @@
             paint.setAlpha(0x20);
             canvas->drawSimpleText(text, strlen(text), kUTF8_SkTextEncoding, 0, 0, font, paint);
             canvas->clipRect(clipRect);
-            paint.setAlpha(0xFF);
+            paint.setAlphaf(1.0f);
             canvas->drawSimpleText(text, strlen(text), kUTF8_SkTextEncoding, 0, 0, font, paint);
             canvas->restore();
             canvas->translate(0, SkIntToScalar(25));
diff --git a/gm/coloremoji_blendmodes.cpp b/gm/coloremoji_blendmodes.cpp
index e24ba53..dccf55a 100644
--- a/gm/coloremoji_blendmodes.cpp
+++ b/gm/coloremoji_blendmodes.cpp
@@ -24,8 +24,8 @@
 #include "SkTypeface.h"
 #include "SkTypes.h"
 #include "SkUTF.h"
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 namespace skiagm {
 
@@ -50,21 +50,18 @@
         paint.setShader(SkGradientShader::MakeSweep(0, 0, colors, nullptr, SK_ARRAY_COUNT(colors),
                                                     0, &local));
 
-        sk_sp<SkTypeface> orig(sk_tool_utils::create_portable_typeface("serif",
-                                                                       SkFontStyle::Bold()));
+        sk_sp<SkTypeface> orig(ToolUtils::create_portable_typeface("serif", SkFontStyle::Bold()));
         if (nullptr == orig) {
             orig = SkTypeface::MakeDefault();
         }
-        fColorType = sk_tool_utils::emoji_typeface();
+        fColorType = ToolUtils::emoji_typeface();
 
         fBG.installPixels(SkImageInfo::Make(2, 2, kARGB_4444_SkColorType,
                                             kOpaque_SkAlphaType), gData, 4);
     }
 
     virtual SkString onShortName() override {
-        SkString name("coloremoji_blendmodes");
-        name.append(sk_tool_utils::platform_font_manager());
-        return name;
+        return SkString("coloremoji_blendmodes");
     }
 
     virtual SkISize onISize() override {
@@ -111,10 +108,9 @@
         const SkScalar h = SkIntToScalar(H);
         SkMatrix m;
         m.setScale(SkIntToScalar(6), SkIntToScalar(6));
-        auto s = SkShader::MakeBitmapShader(fBG, SkShader::kRepeat_TileMode,
-                                            SkShader::kRepeat_TileMode, &m);
+        auto s = fBG.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &m);
 
-        SkFont labelFont(sk_tool_utils::create_portable_typeface());
+        SkFont labelFont(ToolUtils::create_portable_typeface());
 
         SkPaint textP;
         textP.setAntiAlias(true);
@@ -143,7 +139,7 @@
                 SkAutoCanvasRestore arc(canvas, true);
                 canvas->clipRect(r);
                 textP.setBlendMode(gModes[i]);
-                const char* text = sk_tool_utils::emoji_sample_text();
+                const char* text    = ToolUtils::emoji_sample_text();
                 SkUnichar unichar = SkUTF::NextUTF8(&text, text + strlen(text));
                 SkASSERT(unichar >= 0);
                 canvas->drawSimpleText(&unichar, 4, kUTF32_SkTextEncoding, x+ w/10.f, y + 7.f*h/8.f,
diff --git a/gm/colorfilteralpha8.cpp b/gm/colorfilteralpha8.cpp
index 0851410..37f2a91 100644
--- a/gm/colorfilteralpha8.cpp
+++ b/gm/colorfilteralpha8.cpp
@@ -37,7 +37,7 @@
                 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
                 0.0f, 0.0f, 0.0f, 0.0f, 255.0f
         };
-        paint.setColorFilter(SkColorFilter::MakeMatrixFilterRowMajor255(opaqueGrayMatrix));
+        paint.setColorFilter(SkColorFilters::MatrixRowMajor255(opaqueGrayMatrix));
 
         canvas->drawBitmap(bitmap, 100.0f, 100.0f, &paint);
     }
diff --git a/gm/colorfilterimagefilter.cpp b/gm/colorfilterimagefilter.cpp
index 8c5fad6..9cc2ccd 100644
--- a/gm/colorfilterimagefilter.cpp
+++ b/gm/colorfilterimagefilter.cpp
@@ -26,7 +26,7 @@
         0, 1, 0, 0, amount255,
         0, 0, 1, 0, amount255,
         0, 0, 0, 1, 0 };
-    return SkColorFilter::MakeMatrixFilterRowMajor255(matrix);
+    return SkColorFilters::MatrixRowMajor255(matrix);
 }
 
 static sk_sp<SkColorFilter> cf_make_grayscale() {
@@ -36,11 +36,11 @@
     matrix[1] = matrix[6] = matrix[11] = 0.7152f;
     matrix[2] = matrix[7] = matrix[12] = 0.0722f;
     matrix[18] = 1.0f;
-    return SkColorFilter::MakeMatrixFilterRowMajor255(matrix);
+    return SkColorFilters::MatrixRowMajor255(matrix);
 }
 
 static sk_sp<SkColorFilter> cf_make_colorize(SkColor color) {
-    return SkColorFilter::MakeModeFilter(color, SkBlendMode::kSrc);
+    return SkColorFilters::Blend(color, SkBlendMode::kSrc);
 }
 
 static void sk_gm_get_colorfilters(SkTArray<sk_sp<SkColorFilter>>* array) {
@@ -58,13 +58,13 @@
 static sk_sp<SkShader> sh_make_lineargradient0() {
     const SkPoint pts[] = { { 0, 0 }, { 100, 100 } };
     const SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE };
-    return SkGradientShader::MakeLinear(pts, colors, nullptr, 3, SkShader::kRepeat_TileMode);
+    return SkGradientShader::MakeLinear(pts, colors, nullptr, 3, SkTileMode::kRepeat);
 }
 
 static sk_sp<SkShader> sh_make_lineargradient1() {
     const SkPoint pts[] = { { 0, 0 }, { 100, 100 } };
     const SkColor colors[] = { SK_ColorRED, 0x0000FF00, SK_ColorBLUE };
-    return SkGradientShader::MakeLinear(pts, colors, nullptr, 3, SkShader::kRepeat_TileMode);
+    return SkGradientShader::MakeLinear(pts, colors, nullptr, 3, SkTileMode::kRepeat);
 }
 
 static sk_sp<SkShader> sh_make_image() {
@@ -72,7 +72,7 @@
     if (!image) {
         return nullptr;
     }
-    return image->makeShader(SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode);
+    return image->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat);
 }
 
 static void sk_gm_get_shaders(SkTDArray<SkShader*>* array) {
@@ -178,7 +178,7 @@
     SkAutoCanvasRestore autoCanvasRestore(canvas, false);
     SkColorMatrix cm;
     cm.setSaturation(0.0f);
-    sk_sp<SkColorFilter> cf(SkColorFilter::MakeMatrixFilterRowMajor255(cm.fMat));
+    sk_sp<SkColorFilter> cf(SkColorFilters::MatrixRowMajor255(cm.fMat));
     SkPaint p;
     p.setImageFilter(SkColorFilterImageFilter::Make(std::move(cf), nullptr));
     canvas->saveLayer(nullptr, &p);
@@ -204,7 +204,7 @@
     const SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
     *shaders.append() = SkGradientShader::MakeTwoPointConical({0, 0}, 50, {0, 0}, 150,
                                                               colors, nullptr, 2,
-                                                              SkShader::kClamp_TileMode).release();
+                                                              SkTileMode::kClamp).release();
 
     SkPaint paint;
     SkRect r = SkRect::MakeWH(120, 120);
@@ -226,12 +226,12 @@
     }
 }
 
-DEF_SIMPLE_GM(mixershader, canvas, 800, 700) {
-    auto shaderA = GetResourceAsImage("images/mandrill_128.png")->makeShader(SkShader::kClamp_TileMode,
-                                                                      SkShader::kClamp_TileMode);
+template <typename Maker> void do_mixershader(SkCanvas* canvas, Maker&& maker) {
+    auto shaderA = GetResourceAsImage("images/mandrill_128.png")->makeShader(SkTileMode::kClamp,
+                                                                             SkTileMode::kClamp);
     const SkColor colors[] = { SK_ColorGREEN, 0 };
     auto shaderB = SkGradientShader::MakeRadial({60, 60}, 55, colors, nullptr, 2,
-                                                SkShader::kClamp_TileMode,
+                                                SkTileMode::kClamp,
                                                 SkGradientShader::kInterpolateColorsInPremul_Flag,
                                                 nullptr);
     const SkBlendMode modes[] = {
@@ -247,7 +247,7 @@
         const int count = 6;
         for (int x = 0; x < count; ++x) {
             const float t = x * 1.0f / (count - 1);
-            paint.setShader(SkShader::MakeCompose(shaderA, shaderB, mode, t));
+            paint.setShader(maker(shaderA, shaderB, mode, t));
             canvas->drawRect(r, paint);
             canvas->translate(r.width() + 10, 0);
         }
@@ -255,3 +255,16 @@
         canvas->translate(0, r.height() + 20);
     }
 }
+
+DEF_SIMPLE_GM(mixershader, canvas, 800, 700) {
+    do_mixershader(canvas, [](sk_sp<SkShader> a, sk_sp<SkShader> b, SkBlendMode mode, float t) {
+        auto sh = SkShaders::Blend(mode, a, b);
+        return SkShaders::Lerp(t, a, sh);
+    });
+}
+
+DEF_SIMPLE_GM(mixershader2, canvas, 800, 700) {
+    do_mixershader(canvas, [](sk_sp<SkShader> a, sk_sp<SkShader> b, SkBlendMode mode, float t) {
+        return SkShaders::Lerp(t, a, SkShaders::Blend(mode, a, b));
+    });
+}
diff --git a/gm/colorfilters.cpp b/gm/colorfilters.cpp
index 2accf53..b0d529a 100644
--- a/gm/colorfilters.cpp
+++ b/gm/colorfilters.cpp
@@ -20,7 +20,7 @@
         SK_ColorCYAN, SK_ColorMAGENTA, SK_ColorYELLOW,
     };
     return SkGradientShader::MakeLinear(pts, colors, nullptr, SK_ARRAY_COUNT(colors),
-                                        SkShader::kClamp_TileMode);
+                                        SkTileMode::kClamp);
 }
 
 typedef void (*InstallPaint)(SkPaint*, uint32_t, uint32_t);
diff --git a/gm/colormatrix.cpp b/gm/colormatrix.cpp
index cd49168..41b2da6 100644
--- a/gm/colormatrix.cpp
+++ b/gm/colormatrix.cpp
@@ -14,11 +14,11 @@
 #define HEIGHT 500
 
 static void set_color_matrix(SkPaint* paint, const SkColorMatrix& matrix) {
-    paint->setColorFilter(SkColorFilter::MakeMatrixFilterRowMajor255(matrix.fMat));
+    paint->setColorFilter(SkColorFilters::MatrixRowMajor255(matrix.fMat));
 }
 
 static void set_array(SkPaint* paint, const SkScalar array[]) {
-    paint->setColorFilter(SkColorFilter::MakeMatrixFilterRowMajor255(array));
+    paint->setColorFilter(SkColorFilters::MatrixRowMajor255(array));
 }
 
 class ColorMatrixGM : public skiagm::GM {
@@ -68,7 +68,7 @@
         SkColor colors[] = {0x00000000, 0xFFFFFFFF};
         SkPaint paint;
         paint.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, 2,
-                                                     SkShader::kClamp_TileMode));
+                                                     SkTileMode::kClamp));
         canvas.drawRect(SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height)), paint);
         return SkImage::MakeFromBitmap(bm);
     }
diff --git a/gm/colorwheel.cpp b/gm/colorwheel.cpp
index 230b85c..2f2d1a3 100644
--- a/gm/colorwheel.cpp
+++ b/gm/colorwheel.cpp
@@ -8,8 +8,8 @@
 #include "Resources.h"
 #include "SkData.h"
 #include "SkImage.h"
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 static void draw_image(SkCanvas* canvas, const char* resource, int x, int y) {
     sk_sp<SkImage> image(GetResourceAsImage(resource));
@@ -31,7 +31,7 @@
   background and compression artifacts.
  */
 DEF_SIMPLE_GM(colorwheel, canvas, 256, 256) {
-    sk_tool_utils::draw_checkerboard(canvas);
+    ToolUtils::draw_checkerboard(canvas);
     draw_image(canvas, "images/color_wheel.png", 0, 0);  // top left
     draw_image(canvas, "images/color_wheel.gif", 128, 0);  // top right
     draw_image(canvas, "images/color_wheel.webp", 0, 128);  // bottom left
@@ -40,7 +40,7 @@
 
 DEF_SIMPLE_GM(colorwheelnative, canvas, 128, 28) {
     SkPaint paint;
-    SkFont font(sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle::Bold()), 18);
+    SkFont  font(ToolUtils::create_portable_typeface("sans-serif", SkFontStyle::Bold()), 18);
     font.setEdging(SkFont::Edging::kAlias);
 
     canvas->clear(SK_ColorLTGRAY);
diff --git a/gm/complexclip.cpp b/gm/complexclip.cpp
index 415151f..c0a64c9 100644
--- a/gm/complexclip.cpp
+++ b/gm/complexclip.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkFont.h"
 #include "SkPath.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -69,7 +69,7 @@
         SkPath clipB;
         clipB.addPoly({{40,  10}, {190, 15}, {195, 190}, {40,  185}, {155, 100}}, false).close();
 
-        SkFont font(sk_tool_utils::create_portable_typeface(), 20);
+        SkFont font(ToolUtils::create_portable_typeface(), 20);
 
         constexpr struct {
             SkClipOp fOp;
diff --git a/gm/complexclip3.cpp b/gm/complexclip3.cpp
index afb5a5a..b2b2a57 100644
--- a/gm/complexclip3.cpp
+++ b/gm/complexclip3.cpp
@@ -4,10 +4,10 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkPath.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #include <utility>
 
@@ -53,7 +53,7 @@
         SkPaint paint;
         paint.setAntiAlias(true);
 
-        SkFont font(sk_tool_utils::create_portable_typeface(), 20);
+        SkFont font(ToolUtils::create_portable_typeface(), 20);
 
         constexpr struct {
             SkClipOp    fOp;
diff --git a/gm/complexclip_blur_tiled.cpp b/gm/complexclip_blur_tiled.cpp
index 47234da..f69c40d 100644
--- a/gm/complexclip_blur_tiled.cpp
+++ b/gm/complexclip_blur_tiled.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
 #include "SkBlurImageFilter.h"
+#include "SkClipOpPriv.h"
 #include "SkRRect.h"
 #include "SkSurface.h"
-#include "SkClipOpPriv.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #define WIDTH 512
 #define HEIGHT 512
@@ -38,7 +38,7 @@
         SkRect bounds = canvas->getLocalClipBounds();
         int ts = SkScalarCeilToInt(tileSize);
         SkImageInfo info = SkImageInfo::MakeN32Premul(ts, ts);
-        auto tileSurface(sk_tool_utils::makeSurface(canvas, info));
+        auto           tileSurface(ToolUtils::makeSurface(canvas, info));
         SkCanvas* tileCanvas = tileSurface->getCanvas();
         for (SkScalar y = bounds.top(); y < bounds.bottom(); y += tileSize) {
             for (SkScalar x = bounds.left(); x < bounds.right(); x += tileSize) {
diff --git a/gm/composeshader.cpp b/gm/composeshader.cpp
index 8033ff7..46e3e4d 100644
--- a/gm/composeshader.cpp
+++ b/gm/composeshader.cpp
@@ -24,15 +24,15 @@
     pts[1].set(SkIntToScalar(100), 0);
     colors[0] = SK_ColorRED;
     colors[1] = SK_ColorBLUE;
-    auto shaderA = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode);
+    auto shaderA = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
 
     pts[0].set(0, 0);
     pts[1].set(0, SkIntToScalar(100));
     colors[0] = SK_ColorBLACK;
     colors[1] = SkColorSetARGB(0x80, 0, 0, 0);
-    auto shaderB = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode);
+    auto shaderB = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
 
-    return SkShader::MakeComposeShader(std::move(shaderA), std::move(shaderB), mode);
+    return SkShaders::Blend(mode, std::move(shaderA), std::move(shaderB));
 }
 
 class ComposeShaderGM : public skiagm::GM {
@@ -93,7 +93,7 @@
         for (size_t y = 0; y < SK_ARRAY_COUNT(shaders); ++y) {
             canvas->save();
             for (int alpha = 0xFF; alpha > 0; alpha -= 0x28) {
-                paint.setAlpha(0xFF);
+                paint.setAlphaf(1.0f);
                 paint.setShader(nullptr);
                 canvas->drawRect(r, paint);
 
@@ -147,7 +147,7 @@
     pts[1].set(SkIntToScalar(length), 0);
     colors[0] = SK_ColorBLUE;
     colors[1] = SkColorSetARGB(0, 0, 0, 0xFF);
-    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode);
+    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
 }
 
 
@@ -161,10 +161,9 @@
         draw_alpha8_bm(&fAlpha8Bitmap, squareLength);
         SkMatrix s;
         s.reset();
-        fColorBitmapShader = SkShader::MakeBitmapShader(fColorBitmap, SkShader::kRepeat_TileMode,
-                                                        SkShader::kRepeat_TileMode, &s);
-        fAlpha8BitmapShader = SkShader::MakeBitmapShader(fAlpha8Bitmap, SkShader::kRepeat_TileMode,
-                                                         SkShader::kRepeat_TileMode, &s);
+        fColorBitmapShader = fColorBitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &s);
+        fAlpha8BitmapShader = fAlpha8Bitmap.makeShader(SkTileMode::kRepeat,
+                                                       SkTileMode::kRepeat, &s);
         fLinearGradientShader = make_linear_gradient_shader(squareLength);
     }
 
@@ -181,9 +180,9 @@
 
         sk_sp<SkShader> shaders[] = {
             // gradient should appear over color bitmap
-            SkShader::MakeComposeShader(fLinearGradientShader, fColorBitmapShader, mode),
+            SkShaders::Blend(mode, fLinearGradientShader, fColorBitmapShader),
             // gradient should appear over alpha8 bitmap colorized by the paint color
-            SkShader::MakeComposeShader(fLinearGradientShader, fAlpha8BitmapShader, mode),
+            SkShaders::Blend(mode, fLinearGradientShader, fAlpha8BitmapShader),
         };
 
         SkPaint paint;
@@ -253,8 +252,7 @@
     sk_sp<SkImage> skSrc = SkImage::MakeFromBitmap(skBitmap);
     sk_sp<SkImage> skMaskImage = SkImage::MakeFromBitmap(skMask);
     paint.setShader(
-        SkShader::MakeComposeShader(skMaskImage->makeShader(), skSrc->makeShader(),
-                                    SkBlendMode::kSrcIn));
+        SkShaders::Blend(SkBlendMode::kSrcIn, skMaskImage->makeShader(), skSrc->makeShader()));
     canvas->drawRect(r, paint);
 }
 
@@ -263,13 +261,13 @@
 static sk_sp<SkShader> make_src_shader(SkScalar size) {
     const SkPoint pts[] = { { 0, 0 }, { 0, size } };
     const SkColor colors[] = { 0xFF0000FF, 0x000000FF };
-    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode);
+    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
 }
 
 static sk_sp<SkShader> make_dst_shader(SkScalar size) {
     const SkPoint pts[] = { { 0, 0 }, { size, 0 } };
     const SkColor colors[] = { SK_ColorRED, 0x00FF0000 };
-    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode);
+    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
 }
 
 const SkScalar gCellSize = 100;
@@ -297,7 +295,7 @@
                           SkBlendMode mode, SkAlpha alpha) {
     SkPaint p;
     p.setAlpha(alpha);
-    p.setShader(SkShader::MakeCompose(dst, src, mode));
+    p.setShader(SkShaders::Blend(mode, dst, src));
     canvas->drawRect(SkRect::MakeWH(gCellSize, gCellSize), p);
 }
 
diff --git a/gm/compositor_quads.cpp b/gm/compositor_quads.cpp
new file mode 100644
index 0000000..ce4bc23
--- /dev/null
+++ b/gm/compositor_quads.cpp
@@ -0,0 +1,1039 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+
+#if SK_SUPPORT_GPU
+
+#include "Resources.h"
+#include "SkColorMatrixFilter.h"
+#include "SkFont.h"
+#include "SkGradientShader.h"
+#include "SkLineClipper.h"
+#include "SkMorphologyImageFilter.h"
+#include "SkPaintFilterCanvas.h"
+#include "SkShaderMaskFilter.h"
+#include "YUVUtils.h"
+
+#include <array>
+
+// This GM mimics the draw calls used by complex compositors that focus on drawing rectangles
+// and quadrilaterals with per-edge AA, with complex images, effects, and seamless tiling.
+// It will be updated to reflect the patterns seen in Chromium's SkiaRenderer. It is currently
+// restricted to adding draw ops directly in Ganesh since there is no fully-specified public API.
+
+static constexpr SkScalar kTileWidth = 40;
+static constexpr SkScalar kTileHeight = 30;
+
+static constexpr int kRowCount = 4;
+static constexpr int kColCount = 3;
+
+// To mimic Chromium's BSP clipping strategy, a set of three lines formed by triangle edges
+// of the below points are used to clip against the regular tile grid. The tile grid occupies
+// a 120 x 120 rectangle (40px * 3 cols by 30px * 4 rows).
+static constexpr SkPoint kClipP1 = {1.75f * kTileWidth, 0.8f * kTileHeight};
+static constexpr SkPoint kClipP2 = {0.6f * kTileWidth, 2.f * kTileHeight};
+static constexpr SkPoint kClipP3 = {2.9f * kTileWidth, 3.5f * kTileHeight};
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+// Utilities for operating on lines and tiles
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+// p0 and p1 form a segment contained the tile grid, so extends them by a large enough margin
+// that the output points stored in 'line' are outside the tile grid (thus effectively infinite).
+static void clipping_line_segment(const SkPoint& p0, const SkPoint& p1, SkPoint line[2]) {
+    SkVector v = p1 - p0;
+    // 10f was chosen as a balance between large enough to scale the currently set clip
+    // points outside of the tile grid, but small enough to preserve precision.
+    line[0] = p0 - v * 10.f;
+    line[1] = p1 + v * 10.f;
+}
+
+// Returns true if line segment (p0-p1) intersects with line segment (l0-l1); if true is returned,
+// the intersection point is stored in 'intersect'.
+static bool intersect_line_segments(const SkPoint& p0, const SkPoint& p1,
+                                    const SkPoint& l0, const SkPoint& l1, SkPoint* intersect) {
+    static constexpr SkScalar kHorizontalTolerance = 0.01f; // Pretty conservative
+
+    // Use doubles for accuracy, since the clipping strategy used below can create T
+    // junctions, and lower precision could artificially create gaps
+    double pY = (double) p1.fY - (double) p0.fY;
+    double pX = (double) p1.fX - (double) p0.fX;
+    double lY = (double) l1.fY - (double) l0.fY;
+    double lX = (double) l1.fX - (double) l0.fX;
+    double plY = (double) p0.fY - (double) l0.fY;
+    double plX = (double) p0.fX - (double) l0.fX;
+    if (SkScalarNearlyZero(pY, kHorizontalTolerance)) {
+        if (SkScalarNearlyZero(lY, kHorizontalTolerance)) {
+            // Two horizontal lines
+            return false;
+        } else {
+            // Recalculate but swap p and l
+            return intersect_line_segments(l0, l1, p0, p1, intersect);
+        }
+    }
+
+    // Up to now, the line segments do not form an invalid intersection
+    double lNumerator = plX * pY - plY * pX;
+    double lDenom = lX * pY - lY * pX;
+    if (SkScalarNearlyZero(lDenom)) {
+        // Parallel or identical
+        return false;
+    }
+
+    // Calculate alphaL that provides the intersection point along (l0-l1), e.g. l0+alphaL*(l1-l0)
+    double alphaL = lNumerator / lDenom;
+    if (alphaL < 0.0 || alphaL > 1.0) {
+        // Outside of the l segment
+        return false;
+    }
+
+    // Calculate alphaP from the valid alphaL (since it could be outside p segment)
+    // double alphaP = (alphaL * l.fY - pl.fY) / p.fY;
+    double alphaP = (alphaL * lY - plY) / pY;
+    if (alphaP < 0.0 || alphaP > 1.0) {
+        // Outside of p segment
+        return false;
+    }
+
+    // Is valid, so calculate the actual intersection point
+    *intersect = l1 * SkScalar(alphaL) + l0 * SkScalar(1.0 - alphaL);
+    return true;
+}
+
+// Draw a line through the two points, outset by a fixed length in screen space
+static void draw_outset_line(SkCanvas* canvas, const SkMatrix& local, const SkPoint pts[2],
+                             const SkPaint& paint) {
+    static constexpr SkScalar kLineOutset = 10.f;
+    SkPoint mapped[2];
+    local.mapPoints(mapped, pts, 2);
+    SkVector v = mapped[1] - mapped[0];
+    v.setLength(v.length() + kLineOutset);
+    canvas->drawLine(mapped[1] - v, mapped[0] + v, paint);
+}
+
+// Draw grid of red lines at interior tile boundaries.
+static void draw_tile_boundaries(SkCanvas* canvas, const SkMatrix& local) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setColor(SK_ColorRED);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(0.f);
+    for (int x = 1; x < kColCount; ++x) {
+        SkPoint pts[] = {{x * kTileWidth, 0}, {x * kTileWidth, kRowCount * kTileHeight}};
+        draw_outset_line(canvas, local, pts, paint);
+    }
+    for (int y = 1; y < kRowCount; ++y) {
+        SkPoint pts[] = {{0, y * kTileHeight}, {kTileWidth * kColCount, y * kTileHeight}};
+        draw_outset_line(canvas, local, pts, paint);
+    }
+}
+
+// Draw the arbitrary clipping/split boundaries that intersect the tile grid as green lines
+static void draw_clipping_boundaries(SkCanvas* canvas, const SkMatrix& local) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setColor(SK_ColorGREEN);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(0.f);
+
+    // Clip the "infinite" line segments to a rectangular region outside the tile grid
+    SkRect border = SkRect::MakeWH(kTileWidth * kColCount, kTileHeight * kRowCount);
+
+    // Draw p1 to p2
+    SkPoint line[2];
+    SkPoint clippedLine[2];
+    clipping_line_segment(kClipP1, kClipP2, line);
+    SkAssertResult(SkLineClipper::IntersectLine(line, border, clippedLine));
+    draw_outset_line(canvas, local, clippedLine, paint);
+
+    // Draw p2 to p3
+    clipping_line_segment(kClipP2, kClipP3, line);
+    SkAssertResult(SkLineClipper::IntersectLine(line, border, clippedLine));
+    draw_outset_line(canvas, local, clippedLine, paint);
+
+    // Draw p3 to p1
+    clipping_line_segment(kClipP3, kClipP1, line);
+    SkAssertResult(SkLineClipper::IntersectLine(line, border, clippedLine));
+    draw_outset_line(canvas, local, clippedLine, paint);
+}
+
+static void draw_text(SkCanvas* canvas, const char* text) {
+    canvas->drawString(text, 0, 0, SkFont(nullptr, 12), SkPaint());
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+// Abstraction for rendering a possibly clipped tile, that can apply different effects to mimic
+// the Chromium quad types, and a generic GM template to arrange renderers x transforms in a grid
+/////////////////////////////////////////////////////////////////////////////////////////////////
+
+class ClipTileRenderer : public SkRefCntBase {
+public:
+    virtual ~ClipTileRenderer() {}
+
+    // Draw the base rect, possibly clipped by 'clip' if that is not null. The edges to antialias
+    // are specified in 'edgeAA' (to make manipulation easier than an unsigned bitfield). 'tileID'
+    // represents the location of rect within the tile grid, 'quadID' is the unique ID of the clip
+    // region within the tile (reset for each tile).
+    //
+    // The edgeAA order matches that of clip, so it refers to top, right, bottom, left.
+    // Return draw count
+    virtual int drawTile(SkCanvas* canvas, const SkRect& rect, const SkPoint clip[4],
+                          const bool edgeAA[4], int tileID, int quadID) = 0;
+
+    virtual void drawBanner(SkCanvas* canvas) = 0;
+
+    // Return draw count
+    virtual int drawTiles(SkCanvas* canvas) {
+        // All three lines in a list
+        SkPoint lines[6];
+        clipping_line_segment(kClipP1, kClipP2, lines);
+        clipping_line_segment(kClipP2, kClipP3, lines + 2);
+        clipping_line_segment(kClipP3, kClipP1, lines + 4);
+
+        bool edgeAA[4];
+        int tileID = 0;
+        int drawCount = 0;
+        for (int i = 0; i < kRowCount; ++i) {
+            for (int j = 0; j < kColCount; ++j) {
+                // The unclipped tile geometry
+                SkRect tile = SkRect::MakeXYWH(j * kTileWidth, i * kTileHeight,
+                                               kTileWidth, kTileHeight);
+                // Base edge AA flags if there are no clips; clipped lines will only turn off edges
+                edgeAA[0] = i == 0;             // Top
+                edgeAA[1] = j == kColCount - 1; // Right
+                edgeAA[2] = i == kRowCount - 1; // Bottom
+                edgeAA[3] = j == 0;             // Left
+
+                // Now clip against the 3 lines formed by kClipPx and split into general purpose
+                // quads as needed.
+                int quadCount = 0;
+                drawCount += this->clipTile(canvas, tileID, tile, nullptr, edgeAA, lines, 3,
+                                            &quadCount);
+                tileID++;
+            }
+        }
+
+        return drawCount;
+    }
+
+protected:
+    SkCanvas::QuadAAFlags maskToFlags(const bool edgeAA[4]) const {
+        unsigned flags = (edgeAA[0] * SkCanvas::kTop_QuadAAFlag) |
+                         (edgeAA[1] * SkCanvas::kRight_QuadAAFlag) |
+                         (edgeAA[2] * SkCanvas::kBottom_QuadAAFlag) |
+                         (edgeAA[3] * SkCanvas::kLeft_QuadAAFlag);
+        return static_cast<SkCanvas::QuadAAFlags>(flags);
+    }
+
+    // Recursively splits the quadrilateral against the segments stored in 'lines', which must be
+    // 2 * lineCount long. Increments 'quadCount' for each split quadrilateral, and invokes the
+    // drawTile at leaves.
+    int clipTile(SkCanvas* canvas, int tileID, const SkRect& baseRect, const SkPoint quad[4],
+                  const bool edgeAA[4], const SkPoint lines[], int lineCount, int* quadCount) {
+        if (lineCount == 0) {
+            // No lines, so end recursion by drawing the tile. If the tile was never split then
+            // 'quad' remains null so that drawTile() can differentiate how it should draw.
+            int draws = this->drawTile(canvas, baseRect, quad, edgeAA, tileID, *quadCount);
+            *quadCount = *quadCount + 1;
+            return draws;
+        }
+
+        static constexpr int kTL = 0; // Top-left point index in points array
+        static constexpr int kTR = 1; // Top-right point index in points array
+        static constexpr int kBR = 2; // Bottom-right point index in points array
+        static constexpr int kBL = 3; // Bottom-left point index in points array
+        static constexpr int kS0 = 4; // First split point index in points array
+        static constexpr int kS1 = 5; // Second split point index in points array
+
+        SkPoint points[6];
+        if (quad) {
+            // Copy the original 4 points into set of points to consider
+            for (int i = 0; i < 4; ++i) {
+                points[i] = quad[i];
+            }
+        } else {
+            //  Haven't been split yet, so fill in based on the rect
+            baseRect.toQuad(points);
+        }
+
+        // Consider the first line against the 4 quad edges in tile, which should have 0,1, or 2
+        // intersection points since the tile is convex.
+        int splitIndices[2]; // Edge that was intersected
+        int intersectionCount = 0;
+        for (int i = 0; i < 4; ++i) {
+            SkPoint intersect;
+            if (intersect_line_segments(points[i], points[i == 3 ? 0 : i + 1],
+                                        lines[0], lines[1], &intersect)) {
+                // If the intersected point is the same as the last found intersection, the line
+                // runs through a vertex, so don't double count it
+                bool duplicate = false;
+                for (int j = 0; j < intersectionCount; ++j) {
+                    if (SkScalarNearlyZero((intersect - points[kS0 + j]).length())) {
+                        duplicate = true;
+                        break;
+                    }
+                }
+                if (!duplicate) {
+                    points[kS0 + intersectionCount] = intersect;
+                    splitIndices[intersectionCount] = i;
+                    intersectionCount++;
+                }
+            }
+        }
+
+        if (intersectionCount < 2) {
+            // Either the first line never intersected the quad (count == 0), or it intersected at a
+            // single vertex without going through quad area (count == 1), so check next line
+            return this->clipTile(
+                    canvas, tileID, baseRect, quad, edgeAA, lines + 2, lineCount - 1, quadCount);
+        }
+
+        SkASSERT(intersectionCount == 2);
+        // Split the tile points into 2+ sub quads and recurse to the next lines, which may or may
+        // not further split the tile. Since the configurations are relatively simple, the possible
+        // splits are hardcoded below; subtile quad orderings are such that the sub tiles remain in
+        // clockwise order and match expected edges for QuadAAFlags. subtile indices refer to the
+        // 6-element 'points' array.
+        SkSTArray<3, std::array<int, 4>> subtiles;
+        int s2 = -1; // Index of an original vertex chosen for a artificial split
+        if (splitIndices[1] - splitIndices[0] == 2) {
+            // Opposite edges, so the split trivially forms 2 sub quads
+            if (splitIndices[0] == 0) {
+                subtiles.push_back({{kTL, kS0, kS1, kBL}});
+                subtiles.push_back({{kS0, kTR, kBR, kS1}});
+            } else {
+                subtiles.push_back({{kTL, kTR, kS0, kS1}});
+                subtiles.push_back({{kS1, kS0, kBR, kBL}});
+            }
+        } else {
+            // Adjacent edges, which makes for a more complicated split, since it forms a degenerate
+            // quad (triangle) and a pentagon that must be artificially split. The pentagon is split
+            // using one of the original vertices (remembered in 's2'), which adds an additional
+            // degenerate quad, but ensures there are no T-junctions.
+            switch(splitIndices[0]) {
+                case 0:
+                    // Could be connected to edge 1 or edge 3
+                    if (splitIndices[1] == 1) {
+                        s2 = kBL;
+                        subtiles.push_back({{kS0, kTR, kS1, kS0}}); // degenerate
+                        subtiles.push_back({{kTL, kS0, edgeAA[0] ? kS0 : kBL, kBL}}); // degenerate
+                        subtiles.push_back({{kS0, kS1, kBR, kBL}});
+                    } else {
+                        SkASSERT(splitIndices[1] == 3);
+                        s2 = kBR;
+                        subtiles.push_back({{kTL, kS0, kS1, kS1}}); // degenerate
+                        subtiles.push_back({{kS1, edgeAA[3] ? kS1 : kBR, kBR, kBL}}); // degenerate
+                        subtiles.push_back({{kS0, kTR, kBR, kS1}});
+                    }
+                    break;
+                case 1:
+                    // Edge 0 handled above, should only be connected to edge 2
+                    SkASSERT(splitIndices[1] == 2);
+                    s2 = kTL;
+                    subtiles.push_back({{kS0, kS0, kBR, kS1}}); // degenerate
+                    subtiles.push_back({{kTL, kTR, kS0, edgeAA[1] ? kS0 : kTL}}); // degenerate
+                    subtiles.push_back({{kTL, kS0, kS1, kBL}});
+                    break;
+                case 2:
+                    // Edge 1 handled above, should only be connected to edge 3
+                    SkASSERT(splitIndices[1] == 3);
+                    s2 = kTR;
+                    subtiles.push_back({{kS1, kS0, kS0, kBL}}); // degenerate
+                    subtiles.push_back({{edgeAA[2] ? kS0 : kTR, kTR, kBR, kS0}}); // degenerate
+                    subtiles.push_back({{kTL, kTR, kS0, kS1}});
+                    break;
+                case 3:
+                    // Fall through, an adjacent edge split that hits edge 3 should have first found
+                    // been found with edge 0 or edge 2 for the other end
+                default:
+                    SkASSERT(false);
+                    return 0;
+            }
+        }
+
+        SkPoint sub[4];
+        bool subAA[4];
+        int draws = 0;
+        for (int i = 0; i < subtiles.count(); ++i) {
+            // Fill in the quad points and update edge AA rules for new interior edges
+            for (int j = 0; j < 4; ++j) {
+                int p = subtiles[i][j];
+                sub[j] = points[p];
+
+                int np = j == 3 ? subtiles[i][0] : subtiles[i][j + 1];
+                // The "new" edges are the edges that connect between the two split points or
+                // between a split point and the chosen s2 point. Otherwise the edge remains aligned
+                // with the original shape, so should preserve the AA setting.
+                if ((p >= kS0 && (np == s2 || np >= kS0)) ||
+                    ((np >= kS0) && (p == s2 || p >= kS0))) {
+                    // New edge
+                    subAA[j] = false;
+                } else {
+                    // The subtiles indices were arranged so that their edge ordering was still top,
+                    // right, bottom, left so 'j' can be used to access edgeAA
+                    subAA[j] = edgeAA[j];
+                }
+            }
+
+            // Split the sub quad with the next line
+            draws += this->clipTile(canvas, tileID, baseRect, sub, subAA, lines + 2, lineCount - 1,
+                                    quadCount);
+        }
+        return draws;
+    }
+};
+
+static constexpr int kMatrixCount = 5;
+
+class CompositorGM : public skiagm::GM {
+public:
+    CompositorGM(const char* name, sk_sp<ClipTileRenderer> renderer)
+            : fName(name) {
+        fRenderers.push_back(std::move(renderer));
+    }
+    CompositorGM(const char* name, const SkTArray<sk_sp<ClipTileRenderer>> renderers)
+            : fRenderers(renderers)
+            , fName(name) {}
+
+protected:
+    SkISize onISize() override {
+        // The GM draws a grid of renderers (rows) x transforms (col). Within each cell, the
+        // renderer draws the transformed tile grid, which is approximately
+        // (kColCount*kTileWidth, kRowCount*kTileHeight), although it has additional line
+        // visualizations and can be transformed outside of those rectangular bounds (i.e. persp),
+        // so pad the cell dimensions to be conservative. Must also account for the banner text.
+        static constexpr SkScalar kCellWidth = 1.3f * kColCount * kTileWidth;
+        static constexpr SkScalar kCellHeight = 1.3f * kRowCount * kTileHeight;
+        return SkISize::Make(SkScalarRoundToInt(kCellWidth * kMatrixCount + 175.f),
+                             SkScalarRoundToInt(kCellHeight * fRenderers.count() + 75.f));
+    }
+
+    SkString onShortName() override {
+        SkString fullName;
+        fullName.appendf("compositor_quads_%s", fName.c_str());
+        return fullName;
+    }
+
+    void onOnceBeforeDraw() override {
+        this->configureMatrices();
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        static constexpr SkScalar kGap = 40.f;
+        static constexpr SkScalar kBannerWidth = 120.f;
+        static constexpr SkScalar kOffset = 15.f;
+
+        SkTArray<int> drawCounts(fRenderers.count());
+        drawCounts.push_back_n(fRenderers.count(), 0);
+
+        canvas->save();
+        canvas->translate(kOffset + kBannerWidth, kOffset);
+        for (int i = 0; i < fMatrices.count(); ++i) {
+            canvas->save();
+            draw_text(canvas, fMatrixNames[i].c_str());
+
+            canvas->translate(0.f, kGap);
+            for (int j = 0; j < fRenderers.count(); ++j) {
+                canvas->save();
+                draw_tile_boundaries(canvas, fMatrices[i]);
+                draw_clipping_boundaries(canvas, fMatrices[i]);
+
+                canvas->concat(fMatrices[i]);
+                drawCounts[j] += fRenderers[j]->drawTiles(canvas);
+
+                canvas->restore();
+                // And advance to the next row
+                canvas->translate(0.f, kGap + kRowCount * kTileHeight);
+            }
+            // Reset back to the left edge
+            canvas->restore();
+            // And advance to the next column
+            canvas->translate(kGap + kColCount * kTileWidth, 0.f);
+        }
+        canvas->restore();
+
+        // Print a row header, with total draw counts
+        canvas->save();
+        canvas->translate(kOffset, kGap + 0.5f * kRowCount * kTileHeight);
+        for (int j = 0; j < fRenderers.count(); ++j) {
+            fRenderers[j]->drawBanner(canvas);
+            canvas->translate(0.f, 15.f);
+            draw_text(canvas, SkStringPrintf("Draws = %d", drawCounts[j]).c_str());
+            canvas->translate(0.f, kGap + kRowCount * kTileHeight);
+        }
+        canvas->restore();
+    }
+
+private:
+    SkTArray<sk_sp<ClipTileRenderer>> fRenderers;
+    SkTArray<SkMatrix> fMatrices;
+    SkTArray<SkString> fMatrixNames;
+
+    SkString fName;
+
+    void configureMatrices() {
+        fMatrices.reset();
+        fMatrixNames.reset();
+        fMatrices.push_back_n(kMatrixCount);
+
+        // Identity
+        fMatrices[0].setIdentity();
+        fMatrixNames.push_back(SkString("Identity"));
+
+        // Translate/scale
+        fMatrices[1].setTranslate(5.5f, 20.25f);
+        fMatrices[1].postScale(.9f, .7f);
+        fMatrixNames.push_back(SkString("T+S"));
+
+        // Rotation
+        fMatrices[2].setRotate(20.0f);
+        fMatrices[2].preTranslate(15.f, -20.f);
+        fMatrixNames.push_back(SkString("Rotate"));
+
+        // Skew
+        fMatrices[3].setSkew(.5f, .25f);
+        fMatrices[3].preTranslate(-30.f, 0.f);
+        fMatrixNames.push_back(SkString("Skew"));
+
+        // Perspective
+        SkPoint src[4];
+        SkRect::MakeWH(kColCount * kTileWidth, kRowCount * kTileHeight).toQuad(src);
+        SkPoint dst[4] = {{0, 0},
+                          {kColCount * kTileWidth + 10.f, 15.f},
+                          {kColCount * kTileWidth - 28.f, kRowCount * kTileHeight + 40.f},
+                          {25.f, kRowCount * kTileHeight - 15.f}};
+        SkAssertResult(fMatrices[4].setPolyToPoly(src, dst, 4));
+        fMatrices[4].preTranslate(0.f, 10.f);
+        fMatrixNames.push_back(SkString("Perspective"));
+
+        SkASSERT(fMatrices.count() == fMatrixNames.count());
+    }
+
+    typedef skiagm::GM INHERITED;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////
+// Implementations of TileRenderer that color the clipped tiles in various ways
+////////////////////////////////////////////////////////////////////////////////////////////////
+
+class DebugTileRenderer : public ClipTileRenderer {
+public:
+
+    static sk_sp<ClipTileRenderer> Make() {
+        // Since aa override is disabled, the quad flags arg doesn't matter.
+        return sk_sp<ClipTileRenderer>(new DebugTileRenderer(SkCanvas::kAll_QuadAAFlags, false));
+    }
+
+    static sk_sp<ClipTileRenderer> MakeAA() {
+        return sk_sp<ClipTileRenderer>(new DebugTileRenderer(SkCanvas::kAll_QuadAAFlags, true));
+    }
+
+    static sk_sp<ClipTileRenderer> MakeNonAA() {
+        return sk_sp<ClipTileRenderer>(new DebugTileRenderer(SkCanvas::kNone_QuadAAFlags, true));
+    }
+
+    int drawTile(SkCanvas* canvas, const SkRect& rect, const SkPoint clip[4], const bool edgeAA[4],
+                  int tileID, int quadID) override {
+        // Colorize the tile based on its grid position and quad ID
+        int i = tileID / kColCount;
+        int j = tileID % kColCount;
+
+        SkColor4f c = {(i + 1.f) / kRowCount, (j + 1.f) / kColCount, .4f, 1.f};
+        float alpha = quadID / 10.f;
+        c.fR = c.fR * (1 - alpha) + alpha;
+        c.fG = c.fG * (1 - alpha) + alpha;
+        c.fB = c.fB * (1 - alpha) + alpha;
+        c.fA = c.fA * (1 - alpha) + alpha;
+
+        SkCanvas::QuadAAFlags aaFlags = fEnableAAOverride ? fAAOverride : this->maskToFlags(edgeAA);
+        canvas->experimental_DrawEdgeAAQuad(
+                rect, clip, aaFlags, c.toSkColor(), SkBlendMode::kSrcOver);
+        return 1;
+    }
+
+    void drawBanner(SkCanvas* canvas) override {
+        draw_text(canvas, "Edge AA");
+        canvas->translate(0.f, 15.f);
+
+        SkString config;
+        static const char* kFormat = "Ext(%s) - Int(%s)";
+        if (fEnableAAOverride) {
+            SkASSERT(fAAOverride == SkCanvas::kAll_QuadAAFlags ||
+                     fAAOverride == SkCanvas::kNone_QuadAAFlags);
+            if (fAAOverride == SkCanvas::kAll_QuadAAFlags) {
+                config.appendf(kFormat, "yes", "yes");
+            } else {
+                config.appendf(kFormat, "no", "no");
+            }
+        } else {
+            config.appendf(kFormat, "yes", "no");
+        }
+        draw_text(canvas, config.c_str());
+    }
+
+private:
+    SkCanvas::QuadAAFlags fAAOverride;
+    bool fEnableAAOverride;
+
+    DebugTileRenderer(SkCanvas::QuadAAFlags aa, bool enableAAOverrde)
+            : fAAOverride(aa)
+            , fEnableAAOverride(enableAAOverrde) {}
+
+    typedef ClipTileRenderer INHERITED;
+};
+
+// Tests tmp_drawEdgeAAQuad
+class SolidColorRenderer : public ClipTileRenderer {
+public:
+
+    static sk_sp<ClipTileRenderer> Make(const SkColor4f& color) {
+        return sk_sp<ClipTileRenderer>(new SolidColorRenderer(color));
+    }
+
+    int drawTile(SkCanvas* canvas, const SkRect& rect, const SkPoint clip[4], const bool edgeAA[4],
+                  int tileID, int quadID) override {
+        canvas->experimental_DrawEdgeAAQuad(rect, clip, this->maskToFlags(edgeAA),
+                                            fColor.toSkColor(), SkBlendMode::kSrcOver);
+        return 1;
+    }
+
+    void drawBanner(SkCanvas* canvas) override {
+        draw_text(canvas, "Solid Color");
+    }
+
+private:
+    SkColor4f fColor;
+
+    SolidColorRenderer(const SkColor4f& color) : fColor(color) {}
+
+    typedef ClipTileRenderer INHERITED;
+};
+
+// Tests drawEdgeAAImageSet(), but can batch the entries together in different ways
+class TextureSetRenderer : public ClipTileRenderer {
+public:
+
+    static sk_sp<ClipTileRenderer> MakeUnbatched(sk_sp<SkImage> image) {
+        return Make("Texture", "", std::move(image), nullptr, nullptr, nullptr, nullptr,
+                    1.f, true, 0);
+    }
+
+    static sk_sp<ClipTileRenderer> MakeBatched(sk_sp<SkImage> image, int transformCount) {
+        const char* subtitle = transformCount == 0 ? "" : "w/ xforms";
+        return Make("Texture Set", subtitle, std::move(image), nullptr, nullptr, nullptr, nullptr,
+                    1.f, false, transformCount);
+    }
+
+    static sk_sp<ClipTileRenderer> MakeShader(const char* name, sk_sp<SkImage> image,
+                                              sk_sp<SkShader> shader, bool local) {
+        return Make("Shader", name, std::move(image), std::move(shader),
+                    nullptr, nullptr, nullptr, 1.f, local, 0);
+    }
+
+    static sk_sp<ClipTileRenderer> MakeColorFilter(const char* name, sk_sp<SkImage> image,
+                                                   sk_sp<SkColorFilter> filter) {
+        return Make("Color Filter", name, std::move(image), nullptr, std::move(filter), nullptr,
+                    nullptr, 1.f, false, 0);
+    }
+
+    static sk_sp<ClipTileRenderer> MakeImageFilter(const char* name, sk_sp<SkImage> image,
+                                                   sk_sp<SkImageFilter> filter) {
+        return Make("Image Filter", name, std::move(image), nullptr, nullptr, std::move(filter),
+                    nullptr, 1.f, false, 0);
+    }
+
+    static sk_sp<ClipTileRenderer> MakeMaskFilter(const char* name, sk_sp<SkImage> image,
+                                                  sk_sp<SkMaskFilter> filter) {
+        return Make("Mask Filter", name, std::move(image), nullptr, nullptr, nullptr,
+                    std::move(filter), 1.f, false, 0);
+    }
+
+    static sk_sp<ClipTileRenderer> MakeAlpha(sk_sp<SkImage> image, SkScalar alpha) {
+        return Make("Alpha", SkStringPrintf("a = %.2f", alpha).c_str(), std::move(image), nullptr,
+                    nullptr, nullptr, nullptr, alpha, false, 0);
+    }
+
+    static sk_sp<ClipTileRenderer> Make(const char* topBanner, const char* bottomBanner,
+                                        sk_sp<SkImage> image, sk_sp<SkShader> shader,
+                                        sk_sp<SkColorFilter> colorFilter,
+                                        sk_sp<SkImageFilter> imageFilter,
+                                        sk_sp<SkMaskFilter> maskFilter, SkScalar paintAlpha,
+                                        bool resetAfterEachQuad, int transformCount) {
+        return sk_sp<ClipTileRenderer>(new TextureSetRenderer(topBanner, bottomBanner,
+                std::move(image), std::move(shader), std::move(colorFilter), std::move(imageFilter),
+                std::move(maskFilter), paintAlpha, resetAfterEachQuad, transformCount));
+    }
+
+    int drawTiles(SkCanvas* canvas) override {
+        int draws = this->INHERITED::drawTiles(canvas);
+        // Push the last tile set
+        draws += this->drawAndReset(canvas);
+        return draws;
+    }
+
+    int drawTile(SkCanvas* canvas, const SkRect& rect, const SkPoint clip[4], const bool edgeAA[4],
+                  int tileID, int quadID) override {
+        // Now don't actually draw the tile, accumulate it in the growing entry set
+        bool hasClip = false;
+        if (clip) {
+            // Record the four points into fDstClips
+            fDstClips.push_back_n(4, clip);
+            hasClip = true;
+        }
+
+        int matrixIdx = -1;
+        if (!fResetEachQuad && fTransformBatchCount > 0) {
+            // Handle transform batching. This works by capturing the CTM of the first tile draw,
+            // and then calculate the difference between that and future CTMs for later tiles.
+            if (fPreViewMatrices.count() == 0) {
+                fBaseCTM = canvas->getTotalMatrix();
+                fPreViewMatrices.push_back(SkMatrix::I());
+                matrixIdx = 0;
+            } else {
+                // Calculate matrix s.t. getTotalMatrix() = fBaseCTM * M
+                SkMatrix invBase;
+                if (!fBaseCTM.invert(&invBase)) {
+                    SkDebugf("Cannot invert CTM, transform batching will not be correct.\n");
+                } else {
+                    SkMatrix preView = SkMatrix::Concat(invBase, canvas->getTotalMatrix());
+                    if (preView != fPreViewMatrices[fPreViewMatrices.count() - 1]) {
+                        // Add the new matrix
+                        fPreViewMatrices.push_back(preView);
+                    } // else re-use the last matrix
+                    matrixIdx = fPreViewMatrices.count() - 1;
+                }
+            }
+        }
+
+        // This acts like the whole image is rendered over the entire tile grid, so derive local
+        // coordinates from 'rect', based on the grid to image transform.
+        SkMatrix gridToImage = SkMatrix::MakeRectToRect(SkRect::MakeWH(kColCount * kTileWidth,
+                                                                       kRowCount * kTileHeight),
+                                                        SkRect::MakeWH(fImage->width(),
+                                                                       fImage->height()),
+                                                        SkMatrix::kFill_ScaleToFit);
+        SkRect localRect = gridToImage.mapRect(rect);
+
+        // drawTextureSet automatically derives appropriate local quad from localRect if clipPtr
+        // is not null.
+        fSetEntries.push_back(
+                {fImage, localRect, rect, matrixIdx, 1.f, this->maskToFlags(edgeAA), hasClip});
+
+        if (fResetEachQuad) {
+            // Only ever draw one entry at a time
+            return this->drawAndReset(canvas);
+        } else {
+            return 0;
+        }
+    }
+
+    void drawBanner(SkCanvas* canvas) override {
+        if (fTopBanner.size() > 0) {
+            draw_text(canvas, fTopBanner.c_str());
+        }
+        canvas->translate(0.f, 15.f);
+        if (fBottomBanner.size() > 0) {
+            draw_text(canvas, fBottomBanner.c_str());
+        }
+    }
+
+private:
+    SkString fTopBanner;
+    SkString fBottomBanner;
+
+    sk_sp<SkImage> fImage;
+    sk_sp<SkShader> fShader;
+    sk_sp<SkColorFilter> fColorFilter;
+    sk_sp<SkImageFilter> fImageFilter;
+    sk_sp<SkMaskFilter> fMaskFilter;
+    SkScalar fPaintAlpha;
+
+    // Batching rules
+    bool fResetEachQuad;
+    int fTransformBatchCount;
+
+    SkTArray<SkPoint> fDstClips;
+    SkTArray<SkMatrix> fPreViewMatrices;
+    SkTArray<SkCanvas::ImageSetEntry> fSetEntries;
+
+    SkMatrix fBaseCTM;
+    int fBatchCount;
+
+    TextureSetRenderer(const char* topBanner,
+                       const char* bottomBanner,
+                       sk_sp<SkImage> image,
+                       sk_sp<SkShader> shader,
+                       sk_sp<SkColorFilter> colorFilter,
+                       sk_sp<SkImageFilter> imageFilter,
+                       sk_sp<SkMaskFilter> maskFilter,
+                       SkScalar paintAlpha,
+                       bool resetEachQuad,
+                       int transformBatchCount)
+            : fTopBanner(topBanner)
+            , fBottomBanner(bottomBanner)
+            , fImage(std::move(image))
+            , fShader(std::move(shader))
+            , fColorFilter(std::move(colorFilter))
+            , fImageFilter(std::move(imageFilter))
+            , fMaskFilter(std::move(maskFilter))
+            , fPaintAlpha(paintAlpha)
+            , fResetEachQuad(resetEachQuad)
+            , fTransformBatchCount(transformBatchCount)
+            , fBatchCount(0) {
+        SkASSERT(transformBatchCount >= 0 && (!resetEachQuad || transformBatchCount == 0));
+    }
+
+    void configureTilePaint(const SkRect& rect, SkPaint* paint) const {
+        paint->setAntiAlias(true);
+        paint->setFilterQuality(kLow_SkFilterQuality);
+        paint->setBlendMode(SkBlendMode::kSrcOver);
+
+        // Send non-white RGB, that should be ignored
+        paint->setColor4f({1.f, 0.4f, 0.25f, fPaintAlpha}, nullptr);
+
+
+        if (fShader) {
+            if (fResetEachQuad) {
+                // Apply a local transform in the shader to map from the tile rectangle to (0,0,w,h)
+                static const SkRect kTarget = SkRect::MakeWH(kTileWidth, kTileHeight);
+                SkMatrix local = SkMatrix::MakeRectToRect(kTarget, rect,
+                                                          SkMatrix::kFill_ScaleToFit);
+                paint->setShader(fShader->makeWithLocalMatrix(local));
+            } else {
+                paint->setShader(fShader);
+            }
+        }
+
+        paint->setColorFilter(fColorFilter);
+        paint->setImageFilter(fImageFilter);
+        paint->setMaskFilter(fMaskFilter);
+    }
+
+    int drawAndReset(SkCanvas* canvas) {
+        // Early out if there's nothing to draw
+        if (fSetEntries.count() == 0) {
+            SkASSERT(fDstClips.count() == 0 && fPreViewMatrices.count() == 0);
+            return 0;
+        }
+
+        if (!fResetEachQuad && fTransformBatchCount > 0) {
+            // A batch is completed
+            fBatchCount++;
+            if (fBatchCount < fTransformBatchCount) {
+                // Haven't hit the point to submit yet, but end the current tile
+                return 0;
+            }
+
+            // Submitting all tiles back to where fBaseCTM was the canvas' matrix, while the
+            // canvas currently has the CTM of the last tile batch, so reset it.
+            canvas->setMatrix(fBaseCTM);
+        }
+
+#ifdef SK_DEBUG
+        int expectedDstClipCount = 0;
+        for (int i = 0; i < fSetEntries.count(); ++i) {
+            expectedDstClipCount += 4 * fSetEntries[i].fHasClip;
+            SkASSERT(fSetEntries[i].fMatrixIndex < 0 ||
+                     fSetEntries[i].fMatrixIndex < fPreViewMatrices.count());
+        }
+        SkASSERT(expectedDstClipCount == fDstClips.count());
+#endif
+
+        SkPaint paint;
+        SkRect lastTileRect = fSetEntries[fSetEntries.count() - 1].fDstRect;
+        this->configureTilePaint(lastTileRect, &paint);
+
+        canvas->experimental_DrawEdgeAAImageSet(
+                fSetEntries.begin(), fSetEntries.count(), fDstClips.begin(),
+                fPreViewMatrices.begin(), &paint, SkCanvas::kFast_SrcRectConstraint);
+
+        // Reset for next tile
+        fDstClips.reset();
+        fPreViewMatrices.reset();
+        fSetEntries.reset();
+        fBatchCount = 0;
+
+        return 1;
+    }
+
+    typedef ClipTileRenderer INHERITED;
+};
+
+class YUVTextureSetRenderer : public ClipTileRenderer {
+public:
+    static sk_sp<ClipTileRenderer> MakeFromJPEG(sk_sp<SkData> imageData) {
+        return sk_sp<ClipTileRenderer>(new YUVTextureSetRenderer(std::move(imageData)));
+    }
+
+    int drawTiles(SkCanvas* canvas) override {
+        // Refresh the SkImage at the start, so that it's not attempted for every set entry
+        if (fYUVData) {
+            fImage = fYUVData->refImage(canvas->getGrContext());
+            if (!fImage) {
+                return 0;
+            }
+        }
+
+        int draws = this->INHERITED::drawTiles(canvas);
+        // Push the last tile set
+        draws += this->drawAndReset(canvas);
+        return draws;
+    }
+
+    int drawTile(SkCanvas* canvas, const SkRect& rect, const SkPoint clip[4], const bool edgeAA[4],
+                  int tileID, int quadID) override {
+        SkASSERT(fImage);
+        // Now don't actually draw the tile, accumulate it in the growing entry set
+        bool hasClip = false;
+        if (clip) {
+            // Record the four points into fDstClips
+            fDstClips.push_back_n(4, clip);
+            hasClip = true;
+        }
+
+        // This acts like the whole image is rendered over the entire tile grid, so derive local
+        // coordinates from 'rect', based on the grid to image transform.
+        SkMatrix gridToImage = SkMatrix::MakeRectToRect(SkRect::MakeWH(kColCount * kTileWidth,
+                                                                       kRowCount * kTileHeight),
+                                                        SkRect::MakeWH(fImage->width(),
+                                                                       fImage->height()),
+                                                        SkMatrix::kFill_ScaleToFit);
+        SkRect localRect = gridToImage.mapRect(rect);
+
+        // drawTextureSet automatically derives appropriate local quad from localRect if clipPtr
+        // is not null.
+        fSetEntries.push_back(
+                {fImage, localRect, rect, -1, 1.f, this->maskToFlags(edgeAA), hasClip});
+        return 0;
+    }
+
+    void drawBanner(SkCanvas* canvas) override {
+        draw_text(canvas, "Texture");
+        canvas->translate(0.f, 15.f);
+        draw_text(canvas, "YUV - GPU Only");
+    }
+
+private:
+    std::unique_ptr<sk_gpu_test::LazyYUVImage> fYUVData;
+    // The last accessed SkImage from fYUVData, held here for easy access by drawTile
+    sk_sp<SkImage> fImage;
+
+    SkTArray<SkPoint> fDstClips;
+    SkTArray<SkCanvas::ImageSetEntry> fSetEntries;
+
+    YUVTextureSetRenderer(sk_sp<SkData> jpegData)
+            : fYUVData(sk_gpu_test::LazyYUVImage::Make(std::move(jpegData)))
+            , fImage(nullptr) {}
+
+    int drawAndReset(SkCanvas* canvas) {
+        // Early out if there's nothing to draw
+        if (fSetEntries.count() == 0) {
+            SkASSERT(fDstClips.count() == 0);
+            return 0;
+        }
+
+#ifdef SK_DEBUG
+        int expectedDstClipCount = 0;
+        for (int i = 0; i < fSetEntries.count(); ++i) {
+            expectedDstClipCount += 4 * fSetEntries[i].fHasClip;
+        }
+        SkASSERT(expectedDstClipCount == fDstClips.count());
+#endif
+
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setFilterQuality(kLow_SkFilterQuality);
+        paint.setBlendMode(SkBlendMode::kSrcOver);
+
+        canvas->experimental_DrawEdgeAAImageSet(
+                fSetEntries.begin(), fSetEntries.count(), fDstClips.begin(), nullptr, &paint,
+                SkCanvas::kFast_SrcRectConstraint);
+
+        // Reset for next tile
+        fDstClips.reset();
+        fSetEntries.reset();
+
+        return 1;
+    }
+
+    typedef ClipTileRenderer INHERITED;
+};
+
+static SkTArray<sk_sp<ClipTileRenderer>> make_debug_renderers() {
+    SkTArray<sk_sp<ClipTileRenderer>> renderers;
+    renderers.push_back(DebugTileRenderer::Make());
+    renderers.push_back(DebugTileRenderer::MakeAA());
+    renderers.push_back(DebugTileRenderer::MakeNonAA());
+    return renderers;
+}
+
+static SkTArray<sk_sp<ClipTileRenderer>> make_shader_renderers() {
+    static constexpr SkPoint kPts[] = { {0.f, 0.f}, {0.25f * kTileWidth, 0.25f * kTileHeight} };
+    static constexpr SkColor kColors[] = { SK_ColorBLUE, SK_ColorWHITE };
+    auto gradient = SkGradientShader::MakeLinear(kPts, kColors, nullptr, 2,
+                                                 SkTileMode::kMirror);
+
+    auto info = SkImageInfo::Make(1, 1, kAlpha_8_SkColorType, kOpaque_SkAlphaType);
+    SkBitmap bm;
+    bm.allocPixels(info);
+    bm.eraseColor(SK_ColorWHITE);
+    sk_sp<SkImage> image = SkImage::MakeFromBitmap(bm);
+
+    SkTArray<sk_sp<ClipTileRenderer>> renderers;
+    renderers.push_back(TextureSetRenderer::MakeShader("Gradient", image, gradient, false));
+    renderers.push_back(TextureSetRenderer::MakeShader("Local Gradient", image, gradient, true));
+    return renderers;
+}
+
+static SkTArray<sk_sp<ClipTileRenderer>> make_image_renderers() {
+    sk_sp<SkImage> mandrill = GetResourceAsImage("images/mandrill_512.png");
+    SkTArray<sk_sp<ClipTileRenderer>> renderers;
+    renderers.push_back(TextureSetRenderer::MakeUnbatched(mandrill));
+    renderers.push_back(TextureSetRenderer::MakeBatched(mandrill, 0));
+    renderers.push_back(TextureSetRenderer::MakeBatched(mandrill, kMatrixCount));
+    renderers.push_back(YUVTextureSetRenderer::MakeFromJPEG(
+            GetResourceAsData("images/mandrill_h1v1.jpg")));
+    return renderers;
+}
+
+static SkTArray<sk_sp<ClipTileRenderer>> make_filtered_renderers() {
+    sk_sp<SkImage> mandrill = GetResourceAsImage("images/mandrill_512.png");
+
+    SkColorMatrix cm;
+    cm.setSaturation(10);
+    sk_sp<SkColorFilter> colorFilter = SkColorFilters::MatrixRowMajor255(cm.fMat);
+    sk_sp<SkImageFilter> imageFilter = SkDilateImageFilter::Make(8, 8, nullptr);
+
+    static constexpr SkColor kAlphas[] = { SK_ColorTRANSPARENT, SK_ColorBLACK };
+    auto alphaGradient = SkGradientShader::MakeRadial(
+            {0.5f * kTileWidth * kColCount, 0.5f * kTileHeight * kRowCount},
+            0.25f * kTileWidth * kColCount, kAlphas, nullptr, 2, SkTileMode::kClamp);
+    sk_sp<SkMaskFilter> maskFilter = SkShaderMaskFilter::Make(std::move(alphaGradient));
+
+    SkTArray<sk_sp<ClipTileRenderer>> renderers;
+    renderers.push_back(TextureSetRenderer::MakeAlpha(mandrill, 0.5f));
+    renderers.push_back(TextureSetRenderer::MakeColorFilter("Saturation", mandrill,
+                                                            std::move(colorFilter)));
+    // NOTE: won't draw correctly until SkCanvas' AutoLoopers are used to handle image filters
+    renderers.push_back(TextureSetRenderer::MakeImageFilter("Dilate", mandrill,
+                                                            std::move(imageFilter)));
+
+    renderers.push_back(TextureSetRenderer::MakeMaskFilter("Shader", mandrill,
+                                                           std::move(maskFilter)));
+    // NOTE: blur mask filters do work (tested locally), but visually they don't make much
+    // sense, since each quad is blurred independently
+    return renderers;
+}
+
+DEF_GM(return new CompositorGM("debug", make_debug_renderers());)
+DEF_GM(return new CompositorGM("color", SolidColorRenderer::Make({.2f, .8f, .3f, 1.f}));)
+DEF_GM(return new CompositorGM("shader", make_shader_renderers());)
+DEF_GM(return new CompositorGM("image", make_image_renderers());)
+DEF_GM(return new CompositorGM("filter", make_filtered_renderers());)
+
+#endif // SK_SUPPORT_GPU
diff --git a/gm/conicpaths.cpp b/gm/conicpaths.cpp
index e0a775a..3f03bf0 100644
--- a/gm/conicpaths.cpp
+++ b/gm/conicpaths.cpp
@@ -159,6 +159,36 @@
     canvas->drawCircle(c, radius, paint);
 }
 
+/* ovals should not be blurry */
+DEF_SIMPLE_GM(largeovals, canvas, 250, 250) {
+    // Test EllipseOp
+    SkRect r = SkRect::MakeXYWH(-520, -520, 5000, 4000);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(100);
+    canvas->drawOval(r, paint);
+    r.offset(-15, -15);
+    paint.setColor(SK_ColorDKGRAY);
+    // we use stroke and fill to avoid falling into the SimpleFill path
+    paint.setStyle(SkPaint::kStrokeAndFill_Style);
+    paint.setStrokeWidth(1);
+    canvas->drawOval(r, paint);
+
+    // Test DIEllipseOp
+    canvas->rotate(1.0f);
+    r.offset(55, 55);
+    paint.setColor(SK_ColorGRAY);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(100);
+    canvas->drawOval(r, paint);
+    r.offset(-15, -15);
+    paint.setColor(SK_ColorLTGRAY);
+    paint.setStyle(SkPaint::kStrokeAndFill_Style);
+    paint.setStrokeWidth(1);
+    canvas->drawOval(r, paint);
+}
+
 DEF_SIMPLE_GM(crbug_640176, canvas, 250, 250) {
     SkPath path;
     path.moveTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000));  // 0, 0
diff --git a/gm/constcolorprocessor.cpp b/gm/constcolorprocessor.cpp
index af99e60..0b572f1 100644
--- a/gm/constcolorprocessor.cpp
+++ b/gm/constcolorprocessor.cpp
@@ -7,14 +7,14 @@
 
 // This test only works with the GPU backend.
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "GrContext.h"
 #include "GrRenderTargetContextPriv.h"
 #include "SkGr.h"
 #include "SkGradientShader.h"
-#include "effects/GrConstColorProcessor.h"
+#include "effects/generated/GrConstColorProcessor.h"
 #include "ops/GrDrawOp.h"
 #include "ops/GrFillRectOp.h"
 
@@ -41,7 +41,7 @@
         SkColor colors[] = { 0xFFFF0000, 0x2000FF00, 0xFF0000FF};
         SkPoint pts[] = { SkPoint::Make(0, 0), SkPoint::Make(kRectSize, kRectSize) };
         fShader = SkGradientShader::MakeLinear(pts, colors, nullptr, SK_ARRAY_COUNT(colors),
-                                               SkShader::kClamp_TileMode);
+                                               SkTileMode::kClamp);
     }
 
     void onDraw(GrContext* context, GrRenderTargetContext* renderTargetContext,
@@ -103,7 +103,7 @@
                     // 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.
                     SkFont labelFont;
-                    labelFont.setTypeface(sk_tool_utils::create_portable_typeface());
+                    labelFont.setTypeface(ToolUtils::create_portable_typeface());
                     labelFont.setEdging(SkFont::Edging::kAntiAlias);
                     labelFont.setSize(10.f);
                     SkPaint labelPaint;
diff --git a/gm/convex_all_line_paths.cpp b/gm/convex_all_line_paths.cpp
index e4a959c..1b607ff 100644
--- a/gm/convex_all_line_paths.cpp
+++ b/gm/convex_all_line_paths.cpp
@@ -10,15 +10,14 @@
 #include "SkPathPriv.h"
 
 static void create_ngon(int n, SkPoint* pts, SkScalar width, SkScalar height) {
-    float angleStep = 360.0f / n, angle = 0.0f, sin, cos;
+    float angleStep = 360.0f / n, angle = 0.0f;
     if ((n % 2) == 1) {
         angle = angleStep/2.0f;
     }
 
     for (int i = 0; i < n; ++i) {
-        sin = SkScalarSinCos(SkDegreesToRadians(angle), &cos);
-        pts[i].fX = -sin * width;
-        pts[i].fY = cos * height;
+        pts[i].fX = -SkScalarSin(SkDegreesToRadians(angle)) * width;
+        pts[i].fY =  SkScalarCos(SkDegreesToRadians(angle)) * height;
         angle += angleStep;
     }
 }
diff --git a/gm/convexpaths.cpp b/gm/convexpaths.cpp
index 6354832..ea0f690 100644
--- a/gm/convexpaths.cpp
+++ b/gm/convexpaths.cpp
@@ -252,6 +252,16 @@
                                    0, 0,
                                    100 * SK_Scalar1, 100 * SK_Scalar1);
 
+        // skbug.com/8928
+        fPaths.push_back().moveTo(16.875f, 192.594f);
+        fPaths.back().cubicTo(45.625f, 192.594f, 74.375f, 192.594f, 103.125f, 192.594f);
+        fPaths.back().cubicTo(88.75f, 167.708f, 74.375f, 142.823f, 60, 117.938f);
+        fPaths.back().cubicTo(45.625f, 142.823f, 31.25f, 167.708f, 16.875f, 192.594f);
+        fPaths.back().close();
+        SkMatrix m;
+        m.setAll(0.1f, 0, -1, 0, 0.115207f, -2.64977f, 0, 0, 1);
+        fPaths.back().transform(m);
+
         // small circle. This is listed last so that it has device coords far
         // from the origin (small area relative to x,y values).
         fPaths.push_back().addCircle(0, 0, 1.2f);
diff --git a/gm/convexpolyclip.cpp b/gm/convexpolyclip.cpp
index 2ab41ca..d531b66 100644
--- a/gm/convexpolyclip.cpp
+++ b/gm/convexpolyclip.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkBitmap.h"
 #include "SkFont.h"
@@ -26,12 +26,13 @@
 
     SkScalar    radius = 3 * SkMaxScalar(wScalar, hScalar);
 
-    SkColor     colors[] = { SK_ColorDKGRAY,
-                             sk_tool_utils::color_to_565(0xFF222255),
-                             sk_tool_utils::color_to_565(0xFF331133),
-                             sk_tool_utils::color_to_565(0xFF884422),
-                             sk_tool_utils::color_to_565(0xFF000022), SK_ColorWHITE,
-                             sk_tool_utils::color_to_565(0xFFAABBCC) };
+    SkColor colors[] = {SK_ColorDKGRAY,
+                        ToolUtils::color_to_565(0xFF222255),
+                        ToolUtils::color_to_565(0xFF331133),
+                        ToolUtils::color_to_565(0xFF884422),
+                        ToolUtils::color_to_565(0xFF000022),
+                        SK_ColorWHITE,
+                        ToolUtils::color_to_565(0xFFAABBCC)};
 
     SkScalar    pos[] = {0,
                          SK_Scalar1 / 6,
@@ -49,7 +50,7 @@
                         pt, radius,
                         colors, pos,
                         SK_ARRAY_COUNT(colors),
-                        SkShader::kRepeat_TileMode,
+                        SkTileMode::kRepeat,
                         0, &mat));
         canvas.drawRect(rect, paint);
         rect.inset(wScalar / 8, hScalar / 8);
@@ -57,7 +58,7 @@
         mat.postScale(SK_Scalar1 / 3, SK_Scalar1 / 3);
     }
 
-    SkFont font(sk_tool_utils::create_portable_typeface(), wScalar / 2.2f);
+    SkFont font(ToolUtils::create_portable_typeface(), wScalar / 2.2f);
 
     paint.setShader(nullptr);
     paint.setColor(SK_ColorLTGRAY);
@@ -110,8 +111,7 @@
         const SkPoint center = { kRadius, kRadius };
         for (int i = 0; i < 6; ++i) {
             SkScalar angle = 2 * SK_ScalarPI * i / 6;
-            SkPoint point;
-            point.fY = SkScalarSinCos(angle, &point.fX);
+            SkPoint point = { SkScalarCos(angle), SkScalarSin(angle) };
             point.scale(kRadius);
             point = center + point;
             if (0 == i) {
@@ -150,7 +150,7 @@
         canvas->drawBitmapRect(fBmp, SkRect::MakeIWH(size.fWidth, size.fHeight), &bgPaint);
 
         constexpr char kTxt[] = "Clip Me!";
-        SkFont font(sk_tool_utils::create_portable_typeface(), 23);
+        SkFont         font(ToolUtils::create_portable_typeface(), 23);
         SkScalar textW = font.measureText(kTxt, SK_ARRAY_COUNT(kTxt)-1, kUTF8_SkTextEncoding);
         SkPaint txtPaint;
         txtPaint.setColor(SK_ColorDKGRAY);
diff --git a/gm/convexpolyeffect.cpp b/gm/convexpolyeffect.cpp
index 6f8e64d..996a024 100644
--- a/gm/convexpolyeffect.cpp
+++ b/gm/convexpolyeffect.cpp
@@ -14,6 +14,8 @@
 #include "GrMemoryPool.h"
 #include "GrOpFlushState.h"
 #include "GrPathUtils.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContextPriv.h"
 #include "SkColorPriv.h"
 #include "SkGeometry.h"
@@ -41,7 +43,7 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const SkRect& rect) {
         GrOpMemoryPool* pool = context->priv().opMemoryPool();
@@ -57,9 +59,11 @@
 
     FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
-        return fProcessors.finalize(fColor, GrProcessorAnalysisCoverage::kNone, clip, false, caps,
-                                    &fColor);
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
+        return fProcessors.finalize(
+                fColor, GrProcessorAnalysisCoverage::kNone, clip, &GrUserStencilSettings::kUnused,
+                fsaaType, caps, clampType, &fColor);
     }
 
 private:
@@ -92,9 +96,11 @@
         }
 
         SkPointPriv::SetRectTriStrip(verts, fRect, sizeof(SkPoint));
+        helper.recordDraw(target, std::move(gp));
+    }
 
-        auto pipe = target->makePipeline(0, std::move(fProcessors), target->detachAppliedClip());
-        helper.recordDraw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState);
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        flushState->executeDrawsAndUploadsForMeshDrawOp(this, chainBounds, std::move(fProcessors));
     }
 
     SkPMColor4f fColor;
@@ -139,8 +145,7 @@
         const SkPoint center = { kRadius, kRadius };
         for (int i = 0; i < GrConvexPolyEffect::kMaxEdges; ++i) {
             SkScalar angle = 2 * SK_ScalarPI * i / GrConvexPolyEffect::kMaxEdges;
-            SkPoint point;
-            point.fY = SkScalarSinCos(angle, &point.fX);
+            SkPoint point = { SkScalarCos(angle), SkScalarSin(angle) };
             point.scale(kRadius);
             point = center + point;
             if (0 == i) {
diff --git a/gm/copyTo4444.cpp b/gm/copyTo4444.cpp
index 46b034f..0f4a276 100644
--- a/gm/copyTo4444.cpp
+++ b/gm/copyTo4444.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "Resources.h"
 #include "SkCanvas.h"
@@ -30,18 +30,18 @@
         return SkISize::Make(360, 180);
     }
 
-    virtual void onDraw(SkCanvas* canvas) {
+    DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) {
         SkBitmap bm, bm4444;
         if (!GetResourceAsBitmap("images/dog.jpg", &bm)) {
-            DrawFailureMessage(canvas, "Could not decode the file. "
-                                       "Did you forget to set the resourcePath?");
-            return;
+            *errorMsg = "Could not decode the file. Did you forget to set the resourcePath?";
+            return DrawResult::kFail;
         }
         canvas->drawBitmap(bm, 0, 0);
 
         // This should dither or we will see artifacts in the background of the image.
-        SkAssertResult(sk_tool_utils::copy_to(&bm4444, kARGB_4444_SkColorType, bm));
+        SkAssertResult(ToolUtils::copy_to(&bm4444, kARGB_4444_SkColorType, bm));
         canvas->drawBitmap(bm4444, SkIntToScalar(bm.width()), 0);
+        return DrawResult::kOk;
     }
 
 private:
diff --git a/gm/crbug_691386.cpp b/gm/crbug_691386.cpp
index 76572b7..de7cfe6 100644
--- a/gm/crbug_691386.cpp
+++ b/gm/crbug_691386.cpp
@@ -8,11 +8,11 @@
 #include "gm.h"
 #include "SkParsePath.h"
 
-DEF_SIMPLE_GM(crbug_691386, canvas, 256, 256) {
+DEF_SIMPLE_GM_CAN_FAIL(crbug_691386, canvas, errorMsg, 256, 256) {
     SkPath path;
     if (!SkParsePath::FromSVGString("M -1 0 A 1 1 0 0 0 1 0 Z", &path)) {
-        skiagm::GM::DrawFailureMessage(canvas, "Failed to parse path.");
-        return;
+        *errorMsg = "Failed to parse path.";
+        return skiagm::DrawResult::kFail;
     }
     SkPaint p;
     p.setStyle(SkPaint::kStroke_Style);
@@ -20,4 +20,5 @@
     canvas->scale(96.0f, 96.0f);
     canvas->translate(1.25f, 1.25f);
     canvas->drawPath(path, p);
+    return skiagm::DrawResult::kOk;
 }
diff --git a/gm/crbug_899512.cpp b/gm/crbug_899512.cpp
index ae76e1a..6498726 100644
--- a/gm/crbug_899512.cpp
+++ b/gm/crbug_899512.cpp
@@ -18,6 +18,6 @@
     canvas->concat(matrix);
     SkPaint paint;
     paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 6.2735f, false));
-    paint.setColorFilter(SkColorFilter::MakeModeFilter(SK_ColorBLACK, SkBlendMode::kSrcIn));
+    paint.setColorFilter(SkColorFilters::Blend(SK_ColorBLACK, SkBlendMode::kSrcIn));
     canvas->drawRect(SkRect::MakeXYWH(0, 10, 200, 200), paint);
 }
diff --git a/gm/crbug_938592.cpp b/gm/crbug_938592.cpp
new file mode 100644
index 0000000..0284cd9
--- /dev/null
+++ b/gm/crbug_938592.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCanvas.h"
+#include "SkGradientShader.h"
+#include "SkPaint.h"
+#include "gm.h"
+
+// This draws a hard stop gradient applied to a rectangle. The hard stops fall at half pixel
+// boundaries in y. On some GPUs we've found that the winding of the two triangles that make up the
+// rectangle can affect whether the interpolants for local coords wind up agreeing across a row.
+// When they disagree the hard stop gradient appears jagged. We draw the rectangle four times:
+// no-mirroring, mirror in x, mirror in y, and mirror in x and y.
+DEF_SIMPLE_GM(crbug_938592, canvas, 500, 300) {
+    static constexpr SkPoint pts[] = {{0, 0}, {0, 30}};
+    static constexpr SkScalar pos[] = {0.f, 9.f / 20, 9.f / 20, 11.f / 20, 11.f / 20, 20.f / 20};
+    static constexpr SkColor c0 = SK_ColorBLUE;
+    static constexpr SkColor c1 = SK_ColorRED;
+    static constexpr SkColor c2 = SK_ColorGREEN;
+    static constexpr SkColor colors[] = {c0, c0, c1, c1, c2, c2};
+    auto grad = SkGradientShader::MakeLinear(pts, colors, pos, 6, SkTileMode::kClamp);
+    SkPaint paint;
+    paint.setShader(grad);
+    static constexpr int kMirrorX = 400;
+    static constexpr int kMirrorY = 200;
+    canvas->translate(50, 50);
+    for (int i = 0; i < 4; ++i) {
+        canvas->save();
+        if (i & 0b01) {
+            canvas->translate(0, kMirrorY);
+            canvas->scale(1.f, -1.f);
+        }
+        if (i & 0b10) {
+            canvas->translate(kMirrorX, 0);
+            canvas->scale(-1.f, 1.f);
+        }
+        canvas->drawRect({0, 0, 150, 30}, paint);
+        canvas->restore();
+    }
+}
diff --git a/gm/crbug_947055.cpp b/gm/crbug_947055.cpp
new file mode 100644
index 0000000..20744e2
--- /dev/null
+++ b/gm/crbug_947055.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkCanvas.h"
+#include "SkPath.h"
+
+DEF_SIMPLE_GM_BG(crbug_947055, canvas, 200, 50, SK_ColorBLUE) {
+    // Green 2D rectangle to highlight the red rectangle. Isn't necessary
+    // to trigger problem, but helps show the extreme corner outsets.
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setColor(SK_ColorGREEN);
+    canvas->drawRect(SkRect::MakeXYWH(19.f, 7.f, 180.f, 10.f), paint);
+
+    // Red perspective rectangle with bad AA on Ganesh
+    paint.setColor(SK_ColorRED);
+    canvas->concat(SkMatrix::MakeAll(1.0f,  2.4520f, 19.0f,
+                                     0.0f,  0.3528f,  9.5f,
+                                     0.0f,  0.0225f,  1.0f));
+    canvas->drawRect(SkRect::MakeWH(180.f, 500.f), paint);
+}
diff --git a/gm/crosscontextimage.cpp b/gm/crosscontextimage.cpp
index 9c948d9..3515891 100644
--- a/gm/crosscontextimage.cpp
+++ b/gm/crosscontextimage.cpp
@@ -11,36 +11,44 @@
 #include "GrContext.h"
 #include "SkImage.h"
 
-DEF_SIMPLE_GPU_GM(cross_context_image, context, rtc, canvas, 512 * 3 + 60, 512 + 128 + 30) {
-    sk_sp<SkData> encodedData = GetResourceAsData("images/mandrill_512.png");
+DEF_SIMPLE_GPU_GM_CAN_FAIL(cross_context_image, context, rtc, canvas, errorMsg,
+                           5 * 256 + 60, 256 + 128 + 30) {
+    sk_sp<SkData> encodedData = GetResourceAsData("images/mandrill_256.png");
     if (!encodedData) {
-        skiagm::GM::DrawFailureMessage(canvas, "Could not load mandrill_512.png. "
-                                               "Did you forget to set the resourcePath?");
-        return;
+        *errorMsg = "Could not load mandrill_256.png. Did you forget to set the resourcePath?";
+        return skiagm::DrawResult::kFail;
     }
 
-    sk_sp<SkImage> encodedImage = SkImage::MakeFromEncoded(encodedData);
-    canvas->drawImage(encodedImage, 10, 10);
-
-    sk_sp<SkImage> crossContextImage = SkImage::MakeCrossContextFromEncoded(
-            context, encodedData, false, canvas->imageInfo().colorSpace());
-    canvas->drawImage(crossContextImage, 512 + 30, 10);
+    sk_sp<SkImage> images[5];
+    images[0] = SkImage::MakeFromEncoded(encodedData);
 
     SkBitmap bmp;
     SkPixmap pixmap;
-    SkAssertResult(encodedImage->asLegacyBitmap(&bmp) &&
+    SkAssertResult(images[0]->asLegacyBitmap(&bmp) &&
                    bmp.peekPixels(&pixmap));
 
-    sk_sp<SkImage> crossContextRaster = SkImage::MakeCrossContextFromPixmap(
-            context, pixmap, false, canvas->imageInfo().colorSpace());
-    canvas->drawImage(crossContextRaster, 512 + 512 + 60, 10);
+    images[1] = SkImage::MakeCrossContextFromEncoded(context, encodedData, false, nullptr);
+    images[2] = SkImage::MakeCrossContextFromEncoded(context, encodedData, true, nullptr);
+    images[3] = SkImage::MakeCrossContextFromPixmap(context, pixmap, false, nullptr);
+    images[4] = SkImage::MakeCrossContextFromPixmap(context, pixmap, true, nullptr);
 
-    SkIRect subset = SkIRect::MakeXYWH(256 - 64, 256 - 64, 128, 128);
-    sk_sp<SkImage> encodedSubset = encodedImage->makeSubset(subset);
-    sk_sp<SkImage> crossContextSubset = crossContextImage->makeSubset(subset);
-    sk_sp<SkImage> crossContextRasterSubset = crossContextRaster->makeSubset(subset);
+    canvas->translate(10, 10);
 
-    canvas->drawImage(encodedSubset, 10, 512 + 30);
-    canvas->drawImage(crossContextSubset, 512 + 30, 512 + 30);
-    canvas->drawImage(crossContextRasterSubset, 512 + 512 + 60, 512 + 30);
+    for (size_t i = 0; i < SK_ARRAY_COUNT(images); ++i) {
+        canvas->save();
+
+        canvas->drawImage(images[i], 0, 0);
+        canvas->translate(0, 256 + 10);
+
+        canvas->drawImage(images[i]->makeSubset(SkIRect::MakeXYWH(64, 64, 128, 128)), 0, 0);
+        canvas->translate(128, 0);
+
+        SkPaint paint;
+        paint.setFilterQuality(kMedium_SkFilterQuality);
+        canvas->drawImageRect(images[i], SkRect::MakeWH(128, 128), &paint);
+
+        canvas->restore();
+        canvas->translate(256 + 10, 0);
+    }
+    return skiagm::DrawResult::kOk;
 }
diff --git a/gm/cubicpaths.cpp b/gm/cubicpaths.cpp
index 14f8ae6..d4bf225 100644
--- a/gm/cubicpaths.cpp
+++ b/gm/cubicpaths.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkPaint.h"
 #include "SkPath.h"
 #include "SkRandom.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 // https://bug.skia.org/1316 shows that this cubic, when slightly clipped, creates big
 // (incorrect) changes to its control points.
@@ -198,7 +198,7 @@
         SkPaint titlePaint;
         titlePaint.setColor(SK_ColorBLACK);
         titlePaint.setAntiAlias(true);
-        SkFont font(sk_tool_utils::create_portable_typeface(), 15);
+        SkFont     font(ToolUtils::create_portable_typeface(), 15);
         const char title[] = "Cubic Drawn Into Rectangle Clips With "
                              "Indicated Style, Fill and Linecaps, with stroke width 10";
         canvas->drawString(title, 20, 20, font, titlePaint);
@@ -328,7 +328,7 @@
         SkPaint titlePaint;
         titlePaint.setColor(SK_ColorBLACK);
         titlePaint.setAntiAlias(true);
-        SkFont font(sk_tool_utils::create_portable_typeface(), 15);
+        SkFont     font(ToolUtils::create_portable_typeface(), 15);
         const char title[] = "Cubic Closed Drawn Into Rectangle Clips With "
                              "Indicated Style, Fill and Linecaps, with stroke width 10";
         canvas->drawString(title, 20, 20, font, titlePaint);
diff --git a/gm/daa.cpp b/gm/daa.cpp
index 3b049f7..6338a48 100644
--- a/gm/daa.cpp
+++ b/gm/daa.cpp
@@ -12,25 +12,119 @@
 // This GM shows off a flaw in delta-based rasterizers (DAA, CCPR, etc.).
 // See also the bottom of dashing4 and skia:6886.
 
-static const int K = 50;
+static const int K = 49;
 
-DEF_SIMPLE_GM(daa, canvas, K+350, K) {
+DEF_SIMPLE_GM(daa, canvas, K+350, 5*K) {
     SkPaint paint;
     paint.setAntiAlias(true);
 
-    paint.setColor(SK_ColorBLACK);
-    canvas->drawString("Should be a green square with no red showing through.",
-                       K*1.5f, K/2, SkFont(), paint);
+    {
+        paint.setColor(SK_ColorBLACK);
+        canvas->drawString("Should be a green square with no red showing through.",
+                           K*1.5f, K*0.5f, SkFont(), paint);
 
-    paint.setColor(SK_ColorRED);
-    canvas->drawRect({0,0,K,K}, paint);
+        paint.setColor(SK_ColorRED);
+        canvas->drawRect({0,0,K,K}, paint);
 
-    SkPath path;
-    SkPoint tri1[] = {{0,0},{K,K},{0,K},{0,0}};
-    SkPoint tri2[] = {{0,0},{K,K},{K,0},{0,0}};
-    path.addPoly(tri1, SK_ARRAY_COUNT(tri1), false);
-    path.addPoly(tri2, SK_ARRAY_COUNT(tri2), false);
+        SkPath path;
+        SkPoint tri1[] = {{0,0},{K,K},{0,K},{0,0}};
+        SkPoint tri2[] = {{0,0},{K,K},{K,0},{0,0}};
+        path.addPoly(tri1, SK_ARRAY_COUNT(tri1), false);
+        path.addPoly(tri2, SK_ARRAY_COUNT(tri2), false);
 
-    paint.setColor(SK_ColorGREEN);
-    canvas->drawPath(path, paint);
+        paint.setColor(SK_ColorGREEN);
+        canvas->drawPath(path, paint);
+    }
+
+    canvas->translate(0,K);
+    {
+        paint.setColor(SK_ColorBLACK);
+        canvas->drawString("Adjacent rects, two draws.  Blue then green, no red?",
+                           K*1.5f, K*0.5f, SkFont(), paint);
+
+        paint.setColor(SK_ColorRED);
+        canvas->drawRect({0,0,K,K}, paint);
+
+        {
+            SkPath path;
+            SkPoint rect1[] = {{0,0},{0,K},{K*0.5f,K},{K*0.5f,0}};
+            path.addPoly(rect1, SK_ARRAY_COUNT(rect1), false);
+
+            paint.setColor(SK_ColorBLUE);
+            canvas->drawPath(path, paint);
+        }
+
+        {
+            SkPath path;
+            SkPoint rect2[] = {{K*0.5f,0},{K*0.5f,K},{K,K},{K,0}};
+            path.addPoly(rect2, SK_ARRAY_COUNT(rect2), false);
+
+            paint.setColor(SK_ColorGREEN);
+            canvas->drawPath(path, paint);
+        }
+    }
+
+    canvas->translate(0,K);
+    {
+        paint.setColor(SK_ColorBLACK);
+        canvas->drawString("Adjacent rects, wound together.  All green?",
+                           K*1.5f, K*0.5f, SkFont(), paint);
+
+        paint.setColor(SK_ColorRED);
+        canvas->drawRect({0,0,K,K}, paint);
+
+        {
+            SkPath path;
+            SkPoint rect1[] = {{0,0},{0,K},{K*0.5f,K},{K*0.5f,0}};
+            SkPoint rect2[] = {{K*0.5f,0},{K*0.5f,K},{K,K},{K,0}};
+
+            path.addPoly(rect1, SK_ARRAY_COUNT(rect1), false);
+            path.addPoly(rect2, SK_ARRAY_COUNT(rect2), false);
+
+            paint.setColor(SK_ColorGREEN);
+            canvas->drawPath(path, paint);
+        }
+    }
+
+    canvas->translate(0,K);
+    {
+        paint.setColor(SK_ColorBLACK);
+        canvas->drawString("Adjacent rects, wound opposite.  All green?",
+                           K*1.5f, K*0.5f, SkFont(), paint);
+
+        paint.setColor(SK_ColorRED);
+        canvas->drawRect({0,0,K,K}, paint);
+
+        {
+            SkPath path;
+            SkPoint rect1[] = {{0,0},{0,K},{K*0.5f,K},{K*0.5f,0}};
+            SkPoint rect2[] = {{K*0.5f,0},{K,0},{K,K},{K*0.5f,K}};
+
+            path.addPoly(rect1, SK_ARRAY_COUNT(rect1), false);
+            path.addPoly(rect2, SK_ARRAY_COUNT(rect2), false);
+
+            paint.setColor(SK_ColorGREEN);
+            canvas->drawPath(path, paint);
+        }
+    }
+
+    canvas->translate(0,K);
+    {
+        paint.setColor(SK_ColorBLACK);
+        canvas->drawString("One poly, wound opposite.  All green?",
+                           K*1.5f, K*0.5f, SkFont(), paint);
+
+        paint.setColor(SK_ColorRED);
+        canvas->drawRect({0,0,K,K}, paint);
+
+        {
+            SkPath path;
+            SkPoint poly[] = {{K*0.5f,0},{0,0},{0,K},{K*0.5f,K},{K*0.5f,0},{K,0},{K,K},{K*0.5f,K}};
+
+            path.addPoly(poly, SK_ARRAY_COUNT(poly), false);
+
+            paint.setColor(SK_ColorGREEN);
+            canvas->drawPath(path, paint);
+        }
+    }
 }
diff --git a/gm/dashcircle.cpp b/gm/dashcircle.cpp
index 3168a0e..f92404f 100644
--- a/gm/dashcircle.cpp
+++ b/gm/dashcircle.cpp
@@ -5,10 +5,10 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "SkAnimTimer.h"
-#include "SkPath.h"
+#include "AnimTimer.h"
 #include "SkDashPathEffect.h"
+#include "SkPath.h"
+#include "gm.h"
 
 int dash1[] = { 1, 1 };
 int dash2[] = { 1, 3 };
@@ -94,7 +94,7 @@
         }
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         constexpr SkScalar kDesiredDurationSecs = 100.0f;
 
         fRotation = timer.scaled(360.0f/kDesiredDurationSecs, 360.0f);
@@ -215,7 +215,7 @@
     }
 
 protected:
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         fPhaseDegrees = timer.secs();
         return true;
     }
diff --git a/gm/dashcubics.cpp b/gm/dashcubics.cpp
index 4c1f028..0e24cf9 100644
--- a/gm/dashcubics.cpp
+++ b/gm/dashcubics.cpp
@@ -5,14 +5,14 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "SkAnimTimer.h"
+#include "AnimTimer.h"
 #include "SkCanvas.h"
 #include "SkDashPathEffect.h"
-#include "SkPath.h"
 #include "SkParsePath.h"
+#include "SkPath.h"
 #include "SkTArray.h"
 #include "SkTrimPathEffect.h"
+#include "gm.h"
 
 #include <utility>
 
@@ -153,7 +153,7 @@
         }
     }
 
-    bool onAnimate(const SkAnimTimer& t) override {
+    bool onAnimate(const AnimTimer& t) override {
         fOffset = t.msec() / 2000.0f;
         fOffset -= floorf(fOffset);
         return true;
diff --git a/gm/dashing.cpp b/gm/dashing.cpp
index cacdc00..bcfa1cc 100644
--- a/gm/dashing.cpp
+++ b/gm/dashing.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
-#include "SkPaint.h"
 #include "SkDashPathEffect.h"
+#include "SkPaint.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static void drawline(SkCanvas* canvas, int on, int off, const SkPaint& paint,
                      SkScalar finalX = SkIntToScalar(600), SkScalar finalY = SkIntToScalar(0),
@@ -107,8 +107,7 @@
     path->moveTo(0, -SK_Scalar1);
     for (int i = 1; i < n; i++) {
         rad += drad;
-        SkScalar cosV, sinV = SkScalarSinCos(rad, &cosV);
-        path->lineTo(cosV, sinV);
+        path->lineTo(SkScalarCos(rad), SkScalarSin(rad));
     }
     path->close();
 }
@@ -562,7 +561,7 @@
     p.setStrokeJoin(SkPaint::kRound_Join);
     p.setARGB(0xff, 0xbb, 0x00, 0x00);
 
-    SkFont font(sk_tool_utils::create_portable_typeface(), 100);
+    SkFont font(ToolUtils::create_portable_typeface(), 100);
 
     const SkScalar intervals[] = { 12, 12 };
     p.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0));
diff --git a/gm/degeneratesegments.cpp b/gm/degeneratesegments.cpp
index 0ebca10..17de581 100644
--- a/gm/degeneratesegments.cpp
+++ b/gm/degeneratesegments.cpp
@@ -4,12 +4,12 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkPaint.h"
 #include "SkPath.h"
 #include "SkRandom.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -289,7 +289,7 @@
         SkPaint titlePaint;
         titlePaint.setColor(SK_ColorBLACK);
         titlePaint.setAntiAlias(true);
-        SkFont font(sk_tool_utils::create_portable_typeface(), 15);
+        SkFont     font(ToolUtils::create_portable_typeface(), 15);
         const char title[] = "Random Paths Drawn Into Rectangle Clips With "
                              "Indicated Style, Fill and Linecaps, "
                              "with Stroke width 6";
@@ -314,7 +314,7 @@
                     canvas->translate(rect.width() + 4*SK_Scalar1, 0);
                 }
 
-                SkColor color = sk_tool_utils::color_to_565(0xff007000);
+                SkColor      color = ToolUtils::color_to_565(0xff007000);
                 StyleAndName style = gStyles[(rand.nextU() >> 16) % numStyles];
                 CapAndName cap = gCaps[(rand.nextU() >> 16) % numCaps];
                 FillAndName fill = gFills[(rand.nextU() >> 16) % numFills];
diff --git a/gm/dftext.cpp b/gm/dftext.cpp
index d0bf1af..37f8b84 100644
--- a/gm/dftext.cpp
+++ b/gm/dftext.cpp
@@ -13,8 +13,8 @@
 #include "SkTextBlob.h"
 #include "SkTo.h"
 #include "SkTypeface.h"
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 class DFTextGM : public skiagm::GM {
 public:
@@ -24,14 +24,12 @@
 
 protected:
     void onOnceBeforeDraw() override {
-        fEmojiTypeface = sk_tool_utils::emoji_typeface();
-        fEmojiText = sk_tool_utils::emoji_sample_text();
+        fEmojiTypeface = ToolUtils::emoji_typeface();
+        fEmojiText     = ToolUtils::emoji_sample_text();
     }
 
     SkString onShortName() override {
-        SkString name("dftext");
-        name.append(sk_tool_utils::platform_font_manager());
-        return name;
+        return SkString("dftext");
     }
 
     SkISize onISize() override {
@@ -60,7 +58,7 @@
         SkPaint paint;
         paint.setAntiAlias(true);
 
-        SkFont font(sk_tool_utils::create_portable_typeface("serif", SkFontStyle()));
+        SkFont font(ToolUtils::create_portable_typeface("serif", SkFontStyle()));
         font.setSubpixel(true);
 
         const char* text = "Hamburgefons";
diff --git a/gm/dftext_blob_persp.cpp b/gm/dftext_blob_persp.cpp
index 4a918cb..70111e4 100644
--- a/gm/dftext_blob_persp.cpp
+++ b/gm/dftext_blob_persp.cpp
@@ -5,13 +5,13 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
 #include "Resources.h"
 #include "SkCanvas.h"
 #include "SkSurface.h"
 #include "SkTextBlob.h"
 #include "SkTypeface.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 /**
  * This GM tests reusing the same text blobs with distance fields rendering using various
@@ -24,9 +24,7 @@
 
 protected:
     SkString onShortName() override {
-        SkString name("dftext_blob_persp");
-        name.append(sk_tool_utils::platform_font_manager());
-        return name;
+        return SkString("dftext_blob_persp");
     }
 
     SkISize onISize() override { return SkISize::Make(900, 350); }
@@ -40,7 +38,7 @@
                             SkFont::Edging::kSubpixelAntiAlias));
             font.setSubpixel(true);
             SkTextBlobBuilder builder;
-            sk_tool_utils::add_to_text_blob(&builder, "SkiaText", font, 0, 0);
+            ToolUtils::add_to_text_blob(&builder, "SkiaText", font, 0, 0);
             fBlobs.emplace_back(builder.make());
         }
     }
diff --git a/gm/discard.cpp b/gm/discard.cpp
index b4fe7e7..12d4ee0 100644
--- a/gm/discard.cpp
+++ b/gm/discard.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkPaint.h"
 #include "SkRandom.h"
 #include "SkSurface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -33,15 +33,16 @@
         return SkISize::Make(100, 100);
     }
 
-    void onDraw(GrContext* context, GrRenderTargetContext*, SkCanvas* canvas) override {
+    DrawResult onDraw(GrContext* context, GrRenderTargetContext*, SkCanvas* canvas,
+                      SkString* errorMsg) override {
         SkISize size = this->getISize();
         size.fWidth /= 10;
         size.fHeight /= 10;
         SkImageInfo info = SkImageInfo::MakeN32Premul(size);
         auto surface = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info);
         if (nullptr == surface) {
-            DrawFailureMessage(canvas, "Could not create render target.");
-            return;
+            *errorMsg = "Could not create render target.";
+            return DrawResult::kFail;
         }
 
         canvas->clear(SK_ColorBLACK);
@@ -51,7 +52,7 @@
             for (int y = 0; y < 10; ++y) {
               surface->getCanvas()->discard();
               // Make something that isn't too close to the background color, black.
-              SkColor color = sk_tool_utils::color_to_565(rand.nextU() | 0xFF404040);
+              SkColor color = ToolUtils::color_to_565(rand.nextU() | 0xFF404040);
               switch (rand.nextULessThan(3)) {
                   case 0:
                       surface->getCanvas()->drawColor(color);
@@ -61,7 +62,7 @@
                       break;
                   case 2:
                       SkPaint paint;
-                      paint.setShader(SkShader::MakeColorShader(color));
+                      paint.setShader(SkShaders::Color(color));
                       surface->getCanvas()->drawPaint(paint);
                       break;
               }
@@ -70,6 +71,7 @@
         }
 
         surface->getCanvas()->discard();
+        return DrawResult::kOk;
     }
 
 private:
diff --git a/gm/displacement.cpp b/gm/displacement.cpp
index 9fea018..83a5085 100644
--- a/gm/displacement.cpp
+++ b/gm/displacement.cpp
@@ -8,8 +8,8 @@
 #include "SkDisplacementMapEffect.h"
 #include "SkImage.h"
 #include "SkImageSource.h"
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 namespace skiagm {
 
@@ -25,21 +25,17 @@
     }
 
     void onOnceBeforeDraw() override {
-        fBitmap = sk_tool_utils::create_string_bitmap(80, 80, 0xFF884422, 15, 55, 96, "g");
+        fBitmap = ToolUtils::create_string_bitmap(80, 80, 0xFF884422, 15, 55, 96, "g");
 
-        SkColor c1 = sk_tool_utils::color_to_565(0xFF244484);
-        SkColor c2 = sk_tool_utils::color_to_565(0xFF804020);
+        SkColor c1 = ToolUtils::color_to_565(0xFF244484);
+        SkColor c2 = ToolUtils::color_to_565(0xFF804020);
 
-        fCheckerboard = SkImage::MakeFromBitmap(
-            sk_tool_utils::create_checkerboard_bitmap(80, 80, c1, c2, 8));
-        fSmall = SkImage::MakeFromBitmap(
-            sk_tool_utils::create_checkerboard_bitmap(64, 64, c1, c2, 8));
-        fLarge = SkImage::MakeFromBitmap(
-            sk_tool_utils::create_checkerboard_bitmap(96, 96, c1, c2, 8));
-        fLargeW =
-            SkImage::MakeFromBitmap(sk_tool_utils::create_checkerboard_bitmap(96, 64, c1, c2, 8));
-        fLargeH = SkImage::MakeFromBitmap(
-            sk_tool_utils::create_checkerboard_bitmap(64, 96, c1, c2, 8));
+        fCheckerboard =
+                SkImage::MakeFromBitmap(ToolUtils::create_checkerboard_bitmap(80, 80, c1, c2, 8));
+        fSmall  = SkImage::MakeFromBitmap(ToolUtils::create_checkerboard_bitmap(64, 64, c1, c2, 8));
+        fLarge  = SkImage::MakeFromBitmap(ToolUtils::create_checkerboard_bitmap(96, 96, c1, c2, 8));
+        fLargeW = SkImage::MakeFromBitmap(ToolUtils::create_checkerboard_bitmap(96, 64, c1, c2, 8));
+        fLargeH = SkImage::MakeFromBitmap(ToolUtils::create_checkerboard_bitmap(64, 96, c1, c2, 8));
     }
 
     SkISize onISize() override {
diff --git a/gm/downsamplebitmap.cpp b/gm/downsamplebitmap.cpp
index cc521d0..e0dde1b 100644
--- a/gm/downsamplebitmap.cpp
+++ b/gm/downsamplebitmap.cpp
@@ -6,8 +6,8 @@
  */
 
 #include "Resources.h"
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 static const char* kFilterQualityNames[] = { "none", "low", "medium", "high" };
 
@@ -87,13 +87,13 @@
     font.setSubpixel(true);
     font.setSize(textSize);
 
-    font.setTypeface(sk_tool_utils::create_portable_typeface("serif", SkFontStyle()));
+    font.setTypeface(ToolUtils::create_portable_typeface("serif", SkFontStyle()));
     canvas.drawString("Hamburgefons", textSize/2, 1.2f*textSize, font, paint);
-    font.setTypeface(sk_tool_utils::create_portable_typeface("serif", SkFontStyle::Bold()));
+    font.setTypeface(ToolUtils::create_portable_typeface("serif", SkFontStyle::Bold()));
     canvas.drawString("Hamburgefons", textSize/2, 2.4f*textSize, font, paint);
-    font.setTypeface(sk_tool_utils::create_portable_typeface("serif", SkFontStyle::Italic()));
+    font.setTypeface(ToolUtils::create_portable_typeface("serif", SkFontStyle::Italic()));
     canvas.drawString("Hamburgefons", textSize/2, 3.6f*textSize, font, paint);
-    font.setTypeface(sk_tool_utils::create_portable_typeface("serif", SkFontStyle::BoldItalic()));
+    font.setTypeface(ToolUtils::create_portable_typeface("serif", SkFontStyle::BoldItalic()));
     canvas.drawString("Hamburgefons", textSize/2, 4.8f*textSize, font, paint);
 
     return bm;
diff --git a/gm/drawatlas.cpp b/gm/drawatlas.cpp
index 5997bf6..5dafab4 100644
--- a/gm/drawatlas.cpp
+++ b/gm/drawatlas.cpp
@@ -12,12 +12,12 @@
 #include "SkRSXform.h"
 #include "SkSurface.h"
 #include "SkTextBlob.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 class DrawAtlasGM : public skiagm::GM {
     static sk_sp<SkImage> MakeAtlas(SkCanvas* caller, const SkRect& target) {
         SkImageInfo info = SkImageInfo::MakeN32Premul(100, 100);
-        auto surface(sk_tool_utils::makeSurface(caller, info));
+        auto        surface(ToolUtils::makeSurface(caller, info));
         SkCanvas* canvas = surface->getCanvas();
         // draw red everywhere, but we don't expect to see it in the draw, testing the notion
         // that drawAtlas draws a subset-region of the atlas.
@@ -157,7 +157,7 @@
 static sk_sp<SkShader> make_shader() {
     SkPoint pts[2] = {{0, 0}, {220, 0}};
     SkColor colors[2] = {SK_ColorRED, SK_ColorBLUE};
-    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kMirror_TileMode);
+    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kMirror);
 }
 
 static void drawTextPath(SkCanvas* canvas, bool doStroke) {
@@ -212,7 +212,7 @@
 // Exercise xform blob and its bounds
 DEF_SIMPLE_GM(blob_rsxform, canvas, 500, 100) {
     SkFont font;
-    font.setTypeface(sk_tool_utils::create_portable_typeface());
+    font.setTypeface(ToolUtils::create_portable_typeface());
     font.setSize(50);
 
     const char text[] = "CrazyXform";
@@ -270,7 +270,7 @@
     auto verts = make_vertices(image, tex, color);
     const sk_sp<SkColorFilter> filters[] = {
         nullptr,
-        SkColorFilter::MakeModeFilter(0xFF00FF88, SkBlendMode::kModulate),
+        SkColorFilters::Blend(0xFF00FF88, SkBlendMode::kModulate),
     };
     const SkBlendMode modes[] = {
         SkBlendMode::kSrcOver,
@@ -280,8 +280,8 @@
     canvas->translate(10, 10);
     SkPaint paint;
     for (SkBlendMode mode : modes) {
-        for (int alpha : { 0xFF, 0x7F }) {
-            paint.setAlpha(alpha);
+        for (float alpha : { 1.0f, 0.5f }) {
+            paint.setAlphaf(alpha);
             canvas->save();
             for (auto cf : filters) {
                 paint.setColorFilter(cf);
diff --git a/gm/drawatlascolor.cpp b/gm/drawatlascolor.cpp
index 82bf998..532b771 100644
--- a/gm/drawatlascolor.cpp
+++ b/gm/drawatlascolor.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkRSXform.h"
 #include "SkSurface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 // Create a square atlas of:
 //   opaque white  |     opaque red
@@ -20,7 +20,7 @@
     const int kBlockSize = atlasSize/2;
 
     SkImageInfo info = SkImageInfo::MakeN32Premul(atlasSize, atlasSize);
-    auto surface(sk_tool_utils::makeSurface(caller, info));
+    auto        surface(ToolUtils::makeSurface(caller, info));
     SkCanvas* canvas = surface->getCanvas();
 
     SkPaint paint;
@@ -128,7 +128,7 @@
             quadColors[i] = gColors[i];
         }
 
-        SkFont font(sk_tool_utils::create_portable_typeface(), kTextPad);
+        SkFont font(ToolUtils::create_portable_typeface(), kTextPad);
 
         for (int i = 0; i < numModes; ++i) {
             const char* label = SkBlendMode_Name(gModes[i]);
diff --git a/gm/drawbitmaprect.cpp b/gm/drawbitmaprect.cpp
index 251c247..ebc768e 100644
--- a/gm/drawbitmaprect.cpp
+++ b/gm/drawbitmaprect.cpp
@@ -5,8 +5,6 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkBlurMask.h"
 #include "SkBlurMaskFilter.h"
 #include "SkColorPriv.h"
@@ -16,7 +14,8 @@
 #include "SkMathPriv.h"
 #include "SkShader.h"
 #include "SkSurface.h"
-
+#include "ToolUtils.h"
+#include "gm.h"
 
 static SkBitmap make_chessbm(int w, int h) {
     SkBitmap bm;
@@ -35,7 +34,7 @@
 static sk_sp<SkImage> makebm(SkCanvas* origCanvas, SkBitmap* resultBM, int w, int h) {
     SkImageInfo info = SkImageInfo::MakeN32Premul(w, h);
 
-    auto surface(sk_tool_utils::makeSurface(origCanvas, info));
+    auto      surface(ToolUtils::makeSurface(origCanvas, info));
     SkCanvas* canvas = surface->getCanvas();
 
     canvas->clear(SK_ColorTRANSPARENT);
@@ -68,7 +67,7 @@
                         pt, radius,
                         colors, pos,
                         SK_ARRAY_COUNT(colors),
-                        SkShader::kRepeat_TileMode,
+                        SkTileMode::kRepeat,
                         0, &mat));
         canvas->drawRect(rect, paint);
         rect.inset(wScalar / 8, hScalar / 8);
@@ -163,7 +162,7 @@
         const int kPadX = 30;
         const int kPadY = 40;
         SkPaint paint;
-        paint.setAlpha(0x20);
+        paint.setAlphaf(0.125f);
         canvas->drawImageRect(fImage, SkRect::MakeIWH(gSize, gSize), &paint);
         canvas->translate(SK_Scalar1 * kPadX / 2,
                           SK_Scalar1 * kPadY / 2);
@@ -172,7 +171,7 @@
         blackPaint.setColor(SK_ColorBLACK);
         blackPaint.setAntiAlias(true);
 
-        SkFont font(sk_tool_utils::create_portable_typeface(), titleHeight);
+        SkFont font(ToolUtils::create_portable_typeface(), titleHeight);
 
         SkString title;
         title.printf("Bitmap size: %d x %d", gBmpSize, gBmpSize);
diff --git a/gm/drawimageset.cpp b/gm/drawimageset.cpp
index f993a7c..e204cbb 100644
--- a/gm/drawimageset.cpp
+++ b/gm/drawimageset.cpp
@@ -10,7 +10,7 @@
 #include <algorithm>
 #include "SkGradientShader.h"
 #include "SkSurface.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 // Makes a set of m x n tiled images to be drawn with SkCanvas::experimental_drawImageSetV1().
 static void make_image_tiles(int tileW, int tileH, int m, int n, const SkColor colors[4],
@@ -26,7 +26,7 @@
     SkPaint paint;
 
     SkPoint pts1[] = {{0.f, 0.f}, {(SkScalar)w, (SkScalar)h}};
-    auto grad = SkGradientShader::MakeLinear(pts1, colors, nullptr, 2, SkShader::kClamp_TileMode);
+    auto grad = SkGradientShader::MakeLinear(pts1, colors, nullptr, 2, SkTileMode::kClamp);
     paint.setShader(std::move(grad));
     paint.setAntiAlias(true);
     paint.setStyle(SkPaint::kStroke_Style);
@@ -39,7 +39,7 @@
     }
 
     SkPoint pts2[] = {{0.f, (SkScalar)h}, {(SkScalar)w, 0.f}};
-    grad = SkGradientShader::MakeLinear(pts2, colors + 2, nullptr, 2, SkShader::kClamp_TileMode);
+    grad = SkGradientShader::MakeLinear(pts2, colors + 2, nullptr, 2, SkTileMode::kClamp);
     paint.setShader(std::move(grad));
     paint.setBlendMode(SkBlendMode::kMultiply);
     stripePts[0] = {-w - kStripeW, h + kStripeW};
@@ -121,6 +121,10 @@
         SkAssertResult(matrices[3].setPolyToPoly(src, dst, 4));
         matrices[3].postTranslate(100.f, d);
         for (auto fm : {kNone_SkFilterQuality, kLow_SkFilterQuality}) {
+            SkPaint setPaint;
+            setPaint.setFilterQuality(fm);
+            setPaint.setBlendMode(SkBlendMode::kSrcOver);
+
             for (size_t m = 0; m < SK_ARRAY_COUNT(matrices); ++m) {
                 // Draw grid of red lines at interior tile boundaries.
                 static constexpr SkScalar kLineOutset = 10.f;
@@ -145,7 +149,8 @@
                 }
                 canvas->save();
                 canvas->concat(matrices[m]);
-                canvas->experimental_DrawImageSetV1(fSet, kM * kN, fm, SkBlendMode::kSrcOver);
+                canvas->experimental_DrawEdgeAAImageSet(fSet, kM * kN, nullptr, nullptr, &setPaint,
+                                                        SkCanvas::kFast_SrcRectConstraint);
                 canvas->restore();
             }
             // A more exotic case with an unusual blend mode, mixed aa flags set, and alpha,
@@ -158,7 +163,10 @@
             entry.fAAFlags = SkCanvas::kLeft_QuadAAFlag | SkCanvas::kTop_QuadAAFlag;
             canvas->save();
             canvas->rotate(3.f);
-            canvas->experimental_DrawImageSetV1(&entry, 1, fm, SkBlendMode::kExclusion);
+
+            setPaint.setBlendMode(SkBlendMode::kExclusion);
+            canvas->experimental_DrawEdgeAAImageSet(&entry, 1, nullptr, nullptr, &setPaint,
+                                                    SkCanvas::kFast_SrcRectConstraint);
             canvas->restore();
             canvas->translate(2 * d, 0);
         }
@@ -183,7 +191,7 @@
     }
 
     void onDraw(SkCanvas* canvas) override {
-        sk_tool_utils::draw_checkerboard(canvas, SK_ColorBLACK, SK_ColorWHITE, 50);
+        ToolUtils::draw_checkerboard(canvas, SK_ColorBLACK, SK_ColorWHITE, 50);
         static constexpr SkScalar kW = kM * kTileW;
         static constexpr SkScalar kH = kN * kTileH;
         SkMatrix matrices[5];
@@ -202,6 +210,10 @@
         matrices[4].postRotate(90, kW / 2.f, kH / 2.f);
         matrices[4].postScale(2.f, 0.5f);
 
+        SkPaint paint;
+        paint.setFilterQuality(kLow_SkFilterQuality);
+        paint.setBlendMode(SkBlendMode::kSrcOver);
+
         static constexpr SkScalar kTranslate = SkTMax(kW, kH) * 2.f + 10.f;
         canvas->translate(5.f, 5.f);
         canvas->save();
@@ -211,8 +223,8 @@
             for (size_t m = 0; m < SK_ARRAY_COUNT(matrices); ++m) {
                 canvas->save();
                 canvas->concat(matrices[m]);
-                canvas->experimental_DrawImageSetV1(fSet, kM * kN, kLow_SkFilterQuality,
-                                                    SkBlendMode::kSrcOver);
+                canvas->experimental_DrawEdgeAAImageSet(fSet, kM * kN, nullptr, nullptr, &paint,
+                                                        SkCanvas::kFast_SrcRectConstraint);
                 canvas->restore();
                 canvas->translate(kTranslate, 0);
             }
@@ -234,8 +246,8 @@
             for (size_t m = 0; m < SK_ARRAY_COUNT(matrices); ++m) {
                 canvas->save();
                 canvas->concat(matrices[m]);
-                canvas->experimental_DrawImageSetV1(scaledSet, kM * kN, kLow_SkFilterQuality,
-                                                    SkBlendMode::kSrcOver);
+                canvas->experimental_DrawEdgeAAImageSet(scaledSet, kM * kN, nullptr, nullptr,
+                                                        &paint, SkCanvas::kFast_SrcRectConstraint);
                 canvas->restore();
                 canvas->translate(kTranslate, 0);
             }
diff --git a/gm/drawlooper.cpp b/gm/drawlooper.cpp
index a9fe09f..cb1bf9c 100644
--- a/gm/drawlooper.cpp
+++ b/gm/drawlooper.cpp
@@ -5,14 +5,14 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkBlurMask.h"
 #include "SkCanvas.h"
 #include "SkGraphics.h"
 #include "SkLayerDrawLooper.h"
 #include "SkMaskFilter.h"
 #include "SkRandom.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #define WIDTH   200
 #define HEIGHT  200
@@ -39,7 +39,7 @@
         paint.setAntiAlias(true);
         paint.setLooper(fLooper);
 
-        SkFont font(sk_tool_utils::create_portable_typeface(), 72);
+        SkFont font(ToolUtils::create_portable_typeface(), 72);
 
         canvas->drawCircle(50, 50, 30, paint);
         canvas->drawRect({ 150, 50, 200, 100 }, paint);
diff --git a/gm/drawminibitmaprect.cpp b/gm/drawminibitmaprect.cpp
index be842dc..c8a875d 100644
--- a/gm/drawminibitmaprect.cpp
+++ b/gm/drawminibitmaprect.cpp
@@ -48,7 +48,7 @@
                         pt, radius,
                         colors, pos,
                         SK_ARRAY_COUNT(colors),
-                        SkShader::kRepeat_TileMode,
+                        SkTileMode::kRepeat,
                         0, &mat));
         canvas->drawRect(rect, paint);
         rect.inset(wScalar / 8, hScalar / 8);
diff --git a/gm/drawquadset.cpp b/gm/drawquadset.cpp
index 929124f..8b22fc5 100644
--- a/gm/drawquadset.cpp
+++ b/gm/drawquadset.cpp
@@ -31,7 +31,9 @@
 
     GrRenderTargetContext* rtc = canvas->internal_private_accessTopLayerRenderTargetContext();
 
-    auto gradient = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kMirror_TileMode);
+    GrContext* context = canvas->getGrContext();
+
+    auto gradient = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kMirror);
     SkPaint paint;
     paint.setShader(gradient);
 
@@ -63,17 +65,17 @@
                 // Use non-public API to leverage general GrPaint capabilities
                 SkMatrix view = canvas->getTotalMatrix();
                 GrPaint grPaint;
-                SkPaintToGrPaint(rtc->surfPriv().getContext(), rtc->colorSpaceInfo(), paint, view,
-                                 &grPaint);
-                rtc->fillRectWithEdgeAA(GrNoClip(), std::move(grPaint),
+                SkPaintToGrPaint(context, rtc->colorSpaceInfo(), paint, view, &grPaint);
+                rtc->fillRectWithEdgeAA(GrNoClip(), std::move(grPaint), GrAA::kYes,
                                         static_cast<GrQuadAAFlags>(aa), view, tile);
             } else {
                 // Fallback to solid color on raster backend since the public API only has color
                 SkColor color = alignGradients ? SK_ColorBLUE
                                                : (i * kColCount + j) % 2 == 0 ? SK_ColorBLUE
                                                                               : SK_ColorWHITE;
-                canvas->experimental_DrawEdgeAARectV1(
-                        tile, static_cast<SkCanvas::QuadAAFlags>(aa), color, SkBlendMode::kSrcOver);
+                canvas->experimental_DrawEdgeAAQuad(
+                        tile, nullptr, static_cast<SkCanvas::QuadAAFlags>(aa), color,
+                        SkBlendMode::kSrcOver);
             }
 
             if (!alignGradients) {
@@ -110,8 +112,8 @@
                 aa |= SkCanvas::kRight_QuadAAFlag;
             }
 
-            canvas->experimental_DrawEdgeAARectV1(
-                    tile, static_cast<SkCanvas::QuadAAFlags>(aa), color.toSkColor(),
+            canvas->experimental_DrawEdgeAAQuad(
+                    tile, nullptr, static_cast<SkCanvas::QuadAAFlags>(aa), color.toSkColor(),
                     SkBlendMode::kSrcOver);
         }
     }
diff --git a/gm/drawregionmodes.cpp b/gm/drawregionmodes.cpp
index 0a8a2ba..d0ecfb4 100644
--- a/gm/drawregionmodes.cpp
+++ b/gm/drawregionmodes.cpp
@@ -68,7 +68,7 @@
         SkPoint points[] = { SkPoint::Make(50.0f, 50.0f), SkPoint::Make(150.0f, 150.0f) };
         SkColor colors[] = { SK_ColorBLUE, SK_ColorYELLOW };
         paint.setShader(SkGradientShader::MakeLinear(points, colors, nullptr, 2,
-                                                     SkShader::kClamp_TileMode));
+                                                     SkTileMode::kClamp));
         canvas->drawRegion(fRegion, paint);
     }
 
diff --git a/gm/dropshadowimagefilter.cpp b/gm/dropshadowimagefilter.cpp
index 1710dbd..e14844d 100644
--- a/gm/dropshadowimagefilter.cpp
+++ b/gm/dropshadowimagefilter.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkColorFilter.h"
 #include "SkColorFilterImageFilter.h"
 #include "SkDropShadowImageFilter.h"
 #include "SkTextUtils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 ///////////////////////////////////////////////////////////////////////////////
 
@@ -41,7 +41,7 @@
     paint.setColor(SK_ColorGREEN);
     paint.setAntiAlias(true);
 
-    SkFont font(sk_tool_utils::create_portable_typeface(), r.height()/2);
+    SkFont font(ToolUtils::create_portable_typeface(), r.height() / 2);
     canvas->save();
     canvas->clipRect(r);
     SkTextUtils::DrawString(canvas, "Text", r.centerX(), r.centerY(), font, paint, SkTextUtils::kCenter_Align);
@@ -74,8 +74,7 @@
         draw_bitmap, draw_path, draw_paint, draw_text
     };
 
-    sk_sp<SkColorFilter> cf(SkColorFilter::MakeModeFilter(SK_ColorMAGENTA,
-                                                          SkBlendMode::kSrcIn));
+    sk_sp<SkColorFilter> cf(SkColorFilters::Blend(SK_ColorMAGENTA, SkBlendMode::kSrcIn));
     sk_sp<SkImageFilter> cfif(SkColorFilterImageFilter::Make(std::move(cf), nullptr));
     SkImageFilter::CropRect cropRect(SkRect::Make(SkIRect::MakeXYWH(10, 10, 44, 44)),
                                      SkImageFilter::CropRect::kHasAll_CropEdge);
diff --git a/gm/dstreadshuffle.cpp b/gm/dstreadshuffle.cpp
index fbaac15..a73b71c 100644
--- a/gm/dstreadshuffle.cpp
+++ b/gm/dstreadshuffle.cpp
@@ -4,13 +4,13 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkBitmap.h"
 #include "SkPath.h"
 #include "SkRandom.h"
 #include "SkShader.h"
 #include "SkSurface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -82,7 +82,7 @@
                 break;
             case kText_ShapeType: {
                 const char* text = "N";
-                SkFont font(sk_tool_utils::create_portable_typeface(), 100);
+                SkFont      font(ToolUtils::create_portable_typeface(), 100);
                 font.setEmbolden(true);
                 canvas->drawString(text, 0.f, 100.f, font, *paint);
             }
@@ -92,7 +92,7 @@
     }
 
     static SkColor GetColor(SkRandom* random) {
-        SkColor color = sk_tool_utils::color_to_565(random->nextU() | 0xFF000000);
+        SkColor color = ToolUtils::color_to_565(random->nextU() | 0xFF000000);
         return SkColorSetA(color, 0x80);
     }
 
diff --git a/gm/emboss.cpp b/gm/emboss.cpp
index 2a96247..04e0a36 100644
--- a/gm/emboss.cpp
+++ b/gm/emboss.cpp
@@ -53,7 +53,7 @@
 
         // this combination of emboss+colorfilter used to crash -- so we exercise it to
         // confirm that we have a fix.
-        paint.setColorFilter(SkColorFilter::MakeModeFilter(0xFFFF0000, SkBlendMode::kSrcATop));
+        paint.setColorFilter(SkColorFilters::Blend(0xFFFF0000, SkBlendMode::kSrcATop));
         canvas->drawBitmap(bm, 10, 10, &paint);
         canvas->translate(bm.width() + SkIntToScalar(10), 0);
 
@@ -64,7 +64,7 @@
             SkBlurMask::ConvertRadiusToSigma(4),
             { { SK_Scalar1, SK_Scalar1, SK_Scalar1 }, 0, 128, 16*2 }));
         paint.setColorFilter(nullptr);
-        paint.setShader(SkShader::MakeColorShader(SK_ColorBLUE));
+        paint.setShader(SkShaders::Color(SK_ColorBLUE));
         paint.setDither(true);
         canvas->drawCircle(SkIntToScalar(50), SkIntToScalar(50),
                            SkIntToScalar(30), paint);
diff --git a/gm/emptypath.cpp b/gm/emptypath.cpp
index 1c77580..4f77bfe 100644
--- a/gm/emptypath.cpp
+++ b/gm/emptypath.cpp
@@ -4,12 +4,12 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkPaint.h"
 #include "SkPath.h"
 #include "SkRandom.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -61,7 +61,7 @@
             {SkPaint::kStrokeAndFill_Style, "Stroke And Fill"},
         };
 
-        SkFont font(sk_tool_utils::create_portable_typeface(), 15);
+        SkFont     font(ToolUtils::create_portable_typeface(), 15);
         const char title[] = "Empty Paths Drawn Into Rectangle Clips With "
                              "Indicated Style and Fill";
         canvas->drawString(title, 20.0f, 20.0f, font, SkPaint());
@@ -86,7 +86,7 @@
 
                 SkColor color = rand.nextU();
                 color = 0xff000000 | color; // force solid
-                color = sk_tool_utils::color_to_565(color);
+                color         = ToolUtils::color_to_565(color);
                 this->drawEmpty(canvas, color, rect,
                                 gStyles[style].fStyle, gFills[fill].fFill);
 
@@ -99,7 +99,7 @@
 
                 SkPaint labelPaint;
                 labelPaint.setColor(color);
-                SkFont labelFont(sk_tool_utils::create_portable_typeface(), 12);
+                SkFont labelFont(ToolUtils::create_portable_typeface(), 12);
                 canvas->drawString(gStyles[style].fName, 0, rect.height() + 15.0f,
                                    labelFont, labelPaint);
                 canvas->drawString(gFills[fill].fName, 0, rect.height() + 28.0f,
diff --git a/gm/encode-alpha-jpeg.cpp b/gm/encode-alpha-jpeg.cpp
index f716c77..673283d 100644
--- a/gm/encode-alpha-jpeg.cpp
+++ b/gm/encode-alpha-jpeg.cpp
@@ -42,12 +42,12 @@
         return SkISize::Make(400, 200);
     }
 
-    void onDraw(SkCanvas* canvas) override {
+    DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
         sk_sp<SkImage> srcImg = GetResourceAsImage("images/rainbow-gradient.png");
         if (!srcImg) {
-            DrawFailureMessage(canvas, "Could not load images/rainbow-gradient.png. "
-                                       "Did you forget to set the resourcePath?");
-            return;
+            *errorMsg = "Could not load images/rainbow-gradient.png. "
+                        "Did you forget to set the resourcePath?";
+            return DrawResult::kFail;
         }
         fStorage.reset(srcImg->width() * srcImg->height() *
                 SkColorTypeBytesPerPixel(kRGBA_F16_SkColorType));
@@ -71,24 +71,24 @@
         canvas->drawImage(img0, 100.0f, 0.0f);
         canvas->drawImage(img1, 100.0f, 100.0f);
 
-        if (canvas->imageInfo().colorSpace()) {
-            // Encode F16 premul
-            info = SkImageInfo::Make(srcImg->width(), srcImg->height(), kRGBA_F16_SkColorType,
-                    kPremul_SkAlphaType, SkColorSpace::MakeSRGBLinear());
-            read_into_pixmap(&src, info, fStorage.get(), srcImg);
-            img0 = encode_pixmap_and_make_image(src, SkJpegEncoder::AlphaOption::kIgnore);
-            img1 = encode_pixmap_and_make_image(src, SkJpegEncoder::AlphaOption::kBlendOnBlack);
-            canvas->drawImage(img0, 200.0f, 0.0f);
-            canvas->drawImage(img1, 200.0f, 100.0f);
+        // Encode F16 premul
+        info = SkImageInfo::Make(srcImg->width(), srcImg->height(), kRGBA_F16_SkColorType,
+                kPremul_SkAlphaType, SkColorSpace::MakeSRGB());
+        read_into_pixmap(&src, info, fStorage.get(), srcImg);
+        img0 = encode_pixmap_and_make_image(src, SkJpegEncoder::AlphaOption::kIgnore);
+        img1 = encode_pixmap_and_make_image(src, SkJpegEncoder::AlphaOption::kBlendOnBlack);
+        canvas->drawImage(img0, 200.0f, 0.0f);
+        canvas->drawImage(img1, 200.0f, 100.0f);
 
-            // Encode F16 unpremul
-            info = info.makeAlphaType(kUnpremul_SkAlphaType);
-            read_into_pixmap(&src, info, fStorage.get(), srcImg);
-            img0 = encode_pixmap_and_make_image(src, SkJpegEncoder::AlphaOption::kIgnore);
-            img1 = encode_pixmap_and_make_image(src, SkJpegEncoder::AlphaOption::kBlendOnBlack);
-            canvas->drawImage(img0, 300.0f, 0.0f);
-            canvas->drawImage(img1, 300.0f, 100.0f);
-        }
+        // Encode F16 unpremul
+        info = info.makeAlphaType(kUnpremul_SkAlphaType);
+        read_into_pixmap(&src, info, fStorage.get(), srcImg);
+        img0 = encode_pixmap_and_make_image(src, SkJpegEncoder::AlphaOption::kIgnore);
+        img1 = encode_pixmap_and_make_image(src, SkJpegEncoder::AlphaOption::kBlendOnBlack);
+        canvas->drawImage(img0, 300.0f, 0.0f);
+        canvas->drawImage(img1, 300.0f, 100.0f);
+
+        return DrawResult::kOk;
     }
 
 private:
diff --git a/gm/encode-platform.cpp b/gm/encode-platform.cpp
index c9ea8ce..6f7b301 100644
--- a/gm/encode-platform.cpp
+++ b/gm/encode-platform.cpp
@@ -81,19 +81,19 @@
         return SkISize::Make(256 * SK_ARRAY_COUNT(kTypes), 256 * 3);
     }
 
-    void onDraw(SkCanvas* canvas) override {
+    DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
         SkBitmap opaqueBm, premulBm, unpremulBm;
 
         if (!GetResourceAsBitmap("images/mandrill_256.png", &opaqueBm)) {
-            DrawFailureMessage(canvas, "Could not load images/mandrill_256.png.png. "
-                                       "Did you forget to set the resourcePath?");
-            return;
+            *errorMsg = "Could not load images/mandrill_256.png.png. "
+                        "Did you forget to set the resourcePath?";
+            return DrawResult::kFail;
         }
         SkBitmap tmp;
         if (!GetResourceAsBitmap("images/yellow_rose.png", &tmp)) {
-            DrawFailureMessage(canvas, "Could not load images/yellow_rose.png. "
-                                       "Did you forget to set the resourcePath?");
-            return;
+            *errorMsg = "Could not load images/yellow_rose.png. "
+                        "Did you forget to set the resourcePath?";
+            return DrawResult::kFail;
         }
         tmp.extractSubset(&premulBm, SkIRect::MakeWH(256, 256));
         tmp.reset();
@@ -111,6 +111,7 @@
 
             canvas->translate(256.0f, 0.0f);
         }
+        return DrawResult::kOk;
     }
 
 private:
diff --git a/gm/etc1.cpp b/gm/etc1.cpp
index 4a716f5..a1bc415 100644
--- a/gm/etc1.cpp
+++ b/gm/etc1.cpp
@@ -5,10 +5,10 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkImage.h"
 #include "SkRandom.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #if SK_SUPPORT_GPU && !defined(SK_BUILD_FOR_GOOGLE3)
 #include "etc1.h"
@@ -18,7 +18,7 @@
 #include "GrRenderTargetContext.h"
 #include "GrRenderTargetContextPriv.h"
 #include "GrTextureProxy.h"
-#include "effects/GrSimpleTextureEffect.h"
+#include "effects/generated/GrSimpleTextureEffect.h"
 #include "ops/GrFillRectOp.h"
 
 // Basic test of Ganesh's ETC1 support
@@ -62,8 +62,7 @@
         }
     }
 
-    void onDraw(GrContext* context, GrRenderTargetContext* renderTargetContext,
-                SkCanvas* canvas) override {
+    void onDraw(GrContext* context, GrRenderTargetContext*, SkCanvas* canvas) override {
         sk_sp<SkImage> image = SkImage::MakeFromCompressed(context, fETC1Data,
                                                            kTexWidth, kTexHeight,
                                                            SkImage::kETC1_CompressionType);
diff --git a/gm/fadefilter.cpp b/gm/fadefilter.cpp
index c9c362b..f7fb9c9 100644
--- a/gm/fadefilter.cpp
+++ b/gm/fadefilter.cpp
@@ -15,7 +15,7 @@
                             0, 1, 0, 0, 128.0f,
                             0, 0, 1, 0, 128.0f,
                             0, 0, 0, 1, 0 };
-    sk_sp<SkColorFilter> colorFilter(SkColorFilter::MakeMatrixFilterRowMajor255(matrix));
+    sk_sp<SkColorFilter> colorFilter(SkColorFilters::MatrixRowMajor255(matrix));
     SkPaint layerPaint;
     layerPaint.setImageFilter(SkColorFilterImageFilter::Make(std::move(colorFilter), nullptr));
     canvas->drawRect(SkRect::MakeLTRB(64, 64, 192, 192), layerPaint);
diff --git a/gm/fatpathfill.cpp b/gm/fatpathfill.cpp
index 21d9085..3b842a2 100644
--- a/gm/fatpathfill.cpp
+++ b/gm/fatpathfill.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkPath.h"
 #include "SkSurface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #define ZOOM    32
 #define SMALL_W 9
@@ -22,7 +22,7 @@
 
 static void draw_pixel_centers(SkCanvas* canvas) {
     SkPaint paint;
-    paint.setColor(sk_tool_utils::color_to_565(0xFF0088FF));
+    paint.setColor(ToolUtils::color_to_565(0xFF0088FF));
     paint.setAntiAlias(true);
 
     for (int y = 0; y < SMALL_H; ++y) {
diff --git a/gm/filltypespersp.cpp b/gm/filltypespersp.cpp
index 01c930d..10ca319 100644
--- a/gm/filltypespersp.cpp
+++ b/gm/filltypespersp.cpp
@@ -60,7 +60,7 @@
                                                      colors,
                                                      pos,
                                                      SK_ARRAY_COUNT(colors),
-                                                     SkShader::kClamp_TileMode));
+                                                     SkTileMode::kClamp));
         paint.setAntiAlias(aa);
 
         showPath(canvas,   0,   0, SkPath::kWinding_FillType,
@@ -89,7 +89,7 @@
                                                       colors,
                                                       pos,
                                                       SK_ARRAY_COUNT(colors),
-                                                      SkShader::kClamp_TileMode));
+                                                      SkTileMode::kClamp));
         canvas->save();
             canvas->translate(SkIntToScalar(100), SkIntToScalar(100));
             SkMatrix mat;
diff --git a/gm/filterbitmap.cpp b/gm/filterbitmap.cpp
index 9694155..0deb828 100644
--- a/gm/filterbitmap.cpp
+++ b/gm/filterbitmap.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "Resources.h"
 #include "SkGradientShader.h"
@@ -14,7 +14,7 @@
 #include "SkTypeface.h"
 
 static void setTypeface(SkFont* font, const char name[], SkFontStyle style) {
-    font->setTypeface(sk_tool_utils::create_portable_typeface(name, style));
+    font->setTypeface(ToolUtils::create_portable_typeface(name, style));
 }
 
 static SkSize computeSize(const SkBitmap& bm, const SkMatrix& mat) {
@@ -170,7 +170,7 @@
           }
           if (fConvertToG8) {
               SkBitmap tmp;
-              sk_tool_utils::copy_to_g8(&tmp, fBM);
+              ToolUtils::copy_to_g8(&tmp, fBM);
               fBM = tmp;
           }
       }
@@ -205,7 +205,7 @@
 
         if (fConvertToG8) {
             SkBitmap tmp;
-            sk_tool_utils::copy_to_g8(&tmp, fBM);
+            ToolUtils::copy_to_g8(&tmp, fBM);
             fBM = tmp;
         }
       }
diff --git a/gm/filterbug.cpp b/gm/filterbug.cpp
index 258374f..bd413b3 100644
--- a/gm/filterbug.cpp
+++ b/gm/filterbug.cpp
@@ -7,7 +7,7 @@
 
 #include "gm.h"
 #include "SkColorPriv.h"
-#include "SkImageShader.h"
+#include "SkImage.h"
 
 static const sk_sp<SkImage> make_image(int firstBlackRow, int lastBlackRow) {
     static const int kWidth = 25;
@@ -56,9 +56,7 @@
             p1.setFilterQuality(kFilterQuality);
             SkMatrix localMat;
             localMat.setScaleTranslate(2.0f, 2.0f, 50.0f, 0.0f);
-            p1.setShader(SkImageShader::Make(fTop,
-                                             SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode,
-                                             &localMat));
+            p1.setShader(fTop->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &localMat));
 
             canvas->drawRect(r1, p1);
         }
@@ -82,9 +80,7 @@
             p3.setFilterQuality(kFilterQuality);
             SkMatrix localMat;
             localMat.setScaleTranslate(2.0f, 2.0f, 50.0f, 86.0f);
-            p3.setShader(SkImageShader::Make(fBot,
-                                             SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode,
-                                             &localMat));
+            p3.setShader(fBot->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &localMat));
 
             canvas->drawRect(r3, p3);
         }
diff --git a/gm/filterindiabox.cpp b/gm/filterindiabox.cpp
index 01ecb21..fe4f31f 100644
--- a/gm/filterindiabox.cpp
+++ b/gm/filterindiabox.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "Resources.h"
 #include "SkBitmapProcState.h"
@@ -60,9 +60,7 @@
     SkMatrix    fMatrix[2];
     SkString    fName;
 
-    FilterIndiaBoxGM() {
-        this->setBGColor(sk_tool_utils::color_to_565(0xFFDDDDDD));
-    }
+    FilterIndiaBoxGM() { this->setBGColor(ToolUtils::color_to_565(0xFFDDDDDD)); }
 
     FilterIndiaBoxGM(const char filename[]) : fFilename(filename) {
         fName.printf("filterindiabox");
diff --git a/gm/flippity.cpp b/gm/flippity.cpp
index ccd16f6..4a8696e 100644
--- a/gm/flippity.cpp
+++ b/gm/flippity.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkSurface.h"
 
@@ -64,7 +64,7 @@
 
     SkFont font;
     font.setEdging(SkFont::Edging::kAntiAlias);
-    font.setTypeface(sk_tool_utils::create_portable_typeface());
+    font.setTypeface(ToolUtils::create_portable_typeface());
     font.setSize(32);
 
     SkRect bounds;
diff --git a/gm/fontcache.cpp b/gm/fontcache.cpp
index d19fc67..a96e944 100644
--- a/gm/fontcache.cpp
+++ b/gm/fontcache.cpp
@@ -11,14 +11,14 @@
 #include "gm.h"
 
 #include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrContextOptions.h"
+#include "GrContextPriv.h"
 #include "SkCanvas.h"
 #include "SkGraphics.h"
 #include "SkImage.h"
 #include "SkTypeface.h"
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 static SkScalar draw_string(SkCanvas* canvas, const SkString& text, SkScalar x,
                            SkScalar y, const SkFont& font) {
@@ -51,16 +51,15 @@
     SkISize onISize() override { return SkISize::Make(kSize, kSize); }
 
     void onOnceBeforeDraw() override {
-        fTypefaces[0] = sk_tool_utils::create_portable_typeface("serif", SkFontStyle::Italic());
-        fTypefaces[1] = sk_tool_utils::create_portable_typeface("sans-serif",SkFontStyle::Italic());
-        fTypefaces[2] = sk_tool_utils::create_portable_typeface("serif", SkFontStyle::Normal());
-        fTypefaces[3] =
-                sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle::Normal());
-        fTypefaces[4] = sk_tool_utils::create_portable_typeface("serif", SkFontStyle::Bold());
-        fTypefaces[5] = sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle::Bold());
+        fTypefaces[0] = ToolUtils::create_portable_typeface("serif", SkFontStyle::Italic());
+        fTypefaces[1] = ToolUtils::create_portable_typeface("sans-serif", SkFontStyle::Italic());
+        fTypefaces[2] = ToolUtils::create_portable_typeface("serif", SkFontStyle::Normal());
+        fTypefaces[3] = ToolUtils::create_portable_typeface("sans-serif", SkFontStyle::Normal());
+        fTypefaces[4] = ToolUtils::create_portable_typeface("serif", SkFontStyle::Bold());
+        fTypefaces[5] = ToolUtils::create_portable_typeface("sans-serif", SkFontStyle::Bold());
     }
 
-    void onDraw(GrContext*, GrRenderTargetContext* renderTargetContext, SkCanvas* canvas) override {
+    void onDraw(GrContext*, GrRenderTargetContext*, SkCanvas* canvas) override {
         this->drawText(canvas);
         //  Debugging tool for GPU.
         static const bool kShowAtlas = false;
diff --git a/gm/fontmgr.cpp b/gm/fontmgr.cpp
index 2249b51..efa9ad1 100644
--- a/gm/fontmgr.cpp
+++ b/gm/fontmgr.cpp
@@ -5,16 +5,17 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
+#include "CommandLineFlags.h"
 #include "SkCanvas.h"
-#include "SkCommonFlags.h"
 #include "SkFontMetrics.h"
 #include "SkFontMgr.h"
 #include "SkFontPriv.h"
-#include "SkPath.h"
 #include "SkGraphics.h"
+#include "SkMetaData.h"
+#include "SkPath.h"
 #include "SkTypeface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 // limit this just so we don't take too long to draw
 #define MAX_FAMILIES    30
@@ -61,7 +62,6 @@
 
         fName.set("fontmgr_iter");
         fFM = SkFontMgr::RefDefault();
-        fName.append(sk_tool_utils::platform_font_manager());
     }
 
 protected:
@@ -127,9 +127,7 @@
 
 protected:
     SkString onShortName() override {
-        SkString name("fontmgr_match");
-        name.append(sk_tool_utils::platform_font_manager());
-        return name;
+        return SkString("fontmgr_match");
     }
 
     SkISize onISize() override {
@@ -172,7 +170,7 @@
         }
     }
 
-    void onDraw(SkCanvas* canvas) override {
+    DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
         SkFont font;
         font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
         font.setSubpixel(true);
@@ -190,14 +188,15 @@
             }
         }
         if (nullptr == fset.get()) {
-            DrawFailureMessage(canvas, "No SkFontStyleSet");
-            return;
+            *errorMsg = "No SkFontStyleSet";
+            return DrawResult::kFail;
         }
 
         canvas->translate(20, 40);
         this->exploreFamily(canvas, font, fset.get());
         canvas->translate(150, 0);
         this->iterateFamily(canvas, font, fset.get());
+        return DrawResult::kOk;
     }
 
 private:
@@ -207,19 +206,28 @@
 class FontMgrBoundsGM : public skiagm::GM {
 public:
     FontMgrBoundsGM(double scale, double skew)
-        : fScaleX(SkDoubleToScalar(scale))
+        : fFM(SkFontMgr::RefDefault())
+        , fName("fontmgr_bounds")
+        , fScaleX(SkDoubleToScalar(scale))
         , fSkewX(SkDoubleToScalar(skew))
+        , fLabelBounds(false)
     {
-        fName.set("fontmgr_bounds");
         if (scale != 1 || skew != 0) {
             fName.appendf("_%g_%g", scale, skew);
         }
-        fName.append(sk_tool_utils::platform_font_manager());
-        fFM = SkFontMgr::RefDefault();
+    }
+
+    bool onGetControls(SkMetaData* controls) override {
+        controls->setBool("Label Bounds", fLabelBounds);
+        return true;
+    }
+
+    void onSetControls(const SkMetaData& controls) override {
+        controls.findBool("Label Bounds", &fLabelBounds);
     }
 
     static void show_bounds(SkCanvas* canvas, const SkFont& font, SkScalar x, SkScalar y,
-                            SkColor boundsColor)
+                            SkColor boundsColor, bool labelBounds)
     {
         SkRect fontBounds = SkFontPriv::GetFontBounds(font).makeOffset(x, y);
 
@@ -233,9 +241,9 @@
         font.getMetrics(&fm);
         SkPaint metricsPaint(boundsPaint);
         metricsPaint.setStyle(SkPaint::kFill_Style);
-        metricsPaint.setAlpha(0x40);
+        metricsPaint.setAlphaf(0.25f);
         if ((fm.fFlags & SkFontMetrics::kUnderlinePositionIsValid_Flag) &&
-            (fm.fFlags & SkFontMetrics::kUnderlinePositionIsValid_Flag))
+            (fm.fFlags & SkFontMetrics::kUnderlineThicknessIsValid_Flag))
         {
             SkRect underline{ fontBounds.fLeft,  fm.fUnderlinePosition+y,
                               fontBounds.fRight, fm.fUnderlinePosition+y + fm.fUnderlineThickness };
@@ -243,7 +251,7 @@
         }
 
         if ((fm.fFlags & SkFontMetrics::kStrikeoutPositionIsValid_Flag) &&
-            (fm.fFlags & SkFontMetrics::kStrikeoutPositionIsValid_Flag))
+            (fm.fFlags & SkFontMetrics::kStrikeoutThicknessIsValid_Flag))
         {
             SkRect strikeout{ fontBounds.fLeft,  fm.fStrikeoutPosition+y - fm.fStrikeoutThickness,
                               fontBounds.fRight, fm.fStrikeoutPosition+y };
@@ -274,9 +282,9 @@
 
         SkFont labelFont;
         labelFont.setEdging(SkFont::Edging::kAntiAlias);
-        labelFont.setTypeface(sk_tool_utils::create_portable_typeface());
+        labelFont.setTypeface(ToolUtils::create_portable_typeface());
 
-        if (FLAGS_veryVerbose) {
+        if (labelBounds) {
             SkString name;
             font.getTypefaceOrDefault()->getFamilyName(&name);
             canvas->drawString(name, fontBounds.fLeft, fontBounds.fBottom, labelFont, SkPaint());
@@ -290,7 +298,7 @@
             glyphPaint.setStyle(style);
             canvas->drawSimpleText(&str[i], sizeof(str[0]), kGlyphID_SkTextEncoding, x, y, font, glyphPaint);
 
-            if (FLAGS_veryVerbose) {
+            if (labelBounds) {
                 SkString glyphStr;
                 glyphStr.appendS32(str[i]);
                 canvas->drawString(glyphStr, location[i].fX, location[i].fY, labelFont, SkPaint());
@@ -336,7 +344,7 @@
                 if (font.getTypefaceOrDefault() && font.getTypefaceOrDefault()->countGlyphs() < 1000) {
                     SkRect fontBounds = SkFontPriv::GetFontBounds(font);
                     x -= fontBounds.fLeft;
-                    show_bounds(canvas, font, x, y, boundsColors[index & 1]);
+                    show_bounds(canvas, font, x, y, boundsColors[index & 1], fLabelBounds);
                     x += fontBounds.fRight + 20;
                     index += 1;
                     if (x > 900) {
@@ -352,9 +360,11 @@
     }
 
 private:
-    sk_sp<SkFontMgr> fFM;
+    const sk_sp<SkFontMgr> fFM;
     SkString fName;
-    SkScalar fScaleX, fSkewX;
+    const SkScalar fScaleX;
+    const SkScalar fSkewX;
+    bool fLabelBounds;
     typedef GM INHERITED;
 };
 
diff --git a/gm/fontregen.cpp b/gm/fontregen.cpp
index 21cd42c..7f1d697 100644
--- a/gm/fontregen.cpp
+++ b/gm/fontregen.cpp
@@ -24,7 +24,7 @@
 #include "SkTypeface.h"
 #include "gm.h"
 
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 static sk_sp<SkTextBlob> make_blob(const SkString& text, const SkFont& font) {
     size_t len = text.size();
@@ -56,7 +56,7 @@
     SkISize onISize() override { return SkISize::Make(kSize, kSize); }
 
     void onOnceBeforeDraw() override {
-        auto tf = sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle::Normal());
+        auto tf = ToolUtils::create_portable_typeface("sans-serif", SkFontStyle::Normal());
 
         static const SkString kTexts[] = {
             SkString("abcdefghijklmnopqrstuvwxyz"),
@@ -76,12 +76,12 @@
         fBlobs[2] = make_blob(kTexts[2], font);
     }
 
-    void onDraw(GrContext*, GrRenderTargetContext* renderTargetContext, SkCanvas* canvas) override {
+    void onDraw(GrContext* context, GrRenderTargetContext*, SkCanvas* canvas) override {
         SkPaint paint;
         paint.setColor(SK_ColorBLACK);
         canvas->drawTextBlob(fBlobs[0], 10, 80, paint);
         canvas->drawTextBlob(fBlobs[1], 10, 225, paint);
-        canvas->flush();
+        context->flush();
 
         paint.setColor(0xFF010101);
         canvas->drawTextBlob(fBlobs[0], 10, 305, paint);
@@ -90,10 +90,8 @@
         //  Debugging tool for GPU.
         static const bool kShowAtlas = false;
         if (kShowAtlas) {
-            if (auto ctx = canvas->getGrContext()) {
-                auto img = ctx->priv().testingOnly_getFontAtlasImage(kA8_GrMaskFormat);
-                canvas->drawImage(img, 200, 0);
-            }
+            auto img = context->priv().testingOnly_getFontAtlasImage(kA8_GrMaskFormat);
+            canvas->drawImage(img, 200, 0);
         }
     }
 
diff --git a/gm/fontscaler.cpp b/gm/fontscaler.cpp
index 51d8425..85e58e4 100644
--- a/gm/fontscaler.cpp
+++ b/gm/fontscaler.cpp
@@ -5,9 +5,9 @@
  * found in the LICENSE file.
  */
 #include <SkFont.h>
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkTypeface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -20,9 +20,7 @@
 protected:
 
     SkString onShortName() override {
-        SkString name("fontscaler");
-        name.append(sk_tool_utils::platform_font_manager());
-        return name;
+        return SkString("fontscaler");
     }
 
     SkISize onISize() override {
diff --git a/gm/fontscalerdistortable.cpp b/gm/fontscalerdistortable.cpp
index e112f75..7ee043d 100644
--- a/gm/fontscalerdistortable.cpp
+++ b/gm/fontscalerdistortable.cpp
@@ -30,7 +30,7 @@
         return SkISize::Make(550, 700);
     }
 
-    void onDraw(SkCanvas* canvas) override {
+    DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
         SkPaint paint;
         paint.setAntiAlias(true);
         SkFont font;
@@ -41,8 +41,8 @@
         sk_sp<SkTypeface> distortable(MakeResourceAsTypeface("fonts/Distortable.ttf"));
 
         if (!distortableStream) {
-            DrawFailureMessage(canvas, "No distortableStream");
-            return;
+            *errorMsg = "No distortableStream";
+            return DrawResult::kFail;
         }
         const char* text = "abc";
         const size_t textLen = strlen(text);
@@ -89,6 +89,7 @@
             canvas->translate(0, SkIntToScalar(360));
             font.setSubpixel(true);
         }
+        return DrawResult::kOk;
     }
 
 private:
diff --git a/gm/fwidth_squircle.cpp b/gm/fwidth_squircle.cpp
index 3ae8cbe..829c3d9 100644
--- a/gm/fwidth_squircle.cpp
+++ b/gm/fwidth_squircle.cpp
@@ -5,15 +5,19 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkTextUtils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #if SK_SUPPORT_GPU
 
 #include "GrContext.h"
+#include "GrContextPriv.h"
 #include "GrGpuCommandBuffer.h"
 #include "GrMemoryPool.h"
+#include "GrOpFlushState.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrRenderTargetContextPriv.h"
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
@@ -108,7 +112,7 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* ctx, const SkMatrix& viewMatrix) {
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* ctx, const SkMatrix& viewMatrix) {
         GrOpMemoryPool* pool = ctx->priv().opMemoryPool();
         return pool->allocate<FwidthSquircleTestOp>(viewMatrix);
     }
@@ -122,7 +126,8 @@
 
     const char* name() const override { return "ClockwiseTestOp"; }
     FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
-    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*) override {
+    GrProcessorSet::Analysis finalize(
+            const GrCaps&, const GrAppliedClip*, GrFSAAType, GrClampType) override {
         return GrProcessorSet::EmptySetAnalysis();
     }
     void onPrepare(GrOpFlushState*) override {}
@@ -134,8 +139,7 @@
             {+1, +1},
         };
         sk_sp<const GrBuffer> vertexBuffer(flushState->resourceProvider()->createBuffer(
-                sizeof(vertices), GrGpuBufferType::kVertex, kStatic_GrAccessPattern,
-                GrResourceProvider::Flags::kNone, vertices));
+                sizeof(vertices), GrGpuBufferType::kVertex, kStatic_GrAccessPattern, vertices));
         if (!vertexBuffer) {
             return;
         }
@@ -155,15 +159,16 @@
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // Test.
 
-DEF_SIMPLE_GPU_GM(fwidth_squircle, ctx, rtc, canvas, 200, 200) {
+DEF_SIMPLE_GPU_GM_CAN_FAIL(fwidth_squircle, ctx, rtc, canvas, errorMsg, 200, 200) {
     if (!ctx->priv().caps()->shaderCaps()->shaderDerivativeSupport()) {
-        skiagm::GM::DrawFailureMessage(canvas, "Shader derivatives not supported.");
-        return;
+        *errorMsg = "Shader derivatives not supported.";
+        return DrawResult::kSkip;
     }
 
     // Draw the test directly to the frame buffer.
     canvas->clear(SK_ColorWHITE);
     rtc->priv().testingOnly_addDrawOp(FwidthSquircleTestOp::Make(ctx, canvas->getTotalMatrix()));
+    return skiagm::DrawResult::kOk;
 }
 
 }
diff --git a/gm/gamma.cpp b/gm/gamma.cpp
index 6c854ae..9cdd870 100644
--- a/gm/gamma.cpp
+++ b/gm/gamma.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "Resources.h"
 #include "SkBlendModePriv.h"
@@ -18,7 +18,7 @@
     const int szInt = SkScalarTruncToInt(sz);
     const SkScalar tx = sz + 15.0f;
     const SkRect r = SkRect::MakeXYWH(0, 0, sz, sz);
-    SkShader::TileMode rpt = SkShader::kRepeat_TileMode;
+    SkTileMode rpt = SkTileMode::kRepeat;
     auto srgbColorSpace = SkColorSpace::MakeSRGB();
 
     SkBitmap ditherBmp;
@@ -49,7 +49,7 @@
     mipmapPixels[0] = mipmapPixels[3] = SkPackARGB32(0xFF, s25, s25, s25);
     mipmapPixels[1] = mipmapPixels[2] = SkPackARGB32(0xFF, s75, s75, s75);
 
-    SkFont font(sk_tool_utils::create_portable_typeface());
+    SkFont font(ToolUtils::create_portable_typeface());
 
     SkPaint textPaint;
     textPaint.setAntiAlias(true);
@@ -104,7 +104,7 @@
     canvas->save();
 
     // Black/white dither, pixel perfect. This is ground truth.
-    p.setShader(SkShader::MakeBitmapShader(ditherBmp, rpt, rpt));
+    p.setShader(ditherBmp.makeShader(rpt, rpt));
     p.setFilterQuality(SkFilterQuality::kNone_SkFilterQuality);
     nextRect("Dither", "Reference");
 
@@ -113,19 +113,19 @@
     // the raster pipeline into *not* snapping to nearest.
     SkMatrix offsetMatrix = SkMatrix::Concat(
         SkMatrix::MakeScale(-1.0f), SkMatrix::MakeTrans(0.5f, 0.0f));
-    p.setShader(SkShader::MakeBitmapShader(ditherBmp, rpt, rpt, &offsetMatrix));
+    p.setShader(ditherBmp.makeShader(rpt, rpt, &offsetMatrix));
     p.setFilterQuality(SkFilterQuality::kMedium_SkFilterQuality);
     nextRect("Dither", "Bilerp");
 
     // Black/white dither, scaled down by 2x. Tests minification.
     SkMatrix scaleMatrix = SkMatrix::MakeScale(0.5f);
-    p.setShader(SkShader::MakeBitmapShader(ditherBmp, rpt, rpt, &scaleMatrix));
+    p.setShader(ditherBmp.makeShader(rpt, rpt, &scaleMatrix));
     p.setFilterQuality(SkFilterQuality::kMedium_SkFilterQuality);
     nextRect("Dither", "Scale");
 
     // 25%/75% dither, scaled down by 2x. Tests ALL aspects of minification. Specifically, are
     // sRGB sources decoded to linear before computing mipmaps?
-    p.setShader(SkShader::MakeBitmapShader(mipmapBmp, rpt, rpt, &scaleMatrix));
+    p.setShader(mipmapBmp.makeShader(rpt, rpt, &scaleMatrix));
     p.setFilterQuality(SkFilterQuality::kMedium_SkFilterQuality);
     nextRect("MipMaps", nullptr);
 
@@ -141,8 +141,7 @@
             SkPoint::Make(sz + (sz * 10), 0)
         };
         SkColor colors[2] = { SK_ColorBLACK, SK_ColorWHITE };
-        p.setShader(SkGradientShader::MakeLinear(points, colors, nullptr, 2,
-                                                 SkShader::kClamp_TileMode));
+        p.setShader(SkGradientShader::MakeLinear(points, colors, nullptr, 2, SkTileMode::kClamp));
         nextRect("Gradient", "Interpolation");
     }
 
@@ -154,8 +153,7 @@
             SkPoint::Make(sz, 0)
         };
         SkColor colors[2] = { 0xffbbbbbb, 0xffbdbdbd };
-        p.setShader(SkGradientShader::MakeLinear(points, colors, nullptr, 2,
-                                                 SkShader::kClamp_TileMode));
+        p.setShader(SkGradientShader::MakeLinear(points, colors, nullptr, 2, SkTileMode::kClamp));
         nextRect("Gradient", "Endpoints");
     }
 
@@ -167,8 +165,7 @@
             SkPoint::Make(sz, 0)
         };
         SkColor colors[3] = { 0xffbbbbbb, 0xffbdbdbd, 0xffbbbbbb };
-        p.setShader(SkGradientShader::MakeLinear(points, colors, nullptr, 3,
-                                                 SkShader::kClamp_TileMode));
+        p.setShader(SkGradientShader::MakeLinear(points, colors, nullptr, 3, SkTileMode::kClamp));
         nextRect("Gradient", "3-Stop");
     }
 
@@ -180,8 +177,7 @@
             SkPoint::Make(sz, 0)
         };
         SkColor colors[5] = { 0xffbbbbbb, 0xffbdbdbd, 0xffbbbbbb, 0xffbdbdbd, 0xffbbbbbb };
-        p.setShader(SkGradientShader::MakeLinear(points, colors, nullptr, 5,
-                                                 SkShader::kClamp_TileMode));
+        p.setShader(SkGradientShader::MakeLinear(points, colors, nullptr, 5, SkTileMode::kClamp));
         nextRect("Gradient", "Texture");
     }
 
@@ -192,12 +188,12 @@
     nextBitmap(srgbGreyBmp, "sRGB BMP");
 
     // Bitmap wrapped in a shader (linear):
-    p.setShader(SkShader::MakeBitmapShader(linearGreyBmp, rpt, rpt));
+    p.setShader(linearGreyBmp.makeShader(rpt, rpt));
     p.setFilterQuality(SkFilterQuality::kMedium_SkFilterQuality);
     nextRect("Lnr BMP", "Shader");
 
     // Bitmap wrapped in a shader (sRGB):
-    p.setShader(SkShader::MakeBitmapShader(srgbGreyBmp, rpt, rpt));
+    p.setShader(srgbGreyBmp.makeShader(rpt, rpt));
     p.setFilterQuality(SkFilterQuality::kMedium_SkFilterQuality);
     nextRect("sRGB BMP", "Shader");
 
diff --git a/gm/gammatext.cpp b/gm/gammatext.cpp
index fcdac17..17c795f 100644
--- a/gm/gammatext.cpp
+++ b/gm/gammatext.cpp
@@ -5,18 +5,17 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
-#include "SkPath.h"
 #include "SkGradientShader.h"
+#include "SkPath.h"
 #include "SkTypeface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static sk_sp<SkShader> make_heatGradient(const SkPoint pts[2]) {
     const SkColor bw[] = { SK_ColorBLACK, SK_ColorWHITE };
 
-    return SkGradientShader::MakeLinear(pts, bw, nullptr, SK_ARRAY_COUNT(bw),
-                                        SkShader::kClamp_TileMode);
+    return SkGradientShader::MakeLinear(pts, bw, nullptr, SK_ARRAY_COUNT(bw), SkTileMode::kClamp);
 }
 
 /**
@@ -31,9 +30,7 @@
 class GammaTextGM : public skiagm::GM {
 protected:
     SkString onShortName() override {
-        SkString name("gammatext");
-        name.append(sk_tool_utils::platform_font_manager());
-        return name;
+        return SkString("gammatext");
     }
 
     SkISize onISize() override {
@@ -93,7 +90,7 @@
     SkColor colors[2];
     colors[0] = c;
     colors[1] = SkColorSetA(c, 0);
-    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode);
+    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
 }
 
 static void draw_pair(SkCanvas* canvas, const SkFont& font, SkColor color,
@@ -102,7 +99,7 @@
     SkPaint paint;
     paint.setColor(color);
     canvas->drawString(text, 10, 20, font, paint);
-    paint.setShader(SkShader::MakeColorShader(paint.getColor()));
+    paint.setShader(SkShaders::Color(paint.getColor()));
     canvas->drawString(text, 10, 40, font, paint);
     paint.setShader(shader);
     canvas->drawString(text, 10, 60, font, paint);
diff --git a/gm/getpostextpath.cpp b/gm/getpostextpath.cpp
index cde2099..f61aad6 100644
--- a/gm/getpostextpath.cpp
+++ b/gm/getpostextpath.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkCanvas.h"
 #include "SkFontPriv.h"
@@ -30,7 +30,7 @@
     SkPath path;
 
     SkFont font;
-    font.setTypeface(sk_tool_utils::create_portable_typeface());
+    font.setTypeface(ToolUtils::create_portable_typeface());
     font.setSize(48);
 
     SkPaint paint;
@@ -39,7 +39,7 @@
     canvas->translate(SkIntToScalar(10), SkIntToScalar(64));
 
     canvas->drawSimpleText(text, len, kUTF8_SkTextEncoding, 0, 0, font, paint);
-    sk_tool_utils::get_text_path(font, text, len, kUTF8_SkTextEncoding, &path, nullptr);
+    ToolUtils::get_text_path(font, text, len, kUTF8_SkTextEncoding, &path, nullptr);
     strokePath(canvas, path);
     path.reset();
 
@@ -60,6 +60,6 @@
     canvas->translate(0, SkIntToScalar(64));
 
     canvas->drawTextBlob(SkTextBlob::MakeFromPosText(text, len, &pos[0], font), 0, 0, paint);
-    sk_tool_utils::get_text_path(font, text, len, kUTF8_SkTextEncoding, &path, &pos[0]);
+    ToolUtils::get_text_path(font, text, len, kUTF8_SkTextEncoding, &path, &pos[0]);
     strokePath(canvas, path);
 }
diff --git a/gm/giantbitmap.cpp b/gm/giantbitmap.cpp
index 37b76a6..17b6063 100644
--- a/gm/giantbitmap.cpp
+++ b/gm/giantbitmap.cpp
@@ -20,7 +20,7 @@
 
 class GiantBitmapGM : public skiagm::GM {
     SkBitmap* fBM;
-    SkShader::TileMode fMode;
+    SkTileMode fMode;
     bool fDoFilter;
     bool fDoRotate;
 
@@ -60,7 +60,7 @@
     }
 
 public:
-    GiantBitmapGM(SkShader::TileMode mode, bool doFilter, bool doRotate) : fBM(nullptr) {
+    GiantBitmapGM(SkTileMode mode, bool doFilter, bool doRotate) : fBM(nullptr) {
         fMode = mode;
         fDoFilter = doFilter;
         fDoRotate = doRotate;
@@ -73,15 +73,18 @@
     SkString onShortName() override {
         SkString str("giantbitmap_");
         switch (fMode) {
-            case SkShader::kClamp_TileMode:
+            case SkTileMode::kClamp:
                 str.append("clamp");
                 break;
-            case SkShader::kRepeat_TileMode:
+            case SkTileMode::kRepeat:
                 str.append("repeat");
                 break;
-            case SkShader::kMirror_TileMode:
+            case SkTileMode::kMirror:
                 str.append("mirror");
                 break;
+            case SkTileMode::kDecal:
+                str.append("decal");
+                break;
             default:
                 break;
         }
@@ -104,7 +107,7 @@
             SkScalar scale = 11*SK_Scalar1/12;
             m.setScale(scale, scale);
         }
-        paint.setShader(SkShader::MakeBitmapShader(getBitmap(), fMode, fMode, &m));
+        paint.setShader(getBitmap().makeShader(fMode, fMode, &m));
         paint.setFilterQuality(fDoFilter ? kLow_SkFilterQuality : kNone_SkFilterQuality);
 
         canvas->translate(SkIntToScalar(50), SkIntToScalar(50));
@@ -120,16 +123,16 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-DEF_GM( return new GiantBitmapGM(SkShader::kClamp_TileMode, false, false); )
-DEF_GM( return new GiantBitmapGM(SkShader::kRepeat_TileMode, false, false); )
-DEF_GM( return new GiantBitmapGM(SkShader::kMirror_TileMode, false, false); )
-DEF_GM( return new GiantBitmapGM(SkShader::kClamp_TileMode, true, false); )
-DEF_GM( return new GiantBitmapGM(SkShader::kRepeat_TileMode, true, false); )
-DEF_GM( return new GiantBitmapGM(SkShader::kMirror_TileMode, true, false); )
+DEF_GM( return new GiantBitmapGM(SkTileMode::kClamp, false, false); )
+DEF_GM( return new GiantBitmapGM(SkTileMode::kRepeat, false, false); )
+DEF_GM( return new GiantBitmapGM(SkTileMode::kMirror, false, false); )
+DEF_GM( return new GiantBitmapGM(SkTileMode::kClamp, true, false); )
+DEF_GM( return new GiantBitmapGM(SkTileMode::kRepeat, true, false); )
+DEF_GM( return new GiantBitmapGM(SkTileMode::kMirror, true, false); )
 
-DEF_GM( return new GiantBitmapGM(SkShader::kClamp_TileMode, false, true); )
-DEF_GM( return new GiantBitmapGM(SkShader::kRepeat_TileMode, false, true); )
-DEF_GM( return new GiantBitmapGM(SkShader::kMirror_TileMode, false, true); )
-DEF_GM( return new GiantBitmapGM(SkShader::kClamp_TileMode, true, true); )
-DEF_GM( return new GiantBitmapGM(SkShader::kRepeat_TileMode, true, true); )
-DEF_GM( return new GiantBitmapGM(SkShader::kMirror_TileMode, true, true); )
+DEF_GM( return new GiantBitmapGM(SkTileMode::kClamp, false, true); )
+DEF_GM( return new GiantBitmapGM(SkTileMode::kRepeat, false, true); )
+DEF_GM( return new GiantBitmapGM(SkTileMode::kMirror, false, true); )
+DEF_GM( return new GiantBitmapGM(SkTileMode::kClamp, true, true); )
+DEF_GM( return new GiantBitmapGM(SkTileMode::kRepeat, true, true); )
+DEF_GM( return new GiantBitmapGM(SkTileMode::kMirror, true, true); )
diff --git a/gm/glyph_pos.cpp b/gm/glyph_pos.cpp
index c5e43c8..54d5ff8 100644
--- a/gm/glyph_pos.cpp
+++ b/gm/glyph_pos.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkFont.h"
 #include "SkTypeface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 /* This test tries to define the effect of using hairline strokes on text.
  * Provides non-hairline images for reference and consistency checks.
@@ -87,7 +87,7 @@
     paint.setStrokeWidth(strokeWidth);
     paint.setStyle(strokeStyle);
 
-    SkFont font(sk_tool_utils::create_portable_typeface(), kTextHeight * textScale);
+    SkFont font(ToolUtils::create_portable_typeface(), kTextHeight * textScale);
 
     // This demonstrates that we can not measure the text if
     // there's a device transform. The canvas total matrix will
diff --git a/gm/gm.cpp b/gm/gm.cpp
index aef7184..1831aaa 100644
--- a/gm/gm.cpp
+++ b/gm/gm.cpp
@@ -8,93 +8,14 @@
 #include "gm.h"
 
 #include "GrContext.h"
-#include "sk_tool_utils.h"
 #include "SkShader.h"
 #include "SkTraceEvent.h"
+#include "ToolUtils.h"
 using namespace skiagm;
 
-GM::GM(SkColor bgColor) {
-    fMode = kGM_Mode;
-    fBGColor = bgColor;
-    fCanvasIsDeferred = false;
-    fHaveCalledOnceBeforeDraw = false;
-}
+constexpr char GM::kErrorMsg_DrawSkippedGpuOnly[];
 
-GM::~GM() {}
-
-void GM::draw(SkCanvas* canvas) {
-    TRACE_EVENT1("GM", TRACE_FUNC, "name", TRACE_STR_COPY(this->getName()));
-    this->drawBackground(canvas);
-    this->drawContent(canvas);
-}
-
-void GM::drawContent(SkCanvas* canvas) {
-    TRACE_EVENT0("GM", TRACE_FUNC);
-    if (!fHaveCalledOnceBeforeDraw) {
-        fHaveCalledOnceBeforeDraw = true;
-        this->onOnceBeforeDraw();
-    }
-    SkAutoCanvasRestore acr(canvas, true);
-    this->onDraw(canvas);
-}
-
-void GM::drawBackground(SkCanvas* canvas) {
-    TRACE_EVENT0("GM", TRACE_FUNC);
-    if (!fHaveCalledOnceBeforeDraw) {
-        fHaveCalledOnceBeforeDraw = true;
-        this->onOnceBeforeDraw();
-    }
-    SkAutoCanvasRestore acr(canvas, true);
-    canvas->drawColor(fBGColor, SkBlendMode::kSrc);
-}
-
-const char* GM::getName() {
-    if (fShortName.size() == 0) {
-        fShortName = this->onShortName();
-    }
-    return fShortName.c_str();
-}
-
-void GM::setBGColor(SkColor color) {
-    fBGColor = color;
-}
-
-bool GM::animate(const SkAnimTimer& timer) {
-    return this->onAnimate(timer);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////////
-
-void GM::drawSizeBounds(SkCanvas* canvas, SkColor color) {
-    SkISize size = this->getISize();
-    SkRect r = SkRect::MakeWH(SkIntToScalar(size.width()),
-                              SkIntToScalar(size.height()));
-    SkPaint paint;
-    paint.setColor(color);
-    canvas->drawRect(r, paint);
-}
-
-void GM::DrawGpuOnlyMessage(SkCanvas* canvas) {
-    SkBitmap bmp;
-    bmp.allocN32Pixels(128, 64);
-    SkCanvas bmpCanvas(bmp);
-    bmpCanvas.drawColor(SK_ColorWHITE);
-    SkFont font(sk_tool_utils::create_portable_typeface(), 20);
-    SkPaint paint;
-    paint.setColor(SK_ColorRED);
-    bmpCanvas.drawString("GPU Only", 20, 40, font, paint);
-    SkMatrix localM;
-    localM.setRotate(35.f);
-    localM.postTranslate(10.f, 0.f);
-    paint.setShader(SkShader::MakeBitmapShader(bmp, SkShader::kMirror_TileMode,
-                                               SkShader::kMirror_TileMode,
-                                               &localM));
-    paint.setFilterQuality(kMedium_SkFilterQuality);
-    canvas->drawPaint(paint);
-    return;
-}
-
-void GM::DrawFailureMessage(SkCanvas* canvas, const char format[], ...)  {
+static void draw_failure_message(SkCanvas* canvas, const char format[], ...)  {
     SkString failureMsg;
 
     va_list argp;
@@ -112,21 +33,146 @@
     canvas->drawString(failureMsg, kOffset, bounds.height() + kOffset, font, textPaint);
 }
 
+static void draw_gpu_only_message(SkCanvas* canvas) {
+    SkBitmap bmp;
+    bmp.allocN32Pixels(128, 64);
+    SkCanvas bmpCanvas(bmp);
+    bmpCanvas.drawColor(SK_ColorWHITE);
+    SkFont  font(ToolUtils::create_portable_typeface(), 20);
+    SkPaint paint;
+    paint.setColor(SK_ColorRED);
+    bmpCanvas.drawString("GPU Only", 20, 40, font, paint);
+    SkMatrix localM;
+    localM.setRotate(35.f);
+    localM.postTranslate(10.f, 0.f);
+    paint.setShader(bmp.makeShader(SkTileMode::kMirror, SkTileMode::kMirror, &localM));
+    paint.setFilterQuality(kMedium_SkFilterQuality);
+    canvas->drawPaint(paint);
+}
+
+GM::GM(SkColor bgColor) {
+    fMode = kGM_Mode;
+    fBGColor = bgColor;
+    fCanvasIsDeferred = false;
+    fHaveCalledOnceBeforeDraw = false;
+}
+
+GM::~GM() {}
+
+DrawResult GM::draw(SkCanvas* canvas, SkString* errorMsg) {
+    TRACE_EVENT1("GM", TRACE_FUNC, "name", TRACE_STR_COPY(this->getName()));
+    this->drawBackground(canvas);
+    return this->drawContent(canvas, errorMsg);
+}
+
+DrawResult GM::drawContent(SkCanvas* canvas, SkString* errorMsg) {
+    TRACE_EVENT0("GM", TRACE_FUNC);
+    if (!fHaveCalledOnceBeforeDraw) {
+        fHaveCalledOnceBeforeDraw = true;
+        this->onOnceBeforeDraw();
+    }
+    SkAutoCanvasRestore acr(canvas, true);
+    DrawResult drawResult = this->onDraw(canvas, errorMsg);
+    if (DrawResult::kOk != drawResult) {
+        if (DrawResult::kFail == drawResult) {
+            draw_failure_message(canvas, "DRAW FAILED: %s", errorMsg->c_str());
+        } else if (SkString(kErrorMsg_DrawSkippedGpuOnly) == *errorMsg) {
+            draw_gpu_only_message(canvas);
+        } else {
+            draw_failure_message(canvas, "DRAW SKIPPED: %s", errorMsg->c_str());
+        }
+    }
+    return drawResult;
+}
+
+void GM::drawBackground(SkCanvas* canvas) {
+    TRACE_EVENT0("GM", TRACE_FUNC);
+    if (!fHaveCalledOnceBeforeDraw) {
+        fHaveCalledOnceBeforeDraw = true;
+        this->onOnceBeforeDraw();
+    }
+    SkAutoCanvasRestore acr(canvas, true);
+    canvas->drawColor(fBGColor, SkBlendMode::kSrc);
+}
+
+DrawResult GM::onDraw(SkCanvas* canvas, SkString* errorMsg) {
+    this->onDraw(canvas);
+    return DrawResult::kOk;
+}
+void GM::onDraw(SkCanvas*) { SK_ABORT("Not implemented."); }
+
+
+SkISize SimpleGM::onISize() { return fSize; }
+SkString SimpleGM::onShortName() { return fName; }
+DrawResult SimpleGM::onDraw(SkCanvas* canvas, SkString* errorMsg) {
+    return fDrawProc(canvas, errorMsg);
+}
+
+SkISize SimpleGpuGM::onISize() { return fSize; }
+SkString SimpleGpuGM::onShortName() { return fName; }
+DrawResult SimpleGpuGM::onDraw(GrContext* ctx, GrRenderTargetContext* rtc, SkCanvas* canvas,
+                               SkString* errorMsg) {
+    return fDrawProc(ctx, rtc, canvas, errorMsg);
+}
+
+const char* GM::getName() {
+    if (fShortName.size() == 0) {
+        fShortName = this->onShortName();
+    }
+    return fShortName.c_str();
+}
+
+void GM::setBGColor(SkColor color) {
+    fBGColor = color;
+}
+
+bool GM::animate(const AnimTimer& timer) { return this->onAnimate(timer); }
+
+bool GM::runAsBench() const { return false; }
+void GM::modifyGrContextOptions(GrContextOptions* options) {}
+
+void GM::onOnceBeforeDraw() {}
+
+bool GM::onAnimate(const AnimTimer&) { return false; }
+bool GM::onHandleKey(SkUnichar uni) { return false; }
+bool GM::onGetControls(SkMetaData*) { return false; }
+void GM::onSetControls(const SkMetaData&) {}
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+
+void GM::drawSizeBounds(SkCanvas* canvas, SkColor color) {
+    SkISize size = this->getISize();
+    SkRect r = SkRect::MakeWH(SkIntToScalar(size.width()),
+                              SkIntToScalar(size.height()));
+    SkPaint paint;
+    paint.setColor(color);
+    canvas->drawRect(r, paint);
+}
+
 // need to explicitly declare this, or we get some weird infinite loop llist
 template GMRegistry* GMRegistry::gHead;
 
-void GpuGM::onDraw(SkCanvas* canvas) {
+DrawResult GpuGM::onDraw(GrContext* ctx, GrRenderTargetContext* rtc, SkCanvas* canvas,
+                          SkString* errorMsg) {
+    this->onDraw(ctx, rtc, canvas);
+    return DrawResult::kOk;
+}
+void GpuGM::onDraw(GrContext*, GrRenderTargetContext*, SkCanvas*) {
+    SK_ABORT("Not implemented.");
+}
+
+DrawResult GpuGM::onDraw(SkCanvas* canvas, SkString* errorMsg) {
     GrContext* ctx = canvas->getGrContext();
     GrRenderTargetContext* rtc = canvas->internal_private_accessTopLayerRenderTargetContext();
     if (!ctx || !rtc) {
-        skiagm::GM::DrawGpuOnlyMessage(canvas);
-        return;
+        *errorMsg = kErrorMsg_DrawSkippedGpuOnly;
+        return DrawResult::kSkip;
     }
     if (ctx->abandoned()) {
-        skiagm::GM::DrawFailureMessage(canvas, "GrContext abandoned.");
-        return;
+        *errorMsg = "GrContext abandoned.";
+        return DrawResult::kSkip;
     }
-    this->onDraw(ctx, rtc, canvas);
+    return this->onDraw(ctx, rtc, canvas, errorMsg);
 }
 
 template <typename Fn>
diff --git a/gm/gm.h b/gm/gm.h
index 9d3bc2f..e7dee35 100644
--- a/gm/gm.h
+++ b/gm/gm.h
@@ -17,7 +17,7 @@
 #include "SkSize.h"
 #include "SkString.h"
 
-class SkAnimTimer;
+class AnimTimer;
 class SkMetaData;
 struct GrContextOptions;
 
@@ -36,26 +36,62 @@
 #define DEF_SIMPLE_GM_BG(NAME, CANVAS, W, H, BGCOLOR) \
     DEF_SIMPLE_GM_BG_NAME(NAME, CANVAS, W, H, BGCOLOR, SkString(#NAME))
 #define DEF_SIMPLE_GM_BG_NAME(NAME, CANVAS, W, H, BGCOLOR, NAME_STR) \
-    static void SK_MACRO_CONCAT(NAME, _GM)(SkCanvas * CANVAS); \
-    DEF_GM(return new skiagm::SimpleGM(BGCOLOR, NAME_STR, SkISize::Make(W, H), \
-                                       SK_MACRO_CONCAT(NAME, _GM));) \
-    void SK_MACRO_CONCAT(NAME, _GM)(SkCanvas * CANVAS)
+    static void SK_MACRO_CONCAT(NAME,_GM_inner)(SkCanvas*); \
+    DEF_SIMPLE_GM_BG_NAME_CAN_FAIL(NAME, CANVAS,, W, H, BGCOLOR, NAME_STR) { \
+        SK_MACRO_CONCAT(NAME,_GM_inner)(CANVAS); \
+        return skiagm::DrawResult::kOk; \
+    } \
+    void SK_MACRO_CONCAT(NAME,_GM_inner)(SkCanvas* CANVAS)
+
+#define DEF_SIMPLE_GM_CAN_FAIL(NAME, CANVAS, ERR_MSG, W, H) \
+    DEF_SIMPLE_GM_BG_NAME_CAN_FAIL(NAME, CANVAS, ERR_MSG, W, H, SK_ColorWHITE, SkString(#NAME))
+#define DEF_SIMPLE_GM_BG_CAN_FAIL(NAME, CANVAS, ERR_MSG, W, H, BGCOLOR) \
+    DEF_SIMPLE_GM_BG_NAME_CAN_FAIL(NAME, CANVAS, ERR_MSG, W, H, BGCOLOR, SkString(#NAME))
+#define DEF_SIMPLE_GM_BG_NAME_CAN_FAIL(NAME, CANVAS, ERR_MSG, W, H, BGCOLOR, NAME_STR) \
+    static skiagm::DrawResult SK_MACRO_CONCAT(NAME,_GM)(SkCanvas*, SkString*); \
+    DEF_GM(return new skiagm::SimpleGM(BGCOLOR, NAME_STR, {W,H}, SK_MACRO_CONCAT(NAME,_GM));) \
+    skiagm::DrawResult SK_MACRO_CONCAT(NAME,_GM)(SkCanvas* CANVAS, SkString* ERR_MSG)
+
 
 // A Simple GpuGM makes direct GPU calls. Its onDraw hook that includes GPU objects as params, and
 // is only invoked on GPU configs. Non-GPU configs automatically draw a GPU-only message and abort.
 #define DEF_SIMPLE_GPU_GM(NAME, GR_CONTEXT, RENDER_TARGET_CONTEXT, CANVAS, W, H) \
     DEF_SIMPLE_GPU_GM_BG(NAME, GR_CONTEXT, RENDER_TARGET_CONTEXT, CANVAS, W, H, SK_ColorWHITE)
-#define DEF_SIMPLE_GPU_GM_BG(NAME, GR_CONTEXT, RENDER_TARGET_CONTEXT, CANVAS, W, H, BGCOLOR)\
-    static void SK_MACRO_CONCAT(NAME, _GM)(GrContext*, GrRenderTargetContext*, SkCanvas*); \
-    DEF_GM(return new skiagm::SimpleGpuGM(BGCOLOR, SkString(#NAME), SkISize::Make(W, H), \
-                                          SK_MACRO_CONCAT(NAME, _GM));) \
-    void SK_MACRO_CONCAT(NAME, _GM)( \
+#define DEF_SIMPLE_GPU_GM_BG(NAME, GR_CONTEXT, RENDER_TARGET_CONTEXT, CANVAS, W, H, BGCOLOR) \
+    static void SK_MACRO_CONCAT(NAME,_GM_inner)(GrContext*, GrRenderTargetContext*, SkCanvas*); \
+    DEF_SIMPLE_GPU_GM_BG_CAN_FAIL(NAME, GR_CONTEXT, RENDER_TARGET_CONTEXT, CANVAS,, W, H, \
+                                  BGCOLOR) { \
+        SK_MACRO_CONCAT(NAME,_GM_inner)(GR_CONTEXT, RENDER_TARGET_CONTEXT, CANVAS); \
+        return skiagm::DrawResult::kOk; \
+    } \
+    void SK_MACRO_CONCAT(NAME,_GM_inner)( \
             GrContext* GR_CONTEXT, GrRenderTargetContext* RENDER_TARGET_CONTEXT, SkCanvas* CANVAS)
 
+#define DEF_SIMPLE_GPU_GM_CAN_FAIL(NAME, GR_CONTEXT, RENDER_TARGET_CONTEXT, CANVAS, ERR_MSG, W, H) \
+    DEF_SIMPLE_GPU_GM_BG_CAN_FAIL(NAME, GR_CONTEXT, RENDER_TARGET_CONTEXT, CANVAS, \
+                                  ERR_MSG, W, H, SK_ColorWHITE)
+#define DEF_SIMPLE_GPU_GM_BG_CAN_FAIL(NAME, GR_CONTEXT, RENDER_TARGET_CONTEXT, CANVAS, ERR_MSG, W, \
+                                      H, BGCOLOR) \
+    static skiagm::DrawResult SK_MACRO_CONCAT(NAME,_GM)( \
+            GrContext*, GrRenderTargetContext*, SkCanvas*, SkString*); \
+    DEF_GM(return new skiagm::SimpleGpuGM(BGCOLOR, SkString(#NAME), {W,H}, \
+                                          SK_MACRO_CONCAT(NAME,_GM));) \
+    skiagm::DrawResult SK_MACRO_CONCAT(NAME,_GM)( \
+            GrContext* GR_CONTEXT, GrRenderTargetContext* RENDER_TARGET_CONTEXT, SkCanvas* CANVAS, \
+            SkString* ERR_MSG)
+
 namespace skiagm {
 
+    enum class DrawResult {
+        kOk,  // Test drew successfully.
+        kFail,  // Test failed to draw.
+        kSkip  // Test is not applicable in this context and should be skipped.
+    };
+
     class GM {
     public:
+        using DrawResult = skiagm::DrawResult;
+
         GM(SkColor backgroundColor = SK_ColorWHITE);
         virtual ~GM();
 
@@ -68,14 +104,25 @@
         void setMode(Mode mode) { fMode = mode; }
         Mode getMode() const { return fMode; }
 
-        void draw(SkCanvas*);
+        static constexpr char kErrorMsg_DrawSkippedGpuOnly[] = "This test is for GPU configs only.";
+
+        DrawResult draw(SkCanvas* canvas) {
+            SkString errorMsg;
+            return this->draw(canvas, &errorMsg);
+        }
+        DrawResult draw(SkCanvas*, SkString* errorMsg);
+
         void drawBackground(SkCanvas*);
-        void drawContent(SkCanvas*);
+        DrawResult drawContent(SkCanvas* canvas) {
+            SkString errorMsg;
+            return this->drawContent(canvas, &errorMsg);
+        }
+        DrawResult drawContent(SkCanvas*, SkString* errorMsg);
 
         SkISize getISize() { return this->onISize(); }
         const char* getName();
 
-        virtual bool runAsBench() const { return false; }
+        virtual bool runAsBench() const;
 
         SkScalar width() {
             return SkIntToScalar(this->getISize().width());
@@ -95,7 +142,7 @@
             fCanvasIsDeferred = isDeferred;
         }
 
-        bool animate(const SkAnimTimer&);
+        bool animate(const AnimTimer&);
         bool handleKey(SkUnichar uni) {
             return this->onHandleKey(uni);
         }
@@ -103,23 +150,20 @@
         bool getControls(SkMetaData* controls) { return this->onGetControls(controls); }
         void setControls(const SkMetaData& controls) { this->onSetControls(controls); }
 
-        virtual void modifyGrContextOptions(GrContextOptions* options) {}
-
-        /** draws a standard message that the GM is only intended to be used with the GPU.*/
-        static void DrawGpuOnlyMessage(SkCanvas*);
-
-        static void DrawFailureMessage(SkCanvas*, const char[], ...) SK_PRINTF_LIKE(2, 3);
+        virtual void modifyGrContextOptions(GrContextOptions* options);
 
     protected:
-        virtual void onOnceBeforeDraw() {}
-        virtual void onDraw(SkCanvas*) = 0;
+        virtual void onOnceBeforeDraw();
+        virtual DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg);
+        virtual void onDraw(SkCanvas*);
+
         virtual SkISize onISize() = 0;
         virtual SkString onShortName() = 0;
 
-        virtual bool onAnimate(const SkAnimTimer&) { return false; }
-        virtual bool onHandleKey(SkUnichar uni) { return false; }
-        virtual bool onGetControls(SkMetaData*) { return false; }
-        virtual void onSetControls(const SkMetaData&) {}
+        virtual bool onAnimate(const AnimTimer&);
+        virtual bool onHandleKey(SkUnichar uni);
+        virtual bool onGetControls(SkMetaData*);
+        virtual void onSetControls(const SkMetaData&);
 
     private:
         Mode     fMode;
@@ -139,22 +183,26 @@
     public:
         GpuGM(SkColor backgroundColor = SK_ColorWHITE) : GM(backgroundColor) {}
     private:
-        void onDraw(SkCanvas*) final;
-        virtual void onDraw(GrContext*, GrRenderTargetContext*, SkCanvas*) = 0;
+        using GM::onDraw;
+        DrawResult onDraw(SkCanvas*, SkString* errorMsg) final;
+
+        virtual DrawResult onDraw(GrContext* ctx, GrRenderTargetContext* rtc, SkCanvas* canvas,
+                                  SkString* errorMsg);
+        virtual void onDraw(GrContext*, GrRenderTargetContext*, SkCanvas*);
     };
 
     // SimpleGM is intended for basic GMs that can define their entire implementation inside a
     // single "draw" function pointer.
     class SimpleGM : public GM {
     public:
-        using DrawProc = void(*)(SkCanvas*);
+        using DrawProc = DrawResult(*)(SkCanvas*, SkString*);
         SimpleGM(SkColor bgColor, const SkString& name, const SkISize& size, DrawProc drawProc)
                 : GM(bgColor), fName(name), fSize(size), fDrawProc(drawProc) {}
 
     private:
-        SkISize onISize() override { return fSize; }
-        SkString onShortName() override { return fName; }
-        void onDraw(SkCanvas* canvas) override { fDrawProc(canvas); }
+        SkISize onISize() override;
+        SkString onShortName() override;
+        DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override;
 
         const SkString fName;
         const SkISize fSize;
@@ -163,16 +211,15 @@
 
     class SimpleGpuGM : public GpuGM {
     public:
-        using DrawProc = void(*)(GrContext*, GrRenderTargetContext*, SkCanvas*);
+        using DrawProc = DrawResult(*)(GrContext*, GrRenderTargetContext*, SkCanvas*, SkString*);
         SimpleGpuGM(SkColor bgColor, const SkString& name, const SkISize& size, DrawProc drawProc)
                 : GpuGM(bgColor), fName(name), fSize(size), fDrawProc(drawProc) {}
 
     private:
-        SkISize onISize() override { return fSize; }
-        SkString onShortName() override { return fName; }
-        void onDraw(GrContext* ctx, GrRenderTargetContext* rtc, SkCanvas* canvas) override {
-            fDrawProc(ctx, rtc, canvas);
-        }
+        SkISize onISize() override;
+        SkString onShortName() override;
+        DrawResult onDraw(GrContext* ctx, GrRenderTargetContext* rtc, SkCanvas* canvas,
+                          SkString* errorMsg) override;
 
         const SkString fName;
         const SkISize fSize;
diff --git a/gm/gradientDirtyLaundry.cpp b/gm/gradientDirtyLaundry.cpp
index b7f78d3..f2e17fb 100644
--- a/gm/gradientDirtyLaundry.cpp
+++ b/gm/gradientDirtyLaundry.cpp
@@ -34,22 +34,22 @@
     //  { 2, gCol2, nullptr },
 };
 
-static sk_sp<SkShader> MakeLinear(const SkPoint pts[2], const GradData& data, SkShader::TileMode tm) {
+static sk_sp<SkShader> MakeLinear(const SkPoint pts[2], const GradData& data, SkTileMode tm) {
     return SkGradientShader::MakeLinear(pts, data.fColors, data.fPos, data.fCount, tm);
 }
 
-static sk_sp<SkShader> MakeRadial(const SkPoint pts[2], const GradData& data, SkShader::TileMode tm) {
+static sk_sp<SkShader> MakeRadial(const SkPoint pts[2], const GradData& data, SkTileMode tm) {
     const SkPoint pt{ SkScalarAve(pts[0].fX, pts[1].fX), SkScalarAve(pts[0].fY, pts[1].fY) };
     return SkGradientShader::MakeRadial(pt, pt.fX, data.fColors, data.fPos, data.fCount, tm);
 }
 
-static sk_sp<SkShader> MakeSweep(const SkPoint pts[2], const GradData& data, SkShader::TileMode) {
+static sk_sp<SkShader> MakeSweep(const SkPoint pts[2], const GradData& data, SkTileMode) {
     const SkPoint pt{ SkScalarAve(pts[0].fX, pts[1].fX), SkScalarAve(pts[0].fY, pts[1].fY) };
     return SkGradientShader::MakeSweep(pt.fX, pt.fY, data.fColors, data.fPos, data.fCount);
 }
 
 
-typedef sk_sp<SkShader> (*GradMaker)(const SkPoint pts[2], const GradData&, SkShader::TileMode);
+typedef sk_sp<SkShader> (*GradMaker)(const SkPoint pts[2], const GradData&, SkTileMode);
 
 constexpr GradMaker gGradMakers[] = {
     MakeLinear, MakeRadial, MakeSweep,
@@ -71,7 +71,7 @@
         SkPoint pts[2] = { { 0, 0 },
                            { SkIntToScalar(100), SkIntToScalar(100) }
         };
-        SkShader::TileMode tm = SkShader::kClamp_TileMode;
+        SkTileMode tm = SkTileMode::kClamp;
         SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
         SkPaint paint;
         paint.setAntiAlias(true);
diff --git a/gm/gradient_matrix.cpp b/gm/gradient_matrix.cpp
index 22cd316..6f33502 100644
--- a/gm/gradient_matrix.cpp
+++ b/gm/gradient_matrix.cpp
@@ -63,7 +63,7 @@
 
 static sk_sp<SkShader> make_linear_gradient(const SkPoint pts[2], const SkMatrix& localMatrix) {
     return SkGradientShader::MakeLinear(pts, gColors, nullptr, SK_ARRAY_COUNT(gColors),
-                                        SkShader::kClamp_TileMode, 0, &localMatrix);
+                                        SkTileMode::kClamp, 0, &localMatrix);
 }
 
 static sk_sp<SkShader> make_radial_gradient(const SkPoint pts[2], const SkMatrix& localMatrix) {
@@ -72,7 +72,7 @@
                SkScalarAve(pts[0].fY, pts[1].fY));
     float radius = (center - pts[0]).length();
     return SkGradientShader::MakeRadial(center, radius, gColors, nullptr, SK_ARRAY_COUNT(gColors),
-                                        SkShader::kClamp_TileMode, 0, &localMatrix);
+                                        SkTileMode::kClamp, 0, &localMatrix);
 }
 
 static void draw_gradients(SkCanvas* canvas,
diff --git a/gm/gradients.cpp b/gm/gradients.cpp
index 1a3a9fd..3be8e77 100644
--- a/gm/gradients.cpp
+++ b/gm/gradients.cpp
@@ -53,20 +53,20 @@
 };
 
 static sk_sp<SkShader> MakeLinear(const SkPoint pts[2], const GradData& data,
-                                  SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                                  SkTileMode tm, const SkMatrix& localMatrix) {
     return SkGradientShader::MakeLinear(pts, data.fColors, data.fPos, data.fCount, tm, 0,
                                         &localMatrix);
 }
 
 static sk_sp<SkShader> MakeLinear4f(const SkPoint pts[2], const GradData& data,
-                                    SkShader::TileMode tm, const SkMatrix& localMatrix) {
-    auto srgb = SkColorSpace::MakeSRGBLinear();
+                                    SkTileMode tm, const SkMatrix& localMatrix) {
+    auto srgb = SkColorSpace::MakeSRGB();
     return SkGradientShader::MakeLinear(pts, data.fColors4f, srgb, data.fPos, data.fCount, tm, 0,
                                         &localMatrix);
 }
 
 static sk_sp<SkShader> MakeRadial(const SkPoint pts[2], const GradData& data,
-                                  SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                                  SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center;
     center.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
@@ -75,17 +75,17 @@
 }
 
 static sk_sp<SkShader> MakeRadial4f(const SkPoint pts[2], const GradData& data,
-                                    SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                                    SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center;
     center.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
-    auto srgb = SkColorSpace::MakeSRGBLinear();
+    auto srgb = SkColorSpace::MakeSRGB();
     return SkGradientShader::MakeRadial(center, center.fX, data.fColors4f, srgb, data.fPos,
                                         data.fCount, tm, 0, &localMatrix);
 }
 
 static sk_sp<SkShader> MakeSweep(const SkPoint pts[2], const GradData& data,
-                                 SkShader::TileMode, const SkMatrix& localMatrix) {
+                                 SkTileMode, const SkMatrix& localMatrix) {
     SkPoint center;
     center.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
@@ -94,17 +94,17 @@
 }
 
 static sk_sp<SkShader> MakeSweep4f(const SkPoint pts[2], const GradData& data,
-                                   SkShader::TileMode, const SkMatrix& localMatrix) {
+                                   SkTileMode, const SkMatrix& localMatrix) {
     SkPoint center;
     center.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
-    auto srgb = SkColorSpace::MakeSRGBLinear();
+    auto srgb = SkColorSpace::MakeSRGB();
     return SkGradientShader::MakeSweep(center.fX, center.fY, data.fColors4f, srgb, data.fPos,
                                        data.fCount, 0, &localMatrix);
 }
 
 static sk_sp<SkShader> Make2Radial(const SkPoint pts[2], const GradData& data,
-                                   SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                                   SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                 SkScalarAve(pts[0].fY, pts[1].fY));
@@ -117,13 +117,13 @@
 }
 
 static sk_sp<SkShader> Make2Radial4f(const SkPoint pts[2], const GradData& data,
-                                     SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                                     SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                 SkScalarAve(pts[0].fY, pts[1].fY));
     center1.set(SkScalarInterp(pts[0].fX, pts[1].fX, SkIntToScalar(3) / 5),
                 SkScalarInterp(pts[0].fY, pts[1].fY, SkIntToScalar(1) / 4));
-    auto srgb = SkColorSpace::MakeSRGBLinear();
+    auto srgb = SkColorSpace::MakeSRGB();
     return SkGradientShader::MakeTwoPointConical(center1, (pts[1].fX - pts[0].fX) / 7,
                                                  center0, (pts[1].fX - pts[0].fX) / 2,
                                                  data.fColors4f, srgb, data.fPos, data.fCount, tm,
@@ -131,7 +131,7 @@
 }
 
 static sk_sp<SkShader> Make2Conical(const SkPoint pts[2], const GradData& data,
-                                    SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                                    SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     SkScalar radius0 = (pts[1].fX - pts[0].fX) / 10;
     SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
@@ -143,20 +143,20 @@
 }
 
 static sk_sp<SkShader> Make2Conical4f(const SkPoint pts[2], const GradData& data,
-                                      SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                                      SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     SkScalar radius0 = (pts[1].fX - pts[0].fX) / 10;
     SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
     center0.set(pts[0].fX + radius0, pts[0].fY + radius0);
     center1.set(pts[1].fX - radius1, pts[1].fY - radius1);
-    auto srgb = SkColorSpace::MakeSRGBLinear();
+    auto srgb = SkColorSpace::MakeSRGB();
     return SkGradientShader::MakeTwoPointConical(center1, radius1, center0, radius0,
                                                  data.fColors4f, srgb, data.fPos,
                                                  data.fCount, tm, 0, &localMatrix);
 }
 
 typedef sk_sp<SkShader> (*GradMaker)(const SkPoint pts[2], const GradData& data,
-                                     SkShader::TileMode tm, const SkMatrix& localMatrix);
+                                     SkTileMode tm, const SkMatrix& localMatrix);
 constexpr GradMaker gGradMakers[] = {
     MakeLinear, MakeRadial, MakeSweep, Make2Radial, Make2Conical
 };
@@ -186,7 +186,7 @@
             { 0, 0 },
             { SkIntToScalar(100), SkIntToScalar(100) }
         };
-        SkShader::TileMode tm = SkShader::kClamp_TileMode;
+        SkTileMode tm = SkTileMode::kClamp;
         SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
         SkPaint paint;
         paint.setAntiAlias(true);
@@ -242,7 +242,7 @@
             { 0, 0 },
             { SkIntToScalar(100), SkIntToScalar(100) }
         };
-        SkShader::TileMode tm = SkShader::kClamp_TileMode;
+        SkTileMode tm = SkTileMode::kClamp;
         SkRect r ={ 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
         SkPaint paint;
         paint.setAntiAlias(true);
@@ -300,7 +300,7 @@
             { 0, 0 },
             { SkIntToScalar(100), SkIntToScalar(100) }
         };
-        SkShader::TileMode tm = SkShader::kClamp_TileMode;
+        SkTileMode tm = SkTileMode::kClamp;
         SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
         SkPaint paint;
         paint.setAntiAlias(true);
@@ -407,7 +407,7 @@
         SkPaint paint;
         paint.setShader(SkGradientShader::MakeTwoPointConical(c0, r0, c1, r1, colors,
                                                               pos, SK_ARRAY_COUNT(pos),
-                                                              SkShader::kClamp_TileMode));
+                                                              SkTileMode::kClamp));
         paint.setDither(fDither);
         canvas->drawPaint(paint);
     }
@@ -451,7 +451,7 @@
     canvas->drawRect(SkRect::MakeWH(100, 150), paint);
     paint.setShader(SkGradientShader::MakeTwoPointConical(c0, r0, c1, r1, colors, pos,
                                                           SK_ARRAY_COUNT(pos),
-                                                          SkShader::kClamp_TileMode));
+                                                          SkTileMode::kClamp));
     canvas->drawRect(SkRect::MakeWH(100, 150), paint);
 }
 
@@ -487,7 +487,7 @@
         paint.setShader(SkGradientShader::MakeRadial(
             SkPoint(center),
             SkIntToScalar(200), gColors, nullptr, 5,
-            SkShader::kClamp_TileMode));
+            SkTileMode::kClamp));
         canvas->drawRect(r, paint);
     }
 
@@ -529,7 +529,7 @@
                              1.0f };
         paint.setShader(SkGradientShader::MakeRadial(center, radius, colors, pos,
                                                      SK_ARRAY_COUNT(pos),
-                                                     SkShader::kClamp_TileMode));
+                                                     SkTileMode::kClamp));
         SkRect r = {
             0, 0, SkIntToScalar(dim.width()), SkIntToScalar(dim.height())
         };
@@ -581,11 +581,11 @@
                                                          flags[i], nullptr));
             paint2.setShader(SkGradientShader::MakeRadial(center, radius, colors1,
                                                           nullptr, SK_ARRAY_COUNT(colors1),
-                                                          SkShader::kClamp_TileMode,
+                                                          SkTileMode::kClamp,
                                                           flags[i], nullptr));
             paint3.setShader(SkGradientShader::MakeRadial(center, radius, colors2,
                                                           nullptr, SK_ARRAY_COUNT(colors2),
-                                                          SkShader::kClamp_TileMode,
+                                                          SkTileMode::kClamp,
                                                           flags[i], nullptr));
             paint1.setDither(fDither);
             paint2.setDither(fDither);
@@ -626,7 +626,7 @@
         const SkScalar kRadius = 3000;
         const SkColor gColors[] = { 0xFFFFFFFF, 0xFF000000 };
         fShader = SkGradientShader::MakeRadial(center, kRadius, gColors, nullptr, 2,
-                                               SkShader::kClamp_TileMode);
+                                               SkTileMode::kClamp);
     }
 
     void onDraw(SkCanvas* canvas) override {
@@ -663,7 +663,7 @@
                 SK_ColorRED };
         const SkScalar pos[] = { 0, .4f, .4f, .8f, .8f, 1 };
         fShader = SkGradientShader::MakeRadial(center, kRadius, colors, pos,
-                                               SK_ARRAY_COUNT(gColors), SkShader::kClamp_TileMode);
+                                               SK_ARRAY_COUNT(gColors), SkTileMode::kClamp);
     }
 
     void onDraw(SkCanvas* canvas) override {
@@ -711,7 +711,7 @@
                 pos[inner] = unitPos[inner] / (kMinWidth + index * kWidthBump);
             }
             fShader[index] = SkGradientShader::MakeLinear(pts, colors, pos,
-                    SK_ARRAY_COUNT(gColors), SkShader::kClamp_TileMode);
+                    SK_ARRAY_COUNT(gColors), SkTileMode::kClamp);
         }
     }
 
@@ -780,7 +780,7 @@
         for (unsigned i = 0; i < SK_ARRAY_COUNT(configs); ++i) {
             SkAutoCanvasRestore acr(canvas, true);
             paint.setShader(SkGradientShader::MakeLinear(configs[i].pts, colors, configs[i].pos,
-                                                         kStopCount, SkShader::kClamp_TileMode,
+                                                         kStopCount, SkTileMode::kClamp,
                                                          fFlags, nullptr));
             canvas->translate(kRectSize * ((i % 4) * 1.5f + 0.25f),
                               kRectSize * ((i / 4) * 1.5f + 0.25f));
@@ -808,25 +808,25 @@
 
 #define SIZE 121
 
-static sk_sp<SkShader> make_linear(const GradRun& run, SkShader::TileMode mode) {
+static sk_sp<SkShader> make_linear(const GradRun& run, SkTileMode mode) {
     const SkPoint pts[] { { 30, 30 }, { SIZE - 30, SIZE - 30 } };
     return SkGradientShader::MakeLinear(pts, run.fColors, run.fPos, run.fCount, mode);
 }
 
-static sk_sp<SkShader> make_radial(const GradRun& run, SkShader::TileMode mode) {
+static sk_sp<SkShader> make_radial(const GradRun& run, SkTileMode mode) {
     const SkScalar half = SIZE * 0.5f;
     return SkGradientShader::MakeRadial({half,half}, half - 10, run.fColors, run.fPos,
                                         run.fCount, mode);
 }
 
-static sk_sp<SkShader> make_conical(const GradRun& run, SkShader::TileMode mode) {
+static sk_sp<SkShader> make_conical(const GradRun& run, SkTileMode mode) {
     const SkScalar half = SIZE * 0.5f;
     const SkPoint center { half, half };
     return SkGradientShader::MakeTwoPointConical(center, 20, center, half - 10,
                                                  run.fColors, run.fPos, run.fCount, mode);
 }
 
-static sk_sp<SkShader> make_sweep(const GradRun& run, SkShader::TileMode) {
+static sk_sp<SkShader> make_sweep(const GradRun& run, SkTileMode) {
     const SkScalar half = SIZE * 0.5f;
     return SkGradientShader::MakeSweep(half, half, run.fColors, run.fPos, run.fCount);
 }
@@ -866,14 +866,14 @@
             4,
         },
     };
-    sk_sp<SkShader> (*factories[])(const GradRun&, SkShader::TileMode) {
+    sk_sp<SkShader> (*factories[])(const GradRun&, SkTileMode) {
         make_linear, make_radial, make_conical, make_sweep
     };
 
     const SkRect rect = SkRect::MakeWH(SIZE, SIZE);
     const SkScalar dx = SIZE + 20;
     const SkScalar dy = SIZE + 20;
-    const SkShader::TileMode mode = SkShader::kClamp_TileMode;
+    const SkTileMode mode = SkTileMode::kClamp;
 
     SkPaint paint;
     canvas->translate(10, 10 - dy);
@@ -905,7 +905,7 @@
 
     SkPaint p;
     p.setShader(SkGradientShader::MakeLinear(
-        pts, colors, nullptr, SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode));
+        pts, colors, nullptr, SK_ARRAY_COUNT(colors), SkTileMode::kClamp));
 
     canvas->drawRect(SkRect::MakeXYWH(0, 0, 500, 500), p);
 }
@@ -951,19 +951,19 @@
 
         SkPoint pts1[] = { { 0, 0 }, { kTileSize, kTileSize }};
         p.setShader(SkGradientShader::MakeLinear(pts1, colors1, pos, SK_ARRAY_COUNT(colors1),
-                                                 SkShader::kClamp_TileMode, 0, nullptr));
+                                                 SkTileMode::kClamp, 0, nullptr));
         recorder.getRecordingCanvas()->drawPaint(p);
 
         SkPoint pts2[] = { { 0, kTileSize }, { kTileSize, 0 }};
         p.setShader(SkGradientShader::MakeLinear(pts2, colors2, pos, SK_ARRAY_COUNT(colors2),
-                                                 SkShader::kClamp_TileMode, 0, nullptr));
+                                                 SkTileMode::kClamp, 0, nullptr));
         recorder.getRecordingCanvas()->drawPaint(p);
 
         SkMatrix m = SkMatrix::I();
         m.preRotate(45);
-        return SkShader::MakePictureShader(recorder.finishRecordingAsPicture(),
-                                           SkShader::kRepeat_TileMode,
-                                           SkShader::kRepeat_TileMode, &m, nullptr);
+        return recorder.finishRecordingAsPicture()->makeShader(
+                                           SkTileMode::kRepeat,
+                                           SkTileMode::kRepeat, &m, nullptr);
     });
 
     draw_circle_shader(canvas, 400, 150, 100, []() -> sk_sp<SkShader> {
@@ -982,9 +982,9 @@
         SkPictureRecorder recorder;
         recorder.beginRecording(SkRect::MakeWH(kTileSize, kTileSize))->drawPaint(p);
 
-        return SkShader::MakePictureShader(recorder.finishRecordingAsPicture(),
-                                           SkShader::kRepeat_TileMode,
-                                           SkShader::kRepeat_TileMode, nullptr, nullptr);
+        return recorder.finishRecordingAsPicture()->makeShader(
+                                           SkTileMode::kRepeat,
+                                           SkTileMode::kRepeat);
     });
 
     draw_circle_shader(canvas, 650, 150, 100, []() -> sk_sp<SkShader> {
@@ -1004,19 +1004,18 @@
         sk_sp<SkShader> sweep2 = SkGradientShader::MakeSweep(center.x(), center.y(), colors, pos,
                                                              SK_ARRAY_COUNT(colors), 0, &m);
 
-        sk_sp<SkShader> sweep(SkShader::MakeComposeShader(sweep1, sweep2, SkBlendMode::kExclusion));
+        sk_sp<SkShader> sweep(SkShaders::Blend(SkBlendMode::kExclusion, sweep1, sweep2));
 
         SkScalar radialPos[] = { 0, .02f, .02f, .04f, .04f, .08f, .08f, .16f, .16f, .31f, .31f,
                                  .62f, .62f, 1, 1, 1 };
         static_assert(SK_ARRAY_COUNT(colors) == SK_ARRAY_COUNT(radialPos),
                       "color/pos size mismatch");
 
-        return SkShader::MakeComposeShader(sweep,
-                                           SkGradientShader::MakeRadial(center, 100, colors,
-                                                                        radialPos,
-                                                                        SK_ARRAY_COUNT(radialPos),
-                                                                        SkShader::kClamp_TileMode),
-                                           SkBlendMode::kExclusion);
+        return SkShaders::Blend(SkBlendMode::kExclusion, sweep,
+                                SkGradientShader::MakeRadial(center, 100, colors,
+                                                             radialPos,
+                                                             SK_ARRAY_COUNT(radialPos),
+                                                             SkTileMode::kClamp));
     });
 }
 
@@ -1026,9 +1025,9 @@
     static constexpr SkScalar   pos[] = { 0, .25f, .50f };
     static_assert(SK_ARRAY_COUNT(colors) == SK_ARRAY_COUNT(pos), "size mismatch");
 
-    static constexpr SkShader::TileMode modes[] = { SkShader::kClamp_TileMode,
-                                                    SkShader::kRepeat_TileMode,
-                                                    SkShader::kMirror_TileMode };
+    static constexpr SkTileMode modes[] = { SkTileMode::kClamp,
+                                            SkTileMode::kRepeat,
+                                            SkTileMode::kMirror };
 
     static const struct {
         SkScalar start, end;
@@ -1083,10 +1082,10 @@
         { colors4, hardCenter, 4 }, // kSingleHardStop_ColorType
     };
 
-    static const SkShader::TileMode modes[] = {
-        SkShader::kClamp_TileMode,
-        SkShader::kRepeat_TileMode,
-        SkShader::kMirror_TileMode,
+    static const SkTileMode modes[] = {
+        SkTileMode::kClamp,
+        SkTileMode::kRepeat,
+        SkTileMode::kMirror,
     };
 
     static constexpr SkScalar size = 200;
diff --git a/gm/gradients_2pt_conical.cpp b/gm/gradients_2pt_conical.cpp
index 81549e3..27a1eef 100644
--- a/gm/gradients_2pt_conical.cpp
+++ b/gm/gradients_2pt_conical.cpp
@@ -38,7 +38,7 @@
 };
 
 static sk_sp<SkShader> Make2ConicalOutside(const SkPoint pts[2], const GradData& data,
-                                           SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                                           SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     SkScalar radius0 = (pts[1].fX - pts[0].fX) / 10;
     SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
@@ -49,7 +49,7 @@
 }
 
 static sk_sp<SkShader> Make2ConicalOutsideStrip(const SkPoint pts[2], const GradData& data,
-                                                SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                                                SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     SkScalar radius = (pts[1].fX - pts[0].fX) / 3;
     center0.set(pts[0].fX, pts[0].fY);
@@ -59,7 +59,7 @@
 }
 
 static sk_sp<SkShader> Make2ConicalOutsideFlip(const SkPoint pts[2], const GradData& data,
-                             SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                             SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     SkScalar radius0 = (pts[1].fX - pts[0].fX) / 10;
     SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
@@ -70,7 +70,7 @@
 }
 
 static sk_sp<SkShader> Make2ConicalInside(const SkPoint pts[2], const GradData& data,
-                                          SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                                          SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                 SkScalarAve(pts[0].fY, pts[1].fY));
@@ -83,7 +83,7 @@
 }
 
 static sk_sp<SkShader> Make2ConicalInsideFlip(const SkPoint pts[2], const GradData& data,
-                                              SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                                              SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                 SkScalarAve(pts[0].fY, pts[1].fY));
@@ -96,7 +96,7 @@
 }
 
 static sk_sp<SkShader> Make2ConicalInsideCenter(const SkPoint pts[2], const GradData& data,
-                             SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                             SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0;
     center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                 SkScalarAve(pts[0].fY, pts[1].fY));
@@ -107,7 +107,7 @@
 }
 
 static sk_sp<SkShader> Make2ConicalInsideCenterReversed(const SkPoint pts[2], const GradData& data,
-                             SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                             SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0;
     center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                 SkScalarAve(pts[0].fY, pts[1].fY));
@@ -118,7 +118,7 @@
 }
 
 static sk_sp<SkShader> Make2ConicalZeroRad(const SkPoint pts[2], const GradData& data,
-                                           SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                                           SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                 SkScalarAve(pts[0].fY, pts[1].fY));
@@ -131,7 +131,7 @@
 }
 
 static sk_sp<SkShader> Make2ConicalZeroRadFlip(const SkPoint pts[2], const GradData& data,
-                                               SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                                               SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                 SkScalarAve(pts[0].fY, pts[1].fY));
@@ -144,7 +144,7 @@
 }
 
 static sk_sp<SkShader> Make2ConicalZeroRadCenter(const SkPoint pts[2], const GradData& data,
-                             SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                             SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                 SkScalarAve(pts[0].fY, pts[1].fY));
@@ -156,7 +156,7 @@
 }
 
 static sk_sp<SkShader> Make2ConicalZeroRadOutside(const SkPoint pts[2], const GradData& data,
-                                                  SkShader::TileMode tm,
+                                                  SkTileMode tm,
                                                   const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     SkScalar radius0 = 0.f;
@@ -169,7 +169,7 @@
 }
 
 static sk_sp<SkShader> Make2ConicalZeroRadFlipOutside(const SkPoint pts[2], const GradData& data,
-                                                      SkShader::TileMode tm,
+                                                      SkTileMode tm,
                                                       const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     SkScalar radius0 = 0.f;
@@ -181,7 +181,7 @@
 }
 
 static sk_sp<SkShader> Make2ConicalEdgeX(const SkPoint pts[2], const GradData& data,
-                                         SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                                         SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     SkScalar radius0 = (pts[1].fX - pts[0].fX) / 7;
     SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
@@ -193,7 +193,7 @@
 }
 
 static sk_sp<SkShader> Make2ConicalEdgeY(const SkPoint pts[2], const GradData& data,
-                                         SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                                         SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     SkScalar radius0 = (pts[1].fX - pts[0].fX) / 7;
     SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
@@ -205,7 +205,7 @@
 }
 
 static sk_sp<SkShader> Make2ConicalZeroRadEdgeX(const SkPoint pts[2], const GradData& data,
-                                                SkShader::TileMode tm,
+                                                SkTileMode tm,
                                                 const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     SkScalar radius0 = 0.f;
@@ -218,7 +218,7 @@
 }
 
 static sk_sp<SkShader> Make2ConicalZeroRadEdgeY(const SkPoint pts[2], const GradData& data,
-                                                SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                                                SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     SkScalar radius0 = 0.f;
     SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
@@ -230,7 +230,7 @@
 }
 
 static sk_sp<SkShader> Make2ConicalTouchX(const SkPoint pts[2], const GradData& data,
-                                          SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                                          SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     SkScalar radius0 = (pts[1].fX - pts[0].fX) / 7;
     SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
@@ -242,7 +242,7 @@
 }
 
 static sk_sp<SkShader> Make2ConicalTouchY(const SkPoint pts[2], const GradData& data,
-                                          SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                                          SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     SkScalar radius0 = (pts[1].fX - pts[0].fX) / 7;
     SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
@@ -254,7 +254,7 @@
 }
 
 static sk_sp<SkShader> Make2ConicalInsideSmallRad(const SkPoint pts[2], const GradData& data,
-                             SkShader::TileMode tm, const SkMatrix& localMatrix) {
+                             SkTileMode tm, const SkMatrix& localMatrix) {
     SkPoint center0, center1;
     center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                 SkScalarAve(pts[0].fY, pts[1].fY));
@@ -267,7 +267,7 @@
 }
 
 typedef sk_sp<SkShader> (*GradMaker)(const SkPoint pts[2], const GradData& data,
-                                     SkShader::TileMode tm, const SkMatrix& localMatrix);
+                                     SkTileMode tm, const SkMatrix& localMatrix);
 
 constexpr GradMaker gGradMakersOutside[] = {
     Make2ConicalOutside, Make2ConicalOutsideFlip,
@@ -310,7 +310,7 @@
 class ConicalGradientsGM : public GM {
 public:
     ConicalGradientsGM(GradCaseType gradCaseType, bool dither,
-                       SkShader::TileMode mode = SkShader::kClamp_TileMode)
+                       SkTileMode mode = SkTileMode::kClamp)
         : fGradCaseType(gradCaseType)
         , fDither(dither)
         , fMode(mode) {
@@ -318,10 +318,10 @@
         fName.printf("gradients_2pt_conical_%s%s", gGradCases[gradCaseType].fName,
                      fDither ? "" : "_nodither");
         switch (mode) {
-        case SkShader::kRepeat_TileMode:
+        case SkTileMode::kRepeat:
             fName.appendf("_repeat");
             break;
-        case SkShader::kMirror_TileMode:
+        case SkTileMode::kMirror:
             fName.appendf("_mirror");
             break;
         default:
@@ -377,7 +377,7 @@
     GradCaseType fGradCaseType;
     SkString fName;
     bool fDither;
-    SkShader::TileMode fMode;
+    SkTileMode fMode;
 };
 ///////////////////////////////////////////////////////////////////////////////
 
@@ -385,13 +385,13 @@
 DEF_GM( return new ConicalGradientsGM(kOutside_GradCaseType, true); )
 DEF_GM( return new ConicalGradientsGM(kEdge_GradCaseType, true); )
 
-DEF_GM( return new ConicalGradientsGM(kInside_GradCaseType, true, SkShader::kRepeat_TileMode); )
-DEF_GM( return new ConicalGradientsGM(kOutside_GradCaseType, true, SkShader::kRepeat_TileMode); )
-DEF_GM( return new ConicalGradientsGM(kEdge_GradCaseType, true, SkShader::kRepeat_TileMode); )
+DEF_GM( return new ConicalGradientsGM(kInside_GradCaseType, true, SkTileMode::kRepeat); )
+DEF_GM( return new ConicalGradientsGM(kOutside_GradCaseType, true, SkTileMode::kRepeat); )
+DEF_GM( return new ConicalGradientsGM(kEdge_GradCaseType, true, SkTileMode::kRepeat); )
 
-DEF_GM( return new ConicalGradientsGM(kInside_GradCaseType, true, SkShader::kMirror_TileMode); )
-DEF_GM( return new ConicalGradientsGM(kOutside_GradCaseType, true, SkShader::kMirror_TileMode); )
-DEF_GM( return new ConicalGradientsGM(kEdge_GradCaseType, true, SkShader::kMirror_TileMode); )
+DEF_GM( return new ConicalGradientsGM(kInside_GradCaseType, true, SkTileMode::kMirror); )
+DEF_GM( return new ConicalGradientsGM(kOutside_GradCaseType, true, SkTileMode::kMirror); )
+DEF_GM( return new ConicalGradientsGM(kEdge_GradCaseType, true, SkTileMode::kMirror); )
 
 DEF_GM( return new ConicalGradientsGM(kInside_GradCaseType, false); )
 DEF_GM( return new ConicalGradientsGM(kOutside_GradCaseType, false); )
diff --git a/gm/gradients_degenerate.cpp b/gm/gradients_degenerate.cpp
index e6a3f87..97ccc49 100644
--- a/gm/gradients_degenerate.cpp
+++ b/gm/gradients_degenerate.cpp
@@ -17,10 +17,10 @@
 static const SkScalar POS[] = { 0.0, 0.0, 0.5, 1.0, 1.0 };
 static const int COLOR_CT = SK_ARRAY_COUNT(COLORS);
 
-static const SkShader::TileMode TILE_MODES[] = { SkShader::kDecal_TileMode,
-                                                 SkShader::kRepeat_TileMode,
-                                                 SkShader::kMirror_TileMode,
-                                                 SkShader::kClamp_TileMode };
+static const SkTileMode TILE_MODES[] = { SkTileMode::kDecal,
+                                         SkTileMode::kRepeat,
+                                         SkTileMode::kMirror,
+                                         SkTileMode::kClamp };
 static const char* TILE_NAMES[] = { "decal", "repeat", "mirror", "clamp" };
 static const int TILE_MODE_CT = SK_ARRAY_COUNT(TILE_MODES);
 
@@ -29,7 +29,7 @@
 
 static const SkPoint CENTER = SkPoint::Make(TILE_SIZE / 2, TILE_SIZE / 2);
 
-typedef sk_sp<SkShader> (*GradientFactory)(SkShader::TileMode tm);
+typedef sk_sp<SkShader> (*GradientFactory)(SkTileMode tm);
 
 static void draw_tile_header(SkCanvas* canvas) {
     canvas->save();
@@ -72,37 +72,37 @@
     canvas->translate(0, 3 * TILE_GAP + TILE_SIZE);
 }
 
-static sk_sp<SkShader> make_linear(SkShader::TileMode mode) {
+static sk_sp<SkShader> make_linear(SkTileMode mode) {
     // Same position
     SkPoint pts[2] = {CENTER, CENTER};
     return SkGradientShader::MakeLinear(pts, COLORS, POS, COLOR_CT, mode);
 }
 
-static sk_sp<SkShader> make_radial(SkShader::TileMode mode) {
+static sk_sp<SkShader> make_radial(SkTileMode mode) {
     // Radius = 0
     return SkGradientShader::MakeRadial(CENTER, 0.0, COLORS, POS, COLOR_CT, mode);
 }
 
-static sk_sp<SkShader> make_sweep(SkShader::TileMode mode) {
+static sk_sp<SkShader> make_sweep(SkTileMode mode) {
     // Start and end angles at 45
     static constexpr SkScalar SWEEP_ANG = 45.0;
     return SkGradientShader::MakeSweep(CENTER.fX, CENTER.fY, COLORS, POS, COLOR_CT, mode,
                                        SWEEP_ANG, SWEEP_ANG, 0, nullptr);
 }
 
-static sk_sp<SkShader> make_sweep_zero_ang(SkShader::TileMode mode) {
+static sk_sp<SkShader> make_sweep_zero_ang(SkTileMode mode) {
     // Start and end angles at 0
     return SkGradientShader::MakeSweep(CENTER.fX, CENTER.fY, COLORS, POS, COLOR_CT, mode,
                                        0.0, 0.0, 0, nullptr);
 }
 
-static sk_sp<SkShader> make_2pt_conic(SkShader::TileMode mode) {
+static sk_sp<SkShader> make_2pt_conic(SkTileMode mode) {
     // Start and end radius = TILE_SIZE, same position
     return SkGradientShader::MakeTwoPointConical(CENTER, TILE_SIZE / 2, CENTER, TILE_SIZE / 2,
                                                  COLORS, POS, COLOR_CT, mode);
 }
 
-static sk_sp<SkShader> make_2pt_conic_zero_rad(SkShader::TileMode mode) {
+static sk_sp<SkShader> make_2pt_conic_zero_rad(SkTileMode mode) {
     // Start and end radius = 0, same position
     return SkGradientShader::MakeTwoPointConical(CENTER, 0.0, CENTER, 0.0, COLORS, POS,
                                                  COLOR_CT, mode);
diff --git a/gm/gradients_no_texture.cpp b/gm/gradients_no_texture.cpp
index 694642e..2814f3c 100644
--- a/gm/gradients_no_texture.cpp
+++ b/gm/gradients_no_texture.cpp
@@ -26,25 +26,25 @@
     { 4, gColors, nullptr },
 };
 
-static sk_sp<SkShader> MakeLinear(const SkPoint pts[2], const GradData& data, SkShader::TileMode tm) {
+static sk_sp<SkShader> MakeLinear(const SkPoint pts[2], const GradData& data, SkTileMode tm) {
     return SkGradientShader::MakeLinear(pts, data.fColors, data.fPos, data.fCount, tm);
 }
 
-static sk_sp<SkShader> MakeRadial(const SkPoint pts[2], const GradData& data, SkShader::TileMode tm) {
+static sk_sp<SkShader> MakeRadial(const SkPoint pts[2], const GradData& data, SkTileMode tm) {
     SkPoint center;
     center.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
     return SkGradientShader::MakeRadial(center, center.fX, data.fColors, data.fPos, data.fCount, tm);
 }
 
-static sk_sp<SkShader> MakeSweep(const SkPoint pts[2], const GradData& data, SkShader::TileMode) {
+static sk_sp<SkShader> MakeSweep(const SkPoint pts[2], const GradData& data, SkTileMode) {
     SkPoint center;
     center.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
     return SkGradientShader::MakeSweep(center.fX, center.fY, data.fColors, data.fPos, data.fCount);
 }
 
-static sk_sp<SkShader> Make2Radial(const SkPoint pts[2], const GradData& data, SkShader::TileMode tm) {
+static sk_sp<SkShader> Make2Radial(const SkPoint pts[2], const GradData& data, SkTileMode tm) {
     SkPoint center0, center1;
     center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                 SkScalarAve(pts[0].fY, pts[1].fY));
@@ -56,7 +56,7 @@
         data.fColors, data.fPos, data.fCount, tm);
 }
 
-static sk_sp<SkShader> Make2Conical(const SkPoint pts[2], const GradData& data, SkShader::TileMode tm) {
+static sk_sp<SkShader> Make2Conical(const SkPoint pts[2], const GradData& data, SkTileMode tm) {
     SkPoint center0, center1;
     SkScalar radius0 = (pts[1].fX - pts[0].fX) / 10;
     SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
@@ -69,7 +69,7 @@
 }
 
 
-typedef sk_sp<SkShader> (*GradMaker)(const SkPoint pts[2], const GradData& data, SkShader::TileMode tm);
+typedef sk_sp<SkShader> (*GradMaker)(const SkPoint pts[2], const GradData& data, SkTileMode tm);
 
 constexpr GradMaker gGradMakers[] = {
     MakeLinear, MakeRadial, MakeSweep, Make2Radial, Make2Conical,
@@ -94,7 +94,7 @@
     void onDraw(SkCanvas* canvas) override {
         constexpr SkPoint kPts[2] = { { 0, 0 },
                                          { SkIntToScalar(50), SkIntToScalar(50) } };
-        constexpr SkShader::TileMode kTM = SkShader::kClamp_TileMode;
+        constexpr SkTileMode kTM = SkTileMode::kClamp;
         SkRect kRect = { 0, 0, SkIntToScalar(50), SkIntToScalar(50) };
         SkPaint paint;
         paint.setAntiAlias(true);
@@ -255,7 +255,7 @@
             ColorPos rec;
             procs[i](&rec);
             paint.setShader(SkGradientShader::MakeLinear(pts, rec.fColors, rec.fPos, rec.fCount,
-                                                         SkShader::kClamp_TileMode));
+                                                         SkTileMode::kClamp));
             canvas->drawRect(drawR, paint);
 
             canvas->save();
diff --git a/gm/gradtext.cpp b/gm/gradtext.cpp
index 873b516..a4fd0b4 100644
--- a/gm/gradtext.cpp
+++ b/gm/gradtext.cpp
@@ -5,18 +5,18 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkGradientShader.h"
 #include "SkTypeface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 // test shader w/ transparency
 static sk_sp<SkShader> make_grad(SkScalar width) {
     SkColor colors[] = { SK_ColorRED, 0x0000FF00, SK_ColorBLUE };
     SkPoint pts[] = { { 0, 0 }, { width, 0 } };
     return SkGradientShader::MakeLinear(pts, colors, nullptr, SK_ARRAY_COUNT(colors),
-                                        SkShader::kMirror_TileMode);
+                                        SkTileMode::kMirror);
 }
 
 // test opaque shader
@@ -24,13 +24,13 @@
     SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE };
     SkPoint pts[] = { { 0, 0 }, { width, 0 } };
     return SkGradientShader::MakeLinear(pts, colors, nullptr, SK_ARRAY_COUNT(colors),
-                                        SkShader::kMirror_TileMode);
+                                        SkTileMode::kMirror);
 }
 
 static sk_sp<SkShader> make_chrome_solid() {
     SkColor colors[] = { SK_ColorGREEN, SK_ColorGREEN };
     SkPoint pts[] = { { 0, 0 }, { 1, 0 } };
-    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode);
+    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
 }
 
 namespace skiagm {
@@ -55,7 +55,7 @@
         // Minimal repro doesn't require AA, LCD, or a nondefault typeface
         paint.setShader(make_chrome_solid());
 
-        SkFont font(sk_tool_utils::create_portable_typeface(), 500);
+        SkFont font(ToolUtils::create_portable_typeface(), 500);
         font.setEdging(SkFont::Edging::kAlias);
 
         canvas->drawString("I", 0, 100, font, paint);
@@ -75,7 +75,7 @@
     virtual SkISize onISize() { return SkISize::Make(500, 480); }
     virtual void onDraw(SkCanvas* canvas) {
         SkPaint paint;
-        SkFont font(sk_tool_utils::create_portable_typeface());
+        SkFont  font(ToolUtils::create_portable_typeface());
         font.setEdging(SkFont::Edging::kAlias);
 
         paint.setStyle(SkPaint::kFill_Style);
@@ -103,7 +103,7 @@
 
 DEF_SIMPLE_GM(gradtext, canvas, 500, 480) {
     static constexpr float kTextSize = 26.0f;
-    SkFont font(sk_tool_utils::create_portable_typeface(), kTextSize);
+    SkFont                 font(ToolUtils::create_portable_typeface(), kTextSize);
 
     canvas->drawRect({0, 0, 500, 240}, SkPaint());
     canvas->translate(20.0f, kTextSize);
diff --git a/gm/hairlines.cpp b/gm/hairlines.cpp
index 6954207..6d4b8d4 100644
--- a/gm/hairlines.cpp
+++ b/gm/hairlines.cpp
@@ -149,13 +149,11 @@
             bug->addArc(circle, kStartAngle, kSweepAngle);
 
             // Now add the chord that should cap the circular arc
-            SkScalar cosV, sinV = SkScalarSinCos(SkDegreesToRadians(kStartAngle), &cosV);
+            SkPoint p0 = { kRad * SkScalarCos(SkDegreesToRadians(kStartAngle)),
+                           kRad * SkScalarSin(SkDegreesToRadians(kStartAngle)) };
 
-            SkPoint p0 = SkPoint::Make(kRad * cosV, kRad * sinV);
-
-            sinV = SkScalarSinCos(SkDegreesToRadians(kStartAngle + kSweepAngle), &cosV);
-
-            SkPoint p1 = SkPoint::Make(kRad * cosV, kRad * sinV);
+            SkPoint p1 = { kRad * SkScalarCos(SkDegreesToRadians(kStartAngle + kSweepAngle)),
+                           kRad * SkScalarSin(SkDegreesToRadians(kStartAngle + kSweepAngle)) };
 
             bug->moveTo(p0);
             bug->lineTo(p1);
diff --git a/gm/hairmodes.cpp b/gm/hairmodes.cpp
index 151af3e..9793e93 100644
--- a/gm/hairmodes.cpp
+++ b/gm/hairmodes.cpp
@@ -63,8 +63,7 @@
 
     SkMatrix m;
     m.setScale(SkIntToScalar(6), SkIntToScalar(6));
-    return SkShader::MakeBitmapShader(bm,
-                                      SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &m);
+    return bm.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &m);
 }
 
 namespace skiagm {
diff --git a/gm/hardstop_gradients.cpp b/gm/hardstop_gradients.cpp
index 84b7ab8..64d42e8 100644
--- a/gm/hardstop_gradients.cpp
+++ b/gm/hardstop_gradients.cpp
@@ -133,10 +133,10 @@
             4,
         };
 
-        SkShader::TileMode tilemodes[NUM_COLS] = {
-            SkShader::kClamp_TileMode,
-            SkShader::kRepeat_TileMode,
-            SkShader::kMirror_TileMode,
+        SkTileMode tilemodes[NUM_COLS] = {
+            SkTileMode::kClamp,
+            SkTileMode::kRepeat,
+            SkTileMode::kMirror,
         };
 
         for (int cellRow = 0; cellRow < NUM_ROWS; cellRow++) {
diff --git a/gm/highcontrastfilter.cpp b/gm/highcontrastfilter.cpp
index d3da2ac..cc7ef28 100644
--- a/gm/highcontrastfilter.cpp
+++ b/gm/highcontrastfilter.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkFont.h"
 #include "SkGradientShader.h"
 #include "SkHighContrastFilter.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 using InvertStyle = SkHighContrastConfig::InvertStyle;
 
@@ -32,7 +32,7 @@
              config.fContrast);
 
     SkFont font;
-    font.setTypeface(sk_tool_utils::create_portable_typeface());
+    font.setTypeface(ToolUtils::create_portable_typeface());
     font.setSize(0.05f);
     font.setEdging(SkFont::Edging::kAlias);
 
@@ -73,14 +73,14 @@
     SkScalar    pos[] = { 0.2f, 0.8f };
     paint.setShader(SkGradientShader::MakeLinear(
         pts, colors, pos,
-        SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode));
+        SK_ARRAY_COUNT(colors), SkTileMode::kClamp));
     canvas->drawRect(bounds, paint);
 
     bounds = SkRect::MakeLTRB(0.1f, 0.6f, 0.9f, 0.8f);
     SkColor colors2[] = { SK_ColorGREEN, SK_ColorWHITE };
     paint.setShader(SkGradientShader::MakeLinear(
         pts, colors2, pos,
-        SK_ARRAY_COUNT(colors2), SkShader::kClamp_TileMode));
+        SK_ARRAY_COUNT(colors2), SkTileMode::kClamp));
     canvas->drawRect(bounds, paint);
 
     canvas->restore();
@@ -99,10 +99,10 @@
         fFilter = SkHighContrastFilter::Make(fConfig);
         fGr1 = SkGradientShader::MakeLinear(
             g1Points, g1Colors, pos, SK_ARRAY_COUNT(g1Colors),
-            SkShader::kClamp_TileMode);
+            SkTileMode::kClamp);
         fGr2 = SkGradientShader::MakeLinear(
             g2Points, g2Colors, pos, SK_ARRAY_COUNT(g2Colors),
-            SkShader::kClamp_TileMode);
+            SkTileMode::kClamp);
     }
 
 protected:
diff --git a/gm/hsl.cpp b/gm/hsl.cpp
index 03bc3f7..6839811 100644
--- a/gm/hsl.cpp
+++ b/gm/hsl.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 // Hue, Saturation, Color, and Luminosity blend modes are oddballs.
 // They nominally convert their inputs to unpremul, then to HSL, then
@@ -146,7 +146,7 @@
 
 DEF_SIMPLE_GM(hsl, canvas, 600, 100) {
     SkPaint paint;
-    SkFont font(sk_tool_utils::create_portable_typeface());
+    SkFont  font(ToolUtils::create_portable_typeface());
 
     const char* comment = "HSL blend modes are correct when you see no circles in the squares.";
     canvas->drawString(comment, 10,10, font, paint);
diff --git a/gm/image.cpp b/gm/image.cpp
index 98a579b..b6e438c 100644
--- a/gm/image.cpp
+++ b/gm/image.cpp
@@ -13,8 +13,8 @@
 #include "SkRandom.h"
 #include "SkStream.h"
 #include "SkSurface.h"
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include <functional>
 
@@ -113,7 +113,7 @@
     void onDraw(SkCanvas* canvas) override {
         canvas->scale(2, 2);
 
-        SkFont font(sk_tool_utils::create_portable_typeface(), 8);
+        SkFont font(ToolUtils::create_portable_typeface(), 8);
 
         canvas->drawString("Original Img",  10,  60, font, SkPaint());
         canvas->drawString("Modified Img",  10, 140, font, SkPaint());
@@ -396,13 +396,13 @@
     return reader.readImage();
 }
 
-DEF_SIMPLE_GM(image_subset, canvas, 440, 220) {
+DEF_SIMPLE_GM_CAN_FAIL(image_subset, canvas, errorMsg, 440, 220) {
     SkImageInfo info = SkImageInfo::MakeN32Premul(200, 200, nullptr);
-    auto surf = sk_tool_utils::makeSurface(canvas, info, nullptr);
+    auto        surf = ToolUtils::makeSurface(canvas, info, nullptr);
     auto img = make_lazy_image(surf.get());
     if (!img) {
-        skiagm::GM::DrawFailureMessage(canvas, "Failed to make lazy image.");
-        return;
+        *errorMsg = "Failed to make lazy image.";
+        return skiagm::DrawResult::kFail;
     }
 
     canvas->drawImage(img, 10, 10, nullptr);
@@ -410,4 +410,5 @@
     canvas->drawImage(sub, 220, 10);
     sub = serial_deserial(sub.get());
     canvas->drawImage(sub, 220+110, 10);
+    return skiagm::DrawResult::kOk;
 }
diff --git a/gm/image_pict.cpp b/gm/image_pict.cpp
index 2c842b7..6614c99 100644
--- a/gm/image_pict.cpp
+++ b/gm/image_pict.cpp
@@ -156,11 +156,11 @@
             surface->getCanvas()->translate(-100, -100);
             surface->getCanvas()->drawPicture(pic);
             sk_sp<SkImage> image(surface->makeImageSnapshot());
-            fProxy = as_IB(image)->asTextureProxyRef();
+            fProxy = as_IB(image)->asTextureProxyRef(fCtx.get());
         }
     }
 protected:
-    sk_sp<GrTextureProxy> onGenerateTexture(GrContext* ctx, const SkImageInfo& info,
+    sk_sp<GrTextureProxy> onGenerateTexture(GrRecordingContext* ctx, const SkImageInfo& info,
                                             const SkIPoint& origin,
                                             bool willBeMipped) override {
         SkASSERT(ctx);
diff --git a/gm/image_shader.cpp b/gm/image_shader.cpp
index 5e55786..37242d5 100644
--- a/gm/image_shader.cpp
+++ b/gm/image_shader.cpp
@@ -103,7 +103,7 @@
         canvas->drawImage(image, 0, 0);
         canvas->translate(0, 120);
 
-        const SkShader::TileMode tile = SkShader::kRepeat_TileMode;
+        const SkTileMode tile = SkTileMode::kRepeat;
         const SkMatrix localM = SkMatrix::MakeTrans(-50, -50);
         SkPaint paint;
         paint.setShader(image->makeShader(tile, tile, &localM));
diff --git a/gm/imagealphathreshold.cpp b/gm/imagealphathreshold.cpp
index 9e66fef..1fe4dad 100644
--- a/gm/imagealphathreshold.cpp
+++ b/gm/imagealphathreshold.cpp
@@ -5,14 +5,14 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
 #include "SkAlphaThresholdFilter.h"
 #include "SkImageSource.h"
 #include "SkOffsetImageFilter.h"
 #include "SkRandom.h"
 #include "SkRegion.h"
 #include "SkSurface.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #define WIDTH 500
 #define HEIGHT 500
@@ -104,7 +104,7 @@
 
     SkImageInfo info = SkImageInfo::Make(width, height, ct, at, std::move(cs));
 
-    return sk_tool_utils::makeSurface(canvas, info);
+    return ToolUtils::makeSurface(canvas, info);
 }
 
 class ImageAlphaThresholdSurfaceGM : public skiagm::GM {
@@ -122,7 +122,7 @@
         return SkISize::Make(WIDTH, HEIGHT);
     }
 
-    void onDraw(SkCanvas* canvas) override {
+    DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
         SkMatrix matrix;
         matrix.reset();
         matrix.setTranslate(WIDTH * .1f, HEIGHT * .1f);
@@ -133,8 +133,8 @@
         sk_sp<SkSurface> surface(make_color_matching_surface(canvas, WIDTH, HEIGHT,
                                                              kPremul_SkAlphaType));
         if (!surface) {
-            DrawFailureMessage(canvas, "make_color_matching_surface failed");
-            return;
+            *errorMsg = "make_color_matching_surface failed";
+            return DrawResult::kFail;
         }
 
         surface->getCanvas()->clear(SK_ColorTRANSPARENT);
@@ -143,6 +143,7 @@
         SkPaint paint = create_filter_paint();
         canvas->clipRect(SkRect::MakeLTRB(100, 100, WIDTH - 100, HEIGHT - 100));
         canvas->drawImage(surface->makeImageSnapshot().get(), 0, 0, &paint);
+        return DrawResult::kOk;
     }
 
 private:
diff --git a/gm/imageblur.cpp b/gm/imageblur.cpp
index 3531658..5aca868 100644
--- a/gm/imageblur.cpp
+++ b/gm/imageblur.cpp
@@ -5,10 +5,10 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkBlurImageFilter.h"
 #include "SkRandom.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #define WIDTH 500
 #define HEIGHT 500
@@ -21,11 +21,11 @@
 
         SkRandom rand;
         SkPaint textPaint;
-        SkFont font(sk_tool_utils::create_portable_typeface());
+        SkFont   font(ToolUtils::create_portable_typeface());
         for (int i = 0; i < 25; ++i) {
             int x = rand.nextULessThan(WIDTH);
             int y = rand.nextULessThan(HEIGHT);
-            textPaint.setColor(sk_tool_utils::color_to_565(rand.nextBits(24) | 0xFF000000));
+            textPaint.setColor(ToolUtils::color_to_565(rand.nextBits(24) | 0xFF000000));
             font.setSize(rand.nextRangeScalar(0, 300));
             canvas->drawString(str, SkIntToScalar(x), SkIntToScalar(y), font, textPaint);
         }
diff --git a/gm/imageblur2.cpp b/gm/imageblur2.cpp
index 4cb94c9..1bed94c 100644
--- a/gm/imageblur2.cpp
+++ b/gm/imageblur2.cpp
@@ -5,10 +5,10 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkBlurImageFilter.h"
 #include "SkRandom.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 // TODO deprecate imageblur
 
@@ -31,7 +31,7 @@
     constexpr SkScalar dy = kHeight / sigmaCount;
     constexpr SkScalar textSize = 12;
 
-    SkFont font(sk_tool_utils::create_portable_typeface(), textSize);
+    SkFont font(ToolUtils::create_portable_typeface(), textSize);
     font.setEdging(SkFont::Edging::kAlias);
 
     for (int x = 0; x < sigmaCount; x++) {
@@ -45,7 +45,7 @@
 
             SkRandom rand;
             SkPaint textPaint;
-            textPaint.setColor(sk_tool_utils::color_to_565(rand.nextBits(24) | 0xFF000000));
+            textPaint.setColor(ToolUtils::color_to_565(rand.nextBits(24) | 0xFF000000));
             for (int i = 0; i < testStringCount; i++) {
                 canvas->drawString(kTestStrings[i],
                                    SkIntToScalar(x * dx),
diff --git a/gm/imageblurclampmode.cpp b/gm/imageblurclampmode.cpp
index f16f01d..2cd1723 100644
--- a/gm/imageblurclampmode.cpp
+++ b/gm/imageblurclampmode.cpp
@@ -5,14 +5,14 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
-#include "SkSurface.h"
 #include "SkBlurImageFilter.h"
+#include "SkSurface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static sk_sp<SkImage> make_image(SkCanvas* canvas) {
     SkImageInfo info = SkImageInfo::MakeN32Premul(250, 200);
-    auto surface = sk_tool_utils::makeSurface(canvas, info);
+    auto        surface = ToolUtils::makeSurface(canvas, info);
     SkCanvas* c = surface->getCanvas();
     SkPaint paint;
     paint.setAntiAlias(true);
diff --git a/gm/imageblurrepeatmode.cpp b/gm/imageblurrepeatmode.cpp
index 27465aa..9dafd1d 100644
--- a/gm/imageblurrepeatmode.cpp
+++ b/gm/imageblurrepeatmode.cpp
@@ -5,14 +5,14 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
-#include "SkSurface.h"
 #include "SkBlurImageFilter.h"
+#include "SkSurface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static sk_sp<SkImage> make_image(SkCanvas* canvas, int direction) {
     SkImageInfo info = SkImageInfo::MakeN32Premul(250, 200);
-    auto surface = sk_tool_utils::makeSurface(canvas, info);
+    auto        surface = ToolUtils::makeSurface(canvas, info);
     SkCanvas* c = surface->getCanvas();
     SkPaint paint;
     paint.setAntiAlias(true);
@@ -28,7 +28,7 @@
         for (int x = 0; x < info.width(); x += width) {
             paint.setColor(colors[x/width % 5]);
             if (yDirection) {
-                paint.setAlpha(127);
+                paint.setAlphaf(0.5f);
             }
             c->drawRect(SkRect::MakeXYWH(x, 0, width, info.height()), paint);
         }
@@ -38,7 +38,7 @@
         for (int y = 0; y < info.height(); y += width) {
             paint.setColor(colors[y/width % 5]);
             if (xDirection) {
-                paint.setAlpha(127);
+                paint.setAlphaf(0.5f);
             }
             c->drawRect(SkRect::MakeXYWH(0, y, info.width(), width), paint);
         }
diff --git a/gm/imageblurtiled.cpp b/gm/imageblurtiled.cpp
index 5a57721..f1d15d0 100644
--- a/gm/imageblurtiled.cpp
+++ b/gm/imageblurtiled.cpp
@@ -5,10 +5,10 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkBlurImageFilter.h"
 #include "SkRandom.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #define WIDTH 640
 #define HEIGHT 480
@@ -46,7 +46,7 @@
                     "jumped over",
                     "the lazy dog.",
                 };
-                SkFont font(sk_tool_utils::create_portable_typeface(), 100);
+                SkFont font(ToolUtils::create_portable_typeface(), 100);
                 int posY = 0;
                 for (unsigned i = 0; i < SK_ARRAY_COUNT(str); i++) {
                     posY += 100;
diff --git a/gm/imagefilters.cpp b/gm/imagefilters.cpp
index bc33a57..5065d13 100644
--- a/gm/imagefilters.cpp
+++ b/gm/imagefilters.cpp
@@ -5,13 +5,13 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
 #include "SkBlurImageFilter.h"
 #include "SkColorMatrixFilter.h"
 #include "SkImage.h"
 #include "SkImageFilter.h"
 #include "SkSurface.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 /**
  *  Test drawing a primitive w/ an imagefilter (in this case, just matrix w/ identity) to see
@@ -70,7 +70,7 @@
 
 static sk_sp<SkImage> make_image(SkCanvas* canvas) {
     const SkImageInfo info = SkImageInfo::MakeS32(100, 100, kPremul_SkAlphaType);
-    auto surface(sk_tool_utils::makeSurface(canvas, info));
+    auto              surface(ToolUtils::makeSurface(canvas, info));
     surface->getCanvas()->drawRect(SkRect::MakeXYWH(25, 25, 50, 50), SkPaint());
     return surface->makeImageSnapshot();
 }
@@ -138,7 +138,7 @@
 DEF_SIMPLE_GM(savelayer_with_backdrop, canvas, 830, 550) {
     SkColorMatrix cm;
     cm.setSaturation(10);
-    sk_sp<SkColorFilter> cf(SkColorFilter::MakeMatrixFilterRowMajor255(cm.fMat));
+    sk_sp<SkColorFilter> cf(SkColorFilters::MatrixRowMajor255(cm.fMat));
     const SkScalar kernel[] = { 4, 0, 4, 0, -15, 0, 4, 0, 4 };
     sk_sp<SkImageFilter> filters[] = {
         SkBlurImageFilter::Make(10, 10, nullptr),
diff --git a/gm/imagefiltersbase.cpp b/gm/imagefiltersbase.cpp
index 40bb810..cd5f9d2 100644
--- a/gm/imagefiltersbase.cpp
+++ b/gm/imagefiltersbase.cpp
@@ -5,14 +5,14 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkColorFilter.h"
 #include "SkColorPriv.h"
 #include "SkImageFilterPriv.h"
 #include "SkShader.h"
 #include "SkTextUtils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #include "SkBlurImageFilter.h"
 #include "SkColorFilterImageFilter.h"
@@ -33,9 +33,6 @@
                                         SkIPoint* offset) const override {
         return nullptr;
     }
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override {
-        return sk_ref_sp(this);
-    }
 
 private:
 
@@ -61,9 +58,6 @@
         offset->set(0, 0);
         return sk_ref_sp<SkSpecialImage>(source);
     }
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override {
-        return sk_ref_sp(const_cast<IdentityImageFilter*>(this));
-    }
 
 private:
     IdentityImageFilter(sk_sp<SkImageFilter> input) : INHERITED(&input, 1, nullptr) {}
@@ -127,7 +121,7 @@
     SkPaint paint;
     paint.setImageFilter(imf);
     paint.setColor(SK_ColorCYAN);
-    SkFont font(sk_tool_utils::create_portable_typeface(), r.height()/2);
+    SkFont font(ToolUtils::create_portable_typeface(), r.height() / 2);
     SkTextUtils::DrawString(canvas, "Text", r.centerX(), r.centerY(), font, paint,
                             SkTextUtils::kCenter_Align);
 }
@@ -175,7 +169,7 @@
             draw_bitmap,
         };
 
-        auto cf = SkColorFilter::MakeModeFilter(SK_ColorRED, SkBlendMode::kSrcIn);
+        auto cf = SkColorFilters::Blend(SK_ColorRED, SkBlendMode::kSrcIn);
         sk_sp<SkImageFilter> filters[] = {
             nullptr,
             IdentityImageFilter::Make(nullptr),
@@ -241,7 +235,7 @@
             SkFont::Edging::kAntiAlias,
             SkFont::Edging::kSubpixelAntiAlias,
         };
-        SkFont font(sk_tool_utils::create_portable_typeface(), 30);
+        SkFont font(ToolUtils::create_portable_typeface(), 30);
 
         SkAutoCanvasRestore acr(canvas, true);
         for (SkFont::Edging edging : kEdgings) {
@@ -300,7 +294,7 @@
     ImageFiltersText_CF() : ImageFiltersTextBaseGM("color") {}
 
     void installFilter(SkPaint* paint) override {
-        paint->setColorFilter(SkColorFilter::MakeModeFilter(SK_ColorBLUE, SkBlendMode::kSrcIn));
+        paint->setColorFilter(SkColorFilters::Blend(SK_ColorBLUE, SkBlendMode::kSrcIn));
     }
 };
 DEF_GM( return new ImageFiltersText_CF; )
diff --git a/gm/imagefiltersclipped.cpp b/gm/imagefiltersclipped.cpp
index 6236c38..16a7fc6 100644
--- a/gm/imagefiltersclipped.cpp
+++ b/gm/imagefiltersclipped.cpp
@@ -20,8 +20,8 @@
 #include "SkPoint3.h"
 #include "SkScalar.h"
 #include "SkSurface.h"
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #define RESIZE_FACTOR_X SkIntToScalar(2)
 #define RESIZE_FACTOR_Y SkIntToScalar(5)
@@ -38,7 +38,7 @@
     colors[1] = SK_ColorBLACK;
     SkPaint paint;
     paint.setShader(SkGradientShader::MakeRadial(SkPoint::Make(x, y), radius, colors, nullptr,
-        2, SkShader::kClamp_TileMode));
+        2, SkTileMode::kClamp));
     canvas->drawCircle(x, y, radius, paint);
     return surface->makeImageSnapshot();
 }
@@ -79,8 +79,8 @@
     }
 
     void onOnceBeforeDraw() override {
-        fCheckerboard = SkImage::MakeFromBitmap
-            (sk_tool_utils::create_checkerboard_bitmap(64, 64, 0xFFA0A0A0, 0xFF404040, 8));
+        fCheckerboard = SkImage::MakeFromBitmap(
+                ToolUtils::create_checkerboard_bitmap(64, 64, 0xFFA0A0A0, 0xFF404040, 8));
         fGradientCircle = make_gradient_circle(64, 64);
     }
 
diff --git a/gm/imagefilterscropexpand.cpp b/gm/imagefilterscropexpand.cpp
index 6b1bac2..94b301f 100644
--- a/gm/imagefilterscropexpand.cpp
+++ b/gm/imagefilterscropexpand.cpp
@@ -54,7 +54,7 @@
                             0, 1, 0, 0, 255,
                             0, 0, 1, 0, 0,
                             0, 0, 0, 1, 32 };
-    sk_sp<SkColorFilter> cfAlphaTrans(SkColorFilter::MakeMatrixFilterRowMajor255(matrix));
+    sk_sp<SkColorFilter> cfAlphaTrans(SkColorFilters::MatrixRowMajor255(matrix));
 
     SkRect r = SkRect::MakeWH(SkIntToScalar(64), SkIntToScalar(64));
     SkScalar MARGIN = SkIntToScalar(12);
@@ -165,7 +165,7 @@
         colors[1] = SK_ColorBLACK;
         SkPaint paint;
         paint.setShader(SkGradientShader::MakeRadial(SkPoint::Make(x, y), radius, colors, nullptr,
-                                                     2, SkShader::kClamp_TileMode));
+                                                     2, SkTileMode::kClamp));
         canvas->drawCircle(x, y, radius, paint);
 
         return surface->makeImageSnapshot();
diff --git a/gm/imagefilterscropped.cpp b/gm/imagefilterscropped.cpp
index 179fdc3..2338aff 100644
--- a/gm/imagefilterscropped.cpp
+++ b/gm/imagefilterscropped.cpp
@@ -5,13 +5,13 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkColorFilter.h"
 #include "SkColorPriv.h"
 #include "SkShader.h"
 #include "SkTextUtils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #include "SkBlurImageFilter.h"
 #include "SkMorphologyImageFilter.h"
@@ -44,7 +44,7 @@
     paint.setImageFilter(std::move(imf));
     paint.setColor(SK_ColorGREEN);
 
-    SkFont font(sk_tool_utils::create_portable_typeface(), r.height()/2);
+    SkFont font(ToolUtils::create_portable_typeface(), r.height() / 2);
     SkTextUtils::DrawString(canvas, "Text", r.centerX(), r.centerY(), font, paint, SkTextUtils::kCenter_Align);
 }
 
@@ -114,7 +114,7 @@
             draw_bitmap, draw_path, draw_paint, draw_text
         };
 
-        sk_sp<SkColorFilter> cf(SkColorFilter::MakeModeFilter(SK_ColorBLUE,
+        sk_sp<SkColorFilter> cf(SkColorFilters::Blend(SK_ColorBLUE,
                                                               SkBlendMode::kSrcIn));
         SkImageFilter::CropRect cropRect(SkRect::Make(SkIRect::MakeXYWH(10, 10, 44, 44)),
                                          SkImageFilter::CropRect::kHasAll_CropEdge);
diff --git a/gm/imagefiltersgraph.cpp b/gm/imagefiltersgraph.cpp
index a369dc0..7955b52 100644
--- a/gm/imagefiltersgraph.cpp
+++ b/gm/imagefiltersgraph.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkArithmeticImageFilter.h"
 #include "SkBlurImageFilter.h"
@@ -39,14 +39,14 @@
 
     void onOnceBeforeDraw() override {
         fImage = SkImage::MakeFromBitmap(
-            sk_tool_utils::create_string_bitmap(100, 100, SK_ColorWHITE, 20, 70, 96, "e"));
+                ToolUtils::create_string_bitmap(100, 100, SK_ColorWHITE, 20, 70, 96, "e"));
     }
 
     void onDraw(SkCanvas* canvas) override {
         canvas->clear(SK_ColorBLACK);
         {
             sk_sp<SkImageFilter> bitmapSource(SkImageSource::Make(fImage));
-            sk_sp<SkColorFilter> cf(SkColorFilter::MakeModeFilter(SK_ColorRED,
+            sk_sp<SkColorFilter> cf(SkColorFilters::Blend(SK_ColorRED,
                                                                   SkBlendMode::kSrcIn));
             sk_sp<SkImageFilter> blur(SkBlurImageFilter::Make(4.0f, 4.0f, std::move(bitmapSource)));
             sk_sp<SkImageFilter> erode(SkErodeImageFilter::Make(4, 4, blur));
@@ -67,7 +67,7 @@
                                     0, 0, SK_Scalar1, 0, 0,
                                     0, 0, 0, 0.5f, 0 };
 
-            sk_sp<SkColorFilter> matrixFilter(SkColorFilter::MakeMatrixFilterRowMajor255(matrix));
+            sk_sp<SkColorFilter> matrixFilter(SkColorFilters::MatrixRowMajor255(matrix));
             sk_sp<SkImageFilter> colorMorph(SkColorFilterImageFilter::Make(std::move(matrixFilter),
                                                                            std::move(morph)));
             SkPaint paint;
@@ -82,7 +82,7 @@
                                     0, SK_Scalar1, 0, 0, 0,
                                     0, 0, SK_Scalar1, 0, 0,
                                     0, 0, 0, 0.5f, 0 };
-            sk_sp<SkColorFilter> matrixCF(SkColorFilter::MakeMatrixFilterRowMajor255(matrix));
+            sk_sp<SkColorFilter> matrixCF(SkColorFilters::MatrixRowMajor255(matrix));
             sk_sp<SkImageFilter> matrixFilter(SkColorFilterImageFilter::Make(std::move(matrixCF),
                                                                              nullptr));
             sk_sp<SkImageFilter> offsetFilter(SkOffsetImageFilter::Make(10.0f, 10.f,
@@ -141,10 +141,8 @@
         }
         {
             // Test that crop offsets are absolute, not relative to the parent's crop rect.
-            sk_sp<SkColorFilter> cf1(SkColorFilter::MakeModeFilter(SK_ColorBLUE,
-                                                                   SkBlendMode::kSrcIn));
-            sk_sp<SkColorFilter> cf2(SkColorFilter::MakeModeFilter(SK_ColorGREEN,
-                                                                   SkBlendMode::kSrcIn));
+            sk_sp<SkColorFilter> cf1(SkColorFilters::Blend(SK_ColorBLUE, SkBlendMode::kSrcIn));
+            sk_sp<SkColorFilter> cf2(SkColorFilters::Blend(SK_ColorGREEN, SkBlendMode::kSrcIn));
             SkImageFilter::CropRect outerRect(SkRect::MakeXYWH(SkIntToScalar(10), SkIntToScalar(10),
                                                                SkIntToScalar(80), SkIntToScalar(80)));
             SkImageFilter::CropRect innerRect(SkRect::MakeXYWH(SkIntToScalar(20), SkIntToScalar(20),
diff --git a/gm/imagefiltersscaled.cpp b/gm/imagefiltersscaled.cpp
index dc87da3..f3d354a 100644
--- a/gm/imagefiltersscaled.cpp
+++ b/gm/imagefiltersscaled.cpp
@@ -20,8 +20,8 @@
 #include "SkPoint3.h"
 #include "SkScalar.h"
 #include "SkSurface.h"
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #define RESIZE_FACTOR SkIntToScalar(4)
 
@@ -37,7 +37,7 @@
     colors[1] = SK_ColorBLACK;
     SkPaint paint;
     paint.setShader(SkGradientShader::MakeRadial(SkPoint::Make(x, y), radius, colors, nullptr,
-        2, SkShader::kClamp_TileMode));
+        2, SkTileMode::kClamp));
     canvas->drawCircle(x, y, radius, paint);
 
     return surface->makeImageSnapshot();
@@ -64,7 +64,7 @@
 
     void onOnceBeforeDraw() override {
         fCheckerboard = SkImage::MakeFromBitmap(
-            sk_tool_utils::create_checkerboard_bitmap(64, 64, 0xFFA0A0A0, 0xFF404040, 8));
+                ToolUtils::create_checkerboard_bitmap(64, 64, 0xFFA0A0A0, 0xFF404040, 8));
         fGradientCircle = make_gradient_circle(64, 64);
     }
 
diff --git a/gm/imagefilterstransformed.cpp b/gm/imagefilterstransformed.cpp
index 941a04c..9d2ed28 100644
--- a/gm/imagefilterstransformed.cpp
+++ b/gm/imagefilterstransformed.cpp
@@ -15,8 +15,8 @@
 #include "SkMorphologyImageFilter.h"
 #include "SkScalar.h"
 #include "SkSurface.h"
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 namespace skiagm {
 
@@ -38,7 +38,7 @@
     colors[1] = SK_ColorBLACK;
     SkPaint paint;
     paint.setShader(SkGradientShader::MakeRadial(SkPoint::Make(x, y), radius, colors, nullptr, 2,
-                                                 SkShader::kClamp_TileMode));
+                                                 SkTileMode::kClamp));
     canvas->drawCircle(x, y, radius, paint);
 
     return surface->makeImageSnapshot();
@@ -58,7 +58,7 @@
 
     void onOnceBeforeDraw() override {
         fCheckerboard = SkImage::MakeFromBitmap(
-            sk_tool_utils::create_checkerboard_bitmap(64, 64, 0xFFA0A0A0, 0xFF404040, 8));
+                ToolUtils::create_checkerboard_bitmap(64, 64, 0xFFA0A0A0, 0xFF404040, 8));
         fGradientCircle = make_gradient_circle(64, 64);
     }
 
diff --git a/gm/imagefromyuvtextures.cpp b/gm/imagefromyuvtextures.cpp
index 42cc8d8..47f86c6 100644
--- a/gm/imagefromyuvtextures.cpp
+++ b/gm/imagefromyuvtextures.cpp
@@ -18,6 +18,17 @@
 #include "SkImage.h"
 #include "SkTo.h"
 
+static sk_sp<SkColorFilter> yuv_to_rgb_colorfilter() {
+    static const float kJPEGConversionMatrix[20] = {
+        1.0f,  0.0f,       1.402f,    0.0f, -180.0f,
+        1.0f, -0.344136f, -0.714136f, 0.0f,  136.0f,
+        1.0f,  1.772f,     0.0f,      0.0f, -227.6f,
+        0.0f,  0.0f,       0.0f,      1.0f,    0.0f
+    };
+
+    return SkColorFilters::MatrixRowMajor255(kJPEGConversionMatrix);
+}
+
 namespace skiagm {
 class ImageFromYUVTextures : public GpuGM {
 public:
@@ -31,7 +42,7 @@
     }
 
     SkISize onISize() override {
-        return SkISize::Make(50, 300);
+        return SkISize::Make(kBmpSize + 2 * kPad, 390);
     }
 
     void onOnceBeforeDraw() override {
@@ -42,7 +53,7 @@
             { SK_ColorBLUE, SK_ColorYELLOW, SK_ColorGREEN, SK_ColorWHITE };
         paint.setShader(SkGradientShader::MakeRadial(SkPoint::Make(0,0), kBmpSize / 2.f, kColors,
                                                      nullptr, SK_ARRAY_COUNT(kColors),
-                                                     SkShader::kMirror_TileMode));
+                                                     SkTileMode::kMirror));
         SkBitmap rgbBmp;
         rgbBmp.allocN32Pixels(kBmpSize, kBmpSize, true);
         SkCanvas canvas(rgbBmp);
@@ -61,8 +72,8 @@
         uvPixels[0] = static_cast<signed char*>(fYUVBmps[1].getPixels());
         uvPixels[1] = static_cast<signed char*>(fYUVBmps[2].getPixels());
 
-        // Here we encode using the NTC encoding (even though we will draw it with all the supported
-        // yuv color spaces when converted back to RGB)
+        // Here we encode using the kJPEG_SkYUVColorSpace (i.e., full-swing Rec 601) even though
+        // we will draw it with all the supported yuv color spaces when converted back to RGB
         for (int i = 0; i < kBmpSize * kBmpSize; ++i) {
             yPixels[i] = static_cast<unsigned char>(0.299f * SkGetPackedR32(rgbColors[i]) +
                                                     0.587f * SkGetPackedG32(rgbColors[i]) +
@@ -145,45 +156,51 @@
     }
 
     void onDraw(GrContext* context, GrRenderTargetContext*, SkCanvas* canvas) override {
-        constexpr SkScalar kPad = 10.f;
+        // draw the original
+        SkScalar yOffset = kPad;
+        canvas->drawImage(fRGBImage.get(), kPad, yOffset);
+        yOffset += kBmpSize + kPad;
 
-        SkTArray<sk_sp<SkImage>> images;
-        images.push_back(fRGBImage);
         for (int space = kJPEG_SkYUVColorSpace; space <= kLastEnum_SkYUVColorSpace; ++space) {
             GrBackendTexture yuvTextures[3];
             this->createYUVTextures(context, yuvTextures);
-            images.push_back(SkImage::MakeFromYUVTexturesCopy(context,
-                                                              static_cast<SkYUVColorSpace>(space),
-                                                              yuvTextures,
-                                                              kTopLeft_GrSurfaceOrigin));
+            auto image = SkImage::MakeFromYUVTexturesCopy(context,
+                                                          static_cast<SkYUVColorSpace>(space),
+                                                          yuvTextures,
+                                                          kTopLeft_GrSurfaceOrigin);
             this->deleteBackendTextures(context, yuvTextures, 3);
-        }
-        for (int i = 0; i < images.count(); ++ i) {
-            SkScalar y = (i + 1) * kPad + i * fYUVBmps[0].height();
-            SkScalar x = kPad;
 
-            canvas->drawImage(images[i].get(), x, y);
+            SkPaint paint;
+            if (kIdentity_SkYUVColorSpace == space) {
+                // The identity color space needs post-processing to appear correct
+                paint.setColorFilter(yuv_to_rgb_colorfilter());
+            }
+
+            canvas->drawImage(image.get(), kPad, yOffset, &paint);
+            yOffset += kBmpSize + kPad;
         }
 
-        sk_sp<SkImage> image;
-        for (int space = kJPEG_SkYUVColorSpace, i = images.count();
-             space <= kLastEnum_SkYUVColorSpace; ++space, ++i) {
+        for (int space = kJPEG_SkYUVColorSpace; space <= kLastEnum_SkYUVColorSpace; ++space) {
             GrBackendTexture yuvTextures[3];
             GrBackendTexture resultTexture;
             this->createYUVTextures(context, yuvTextures);
             this->createResultTexture(
                     context, yuvTextures[0].width(), yuvTextures[0].height(), &resultTexture);
-            image = SkImage::MakeFromYUVTexturesCopyWithExternalBackend(
-                    context,
-                    static_cast<SkYUVColorSpace>(space),
-                    yuvTextures,
-                    kTopLeft_GrSurfaceOrigin,
-                    resultTexture);
+            auto image = SkImage::MakeFromYUVTexturesCopyWithExternalBackend(
+                                                          context,
+                                                          static_cast<SkYUVColorSpace>(space),
+                                                          yuvTextures,
+                                                          kTopLeft_GrSurfaceOrigin,
+                                                          resultTexture);
 
-            SkScalar y = (i + 1) * kPad + i * fYUVBmps[0].height();
-            SkScalar x = kPad;
+            SkPaint paint;
+            if (kIdentity_SkYUVColorSpace == space) {
+                // The identity color space needs post-processing to appear correct
+                paint.setColorFilter(yuv_to_rgb_colorfilter());
+            }
+            canvas->drawImage(image.get(), kPad, yOffset, &paint);
+            yOffset += kBmpSize + kPad;
 
-            canvas->drawImage(image.get(), x, y);
             GrBackendTexture texturesToDelete[4]{
                     yuvTextures[0],
                     yuvTextures[1],
@@ -198,7 +215,8 @@
     sk_sp<SkImage>  fRGBImage;
     SkBitmap        fYUVBmps[3];
 
-    static constexpr int kBmpSize = 32;
+    static constexpr SkScalar kPad = 10.0f;
+    static constexpr int kBmpSize  = 32;
 
     typedef GM INHERITED;
 };
diff --git a/gm/imagemagnifier.cpp b/gm/imagemagnifier.cpp
index 8fbc58d..b34dfb6 100644
--- a/gm/imagemagnifier.cpp
+++ b/gm/imagemagnifier.cpp
@@ -5,13 +5,13 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkImageSource.h"
 #include "SkMagnifierImageFilter.h"
 #include "SkPixelRef.h"
 #include "SkRandom.h"
 #include "SkSurface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #define WIDTH 500
 #define HEIGHT 500
@@ -27,12 +27,12 @@
         canvas->saveLayer(nullptr, &filterPaint);
         const char* str = "The quick brown fox jumped over the lazy dog.";
         SkRandom rand;
-        SkFont font(sk_tool_utils::create_portable_typeface());
+        SkFont      font(ToolUtils::create_portable_typeface());
         for (int i = 0; i < 25; ++i) {
             int x = rand.nextULessThan(WIDTH);
             int y = rand.nextULessThan(HEIGHT);
             SkPaint paint;
-            paint.setColor(sk_tool_utils::color_to_565(rand.nextBits(24) | 0xFF000000));
+            paint.setColor(ToolUtils::color_to_565(rand.nextBits(24) | 0xFF000000));
             font.setSize(rand.nextRangeScalar(0, 300));
             canvas->drawString(str, SkIntToScalar(x), SkIntToScalar(y), font, paint);
         }
diff --git a/gm/imagemakewithfilter.cpp b/gm/imagemakewithfilter.cpp
index 9805a0b..ef90f17 100644
--- a/gm/imagemakewithfilter.cpp
+++ b/gm/imagemakewithfilter.cpp
@@ -5,14 +5,14 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkBlurImageFilter.h"
 #include "SkCanvas.h"
 #include "SkColorFilter.h"
 #include "SkColorFilterImageFilter.h"
 #include "SkDropShadowImageFilter.h"
 #include "SkSurface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 ///////////////////////////////////////////////////////////////////////////////
 
@@ -45,7 +45,7 @@
     SkISize onISize() override { return SkISize::Make(440, 530); }
 
     void onDraw(SkCanvas* canvas) override {
-        auto cf = SkColorFilter::MakeModeFilter(SK_ColorGREEN, SkBlendMode::kSrc);
+        auto cf = SkColorFilters::Blend(SK_ColorGREEN, SkBlendMode::kSrc);
         sk_sp<SkImageFilter> filters[] = {
             SkColorFilterImageFilter::Make(std::move(cf), nullptr),
             SkBlurImageFilter::Make(2.0f, 2.0f, nullptr),
@@ -70,8 +70,8 @@
 
         canvas->translate(MARGIN, MARGIN);
 
-        sk_sp<SkSurface> surface = sk_tool_utils::makeSurface(canvas, info);
-        sk_tool_utils::draw_checkerboard(surface->getCanvas());
+        sk_sp<SkSurface> surface = ToolUtils::makeSurface(canvas, info);
+        ToolUtils::draw_checkerboard(surface->getCanvas());
         sk_sp<SkImage> source = surface->makeImageSnapshot();
 
         for (auto clipBound : clipBounds) {
diff --git a/gm/imagemasksubset.cpp b/gm/imagemasksubset.cpp
index adfea6b..6998055 100644
--- a/gm/imagemasksubset.cpp
+++ b/gm/imagemasksubset.cpp
@@ -5,13 +5,13 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkImage.h"
 #include "SkImageGenerator.h"
 #include "SkMakeUnique.h"
 #include "SkSurface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace {
 
@@ -20,7 +20,7 @@
 const SkRect    kDest = SkRect::MakeXYWH(10, 10, 100, 100);
 
 sk_sp<SkImage> make_mask(const sk_sp<SkSurface>& surface) {
-    sk_tool_utils::draw_checkerboard(surface->getCanvas(), 0x80808080, 0x00000000, 5);
+    ToolUtils::draw_checkerboard(surface->getCanvas(), 0x80808080, 0x00000000, 5);
     return surface->makeImageSnapshot();
 }
 
diff --git a/gm/imageresizetiled.cpp b/gm/imageresizetiled.cpp
index c242cc5..f85afd8 100644
--- a/gm/imageresizetiled.cpp
+++ b/gm/imageresizetiled.cpp
@@ -5,10 +5,10 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkImageFilter.h"
 #include "SkRandom.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #define WIDTH 640
 #define HEIGHT 480
@@ -23,7 +23,7 @@
                                                              kNone_SkFilterQuality,
                                                              nullptr));
 
-        SkFont font(sk_tool_utils::create_portable_typeface(), 100);
+        SkFont         font(ToolUtils::create_portable_typeface(), 100);
         const SkScalar tile_size = SkIntToScalar(100);
         for (SkScalar y = 0; y < HEIGHT; y += tile_size) {
             for (SkScalar x = 0; x < WIDTH; x += tile_size) {
diff --git a/gm/imagesource.cpp b/gm/imagesource.cpp
index 50d818b..ea323e6 100644
--- a/gm/imagesource.cpp
+++ b/gm/imagesource.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkImage.h"
 #include "SkImageSource.h"
@@ -36,7 +36,7 @@
     SkISize onISize() override { return SkISize::Make(500, 150); }
 
     void onOnceBeforeDraw() override {
-        SkBitmap bm = sk_tool_utils::create_string_bitmap(100, 100, 0xFFFFFFFF, 20, 70, 96, "e");
+        SkBitmap bm = ToolUtils::create_string_bitmap(100, 100, 0xFFFFFFFF, 20, 70, 96, "e");
         fImage = SkImage::MakeFromBitmap(bm);
     }
 
diff --git a/gm/internal_links.cpp b/gm/internal_links.cpp
index 51d5e20..fc710a8 100644
--- a/gm/internal_links.cpp
+++ b/gm/internal_links.cpp
@@ -4,8 +4,8 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkAnnotation.h"
 #include "SkData.h"
@@ -60,7 +60,7 @@
                                        SkIntToScalar(50), SkIntToScalar(20));
         canvas->drawRect(rect, paint);
 
-        SkFont font(sk_tool_utils::create_portable_typeface(), 25);
+        SkFont font(ToolUtils::create_portable_typeface(), 25);
         paint.setColor(SK_ColorBLACK);
         canvas->drawString(text, x, y, font, paint);
     }
diff --git a/gm/largeglyphblur.cpp b/gm/largeglyphblur.cpp
index c07c246..f1fe1d1 100644
--- a/gm/largeglyphblur.cpp
+++ b/gm/largeglyphblur.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkBlurMask.h"
 #include "SkCanvas.h"
@@ -18,7 +18,7 @@
 DEF_SIMPLE_GM(largeglyphblur, canvas, 1920, 600) {
     const char text[] = "Hamburgefons";
 
-    SkFont font(sk_tool_utils::create_portable_typeface(), 256);
+    SkFont font(ToolUtils::create_portable_typeface(), 256);
     auto blob = SkTextBlob::MakeFromText(text, strlen(text), font);
 
     // setup up maskfilter
diff --git a/gm/lattice.cpp b/gm/lattice.cpp
index 5c2bd50..b56e980 100644
--- a/gm/lattice.cpp
+++ b/gm/lattice.cpp
@@ -5,14 +5,14 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
 #include "SkSurface.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static sk_sp<SkSurface> make_surface(SkCanvas* root, int N, int padLeft, int padTop,
                                      int padRight, int padBottom) {
     SkImageInfo info = SkImageInfo::MakeN32Premul(N + padLeft + padRight, N + padTop + padBottom);
-    return sk_tool_utils::makeSurface(root, info);
+    return ToolUtils::makeSurface(root, info);
 }
 
 static sk_sp<SkImage> make_image(SkCanvas* root, int* xDivs, int* yDivs, int padLeft, int padTop,
@@ -339,7 +339,7 @@
 
 // Code paths that incorporate the paint color when drawing the lattice (using an alpha image)
 DEF_SIMPLE_GM_BG(lattice_alpha, canvas, 120, 120, SK_ColorWHITE) {
-    auto surface = sk_tool_utils::makeSurface(canvas, SkImageInfo::MakeA8(100, 100));
+    auto surface = ToolUtils::makeSurface(canvas, SkImageInfo::MakeA8(100, 100));
     surface->getCanvas()->clear(0);
     surface->getCanvas()->drawCircle(50, 50, 50, SkPaint());
     auto image = surface->makeImageSnapshot();
diff --git a/gm/lcdblendmodes.cpp b/gm/lcdblendmodes.cpp
index 02650f0..d875a00 100644
--- a/gm/lcdblendmodes.cpp
+++ b/gm/lcdblendmodes.cpp
@@ -10,11 +10,11 @@
  * Tests text rendering with LCD and the various blend modes.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkGradientShader.h"
 #include "SkSurface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -32,7 +32,7 @@
         SK_ColorRED, SK_ColorGREEN,
     };
     return SkGradientShader::MakeLinear(pts, colors, nullptr, SK_ARRAY_COUNT(colors),
-                                        SkShader::kRepeat_TileMode);
+                                        SkTileMode::kRepeat);
 }
 
 class LcdBlendGM : public skiagm::GM {
@@ -48,7 +48,7 @@
     }
 
     void onOnceBeforeDraw() override {
-        fCheckerboard = sk_tool_utils::create_checkerboard_shader(SK_ColorBLACK, SK_ColorWHITE, 4);
+        fCheckerboard = ToolUtils::create_checkerboard_shader(SK_ColorBLACK, SK_ColorWHITE, 4);
     }
 
     SkISize onISize() override { return SkISize::Make(kWidth, kHeight); }
@@ -62,7 +62,7 @@
         canvas->drawRect(r, p);
 
         SkImageInfo info = SkImageInfo::MakeN32Premul(kWidth, kHeight);
-        auto surface(sk_tool_utils::makeSurface(canvas, info));
+        auto        surface(ToolUtils::makeSurface(canvas, info));
 
         SkCanvas* surfCanvas = surface->getCanvas();
         this->drawColumn(surfCanvas, SK_ColorBLACK, SK_ColorWHITE, false);
@@ -119,7 +119,7 @@
             SkPaint paint;
             paint.setColor(textColor);
             paint.setBlendMode(gModes[m]);
-            SkFont font(sk_tool_utils::create_portable_typeface(), fTextHeight);
+            SkFont font(ToolUtils::create_portable_typeface(), fTextHeight);
             font.setSubpixel(true);
             font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
             if (useGrad) {
diff --git a/gm/lcdoverlap.cpp b/gm/lcdoverlap.cpp
index 6dd54a0..19e69f1 100644
--- a/gm/lcdoverlap.cpp
+++ b/gm/lcdoverlap.cpp
@@ -10,11 +10,11 @@
  * Tests overlapping LCD text
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkSurface.h"
 #include "SkTextBlob.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -37,12 +37,12 @@
         // build text blob
         SkTextBlobBuilder builder;
 
-        SkFont font(sk_tool_utils::create_portable_typeface(), 32);
+        SkFont      font(ToolUtils::create_portable_typeface(), 32);
         const char* text = "able was I ere I saw elba";
         font.setSubpixel(true);
         font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
         // If we use SkTextBlob::MakeFromText, we get very different positioning ... why?
-        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, 0);
+        ToolUtils::add_to_text_blob(&builder, text, font, 0, 0);
         fBlob = builder.make();
     }
 
diff --git a/gm/lcdtext.cpp b/gm/lcdtext.cpp
index e605ac0..0f7304e 100644
--- a/gm/lcdtext.cpp
+++ b/gm/lcdtext.cpp
@@ -9,14 +9,13 @@
 /* Tests text rendering with LCD and subpixel rendering turned on and off.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkPicture.h"
 #include "SkPictureImageFilter.h"
 #include "SkPictureRecorder.h"
 #include "SkSurface.h"
-
+#include "ToolUtils.h"
+#include "gm.h"
 
 class LcdTextGM : public skiagm::GM {
 public:
@@ -28,9 +27,7 @@
 protected:
 
     SkString onShortName() {
-        SkString name("lcdtext");
-        name.append(sk_tool_utils::platform_font_manager());
-        return name;
+        return SkString("lcdtext");
     }
 
     SkISize onISize() { return SkISize::Make(640, 480); }
diff --git a/gm/lighting.cpp b/gm/lighting.cpp
index f4d83ba..285cab7 100644
--- a/gm/lighting.cpp
+++ b/gm/lighting.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
-#include "SkAnimTimer.h"
+#include "AnimTimer.h"
 #include "SkLightingImageFilter.h"
 #include "SkOffsetImageFilter.h"
 #include "SkPoint3.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #define WIDTH 330
 #define HEIGHT 660
@@ -44,7 +44,7 @@
     }
 
     void onOnceBeforeDraw() override {
-        fBitmap = sk_tool_utils::create_string_bitmap(100, 100, 0xFFFFFFFF, 20, 70, 96, "e");
+        fBitmap = ToolUtils::create_string_bitmap(100, 100, 0xFFFFFFFF, 20, 70, 96, "e");
     }
 
     void onDraw(SkCanvas* canvas) override {
@@ -60,8 +60,8 @@
             canvas->restore();
           }
         }
-        SkScalar cosAzimuth;
-        SkScalar sinAzimuth = SkScalarSinCos(SkDegreesToRadians(fAzimuth), &cosAzimuth);
+        SkScalar sinAzimuth = SkScalarSin(SkDegreesToRadians(fAzimuth)),
+                 cosAzimuth = SkScalarCos(SkDegreesToRadians(fAzimuth));
 
         SkPoint3 spotTarget = SkPoint3::Make(SkIntToScalar(40), SkIntToScalar(40), 0);
         SkPoint3 spotLocation = SkPoint3::Make(spotTarget.fX + 70.7214f * cosAzimuth,
@@ -156,7 +156,7 @@
         }
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         constexpr SkScalar kDesiredDurationSecs = 15.0f;
 
         fAzimuth = kStartAzimuth + timer.scaled(360.0f/kDesiredDurationSecs, 360.0f);
diff --git a/gm/lightingshader.cpp b/gm/lightingshader.cpp
index f7fbe34..a9c4383 100644
--- a/gm/lightingshader.cpp
+++ b/gm/lightingshader.cpp
@@ -5,19 +5,19 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkLightingShader.h"
 #include "SkNormalSource.h"
 #include "SkPoint3.h"
 #include "SkShader.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 // Create a hemispherical normal map
 static SkBitmap make_hemi_normalmap(int texSize) {
     SkBitmap hemi;
     hemi.allocN32Pixels(texSize, texSize);
 
-    sk_tool_utils::create_hemi_normal_map(&hemi, SkIRect::MakeWH(texSize, texSize));
+    ToolUtils::create_hemi_normal_map(&hemi, SkIRect::MakeWH(texSize, texSize));
     return hemi;
 }
 
@@ -26,7 +26,7 @@
     SkBitmap frustum;
     frustum.allocN32Pixels(texSize, texSize);
 
-    sk_tool_utils::create_frustum_normal_map(&frustum, SkIRect::MakeWH(texSize, texSize));
+    ToolUtils::create_frustum_normal_map(&frustum, SkIRect::MakeWH(texSize, texSize));
     return frustum;
 }
 
@@ -35,7 +35,7 @@
     SkBitmap tetra;
     tetra.allocN32Pixels(texSize, texSize);
 
-    sk_tool_utils::create_tetra_normal_map(&tetra, SkIRect::MakeWH(texSize, texSize));
+    ToolUtils::create_tetra_normal_map(&tetra, SkIRect::MakeWH(texSize, texSize));
     return tetra;
 }
 
@@ -78,11 +78,8 @@
             fLights = builder.finish();
         }
 
-        fDiffuse = sk_tool_utils::create_checkerboard_bitmap(
-                                                        kTexSize, kTexSize,
-                                                        0x00000000,
-                                                        sk_tool_utils::color_to_565(0xFF804020),
-                                                        8);
+        fDiffuse = ToolUtils::create_checkerboard_bitmap(
+                kTexSize, kTexSize, 0x00000000, ToolUtils::color_to_565(0xFF804020), 8);
 
         fNormalMaps[kHemi_NormalMap]    = make_hemi_normalmap(kTexSize);
         fNormalMaps[kFrustum_NormalMap] = make_frustum_normalmap(kTexSize);
@@ -99,10 +96,8 @@
         const SkMatrix& ctm = canvas->getTotalMatrix();
 
         SkPaint paint;
-        sk_sp<SkShader> diffuseShader = SkShader::MakeBitmapShader(fDiffuse,
-                SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, &matrix);
-        sk_sp<SkShader> normalMap = SkShader::MakeBitmapShader(fNormalMaps[mapType],
-                SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, &matrix);
+        sk_sp<SkShader> diffuseShader = fDiffuse.makeShader(&matrix);
+        sk_sp<SkShader> normalMap = fNormalMaps[mapType].makeShader(&matrix);
         sk_sp<SkNormalSource> normalSource = SkNormalSource::MakeFromNormalMap(std::move(normalMap),
                                                                                ctm);
         paint.setShader(SkLightingShader::Make(std::move(diffuseShader), std::move(normalSource),
diff --git a/gm/lightingshader2.cpp b/gm/lightingshader2.cpp
index 62d9c2a..883fbbf 100644
--- a/gm/lightingshader2.cpp
+++ b/gm/lightingshader2.cpp
@@ -5,20 +5,20 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkLightingShader.h"
 #include "SkNormalSource.h"
 #include "SkPoint3.h"
 #include "SkShader.h"
 #include "SkTypeface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 // Create a truncated pyramid normal map
 static SkBitmap make_frustum_normalmap(int texSize) {
     SkBitmap frustum;
     frustum.allocN32Pixels(texSize, texSize);
 
-    sk_tool_utils::create_frustum_normal_map(&frustum, SkIRect::MakeWH(texSize, texSize));
+    ToolUtils::create_frustum_normal_map(&frustum, SkIRect::MakeWH(texSize, texSize));
     return frustum;
 }
 
@@ -29,7 +29,7 @@
 class LightingShader2GM : public GM {
 public:
     LightingShader2GM() : fRect(SkRect::MakeIWH(kTexSize, kTexSize)) {
-        this->setBGColor(sk_tool_utils::color_to_565(0xFF0000CC));
+        this->setBGColor(ToolUtils::color_to_565(0xFF0000CC));
     }
 
 protected:
@@ -77,25 +77,20 @@
         SkRect bitmapBounds = SkRect::MakeIWH(kTexSize, kTexSize);
         matrix.setRectToRect(bitmapBounds, fRect, SkMatrix::kFill_ScaleToFit);
 
-        SkBitmap opaqueDiffuseMap = sk_tool_utils::create_checkerboard_bitmap(
-                kTexSize, kTexSize, SK_ColorBLACK,
-                0xFF808080,
-                8);
-        fOpaqueDiffuse = SkShader::MakeBitmapShader(opaqueDiffuseMap, SkShader::kClamp_TileMode,
-                                                    SkShader::kClamp_TileMode, &matrix);
+        SkBitmap opaqueDiffuseMap = ToolUtils::create_checkerboard_bitmap(
+                kTexSize, kTexSize, SK_ColorBLACK, 0xFF808080, 8);
+        fOpaqueDiffuse = opaqueDiffuseMap.makeShader(&matrix);
 
-        SkBitmap translucentDiffuseMap = sk_tool_utils::create_checkerboard_bitmap(
-                kTexSize, kTexSize,
-                SkColorSetARGB(0x55, 0x00, 0x00, 0x00),
-                SkColorSetARGB(0x55, 0x80, 0x80, 0x80),
-                8);
-        fTranslucentDiffuse = SkShader::MakeBitmapShader(translucentDiffuseMap,
-                                                         SkShader::kClamp_TileMode,
-                                                         SkShader::kClamp_TileMode, &matrix);
+        SkBitmap translucentDiffuseMap =
+                ToolUtils::create_checkerboard_bitmap(kTexSize,
+                                                      kTexSize,
+                                                      SkColorSetARGB(0x55, 0x00, 0x00, 0x00),
+                                                      SkColorSetARGB(0x55, 0x80, 0x80, 0x80),
+                                                      8);
+        fTranslucentDiffuse = translucentDiffuseMap.makeShader(&matrix);
 
         SkBitmap normalMap = make_frustum_normalmap(kTexSize);
-        fNormalMapShader = SkShader::MakeBitmapShader(normalMap, SkShader::kClamp_TileMode,
-                                                      SkShader::kClamp_TileMode, &matrix);
+        fNormalMapShader = normalMap.makeShader(&matrix);
 
     }
 
@@ -143,8 +138,7 @@
 
     void onDraw(SkCanvas* canvas) override {
         SkPaint labelPaint;
-        SkFont font(
-                sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle()), kLabelSize);
+        SkFont  font(ToolUtils::create_portable_typeface("sans-serif", SkFontStyle()), kLabelSize);
 
         int gridNum = 0;
 
diff --git a/gm/linepaths.cpp b/gm/linepaths.cpp
index e3f386d..f6424a7 100644
--- a/gm/linepaths.cpp
+++ b/gm/linepaths.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
-#include "SkPath.h"
 #include "SkPaint.h"
+#include "SkPath.h"
 #include "SkRandom.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static void drawPath(SkPath& path,SkCanvas* canvas,SkColor color,
                      const SkRect& clip,SkPaint::Cap cap, SkPaint::Join join,
@@ -77,7 +77,7 @@
         titlePaint.setColor(SK_ColorBLACK);
         titlePaint.setAntiAlias(true);
 
-        SkFont font(sk_tool_utils::create_portable_typeface(), 15.0f);
+        SkFont font(ToolUtils::create_portable_typeface(), 15.0f);
 
         const char titleNoClose[] = "Line Drawn Into Rectangle Clips With "
             "Indicated Style, Fill and Linecaps, with stroke width 10";
@@ -106,7 +106,7 @@
                         canvas->translate(rect.width() + 40 * SK_Scalar1, 0);
                     }
 
-                    SkColor color = sk_tool_utils::color_to_565(0xff007000);
+                    SkColor color = ToolUtils::color_to_565(0xff007000);
                     drawPath(path.fPath, canvas, color, rect,
                                     gCaps[cap].fCap, gCaps[cap].fJoin, gStyles[style].fStyle,
                                     gFills[fill].fFill, SK_Scalar1*10);
diff --git a/gm/localmatriximagefilter.cpp b/gm/localmatriximagefilter.cpp
index 8280c07..4bb433c 100644
--- a/gm/localmatriximagefilter.cpp
+++ b/gm/localmatriximagefilter.cpp
@@ -5,19 +5,19 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "SkCanvas.h"
 #include "SkBlurImageFilter.h"
+#include "SkCanvas.h"
 #include "SkColorFilterImageFilter.h"
 #include "SkModeColorFilter.h"
 #include "SkMorphologyImageFilter.h"
 #include "SkOffsetImageFilter.h"
 #include "SkSurface.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static sk_sp<SkImage> make_image(SkCanvas* rootCanvas) {
     SkImageInfo info = SkImageInfo::MakeN32Premul(100, 100);
-    auto surface(sk_tool_utils::makeSurface(rootCanvas, info));
+    auto        surface(ToolUtils::makeSurface(rootCanvas, info));
 
     SkPaint paint;
     paint.setAntiAlias(true);
diff --git a/gm/localmatriximageshader.cpp b/gm/localmatriximageshader.cpp
index b041c70..fc5f0f2 100644
--- a/gm/localmatriximageshader.cpp
+++ b/gm/localmatriximageshader.cpp
@@ -5,15 +5,15 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
 #include "Resources.h"
 #include "SkCanvas.h"
 #include "SkSurface.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static sk_sp<SkImage> make_image(SkCanvas* rootCanvas, SkColor color) {
     SkImageInfo info = SkImageInfo::MakeN32Premul(100, 100);
-    auto surface(sk_tool_utils::makeSurface(rootCanvas, info));
+    auto        surface(ToolUtils::makeSurface(rootCanvas, info));
 
     SkPaint paint;
     paint.setAntiAlias(true);
@@ -46,7 +46,7 @@
     canvas->translate(100.0f, 0.0f);
 
     // Use isAImage() and confirm that the shaders will draw exactly the same (to the right by 100).
-    SkShader::TileMode mode[2];
+    SkTileMode mode[2];
     SkMatrix matrix;
     SkImage* image = redLocalMatrixShader->isAImage(&matrix, mode);
     paint.setShader(image->makeShader(mode[0], mode[1], &matrix));
diff --git a/gm/localmatrixshader.cpp b/gm/localmatrixshader.cpp
index bbd2775..ebb6997 100644
--- a/gm/localmatrixshader.cpp
+++ b/gm/localmatrixshader.cpp
@@ -5,16 +5,16 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
 #include "SkCanvas.h"
 #include "SkShader.h"
 #include "SkSurface.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static sk_sp<SkImage> make_image(SkCanvas* rootCanvas) {
     static constexpr SkScalar kSize = 50;
     SkImageInfo info = SkImageInfo::MakeN32Premul(kSize, kSize);
-    auto surface = sk_tool_utils::makeSurface(rootCanvas, info);
+    auto                      surface = ToolUtils::makeSurface(rootCanvas, info);
 
     SkPaint p;
     p.setAntiAlias(true);
@@ -49,17 +49,17 @@
 
         // SkLocalMatrixShader(SkComposeShader(SkImageShader(inner)), outer)
         [](const sk_sp<SkImage>& img, const SkMatrix& inner, const SkMatrix& outer) {
-            return SkShader::MakeCompose(SkShader::MakeColorShader(SK_ColorTRANSPARENT),
-                                         img->makeShader(&inner),
-                                         SkBlendMode::kSrcOver)
+            return SkShaders::Blend(SkBlendMode::kSrcOver,
+                                    SkShaders::Color(SK_ColorTRANSPARENT),
+                                    img->makeShader(&inner))
                    ->makeWithLocalMatrix(outer);
         },
 
         // SkLocalMatrixShader(SkComposeShader(SkLocalMatrixShader(SkImageShader(I), inner)), outer)
         [](const sk_sp<SkImage>& img, const SkMatrix& inner, const SkMatrix& outer) {
-            return SkShader::MakeCompose(SkShader::MakeColorShader(SK_ColorTRANSPARENT),
-                                         img->makeShader()->makeWithLocalMatrix(inner),
-                                         SkBlendMode::kSrcOver)
+            return SkShaders::Blend(SkBlendMode::kSrcOver,
+                                    SkShaders::Color(SK_ColorTRANSPARENT),
+                                    img->makeShader()->makeWithLocalMatrix(inner))
                    ->makeWithLocalMatrix(outer);
         },
     };
diff --git a/gm/lumafilter.cpp b/gm/lumafilter.cpp
index 82cb1a0..441986a 100644
--- a/gm/lumafilter.cpp
+++ b/gm/lumafilter.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkBlendModePriv.h"
 #include "SkCanvas.h"
 #include "SkGradientShader.h"
 #include "SkLumaColorFilter.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static SkScalar kSize   = 80;
 static SkScalar kInset  = 10;
@@ -19,7 +19,7 @@
 
 static void draw_label(SkCanvas* canvas, const char* label,
                        const SkPoint& offset) {
-    SkFont font(sk_tool_utils::create_portable_typeface());
+    SkFont font(ToolUtils::create_portable_typeface());
     font.setEdging(SkFont::Edging::kAlias);
 
     size_t len = strlen(label);
@@ -88,9 +88,9 @@
 
         fFilter = SkLumaColorFilter::Make();
         fGr1 = SkGradientShader::MakeLinear(g1Points, g1Colors, pos, SK_ARRAY_COUNT(g1Colors),
-                                            SkShader::kClamp_TileMode);
+                                            SkTileMode::kClamp);
         fGr2 = SkGradientShader::MakeLinear(g2Points, g2Colors, pos, SK_ARRAY_COUNT(g2Colors),
-                                            SkShader::kClamp_TileMode);
+                                            SkTileMode::kClamp);
     }
 
 protected:
diff --git a/gm/mac_aa_explorer.cpp b/gm/mac_aa_explorer.cpp
new file mode 100644
index 0000000..463aa72
--- /dev/null
+++ b/gm/mac_aa_explorer.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkFont.h"
+#include "SkSurface.h"
+
+#ifdef SK_BUILD_FOR_MAC
+
+#import <ApplicationServices/ApplicationServices.h>
+
+static void std_cg_setup(CGContextRef ctx) {
+    CGContextSetAllowsFontSubpixelQuantization(ctx, false);
+    CGContextSetShouldSubpixelQuantizeFonts(ctx, false);
+
+    // Because CG always draws from the horizontal baseline,
+    // if there is a non-integral translation from the horizontal origin to the vertical origin,
+    // then CG cannot draw the glyph in the correct location without subpixel positioning.
+    CGContextSetAllowsFontSubpixelPositioning(ctx, true);
+    CGContextSetShouldSubpixelPositionFonts(ctx, true);
+
+    CGContextSetAllowsFontSmoothing(ctx, true);
+    CGContextSetShouldAntialias(ctx, true);
+
+    CGContextSetTextDrawingMode(ctx, kCGTextFill);
+
+    // Draw black on white to create mask. (Special path exists to speed this up in CG.)
+    CGContextSetGrayFillColor(ctx, 0.0f, 1.0f);
+}
+
+static CGContextRef make_cg_ctx(const SkPixmap& pm) {
+    CGBitmapInfo info;
+    CGColorSpaceRef cs;
+
+    switch (pm.colorType()) {
+        case kRGBA_8888_SkColorType:
+            info = kCGBitmapByteOrder32Host | kCGImageAlphaNoneSkipFirst;
+            cs = CGColorSpaceCreateDeviceRGB();
+            break;
+        case kGray_8_SkColorType:
+            info = kCGImageAlphaNone;
+            cs = CGColorSpaceCreateDeviceGray();
+            break;
+        case kAlpha_8_SkColorType:
+            info = kCGImageAlphaOnly;
+            cs = nullptr;
+            break;
+        default:
+            return nullptr;
+    }
+    auto ctx = CGBitmapContextCreate(pm.writable_addr(), pm.width(), pm.height(), 8, pm.rowBytes(),
+                                     cs, info);
+    std_cg_setup(ctx);
+    return ctx;
+}
+
+static void test_mac_fonts(SkCanvas* canvas, SkScalar size, SkScalar xpos) {
+    int w = 32;
+    int h = 32;
+
+    canvas->scale(10, 10);
+    SkScalar y = 1;
+
+    for (SkColorType ct : {kRGBA_8888_SkColorType, kGray_8_SkColorType, kAlpha_8_SkColorType}) {
+        SkImageInfo ii = SkImageInfo::Make(w, h, ct, kPremul_SkAlphaType);
+        auto surf = SkSurface::MakeRaster(ii);
+        SkPixmap pm;
+        surf->peekPixels(&pm);
+        CGContextRef ctx = make_cg_ctx(pm);
+        CGContextSelectFont(ctx, "Times", size, kCGEncodingMacRoman);
+
+        SkScalar x = 1;
+        for (bool smooth : {false, true}) {
+            surf->getCanvas()->clear(ct == kAlpha_8_SkColorType ? 0 : 0xFFFFFFFF);
+            CGContextSetShouldSmoothFonts(ctx, smooth);
+            CGContextShowTextAtPoint(ctx, 2 + xpos, 2, "A", 1);
+
+            surf->draw(canvas, x, y, nullptr);
+            x += pm.width();
+        }
+        y += pm.height();
+    }
+}
+
+class MacAAFontsGM : public skiagm::GM {
+    SkScalar fSize = 16;
+    SkScalar fXPos = 0;
+
+public:
+    MacAAFontsGM() {}
+    ~MacAAFontsGM() override {}
+
+protected:
+    DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
+        test_mac_fonts(canvas, fSize, fXPos);
+
+        return DrawResult::kOk;
+    }
+
+    SkISize onISize() override { return { 1024, 768 }; }
+
+    SkString onShortName() override { return SkString("macaatest"); }
+
+    bool onHandleKey(SkUnichar uni) override {
+        switch (uni) {
+            case 'i': fSize += 1; return true;
+            case 'k': fSize -= 1; return true;
+            case 'j': fXPos -= 1.0f/16; return true;
+            case 'l': fXPos += 1.0f/16; return true;
+            default: break;
+        }
+        return false;
+    }
+};
+DEF_GM(return new MacAAFontsGM;)
+
+#endif
+
+DEF_SIMPLE_GM(macaa_colors, canvas, 800, 500) {
+    const SkColor GRAY = 0xFF808080;
+    const SkColor colors[] = {
+        SK_ColorBLACK, SK_ColorWHITE,
+        SK_ColorBLACK, GRAY,
+        SK_ColorWHITE, SK_ColorBLACK,
+        SK_ColorWHITE, GRAY,
+    };
+    const SkScalar sizes[] = {10, 12, 15, 18, 24};
+
+    const SkScalar width = 200;
+    const SkScalar height = 500;
+    const char str[] = "Hamburgefons";
+    const size_t len = strlen(str);
+
+    SkFont font;
+    font.setTypeface(SkTypeface::MakeFromName("Times", SkFontStyle()));
+
+    for (size_t i = 0; i < SK_ARRAY_COUNT(colors); i += 2) {
+        canvas->save();
+
+        SkPaint paint;
+        paint.setColor(colors[i+1]);
+        canvas->drawRect({0, 0, width, height}, paint);
+        paint.setColor(colors[i]);
+
+        SkScalar y = 10;
+        SkScalar x = 10;
+        for (SkScalar ps : sizes) {
+            font.setSize(ps);
+            for (bool lcd : {false, true}) {
+                font.setEdging(lcd ? SkFont::Edging::kSubpixelAntiAlias
+                                   : SkFont::Edging::kAntiAlias);
+                for (auto h : {kNo_SkFontHinting, kNormal_SkFontHinting}) {
+                    font.setHinting(h);
+
+                    y += font.getSpacing() + 2;
+                    canvas->drawSimpleText(str, len, kUTF8_SkTextEncoding, x, y, font, paint);
+                }
+            }
+            y += 8;
+        }
+        canvas->restore();
+        canvas->translate(width, 0);
+    }
+}
diff --git a/gm/makecolorspace.cpp b/gm/makecolorspace.cpp
index 39afc69..3b8a5ae 100644
--- a/gm/makecolorspace.cpp
+++ b/gm/makecolorspace.cpp
@@ -48,7 +48,7 @@
         return SkISize::Make(128*3, 128*4);
     }
 
-    void onDraw(SkCanvas* canvas) override {
+    DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
         sk_sp<SkColorSpace> wideGamut = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB,
                                                               SkNamedGamut::kAdobeRGB);
         sk_sp<SkColorSpace> wideGamutLinear = wideGamut->makeLinearGamma();
@@ -57,9 +57,8 @@
         sk_sp<SkImage> opaqueImage = GetResourceAsImage("images/mandrill_128.png");
         sk_sp<SkImage> premulImage = GetResourceAsImage("images/color_wheel.png");
         if (!opaqueImage || !premulImage) {
-            DrawFailureMessage(canvas, "Failed to load images. "
-                                       "Did you forget to set the resourcePath?");
-            return;
+            *errorMsg = "Failed to load images. Did you forget to set the resourcePath?";
+            return DrawResult::kFail;
         }
         canvas->drawImage(opaqueImage, 0.0f, 0.0f);
         canvas->drawImage(make_color_space(opaqueImage, wideGamut), 128.0f, 0.0f);
@@ -78,6 +77,7 @@
         canvas->drawImage(premulImage, 0.0f, 128.0f);
         canvas->drawImage(make_color_space(premulImage, wideGamut), 128.0f, 128.0f);
         canvas->drawImage(make_color_space(premulImage, wideGamutLinear), 256.0f, 128.0f);
+        return DrawResult::kOk;
     }
 
 private:
diff --git a/gm/manypaths.cpp b/gm/manypaths.cpp
index 0d8c842..00bdfad 100644
--- a/gm/manypaths.cpp
+++ b/gm/manypaths.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
+#include "SkRRect.h"
 #include "SkRandom.h"
 #include "SkRect.h"
-#include "SkRRect.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -19,7 +19,7 @@
     hsv[1] = rand->nextRangeF(0.5f, 1.0f);
     hsv[2] = rand->nextRangeF(0.5f, 1.0f);
 
-    return sk_tool_utils::color_to_565(SkHSVToColor(hsv));
+    return ToolUtils::color_to_565(SkHSVToColor(hsv));
 }
 
 class ManyCirclesGM : public GM {
diff --git a/gm/matrixconvolution.cpp b/gm/matrixconvolution.cpp
index 24fa578..a729e3f 100644
--- a/gm/matrixconvolution.cpp
+++ b/gm/matrixconvolution.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkColor.h"
 #include "SkGradientShader.h"
 #include "SkMatrixConvolutionImageFilter.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -39,8 +39,8 @@
                            {0, 80.0f} };
         SkScalar pos[2] = { 0, 80.0f };
         paint.setShader(SkGradientShader::MakeLinear(
-            pts, fColors, pos, 2, SkShader::kClamp_TileMode));
-        SkFont font(sk_tool_utils::create_portable_typeface(), 180.0f);
+            pts, fColors, pos, 2, SkTileMode::kClamp));
+        SkFont font(ToolUtils::create_portable_typeface(), 180.0f);
         canvas.drawString("e", -10.0f, 80.0f, font, paint);
     }
 
diff --git a/gm/megalooper.cpp b/gm/megalooper.cpp
index e2e53e7..640fcfe 100644
--- a/gm/megalooper.cpp
+++ b/gm/megalooper.cpp
@@ -174,7 +174,7 @@
 
         paint->setMaskFilter(MakeBlur());
 
-        paint->setColorFilter(SkColorFilter::MakeModeFilter(color, SkBlendMode::kSrcIn));
+        paint->setColorFilter(SkColorFilters::Blend(color, SkBlendMode::kSrcIn));
 
         return looperBuilder.detach();
     }
@@ -220,7 +220,7 @@
 
             paint->setMaskFilter(MakeBlur());
 
-            paint->setColorFilter(SkColorFilter::MakeModeFilter(gColors[i], SkBlendMode::kSrcIn));
+            paint->setColorFilter(SkColorFilters::Blend(gColors[i], SkBlendMode::kSrcIn));
         }
 
         return looperBuilder.detach();
diff --git a/gm/mixedtextblobs.cpp b/gm/mixedtextblobs.cpp
index 9e83b90..788a98a 100644
--- a/gm/mixedtextblobs.cpp
+++ b/gm/mixedtextblobs.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "Resources.h"
 #include "SkCanvas.h"
@@ -26,10 +26,10 @@
     SkPaint paint(skPaint);
     canvas->save();
     canvas->drawRect(clipRect, clipHairline);
-    paint.setAlpha(0x20);
+    paint.setAlphaf(0.125f);
     canvas->drawTextBlob(blob, 0, 0, paint);
     canvas->clipRect(clipRect);
-    paint.setAlpha(0xFF);
+    paint.setAlphaf(1.0f);
     canvas->drawTextBlob(blob, 0, 0, paint);
     canvas->restore();
 }
@@ -40,15 +40,15 @@
 
 protected:
     void onOnceBeforeDraw() override {
-        fEmojiTypeface = sk_tool_utils::emoji_typeface();
-        fEmojiText = sk_tool_utils::emoji_sample_text();
+        fEmojiTypeface      = ToolUtils::planet_typeface();
+        fEmojiText = "♁♃";
         fReallyBigATypeface = MakeResourceAsTypeface("fonts/ReallyBigA.ttf");
 
         SkTextBlobBuilder builder;
 
         // make textblob
         // Text so large we draw as paths
-        SkFont font(sk_tool_utils::create_portable_typeface(), 385);
+        SkFont font(ToolUtils::create_portable_typeface(), 385);
         font.setEdging(SkFont::Edging::kAlias);
         const char* text = "O";
 
@@ -56,7 +56,7 @@
         font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
 
         SkScalar yOffset = bounds.height();
-        sk_tool_utils::add_to_text_blob(&builder, text, font, 10, yOffset);
+        ToolUtils::add_to_text_blob(&builder, text, font, 10, yOffset);
         SkScalar corruptedAx = bounds.width();
         SkScalar corruptedAy = yOffset;
 
@@ -72,32 +72,31 @@
         font.setSubpixel(true);
         text = "LCD!!!!!";
         font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
-        sk_tool_utils::add_to_text_blob(&builder, text, font, xOffset - bounds.width() * 0.25f,
-                                        yOffset - bounds.height() * 0.5f);
-        yOffset += bounds.height();
+        ToolUtils::add_to_text_blob(&builder,
+                                    text,
+                                    font,
+                                    xOffset - bounds.width() * 0.25f,
+                                    yOffset - bounds.height() * 0.5f);
 
-        // color emoji
+        // color emoji font with large glyph
         if (fEmojiTypeface) {
             font.setEdging(SkFont::Edging::kAlias);
             font.setSubpixel(false);
             font.setTypeface(fEmojiTypeface);
-            text = fEmojiText;
-            font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
-            sk_tool_utils::add_to_text_blob(&builder, text, font, xOffset - bounds.width() * 0.3f,
-                                            yOffset);
+            font.measureText(fEmojiText, strlen(fEmojiText), kUTF8_SkTextEncoding, &bounds);
+            ToolUtils::add_to_text_blob(&builder, fEmojiText, font, xOffset, yOffset);
         }
 
-        // Corrupted font
+        // outline font with large glyph
         font.setSize(12);
         text = "aA";
         font.setTypeface(fReallyBigATypeface);
-        sk_tool_utils::add_to_text_blob(&builder, text, font, corruptedAx, corruptedAy);
+        ToolUtils::add_to_text_blob(&builder, text, font, corruptedAx, corruptedAy);
         fBlob = builder.make();
     }
 
     SkString onShortName() override {
-        return SkStringPrintf("mixedtextblobs%s",
-                              sk_tool_utils::platform_font_manager());
+        return SkString("mixedtextblobs");
     }
 
     SkISize onISize() override {
diff --git a/gm/mixercolorfilter.cpp b/gm/mixercolorfilter.cpp
new file mode 100644
index 0000000..63fef70
--- /dev/null
+++ b/gm/mixercolorfilter.cpp
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+
+#include "AnimTimer.h"
+#include "SkCanvas.h"
+#include "SkColorFilter.h"
+#include "SkGradientShader.h"
+#include "SkLumaColorFilter.h"
+#include "SkTableColorFilter.h"
+
+// A tint filter maps colors to a given range (gradient), based on the input luminance:
+//
+//   c' = lerp(lo, hi, luma(c))
+//
+// TODO: move to public headers/API?
+//
+static sk_sp<SkColorFilter> MakeTintColorFilter(SkColor lo, SkColor hi) {
+    const auto r_lo = SkColorGetR(lo),
+    g_lo = SkColorGetG(lo),
+    b_lo = SkColorGetB(lo),
+    a_lo = SkColorGetA(lo),
+    r_hi = SkColorGetR(hi),
+    g_hi = SkColorGetG(hi),
+    b_hi = SkColorGetB(hi),
+    a_hi = SkColorGetA(hi);
+
+    // We map component-wise:
+    //
+    //   r' = lo.r + (hi.r - lo.r) * luma
+    //   g' = lo.g + (hi.g - lo.g) * luma
+    //   b' = lo.b + (hi.b - lo.b) * luma
+    //   a' = lo.a + (hi.a - lo.a) * luma
+    //
+    // The input luminance is stored in the alpha channel
+    // (and RGB are cleared -- see SkLumaColorFilter). Thus:
+    const SkScalar tint_matrix[] = {
+        0, 0, 0, (r_hi - r_lo) / 255.0f, SkIntToScalar(r_lo),
+        0, 0, 0, (g_hi - g_lo) / 255.0f, SkIntToScalar(g_lo),
+        0, 0, 0, (b_hi - b_lo) / 255.0f, SkIntToScalar(b_lo),
+        0, 0, 0, (a_hi - a_lo) / 255.0f, SkIntToScalar(a_lo),
+    };
+
+    return SkColorFilters::MatrixRowMajor255(tint_matrix)
+    ->makeComposed(SkLumaColorFilter::Make());
+}
+
+namespace {
+
+class MixerCFGM final : public skiagm::GM {
+public:
+    MixerCFGM(const SkSize& tileSize, size_t tileCount)
+        : fTileSize(tileSize)
+        , fTileCount(tileCount) {}
+
+protected:
+    SkString onShortName() override {
+        return SkString("mixerCF");
+    }
+
+    SkISize onISize() override {
+        return SkISize::Make(fTileSize.width()  * 1.2f * fTileCount,
+                             fTileSize.height() * 1.2f * 3);         // 3 rows
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        SkPaint paint;
+
+        const SkColor gradient_colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorRED };
+        paint.setShader(SkGradientShader::MakeSweep(fTileSize.width()  / 2,
+                                                    fTileSize.height() / 2,
+                                                    gradient_colors, nullptr,
+                                                    SK_ARRAY_COUNT(gradient_colors)));
+
+        auto cf0 = MakeTintColorFilter(0xff300000, 0xffa00000);  // red tint
+        auto cf1 = MakeTintColorFilter(0xff003000, 0xff00a000);  // green tint
+
+        this->mixRow(canvas, paint, nullptr,     cf1);
+        this->mixRow(canvas, paint,     cf0, nullptr);
+        this->mixRow(canvas, paint,     cf0,     cf1);
+    }
+
+private:
+    const SkSize fTileSize;
+    const size_t fTileCount;
+
+    void mixRow(SkCanvas* canvas, SkPaint& paint,
+                sk_sp<SkColorFilter> cf0, sk_sp<SkColorFilter> cf1) {
+        canvas->translate(0, fTileSize.height() * 0.1f);
+        {
+            SkAutoCanvasRestore arc(canvas, true);
+            for (size_t i = 0; i < fTileCount; ++i) {
+                paint.setColorFilter(
+                    SkColorFilters::Lerp(static_cast<float>(i) / (fTileCount - 1), cf0, cf1));
+                canvas->translate(fTileSize.width() * 0.1f, 0);
+                canvas->drawRect(SkRect::MakeWH(fTileSize.width(), fTileSize.height()), paint);
+                canvas->translate(fTileSize.width() * 1.1f, 0);
+            }
+        }
+        canvas->translate(0, fTileSize.height() * 1.1f);
+    }
+
+    using INHERITED = skiagm::GM;
+};
+
+} // namespace
+DEF_GM( return new MixerCFGM(SkSize::Make(200, 250), 5); )
+
+#include "Resources.h"
+
+static sk_sp<SkShader> make_resource_shader(const char path[], int size) {
+    auto img = GetResourceAsImage(path);
+    if (!img) {
+        return nullptr;
+    }
+    SkRect src = SkRect::MakeIWH(img->width(), img->height());
+    SkRect dst = SkRect::MakeIWH(size, size);
+    SkMatrix m;
+    m.setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
+    return img->makeShader(&m);
+}
+
+static sk_sp<SkShader> make_grad(int size, float t) {
+    SkASSERT(t >= 0 && t <= 1);
+    unsigned r = SkScalarRoundToInt(t * 255);
+    SkColor c = SkColorSetARGB(r, r, 0, 0);
+
+    SkColor colors[] = { 0, c, SK_ColorRED };
+    SkPoint pts[] = {{0, 0}, {size*1.0f, size*1.0f}};
+    SkScalar pos[] = {0, 1 - t, 1.0f};
+    return SkGradientShader::MakeLinear(pts, colors, pos, SK_ARRAY_COUNT(colors),
+                                        SkTileMode::kClamp);
+}
+
+class ShaderMixerGM final : public skiagm::GM {
+    enum { SIZE = 256 };
+    float fPos = 0.5f;
+    sk_sp<SkShader> fS0, fS1;
+
+public:
+    ShaderMixerGM() {}
+
+protected:
+    SkString onShortName() override {
+        return SkString("mixershader_shadermixer");
+    }
+
+    void onOnceBeforeDraw() override {
+        fS0 = make_resource_shader("images/mandrill_256.png", SIZE);
+        fS1 = make_resource_shader("images/baby_tux.png", SIZE);
+    }
+
+    SkISize onISize() override { return {542, 542}; }
+
+    void onDraw(SkCanvas* canvas) override {
+        SkRect r = SkRect::MakeIWH(SIZE, SIZE);
+
+        SkPaint paint;
+
+        canvas->translate(10, 10);
+
+        canvas->save();
+        paint.setShader(fS0);
+        canvas->drawRect(r, paint);
+        canvas->translate(SIZE + 10.0f, 0);
+        paint.setShader(fS1);
+        canvas->drawRect(r, paint);
+        canvas->restore();
+
+        auto sh2 = make_grad(SIZE, fPos);
+
+        canvas->translate(0, SIZE + 10.0f);
+        paint.setShader(sh2);
+        canvas->drawRect(r, paint);
+
+        auto sh = SkShaders::Lerp(sh2, fS0, fS1);
+        canvas->translate(SIZE + 10.0f, 0);
+        paint.setShader(sh);
+        canvas->drawRect(r, paint);
+    }
+
+    bool onAnimate(const AnimTimer& timer) override {
+        fPos = (sin(timer.secs()) + 1) * 0.5f;
+        return true;
+    }
+
+private:
+    using INHERITED = skiagm::GM;
+};
+DEF_GM( return new ShaderMixerGM; )
diff --git a/gm/modecolorfilters.cpp b/gm/modecolorfilters.cpp
index 6f933fe..3950f1a 100644
--- a/gm/modecolorfilters.cpp
+++ b/gm/modecolorfilters.cpp
@@ -5,10 +5,10 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkColorFilter.h"
 #include "SkGradientShader.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #define WIDTH 512
 #define HEIGHT 1024
@@ -20,7 +20,7 @@
     constexpr SkPoint kPts[] = {{0, 0}, {1, 1}};
     SkColor colors[] = {color, color};
 
-    return SkGradientShader::MakeLinear(kPts, colors, nullptr, 2, SkShader::kClamp_TileMode);
+    return SkGradientShader::MakeLinear(kPts, colors, nullptr, 2, SkTileMode::kClamp);
 }
 
 static sk_sp<SkShader> make_solid_shader() {
@@ -40,16 +40,16 @@
     SkBitmap bmp;
     bmp.allocN32Pixels(2 * checkSize, 2 * checkSize);
     SkCanvas canvas(bmp);
-    canvas.clear(sk_tool_utils::color_to_565(0xFF800000));
+    canvas.clear(ToolUtils::color_to_565(0xFF800000));
     SkPaint paint;
-    paint.setColor(sk_tool_utils::color_to_565(0xFF000080));
+    paint.setColor(ToolUtils::color_to_565(0xFF000080));
     SkRect rect0 = SkRect::MakeXYWH(0, 0,
                                     SkIntToScalar(checkSize), SkIntToScalar(checkSize));
     SkRect rect1 = SkRect::MakeXYWH(SkIntToScalar(checkSize), SkIntToScalar(checkSize),
                                     SkIntToScalar(checkSize), SkIntToScalar(checkSize));
     canvas.drawRect(rect1, paint);
     canvas.drawRect(rect0, paint);
-    return SkShader::MakeBitmapShader(bmp, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode);
+    return bmp.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat);
 }
 
 class ModeColorFilterGM : public GM {
@@ -122,7 +122,7 @@
         const int kRectsPerRow = SkMax32(this->getISize().fWidth / kRectWidth, 1);
         for (size_t cfm = 0; cfm < SK_ARRAY_COUNT(modes); ++cfm) {
             for (size_t cfc = 0; cfc < SK_ARRAY_COUNT(colors); ++cfc) {
-                paint.setColorFilter(SkColorFilter::MakeModeFilter(colors[cfc], modes[cfm]));
+                paint.setColorFilter(SkColorFilters::Blend(colors[cfc], modes[cfm]));
                 for (size_t s = 0; s < SK_ARRAY_COUNT(shaders); ++s) {
                     paint.setShader(shaders[s]);
                     bool hasShader = nullptr == paint.getShader();
diff --git a/gm/morphology.cpp b/gm/morphology.cpp
index d21053a..063508c 100644
--- a/gm/morphology.cpp
+++ b/gm/morphology.cpp
@@ -5,9 +5,9 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkMorphologyImageFilter.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #define WIDTH 700
 #define HEIGHT 560
@@ -30,7 +30,7 @@
         SkCanvas canvas(fBitmap);
         canvas.clear(0x0);
 
-        SkFont font(sk_tool_utils::create_portable_typeface(), 64.0f);
+        SkFont  font(ToolUtils::create_portable_typeface(), 64.0f);
         SkPaint paint;
         paint.setColor(0xFFFFFFFF);
         canvas.drawString("ABC", 10, 55,  font, paint);
diff --git a/gm/multipicturedraw.cpp b/gm/multipicturedraw.cpp
index 3879fe7..cba275f 100644
--- a/gm/multipicturedraw.cpp
+++ b/gm/multipicturedraw.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkColorFilter.h"
 #include "SkMultiPictureDraw.h"
@@ -241,7 +241,7 @@
 static sk_sp<SkSurface> create_compat_surface(SkCanvas* canvas, int width, int height) {
     SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
 
-    return sk_tool_utils::makeSurface(canvas, info);
+    return ToolUtils::makeSurface(canvas, info);
 }
 
 // This class stores the information required to compose all the result
@@ -409,7 +409,7 @@
             step.fY = SkIntToScalar(y*kTileHeight);
             step.fPaint = new SkPaint;
             step.fPaint->setColorFilter(
-                SkColorFilter::MakeModeFilter(colors[x][y], SkBlendMode::kModulate));
+                SkColorFilters::Blend(colors[x][y], SkBlendMode::kModulate));
 
             step.fSurf = create_compat_surface(finalCanvas, kTileWidth, kTileHeight);
 
diff --git a/gm/ninepatchstretch.cpp b/gm/ninepatchstretch.cpp
index 1644c2c..c2b51f8 100644
--- a/gm/ninepatchstretch.cpp
+++ b/gm/ninepatchstretch.cpp
@@ -5,13 +5,13 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
 #include "SkSurface.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static sk_sp<SkSurface> make_surface(SkCanvas* root, int N) {
     SkImageInfo info = SkImageInfo::MakeN32Premul(N, N);
-    return sk_tool_utils::makeSurface(root, info);
+    return ToolUtils::makeSurface(root, info);
 }
 
 static sk_sp<SkImage> make_image(SkCanvas* root, SkIRect* center) {
diff --git a/gm/offsetimagefilter.cpp b/gm/offsetimagefilter.cpp
index 0c1d32c..4706157 100644
--- a/gm/offsetimagefilter.cpp
+++ b/gm/offsetimagefilter.cpp
@@ -9,8 +9,8 @@
 #include "SkImageSource.h"
 #include "SkOffsetImageFilter.h"
 #include "SkSurface.h"
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #define WIDTH 600
 #define HEIGHT 100
@@ -33,13 +33,10 @@
 
     void onOnceBeforeDraw() override {
         fBitmap = SkImage::MakeFromBitmap(
-            sk_tool_utils::create_string_bitmap(80, 80, 0xD000D000, 15, 65, 96, "e"));
+                ToolUtils::create_string_bitmap(80, 80, 0xD000D000, 15, 65, 96, "e"));
 
         fCheckerboard = SkImage::MakeFromBitmap(
-            sk_tool_utils::create_checkerboard_bitmap(80, 80,
-                                                      0xFFA0A0A0,
-                                                      0xFF404040,
-                                                      8));
+                ToolUtils::create_checkerboard_bitmap(80, 80, 0xFFA0A0A0, 0xFF404040, 8));
     }
 
     void onDraw(SkCanvas* canvas) override {
diff --git a/gm/ovals.cpp b/gm/ovals.cpp
index d9de1a5..d8f93ef 100644
--- a/gm/ovals.cpp
+++ b/gm/ovals.cpp
@@ -5,16 +5,16 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
-#include "SkTArray.h"
-#include "SkRandom.h"
-#include "SkMatrix.h"
+#include "SkBlurDrawLooper.h"
 #include "SkBlurMaskFilter.h"
 #include "SkColorFilter.h"
 #include "SkGradientShader.h"
-#include "SkBlurDrawLooper.h"
+#include "SkMatrix.h"
+#include "SkRandom.h"
 #include "SkRect.h"
+#include "SkTArray.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -133,7 +133,7 @@
         hsv[1] = rand->nextRangeF(0.75f, 1.0f);
         hsv[2] = rand->nextRangeF(0.75f, 1.0f);
 
-        return sk_tool_utils::color_to_565(SkHSVToColor(hsv));
+        return ToolUtils::color_to_565(SkHSVToColor(hsv));
     }
 
     void onDraw(SkCanvas* canvas) override {
@@ -248,7 +248,7 @@
         SkColor colors[] = { SK_ColorBLUE, SK_ColorRED, SK_ColorGREEN };
         SkScalar pos[] = { 0, SK_ScalarHalf, SK_Scalar1 };
         auto shader = SkGradientShader::MakeRadial(center, 20, colors, pos, SK_ARRAY_COUNT(colors),
-                                                   SkShader::kClamp_TileMode);
+                                                   SkTileMode::kClamp);
 
         for (int i = 0; i < fPaints.count(); ++i) {
             canvas->save();
diff --git a/gm/overdrawcanvas.cpp b/gm/overdrawcanvas.cpp
new file mode 100644
index 0000000..0b8fef0
--- /dev/null
+++ b/gm/overdrawcanvas.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkOverdrawCanvas.h"
+#include "SkOverdrawColorFilter.h"
+#include "ToolUtils.h"
+#include "gm.h"
+
+#define WIDTH 500
+#define HEIGHT 500
+
+
+static const uint32_t kOverdrawColors[6] = {
+        0x00000000, 0x5f00005f, 0x2f2f0000, 0x2f002f00, 0x3f00003f, 0x7f00007f,
+};
+
+
+DEF_SIMPLE_GM_BG(overdraw_canvas,       canvas, WIDTH, HEIGHT, SK_ColorWHITE) {
+    // Set up the overdraw canvas.
+    SkImageInfo offscreenInfo = SkImageInfo::MakeA8(WIDTH, HEIGHT);
+    sk_sp<SkSurface> offscreen = SkSurface::MakeRaster(offscreenInfo);
+    auto c = offscreen->getCanvas();
+
+    SkOverdrawCanvas overdrawCanvas(c);
+
+    overdrawCanvas.drawRect(SkRect::MakeLTRB(10, 10, 200, 200), SkPaint());
+    overdrawCanvas.drawRect(SkRect::MakeLTRB(20, 20, 190, 190), SkPaint());
+    overdrawCanvas.drawRect(SkRect::MakeLTRB(30, 30, 180, 180), SkPaint());
+    overdrawCanvas.drawRect(SkRect::MakeLTRB(40, 40, 170, 170), SkPaint());
+    overdrawCanvas.drawRect(SkRect::MakeLTRB(50, 50, 160, 160), SkPaint());
+    overdrawCanvas.drawRect(SkRect::MakeLTRB(60, 60, 150, 150), SkPaint());
+
+    char text[] = "Ae_p";
+    overdrawCanvas.drawSimpleText(text, 4, SkTextEncoding::kUTF8, 300, 300, SkFont(), SkPaint());
+
+    sk_sp<SkImage> counts = offscreen->makeImageSnapshot();
+
+    // Draw overdraw colors to the canvas.  The color filter will convert counts to colors.
+    SkPaint paint;
+    paint.setColorFilter(SkOverdrawColorFilter::Make(kOverdrawColors));
+    canvas->drawImage(counts.get(), 0.0f, 0.0f, &paint);
+    canvas->drawString("This is some text:", 180, 300, SkFont(), SkPaint());
+}
+
diff --git a/gm/p3.cpp b/gm/p3.cpp
index 5b1d24f..297a870 100644
--- a/gm/p3.cpp
+++ b/gm/p3.cpp
@@ -179,8 +179,7 @@
         SkAssertResult(pm.erase({1,0,0,1} /*in p3*/));
 
         SkPaint paint;
-        paint.setShader(SkShader::MakeBitmapShader(bm, SkShader::kRepeat_TileMode,
-                                                   SkShader::kRepeat_TileMode));
+        paint.setShader(bm.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat));
 
         canvas->drawRect({10,10,70,70}, paint);
         compare_pixel("drawBitmapAsShader P3 red, from SkPixmap::erase",
@@ -190,6 +189,8 @@
 
     canvas->translate(0,80);
 
+    // TODO(mtklein): sample and check the middle points of these gradients too.
+
     // Draw a gradient from P3 red to P3 green interpolating in unpremul P3, checking the corners.
     {
 
@@ -199,7 +200,7 @@
         SkPaint paint;
         paint.setShader(SkGradientShader::MakeLinear(points, colors, p3,
                                                      nullptr, SK_ARRAY_COUNT(colors),
-                                                     SkShader::kClamp_TileMode));
+                                                     SkTileMode::kClamp));
         canvas->drawRect({10,10,70,70}, paint);
         canvas->save();
             compare_pixel("UPM P3 gradient, P3 red",
@@ -226,7 +227,7 @@
         paint.setShader(
                 SkGradientShader::MakeLinear(points, colors, p3,
                                              nullptr, SK_ARRAY_COUNT(colors),
-                                             SkShader::kClamp_TileMode,
+                                             SkTileMode::kClamp,
                                              SkGradientShader::kInterpolateColorsInPremul_Flag,
                                              nullptr/*local matrix*/));
         canvas->drawRect({10,10,70,70}, paint);
@@ -254,7 +255,7 @@
         SkPaint paint;
         paint.setShader(SkGradientShader::MakeLinear(points, colors, srgb,
                                                      nullptr, SK_ARRAY_COUNT(colors),
-                                                     SkShader::kClamp_TileMode));
+                                                     SkTileMode::kClamp));
         canvas->drawRect({10,10,70,70}, paint);
         canvas->save();
             compare_pixel("UPM sRGB gradient, P3 red",
@@ -281,18 +282,18 @@
         paint.setShader(
                 SkGradientShader::MakeLinear(points, colors, srgb,
                                              nullptr, SK_ARRAY_COUNT(colors),
-                                             SkShader::kClamp_TileMode,
+                                             SkTileMode::kClamp,
                                              SkGradientShader::kInterpolateColorsInPremul_Flag,
                                              nullptr/*local matrix*/));
         canvas->drawRect({10,10,70,70}, paint);
         canvas->save();
-            compare_pixel("PM P3 gradient, P3 red",
+            compare_pixel("PM sRGB gradient, P3 red",
                           canvas, 10,10,
                           {1,0,0,1}, p3.get());
 
             canvas->translate(180, 0);
 
-            compare_pixel("PM P3 gradient, P3 green",
+            compare_pixel("PM sRGB gradient, P3 green",
                           canvas, 69,69,
                           {0,1,0,1}, p3.get());
         canvas->restore();
@@ -309,7 +310,7 @@
         paint.setShader(
                 SkGradientShader::MakeLinear(points, colors, p3,
                                              nullptr, SK_ARRAY_COUNT(colors),
-                                             SkShader::kClamp_TileMode,
+                                             SkTileMode::kClamp,
                                              SkGradientShader::kInterpolateColorsInPremul_Flag,
                                              nullptr/*local matrix*/));
         canvas->drawRect({10,10,70,70}, paint);
@@ -344,8 +345,7 @@
         SkPaint as_shader;
         as_shader.setColor4f({1,0,0,1}, p3.get());
         as_shader.setFilterQuality(kLow_SkFilterQuality);
-        as_shader.setShader(SkShader::MakeBitmapShader(bm, SkShader::kClamp_TileMode
-                                                         , SkShader::kClamp_TileMode));
+        as_shader.setShader(bm.makeShader());
 
         canvas->drawBitmap(bm, 10,10, &as_bitmap);
         compare_pixel("A8 sprite bitmap P3 red",
diff --git a/gm/patch.cpp b/gm/patch.cpp
index 5cfc8b5..6f05142 100644
--- a/gm/patch.cpp
+++ b/gm/patch.cpp
@@ -19,7 +19,7 @@
     const SkPoint pts[] = { { 100.f / 4.f, 0.f }, { 3.f * 100.f / 4.f, 100.f } };
 
     return SkGradientShader::MakeLinear(pts, colors, nullptr, SK_ARRAY_COUNT(colors),
-                                        SkShader::kMirror_TileMode);
+                                        SkTileMode::kMirror);
 }
 
 static void draw_control_points(SkCanvas* canvas, const SkPoint cubics[12]) {
@@ -98,7 +98,7 @@
     if (img) {
         SkScalar w = img->width();
         SkScalar h = img->height();
-        shader = img->makeShader(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode);
+        shader = img->makeShader();
         texStorage[0].set(0, 0);
         texStorage[1].set(w, 0);
         texStorage[2].set(w, h);
diff --git a/gm/pathfill.cpp b/gm/pathfill.cpp
index 2f44503..7626bd6 100644
--- a/gm/pathfill.cpp
+++ b/gm/pathfill.cpp
@@ -118,8 +118,7 @@
     path->moveTo(c, c - r);
     for (int i = 1; i < n; i++) {
         rad += drad;
-        SkScalar cosV, sinV = SkScalarSinCos(rad, &cosV);
-        path->lineTo(c + cosV * r, c + sinV * r);
+        path->lineTo(c + SkScalarCos(rad) * r, c + SkScalarSin(rad) * r);
     }
     path->close();
     return r * 2 * 6 / 5;
diff --git a/gm/pathinterior.cpp b/gm/pathinterior.cpp
index 4ad18a6..d53cffb 100644
--- a/gm/pathinterior.cpp
+++ b/gm/pathinterior.cpp
@@ -5,14 +5,14 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkBlurMaskFilter.h"
 #include "SkCanvas.h"
 #include "SkGraphics.h"
 #include "SkLayerDrawLooper.h"
 #include "SkPath.h"
 #include "SkRandom.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static SkRect inset(const SkRect& r) {
     SkRect rect = r;
@@ -46,7 +46,7 @@
         bool hasInterior = false;
 #endif
 
-        paint.setColor(hasInterior ? sk_tool_utils::color_to_565(0xFF8888FF) : SK_ColorGRAY);
+        paint.setColor(hasInterior ? ToolUtils::color_to_565(0xFF8888FF) : SK_ColorGRAY);
         canvas->drawPath(path, paint);
         paint.setStyle(SkPaint::kStroke_Style);
         paint.setColor(SK_ColorRED);
diff --git a/gm/pathopsinverse.cpp b/gm/pathopsinverse.cpp
index 32dcda9..3e2d08c 100644
--- a/gm/pathopsinverse.cpp
+++ b/gm/pathopsinverse.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkBitmap.h"
 #include "SkPath.h"
 #include "SkPathOps.h"
 #include "SkRect.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -21,16 +21,16 @@
 
 protected:
     void onOnceBeforeDraw() override {
-        const unsigned oneColor = sk_tool_utils::color_to_565(0xFF8080FF);
+        const unsigned oneColor   = ToolUtils::color_to_565(0xFF8080FF);
         const unsigned twoColor = 0x807F1f1f;
         SkColor blendColor = blend(oneColor, twoColor);
         makePaint(&fOnePaint, oneColor);
         makePaint(&fTwoPaint, twoColor);
         makePaint(&fOpPaint[kDifference_SkPathOp], oneColor);
         makePaint(&fOpPaint[kIntersect_SkPathOp], blendColor);
-        makePaint(&fOpPaint[kUnion_SkPathOp], sk_tool_utils::color_to_565(0xFFc0FFc0));
+        makePaint(&fOpPaint[kUnion_SkPathOp], ToolUtils::color_to_565(0xFFc0FFc0));
         makePaint(&fOpPaint[kReverseDifference_SkPathOp], twoColor);
-        makePaint(&fOpPaint[kXOR_SkPathOp], sk_tool_utils::color_to_565(0xFFa0FFe0));
+        makePaint(&fOpPaint[kXOR_SkPathOp], ToolUtils::color_to_565(0xFFa0FFe0));
         makePaint(&fOutlinePaint, 0xFF000000);
         fOutlinePaint.setStyle(SkPaint::kStroke_Style);
     }
diff --git a/gm/pdf_never_embed.cpp b/gm/pdf_never_embed.cpp
index 914bd79..9c5188f 100644
--- a/gm/pdf_never_embed.cpp
+++ b/gm/pdf_never_embed.cpp
@@ -24,14 +24,14 @@
     canvas->drawTextBlob(builder.make(), x, y, paint);
 }
 
-DEF_SIMPLE_GM(pdf_never_embed, canvas, 512, 512) {
+DEF_SIMPLE_GM_CAN_FAIL(pdf_never_embed, canvas, errorMsg, 512, 512) {
     SkPaint p;
 
     SkFont font(MakeResourceAsTypeface("fonts/Roboto2-Regular_NoEmbed.ttf"), 60);
     if (!font.getTypefaceOrDefault()) {
-        skiagm::GM::DrawFailureMessage(canvas, "Could not load fonts/Roboto2-Regular_NoEmbed.ttf. "
-                                               "Did you forget to set the resourcePath?");
-        return;
+        *errorMsg = "Could not load fonts/Roboto2-Regular_NoEmbed.ttf. "
+                    "Did you forget to set the resourcePath?";
+        return skiagm::DrawResult::kFail;
     }
 
     const char text[] = "HELLO, WORLD!";
@@ -54,6 +54,7 @@
     canvas->scale(1.0, 0.5);
     p.setColor(0xF0000080);
     canvas->drawSimpleText(text, strlen(text), kUTF8_SkTextEncoding, 30, 700, font, p);
+    return skiagm::DrawResult::kOk;
 }
 
 
diff --git a/gm/perspimages.cpp b/gm/perspimages.cpp
index b4ad5eb..ebce9e91 100644
--- a/gm/perspimages.cpp
+++ b/gm/perspimages.cpp
@@ -5,13 +5,13 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
 #include "Resources.h"
 #include "SkGradientShader.h"
 #include "SkImage.h"
 #include "SkPath.h"
 #include "SkSurface.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static sk_sp<SkImage> make_image1() { return GetResourceAsImage("images/mandrill_128.png"); }
 
diff --git a/gm/perspshaders.cpp b/gm/perspshaders.cpp
index 31c48f2..aaecad8 100644
--- a/gm/perspshaders.cpp
+++ b/gm/perspshaders.cpp
@@ -5,20 +5,19 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkGradientShader.h"
 #include "SkImage.h"
 #include "SkPath.h"
 #include "SkSurface.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static sk_sp<SkImage> make_image(SkCanvas* origCanvas, int w, int h) {
     SkImageInfo info = SkImageInfo::MakeN32Premul(w, h);
-    auto surface(sk_tool_utils::makeSurface(origCanvas, info));
+    auto        surface(ToolUtils::makeSurface(origCanvas, info));
     SkCanvas* canvas = surface->getCanvas();
 
-    sk_tool_utils::draw_checkerboard(canvas, SK_ColorRED, SK_ColorGREEN, w/10);
+    ToolUtils::draw_checkerboard(canvas, SK_ColorRED, SK_ColorGREEN, w / 10);
     return surface->makeImageSnapshot();
 }
 
@@ -41,12 +40,10 @@
     }
 
     void onOnceBeforeDraw() override {
-        fBitmap = sk_tool_utils::create_checkerboard_bitmap(kCellSize, kCellSize,
-                                                            SK_ColorBLUE, SK_ColorYELLOW,
-                                                            kCellSize/10);
+        fBitmap = ToolUtils::create_checkerboard_bitmap(
+                kCellSize, kCellSize, SK_ColorBLUE, SK_ColorYELLOW, kCellSize / 10);
 
-        fBitmapShader = SkShader::MakeBitmapShader(fBitmap, SkShader::kClamp_TileMode,
-                                                   SkShader::kClamp_TileMode);
+        fBitmapShader = fBitmap.makeShader();
         SkPoint pts1[] = {
             { 0, 0 },
             { SkIntToScalar(kCellSize), SkIntToScalar(kCellSize) }
@@ -61,9 +58,9 @@
         constexpr SkScalar pos[] = { 0, 0.25f, 0.5f, 0.75f, SK_Scalar1 };
 
         fLinearGrad1 = SkGradientShader::MakeLinear(pts1, colors, pos, SK_ARRAY_COUNT(colors),
-                                                    SkShader::kClamp_TileMode);
+                                                    SkTileMode::kClamp);
         fLinearGrad2 = SkGradientShader::MakeLinear(pts2, colors, pos, SK_ARRAY_COUNT(colors),
-                                                    SkShader::kClamp_TileMode);
+                                                    SkTileMode::kClamp);
 
         fPerspMatrix.reset();
         fPerspMatrix.setPerspY(SK_Scalar1 / 50);
diff --git a/gm/picture.cpp b/gm/picture.cpp
index 5b3cdfb..2ffdcc5 100644
--- a/gm/picture.cpp
+++ b/gm/picture.cpp
@@ -71,7 +71,7 @@
         matrix.postTranslate(110, 0);
         canvas->drawPicture(fPicture, &matrix, &paint);
 
-        paint.setAlpha(0x80);
+        paint.setAlphaf(0.5f);
         matrix.postTranslate(110, 0);
         canvas->drawPicture(fPicture, &matrix, &paint);
     }
diff --git a/gm/pictureimagefilter.cpp b/gm/pictureimagefilter.cpp
index cf8b579..7e5210e 100644
--- a/gm/pictureimagefilter.cpp
+++ b/gm/pictureimagefilter.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkPictureImageFilter.h"
 #include "SkPictureRecorder.h"
@@ -32,7 +32,7 @@
     SkCanvas* canvas = recorder.beginRecording(100, 100, nullptr, 0);
     SkPaint paint;
     paint.setColor(0xFFFFFFFF);
-    SkFont font(sk_tool_utils::create_portable_typeface(), 96.0f);
+    SkFont font(ToolUtils::create_portable_typeface(), 96.0f);
     canvas->drawString("e", 20.0f, 70.0f, font, paint);
     return recorder.finishRecordingAsPicture();
 }
@@ -45,7 +45,7 @@
     SkPaint paint;
     paint.setColor(0xFFFFFFFF);
     // this has to be small enough that it doesn't become a path
-    SkFont font(sk_tool_utils::create_portable_typeface(), 36.0f);
+    SkFont font(ToolUtils::create_portable_typeface(), 36.0f);
     font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
     canvas->drawString("e", 20.0f, 70.0f, font, paint);
     return recorder.finishRecordingAsPicture();
diff --git a/gm/pictureimagegenerator.cpp b/gm/pictureimagegenerator.cpp
index 04b7064..70076e0 100644
--- a/gm/pictureimagegenerator.cpp
+++ b/gm/pictureimagegenerator.cpp
@@ -5,8 +5,6 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkBitmap.h"
 #include "SkCanvas.h"
 #include "SkFontPriv.h"
@@ -18,6 +16,8 @@
 #include "SkPicture.h"
 #include "SkPictureRecorder.h"
 #include "SkTextUtils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static void draw_vector_logo(SkCanvas* canvas, const SkRect& viewBox) {
     constexpr char kSkiaStr[] = "SKIA";
@@ -28,7 +28,7 @@
     SkPaint paint;
     paint.setAntiAlias(true);
 
-    SkFont font(sk_tool_utils::create_portable_typeface());
+    SkFont font(ToolUtils::create_portable_typeface());
     font.setSubpixel(true);
     font.setEmbolden(true);
 
@@ -74,7 +74,7 @@
     const SkColor colors1[] = { SK_ColorTRANSPARENT, SK_ColorBLACK };
     SkASSERT(SK_ARRAY_COUNT(pos1) == SK_ARRAY_COUNT(colors1));
     paint.setShader(SkGradientShader::MakeLinear(pts1, colors1, pos1, SK_ARRAY_COUNT(pos1),
-                                                 SkShader::kClamp_TileMode));
+                                                 SkTileMode::kClamp));
     canvas->drawRect(underlineRect, paint);
 
     const SkPoint pts2[] = { SkPoint::Make(iBox.x() - iBox.width() * kGradientPad, 0),
@@ -92,7 +92,7 @@
     };
     SkASSERT(SK_ARRAY_COUNT(pos2) == SK_ARRAY_COUNT(colors2));
     paint.setShader(SkGradientShader::MakeLinear(pts2, colors2, pos2, SK_ARRAY_COUNT(pos2),
-                                                 SkShader::kClamp_TileMode));
+                                                 SkTileMode::kClamp));
     canvas->drawSimpleText(kSkiaStr, textLen, kUTF8_SkTextEncoding, 0, 0, font, paint);
 }
 
@@ -149,7 +149,7 @@
 
         for (size_t i = 0; i < SK_ARRAY_COUNT(configs); ++i) {
             SkPaint p;
-            p.setAlpha(SkScalarRoundToInt(255 * configs[i].opacity));
+            p.setAlphaf(configs[i].opacity);
 
             SkMatrix m = SkMatrix::MakeScale(configs[i].scaleX, configs[i].scaleY);
             if (configs[i].scaleX < 0) {
@@ -173,7 +173,7 @@
             const SkScalar y = kDrawSize * (i / kDrawsPerRow);
 
             p.setColor(0xfff0f0f0);
-            p.setAlpha(255);
+            p.setAlphaf(1.0f);
             canvas->drawRect(SkRect::MakeXYWH(x, y,
                                               SkIntToScalar(bm.width()),
                                               SkIntToScalar(bm.height())), p);
diff --git a/gm/pictureshader.cpp b/gm/pictureshader.cpp
index 1f98d46..bfadac1 100644
--- a/gm/pictureshader.cpp
+++ b/gm/pictureshader.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkBitmap.h"
 #include "SkPaint.h"
@@ -15,12 +15,12 @@
 #include "SkShader.h"
 
 static struct {
-    SkShader::TileMode tmx;
-    SkShader::TileMode tmy;
+    SkTileMode tmx;
+    SkTileMode tmy;
 } kTileConfigs[] = {
-    { SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode },
-    { SkShader::kRepeat_TileMode, SkShader::kClamp_TileMode  },
-    { SkShader::kMirror_TileMode, SkShader::kRepeat_TileMode },
+    { SkTileMode::kRepeat, SkTileMode::kRepeat },
+    { SkTileMode::kRepeat, SkTileMode::kClamp  },
+    { SkTileMode::kMirror, SkTileMode::kRepeat },
 };
 
 class PictureShaderGM : public skiagm::GM {
@@ -151,11 +151,10 @@
         canvas->drawRect(SkRect::MakeWH(fSceneSize, fSceneSize), paint);
         canvas->drawRect(SkRect::MakeXYWH(fSceneSize * 1.1f, 0, fSceneSize, fSceneSize), paint);
 
-        auto pictureShader = SkShader::MakePictureShader(fPicture, kTileConfigs[tileMode].tmx,
-                                                         kTileConfigs[tileMode].tmy,
-                                                         fUseLocalMatrixWrapper
-                                                            ? nullptr : &localMatrix,
-                                                         nullptr);
+        auto pictureShader = fPicture->makeShader(kTileConfigs[tileMode].tmx,
+                                                  kTileConfigs[tileMode].tmy,
+                                                  fUseLocalMatrixWrapper ? nullptr : &localMatrix,
+                                                  nullptr);
         paint.setShader(fUseLocalMatrixWrapper
                             ? pictureShader->makeWithLocalMatrix(localMatrix)
                             : pictureShader);
@@ -163,7 +162,7 @@
 
         canvas->translate(fSceneSize * 1.1f, 0);
 
-        auto bitmapShader = SkShader::MakeBitmapShader(fBitmap,
+        auto bitmapShader = fBitmap.makeShader(
                                                        kTileConfigs[tileMode].tmx,
                                                        kTileConfigs[tileMode].tmy,
                                                        fUseLocalMatrixWrapper
@@ -199,23 +198,21 @@
     SkRect r = tile;
     r.inset(4, 4);
     SkPaint p;
-    p.setColor(sk_tool_utils::color_to_565(0xFF303F9F));  // dark blue
+    p.setColor(ToolUtils::color_to_565(0xFF303F9F));  // dark blue
     c->drawRect(r, p);
-    p.setColor(sk_tool_utils::color_to_565(0xFFC5CAE9));  // light blue
+    p.setColor(ToolUtils::color_to_565(0xFFC5CAE9));  // light blue
     p.setStrokeWidth(10);
     c->drawLine(20, 20, 80, 80, p);
 
     sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
 
-    p.setColor(sk_tool_utils::color_to_565(0xFF8BC34A));  // green
+    p.setColor(ToolUtils::color_to_565(0xFF8BC34A));  // green
     canvas->drawPaint(p);
 
     canvas->clipRect(SkRect::MakeXYWH(0, 0, 400, 350));
     p.setColor(0xFFB6B6B6);  // gray
     canvas->drawPaint(p);
 
-    p.setShader(SkShader::MakePictureShader(std::move(picture), SkShader::kRepeat_TileMode,
-                                            SkShader::kRepeat_TileMode,
-                                            nullptr, nullptr));
+    p.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat));
     canvas->drawPaint(p);
 }
diff --git a/gm/pictureshadercache.cpp b/gm/pictureshadercache.cpp
index e3badd8..f847732 100644
--- a/gm/pictureshadercache.cpp
+++ b/gm/pictureshadercache.cpp
@@ -54,9 +54,7 @@
 
     void onDraw(SkCanvas* canvas) override {
         SkPaint paint;
-        paint.setShader(SkShader::MakePictureShader(fPicture, SkShader::kRepeat_TileMode,
-                                                    SkShader::kRepeat_TileMode, nullptr,
-                                                    nullptr));
+        paint.setShader(fPicture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat));
 
         {
             // Render in a funny color space that converts green to yellow.
diff --git a/gm/pictureshadertile.cpp b/gm/pictureshadertile.cpp
index 10d5e5d..753a67b 100644
--- a/gm/pictureshadertile.cpp
+++ b/gm/pictureshadertile.cpp
@@ -124,9 +124,8 @@
                 tilePtr = nullptr;
             }
 
-            fShaders[i] = SkShader::MakePictureShader(pictureRef, SkShader::kRepeat_TileMode,
-                                                      SkShader::kRepeat_TileMode, &localMatrix,
-                                                      tilePtr);
+            fShaders[i] = pictureRef->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat,
+                                                 &localMatrix, tilePtr);
         }
     }
 
diff --git a/gm/pixelsnap.cpp b/gm/pixelsnap.cpp
index b1373b6..671045f 100644
--- a/gm/pixelsnap.cpp
+++ b/gm/pixelsnap.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkShader.h"
 
@@ -35,14 +35,13 @@
 
     void onDraw(SkCanvas* canvas) override {
         SkPaint bgPaint;
-        bgPaint.setShader(
-                sk_tool_utils::create_checkerboard_shader(0xFFAAAAAA, 0xFF777777, 1));
+        bgPaint.setShader(ToolUtils::create_checkerboard_shader(0xFFAAAAAA, 0xFF777777, 1));
         canvas->drawPaint(bgPaint);
 
         SkString offset;
         SkPaint labelPaint;
         labelPaint.setColor(SK_ColorWHITE);
-        SkFont font(sk_tool_utils::create_portable_typeface(), SkIntToScalar(kLabelTextSize));
+        SkFont  font(ToolUtils::create_portable_typeface(), SkIntToScalar(kLabelTextSize));
         SkPaint linePaint;
         linePaint.setColor(SK_ColorWHITE);
 
diff --git a/gm/poly2poly.cpp b/gm/poly2poly.cpp
index ea57208..4f1dae9 100644
--- a/gm/poly2poly.cpp
+++ b/gm/poly2poly.cpp
@@ -5,181 +5,13 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "Resources.h"
 #include "SkFontMetrics.h"
 #include "SkPath.h"
 #include "SkTextUtils.h"
 #include "SkTypeface.h"
-
-class SkJSCanvas {
-public:
-    SkJSCanvas(SkCanvas* target);
-    ~SkJSCanvas();
-
-    void save();
-    void restore();
-
-    double lineWidth;
-    void setLineWidth(double);
-
-    void beginPath();
-    void moveTo(double x, double y);
-    void lineTo(double x, double y);
-    void closePath();
-
-    void fill();
-    void stroke();
-
-    void fillText(const char text[], double x, double y);
-
-private:
-    SkCanvas*   fTarget;
-    SkPaint     fFillPaint;
-    SkPaint     fStrokePaint;
-    SkPath      fPath;
-    SkFont      fFont;
-};
-
-SkJSCanvas::SkJSCanvas(SkCanvas* target)
-        : fTarget(target)
-        , fFont(sk_tool_utils::create_portable_typeface(), 12) {
-    fFillPaint.setAntiAlias(true);
-    fStrokePaint.setAntiAlias(true);
-    fStrokePaint.setStyle(SkPaint::kStroke_Style);
-    fStrokePaint.setStrokeWidth(SK_Scalar1);
-}
-
-SkJSCanvas::~SkJSCanvas() {}
-
-void SkJSCanvas::save() { fTarget->save(); }
-void SkJSCanvas::restore() { fTarget->restore(); }
-
-void SkJSCanvas::beginPath() { fPath.reset(); }
-void SkJSCanvas::moveTo(double x, double y) {
-    fPath.moveTo(SkDoubleToScalar(x), SkDoubleToScalar(y));
-}
-
-void SkJSCanvas::lineTo(double x, double y) {
-    fPath.lineTo(SkDoubleToScalar(x), SkDoubleToScalar(y));
-}
-
-void SkJSCanvas::closePath() { fPath.close(); }
-
-void SkJSCanvas::fill() {
-    fTarget->drawPath(fPath, fFillPaint);
-}
-
-void SkJSCanvas::stroke() {
-    fStrokePaint.setStrokeWidth(SkDoubleToScalar(lineWidth));
-    fTarget->drawPath(fPath, fStrokePaint);
-}
-
-void SkJSCanvas::fillText(const char text[], double x, double y) {
-    fTarget->drawString(text, SkDoubleToScalar(x), SkDoubleToScalar(y), fFont, fFillPaint);
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-static void dump(const SkPath& path) {
-    const SkRect& r = path.getBounds();
-    SkDebugf("isEmpty %d, bounds [%g %g %g %g]\n", path.isEmpty(),
-             r.fLeft, r.fTop, r.fRight, r.fBottom);
-}
-
-static void test_stroke(SkCanvas* canvas) {
-    if (true) {
-        SkPath path;
-        dump(path);
-        path.reset(); path.moveTo(0, 0);
-        dump(path);
-        path.reset(); path.moveTo(100, 100);
-        dump(path);
-        path.reset(); path.moveTo(0, 0); path.moveTo(100, 100);
-        dump(path);
-        path.reset(); path.moveTo(0, 0); path.lineTo(100, 100);
-        dump(path);
-        path.reset(); path.moveTo(0, 0); path.lineTo(100, 100); path.moveTo(200, 200);
-        dump(path);
-    }
-
-#if 0
-    // TEST 1 - The rectangle as it's expected to look
-    var canvas = document.createElement('canvas');
-    document.body.appendChild(canvas);
-    var ctx = canvas.getContext("2d");
-#else
-    SkJSCanvas ctx(canvas);
-#endif
-
-    ctx.save();
-    ctx.lineWidth = 2;
-    ctx.beginPath();
-    ctx.moveTo(10, 100);
-    ctx.lineTo(150, 100);
-    ctx.lineTo(150, 15);
-    ctx.lineTo(10, 15);
-    ctx.closePath();
-
-    // no extra moveTo here
-    // ctx.moveTo(175, 125);
-
-    ctx.stroke();
-    ctx.restore();
-
-    ctx.fillText("As Expected", 10, 10);
-
-#if 0
-    // TEST 2 - Includes an extra moveTo call before stroke; the rectangle appears larger
-    canvas = document.createElement('canvas');
-    document.body.appendChild(canvas);
-    ctx = canvas.getContext("2d");
-#else
-    canvas->translate(200, 0);
-#endif
-
-    ctx.save();
-    ctx.lineWidth = 2;
-    ctx.beginPath();
-    ctx.moveTo(10, 100);
-    ctx.lineTo(150, 100);
-    ctx.lineTo(150, 15);
-    ctx.lineTo(10, 15);
-    ctx.closePath();
-
-    ctx.moveTo(175, 125);
-
-    ctx.stroke();
-    ctx.restore();
-
-    ctx.fillText("Larger Rectangle", 10, 10);
-
-#if 0
-    // TEST 3 - Identical to test 2 except the line width is 1
-    canvas = document.createElement('canvas');
-    document.body.appendChild(canvas);
-    ctx = canvas.getContext("2d");
-#else
-    canvas->translate(200, 0);
-#endif
-
-    ctx.save();
-    ctx.lineWidth = 1;
-    ctx.beginPath();
-    ctx.moveTo(10, 100);
-    ctx.lineTo(150, 100);
-    ctx.lineTo(150, 15);
-    ctx.lineTo(10, 15);
-    ctx.closePath();
-
-    ctx.moveTo(175, 125);
-
-    ctx.stroke();
-    ctx.restore();
-
-    ctx.fillText("As Expected - line width 1", 10, 10);
-}
+#include "ToolUtils.h"
+#include "gm.h"
 
 class Poly2PolyGM : public skiagm::GM {
 public:
@@ -233,8 +65,6 @@
     }
 
     void onDraw(SkCanvas* canvas) override {
-        if (false) { test_stroke(canvas); return; }
-
         SkPaint paint;
         paint.setAntiAlias(true);
         paint.setStrokeWidth(SkIntToScalar(4));
diff --git a/gm/polygonoffset.cpp b/gm/polygonoffset.cpp
index 705eeda..2775215 100644
--- a/gm/polygonoffset.cpp
+++ b/gm/polygonoffset.cpp
@@ -5,13 +5,13 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
-#include "SkPolyUtils.h"
 #include "SkPathPriv.h"
+#include "SkPolyUtils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static void create_ngon(int n, SkPoint* pts, SkScalar w, SkScalar h, SkPath::Direction dir) {
-    float angleStep = 360.0f / n, angle = 0.0f, sin, cos;
+    float angleStep = 360.0f / n, angle = 0.0f;
     if ((n % 2) == 1) {
         angle = angleStep/2.0f;
     }
@@ -21,9 +21,8 @@
     }
 
     for (int i = 0; i < n; ++i) {
-        sin = SkScalarSinCos(SkDegreesToRadians(angle), &cos);
-        pts[i].fX = -sin * w;
-        pts[i].fY = cos * h;
+        pts[i].fX = -SkScalarSin(SkDegreesToRadians(angle)) * w;
+        pts[i].fY =  SkScalarCos(SkDegreesToRadians(angle)) * h;
         angle += angleStep;
     }
 }
@@ -572,7 +571,7 @@
                 }
                 path.close();
 
-                paint.setColor(sk_tool_utils::color_to_565(colors[i]));
+                paint.setColor(ToolUtils::color_to_565(colors[i]));
                 canvas->save();
                 canvas->translate(center.fX, center.fY);
                 canvas->drawPath(path, paint);
diff --git a/gm/quadpaths.cpp b/gm/quadpaths.cpp
index 6f4f7d0..3f3d861 100644
--- a/gm/quadpaths.cpp
+++ b/gm/quadpaths.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkPaint.h"
 #include "SkPath.h"
 #include "SkRandom.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -84,8 +84,8 @@
         path.fName = "moveTo-quad";
 
         SkPaint titlePaint;
-        SkFont font(sk_tool_utils::create_portable_typeface(), 15);
-        SkFont labelFont(sk_tool_utils::create_portable_typeface(), 10);
+        SkFont  font(ToolUtils::create_portable_typeface(), 15);
+        SkFont  labelFont(ToolUtils::create_portable_typeface(), 10);
 
         const char title[] = "Quad Drawn Into Rectangle Clips With "
                              "Indicated Style, Fill and Linecaps, with stroke width 10";
@@ -111,7 +111,7 @@
                         canvas->translate(rect.width() + 40 * SK_Scalar1, 0);
                     }
 
-                    SkColor color = sk_tool_utils::color_to_565(0xff007000);
+                    SkColor color = ToolUtils::color_to_565(0xff007000);
                     this->drawPath(path.fPath, canvas, color, rect,
                                     gCaps[cap].fCap, gCaps[cap].fJoin, gStyles[style].fStyle,
                                     gFills[fill].fFill, SK_Scalar1*10);
@@ -215,8 +215,8 @@
         path.fName = "moveTo-quad-close";
 
         SkPaint titlePaint;
-        SkFont font(sk_tool_utils::create_portable_typeface(), 15);
-        SkFont labelFont(sk_tool_utils::create_portable_typeface(), 10);
+        SkFont     font(ToolUtils::create_portable_typeface(), 15);
+        SkFont     labelFont(ToolUtils::create_portable_typeface(), 10);
         const char title[] = "Quad Closed Drawn Into Rectangle Clips With "
                              "Indicated Style, Fill and Linecaps, with stroke width 10";
         canvas->drawString(title, 20.0f, 20.0f, font, titlePaint);
@@ -241,7 +241,7 @@
                         canvas->translate(rect.width() + 40 * SK_Scalar1, 0);
                     }
 
-                    SkColor color = sk_tool_utils::color_to_565(0xff007000);
+                    SkColor color = ToolUtils::color_to_565(0xff007000);
                     this->drawPath(path.fPath, canvas, color, rect,
                                     gCaps[cap].fCap, gCaps[cap].fJoin, gStyles[style].fStyle,
                                     gFills[fill].fFill, SK_Scalar1*10);
diff --git a/gm/radial_gradient_precision.cpp b/gm/radial_gradient_precision.cpp
index b4e9a11..bb4e155 100644
--- a/gm/radial_gradient_precision.cpp
+++ b/gm/radial_gradient_precision.cpp
@@ -10,13 +10,13 @@
 
 // All we're looking for here is that we see a smooth gradient.
 DEF_SIMPLE_GM(radial_gradient_precision, canvas, 200, 200) {
-    SkPoint  center   = {100000, 100000};
+    SkPoint  center   = {1000, 1000};
     SkScalar radius   = 40;
-    SkColor  colors[] = {SK_ColorBLACK, SK_ColorWHITE};
+    SkColor  colors[] = {SK_ColorBLACK, SK_ColorGREEN};
 
     SkPaint p;
     p.setShader(SkGradientShader::MakeRadial(center, radius,
                                              colors, nullptr, SK_ARRAY_COUNT(colors),
-                                             SkShader::kRepeat_TileMode));
+                                             SkTileMode::kRepeat));
     canvas->drawPaint(p);
 }
diff --git a/gm/readpixels.cpp b/gm/readpixels.cpp
index e69803b..ac53784 100644
--- a/gm/readpixels.cpp
+++ b/gm/readpixels.cpp
@@ -184,10 +184,10 @@
         return SkISize::Make(3 * (kEncodedWidth + 1), 12 * (kEncodedHeight + 1));
     }
 
-    void onDraw(SkCanvas* canvas) override {
+    DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
         if (!canvas->imageInfo().colorSpace()) {
-            // This gm is only interesting in color correct modes.
-            return;
+            *errorMsg = "This gm is only interesting in color correct modes.";
+            return DrawResult::kSkip;
         }
 
         const SkAlphaType alphaTypes[] = {
@@ -224,6 +224,7 @@
             canvas->restore();
             canvas->translate((float) kEncodedWidth + 1, 0.0f);
         }
+        return DrawResult::kOk;
     }
 
 private:
@@ -247,10 +248,10 @@
         return SkISize::Make(3 * kWidth, 12 * kHeight);
     }
 
-    void onDraw(SkCanvas* canvas) override {
+    DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
         if (!canvas->imageInfo().colorSpace()) {
-            // This gm is only interesting in color correct modes.
-            return;
+            *errorMsg = "This gm is only interesting in color correct modes.";
+            return DrawResult::kSkip;
         }
 
         const sk_sp<SkImage> images[] = {
@@ -291,6 +292,7 @@
                 canvas->translate((float) kWidth, 0.0f);
             }
         }
+        return DrawResult::kOk;
     }
 
 private:
diff --git a/gm/recordopts.cpp b/gm/recordopts.cpp
index 341b7ce..c860620 100644
--- a/gm/recordopts.cpp
+++ b/gm/recordopts.cpp
@@ -6,6 +6,7 @@
  */
 
 #include "gm.h"
+#include "GrContext.h"
 #include "SkCanvas.h"
 #include "SkPath.h"
 #include "SkPictureRecorder.h"
@@ -79,7 +80,6 @@
         p.setColor(SK_ColorWHITE);
         SkASSERT(shapeColor != SK_ColorWHITE);
         canvas.drawRect(SkRect::MakeWH(SkIntToScalar(7), SkIntToScalar(7)), p);
-        canvas.flush();
     }
 
     SkRect targetRect(SkRect::MakeWH(SkIntToScalar(kTestRectSize), SkIntToScalar(kTestRectSize)));
@@ -130,6 +130,7 @@
 //    (the grey dent is from the color filter removing everything but the "good" green, see below)
 //  - Last 6 rows are grey
 DEF_SIMPLE_GM(recordopts, canvas, (kTestRectSize+1)*2, (kTestRectSize+1)*15) {
+    GrContext* context = canvas->getGrContext();
     canvas->clear(SK_ColorTRANSPARENT);
 
     typedef void (*TestVariantSequence)(SkCanvas*, SkColor, InstallDetectorFunc);
@@ -152,7 +153,9 @@
 
         TestVariantSequence drawTestSequence = funcs[k];
         drawTestSequence(canvas, shapeColor, no_detector_install);
-        canvas->flush();
+        if (context) {
+            context->flush();
+        }
         canvas->translate(SkIntToScalar(kTestRectSize) + SkIntToScalar(1), SkIntToScalar(0));
         {
             SkPictureRecorder recorder;
@@ -160,7 +163,9 @@
                                                      SkIntToScalar(kTestRectSize)),
                              shapeColor, no_detector_install);
             recorder.finishRecordingAsPicture()->playback(canvas);
-            canvas->flush();
+            if (context) {
+                context->flush();
+            }
         }
         canvas->restore();
         canvas->translate(SkIntToScalar(0), SkIntToScalar(kTestRectSize) + SkIntToScalar(1));
@@ -193,7 +198,9 @@
                 TestVariantSequence drawTestSequence = funcs[k];
                 canvas->save();
                 drawTestSequence(canvas, shapeColor, detectorInstallFunc);
-                canvas->flush();
+                if (context) {
+                    context->flush();
+                }
                 canvas->translate(SkIntToScalar(kTestRectSize) + SkIntToScalar(1), SkIntToScalar(0));
                 {
                     SkPictureRecorder recorder;
@@ -201,7 +208,9 @@
                                                              SkIntToScalar(kTestRectSize)),
                                      shapeColor, detectorInstallFunc);
                     recorder.finishRecordingAsPicture()->playback(canvas);
-                    canvas->flush();
+                    if (context) {
+                        context->flush();
+                    }
                 }
 
                 canvas->restore();
diff --git a/gm/rectangletexture.cpp b/gm/rectangletexture.cpp
index 4a3c2f6..fa92d5f 100644
--- a/gm/rectangletexture.cpp
+++ b/gm/rectangletexture.cpp
@@ -40,20 +40,17 @@
         SkCanvas canvas(bmp);
         SkPoint pts[] = { {0, 0}, {0, SkIntToScalar(height)} };
         SkColor colors0[] = { 0xFF1060B0 , 0xFF102030 };
-        paint.setShader(SkGradientShader::MakeLinear(pts, colors0, nullptr, 2,
-                                                     SkShader::kClamp_TileMode));
+        paint.setShader(SkGradientShader::MakeLinear(pts, colors0, nullptr, 2, SkTileMode::kClamp));
         canvas.drawPaint(paint);
 
         SkColor colors1[] = {0xFFA07010, 0xFFA02080};
         paint.setAntiAlias(true);
-        paint.setShader(SkGradientShader::MakeLinear(pts, colors1, nullptr, 2,
-                                                     SkShader::kClamp_TileMode));
+        paint.setShader(SkGradientShader::MakeLinear(pts, colors1, nullptr, 2, SkTileMode::kClamp));
         canvas.drawCircle(SkIntToScalar(width) / 2, SkIntToScalar(height) / 2,
                           SkIntToScalar(width + height) / 5, paint);
     }
 
-    sk_sp<SkImage> createRectangleTextureImg(GrContext* context, GrSurfaceOrigin origin, int width,
-                                             int height, const uint32_t* pixels) {
+    static const GrGLContext* GetGLContextIfSupported(GrContext* context) {
         GrGpu* gpu = context->priv().getGpu();
         if (!gpu) {
             return nullptr;
@@ -63,9 +60,32 @@
             return nullptr;
         }
 
-        if (!(kGL_GrGLStandard == glCtx->standard() && glCtx->version() >= GR_GL_VER(3, 1)) &&
-            !(glCtx->hasExtension("GL_ARB_texture_rectangle") ||
-              glCtx->hasExtension("GL_ANGLE_texture_rectangle"))) {
+    #if 0 // TODO(bsalomon): use extensions on GLES?
+        bool is_GL31 = glCtx->standard() == kGL_GrGLStandard
+                    && glCtx->version()  >= GR_GL_VER(3, 1);
+        if (!is_GL31
+                && !glCtx->hasExtension("GL_ARB_texture_rectangle")
+                && !glCtx->hasExtension("GL_ANGLE_texture_rectangle")) {
+            return nullptr;
+        }
+    #else
+        if (glCtx->standard() != kGL_GrGLStandard) {
+            return nullptr;
+        }
+        if (glCtx->version() < GR_GL_VER(3,1)
+                && !glCtx->hasExtension("GL_ARB_texture_rectangle")
+                && !glCtx->hasExtension("GL_ANGLE_texture_rectangle")) {
+            return nullptr;
+        }
+    #endif
+
+        return glCtx;
+    }
+
+    sk_sp<SkImage> createRectangleTextureImg(GrContext* context, GrSurfaceOrigin origin, int width,
+                                             int height, const uint32_t* pixels) {
+        const GrGLContext* glCtx = GetGLContextIfSupported(context);
+        if (!glCtx) {
             return nullptr;
         }
 
@@ -117,7 +137,13 @@
         return nullptr;
     }
 
-    void onDraw(GrContext* context, GrRenderTargetContext*, SkCanvas* canvas) override {
+    DrawResult onDraw(GrContext* context, GrRenderTargetContext*, SkCanvas* canvas,
+                      SkString* errorMsg) override {
+        if (!GetGLContextIfSupported(context)) {
+            *errorMsg = "this GM requires an OpenGL 3.1+ context";
+            return DrawResult::kSkip;
+        }
+
         constexpr int kWidth = 50;
         constexpr int kHeight = 50;
         constexpr SkScalar kPad = 5.f;
@@ -133,8 +159,8 @@
         };
         SkASSERT(SkToBool(rectImgs[0]) == SkToBool(rectImgs[1]));
         if (!rectImgs[0]) {
-            DrawFailureMessage(canvas, "Could not create rectangle texture image.");
-            return;
+            *errorMsg = "Could not create rectangle texture image.";
+            return DrawResult::kFail;
         }
 
         constexpr SkFilterQuality kQualities[] = {
@@ -168,8 +194,8 @@
                     // repeat/mirror shader
                     SkPaint repeatPaint;
                     repeatPaint.setFilterQuality(q);
-                    repeatPaint.setShader(rectImgs[i]->makeShader(SkShader::kRepeat_TileMode,
-                                                                  SkShader::kMirror_TileMode));
+                    repeatPaint.setShader(rectImgs[i]->makeShader(SkTileMode::kRepeat,
+                                                                  SkTileMode::kMirror));
                     canvas->drawRect(SkRect::MakeWH(1.5f * kWidth, 1.5f * kHeight), repeatPaint);
                     canvas->translate(1.5f * kWidth + kPad, 0);
 
@@ -189,6 +215,7 @@
                 canvas->translate(0, kPad + 1.5f * kHeight * s);
             }
         }
+        return DrawResult::kOk;
     }
 
 private:
diff --git a/gm/rects.cpp b/gm/rects.cpp
index 2786be7..b875d83 100644
--- a/gm/rects.cpp
+++ b/gm/rects.cpp
@@ -81,7 +81,7 @@
             SkScalar pos[] = { 0, SK_ScalarHalf, SK_Scalar1 };
             p.setShader(SkGradientShader::MakeRadial(center, 20, colors, pos,
                                                      SK_ARRAY_COUNT(colors),
-                                                     SkShader::kClamp_TileMode));
+                                                     SkTileMode::kClamp));
             fPaints.push_back(p);
         }
 
diff --git a/gm/repeated_bitmap.cpp b/gm/repeated_bitmap.cpp
index 6f23ea2..154810d 100644
--- a/gm/repeated_bitmap.cpp
+++ b/gm/repeated_bitmap.cpp
@@ -7,15 +7,15 @@
 
 #include "Resources.h"
 #include "SkImage.h"
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
-static void draw_rotated_image(SkCanvas* canvas, const SkImage* image) {
-    sk_tool_utils::draw_checkerboard(canvas, SkColorSetRGB(156, 154, 156),
-                                     SK_ColorWHITE, 12);
+static skiagm::DrawResult draw_rotated_image(SkCanvas* canvas, const SkImage* image,
+                                             SkString* errorMsg) {
+    ToolUtils::draw_checkerboard(canvas, SkColorSetRGB(156, 154, 156), SK_ColorWHITE, 12);
     if (!image) {
-        skiagm::GM::DrawFailureMessage(canvas, "No image. Did you forget to set the resourcePath?");
-        return;
+        *errorMsg = "No image. Did you forget to set the resourcePath?";
+        return skiagm::DrawResult::kFail;
     }
     SkRect rect = SkRect::MakeLTRB(-68.0f, -68.0f, 68.0f, 68.0f);
     SkPaint paint;
@@ -33,12 +33,13 @@
             canvas->drawImage(image, point[0], point[1]);
         }
     }
+    return skiagm::DrawResult::kOk;
 }
 
-DEF_SIMPLE_GM(repeated_bitmap, canvas, 576, 576) {
-    draw_rotated_image(canvas, GetResourceAsImage("images/randPixels.png").get());
+DEF_SIMPLE_GM_CAN_FAIL(repeated_bitmap, canvas, errorMsg, 576, 576) {
+    return draw_rotated_image(canvas, GetResourceAsImage("images/randPixels.png").get(), errorMsg);
 }
 
-DEF_SIMPLE_GM(repeated_bitmap_jpg, canvas, 576, 576) {
-    draw_rotated_image(canvas, GetResourceAsImage("images/color_wheel.jpg").get());
+DEF_SIMPLE_GM_CAN_FAIL(repeated_bitmap_jpg, canvas, errorMsg, 576, 576) {
+    return draw_rotated_image(canvas, GetResourceAsImage("images/color_wheel.jpg").get(), errorMsg);
 }
diff --git a/gm/roundrects.cpp b/gm/roundrects.cpp
index 12b0e71..dccfd36 100644
--- a/gm/roundrects.cpp
+++ b/gm/roundrects.cpp
@@ -5,17 +5,17 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
-#include "SkTArray.h"
-#include "SkRandom.h"
-#include "SkMatrix.h"
+#include "SkBlurDrawLooper.h"
 #include "SkBlurMaskFilter.h"
 #include "SkColorFilter.h"
 #include "SkGradientShader.h"
-#include "SkBlurDrawLooper.h"
-#include "SkRect.h"
+#include "SkMatrix.h"
 #include "SkRRect.h"
+#include "SkRandom.h"
+#include "SkRect.h"
+#include "SkTArray.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -25,7 +25,7 @@
     hsv[1] = rand->nextRangeF(0.75f, 1.0f);
     hsv[2] = rand->nextRangeF(0.75f, 1.0f);
 
-    return sk_tool_utils::color_to_565(SkHSVToColor(hsv));
+    return ToolUtils::color_to_565(SkHSVToColor(hsv));
 }
 
 class RoundRectGM : public GM {
@@ -263,7 +263,7 @@
         SkColor colors[] = { SK_ColorBLUE, SK_ColorRED, SK_ColorGREEN };
         SkScalar pos[] = { 0, SK_ScalarHalf, SK_Scalar1 };
         auto shader = SkGradientShader::MakeRadial(center, 20, colors, pos, SK_ARRAY_COUNT(colors),
-                                                   SkShader::kClamp_TileMode);
+                                                   SkTileMode::kClamp);
 
         for (int i = 0; i < fPaints.count(); ++i) {
             canvas->save();
diff --git a/gm/rrect.cpp b/gm/rrect.cpp
index f8fff7a..c7d9cca 100644
--- a/gm/rrect.cpp
+++ b/gm/rrect.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
-#include "SkRRect.h"
 #include "SkPath.h"
+#include "SkRRect.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 typedef void (*InsetProc)(const SkRRect&, SkScalar dx, SkScalar dy, SkRRect*);
 
@@ -105,7 +105,7 @@
     if (rrect.isRect()) {
         paint.setColor(SK_ColorRED);
     } else if (rrect.isOval()) {
-        paint.setColor(sk_tool_utils::color_to_565(0xFF008800));
+        paint.setColor(ToolUtils::color_to_565(0xFF008800));
     } else if (rrect.isSimple()) {
         paint.setColor(SK_ColorBLUE);
     } else {
diff --git a/gm/rrectclipdrawpaint.cpp b/gm/rrectclipdrawpaint.cpp
index 291dc48..0636a2b 100644
--- a/gm/rrectclipdrawpaint.cpp
+++ b/gm/rrectclipdrawpaint.cpp
@@ -35,8 +35,7 @@
 
     constexpr SkPoint kPts[] = {{0.f, 0.f}, {256.f, 256.f}};
     constexpr SkColor kColors1[] = {SK_ColorCYAN, SK_ColorGREEN};
-    p.setShader(SkGradientShader::MakeLinear(kPts, kColors1, nullptr, 2,
-                                             SkShader::kClamp_TileMode));
+    p.setShader(SkGradientShader::MakeLinear(kPts, kColors1, nullptr, 2, SkTileMode::kClamp));
     canvas->concat(zoomOut);
     canvas->saveLayer(layerRect, nullptr);
     canvas->clipRRect(rrect, true);
@@ -45,7 +44,7 @@
 
     constexpr SkColor kColors2[] = {SK_ColorMAGENTA, SK_ColorGRAY};
     p.setShader(SkGradientShader::MakeRadial({128.f, 128.f}, 128.f, kColors2, nullptr, 2,
-                                             SkShader::kClamp_TileMode));
+                                             SkTileMode::kClamp));
     canvas->concat(zoomOut);
     canvas->saveLayer(layerRect, nullptr);
     canvas->clipRRect(rrect, false);
diff --git a/gm/rrects.cpp b/gm/rrects.cpp
index ec05d21..52df12a 100644
--- a/gm/rrects.cpp
+++ b/gm/rrects.cpp
@@ -60,18 +60,13 @@
 
     SkISize onISize() override { return SkISize::Make(kImageWidth, kImageHeight); }
 
-    void onDraw(SkCanvas* canvas) override {
+    DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
         GrRenderTargetContext* renderTargetContext =
             canvas->internal_private_accessTopLayerRenderTargetContext();
-        if (kEffect_Type == fType && !renderTargetContext) {
-            skiagm::GM::DrawGpuOnlyMessage(canvas);
-            return;
-        }
-
         GrContext* context = canvas->getGrContext();
-        if (kEffect_Type == fType && !context) {
-            skiagm::GM::DrawGpuOnlyMessage(canvas);
-            return;
+        if (kEffect_Type == fType && (!renderTargetContext || !context)) {
+            *errorMsg = kErrorMsg_DrawSkippedGpuOnly;
+            return DrawResult::kSkip;
         }
 
         SkPaint paint;
@@ -142,6 +137,7 @@
                 y += kTileY;
             }
         }
+        return DrawResult::kOk;
     }
 
     void setUpRRects() {
diff --git a/gm/runtimecolorfilter.cpp b/gm/runtimecolorfilter.cpp
new file mode 100644
index 0000000..70b7ee9
--- /dev/null
+++ b/gm/runtimecolorfilter.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Resources.h"
+#include "SkCanvas.h"
+#include "SkColorFilterPriv.h"
+#include "SkImage.h"
+#include "SkReadBuffer.h"
+#include "ToolUtils.h"
+#include "effects/GrSkSLFP.h"
+#include "gm.h"
+
+const char* SKSL_TEST_SRC = R"(
+    layout(ctype=float) in uniform half b;
+
+    void main(inout half4 color) {
+        color.rg = color.gr;
+        color.b = b;
+    }
+)";
+
+static void runtimeCpuFunc(float color[4], const void* context) {
+    std::swap(color[0], color[1]);
+    color[2] = *(float*) context;
+}
+
+DEF_SIMPLE_GPU_GM(runtimecolorfilter, context, rtc, canvas, 768, 256) {
+    auto img = GetResourceAsImage("images/mandrill_256.png");
+    canvas->drawImage(img, 0, 0, nullptr);
+
+    float b = 0.75;
+    sk_sp<SkData> data = SkData::MakeWithCopy(&b, sizeof(b));
+    static SkRuntimeColorFilterFactory fact = SkRuntimeColorFilterFactory(SkString(SKSL_TEST_SRC),
+                                                                          runtimeCpuFunc);
+    auto cf1 = fact.make(data);
+    SkPaint p;
+    p.setColorFilter(cf1);
+    canvas->drawImage(img, 256, 0, &p);
+
+    static constexpr size_t kBufferSize = 512;
+    char buffer[kBufferSize];
+    SkBinaryWriteBuffer wb(buffer, kBufferSize);
+    wb.writeFlattenable(cf1.get());
+    SkReadBuffer rb(buffer, kBufferSize);
+    auto cf2 = rb.readColorFilter();
+    if (cf2) {
+        p.setColorFilter(cf2);
+        canvas->drawImage(img, 512, 0, &p);
+    }
+}
+
+DEF_SIMPLE_GM(runtimecolorfilter_interpreted, canvas, 768, 256) {
+    auto img = GetResourceAsImage("images/mandrill_256.png");
+    canvas->drawImage(img, 0, 0, nullptr);
+
+    float b = 0.75;
+    sk_sp<SkData> data = SkData::MakeWithCopy(&b, sizeof(b));
+    static SkRuntimeColorFilterFactory fact = SkRuntimeColorFilterFactory(SkString(SKSL_TEST_SRC),
+                                                                          nullptr);
+    auto cf1 = fact.make(data);
+    SkPaint p;
+    p.setColorFilter(cf1);
+    canvas->drawImage(img, 256, 0, &p);
+
+    static constexpr size_t kBufferSize = 512;
+    char buffer[kBufferSize];
+    SkBinaryWriteBuffer wb(buffer, kBufferSize);
+    wb.writeFlattenable(cf1.get());
+    SkReadBuffer rb(buffer, kBufferSize);
+    auto cf2 = rb.readColorFilter();
+    if (cf2) {
+        p.setColorFilter(cf2);
+        canvas->drawImage(img, 512, 0, &p);
+    }
+}
diff --git a/gm/samplelocations.cpp b/gm/samplelocations.cpp
new file mode 100644
index 0000000..a9a787a
--- /dev/null
+++ b/gm/samplelocations.cpp
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+
+#if SK_SUPPORT_GPU
+
+#include "GrClip.h"
+#include "GrContext.h"
+#include "GrContextPriv.h"
+#include "GrMemoryPool.h"
+#include "GrOpFlushState.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
+#include "GrRenderTargetContext.h"
+#include "GrRenderTargetContextPriv.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLGeometryProcessor.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "glsl/GrGLSLVarying.h"
+#include "glsl/GrGLSLVertexGeoBuilder.h"
+
+namespace skiagm {
+
+enum class GradType : bool {
+    kHW,
+    kSW
+};
+
+/**
+ * This test ensures that the shaderBuilder's sample offsets and sample mask are correlated with
+ * actual HW sample locations. It does so by drawing pseudo-random subpixel boxes, and only turning
+ * off the samples whose locations fall inside the boxes.
+ */
+class SampleLocationsGM : public GpuGM {
+public:
+    SampleLocationsGM(GradType gradType, GrSurfaceOrigin origin)
+            : fGradType(gradType)
+            , fOrigin(origin) {}
+
+private:
+    SkString onShortName() override;
+    SkISize onISize() override { return SkISize::Make(200, 200); }
+    DrawResult onDraw(GrContext*, GrRenderTargetContext*, SkCanvas*, SkString* errorMsg) override;
+
+    const GradType fGradType;
+    const GrSurfaceOrigin fOrigin;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// SkSL code.
+
+class SampleLocationsTestProcessor : public GrGeometryProcessor {
+public:
+    SampleLocationsTestProcessor(GradType gradType)
+            : GrGeometryProcessor(kSampleLocationsTestProcessor_ClassID)
+            , fGradType(gradType) {
+        this->setWillUseCustomFeature(CustomFeatures::kSampleLocations);
+    }
+    const char* name() const override { return "SampleLocationsTestProcessor"; }
+    void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {
+        b->add32((uint32_t)fGradType);
+    }
+    GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final;
+
+private:
+    const GradType fGradType;
+
+    class Impl;
+};
+
+class SampleLocationsTestProcessor::Impl : public GrGLSLGeometryProcessor {
+    void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
+        const auto& proc = args.fGP.cast<SampleLocationsTestProcessor>();
+        auto* v = args.fVertBuilder;
+        auto* f = args.fFragBuilder;
+
+        GrGLSLVarying coord(kFloat2_GrSLType);
+        GrGLSLVarying grad(kFloat2_GrSLType);
+        args.fVaryingHandler->addVarying("coord", &coord);
+        if (GradType::kSW == proc.fGradType) {
+            args.fVaryingHandler->addVarying("grad", &grad);
+        }
+
+        // Pixel grid.
+        v->codeAppendf("int x = sk_InstanceID %% 200;");
+        v->codeAppendf("int y = sk_InstanceID / 200;");
+
+        // Create pseudo-random rectangles inside a 16x16 subpixel grid. This works out nicely
+        // because there are 17 positions on the grid (including both edges), and 17 is a great
+        // prime number for generating pseudo-random numbers.
+        v->codeAppendf("int ileft = (sk_InstanceID*929) %% 17;");
+        v->codeAppendf("int iright = ileft + 1 + ((sk_InstanceID*1637) %% (17 - ileft));");
+        v->codeAppendf("int itop = (sk_InstanceID*313) %% 17;");
+        v->codeAppendf("int ibot = itop + 1 + ((sk_InstanceID*1901) %% (17 - itop));");
+
+        // Outset (or inset) the rectangle, for the very likely scenario that samples fall on exact
+        // 16ths of a pixel. GL_SUBPIXEL_BITS is allowed to be as low as 4, so try not to let the
+        // outset value to get too small.
+        v->codeAppendf("float outset = 1/32.0;");
+        v->codeAppendf("outset = (0 == (x + y) %% 2) ? -outset : +outset;");
+        v->codeAppendf("float l = ileft/16.0 - outset;");
+        v->codeAppendf("float r = iright/16.0 + outset;");
+        v->codeAppendf("float t = itop/16.0 - outset;");
+        v->codeAppendf("float b = ibot/16.0 + outset;");
+
+        v->codeAppendf("float2 vertexpos;");
+        v->codeAppendf("vertexpos.x = float(x) + ((0 == (sk_VertexID %% 2)) ? l : r);");
+        v->codeAppendf("vertexpos.y = float(y) + ((0 == (sk_VertexID / 2)) ? t : b);");
+        gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertexpos");
+
+        v->codeAppendf("%s.x = (0 == (sk_VertexID %% 2)) ? -1 : +1;", coord.vsOut());
+        v->codeAppendf("%s.y = (0 == (sk_VertexID / 2)) ? -1 : +1;", coord.vsOut());
+        if (GradType::kSW == proc.fGradType) {
+            v->codeAppendf("%s = 2/float2(r - l, b - t);", grad.vsOut());
+        }
+
+        // Fragment shader: Output RED.
+        f->codeAppendf("%s = half4(1,0,0,1);", args.fOutputColor);
+        f->codeAppendf("%s = half4(1);", args.fOutputCoverage);
+
+        // Now turn off all the samples inside our sub-rectangle. As long as the shaderBuilder's
+        // sample offsets and sample mask are correlated with actual HW sample locations, no red
+        // will bleed through.
+        f->codeAppendf("for (int i = 0; i < %i; ++i) {",
+                       f->getProgramBuilder()->effectiveSampleCnt());
+        if (GradType::kHW == proc.fGradType) {
+            f->codeAppendf("float2x2 grad = float2x2(dFdx(%s), dFdy(%s));",
+                           coord.fsIn(), coord.fsIn());
+        } else {
+            f->codeAppendf("float2x2 grad = float2x2(%s.x, 0, 0, %s.y);", grad.fsIn(), grad.fsIn());
+        }
+        f->codeAppendf(    "float2 samplecoord = %s[i] * grad + %s;",
+                           f->sampleOffsets(), coord.fsIn());
+        f->codeAppendf(    "if (all(lessThanEqual(abs(samplecoord), float2(1)))) {");
+        f->maskOffMultisampleCoverage(
+                "~(1 << i)", GrGLSLFPFragmentBuilder::ScopeFlags::kInsideLoop);
+        f->codeAppendf(    "}");
+        f->codeAppendf("}");
+    }
+
+    void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&,
+                 FPCoordTransformIter&&) override {}
+};
+
+GrGLSLPrimitiveProcessor* SampleLocationsTestProcessor::createGLSLInstance(
+        const GrShaderCaps&) const {
+    return new Impl();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Draw Op.
+
+class SampleLocationsTestOp : public GrDrawOp {
+public:
+    DEFINE_OP_CLASS_ID
+
+    static std::unique_ptr<GrDrawOp> Make(
+            GrRecordingContext* ctx, const SkMatrix& viewMatrix, GradType gradType) {
+        GrOpMemoryPool* pool = ctx->priv().opMemoryPool();
+        return pool->allocate<SampleLocationsTestOp>(gradType);
+    }
+
+private:
+    SampleLocationsTestOp(GradType gradType) : GrDrawOp(ClassID()), fGradType(gradType) {
+        this->setBounds(SkRect::MakeIWH(200, 200), HasAABloat::kNo, IsZeroArea::kNo);
+    }
+
+    const char* name() const override { return "SampleLocationsTestOp"; }
+    FixedFunctionFlags fixedFunctionFlags() const override {
+        return FixedFunctionFlags::kUsesHWAA | FixedFunctionFlags::kUsesStencil;
+    }
+    GrProcessorSet::Analysis finalize(
+            const GrCaps&, const GrAppliedClip*, GrFSAAType, GrClampType) override {
+        return GrProcessorSet::EmptySetAnalysis();
+    }
+    void onPrepare(GrOpFlushState*) override {}
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        static constexpr GrUserStencilSettings kStencilWrite(
+            GrUserStencilSettings::StaticInit<
+                0x0001,
+                GrUserStencilTest::kAlways,
+                0xffff,
+                GrUserStencilOp::kReplace,
+                GrUserStencilOp::kKeep,
+                0xffff>()
+        );
+
+        GrPipeline pipeline(GrScissorTest::kDisabled, SkBlendMode::kSrcOver,
+                            GrPipeline::InputFlags::kHWAntialias, &kStencilWrite);
+
+        GrMesh mesh(GrPrimitiveType::kTriangleStrip);
+        mesh.setInstanced(nullptr, 200*200, 0, 4);
+        flushState->rtCommandBuffer()->draw(
+                SampleLocationsTestProcessor(fGradType), pipeline, nullptr, nullptr, &mesh, 1,
+                SkRect::MakeIWH(200, 200));
+    }
+
+    const GradType fGradType;
+
+    friend class ::GrOpMemoryPool; // for ctor
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Test.
+
+SkString SampleLocationsGM::onShortName() {
+    SkString name("samplelocations");
+    name.append((GradType::kHW == fGradType) ? "_hwgrad" : "_swgrad");
+    name.append((kTopLeft_GrSurfaceOrigin == fOrigin) ? "_topleft" : "_botleft");
+    return name;
+}
+
+DrawResult SampleLocationsGM::onDraw(
+        GrContext* ctx, GrRenderTargetContext* rtc, SkCanvas* canvas, SkString* errorMsg) {
+    if (rtc->numStencilSamples() <= 1) {
+        *errorMsg = "MSAA only.";
+        return DrawResult::kSkip;
+    }
+    if (!ctx->priv().caps()->sampleLocationsSupport()) {
+        *errorMsg = "Requires support for sample locations.";
+        return DrawResult::kSkip;
+    }
+    if (!ctx->priv().caps()->shaderCaps()->sampleVariablesSupport()) {
+        *errorMsg = "Requires support for sample variables.";
+        return DrawResult::kSkip;
+    }
+
+    static constexpr GrUserStencilSettings kStencilCover(
+        GrUserStencilSettings::StaticInit<
+            0x0000,
+            GrUserStencilTest::kNotEqual,
+            0xffff,
+            GrUserStencilOp::kZero,
+            GrUserStencilOp::kKeep,
+            0xffff>()
+    );
+
+    if (auto offscreenRTC = ctx->priv().makeDeferredRenderTargetContext(
+            rtc->asSurfaceProxy()->backendFormat(), SkBackingFit::kExact, 200, 200,
+            rtc->asSurfaceProxy()->config(), nullptr, rtc->numStencilSamples(), GrMipMapped::kNo,
+            fOrigin)) {
+        offscreenRTC->clear(nullptr, {0,1,0,1}, GrRenderTargetContext::CanClearFullscreen::kYes);
+
+        // Stencil.
+        offscreenRTC->priv().testingOnly_addDrawOp(
+                SampleLocationsTestOp::Make(ctx, canvas->getTotalMatrix(), fGradType));
+
+        // Cover.
+        GrPaint coverPaint;
+        coverPaint.setColor4f({1,0,0,1});
+        coverPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrcOver));
+        rtc->priv().drawFilledRect(
+                GrNoClip(), std::move(coverPaint), GrAA::kNo, SkMatrix::I(),
+                SkRect::MakeWH(200, 200), &kStencilCover);
+
+        // Copy offscreen texture to canvas.
+        rtc->drawTexture(
+                GrNoClip(), sk_ref_sp(offscreenRTC->asTextureProxy()),
+                GrSamplerState::Filter::kNearest, SkBlendMode::kSrc, SK_PMColor4fWHITE,
+                {0,0,200,200}, {0,0,200,200}, GrAA::kNo, GrQuadAAFlags::kNone,
+                SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint, SkMatrix::I(), nullptr);
+    }
+
+    return skiagm::DrawResult::kOk;
+}
+
+DEF_GM( return new SampleLocationsGM(GradType::kHW, kTopLeft_GrSurfaceOrigin); )
+DEF_GM( return new SampleLocationsGM(GradType::kHW, kBottomLeft_GrSurfaceOrigin); )
+DEF_GM( return new SampleLocationsGM(GradType::kSW, kTopLeft_GrSurfaceOrigin); )
+DEF_GM( return new SampleLocationsGM(GradType::kSW, kBottomLeft_GrSurfaceOrigin); )
+
+}
+
+#endif  // SK_SUPPORT_GPU
diff --git a/gm/samplerstress.cpp b/gm/samplerstress.cpp
index 538a385..3653bfd 100644
--- a/gm/samplerstress.cpp
+++ b/gm/samplerstress.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkMaskFilter.h"
 #include "SkPath.h"
 #include "SkShader.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -72,8 +72,7 @@
 
         createTexture();
 
-        fShader = SkShader::MakeBitmapShader(fTexture, SkShader::kRepeat_TileMode,
-                                             SkShader::kRepeat_TileMode);
+        fShader = fTexture.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat);
     }
 
     void createMaskFilter() {
@@ -97,7 +96,7 @@
         paint.setAntiAlias(true);
         paint.setShader(fShader);
         paint.setMaskFilter(fMaskFilter);
-        SkFont font(sk_tool_utils::create_portable_typeface(), 72);
+        SkFont font(ToolUtils::create_portable_typeface(), 72);
 
         SkRect temp;
         temp.set(SkIntToScalar(115),
diff --git a/gm/savelayer.cpp b/gm/savelayer.cpp
index bf59c31..2246177 100644
--- a/gm/savelayer.cpp
+++ b/gm/savelayer.cpp
@@ -5,10 +5,10 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvasPriv.h"
-
+#include "SkShaderMaskFilter.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 // This GM tests out the deprecated Android-specific unclipped saveLayer "feature".
 // In particular, it attempts to compare the performance of unclipped saveLayers with alternatives.
@@ -25,7 +25,7 @@
     SkRandom rand;
 
     for (int i = 0; i < 20; ++i) {
-        paint.setColor(sk_tool_utils::color_to_565(rand.nextU() | (0xFF << 24)));
+        paint.setColor(ToolUtils::color_to_565(rand.nextU() | (0xFF << 24)));
         canvas->drawRect({ 15, 15, 290, 40 }, paint);
         canvas->translate(0, 30);
     }
@@ -86,8 +86,8 @@
 
 DEF_SIMPLE_GM(picture_savelayer, canvas, 320, 640) {
     SkPaint paint1, paint2, paint3;
-    paint1.setAlpha(0x7f);
-    paint2.setAlpha(0x3f);
+    paint1.setAlphaf(0.5f);
+    paint2.setAlphaf(0.25f);
     paint3.setColor(0xFFFF0000);
     SkRect rect1{40, 5, 80, 70}, rect2{5, 40, 70, 80}, rect3{10, 10, 70, 70};
     // In the future, we might also test the clipped case by allowing i = 0
@@ -141,7 +141,7 @@
     canvas->drawPaint(paint);
 
     paint.setShader(SkGradientShader::MakeRadial({cx, cy}, size / 4, colors, nullptr, 2,
-                                                 SkShader::kClamp_TileMode));
+                                                 SkTileMode::kClamp));
     canvas->drawCircle(cx, cy, size / 4, paint);
 }
 
@@ -277,6 +277,63 @@
     canvas->restore();
 }
 
+DEF_SIMPLE_GM(savelayer_clipmask_maskfilter, canvas, 500, 500) {
+    // Offscreen surface for making the clip mask and mask filter images
+    auto surf = SkSurface::MakeRaster(SkImageInfo::MakeA8(100, 100));
+    SkPaint maskPaint;
+    maskPaint.setColor(SK_ColorWHITE);
+    maskPaint.setAntiAlias(true);
+
+    // Draw a centered circle for the mask filter
+    surf->getCanvas()->clear(SK_ColorTRANSPARENT);
+    surf->getCanvas()->drawCircle(50.f, 50.f, 50.f, maskPaint);
+    auto maskFilterImage = surf->makeImageSnapshot();
+    sk_sp<SkMaskFilter> maskFilter = SkShaderMaskFilter::Make(maskFilterImage->makeShader());
+
+    // Cut out a cross for the clip mask
+    surf->getCanvas()->clear(SK_ColorTRANSPARENT);
+    surf->getCanvas()->drawRect(SkRect::MakeLTRB(0.f, 0.f, 40.f, 40.f), maskPaint);
+    surf->getCanvas()->drawRect(SkRect::MakeLTRB(60.f, 0.f, 100.f, 40.f), maskPaint);
+    surf->getCanvas()->drawRect(SkRect::MakeLTRB(0.f, 60.f, 40.f, 100.f), maskPaint);
+    surf->getCanvas()->drawRect(SkRect::MakeLTRB(60.f, 60.f, 100.f, 100.f), maskPaint);
+    auto clipMaskImage = surf->makeImageSnapshot();
+    SkMatrix clipMatrix = SkMatrix::I();
+    SkRect clipBounds = SkRect::MakeWH(100, 100);
+
+    // On the main canvas, save a 100x100 layer three times, applying clip mask, mask filter, or
+    // both, translating across the GM for each configuration.
+    canvas->clear(SK_ColorGRAY);
+
+    canvas->translate(25.f, 0.f);
+
+    // Clip mask only
+    SkCanvas::SaveLayerRec rec;
+    rec.fBounds = &clipBounds;
+    rec.fClipMask = clipMaskImage.get();
+    rec.fClipMatrix = &clipMatrix;
+    canvas->saveLayer(rec);
+    canvas->clear(SK_ColorWHITE);
+    canvas->restore();
+
+    canvas->translate(125.f, 0.f);
+
+    // Mask filter only
+    maskPaint.setMaskFilter(maskFilter);
+    rec.fClipMask = nullptr;
+    rec.fPaint = &maskPaint;
+    canvas->saveLayer(rec);
+    canvas->clear(SK_ColorWHITE);
+    canvas->restore();
+
+    canvas->translate(125.f, 0.f);
+
+    // Both
+    rec.fClipMask = clipMaskImage.get();
+    canvas->saveLayer(rec);
+    canvas->clear(SK_ColorWHITE);
+    canvas->restore();
+}
+
 #include "SkFont.h"
 #include "SkGradientShader.h"
 #include "SkTextBlob.h"
@@ -306,7 +363,7 @@
     // draw the treatment
     const SkPoint pts[] = { {r.fLeft,0}, {r.fRight, 0} };
     const SkColor colors[] = { 0x88000000, 0x0 };
-    auto sh = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode);
+    auto sh = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
     p.setShader(sh);
     p.setBlendMode(SkBlendMode::kDstIn);
     canvas->drawRect(r, p);
@@ -319,7 +376,7 @@
     // just outline where we expect the treatment to appear
     p.reset();
     p.setStyle(SkPaint::kStroke_Style);
-    p.setAlpha(0x40);
+    p.setAlphaf(0.25f);
     canvas->drawRect(r, p);
 }
 
diff --git a/gm/scaledemoji.cpp b/gm/scaledemoji.cpp
index 3e19803..c082dcd 100644
--- a/gm/scaledemoji.cpp
+++ b/gm/scaledemoji.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "Resources.h"
 #include "SkCanvas.h"
@@ -40,14 +40,12 @@
     } fEmojiFont;
 
     void onOnceBeforeDraw() override {
-        fEmojiFont.fTypeface = sk_tool_utils::emoji_typeface();
-        fEmojiFont.fText = sk_tool_utils::emoji_sample_text();
+        fEmojiFont.fTypeface = ToolUtils::emoji_typeface();
+        fEmojiFont.fText     = ToolUtils::emoji_sample_text();
     }
 
     SkString onShortName() override {
-        SkString name("scaledemoji");
-        name.append(sk_tool_utils::platform_font_manager());
-        return name;
+        return SkString("scaledemoji");
     }
 
     SkISize onISize() override { return SkISize::Make(1200, 1200); }
@@ -92,14 +90,12 @@
     } fEmojiFont;
 
     void onOnceBeforeDraw() override {
-        fEmojiFont.fTypeface = sk_tool_utils::emoji_typeface();
-        fEmojiFont.fText = sk_tool_utils::emoji_sample_text();
+        fEmojiFont.fTypeface = ToolUtils::emoji_typeface();
+        fEmojiFont.fText     = ToolUtils::emoji_sample_text();
     }
 
     SkString onShortName() override {
-        SkString name("scaledemojipos");
-        name.append(sk_tool_utils::platform_font_manager());
-        return name;
+        return SkString("scaledemojipos");
     }
 
     SkISize onISize() override { return SkISize::Make(1200, 1200); }
diff --git a/gm/scaledemoji_rendering.cpp b/gm/scaledemoji_rendering.cpp
index 0987f63..3440bc6 100644
--- a/gm/scaledemoji_rendering.cpp
+++ b/gm/scaledemoji_rendering.cpp
@@ -5,8 +5,8 @@
 * found in the LICENSE file.
 */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "Resources.h"
 #include "SkCanvas.h"
@@ -27,13 +27,11 @@
         typefaces[0] = MakeResourceAsTypeface("fonts/colr.ttf");
         typefaces[1] = MakeResourceAsTypeface("fonts/sbix.ttf");
         typefaces[2] = MakeResourceAsTypeface("fonts/cbdt.ttf");
-        typefaces[3] = sk_tool_utils::create_portable_typeface("Emoji", SkFontStyle());
+        typefaces[3] = ToolUtils::create_portable_typeface("Emoji", SkFontStyle());
     }
 
     SkString onShortName() override {
-        SkString name("scaledemoji_rendering");
-        name.append(sk_tool_utils::platform_font_manager());
-        return name;
+        return SkString("scaledemoji_rendering");
     }
 
     SkISize onISize() override { return SkISize::Make(1200, 1200); }
@@ -48,7 +46,7 @@
             font.setEdging(SkFont::Edging::kAlias);
 
             SkPaint paint;
-            const char* text = sk_tool_utils::emoji_sample_text();
+            const char*   text = ToolUtils::emoji_sample_text();
             SkFontMetrics metrics;
 
             for (SkScalar textSize : { 70, 150 }) {
diff --git a/gm/shadermaskfilter.cpp b/gm/shadermaskfilter.cpp
index cd3af70..362a46a 100644
--- a/gm/shadermaskfilter.cpp
+++ b/gm/shadermaskfilter.cpp
@@ -5,8 +5,6 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkBlendModePriv.h"
 #include "SkCanvas.h"
 #include "SkImage.h"
@@ -14,15 +12,20 @@
 #include "SkPictureRecorder.h"
 #include "SkShaderMaskFilter.h"
 #include "SkTextUtils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static void draw_masked_image(SkCanvas* canvas, const SkImage* image, SkScalar x, SkScalar y,
                               const SkImage* mask, sk_sp<SkMaskFilter> outer, SkBlendMode mode) {
     SkMatrix matrix = SkMatrix::MakeScale(SkIntToScalar(image->width()) / mask->width(),
                                           SkIntToScalar(image->height() / mask->height()));
+    // The geometry of the drawImage is also translated by (x,y) so make the mask filter's
+    // coordinate system align with the rendered rectangle.
+    matrix.postTranslate(x, y);
     SkPaint paint;
     auto mf = SkShaderMaskFilter::Make(mask->makeShader(&matrix));
     if (outer) {
-        mf = SkMaskFilter::MakeCompose(outer, mf);
+        mf = SkMaskFilter::MakeCompose(outer->makeWithMatrix(matrix), mf);
     }
     paint.setMaskFilter(mf);
     paint.setAntiAlias(true);
@@ -36,7 +39,7 @@
         { r.fLeft, r.fTop }, { r.fRight, r.fBottom },
     };
     const SkColor colors[] = { 0, SK_ColorWHITE };
-    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kRepeat_TileMode);
+    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kRepeat);
 }
 
 DEF_SIMPLE_GM(shadermaskfilter_gradient, canvas, 512, 512) {
@@ -55,15 +58,14 @@
 }
 
 #include "Resources.h"
-DEF_SIMPLE_GM(shadermaskfilter_image, canvas, 560, 370) {
+DEF_SIMPLE_GM_CAN_FAIL(shadermaskfilter_image, canvas, errorMsg, 560, 370) {
     canvas->scale(1.25f, 1.25f);
 
     auto image = GetResourceAsImage("images/mandrill_128.png");
     auto mask = GetResourceAsImage("images/color_wheel.png");
     if (!image || !mask) {
-        skiagm::GM::DrawFailureMessage(canvas, "Could not load images. "
-                                               "Did you forget to set the resourcePath?");
-        return;
+        *errorMsg = "Could not load images. Did you forget to set the resourcePath?";
+        return skiagm::DrawResult::kFail;
     }
     auto blurmf = SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 5);
     auto gradmf = SkShaderMaskFilter::Make(make_shader(SkRect::MakeIWH(mask->width(),
@@ -79,6 +81,7 @@
         canvas->restore();
         canvas->translate(0, image->height() + 20.f);
     }
+    return skiagm::DrawResult::kOk;
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -93,9 +96,8 @@
 
     SkPictureRecorder recorder;
     recorder.beginRecording(1000, 1000)->drawPath(path, paint);
-    auto shader = SkShader::MakePictureShader(recorder.finishRecordingAsPicture(),
-                                              SkShader::kClamp_TileMode, SkShader::kClamp_TileMode,
-                                              nullptr, nullptr);
+    auto shader = recorder.finishRecordingAsPicture()->makeShader(SkTileMode::kClamp,
+                                                                  SkTileMode::kClamp);
     return SkShaderMaskFilter::Make(shader);
 }
 
@@ -175,7 +177,7 @@
 #include "SkMaskFilter.h"
 static sk_sp<SkImage> make_circle_image(SkCanvas* canvas, SkScalar radius, int margin) {
     const int n = SkScalarCeilToInt(radius) * 2 + margin * 2;
-    auto surf = sk_tool_utils::makeSurface(canvas, SkImageInfo::MakeN32Premul(n, n));
+    auto      surf = ToolUtils::makeSurface(canvas, SkImageInfo::MakeN32Premul(n, n));
     SkPaint paint;
     paint.setAntiAlias(true);
     surf->getCanvas()->drawCircle(n * 0.5f, n * 0.5f, radius, paint);
@@ -237,21 +239,22 @@
 
     using ShaderMakerT = sk_sp<SkShader>(*)(SkCanvas*, const SkMatrix& lm);
     static const ShaderMakerT gShaderMakers[] = {
-        [](SkCanvas* canvas, const SkMatrix& lm) -> sk_sp<SkShader> {
-            auto surface = sk_tool_utils::makeSurface(canvas,
-                                                      SkImageInfo::MakeN32Premul(kSize, kSize));
-            draw_mask(surface->getCanvas());
-            return surface->makeImageSnapshot()->makeShader(SkShader::kClamp_TileMode,
-                                                            SkShader::kClamp_TileMode, &lm);
-        },
-        [](SkCanvas*, const SkMatrix& lm) -> sk_sp<SkShader> {
-            SkPictureRecorder recorder;
-            draw_mask(recorder.beginRecording(kSize, kSize));
-            return SkShader::MakePictureShader(recorder.finishRecordingAsPicture(),
-                                               SkShader::kClamp_TileMode,
-                                               SkShader::kClamp_TileMode,
-                                               &lm, nullptr);
-        },
+            [](SkCanvas* canvas, const SkMatrix& lm) -> sk_sp<SkShader> {
+                auto surface =
+                        ToolUtils::makeSurface(canvas, SkImageInfo::MakeN32Premul(kSize, kSize));
+                draw_mask(surface->getCanvas());
+                return surface->makeImageSnapshot()->makeShader(
+                        SkTileMode::kClamp, SkTileMode::kClamp, &lm);
+            },
+            [](SkCanvas*, const SkMatrix& lm) -> sk_sp<SkShader> {
+                SkPictureRecorder recorder;
+                draw_mask(recorder.beginRecording(kSize, kSize));
+                return recorder.finishRecordingAsPicture()->makeShader(
+                                                   SkTileMode::kClamp,
+                                                   SkTileMode::kClamp,
+                                                   &lm,
+                                                   nullptr);
+            },
     };
 
     struct Config {
diff --git a/gm/shadertext3.cpp b/gm/shadertext3.cpp
index 150e205..1e54adc 100644
--- a/gm/shadertext3.cpp
+++ b/gm/shadertext3.cpp
@@ -5,10 +5,10 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkGradientShader.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -28,10 +28,10 @@
     SkPaint     paint;
 
     paint.setShader(SkGradientShader::MakeLinear(kPts0, kColors0, kPos,
-                    SK_ARRAY_COUNT(kColors0), SkShader::kClamp_TileMode));
+                    SK_ARRAY_COUNT(kColors0), SkTileMode::kClamp));
     canvas.drawPaint(paint);
     paint.setShader(SkGradientShader::MakeLinear(kPts1, kColors1, kPos,
-                    SK_ARRAY_COUNT(kColors1), SkShader::kClamp_TileMode));
+                    SK_ARRAY_COUNT(kColors1), SkTileMode::kClamp));
     canvas.drawPaint(paint);
 }
 
@@ -67,10 +67,10 @@
         SkPaint bmpPaint;
         bmpPaint.setAntiAlias(true);
         bmpPaint.setFilterQuality(kLow_SkFilterQuality);
-        bmpPaint.setAlpha(0x80);
+        bmpPaint.setAlphaf(0.5f);
         canvas->drawBitmap(fBmp, 5.f, 5.f, &bmpPaint);
 
-        SkFont font(sk_tool_utils::create_portable_typeface(), SkIntToScalar(kPointSize));
+        SkFont  font(ToolUtils::create_portable_typeface(), SkIntToScalar(kPointSize));
         SkPaint outlinePaint;
         outlinePaint.setStyle(SkPaint::kStroke_Style);
         outlinePaint.setStrokeWidth(0.f);
@@ -80,9 +80,9 @@
         // draw glyphs scaled up
         canvas->scale(2.f, 2.f);
 
-        constexpr SkShader::TileMode kTileModes[] = {
-            SkShader::kRepeat_TileMode,
-            SkShader::kMirror_TileMode,
+        constexpr SkTileMode kTileModes[] = {
+            SkTileMode::kRepeat,
+            SkTileMode::kMirror,
         };
 
         // position the baseline of the first run
@@ -100,8 +100,7 @@
                 SkPaint fillPaint;
                 fillPaint.setAntiAlias(true);
                 fillPaint.setFilterQuality(kLow_SkFilterQuality);
-                fillPaint.setShader(SkShader::MakeBitmapShader(fBmp, kTileModes[tm0],
-                                                               kTileModes[tm1], &localM));
+                fillPaint.setShader(fBmp.makeShader(kTileModes[tm0], kTileModes[tm1], &localM));
 
                 constexpr char kText[] = "B";
                 canvas->drawString(kText, 0, 0, font, fillPaint);
diff --git a/gm/shadows.cpp b/gm/shadows.cpp
index 1160e49..4cd1076 100644
--- a/gm/shadows.cpp
+++ b/gm/shadows.cpp
@@ -108,9 +108,7 @@
             canvas->drawBitmap(fBitmap, 10, 10, &paint);
 
             canvas->translate(0, 40);
-            paint.setShader(SkShader::MakeBitmapShader(
-                                          fBitmap, SkShader::kRepeat_TileMode,
-                                          SkShader::kRepeat_TileMode));
+            paint.setShader(fBitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat));
 
             // see bug.skia.org/562 (shows bug as reported)
             paint.setStyle(SkPaint::kFill_Style);
diff --git a/gm/shadowutils.cpp b/gm/shadowutils.cpp
index b88ecc2..b623acb 100644
--- a/gm/shadowutils.cpp
+++ b/gm/shadowutils.cpp
@@ -125,7 +125,7 @@
                 } else {
                     paint.setColor(kDebugColorOccluders == mode ? SK_ColorLTGRAY : SK_ColorWHITE);
                     if (SkToBool(flags & kTransparentOccluder_ShadowFlag)) {
-                        paint.setAlpha(128);
+                        paint.setAlphaf(0.5f);
                     }
                     paint.setStyle(SkPaint::kFill_Style);
                 }
diff --git a/gm/shallowgradient.cpp b/gm/shallowgradient.cpp
index d2336ab..2cca3c1 100644
--- a/gm/shallowgradient.cpp
+++ b/gm/shallowgradient.cpp
@@ -12,19 +12,19 @@
 
 static sk_sp<SkShader> shader_linear(const SkColor colors[], int count, const SkSize& size) {
     SkPoint pts[] = { { 0, 0 }, { size.width(), size.height() } };
-    return SkGradientShader::MakeLinear(pts, colors, nullptr, count, SkShader::kClamp_TileMode);
+    return SkGradientShader::MakeLinear(pts, colors, nullptr, count, SkTileMode::kClamp);
 }
 
 static sk_sp<SkShader> shader_radial(const SkColor colors[], int count, const SkSize& size) {
     SkPoint center = { size.width()/2, size.height()/2 };
     return SkGradientShader::MakeRadial(center, size.width()/2, colors, nullptr, count,
-                                        SkShader::kClamp_TileMode);
+                                        SkTileMode::kClamp);
 }
 
 static sk_sp<SkShader> shader_conical(const SkColor colors[], int count, const SkSize& size) {
     SkPoint center = { size.width()/2, size.height()/2 };
     return SkGradientShader::MakeTwoPointConical(center, size.width()/64, center, size.width()/2,
-                                                colors, nullptr, count, SkShader::kClamp_TileMode);
+                                                colors, nullptr, count, SkTileMode::kClamp);
 }
 
 static sk_sp<SkShader> shader_sweep(const SkColor colors[], int count, const SkSize& size) {
diff --git a/gm/shapes.cpp b/gm/shapes.cpp
index 0b01390..63ddf2a 100644
--- a/gm/shapes.cpp
+++ b/gm/shapes.cpp
@@ -95,7 +95,7 @@
         for (int i = 0; i < fShapes.count(); i++) {
             SkPaint paint(fPaint);
             paint.setColor(rand.nextU() & ~0x808080);
-            paint.setAlpha(128);  // Use alpha to detect double blends.
+            paint.setAlphaf(0.5f);  // Use alpha to detect double blends.
             const SkRRect& shape = fShapes[i];
             canvas->save();
             canvas->rotate(fRotations[i]);
@@ -141,7 +141,7 @@
             inner.transform(innerXform, &xformedInner);
             SkPaint paint(fPaint);
             paint.setColor(rand.nextU() & ~0x808080);
-            paint.setAlpha(128);  // Use alpha to detect double blends.
+            paint.setAlphaf(0.5f);  // Use alpha to detect double blends.
             canvas->save();
             canvas->rotate(fRotations[i]);
             canvas->drawDRRect(outer, xformedInner, paint);
diff --git a/gm/shapes_as_paths.cpp b/gm/shapes_as_paths.cpp
deleted file mode 100644
index 8305715..0000000
--- a/gm/shapes_as_paths.cpp
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "gm.h"
-
-#include "SkAutoPixmapStorage.h"
-#include "SkColorPriv.h"
-#include "SkImage.h"
-#include "SkPath.h"
-#include "SkSurface.h"
-
-namespace skiagm {
-
-static void draw_diff(SkCanvas* canvas, SkImage* imgA, SkImage* imgB) {
-    SkASSERT(imgA->dimensions() == imgB->dimensions());
-
-    int w = imgA->width(), h = imgA->height();
-
-    // First, draw the two images faintly overlaid
-    SkPaint paint;
-    paint.setAlpha(64);
-    paint.setBlendMode(SkBlendMode::kPlus);
-    canvas->drawImage(imgA, 0, 0, &paint);
-    canvas->drawImage(imgB, 0, 0, &paint);
-
-    // Next, read the pixels back, figure out if there are any differences
-    SkImageInfo info = SkImageInfo::MakeN32Premul(w, h);
-    SkAutoPixmapStorage pmapA;
-    SkAutoPixmapStorage pmapB;
-    pmapA.alloc(info);
-    pmapB.alloc(info);
-    if (!imgA->readPixels(pmapA, 0, 0) || !imgB->readPixels(pmapB, 0, 0)) {
-        skiagm::GM::DrawFailureMessage(canvas, "Failed to read pixels.");
-        return;
-    }
-
-    int maxDiffX = 0, maxDiffY = 0, maxDiff = 0;
-    SkBitmap highlight;
-    highlight.allocN32Pixels(w, h);
-    highlight.eraseColor(SK_ColorTRANSPARENT);
-
-    for (int y = 0; y < h; ++y) {
-        for (int x = 0; x < w; ++x) {
-            uint32_t pixelA = *pmapA.addr32(x, y);
-            uint32_t pixelB = *pmapB.addr32(x, y);
-            if (pixelA != pixelB) {
-                int diff =
-                    SkTAbs((int)(SkColorGetR(pixelA) - SkColorGetR(pixelB))) +
-                    SkTAbs((int)(SkColorGetG(pixelA) - SkColorGetG(pixelB))) +
-                    SkTAbs((int)(SkColorGetB(pixelA) - SkColorGetB(pixelB))) +
-                    SkTAbs((int)(SkColorGetA(pixelA) - SkColorGetA(pixelB)));
-                if (diff > maxDiff) {
-                    maxDiffX = x;
-                    maxDiffY = y;
-                    maxDiff = diff;
-                }
-                *highlight.getAddr32(x, y) = SkPackARGB32(0xA0, 0xA0, 0x00, 0x00);
-            }
-        }
-    }
-
-    SkPaint outline;
-    outline.setStyle(SkPaint::kStroke_Style);
-    outline.setColor(maxDiff == 0 ? 0xFF007F00 : 0xFF7F0000);
-
-    if (maxDiff > 0) {
-        // Call extra attention to the region we're going to zoom
-        SkPMColor yellow = SkPackARGB32(0xFF, 0xFF, 0xFF, 0x00);
-        *highlight.getAddr32(maxDiffX, maxDiffY) = yellow;
-        *highlight.getAddr32(SkTMax(maxDiffX - 1, 0), maxDiffY) = yellow;
-        *highlight.getAddr32(maxDiffX, SkTMax(maxDiffY - 1, 0)) = yellow;
-        *highlight.getAddr32(SkTMin(maxDiffX + 1, w - 1), maxDiffY) = yellow;
-        *highlight.getAddr32(maxDiffX, SkTMin(maxDiffY + 1, h - 1)) = yellow;
-
-        // Draw the overlay
-        canvas->drawBitmap(highlight, 0, 0);
-
-        // Draw zoom of largest pixel diff
-        SkBitmap bmpA, bmpB;
-        SkAssertResult(bmpA.installPixels(pmapA));
-        SkAssertResult(bmpB.installPixels(pmapB));
-        canvas->drawBitmapRect(bmpA, SkRect::MakeXYWH(maxDiffX - 5, maxDiffY - 5, 10, 10),
-                               SkRect::MakeXYWH(w, 0, w, h), nullptr);
-        canvas->drawBitmapRect(bmpB, SkRect::MakeXYWH(maxDiffX - 5, maxDiffY - 5, 10, 10),
-                               SkRect::MakeXYWH(2 * w, 0, w, h), nullptr);
-
-        // Add lines to separate zoom boxes
-        canvas->drawLine(w, 0, w, h, outline);
-        canvas->drawLine(2 * w, 0, 2 * w, h, outline);
-    }
-
-    // Draw outline of whole test region
-    canvas->drawRect(SkRect::MakeWH(3 * w, h), outline);
-}
-
-namespace {
-typedef std::function<void(SkCanvas*, const SkRect&, const SkPaint&)> ShapeDrawFunc;
-}
-
-/**
- *  Iterates over a variety of rect shapes, paint parameters, and matrices, calling two different
- *  user-supplied draw callbacks. Produces a grid clearly showing if the two callbacks produce the
- *  same visual results in all cases.
- */
-static void draw_rect_geom_diff_grid(SkCanvas* canvas, ShapeDrawFunc f1, ShapeDrawFunc f2) {
-    // Variables:
-    // - Fill, hairline, wide stroke
-    // - Axis aligned, rotated, scaled, scaled negative, perspective
-    // - Source geometry (normal, collapsed, inverted)
-    //
-    // Things not (yet?) tested:
-    // - AntiAlias on/off
-    // - StrokeAndFill
-    // - Cap/join
-    // - Anything even more elaborate...
-
-    const SkRect kRects[] = {
-        SkRect::MakeXYWH(10, 10, 30, 30),  // Normal
-        SkRect::MakeXYWH(10, 25, 30, 0),   // Collapsed
-        SkRect::MakeXYWH(10, 40, 30, -30), // Inverted
-    };
-
-    const struct { SkPaint::Style fStyle; SkScalar fStrokeWidth; } kStyles[] = {
-        { SkPaint::kFill_Style, 0 },   // Filled
-        { SkPaint::kStroke_Style, 0 }, // Hairline
-        { SkPaint::kStroke_Style, 5 }, // Wide stroke
-    };
-
-    SkMatrix mI = SkMatrix::I();
-    SkMatrix mRot;
-    mRot.setRotate(30, 25, 25);
-    SkMatrix mScale;
-    mScale.setScaleTranslate(0.5f, 1, 12.5f, 0);
-    SkMatrix mFlipX;
-    mFlipX.setScaleTranslate(-1, 1, 50, 0);
-    SkMatrix mFlipY;
-    mFlipY.setScaleTranslate(1, -1, 0, 50);
-    SkMatrix mFlipXY;
-    mFlipXY.setScaleTranslate(-1, -1, 50, 50);
-    SkMatrix mPersp;
-    mPersp.setIdentity();
-    mPersp.setPerspY(0.002f);
-
-    const SkMatrix* kMatrices[] = { &mI, &mRot, &mScale, &mFlipX, &mFlipY, &mFlipXY, &mPersp, };
-
-    canvas->translate(10, 10);
-
-    SkImageInfo info = canvas->imageInfo().makeWH(50, 50);
-    auto surface = canvas->makeSurface(info);
-    if (!surface) {
-        surface = SkSurface::MakeRasterN32Premul(50, 50);
-    }
-
-    for (const SkRect& rect : kRects) {
-        for (const auto& style : kStyles) {
-            canvas->save();
-
-            for (const SkMatrix* mat : kMatrices) {
-                SkPaint paint;
-                paint.setColor(SK_ColorWHITE);
-                paint.setAntiAlias(true);
-                paint.setStyle(style.fStyle);
-                paint.setStrokeWidth(style.fStrokeWidth);
-
-                // Do first draw
-                surface->getCanvas()->clear(SK_ColorBLACK);
-                surface->getCanvas()->save();
-                surface->getCanvas()->concat(*mat);
-                f1(surface->getCanvas(), rect, paint);
-                surface->getCanvas()->restore();
-                auto imgA = surface->makeImageSnapshot();
-
-                // Do second draw
-                surface->getCanvas()->clear(SK_ColorBLACK);
-                surface->getCanvas()->save();
-                surface->getCanvas()->concat(*mat);
-                f2(surface->getCanvas(), rect, paint);
-                surface->getCanvas()->restore();
-                auto imgB = surface->makeImageSnapshot();
-
-                draw_diff(canvas, imgA.get(), imgB.get());
-                canvas->translate(160, 0);
-            }
-            canvas->restore();
-            canvas->translate(0, 60);
-        }
-    }
-}
-
-static const int kNumRows = 9;
-static const int kNumColumns = 7;
-static const int kTotalWidth = kNumColumns * 160 + 10;
-static const int kTotalHeight = kNumRows * 60 + 10;
-
-DEF_SIMPLE_GM_BG(rects_as_paths, canvas, kTotalWidth, kTotalHeight, SK_ColorBLACK) {
-    // Drawing a rect vs. adding it to a path and drawing the path, should produce same results.
-    auto rectDrawFunc = [](SkCanvas* canvas, const SkRect& rect, const SkPaint& paint) {
-        canvas->drawRect(rect, paint);
-    };
-    auto pathDrawFunc = [](SkCanvas* canvas, const SkRect& rect, const SkPaint& paint) {
-        SkPath path;
-        path.addRect(rect);
-        canvas->drawPath(path, paint);
-    };
-
-    draw_rect_geom_diff_grid(canvas, rectDrawFunc, pathDrawFunc);
-}
-
-DEF_SIMPLE_GM_BG(ovals_as_paths, canvas, kTotalWidth, kTotalHeight, SK_ColorBLACK) {
-    // Drawing an oval vs. adding it to a path and drawing the path, should produce same results.
-    auto ovalDrawFunc = [](SkCanvas* canvas, const SkRect& rect, const SkPaint& paint) {
-        canvas->drawOval(rect, paint);
-    };
-    auto pathDrawFunc = [](SkCanvas* canvas, const SkRect& rect, const SkPaint& paint) {
-        SkPath path;
-        path.addOval(rect);
-        canvas->drawPath(path, paint);
-    };
-
-    draw_rect_geom_diff_grid(canvas, ovalDrawFunc, pathDrawFunc);
-}
-
-DEF_SIMPLE_GM_BG(arcs_as_paths, canvas, kTotalWidth, kTotalHeight, SK_ColorBLACK) {
-    // Drawing an arc vs. adding it to a path and drawing the path, should produce same results.
-    auto arcDrawFunc = [](SkCanvas* canvas, const SkRect& rect, const SkPaint& paint) {
-        canvas->drawArc(rect, 10, 200, false, paint);
-    };
-    auto pathDrawFunc = [](SkCanvas* canvas, const SkRect& rect, const SkPaint& paint) {
-        SkPath path;
-        path.addArc(rect, 10, 200);
-        canvas->drawPath(path, paint);
-    };
-
-    draw_rect_geom_diff_grid(canvas, arcDrawFunc, pathDrawFunc);
-}
-
-}
diff --git a/gm/sharedcorners.cpp b/gm/sharedcorners.cpp
index 7d9dedd..8d0e434 100644
--- a/gm/sharedcorners.cpp
+++ b/gm/sharedcorners.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkPaint.h"
 #include "SkPath.h"
 #include "SkPoint.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #include <array>
 #include <vector>
@@ -24,9 +24,7 @@
 // analytic AA is working properly.
 class SharedCornersGM : public GM {
 public:
-    SharedCornersGM() {
-        this->setBGColor(sk_tool_utils::color_to_565(0xFF1A65D7));
-    }
+    SharedCornersGM() { this->setBGColor(ToolUtils::color_to_565(0xFF1A65D7)); }
 
 protected:
     SkString onShortName() override {
diff --git a/gm/showmiplevels.cpp b/gm/showmiplevels.cpp
index 4a3a836..ce3c119 100644
--- a/gm/showmiplevels.cpp
+++ b/gm/showmiplevels.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "Resources.h"
 #include "SkColorPriv.h"
@@ -118,7 +118,7 @@
 
     static void DrawAndFrame(SkCanvas* canvas, const SkBitmap& orig, SkScalar x, SkScalar y) {
         SkBitmap bm;
-        sk_tool_utils::copy_to(&bm, orig.colorType(), orig);
+        ToolUtils::copy_to(&bm, orig.colorType(), orig);
         apply_gamma(bm);
 
         canvas->drawBitmap(bm, x, y, nullptr);
@@ -169,7 +169,7 @@
     }
 
     void onOnceBeforeDraw() override {
-        fBM[0] = sk_tool_utils::create_checkerboard_bitmap(fN, fN, SK_ColorBLACK, SK_ColorWHITE, 2);
+        fBM[0] = ToolUtils::create_checkerboard_bitmap(fN, fN, SK_ColorBLACK, SK_ColorWHITE, 2);
         fBM[1] = make_bitmap(fN, fN);
         fBM[2] = make_bitmap2(fN, fN);
         fBM[3] = make_bitmap3(fN, fN);
@@ -196,7 +196,7 @@
 
 void copy_to(SkBitmap* dst, SkColorType dstColorType, const SkBitmap& src) {
     if (kGray_8_SkColorType == dstColorType) {
-        return sk_tool_utils::copy_to_g8(dst, src);
+        return ToolUtils::copy_to_g8(dst, src);
     }
 
     const SkBitmap* srcPtr = &src;
@@ -206,7 +206,7 @@
         srcPtr = &tmp;
     }
 
-    sk_tool_utils::copy_to(dst, dstColorType, *srcPtr);
+    ToolUtils::copy_to(dst, dstColorType, *srcPtr);
 }
 
 /**
@@ -282,8 +282,7 @@
     }
 
     void onOnceBeforeDraw() override {
-        fBM[0] = sk_tool_utils::create_checkerboard_bitmap(fW, fH,
-                                                           SHOW_MIP_COLOR, SK_ColorWHITE, 2);
+        fBM[0] = ToolUtils::create_checkerboard_bitmap(fW, fH, SHOW_MIP_COLOR, SK_ColorWHITE, 2);
         fBM[1] = make_bitmap(fW, fH);
         fBM[2] = make_bitmap2(fW, fH);
         fBM[3] = make_bitmap3(fW, fH);
diff --git a/gm/simple_magnification.cpp b/gm/simple_magnification.cpp
index 6b70a43..3311f85 100644
--- a/gm/simple_magnification.cpp
+++ b/gm/simple_magnification.cpp
@@ -16,39 +16,36 @@
         SkImageInfo ii = SkImageInfo::Make(size, size, kN32_SkColorType, kPremul_SkAlphaType);
         sk_sp<SkSurface> surf(SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, ii, 0,
                                                           origin, nullptr));
-        if (!surf) {
-            return nullptr;
+        if (surf) {
+            SkCanvas* canvas = surf->getCanvas();
+
+            canvas->clear(SK_ColorRED);
+            const struct {
+                SkPoint fPt;
+                SkColor fColor;
+            } rec[] = {
+                { { 1.5f, 1.5f }, SK_ColorGREEN },
+                { { 2.5f, 1.5f }, SK_ColorBLUE },
+                { { 1.5f, 2.5f }, SK_ColorCYAN },
+                { { 2.5f, 2.5f }, SK_ColorGRAY },
+            };
+            SkPaint paint;
+            for (const auto& r : rec) {
+                paint.setColor(r.fColor);
+                canvas->drawPoints(SkCanvas::kPoints_PointMode, 1, &r.fPt, paint);
+            }
+            return surf->makeImageSnapshot();
         }
-
-        SkCanvas* canvas = surf->getCanvas();
-
-        canvas->clear(SK_ColorRED);
-        const struct {
-            SkPoint fPt;
-            SkColor fColor;
-        } rec[] = {
-            { { 1.5f, 1.5f }, SK_ColorGREEN },
-            { { 2.5f, 1.5f }, SK_ColorBLUE },
-            { { 1.5f, 2.5f }, SK_ColorCYAN },
-            { { 2.5f, 2.5f }, SK_ColorGRAY },
-        };
-        SkPaint paint;
-        for (const auto& r : rec) {
-            paint.setColor(r.fColor);
-            canvas->drawPoints(SkCanvas::kPoints_PointMode, 1, &r.fPt, paint);
-        }
-        return surf->makeImageSnapshot();
-    } else {
-        SkBitmap bm;
-        bm.allocN32Pixels(size, size);
-        bm.eraseColor(SK_ColorRED);
-        *bm.getAddr32(1, 1) = SkPackARGB32(0xFF, 0x00, 0xFF, 0x00);
-        *bm.getAddr32(2, 1) = SkPackARGB32(0xFF, 0x00, 0x00, 0xFF);
-        *bm.getAddr32(1, 2) = SkPackARGB32(0xFF, 0x00, 0xFF, 0xFF);
-        *bm.getAddr32(2, 2) = SkPackARGB32(0xFF, 0x88, 0x88, 0x88);
-
-        return SkImage::MakeFromBitmap(bm);
     }
+
+    SkBitmap bm;
+    bm.allocN32Pixels(size, size);
+    bm.eraseColor(SK_ColorRED);
+    *bm.getAddr32(1, 1) = SkPackARGB32(0xFF, 0x00, 0xFF, 0x00);
+    *bm.getAddr32(2, 1) = SkPackARGB32(0xFF, 0x00, 0x00, 0xFF);
+    *bm.getAddr32(1, 2) = SkPackARGB32(0xFF, 0x00, 0xFF, 0xFF);
+    *bm.getAddr32(2, 2) = SkPackARGB32(0xFF, 0x88, 0x88, 0x88);
+    return SkImage::MakeFromBitmap(bm);
 }
 
 /*
@@ -99,15 +96,14 @@
         canvas->restore();
     }
 
-    void onDraw(SkCanvas* canvas) override {
+    DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
         GrContext* context = canvas->getGrContext();
 
         sk_sp<SkImage> bottomLImg = make_image(context, kImgSize, kBottomLeft_GrSurfaceOrigin);
         sk_sp<SkImage> topLImg = make_image(context, kImgSize, kTopLeft_GrSurfaceOrigin);
         if (!bottomLImg || !topLImg) {
-            DrawFailureMessage(canvas, "Could not load images. "
-                                       "Did you forget to set the resourcePath?");
-            return;
+            *errorMsg = "Could not load images. Did you forget to set the resourcePath?";
+            return DrawResult::kFail;
         }
 
         int bigOffset = 2 * kPad + kImgSize;
@@ -116,6 +112,7 @@
         this->draw(canvas, topLImg, SkIPoint::Make(bigOffset, kPad), 1);
         this->draw(canvas, bottomLImg, SkIPoint::Make(kPad, bigOffset), 7);
         this->draw(canvas, topLImg, SkIPoint::Make(bigOffset, bigOffset), 7);
+        return DrawResult::kOk;
     }
 
 private:
diff --git a/gm/simpleaaclip.cpp b/gm/simpleaaclip.cpp
index c2d64df..b534b84 100644
--- a/gm/simpleaaclip.cpp
+++ b/gm/simpleaaclip.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkAAClip.h"
 #include "SkCanvas.h"
 #include "SkPath.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -27,7 +27,7 @@
     // need to copy for deferred drawing test to work
     SkBitmap bm2;
 
-    sk_tool_utils::copy_to(&bm2, bm.colorType(), bm);
+    ToolUtils::copy_to(&bm2, bm.colorType(), bm);
 
     canvas->drawBitmap(bm2,
                        SK_Scalar1 * mask.fBounds.fLeft,
@@ -147,16 +147,16 @@
             const char*     fName;
             SkClipOp        fOp;
         } gOps[] = {
-            { SK_ColorBLACK,    "Difference", kDifference_SkClipOp    },
-            { SK_ColorRED,      "Intersect",  kIntersect_SkClipOp     },
-            { sk_tool_utils::color_to_565(0xFF008800), "Union", kUnion_SkClipOp },
-            { SK_ColorGREEN,    "Rev Diff",   kReverseDifference_SkClipOp },
-            { SK_ColorYELLOW,   "Replace",    kReplace_SkClipOp       },
-            { SK_ColorBLUE,     "XOR",        kXOR_SkClipOp           },
+                {SK_ColorBLACK, "Difference", kDifference_SkClipOp},
+                {SK_ColorRED, "Intersect", kIntersect_SkClipOp},
+                {ToolUtils::color_to_565(0xFF008800), "Union", kUnion_SkClipOp},
+                {SK_ColorGREEN, "Rev Diff", kReverseDifference_SkClipOp},
+                {SK_ColorYELLOW, "Replace", kReplace_SkClipOp},
+                {SK_ColorBLUE, "XOR", kXOR_SkClipOp},
         };
 
         SkPaint textPaint;
-        SkFont font(sk_tool_utils::create_portable_typeface(), 24);
+        SkFont  font(ToolUtils::create_portable_typeface(), 24);
         int xOff = 0;
 
         for (size_t op = 0; op < SK_ARRAY_COUNT(gOps); op++) {
diff --git a/gm/simplerect.cpp b/gm/simplerect.cpp
index b27c348..8076bc9 100644
--- a/gm/simplerect.cpp
+++ b/gm/simplerect.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkBlurMask.h"
 #include "SkBlurMaskFilter.h"
 #include "SkPath.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 class SimpleRectGM : public skiagm::GM {
 public:
@@ -36,7 +36,7 @@
         SkRandom rand;
         SkPaint paint;
         for (int i = 0; i < 10000; i++) {
-            paint.setColor(sk_tool_utils::color_to_565(rand.nextU() | (0xFF << 24)));
+            paint.setColor(ToolUtils::color_to_565(rand.nextU() | (0xFF << 24)));
             SkScalar x = rand.nextRangeScalar(min, max);
             SkScalar y = rand.nextRangeScalar(min, max);
             SkScalar w = rand.nextRangeScalar(0, size);
@@ -45,9 +45,7 @@
         }
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
-        return true;
-    }
+    bool onAnimate(const AnimTimer& timer) override { return true; }
 
 private:
 
diff --git a/gm/skbug1719.cpp b/gm/skbug1719.cpp
index ca9e8bd..0d0b3f2 100644
--- a/gm/skbug1719.cpp
+++ b/gm/skbug1719.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkColorFilter.h"
 #include "SkMaskFilter.h"
 #include "SkPath.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 /**
  * This test exercises bug 1719. An anti-aliased blurred path is rendered through a soft clip. On
@@ -62,7 +62,7 @@
         paint.setColor(0xFF000000);
         paint.setMaskFilter(
             SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 0.78867501f));
-        paint.setColorFilter(SkColorFilter::MakeModeFilter(0xBFFFFFFF, SkBlendMode::kSrcIn));
+        paint.setColorFilter(SkColorFilters::Blend(0xBFFFFFFF, SkBlendMode::kSrcIn));
 
         canvas->clipPath(clipPath, true);
         canvas->drawPath(drawPath, paint);
diff --git a/gm/skbug_257.cpp b/gm/skbug_257.cpp
index 49f06dc..296babf 100644
--- a/gm/skbug_257.cpp
+++ b/gm/skbug_257.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkImage.h"
 #include "SkRRect.h"
 #include "SkTextBlob.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static void rotated_checkerboard_shader(SkPaint* paint,
                                         SkColor c1,
@@ -23,9 +23,7 @@
     SkMatrix matrix;
     matrix.setScale(0.75f, 0.75f);
     matrix.preRotate(30.0f);
-    paint->setShader(
-            SkShader::MakeBitmapShader(bm, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode,
-                                       &matrix));
+    paint->setShader(bm.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &matrix));
 }
 
 static void exercise_draw_pos_text(SkCanvas* canvas,
@@ -54,7 +52,7 @@
 
 static void test_text(SkCanvas* canvas, SkScalar size,
                       SkColor color, SkScalar Y) {
-    SkFont font(sk_tool_utils::create_portable_typeface(), 24);
+    SkFont font(ToolUtils::create_portable_typeface(), 24);
     font.setEdging(SkFont::Edging::kAlias);
     SkPaint type;
     type.setColor(color);
diff --git a/gm/skbug_8955.cpp b/gm/skbug_8955.cpp
new file mode 100644
index 0000000..82c94c2
--- /dev/null
+++ b/gm/skbug_8955.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+
+#include "SkFont.h"
+#include "SkPaint.h"
+#include "SkTextBlob.h"
+
+DEF_SIMPLE_GM(skbug_8955, canvas, 100, 100) {
+    SkPaint p;
+    SkFont font;
+    font.setSize(50);
+    auto blob = SkTextBlob::MakeFromText("+", 1, font);
+
+    // This bug only appeared when drawing the same text blob. We would generate no glyphs on the
+    // first draw, and fail to mark the blob as having any bitmap runs. That would prevent us from
+    // re-generating the blob on the second draw, even though the matrix had been restored.
+    canvas->save();
+    canvas->scale(0, 0);
+    canvas->drawTextBlob(blob, 30, 60, p);
+    canvas->restore();
+    canvas->drawTextBlob(blob, 30, 60, p);
+}
diff --git a/gm/smallpaths.cpp b/gm/smallpaths.cpp
index 779e0d6..69d7c08 100644
--- a/gm/smallpaths.cpp
+++ b/gm/smallpaths.cpp
@@ -48,8 +48,7 @@
     path->moveTo(c, c - r);
     for (int i = 1; i < n; i++) {
         rad += drad;
-        SkScalar cosV, sinV = SkScalarSinCos(rad, &cosV);
-        path->lineTo(c + cosV * r, c + sinV * r);
+        path->lineTo(c + SkScalarCos(rad) * r, c + SkScalarSin(rad) * r);
     }
     path->close();
     return r * 2 * 6 / 5;
diff --git a/gm/srcmode.cpp b/gm/srcmode.cpp
index f64b1d1..fb9b434 100644
--- a/gm/srcmode.cpp
+++ b/gm/srcmode.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkGradientShader.h"
 #include "SkPath.h"
 #include "SkSurface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #define W   SkIntToScalar(80)
 #define H   SkIntToScalar(60)
@@ -25,7 +25,7 @@
     const SkColor colors[] = { SK_ColorGREEN, SK_ColorBLUE };
     const SkPoint pts[] = { { 0, 0 }, { W, H } };
     paint->setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, SK_ARRAY_COUNT(colors),
-                                                  SkShader::kClamp_TileMode));
+                                                  SkTileMode::kClamp));
 }
 
 typedef void (*Proc)(SkCanvas*, const SkPaint&, const SkFont&);
@@ -74,7 +74,7 @@
         canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
 
         SkPaint paint;
-        SkFont font(sk_tool_utils::create_portable_typeface(), H/4);
+        SkFont  font(ToolUtils::create_portable_typeface(), H / 4);
         paint.setColor(0x80F60000);
 
         const Proc procs[] = {
diff --git a/gm/srgb.cpp b/gm/srgb.cpp
index 21571c5..3808563 100644
--- a/gm/srgb.cpp
+++ b/gm/srgb.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
-#include "SkCanvas.h"
-#include "SkImage.h"
 #include "Resources.h"
+#include "SkCanvas.h"
 #include "SkColorFilter.h"
+#include "SkImage.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 DEF_SIMPLE_GM(srgb_colorfilter, canvas, 512, 256*3) {
     auto img = GetResourceAsImage("images/mandrill_256.png");
@@ -21,9 +21,9 @@
         0, 0, 1, 0, 0,
         -1, 0, 0, 1, 0,
     };
-    auto cf0 = SkColorFilter::MakeMatrixFilterRowMajor255(array);
-    auto cf1 = SkColorFilter::MakeLinearToSRGBGamma();
-    auto cf2 = SkColorFilter::MakeSRGBToLinearGamma();
+    auto cf0 = SkColorFilters::MatrixRowMajor255(array);
+    auto cf1 = SkColorFilters::LinearToSRGBGamma();
+    auto cf2 = SkColorFilters::SRGBToLinearGamma();
 
     SkPaint p;
     p.setColorFilter(cf0);
diff --git a/gm/stringart.cpp b/gm/stringart.cpp
index cc423eb..ccd84f8 100644
--- a/gm/stringart.cpp
+++ b/gm/stringart.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
-#include "SkAnimTimer.h"
+#include "AnimTimer.h"
 #include "SkCanvas.h"
 #include "SkPath.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 // Reproduces https://code.google.com/p/chromium/issues/detail?id=279014
 
@@ -56,12 +56,12 @@
         SkPaint paint;
         paint.setAntiAlias(true);
         paint.setStyle(SkPaint::kStroke_Style);
-        paint.setColor(sk_tool_utils::color_to_565(0xFF007700));
+        paint.setColor(ToolUtils::color_to_565(0xFF007700));
 
         canvas->drawPath(path, paint);
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         constexpr SkScalar kDesiredDurationSecs = 3.0f;
 
         // Make the animation ping-pong back and forth but start in the fully drawn state
@@ -141,7 +141,7 @@
         }
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         SkScalar time = (float)(fmod(timer.secs(), fDur) / fDur);
         for (auto anim : fAnims) {
             anim->seek(time);
diff --git a/gm/stroke_rect_shader.cpp b/gm/stroke_rect_shader.cpp
index 1826f62..4833741 100644
--- a/gm/stroke_rect_shader.cpp
+++ b/gm/stroke_rect_shader.cpp
@@ -19,7 +19,7 @@
     constexpr SkColor kColors[] {SK_ColorRED, SK_ColorBLUE};
     SkPaint paint;
     sk_sp<SkShader> shader = SkGradientShader::MakeLinear(kPts, kColors, nullptr, 2,
-                                                          SkShader::kClamp_TileMode);
+                                                          SkTileMode::kClamp);
     paint.setShader(std::move(shader));
     paint.setStyle(SkPaint::kStroke_Style);
     // Do a large initial translate so that local coords disagree with device coords significantly
diff --git a/gm/strokedlines.cpp b/gm/strokedlines.cpp
index e3f409e..13862bc 100644
--- a/gm/strokedlines.cpp
+++ b/gm/strokedlines.cpp
@@ -5,14 +5,14 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkDashPathEffect.h"
 #include "SkGradientShader.h"
 #include "SkMaskFilter.h"
 #include "SkPaint.h"
 #include "SkPath.h"
 #include "SkPoint3.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 constexpr int kNumColumns = 6;
 constexpr int kNumRows = 8;
@@ -25,7 +25,8 @@
     SkScalar cos, sin;
 
     // first fin
-    sin = SkScalarSinCos(angle + (SK_ScalarPI/4), &cos);
+    sin = SkScalarSin(angle + (SK_ScalarPI/4));
+    cos = SkScalarCos(angle + (SK_ScalarPI/4));
     sin *= kRadius / 2.0f;
     cos *= kRadius / 2.0f;
 
@@ -35,7 +36,8 @@
     canvas->drawPath(p, paint);
 
     // second fin
-    sin = SkScalarSinCos(angle - (SK_ScalarPI/4), &cos);
+    sin = SkScalarSin(angle - (SK_ScalarPI/4));
+    cos = SkScalarCos(angle - (SK_ScalarPI/4));
     sin *= kRadius / 2.0f;
     cos *= kRadius / 2.0f;
 
@@ -52,7 +54,8 @@
 
     SkScalar sin, cos, angle = 0.0f;
     for (int i = 0; i < kNumSpokes/2; ++i, angle += SK_ScalarPI/(kNumSpokes/2)) {
-        sin = SkScalarSinCos(angle, &cos);
+        sin = SkScalarSin(angle);
+        cos = SkScalarCos(angle);
         sin *= kRadius;
         cos *= kRadius;
 
@@ -99,9 +102,7 @@
 // Various shaders are applied to ensure the coordinate spaces work out right.
 class StrokedLinesGM : public GM {
 public:
-    StrokedLinesGM() {
-        this->setBGColor(sk_tool_utils::color_to_565(0xFF1A65D7));
-    }
+    StrokedLinesGM() { this->setBGColor(ToolUtils::color_to_565(0xFF1A65D7)); }
 
 protected:
     SkString onShortName() override {
@@ -126,8 +127,7 @@
             SkPoint pts[] = { {-kRadius-kPad, -kRadius-kPad }, { kRadius+kPad, kRadius+kPad } };
 
             SkPaint p;
-            p.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, 2,
-                                                     SkShader::kClamp_TileMode, 0, nullptr));
+            p.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp));
 
             fPaints.push_back(p);
         }
@@ -153,10 +153,7 @@
             m.preScale(3.0f, 3.0f);
 
             SkPaint p;
-            p.setShader(SkShader::MakeBitmapShader(bm,
-                                                   SkShader::kRepeat_TileMode,
-                                                   SkShader::kRepeat_TileMode,
-                                                   &m));
+            p.setShader(bm.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &m));
             fPaints.push_back(p);
         }
         {
diff --git a/gm/strokefill.cpp b/gm/strokefill.cpp
index a557ab2..afce0c4 100644
--- a/gm/strokefill.cpp
+++ b/gm/strokefill.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkPathPriv.h"
 #include "SkTextFormatParams.h"
 #include "SkTypeface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 /* Generated on a Mac with:
  * paint.setTypeface(SkTypeface::CreateByName("Papyrus"));
@@ -258,7 +258,7 @@
 
         // use the portable typeface to generically test the fake bold code everywhere
         // (as long as the freetype option to do the bolding itself isn't enabled)
-        SkFont font(sk_tool_utils::create_portable_typeface("serif", SkFontStyle()), 100);
+        SkFont  font(ToolUtils::create_portable_typeface("serif", SkFontStyle()), 100);
         SkPaint paint;
         paint.setAntiAlias(true);
         paint.setStrokeWidth(SkIntToScalar(5));
diff --git a/gm/strokes.cpp b/gm/strokes.cpp
index 109a7fc..588ea06 100644
--- a/gm/strokes.cpp
+++ b/gm/strokes.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
-#include "SkPath.h"
-#include "SkRandom.h"
 #include "SkDashPathEffect.h"
 #include "SkParsePath.h"
+#include "SkPath.h"
+#include "SkRandom.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #define W   400
 #define H   400
@@ -31,7 +31,7 @@
     r->offset(-w/2 + woffset, -h/2 + hoffset);
 
     paint->setColor(rand.nextU());
-    paint->setAlpha(0xFF);
+    paint->setAlphaf(1.0f);
 }
 
 
@@ -118,8 +118,8 @@
         strokePaint = fillPaint;
         strokePaint.setStyle(SkPaint::kStroke_Style);
         for (int i = 0; i < 2; ++i) {
-            fillPaint.setAlpha(255);
-            strokePaint.setAlpha(255);
+            fillPaint.setAlphaf(1.0f);
+            strokePaint.setAlphaf(1.0f);
             strokePaint.setStrokeWidth(i ? 8.f : 10.f);
             strokePaint.setStrokeCap(i ? SkPaint::kSquare_Cap : SkPaint::kRound_Cap);
             canvas->save();
@@ -137,13 +137,13 @@
             canvas->translate(0, 20);
             canvas->drawPath(fRefPath[i * 2], fillPaint);
             strokePaint.setStrokeWidth(20);
-            strokePaint.setAlpha(127);
+            strokePaint.setAlphaf(0.5f);
             canvas->translate(0, 50);
             canvas->drawPath(fMoveHfPath, strokePaint);
             canvas->translate(0, 30);
             canvas->drawPath(fMoveZfPath, strokePaint);
             canvas->translate(0, 30);
-            fillPaint.setAlpha(127);
+            fillPaint.setAlphaf(0.5f);
             canvas->drawPath(fRefPath[1 + i * 2], fillPaint);
             canvas->translate(0, 30);
             canvas->drawPath(fCubicPath, strokePaint);
@@ -390,7 +390,7 @@
         SkPaint fillPaint(origPaint);
         fillPaint.setColor(SK_ColorRED);
         SkPaint strokePaint(origPaint);
-        strokePaint.setColor(sk_tool_utils::color_to_565(0xFF4444FF));
+        strokePaint.setColor(ToolUtils::color_to_565(0xFF4444FF));
 
         void (*procs[])(SkPath*, const SkRect&, SkString*) = {
             make0, make1, make2, make3, make4, make5
diff --git a/gm/stroketext.cpp b/gm/stroketext.cpp
index fb76975..bff1228 100644
--- a/gm/stroketext.cpp
+++ b/gm/stroketext.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkDashPathEffect.h"
 #include "SkTextBlob.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static void test_nulldev(SkCanvas* canvas) {
     SkBitmap bm;
@@ -75,7 +75,7 @@
     SkPaint paint;
     paint.setAntiAlias(true);
 
-    SkFont font(sk_tool_utils::create_portable_typeface(), kBelowThreshold_TextSize);
+    SkFont font(ToolUtils::create_portable_typeface(), kBelowThreshold_TextSize);
     draw_text_set(canvas, paint, font);
 
     canvas->translate(600, 0);
diff --git a/gm/subsetshader.cpp b/gm/subsetshader.cpp
index 545788b..aa27c79 100644
--- a/gm/subsetshader.cpp
+++ b/gm/subsetshader.cpp
@@ -10,14 +10,14 @@
 #include "SkShader.h"
 #include "gm.h"
 
-DEF_SIMPLE_GM(bitmap_subset_shader, canvas, 256, 256) {
+DEF_SIMPLE_GM_CAN_FAIL(bitmap_subset_shader, canvas, errorMsg, 256, 256) {
     canvas->clear(SK_ColorWHITE);
 
     SkBitmap source;
     if (!GetResourceAsBitmap("images/color_wheel.png", &source)) {
-        skiagm::GM::DrawFailureMessage(canvas, "Could not load images/color_wheel.png. "
-                                               "Did you forget to set the resourcePath?");
-        return;
+        *errorMsg = "Could not load images/color_wheel.png. "
+                    "Did you forget to set the resourcePath?";
+        return skiagm::DrawResult::kFail;
     }
     SkIRect left = SkIRect::MakeWH(source.width()/2, source.height());
     SkIRect right = SkIRect::MakeXYWH(source.width()/2, 0,
@@ -29,10 +29,11 @@
     SkMatrix matrix;
     matrix.setScale(0.75f, 0.75f);
     matrix.preRotate(30.0f);
-    SkShader::TileMode tm = SkShader::kRepeat_TileMode;
+    SkTileMode tm = SkTileMode::kRepeat;
     SkPaint paint;
-    paint.setShader(SkShader::MakeBitmapShader(leftBitmap, tm, tm, &matrix));
+    paint.setShader(leftBitmap.makeShader(tm, tm, &matrix));
     canvas->drawRect(SkRect::MakeWH(256.0f, 128.0f), paint);
-    paint.setShader(SkShader::MakeBitmapShader(rightBitmap, tm, tm, &matrix));
+    paint.setShader(rightBitmap.makeShader(tm, tm, &matrix));
     canvas->drawRect(SkRect::MakeXYWH(0, 128.0f, 256.0f, 128.0f), paint);
+    return skiagm::DrawResult::kOk;
 }
diff --git a/gm/surface.cpp b/gm/surface.cpp
index f8424ba..8606206 100644
--- a/gm/surface.cpp
+++ b/gm/surface.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkGradientShader.h"
 #include "SkSurface.h"
 #include "SkSurfaceProps.h"
 #include "SkTextUtils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #define W 200
 #define H 100
@@ -20,7 +20,7 @@
     int b = 0xBB;
     SkPoint pts[] = { { 0, 0 }, { W, H } };
     SkColor colors[] = { SkColorSetRGB(a, a, a), SkColorSetRGB(b, b, b) };
-    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode);
+    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
 }
 
 static sk_sp<SkSurface> make_surface(GrContext* ctx, const SkImageInfo& info, SkPixelGeometry geo) {
@@ -43,7 +43,7 @@
     paint.setShader(nullptr);
 
     paint.setColor(SK_ColorWHITE);
-    SkFont font(sk_tool_utils::create_portable_typeface(), 32);
+    SkFont font(ToolUtils::create_portable_typeface(), 32);
     font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
     SkTextUtils::DrawString(canvas, label, W / 2, H * 3 / 4, font, paint,
                             SkTextUtils::kCenter_Align);
@@ -124,7 +124,7 @@
     void onDraw(SkCanvas* canvas) override {
         SkImageInfo info = SkImageInfo::MakeN32Premul(100, 100);
 
-        auto surf(sk_tool_utils::makeSurface(canvas, info, nullptr));
+        auto surf(ToolUtils::makeSurface(canvas, info, nullptr));
         drawInto(surf->getCanvas());
 
         sk_sp<SkImage> image(surf->makeImageSnapshot());
@@ -149,7 +149,7 @@
 
 DEF_SIMPLE_GM(copy_on_write_retain, canvas, 256, 256) {
     const SkImageInfo info = SkImageInfo::MakeN32Premul(256, 256);
-    sk_sp<SkSurface> surf = sk_tool_utils::makeSurface(canvas, info);
+    sk_sp<SkSurface>  surf = ToolUtils::makeSurface(canvas, info);
 
     surf->getCanvas()->clear(SK_ColorRED);
     // its important that image survives longer than the next draw, so the surface will see
@@ -167,7 +167,7 @@
 
 DEF_SIMPLE_GM(copy_on_write_savelayer, canvas, 256, 256) {
     const SkImageInfo info = SkImageInfo::MakeN32Premul(256, 256);
-    sk_sp<SkSurface> surf = sk_tool_utils::makeSurface(canvas, info);
+    sk_sp<SkSurface>  surf = ToolUtils::makeSurface(canvas, info);
     surf->getCanvas()->clear(SK_ColorRED);
     // its important that image survives longer than the next draw, so the surface will see
     // an outstanding image, and have to decide if it should retain or discard those pixels
@@ -177,7 +177,7 @@
     // not trigger discard, even tho its alpha (SK_ColorBLUE) is opaque, since it is in a layer
     // with a non-opaque paint.
     SkPaint paint;
-    paint.setAlpha(0x40);
+    paint.setAlphaf(0.25f);
     surf->getCanvas()->saveLayer({0, 0, 256, 256}, &paint);
     surf->getCanvas()->clear(SK_ColorBLUE);
     surf->getCanvas()->restore();
@@ -188,7 +188,7 @@
 
 DEF_SIMPLE_GM(surface_underdraw, canvas, 256, 256) {
     SkImageInfo info = SkImageInfo::MakeN32Premul(256, 256, nullptr);
-    auto surf = sk_tool_utils::makeSurface(canvas, info);
+    auto        surf = ToolUtils::makeSurface(canvas, info);
 
     const SkIRect subset = SkIRect::MakeLTRB(180, 0, 256, 256);
 
@@ -196,7 +196,7 @@
     {
         SkPoint pts[] = {{0, 0}, {40, 50}};
         SkColor colors[] = {SK_ColorRED, SK_ColorBLUE};
-        auto sh = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kRepeat_TileMode);
+        auto sh = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kRepeat);
         SkPaint paint;
         paint.setShader(sh);
         surf->getCanvas()->drawPaint(paint);
@@ -225,7 +225,7 @@
     {
         SkPoint pts[] = {{SkIntToScalar(subset.left()), 0}, {SkIntToScalar(subset.right()), 0}};
         SkColor colors[] = {0xFF000000, 0};
-        auto sh = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode);
+        auto sh = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
         SkPaint paint;
         paint.setShader(sh);
         paint.setBlendMode(SkBlendMode::kDstIn);
diff --git a/gm/tablecolorfilter.cpp b/gm/tablecolorfilter.cpp
index 44d690d..6fe2aeb 100644
--- a/gm/tablecolorfilter.cpp
+++ b/gm/tablecolorfilter.cpp
@@ -18,7 +18,7 @@
         SK_ColorRED, 0, SK_ColorBLUE, SK_ColorWHITE
     };
     return SkGradientShader::MakeLinear(pts, colors, nullptr, SK_ARRAY_COUNT(colors),
-                                        SkShader::kClamp_TileMode);
+                                        SkTileMode::kClamp);
 }
 static void make_bm0(SkBitmap* bm) {
     int W = 120;
@@ -38,7 +38,7 @@
         SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE,
     };
     return SkGradientShader::MakeRadial(SkPoint::Make(cx, cy), cx, colors, nullptr,
-                                        SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode);
+                                        SK_ARRAY_COUNT(colors), SkTileMode::kClamp);
 }
 static void make_bm1(SkBitmap* bm) {
     int W = 120;
@@ -230,7 +230,7 @@
         int index = 0;
         for (int i = 0; i < MODE_COUNT; ++i) {
             for (int j = 0; j < COLOR_COUNT; ++j) {
-                filters[index++] = SkColorFilter::MakeModeFilter(fColors[j], fModes[i]);
+                filters[index++] = SkColorFilters::Blend(fColors[j], fModes[i]);
             }
         }
 
diff --git a/gm/testgradient.cpp b/gm/testgradient.cpp
index f871064..b2a4508 100644
--- a/gm/testgradient.cpp
+++ b/gm/testgradient.cpp
@@ -44,8 +44,7 @@
         SkColor colors[2] = {SK_ColorBLUE, SK_ColorYELLOW};
         SkPaint newPaint(paint);
         newPaint.setShader(SkGradientShader::MakeLinear(
-                points, colors, nullptr, 2,
-                SkShader::kClamp_TileMode, 0, nullptr));
+                points, colors, nullptr, 2, SkTileMode::kClamp));
         canvas->drawRect(rect, newPaint);
 
         SkRRect oval;
diff --git a/gm/textblob.cpp b/gm/textblob.cpp
index d22a35c..6d6bfab 100644
--- a/gm/textblob.cpp
+++ b/gm/textblob.cpp
@@ -19,8 +19,8 @@
 #include "SkTextBlob.h"
 #include "SkTypeface.h"
 #include "SkTypes.h"
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include <cstring>
 
@@ -84,7 +84,7 @@
 
 protected:
     void onOnceBeforeDraw() override {
-        fTypeface = sk_tool_utils::create_portable_typeface("serif", SkFontStyle());
+        fTypeface = ToolUtils::create_portable_typeface("serif", SkFontStyle());
         SkFont font(fTypeface);
         size_t txtLen = strlen(fText);
         int glyphCount = font.countText(fText, txtLen, kUTF8_SkTextEncoding);
diff --git a/gm/textblobblockreordering.cpp b/gm/textblobblockreordering.cpp
index 1121380..b847c29 100644
--- a/gm/textblobblockreordering.cpp
+++ b/gm/textblobblockreordering.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkCanvas.h"
 #include "SkTextBlob.h"
@@ -24,7 +24,7 @@
 
         // make textblob
         // Large text is used to trigger atlas eviction
-        SkFont font(sk_tool_utils::create_portable_typeface(), 56);
+        SkFont font(ToolUtils::create_portable_typeface(), 56);
         font.setEdging(SkFont::Edging::kAlias);
         const char* text = "AB";
 
@@ -32,7 +32,7 @@
         font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
 
         SkScalar yOffset = bounds.height();
-        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, yOffset - 30);
+        ToolUtils::add_to_text_blob(&builder, text, font, 0, yOffset - 30);
 
         // build
         fBlob = builder.make();
diff --git a/gm/textblobcolortrans.cpp b/gm/textblobcolortrans.cpp
index 40f58ed..faaf936 100644
--- a/gm/textblobcolortrans.cpp
+++ b/gm/textblobcolortrans.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "Resources.h"
 #include "SkCanvas.h"
@@ -29,7 +29,7 @@
 
         // make textblob
         // Large text is used to trigger atlas eviction
-        SkFont font(sk_tool_utils::create_portable_typeface(), 256);
+        SkFont font(ToolUtils::create_portable_typeface(), 256);
         font.setEdging(SkFont::Edging::kAlias);
         const char* text = "AB";
 
@@ -37,13 +37,13 @@
         font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
 
         SkScalar yOffset = bounds.height();
-        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, yOffset - 30);
+        ToolUtils::add_to_text_blob(&builder, text, font, 0, yOffset - 30);
 
         // A8
         font.setSize(28);
         text = "The quick brown fox jumps over the lazy dog.";
         font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
-        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, yOffset - 8);
+        ToolUtils::add_to_text_blob(&builder, text, font, 0, yOffset - 8);
 
         // build
         fBlob = builder.make();
diff --git a/gm/textblobgeometrychange.cpp b/gm/textblobgeometrychange.cpp
index 250d012..97dc6fc 100644
--- a/gm/textblobgeometrychange.cpp
+++ b/gm/textblobgeometrychange.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkCanvas.h"
 #include "SkSurface.h"
@@ -31,18 +31,18 @@
     void onDraw(SkCanvas* canvas) override {
         const char text[] = "Hamburgefons";
 
-        SkFont font(sk_tool_utils::create_portable_typeface(), 20);
+        SkFont font(ToolUtils::create_portable_typeface(), 20);
         font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
 
         SkTextBlobBuilder builder;
 
-        sk_tool_utils::add_to_text_blob(&builder, text, font, 10, 10);
+        ToolUtils::add_to_text_blob(&builder, text, font, 10, 10);
 
         sk_sp<SkTextBlob> blob(builder.make());
 
         SkImageInfo info = SkImageInfo::MakeN32Premul(200, 200);
         SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
-        auto surface = sk_tool_utils::makeSurface(canvas, info, &props);
+        auto           surface = ToolUtils::makeSurface(canvas, info, &props);
         SkCanvas* c = surface->getCanvas();
 
         // LCD text on white background
diff --git a/gm/textbloblooper.cpp b/gm/textbloblooper.cpp
index 51e8155..23b726c 100644
--- a/gm/textbloblooper.cpp
+++ b/gm/textbloblooper.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "Sk2DPathEffect.h"
 #include "SkBlurMask.h"
@@ -24,7 +24,7 @@
 constexpr int kWidth = 1250;
 constexpr int kHeight = 700;
 
-// Unlike the variant in sk_tool_utils, this version positions the glyphs on a diagonal
+// Unlike the variant in ToolUtils, this version positions the glyphs on a diagonal
 static void add_to_text_blob(SkTextBlobBuilder* builder, const char* text, const SkFont& font,
                              SkScalar x, SkScalar y) {
     SkTDArray<uint16_t> glyphs;
@@ -87,7 +87,7 @@
         SK_ColorCYAN, SK_ColorMAGENTA, SK_ColorYELLOW,
     };
     return SkGradientShader::MakeLinear(pts, colors, nullptr, SK_ARRAY_COUNT(colors),
-                                        SkShader::kClamp_TileMode);
+                                        SkTileMode::kClamp);
 }
 
 static void color_filter(SkPaint* paint) {
@@ -143,7 +143,7 @@
         const char* text = "The quick brown fox jumps over the lazy dog";
         font.setSubpixel(true);
         font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
-        font.setTypeface(sk_tool_utils::create_portable_typeface());
+        font.setTypeface(ToolUtils::create_portable_typeface());
         add_to_text_blob(&builder, text, font, 0, 0);
         fBlob = builder.make();
 
diff --git a/gm/textblobmixedsizes.cpp b/gm/textblobmixedsizes.cpp
index 2246350..51e560d 100644
--- a/gm/textblobmixedsizes.cpp
+++ b/gm/textblobmixedsizes.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "Resources.h"
 #include "SkBlurMask.h"
@@ -37,7 +37,7 @@
 
         const char* text = "Skia";
 
-        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, 0);
+        ToolUtils::add_to_text_blob(&builder, text, font, 0, 0);
 
         // large
         SkRect bounds;
@@ -45,43 +45,42 @@
         SkScalar yOffset = bounds.height();
         font.setSize(162);
 
-        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, yOffset);
+        ToolUtils::add_to_text_blob(&builder, text, font, 0, yOffset);
 
         // Medium
         font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
         yOffset += bounds.height();
         font.setSize(72);
 
-        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, yOffset);
+        ToolUtils::add_to_text_blob(&builder, text, font, 0, yOffset);
 
         // Small
         font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
         yOffset += bounds.height();
         font.setSize(32);
 
-        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, yOffset);
+        ToolUtils::add_to_text_blob(&builder, text, font, 0, yOffset);
 
         // micro (will fall out of distance field text even if distance field text is enabled)
         font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
         yOffset += bounds.height();
         font.setSize(14);
 
-        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, yOffset);
+        ToolUtils::add_to_text_blob(&builder, text, font, 0, yOffset);
 
         // Zero size.
         font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
         yOffset += bounds.height();
         font.setSize(0);
 
-        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, yOffset);
+        ToolUtils::add_to_text_blob(&builder, text, font, 0, yOffset);
 
         // build
         fBlob = builder.make();
     }
 
     SkString onShortName() override {
-        return SkStringPrintf("textblobmixedsizes%s%s",
-                              sk_tool_utils::platform_font_manager(),
+        return SkStringPrintf("textblobmixedsizes%s",
                               fUseDFT ? "_df" : "");
     }
 
diff --git a/gm/textblobrandomfont.cpp b/gm/textblobrandomfont.cpp
index 581e99d..cebc0bf 100644
--- a/gm/textblobrandomfont.cpp
+++ b/gm/textblobrandomfont.cpp
@@ -5,13 +5,13 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
+#include "RandomScalerContext.h"
 #include "Resources.h"
 #include "SkCanvas.h"
 #include "SkGradientShader.h"
-#include "SkRandomScalerContext.h"
 #include "SkStream.h"
 #include "SkSurface.h"
 #include "SkTextBlob.h"
@@ -42,7 +42,7 @@
         font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
 
         // Setup our random scaler context
-        auto typeface = sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle::Bold());
+        auto typeface = ToolUtils::create_portable_typeface("sans-serif", SkFontStyle::Bold());
         if (!typeface) {
             typeface = SkTypeface::MakeDefault();
         }
@@ -52,7 +52,7 @@
         SkRect bounds;
         font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
         y -= bounds.fTop;
-        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, y);
+        ToolUtils::add_to_text_blob(&builder, text, font, 0, y);
         y += bounds.fBottom;
 
         // A8
@@ -63,21 +63,21 @@
         font.setEdging(SkFont::Edging::kAntiAlias);
         font.measureText(bigtext1, strlen(bigtext1), kUTF8_SkTextEncoding, &bounds);
         y -= bounds.fTop;
-        sk_tool_utils::add_to_text_blob(&builder, bigtext1, font, 0, y);
+        ToolUtils::add_to_text_blob(&builder, bigtext1, font, 0, y);
         y += bounds.fBottom;
 
         font.measureText(bigtext2, strlen(bigtext2), kUTF8_SkTextEncoding, &bounds);
         y -= bounds.fTop;
-        sk_tool_utils::add_to_text_blob(&builder, bigtext2, font, 0, y);
+        ToolUtils::add_to_text_blob(&builder, bigtext2, font, 0, y);
         y += bounds.fBottom;
 
         // color emoji
-        if (sk_sp<SkTypeface> origEmoji = sk_tool_utils::emoji_typeface()) {
+        if (sk_sp<SkTypeface> origEmoji = ToolUtils::emoji_typeface()) {
             font.setTypeface(sk_make_sp<SkRandomTypeface>(origEmoji, paint, false));
-            const char* emojiText = sk_tool_utils::emoji_sample_text();
+            const char* emojiText = ToolUtils::emoji_sample_text();
             font.measureText(emojiText, strlen(emojiText), kUTF8_SkTextEncoding, &bounds);
             y -= bounds.fTop;
-            sk_tool_utils::add_to_text_blob(&builder, emojiText, font, 0, y);
+            ToolUtils::add_to_text_blob(&builder, emojiText, font, 0, y);
             y += bounds.fBottom;
         }
 
@@ -93,9 +93,10 @@
         return SkISize::Make(kWidth, kHeight);
     }
 
-    void onDraw(GrContext* context, GrRenderTargetContext*, SkCanvas* canvas) override {
+    DrawResult onDraw(GrContext* context, GrRenderTargetContext*, SkCanvas* canvas,
+                      SkString* errorMsg) override {
         // This GM exists to test a specific feature of the GPU backend.
-        // This GM uses sk_tool_utils::makeSurface which doesn't work well with vias.
+        // This GM uses ToolUtils::makeSurface which doesn't work well with vias.
         // This GM uses SkRandomTypeface which doesn't work well with serialization.
         canvas->drawColor(SK_ColorWHITE);
 
@@ -103,10 +104,10 @@
                                              kPremul_SkAlphaType,
                                              canvas->imageInfo().refColorSpace());
         SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
-        auto surface(sk_tool_utils::makeSurface(canvas, info, &props));
+        auto           surface(ToolUtils::makeSurface(canvas, info, &props));
         if (!surface) {
-            DrawFailureMessage(canvas, "This test requires a surface");
-            return;
+            *errorMsg = "This test requires a surface";
+            return DrawResult::kFail;
         }
 
         SkPaint paint;
@@ -139,6 +140,7 @@
         canvas->rotate(-0.05f);
         canvas->drawTextBlob(fBlob, 10, yOffset, paint);
         yOffset += stride;
+        return DrawResult::kOk;
     }
 
 private:
diff --git a/gm/textblobshader.cpp b/gm/textblobshader.cpp
index 971f628..358005a 100644
--- a/gm/textblobshader.cpp
+++ b/gm/textblobshader.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkCanvas.h"
 #include "SkGradientShader.h"
@@ -24,7 +24,7 @@
 private:
     void onOnceBeforeDraw() override {
         {
-            SkFont font(sk_tool_utils::create_portable_typeface());
+            SkFont      font(ToolUtils::create_portable_typeface());
             const char* txt = "Blobber";
             size_t txtLen = strlen(txt);
             fGlyphs.append(font.countText(txt, txtLen, kUTF8_SkTextEncoding));
@@ -35,7 +35,7 @@
         font.setSubpixel(true);
         font.setEdging(SkFont::Edging::kAntiAlias);
         font.setSize(30);
-        font.setTypeface(sk_tool_utils::create_portable_typeface());
+        font.setTypeface(ToolUtils::create_portable_typeface());
 
         SkTextBlobBuilder builder;
         int glyphCount = fGlyphs.count();
@@ -73,7 +73,7 @@
                                                SkIntToScalar(sz.height() / 2)),
                                                sz.width() * .66f, colors, pos,
                                                SK_ARRAY_COUNT(colors),
-                                               SkShader::kRepeat_TileMode);
+                                               SkTileMode::kRepeat);
     }
 
     SkString onShortName() override {
diff --git a/gm/textblobtransforms.cpp b/gm/textblobtransforms.cpp
index 24cb564..6977070 100644
--- a/gm/textblobtransforms.cpp
+++ b/gm/textblobtransforms.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "Resources.h"
 #include "SkCanvas.h"
@@ -26,19 +26,19 @@
         SkTextBlobBuilder builder;
 
         // make textblob.  To stress distance fields, we choose sizes appropriately
-        SkFont font(sk_tool_utils::create_portable_typeface(), 162);
+        SkFont font(ToolUtils::create_portable_typeface(), 162);
         font.setEdging(SkFont::Edging::kAlias);
         const char* text = "A";
 
         SkRect bounds;
         font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
-        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, 0);
+        ToolUtils::add_to_text_blob(&builder, text, font, 0, 0);
 
         // Medium
         SkScalar xOffset = bounds.width() + 5;
         font.setSize(72);
         text = "B";
-        sk_tool_utils::add_to_text_blob(&builder, text, font, xOffset, 0);
+        ToolUtils::add_to_text_blob(&builder, text, font, xOffset, 0);
 
         font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
         SkScalar yOffset = bounds.height();
@@ -46,7 +46,7 @@
         // Small
         font.setSize(32);
         text = "C";
-        sk_tool_utils::add_to_text_blob(&builder, text, font, xOffset, -yOffset - 10);
+        ToolUtils::add_to_text_blob(&builder, text, font, xOffset, -yOffset - 10);
 
         // build
         fBlob = builder.make();
diff --git a/gm/textblobuseaftergpufree.cpp b/gm/textblobuseaftergpufree.cpp
index 075c934..4c70561 100644
--- a/gm/textblobuseaftergpufree.cpp
+++ b/gm/textblobuseaftergpufree.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkCanvas.h"
 #include "SkSurface.h"
@@ -31,7 +31,7 @@
     void onDraw(GrContext* context, GrRenderTargetContext*, SkCanvas* canvas) override {
         const char text[] = "Hamburgefons";
 
-        SkFont font(sk_tool_utils::create_portable_typeface(), 20);
+        SkFont font(ToolUtils::create_portable_typeface(), 20);
         auto blob = SkTextBlob::MakeFromText(text, strlen(text), font);
 
         // draw textblob
diff --git a/gm/texteffects.cpp b/gm/texteffects.cpp
index 8fdaf8e..eafa0cf 100644
--- a/gm/texteffects.cpp
+++ b/gm/texteffects.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkBlurMask.h"
 #include "SkBlurMaskFilter.h"
@@ -106,8 +106,7 @@
 
     for (size_t font = 0; font < SK_ARRAY_COUNT(fam); ++font) {
         for (SkScalar textSize = 100; textSize > 10; textSize -= 20) {
-            SkFont skFont(
-                    sk_tool_utils::create_portable_typeface(fam[font], SkFontStyle()), textSize);
+            SkFont skFont(ToolUtils::create_portable_typeface(fam[font], SkFontStyle()), textSize);
             const SkScalar uWidth = textSize / 15;
             paint.setStrokeWidth(uWidth);
             paint.setStyle(SkPaint::kFill_Style);
@@ -230,7 +229,7 @@
     const char text[] = "Hyjay {worlp}.";
     const size_t length = strlen(text);
     SkFont font;
-    font.setTypeface(sk_tool_utils::create_portable_typeface());
+    font.setTypeface(ToolUtils::create_portable_typeface());
     font.setSize(100);
     font.setEdging(SkFont::Edging::kAntiAlias);
     const int count = font.countText(text, length, kUTF8_SkTextEncoding);
diff --git a/gm/texturedomaineffect.cpp b/gm/texturedomaineffect.cpp
index e56ee8b..0a6361a 100644
--- a/gm/texturedomaineffect.cpp
+++ b/gm/texturedomaineffect.cpp
@@ -13,9 +13,8 @@
 #include "GrContextPriv.h"
 #include "GrProxyProvider.h"
 #include "GrRenderTargetContextPriv.h"
+#include "SkBitmap.h"
 #include "SkGradientShader.h"
-#include "SkImage.h"
-#include "SkImage_Base.h"
 #include "SkSurface.h"
 #include "effects/GrTextureDomain.h"
 #include "ops/GrDrawOp.h"
@@ -51,56 +50,43 @@
     }
 
     void onOnceBeforeDraw() override {
-        // TODO: do this with gpu backend
-        SkImageInfo ii = SkImageInfo::Make(kTargetWidth, kTargetHeight, kN32_SkColorType,
-                                           kPremul_SkAlphaType);
-        auto surface = SkSurface::MakeRaster(ii);
-        SkCanvas* canvas = surface->getCanvas();
-        canvas->clear(0x00000000);
+        fBitmap.allocN32Pixels(kTargetWidth, kTargetHeight);
+        SkCanvas canvas(fBitmap);
+        canvas.clear(0x00000000);
         SkPaint paint;
 
         SkColor colors1[] = { SK_ColorCYAN, SK_ColorLTGRAY, SK_ColorGRAY };
         paint.setShader(SkGradientShader::MakeSweep(65.f, 75.f, colors1, nullptr,
                                                     SK_ARRAY_COUNT(colors1)));
-        canvas->drawOval(SkRect::MakeXYWH(-5.f, -5.f, kTargetWidth + 10.f, kTargetHeight + 10.f),
-                         paint);
+        canvas.drawOval(SkRect::MakeXYWH(-5.f, -5.f, kTargetWidth + 10.f, kTargetHeight + 10.f),
+                        paint);
 
         SkColor colors2[] = { SK_ColorMAGENTA, SK_ColorLTGRAY, SK_ColorYELLOW };
         paint.setShader(SkGradientShader::MakeSweep(45.f, 55.f, colors2, nullptr,
                                                     SK_ARRAY_COUNT(colors2)));
         paint.setBlendMode(SkBlendMode::kDarken);
-        canvas->drawOval(SkRect::MakeXYWH(-5.f, -5.f, kTargetWidth + 10.f, kTargetHeight + 10.f),
-                         paint);
+        canvas.drawOval(SkRect::MakeXYWH(-5.f, -5.f, kTargetWidth + 10.f, kTargetHeight + 10.f),
+                        paint);
 
         SkColor colors3[] = { SK_ColorBLUE, SK_ColorLTGRAY, SK_ColorGREEN };
         paint.setShader(SkGradientShader::MakeSweep(25.f, 35.f, colors3, nullptr,
                                                     SK_ARRAY_COUNT(colors3)));
         paint.setBlendMode(SkBlendMode::kLighten);
-        canvas->drawOval(SkRect::MakeXYWH(-5.f, -5.f, kTargetWidth + 10.f, kTargetHeight + 10.f),
-                         paint);
-
-        fImage = surface->makeImageSnapshot();
+        canvas.drawOval(SkRect::MakeXYWH(-5.f, -5.f, kTargetWidth + 10.f, kTargetHeight + 10.f),
+                        paint);
     }
 
-    void onDraw(GrContext* context, GrRenderTargetContext* renderTargetContext,
-                SkCanvas* canvas) override {
+    DrawResult onDraw(GrContext* context, GrRenderTargetContext* renderTargetContext,
+                      SkCanvas* canvas, SkString* errorMsg) override {
         GrProxyProvider* proxyProvider = context->priv().proxyProvider();
         sk_sp<GrTextureProxy> proxy;
-        if (fFilter == GrSamplerState::Filter::kMipMap) {
-            SkBitmap copy;
-            SkImageInfo info = as_IB(fImage)->onImageInfo().makeColorType(kN32_SkColorType);
-            if (!copy.tryAllocPixels(info) || !fImage->readPixels(copy.pixmap(), 0, 0)) {
-                DrawFailureMessage(canvas, "Failed to read pixels.");
-                return;
-            }
-            proxy = proxyProvider->createMipMapProxyFromBitmap(copy);
-        } else {
-            proxy = proxyProvider->createTextureProxy(
-                fImage, kNone_GrSurfaceFlags, 1, SkBudgeted::kYes, SkBackingFit::kExact);
-        }
+        GrMipMapped mipMapped = fFilter == GrSamplerState::Filter::kMipMap &&
+                                context->priv().caps()->mipMapSupport()
+                ? GrMipMapped::kYes : GrMipMapped::kNo;
+        proxy = proxyProvider->createProxyFromBitmap(fBitmap, mipMapped);
         if (!proxy) {
-            DrawFailureMessage(canvas, "Failed to create proxy.");
-            return;
+            *errorMsg = "Failed to create proxy.";
+            return DrawResult::kFail;
         }
 
         SkTArray<SkMatrix> textureMatrices;
@@ -110,12 +96,12 @@
         textureMatrices.back().setRotate(45.f, proxy->width() / 2.f, proxy->height() / 2.f);
 
         const SkIRect texelDomains[] = {
-            fImage->bounds(),
-            SkIRect::MakeXYWH(fImage->width() / 4 - 1, fImage->height() / 4 - 1,
-                              fImage->width() / 2 + 2, fImage->height() / 2 + 2),
+            fBitmap.bounds(),
+            SkIRect::MakeXYWH(fBitmap.width() / 4 - 1, fBitmap.height() / 4 - 1,
+                              fBitmap.width() / 2 + 2, fBitmap.height() / 2 + 2),
         };
 
-        SkRect renderRect = SkRect::Make(fImage->bounds());
+        SkRect renderRect = SkRect::Make(fBitmap.bounds());
         renderRect.outset(kDrawPad, kDrawPad);
 
         SkScalar y = kDrawPad + kTestPad;
@@ -150,6 +136,7 @@
                 y += renderRect.height() + kTestPad;
             }
         }
+        return DrawResult::kOk;
     }
 
 private:
@@ -157,7 +144,7 @@
     static constexpr SkScalar kTestPad = 10.f;
     static constexpr int      kTargetWidth = 100;
     static constexpr int      kTargetHeight = 100;
-    sk_sp<SkImage> fImage;
+    SkBitmap fBitmap;
     GrSamplerState::Filter fFilter;
 
     typedef GM INHERITED;
diff --git a/gm/tiledscaledbitmap.cpp b/gm/tiledscaledbitmap.cpp
index 78c54fd..4bf827f 100644
--- a/gm/tiledscaledbitmap.cpp
+++ b/gm/tiledscaledbitmap.cpp
@@ -62,8 +62,7 @@
         mat.setScale(121.f/360.f, 93.f/288.f);
         mat.postTranslate(-72, -72);
 
-        paint.setShader(SkShader::MakeBitmapShader(fBitmap, SkShader::kRepeat_TileMode,
-                                                   SkShader::kRepeat_TileMode, &mat));
+        paint.setShader(fBitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &mat));
         canvas->drawRect({ 8, 8, 1008, 608 }, paint);
     }
 
diff --git a/gm/tileimagefilter.cpp b/gm/tileimagefilter.cpp
index a2a731e..dd53151 100644
--- a/gm/tileimagefilter.cpp
+++ b/gm/tileimagefilter.cpp
@@ -10,8 +10,8 @@
 #include "SkImage.h"
 #include "SkImageSource.h"
 #include "SkTileImageFilter.h"
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #define WIDTH 400
 #define HEIGHT 200
@@ -36,13 +36,10 @@
 
     void onOnceBeforeDraw() override {
         fBitmap = SkImage::MakeFromBitmap(
-            sk_tool_utils::create_string_bitmap(50, 50, 0xD000D000, 10, 45, 50, "e"));
+                ToolUtils::create_string_bitmap(50, 50, 0xD000D000, 10, 45, 50, "e"));
 
         fCheckerboard = SkImage::MakeFromBitmap(
-            sk_tool_utils::create_checkerboard_bitmap(80, 80,
-                                                      0xFFA0A0A0,
-                                                      0xFF404040,
-                                                      8));
+                ToolUtils::create_checkerboard_bitmap(80, 80, 0xFFA0A0A0, 0xFF404040, 8));
     }
 
     void onDraw(SkCanvas* canvas) override {
@@ -94,7 +91,7 @@
         SkRect dstRect = SkRect::MakeWH(SkIntToScalar(fBitmap->width() * 2),
                                         SkIntToScalar(fBitmap->height() * 2));
         sk_sp<SkImageFilter> tile(SkTileImageFilter::Make(srcRect, dstRect, nullptr));
-        sk_sp<SkColorFilter> cf(SkColorFilter::MakeMatrixFilterRowMajor255(matrix));
+        sk_sp<SkColorFilter> cf(SkColorFilters::MatrixRowMajor255(matrix));
 
         SkPaint paint;
         paint.setImageFilter(SkColorFilterImageFilter::Make(std::move(cf), std::move(tile)));
@@ -113,8 +110,7 @@
         srcRect = SkRect::MakeXYWH(0, 0, 50, 50);
         dstRect = SkRect::MakeXYWH(0, 0, 100, 100);
         SkImageFilter::CropRect cropRect(SkRect::MakeXYWH(5, 5, 40, 40));
-        sk_sp<SkColorFilter> greenCF = SkColorFilter::MakeModeFilter(SK_ColorGREEN,
-                                                                     SkBlendMode::kSrc);
+        sk_sp<SkColorFilter> greenCF = SkColorFilters::Blend(SK_ColorGREEN, SkBlendMode::kSrc);
         sk_sp<SkImageFilter> green(SkColorFilterImageFilter::Make(std::move(greenCF),
                                                                   nullptr,
                                                                   &cropRect));
diff --git a/gm/tilemodes.cpp b/gm/tilemodes.cpp
index fa94deb..e9af37b 100644
--- a/gm/tilemodes.cpp
+++ b/gm/tilemodes.cpp
@@ -4,8 +4,6 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkColorFilter.h"
 #include "SkMaskFilter.h"
 #include "SkPath.h"
@@ -13,6 +11,8 @@
 #include "SkShader.h"
 #include "SkTextUtils.h"
 #include "SkUTF.h"
+#include "ToolUtils.h"
+#include "gm.h"
 // effects
 #include "SkGradientShader.h"
 #include "SkBlurDrawLooper.h"
@@ -31,13 +31,13 @@
 
     paint.setDither(true);
     paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, SK_ARRAY_COUNT(colors),
-                                                 SkShader::kClamp_TileMode));
+                                                 SkTileMode::kClamp));
     canvas.drawPaint(paint);
 }
 
 static void setup(SkPaint* paint, const SkBitmap& bm, bool filter,
-                  SkShader::TileMode tmx, SkShader::TileMode tmy) {
-    paint->setShader(SkShader::MakeBitmapShader(bm, tmx, tmy));
+                  SkTileMode tmx, SkTileMode tmy) {
+    paint->setShader(bm.makeShader(tmx, tmy));
     paint->setFilterQuality(filter ? kLow_SkFilterQuality : kNone_SkFilterQuality);
 }
 
@@ -80,7 +80,7 @@
 
     void onDraw(SkCanvas* canvas) override {
         SkPaint textPaint;
-        SkFont font(sk_tool_utils::create_portable_typeface(), 12);
+        SkFont  font(ToolUtils::create_portable_typeface(), 12);
 
         int size = fPowerOfTwoSize ? kPOTSize : kNPOTSize;
 
@@ -91,8 +91,8 @@
         constexpr bool gFilters[] = { false, true };
         static const char* gFilterNames[] = { "point", "bilinear" };
 
-        constexpr SkShader::TileMode gModes[] = {
-            SkShader::kClamp_TileMode, SkShader::kRepeat_TileMode, SkShader::kMirror_TileMode };
+        constexpr SkTileMode gModes[] = {
+            SkTileMode::kClamp, SkTileMode::kRepeat, SkTileMode::kMirror };
         static const char* gModeNames[] = { "C", "R", "M" };
 
         SkScalar y = SkIntToScalar(24);
@@ -103,7 +103,7 @@
                 SkPaint p;
                 p.setDither(true);
                 SkString str;
-                SkFont font(sk_tool_utils::create_portable_typeface());
+                SkFont   font(ToolUtils::create_portable_typeface());
                 str.printf("[%s,%s]", gModeNames[kx], gModeNames[ky]);
 
                 SkTextUtils::DrawString(canvas, str.c_str(), x + r.width()/2, y, font, p,
@@ -156,17 +156,17 @@
 constexpr int gWidth = 32;
 constexpr int gHeight = 32;
 
-static sk_sp<SkShader> make_bm(SkShader::TileMode tx, SkShader::TileMode ty) {
+static sk_sp<SkShader> make_bm(SkTileMode tx, SkTileMode ty) {
     SkBitmap bm;
     makebm(&bm, kN32_SkColorType, gWidth, gHeight);
-    return SkShader::MakeBitmapShader(bm, tx, ty);
+    return bm.makeShader(tx, ty);
 }
 
-static sk_sp<SkShader> make_grad(SkShader::TileMode tx, SkShader::TileMode ty) {
+static sk_sp<SkShader> make_grad(SkTileMode tx, SkTileMode ty) {
     SkPoint pts[] = { { 0, 0 }, { SkIntToScalar(gWidth), SkIntToScalar(gHeight)} };
     SkPoint center = { SkIntToScalar(gWidth)/2, SkIntToScalar(gHeight)/2 };
     SkScalar rad = SkIntToScalar(gWidth)/2;
-    SkColor colors[] = { 0xFFFF0000, sk_tool_utils::color_to_565(0xFF0044FF) };
+    SkColor  colors[] = {0xFFFF0000, ToolUtils::color_to_565(0xFF0044FF)};
 
     int index = (int)ty;
     switch (index % 3) {
@@ -181,7 +181,7 @@
     return nullptr;
 }
 
-typedef sk_sp<SkShader> (*ShaderProc)(SkShader::TileMode, SkShader::TileMode);
+typedef sk_sp<SkShader> (*ShaderProc)(SkTileMode, SkTileMode);
 
 class Tiling2GM : public skiagm::GM {
     ShaderProc fProc;
@@ -206,8 +206,8 @@
         const SkScalar h = SkIntToScalar(gHeight);
         SkRect r = { -w, -h, w*2, h*2 };
 
-        constexpr SkShader::TileMode gModes[] = {
-            SkShader::kClamp_TileMode, SkShader::kRepeat_TileMode, SkShader::kMirror_TileMode
+        constexpr SkTileMode gModes[] = {
+            SkTileMode::kClamp, SkTileMode::kRepeat, SkTileMode::kMirror
         };
         const char* gModeNames[] = {
             "Clamp", "Repeat", "Mirror"
@@ -216,7 +216,7 @@
         SkScalar y = SkIntToScalar(24);
         SkScalar x = SkIntToScalar(66);
 
-        SkFont font(sk_tool_utils::create_portable_typeface());
+        SkFont font(ToolUtils::create_portable_typeface());
 
         for (size_t kx = 0; kx < SK_ARRAY_COUNT(gModes); kx++) {
             SkString str(gModeNames[kx]);
@@ -268,30 +268,30 @@
     SkRect r = { -20, -20, img->width() + 20.0f, img->height() + 20.0f };
     canvas->translate(45, 45);
 
-    std::function<void(SkPaint*, SkShader::TileMode, SkShader::TileMode)> shader_procs[] = {
-        [img](SkPaint* paint, SkShader::TileMode tx, SkShader::TileMode ty) {
+    std::function<void(SkPaint*, SkTileMode, SkTileMode)> shader_procs[] = {
+        [img](SkPaint* paint, SkTileMode tx, SkTileMode ty) {
             // Test no filtering with decal mode
             paint->setShader(img->makeShader(tx, ty));
             paint->setFilterQuality(kNone_SkFilterQuality);
         },
-        [img](SkPaint* paint, SkShader::TileMode tx, SkShader::TileMode ty) {
+        [img](SkPaint* paint, SkTileMode tx, SkTileMode ty) {
             // Test bilerp approximation for decal mode (or clamp to border HW)
             paint->setShader(img->makeShader(tx, ty));
             paint->setFilterQuality(kLow_SkFilterQuality);
         },
-        [img](SkPaint* paint, SkShader::TileMode tx, SkShader::TileMode ty) {
+        [img](SkPaint* paint, SkTileMode tx, SkTileMode ty) {
             // Test bicubic filter with decal mode
             paint->setShader(img->makeShader(tx, ty));
             paint->setFilterQuality(kHigh_SkFilterQuality);
         },
-        [img](SkPaint* paint, SkShader::TileMode tx, SkShader::TileMode ty) {
+        [img](SkPaint* paint, SkTileMode tx, SkTileMode ty) {
             SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
             const SkPoint pts[] = {{ 0, 0 }, {img->width()*1.0f, img->height()*1.0f }};
             const SkScalar* pos = nullptr;
             const int count = SK_ARRAY_COUNT(colors);
             paint->setShader(SkGradientShader::MakeLinear(pts, colors, pos, count, tx));
         },
-        [img](SkPaint* paint, SkShader::TileMode tx, SkShader::TileMode ty) {
+        [img](SkPaint* paint, SkTileMode tx, SkTileMode ty) {
             SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
             const SkScalar* pos = nullptr;
             const int count = SK_ARRAY_COUNT(colors);
@@ -301,13 +301,13 @@
     };
 
     const struct XY {
-        SkShader::TileMode  fX;
-        SkShader::TileMode  fY;
+        SkTileMode  fX;
+        SkTileMode  fY;
     } pairs[] = {
-        { SkShader::kClamp_TileMode,    SkShader::kClamp_TileMode },
-        { SkShader::kClamp_TileMode,    SkShader::kDecal_TileMode },
-        { SkShader::kDecal_TileMode,    SkShader::kClamp_TileMode },
-        { SkShader::kDecal_TileMode,    SkShader::kDecal_TileMode },
+        { SkTileMode::kClamp,    SkTileMode::kClamp },
+        { SkTileMode::kClamp,    SkTileMode::kDecal },
+        { SkTileMode::kDecal,    SkTileMode::kClamp },
+        { SkTileMode::kDecal,    SkTileMode::kDecal },
     };
     for (const auto& p : pairs) {
         SkPaint paint;
diff --git a/gm/tilemodes_scaled.cpp b/gm/tilemodes_scaled.cpp
index 9366927..21d4754 100644
--- a/gm/tilemodes_scaled.cpp
+++ b/gm/tilemodes_scaled.cpp
@@ -4,8 +4,6 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkColorFilter.h"
 #include "SkMaskFilter.h"
 #include "SkPath.h"
@@ -13,6 +11,8 @@
 #include "SkShader.h"
 #include "SkTextUtils.h"
 #include "SkUTF.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 // effects
 #include "SkGradientShader.h"
@@ -30,13 +30,13 @@
 
     paint.setDither(true);
     paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos,
-                SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode));
+                SK_ARRAY_COUNT(colors), SkTileMode::kClamp));
     canvas.drawPaint(paint);
 }
 
 static void setup(SkPaint* paint, const SkBitmap& bm, SkFilterQuality filter_level,
-                  SkShader::TileMode tmx, SkShader::TileMode tmy) {
-    paint->setShader(SkShader::MakeBitmapShader(bm, tmx, tmy));
+                  SkTileMode tmx, SkTileMode tmy) {
+    paint->setShader(bm.makeShader(tmx, tmy));
     paint->setFilterQuality(filter_level);
 }
 
@@ -78,7 +78,7 @@
 
     void onDraw(SkCanvas* canvas) override {
         SkPaint textPaint;
-        SkFont font(sk_tool_utils::create_portable_typeface(), 12);
+        SkFont  font(ToolUtils::create_portable_typeface(), 12);
 
         float scale = 32.f/kPOTSize;
 
@@ -95,8 +95,8 @@
               kHigh_SkFilterQuality };
         const char* gFilterNames[] = { "None", "Low", "Medium", "High" };
 
-        constexpr SkShader::TileMode gModes[] = {
-            SkShader::kClamp_TileMode, SkShader::kRepeat_TileMode, SkShader::kMirror_TileMode };
+        constexpr SkTileMode gModes[] = {
+            SkTileMode::kClamp, SkTileMode::kRepeat, SkTileMode::kMirror };
         const char* gModeNames[] = { "C", "R", "M" };
 
         SkScalar y = SkIntToScalar(24);
@@ -156,17 +156,17 @@
 constexpr int gWidth = 32;
 constexpr int gHeight = 32;
 
-static sk_sp<SkShader> make_bm(SkShader::TileMode tx, SkShader::TileMode ty) {
+static sk_sp<SkShader> make_bm(SkTileMode tx, SkTileMode ty) {
     SkBitmap bm;
     makebm(&bm, kN32_SkColorType, gWidth, gHeight);
-    return SkShader::MakeBitmapShader(bm, tx, ty);
+    return bm.makeShader(tx, ty);
 }
 
-static sk_sp<SkShader> make_grad(SkShader::TileMode tx, SkShader::TileMode ty) {
+static sk_sp<SkShader> make_grad(SkTileMode tx, SkTileMode ty) {
     SkPoint pts[] = { { 0, 0 }, { SkIntToScalar(gWidth), SkIntToScalar(gHeight)} };
     SkPoint center = { SkIntToScalar(gWidth)/2, SkIntToScalar(gHeight)/2 };
     SkScalar rad = SkIntToScalar(gWidth)/2;
-    SkColor colors[] = { 0xFFFF0000, sk_tool_utils::color_to_565(0xFF0044FF) };
+    SkColor  colors[] = {0xFFFF0000, ToolUtils::color_to_565(0xFF0044FF)};
 
     int index = (int)ty;
     switch (index % 3) {
@@ -181,7 +181,7 @@
     return nullptr;
 }
 
-typedef sk_sp<SkShader> (*ShaderProc)(SkShader::TileMode, SkShader::TileMode);
+typedef sk_sp<SkShader> (*ShaderProc)(SkTileMode, SkTileMode);
 
 class ScaledTiling2GM : public skiagm::GM {
     ShaderProc fProc;
@@ -206,8 +206,8 @@
         const SkScalar h = SkIntToScalar(gHeight);
         SkRect r = { -w, -h, w*2, h*2 };
 
-        constexpr SkShader::TileMode gModes[] = {
-            SkShader::kClamp_TileMode, SkShader::kRepeat_TileMode, SkShader::kMirror_TileMode
+        constexpr SkTileMode gModes[] = {
+            SkTileMode::kClamp, SkTileMode::kRepeat, SkTileMode::kMirror
         };
         const char* gModeNames[] = {
             "Clamp", "Repeat", "Mirror"
@@ -216,7 +216,7 @@
         SkScalar y = SkIntToScalar(24);
         SkScalar x = SkIntToScalar(66);
 
-        SkFont font(sk_tool_utils::create_portable_typeface());
+        SkFont font(ToolUtils::create_portable_typeface());
 
         for (size_t kx = 0; kx < SK_ARRAY_COUNT(gModes); kx++) {
             SkString str(gModeNames[kx]);
diff --git a/gm/tinybitmap.cpp b/gm/tinybitmap.cpp
index 3592915..0d99f9e 100644
--- a/gm/tinybitmap.cpp
+++ b/gm/tinybitmap.cpp
@@ -4,12 +4,12 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
-#include "gm.h"
-#include "sk_tool_utils.h"
+#include "SkCanvas.h"
 #include "SkColorPriv.h"
 #include "SkShader.h"
-#include "SkCanvas.h"
 #include "SkUTF.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -36,9 +36,8 @@
     virtual void onDraw(SkCanvas* canvas) {
         SkBitmap bm = make_bitmap();
         SkPaint paint;
-        paint.setAlpha(0x80);
-        paint.setShader(SkShader::MakeBitmapShader(bm, SkShader::kRepeat_TileMode,
-                                                   SkShader::kMirror_TileMode));
+        paint.setAlphaf(0.5f);
+        paint.setShader(bm.makeShader(SkTileMode::kRepeat, SkTileMode::kMirror));
         canvas->drawPaint(paint);
     }
 
diff --git a/gm/tosrgb_colorfilter.cpp b/gm/tosrgb_colorfilter.cpp
deleted file mode 100644
index 8637386..0000000
--- a/gm/tosrgb_colorfilter.cpp
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "gm.h"
-
-#include "SkColorPriv.h"
-#include "SkToSRGBColorFilter.h"
-
-DEF_SIMPLE_GM_BG(tosrgb_colorfilter, canvas, 130, 130, SK_ColorBLACK) {
-    // Src bitmap with some colors that we're going to interpret as being in a few different spaces
-    SkBitmap bmp;
-    bmp.allocN32Pixels(3, 2);
-    SkPMColor* pixels = reinterpret_cast<SkPMColor*>(bmp.getPixels());
-    pixels[0] = SkPackARGB32(0xFF, 0xA0, 0x00, 0x00);
-    pixels[1] = SkPackARGB32(0xFF, 0x00, 0xA0, 0x00);
-    pixels[2] = SkPackARGB32(0xFF, 0x00, 0x00, 0xA0);
-    pixels[3] = SkPackARGB32(0xFF, 0x00, 0xA0, 0xA0);
-    pixels[4] = SkPackARGB32(0xFF, 0xA0, 0x00, 0xA0);
-    pixels[5] = SkPackARGB32(0xFF, 0xA0, 0xA0, 0x00);
-
-    // Reference image
-    canvas->drawBitmapRect(bmp, SkRect::MakeXYWH(10, 10, 50, 50), nullptr);
-
-    auto srgb = SkColorSpace::MakeSRGB();
-    auto rec2020 = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kRec2020);
-
-    // NarrowGamut RGB (an artifically smaller than sRGB gamut)
-    skcms_Matrix3x3 narrowGamutRGBMatrix;
-    SkAssertResult(skcms_PrimariesToXYZD50(
-        0.54f, 0.33f,     // Rx, Ry
-        0.33f, 0.50f,     // Gx, Gy
-        0.25f, 0.20f,     // Bx, By
-        0.3127f, 0.3290f, // Wx, Wy
-        &narrowGamutRGBMatrix));
-    auto narrow = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, narrowGamutRGBMatrix);
-
-    SkPaint paint;
-
-    // Transforming sRGB -> sRGB should do nothing. Top two squares should look identical.
-    paint.setColorFilter(SkToSRGBColorFilter::Make(srgb));
-    canvas->drawBitmapRect(bmp, SkRect::MakeXYWH(70, 10, 50, 50), &paint);
-
-    // Rec2020 -> sRGB should produce more vivid colors.
-    paint.setColorFilter(SkToSRGBColorFilter::Make(rec2020));
-    canvas->drawBitmapRect(bmp, SkRect::MakeXYWH(10, 70, 50, 50), &paint);
-
-    // Narrow -> sRGB should produce more muted colors.
-    paint.setColorFilter(SkToSRGBColorFilter::Make(narrow));
-    canvas->drawBitmapRect(bmp, SkRect::MakeXYWH(70, 70, 50, 50), &paint);
-}
diff --git a/gm/transparency.cpp b/gm/transparency.cpp
index 1b008fe..645dfb9 100644
--- a/gm/transparency.cpp
+++ b/gm/transparency.cpp
@@ -30,8 +30,7 @@
         shaderColors[0] = SK_AlphaTRANSPARENT;
         shaderColors[1] = kColors[i];
         SkPaint p;
-        p.setShader(SkGradientShader::MakeLinear(pts, shaderColors, nullptr, 2,
-                                                 SkShader::kClamp_TileMode));
+        p.setShader(SkGradientShader::MakeLinear(pts, shaderColors, nullptr, 2, SkTileMode::kClamp));
         canvas->drawRect(SkRect::MakeXYWH(0, i * kRowHeight, width, kRowHeight), p);
     }
 }
@@ -43,7 +42,7 @@
     bm.eraseColor(c1);
     bm.eraseArea(SkIRect::MakeLTRB(0, 0, size, size), c2);
     bm.eraseArea(SkIRect::MakeLTRB(size, size, 2 * size, 2 * size), c2);
-    return SkShader::MakeBitmapShader(bm, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode);
+    return bm.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat);
 }
 
 // http://crrev.com/834303005
diff --git a/gm/typeface.cpp b/gm/typeface.cpp
index 39d27fd..35aec97 100644
--- a/gm/typeface.cpp
+++ b/gm/typeface.cpp
@@ -5,8 +5,6 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "Resources.h"
 #include "SkBlurTypes.h"
 #include "SkCanvas.h"
@@ -17,6 +15,8 @@
 #include "SkTextBlob.h"
 #include "SkTypeface.h"
 #include "SkTypes.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 static void getGlyphPositions(const SkFont& font, const uint16_t glyphs[],
                              int count, SkScalar x, SkScalar y, SkPoint pos[]) {
@@ -101,7 +101,6 @@
         if (fApplyKerning) {
             name.append("_kerning");
         }
-        name.append(sk_tool_utils::platform_font_manager());
         return name;
     }
 
@@ -149,8 +148,7 @@
 static void draw_typeface_rendering_gm(SkCanvas* canvas, sk_sp<SkTypeface> face,
                                        char character = 'A') {
     struct AliasType {
-        bool antiAlias;
-        bool subpixelAntitalias;
+        SkFont::Edging edging;
         bool inLayer;
     } constexpr aliasTypes[] {
 #ifndef SK_BUILD_FOR_IOS
@@ -164,24 +162,12 @@
         //       0x330b19d8 <+88>: ldr    r0, [r4, #0x4]
         // Disable testing embedded bitmaps on iOS for now.
         // See https://bug.skia.org/5530 .
-        { false, false, false },  // aliased
+        { SkFont::Edging::kAlias            , false },
 #endif
-        { true,  false, false },  // anti-aliased
-        { true,  true , false },  // subpixel anti-aliased
-        { true,  false, true  },  // anti-aliased in layer (flat pixel geometry)
-        { true,  true , true  },  // subpixel anti-aliased in layer (flat pixel geometry)
-    };
-
-    auto compute_edging = [](AliasType at) {
-        if (at.antiAlias) {
-            if (at.subpixelAntitalias) {
-                return SkFont::Edging::kSubpixelAntiAlias;
-            } else {
-                return SkFont::Edging::kAntiAlias;
-            }
-        } else {
-            return SkFont::Edging::kAlias;
-        }
+        { SkFont::Edging::kAntiAlias        , false },
+        { SkFont::Edging::kSubpixelAntiAlias, false },
+        { SkFont::Edging::kAntiAlias        , true  },
+        { SkFont::Edging::kSubpixelAntiAlias, true  },
     };
 
     // The hintgasp.ttf is designed for the following sizes to be different.
@@ -229,7 +215,7 @@
             font.setSubpixel(subpixel.requested);
 
             for (const AliasType& alias : aliasTypes) {
-                font.setEdging(compute_edging(alias));
+                font.setEdging(alias.edging);
                 SkAutoCanvasRestore acr(canvas, false);
                 if (alias.inLayer) {
                     canvas->saveLayer(nullptr, &paint);
@@ -292,7 +278,7 @@
 
             font.setEmbolden(fakeBold);
             for (const AliasType& alias : aliasTypes) {
-                font.setEdging(compute_edging(alias));
+                font.setEdging(alias.edging);
                 SkAutoCanvasRestore acr(canvas, false);
                 if (alias.inLayer) {
                     canvas->saveLayer(nullptr, &paint);
@@ -343,7 +329,7 @@
                 y += dy;
                 x = 5;
 
-                font.setEdging(compute_edging(alias));
+                font.setEdging(alias.edging);
                 SkAutoCanvasRestore acr(canvas, false);
                 if (alias.inLayer) {
                     canvas->saveLayer(nullptr, &paint);
@@ -363,9 +349,7 @@
     }
 }
 
-DEF_SIMPLE_GM_BG_NAME(typefacerendering, canvas, 640, 840, SK_ColorWHITE,
-                      SkStringPrintf("typefacerendering%s",
-                                     sk_tool_utils::platform_font_manager())) {
+DEF_SIMPLE_GM(typefacerendering, canvas, 640, 840) {
     if (sk_sp<SkTypeface> face = MakeResourceAsTypeface("fonts/hintgasp.ttf")) {
         draw_typeface_rendering_gm(canvas, std::move(face));
     }
@@ -374,18 +358,14 @@
 // Type1 fonts don't currently work in Skia on Windows.
 #ifndef SK_BUILD_FOR_WIN
 
-DEF_SIMPLE_GM_BG_NAME(typefacerendering_pfa, canvas, 640, 840, SK_ColorWHITE,
-                      SkStringPrintf("typefacerendering_pfa%s",
-                                     sk_tool_utils::platform_font_manager())) {
+DEF_SIMPLE_GM(typefacerendering_pfa, canvas, 640, 840) {
     if (sk_sp<SkTypeface> face = MakeResourceAsTypeface("fonts/Roboto2-Regular.pfa")) {
         // This subsetted typeface doesn't have the character 'A'.
         draw_typeface_rendering_gm(canvas, std::move(face), 'O');
     }
 }
 
-DEF_SIMPLE_GM_BG_NAME(typefacerendering_pfb, canvas, 640, 840, SK_ColorWHITE,
-                      SkStringPrintf("typefacerendering_pfb%s",
-                                     sk_tool_utils::platform_font_manager())) {
+DEF_SIMPLE_GM(typefacerendering_pfb, canvas, 640, 840) {
     if (sk_sp<SkTypeface> face = MakeResourceAsTypeface("fonts/Roboto2-Regular.pfb")) {
         draw_typeface_rendering_gm(canvas, std::move(face), 'O');
     }
diff --git a/gm/variedtext.cpp b/gm/variedtext.cpp
index c11d00e..0aea8d8 100644
--- a/gm/variedtext.cpp
+++ b/gm/variedtext.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkPath.h"
-#include "SkTypeface.h"
 #include "SkRandom.h"
+#include "SkTypeface.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 /**
  * Draws text with random parameters. The text draws each get their own clip rect. It is also
@@ -53,10 +53,10 @@
         SkScalar h = SkIntToScalar(size.fHeight);
 
         static_assert(4 == SK_ARRAY_COUNT(fTypefaces), "typeface_cnt");
-        fTypefaces[0] = sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle());
-        fTypefaces[1] = sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle::Bold());
-        fTypefaces[2] = sk_tool_utils::create_portable_typeface("serif", SkFontStyle());
-        fTypefaces[3] = sk_tool_utils::create_portable_typeface("serif", SkFontStyle::Bold());
+        fTypefaces[0] = ToolUtils::create_portable_typeface("sans-serif", SkFontStyle());
+        fTypefaces[1] = ToolUtils::create_portable_typeface("sans-serif", SkFontStyle::Bold());
+        fTypefaces[2] = ToolUtils::create_portable_typeface("serif", SkFontStyle());
+        fTypefaces[3] = ToolUtils::create_portable_typeface("serif", SkFontStyle::Bold());
 
         SkRandom random;
         for (int i = 0; i < kCnt; ++i) {
@@ -69,7 +69,7 @@
 
             fColors[i] = random.nextU();
             fColors[i] |= 0xFF000000;
-            fColors[i] = sk_tool_utils::color_to_565(fColors[i]);
+            fColors[i] = ToolUtils::color_to_565(fColors[i]);
 
             constexpr SkScalar kMinPtSize = 8.f;
             constexpr SkScalar kMaxPtSize = 32.f;
diff --git a/gm/vertices.cpp b/gm/vertices.cpp
index 7c7c011..bf5e884 100644
--- a/gm/vertices.cpp
+++ b/gm/vertices.cpp
@@ -24,7 +24,7 @@
 
     sk_sp<SkShader> grad = SkGradientShader::MakeLinear(pts, colors, nullptr,
                                                         SK_ARRAY_COUNT(colors),
-                                                        SkShader::kMirror_TileMode, 0,
+                                                        SkTileMode::kMirror, 0,
                                                         &localMatrix);
     // Throw in a couple of local matrix wrappers for good measure.
     return shaderScale == 1
@@ -35,11 +35,11 @@
 }
 
 static sk_sp<SkShader> make_shader2() {
-    return SkShader::MakeColorShader(SK_ColorBLUE);
+    return SkShaders::Color(SK_ColorBLUE);
 }
 
 static sk_sp<SkColorFilter> make_color_filter() {
-    return SkColorFilter::MakeModeFilter(0xFFAABBCC, SkBlendMode::kDarken);
+    return SkColorFilters::Blend(0xFFAABBCC, SkBlendMode::kDarken);
 }
 
 static constexpr SkScalar kMeshSize = 30;
@@ -154,7 +154,7 @@
         int x = 0;
         for (auto mode : modes) {
             canvas->save();
-            for (uint8_t alpha : {0xFF, 0x80}) {
+            for (float alpha : {1.0f, 0.5f}) {
                 for (const auto& cf : {sk_sp<SkColorFilter>(nullptr), fColorFilter}) {
                     for (const auto& shader : {fShader1, fShader2}) {
                         static constexpr struct {
@@ -164,7 +164,7 @@
                         for (auto attrs : kAttrs) {
                             paint.setShader(shader);
                             paint.setColorFilter(cf);
-                            paint.setAlpha(alpha);
+                            paint.setAlphaf(alpha);
 
                             const SkColor* colors = attrs.fHasColors ? fColors : nullptr;
                             const SkPoint* texs = attrs.fHasTexs ? fTexs : nullptr;
diff --git a/gm/verylargebitmap.cpp b/gm/verylargebitmap.cpp
index 158a103..d6e3d96 100644
--- a/gm/verylargebitmap.cpp
+++ b/gm/verylargebitmap.cpp
@@ -17,7 +17,7 @@
     const SkScalar radius = 40;
     SkPaint paint;
     paint.setShader(SkGradientShader::MakeRadial(center, radius, colors, nullptr, 2,
-                                                 SkShader::kMirror_TileMode));
+                                                 SkTileMode::kMirror));
     paint.setBlendMode(SkBlendMode::kSrc);
     canvas->drawPaint(paint);
 }
diff --git a/gm/wacky_yuv_formats.cpp b/gm/wacky_yuv_formats.cpp
index a98f019..4870f01 100644
--- a/gm/wacky_yuv_formats.cpp
+++ b/gm/wacky_yuv_formats.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "ToolUtils.h"
 #include "gm.h"
-#include "sk_tool_utils.h"
 
 #include "SkColorPriv.h"
 #include "SkImageGenerator.h"
@@ -29,6 +29,7 @@
 enum YUVFormat {
     // 4:4:4 formats, 32 bpp
     kAYUV_YUVFormat,  // 8-bit YUVA values all interleaved
+    kY410_YUVFormat,  // AVYU w/ 10bpp for YUV and 2 for A all interleaved
 
     // 4:2:0 formats, 12 bpp
     kNV12_YUVFormat, // 8-bit Y plane + 2x2 down sampled interleaved U/V planes
@@ -151,20 +152,21 @@
     return p;
 }
 
-static SkBitmap make_bitmap(const SkPath& path, const SkTDArray<SkRect>& circles, bool opaque) {
-    const SkColor kGreen  = sk_tool_utils::color_to_565(SkColorSetARGB(0xFF, 178, 240, 104));
-    const SkColor kBlue   = sk_tool_utils::color_to_565(SkColorSetARGB(0xFF, 173, 167, 252));
-    const SkColor kYellow = sk_tool_utils::color_to_565(SkColorSetARGB(0xFF, 255, 221, 117));
+static SkBitmap make_bitmap(SkColorType colorType, const SkPath& path,
+                            const SkTDArray<SkRect>& circles, bool opaque) {
+    const SkColor kGreen  = ToolUtils::color_to_565(SkColorSetARGB(0xFF, 178, 240, 104));
+    const SkColor kBlue   = ToolUtils::color_to_565(SkColorSetARGB(0xFF, 173, 167, 252));
+    const SkColor kYellow = ToolUtils::color_to_565(SkColorSetARGB(0xFF, 255, 221, 117));
 
-    SkImageInfo ii = SkImageInfo::MakeN32(kTileWidthHeight, kTileWidthHeight, kPremul_SkAlphaType);
+    SkImageInfo ii = SkImageInfo::Make(kTileWidthHeight, kTileWidthHeight,
+                                       colorType, kPremul_SkAlphaType);
 
     SkBitmap bm;
     bm.allocPixels(ii);
 
-    std::unique_ptr<SkCanvas> canvas = SkCanvas::MakeRasterDirectN32(ii.width(), ii.height(),
-                                                                     (SkPMColor*)bm.getPixels(),
-                                                                     bm.rowBytes());
-
+    std::unique_ptr<SkCanvas> canvas = SkCanvas::MakeRasterDirect(ii,
+                                                                  bm.getPixels(),
+                                                                  bm.rowBytes());
     canvas->clear(opaque ? kGreen : SK_ColorTRANSPARENT);
 
     SkPaint paint;
@@ -237,45 +239,41 @@
 
 
 static SkPMColor convert_yuva_to_rgba_jpeg(uint8_t y, uint8_t u, uint8_t v, uint8_t a) {
-    int c = y;
-    int d = u - 128;
-    int e = v - 128;
-
-    uint8_t r = SkScalarPin(SkScalarRoundToInt( 1.0f * c                   +  1.402f    * e ),
+    uint8_t r = SkScalarPin(SkScalarRoundToInt( 1.0f * y                   +  1.402f    * v  - 0.703749f * 255),
                             0, 255);
-    uint8_t g = SkScalarPin(SkScalarRoundToInt( 1.0f * c - (0.344136f * d) - (0.714136f * e)),
+    uint8_t g = SkScalarPin(SkScalarRoundToInt( 1.0f * y - (0.344136f * u) - (0.714136f * v) + 0.531211f * 255),
                             0, 255);
-    uint8_t b = SkScalarPin(SkScalarRoundToInt( 1.0f * c +  1.773f    * d                   ),
+    uint8_t b = SkScalarPin(SkScalarRoundToInt( 1.0f * y +  1.772f    * u                    - 0.889475f * 255),
                             0, 255);
 
-    return SkPremultiplyARGBInline(a, r, g, b);
+    SkPMColor c = SkPremultiplyARGBInline(a, b, g, r);
+    return c;
 }
 
 static SkPMColor convert_yuva_to_rgba_601(uint8_t y, uint8_t u, uint8_t v, uint8_t a) {
-    int c = y - 16;
-    int d = u - 128;
-    int e = v - 128;
+    uint8_t r = SkScalarPin(SkScalarRoundToInt( 1.164f * y                +  1.596f * v  - 0.87075f * 255), 0, 255);
+    uint8_t g = SkScalarPin(SkScalarRoundToInt( 1.164f * y - (0.391f * u) - (0.813f * v) + 0.52925f * 255), 0, 255);
+    uint8_t b = SkScalarPin(SkScalarRoundToInt( 1.164f * y +  2.018f * u                 - 1.08175f * 255), 0, 255);
 
-    uint8_t r = SkScalarPin(SkScalarRoundToInt( 1.164f * c                +  1.596f * e ), 0, 255);
-    uint8_t g = SkScalarPin(SkScalarRoundToInt( 1.164f * c - (0.391f * d) - (0.813f * e)), 0, 255);
-    uint8_t b = SkScalarPin(SkScalarRoundToInt( 1.164f * c +  2.018f * d                ), 0, 255);
-
-    return SkPremultiplyARGBInline(a, r, g, b);
+    SkPMColor c = SkPremultiplyARGBInline(a, b, g, r);
+    return c;
 }
 
 static SkPMColor convert_yuva_to_rgba_709(uint8_t y, uint8_t u, uint8_t v, uint8_t a) {
-    int c = y - 16;
-    int d = u - 128;
-    int e = v - 128;
+    uint8_t r = SkScalarPin(SkScalarRoundToInt( 1.164f * y                + (1.793f * v) - 0.96925f * 255), 0, 255);
+    uint8_t g = SkScalarPin(SkScalarRoundToInt( 1.164f * y - (0.213f * u) - (0.533f * v) + 0.30025f * 255), 0, 255);
+    uint8_t b = SkScalarPin(SkScalarRoundToInt( 1.164f * y + (2.112f * u)                - 1.12875f * 255), 0, 255);
 
-    uint8_t r = SkScalarPin(SkScalarRoundToInt( 1.164f * c                +  1.793f * e ), 0, 255);
-    uint8_t g = SkScalarPin(SkScalarRoundToInt( 1.164f * c - (0.213f * d) - (0.533f * e)), 0, 255);
-    uint8_t b = SkScalarPin(SkScalarRoundToInt( 1.164f * c +  2.112f * d                ), 0, 255);
-
-    return SkPremultiplyARGBInline(a, r, g, b);
+    SkPMColor c = SkPremultiplyARGBInline(a, b, g, r);
+    return c;
 }
 
 static void extract_planes(const SkBitmap& bm, SkYUVColorSpace yuvColorSpace, PlaneData* planes) {
+    if (kIdentity_SkYUVColorSpace == yuvColorSpace) {
+        // To test the identity color space we use JPEG YUV planes
+        yuvColorSpace = kJPEG_SkYUVColorSpace;
+    }
+
     SkASSERT(!(bm.width() % 2));
     SkASSERT(!(bm.height() % 2));
 
@@ -351,7 +349,8 @@
 
                     // NOT premul!
                     // V and Y swapped to match RGBA layout
-                    *yuvaFull.getAddr32(x, y) = SkColorSetARGB(A, V, U, Y);
+                    SkColor c = SkColorSetARGB(A, V, U, Y);
+                    *yuvaFull.getAddr32(x, y) = c;
                 }
             }
 
@@ -367,6 +366,39 @@
             yuvaIndices[3].fChannel = SkColorChannel::kA;
             break;
         }
+        case kY410_YUVFormat: {
+            SkBitmap yuvaFull;
+
+            yuvaFull.allocPixels(SkImageInfo::Make(planes.fYFull.width(), planes.fYFull.height(),
+                                                   kRGBA_1010102_SkColorType,
+                                                   kUnpremul_SkAlphaType));
+
+            for (int y = 0; y < planes.fYFull.height(); ++y) {
+                for (int x = 0; x < planes.fYFull.width(); ++x) {
+
+                    uint32_t Y = (*planes.fYFull.getAddr8(x, y) / 255.0f) * 1023.0f;
+                    uint32_t U = (*planes.fUFull.getAddr8(x, y) / 255.0f) * 1023.0f;
+                    uint32_t V = (*planes.fVFull.getAddr8(x, y) / 255.0f) * 1023.0f;
+                    uint8_t  A = (*planes.fAFull.getAddr8(x, y) / 255.0f) * 3.0f;
+
+                    // NOT premul!
+                    // AVYU but w/ V and U swapped to match RGBA layout
+                    *yuvaFull.getAddr32(x, y) = (A << 30) | (U << 20) | (Y << 10) | (V << 0);
+                }
+            }
+
+            resultBMs[nextLayer++] = yuvaFull;
+
+            yuvaIndices[0].fIndex = 0;
+            yuvaIndices[0].fChannel = SkColorChannel::kG;
+            yuvaIndices[1].fIndex = 0;
+            yuvaIndices[1].fChannel = SkColorChannel::kB;
+            yuvaIndices[2].fIndex = 0;
+            yuvaIndices[2].fChannel = SkColorChannel::kR;
+            yuvaIndices[3].fIndex = 0;
+            yuvaIndices[3].fChannel = SkColorChannel::kA;
+            break;
+        }
         case kNV12_YUVFormat: {
             SkBitmap uvQuarter;
 
@@ -383,7 +415,7 @@
 
                     // NOT premul!
                     // U and 0 swapped to match RGBA layout
-                    *uvQuarter.getAddr32(x, y) = SkColorSetARGB(0, 0, V, U);
+                    *uvQuarter.getAddr32(x, y) = SkColorSetARGB(0xFF, 0, V, U);
                 }
             }
 
@@ -414,7 +446,7 @@
 
                     // NOT premul!
                     // V and 0 swapped to match RGBA layout
-                    *vuQuarter.getAddr32(x, y) = SkColorSetARGB(0, 0, U, V);
+                    *vuQuarter.getAddr32(x, y) = SkColorSetARGB(0xFF, 0, U, V);
                 }
             }
 
@@ -455,7 +487,7 @@
             break;
     }
 
-    if (kAYUV_YUVFormat != yuvFormat) {
+    if (kAYUV_YUVFormat != yuvFormat && kY410_YUVFormat != yuvFormat) {
         if (opaque) {
             yuvaIndices[3].fIndex = -1;
         } else {
@@ -468,30 +500,51 @@
 
 }
 
-static uint8_t look_up(float x1, float y1, const SkBitmap& bm, SkColorChannel  channel) {
+static uint8_t look_up(float x1, float y1, const SkBitmap& bm, SkColorChannel channel) {
     uint8_t result;
 
+    SkASSERT(x1 > 0 && x1 < 1.0f);
+    SkASSERT(y1 > 0 && y1 < 1.0f);
     int x = SkScalarFloorToInt(x1 * bm.width());
     int y = SkScalarFloorToInt(y1 * bm.height());
 
     if (kAlpha_8_SkColorType == bm.colorType()) {
         SkASSERT(SkColorChannel::kA == channel);
         result = *bm.getAddr8(x, y);
-    } else {
-        SkASSERT(kRGBA_8888_SkColorType == bm.colorType());
+    } else if (kRGBA_8888_SkColorType == bm.colorType()) {
+        SkColor c = *bm.getAddr32(x, y);
 
         switch (channel) {
             case SkColorChannel::kR:
-                result = SkColorGetR(bm.getColor(x, y));
+                result = SkColorGetB(c);
                 break;
             case SkColorChannel::kG:
-                result = SkColorGetG(bm.getColor(x, y));
+                result = SkColorGetG(c);
                 break;
             case SkColorChannel::kB:
-                result = SkColorGetB(bm.getColor(x, y));
+                result = SkColorGetR(c);
                 break;
             case SkColorChannel::kA:
-                result = SkColorGetA(bm.getColor(x, y));
+                result = SkColorGetA(c);
+                break;
+        }
+    } else {
+        SkASSERT(kRGBA_1010102_SkColorType == bm.colorType());
+
+        SkColor c = *bm.getAddr32(x, y);
+
+        switch (channel) {
+            case SkColorChannel::kR:
+                result = ((c >>  0) & 0x3ff) * (255.0f/1023.0f);
+                break;
+            case SkColorChannel::kG:
+                result = ((c >> 10) & 0x3ff) * (255.0f/1023.0f);
+                break;
+            case SkColorChannel::kB:
+                result = ((c >> 20) & 0x3ff) * (255.0f/1023.0f);
+                break;
+            case SkColorChannel::kA:
+                result = ((c >> 30) & 0x3) * (255.0f/3.0f);
                 break;
         }
     }
@@ -506,7 +559,8 @@
                  SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount],
                  SkBitmap bitmaps[SkYUVASizeInfo::kMaxCount])
             : SkImageGenerator(ii)
-            , fYUVColorSpace(yuvColorSpace) {
+            , fYUVColorSpace(yuvColorSpace)
+            , fAllA8(true) {
         memcpy(fYUVAIndices, yuvaIndices, sizeof(fYUVAIndices));
 
         SkAssertResult(SkYUVAIndex::AreValidIndices(fYUVAIndices, &fNumBitmaps));
@@ -514,6 +568,9 @@
 
         for (int i = 0; i < fNumBitmaps; ++i) {
             fYUVBitmaps[i] = bitmaps[i];
+            if (kAlpha_8_SkColorType != fYUVBitmaps[i].colorType()) {
+                fAllA8 = false;
+            }
         }
     }
 
@@ -522,7 +579,8 @@
                      const Options&) override {
 
         if (kUnknown_SkColorType == fFlattened.colorType()) {
-            fFlattened.allocPixels(this->getInfo());
+            fFlattened.allocPixels(info);
+            SkASSERT(kPremul_SkAlphaType == info.alphaType());
 
             for (int y = 0; y < info.height(); ++y) {
                 for (int x = 0; x < info.width(); ++x) {
@@ -561,6 +619,9 @@
                         case kRec709_SkYUVColorSpace:
                             *fFlattened.getAddr32(x, y) = convert_yuva_to_rgba_709(Y, U, V, A);
                             break;
+                        case kIdentity_SkYUVColorSpace:
+                            *fFlattened.getAddr32(x, y) = SkPremultiplyARGBInline(A, V, U, Y);
+                            break;
                     }
                 }
             }
@@ -573,6 +634,11 @@
                       SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount],
                       SkYUVColorSpace* yuvColorSpace) const override {
 
+        // The onQueryYUVA8/onGetYUVA8Planes can only handle A8 planes
+        if (!fAllA8) {
+            return false;
+        }
+
         memcpy(yuvaIndices, fYUVAIndices, sizeof(fYUVAIndices));
         *yuvColorSpace = fYUVColorSpace;
 
@@ -593,6 +659,7 @@
 
     bool onGetYUVA8Planes(const SkYUVASizeInfo&, const SkYUVAIndex[SkYUVAIndex::kIndexCount],
                           void* planes[SkYUVASizeInfo::kMaxCount]) override {
+        SkASSERT(fAllA8);
         for (int i = 0; i < fNumBitmaps; ++i) {
             planes[i] = fYUVBitmaps[i].getPixels();
         }
@@ -605,6 +672,7 @@
     int             fNumBitmaps;
     SkBitmap        fYUVBitmaps[SkYUVASizeInfo::kMaxCount];
     SkBitmap        fFlattened;
+    bool            fAllA8;     // are all the SkBitmaps in "fYUVBitmaps" A8?
 
 };
 
@@ -619,11 +687,11 @@
 }
 
 static void draw_col_label(SkCanvas* canvas, int x, int yuvColorSpace, bool opaque) {
-    static const char* kYUVColorSpaceNames[] = { "JPEG", "601", "709" };
+    static const char* kYUVColorSpaceNames[] = { "JPEG", "601", "709", "Identity" };
     GR_STATIC_ASSERT(SK_ARRAY_COUNT(kYUVColorSpaceNames) == kLastEnum_SkYUVColorSpace+1);
 
     SkPaint paint;
-    SkFont font(sk_tool_utils::create_portable_typeface(nullptr, SkFontStyle::Bold()), 16);
+    SkFont  font(ToolUtils::create_portable_typeface(nullptr, SkFontStyle::Bold()), 16);
     font.setEdging(SkFont::Edging::kAlias);
 
     SkRect textRect;
@@ -644,11 +712,11 @@
 }
 
 static void draw_row_label(SkCanvas* canvas, int y, int yuvFormat) {
-    static const char* kYUVFormatNames[] = { "AYUV", "NV12", "NV21", "I420", "YV12" };
+    static const char* kYUVFormatNames[] = { "AYUV", "Y410", "NV12", "NV21", "I420", "YV12" };
     GR_STATIC_ASSERT(SK_ARRAY_COUNT(kYUVFormatNames) == kLast_YUVFormat+1);
 
     SkPaint paint;
-    SkFont font(sk_tool_utils::create_portable_typeface(nullptr, SkFontStyle::Bold()), 16);
+    SkFont  font(ToolUtils::create_portable_typeface(nullptr, SkFontStyle::Bold()), 16);
     font.setEdging(SkFont::Edging::kAlias);
 
     SkRect textRect;
@@ -706,14 +774,26 @@
     return tex;
 }
 
+static sk_sp<SkColorFilter> yuv_to_rgb_colorfilter() {
+    static const float kJPEGConversionMatrix[20] = {
+        1.0f,  0.0f,       1.402f,    0.0f, -180.0f,
+        1.0f, -0.344136f, -0.714136f, 0.0f,  136.0f,
+        1.0f,  1.772f,     0.0f,      0.0f, -227.6f,
+        0.0f,  0.0f,       0.0f,      1.0f,    0.0f
+    };
+
+    return SkColorFilters::MatrixRowMajor255(kJPEGConversionMatrix);
+}
+
 namespace skiagm {
 
 // This GM creates an opaque and transparent bitmap, extracts the planes and then recombines
 // them into various YUV formats. It then renders the results in the grid:
 //
-//                   JPEG                   601                     709
-//        Transparent  Opaque       Transparent  Opaque        Transparent  Opaque
+//                 JPEG                  601                   709                Identity
+//        Transparent  Opaque   Transparent  Opaque   Transparent  Opaque   Transparent Opaque
 // AYUV
+// Y410
 // NV12
 // NV21
 // I420
@@ -731,6 +811,7 @@
         if (fUseTargetColorSpace) {
             name += "_cs";
         }
+
         return name;
     }
 
@@ -750,14 +831,14 @@
             // transparent
             SkTDArray<SkRect> circles;
             SkPath path = create_splat(origin, innerRadius, outerRadius, 1.0f, 5, &circles);
-            fOriginalBMs[0] = make_bitmap(path, circles, false);
+            fOriginalBMs[0] = make_bitmap(kRGBA_8888_SkColorType, path, circles, false);
         }
 
         {
             // opaque
             SkTDArray<SkRect> circles;
             SkPath path = create_splat(origin, innerRadius, outerRadius, 1.0f, 7, &circles);
-            fOriginalBMs[1] = make_bitmap(path, circles, true);
+            fOriginalBMs[1] = make_bitmap(kRGBA_8888_SkColorType, path, circles, true);
         }
 
         if (fUseTargetColorSpace) {
@@ -775,13 +856,19 @@
                 for (int format = kAYUV_YUVFormat; format <= kLast_YUVFormat; ++format) {
                     SkBitmap resultBMs[4];
                     SkYUVAIndex yuvaIndices[4];
+
                     create_YUV(planes, (YUVFormat) format, resultBMs, yuvaIndices, opaque);
+
                     int numTextures;
                     if (!SkYUVAIndex::AreValidIndices(yuvaIndices, &numTextures)) {
                         continue;
                     }
 
                     if (context) {
+                        if (context->abandoned()) {
+                            return;
+                        }
+
                         GrGpu* gpu = context->priv().getGpu();
                         if (!gpu) {
                             return;
@@ -848,6 +935,12 @@
 
         int x = kLabelWidth;
         for (int cs = kJPEG_SkYUVColorSpace; cs <= kLastEnum_SkYUVColorSpace; ++cs) {
+            SkPaint paint;
+            if (kIdentity_SkYUVColorSpace == cs) {
+                // The identity color space needs post processing to appear correctly
+                paint.setColorFilter(yuv_to_rgb_colorfilter());
+            }
+
             for (int opaque : { 0, 1 }) {
                 int y = kLabelHeight;
 
@@ -859,11 +952,14 @@
                 for (int format = kAYUV_YUVFormat; format <= kLast_YUVFormat; ++format) {
                     draw_row_label(canvas, y, format);
                     if (fUseTargetColorSpace && fImages[opaque][cs][format]) {
+                        // Making a CS-specific version of a kIdentity_SkYUVColorSpace YUV image
+                        // doesn't make a whole lot of sense. The colorSpace conversion will
+                        // operate on the YUV components rather than the RGB components.
                         sk_sp<SkImage> csImage =
                             fImages[opaque][cs][format]->makeColorSpace(fTargetColorSpace);
-                        canvas->drawImage(csImage, x, y);
+                        canvas->drawImage(csImage, x, y, &paint);
                     } else {
-                        canvas->drawImage(fImages[opaque][cs][format], x, y);
+                        canvas->drawImage(fImages[opaque][cs][format], x, y, &paint);
                     }
                     y += kTileWidthHeight + kPad;
                 }
@@ -872,24 +968,26 @@
             }
         }
         if (auto context = canvas->getGrContext()) {
-            context->flush();
-            GrGpu* gpu = context->priv().getGpu();
-            SkASSERT(gpu);
-            gpu->testingOnly_flushGpuAndSync();
-            for (const auto& tex : fBackendTextures) {
-                gpu->deleteTestingOnlyBackendTexture(tex);
+            if (!context->abandoned()) {
+                context->flush();
+                GrGpu* gpu = context->priv().getGpu();
+                SkASSERT(gpu);
+                gpu->testingOnly_flushGpuAndSync();
+                for (const auto& tex : fBackendTextures) {
+                    gpu->deleteTestingOnlyBackendTexture(tex);
+                }
+                fBackendTextures.reset();
             }
-            fBackendTextures.reset();
         }
         SkASSERT(!fBackendTextures.count());
     }
 
 private:
-    SkBitmap fOriginalBMs[2];
-    sk_sp<SkImage> fImages[2][kLastEnum_SkYUVColorSpace + 1][kLast_YUVFormat + 1];
+    SkBitmap                   fOriginalBMs[2];
+    sk_sp<SkImage>             fImages[2][kLastEnum_SkYUVColorSpace + 1][kLast_YUVFormat + 1];
     SkTArray<GrBackendTexture> fBackendTextures;
-    bool fUseTargetColorSpace;
-    sk_sp<SkColorSpace> fTargetColorSpace;
+    bool                       fUseTargetColorSpace;
+    sk_sp<SkColorSpace>        fTargetColorSpace;
 
     typedef GM INHERITED;
 };
@@ -899,7 +997,7 @@
 DEF_GM(return new WackyYUVFormatsGM(false);)
 DEF_GM(return new WackyYUVFormatsGM(true);)
 
-class YUVMakeColorSpaceGM : public GM {
+class YUVMakeColorSpaceGM : public GpuGM {
 public:
     YUVMakeColorSpaceGM() {
         this->setBGColor(0xFFCCCCCC);
@@ -926,14 +1024,14 @@
             // transparent
             SkTDArray<SkRect> circles;
             SkPath path = create_splat(origin, innerRadius, outerRadius, 1.0f, 5, &circles);
-            fOriginalBMs[0] = make_bitmap(path, circles, false);
+            fOriginalBMs[0] = make_bitmap(kN32_SkColorType, path, circles, false);
         }
 
         {
             // opaque
             SkTDArray<SkRect> circles;
             SkPath path = create_splat(origin, innerRadius, outerRadius, 1.0f, 7, &circles);
-            fOriginalBMs[1] = make_bitmap(path, circles, true);
+            fOriginalBMs[1] = make_bitmap(kN32_SkColorType, path, circles, true);
         }
 
         fTargetColorSpace = SkColorSpace::MakeSRGB()->makeColorSpin();
@@ -946,7 +1044,9 @@
 
             SkBitmap resultBMs[4];
             SkYUVAIndex yuvaIndices[4];
+
             create_YUV(planes, kAYUV_YUVFormat, resultBMs, yuvaIndices, opaque);
+
             int numTextures;
             if (!SkYUVAIndex::AreValidIndices(yuvaIndices, &numTextures)) {
                 continue;
@@ -983,12 +1083,7 @@
         }
     }
 
-    void onDraw(SkCanvas* canvas) override {
-        GrContext* context = canvas->getGrContext();
-        if (!context || context->abandoned()) {
-            skiagm::GM::DrawGpuOnlyMessage(canvas);
-            return;
-        }
+    void onDraw(GrContext* context, GrRenderTargetContext*, SkCanvas* canvas) override {
         this->createImages(context);
 
         int x = kPad;
@@ -1016,7 +1111,7 @@
                 y += kTileWidthHeight + kPad;
 
                 SkBitmap readBack;
-                readBack.allocPixels(as_IB(yuv)->onImageInfo());
+                readBack.allocPixels(yuv->imageInfo());
                 yuv->readPixels(readBack.pixmap(), 0, 0);
                 canvas->drawBitmap(readBack, x, y);
 
diff --git a/gm/windowrectangles.cpp b/gm/windowrectangles.cpp
index 89e08dd..d2ec7e6 100644
--- a/gm/windowrectangles.cpp
+++ b/gm/windowrectangles.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkClipStack.h"
 #include "SkRRect.h"
 #include "SkTextUtils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #include "GrAppliedClip.h"
 #include "GrCaps.h"
@@ -30,15 +30,15 @@
 
 class WindowRectanglesBaseGM : public GM {
 protected:
-    virtual void onCoverClipStack(const SkClipStack&, SkCanvas*) = 0;
+    virtual DrawResult onCoverClipStack(const SkClipStack&, SkCanvas*, SkString* errorMsg) = 0;
 
 private:
     SkISize onISize() override { return SkISize::Make(kDeviceRect.width(), kDeviceRect.height()); }
-    void onDraw(SkCanvas*) final;
+    DrawResult onDraw(SkCanvas*, SkString* errorMsg) final;
 };
 
-void WindowRectanglesBaseGM::onDraw(SkCanvas* canvas) {
-    sk_tool_utils::draw_checkerboard(canvas, 0xffffffff, 0xffc6c3c6, 25);
+DrawResult WindowRectanglesBaseGM::onDraw(SkCanvas* canvas, SkString* errorMsg) {
+    ToolUtils::draw_checkerboard(canvas, 0xffffffff, 0xffc6c3c6, 25);
 
     SkClipStack stack;
     stack.clipRect(SkRect::MakeXYWH(370.75, 80.25, 149, 100), SkMatrix::I(),
@@ -57,7 +57,7 @@
     complx.setRectRadii(SkRect::MakeXYWH(80.25, 80.75, 100, 149), complxRadii);
     stack.clipRRect(complx, SkMatrix::I(), kDifference_SkClipOp, false);
 
-    this->onCoverClipStack(stack, canvas);
+    return this->onCoverClipStack(stack, canvas, errorMsg);
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -68,10 +68,11 @@
 class WindowRectanglesGM : public WindowRectanglesBaseGM {
 private:
     SkString onShortName() final { return SkString("windowrectangles"); }
-    void onCoverClipStack(const SkClipStack&, SkCanvas*) final;
+    DrawResult onCoverClipStack(const SkClipStack&, SkCanvas*, SkString* errorMsg) final;
 };
 
-void WindowRectanglesGM::onCoverClipStack(const SkClipStack& stack, SkCanvas* canvas) {
+DrawResult WindowRectanglesGM::onCoverClipStack(const SkClipStack& stack, SkCanvas* canvas,
+                                                SkString* errorMsg) {
     SkPaint paint;
     paint.setColor(0xff00aa80);
 
@@ -97,6 +98,7 @@
     }
 
     canvas->drawRect(SkRect::Make(kCoverRect), paint);
+    return DrawResult::kOk;
 }
 
 DEF_GM( return new WindowRectanglesGM(); )
@@ -121,7 +123,7 @@
 private:
     constexpr static int kMaskCheckerSize = 5;
     SkString onShortName() final { return SkString("windowrectangles_mask"); }
-    void onCoverClipStack(const SkClipStack&, SkCanvas*) final;
+    DrawResult onCoverClipStack(const SkClipStack&, SkCanvas*, SkString* errorMsg) final;
     void visualizeAlphaMask(GrContext*, GrRenderTargetContext*, const GrReducedClip&, GrPaint&&);
     void visualizeStencilMask(GrContext*, GrRenderTargetContext*, const GrReducedClip&, GrPaint&&);
     void stencilCheckerboard(GrRenderTargetContext*, bool flip);
@@ -150,7 +152,7 @@
     AlphaOnlyClip(sk_sp<GrTextureProxy> mask, int x, int y) : fMask(mask), fX(x), fY(y) {}
 
 private:
-    bool apply(GrContext*, GrRenderTargetContext*, bool, bool, GrAppliedClip* out,
+    bool apply(GrRecordingContext*, GrRenderTargetContext*, bool, bool, GrAppliedClip* out,
                SkRect* bounds) const override {
         int w = fMask->width();
         int h = fMask->height();
@@ -170,13 +172,18 @@
     return GrStencilClip(SkClipStack::kEmptyGenID);
 };
 
-void WindowRectanglesMaskGM::onCoverClipStack(const SkClipStack& stack, SkCanvas* canvas) {
+DrawResult WindowRectanglesMaskGM::onCoverClipStack(const SkClipStack& stack, SkCanvas* canvas,
+                                                        SkString* errorMsg) {
     GrContext* ctx = canvas->getGrContext();
     GrRenderTargetContext* rtc = canvas->internal_private_accessTopLayerRenderTargetContext();
-
-    if (!ctx || !rtc || rtc->priv().maxWindowRectangles() < kNumWindows) {
-        DrawFailureMessage(canvas, "Requires GPU with %i window rectangles", kNumWindows);
-        return;
+    if (!ctx || !rtc) {
+        *errorMsg = kErrorMsg_DrawSkippedGpuOnly;
+        return DrawResult::kSkip;
+    }
+    if (rtc->priv().maxWindowRectangles() < kNumWindows) {
+        *errorMsg = "Requires at least 8 window rectangles. "
+                    "(Are you off FBO 0? Use sRGB to force offscreen rendering.)";
+        return DrawResult::kSkip;
     }
 
     const GrReducedClip reducedClip(stack, SkRect::Make(kCoverRect), rtc->caps(), kNumWindows);
@@ -189,6 +196,7 @@
         paint.setColor4f({ 1, 0.25f, 0.25f, 1 });
         this->visualizeStencilMask(ctx, rtc, reducedClip, std::move(paint));
     }
+    return DrawResult::kOk;
 }
 
 void WindowRectanglesMaskGM::visualizeAlphaMask(GrContext* ctx, GrRenderTargetContext* rtc,
@@ -258,8 +266,8 @@
         for (int x = (y & 1) == flip ? 0 : kMaskCheckerSize;
              x < kDeviceRect.width(); x += 2 * kMaskCheckerSize) {
             SkIRect checker = SkIRect::MakeXYWH(x, y, kMaskCheckerSize, kMaskCheckerSize);
-            rtc->priv().stencilRect(GrNoClip(), &kSetClip, GrAAType::kNone, SkMatrix::I(),
-                                    SkRect::Make(checker));
+            rtc->priv().stencilRect(
+                    GrNoClip(), &kSetClip, GrAA::kNo, SkMatrix::I(), SkRect::Make(checker));
         }
     }
 }
diff --git a/gm/xfermodeimagefilter.cpp b/gm/xfermodeimagefilter.cpp
index 280400a..a0d1da8 100644
--- a/gm/xfermodeimagefilter.cpp
+++ b/gm/xfermodeimagefilter.cpp
@@ -5,13 +5,13 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkArithmeticImageFilter.h"
 #include "SkImage.h"
 #include "SkImageSource.h"
 #include "SkOffsetImageFilter.h"
 #include "SkXfermodeImageFilter.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 #define WIDTH 600
 #define HEIGHT 700
@@ -35,13 +35,10 @@
     }
 
     void onOnceBeforeDraw() override {
-        fBitmap = sk_tool_utils::create_string_bitmap(80, 80, 0xD000D000, 15, 65, 96, "e");
+        fBitmap = ToolUtils::create_string_bitmap(80, 80, 0xD000D000, 15, 65, 96, "e");
 
         fCheckerboard = SkImage::MakeFromBitmap(
-            sk_tool_utils::create_checkerboard_bitmap(80, 80,
-                                                      0xFFA0A0A0,
-                                                      0xFF404040,
-                                                      8));
+                ToolUtils::create_checkerboard_bitmap(80, 80, 0xFFA0A0A0, 0xFF404040, 8));
     }
 
     void onDraw(SkCanvas* canvas) override {
diff --git a/gm/xfermodes.cpp b/gm/xfermodes.cpp
index 3af85d2..df58a84 100644
--- a/gm/xfermodes.cpp
+++ b/gm/xfermodes.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkBitmap.h"
 #include "SkShader.h"
 #include "SkTextUtils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 enum SrcType {
     //! A WxH image with a rectangle in the lower right.
@@ -88,7 +88,7 @@
 
     {
         SkCanvas c(*src);
-        p.setColor(sk_tool_utils::color_to_565(0xFFFFCC44));
+        p.setColor(ToolUtils::color_to_565(0xFFFFCC44));
         r.set(0, 0, ww*3/4, hh*3/4);
         c.drawOval(r, p);
     }
@@ -98,7 +98,7 @@
 
     {
         SkCanvas c(*dst);
-        p.setColor(sk_tool_utils::color_to_565(0xFF66AAFF));
+        p.setColor(ToolUtils::color_to_565(0xFF66AAFF));
         r.set(ww/3, hh/3, ww*19/20, hh*19/20);
         c.drawRect(r, p);
     }
@@ -145,11 +145,11 @@
             case kQuarterClear_SrcType: {
                 SkScalar halfW = SkIntToScalar(W) / 2;
                 SkScalar halfH = SkIntToScalar(H) / 2;
-                p.setColor(sk_tool_utils::color_to_565(0xFF66AAFF));
+                p.setColor(ToolUtils::color_to_565(0xFF66AAFF));
                 SkRect r = SkRect::MakeXYWH(x + halfW, y, halfW,
                                             SkIntToScalar(H));
                 canvas->drawRect(r, p);
-                p.setColor(sk_tool_utils::color_to_565(0xFFAA66FF));
+                p.setColor(ToolUtils::color_to_565(0xFFAA66FF));
                 r = SkRect::MakeXYWH(x, y + halfH, SkIntToScalar(W), halfH);
                 canvas->drawRect(r, p);
                 break;
@@ -168,7 +168,7 @@
                 SkScalar h = SkIntToScalar(H);
                 SkRect r = SkRect::MakeXYWH(x + w / 3, y + h / 3,
                                             w * 37 / 60, h * 37 / 60);
-                p.setColor(sk_tool_utils::color_to_565(0xFF66AAFF));
+                p.setColor(ToolUtils::color_to_565(0xFF66AAFF));
                 canvas->drawRect(r, p);
                 break;
             }
@@ -222,13 +222,12 @@
         const SkScalar h = SkIntToScalar(H);
         SkMatrix m;
         m.setScale(SkIntToScalar(6), SkIntToScalar(6));
-        auto s = SkShader::MakeBitmapShader(fBG, SkShader::kRepeat_TileMode,
-                                            SkShader::kRepeat_TileMode, &m);
+        auto s = fBG.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &m);
 
         SkPaint labelP;
         labelP.setAntiAlias(true);
 
-        SkFont font(sk_tool_utils::create_portable_typeface());
+        SkFont font(ToolUtils::create_portable_typeface());
 
         const int W = 5;
 
diff --git a/gm/xfermodes2.cpp b/gm/xfermodes2.cpp
index 26de6fe..df6127c 100644
--- a/gm/xfermodes2.cpp
+++ b/gm/xfermodes2.cpp
@@ -4,13 +4,13 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
-#include "gm.h"
-#include "sk_tool_utils.h"
 #include "SkBitmap.h"
-#include "SkShader.h"
 #include "SkBlendModePriv.h"
 #include "SkColorPriv.h"
+#include "SkShader.h"
 #include "SkTextUtils.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -33,7 +33,7 @@
         const SkScalar w = SkIntToScalar(kSize);
         const SkScalar h = SkIntToScalar(kSize);
 
-        SkFont font(sk_tool_utils::create_portable_typeface());
+        SkFont font(ToolUtils::create_portable_typeface());
 
         const int W = 6;
 
@@ -95,8 +95,7 @@
 
         SkMatrix lm;
         lm.setScale(SkIntToScalar(16), SkIntToScalar(16));
-        fBG = SkShader::MakeBitmapShader(bg, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode,
-                                         &lm);
+        fBG = bg.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &lm);
 
         SkBitmap srcBmp;
         srcBmp.allocN32Pixels(kSize, kSize);
@@ -109,8 +108,7 @@
                 pixels[kSize * y + x] = rowColor;
             }
         }
-        fSrc = SkShader::MakeBitmapShader(srcBmp, SkShader::kClamp_TileMode,
-                                          SkShader::kClamp_TileMode);
+        fSrc = srcBmp.makeShader();
         SkBitmap dstBmp;
         dstBmp.allocN32Pixels(kSize, kSize);
         pixels = reinterpret_cast<SkPMColor*>(dstBmp.getPixels());
@@ -122,8 +120,7 @@
                 pixels[kSize * y + x] = colColor;
             }
         }
-        fDst = SkShader::MakeBitmapShader(dstBmp, SkShader::kClamp_TileMode,
-                                          SkShader::kClamp_TileMode);
+        fDst = dstBmp.makeShader();
     }
 
     enum {
diff --git a/gm/xfermodes3.cpp b/gm/xfermodes3.cpp
index 86aee17..45eb568 100644
--- a/gm/xfermodes3.cpp
+++ b/gm/xfermodes3.cpp
@@ -5,15 +5,15 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
+#include "GrContext.h"
 #include "SkBitmap.h"
-#include "SkGradientShader.h"
-#include "SkSurface.h"
 #include "SkBlendModePriv.h"
 #include "SkColorPriv.h"
+#include "SkGradientShader.h"
+#include "SkSurface.h"
 #include "SkTextUtils.h"
-#include "GrContext.h"
+#include "ToolUtils.h"
+#include "gm.h"
 
 namespace skiagm {
 
@@ -23,7 +23,7 @@
  */
 class Xfermodes3GM : public GM {
 public:
-    Xfermodes3GM() { this->setBGColor(sk_tool_utils::color_to_565(0xFF70D0E0)); }
+    Xfermodes3GM() { this->setBGColor(ToolUtils::color_to_565(0xFF70D0E0)); }
 
 protected:
     SkString onShortName() override {
@@ -37,7 +37,7 @@
     void onDraw(SkCanvas* canvas) override {
         canvas->translate(SkIntToScalar(10), SkIntToScalar(20));
 
-        SkFont font(sk_tool_utils::create_portable_typeface());
+        SkFont  font(ToolUtils::create_portable_typeface());
         SkPaint labelP;
 
         constexpr SkColor kSolidColors[] = {
@@ -169,8 +169,7 @@
 
         SkMatrix lm;
         lm.setScale(SkIntToScalar(kCheckSize), SkIntToScalar(kCheckSize));
-        fBGShader = SkShader::MakeBitmapShader(bg, SkShader::kRepeat_TileMode,
-                                               SkShader::kRepeat_TileMode, &lm);
+        fBGShader = bg.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &lm);
 
         SkPaint bmpPaint;
         const SkPoint kCenter = { SkIntToScalar(kSize) / 2, SkIntToScalar(kSize) / 2 };
@@ -179,7 +178,7 @@
         };
         bmpPaint.setShader(SkGradientShader::MakeRadial(kCenter, 3 * SkIntToScalar(kSize) / 4,
                                                         kColors, nullptr, SK_ARRAY_COUNT(kColors),
-                                                        SkShader::kRepeat_TileMode));
+                                                        SkTileMode::kRepeat));
 
         SkBitmap bmp;
         bmp.allocN32Pixels(kSize, kSize);
@@ -190,8 +189,7 @@
                         7 * SkIntToScalar(kSize) / 8, 7 * SkIntToScalar(kSize) / 8};
         bmpCanvas.drawRect(rect, bmpPaint);
 
-        fBmpShader = SkShader::MakeBitmapShader(bmp, SkShader::kClamp_TileMode,
-                                                SkShader::kClamp_TileMode);
+        fBmpShader = bmp.makeShader();
     }
 
     enum {
diff --git a/gm/yuvtorgbeffect.cpp b/gm/yuvtorgbeffect.cpp
index 12d651b..fe8d127 100644
--- a/gm/yuvtorgbeffect.cpp
+++ b/gm/yuvtorgbeffect.cpp
@@ -41,7 +41,8 @@
     }
 
     SkISize onISize() override {
-        return SkISize::Make(238, 120);
+        int numRows = kLastEnum_SkYUVColorSpace + 1;
+        return SkISize::Make(238, kDrawPad + numRows * kColorSpaceOffset);
     }
 
     void onOnceBeforeDraw() override {
@@ -72,8 +73,8 @@
         }
     }
 
-    void onDraw(GrContext* context, GrRenderTargetContext* renderTargetContext,
-                SkCanvas* canvas) override {
+    DrawResult onDraw(GrContext* context, GrRenderTargetContext* renderTargetContext,
+                      SkCanvas* canvas, SkString* errorMsg) override {
         GrProxyProvider* proxyProvider = context->priv().proxyProvider();
         sk_sp<GrTextureProxy> proxies[3];
 
@@ -81,15 +82,11 @@
             proxies[i] = proxyProvider->createTextureProxy(fImage[i], kNone_GrSurfaceFlags, 1,
                                                            SkBudgeted::kYes, SkBackingFit::kExact);
             if (!proxies[i]) {
-                DrawFailureMessage(canvas, "Failed to create proxy");
-                return;
+                *errorMsg = "Failed to create proxy";
+                return DrawResult::kFail;
             }
         }
 
-        constexpr SkScalar kDrawPad = 10.f;
-        constexpr SkScalar kTestPad = 10.f;
-        constexpr SkScalar kColorSpaceOffset = 36.f;
-
         for (int space = kJPEG_SkYUVColorSpace; space <= kLastEnum_SkYUVColorSpace; ++space) {
             SkRect renderRect = SkRect::MakeWH(SkIntToScalar(fImage[0]->width()),
                                                SkIntToScalar(fImage[0]->height()));
@@ -126,11 +123,16 @@
                 x += renderRect.width() + kTestPad;
             }
         }
+        return DrawResult::kOk;
      }
 
 private:
     sk_sp<SkImage> fImage[3];
 
+    static constexpr SkScalar kDrawPad = 10.f;
+    static constexpr SkScalar kTestPad = 10.f;
+    static constexpr SkScalar kColorSpaceOffset = 36.f;
+
     typedef GM INHERITED;
 };
 
@@ -150,7 +152,8 @@
     }
 
     SkISize onISize() override {
-        return SkISize::Make(48, 120);
+        int numRows = kLastEnum_SkYUVColorSpace + 1;
+        return SkISize::Make(48, kDrawPad + numRows * kColorSpaceOffset);
     }
 
     void onOnceBeforeDraw() override {
@@ -189,8 +192,8 @@
         }
     }
 
-    void onDraw(GrContext* context, GrRenderTargetContext* renderTargetContext,
-                SkCanvas* canvas) override {
+    DrawResult onDraw(GrContext* context, GrRenderTargetContext* renderTargetContext,
+                      SkCanvas* canvas, SkString* errorMsg) override {
         GrProxyProvider* proxyProvider = context->priv().proxyProvider();
         sk_sp<GrTextureProxy> proxies[2];
 
@@ -198,8 +201,8 @@
             proxies[i] = proxyProvider->createTextureProxy(fImage[i], kNone_GrSurfaceFlags, 1,
                                                            SkBudgeted::kYes, SkBackingFit::kExact);
             if (!proxies[i]) {
-                DrawFailureMessage(canvas, "Failed to create proxy");
-                return;
+                *errorMsg = "Failed to create proxy";
+                return DrawResult::kFail;
             }
         }
 
@@ -210,10 +213,6 @@
             { -1, SkColorChannel::kA }
         };
 
-        constexpr SkScalar kDrawPad = 10.f;
-        constexpr SkScalar kTestPad = 10.f;
-        constexpr SkScalar kColorSpaceOffset = 36.f;
-
         for (int space = kJPEG_SkYUVColorSpace; space <= kLastEnum_SkYUVColorSpace; ++space) {
             SkRect renderRect = SkRect::MakeWH(SkIntToScalar(fImage[0]->width()),
                                                SkIntToScalar(fImage[0]->height()));
@@ -236,11 +235,16 @@
                 renderTargetContext->priv().testingOnly_addDrawOp(std::move(op));
             }
         }
+        return DrawResult::kOk;
     }
 
 private:
     sk_sp<SkImage> fImage[2];
 
+    static constexpr SkScalar kDrawPad = 10.f;
+    static constexpr SkScalar kTestPad = 10.f;
+    static constexpr SkScalar kColorSpaceOffset = 36.f;
+
     typedef GM INHERITED;
 };
 
diff --git a/gn/BUILD.gn b/gn/BUILD.gn
index 9bfa415..6183189 100644
--- a/gn/BUILD.gn
+++ b/gn/BUILD.gn
@@ -296,6 +296,10 @@
       # Probably only triggers when /EHsc is enabled.
       "/wd4291",  # no matching operator delete found;
                   # memory will not be freed if initialization throws an exception
+
+      # These only show up in shared builds:
+      "/wd4251",  # class 'type' needs to have dll-interface to be used by clients of class 'type2'
+      "/wd4275",  # non dll-interface class 'base' used as base for dll-interface class 'derived'
     ]
   } else {
     cflags += [
@@ -318,6 +322,7 @@
 
   if (is_clang) {
     cflags += [
+      "-fcolor-diagnostics",
       "-Weverything",
       "-Wno-unknown-warning-option",  # Let older Clangs ignore newer Clangs' warnings.
     ]
@@ -428,10 +433,7 @@
   } else if (is_win) {
     cflags = [ "/Z7" ]
     if (is_clang) {
-      cflags += [
-        "-mllvm",
-        "-emit-codeview-ghash-section",
-      ]
+      cflags += [ "-gcodeview-ghash" ]
       ldflags = [ "/DEBUG:GHASH" ]
     } else {
       ldflags = [ "/DEBUG:FASTLINK" ]
diff --git a/gn/bench.gni b/gn/bench.gni
index 47b7472..df4ed52 100644
--- a/gn/bench.gni
+++ b/gn/bench.gni
@@ -31,10 +31,8 @@
   "$_bench/ClipStrategyBench.cpp",
   "$_bench/CmapBench.cpp",
   "$_bench/CodecBench.cpp",
-  "$_bench/ColorCanvasDrawBitmapBench.cpp",
   "$_bench/ColorFilterBench.cpp",
   "$_bench/ColorPrivBench.cpp",
-  "$_bench/ColorSpaceXformBench.cpp",
   "$_bench/CompositingImagesBench.cpp",
   "$_bench/ControlBench.cpp",
   "$_bench/CoverageBench.cpp",
diff --git a/gn/compile_processors.py b/gn/compile_processors.py
index 20b8d7d..922b653 100755
--- a/gn/compile_processors.py
+++ b/gn/compile_processors.py
@@ -13,15 +13,20 @@
 clangFormat = sys.argv[2]
 processors = sys.argv[3:]
 for p in processors:
-    path, _ = os.path.splitext(p)
     print("Recompiling " + p + "...")
     try:
-        subprocess.check_output([skslc, p, path + ".h"])
+        noExt, _ = os.path.splitext(p)
+        head, tail = os.path.split(noExt)
+        targetDir = os.path.join(head, "generated")
+        if not os.path.exists(targetDir):
+            os.mkdir(targetDir)
+        target = os.path.join(targetDir, tail)
+        subprocess.check_output([skslc, p, target + ".h"])
         subprocess.check_call(clangFormat + " --sort-includes=false -i \"" +
-                              path + ".h\"", shell=True)
-        subprocess.check_output([skslc, p, path + ".cpp"])
+                              target + ".h\"", shell=True)
+        subprocess.check_output([skslc, p, target + ".cpp"])
         subprocess.check_call(clangFormat + " --sort-includes=false -i \"" +
-                              path + ".cpp\"", shell=True)
+                              target + ".cpp\"", shell=True)
     except subprocess.CalledProcessError as err:
         print("### Error compiling " + p + ":")
         print(err.output)
diff --git a/gn/core.gni b/gn/core.gni
index d5b99fd..f80f511 100644
--- a/gn/core.gni
+++ b/gn/core.gni
@@ -7,6 +7,97 @@
 _src = get_path_info("../src", "abspath")
 _include = get_path_info("../include", "abspath")
 
+skia_core_public = [
+  "$_include/core/SkAnnotation.h",
+  "$_include/core/SkBBHFactory.h",
+  "$_include/core/SkBitmap.h",
+  "$_include/core/SkBlendMode.h",
+  "$_include/core/SkBlurTypes.h",
+  "$_include/core/SkCanvas.h",
+  "$_include/core/SkCanvasVirtualEnforcer.h",
+  "$_include/core/SkClipOp.h",
+  "$_include/core/SkColor.h",
+  "$_include/core/SkColorFilter.h",
+  "$_include/core/SkColorPriv.h",
+  "$_include/core/SkColorSpace.h",
+  "$_include/core/SkContourMeasure.h",
+  "$_include/core/SkCoverageMode.h",
+  "$_include/core/SkCubicMap.h",
+  "$_include/core/SkData.h",
+  "$_include/core/SkDataTable.h",
+  "$_include/core/SkDeferredDisplayListRecorder.h",
+  "$_include/core/SkDeque.h",
+  "$_include/core/SkDocument.h",
+  "$_include/core/SkDrawLooper.h",
+  "$_include/core/SkDrawable.h",
+  "$_include/core/SkEncodedImageFormat.h",
+  "$_include/core/SkExecutor.h",
+  "$_include/core/SkFilterQuality.h",
+  "$_include/core/SkFlattenable.h",
+  "$_include/core/SkFont.h",
+  "$_include/core/SkFontArguments.h",
+  "$_include/core/SkFontLCDConfig.h",
+  "$_include/core/SkFontMetrics.h",
+  "$_include/core/SkFontMgr.h",
+  "$_include/core/SkFontParameters.h",
+  "$_include/core/SkFontStyle.h",
+  "$_include/core/SkFontTypes.h",
+  "$_include/core/SkGraphics.h",
+  "$_include/core/SkICC.h",
+  "$_include/core/SkImage.h",
+  "$_include/core/SkImageEncoder.h",
+  "$_include/core/SkImageFilter.h",
+  "$_include/core/SkImageGenerator.h",
+  "$_include/core/SkImageInfo.h",
+  "$_include/core/SkMallocPixelRef.h",
+  "$_include/core/SkMaskFilter.h",
+  "$_include/core/SkMath.h",
+  "$_include/core/SkMatrix.h",
+  "$_include/core/SkMatrix44.h",
+  "$_include/core/SkMilestone.h",
+  "$_include/core/SkOverdrawCanvas.h",
+  "$_include/core/SkPaint.h",
+  "$_include/core/SkPath.h",
+  "$_include/core/SkPathEffect.h",
+  "$_include/core/SkPathMeasure.h",
+  "$_include/core/SkPixelRef.h",
+  "$_include/core/SkPixmap.h",
+  "$_include/core/SkPngChunkReader.h",
+  "$_include/core/SkPoint.h",
+  "$_include/core/SkPoint3.h",
+  "$_include/core/SkPostConfig.h",
+  "$_include/core/SkPreConfig.h",
+  "$_include/core/SkPromiseImageTexture.h",
+  "$_include/core/SkRRect.h",
+  "$_include/core/SkRSXform.h",
+  "$_include/core/SkRWBuffer.h",
+  "$_include/core/SkRasterHandleAllocator.h",
+  "$_include/core/SkRect.h",
+  "$_include/core/SkRefCnt.h",
+  "$_include/core/SkRegion.h",
+  "$_include/core/SkScalar.h",
+  "$_include/core/SkSerialProcs.h",
+  "$_include/core/SkShader.h",
+  "$_include/core/SkSize.h",
+  "$_include/core/SkStream.h",
+  "$_include/core/SkString.h",
+  "$_include/core/SkStrokeRec.h",
+  "$_include/core/SkSurface.h",
+  "$_include/core/SkSurfaceCharacterization.h",
+  "$_include/core/SkSurfaceProps.h",
+  "$_include/core/SkSwizzle.h",
+  "$_include/core/SkTLazy.h",
+  "$_include/core/SkTextBlob.h",
+  "$_include/core/SkTime.h",
+  "$_include/core/SkTraceMemoryDump.h",
+  "$_include/core/SkTypeface.h",
+  "$_include/core/SkTypes.h",
+  "$_include/core/SkUnPreMultiply.h",
+  "$_include/core/SkVertices.h",
+  "$_include/core/SkYUVAIndex.h",
+  "$_include/core/SkYUVASizeInfo.h",
+]
+
 skia_core_sources = [
   "$_src/c/sk_imageinfo.cpp",
   "$_src/c/sk_paint.cpp",
@@ -53,8 +144,6 @@
   "$_src/core/SkCanvas.cpp",
   "$_src/core/SkCanvasPriv.cpp",
   "$_src/core/SkCanvasPriv.h",
-  "$_src/core/SkCoverageDelta.h",
-  "$_src/core/SkCoverageDelta.cpp",
   "$_src/core/SkClipStack.cpp",
   "$_src/core/SkClipStack.h",
   "$_src/core/SkClipStackDevice.cpp",
@@ -64,10 +153,7 @@
   "$_src/core/SkColorMatrixFilterRowMajor255.cpp",
   "$_src/core/SkColorMatrixFilterRowMajor255.h",
   "$_src/core/SkColorSpace.cpp",
-  "$_src/core/SkColorSpaceXformCanvas.cpp",
   "$_src/core/SkColorSpaceXformSteps.cpp",
-  "$_src/core/SkColorSpaceXformer.cpp",
-  "$_src/core/SkColorSpaceXformer.h",
   "$_src/core/SkContourMeasure.cpp",
   "$_src/core/SkConvertPixels.cpp",
   "$_src/core/SkConvertPixels.h",
@@ -77,7 +163,6 @@
   "$_src/core/SkCubicClipper.cpp",
   "$_src/core/SkCubicClipper.h",
   "$_src/core/SkCubicMap.cpp",
-  "$_src/core/SkCubicMap.h",
   "$_src/core/SkData.cpp",
   "$_src/core/SkDataTable.cpp",
   "$_src/core/SkDebug.cpp",
@@ -113,7 +198,6 @@
   "$_src/core/SkEdge.cpp",
   "$_src/core/SkEdge.h",
   "$_src/core/SkFDot6.h",
-  "$_src/core/SkFindAndPlaceGlyph.h",
   "$_src/core/SkArenaAlloc.cpp",
   "$_src/core/SkArenaAllocList.h",
   "$_src/core/SkGaussFilter.cpp",
@@ -133,8 +217,6 @@
   "$_src/core/SkGlobalInitialization_core.cpp",
   "$_src/core/SkGlyph.h",
   "$_src/core/SkGlyph.cpp",
-  "$_src/core/SkStrike.cpp",
-  "$_src/core/SkStrike.h",
   "$_src/core/SkGlyphRun.cpp",
   "$_src/core/SkGlyphRun.h",
   "$_src/core/SkGlyphRunPainter.cpp",
@@ -244,7 +326,6 @@
   "$_src/core/SkScan.h",
   "$_src/core/SkScanPriv.h",
   "$_src/core/SkScan_AAAPath.cpp",
-  "$_src/core/SkScan_DAAPath.cpp",
   "$_src/core/SkScan_AntiPath.cpp",
   "$_src/core/SkScan_Antihair.cpp",
   "$_src/core/SkScan_Hairline.cpp",
@@ -264,8 +345,11 @@
   "$_src/core/SkSpriteBlitter.h",
   "$_src/core/SkStream.cpp",
   "$_src/core/SkStreamPriv.h",
+  "$_src/core/SkStrike.cpp",
+  "$_src/core/SkStrike.h",
   "$_src/core/SkStrikeCache.cpp",
   "$_src/core/SkStrikeCache.h",
+  "$_src/core/SkStrikeInterface.h",
   "$_src/core/SkString.cpp",
   "$_src/core/SkStringUtils.cpp",
   "$_src/core/SkStroke.h",
@@ -349,69 +433,6 @@
   "$_src/shaders/SkShader.cpp",
   "$_src/shaders/SkShaderBase.h",
 
-  "$_include/core/SkBBHFactory.h",
-  "$_include/core/SkBitmap.h",
-  "$_include/core/SkCanvas.h",
-  "$_include/core/SkColor.h",
-  "$_include/core/SkColorFilter.h",
-  "$_include/core/SkColorPriv.h",
-  "$_include/core/SkData.h",
-  "$_include/core/SkDeferredDisplayListRecorder.h",
-  "$_include/core/SkDeque.h",
-  "$_include/core/SkDocument.h",
-  "$_include/core/SkDrawable.h",
-  "$_include/core/SkDrawLooper.h",
-  "$_include/core/SkFlattenable.h",
-  "$_include/core/SkFont.h",
-  "$_include/core/SkFontArguments.h",
-  "$_include/core/SkFontLCDConfig.h",
-  "$_include/core/SkFontMgr.h",
-  "$_include/core/SkFontParameters.h",
-  "$_include/core/SkFontStyle.h",
-  "$_include/core/SkGraphics.h",
-  "$_include/core/SkImage.h",
-  "$_include/core/SkImageEncoder.h",
-  "$_include/core/SkImageFilter.h",
-  "$_include/core/SkImageGenerator.h",
-  "$_include/core/SkImageInfo.h",
-  "$_include/core/SkMallocPixelRef.h",
-  "$_include/core/SkMaskFilter.h",
-  "$_include/core/SkMath.h",
-  "$_include/core/SkMatrix.h",
-  "$_include/core/SkMatrix44.h",
-  "$_include/core/SkOverdrawCanvas.h",
-  "$_include/core/SkPaint.h",
-  "$_include/core/SkPath.h",
-  "$_include/core/SkPathEffect.h",
-  "$_include/core/SkPathMeasure.h",
-  "$_include/core/SkPixelRef.h",
-  "$_include/core/SkPoint.h",
-  "$_include/core/SkPoint3.h",
-  "$_include/core/SkPreConfig.h",
-  "$_include/core/SkPromiseImageTexture.h",
-  "$_include/core/SkRect.h",
-  "$_include/core/SkRefCnt.h",
-  "$_include/core/SkRegion.h",
-  "$_include/core/SkRRect.h",
-  "$_include/core/SkRSXform.h",
-  "$_include/core/SkScalar.h",
-  "$_include/core/SkShader.h",
-  "$_include/core/SkStream.h",
-  "$_include/core/SkString.h",
-  "$_include/core/SkStrokeRec.h",
-  "$_include/core/SkSurface.h",
-  "$_include/core/SkSurfaceCharacterization.h",
-  "$_include/core/SkSwizzle.h",
-  "$_include/core/SkTextBlob.h",
-  "$_include/core/SkTime.h",
-  "$_include/core/SkTLazy.h",
-  "$_include/core/SkTypeface.h",
-  "$_include/core/SkTypes.h",
-  "$_include/core/SkUnPreMultiply.h",
-  "$_include/core/SkVertices.h",
-  "$_include/core/SkYUVAIndex.h",
-  "$_include/core/SkYUVASizeInfo.h",
-
   "$_src/opts/SkBlitMask_opts.h",
   "$_src/opts/SkBlitRow_opts.h",
   "$_src/opts/SkChecksum_opts.h",
@@ -452,9 +473,9 @@
   "$_include/private/SkWeakRefCnt.h",
 ]
 
-skia_pathops_sources = [
-  "$_include/pathops/SkPathOps.h",
+skia_pathops_public = [ "$_include/pathops/SkPathOps.h" ]
 
+skia_pathops_sources = [
   "$_src/pathops/SkAddIntersections.cpp",
   "$_src/pathops/SkDConicLineIntersection.cpp",
   "$_src/pathops/SkDCubicLineIntersection.cpp",
@@ -514,6 +535,12 @@
   "$_src/pathops/SkReduceOrder.h",
 ]
 
+skia_skpicture_public = [
+  "$_include/core/SkMultiPictureDraw.h",
+  "$_include/core/SkPicture.h",
+  "$_include/core/SkPictureRecorder.h",
+]
+
 skia_skpicture_sources = [
   "$_include/core/SkMultiPictureDraw.h",
   "$_include/core/SkPicture.h",
@@ -541,4 +568,7 @@
 skia_core_sources += skia_pathops_sources
 skia_core_sources += skia_skpicture_sources
 
+skia_core_public += skia_pathops_public
+skia_core_public += skia_skpicture_public
+
 skia_core_defines = []  #  Used to be used by Chromium, but no longer.
diff --git a/gn/effects.gni b/gn/effects.gni
index 217eb6a..1081794 100644
--- a/gn/effects.gni
+++ b/gn/effects.gni
@@ -7,6 +7,25 @@
 _src = get_path_info("../src", "abspath")
 _include = get_path_info("../include", "abspath")
 
+skia_effects_public = [
+  "$_include/effects/Sk1DPathEffect.h",
+  "$_include/effects/Sk2DPathEffect.h",
+  "$_include/effects/SkBlurDrawLooper.h",
+  "$_include/effects/SkBlurMaskFilter.h",
+  "$_include/effects/SkColorMatrix.h",
+  "$_include/effects/SkColorMatrixFilter.h",
+  "$_include/effects/SkCornerPathEffect.h",
+  "$_include/effects/SkDashPathEffect.h",
+  "$_include/effects/SkDiscretePathEffect.h",
+  "$_include/effects/SkGradientShader.h",
+  "$_include/effects/SkLayerDrawLooper.h",
+  "$_include/effects/SkLumaColorFilter.h",
+  "$_include/effects/SkOverdrawColorFilter.h",
+  "$_include/effects/SkPerlinNoiseShader.h",
+  "$_include/effects/SkTableColorFilter.h",
+  "$_include/effects/SkTableMaskFilter.h",
+]
+
 skia_effects_sources = [
   "$_src/c/sk_effects.cpp",
 
@@ -30,7 +49,6 @@
   "$_src/effects/SkShaderMaskFilter.cpp",
   "$_src/effects/SkTableColorFilter.cpp",
   "$_src/effects/SkTableMaskFilter.cpp",
-  "$_src/effects/SkToSRGBColorFilter.cpp",
   "$_src/effects/SkTrimPathEffect.cpp",
 
   "$_src/shaders/SkPerlinNoiseShader.cpp",
@@ -49,22 +67,4 @@
   "$_src/shaders/gradients/SkTwoPointConicalGradient.h",
   "$_src/shaders/gradients/SkSweepGradient.cpp",
   "$_src/shaders/gradients/SkSweepGradient.h",
-
-  "$_include/effects/Sk1DPathEffect.h",
-  "$_include/effects/Sk2DPathEffect.h",
-  "$_include/effects/SkBlurDrawLooper.h",
-  "$_include/effects/SkBlurMaskFilter.h",
-  "$_include/effects/SkColorMatrix.h",
-  "$_include/effects/SkColorMatrixFilter.h",
-  "$_include/effects/SkCornerPathEffect.h",
-  "$_include/effects/SkDashPathEffect.h",
-  "$_include/effects/SkDiscretePathEffect.h",
-  "$_include/effects/SkGradientShader.h",
-  "$_include/effects/SkLayerDrawLooper.h",
-  "$_include/effects/SkLumaColorFilter.h",
-  "$_include/effects/SkOverdrawColorFilter.h",
-  "$_include/effects/SkPerlinNoiseShader.h",
-  "$_include/effects/SkTableColorFilter.h",
-  "$_include/effects/SkTableMaskFilter.h",
-  "$_include/effects/SkToSRGBColorFilter.h",
 ]
diff --git a/gn/effects_imagefilters.gni b/gn/effects_imagefilters.gni
index db7de98..e7f3790 100644
--- a/gn/effects_imagefilters.gni
+++ b/gn/effects_imagefilters.gni
@@ -7,6 +7,23 @@
 _src = get_path_info("../src", "abspath")
 _include = get_path_info("../include", "abspath")
 
+skia_effects_imagefilter_public = [
+  "$_include/effects/SkAlphaThresholdFilter.h",
+  "$_include/effects/SkArithmeticImageFilter.h",
+  "$_include/effects/SkBlurImageFilter.h",
+  "$_include/effects/SkColorFilterImageFilter.h",
+  "$_include/effects/SkDisplacementMapEffect.h",
+  "$_include/effects/SkDropShadowImageFilter.h",
+  "$_include/effects/SkImageSource.h",
+  "$_include/effects/SkLightingImageFilter.h",
+  "$_include/effects/SkMagnifierImageFilter.h",
+  "$_include/effects/SkMorphologyImageFilter.h",
+  "$_include/effects/SkOffsetImageFilter.h",
+  "$_include/effects/SkPaintImageFilter.h",
+  "$_include/effects/SkTileImageFilter.h",
+  "$_include/effects/SkXfermodeImageFilter.h",
+]
+
 skia_effects_imagefilter_sources = [
   "$_src/effects/imagefilters/SkAlphaThresholdFilter.cpp",
   "$_src/effects/imagefilters/SkArithmeticImageFilter.cpp",
@@ -26,19 +43,4 @@
   "$_src/effects/imagefilters/SkPictureImageFilter.cpp",
   "$_src/effects/imagefilters/SkTileImageFilter.cpp",
   "$_src/effects/imagefilters/SkXfermodeImageFilter.cpp",
-
-  "$_include/effects/SkAlphaThresholdFilter.h",
-  "$_include/effects/SkArithmeticImageFilter.h",
-  "$_include/effects/SkBlurImageFilter.h",
-  "$_include/effects/SkColorFilterImageFilter.h",
-  "$_include/effects/SkDisplacementMapEffect.h",
-  "$_include/effects/SkDropShadowImageFilter.h",
-  "$_include/effects/SkImageSource.h",
-  "$_include/effects/SkLightingImageFilter.h",
-  "$_include/effects/SkMagnifierImageFilter.h",
-  "$_include/effects/SkMorphologyImageFilter.h",
-  "$_include/effects/SkOffsetImageFilter.h",
-  "$_include/effects/SkPaintImageFilter.h",
-  "$_include/effects/SkTileImageFilter.h",
-  "$_include/effects/SkXfermodeImageFilter.h",
 ]
diff --git a/gn/flutter_defines.gni b/gn/flutter_defines.gni
index bd0b95d..e288410 100644
--- a/gn/flutter_defines.gni
+++ b/gn/flutter_defines.gni
@@ -3,7 +3,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 flutter_defines = [
-  "SK_DISABLE_EXPLICIT_GPU_RESOURCE_ALLOCATION",
+  "SK_DISABLE_OPLIST_SORTING",
   "SK_LEGACY_SKCODEC_NONE_ENUM",
 
   # Flutter always wants this https://github.com/flutter/flutter/issues/11402
@@ -11,7 +11,6 @@
 
   # Remove software rasterizers to save some code size.
   "SK_DISABLE_AAA",
-  "SK_DISABLE_DAA",
 
   # API staging
 
diff --git a/gn/gm.gni b/gn/gm.gni
index 3232b25..6f6216d 100644
--- a/gn/gm.gni
+++ b/gn/gm.gni
@@ -27,6 +27,7 @@
   "$_gm/arithmode.cpp",
   "$_gm/atlastext.cpp",
   "$_gm/b_119394958.cpp",
+  "$_gm/backdrop.cpp",
   "$_gm/badpaint.cpp",
   "$_gm/beziereffects.cpp",
   "$_gm/beziers.cpp",
@@ -88,6 +89,7 @@
   "$_gm/complexclip3.cpp",
   "$_gm/complexclip4.cpp",
   "$_gm/composeshader.cpp",
+  "$_gm/compositor_quads.cpp",
   "$_gm/concavepaths.cpp",
   "$_gm/conicpaths.cpp",
   "$_gm/constcolorprocessor.cpp",
@@ -105,6 +107,8 @@
   "$_gm/crbug_899512.cpp",
   "$_gm/crbug_905548.cpp",
   "$_gm/crbug_918512.cpp",
+  "$_gm/crbug_938592.cpp",
+  "$_gm/crbug_947055.cpp",
   "$_gm/croppedrects.cpp",
   "$_gm/crosscontextimage.cpp",
   "$_gm/cubicpaths.cpp",
@@ -220,6 +224,7 @@
   "$_gm/localmatriximageshader.cpp",
   "$_gm/localmatrixshader.cpp",
   "$_gm/lumafilter.cpp",
+  "$_gm/mac_aa_explorer.cpp",
   "$_gm/makecolorspace.cpp",
   "$_gm/makeRasterImage.cpp",
   "$_gm/mandoline.cpp",
@@ -229,6 +234,7 @@
   "$_gm/megalooper.cpp",
   "$_gm/mipmap.cpp",
   "$_gm/mixedtextblobs.cpp",
+  "$_gm/mixercolorfilter.cpp",
   "$_gm/modecolorfilters.cpp",
   "$_gm/morphology.cpp",
   "$_gm/multipicturedraw.cpp",
@@ -238,6 +244,7 @@
   "$_gm/offsetimagefilter.cpp",
   "$_gm/orientation.cpp",
   "$_gm/ovals.cpp",
+  "$_gm/overdrawcanvas.cpp",
   "$_gm/overdrawcolorfilter.cpp",
   "$_gm/OverStroke.cpp",
   "$_gm/p3.cpp",
@@ -280,6 +287,8 @@
   "$_gm/rrect.cpp",
   "$_gm/rrectclipdrawpaint.cpp",
   "$_gm/rrects.cpp",
+  "$_gm/runtimecolorfilter.cpp",
+  "$_gm/samplelocations.cpp",
   "$_gm/samplerstress.cpp",
   "$_gm/savelayer.cpp",
   "$_gm/scaledstrokes.cpp",
@@ -291,7 +300,6 @@
   "$_gm/shadowutils.cpp",
   "$_gm/shallowgradient.cpp",
   "$_gm/shapes.cpp",
-  "$_gm/shapes_as_paths.cpp",
   "$_gm/sharedcorners.cpp",
   "$_gm/showmiplevels.cpp",
   "$_gm/simpleaaclip.cpp",
@@ -301,6 +309,7 @@
   "$_gm/skbug_4868.cpp",
   "$_gm/skbug_5321.cpp",
   "$_gm/skbug_8664.cpp",
+  "$_gm/skbug_8955.cpp",
   "$_gm/skbug1719.cpp",
   "$_gm/skinning.cpp",
   "$_gm/smallarc.cpp",
@@ -308,7 +317,6 @@
   "$_gm/spritebitmap.cpp",
   "$_gm/srcmode.cpp",
   "$_gm/srgb.cpp",
-  "$_gm/tosrgb_colorfilter.cpp",
   "$_gm/stlouisarch.cpp",
   "$_gm/stringart.cpp",
   "$_gm/stroke_rect_shader.cpp",
diff --git a/gn/gn_to_bp.py b/gn/gn_to_bp.py
index 8174a32..ca2dcc4 100644
--- a/gn/gn_to_bp.py
+++ b/gn/gn_to_bp.py
@@ -128,6 +128,21 @@
           "include/config/mac",
         ],
       },
+      windows: {
+        cflags: [
+          "-mssse3",
+          "-Wno-unknown-pragmas",
+        ],
+        srcs: [
+          $win_srcs
+        ],
+        local_include_dirs: [
+          "include/config/win",
+        ],
+        export_include_dirs: [
+          "include/config/win",
+        ],
+      },
     },
 
     defaults: ["skia_deps",
@@ -173,7 +188,6 @@
         "libpiex",
         "libpng",
         "libz",
-        "libcutils",
     ],
     static_libs: [
         "libarect",
@@ -185,18 +199,40 @@
     target: {
       android: {
         shared_libs: [
+            "libcutils",
             "libEGL",
             "libGLESv2",
             "libheif",
             "libvulkan",
             "libnativewindow",
         ],
+        export_shared_lib_headers: [
+            "libvulkan",
+        ],
+      },
+      host: {
+        static_libs: [
+          "libcutils",
+        ],
       },
       darwin: {
         host_ldlibs: [
             "-framework AppKit",
         ],
       },
+      windows: {
+        // clang-r353983 emits error when building Skia for Windows. Do not
+        // build it for now until the compiler issue is addressed.
+        // enabled: true,
+        host_ldlibs: [
+            "-lgdi32",
+            "-loleaut32",
+            "-lole32",
+            "-lopengl32",
+            "-luuid",
+            "-lwindowscodecs",
+        ],
+      },
     },
 }
 
@@ -207,7 +243,6 @@
         "skia_pgo_no_profile_use"
     ],
     static_libs: [
-        "libjsoncpp",
         "libskia",
     ],
     cflags: [
@@ -271,6 +306,8 @@
     'skia_enable_fontmgr_custom':         'false',
     'skia_enable_fontmgr_custom_empty':   'true',
     'skia_enable_fontmgr_android':        'false',
+    'skia_enable_fontmgr_win':            'false',
+    'skia_enable_fontmgr_win_gdi':        'false',
     'skia_use_fonthost_mac':              'false',
 
     'skia_use_freetype':                  'true',
@@ -289,11 +326,22 @@
   else:
     d['skia_use_vulkan']   = 'false'
     d['skia_enable_gpu']   = 'false'
+
+  if target_os == '"win"':
+    # The Android Windows build system does not provide FontSub.h
+    d['skia_use_xps'] = 'false'
+
+    # BUILDCONFIG.gn expects these to be set when building for Windows, but
+    # we're just creating Android.bp, so we don't need them. Populate with
+    # some dummy values.
+    d['win_vc'] = '"dummy_version"'
+    d['win_sdk_version'] = '"dummy_version"'
   return d
 
 gn_args       = generate_args('"android"', True)
 gn_args_linux = generate_args('"linux"',   False)
 gn_args_mac   = generate_args('"mac"',     False)
+gn_args_win   = generate_args('"win"',     False)
 
 js = gn_to_bp_utils.GenerateJSONFromGN(gn_args)
 
@@ -340,10 +388,18 @@
                                    None)
 mac_srcs        = strip_headers(mac_srcs)
 
+js_win          = gn_to_bp_utils.GenerateJSONFromGN(gn_args_win)
+win_srcs        = strip_slashes(js_win['targets']['//:skia']['sources'])
+gn_to_bp_utils.GrabDependentValues(js_win, '//:skia', 'sources', win_srcs,
+                                   None)
+win_srcs        = strip_headers(win_srcs)
+
 srcs = android_srcs.intersection(linux_srcs).intersection(mac_srcs)
+srcs = srcs.intersection(win_srcs)
 android_srcs    = android_srcs.difference(srcs)
 linux_srcs      =   linux_srcs.difference(srcs)
-mac_srcs        =   mac_srcs.difference(srcs)
+mac_srcs        =     mac_srcs.difference(srcs)
+win_srcs        =     win_srcs.difference(srcs)
 dm_srcs         = strip_headers(dm_srcs)
 nanobench_srcs  = strip_headers(nanobench_srcs)
 
@@ -358,6 +414,7 @@
 android_defines = get_defines(js)
 linux_defines   = get_defines(js_linux)
 mac_defines     = get_defines(js_mac)
+win_defines     = get_defines(js_win)
 
 def mkdir_if_not_exists(path):
   if not os.path.exists(path):
@@ -365,6 +422,7 @@
 mkdir_if_not_exists('include/config/android/')
 mkdir_if_not_exists('include/config/linux/')
 mkdir_if_not_exists('include/config/mac/')
+mkdir_if_not_exists('include/config/win/')
 
 platforms = { 'IOS', 'MAC', 'WIN', 'ANDROID', 'UNIX' }
 
@@ -409,6 +467,7 @@
 
 write_config('include/config/linux/SkUserConfig.h', linux_defines, 'UNIX')
 write_config('include/config/mac/SkUserConfig.h',   mac_defines, 'MAC')
+write_config('include/config/win/SkUserConfig.h',   win_defines, 'WIN')
 
 # Turn a list of strings into the style bpfmt outputs.
 def bpfmt(indent, lst, sort=True):
@@ -446,4 +505,5 @@
     'android_srcs':  bpfmt(10, android_srcs),
     'linux_srcs':    bpfmt(10, linux_srcs),
     'mac_srcs':      bpfmt(10, mac_srcs),
+    'win_srcs':      bpfmt(10, win_srcs),
   })
diff --git a/gn/gn_to_bp_utils.py b/gn/gn_to_bp_utils.py
index c572da8..938024c 100644
--- a/gn/gn_to_bp_utils.py
+++ b/gn/gn_to_bp_utils.py
@@ -104,6 +104,10 @@
   # Most defines go into SkUserConfig.h
   defines.remove('NDEBUG')                 # Controlled by the Android build
   defines.remove('SKIA_IMPLEMENTATION=1')  # don't export this define.
+  if 'WIN32_LEAN_AND_MEAN' in defines:     # Controlled by the Android build
+    defines.remove('WIN32_LEAN_AND_MEAN')
+  if '_HAS_EXCEPTIONS=0' in defines:       # Controlled by the Android build
+    defines.remove('_HAS_EXCEPTIONS=0')
 
   #... and all the #defines we want to put in SkUserConfig.h.
   with open(userConfigPath, 'w') as f:
diff --git a/gn/gn_to_cmake.py b/gn/gn_to_cmake.py
index 641fe08..7d26a2f 100644
--- a/gn/gn_to_cmake.py
+++ b/gn/gn_to_cmake.py
@@ -116,6 +116,7 @@
   '.cc': 'cxx',
   '.cpp': 'cxx',
   '.cxx': 'cxx',
+  '.mm': 'cxx',
   '.c': 'c',
   '.s': 'asm',
   '.S': 'asm',
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 098ea7e..3f8a8f9 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -57,12 +57,13 @@
   "$_src/gpu/GrBackendTextureImageGenerator.h",
   "$_src/gpu/GrAHardwareBufferImageGenerator.cpp",
   "$_src/gpu/GrAHardwareBufferImageGenerator.h",
+  "$_src/gpu/GrAHardwareBufferUtils.cpp",
+  "$_src/gpu/GrAHardwareBufferUtils.h",
   "$_src/gpu/GrBaseContextPriv.h",
   "$_src/gpu/GrBitmapTextureMaker.cpp",
   "$_src/gpu/GrBitmapTextureMaker.h",
   "$_src/gpu/GrBlurUtils.cpp",
   "$_src/gpu/GrBlurUtils.h",
-  "$_src/gpu/GrBuffer.cpp",
   "$_src/gpu/GrBuffer.h",
   "$_src/gpu/GrBufferAllocPool.cpp",
   "$_src/gpu/GrBufferAllocPool.h",
@@ -81,6 +82,7 @@
   "$_src/gpu/GrContextThreadSafeProxy.cpp",
   "$_src/gpu/GrContextThreadSafeProxyPriv.h",
   "$_src/gpu/GrCoordTransform.h",
+  "$_src/gpu/GrCpuBuffer.h",
   "$_src/gpu/GrDDLContext.cpp",
   "$_src/gpu/GrDefaultGeoProcFactory.cpp",
   "$_src/gpu/GrDefaultGeoProcFactory.h",
@@ -88,7 +90,6 @@
   "$_src/gpu/GrDeferredUpload.h",
   "$_src/gpu/GrDeinstantiateProxyTracker.cpp",
   "$_src/gpu/GrDeinstantiateProxyTracker.h",
-  "$_src/gpu/GrDirectContext.cpp",
   "$_src/gpu/GrDistanceFieldGenFromVector.cpp",
   "$_src/gpu/GrDistanceFieldGenFromVector.h",
   "$_src/gpu/GrDrawingManager.cpp",
@@ -106,6 +107,8 @@
   "$_src/gpu/GrGlyph.h",
   "$_src/gpu/GrGpu.cpp",
   "$_src/gpu/GrGpu.h",
+  "$_src/gpu/GrGpuBuffer.cpp",
+  "$_src/gpu/GrGpuBuffer.h",
   "$_src/gpu/GrGpuResourceCacheAccess.h",
   "$_src/gpu/GrGpuCommandBuffer.cpp",
   "$_src/gpu/GrGpuCommandBuffer.h",
@@ -115,6 +118,7 @@
   "$_src/gpu/GrImageContextPriv.h",
   "$_src/gpu/GrImageTextureMaker.cpp",
   "$_src/gpu/GrImageTextureMaker.h",
+  "$_src/gpu/GrLegacyDirectContext.cpp",
   "$_src/gpu/GrMemoryPool.cpp",
   "$_src/gpu/GrMemoryPool.h",
   "$_src/gpu/GrMesh.h",
@@ -178,6 +182,8 @@
   "$_src/gpu/GrResourceProvider.cpp",
   "$_src/gpu/GrResourceProvider.h",
   "$_src/gpu/GrResourceProviderPriv.h",
+  "$_src/gpu/GrSamplePatternDictionary.h",
+  "$_src/gpu/GrSamplePatternDictionary.cpp",
   "$_src/gpu/GrScissorState.h",
   "$_src/gpu/GrSemaphore.h",
   "$_src/gpu/GrShaderCaps.h",
@@ -243,8 +249,6 @@
   "$_src/gpu/ops/GrAAConvexTessellator.h",
   "$_src/gpu/ops/GrAAConvexPathRenderer.cpp",
   "$_src/gpu/ops/GrAAConvexPathRenderer.h",
-  "$_src/gpu/ops/GrAAFillRRectOp.cpp",
-  "$_src/gpu/ops/GrAAFillRRectOp.h",
   "$_src/gpu/ops/GrAAHairLinePathRenderer.cpp",
   "$_src/gpu/ops/GrAAHairLinePathRenderer.h",
   "$_src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp",
@@ -274,6 +278,8 @@
   "$_src/gpu/ops/GrDrawVerticesOp.h",
   "$_src/gpu/ops/GrFillRectOp.cpp",
   "$_src/gpu/ops/GrFillRectOp.h",
+  "$_src/gpu/ops/GrFillRRectOp.cpp",
+  "$_src/gpu/ops/GrFillRRectOp.h",
   "$_src/gpu/ops/GrMeshDrawOp.cpp",
   "$_src/gpu/ops/GrMeshDrawOp.h",
   "$_src/gpu/ops/GrLatticeOp.cpp",
@@ -301,20 +307,6 @@
   "$_src/gpu/ops/GrTextureOp.cpp",
   "$_src/gpu/ops/GrTextureOp.h",
 
-  "$_src/gpu/effects/GrAARectEffect.cpp",
-  "$_src/gpu/effects/GrAARectEffect.h",
-  "$_src/gpu/effects/GrAlphaThresholdFragmentProcessor.cpp",
-  "$_src/gpu/effects/GrAlphaThresholdFragmentProcessor.h",
-  "$_src/gpu/effects/GrBlurredEdgeFragmentProcessor.cpp",
-  "$_src/gpu/effects/GrBlurredEdgeFragmentProcessor.h",
-  "$_src/gpu/effects/GrCircleBlurFragmentProcessor.cpp",
-  "$_src/gpu/effects/GrCircleBlurFragmentProcessor.h",
-  "$_src/gpu/effects/GrCircleEffect.cpp",
-  "$_src/gpu/effects/GrCircleEffect.h",
-  "$_src/gpu/effects/GrConfigConversionEffect.cpp",
-  "$_src/gpu/effects/GrConfigConversionEffect.h",
-  "$_src/gpu/effects/GrConstColorProcessor.cpp",
-  "$_src/gpu/effects/GrConstColorProcessor.h",
   "$_src/gpu/effects/GrCoverageSetOpXP.cpp",
   "$_src/gpu/effects/GrCoverageSetOpXP.h",
   "$_src/gpu/effects/GrCustomXfermode.cpp",
@@ -331,32 +323,18 @@
   "$_src/gpu/effects/GrDisableColorXP.h",
   "$_src/gpu/effects/GrDistanceFieldGeoProc.cpp",
   "$_src/gpu/effects/GrDistanceFieldGeoProc.h",
-  "$_src/gpu/effects/GrEllipseEffect.cpp",
-  "$_src/gpu/effects/GrEllipseEffect.h",
   "$_src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp",
   "$_src/gpu/effects/GrGaussianConvolutionFragmentProcessor.h",
-  "$_src/gpu/effects/GrLumaColorFilterEffect.cpp",
-  "$_src/gpu/effects/GrLumaColorFilterEffect.h",
-  "$_src/gpu/effects/GrMagnifierEffect.cpp",
-  "$_src/gpu/effects/GrMagnifierEffect.h",
   "$_src/gpu/effects/GrMatrixConvolutionEffect.cpp",
   "$_src/gpu/effects/GrMatrixConvolutionEffect.h",
   "$_src/gpu/effects/GrOvalEffect.cpp",
   "$_src/gpu/effects/GrOvalEffect.h",
   "$_src/gpu/effects/GrPorterDuffXferProcessor.cpp",
   "$_src/gpu/effects/GrPorterDuffXferProcessor.h",
-  "$_src/gpu/effects/GrPremulInputFragmentProcessor.cpp",
-  "$_src/gpu/effects/GrPremulInputFragmentProcessor.h",
-  "$_src/gpu/effects/GrRectBlurEffect.cpp",
-  "$_src/gpu/effects/GrRectBlurEffect.h",
-  "$_src/gpu/effects/GrRRectBlurEffect.cpp",
-  "$_src/gpu/effects/GrRRectBlurEffect.h",
   "$_src/gpu/effects/GrRRectEffect.cpp",
   "$_src/gpu/effects/GrRRectEffect.h",
   "$_src/gpu/effects/GrShadowGeoProc.cpp",
   "$_src/gpu/effects/GrShadowGeoProc.h",
-  "$_src/gpu/effects/GrSimpleTextureEffect.cpp",
-  "$_src/gpu/effects/GrSimpleTextureEffect.h",
   "$_src/gpu/effects/GrSkSLFP.cpp",
   "$_src/gpu/effects/GrSkSLFP.h",
   "$_src/gpu/effects/GrSRGBEffect.cpp",
@@ -368,32 +346,68 @@
   "$_src/gpu/effects/GrYUVtoRGBEffect.cpp",
   "$_src/gpu/effects/GrYUVtoRGBEffect.h",
 
+  "$_src/gpu/effects/generated/GrAARectEffect.cpp",
+  "$_src/gpu/effects/generated/GrAARectEffect.h",
+  "$_src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.cpp",
+  "$_src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.h",
+  "$_src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.cpp",
+  "$_src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.h",
+  "$_src/gpu/effects/generated/GrCircleBlurFragmentProcessor.cpp",
+  "$_src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h",
+  "$_src/gpu/effects/generated/GrCircleEffect.cpp",
+  "$_src/gpu/effects/generated/GrCircleEffect.h",
+  "$_src/gpu/effects/generated/GrComposeLerpEffect.cpp",
+  "$_src/gpu/effects/generated/GrComposeLerpEffect.h",
+  "$_src/gpu/effects/generated/GrComposeLerpRedEffect.cpp",
+  "$_src/gpu/effects/generated/GrComposeLerpRedEffect.h",
+  "$_src/gpu/effects/generated/GrConfigConversionEffect.cpp",
+  "$_src/gpu/effects/generated/GrConfigConversionEffect.h",
+  "$_src/gpu/effects/generated/GrConstColorProcessor.cpp",
+  "$_src/gpu/effects/generated/GrConstColorProcessor.h",
+  "$_src/gpu/effects/generated/GrEllipseEffect.cpp",
+  "$_src/gpu/effects/generated/GrEllipseEffect.h",
+  "$_src/gpu/effects/generated/GrLumaColorFilterEffect.cpp",
+  "$_src/gpu/effects/generated/GrLumaColorFilterEffect.h",
+  "$_src/gpu/effects/generated/GrMagnifierEffect.cpp",
+  "$_src/gpu/effects/generated/GrMagnifierEffect.h",
+  "$_src/gpu/effects/generated/GrMixerEffect.cpp",
+  "$_src/gpu/effects/generated/GrMixerEffect.h",
+  "$_src/gpu/effects/generated/GrPremulInputFragmentProcessor.cpp",
+  "$_src/gpu/effects/generated/GrPremulInputFragmentProcessor.h",
+  "$_src/gpu/effects/generated/GrRectBlurEffect.cpp",
+  "$_src/gpu/effects/generated/GrRectBlurEffect.h",
+  "$_src/gpu/effects/generated/GrRRectBlurEffect.cpp",
+  "$_src/gpu/effects/generated/GrRRectBlurEffect.h",
+  "$_src/gpu/effects/generated/GrSimpleTextureEffect.cpp",
+  "$_src/gpu/effects/generated/GrSimpleTextureEffect.h",
+
   # gradients
-  "$_src/gpu/gradients/GrDualIntervalGradientColorizer.cpp",
-  "$_src/gpu/gradients/GrDualIntervalGradientColorizer.h",
-  "$_src/gpu/gradients/GrSingleIntervalGradientColorizer.cpp",
-  "$_src/gpu/gradients/GrSingleIntervalGradientColorizer.h",
-  "$_src/gpu/gradients/GrTextureGradientColorizer.cpp",
-  "$_src/gpu/gradients/GrTextureGradientColorizer.h",
-  "$_src/gpu/gradients/GrUnrolledBinaryGradientColorizer.cpp",
-  "$_src/gpu/gradients/GrUnrolledBinaryGradientColorizer.h",
-  "$_src/gpu/gradients/GrLinearGradientLayout.cpp",
-  "$_src/gpu/gradients/GrLinearGradientLayout.h",
-  "$_src/gpu/gradients/GrRadialGradientLayout.cpp",
-  "$_src/gpu/gradients/GrRadialGradientLayout.h",
-  "$_src/gpu/gradients/GrSweepGradientLayout.cpp",
-  "$_src/gpu/gradients/GrSweepGradientLayout.h",
-  "$_src/gpu/gradients/GrTwoPointConicalGradientLayout.cpp",
-  "$_src/gpu/gradients/GrTwoPointConicalGradientLayout.h",
-  "$_src/gpu/gradients/GrClampedGradientEffect.cpp",
-  "$_src/gpu/gradients/GrClampedGradientEffect.h",
-  "$_src/gpu/gradients/GrTiledGradientEffect.cpp",
-  "$_src/gpu/gradients/GrTiledGradientEffect.h",
   "$_src/gpu/gradients/GrGradientBitmapCache.cpp",
   "$_src/gpu/gradients/GrGradientBitmapCache.h",
   "$_src/gpu/gradients/GrGradientShader.cpp",
   "$_src/gpu/gradients/GrGradientShader.h",
 
+  "$_src/gpu/gradients/generated/GrDualIntervalGradientColorizer.cpp",
+  "$_src/gpu/gradients/generated/GrDualIntervalGradientColorizer.h",
+  "$_src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.cpp",
+  "$_src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.h",
+  "$_src/gpu/gradients/generated/GrTextureGradientColorizer.cpp",
+  "$_src/gpu/gradients/generated/GrTextureGradientColorizer.h",
+  "$_src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.cpp",
+  "$_src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.h",
+  "$_src/gpu/gradients/generated/GrLinearGradientLayout.cpp",
+  "$_src/gpu/gradients/generated/GrLinearGradientLayout.h",
+  "$_src/gpu/gradients/generated/GrRadialGradientLayout.cpp",
+  "$_src/gpu/gradients/generated/GrRadialGradientLayout.h",
+  "$_src/gpu/gradients/generated/GrSweepGradientLayout.cpp",
+  "$_src/gpu/gradients/generated/GrSweepGradientLayout.h",
+  "$_src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.cpp",
+  "$_src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.h",
+  "$_src/gpu/gradients/generated/GrClampedGradientEffect.cpp",
+  "$_src/gpu/gradients/generated/GrClampedGradientEffect.h",
+  "$_src/gpu/gradients/generated/GrTiledGradientEffect.cpp",
+  "$_src/gpu/gradients/generated/GrTiledGradientEffect.h",
+
   # text
   "$_src/gpu/text/GrAtlasManager.cpp",
   "$_src/gpu/text/GrAtlasManager.h",
@@ -412,7 +426,11 @@
   "$_src/gpu/text/GrTextBlobVertexRegenerator.cpp",
   "$_src/gpu/text/GrTextTarget.h",
 
+  "$_src/gpu/gl/GrGLAssembleGLESInterfaceAutogen.cpp",
+  "$_src/gpu/gl/GrGLAssembleGLInterfaceAutogen.cpp",
+  "$_src/gpu/gl/GrGLAssembleHelpers.cpp",
   "$_src/gpu/gl/GrGLAssembleInterface.cpp",
+  "$_src/gpu/gl/GrGLAssembleWebGLInterfaceAutogen.cpp",
   "$_src/gpu/gl/GrGLBuffer.cpp",
   "$_src/gpu/gl/GrGLBuffer.h",
   "$_src/gpu/gl/GrGLCaps.cpp",
@@ -420,7 +438,6 @@
   "$_src/gpu/gl/GrGLContext.cpp",
   "$_src/gpu/gl/GrGLContext.h",
   "$_src/gpu/gl/GrGLMakeNativeInterface_none.cpp",
-  "$_src/gpu/gl/GrGLCreateNullInterface.cpp",
   "$_src/gpu/gl/GrGLDefines.h",
   "$_src/gpu/gl/GrGLGLSL.cpp",
   "$_src/gpu/gl/GrGLGLSL.h",
@@ -430,7 +447,7 @@
   "$_src/gpu/gl/GrGLGpuCommandBuffer.h",
   "$_src/gpu/gl/GrGLGpuProgramCache.cpp",
   "$_src/gpu/gl/GrGLExtensions.cpp",
-  "$_src/gpu/gl/GrGLInterface.cpp",
+  "$_src/gpu/gl/GrGLInterfaceAutogen.cpp",
   "$_src/gpu/gl/GrGLIRect.h",
   "$_src/gpu/gl/GrGLProgram.cpp",
   "$_src/gpu/gl/GrGLProgram.h",
@@ -442,8 +459,6 @@
   "$_src/gpu/gl/GrGLSemaphore.h",
   "$_src/gpu/gl/GrGLStencilAttachment.cpp",
   "$_src/gpu/gl/GrGLStencilAttachment.h",
-  "$_src/gpu/gl/GrGLTestInterface.cpp",
-  "$_src/gpu/gl/GrGLTestInterface.h",
   "$_src/gpu/gl/GrGLTexture.cpp",
   "$_src/gpu/gl/GrGLTexture.h",
   "$_src/gpu/gl/GrGLTextureRenderTarget.cpp",
@@ -529,9 +544,11 @@
   "$_src/gpu/ccpr/GrCCConicShader.cpp",
   "$_src/gpu/ccpr/GrCCConicShader.h",
   "$_src/gpu/ccpr/GrCCCoverageProcessor.cpp",
-  "$_src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp",
-  "$_src/gpu/ccpr/GrCCCoverageProcessor_VSImpl.cpp",
   "$_src/gpu/ccpr/GrCCCoverageProcessor.h",
+  "$_src/gpu/ccpr/GrGSCoverageProcessor.cpp",
+  "$_src/gpu/ccpr/GrGSCoverageProcessor.h",
+  "$_src/gpu/ccpr/GrVSCoverageProcessor.cpp",
+  "$_src/gpu/ccpr/GrVSCoverageProcessor.h",
   "$_src/gpu/ccpr/GrCCCubicShader.cpp",
   "$_src/gpu/ccpr/GrCCCubicShader.h",
   "$_src/gpu/ccpr/GrCCDrawPathsOp.cpp",
diff --git a/gn/pdf.gni b/gn/pdf.gni
index a75bd04..4b63063 100644
--- a/gn/pdf.gni
+++ b/gn/pdf.gni
@@ -32,6 +32,8 @@
   "$_src/pdf/SkPDFFormXObject.h",
   "$_src/pdf/SkPDFGradientShader.cpp",
   "$_src/pdf/SkPDFGradientShader.h",
+  "$_src/pdf/SkPDFGraphicStackState.cpp",
+  "$_src/pdf/SkPDFGraphicStackState.h",
   "$_src/pdf/SkPDFGraphicState.cpp",
   "$_src/pdf/SkPDFGraphicState.h",
   "$_src/pdf/SkPDFMakeCIDGlyphWidthsArray.cpp",
diff --git a/gn/run_sksllex.py b/gn/run_sksllex.py
index 0377283..5a970df 100755
--- a/gn/run_sksllex.py
+++ b/gn/run_sksllex.py
@@ -20,14 +20,6 @@
                           shell=True)
     subprocess.check_call(clangFormat + " -i \"" + src +
                           "/sksl/SkSLLexer.cpp\"", shell=True)
-    subprocess.check_output([sksllex, src + "/sksl/lex/layout.lex",
-                             "LayoutLexer", "LayoutToken",
-                             src + "/sksl/SkSLLayoutLexer.h",
-                             src + "/sksl/SkSLLayoutLexer.cpp"])
-    subprocess.check_call(clangFormat + " -i \"" + src +
-                          "/sksl/SkSLLayoutLexer.h\"", shell=True)
-    subprocess.check_call(clangFormat + " -i \"" + src +
-                          "/sksl/SkSLLayoutLexer.cpp\"", shell=True)
 except subprocess.CalledProcessError as err:
     print("### Lexer error:")
     print(err.output)
diff --git a/gn/samples.gni b/gn/samples.gni
index 05862d2..db6e5fd 100644
--- a/gn/samples.gni
+++ b/gn/samples.gni
@@ -37,6 +37,7 @@
   "$_samplecode/SampleConcavePaths.cpp",
   "$_samplecode/SampleCowboy.cpp",
   "$_samplecode/SampleCusp.cpp",
+  "$_samplecode/SampleDegenerateQuads.cpp",
   "$_samplecode/SampleDegenerateTwoPtRadials.cpp",
   "$_samplecode/SampleEffects.cpp",
   "$_samplecode/SampleEmboss.cpp",
@@ -45,7 +46,6 @@
   "$_samplecode/SampleFilter2.cpp",
   "$_samplecode/SampleFilterQuality.cpp",
   "$_samplecode/SampleFlutterAnimate.cpp",
-  "$_samplecode/SampleFuzz.cpp",
   "$_samplecode/SampleGlyphTransform.cpp",
   "$_samplecode/SampleGradients.cpp",
   "$_samplecode/SampleHairCurves.cpp",
@@ -62,12 +62,12 @@
   "$_samplecode/SampleManyRects.cpp",
   "$_samplecode/SampleMegaStroke.cpp",
   "$_samplecode/SampleNima.cpp",
+  "$_samplecode/SampleMixer.cpp",
   "$_samplecode/SamplePatch.cpp",
   "$_samplecode/SamplePath.cpp",
   "$_samplecode/SamplePathText.cpp",
   "$_samplecode/SamplePathClip.cpp",
   "$_samplecode/SamplePathEffects.cpp",
-  "$_samplecode/SamplePathFuzz.cpp",
   "$_samplecode/SamplePathOverstroke.cpp",
   "$_samplecode/SamplePdfFileViewer.cpp",
   "$_samplecode/SamplePoints.cpp",
@@ -76,6 +76,7 @@
   "$_samplecode/SampleRectanizer.cpp",
   "$_samplecode/SampleRegion.cpp",
   "$_samplecode/SampleRepeatTile.cpp",
+  "$_samplecode/SampleSG.cpp",
   "$_samplecode/SampleShaders.cpp",
   "$_samplecode/SampleShadowColor.cpp",
   "$_samplecode/SampleShadowReference.cpp",
diff --git a/gn/skia.gni b/gn/skia.gni
index 1834ab9..fa745ca 100644
--- a/gn/skia.gni
+++ b/gn/skia.gni
@@ -2,6 +2,21 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-declare_args() {
-  skia_use_icu = !is_fuchsia && !is_ios
+if (!defined(is_skia_standalone)) {
+  is_skia_standalone = false
 }
+is_skia_dev_build = is_skia_standalone && !is_official_build
+
+declare_args() {
+  skia_enable_gpu = true
+  skia_enable_tools = is_skia_dev_build
+  skia_use_icu = !is_fuchsia && !is_ios
+  skia_use_harfbuzz = true
+}
+declare_args() {
+  # TODO: set skia_pdf_subset_harfbuzz to skia_use_harfbuzz.
+  skia_pdf_subset_harfbuzz = false
+}
+
+# Our tools require static linking (they use non-exported symbols), and the GPU backend.
+skia_enable_tools = skia_enable_tools && !is_component_build && skia_enable_gpu
diff --git a/gn/sksl.gni b/gn/sksl.gni
index 5890597..50ec8e7 100644
--- a/gn/sksl.gni
+++ b/gn/sksl.gni
@@ -7,6 +7,7 @@
 _src = get_path_info("../src", "abspath")
 
 skia_sksl_sources = [
+  "$_src/sksl/SkSLByteCodeGenerator.cpp",
   "$_src/sksl/SkSLCFGGenerator.cpp",
   "$_src/sksl/SkSLCompiler.cpp",
   "$_src/sksl/SkSLCPPCodeGenerator.cpp",
@@ -36,11 +37,14 @@
   "$_src/gpu/effects/GrBlurredEdgeFragmentProcessor.fp",
   "$_src/gpu/effects/GrCircleBlurFragmentProcessor.fp",
   "$_src/gpu/effects/GrCircleEffect.fp",
+  "$_src/gpu/effects/GrComposeLerpEffect.fp",
+  "$_src/gpu/effects/GrComposeLerpRedEffect.fp",
   "$_src/gpu/effects/GrConfigConversionEffect.fp",
   "$_src/gpu/effects/GrConstColorProcessor.fp",
   "$_src/gpu/effects/GrEllipseEffect.fp",
   "$_src/gpu/effects/GrLumaColorFilterEffect.fp",
   "$_src/gpu/effects/GrMagnifierEffect.fp",
+  "$_src/gpu/effects/GrMixerEffect.fp",
   "$_src/gpu/effects/GrPremulInputFragmentProcessor.fp",
   "$_src/gpu/effects/GrRectBlurEffect.fp",
   "$_src/gpu/effects/GrRRectBlurEffect.fp",
diff --git a/gn/tests.gni b/gn/tests.gni
index 4df8bae..6bce617 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -52,6 +52,7 @@
   "$_tests/DefaultPathRendererTest.cpp",
   "$_tests/DeferredDisplayListTest.cpp",
   "$_tests/DequeTest.cpp",
+  "$_tests/DescriptorTest.cpp",
   "$_tests/DetermineDomainModeTest.cpp",
   "$_tests/DeviceTest.cpp",
   "$_tests/DiscardableMemoryPoolTest.cpp",
@@ -208,6 +209,7 @@
   "$_tests/RRectInPathTest.cpp",
   "$_tests/RTreeTest.cpp",
   "$_tests/SafeMathTest.cpp",
+  "$_tests/SamplePatternDictionaryTest.cpp",
   "$_tests/ScalarTest.cpp",
   "$_tests/ScaleToSidesTest.cpp",
   "$_tests/SerializationTest.cpp",
@@ -236,6 +238,7 @@
   "$_tests/SkSLErrorTest.cpp",
   "$_tests/SkSLFPTest.cpp",
   "$_tests/SkSLGLSLTest.cpp",
+  "$_tests/SkSLInterpreterTest.cpp",
   "$_tests/SkSLJITTest.cpp",
   "$_tests/SkSLMemoryLayoutTest.cpp",
   "$_tests/SkSLMetalTest.cpp",
@@ -260,9 +263,9 @@
   "$_tests/SwizzlerTest.cpp",
   "$_tests/TArrayTest.cpp",
   "$_tests/TDPQueueTest.cpp",
-  "$_tests/TableColorFilterTest.cpp",
   "$_tests/TemplatesTest.cpp",
   "$_tests/TessellatingPathRendererTests.cpp",
+  "$_tests/TextureBindingsResetTest.cpp",
   "$_tests/Test.cpp",
   "$_tests/TestTest.cpp",
   "$_tests/TestUtils.h",
@@ -274,7 +277,6 @@
   "$_tests/Time.cpp",
   "$_tests/TLazyTest.cpp",
   "$_tests/TopoSortTest.cpp",
-  "$_tests/ToSRGBColorFilter.cpp",
   "$_tests/TraceMemoryDumpTest.cpp",
   "$_tests/TracingTest.cpp",
   "$_tests/TransferPixelsTest.cpp",
@@ -286,6 +288,7 @@
   "$_tests/VkDrawableTest.cpp",
   "$_tests/VkHardwareBufferTest.cpp",
   "$_tests/VkMakeCopyPipelineTest.cpp",
+  "$_tests/VkPriorityExtensionTest.cpp",
   "$_tests/VkWrapTests.cpp",
   "$_tests/VptrTest.cpp",
   "$_tests/WindowRectanglesTest.cpp",
diff --git a/gn/toolchain/BUILD.gn b/gn/toolchain/BUILD.gn
index cf6437b..0438147 100644
--- a/gn/toolchain/BUILD.gn
+++ b/gn/toolchain/BUILD.gn
@@ -270,7 +270,21 @@
 
       rspfile = "{{output}}.rsp"
       rspfile_content = "{{inputs}}"
-      command = "$cc_wrapper $cxx -shared {{ldflags}} @$rspfile {{solibs}} {{libs}} $rpath -o {{output}}"
+
+      # --start-group/--end-group let us link multiple .a {{inputs}}
+      # without worrying about their relative order on the link line.
+      #
+      # This is mostly important for traditional linkers like GNU ld and Gold.
+      # The Mac/iOS linker neither needs nor accepts these flags.
+      # LLD doesn't need these flags, but accepts and ignores them.
+      _start_group = "-Wl,--start-group"
+      _end_group = "-Wl,--end-group"
+      if (is_mac || is_ios) {
+        _start_group = ""
+        _end_group = ""
+      }
+
+      command = "$cc_wrapper $cxx -shared {{ldflags}} $_start_group @$rspfile {{solibs}} $_end_group {{libs}} $rpath -o {{output}}"
       outputs = [
         "{{root_out_dir}}/$soname",
       ]
@@ -280,7 +294,8 @@
     }
 
     tool("link") {
-      rspfile = "{{output}}.rsp"
+      exe_name = "{{root_out_dir}}/{{target_output_name}}{{output_extension}}"
+      rspfile = "$exe_name.rsp"
       rspfile_content = "{{inputs}}"
 
       # --start-group/--end-group let us link multiple .a {{inputs}}
@@ -295,10 +310,18 @@
         _start_group = ""
         _end_group = ""
       }
-      command = "$cc_wrapper $cxx {{ldflags}} $_start_group @$rspfile {{solibs}} $_end_group {{libs}} -o {{output}}"
+      if (is_ios) {
+        command = "$cc_wrapper $cxx {{ldflags}} $_start_group @$rspfile {{solibs}} $_end_group {{libs}} -o $exe_name && dsymutil $exe_name"
+      } else {
+        command = "$cc_wrapper $cxx {{ldflags}} $_start_group @$rspfile {{solibs}} $_end_group {{libs}} -o $exe_name"
+      }
+
       outputs = [
-        "{{root_out_dir}}/{{target_output_name}}{{output_extension}}",
+        "$exe_name",
       ]
+      if (is_ios) {
+        outputs += [ "{{root_out_dir}}/{{target_output_name}}.dSYM" ]
+      }
       description = "link {{output}}"
     }
 
diff --git a/gn/utils.gni b/gn/utils.gni
index 71a5224..74d890c 100644
--- a/gn/utils.gni
+++ b/gn/utils.gni
@@ -7,7 +7,7 @@
 _src = get_path_info("../src", "abspath")
 _include = get_path_info("../include", "abspath")
 
-skia_utils_sources = [
+skia_utils_public = [
   "$_include/utils/SkAnimCodecPlayer.h",
   "$_include/utils/SkFrontBufferedStream.h",
   "$_include/utils/SkCamera.h",
@@ -22,7 +22,12 @@
   "$_include/utils/SkParsePath.h",
   "$_include/utils/SkRandom.h",
   "$_include/utils/SkShadowUtils.h",
+  
+  #mac
+  "$_include/utils/mac/SkCGUtils.h",
+]
 
+skia_utils_sources = [
   "$_src/utils/Sk3D.cpp",
   "$_src/utils/SkAnimCodecPlayer.cpp",
   "$_src/utils/SkBase64.cpp",
@@ -72,7 +77,6 @@
   "$_src/utils/SkWhitelistTypefaces.cpp",
 
   #mac
-  "$_include/utils/mac/SkCGUtils.h",
   "$_src/utils/mac/SkCreateCGImageRef.cpp",
   "$_src/utils/mac/SkUniqueCFRef.h",
 
diff --git a/gn/xps.gni b/gn/xps.gni
index 60b37f0..48b054c 100644
--- a/gn/xps.gni
+++ b/gn/xps.gni
@@ -7,6 +7,10 @@
 _src = get_path_info("../src", "abspath")
 _include = get_path_info("../include", "abspath")
 
+skia_xps_public = [
+  "$_include/docs/SkXPSDocument.h",
+]
+
 skia_xps_sources = [
   "$_include/docs/SkXPSDocument.h",
   "$_src/xps/SkXPSDocument.cpp",
diff --git a/include/android/SkAndroidFrameworkUtils.h b/include/android/SkAndroidFrameworkUtils.h
index 67b8492..a4a937e 100644
--- a/include/android/SkAndroidFrameworkUtils.h
+++ b/include/android/SkAndroidFrameworkUtils.h
@@ -40,6 +40,15 @@
     static sk_sp<SkSurface> getSurfaceFromCanvas(SkCanvas* canvas);
 
     static int SaveBehind(SkCanvas* canvas, const SkRect* subset);
+
+    /**
+     * Unrolls a chain of nested SkPaintFilterCanvas to return the base wrapped canvas.
+     *
+     *  @param  canvas A SkPaintFilterCanvas or any other SkCanvas subclass.
+     *
+     *  @return SkCanvas that was found in the innermost SkPaintFilterCanvas.
+     */
+    static SkCanvas* getBaseWrappedCanvas(SkCanvas* canvas);
 };
 
 #endif // SK_BUILD_FOR_ANDROID_ANDROID
diff --git a/include/android/SkAnimatedImage.h b/include/android/SkAnimatedImage.h
index 983a57b..7c6855b 100644
--- a/include/android/SkAnimatedImage.h
+++ b/include/android/SkAnimatedImage.h
@@ -28,6 +28,19 @@
      *  Returns null on failure to allocate pixels. On success, this will
      *  decode the first frame.
      *
+     *  @param info Width and height may require scaling.
+     *  @param cropRect Rectangle to crop to after scaling.
+     *  @param postProcess Picture to apply after scaling and cropping.
+     */
+    static sk_sp<SkAnimatedImage> Make(std::unique_ptr<SkAndroidCodec>,
+            const SkImageInfo& info, SkIRect cropRect, sk_sp<SkPicture> postProcess);
+
+    /**
+     *  Create an SkAnimatedImage from the SkAndroidCodec.
+     *
+     *  Returns null on failure to allocate pixels. On success, this will
+     *  decode the first frame.
+     *
      *  @param scaledSize Size to draw the image, possibly requiring scaling.
      *  @param cropRect Rectangle to crop to after scaling.
      *  @param postProcess Picture to apply after scaling and cropping.
diff --git a/include/codec/SkCodec.h b/include/codec/SkCodec.h
index 6ce7b99..15e9e47 100644
--- a/include/codec/SkCodec.h
+++ b/include/codec/SkCodec.h
@@ -609,7 +609,8 @@
     struct FrameInfo {
         /**
          *  The frame that this frame needs to be blended with, or
-         *  kNoFrame if this frame is independent.
+         *  kNoFrame if this frame is independent (so it can be
+         *  drawn over an uninitialized buffer).
          *
          *  Note that this is the *earliest* frame that can be used
          *  for blending. Any frame from [fRequiredFrame, i) can be
diff --git a/include/codec/SkCodecAnimation.h b/include/codec/SkCodecAnimation.h
index 9a4daff..2ddb78d 100644
--- a/include/codec/SkCodecAnimation.h
+++ b/include/codec/SkCodecAnimation.h
@@ -39,5 +39,5 @@
          */
         kRestorePrevious    = 3,
     };
-};
+}
 #endif // SkCodecAnimation_DEFINED
diff --git a/include/core/SkBitmap.h b/include/core/SkBitmap.h
index 0b7339d..76f255d 100644
--- a/include/core/SkBitmap.h
+++ b/include/core/SkBitmap.h
@@ -5,16 +5,6 @@
  * found in the LICENSE file.
  */
 
-/* Generated by tools/bookmaker from include/core/SkBitmap.h and docs/SkBitmap_Reference.bmh
-   on 2018-09-13 13:59:55. Additional documentation and examples can be found at:
-   https://skia.org/user/api/SkBitmap_Reference
-
-   You may edit either file directly. Structural changes to public interfaces require
-   editing both files. After editing docs/SkBitmap_Reference.bmh, run:
-       bookmaker -b docs -i include/core/SkBitmap.h -p
-   to create an updated version of this file.
- */
-
 #ifndef SkBitmap_DEFINED
 #define SkBitmap_DEFINED
 
@@ -23,12 +13,14 @@
 #include "SkPixmap.h"
 #include "SkPoint.h"
 #include "SkRefCnt.h"
+#include "SkTileMode.h"
 
 struct SkMask;
 struct SkIRect;
 struct SkRect;
 class SkPaint;
 class SkPixelRef;
+class SkShader;
 class SkString;
 
 /** \class SkBitmap
@@ -473,9 +465,7 @@
         @param info   contains width, height, SkAlphaType, SkColorType, SkColorSpace
         @param flags  kZeroPixels_AllocFlag, or zero
     */
-    void allocPixelsFlags(const SkImageInfo& info, uint32_t flags) {
-        SkASSERT_RELEASE(this->tryAllocPixelsFlags(info, flags));
-    }
+    void allocPixelsFlags(const SkImageInfo& info, uint32_t flags);
 
     /** Sets SkImageInfo to info following the rules in setInfo() and allocates pixel
         memory. rowBytes must equal or exceed info.width() times info.bytesPerPixel(),
@@ -511,9 +501,7 @@
         @param info      contains width, height, SkAlphaType, SkColorType, SkColorSpace
         @param rowBytes  size of pixel row or larger; may be zero
     */
-    void allocPixels(const SkImageInfo& info, size_t rowBytes) {
-        SkASSERT_RELEASE(this->tryAllocPixels(info, rowBytes));
-    }
+    void allocPixels(const SkImageInfo& info, size_t rowBytes);
 
     /** Sets SkImageInfo to info following the rules in setInfo() and allocates pixel
         memory.
@@ -547,9 +535,7 @@
 
         @param info  contains width, height, SkAlphaType, SkColorType, SkColorSpace
     */
-    void allocPixels(const SkImageInfo& info) {
-        this->allocPixels(info, info.minRowBytes());
-    }
+    void allocPixels(const SkImageInfo& info);
 
     /** Sets SkImageInfo to width, height, and native color type; and allocates
         pixel memory. If isOpaque is true, sets SkImageInfo to kOpaque_SkAlphaType;
@@ -568,15 +554,11 @@
         @param isOpaque  true if pixels do not have transparency
         @return          true if pixel storage is allocated
     */
-    bool SK_WARN_UNUSED_RESULT tryAllocN32Pixels(int width, int height, bool isOpaque = false) {
-        SkImageInfo info = SkImageInfo::MakeN32(width, height,
-                                            isOpaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType);
-        return this->tryAllocPixels(info);
-    }
+    bool SK_WARN_UNUSED_RESULT tryAllocN32Pixels(int width, int height, bool isOpaque = false);
 
     /** Sets SkImageInfo to width, height, and the native color type; and allocates
-        pixel memory. If isOpaque is true, sets SkImageInfo to kPremul_SkAlphaType;
-        otherwise, sets to kOpaque_SkAlphaType.
+        pixel memory. If isOpaque is true, sets SkImageInfo to kOpaque_SkAlphaType;
+        otherwise, sets to kPremul_SkAlphaType.
 
         Aborts if width exceeds 29 bits or is negative, or height is negative, or
         allocation fails. Abort steps may be provided by the user at compile time by
@@ -589,11 +571,7 @@
         @param height    pixel row count; must be zero or greater
         @param isOpaque  true if pixels do not have transparency
     */
-    void allocN32Pixels(int width, int height, bool isOpaque = false) {
-        SkImageInfo info = SkImageInfo::MakeN32(width, height,
-                                            isOpaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType);
-        this->allocPixels(info);
-    }
+    void allocN32Pixels(int width, int height, bool isOpaque = false);
 
     /** Sets SkImageInfo to info following the rules in setInfo(), and creates SkPixelRef
         containing pixels and rowBytes. releaseProc, if not nullptr, is called
@@ -689,9 +667,7 @@
         Abort steps may be provided by the user at compile
         time by defining SK_ABORT.
     */
-    void allocPixels() {
-        this->allocPixels((Allocator*)nullptr);
-    }
+    void allocPixels();
 
     /** Allocates pixel memory with allocator, and replaces existing SkPixelRef.
         The allocation size is determined by SkImageInfo width, height, and SkColorType.
@@ -713,9 +689,7 @@
 
         @param allocator  instance of SkBitmap::Allocator instantiation
     */
-    void allocPixels(Allocator* allocator) {
-        SkASSERT_RELEASE(this->tryAllocPixels(allocator));
-    }
+    void allocPixels(Allocator* allocator);
 
     /** Returns SkPixelRef, which contains: pixel base address; its dimensions; and
         rowBytes(), the interval from one row to the next. Does not change SkPixelRef
@@ -1122,6 +1096,11 @@
     */
     bool peekPixels(SkPixmap* pixmap) const;
 
+    sk_sp<SkShader> makeShader(SkTileMode tmx, SkTileMode tmy,
+                               const SkMatrix* localMatrix = nullptr) const;
+    // defaults to Clamp in x, and y
+    sk_sp<SkShader> makeShader(const SkMatrix* localMatrix = nullptr) const;
+
     /** Asserts if internal values are illegal or inconsistent. Only available if
         SK_DEBUG is defined at compile time.
     */
diff --git a/include/core/SkBlendMode.h b/include/core/SkBlendMode.h
index b62132a..d28d2a6 100644
--- a/include/core/SkBlendMode.h
+++ b/include/core/SkBlendMode.h
@@ -5,16 +5,6 @@
  * found in the LICENSE file.
  */
 
-/* Generated by tools/bookmaker from include/core/SkBlendMode.h and docs/SkBlendMode_Reference.bmh
-   on 2018-07-13 08:15:10. Additional documentation and examples can be found at:
-   https://skia.org/user/api/SkBlendMode_Reference
-
-   You may edit either file directly. Structural changes to public interfaces require
-   editing both files. After editing docs/SkBlendMode_Reference.bmh, run:
-       bookmaker -b docs -i include/core/SkBlendMode.h -p
-   to create an updated version of this file.
- */
-
 #ifndef SkBlendMode_DEFINED
 #define SkBlendMode_DEFINED
 
diff --git a/include/core/SkCanvas.h b/include/core/SkCanvas.h
index 4e1c737..e1b4d54 100644
--- a/include/core/SkCanvas.h
+++ b/include/core/SkCanvas.h
@@ -5,16 +5,6 @@
  * found in the LICENSE file.
  */
 
-/* Generated by tools/bookmaker from include/core/SkCanvas.h and docs/SkCanvas_Reference.bmh
-   on 2018-08-28 10:32:58. Additional documentation and examples can be found at:
-   https://skia.org/user/api/SkCanvas_Reference
-
-   You may edit either file directly. Structural changes to public interfaces require
-   editing both files. After editing docs/SkCanvas_Reference.bmh, run:
-       bookmaker -b docs -i include/core/SkCanvas.h -p
-   to create an updated version of this file.
- */
-
 #ifndef SkCanvas_DEFINED
 #define SkCanvas_DEFINED
 
@@ -41,6 +31,7 @@
 class SkGlyphRunBuilder;
 class SkImage;
 class SkImageFilter;
+class SkPaintFilterCanvas;
 class SkPath;
 class SkPicture;
 class SkPixmap;
@@ -295,7 +286,7 @@
         Pixels are readable when SkBaseDevice is raster. Pixels are not readable when SkCanvas
         is returned from GPU surface, returned by SkDocument::beginPage, returned by
         SkPictureRecorder::beginRecording, or SkCanvas is the base of a utility class
-        like SkDebugCanvas.
+        like DebugCanvas.
 
         pixmap is valid only while SkCanvas is in scope and unchanged. Any
         SkCanvas or SkSurface call may invalidate the pixmap values.
@@ -316,7 +307,7 @@
         Pixels are readable when SkBaseDevice is raster, or backed by a GPU.
         Pixels are not readable when SkCanvas is returned by SkDocument::beginPage,
         returned by SkPictureRecorder::beginRecording, or SkCanvas is the base of a utility
-        class like SkDebugCanvas.
+        class like DebugCanvas.
 
         The destination pixel storage must be allocated by the caller.
 
@@ -353,7 +344,7 @@
         Pixels are readable when SkBaseDevice is raster, or backed by a GPU.
         Pixels are not readable when SkCanvas is returned by SkDocument::beginPage,
         returned by SkPictureRecorder::beginRecording, or SkCanvas is the base of a utility
-        class like SkDebugCanvas.
+        class like DebugCanvas.
 
         Caller must allocate pixel storage in pixmap if needed.
 
@@ -388,7 +379,7 @@
         Pixels are readable when SkBaseDevice is raster, or backed by a GPU.
         Pixels are not readable when SkCanvas is returned by SkDocument::beginPage,
         returned by SkPictureRecorder::beginRecording, or SkCanvas is the base of a utility
-        class like SkDebugCanvas.
+        class like DebugCanvas.
 
         Caller must allocate pixel storage in bitmap if needed.
 
@@ -423,7 +414,7 @@
         Pixels are writable when SkBaseDevice is raster, or backed by a GPU.
         Pixels are not writable when SkCanvas is returned by SkDocument::beginPage,
         returned by SkPictureRecorder::beginRecording, or SkCanvas is the base of a utility
-        class like SkDebugCanvas.
+        class like DebugCanvas.
 
         Pixel values are converted only if SkColorType and SkAlphaType
         do not match. Only pixels within both source and destination rectangles
@@ -460,7 +451,7 @@
         Pixels are writable when SkBaseDevice is raster, or backed by a GPU.
         Pixels are not writable when SkCanvas is returned by SkDocument::beginPage,
         returned by SkPictureRecorder::beginRecording, or SkCanvas is the base of a utility
-        class like SkDebugCanvas.
+        class like DebugCanvas.
 
         Pixel values are converted only if SkColorType and SkAlphaType
         do not match. Only pixels within both source and destination rectangles
@@ -614,7 +605,10 @@
             @param bounds          layer dimensions; may be nullptr
             @param paint           applied to layer when overlaying prior layer;
                                    may be nullptr
-            @param backdrop        prior layer copied with SkImageFilter; may be nullptr
+            @param backdrop        If not null, this causes the current layer to be filtered by
+                                   backdrop, and then drawn into the new layer
+                                   (respecting the current clip).
+                                   If null, the new layer is initialized with transparent-black.
             @param saveLayerFlags  SaveLayerRec options to modify layer
             @return                SaveLayerRec fully specified
         */
@@ -631,13 +625,13 @@
             clipMatrix uses alpha channel of image, transformed by clipMatrix, to clip
             layer when drawn to SkCanvas.
 
-            Implementation is not complete; has no effect if SkBaseDevice is GPU-backed.
-
             @param bounds          layer dimensions; may be nullptr
             @param paint           graphics state applied to layer when overlaying prior
                                    layer; may be nullptr
-            @param backdrop        prior layer copied with SkImageFilter;
-                                   may be nullptr
+            @param backdrop        If not null, this causes the current layer to be filtered by
+                                   backdrop, and then drawn into the new layer
+                                   (respecting the current clip).
+                                   If null, the new layer is initialized with transparent-black.
             @param clipMask        clip applied to layer; may be nullptr
             @param clipMatrix      matrix applied to clipMask; may be nullptr to use
                                    identity matrix
@@ -661,7 +655,12 @@
         /** modifies overlay */
         const SkPaint*       fPaint          = nullptr;
 
-        /** applies SkImageFilter to prior layer */
+        /**
+         *  If not null, this triggers the same initialization behavior as setting
+         *  kInitWithPrevious_SaveLayerFlag on fSaveLayerFlags: the current layer is copied into
+         *  the new layer, rather than initializing the new layer with transparent-black.
+         *  This is then filtered by fBackdrop (respecting the current clip).
+         */
         const SkImageFilter* fBackdrop       = nullptr;
 
         /** clips layer with mask alpha */
@@ -1297,12 +1296,8 @@
     /** Draws SkImage image, with its top-left corner at (left, top),
         using clip, SkMatrix, and optional SkPaint paint.
 
-        If paint is supplied, apply SkColorFilter, alpha, SkImageFilter, SkBlendMode,
-        and SkDrawLooper. If image is kAlpha_8_SkColorType, apply SkShader.
-        If paint contains SkMaskFilter, generate mask from image bounds. If generated
-        mask extends beyond image bounds, replicate image edge colors, just as SkShader
-        made from SkImage::makeShader with SkShader::kClamp_TileMode set replicates the
-        image edge color when it samples outside of its bounds.
+        This is equivalent to drawImageRect() using a dst rect at (x,y) with the
+        same width and height of the image.
 
         @param image  uncompressed rectangular map of pixels
         @param left   left side of image
@@ -1316,12 +1311,8 @@
     /** Draws SkImage image, with its top-left corner at (left, top),
         using clip, SkMatrix, and optional SkPaint paint.
 
-        If SkPaint paint is supplied, apply SkColorFilter, alpha, SkImageFilter,
-        SkBlendMode, and SkDrawLooper. If image is kAlpha_8_SkColorType, apply SkShader.
-        If paint contains SkMaskFilter, generate mask from image bounds. If generated
-        mask extends beyond image bounds, replicate image edge colors, just as SkShader
-        made from SkImage::makeShader with SkShader::kClamp_TileMode set replicates the
-        image edge color when it samples outside of its bounds.
+        This is equivalent to drawImageRect() using a dst rect at (x,y) with the
+        same width and height of the image.
 
         @param image  uncompressed rectangular map of pixels
         @param left   left side of image
@@ -1360,6 +1351,10 @@
         as SkShader made from SkImage::makeShader with SkShader::kClamp_TileMode set
         replicates the image edge color when it samples outside of its bounds.
 
+        When using a shader or shader mask filter, its coordinate system is based on the
+        current CTM, so will reflect the dst rect geometry and is equivalent to
+        drawRect(dst). The src rect is only used to access the provided image.
+
         constraint set to kStrict_SrcRectConstraint limits SkPaint SkFilterQuality to
         sample within src; set to kFast_SrcRectConstraint allows sampling outside to
         improve performance.
@@ -1388,6 +1383,10 @@
         as SkShader made from SkImage::makeShader with SkShader::kClamp_TileMode set
         replicates the image edge color when it samples outside of its bounds.
 
+        When using a shader or shader mask filter, its coordinate system is based on the
+        current CTM, so will reflect the dst rect geometry and is equivalent to
+        drawRect(dst). The src rect is only used to access the provided image.
+
         constraint set to kStrict_SrcRectConstraint limits SkPaint SkFilterQuality to
         sample within isrc; set to kFast_SrcRectConstraint allows sampling outside to
         improve performance.
@@ -1414,6 +1413,10 @@
         as SkShader made from SkImage::makeShader with SkShader::kClamp_TileMode set
         replicates the image edge color when it samples outside of its bounds.
 
+        When using a shader or shader mask filter, its coordinate system is based on the
+        current CTM, so will reflect the dst rect geometry and is equivalent to
+        drawRect(dst).
+
         @param image       SkImage containing pixels, dimensions, and format
         @param dst         destination SkRect of image to draw to
         @param paint       SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter,
@@ -1432,6 +1435,10 @@
         as SkShader made from SkImage::makeShader with SkShader::kClamp_TileMode set
         replicates the image edge color when it samples outside of its bounds.
 
+        When using a shader or shader mask filter, its coordinate system is based on the
+        current CTM, so will reflect the dst rect geometry and is equivalent to
+        drawRect(dst). The src rect is only used to access the provided image.
+
         @param image       SkImage containing pixels, dimensions, and format
         @param src         source SkRect of image to draw from
         @param dst         destination SkRect of image to draw to
@@ -1457,6 +1464,10 @@
         as SkShader made from SkImage::makeShader with SkShader::kClamp_TileMode set
         replicates the image edge color when it samples outside of its bounds.
 
+        When using a shader or shader mask filter, its coordinate system is based on the
+        current CTM, so will reflect the dst rect geometry and is equivalent to
+        drawRect(dst). The src rect is only used to access the provided image.
+
         constraint set to kStrict_SrcRectConstraint limits SkPaint SkFilterQuality to
         sample within image; set to kFast_SrcRectConstraint allows sampling outside to
         improve performance.
@@ -1485,6 +1496,10 @@
         as SkShader made from SkImage::makeShader with SkShader::kClamp_TileMode set
         replicates the image edge color when it samples outside of its bounds.
 
+        When using a shader or shader mask filter, its coordinate system is based on the
+        current CTM, so will reflect the dst rect geometry and is equivalent to
+        drawRect(dst).
+
         constraint set to kStrict_SrcRectConstraint limits SkPaint SkFilterQuality to
         sample within image; set to kFast_SrcRectConstraint allows sampling outside to
         improve performance.
@@ -1791,34 +1806,80 @@
     };
 
     /** This is used by the experimental API below. */
-    struct ImageSetEntry {
+    struct SK_API ImageSetEntry {
+        ImageSetEntry(sk_sp<const SkImage> image, const SkRect& srcRect, const SkRect& dstRect,
+                      int matrixIndex, float alpha, unsigned aaFlags, bool hasClip);
+
+        ImageSetEntry(sk_sp<const SkImage> image, const SkRect& srcRect, const SkRect& dstRect,
+                      float alpha, unsigned aaFlags);
+
+        ImageSetEntry();
+        ~ImageSetEntry();
+        ImageSetEntry(const ImageSetEntry&);
+        ImageSetEntry& operator=(const ImageSetEntry&);
+
         sk_sp<const SkImage> fImage;
         SkRect fSrcRect;
         SkRect fDstRect;
-        float fAlpha;
-        unsigned fAAFlags;  // QuadAAFlags
+        int fMatrixIndex = -1; // Index into the preViewMatrices arg, or < 0
+        float fAlpha = 1.f;
+        unsigned fAAFlags = kNone_QuadAAFlags; // QuadAAFlags
+        bool fHasClip = false; // True to use next 4 points in dstClip arg as quad
     };
 
     /**
-     * This is an experimental API for the SkiaRenderer Chromium project. The signature will
-     * surely evolve if this is not removed. It currently offers no performance advantage over
-     * drawing images independently, though may in the future. The antialiasing flags are intended
-     * to allow control over each edge's AA status, to allow perfect seaming for tile sets. The
-     * current implementation only antialiases if all edges are flagged, however.
-     * Results are undefined if an image's src rect is not within the image's bounds.
-     */
-    void experimental_DrawImageSetV1(const ImageSetEntry imageSet[], int cnt,
-                                     SkFilterQuality quality, SkBlendMode mode);
-
-    /**
-     * This is an experimental API for the SkiaRenderer Chromium project. The signature will
-     * surely evolve if this is not removed. The antialiasing flags are intended to allow control
-     * over each edge's AA status, to allow perfect seaming for tile sets.
+     * This is an experimental API for the SkiaRenderer Chromium project, and its API will surely
+     * evolve if it is not removed outright.
      *
-     * When not fully supported, the implementation only antialiases if all edges are flagged.
+     * This behaves very similarly to drawRect() combined with a clipPath() formed by clip
+     * quadrilateral. 'rect' and 'clip' are in the same coordinate space. If 'clip' is null, then it
+     * is as if the rectangle was not clipped (or, alternatively, clipped to itself). If not null,
+     * then it must provide 4 points.
+     *
+     * In addition to combining the draw and clipping into one operation, this function adds the
+     * additional capability of controlling each of the rectangle's edges anti-aliasing
+     * independently.  The edges of the clip will respect the per-edge AA flags. It is required that
+     * 'clip' be contained inside 'rect'. In terms of mapping to edge labels, the 'clip' points
+     * should be ordered top-left, top-right, bottom-right, bottom-left so that the edge between [0]
+     * and [1] is "top", [1] and [2] is "right", [2] and [3] is "bottom", and [3] and [0] is "left".
+     * This ordering matches SkRect::toQuad().
+     *
+     * This API only draws solid color, filled rectangles so it does not accept a full SkPaint.
      */
-    void experimental_DrawEdgeAARectV1(const SkRect& r, QuadAAFlags edgeAA, SkColor color,
-                                       SkBlendMode mode);
+    void experimental_DrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], QuadAAFlags aaFlags,
+                                     SkColor color, SkBlendMode mode);
+    /**
+     * This is an bulk variant of experimental_DrawEdgeAAQuad() that renders 'cnt' textured quads.
+     * For each entry, 'fDstRect' is rendered with its clip (determined by entry's 'fHasClip' and
+     * the current index in 'dstClip'). The entry's fImage is applied to the destination rectangle
+     * by sampling from 'fSrcRect' sub-image.  The corners of 'fSrcRect' map to the corners of
+     * 'fDstRect', just like in drawImageRect(), and they will be properly interpolated when
+     * applying a clip.
+     *
+     * Like experimental_DrawEdgeAAQuad(), each entry can specify edge AA flags that apply to both
+     * the destination rect and its clip.
+     *
+     * If provided, the 'dstClips' array must have length equal 4 * the number of entries with
+     * fHasClip true. If 'dstClips' is null, every entry must have 'fHasClip' set to false. The
+     * destination clip coordinates will be read consecutively with the image set entries, advancing
+     * by 4 points every time an entry with fHasClip is passed.
+     *
+     * This entry point supports per-entry manipulations to the canvas's current matrix. If an
+     * entry provides 'fMatrixIndex' >= 0, it will be drawn as if the canvas's CTM was
+     * canvas->getTotalMatrix() * preViewMatrices[fMatrixIndex]. If 'fMatrixIndex' is less than 0,
+     * the pre-view matrix transform is implicitly the identity, so it will be drawn using just the
+     * current canvas matrix. The pre-view matrix modifies the canvas's view matrix, it does not
+     * affect the local coordinates of each entry.
+     *
+     * An optional paint may be provided, which supports the same subset of features usable with
+     * drawImageRect (i.e. assumed to be filled and no path effects). When a paint is provided, the
+     * image set is drawn as if each image used the applied paint independently, so each is affected
+     * by the image, color, and/or mask filter.
+     */
+    void experimental_DrawEdgeAAImageSet(const ImageSetEntry imageSet[], int cnt,
+                                         const SkPoint dstClips[], const SkMatrix preViewMatrices[],
+                                         const SkPaint* paint = nullptr,
+                                         SrcRectConstraint constraint = kStrict_SrcRectConstraint);
 
     /** Draws text, with origin at (x, y), using clip, SkMatrix, SkFont font,
         and SkPaint paint.
@@ -2353,8 +2414,6 @@
     // that mechanism  will be required to implement the new function.
     virtual void onDrawPaint(const SkPaint& paint);
     virtual void onDrawRect(const SkRect& rect, const SkPaint& paint);
-    virtual void onDrawEdgeAARect(const SkRect& rect, QuadAAFlags edgeAA, SkColor color,
-                                  SkBlendMode mode);
     virtual void onDrawRRect(const SkRRect& rrect, const SkPaint& paint);
     virtual void onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint);
     virtual void onDrawOval(const SkRect& rect, const SkPaint& paint);
@@ -2387,9 +2446,6 @@
     virtual void onDrawImageLattice(const SkImage* image, const Lattice& lattice, const SkRect& dst,
                                     const SkPaint* paint);
 
-    virtual void onDrawImageSet(const ImageSetEntry imageSet[], int count, SkFilterQuality,
-                                SkBlendMode);
-
     virtual void onDrawBitmap(const SkBitmap& bitmap, SkScalar dx, SkScalar dy,
                               const SkPaint* paint);
     virtual void onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* src, const SkRect& dst,
@@ -2410,6 +2466,12 @@
     virtual void onDrawPicture(const SkPicture* picture, const SkMatrix* matrix,
                                const SkPaint* paint);
 
+    virtual void onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], QuadAAFlags aaFlags,
+                                  SkColor color, SkBlendMode mode);
+    virtual void onDrawEdgeAAImageSet(const ImageSetEntry imageSet[], int count,
+                                      const SkPoint dstClips[], const SkMatrix preViewMatrices[],
+                                      const SkPaint* paint, SrcRectConstraint constraint);
+
     enum ClipEdgeStyle {
         kHard_ClipEdgeStyle,
         kSoft_ClipEdgeStyle
@@ -2531,13 +2593,12 @@
     friend class SkCanvasPriv;      // needs kDontClipToLayer_PrivateSaveLayerFlag
     friend class SkDrawIter;        // needs setupDrawForLayerDevice()
     friend class AutoDrawLooper;
-    friend class SkDebugCanvas;     // needs experimental fAllowSimplifyClip
+    friend class DebugCanvas;       // needs experimental fAllowSimplifyClip
     friend class SkSurface_Raster;  // needs getDevice()
     friend class SkNoDrawCanvas;    // needs resetForNextPicture()
     friend class SkPictureRecord;   // predrawNotify (why does it need it? <reed>)
     friend class SkOverdrawCanvas;
     friend class SkRasterHandleAllocator;
-
 protected:
     // For use by SkNoDrawCanvas (via SkCanvasVirtualEnforcer, which can't be a friend)
     SkCanvas(const SkIRect& bounds);
@@ -2609,6 +2670,8 @@
      */
     bool androidFramework_isClipAA() const;
 
+    virtual SkPaintFilterCanvas* internal_private_asPaintFilterCanvas() const { return nullptr; }
+
     /**
      *  Keep track of the device clip bounds and if the matrix is scale-translate.  This allows
      *  us to do a fast quick reject in the common case.
diff --git a/include/core/SkCanvasVirtualEnforcer.h b/include/core/SkCanvasVirtualEnforcer.h
index f097911..4a284b3 100644
--- a/include/core/SkCanvasVirtualEnforcer.h
+++ b/include/core/SkCanvasVirtualEnforcer.h
@@ -53,15 +53,17 @@
 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
     // This is under active development for Chrome and not used in Android. Hold off on adding
     // implementations in Android's SkCanvas subclasses until this stabilizes.
-    void onDrawImageSet(const SkCanvas::ImageSetEntry[], int count, SkFilterQuality,
-                        SkBlendMode) override {};
-    void onDrawEdgeAARect(const SkRect& rect, SkCanvas::QuadAAFlags edgeAA, SkColor color,
-                          SkBlendMode mode) override {};
+    void onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
+            SkCanvas::QuadAAFlags aaFlags, SkColor color, SkBlendMode mode) override {}
+    void onDrawEdgeAAImageSet(const SkCanvas::ImageSetEntry imageSet[], int count,
+            const SkPoint dstClips[], const SkMatrix preViewMatrices[], const SkPaint* paint,
+            SkCanvas::SrcRectConstraint constraint) override {}
 #else
-    void onDrawImageSet(const SkCanvas::ImageSetEntry[], int count, SkFilterQuality,
-                        SkBlendMode) override = 0;
-    void onDrawEdgeAARect(const SkRect& rect, SkCanvas::QuadAAFlags edgeAA, SkColor color,
-                          SkBlendMode mode) override = 0;
+    void onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
+            SkCanvas::QuadAAFlags aaFlags, SkColor color, SkBlendMode mode) override = 0;
+    void onDrawEdgeAAImageSet(const SkCanvas::ImageSetEntry imageSet[], int count,
+            const SkPoint dstClips[], const SkMatrix preViewMatrices[], const SkPaint* paint,
+            SkCanvas::SrcRectConstraint constraint) override = 0;
 #endif
 
     void onDrawBitmap(const SkBitmap& bitmap, SkScalar dx, SkScalar dy,
diff --git a/include/core/SkColor.h b/include/core/SkColor.h
index 638e55b..9ac58f3 100644
--- a/include/core/SkColor.h
+++ b/include/core/SkColor.h
@@ -5,16 +5,6 @@
  * found in the LICENSE file.
  */
 
-/* Generated by tools/bookmaker from include/core/SkColor.h and docs/SkColor_Reference.bmh
-   on 2018-06-14 13:13:34. Additional documentation and examples can be found at:
-   https://skia.org/user/api/SkColor_Reference
-
-   You may edit either file directly. Structural changes to public interfaces require
-   editing both files. After editing docs/SkColor_Reference.bmh, run:
-       bookmaker -b docs -i include/core/SkColor.h -p
-   to create an updated version of this file.
- */
-
 #ifndef SkColor_DEFINED
 #define SkColor_DEFINED
 
@@ -322,6 +312,14 @@
         return fA == 1.0f;
     }
 
+    /** Returns true if all channels are in [0, 1]. */
+    bool fitsInBytes() const {
+        SkASSERT(fA >= 0.0f && fA <= 1.0f);
+        return fR >= 0.0f && fR <= 1.0f &&
+               fG >= 0.0f && fG <= 1.0f &&
+               fB >= 0.0f && fB <= 1.0f;
+    }
+
     /** Returns closest SkRGBA4f to SkColor. Only allowed if SkRGBA4f is unpremultiplied.
 
         @param color   Color with Alpha, red, blue, and green components
diff --git a/include/core/SkColorFilter.h b/include/core/SkColorFilter.h
index 4fbacf3..33d8e08 100644
--- a/include/core/SkColorFilter.h
+++ b/include/core/SkColorFilter.h
@@ -13,14 +13,12 @@
 #include "SkFlattenable.h"
 #include "SkRefCnt.h"
 
-class GrContext;
 class GrColorSpaceInfo;
 class GrFragmentProcessor;
-class SkArenaAlloc;
+class GrRecordingContext;
 class SkBitmap;
 class SkColorSpace;
-class SkColorSpaceXformer;
-class SkRasterPipeline;
+struct SkStageRec;
 class SkString;
 
 /**
@@ -33,39 +31,21 @@
  */
 class SK_API SkColorFilter : public SkFlattenable {
 public:
-    /**
+    /** DEPRECATED. skbug.com/8941
      *  If the filter can be represented by a source color plus Mode, this
      *  returns true, and sets (if not NULL) the color and mode appropriately.
      *  If not, this returns false and ignores the parameters.
      */
     virtual bool asColorMode(SkColor* color, SkBlendMode* bmode) const;
 
-    /**
+    /** DEPRECATED. skbug.com/8941
      *  If the filter can be represented by a 5x4 matrix, this
      *  returns true, and sets the matrix appropriately.
      *  If not, this returns false and ignores the parameter.
      */
     virtual bool asColorMatrix(SkScalar matrix[20]) const;
 
-    /**
-     *  If the filter can be represented by per-component table, return true,
-     *  and if table is not null, copy the bitmap containing the table into it.
-     *
-     *  The table bitmap will be in SkBitmap::kA8_Config. Each row corresponding
-     *  to each component in ARGB order. e.g. row[0] == alpha, row[1] == red,
-     *  etc. To transform a color, you (logically) perform the following:
-     *
-     *      a' = *table.getAddr8(a, 0);
-     *      r' = *table.getAddr8(r, 1);
-     *      g' = *table.getAddr8(g, 2);
-     *      b' = *table.getAddr8(b, 3);
-     *
-     *  The original component value is the horizontal index for a given row,
-     *  and the stored value at that index is the new value for that component.
-     */
-    virtual bool asComponentTable(SkBitmap* table) const;
-
-    void appendStages(SkRasterPipeline*, SkColorSpace*, SkArenaAlloc*, bool shaderIsOpaque) const;
+    bool appendStages(const SkStageRec& rec, bool shaderIsOpaque) const;
 
     enum Flags {
         /** If set the filter methods will not change the alpha channel of the colors.
@@ -80,17 +60,6 @@
     SkColor filterColor(SkColor) const;
     SkColor4f filterColor4f(const SkColor4f&, SkColorSpace*) const;
 
-    /** Create a colorfilter that uses the specified color and mode.
-        If the Mode is DST, this function will return NULL (since that
-        mode will have no effect on the result).
-        @param c    The source color used with the specified mode
-        @param mode The blend that is applied to each color in
-                        the colorfilter's filterSpan[16,32] methods
-        @return colorfilter object that applies the src color and mode,
-                    or NULL if the mode will have no effect.
-    */
-    static sk_sp<SkColorFilter> MakeModeFilter(SkColor c, SkBlendMode mode);
-
     /** Construct a colorfilter whose effect is to first apply the inner filter and then apply
      *  this filter, applied to the output of the inner filter.
      *
@@ -101,25 +70,6 @@
      */
     sk_sp<SkColorFilter> makeComposed(sk_sp<SkColorFilter> inner) const;
 
-    // DEPRECATED, call makeComposed instead
-    static sk_sp<SkColorFilter> MakeComposeFilter(sk_sp<SkColorFilter> outer,
-                                                  sk_sp<SkColorFilter> inner) {
-        return outer ? outer->makeComposed(inner) : inner;
-    }
-
-    /** Construct a color filter that transforms a color by a 4x5 matrix. The matrix is in row-
-     *  major order and the translation column is specified in unnormalized, 0...255, space.
-     */
-    static sk_sp<SkColorFilter> MakeMatrixFilterRowMajor255(const SkScalar array[20]);
-
-    /** Construct a colorfilter that applies the srgb gamma curve to the RGB channels */
-    static sk_sp<SkColorFilter> MakeLinearToSRGBGamma();
-
-    /** Construct a colorfilter that applies the inverse of the srgb gamma curve to the
-     *  RGB channels
-     */
-    static sk_sp<SkColorFilter> MakeSRGBToLinearGamma();
-
 #if SK_SUPPORT_GPU
     /**
      *  A subclass may implement this factory function to work with the GPU backend. It returns
@@ -131,7 +81,7 @@
      *  A null return indicates that the color filter isn't implemented for the GPU backend.
      */
     virtual std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
-            GrContext*, const GrColorSpaceInfo& dstColorSpaceInfo) const;
+            GrRecordingContext*, const GrColorSpaceInfo& dstColorSpaceInfo) const;
 #endif
 
     bool affectsTransparentBlack() const {
@@ -155,24 +105,51 @@
                                   kSkColorFilter_Type, data, size, procs).release()));
     }
 
-protected:
-    SkColorFilter() {}
+#ifdef SK_SUPPORT_LEGACY_COLORFILTER_FACTORIES
+    /** Create a colorfilter that uses the specified color and mode.
+         If the Mode is DST, this function will return NULL (since that
+         mode will have no effect on the result).
+         @param c    The source color used with the specified mode
+         @param mode The blend that is applied to each color in
+         the colorfilter's filterSpan[16,32] methods
+         @return colorfilter object that applies the src color and mode,
+         or NULL if the mode will have no effect.
+     */
+    static sk_sp<SkColorFilter> MakeModeFilter(SkColor c, SkBlendMode mode);
 
-    sk_sp<SkColorFilter> makeColorSpace(SkColorSpaceXformer* xformer) const {
-        return this->onMakeColorSpace(xformer);
+    // DEPRECATED, call makeComposed instead
+    static sk_sp<SkColorFilter> MakeComposeFilter(sk_sp<SkColorFilter> outer,
+                                                  sk_sp<SkColorFilter> inner) {
+        return outer ? outer->makeComposed(inner) : inner;
     }
-    virtual sk_sp<SkColorFilter> onMakeColorSpace(SkColorSpaceXformer*) const {
-        return sk_ref_sp(const_cast<SkColorFilter*>(this));
-    }
+
+    /** Construct a color filter that transforms a color by a 4x5 matrix. The matrix is in row-
+     *  major order and the translation column is specified in unnormalized, 0...255, space.
+     */
+    static sk_sp<SkColorFilter> MakeMatrixFilterRowMajor255(const SkScalar array[20]);
+
+    /** Construct a colorfilter that applies the srgb gamma curve to the RGB channels */
+    static sk_sp<SkColorFilter> MakeLinearToSRGBGamma();
+
+    /** Construct a colorfilter that applies the inverse of the srgb gamma curve to the
+     *  RGB channels
+     */
+    static sk_sp<SkColorFilter> MakeSRGBToLinearGamma();
 
     /**
-     *  If this subclass can optimally createa composition with the inner filter, return it as
-     *  a new filter (which the caller must unref() when it is done). If no such optimization
-     *  is known, return NULL.
+     *  Returns a new filter that returns the weighted average between the outputs of
+     *  two other filters. If either is null, then it is treated as an identity filter.
      *
-     *  e.g. result(color) == this_filter(inner(color))
+     *  result = cf0(color) * (1 - weight) + cf1(color) * weight
+     *
+     *  If both filters are null, or if weight is NaN, then null is returned.
      */
-    virtual sk_sp<SkColorFilter> onMakeComposed(sk_sp<SkColorFilter>) const { return nullptr; }
+    static sk_sp<SkColorFilter> MakeLerp(sk_sp<SkColorFilter> cf0, sk_sp<SkColorFilter> cf1,
+                                         float weight);
+#endif
+
+protected:
+    SkColorFilter() {}
 
 private:
     /*
@@ -184,13 +161,26 @@
      */
     virtual int privateComposedFilterCount() const { return 1; }
 
-    virtual void onAppendStages(SkRasterPipeline*, SkColorSpace*, SkArenaAlloc*,
-                                bool shaderIsOpaque) const = 0;
+    virtual bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const = 0;
 
-    friend class SkColorSpaceXformer;
     friend class SkComposeColorFilter;
 
     typedef SkFlattenable INHERITED;
 };
 
+class SK_API SkColorFilters {
+public:
+    static sk_sp<SkColorFilter> Compose(sk_sp<SkColorFilter> outer, sk_sp<SkColorFilter> inner) {
+        return outer ? outer->makeComposed(inner) : inner;
+    }
+    static sk_sp<SkColorFilter> Blend(SkColor c, SkBlendMode mode);
+    static sk_sp<SkColorFilter> MatrixRowMajor255(const SkScalar array[20]);
+    static sk_sp<SkColorFilter> LinearToSRGBGamma();
+    static sk_sp<SkColorFilter> SRGBToLinearGamma();
+    static sk_sp<SkColorFilter> Lerp(float t, sk_sp<SkColorFilter> dst, sk_sp<SkColorFilter> src);
+
+private:
+    SkColorFilters() = delete;
+};
+
 #endif
diff --git a/include/core/SkColorSpaceXformCanvas.h b/include/core/SkColorSpaceXformCanvas.h
deleted file mode 100644
index 6bca6c7..0000000
--- a/include/core/SkColorSpaceXformCanvas.h
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkColorSpaceXformCanvas_DEFINED
-#define SkColorSpaceXformCanvas_DEFINED
-
-#include <SkCanvas.h>
-#include <SkColorSpace.h>
-#include <memory>
-
-// Proxy SkCanvas calls to unowned target, transforming colors into targetCS as it goes.
-// May return nullptr if |targetCS| is unsupported.
-std::unique_ptr<SkCanvas> SK_API SkCreateColorSpaceXformCanvas(SkCanvas* target,
-                                                               sk_sp<SkColorSpace> targetCS);
-
-#endif  //SkColorSpaceXformCanvas_DEFINED
diff --git a/include/core/SkContourMeasure.h b/include/core/SkContourMeasure.h
index 223f3b5..7e4a95d 100644
--- a/include/core/SkContourMeasure.h
+++ b/include/core/SkContourMeasure.h
@@ -92,9 +92,19 @@
 class SK_API SkContourMeasureIter : SkNoncopyable {
 public:
     SkContourMeasureIter();
+    /**
+     *  Initialize the Iter with a path.
+     *  The parts of the path that are needed are copied, so the client is free to modify/delete
+     *  the path after this call.
+     */
     SkContourMeasureIter(const SkPath& path, bool forceClosed, SkScalar resScale = 1);
     ~SkContourMeasureIter();
 
+    /**
+     *  Reset the Iter with a path.
+     *  The parts of the path that are needed are copied, so the client is free to modify/delete
+     *  the path after this call.
+     */
     void reset(const SkPath& path, bool forceClosed, SkScalar resScale = 1);
 
     /**
@@ -111,7 +121,7 @@
     sk_sp<SkContourMeasure> next();
 
 private:
-    SkPath::Iter    fIter;
+    SkPath::RawIter fIter;
     SkPath          fPath;
     SkScalar        fTolerance;
     bool            fForceClosed;
@@ -122,6 +132,7 @@
 
     SkContourMeasure* buildSegments();
 
+    SkScalar compute_line_seg(SkPoint p0, SkPoint p1, SkScalar distance, unsigned ptIndex);
     SkScalar compute_quad_segs(const SkPoint pts[3], SkScalar distance,
                                int mint, int maxt, unsigned ptIndex);
     SkScalar compute_conic_segs(const SkConic& conic, SkScalar distance,
diff --git a/include/core/SkCubicMap.h b/include/core/SkCubicMap.h
new file mode 100644
index 0000000..1375c78
--- /dev/null
+++ b/include/core/SkCubicMap.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkCubicMap_DEFINED
+#define SkCubicMap_DEFINED
+
+#include "SkPoint.h"
+
+/**
+ *  Fast evaluation of a cubic ease-in / ease-out curve. This is defined as a parametric cubic
+ *  curve inside the unit square.
+ *
+ *  pt[0] is implicitly { 0, 0 }
+ *  pt[3] is implicitly { 1, 1 }
+ *  pts[1,2].X are inside the unit [0..1]
+ */
+class SK_API SkCubicMap {
+public:
+    SkCubicMap(SkPoint p1, SkPoint p2);
+
+    float computeYFromX(float x) const;
+
+    SkPoint computeFromT(float t) const;
+
+private:
+    enum Type {
+        kLine_Type,     // x == y
+        kCubeRoot_Type, // At^3 == x
+        kSolver_Type,   // general monotonic cubic solver
+    };
+
+    SkPoint fCoeff[3];
+    Type    fType;
+};
+
+#endif
+
diff --git a/include/core/SkDeferredDisplayListRecorder.h b/include/core/SkDeferredDisplayListRecorder.h
index 1185e17..c044647 100644
--- a/include/core/SkDeferredDisplayListRecorder.h
+++ b/include/core/SkDeferredDisplayListRecorder.h
@@ -58,51 +58,45 @@
     using PromiseImageTextureReleaseProc = void (*)(PromiseImageTextureContext);
     using PromiseImageTextureDoneProc = void (*)(PromiseImageTextureContext);
 
-    // Deprecated types. To be removed.
-    using LegacyPromiseImageTextureFulfillProc = void (*)(PromiseImageTextureContext,
-                                                          GrBackendTexture*);
-    using TextureContext = PromiseImageTextureContext;
-
-    enum class DelayReleaseCallback : bool { kNo = false, kYes = true };
+    enum class PromiseImageApiVersion { kLegacy, kNew };
 
     /**
-        Create a new SkImage that is very similar to an SkImage created by MakeFromTexture. The main
-        difference is that the client doesn't have the backend texture on the gpu yet but they know
-        all the properties of the texture. So instead of passing in a GrBackendTexture the client
-        supplies a GrBackendFormat, width, height, and GrMipMapped state.
+        Create a new SkImage that is very similar to an SkImage created by MakeFromTexture. The
+        difference is that the caller need not have created the texture nor populated it with the
+        image pixel data. Moreover, the SkImage may be created on a thread as the creation of the
+        image does not require access to the backend API or GrContext. Instead of passing a
+        GrBackendTexture the client supplies a description of the texture consisting of
+        GrBackendFormat, width, height, and GrMipMapped state. The resulting SkImage can be drawn
+        to a SkDeferredDisplayListRecorder or directly to a GPU-backed SkSurface.
 
-        When we actually send the draw calls to the GPU, we will call the textureFulfillProc and
-        the client will return a GrBackendTexture to us. The properties of the GrBackendTexture must
-        match those set during the SkImage creation, and it must have a valid backend gpu texture.
-        The gpu texture supplied by the client must stay valid until we call the textureReleaseProc.
+        When the actual texture is required to perform a backend API draw, textureFulfillProc will
+        be called to receive a GrBackendTexture. The properties of the GrBackendTexture must match
+        those set during the SkImage creation, and it must refer to a valid existing texture in the
+        backend API context/device, and be populated with the image pixel data. The texture contents
+        cannot be modified until textureReleaseProc is called. The texture cannot be deleted until
+        textureDoneProc is called.
 
-        The following applies when DelayReleaseCallback is kNo:
-            When we are done with the texture returned by the textureFulfillProc we will call the
-            textureReleaseProc passing in the textureContext. This is a signal to the client that
-            they are free to delete the underlying gpu texture. If future draws also use the same
-            promise image we will call the textureFulfillProc again if we've already called the
-            textureReleaseProc. We will always call textureFulfillProc and textureReleaseProc in
-            pairs. In other words we will never call textureFulfillProc or textureReleaseProc
-            multiple times for the same textureContext before calling the other.
+        When all the following are true:
+            * the promise SkImage is deleted,
+            * any SkDeferredDisplayLists that recorded draws referencing the image are deleted,
+            * and all draws referencing the texture have been flushed (via GrContext::flush or
+              SkSurface::flush)
+        the textureReleaseProc is called. When the following additional constraint is met
+           * the texture is safe to delete in the underlying API
+        the textureDoneProc is called. For some APIs (e.g. GL) the two states are equivalent.
+        However, for others (e.g. Vulkan) they are not as it is not legal to delete a texture until
+        the GPU work referencing it has completed.
 
-            We call the textureDoneProc when we will no longer call the textureFulfillProc again. We
-            pass in the textureContext as a parameter to the textureDoneProc. We also guarantee that
-            there will be no outstanding textureReleaseProcs that still need to be called when we
-            call the textureDoneProc. Thus when the textureDoneProc gets called the client is able
-            to cleanup all GPU objects and meta data needed for the textureFulfill call.
+        There is at most one call to each of textureFulfillProc, textureReleaseProc, and
+        textureDoneProc. textureDoneProc is always called even if image creation fails or if the
+        image is never fulfilled (e.g. it is never drawn or all draws are clipped out). If
+        textureFulfillProc is called then textureReleaseProc will always be called even if
+        textureFulfillProc failed.
 
-        When delayReleaseCallback is kYes:
-            When all the following are true:
-                * the promise image is deleted,
-                * any SkDeferredDisplayLists that recorded draws referencing the image are deleted,
-                * and the texture is safe to delete in the underlying API with respect to drawn
-                  SkDeferredDisplayLists that reference the image
-            the textureReleaseProc and then textureDoneProc are called. The texture can be deleted
-            by the client as soon as textureReleaseProc is called. In this mode there is only one
-            call to each of textureFulfillProc, textureReleaseProc, and textureDoneProc.
+        If 'version' is set to kLegacy then the textureReleaseProc call is delayed until the
+        conditions for textureDoneProc are met and then they are both called.
 
-
-        This call is only valid if the SkDeferredDisplayListRecorder is backed by a gpu context.
+        This call is only valid if the SkDeferredDisplayListRecorder is backed by a GPU context.
 
         @param backendFormat       format of promised gpu texture
         @param width               width of promised gpu texture
@@ -118,82 +112,51 @@
         @param colorSpace          range of colors; may be nullptr
         @param textureFulfillProc  function called to get actual gpu texture
         @param textureReleaseProc  function called when texture can be released
-        @param promiseDoneProc     function called when we will no longer call textureFulfillProc
+        @param textureDoneProc     function called when we will no longer call textureFulfillProc
         @param textureContext      state passed to textureFulfillProc and textureReleaseProc
+        @param version             controls when textureReleaseProc is called
         @return                    created SkImage, or nullptr
      */
-    sk_sp<SkImage> makePromiseTexture(const GrBackendFormat& backendFormat,
-                                      int width,
-                                      int height,
-                                      GrMipMapped mipMapped,
-                                      GrSurfaceOrigin origin,
-                                      SkColorType colorType,
-                                      SkAlphaType alphaType,
-                                      sk_sp<SkColorSpace> colorSpace,
-                                      PromiseImageTextureFulfillProc textureFulfillProc,
-                                      PromiseImageTextureReleaseProc textureReleaseProc,
-                                      PromiseImageTextureDoneProc textureDoneProc,
-                                      PromiseImageTextureContext textureContext,
-                                      DelayReleaseCallback delayReleaseCallback);
-
-    /** Deprecated version that assumes DelayReleaseCallback::kNo. */
-    sk_sp<SkImage> makePromiseTexture(const GrBackendFormat& backendFormat,
-                                      int width,
-                                      int height,
-                                      GrMipMapped mipMapped,
-                                      GrSurfaceOrigin origin,
-                                      SkColorType colorType,
-                                      SkAlphaType alphaType,
-                                      sk_sp<SkColorSpace> colorSpace,
-                                      PromiseImageTextureFulfillProc textureFulfillProc,
-                                      PromiseImageTextureReleaseProc textureReleaseProc,
-                                      PromiseImageTextureDoneProc textureDoneProc,
-                                      PromiseImageTextureContext textureContext) {
-        return this->makePromiseTexture(backendFormat, width, height, mipMapped, origin, colorType,
-                                        alphaType, colorSpace, textureFulfillProc,
-                                        textureReleaseProc, textureDoneProc, textureContext,
-                                        DelayReleaseCallback::kNo);
-    }
+    sk_sp<SkImage> makePromiseTexture(
+            const GrBackendFormat& backendFormat,
+            int width,
+            int height,
+            GrMipMapped mipMapped,
+            GrSurfaceOrigin origin,
+            SkColorType colorType,
+            SkAlphaType alphaType,
+            sk_sp<SkColorSpace> colorSpace,
+            PromiseImageTextureFulfillProc textureFulfillProc,
+            PromiseImageTextureReleaseProc textureReleaseProc,
+            PromiseImageTextureDoneProc textureDoneProc,
+            PromiseImageTextureContext textureContext,
+            PromiseImageApiVersion version = PromiseImageApiVersion::kLegacy);
 
     /**
-        This entry point operates the same as 'makePromiseTexture' except that its
-        textureFulfillProc can be called up to four times to fetch the required YUVA
-        planes (passing a different textureContext to each call). So, if the 'yuvaIndices'
-        indicate that only the first two backend textures are used, 'textureFulfillProc' will
-        be called with the first two 'textureContexts'.
+        This entry point operates like 'makePromiseTexture' but it is used to construct a SkImage
+        from YUV[A] data. The source data may be planar (i.e. spread across multiple textures). In
+        the extreme Y, U, V, and A are all in different planes and thus the image is specified by
+        four textures. 'yuvaIndices' specifies the mapping from texture color channels to Y, U, V,
+        and possibly A components. It therefore indicates how many unique textures compose the full
+        image. Separate textureFulfillProc, textureReleaseProc, and textureDoneProc calls are made
+        for each texture and each texture has its own PromiseImageTextureContext. 'yuvFormats',
+        'yuvaSizes', and 'textureContexts' have one entry for each of the up to four textures, as
+        indicated by 'yuvaIndices'.
      */
-    sk_sp<SkImage> makeYUVAPromiseTexture(SkYUVColorSpace yuvColorSpace,
-                                          const GrBackendFormat yuvaFormats[],
-                                          const SkISize yuvaSizes[],
-                                          const SkYUVAIndex yuvaIndices[4],
-                                          int imageWidth,
-                                          int imageHeight,
-                                          GrSurfaceOrigin imageOrigin,
-                                          sk_sp<SkColorSpace> imageColorSpace,
-                                          PromiseImageTextureFulfillProc textureFulfillProc,
-                                          PromiseImageTextureReleaseProc textureReleaseProc,
-                                          PromiseImageTextureDoneProc textureDoneProc,
-                                          PromiseImageTextureContext textureContexts[],
-                                          DelayReleaseCallback delayReleaseCallback);
-
-    /** Deprecated version that assumes DelayReleaseCallback::kNo. */
-    sk_sp<SkImage> makeYUVAPromiseTexture(SkYUVColorSpace yuvColorSpace,
-                                          const GrBackendFormat yuvaFormats[],
-                                          const SkISize yuvaSizes[],
-                                          const SkYUVAIndex yuvaIndices[4],
-                                          int imageWidth,
-                                          int imageHeight,
-                                          GrSurfaceOrigin imageOrigin,
-                                          sk_sp<SkColorSpace> imageColorSpace,
-                                          PromiseImageTextureFulfillProc textureFulfillProc,
-                                          PromiseImageTextureReleaseProc textureReleaseProc,
-                                          PromiseImageTextureDoneProc textureDoneProc,
-                                          PromiseImageTextureContext textureContexts[]) {
-        return this->makeYUVAPromiseTexture(
-                yuvColorSpace, yuvaFormats, yuvaSizes, yuvaIndices, imageWidth, imageHeight,
-                imageOrigin, std::move(imageColorSpace), textureFulfillProc, textureReleaseProc,
-                textureDoneProc, textureContexts, DelayReleaseCallback::kNo);
-    }
+    sk_sp<SkImage> makeYUVAPromiseTexture(
+            SkYUVColorSpace yuvColorSpace,
+            const GrBackendFormat yuvaFormats[],
+            const SkISize yuvaSizes[],
+            const SkYUVAIndex yuvaIndices[4],
+            int imageWidth,
+            int imageHeight,
+            GrSurfaceOrigin imageOrigin,
+            sk_sp<SkColorSpace> imageColorSpace,
+            PromiseImageTextureFulfillProc textureFulfillProc,
+            PromiseImageTextureReleaseProc textureReleaseProc,
+            PromiseImageTextureDoneProc textureDoneProc,
+            PromiseImageTextureContext textureContexts[],
+            PromiseImageApiVersion version = PromiseImageApiVersion::kLegacy);
 
 private:
     bool init();
diff --git a/include/core/SkDrawLooper.h b/include/core/SkDrawLooper.h
index c6b2d66..ee0f237 100644
--- a/include/core/SkDrawLooper.h
+++ b/include/core/SkDrawLooper.h
@@ -18,7 +18,6 @@
 
 class  SkArenaAlloc;
 class  SkCanvas;
-class  SkColorSpaceXformer;
 class  SkPaint;
 struct SkRect;
 class  SkString;
@@ -113,16 +112,9 @@
     }
 
 protected:
-    sk_sp<SkDrawLooper> makeColorSpace(SkColorSpaceXformer* xformer) const {
-        return this->onMakeColorSpace(xformer);
-    }
-    virtual sk_sp<SkDrawLooper> onMakeColorSpace(SkColorSpaceXformer*) const = 0;
-
     SkDrawLooper() {}
 
 private:
-    friend class SkColorSpaceXformer;
-
     typedef SkFlattenable INHERITED;
 };
 
diff --git a/include/core/SkExecutor.h b/include/core/SkExecutor.h
index ad612f3..9e1934d 100644
--- a/include/core/SkExecutor.h
+++ b/include/core/SkExecutor.h
@@ -10,8 +10,9 @@
 
 #include <functional>
 #include <memory>
+#include "SkTypes.h"
 
-class SkExecutor {
+class SK_API SkExecutor {
 public:
     virtual ~SkExecutor();
 
diff --git a/include/core/SkFont.h b/include/core/SkFont.h
index 4a14745..74b7df5 100644
--- a/include/core/SkFont.h
+++ b/include/core/SkFont.h
@@ -361,12 +361,12 @@
         @param widths      returns text advances for each glyph; may be nullptr
         @param bounds      returns bounds for each glyph relative to (0, 0); may be nullptr
     */
-    void getWidths(const uint16_t glyphs[], int count, SkScalar widths[], SkRect bounds[]) const {
+    void getWidths(const SkGlyphID glyphs[], int count, SkScalar widths[], SkRect bounds[]) const {
         this->getWidthsBounds(glyphs, count, widths, bounds, nullptr);
     }
 
     // DEPRECATED
-    void getWidths(const uint16_t glyphs[], int count, SkScalar widths[], std::nullptr_t) const {
+    void getWidths(const SkGlyphID glyphs[], int count, SkScalar widths[], std::nullptr_t) const {
         this->getWidths(glyphs, count, widths);
     }
 
@@ -379,7 +379,7 @@
         @param count       number of glyphs
         @param widths      returns text advances for each glyph
      */
-    void getWidths(const uint16_t glyphs[], int count, SkScalar widths[]) const {
+    void getWidths(const SkGlyphID glyphs[], int count, SkScalar widths[]) const {
         this->getWidthsBounds(glyphs, count, widths, nullptr, nullptr);
     }
 
@@ -394,7 +394,7 @@
         @param bounds      returns bounds for each glyph relative to (0, 0); may be nullptr
         @param paint       optional, specifies stroking, SkPathEffect and SkMaskFilter
      */
-    void getWidthsBounds(const uint16_t glyphs[], int count, SkScalar widths[], SkRect bounds[],
+    void getWidthsBounds(const SkGlyphID glyphs[], int count, SkScalar widths[], SkRect bounds[],
                          const SkPaint* paint) const;
 
 
@@ -407,7 +407,7 @@
         @param bounds      returns bounds for each glyph relative to (0, 0); may be nullptr
         @param paint       optional, specifies stroking, SkPathEffect, and SkMaskFilter
      */
-    void getBounds(const uint16_t glyphs[], int count, SkRect bounds[],
+    void getBounds(const SkGlyphID glyphs[], int count, SkRect bounds[],
                    const SkPaint* paint) const {
         this->getWidthsBounds(glyphs, count, nullptr, bounds, paint);
     }
@@ -420,7 +420,7 @@
         @param pos      returns glyphs positions
         @param origin   location of the first glyph. Defaults to {0, 0}.
      */
-    void getPos(const uint16_t glyphs[], int count, SkPoint pos[], SkPoint origin = {0, 0}) const;
+    void getPos(const SkGlyphID glyphs[], int count, SkPoint pos[], SkPoint origin = {0, 0}) const;
 
     /** Retrieves the x-positions for each glyph, beginning at the specified origin. The caller
         must allocated at least count number of elements in the xpos[] array.
@@ -430,7 +430,7 @@
         @param xpos     returns glyphs x-positions
         @param origin   x-position of the first glyph. Defaults to 0.
      */
-    void getXPos(const uint16_t glyphs[], int count, SkScalar xpos[], SkScalar origin = 0) const;
+    void getXPos(const SkGlyphID glyphs[], int count, SkScalar xpos[], SkScalar origin = 0) const;
 
     /** Returns path corresponding to glyph outline.
         If glyph has an outline, copies outline to path and returns true.
@@ -441,7 +441,7 @@
         @param path     pointer to existing SkPath
         @return         true if glyphID is described by path
      */
-    bool getPath(uint16_t glyphID, SkPath* path) const;
+    bool getPath(SkGlyphID glyphID, SkPath* path) const;
 
     /** Returns path corresponding to glyph array.
 
@@ -450,7 +450,7 @@
         @param glyphPathProc function returning one glyph description as path
         @param ctx           function context
    */
-    void getPaths(const uint16_t glyphIDs[], int count,
+    void getPaths(const SkGlyphID glyphIDs[], int count,
                   void (*glyphPathProc)(const SkPath* pathOrNull, const SkMatrix& mx, void* ctx),
                   void* ctx) const;
 
@@ -511,7 +511,6 @@
     friend class SkCanonicalizeFont;
     friend class SkFontPriv;
     friend class SkGlyphRunListPainter;
-    friend class SkPaint;
     friend class SkTextBlobCacheDiffCanvas;
     friend class SVGTextBuilder;
 };
diff --git a/include/core/SkGraphics.h b/include/core/SkGraphics.h
index 9830768..84b0da8 100644
--- a/include/core/SkGraphics.h
+++ b/include/core/SkGraphics.h
@@ -27,12 +27,6 @@
     static void Term() {}
 
     /**
-     *  Return the version numbers for the library. If the parameter is not
-     *  null, it is set to the version number.
-     */
-    static void GetVersion(int32_t* major, int32_t* minor, int32_t* patch);
-
-    /**
      *  Return the max number of bytes that should be used by the font cache.
      *  If the cache needs to allocate more, it will purge previous entries.
      *  This max can be changed by calling SetFontCacheLimit().
diff --git a/include/core/SkImage.h b/include/core/SkImage.h
index c83769c..020c8c2 100644
--- a/include/core/SkImage.h
+++ b/include/core/SkImage.h
@@ -5,16 +5,6 @@
  * found in the LICENSE file.
  */
 
-/* Generated by tools/bookmaker from include/core/SkImage.h and docs/SkImage_Reference.bmh
-   on 2018-09-18 07:26:44. Additional documentation and examples can be found at:
-   https://skia.org/user/api/SkImage_Reference
-
-   You may edit either file directly. Structural changes to public interfaces require
-   editing both files. After editing docs/SkImage_Reference.bmh, run:
-       bookmaker -b docs -i include/core/SkImage.h -p
-   to create an updated version of this file.
- */
-
 #ifndef SkImage_DEFINED
 #define SkImage_DEFINED
 
@@ -24,7 +14,8 @@
 #include "SkImageEncoder.h"
 #include "SkRefCnt.h"
 #include "SkScalar.h"
-#include "SkShader.h"
+#include "SkShader.h"   // can remove once we switch to SkTileMode
+#include "SkTileMode.h"
 
 #if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26
 #include <android/hardware_buffer.h>
@@ -176,8 +167,10 @@
     */
     static sk_sp<SkImage> MakeFromEncoded(sk_sp<SkData> encoded, const SkIRect* subset = nullptr);
 
+    // Experimental
     enum CompressionType {
-        kETC1_CompressionType, //!< compressed data uses ETC1 compression
+        kETC1_CompressionType,
+        kLast_CompressionType = kETC1_CompressionType,
     };
 
     /** Creates a GPU-backed SkImage from compressed data.
@@ -357,7 +350,7 @@
         @param context         GPU context
         @param yuvColorSpace   How the YUV values are converted to RGB. One of:
                                            kJPEG_SkYUVColorSpace, kRec601_SkYUVColorSpace,
-                                           kRec709_SkYUVColorSpace
+                                           kRec709_SkYUVColorSpace, kIdentity_SkYUVColorSpace
         @param yuvaTextures    array of (up to four) YUVA textures on GPU which contain the,
                                possibly interleaved, YUVA planes
         @param yuvaIndices     array indicating which texture in yuvaTextures, and channel
@@ -382,7 +375,7 @@
         @param context         GPU context
         @param yuvColorSpace   How the YUV values are converted to RGB. One of:
                                            kJPEG_SkYUVColorSpace, kRec601_SkYUVColorSpace,
-                                           kRec709_SkYUVColorSpace
+                                           kRec709_SkYUVColorSpace, kIdentity_SkYUVColorSpace
         @param yuvaTextures    array of (up to four) YUVA textures on GPU which contain the,
                                possibly interleaved, YUVA planes
         @param yuvaIndices     array indicating which texture in yuvaTextures, and channel
@@ -410,7 +403,7 @@
         @param context         GPU context
         @param yuvColorSpace   How the YUV values are converted to RGB. One of:
                                            kJPEG_SkYUVColorSpace, kRec601_SkYUVColorSpace,
-                                           kRec709_SkYUVColorSpace
+                                           kRec709_SkYUVColorSpace, kIdentity_SkYUVColorSpace
         @param yuvaTextures    array of (up to four) YUVA textures on GPU which contain the,
                                possibly interleaved, YUVA planes
         @param yuvaIndices     array indicating which texture in yuvaTextures, and channel
@@ -442,7 +435,7 @@
         @param context                GPU context
         @param yuvColorSpace          How the YUV values are converted to RGB. One of:
                                             kJPEG_SkYUVColorSpace, kRec601_SkYUVColorSpace,
-                                            kRec709_SkYUVColorSpace
+                                            kRec709_SkYUVColorSpace, kIdentity_SkYUVColorSpace
         @param yuvaPixmaps            array of (up to four) SkPixmap which contain the,
                                       possibly interleaved, YUVA planes
         @param yuvaIndices            array indicating which pixmap in yuvaPixmaps, and channel
@@ -484,7 +477,7 @@
 
         @param context         GPU context
         @param yuvColorSpace   one of: kJPEG_SkYUVColorSpace, kRec601_SkYUVColorSpace,
-                               kRec709_SkYUVColorSpace
+                               kRec709_SkYUVColorSpace, kIdentity_SkYUVColorSpace
         @param nv12Textures    array of YUV textures on GPU
         @param imageOrigin     one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin
         @param imageColorSpace range of colors; may be nullptr
@@ -505,7 +498,7 @@
 
         @param context         GPU context
         @param yuvColorSpace   one of: kJPEG_SkYUVColorSpace, kRec601_SkYUVColorSpace,
-                               kRec709_SkYUVColorSpace
+                               kRec709_SkYUVColorSpace, kIdentity_SkYUVColorSpace
         @param nv12Textures    array of YUV textures on GPU
         @param imageOrigin     one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin
         @param backendTexture  the resource that stores the final pixels
@@ -564,31 +557,54 @@
             SkAlphaType alphaType = kPremul_SkAlphaType,
             sk_sp<SkColorSpace> colorSpace = nullptr,
             GrSurfaceOrigin surfaceOrigin = kTopLeft_GrSurfaceOrigin);
+
+    /** Creates SkImage from Android hardware buffer and uploads the data from the SkPixmap to it.
+        Returned SkImage takes a reference on the buffer.
+
+        Only available on Android, when __ANDROID_API__ is defined to be 26 or greater.
+
+        @param pixmap          SkPixmap that contains data to be uploaded to the AHardwareBuffer
+        @param hardwareBuffer  AHardwareBuffer Android hardware buffer
+        @param surfaceOrigin   one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin
+        @return                created SkImage, or nullptr
+    */
+    static sk_sp<SkImage> MakeFromAHardwareBufferWithData(
+            GrContext* context,
+            const SkPixmap& pixmap,
+            AHardwareBuffer* hardwareBuffer,
+            GrSurfaceOrigin surfaceOrigin = kTopLeft_GrSurfaceOrigin);
 #endif
 
+    /** Returns a SkImageInfo describing the width, height, color type, alpha type, and color space
+        of the SkImage.
+
+        @return  image info of SkImage.
+    */
+    const SkImageInfo& imageInfo() const { return fInfo; }
+
     /** Returns pixel count in each row.
 
         @return  pixel width in SkImage
     */
-    int width() const { return fWidth; }
+    int width() const { return fInfo.width(); }
 
     /** Returns pixel row count.
 
         @return  pixel height in SkImage
     */
-    int height() const { return fHeight; }
+    int height() const { return fInfo.height(); }
 
     /** Returns SkISize { width(), height() }.
 
         @return  integral size of width() and height()
     */
-    SkISize dimensions() const { return SkISize::Make(fWidth, fHeight); }
+    SkISize dimensions() const { return SkISize::Make(fInfo.width(), fInfo.height()); }
 
     /** Returns SkIRect { 0, 0, width(), height() }.
 
         @return  integral rectangle from origin to width() and height()
     */
-    SkIRect bounds() const { return SkIRect::MakeWH(fWidth, fHeight); }
+    SkIRect bounds() const { return SkIRect::MakeWH(fInfo.width(), fInfo.height()); }
 
     /** Returns value unique to image. SkImage contents cannot change after SkImage is
         created. Any operation to create a new SkImage will receive generate a new
@@ -655,18 +671,23 @@
     bool isOpaque() const { return SkAlphaTypeIsOpaque(this->alphaType()); }
 
     /** Creates SkShader from SkImage. SkShader dimensions are taken from SkImage. SkShader uses
-        SkShader::TileMode rules to fill drawn area outside SkImage. localMatrix permits
+        SkTileMode rules to fill drawn area outside SkImage. localMatrix permits
         transforming SkImage before SkCanvas matrix is applied.
 
-        @param tileMode1    tiling on x-axis, one of: SkShader::kClamp_TileMode,
-                            SkShader::kRepeat_TileMode, SkShader::kMirror_TileMode
-        @param tileMode2    tiling on y-axis, one of: SkShader::kClamp_TileMode,
-                            SkShader::kRepeat_TileMode, SkShader::kMirror_TileMode
+        @param tmx          tiling in the x direction
+        @param tmy          tiling in the y direction
         @param localMatrix  SkImage transformation, or nullptr
         @return             SkShader containing SkImage
     */
-    sk_sp<SkShader> makeShader(SkShader::TileMode tileMode1, SkShader::TileMode tileMode2,
+    sk_sp<SkShader> makeShader(SkTileMode tmx, SkTileMode tmy,
                                const SkMatrix* localMatrix = nullptr) const;
+#ifdef SK_SUPPORT_LEGACY_TILEMODE_ENUM
+    sk_sp<SkShader> makeShader(SkShader::TileMode tmx, SkShader::TileMode tmy,
+                               const SkMatrix* localMatrix = nullptr) const {
+        return this->makeShader(static_cast<SkTileMode>(tmx), static_cast<SkTileMode>(tmy),
+                                localMatrix);
+    }
+#endif
 
     /** Creates SkShader from SkImage. SkShader dimensions are taken from SkImage. SkShader uses
         SkShader::kClamp_TileMode to fill drawn area outside SkImage. localMatrix permits
@@ -676,7 +697,7 @@
         @return             SkShader containing SkImage
     */
     sk_sp<SkShader> makeShader(const SkMatrix* localMatrix = nullptr) const {
-        return this->makeShader(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, localMatrix);
+        return this->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, localMatrix);
     }
 
     /** Copies SkImage pixel address, row bytes, and SkImageInfo to pixmap, if address
@@ -1053,11 +1074,10 @@
                                               sk_sp<SkColorSpace> targetColorSpace) const;
 
 private:
-    SkImage(int width, int height, uint32_t uniqueID);
+    SkImage(const SkImageInfo& info, uint32_t uniqueID);
     friend class SkImage_Base;
 
-    const int       fWidth;
-    const int       fHeight;
+    SkImageInfo     fInfo;
     const uint32_t  fUniqueID;
 
     typedef SkRefCnt INHERITED;
diff --git a/include/core/SkImageEncoder.h b/include/core/SkImageEncoder.h
index a6d7cb8..e44b3eb 100644
--- a/include/core/SkImageEncoder.h
+++ b/include/core/SkImageEncoder.h
@@ -27,10 +27,11 @@
  * Will always return false if Skia is compiled without image
  * encoders.
  *
- * Note that webp encodes will use webp lossy compression.
+ * For SkEncodedImageFormat::kWEBP, if quality is 100, it will use lossless compression. Otherwise
+ * it will use lossy.
  *
  * For examples of encoding an image to a file or to a block of memory,
- * see tools/sk_tool_utils.h.
+ * see tools/ToolUtils.h.
  */
 SK_API bool SkEncodeImage(SkWStream* dst, const SkPixmap& src,
                           SkEncodedImageFormat format, int quality);
@@ -56,7 +57,8 @@
  * Will always return nullptr if Skia is compiled without image
  * encoders.
  *
- * Note that webp encodes will use webp lossy compression.
+ * For SkEncodedImageFormat::kWEBP, if quality is 100, it will use lossless compression. Otherwise
+ * it will use lossy.
  */
 SK_API sk_sp<SkData> SkEncodePixmap(const SkPixmap& src, SkEncodedImageFormat format, int quality);
 
diff --git a/include/core/SkImageFilter.h b/include/core/SkImageFilter.h
index 428455b..b63d9b2 100644
--- a/include/core/SkImageFilter.h
+++ b/include/core/SkImageFilter.h
@@ -18,11 +18,10 @@
 #include "SkMatrix.h"
 #include "SkRect.h"
 
-class GrContext;
 class GrFragmentProcessor;
 class SkColorFilter;
-class SkColorSpaceXformer;
 struct SkIPoint;
+class GrRecordingContext;
 class SkSpecialImage;
 class SkImageFilterCache;
 struct SkImageFilterCacheKey;
@@ -167,7 +166,7 @@
                          MapDirection, const SkIRect* inputRect = nullptr) const;
 
 #if SK_SUPPORT_GPU
-    static sk_sp<SkSpecialImage> DrawWithFP(GrContext* context,
+    static sk_sp<SkSpecialImage> DrawWithFP(GrRecordingContext* context,
                                             std::unique_ptr<GrFragmentProcessor> fp,
                                             const SkIRect& bounds,
                                             const OutputProperties& outputProperties);
@@ -433,14 +432,6 @@
     static sk_sp<SkSpecialImage> ImageToColorSpace(SkSpecialImage* src, const OutputProperties&);
 #endif
 
-    /**
-     *  Returns an image filter transformed into a new color space via the |xformer|.
-     */
-    sk_sp<SkImageFilter> makeColorSpace(SkColorSpaceXformer* xformer) const {
-        return this->onMakeColorSpace(xformer);
-    }
-    virtual sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const = 0;
-
     sk_sp<SkImageFilter> refMe() const {
         return sk_ref_sp(const_cast<SkImageFilter*>(this));
     }
@@ -455,9 +446,6 @@
                                              const SkIRect& originalSrcBounds);
 
 private:
-    // For makeColorSpace().
-    friend class SkColorSpaceXformer;
-
     friend class SkGraphics;
 
     static void PurgeCache();
diff --git a/include/core/SkImageGenerator.h b/include/core/SkImageGenerator.h
index 746a117..d66bfa7 100644
--- a/include/core/SkImageGenerator.h
+++ b/include/core/SkImageGenerator.h
@@ -15,8 +15,7 @@
 #include "SkYUVAIndex.h"
 #include "SkYUVASizeInfo.h"
 
-class GrContext;
-class GrContextThreadSafeProxy;
+class GrRecordingContext;
 class GrTextureProxy;
 class GrSamplerState;
 class SkBitmap;
@@ -140,7 +139,7 @@
      *  the generator is allowed to return a non mipped proxy, but this will have some additional
      *  overhead in later allocating mips and copying of the base layer.
      */
-    sk_sp<GrTextureProxy> generateTexture(GrContext*, const SkImageInfo& info,
+    sk_sp<GrTextureProxy> generateTexture(GrRecordingContext*, const SkImageInfo& info,
                                           const SkIPoint& origin,
                                           bool willNeedMipMaps);
 #endif
@@ -183,7 +182,8 @@
     };
 
     virtual TexGenType onCanGenerateTexture() const { return TexGenType::kNone; }
-    virtual sk_sp<GrTextureProxy> onGenerateTexture(GrContext*, const SkImageInfo&, const SkIPoint&,
+    virtual sk_sp<GrTextureProxy> onGenerateTexture(GrRecordingContext*, const SkImageInfo&,
+                                                    const SkIPoint&,
                                                     bool willNeedMipMaps);  // returns nullptr
 #endif
 
diff --git a/include/core/SkImageInfo.h b/include/core/SkImageInfo.h
index 450bcd6..f1cbf7a 100644
--- a/include/core/SkImageInfo.h
+++ b/include/core/SkImageInfo.h
@@ -5,16 +5,6 @@
  * found in the LICENSE file.
  */
 
-/* Generated by tools/bookmaker from include/core/SkImageInfo.h and docs/SkImageInfo_Reference.bmh
-   on 2018-07-13 08:15:11. Additional documentation and examples can be found at:
-   https://skia.org/user/api/SkImageInfo_Reference
-
-   You may edit either file directly. Structural changes to public interfaces require
-   editing both files. After editing docs/SkImageInfo_Reference.bmh, run:
-       bookmaker -b docs -i include/core/SkImageInfo.h -p
-   to create an updated version of this file.
- */
-
 #ifndef SkImageInfo_DEFINED
 #define SkImageInfo_DEFINED
 
@@ -92,7 +82,8 @@
     kRGBA_1010102_SkColorType, //!< 10 bits for red, green, blue; 2 bits for alpha; in 32-bit word
     kRGB_101010x_SkColorType,  //!< pixel with 10 bits each for red, green, blue; in 32-bit word
     kGray_8_SkColorType,       //!< pixel with grayscale level in 8-bit byte
-    kRGBA_F16_SkColorType,   //!< pixel with half floats for red, green, blue, alpha; in 64-bit word
+    kRGBA_F16Norm_SkColorType, //!< pixel with half floats in [0,1] for red, green, blue, alpha; in 64-bit word
+    kRGBA_F16_SkColorType,     //!< pixel with half floats for red, green, blue, alpha; in 64-bit word
     kRGBA_F32_SkColorType,     //!< pixel using C float for red, green, blue, alpha; in 128-bit word
     kLastEnum_SkColorType     = kRGBA_F32_SkColorType,//!< last valid value
 
@@ -171,12 +162,17 @@
     JPEG YUV values encode the full range of 0 to 255 for all three components.
     Video YUV values range from 16 to 235 for all three components. Details of
     encoding and conversion to RGB are described in YCbCr color space.
+
+    The identity colorspace exists to provide a utility mapping from Y to R, U to G and V to B.
+    It can be used to visualize the YUV planes or to explicitly post process the YUV channels.
 */
 enum SkYUVColorSpace {
     kJPEG_SkYUVColorSpace,                               //!< describes full range
     kRec601_SkYUVColorSpace,                             //!< describes SDTV range
     kRec709_SkYUVColorSpace,                             //!< describes HDTV range
-    kLastEnum_SkYUVColorSpace = kRec709_SkYUVColorSpace, //!< last valid value
+    kIdentity_SkYUVColorSpace,                           //!< maps Y->R, U->G, V->B
+
+    kLastEnum_SkYUVColorSpace = kIdentity_SkYUVColorSpace, //!< last valid value
 };
 
 /** \struct SkImageInfo
diff --git a/include/core/SkMallocPixelRef.h b/include/core/SkMallocPixelRef.h
index c85c924..5971a63 100644
--- a/include/core/SkMallocPixelRef.h
+++ b/include/core/SkMallocPixelRef.h
@@ -20,10 +20,9 @@
 class SK_API SkMallocPixelRef : public SkPixelRef {
 public:
     /**
-     *  Return a new SkMallocPixelRef with the provided pixel storage, rowBytes,
-     *  and optional colortable. The caller is responsible for managing the
-     *  lifetime of the pixel storage buffer, as this pixelref will not try
-     *  to delete it.
+     *  Return a new SkMallocPixelRef with the provided pixel storage and
+     *  rowBytes.  The caller is responsible for managing the lifetime of the
+     *  pixel storage buffer, as this pixelref will not try to delete it.
      *
      *  Returns NULL on failure.
      */
@@ -35,7 +34,7 @@
      *  If rowBytes is > 0, then it will be respected, or NULL will be returned
      *  if rowBytes is invalid for the specified info.
      *
-     *  This pixelref will ref() the specified colortable (if not NULL).
+     *  All pixel bytes are left uninitialized.
      *
      *  Returns NULL on failure.
      */
@@ -47,9 +46,8 @@
     static sk_sp<SkPixelRef> MakeZeroed(const SkImageInfo&, size_t rowBytes);
 
     /**
-     *  Return a new SkMallocPixelRef with the provided pixel storage,
-     *  rowBytes, and optional colortable. On destruction, ReleaseProc
-     *  will be called.
+     *  Return a new SkMallocPixelRef with the provided pixel storage and
+     *  rowBytes. On destruction, ReleaseProc will be called.
      *
      *  If ReleaseProc is NULL, the pixels will never be released. This
      *  can be useful if the pixels were stack allocated. However, such an
@@ -63,10 +61,9 @@
                                           ReleaseProc proc, void* context);
 
     /**
-     *  Return a new SkMallocPixelRef that will use the provided
-     *  SkData, rowBytes, and optional colortable as pixel storage.
-     *  The SkData will be ref()ed and on destruction of the PielRef,
-     *  the SkData will be unref()ed.
+     *  Return a new SkMallocPixelRef that will use the provided SkData and
+     *  rowBytes as pixel storage.  The SkData will be ref()ed and on
+     *  destruction of the PixelRef, the SkData will be unref()ed.
      *
      *  Returns NULL on failure.
      */
diff --git a/include/core/SkMatrix.h b/include/core/SkMatrix.h
index 1816b3b..4f723b4 100644
--- a/include/core/SkMatrix.h
+++ b/include/core/SkMatrix.h
@@ -5,16 +5,6 @@
  * found in the LICENSE file.
  */
 
-/* Generated by tools/bookmaker from include/core/SkMatrix.h and docs/SkMatrix_Reference.bmh
-   on 2018-09-13 13:59:55. Additional documentation and examples can be found at:
-   https://skia.org/user/api/SkMatrix_Reference
-
-   You may edit either file directly. Structural changes to public interfaces require
-   editing both files. After editing docs/SkMatrix_Reference.bmh, run:
-       bookmaker -b docs -i include/core/SkMatrix.h -p
-   to create an updated version of this file.
- */
-
 #ifndef SkMatrix_DEFINED
 #define SkMatrix_DEFINED
 
@@ -1225,12 +1215,7 @@
         @param src    SkPoint to transform
         @param count  number of SkPoint to transform
     */
-    void mapPoints(SkPoint dst[], const SkPoint src[], int count) const {
-        SkASSERT((dst && src && count > 0) || 0 == count);
-        // no partial overlap
-        SkASSERT(src == dst || &dst[count] <= &src[0] || &src[count] <= &dst[0]);
-        this->getMapPtsProc()(*this, dst, src, count);
-    }
+    void mapPoints(SkPoint dst[], const SkPoint src[], int count) const;
 
     /** Maps pts SkPoint array of length count in place. SkPoint are mapped by multiplying
         each SkPoint by SkMatrix. Given:
@@ -1294,10 +1279,7 @@
         @param y       y-axis value of SkPoint to map
         @param result  storage for mapped SkPoint
     */
-    void mapXY(SkScalar x, SkScalar y, SkPoint* result) const {
-        SkASSERT(result);
-        this->getMapXYProc()(*this, x, y, result);
-    }
+    void mapXY(SkScalar x, SkScalar y, SkPoint* result) const;
 
     /** Returns SkPoint (x, y) multiplied by SkMatrix. Given:
 
@@ -1317,7 +1299,7 @@
     */
     SkPoint mapXY(SkScalar x, SkScalar y) const {
         SkPoint result;
-        this->getMapXYProc()(*this, x, y, &result);
+        this->mapXY(x,y, &result);
         return result;
     }
 
diff --git a/include/core/SkMatrix44.h b/include/core/SkMatrix44.h
index 9fc7c90..278b49d 100644
--- a/include/core/SkMatrix44.h
+++ b/include/core/SkMatrix44.h
@@ -133,7 +133,6 @@
 
     The SkMatrix44 class holds a 4x4 matrix.
 
-    SkMatrix44 is not thread safe unless you've first called SkMatrix44::getType().
 */
 class SK_API SkMatrix44 {
 public:
@@ -157,23 +156,14 @@
 
     constexpr SkMatrix44() : SkMatrix44{kIdentity_Constructor} {}
 
-    SkMatrix44(const SkMatrix44& src) {
-        memcpy(fMat, src.fMat, sizeof(fMat));
-        fTypeMask.store(src.fTypeMask, std::memory_order_relaxed);
-    }
+    SkMatrix44(const SkMatrix44& src) = default;
+
+    SkMatrix44& operator=(const SkMatrix44& src) = default;
 
     SkMatrix44(const SkMatrix44& a, const SkMatrix44& b) {
         this->setConcat(a, b);
     }
 
-    SkMatrix44& operator=(const SkMatrix44& src) {
-        if (&src != this) {
-            memcpy(fMat, src.fMat, sizeof(fMat));
-            fTypeMask.store(src.fTypeMask, std::memory_order_relaxed);
-        }
-        return *this;
-    }
-
     bool operator==(const SkMatrix44& other) const;
     bool operator!=(const SkMatrix44& other) const {
         return !(other == *this);
@@ -196,12 +186,13 @@
      */
     static const SkMatrix44& I();
 
-    enum TypeMask {
-        kIdentity_Mask      = 0,
-        kTranslate_Mask     = 0x01,  //!< set if the matrix has translation
-        kScale_Mask         = 0x02,  //!< set if the matrix has any scale != 1
-        kAffine_Mask        = 0x04,  //!< set if the matrix skews or rotates
-        kPerspective_Mask   = 0x08   //!< set if the matrix is in perspective
+    using TypeMask = uint8_t;
+    enum : TypeMask {
+        kIdentity_Mask = 0,
+        kTranslate_Mask = 1 << 0,    //!< set if the matrix has translation
+        kScale_Mask = 1 << 1,        //!< set if the matrix has any scale != 1
+        kAffine_Mask = 1 << 2,       //!< set if the matrix skews or rotates
+        kPerspective_Mask = 1 << 3,  //!< set if the matrix is in perspective
     };
 
     /**
@@ -211,13 +202,7 @@
      *  other bits may be set to true even in the case of a pure perspective
      *  transform.
      */
-    inline TypeMask getType() const {
-        if (fTypeMask.load(std::memory_order_relaxed) & kUnknown_Mask) {
-            fTypeMask.store(this->computeTypeMask(), std::memory_order_relaxed);
-        }
-        SkASSERT(!(fTypeMask & kUnknown_Mask));
-        return (TypeMask)fTypeMask.load(std::memory_order_relaxed);
-    }
+    inline TypeMask getType() const { return fTypeMask; }
 
     /**
      *  Return true if the matrix is identity.
@@ -276,7 +261,7 @@
         SkASSERT((unsigned)row <= 3);
         SkASSERT((unsigned)col <= 3);
         fMat[col][row] = value;
-        this->dirtyTypeMask();
+        this->recomputeTypeMask();
     }
 
     inline double getDouble(int row, int col) const {
@@ -337,6 +322,11 @@
                 SkMScalar m_02, SkMScalar m_12, SkMScalar m_22);
     void set3x3RowMajorf(const float[]);
 
+    void set4x4(SkMScalar m_00, SkMScalar m_10, SkMScalar m_20, SkMScalar m_30,
+                SkMScalar m_01, SkMScalar m_11, SkMScalar m_21, SkMScalar m_31,
+                SkMScalar m_02, SkMScalar m_12, SkMScalar m_22, SkMScalar m_32,
+                SkMScalar m_03, SkMScalar m_13, SkMScalar m_23, SkMScalar m_33);
+
     void setTranslate(SkMScalar dx, SkMScalar dy, SkMScalar dz);
     void preTranslate(SkMScalar dx, SkMScalar dy, SkMScalar dz);
     void postTranslate(SkMScalar dx, SkMScalar dy, SkMScalar dz);
@@ -446,10 +436,8 @@
 
 private:
     /* This is indexed by [col][row]. */
-    SkMScalar                       fMat[4][4];
-    mutable std::atomic<unsigned>   fTypeMask;
-
-    static constexpr int kUnknown_Mask = 0x80;
+    SkMScalar fMat[4][4];
+    TypeMask fTypeMask;
 
     static constexpr int kAllPublic_Masks = 0xF;
 
@@ -468,23 +456,11 @@
     SkMScalar perspY() const { return fMat[1][3]; }
     SkMScalar perspZ() const { return fMat[2][3]; }
 
-    int computeTypeMask() const;
+    void recomputeTypeMask();
 
-    inline void dirtyTypeMask() {
-        fTypeMask.store(kUnknown_Mask, std::memory_order_relaxed);
-    }
-
-    inline void setTypeMask(int mask) {
-        SkASSERT(0 == (~(kAllPublic_Masks | kUnknown_Mask) & mask));
-        fTypeMask.store(mask, std::memory_order_relaxed);
-    }
-
-    /**
-     *  Does not take the time to 'compute' the typemask. Only returns true if
-     *  we already know that this matrix is identity.
-     */
-    inline bool isTriviallyIdentity() const {
-        return 0 == fTypeMask.load(std::memory_order_relaxed);
+    inline void setTypeMask(TypeMask mask) {
+        SkASSERT(0 == (~kAllPublic_Masks & mask));
+        fTypeMask = mask;
     }
 
     inline const SkMScalar* values() const { return &fMat[0][0]; }
diff --git a/include/core/SkMilestone.h b/include/core/SkMilestone.h
index f60cc10..dee0c85 100644
--- a/include/core/SkMilestone.h
+++ b/include/core/SkMilestone.h
@@ -5,5 +5,5 @@
  * found in the LICENSE file.
  */
 #ifndef SK_MILESTONE
-#define SK_MILESTONE 74
+#define SK_MILESTONE 75
 #endif
diff --git a/include/core/SkOverdrawCanvas.h b/include/core/SkOverdrawCanvas.h
index e0d05c8..cbd6646 100644
--- a/include/core/SkOverdrawCanvas.h
+++ b/include/core/SkOverdrawCanvas.h
@@ -26,7 +26,6 @@
                      const SkPaint&) override;
     void onDrawPaint(const SkPaint&) override;
     void onDrawRect(const SkRect&, const SkPaint&) override;
-    void onDrawEdgeAARect(const SkRect&, SkCanvas::QuadAAFlags, SkColor, SkBlendMode) override;
     void onDrawRegion(const SkRegion&, const SkPaint&) override;
     void onDrawOval(const SkRect&, const SkPaint&) override;
     void onDrawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&) override;
@@ -43,7 +42,6 @@
                          SrcRectConstraint) override;
     void onDrawImageNine(const SkImage*, const SkIRect&, const SkRect&, const SkPaint*) override;
     void onDrawImageLattice(const SkImage*, const Lattice&, const SkRect&, const SkPaint*) override;
-    void onDrawImageSet(const ImageSetEntry[], int count, SkFilterQuality, SkBlendMode) override;
     void onDrawBitmap(const SkBitmap&, SkScalar, SkScalar, const SkPaint*) override;
     void onDrawBitmapRect(const SkBitmap&, const SkRect*, const SkRect&, const SkPaint*,
                           SrcRectConstraint) override;
@@ -56,6 +54,11 @@
     void onDrawAnnotation(const SkRect&, const char key[], SkData* value) override;
     void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override;
 
+    void onDrawEdgeAAQuad(const SkRect&, const SkPoint[4], SkCanvas::QuadAAFlags, SkColor,
+                          SkBlendMode) override;
+    void onDrawEdgeAAImageSet(const ImageSetEntry[], int count, const SkPoint[], const SkMatrix[],
+                              const SkPaint*, SrcRectConstraint) override;
+
 private:
     void drawPosTextCommon(const SkGlyphID[], int, const SkScalar[], int, const SkPoint&,
                            const SkFont&, const SkPaint&);
diff --git a/include/core/SkPaint.h b/include/core/SkPaint.h
index 3d6a938..36ad18f 100644
--- a/include/core/SkPaint.h
+++ b/include/core/SkPaint.h
@@ -5,16 +5,6 @@
  * found in the LICENSE file.
  */
 
-/* Generated by tools/bookmaker from include/core/SkPaint.h and docs/SkPaint_Reference.bmh
-   on 2018-08-28 10:32:58. Additional documentation and examples can be found at:
-   https://skia.org/user/api/SkPaint_Reference
-
-   You may edit either file directly. Structural changes to public interfaces require
-   editing both files. After editing docs/SkPaint_Reference.bmh, run:
-       bookmaker -b docs -i include/core/SkPaint.h -p
-   to create an updated version of this file.
- */
-
 #ifndef SkPaint_DEFINED
 #define SkPaint_DEFINED
 
@@ -32,7 +22,6 @@
 class SkMaskFilter;
 class SkPath;
 class SkPathEffect;
-struct SkPoint;
 class SkShader;
 
 /** \class SkPaint
@@ -260,17 +249,25 @@
 
         @return  alpha ranging from zero, fully transparent, to 255, fully opaque
     */
-    uint8_t getAlpha() const { return sk_float_round2int(fColor4f.fA * 255); }
+    float getAlphaf() const { return fColor4f.fA; }
+
+    // Helper that scales the alpha by 255.
+    uint8_t getAlpha() const { return sk_float_round2int(this->getAlphaf() * 255); }
 
     /** Replaces alpha, leaving RGB
         unchanged. An out of range value triggers an assert in the debug
-        build. a is a value from zero to 255.
-        a set to zero makes color fully transparent; a set to 255 makes color
+        build. a is a value from 0.0 to 1.0.
+        a set to zero makes color fully transparent; a set to 1.0 makes color
         fully opaque.
 
         @param a  alpha component of color
     */
-    void setAlpha(U8CPU a);
+    void setAlphaf(float a);
+
+    // Helper that accepts an int between 0 and 255, and divides it by 255.0
+    void setAlpha(U8CPU a) {
+        this->setAlphaf(a * (1.0f / 255));
+    }
 
     /** Sets color used when drawing solid fills. The color components range from 0 to 255.
         The color is unpremultiplied; alpha sets the transparency independent of RGB.
@@ -681,6 +678,7 @@
             unsigned    fStyle : 2;
             unsigned    fFilterQuality : 2;
             unsigned    fBlendMode : 8; // only need 5-6?
+            unsigned    fPadding : 14;  // 14==32-1-1-2-2-2-2-8
         } fBitfields;
         uint32_t fBitfieldsUInt;
     };
diff --git a/include/core/SkPath.h b/include/core/SkPath.h
index f19bae4..7c7207a 100644
--- a/include/core/SkPath.h
+++ b/include/core/SkPath.h
@@ -5,16 +5,6 @@
  * found in the LICENSE file.
  */
 
-/* Generated by tools/bookmaker from include/core/SkPath.h and docs/SkPath_Reference.bmh
-   on 2018-09-13 13:59:55. Additional documentation and examples can be found at:
-   https://skia.org/user/api/SkPath_Reference
-
-   You may edit either file directly. Structural changes to public interfaces require
-   editing both files. After editing docs/SkPath_Reference.bmh, run:
-       bookmaker -b docs -i include/core/SkPath.h -p
-   to create an updated version of this file.
- */
-
 #ifndef SkPath_DEFINED
 #define SkPath_DEFINED
 
@@ -1485,9 +1475,6 @@
         */
         bool isClosedContour() const;
 
-        // Returns the next verb, or kDone, without changing the state of the iterator
-        Verb peekVerb() const;
-
     private:
         const SkPoint*  fPts;
         const uint8_t*  fVerbs;
@@ -1688,7 +1675,6 @@
     mutable std::atomic<uint8_t>   fFirstDirection; // really an SkPathPriv::FirstDirection
     uint8_t                        fFillType    : 2;
     uint8_t                        fIsVolatile  : 1;
-    uint8_t                        fIsBadForDAA : 1;
 
     /** Resets all fields other than fPathRef to their initial 'empty' values.
      *  Assumes the caller has already emptied fPathRef.
diff --git a/include/core/SkPathMeasure.h b/include/core/SkPathMeasure.h
index 9d77da2..c486d31 100644
--- a/include/core/SkPathMeasure.h
+++ b/include/core/SkPathMeasure.h
@@ -16,22 +16,18 @@
 class SK_API SkPathMeasure : SkNoncopyable {
 public:
     SkPathMeasure();
-    /** Initialize the pathmeasure with the specified path. The path must remain valid
-        for the lifetime of the measure object, or until setPath() is called with
-        a different path (or null), since the measure object keeps a pointer to the
-        path object (does not copy its data).
-
-        resScale controls the precision of the measure. values > 1 increase the
-        precision (and possible slow down the computation).
-    */
+    /** Initialize the pathmeasure with the specified path. The parts of the path that are needed
+     *  are copied, so the client is free to modify/delete the path after this call.
+     *
+     *  resScale controls the precision of the measure. values > 1 increase the
+     *  precision (and possible slow down the computation).
+     */
     SkPathMeasure(const SkPath& path, bool forceClosed, SkScalar resScale = 1);
     ~SkPathMeasure();
 
-    /** Reset the pathmeasure with the specified path. The path must remain valid
-        for the lifetime of the measure object, or until setPath() is called with
-        a different path (or null), since the measure object keeps a pointer to the
-        path object (does not copy its data).
-    */
+    /** Reset the pathmeasure with the specified path. The parts of the path that are needed
+     *  are copied, so the client is free to modify/delete the path after this call..
+     */
     void setPath(const SkPath*, bool forceClosed);
 
     /** Return the total length of the current contour, or 0 if no path
diff --git a/include/core/SkPicture.h b/include/core/SkPicture.h
index 1e60cb6..49798a8 100644
--- a/include/core/SkPicture.h
+++ b/include/core/SkPicture.h
@@ -5,28 +5,21 @@
  * found in the LICENSE file.
  */
 
-/* Generated by tools/bookmaker from include/core/SkPicture.h and docs/SkPicture_Reference.bmh
-   on 2018-08-10 12:59:44. Additional documentation and examples can be found at:
-   https://skia.org/user/api/SkPicture_Reference
-
-   You may edit either file directly. Structural changes to public interfaces require
-   editing both files. After editing docs/SkPicture_Reference.bmh, run:
-       bookmaker -b docs -i include/core/SkPicture.h -p
-   to create an updated version of this file.
- */
-
 #ifndef SkPicture_DEFINED
 #define SkPicture_DEFINED
 
 #include "SkRefCnt.h"
 #include "SkRect.h"
+#include "SkTileMode.h"
 #include "SkTypes.h"
 
 class SkCanvas;
 class SkData;
 struct SkDeserialProcs;
 class SkImage;
+class SkMatrix;
 struct SkSerialProcs;
+class SkShader;
 class SkStream;
 class SkWStream;
 
@@ -203,6 +196,23 @@
     */
     virtual size_t approximateBytesUsed() const = 0;
 
+    /** Return a new shader that will draw with this picture.
+     *
+     *  @param tmx  The tiling mode to use when sampling in the x-direction.
+     *  @param tmy  The tiling mode to use when sampling in the y-direction.
+     *  @param localMatrix Optional matrix used when sampling
+     *  @param tile The tile rectangle in picture coordinates: this represents the subset
+     *              (or superset) of the picture used when building a tile. It is not
+     *              affected by localMatrix and does not imply scaling (only translation
+     *              and cropping). If null, the tile rect is considered equal to the picture
+     *              bounds.
+     *  @return     Returns a new shader object. Note: this function never returns null.
+     */
+    sk_sp<SkShader> makeShader(SkTileMode tmx, SkTileMode tmy,
+                               const SkMatrix* localMatrix, const SkRect* tileRect) const;
+    sk_sp<SkShader> makeShader(SkTileMode tmx, SkTileMode tmy,
+                               const SkMatrix* localMatrix = nullptr) const;
+
 private:
     // Subclass whitelist.
     SkPicture();
diff --git a/include/core/SkPixmap.h b/include/core/SkPixmap.h
index 5c15876..37a1b2d 100644
--- a/include/core/SkPixmap.h
+++ b/include/core/SkPixmap.h
@@ -5,16 +5,6 @@
  * found in the LICENSE file.
  */
 
-/* Generated by tools/bookmaker from include/core/SkPixmap.h and docs/SkPixmap_Reference.bmh
-   on 2018-06-08 11:48:28. Additional documentation and examples can be found at:
-   https://skia.org/user/api/SkPixmap_Reference
-
-   You may edit either file directly. Structural changes to public interfaces require
-   editing both files. After editing docs/SkPixmap_Reference.bmh, run:
-       bookmaker -b docs -i include/core/SkPixmap.h -p
-   to create an updated version of this file.
- */
-
 #ifndef SkPixmap_DEFINED
 #define SkPixmap_DEFINED
 
@@ -185,6 +175,16 @@
     */
     SkColorSpace* colorSpace() const { return fInfo.colorSpace(); }
 
+    /** Returns smart pointer to SkColorSpace, the range of colors, associated with
+        SkImageInfo. The smart pointer tracks the number of objects sharing this
+        SkColorSpace reference so the memory is released when the owners destruct.
+
+        The returned SkColorSpace is immutable.
+
+        @return  SkColorSpace in SkImageInfo wrapped in a smart pointer
+    */
+    sk_sp<SkColorSpace> refColorSpace() const { return fInfo.refColorSpace(); }
+
     /** Returns true if SkAlphaType is kOpaque_SkAlphaType.
         Does not check if SkColorType allows alpha, or if any pixel value has
         transparency.
@@ -348,7 +348,8 @@
     */
     const uint16_t* addrF16() const {
         SkASSERT(8 == fInfo.bytesPerPixel());
-        SkASSERT(kRGBA_F16_SkColorType == fInfo.colorType());
+        SkASSERT(kRGBA_F16_SkColorType     == fInfo.colorType() ||
+                 kRGBA_F16Norm_SkColorType == fInfo.colorType());
         return reinterpret_cast<const uint16_t*>(fPixels);
     }
 
@@ -440,7 +441,8 @@
         @return   readable unsigned 16-bit pointer to pixel component at (x, y)
     */
     const uint16_t* addrF16(int x, int y) const {
-        SkASSERT(kRGBA_F16_SkColorType == fInfo.colorType());
+        SkASSERT(kRGBA_F16_SkColorType     == fInfo.colorType() ||
+                 kRGBA_F16Norm_SkColorType == fInfo.colorType());
         return reinterpret_cast<const uint16_t*>(this->addr64(x, y));
     }
 
diff --git a/include/core/SkPoint.h b/include/core/SkPoint.h
index 1c4768f..93b0bb0 100644
--- a/include/core/SkPoint.h
+++ b/include/core/SkPoint.h
@@ -5,16 +5,6 @@
  * found in the LICENSE file.
  */
 
-/* Generated by tools/bookmaker from include/core/SkPoint.h and docs/SkPoint_Reference.bmh
-   on 2018-09-13 13:59:55. Additional documentation and examples can be found at:
-   https://skia.org/user/api/SkPoint_Reference
-
-   You may edit either file directly. Structural changes to public interfaces require
-   editing both files. After editing docs/SkPoint_Reference.bmh, run:
-       bookmaker -b docs -i include/core/SkPoint.h -p
-   to create an updated version of this file.
- */
-
 #ifndef SkPoint_DEFINED
 #define SkPoint_DEFINED
 
diff --git a/include/core/SkPreConfig.h b/include/core/SkPreConfig.h
index 1f83c14..923d965 100644
--- a/include/core/SkPreConfig.h
+++ b/include/core/SkPreConfig.h
@@ -44,7 +44,7 @@
 
 //////////////////////////////////////////////////////////////////////
 
-#ifdef SK_BUILD_FOR_WIN
+#if defined(SK_BUILD_FOR_WIN) && !defined(__clang__)
     #if !defined(SK_RESTRICT)
         #define SK_RESTRICT __restrict
     #endif
diff --git a/include/core/SkRRect.h b/include/core/SkRRect.h
index 32fbd12..f9fa009 100644
--- a/include/core/SkRRect.h
+++ b/include/core/SkRRect.h
@@ -5,16 +5,6 @@
  * found in the LICENSE file.
  */
 
-/* Generated by tools/bookmaker from include/core/SkRRect.h and docs/SkRRect_Reference.bmh
-   on 2018-08-10 12:59:44. Additional documentation and examples can be found at:
-   https://skia.org/user/api/SkRRect_Reference
-
-   You may edit either file directly. Structural changes to public interfaces require
-   editing both files. After editing docs/SkRRect_Reference.bmh, run:
-       bookmaker -b docs -i include/core/SkRRect.h -p
-   to create an updated version of this file.
- */
-
 #ifndef SkRRect_DEFINED
 #define SkRRect_DEFINED
 
@@ -464,7 +454,7 @@
 
     /** Transforms by SkRRect by matrix, storing result in dst.
         Returns true if SkRRect transformed can be represented by another SkRRect.
-        Returns false if matrix contains transformations other than scale and translate.
+        Returns false if matrix contains transformations that are not axis aligned.
 
         Asserts in debug builds if SkRRect equals dst.
 
diff --git a/include/core/SkRect.h b/include/core/SkRect.h
index 863a5fb8..ac84e39 100644
--- a/include/core/SkRect.h
+++ b/include/core/SkRect.h
@@ -5,16 +5,6 @@
  * found in the LICENSE file.
  */
 
-/* Generated by tools/bookmaker from include/core/SkRect.h and docs/SkRect_Reference.bmh
-   on 2018-09-13 13:59:55. Additional documentation and examples can be found at:
-   https://skia.org/user/api/SkRect_Reference
-
-   You may edit either file directly. Structural changes to public interfaces require
-   editing both files. After editing docs/SkRect_Reference.bmh, run:
-       bookmaker -b docs -i include/core/SkRect.h -p
-   to create an updated version of this file.
- */
-
 #ifndef SkRect_DEFINED
 #define SkRect_DEFINED
 
diff --git a/include/core/SkRegion.h b/include/core/SkRegion.h
index 07839f2..f9d2ac7 100644
--- a/include/core/SkRegion.h
+++ b/include/core/SkRegion.h
@@ -5,16 +5,6 @@
  * found in the LICENSE file.
  */
 
-/* Generated by tools/bookmaker from include/core/SkRegion.h and docs/SkRegion_Reference.bmh
-   on 2018-08-10 12:59:44. Additional documentation and examples can be found at:
-   https://skia.org/user/api/SkRegion_Reference
-
-   You may edit either file directly. Structural changes to public interfaces require
-   editing both files. After editing docs/SkRegion_Reference.bmh, run:
-       bookmaker -b docs -i include/core/SkRegion.h -p
-   to create an updated version of this file.
- */
-
 #ifndef SkRegion_DEFINED
 #define SkRegion_DEFINED
 
diff --git a/include/core/SkScalar.h b/include/core/SkScalar.h
index ba7cc7c..53f1ab0 100644
--- a/include/core/SkScalar.h
+++ b/include/core/SkScalar.h
@@ -118,8 +118,6 @@
     return SkTPin(x, min, max);
 }
 
-SkScalar SkScalarSinCos(SkScalar radians, SkScalar* cosValue);
-
 static inline SkScalar SkScalarSquare(SkScalar x) { return x * x; }
 
 #define SkScalarInvert(x)           sk_ieee_float_divide_TODO_IS_DIVIDE_BY_ZERO_SAFE_HERE(SK_Scalar1, (x))
@@ -165,6 +163,16 @@
     return SkScalarAbs(x-y) <= tolerance;
 }
 
+static inline float SkScalarSinSnapToZero(SkScalar radians) {
+    float v = SkScalarSin(radians);
+    return SkScalarNearlyZero(v) ? 0.0f : v;
+}
+
+static inline float SkScalarCosSnapToZero(SkScalar radians) {
+    float v = SkScalarCos(radians);
+    return SkScalarNearlyZero(v) ? 0.0f : v;
+}
+
 /** Linearly interpolate between A and B, based on t.
     If t is 0, return A
     If t is 1, return B
diff --git a/include/core/SkShader.h b/include/core/SkShader.h
index a8e75a1..3ad0ad3 100644
--- a/include/core/SkShader.h
+++ b/include/core/SkShader.h
@@ -14,12 +14,12 @@
 #include "SkFlattenable.h"
 #include "SkImageInfo.h"
 #include "SkMatrix.h"
+#include "SkTileMode.h"
 
 class SkArenaAlloc;
 class SkBitmap;
 class SkColorFilter;
 class SkColorSpace;
-class SkColorSpaceXformer;
 class SkImage;
 class SkPath;
 class SkPicture;
@@ -39,6 +39,7 @@
  */
 class SK_API SkShader : public SkFlattenable {
 public:
+#ifdef SK_SUPPORT_LEGACY_TILEMODE_ENUM
     enum TileMode {
         /**
          *  Replicate the edge color if the shader draws outside of its
@@ -66,14 +67,7 @@
     };
 
     static constexpr int kTileModeCount = kLast_TileMode + 1;
-
-    /**
-     *  Returns the local matrix.
-     *
-     *  FIXME: This can be incorrect for a Shader with its own local matrix
-     *  that is also wrapped via CreateLocalMatrixShader.
-     */
-    const SkMatrix& getLocalMatrix() const;
+#endif
 
     /**
      *  Returns true if the shader is guaranteed to produce only opaque
@@ -87,10 +81,15 @@
      *  Iff this shader is backed by a single SkImage, return its ptr (the caller must ref this
      *  if they want to keep it longer than the lifetime of the shader). If not, return nullptr.
      */
-    SkImage* isAImage(SkMatrix* localMatrix, TileMode xy[2]) const;
+    SkImage* isAImage(SkMatrix* localMatrix, SkTileMode xy[2]) const;
+#ifdef SK_SUPPORT_LEGACY_TILEMODE_ENUM
+    SkImage* isAImage(SkMatrix* localMatrix, TileMode xy[2]) const {
+        return this->isAImage(localMatrix, (SkTileMode*)xy);
+    }
+#endif
 
     bool isAImage() const {
-        return this->isAImage(nullptr, nullptr) != nullptr;
+        return this->isAImage(nullptr, (SkTileMode*)nullptr) != nullptr;
     }
 
     /**
@@ -141,21 +140,17 @@
         SkScalar*   fColorOffsets;  //!< The unit offset for color transitions.
         SkPoint     fPoint[2];      //!< Type specific, see above.
         SkScalar    fRadius[2];     //!< Type specific, see above.
+#ifdef SK_SUPPORT_LEGACY_TILEMODE_ENUM
         TileMode    fTileMode;      //!< The tile mode used.
+#else
+        SkTileMode  fTileMode;
+#endif
         uint32_t    fGradientFlags; //!< see SkGradientShader::Flags
     };
 
+    // DEPRECATED. skbug.com/8941
     virtual GradientType asAGradient(GradientInfo* info) const;
 
-#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
-    struct ComposeRec {
-        const SkShader*     fShaderA;
-        const SkShader*     fShaderB;
-        SkBlendMode         fBlendMode;
-    };
-    virtual bool asACompose(ComposeRec*) const { return false; }
-#endif
-
     //////////////////////////////////////////////////////////////////////////
     //  Methods to create combinations or variants of shaders
 
@@ -174,6 +169,7 @@
     //////////////////////////////////////////////////////////////////////////
     //  Factory methods for stock shaders
 
+#ifdef SK_SUPPORT_LEGACY_SHADER_FACTORIES
     /**
      *  Call this to create a new "empty" shader, that will not draw anything.
      */
@@ -193,40 +189,30 @@
      */
     static sk_sp<SkShader> MakeColorShader(const SkColor4f&, sk_sp<SkColorSpace>);
 
-    /**
-     *  Compose two shaders together, using two operators: mode and lerp. The resulting colors
-     *  are computed by first combining the src and dst shaders using mode, and then linearly
-     *  interpolating between the dst and result colors using lerp.
-     *
-     *      result = dst * (1 - lerp) + (src (mode) dst) * lerp
-     *
-     *  If either shader is nullptr, then this returns nullptr.
-     *  If lerp is NaN then this returns nullptr, otherwise lerp is clamped to [0..1].
-     */
-    static sk_sp<SkShader> MakeCompose(sk_sp<SkShader> dst, sk_sp<SkShader> src,
-                                       SkBlendMode mode, float lerp = 1);
+    static sk_sp<SkShader> MakeBlend(SkBlendMode mode, sk_sp<SkShader> dst, sk_sp<SkShader> src);
 
     /*
-     *  DEPRECATED: call MakeCompose.
+     *  DEPRECATED: call MakeBlend.
      */
     static sk_sp<SkShader> MakeComposeShader(sk_sp<SkShader> dst, sk_sp<SkShader> src,
                                              SkBlendMode mode) {
-        return MakeCompose(std::move(dst), std::move(src), mode, 1);
+        return MakeBlend(mode, std::move(dst), std::move(src));
     }
 
     /**
      *  Compose two shaders together using a weighted average.
      *
-     *  result = dst * (1 - lerp) + src * lerp
+     *  result = dst * (1 - weight) + src * weight
      *
      *  If either shader is nullptr, then this returns nullptr.
-     *  If lerp is NaN then this returns nullptr, otherwise lerp is clamped to [0..1].
+     *  If weight is NaN then this returns nullptr, otherwise lerp is clamped to [0..1].
      */
-    static sk_sp<SkShader> MakeMixer(sk_sp<SkShader> dst, sk_sp<SkShader> src, float lerp) {
-        return MakeCompose(std::move(dst), std::move(src), SkBlendMode::kSrc, lerp);
-    }
+    static sk_sp<SkShader> MakeLerp(float weight, sk_sp<SkShader> dst, sk_sp<SkShader> src);
+#endif
 
-    /** Call this to create a new shader that will draw with the specified bitmap.
+#ifdef SK_SUPPORT_LEGACY_BITMAPSHADER_FACTORY
+    /** DEPRECATED. call bitmap.makeShader()
+     *  Call this to create a new shader that will draw with the specified bitmap.
      *
      *  If the bitmap cannot be used (e.g. has no pixels, or its dimensions
      *  exceed implementation limits (currently at 64K - 1)) then SkEmptyShader
@@ -240,12 +226,15 @@
      *  @param tmy  The tiling mode to use when sampling the bitmap in the y-direction.
      *  @return     Returns a new shader object. Note: this function never returns null.
     */
+    static sk_sp<SkShader> MakeBitmapShader(const SkBitmap& src, SkTileMode tmx, SkTileMode tmy,
+                                            const SkMatrix* localMatrix = nullptr);
+#endif
+#ifdef SK_SUPPORT_LEGACY_TILEMODE_ENUM
     static sk_sp<SkShader> MakeBitmapShader(const SkBitmap& src, TileMode tmx, TileMode tmy,
                                             const SkMatrix* localMatrix = nullptr);
 
-    // NOTE: You can create an SkImage Shader with SkImage::newShader().
-
-    /** Call this to create a new shader that will draw with the specified picture.
+    /** DEPRECATED: call picture->makeShader(...)
+     *  Call this to create a new shader that will draw with the specified picture.
      *
      *  @param src  The picture to use inside the shader (if not NULL, its ref count
      *              is incremented). The SkPicture must not be changed after
@@ -261,13 +250,12 @@
     */
     static sk_sp<SkShader> MakePictureShader(sk_sp<SkPicture> src, TileMode tmx, TileMode tmy,
                                              const SkMatrix* localMatrix, const SkRect* tile);
+#endif
 
-    /**
-     *  If this shader can be represented by another shader + a localMatrix, return that shader and
-     *  the localMatrix. If not, return nullptr and ignore the localMatrix parameter.
-     */
-    // TODO: clean up clients, move to SkShaderBase.
-    virtual sk_sp<SkShader> makeAsALocalMatrixShader(SkMatrix* localMatrix) const;
+#ifdef SK_SUPPORT_LEGACY_SHADER_LOCALMATRIX
+    SkMatrix getLocalMatrix() const;
+    sk_sp<SkShader> makeAsALocalMatrixShader(SkMatrix* localMatrix) const;
+#endif
 
 private:
     SkShader() = default;
@@ -276,4 +264,18 @@
     typedef SkFlattenable INHERITED;
 };
 
+class SK_API SkShaders {
+public:
+    static sk_sp<SkShader> Empty();
+    static sk_sp<SkShader> Color(SkColor);
+    static sk_sp<SkShader> Color(const SkColor4f&, sk_sp<SkColorSpace>);
+    static sk_sp<SkShader> Blend(SkBlendMode mode, sk_sp<SkShader> dst, sk_sp<SkShader> src);
+    static sk_sp<SkShader> Lerp(float t, sk_sp<SkShader> dst, sk_sp<SkShader> src);
+
+    static sk_sp<SkShader> Lerp(sk_sp<SkShader> red, sk_sp<SkShader> dst, sk_sp<SkShader> src);
+
+private:
+    SkShaders() = delete;
+};
+
 #endif
diff --git a/include/core/SkString.h b/include/core/SkString.h
index 300d9ed..f0c74f0 100644
--- a/include/core/SkString.h
+++ b/include/core/SkString.h
@@ -260,9 +260,9 @@
     sk_sp<Rec> fRec;
 
 #ifdef SK_DEBUG
-    void validate() const;
+    const SkString& validate() const;
 #else
-    void validate() const {}
+    const SkString& validate() const { return *this; }
 #endif
 
     static const Rec gEmptyRec;
diff --git a/include/core/SkStrokeRec.h b/include/core/SkStrokeRec.h
index d5470b1..1a224cf 100644
--- a/include/core/SkStrokeRec.h
+++ b/include/core/SkStrokeRec.h
@@ -14,7 +14,7 @@
 class SkPath;
 
 SK_BEGIN_REQUIRE_DENSE
-class SkStrokeRec {
+class SK_API SkStrokeRec {
 public:
     enum InitStyle {
         kHairline_InitStyle,
diff --git a/include/core/SkSurface.h b/include/core/SkSurface.h
index 525cdad..48ec1f6 100644
--- a/include/core/SkSurface.h
+++ b/include/core/SkSurface.h
@@ -5,16 +5,6 @@
  * found in the LICENSE file.
  */
 
-/* Generated by tools/bookmaker from include/core/SkSurface.h and docs/SkSurface_Reference.bmh
-   on 2018-09-13 13:59:55. Additional documentation and examples can be found at:
-   https://skia.org/user/api/SkSurface_Reference
-
-   You may edit either file directly. Structural changes to public interfaces require
-   editing both files. After editing docs/SkSurface_Reference.bmh, run:
-       bookmaker -b docs -i include/core/SkSurface.h -p
-   to create an updated version of this file.
- */
-
 #ifndef SkSurface_DEFINED
 #define SkSurface_DEFINED
 
@@ -24,6 +14,10 @@
 
 #include "GrTypes.h"
 
+#if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26
+#include <android/hardware_buffer.h>
+#endif
+
 class SkCanvas;
 class SkDeferredDisplayList;
 class SkPaint;
@@ -32,6 +26,7 @@
 class GrBackendSemaphore;
 class GrBackendTexture;
 class GrContext;
+class GrRecordingContext;
 class GrRenderTarget;
 
 /** \class SkSurface
@@ -163,9 +158,7 @@
         @return              SkSurface if all parameters are valid; otherwise, nullptr
     */
     static sk_sp<SkSurface> MakeRasterN32Premul(int width, int height,
-                                                const SkSurfaceProps* surfaceProps = nullptr) {
-        return MakeRaster(SkImageInfo::MakeN32Premul(width, height), surfaceProps);
-    }
+                                                const SkSurfaceProps* surfaceProps = nullptr);
 
     /** Caller data passed to RenderTarget/TextureReleaseProc; may be nullptr. */
     typedef void* ReleaseContext;
@@ -241,7 +234,7 @@
         @param colorSpace               range of colors
         @param surfaceProps             LCD striping orientation and setting for device independent
                                         fonts; may be nullptr
-        @param renderTargetReleaseProc  function called when texture can be released
+        @param releaseProc              function called when texture can be released
         @param releaseContext           state passed to textureReleaseProc
         @return                         SkSurface if all parameters are valid; otherwise, nullptr
     */
@@ -251,7 +244,7 @@
                                                 SkColorType colorType,
                                                 sk_sp<SkColorSpace> colorSpace,
                                                 const SkSurfaceProps* surfaceProps,
-                                                TextureReleaseProc textureReleaseProc = nullptr,
+                                                RenderTargetReleaseProc releaseProc = nullptr,
                                                 ReleaseContext releaseContext = nullptr);
 
     /** Wraps a GPU-backed texture into SkSurface. Caller must ensure backendTexture is
@@ -292,6 +285,34 @@
                                                             sk_sp<SkColorSpace> colorSpace,
                                                             const SkSurfaceProps* surfaceProps);
 
+#if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26
+    /** Private.
+        Creates SkSurface from Android hardware buffer.
+        Returned SkSurface takes a reference on the buffer. The ref on the buffer will be released
+        when the SkSurface is destroyed and there is no pending work on the GPU involving the
+        buffer.
+
+        Only available on Android, when __ANDROID_API__ is defined to be 26 or greater.
+
+        Currently this is only supported for buffers that can be textured as well as rendered to.
+        In other workds that must have both AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT and
+        AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE usage bits.
+
+        @param context         GPU context
+        @param hardwareBuffer  AHardwareBuffer Android hardware buffer
+        @param origin          one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin
+        @param colorSpace      range of colors; may be nullptr
+        @param surfaceProps    LCD striping orientation and setting for device independent
+                               fonts; may be nullptr
+        @return                created SkSurface, or nullptr
+    */
+    static sk_sp<SkSurface> MakeFromAHardwareBuffer(GrContext* context,
+                                                    AHardwareBuffer* hardwareBuffer,
+                                                    GrSurfaceOrigin origin,
+                                                    sk_sp<SkColorSpace> colorSpace,
+                                                    const SkSurfaceProps* surfaceProps);
+#endif
+
     /** Returns SkSurface on GPU indicated by context. Allocates memory for
         pixels, based on the width, height, and SkColorType in SkImageInfo.  budgeted
         selects whether allocation for pixels is tracked by context. imageInfo
@@ -386,7 +407,7 @@
         @param budgeted          one of: SkBudgeted::kNo, SkBudgeted::kYes
         @return                  SkSurface if all parameters are valid; otherwise, nullptr
     */
-    static sk_sp<SkSurface> MakeRenderTarget(GrContext* context,
+    static sk_sp<SkSurface> MakeRenderTarget(GrRecordingContext* context,
                                              const SkSurfaceCharacterization& characterization,
                                              SkBudgeted budgeted);
 
@@ -687,9 +708,24 @@
     */
     void flush();
 
+    enum class BackendSurfaceAccess {
+        kNoAccess,  //!< back-end object will not be used by client
+        kPresent,   //!< back-end surface will be used for presenting to screen
+    };
+
     /** Issues pending SkSurface commands to the GPU-backed API and resolves any SkSurface MSAA.
-        After issuing all commands, signalSemaphores of count numSemaphores semaphores
-        are signaled by the GPU.
+        After issuing all commands, signalSemaphores of count numSemaphores are signaled by the GPU.
+        The work that is submitted to the GPU will be dependent on the BackendSurfaceAccess that is
+        passed in.
+
+        If BackendSurfaceAccess::kNoAccess is passed in all commands will be issued to the GPU.
+
+        If BackendSurfaceAccess::kPresent is passed in and the backend API is not Vulkan, it is
+        treated the same as kNoAccess. If the backend API is Vulkan, the VkImage that backs the
+        SkSurface will be transferred back to its original queue. If the SkSurface was created by
+        wrapping a VkImage, the queue will be set to the queue which was originally passed in on
+        the GrVkImageInfo. Additionally, if the original queue was not external or foreign the
+        layout of the VkImage will be set to VK_IMAGE_LAYOUT_PRESENT_SRC_KHR.
 
         For each GrBackendSemaphore in signalSemaphores:
         if GrBackendSemaphore is initialized, the GPU back-end uses the semaphore as is;
@@ -710,10 +746,28 @@
 
         Pending surface commands are flushed regardless of the return result.
 
+        @param access            type of access the call will do on the backend object after flush
+        @param flags             flush options
         @param numSemaphores     size of signalSemaphores array
         @param signalSemaphores  array of semaphore containers
         @return                  one of: GrSemaphoresSubmitted::kYes, GrSemaphoresSubmitted::kNo
     */
+    GrSemaphoresSubmitted flush(BackendSurfaceAccess access, GrFlushFlags flags,
+                                int numSemaphores, GrBackendSemaphore signalSemaphores[]);
+
+    /** The below enum and flush call are deprected
+     */
+
+    enum FlushFlags {
+        kNone_FlushFlags = 0,
+        // flush will wait till all submitted GPU work is finished before returning.
+        kSyncCpu_FlushFlag = 0x1,
+    };
+    GrSemaphoresSubmitted flush(BackendSurfaceAccess access, FlushFlags flags,
+                                int numSemaphores, GrBackendSemaphore signalSemaphores[]);
+
+    /** Deprecated.
+    */
     GrSemaphoresSubmitted flushAndSignalSemaphores(int numSemaphores,
                                                    GrBackendSemaphore signalSemaphores[]);
 
diff --git a/include/core/SkSurfaceCharacterization.h b/include/core/SkSurfaceCharacterization.h
index 5e6066c..4d0aa2b 100644
--- a/include/core/SkSurfaceCharacterization.h
+++ b/include/core/SkSurfaceCharacterization.h
@@ -86,6 +86,7 @@
 
 private:
     friend class SkSurface_Gpu; // for 'set' & 'config'
+    friend class GrVkSecondaryCBDrawContext; // for 'set' & 'config'
     friend class GrContextThreadSafeProxy; // for private ctor
     friend class SkDeferredDisplayListRecorder; // for 'config'
     friend class SkSurface; // for 'config'
diff --git a/include/core/SkTLazy.h b/include/core/SkTLazy.h
index 29a8b49..cdb5a61 100644
--- a/include/core/SkTLazy.h
+++ b/include/core/SkTLazy.h
@@ -8,9 +8,9 @@
 #ifndef SkTLazy_DEFINED
 #define SkTLazy_DEFINED
 
-#include "../private/SkTemplates.h"
 #include "SkTypes.h"
 #include <new>
+#include <type_traits>
 #include <utility>
 
 /**
@@ -21,8 +21,8 @@
 public:
     SkTLazy() = default;
     explicit SkTLazy(const T* src) : fPtr(src ? new (&fStorage) T(*src) : nullptr) {}
-    SkTLazy(const SkTLazy& that) { *this = that; }
-    SkTLazy(SkTLazy&& that) { *this = std::move(that); }
+    SkTLazy(const SkTLazy& that) : fPtr(that.fPtr ? new (&fStorage) T(*that.fPtr) : nullptr) {}
+    SkTLazy(SkTLazy&& that) : fPtr(that.fPtr ? new (&fStorage) T(std::move(*that.fPtr)) : nullptr){}
 
     ~SkTLazy() { this->reset(); }
 
diff --git a/include/core/SkTextBlob.h b/include/core/SkTextBlob.h
index 859b446..2962986 100644
--- a/include/core/SkTextBlob.h
+++ b/include/core/SkTextBlob.h
@@ -5,16 +5,6 @@
  * found in the LICENSE file.
  */
 
-/* Generated by tools/bookmaker from include/core/SkTextBlob.h and docs/SkTextBlob_Reference.bmh
-   on 2018-08-10 12:59:44. Additional documentation and examples can be found at:
-   https://skia.org/user/api/SkTextBlob_Reference
-
-   You may edit either file directly. Structural changes to public interfaces require
-   editing both files. After editing docs/SkTextBlob_Reference.bmh, run:
-       bookmaker -b docs -i include/core/SkTextBlob.h -p
-   to create an updated version of this file.
- */
-
 #ifndef SkTextBlob_DEFINED
 #define SkTextBlob_DEFINED
 
diff --git a/include/core/SkTileMode.h b/include/core/SkTileMode.h
new file mode 100644
index 0000000..8761b4c
--- /dev/null
+++ b/include/core/SkTileMode.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkTileModes_DEFINED
+#define SkTileModes_DEFINED
+
+#include "SkTypes.h"
+
+enum class SkTileMode {
+    /**
+     *  Replicate the edge color if the shader draws outside of its
+     *  original bounds.
+     */
+    kClamp,
+
+    /**
+     *  Repeat the shader's image horizontally and vertically.
+     */
+    kRepeat,
+
+    /**
+     *  Repeat the shader's image horizontally and vertically, alternating
+     *  mirror images so that adjacent images always seam.
+     */
+    kMirror,
+
+    /**
+     *  Only draw within the original domain, return transparent-black everywhere else.
+     */
+    kDecal,
+
+    kLastTileMode = kDecal,
+};
+
+static constexpr int kSkTileModeCount = static_cast<int>(SkTileMode::kLastTileMode) + 1;
+
+#endif
diff --git a/include/core/SkTypeface.h b/include/core/SkTypeface.h
index ac6914d..7f5af8c 100644
--- a/include/core/SkTypeface.h
+++ b/include/core/SkTypeface.h
@@ -314,7 +314,7 @@
      *  collection.
      *  The caller is responsible for deleting the stream.
      */
-    SkStreamAsset* openStream(int* ttcIndex) const;
+    std::unique_ptr<SkStreamAsset> openStream(int* ttcIndex) const;
 
     /**
      *  Return the font data, or nullptr on failure.
@@ -380,7 +380,7 @@
     // dstArray is non-null, and points to an array of size this->countGlyphs().
     virtual void getGlyphToUnicodeMap(SkUnichar* dstArray) const;
 
-    virtual SkStreamAsset* onOpenStream(int* ttcIndex) const = 0;
+    virtual std::unique_ptr<SkStreamAsset> onOpenStream(int* ttcIndex) const = 0;
     // TODO: make pure virtual.
     virtual std::unique_ptr<SkFontData> onMakeFontData() const;
 
diff --git a/include/core/SkTypes.h b/include/core/SkTypes.h
index 697afc8..32da7dd 100644
--- a/include/core/SkTypes.h
+++ b/include/core/SkTypes.h
@@ -19,13 +19,6 @@
 /** \file SkTypes.h
 */
 
-/** See SkGraphics::GetVersion() to retrieve these at runtime
-*/
-#define SKIA_VERSION_MAJOR  1
-#define SKIA_VERSION_MINOR  0
-#define SKIA_VERSION_PATCH  0
-
-
 /** Called internally if we hit an unrecoverable error.
     The platform implementation must not return, but should either throw
     an exception or otherwise exit.
diff --git a/include/core/SkYUVAIndex.h b/include/core/SkYUVAIndex.h
index f52786c..b9bc396 100644
--- a/include/core/SkYUVAIndex.h
+++ b/include/core/SkYUVAIndex.h
@@ -49,7 +49,7 @@
     };
     static constexpr int kIndexCount = kLast_Index + 1;
 
-    /** The index is a number between -1..3 which definies which image source to read from, where -1
+    /** The index is a number between -1..3 which defines which image source to read from, where -1
      * means the image source doesn't exist. The assumption is we will always have image sources for
      * each of YUV planes, but optionally have image source for A plane. */
     int            fIndex;
diff --git a/include/docs/SkPDFDocument.h b/include/docs/SkPDFDocument.h
index 3d2dd29..07a7310 100644
--- a/include/docs/SkPDFDocument.h
+++ b/include/docs/SkPDFDocument.h
@@ -159,6 +159,14 @@
         Experimental.
     */
     SkExecutor* fExecutor = nullptr;
+
+    /** Preferred Subsetter. Only respected if both are compiled in.
+        Experimental.
+    */
+    enum Subsetter {
+        kHarfbuzz_Subsetter,
+        kSfntly_Subsetter,
+    } fSubsetter = kHarfbuzz_Subsetter;
 };
 
 /** Associate a node ID with subsequent drawing commands in an
diff --git a/include/effects/SkColorFilterImageFilter.h b/include/effects/SkColorFilterImageFilter.h
index 99be9ed..1237e00 100644
--- a/include/effects/SkColorFilterImageFilter.h
+++ b/include/effects/SkColorFilterImageFilter.h
@@ -21,7 +21,6 @@
     void flatten(SkWriteBuffer&) const override;
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
                                         SkIPoint* offset) const override;
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
     bool onIsColorFilterNode(SkColorFilter**) const override;
     bool onCanHandleComplexCTM() const override { return true; }
     bool affectsTransparentBlack() const override;
diff --git a/include/effects/SkComposeImageFilter.h b/include/effects/SkComposeImageFilter.h
index ab3ce63..a2ab996 100644
--- a/include/effects/SkComposeImageFilter.h
+++ b/include/effects/SkComposeImageFilter.h
@@ -24,7 +24,6 @@
     }
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
                                         SkIPoint* offset) const override;
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
     SkIRect onFilterBounds(const SkIRect&, const SkMatrix& ctm,
                            MapDirection, const SkIRect* inputRect) const override;
     bool onCanHandleComplexCTM() const override { return true; }
diff --git a/include/effects/SkDisplacementMapEffect.h b/include/effects/SkDisplacementMapEffect.h
index a5fd1bd..5f8d4f9 100644
--- a/include/effects/SkDisplacementMapEffect.h
+++ b/include/effects/SkDisplacementMapEffect.h
@@ -35,7 +35,6 @@
 
     virtual SkIRect onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
                                    MapDirection, const SkIRect* inputRect) const override;
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
     SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm,
                                MapDirection, const SkIRect* inputRect) const override;
 
diff --git a/include/effects/SkDropShadowImageFilter.h b/include/effects/SkDropShadowImageFilter.h
index b28d244..88dfe30 100644
--- a/include/effects/SkDropShadowImageFilter.h
+++ b/include/effects/SkDropShadowImageFilter.h
@@ -35,7 +35,6 @@
     void flatten(SkWriteBuffer&) const override;
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
                                         SkIPoint* offset) const override;
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
     SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
                                MapDirection, const SkIRect* inputRect) const override;
 
diff --git a/include/effects/SkGradientShader.h b/include/effects/SkGradientShader.h
index 3b537a5..2706eb1 100644
--- a/include/effects/SkGradientShader.h
+++ b/include/effects/SkGradientShader.h
@@ -69,13 +69,26 @@
     */
     static sk_sp<SkShader> MakeLinear(const SkPoint pts[2],
                                       const SkColor colors[], const SkScalar pos[], int count,
-                                      SkShader::TileMode mode,
+                                      SkTileMode mode,
                                       uint32_t flags, const SkMatrix* localMatrix);
     static sk_sp<SkShader> MakeLinear(const SkPoint pts[2],
                                       const SkColor colors[], const SkScalar pos[], int count,
+                                      SkTileMode mode) {
+        return MakeLinear(pts, colors, pos, count, mode, 0, nullptr);
+    }
+#ifdef SK_SUPPORT_LEGACY_TILEMODE_ENUM
+    static sk_sp<SkShader> MakeLinear(const SkPoint pts[2],
+                                      const SkColor colors[], const SkScalar pos[], int count,
+                                      SkShader::TileMode mode,
+                                      uint32_t flags, const SkMatrix* localMatrix) {
+        return MakeLinear(pts, colors, pos, count, (SkTileMode)mode, flags, localMatrix);
+    }
+    static sk_sp<SkShader> MakeLinear(const SkPoint pts[2],
+                                      const SkColor colors[], const SkScalar pos[], int count,
                                       SkShader::TileMode mode) {
         return MakeLinear(pts, colors, pos, count, mode, 0, nullptr);
     }
+#endif
 
     /** Returns a shader that generates a linear gradient between the two specified points.
         <p />
@@ -91,13 +104,26 @@
     */
     static sk_sp<SkShader> MakeLinear(const SkPoint pts[2],
                                       const SkColor4f colors[], sk_sp<SkColorSpace> colorSpace,
-                                      const SkScalar pos[], int count, SkShader::TileMode mode,
+                                      const SkScalar pos[], int count, SkTileMode mode,
                                       uint32_t flags, const SkMatrix* localMatrix);
     static sk_sp<SkShader> MakeLinear(const SkPoint pts[2],
                                       const SkColor4f colors[], sk_sp<SkColorSpace> colorSpace,
+                                      const SkScalar pos[], int count, SkTileMode mode) {
+        return MakeLinear(pts, colors, std::move(colorSpace), pos, count, mode, 0, nullptr);
+    }
+#ifdef SK_SUPPORT_LEGACY_TILEMODE_ENUM
+    static sk_sp<SkShader> MakeLinear(const SkPoint pts[2],
+                                      const SkColor4f colors[], sk_sp<SkColorSpace> colorSpace,
+                                      const SkScalar pos[], int count, SkShader::TileMode mode,
+                                      uint32_t flags, const SkMatrix* localMatrix) {
+        return MakeLinear(pts, colors, colorSpace, pos, count, (SkTileMode)mode, flags, localMatrix);
+    }
+    static sk_sp<SkShader> MakeLinear(const SkPoint pts[2],
+                                      const SkColor4f colors[], sk_sp<SkColorSpace> colorSpace,
                                       const SkScalar pos[], int count, SkShader::TileMode mode) {
         return MakeLinear(pts, colors, std::move(colorSpace), pos, count, mode, 0, nullptr);
     }
+#endif
 
     /** Returns a shader that generates a radial gradient given the center and radius.
         <p />
@@ -114,13 +140,26 @@
     */
     static sk_sp<SkShader> MakeRadial(const SkPoint& center, SkScalar radius,
                                       const SkColor colors[], const SkScalar pos[], int count,
-                                      SkShader::TileMode mode,
+                                      SkTileMode mode,
                                       uint32_t flags, const SkMatrix* localMatrix);
     static sk_sp<SkShader> MakeRadial(const SkPoint& center, SkScalar radius,
                                       const SkColor colors[], const SkScalar pos[], int count,
+                                      SkTileMode mode) {
+        return MakeRadial(center, radius, colors, pos, count, mode, 0, nullptr);
+    }
+#ifdef SK_SUPPORT_LEGACY_TILEMODE_ENUM
+    static sk_sp<SkShader> MakeRadial(const SkPoint& center, SkScalar radius,
+                                      const SkColor colors[], const SkScalar pos[], int count,
+                                      SkShader::TileMode mode,
+                                      uint32_t flags, const SkMatrix* localMatrix) {
+        return MakeRadial(center, radius, colors, pos, count, (SkTileMode)mode, flags, localMatrix);
+    }
+    static sk_sp<SkShader> MakeRadial(const SkPoint& center, SkScalar radius,
+                                      const SkColor colors[], const SkScalar pos[], int count,
                                       SkShader::TileMode mode) {
         return MakeRadial(center, radius, colors, pos, count, mode, 0, nullptr);
     }
+#endif
 
     /** Returns a shader that generates a radial gradient given the center and radius.
         <p />
@@ -137,14 +176,29 @@
     */
     static sk_sp<SkShader> MakeRadial(const SkPoint& center, SkScalar radius,
                                       const SkColor4f colors[], sk_sp<SkColorSpace> colorSpace,
-                                      const SkScalar pos[], int count, SkShader::TileMode mode,
+                                      const SkScalar pos[], int count, SkTileMode mode,
                                       uint32_t flags, const SkMatrix* localMatrix);
     static sk_sp<SkShader> MakeRadial(const SkPoint& center, SkScalar radius,
                                       const SkColor4f colors[], sk_sp<SkColorSpace> colorSpace,
+                                      const SkScalar pos[], int count, SkTileMode mode) {
+        return MakeRadial(center, radius, colors, std::move(colorSpace), pos, count, mode,
+                          0, nullptr);
+    }
+#ifdef SK_SUPPORT_LEGACY_TILEMODE_ENUM
+    static sk_sp<SkShader> MakeRadial(const SkPoint& center, SkScalar radius,
+                                      const SkColor4f colors[], sk_sp<SkColorSpace> colorSpace,
+                                      const SkScalar pos[], int count, SkShader::TileMode mode,
+                                      uint32_t flags, const SkMatrix* localMatrix) {
+        return MakeRadial(center, radius, colors, colorSpace, pos, count, (SkTileMode)mode,
+                          flags, localMatrix);
+    }
+    static sk_sp<SkShader> MakeRadial(const SkPoint& center, SkScalar radius,
+                                      const SkColor4f colors[], sk_sp<SkColorSpace> colorSpace,
                                       const SkScalar pos[], int count, SkShader::TileMode mode) {
         return MakeRadial(center, radius, colors, std::move(colorSpace), pos, count, mode,
                           0, nullptr);
     }
+#endif
 
     /**
      *  Returns a shader that generates a conical gradient given two circles, or
@@ -155,15 +209,32 @@
     static sk_sp<SkShader> MakeTwoPointConical(const SkPoint& start, SkScalar startRadius,
                                                const SkPoint& end, SkScalar endRadius,
                                                const SkColor colors[], const SkScalar pos[],
-                                               int count, SkShader::TileMode mode,
+                                               int count, SkTileMode mode,
                                                uint32_t flags, const SkMatrix* localMatrix);
     static sk_sp<SkShader> MakeTwoPointConical(const SkPoint& start, SkScalar startRadius,
                                                const SkPoint& end, SkScalar endRadius,
                                                const SkColor colors[], const SkScalar pos[],
+                                               int count, SkTileMode mode) {
+        return MakeTwoPointConical(start, startRadius, end, endRadius, colors, pos, count, mode,
+                                   0, nullptr);
+    }
+#ifdef SK_SUPPORT_LEGACY_TILEMODE_ENUM
+    static sk_sp<SkShader> MakeTwoPointConical(const SkPoint& start, SkScalar startRadius,
+                                               const SkPoint& end, SkScalar endRadius,
+                                               const SkColor colors[], const SkScalar pos[],
+                                               int count, SkShader::TileMode mode,
+                                               uint32_t flags, const SkMatrix* localMatrix) {
+        return MakeTwoPointConical(start, startRadius, end, endRadius, colors, pos, count,
+                                   (SkTileMode)mode, flags, localMatrix);
+    }
+    static sk_sp<SkShader> MakeTwoPointConical(const SkPoint& start, SkScalar startRadius,
+                                               const SkPoint& end, SkScalar endRadius,
+                                               const SkColor colors[], const SkScalar pos[],
                                                int count, SkShader::TileMode mode) {
         return MakeTwoPointConical(start, startRadius, end, endRadius, colors, pos, count, mode,
                                    0, nullptr);
     }
+#endif
 
     /**
      *  Returns a shader that generates a conical gradient given two circles, or
@@ -175,16 +246,35 @@
                                                const SkPoint& end, SkScalar endRadius,
                                                const SkColor4f colors[],
                                                sk_sp<SkColorSpace> colorSpace, const SkScalar pos[],
-                                               int count, SkShader::TileMode mode,
+                                               int count, SkTileMode mode,
                                                uint32_t flags, const SkMatrix* localMatrix);
     static sk_sp<SkShader> MakeTwoPointConical(const SkPoint& start, SkScalar startRadius,
                                                const SkPoint& end, SkScalar endRadius,
                                                const SkColor4f colors[],
                                                sk_sp<SkColorSpace> colorSpace, const SkScalar pos[],
+                                               int count, SkTileMode mode) {
+        return MakeTwoPointConical(start, startRadius, end, endRadius, colors,
+                                   std::move(colorSpace), pos, count, mode, 0, nullptr);
+    }
+#ifdef SK_SUPPORT_LEGACY_TILEMODE_ENUM
+    static sk_sp<SkShader> MakeTwoPointConical(const SkPoint& start, SkScalar startRadius,
+                                               const SkPoint& end, SkScalar endRadius,
+                                               const SkColor4f colors[],
+                                               sk_sp<SkColorSpace> colorSpace, const SkScalar pos[],
+                                               int count, SkShader::TileMode mode,
+                                               uint32_t flags, const SkMatrix* localMatrix) {
+        return MakeTwoPointConical(start, startRadius, end, endRadius, colors, colorSpace, pos,
+                                   count, (SkTileMode)mode, flags, localMatrix);
+    }
+    static sk_sp<SkShader> MakeTwoPointConical(const SkPoint& start, SkScalar startRadius,
+                                               const SkPoint& end, SkScalar endRadius,
+                                               const SkColor4f colors[],
+                                               sk_sp<SkColorSpace> colorSpace, const SkScalar pos[],
                                                int count, SkShader::TileMode mode) {
         return MakeTwoPointConical(start, startRadius, end, endRadius, colors,
                                    std::move(colorSpace), pos, count, mode, 0, nullptr);
     }
+#endif
 
     /** Returns a shader that generates a sweep gradient given a center.
         <p />
@@ -204,19 +294,29 @@
     */
     static sk_sp<SkShader> MakeSweep(SkScalar cx, SkScalar cy,
                                      const SkColor colors[], const SkScalar pos[], int count,
-                                     SkShader::TileMode mode,
+                                     SkTileMode mode,
                                      SkScalar startAngle, SkScalar endAngle,
                                      uint32_t flags, const SkMatrix* localMatrix);
     static sk_sp<SkShader> MakeSweep(SkScalar cx, SkScalar cy,
                                      const SkColor colors[], const SkScalar pos[], int count,
                                      uint32_t flags, const SkMatrix* localMatrix) {
-        return MakeSweep(cx, cy, colors, pos, count, SkShader::kClamp_TileMode, 0, 360, flags,
+        return MakeSweep(cx, cy, colors, pos, count, SkTileMode::kClamp, 0, 360, flags,
                          localMatrix);
     }
     static sk_sp<SkShader> MakeSweep(SkScalar cx, SkScalar cy,
                                      const SkColor colors[], const SkScalar pos[], int count) {
         return MakeSweep(cx, cy, colors, pos, count, 0, nullptr);
     }
+#ifdef SK_SUPPORT_LEGACY_TILEMODE_ENUM
+    static sk_sp<SkShader> MakeSweep(SkScalar cx, SkScalar cy,
+                                     const SkColor colors[], const SkScalar pos[], int count,
+                                     SkShader::TileMode mode,
+                                     SkScalar startAngle, SkScalar endAngle,
+                                     uint32_t flags, const SkMatrix* localMatrix) {
+        return MakeSweep(cx, cy, colors, pos, count, (SkTileMode)mode, startAngle, endAngle,
+                         flags, localMatrix);
+    }
+#endif
 
     /** Returns a shader that generates a sweep gradient given a center.
         <p />
@@ -237,7 +337,7 @@
     static sk_sp<SkShader> MakeSweep(SkScalar cx, SkScalar cy,
                                      const SkColor4f colors[], sk_sp<SkColorSpace> colorSpace,
                                      const SkScalar pos[], int count,
-                                     SkShader::TileMode mode,
+                                     SkTileMode mode,
                                      SkScalar startAngle, SkScalar endAngle,
                                      uint32_t flags, const SkMatrix* localMatrix);
     static sk_sp<SkShader> MakeSweep(SkScalar cx, SkScalar cy,
@@ -245,13 +345,24 @@
                                      const SkScalar pos[], int count,
                                      uint32_t flags, const SkMatrix* localMatrix) {
         return MakeSweep(cx, cy, colors, std::move(colorSpace), pos, count,
-                         SkShader::kClamp_TileMode, 0, 360, flags, localMatrix);
+                         SkTileMode::kClamp, 0, 360, flags, localMatrix);
     }
     static sk_sp<SkShader> MakeSweep(SkScalar cx, SkScalar cy,
                                      const SkColor4f colors[], sk_sp<SkColorSpace> colorSpace,
                                      const SkScalar pos[], int count) {
         return MakeSweep(cx, cy, colors, std::move(colorSpace), pos, count, 0, nullptr);
     }
+#ifdef SK_SUPPORT_LEGACY_TILEMODE_ENUM
+    static sk_sp<SkShader> MakeSweep(SkScalar cx, SkScalar cy,
+                                     const SkColor4f colors[], sk_sp<SkColorSpace> colorSpace,
+                                     const SkScalar pos[], int count,
+                                     SkShader::TileMode mode,
+                                     SkScalar startAngle, SkScalar endAngle,
+                                     uint32_t flags, const SkMatrix* localMatrix) {
+        return MakeSweep(cx, cy, colors, colorSpace, pos, count, (SkTileMode)mode, startAngle,
+                         endAngle, flags, localMatrix);
+    }
+#endif
 
     static void RegisterFlattenables();
 };
diff --git a/include/effects/SkImageSource.h b/include/effects/SkImageSource.h
index 13aede1..6a669e6 100644
--- a/include/effects/SkImageSource.h
+++ b/include/effects/SkImageSource.h
@@ -27,7 +27,6 @@
 
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
                                         SkIPoint* offset) const override;
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
 
     SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm,
                                MapDirection, const SkIRect* inputRect) const override;
diff --git a/include/effects/SkLayerDrawLooper.h b/include/effects/SkLayerDrawLooper.h
index 43eeeca..5cb3b18 100644
--- a/include/effects/SkLayerDrawLooper.h
+++ b/include/effects/SkLayerDrawLooper.h
@@ -77,8 +77,6 @@
     bool asABlurShadow(BlurShadowRec* rec) const override;
 
 protected:
-    sk_sp<SkDrawLooper> onMakeColorSpace(SkColorSpaceXformer*) const override;
-
     SkLayerDrawLooper();
 
     void flatten(SkWriteBuffer&) const override;
diff --git a/include/effects/SkLumaColorFilter.h b/include/effects/SkLumaColorFilter.h
index 500dae9..af380cf 100644
--- a/include/effects/SkLumaColorFilter.h
+++ b/include/effects/SkLumaColorFilter.h
@@ -14,15 +14,21 @@
 class SkRasterPipeline;
 
 /**
- *  Luminance-to-alpha color filter, as defined in
- *  http://www.w3.org/TR/SVG/masking.html#Masking
- *  http://www.w3.org/TR/css-masking/#MaskValues
+ *  SkLumaColorFilter multiplies the luma of its input into the alpha channel,
+ *  and sets the red, green, and blue channels to zero.
  *
- *  The resulting color is black with transparency equal to the
- *  luminance value modulated by alpha:
+ *    SkLumaColorFilter(r,g,b,a) = {0,0,0, a * luma(r,g,b)}
  *
- *    C' = [ Lum * a, 0, 0, 0 ]
+ *  This is similar to a luminanceToAlpha feColorMatrix,
+ *  but note how this filter folds in the previous alpha,
+ *  something an feColorMatrix cannot do.
  *
+ *    feColorMatrix(luminanceToAlpha; r,g,b,a) = {0,0,0, luma(r,g,b)}
+ *
+ *  (Despite its name, an feColorMatrix using luminanceToAlpha does
+ *  actually compute luma, a dot-product of gamma-encoded color channels,
+ *  not luminance, a dot-product of linear color channels.  So at least
+ *  SkLumaColorFilter and feColorMatrix+luminanceToAlpha agree there.)
  */
 
  #include "SkFlattenable.h"
@@ -33,7 +39,7 @@
 
 #if SK_SUPPORT_GPU
     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
-            GrContext*, const GrColorSpaceInfo&) const override;
+            GrRecordingContext*, const GrColorSpaceInfo&) const override;
 #endif
 
 protected:
@@ -43,8 +49,7 @@
     SK_FLATTENABLE_HOOKS(SkLumaColorFilter)
 
     SkLumaColorFilter();
-    void onAppendStages(SkRasterPipeline*, SkColorSpace*, SkArenaAlloc*,
-                        bool shaderIsOpaque) const override;
+    bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override;
 
     typedef SkColorFilter INHERITED;
 };
diff --git a/include/effects/SkMagnifierImageFilter.h b/include/effects/SkMagnifierImageFilter.h
index f060b48..2fb7ab3 100644
--- a/include/effects/SkMagnifierImageFilter.h
+++ b/include/effects/SkMagnifierImageFilter.h
@@ -27,7 +27,6 @@
 
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
                                         SkIPoint* offset) const override;
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
 
 private:
     SK_FLATTENABLE_HOOKS(SkMagnifierImageFilter)
diff --git a/include/effects/SkMatrixConvolutionImageFilter.h b/include/effects/SkMatrixConvolutionImageFilter.h
index bde0863..5c2dfbd 100644
--- a/include/effects/SkMatrixConvolutionImageFilter.h
+++ b/include/effects/SkMatrixConvolutionImageFilter.h
@@ -82,7 +82,6 @@
 
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
                                         SkIPoint* offset) const override;
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
     SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm,
                                MapDirection, const SkIRect* inputRect) const override;
     bool affectsTransparentBlack() const override;
diff --git a/include/effects/SkMergeImageFilter.h b/include/effects/SkMergeImageFilter.h
index ed66596..7eacb8d 100644
--- a/include/effects/SkMergeImageFilter.h
+++ b/include/effects/SkMergeImageFilter.h
@@ -29,7 +29,6 @@
     void flatten(SkWriteBuffer&) const override;
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
                                         SkIPoint* offset) const override;
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
     bool onCanHandleComplexCTM() const override { return true; }
 
 private:
diff --git a/include/effects/SkMorphologyImageFilter.h b/include/effects/SkMorphologyImageFilter.h
index 5ed624f..7059cc0 100644
--- a/include/effects/SkMorphologyImageFilter.h
+++ b/include/effects/SkMorphologyImageFilter.h
@@ -44,7 +44,6 @@
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source,
                                         const Context&,
                                         SkIPoint* offset) const override;
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
     void flatten(SkWriteBuffer&) const override;
 
     SkISize radius() const { return fRadius; }
diff --git a/include/effects/SkOffsetImageFilter.h b/include/effects/SkOffsetImageFilter.h
index e9a771c..f5f12c8 100644
--- a/include/effects/SkOffsetImageFilter.h
+++ b/include/effects/SkOffsetImageFilter.h
@@ -24,7 +24,6 @@
     void flatten(SkWriteBuffer&) const override;
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
                                         SkIPoint* offset) const override;
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
     SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm,
                                MapDirection, const SkIRect* inputRect) const override;
 
diff --git a/include/effects/SkOpPathEffect.h b/include/effects/SkOpPathEffect.h
index 87080fc..d97e3b3 100644
--- a/include/effects/SkOpPathEffect.h
+++ b/include/effects/SkOpPathEffect.h
@@ -12,7 +12,7 @@
 #include "SkPaint.h"
 #include "SkPathOps.h"
 
-class SkMergePathEffect {
+class SK_API SkMergePathEffect {
 public:
     /*  Defers to two other patheffects, and then combines their outputs using the specified op.
      *  e.g.
@@ -23,13 +23,13 @@
     static sk_sp<SkPathEffect> Make(sk_sp<SkPathEffect> one, sk_sp<SkPathEffect> two, SkPathOp op);
 };
 
-class SkMatrixPathEffect {
+class SK_API SkMatrixPathEffect {
 public:
     static sk_sp<SkPathEffect> MakeTranslate(SkScalar dx, SkScalar dy);
     static sk_sp<SkPathEffect> Make(const SkMatrix&);
 };
 
-class SkStrokePathEffect {
+class SK_API SkStrokePathEffect {
 public:
     static sk_sp<SkPathEffect> Make(SkScalar width, SkPaint::Join, SkPaint::Cap,
                                     SkScalar miter = 4);
diff --git a/include/effects/SkOverdrawColorFilter.h b/include/effects/SkOverdrawColorFilter.h
index cd2c100..9777dd0 100644
--- a/include/effects/SkOverdrawColorFilter.h
+++ b/include/effects/SkOverdrawColorFilter.h
@@ -29,7 +29,7 @@
 
 #if SK_SUPPORT_GPU
     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
-            GrContext*, const GrColorSpaceInfo&) const override;
+            GrRecordingContext*, const GrColorSpaceInfo&) const override;
 #endif
 
     static void RegisterFlattenables();
@@ -44,7 +44,7 @@
         memcpy(fColors, colors, kNumColors * sizeof(SkPMColor));
     }
 
-    void onAppendStages(SkRasterPipeline*, SkColorSpace*, SkArenaAlloc*, bool) const override;
+    bool onAppendStages(const SkStageRec&, bool) const override;
 
     SkPMColor fColors[kNumColors];
 
diff --git a/include/effects/SkPaintImageFilter.h b/include/effects/SkPaintImageFilter.h
index 4b6772e..48e0116 100644
--- a/include/effects/SkPaintImageFilter.h
+++ b/include/effects/SkPaintImageFilter.h
@@ -31,7 +31,6 @@
     void flatten(SkWriteBuffer&) const override;
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
                                         SkIPoint* offset) const override;
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer* xformer) const override;
 
 private:
     SK_FLATTENABLE_HOOKS(SkPaintImageFilter)
diff --git a/include/effects/SkPictureImageFilter.h b/include/effects/SkPictureImageFilter.h
index a1a5b38..4e2fd20 100644
--- a/include/effects/SkPictureImageFilter.h
+++ b/include/effects/SkPictureImageFilter.h
@@ -36,21 +36,16 @@
     void flatten(SkWriteBuffer&) const override;
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
                                         SkIPoint* offset) const override;
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
 
 private:
     SK_FLATTENABLE_HOOKS(SkPictureImageFilter)
 
     explicit SkPictureImageFilter(sk_sp<SkPicture> picture);
-    SkPictureImageFilter(sk_sp<SkPicture> picture, const SkRect& cropRect, sk_sp<SkColorSpace>);
+    SkPictureImageFilter(sk_sp<SkPicture> picture, const SkRect& cropRect);
 
     sk_sp<SkPicture>    fPicture;
     SkRect              fCropRect;
 
-    // Should never be set by a public constructor.  This is only used when onMakeColorSpace()
-    // forces a deferred color space xform.
-    sk_sp<SkColorSpace>   fColorSpace;
-
     typedef SkImageFilter INHERITED;
 };
 
diff --git a/include/effects/SkTileImageFilter.h b/include/effects/SkTileImageFilter.h
index bcd2f5b..b482717 100644
--- a/include/effects/SkTileImageFilter.h
+++ b/include/effects/SkTileImageFilter.h
@@ -33,7 +33,6 @@
 
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
                                         SkIPoint* offset) const override;
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
 
 private:
     SK_FLATTENABLE_HOOKS(SkTileImageFilter)
diff --git a/include/effects/SkToSRGBColorFilter.h b/include/effects/SkToSRGBColorFilter.h
deleted file mode 100644
index f577fb9..0000000
--- a/include/effects/SkToSRGBColorFilter.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkToSRGBColorFilter_DEFINED
-#define SkToSRGBColorFilter_DEFINED
-
-#include "SkFlattenable.h"
-#include "SkColorFilter.h"
-#include "SkRefCnt.h"
-
-class SkColorSpace;
-class SkRasterPipeline;
-
-/**
- *  Color filter that converts from supplied color space to sRGB (both gamut and transfer function).
- */
-class SK_API SkToSRGBColorFilter : public SkColorFilter {
-public:
-    static sk_sp<SkColorFilter> Make(sk_sp<SkColorSpace> srcColorSpace);
-
-#if SK_SUPPORT_GPU
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
-            GrContext*, const GrColorSpaceInfo&) const override;
-#endif
-
-private:
-    SK_FLATTENABLE_HOOKS(SkToSRGBColorFilter)
-
-    void flatten(SkWriteBuffer&) const override;
-    SkToSRGBColorFilter(sk_sp<SkColorSpace>);
-    void onAppendStages(SkRasterPipeline*, SkColorSpace*, SkArenaAlloc*,
-                        bool shaderIsOpaque) const override;
-
-    sk_sp<SkColorSpace> fSrcColorSpace;
-
-    typedef SkColorFilter INHERITED;
-};
-
-#endif
diff --git a/include/encode/SkWebpEncoder.h b/include/encode/SkWebpEncoder.h
index 8d5f4a6..d4e0535 100644
--- a/include/encode/SkWebpEncoder.h
+++ b/include/encode/SkWebpEncoder.h
@@ -43,6 +43,6 @@
      *  Returns true on success.  Returns false on an invalid or unsupported |src|.
      */
     SK_API bool Encode(SkWStream* dst, const SkPixmap& src, const Options& options);
-};
+}
 
 #endif
diff --git a/include/gpu/GrBackendSurface.h b/include/gpu/GrBackendSurface.h
index 28543c3..3ccec3a 100644
--- a/include/gpu/GrBackendSurface.h
+++ b/include/gpu/GrBackendSurface.h
@@ -93,7 +93,9 @@
     // it returns nullptr.
     const GrPixelConfig* getMockFormat() const;
 
-    // If possible, copies the GrBackendFormat and forces the texture type to be Texture2D
+    // If possible, copies the GrBackendFormat and forces the texture type to be Texture2D. If the
+    // GrBackendFormat was for Vulkan and it originally had a GrVkYcbcrConversionInfo, we will
+    // remove the conversion and set the format to be VK_FORMAT_R8G8B8A8_UNORM.
     GrBackendFormat makeTexture2D() const;
 
     // Returns true if the backend format has been initialized.
diff --git a/include/gpu/GrConfig.h b/include/gpu/GrConfig.h
index 1c07dc4..2ab4c7e 100644
--- a/include/gpu/GrConfig.h
+++ b/include/gpu/GrConfig.h
@@ -51,17 +51,6 @@
 ///////////////////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////
 
-#if defined(SK_BUILD_FOR_WIN)
-// VC8 doesn't support stdint.h, so we define those types here.
-typedef signed char int8_t;
-typedef unsigned char uint8_t;
-typedef short int16_t;
-typedef unsigned short uint16_t;
-typedef int int32_t;
-typedef unsigned uint32_t;
-typedef __int64 int64_t;
-typedef unsigned __int64 uint64_t;
-#else
 /*
  *  Include stdint.h with defines that trigger declaration of C99 limit/const
  *  macros here before anyone else has a chance to include stdint.h without
@@ -74,7 +63,6 @@
 #define __STDC_CONSTANT_MACROS
 #endif
 #include <stdint.h>
-#endif
 
 ///////////////////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h
index 3aeab96..4869746 100644
--- a/include/gpu/GrContext.h
+++ b/include/gpu/GrContext.h
@@ -11,9 +11,7 @@
 #include "SkMatrix.h"
 #include "SkPathEffect.h"
 #include "SkTypes.h"
-#include "../private/GrAuditTrail.h"
 #include "../private/GrRecordingContext.h"
-#include "../private/GrSingleOwner.h"
 #include "GrContextOptions.h"
 
 // We shouldn't need this but currently Android is relying on this being include transitively.
@@ -25,15 +23,11 @@
 class GrCaps;
 class GrContextPriv;
 class GrContextThreadSafeProxy;
-class GrDrawingManager;
 class GrFragmentProcessor;
 struct GrGLInterface;
-class GrStrikeCache;
 class GrGpu;
 struct GrMockOptions;
-class GrOpMemoryPool;
 class GrPath;
-class GrProxyProvider;
 class GrRenderTargetContext;
 class GrResourceCache;
 class GrResourceProvider;
@@ -41,7 +35,6 @@
 class GrSkSLFPFactoryCache;
 class GrSurfaceProxy;
 class GrSwizzle;
-class GrTextBlobCache;
 class GrTextContext;
 class GrTextureProxy;
 struct GrVkBackendContext;
@@ -94,6 +87,17 @@
     void resetContext(uint32_t state = kAll_GrBackendState);
 
     /**
+     * If the backend is GrBackendApi::kOpenGL, then all texture unit/target combinations for which
+     * the GrContext has modified the bound texture will have texture id 0 bound. This does not
+     * flush the GrContext. Calling resetContext() does not change the set that will be bound
+     * to texture id 0 on the next call to resetGLTextureBindings(). After this is called
+     * all unit/target combinations are considered to have unmodified bindings until the GrContext
+     * subsequently modifies them (meaning if this is called twice in a row with no intervening
+     * GrContext usage then the second call is a no-op.)
+     */
+    void resetGLTextureBindings();
+
+    /**
      * Abandons all GPU resources and assumes the underlying backend 3D API context is no longer
      * usable. Call this if you have lost the associated GPU context, and thus internal texture,
      * buffer, etc. references/IDs are now invalid. Calling this ensures that the destructors of the
@@ -104,12 +108,12 @@
      * The typical use case for this function is that the underlying 3D context was lost and further
      * API calls may crash.
      */
-    virtual void abandonContext();
+    void abandonContext() override;
 
     /**
      * Returns true if the context was abandoned.
      */
-    bool abandoned() const;
+    using GrImageContext::abandoned;
 
     /**
      * This is similar to abandonContext() however the underlying 3D context is not yet lost and
@@ -264,8 +268,16 @@
      * added any semaphores to signal on the GPU. Thus the client should not have the GPU wait on
      * any of the semaphores. However, any pending commands to the context will still be flushed.
      */
+    GrSemaphoresSubmitted flush(GrFlushFlags flags, int numSemaphores,
+                                GrBackendSemaphore signalSemaphores[]);
+
+    /**
+     * Deprecated.
+     */
     GrSemaphoresSubmitted flushAndSignalSemaphores(int numSemaphores,
-                                                   GrBackendSemaphore signalSemaphores[]);
+                                                   GrBackendSemaphore signalSemaphores[]) {
+        return this->flush(kNone_GrFlushFlags, numSemaphores, signalSemaphores);
+    }
 
     // Provides access to functions that aren't part of the public API.
     GrContextPriv priv();
@@ -279,11 +291,16 @@
 
     void storeVkPipelineCacheData();
 
+    static size_t ComputeTextureSize(SkColorType type, int width, int height, GrMipMapped,
+                                     bool useNextPow2 = false);
+
 protected:
-    GrContext(GrBackendApi, const GrContextOptions& options, int32_t id = SK_InvalidGenID);
+    GrContext(GrBackendApi, const GrContextOptions&, int32_t contextID = SK_InvalidGenID);
 
     bool init(sk_sp<const GrCaps>, sk_sp<GrSkSLFPFactoryCache>) override;
 
+    GrContext* asDirectContext() override { return this; }
+
     virtual GrAtlasManager* onGetAtlasManager() = 0;
 
     sk_sp<GrContextThreadSafeProxy>         fThreadSafeProxy;
@@ -297,27 +314,11 @@
     sk_sp<GrGpu>                            fGpu;
     GrResourceCache*                        fResourceCache;
     GrResourceProvider*                     fResourceProvider;
-    GrProxyProvider*                        fProxyProvider;
-
-    // All the GrOp-derived classes use this pool.
-    sk_sp<GrOpMemoryPool>                   fOpMemoryPool;
-
-    GrStrikeCache*                           fGlyphCache;
-    std::unique_ptr<GrTextBlobCache>        fTextBlobCache;
 
     bool                                    fDidTestPMConversions;
     // true if the PM/UPM conversion succeeded; false otherwise
     bool                                    fPMUPMConversionsRoundTrip;
 
-    // In debug builds we guard against improper thread handling
-    // This guard is passed to the GrDrawingManager and, from there to all the
-    // GrRenderTargetContexts.  It is also passed to the GrResourceProvider and SkGpuDevice.
-    mutable GrSingleOwner                   fSingleOwner;
-
-    std::unique_ptr<GrDrawingManager>       fDrawingManager;
-
-    GrAuditTrail                            fAuditTrail;
-
     GrContextOptions::PersistentCache*      fPersistentCache;
 
     // TODO: have the GrClipStackClip use renderTargetContexts and rm this friending
@@ -336,12 +337,6 @@
      */
     bool validPMUPMConversionExists();
 
-    /**
-     * A callback similar to the above for use by the TextBlobCache
-     * TODO move textblob draw calls below context so we can use the call above.
-     */
-    static void TextBlobCacheOverBudgetCB(void* data);
-
     typedef GrRecordingContext INHERITED;
 };
 
diff --git a/include/gpu/GrContextOptions.h b/include/gpu/GrContextOptions.h
index 77e2970..3bfe6f3 100644
--- a/include/gpu/GrContextOptions.h
+++ b/include/gpu/GrContextOptions.h
@@ -19,7 +19,7 @@
 class SkExecutor;
 
 #if SK_SUPPORT_GPU
-struct GrContextOptions {
+struct SK_API GrContextOptions {
     enum class Enable {
         /** Forces an option to be disabled. */
         kNo,
@@ -36,7 +36,7 @@
      * Skia stores compiled shader binaries (only when glProgramBinary / glGetProgramBinary are
      * supported) when provided a persistent cache, but this may extend to other data in the future.
      */
-    class PersistentCache {
+    class SK_API PersistentCache {
     public:
         virtual ~PersistentCache() {}
 
@@ -78,11 +78,12 @@
     bool fDoManualMipmapping = false;
 
     /**
-     * Disables the coverage counting path renderer. Coverage counting can sometimes cause new
-     * rendering artifacts along shared edges if care isn't taken to ensure both contours wind in
-     * the same direction.
+     * Disables the use of coverage counting shortcuts to render paths. Coverage counting can cause
+     * artifacts along shared edges if care isn't taken to ensure both contours wind in the same
+     * direction.
      */
-    bool fDisableCoverageCountingPaths = false;
+    // FIXME: Once this is removed from Chrome and Android, rename to fEnable"".
+    bool fDisableCoverageCountingPaths = true;
 
     /**
      * Disables distance field rendering for paths. Distance field computation can be expensive,
@@ -145,15 +146,8 @@
     Enable fUseDrawInsteadOfClear = Enable::kDefault;
 
     /**
-     * Allow Ganesh to explicitly allocate resources at flush time rather than incrementally while
-     * drawing. This will eventually just be the way it is but, for now, it is optional.
-     */
-    Enable fExplicitlyAllocateGPUResources = Enable::kDefault;
-
-    /**
      * Allow Ganesh to sort the opLists prior to allocating resources. This is an optional
-     * behavior that is only relevant when 'fExplicitlyAllocateGPUResources' is enabled.
-     * Eventually this will just be what is done and will not be optional.
+     * behavior but, eventually, this will just be what is done and will not be optional.
      */
     Enable fSortRenderTargets = Enable::kDefault;
 
@@ -183,6 +177,14 @@
      */
     PersistentCache* fPersistentCache = nullptr;
 
+    /**
+     * This affects the usage of the PersistentCache. If this is set to true GLSL shader strings
+     * rather than GL program binaries will be cached. It is intended to be used when the driver's
+     * binary loading/storing is believed to have bugs. Caching GLSL strings still saves a
+     * significant amount of CPU work when a GL program is created.
+     */
+     bool fDisallowGLSLBinaryCaching = false;
+
 #if GR_TEST_UTILS
     /**
      * Private options that are only meant for testing within Skia's tools.
@@ -218,12 +220,6 @@
      * Include or exclude specific GPU path renderers.
      */
     GpuPathRenderers fGpuPathRenderers = GpuPathRenderers::kAll;
-
-    /**
-     * Disables using multiple texture units to batch multiple images into a single draw on
-     * supported GPUs.
-     */
-    bool fDisableImageMultitexturing = false;
 #endif
 
 #if SK_SUPPORT_ATLAS_TEXT
diff --git a/include/gpu/GrContextThreadSafeProxy.h b/include/gpu/GrContextThreadSafeProxy.h
index 4387fcc..33977d8 100644
--- a/include/gpu/GrContextThreadSafeProxy.h
+++ b/include/gpu/GrContextThreadSafeProxy.h
@@ -8,14 +8,10 @@
 #ifndef GrContextThreadSafeProxy_DEFINED
 #define GrContextThreadSafeProxy_DEFINED
 
-#include "GrContextOptions.h"
-#include "SkRefCnt.h"
 #include "../private/GrContext_Base.h"
 
 class GrBackendFormat;
-class GrCaps;
 class GrContextThreadSafeProxyPriv;
-class GrSkSLFPFactoryCache;
 struct SkImageInfo;
 class SkSurfaceCharacterization;
 
@@ -27,8 +23,6 @@
 public:
     ~GrContextThreadSafeProxy() override;
 
-    bool matches(GrContext_Base* context) const;
-
     /**
      *  Create a surface characterization for a DDL that will be replayed into the GrContext
      *  that created this proxy. On failure the resulting characterization will be invalid (i.e.,
diff --git a/include/gpu/GrGpuResource.h b/include/gpu/GrGpuResource.h
index 0215e2b..948d17d8 100644
--- a/include/gpu/GrGpuResource.h
+++ b/include/gpu/GrGpuResource.h
@@ -33,12 +33,14 @@
  *
  * The latter two ref types are private and intended only for Gr core code.
  *
- * When all the ref/io counts reach zero DERIVED::notifyAllCntsAreZero() will be called (static poly
- * morphism using CRTP). Similarly when the ref (but not necessarily pending read/write) count
- * reaches 0 DERIVED::notifyRefCountIsZero() will be called. In the case when an unref() causes both
+ * PRIOR to the last ref/IO count being removed DERIVED::notifyAllCntsWillBeZero() will be called
+ * (static poly morphism using CRTP). It is legal for additional ref's or pending IOs to be added
+ * during this time. AFTER all the ref/io counts reach zero DERIVED::notifyAllCntsAreZero() will be
+ * called. Similarly when the ref (but not necessarily pending read/write) count reaches 0
+ * DERIVED::notifyRefCountIsZero() will be called. In the case when an unref() causes both
  * the ref cnt to reach zero and the other counts are zero, notifyRefCountIsZero() will be called
  * before notifyAllCntsAreZero(). Moreover, if notifyRefCountIsZero() returns false then
- * notifyAllRefCntsAreZero() won't be called at all. notifyRefCountIsZero() must return false if the
+ * notifyAllCntsAreZero() won't be called at all. notifyRefCountIsZero() must return false if the
  * object may be deleted after notifyRefCntIsZero() returns.
  *
  * GrIORef and GrGpuResource are separate classes for organizational reasons and to be
@@ -51,6 +53,8 @@
     // refs (e.g. pending reads). We also don't require thread safety as GrCacheable objects are
     // not intended to cross thread boundaries.
     void ref() const {
+        // Only the cache should be able to add the first ref to a resource.
+        SkASSERT(fRefCnt > 0);
         this->validate();
         ++fRefCnt;
     }
@@ -58,7 +62,13 @@
     void unref() const {
         this->validate();
 
-        if (!(--fRefCnt)) {
+        if (fRefCnt == 1) {
+            if (!this->internalHasPendingIO()) {
+                static_cast<const DERIVED*>(this)->notifyAllCntsWillBeZero();
+            }
+            SkASSERT(fRefCnt > 0);
+        }
+        if (--fRefCnt == 0) {
             if (!static_cast<const DERIVED*>(this)->notifyRefCountIsZero()) {
                 return;
             }
@@ -92,8 +102,13 @@
     bool internalHasRef() const { return SkToBool(fRefCnt); }
     bool internalHasUniqueRef() const { return fRefCnt == 1; }
 
+    // Privileged method that allows going from ref count = 0 to ref count = 1.
+    void addInitialRef() const {
+        this->validate();
+        ++fRefCnt;
+    }
+
 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);
@@ -105,6 +120,9 @@
 
     void completedRead() const {
         this->validate();
+        if (fPendingReads == 1 && !fPendingWrites && !fRefCnt) {
+            static_cast<const DERIVED*>(this)->notifyAllCntsWillBeZero();
+        }
         --fPendingReads;
         this->didRemoveRefOrPendingIO(kPendingRead_CntType);
     }
@@ -116,11 +134,13 @@
 
     void completedWrite() const {
         this->validate();
+        if (fPendingWrites == 1 && !fPendingReads && !fRefCnt) {
+            static_cast<const DERIVED*>(this)->notifyAllCntsWillBeZero();
+        }
         --fPendingWrites;
         this->didRemoveRefOrPendingIO(kPendingWrite_CntType);
     }
 
-private:
     void didRemoveRefOrPendingIO(CntType cntTypeRemoved) const {
         if (0 == fPendingReads && 0 == fPendingWrites && 0 == fRefCnt) {
             static_cast<const DERIVED*>(this)->notifyAllCntsAreZero(cntTypeRemoved);
@@ -131,6 +151,7 @@
     mutable int32_t fPendingReads;
     mutable int32_t fPendingWrites;
 
+    friend class GrIORefProxy;    // needs to forward on wrapped IO calls
     friend class GrResourceCache; // to check IO ref counts.
 
     template <typename, GrIOType> friend class GrPendingIOResource;
@@ -179,28 +200,20 @@
 
     class UniqueID {
     public:
-        static UniqueID InvalidID() {
-            return UniqueID(uint32_t(SK_InvalidUniqueID));
-        }
-
-        UniqueID() {}
+        UniqueID() = default;
 
         explicit UniqueID(uint32_t id) : fID(id) {}
 
         uint32_t asUInt() const { return fID; }
 
-        bool operator==(const UniqueID& other) const {
-            return fID == other.fID;
-        }
-        bool operator!=(const UniqueID& other) const {
-            return !(*this == other);
-        }
+        bool operator==(const UniqueID& other) const { return fID == other.fID; }
+        bool operator!=(const UniqueID& other) const { return !(*this == other); }
 
         void makeInvalid() { fID = SK_InvalidUniqueID; }
-        bool isInvalid() const { return SK_InvalidUniqueID == fID; }
+        bool isInvalid() const { return  fID == SK_InvalidUniqueID; }
 
     protected:
-        uint32_t fID;
+        uint32_t fID = SK_InvalidUniqueID;
     };
 
     /**
@@ -222,6 +235,12 @@
     inline const CacheAccess cacheAccess() const;
 
     /**
+     * Internal-only helper class used for manipulations of the resource by GrSurfaceProxy.
+     */
+    class ProxyAccess;
+    inline ProxyAccess proxyAccess();
+
+    /**
      * Internal-only helper class used for manipulations of the resource by internal code.
      */
     class ResourcePriv;
@@ -289,6 +308,7 @@
 
 private:
     bool isPurgeable() const;
+    bool hasRef() const;
     bool hasRefOrPendingIO() const;
 
     /**
@@ -315,11 +335,12 @@
     /**
      * Called by GrResourceCache when a resource loses its last ref or pending IO.
      */
-    virtual void removedLastRefOrPendingIO() {}
+    virtual void willRemoveLastRefOrPendingIO() {}
 
     // See comments in CacheAccess and ResourcePriv.
     void setUniqueKey(const GrUniqueKey&);
     void removeUniqueKey();
+    void notifyAllCntsWillBeZero() const;
     void notifyAllCntsAreZero(CntType) const;
     bool notifyRefCountIsZero() const;
     void removeScratchKey();
@@ -355,4 +376,24 @@
     friend class GrIORef<GrGpuResource>; // to access notifyAllCntsAreZero and notifyRefCntIsZero.
 };
 
+class GrGpuResource::ProxyAccess {
+private:
+    ProxyAccess(GrGpuResource* resource) : fResource(resource) {}
+
+    /** Proxies are allowed to take a resource from no refs to one ref. */
+    void ref(GrResourceCache* cache);
+
+    // No taking addresses of this type.
+    const CacheAccess* operator&() const = delete;
+    CacheAccess* operator&() = delete;
+
+    GrGpuResource* fResource;
+
+    friend class GrGpuResource;
+    friend class GrSurfaceProxy;
+    friend class GrIORefProxy;
+};
+
+inline GrGpuResource::ProxyAccess GrGpuResource::proxyAccess() { return ProxyAccess(this); }
+
 #endif
diff --git a/include/gpu/GrRenderTarget.h b/include/gpu/GrRenderTarget.h
index 32c6225..b26ba0e 100644
--- a/include/gpu/GrRenderTarget.h
+++ b/include/gpu/GrRenderTarget.h
@@ -125,10 +125,10 @@
 
     friend class GrRenderTargetPriv;
 
-    int                  fSampleCnt;
+    int fSampleCnt;
+    int fSamplePatternKey;
     sk_sp<GrStencilAttachment> fStencilAttachment;
-
-    SkIRect              fResolveRect;
+    SkIRect fResolveRect;
 
     typedef GrSurface INHERITED;
 };
diff --git a/include/gpu/GrSurface.h b/include/gpu/GrSurface.h
index c4ff818..b03df55 100644
--- a/include/gpu/GrSurface.h
+++ b/include/gpu/GrSurface.h
@@ -45,7 +45,7 @@
 
     virtual GrBackendFormat backendFormat() const = 0;
 
-    void setRelease(sk_sp<GrReleaseProcHelper> releaseHelper) {
+    void setRelease(sk_sp<GrRefCntedCallback> releaseHelper) {
         this->onSetRelease(releaseHelper);
         fReleaseHelper = std::move(releaseHelper);
     }
@@ -55,7 +55,7 @@
     typedef void* ReleaseCtx;
     typedef void (*ReleaseProc)(ReleaseCtx);
     void setRelease(ReleaseProc proc, ReleaseCtx ctx) {
-        sk_sp<GrReleaseProcHelper> helper(new GrReleaseProcHelper(proc, ctx));
+        sk_sp<GrRefCntedCallback> helper(new GrRefCntedCallback(proc, ctx));
         this->setRelease(std::move(helper));
     }
 
@@ -132,7 +132,10 @@
 private:
     const char* getResourceType() const override { return "Surface"; }
 
-    virtual void onSetRelease(sk_sp<GrReleaseProcHelper> releaseHelper) = 0;
+    // Unmanaged backends (e.g. Vulkan) may want to specially handle the release proc in order to
+    // ensure it isn't called until GPU work related to the resource is completed.
+    virtual void onSetRelease(sk_sp<GrRefCntedCallback>) {}
+
     void invokeReleaseProc() {
         // Depending on the ref count of fReleaseHelper this may or may not actually trigger the
         // ReleaseProc to be called.
@@ -143,7 +146,7 @@
     int                        fWidth;
     int                        fHeight;
     GrInternalSurfaceFlags     fSurfaceFlags;
-    sk_sp<GrReleaseProcHelper> fReleaseHelper;
+    sk_sp<GrRefCntedCallback>  fReleaseHelper;
 
     typedef GrGpuResource INHERITED;
 };
diff --git a/include/gpu/GrTexture.h b/include/gpu/GrTexture.h
index 542e785..e8f8035 100644
--- a/include/gpu/GrTexture.h
+++ b/include/gpu/GrTexture.h
@@ -49,17 +49,32 @@
     }
 #endif
 
+    /** See addIdleProc. */
+    enum class IdleState {
+        kFlushed,
+        kFinished
+    };
     /**
-     * Installs a proc on this texture. It will be called when the texture becomes "idle". Idle is
-     * defined to mean that the texture has no refs or pending IOs and that GPU I/O operations on
-     * the texture are completed if the backend API disallows deletion of a texture before such
-     * operations occur (e.g. Vulkan). After the idle proc is called it is removed. The idle proc
-     * will always be called before the texture is destroyed, even in unusual shutdown scenarios
-     * (e.g. GrContext::abandonContext()).
+     * Installs a proc on this texture. It will be called when the texture becomes "idle". There
+     * are two types of idle states as indicated by IdleState. For managed backends (e.g. GL where
+     * a driver typically handles CPU/GPU synchronization of resource access) there is no difference
+     * between the two. They both mean "all work related to the resource has been flushed to the
+     * backend API and the texture is not owned outside the resource cache".
+     *
+     * If the API is unmanaged (e.g. Vulkan) then kFinished has the additional constraint that the
+     * work flushed to the GPU is finished.
      */
-    using IdleProc = void(void*);
-    virtual void setIdleProc(IdleProc, void* context) = 0;
-    virtual void* idleContext() const = 0;
+    virtual void addIdleProc(sk_sp<GrRefCntedCallback> idleProc, IdleState) {
+        // This is the default implementation for the managed case where the IdleState can be
+        // ignored. Unmanaged backends, e.g. Vulkan, must override this to consider IdleState.
+        fIdleProcs.push_back(std::move(idleProc));
+    }
+    /** Helper version of addIdleProc that creates the ref-counted wrapper. */
+    void addIdleProc(GrRefCntedCallback::Callback callback,
+                     GrRefCntedCallback::Context context,
+                     IdleState state) {
+        this->addIdleProc(sk_make_sp<GrRefCntedCallback>(callback, context), state);
+    }
 
     /** Access methods that are only to be used within Skia code. */
     inline GrTexturePriv texturePriv();
@@ -70,6 +85,13 @@
 
     virtual bool onStealBackendTexture(GrBackendTexture*, SkImage::BackendTextureReleaseProc*) = 0;
 
+    SkTArray<sk_sp<GrRefCntedCallback>> fIdleProcs;
+
+    void willRemoveLastRefOrPendingIO() override {
+        // We're about to be idle in the resource cache. Do our part to trigger the idle callbacks.
+        fIdleProcs.reset();
+    }
+
 private:
     void computeScratchKey(GrScratchKey*) const override;
     size_t onGpuMemorySize() const override;
diff --git a/include/gpu/GrTypes.h b/include/gpu/GrTypes.h
index 6082d97..1d9ae7c 100644
--- a/include/gpu/GrTypes.h
+++ b/include/gpu/GrTypes.h
@@ -121,7 +121,7 @@
     friend constexpr GrTFlagsMask<X> operator ~(X); \
     friend constexpr X operator |(X, X); \
     friend X& operator |=(X&, X); \
-    friend constexpr bool operator &(X, X);
+    friend constexpr bool operator &(X, X)
 
 ////////////////////////////////////////////////////////////////////////////////
 
@@ -259,6 +259,12 @@
  */
 static const uint32_t kAll_GrBackendState = 0xffffffff;
 
+enum GrFlushFlags {
+    kNone_GrFlushFlags = 0,
+    // flush will wait till all submitted GPU work is finished before returning.
+    kSyncCpu_GrFlushFlag = 0x1,
+};
+
 /**
  * Enum used as return value when flush with semaphores so the client knows whether the semaphores
  * were submitted to GPU or not.
diff --git a/include/gpu/gl/GrGLAssembleHelpers.h b/include/gpu/gl/GrGLAssembleHelpers.h
new file mode 100644
index 0000000..dde92c3
--- /dev/null
+++ b/include/gpu/gl/GrGLAssembleHelpers.h
@@ -0,0 +1,11 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gl/GrGLAssembleInterface.h"
+
+void GrGetEGLQueryAndDisplay(GrEGLQueryStringFn** queryString, GrEGLDisplay* display,
+                             void* ctx, GrGLGetProc get);
diff --git a/include/gpu/gl/GrGLAssembleInterface.h b/include/gpu/gl/GrGLAssembleInterface.h
index 42f842e..2dc1b52 100644
--- a/include/gpu/gl/GrGLAssembleInterface.h
+++ b/include/gpu/gl/GrGLAssembleInterface.h
@@ -28,5 +28,12 @@
  */
 SK_API sk_sp<const GrGLInterface> GrGLMakeAssembledGLESInterface(void *ctx, GrGLGetProc get);
 
+/**
+ * Generic function for creating a GrGLInterface for a WebGL (similar to OpenGL ES) context. It
+ * calls get() to get each function address. ctx is a generic ptr passed to and interpreted by
+ * get().
+ */
+SK_API sk_sp<const GrGLInterface> GrGLMakeAssembledWebGLInterface(void *ctx, GrGLGetProc get);
+
 /** Deprecated version of GrGLMakeAssembledInterface() that returns a bare pointer. */
 SK_API const GrGLInterface* GrGLAssembleInterface(void *ctx, GrGLGetProc get);
diff --git a/include/gpu/gl/GrGLFunctions.h b/include/gpu/gl/GrGLFunctions.h
index c0a681d..6faf1ca 100644
--- a/include/gpu/gl/GrGLFunctions.h
+++ b/include/gpu/gl/GrGLFunctions.h
@@ -132,7 +132,6 @@
 using GrGLProgramParameteriFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint program, GrGLenum pname, GrGLint value);
 using GrGLPushGroupMarkerFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLsizei length, const char* marker);
 using GrGLQueryCounterFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint id, GrGLenum target);
-using GrGLRasterSamplesFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint samples, GrGLboolean fixedsamplelocations);
 using GrGLReadBufferFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum src);
 using GrGLReadPixelsFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height, GrGLenum format, GrGLenum type, GrGLvoid* pixels);
 using GrGLRenderbufferStorageFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum internalformat, GrGLsizei width, GrGLsizei height);
diff --git a/include/gpu/gl/GrGLInterface.h b/include/gpu/gl/GrGLInterface.h
index 56683b6..c2288b4 100644
--- a/include/gpu/gl/GrGLInterface.h
+++ b/include/gpu/gl/GrGLInterface.h
@@ -35,13 +35,6 @@
 SK_API const GrGLInterface* GrGLCreateNativeInterface();
 
 /**
- * Creates a null GrGLInterface that doesn't draw anything. Used for measuring
- * CPU overhead. TODO: We would like to move this to tools/gpu/gl/null but currently
- * Chromium is using it in its unit tests.
- */
-const SK_API GrGLInterface* GrGLCreateNullInterface(bool enableNVPR = false);
-
-/**
  * GrContext uses the following interface to make all calls into OpenGL. When a
  * GrContext is created it is given a GrGLInterface. The interface's function
  * pointers must be valid for the OpenGL context associated with the GrContext.
@@ -195,7 +188,6 @@
         GrGLFunction<GrGLPopGroupMarkerFn> fPopGroupMarker;
         GrGLFunction<GrGLPushGroupMarkerFn> fPushGroupMarker;
         GrGLFunction<GrGLQueryCounterFn> fQueryCounter;
-        GrGLFunction<GrGLRasterSamplesFn> fRasterSamples;
         GrGLFunction<GrGLReadBufferFn> fReadBuffer;
         GrGLFunction<GrGLReadPixelsFn> fReadPixels;
         GrGLFunction<GrGLRenderbufferStorageFn> fRenderbufferStorage;
diff --git a/include/gpu/gl/GrGLTypes.h b/include/gpu/gl/GrGLTypes.h
index cb8d797..f66a092 100644
--- a/include/gpu/gl/GrGLTypes.h
+++ b/include/gpu/gl/GrGLTypes.h
@@ -19,8 +19,35 @@
     kNone_GrGLStandard,
     kGL_GrGLStandard,
     kGLES_GrGLStandard,
+    kWebGL_GrGLStandard,
 };
-static const int kGrGLStandardCnt = 3;
+static const int kGrGLStandardCnt = 4;
+
+// The following allow certain interfaces to be turned off at compile time
+// (for example, to lower code size).
+#if SK_ASSUME_GL_ES
+    #define GR_IS_GR_GL(standard) false
+    #define GR_IS_GR_GL_ES(standard) true
+    #define GR_IS_GR_WEBGL(standard) false
+    #define SK_DISABLE_GL_INTERFACE 1
+    #define SK_DISABLE_WEBGL_INTERFACE 1
+#elif SK_ASSUME_GL
+    #define GR_IS_GR_GL(standard) true
+    #define GR_IS_GR_GL_ES(standard) false
+    #define GR_IS_GR_WEBGL(standard) false
+    #define SK_DISABLE_GL_ES_INTERFACE 1
+    #define SK_DISABLE_WEBGL_INTERFACE 1
+#elif SK_ASSUME_WEBGL
+    #define GR_IS_GR_GL(standard) false
+    #define GR_IS_GR_GL_ES(standard) false
+    #define GR_IS_GR_WEBGL(standard) true
+    #define SK_DISABLE_GL_ES_INTERFACE 1
+    #define SK_DISABLE_GL_INTERFACE 1
+#else
+    #define GR_IS_GR_GL(standard) (kGL_GrGLStandard == standard)
+    #define GR_IS_GR_GL_ES(standard) (kGLES_GrGLStandard == standard)
+    #define GR_IS_GR_WEBGL(standard) (kWebGL_GrGLStandard == standard)
+#endif
 
 ///////////////////////////////////////////////////////////////////////////////
 
diff --git a/include/gpu/mock/GrMockTypes.h b/include/gpu/mock/GrMockTypes.h
index fe2d388..0eccc31 100644
--- a/include/gpu/mock/GrMockTypes.h
+++ b/include/gpu/mock/GrMockTypes.h
@@ -48,7 +48,7 @@
 
     struct ConfigOptions {
         enum Renderability { kNo, kNonMSAA, kMSAA };
-        Renderability fRenderability;
+        Renderability fRenderability = kNo;
         bool fTexturable = false;
     };
 
@@ -68,6 +68,7 @@
     int fMaxVertexSamplers = 0;
     int fMaxFragmentSamplers = 8;
     bool fShaderDerivativeSupport = true;
+    bool fDualSourceBlendingSupport = false;
 
     // GrMockGpu options.
     bool fFailTextureAllocations = false;
diff --git a/include/gpu/vk/GrVkMemoryAllocator.h b/include/gpu/vk/GrVkMemoryAllocator.h
index 62ac4ac..c601258 100644
--- a/include/gpu/vk/GrVkMemoryAllocator.h
+++ b/include/gpu/vk/GrVkMemoryAllocator.h
@@ -83,6 +83,6 @@
     virtual uint64_t totalAllocatedMemory() const = 0;
 };
 
-GR_MAKE_BITFIELD_CLASS_OPS(GrVkMemoryAllocator::AllocationPropertyFlags);
+GR_MAKE_BITFIELD_CLASS_OPS(GrVkMemoryAllocator::AllocationPropertyFlags)
 
 #endif
diff --git a/include/gpu/vk/GrVkTypes.h b/include/gpu/vk/GrVkTypes.h
index 9bd5d61..325fb02 100644
--- a/include/gpu/vk/GrVkTypes.h
+++ b/include/gpu/vk/GrVkTypes.h
@@ -210,6 +210,8 @@
  * to render offscreen textures which will be sampled in draws added to the passed in
  * VkCommandBuffer. If this is done the SkDrawable is in charge of adding the required memory
  * barriers to the queue for the sampled images since the Skia backend will not do this.
+ *
+ * The VkImage is informational only and should not be used or modified in any ways.
  */
 struct GrVkDrawableInfo {
     VkCommandBuffer fSecondaryCommandBuffer;
@@ -217,6 +219,7 @@
     VkRenderPass    fCompatibleRenderPass;
     VkFormat        fFormat;
     VkRect2D*       fDrawBounds;
+    VkImage         fImage;
 };
 
 #endif
diff --git a/include/private/GrCCClipPath.h b/include/private/GrCCClipPath.h
index 8c1e7d4..fdc6a25 100644
--- a/include/private/GrCCClipPath.h
+++ b/include/private/GrCCClipPath.h
@@ -71,11 +71,11 @@
 
     const GrCCAtlas* fAtlas = nullptr;
     SkIVector fDevToAtlasOffset;  // Translation from device space to location in atlas.
-    SkDEBUGCODE(bool fHasAtlas = false);
+    SkDEBUGCODE(bool fHasAtlas = false;)
 
     SkVector fAtlasScale;
     SkVector fAtlasTranslate;
-    SkDEBUGCODE(bool fHasAtlasTransform = false);
+    SkDEBUGCODE(bool fHasAtlasTransform = false;)
 };
 
 #endif
diff --git a/include/private/GrColor.h b/include/private/GrColor.h
index a8fd5f7..7c52aa6 100644
--- a/include/private/GrColor.h
+++ b/include/private/GrColor.h
@@ -71,12 +71,10 @@
     return value * ONE_OVER_255;
 }
 
-/** Returns true if all channels are in [0, 1]. Used to pick vertex attribute types. */
+/** Used to pick vertex attribute types. */
 static inline bool SkPMColor4fFitsInBytes(const SkPMColor4f& color) {
-    SkASSERT(color.fA >= 0.0f && color.fA <= 1.0f);
-    return color.fR >= 0.0f && color.fR <= 1.0f &&
-           color.fG >= 0.0f && color.fG <= 1.0f &&
-           color.fB >= 0.0f && color.fB <= 1.0f;
+    // Might want to instead check that the components are [0...a] instead of [0...1]?
+    return color.fitsInBytes();
 }
 
 static inline uint64_t SkPMColor4f_toFP16(const SkPMColor4f& color) {
diff --git a/include/private/GrContext_Base.h b/include/private/GrContext_Base.h
index ddf0084..81c885f 100644
--- a/include/private/GrContext_Base.h
+++ b/include/private/GrContext_Base.h
@@ -35,7 +35,9 @@
 protected:
     friend class GrBaseContextPriv; // for hidden functions
 
-    GrContext_Base(GrBackendApi backend, const GrContextOptions& options, uint32_t uniqueID);
+    GrContext_Base(GrBackendApi backend, const GrContextOptions& options, uint32_t contextID);
+
+    virtual bool init(sk_sp<const GrCaps>, sk_sp<GrSkSLFPFactoryCache>);
 
     /**
      * An identifier for this context. The id is used by all compatible contexts. For example,
@@ -46,23 +48,26 @@
      */
     uint32_t contextID() const { return fContextID; }
 
+    bool matches(GrContext_Base* candidate) const {
+        return candidate->contextID() == this->contextID();
+    }
+
     /*
      * The options in effect for this context
      */
     const GrContextOptions& options() const { return fOptions; }
 
+    bool explicitlyAllocateGPUResources() const { return true; }
+
     const GrCaps* caps() const;
     sk_sp<const GrCaps> refCaps() const;
 
     sk_sp<GrSkSLFPFactoryCache> fpFactoryCache();
 
-    GrContext_Base* asBaseContext() { return this; }
     virtual GrImageContext* asImageContext() { return nullptr; }
     virtual GrRecordingContext* asRecordingContext() { return nullptr; }
     virtual GrContext* asDirectContext() { return nullptr; }
 
-    virtual bool init(sk_sp<const GrCaps>, sk_sp<GrSkSLFPFactoryCache>);
-
 private:
     const GrBackendApi          fBackend;
     const GrContextOptions      fOptions;
diff --git a/include/private/GrImageContext.h b/include/private/GrImageContext.h
index bc2be9a..324feef 100644
--- a/include/private/GrImageContext.h
+++ b/include/private/GrImageContext.h
@@ -9,8 +9,10 @@
 #define GrImageContext_DEFINED
 
 #include "GrContext_Base.h"
+#include "../private/GrSingleOwner.h"
 
 class GrImageContextPriv;
+class GrProxyProvider;
 
 class SK_API GrImageContext : public GrContext_Base {
 public:
@@ -23,11 +25,28 @@
 protected:
     friend class GrImageContextPriv; // for hidden functions
 
-    GrImageContext(GrBackendApi, const GrContextOptions&, uint32_t uniqueID);
+    GrImageContext(GrBackendApi, const GrContextOptions&, uint32_t contextID);
+
+    virtual void abandonContext();
+    bool abandoned() const;
+
+    GrProxyProvider* proxyProvider() { return fProxyProvider.get(); }
+    const GrProxyProvider* proxyProvider() const { return fProxyProvider.get(); }
+
+    /** This is only useful for debug purposes */
+    GrSingleOwner* singleOwner() const { return &fSingleOwner; }
 
     GrImageContext* asImageContext() override { return this; }
 
 private:
+    std::unique_ptr<GrProxyProvider> fProxyProvider;
+    bool                             fAbandoned = false;
+
+    // In debug builds we guard against improper thread handling
+    // This guard is passed to the GrDrawingManager and, from there to all the
+    // GrRenderTargetContexts.  It is also passed to the GrResourceProvider and SkGpuDevice.
+    mutable GrSingleOwner            fSingleOwner;
+
     typedef GrContext_Base INHERITED;
 };
 
diff --git a/include/private/GrOpList.h b/include/private/GrOpList.h
index 6bd9eef..72ef07e 100644
--- a/include/private/GrOpList.h
+++ b/include/private/GrOpList.h
@@ -18,6 +18,7 @@
 class GrCaps;
 class GrOpFlushState;
 class GrOpMemoryPool;
+class GrRecordingContext;
 class GrRenderTargetOpList;
 class GrResourceAllocator;
 class GrResourceProvider;
@@ -39,7 +40,7 @@
     void prepare(GrOpFlushState* flushState);
     bool execute(GrOpFlushState* flushState) { return this->onExecute(flushState); }
 
-    virtual bool copySurface(GrContext*,
+    virtual bool copySurface(GrRecordingContext*,
                              GrSurfaceProxy* dst,
                              GrSurfaceProxy* src,
                              const SkIRect& srcRect,
@@ -112,10 +113,20 @@
 private:
     friend class GrDrawingManager; // for resetFlag, TopoSortTraits & gatherProxyIntervals
 
+    virtual bool onIsUsed(GrSurfaceProxy*) const = 0;
+
+    bool isUsed(GrSurfaceProxy* proxy) const {
+        if (proxy == fTarget.get()) {
+            return true;
+        }
+
+        return this->onIsUsed(proxy);
+    }
+
     void addDependency(GrOpList* dependedOn);
     void addDependent(GrOpList* dependent);
-    SkDEBUGCODE(bool isDependedent(const GrOpList* dependent) const);
-    SkDEBUGCODE(void validate() const);
+    SkDEBUGCODE(bool isDependedent(const GrOpList* dependent) const;)
+    SkDEBUGCODE(void validate() const;)
     void closeThoseWhoDependOnMe(const GrCaps&);
 
     // Remove all Ops which reference proxies that are not instantiated.
diff --git a/include/private/GrRecordingContext.h b/include/private/GrRecordingContext.h
index b7e035b..48e673f 100644
--- a/include/private/GrRecordingContext.h
+++ b/include/private/GrRecordingContext.h
@@ -8,9 +8,16 @@
 #ifndef GrRecordingContext_DEFINED
 #define GrRecordingContext_DEFINED
 
+#include "GrAuditTrail.h"
 #include "GrImageContext.h"
+#include "SkRefCnt.h"
 
+class GrDrawingManager;
+class GrOnFlushCallbackObject;
+class GrOpMemoryPool;
 class GrRecordingContextPriv;
+class GrStrikeCache;
+class GrTextBlobCache;
 
 class SK_API GrRecordingContext : public GrImageContext {
 public:
@@ -23,11 +30,91 @@
 protected:
     friend class GrRecordingContextPriv; // for hidden functions
 
-    GrRecordingContext(GrBackendApi, const GrContextOptions&, uint32_t uniqueID);
+    GrRecordingContext(GrBackendApi, const GrContextOptions&, uint32_t contextID);
+    bool init(sk_sp<const GrCaps>, sk_sp<GrSkSLFPFactoryCache>) override;
+    void setupDrawingManager(bool explicitlyAllocate, bool sortOpLists);
+
+    void abandonContext() override;
+
+    GrDrawingManager* drawingManager();
+
+    sk_sp<GrOpMemoryPool> refOpMemoryPool();
+    GrOpMemoryPool* opMemoryPool();
+
+    GrStrikeCache* getGrStrikeCache() { return fStrikeCache.get(); }
+    GrTextBlobCache* getTextBlobCache();
+    const GrTextBlobCache* getTextBlobCache() const;
+
+    /**
+     * Registers an object for flush-related callbacks. (See GrOnFlushCallbackObject.)
+     *
+     * NOTE: the drawing manager tracks this object as a raw pointer; it is up to the caller to
+     * ensure its lifetime is tied to that of the context.
+     */
+    void addOnFlushCallbackObject(GrOnFlushCallbackObject*);
+
+    sk_sp<GrSurfaceContext> makeWrappedSurfaceContext(sk_sp<GrSurfaceProxy>,
+                                                      sk_sp<SkColorSpace> = nullptr,
+                                                      const SkSurfaceProps* = nullptr);
+
+    sk_sp<GrSurfaceContext> makeDeferredSurfaceContext(const GrBackendFormat&,
+                                                       const GrSurfaceDesc&,
+                                                       GrSurfaceOrigin,
+                                                       GrMipMapped,
+                                                       SkBackingFit,
+                                                       SkBudgeted,
+                                                       sk_sp<SkColorSpace> colorSpace = nullptr,
+                                                       const SkSurfaceProps* = nullptr);
+
+    /*
+     * Create a new render target context backed by a deferred-style
+     * GrRenderTargetProxy. We guarantee that "asTextureProxy" will succeed for
+     * renderTargetContexts created via this entry point.
+     */
+    sk_sp<GrRenderTargetContext> makeDeferredRenderTargetContext(
+                                            const GrBackendFormat& format,
+                                            SkBackingFit fit,
+                                            int width, int height,
+                                            GrPixelConfig config,
+                                            sk_sp<SkColorSpace> colorSpace,
+                                            int sampleCnt = 1,
+                                            GrMipMapped = GrMipMapped::kNo,
+                                            GrSurfaceOrigin origin = kBottomLeft_GrSurfaceOrigin,
+                                            const SkSurfaceProps* surfaceProps = nullptr,
+                                            SkBudgeted = SkBudgeted::kYes);
+
+    /*
+     * This method will attempt to create a renderTargetContext that has, at least, the number of
+     * channels and precision per channel as requested in 'config' (e.g., A8 and 888 can be
+     * converted to 8888). It may also swizzle the channels (e.g., BGRA -> RGBA).
+     * SRGB-ness will be preserved.
+     */
+    sk_sp<GrRenderTargetContext> makeDeferredRenderTargetContextWithFallback(
+                                            const GrBackendFormat& format,
+                                            SkBackingFit fit,
+                                            int width, int height,
+                                            GrPixelConfig config,
+                                            sk_sp<SkColorSpace> colorSpace,
+                                            int sampleCnt = 1,
+                                            GrMipMapped = GrMipMapped::kNo,
+                                            GrSurfaceOrigin origin = kBottomLeft_GrSurfaceOrigin,
+                                            const SkSurfaceProps* surfaceProps = nullptr,
+                                            SkBudgeted budgeted = SkBudgeted::kYes);
+
+    GrAuditTrail* auditTrail() { return &fAuditTrail; }
 
     GrRecordingContext* asRecordingContext() override { return this; }
 
 private:
+    std::unique_ptr<GrDrawingManager> fDrawingManager;
+    // All the GrOp-derived classes use this pool.
+    sk_sp<GrOpMemoryPool>             fOpMemoryPool;
+
+    std::unique_ptr<GrStrikeCache>    fStrikeCache;
+    std::unique_ptr<GrTextBlobCache>  fTextBlobCache;
+
+    GrAuditTrail                      fAuditTrail;
+
     typedef GrImageContext INHERITED;
 };
 
diff --git a/include/private/GrRenderTargetProxy.h b/include/private/GrRenderTargetProxy.h
index 3cedf74..ecd6772 100644
--- a/include/private/GrRenderTargetProxy.h
+++ b/include/private/GrRenderTargetProxy.h
@@ -24,7 +24,7 @@
     const GrRenderTargetProxy* asRenderTargetProxy() const override { return this; }
 
     // Actually instantiate the backing rendertarget, if necessary.
-    bool instantiate(GrResourceProvider*) override;
+    bool instantiate(GrResourceProvider*, bool dontForceNoPendingIO = false) override;
 
     GrFSAAType fsaaType() const {
         if (fSampleCnt <= 1) {
@@ -71,6 +71,8 @@
     GrRenderTargetProxy(const GrCaps&, const GrBackendFormat&, const GrSurfaceDesc&,
                         GrSurfaceOrigin, SkBackingFit, SkBudgeted, GrInternalSurfaceFlags);
 
+    enum class WrapsVkSecondaryCB : bool { kNo = false, kYes = true };
+
     // Lazy-callback version
     // There are two main use cases for lazily-instantiated proxies:
     //   basic knowledge - width, height, config, samples, origin are known
@@ -83,10 +85,10 @@
     // know the final size until flush time.
     GrRenderTargetProxy(LazyInstantiateCallback&&, LazyInstantiationType lazyType,
                         const GrBackendFormat&, const GrSurfaceDesc&, GrSurfaceOrigin,
-                        SkBackingFit, SkBudgeted, GrInternalSurfaceFlags);
+                        SkBackingFit, SkBudgeted, GrInternalSurfaceFlags,
+                        WrapsVkSecondaryCB wrapsVkSecondaryCB);
 
     // Wrapped version
-    enum class WrapsVkSecondaryCB : bool { kNo = false, kYes = true };
     GrRenderTargetProxy(sk_sp<GrSurface>, GrSurfaceOrigin,
                         WrapsVkSecondaryCB wrapsVkSecondaryCB = WrapsVkSecondaryCB::kNo);
 
diff --git a/include/private/GrSurfaceProxy.h b/include/private/GrSurfaceProxy.h
index 1c7b01f..aeb48f2 100644
--- a/include/private/GrSurfaceProxy.h
+++ b/include/private/GrSurfaceProxy.h
@@ -12,12 +12,13 @@
 #include "GrBackendSurface.h"
 #include "GrGpuResource.h"
 #include "GrSurface.h"
-
+#include "GrTexture.h"
 #include "SkRect.h"
 
 class GrCaps;
+class GrContext_Base;
 class GrOpList;
-class GrProxyProvider;
+class GrRecordingContext;
 class GrRenderTargetOpList;
 class GrRenderTargetProxy;
 class GrResourceProvider;
@@ -150,16 +151,36 @@
         // have forwarded on the unref call that got us here.
     }
 
+    // Privileged method that allows going from ref count = 0 to ref count = 1.
+    void addInitialRef(GrResourceCache* cache) const {
+        this->validate();
+        ++fRefCnt;
+        if (fTarget) {
+            fTarget->proxyAccess().ref(cache);
+        }
+    }
+
     // This GrIORefProxy was deferred before but has just been instantiated. To
     // make all the reffing & unreffing work out we now need to transfer any deferred
     // refs & unrefs to the new GrSurface
     void transferRefs() {
         SkASSERT(fTarget);
+        // Make sure we're going to take some ownership of our target.
+        SkASSERT(fRefCnt > 0 || fPendingReads > 0 || fPendingWrites > 0);
 
-        SkASSERT(fTarget->fRefCnt > 0);
-        fTarget->fRefCnt += (fRefCnt-1); // don't xfer the proxy's creation ref
+        // Transfer pending read/writes first so that if we decrement the target's ref cnt we don't
+        // cause a purge of the target.
         fTarget->fPendingReads += fPendingReads;
         fTarget->fPendingWrites += fPendingWrites;
+        SkASSERT(fTarget->fRefCnt > 0);
+        SkASSERT(fRefCnt >= 0);
+        // Don't xfer the proxy's creation ref. If we're going to subtract a ref do it via unref()
+        // so that proper cache notifications occur.
+        if (!fRefCnt) {
+            fTarget->unref();
+        } else {
+            fTarget->fRefCnt += (fRefCnt - 1);
+        }
     }
 
     int32_t internalGetProxyRefCnt() const {
@@ -203,6 +224,45 @@
 
 class GrSurfaceProxy : public GrIORefProxy {
 public:
+    /**
+     * Some lazy proxy callbacks want to set their own (or no key) on the GrSurfaces they return.
+     * Others want the GrSurface's key to be kept in sync with the proxy's key. This enum controls
+     * the key relationship between proxies and their targets.
+     */
+    enum class LazyInstantiationKeyMode {
+        /**
+         * Don't key the GrSurface with the proxy's key. The lazy instantiation callback is free to
+         * return a GrSurface that already has a unique key unrelated to the proxy's key.
+         */
+        kUnsynced,
+        /**
+         * Keep the GrSurface's unique key in sync with the proxy's unique key. The GrSurface
+         * returned from the lazy instantiation callback must not have a unique key or have the same
+         * same unique key as the proxy. If the proxy is later assigned a key it is in turn assigned
+         * to the GrSurface.
+         */
+        kSynced
+    };
+
+    struct LazyInstantiationResult {
+        LazyInstantiationResult() = default;
+        LazyInstantiationResult(const LazyInstantiationResult&) = default;
+        LazyInstantiationResult(LazyInstantiationResult&& that) = default;
+        LazyInstantiationResult(sk_sp<GrSurface> surf,
+                                LazyInstantiationKeyMode mode = LazyInstantiationKeyMode::kSynced)
+                : fSurface(std::move(surf)), fKeyMode(mode) {}
+        LazyInstantiationResult(sk_sp<GrTexture> tex)
+                : LazyInstantiationResult(sk_sp<GrSurface>(std::move(tex))) {}
+
+        LazyInstantiationResult& operator=(const LazyInstantiationResult&) = default;
+        LazyInstantiationResult& operator=(LazyInstantiationResult&&) = default;
+
+        sk_sp<GrSurface> fSurface;
+        LazyInstantiationKeyMode fKeyMode = LazyInstantiationKeyMode::kSynced;
+    };
+
+    using LazyInstantiateCallback = std::function<LazyInstantiationResult(GrResourceProvider*)>;
+
     enum class LazyInstantiationType {
         kSingleUse,      // Instantiation callback is allowed to be called only once.
         kMultipleUse,    // Instantiation callback can be called multiple times.
@@ -320,7 +380,8 @@
         return fUniqueID;
     }
 
-    virtual bool instantiate(GrResourceProvider* resourceProvider) = 0;
+    virtual bool instantiate(GrResourceProvider* resourceProvider,
+                             bool dontForceNoPendingIO = false) = 0;
 
     void deinstantiate();
 
@@ -396,25 +457,33 @@
 
     // Helper function that creates a temporary SurfaceContext to perform the copy
     // The copy is is not a render target and not multisampled.
-    static sk_sp<GrTextureProxy> Copy(GrContext*, GrSurfaceProxy* src, GrMipMapped, SkIRect srcRect,
-                                      SkBackingFit, SkBudgeted);
+    static sk_sp<GrTextureProxy> Copy(GrRecordingContext*, GrSurfaceProxy* src, GrMipMapped,
+                                      SkIRect srcRect, SkBackingFit, SkBudgeted);
 
     // Copy the entire 'src'
-    static sk_sp<GrTextureProxy> Copy(GrContext*, GrSurfaceProxy* src, GrMipMapped, SkBackingFit,
-                                      SkBudgeted budgeted);
+    static sk_sp<GrTextureProxy> Copy(GrRecordingContext*, GrSurfaceProxy* src, GrMipMapped,
+                                      SkBackingFit, SkBudgeted);
 
     // Test-only entry point - should decrease in use as proxies propagate
-    static sk_sp<GrSurfaceContext> TestCopy(GrContext* context, const GrSurfaceDesc& dstDesc,
+    static sk_sp<GrSurfaceContext> TestCopy(GrRecordingContext* context,
+                                            const GrSurfaceDesc& dstDesc,
                                             GrSurfaceOrigin, GrSurfaceProxy* srcProxy);
 
     bool isWrapped_ForTesting() const;
 
-    SkDEBUGCODE(void validate(GrContext*) const;)
+    SkDEBUGCODE(void validate(GrContext_Base*) const;)
 
     // Provides access to functions that aren't part of the public API.
     inline GrSurfaceProxyPriv priv();
     inline const GrSurfaceProxyPriv priv() const;
 
+    /**
+     * Provides privileged access to select callers to be able to add a ref to a GrSurfaceProxy
+     * with zero refs.
+     */
+    class FirstRefAccess;
+    inline FirstRefAccess firstRefAccess();
+
     GrInternalSurfaceFlags testingOnly_getFlags() const;
 
 protected:
@@ -427,8 +496,6 @@
         // Note: this ctor pulls a new uniqueID from the same pool at the GrGpuResources
     }
 
-    using LazyInstantiateCallback = std::function<sk_sp<GrSurface>(GrResourceProvider*)>;
-
     // Lazy-callback version
     GrSurfaceProxy(LazyInstantiateCallback&&, LazyInstantiationType,
                    const GrBackendFormat& format, const GrSurfaceDesc&, GrSurfaceOrigin,
@@ -460,10 +527,12 @@
     void assign(sk_sp<GrSurface> surface);
 
     sk_sp<GrSurface> createSurfaceImpl(GrResourceProvider*, int sampleCnt, bool needsStencil,
-                                       GrSurfaceDescFlags, GrMipMapped) const;
+                                       GrSurfaceDescFlags, GrMipMapped,
+                                       bool forceNoPendingIO) const;
 
     bool instantiateImpl(GrResourceProvider* resourceProvider, int sampleCnt, bool needsStencil,
-                         GrSurfaceDescFlags descFlags, GrMipMapped, const GrUniqueKey*);
+                         GrSurfaceDescFlags descFlags, GrMipMapped, const GrUniqueKey*,
+                         bool dontForceNoPendingIO);
 
     // In many cases these flags aren't actually known until the proxy has been instantiated.
     // However, Ganesh frequently needs to change its behavior based on these settings. For
@@ -525,4 +594,25 @@
     typedef GrIORefProxy INHERITED;
 };
 
+class GrSurfaceProxy::FirstRefAccess {
+private:
+    void ref(GrResourceCache* cache) { fProxy->addInitialRef(cache); }
+
+    FirstRefAccess(GrSurfaceProxy* proxy) : fProxy(proxy) {}
+
+    // No taking addresses of this type.
+    const FirstRefAccess* operator&() const = delete;
+    FirstRefAccess* operator&() = delete;
+
+    GrSurfaceProxy* fProxy;
+
+    friend class GrSurfaceProxy;
+    friend class GrProxyProvider;
+    friend class GrDeinstantiateProxyTracker;
+};
+
+inline GrSurfaceProxy::FirstRefAccess GrSurfaceProxy::firstRefAccess() {
+    return FirstRefAccess(this);
+}
+
 #endif
diff --git a/include/private/GrTextureProxy.h b/include/private/GrTextureProxy.h
index 71d581c..6851cde 100644
--- a/include/private/GrTextureProxy.h
+++ b/include/private/GrTextureProxy.h
@@ -25,7 +25,7 @@
     const GrTextureProxy* asTextureProxy() const override { return this; }
 
     // Actually instantiate the backing texture, if necessary
-    bool instantiate(GrResourceProvider*) override;
+    bool instantiate(GrResourceProvider*, bool dontForceNoPendingIO = false) override;
 
     GrSamplerState::Filter highestFilterMode() const;
 
@@ -57,7 +57,7 @@
      */
     const GrUniqueKey& getUniqueKey() const {
 #ifdef SK_DEBUG
-        if (fTarget && fUniqueKey.isValid()) {
+        if (fTarget && fUniqueKey.isValid() && fSyncTargetKey) {
             SkASSERT(fTarget->getUniqueKey().isValid());
             // It is possible for a non-keyed proxy to have a uniquely keyed resource assigned to
             // it. This just means that a future user of the resource will be filling it with unique
@@ -83,9 +83,10 @@
 
 protected:
     // DDL TODO: rm the GrSurfaceProxy friending
-    friend class GrSurfaceProxy; // for ctors
-    friend class GrProxyProvider; // for ctors
+    friend class GrSurfaceProxy;   // for ctors
+    friend class GrProxyProvider;  // for ctors
     friend class GrTextureProxyPriv;
+    friend class GrSurfaceProxyPriv;  // ability to change key sync state after lazy instantiation.
 
     // Deferred version - when constructed with data the origin is always kTopLeft.
     GrTextureProxy(const GrBackendFormat&, const GrSurfaceDesc& srcDesc, GrMipMapped, SkBackingFit,
@@ -116,6 +117,8 @@
 
     sk_sp<GrSurface> createSurface(GrResourceProvider*) const override;
 
+    void setTargetKeySync(bool sync) { fSyncTargetKey = sync; }
+
 private:
     // WARNING: Be careful when adding or removing fields here. ASAN is likely to trigger warnings
     // when instantiating GrTextureRenderTargetProxy. The std::function in GrSurfaceProxy makes
@@ -126,6 +129,7 @@
     // address of other types, leading to this problem.
 
     GrMipMapped      fMipMapped;
+    bool             fSyncTargetKey = true;  // Should target's unique key be sync'ed with ours.
 
     GrUniqueKey      fUniqueKey;
     GrProxyProvider* fProxyProvider; // only set when fUniqueKey is valid
diff --git a/include/private/GrTypesPriv.h b/include/private/GrTypesPriv.h
index 7900336..d34d1de 100644
--- a/include/private/GrTypesPriv.h
+++ b/include/private/GrTypesPriv.h
@@ -37,11 +37,16 @@
 enum GrPixelConfig {
     kUnknown_GrPixelConfig,
     kAlpha_8_GrPixelConfig,
+    kAlpha_8_as_Alpha_GrPixelConfig,
+    kAlpha_8_as_Red_GrPixelConfig,
     kGray_8_GrPixelConfig,
+    kGray_8_as_Lum_GrPixelConfig,
+    kGray_8_as_Red_GrPixelConfig,
     kRGB_565_GrPixelConfig,
     kRGBA_4444_GrPixelConfig,
     kRGBA_8888_GrPixelConfig,
     kRGB_888_GrPixelConfig,
+    kRGB_888X_GrPixelConfig,
     kRG_88_GrPixelConfig,
     kBGRA_8888_GrPixelConfig,
     kSRGBA_8888_GrPixelConfig,
@@ -50,17 +55,12 @@
     kRGBA_float_GrPixelConfig,
     kRG_float_GrPixelConfig,
     kAlpha_half_GrPixelConfig,
+    kAlpha_half_as_Red_GrPixelConfig,
     kRGBA_half_GrPixelConfig,
+    kRGBA_half_Clamped_GrPixelConfig,
     kRGB_ETC1_GrPixelConfig,
 
-    /** For internal usage. */
-    kPrivateConfig1_GrPixelConfig,
-    kPrivateConfig2_GrPixelConfig,
-    kPrivateConfig3_GrPixelConfig,
-    kPrivateConfig4_GrPixelConfig,
-    kPrivateConfig5_GrPixelConfig,
-
-    kLast_GrPixelConfig = kPrivateConfig5_GrPixelConfig
+    kLast_GrPixelConfig = kRGB_ETC1_GrPixelConfig
 };
 static const int kGrPixelConfigCnt = kLast_GrPixelConfig + 1;
 
@@ -241,8 +241,8 @@
 };
 
 struct GrMipLevel {
-    const void* fPixels;
-    size_t fRowBytes;
+    const void* fPixels = nullptr;
+    size_t fRowBytes = 0;
 };
 
 /**
@@ -313,13 +313,31 @@
 };
 
 /**
- * Not all drawing code paths support using mixed samples when available and instead use
- * coverage-based aa.
+ * Some pixel configs are inherently clamped to [0,1], some are allowed to go outside that range,
+ * and some are FP but manually clamped in the XP.
  */
-enum class GrAllowMixedSamples : bool { kNo = false, kYes = true };
+enum class GrClampType {
+    kAuto,    // Normalized, fixed-point configs
+    kManual,  // Clamped FP configs
+    kNone,    // Normal (unclamped) FP configs
+};
 
-GrAAType GrChooseAAType(GrAA, GrFSAAType, GrAllowMixedSamples, const GrCaps&);
-
+/**
+ * A number of rectangle/quadrilateral drawing APIs can control anti-aliasing on a per edge basis.
+ * These masks specify which edges are AA'ed. The intent for this is to support tiling with seamless
+ * boundaries, where the inner edges are non-AA and the outer edges are AA. Regular draws (where AA
+ * is specified by GrAA) is almost equivalent to kNone or kAll, with the exception of how MSAA is
+ * handled.
+ *
+ * When tiling and there is MSAA, mixed edge rectangles are processed with MSAA, so in order for the
+ * tiled edges to remain seamless, inner tiles with kNone must also be processed with MSAA. In
+ * regular drawing, however, kNone should disable MSAA (if it's supported) to match the expected
+ * appearance.
+ *
+ * Therefore, APIs that use per-edge AA flags also take a GrAA value so that they can differentiate
+ * between the regular and tiling use case behaviors. Tiling operations should always pass
+ * GrAA::kYes while regular options should pass GrAA based on the SkPaint's anti-alias state.
+ */
 enum class GrQuadAAFlags {
     kLeft   = SkCanvas::kLeft_QuadAAFlag,
     kTop    = SkCanvas::kTop_QuadAAFlag,
@@ -413,7 +431,7 @@
     kGeometry_GrShaderFlag = 1 << kGeometry_GrShaderType,
     kFragment_GrShaderFlag = 1 << kFragment_GrShaderType
 };
-GR_MAKE_BITFIELD_OPS(GrShaderFlags);
+GR_MAKE_BITFIELD_OPS(GrShaderFlags)
 
 /**
  * Precisions of shader language variables. Not all shading languages support precisions or actually
@@ -625,110 +643,6 @@
     return false;
 }
 
-static inline bool GrSLTypeAcceptsPrecision(GrSLType type) {
-    switch (type) {
-        case kTexture2DSampler_GrSLType:
-        case kTextureExternalSampler_GrSLType:
-        case kTexture2DRectSampler_GrSLType:
-            return true;
-
-        case kVoid_GrSLType:
-        case kBool_GrSLType:
-        case kByte_GrSLType:
-        case kByte2_GrSLType:
-        case kByte3_GrSLType:
-        case kByte4_GrSLType:
-        case kUByte_GrSLType:
-        case kUByte2_GrSLType:
-        case kUByte3_GrSLType:
-        case kUByte4_GrSLType:
-        case kShort_GrSLType:
-        case kShort2_GrSLType:
-        case kShort3_GrSLType:
-        case kShort4_GrSLType:
-        case kUShort_GrSLType:
-        case kUShort2_GrSLType:
-        case kUShort3_GrSLType:
-        case kUShort4_GrSLType:
-        case kFloat_GrSLType:
-        case kFloat2_GrSLType:
-        case kFloat3_GrSLType:
-        case kFloat4_GrSLType:
-        case kFloat2x2_GrSLType:
-        case kFloat3x3_GrSLType:
-        case kFloat4x4_GrSLType:
-        case kHalf_GrSLType:
-        case kHalf2_GrSLType:
-        case kHalf3_GrSLType:
-        case kHalf4_GrSLType:
-        case kHalf2x2_GrSLType:
-        case kHalf3x3_GrSLType:
-        case kHalf4x4_GrSLType:
-        case kInt_GrSLType:
-        case kInt2_GrSLType:
-        case kInt3_GrSLType:
-        case kInt4_GrSLType:
-        case kUint_GrSLType:
-        case kUint2_GrSLType:
-            return false;
-    }
-    SK_ABORT("Unexpected type");
-    return false;
-}
-
-// temporarily accepting (but ignoring) precision modifiers on the new types; this will be killed
-// in a future CL
-static inline bool GrSLTypeTemporarilyAcceptsPrecision(GrSLType type) {
-    switch (type) {
-        case kShort_GrSLType:
-        case kUShort_GrSLType:
-        case kFloat_GrSLType:
-        case kFloat2_GrSLType:
-        case kFloat3_GrSLType:
-        case kFloat4_GrSLType:
-        case kFloat2x2_GrSLType:
-        case kFloat3x3_GrSLType:
-        case kFloat4x4_GrSLType:
-        case kHalf_GrSLType:
-        case kHalf2_GrSLType:
-        case kHalf3_GrSLType:
-        case kHalf4_GrSLType:
-        case kHalf2x2_GrSLType:
-        case kHalf3x3_GrSLType:
-        case kHalf4x4_GrSLType:
-        case kInt_GrSLType:
-        case kInt2_GrSLType:
-        case kInt3_GrSLType:
-        case kInt4_GrSLType:
-        case kUint_GrSLType:
-        case kUint2_GrSLType:
-        case kTexture2DSampler_GrSLType:
-        case kTextureExternalSampler_GrSLType:
-        case kTexture2DRectSampler_GrSLType:
-            return true;
-
-        case kVoid_GrSLType:
-        case kBool_GrSLType:
-        case kByte_GrSLType:
-        case kByte2_GrSLType:
-        case kByte3_GrSLType:
-        case kByte4_GrSLType:
-        case kUByte_GrSLType:
-        case kUByte2_GrSLType:
-        case kUByte3_GrSLType:
-        case kUByte4_GrSLType:
-        case kShort2_GrSLType:
-        case kShort3_GrSLType:
-        case kShort4_GrSLType:
-        case kUShort2_GrSLType:
-        case kUShort3_GrSLType:
-        case kUShort4_GrSLType:
-            return false;
-    }
-    SK_ABORT("Unexpected type");
-    return false;
-}
-
 //////////////////////////////////////////////////////////////////////////////
 
 /**
@@ -829,19 +743,6 @@
 };
 static const int kGrGpuBufferTypeCount = static_cast<int>(GrGpuBufferType::kXferGpuToCpu) + 1;
 
-static inline bool GrBufferTypeIsVertexOrIndex(GrGpuBufferType type) {
-    switch (type) {
-        case GrGpuBufferType::kVertex:
-        case GrGpuBufferType::kIndex:
-            return true;
-        case GrGpuBufferType::kXferCpuToGpu:
-        case GrGpuBufferType::kXferGpuToCpu:
-            return false;
-    }
-    SK_ABORT("Unexpected GrGpuBufferType.");
-    return false;
-}
-
 /**
  * Provides a performance hint regarding the frequency at which a data store will be accessed.
  */
@@ -932,7 +833,9 @@
     kSmall             = 1 << 6,
     kTessellating      = 1 << 7,
 
-    kAll               = (kTessellating | (kTessellating - 1))
+    kAll               = (kTessellating | (kTessellating - 1)),
+    kDefault           = kAll & ~kCoverageCounting
+
 };
 
 /**
@@ -947,16 +850,6 @@
 GR_MAKE_BITFIELD_CLASS_OPS(GpuPathRenderers)
 
 /**
- * We want to extend the GrPixelConfig enum to add cases for dealing with alpha_8 which is
- * internally either alpha8 or red8. Also for Gray_8 which can be luminance_8 or red_8.
- */
-static constexpr GrPixelConfig kAlpha_8_as_Alpha_GrPixelConfig = kPrivateConfig1_GrPixelConfig;
-static constexpr GrPixelConfig kAlpha_8_as_Red_GrPixelConfig = kPrivateConfig2_GrPixelConfig;
-static constexpr GrPixelConfig kAlpha_half_as_Red_GrPixelConfig = kPrivateConfig3_GrPixelConfig;
-static constexpr GrPixelConfig kGray_8_as_Lum_GrPixelConfig = kPrivateConfig4_GrPixelConfig;
-static constexpr GrPixelConfig kGray_8_as_Red_GrPixelConfig = kPrivateConfig5_GrPixelConfig;
-
-/**
  * Refers to the encoding of a GPU buffer as it will be interpreted by the GPU when sampling and
  * blending.
  */
@@ -991,6 +884,7 @@
         case kRGB_565_GrPixelConfig:
         case kRGBA_4444_GrPixelConfig:
         case kRGB_888_GrPixelConfig:
+        case kRGB_888X_GrPixelConfig:
         case kRG_88_GrPixelConfig:
         case kRGBA_8888_GrPixelConfig:
         case kBGRA_8888_GrPixelConfig:
@@ -1000,6 +894,7 @@
         case kAlpha_half_GrPixelConfig:
         case kAlpha_half_as_Red_GrPixelConfig:
         case kRGBA_half_GrPixelConfig:
+        case kRGBA_half_Clamped_GrPixelConfig:
         case kRGB_ETC1_GrPixelConfig:
             return GrSRGBEncoded::kNo;
     }
@@ -1028,12 +923,14 @@
             return 2;
         case kRGBA_8888_GrPixelConfig:
         case kRGB_888_GrPixelConfig:  // Assuming GPUs store this 4-byte aligned.
+        case kRGB_888X_GrPixelConfig:
         case kBGRA_8888_GrPixelConfig:
         case kSRGBA_8888_GrPixelConfig:
         case kSBGRA_8888_GrPixelConfig:
         case kRGBA_1010102_GrPixelConfig:
             return 4;
         case kRGBA_half_GrPixelConfig:
+        case kRGBA_half_Clamped_GrPixelConfig:
             return 8;
         case kRGBA_float_GrPixelConfig:
             return 16;
@@ -1051,6 +948,7 @@
     switch (config) {
         case kRGB_565_GrPixelConfig:
         case kRGB_888_GrPixelConfig:
+        case kRGB_888X_GrPixelConfig:
         case kRG_88_GrPixelConfig:
         case kGray_8_GrPixelConfig:
         case kGray_8_as_Lum_GrPixelConfig:
@@ -1070,6 +968,7 @@
         case kSBGRA_8888_GrPixelConfig:
         case kRGBA_1010102_GrPixelConfig:
         case kRGBA_half_GrPixelConfig:
+        case kRGBA_half_Clamped_GrPixelConfig:
         case kRGBA_float_GrPixelConfig:
         case kUnknown_GrPixelConfig:
             return false;
@@ -1094,6 +993,7 @@
         case kRGBA_4444_GrPixelConfig:
         case kRGBA_8888_GrPixelConfig:
         case kRGB_888_GrPixelConfig:
+        case kRGB_888X_GrPixelConfig:
         case kRG_88_GrPixelConfig:
         case kBGRA_8888_GrPixelConfig:
         case kSRGBA_8888_GrPixelConfig:
@@ -1102,6 +1002,7 @@
         case kRGBA_float_GrPixelConfig:
         case kRG_float_GrPixelConfig:
         case kRGBA_half_GrPixelConfig:
+        case kRGBA_half_Clamped_GrPixelConfig:
         case kRGB_ETC1_GrPixelConfig:
             return false;
     }
@@ -1121,6 +1022,7 @@
         case kRGB_565_GrPixelConfig:
         case kRGBA_4444_GrPixelConfig:
         case kRGB_888_GrPixelConfig:
+        case kRGB_888X_GrPixelConfig:
         case kRG_88_GrPixelConfig:
         case kRGBA_8888_GrPixelConfig:
         case kBGRA_8888_GrPixelConfig:
@@ -1134,12 +1036,20 @@
         case kAlpha_half_GrPixelConfig:
         case kAlpha_half_as_Red_GrPixelConfig:
         case kRGBA_half_GrPixelConfig:
+        case kRGBA_half_Clamped_GrPixelConfig:
             return true;
     }
     SK_ABORT("Invalid pixel config.");
     return false;
 }
 
+static inline GrClampType GrPixelConfigClampType(GrPixelConfig config) {
+    if (!GrPixelConfigIsFloatingPoint(config)) {
+        return GrClampType::kAuto;
+    }
+    return kRGBA_half_Clamped_GrPixelConfig == config ? GrClampType::kManual : GrClampType::kNone;
+}
+
 /**
  * Returns true if the pixel config is a GPU-specific compressed format
  * representation.
@@ -1158,6 +1068,7 @@
         case kRGB_565_GrPixelConfig:
         case kRGBA_4444_GrPixelConfig:
         case kRGB_888_GrPixelConfig:
+        case kRGB_888X_GrPixelConfig:
         case kRG_88_GrPixelConfig:
         case kRGBA_8888_GrPixelConfig:
         case kBGRA_8888_GrPixelConfig:
@@ -1169,6 +1080,7 @@
         case kAlpha_half_GrPixelConfig:
         case kAlpha_half_as_Red_GrPixelConfig:
         case kRGBA_half_GrPixelConfig:
+        case kRGBA_half_Clamped_GrPixelConfig:
             return false;
     }
     SK_ABORT("Invalid pixel config");
@@ -1192,6 +1104,7 @@
         case kRGB_565_GrPixelConfig:
         case kRGBA_4444_GrPixelConfig:
         case kRGB_888_GrPixelConfig:
+        case kRGB_888X_GrPixelConfig:
         case kRG_88_GrPixelConfig:
         case kRGBA_8888_GrPixelConfig:
         case kBGRA_8888_GrPixelConfig:
@@ -1203,6 +1116,7 @@
         case kAlpha_half_GrPixelConfig:
         case kAlpha_half_as_Red_GrPixelConfig:
         case kRGBA_half_GrPixelConfig:
+        case kRGBA_half_Clamped_GrPixelConfig:
             return config;
         }
     SK_ABORT("Invalid pixel config");
@@ -1232,6 +1146,7 @@
         case kRGB_565_GrPixelConfig:
         case kRGBA_4444_GrPixelConfig:
         case kRGB_888_GrPixelConfig:
+        case kRGB_888X_GrPixelConfig:
         case kRG_88_GrPixelConfig:
         case kRGBA_8888_GrPixelConfig:
         case kBGRA_8888_GrPixelConfig:
@@ -1243,6 +1158,7 @@
         case kAlpha_half_GrPixelConfig:
         case kAlpha_half_as_Red_GrPixelConfig:
         case kRGBA_half_GrPixelConfig:
+        case kRGBA_half_Clamped_GrPixelConfig:
             SK_ABORT("Unknown compressed pixel config");
             return 4 * width * height;
     }
@@ -1267,6 +1183,7 @@
         case kRGBA_4444_GrPixelConfig:
         case kRGBA_8888_GrPixelConfig:
         case kRGB_888_GrPixelConfig:
+        case kRGB_888X_GrPixelConfig:
         case kRG_88_GrPixelConfig:
         case kBGRA_8888_GrPixelConfig:
         case kSRGBA_8888_GrPixelConfig:
@@ -1279,6 +1196,7 @@
         case kAlpha_half_GrPixelConfig:
         case kAlpha_half_as_Red_GrPixelConfig:
         case kRGBA_half_GrPixelConfig:
+        case kRGBA_half_Clamped_GrPixelConfig:
         case kRGBA_1010102_GrPixelConfig:
             return kMedium_GrSLPrecision;
     }
@@ -1306,6 +1224,7 @@
     kGray_8,
     kAlpha_F16,
     kRGBA_F16,
+    kRGBA_F16_Clamped,
     kRG_F32,
     kRGBA_F32,
     kRGB_ETC1,   // This type doesn't appear in SkColorType at all.
@@ -1313,21 +1232,22 @@
 
 static inline SkColorType GrColorTypeToSkColorType(GrColorType ct) {
     switch (ct) {
-        case GrColorType::kUnknown:      return kUnknown_SkColorType;
-        case GrColorType::kAlpha_8:      return kAlpha_8_SkColorType;
-        case GrColorType::kRGB_565:      return kRGB_565_SkColorType;
-        case GrColorType::kABGR_4444:    return kARGB_4444_SkColorType;
-        case GrColorType::kRGBA_8888:    return kRGBA_8888_SkColorType;
-        case GrColorType::kRGB_888x:     return kRGB_888x_SkColorType;
-        case GrColorType::kRG_88:        return kUnknown_SkColorType;
-        case GrColorType::kBGRA_8888:    return kBGRA_8888_SkColorType;
-        case GrColorType::kRGBA_1010102: return kRGBA_1010102_SkColorType;
-        case GrColorType::kGray_8:       return kGray_8_SkColorType;
-        case GrColorType::kAlpha_F16:    return kUnknown_SkColorType;
-        case GrColorType::kRGBA_F16:     return kRGBA_F16_SkColorType;
-        case GrColorType::kRG_F32:       return kUnknown_SkColorType;
-        case GrColorType::kRGBA_F32:     return kRGBA_F32_SkColorType;
-        case GrColorType::kRGB_ETC1:     return kUnknown_SkColorType;
+        case GrColorType::kUnknown:          return kUnknown_SkColorType;
+        case GrColorType::kAlpha_8:          return kAlpha_8_SkColorType;
+        case GrColorType::kRGB_565:          return kRGB_565_SkColorType;
+        case GrColorType::kABGR_4444:        return kARGB_4444_SkColorType;
+        case GrColorType::kRGBA_8888:        return kRGBA_8888_SkColorType;
+        case GrColorType::kRGB_888x:         return kRGB_888x_SkColorType;
+        case GrColorType::kRG_88:            return kUnknown_SkColorType;
+        case GrColorType::kBGRA_8888:        return kBGRA_8888_SkColorType;
+        case GrColorType::kRGBA_1010102:     return kRGBA_1010102_SkColorType;
+        case GrColorType::kGray_8:           return kGray_8_SkColorType;
+        case GrColorType::kAlpha_F16:        return kUnknown_SkColorType;
+        case GrColorType::kRGBA_F16:         return kRGBA_F16_SkColorType;
+        case GrColorType::kRGBA_F16_Clamped: return kRGBA_F16Norm_SkColorType;
+        case GrColorType::kRG_F32:           return kUnknown_SkColorType;
+        case GrColorType::kRGBA_F32:         return kRGBA_F32_SkColorType;
+        case GrColorType::kRGB_ETC1:         return kUnknown_SkColorType;
     }
     SK_ABORT("Invalid GrColorType");
     return kUnknown_SkColorType;
@@ -1343,6 +1263,7 @@
         case kRGB_888x_SkColorType:     return GrColorType::kRGB_888x;
         case kBGRA_8888_SkColorType:    return GrColorType::kBGRA_8888;
         case kGray_8_SkColorType:       return GrColorType::kGray_8;
+        case kRGBA_F16Norm_SkColorType: return GrColorType::kRGBA_F16_Clamped;
         case kRGBA_F16_SkColorType:     return GrColorType::kRGBA_F16;
         case kRGBA_1010102_SkColorType: return GrColorType::kRGBA_1010102;
         case kRGB_101010x_SkColorType:  return GrColorType::kUnknown;
@@ -1354,23 +1275,24 @@
 
 static inline uint32_t GrColorTypeComponentFlags(GrColorType ct) {
     switch (ct) {
-        case GrColorType::kUnknown:      return 0;
-        case GrColorType::kAlpha_8:      return kAlpha_SkColorTypeComponentFlag;
-        case GrColorType::kRGB_565:      return kRGB_SkColorTypeComponentFlags;
-        case GrColorType::kABGR_4444:    return kRGBA_SkColorTypeComponentFlags;
-        case GrColorType::kRGBA_8888:    return kRGBA_SkColorTypeComponentFlags;
-        case GrColorType::kRGB_888x:     return kRGB_SkColorTypeComponentFlags;
-        case GrColorType::kRG_88:        return kRed_SkColorTypeComponentFlag |
-                                                kGreen_SkColorTypeComponentFlag;
-        case GrColorType::kBGRA_8888:    return kRGBA_SkColorTypeComponentFlags;
-        case GrColorType::kRGBA_1010102: return kRGBA_SkColorTypeComponentFlags;
-        case GrColorType::kGray_8:       return kGray_SkColorTypeComponentFlag;
-        case GrColorType::kAlpha_F16:    return kAlpha_SkColorTypeComponentFlag;
-        case GrColorType::kRGBA_F16:     return kRGBA_SkColorTypeComponentFlags;
-        case GrColorType::kRG_F32:       return kRed_SkColorTypeComponentFlag |
-                                                kGreen_SkColorTypeComponentFlag;
-        case GrColorType::kRGBA_F32:     return kRGBA_SkColorTypeComponentFlags;
-        case GrColorType::kRGB_ETC1:     return kRGB_SkColorTypeComponentFlags;
+        case GrColorType::kUnknown:          return 0;
+        case GrColorType::kAlpha_8:          return kAlpha_SkColorTypeComponentFlag;
+        case GrColorType::kRGB_565:          return kRGB_SkColorTypeComponentFlags;
+        case GrColorType::kABGR_4444:        return kRGBA_SkColorTypeComponentFlags;
+        case GrColorType::kRGBA_8888:        return kRGBA_SkColorTypeComponentFlags;
+        case GrColorType::kRGB_888x:         return kRGB_SkColorTypeComponentFlags;
+        case GrColorType::kRG_88:            return kRed_SkColorTypeComponentFlag |
+                                                    kGreen_SkColorTypeComponentFlag;
+        case GrColorType::kBGRA_8888:        return kRGBA_SkColorTypeComponentFlags;
+        case GrColorType::kRGBA_1010102:     return kRGBA_SkColorTypeComponentFlags;
+        case GrColorType::kGray_8:           return kGray_SkColorTypeComponentFlag;
+        case GrColorType::kAlpha_F16:        return kAlpha_SkColorTypeComponentFlag;
+        case GrColorType::kRGBA_F16:         return kRGBA_SkColorTypeComponentFlags;
+        case GrColorType::kRGBA_F16_Clamped: return kRGBA_SkColorTypeComponentFlags;
+        case GrColorType::kRG_F32:           return kRed_SkColorTypeComponentFlag |
+                                                    kGreen_SkColorTypeComponentFlag;
+        case GrColorType::kRGBA_F32:         return kRGBA_SkColorTypeComponentFlags;
+        case GrColorType::kRGB_ETC1:         return kRGB_SkColorTypeComponentFlags;
     }
     SK_ABORT("Invalid GrColorType");
     return kUnknown_SkColorType;
@@ -1386,21 +1308,22 @@
 
 static inline int GrColorTypeBytesPerPixel(GrColorType ct) {
     switch (ct) {
-        case GrColorType::kUnknown:      return 0;
-        case GrColorType::kRGB_ETC1:     return 0;
-        case GrColorType::kAlpha_8:      return 1;
-        case GrColorType::kRGB_565:      return 2;
-        case GrColorType::kABGR_4444:    return 2;
-        case GrColorType::kRGBA_8888:    return 4;
-        case GrColorType::kRGB_888x:     return 4;
-        case GrColorType::kRG_88:        return 2;
-        case GrColorType::kBGRA_8888:    return 4;
-        case GrColorType::kRGBA_1010102: return 4;
-        case GrColorType::kGray_8:       return 1;
-        case GrColorType::kAlpha_F16:    return 2;
-        case GrColorType::kRGBA_F16:     return 8;
-        case GrColorType::kRG_F32:       return 8;
-        case GrColorType::kRGBA_F32:     return 16;
+        case GrColorType::kUnknown:          return 0;
+        case GrColorType::kRGB_ETC1:         return 0;
+        case GrColorType::kAlpha_8:          return 1;
+        case GrColorType::kRGB_565:          return 2;
+        case GrColorType::kABGR_4444:        return 2;
+        case GrColorType::kRGBA_8888:        return 4;
+        case GrColorType::kRGB_888x:         return 4;
+        case GrColorType::kRG_88:            return 2;
+        case GrColorType::kBGRA_8888:        return 4;
+        case GrColorType::kRGBA_1010102:     return 4;
+        case GrColorType::kGray_8:           return 1;
+        case GrColorType::kAlpha_F16:        return 2;
+        case GrColorType::kRGBA_F16:         return 8;
+        case GrColorType::kRGBA_F16_Clamped: return 8;
+        case GrColorType::kRG_F32:           return 8;
+        case GrColorType::kRGBA_F32:         return 16;
     }
     SK_ABORT("Invalid GrColorType");
     return 0;
@@ -1430,6 +1353,9 @@
         case kRGB_888_GrPixelConfig:
             *srgbEncoded = GrSRGBEncoded::kNo;
             return GrColorType::kRGB_888x;
+        case kRGB_888X_GrPixelConfig:
+            *srgbEncoded = GrSRGBEncoded::kNo;
+            return GrColorType::kRGB_888x;
         case kRG_88_GrPixelConfig:
             *srgbEncoded = GrSRGBEncoded::kNo;
             return GrColorType::kRG_88;
@@ -1457,6 +1383,9 @@
         case kRGBA_half_GrPixelConfig:
             *srgbEncoded = GrSRGBEncoded::kNo;
             return GrColorType::kRGBA_F16;
+        case kRGBA_half_Clamped_GrPixelConfig:
+            *srgbEncoded = GrSRGBEncoded::kNo;
+            return GrColorType::kRGBA_F16_Clamped;
         case kRGB_ETC1_GrPixelConfig:
             *srgbEncoded = GrSRGBEncoded::kNo;
             return GrColorType::kRGB_ETC1;
@@ -1541,6 +1470,10 @@
             return (GrSRGBEncoded::kYes == srgbEncoded) ? kUnknown_GrPixelConfig
                                                         : kRGBA_half_GrPixelConfig;
 
+        case GrColorType::kRGBA_F16_Clamped:
+            return (GrSRGBEncoded::kYes == srgbEncoded) ? kUnknown_GrPixelConfig
+                                                        : kRGBA_half_Clamped_GrPixelConfig;
+
         case GrColorType::kRGB_ETC1:
             return (GrSRGBEncoded::kYes == srgbEncoded) ? kUnknown_GrPixelConfig
                                                         : kRGB_ETC1_GrPixelConfig;
@@ -1549,20 +1482,24 @@
     return kUnknown_GrPixelConfig;
 }
 
-class GrReleaseProcHelper : public SkRefCnt {
+/**
+ * Ref-counted object that calls a callback from its destructor.
+ */
+class GrRefCntedCallback : public SkRefCnt {
 public:
-    // These match the definitions in SkImage, from whence they came
-    typedef void* ReleaseCtx;
-    typedef void (*ReleaseProc)(ReleaseCtx);
+    using Context = void*;
+    using Callback = void (*)(Context);
 
-    GrReleaseProcHelper(ReleaseProc proc, ReleaseCtx ctx) : fReleaseProc(proc), fReleaseCtx(ctx) {
+    GrRefCntedCallback(Callback proc, Context ctx) : fReleaseProc(proc), fReleaseCtx(ctx) {
         SkASSERT(proc);
     }
-    ~GrReleaseProcHelper() override { fReleaseProc(fReleaseCtx); }
+    ~GrRefCntedCallback() override { fReleaseProc ? fReleaseProc(fReleaseCtx) : void(); }
+
+    Context context() const { return fReleaseCtx; }
 
 private:
-    ReleaseProc fReleaseProc;
-    ReleaseCtx  fReleaseCtx;
+    Callback fReleaseProc;
+    Context fReleaseCtx;
 };
 
 #endif
diff --git a/include/private/SkEncodedInfo.h b/include/private/SkEncodedInfo.h
index a539824..88c52eb 100644
--- a/include/private/SkEncodedInfo.h
+++ b/include/private/SkEncodedInfo.h
@@ -129,12 +129,10 @@
                 SkASSERT(8 == bitsPerComponent);
                 break;
             case kRGBA_Color:
-                SkASSERT(kOpaque_Alpha != alpha);
                 SkASSERT(bitsPerComponent >= 8);
                 break;
             case kBGRA_Color:
             case kYUVA_Color:
-                SkASSERT(kOpaque_Alpha != alpha);
                 SkASSERT(8 == bitsPerComponent);
                 break;
             case kXAlpha_Color:
diff --git a/include/private/SkFloatingPoint.h b/include/private/SkFloatingPoint.h
index ba30475..b3978f4 100644
--- a/include/private/SkFloatingPoint.h
+++ b/include/private/SkFloatingPoint.h
@@ -144,6 +144,10 @@
 #define SK_FloatInfinity            (+std::numeric_limits<float>::infinity())
 #define SK_FloatNegativeInfinity    (-std::numeric_limits<float>::infinity())
 
+// Returns false if any of the floats are outside of [0...1]
+// Returns true if count is 0
+bool sk_floats_are_unit(const float array[], size_t count);
+
 static inline float sk_float_rsqrt_portable(float x) {
     // Get initial estimate.
     int i;
diff --git a/include/private/SkImageInfoPriv.h b/include/private/SkImageInfoPriv.h
index 1f064bd..2841504 100644
--- a/include/private/SkImageInfoPriv.h
+++ b/include/private/SkImageInfoPriv.h
@@ -25,18 +25,19 @@
 
 static inline uint32_t SkColorTypeComponentFlags(SkColorType ct) {
     switch (ct) {
-        case kUnknown_SkColorType:      return 0;
-        case kAlpha_8_SkColorType:      return kAlpha_SkColorTypeComponentFlag;
-        case kRGB_565_SkColorType:      return kRGB_SkColorTypeComponentFlags;
-        case kARGB_4444_SkColorType:    return kRGBA_SkColorTypeComponentFlags;
-        case kRGBA_8888_SkColorType:    return kRGBA_SkColorTypeComponentFlags;
-        case kRGB_888x_SkColorType:     return kRGB_SkColorTypeComponentFlags;
-        case kBGRA_8888_SkColorType:    return kRGBA_SkColorTypeComponentFlags;
-        case kRGBA_1010102_SkColorType: return kRGBA_SkColorTypeComponentFlags;
-        case kRGB_101010x_SkColorType:  return kRGB_SkColorTypeComponentFlags;
-        case kGray_8_SkColorType:       return kGray_SkColorTypeComponentFlag;
-        case kRGBA_F16_SkColorType:     return kRGBA_SkColorTypeComponentFlags;
-        case kRGBA_F32_SkColorType:     return kRGBA_SkColorTypeComponentFlags;
+        case kUnknown_SkColorType:          return 0;
+        case kAlpha_8_SkColorType:          return kAlpha_SkColorTypeComponentFlag;
+        case kRGB_565_SkColorType:          return kRGB_SkColorTypeComponentFlags;
+        case kARGB_4444_SkColorType:        return kRGBA_SkColorTypeComponentFlags;
+        case kRGBA_8888_SkColorType:        return kRGBA_SkColorTypeComponentFlags;
+        case kRGB_888x_SkColorType:         return kRGB_SkColorTypeComponentFlags;
+        case kBGRA_8888_SkColorType:        return kRGBA_SkColorTypeComponentFlags;
+        case kRGBA_1010102_SkColorType:     return kRGBA_SkColorTypeComponentFlags;
+        case kRGB_101010x_SkColorType:      return kRGB_SkColorTypeComponentFlags;
+        case kGray_8_SkColorType:           return kGray_SkColorTypeComponentFlag;
+        case kRGBA_F16Norm_SkColorType:     return kRGBA_SkColorTypeComponentFlags;
+        case kRGBA_F16_SkColorType:         return kRGBA_SkColorTypeComponentFlags;
+        case kRGBA_F32_SkColorType:         return kRGBA_SkColorTypeComponentFlags;
     }
     return 0;
 }
@@ -58,18 +59,19 @@
 
 static int SkColorTypeShiftPerPixel(SkColorType ct) {
     switch (ct) {
-        case kUnknown_SkColorType:      return 0;
-        case kAlpha_8_SkColorType:      return 0;
-        case kRGB_565_SkColorType:      return 1;
-        case kARGB_4444_SkColorType:    return 1;
-        case kRGBA_8888_SkColorType:    return 2;
-        case kRGB_888x_SkColorType:     return 2;
-        case kBGRA_8888_SkColorType:    return 2;
-        case kRGBA_1010102_SkColorType: return 2;
-        case kRGB_101010x_SkColorType:  return 2;
-        case kGray_8_SkColorType:       return 0;
-        case kRGBA_F16_SkColorType:     return 3;
-        case kRGBA_F32_SkColorType:     return 4;
+        case kUnknown_SkColorType:          return 0;
+        case kAlpha_8_SkColorType:          return 0;
+        case kRGB_565_SkColorType:          return 1;
+        case kARGB_4444_SkColorType:        return 1;
+        case kRGBA_8888_SkColorType:        return 2;
+        case kRGB_888x_SkColorType:         return 2;
+        case kBGRA_8888_SkColorType:        return 2;
+        case kRGBA_1010102_SkColorType:     return 2;
+        case kRGB_101010x_SkColorType:      return 2;
+        case kGray_8_SkColorType:           return 0;
+        case kRGBA_F16Norm_SkColorType:     return 3;
+        case kRGBA_F16_SkColorType:         return 3;
+        case kRGBA_F32_SkColorType:         return 4;
     }
     return 0;
 }
diff --git a/include/private/SkPathRef.h b/include/private/SkPathRef.h
index 91c05ba..fbf8046 100644
--- a/include/private/SkPathRef.h
+++ b/include/private/SkPathRef.h
@@ -402,6 +402,7 @@
     void resetToSize(int verbCount, int pointCount, int conicCount,
                      int reserveVerbs = 0, int reservePoints = 0) {
         SkDEBUGCODE(this->validate();)
+        this->callGenIDChangeListeners();
         fBoundsIsDirty = true;      // this also invalidates fIsFinite
         fGenerationID = 0;
 
diff --git a/include/private/SkVx.h b/include/private/SkVx.h
index ddfbc0a..ece01fa 100644
--- a/include/private/SkVx.h
+++ b/include/private/SkVx.h
@@ -18,17 +18,19 @@
 // with across translation units.  skvx::Vec<N,T> always has N*sizeof(T) size
 // and alignof(T) alignment and is safe to use across translation units freely.
 
-
-// It'd be nice to not pull in any Skia headers here, in case we want to spin this file off.
-#include <algorithm>         // std::accumulate, std::copy, std::fill, std::transform, etc.
+#include "SkTypes.h"         // SK_CPU_SSE_LEVEL*, etc.
+#include <algorithm>         // std::min, std::max
+#include <cmath>             // std::ceil, std::floor, std::trunc, std::round, std::sqrt, etc.
 #include <cstdint>           // intXX_t
 #include <cstring>           // memcpy()
-#include <cmath>             // std::ceil, std::floor, std::trunc, std::round, std::sqrt, etc.
-#include <functional>        // std::plus, std::minus, std::multiplies, etc.
 #include <initializer_list>  // std::initializer_list
 
-// We try to use <algorithm> and <functional> where natural so that the more
-// idiosyncratic parts that can't use them stand out.  This is an experiment.
+#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE1
+    #include <immintrin.h>
+#elif defined(SK_ARM_HAS_NEON)
+    #include <arm_neon.h>
+#endif
+
 
 namespace skvx {
 
@@ -39,7 +41,7 @@
 struct Vec {
     static_assert((N & (N-1)) == 0, "N must be a power of 2.");
 
-    T vals[N];
+    Vec<N/2,T> lo, hi;
 
     // Methods belong here in the class declaration of Vec only if:
     //   - they must be here, like constructors or operator[];
@@ -48,14 +50,20 @@
 
     Vec() = default;
 
-    Vec(T x) { std::fill(vals,vals+N, x); }
+    template <typename U,
+              typename=typename std::enable_if<std::is_convertible<U,T>::value>::type>
+    Vec(U x) : lo(x), hi(x) {}
 
-    Vec(std::initializer_list<T> xs) : Vec(0) {
-        std::copy(xs.begin(), xs.begin() + std::min(xs.size(), (size_t)N), vals);
+    Vec(std::initializer_list<T> xs) {
+        T vals[N] = {0};
+        memcpy(vals, xs.begin(), std::min(xs.size(), (size_t)N)*sizeof(T));
+
+        lo = Vec<N/2,T>::Load(vals +   0);
+        hi = Vec<N/2,T>::Load(vals + N/2);
     }
 
-    T  operator[](int i) const { return vals[i]; }
-    T& operator[](int i)       { return vals[i]; }
+    T  operator[](int i) const { return i < N/2 ? lo[i] : hi[i-N/2]; }
+    T& operator[](int i)       { return i < N/2 ? lo[i] : hi[i-N/2]; }
 
     static Vec Load(const void* ptr) {
         Vec v;
@@ -67,71 +75,75 @@
     }
 };
 
+template <typename T>
+struct Vec<1,T> {
+    T val;
 
-#if defined(_MSC_VER)
-    #define ALWAYS_INLINE __forceinline
-#else
-    #define ALWAYS_INLINE __attribute__((always_inline))
-#endif
+    Vec() = default;
 
-// Helps tamp down on the repetitive boilerplate.
-#define ___ template <int N, typename T> static inline ALWAYS_INLINE
+    template <typename U,
+              typename=typename std::enable_if<std::is_convertible<U,T>::value>::type>
+    Vec(U x) : val(x) {}
 
-#if defined(__GNUC__) && !defined(__clang__) && defined(__SSE__)
+    Vec(std::initializer_list<T> xs) : val(xs.size() ? *xs.begin() : 0) {}
+
+    T  operator[](int) const { return val; }
+    T& operator[](int)       { return val; }
+
+    static Vec Load(const void* ptr) {
+        Vec v;
+        memcpy(&v, ptr, sizeof(Vec));
+        return v;
+    }
+    void store(void* ptr) const {
+        memcpy(ptr, this, sizeof(Vec));
+    }
+};
+
+#if defined(__GNUC__) && !defined(__clang__) && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE1
     // GCC warns about ABI changes when returning >= 32 byte vectors when -mavx is not enabled.
-    // The functions that do that (BitPun::operator U() and to_vext()) are marked ALWAYS_INLINE,
-    // so we can just stifle the warning.
+    // This only happens for types like VExt whose ABI we don't care about, not for Vec itself.
     #pragma GCC diagnostic ignored "-Wpsabi"
 #endif
 
-// BitPun<V> holds a V and can implicitly bit-pun that V to any other equal sized type U.
-template <typename V>
-struct BitPun {
-    V v;
+// Helps tamp down on the repetitive boilerplate.
+#define SIT   template <       typename T> static inline
+#define SINT  template <int N, typename T> static inline
+#define SINTU template <int N, typename T, typename U, \
+                        typename=typename std::enable_if<std::is_convertible<U,T>::value>::type> \
+              static inline
 
-    template <typename U>
-    ALWAYS_INLINE operator U() const {
-        static_assert(sizeof(U) == sizeof(V), "");
-        U u;
-        memcpy(&u, &v, sizeof(U));
-        return u;
-    }
-};
-template <typename V>
-static inline ALWAYS_INLINE BitPun<V> bit_pun(V v) { return {v}; }
+template <typename D, typename S>
+static inline D bit_pun(S s) {
+    static_assert(sizeof(D) == sizeof(S), "");
+    D d;
+    memcpy(&d, &s, sizeof(D));
+    return d;
+}
 
 // Translate from a value type T to its corresponding Mask, the result of a comparison.
-template <typename T> struct MaskHelper { using type = T; };
-template <> struct MaskHelper<float > { using type = int32_t; };
-template <> struct MaskHelper<double> { using type = int64_t; };
-template <typename T> using Mask = typename MaskHelper<T>::type;
+template <typename T> struct Mask { using type = T; };
+template <> struct Mask<float > { using type = int32_t; };
+template <> struct Mask<double> { using type = int64_t; };
+template <typename T> using M = typename Mask<T>::type;
 
-
-// Apply op() to each lane of one or two input vectors, returning a new vector of the results.
-template <int N, typename T, typename Op>
-static inline auto map(Vec<N,T> x, Op op) -> Vec<N, decltype(op(x[0]))> {
-    Vec<N, decltype(op(x[0]))> results;
-    std::transform(x.vals, x.vals+N, results.vals, op);
-    return results;
+// Join two Vec<N,T> into one Vec<2N,T>.
+SINT Vec<2*N,T> join(Vec<N,T> lo, Vec<N,T> hi) {
+    Vec<2*N,T> v;
+    v.lo = lo;
+    v.hi = hi;
+    return v;
 }
-template <int N, typename T, typename Op>
-static inline auto map(Vec<N,T> x, Vec<N,T> y, Op op) -> Vec<N, decltype(op(x[0], y[0]))> {
-    Vec<N, decltype(op(x[0], y[0]))> results;
-    std::transform(x.vals, x.vals+N, y.vals, results.vals, op);
-    return results;
-}
-
 
 // We have two default strategies for implementing most operations:
 //    1) lean on Clang/GCC vector extensions when available;
-//    2) fall back to portable implementations when not.
-// At the end we can drop in platform-specific implementations that override these defaults.
+//    2) recurse to scalar portable implementations when not.
+// At the end we can drop in platform-specific implementations that override either default.
 
 #if !defined(SKNX_NO_SIMD) && (defined(__clang__) || defined(__GNUC__))
 
     // VExt<N,T> types have the same size as Vec<N,T> and support most operations directly.
     // N.B. VExt<N,T> alignment is N*alignof(T), stricter than Vec<N,T>'s alignof(T).
-
     #if defined(__clang__)
         template <int N, typename T>
         using VExt = T __attribute__((ext_vector_type(N)));
@@ -144,175 +156,227 @@
 
         template <int N, typename T>
         using VExt = typename VExtHelper<N,T>::type;
+
+        // For some reason some (new!) versions of GCC cannot seem to deduce N in the generic
+        // to_vec<N,T>() below for N=4 and T=float.  This workaround seems to help...
+        static inline Vec<4,float> to_vec(VExt<4,float> v) { return bit_pun<Vec<4,float>>(v); }
     #endif
 
-    ___ VExt<N,T> to_vext(Vec<N,T> v) { return bit_pun(v); }
+    SINT VExt<N,T> to_vext(Vec<N,T> v) { return bit_pun<VExt<N,T>>(v); }
+    SINT Vec <N,T> to_vec(VExt<N,T> v) { return bit_pun<Vec <N,T>>(v); }
 
-    ___ Vec<N,T> operator+(Vec<N,T> x, Vec<N,T> y) { return bit_pun(to_vext(x) + to_vext(y)); }
-    ___ Vec<N,T> operator-(Vec<N,T> x, Vec<N,T> y) { return bit_pun(to_vext(x) - to_vext(y)); }
-    ___ Vec<N,T> operator*(Vec<N,T> x, Vec<N,T> y) { return bit_pun(to_vext(x) * to_vext(y)); }
-    ___ Vec<N,T> operator/(Vec<N,T> x, Vec<N,T> y) { return bit_pun(to_vext(x) / to_vext(y)); }
+    SINT Vec<N,T> operator+(Vec<N,T> x, Vec<N,T> y) { return to_vec<N,T>(to_vext(x) + to_vext(y)); }
+    SINT Vec<N,T> operator-(Vec<N,T> x, Vec<N,T> y) { return to_vec<N,T>(to_vext(x) - to_vext(y)); }
+    SINT Vec<N,T> operator*(Vec<N,T> x, Vec<N,T> y) { return to_vec<N,T>(to_vext(x) * to_vext(y)); }
+    SINT Vec<N,T> operator/(Vec<N,T> x, Vec<N,T> y) { return to_vec<N,T>(to_vext(x) / to_vext(y)); }
 
-    ___ Vec<N,T> operator^(Vec<N,T> x, Vec<N,T> y) { return bit_pun(to_vext(x) ^ to_vext(y)); }
-    ___ Vec<N,T> operator&(Vec<N,T> x, Vec<N,T> y) { return bit_pun(to_vext(x) & to_vext(y)); }
-    ___ Vec<N,T> operator|(Vec<N,T> x, Vec<N,T> y) { return bit_pun(to_vext(x) | to_vext(y)); }
+    SINT Vec<N,T> operator^(Vec<N,T> x, Vec<N,T> y) { return to_vec<N,T>(to_vext(x) ^ to_vext(y)); }
+    SINT Vec<N,T> operator&(Vec<N,T> x, Vec<N,T> y) { return to_vec<N,T>(to_vext(x) & to_vext(y)); }
+    SINT Vec<N,T> operator|(Vec<N,T> x, Vec<N,T> y) { return to_vec<N,T>(to_vext(x) | to_vext(y)); }
 
-    ___ Vec<N,T> operator!(Vec<N,T> x) { return bit_pun(!to_vext(x)); }
-    ___ Vec<N,T> operator-(Vec<N,T> x) { return bit_pun(-to_vext(x)); }
-    ___ Vec<N,T> operator~(Vec<N,T> x) { return bit_pun(~to_vext(x)); }
+    SINT Vec<N,T> operator!(Vec<N,T> x) { return to_vec<N,T>(!to_vext(x)); }
+    SINT Vec<N,T> operator-(Vec<N,T> x) { return to_vec<N,T>(-to_vext(x)); }
+    SINT Vec<N,T> operator~(Vec<N,T> x) { return to_vec<N,T>(~to_vext(x)); }
 
-    ___ Vec<N,T> operator<<(Vec<N,T> x, int bits) { return bit_pun(to_vext(x) << bits); }
-    ___ Vec<N,T> operator>>(Vec<N,T> x, int bits) { return bit_pun(to_vext(x) >> bits); }
+    SINT Vec<N,T> operator<<(Vec<N,T> x, int bits) { return to_vec<N,T>(to_vext(x) << bits); }
+    SINT Vec<N,T> operator>>(Vec<N,T> x, int bits) { return to_vec<N,T>(to_vext(x) >> bits); }
 
-    ___ Vec<N, Mask<T>> operator==(Vec<N,T> x, Vec<N,T> y) { return bit_pun(to_vext(x) == to_vext(y)); }
-    ___ Vec<N, Mask<T>> operator!=(Vec<N,T> x, Vec<N,T> y) { return bit_pun(to_vext(x) != to_vext(y)); }
-    ___ Vec<N, Mask<T>> operator<=(Vec<N,T> x, Vec<N,T> y) { return bit_pun(to_vext(x) <= to_vext(y)); }
-    ___ Vec<N, Mask<T>> operator>=(Vec<N,T> x, Vec<N,T> y) { return bit_pun(to_vext(x) >= to_vext(y)); }
-    ___ Vec<N, Mask<T>> operator< (Vec<N,T> x, Vec<N,T> y) { return bit_pun(to_vext(x) <  to_vext(y)); }
-    ___ Vec<N, Mask<T>> operator> (Vec<N,T> x, Vec<N,T> y) { return bit_pun(to_vext(x) >  to_vext(y)); }
+    SINT Vec<N,M<T>> operator==(Vec<N,T> x, Vec<N,T> y) { return bit_pun<Vec<N,M<T>>>(to_vext(x) == to_vext(y)); }
+    SINT Vec<N,M<T>> operator!=(Vec<N,T> x, Vec<N,T> y) { return bit_pun<Vec<N,M<T>>>(to_vext(x) != to_vext(y)); }
+    SINT Vec<N,M<T>> operator<=(Vec<N,T> x, Vec<N,T> y) { return bit_pun<Vec<N,M<T>>>(to_vext(x) <= to_vext(y)); }
+    SINT Vec<N,M<T>> operator>=(Vec<N,T> x, Vec<N,T> y) { return bit_pun<Vec<N,M<T>>>(to_vext(x) >= to_vext(y)); }
+    SINT Vec<N,M<T>> operator< (Vec<N,T> x, Vec<N,T> y) { return bit_pun<Vec<N,M<T>>>(to_vext(x) <  to_vext(y)); }
+    SINT Vec<N,M<T>> operator> (Vec<N,T> x, Vec<N,T> y) { return bit_pun<Vec<N,M<T>>>(to_vext(x) >  to_vext(y)); }
 
 #else
 
     // Either SKNX_NO_SIMD is defined, or Clang/GCC vector extensions are not available.
     // We'll implement things portably, in a way that should be easily autovectorizable.
 
-    ___ Vec<N,T> operator+(Vec<N,T> x, Vec<N,T> y) { return map(x,y, std::plus      <T>{}); }
-    ___ Vec<N,T> operator-(Vec<N,T> x, Vec<N,T> y) { return map(x,y, std::minus     <T>{}); }
-    ___ Vec<N,T> operator*(Vec<N,T> x, Vec<N,T> y) { return map(x,y, std::multiplies<T>{}); }
-    ___ Vec<N,T> operator/(Vec<N,T> x, Vec<N,T> y) { return map(x,y, std::divides   <T>{}); }
+    // N == 1 scalar implementations.
+    SIT Vec<1,T> operator+(Vec<1,T> x, Vec<1,T> y) { return x.val + y.val; }
+    SIT Vec<1,T> operator-(Vec<1,T> x, Vec<1,T> y) { return x.val - y.val; }
+    SIT Vec<1,T> operator*(Vec<1,T> x, Vec<1,T> y) { return x.val * y.val; }
+    SIT Vec<1,T> operator/(Vec<1,T> x, Vec<1,T> y) { return x.val / y.val; }
 
-    ___ Vec<N,T> operator^(Vec<N,T> x, Vec<N,T> y) { return map(x,y, std::bit_xor<T>{}); }
-    ___ Vec<N,T> operator&(Vec<N,T> x, Vec<N,T> y) { return map(x,y, std::bit_and<T>{}); }
-    ___ Vec<N,T> operator|(Vec<N,T> x, Vec<N,T> y) { return map(x,y, std::bit_or <T>{}); }
+    SIT Vec<1,T> operator^(Vec<1,T> x, Vec<1,T> y) { return x.val ^ y.val; }
+    SIT Vec<1,T> operator&(Vec<1,T> x, Vec<1,T> y) { return x.val & y.val; }
+    SIT Vec<1,T> operator|(Vec<1,T> x, Vec<1,T> y) { return x.val | y.val; }
 
-    ___ Vec<N,T> operator!(Vec<N,T> x) { return map(x, std::logical_not<T>{}); }
-    ___ Vec<N,T> operator-(Vec<N,T> x) { return map(x, std::negate     <T>{}); }
-    ___ Vec<N,T> operator~(Vec<N,T> x) { return map(x, std::bit_not    <T>{}); }
+    SIT Vec<1,T> operator!(Vec<1,T> x) { return !x.val; }
+    SIT Vec<1,T> operator-(Vec<1,T> x) { return -x.val; }
+    SIT Vec<1,T> operator~(Vec<1,T> x) { return ~x.val; }
 
-    ___ Vec<N,T> operator<<(Vec<N,T> x, int bits) { return map(x, [bits](T a) { return a << bits; }); }
-    ___ Vec<N,T> operator>>(Vec<N,T> x, int bits) { return map(x, [bits](T a) { return a >> bits; }); }
+    SIT Vec<1,T> operator<<(Vec<1,T> x, int bits) { return x.val << bits; }
+    SIT Vec<1,T> operator>>(Vec<1,T> x, int bits) { return x.val >> bits; }
 
-    ___ Vec<N, Mask<T>> operator==(Vec<N,T> x, Vec<N,T> y) { return map(x,y, [](T a, T b) -> Mask<T> { return a == b ? ~0 : 0; }); }
-    ___ Vec<N, Mask<T>> operator!=(Vec<N,T> x, Vec<N,T> y) { return map(x,y, [](T a, T b) -> Mask<T> { return a != b ? ~0 : 0; }); }
-    ___ Vec<N, Mask<T>> operator<=(Vec<N,T> x, Vec<N,T> y) { return map(x,y, [](T a, T b) -> Mask<T> { return a <= b ? ~0 : 0; }); }
-    ___ Vec<N, Mask<T>> operator>=(Vec<N,T> x, Vec<N,T> y) { return map(x,y, [](T a, T b) -> Mask<T> { return a >= b ? ~0 : 0; }); }
-    ___ Vec<N, Mask<T>> operator< (Vec<N,T> x, Vec<N,T> y) { return map(x,y, [](T a, T b) -> Mask<T> { return a <  b ? ~0 : 0; }); }
-    ___ Vec<N, Mask<T>> operator> (Vec<N,T> x, Vec<N,T> y) { return map(x,y, [](T a, T b) -> Mask<T> { return a >  b ? ~0 : 0; }); }
+    SIT Vec<1,M<T>> operator==(Vec<1,T> x, Vec<1,T> y) { return x.val == y.val ? ~0 : 0; }
+    SIT Vec<1,M<T>> operator!=(Vec<1,T> x, Vec<1,T> y) { return x.val != y.val ? ~0 : 0; }
+    SIT Vec<1,M<T>> operator<=(Vec<1,T> x, Vec<1,T> y) { return x.val <= y.val ? ~0 : 0; }
+    SIT Vec<1,M<T>> operator>=(Vec<1,T> x, Vec<1,T> y) { return x.val >= y.val ? ~0 : 0; }
+    SIT Vec<1,M<T>> operator< (Vec<1,T> x, Vec<1,T> y) { return x.val <  y.val ? ~0 : 0; }
+    SIT Vec<1,M<T>> operator> (Vec<1,T> x, Vec<1,T> y) { return x.val >  y.val ? ~0 : 0; }
+
+    // All default N != 1 implementations just recurse on lo and hi halves.
+    SINT Vec<N,T> operator+(Vec<N,T> x, Vec<N,T> y) { return join(x.lo + y.lo, x.hi + y.hi); }
+    SINT Vec<N,T> operator-(Vec<N,T> x, Vec<N,T> y) { return join(x.lo - y.lo, x.hi - y.hi); }
+    SINT Vec<N,T> operator*(Vec<N,T> x, Vec<N,T> y) { return join(x.lo * y.lo, x.hi * y.hi); }
+    SINT Vec<N,T> operator/(Vec<N,T> x, Vec<N,T> y) { return join(x.lo / y.lo, x.hi / y.hi); }
+
+    SINT Vec<N,T> operator^(Vec<N,T> x, Vec<N,T> y) { return join(x.lo ^ y.lo, x.hi ^ y.hi); }
+    SINT Vec<N,T> operator&(Vec<N,T> x, Vec<N,T> y) { return join(x.lo & y.lo, x.hi & y.hi); }
+    SINT Vec<N,T> operator|(Vec<N,T> x, Vec<N,T> y) { return join(x.lo | y.lo, x.hi | y.hi); }
+
+    SINT Vec<N,T> operator!(Vec<N,T> x) { return join(!x.lo, !x.hi); }
+    SINT Vec<N,T> operator-(Vec<N,T> x) { return join(-x.lo, -x.hi); }
+    SINT Vec<N,T> operator~(Vec<N,T> x) { return join(~x.lo, ~x.hi); }
+
+    SINT Vec<N,T> operator<<(Vec<N,T> x, int bits) { return join(x.lo << bits, x.hi << bits); }
+    SINT Vec<N,T> operator>>(Vec<N,T> x, int bits) { return join(x.lo >> bits, x.hi >> bits); }
+
+    SINT Vec<N,M<T>> operator==(Vec<N,T> x, Vec<N,T> y) { return join(x.lo == y.lo, x.hi == y.hi); }
+    SINT Vec<N,M<T>> operator!=(Vec<N,T> x, Vec<N,T> y) { return join(x.lo != y.lo, x.hi != y.hi); }
+    SINT Vec<N,M<T>> operator<=(Vec<N,T> x, Vec<N,T> y) { return join(x.lo <= y.lo, x.hi <= y.hi); }
+    SINT Vec<N,M<T>> operator>=(Vec<N,T> x, Vec<N,T> y) { return join(x.lo >= y.lo, x.hi >= y.hi); }
+    SINT Vec<N,M<T>> operator< (Vec<N,T> x, Vec<N,T> y) { return join(x.lo <  y.lo, x.hi <  y.hi); }
+    SINT Vec<N,M<T>> operator> (Vec<N,T> x, Vec<N,T> y) { return join(x.lo >  y.lo, x.hi >  y.hi); }
 #endif
 
-// Some operations we want are not expressible with Clang/GCC vector extensions,
-// so we implement them using the same approach as the alternate path above.
+// Some operations we want are not expressible with Clang/GCC vector
+// extensions, so we implement them using the recursive approach.
 
-___ Vec<N,T> if_then_else(Vec<N,Mask<T>> cond, Vec<N,T> t, Vec<N,T> e) {
-    Vec<N,Mask<T>> t_bits = bit_pun(t),
-                   e_bits = bit_pun(e);
-    return bit_pun( (cond & t_bits) | (~cond & e_bits) );
+// N == 1 scalar implementations.
+SIT Vec<1,T> if_then_else(Vec<1,M<T>> cond, Vec<1,T> t, Vec<1,T> e) {
+    auto t_bits = bit_pun<M<T>>(t),
+         e_bits = bit_pun<M<T>>(e);
+    return bit_pun<T>( (cond.val & t_bits) | (~cond.val & e_bits) );
 }
 
-___ const T* begin(const Vec<N,T>& x) { return x.vals  ; }
-___       T* begin(      Vec<N,T>& x) { return x.vals  ; }
-___ const T*   end(const Vec<N,T>& x) { return x.vals+N; }
-___       T*   end(      Vec<N,T>& x) { return x.vals+N; }
+SIT bool any(Vec<1,T> x) { return x.val != 0; }
+SIT bool all(Vec<1,T> x) { return x.val != 0; }
 
-___ Vec<N,T> min(Vec<N,T> x, Vec<N,T> y) { return map(x,y, [](T a, T b) { return std::min(a,b); }); }
-___ Vec<N,T> max(Vec<N,T> x, Vec<N,T> y) { return map(x,y, [](T a, T b) { return std::max(a,b); }); }
+SIT T min(Vec<1,T> x) { return x.val; }
+SIT T max(Vec<1,T> x) { return x.val; }
+
+SIT Vec<1,T> min(Vec<1,T> x, Vec<1,T> y) { return std::min(x.val, y.val); }
+SIT Vec<1,T> max(Vec<1,T> x, Vec<1,T> y) { return std::max(x.val, y.val); }
+
+SIT Vec<1,T>  ceil(Vec<1,T> x) { return std:: ceil(x.val); }
+SIT Vec<1,T> floor(Vec<1,T> x) { return std::floor(x.val); }
+SIT Vec<1,T> trunc(Vec<1,T> x) { return std::trunc(x.val); }
+SIT Vec<1,T> round(Vec<1,T> x) { return std::round(x.val); }
+SIT Vec<1,T>  sqrt(Vec<1,T> x) { return std:: sqrt(x.val); }
+SIT Vec<1,T>   abs(Vec<1,T> x) { return std::  abs(x.val); }
+
+SIT Vec<1,T>   rcp(Vec<1,T> x) { return 1 / x.val; }
+SIT Vec<1,T> rsqrt(Vec<1,T> x) { return rcp(sqrt(x)); }
+SIT Vec<1,T>   mad(Vec<1,T> f,
+                   Vec<1,T> m,
+                   Vec<1,T> a) { return f*m+a; }
+
+// All default N != 1 implementations just recurse on lo and hi halves.
+SINT Vec<N,T> if_then_else(Vec<N,M<T>> cond, Vec<N,T> t, Vec<N,T> e) {
+    return join(if_then_else(cond.lo, t.lo, e.lo),
+                if_then_else(cond.hi, t.hi, e.hi));
+}
+
+SINT bool any(Vec<N,T> x) { return any(x.lo) || any(x.hi); }
+SINT bool all(Vec<N,T> x) { return all(x.lo) && all(x.hi); }
+
+SINT T min(Vec<N,T> x) { return std::min(min(x.lo), min(x.hi)); }
+SINT T max(Vec<N,T> x) { return std::max(max(x.lo), max(x.hi)); }
+
+SINT Vec<N,T> min(Vec<N,T> x, Vec<N,T> y) { return join(min(x.lo, y.lo), min(x.hi, y.hi)); }
+SINT Vec<N,T> max(Vec<N,T> x, Vec<N,T> y) { return join(max(x.lo, y.lo), max(x.hi, y.hi)); }
+
+SINT Vec<N,T>  ceil(Vec<N,T> x) { return join( ceil(x.lo),  ceil(x.hi)); }
+SINT Vec<N,T> floor(Vec<N,T> x) { return join(floor(x.lo), floor(x.hi)); }
+SINT Vec<N,T> trunc(Vec<N,T> x) { return join(trunc(x.lo), trunc(x.hi)); }
+SINT Vec<N,T> round(Vec<N,T> x) { return join(round(x.lo), round(x.hi)); }
+SINT Vec<N,T>  sqrt(Vec<N,T> x) { return join( sqrt(x.lo),  sqrt(x.hi)); }
+SINT Vec<N,T>   abs(Vec<N,T> x) { return join(  abs(x.lo),   abs(x.hi)); }
+
+SINT Vec<N,T>   rcp(Vec<N,T> x) { return join(  rcp(x.lo),   rcp(x.hi)); }
+SINT Vec<N,T> rsqrt(Vec<N,T> x) { return join(rsqrt(x.lo), rsqrt(x.hi)); }
+SINT Vec<N,T>   mad(Vec<N,T> f,
+                    Vec<N,T> m,
+                    Vec<N,T> a) { return join(mad(f.lo, m.lo, a.lo), mad(f.hi, m.hi, a.hi)); }
+
 
 // Scalar/vector operations just splat the scalar to a vector...
-___ Vec<N,T>       operator+ (T x, Vec<N,T> y) { return Vec<N,T>(x) +  y; }
-___ Vec<N,T>       operator- (T x, Vec<N,T> y) { return Vec<N,T>(x) -  y; }
-___ Vec<N,T>       operator* (T x, Vec<N,T> y) { return Vec<N,T>(x) *  y; }
-___ Vec<N,T>       operator/ (T x, Vec<N,T> y) { return Vec<N,T>(x) /  y; }
-___ Vec<N,T>       operator^ (T x, Vec<N,T> y) { return Vec<N,T>(x) ^  y; }
-___ Vec<N,T>       operator& (T x, Vec<N,T> y) { return Vec<N,T>(x) &  y; }
-___ Vec<N,T>       operator| (T x, Vec<N,T> y) { return Vec<N,T>(x) |  y; }
-___ Vec<N,Mask<T>> operator==(T x, Vec<N,T> y) { return Vec<N,T>(x) == y; }
-___ Vec<N,Mask<T>> operator!=(T x, Vec<N,T> y) { return Vec<N,T>(x) != y; }
-___ Vec<N,Mask<T>> operator<=(T x, Vec<N,T> y) { return Vec<N,T>(x) <= y; }
-___ Vec<N,Mask<T>> operator>=(T x, Vec<N,T> y) { return Vec<N,T>(x) >= y; }
-___ Vec<N,Mask<T>> operator< (T x, Vec<N,T> y) { return Vec<N,T>(x) <  y; }
-___ Vec<N,Mask<T>> operator> (T x, Vec<N,T> y) { return Vec<N,T>(x) >  y; }
-___ Vec<N,T>              min(T x, Vec<N,T> y) { return min(Vec<N,T>(x), y); }
-___ Vec<N,T>              max(T x, Vec<N,T> y) { return max(Vec<N,T>(x), y); }
+SINTU Vec<N,T>    operator+ (U x, Vec<N,T> y) { return Vec<N,T>(x) +  y; }
+SINTU Vec<N,T>    operator- (U x, Vec<N,T> y) { return Vec<N,T>(x) -  y; }
+SINTU Vec<N,T>    operator* (U x, Vec<N,T> y) { return Vec<N,T>(x) *  y; }
+SINTU Vec<N,T>    operator/ (U x, Vec<N,T> y) { return Vec<N,T>(x) /  y; }
+SINTU Vec<N,T>    operator^ (U x, Vec<N,T> y) { return Vec<N,T>(x) ^  y; }
+SINTU Vec<N,T>    operator& (U x, Vec<N,T> y) { return Vec<N,T>(x) &  y; }
+SINTU Vec<N,T>    operator| (U x, Vec<N,T> y) { return Vec<N,T>(x) |  y; }
+SINTU Vec<N,M<T>> operator==(U x, Vec<N,T> y) { return Vec<N,T>(x) == y; }
+SINTU Vec<N,M<T>> operator!=(U x, Vec<N,T> y) { return Vec<N,T>(x) != y; }
+SINTU Vec<N,M<T>> operator<=(U x, Vec<N,T> y) { return Vec<N,T>(x) <= y; }
+SINTU Vec<N,M<T>> operator>=(U x, Vec<N,T> y) { return Vec<N,T>(x) >= y; }
+SINTU Vec<N,M<T>> operator< (U x, Vec<N,T> y) { return Vec<N,T>(x) <  y; }
+SINTU Vec<N,M<T>> operator> (U x, Vec<N,T> y) { return Vec<N,T>(x) >  y; }
+SINTU Vec<N,T>           min(U x, Vec<N,T> y) { return min(Vec<N,T>(x), y); }
+SINTU Vec<N,T>           max(U x, Vec<N,T> y) { return max(Vec<N,T>(x), y); }
+
 // ... and same deal for vector/scalar operations.
-___ Vec<N,T>       operator+ (Vec<N,T> x, T y) { return x +  Vec<N,T>(y); }
-___ Vec<N,T>       operator- (Vec<N,T> x, T y) { return x -  Vec<N,T>(y); }
-___ Vec<N,T>       operator* (Vec<N,T> x, T y) { return x *  Vec<N,T>(y); }
-___ Vec<N,T>       operator/ (Vec<N,T> x, T y) { return x /  Vec<N,T>(y); }
-___ Vec<N,T>       operator^ (Vec<N,T> x, T y) { return x ^  Vec<N,T>(y); }
-___ Vec<N,T>       operator& (Vec<N,T> x, T y) { return x &  Vec<N,T>(y); }
-___ Vec<N,T>       operator| (Vec<N,T> x, T y) { return x |  Vec<N,T>(y); }
-___ Vec<N,Mask<T>> operator==(Vec<N,T> x, T y) { return x == Vec<N,T>(y); }
-___ Vec<N,Mask<T>> operator!=(Vec<N,T> x, T y) { return x != Vec<N,T>(y); }
-___ Vec<N,Mask<T>> operator<=(Vec<N,T> x, T y) { return x <= Vec<N,T>(y); }
-___ Vec<N,Mask<T>> operator>=(Vec<N,T> x, T y) { return x >= Vec<N,T>(y); }
-___ Vec<N,Mask<T>> operator< (Vec<N,T> x, T y) { return x <  Vec<N,T>(y); }
-___ Vec<N,Mask<T>> operator> (Vec<N,T> x, T y) { return x >  Vec<N,T>(y); }
-___ Vec<N,T>              min(Vec<N,T> x, T y) { return min(x, Vec<N,T>(y)); }
-___ Vec<N,T>              max(Vec<N,T> x, T y) { return max(x, Vec<N,T>(y)); }
+SINTU Vec<N,T>    operator+ (Vec<N,T> x, U y) { return x +  Vec<N,T>(y); }
+SINTU Vec<N,T>    operator- (Vec<N,T> x, U y) { return x -  Vec<N,T>(y); }
+SINTU Vec<N,T>    operator* (Vec<N,T> x, U y) { return x *  Vec<N,T>(y); }
+SINTU Vec<N,T>    operator/ (Vec<N,T> x, U y) { return x /  Vec<N,T>(y); }
+SINTU Vec<N,T>    operator^ (Vec<N,T> x, U y) { return x ^  Vec<N,T>(y); }
+SINTU Vec<N,T>    operator& (Vec<N,T> x, U y) { return x &  Vec<N,T>(y); }
+SINTU Vec<N,T>    operator| (Vec<N,T> x, U y) { return x |  Vec<N,T>(y); }
+SINTU Vec<N,M<T>> operator==(Vec<N,T> x, U y) { return x == Vec<N,T>(y); }
+SINTU Vec<N,M<T>> operator!=(Vec<N,T> x, U y) { return x != Vec<N,T>(y); }
+SINTU Vec<N,M<T>> operator<=(Vec<N,T> x, U y) { return x <= Vec<N,T>(y); }
+SINTU Vec<N,M<T>> operator>=(Vec<N,T> x, U y) { return x >= Vec<N,T>(y); }
+SINTU Vec<N,M<T>> operator< (Vec<N,T> x, U y) { return x <  Vec<N,T>(y); }
+SINTU Vec<N,M<T>> operator> (Vec<N,T> x, U y) { return x >  Vec<N,T>(y); }
+SINTU Vec<N,T>           min(Vec<N,T> x, U y) { return min(x, Vec<N,T>(y)); }
+SINTU Vec<N,T>           max(Vec<N,T> x, U y) { return max(x, Vec<N,T>(y)); }
+
+// All vector/scalar combinations for mad() with at least one vector.
+SINTU Vec<N,T> mad(U f, Vec<N,T> m, Vec<N,T> a) { return Vec<N,T>(f)*m + a; }
+SINTU Vec<N,T> mad(Vec<N,T> f, U m, Vec<N,T> a) { return f*Vec<N,T>(m) + a; }
+SINTU Vec<N,T> mad(Vec<N,T> f, Vec<N,T> m, U a) { return f*m + Vec<N,T>(a); }
+SINTU Vec<N,T> mad(Vec<N,T> f, U m, U a) { return f*Vec<N,T>(m) + Vec<N,T>(a); }
+SINTU Vec<N,T> mad(U f, Vec<N,T> m, U a) { return Vec<N,T>(f)*m + Vec<N,T>(a); }
+SINTU Vec<N,T> mad(U f, U m, Vec<N,T> a) { return Vec<N,T>(f)*Vec<N,T>(m) + a; }
 
 // The various op= operators, for vectors...
-___ Vec<N,T>& operator+=(Vec<N,T>& x, Vec<N,T> y) { return (x = x + y); }
-___ Vec<N,T>& operator-=(Vec<N,T>& x, Vec<N,T> y) { return (x = x - y); }
-___ Vec<N,T>& operator*=(Vec<N,T>& x, Vec<N,T> y) { return (x = x * y); }
-___ Vec<N,T>& operator/=(Vec<N,T>& x, Vec<N,T> y) { return (x = x / y); }
-___ Vec<N,T>& operator^=(Vec<N,T>& x, Vec<N,T> y) { return (x = x ^ y); }
-___ Vec<N,T>& operator&=(Vec<N,T>& x, Vec<N,T> y) { return (x = x & y); }
-___ Vec<N,T>& operator|=(Vec<N,T>& x, Vec<N,T> y) { return (x = x | y); }
+SINT Vec<N,T>& operator+=(Vec<N,T>& x, Vec<N,T> y) { return (x = x + y); }
+SINT Vec<N,T>& operator-=(Vec<N,T>& x, Vec<N,T> y) { return (x = x - y); }
+SINT Vec<N,T>& operator*=(Vec<N,T>& x, Vec<N,T> y) { return (x = x * y); }
+SINT Vec<N,T>& operator/=(Vec<N,T>& x, Vec<N,T> y) { return (x = x / y); }
+SINT Vec<N,T>& operator^=(Vec<N,T>& x, Vec<N,T> y) { return (x = x ^ y); }
+SINT Vec<N,T>& operator&=(Vec<N,T>& x, Vec<N,T> y) { return (x = x & y); }
+SINT Vec<N,T>& operator|=(Vec<N,T>& x, Vec<N,T> y) { return (x = x | y); }
+
 // ... for scalars...
-___ Vec<N,T>& operator+=(Vec<N,T>& x, T y) { return (x = x + Vec<N,T>(y)); }
-___ Vec<N,T>& operator-=(Vec<N,T>& x, T y) { return (x = x - Vec<N,T>(y)); }
-___ Vec<N,T>& operator*=(Vec<N,T>& x, T y) { return (x = x * Vec<N,T>(y)); }
-___ Vec<N,T>& operator/=(Vec<N,T>& x, T y) { return (x = x / Vec<N,T>(y)); }
-___ Vec<N,T>& operator^=(Vec<N,T>& x, T y) { return (x = x ^ Vec<N,T>(y)); }
-___ Vec<N,T>& operator&=(Vec<N,T>& x, T y) { return (x = x & Vec<N,T>(y)); }
-___ Vec<N,T>& operator|=(Vec<N,T>& x, T y) { return (x = x | Vec<N,T>(y)); }
+SINTU Vec<N,T>& operator+=(Vec<N,T>& x, U y) { return (x = x + Vec<N,T>(y)); }
+SINTU Vec<N,T>& operator-=(Vec<N,T>& x, U y) { return (x = x - Vec<N,T>(y)); }
+SINTU Vec<N,T>& operator*=(Vec<N,T>& x, U y) { return (x = x * Vec<N,T>(y)); }
+SINTU Vec<N,T>& operator/=(Vec<N,T>& x, U y) { return (x = x / Vec<N,T>(y)); }
+SINTU Vec<N,T>& operator^=(Vec<N,T>& x, U y) { return (x = x ^ Vec<N,T>(y)); }
+SINTU Vec<N,T>& operator&=(Vec<N,T>& x, U y) { return (x = x & Vec<N,T>(y)); }
+SINTU Vec<N,T>& operator|=(Vec<N,T>& x, U y) { return (x = x | Vec<N,T>(y)); }
+
 // ... and for shifts.
-___ Vec<N,T>& operator<<=(Vec<N,T>& x, int bits) { return (x = x << bits); }
-___ Vec<N,T>& operator>>=(Vec<N,T>& x, int bits) { return (x = x >> bits); }
-
-___ Vec<N,T>  ceil(Vec<N,T> x) { return map(x, [](T a) { return std:: ceil(a); }); }
-___ Vec<N,T> floor(Vec<N,T> x) { return map(x, [](T a) { return std::floor(a); }); }
-___ Vec<N,T> trunc(Vec<N,T> x) { return map(x, [](T a) { return std::trunc(a); }); }
-___ Vec<N,T> round(Vec<N,T> x) { return map(x, [](T a) { return std::round(a); }); }
-___ Vec<N,T>  sqrt(Vec<N,T> x) { return map(x, [](T a) { return std:: sqrt(a); }); }
-___ Vec<N,T>   abs(Vec<N,T> x) { return if_then_else(x < T(0), -x, x); }
-
-___ T min(Vec<N,T> x) { return *std::min_element(x.vals, x.vals+N); }
-___ T max(Vec<N,T> x) { return *std::max_element(x.vals, x.vals+N); }
-
-___ bool any(Vec<N,T> x) { return std::any_of(x.vals, x.vals+N, [](T a) { return a != Mask<T>(0); }); }
-___ bool all(Vec<N,T> x) { return std::all_of(x.vals, x.vals+N, [](T a) { return a != Mask<T>(0); }); }
-
-// These operations may have platform-specific results:
-//   - mad() may fuse the mul-add if FMA instructions are available;
-//   - rcp() and rsqrt() have plaform-specific precision.
-___ Vec<N,T>   mad(Vec<N,T> f, Vec<N,T> m, Vec<N,T> a) { return f*m+a; }
-___ Vec<N,T>   rcp(Vec<N,T> x) { return T(1) / x; }
-___ Vec<N,T> rsqrt(Vec<N,T> x) { return rcp(sqrt(x)); }
-
-// All vector/scalar combinations for mad().
-___ Vec<N,T> mad(T f, Vec<N,T> m, Vec<N,T> a) { return Vec<N,T>(f)*m + a; }
-___ Vec<N,T> mad(Vec<N,T> f, T m, Vec<N,T> a) { return f*Vec<N,T>(m) + a; }
-___ Vec<N,T> mad(Vec<N,T> f, Vec<N,T> m, T a) { return f*m + Vec<N,T>(a); }
-___ Vec<N,T> mad(Vec<N,T> f, T m, T a) { return f*Vec<N,T>(m) + Vec<N,T>(a); }
-___ Vec<N,T> mad(T f, Vec<N,T> m, T a) { return Vec<N,T>(f)*m + Vec<N,T>(a); }
-___ Vec<N,T> mad(T f, T m, Vec<N,T> a) { return Vec<N,T>(f)*Vec<N,T>(m) + a; }
-
-// Platform-specific specializations and overloads can now drop in here.
-
-}  // namespace skvx
-
-// These next few routines take extra template arguments that prevent
-// argument-dependent lookup.  They must live outside the skvx namespace,
-// but since they operate only on skvx::Vec, that shouldn't be a big deal.
+SINT Vec<N,T>& operator<<=(Vec<N,T>& x, int bits) { return (x = x << bits); }
+SINT Vec<N,T>& operator>>=(Vec<N,T>& x, int bits) { return (x = x >> bits); }
 
 // cast() Vec<N,S> to Vec<N,D>, as if applying a C-cast to each lane.
+template <typename D, typename S>
+static inline Vec<1,D> cast(Vec<1,S> src) { return (D)src.val; }
+
 template <typename D, int N, typename S>
-static inline ALWAYS_INLINE skvx::Vec<N,D> cast(skvx::Vec<N,S> src) {
+static inline Vec<N,D> cast(Vec<N,S> src) {
 #if !defined(SKNX_NO_SIMD) && defined(__clang__)
-    return skvx::bit_pun(__builtin_convertvector(skvx::to_vext(src), skvx::VExt<N,D>));
+    return to_vec(__builtin_convertvector(to_vext(src), VExt<N,D>));
 #else
-    return skvx::map(src, [](S a) { return (D)a; });
+    return join(cast<D>(src.lo), cast<D>(src.hi));
 #endif
 }
 
@@ -324,11 +388,62 @@
 //    shuffle<3,3,3,3>        (rgba) ~> {A,A,A,A}
 // The only real restriction is that the output also be a legal N=power-of-two sknx::Vec.
 template <int... Ix, int N, typename T>
-static inline ALWAYS_INLINE skvx::Vec<sizeof...(Ix),T> shuffle(skvx::Vec<N,T> x) {
+static inline Vec<sizeof...(Ix),T> shuffle(Vec<N,T> x) {
     return { x[Ix]... };
 }
 
-#undef ALWAYS_INLINE
-#undef ___
+#if !defined(SKNX_NO_SIMD)
+    // Platform-specific specializations and overloads can now drop in here.
+
+    #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE1
+        static inline Vec<4,float> sqrt(Vec<4,float> x) {
+            return bit_pun<Vec<4,float>>(_mm_sqrt_ps(bit_pun<__m128>(x)));
+        }
+        static inline Vec<4,float> rsqrt(Vec<4,float> x) {
+            return bit_pun<Vec<4,float>>(_mm_rsqrt_ps(bit_pun<__m128>(x)));
+        }
+        static inline Vec<4,float> rcp(Vec<4,float> x) {
+            return bit_pun<Vec<4,float>>(_mm_rcp_ps(bit_pun<__m128>(x)));
+        }
+
+        static inline Vec<2,float>  sqrt(Vec<2,float> x) {
+            return shuffle<0,1>( sqrt(shuffle<0,1,0,1>(x)));
+        }
+        static inline Vec<2,float> rsqrt(Vec<2,float> x) {
+            return shuffle<0,1>(rsqrt(shuffle<0,1,0,1>(x)));
+        }
+        static inline Vec<2,float>   rcp(Vec<2,float> x) {
+            return shuffle<0,1>(  rcp(shuffle<0,1,0,1>(x)));
+        }
+    #endif
+
+    #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41
+        static inline Vec<4,float> if_then_else(Vec<4,int> c, Vec<4,float> t, Vec<4,float> e) {
+            return bit_pun<Vec<4,float>>(_mm_blendv_ps(bit_pun<__m128>(e),
+                                                       bit_pun<__m128>(t),
+                                                       bit_pun<__m128>(c)));
+        }
+    #elif SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE1
+        static inline Vec<4,float> if_then_else(Vec<4,int> c, Vec<4,float> t, Vec<4,float> e) {
+            return bit_pun<Vec<4,float>>(_mm_or_ps(_mm_and_ps   (bit_pun<__m128>(c),
+                                                                 bit_pun<__m128>(t)),
+                                                   _mm_andnot_ps(bit_pun<__m128>(c),
+                                                                 bit_pun<__m128>(e))));
+        }
+    #elif defined(SK_ARM_HAS_NEON)
+        static inline Vec<4,float> if_then_else(Vec<4,int> c, Vec<4,float> t, Vec<4,float> e) {
+            return bit_pun<Vec<4,float>>(vbslq_f32(bit_pun<uint32x4_t> (c),
+                                                   bit_pun<float32x4_t>(t),
+                                                   bit_pun<float32x4_t>(e)));
+        }
+    #endif
+
+#endif  // !defined(SKNX_NO_SIMD)
+
+}  // namespace skvx
+
+#undef SINTU
+#undef SINT
+#undef SIT
 
 #endif//SKVX_DEFINED
diff --git a/include/svg/SkSVGCanvas.h b/include/svg/SkSVGCanvas.h
index 1cc091a..1bf85a0 100644
--- a/include/svg/SkSVGCanvas.h
+++ b/include/svg/SkSVGCanvas.h
@@ -11,14 +11,13 @@
 #include "SkCanvas.h"
 
 class SkWStream;
-class SkXMLWriter;
 
 class SK_API SkSVGCanvas {
 public:
     /**
      *  Returns a new canvas that will generate SVG commands from its draw calls, and send
-     *  them to the provided xmlwriter. Ownership of the xmlwriter is not transfered to the canvas,
-     *  but it must stay valid during the lifetime of the returned canvas.
+     *  them to the provided stream. Ownership of the stream is not transfered, and it must
+     *  remain valid for the lifetime of the returned canvas.
      *
      *  The canvas may buffer some drawing calls, so the output is not guaranteed to be valid
      *  or complete until the canvas instance is deleted.
@@ -27,9 +26,6 @@
      *  SVG element).
      */
     static std::unique_ptr<SkCanvas> Make(const SkRect& bounds, SkWStream*);
-
-    // Internal only.
-    static std::unique_ptr<SkCanvas> Make(const SkRect& bounds, SkXMLWriter*);
 };
 
 #endif
diff --git a/include/utils/SkNWayCanvas.h b/include/utils/SkNWayCanvas.h
index 09b071d..716c3b8 100644
--- a/include/utils/SkNWayCanvas.h
+++ b/include/utils/SkNWayCanvas.h
@@ -43,7 +43,6 @@
     void onDrawPaint(const SkPaint&) override;
     void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) override;
     void onDrawRect(const SkRect&, const SkPaint&) override;
-    void onDrawEdgeAARect(const SkRect&, SkCanvas::QuadAAFlags, SkColor, SkBlendMode) override;
     void onDrawRegion(const SkRegion&, const SkPaint&) override;
     void onDrawOval(const SkRect&, const SkPaint&) override;
     void onDrawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&) override;
@@ -60,8 +59,6 @@
     void onDrawImageLattice(const SkImage*, const Lattice&, const SkRect&, const SkPaint*) override;
     void onDrawImageNine(const SkImage*, const SkIRect& center, const SkRect& dst,
                          const SkPaint*) override;
-    void onDrawImageSet(const SkCanvas::ImageSetEntry[], int count, SkFilterQuality,
-                        SkBlendMode) override;
     void onDrawBitmapNine(const SkBitmap&, const SkIRect& center, const SkRect& dst,
                           const SkPaint*) override;
     void onDrawVerticesObject(const SkVertices*, const SkVertices::Bone bones[], int boneCount,
@@ -79,6 +76,11 @@
     void onDrawDrawable(SkDrawable*, const SkMatrix*) override;
     void onDrawAnnotation(const SkRect&, const char[], SkData*) override;
 
+    void onDrawEdgeAAQuad(const SkRect&, const SkPoint[4], QuadAAFlags, SkColor,
+                          SkBlendMode) override;
+    void onDrawEdgeAAImageSet(const ImageSetEntry[], int count, const SkPoint[], const SkMatrix[],
+                              const SkPaint*, SrcRectConstraint) override;
+
     void onFlush() override;
 
     class Iter;
diff --git a/include/utils/SkNoDrawCanvas.h b/include/utils/SkNoDrawCanvas.h
index ff1b027..a172632 100644
--- a/include/utils/SkNoDrawCanvas.h
+++ b/include/utils/SkNoDrawCanvas.h
@@ -51,7 +51,6 @@
     void onDrawPaint(const SkPaint&) override {}
     void onDrawPoints(PointMode, size_t, const SkPoint[], const SkPaint&) override {}
     void onDrawRect(const SkRect&, const SkPaint&) override {}
-    void onDrawEdgeAARect(const SkRect&, SkCanvas::QuadAAFlags, SkColor, SkBlendMode) override {}
     void onDrawRegion(const SkRegion&, const SkPaint&) override {}
     void onDrawOval(const SkRect&, const SkPaint&) override {}
     void onDrawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&) override {}
@@ -68,8 +67,6 @@
                           const SkPaint*) override {}
     void onDrawImageLattice(const SkImage*, const Lattice&, const SkRect&,
                             const SkPaint*) override {}
-    void onDrawImageSet(const SkCanvas::ImageSetEntry[], int, SkFilterQuality,
-                        SkBlendMode) override {}
     void onDrawBitmapLattice(const SkBitmap&, const Lattice&, const SkRect&,
                              const SkPaint*) override {}
     void onDrawVerticesObject(const SkVertices*, const SkVertices::Bone[], int, SkBlendMode,
@@ -79,6 +76,11 @@
     void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override {}
     void onDrawPicture(const SkPicture*, const SkMatrix*, const SkPaint*) override {}
 
+    void onDrawEdgeAAQuad(const SkRect&, const SkPoint[4], QuadAAFlags, SkColor,
+                          SkBlendMode) override {}
+    void onDrawEdgeAAImageSet(const ImageSetEntry[], int, const SkPoint[],
+                              const SkMatrix[], const SkPaint*, SrcRectConstraint) override {}
+
 private:
     typedef SkCanvasVirtualEnforcer<SkCanvas> INHERITED;
 };
diff --git a/include/utils/SkPaintFilterCanvas.h b/include/utils/SkPaintFilterCanvas.h
index 2781a20..a89c3ad 100644
--- a/include/utils/SkPaintFilterCanvas.h
+++ b/include/utils/SkPaintFilterCanvas.h
@@ -12,6 +12,8 @@
 #include "SkNWayCanvas.h"
 #include "SkTLazy.h"
 
+class SkAndroidFrameworkUtils;
+
 /** \class SkPaintFilterCanvas
 
     A utility proxy base class for implementing draw/paint filters.
@@ -68,7 +70,6 @@
     void onDrawPaint(const SkPaint&) override;
     void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) override;
     void onDrawRect(const SkRect&, const SkPaint&) override;
-    void onDrawEdgeAARect(const SkRect&, SkCanvas::QuadAAFlags, SkColor, SkBlendMode) override;
     void onDrawRRect(const SkRRect&, const SkPaint&) override;
     void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override;
     void onDrawRegion(const SkRegion&, const SkPaint&) override;
@@ -89,8 +90,6 @@
                          const SkPaint*) override;
     void onDrawImageLattice(const SkImage*, const Lattice&, const SkRect&,
                             const SkPaint*) override;
-    void onDrawImageSet(const SkCanvas::ImageSetEntry[], int count, SkFilterQuality,
-                        SkBlendMode) override;
     void onDrawVerticesObject(const SkVertices*, const SkVertices::Bone bones[], int boneCount,
                               SkBlendMode, const SkPaint&) override;
     void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
@@ -106,6 +105,11 @@
     void onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) override;
     void onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) override;
 
+    void onDrawEdgeAAQuad(const SkRect&, const SkPoint[4], QuadAAFlags, SkColor,
+                          SkBlendMode) override;
+    void onDrawEdgeAAImageSet(const ImageSetEntry[], int count, const SkPoint[], const SkMatrix[],
+                              const SkPaint*, SrcRectConstraint) override;
+
     // Forwarded to the wrapped canvas.
     sk_sp<SkSurface> onNewSurface(const SkImageInfo&, const SkSurfaceProps&) override;
     bool onPeekPixels(SkPixmap* pixmap) override;
@@ -117,6 +121,12 @@
     class AutoPaintFilter;
 
     SkCanvas* proxy() const { SkASSERT(fList.count() == 1); return fList[0]; }
+
+    SkPaintFilterCanvas* internal_private_asPaintFilterCanvas() const override {
+        return const_cast<SkPaintFilterCanvas*>(this);
+    }
+
+    friend class SkAndroidFrameworkUtils;
 };
 
 #endif
diff --git a/include/utils/SkTextUtils.h b/include/utils/SkTextUtils.h
index 9969032..24b995e 100644
--- a/include/utils/SkTextUtils.h
+++ b/include/utils/SkTextUtils.h
@@ -15,7 +15,7 @@
 
 class SkPath;
 
-class SkTextUtils {
+class SK_API SkTextUtils {
 public:
     enum Align {
         kLeft_Align,
diff --git a/infra/bots/README.md b/infra/bots/README.md
index 9683e18..73d5cdd 100644
--- a/infra/bots/README.md
+++ b/infra/bots/README.md
@@ -26,13 +26,7 @@
 gen_tasks.go or one of the following input JSON files, rather than tasks.json
 itself:
 
-  * android_map.json - Maps human-friendly names of Android devices to their
-      device codename and desired OS version. Edit this file when adding a new
-      type of Android device or updating the desired OS version.
   * cfg.json - Basic configuration information for gen_tasks.go.
-  * gpu_map.json - Maps human-friendly names of GPUs to an appropriate Swarming
-      dimension, typically the PCI ID of the GPU. Edit this file when adding a
-      new GPU.
   * jobs.json - The master list of all jobs to run. Edit this to add or remove
       bots.
 
diff --git a/infra/bots/android_bin.isolate b/infra/bots/android_bin.isolate
deleted file mode 100644
index 6491571..0000000
--- a/infra/bots/android_bin.isolate
+++ /dev/null
@@ -1,11 +0,0 @@
-{
-  'conditions': [
-    ['OS=="Android"', {
-      'variables': {
-        'files': [
-          '../../platform_tools/android/bin/',
-        ],
-      },
-    }],
-  ],
-}
diff --git a/infra/bots/assets/android_ndk_darwin/VERSION b/infra/bots/assets/android_ndk_darwin/VERSION
index c793025..301160a 100644
--- a/infra/bots/assets/android_ndk_darwin/VERSION
+++ b/infra/bots/assets/android_ndk_darwin/VERSION
@@ -1 +1 @@
-7
\ No newline at end of file
+8
\ No newline at end of file
diff --git a/infra/bots/assets/android_ndk_darwin/create.py b/infra/bots/assets/android_ndk_darwin/create.py
index 2b5faea..a78bf1b 100755
--- a/infra/bots/assets/android_ndk_darwin/create.py
+++ b/infra/bots/assets/android_ndk_darwin/create.py
@@ -15,7 +15,7 @@
 import shutil
 import subprocess
 
-NDK_VER = "android-ndk-r19-beta2"
+NDK_VER = "android-ndk-r19b"
 NDK_URL = \
     "https://dl.google.com/android/repository/%s-darwin-x86_64.zip" % NDK_VER
 
diff --git a/infra/bots/assets/android_ndk_linux/VERSION b/infra/bots/assets/android_ndk_linux/VERSION
index ca7bf83..da2d398 100644
--- a/infra/bots/assets/android_ndk_linux/VERSION
+++ b/infra/bots/assets/android_ndk_linux/VERSION
@@ -1 +1 @@
-13
\ No newline at end of file
+14
\ No newline at end of file
diff --git a/infra/bots/assets/android_ndk_linux/create.py b/infra/bots/assets/android_ndk_linux/create.py
index 50f118c..c610664 100755
--- a/infra/bots/assets/android_ndk_linux/create.py
+++ b/infra/bots/assets/android_ndk_linux/create.py
@@ -15,7 +15,7 @@
 import shutil
 import subprocess
 
-NDK_VER = "android-ndk-r19-beta2"
+NDK_VER = "android-ndk-r19b"
 NDK_URL = \
     "https://dl.google.com/android/repository/%s-linux-x86_64.zip" % NDK_VER
 
diff --git a/infra/bots/assets/android_ndk_windows/VERSION b/infra/bots/assets/android_ndk_windows/VERSION
index 301160a..f11c82a 100644
--- a/infra/bots/assets/android_ndk_windows/VERSION
+++ b/infra/bots/assets/android_ndk_windows/VERSION
@@ -1 +1 @@
-8
\ No newline at end of file
+9
\ No newline at end of file
diff --git a/infra/bots/assets/android_ndk_windows/create.py b/infra/bots/assets/android_ndk_windows/create.py
index 30ae2ba..b86fd1b 100755
--- a/infra/bots/assets/android_ndk_windows/create.py
+++ b/infra/bots/assets/android_ndk_windows/create.py
@@ -15,7 +15,7 @@
 import shutil
 import subprocess
 
-NDK_VER = "android-ndk-r19-beta2"
+NDK_VER = "android-ndk-r19b"
 NDK_URL = \
     "https://dl.google.com/android/repository/%s-windows-x86_64.zip" % NDK_VER
 
diff --git a/infra/bots/assets/clang_linux/VERSION b/infra/bots/assets/clang_linux/VERSION
index ca7bf83..da2d398 100644
--- a/infra/bots/assets/clang_linux/VERSION
+++ b/infra/bots/assets/clang_linux/VERSION
@@ -1 +1 @@
-13
\ No newline at end of file
+14
\ No newline at end of file
diff --git a/infra/bots/assets/clang_linux/create.py b/infra/bots/assets/clang_linux/create.py
index 887b3f0..e3cd224 100755
--- a/infra/bots/assets/clang_linux/create.py
+++ b/infra/bots/assets/clang_linux/create.py
@@ -15,7 +15,7 @@
 import tempfile
 
 REPO = "https://llvm.googlesource.com/"
-BRANCH = "release_70"
+BRANCH = "release_80"
 
 def create_asset(target_dir):
   # CMake will sometimes barf if we pass it a relative path.
diff --git a/infra/bots/assets/clang_win/VERSION b/infra/bots/assets/clang_win/VERSION
index 301160a..f11c82a 100644
--- a/infra/bots/assets/clang_win/VERSION
+++ b/infra/bots/assets/clang_win/VERSION
@@ -1 +1 @@
-8
\ No newline at end of file
+9
\ No newline at end of file
diff --git a/infra/bots/assets/clang_win/create.py b/infra/bots/assets/clang_win/create.py
index 570e77a..9ee1db1 100755
--- a/infra/bots/assets/clang_win/create.py
+++ b/infra/bots/assets/clang_win/create.py
@@ -18,7 +18,7 @@
 
 # Copied from CLANG_REVISION here:
 # https://cs.chromium.org/chromium/src/tools/clang/scripts/update.py
-CLANG_REVISION = '346388'
+CLANG_REVISION = '352921'
 CLANG_SUB_REVISION = '1'
 CLANG_PKG_VERSION = '%s-%s' % (CLANG_REVISION, CLANG_SUB_REVISION)
 GS_URL = ('https://commondatastorage.googleapis.com/chromium-browser-clang'
diff --git a/infra/bots/assets/cmake_mac/VERSION b/infra/bots/assets/cmake_mac/VERSION
new file mode 100644
index 0000000..c227083
--- /dev/null
+++ b/infra/bots/assets/cmake_mac/VERSION
@@ -0,0 +1 @@
+0
\ No newline at end of file
diff --git a/infra/bots/assets/cmake_mac/common.py b/infra/bots/assets/cmake_mac/common.py
new file mode 100755
index 0000000..caa0ad8
--- /dev/null
+++ b/infra/bots/assets/cmake_mac/common.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env 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.
+
+
+"""Common vars used by scripts in this directory."""
+
+
+import os
+import sys
+
+FILE_DIR = os.path.dirname(os.path.abspath(__file__))
+INFRA_BOTS_DIR = os.path.realpath(os.path.join(FILE_DIR, os.pardir, os.pardir))
+
+sys.path.insert(0, INFRA_BOTS_DIR)
+from assets import assets
+
+ASSET_NAME = os.path.basename(FILE_DIR)
+
+
+def run(cmd):
+  """Run a command, eg. "upload" or "download". """
+  assets.main([cmd, ASSET_NAME] + sys.argv[1:])
diff --git a/infra/bots/assets/cmake_mac/create.py b/infra/bots/assets/cmake_mac/create.py
new file mode 100755
index 0000000..a61b136
--- /dev/null
+++ b/infra/bots/assets/cmake_mac/create.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+#
+# Copyright 2019 Google Inc.
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+"""Create the asset."""
+
+
+import argparse
+import common
+import subprocess
+import utils
+
+VERSION = '3.13.4'
+URL = ('https://github.com/Kitware/CMake/releases/download/v%s/'
+       'cmake-%s-Darwin-x86_64.tar.gz') % (VERSION, VERSION)
+
+
+def create_asset(target_dir):
+  """Create the asset."""
+  with utils.tmp_dir():
+    subprocess.check_call(['wget', URL, '--output-document=cmake.tar.gz'])
+    subprocess.check_call(['tar', '--extract', '--gunzip', '--file',
+                           'cmake.tar.gz', '--directory', target_dir,
+                           '--strip-components', '1'])
+
+
+def main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument('--target_dir', '-t', required=True)
+  args = parser.parse_args()
+  create_asset(args.target_dir)
+
+
+if __name__ == '__main__':
+  main()
diff --git a/infra/bots/assets/cmake_mac/create_and_upload.py b/infra/bots/assets/cmake_mac/create_and_upload.py
new file mode 100755
index 0000000..de56a80
--- /dev/null
+++ b/infra/bots/assets/cmake_mac/create_and_upload.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env 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.
+
+
+"""Create the asset and upload it."""
+
+
+import argparse
+import common
+import os
+import subprocess
+import sys
+import utils
+
+
+def main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument('--gsutil')
+  args = parser.parse_args()
+
+  with utils.tmp_dir():
+    cwd = os.getcwd()
+    create_script = os.path.join(common.FILE_DIR, 'create.py')
+    upload_script = os.path.join(common.FILE_DIR, 'upload.py')
+
+    try:
+      subprocess.check_call(['python', create_script, '-t', cwd])
+      cmd = ['python', upload_script, '-t', cwd]
+      if args.gsutil:
+        cmd.extend(['--gsutil', args.gsutil])
+      subprocess.check_call(cmd)
+    except subprocess.CalledProcessError:
+      # Trap exceptions to avoid printing two stacktraces.
+      sys.exit(1)
+
+
+if __name__ == '__main__':
+  main()
diff --git a/infra/bots/assets/cmake_mac/download.py b/infra/bots/assets/cmake_mac/download.py
new file mode 100755
index 0000000..ca999e0
--- /dev/null
+++ b/infra/bots/assets/cmake_mac/download.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env 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.
+
+
+"""Download the current version of the asset."""
+
+
+import common
+
+
+if __name__ == '__main__':
+  common.run('download')
diff --git a/infra/bots/assets/cmake_mac/upload.py b/infra/bots/assets/cmake_mac/upload.py
new file mode 100755
index 0000000..bdfbda7
--- /dev/null
+++ b/infra/bots/assets/cmake_mac/upload.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env 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.
+
+
+"""Upload a new version of the asset."""
+
+
+import common
+
+
+if __name__ == '__main__':
+  common.run('upload')
diff --git a/infra/bots/assets/go_deps/VERSION b/infra/bots/assets/go_deps/VERSION
index 70e1a64..6e16ebf 100644
--- a/infra/bots/assets/go_deps/VERSION
+++ b/infra/bots/assets/go_deps/VERSION
@@ -1 +1 @@
-144
\ No newline at end of file
+208
\ No newline at end of file
diff --git a/infra/bots/assets/go_deps/create.py b/infra/bots/assets/go_deps/create.py
index 97f1479..bdd5cb5 100755
--- a/infra/bots/assets/go_deps/create.py
+++ b/infra/bots/assets/go_deps/create.py
@@ -16,12 +16,18 @@
 
 def create_asset(target_dir):
   """Create the asset."""
+  print 'Syncing Go checkouts...'
   env = {}
   env.update(os.environ)
   env['GOPATH'] = target_dir
   subprocess.check_call(
       ['go', 'get', '-u', '-t', 'go.skia.org/infra/...'],
       env=env)
+  skia_log = subprocess.check_output(
+      ['git', 'log', '-n1'],
+      cwd=os.path.join(target_dir, 'src', 'go.skia.org', 'infra'))
+  print 'Got go.skia.org/infra at:\n%s' % skia_log
+
   # There's a broken symlink which causes a lot of problems. Delete it.
   bad_symlink = os.path.join(
       target_dir, 'src', 'go.chromium.org', 'luci', 'machine-db', 'appengine',
diff --git a/infra/bots/assets/mesa_intel_driver_linux/VERSION b/infra/bots/assets/mesa_intel_driver_linux/VERSION
index c227083..7813681 100644
--- a/infra/bots/assets/mesa_intel_driver_linux/VERSION
+++ b/infra/bots/assets/mesa_intel_driver_linux/VERSION
@@ -1 +1 @@
-0
\ No newline at end of file
+5
\ No newline at end of file
diff --git a/infra/bots/assets/mesa_intel_driver_linux/create.py b/infra/bots/assets/mesa_intel_driver_linux/create.py
index 519baf7..279360f 100755
--- a/infra/bots/assets/mesa_intel_driver_linux/create.py
+++ b/infra/bots/assets/mesa_intel_driver_linux/create.py
@@ -13,9 +13,13 @@
 import argparse
 import subprocess
 
-DOCKER_IMAGE='gcr.io/skia-public/mesa-driver-builder:v1'
-BUILD_SCRIPT='/opt/build_mesa.sh'
-MESA_VERSION='18.1.7'
+# TODO(dogben): In the future, it might be simpler to build the docker image as
+# part of this script so that we don't need to push it to the container repo.
+# Doing so would make this script more repeatable, since someone could
+# accidentally change the Docker image that "v2" points to.
+DOCKER_IMAGE = 'gcr.io/skia-public/mesa-driver-builder:v2'
+BUILD_SCRIPT = '/opt/build_mesa.sh'
+MESA_VERSION = '18.3.3'
 
 
 def create_asset(target_dir):
diff --git a/infra/bots/assets/mesa_intel_driver_linux/mesa-driver-builder/Dockerfile b/infra/bots/assets/mesa_intel_driver_linux/mesa-driver-builder/Dockerfile
index 6134c5b..cf75031 100644
--- a/infra/bots/assets/mesa_intel_driver_linux/mesa-driver-builder/Dockerfile
+++ b/infra/bots/assets/mesa_intel_driver_linux/mesa-driver-builder/Dockerfile
@@ -1,6 +1,9 @@
-FROM debian:9.4
+FROM debian:9.6
 
-# Enabling backports gives us access to clang-6 and new versions of libdrm2 package.
+ENV DEBIAN_FRONTEND noninteractive
+
+# Enabling backports gives us access to clang-6 and makes it more likely that
+# 'apt-get build-dep' will install the correct dependencies.
 # Mesa builds newer than 17.0.4 or so require libdrm > 2.4.75, but the
 # default one in stretch is 2.4.74.
 # Note that the hosts that use these drivers will also need the newer version of libdrm2
@@ -8,61 +11,19 @@
 #     symbol lookup error: ./mesa_intel_driver/libGL.so.1: undefined symbol: drmGetDevice2
 #
 # Hosts can install this by adding the stretch-backports debian source (see next RUN)
-# and the performing `sudo apt-get update && sudo apt-get install libdrm2=2.4.91-2~bpo9+1`
-RUN echo "deb http://ftp.debian.org/debian stretch-backports main" >> /etc/apt/sources.list
-
-RUN apt-get update && apt-get upgrade -y
-
-ENV DEBIAN_FRONTEND noninteractive
-
-RUN apt-get install -y \
-    autoconf \
-    bison \
-    build-essential \
-    clang-6.0 \
-    flex \
-    libdrm-amdgpu1=2.4.91-2~bpo9+1 \
-    libdrm-dev=2.4.91-2~bpo9+1 \
-    libdrm-intel1=2.4.91-2~bpo9+1 \
-    libdrm-nouveau2=2.4.91-2~bpo9+1 \
-    libdrm-radeon1=2.4.91-2~bpo9+1 \
-    libdrm2=2.4.91-2~bpo9+1 \
-    libomxil-bellagio-dev \
-    libpthread-stubs0-dev \
-    libtool \
-    libva-dev \
-    libx11-xcb-dev \
-    libxcb-dri2-0-dev \
-    libxcb-dri3-dev \
-    libxcb-glx0-dev \
-    libxcb-present-dev \
-    libxcb1-dev \
-    libxdamage-dev \
-    libxext-dev \
-    libxshmfence-dev \
-    llvm-dev \
-    pkg-config \
-    python-pip \
-    python2.7 \
-    scons \
-    software-properties-common \
-    wget \
-    x11proto-dri2-dev \
-    x11proto-dri3-dev \
-    x11proto-gl-dev \
-    x11proto-present-dev \
-    x11proto-xext-dev \
-    xserver-xorg-core \
-    xserver-xorg-dev
-
-RUN pip install mako
+# and then performing `sudo apt-get update && sudo apt-get install libdrm2=2.4.95-1~bpo9+1`
+RUN echo "deb http://ftp.debian.org/debian stretch-backports main" >> /etc/apt/sources.list && \
+    echo "deb-src http://ftp.debian.org/debian stretch-backports main" >> /etc/apt/sources.list && \
+    apt-get update && apt-get upgrade -y && \
+    apt-get install -y wget clang-6.0 && \
+    apt-get -t stretch-backports build-dep -y mesa && \
+    rm -rf /var/lib/apt/lists/*
 
 ENV CC="/usr/lib/llvm-6.0/bin/clang" \
     CXX="/usr/lib/llvm-6.0/bin/clang++" \
-    PATH=/usr/lib/llvm-6.0/bin:$PATH
+    PATH=/usr/lib/llvm-6.0/bin:$PATH \
+    # Default to this version of MESA, but it can be overridden with
+    # -e MESA_VERSION=X.Y.Z when running the docker container
+    MESA_VERSION=18.3.2
 
-# Default to this version of MESA, but it can be overridden with
-# -e MESA_VERSION=X.Y.Z when running the docker container
-ENV MESA_VERSION=18.1.7
-
-COPY ./build_mesa.sh /opt/build_mesa.sh
\ No newline at end of file
+COPY ./build_mesa.sh /opt/build_mesa.sh
diff --git a/infra/bots/assets/mesa_intel_driver_linux/mesa-driver-builder/build_mesa.sh b/infra/bots/assets/mesa_intel_driver_linux/mesa-driver-builder/build_mesa.sh
index ad9eeec..37a5aa5 100755
--- a/infra/bots/assets/mesa_intel_driver_linux/mesa-driver-builder/build_mesa.sh
+++ b/infra/bots/assets/mesa_intel_driver_linux/mesa-driver-builder/build_mesa.sh
@@ -13,16 +13,13 @@
 
 pushd /tmp
 
-wget ftp://ftp.freedesktop.org/pub/mesa/mesa-$MESA_VERSION.tar.gz
+wget https://mesa.freedesktop.org/archive/mesa-$MESA_VERSION.tar.gz
 tar --gunzip --extract --file mesa-$MESA_VERSION.tar.gz
 cd mesa-$MESA_VERSION/
 
 ./configure --with-dri-drivers=i965 --with-gallium-drivers= --with-vulkan-drivers=intel
 make -j 50
 
-rm -rf lib/gallium
-rm -f  lib/nouveau_vieux_dri.so lib/r200_dri.so lib/radeon_dri.so
-
 cp lib/* /OUT
 cp src/intel/vulkan/intel_icd.x86_64.json /OUT
 
diff --git a/infra/bots/assets/skp/VERSION b/infra/bots/assets/skp/VERSION
index 9a13717..a5b5e0f 100644
--- a/infra/bots/assets/skp/VERSION
+++ b/infra/bots/assets/skp/VERSION
@@ -1 +1 @@
-169
\ No newline at end of file
+181
\ No newline at end of file
diff --git a/infra/bots/buildstats/buildstats_cpp.py b/infra/bots/buildstats/buildstats_cpp.py
index af189c5..5401f91 100644
--- a/infra/bots/buildstats/buildstats_cpp.py
+++ b/infra/bots/buildstats/buildstats_cpp.py
@@ -83,5 +83,5 @@
     output.write(json.dumps(results, indent=2))
 
 
-if '__main__' == __name__:
+if __name__ == '__main__':
   main()
diff --git a/infra/bots/buildstats/buildstats_flutter.py b/infra/bots/buildstats/buildstats_flutter.py
index 050d60f..684d200 100644
--- a/infra/bots/buildstats/buildstats_flutter.py
+++ b/infra/bots/buildstats/buildstats_flutter.py
@@ -156,5 +156,5 @@
     print '%-10s: %-80s in %s' % (bytes_or_kb(filesize), symbol, this_file)
 
 
-if '__main__' == __name__:
+if __name__ == '__main__':
   main()
diff --git a/infra/bots/buildstats/buildstats_wasm.py b/infra/bots/buildstats/buildstats_wasm.py
index 4e24fac..ac85033 100644
--- a/infra/bots/buildstats/buildstats_wasm.py
+++ b/infra/bots/buildstats/buildstats_wasm.py
@@ -75,5 +75,5 @@
     output.write(json.dumps(results, indent=2))
 
 
-if '__main__' == __name__:
+if __name__ == '__main__':
   main()
diff --git a/infra/bots/buildstats/buildstats_web.py b/infra/bots/buildstats/buildstats_web.py
index bdee07c..e856c98 100644
--- a/infra/bots/buildstats/buildstats_web.py
+++ b/infra/bots/buildstats/buildstats_web.py
@@ -59,5 +59,5 @@
     output.write(json.dumps(results, indent=2))
 
 
-if '__main__' == __name__:
+if __name__ == '__main__':
   main()
diff --git a/infra/bots/buildstats/make_treemap.py b/infra/bots/buildstats/make_treemap.py
new file mode 100644
index 0000000..ec1fb87
--- /dev/null
+++ b/infra/bots/buildstats/make_treemap.py
@@ -0,0 +1,44 @@
+# Copyright 2019 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.
+
+
+"""Creates a .tar.gz file containing an HTML treemap displaying the codesize.
+
+   Requires docker to be installed.
+
+   Example usage:
+   python make_treemap.py $SKIA_ROOT/out/Release/skottie_tool /tmp/size
+
+"""
+
+
+import os
+import subprocess
+import sys
+import tempfile
+
+DOCKER_IMAGE = 'gcr.io/skia-public/binary-size:v1'
+DOCKER_SCRIPT = '/opt/binary_size/src/run_binary_size_analysis.py'
+
+def main():
+  input_file = sys.argv[1]
+  out_dir = sys.argv[2]
+
+  input_base = os.path.basename(input_file)
+  input_dir = os.path.dirname(input_file)
+  temp_out = tempfile.mkdtemp('treemap')
+
+  subprocess.check_call(['docker', 'run', '--volume', '%s:/IN' % input_dir,
+                         '--volume', '%s:/OUT' % temp_out,
+                         DOCKER_IMAGE, DOCKER_SCRIPT,
+                         '--library', '/IN/%s' % input_base,
+                         '--destdir', '/OUT'])
+
+  subprocess.check_call(['tar', '--directory=%s' % temp_out, '-zcf',
+                         '%s/%s_tree.tar.gz' % (out_dir, input_base),
+                         '.'])
+
+
+if __name__ == '__main__':
+  main()
diff --git a/infra/bots/gen_tasks.go b/infra/bots/gen_tasks.go
index 0df2d16..2925bd3 100644
--- a/infra/bots/gen_tasks.go
+++ b/infra/bots/gen_tasks.go
@@ -41,9 +41,8 @@
 	ISOLATE_WIN_TOOLCHAIN_NAME = "Housekeeper-PerCommit-IsolateWinToolchain"
 
 	DEFAULT_OS_DEBIAN    = "Debian-9.4"
-	DEFAULT_OS_LINUX_GCE = DEFAULT_OS_DEBIAN
+	DEFAULT_OS_LINUX_GCE = "Debian-9.8"
 	DEFAULT_OS_MAC       = "Mac-10.13.6"
-	DEFAULT_OS_UBUNTU    = "Ubuntu-14.04"
 	DEFAULT_OS_WIN       = "Windows-2016Server-14393"
 
 	DEFAULT_PROJECT = "skia"
@@ -66,7 +65,6 @@
 	// Name prefix for upload jobs.
 	PREFIX_UPLOAD = "Upload"
 
-	SERVICE_ACCOUNT_BOOKMAKER          = "skia-bookmaker@skia-swarming-bots.iam.gserviceaccount.com"
 	SERVICE_ACCOUNT_COMPILE            = "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
 	SERVICE_ACCOUNT_HOUSEKEEPER        = "skia-external-housekeeper@skia-swarming-bots.iam.gserviceaccount.com"
 	SERVICE_ACCOUNT_RECREATE_SKPS      = "skia-recreate-skps@skia-swarming-bots.iam.gserviceaccount.com"
@@ -274,6 +272,7 @@
 		"repository":           specs.PLACEHOLDER_REPO,
 		"revision":             specs.PLACEHOLDER_REVISION,
 		"swarm_out_dir":        outputDir,
+		"task_id":              specs.PLACEHOLDER_TASK_ID,
 	}
 	for k, v := range extraProps {
 		properties[k] = v
@@ -315,7 +314,7 @@
 		Dependencies: []string{BUNDLE_RECIPES_NAME},
 		Dimensions:   dimensions,
 		EnvPrefixes: map[string][]string{
-			"PATH": []string{"cipd_bin_packages", "cipd_bin_packages/bin"},
+			"PATH":                    []string{"cipd_bin_packages", "cipd_bin_packages/bin"},
 			"VPYTHON_VIRTUALENV_ROOT": []string{"cache/vpython"},
 		},
 		ExtraTags: map[string]string{
@@ -361,14 +360,12 @@
 // deriveCompileTaskName returns the name of a compile task based on the given
 // job name.
 func deriveCompileTaskName(jobName string, parts map[string]string) string {
-	if strings.Contains(jobName, "Bookmaker") {
-		return "Build-Debian9-GCC-x86_64-Release"
-	} else if parts["role"] == "Test" || parts["role"] == "Perf" || parts["role"] == "Calmbench" {
+	if parts["role"] == "Test" || parts["role"] == "Perf" || parts["role"] == "Calmbench" {
 		task_os := parts["os"]
 		ec := []string{}
 		if val := parts["extra_config"]; val != "" {
 			ec = strings.Split(val, "_")
-			ignore := []string{"Skpbench", "AbandonGpuContext", "PreAbandonGpuContext", "Valgrind", "ReleaseAndAbandonGpuContext", "CCPR", "FSAA", "FAAA", "FDAA", "NativeFonts", "GDI", "NoGPUThreads", "ProcDump", "DDL1", "DDL3", "T8888", "DDLTotal", "DDLRecord", "9x9", "BonusConfigs"}
+			ignore := []string{"Skpbench", "AbandonGpuContext", "PreAbandonGpuContext", "Valgrind", "ReleaseAndAbandonGpuContext", "CCPR", "FSAA", "FAAA", "FDAA", "NativeFonts", "GDI", "NoGPUThreads", "ProcDump", "DDL1", "DDL3", "T8888", "DDLTotal", "DDLRecord", "9x9", "BonusConfigs", "SkottieTracing"}
 			keep := make([]string, 0, len(ec))
 			for _, part := range ec {
 				if !util.In(part, ignore) {
@@ -395,6 +392,8 @@
 			task_os = "Win"
 		} else if strings.Contains(task_os, "Ubuntu") || strings.Contains(task_os, "Debian") {
 			task_os = "Debian9"
+		} else if strings.Contains(task_os, "Mac") {
+			task_os = "Mac"
 		}
 		jobNameMap := map[string]string{
 			"role":          "Build",
@@ -449,12 +448,11 @@
 			"ChromeOS":   "ChromeOS",
 			"Debian9":    DEFAULT_OS_DEBIAN,
 			"Mac":        DEFAULT_OS_MAC,
-			"Ubuntu14":   DEFAULT_OS_UBUNTU,
-			"Ubuntu17":   "Ubuntu-17.04",
+			"Mac10.13":   DEFAULT_OS_MAC,
+			"Mac10.14":   "Mac-10.14.3",
 			"Ubuntu18":   "Ubuntu-18.04",
 			"Win":        DEFAULT_OS_WIN,
-			"Win10":      "Windows-10-17763.195",
-			"Win2k8":     "Windows-2008ServerR2-SP1",
+			"Win10":      "Windows-10-17763.379",
 			"Win2016":    DEFAULT_OS_WIN,
 			"Win7":       "Windows-7-SP1",
 			"Win8":       "Windows-8.1-SP0",
@@ -468,7 +466,7 @@
 			d["os"] = "Windows-10-16299.309"
 		}
 		if d["os"] == DEFAULT_OS_WIN {
-			// TODO(dogben): Temporarily add image dimension during upgrade.
+			// Upgrades result in a new image but not a new OS version.
 			d["image"] = "windows-server-2016-dc-v20190108"
 		}
 	} else {
@@ -492,6 +490,7 @@
 				"NexusPlayer":     {"fugu", "OPR2.170623.027"},
 				"Pixel":           {"sailfish", "PPR1.180610.009"},
 				"Pixel2XL":        {"taimen", "PPR1.180610.009"},
+				"Pixel3":          {"blueline", "PQ1A.190105.004"},
 			}[parts["model"]]
 			if !ok {
 				glog.Fatalf("Entry %q not found in Android mapping.", parts["model"])
@@ -521,7 +520,7 @@
 				glog.Fatalf("Please update defaultSwarmDimensions for SKQP::Emulator %s %s.", parts["os"], parts["model"])
 			}
 			d["cpu"] = "x86-64-i5-7260U"
-			d["os"] = "Debian-9.4"
+			d["os"] = DEFAULT_OS_DEBIAN
 			// KVM means Kernel-based Virtual Machine, that is, can this vm virtualize commands
 			// For us, this means, can we run an x86 android emulator on it.
 			// kjlubick tried running this on GCE, but it was a bit too slow on the large install.
@@ -535,6 +534,7 @@
 				},
 				"AVX2": {
 					"GCE":            "x86-64-Haswell_GCE",
+					"MacBookAir7.2":  "x86-64-i5-5350U",
 					"MacBookPro11.5": "x86-64-i7-4870HQ",
 					"NUC5i7RYH":      "x86-64-i7-5557U",
 				},
@@ -591,8 +591,8 @@
 					glog.Fatalf("Entry %q not found in Ubuntu GPU mapping.", parts["cpu_or_gpu_value"])
 				}
 				if parts["os"] == "Ubuntu18" && parts["cpu_or_gpu_value"] == "QuadroP400" {
-					// Ubuntu18 has a slightly newer GPU driver.
-					gpu = "10de:1cb3-390.87"
+					// Ubuntu18 has a newer GPU driver.
+					gpu = "10de:1cb3-415.27"
 				}
 				d["gpu"] = gpu
 			} else if strings.Contains(parts["os"], "Mac") {
@@ -636,8 +636,8 @@
 				return dockerGceDimensions()
 			}
 			if parts["role"] == "BuildStats" {
-				// Doesn't require a lot of resources
-				return linuxGceDimensions(MACHINE_TYPE_MEDIUM)
+				// Doesn't require a lot of resources, but some steps require docker
+				return dockerGceDimensions()
 			}
 			// Use many-core machines for Build tasks.
 			return linuxGceDimensions(MACHINE_TYPE_LARGE)
@@ -953,6 +953,7 @@
 func checkGeneratedFiles(b *specs.TasksCfgBuilder, name string) string {
 	task := kitchenTask(name, "check_generated_files", "swarm_recipe.isolate", SERVICE_ACCOUNT_COMPILE, linuxGceDimensions(MACHINE_TYPE_LARGE), nil, OUTPUT_NONE)
 	task.Caches = append(task.Caches, CACHES_WORKDIR...)
+	usesGo(b, task)
 	b.MustAddTask(name, task)
 	return name
 }
@@ -966,18 +967,6 @@
 	return name
 }
 
-// bookmaker generates a Bookmaker task. Returns the name of the last task
-// in the generated chain of tasks, which the Job should add as a dependency.
-func bookmaker(b *specs.TasksCfgBuilder, name, compileTaskName string) string {
-	task := kitchenTask(name, "bookmaker", "swarm_recipe.isolate", SERVICE_ACCOUNT_BOOKMAKER, linuxGceDimensions(MACHINE_TYPE_SMALL), nil, OUTPUT_NONE)
-	task.Caches = append(task.Caches, CACHES_WORKDIR...)
-	task.CipdPackages = append(task.CipdPackages, CIPD_PKGS_GIT...)
-	task.Dependencies = append(task.Dependencies, compileTaskName, isolateCIPDAsset(b, ISOLATE_GO_DEPS_NAME))
-	timeout(task, 2*time.Hour)
-	b.MustAddTask(name, task)
-	return name
-}
-
 // androidFrameworkCompile generates an Android Framework Compile task. Returns
 // the name of the last task in the generated chain of tasks, which the Job
 // should add as a dependency.
@@ -998,6 +987,8 @@
 	return name
 }
 
+var BUILD_STATS_NO_UPLOAD = []string{"BuildStats-Debian9-Clang-x86_64-Release"}
+
 func buildstats(b *specs.TasksCfgBuilder, name string, parts map[string]string, compileTaskName string) string {
 	task := kitchenTask(name, "compute_buildstats", "swarm_recipe.isolate", "", swarmDimensions(parts), nil, OUTPUT_PERF)
 	task.Dependencies = append(task.Dependencies, compileTaskName)
@@ -1005,8 +996,8 @@
 	b.MustAddTask(name, task)
 
 	// Upload release results (for tracking in perf)
-	// We have some jobs that are FYI (e.g. Debug-CanvasKit)
-	if strings.Contains(name, "Release") {
+	// We have some jobs that are FYI (e.g. Debug-CanvasKit, tree-map generator)
+	if strings.Contains(name, "Release") && !util.In(name, BUILD_STATS_NO_UPLOAD) {
 		uploadName := fmt.Sprintf("%s%s%s", PREFIX_UPLOAD, jobNameSchema.Sep, name)
 		extraProps := map[string]string{
 			"gs_bucket": CONFIG.GsBucketNano,
@@ -1136,6 +1127,8 @@
 		timeout(task, 9*time.Hour)
 		task.Expiration = 48 * time.Hour
 		task.CipdPackages = append(task.CipdPackages, b.MustGetCipdPackageFromAsset("valgrind"))
+		// Since Valgrind runs on the same bots as the CQ, we restrict Valgrind to a subset of the bots
+		// to ensure there are always bots free for CQ tasks.
 		task.Dimensions = append(task.Dimensions, "valgrind:1")
 	} else if strings.Contains(parts["extra_config"], "MSAN") {
 		timeout(task, 9*time.Hour)
@@ -1174,6 +1167,8 @@
 		recipe = "perf_pathkit"
 	} else if strings.Contains(name, "CanvasKit") {
 		recipe = "perf_canvaskit"
+	} else if strings.Contains(name, "SkottieTracing") {
+		recipe = "perf_skottietrace"
 	}
 	task := kitchenTask(name, recipe, isolate, "", swarmDimensions(parts), nil, OUTPUT_PERF)
 	task.CipdPackages = append(task.CipdPackages, pkgs...)
@@ -1188,12 +1183,16 @@
 		timeout(task, 9*time.Hour)
 		task.Expiration = 48 * time.Hour
 		task.CipdPackages = append(task.CipdPackages, b.MustGetCipdPackageFromAsset("valgrind"))
+		// Since Valgrind runs on the same bots as the CQ, we restrict Valgrind to a subset of the bots
+		// to ensure there are always bots free for CQ tasks.
 		task.Dimensions = append(task.Dimensions, "valgrind:1")
 	} else if strings.Contains(parts["extra_config"], "MSAN") {
 		timeout(task, 9*time.Hour)
 	} else if parts["arch"] == "x86" && parts["configuration"] == "Debug" {
 		// skia:6737
 		timeout(task, 6*time.Hour)
+	} else if strings.Contains(parts["extra_config"], "Skottie") {
+		task.CipdPackages = append(task.CipdPackages, b.MustGetCipdPackageFromAsset("lottie-samples"))
 	}
 	iid := internalHardwareLabel(parts)
 	if iid != nil {
@@ -1239,22 +1238,12 @@
 	// Use MACHINE_TYPE_LARGE because it seems to save time versus MEDIUM and we want presubmit to be
 	// fast.
 	task := kitchenTask(name, "run_presubmit", "empty.isolate", SERVICE_ACCOUNT_COMPILE, linuxGceDimensions(MACHINE_TYPE_LARGE), extraProps, OUTPUT_NONE)
-
-	replaceArg := func(key, value string) {
-		found := false
-		for idx, arg := range task.Command {
-			if arg == key {
-				task.Command[idx+1] = value
-				found = true
-			}
-		}
-		if !found {
-			task.Command = append(task.Command, key, value)
-		}
-	}
-	replaceArg("-repository", "https://chromium.googlesource.com/chromium/tools/build")
-	replaceArg("-revision", "HEAD")
 	usesGit(task, name)
+	task.CipdPackages = append(task.CipdPackages, &specs.CipdPackage{
+		Name:    "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",
+		Path:    "recipe_bundle",
+		Version: "refs/heads/master",
+	})
 	task.Dependencies = []string{} // No bundled recipes for this one.
 	b.MustAddTask(name, task)
 	return name
@@ -1346,9 +1335,6 @@
 		priority = 1
 		deps = append(deps, presubmit(b, name))
 	}
-	if strings.Contains(name, "Bookmaker") {
-		deps = append(deps, bookmaker(b, name, compileTaskName))
-	}
 
 	// Common assets needed by the remaining bots.
 
@@ -1402,6 +1388,14 @@
 		deps = append(deps, calmbench(b, name, parts, compileTaskName, compileParentName))
 	}
 
+	// Valgrind runs at a low priority so that it doesn't occupy all the bots.
+	if strings.Contains(name, "Valgrind") {
+		// Priority of 0.085 should result in Valgrind tasks with a blamelist of ~10 commits having the
+		// same score as other tasks with a blamelist of 1 commit, when we have insufficient bot
+		// capacity to run more frequently.
+		priority = 0.085
+	}
+
 	// BuildStats bots. This computes things like binary size.
 	if parts["role"] == "BuildStats" {
 		deps = append(deps, buildstats(b, name, parts, compileTaskName))
diff --git a/infra/bots/ios_bin.isolate b/infra/bots/ios_bin.isolate
index b51e513..b2d542e 100644
--- a/infra/bots/ios_bin.isolate
+++ b/infra/bots/ios_bin.isolate
@@ -1,25 +1,7 @@
 {
-  'conditions': [
-    ['OS=="iOS-9.3.1"', {
-      'variables': {
-        'files': [
-          '../../platform_tools/ios/bin/',
-        ],
-      },
-    }],
-    ['OS=="iOS-10.3.1"', {
-      'variables': {
-        'files': [
-          '../../platform_tools/ios/bin/',
-        ],
-      },
-    }],
-    ['OS=="iOS-11.4.1"', {
-      'variables': {
-        'files': [
-          '../../platform_tools/ios/bin/',
-        ],
-      },
-    }],
-  ],
+  'variables': {
+    'files': [
+      '../../platform_tools/ios/bin/',
+    ],
+  },
 }
diff --git a/infra/bots/jobs.json b/infra/bots/jobs.json
index afda5e3..ab710e2 100644
--- a/infra/bots/jobs.json
+++ b/infra/bots/jobs.json
@@ -39,6 +39,7 @@
   "Build-Debian9-Clang-x86_64-Debug-SwiftShader",
   "Build-Debian9-Clang-x86_64-Debug-Tidy",
   "Build-Debian9-Clang-x86_64-Debug-Vulkan",
+  "Build-Debian9-Clang-x86_64-Debug-Wuffs",
   "Build-Debian9-Clang-x86_64-Release",
   "Build-Debian9-Clang-x86_64-Release-ANGLE",
   "Build-Debian9-Clang-x86_64-Release-ASAN",
@@ -57,6 +58,7 @@
   "Build-Debian9-Clang-x86_64-Release-TSAN",
   "Build-Debian9-Clang-x86_64-Release-TSAN_Vulkan",
   "Build-Debian9-Clang-x86_64-Release-Vulkan",
+  "Build-Debian9-Clang-x86_64-Release-Wuffs",
   "Build-Debian9-EMCC-asmjs-Debug-PathKit",
   "Build-Debian9-EMCC-asmjs-Release-PathKit",
   "Build-Debian9-EMCC-wasm-Debug-CanvasKit",
@@ -89,6 +91,7 @@
   "Build-Mac-Clang-x86_64-Debug-CommandBuffer",
   "Build-Mac-Clang-x86_64-Debug-Metal",
   "Build-Mac-Clang-x86_64-Debug-MoltenVK_Vulkan",
+  "Build-Mac-Clang-x86_64-Debug-OpenCL",
   "Build-Mac-Clang-x86_64-Release",
   "Build-Mac-Clang-x86_64-Release-CommandBuffer",
   "Build-Mac-Clang-x86_64-Release-Metal",
@@ -103,21 +106,26 @@
   "Build-Win-Clang-x86_64-Debug-OpenCL",
   "Build-Win-Clang-x86_64-Debug-UBSAN",
   "Build-Win-Clang-x86_64-Debug-Vulkan",
+  "Build-Win-Clang-x86_64-Debug-Wuffs",
   "Build-Win-Clang-x86_64-Release",
   "Build-Win-Clang-x86_64-Release-ANGLE",
   "Build-Win-Clang-x86_64-Release-UBSAN",
   "Build-Win-Clang-x86_64-Release-Vulkan",
+  "Build-Win-Clang-x86_64-Release-Shared",
   "Build-Win-MSVC-arm64-Debug",
   "Build-Win-MSVC-arm64-Release",
   "Build-Win-MSVC-x86-Debug",
   "Build-Win-MSVC-x86-Release",
   "Build-Win-MSVC-x86_64-Debug",
   "Build-Win-MSVC-x86_64-Debug-MSRTC",
-  "Build-Win-MSVC-x86_64-Debug-Vulkan",
   "Build-Win-MSVC-x86_64-Debug-MSRTC_Vulkan",
+  "Build-Win-MSVC-x86_64-Debug-Vulkan",
+  "Build-Win-MSVC-x86_64-Debug-Wuffs",
   "Build-Win-MSVC-x86_64-Release",
   "Build-Win-MSVC-x86_64-Release-Vulkan",
+  "Build-Win-MSVC-x86_64-Release-Shared",
   "BuildStats-Debian9-Clang-arm-Release-Flutter_Android",
+  "BuildStats-Debian9-Clang-x86_64-Release",
   "BuildStats-Debian9-EMCC-asmjs-Release-PathKit",
   "BuildStats-Debian9-EMCC-wasm-Debug-CanvasKit",
   "BuildStats-Debian9-EMCC-wasm-Debug-CanvasKit_CPU",
@@ -125,8 +133,7 @@
   "BuildStats-Debian9-EMCC-wasm-Release-CanvasKit_CPU",
   "BuildStats-Debian9-EMCC-wasm-Release-PathKit",
   "Calmbench-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All",
-  "Calmbench-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All",
-  "Housekeeper-Nightly-Bookmaker",
+  "Calmbench-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All",
   "Housekeeper-Nightly-RecreateSKPs_Canary",
   "Housekeeper-Nightly-UpdateGoDEPS",
   "Housekeeper-OnDemand-Presubmit",
@@ -141,6 +148,7 @@
   "Housekeeper-Weekly-RecreateSKPs",
   "Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Debug-All-Android",
   "Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android",
+  "Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android_SkottieTracing",
   "Perf-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android",
   "Perf-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android",
   "Perf-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android_NoGPUThreads",
@@ -194,6 +202,13 @@
   "Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Skpbench",
   "Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan",
   "Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan_Skpbench",
+  "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android",
+  "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_Vulkan",
+  "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android",
+  "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_CCPR_Skpbench",
+  "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Skpbench",
+  "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan",
+  "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan_Skpbench",
   "Perf-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Debug-All",
   "Perf-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Release-All",
   "Perf-ChromeOS-Clang-AcerChromebook13_CB5_311-GPU-TegraK1-arm-Debug-All",
@@ -222,6 +237,8 @@
   "Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-BonusConfigs",
   "Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Fast",
   "Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_FORCE_RASTER_PIPELINE_BLITTER",
+  "Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SkottieTracing",
+  "Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Wuffs",
   "Perf-Debian9-Clang-GCE-CPU-AVX512-x86_64-Debug-All",
   "Perf-Debian9-Clang-GCE-CPU-AVX512-x86_64-Release-All",
   "Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All",
@@ -240,37 +257,30 @@
   "Perf-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit",
   "Perf-Debian9-GCC-GCE-CPU-AVX2-x86-Debug-All",
   "Perf-Debian9-GCC-GCE-CPU-AVX2-x86_64-Debug-All",
-  "Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All",
-  "Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All",
-  "Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer",
-  "Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All",
-  "Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All",
-  "Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-CommandBuffer",
-  "Perf-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All",
-  "Perf-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN",
-  "Perf-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All",
-  "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All",
-  "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan",
-  "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All",
-  "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-CommandBuffer",
-  "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan",
-  "Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All",
-  "Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All",
-  "Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer",
-  "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All",
-  "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN",
-  "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan",
-  "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All",
-  "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ASAN",
-  "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan",
-  "Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41",
-  "Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41",
+  "Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All",
+  "Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All",
+  "Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer",
+  "Perf-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All",
+  "Perf-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN",
+  "Perf-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All",
+  "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All",
+  "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan",
+  "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All",
+  "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-CommandBuffer",
+  "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan",
+  "Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All",
+  "Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All",
+  "Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer",
+  "Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All",
+  "Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All",
+  "Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-CommandBuffer",
   "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All",
   "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN",
   "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan",
   "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All",
   "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ASAN",
   "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan",
+  "Perf-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41",
   "Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All",
   "Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All-ANGLE",
   "Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All-Vulkan",
@@ -390,12 +400,24 @@
   "Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android_Vulkan",
   "Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android",
   "Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_Vulkan",
-  "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-All-Android",
-  "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android",
+  "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_0_4-Android",
+  "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_1_4-Android",
+  "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_2_4-Android",
+  "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_3_4-Android",
+  "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_0_4-Android",
+  "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_1_4-Android",
+  "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_2_4-Android",
+  "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_3_4-Android",
   "Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-All-Android",
   "Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-All-Android",
-  "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-All-Android",
-  "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-All-Android",
+  "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_0_4-Android",
+  "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_1_4-Android",
+  "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_2_4-Android",
+  "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_3_4-Android",
+  "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_0_4-Android",
+  "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_1_4-Android",
+  "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_2_4-Android",
+  "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_3_4-Android",
   "Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Debug-All-Android",
   "Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android",
   "Test-Android-Clang-Pixel-CPU-Snapdragon821-arm-Release-All-Android_ASAN",
@@ -415,6 +437,12 @@
   "Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android_Vulkan",
   "Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android",
   "Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan",
+  "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android",
+  "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_DDL1_Vulkan",
+  "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_DDL3_Vulkan",
+  "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_Vulkan",
+  "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android",
+  "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan",
   "Test-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Debug-All",
   "Test-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Release-All",
   "Test-ChromeOS-Clang-AcerChromebook13_CB5_311-GPU-TegraK1-arm-Debug-All",
@@ -439,6 +467,7 @@
   "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-NativeFonts",
   "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SK_USE_DISCARDABLE_SCALEDIMAGECACHE",
   "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SafeStack",
+  "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-Wuffs",
   "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All",
   "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-ASAN",
   "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-BonusConfigs",
@@ -477,78 +506,57 @@
   "Test-Debian9-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Release-All",
   "Test-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Debug-All",
   "Test-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Release-All",
-  "Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit",
   "Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit",
-  "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-CanvasKit",
-  "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit",
   "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit",
   "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit",
-  "Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Debug-All-CanvasKit",
   "Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit",
   "Test-Debian9-GCC-GCE-CPU-AVX2-x86-Debug-All",
   "Test-Debian9-GCC-GCE-CPU-AVX2-x86-Release-All",
   "Test-Debian9-GCC-GCE-CPU-AVX2-x86_64-Debug-All",
   "Test-Debian9-GCC-GCE-CPU-AVX2-x86_64-Release-All",
-  "Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All",
-  "Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All-CommandBuffer",
-  "Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All",
-  "Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts",
-  "Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All",
-  "Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All-CommandBuffer",
-  "Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All",
-  "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All",
-  "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN",
-  "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-NativeFonts",
-  "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All",
-  "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All-TSAN",
-  "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All",
-  "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-CommandBuffer",
-  "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal",
-  "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan",
-  "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All",
-  "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-Metal",
-  "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan",
-  "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-TSAN",
-  "Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All",
-  "Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer",
-  "Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All",
-  "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All",
-  "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN",
-  "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1",
-  "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan",
-  "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3",
-  "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN",
-  "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN_Vulkan",
-  "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan",
-  "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan",
-  "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All",
-  "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ASAN",
-  "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN",
-  "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN_Vulkan",
-  "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_TSAN",
-  "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_TSAN_Vulkan",
-  "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-PreAbandonGpuContext",
-  "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan",
-  "Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41",
-  "Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_PreAbandonGpuContext_SK_CPU_LIMIT_SSE41",
-  "Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41",
+  "Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All",
+  "Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All-CommandBuffer",
+  "Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All",
+  "Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts",
+  "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All",
+  "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN",
+  "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-NativeFonts",
+  "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All",
+  "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All-TSAN",
+  "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All",
+  "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-CommandBuffer",
+  "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal",
+  "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan",
+  "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-OpenCL",
+  "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All",
+  "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-Metal",
+  "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan",
+  "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-TSAN",
+  "Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All",
+  "Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer",
+  "Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All",
+  "Test-Mac10.14-Clang-MacBookAir7.2-CPU-AVX2-x86_64-Debug-All-NativeFonts",
+  "Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All",
+  "Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All-CommandBuffer",
+  "Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All",
   "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All",
   "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN",
   "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1",
   "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan",
   "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3",
   "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN",
-  "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN_Vulkan",
   "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan",
   "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan",
   "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All",
   "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ASAN",
   "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN",
-  "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN_Vulkan",
   "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_TSAN",
   "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_TSAN_Vulkan",
   "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-PreAbandonGpuContext",
   "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan",
+  "Test-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41",
+  "Test-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_PreAbandonGpuContext_SK_CPU_LIMIT_SSE41",
+  "Test-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41",
   "Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All",
   "Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All-ANGLE",
   "Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All-Vulkan",
@@ -614,20 +622,18 @@
   "Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-Vulkan",
   "Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All",
   "Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All-MSRTC",
-  "Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan",
   "Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All-MSRTC_Vulkan",
+  "Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan",
   "Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All",
   "Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan",
   "Test-Win2016-Clang-GCE-CPU-AVX2-x86-Debug-All",
   "Test-Win2016-Clang-GCE-CPU-AVX2-x86-Release-All",
   "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All",
   "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FAAA",
-  "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FDAA",
   "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FSAA",
   "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-UBSAN",
   "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All",
   "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FAAA",
-  "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FDAA",
   "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FSAA",
   "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-UBSAN",
   "Test-Win2016-MSVC-GCE-CPU-AVX2-x86-Debug-All",
diff --git a/infra/bots/perf_skia_bundled.isolate b/infra/bots/perf_skia_bundled.isolate
index 8504083..6001f3e 100644
--- a/infra/bots/perf_skia_bundled.isolate
+++ b/infra/bots/perf_skia_bundled.isolate
@@ -1,6 +1,5 @@
 {
   'includes': [
-    'android_bin.isolate',
     'assets.isolate',
     'ios_bin.isolate',
     'resources.isolate',
diff --git a/infra/bots/recipe_modules/build/canvaskit.py b/infra/bots/recipe_modules/build/canvaskit.py
index fedb27c..92bebff 100644
--- a/infra/bots/recipe_modules/build/canvaskit.py
+++ b/infra/bots/recipe_modules/build/canvaskit.py
@@ -2,7 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-DOCKER_IMAGE = 'gcr.io/skia-public/emsdk-release:1.38.16_v1'
+DOCKER_IMAGE = 'gcr.io/skia-public/canvaskit-emsdk:1.38.27_v1'
 INNER_BUILD_SCRIPT = '/SRC/skia/infra/canvaskit/build_canvaskit.sh'
 
 BUILD_PRODUCTS_ISOLATE_WHITELIST_WASM = [
diff --git a/infra/bots/recipe_modules/build/default.py b/infra/bots/recipe_modules/build/default.py
index f9fa564..e24ad5f 100644
--- a/infra/bots/recipe_modules/build/default.py
+++ b/infra/bots/recipe_modules/build/default.py
@@ -182,6 +182,11 @@
         '-L%s' % swiftshader_out,
     ])
   if 'CommandBuffer' in extra_tokens:
+    # CommandBuffer runs against GLES version of CommandBuffer also, so
+    # include both.
+    args.update({
+      'skia_gl_standard': '""',
+    })
     chrome_dir = checkout_root
     api.run.run_once(build_command_buffer, api, chrome_dir, skia_dir, out_dir)
   if 'MSAN' in extra_tokens:
@@ -197,6 +202,7 @@
       'skia_enable_pdf':        'false',
       'skia_use_expat':         'false',
       'skia_use_freetype':      'false',
+      'skia_use_harfbuzz':      'false',
       'skia_use_libjpeg_turbo': 'false',
       'skia_use_libpng':        'false',
       'skia_use_libwebp':       'false',
@@ -254,6 +260,9 @@
     assert sanitize == ''
     sanitize = 'MSVC'
 
+  if 'Wuffs' in extra_tokens:
+    args['skia_use_wuffs'] = 'true'
+
   for (k,v) in {
     'cc':  cc,
     'cxx': cxx,
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Android_API26.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Android_API26.json
index 738fdbe..9a415d3 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Android_API26.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Android_API26.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -58,7 +58,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-arm-Release-Android_API26/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -73,7 +73,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Android_ASAN.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Android_ASAN.json
index 633f1f4..4d51919 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Android_ASAN.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Android_ASAN.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -58,7 +58,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-arm-Release-Android_ASAN/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -73,7 +73,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Chromebook_GLES.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Chromebook_GLES.json
index b81271a..405b70e 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Chromebook_GLES.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Chromebook_GLES.json
@@ -23,7 +23,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/armhf_sysroot/lib",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -39,7 +39,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/armhf_sysroot/lib",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -55,7 +55,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/armhf_sysroot/lib",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -63,7 +63,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-arm-Release-Chromebook_GLES/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -78,7 +78,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Flutter_Android.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Flutter_Android.json
index 8e8f7c0..29258a3 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Flutter_Android.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Flutter_Android.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/src",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn_gen"
   },
@@ -36,7 +36,7 @@
     "cwd": "[START_DIR]/cache/work/src",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "build_flutter"
   },
@@ -44,7 +44,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-arm-Release-Flutter_Android/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -59,7 +59,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86-devrel-Android_SKQP.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86-devrel-Android_SKQP.json
index e1b4037..d4de6dd 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86-devrel-Android_SKQP.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86-devrel-Android_SKQP.json
@@ -31,7 +31,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Build SKQP apk with Docker"
   },
@@ -39,7 +39,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86-devrel-Android_SKQP/devrel",
       "[START_DIR]/[SWARM_OUT_DIR]/out/devrel"
     ],
@@ -54,7 +54,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Chromebook_GLES.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Chromebook_GLES.json
index c19ce47..6fb5125 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Chromebook_GLES.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Chromebook_GLES.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -52,7 +52,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -60,7 +60,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Debug-Chromebook_GLES/Debug",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
     ],
@@ -75,7 +75,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Coverage.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Coverage.json
index c5677b2..919196f 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Coverage.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Coverage.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -58,7 +58,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Debug-Coverage/Debug",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
     ],
@@ -73,7 +73,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-MSAN.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-MSAN.json
index a06e0ecf..5e2d21b 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-MSAN.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-MSAN.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -58,7 +58,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Debug-MSAN/Debug",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
     ],
@@ -73,7 +73,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-OpenCL.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-OpenCL.json
index a22159a..313bb4a 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-OpenCL.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-OpenCL.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -58,7 +58,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Debug-OpenCL/Debug",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
     ],
@@ -73,7 +73,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-SK_CPU_LIMIT_SSE41.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-SK_CPU_LIMIT_SSE41.json
index 28eb7f9..401cd92 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-SK_CPU_LIMIT_SSE41.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-SK_CPU_LIMIT_SSE41.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -58,7 +58,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Debug-SK_CPU_LIMIT_SSE41/Debug",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
     ],
@@ -73,7 +73,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-SafeStack.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-SafeStack.json
index 6a29247..095dda8 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-SafeStack.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-SafeStack.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -58,7 +58,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Debug-SafeStack/Debug",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
     ],
@@ -73,7 +73,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Tidy.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Tidy.json
index e42e703..ff762ea 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Tidy.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Tidy.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "[START_DIR]/clang_linux/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/clang_linux/bin:<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "[START_DIR]/clang_linux/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/clang_linux/bin:<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -58,7 +58,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Debug-Tidy/Debug",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
     ],
@@ -73,7 +73,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Wuffs.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Wuffs.json
new file mode 100644
index 0000000..4a74503
--- /dev/null
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Wuffs.json
@@ -0,0 +1,99 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/cache/work/skia/infra/bots/assets/clang_linux/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get clang_linux VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/cache/work/skia/bin/fetch-gn"
+    ],
+    "cwd": "[START_DIR]/cache/work/skia",
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "fetch-gn"
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cache/work/skia/bin/gn",
+      "gen",
+      "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Debug-Wuffs/Debug",
+      "--args=cc=\"[START_DIR]/clang_linux/bin/clang\" cxx=\"[START_DIR]/clang_linux/bin/clang++\" extra_cflags=[\"-B[START_DIR]/clang_linux/bin\", \"-DDUMMY_clang_linux_version=42\", \"-O1\"] extra_ldflags=[\"-B[START_DIR]/clang_linux/bin\", \"-fuse-ld=lld\"] skia_use_wuffs=true target_cpu=\"x86_64\""
+    ],
+    "cwd": "[START_DIR]/cache/work/skia",
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "gn gen"
+  },
+  {
+    "cmd": [
+      "ninja",
+      "-C",
+      "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Debug-Wuffs/Debug"
+    ],
+    "cwd": "[START_DIR]/cache/work/skia",
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "ninja"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Debug-Wuffs/Debug",
+      "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
+    ],
+    "infra_step": true,
+    "name": "copy build products",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import errno@@@",
+      "@@@STEP_LOG_LINE@python.inline@import glob@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import shutil@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
+      "@@@STEP_LOG_LINE@python.inline@except OSError as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if e.errno != errno.EEXIST:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@for pattern in build_products_whitelist:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  path = os.path.join(src, pattern)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  for f in glob.glob(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    dst_path = os.path.join(dst, os.path.relpath(f, src))@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not os.path.isdir(os.path.dirname(dst_path)):@@@",
+      "@@@STEP_LOG_LINE@python.inline@      os.makedirs(os.path.dirname(dst_path))@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Copying build product %s to %s' % (f, dst_path)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    shutil.move(f, dst_path)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-ASAN.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-ASAN.json
index a75ea49..0c60d6a 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-ASAN.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-ASAN.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -58,7 +58,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Release-ASAN/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -73,7 +73,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-CMake.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-CMake.json
index 49f8d48..7fe0d17 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-CMake.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-CMake.json
@@ -29,7 +29,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Build Skia using CMake in Docker"
   },
@@ -37,7 +37,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Release-CMake/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -52,7 +52,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-Fast.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-Fast.json
index c3bd70e..b4d7c2d 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-Fast.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-Fast.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -58,7 +58,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Release-Fast/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -73,7 +73,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-NoDEPS.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-NoDEPS.json
index ec9d330..1329194 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-NoDEPS.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-NoDEPS.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -32,12 +32,12 @@
       "[START_DIR]/cache/work/skia/bin/gn",
       "gen",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Release-NoDEPS/Release",
-      "--args=cc=\"[START_DIR]/clang_linux/bin/clang\" cxx=\"[START_DIR]/clang_linux/bin/clang++\" extra_cflags=[\"-B[START_DIR]/clang_linux/bin\", \"-DDUMMY_clang_linux_version=42\"] extra_ldflags=[\"-B[START_DIR]/clang_linux/bin\", \"-fuse-ld=lld\"] is_debug=false is_official_build=true skia_enable_fontmgr_empty=true skia_enable_gpu=true skia_enable_pdf=false skia_use_expat=false skia_use_freetype=false skia_use_libjpeg_turbo=false skia_use_libpng=false skia_use_libwebp=false skia_use_vulkan=false skia_use_zlib=false target_cpu=\"x86_64\""
+      "--args=cc=\"[START_DIR]/clang_linux/bin/clang\" cxx=\"[START_DIR]/clang_linux/bin/clang++\" extra_cflags=[\"-B[START_DIR]/clang_linux/bin\", \"-DDUMMY_clang_linux_version=42\"] extra_ldflags=[\"-B[START_DIR]/clang_linux/bin\", \"-fuse-ld=lld\"] is_debug=false is_official_build=true skia_enable_fontmgr_empty=true skia_enable_gpu=true skia_enable_pdf=false skia_use_expat=false skia_use_freetype=false skia_use_harfbuzz=false skia_use_libjpeg_turbo=false skia_use_libpng=false skia_use_libwebp=false skia_use_vulkan=false skia_use_zlib=false target_cpu=\"x86_64\""
     ],
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -58,7 +58,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Release-NoDEPS/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -73,7 +73,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-Static.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-Static.json
index 0d2e6cb..0ab1d8a 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-Static.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-Static.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -58,7 +58,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Release-Static/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -73,7 +73,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-SwiftShader.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-SwiftShader.json
index e3977ad..1a2ff0e 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-SwiftShader.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-SwiftShader.json
@@ -40,7 +40,7 @@
       "CC": "[START_DIR]/clang_linux/bin/clang",
       "CHROME_HEADLESS": "1",
       "CXX": "[START_DIR]/clang_linux/bin/clang++",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]:[START_DIR]/cmake_linux/bin"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]:[START_DIR]/cmake_linux/bin"
     },
     "name": "swiftshader cmake"
   },
@@ -57,7 +57,7 @@
       "CC": "[START_DIR]/clang_linux/bin/clang",
       "CHROME_HEADLESS": "1",
       "CXX": "[START_DIR]/clang_linux/bin/clang++",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]:[START_DIR]/cmake_linux/bin"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]:[START_DIR]/cmake_linux/bin"
     },
     "name": "swiftshader ninja"
   },
@@ -70,7 +70,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -85,7 +85,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -98,7 +98,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -106,7 +106,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Release-SwiftShader/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -121,7 +121,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
@@ -144,7 +144,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Release-SwiftShader/Release/swiftshader_out",
       "[START_DIR]/[SWARM_OUT_DIR]/swiftshader_out"
     ],
@@ -159,7 +159,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-Vulkan.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-Vulkan.json
index f609a7b..17d9b42 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-Vulkan.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-Vulkan.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -58,7 +58,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Release-Vulkan/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -73,7 +73,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-asmjs-Debug-PathKit.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-asmjs-Debug-PathKit.json
index 0dc7b51..b886ad4 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-asmjs-Debug-PathKit.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-asmjs-Debug-PathKit.json
@@ -23,7 +23,7 @@
       "[START_DIR]/cache/work:/SRC",
       "--volume",
       "[START_DIR]/cache/docker/pathkit:/OUT",
-      "gcr.io/skia-public/emsdk-release:1.38.16_v1",
+      "gcr.io/skia-public/emsdk-release:1.38.27_v1",
       "/SRC/skia/infra/pathkit/build_pathkit.sh",
       "debug",
       "asm.js"
@@ -31,7 +31,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Build PathKit with Docker"
   },
@@ -39,7 +39,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-EMCC-asmjs-Debug-PathKit/Debug",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
     ],
@@ -54,7 +54,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-asmjs-Release-PathKit.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-asmjs-Release-PathKit.json
index 7522253..6dcb8af 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-asmjs-Release-PathKit.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-asmjs-Release-PathKit.json
@@ -23,14 +23,14 @@
       "[START_DIR]/cache/work:/SRC",
       "--volume",
       "[START_DIR]/cache/docker/pathkit:/OUT",
-      "gcr.io/skia-public/emsdk-release:1.38.16_v1",
+      "gcr.io/skia-public/emsdk-release:1.38.27_v1",
       "/SRC/skia/infra/pathkit/build_pathkit.sh",
       "asm.js"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Build PathKit with Docker"
   },
@@ -38,7 +38,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-EMCC-asmjs-Release-PathKit/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -53,7 +53,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Debug-CanvasKit.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Debug-CanvasKit.json
index 66618a0..f73db88 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Debug-CanvasKit.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Debug-CanvasKit.json
@@ -23,14 +23,14 @@
       "[START_DIR]/cache/work:/SRC",
       "--volume",
       "[START_DIR]/cache/docker/canvaskit:/OUT",
-      "gcr.io/skia-public/emsdk-release:1.38.16_v1",
+      "gcr.io/skia-public/canvaskit-emsdk:1.38.27_v1",
       "/SRC/skia/infra/canvaskit/build_canvaskit.sh",
       "debug"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Build CanvasKit with Docker"
   },
@@ -38,7 +38,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-EMCC-wasm-Debug-CanvasKit/Debug",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
     ],
@@ -53,7 +53,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Debug-PathKit.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Debug-PathKit.json
index daafe00..a0d1d04 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Debug-PathKit.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Debug-PathKit.json
@@ -23,14 +23,14 @@
       "[START_DIR]/cache/work:/SRC",
       "--volume",
       "[START_DIR]/cache/docker/pathkit:/OUT",
-      "gcr.io/skia-public/emsdk-release:1.38.16_v1",
+      "gcr.io/skia-public/emsdk-release:1.38.27_v1",
       "/SRC/skia/infra/pathkit/build_pathkit.sh",
       "debug"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Build PathKit with Docker"
   },
@@ -38,7 +38,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-EMCC-wasm-Debug-PathKit/Debug",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
     ],
@@ -53,7 +53,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU.json
index 29723d5..bceaf3fb 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU.json
@@ -23,14 +23,14 @@
       "[START_DIR]/cache/work:/SRC",
       "--volume",
       "[START_DIR]/cache/docker/canvaskit:/OUT",
-      "gcr.io/skia-public/emsdk-release:1.38.16_v1",
+      "gcr.io/skia-public/canvaskit-emsdk:1.38.27_v1",
       "/SRC/skia/infra/canvaskit/build_canvaskit.sh",
       "cpu"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Build CanvasKit with Docker"
   },
@@ -38,7 +38,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -53,7 +53,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-PathKit.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-PathKit.json
index cdefc74..e806468 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-PathKit.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-PathKit.json
@@ -23,13 +23,13 @@
       "[START_DIR]/cache/work:/SRC",
       "--volume",
       "[START_DIR]/cache/docker/pathkit:/OUT",
-      "gcr.io/skia-public/emsdk-release:1.38.16_v1",
+      "gcr.io/skia-public/emsdk-release:1.38.27_v1",
       "/SRC/skia/infra/pathkit/build_pathkit.sh"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Build PathKit with Docker"
   },
@@ -37,7 +37,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-EMCC-wasm-Release-PathKit/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -52,7 +52,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-arm-Release-Chromecast.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-arm-Release-Chromecast.json
index 02d3129..5ccdaa9 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-arm-Release-Chromecast.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-arm-Release-Chromecast.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -52,7 +52,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -60,7 +60,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-GCC-arm-Release-Chromecast/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -75,7 +75,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-loongson3a-Release.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-loongson3a-Release.json
index c8b9d0d..c7ae492 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-loongson3a-Release.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-loongson3a-Release.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -38,7 +38,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/mips64el_toolchain_linux/lib/x86_64-linux-gnu/",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -52,7 +52,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/mips64el_toolchain_linux/lib/x86_64-linux-gnu/",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -60,7 +60,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-GCC-loongson3a-Release/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -75,7 +75,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-x86_64-Release-ANGLE.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-x86_64-Release-ANGLE.json
index 597343d..af25ec1 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-x86_64-Release-ANGLE.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-x86_64-Release-ANGLE.json
@@ -8,7 +8,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -23,7 +23,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -36,7 +36,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -44,7 +44,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-GCC-x86_64-Release-ANGLE/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -59,7 +59,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-x86_64-Release-NoGPU.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-x86_64-Release-NoGPU.json
index 21766c7..f90845b 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-x86_64-Release-NoGPU.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-x86_64-Release-NoGPU.json
@@ -8,7 +8,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -23,7 +23,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -36,7 +36,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -44,7 +44,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-GCC-x86_64-Release-NoGPU/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -59,7 +59,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-x86_64-Release-Shared.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-x86_64-Release-Shared.json
index 54dea7c..d0a246c 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-x86_64-Release-Shared.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-x86_64-Release-Shared.json
@@ -8,7 +8,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -23,7 +23,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -36,7 +36,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -44,7 +44,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-GCC-x86_64-Release-Shared/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -59,7 +59,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-arm64-Debug-Android_Vulkan.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-arm64-Debug-Android_Vulkan.json
index 5fc69b1..509e4cc 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-arm64-Debug-Android_Vulkan.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-arm64-Debug-Android_Vulkan.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -58,7 +58,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Mac-Clang-arm64-Debug-Android_Vulkan/Debug",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
     ],
@@ -73,7 +73,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-arm64-Debug-iOS.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-arm64-Debug-iOS.json
index f13dd9f..6ad6e2e 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-arm64-Debug-iOS.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-arm64-Debug-iOS.json
@@ -43,7 +43,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -58,7 +58,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -71,7 +71,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -79,7 +79,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Mac-Clang-arm64-Debug-iOS/Debug",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
     ],
@@ -94,7 +94,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Debug-ASAN.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Debug-ASAN.json
index 2aa1572..ffc0af2 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Debug-ASAN.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Debug-ASAN.json
@@ -43,7 +43,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -58,7 +58,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -71,7 +71,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -79,7 +79,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Mac-Clang-x86_64-Debug-ASAN/Debug",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
     ],
@@ -94,7 +94,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json
index 162759b..4d42b4e 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json
@@ -49,7 +49,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "build command_buffer"
   },
@@ -62,7 +62,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -72,12 +72,12 @@
       "[START_DIR]/cache/work/skia/bin/gn",
       "gen",
       "[START_DIR]/cache/work/skia/out/Build-Mac-Clang-x86_64-Debug-CommandBuffer/Debug",
-      "--args=cc=\"clang\" cxx=\"clang++\" extra_cflags=[\"-DDUMMY_xcode_build_version=9c40b\", \"-O1\"] target_cpu=\"x86_64\""
+      "--args=cc=\"clang\" cxx=\"clang++\" extra_cflags=[\"-DDUMMY_xcode_build_version=9c40b\", \"-O1\"] skia_gl_standard=\"\" target_cpu=\"x86_64\""
     ],
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -90,7 +90,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -98,7 +98,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Mac-Clang-x86_64-Debug-CommandBuffer/Debug",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
     ],
@@ -113,7 +113,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Debug-Metal.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Debug-Metal.json
index c769d5e..62dcc89 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Debug-Metal.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Debug-Metal.json
@@ -43,7 +43,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -58,7 +58,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -71,7 +71,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -79,7 +79,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Mac-Clang-x86_64-Debug-Metal/Debug",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
     ],
@@ -94,7 +94,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Release-MoltenVK_Vulkan.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Release-MoltenVK_Vulkan.json
index 2f3af16..ca4f0ae 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Release-MoltenVK_Vulkan.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Release-MoltenVK_Vulkan.json
@@ -43,7 +43,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -58,7 +58,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -71,7 +71,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -79,7 +79,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Mac-Clang-x86_64-Release-MoltenVK_Vulkan/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -94,7 +94,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-arm64-Release-Android.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-arm64-Release-Android.json
index 5d9beaf..8fa04cb 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-arm64-Release-Android.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-arm64-Release-Android.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -58,7 +58,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Win-Clang-arm64-Release-Android/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -73,7 +73,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86-Debug-Exceptions.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86-Debug-Exceptions.json
index d2508e0..8e64639 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86-Debug-Exceptions.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86-Debug-Exceptions.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -58,7 +58,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Win-Clang-x86-Debug-Exceptions/Debug",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
     ],
@@ -73,7 +73,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Debug-OpenCL.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Debug-OpenCL.json
index 465f3b8..72e621b 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Debug-OpenCL.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Debug-OpenCL.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -58,7 +58,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Win-Clang-x86_64-Debug-OpenCL/Debug_x64",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug_x64"
     ],
@@ -73,7 +73,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Release-Vulkan.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Release-Vulkan.json
index af18c44..3d0f3a2 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Release-Vulkan.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Release-Vulkan.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -58,7 +58,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Win-Clang-x86_64-Release-Vulkan/Release_x64",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release_x64"
     ],
@@ -73,7 +73,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-MSVC-x86_64-Debug-MSRTC.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-MSVC-x86_64-Debug-MSRTC.json
index de07a5d..11bc0a9 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-MSVC-x86_64-Debug-MSRTC.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-MSVC-x86_64-Debug-MSRTC.json
@@ -8,7 +8,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -23,7 +23,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -36,7 +36,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -44,7 +44,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Win-MSVC-x86_64-Debug-MSRTC/Debug_x64",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug_x64"
     ],
@@ -59,7 +59,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Housekeeper-PerCommit-CheckGeneratedFiles.json b/infra/bots/recipe_modules/build/examples/full.expected/Housekeeper-PerCommit-CheckGeneratedFiles.json
index 8ffddaf..a87a8af 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Housekeeper-PerCommit-CheckGeneratedFiles.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Housekeeper-PerCommit-CheckGeneratedFiles.json
@@ -8,7 +8,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-clang-format"
@@ -37,7 +37,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "[START_DIR]/cache/work/skia/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/cache/work/skia/bin:<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "[START_DIR]/cache/work/skia/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/cache/work/skia/bin:<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -58,7 +58,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Housekeeper-PerCommit-CheckGeneratedFiles/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
@@ -73,7 +73,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-universal-devrel-All-Android_SKQP.json b/infra/bots/recipe_modules/build/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-universal-devrel-All-Android_SKQP.json
index af18cbd..e1a467e 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-universal-devrel-All-Android_SKQP.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-universal-devrel-All-Android_SKQP.json
@@ -22,7 +22,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -41,7 +41,7 @@
       "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "build firebase runner"
   },
@@ -55,7 +55,7 @@
       "ANDROID_NDK": "[START_DIR]/android_ndk_linux",
       "APK_OUTPUT_DIR": "[START_DIR]/cache/work/skia/out/Test-Debian9-Clang-GCE-CPU-AVX2-universal-devrel-All-Android_SKQP/devrel",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "make_universal"
   },
@@ -63,7 +63,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Test-Debian9-Clang-GCE-CPU-AVX2-universal-devrel-All-Android_SKQP/devrel",
       "[START_DIR]/[SWARM_OUT_DIR]/out/devrel"
     ],
@@ -78,7 +78,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipe_modules/build/examples/full.py b/infra/bots/recipe_modules/build/examples/full.py
index dbf8128..b007295 100644
--- a/infra/bots/recipe_modules/build/examples/full.py
+++ b/infra/bots/recipe_modules/build/examples/full.py
@@ -37,6 +37,7 @@
   'Build-Debian9-Clang-x86_64-Debug-SK_CPU_LIMIT_SSE41',
   'Build-Debian9-Clang-x86_64-Debug-SafeStack',
   'Build-Debian9-Clang-x86_64-Debug-Tidy',
+  'Build-Debian9-Clang-x86_64-Debug-Wuffs',
   'Build-Debian9-Clang-x86_64-Release-ASAN',
   'Build-Debian9-Clang-x86_64-Release-CMake',
   'Build-Debian9-Clang-x86_64-Release-Fast',
diff --git a/infra/bots/recipe_modules/build/pathkit.py b/infra/bots/recipe_modules/build/pathkit.py
index f0a19f2..f339bb5 100644
--- a/infra/bots/recipe_modules/build/pathkit.py
+++ b/infra/bots/recipe_modules/build/pathkit.py
@@ -2,7 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-DOCKER_IMAGE = 'gcr.io/skia-public/emsdk-release:1.38.16_v1'
+DOCKER_IMAGE = 'gcr.io/skia-public/emsdk-release:1.38.27_v1'
 INNER_BUILD_SCRIPT = '/SRC/skia/infra/pathkit/build_pathkit.sh'
 
 BUILD_PRODUCTS_ISOLATE_WHITELIST_WASM = [
diff --git a/infra/bots/recipe_modules/build/util.py b/infra/bots/recipe_modules/build/util.py
index c99aff2..6068fed 100644
--- a/infra/bots/recipe_modules/build/util.py
+++ b/infra/bots/recipe_modules/build/util.py
@@ -7,7 +7,6 @@
 
 
 BUILD_PRODUCTS_ISOLATE_WHITELIST = [
-  'bookmaker',
   'dm',
   'dm.exe',
   'dm.app',
@@ -25,6 +24,7 @@
   '*.dylib',
   'skia_launcher',
   'skiaserve',
+  'skottie_tool',
   'lib/*.so',
   'run_testlab',
   'skqp-universal-debug.apk',
diff --git a/infra/bots/recipe_modules/checkout/examples/full.expected/Build-Debian9-Clang-x86_64-Release-NoDEPS.json b/infra/bots/recipe_modules/checkout/examples/full.expected/Build-Debian9-Clang-x86_64-Release-NoDEPS.json
index 72543a7..0a83c47 100644
--- a/infra/bots/recipe_modules/checkout/examples/full.expected/Build-Debian9-Clang-x86_64-Release-NoDEPS.json
+++ b/infra/bots/recipe_modules/checkout/examples/full.expected/Build-Debian9-Clang-x86_64-Release-NoDEPS.json
@@ -22,7 +22,7 @@
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
-      "PATH": "RECIPE_PACKAGE_REPO[depot_tools]:<PATH>"
+      "PATH": "RECIPE_REPO[depot_tools]:<PATH>"
     },
     "infra_step": true,
     "name": "git fetch"
diff --git a/infra/bots/recipe_modules/checkout/examples/full.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json b/infra/bots/recipe_modules/checkout/examples/full.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json
index 49d1a69..9b689f5 100644
--- a/infra/bots/recipe_modules/checkout/examples/full.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json
+++ b/infra/bots/recipe_modules/checkout/examples/full.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -97,7 +97,7 @@
     "cmd": [
       "python",
       "-u",
-      "RECIPE_PACKAGE_REPO[depot_tools]/gclient.py",
+      "RECIPE_REPO[depot_tools]/gclient.py",
       "runhooks"
     ],
     "cwd": "[START_DIR]/cache/work",
@@ -107,7 +107,7 @@
     },
     "env_suffixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "name": "gclient runhooks"
diff --git a/infra/bots/recipe_modules/checkout/examples/full.expected/Build-Win-Clang-x86_64-Release-ParentRevision.json b/infra/bots/recipe_modules/checkout/examples/full.expected/Build-Win-Clang-x86_64-Release-ParentRevision.json
index 4a5f8c4..eea034c 100644
--- a/infra/bots/recipe_modules/checkout/examples/full.expected/Build-Win-Clang-x86_64-Release-ParentRevision.json
+++ b/infra/bots/recipe_modules/checkout/examples/full.expected/Build-Win-Clang-x86_64-Release-ParentRevision.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
diff --git a/infra/bots/recipe_modules/checkout/examples/full.expected/Housekeeper-Weekly-RecreateSKPs.json b/infra/bots/recipe_modules/checkout/examples/full.expected/Housekeeper-Weekly-RecreateSKPs.json
index e683f3a..96a0134 100644
--- a/infra/bots/recipe_modules/checkout/examples/full.expected/Housekeeper-Weekly-RecreateSKPs.json
+++ b/infra/bots/recipe_modules/checkout/examples/full.expected/Housekeeper-Weekly-RecreateSKPs.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -97,7 +97,7 @@
     "cmd": [
       "python",
       "-u",
-      "RECIPE_PACKAGE_REPO[depot_tools]/gclient.py",
+      "RECIPE_REPO[depot_tools]/gclient.py",
       "runhooks"
     ],
     "cwd": "[START_DIR]/cache/work",
@@ -108,7 +108,7 @@
     },
     "env_suffixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "name": "gclient runhooks"
diff --git a/infra/bots/recipe_modules/checkout/examples/full.expected/cross_repo_trybot.json b/infra/bots/recipe_modules/checkout/examples/full.expected/cross_repo_trybot.json
index 9561051..4fef20f 100644
--- a/infra/bots/recipe_modules/checkout/examples/full.expected/cross_repo_trybot.json
+++ b/infra/bots/recipe_modules/checkout/examples/full.expected/cross_repo_trybot.json
@@ -52,7 +52,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
diff --git a/infra/bots/recipe_modules/checkout/examples/full.expected/flutter_trybot.json b/infra/bots/recipe_modules/checkout/examples/full.expected/flutter_trybot.json
index da67b7a..17c1742 100644
--- a/infra/bots/recipe_modules/checkout/examples/full.expected/flutter_trybot.json
+++ b/infra/bots/recipe_modules/checkout/examples/full.expected/flutter_trybot.json
@@ -54,7 +54,7 @@
     "cwd": "[START_DIR]/cache/work/flutter",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -116,7 +116,7 @@
     "cmd": [
       "python",
       "-u",
-      "RECIPE_PACKAGE_REPO[depot_tools]/gclient.py",
+      "RECIPE_REPO[depot_tools]/gclient.py",
       "runhooks"
     ],
     "cwd": "[START_DIR]/cache/work/flutter",
@@ -125,7 +125,7 @@
     },
     "env_suffixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "name": "gclient runhooks"
diff --git a/infra/bots/recipe_modules/checkout/examples/full.expected/parent_revision_trybot.json b/infra/bots/recipe_modules/checkout/examples/full.expected/parent_revision_trybot.json
index 0dd56fd..d5b80d3 100644
--- a/infra/bots/recipe_modules/checkout/examples/full.expected/parent_revision_trybot.json
+++ b/infra/bots/recipe_modules/checkout/examples/full.expected/parent_revision_trybot.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
diff --git a/infra/bots/recipe_modules/checkout/examples/full.expected/trybot.json b/infra/bots/recipe_modules/checkout/examples/full.expected/trybot.json
index 2a719fb..0b756f1 100644
--- a/infra/bots/recipe_modules/checkout/examples/full.expected/trybot.json
+++ b/infra/bots/recipe_modules/checkout/examples/full.expected/trybot.json
@@ -52,7 +52,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
diff --git a/infra/bots/recipe_modules/doxygen/examples/full.expected/doxygen.json b/infra/bots/recipe_modules/doxygen/examples/full.expected/doxygen.json
index 0beee76..9ca5f77 100644
--- a/infra/bots/recipe_modules/doxygen/examples/full.expected/doxygen.json
+++ b/infra/bots/recipe_modules/doxygen/examples/full.expected/doxygen.json
@@ -6,7 +6,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "generate and upload doxygen"
   },
diff --git a/infra/bots/recipe_modules/flavor/android.py b/infra/bots/recipe_modules/flavor/android.py
index 3abaa4e..f2a57e1 100644
--- a/infra/bots/recipe_modules/flavor/android.py
+++ b/infra/bots/recipe_modules/flavor/android.py
@@ -489,13 +489,14 @@
       self._adb('kill adb server', 'kill-server')
 
   def step(self, name, cmd, **kwargs):
-    if (cmd[0] == 'nanobench'):
-      self._scale_for_nanobench()
-    else:
-      self._scale_for_dm()
-    app = self.host_dirs.bin_dir.join(cmd[0])
-    self._adb('push %s' % cmd[0],
-              'push', app, self.device_dirs.bin_dir)
+    if not kwargs.get('skip_binary_push', False):
+      if (cmd[0] == 'nanobench'):
+        self._scale_for_nanobench()
+      else:
+        self._scale_for_dm()
+      app = self.host_dirs.bin_dir.join(cmd[0])
+      self._adb('push %s' % cmd[0],
+                'push', app, self.device_dirs.bin_dir)
 
     sh = '%s.sh' % cmd[0]
     self.m.run.writefile(self.m.vars.tmp_dir.join(sh),
diff --git a/infra/bots/recipe_modules/flavor/api.py b/infra/bots/recipe_modules/flavor/api.py
index 090e04c..afc219b 100644
--- a/infra/bots/recipe_modules/flavor/api.py
+++ b/infra/bots/recipe_modules/flavor/api.py
@@ -36,7 +36,8 @@
 VERSION_NONE = -1
 
 def is_android(vars_api):
-  return 'Android' in vars_api.extra_tokens
+  return ('Android' in vars_api.extra_tokens or
+          'Android' in vars_api.builder_cfg.get('os', ''))
 
 def is_chromecast(vars_api):
   return ('Chromecast' in vars_api.extra_tokens or
diff --git a/infra/bots/recipe_modules/flavor/default.py b/infra/bots/recipe_modules/flavor/default.py
index 78ed397..6cd586d 100644
--- a/infra/bots/recipe_modules/flavor/default.py
+++ b/infra/bots/recipe_modules/flavor/default.py
@@ -139,6 +139,14 @@
     self.m.file.ensure_directory(
         'makedirs %s' % self.m.path.basename(path), path)
 
+  def read_file_on_device(self, path, **kwargs):
+    """Reads the specified file."""
+    return self.m.file.read_text('read %s' % path, path)
+
+  def remove_file_on_device(self, path):
+    """Removes the specified file."""
+    return self.m.file.remove('remove %s' % path, path)
+
   def install(self):
     """Run device-specific installation steps."""
     pass
@@ -155,7 +163,7 @@
     return self.m.run(self.m.python, title, script=script, args=args,
                infra_step=infra_step)
 
-  def step(self, name, cmd):
+  def step(self, name, cmd, **unused_kwargs):
     app = self.device_dirs.bin_dir.join(cmd[0])
     cmd = [app] + cmd[1:]
     env = self.m.context.env
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android_SkottieTracing.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android_SkottieTracing.json
new file mode 100644
index 0000000..dd5c792
--- /dev/null
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android_SkottieTracing.json
@@ -0,0 +1,828 @@
+[
+  {
+    "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": [
+      "/opt/infra-android/tools/adb",
+      "push",
+      "file.txt",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push file.txt file.txt"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read file.txt",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "results_dir"
+    ],
+    "infra_step": true,
+    "name": "rmtree results_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "results_dir"
+    ],
+    "infra_step": true,
+    "name": "makedirs results_dir"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-rf",
+      "device_results_dir"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm device_results_dir"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "mkdir",
+      "-p",
+      "device_results_dir"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir device_results_dir"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/resources"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
+  },
+  {
+    "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(['/opt/infra-android/tools/adb', 'push',\n                           os.path.realpath(os.path.join(host, p, f)),\n                           os.path.join(device, p, f)])\n",
+      "[START_DIR]/skia/resources",
+      "/sdcard/revenge_of_the_skiabot/resources"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
+    "~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(['/opt/infra-android/tools/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": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skp VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SKP_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/SKP_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "/sdcard/revenge_of_the_skiabot/SKP_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-rf",
+      "/sdcard/revenge_of_the_skiabot/skps"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/skps"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/skps"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "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(['/opt/infra-android/tools/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"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "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(['/opt/infra-android/tools/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": [
+      "/opt/infra-android/tools/adb",
+      "push",
+      "[START_DIR]/tmp/SKP_VERSION",
+      "/sdcard/revenge_of_the_skiabot/SKP_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "/sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-rf",
+      "/sdcard/revenge_of_the_skiabot/images"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/images"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/images"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
+  },
+  {
+    "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(['/opt/infra-android/tools/adb', 'push',\n                           os.path.realpath(os.path.join(host, p, f)),\n                           os.path.join(device, p, f)])\n",
+      "[START_DIR]/skimage",
+      "/sdcard/revenge_of_the_skiabot/images"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
+    "~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(['/opt/infra-android/tools/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": [
+      "/opt/infra-android/tools/adb",
+      "push",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION",
+      "/sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get svg VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SVG_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/SVG_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "/sdcard/revenge_of_the_skiabot/SVG_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-rf",
+      "/sdcard/revenge_of_the_skiabot/svgs"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/svgs"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
+  },
+  {
+    "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(['/opt/infra-android/tools/adb', 'push',\n                           os.path.realpath(os.path.join(host, p, f)),\n                           os.path.join(device, p, f)])\n",
+      "[START_DIR]/svg",
+      "/sdcard/revenge_of_the_skiabot/svgs"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
+    "~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(['/opt/infra-android/tools/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": [
+      "/opt/infra-android/tools/adb",
+      "push",
+      "[START_DIR]/tmp/SVG_VERSION",
+      "/sdcard/revenge_of_the_skiabot/SVG_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "set -x; /data/local/tmp/dm --some-flag; echo $? >/data/local/tmp/rc",
+      "[START_DIR]/tmp/dm.sh"
+    ],
+    "infra_step": true,
+    "name": "write dm.sh"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "push",
+      "[START_DIR]/tmp/dm.sh",
+      "/data/local/tmp/"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push dm.sh"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "logcat",
+      "-c"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "clear log"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport subprocess\nimport sys\nbin_dir = sys.argv[1]\nsh      = sys.argv[2]\nsubprocess.check_call(['/opt/infra-android/tools/adb', 'shell', 'sh', bin_dir + sh])\ntry:\n  sys.exit(int(subprocess.check_output(['/opt/infra-android/tools/adb', 'shell', 'cat',\n                                        bin_dir + 'rc'])))\nexcept ValueError:\n  print \"Couldn't read the return code.  Probably killed for OOM.\"\n  sys.exit(1)\n",
+      "/data/local/tmp/",
+      "dm.sh"
+    ],
+    "name": "dm",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@bin_dir = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@sh      = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_call(['/opt/infra-android/tools/adb', 'shell', 'sh', bin_dir + sh])@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(int(subprocess.check_output(['/opt/infra-android/tools/adb', 'shell', 'cat',@@@",
+      "@@@STEP_LOG_LINE@python.inline@                                        bin_dir + 'rc'])))@@@",
+      "@@@STEP_LOG_LINE@python.inline@except ValueError:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  print \"Couldn't read the return code.  Probably killed for OOM.\"@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(1)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "adb pull"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "pull",
+      "/sdcard/revenge_of_the_skiabot/perf",
+      "[CLEANUP]/adb_pull_tmp_1"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/perf",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "glob",
+      "[CLEANUP]/adb_pull_tmp_1",
+      "perf/*"
+    ],
+    "infra_step": true,
+    "name": "adb pull.list pulled files",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_END@glob@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "adb pull.copy 1.png",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "adb pull.copy 2.png",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[CLEANUP]/adb_pull_tmp_1"
+    ],
+    "infra_step": true,
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nout = sys.argv[1]\nlog = subprocess.check_output(['/opt/infra-android/tools/adb', 'logcat', '-d'])\nfor line in log.split('\\n'):\n  tokens = line.split()\n  if len(tokens) == 11 and tokens[-7] == 'F' and tokens[-3] == 'pc':\n    addr, path = tokens[-2:]\n    local = os.path.join(out, os.path.basename(path))\n    if os.path.exists(local):\n      sym = subprocess.check_output(['addr2line', '-Cfpe', local, addr])\n      line = line.replace(addr, addr + ' ' + sym.strip())\n  print line\n",
+      "[START_DIR]/build"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "dump log",
+    "timeout": 300,
+    "~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(['/opt/infra-android/tools/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": [
+      "/opt/infra-android/tools/adb",
+      "kill-server"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "kill adb server"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android.json
index 763f06c..ecd977a 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android.json
@@ -24,13 +24,47 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push file.txt file.txt"
   },
   {
     "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read file.txt",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm file.txt"
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
@@ -69,7 +103,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm device_results_dir"
@@ -86,7 +120,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir device_results_dir"
@@ -103,7 +137,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -118,7 +152,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -180,7 +214,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -198,7 +232,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -215,7 +249,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -232,7 +266,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -247,7 +281,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -281,7 +315,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -325,7 +359,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -343,7 +377,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -360,7 +394,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -377,7 +411,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -392,7 +426,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -426,7 +460,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -470,7 +504,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -488,7 +522,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -505,7 +539,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -522,7 +556,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -537,7 +571,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -571,7 +605,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -587,7 +621,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push nanobench"
@@ -617,7 +651,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push nanobench.sh"
@@ -632,7 +666,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -677,7 +711,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/perf",
@@ -765,7 +799,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -798,7 +832,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android.json
index d11b550..88b7c41 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android.json
@@ -24,13 +24,47 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push file.txt file.txt"
   },
   {
     "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read file.txt",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm file.txt"
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
@@ -69,7 +103,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm device_results_dir"
@@ -86,7 +120,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir device_results_dir"
@@ -103,7 +137,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -118,7 +152,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -180,7 +214,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -198,7 +232,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -215,7 +249,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -232,7 +266,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -247,7 +281,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -281,7 +315,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -325,7 +359,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -343,7 +377,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -360,7 +394,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -377,7 +411,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -392,7 +426,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -426,7 +460,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -470,7 +504,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -488,7 +522,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -505,7 +539,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -522,7 +556,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -537,7 +571,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -571,7 +605,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -587,7 +621,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 4's governor to userspace",
@@ -629,7 +663,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Scale CPU 4 to 0.600000",
@@ -703,7 +737,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Disabling CPU 0",
@@ -753,7 +787,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Disabling CPU 1",
@@ -803,7 +837,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Disabling CPU 2",
@@ -853,7 +887,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Disabling CPU 3",
@@ -902,7 +936,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Lock GPU to 600000000 (and other perf tweaks)",
@@ -965,7 +999,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push nanobench"
@@ -995,7 +1029,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push nanobench.sh"
@@ -1010,7 +1044,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -1055,7 +1089,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/perf",
@@ -1143,7 +1177,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -1176,7 +1210,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All.json
index 483c87b..2e52e5e 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All.json
@@ -7,7 +7,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read chromeos ip",
@@ -43,6 +43,44 @@
   },
   {
     "cmd": [
+      "ssh",
+      "-oConnectTimeout=15",
+      "-oBatchMode=yes",
+      "-t",
+      "-t",
+      "foo@127.0.0.1",
+      "cat",
+      "file.txt"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read file.txt",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "ssh",
+      "-oConnectTimeout=15",
+      "-oBatchMode=yes",
+      "-t",
+      "-t",
+      "foo@127.0.0.1",
+      "rm",
+      "-f",
+      "file.txt"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm file.txt"
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
@@ -83,7 +121,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm device_results_dir"
@@ -102,7 +140,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir device_results_dir"
@@ -121,7 +159,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/resources"
@@ -143,7 +181,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "remount /home/chronos/user/ as exec"
@@ -162,7 +200,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/bin"
@@ -181,7 +219,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/bin"
@@ -247,7 +285,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /home/chronos/user/SKP_VERSION",
@@ -267,7 +305,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/SKP_VERSION"
@@ -286,7 +324,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/skps"
@@ -305,7 +343,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/skps"
@@ -391,7 +429,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /home/chronos/user/SK_IMAGE_VERSION",
@@ -411,7 +449,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/SK_IMAGE_VERSION"
@@ -430,7 +468,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/images"
@@ -449,7 +487,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/images"
@@ -535,7 +573,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /home/chronos/user/SVG_VERSION",
@@ -555,7 +593,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/SVG_VERSION"
@@ -574,7 +612,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/svgs"
@@ -593,7 +631,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/svgs"
@@ -700,7 +738,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "chmod nanobench"
@@ -718,7 +756,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "nanobench"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All.json
index 02245f4..7815c2f 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All.json
@@ -21,7 +21,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read chromecast ip",
@@ -43,7 +43,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb connect 192.168.1.2:5555"
@@ -58,13 +58,45 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push file.txt file.txt"
   },
   {
     "cmd": [
+      "adb",
+      "shell",
+      "cat",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read file.txt",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "adb",
+      "shell",
+      "rm",
+      "-f",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm file.txt"
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
@@ -102,7 +134,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm device_results_dir"
@@ -118,7 +150,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir device_results_dir"
@@ -134,7 +166,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /cache/skia/resources"
@@ -150,7 +182,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /cache/skia/bin"
@@ -165,7 +197,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /cache/skia/resources",
@@ -229,7 +261,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /cache/skia/SKP_VERSION",
@@ -246,7 +278,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /cache/skia/SKP_VERSION"
@@ -262,7 +294,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /cache/skia/skps"
@@ -278,7 +310,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /cache/skia/skps"
@@ -293,7 +325,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /cache/skia/skps",
@@ -329,7 +361,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /cache/skia/SKP_VERSION"
@@ -372,7 +404,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /cache/skia/SK_IMAGE_VERSION",
@@ -389,7 +421,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /cache/skia/SK_IMAGE_VERSION"
@@ -405,7 +437,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /cache/skia/images"
@@ -421,7 +453,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /cache/skia/images"
@@ -436,7 +468,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /cache/skia/images",
@@ -472,7 +504,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /cache/skia/SK_IMAGE_VERSION"
@@ -515,7 +547,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /cache/skia/SVG_VERSION",
@@ -532,7 +564,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /cache/skia/SVG_VERSION"
@@ -548,7 +580,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /cache/skia/svgs"
@@ -564,7 +596,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /cache/skia/svgs"
@@ -579,7 +611,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /cache/skia/svgs",
@@ -615,7 +647,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /cache/skia/SVG_VERSION"
@@ -630,7 +662,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push nanobench"
@@ -647,7 +679,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "nanobench"
   },
@@ -665,7 +697,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /cache/skia/perf",
@@ -757,7 +789,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Delete executables"
@@ -770,7 +802,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "disconnect"
@@ -783,7 +815,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb connect 192.168.1.2:5555 (2)"
@@ -797,7 +829,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -828,7 +860,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "disconnect (2)"
@@ -841,7 +873,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN.json
index 2c6f9de..17af3c2 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN.json
@@ -6,6 +6,33 @@
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
       "--json-output",
       "/path/to/tmp/json",
+      "copy",
+      "file.txt",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "read file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "file.txt"
+    ],
+    "infra_step": true,
+    "name": "remove file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
       "rmtree",
       "results_dir"
     ],
@@ -152,7 +179,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/clang_linux/msan:[START_DIR]/clang_linux/lib",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]:[START_DIR]/clang_linux/bin"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]:[START_DIR]/clang_linux/bin"
     },
     "name": "symbolized nanobench"
   },
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-ASAN.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-ASAN.json
index add914b..d6baf1f 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-ASAN.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-ASAN.json
@@ -6,6 +6,33 @@
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
       "--json-output",
       "/path/to/tmp/json",
+      "copy",
+      "file.txt",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "read file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "file.txt"
+    ],
+    "infra_step": true,
+    "name": "remove file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
       "rmtree",
       "results_dir"
     ],
@@ -154,7 +181,7 @@
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/clang_linux/lib",
       "LSAN_OPTIONS": "symbolize=1 print_suppressions=1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]:[START_DIR]/clang_linux/bin",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]:[START_DIR]/clang_linux/bin",
       "UBSAN_OPTIONS": "symbolize=1 print_stacktrace=1"
     },
     "name": "symbolized nanobench"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android.json
index 6d6207e..b06de4b 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android.json
@@ -24,13 +24,47 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push file.txt file.txt"
   },
   {
     "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read file.txt",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm file.txt"
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
@@ -69,7 +103,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm device_results_dir"
@@ -86,7 +120,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir device_results_dir"
@@ -103,7 +137,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -118,7 +152,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -180,7 +214,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -198,7 +232,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -215,7 +249,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -232,7 +266,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -247,7 +281,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -281,7 +315,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -325,7 +359,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -343,7 +377,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -360,7 +394,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -377,7 +411,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -392,7 +426,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -426,7 +460,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -470,7 +504,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -488,7 +522,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -505,7 +539,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -522,7 +556,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -537,7 +571,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -571,7 +605,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -587,7 +621,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 0's governor to hotplug",
@@ -629,7 +663,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -659,7 +693,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm.sh"
@@ -674,7 +708,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -719,7 +753,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out",
@@ -807,7 +841,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -840,7 +874,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android.json
index 4c9b41f..bb73f01 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android.json
@@ -24,13 +24,47 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push file.txt file.txt"
   },
   {
     "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read file.txt",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm file.txt"
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
@@ -69,7 +103,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm device_results_dir"
@@ -86,7 +120,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir device_results_dir"
@@ -103,7 +137,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -118,7 +152,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -180,7 +214,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -198,7 +232,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -215,7 +249,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -232,7 +266,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -247,7 +281,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -281,7 +315,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -325,7 +359,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -343,7 +377,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -360,7 +394,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -377,7 +411,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -392,7 +426,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -426,7 +460,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -470,7 +504,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -488,7 +522,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -505,7 +539,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -522,7 +556,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -537,7 +571,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -571,7 +605,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -587,7 +621,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -617,7 +651,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm.sh"
@@ -632,7 +666,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -677,7 +711,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out",
@@ -765,7 +799,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -798,7 +832,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android.json
index 1b40c0f..b08b039 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android.json
@@ -24,13 +24,47 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push file.txt file.txt"
   },
   {
     "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read file.txt",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm file.txt"
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
@@ -69,7 +103,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm device_results_dir"
@@ -86,7 +120,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir device_results_dir"
@@ -103,7 +137,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -118,7 +152,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -180,7 +214,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -198,7 +232,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -215,7 +249,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -232,7 +266,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -247,7 +281,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -281,7 +315,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -325,7 +359,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -343,7 +377,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -360,7 +394,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -377,7 +411,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -392,7 +426,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -426,7 +460,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -470,7 +504,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -488,7 +522,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -505,7 +539,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -522,7 +556,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -537,7 +571,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -571,7 +605,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -587,7 +621,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Enabling CPU 0",
@@ -637,7 +671,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Enabling CPU 1",
@@ -687,7 +721,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Enabling CPU 2",
@@ -737,7 +771,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Enabling CPU 3",
@@ -787,7 +821,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 4's governor to ondemand",
@@ -829,7 +863,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 0's governor to ondemand",
@@ -871,7 +905,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -901,7 +935,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm.sh"
@@ -916,7 +950,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -961,7 +995,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out",
@@ -1049,7 +1083,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -1082,7 +1116,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_ASAN.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_ASAN.json
index b46ac62..3611230 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_ASAN.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_ASAN.json
@@ -24,13 +24,47 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push file.txt file.txt"
   },
   {
     "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read file.txt",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm file.txt"
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
@@ -69,7 +103,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm device_results_dir"
@@ -86,7 +120,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir device_results_dir"
@@ -103,7 +137,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -118,7 +152,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Setting up device to run ASAN",
@@ -208,7 +242,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -270,7 +304,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -288,7 +322,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -305,7 +339,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -322,7 +356,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -337,7 +371,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -371,7 +405,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -415,7 +449,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -433,7 +467,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -450,7 +484,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -467,7 +501,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -482,7 +516,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -516,7 +550,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -560,7 +594,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -578,7 +612,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -595,7 +629,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -612,7 +646,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -627,7 +661,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -661,7 +695,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -677,7 +711,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Enabling CPU 0",
@@ -727,7 +761,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Enabling CPU 1",
@@ -777,7 +811,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Enabling CPU 2",
@@ -827,7 +861,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Enabling CPU 3",
@@ -877,7 +911,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 4's governor to ondemand",
@@ -919,7 +953,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 0's governor to ondemand",
@@ -961,7 +995,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -991,7 +1025,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm.sh"
@@ -1006,7 +1040,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -1051,7 +1085,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out",
@@ -1137,7 +1171,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "wait for device before uninstalling ASAN",
@@ -1150,7 +1184,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "uninstall ASAN",
@@ -1165,7 +1199,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -1198,7 +1232,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All.json
index 5a6f795..ea98f1f 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All.json
@@ -7,7 +7,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read chromeos ip",
@@ -43,6 +43,44 @@
   },
   {
     "cmd": [
+      "ssh",
+      "-oConnectTimeout=15",
+      "-oBatchMode=yes",
+      "-t",
+      "-t",
+      "foo@127.0.0.1",
+      "cat",
+      "file.txt"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read file.txt",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "ssh",
+      "-oConnectTimeout=15",
+      "-oBatchMode=yes",
+      "-t",
+      "-t",
+      "foo@127.0.0.1",
+      "rm",
+      "-f",
+      "file.txt"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm file.txt"
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
@@ -83,7 +121,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm device_results_dir"
@@ -102,7 +140,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir device_results_dir"
@@ -121,7 +159,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/resources"
@@ -143,7 +181,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "remount /home/chronos/user/ as exec"
@@ -162,7 +200,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/bin"
@@ -181,7 +219,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/bin"
@@ -247,7 +285,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /home/chronos/user/SKP_VERSION",
@@ -267,7 +305,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/SKP_VERSION"
@@ -286,7 +324,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/skps"
@@ -305,7 +343,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/skps"
@@ -391,7 +429,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /home/chronos/user/SK_IMAGE_VERSION",
@@ -411,7 +449,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/SK_IMAGE_VERSION"
@@ -430,7 +468,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/images"
@@ -449,7 +487,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/images"
@@ -535,7 +573,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /home/chronos/user/SVG_VERSION",
@@ -555,7 +593,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/SVG_VERSION"
@@ -574,7 +612,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/svgs"
@@ -593,7 +631,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/svgs"
@@ -700,7 +738,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "chmod dm"
@@ -718,7 +756,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dm"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-Coverage.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-Coverage.json
index 67a7c43..23f23b2 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-Coverage.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-Coverage.json
@@ -6,6 +6,33 @@
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
       "--json-output",
       "/path/to/tmp/json",
+      "copy",
+      "file.txt",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "read file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "file.txt"
+    ],
+    "infra_step": true,
+    "name": "remove file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
       "rmtree",
       "results_dir"
     ],
@@ -153,7 +180,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "LLVM_PROFILE_FILE": "[START_DIR]/[SWARM_OUT_DIR]/All.profraw",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "symbolized dm"
   },
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Lottie.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Lottie.json
index 9464e4e..7426c27 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Lottie.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Lottie.json
@@ -6,6 +6,33 @@
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
       "--json-output",
       "/path/to/tmp/json",
+      "copy",
+      "file.txt",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "read file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "file.txt"
+    ],
+    "infra_step": true,
+    "name": "remove file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
       "rmtree",
       "results_dir"
     ],
@@ -96,7 +123,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "symbolized dm"
   },
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN.json
index 32d798f..4125178 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN.json
@@ -6,6 +6,33 @@
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
       "--json-output",
       "/path/to/tmp/json",
+      "copy",
+      "file.txt",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "read file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "file.txt"
+    ],
+    "infra_step": true,
+    "name": "remove file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
       "rmtree",
       "results_dir"
     ],
@@ -152,7 +179,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/clang_linux/lib",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]:[START_DIR]/clang_linux/bin",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]:[START_DIR]/clang_linux/bin",
       "TSAN_OPTIONS": "report_signal_unsafe=0"
     },
     "name": "symbolized dm"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Debug-All-SwiftShader.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Debug-All-SwiftShader.json
index dc58bf7..78c7b41 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Debug-All-SwiftShader.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Debug-All-SwiftShader.json
@@ -6,6 +6,33 @@
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
       "--json-output",
       "/path/to/tmp/json",
+      "copy",
+      "file.txt",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "read file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "file.txt"
+    ],
+    "infra_step": true,
+    "name": "remove file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
       "rmtree",
       "results_dir"
     ],
@@ -153,7 +180,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/build/swiftshader_out",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "symbolized dm"
   },
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-OpenCL.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-OpenCL.json
index 7ca6820..5232d2a 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-OpenCL.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-OpenCL.json
@@ -6,6 +6,33 @@
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
       "--json-output",
       "/path/to/tmp/json",
+      "copy",
+      "file.txt",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "read file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "file.txt"
+    ],
+    "infra_step": true,
+    "name": "remove file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
       "rmtree",
       "results_dir"
     ],
@@ -184,7 +211,7 @@
       "LD_LIBRARY_PATH": "[START_DIR]/mesa_intel_driver_linux:[START_DIR]/opencl_ocl_icd_linux:[START_DIR]/opencl_intel_neo_linux",
       "LIBGL_DRIVERS_PATH": "[START_DIR]/mesa_intel_driver_linux",
       "OPENCL_VENDOR_PATH": "[START_DIR]/tmp/OpenCL/vendors",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]",
       "VK_ICD_FILENAMES": "[START_DIR]/mesa_intel_driver_linux/intel_icd.x86_64.json"
     },
     "name": "symbolized dm"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-Vulkan.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-Vulkan.json
index cc62ecd..1f98c87 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-Vulkan.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-Vulkan.json
@@ -6,6 +6,33 @@
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
       "--json-output",
       "/path/to/tmp/json",
+      "copy",
+      "file.txt",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "read file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "file.txt"
+    ],
+    "infra_step": true,
+    "name": "remove file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
       "rmtree",
       "results_dir"
     ],
@@ -154,7 +181,7 @@
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/mesa_intel_driver_linux:[START_DIR]/linux_vulkan_sdk/lib",
       "LIBGL_DRIVERS_PATH": "[START_DIR]/mesa_intel_driver_linux",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]:[START_DIR]/linux_vulkan_sdk/bin",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]:[START_DIR]/linux_vulkan_sdk/bin",
       "VK_ICD_FILENAMES": "[START_DIR]/mesa_intel_driver_linux/intel_icd.x86_64.json"
     },
     "name": "symbolized dm"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN.json
deleted file mode 100644
index eb79aa8..0000000
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN.json
+++ /dev/null
@@ -1,161 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "rmtree",
-      "results_dir"
-    ],
-    "infra_step": true,
-    "name": "rmtree results_dir"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "results_dir"
-    ],
-    "infra_step": true,
-    "name": "makedirs results_dir"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "rmtree",
-      "device_results_dir"
-    ],
-    "infra_step": true,
-    "name": "rmtree device_results_dir"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "device_results_dir"
-    ],
-    "infra_step": true,
-    "name": "makedirs device_results_dir"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skp VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SKP_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SKP_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skimage VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SK_IMAGE_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SK_IMAGE_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get svg VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SVG_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SVG_VERSION"
-  },
-  {
-    "cmd": [
-      "[START_DIR]/build/dm",
-      "--some-flag"
-    ],
-    "env": {
-      "ASAN_OPTIONS": "symbolize=1",
-      "CHROME_HEADLESS": "1",
-      "LD_LIBRARY_PATH": "[START_DIR]/clang_linux/lib",
-      "LSAN_OPTIONS": "symbolize=1 print_suppressions=1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]:[START_DIR]/clang_linux/bin",
-      "UBSAN_OPTIONS": "symbolize=1 print_stacktrace=1"
-    },
-    "name": "dm"
-  },
-  {
-    "jsonResult": null,
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN.json
new file mode 100644
index 0000000..c2ac478
--- /dev/null
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN.json
@@ -0,0 +1,188 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "file.txt",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "read file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "file.txt"
+    ],
+    "infra_step": true,
+    "name": "remove file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "results_dir"
+    ],
+    "infra_step": true,
+    "name": "rmtree results_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "results_dir"
+    ],
+    "infra_step": true,
+    "name": "makedirs results_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "device_results_dir"
+    ],
+    "infra_step": true,
+    "name": "rmtree device_results_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "device_results_dir"
+    ],
+    "infra_step": true,
+    "name": "makedirs device_results_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skp VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SKP_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get svg VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SVG_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "[START_DIR]/build/dm",
+      "--some-flag"
+    ],
+    "env": {
+      "ASAN_OPTIONS": "symbolize=1",
+      "CHROME_HEADLESS": "1",
+      "LD_LIBRARY_PATH": "[START_DIR]/clang_linux/lib",
+      "LSAN_OPTIONS": "symbolize=1 print_suppressions=1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]:[START_DIR]/clang_linux/bin",
+      "UBSAN_OPTIONS": "symbolize=1 print_stacktrace=1"
+    },
+    "name": "dm"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41.json
index e86a122..b1d1bb6 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41.json
@@ -6,6 +6,33 @@
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
       "--json-output",
       "/path/to/tmp/json",
+      "copy",
+      "file.txt",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "read file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "file.txt"
+    ],
+    "infra_step": true,
+    "name": "remove file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
       "rmtree",
       "results_dir"
     ],
@@ -153,7 +180,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]",
       "VALGRIND_LIB": "[START_DIR]/valgrind/lib/valgrind"
     },
     "name": "dm"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_ProcDump.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_ProcDump.json
index 8b9acdb..aaf0b74 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_ProcDump.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_ProcDump.json
@@ -6,6 +6,33 @@
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
       "--json-output",
       "/path/to/tmp/json",
+      "copy",
+      "file.txt",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "read file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "file.txt"
+    ],
+    "infra_step": true,
+    "name": "remove file.txt"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
       "rmtree",
       "results_dir"
     ],
@@ -168,7 +195,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm"
   },
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed.json b/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed.json
index 44680fd..05470c0 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed.json
@@ -24,13 +24,47 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push file.txt file.txt"
   },
   {
     "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "cat",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read file.txt",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "rm",
+      "-f",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm file.txt"
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
@@ -69,7 +103,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm device_results_dir"
@@ -86,7 +120,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir device_results_dir"
@@ -103,7 +137,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -118,7 +152,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -180,7 +214,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -198,7 +232,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -215,7 +249,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -232,7 +266,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -247,7 +281,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -281,7 +315,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -325,7 +359,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -343,7 +377,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -360,7 +394,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -377,7 +411,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -392,7 +426,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -426,7 +460,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -470,7 +504,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -488,7 +522,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -505,7 +539,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -522,7 +556,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -537,7 +571,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -571,7 +605,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -587,7 +621,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 0's governor to userspace",
@@ -629,13 +663,12 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Scale CPU 0 to 0.600000",
     "timeout": 30,
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@import os@@@",
       "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
@@ -705,13 +738,12 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Scale CPU 0 to 0.600000 (attempt 2)",
     "timeout": 30,
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@import os@@@",
       "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
@@ -781,13 +813,12 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Scale CPU 0 to 0.600000 (attempt 3)",
     "timeout": 30,
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@import os@@@",
       "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
@@ -855,7 +886,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -902,7 +933,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed_golo.json b/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed_golo.json
index df7ff62..6f1278e 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed_golo.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed_golo.json
@@ -24,13 +24,47 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push file.txt file.txt"
   },
   {
     "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read file.txt",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm file.txt"
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
@@ -69,7 +103,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm device_results_dir"
@@ -86,7 +120,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir device_results_dir"
@@ -103,7 +137,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -118,7 +152,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -180,7 +214,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -198,7 +232,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -215,7 +249,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -232,7 +266,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -247,7 +281,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -281,7 +315,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -325,7 +359,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -343,7 +377,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -360,7 +394,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -377,7 +411,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -392,7 +426,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -426,7 +460,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -470,7 +504,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -488,7 +522,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -505,7 +539,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -522,7 +556,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -537,7 +571,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -571,7 +605,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -587,7 +621,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 4's governor to userspace",
@@ -629,13 +663,12 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Scale CPU 4 to 0.600000",
     "timeout": 30,
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@import os@@@",
       "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
@@ -705,13 +738,12 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Scale CPU 4 to 0.600000 (attempt 2)",
     "timeout": 30,
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@import os@@@",
       "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
@@ -781,13 +813,12 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Scale CPU 4 to 0.600000 (attempt 3)",
     "timeout": 30,
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@import os@@@",
       "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
@@ -855,7 +886,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -902,7 +933,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed_once.json b/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed_once.json
index 36c4c17..ead376d 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed_once.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed_once.json
@@ -24,13 +24,47 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push file.txt file.txt"
   },
   {
     "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read file.txt",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm file.txt"
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
@@ -69,7 +103,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm device_results_dir"
@@ -86,7 +120,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir device_results_dir"
@@ -103,7 +137,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -118,7 +152,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -180,7 +214,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -198,7 +232,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -215,7 +249,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -232,7 +266,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -247,7 +281,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -281,7 +315,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -325,7 +359,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -343,7 +377,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -360,7 +394,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -377,7 +411,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -392,7 +426,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -426,7 +460,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -470,7 +504,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -488,7 +522,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -505,7 +539,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -522,7 +556,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -537,7 +571,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -571,7 +605,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -587,7 +621,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 0's governor to userspace",
@@ -629,13 +663,12 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Scale CPU 0 to 0.600000",
     "timeout": 30,
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@import os@@@",
       "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
@@ -705,7 +738,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Scale CPU 0 to 0.600000 (attempt 2)",
@@ -779,7 +812,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 2's governor to userspace",
@@ -821,7 +854,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Scale CPU 2 to 0.600000",
@@ -895,7 +928,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push nanobench"
@@ -925,7 +958,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push nanobench.sh"
@@ -940,7 +973,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -985,7 +1018,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/perf",
@@ -1073,7 +1106,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -1106,7 +1139,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/failed_infra_step.json b/infra/bots/recipe_modules/flavor/examples/full.expected/failed_infra_step.json
index b9fbf9c..c4ef4b5 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/failed_infra_step.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/failed_infra_step.json
@@ -24,13 +24,47 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push file.txt file.txt"
   },
   {
     "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read file.txt",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm file.txt"
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
@@ -69,7 +103,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm device_results_dir"
@@ -86,7 +120,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir device_results_dir"
@@ -103,7 +137,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -118,7 +152,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -180,7 +214,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -198,7 +232,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -215,7 +249,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -232,7 +266,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -247,7 +281,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -281,7 +315,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -325,7 +359,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -343,7 +377,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -360,7 +394,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -377,7 +411,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -392,7 +426,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -426,7 +460,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -470,7 +504,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -488,7 +522,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -505,7 +539,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -522,7 +556,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -537,7 +571,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -571,7 +605,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -587,7 +621,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 0's governor to userspace",
@@ -629,7 +663,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Scale CPU 0 to 0.600000",
@@ -703,7 +737,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 2's governor to userspace",
@@ -745,7 +779,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Scale CPU 2 to 0.600000",
@@ -819,7 +853,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push nanobench"
@@ -849,7 +883,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push nanobench.sh"
@@ -864,7 +898,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -909,7 +943,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/perf",
@@ -997,13 +1031,12 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
     "timeout": 300,
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@import os@@@",
       "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
@@ -1046,7 +1079,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/failed_read_version.json b/infra/bots/recipe_modules/flavor/examples/full.expected/failed_read_version.json
index d9da730..9a4504b 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/failed_read_version.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/failed_read_version.json
@@ -24,13 +24,47 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push file.txt file.txt"
   },
   {
     "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read file.txt",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm file.txt"
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
@@ -69,7 +103,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm device_results_dir"
@@ -86,7 +120,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir device_results_dir"
@@ -103,7 +137,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -118,7 +152,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -180,7 +214,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -198,7 +232,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -215,7 +249,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -232,7 +266,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -247,7 +281,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -281,7 +315,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -325,13 +359,12 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_EXCEPTION@@@"
     ]
   },
@@ -344,7 +377,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server after failure of 'read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION' (attempt 1)",
@@ -359,7 +392,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "wait for device after failure of 'read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION' (attempt 1)",
@@ -376,7 +409,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION (attempt 2)",
@@ -394,7 +427,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -411,7 +444,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -428,7 +461,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -443,7 +476,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -477,7 +510,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -521,7 +554,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -539,7 +572,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -556,7 +589,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -573,7 +606,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -588,7 +621,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -622,7 +655,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -638,7 +671,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 0's governor to userspace",
@@ -680,7 +713,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Scale CPU 0 to 0.600000",
@@ -754,7 +787,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 2's governor to userspace",
@@ -796,7 +829,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Scale CPU 2 to 0.600000",
@@ -870,7 +903,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push nanobench"
@@ -900,7 +933,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push nanobench.sh"
@@ -915,7 +948,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -960,7 +993,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/perf",
@@ -1048,7 +1081,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -1081,7 +1114,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/retry_adb_command.json b/infra/bots/recipe_modules/flavor/examples/full.expected/retry_adb_command.json
index 52cd188..3010b88 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/retry_adb_command.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/retry_adb_command.json
@@ -24,13 +24,47 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push file.txt file.txt"
   },
   {
     "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read file.txt",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm file.txt"
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
@@ -69,7 +103,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm device_results_dir"
@@ -86,7 +120,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir device_results_dir"
@@ -103,12 +137,11 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_EXCEPTION@@@"
     ]
   },
@@ -121,7 +154,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server after failure of 'mkdir /sdcard/revenge_of_the_skiabot/resources' (attempt 1)",
@@ -136,7 +169,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "wait for device after failure of 'mkdir /sdcard/revenge_of_the_skiabot/resources' (attempt 1)",
@@ -154,7 +187,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources (attempt 2)"
@@ -169,7 +202,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -231,7 +264,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -249,7 +282,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -266,7 +299,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -283,7 +316,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -298,7 +331,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -332,7 +365,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -376,7 +409,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -394,7 +427,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -411,7 +444,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -428,7 +461,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -443,7 +476,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -477,7 +510,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -521,7 +554,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -539,7 +572,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -556,7 +589,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -573,7 +606,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -588,7 +621,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -622,7 +655,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -638,7 +671,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 0's governor to userspace",
@@ -680,7 +713,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Scale CPU 0 to 0.600000",
@@ -754,7 +787,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 2's governor to userspace",
@@ -796,7 +829,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Scale CPU 2 to 0.600000",
@@ -870,7 +903,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push nanobench"
@@ -900,7 +933,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push nanobench.sh"
@@ -915,7 +948,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -960,7 +993,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/perf",
@@ -1048,7 +1081,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -1081,7 +1114,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/retry_adb_command_retries_exhausted.json b/infra/bots/recipe_modules/flavor/examples/full.expected/retry_adb_command_retries_exhausted.json
index 11888e9..1508180 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/retry_adb_command_retries_exhausted.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/retry_adb_command_retries_exhausted.json
@@ -24,13 +24,47 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push file.txt file.txt"
   },
   {
     "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read file.txt",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "file.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm file.txt"
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
@@ -69,7 +103,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm device_results_dir"
@@ -86,7 +120,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir device_results_dir"
@@ -103,12 +137,11 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_EXCEPTION@@@"
     ]
   },
@@ -121,7 +154,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server after failure of 'mkdir /sdcard/revenge_of_the_skiabot/resources' (attempt 1)",
@@ -136,7 +169,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "wait for device after failure of 'mkdir /sdcard/revenge_of_the_skiabot/resources' (attempt 1)",
@@ -154,12 +187,11 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources (attempt 2)",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_EXCEPTION@@@"
     ]
   },
@@ -172,7 +204,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server after failure of 'mkdir /sdcard/revenge_of_the_skiabot/resources' (attempt 2)",
@@ -187,7 +219,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "wait for device after failure of 'mkdir /sdcard/revenge_of_the_skiabot/resources' (attempt 2)",
@@ -205,12 +237,11 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources (attempt 3)",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_EXCEPTION@@@"
     ]
   },
@@ -223,7 +254,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -270,7 +301,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/retry_ios_install.json b/infra/bots/recipe_modules/flavor/examples/full.expected/retry_ios_install.json
index f7f0a0f..79088de 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/retry_ios_install.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/retry_ios_install.json
@@ -7,13 +7,38 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_file file.txt"
   },
   {
     "cmd": [
+      "[START_DIR]/skia/platform_tools/ios/bin/ios_cat_file",
+      "file.txt"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "cat_file file.txt",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "[START_DIR]/skia/platform_tools/ios/bin/ios_rm",
+      "file.txt"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm file.txt"
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
@@ -47,7 +72,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm device_results_dir"
@@ -59,7 +84,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir device_results_dir"
@@ -70,7 +95,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "setup_device"
@@ -83,12 +108,11 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "install_dm",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_EXCEPTION@@@"
     ]
   },
@@ -100,7 +124,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "uninstall_dm"
@@ -113,7 +137,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "install_dm (attempt 2)"
@@ -126,7 +150,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "install_nanobench"
@@ -139,7 +163,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_if_needed [START_DIR]/skia/resources"
@@ -179,7 +203,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "cat_file tmp/SKP_VERSION",
@@ -192,7 +216,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm tmp/SKP_VERSION"
@@ -204,7 +228,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm skps"
@@ -216,7 +240,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir skps"
@@ -229,7 +253,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_if_needed [START_DIR]/skp"
@@ -242,7 +266,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_file [START_DIR]/tmp/SKP_VERSION"
@@ -282,7 +306,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "cat_file tmp/SK_IMAGE_VERSION",
@@ -295,7 +319,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm tmp/SK_IMAGE_VERSION"
@@ -307,7 +331,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm images"
@@ -319,7 +343,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir images"
@@ -332,7 +356,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_if_needed [START_DIR]/skimage"
@@ -345,7 +369,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_file [START_DIR]/tmp/SK_IMAGE_VERSION"
@@ -385,7 +409,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "cat_file tmp/SVG_VERSION",
@@ -398,7 +422,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm tmp/SVG_VERSION"
@@ -410,7 +434,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm svgs"
@@ -422,7 +446,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir svgs"
@@ -435,7 +459,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_if_needed [START_DIR]/svg"
@@ -448,7 +472,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_file [START_DIR]/tmp/SVG_VERSION"
@@ -463,7 +487,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm"
   },
@@ -475,7 +499,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "pull_if_needed dm"
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/retry_ios_install_retries_exhausted.json b/infra/bots/recipe_modules/flavor/examples/full.expected/retry_ios_install_retries_exhausted.json
index 2a417b6..2df0826 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/retry_ios_install_retries_exhausted.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/retry_ios_install_retries_exhausted.json
@@ -7,13 +7,38 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_file file.txt"
   },
   {
     "cmd": [
+      "[START_DIR]/skia/platform_tools/ios/bin/ios_cat_file",
+      "file.txt"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "cat_file file.txt",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "[START_DIR]/skia/platform_tools/ios/bin/ios_rm",
+      "file.txt"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm file.txt"
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
@@ -47,7 +72,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm device_results_dir"
@@ -59,7 +84,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir device_results_dir"
@@ -70,7 +95,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "setup_device"
@@ -83,12 +108,11 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "install_dm",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_EXCEPTION@@@"
     ]
   },
@@ -100,7 +124,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "uninstall_dm"
@@ -113,12 +137,11 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "install_dm (attempt 2)",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_EXCEPTION@@@"
     ]
   },
diff --git a/infra/bots/recipe_modules/flavor/examples/full.py b/infra/bots/recipe_modules/flavor/examples/full.py
index 0431ffd..aae9049 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.py
+++ b/infra/bots/recipe_modules/flavor/examples/full.py
@@ -37,6 +37,8 @@
   if 'Build' not in api.properties['buildername']:
     try:
       api.flavor.copy_file_to_device('file.txt', 'file.txt')
+      api.flavor.read_file_on_device('file.txt')
+      api.flavor.remove_file_on_device('file.txt')
       api.flavor.create_clean_host_dir('results_dir')
       api.flavor.create_clean_device_dir('device_results_dir')
       if 'Lottie' in api.properties['buildername']:
@@ -49,7 +51,10 @@
         api.flavor.copy_directory_contents_to_host(
             api.flavor.device_dirs.dm_dir, api.flavor.host_dirs.dm_dir)
       elif 'Perf' in api.properties['buildername']:
-        api.flavor.step('nanobench', ['nanobench', '--some-flag'])
+        if 'SkottieTracing' in api.properties['buildername']:
+          api.flavor.step('dm', ['dm', '--some-flag'], skip_binary_push=True)
+        else:
+          api.flavor.step('nanobench', ['nanobench', '--some-flag'])
         api.flavor.copy_directory_contents_to_host(
             api.flavor.device_dirs.perf_data_dir,
             api.flavor.host_dirs.perf_data_dir)
@@ -59,6 +64,8 @@
 
 
 TEST_BUILDERS = [
+  ('Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All'
+   '-Android_SkottieTracing'),
   'Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android',
   'Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android',
   'Perf-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All',
@@ -76,7 +83,7 @@
   'Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Debug-All-SwiftShader',
   'Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-OpenCL',
   'Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-Vulkan',
-  'Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN',
+  'Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN',
   ('Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All'
    '-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41'),
   'Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_ProcDump',
diff --git a/infra/bots/recipe_modules/gsutil/examples/full.expected/failed_all_uploads.json b/infra/bots/recipe_modules/gsutil/examples/full.expected/failed_all_uploads.json
index 58acf47..a057b44 100644
--- a/infra/bots/recipe_modules/gsutil/examples/full.expected/failed_all_uploads.json
+++ b/infra/bots/recipe_modules/gsutil/examples/full.expected/failed_all_uploads.json
@@ -10,7 +10,6 @@
     ],
     "name": "upload test file",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
@@ -25,7 +24,6 @@
     ],
     "name": "upload test file (attempt 2)",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
@@ -40,7 +38,6 @@
     ],
     "name": "upload test file (attempt 3)",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
@@ -55,7 +52,6 @@
     ],
     "name": "upload test file (attempt 4)",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
@@ -70,7 +66,6 @@
     ],
     "name": "upload test file (attempt 5)",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
diff --git a/infra/bots/recipe_modules/gsutil/examples/full.expected/failed_one_upload.json b/infra/bots/recipe_modules/gsutil/examples/full.expected/failed_one_upload.json
index 6dc7f6b..2fc5635 100644
--- a/infra/bots/recipe_modules/gsutil/examples/full.expected/failed_one_upload.json
+++ b/infra/bots/recipe_modules/gsutil/examples/full.expected/failed_one_upload.json
@@ -10,7 +10,6 @@
     ],
     "name": "upload test file",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
diff --git a/infra/bots/recipe_modules/infra/examples/full.expected/infra_tests.json b/infra/bots/recipe_modules/infra/examples/full.expected/infra_tests.json
index 9f5a978..89735dd 100644
--- a/infra/bots/recipe_modules/infra/examples/full.expected/infra_tests.json
+++ b/infra/bots/recipe_modules/infra/examples/full.expected/infra_tests.json
@@ -9,7 +9,7 @@
       "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "go version"
   },
@@ -23,7 +23,7 @@
       "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "env go version"
   },
diff --git a/infra/bots/recipe_modules/run/examples/full.expected/test.json b/infra/bots/recipe_modules/run/examples/full.expected/test.json
index 35388fc..8fdbb194 100644
--- a/infra/bots/recipe_modules/run/examples/full.expected/test.json
+++ b/infra/bots/recipe_modules/run/examples/full.expected/test.json
@@ -5,11 +5,10 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "fail",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
@@ -19,11 +18,10 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "fail again",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
@@ -34,7 +32,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "do a thing"
   },
@@ -45,7 +43,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "run 0"
   },
@@ -110,7 +108,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "mydir:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "mydir:<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "env"
   },
@@ -120,11 +118,10 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "retry fail",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
@@ -135,7 +132,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "between_attempts #1"
   },
@@ -145,11 +142,10 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "retry fail (attempt 2)",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
@@ -160,7 +156,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "between_attempts #2"
   },
@@ -170,11 +166,10 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "retry fail (attempt 3)",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
@@ -185,7 +180,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "between_attempts #3"
   },
@@ -195,11 +190,10 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "retry fail (attempt 4)",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
@@ -210,7 +204,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "between_attempts #4"
   },
@@ -220,11 +214,10 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "retry fail (attempt 5)",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
@@ -234,11 +227,10 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "retry success",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
@@ -249,7 +241,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "between_attempts #1 (2)"
   },
@@ -259,11 +251,10 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "retry success (attempt 2)",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
@@ -274,7 +265,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "between_attempts #2 (2)"
   },
@@ -284,7 +275,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "retry success (attempt 3)"
   },
diff --git a/infra/bots/recipe_modules/vars/api.py b/infra/bots/recipe_modules/vars/api.py
index e950000..b736a20 100644
--- a/infra/bots/recipe_modules/vars/api.py
+++ b/infra/bots/recipe_modules/vars/api.py
@@ -29,7 +29,7 @@
     self.default_env['CHROME_HEADLESS'] = '1'
     self.default_env['PATH'] = self.m.path.pathsep.join([
         self.default_env.get('PATH', '%(PATH)s'),
-        str(self.m.bot_update._module.PACKAGE_REPO_ROOT),
+        str(self.m.bot_update.repo_resource()),
     ])
     self.cache_dir = self.slave_dir.join('cache')
 
diff --git a/infra/bots/recipes.py b/infra/bots/recipes.py
index 114821d..2fe0086 100755
--- a/infra/bots/recipes.py
+++ b/infra/bots/recipes.py
@@ -69,7 +69,10 @@
 
     # If we're running ./recipes.py from the recipe_engine repo itself, then
     # return None to signal that there's no EngineDep.
-    if pb['project_id'] == 'recipe_engine':
+    repo_name = pb.get('repo_name')
+    if not repo_name:
+      repo_name = pb['project_id']
+    if repo_name == 'recipe_engine':
       return None, pb.get('recipes_path', '')
 
     engine = pb['deps']['recipe_engine']
@@ -97,6 +100,20 @@
 _BAT = '.bat' if sys.platform.startswith(('win', 'cygwin')) else ''
 GIT = 'git' + _BAT
 VPYTHON = 'vpython' + _BAT
+CIPD = 'cipd' + _BAT
+REQUIRED_BINARIES = {GIT, VPYTHON, CIPD}
+
+
+def _is_executable(path):
+  return os.path.isfile(path) and os.access(path, os.X_OK)
+
+# TODO: Use shutil.which once we switch to Python3.
+def _is_on_path(basename):
+  for path in os.environ['PATH'].split(os.pathsep):
+    full_path = os.path.join(path, basename)
+    if _is_executable(full_path):
+      return True
+  return False
 
 
 def _subprocess_call(argv, **kwargs):
@@ -168,10 +185,18 @@
     except subprocess.CalledProcessError:
       _git_check_call(['reset', '-q', '--hard', revision], cwd=engine_path)
 
+    # If the engine has refactored/moved modules we need to clean all .pyc files
+    # or things will get squirrely.
+    _git_check_call(['clean', '-qxf'], cwd=engine_path)
+
   return engine_path
 
 
 def main():
+  for required_binary in REQUIRED_BINARIES:
+    if not _is_on_path(required_binary):
+      return 'Required binary is not found on PATH: %s' % required_binary
+
   if '--verbose' in sys.argv:
     logging.getLogger().setLevel(logging.INFO)
 
diff --git a/infra/bots/recipes/android_compile.expected/android_compile_trybot.json b/infra/bots/recipes/android_compile.expected/android_compile_trybot.json
index 9d089e0..4f72b3a 100644
--- a/infra/bots/recipes/android_compile.expected/android_compile_trybot.json
+++ b/infra/bots/recipes/android_compile.expected/android_compile_trybot.json
@@ -6,7 +6,7 @@
       "--lunch_target",
       "cf_x86_phone-eng",
       "--mmma_targets",
-      "frameworks/base/core/jni,external/skia",
+      "frameworks/base/core/jni,frameworks/base/libs/hwui,external/skia",
       "--issue",
       "1234",
       "--patchset",
diff --git a/infra/bots/recipes/android_compile.expected/android_compile_trybot_failure.json b/infra/bots/recipes/android_compile.expected/android_compile_trybot_failure.json
index af1f121..1004c3b 100644
--- a/infra/bots/recipes/android_compile.expected/android_compile_trybot_failure.json
+++ b/infra/bots/recipes/android_compile.expected/android_compile_trybot_failure.json
@@ -6,7 +6,7 @@
       "--lunch_target",
       "cf_x86_phone-eng",
       "--mmma_targets",
-      "frameworks/base/core/jni,external/skia",
+      "frameworks/base/core/jni,frameworks/base/libs/hwui,external/skia",
       "--issue",
       "1234",
       "--patchset",
@@ -14,7 +14,6 @@
     ],
     "name": "Trigger and wait for task on android compile server",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
diff --git a/infra/bots/recipes/android_compile.py b/infra/bots/recipes/android_compile.py
index 80dfff8..f3ebbee 100644
--- a/infra/bots/recipes/android_compile.py
+++ b/infra/bots/recipes/android_compile.py
@@ -19,7 +19,8 @@
 SDK_LUNCH_TARGET = 'sdk'
 
 LUNCH_TARGET_TO_MMMA_TARGETS = {
-  CF_X86_PHONE_ENG_LUNCH_TARGET: 'frameworks/base/core/jni,external/skia',
+  CF_X86_PHONE_ENG_LUNCH_TARGET: (
+      'frameworks/base/core/jni,frameworks/base/libs/hwui,external/skia'),
   SDK_LUNCH_TARGET: 'external/skia',
 }
 
diff --git a/infra/bots/recipes/bookmaker.expected/nightly_bookmaker.json b/infra/bots/recipes/bookmaker.expected/nightly_bookmaker.json
deleted file mode 100644
index 7295fac..0000000
--- a/infra/bots/recipes/bookmaker.expected/nightly_bookmaker.json
+++ /dev/null
@@ -1,210 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/cache/work"
-    ],
-    "infra_step": true,
-    "name": "makedirs checkout_path"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "remove",
-      "[START_DIR]/cache/work/.gclient_entries"
-    ],
-    "infra_step": true,
-    "name": "remove [START_DIR]/cache/work/.gclient_entries"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py",
-      "--spec-path",
-      "cache_dir = '[START_DIR]/cache/git'\nsolutions = [{'deps_file': '.DEPS.git', 'managed': False, 'name': 'skia', 'url': 'https://skia.googlesource.com/skia.git'}]",
-      "--patch_root",
-      "skia",
-      "--revision_mapping_file",
-      "{\"got_revision\": \"skia\"}",
-      "--git-cache-dir",
-      "[START_DIR]/cache/git",
-      "--cleanup-dir",
-      "[CLEANUP]/bot_update",
-      "--output_json",
-      "/path/to/tmp/json",
-      "--revision",
-      "skia@abc123"
-    ],
-    "cwd": "[START_DIR]/cache/work",
-    "env_prefixes": {
-      "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
-      ]
-    },
-    "infra_step": true,
-    "name": "bot_update",
-    "~followup_annotations": [
-      "@@@STEP_TEXT@Some step text@@@",
-      "@@@STEP_LOG_LINE@json.output@{@@@",
-      "@@@STEP_LOG_LINE@json.output@  \"did_run\": true, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"fixed_revisions\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"skia\": \"abc123\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"manifest\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"skia\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@      \"repository\": \"https://fake.org/skia.git\", @@@",
-      "@@@STEP_LOG_LINE@json.output@      \"revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@STEP_LOG_LINE@json.output@    }@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"patch_failure\": false, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"patch_root\": \"skia\", @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"properties\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"got_revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\", @@@",
-      "@@@STEP_LOG_LINE@json.output@    \"got_revision_cp\": \"refs/heads/master@{#164710}\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"root\": \"skia\", @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"source_manifest\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"directories\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@      \"skia\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"git_checkout\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@          \"repo_url\": \"https://fake.org/skia.git\", @@@",
-      "@@@STEP_LOG_LINE@json.output@          \"revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@STEP_LOG_LINE@json.output@        }@@@",
-      "@@@STEP_LOG_LINE@json.output@      }@@@",
-      "@@@STEP_LOG_LINE@json.output@    }, @@@",
-      "@@@STEP_LOG_LINE@json.output@    \"version\": 0@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"step_text\": \"Some step text\"@@@",
-      "@@@STEP_LOG_LINE@json.output@}@@@",
-      "@@@STEP_LOG_END@json.output@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision@\"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision_cp@\"refs/heads/master@{#164710}\"@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/build/bookmaker",
-      "-a",
-      "docs/status.json",
-      "-e",
-      "[START_DIR]/fiddle.json"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "Extract all fiddles out of md files"
-  },
-  {
-    "cmd": [
-      "cat",
-      "[START_DIR]/fiddle.json"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "Output fiddle.json"
-  },
-  {
-    "cmd": [
-      "fiddlecli",
-      "--input",
-      "[START_DIR]/fiddle.json",
-      "--output",
-      "[START_DIR]/fiddleout.json",
-      "--procs",
-      "10",
-      "--logtostderr",
-      "--force"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "Force fiddle to compile all examples"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/fiddleout.json",
-      "/path/to/tmp/"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>"
-    },
-    "infra_step": true,
-    "name": "Read fiddleout.json"
-  },
-  {
-    "cmd": [
-      "cat",
-      "[START_DIR]/fiddleout.json"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "Output fiddleout.json"
-  },
-  {
-    "cmd": [
-      "python",
-      "[START_DIR]/cache/work/skia/infra/bots/upload_md.py",
-      "--bookmaker_binary",
-      "[START_DIR]/build/bookmaker",
-      "--fiddlecli_output",
-      "[START_DIR]/fiddleout.json"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "Generate and Upload Markdown files"
-  },
-  {
-    "jsonResult": null,
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipes/bookmaker.expected/nightly_failed_extract_fiddles.json b/infra/bots/recipes/bookmaker.expected/nightly_failed_extract_fiddles.json
deleted file mode 100644
index e68670e..0000000
--- a/infra/bots/recipes/bookmaker.expected/nightly_failed_extract_fiddles.json
+++ /dev/null
@@ -1,127 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/cache/work"
-    ],
-    "infra_step": true,
-    "name": "makedirs checkout_path"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "remove",
-      "[START_DIR]/cache/work/.gclient_entries"
-    ],
-    "infra_step": true,
-    "name": "remove [START_DIR]/cache/work/.gclient_entries"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py",
-      "--spec-path",
-      "cache_dir = '[START_DIR]/cache/git'\nsolutions = [{'deps_file': '.DEPS.git', 'managed': False, 'name': 'skia', 'url': 'https://skia.googlesource.com/skia.git'}]",
-      "--patch_root",
-      "skia",
-      "--revision_mapping_file",
-      "{\"got_revision\": \"skia\"}",
-      "--git-cache-dir",
-      "[START_DIR]/cache/git",
-      "--cleanup-dir",
-      "[CLEANUP]/bot_update",
-      "--output_json",
-      "/path/to/tmp/json",
-      "--revision",
-      "skia@abc123"
-    ],
-    "cwd": "[START_DIR]/cache/work",
-    "env_prefixes": {
-      "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
-      ]
-    },
-    "infra_step": true,
-    "name": "bot_update",
-    "~followup_annotations": [
-      "@@@STEP_TEXT@Some step text@@@",
-      "@@@STEP_LOG_LINE@json.output@{@@@",
-      "@@@STEP_LOG_LINE@json.output@  \"did_run\": true, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"fixed_revisions\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"skia\": \"abc123\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"manifest\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"skia\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@      \"repository\": \"https://fake.org/skia.git\", @@@",
-      "@@@STEP_LOG_LINE@json.output@      \"revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@STEP_LOG_LINE@json.output@    }@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"patch_failure\": false, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"patch_root\": \"skia\", @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"properties\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"got_revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\", @@@",
-      "@@@STEP_LOG_LINE@json.output@    \"got_revision_cp\": \"refs/heads/master@{#164710}\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"root\": \"skia\", @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"source_manifest\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"directories\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@      \"skia\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"git_checkout\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@          \"repo_url\": \"https://fake.org/skia.git\", @@@",
-      "@@@STEP_LOG_LINE@json.output@          \"revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@STEP_LOG_LINE@json.output@        }@@@",
-      "@@@STEP_LOG_LINE@json.output@      }@@@",
-      "@@@STEP_LOG_LINE@json.output@    }, @@@",
-      "@@@STEP_LOG_LINE@json.output@    \"version\": 0@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"step_text\": \"Some step text\"@@@",
-      "@@@STEP_LOG_LINE@json.output@}@@@",
-      "@@@STEP_LOG_END@json.output@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision@\"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision_cp@\"refs/heads/master@{#164710}\"@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/build/bookmaker",
-      "-a",
-      "docs/status.json",
-      "-e",
-      "[START_DIR]/fiddle.json"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "Extract all fiddles out of md files",
-    "~followup_annotations": [
-      "step returned non-zero exit code: 1",
-      "@@@STEP_FAILURE@@@"
-    ]
-  },
-  {
-    "failure": {
-      "failure": {
-        "step": "Extract all fiddles out of md files"
-      },
-      "humanReason": "Step('Extract all fiddles out of md files') failed with return_code 1"
-    },
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipes/bookmaker.expected/nightly_failed_fiddlecli.json b/infra/bots/recipes/bookmaker.expected/nightly_failed_fiddlecli.json
deleted file mode 100644
index 607b793..0000000
--- a/infra/bots/recipes/bookmaker.expected/nightly_failed_fiddlecli.json
+++ /dev/null
@@ -1,164 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/cache/work"
-    ],
-    "infra_step": true,
-    "name": "makedirs checkout_path"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "remove",
-      "[START_DIR]/cache/work/.gclient_entries"
-    ],
-    "infra_step": true,
-    "name": "remove [START_DIR]/cache/work/.gclient_entries"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py",
-      "--spec-path",
-      "cache_dir = '[START_DIR]/cache/git'\nsolutions = [{'deps_file': '.DEPS.git', 'managed': False, 'name': 'skia', 'url': 'https://skia.googlesource.com/skia.git'}]",
-      "--patch_root",
-      "skia",
-      "--revision_mapping_file",
-      "{\"got_revision\": \"skia\"}",
-      "--git-cache-dir",
-      "[START_DIR]/cache/git",
-      "--cleanup-dir",
-      "[CLEANUP]/bot_update",
-      "--output_json",
-      "/path/to/tmp/json",
-      "--revision",
-      "skia@abc123"
-    ],
-    "cwd": "[START_DIR]/cache/work",
-    "env_prefixes": {
-      "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
-      ]
-    },
-    "infra_step": true,
-    "name": "bot_update",
-    "~followup_annotations": [
-      "@@@STEP_TEXT@Some step text@@@",
-      "@@@STEP_LOG_LINE@json.output@{@@@",
-      "@@@STEP_LOG_LINE@json.output@  \"did_run\": true, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"fixed_revisions\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"skia\": \"abc123\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"manifest\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"skia\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@      \"repository\": \"https://fake.org/skia.git\", @@@",
-      "@@@STEP_LOG_LINE@json.output@      \"revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@STEP_LOG_LINE@json.output@    }@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"patch_failure\": false, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"patch_root\": \"skia\", @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"properties\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"got_revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\", @@@",
-      "@@@STEP_LOG_LINE@json.output@    \"got_revision_cp\": \"refs/heads/master@{#164710}\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"root\": \"skia\", @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"source_manifest\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"directories\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@      \"skia\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"git_checkout\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@          \"repo_url\": \"https://fake.org/skia.git\", @@@",
-      "@@@STEP_LOG_LINE@json.output@          \"revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@STEP_LOG_LINE@json.output@        }@@@",
-      "@@@STEP_LOG_LINE@json.output@      }@@@",
-      "@@@STEP_LOG_LINE@json.output@    }, @@@",
-      "@@@STEP_LOG_LINE@json.output@    \"version\": 0@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"step_text\": \"Some step text\"@@@",
-      "@@@STEP_LOG_LINE@json.output@}@@@",
-      "@@@STEP_LOG_END@json.output@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision@\"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision_cp@\"refs/heads/master@{#164710}\"@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/build/bookmaker",
-      "-a",
-      "docs/status.json",
-      "-e",
-      "[START_DIR]/fiddle.json"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "Extract all fiddles out of md files"
-  },
-  {
-    "cmd": [
-      "cat",
-      "[START_DIR]/fiddle.json"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "Output fiddle.json"
-  },
-  {
-    "cmd": [
-      "fiddlecli",
-      "--input",
-      "[START_DIR]/fiddle.json",
-      "--output",
-      "[START_DIR]/fiddleout.json",
-      "--procs",
-      "10",
-      "--logtostderr",
-      "--force"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "Force fiddle to compile all examples",
-    "~followup_annotations": [
-      "step returned non-zero exit code: 1",
-      "@@@STEP_FAILURE@@@"
-    ]
-  },
-  {
-    "failure": {
-      "failure": {
-        "step": "Force fiddle to compile all examples"
-      },
-      "humanReason": "Step('Force fiddle to compile all examples') failed with return_code 1"
-    },
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipes/bookmaker.expected/nightly_failed_fiddles.json b/infra/bots/recipes/bookmaker.expected/nightly_failed_fiddles.json
deleted file mode 100644
index 8eb10c5..0000000
--- a/infra/bots/recipes/bookmaker.expected/nightly_failed_fiddles.json
+++ /dev/null
@@ -1,196 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/cache/work"
-    ],
-    "infra_step": true,
-    "name": "makedirs checkout_path"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "remove",
-      "[START_DIR]/cache/work/.gclient_entries"
-    ],
-    "infra_step": true,
-    "name": "remove [START_DIR]/cache/work/.gclient_entries"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py",
-      "--spec-path",
-      "cache_dir = '[START_DIR]/cache/git'\nsolutions = [{'deps_file': '.DEPS.git', 'managed': False, 'name': 'skia', 'url': 'https://skia.googlesource.com/skia.git'}]",
-      "--patch_root",
-      "skia",
-      "--revision_mapping_file",
-      "{\"got_revision\": \"skia\"}",
-      "--git-cache-dir",
-      "[START_DIR]/cache/git",
-      "--cleanup-dir",
-      "[CLEANUP]/bot_update",
-      "--output_json",
-      "/path/to/tmp/json",
-      "--revision",
-      "skia@abc123"
-    ],
-    "cwd": "[START_DIR]/cache/work",
-    "env_prefixes": {
-      "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
-      ]
-    },
-    "infra_step": true,
-    "name": "bot_update",
-    "~followup_annotations": [
-      "@@@STEP_TEXT@Some step text@@@",
-      "@@@STEP_LOG_LINE@json.output@{@@@",
-      "@@@STEP_LOG_LINE@json.output@  \"did_run\": true, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"fixed_revisions\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"skia\": \"abc123\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"manifest\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"skia\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@      \"repository\": \"https://fake.org/skia.git\", @@@",
-      "@@@STEP_LOG_LINE@json.output@      \"revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@STEP_LOG_LINE@json.output@    }@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"patch_failure\": false, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"patch_root\": \"skia\", @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"properties\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"got_revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\", @@@",
-      "@@@STEP_LOG_LINE@json.output@    \"got_revision_cp\": \"refs/heads/master@{#164710}\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"root\": \"skia\", @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"source_manifest\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"directories\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@      \"skia\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"git_checkout\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@          \"repo_url\": \"https://fake.org/skia.git\", @@@",
-      "@@@STEP_LOG_LINE@json.output@          \"revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@STEP_LOG_LINE@json.output@        }@@@",
-      "@@@STEP_LOG_LINE@json.output@      }@@@",
-      "@@@STEP_LOG_LINE@json.output@    }, @@@",
-      "@@@STEP_LOG_LINE@json.output@    \"version\": 0@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"step_text\": \"Some step text\"@@@",
-      "@@@STEP_LOG_LINE@json.output@}@@@",
-      "@@@STEP_LOG_END@json.output@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision@\"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision_cp@\"refs/heads/master@{#164710}\"@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/build/bookmaker",
-      "-a",
-      "docs/status.json",
-      "-e",
-      "[START_DIR]/fiddle.json"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "Extract all fiddles out of md files"
-  },
-  {
-    "cmd": [
-      "cat",
-      "[START_DIR]/fiddle.json"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "Output fiddle.json"
-  },
-  {
-    "cmd": [
-      "fiddlecli",
-      "--input",
-      "[START_DIR]/fiddle.json",
-      "--output",
-      "[START_DIR]/fiddleout.json",
-      "--procs",
-      "10",
-      "--logtostderr",
-      "--force"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "Force fiddle to compile all examples"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/fiddleout.json",
-      "/path/to/tmp/"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>"
-    },
-    "infra_step": true,
-    "name": "Read fiddleout.json"
-  },
-  {
-    "cmd": [
-      "cat",
-      "[START_DIR]/fiddleout.json"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "Output fiddleout.json"
-  },
-  {
-    "failure": {
-      "failure": {
-        "step": ""
-      },
-      "humanReason": "Failed fiddles with their errors:\n\n\n1. https://fiddle.skia.org/c/abc\n\nninja: Entering directory `out/Release'\n[1/7] ACTION //:skia.h(//gn/toolchain:gcc_like)\n[2/7] stamp obj/skia.h.stamp\n[3/7] compile ../../tools/fiddle/draw.cpp\nFAILED: obj/tools/fiddle/fiddle.draw.o \nc++ -MMD -MF obj/tools/fiddle/fiddle.draw.o.d -DNDEBUG -DSK_HAS_HEIF_LIBRARY -DSK_HAS_JPEG_LIBRARY -DSK_SUPPORT_PDF -DSK_PDF_USE_SFNTLY -DSK_HAS_PNG_LIBRARY -DSK_CODEC_DECODES_RAW -DSK_HAS_WEBP_LIBRARY -DSK_XML -DSK_GAMMA_APPLY_TO_A8 -DSK_ENABLE_DISCRETE_GPU -DGR_TEST_UTILS=1 -DSK_SAMPLES_FOR_X -DSK_SUPPORT_ATLAS_TEXT=1 -I../../tools/flags -I../../include/private -I../../src/c -I../../src/codec -I../../src/core -I../../src/effects -I../../src/fonts -I../../src/image -I../../src/images -I../../src/lazy -I../../src/opts -I../../src/pathops -I../../src/pdf -I../../src/ports -I../../src/sfnt -I../../src/shaders -I../../src/shaders/gradients -I../../src/sksl -I../../src/utils -I../../src/utils/win -I../../src/xml -I../../third_party/gif -I../../src/gpu -I../../tools/gpu -I../../include/android -I../../include/c -I../../include/codec -I../../include/config -I../../include/core -I../../include/effects -I../../include/encode -I../../include/gpu -I../../include/gpu/gl -I../../include/atlastext -I../../include/pathops -I../../include/ports -I../../include/svg -I../../include/utils -I../../include/utils/mac -I../../include/atlastext -Igen -fstrict-aliasing -fPIC -Werror -Wall -Wextra -Winit-self -Wpointer-arith -Wsign-compare -Wvla -Wno-deprecated-declarations -Wno-maybe-uninitialized -Wno-unused-parameter -O3 -fdata-sections -ffunction-sections -g -std=c++14 -fno-exceptions -fno-rtti -Wnon-virtual-dtor -Wno-error -c ../../tools/fiddle/draw.cpp -o obj/tools/fiddle/fiddle.draw.o\n../../tools/fiddle/draw.cpp: In function 'void draw(SkCanvas*)':\ndraw.cpp:5:12: error: aggregate 'SkMask mask' has incomplete type and cannot be defined\n }\n            ^   \ndraw.cpp:6:28: error: incomplete type 'SkMask' used in nested name specifier\n \n                            ^         \ndraw.cpp:14:28: error: incomplete type 'SkMask' used in nested name specifier\n     uint8_t bytes[] = { 0, 1, 2, 3, 4, 5, 6, 7 };\n                            ^~~~~~~~~~\n[4/7] compile ../../tools/fiddle/egl_context.cpp\n[5/7] compile ../../tools/fiddle/fiddle_main.cpp\n[6/7] link libskia.a\nninja: build stopped: subcommand failed.\n\n\n\n"
-    },
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipes/bookmaker.expected/nightly_failed_upload.json b/infra/bots/recipes/bookmaker.expected/nightly_failed_upload.json
deleted file mode 100644
index 90a5ed4..0000000
--- a/infra/bots/recipes/bookmaker.expected/nightly_failed_upload.json
+++ /dev/null
@@ -1,183 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/cache/work"
-    ],
-    "infra_step": true,
-    "name": "makedirs checkout_path"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "remove",
-      "[START_DIR]/cache/work/.gclient_entries"
-    ],
-    "infra_step": true,
-    "name": "remove [START_DIR]/cache/work/.gclient_entries"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py",
-      "--spec-path",
-      "cache_dir = '[START_DIR]/cache/git'\nsolutions = [{'deps_file': '.DEPS.git', 'managed': False, 'name': 'skia', 'url': 'https://skia.googlesource.com/skia.git'}]",
-      "--patch_root",
-      "skia",
-      "--revision_mapping_file",
-      "{\"got_revision\": \"skia\"}",
-      "--git-cache-dir",
-      "[START_DIR]/cache/git",
-      "--cleanup-dir",
-      "[CLEANUP]/bot_update",
-      "--output_json",
-      "/path/to/tmp/json",
-      "--revision",
-      "skia@abc123"
-    ],
-    "cwd": "[START_DIR]/cache/work",
-    "env_prefixes": {
-      "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
-      ]
-    },
-    "infra_step": true,
-    "name": "bot_update",
-    "~followup_annotations": [
-      "@@@STEP_TEXT@Some step text@@@",
-      "@@@STEP_LOG_LINE@json.output@{@@@",
-      "@@@STEP_LOG_LINE@json.output@  \"did_run\": true, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"fixed_revisions\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"skia\": \"abc123\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"manifest\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"skia\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@      \"repository\": \"https://fake.org/skia.git\", @@@",
-      "@@@STEP_LOG_LINE@json.output@      \"revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@STEP_LOG_LINE@json.output@    }@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"patch_failure\": false, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"patch_root\": \"skia\", @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"properties\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"got_revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\", @@@",
-      "@@@STEP_LOG_LINE@json.output@    \"got_revision_cp\": \"refs/heads/master@{#164710}\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"root\": \"skia\", @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"source_manifest\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"directories\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@      \"skia\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"git_checkout\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@          \"repo_url\": \"https://fake.org/skia.git\", @@@",
-      "@@@STEP_LOG_LINE@json.output@          \"revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@STEP_LOG_LINE@json.output@        }@@@",
-      "@@@STEP_LOG_LINE@json.output@      }@@@",
-      "@@@STEP_LOG_LINE@json.output@    }, @@@",
-      "@@@STEP_LOG_LINE@json.output@    \"version\": 0@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"step_text\": \"Some step text\"@@@",
-      "@@@STEP_LOG_LINE@json.output@}@@@",
-      "@@@STEP_LOG_END@json.output@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision@\"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision_cp@\"refs/heads/master@{#164710}\"@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/build/bookmaker",
-      "-a",
-      "docs/status.json",
-      "-e",
-      "[START_DIR]/fiddle.json"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "Extract all fiddles out of md files"
-  },
-  {
-    "cmd": [
-      "cat",
-      "[START_DIR]/fiddle.json"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "Output fiddle.json"
-  },
-  {
-    "cmd": [
-      "fiddlecli",
-      "--input",
-      "[START_DIR]/fiddle.json",
-      "--output",
-      "[START_DIR]/fiddleout.json",
-      "--procs",
-      "10",
-      "--logtostderr",
-      "--force"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "Force fiddle to compile all examples"
-  },
-  {
-    "cmd": [
-      "python",
-      "[START_DIR]/cache/work/skia/infra/bots/upload_md.py",
-      "--bookmaker_binary",
-      "[START_DIR]/build/bookmaker",
-      "--fiddlecli_output",
-      "[START_DIR]/fiddleout.json"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "Generate and Upload Markdown files",
-    "~followup_annotations": [
-      "step returned non-zero exit code: 1",
-      "@@@STEP_FAILURE@@@"
-    ]
-  },
-  {
-    "failure": {
-      "failure": {
-        "step": "Generate and Upload Markdown files"
-      },
-      "humanReason": "Step('Generate and Upload Markdown files') failed with return_code 1"
-    },
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipes/bookmaker.expected/percommit_bookmaker.json b/infra/bots/recipes/bookmaker.expected/percommit_bookmaker.json
deleted file mode 100644
index 0230b60..0000000
--- a/infra/bots/recipes/bookmaker.expected/percommit_bookmaker.json
+++ /dev/null
@@ -1,117 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/cache/work"
-    ],
-    "infra_step": true,
-    "name": "makedirs checkout_path"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "remove",
-      "[START_DIR]/cache/work/.gclient_entries"
-    ],
-    "infra_step": true,
-    "name": "remove [START_DIR]/cache/work/.gclient_entries"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py",
-      "--spec-path",
-      "cache_dir = '[START_DIR]/cache/git'\nsolutions = [{'deps_file': '.DEPS.git', 'managed': False, 'name': 'skia', 'url': 'https://skia.googlesource.com/skia.git'}]",
-      "--patch_root",
-      "skia",
-      "--revision_mapping_file",
-      "{\"got_revision\": \"skia\"}",
-      "--git-cache-dir",
-      "[START_DIR]/cache/git",
-      "--cleanup-dir",
-      "[CLEANUP]/bot_update",
-      "--output_json",
-      "/path/to/tmp/json",
-      "--revision",
-      "skia@abc123"
-    ],
-    "cwd": "[START_DIR]/cache/work",
-    "env_prefixes": {
-      "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
-      ]
-    },
-    "infra_step": true,
-    "name": "bot_update",
-    "~followup_annotations": [
-      "@@@STEP_TEXT@Some step text@@@",
-      "@@@STEP_LOG_LINE@json.output@{@@@",
-      "@@@STEP_LOG_LINE@json.output@  \"did_run\": true, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"fixed_revisions\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"skia\": \"abc123\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"manifest\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"skia\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@      \"repository\": \"https://fake.org/skia.git\", @@@",
-      "@@@STEP_LOG_LINE@json.output@      \"revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@STEP_LOG_LINE@json.output@    }@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"patch_failure\": false, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"patch_root\": \"skia\", @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"properties\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"got_revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\", @@@",
-      "@@@STEP_LOG_LINE@json.output@    \"got_revision_cp\": \"refs/heads/master@{#164710}\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"root\": \"skia\", @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"source_manifest\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"directories\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@      \"skia\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"git_checkout\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@          \"repo_url\": \"https://fake.org/skia.git\", @@@",
-      "@@@STEP_LOG_LINE@json.output@          \"revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@STEP_LOG_LINE@json.output@        }@@@",
-      "@@@STEP_LOG_LINE@json.output@      }@@@",
-      "@@@STEP_LOG_LINE@json.output@    }, @@@",
-      "@@@STEP_LOG_LINE@json.output@    \"version\": 0@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"step_text\": \"Some step text\"@@@",
-      "@@@STEP_LOG_LINE@json.output@}@@@",
-      "@@@STEP_LOG_END@json.output@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision@\"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision_cp@\"refs/heads/master@{#164710}\"@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/build/bookmaker",
-      "-a",
-      "docs/status.json",
-      "-x"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "Validate docs match include/core/*.h"
-  },
-  {
-    "jsonResult": null,
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipes/bookmaker.expected/percommit_failed_validation.json b/infra/bots/recipes/bookmaker.expected/percommit_failed_validation.json
deleted file mode 100644
index a1f1aaf..0000000
--- a/infra/bots/recipes/bookmaker.expected/percommit_failed_validation.json
+++ /dev/null
@@ -1,126 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/cache/work"
-    ],
-    "infra_step": true,
-    "name": "makedirs checkout_path"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "remove",
-      "[START_DIR]/cache/work/.gclient_entries"
-    ],
-    "infra_step": true,
-    "name": "remove [START_DIR]/cache/work/.gclient_entries"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py",
-      "--spec-path",
-      "cache_dir = '[START_DIR]/cache/git'\nsolutions = [{'deps_file': '.DEPS.git', 'managed': False, 'name': 'skia', 'url': 'https://skia.googlesource.com/skia.git'}]",
-      "--patch_root",
-      "skia",
-      "--revision_mapping_file",
-      "{\"got_revision\": \"skia\"}",
-      "--git-cache-dir",
-      "[START_DIR]/cache/git",
-      "--cleanup-dir",
-      "[CLEANUP]/bot_update",
-      "--output_json",
-      "/path/to/tmp/json",
-      "--revision",
-      "skia@abc123"
-    ],
-    "cwd": "[START_DIR]/cache/work",
-    "env_prefixes": {
-      "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
-      ]
-    },
-    "infra_step": true,
-    "name": "bot_update",
-    "~followup_annotations": [
-      "@@@STEP_TEXT@Some step text@@@",
-      "@@@STEP_LOG_LINE@json.output@{@@@",
-      "@@@STEP_LOG_LINE@json.output@  \"did_run\": true, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"fixed_revisions\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"skia\": \"abc123\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"manifest\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"skia\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@      \"repository\": \"https://fake.org/skia.git\", @@@",
-      "@@@STEP_LOG_LINE@json.output@      \"revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@STEP_LOG_LINE@json.output@    }@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"patch_failure\": false, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"patch_root\": \"skia\", @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"properties\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"got_revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\", @@@",
-      "@@@STEP_LOG_LINE@json.output@    \"got_revision_cp\": \"refs/heads/master@{#164710}\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"root\": \"skia\", @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"source_manifest\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"directories\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@      \"skia\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"git_checkout\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@          \"repo_url\": \"https://fake.org/skia.git\", @@@",
-      "@@@STEP_LOG_LINE@json.output@          \"revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@STEP_LOG_LINE@json.output@        }@@@",
-      "@@@STEP_LOG_LINE@json.output@      }@@@",
-      "@@@STEP_LOG_LINE@json.output@    }, @@@",
-      "@@@STEP_LOG_LINE@json.output@    \"version\": 0@@@",
-      "@@@STEP_LOG_LINE@json.output@  }, @@@",
-      "@@@STEP_LOG_LINE@json.output@  \"step_text\": \"Some step text\"@@@",
-      "@@@STEP_LOG_LINE@json.output@}@@@",
-      "@@@STEP_LOG_END@json.output@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision@\"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision_cp@\"refs/heads/master@{#164710}\"@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/build/bookmaker",
-      "-a",
-      "docs/status.json",
-      "-x"
-    ],
-    "cwd": "[START_DIR]/cache/work/skia",
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOCACHE": "[START_DIR]/cache/go_cache",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "Validate docs match include/core/*.h",
-    "~followup_annotations": [
-      "step returned non-zero exit code: 1",
-      "@@@STEP_FAILURE@@@"
-    ]
-  },
-  {
-    "failure": {
-      "failure": {
-        "step": "Validate docs match include/core/*.h"
-      },
-      "humanReason": "Step('Validate docs match include/core/*.h') failed with return_code 1\n\nView the output of the \"Validate docs match include/core/*.h\" step to see how to get this bot green.\n\nhttps://skia.org/user/api/usingBookmaker details how to build and run the bookmaker utility locally if needed."
-    },
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipes/bookmaker.py b/infra/bots/recipes/bookmaker.py
deleted file mode 100644
index eaa0a80..0000000
--- a/infra/bots/recipes/bookmaker.py
+++ /dev/null
@@ -1,315 +0,0 @@
-# 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.
-
-# Recipe which:
-# 1) Extracts all fiddles out of markdown files.
-# 2) Forces fiddle.skia.org to compile all those fiddles and get output in JSON.
-# 3) Scans the output and reports any compiletime/runtime errors.
-# 4) Updates markdown in site/user/api/ using the new hashes (if any) from
-#    fiddle.skia.org.
-
-import json
-
-
-DEPS = [
-  'recipe_engine/context',
-  'recipe_engine/file',
-  'recipe_engine/path',
-  'recipe_engine/properties',
-  'recipe_engine/step',
-  'checkout',
-  'infra',
-  'run',
-  'vars',
-]
-
-
-def RunSteps(api):
-  api.vars.setup()
-  checkout_root = api.checkout.default_checkout_root
-  api.checkout.bot_update(checkout_root=checkout_root)
-
-  skia_dir = checkout_root.join('skia')
-  with api.context(cwd=skia_dir, env=api.infra.go_env):
-    bookmaker_binary = api.vars.build_dir.join('bookmaker')
-    buildername = api.vars.builder_name
-
-    if 'PerCommit' in buildername:
-      # Check to see if docs matches include/core.
-      cmd = [bookmaker_binary,
-             '-a', 'docs/status.json',  # File containing status of docs.
-             '-x',  # Check bmh against includes.
-             ]
-      try:
-        api.run(api.step, 'Validate docs match include/core/*.h', cmd=cmd)
-      except api.step.StepFailure as e:
-        # Display what needs to be fixed.
-        e.reason += (
-            '\n\nView the output of the "Validate docs match include/core/*.h" '
-            'step to see how to get this bot green.'
-            '\n\nhttps://skia.org/user/api/usingBookmaker details how to build '
-            'and run the bookmaker utility locally if needed.')
-        raise e
-
-    elif 'Nightly' in buildername:
-      # fiddlecli is compiled and bundled into the go_deps asset. With
-      # api.infra.go_env, it is on PATH.
-      fiddlecli_binary = 'fiddlecli'
-      fiddlecli_input = api.path.join(api.path['start_dir'], 'fiddle.json')
-      fiddlecli_output = api.path.join(api.path['start_dir'], 'fiddleout.json')
-
-      # Step 1: Extract all fiddles out of markdown files.
-      cmd = [bookmaker_binary,
-             '-a', 'docs/status.json',  # File containing status of docs.
-             '-e', fiddlecli_input,  # Fiddle cli input.
-             ]
-      api.run(api.step, 'Extract all fiddles out of md files', cmd=cmd)
-
-      # Output fiddle.json for easy debugging.
-      api.run(api.step, 'Output fiddle.json',
-              cmd=['cat', fiddlecli_input])
-
-      # Step 2: Forces fiddle.skia.org to compile all fiddles extracted out of
-      #         markdown files and get output in JSON.
-      cmd = [fiddlecli_binary,
-             '--input', fiddlecli_input,
-             '--output', fiddlecli_output,
-             '--procs', 10, # Number of concurrent requests.
-             '--logtostderr',
-             '--force',
-          ]
-      api.run(api.step, 'Force fiddle to compile all examples', cmd=cmd)
-
-      # Step 3: Scan the output of fiddlecli for any compiletime/runtime errors.
-      #         Fail the recipe is there are any errors and summarize results at
-      #         the end.
-      if api.path.exists(fiddlecli_output):
-        test_data = api.properties.get('fiddleout_test_data', '{}')
-        content = api.file.read_text('Read fiddleout.json',
-                                     fiddlecli_output, test_data=test_data)
-        out = json.loads(content)
-
-        # Output fiddleout.json for easy debugging.
-        api.run(api.step, 'Output fiddleout.json',
-                cmd=['cat', fiddlecli_output])
-
-        failing_fiddles_to_errors = {}
-        for fiddle_name in out:
-          props = out[fiddle_name]
-          if props['compile_errors'] or props['runtime_error']:
-            # Construct the error.
-            error = props['runtime_error']
-            if props['compile_errors']:
-              for e in props['compile_errors']:
-                error += '%s\n' % e['text']
-            failing_fiddles_to_errors[props['fiddleHash']] = error
-
-        if failing_fiddles_to_errors:
-          # create an eror message and fail the bot!
-          failure_msg = 'Failed fiddles with their errors:\n\n\n'
-          counter = 0
-          for fiddle_hash, error in failing_fiddles_to_errors.iteritems():
-            counter += 1
-            failure_msg += '%d. https://fiddle.skia.org/c/%s\n\n' % (
-                counter, fiddle_hash)
-            failure_msg += '%s\n\n' % error
-          raise api.step.StepFailure(failure_msg)
-
-      # Step 4: Update docs in site/user/api/ with the output of fiddlecli.
-      #         If there are any new changes then upload and commit the changes.
-      cmd = ['python',
-             skia_dir.join('infra', 'bots', 'upload_md.py'),
-            '--bookmaker_binary', bookmaker_binary,
-             '--fiddlecli_output', fiddlecli_output]
-      with api.context(cwd=skia_dir):
-        api.run(api.step, 'Generate and Upload Markdown files', cmd=cmd)
-
-
-def GenTests(api):
-  fiddleout_no_errors_test_data = """
-{"fiddle1": {"fiddleHash": "abc",
-             "compile_errors": [],
-             "runtime_error": ""}}
-"""
-  fiddleout_with_errors_test_data = """
-{"fiddle1": {"fiddleHash": "abc",
-             "compile_errors": [
-            {
-                "text": "ninja: Entering directory `out/Release'",
-                "line": 0,
-                "col": 0
-            },
-            {
-                "text": "[1/7] ACTION //:skia.h(//gn/toolchain:gcc_like)",
-                "line": 0,
-                "col": 0
-            },
-            {
-                "text": "[2/7] stamp obj/skia.h.stamp",
-                "line": 0,
-                "col": 0
-            },
-            {
-                "text": "[3/7] compile ../../tools/fiddle/draw.cpp",
-                "line": 0,
-                "col": 0
-            },
-            {
-                "text": "FAILED: obj/tools/fiddle/fiddle.draw.o ",
-                "line": 0,
-                "col": 0
-            },
-            {
-                "text": "c++ -MMD -MF obj/tools/fiddle/fiddle.draw.o.d -DNDEBUG -DSK_HAS_HEIF_LIBRARY -DSK_HAS_JPEG_LIBRARY -DSK_SUPPORT_PDF -DSK_PDF_USE_SFNTLY -DSK_HAS_PNG_LIBRARY -DSK_CODEC_DECODES_RAW -DSK_HAS_WEBP_LIBRARY -DSK_XML -DSK_GAMMA_APPLY_TO_A8 -DSK_ENABLE_DISCRETE_GPU -DGR_TEST_UTILS=1 -DSK_SAMPLES_FOR_X -DSK_SUPPORT_ATLAS_TEXT=1 -I../../tools/flags -I../../include/private -I../../src/c -I../../src/codec -I../../src/core -I../../src/effects -I../../src/fonts -I../../src/image -I../../src/images -I../../src/lazy -I../../src/opts -I../../src/pathops -I../../src/pdf -I../../src/ports -I../../src/sfnt -I../../src/shaders -I../../src/shaders/gradients -I../../src/sksl -I../../src/utils -I../../src/utils/win -I../../src/xml -I../../third_party/gif -I../../src/gpu -I../../tools/gpu -I../../include/android -I../../include/c -I../../include/codec -I../../include/config -I../../include/core -I../../include/effects -I../../include/encode -I../../include/gpu -I../../include/gpu/gl -I../../include/atlastext -I../../include/pathops -I../../include/ports -I../../include/svg -I../../include/utils -I../../include/utils/mac -I../../include/atlastext -Igen -fstrict-aliasing -fPIC -Werror -Wall -Wextra -Winit-self -Wpointer-arith -Wsign-compare -Wvla -Wno-deprecated-declarations -Wno-maybe-uninitialized -Wno-unused-parameter -O3 -fdata-sections -ffunction-sections -g -std=c++14 -fno-exceptions -fno-rtti -Wnon-virtual-dtor -Wno-error -c ../../tools/fiddle/draw.cpp -o obj/tools/fiddle/fiddle.draw.o",
-                "line": 0,
-                "col": 0
-            },
-            {
-                "text": "../../tools/fiddle/draw.cpp: In function 'void draw(SkCanvas*)':",
-                "line": 0,
-                "col": 0
-            },
-            {
-                "text": "draw.cpp:5:12: error: aggregate 'SkMask mask' has incomplete type and cannot be defined",
-                "line": 5,
-                "col": 12
-            },
-            {
-                "text": " }",
-                "line": 0,
-                "col": 0
-            },
-            {
-                "text": "            ^   ",
-                "line": 0,
-                "col": 0
-            },
-            {
-                "text": "draw.cpp:6:28: error: incomplete type 'SkMask' used in nested name specifier",
-                "line": 6,
-                "col": 28
-            },
-            {
-                "text": " ",
-                "line": 0,
-                "col": 0
-            },
-            {
-                "text": "                            ^         ",
-                "line": 0,
-                "col": 0
-            },
-            {
-                "text": "draw.cpp:14:28: error: incomplete type 'SkMask' used in nested name specifier",
-                "line": 14,
-                "col": 28
-            },
-            {
-                "text": "     uint8_t bytes[] = { 0, 1, 2, 3, 4, 5, 6, 7 };",
-                "line": 0,
-                "col": 0
-            },
-            {
-                "text": "                            ^~~~~~~~~~",
-                "line": 0,
-                "col": 0
-            },
-            {
-                "text": "[4/7] compile ../../tools/fiddle/egl_context.cpp",
-                "line": 0,
-                "col": 0
-            },
-            {
-                "text": "[5/7] compile ../../tools/fiddle/fiddle_main.cpp",
-                "line": 0,
-                "col": 0
-            },
-            {
-                "text": "[6/7] link libskia.a",
-                "line": 0,
-                "col": 0
-            },
-            {
-                "text": "ninja: build stopped: subcommand failed.",
-                "line": 0,
-                "col": 0
-            },
-            {
-                "text": "",
-                "line": 0,
-                "col": 0
-            }
-        ],
-             "runtime_error": ""}}
-"""
-  yield (
-      api.test('percommit_bookmaker') +
-      api.properties(buildername='Housekeeper-PerCommit-Bookmaker',
-                     repository='https://skia.googlesource.com/skia.git',
-                     revision='abc123',
-                     path_config='kitchen',
-                     swarm_out_dir='[SWARM_OUT_DIR]')
-  )
-
-  yield (
-      api.test('percommit_failed_validation') +
-      api.properties(buildername='Housekeeper-PerCommit-Bookmaker',
-                     repository='https://skia.googlesource.com/skia.git',
-                     revision='abc123',
-                     path_config='kitchen',
-                     swarm_out_dir='[SWARM_OUT_DIR]') +
-      api.step_data('Validate docs match include/core/*.h', retcode=1)
-  )
-
-  yield (
-      api.test('nightly_bookmaker') +
-      api.properties(buildername='Housekeeper-Nightly-Bookmaker',
-                     repository='https://skia.googlesource.com/skia.git',
-                     revision='abc123',
-                     path_config='kitchen',
-                     fiddleout_test_data=fiddleout_no_errors_test_data,
-                     swarm_out_dir='[SWARM_OUT_DIR]') +
-      api.path.exists(api.path['start_dir'].join('fiddleout.json'))
-  )
-
-  yield (
-      api.test('nightly_failed_fiddles') +
-      api.properties(buildername='Housekeeper-Nightly-Bookmaker',
-                     repository='https://skia.googlesource.com/skia.git',
-                     revision='abc123',
-                     path_config='kitchen',
-                     fiddleout_test_data=fiddleout_with_errors_test_data,
-                     swarm_out_dir='[SWARM_OUT_DIR]') +
-      api.path.exists(api.path['start_dir'].join('fiddleout.json'))
-  )
-
-  yield (
-      api.test('nightly_failed_extract_fiddles') +
-      api.properties(buildername='Housekeeper-Nightly-Bookmaker',
-                     repository='https://skia.googlesource.com/skia.git',
-                     revision='abc123',
-                     path_config='kitchen',
-                     swarm_out_dir='[SWARM_OUT_DIR]') +
-      api.step_data('Extract all fiddles out of md files', retcode=1)
-  )
-
-  yield (
-      api.test('nightly_failed_fiddlecli') +
-      api.properties(buildername='Housekeeper-Nightly-Bookmaker',
-                     repository='https://skia.googlesource.com/skia.git',
-                     revision='abc123',
-                     path_config='kitchen',
-                     swarm_out_dir='[SWARM_OUT_DIR]') +
-      api.step_data('Force fiddle to compile all examples', retcode=1)
-  )
-
-  yield (
-      api.test('nightly_failed_upload') +
-      api.properties(buildername='Housekeeper-Nightly-Bookmaker',
-                     repository='https://skia.googlesource.com/skia.git',
-                     revision='abc123',
-                     path_config='kitchen',
-                     swarm_out_dir='[SWARM_OUT_DIR]') +
-      api.step_data('Generate and Upload Markdown files', retcode=1)
-  )
diff --git a/infra/bots/recipes/calmbench.expected/Calmbench-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All.json b/infra/bots/recipes/calmbench.expected/Calmbench-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All.json
index 60e5044..52337f8 100644
--- a/infra/bots/recipes/calmbench.expected/Calmbench-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All.json
+++ b/infra/bots/recipes/calmbench.expected/Calmbench-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All.json
@@ -121,7 +121,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Run calmbench"
   },
diff --git a/infra/bots/recipes/calmbench.expected/Calmbench-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All.json b/infra/bots/recipes/calmbench.expected/Calmbench-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All.json
index 6be8d18..279d126 100644
--- a/infra/bots/recipes/calmbench.expected/Calmbench-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All.json
+++ b/infra/bots/recipes/calmbench.expected/Calmbench-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All.json
@@ -121,7 +121,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Run calmbench"
   },
diff --git a/infra/bots/recipes/check_generated_files.expected/Housekeeper-PerCommit-CheckGeneratedFiles.json b/infra/bots/recipes/check_generated_files.expected/Housekeeper-PerCommit-CheckGeneratedFiles.json
index 438d4eb..44ca917 100644
--- a/infra/bots/recipes/check_generated_files.expected/Housekeeper-PerCommit-CheckGeneratedFiles.json
+++ b/infra/bots/recipes/check_generated_files.expected/Housekeeper-PerCommit-CheckGeneratedFiles.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -117,13 +117,29 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "git diff #1",
     "stdout": "/path/to/tmp/"
   },
   {
     "cmd": [
+      "make",
+      "-C",
+      "tools/gpu/gl/interface",
+      "generate"
+    ],
+    "cwd": "[START_DIR]/cache/work/skia",
+    "env": {
+      "GOCACHE": "[START_DIR]/cache/go_cache",
+      "GOPATH": "[START_DIR]/go_deps",
+      "GOROOT": "[START_DIR]/go/go",
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>"
+    },
+    "name": "generate gl interfaces"
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "import os\nimport subprocess\n\nfor r, d, files in os.walk('[START_DIR]/cache/work/skia'):\n  for f in files:\n    if f.endswith('.fp'):\n      path = os.path.join(r, f)\n      print 'touch %s' % path\n      subprocess.check_call(['touch', path])\n"
@@ -131,7 +147,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "touch fp files",
     "~followup_annotations": [
@@ -156,7 +172,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -170,7 +186,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-clang-format"
@@ -185,7 +201,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "[START_DIR]/cache/work/skia/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/cache/work/skia/bin:<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -198,7 +214,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "[START_DIR]/cache/work/skia/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/cache/work/skia/bin:<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -211,7 +227,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "git diff #2",
     "stdout": "/path/to/tmp/"
@@ -225,7 +241,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "compare diffs",
     "~followup_annotations": [
diff --git a/infra/bots/recipes/check_generated_files.py b/infra/bots/recipes/check_generated_files.py
index 537d775..07a6770 100644
--- a/infra/bots/recipes/check_generated_files.py
+++ b/infra/bots/recipes/check_generated_files.py
@@ -7,6 +7,7 @@
 
 DEPS = [
   'build',
+  'infra',
   'recipe_engine/context',
   'recipe_engine/file',
   'recipe_engine/path',
@@ -40,6 +41,10 @@
         cmd=['git', 'diff', '--no-ext-diff'],
         stdout=api.m.raw_io.output()).stdout
 
+    with api.context(env=api.infra.go_env):
+      api.step('generate gl interfaces',
+               cmd=['make', '-C', 'tools/gpu/gl/interface', 'generate'])
+
     # Touch all .fp files so that the generated files are rebuilt.
     api.run(
         api.python.inline,
diff --git a/infra/bots/recipes/compile.expected/Build-Debian9-Clang-universal-devrel-Android_SKQP.json b/infra/bots/recipes/compile.expected/Build-Debian9-Clang-universal-devrel-Android_SKQP.json
index e209f6a..3fdd9c9 100644
--- a/infra/bots/recipes/compile.expected/Build-Debian9-Clang-universal-devrel-Android_SKQP.json
+++ b/infra/bots/recipes/compile.expected/Build-Debian9-Clang-universal-devrel-Android_SKQP.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -140,7 +140,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Build SKQP apk with Docker"
   },
@@ -148,7 +148,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-universal-devrel-Android_SKQP/devrel",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
@@ -163,7 +163,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipes/compile.expected/Build-Debian9-Clang-x86_64-Release-NoDEPS.json b/infra/bots/recipes/compile.expected/Build-Debian9-Clang-x86_64-Release-NoDEPS.json
index 27c716b..e2ef5e1 100644
--- a/infra/bots/recipes/compile.expected/Build-Debian9-Clang-x86_64-Release-NoDEPS.json
+++ b/infra/bots/recipes/compile.expected/Build-Debian9-Clang-x86_64-Release-NoDEPS.json
@@ -22,7 +22,7 @@
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
-      "PATH": "RECIPE_PACKAGE_REPO[depot_tools]:<PATH>"
+      "PATH": "RECIPE_REPO[depot_tools]:<PATH>"
     },
     "infra_step": true,
     "name": "git fetch"
@@ -102,7 +102,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -112,12 +112,12 @@
       "[START_DIR]/skia/bin/gn",
       "gen",
       "[START_DIR]/skia/out/Build-Debian9-Clang-x86_64-Release-NoDEPS/Release",
-      "--args=cc=\"[START_DIR]/clang_linux/bin/clang\" cxx=\"[START_DIR]/clang_linux/bin/clang++\" extra_cflags=[\"-B[START_DIR]/clang_linux/bin\", \"-DDUMMY_clang_linux_version=42\"] extra_ldflags=[\"-B[START_DIR]/clang_linux/bin\", \"-fuse-ld=lld\"] is_debug=false is_official_build=true skia_enable_fontmgr_empty=true skia_enable_gpu=true skia_enable_pdf=false skia_use_expat=false skia_use_freetype=false skia_use_libjpeg_turbo=false skia_use_libpng=false skia_use_libwebp=false skia_use_vulkan=false skia_use_zlib=false target_cpu=\"x86_64\""
+      "--args=cc=\"[START_DIR]/clang_linux/bin/clang\" cxx=\"[START_DIR]/clang_linux/bin/clang++\" extra_cflags=[\"-B[START_DIR]/clang_linux/bin\", \"-DDUMMY_clang_linux_version=42\"] extra_ldflags=[\"-B[START_DIR]/clang_linux/bin\", \"-fuse-ld=lld\"] is_debug=false is_official_build=true skia_enable_fontmgr_empty=true skia_enable_gpu=true skia_enable_pdf=false skia_use_expat=false skia_use_freetype=false skia_use_harfbuzz=false skia_use_libjpeg_turbo=false skia_use_libpng=false skia_use_libwebp=false skia_use_vulkan=false skia_use_zlib=false target_cpu=\"x86_64\""
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -130,7 +130,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -138,7 +138,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/skia/out/Build-Debian9-Clang-x86_64-Release-NoDEPS/Release",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
@@ -153,7 +153,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipes/compile.expected/Build-Debian9-Clang-x86_64-Release-ParentRevision.json b/infra/bots/recipes/compile.expected/Build-Debian9-Clang-x86_64-Release-ParentRevision.json
index 5477ba2..702b56e 100644
--- a/infra/bots/recipes/compile.expected/Build-Debian9-Clang-x86_64-Release-ParentRevision.json
+++ b/infra/bots/recipes/compile.expected/Build-Debian9-Clang-x86_64-Release-ParentRevision.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -131,7 +131,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -146,7 +146,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -159,7 +159,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -167,7 +167,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Release-ParentRevision/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/ParentRevision"
     ],
@@ -182,7 +182,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipes/compile.expected/Build-Debian9-GCC-x86_64-Release-Flutter_Android.json b/infra/bots/recipes/compile.expected/Build-Debian9-GCC-x86_64-Release-Flutter_Android.json
index 1046f27..8a7bc5f 100644
--- a/infra/bots/recipes/compile.expected/Build-Debian9-GCC-x86_64-Release-Flutter_Android.json
+++ b/infra/bots/recipes/compile.expected/Build-Debian9-GCC-x86_64-Release-Flutter_Android.json
@@ -52,7 +52,7 @@
     "cwd": "[START_DIR]/cache/work/flutter",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -114,7 +114,7 @@
     "cmd": [
       "python",
       "-u",
-      "RECIPE_PACKAGE_REPO[depot_tools]/gclient.py",
+      "RECIPE_REPO[depot_tools]/gclient.py",
       "runhooks"
     ],
     "cwd": "[START_DIR]/cache/work/flutter",
@@ -123,7 +123,7 @@
     },
     "env_suffixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "name": "gclient runhooks"
@@ -166,7 +166,7 @@
     "cwd": "[START_DIR]/cache/work/flutter/src",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn_gen"
   },
@@ -180,7 +180,7 @@
     "cwd": "[START_DIR]/cache/work/flutter/src",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "build_flutter"
   },
@@ -188,7 +188,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/flutter/src/out/android_release",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
@@ -203,7 +203,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipes/compile.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json b/infra/bots/recipes/compile.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json
index 0edab0c..b0ded97 100644
--- a/infra/bots/recipes/compile.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json
+++ b/infra/bots/recipes/compile.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -97,7 +97,7 @@
     "cmd": [
       "python",
       "-u",
-      "RECIPE_PACKAGE_REPO[depot_tools]/gclient.py",
+      "RECIPE_REPO[depot_tools]/gclient.py",
       "runhooks"
     ],
     "cwd": "[START_DIR]/cache/work",
@@ -107,7 +107,7 @@
     },
     "env_suffixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "name": "gclient runhooks"
@@ -177,7 +177,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "build command_buffer"
   },
@@ -190,7 +190,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -200,12 +200,12 @@
       "[START_DIR]/cache/work/skia/bin/gn",
       "gen",
       "[START_DIR]/cache/work/skia/out/Build-Mac-Clang-x86_64-Debug-CommandBuffer/Debug",
-      "--args=cc=\"clang\" cxx=\"clang++\" extra_cflags=[\"-DDUMMY_xcode_build_version=9c40b\", \"-O1\"] target_cpu=\"x86_64\""
+      "--args=cc=\"clang\" cxx=\"clang++\" extra_cflags=[\"-DDUMMY_xcode_build_version=9c40b\", \"-O1\"] skia_gl_standard=\"\" target_cpu=\"x86_64\""
     ],
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -218,7 +218,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -226,7 +226,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Mac-Clang-x86_64-Debug-CommandBuffer/Debug",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
@@ -241,7 +241,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipes/compile.expected/Build-Win-Clang-x86-Debug.json b/infra/bots/recipes/compile.expected/Build-Win-Clang-x86-Debug.json
index 81b8cc4..8e264cc 100644
--- a/infra/bots/recipes/compile.expected/Build-Win-Clang-x86-Debug.json
+++ b/infra/bots/recipes/compile.expected/Build-Win-Clang-x86-Debug.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -131,7 +131,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "fetch-gn"
@@ -146,7 +146,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "gn gen"
   },
@@ -159,7 +159,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "ninja"
   },
@@ -167,7 +167,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[START_DIR]/cache/work/skia/out/Build-Win-Clang-x86-Debug/Debug",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
@@ -182,7 +182,7 @@
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
       "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
-      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'skottie_tool', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@try:@@@",
       "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
diff --git a/infra/bots/recipes/compute_buildstats.expected/normal_bot.json b/infra/bots/recipes/compute_buildstats.expected/normal_bot.json
index acc4e08..ed796f6 100644
--- a/infra/bots/recipes/compute_buildstats.expected/normal_bot.json
+++ b/infra/bots/recipes/compute_buildstats.expected/normal_bot.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -172,7 +172,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Analyze wasm",
     "stdout": "/path/to/tmp/",
@@ -228,7 +228,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Analyze [START_DIR]/build/pathkit.js"
   },
@@ -265,7 +265,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Analyze [START_DIR]/build/pathkit.js.mem"
   },
@@ -304,7 +304,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Analyze flutter",
     "stdout": "/path/to/tmp/",
@@ -371,11 +371,48 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Analyze [START_DIR]/build/libskia.so"
   },
   {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "glob",
+      "[START_DIR]/build",
+      "skottie_tool"
+    ],
+    "cwd": "[START_DIR]/build",
+    "infra_step": true,
+    "name": "find skottie_tool",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/build/skottie_tool@@@",
+      "@@@STEP_LOG_END@glob@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/cache/work/skia/infra/bots/buildstats/make_treemap.py",
+      "[START_DIR]/build/skottie_tool",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "cwd": "[START_DIR]/cache/work/skia",
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "DOCKER_CONFIG": "/home/chrome-bot/.docker",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "Make code size treemap",
+    "stdout": "/path/to/tmp/"
+  },
+  {
     "jsonResult": null,
     "name": "$result"
   }
diff --git a/infra/bots/recipes/compute_buildstats.expected/trybot.json b/infra/bots/recipes/compute_buildstats.expected/trybot.json
index 5ed14aa..bbf0827 100644
--- a/infra/bots/recipes/compute_buildstats.expected/trybot.json
+++ b/infra/bots/recipes/compute_buildstats.expected/trybot.json
@@ -31,7 +31,7 @@
     "cmd": [
       "python",
       "-u",
-      "RECIPE_PACKAGE_REPO[depot_tools]/gerrit_client.py",
+      "RECIPE_REPO[depot_tools]/gerrit_client.py",
       "changes",
       "--host",
       "https://skia-review.googlesource.com",
@@ -48,7 +48,7 @@
     ],
     "cwd": "[START_DIR]/cache/work",
     "env": {
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "gerrit fetch current CL info",
@@ -94,7 +94,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -216,7 +216,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Analyze wasm",
     "stdout": "/path/to/tmp/",
@@ -272,7 +272,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Analyze [START_DIR]/build/pathkit.js"
   },
@@ -309,7 +309,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Analyze [START_DIR]/build/pathkit.js.mem"
   },
@@ -348,7 +348,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Analyze flutter",
     "stdout": "/path/to/tmp/",
@@ -415,11 +415,48 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Analyze [START_DIR]/build/libskia.so"
   },
   {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "glob",
+      "[START_DIR]/build",
+      "skottie_tool"
+    ],
+    "cwd": "[START_DIR]/build",
+    "infra_step": true,
+    "name": "find skottie_tool",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@glob@[START_DIR]/build/skottie_tool@@@",
+      "@@@STEP_LOG_END@glob@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/cache/work/skia/infra/bots/buildstats/make_treemap.py",
+      "[START_DIR]/build/skottie_tool",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "cwd": "[START_DIR]/cache/work/skia",
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "DOCKER_CONFIG": "/home/chrome-bot/.docker",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "Make code size treemap",
+    "stdout": "/path/to/tmp/"
+  },
+  {
     "jsonResult": null,
     "name": "$result"
   }
diff --git a/infra/bots/recipes/compute_buildstats.py b/infra/bots/recipes/compute_buildstats.py
index 648a592..bcd7fce 100644
--- a/infra/bots/recipes/compute_buildstats.py
+++ b/infra/bots/recipes/compute_buildstats.py
@@ -6,6 +6,7 @@
 
 DEPS = [
   'checkout',
+  'env',
   'recipe_engine/context',
   'recipe_engine/file',
   'recipe_engine/path',
@@ -37,7 +38,7 @@
         '*.wasm',
         test_data=['pathkit.wasm'])
     analyzed += len(files)
-    if len(files):
+    if files:
       analyze_wasm_file(api, checkout_root, out_dir, files)
 
     files = api.file.glob_paths(
@@ -46,7 +47,7 @@
         '*.js',
         test_data=['pathkit.js'])
     analyzed += len(files)
-    if len(files):
+    if files:
       analyze_web_file(api, checkout_root, out_dir, files)
 
     files = api.file.glob_paths(
@@ -55,7 +56,7 @@
         '*.js.mem',
         test_data=['pathkit.js.mem'])
     analyzed += len(files)
-    if len(files):
+    if files:
       analyze_web_file(api, checkout_root, out_dir, files)
 
     files = api.file.glob_paths(
@@ -64,7 +65,7 @@
         'libflutter.so',
         test_data=['libflutter.so'])
     analyzed += len(files)
-    if len(files):
+    if files:
       analyze_flutter_lib(api, checkout_root, out_dir, files)
 
     files = api.file.glob_paths(
@@ -73,8 +74,18 @@
         'libskia.so',
         test_data=['libskia.so'])
     analyzed += len(files)
-    if len(files):
+    if files:
       analyze_cpp_lib(api, checkout_root, out_dir, files)
+
+    files = api.file.glob_paths(
+        'find skottie_tool',
+        bin_dir,
+        'skottie_tool',
+        test_data=['skottie_tool'])
+    analyzed += len(files)
+    if files:
+      make_treemap(api, checkout_root, out_dir, files)
+
   if not analyzed: # pragma: nocover
     raise Exception('No files were analyzed!')
 
@@ -172,8 +183,8 @@
       script = skia_dir.join('infra', 'bots', 'buildstats',
                              'buildstats_wasm.py')
       step_data = api.run(api.python, 'Analyze wasm', script=script,
-                         args=[f, out_dir, keystr, propstr, bloaty_exe],
-                         stdout=api.raw_io.output())
+                          args=[f, out_dir, keystr, propstr, bloaty_exe],
+                          stdout=api.raw_io.output())
       if step_data and step_data.stdout:
         magic_seperator = '#$%^&*'
         sections = step_data.stdout.split(magic_seperator)
@@ -186,6 +197,21 @@
         logs['perf_json']           = sections[3].split('\n')
 
 
+# make a zip file containing an HTML treemap of the files
+def make_treemap(api, checkout_root, out_dir, files):
+  for f in files:
+    env = {'DOCKER_CONFIG': '/home/chrome-bot/.docker'}
+    with api.env(env):
+      skia_dir = checkout_root.join('skia')
+      with api.context(cwd=skia_dir):
+        script = skia_dir.join('infra', 'bots', 'buildstats',
+                               'make_treemap.py')
+        api.run(api.python, 'Make code size treemap',
+                             script=script,
+                             args=[f, out_dir],
+                             stdout=api.raw_io.output())
+
+
 def GenTests(api):
   builder = 'BuildStats-Debian9-EMCC-wasm-Release-PathKit'
   yield (
diff --git a/infra/bots/recipes/compute_test.expected/Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-OpenCL.json b/infra/bots/recipes/compute_test.expected/Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-OpenCL.json
index a680c54..6b1044c 100644
--- a/infra/bots/recipes/compute_test.expected/Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-OpenCL.json
+++ b/infra/bots/recipes/compute_test.expected/Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-OpenCL.json
@@ -13,7 +13,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdirs OpenCL/vendors"
@@ -31,7 +31,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "write NEO OpenCL ICD"
@@ -46,7 +46,7 @@
       "LD_LIBRARY_PATH": "[START_DIR]/mesa_intel_driver_linux:[START_DIR]/opencl_ocl_icd_linux:[START_DIR]/opencl_intel_neo_linux",
       "LIBGL_DRIVERS_PATH": "[START_DIR]/mesa_intel_driver_linux",
       "OPENCL_VENDOR_PATH": "[START_DIR]/tmp/OpenCL/vendors",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]",
       "VK_ICD_FILENAMES": "[START_DIR]/mesa_intel_driver_linux/intel_icd.x86_64.json"
     },
     "name": "hello-opencl"
diff --git a/infra/bots/recipes/housekeeper.expected/Housekeeper-PerCommit-Trybot.json b/infra/bots/recipes/housekeeper.expected/Housekeeper-PerCommit-Trybot.json
index 2a719fb..0b756f1 100644
--- a/infra/bots/recipes/housekeeper.expected/Housekeeper-PerCommit-Trybot.json
+++ b/infra/bots/recipes/housekeeper.expected/Housekeeper-PerCommit-Trybot.json
@@ -52,7 +52,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
diff --git a/infra/bots/recipes/housekeeper.expected/Housekeeper-PerCommit.json b/infra/bots/recipes/housekeeper.expected/Housekeeper-PerCommit.json
index 13e2ccc..129e20a 100644
--- a/infra/bots/recipes/housekeeper.expected/Housekeeper-PerCommit.json
+++ b/infra/bots/recipes/housekeeper.expected/Housekeeper-PerCommit.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -116,7 +116,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "generate and upload doxygen"
   },
diff --git a/infra/bots/recipes/infra.expected/infra_tests.json b/infra/bots/recipes/infra.expected/infra_tests.json
index 8770e30..39387fc 100644
--- a/infra/bots/recipes/infra.expected/infra_tests.json
+++ b/infra/bots/recipes/infra.expected/infra_tests.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
diff --git a/infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-All-Android.json b/infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-All-Android.json
index fdf3363..4234124 100644
--- a/infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-All-Android.json
+++ b/infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-All-Android.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -55,7 +55,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -117,7 +117,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -135,7 +135,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -152,7 +152,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -169,7 +169,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -184,7 +184,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -218,7 +218,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -262,7 +262,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -280,7 +280,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -297,7 +297,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -314,7 +314,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -329,7 +329,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -363,7 +363,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -407,7 +407,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -425,7 +425,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -442,7 +442,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -459,7 +459,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -474,7 +474,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -508,7 +508,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -538,7 +538,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 0's governor to userspace",
@@ -580,7 +580,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Scale CPU 0 to 0.600000",
@@ -653,7 +653,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Lock GPU to 450000000 (and other perf tweaks)",
@@ -716,7 +716,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push nanobench"
@@ -734,7 +734,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "write nanobench.sh"
@@ -750,7 +750,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push nanobench.sh"
@@ -765,7 +765,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -780,7 +780,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "nanobench",
     "~followup_annotations": [
@@ -808,7 +808,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -841,7 +841,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_NoGPUThreads.json b/infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_NoGPUThreads.json
index 907c57d..bcaf076 100644
--- a/infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_NoGPUThreads.json
+++ b/infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_NoGPUThreads.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -55,7 +55,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -117,7 +117,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -135,7 +135,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -152,7 +152,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -169,7 +169,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -184,7 +184,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -218,7 +218,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -262,7 +262,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -280,7 +280,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -297,7 +297,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -314,7 +314,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -329,7 +329,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -363,7 +363,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -407,7 +407,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -425,7 +425,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -442,7 +442,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -459,7 +459,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -474,7 +474,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -508,7 +508,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -525,7 +525,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/perf"
@@ -542,7 +542,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/perf"
@@ -572,7 +572,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 4's governor to userspace",
@@ -614,7 +614,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Scale CPU 4 to 0.600000",
@@ -688,7 +688,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Disabling CPU 0",
@@ -738,7 +738,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Disabling CPU 1",
@@ -788,7 +788,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Disabling CPU 2",
@@ -838,7 +838,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Disabling CPU 3",
@@ -887,7 +887,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Lock GPU to 600000000 (and other perf tweaks)",
@@ -950,7 +950,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push nanobench"
@@ -968,7 +968,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "write nanobench.sh"
@@ -984,7 +984,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push nanobench.sh"
@@ -999,7 +999,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -1014,7 +1014,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "nanobench",
     "~followup_annotations": [
@@ -1063,7 +1063,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/perf",
@@ -1151,7 +1151,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -1184,7 +1184,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-All-Android.json b/infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-All-Android.json
new file mode 100644
index 0000000..54d039f
--- /dev/null
+++ b/infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-All-Android.json
@@ -0,0 +1,791 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "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": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/resources"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
+  },
+  {
+    "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(['/usr/bin/adb.1.0.35', 'push',\n                           os.path.realpath(os.path.join(host, p, f)),\n                           os.path.join(device, p, f)])\n",
+      "[START_DIR]/skia/resources",
+      "/sdcard/revenge_of_the_skiabot/resources"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
+    "~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(['/usr/bin/adb.1.0.35', '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": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skp VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SKP_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/SKP_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "rm",
+      "-f",
+      "/sdcard/revenge_of_the_skiabot/SKP_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "rm",
+      "-rf",
+      "/sdcard/revenge_of_the_skiabot/skps"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/skps"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/skps"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "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(['/usr/bin/adb.1.0.35', '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"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "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(['/usr/bin/adb.1.0.35', '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": [
+      "/usr/bin/adb.1.0.35",
+      "push",
+      "[START_DIR]/tmp/SKP_VERSION",
+      "/sdcard/revenge_of_the_skiabot/SKP_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "rm",
+      "-f",
+      "/sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "rm",
+      "-rf",
+      "/sdcard/revenge_of_the_skiabot/images"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/images"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/images"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
+  },
+  {
+    "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(['/usr/bin/adb.1.0.35', 'push',\n                           os.path.realpath(os.path.join(host, p, f)),\n                           os.path.join(device, p, f)])\n",
+      "[START_DIR]/skimage",
+      "/sdcard/revenge_of_the_skiabot/images"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
+    "~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(['/usr/bin/adb.1.0.35', '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": [
+      "/usr/bin/adb.1.0.35",
+      "push",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION",
+      "/sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get svg VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SVG_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/SVG_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "rm",
+      "-f",
+      "/sdcard/revenge_of_the_skiabot/SVG_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "rm",
+      "-rf",
+      "/sdcard/revenge_of_the_skiabot/svgs"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/svgs"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
+  },
+  {
+    "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(['/usr/bin/adb.1.0.35', 'push',\n                           os.path.realpath(os.path.join(host, p, f)),\n                           os.path.join(device, p, f)])\n",
+      "[START_DIR]/svg",
+      "/sdcard/revenge_of_the_skiabot/svgs"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
+    "~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(['/usr/bin/adb.1.0.35', '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": [
+      "/usr/bin/adb.1.0.35",
+      "push",
+      "[START_DIR]/tmp/SVG_VERSION",
+      "/sdcard/revenge_of_the_skiabot/SVG_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
+  },
+  {
+    "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 os\nimport subprocess\nimport sys\nimport time\nADB = sys.argv[1]\ncpu = int(sys.argv[2])\ngov = sys.argv[3]\n\nlog = subprocess.check_output([ADB, 'root'])\n# check for message like 'adbd cannot run as root in production builds'\nprint log\nif 'cannot' in log:\n  raise Exception('adb root failed')\n\nsubprocess.check_output([ADB, 'shell', 'echo \"%s\" > '\n    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % (gov, cpu)])\nactual_gov = subprocess.check_output([ADB, 'shell', 'cat '\n    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % cpu]).strip()\nif actual_gov != gov:\n  raise Exception('(actual, expected) (%s, %s)'\n                  % (actual_gov, gov))\n",
+      "/usr/bin/adb.1.0.35",
+      "0",
+      "userspace"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "Set CPU 0's governor to userspace",
+    "timeout": 30,
+    "~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@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@ADB = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@cpu = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@gov = sys.argv[3]@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@log = subprocess.check_output([ADB, 'root'])@@@",
+      "@@@STEP_LOG_LINE@python.inline@# check for message like 'adbd cannot run as root in production builds'@@@",
+      "@@@STEP_LOG_LINE@python.inline@print log@@@",
+      "@@@STEP_LOG_LINE@python.inline@if 'cannot' in log:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('adb root failed')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_output([ADB, 'shell', 'echo \"%s\" > '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % (gov, cpu)])@@@",
+      "@@@STEP_LOG_LINE@python.inline@actual_gov = subprocess.check_output([ADB, 'shell', 'cat '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % cpu]).strip()@@@",
+      "@@@STEP_LOG_LINE@python.inline@if actual_gov != gov:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('(actual, expected) (%s, %s)'@@@",
+      "@@@STEP_LOG_LINE@python.inline@                  % (actual_gov, gov))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nimport time\nADB = sys.argv[1]\ntarget_percent = float(sys.argv[2])\ncpu = int(sys.argv[3])\nlog = subprocess.check_output([ADB, 'root'])\n# check for message like 'adbd cannot run as root in production builds'\nprint log\nif 'cannot' in log:\n  raise Exception('adb root failed')\n\nroot = '/sys/devices/system/cpu/cpu%d/cpufreq' %cpu\n\n# All devices we test on give a list of their available frequencies.\navailable_freqs = subprocess.check_output([ADB, 'shell',\n    'cat %s/scaling_available_frequencies' % root])\n\n# Check for message like '/system/bin/sh: file not found'\nif available_freqs and '/system/bin/sh' not in available_freqs:\n  available_freqs = sorted(\n      int(i) for i in available_freqs.strip().split())\nelse:\n  raise Exception('Could not get list of available frequencies: %s' %\n                  available_freqs)\n\nmaxfreq = available_freqs[-1]\ntarget = int(round(maxfreq * target_percent))\nfreq = maxfreq\nfor f in reversed(available_freqs):\n  if f <= target:\n    freq = f\n    break\n\nprint 'Setting frequency to %d' % freq\n\n# If scaling_max_freq is lower than our attempted setting, it won't take.\n# We must set min first, because if we try to set max to be less than min\n# (which sometimes happens after certain devices reboot) it returns a\n# perplexing permissions error.\nsubprocess.check_output([ADB, 'shell', 'echo 0 > '\n    '%s/scaling_min_freq' % root])\nsubprocess.check_output([ADB, 'shell', 'echo %d > '\n    '%s/scaling_max_freq' % (freq, root)])\nsubprocess.check_output([ADB, 'shell', 'echo %d > '\n    '%s/scaling_setspeed' % (freq, root)])\ntime.sleep(5)\nactual_freq = subprocess.check_output([ADB, 'shell', 'cat '\n    '%s/scaling_cur_freq' % root]).strip()\nif actual_freq != str(freq):\n  raise Exception('(actual, expected) (%s, %d)'\n                  % (actual_freq, freq))\n",
+      "/usr/bin/adb.1.0.35",
+      "0.6",
+      "0"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "Scale CPU 0 to 0.600000",
+    "timeout": 30,
+    "~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@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@ADB = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@target_percent = float(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@cpu = int(sys.argv[3])@@@",
+      "@@@STEP_LOG_LINE@python.inline@log = subprocess.check_output([ADB, 'root'])@@@",
+      "@@@STEP_LOG_LINE@python.inline@# check for message like 'adbd cannot run as root in production builds'@@@",
+      "@@@STEP_LOG_LINE@python.inline@print log@@@",
+      "@@@STEP_LOG_LINE@python.inline@if 'cannot' in log:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('adb root failed')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@root = '/sys/devices/system/cpu/cpu%d/cpufreq' %cpu@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# All devices we test on give a list of their available frequencies.@@@",
+      "@@@STEP_LOG_LINE@python.inline@available_freqs = subprocess.check_output([ADB, 'shell',@@@",
+      "@@@STEP_LOG_LINE@python.inline@    'cat %s/scaling_available_frequencies' % root])@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# Check for message like '/system/bin/sh: file not found'@@@",
+      "@@@STEP_LOG_LINE@python.inline@if available_freqs and '/system/bin/sh' not in available_freqs:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  available_freqs = sorted(@@@",
+      "@@@STEP_LOG_LINE@python.inline@      int(i) for i in available_freqs.strip().split())@@@",
+      "@@@STEP_LOG_LINE@python.inline@else:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('Could not get list of available frequencies: %s' %@@@",
+      "@@@STEP_LOG_LINE@python.inline@                  available_freqs)@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@maxfreq = available_freqs[-1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@target = int(round(maxfreq * target_percent))@@@",
+      "@@@STEP_LOG_LINE@python.inline@freq = maxfreq@@@",
+      "@@@STEP_LOG_LINE@python.inline@for f in reversed(available_freqs):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if f <= target:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    freq = f@@@",
+      "@@@STEP_LOG_LINE@python.inline@    break@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@print 'Setting frequency to %d' % freq@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# If scaling_max_freq is lower than our attempted setting, it won't take.@@@",
+      "@@@STEP_LOG_LINE@python.inline@# We must set min first, because if we try to set max to be less than min@@@",
+      "@@@STEP_LOG_LINE@python.inline@# (which sometimes happens after certain devices reboot) it returns a@@@",
+      "@@@STEP_LOG_LINE@python.inline@# perplexing permissions error.@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_output([ADB, 'shell', 'echo 0 > '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '%s/scaling_min_freq' % root])@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_output([ADB, 'shell', 'echo %d > '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '%s/scaling_max_freq' % (freq, root)])@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_output([ADB, 'shell', 'echo %d > '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '%s/scaling_setspeed' % (freq, root)])@@@",
+      "@@@STEP_LOG_LINE@python.inline@time.sleep(5)@@@",
+      "@@@STEP_LOG_LINE@python.inline@actual_freq = subprocess.check_output([ADB, 'shell', 'cat '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '%s/scaling_cur_freq' % root]).strip()@@@",
+      "@@@STEP_LOG_LINE@python.inline@if actual_freq != str(freq):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('(actual, expected) (%s, %d)'@@@",
+      "@@@STEP_LOG_LINE@python.inline@                  % (actual_freq, freq))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "push",
+      "[START_DIR]/build/nanobench",
+      "/data/local/tmp/"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push nanobench"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "set -x; /data/local/tmp/nanobench -i /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/nanobench --svgs /sdcard/revenge_of_the_skiabot/svgs --pre_log --scales 1.0 1.1 --nogpu --purgeBetweenBenches --config 8888 nonrendering --match ~blurroundrect ~patch_grid ~desk_carsvg ~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; echo $? >/data/local/tmp/rc",
+      "[START_DIR]/tmp/nanobench.sh"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "write nanobench.sh"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "push",
+      "[START_DIR]/tmp/nanobench.sh",
+      "/data/local/tmp/"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push nanobench.sh"
+  },
+  {
+    "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "logcat",
+      "-c"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "clear log"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport subprocess\nimport sys\nbin_dir = sys.argv[1]\nsh      = sys.argv[2]\nsubprocess.check_call(['/usr/bin/adb.1.0.35', 'shell', 'sh', bin_dir + sh])\ntry:\n  sys.exit(int(subprocess.check_output(['/usr/bin/adb.1.0.35', 'shell', 'cat',\n                                        bin_dir + 'rc'])))\nexcept ValueError:\n  print \"Couldn't read the return code.  Probably killed for OOM.\"\n  sys.exit(1)\n",
+      "/data/local/tmp/",
+      "nanobench.sh"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "nanobench",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@bin_dir = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@sh      = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_call(['/usr/bin/adb.1.0.35', 'shell', 'sh', bin_dir + sh])@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(int(subprocess.check_output(['/usr/bin/adb.1.0.35', 'shell', 'cat',@@@",
+      "@@@STEP_LOG_LINE@python.inline@                                        bin_dir + 'rc'])))@@@",
+      "@@@STEP_LOG_LINE@python.inline@except ValueError:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  print \"Couldn't read the return code.  Probably killed for OOM.\"@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(1)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nout = sys.argv[1]\nlog = subprocess.check_output(['/usr/bin/adb.1.0.35', 'logcat', '-d'])\nfor line in log.split('\\n'):\n  tokens = line.split()\n  if len(tokens) == 11 and tokens[-7] == 'F' and tokens[-3] == 'pc':\n    addr, path = tokens[-2:]\n    local = os.path.join(out, os.path.basename(path))\n    if os.path.exists(local):\n      sym = subprocess.check_output(['addr2line', '-Cfpe', local, addr])\n      line = line.replace(addr, addr + ' ' + sym.strip())\n  print line\n",
+      "[START_DIR]/build"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "dump log",
+    "timeout": 300,
+    "~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(['/usr/bin/adb.1.0.35', '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": [
+      "/usr/bin/adb.1.0.35",
+      "kill-server"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "kill adb server"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/perf.expected/Perf-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Release-All.json b/infra/bots/recipes/perf.expected/Perf-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Release-All.json
index 7796a8f..b8e57bb 100644
--- a/infra/bots/recipes/perf.expected/Perf-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Release-All.json
+++ b/infra/bots/recipes/perf.expected/Perf-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Release-All.json
@@ -22,7 +22,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read chromeos ip",
@@ -50,7 +50,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/resources"
@@ -72,7 +72,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "remount /home/chronos/user/ as exec"
@@ -91,7 +91,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/bin"
@@ -110,7 +110,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/bin"
@@ -176,7 +176,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /home/chronos/user/SKP_VERSION",
@@ -196,7 +196,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/SKP_VERSION"
@@ -215,7 +215,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/skps"
@@ -234,7 +234,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/skps"
@@ -320,7 +320,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /home/chronos/user/SK_IMAGE_VERSION",
@@ -340,7 +340,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/SK_IMAGE_VERSION"
@@ -359,7 +359,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/images"
@@ -378,7 +378,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/images"
@@ -464,7 +464,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /home/chronos/user/SVG_VERSION",
@@ -484,7 +484,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/SVG_VERSION"
@@ -503,7 +503,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/svgs"
@@ -522,7 +522,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/svgs"
@@ -581,7 +581,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/perf"
@@ -600,7 +600,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/perf"
@@ -645,7 +645,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rmtree [SWARM_OUT_DIR]"
@@ -664,7 +664,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "makedirs [SWARM_OUT_DIR]"
@@ -679,7 +679,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "scp [START_DIR]/build/nanobench foo@127.0.0.1:/home/chronos/user/bin/nanobench",
@@ -707,7 +707,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "chmod nanobench"
@@ -784,7 +784,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "nanobench"
diff --git a/infra/bots/recipes/perf.expected/Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Debug-All.json b/infra/bots/recipes/perf.expected/Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Debug-All.json
index 6e4009d..28c68c8 100644
--- a/infra/bots/recipes/perf.expected/Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Debug-All.json
+++ b/infra/bots/recipes/perf.expected/Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Debug-All.json
@@ -36,7 +36,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read chromecast ip",
@@ -58,7 +58,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb connect 192.168.1.2:5555"
@@ -74,7 +74,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /cache/skia/resources"
@@ -90,7 +90,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /cache/skia/bin"
@@ -105,7 +105,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /cache/skia/resources",
@@ -169,7 +169,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /cache/skia/SKP_VERSION",
@@ -186,7 +186,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /cache/skia/SKP_VERSION"
@@ -202,7 +202,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /cache/skia/skps"
@@ -218,7 +218,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /cache/skia/skps"
@@ -233,7 +233,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /cache/skia/skps",
@@ -269,7 +269,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /cache/skia/SKP_VERSION"
@@ -298,7 +298,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push nanobench"
@@ -334,7 +334,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "nanobench"
   },
@@ -351,7 +351,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Delete executables"
@@ -364,7 +364,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "disconnect"
@@ -377,7 +377,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb connect 192.168.1.2:5555 (2)"
@@ -391,7 +391,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -422,7 +422,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "disconnect (2)"
@@ -435,7 +435,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/perf.expected/Perf-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All.json b/infra/bots/recipes/perf.expected/Perf-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All.json
index 9b8f44b..4f8b0da 100644
--- a/infra/bots/recipes/perf.expected/Perf-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All.json
+++ b/infra/bots/recipes/perf.expected/Perf-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All.json
@@ -36,7 +36,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read chromecast ip",
@@ -58,7 +58,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb connect 192.168.1.2:5555"
@@ -74,7 +74,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /cache/skia/resources"
@@ -90,7 +90,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /cache/skia/bin"
@@ -105,7 +105,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /cache/skia/resources",
@@ -169,7 +169,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /cache/skia/SKP_VERSION",
@@ -186,7 +186,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /cache/skia/SKP_VERSION"
@@ -202,7 +202,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /cache/skia/skps"
@@ -218,7 +218,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /cache/skia/skps"
@@ -233,7 +233,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /cache/skia/skps",
@@ -269,7 +269,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /cache/skia/SKP_VERSION"
@@ -285,7 +285,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /cache/skia/perf"
@@ -301,7 +301,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /cache/skia/perf"
@@ -330,7 +330,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push nanobench"
@@ -388,7 +388,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "nanobench"
   },
@@ -421,7 +421,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /cache/skia/perf",
@@ -513,7 +513,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Delete executables"
@@ -526,7 +526,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "disconnect"
@@ -539,7 +539,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb connect 192.168.1.2:5555 (2)"
@@ -553,7 +553,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -584,7 +584,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "disconnect (2)"
@@ -597,7 +597,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN.json b/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN.json
index 4c6af6f..7f131a9 100644
--- a/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN.json
+++ b/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN.json
@@ -182,7 +182,7 @@
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/clang_linux/lib",
       "LSAN_OPTIONS": "symbolize=1 print_suppressions=1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]:[START_DIR]/clang_linux/bin",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]:[START_DIR]/clang_linux/bin",
       "UBSAN_OPTIONS": "symbolize=1 print_stacktrace=1"
     },
     "name": "symbolized nanobench"
diff --git a/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All.json b/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All.json
index 2621714..771d92b 100644
--- a/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All.json
+++ b/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All.json
@@ -174,7 +174,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "symbolized nanobench"
   },
diff --git a/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-BonusConfigs.json b/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-BonusConfigs.json
index 320af2a..aa7d7b8 100644
--- a/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-BonusConfigs.json
+++ b/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-BonusConfigs.json
@@ -231,7 +231,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "symbolized nanobench"
   },
diff --git a/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All-Vulkan.json b/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All-Vulkan.json
index 8982756..3ae8b80 100644
--- a/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All-Vulkan.json
+++ b/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All-Vulkan.json
@@ -203,7 +203,7 @@
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/mesa_intel_driver_linux:[START_DIR]/linux_vulkan_sdk/lib",
       "LIBGL_DRIVERS_PATH": "[START_DIR]/mesa_intel_driver_linux",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]:[START_DIR]/linux_vulkan_sdk/bin",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]:[START_DIR]/linux_vulkan_sdk/bin",
       "VK_ICD_FILENAMES": "[START_DIR]/mesa_intel_driver_linux/intel_icd.x86_64.json"
     },
     "name": "symbolized nanobench"
diff --git a/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All.json b/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All.json
index f6ebd79..b72cf5f 100644
--- a/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All.json
+++ b/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All.json
@@ -233,7 +233,7 @@
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/mesa_intel_driver_linux",
       "LIBGL_DRIVERS_PATH": "[START_DIR]/mesa_intel_driver_linux",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]",
       "VK_ICD_FILENAMES": "[START_DIR]/mesa_intel_driver_linux/intel_icd.x86_64.json"
     },
     "name": "symbolized nanobench"
diff --git a/infra/bots/recipes/perf.expected/Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer.json b/infra/bots/recipes/perf.expected/Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer.json
deleted file mode 100644
index 3964129..0000000
--- a/infra/bots/recipes/perf.expected/Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer.json
+++ /dev/null
@@ -1,249 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/tmp"
-    ],
-    "infra_step": true,
-    "name": "makedirs tmp_dir"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skp VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SKP_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SKP_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skimage VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SK_IMAGE_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SK_IMAGE_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get svg VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SVG_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SVG_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "rmtree",
-      "[START_DIR]/[SWARM_OUT_DIR]"
-    ],
-    "infra_step": true,
-    "name": "rmtree [SWARM_OUT_DIR]"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/[SWARM_OUT_DIR]"
-    ],
-    "infra_step": true,
-    "name": "makedirs [SWARM_OUT_DIR]"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
-    ],
-    "name": "get swarming bot id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
-    ],
-    "name": "get swarming task id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/build/nanobench",
-      "-i",
-      "[START_DIR]/skia/resources",
-      "--skps",
-      "[START_DIR]/skp",
-      "--images",
-      "[START_DIR]/skimage/nanobench",
-      "--svgs",
-      "[START_DIR]/svg",
-      "--pre_log",
-      "--images",
-      "--gpuStatsDump",
-      "true",
-      "--scales",
-      "1.0",
-      "1.1",
-      "--nocpu",
-      "--config",
-      "commandbuffer",
-      "--match",
-      "~^desk_micrographygirlsvg.skp_1.1$",
-      "~inc0.gif",
-      "~inc1.gif",
-      "~incInterlaced.gif",
-      "~inc0.jpg",
-      "~incGray.jpg",
-      "~inc0.wbmp",
-      "~inc1.wbmp",
-      "~inc0.webp",
-      "~inc1.webp",
-      "~inc0.ico",
-      "~inc1.ico",
-      "~inc0.png",
-      "~inc1.png",
-      "~inc2.png",
-      "~inc12.png",
-      "~inc13.png",
-      "~inc14.png",
-      "~inc0.webp",
-      "~inc1.webp",
-      "--outResultsFile",
-      "[START_DIR]/[SWARM_OUT_DIR]/nanobench_abc123_1337000001.json",
-      "--properties",
-      "gitHash",
-      "abc123",
-      "swarming_bot_id",
-      "skia-bot-123",
-      "swarming_task_id",
-      "123456",
-      "--key",
-      "arch",
-      "x86_64",
-      "compiler",
-      "Clang",
-      "cpu_or_gpu",
-      "GPU",
-      "cpu_or_gpu_value",
-      "IntelHD615",
-      "extra_config",
-      "CommandBuffer",
-      "model",
-      "MacBook10.1",
-      "os",
-      "Mac"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "nanobench"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/[SWARM_OUT_DIR]"
-    ],
-    "infra_step": true,
-    "name": "makedirs perf_dir"
-  },
-  {
-    "jsonResult": null,
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipes/perf.expected/Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan.json b/infra/bots/recipes/perf.expected/Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan.json
deleted file mode 100644
index 4f9186a..0000000
--- a/infra/bots/recipes/perf.expected/Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan.json
+++ /dev/null
@@ -1,250 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/tmp"
-    ],
-    "infra_step": true,
-    "name": "makedirs tmp_dir"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skp VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SKP_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SKP_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skimage VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SK_IMAGE_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SK_IMAGE_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get svg VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SVG_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SVG_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "rmtree",
-      "[START_DIR]/[SWARM_OUT_DIR]"
-    ],
-    "infra_step": true,
-    "name": "rmtree [SWARM_OUT_DIR]"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/[SWARM_OUT_DIR]"
-    ],
-    "infra_step": true,
-    "name": "makedirs [SWARM_OUT_DIR]"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
-    ],
-    "name": "get swarming bot id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
-    ],
-    "name": "get swarming task id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/build/nanobench",
-      "-i",
-      "[START_DIR]/skia/resources",
-      "--skps",
-      "[START_DIR]/skp",
-      "--images",
-      "[START_DIR]/skimage/nanobench",
-      "--svgs",
-      "[START_DIR]/svg",
-      "--pre_log",
-      "--images",
-      "--gpuStatsDump",
-      "true",
-      "--scales",
-      "1.0",
-      "1.1",
-      "--nocpu",
-      "--config",
-      "vk",
-      "--match",
-      "~^path_text_clipped_uncached$",
-      "~^path_text_uncached$",
-      "~inc0.gif",
-      "~inc1.gif",
-      "~incInterlaced.gif",
-      "~inc0.jpg",
-      "~incGray.jpg",
-      "~inc0.wbmp",
-      "~inc1.wbmp",
-      "~inc0.webp",
-      "~inc1.webp",
-      "~inc0.ico",
-      "~inc1.ico",
-      "~inc0.png",
-      "~inc1.png",
-      "~inc2.png",
-      "~inc12.png",
-      "~inc13.png",
-      "~inc14.png",
-      "~inc0.webp",
-      "~inc1.webp",
-      "--outResultsFile",
-      "[START_DIR]/[SWARM_OUT_DIR]/nanobench_abc123_1337000001.json",
-      "--properties",
-      "gitHash",
-      "abc123",
-      "swarming_bot_id",
-      "skia-bot-123",
-      "swarming_task_id",
-      "123456",
-      "--key",
-      "arch",
-      "x86_64",
-      "compiler",
-      "Clang",
-      "cpu_or_gpu",
-      "GPU",
-      "cpu_or_gpu_value",
-      "RadeonHD8870M",
-      "extra_config",
-      "MoltenVK_Vulkan",
-      "model",
-      "MacBookPro11.5",
-      "os",
-      "Mac"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "nanobench"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/[SWARM_OUT_DIR]"
-    ],
-    "infra_step": true,
-    "name": "makedirs perf_dir"
-  },
-  {
-    "jsonResult": null,
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipes/perf.expected/Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer.json b/infra/bots/recipes/perf.expected/Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer.json
deleted file mode 100644
index a0330a4..0000000
--- a/infra/bots/recipes/perf.expected/Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer.json
+++ /dev/null
@@ -1,248 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/tmp"
-    ],
-    "infra_step": true,
-    "name": "makedirs tmp_dir"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skp VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SKP_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SKP_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skimage VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SK_IMAGE_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SK_IMAGE_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get svg VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SVG_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SVG_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "rmtree",
-      "[START_DIR]/[SWARM_OUT_DIR]"
-    ],
-    "infra_step": true,
-    "name": "rmtree [SWARM_OUT_DIR]"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/[SWARM_OUT_DIR]"
-    ],
-    "infra_step": true,
-    "name": "makedirs [SWARM_OUT_DIR]"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
-    ],
-    "name": "get swarming bot id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
-    ],
-    "name": "get swarming task id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/build/nanobench",
-      "-i",
-      "[START_DIR]/skia/resources",
-      "--skps",
-      "[START_DIR]/skp",
-      "--images",
-      "[START_DIR]/skimage/nanobench",
-      "--svgs",
-      "[START_DIR]/svg",
-      "--pre_log",
-      "--images",
-      "--gpuStatsDump",
-      "true",
-      "--scales",
-      "1.0",
-      "1.1",
-      "--nocpu",
-      "--config",
-      "commandbuffer",
-      "--match",
-      "~inc0.gif",
-      "~inc1.gif",
-      "~incInterlaced.gif",
-      "~inc0.jpg",
-      "~incGray.jpg",
-      "~inc0.wbmp",
-      "~inc1.wbmp",
-      "~inc0.webp",
-      "~inc1.webp",
-      "~inc0.ico",
-      "~inc1.ico",
-      "~inc0.png",
-      "~inc1.png",
-      "~inc2.png",
-      "~inc12.png",
-      "~inc13.png",
-      "~inc14.png",
-      "~inc0.webp",
-      "~inc1.webp",
-      "--outResultsFile",
-      "[START_DIR]/[SWARM_OUT_DIR]/nanobench_abc123_1337000001.json",
-      "--properties",
-      "gitHash",
-      "abc123",
-      "swarming_bot_id",
-      "skia-bot-123",
-      "swarming_task_id",
-      "123456",
-      "--key",
-      "arch",
-      "x86_64",
-      "compiler",
-      "Clang",
-      "cpu_or_gpu",
-      "GPU",
-      "cpu_or_gpu_value",
-      "IntelIris5100",
-      "extra_config",
-      "CommandBuffer",
-      "model",
-      "MacMini7.1",
-      "os",
-      "Mac"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "nanobench"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/[SWARM_OUT_DIR]"
-    ],
-    "infra_step": true,
-    "name": "makedirs perf_dir"
-  },
-  {
-    "jsonResult": null,
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipes/perf.expected/Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer.json b/infra/bots/recipes/perf.expected/Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer.json
new file mode 100644
index 0000000..8156b72
--- /dev/null
+++ b/infra/bots/recipes/perf.expected/Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer.json
@@ -0,0 +1,249 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skp VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SKP_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get svg VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SVG_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "rmtree [SWARM_OUT_DIR]"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "makedirs [SWARM_OUT_DIR]"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
+    ],
+    "name": "get swarming task id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/build/nanobench",
+      "-i",
+      "[START_DIR]/skia/resources",
+      "--skps",
+      "[START_DIR]/skp",
+      "--images",
+      "[START_DIR]/skimage/nanobench",
+      "--svgs",
+      "[START_DIR]/svg",
+      "--pre_log",
+      "--images",
+      "--gpuStatsDump",
+      "true",
+      "--scales",
+      "1.0",
+      "1.1",
+      "--nocpu",
+      "--config",
+      "commandbuffer",
+      "--match",
+      "~^desk_micrographygirlsvg.skp_1.1$",
+      "~inc0.gif",
+      "~inc1.gif",
+      "~incInterlaced.gif",
+      "~inc0.jpg",
+      "~incGray.jpg",
+      "~inc0.wbmp",
+      "~inc1.wbmp",
+      "~inc0.webp",
+      "~inc1.webp",
+      "~inc0.ico",
+      "~inc1.ico",
+      "~inc0.png",
+      "~inc1.png",
+      "~inc2.png",
+      "~inc12.png",
+      "~inc13.png",
+      "~inc14.png",
+      "~inc0.webp",
+      "~inc1.webp",
+      "--outResultsFile",
+      "[START_DIR]/[SWARM_OUT_DIR]/nanobench_abc123_1337000001.json",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "swarming_bot_id",
+      "skia-bot-123",
+      "swarming_task_id",
+      "123456",
+      "--key",
+      "arch",
+      "x86_64",
+      "compiler",
+      "Clang",
+      "cpu_or_gpu",
+      "GPU",
+      "cpu_or_gpu_value",
+      "IntelHD615",
+      "extra_config",
+      "CommandBuffer",
+      "model",
+      "MacBook10.1",
+      "os",
+      "Mac10.13"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "nanobench"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "makedirs perf_dir"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/perf.expected/Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan.json b/infra/bots/recipes/perf.expected/Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan.json
new file mode 100644
index 0000000..0ee1fa8
--- /dev/null
+++ b/infra/bots/recipes/perf.expected/Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan.json
@@ -0,0 +1,250 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skp VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SKP_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get svg VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SVG_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "rmtree [SWARM_OUT_DIR]"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "makedirs [SWARM_OUT_DIR]"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
+    ],
+    "name": "get swarming task id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/build/nanobench",
+      "-i",
+      "[START_DIR]/skia/resources",
+      "--skps",
+      "[START_DIR]/skp",
+      "--images",
+      "[START_DIR]/skimage/nanobench",
+      "--svgs",
+      "[START_DIR]/svg",
+      "--pre_log",
+      "--images",
+      "--gpuStatsDump",
+      "true",
+      "--scales",
+      "1.0",
+      "1.1",
+      "--nocpu",
+      "--config",
+      "vk",
+      "--match",
+      "~^path_text_clipped_uncached$",
+      "~^path_text_uncached$",
+      "~inc0.gif",
+      "~inc1.gif",
+      "~incInterlaced.gif",
+      "~inc0.jpg",
+      "~incGray.jpg",
+      "~inc0.wbmp",
+      "~inc1.wbmp",
+      "~inc0.webp",
+      "~inc1.webp",
+      "~inc0.ico",
+      "~inc1.ico",
+      "~inc0.png",
+      "~inc1.png",
+      "~inc2.png",
+      "~inc12.png",
+      "~inc13.png",
+      "~inc14.png",
+      "~inc0.webp",
+      "~inc1.webp",
+      "--outResultsFile",
+      "[START_DIR]/[SWARM_OUT_DIR]/nanobench_abc123_1337000001.json",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "swarming_bot_id",
+      "skia-bot-123",
+      "swarming_task_id",
+      "123456",
+      "--key",
+      "arch",
+      "x86_64",
+      "compiler",
+      "Clang",
+      "cpu_or_gpu",
+      "GPU",
+      "cpu_or_gpu_value",
+      "RadeonHD8870M",
+      "extra_config",
+      "MoltenVK_Vulkan",
+      "model",
+      "MacBookPro11.5",
+      "os",
+      "Mac10.13"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "nanobench"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "makedirs perf_dir"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/perf.expected/Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer.json b/infra/bots/recipes/perf.expected/Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer.json
new file mode 100644
index 0000000..561ae8e
--- /dev/null
+++ b/infra/bots/recipes/perf.expected/Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer.json
@@ -0,0 +1,248 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skp VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SKP_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get svg VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SVG_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "rmtree [SWARM_OUT_DIR]"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "makedirs [SWARM_OUT_DIR]"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
+    ],
+    "name": "get swarming task id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/build/nanobench",
+      "-i",
+      "[START_DIR]/skia/resources",
+      "--skps",
+      "[START_DIR]/skp",
+      "--images",
+      "[START_DIR]/skimage/nanobench",
+      "--svgs",
+      "[START_DIR]/svg",
+      "--pre_log",
+      "--images",
+      "--gpuStatsDump",
+      "true",
+      "--scales",
+      "1.0",
+      "1.1",
+      "--nocpu",
+      "--config",
+      "commandbuffer",
+      "--match",
+      "~inc0.gif",
+      "~inc1.gif",
+      "~incInterlaced.gif",
+      "~inc0.jpg",
+      "~incGray.jpg",
+      "~inc0.wbmp",
+      "~inc1.wbmp",
+      "~inc0.webp",
+      "~inc1.webp",
+      "~inc0.ico",
+      "~inc1.ico",
+      "~inc0.png",
+      "~inc1.png",
+      "~inc2.png",
+      "~inc12.png",
+      "~inc13.png",
+      "~inc14.png",
+      "~inc0.webp",
+      "~inc1.webp",
+      "--outResultsFile",
+      "[START_DIR]/[SWARM_OUT_DIR]/nanobench_abc123_1337000001.json",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "swarming_bot_id",
+      "skia-bot-123",
+      "swarming_task_id",
+      "123456",
+      "--key",
+      "arch",
+      "x86_64",
+      "compiler",
+      "Clang",
+      "cpu_or_gpu",
+      "GPU",
+      "cpu_or_gpu_value",
+      "IntelIris5100",
+      "extra_config",
+      "CommandBuffer",
+      "model",
+      "MacMini7.1",
+      "os",
+      "Mac10.13"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "nanobench"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "makedirs perf_dir"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/perf.expected/Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41.json b/infra/bots/recipes/perf.expected/Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41.json
deleted file mode 100644
index 06dd2b6..0000000
--- a/infra/bots/recipes/perf.expected/Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41.json
+++ /dev/null
@@ -1,198 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/tmp"
-    ],
-    "infra_step": true,
-    "name": "makedirs tmp_dir"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skp VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SKP_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SKP_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skimage VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SK_IMAGE_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SK_IMAGE_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get svg VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SVG_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SVG_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
-    ],
-    "name": "get swarming bot id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
-    ],
-    "name": "get swarming task id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/valgrind/bin/valgrind",
-      "--gen-suppressions=all",
-      "--leak-check=full",
-      "--track-origins=yes",
-      "--error-exitcode=1",
-      "--num-callers=40",
-      "--suppressions=[START_DIR]/skia/tools/valgrind.supp",
-      "[START_DIR]/build/nanobench",
-      "-i",
-      "[START_DIR]/skia/resources",
-      "--skps",
-      "[START_DIR]/skp",
-      "--images",
-      "[START_DIR]/skimage/nanobench",
-      "--pre_log",
-      "--images",
-      "--gpuStatsDump",
-      "true",
-      "--scales",
-      "1.0",
-      "1.1",
-      "--nocpu",
-      "--config",
-      "gl",
-      "glsrgb",
-      "glmsaa8",
-      "glnvpr8",
-      "--loops",
-      "1",
-      "--samples",
-      "1",
-      "--keepAlive",
-      "true",
-      "--reduceOpListSplitting",
-      "--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",
-      "--abandonGpuContext"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
-      "VALGRIND_LIB": "[START_DIR]/valgrind/lib/valgrind"
-    },
-    "name": "nanobench"
-  },
-  {
-    "jsonResult": null,
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipes/perf.expected/Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41.json b/infra/bots/recipes/perf.expected/Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41.json
new file mode 100644
index 0000000..424f981
--- /dev/null
+++ b/infra/bots/recipes/perf.expected/Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41.json
@@ -0,0 +1,197 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skp VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SKP_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get svg VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SVG_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
+    ],
+    "name": "get swarming task id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/valgrind/bin/valgrind",
+      "--gen-suppressions=all",
+      "--leak-check=full",
+      "--track-origins=yes",
+      "--error-exitcode=1",
+      "--num-callers=40",
+      "--suppressions=[START_DIR]/skia/tools/valgrind.supp",
+      "[START_DIR]/build/nanobench",
+      "-i",
+      "[START_DIR]/skia/resources",
+      "--skps",
+      "[START_DIR]/skp",
+      "--images",
+      "[START_DIR]/skimage/nanobench",
+      "--pre_log",
+      "--images",
+      "--gpuStatsDump",
+      "true",
+      "--scales",
+      "1.0",
+      "1.1",
+      "--nocpu",
+      "--config",
+      "gl",
+      "glsrgb",
+      "glmsaa8",
+      "glnvpr8",
+      "--loops",
+      "1",
+      "--samples",
+      "1",
+      "--keepAlive",
+      "true",
+      "--reduceOpListSplitting",
+      "--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"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]",
+      "VALGRIND_LIB": "[START_DIR]/valgrind/lib/valgrind"
+    },
+    "name": "nanobench"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/perf.expected/Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ANGLE.json b/infra/bots/recipes/perf.expected/Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ANGLE.json
index 41b1271..ce277c3 100644
--- a/infra/bots/recipes/perf.expected/Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ANGLE.json
+++ b/infra/bots/recipes/perf.expected/Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ANGLE.json
@@ -226,7 +226,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "name": "nanobench"
   },
diff --git a/infra/bots/recipes/perf.expected/Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan.json b/infra/bots/recipes/perf.expected/Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan.json
index 59da027..41886bb 100644
--- a/infra/bots/recipes/perf.expected/Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan.json
+++ b/infra/bots/recipes/perf.expected/Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan.json
@@ -251,7 +251,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "name": "nanobench"
   },
diff --git a/infra/bots/recipes/perf.expected/Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All.json b/infra/bots/recipes/perf.expected/Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All.json
new file mode 100644
index 0000000..05b4953
--- /dev/null
+++ b/infra/bots/recipes/perf.expected/Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All.json
@@ -0,0 +1,256 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]\\tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]\\skia\\infra\\bots\\assets\\skp\\VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skp VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]\\tmp\\SKP_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]\\skia\\infra\\bots\\assets\\skimage\\VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]\\tmp\\SK_IMAGE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]\\skia\\infra\\bots\\assets\\svg\\VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get svg VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]\\tmp\\SVG_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[START_DIR]\\[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "rmtree [SWARM_OUT_DIR]"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]\\[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "makedirs [SWARM_OUT_DIR]"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
+    ],
+    "name": "get swarming task id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]\\build\\nanobench",
+      "-i",
+      "[START_DIR]\\skia\\resources",
+      "--skps",
+      "[START_DIR]\\skp",
+      "--images",
+      "[START_DIR]\\skimage\\nanobench",
+      "--svgs",
+      "[START_DIR]\\svg",
+      "--pre_log",
+      "--images",
+      "--gpuStatsDump",
+      "true",
+      "--scales",
+      "1.0",
+      "1.1",
+      "--nocpu",
+      "--config",
+      "gl",
+      "glsrgb",
+      "--reduceOpListSplitting",
+      "--match",
+      "~^top25desk_techcrunch.skp_1_mpd$",
+      "~^top25desk_techcrunch.skp_1$",
+      "~^top25desk_techcrunch.skp_1.1_mpd$",
+      "~^top25desk_techcrunch.skp_1.1$",
+      "~^mobi_wsj.skp_1_mpd$",
+      "~^mobi_wsj.skp_1$",
+      "~^mobi_wsj.skp_1.1_mpd$",
+      "~^mobi_wsj.skp_1.1$",
+      "~inc0.gif",
+      "~inc1.gif",
+      "~incInterlaced.gif",
+      "~inc0.jpg",
+      "~incGray.jpg",
+      "~inc0.wbmp",
+      "~inc1.wbmp",
+      "~inc0.webp",
+      "~inc1.webp",
+      "~inc0.ico",
+      "~inc1.ico",
+      "~inc0.png",
+      "~inc1.png",
+      "~inc2.png",
+      "~inc12.png",
+      "~inc13.png",
+      "~inc14.png",
+      "~inc0.webp",
+      "~inc1.webp",
+      "--outResultsFile",
+      "[START_DIR]\\[SWARM_OUT_DIR]\\nanobench_abc123_1337000001.json",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "swarming_bot_id",
+      "skia-bot-123",
+      "swarming_task_id",
+      "123456",
+      "--key",
+      "arch",
+      "x86_64",
+      "compiler",
+      "Clang",
+      "cpu_or_gpu",
+      "GPU",
+      "cpu_or_gpu_value",
+      "IntelIris655",
+      "model",
+      "NUC8i5BEK",
+      "os",
+      "Win10"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
+    },
+    "name": "nanobench"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]\\[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "makedirs perf_dir"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/perf.expected/Perf-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All.json b/infra/bots/recipes/perf.expected/Perf-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All.json
index 2615cce..d3693c5 100644
--- a/infra/bots/recipes/perf.expected/Perf-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All.json
+++ b/infra/bots/recipes/perf.expected/Perf-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All.json
@@ -22,7 +22,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "setup_device"
@@ -37,7 +37,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "install_dm"
@@ -52,7 +52,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "install_nanobench"
@@ -67,7 +67,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_if_needed [START_DIR]/skia/resources"
@@ -117,7 +117,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "cat_file tmp/SKP_VERSION",
@@ -132,7 +132,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm tmp/SKP_VERSION"
@@ -146,7 +146,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm skps"
@@ -160,7 +160,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir skps"
@@ -175,7 +175,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_if_needed [START_DIR]/skp"
@@ -190,7 +190,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_file [START_DIR]/tmp/SKP_VERSION"
@@ -240,7 +240,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "cat_file tmp/SK_IMAGE_VERSION",
@@ -255,7 +255,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm tmp/SK_IMAGE_VERSION"
@@ -269,7 +269,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm images"
@@ -283,7 +283,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir images"
@@ -298,7 +298,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_if_needed [START_DIR]/skimage"
@@ -313,7 +313,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_file [START_DIR]/tmp/SK_IMAGE_VERSION"
@@ -363,7 +363,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "cat_file tmp/SVG_VERSION",
@@ -378,7 +378,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm tmp/SVG_VERSION"
@@ -392,7 +392,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm svgs"
@@ -406,7 +406,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir svgs"
@@ -421,7 +421,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_if_needed [START_DIR]/svg"
@@ -436,7 +436,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_file [START_DIR]/tmp/SVG_VERSION"
@@ -450,7 +450,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm perf"
@@ -464,7 +464,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir perf"
@@ -585,7 +585,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "nanobench"
   },
@@ -618,7 +618,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.nanobench",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "pull_if_needed perf"
diff --git a/infra/bots/recipes/perf.expected/trybot.json b/infra/bots/recipes/perf.expected/trybot.json
index 1a7310e..be5edcb 100644
--- a/infra/bots/recipes/perf.expected/trybot.json
+++ b/infra/bots/recipes/perf.expected/trybot.json
@@ -228,7 +228,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "nanobench"
   },
diff --git a/infra/bots/recipes/perf.py b/infra/bots/recipes/perf.py
index be7b5f0..040920f 100644
--- a/infra/bots/recipes/perf.py
+++ b/infra/bots/recipes/perf.py
@@ -69,6 +69,9 @@
           'enarrow',
       ]
 
+    if 'Nexus7' in bot:
+      args.append('--purgeBetweenBenches')  # Debugging skia:8929
+
   elif api.vars.builder_cfg.get('cpu_or_gpu') == 'GPU':
     args.append('--nocpu')
 
@@ -243,6 +246,18 @@
   if ('ASAN' in bot or 'UBSAN' in bot) and 'CPU' in bot:
     # floor2int_undef benches undefined behavior, so ASAN correctly complains.
     match.append('~^floor2int_undef$')
+  if (('Iris655' in bot or 'Iris540' in bot) and 'Release' in bot and
+      'Win10' in bot and 'Vulkan' not in bot and 'ANGLE' not in bot):
+    # skia:8706
+    match.append('~^top25desk_techcrunch.skp_1_mpd$')
+    match.append('~^top25desk_techcrunch.skp_1$')
+    match.append('~^top25desk_techcrunch.skp_1.1_mpd$')
+    match.append('~^top25desk_techcrunch.skp_1.1$')
+    # skia:skia:8706
+    match.append('~^mobi_wsj.skp_1_mpd$')
+    match.append('~^mobi_wsj.skp_1$')
+    match.append('~^mobi_wsj.skp_1.1_mpd$')
+    match.append('~^mobi_wsj.skp_1.1$')
 
   # 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.
@@ -352,10 +367,6 @@
       if not k in keys_blacklist:
         args.extend([k, api.vars.builder_cfg[k]])
 
-  # See skia:2789.
-  if 'AbandonGpuContext' in api.vars.extra_tokens:
-    args.extend(['--abandonGpuContext'])
-
   api.run(api.flavor.step, target, cmd=args,
           abort_on_failure=False)
 
@@ -391,6 +402,7 @@
 
 
 TEST_BUILDERS = [
+  'Perf-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-All-Android',
   'Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-All-Android',
   ('Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-'
    'Android_NoGPUThreads'),
@@ -402,15 +414,17 @@
   'Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-BonusConfigs',
   'Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All-Vulkan',
   'Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All',
-  ('Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-'
+  ('Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-'
    'CommandBuffer'),
-  'Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer',
-  ('Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-'
+  ('Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-'
    'MoltenVK_Vulkan'),
+  ('Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-'
+   'CommandBuffer'),
   ('Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-'
-    'Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41'),
+    'Valgrind_SK_CPU_LIMIT_SSE41'),
   'Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ANGLE',
   'Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan',
+  'Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All',
   'Perf-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All',
 ]
 
diff --git a/infra/bots/recipes/perf_canvaskit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit.json b/infra/bots/recipes/perf_canvaskit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit.json
index 503ec27..5615330 100644
--- a/infra/bots/recipes/perf_canvaskit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit.json
+++ b/infra/bots/recipes/perf_canvaskit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -112,8 +112,8 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport os\nimport shutil\nimport sys\n\ncopy_dest = sys.argv[1]\nbase_dir = sys.argv[2]\nbundle_name = sys.argv[3]\nout_dir = sys.argv[4]\n\n# Clean out old binaries (if any)\ntry:\n  shutil.rmtree(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.ENOENT:\n    raise\n\n# Make folder\ntry:\n  os.makedirs(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\n# Copy binaries (canvaskit.js and canvaskit.wasm) to where the karma tests\n# expect them ($SKIA_ROOT/experimental/canvaskit/canvaskit/bin/)\ndest = os.path.join(copy_dest, 'canvaskit.js')\nshutil.copyfile(os.path.join(base_dir, 'canvaskit.js'), dest)\nos.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\nif bundle_name:\n  dest = os.path.join(copy_dest, bundle_name)\n  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)\n  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\n# Prepare output folder, api.file.ensure_directory doesn't touch\n# the permissions of the out directory if it already exists.\nos.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.\n",
-      "[START_DIR]/cache/work/skia/experimental/canvaskit/canvaskit/bin",
+      "import errno\nimport os\nimport shutil\nimport sys\n\ncopy_dest = sys.argv[1]\nbase_dir = sys.argv[2]\nbundle_name = sys.argv[3]\nout_dir = sys.argv[4]\n\n# Clean out old binaries (if any)\ntry:\n  shutil.rmtree(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.ENOENT:\n    raise\n\n# Make folder\ntry:\n  os.makedirs(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\n# Copy binaries (canvaskit.js and canvaskit.wasm) to where the karma tests\n# expect them ($SKIA_ROOT/modules/canvaskit/canvaskit/bin/)\ndest = os.path.join(copy_dest, 'canvaskit.js')\nshutil.copyfile(os.path.join(base_dir, 'canvaskit.js'), dest)\nos.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\nif bundle_name:\n  dest = os.path.join(copy_dest, bundle_name)\n  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)\n  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\n# Prepare output folder, api.file.ensure_directory doesn't touch\n# the permissions of the out directory if it already exists.\nos.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.\n",
+      "[START_DIR]/cache/work/skia/modules/canvaskit/canvaskit/bin",
       "[START_DIR]/build",
       "canvaskit.wasm",
       "[START_DIR]/[SWARM_OUT_DIR]"
@@ -146,7 +146,7 @@
       "@@@STEP_LOG_LINE@python.inline@    raise@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@# Copy binaries (canvaskit.js and canvaskit.wasm) to where the karma tests@@@",
-      "@@@STEP_LOG_LINE@python.inline@# expect them ($SKIA_ROOT/experimental/canvaskit/canvaskit/bin/)@@@",
+      "@@@STEP_LOG_LINE@python.inline@# expect them ($SKIA_ROOT/modules/canvaskit/canvaskit/bin/)@@@",
       "@@@STEP_LOG_LINE@python.inline@dest = os.path.join(copy_dest, 'canvaskit.js')@@@",
       "@@@STEP_LOG_LINE@python.inline@shutil.copyfile(os.path.join(base_dir, 'canvaskit.js'), dest)@@@",
       "@@@STEP_LOG_LINE@python.inline@os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.@@@",
@@ -200,7 +200,7 @@
       "[START_DIR]/cache/work:/SRC",
       "--volume",
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
-      "gcr.io/skia-public/perf-karma-chrome-tests:68.0.3440.106_v6",
+      "gcr.io/skia-public/perf-karma-chrome-tests:72.0.3626.121_v1",
       "/SRC/skia/infra/canvaskit/perf_canvaskit.sh",
       "--builder",
       "Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit",
@@ -222,7 +222,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Performance tests of canvaskit with Docker"
   },
diff --git a/infra/bots/recipes/perf_canvaskit.expected/pathkit_trybot.json b/infra/bots/recipes/perf_canvaskit.expected/pathkit_trybot.json
index d66ccbe..e771d6c 100644
--- a/infra/bots/recipes/perf_canvaskit.expected/pathkit_trybot.json
+++ b/infra/bots/recipes/perf_canvaskit.expected/pathkit_trybot.json
@@ -52,7 +52,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -114,8 +114,8 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport os\nimport shutil\nimport sys\n\ncopy_dest = sys.argv[1]\nbase_dir = sys.argv[2]\nbundle_name = sys.argv[3]\nout_dir = sys.argv[4]\n\n# Clean out old binaries (if any)\ntry:\n  shutil.rmtree(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.ENOENT:\n    raise\n\n# Make folder\ntry:\n  os.makedirs(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\n# Copy binaries (canvaskit.js and canvaskit.wasm) to where the karma tests\n# expect them ($SKIA_ROOT/experimental/canvaskit/canvaskit/bin/)\ndest = os.path.join(copy_dest, 'canvaskit.js')\nshutil.copyfile(os.path.join(base_dir, 'canvaskit.js'), dest)\nos.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\nif bundle_name:\n  dest = os.path.join(copy_dest, bundle_name)\n  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)\n  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\n# Prepare output folder, api.file.ensure_directory doesn't touch\n# the permissions of the out directory if it already exists.\nos.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.\n",
-      "[START_DIR]/cache/work/skia/experimental/canvaskit/canvaskit/bin",
+      "import errno\nimport os\nimport shutil\nimport sys\n\ncopy_dest = sys.argv[1]\nbase_dir = sys.argv[2]\nbundle_name = sys.argv[3]\nout_dir = sys.argv[4]\n\n# Clean out old binaries (if any)\ntry:\n  shutil.rmtree(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.ENOENT:\n    raise\n\n# Make folder\ntry:\n  os.makedirs(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\n# Copy binaries (canvaskit.js and canvaskit.wasm) to where the karma tests\n# expect them ($SKIA_ROOT/modules/canvaskit/canvaskit/bin/)\ndest = os.path.join(copy_dest, 'canvaskit.js')\nshutil.copyfile(os.path.join(base_dir, 'canvaskit.js'), dest)\nos.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\nif bundle_name:\n  dest = os.path.join(copy_dest, bundle_name)\n  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)\n  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\n# Prepare output folder, api.file.ensure_directory doesn't touch\n# the permissions of the out directory if it already exists.\nos.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.\n",
+      "[START_DIR]/cache/work/skia/modules/canvaskit/canvaskit/bin",
       "[START_DIR]/build",
       "canvaskit.wasm",
       "[START_DIR]/[SWARM_OUT_DIR]"
@@ -148,7 +148,7 @@
       "@@@STEP_LOG_LINE@python.inline@    raise@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@# Copy binaries (canvaskit.js and canvaskit.wasm) to where the karma tests@@@",
-      "@@@STEP_LOG_LINE@python.inline@# expect them ($SKIA_ROOT/experimental/canvaskit/canvaskit/bin/)@@@",
+      "@@@STEP_LOG_LINE@python.inline@# expect them ($SKIA_ROOT/modules/canvaskit/canvaskit/bin/)@@@",
       "@@@STEP_LOG_LINE@python.inline@dest = os.path.join(copy_dest, 'canvaskit.js')@@@",
       "@@@STEP_LOG_LINE@python.inline@shutil.copyfile(os.path.join(base_dir, 'canvaskit.js'), dest)@@@",
       "@@@STEP_LOG_LINE@python.inline@os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.@@@",
@@ -202,7 +202,7 @@
       "[START_DIR]/cache/work:/SRC",
       "--volume",
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
-      "gcr.io/skia-public/perf-karma-chrome-tests:68.0.3440.106_v6",
+      "gcr.io/skia-public/perf-karma-chrome-tests:72.0.3626.121_v1",
       "/SRC/skia/infra/canvaskit/perf_canvaskit.sh",
       "--builder",
       "Perf-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit",
@@ -230,7 +230,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Performance tests of canvaskit with Docker"
   },
diff --git a/infra/bots/recipes/perf_canvaskit.py b/infra/bots/recipes/perf_canvaskit.py
index d7347d0..8ad59df 100644
--- a/infra/bots/recipes/perf_canvaskit.py
+++ b/infra/bots/recipes/perf_canvaskit.py
@@ -18,7 +18,7 @@
 ]
 
 
-DOCKER_IMAGE = 'gcr.io/skia-public/perf-karma-chrome-tests:68.0.3440.106_v6'
+DOCKER_IMAGE = 'gcr.io/skia-public/perf-karma-chrome-tests:72.0.3626.121_v1'
 INNER_KARMA_SCRIPT = '/SRC/skia/infra/canvaskit/perf_canvaskit.sh'
 
 
@@ -33,7 +33,7 @@
 
   # The karma script is configured to look in ./canvaskit/bin/ for
   # the test files to load, so we must copy them there (see Set up for docker).
-  copy_dest = checkout_root.join('skia', 'experimental', 'canvaskit',
+  copy_dest = checkout_root.join('skia', 'modules', 'canvaskit',
                                  'canvaskit', 'bin')
 
   base_dir = api.vars.build_dir
@@ -66,7 +66,7 @@
     raise
 
 # Copy binaries (canvaskit.js and canvaskit.wasm) to where the karma tests
-# expect them ($SKIA_ROOT/experimental/canvaskit/canvaskit/bin/)
+# expect them ($SKIA_ROOT/modules/canvaskit/canvaskit/bin/)
 dest = os.path.join(copy_dest, 'canvaskit.js')
 shutil.copyfile(os.path.join(base_dir, 'canvaskit.js'), dest)
 os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.
diff --git a/infra/bots/recipes/perf_pathkit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json b/infra/bots/recipes/perf_pathkit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json
index 7f5e488..18a3196 100644
--- a/infra/bots/recipes/perf_pathkit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json
+++ b/infra/bots/recipes/perf_pathkit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -202,7 +202,7 @@
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
       "--env",
       "ASM_JS=1",
-      "gcr.io/skia-public/perf-karma-chrome-tests:68.0.3440.106_v6",
+      "gcr.io/skia-public/perf-karma-chrome-tests:72.0.3626.121_v1",
       "/SRC/skia/infra/pathkit/perf_pathkit.sh",
       "--builder",
       "Perf-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit",
@@ -226,7 +226,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Performance tests of PathKit with Docker"
   },
diff --git a/infra/bots/recipes/perf_pathkit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit.json b/infra/bots/recipes/perf_pathkit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit.json
index efb0d2d..d78a005 100644
--- a/infra/bots/recipes/perf_pathkit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit.json
+++ b/infra/bots/recipes/perf_pathkit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -200,7 +200,7 @@
       "[START_DIR]/cache/work:/SRC",
       "--volume",
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
-      "gcr.io/skia-public/perf-karma-chrome-tests:68.0.3440.106_v6",
+      "gcr.io/skia-public/perf-karma-chrome-tests:72.0.3626.121_v1",
       "/SRC/skia/infra/pathkit/perf_pathkit.sh",
       "--builder",
       "Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit",
@@ -222,7 +222,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Performance tests of PathKit with Docker"
   },
diff --git a/infra/bots/recipes/perf_pathkit.expected/pathkit_trybot.json b/infra/bots/recipes/perf_pathkit.expected/pathkit_trybot.json
index 36d8f86..ba0f4e2 100644
--- a/infra/bots/recipes/perf_pathkit.expected/pathkit_trybot.json
+++ b/infra/bots/recipes/perf_pathkit.expected/pathkit_trybot.json
@@ -52,7 +52,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -202,7 +202,7 @@
       "[START_DIR]/cache/work:/SRC",
       "--volume",
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
-      "gcr.io/skia-public/perf-karma-chrome-tests:68.0.3440.106_v6",
+      "gcr.io/skia-public/perf-karma-chrome-tests:72.0.3626.121_v1",
       "/SRC/skia/infra/pathkit/perf_pathkit.sh",
       "--builder",
       "Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit",
@@ -230,7 +230,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Performance tests of PathKit with Docker"
   },
diff --git a/infra/bots/recipes/perf_pathkit.py b/infra/bots/recipes/perf_pathkit.py
index a313bf2..d45597b 100644
--- a/infra/bots/recipes/perf_pathkit.py
+++ b/infra/bots/recipes/perf_pathkit.py
@@ -18,7 +18,7 @@
 ]
 
 
-DOCKER_IMAGE = 'gcr.io/skia-public/perf-karma-chrome-tests:68.0.3440.106_v6'
+DOCKER_IMAGE = 'gcr.io/skia-public/perf-karma-chrome-tests:72.0.3626.121_v1'
 INNER_KARMA_SCRIPT = '/SRC/skia/infra/pathkit/perf_pathkit.sh'
 
 
diff --git a/infra/bots/recipes/perf_skottietrace.expected/Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android_SkottieTracing.json b/infra/bots/recipes/perf_skottietrace.expected/Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android_SkottieTracing.json
new file mode 100644
index 0000000..387dd61
--- /dev/null
+++ b/infra/bots/recipes/perf_skottietrace.expected/Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android_SkottieTracing.json
@@ -0,0 +1,1015 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "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": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/resources"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
+  },
+  {
+    "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(['/opt/infra-android/tools/adb', 'push',\n                           os.path.realpath(os.path.join(host, p, f)),\n                           os.path.join(device, p, f)])\n",
+      "[START_DIR]/skia/resources",
+      "/sdcard/revenge_of_the_skiabot/resources"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
+    "~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(['/opt/infra-android/tools/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": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/lottie-samples/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get lottie-samples VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/LOTTIE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write LOTTIE_VERSION"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/LOTTIE_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read /sdcard/revenge_of_the_skiabot/LOTTIE_VERSION",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "/sdcard/revenge_of_the_skiabot/LOTTIE_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/LOTTIE_VERSION"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-rf",
+      "/sdcard/revenge_of_the_skiabot/lotties"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/lotties"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/lotties"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/lotties"
+  },
+  {
+    "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(['/opt/infra-android/tools/adb', 'push',\n                           os.path.realpath(os.path.join(host, p, f)),\n                           os.path.join(device, p, f)])\n",
+      "[START_DIR]/lottie-samples",
+      "/sdcard/revenge_of_the_skiabot/lotties"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/lottie-samples/* /sdcard/revenge_of_the_skiabot/lotties",
+    "~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(['/opt/infra-android/tools/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": [
+      "/opt/infra-android/tools/adb",
+      "push",
+      "[START_DIR]/tmp/LOTTIE_VERSION",
+      "/sdcard/revenge_of_the_skiabot/LOTTIE_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/tmp/LOTTIE_VERSION /sdcard/revenge_of_the_skiabot/LOTTIE_VERSION"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-rf",
+      "/sdcard/revenge_of_the_skiabot/dm_out"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/dm_out"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/dm_out"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/dm_out"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "listdir",
+      "[START_DIR]/lottie-samples"
+    ],
+    "infra_step": true,
+    "name": "list lottie files",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/lottie-samples/LICENSE@@@",
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/lottie-samples/lottie 3!.json@@@",
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/lottie-samples/lottie(test)'!2.json@@@",
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/lottie-samples/lottie1.json@@@",
+      "@@@STEP_LOG_END@listdir@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nimport time\nADB = sys.argv[1]\ncpu = int(sys.argv[2])\ngov = sys.argv[3]\n\nlog = subprocess.check_output([ADB, 'root'])\n# check for message like 'adbd cannot run as root in production builds'\nprint log\nif 'cannot' in log:\n  raise Exception('adb root failed')\n\nsubprocess.check_output([ADB, 'shell', 'echo \"%s\" > '\n    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % (gov, cpu)])\nactual_gov = subprocess.check_output([ADB, 'shell', 'cat '\n    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % cpu]).strip()\nif actual_gov != gov:\n  raise Exception('(actual, expected) (%s, %s)'\n                  % (actual_gov, gov))\n",
+      "/opt/infra-android/tools/adb",
+      "0",
+      "hotplug"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "Set CPU 0's governor to hotplug",
+    "timeout": 30,
+    "~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@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@ADB = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@cpu = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@gov = sys.argv[3]@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@log = subprocess.check_output([ADB, 'root'])@@@",
+      "@@@STEP_LOG_LINE@python.inline@# check for message like 'adbd cannot run as root in production builds'@@@",
+      "@@@STEP_LOG_LINE@python.inline@print log@@@",
+      "@@@STEP_LOG_LINE@python.inline@if 'cannot' in log:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('adb root failed')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_output([ADB, 'shell', 'echo \"%s\" > '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % (gov, cpu)])@@@",
+      "@@@STEP_LOG_LINE@python.inline@actual_gov = subprocess.check_output([ADB, 'shell', 'cat '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % cpu]).strip()@@@",
+      "@@@STEP_LOG_LINE@python.inline@if actual_gov != gov:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('(actual, expected) (%s, %s)'@@@",
+      "@@@STEP_LOG_LINE@python.inline@                  % (actual_gov, gov))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "push",
+      "[START_DIR]/build/dm",
+      "/data/local/tmp/"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push dm"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --lotties /sdcard/revenge_of_the_skiabot/lotties --src lottie --nonativeFonts --verbose --traceMatch skottie --trace /sdcard/revenge_of_the_skiabot/dm_out/2.json --match \"^lottie 3!.json$\" --config gles --nocpu; echo $? >/data/local/tmp/rc",
+      "[START_DIR]/tmp/dm.sh"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "write dm.sh"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "push",
+      "[START_DIR]/tmp/dm.sh",
+      "/data/local/tmp/"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push dm.sh"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "logcat",
+      "-c"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "clear log"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport subprocess\nimport sys\nbin_dir = sys.argv[1]\nsh      = sys.argv[2]\nsubprocess.check_call(['/opt/infra-android/tools/adb', 'shell', 'sh', bin_dir + sh])\ntry:\n  sys.exit(int(subprocess.check_output(['/opt/infra-android/tools/adb', 'shell', 'cat',\n                                        bin_dir + 'rc'])))\nexcept ValueError:\n  print \"Couldn't read the return code.  Probably killed for OOM.\"\n  sys.exit(1)\n",
+      "/data/local/tmp/",
+      "dm.sh"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "dm",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@bin_dir = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@sh      = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_call(['/opt/infra-android/tools/adb', 'shell', 'sh', bin_dir + sh])@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(int(subprocess.check_output(['/opt/infra-android/tools/adb', 'shell', 'cat',@@@",
+      "@@@STEP_LOG_LINE@python.inline@                                        bin_dir + 'rc'])))@@@",
+      "@@@STEP_LOG_LINE@python.inline@except ValueError:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  print \"Couldn't read the return code.  Probably killed for OOM.\"@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(1)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/dm_out/2.json"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read /sdcard/revenge_of_the_skiabot/dm_out/2.json",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport json\nimport sys\n\ntrace_output = sys.argv[1]\ntrace_json = json.loads(trace_output)\nlottie_filename = sys.argv[2]\noutput_json_file = sys.argv[3]\n\nperf_results = {}\nframe_max = 0\nframe_min = 0\nframe_cumulative = 0\ncurrent_frame_duration = 0\ntotal_frames = 0\nframe_start = False\nskipped_first_seek = False  # Skip the first seek constructor call.\nfor trace in trace_json:\n  if 'skottie::Animation::seek' in trace['name']:\n    if not skipped_first_seek:\n      skipped_first_seek = True\n      continue\n    if frame_start:\n      raise Exception('We got consecutive Animation::seek without a ' +\n                      'render. Something is wrong.')\n    frame_start = True\n    current_frame_duration = trace['dur']\n  elif 'skottie::Animation::render' in trace['name']:\n    if not frame_start:\n      raise Exception('We got an Animation::render without a seek first. ' +\n                      'Something is wrong.')\n\n    current_frame_duration += trace['dur']\n    frame_start = False\n    total_frames += 1\n    frame_max = max(frame_max, current_frame_duration)\n    frame_min = (min(frame_min, current_frame_duration)\n                 if frame_min else current_frame_duration)\n    frame_cumulative += current_frame_duration\n\nexpected_dm_frames = 25\nif total_frames != expected_dm_frames:\n  raise Exception(\n      'Got ' + str(total_frames) + ' frames instead of ' +\n      str(expected_dm_frames))\nperf_results['frame_max_us'] = frame_max\nperf_results['frame_min_us'] = frame_min\nperf_results['frame_avg_us'] = frame_cumulative/total_frames\n\n# Write perf_results to the output json.\nwith open(output_json_file, 'w') as f:\n  f.write(json.dumps(perf_results))\n",
+      "\n[{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":452,\"dur\":2.57,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPaint(const SkPaint &)\",\"ts\":473,\"dur\":2.67e+03,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.15e+03,\"dur\":2.25,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.15e+03,\"dur\":216,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPath(const SkPath &, const SkPaint &)\",\"ts\":3.35e+03,\"dur\":15.1,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.37e+03,\"dur\":1.17,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.37e+03,\"dur\":140,\"tid\":1,\"pid\":0}]\n",
+      "lottie 3!.json",
+      "/path/to/tmp/json"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "parse lottie 3!.json trace",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_avg_us\": 179.71, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_max_us\": 218.25, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_min_us\": 141.17@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import json@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_output = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_json = json.loads(trace_output)@@@",
+      "@@@STEP_LOG_LINE@python.inline@lottie_filename = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@output_json_file = sys.argv[3]@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results = {}@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_max = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_min = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_cumulative = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@current_frame_duration = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@total_frames = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@skipped_first_seek = False  # Skip the first seek constructor call.@@@",
+      "@@@STEP_LOG_LINE@python.inline@for trace in trace_json:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if 'skottie::Animation::seek' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not skipped_first_seek:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      skipped_first_seek = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@      continue@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got consecutive Animation::seek without a ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'render. Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration = trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@  elif 'skottie::Animation::render' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got an Animation::render without a seek first. ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration += trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@    total_frames += 1@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_max = max(frame_max, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_min = (min(frame_min, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@                 if frame_min else current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_cumulative += current_frame_duration@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@expected_dm_frames = 25@@@",
+      "@@@STEP_LOG_LINE@python.inline@if total_frames != expected_dm_frames:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception(@@@",
+      "@@@STEP_LOG_LINE@python.inline@      'Got ' + str(total_frames) + ' frames instead of ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@      str(expected_dm_frames))@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_max_us'] = frame_max@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_min_us'] = frame_min@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_avg_us'] = frame_cumulative/total_frames@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# Write perf_results to the output json.@@@",
+      "@@@STEP_LOG_LINE@python.inline@with open(output_json_file, 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  f.write(json.dumps(perf_results))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "/sdcard/revenge_of_the_skiabot/dm_out/2.json"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/dm_out/2.json"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --lotties /sdcard/revenge_of_the_skiabot/lotties --src lottie --nonativeFonts --verbose --traceMatch skottie --trace /sdcard/revenge_of_the_skiabot/dm_out/3.json --match \\^lottie\\(test\\)\\'\\!2\\.json\\$ --config gles --nocpu; echo $? >/data/local/tmp/rc",
+      "[START_DIR]/tmp/dm.sh"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "write dm.sh (2)"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "push",
+      "[START_DIR]/tmp/dm.sh",
+      "/data/local/tmp/"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push dm.sh (2)"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "logcat",
+      "-c"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "clear log (2)"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport subprocess\nimport sys\nbin_dir = sys.argv[1]\nsh      = sys.argv[2]\nsubprocess.check_call(['/opt/infra-android/tools/adb', 'shell', 'sh', bin_dir + sh])\ntry:\n  sys.exit(int(subprocess.check_output(['/opt/infra-android/tools/adb', 'shell', 'cat',\n                                        bin_dir + 'rc'])))\nexcept ValueError:\n  print \"Couldn't read the return code.  Probably killed for OOM.\"\n  sys.exit(1)\n",
+      "/data/local/tmp/",
+      "dm.sh"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "dm (2)",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@bin_dir = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@sh      = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_call(['/opt/infra-android/tools/adb', 'shell', 'sh', bin_dir + sh])@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(int(subprocess.check_output(['/opt/infra-android/tools/adb', 'shell', 'cat',@@@",
+      "@@@STEP_LOG_LINE@python.inline@                                        bin_dir + 'rc'])))@@@",
+      "@@@STEP_LOG_LINE@python.inline@except ValueError:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  print \"Couldn't read the return code.  Probably killed for OOM.\"@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(1)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/dm_out/3.json"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read /sdcard/revenge_of_the_skiabot/dm_out/3.json",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport json\nimport sys\n\ntrace_output = sys.argv[1]\ntrace_json = json.loads(trace_output)\nlottie_filename = sys.argv[2]\noutput_json_file = sys.argv[3]\n\nperf_results = {}\nframe_max = 0\nframe_min = 0\nframe_cumulative = 0\ncurrent_frame_duration = 0\ntotal_frames = 0\nframe_start = False\nskipped_first_seek = False  # Skip the first seek constructor call.\nfor trace in trace_json:\n  if 'skottie::Animation::seek' in trace['name']:\n    if not skipped_first_seek:\n      skipped_first_seek = True\n      continue\n    if frame_start:\n      raise Exception('We got consecutive Animation::seek without a ' +\n                      'render. Something is wrong.')\n    frame_start = True\n    current_frame_duration = trace['dur']\n  elif 'skottie::Animation::render' in trace['name']:\n    if not frame_start:\n      raise Exception('We got an Animation::render without a seek first. ' +\n                      'Something is wrong.')\n\n    current_frame_duration += trace['dur']\n    frame_start = False\n    total_frames += 1\n    frame_max = max(frame_max, current_frame_duration)\n    frame_min = (min(frame_min, current_frame_duration)\n                 if frame_min else current_frame_duration)\n    frame_cumulative += current_frame_duration\n\nexpected_dm_frames = 25\nif total_frames != expected_dm_frames:\n  raise Exception(\n      'Got ' + str(total_frames) + ' frames instead of ' +\n      str(expected_dm_frames))\nperf_results['frame_max_us'] = frame_max\nperf_results['frame_min_us'] = frame_min\nperf_results['frame_avg_us'] = frame_cumulative/total_frames\n\n# Write perf_results to the output json.\nwith open(output_json_file, 'w') as f:\n  f.write(json.dumps(perf_results))\n",
+      "\n[{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":452,\"dur\":2.57,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPaint(const SkPaint &)\",\"ts\":473,\"dur\":2.67e+03,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.15e+03,\"dur\":2.25,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.15e+03,\"dur\":216,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPath(const SkPath &, const SkPaint &)\",\"ts\":3.35e+03,\"dur\":15.1,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.37e+03,\"dur\":1.17,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.37e+03,\"dur\":140,\"tid\":1,\"pid\":0}]\n",
+      "lottie(test)'!2.json",
+      "/path/to/tmp/json"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "parse lottie(test)'!2.json trace",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_avg_us\": 179.71, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_max_us\": 218.25, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_min_us\": 141.17@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import json@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_output = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_json = json.loads(trace_output)@@@",
+      "@@@STEP_LOG_LINE@python.inline@lottie_filename = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@output_json_file = sys.argv[3]@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results = {}@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_max = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_min = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_cumulative = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@current_frame_duration = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@total_frames = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@skipped_first_seek = False  # Skip the first seek constructor call.@@@",
+      "@@@STEP_LOG_LINE@python.inline@for trace in trace_json:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if 'skottie::Animation::seek' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not skipped_first_seek:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      skipped_first_seek = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@      continue@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got consecutive Animation::seek without a ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'render. Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration = trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@  elif 'skottie::Animation::render' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got an Animation::render without a seek first. ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration += trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@    total_frames += 1@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_max = max(frame_max, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_min = (min(frame_min, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@                 if frame_min else current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_cumulative += current_frame_duration@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@expected_dm_frames = 25@@@",
+      "@@@STEP_LOG_LINE@python.inline@if total_frames != expected_dm_frames:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception(@@@",
+      "@@@STEP_LOG_LINE@python.inline@      'Got ' + str(total_frames) + ' frames instead of ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@      str(expected_dm_frames))@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_max_us'] = frame_max@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_min_us'] = frame_min@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_avg_us'] = frame_cumulative/total_frames@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# Write perf_results to the output json.@@@",
+      "@@@STEP_LOG_LINE@python.inline@with open(output_json_file, 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  f.write(json.dumps(perf_results))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "/sdcard/revenge_of_the_skiabot/dm_out/3.json"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/dm_out/3.json"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --lotties /sdcard/revenge_of_the_skiabot/lotties --src lottie --nonativeFonts --verbose --traceMatch skottie --trace /sdcard/revenge_of_the_skiabot/dm_out/4.json --match \\^lottie1\\.json\\$ --config gles --nocpu; echo $? >/data/local/tmp/rc",
+      "[START_DIR]/tmp/dm.sh"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "write dm.sh (3)"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "push",
+      "[START_DIR]/tmp/dm.sh",
+      "/data/local/tmp/"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push dm.sh (3)"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "logcat",
+      "-c"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "clear log (3)"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport subprocess\nimport sys\nbin_dir = sys.argv[1]\nsh      = sys.argv[2]\nsubprocess.check_call(['/opt/infra-android/tools/adb', 'shell', 'sh', bin_dir + sh])\ntry:\n  sys.exit(int(subprocess.check_output(['/opt/infra-android/tools/adb', 'shell', 'cat',\n                                        bin_dir + 'rc'])))\nexcept ValueError:\n  print \"Couldn't read the return code.  Probably killed for OOM.\"\n  sys.exit(1)\n",
+      "/data/local/tmp/",
+      "dm.sh"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "dm (3)",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@bin_dir = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@sh      = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_call(['/opt/infra-android/tools/adb', 'shell', 'sh', bin_dir + sh])@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(int(subprocess.check_output(['/opt/infra-android/tools/adb', 'shell', 'cat',@@@",
+      "@@@STEP_LOG_LINE@python.inline@                                        bin_dir + 'rc'])))@@@",
+      "@@@STEP_LOG_LINE@python.inline@except ValueError:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  print \"Couldn't read the return code.  Probably killed for OOM.\"@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(1)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/dm_out/4.json"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read /sdcard/revenge_of_the_skiabot/dm_out/4.json",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport json\nimport sys\n\ntrace_output = sys.argv[1]\ntrace_json = json.loads(trace_output)\nlottie_filename = sys.argv[2]\noutput_json_file = sys.argv[3]\n\nperf_results = {}\nframe_max = 0\nframe_min = 0\nframe_cumulative = 0\ncurrent_frame_duration = 0\ntotal_frames = 0\nframe_start = False\nskipped_first_seek = False  # Skip the first seek constructor call.\nfor trace in trace_json:\n  if 'skottie::Animation::seek' in trace['name']:\n    if not skipped_first_seek:\n      skipped_first_seek = True\n      continue\n    if frame_start:\n      raise Exception('We got consecutive Animation::seek without a ' +\n                      'render. Something is wrong.')\n    frame_start = True\n    current_frame_duration = trace['dur']\n  elif 'skottie::Animation::render' in trace['name']:\n    if not frame_start:\n      raise Exception('We got an Animation::render without a seek first. ' +\n                      'Something is wrong.')\n\n    current_frame_duration += trace['dur']\n    frame_start = False\n    total_frames += 1\n    frame_max = max(frame_max, current_frame_duration)\n    frame_min = (min(frame_min, current_frame_duration)\n                 if frame_min else current_frame_duration)\n    frame_cumulative += current_frame_duration\n\nexpected_dm_frames = 25\nif total_frames != expected_dm_frames:\n  raise Exception(\n      'Got ' + str(total_frames) + ' frames instead of ' +\n      str(expected_dm_frames))\nperf_results['frame_max_us'] = frame_max\nperf_results['frame_min_us'] = frame_min\nperf_results['frame_avg_us'] = frame_cumulative/total_frames\n\n# Write perf_results to the output json.\nwith open(output_json_file, 'w') as f:\n  f.write(json.dumps(perf_results))\n",
+      "\n[{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":452,\"dur\":2.57,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPaint(const SkPaint &)\",\"ts\":473,\"dur\":2.67e+03,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.15e+03,\"dur\":2.25,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.15e+03,\"dur\":216,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPath(const SkPath &, const SkPaint &)\",\"ts\":3.35e+03,\"dur\":15.1,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.37e+03,\"dur\":1.17,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.37e+03,\"dur\":140,\"tid\":1,\"pid\":0}]\n",
+      "lottie1.json",
+      "/path/to/tmp/json"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "parse lottie1.json trace",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_avg_us\": 179.71, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_max_us\": 218.25, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_min_us\": 141.17@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import json@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_output = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_json = json.loads(trace_output)@@@",
+      "@@@STEP_LOG_LINE@python.inline@lottie_filename = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@output_json_file = sys.argv[3]@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results = {}@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_max = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_min = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_cumulative = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@current_frame_duration = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@total_frames = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@skipped_first_seek = False  # Skip the first seek constructor call.@@@",
+      "@@@STEP_LOG_LINE@python.inline@for trace in trace_json:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if 'skottie::Animation::seek' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not skipped_first_seek:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      skipped_first_seek = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@      continue@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got consecutive Animation::seek without a ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'render. Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration = trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@  elif 'skottie::Animation::render' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got an Animation::render without a seek first. ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration += trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@    total_frames += 1@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_max = max(frame_max, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_min = (min(frame_min, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@                 if frame_min else current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_cumulative += current_frame_duration@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@expected_dm_frames = 25@@@",
+      "@@@STEP_LOG_LINE@python.inline@if total_frames != expected_dm_frames:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception(@@@",
+      "@@@STEP_LOG_LINE@python.inline@      'Got ' + str(total_frames) + ' frames instead of ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@      str(expected_dm_frames))@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_max_us'] = frame_max@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_min_us'] = frame_min@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_avg_us'] = frame_cumulative/total_frames@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# Write perf_results to the output json.@@@",
+      "@@@STEP_LOG_LINE@python.inline@with open(output_json_file, 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  f.write(json.dumps(perf_results))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "/sdcard/revenge_of_the_skiabot/dm_out/4.json"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/dm_out/4.json"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id (2)",
+    "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",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "makedirs perf_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import json\nwith open('[START_DIR]/[SWARM_OUT_DIR]/perf_abc123_1337000001.json', 'w') as outfile:\n  json.dump(obj={'swarming_task_id': '', 'swarming_bot_id': '', 'results': {'gles': {\"lottie(test)'!2.json\": {'frame_avg_us': 179.71, 'frame_max_us': 218.25, 'frame_min_us': 141.17}, 'lottie1.json': {'frame_avg_us': 179.71, 'frame_max_us': 218.25, 'frame_min_us': 141.17}, 'lottie 3!.json': {'frame_avg_us': 179.71, 'frame_max_us': 218.25, 'frame_min_us': 141.17}}}, 'key': {'extra_config': 'Android_SkottieTracing', 'bench_type': 'tracing', 'cpu_or_gpu_value': 'Mali400MP2', 'arch': 'arm', 'source_type': 'skottie', 'cpu_or_gpu': 'GPU', 'model': 'AndroidOne', 'configuration': 'Release', 'os': 'Android', 'compiler': 'Clang'}, 'gitHash': 'abc123'}, fp=outfile, indent=4)\n"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "write output JSON",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import json@@@",
+      "@@@STEP_LOG_LINE@python.inline@with open('[START_DIR]/[SWARM_OUT_DIR]/perf_abc123_1337000001.json', 'w') as outfile:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  json.dump(obj={'swarming_task_id': '', 'swarming_bot_id': '', 'results': {'gles': {\"lottie(test)'!2.json\": {'frame_avg_us': 179.71, 'frame_max_us': 218.25, 'frame_min_us': 141.17}, 'lottie1.json': {'frame_avg_us': 179.71, 'frame_max_us': 218.25, 'frame_min_us': 141.17}, 'lottie 3!.json': {'frame_avg_us': 179.71, 'frame_max_us': 218.25, 'frame_min_us': 141.17}}}, 'key': {'extra_config': 'Android_SkottieTracing', 'bench_type': 'tracing', 'cpu_or_gpu_value': 'Mali400MP2', 'arch': 'arm', 'source_type': 'skottie', 'cpu_or_gpu': 'GPU', 'model': 'AndroidOne', 'configuration': 'Release', 'os': 'Android', 'compiler': 'Clang'}, 'gitHash': 'abc123'}, fp=outfile, indent=4)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nout = sys.argv[1]\nlog = subprocess.check_output(['/opt/infra-android/tools/adb', 'logcat', '-d'])\nfor line in log.split('\\n'):\n  tokens = line.split()\n  if len(tokens) == 11 and tokens[-7] == 'F' and tokens[-3] == 'pc':\n    addr, path = tokens[-2:]\n    local = os.path.join(out, os.path.basename(path))\n    if os.path.exists(local):\n      sym = subprocess.check_output(['addr2line', '-Cfpe', local, addr])\n      line = line.replace(addr, addr + ' ' + sym.strip())\n  print line\n",
+      "[START_DIR]/build"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "dump log",
+    "timeout": 300,
+    "~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(['/opt/infra-android/tools/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": [
+      "/opt/infra-android/tools/adb",
+      "kill-server"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "kill adb server"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/perf_skottietrace.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SkottieTracing.json b/infra/bots/recipes/perf_skottietrace.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SkottieTracing.json
new file mode 100644
index 0000000..704f25d
--- /dev/null
+++ b/infra/bots/recipes/perf_skottietrace.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SkottieTracing.json
@@ -0,0 +1,567 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/lottie-samples/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get lottie-samples VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/LOTTIE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write LOTTIE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "rmtree [SWARM_OUT_DIR]"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "makedirs [SWARM_OUT_DIR]"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "listdir",
+      "[START_DIR]/lottie-samples"
+    ],
+    "infra_step": true,
+    "name": "list lottie files",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/lottie-samples/LICENSE@@@",
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/lottie-samples/lottie 3!.json@@@",
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/lottie-samples/lottie(test)'!2.json@@@",
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/lottie-samples/lottie1.json@@@",
+      "@@@STEP_LOG_END@listdir@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[skia::flavor]/resources/symbolize_stack_trace.py",
+      "[START_DIR]",
+      "catchsegv",
+      "[START_DIR]/build/dm",
+      "--resourcePath",
+      "[START_DIR]/skia/resources",
+      "--lotties",
+      "[START_DIR]/lottie-samples",
+      "--src",
+      "lottie",
+      "--nonativeFonts",
+      "--verbose",
+      "--traceMatch",
+      "skottie",
+      "--trace",
+      "[START_DIR]/[SWARM_OUT_DIR]/2.json",
+      "--match",
+      "^lottie 3!.json$",
+      "--config",
+      "8888",
+      "--nogpu"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "symbolized dm"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/[SWARM_OUT_DIR]/2.json",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "read [START_DIR]/[SWARM_OUT_DIR]/2.json"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport json\nimport sys\n\ntrace_output = sys.argv[1]\ntrace_json = json.loads(trace_output)\nlottie_filename = sys.argv[2]\noutput_json_file = sys.argv[3]\n\nperf_results = {}\nframe_max = 0\nframe_min = 0\nframe_cumulative = 0\ncurrent_frame_duration = 0\ntotal_frames = 0\nframe_start = False\nskipped_first_seek = False  # Skip the first seek constructor call.\nfor trace in trace_json:\n  if 'skottie::Animation::seek' in trace['name']:\n    if not skipped_first_seek:\n      skipped_first_seek = True\n      continue\n    if frame_start:\n      raise Exception('We got consecutive Animation::seek without a ' +\n                      'render. Something is wrong.')\n    frame_start = True\n    current_frame_duration = trace['dur']\n  elif 'skottie::Animation::render' in trace['name']:\n    if not frame_start:\n      raise Exception('We got an Animation::render without a seek first. ' +\n                      'Something is wrong.')\n\n    current_frame_duration += trace['dur']\n    frame_start = False\n    total_frames += 1\n    frame_max = max(frame_max, current_frame_duration)\n    frame_min = (min(frame_min, current_frame_duration)\n                 if frame_min else current_frame_duration)\n    frame_cumulative += current_frame_duration\n\nexpected_dm_frames = 25\nif total_frames != expected_dm_frames:\n  raise Exception(\n      'Got ' + str(total_frames) + ' frames instead of ' +\n      str(expected_dm_frames))\nperf_results['frame_max_us'] = frame_max\nperf_results['frame_min_us'] = frame_min\nperf_results['frame_avg_us'] = frame_cumulative/total_frames\n\n# Write perf_results to the output json.\nwith open(output_json_file, 'w') as f:\n  f.write(json.dumps(perf_results))\n",
+      "\n[{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":452,\"dur\":2.57,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPaint(const SkPaint &)\",\"ts\":473,\"dur\":2.67e+03,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.15e+03,\"dur\":2.25,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.15e+03,\"dur\":216,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPath(const SkPath &, const SkPaint &)\",\"ts\":3.35e+03,\"dur\":15.1,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.37e+03,\"dur\":1.17,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.37e+03,\"dur\":140,\"tid\":1,\"pid\":0}]\n",
+      "lottie 3!.json",
+      "/path/to/tmp/json"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "parse lottie 3!.json trace",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_avg_us\": 179.71, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_max_us\": 218.25, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_min_us\": 141.17@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import json@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_output = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_json = json.loads(trace_output)@@@",
+      "@@@STEP_LOG_LINE@python.inline@lottie_filename = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@output_json_file = sys.argv[3]@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results = {}@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_max = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_min = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_cumulative = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@current_frame_duration = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@total_frames = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@skipped_first_seek = False  # Skip the first seek constructor call.@@@",
+      "@@@STEP_LOG_LINE@python.inline@for trace in trace_json:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if 'skottie::Animation::seek' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not skipped_first_seek:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      skipped_first_seek = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@      continue@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got consecutive Animation::seek without a ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'render. Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration = trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@  elif 'skottie::Animation::render' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got an Animation::render without a seek first. ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration += trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@    total_frames += 1@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_max = max(frame_max, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_min = (min(frame_min, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@                 if frame_min else current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_cumulative += current_frame_duration@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@expected_dm_frames = 25@@@",
+      "@@@STEP_LOG_LINE@python.inline@if total_frames != expected_dm_frames:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception(@@@",
+      "@@@STEP_LOG_LINE@python.inline@      'Got ' + str(total_frames) + ' frames instead of ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@      str(expected_dm_frames))@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_max_us'] = frame_max@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_min_us'] = frame_min@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_avg_us'] = frame_cumulative/total_frames@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# Write perf_results to the output json.@@@",
+      "@@@STEP_LOG_LINE@python.inline@with open(output_json_file, 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  f.write(json.dumps(perf_results))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "[START_DIR]/[SWARM_OUT_DIR]/2.json"
+    ],
+    "infra_step": true,
+    "name": "remove [START_DIR]/[SWARM_OUT_DIR]/2.json"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[skia::flavor]/resources/symbolize_stack_trace.py",
+      "[START_DIR]",
+      "catchsegv",
+      "[START_DIR]/build/dm",
+      "--resourcePath",
+      "[START_DIR]/skia/resources",
+      "--lotties",
+      "[START_DIR]/lottie-samples",
+      "--src",
+      "lottie",
+      "--nonativeFonts",
+      "--verbose",
+      "--traceMatch",
+      "skottie",
+      "--trace",
+      "[START_DIR]/[SWARM_OUT_DIR]/3.json",
+      "--match",
+      "^lottie(test)'!2.json$",
+      "--config",
+      "8888",
+      "--nogpu"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "symbolized dm (2)"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/[SWARM_OUT_DIR]/3.json",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "read [START_DIR]/[SWARM_OUT_DIR]/3.json"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport json\nimport sys\n\ntrace_output = sys.argv[1]\ntrace_json = json.loads(trace_output)\nlottie_filename = sys.argv[2]\noutput_json_file = sys.argv[3]\n\nperf_results = {}\nframe_max = 0\nframe_min = 0\nframe_cumulative = 0\ncurrent_frame_duration = 0\ntotal_frames = 0\nframe_start = False\nskipped_first_seek = False  # Skip the first seek constructor call.\nfor trace in trace_json:\n  if 'skottie::Animation::seek' in trace['name']:\n    if not skipped_first_seek:\n      skipped_first_seek = True\n      continue\n    if frame_start:\n      raise Exception('We got consecutive Animation::seek without a ' +\n                      'render. Something is wrong.')\n    frame_start = True\n    current_frame_duration = trace['dur']\n  elif 'skottie::Animation::render' in trace['name']:\n    if not frame_start:\n      raise Exception('We got an Animation::render without a seek first. ' +\n                      'Something is wrong.')\n\n    current_frame_duration += trace['dur']\n    frame_start = False\n    total_frames += 1\n    frame_max = max(frame_max, current_frame_duration)\n    frame_min = (min(frame_min, current_frame_duration)\n                 if frame_min else current_frame_duration)\n    frame_cumulative += current_frame_duration\n\nexpected_dm_frames = 25\nif total_frames != expected_dm_frames:\n  raise Exception(\n      'Got ' + str(total_frames) + ' frames instead of ' +\n      str(expected_dm_frames))\nperf_results['frame_max_us'] = frame_max\nperf_results['frame_min_us'] = frame_min\nperf_results['frame_avg_us'] = frame_cumulative/total_frames\n\n# Write perf_results to the output json.\nwith open(output_json_file, 'w') as f:\n  f.write(json.dumps(perf_results))\n",
+      "\n[{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":452,\"dur\":2.57,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPaint(const SkPaint &)\",\"ts\":473,\"dur\":2.67e+03,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.15e+03,\"dur\":2.25,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.15e+03,\"dur\":216,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPath(const SkPath &, const SkPaint &)\",\"ts\":3.35e+03,\"dur\":15.1,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.37e+03,\"dur\":1.17,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.37e+03,\"dur\":140,\"tid\":1,\"pid\":0}]\n",
+      "lottie(test)'!2.json",
+      "/path/to/tmp/json"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "parse lottie(test)'!2.json trace",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_avg_us\": 179.71, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_max_us\": 218.25, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_min_us\": 141.17@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import json@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_output = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_json = json.loads(trace_output)@@@",
+      "@@@STEP_LOG_LINE@python.inline@lottie_filename = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@output_json_file = sys.argv[3]@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results = {}@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_max = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_min = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_cumulative = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@current_frame_duration = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@total_frames = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@skipped_first_seek = False  # Skip the first seek constructor call.@@@",
+      "@@@STEP_LOG_LINE@python.inline@for trace in trace_json:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if 'skottie::Animation::seek' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not skipped_first_seek:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      skipped_first_seek = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@      continue@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got consecutive Animation::seek without a ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'render. Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration = trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@  elif 'skottie::Animation::render' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got an Animation::render without a seek first. ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration += trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@    total_frames += 1@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_max = max(frame_max, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_min = (min(frame_min, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@                 if frame_min else current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_cumulative += current_frame_duration@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@expected_dm_frames = 25@@@",
+      "@@@STEP_LOG_LINE@python.inline@if total_frames != expected_dm_frames:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception(@@@",
+      "@@@STEP_LOG_LINE@python.inline@      'Got ' + str(total_frames) + ' frames instead of ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@      str(expected_dm_frames))@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_max_us'] = frame_max@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_min_us'] = frame_min@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_avg_us'] = frame_cumulative/total_frames@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# Write perf_results to the output json.@@@",
+      "@@@STEP_LOG_LINE@python.inline@with open(output_json_file, 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  f.write(json.dumps(perf_results))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "[START_DIR]/[SWARM_OUT_DIR]/3.json"
+    ],
+    "infra_step": true,
+    "name": "remove [START_DIR]/[SWARM_OUT_DIR]/3.json"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[skia::flavor]/resources/symbolize_stack_trace.py",
+      "[START_DIR]",
+      "catchsegv",
+      "[START_DIR]/build/dm",
+      "--resourcePath",
+      "[START_DIR]/skia/resources",
+      "--lotties",
+      "[START_DIR]/lottie-samples",
+      "--src",
+      "lottie",
+      "--nonativeFonts",
+      "--verbose",
+      "--traceMatch",
+      "skottie",
+      "--trace",
+      "[START_DIR]/[SWARM_OUT_DIR]/4.json",
+      "--match",
+      "^lottie1.json$",
+      "--config",
+      "8888",
+      "--nogpu"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "symbolized dm (3)"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/[SWARM_OUT_DIR]/4.json",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "read [START_DIR]/[SWARM_OUT_DIR]/4.json"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport json\nimport sys\n\ntrace_output = sys.argv[1]\ntrace_json = json.loads(trace_output)\nlottie_filename = sys.argv[2]\noutput_json_file = sys.argv[3]\n\nperf_results = {}\nframe_max = 0\nframe_min = 0\nframe_cumulative = 0\ncurrent_frame_duration = 0\ntotal_frames = 0\nframe_start = False\nskipped_first_seek = False  # Skip the first seek constructor call.\nfor trace in trace_json:\n  if 'skottie::Animation::seek' in trace['name']:\n    if not skipped_first_seek:\n      skipped_first_seek = True\n      continue\n    if frame_start:\n      raise Exception('We got consecutive Animation::seek without a ' +\n                      'render. Something is wrong.')\n    frame_start = True\n    current_frame_duration = trace['dur']\n  elif 'skottie::Animation::render' in trace['name']:\n    if not frame_start:\n      raise Exception('We got an Animation::render without a seek first. ' +\n                      'Something is wrong.')\n\n    current_frame_duration += trace['dur']\n    frame_start = False\n    total_frames += 1\n    frame_max = max(frame_max, current_frame_duration)\n    frame_min = (min(frame_min, current_frame_duration)\n                 if frame_min else current_frame_duration)\n    frame_cumulative += current_frame_duration\n\nexpected_dm_frames = 25\nif total_frames != expected_dm_frames:\n  raise Exception(\n      'Got ' + str(total_frames) + ' frames instead of ' +\n      str(expected_dm_frames))\nperf_results['frame_max_us'] = frame_max\nperf_results['frame_min_us'] = frame_min\nperf_results['frame_avg_us'] = frame_cumulative/total_frames\n\n# Write perf_results to the output json.\nwith open(output_json_file, 'w') as f:\n  f.write(json.dumps(perf_results))\n",
+      "\n[{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":452,\"dur\":2.57,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPaint(const SkPaint &)\",\"ts\":473,\"dur\":2.67e+03,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.15e+03,\"dur\":2.25,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.15e+03,\"dur\":216,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPath(const SkPath &, const SkPaint &)\",\"ts\":3.35e+03,\"dur\":15.1,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.37e+03,\"dur\":1.17,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.37e+03,\"dur\":140,\"tid\":1,\"pid\":0}]\n",
+      "lottie1.json",
+      "/path/to/tmp/json"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "parse lottie1.json trace",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_avg_us\": 179.71, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_max_us\": 218.25, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_min_us\": 141.17@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import json@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_output = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_json = json.loads(trace_output)@@@",
+      "@@@STEP_LOG_LINE@python.inline@lottie_filename = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@output_json_file = sys.argv[3]@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results = {}@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_max = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_min = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_cumulative = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@current_frame_duration = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@total_frames = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@skipped_first_seek = False  # Skip the first seek constructor call.@@@",
+      "@@@STEP_LOG_LINE@python.inline@for trace in trace_json:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if 'skottie::Animation::seek' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not skipped_first_seek:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      skipped_first_seek = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@      continue@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got consecutive Animation::seek without a ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'render. Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration = trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@  elif 'skottie::Animation::render' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got an Animation::render without a seek first. ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration += trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@    total_frames += 1@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_max = max(frame_max, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_min = (min(frame_min, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@                 if frame_min else current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_cumulative += current_frame_duration@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@expected_dm_frames = 25@@@",
+      "@@@STEP_LOG_LINE@python.inline@if total_frames != expected_dm_frames:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception(@@@",
+      "@@@STEP_LOG_LINE@python.inline@      'Got ' + str(total_frames) + ' frames instead of ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@      str(expected_dm_frames))@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_max_us'] = frame_max@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_min_us'] = frame_min@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_avg_us'] = frame_cumulative/total_frames@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# Write perf_results to the output json.@@@",
+      "@@@STEP_LOG_LINE@python.inline@with open(output_json_file, 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  f.write(json.dumps(perf_results))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "[START_DIR]/[SWARM_OUT_DIR]/4.json"
+    ],
+    "infra_step": true,
+    "name": "remove [START_DIR]/[SWARM_OUT_DIR]/4.json"
+  },
+  {
+    "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",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "makedirs perf_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import json\nwith open('[START_DIR]/[SWARM_OUT_DIR]/perf_abc123_1337000001.json', 'w') as outfile:\n  json.dump(obj={'swarming_task_id': '', 'swarming_bot_id': '', 'results': {'gles': {\"lottie(test)'!2.json\": {'frame_avg_us': 179.71, 'frame_max_us': 218.25, 'frame_min_us': 141.17}, 'lottie1.json': {'frame_avg_us': 179.71, 'frame_max_us': 218.25, 'frame_min_us': 141.17}, 'lottie 3!.json': {'frame_avg_us': 179.71, 'frame_max_us': 218.25, 'frame_min_us': 141.17}}}, 'key': {'extra_config': 'SkottieTracing', 'bench_type': 'tracing', 'cpu_or_gpu_value': 'AVX2', 'arch': 'x86_64', 'source_type': 'skottie', 'cpu_or_gpu': 'CPU', 'model': 'GCE', 'configuration': 'Release', 'os': 'Debian9', 'compiler': 'Clang'}, 'gitHash': 'abc123'}, fp=outfile, indent=4)\n"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "write output JSON",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import json@@@",
+      "@@@STEP_LOG_LINE@python.inline@with open('[START_DIR]/[SWARM_OUT_DIR]/perf_abc123_1337000001.json', 'w') as outfile:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  json.dump(obj={'swarming_task_id': '', 'swarming_bot_id': '', 'results': {'gles': {\"lottie(test)'!2.json\": {'frame_avg_us': 179.71, 'frame_max_us': 218.25, 'frame_min_us': 141.17}, 'lottie1.json': {'frame_avg_us': 179.71, 'frame_max_us': 218.25, 'frame_min_us': 141.17}, 'lottie 3!.json': {'frame_avg_us': 179.71, 'frame_max_us': 218.25, 'frame_min_us': 141.17}}}, 'key': {'extra_config': 'SkottieTracing', 'bench_type': 'tracing', 'cpu_or_gpu_value': 'AVX2', 'arch': 'x86_64', 'source_type': 'skottie', 'cpu_or_gpu': 'CPU', 'model': 'GCE', 'configuration': 'Release', 'os': 'Debian9', 'compiler': 'Clang'}, 'gitHash': 'abc123'}, fp=outfile, indent=4)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/perf_skottietrace.expected/skottietracing_parse_trace_error.json b/infra/bots/recipes/perf_skottietrace.expected/skottietracing_parse_trace_error.json
new file mode 100644
index 0000000..3c44f0d
--- /dev/null
+++ b/infra/bots/recipes/perf_skottietrace.expected/skottietracing_parse_trace_error.json
@@ -0,0 +1,567 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "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": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/resources"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
+  },
+  {
+    "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(['/opt/infra-android/tools/adb', 'push',\n                           os.path.realpath(os.path.join(host, p, f)),\n                           os.path.join(device, p, f)])\n",
+      "[START_DIR]/skia/resources",
+      "/sdcard/revenge_of_the_skiabot/resources"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
+    "~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(['/opt/infra-android/tools/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": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/lottie-samples/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get lottie-samples VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/LOTTIE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write LOTTIE_VERSION"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/LOTTIE_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read /sdcard/revenge_of_the_skiabot/LOTTIE_VERSION",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "/sdcard/revenge_of_the_skiabot/LOTTIE_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/LOTTIE_VERSION"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-rf",
+      "/sdcard/revenge_of_the_skiabot/lotties"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/lotties"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/lotties"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/lotties"
+  },
+  {
+    "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(['/opt/infra-android/tools/adb', 'push',\n                           os.path.realpath(os.path.join(host, p, f)),\n                           os.path.join(device, p, f)])\n",
+      "[START_DIR]/lottie-samples",
+      "/sdcard/revenge_of_the_skiabot/lotties"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/lottie-samples/* /sdcard/revenge_of_the_skiabot/lotties",
+    "~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(['/opt/infra-android/tools/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": [
+      "/opt/infra-android/tools/adb",
+      "push",
+      "[START_DIR]/tmp/LOTTIE_VERSION",
+      "/sdcard/revenge_of_the_skiabot/LOTTIE_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/tmp/LOTTIE_VERSION /sdcard/revenge_of_the_skiabot/LOTTIE_VERSION"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-rf",
+      "/sdcard/revenge_of_the_skiabot/dm_out"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/dm_out"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/dm_out"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/dm_out"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "listdir",
+      "[START_DIR]/lottie-samples"
+    ],
+    "infra_step": true,
+    "name": "list lottie files",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/lottie-samples/LICENSE@@@",
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/lottie-samples/lottie 3!.json@@@",
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/lottie-samples/lottie(test)'!2.json@@@",
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/lottie-samples/lottie1.json@@@",
+      "@@@STEP_LOG_END@listdir@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nimport time\nADB = sys.argv[1]\ncpu = int(sys.argv[2])\ngov = sys.argv[3]\n\nlog = subprocess.check_output([ADB, 'root'])\n# check for message like 'adbd cannot run as root in production builds'\nprint log\nif 'cannot' in log:\n  raise Exception('adb root failed')\n\nsubprocess.check_output([ADB, 'shell', 'echo \"%s\" > '\n    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % (gov, cpu)])\nactual_gov = subprocess.check_output([ADB, 'shell', 'cat '\n    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % cpu]).strip()\nif actual_gov != gov:\n  raise Exception('(actual, expected) (%s, %s)'\n                  % (actual_gov, gov))\n",
+      "/opt/infra-android/tools/adb",
+      "0",
+      "hotplug"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "Set CPU 0's governor to hotplug",
+    "timeout": 30,
+    "~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@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@ADB = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@cpu = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@gov = sys.argv[3]@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@log = subprocess.check_output([ADB, 'root'])@@@",
+      "@@@STEP_LOG_LINE@python.inline@# check for message like 'adbd cannot run as root in production builds'@@@",
+      "@@@STEP_LOG_LINE@python.inline@print log@@@",
+      "@@@STEP_LOG_LINE@python.inline@if 'cannot' in log:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('adb root failed')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_output([ADB, 'shell', 'echo \"%s\" > '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % (gov, cpu)])@@@",
+      "@@@STEP_LOG_LINE@python.inline@actual_gov = subprocess.check_output([ADB, 'shell', 'cat '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % cpu]).strip()@@@",
+      "@@@STEP_LOG_LINE@python.inline@if actual_gov != gov:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('(actual, expected) (%s, %s)'@@@",
+      "@@@STEP_LOG_LINE@python.inline@                  % (actual_gov, gov))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "push",
+      "[START_DIR]/build/dm",
+      "/data/local/tmp/"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push dm"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --lotties /sdcard/revenge_of_the_skiabot/lotties --src lottie --nonativeFonts --verbose --traceMatch skottie --trace /sdcard/revenge_of_the_skiabot/dm_out/2.json --match \"^lottie 3!.json$\" --config gles --nocpu; echo $? >/data/local/tmp/rc",
+      "[START_DIR]/tmp/dm.sh"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "write dm.sh"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "push",
+      "[START_DIR]/tmp/dm.sh",
+      "/data/local/tmp/"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push dm.sh"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "logcat",
+      "-c"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "clear log"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport subprocess\nimport sys\nbin_dir = sys.argv[1]\nsh      = sys.argv[2]\nsubprocess.check_call(['/opt/infra-android/tools/adb', 'shell', 'sh', bin_dir + sh])\ntry:\n  sys.exit(int(subprocess.check_output(['/opt/infra-android/tools/adb', 'shell', 'cat',\n                                        bin_dir + 'rc'])))\nexcept ValueError:\n  print \"Couldn't read the return code.  Probably killed for OOM.\"\n  sys.exit(1)\n",
+      "/data/local/tmp/",
+      "dm.sh"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "dm",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@bin_dir = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@sh      = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_call(['/opt/infra-android/tools/adb', 'shell', 'sh', bin_dir + sh])@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(int(subprocess.check_output(['/opt/infra-android/tools/adb', 'shell', 'cat',@@@",
+      "@@@STEP_LOG_LINE@python.inline@                                        bin_dir + 'rc'])))@@@",
+      "@@@STEP_LOG_LINE@python.inline@except ValueError:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  print \"Couldn't read the return code.  Probably killed for OOM.\"@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(1)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/dm_out/2.json"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read /sdcard/revenge_of_the_skiabot/dm_out/2.json",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport json\nimport sys\n\ntrace_output = sys.argv[1]\ntrace_json = json.loads(trace_output)\nlottie_filename = sys.argv[2]\noutput_json_file = sys.argv[3]\n\nperf_results = {}\nframe_max = 0\nframe_min = 0\nframe_cumulative = 0\ncurrent_frame_duration = 0\ntotal_frames = 0\nframe_start = False\nskipped_first_seek = False  # Skip the first seek constructor call.\nfor trace in trace_json:\n  if 'skottie::Animation::seek' in trace['name']:\n    if not skipped_first_seek:\n      skipped_first_seek = True\n      continue\n    if frame_start:\n      raise Exception('We got consecutive Animation::seek without a ' +\n                      'render. Something is wrong.')\n    frame_start = True\n    current_frame_duration = trace['dur']\n  elif 'skottie::Animation::render' in trace['name']:\n    if not frame_start:\n      raise Exception('We got an Animation::render without a seek first. ' +\n                      'Something is wrong.')\n\n    current_frame_duration += trace['dur']\n    frame_start = False\n    total_frames += 1\n    frame_max = max(frame_max, current_frame_duration)\n    frame_min = (min(frame_min, current_frame_duration)\n                 if frame_min else current_frame_duration)\n    frame_cumulative += current_frame_duration\n\nexpected_dm_frames = 25\nif total_frames != expected_dm_frames:\n  raise Exception(\n      'Got ' + str(total_frames) + ' frames instead of ' +\n      str(expected_dm_frames))\nperf_results['frame_max_us'] = frame_max\nperf_results['frame_min_us'] = frame_min\nperf_results['frame_avg_us'] = frame_cumulative/total_frames\n\n# Write perf_results to the output json.\nwith open(output_json_file, 'w') as f:\n  f.write(json.dumps(perf_results))\n",
+      "\n[{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":452,\"dur\":2.57,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPaint(const SkPaint &)\",\"ts\":473,\"dur\":2.67e+03,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.15e+03,\"dur\":2.25,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.15e+03,\"dur\":216,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPath(const SkPath &, const SkPaint &)\",\"ts\":3.35e+03,\"dur\":15.1,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.37e+03,\"dur\":1.17,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.37e+03,\"dur\":140,\"tid\":1,\"pid\":0}]\n",
+      "lottie 3!.json",
+      "/path/to/tmp/json"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "parse lottie 3!.json trace",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_avg_us\": 179.71, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_max_us\": 218.25, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_min_us\": 141.17@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import json@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_output = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_json = json.loads(trace_output)@@@",
+      "@@@STEP_LOG_LINE@python.inline@lottie_filename = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@output_json_file = sys.argv[3]@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results = {}@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_max = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_min = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_cumulative = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@current_frame_duration = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@total_frames = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@skipped_first_seek = False  # Skip the first seek constructor call.@@@",
+      "@@@STEP_LOG_LINE@python.inline@for trace in trace_json:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if 'skottie::Animation::seek' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not skipped_first_seek:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      skipped_first_seek = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@      continue@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got consecutive Animation::seek without a ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'render. Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration = trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@  elif 'skottie::Animation::render' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got an Animation::render without a seek first. ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration += trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@    total_frames += 1@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_max = max(frame_max, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_min = (min(frame_min, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@                 if frame_min else current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_cumulative += current_frame_duration@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@expected_dm_frames = 25@@@",
+      "@@@STEP_LOG_LINE@python.inline@if total_frames != expected_dm_frames:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception(@@@",
+      "@@@STEP_LOG_LINE@python.inline@      'Got ' + str(total_frames) + ' frames instead of ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@      str(expected_dm_frames))@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_max_us'] = frame_max@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_min_us'] = frame_min@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_avg_us'] = frame_cumulative/total_frames@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# Write perf_results to the output json.@@@",
+      "@@@STEP_LOG_LINE@python.inline@with open(output_json_file, 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  f.write(json.dumps(perf_results))@@@",
+      "@@@STEP_LOG_END@python.inline@@@",
+      "@@@STEP_FAILURE@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nout = sys.argv[1]\nlog = subprocess.check_output(['/opt/infra-android/tools/adb', 'logcat', '-d'])\nfor line in log.split('\\n'):\n  tokens = line.split()\n  if len(tokens) == 11 and tokens[-7] == 'F' and tokens[-3] == 'pc':\n    addr, path = tokens[-2:]\n    local = os.path.join(out, os.path.basename(path))\n    if os.path.exists(local):\n      sym = subprocess.check_output(['addr2line', '-Cfpe', local, addr])\n      line = line.replace(addr, addr + ' ' + sym.strip())\n  print line\n",
+      "[START_DIR]/build"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "dump log",
+    "timeout": 300,
+    "~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(['/opt/infra-android/tools/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": [
+      "/opt/infra-android/tools/adb",
+      "kill-server"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "kill adb server"
+  },
+  {
+    "failure": {
+      "failure": {
+        "step": "parse lottie 3!.json trace"
+      },
+      "humanReason": "Step('parse lottie 3!.json trace') failed with return_code 1"
+    },
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/perf_skottietrace.expected/skottietracing_trybot.json b/infra/bots/recipes/perf_skottietrace.expected/skottietracing_trybot.json
new file mode 100644
index 0000000..b12df8d
--- /dev/null
+++ b/infra/bots/recipes/perf_skottietrace.expected/skottietracing_trybot.json
@@ -0,0 +1,1015 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "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": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/resources"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
+  },
+  {
+    "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(['/opt/infra-android/tools/adb', 'push',\n                           os.path.realpath(os.path.join(host, p, f)),\n                           os.path.join(device, p, f)])\n",
+      "[START_DIR]/skia/resources",
+      "/sdcard/revenge_of_the_skiabot/resources"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
+    "~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(['/opt/infra-android/tools/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": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/lottie-samples/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get lottie-samples VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/LOTTIE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write LOTTIE_VERSION"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/LOTTIE_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read /sdcard/revenge_of_the_skiabot/LOTTIE_VERSION",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "/sdcard/revenge_of_the_skiabot/LOTTIE_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/LOTTIE_VERSION"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-rf",
+      "/sdcard/revenge_of_the_skiabot/lotties"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/lotties"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/lotties"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/lotties"
+  },
+  {
+    "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(['/opt/infra-android/tools/adb', 'push',\n                           os.path.realpath(os.path.join(host, p, f)),\n                           os.path.join(device, p, f)])\n",
+      "[START_DIR]/lottie-samples",
+      "/sdcard/revenge_of_the_skiabot/lotties"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/lottie-samples/* /sdcard/revenge_of_the_skiabot/lotties",
+    "~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(['/opt/infra-android/tools/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": [
+      "/opt/infra-android/tools/adb",
+      "push",
+      "[START_DIR]/tmp/LOTTIE_VERSION",
+      "/sdcard/revenge_of_the_skiabot/LOTTIE_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push [START_DIR]/tmp/LOTTIE_VERSION /sdcard/revenge_of_the_skiabot/LOTTIE_VERSION"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-rf",
+      "/sdcard/revenge_of_the_skiabot/dm_out"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/dm_out"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/dm_out"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/dm_out"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "listdir",
+      "[START_DIR]/lottie-samples"
+    ],
+    "infra_step": true,
+    "name": "list lottie files",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/lottie-samples/LICENSE@@@",
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/lottie-samples/lottie 3!.json@@@",
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/lottie-samples/lottie(test)'!2.json@@@",
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/lottie-samples/lottie1.json@@@",
+      "@@@STEP_LOG_END@listdir@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nimport time\nADB = sys.argv[1]\ncpu = int(sys.argv[2])\ngov = sys.argv[3]\n\nlog = subprocess.check_output([ADB, 'root'])\n# check for message like 'adbd cannot run as root in production builds'\nprint log\nif 'cannot' in log:\n  raise Exception('adb root failed')\n\nsubprocess.check_output([ADB, 'shell', 'echo \"%s\" > '\n    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % (gov, cpu)])\nactual_gov = subprocess.check_output([ADB, 'shell', 'cat '\n    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % cpu]).strip()\nif actual_gov != gov:\n  raise Exception('(actual, expected) (%s, %s)'\n                  % (actual_gov, gov))\n",
+      "/opt/infra-android/tools/adb",
+      "0",
+      "hotplug"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "Set CPU 0's governor to hotplug",
+    "timeout": 30,
+    "~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@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@ADB = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@cpu = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@gov = sys.argv[3]@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@log = subprocess.check_output([ADB, 'root'])@@@",
+      "@@@STEP_LOG_LINE@python.inline@# check for message like 'adbd cannot run as root in production builds'@@@",
+      "@@@STEP_LOG_LINE@python.inline@print log@@@",
+      "@@@STEP_LOG_LINE@python.inline@if 'cannot' in log:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('adb root failed')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_output([ADB, 'shell', 'echo \"%s\" > '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % (gov, cpu)])@@@",
+      "@@@STEP_LOG_LINE@python.inline@actual_gov = subprocess.check_output([ADB, 'shell', 'cat '@@@",
+      "@@@STEP_LOG_LINE@python.inline@    '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % cpu]).strip()@@@",
+      "@@@STEP_LOG_LINE@python.inline@if actual_gov != gov:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception('(actual, expected) (%s, %s)'@@@",
+      "@@@STEP_LOG_LINE@python.inline@                  % (actual_gov, gov))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "push",
+      "[START_DIR]/build/dm",
+      "/data/local/tmp/"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push dm"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --lotties /sdcard/revenge_of_the_skiabot/lotties --src lottie --nonativeFonts --verbose --traceMatch skottie --trace /sdcard/revenge_of_the_skiabot/dm_out/2.json --match \"^lottie 3!.json$\" --config gles --nocpu; echo $? >/data/local/tmp/rc",
+      "[START_DIR]/tmp/dm.sh"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "write dm.sh"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "push",
+      "[START_DIR]/tmp/dm.sh",
+      "/data/local/tmp/"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push dm.sh"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "logcat",
+      "-c"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "clear log"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport subprocess\nimport sys\nbin_dir = sys.argv[1]\nsh      = sys.argv[2]\nsubprocess.check_call(['/opt/infra-android/tools/adb', 'shell', 'sh', bin_dir + sh])\ntry:\n  sys.exit(int(subprocess.check_output(['/opt/infra-android/tools/adb', 'shell', 'cat',\n                                        bin_dir + 'rc'])))\nexcept ValueError:\n  print \"Couldn't read the return code.  Probably killed for OOM.\"\n  sys.exit(1)\n",
+      "/data/local/tmp/",
+      "dm.sh"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "dm",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@bin_dir = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@sh      = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_call(['/opt/infra-android/tools/adb', 'shell', 'sh', bin_dir + sh])@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(int(subprocess.check_output(['/opt/infra-android/tools/adb', 'shell', 'cat',@@@",
+      "@@@STEP_LOG_LINE@python.inline@                                        bin_dir + 'rc'])))@@@",
+      "@@@STEP_LOG_LINE@python.inline@except ValueError:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  print \"Couldn't read the return code.  Probably killed for OOM.\"@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(1)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/dm_out/2.json"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read /sdcard/revenge_of_the_skiabot/dm_out/2.json",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport json\nimport sys\n\ntrace_output = sys.argv[1]\ntrace_json = json.loads(trace_output)\nlottie_filename = sys.argv[2]\noutput_json_file = sys.argv[3]\n\nperf_results = {}\nframe_max = 0\nframe_min = 0\nframe_cumulative = 0\ncurrent_frame_duration = 0\ntotal_frames = 0\nframe_start = False\nskipped_first_seek = False  # Skip the first seek constructor call.\nfor trace in trace_json:\n  if 'skottie::Animation::seek' in trace['name']:\n    if not skipped_first_seek:\n      skipped_first_seek = True\n      continue\n    if frame_start:\n      raise Exception('We got consecutive Animation::seek without a ' +\n                      'render. Something is wrong.')\n    frame_start = True\n    current_frame_duration = trace['dur']\n  elif 'skottie::Animation::render' in trace['name']:\n    if not frame_start:\n      raise Exception('We got an Animation::render without a seek first. ' +\n                      'Something is wrong.')\n\n    current_frame_duration += trace['dur']\n    frame_start = False\n    total_frames += 1\n    frame_max = max(frame_max, current_frame_duration)\n    frame_min = (min(frame_min, current_frame_duration)\n                 if frame_min else current_frame_duration)\n    frame_cumulative += current_frame_duration\n\nexpected_dm_frames = 25\nif total_frames != expected_dm_frames:\n  raise Exception(\n      'Got ' + str(total_frames) + ' frames instead of ' +\n      str(expected_dm_frames))\nperf_results['frame_max_us'] = frame_max\nperf_results['frame_min_us'] = frame_min\nperf_results['frame_avg_us'] = frame_cumulative/total_frames\n\n# Write perf_results to the output json.\nwith open(output_json_file, 'w') as f:\n  f.write(json.dumps(perf_results))\n",
+      "\n[{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":452,\"dur\":2.57,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPaint(const SkPaint &)\",\"ts\":473,\"dur\":2.67e+03,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.15e+03,\"dur\":2.25,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.15e+03,\"dur\":216,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPath(const SkPath &, const SkPaint &)\",\"ts\":3.35e+03,\"dur\":15.1,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.37e+03,\"dur\":1.17,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.37e+03,\"dur\":140,\"tid\":1,\"pid\":0}]\n",
+      "lottie 3!.json",
+      "/path/to/tmp/json"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "parse lottie 3!.json trace",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_avg_us\": 179.71, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_max_us\": 218.25, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_min_us\": 141.17@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import json@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_output = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_json = json.loads(trace_output)@@@",
+      "@@@STEP_LOG_LINE@python.inline@lottie_filename = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@output_json_file = sys.argv[3]@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results = {}@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_max = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_min = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_cumulative = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@current_frame_duration = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@total_frames = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@skipped_first_seek = False  # Skip the first seek constructor call.@@@",
+      "@@@STEP_LOG_LINE@python.inline@for trace in trace_json:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if 'skottie::Animation::seek' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not skipped_first_seek:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      skipped_first_seek = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@      continue@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got consecutive Animation::seek without a ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'render. Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration = trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@  elif 'skottie::Animation::render' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got an Animation::render without a seek first. ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration += trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@    total_frames += 1@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_max = max(frame_max, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_min = (min(frame_min, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@                 if frame_min else current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_cumulative += current_frame_duration@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@expected_dm_frames = 25@@@",
+      "@@@STEP_LOG_LINE@python.inline@if total_frames != expected_dm_frames:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception(@@@",
+      "@@@STEP_LOG_LINE@python.inline@      'Got ' + str(total_frames) + ' frames instead of ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@      str(expected_dm_frames))@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_max_us'] = frame_max@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_min_us'] = frame_min@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_avg_us'] = frame_cumulative/total_frames@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# Write perf_results to the output json.@@@",
+      "@@@STEP_LOG_LINE@python.inline@with open(output_json_file, 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  f.write(json.dumps(perf_results))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "/sdcard/revenge_of_the_skiabot/dm_out/2.json"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/dm_out/2.json"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --lotties /sdcard/revenge_of_the_skiabot/lotties --src lottie --nonativeFonts --verbose --traceMatch skottie --trace /sdcard/revenge_of_the_skiabot/dm_out/3.json --match \\^lottie\\(test\\)\\'\\!2\\.json\\$ --config gles --nocpu; echo $? >/data/local/tmp/rc",
+      "[START_DIR]/tmp/dm.sh"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "write dm.sh (2)"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "push",
+      "[START_DIR]/tmp/dm.sh",
+      "/data/local/tmp/"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push dm.sh (2)"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "logcat",
+      "-c"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "clear log (2)"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport subprocess\nimport sys\nbin_dir = sys.argv[1]\nsh      = sys.argv[2]\nsubprocess.check_call(['/opt/infra-android/tools/adb', 'shell', 'sh', bin_dir + sh])\ntry:\n  sys.exit(int(subprocess.check_output(['/opt/infra-android/tools/adb', 'shell', 'cat',\n                                        bin_dir + 'rc'])))\nexcept ValueError:\n  print \"Couldn't read the return code.  Probably killed for OOM.\"\n  sys.exit(1)\n",
+      "/data/local/tmp/",
+      "dm.sh"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "dm (2)",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@bin_dir = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@sh      = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_call(['/opt/infra-android/tools/adb', 'shell', 'sh', bin_dir + sh])@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(int(subprocess.check_output(['/opt/infra-android/tools/adb', 'shell', 'cat',@@@",
+      "@@@STEP_LOG_LINE@python.inline@                                        bin_dir + 'rc'])))@@@",
+      "@@@STEP_LOG_LINE@python.inline@except ValueError:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  print \"Couldn't read the return code.  Probably killed for OOM.\"@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(1)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/dm_out/3.json"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read /sdcard/revenge_of_the_skiabot/dm_out/3.json",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport json\nimport sys\n\ntrace_output = sys.argv[1]\ntrace_json = json.loads(trace_output)\nlottie_filename = sys.argv[2]\noutput_json_file = sys.argv[3]\n\nperf_results = {}\nframe_max = 0\nframe_min = 0\nframe_cumulative = 0\ncurrent_frame_duration = 0\ntotal_frames = 0\nframe_start = False\nskipped_first_seek = False  # Skip the first seek constructor call.\nfor trace in trace_json:\n  if 'skottie::Animation::seek' in trace['name']:\n    if not skipped_first_seek:\n      skipped_first_seek = True\n      continue\n    if frame_start:\n      raise Exception('We got consecutive Animation::seek without a ' +\n                      'render. Something is wrong.')\n    frame_start = True\n    current_frame_duration = trace['dur']\n  elif 'skottie::Animation::render' in trace['name']:\n    if not frame_start:\n      raise Exception('We got an Animation::render without a seek first. ' +\n                      'Something is wrong.')\n\n    current_frame_duration += trace['dur']\n    frame_start = False\n    total_frames += 1\n    frame_max = max(frame_max, current_frame_duration)\n    frame_min = (min(frame_min, current_frame_duration)\n                 if frame_min else current_frame_duration)\n    frame_cumulative += current_frame_duration\n\nexpected_dm_frames = 25\nif total_frames != expected_dm_frames:\n  raise Exception(\n      'Got ' + str(total_frames) + ' frames instead of ' +\n      str(expected_dm_frames))\nperf_results['frame_max_us'] = frame_max\nperf_results['frame_min_us'] = frame_min\nperf_results['frame_avg_us'] = frame_cumulative/total_frames\n\n# Write perf_results to the output json.\nwith open(output_json_file, 'w') as f:\n  f.write(json.dumps(perf_results))\n",
+      "\n[{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":452,\"dur\":2.57,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPaint(const SkPaint &)\",\"ts\":473,\"dur\":2.67e+03,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.15e+03,\"dur\":2.25,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.15e+03,\"dur\":216,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPath(const SkPath &, const SkPaint &)\",\"ts\":3.35e+03,\"dur\":15.1,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.37e+03,\"dur\":1.17,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.37e+03,\"dur\":140,\"tid\":1,\"pid\":0}]\n",
+      "lottie(test)'!2.json",
+      "/path/to/tmp/json"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "parse lottie(test)'!2.json trace",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_avg_us\": 179.71, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_max_us\": 218.25, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_min_us\": 141.17@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import json@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_output = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_json = json.loads(trace_output)@@@",
+      "@@@STEP_LOG_LINE@python.inline@lottie_filename = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@output_json_file = sys.argv[3]@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results = {}@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_max = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_min = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_cumulative = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@current_frame_duration = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@total_frames = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@skipped_first_seek = False  # Skip the first seek constructor call.@@@",
+      "@@@STEP_LOG_LINE@python.inline@for trace in trace_json:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if 'skottie::Animation::seek' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not skipped_first_seek:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      skipped_first_seek = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@      continue@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got consecutive Animation::seek without a ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'render. Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration = trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@  elif 'skottie::Animation::render' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got an Animation::render without a seek first. ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration += trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@    total_frames += 1@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_max = max(frame_max, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_min = (min(frame_min, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@                 if frame_min else current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_cumulative += current_frame_duration@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@expected_dm_frames = 25@@@",
+      "@@@STEP_LOG_LINE@python.inline@if total_frames != expected_dm_frames:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception(@@@",
+      "@@@STEP_LOG_LINE@python.inline@      'Got ' + str(total_frames) + ' frames instead of ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@      str(expected_dm_frames))@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_max_us'] = frame_max@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_min_us'] = frame_min@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_avg_us'] = frame_cumulative/total_frames@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# Write perf_results to the output json.@@@",
+      "@@@STEP_LOG_LINE@python.inline@with open(output_json_file, 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  f.write(json.dumps(perf_results))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "/sdcard/revenge_of_the_skiabot/dm_out/3.json"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/dm_out/3.json"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --lotties /sdcard/revenge_of_the_skiabot/lotties --src lottie --nonativeFonts --verbose --traceMatch skottie --trace /sdcard/revenge_of_the_skiabot/dm_out/4.json --match \\^lottie1\\.json\\$ --config gles --nocpu; echo $? >/data/local/tmp/rc",
+      "[START_DIR]/tmp/dm.sh"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "write dm.sh (3)"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "push",
+      "[START_DIR]/tmp/dm.sh",
+      "/data/local/tmp/"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "push dm.sh (3)"
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "logcat",
+      "-c"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "clear log (3)"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport subprocess\nimport sys\nbin_dir = sys.argv[1]\nsh      = sys.argv[2]\nsubprocess.check_call(['/opt/infra-android/tools/adb', 'shell', 'sh', bin_dir + sh])\ntry:\n  sys.exit(int(subprocess.check_output(['/opt/infra-android/tools/adb', 'shell', 'cat',\n                                        bin_dir + 'rc'])))\nexcept ValueError:\n  print \"Couldn't read the return code.  Probably killed for OOM.\"\n  sys.exit(1)\n",
+      "/data/local/tmp/",
+      "dm.sh"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "dm (3)",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@bin_dir = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@sh      = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@subprocess.check_call(['/opt/infra-android/tools/adb', 'shell', 'sh', bin_dir + sh])@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(int(subprocess.check_output(['/opt/infra-android/tools/adb', 'shell', 'cat',@@@",
+      "@@@STEP_LOG_LINE@python.inline@                                        bin_dir + 'rc'])))@@@",
+      "@@@STEP_LOG_LINE@python.inline@except ValueError:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  print \"Couldn't read the return code.  Probably killed for OOM.\"@@@",
+      "@@@STEP_LOG_LINE@python.inline@  sys.exit(1)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/dm_out/4.json"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "read /sdcard/revenge_of_the_skiabot/dm_out/4.json",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport json\nimport sys\n\ntrace_output = sys.argv[1]\ntrace_json = json.loads(trace_output)\nlottie_filename = sys.argv[2]\noutput_json_file = sys.argv[3]\n\nperf_results = {}\nframe_max = 0\nframe_min = 0\nframe_cumulative = 0\ncurrent_frame_duration = 0\ntotal_frames = 0\nframe_start = False\nskipped_first_seek = False  # Skip the first seek constructor call.\nfor trace in trace_json:\n  if 'skottie::Animation::seek' in trace['name']:\n    if not skipped_first_seek:\n      skipped_first_seek = True\n      continue\n    if frame_start:\n      raise Exception('We got consecutive Animation::seek without a ' +\n                      'render. Something is wrong.')\n    frame_start = True\n    current_frame_duration = trace['dur']\n  elif 'skottie::Animation::render' in trace['name']:\n    if not frame_start:\n      raise Exception('We got an Animation::render without a seek first. ' +\n                      'Something is wrong.')\n\n    current_frame_duration += trace['dur']\n    frame_start = False\n    total_frames += 1\n    frame_max = max(frame_max, current_frame_duration)\n    frame_min = (min(frame_min, current_frame_duration)\n                 if frame_min else current_frame_duration)\n    frame_cumulative += current_frame_duration\n\nexpected_dm_frames = 25\nif total_frames != expected_dm_frames:\n  raise Exception(\n      'Got ' + str(total_frames) + ' frames instead of ' +\n      str(expected_dm_frames))\nperf_results['frame_max_us'] = frame_max\nperf_results['frame_min_us'] = frame_min\nperf_results['frame_avg_us'] = frame_cumulative/total_frames\n\n# Write perf_results to the output json.\nwith open(output_json_file, 'w') as f:\n  f.write(json.dumps(perf_results))\n",
+      "\n[{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":452,\"dur\":2.57,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPaint(const SkPaint &)\",\"ts\":473,\"dur\":2.67e+03,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.15e+03,\"dur\":2.25,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.15e+03,\"dur\":216,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void SkCanvas::drawPath(const SkPath &, const SkPaint &)\",\"ts\":3.35e+03,\"dur\":15.1,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::seek(SkScalar)\",\"ts\":3.37e+03,\"dur\":1.17,\"tid\":1,\"pid\":0},{\"ph\":\"X\",\"name\":\"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const\",\"ts\":3.37e+03,\"dur\":140,\"tid\":1,\"pid\":0}]\n",
+      "lottie1.json",
+      "/path/to/tmp/json"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "parse lottie1.json trace",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_avg_us\": 179.71, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_max_us\": 218.25, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"frame_min_us\": 141.17@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import json@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_output = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@trace_json = json.loads(trace_output)@@@",
+      "@@@STEP_LOG_LINE@python.inline@lottie_filename = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@output_json_file = sys.argv[3]@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results = {}@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_max = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_min = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_cumulative = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@current_frame_duration = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@total_frames = 0@@@",
+      "@@@STEP_LOG_LINE@python.inline@frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@skipped_first_seek = False  # Skip the first seek constructor call.@@@",
+      "@@@STEP_LOG_LINE@python.inline@for trace in trace_json:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if 'skottie::Animation::seek' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not skipped_first_seek:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      skipped_first_seek = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@      continue@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got consecutive Animation::seek without a ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'render. Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = True@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration = trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@  elif 'skottie::Animation::render' in trace['name']:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not frame_start:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise Exception('We got an Animation::render without a seek first. ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@                      'Something is wrong.')@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@    current_frame_duration += trace['dur']@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_start = False@@@",
+      "@@@STEP_LOG_LINE@python.inline@    total_frames += 1@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_max = max(frame_max, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_min = (min(frame_min, current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@                 if frame_min else current_frame_duration)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    frame_cumulative += current_frame_duration@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@expected_dm_frames = 25@@@",
+      "@@@STEP_LOG_LINE@python.inline@if total_frames != expected_dm_frames:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  raise Exception(@@@",
+      "@@@STEP_LOG_LINE@python.inline@      'Got ' + str(total_frames) + ' frames instead of ' +@@@",
+      "@@@STEP_LOG_LINE@python.inline@      str(expected_dm_frames))@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_max_us'] = frame_max@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_min_us'] = frame_min@@@",
+      "@@@STEP_LOG_LINE@python.inline@perf_results['frame_avg_us'] = frame_cumulative/total_frames@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# Write perf_results to the output json.@@@",
+      "@@@STEP_LOG_LINE@python.inline@with open(output_json_file, 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  f.write(json.dumps(perf_results))@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "/opt/infra-android/tools/adb",
+      "shell",
+      "rm",
+      "-f",
+      "/sdcard/revenge_of_the_skiabot/dm_out/4.json"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "rm /sdcard/revenge_of_the_skiabot/dm_out/4.json"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id (2)",
+    "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",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "makedirs perf_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import json\nwith open('[START_DIR]/[SWARM_OUT_DIR]/perf_abc123_1337000001.json', 'w') as outfile:\n  json.dump(obj={'gitHash': 'abc123', 'results': {'gles': {\"lottie(test)'!2.json\": {'frame_avg_us': 179.71, 'frame_max_us': 218.25, 'frame_min_us': 141.17}, 'lottie1.json': {'frame_avg_us': 179.71, 'frame_max_us': 218.25, 'frame_min_us': 141.17}, 'lottie 3!.json': {'frame_avg_us': 179.71, 'frame_max_us': 218.25, 'frame_min_us': 141.17}}}, 'patch_storage': 'gerrit', 'swarming_task_id': '', 'key': {'extra_config': 'Android_SkottieTracing', 'bench_type': 'tracing', 'cpu_or_gpu_value': 'Mali400MP2', 'arch': 'arm', 'source_type': 'skottie', 'cpu_or_gpu': 'GPU', 'model': 'AndroidOne', 'configuration': 'Release', 'os': 'Android', 'compiler': 'Clang'}, 'swarming_bot_id': '', 'patchset': 7, 'issue': 1234}, fp=outfile, indent=4)\n"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "write output JSON",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import json@@@",
+      "@@@STEP_LOG_LINE@python.inline@with open('[START_DIR]/[SWARM_OUT_DIR]/perf_abc123_1337000001.json', 'w') as outfile:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  json.dump(obj={'gitHash': 'abc123', 'results': {'gles': {\"lottie(test)'!2.json\": {'frame_avg_us': 179.71, 'frame_max_us': 218.25, 'frame_min_us': 141.17}, 'lottie1.json': {'frame_avg_us': 179.71, 'frame_max_us': 218.25, 'frame_min_us': 141.17}, 'lottie 3!.json': {'frame_avg_us': 179.71, 'frame_max_us': 218.25, 'frame_min_us': 141.17}}}, 'patch_storage': 'gerrit', 'swarming_task_id': '', 'key': {'extra_config': 'Android_SkottieTracing', 'bench_type': 'tracing', 'cpu_or_gpu_value': 'Mali400MP2', 'arch': 'arm', 'source_type': 'skottie', 'cpu_or_gpu': 'GPU', 'model': 'AndroidOne', 'configuration': 'Release', 'os': 'Android', 'compiler': 'Clang'}, 'swarming_bot_id': '', 'patchset': 7, 'issue': 1234}, fp=outfile, indent=4)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nout = sys.argv[1]\nlog = subprocess.check_output(['/opt/infra-android/tools/adb', 'logcat', '-d'])\nfor line in log.split('\\n'):\n  tokens = line.split()\n  if len(tokens) == 11 and tokens[-7] == 'F' and tokens[-3] == 'pc':\n    addr, path = tokens[-2:]\n    local = os.path.join(out, os.path.basename(path))\n    if os.path.exists(local):\n      sym = subprocess.check_output(['addr2line', '-Cfpe', local, addr])\n      line = line.replace(addr, addr + ' ' + sym.strip())\n  print line\n",
+      "[START_DIR]/build"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "dump log",
+    "timeout": 300,
+    "~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(['/opt/infra-android/tools/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": [
+      "/opt/infra-android/tools/adb",
+      "kill-server"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "kill adb server"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/perf_skottietrace.py b/infra/bots/recipes/perf_skottietrace.py
new file mode 100644
index 0000000..9192426
--- /dev/null
+++ b/infra/bots/recipes/perf_skottietrace.py
@@ -0,0 +1,345 @@
+# Copyright 2019 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 which runs DM with trace flag on lottie files and then parses the
+# trace output into output JSON files to ingest to perf.skia.org.
+# Design doc: go/skottie-tracing
+
+
+import calendar
+import re
+import string
+
+
+DEPS = [
+  'flavor',
+  'recipe_engine/context',
+  'recipe_engine/file',
+  'recipe_engine/json',
+  'recipe_engine/path',
+  'recipe_engine/step',
+  'recipe_engine/time',
+  'recipe_engine/properties',
+  'recipe_engine/python',
+  'recipe_engine/raw_io',
+  'run',
+  'vars',
+]
+
+SEEK_TRACE_NAME = 'skottie::Animation::seek'
+RENDER_TRACE_NAME = 'skottie::Animation::render'
+EXPECTED_DM_FRAMES = 25
+
+
+def perf_steps(api):
+  """Run DM on lottie files with tracing turned on and then parse the output."""
+  api.flavor.create_clean_device_dir(
+        api.flavor.device_dirs.dm_dir)
+
+  lottie_files = api.file.listdir(
+      'list lottie files', api.flavor.host_dirs.lotties_dir,
+      test_data=['lottie1.json', 'lottie(test)\'!2.json', 'lottie 3!.json',
+                 'LICENSE'])
+  perf_results = {}
+  push_dm = True
+  # Run DM on each lottie file and parse the trace files.
+  for idx, lottie_file in enumerate(lottie_files):
+    lottie_filename = api.path.basename(lottie_file)
+    if not lottie_filename.endswith('.json'):
+      continue
+
+    trace_output_path = api.flavor.device_path_join(
+        api.flavor.device_dirs.dm_dir, '%s.json' % (idx + 1))
+    # See go/skottie-tracing for how these flags were selected.
+    dm_args = [
+      'dm',
+      '--resourcePath', api.flavor.device_dirs.resource_dir,
+      '--lotties', api.flavor.device_dirs.lotties_dir,
+      '--src', 'lottie',
+      '--nonativeFonts',
+      '--verbose',
+      '--traceMatch', 'skottie',  # recipe can OOM without this.
+      '--trace', trace_output_path,
+      '--match', get_trace_match(
+          lottie_filename, 'Android' in api.properties['buildername']),
+    ]
+    if api.vars.builder_cfg.get('cpu_or_gpu') == 'GPU':
+      dm_args.extend(['--config', 'gles', '--nocpu'])
+    elif api.vars.builder_cfg.get('cpu_or_gpu') == 'CPU':
+      dm_args.extend(['--config', '8888', '--nogpu'])
+    api.run(api.flavor.step, 'dm', cmd=dm_args, abort_on_failure=False,
+            skip_binary_push=not push_dm)
+    # We already pushed the binary once. No need to waste time by pushing
+    # the same binary for future runs.
+    push_dm = False
+
+    trace_test_data = api.properties.get('trace_test_data', '{}')
+    trace_file_content = api.flavor.read_file_on_device(trace_output_path)
+    if not trace_file_content and trace_test_data:
+      trace_file_content = trace_test_data
+
+    perf_results[lottie_filename] = parse_trace(
+        trace_file_content, lottie_filename, api)
+    api.flavor.remove_file_on_device(trace_output_path)
+
+  # Construct contents of the output JSON.
+  perf_json = {
+      'gitHash': api.properties['revision'],
+      'swarming_bot_id': api.vars.swarming_bot_id,
+      'swarming_task_id': api.vars.swarming_task_id,
+      'key': {
+        'bench_type': 'tracing',
+        'source_type': 'skottie',
+      },
+      'results': {
+        'gles': perf_results,
+      },
+  }
+  if api.vars.is_trybot:
+    perf_json['issue'] = api.vars.issue
+    perf_json['patchset'] = api.vars.patchset
+    perf_json['patch_storage'] = api.vars.patch_storage
+  # Add tokens from the builder name to the key.
+  reg = re.compile('Perf-(?P<os>[A-Za-z0-9_]+)-'
+                   '(?P<compiler>[A-Za-z0-9_]+)-'
+                   '(?P<model>[A-Za-z0-9_]+)-'
+                   '(?P<cpu_or_gpu>[A-Z]+)-'
+                   '(?P<cpu_or_gpu_value>[A-Za-z0-9_]+)-'
+                   '(?P<arch>[A-Za-z0-9_]+)-'
+                   '(?P<configuration>[A-Za-z0-9_]+)-'
+                   'All(-(?P<extra_config>[A-Za-z0-9_]+)|)')
+  m = reg.match(api.properties['buildername'])
+  keys = ['os', 'compiler', 'model', 'cpu_or_gpu', 'cpu_or_gpu_value', 'arch',
+          'configuration', 'extra_config']
+  for k in keys:
+    perf_json['key'][k] = m.group(k)
+
+  # Create the output JSON file in perf_data_dir for the Upload task to upload.
+  api.file.ensure_directory(
+      'makedirs perf_dir',
+      api.flavor.host_dirs.perf_data_dir)
+  now = api.time.utcnow()
+  ts = int(calendar.timegm(now.utctimetuple()))
+  json_path = api.flavor.host_dirs.perf_data_dir.join(
+      'perf_%s_%d.json' % (api.properties['revision'], ts))
+  api.run(
+      api.python.inline,
+      'write output JSON',
+      program="""import json
+with open('%s', 'w') as outfile:
+  json.dump(obj=%s, fp=outfile, indent=4)
+  """ % (json_path, perf_json))
+
+
+def get_trace_match(lottie_filename, is_android):
+  """Returns the DM regex to match the specified lottie file name."""
+  trace_match = '^%s$' % lottie_filename
+  if is_android and ' ' not in trace_match:
+    # Punctuation characters confuse DM when shelled out over adb, so escape
+    # them. Do not need to do this when there is a space in the match because
+    # subprocess.list2cmdline automatically adds quotes in that case.
+    for sp_char in string.punctuation:
+      if sp_char == '\\':
+        # No need to escape the escape char.
+        continue
+      trace_match = trace_match.replace(sp_char, '\%s' % sp_char)
+  return trace_match
+
+
+def parse_trace(trace_json, lottie_filename, api):
+  """parse_trace parses the specified trace JSON.
+
+  Parses the trace JSON and calculates the time of a single frame. Frame time is
+  considered the same as seek time + render time.
+  Note: The first seek is ignored because it is a constructor call.
+
+  A dictionary is returned that has the following structure:
+  {
+    'frame_max_us': 100,
+    'frame_min_us': 90,
+    'frame_avg_us': 95,
+  }
+  """
+  step_result = api.run(
+      api.python.inline,
+      'parse %s trace' % lottie_filename,
+      program="""
+  import json
+  import sys
+
+  trace_output = sys.argv[1]
+  trace_json = json.loads(trace_output)
+  lottie_filename = sys.argv[2]
+  output_json_file = sys.argv[3]
+
+  perf_results = {}
+  frame_max = 0
+  frame_min = 0
+  frame_cumulative = 0
+  current_frame_duration = 0
+  total_frames = 0
+  frame_start = False
+  skipped_first_seek = False  # Skip the first seek constructor call.
+  for trace in trace_json:
+    if '%s' in trace['name']:
+      if not skipped_first_seek:
+        skipped_first_seek = True
+        continue
+      if frame_start:
+        raise Exception('We got consecutive Animation::seek without a ' +
+                        'render. Something is wrong.')
+      frame_start = True
+      current_frame_duration = trace['dur']
+    elif '%s' in trace['name']:
+      if not frame_start:
+        raise Exception('We got an Animation::render without a seek first. ' +
+                        'Something is wrong.')
+
+      current_frame_duration += trace['dur']
+      frame_start = False
+      total_frames += 1
+      frame_max = max(frame_max, current_frame_duration)
+      frame_min = (min(frame_min, current_frame_duration)
+                   if frame_min else current_frame_duration)
+      frame_cumulative += current_frame_duration
+
+  expected_dm_frames = %d
+  if total_frames != expected_dm_frames:
+    raise Exception(
+        'Got ' + str(total_frames) + ' frames instead of ' +
+        str(expected_dm_frames))
+  perf_results['frame_max_us'] = frame_max
+  perf_results['frame_min_us'] = frame_min
+  perf_results['frame_avg_us'] = frame_cumulative/total_frames
+
+  # Write perf_results to the output json.
+  with open(output_json_file, 'w') as f:
+    f.write(json.dumps(perf_results))
+  """ % (SEEK_TRACE_NAME, RENDER_TRACE_NAME, EXPECTED_DM_FRAMES),
+  args=[trace_json, lottie_filename, api.json.output()])
+
+  # Sanitize float outputs to 2 precision points.
+  output = dict(step_result.json.output)
+  output['frame_max_us'] = float("%.2f" % output['frame_max_us'])
+  output['frame_min_us'] = float("%.2f" % output['frame_min_us'])
+  output['frame_avg_us'] = float("%.2f" % output['frame_avg_us'])
+  return output
+
+
+def RunSteps(api):
+  api.vars.setup()
+  api.file.ensure_directory('makedirs tmp_dir', api.vars.tmp_dir)
+  api.flavor.setup()
+
+  with api.context():
+    try:
+      api.flavor.install(resources=True, lotties=True)
+      perf_steps(api)
+    finally:
+      api.flavor.cleanup_steps()
+    api.run.check_failure()
+
+
+def GenTests(api):
+  trace_output = """
+[{"ph":"X","name":"void skottie::Animation::seek(SkScalar)","ts":452,"dur":2.57,"tid":1,"pid":0},{"ph":"X","name":"void SkCanvas::drawPaint(const SkPaint &)","ts":473,"dur":2.67e+03,"tid":1,"pid":0},{"ph":"X","name":"void skottie::Animation::seek(SkScalar)","ts":3.15e+03,"dur":2.25,"tid":1,"pid":0},{"ph":"X","name":"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const","ts":3.15e+03,"dur":216,"tid":1,"pid":0},{"ph":"X","name":"void SkCanvas::drawPath(const SkPath &, const SkPaint &)","ts":3.35e+03,"dur":15.1,"tid":1,"pid":0},{"ph":"X","name":"void skottie::Animation::seek(SkScalar)","ts":3.37e+03,"dur":1.17,"tid":1,"pid":0},{"ph":"X","name":"void skottie::Animation::render(SkCanvas *, const SkRect *, RenderFlags) const","ts":3.37e+03,"dur":140,"tid":1,"pid":0}]
+"""
+  dm_json_test_data = """
+{
+  "gitHash": "bac53f089dbc473862bc5a2e328ba7600e0ed9c4",
+  "swarming_bot_id": "skia-rpi-094",
+  "swarming_task_id": "438f11c0e19eab11",
+  "key": {
+    "arch": "arm",
+    "compiler": "Clang",
+    "cpu_or_gpu": "GPU",
+    "cpu_or_gpu_value": "Mali400MP2",
+    "extra_config": "Android",
+    "model": "AndroidOne",
+    "os": "Android"
+   },
+   "results": {
+   }
+}
+"""
+  parse_trace_json = {
+      'frame_avg_us': 179.71,
+      'frame_min_us': 141.17,
+      'frame_max_us': 218.25
+  }
+  buildername = ('Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-'
+                  'All-Android_SkottieTracing')
+  cpu_buildername = ('Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-'
+                     'SkottieTracing')
+  yield (
+      api.test(buildername) +
+      api.properties(buildername=buildername,
+                     repository='https://skia.googlesource.com/skia.git',
+                     revision='abc123',
+                     task_id='abc123',
+                     trace_test_data=trace_output,
+                     dm_json_test_data=dm_json_test_data,
+                     path_config='kitchen',
+                     swarm_out_dir='[SWARM_OUT_DIR]') +
+      api.step_data('parse lottie(test)\'!2.json trace',
+                    api.json.output(parse_trace_json)) +
+      api.step_data('parse lottie1.json trace',
+                    api.json.output(parse_trace_json)) +
+      api.step_data('parse lottie 3!.json trace',
+                    api.json.output(parse_trace_json))
+  )
+  yield (
+      api.test(cpu_buildername) +
+      api.properties(buildername=cpu_buildername,
+                     repository='https://skia.googlesource.com/skia.git',
+                     revision='abc123',
+                     task_id='abc123',
+                     trace_test_data=trace_output,
+                     dm_json_test_data=dm_json_test_data,
+                     path_config='kitchen',
+                     swarm_out_dir='[SWARM_OUT_DIR]') +
+      api.step_data('parse lottie(test)\'!2.json trace',
+                    api.json.output(parse_trace_json)) +
+      api.step_data('parse lottie1.json trace',
+                    api.json.output(parse_trace_json)) +
+      api.step_data('parse lottie 3!.json trace',
+                    api.json.output(parse_trace_json))
+  )
+  yield (
+      api.test('skottietracing_parse_trace_error') +
+      api.properties(buildername=buildername,
+                     repository='https://skia.googlesource.com/skia.git',
+                     revision='abc123',
+                     task_id='abc123',
+                     trace_test_data=trace_output,
+                     dm_json_test_data=dm_json_test_data,
+                     path_config='kitchen',
+                     swarm_out_dir='[SWARM_OUT_DIR]') +
+      api.step_data('parse lottie 3!.json trace',
+                    api.json.output(parse_trace_json), retcode=1)
+  )
+  yield (
+      api.test('skottietracing_trybot') +
+      api.properties(buildername=buildername,
+                     repository='https://skia.googlesource.com/skia.git',
+                     revision='abc123',
+                     task_id='abc123',
+                     trace_test_data=trace_output,
+                     dm_json_test_data=dm_json_test_data,
+                     path_config='kitchen',
+                     swarm_out_dir='[SWARM_OUT_DIR]',
+                     patch_ref='89/456789/12',
+                     patch_repo='https://skia.googlesource.com/skia.git',
+                     patch_storage='gerrit',
+                     patch_set=7,
+                     patch_issue=1234,
+                     gerrit_project='skia',
+                     gerrit_url='https://skia-review.googlesource.com/') +
+      api.step_data('parse lottie(test)\'!2.json trace',
+                    api.json.output(parse_trace_json)) +
+      api.step_data('parse lottie1.json trace',
+                    api.json.output(parse_trace_json)) +
+      api.step_data('parse lottie 3!.json trace',
+                    api.json.output(parse_trace_json))
+  )
diff --git a/infra/bots/recipes/recreate_skps.expected/Housekeeper-Nightly-RecreateSKPs_Canary.json b/infra/bots/recipes/recreate_skps.expected/Housekeeper-Nightly-RecreateSKPs_Canary.json
index 0f4eedc..ffea749 100644
--- a/infra/bots/recipes/recreate_skps.expected/Housekeeper-Nightly-RecreateSKPs_Canary.json
+++ b/infra/bots/recipes/recreate_skps.expected/Housekeeper-Nightly-RecreateSKPs_Canary.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -97,7 +97,7 @@
     "cmd": [
       "python",
       "-u",
-      "RECIPE_PACKAGE_REPO[depot_tools]/gclient.py",
+      "RECIPE_REPO[depot_tools]/gclient.py",
       "runhooks"
     ],
     "cwd": "[START_DIR]/cache/work",
@@ -108,7 +108,7 @@
     },
     "env_suffixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "name": "gclient runhooks"
@@ -139,7 +139,7 @@
       "CHROME_HEADLESS": "1",
       "CPPFLAGS": "-DSK_ALLOW_CROSSPROCESS_PICTUREIMAGEFILTERS=1",
       "GYP_GENERATORS": "ninja",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "GN"
   },
@@ -153,7 +153,7 @@
     "cwd": "[START_DIR]/cache/work/src",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Build Chrome"
   },
@@ -199,7 +199,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Recreate SKPs"
   },
diff --git a/infra/bots/recipes/recreate_skps.expected/Housekeeper-Weekly-RecreateSKPs.json b/infra/bots/recipes/recreate_skps.expected/Housekeeper-Weekly-RecreateSKPs.json
index 0d56f30..b8f0d71 100644
--- a/infra/bots/recipes/recreate_skps.expected/Housekeeper-Weekly-RecreateSKPs.json
+++ b/infra/bots/recipes/recreate_skps.expected/Housekeeper-Weekly-RecreateSKPs.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -97,7 +97,7 @@
     "cmd": [
       "python",
       "-u",
-      "RECIPE_PACKAGE_REPO[depot_tools]/gclient.py",
+      "RECIPE_REPO[depot_tools]/gclient.py",
       "runhooks"
     ],
     "cwd": "[START_DIR]/cache/work",
@@ -108,7 +108,7 @@
     },
     "env_suffixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "name": "gclient runhooks"
@@ -139,7 +139,7 @@
       "CHROME_HEADLESS": "1",
       "CPPFLAGS": "-DSK_ALLOW_CROSSPROCESS_PICTUREIMAGEFILTERS=1",
       "GYP_GENERATORS": "ninja",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "GN"
   },
@@ -153,7 +153,7 @@
     "cwd": "[START_DIR]/cache/work/src",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Build Chrome"
   },
@@ -200,7 +200,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Recreate SKPs"
   },
@@ -219,7 +219,7 @@
       "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Upload SKPs"
   },
diff --git a/infra/bots/recipes/recreate_skps.expected/failed_upload.json b/infra/bots/recipes/recreate_skps.expected/failed_upload.json
index 22813b5..3a6c99c 100644
--- a/infra/bots/recipes/recreate_skps.expected/failed_upload.json
+++ b/infra/bots/recipes/recreate_skps.expected/failed_upload.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -97,7 +97,7 @@
     "cmd": [
       "python",
       "-u",
-      "RECIPE_PACKAGE_REPO[depot_tools]/gclient.py",
+      "RECIPE_REPO[depot_tools]/gclient.py",
       "runhooks"
     ],
     "cwd": "[START_DIR]/cache/work",
@@ -108,7 +108,7 @@
     },
     "env_suffixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "name": "gclient runhooks"
@@ -139,7 +139,7 @@
       "CHROME_HEADLESS": "1",
       "CPPFLAGS": "-DSK_ALLOW_CROSSPROCESS_PICTUREIMAGEFILTERS=1",
       "GYP_GENERATORS": "ninja",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "GN"
   },
@@ -153,7 +153,7 @@
     "cwd": "[START_DIR]/cache/work/src",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Build Chrome"
   },
@@ -200,7 +200,7 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Recreate SKPs"
   },
@@ -219,11 +219,10 @@
       "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Upload SKPs",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
diff --git a/infra/bots/recipes/skpbench.expected/Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_CCPR_Skpbench.json b/infra/bots/recipes/skpbench.expected/Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_CCPR_Skpbench.json
index 19f6966..c302564 100644
--- a/infra/bots/recipes/skpbench.expected/Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_CCPR_Skpbench.json
+++ b/infra/bots/recipes/skpbench.expected/Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_CCPR_Skpbench.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -84,7 +84,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -102,7 +102,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -119,7 +119,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -136,7 +136,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -151,7 +151,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -185,7 +185,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -215,7 +215,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push skpbench"
@@ -236,13 +236,14 @@
       "adb.1.0.35",
       "--pr",
       "ccpr",
+      "--cc",
       "--nocache",
       "/sdcard/revenge_of_the_skiabot/skps/desk_*svg.skp",
       "/sdcard/revenge_of_the_skiabot/skps/desk_chalkboard.skp"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "skpbench"
   },
@@ -295,7 +296,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Parse skpbench output into Perf json"
   },
@@ -308,7 +309,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -341,7 +342,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/skpbench.expected/Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench.json b/infra/bots/recipes/skpbench.expected/Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench.json
index 0ccd513..0931fbc 100644
--- a/infra/bots/recipes/skpbench.expected/Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench.json
+++ b/infra/bots/recipes/skpbench.expected/Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench.json
@@ -72,7 +72,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "skpbench"
   },
@@ -139,7 +139,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Parse skpbench output into Perf json"
   },
diff --git a/infra/bots/recipes/skpbench.expected/Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench_DDLRecord_9x9.json b/infra/bots/recipes/skpbench.expected/Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench_DDLRecord_9x9.json
index 3da65f8..dd863db 100644
--- a/infra/bots/recipes/skpbench.expected/Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench_DDLRecord_9x9.json
+++ b/infra/bots/recipes/skpbench.expected/Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench_DDLRecord_9x9.json
@@ -80,7 +80,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "skpbench"
   },
@@ -147,7 +147,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Parse skpbench output into Perf json"
   },
diff --git a/infra/bots/recipes/skpbench.expected/Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench_DDLTotal_9x9.json b/infra/bots/recipes/skpbench.expected/Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench_DDLTotal_9x9.json
index c6d3773..5bb5759 100644
--- a/infra/bots/recipes/skpbench.expected/Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench_DDLTotal_9x9.json
+++ b/infra/bots/recipes/skpbench.expected/Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench_DDLTotal_9x9.json
@@ -79,7 +79,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "skpbench"
   },
@@ -146,7 +146,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Parse skpbench output into Perf json"
   },
diff --git a/infra/bots/recipes/skpbench.expected/trybot.json b/infra/bots/recipes/skpbench.expected/trybot.json
index bb5e528..9caf986 100644
--- a/infra/bots/recipes/skpbench.expected/trybot.json
+++ b/infra/bots/recipes/skpbench.expected/trybot.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -84,7 +84,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -102,7 +102,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -119,7 +119,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -136,7 +136,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -151,7 +151,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -185,7 +185,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -215,7 +215,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push skpbench"
@@ -238,7 +238,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "skpbench"
   },
@@ -297,7 +297,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Parse skpbench output into Perf json"
   },
@@ -310,7 +310,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -343,7 +343,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/skpbench.py b/infra/bots/recipes/skpbench.py
index c0c54bd..09e4bac 100644
--- a/infra/bots/recipes/skpbench.py
+++ b/infra/bots/recipes/skpbench.py
@@ -77,8 +77,7 @@
         '--adb_binary', ADB_BINARY]
   if 'CCPR' in api.vars.builder_name:
     skpbench_args += [
-        '--pr', 'ccpr',
-        '--nocache',
+        '--pr', 'ccpr', '--cc', '--nocache',
         api.path.join(api.flavor.device_dirs.skp_dir, 'desk_*svg.skp'),
         api.path.join(api.flavor.device_dirs.skp_dir, 'desk_chalkboard.skp')]
   else:
diff --git a/infra/bots/recipes/skqp_test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-universal-devrel-All-Android_SKQP.json b/infra/bots/recipes/skqp_test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-universal-devrel-All-Android_SKQP.json
index f54082a..7612048 100644
--- a/infra/bots/recipes/skqp_test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-universal-devrel-All-Android_SKQP.json
+++ b/infra/bots/recipes/skqp_test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-universal-devrel-All-Android_SKQP.json
@@ -27,7 +27,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "run firebase testlab"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android.json
index a3a553c..3456df1 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -55,7 +55,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -117,7 +117,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -135,7 +135,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -152,7 +152,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -169,7 +169,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -184,7 +184,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -218,7 +218,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -262,7 +262,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -280,7 +280,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -297,7 +297,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -314,7 +314,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -329,7 +329,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -363,7 +363,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -407,7 +407,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -425,7 +425,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -442,7 +442,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -459,7 +459,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -474,7 +474,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -508,7 +508,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -553,7 +553,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/dm_out"
@@ -570,7 +570,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/dm_out"
@@ -585,7 +585,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -634,7 +634,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/uninteresting_hashes.txt /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt"
@@ -664,7 +664,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 0's governor to hotplug",
@@ -706,7 +706,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -719,12 +719,12 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android buildbucket_build_id 123454321 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Release cpu_or_gpu GPU cpu_or_gpu_value Mali400MP2 extra_config Android model AndroidOne os Android --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --config gles glesdft glessrgb glesmsaa4 --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ 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 _ gm _ bigblurs _ gm _ bleed _ gm _ bleed_alpha_bmp _ gm _ bleed_alpha_bmp_shader _ gm _ bleed_alpha_image _ gm _ bleed_alpha_image_shader _ gm _ bleed_image _ gm _ dropshadowimagefilter _ gm _ filterfastbounds gles gm _ imageblurtiled _ gm _ imagefiltersclipped _ gm _ imagefiltersscaled _ gm _ imageresizetiled _ gm _ matrixconvolution _ gm _ strokedlines glesmsaa4 gm _ imageblurtiled glesmsaa4 gm _ imagefiltersbase --match ~WritePixels ~PremulAlphaRoundTrip_Gpu ~ReimportImageTextureWithMipLevels --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android buildbucket_build_id 123454321 task_id task_12345 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Release cpu_or_gpu GPU cpu_or_gpu_value Mali400MP2 extra_config Android model AndroidOne os Android style default --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --config gles glesdft glessrgb glesmsaa4 --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ 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 _ gm _ bigblurs _ gm _ bleed _ gm _ bleed_alpha_bmp _ gm _ bleed_alpha_bmp_shader _ gm _ bleed_alpha_image _ gm _ bleed_alpha_image_shader _ gm _ bleed_image _ gm _ dropshadowimagefilter _ gm _ filterfastbounds gles gm _ imageblurtiled _ gm _ imagefiltersclipped _ gm _ imagefiltersscaled _ gm _ imageresizetiled _ gm _ matrixconvolution _ gm _ strokedlines glesmsaa4 gm _ imageblurtiled glesmsaa4 gm _ imagefiltersbase --match ~WritePixels ~PremulAlphaRoundTrip_Gpu ~ReimportImageTextureWithMipLevels --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "write dm.sh"
@@ -740,7 +740,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm.sh"
@@ -755,7 +755,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -770,7 +770,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm",
     "~followup_annotations": [
@@ -804,7 +804,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out",
@@ -892,7 +892,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -925,7 +925,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android.json
index b101e578..570f8cc 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -55,7 +55,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -117,7 +117,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -135,7 +135,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -152,7 +152,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -169,7 +169,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -184,7 +184,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -218,7 +218,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -262,7 +262,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -280,7 +280,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -297,7 +297,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -314,7 +314,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -329,7 +329,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -363,7 +363,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -407,7 +407,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -425,7 +425,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -442,7 +442,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -459,7 +459,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -474,7 +474,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -508,7 +508,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -553,7 +553,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/dm_out"
@@ -570,7 +570,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/dm_out"
@@ -585,7 +585,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -634,7 +634,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/uninteresting_hashes.txt /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt"
@@ -664,7 +664,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -677,12 +677,12 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android buildbucket_build_id 123454321 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm64 compiler Clang configuration Debug cpu_or_gpu GPU cpu_or_gpu_value MaliT760 extra_config Android model GalaxyS6 os Android --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --config gles glesdft glessrgb glesmsaa4 --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ 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 ~SpecialImage ~skbug6653 --nonativeFonts --reduceOpListSplitting --verbose; echo $? >/data/local/tmp/rc",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android buildbucket_build_id 123454321 task_id task_12345 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm64 compiler Clang configuration Debug cpu_or_gpu GPU cpu_or_gpu_value MaliT760 extra_config Android model GalaxyS6 os Android style default --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --config gles glesdft glessrgb glesmsaa4 --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ 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 ~SpecialImage ~skbug6653 --nonativeFonts --reduceOpListSplitting --verbose; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "write dm.sh"
@@ -698,7 +698,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm.sh"
@@ -713,7 +713,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -728,7 +728,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm",
     "~followup_annotations": [
@@ -762,7 +762,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out",
@@ -850,7 +850,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -883,7 +883,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android_NoGPUThreads.json b/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android_NoGPUThreads.json
index b09c6e5..90e30d4 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android_NoGPUThreads.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android_NoGPUThreads.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -55,7 +55,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -117,7 +117,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -135,7 +135,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -152,7 +152,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -169,7 +169,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -184,7 +184,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -218,7 +218,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -262,7 +262,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -280,7 +280,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -297,7 +297,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -314,7 +314,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -329,7 +329,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -363,7 +363,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -407,7 +407,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -425,7 +425,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -442,7 +442,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -459,7 +459,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -474,7 +474,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -508,7 +508,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -553,7 +553,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/dm_out"
@@ -570,7 +570,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/dm_out"
@@ -585,7 +585,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -634,7 +634,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/uninteresting_hashes.txt /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt"
@@ -664,7 +664,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -677,12 +677,12 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android_NoGPUThreads buildbucket_build_id 123454321 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm64 compiler Clang configuration Debug cpu_or_gpu GPU cpu_or_gpu_value MaliT760 extra_config Android_NoGPUThreads model GalaxyS6 os Android --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --config gles glesdft glessrgb glesmsaa4 --src tests gm image colorImage svg --gpuThreads 0 --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ 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 ~SpecialImage ~skbug6653 --nonativeFonts --reduceOpListSplitting --verbose; echo $? >/data/local/tmp/rc",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android_NoGPUThreads buildbucket_build_id 123454321 task_id task_12345 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm64 compiler Clang configuration Debug cpu_or_gpu GPU cpu_or_gpu_value MaliT760 extra_config Android_NoGPUThreads model GalaxyS6 os Android style default --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --config gles glesdft glessrgb glesmsaa4 --src tests gm image colorImage svg --gpuThreads 0 --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ 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 ~SpecialImage ~skbug6653 --nonativeFonts --reduceOpListSplitting --verbose; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "write dm.sh"
@@ -698,7 +698,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm.sh"
@@ -713,7 +713,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -728,7 +728,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm",
     "~followup_annotations": [
@@ -762,7 +762,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out",
@@ -850,7 +850,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -883,7 +883,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan.json b/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan.json
index bc1a833..d38ffe2 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -55,7 +55,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -117,7 +117,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -135,7 +135,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -152,7 +152,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -169,7 +169,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -184,7 +184,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -218,7 +218,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -262,7 +262,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -280,7 +280,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -297,7 +297,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -314,7 +314,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -329,7 +329,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -363,7 +363,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -407,7 +407,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -425,7 +425,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -442,7 +442,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -459,7 +459,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -474,7 +474,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -508,7 +508,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -553,7 +553,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/dm_out"
@@ -570,7 +570,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/dm_out"
@@ -585,7 +585,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -634,7 +634,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/uninteresting_hashes.txt /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt"
@@ -664,7 +664,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -677,12 +677,12 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan buildbucket_build_id 123454321 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm64 compiler Clang configuration Release cpu_or_gpu GPU cpu_or_gpu_value MaliT880 extra_config Android_Vulkan model GalaxyS7_G930FD os Android --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --config vk --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ _ image gen_platf error _ test _ GrShape _ image _ interlaced1.png _ image _ interlaced2.png _ image _ interlaced3.png _ image _ .arw _ image _ .cr2 _ image _ .dng _ image _ .nef _ image _ .nrw _ image _ .orf _ image _ .raf _ image _ .rw2 _ image _ .pef _ image _ .srw _ image _ .ARW _ image _ .CR2 _ image _ .DNG _ image _ .NEF _ image _ .NRW _ image _ .ORF _ image _ .RAF _ image _ .RW2 _ image _ .PEF _ image _ .SRW --nonativeFonts --reduceOpListSplitting --verbose; echo $? >/data/local/tmp/rc",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan buildbucket_build_id 123454321 task_id task_12345 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm64 compiler Clang configuration Release cpu_or_gpu GPU cpu_or_gpu_value MaliT880 extra_config Android_Vulkan model GalaxyS7_G930FD os Android style default --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --config vk --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ _ image gen_platf error _ test _ GrShape _ image _ interlaced1.png _ image _ interlaced2.png _ image _ interlaced3.png _ image _ .arw _ image _ .cr2 _ image _ .dng _ image _ .nef _ image _ .nrw _ image _ .orf _ image _ .raf _ image _ .rw2 _ image _ .pef _ image _ .srw _ image _ .ARW _ image _ .CR2 _ image _ .DNG _ image _ .NEF _ image _ .NRW _ image _ .ORF _ image _ .RAF _ image _ .RW2 _ image _ .PEF _ image _ .SRW --nonativeFonts --reduceOpListSplitting --verbose; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "write dm.sh"
@@ -698,7 +698,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm.sh"
@@ -713,7 +713,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -728,7 +728,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm",
     "~followup_annotations": [
@@ -762,7 +762,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out",
@@ -850,7 +850,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -883,7 +883,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android.json
index 03ce407..7705af2 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -55,7 +55,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -117,7 +117,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -135,7 +135,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -152,7 +152,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -169,7 +169,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -184,7 +184,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -218,7 +218,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -262,7 +262,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -280,7 +280,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -297,7 +297,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -314,7 +314,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -329,7 +329,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -363,7 +363,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -407,7 +407,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -425,7 +425,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -442,7 +442,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -459,7 +459,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -474,7 +474,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -508,7 +508,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -553,7 +553,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/dm_out"
@@ -570,7 +570,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/dm_out"
@@ -585,7 +585,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -634,7 +634,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/uninteresting_hashes.txt /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt"
@@ -664,7 +664,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -677,12 +677,12 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android buildbucket_build_id 123454321 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Release cpu_or_gpu CPU cpu_or_gpu_value Snapdragon617 extra_config Android model MotoG4 os Android --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --threads 0 --nogpu --config 8888 --src tests gm image colorImage --blacklist _ image gen_platf error _ test _ GrShape --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android buildbucket_build_id 123454321 task_id task_12345 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Release cpu_or_gpu CPU cpu_or_gpu_value Snapdragon617 extra_config Android model MotoG4 os Android style default --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --threads 0 --nogpu --config 8888 --src tests gm image colorImage --blacklist _ image gen_platf error _ test _ GrShape --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "write dm.sh"
@@ -698,7 +698,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm.sh"
@@ -713,7 +713,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -728,7 +728,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm",
     "~followup_annotations": [
@@ -762,7 +762,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out",
@@ -850,7 +850,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -883,7 +883,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_CCPR.json b/infra/bots/recipes/test.expected/Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_CCPR.json
index b683676..aaf582a 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_CCPR.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_CCPR.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -55,7 +55,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -117,7 +117,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -135,7 +135,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -152,7 +152,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -169,7 +169,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -184,7 +184,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -218,7 +218,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -262,7 +262,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -280,7 +280,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -297,7 +297,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -314,7 +314,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -329,7 +329,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -363,7 +363,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -407,7 +407,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -425,7 +425,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -442,7 +442,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -459,7 +459,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -474,7 +474,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -508,7 +508,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -553,7 +553,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/dm_out"
@@ -570,7 +570,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/dm_out"
@@ -585,7 +585,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -634,7 +634,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/uninteresting_hashes.txt /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt"
@@ -664,7 +664,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -677,12 +677,12 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_CCPR buildbucket_build_id 123454321 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm64 compiler Clang configuration Debug cpu_or_gpu GPU cpu_or_gpu_value TegraX1 extra_config Android_CCPR model NVIDIA_Shield os Android --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --pr ccpr --cachePathMasks false --config gl --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ _ image gen_platf error _ test _ GrShape _ image _ interlaced1.png _ image _ interlaced2.png _ image _ interlaced3.png _ image _ .arw _ image _ .cr2 _ image _ .dng _ image _ .nef _ image _ .nrw _ image _ .orf _ image _ .raf _ image _ .rw2 _ image _ .pef _ image _ .srw _ image _ .ARW _ image _ .CR2 _ image _ .DNG _ image _ .NEF _ image _ .NRW _ image _ .ORF _ image _ .RAF _ image _ .RW2 _ image _ .PEF _ image _ .SRW --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_CCPR buildbucket_build_id 123454321 task_id task_12345 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm64 compiler Clang configuration Debug cpu_or_gpu GPU cpu_or_gpu_value TegraX1 extra_config Android_CCPR model NVIDIA_Shield os Android style default --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --pr ccpr --cc true --cachePathMasks false --config gl --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ _ image gen_platf error _ test _ GrShape _ image _ interlaced1.png _ image _ interlaced2.png _ image _ interlaced3.png _ image _ .arw _ image _ .cr2 _ image _ .dng _ image _ .nef _ image _ .nrw _ image _ .orf _ image _ .raf _ image _ .rw2 _ image _ .pef _ image _ .srw _ image _ .ARW _ image _ .CR2 _ image _ .DNG _ image _ .NEF _ image _ .NRW _ image _ .ORF _ image _ .RAF _ image _ .RW2 _ image _ .PEF _ image _ .SRW --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "write dm.sh"
@@ -698,7 +698,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm.sh"
@@ -713,7 +713,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -728,7 +728,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm",
     "~followup_annotations": [
@@ -762,7 +762,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out",
@@ -850,7 +850,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -883,7 +883,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android.json
index b25230d..a077eda 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -55,7 +55,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -117,7 +117,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -135,7 +135,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -152,7 +152,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -169,7 +169,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -184,7 +184,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -218,7 +218,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -262,7 +262,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -280,7 +280,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -297,7 +297,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -314,7 +314,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -329,7 +329,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -363,7 +363,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -407,7 +407,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -425,7 +425,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -442,7 +442,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -459,7 +459,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -474,7 +474,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -508,7 +508,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -553,7 +553,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/dm_out"
@@ -570,7 +570,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/dm_out"
@@ -585,7 +585,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -634,7 +634,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/uninteresting_hashes.txt /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt"
@@ -664,7 +664,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 0's governor to ondemand",
@@ -706,7 +706,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -719,12 +719,12 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android buildbucket_build_id 123454321 swarming_bot_id skia-bot-123 swarming_task_id 123456 --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 --dont_write pdf --nocpu --config gles glesdft glessrgb glesmsaa4 --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ 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 _ gm _ encode-platform --noRAW_threading --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android buildbucket_build_id 123454321 task_id task_12345 swarming_bot_id skia-bot-123 swarming_task_id 123456 --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 style default --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --config gles glesdft glessrgb glesmsaa4 --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ 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 _ gm _ encode-platform --noRAW_threading --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "write dm.sh"
@@ -740,7 +740,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm.sh"
@@ -755,7 +755,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -770,7 +770,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm",
     "~followup_annotations": [
@@ -804,7 +804,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out",
@@ -892,7 +892,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -925,7 +925,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android.json
index b242535..fc3bab7 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -55,7 +55,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -117,7 +117,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -135,7 +135,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -152,7 +152,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -169,7 +169,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -184,7 +184,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -218,7 +218,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -262,7 +262,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -280,7 +280,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -297,7 +297,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -314,7 +314,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -329,7 +329,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -363,7 +363,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -407,7 +407,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -425,7 +425,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -442,7 +442,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -459,7 +459,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -474,7 +474,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -508,7 +508,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -553,7 +553,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/dm_out"
@@ -570,7 +570,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/dm_out"
@@ -585,7 +585,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -634,7 +634,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/uninteresting_hashes.txt /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt"
@@ -664,7 +664,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 0's governor to ondemand",
@@ -706,7 +706,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -719,12 +719,12 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android buildbucket_build_id 123454321 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Release cpu_or_gpu CPU cpu_or_gpu_value Tegra3 extra_config Android model Nexus7 os Android --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --threads 2 --nogpu --config 8888 --src tests gm image colorImage --blacklist _ image gen_platf error _ test _ GrShape --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android buildbucket_build_id 123454321 task_id task_12345 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Release cpu_or_gpu CPU cpu_or_gpu_value Tegra3 extra_config Android model Nexus7 os Android style default --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --threads 0 --nogpu --config 8888 --src tests gm image colorImage --blacklist _ image gen_platf error _ test _ GrShape --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "write dm.sh"
@@ -740,7 +740,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm.sh"
@@ -755,7 +755,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -770,7 +770,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm",
     "~followup_annotations": [
@@ -804,7 +804,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out",
@@ -892,7 +892,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -925,7 +925,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android.json
index 1419c09..4a7dbb3 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -55,7 +55,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -117,7 +117,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -135,7 +135,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -152,7 +152,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -169,7 +169,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -184,7 +184,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -218,7 +218,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -262,7 +262,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -280,7 +280,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -297,7 +297,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -314,7 +314,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -329,7 +329,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -363,7 +363,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -407,7 +407,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -425,7 +425,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -442,7 +442,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -459,7 +459,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -474,7 +474,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -508,7 +508,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -553,7 +553,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/dm_out"
@@ -570,7 +570,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/dm_out"
@@ -585,7 +585,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -634,7 +634,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/uninteresting_hashes.txt /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt"
@@ -664,7 +664,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 0's governor to ondemand",
@@ -706,7 +706,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 2's governor to ondemand",
@@ -748,7 +748,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -761,12 +761,12 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android buildbucket_build_id 123454321 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch x86 compiler Clang configuration Release cpu_or_gpu GPU cpu_or_gpu_value PowerVRG6430 extra_config Android model NexusPlayer os Android --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --ignoreSigInt --nocpu --config gles glessrgb --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ 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 --noRAW_threading --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android buildbucket_build_id 123454321 task_id task_12345 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch x86 compiler Clang configuration Release cpu_or_gpu GPU cpu_or_gpu_value PowerVRG6430 extra_config Android model NexusPlayer os Android style default --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --threads 2 --ignoreSigInt --nocpu --config gles glessrgb --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ 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 --noRAW_threading --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "write dm.sh"
@@ -782,7 +782,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm.sh"
@@ -797,7 +797,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -812,7 +812,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm",
     "~followup_annotations": [
@@ -846,7 +846,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out",
@@ -934,7 +934,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -967,7 +967,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel-GPU-Adreno530-arm-Debug-All-Android_ASAN.json b/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel-GPU-Adreno530-arm-Debug-All-Android_ASAN.json
index 2fff380..aa5430d 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel-GPU-Adreno530-arm-Debug-All-Android_ASAN.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel-GPU-Adreno530-arm-Debug-All-Android_ASAN.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -55,7 +55,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Setting up device to run ASAN",
@@ -145,7 +145,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -207,7 +207,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -225,7 +225,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -242,7 +242,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -259,7 +259,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -274,7 +274,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -308,7 +308,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -352,7 +352,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -370,7 +370,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -387,7 +387,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -404,7 +404,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -419,7 +419,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -453,7 +453,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -497,7 +497,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -515,7 +515,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -532,7 +532,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -549,7 +549,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -564,7 +564,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -598,7 +598,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -628,7 +628,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Enabling CPU 0",
@@ -678,7 +678,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Enabling CPU 1",
@@ -728,7 +728,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 2's governor to ondemand",
@@ -770,7 +770,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 0's governor to ondemand",
@@ -812,7 +812,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -825,12 +825,12 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-Pixel-GPU-Adreno530-arm-Debug-All-Android_ASAN buildbucket_build_id 123454321 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Debug cpu_or_gpu GPU cpu_or_gpu_value Adreno530 extra_config Android_ASAN model Pixel os Android --dont_write pdf --nocpu --config gles glesdft glessrgb glesmsaa4 --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ 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 ~BadImage --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-Pixel-GPU-Adreno530-arm-Debug-All-Android_ASAN buildbucket_build_id 123454321 task_id task_12345 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Debug cpu_or_gpu GPU cpu_or_gpu_value Adreno530 extra_config Android_ASAN model Pixel os Android style default --dont_write pdf --nocpu --config gles glesdft glessrgb glesmsaa4 --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ 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 ~BadImage --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "write dm.sh"
@@ -846,7 +846,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm.sh"
@@ -861,7 +861,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -876,7 +876,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm",
     "~followup_annotations": [
@@ -902,7 +902,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "wait for device before uninstalling ASAN",
@@ -915,7 +915,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "uninstall ASAN",
@@ -930,7 +930,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -963,7 +963,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_Vulkan.json b/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_Vulkan.json
index f216477..4ea6999 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_Vulkan.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_Vulkan.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -55,7 +55,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -117,7 +117,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -135,7 +135,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -152,7 +152,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -169,7 +169,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -184,7 +184,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -218,7 +218,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -262,7 +262,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -280,7 +280,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -297,7 +297,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -314,7 +314,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -329,7 +329,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -363,7 +363,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -407,7 +407,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -425,7 +425,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -442,7 +442,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -459,7 +459,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -474,7 +474,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -508,7 +508,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -553,7 +553,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/dm_out"
@@ -570,7 +570,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/dm_out"
@@ -585,7 +585,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -634,7 +634,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/uninteresting_hashes.txt /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt"
@@ -664,7 +664,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Enabling CPU 0",
@@ -714,7 +714,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Enabling CPU 1",
@@ -764,7 +764,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 2's governor to ondemand",
@@ -806,7 +806,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 0's governor to ondemand",
@@ -848,7 +848,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -861,12 +861,12 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_Vulkan buildbucket_build_id 123454321 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm64 compiler Clang configuration Debug cpu_or_gpu GPU cpu_or_gpu_value Adreno530 extra_config Android_Vulkan model Pixel os Android --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --config vk --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ _ image gen_platf error _ test _ GrShape _ 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 ~WritePixelsNonTextureMSAA_Gpu ~WritePixelsMSAA_Gpu --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_Vulkan buildbucket_build_id 123454321 task_id task_12345 swarming_bot_id skia-bot-123 swarming_task_id 123456 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm64 compiler Clang configuration Debug cpu_or_gpu GPU cpu_or_gpu_value Adreno530 extra_config Android_Vulkan model Pixel os Android style default --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --config vk --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ _ image gen_platf error _ test _ GrShape _ 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 ~WritePixelsNonTextureMSAA_Gpu ~WritePixelsMSAA_Gpu --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "write dm.sh"
@@ -882,7 +882,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm.sh"
@@ -897,7 +897,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -912,7 +912,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm",
     "~followup_annotations": [
@@ -946,7 +946,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out",
@@ -1034,7 +1034,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -1067,7 +1067,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/test.expected/Test-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Debug-All.json b/infra/bots/recipes/test.expected/Test-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Debug-All.json
index 346c870..aa05eb6 100644
--- a/infra/bots/recipes/test.expected/Test-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Debug-All.json
+++ b/infra/bots/recipes/test.expected/Test-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Debug-All.json
@@ -22,7 +22,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read chromeos ip",
@@ -50,7 +50,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/resources"
@@ -72,7 +72,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "remount /home/chronos/user/ as exec"
@@ -91,7 +91,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/bin"
@@ -110,7 +110,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/bin"
@@ -176,7 +176,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /home/chronos/user/SKP_VERSION",
@@ -196,7 +196,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/SKP_VERSION"
@@ -215,7 +215,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/skps"
@@ -234,7 +234,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/skps"
@@ -320,7 +320,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /home/chronos/user/SK_IMAGE_VERSION",
@@ -340,7 +340,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/SK_IMAGE_VERSION"
@@ -359,7 +359,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/images"
@@ -378,7 +378,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/images"
@@ -464,7 +464,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /home/chronos/user/SVG_VERSION",
@@ -484,7 +484,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/SVG_VERSION"
@@ -503,7 +503,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/svgs"
@@ -522,7 +522,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/svgs"
@@ -609,7 +609,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /home/chronos/user/dm_out"
@@ -628,7 +628,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /home/chronos/user/dm_out"
@@ -643,7 +643,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -741,7 +741,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rmtree [SWARM_OUT_DIR]"
@@ -760,7 +760,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "makedirs [SWARM_OUT_DIR]"
@@ -775,7 +775,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "scp [START_DIR]/build/dm foo@127.0.0.1:/home/chronos/user/bin/dm",
@@ -803,7 +803,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "chmod dm"
@@ -833,6 +833,8 @@
       "Test-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Debug-All",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -854,6 +856,8 @@
       "AcerChromebookR13Convertible",
       "os",
       "ChromeOS",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "/home/chronos/user/uninteresting_hashes.txt",
       "--writePath",
@@ -978,7 +982,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dm"
diff --git a/infra/bots/recipes/test.expected/Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All.json b/infra/bots/recipes/test.expected/Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All.json
index c224a72..b1a506b 100644
--- a/infra/bots/recipes/test.expected/Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All.json
+++ b/infra/bots/recipes/test.expected/Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All.json
@@ -36,7 +36,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read chromecast ip",
@@ -58,7 +58,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb connect 192.168.1.2:5555"
@@ -74,7 +74,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /cache/skia/resources"
@@ -90,7 +90,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /cache/skia/bin"
@@ -105,7 +105,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /cache/skia/resources",
@@ -169,7 +169,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /cache/skia/SKP_VERSION",
@@ -186,7 +186,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /cache/skia/SKP_VERSION"
@@ -202,7 +202,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /cache/skia/skps"
@@ -218,7 +218,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /cache/skia/skps"
@@ -233,7 +233,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /cache/skia/skps",
@@ -269,7 +269,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /cache/skia/SKP_VERSION"
@@ -313,7 +313,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /dev/shm/skia/dm_out"
@@ -329,7 +329,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /dev/shm/skia/dm_out"
@@ -344,7 +344,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -392,7 +392,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/uninteresting_hashes.txt /cache/skia/uninteresting_hashes.txt"
@@ -421,7 +421,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -448,6 +448,8 @@
       "Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -467,6 +469,8 @@
       "Chorizo",
       "os",
       "Chromecast",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "/cache/skia/uninteresting_hashes.txt",
       "--writePath",
@@ -494,6 +498,10 @@
       "test",
       "_",
       "GrShape",
+      "_",
+      "gm",
+      "_",
+      "compositor_quads_filter",
       "--match",
       "~bigbitmaprect_",
       "~DrawBitmapRect",
@@ -508,7 +516,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm"
   },
@@ -526,7 +534,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /dev/shm/skia/dm_out",
@@ -618,7 +626,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Delete executables"
@@ -631,7 +639,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "disconnect"
@@ -644,7 +652,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb connect 192.168.1.2:5555 (2)"
@@ -658,7 +666,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -689,7 +697,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "disconnect (2)"
@@ -702,7 +710,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/test.expected/Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All.json b/infra/bots/recipes/test.expected/Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All.json
index f1e29e8..a23bc95 100644
--- a/infra/bots/recipes/test.expected/Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All.json
+++ b/infra/bots/recipes/test.expected/Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All.json
@@ -36,7 +36,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read chromecast ip",
@@ -58,7 +58,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb connect 192.168.1.2:5555"
@@ -74,7 +74,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /cache/skia/resources"
@@ -90,7 +90,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /cache/skia/bin"
@@ -105,7 +105,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /cache/skia/resources",
@@ -169,7 +169,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /cache/skia/SKP_VERSION",
@@ -186,7 +186,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /cache/skia/SKP_VERSION"
@@ -202,7 +202,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /cache/skia/skps"
@@ -218,7 +218,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /cache/skia/skps"
@@ -233,7 +233,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /cache/skia/skps",
@@ -269,7 +269,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /cache/skia/SKP_VERSION"
@@ -313,7 +313,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /dev/shm/skia/dm_out"
@@ -329,7 +329,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /dev/shm/skia/dm_out"
@@ -344,7 +344,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -392,7 +392,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/uninteresting_hashes.txt /cache/skia/uninteresting_hashes.txt"
@@ -421,7 +421,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -448,6 +448,8 @@
       "Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -467,6 +469,8 @@
       "Chorizo",
       "os",
       "Chromecast",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "/cache/skia/uninteresting_hashes.txt",
       "--writePath",
@@ -591,6 +595,10 @@
       "image",
       "_",
       ".SRW",
+      "_",
+      "gm",
+      "_",
+      "compositor_quads_filter",
       "--match",
       "~animated-image-blurs",
       "~blur_0.01",
@@ -615,7 +623,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm"
   },
@@ -633,7 +641,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /dev/shm/skia/dm_out",
@@ -725,7 +733,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Delete executables"
@@ -738,7 +746,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "disconnect"
@@ -751,7 +759,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb connect 192.168.1.2:5555 (2)"
@@ -765,7 +773,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -796,7 +804,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "disconnect (2)"
@@ -809,7 +817,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN.json
index 3fff888..37c0a27 100644
--- a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN.json
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN.json
@@ -149,6 +149,8 @@
       "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -172,6 +174,8 @@
       "GCE",
       "os",
       "Debian9",
+      "style",
+      "default",
       "--dont_write",
       "pdf",
       "--randomProcessorTest",
@@ -185,7 +189,6 @@
       "tiles_rt-8888",
       "lite-8888",
       "serialize-8888",
-      "gbr-8888",
       "f16",
       "srgb",
       "esrgb",
@@ -210,14 +213,6 @@
       "_",
       "_",
       "dstreadshuffle",
-      "gbr-8888",
-      "image",
-      "_",
-      "_",
-      "gbr-8888",
-      "colorImage",
-      "_",
-      "_",
       "g8",
       "image",
       "_",
@@ -373,6 +368,10 @@
       "serialize-8888",
       "gm",
       "_",
+      "compositor_quads_shader",
+      "serialize-8888",
+      "gm",
+      "_",
       "analytic_antialias_convex",
       "serialize-8888",
       "gm",
@@ -467,7 +466,7 @@
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/clang_linux/lib",
       "LSAN_OPTIONS": "symbolize=1 print_suppressions=1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]:[START_DIR]/clang_linux/bin",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]:[START_DIR]/clang_linux/bin",
       "UBSAN_OPTIONS": "symbolize=1 print_stacktrace=1"
     },
     "name": "symbolized dm"
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs.json
index 5022ad3..9f08685 100644
--- a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs.json
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -226,6 +226,8 @@
       "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -249,6 +251,8 @@
       "GCE",
       "os",
       "Debian9",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]/tmp/uninteresting_hashes.txt",
       "--writePath",
@@ -266,7 +270,6 @@
       "tiles_rt-8888",
       "lite-8888",
       "serialize-8888",
-      "gbr-8888",
       "f16",
       "srgb",
       "esrgb",
@@ -286,14 +289,6 @@
       "_",
       "_",
       "dstreadshuffle",
-      "gbr-8888",
-      "image",
-      "_",
-      "_",
-      "gbr-8888",
-      "colorImage",
-      "_",
-      "_",
       "g8",
       "image",
       "_",
@@ -449,6 +444,10 @@
       "serialize-8888",
       "gm",
       "_",
+      "compositor_quads_shader",
+      "serialize-8888",
+      "gm",
+      "_",
       "analytic_antialias_convex",
       "serialize-8888",
       "gm",
@@ -540,7 +539,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "symbolized dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN.json
index 2a42a84..746d91f 100644
--- a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN.json
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN.json
@@ -149,6 +149,8 @@
       "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -172,6 +174,8 @@
       "GCE",
       "os",
       "Debian9",
+      "style",
+      "default",
       "--dont_write",
       "pdf",
       "--nogpu",
@@ -184,7 +188,6 @@
       "tiles_rt-8888",
       "lite-8888",
       "serialize-8888",
-      "gbr-8888",
       "f16",
       "srgb",
       "esrgb",
@@ -204,14 +207,6 @@
       "_",
       "_",
       "dstreadshuffle",
-      "gbr-8888",
-      "image",
-      "_",
-      "_",
-      "gbr-8888",
-      "colorImage",
-      "_",
-      "_",
       "g8",
       "image",
       "_",
@@ -367,6 +362,10 @@
       "serialize-8888",
       "gm",
       "_",
+      "compositor_quads_shader",
+      "serialize-8888",
+      "gm",
+      "_",
       "analytic_antialias_convex",
       "serialize-8888",
       "gm",
@@ -462,7 +461,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/clang_linux/msan:[START_DIR]/clang_linux/lib",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]:[START_DIR]/clang_linux/bin"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]:[START_DIR]/clang_linux/bin"
     },
     "name": "symbolized dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SK_USE_DISCARDABLE_SCALEDIMAGECACHE.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SK_USE_DISCARDABLE_SCALEDIMAGECACHE.json
index 79376da..d5ae28c 100644
--- a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SK_USE_DISCARDABLE_SCALEDIMAGECACHE.json
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SK_USE_DISCARDABLE_SCALEDIMAGECACHE.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -226,6 +226,8 @@
       "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SK_USE_DISCARDABLE_SCALEDIMAGECACHE",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -249,6 +251,8 @@
       "GCE",
       "os",
       "Debian9",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]/tmp/uninteresting_hashes.txt",
       "--writePath",
@@ -277,7 +281,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "symbolized dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-Wuffs.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-Wuffs.json
new file mode 100644
index 0000000..4d1bd0c
--- /dev/null
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-Wuffs.json
@@ -0,0 +1,298 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skp VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SKP_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get svg VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SVG_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[START_DIR]/test"
+    ],
+    "infra_step": true,
+    "name": "rmtree test"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/test"
+    ],
+    "infra_step": true,
+    "name": "makedirs test"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = sys.argv[1]\nRETRIES = 5\nTIMEOUT = 60\nWAIT_BASE = 15\n\nsocket.setdefaulttimeout(TIMEOUT)\nfor retry in range(RETRIES):\n  try:\n    with contextlib.closing(\n        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:\n      hashes = w.read()\n      with open(sys.argv[2], 'w') as f:\n        f.write(hashes)\n        break\n  except Exception as e:\n    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
+      "https://example.com/hashes.txt",
+      "[START_DIR]/tmp/uninteresting_hashes.txt"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "get uninteresting hashes",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
+      "@@@STEP_LOG_LINE@python.inline@import math@@@",
+      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@HASHES_URL = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@RETRIES = 5@@@",
+      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 60@@@",
+      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 15@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
+      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(@@@",
+      "@@@STEP_LOG_LINE@python.inline@        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      hashes = w.read()@@@",
+      "@@@STEP_LOG_LINE@python.inline@      with open(sys.argv[2], 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@        f.write(hashes)@@@",
+      "@@@STEP_LOG_LINE@python.inline@        break@@@",
+      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
+      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
+    ],
+    "name": "get swarming task id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[skia::flavor]/resources/symbolize_stack_trace.py",
+      "[START_DIR]",
+      "catchsegv",
+      "[START_DIR]/build/dm",
+      "--resourcePath",
+      "[START_DIR]/skia/resources",
+      "--skps",
+      "[START_DIR]/skp",
+      "--images",
+      "[START_DIR]/skimage/dm",
+      "--colorImages",
+      "[START_DIR]/skimage/colorspace",
+      "--nameByHash",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "builder",
+      "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-Wuffs",
+      "buildbucket_build_id",
+      "123454321",
+      "task_id",
+      "task_12345",
+      "swarming_bot_id",
+      "skia-bot-123",
+      "swarming_task_id",
+      "123456",
+      "--svgs",
+      "[START_DIR]/svg",
+      "--key",
+      "arch",
+      "x86_64",
+      "compiler",
+      "Clang",
+      "configuration",
+      "Debug",
+      "cpu_or_gpu",
+      "CPU",
+      "cpu_or_gpu_value",
+      "AVX2",
+      "extra_config",
+      "Wuffs",
+      "model",
+      "GCE",
+      "os",
+      "Debian9",
+      "style",
+      "default",
+      "--uninterestingHashesFile",
+      "[START_DIR]/tmp/uninteresting_hashes.txt",
+      "--writePath",
+      "[START_DIR]/[SWARM_OUT_DIR]",
+      "--dont_write",
+      "pdf",
+      "--randomProcessorTest",
+      "--nogpu",
+      "--config",
+      "8888",
+      "--src",
+      "tests",
+      "gm",
+      "image",
+      "colorImage",
+      "--blacklist",
+      "_",
+      "image",
+      "gen_platf",
+      "error",
+      "_",
+      "tests",
+      "_",
+      "Codec_partial",
+      "_",
+      "tests",
+      "_",
+      "Codec_gif",
+      "--nonativeFonts",
+      "--verbose"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "symbolized dm"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-shard_00_10-Coverage.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-shard_00_10-Coverage.json
index 0c73731..8810cb2 100644
--- a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-shard_00_10-Coverage.json
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-shard_00_10-Coverage.json
@@ -150,6 +150,8 @@
       "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-shard_00_10-Coverage",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -173,6 +175,8 @@
       "GCE",
       "os",
       "Debian9",
+      "style",
+      "default",
       "--dont_write",
       "pdf",
       "--randomProcessorTest",
@@ -200,7 +204,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "LLVM_PROFILE_FILE": "[START_DIR]/[SWARM_OUT_DIR]/shard_00_10.profraw",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "symbolized dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Lottie.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Lottie.json
index 29091b1..53bd487 100644
--- a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Lottie.json
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Lottie.json
@@ -80,7 +80,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -170,6 +170,8 @@
       "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Lottie",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -197,6 +199,8 @@
       "Debian9",
       "renderer",
       "skottie",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]/tmp/uninteresting_hashes.txt",
       "--writePath",
@@ -220,7 +224,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "symbolized dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_FORCE_RASTER_PIPELINE_BLITTER.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_FORCE_RASTER_PIPELINE_BLITTER.json
index 4f9f23e..0c094f8 100644
--- a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_FORCE_RASTER_PIPELINE_BLITTER.json
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_FORCE_RASTER_PIPELINE_BLITTER.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -226,6 +226,8 @@
       "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_FORCE_RASTER_PIPELINE_BLITTER",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -249,6 +251,8 @@
       "GCE",
       "os",
       "Debian9",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]/tmp/uninteresting_hashes.txt",
       "--writePath",
@@ -274,7 +278,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "symbolized dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN.json
index 76bf13f..9fc449f 100644
--- a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN.json
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN.json
@@ -149,6 +149,8 @@
       "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -172,6 +174,8 @@
       "GCE",
       "os",
       "Debian9",
+      "style",
+      "default",
       "--dont_write",
       "pdf",
       "--randomProcessorTest",
@@ -185,7 +189,6 @@
       "tiles_rt-8888",
       "lite-8888",
       "serialize-8888",
-      "gbr-8888",
       "f16",
       "srgb",
       "esrgb",
@@ -205,14 +208,6 @@
       "_",
       "_",
       "dstreadshuffle",
-      "gbr-8888",
-      "image",
-      "_",
-      "_",
-      "gbr-8888",
-      "colorImage",
-      "_",
-      "_",
       "g8",
       "image",
       "_",
@@ -368,6 +363,10 @@
       "serialize-8888",
       "gm",
       "_",
+      "compositor_quads_shader",
+      "serialize-8888",
+      "gm",
+      "_",
       "analytic_antialias_convex",
       "serialize-8888",
       "gm",
@@ -464,7 +463,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/clang_linux/lib",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]:[START_DIR]/clang_linux/bin",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]:[START_DIR]/clang_linux/bin",
       "TSAN_OPTIONS": "report_signal_unsafe=0"
     },
     "name": "symbolized dm"
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Release-All-SwiftShader.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Release-All-SwiftShader.json
index 7930fdf..7c749e0 100644
--- a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Release-All-SwiftShader.json
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Release-All-SwiftShader.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -226,6 +226,8 @@
       "Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Release-All-SwiftShader",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -249,6 +251,8 @@
       "GCE",
       "os",
       "Debian9",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]/tmp/uninteresting_hashes.txt",
       "--writePath",
@@ -374,7 +378,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/build/swiftshader_out",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "symbolized dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All-Vulkan.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All-Vulkan.json
index d886aa9..94f5899 100644
--- a/infra/bots/recipes/test.expected/Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All-Vulkan.json
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All-Vulkan.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -226,6 +226,8 @@
       "Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All-Vulkan",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -249,6 +251,8 @@
       "NUC5PPYH",
       "os",
       "Debian9",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]/tmp/uninteresting_hashes.txt",
       "--writePath",
@@ -314,6 +318,14 @@
       "gm",
       "_",
       "orientation",
+      "gltestthreading",
+      "gm",
+      "_",
+      "stroketext",
+      "gltestthreading",
+      "gm",
+      "_",
+      "draw_image_set",
       "_",
       "svg",
       "_",
@@ -445,7 +457,7 @@
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/mesa_intel_driver_linux:[START_DIR]/linux_vulkan_sdk/lib",
       "LIBGL_DRIVERS_PATH": "[START_DIR]/mesa_intel_driver_linux",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]:[START_DIR]/linux_vulkan_sdk/bin",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]:[START_DIR]/linux_vulkan_sdk/bin",
       "VK_ICD_FILENAMES": "[START_DIR]/mesa_intel_driver_linux/intel_icd.x86_64.json"
     },
     "name": "symbolized dm"
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-Vulkan.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-Vulkan.json
index e91dac2..3aeca5c 100644
--- a/infra/bots/recipes/test.expected/Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-Vulkan.json
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-Vulkan.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -226,6 +226,8 @@
       "Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-Vulkan",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -249,6 +251,8 @@
       "NUC7i5BNK",
       "os",
       "Debian9",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]/tmp/uninteresting_hashes.txt",
       "--writePath",
@@ -314,6 +318,14 @@
       "gm",
       "_",
       "orientation",
+      "gltestthreading",
+      "gm",
+      "_",
+      "stroketext",
+      "gltestthreading",
+      "gm",
+      "_",
+      "draw_image_set",
       "_",
       "svg",
       "_",
@@ -426,7 +438,7 @@
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/mesa_intel_driver_linux:[START_DIR]/linux_vulkan_sdk/lib",
       "LIBGL_DRIVERS_PATH": "[START_DIR]/mesa_intel_driver_linux",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]:[START_DIR]/linux_vulkan_sdk/bin",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]:[START_DIR]/linux_vulkan_sdk/bin",
       "VK_ICD_FILENAMES": "[START_DIR]/mesa_intel_driver_linux/intel_icd.x86_64.json"
     },
     "name": "symbolized dm"
diff --git a/infra/bots/recipes/test.expected/Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts.json b/infra/bots/recipes/test.expected/Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts.json
deleted file mode 100644
index 78901c5..0000000
--- a/infra/bots/recipes/test.expected/Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts.json
+++ /dev/null
@@ -1,500 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/tmp"
-    ],
-    "infra_step": true,
-    "name": "makedirs tmp_dir"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skp VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SKP_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SKP_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skimage VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SK_IMAGE_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SK_IMAGE_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get svg VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SVG_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SVG_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "rmtree",
-      "[START_DIR]/test"
-    ],
-    "infra_step": true,
-    "name": "rmtree test"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/test"
-    ],
-    "infra_step": true,
-    "name": "makedirs test"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = sys.argv[1]\nRETRIES = 5\nTIMEOUT = 60\nWAIT_BASE = 15\n\nsocket.setdefaulttimeout(TIMEOUT)\nfor retry in range(RETRIES):\n  try:\n    with contextlib.closing(\n        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:\n      hashes = w.read()\n      with open(sys.argv[2], 'w') as f:\n        f.write(hashes)\n        break\n  except Exception as e:\n    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
-      "https://example.com/hashes.txt",
-      "[START_DIR]/tmp/uninteresting_hashes.txt"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "infra_step": true,
-    "name": "get uninteresting hashes",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
-      "@@@STEP_LOG_LINE@python.inline@import math@@@",
-      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
-      "@@@STEP_LOG_LINE@python.inline@import time@@@",
-      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@HASHES_URL = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@RETRIES = 5@@@",
-      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 60@@@",
-      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 15@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
-      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(@@@",
-      "@@@STEP_LOG_LINE@python.inline@        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      hashes = w.read()@@@",
-      "@@@STEP_LOG_LINE@python.inline@      with open(sys.argv[2], 'w') as f:@@@",
-      "@@@STEP_LOG_LINE@python.inline@        f.write(hashes)@@@",
-      "@@@STEP_LOG_LINE@python.inline@        break@@@",
-      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
-      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
-      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
-      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
-    ],
-    "name": "get swarming bot id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
-    ],
-    "name": "get swarming task id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/build/dm",
-      "--resourcePath",
-      "[START_DIR]/skia/resources",
-      "--skps",
-      "[START_DIR]/skp",
-      "--images",
-      "[START_DIR]/skimage/dm",
-      "--colorImages",
-      "[START_DIR]/skimage/colorspace",
-      "--nameByHash",
-      "--properties",
-      "gitHash",
-      "abc123",
-      "builder",
-      "Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts",
-      "buildbucket_build_id",
-      "123454321",
-      "swarming_bot_id",
-      "skia-bot-123",
-      "swarming_task_id",
-      "123456",
-      "--svgs",
-      "[START_DIR]/svg",
-      "--key",
-      "arch",
-      "x86_64",
-      "compiler",
-      "Clang",
-      "configuration",
-      "Release",
-      "cpu_or_gpu",
-      "GPU",
-      "cpu_or_gpu_value",
-      "IntelHD615",
-      "extra_config",
-      "NativeFonts",
-      "model",
-      "MacBook10.1",
-      "os",
-      "Mac",
-      "--uninterestingHashesFile",
-      "[START_DIR]/tmp/uninteresting_hashes.txt",
-      "--writePath",
-      "[START_DIR]/[SWARM_OUT_DIR]",
-      "--dont_write",
-      "pdf",
-      "--randomProcessorTest",
-      "--nocpu",
-      "--config",
-      "gl",
-      "--src",
-      "tests",
-      "gm",
-      "svg",
-      "--blacklist",
-      "_",
-      "svg",
-      "_",
-      "svgparse_",
-      "_",
-      "image",
-      "gen_platf",
-      "rgba32abf.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "rgb24prof.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "rgb24lprof.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "8bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "4bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "32bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "24bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "frame_larger_than_image.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "inc0.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc1.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc2.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc3.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc4.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc5.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc6.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc7.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc8.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc9.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc10.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc11.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc12.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc13.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc14.png",
-      "_",
-      "image",
-      "gen_platf",
-      "incInterlaced.png",
-      "_",
-      "image",
-      "gen_platf",
-      "incInterlaced.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "inc1.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "inc0.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "butterfly.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "testimgari.jpg",
-      "_",
-      "image",
-      "gen_platf",
-      "rle8-height-negative.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "rle4-height-negative.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "error",
-      "_",
-      "image",
-      "_",
-      "interlaced1.png",
-      "_",
-      "image",
-      "_",
-      "interlaced2.png",
-      "_",
-      "image",
-      "_",
-      "interlaced3.png",
-      "_",
-      "image",
-      "_",
-      ".arw",
-      "_",
-      "image",
-      "_",
-      ".cr2",
-      "_",
-      "image",
-      "_",
-      ".dng",
-      "_",
-      "image",
-      "_",
-      ".nef",
-      "_",
-      "image",
-      "_",
-      ".nrw",
-      "_",
-      "image",
-      "_",
-      ".orf",
-      "_",
-      "image",
-      "_",
-      ".raf",
-      "_",
-      "image",
-      "_",
-      ".rw2",
-      "_",
-      "image",
-      "_",
-      ".pef",
-      "_",
-      "image",
-      "_",
-      ".srw",
-      "_",
-      "image",
-      "_",
-      ".ARW",
-      "_",
-      "image",
-      "_",
-      ".CR2",
-      "_",
-      "image",
-      "_",
-      ".DNG",
-      "_",
-      "image",
-      "_",
-      ".NEF",
-      "_",
-      "image",
-      "_",
-      ".NRW",
-      "_",
-      "image",
-      "_",
-      ".ORF",
-      "_",
-      "image",
-      "_",
-      ".RAF",
-      "_",
-      "image",
-      "_",
-      ".RW2",
-      "_",
-      "image",
-      "_",
-      ".PEF",
-      "_",
-      "image",
-      "_",
-      ".SRW",
-      "--match",
-      "~^GrMeshTest$",
-      "--verbose"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "dm"
-  },
-  {
-    "jsonResult": null,
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipes/test.expected/Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All.json b/infra/bots/recipes/test.expected/Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All.json
deleted file mode 100644
index bcb9aae..0000000
--- a/infra/bots/recipes/test.expected/Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All.json
+++ /dev/null
@@ -1,508 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/tmp"
-    ],
-    "infra_step": true,
-    "name": "makedirs tmp_dir"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skp VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SKP_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SKP_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skimage VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SK_IMAGE_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SK_IMAGE_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get svg VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SVG_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SVG_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "rmtree",
-      "[START_DIR]/test"
-    ],
-    "infra_step": true,
-    "name": "rmtree test"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/test"
-    ],
-    "infra_step": true,
-    "name": "makedirs test"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = sys.argv[1]\nRETRIES = 5\nTIMEOUT = 60\nWAIT_BASE = 15\n\nsocket.setdefaulttimeout(TIMEOUT)\nfor retry in range(RETRIES):\n  try:\n    with contextlib.closing(\n        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:\n      hashes = w.read()\n      with open(sys.argv[2], 'w') as f:\n        f.write(hashes)\n        break\n  except Exception as e:\n    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
-      "https://example.com/hashes.txt",
-      "[START_DIR]/tmp/uninteresting_hashes.txt"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "infra_step": true,
-    "name": "get uninteresting hashes",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
-      "@@@STEP_LOG_LINE@python.inline@import math@@@",
-      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
-      "@@@STEP_LOG_LINE@python.inline@import time@@@",
-      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@HASHES_URL = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@RETRIES = 5@@@",
-      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 60@@@",
-      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 15@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
-      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(@@@",
-      "@@@STEP_LOG_LINE@python.inline@        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      hashes = w.read()@@@",
-      "@@@STEP_LOG_LINE@python.inline@      with open(sys.argv[2], 'w') as f:@@@",
-      "@@@STEP_LOG_LINE@python.inline@        f.write(hashes)@@@",
-      "@@@STEP_LOG_LINE@python.inline@        break@@@",
-      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
-      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
-      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
-      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
-    ],
-    "name": "get swarming bot id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
-    ],
-    "name": "get swarming task id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/build/dm",
-      "--resourcePath",
-      "[START_DIR]/skia/resources",
-      "--skps",
-      "[START_DIR]/skp",
-      "--images",
-      "[START_DIR]/skimage/dm",
-      "--colorImages",
-      "[START_DIR]/skimage/colorspace",
-      "--nameByHash",
-      "--properties",
-      "gitHash",
-      "abc123",
-      "builder",
-      "Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All",
-      "buildbucket_build_id",
-      "123454321",
-      "swarming_bot_id",
-      "skia-bot-123",
-      "swarming_task_id",
-      "123456",
-      "--svgs",
-      "[START_DIR]/svg",
-      "--key",
-      "arch",
-      "x86_64",
-      "compiler",
-      "Clang",
-      "configuration",
-      "Debug",
-      "cpu_or_gpu",
-      "GPU",
-      "cpu_or_gpu_value",
-      "IntelHD6000",
-      "model",
-      "MacBookAir7.2",
-      "os",
-      "Mac",
-      "--uninterestingHashesFile",
-      "[START_DIR]/tmp/uninteresting_hashes.txt",
-      "--writePath",
-      "[START_DIR]/[SWARM_OUT_DIR]",
-      "--dont_write",
-      "pdf",
-      "--randomProcessorTest",
-      "--nocpu",
-      "--config",
-      "gl",
-      "gldft",
-      "glsrgb",
-      "--src",
-      "tests",
-      "gm",
-      "image",
-      "colorImage",
-      "svg",
-      "--blacklist",
-      "_",
-      "svg",
-      "_",
-      "svgparse_",
-      "glsrgb",
-      "image",
-      "_",
-      "_",
-      "_",
-      "image",
-      "gen_platf",
-      "rgba32abf.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "rgb24prof.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "rgb24lprof.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "8bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "4bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "32bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "24bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "frame_larger_than_image.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "inc0.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc1.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc2.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc3.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc4.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc5.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc6.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc7.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc8.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc9.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc10.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc11.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc12.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc13.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc14.png",
-      "_",
-      "image",
-      "gen_platf",
-      "incInterlaced.png",
-      "_",
-      "image",
-      "gen_platf",
-      "incInterlaced.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "inc1.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "inc0.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "butterfly.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "testimgari.jpg",
-      "_",
-      "image",
-      "gen_platf",
-      "rle8-height-negative.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "rle4-height-negative.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "error",
-      "_",
-      "image",
-      "_",
-      "interlaced1.png",
-      "_",
-      "image",
-      "_",
-      "interlaced2.png",
-      "_",
-      "image",
-      "_",
-      "interlaced3.png",
-      "_",
-      "image",
-      "_",
-      ".arw",
-      "_",
-      "image",
-      "_",
-      ".cr2",
-      "_",
-      "image",
-      "_",
-      ".dng",
-      "_",
-      "image",
-      "_",
-      ".nef",
-      "_",
-      "image",
-      "_",
-      ".nrw",
-      "_",
-      "image",
-      "_",
-      ".orf",
-      "_",
-      "image",
-      "_",
-      ".raf",
-      "_",
-      "image",
-      "_",
-      ".rw2",
-      "_",
-      "image",
-      "_",
-      ".pef",
-      "_",
-      "image",
-      "_",
-      ".srw",
-      "_",
-      "image",
-      "_",
-      ".ARW",
-      "_",
-      "image",
-      "_",
-      ".CR2",
-      "_",
-      "image",
-      "_",
-      ".DNG",
-      "_",
-      "image",
-      "_",
-      ".NEF",
-      "_",
-      "image",
-      "_",
-      ".NRW",
-      "_",
-      "image",
-      "_",
-      ".ORF",
-      "_",
-      "image",
-      "_",
-      ".RAF",
-      "_",
-      "image",
-      "_",
-      ".RW2",
-      "_",
-      "image",
-      "_",
-      ".PEF",
-      "_",
-      "image",
-      "_",
-      ".SRW",
-      "--match",
-      "~^ProcessorCloneTest$",
-      "~^GrMeshTest$",
-      "--nonativeFonts",
-      "--verbose"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "dm"
-  },
-  {
-    "jsonResult": null,
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipes/test.expected/Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All.json b/infra/bots/recipes/test.expected/Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All.json
deleted file mode 100644
index c20f8f4..0000000
--- a/infra/bots/recipes/test.expected/Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All.json
+++ /dev/null
@@ -1,402 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/tmp"
-    ],
-    "infra_step": true,
-    "name": "makedirs tmp_dir"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skp VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SKP_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SKP_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skimage VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SK_IMAGE_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SK_IMAGE_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get svg VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SVG_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SVG_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "rmtree",
-      "[START_DIR]/test"
-    ],
-    "infra_step": true,
-    "name": "rmtree test"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/test"
-    ],
-    "infra_step": true,
-    "name": "makedirs test"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = sys.argv[1]\nRETRIES = 5\nTIMEOUT = 60\nWAIT_BASE = 15\n\nsocket.setdefaulttimeout(TIMEOUT)\nfor retry in range(RETRIES):\n  try:\n    with contextlib.closing(\n        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:\n      hashes = w.read()\n      with open(sys.argv[2], 'w') as f:\n        f.write(hashes)\n        break\n  except Exception as e:\n    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
-      "https://example.com/hashes.txt",
-      "[START_DIR]/tmp/uninteresting_hashes.txt"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "infra_step": true,
-    "name": "get uninteresting hashes",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
-      "@@@STEP_LOG_LINE@python.inline@import math@@@",
-      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
-      "@@@STEP_LOG_LINE@python.inline@import time@@@",
-      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@HASHES_URL = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@RETRIES = 5@@@",
-      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 60@@@",
-      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 15@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
-      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(@@@",
-      "@@@STEP_LOG_LINE@python.inline@        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      hashes = w.read()@@@",
-      "@@@STEP_LOG_LINE@python.inline@      with open(sys.argv[2], 'w') as f:@@@",
-      "@@@STEP_LOG_LINE@python.inline@        f.write(hashes)@@@",
-      "@@@STEP_LOG_LINE@python.inline@        break@@@",
-      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
-      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
-      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
-      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
-    ],
-    "name": "get swarming bot id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
-    ],
-    "name": "get swarming task id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/build/dm",
-      "--resourcePath",
-      "[START_DIR]/skia/resources",
-      "--skps",
-      "[START_DIR]/skp",
-      "--images",
-      "[START_DIR]/skimage/dm",
-      "--colorImages",
-      "[START_DIR]/skimage/colorspace",
-      "--nameByHash",
-      "--properties",
-      "gitHash",
-      "abc123",
-      "builder",
-      "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All",
-      "buildbucket_build_id",
-      "123454321",
-      "swarming_bot_id",
-      "skia-bot-123",
-      "swarming_task_id",
-      "123456",
-      "--svgs",
-      "[START_DIR]/svg",
-      "--key",
-      "arch",
-      "x86_64",
-      "compiler",
-      "Clang",
-      "configuration",
-      "Release",
-      "cpu_or_gpu",
-      "CPU",
-      "cpu_or_gpu_value",
-      "AVX2",
-      "model",
-      "MacBookPro11.5",
-      "os",
-      "Mac",
-      "--uninterestingHashesFile",
-      "[START_DIR]/tmp/uninteresting_hashes.txt",
-      "--writePath",
-      "[START_DIR]/[SWARM_OUT_DIR]",
-      "--dont_write",
-      "pdf",
-      "--randomProcessorTest",
-      "--nogpu",
-      "--config",
-      "8888",
-      "--src",
-      "tests",
-      "gm",
-      "image",
-      "colorImage",
-      "--blacklist",
-      "_",
-      "image",
-      "gen_platf",
-      "rgba32abf.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "rgb24prof.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "rgb24lprof.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "8bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "4bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "32bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "24bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "frame_larger_than_image.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "inc0.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc1.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc2.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc3.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc4.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc5.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc6.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc7.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc8.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc9.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc10.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc11.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc12.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc13.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc14.png",
-      "_",
-      "image",
-      "gen_platf",
-      "incInterlaced.png",
-      "_",
-      "image",
-      "gen_platf",
-      "incInterlaced.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "inc1.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "inc0.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "butterfly.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "testimgari.jpg",
-      "_",
-      "image",
-      "gen_platf",
-      "rle8-height-negative.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "rle4-height-negative.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "error",
-      "--nonativeFonts",
-      "--verbose"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "dm"
-  },
-  {
-    "jsonResult": null,
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipes/test.expected/Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal.json b/infra/bots/recipes/test.expected/Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal.json
deleted file mode 100644
index 2c8e2a3..0000000
--- a/infra/bots/recipes/test.expected/Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal.json
+++ /dev/null
@@ -1,527 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/tmp"
-    ],
-    "infra_step": true,
-    "name": "makedirs tmp_dir"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skp VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SKP_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SKP_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skimage VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SK_IMAGE_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SK_IMAGE_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get svg VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SVG_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SVG_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "rmtree",
-      "[START_DIR]/test"
-    ],
-    "infra_step": true,
-    "name": "rmtree test"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/test"
-    ],
-    "infra_step": true,
-    "name": "makedirs test"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = sys.argv[1]\nRETRIES = 5\nTIMEOUT = 60\nWAIT_BASE = 15\n\nsocket.setdefaulttimeout(TIMEOUT)\nfor retry in range(RETRIES):\n  try:\n    with contextlib.closing(\n        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:\n      hashes = w.read()\n      with open(sys.argv[2], 'w') as f:\n        f.write(hashes)\n        break\n  except Exception as e:\n    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
-      "https://example.com/hashes.txt",
-      "[START_DIR]/tmp/uninteresting_hashes.txt"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "infra_step": true,
-    "name": "get uninteresting hashes",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
-      "@@@STEP_LOG_LINE@python.inline@import math@@@",
-      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
-      "@@@STEP_LOG_LINE@python.inline@import time@@@",
-      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@HASHES_URL = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@RETRIES = 5@@@",
-      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 60@@@",
-      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 15@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
-      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(@@@",
-      "@@@STEP_LOG_LINE@python.inline@        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      hashes = w.read()@@@",
-      "@@@STEP_LOG_LINE@python.inline@      with open(sys.argv[2], 'w') as f:@@@",
-      "@@@STEP_LOG_LINE@python.inline@        f.write(hashes)@@@",
-      "@@@STEP_LOG_LINE@python.inline@        break@@@",
-      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
-      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
-      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
-      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
-    ],
-    "name": "get swarming bot id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
-    ],
-    "name": "get swarming task id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/build/dm",
-      "--resourcePath",
-      "[START_DIR]/skia/resources",
-      "--skps",
-      "[START_DIR]/skp",
-      "--images",
-      "[START_DIR]/skimage/dm",
-      "--colorImages",
-      "[START_DIR]/skimage/colorspace",
-      "--nameByHash",
-      "--properties",
-      "gitHash",
-      "abc123",
-      "builder",
-      "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal",
-      "buildbucket_build_id",
-      "123454321",
-      "swarming_bot_id",
-      "skia-bot-123",
-      "swarming_task_id",
-      "123456",
-      "--svgs",
-      "[START_DIR]/svg",
-      "--key",
-      "arch",
-      "x86_64",
-      "compiler",
-      "Clang",
-      "configuration",
-      "Debug",
-      "cpu_or_gpu",
-      "GPU",
-      "cpu_or_gpu_value",
-      "RadeonHD8870M",
-      "extra_config",
-      "Metal",
-      "model",
-      "MacBookPro11.5",
-      "os",
-      "Mac",
-      "--uninterestingHashesFile",
-      "[START_DIR]/tmp/uninteresting_hashes.txt",
-      "--writePath",
-      "[START_DIR]/[SWARM_OUT_DIR]",
-      "--dont_write",
-      "pdf",
-      "--randomProcessorTest",
-      "--nocpu",
-      "--config",
-      "mtl",
-      "--src",
-      "tests",
-      "gm",
-      "image",
-      "colorImage",
-      "svg",
-      "--blacklist",
-      "_",
-      "svg",
-      "_",
-      "svgparse_",
-      "_",
-      "image",
-      "gen_platf",
-      "rgba32abf.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "rgb24prof.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "rgb24lprof.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "8bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "4bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "32bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "24bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "frame_larger_than_image.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "inc0.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc1.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc2.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc3.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc4.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc5.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc6.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc7.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc8.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc9.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc10.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc11.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc12.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc13.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc14.png",
-      "_",
-      "image",
-      "gen_platf",
-      "incInterlaced.png",
-      "_",
-      "image",
-      "gen_platf",
-      "incInterlaced.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "inc1.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "inc0.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "butterfly.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "testimgari.jpg",
-      "_",
-      "image",
-      "gen_platf",
-      "rle8-height-negative.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "rle4-height-negative.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "error",
-      "_",
-      "image",
-      "_",
-      "interlaced1.png",
-      "_",
-      "image",
-      "_",
-      "interlaced2.png",
-      "_",
-      "image",
-      "_",
-      "interlaced3.png",
-      "_",
-      "image",
-      "_",
-      ".arw",
-      "_",
-      "image",
-      "_",
-      ".cr2",
-      "_",
-      "image",
-      "_",
-      ".dng",
-      "_",
-      "image",
-      "_",
-      ".nef",
-      "_",
-      "image",
-      "_",
-      ".nrw",
-      "_",
-      "image",
-      "_",
-      ".orf",
-      "_",
-      "image",
-      "_",
-      ".raf",
-      "_",
-      "image",
-      "_",
-      ".rw2",
-      "_",
-      "image",
-      "_",
-      ".pef",
-      "_",
-      "image",
-      "_",
-      ".srw",
-      "_",
-      "image",
-      "_",
-      ".ARW",
-      "_",
-      "image",
-      "_",
-      ".CR2",
-      "_",
-      "image",
-      "_",
-      ".DNG",
-      "_",
-      "image",
-      "_",
-      ".NEF",
-      "_",
-      "image",
-      "_",
-      ".NRW",
-      "_",
-      "image",
-      "_",
-      ".ORF",
-      "_",
-      "image",
-      "_",
-      ".RAF",
-      "_",
-      "image",
-      "_",
-      ".RW2",
-      "_",
-      "image",
-      "_",
-      ".PEF",
-      "_",
-      "image",
-      "_",
-      ".SRW",
-      "--match",
-      "~^ClearOp$",
-      "~^DDLSurfaceCharacterizationTest$",
-      "~^DDLNonTextureabilityTest$",
-      "~^DDLOperatorEqTest$",
-      "~^DeferredProxyTest$",
-      "~^GPUMemorySize$",
-      "~^GrContext_colorTypeSupportedAsImage$",
-      "~^GrContext_colorTypeSupportedAsSurface$",
-      "~^GrContext_maxSurfaceSamplesForColorType$",
-      "~^GrContextFactory_sharedContexts$",
-      "~^GrPipelineDynamicStateTest$",
-      "~^InitialTextureClear$",
-      "~^PromiseImageTestNoDelayedRelease$",
-      "~^PromiseImageTestDelayedRelease$",
-      "~^PromiseImageTextureReuse$",
-      "~^PromiseImageTextureReuseDifferentConfig$",
-      "~^PromiseImageTextureShutdown$",
-      "~^PromiseImageTextureFullCache$",
-      "~^ResourceAllocatorTest$",
-      "~^RGB565TextureTest$",
-      "~^RGBA4444TextureTest$",
-      "~^TransferPixelsTest$",
-      "~^SurfaceSemaphores$",
-      "~^VertexAttributeCount$",
-      "~^WrappedProxyTest$",
-      "--nonativeFonts",
-      "--verbose"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "dm"
-  },
-  {
-    "jsonResult": null,
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipes/test.expected/Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan.json b/infra/bots/recipes/test.expected/Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan.json
deleted file mode 100644
index 6e94eb0..0000000
--- a/infra/bots/recipes/test.expected/Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan.json
+++ /dev/null
@@ -1,514 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/tmp"
-    ],
-    "infra_step": true,
-    "name": "makedirs tmp_dir"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skp VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SKP_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SKP_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skimage VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SK_IMAGE_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SK_IMAGE_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get svg VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SVG_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SVG_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "rmtree",
-      "[START_DIR]/test"
-    ],
-    "infra_step": true,
-    "name": "rmtree test"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/test"
-    ],
-    "infra_step": true,
-    "name": "makedirs test"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = sys.argv[1]\nRETRIES = 5\nTIMEOUT = 60\nWAIT_BASE = 15\n\nsocket.setdefaulttimeout(TIMEOUT)\nfor retry in range(RETRIES):\n  try:\n    with contextlib.closing(\n        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:\n      hashes = w.read()\n      with open(sys.argv[2], 'w') as f:\n        f.write(hashes)\n        break\n  except Exception as e:\n    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
-      "https://example.com/hashes.txt",
-      "[START_DIR]/tmp/uninteresting_hashes.txt"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "infra_step": true,
-    "name": "get uninteresting hashes",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
-      "@@@STEP_LOG_LINE@python.inline@import math@@@",
-      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
-      "@@@STEP_LOG_LINE@python.inline@import time@@@",
-      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@HASHES_URL = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@RETRIES = 5@@@",
-      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 60@@@",
-      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 15@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
-      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(@@@",
-      "@@@STEP_LOG_LINE@python.inline@        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      hashes = w.read()@@@",
-      "@@@STEP_LOG_LINE@python.inline@      with open(sys.argv[2], 'w') as f:@@@",
-      "@@@STEP_LOG_LINE@python.inline@        f.write(hashes)@@@",
-      "@@@STEP_LOG_LINE@python.inline@        break@@@",
-      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
-      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
-      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
-      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
-    ],
-    "name": "get swarming bot id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
-    ],
-    "name": "get swarming task id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/build/dm",
-      "--resourcePath",
-      "[START_DIR]/skia/resources",
-      "--skps",
-      "[START_DIR]/skp",
-      "--images",
-      "[START_DIR]/skimage/dm",
-      "--colorImages",
-      "[START_DIR]/skimage/colorspace",
-      "--nameByHash",
-      "--properties",
-      "gitHash",
-      "abc123",
-      "builder",
-      "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan",
-      "buildbucket_build_id",
-      "123454321",
-      "swarming_bot_id",
-      "skia-bot-123",
-      "swarming_task_id",
-      "123456",
-      "--svgs",
-      "[START_DIR]/svg",
-      "--key",
-      "arch",
-      "x86_64",
-      "compiler",
-      "Clang",
-      "configuration",
-      "Release",
-      "cpu_or_gpu",
-      "GPU",
-      "cpu_or_gpu_value",
-      "RadeonHD8870M",
-      "extra_config",
-      "MoltenVK_Vulkan",
-      "model",
-      "MacBookPro11.5",
-      "os",
-      "Mac",
-      "--uninterestingHashesFile",
-      "[START_DIR]/tmp/uninteresting_hashes.txt",
-      "--writePath",
-      "[START_DIR]/[SWARM_OUT_DIR]",
-      "--dont_write",
-      "pdf",
-      "--randomProcessorTest",
-      "--nocpu",
-      "--config",
-      "vk",
-      "--src",
-      "tests",
-      "gm",
-      "image",
-      "colorImage",
-      "svg",
-      "--blacklist",
-      "_",
-      "svg",
-      "_",
-      "svgparse_",
-      "_",
-      "image",
-      "gen_platf",
-      "rgba32abf.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "rgb24prof.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "rgb24lprof.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "8bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "4bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "32bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "24bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "frame_larger_than_image.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "inc0.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc1.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc2.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc3.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc4.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc5.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc6.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc7.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc8.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc9.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc10.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc11.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc12.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc13.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc14.png",
-      "_",
-      "image",
-      "gen_platf",
-      "incInterlaced.png",
-      "_",
-      "image",
-      "gen_platf",
-      "incInterlaced.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "inc1.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "inc0.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "butterfly.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "testimgari.jpg",
-      "_",
-      "image",
-      "gen_platf",
-      "rle8-height-negative.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "rle4-height-negative.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "error",
-      "_",
-      "image",
-      "_",
-      "interlaced1.png",
-      "_",
-      "image",
-      "_",
-      "interlaced2.png",
-      "_",
-      "image",
-      "_",
-      "interlaced3.png",
-      "_",
-      "image",
-      "_",
-      ".arw",
-      "_",
-      "image",
-      "_",
-      ".cr2",
-      "_",
-      "image",
-      "_",
-      ".dng",
-      "_",
-      "image",
-      "_",
-      ".nef",
-      "_",
-      "image",
-      "_",
-      ".nrw",
-      "_",
-      "image",
-      "_",
-      ".orf",
-      "_",
-      "image",
-      "_",
-      ".raf",
-      "_",
-      "image",
-      "_",
-      ".rw2",
-      "_",
-      "image",
-      "_",
-      ".pef",
-      "_",
-      "image",
-      "_",
-      ".srw",
-      "_",
-      "image",
-      "_",
-      ".ARW",
-      "_",
-      "image",
-      "_",
-      ".CR2",
-      "_",
-      "image",
-      "_",
-      ".DNG",
-      "_",
-      "image",
-      "_",
-      ".NEF",
-      "_",
-      "image",
-      "_",
-      ".NRW",
-      "_",
-      "image",
-      "_",
-      ".ORF",
-      "_",
-      "image",
-      "_",
-      ".RAF",
-      "_",
-      "image",
-      "_",
-      ".RW2",
-      "_",
-      "image",
-      "_",
-      ".PEF",
-      "_",
-      "image",
-      "_",
-      ".SRW",
-      "_",
-      "gm",
-      "_",
-      "vertices_scaled_shader",
-      "_",
-      "gm",
-      "_",
-      "vertices",
-      "--match",
-      "~^InitialTextureClear$",
-      "~^RGB565TextureTest$",
-      "~^RGBA4444TextureTest$",
-      "~^WritePixelsNonTextureMSAA_Gpu$",
-      "--nonativeFonts",
-      "--verbose"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "dm"
-  },
-  {
-    "jsonResult": null,
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipes/test.expected/Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer.json b/infra/bots/recipes/test.expected/Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer.json
deleted file mode 100644
index aac46f5..0000000
--- a/infra/bots/recipes/test.expected/Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer.json
+++ /dev/null
@@ -1,503 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/tmp"
-    ],
-    "infra_step": true,
-    "name": "makedirs tmp_dir"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skp VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SKP_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SKP_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skimage VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SK_IMAGE_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SK_IMAGE_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get svg VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]/tmp/SVG_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SVG_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "rmtree",
-      "[START_DIR]/test"
-    ],
-    "infra_step": true,
-    "name": "rmtree test"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]/test"
-    ],
-    "infra_step": true,
-    "name": "makedirs test"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = sys.argv[1]\nRETRIES = 5\nTIMEOUT = 60\nWAIT_BASE = 15\n\nsocket.setdefaulttimeout(TIMEOUT)\nfor retry in range(RETRIES):\n  try:\n    with contextlib.closing(\n        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:\n      hashes = w.read()\n      with open(sys.argv[2], 'w') as f:\n        f.write(hashes)\n        break\n  except Exception as e:\n    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
-      "https://example.com/hashes.txt",
-      "[START_DIR]/tmp/uninteresting_hashes.txt"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "infra_step": true,
-    "name": "get uninteresting hashes",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
-      "@@@STEP_LOG_LINE@python.inline@import math@@@",
-      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
-      "@@@STEP_LOG_LINE@python.inline@import time@@@",
-      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@HASHES_URL = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@RETRIES = 5@@@",
-      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 60@@@",
-      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 15@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
-      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(@@@",
-      "@@@STEP_LOG_LINE@python.inline@        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      hashes = w.read()@@@",
-      "@@@STEP_LOG_LINE@python.inline@      with open(sys.argv[2], 'w') as f:@@@",
-      "@@@STEP_LOG_LINE@python.inline@        f.write(hashes)@@@",
-      "@@@STEP_LOG_LINE@python.inline@        break@@@",
-      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
-      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
-      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
-      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
-    ],
-    "name": "get swarming bot id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
-    ],
-    "name": "get swarming task id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/build/dm",
-      "--resourcePath",
-      "[START_DIR]/skia/resources",
-      "--skps",
-      "[START_DIR]/skp",
-      "--images",
-      "[START_DIR]/skimage/dm",
-      "--colorImages",
-      "[START_DIR]/skimage/colorspace",
-      "--nameByHash",
-      "--properties",
-      "gitHash",
-      "abc123",
-      "builder",
-      "Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer",
-      "buildbucket_build_id",
-      "123454321",
-      "swarming_bot_id",
-      "skia-bot-123",
-      "swarming_task_id",
-      "123456",
-      "--svgs",
-      "[START_DIR]/svg",
-      "--key",
-      "arch",
-      "x86_64",
-      "compiler",
-      "Clang",
-      "configuration",
-      "Debug",
-      "cpu_or_gpu",
-      "GPU",
-      "cpu_or_gpu_value",
-      "IntelIris5100",
-      "extra_config",
-      "CommandBuffer",
-      "model",
-      "MacMini7.1",
-      "os",
-      "Mac",
-      "--uninterestingHashesFile",
-      "[START_DIR]/tmp/uninteresting_hashes.txt",
-      "--writePath",
-      "[START_DIR]/[SWARM_OUT_DIR]",
-      "--dont_write",
-      "pdf",
-      "--randomProcessorTest",
-      "--nocpu",
-      "--config",
-      "commandbuffer",
-      "--src",
-      "tests",
-      "gm",
-      "image",
-      "colorImage",
-      "svg",
-      "--blacklist",
-      "_",
-      "svg",
-      "_",
-      "svgparse_",
-      "_",
-      "image",
-      "gen_platf",
-      "rgba32abf.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "rgb24prof.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "rgb24lprof.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "8bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "4bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "32bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "24bpp-pixeldata-cropped.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "frame_larger_than_image.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "inc0.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc1.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc2.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc3.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc4.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc5.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc6.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc7.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc8.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc9.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc10.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc11.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc12.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc13.png",
-      "_",
-      "image",
-      "gen_platf",
-      "inc14.png",
-      "_",
-      "image",
-      "gen_platf",
-      "incInterlaced.png",
-      "_",
-      "image",
-      "gen_platf",
-      "incInterlaced.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "inc1.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "inc0.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "butterfly.gif",
-      "_",
-      "image",
-      "gen_platf",
-      "testimgari.jpg",
-      "_",
-      "image",
-      "gen_platf",
-      "rle8-height-negative.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "rle4-height-negative.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "error",
-      "_",
-      "image",
-      "_",
-      "interlaced1.png",
-      "_",
-      "image",
-      "_",
-      "interlaced2.png",
-      "_",
-      "image",
-      "_",
-      "interlaced3.png",
-      "_",
-      "image",
-      "_",
-      ".arw",
-      "_",
-      "image",
-      "_",
-      ".cr2",
-      "_",
-      "image",
-      "_",
-      ".dng",
-      "_",
-      "image",
-      "_",
-      ".nef",
-      "_",
-      "image",
-      "_",
-      ".nrw",
-      "_",
-      "image",
-      "_",
-      ".orf",
-      "_",
-      "image",
-      "_",
-      ".raf",
-      "_",
-      "image",
-      "_",
-      ".rw2",
-      "_",
-      "image",
-      "_",
-      ".pef",
-      "_",
-      "image",
-      "_",
-      ".srw",
-      "_",
-      "image",
-      "_",
-      ".ARW",
-      "_",
-      "image",
-      "_",
-      ".CR2",
-      "_",
-      "image",
-      "_",
-      ".DNG",
-      "_",
-      "image",
-      "_",
-      ".NEF",
-      "_",
-      "image",
-      "_",
-      ".NRW",
-      "_",
-      "image",
-      "_",
-      ".ORF",
-      "_",
-      "image",
-      "_",
-      ".RAF",
-      "_",
-      "image",
-      "_",
-      ".RW2",
-      "_",
-      "image",
-      "_",
-      ".PEF",
-      "_",
-      "image",
-      "_",
-      ".SRW",
-      "--match",
-      "~HalfFloatAlphaTextureTest",
-      "--nonativeFonts",
-      "--verbose"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "dm"
-  },
-  {
-    "jsonResult": null,
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts.json b/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts.json
new file mode 100644
index 0000000..c8bb6c5
--- /dev/null
+++ b/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts.json
@@ -0,0 +1,504 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skp VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SKP_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get svg VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SVG_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[START_DIR]/test"
+    ],
+    "infra_step": true,
+    "name": "rmtree test"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/test"
+    ],
+    "infra_step": true,
+    "name": "makedirs test"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = sys.argv[1]\nRETRIES = 5\nTIMEOUT = 60\nWAIT_BASE = 15\n\nsocket.setdefaulttimeout(TIMEOUT)\nfor retry in range(RETRIES):\n  try:\n    with contextlib.closing(\n        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:\n      hashes = w.read()\n      with open(sys.argv[2], 'w') as f:\n        f.write(hashes)\n        break\n  except Exception as e:\n    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
+      "https://example.com/hashes.txt",
+      "[START_DIR]/tmp/uninteresting_hashes.txt"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "get uninteresting hashes",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
+      "@@@STEP_LOG_LINE@python.inline@import math@@@",
+      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@HASHES_URL = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@RETRIES = 5@@@",
+      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 60@@@",
+      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 15@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
+      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(@@@",
+      "@@@STEP_LOG_LINE@python.inline@        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      hashes = w.read()@@@",
+      "@@@STEP_LOG_LINE@python.inline@      with open(sys.argv[2], 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@        f.write(hashes)@@@",
+      "@@@STEP_LOG_LINE@python.inline@        break@@@",
+      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
+      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
+    ],
+    "name": "get swarming task id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/build/dm",
+      "--resourcePath",
+      "[START_DIR]/skia/resources",
+      "--skps",
+      "[START_DIR]/skp",
+      "--images",
+      "[START_DIR]/skimage/dm",
+      "--colorImages",
+      "[START_DIR]/skimage/colorspace",
+      "--nameByHash",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "builder",
+      "Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts",
+      "buildbucket_build_id",
+      "123454321",
+      "task_id",
+      "task_12345",
+      "swarming_bot_id",
+      "skia-bot-123",
+      "swarming_task_id",
+      "123456",
+      "--svgs",
+      "[START_DIR]/svg",
+      "--key",
+      "arch",
+      "x86_64",
+      "compiler",
+      "Clang",
+      "configuration",
+      "Release",
+      "cpu_or_gpu",
+      "GPU",
+      "cpu_or_gpu_value",
+      "IntelHD615",
+      "extra_config",
+      "NativeFonts",
+      "model",
+      "MacBook10.1",
+      "os",
+      "Mac10.13",
+      "style",
+      "default",
+      "--uninterestingHashesFile",
+      "[START_DIR]/tmp/uninteresting_hashes.txt",
+      "--writePath",
+      "[START_DIR]/[SWARM_OUT_DIR]",
+      "--dont_write",
+      "pdf",
+      "--randomProcessorTest",
+      "--nocpu",
+      "--config",
+      "gl",
+      "--src",
+      "tests",
+      "gm",
+      "svg",
+      "--blacklist",
+      "_",
+      "svg",
+      "_",
+      "svgparse_",
+      "_",
+      "image",
+      "gen_platf",
+      "rgba32abf.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rgb24prof.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rgb24lprof.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "8bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "4bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "32bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "24bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "frame_larger_than_image.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "inc0.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc1.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc2.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc3.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc4.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc5.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc6.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc7.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc8.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc9.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc10.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc11.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc12.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc13.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc14.png",
+      "_",
+      "image",
+      "gen_platf",
+      "incInterlaced.png",
+      "_",
+      "image",
+      "gen_platf",
+      "incInterlaced.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "inc1.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "inc0.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "butterfly.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "testimgari.jpg",
+      "_",
+      "image",
+      "gen_platf",
+      "rle8-height-negative.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rle4-height-negative.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "error",
+      "_",
+      "image",
+      "_",
+      "interlaced1.png",
+      "_",
+      "image",
+      "_",
+      "interlaced2.png",
+      "_",
+      "image",
+      "_",
+      "interlaced3.png",
+      "_",
+      "image",
+      "_",
+      ".arw",
+      "_",
+      "image",
+      "_",
+      ".cr2",
+      "_",
+      "image",
+      "_",
+      ".dng",
+      "_",
+      "image",
+      "_",
+      ".nef",
+      "_",
+      "image",
+      "_",
+      ".nrw",
+      "_",
+      "image",
+      "_",
+      ".orf",
+      "_",
+      "image",
+      "_",
+      ".raf",
+      "_",
+      "image",
+      "_",
+      ".rw2",
+      "_",
+      "image",
+      "_",
+      ".pef",
+      "_",
+      "image",
+      "_",
+      ".srw",
+      "_",
+      "image",
+      "_",
+      ".ARW",
+      "_",
+      "image",
+      "_",
+      ".CR2",
+      "_",
+      "image",
+      "_",
+      ".DNG",
+      "_",
+      "image",
+      "_",
+      ".NEF",
+      "_",
+      "image",
+      "_",
+      ".NRW",
+      "_",
+      "image",
+      "_",
+      ".ORF",
+      "_",
+      "image",
+      "_",
+      ".RAF",
+      "_",
+      "image",
+      "_",
+      ".RW2",
+      "_",
+      "image",
+      "_",
+      ".PEF",
+      "_",
+      "image",
+      "_",
+      ".SRW",
+      "--match",
+      "~^GrMeshTest$",
+      "--verbose"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "dm"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All.json b/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All.json
new file mode 100644
index 0000000..a475581
--- /dev/null
+++ b/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All.json
@@ -0,0 +1,406 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skp VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SKP_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get svg VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SVG_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[START_DIR]/test"
+    ],
+    "infra_step": true,
+    "name": "rmtree test"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/test"
+    ],
+    "infra_step": true,
+    "name": "makedirs test"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = sys.argv[1]\nRETRIES = 5\nTIMEOUT = 60\nWAIT_BASE = 15\n\nsocket.setdefaulttimeout(TIMEOUT)\nfor retry in range(RETRIES):\n  try:\n    with contextlib.closing(\n        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:\n      hashes = w.read()\n      with open(sys.argv[2], 'w') as f:\n        f.write(hashes)\n        break\n  except Exception as e:\n    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
+      "https://example.com/hashes.txt",
+      "[START_DIR]/tmp/uninteresting_hashes.txt"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "get uninteresting hashes",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
+      "@@@STEP_LOG_LINE@python.inline@import math@@@",
+      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@HASHES_URL = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@RETRIES = 5@@@",
+      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 60@@@",
+      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 15@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
+      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(@@@",
+      "@@@STEP_LOG_LINE@python.inline@        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      hashes = w.read()@@@",
+      "@@@STEP_LOG_LINE@python.inline@      with open(sys.argv[2], 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@        f.write(hashes)@@@",
+      "@@@STEP_LOG_LINE@python.inline@        break@@@",
+      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
+      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
+    ],
+    "name": "get swarming task id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/build/dm",
+      "--resourcePath",
+      "[START_DIR]/skia/resources",
+      "--skps",
+      "[START_DIR]/skp",
+      "--images",
+      "[START_DIR]/skimage/dm",
+      "--colorImages",
+      "[START_DIR]/skimage/colorspace",
+      "--nameByHash",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "builder",
+      "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All",
+      "buildbucket_build_id",
+      "123454321",
+      "task_id",
+      "task_12345",
+      "swarming_bot_id",
+      "skia-bot-123",
+      "swarming_task_id",
+      "123456",
+      "--svgs",
+      "[START_DIR]/svg",
+      "--key",
+      "arch",
+      "x86_64",
+      "compiler",
+      "Clang",
+      "configuration",
+      "Release",
+      "cpu_or_gpu",
+      "CPU",
+      "cpu_or_gpu_value",
+      "AVX2",
+      "model",
+      "MacBookPro11.5",
+      "os",
+      "Mac10.13",
+      "style",
+      "default",
+      "--uninterestingHashesFile",
+      "[START_DIR]/tmp/uninteresting_hashes.txt",
+      "--writePath",
+      "[START_DIR]/[SWARM_OUT_DIR]",
+      "--dont_write",
+      "pdf",
+      "--randomProcessorTest",
+      "--nogpu",
+      "--config",
+      "8888",
+      "--src",
+      "tests",
+      "gm",
+      "image",
+      "colorImage",
+      "--blacklist",
+      "_",
+      "image",
+      "gen_platf",
+      "rgba32abf.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rgb24prof.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rgb24lprof.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "8bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "4bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "32bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "24bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "frame_larger_than_image.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "inc0.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc1.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc2.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc3.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc4.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc5.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc6.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc7.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc8.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc9.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc10.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc11.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc12.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc13.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc14.png",
+      "_",
+      "image",
+      "gen_platf",
+      "incInterlaced.png",
+      "_",
+      "image",
+      "gen_platf",
+      "incInterlaced.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "inc1.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "inc0.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "butterfly.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "testimgari.jpg",
+      "_",
+      "image",
+      "gen_platf",
+      "rle8-height-negative.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rle4-height-negative.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "error",
+      "--nonativeFonts",
+      "--verbose"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "dm"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal.json b/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal.json
new file mode 100644
index 0000000..e2bf6c2
--- /dev/null
+++ b/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal.json
@@ -0,0 +1,508 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skp VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SKP_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get svg VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SVG_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[START_DIR]/test"
+    ],
+    "infra_step": true,
+    "name": "rmtree test"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/test"
+    ],
+    "infra_step": true,
+    "name": "makedirs test"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = sys.argv[1]\nRETRIES = 5\nTIMEOUT = 60\nWAIT_BASE = 15\n\nsocket.setdefaulttimeout(TIMEOUT)\nfor retry in range(RETRIES):\n  try:\n    with contextlib.closing(\n        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:\n      hashes = w.read()\n      with open(sys.argv[2], 'w') as f:\n        f.write(hashes)\n        break\n  except Exception as e:\n    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
+      "https://example.com/hashes.txt",
+      "[START_DIR]/tmp/uninteresting_hashes.txt"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "get uninteresting hashes",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
+      "@@@STEP_LOG_LINE@python.inline@import math@@@",
+      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@HASHES_URL = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@RETRIES = 5@@@",
+      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 60@@@",
+      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 15@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
+      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(@@@",
+      "@@@STEP_LOG_LINE@python.inline@        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      hashes = w.read()@@@",
+      "@@@STEP_LOG_LINE@python.inline@      with open(sys.argv[2], 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@        f.write(hashes)@@@",
+      "@@@STEP_LOG_LINE@python.inline@        break@@@",
+      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
+      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
+    ],
+    "name": "get swarming task id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/build/dm",
+      "--resourcePath",
+      "[START_DIR]/skia/resources",
+      "--skps",
+      "[START_DIR]/skp",
+      "--images",
+      "[START_DIR]/skimage/dm",
+      "--colorImages",
+      "[START_DIR]/skimage/colorspace",
+      "--nameByHash",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "builder",
+      "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal",
+      "buildbucket_build_id",
+      "123454321",
+      "task_id",
+      "task_12345",
+      "swarming_bot_id",
+      "skia-bot-123",
+      "swarming_task_id",
+      "123456",
+      "--svgs",
+      "[START_DIR]/svg",
+      "--key",
+      "arch",
+      "x86_64",
+      "compiler",
+      "Clang",
+      "configuration",
+      "Debug",
+      "cpu_or_gpu",
+      "GPU",
+      "cpu_or_gpu_value",
+      "RadeonHD8870M",
+      "extra_config",
+      "Metal",
+      "model",
+      "MacBookPro11.5",
+      "os",
+      "Mac10.13",
+      "style",
+      "default",
+      "--uninterestingHashesFile",
+      "[START_DIR]/tmp/uninteresting_hashes.txt",
+      "--writePath",
+      "[START_DIR]/[SWARM_OUT_DIR]",
+      "--dont_write",
+      "pdf",
+      "--randomProcessorTest",
+      "--nocpu",
+      "--config",
+      "mtl",
+      "--src",
+      "tests",
+      "gm",
+      "image",
+      "colorImage",
+      "svg",
+      "--blacklist",
+      "_",
+      "svg",
+      "_",
+      "svgparse_",
+      "_",
+      "image",
+      "gen_platf",
+      "rgba32abf.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rgb24prof.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rgb24lprof.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "8bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "4bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "32bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "24bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "frame_larger_than_image.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "inc0.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc1.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc2.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc3.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc4.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc5.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc6.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc7.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc8.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc9.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc10.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc11.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc12.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc13.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc14.png",
+      "_",
+      "image",
+      "gen_platf",
+      "incInterlaced.png",
+      "_",
+      "image",
+      "gen_platf",
+      "incInterlaced.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "inc1.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "inc0.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "butterfly.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "testimgari.jpg",
+      "_",
+      "image",
+      "gen_platf",
+      "rle8-height-negative.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rle4-height-negative.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "error",
+      "_",
+      "image",
+      "_",
+      "interlaced1.png",
+      "_",
+      "image",
+      "_",
+      "interlaced2.png",
+      "_",
+      "image",
+      "_",
+      "interlaced3.png",
+      "_",
+      "image",
+      "_",
+      ".arw",
+      "_",
+      "image",
+      "_",
+      ".cr2",
+      "_",
+      "image",
+      "_",
+      ".dng",
+      "_",
+      "image",
+      "_",
+      ".nef",
+      "_",
+      "image",
+      "_",
+      ".nrw",
+      "_",
+      "image",
+      "_",
+      ".orf",
+      "_",
+      "image",
+      "_",
+      ".raf",
+      "_",
+      "image",
+      "_",
+      ".rw2",
+      "_",
+      "image",
+      "_",
+      ".pef",
+      "_",
+      "image",
+      "_",
+      ".srw",
+      "_",
+      "image",
+      "_",
+      ".ARW",
+      "_",
+      "image",
+      "_",
+      ".CR2",
+      "_",
+      "image",
+      "_",
+      ".DNG",
+      "_",
+      "image",
+      "_",
+      ".NEF",
+      "_",
+      "image",
+      "_",
+      ".NRW",
+      "_",
+      "image",
+      "_",
+      ".ORF",
+      "_",
+      "image",
+      "_",
+      ".RAF",
+      "_",
+      "image",
+      "_",
+      ".RW2",
+      "_",
+      "image",
+      "_",
+      ".PEF",
+      "_",
+      "image",
+      "_",
+      ".SRW",
+      "--match",
+      "~^DDLOperatorEqTest$",
+      "~^WritePixelsNonTexture_Gpu$",
+      "--nonativeFonts",
+      "--verbose"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "dm"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan.json b/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan.json
new file mode 100644
index 0000000..523d013
--- /dev/null
+++ b/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan.json
@@ -0,0 +1,520 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skp VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SKP_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get svg VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SVG_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[START_DIR]/test"
+    ],
+    "infra_step": true,
+    "name": "rmtree test"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/test"
+    ],
+    "infra_step": true,
+    "name": "makedirs test"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = sys.argv[1]\nRETRIES = 5\nTIMEOUT = 60\nWAIT_BASE = 15\n\nsocket.setdefaulttimeout(TIMEOUT)\nfor retry in range(RETRIES):\n  try:\n    with contextlib.closing(\n        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:\n      hashes = w.read()\n      with open(sys.argv[2], 'w') as f:\n        f.write(hashes)\n        break\n  except Exception as e:\n    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
+      "https://example.com/hashes.txt",
+      "[START_DIR]/tmp/uninteresting_hashes.txt"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "get uninteresting hashes",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
+      "@@@STEP_LOG_LINE@python.inline@import math@@@",
+      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@HASHES_URL = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@RETRIES = 5@@@",
+      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 60@@@",
+      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 15@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
+      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(@@@",
+      "@@@STEP_LOG_LINE@python.inline@        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      hashes = w.read()@@@",
+      "@@@STEP_LOG_LINE@python.inline@      with open(sys.argv[2], 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@        f.write(hashes)@@@",
+      "@@@STEP_LOG_LINE@python.inline@        break@@@",
+      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
+      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
+    ],
+    "name": "get swarming task id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/build/dm",
+      "--resourcePath",
+      "[START_DIR]/skia/resources",
+      "--skps",
+      "[START_DIR]/skp",
+      "--images",
+      "[START_DIR]/skimage/dm",
+      "--colorImages",
+      "[START_DIR]/skimage/colorspace",
+      "--nameByHash",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "builder",
+      "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan",
+      "buildbucket_build_id",
+      "123454321",
+      "task_id",
+      "task_12345",
+      "swarming_bot_id",
+      "skia-bot-123",
+      "swarming_task_id",
+      "123456",
+      "--svgs",
+      "[START_DIR]/svg",
+      "--key",
+      "arch",
+      "x86_64",
+      "compiler",
+      "Clang",
+      "configuration",
+      "Release",
+      "cpu_or_gpu",
+      "GPU",
+      "cpu_or_gpu_value",
+      "RadeonHD8870M",
+      "extra_config",
+      "MoltenVK_Vulkan",
+      "model",
+      "MacBookPro11.5",
+      "os",
+      "Mac10.13",
+      "style",
+      "default",
+      "--uninterestingHashesFile",
+      "[START_DIR]/tmp/uninteresting_hashes.txt",
+      "--writePath",
+      "[START_DIR]/[SWARM_OUT_DIR]",
+      "--dont_write",
+      "pdf",
+      "--randomProcessorTest",
+      "--nocpu",
+      "--config",
+      "vk",
+      "--src",
+      "tests",
+      "gm",
+      "image",
+      "colorImage",
+      "svg",
+      "--blacklist",
+      "_",
+      "svg",
+      "_",
+      "svgparse_",
+      "_",
+      "image",
+      "gen_platf",
+      "rgba32abf.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rgb24prof.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rgb24lprof.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "8bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "4bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "32bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "24bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "frame_larger_than_image.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "inc0.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc1.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc2.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc3.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc4.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc5.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc6.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc7.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc8.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc9.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc10.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc11.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc12.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc13.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc14.png",
+      "_",
+      "image",
+      "gen_platf",
+      "incInterlaced.png",
+      "_",
+      "image",
+      "gen_platf",
+      "incInterlaced.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "inc1.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "inc0.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "butterfly.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "testimgari.jpg",
+      "_",
+      "image",
+      "gen_platf",
+      "rle8-height-negative.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rle4-height-negative.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "error",
+      "_",
+      "image",
+      "_",
+      "interlaced1.png",
+      "_",
+      "image",
+      "_",
+      "interlaced2.png",
+      "_",
+      "image",
+      "_",
+      "interlaced3.png",
+      "_",
+      "image",
+      "_",
+      ".arw",
+      "_",
+      "image",
+      "_",
+      ".cr2",
+      "_",
+      "image",
+      "_",
+      ".dng",
+      "_",
+      "image",
+      "_",
+      ".nef",
+      "_",
+      "image",
+      "_",
+      ".nrw",
+      "_",
+      "image",
+      "_",
+      ".orf",
+      "_",
+      "image",
+      "_",
+      ".raf",
+      "_",
+      "image",
+      "_",
+      ".rw2",
+      "_",
+      "image",
+      "_",
+      ".pef",
+      "_",
+      "image",
+      "_",
+      ".srw",
+      "_",
+      "image",
+      "_",
+      ".ARW",
+      "_",
+      "image",
+      "_",
+      ".CR2",
+      "_",
+      "image",
+      "_",
+      ".DNG",
+      "_",
+      "image",
+      "_",
+      ".NEF",
+      "_",
+      "image",
+      "_",
+      ".NRW",
+      "_",
+      "image",
+      "_",
+      ".ORF",
+      "_",
+      "image",
+      "_",
+      ".RAF",
+      "_",
+      "image",
+      "_",
+      ".RW2",
+      "_",
+      "image",
+      "_",
+      ".PEF",
+      "_",
+      "image",
+      "_",
+      ".SRW",
+      "_",
+      "gm",
+      "_",
+      "vertices_scaled_shader",
+      "_",
+      "gm",
+      "_",
+      "vertices",
+      "--match",
+      "~^InitialTextureClear$",
+      "~^RGB565TextureTest$",
+      "~^RGBA4444TextureTest$",
+      "~^TextureIdleProcFlushTest$",
+      "~^TextureStripAtlasManagerColorFilterTest$",
+      "~^WritePixelsNonTextureMSAA_Gpu$",
+      "--nonativeFonts",
+      "--verbose"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "dm"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer.json b/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer.json
new file mode 100644
index 0000000..6732f78
--- /dev/null
+++ b/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer.json
@@ -0,0 +1,507 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skp VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SKP_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get svg VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SVG_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[START_DIR]/test"
+    ],
+    "infra_step": true,
+    "name": "rmtree test"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/test"
+    ],
+    "infra_step": true,
+    "name": "makedirs test"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = sys.argv[1]\nRETRIES = 5\nTIMEOUT = 60\nWAIT_BASE = 15\n\nsocket.setdefaulttimeout(TIMEOUT)\nfor retry in range(RETRIES):\n  try:\n    with contextlib.closing(\n        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:\n      hashes = w.read()\n      with open(sys.argv[2], 'w') as f:\n        f.write(hashes)\n        break\n  except Exception as e:\n    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
+      "https://example.com/hashes.txt",
+      "[START_DIR]/tmp/uninteresting_hashes.txt"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "get uninteresting hashes",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
+      "@@@STEP_LOG_LINE@python.inline@import math@@@",
+      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@HASHES_URL = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@RETRIES = 5@@@",
+      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 60@@@",
+      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 15@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
+      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(@@@",
+      "@@@STEP_LOG_LINE@python.inline@        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      hashes = w.read()@@@",
+      "@@@STEP_LOG_LINE@python.inline@      with open(sys.argv[2], 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@        f.write(hashes)@@@",
+      "@@@STEP_LOG_LINE@python.inline@        break@@@",
+      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
+      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
+    ],
+    "name": "get swarming task id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/build/dm",
+      "--resourcePath",
+      "[START_DIR]/skia/resources",
+      "--skps",
+      "[START_DIR]/skp",
+      "--images",
+      "[START_DIR]/skimage/dm",
+      "--colorImages",
+      "[START_DIR]/skimage/colorspace",
+      "--nameByHash",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "builder",
+      "Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer",
+      "buildbucket_build_id",
+      "123454321",
+      "task_id",
+      "task_12345",
+      "swarming_bot_id",
+      "skia-bot-123",
+      "swarming_task_id",
+      "123456",
+      "--svgs",
+      "[START_DIR]/svg",
+      "--key",
+      "arch",
+      "x86_64",
+      "compiler",
+      "Clang",
+      "configuration",
+      "Debug",
+      "cpu_or_gpu",
+      "GPU",
+      "cpu_or_gpu_value",
+      "IntelIris5100",
+      "extra_config",
+      "CommandBuffer",
+      "model",
+      "MacMini7.1",
+      "os",
+      "Mac10.13",
+      "style",
+      "default",
+      "--uninterestingHashesFile",
+      "[START_DIR]/tmp/uninteresting_hashes.txt",
+      "--writePath",
+      "[START_DIR]/[SWARM_OUT_DIR]",
+      "--dont_write",
+      "pdf",
+      "--randomProcessorTest",
+      "--nocpu",
+      "--config",
+      "commandbuffer",
+      "--src",
+      "tests",
+      "gm",
+      "image",
+      "colorImage",
+      "svg",
+      "--blacklist",
+      "_",
+      "svg",
+      "_",
+      "svgparse_",
+      "_",
+      "image",
+      "gen_platf",
+      "rgba32abf.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rgb24prof.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rgb24lprof.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "8bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "4bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "32bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "24bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "frame_larger_than_image.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "inc0.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc1.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc2.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc3.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc4.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc5.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc6.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc7.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc8.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc9.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc10.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc11.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc12.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc13.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc14.png",
+      "_",
+      "image",
+      "gen_platf",
+      "incInterlaced.png",
+      "_",
+      "image",
+      "gen_platf",
+      "incInterlaced.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "inc1.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "inc0.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "butterfly.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "testimgari.jpg",
+      "_",
+      "image",
+      "gen_platf",
+      "rle8-height-negative.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rle4-height-negative.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "error",
+      "_",
+      "image",
+      "_",
+      "interlaced1.png",
+      "_",
+      "image",
+      "_",
+      "interlaced2.png",
+      "_",
+      "image",
+      "_",
+      "interlaced3.png",
+      "_",
+      "image",
+      "_",
+      ".arw",
+      "_",
+      "image",
+      "_",
+      ".cr2",
+      "_",
+      "image",
+      "_",
+      ".dng",
+      "_",
+      "image",
+      "_",
+      ".nef",
+      "_",
+      "image",
+      "_",
+      ".nrw",
+      "_",
+      "image",
+      "_",
+      ".orf",
+      "_",
+      "image",
+      "_",
+      ".raf",
+      "_",
+      "image",
+      "_",
+      ".rw2",
+      "_",
+      "image",
+      "_",
+      ".pef",
+      "_",
+      "image",
+      "_",
+      ".srw",
+      "_",
+      "image",
+      "_",
+      ".ARW",
+      "_",
+      "image",
+      "_",
+      ".CR2",
+      "_",
+      "image",
+      "_",
+      ".DNG",
+      "_",
+      "image",
+      "_",
+      ".NEF",
+      "_",
+      "image",
+      "_",
+      ".NRW",
+      "_",
+      "image",
+      "_",
+      ".ORF",
+      "_",
+      "image",
+      "_",
+      ".RAF",
+      "_",
+      "image",
+      "_",
+      ".RW2",
+      "_",
+      "image",
+      "_",
+      ".PEF",
+      "_",
+      "image",
+      "_",
+      ".SRW",
+      "--match",
+      "~HalfFloatAlphaTextureTest",
+      "--nonativeFonts",
+      "--verbose"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "dm"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/test.expected/Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All.json b/infra/bots/recipes/test.expected/Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All.json
new file mode 100644
index 0000000..bcd0287
--- /dev/null
+++ b/infra/bots/recipes/test.expected/Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All.json
@@ -0,0 +1,512 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skp VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SKP_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get svg VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SVG_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[START_DIR]/test"
+    ],
+    "infra_step": true,
+    "name": "rmtree test"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/test"
+    ],
+    "infra_step": true,
+    "name": "makedirs test"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = sys.argv[1]\nRETRIES = 5\nTIMEOUT = 60\nWAIT_BASE = 15\n\nsocket.setdefaulttimeout(TIMEOUT)\nfor retry in range(RETRIES):\n  try:\n    with contextlib.closing(\n        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:\n      hashes = w.read()\n      with open(sys.argv[2], 'w') as f:\n        f.write(hashes)\n        break\n  except Exception as e:\n    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
+      "https://example.com/hashes.txt",
+      "[START_DIR]/tmp/uninteresting_hashes.txt"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "get uninteresting hashes",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
+      "@@@STEP_LOG_LINE@python.inline@import math@@@",
+      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@HASHES_URL = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@RETRIES = 5@@@",
+      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 60@@@",
+      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 15@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
+      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(@@@",
+      "@@@STEP_LOG_LINE@python.inline@        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      hashes = w.read()@@@",
+      "@@@STEP_LOG_LINE@python.inline@      with open(sys.argv[2], 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@        f.write(hashes)@@@",
+      "@@@STEP_LOG_LINE@python.inline@        break@@@",
+      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
+      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
+    ],
+    "name": "get swarming task id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/build/dm",
+      "--resourcePath",
+      "[START_DIR]/skia/resources",
+      "--skps",
+      "[START_DIR]/skp",
+      "--images",
+      "[START_DIR]/skimage/dm",
+      "--colorImages",
+      "[START_DIR]/skimage/colorspace",
+      "--nameByHash",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "builder",
+      "Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All",
+      "buildbucket_build_id",
+      "123454321",
+      "task_id",
+      "task_12345",
+      "swarming_bot_id",
+      "skia-bot-123",
+      "swarming_task_id",
+      "123456",
+      "--svgs",
+      "[START_DIR]/svg",
+      "--key",
+      "arch",
+      "x86_64",
+      "compiler",
+      "Clang",
+      "configuration",
+      "Debug",
+      "cpu_or_gpu",
+      "GPU",
+      "cpu_or_gpu_value",
+      "IntelHD6000",
+      "model",
+      "MacBookAir7.2",
+      "os",
+      "Mac10.14",
+      "style",
+      "default",
+      "--uninterestingHashesFile",
+      "[START_DIR]/tmp/uninteresting_hashes.txt",
+      "--writePath",
+      "[START_DIR]/[SWARM_OUT_DIR]",
+      "--dont_write",
+      "pdf",
+      "--randomProcessorTest",
+      "--nocpu",
+      "--config",
+      "gl",
+      "gldft",
+      "glsrgb",
+      "--src",
+      "tests",
+      "gm",
+      "image",
+      "colorImage",
+      "svg",
+      "--blacklist",
+      "_",
+      "svg",
+      "_",
+      "svgparse_",
+      "glsrgb",
+      "image",
+      "_",
+      "_",
+      "_",
+      "image",
+      "gen_platf",
+      "rgba32abf.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rgb24prof.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rgb24lprof.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "8bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "4bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "32bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "24bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "frame_larger_than_image.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "inc0.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc1.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc2.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc3.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc4.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc5.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc6.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc7.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc8.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc9.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc10.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc11.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc12.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc13.png",
+      "_",
+      "image",
+      "gen_platf",
+      "inc14.png",
+      "_",
+      "image",
+      "gen_platf",
+      "incInterlaced.png",
+      "_",
+      "image",
+      "gen_platf",
+      "incInterlaced.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "inc1.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "inc0.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "butterfly.gif",
+      "_",
+      "image",
+      "gen_platf",
+      "testimgari.jpg",
+      "_",
+      "image",
+      "gen_platf",
+      "rle8-height-negative.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rle4-height-negative.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "error",
+      "_",
+      "image",
+      "_",
+      "interlaced1.png",
+      "_",
+      "image",
+      "_",
+      "interlaced2.png",
+      "_",
+      "image",
+      "_",
+      "interlaced3.png",
+      "_",
+      "image",
+      "_",
+      ".arw",
+      "_",
+      "image",
+      "_",
+      ".cr2",
+      "_",
+      "image",
+      "_",
+      ".dng",
+      "_",
+      "image",
+      "_",
+      ".nef",
+      "_",
+      "image",
+      "_",
+      ".nrw",
+      "_",
+      "image",
+      "_",
+      ".orf",
+      "_",
+      "image",
+      "_",
+      ".raf",
+      "_",
+      "image",
+      "_",
+      ".rw2",
+      "_",
+      "image",
+      "_",
+      ".pef",
+      "_",
+      "image",
+      "_",
+      ".srw",
+      "_",
+      "image",
+      "_",
+      ".ARW",
+      "_",
+      "image",
+      "_",
+      ".CR2",
+      "_",
+      "image",
+      "_",
+      ".DNG",
+      "_",
+      "image",
+      "_",
+      ".NEF",
+      "_",
+      "image",
+      "_",
+      ".NRW",
+      "_",
+      "image",
+      "_",
+      ".ORF",
+      "_",
+      "image",
+      "_",
+      ".RAF",
+      "_",
+      "image",
+      "_",
+      ".RW2",
+      "_",
+      "image",
+      "_",
+      ".PEF",
+      "_",
+      "image",
+      "_",
+      ".SRW",
+      "--match",
+      "~^ProcessorCloneTest$",
+      "~^GrMeshTest$",
+      "--nonativeFonts",
+      "--verbose"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "name": "dm"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1.json b/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1.json
index 97855ad..0efd870 100644
--- a/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1.json
+++ b/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -226,6 +226,8 @@
       "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -249,6 +251,8 @@
       "Golo",
       "os",
       "Ubuntu17",
+      "style",
+      "DDL",
       "--uninterestingHashesFile",
       "[START_DIR]/tmp/uninteresting_hashes.txt",
       "--writePath",
@@ -283,6 +287,18 @@
       "gm",
       "_",
       "glyph_pos_h_b",
+      "gltestglslcache",
+      "gm",
+      "_",
+      "atlastext",
+      "gltestglslcache",
+      "gm",
+      "_",
+      "dftext",
+      "gltestglslcache",
+      "gm",
+      "_",
+      "glyph_pos_h_b",
       "_",
       "svg",
       "_",
@@ -390,7 +406,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "symbolized dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3.json b/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3.json
index b389be9..b381bf1 100644
--- a/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3.json
+++ b/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -226,6 +226,8 @@
       "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -249,6 +251,8 @@
       "Golo",
       "os",
       "Ubuntu17",
+      "style",
+      "DDL",
       "--uninterestingHashesFile",
       "[START_DIR]/tmp/uninteresting_hashes.txt",
       "--writePath",
@@ -284,6 +288,18 @@
       "gm",
       "_",
       "glyph_pos_h_b",
+      "gltestglslcache",
+      "gm",
+      "_",
+      "atlastext",
+      "gltestglslcache",
+      "gm",
+      "_",
+      "dftext",
+      "gltestglslcache",
+      "gm",
+      "_",
+      "glyph_pos_h_b",
       "_",
       "svg",
       "_",
@@ -391,7 +407,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "symbolized dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Lottie.json b/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Lottie.json
index 417b254..0526c13 100644
--- a/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Lottie.json
+++ b/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Lottie.json
@@ -80,7 +80,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -170,6 +170,8 @@
       "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Lottie",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -197,6 +199,8 @@
       "Ubuntu17",
       "renderer",
       "skottie",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]/tmp/uninteresting_hashes.txt",
       "--writePath",
@@ -226,6 +230,18 @@
       "gm",
       "_",
       "glyph_pos_h_b",
+      "gltestglslcache",
+      "gm",
+      "_",
+      "atlastext",
+      "gltestglslcache",
+      "gm",
+      "_",
+      "dftext",
+      "gltestglslcache",
+      "gm",
+      "_",
+      "glyph_pos_h_b",
       "_",
       "svg",
       "_",
@@ -333,7 +349,7 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "symbolized dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan_Coverage.json b/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan_Coverage.json
index bcbc898..c9e5f97 100644
--- a/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan_Coverage.json
+++ b/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan_Coverage.json
@@ -150,6 +150,8 @@
       "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan_Coverage",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -173,6 +175,8 @@
       "Golo",
       "os",
       "Ubuntu17",
+      "style",
+      "default",
       "--dont_write",
       "pdf",
       "--randomProcessorTest",
@@ -300,7 +304,7 @@
       "CHROME_HEADLESS": "1",
       "LD_LIBRARY_PATH": "[START_DIR]/linux_vulkan_sdk/lib",
       "LLVM_PROFILE_FILE": "[START_DIR]/[SWARM_OUT_DIR]/All.profraw",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]:[START_DIR]/linux_vulkan_sdk/bin"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]:[START_DIR]/linux_vulkan_sdk/bin"
     },
     "name": "symbolized dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41.json b/infra/bots/recipes/test.expected/Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41.json
index e6e1f06..da0cbd5 100644
--- a/infra/bots/recipes/test.expected/Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41.json
+++ b/infra/bots/recipes/test.expected/Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41.json
@@ -152,6 +152,8 @@
       "Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -175,6 +177,8 @@
       "Golo",
       "os",
       "Ubuntu17",
+      "style",
+      "default",
       "--dont_write",
       "pdf",
       "--randomProcessorTest",
@@ -186,6 +190,7 @@
       "glmsaa8",
       "gl1010102",
       "gltestpersistentcache",
+      "gltestglslcache",
       "--src",
       "tests",
       "gm",
@@ -209,6 +214,18 @@
       "gm",
       "_",
       "glyph_pos_h_b",
+      "gltestglslcache",
+      "gm",
+      "_",
+      "atlastext",
+      "gltestglslcache",
+      "gm",
+      "_",
+      "dftext",
+      "gltestglslcache",
+      "gm",
+      "_",
+      "glyph_pos_h_b",
       "_",
       "svg",
       "_",
@@ -326,7 +343,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]",
       "VALGRIND_LIB": "[START_DIR]/valgrind/lib/valgrind"
     },
     "name": "dm"
diff --git a/infra/bots/recipes/test.expected/Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_PreAbandonGpuContext_SK_CPU_LIMIT_SSE41.json b/infra/bots/recipes/test.expected/Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_PreAbandonGpuContext_SK_CPU_LIMIT_SSE41.json
index 4cfed78..891ffde 100644
--- a/infra/bots/recipes/test.expected/Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_PreAbandonGpuContext_SK_CPU_LIMIT_SSE41.json
+++ b/infra/bots/recipes/test.expected/Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_PreAbandonGpuContext_SK_CPU_LIMIT_SSE41.json
@@ -152,6 +152,8 @@
       "Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_PreAbandonGpuContext_SK_CPU_LIMIT_SSE41",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -175,6 +177,8 @@
       "Golo",
       "os",
       "Ubuntu17",
+      "style",
+      "default",
       "--dont_write",
       "pdf",
       "--randomProcessorTest",
@@ -309,7 +313,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]",
       "VALGRIND_LIB": "[START_DIR]/valgrind/lib/valgrind"
     },
     "name": "dm"
diff --git a/infra/bots/recipes/test.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-BonusConfigs.json b/infra/bots/recipes/test.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-BonusConfigs.json
index e4a0872..c922d67 100644
--- a/infra/bots/recipes/test.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-BonusConfigs.json
+++ b/infra/bots/recipes/test.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-BonusConfigs.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -221,6 +221,8 @@
       "Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-BonusConfigs",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -244,6 +246,8 @@
       "Golo",
       "os",
       "Win10",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]\\tmp\\uninteresting_hashes.txt",
       "--writePath",
@@ -253,7 +257,6 @@
       "--randomProcessorTest",
       "--nocpu",
       "--config",
-      "gbr-gl",
       "glbetex",
       "glbert",
       "glenarrow",
@@ -418,7 +421,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "name": "dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ReleaseAndAbandonGpuContext.json b/infra/bots/recipes/test.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ReleaseAndAbandonGpuContext.json
index d79a4e7..8dcbe07 100644
--- a/infra/bots/recipes/test.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ReleaseAndAbandonGpuContext.json
+++ b/infra/bots/recipes/test.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ReleaseAndAbandonGpuContext.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -221,6 +221,8 @@
       "Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ReleaseAndAbandonGpuContext",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -244,6 +246,8 @@
       "Golo",
       "os",
       "Win10",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]\\tmp\\uninteresting_hashes.txt",
       "--writePath",
@@ -423,7 +427,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "name": "dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Win10-Clang-NUC5i7RYH-CPU-AVX2-x86_64-Debug-All-NativeFonts_GDI.json b/infra/bots/recipes/test.expected/Test-Win10-Clang-NUC5i7RYH-CPU-AVX2-x86_64-Debug-All-NativeFonts_GDI.json
index 8838f80..217ad20 100644
--- a/infra/bots/recipes/test.expected/Test-Win10-Clang-NUC5i7RYH-CPU-AVX2-x86_64-Debug-All-NativeFonts_GDI.json
+++ b/infra/bots/recipes/test.expected/Test-Win10-Clang-NUC5i7RYH-CPU-AVX2-x86_64-Debug-All-NativeFonts_GDI.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -221,6 +221,8 @@
       "Test-Win10-Clang-NUC5i7RYH-CPU-AVX2-x86_64-Debug-All-NativeFonts_GDI",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -244,6 +246,8 @@
       "NUC5i7RYH",
       "os",
       "Win10",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]\\tmp\\uninteresting_hashes.txt",
       "--writePath",
@@ -319,7 +323,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "name": "dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All-ANGLE.json b/infra/bots/recipes/test.expected/Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All-ANGLE.json
index b0968bf..6ee0921 100644
--- a/infra/bots/recipes/test.expected/Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All-ANGLE.json
+++ b/infra/bots/recipes/test.expected/Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All-ANGLE.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -221,6 +221,8 @@
       "Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All-ANGLE",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -244,6 +246,8 @@
       "NUC5i7RYH",
       "os",
       "Win10",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]\\tmp\\uninteresting_hashes.txt",
       "--writePath",
@@ -429,7 +433,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "name": "dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan.json b/infra/bots/recipes/test.expected/Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan.json
index 3b479a5..1698475 100644
--- a/infra/bots/recipes/test.expected/Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan.json
+++ b/infra/bots/recipes/test.expected/Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -221,6 +221,8 @@
       "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -244,6 +246,8 @@
       "NUC8i5BEK",
       "os",
       "Win10",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]\\tmp\\uninteresting_hashes.txt",
       "--writePath",
@@ -412,6 +416,10 @@
       "vk",
       "gm",
       "_",
+      "savelayer_clipmask",
+      "vk",
+      "gm",
+      "_",
       "aarectmodes",
       "vk",
       "gm",
@@ -420,6 +428,10 @@
       "vk",
       "gm",
       "_",
+      "compositor_quads_filter",
+      "vk",
+      "gm",
+      "_",
       "crbug_892988",
       "vk",
       "gm",
@@ -452,6 +464,10 @@
       "vk",
       "gm",
       "_",
+      "fontscaler",
+      "vk",
+      "gm",
+      "_",
       "fontscalerdistortable",
       "vk",
       "gm",
@@ -572,10 +588,6 @@
       "vk",
       "gm",
       "_",
-      "fontscaler",
-      "vk",
-      "gm",
-      "_",
       "mixedtextblobs",
       "vk",
       "gm",
@@ -591,7 +603,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "name": "dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All-ANGLE.json b/infra/bots/recipes/test.expected/Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All-ANGLE.json
index 3f73508..7d97328 100644
--- a/infra/bots/recipes/test.expected/Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All-ANGLE.json
+++ b/infra/bots/recipes/test.expected/Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All-ANGLE.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -221,6 +221,8 @@
       "Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All-ANGLE",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -244,6 +246,8 @@
       "NUCD34010WYKH",
       "os",
       "Win10",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]\\tmp\\uninteresting_hashes.txt",
       "--writePath",
@@ -424,7 +428,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "name": "dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-Vulkan.json b/infra/bots/recipes/test.expected/Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-Vulkan.json
index 1addb5c..124b25d 100644
--- a/infra/bots/recipes/test.expected/Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-Vulkan.json
+++ b/infra/bots/recipes/test.expected/Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-Vulkan.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -221,6 +221,8 @@
       "Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-Vulkan",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -244,6 +246,8 @@
       "ShuttleA",
       "os",
       "Win10",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]\\tmp\\uninteresting_hashes.txt",
       "--writePath",
@@ -416,7 +420,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "name": "dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-ANGLE.json b/infra/bots/recipes/test.expected/Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-ANGLE.json
index 7245d82..d7e97b4 100644
--- a/infra/bots/recipes/test.expected/Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-ANGLE.json
+++ b/infra/bots/recipes/test.expected/Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-ANGLE.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -221,6 +221,8 @@
       "Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-ANGLE",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -244,6 +246,8 @@
       "ShuttleC",
       "os",
       "Win10",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]\\tmp\\uninteresting_hashes.txt",
       "--writePath",
@@ -424,7 +428,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "name": "dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FAAA.json b/infra/bots/recipes/test.expected/Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FAAA.json
index 28b0b5a..6efdce0 100644
--- a/infra/bots/recipes/test.expected/Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FAAA.json
+++ b/infra/bots/recipes/test.expected/Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FAAA.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -221,6 +221,8 @@
       "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FAAA",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -244,6 +246,8 @@
       "GCE",
       "os",
       "Win2016",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]\\tmp\\uninteresting_hashes.txt",
       "--writePath",
@@ -316,15 +320,13 @@
       "image",
       "gen_platf",
       "error",
-      "--deltaAA",
-      "false",
       "--forceAnalyticAA",
       "--nonativeFonts",
       "--verbose"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "name": "dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FDAA.json b/infra/bots/recipes/test.expected/Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FDAA.json
deleted file mode 100644
index dc1e9ae..0000000
--- a/infra/bots/recipes/test.expected/Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FDAA.json
+++ /dev/null
@@ -1,334 +0,0 @@
-[
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]\\tmp"
-    ],
-    "infra_step": true,
-    "name": "makedirs tmp_dir"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]\\skia\\infra\\bots\\assets\\skp\\VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skp VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]\\tmp\\SKP_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SKP_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]\\skia\\infra\\bots\\assets\\skimage\\VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get skimage VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]\\tmp\\SK_IMAGE_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SK_IMAGE_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[START_DIR]\\skia\\infra\\bots\\assets\\svg\\VERSION",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "Get svg VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "42",
-      "[START_DIR]\\tmp\\SVG_VERSION"
-    ],
-    "infra_step": true,
-    "name": "write SVG_VERSION"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "rmtree",
-      "[START_DIR]\\test"
-    ],
-    "infra_step": true,
-    "name": "rmtree test"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[START_DIR]\\test"
-    ],
-    "infra_step": true,
-    "name": "makedirs test"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = sys.argv[1]\nRETRIES = 5\nTIMEOUT = 60\nWAIT_BASE = 15\n\nsocket.setdefaulttimeout(TIMEOUT)\nfor retry in range(RETRIES):\n  try:\n    with contextlib.closing(\n        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:\n      hashes = w.read()\n      with open(sys.argv[2], 'w') as f:\n        f.write(hashes)\n        break\n  except Exception as e:\n    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
-      "https://example.com/hashes.txt",
-      "[START_DIR]\\tmp\\uninteresting_hashes.txt"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "infra_step": true,
-    "name": "get uninteresting hashes",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
-      "@@@STEP_LOG_LINE@python.inline@import math@@@",
-      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
-      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
-      "@@@STEP_LOG_LINE@python.inline@import time@@@",
-      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@HASHES_URL = sys.argv[1]@@@",
-      "@@@STEP_LOG_LINE@python.inline@RETRIES = 5@@@",
-      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 60@@@",
-      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 15@@@",
-      "@@@STEP_LOG_LINE@python.inline@@@@",
-      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
-      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
-      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(@@@",
-      "@@@STEP_LOG_LINE@python.inline@        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      hashes = w.read()@@@",
-      "@@@STEP_LOG_LINE@python.inline@      with open(sys.argv[2], 'w') as f:@@@",
-      "@@@STEP_LOG_LINE@python.inline@        f.write(hashes)@@@",
-      "@@@STEP_LOG_LINE@python.inline@        break@@@",
-      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
-      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
-      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
-      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
-      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
-      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
-    ],
-    "name": "get swarming bot id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
-    ],
-    "name": "get swarming task id",
-    "stdout": "/path/to/tmp/",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@python.inline@import os@@@",
-      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
-      "@@@STEP_LOG_END@python.inline@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]\\build\\dm",
-      "--resourcePath",
-      "[START_DIR]\\skia\\resources",
-      "--skps",
-      "[START_DIR]\\skp",
-      "--images",
-      "[START_DIR]\\skimage\\dm",
-      "--colorImages",
-      "[START_DIR]\\skimage\\colorspace",
-      "--nameByHash",
-      "--properties",
-      "gitHash",
-      "abc123",
-      "builder",
-      "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FDAA",
-      "buildbucket_build_id",
-      "123454321",
-      "swarming_bot_id",
-      "skia-bot-123",
-      "swarming_task_id",
-      "123456",
-      "--svgs",
-      "[START_DIR]\\svg",
-      "--key",
-      "arch",
-      "x86_64",
-      "compiler",
-      "Clang",
-      "configuration",
-      "Debug",
-      "cpu_or_gpu",
-      "CPU",
-      "cpu_or_gpu_value",
-      "AVX2",
-      "extra_config",
-      "FDAA",
-      "model",
-      "GCE",
-      "os",
-      "Win2016",
-      "--uninterestingHashesFile",
-      "[START_DIR]\\tmp\\uninteresting_hashes.txt",
-      "--writePath",
-      "[START_DIR]\\[SWARM_OUT_DIR]",
-      "--dont_write",
-      "pdf",
-      "--randomProcessorTest",
-      "--nogpu",
-      "--config",
-      "8888",
-      "--src",
-      "tests",
-      "gm",
-      "image",
-      "colorImage",
-      "--blacklist",
-      "_",
-      "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",
-      "_",
-      "gm",
-      "_",
-      "composeshader_bitmap",
-      "_",
-      "image",
-      "gen_platf",
-      "testimgari.jpg",
-      "_",
-      "image",
-      "gen_platf",
-      "rle8-height-negative.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "rle4-height-negative.bmp",
-      "_",
-      "image",
-      "gen_platf",
-      "error",
-      "--deltaAA",
-      "--forceDeltaAA",
-      "--nonativeFonts",
-      "--verbose"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "dm"
-  },
-  {
-    "jsonResult": null,
-    "name": "$result"
-  }
-]
\ No newline at end of file
diff --git a/infra/bots/recipes/test.expected/Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FSAA.json b/infra/bots/recipes/test.expected/Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FSAA.json
index 9d2d850..02fcde8 100644
--- a/infra/bots/recipes/test.expected/Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FSAA.json
+++ b/infra/bots/recipes/test.expected/Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FSAA.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -221,6 +221,8 @@
       "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FSAA",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -244,6 +246,8 @@
       "GCE",
       "os",
       "Win2016",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]\\tmp\\uninteresting_hashes.txt",
       "--writePath",
@@ -318,14 +322,12 @@
       "error",
       "--analyticAA",
       "false",
-      "--deltaAA",
-      "false",
       "--nonativeFonts",
       "--verbose"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "name": "dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Debug-All-MSRTC.json b/infra/bots/recipes/test.expected/Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Debug-All-MSRTC.json
index b595a91..5feda9c 100644
--- a/infra/bots/recipes/test.expected/Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Debug-All-MSRTC.json
+++ b/infra/bots/recipes/test.expected/Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Debug-All-MSRTC.json
@@ -145,6 +145,8 @@
       "Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Debug-All-MSRTC",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -168,6 +170,8 @@
       "GCE",
       "os",
       "Win2016",
+      "style",
+      "default",
       "--dont_write",
       "pdf",
       "--randomProcessorTest",
@@ -241,7 +245,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>;RECIPE_REPO[depot_tools]"
     },
     "name": "dm"
   },
diff --git a/infra/bots/recipes/test.expected/Test-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All.json b/infra/bots/recipes/test.expected/Test-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All.json
index d168c97..07f3435 100644
--- a/infra/bots/recipes/test.expected/Test-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All.json
+++ b/infra/bots/recipes/test.expected/Test-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All.json
@@ -22,7 +22,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "setup_device"
@@ -37,7 +37,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "install_dm"
@@ -52,7 +52,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "install_nanobench"
@@ -67,7 +67,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_if_needed [START_DIR]/skia/resources"
@@ -117,7 +117,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "cat_file tmp/SKP_VERSION",
@@ -132,7 +132,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm tmp/SKP_VERSION"
@@ -146,7 +146,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm skps"
@@ -160,7 +160,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir skps"
@@ -175,7 +175,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_if_needed [START_DIR]/skp"
@@ -190,7 +190,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_file [START_DIR]/tmp/SKP_VERSION"
@@ -240,7 +240,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "cat_file tmp/SK_IMAGE_VERSION",
@@ -255,7 +255,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm tmp/SK_IMAGE_VERSION"
@@ -269,7 +269,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm images"
@@ -283,7 +283,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir images"
@@ -298,7 +298,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_if_needed [START_DIR]/skimage"
@@ -313,7 +313,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_file [START_DIR]/tmp/SK_IMAGE_VERSION"
@@ -363,7 +363,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "cat_file tmp/SVG_VERSION",
@@ -378,7 +378,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm tmp/SVG_VERSION"
@@ -392,7 +392,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm svgs"
@@ -406,7 +406,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir svgs"
@@ -421,7 +421,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_if_needed [START_DIR]/svg"
@@ -436,7 +436,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_file [START_DIR]/tmp/SVG_VERSION"
@@ -486,7 +486,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm dm"
@@ -500,7 +500,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir dm"
@@ -517,7 +517,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -565,7 +565,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push_file [START_DIR]/tmp/uninteresting_hashes.txt"
@@ -628,6 +628,8 @@
       "Test-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "skia-bot-123",
       "swarming_task_id",
@@ -649,6 +651,8 @@
       "iPadPro",
       "os",
       "iOS",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "tmp/uninteresting_hashes.txt",
       "--writePath",
@@ -656,8 +660,6 @@
       "--dont_write",
       "pdf",
       "--randomProcessorTest",
-      "--threads",
-      "0",
       "--nocpu",
       "--config",
       "gles",
@@ -901,7 +903,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm"
   },
@@ -915,7 +917,7 @@
       "CHROME_HEADLESS": "1",
       "IOS_BUNDLE_ID": "com.google.dm",
       "IOS_MOUNT_POINT": "[START_DIR]/mnt_iosdevice",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "pull_if_needed dm"
diff --git a/infra/bots/recipes/test.expected/failed_dm.json b/infra/bots/recipes/test.expected/failed_dm.json
index 3be6dea..5bf063c 100644
--- a/infra/bots/recipes/test.expected/failed_dm.json
+++ b/infra/bots/recipes/test.expected/failed_dm.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -226,6 +226,8 @@
       "Test-Debian9-GCC-GCE-CPU-AVX2-x86_64-Debug-All",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "swarming_bot_id",
       "",
       "swarming_task_id",
@@ -247,6 +249,8 @@
       "GCE",
       "os",
       "Debian9",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]/tmp/uninteresting_hashes.txt",
       "--writePath",
@@ -273,11 +277,10 @@
     "cwd": "[START_DIR]/skia",
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "symbolized dm",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
diff --git a/infra/bots/recipes/test.expected/failed_get_hashes.json b/infra/bots/recipes/test.expected/failed_get_hashes.json
index f72fee1..f6834d0 100644
--- a/infra/bots/recipes/test.expected/failed_get_hashes.json
+++ b/infra/bots/recipes/test.expected/failed_get_hashes.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -55,7 +55,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -117,7 +117,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -135,7 +135,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -152,7 +152,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -169,7 +169,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -184,7 +184,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -218,7 +218,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -262,7 +262,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -280,7 +280,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -297,7 +297,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -314,7 +314,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -329,7 +329,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -363,7 +363,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -407,7 +407,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -425,7 +425,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -442,7 +442,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -459,7 +459,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -474,7 +474,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -508,7 +508,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -553,7 +553,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/dm_out"
@@ -570,7 +570,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/dm_out"
@@ -585,12 +585,11 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
       "@@@STEP_LOG_LINE@python.inline@import math@@@",
@@ -636,7 +635,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/uninteresting_hashes.txt /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt"
@@ -680,7 +679,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 0's governor to ondemand",
@@ -722,7 +721,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -735,12 +734,12 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-All-Android buildbucket_build_id 123454321 swarming_bot_id \"\" swarming_task_id \"\" --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Release cpu_or_gpu GPU cpu_or_gpu_value Tegra3 extra_config Android model Nexus7 os Android --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --config gles glesdft glessrgb --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ image _ interlaced1.png _ image _ interlaced2.png _ image _ interlaced3.png _ image _ .arw _ image _ .cr2 _ image _ .dng _ image _ .nef _ image _ .nrw _ image _ .orf _ image _ .raf _ image _ .rw2 _ image _ .pef _ image _ .srw _ image _ .ARW _ image _ .CR2 _ image _ .DNG _ image _ .NEF _ image _ .NRW _ image _ .ORF _ image _ .RAF _ image _ .RW2 _ image _ .PEF _ image _ .SRW --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-All-Android buildbucket_build_id 123454321 task_id task_12345 swarming_bot_id \"\" swarming_task_id \"\" --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Release cpu_or_gpu GPU cpu_or_gpu_value Tegra3 extra_config Android model Nexus7 os Android style default --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --threads 0 --nocpu --config gles glesdft glessrgb --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ image _ interlaced1.png _ image _ interlaced2.png _ image _ interlaced3.png _ image _ .arw _ image _ .cr2 _ image _ .dng _ image _ .nef _ image _ .nrw _ image _ .orf _ image _ .raf _ image _ .rw2 _ image _ .pef _ image _ .srw _ image _ .ARW _ image _ .CR2 _ image _ .DNG _ image _ .NEF _ image _ .NRW _ image _ .ORF _ image _ .RAF _ image _ .RW2 _ image _ .PEF _ image _ .SRW --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "write dm.sh"
@@ -756,7 +755,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm.sh"
@@ -771,7 +770,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -786,7 +785,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm",
     "~followup_annotations": [
@@ -820,7 +819,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out",
@@ -908,7 +907,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -941,7 +940,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/test.expected/failed_pull.json b/infra/bots/recipes/test.expected/failed_pull.json
index 35eec3d..94e6a14 100644
--- a/infra/bots/recipes/test.expected/failed_pull.json
+++ b/infra/bots/recipes/test.expected/failed_pull.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -55,7 +55,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -117,7 +117,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -135,7 +135,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -152,7 +152,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -169,7 +169,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -184,7 +184,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -218,7 +218,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -262,7 +262,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -280,7 +280,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -297,7 +297,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -314,7 +314,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -329,7 +329,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -363,7 +363,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -407,7 +407,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -425,7 +425,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -442,7 +442,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -459,7 +459,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -474,7 +474,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -508,7 +508,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -553,7 +553,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/dm_out"
@@ -570,7 +570,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/dm_out"
@@ -585,7 +585,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -634,7 +634,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/uninteresting_hashes.txt /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt"
@@ -678,7 +678,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Set CPU 0's governor to ondemand",
@@ -720,7 +720,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -733,12 +733,12 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-All-Android buildbucket_build_id 123454321 swarming_bot_id \"\" swarming_task_id \"\" --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Debug cpu_or_gpu GPU cpu_or_gpu_value Tegra3 extra_config Android model Nexus7 os Android --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --config gles glesdft glessrgb --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ image _ interlaced1.png _ image _ interlaced2.png _ image _ interlaced3.png _ image _ .arw _ image _ .cr2 _ image _ .dng _ image _ .nef _ image _ .nrw _ image _ .orf _ image _ .raf _ image _ .rw2 _ image _ .pef _ image _ .srw _ image _ .ARW _ image _ .CR2 _ image _ .DNG _ image _ .NEF _ image _ .NRW _ image _ .ORF _ image _ .RAF _ image _ .RW2 _ image _ .PEF _ image _ .SRW --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-All-Android buildbucket_build_id 123454321 task_id task_12345 swarming_bot_id \"\" swarming_task_id \"\" --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Debug cpu_or_gpu GPU cpu_or_gpu_value Tegra3 extra_config Android model Nexus7 os Android style default --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --threads 0 --nocpu --config gles glesdft glessrgb --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ image _ interlaced1.png _ image _ interlaced2.png _ image _ interlaced3.png _ image _ .arw _ image _ .cr2 _ image _ .dng _ image _ .nef _ image _ .nrw _ image _ .orf _ image _ .raf _ image _ .rw2 _ image _ .pef _ image _ .srw _ image _ .ARW _ image _ .CR2 _ image _ .DNG _ image _ .NEF _ image _ .NRW _ image _ .ORF _ image _ .RAF _ image _ .RW2 _ image _ .PEF _ image _ .SRW --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "write dm.sh"
@@ -754,7 +754,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm.sh"
@@ -769,7 +769,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -784,11 +784,10 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
       "@@@STEP_LOG_LINE@python.inline@import sys@@@",
@@ -820,13 +819,12 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "step returned non-zero exit code: 1",
       "@@@STEP_EXCEPTION@@@"
     ]
   },
@@ -839,7 +837,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.kill adb server after failure of 'pull /sdcard/revenge_of_the_skiabot/dm_out' (attempt 1)",
@@ -857,7 +855,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.wait for device after failure of 'pull /sdcard/revenge_of_the_skiabot/dm_out' (attempt 1)",
@@ -877,13 +875,12 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out (attempt 2)",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "step returned non-zero exit code: 1",
       "@@@STEP_EXCEPTION@@@"
     ]
   },
@@ -896,7 +893,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.kill adb server after failure of 'pull /sdcard/revenge_of_the_skiabot/dm_out' (attempt 2)",
@@ -914,7 +911,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.wait for device after failure of 'pull /sdcard/revenge_of_the_skiabot/dm_out' (attempt 2)",
@@ -934,13 +931,12 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out (attempt 3)",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "step returned non-zero exit code: 1",
       "@@@STEP_EXCEPTION@@@"
     ]
   },
@@ -969,7 +965,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -1002,7 +998,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/test.expected/failed_push.json b/infra/bots/recipes/test.expected/failed_push.json
index e126813..de9e897 100644
--- a/infra/bots/recipes/test.expected/failed_push.json
+++ b/infra/bots/recipes/test.expected/failed_push.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -55,12 +55,11 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@import os@@@",
       "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
@@ -89,7 +88,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -136,7 +135,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/test.expected/internal_bot_2.json b/infra/bots/recipes/test.expected/internal_bot_2.json
index bdfb91c..a1e287b 100644
--- a/infra/bots/recipes/test.expected/internal_bot_2.json
+++ b/infra/bots/recipes/test.expected/internal_bot_2.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -55,7 +55,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -117,7 +117,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -135,7 +135,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -152,7 +152,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -169,7 +169,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -184,7 +184,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -218,7 +218,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -262,7 +262,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -280,7 +280,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -297,7 +297,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -314,7 +314,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -329,7 +329,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -363,7 +363,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -407,7 +407,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -425,7 +425,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -442,7 +442,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -459,7 +459,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -474,7 +474,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -508,7 +508,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -553,7 +553,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/dm_out"
@@ -570,7 +570,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/dm_out"
@@ -585,7 +585,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -634,7 +634,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/uninteresting_hashes.txt /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt"
@@ -678,7 +678,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -691,12 +691,12 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-All-Android buildbucket_build_id 123454321 swarming_bot_id \"\" swarming_task_id \"\" --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Debug cpu_or_gpu GPU cpu_or_gpu_value Tegra3 extra_config Android model Nexus7 os Android --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --config gles glesdft glessrgb --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ test _ SRGBReadWritePixels _ test _ SRGBMipMap _ image _ interlaced1.png _ image _ interlaced2.png _ image _ interlaced3.png _ image _ .arw _ image _ .cr2 _ image _ .dng _ image _ .nef _ image _ .nrw _ image _ .orf _ image _ .raf _ image _ .rw2 _ image _ .pef _ image _ .srw _ image _ .ARW _ image _ .CR2 _ image _ .DNG _ image _ .NEF _ image _ .NRW _ image _ .ORF _ image _ .RAF _ image _ .RW2 _ image _ .PEF _ image _ .SRW --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-All-Android buildbucket_build_id 123454321 task_id task_12345 swarming_bot_id \"\" swarming_task_id \"\" --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Debug cpu_or_gpu GPU cpu_or_gpu_value Tegra3 extra_config Android model Nexus7 os Android style default --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --threads 0 --nocpu --config gles glesdft glessrgb --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ test _ SRGBReadWritePixels _ test _ SRGBMipMap _ image _ interlaced1.png _ image _ interlaced2.png _ image _ interlaced3.png _ image _ .arw _ image _ .cr2 _ image _ .dng _ image _ .nef _ image _ .nrw _ image _ .orf _ image _ .raf _ image _ .rw2 _ image _ .pef _ image _ .srw _ image _ .ARW _ image _ .CR2 _ image _ .DNG _ image _ .NEF _ image _ .NRW _ image _ .ORF _ image _ .RAF _ image _ .RW2 _ image _ .PEF _ image _ .SRW --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "write dm.sh"
@@ -712,7 +712,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm.sh"
@@ -727,7 +727,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -742,7 +742,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm",
     "~followup_annotations": [
@@ -776,7 +776,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out",
@@ -864,7 +864,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -897,7 +897,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/test.expected/internal_bot_5.json b/infra/bots/recipes/test.expected/internal_bot_5.json
index 30d3522..03b5707 100644
--- a/infra/bots/recipes/test.expected/internal_bot_5.json
+++ b/infra/bots/recipes/test.expected/internal_bot_5.json
@@ -40,7 +40,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
@@ -55,7 +55,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources",
@@ -117,7 +117,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
@@ -135,7 +135,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -152,7 +152,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/skps"
@@ -169,7 +169,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
@@ -184,7 +184,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
@@ -218,7 +218,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
@@ -262,7 +262,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION",
@@ -280,7 +280,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -297,7 +297,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/images"
@@ -314,7 +314,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/images"
@@ -329,7 +329,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/skimage/* /sdcard/revenge_of_the_skiabot/images",
@@ -363,7 +363,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SK_IMAGE_VERSION /sdcard/revenge_of_the_skiabot/SK_IMAGE_VERSION"
@@ -407,7 +407,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "read /sdcard/revenge_of_the_skiabot/SVG_VERSION",
@@ -425,7 +425,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -442,7 +442,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/svgs"
@@ -459,7 +459,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/svgs"
@@ -474,7 +474,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/svg/* /sdcard/revenge_of_the_skiabot/svgs",
@@ -508,7 +508,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/SVG_VERSION /sdcard/revenge_of_the_skiabot/SVG_VERSION"
@@ -553,7 +553,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "rm /sdcard/revenge_of_the_skiabot/dm_out"
@@ -570,7 +570,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "mkdir /sdcard/revenge_of_the_skiabot/dm_out"
@@ -585,7 +585,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -634,7 +634,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push [START_DIR]/tmp/uninteresting_hashes.txt /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt"
@@ -678,7 +678,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm"
@@ -691,12 +691,12 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-All-Android buildbucket_build_id 123454321 swarming_bot_id \"\" swarming_task_id \"\" --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Debug cpu_or_gpu GPU cpu_or_gpu_value Tegra3 extra_config Android model Nexus7 os Android --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --config gles glesdft glessrgb --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ test _ SRGBReadWritePixels _ image _ interlaced1.png _ image _ interlaced2.png _ image _ interlaced3.png _ image _ .arw _ image _ .cr2 _ image _ .dng _ image _ .nef _ image _ .nrw _ image _ .orf _ image _ .raf _ image _ .rw2 _ image _ .pef _ image _ .srw _ image _ .ARW _ image _ .CR2 _ image _ .DNG _ image _ .NEF _ image _ .NRW _ image _ .ORF _ image _ .RAF _ image _ .RW2 _ image _ .PEF _ image _ .SRW --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-All-Android buildbucket_build_id 123454321 task_id task_12345 swarming_bot_id \"\" swarming_task_id \"\" --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Debug cpu_or_gpu GPU cpu_or_gpu_value Tegra3 extra_config Android model Nexus7 os Android style default --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --threads 0 --nocpu --config gles glesdft glessrgb --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ test _ SRGBReadWritePixels _ image _ interlaced1.png _ image _ interlaced2.png _ image _ interlaced3.png _ image _ .arw _ image _ .cr2 _ image _ .dng _ image _ .nef _ image _ .nrw _ image _ .orf _ image _ .raf _ image _ .rw2 _ image _ .pef _ image _ .srw _ image _ .ARW _ image _ .CR2 _ image _ .DNG _ image _ .NEF _ image _ .NRW _ image _ .ORF _ image _ .RAF _ image _ .RW2 _ image _ .PEF _ image _ .SRW --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "write dm.sh"
@@ -712,7 +712,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "push dm.sh"
@@ -727,7 +727,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "clear log"
@@ -742,7 +742,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm",
     "~followup_annotations": [
@@ -776,7 +776,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out",
@@ -864,7 +864,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "dump log",
@@ -897,7 +897,7 @@
     "env": {
       "ADB_VENDOR_KEYS": "/home/chrome-bot/.android/chrome_infrastructure_adbkey",
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "kill adb server"
diff --git a/infra/bots/recipes/test.expected/trybot.json b/infra/bots/recipes/test.expected/trybot.json
index 790c46c..decfb99 100644
--- a/infra/bots/recipes/test.expected/trybot.json
+++ b/infra/bots/recipes/test.expected/trybot.json
@@ -136,7 +136,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "get uninteresting hashes",
@@ -221,6 +221,8 @@
       "Test-Win8-Clang-Golo-CPU-AVX-x86-Debug-All",
       "buildbucket_build_id",
       "123454321",
+      "task_id",
+      "task_12345",
       "issue",
       "456789",
       "patchset",
@@ -248,6 +250,8 @@
       "Golo",
       "os",
       "Win8",
+      "style",
+      "default",
       "--uninterestingHashesFile",
       "[START_DIR]/tmp/uninteresting_hashes.txt",
       "--writePath",
@@ -423,7 +427,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "dm"
   },
diff --git a/infra/bots/recipes/test.py b/infra/bots/recipes/test.py
index 4ee18f0..600ed2b 100644
--- a/infra/bots/recipes/test.py
+++ b/infra/bots/recipes/test.py
@@ -69,29 +69,26 @@
   if 'Android' not in bot and 'MSAN' not in bot:
     args.append('--randomProcessorTest')
 
+  thread_limit = None
+  MAIN_THREAD_ONLY = 0
+
   # 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'])
+  if '-x86-' in bot:
+    thread_limit = 4
 
-  # Nexus7 runs out of memory due to having 4 cores and only 1G RAM.
-  if 'CPU' in bot and 'Nexus7' in bot:
-    args.extend(['--threads', '2'])
-
-  # MotoG4 occasionally fails when multiple threads read the same image file.
-  if 'CPU' in bot and 'MotoG4' in bot:
-    args.extend(['--threads', '0'])
-
-  if 'Chromecast' in bot:
-    args.extend(['--threads', '0'])
+  # These bots run out of memory easily.
+  if 'Chromecast' in bot or 'MotoG4' in bot or 'Nexus7' in bot:
+    thread_limit = MAIN_THREAD_ONLY
+  if 'NexusPlayer' in bot:
+    thread_limit = 2
 
   # Avoid issues with dynamically exceeding resource cache limits.
   if 'Test' in bot and 'DISCARDABLE' in bot:
-    args.extend(['--threads', '0'])
+    thread_limit = MAIN_THREAD_ONLY
 
-  # See if staying on the main thread helps skia:6748.
-  if 'Test-iOS' in bot:
-    args.extend(['--threads', '0'])
+  if thread_limit is not None:
+    args.extend(['--threads', str(thread_limit)])
 
   # Android's kernel will occasionally attempt to kill our process, using
   # SIGINT, in an effort to free up resources. If requested, that signal
@@ -115,7 +112,6 @@
         'pdf',
         'g8', '565',
         'pic-8888', 'tiles_rt-8888', 'lite-8888', 'serialize-8888',
-        'gbr-8888',
         'f16', 'srgb', 'esrgb', 'narrow', 'enarrow',
         'p3', 'ep3', 'rec2020', 'erec2020'])
 
@@ -184,8 +180,11 @@
       blacklist('gltestthreading gm _ persp_shaders_bw')
       blacklist('gltestthreading gm _ dftext_blob_persp')
       blacklist('gltestthreading gm _ dftext')
-    # skbug.com/7523 - Flaky on various GPUs
+      # skbug.com/7523 - Flaky on various GPUs
       blacklist('gltestthreading gm _ orientation')
+      # These GMs only differ in the low bits
+      blacklist('gltestthreading gm _ stroketext')
+      blacklist('gltestthreading gm _ draw_image_set')
 
     # CommandBuffer bot *only* runs the command_buffer config.
     if 'CommandBuffer' in bot:
@@ -226,18 +225,21 @@
         # Decoding transparent images to 1010102 just looks bad
         blacklist('vk1010102 image _ _')
       else:
-        configs.extend(['gl1010102', 'gltestpersistentcache'])
+        configs.extend(['gl1010102', 'gltestpersistentcache', 'gltestglslcache'])
         # Decoding transparent images to 1010102 just looks bad
         blacklist('gl1010102 image _ _')
         # These tests produce slightly different pixels run to run on NV.
         blacklist('gltestpersistentcache gm _ atlastext')
         blacklist('gltestpersistentcache gm _ dftext')
         blacklist('gltestpersistentcache gm _ glyph_pos_h_b')
+        blacklist('gltestglslcache gm _ atlastext')
+        blacklist('gltestglslcache gm _ dftext')
+        blacklist('gltestglslcache gm _ glyph_pos_h_b')
 
-    # Test SkColorSpaceXformCanvas and rendering to wrapped dsts on a few bots
+    # Test rendering to wrapped dsts on a few bots
     # Also test 'glenarrow', which hits F16 surfaces and F16 vertex colors.
     if 'BonusConfigs' in api.vars.extra_tokens:
-      configs = ['gbr-gl', 'glbetex', 'glbert', 'glenarrow']
+      configs = ['glbetex', 'glbert', 'glenarrow']
 
 
     if 'ChromeOS' in bot:
@@ -250,7 +252,7 @@
     # Test coverage counting path renderer.
     if 'CCPR' in bot:
       configs = [c for c in configs if c == 'gl' or c == 'gles']
-      args.extend(['--pr', 'ccpr', '--cachePathMasks', 'false'])
+      args.extend(['--pr', 'ccpr', '--cc', 'true', '--cachePathMasks', 'false'])
 
     # DDL is a GPU-only feature
     if 'DDL1' in bot:
@@ -333,10 +335,6 @@
   blacklist('glsrgb image _ _')
   blacklist('glessrgb image _ _')
 
-  # Not any point to running these.
-  blacklist('gbr-8888 image _ _')
-  blacklist('gbr-8888 colorImage _ _')
-
   # --src image --config g8 means "decode into Gray8", which isn't supported.
   blacklist('g8 image _ _')
   blacklist('g8 colorImage _ _')
@@ -484,6 +482,7 @@
   bad_serialize_gms.append('makecolorspace')
   bad_serialize_gms.append('readpixels')
   bad_serialize_gms.append('draw_image_set_rect_to_rect')
+  bad_serialize_gms.append('compositor_quads_shader')
 
   # This GM forces a path to be convex. That property doesn't survive
   # serialization.
@@ -669,9 +668,12 @@
     match.append('~FloatingPointTextureTest$')
 
   if 'Vulkan' in bot and 'Win10' in bot and 'IntelIris655' in bot:
+    # skia:8961
+    blacklist(['vk', 'gm', '_', 'savelayer_clipmask'])
     # skia:8659
     blacklist(['vk', 'gm', '_', 'aarectmodes'])
     blacklist(['vk', 'gm', '_', 'aaxfermodes'])
+    blacklist(['vk', 'gm', '_', 'compositor_quads_filter'])
     blacklist(['vk', 'gm', '_', 'crbug_892988'])
     blacklist(['vk', 'gm', '_', 'dftext'])
     blacklist(['vk', 'gm', '_', 'dftext_blob_persp'])
@@ -680,6 +682,7 @@
     blacklist(['vk', 'gm', '_', 'filterfastbounds'])
     blacklist(['vk', 'gm', '_', 'fontmgr_iter'])
     blacklist(['vk', 'gm', '_', 'fontmgr_match'])
+    blacklist(['vk', 'gm', '_', 'fontscaler'])
     blacklist(['vk', 'gm', '_', 'fontscalerdistortable'])
     blacklist(['vk', 'gm', '_', 'gammagradienttext'])
     blacklist(['vk', 'gm', '_', 'gammatext'])
@@ -711,7 +714,6 @@
     blacklist(['vk', 'gm', '_', 'varied_text_clipped_lcd'])
     blacklist(['vk', 'gm', '_', 'varied_text_ignorable_clip_lcd'])
     if 'Debug' in bot:
-      blacklist(['vk', 'gm', '_', 'fontscaler'])
       blacklist(['vk', 'gm', '_', 'mixedtextblobs'])
       blacklist(['vk', 'gm', '_', 'textblobmixedsizes'])
       blacklist(['vk', 'gm', '_', 'textblobmixedsizes_df'])
@@ -723,6 +725,8 @@
     match.append('~^InitialTextureClear$')
     match.append('~^RGB565TextureTest$')
     match.append('~^RGBA4444TextureTest$')
+    match.append('~^TextureIdleProcFlushTest$')
+    match.append('~^TextureStripAtlasManagerColorFilterTest$')
     match.append('~^WritePixelsNonTextureMSAA_Gpu$')
 
   if 'ANGLE' in bot:
@@ -737,6 +741,10 @@
     # skia:6857
     blacklist(['angle_d3d9_es2', 'gm', '_', 'lighting'])
 
+  if 'Chorizo' in bot:
+    # skia:8869
+    blacklist(['_', 'gm', '_', 'compositor_quads_filter'])
+
   if 'PowerVRGX6250' in bot:
     match.append('~gradients_view_perspective_nodither') #skia:6972
 
@@ -755,31 +763,14 @@
 
   if 'Metal' in bot:
     # skia:8243
-    match.append('~^ClearOp$')
-    match.append('~^DDLSurfaceCharacterizationTest$')
-    match.append('~^DDLNonTextureabilityTest$')
     match.append('~^DDLOperatorEqTest$')
-    match.append('~^DeferredProxyTest$')
-    match.append('~^GPUMemorySize$')
-    match.append('~^GrContext_colorTypeSupportedAsImage$')
-    match.append('~^GrContext_colorTypeSupportedAsSurface$')
-    match.append('~^GrContext_maxSurfaceSamplesForColorType$')
-    match.append('~^GrContextFactory_sharedContexts$')
-    match.append('~^GrPipelineDynamicStateTest$')
-    match.append('~^InitialTextureClear$')
-    match.append('~^PromiseImageTestNoDelayedRelease$')
-    match.append('~^PromiseImageTestDelayedRelease$')
-    match.append('~^PromiseImageTextureReuse$')
-    match.append('~^PromiseImageTextureReuseDifferentConfig$')
-    match.append('~^PromiseImageTextureShutdown$')
-    match.append('~^PromiseImageTextureFullCache$')
-    match.append('~^ResourceAllocatorTest$')
-    match.append('~^RGB565TextureTest$')
-    match.append('~^RGBA4444TextureTest$')
-    match.append('~^TransferPixelsTest$')
-    match.append('~^SurfaceSemaphores$')
-    match.append('~^VertexAttributeCount$')
-    match.append('~^WrappedProxyTest$')
+    match.append('~^WritePixelsNonTexture_Gpu$')
+
+  if 'Wuffs' in api.vars.extra_tokens:
+    # skia:8750
+    blacklist(['_', 'tests', '_', 'Codec_partial'])
+    # skia:8762
+    blacklist(['_', 'tests', '_', 'Codec_gif'])
 
   if blacklisted:
     args.append('--blacklist')
@@ -795,11 +786,9 @@
     args.append('--noRAW_threading')
 
   if 'FSAA' in bot:
-    args.extend(['--analyticAA', 'false', '--deltaAA', 'false'])
+    args.extend(['--analyticAA', 'false'])
   if 'FAAA' in bot:
-    args.extend(['--deltaAA', 'false', '--forceAnalyticAA'])
-  if 'FDAA' in bot:
-    args.extend(['--deltaAA', '--forceDeltaAA'])
+    args.extend(['--forceAnalyticAA'])
 
   if 'NativeFonts' not in bot:
     args.append('--nonativeFonts')
@@ -909,6 +898,7 @@
     'gitHash',              api.properties['revision'],
     'builder',              api.vars.builder_name,
     'buildbucket_build_id', api.properties.get('buildbucket_build_id', ''),
+    'task_id',              api.properties['task_id'],
   ]
   if api.vars.is_trybot:
     properties.extend([
@@ -952,6 +942,11 @@
 
   if 'Lottie' in api.vars.builder_cfg.get('extra_config', ''):
     keys.extend(['renderer', 'skottie'])
+  if 'DDL' in api.vars.builder_cfg.get('extra_config', ''):
+    # 'DDL' style means "--skpViewportSize 2048 --pr ~small"
+    keys.extend(['style', 'DDL'])
+  else:
+    keys.extend(['style', 'default'])
 
   args.extend(keys)
 
@@ -1025,6 +1020,7 @@
   'Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN',
   ('Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All'
    '-SK_USE_DISCARDABLE_SCALEDIMAGECACHE'),
+  'Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-Wuffs',
   'Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Lottie',
   ('Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All'
    '-SK_FORCE_RASTER_PIPELINE_BLITTER'),
@@ -1032,13 +1028,15 @@
   'Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Release-All-SwiftShader',
   'Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All-Vulkan',
   'Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-Vulkan',
-  'Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts',
-  'Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All',
-  'Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All',
-  'Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal',
-  ('Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-'
+  ('Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All'
+   '-NativeFonts'),
+  'Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All',
+  'Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal',
+  ('Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-'
    'MoltenVK_Vulkan'),
-  'Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer',
+  ('Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All'
+   '-CommandBuffer'),
+  'Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All',
   'Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan_Coverage',
   ('Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All'
    '-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41'),
@@ -1057,7 +1055,6 @@
   'Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-Vulkan',
   'Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-ANGLE',
   'Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FAAA',
-  'Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FDAA',
   'Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FSAA',
   'Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Debug-All-MSRTC',
   'Test-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All',
@@ -1073,7 +1070,8 @@
                      revision='abc123',
                      path_config='kitchen',
                      gold_hashes_url='https://example.com/hashes.txt',
-                     swarm_out_dir='[SWARM_OUT_DIR]') +
+                     swarm_out_dir='[SWARM_OUT_DIR]',
+                     task_id='task_12345') +
       api.path.exists(
           api.path['start_dir'].join('skia'),
           api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
@@ -1112,7 +1110,8 @@
                    revision='abc123',
                    path_config='kitchen',
                    gold_hashes_url='https://example.com/hashes.txt',
-                   swarm_out_dir='[SWARM_OUT_DIR]') +
+                   swarm_out_dir='[SWARM_OUT_DIR]',
+                   task_id='task_12345') +
     api.properties(patch_storage='gerrit') +
     api.properties.tryserver(
           buildername=builder,
@@ -1139,7 +1138,8 @@
                    revision='abc123',
                    path_config='kitchen',
                    gold_hashes_url='https://example.com/hashes.txt',
-                   swarm_out_dir='[SWARM_OUT_DIR]') +
+                   swarm_out_dir='[SWARM_OUT_DIR]',
+                   task_id='task_12345') +
     api.path.exists(
         api.path['start_dir'].join('skia'),
         api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
@@ -1161,7 +1161,8 @@
                    revision='abc123',
                    path_config='kitchen',
                    gold_hashes_url='https://example.com/hashes.txt',
-                   swarm_out_dir='[SWARM_OUT_DIR]') +
+                   swarm_out_dir='[SWARM_OUT_DIR]',
+                   task_id='task_12345') +
     api.path.exists(
         api.path['start_dir'].join('skia'),
         api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
@@ -1184,7 +1185,8 @@
                    revision='abc123',
                    path_config='kitchen',
                    gold_hashes_url='https://example.com/hashes.txt',
-                   swarm_out_dir='[SWARM_OUT_DIR]') +
+                   swarm_out_dir='[SWARM_OUT_DIR]',
+                   task_id='task_12345') +
     api.path.exists(
         api.path['start_dir'].join('skia'),
         api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
@@ -1210,7 +1212,8 @@
                    revision='abc123',
                    path_config='kitchen',
                    gold_hashes_url='https://example.com/hashes.txt',
-                   swarm_out_dir='[SWARM_OUT_DIR]') +
+                   swarm_out_dir='[SWARM_OUT_DIR]',
+                   task_id='task_12345') +
     api.path.exists(
         api.path['start_dir'].join('skia'),
         api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
@@ -1235,7 +1238,8 @@
                    path_config='kitchen',
                    swarm_out_dir='[SWARM_OUT_DIR]',
                    gold_hashes_url='https://example.com/hashes.txt',
-                   internal_hardware_label='2') +
+                   internal_hardware_label='2',
+                   task_id='task_12345') +
     api.path.exists(
         api.path['start_dir'].join('skia'),
         api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
@@ -1256,7 +1260,8 @@
                    path_config='kitchen',
                    swarm_out_dir='[SWARM_OUT_DIR]',
                    gold_hashes_url='https://example.com/hashes.txt',
-                   internal_hardware_label='5') +
+                   internal_hardware_label='5',
+                   task_id='task_12345') +
     api.path.exists(
         api.path['start_dir'].join('skia'),
         api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
diff --git a/infra/bots/recipes/test_canvaskit.expected/Test-Debian9-EMCC-GCE-GPU-WEBGL1-wasm-Debug-All-CanvasKit.json b/infra/bots/recipes/test_canvaskit.expected/Test-Debian9-EMCC-GCE-GPU-WEBGL1-wasm-Debug-All-CanvasKit.json
index 06550ed..7b7fdc3 100644
--- a/infra/bots/recipes/test_canvaskit.expected/Test-Debian9-EMCC-GCE-GPU-WEBGL1-wasm-Debug-All-CanvasKit.json
+++ b/infra/bots/recipes/test_canvaskit.expected/Test-Debian9-EMCC-GCE-GPU-WEBGL1-wasm-Debug-All-CanvasKit.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -112,8 +112,8 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport os\nimport shutil\nimport sys\n\ncopy_dest = sys.argv[1]\nbase_dir = sys.argv[2]\nbundle_name = sys.argv[3]\nout_dir = sys.argv[4]\n\n# Clean out old binaries (if any)\ntry:\n  shutil.rmtree(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.ENOENT:\n    raise\n\n# Make folder\ntry:\n  os.makedirs(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\n# Copy binaries (canvaskit.js and canvaskit.wasm) to where the karma tests\n# expect them ($SKIA_ROOT/experimental/canvaskit/canvaskit/bin/)\ndest = os.path.join(copy_dest, 'canvaskit.js')\nshutil.copyfile(os.path.join(base_dir, 'canvaskit.js'), dest)\nos.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\nif bundle_name:\n  dest = os.path.join(copy_dest, bundle_name)\n  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)\n  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\n# Prepare output folder, api.file.ensure_directory doesn't touch\n# the permissions of the out directory if it already exists.\nos.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.\n",
-      "[START_DIR]/cache/work/skia/experimental/canvaskit/canvaskit/bin",
+      "import errno\nimport os\nimport shutil\nimport sys\n\ncopy_dest = sys.argv[1]\nbase_dir = sys.argv[2]\nbundle_name = sys.argv[3]\nout_dir = sys.argv[4]\n\n# Clean out old binaries (if any)\ntry:\n  shutil.rmtree(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.ENOENT:\n    raise\n\n# Make folder\ntry:\n  os.makedirs(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\n# Copy binaries (canvaskit.js and canvaskit.wasm) to where the karma tests\n# expect them ($SKIA_ROOT/modules/canvaskit/canvaskit/bin/)\ndest = os.path.join(copy_dest, 'canvaskit.js')\nshutil.copyfile(os.path.join(base_dir, 'canvaskit.js'), dest)\nos.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\nif bundle_name:\n  dest = os.path.join(copy_dest, bundle_name)\n  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)\n  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\n# Prepare output folder, api.file.ensure_directory doesn't touch\n# the permissions of the out directory if it already exists.\nos.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.\n",
+      "[START_DIR]/cache/work/skia/modules/canvaskit/canvaskit/bin",
       "[START_DIR]/build",
       "canvaskit.wasm",
       "[START_DIR]/[SWARM_OUT_DIR]"
@@ -146,7 +146,7 @@
       "@@@STEP_LOG_LINE@python.inline@    raise@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@# Copy binaries (canvaskit.js and canvaskit.wasm) to where the karma tests@@@",
-      "@@@STEP_LOG_LINE@python.inline@# expect them ($SKIA_ROOT/experimental/canvaskit/canvaskit/bin/)@@@",
+      "@@@STEP_LOG_LINE@python.inline@# expect them ($SKIA_ROOT/modules/canvaskit/canvaskit/bin/)@@@",
       "@@@STEP_LOG_LINE@python.inline@dest = os.path.join(copy_dest, 'canvaskit.js')@@@",
       "@@@STEP_LOG_LINE@python.inline@shutil.copyfile(os.path.join(base_dir, 'canvaskit.js'), dest)@@@",
       "@@@STEP_LOG_LINE@python.inline@os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.@@@",
@@ -200,7 +200,7 @@
       "[START_DIR]/cache/work:/SRC",
       "--volume",
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
-      "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v6",
+      "gcr.io/skia-public/gold-karma-chrome-tests:72.0.3626.121_v1",
       "/SRC/skia/infra/canvaskit/test_canvaskit.sh",
       "--builder",
       "Test-Debian9-EMCC-GCE-GPU-WEBGL1-wasm-Debug-All-CanvasKit",
@@ -222,7 +222,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Test CanvasKit with Docker"
   },
diff --git a/infra/bots/recipes/test_canvaskit.expected/canvaskit_trybot.json b/infra/bots/recipes/test_canvaskit.expected/canvaskit_trybot.json
index d790668..e157e06 100644
--- a/infra/bots/recipes/test_canvaskit.expected/canvaskit_trybot.json
+++ b/infra/bots/recipes/test_canvaskit.expected/canvaskit_trybot.json
@@ -52,7 +52,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -114,8 +114,8 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport os\nimport shutil\nimport sys\n\ncopy_dest = sys.argv[1]\nbase_dir = sys.argv[2]\nbundle_name = sys.argv[3]\nout_dir = sys.argv[4]\n\n# Clean out old binaries (if any)\ntry:\n  shutil.rmtree(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.ENOENT:\n    raise\n\n# Make folder\ntry:\n  os.makedirs(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\n# Copy binaries (canvaskit.js and canvaskit.wasm) to where the karma tests\n# expect them ($SKIA_ROOT/experimental/canvaskit/canvaskit/bin/)\ndest = os.path.join(copy_dest, 'canvaskit.js')\nshutil.copyfile(os.path.join(base_dir, 'canvaskit.js'), dest)\nos.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\nif bundle_name:\n  dest = os.path.join(copy_dest, bundle_name)\n  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)\n  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\n# Prepare output folder, api.file.ensure_directory doesn't touch\n# the permissions of the out directory if it already exists.\nos.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.\n",
-      "[START_DIR]/cache/work/skia/experimental/canvaskit/canvaskit/bin",
+      "import errno\nimport os\nimport shutil\nimport sys\n\ncopy_dest = sys.argv[1]\nbase_dir = sys.argv[2]\nbundle_name = sys.argv[3]\nout_dir = sys.argv[4]\n\n# Clean out old binaries (if any)\ntry:\n  shutil.rmtree(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.ENOENT:\n    raise\n\n# Make folder\ntry:\n  os.makedirs(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\n# Copy binaries (canvaskit.js and canvaskit.wasm) to where the karma tests\n# expect them ($SKIA_ROOT/modules/canvaskit/canvaskit/bin/)\ndest = os.path.join(copy_dest, 'canvaskit.js')\nshutil.copyfile(os.path.join(base_dir, 'canvaskit.js'), dest)\nos.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\nif bundle_name:\n  dest = os.path.join(copy_dest, bundle_name)\n  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)\n  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\n# Prepare output folder, api.file.ensure_directory doesn't touch\n# the permissions of the out directory if it already exists.\nos.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.\n",
+      "[START_DIR]/cache/work/skia/modules/canvaskit/canvaskit/bin",
       "[START_DIR]/build",
       "canvaskit.wasm",
       "[START_DIR]/[SWARM_OUT_DIR]"
@@ -148,7 +148,7 @@
       "@@@STEP_LOG_LINE@python.inline@    raise@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@# Copy binaries (canvaskit.js and canvaskit.wasm) to where the karma tests@@@",
-      "@@@STEP_LOG_LINE@python.inline@# expect them ($SKIA_ROOT/experimental/canvaskit/canvaskit/bin/)@@@",
+      "@@@STEP_LOG_LINE@python.inline@# expect them ($SKIA_ROOT/modules/canvaskit/canvaskit/bin/)@@@",
       "@@@STEP_LOG_LINE@python.inline@dest = os.path.join(copy_dest, 'canvaskit.js')@@@",
       "@@@STEP_LOG_LINE@python.inline@shutil.copyfile(os.path.join(base_dir, 'canvaskit.js'), dest)@@@",
       "@@@STEP_LOG_LINE@python.inline@os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.@@@",
@@ -202,7 +202,7 @@
       "[START_DIR]/cache/work:/SRC",
       "--volume",
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
-      "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v6",
+      "gcr.io/skia-public/gold-karma-chrome-tests:72.0.3626.121_v1",
       "/SRC/skia/infra/canvaskit/test_canvaskit.sh",
       "--builder",
       "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-CanvasKit",
@@ -228,7 +228,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Test CanvasKit with Docker"
   },
diff --git a/infra/bots/recipes/test_canvaskit.py b/infra/bots/recipes/test_canvaskit.py
index ac957e5..9a1fb2e 100644
--- a/infra/bots/recipes/test_canvaskit.py
+++ b/infra/bots/recipes/test_canvaskit.py
@@ -18,7 +18,7 @@
 ]
 
 
-DOCKER_IMAGE = 'gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v6'
+DOCKER_IMAGE = 'gcr.io/skia-public/gold-karma-chrome-tests:72.0.3626.121_v1'
 INNER_KARMA_SCRIPT = '/SRC/skia/infra/canvaskit/test_canvaskit.sh'
 
 
@@ -33,7 +33,7 @@
 
   # The karma script is configured to look in ./canvaskit/bin/ for
   # the test files to load, so we must copy them there (see Set up for docker).
-  copy_dest = checkout_root.join('skia', 'experimental', 'canvaskit',
+  copy_dest = checkout_root.join('skia', 'modules', 'canvaskit',
                                  'canvaskit', 'bin')
 
   base_dir = api.vars.build_dir
@@ -66,7 +66,7 @@
     raise
 
 # Copy binaries (canvaskit.js and canvaskit.wasm) to where the karma tests
-# expect them ($SKIA_ROOT/experimental/canvaskit/canvaskit/bin/)
+# expect them ($SKIA_ROOT/modules/canvaskit/canvaskit/bin/)
 dest = os.path.join(copy_dest, 'canvaskit.js')
 shutil.copyfile(os.path.join(base_dir, 'canvaskit.js'), dest)
 os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.
diff --git a/infra/bots/recipes/test_lottie_web.expected/Test-Debian9-none-GCE-CPU-AVX2-x86_64-Debug-All-LottieWeb.json b/infra/bots/recipes/test_lottie_web.expected/Test-Debian9-none-GCE-CPU-AVX2-x86_64-Debug-All-LottieWeb.json
index 8680476..8e57030 100644
--- a/infra/bots/recipes/test_lottie_web.expected/Test-Debian9-none-GCE-CPU-AVX2-x86_64-Debug-All-LottieWeb.json
+++ b/infra/bots/recipes/test_lottie_web.expected/Test-Debian9-none-GCE-CPU-AVX2-x86_64-Debug-All-LottieWeb.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -229,7 +229,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Create lottie-web Gold output with Docker"
   },
diff --git a/infra/bots/recipes/test_lottie_web.expected/lottie_web_trybot.json b/infra/bots/recipes/test_lottie_web.expected/lottie_web_trybot.json
index f591b82..f381248 100644
--- a/infra/bots/recipes/test_lottie_web.expected/lottie_web_trybot.json
+++ b/infra/bots/recipes/test_lottie_web.expected/lottie_web_trybot.json
@@ -52,7 +52,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -237,7 +237,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Create lottie-web Gold output with Docker"
   },
diff --git a/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit.json b/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit.json
index af27d36..09eb68b 100644
--- a/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit.json
+++ b/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -202,7 +202,7 @@
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
       "--env",
       "ASM_JS=1",
-      "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v6",
+      "gcr.io/skia-public/gold-karma-chrome-tests:72.0.3626.121_v1",
       "/SRC/skia/infra/pathkit/test_pathkit.sh",
       "--builder",
       "Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit",
@@ -226,7 +226,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Test PathKit with Docker"
   },
diff --git a/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json b/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json
index 6395b51..add9e2e 100644
--- a/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json
+++ b/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -202,7 +202,7 @@
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
       "--env",
       "ASM_JS=1",
-      "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v6",
+      "gcr.io/skia-public/gold-karma-chrome-tests:72.0.3626.121_v1",
       "/SRC/skia/infra/pathkit/test_pathkit.sh",
       "--builder",
       "Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit",
@@ -226,7 +226,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Test PathKit with Docker"
   },
diff --git a/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit.json b/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit.json
index 2d0feec..78bd688 100644
--- a/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit.json
+++ b/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -200,7 +200,7 @@
       "[START_DIR]/cache/work:/SRC",
       "--volume",
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
-      "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v6",
+      "gcr.io/skia-public/gold-karma-chrome-tests:72.0.3626.121_v1",
       "/SRC/skia/infra/pathkit/test_pathkit.sh",
       "--builder",
       "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit",
@@ -222,7 +222,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Test PathKit with Docker"
   },
diff --git a/infra/bots/recipes/test_pathkit.expected/pathkit_trybot.json b/infra/bots/recipes/test_pathkit.expected/pathkit_trybot.json
index 9822b8f..b9f7400 100644
--- a/infra/bots/recipes/test_pathkit.expected/pathkit_trybot.json
+++ b/infra/bots/recipes/test_pathkit.expected/pathkit_trybot.json
@@ -52,7 +52,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -202,7 +202,7 @@
       "[START_DIR]/cache/work:/SRC",
       "--volume",
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
-      "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v6",
+      "gcr.io/skia-public/gold-karma-chrome-tests:72.0.3626.121_v1",
       "/SRC/skia/infra/pathkit/test_pathkit.sh",
       "--builder",
       "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit",
@@ -228,7 +228,7 @@
     "env": {
       "CHROME_HEADLESS": "1",
       "DOCKER_CONFIG": "/home/chrome-bot/.docker",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Test PathKit with Docker"
   },
diff --git a/infra/bots/recipes/test_pathkit.py b/infra/bots/recipes/test_pathkit.py
index 2093ec5..2decbd9 100644
--- a/infra/bots/recipes/test_pathkit.py
+++ b/infra/bots/recipes/test_pathkit.py
@@ -18,7 +18,7 @@
 ]
 
 
-DOCKER_IMAGE = 'gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v6'
+DOCKER_IMAGE = 'gcr.io/skia-public/gold-karma-chrome-tests:72.0.3626.121_v1'
 INNER_KARMA_SCRIPT = '/SRC/skia/infra/pathkit/test_pathkit.sh'
 
 
diff --git a/infra/bots/recipes/test_skqp_emulator.expected/Test-Debian9-Clang-GCE-CPU-Emulator-x86-devrel-All-Android_SKQP.json b/infra/bots/recipes/test_skqp_emulator.expected/Test-Debian9-Clang-GCE-CPU-Emulator-x86-devrel-All-Android_SKQP.json
index 5f37624..d05664f 100644
--- a/infra/bots/recipes/test_skqp_emulator.expected/Test-Debian9-Clang-GCE-CPU-Emulator-x86-devrel-All-Android_SKQP.json
+++ b/infra/bots/recipes/test_skqp_emulator.expected/Test-Debian9-Clang-GCE-CPU-Emulator-x86-devrel-All-Android_SKQP.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -156,7 +156,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Test SQKP with Android Emulator in Docker"
   },
@@ -168,7 +168,7 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
     },
     "infra_step": true,
     "name": "Stop Emulator"
diff --git a/infra/bots/recipes/update_go_deps.expected/Housekeeper-Nightly-UpdateGoDEPS.json b/infra/bots/recipes/update_go_deps.expected/Housekeeper-Nightly-UpdateGoDEPS.json
index 82f5f35..3336f14 100644
--- a/infra/bots/recipes/update_go_deps.expected/Housekeeper-Nightly-UpdateGoDEPS.json
+++ b/infra/bots/recipes/update_go_deps.expected/Housekeeper-Nightly-UpdateGoDEPS.json
@@ -50,7 +50,7 @@
     "cwd": "[START_DIR]/cache/work",
     "env_prefixes": {
       "PATH": [
-        "RECIPE_PACKAGE_REPO[depot_tools]"
+        "RECIPE_REPO[depot_tools]"
       ]
     },
     "infra_step": true,
@@ -105,7 +105,7 @@
       "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_REPO[depot_tools]"
     },
     "name": "Update Asset"
   },
diff --git a/infra/bots/recipes/upload_dm_results.expected/failed_all.json b/infra/bots/recipes/upload_dm_results.expected/failed_all.json
index 32d7455..03ebaad 100644
--- a/infra/bots/recipes/upload_dm_results.expected/failed_all.json
+++ b/infra/bots/recipes/upload_dm_results.expected/failed_all.json
@@ -28,7 +28,6 @@
     ],
     "name": "upload .png images",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
@@ -42,7 +41,6 @@
     ],
     "name": "upload .png images (attempt 2)",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
@@ -56,7 +54,6 @@
     ],
     "name": "upload .png images (attempt 3)",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
@@ -70,7 +67,6 @@
     ],
     "name": "upload .png images (attempt 4)",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
@@ -84,7 +80,6 @@
     ],
     "name": "upload .png images (attempt 5)",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
diff --git a/infra/bots/recipes/upload_dm_results.expected/failed_once.json b/infra/bots/recipes/upload_dm_results.expected/failed_once.json
index 81ba679..522f01f 100644
--- a/infra/bots/recipes/upload_dm_results.expected/failed_once.json
+++ b/infra/bots/recipes/upload_dm_results.expected/failed_once.json
@@ -28,7 +28,6 @@
     ],
     "name": "upload .png images",
     "~followup_annotations": [
-      "step returned non-zero exit code: 1",
       "@@@STEP_FAILURE@@@"
     ]
   },
diff --git a/infra/bots/tasks.json b/infra/bots/tasks.json
index 4b5b19a7..20054a9 100755
--- a/infra/bots/tasks.json
+++ b/infra/bots/tasks.json
@@ -203,6 +203,11 @@
         "Build-Debian9-Clang-x86_64-Debug-Vulkan"
       ]
     },
+    "Build-Debian9-Clang-x86_64-Debug-Wuffs": {
+      "tasks": [
+        "Build-Debian9-Clang-x86_64-Debug-Wuffs"
+      ]
+    },
     "Build-Debian9-Clang-x86_64-Release": {
       "tasks": [
         "Build-Debian9-Clang-x86_64-Release"
@@ -293,6 +298,11 @@
         "Build-Debian9-Clang-x86_64-Release-Vulkan"
       ]
     },
+    "Build-Debian9-Clang-x86_64-Release-Wuffs": {
+      "tasks": [
+        "Build-Debian9-Clang-x86_64-Release-Wuffs"
+      ]
+    },
     "Build-Debian9-EMCC-asmjs-Debug-PathKit": {
       "tasks": [
         "Build-Debian9-EMCC-asmjs-Debug-PathKit"
@@ -454,6 +464,11 @@
         "Build-Mac-Clang-x86_64-Debug-MoltenVK_Vulkan"
       ]
     },
+    "Build-Mac-Clang-x86_64-Debug-OpenCL": {
+      "tasks": [
+        "Build-Mac-Clang-x86_64-Debug-OpenCL"
+      ]
+    },
     "Build-Mac-Clang-x86_64-Release": {
       "tasks": [
         "Build-Mac-Clang-x86_64-Release"
@@ -525,6 +540,11 @@
         "Build-Win-Clang-x86_64-Debug-Vulkan"
       ]
     },
+    "Build-Win-Clang-x86_64-Debug-Wuffs": {
+      "tasks": [
+        "Build-Win-Clang-x86_64-Debug-Wuffs"
+      ]
+    },
     "Build-Win-Clang-x86_64-Release": {
       "tasks": [
         "Build-Win-Clang-x86_64-Release"
@@ -535,6 +555,11 @@
         "Build-Win-Clang-x86_64-Release-ANGLE"
       ]
     },
+    "Build-Win-Clang-x86_64-Release-Shared": {
+      "tasks": [
+        "Build-Win-Clang-x86_64-Release-Shared"
+      ]
+    },
     "Build-Win-Clang-x86_64-Release-UBSAN": {
       "tasks": [
         "Build-Win-Clang-x86_64-Release-UBSAN"
@@ -585,11 +610,21 @@
         "Build-Win-MSVC-x86_64-Debug-Vulkan"
       ]
     },
+    "Build-Win-MSVC-x86_64-Debug-Wuffs": {
+      "tasks": [
+        "Build-Win-MSVC-x86_64-Debug-Wuffs"
+      ]
+    },
     "Build-Win-MSVC-x86_64-Release": {
       "tasks": [
         "Build-Win-MSVC-x86_64-Release"
       ]
     },
+    "Build-Win-MSVC-x86_64-Release-Shared": {
+      "tasks": [
+        "Build-Win-MSVC-x86_64-Release-Shared"
+      ]
+    },
     "Build-Win-MSVC-x86_64-Release-Vulkan": {
       "tasks": [
         "Build-Win-MSVC-x86_64-Release-Vulkan"
@@ -601,6 +636,11 @@
       ],
       "trigger": "master"
     },
+    "BuildStats-Debian9-Clang-x86_64-Release": {
+      "tasks": [
+        "BuildStats-Debian9-Clang-x86_64-Release"
+      ]
+    },
     "BuildStats-Debian9-EMCC-asmjs-Release-PathKit": {
       "tasks": [
         "Upload-BuildStats-Debian9-EMCC-asmjs-Release-PathKit"
@@ -636,17 +676,11 @@
         "Upload-Calmbench-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All"
       ]
     },
-    "Calmbench-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
+    "Calmbench-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
       "tasks": [
-        "Upload-Calmbench-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All"
+        "Upload-Calmbench-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All"
       ]
     },
-    "Housekeeper-Nightly-Bookmaker": {
-      "tasks": [
-        "Housekeeper-Nightly-Bookmaker"
-      ],
-      "trigger": "nightly"
-    },
     "Housekeeper-Nightly-RecreateSKPs_Canary": {
       "tasks": [
         "Housekeeper-Nightly-RecreateSKPs_Canary"
@@ -722,6 +756,11 @@
         "Upload-Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android"
       ]
     },
+    "Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android_SkottieTracing": {
+      "tasks": [
+        "Upload-Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android_SkottieTracing"
+      ]
+    },
     "Perf-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android": {
       "tasks": [
         "Perf-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android"
@@ -987,6 +1026,41 @@
         "Upload-Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan_Skpbench"
       ]
     },
+    "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android": {
+      "tasks": [
+        "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android"
+      ]
+    },
+    "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_Vulkan": {
+      "tasks": [
+        "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_Vulkan"
+      ]
+    },
+    "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android": {
+      "tasks": [
+        "Upload-Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android"
+      ]
+    },
+    "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_CCPR_Skpbench": {
+      "tasks": [
+        "Upload-Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_CCPR_Skpbench"
+      ]
+    },
+    "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Skpbench": {
+      "tasks": [
+        "Upload-Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Skpbench"
+      ]
+    },
+    "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan": {
+      "tasks": [
+        "Upload-Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan"
+      ]
+    },
+    "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan_Skpbench": {
+      "tasks": [
+        "Upload-Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan_Skpbench"
+      ]
+    },
     "Perf-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Debug-All": {
       "tasks": [
         "Perf-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Debug-All"
@@ -1127,6 +1201,16 @@
         "Upload-Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_FORCE_RASTER_PIPELINE_BLITTER"
       ]
     },
+    "Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SkottieTracing": {
+      "tasks": [
+        "Upload-Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SkottieTracing"
+      ]
+    },
+    "Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Wuffs": {
+      "tasks": [
+        "Upload-Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Wuffs"
+      ]
+    },
     "Perf-Debian9-Clang-GCE-CPU-AVX512-x86_64-Debug-All": {
       "tasks": [
         "Perf-Debian9-Clang-GCE-CPU-AVX512-x86_64-Debug-All"
@@ -1217,135 +1301,95 @@
         "Perf-Debian9-GCC-GCE-CPU-AVX2-x86_64-Debug-All"
       ]
     },
-    "Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All": {
+    "Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All": {
       "tasks": [
-        "Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All"
+        "Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All"
       ]
     },
-    "Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All": {
+    "Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All": {
       "tasks": [
-        "Upload-Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All"
+        "Upload-Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All"
       ]
     },
-    "Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer": {
+    "Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer": {
       "tasks": [
-        "Upload-Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer"
+        "Upload-Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer"
       ],
       "trigger": "master"
     },
-    "Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All": {
+    "Perf-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All": {
       "tasks": [
-        "Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All"
+        "Perf-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All"
       ]
     },
-    "Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All": {
+    "Perf-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN": {
       "tasks": [
-        "Upload-Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All"
+        "Perf-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN"
       ]
     },
-    "Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-CommandBuffer": {
+    "Perf-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All": {
       "tasks": [
-        "Upload-Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-CommandBuffer"
+        "Upload-Perf-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All"
+      ]
+    },
+    "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All": {
+      "tasks": [
+        "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All"
+      ]
+    },
+    "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan": {
+      "tasks": [
+        "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan"
+      ]
+    },
+    "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All": {
+      "tasks": [
+        "Upload-Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All"
+      ]
+    },
+    "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-CommandBuffer": {
+      "tasks": [
+        "Upload-Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-CommandBuffer"
       ],
       "trigger": "master"
     },
-    "Perf-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All": {
+    "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan": {
       "tasks": [
-        "Perf-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All"
+        "Upload-Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan"
       ]
     },
-    "Perf-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN": {
+    "Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All": {
       "tasks": [
-        "Perf-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN"
+        "Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All"
       ]
     },
-    "Perf-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All": {
+    "Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All": {
       "tasks": [
-        "Upload-Perf-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All"
+        "Upload-Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All"
       ]
     },
-    "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All": {
+    "Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer": {
       "tasks": [
-        "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All"
-      ]
-    },
-    "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan": {
-      "tasks": [
-        "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan"
-      ]
-    },
-    "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All": {
-      "tasks": [
-        "Upload-Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All"
-      ]
-    },
-    "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-CommandBuffer": {
-      "tasks": [
-        "Upload-Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-CommandBuffer"
+        "Upload-Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer"
       ],
       "trigger": "master"
     },
-    "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan": {
+    "Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All": {
       "tasks": [
-        "Upload-Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan"
+        "Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All"
       ]
     },
-    "Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All": {
+    "Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All": {
       "tasks": [
-        "Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All"
+        "Upload-Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All"
       ]
     },
-    "Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All": {
+    "Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-CommandBuffer": {
       "tasks": [
-        "Upload-Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All"
-      ]
-    },
-    "Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer": {
-      "tasks": [
-        "Upload-Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer"
+        "Upload-Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-CommandBuffer"
       ],
       "trigger": "master"
     },
-    "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All": {
-      "tasks": [
-        "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All"
-      ]
-    },
-    "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN": {
-      "tasks": [
-        "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN"
-      ]
-    },
-    "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan": {
-      "tasks": [
-        "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan"
-      ]
-    },
-    "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
-      "tasks": [
-        "Upload-Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All"
-      ]
-    },
-    "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ASAN": {
-      "tasks": [
-        "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ASAN"
-      ]
-    },
-    "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan": {
-      "tasks": [
-        "Upload-Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan"
-      ]
-    },
-    "Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41": {
-      "tasks": [
-        "Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41"
-      ]
-    },
-    "Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41": {
-      "tasks": [
-        "Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41"
-      ]
-    },
     "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All": {
       "tasks": [
         "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All"
@@ -1376,6 +1420,12 @@
         "Upload-Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan"
       ]
     },
+    "Perf-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41": {
+      "priority": 0.085,
+      "tasks": [
+        "Perf-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41"
+      ]
+    },
     "Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All": {
       "tasks": [
         "Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All"
@@ -1971,14 +2021,44 @@
         "Upload-Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_Vulkan"
       ]
     },
-    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-All-Android": {
+    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_0_4-Android": {
       "tasks": [
-        "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-All-Android"
+        "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_0_4-Android"
       ]
     },
-    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android": {
+    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_1_4-Android": {
       "tasks": [
-        "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android"
+        "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_1_4-Android"
+      ]
+    },
+    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_2_4-Android": {
+      "tasks": [
+        "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_2_4-Android"
+      ]
+    },
+    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_3_4-Android": {
+      "tasks": [
+        "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_3_4-Android"
+      ]
+    },
+    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_0_4-Android": {
+      "tasks": [
+        "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_0_4-Android"
+      ]
+    },
+    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_1_4-Android": {
+      "tasks": [
+        "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_1_4-Android"
+      ]
+    },
+    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_2_4-Android": {
+      "tasks": [
+        "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_2_4-Android"
+      ]
+    },
+    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_3_4-Android": {
+      "tasks": [
+        "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_3_4-Android"
       ]
     },
     "Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-All-Android": {
@@ -1991,14 +2071,44 @@
         "Upload-Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-All-Android"
       ]
     },
-    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-All-Android": {
+    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_0_4-Android": {
       "tasks": [
-        "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-All-Android"
+        "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_0_4-Android"
       ]
     },
-    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-All-Android": {
+    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_1_4-Android": {
       "tasks": [
-        "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-All-Android"
+        "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_1_4-Android"
+      ]
+    },
+    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_2_4-Android": {
+      "tasks": [
+        "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_2_4-Android"
+      ]
+    },
+    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_3_4-Android": {
+      "tasks": [
+        "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_3_4-Android"
+      ]
+    },
+    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_0_4-Android": {
+      "tasks": [
+        "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_0_4-Android"
+      ]
+    },
+    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_1_4-Android": {
+      "tasks": [
+        "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_1_4-Android"
+      ]
+    },
+    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_2_4-Android": {
+      "tasks": [
+        "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_2_4-Android"
+      ]
+    },
+    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_3_4-Android": {
+      "tasks": [
+        "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_3_4-Android"
       ]
     },
     "Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Debug-All-Android": {
@@ -2096,6 +2206,36 @@
         "Upload-Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan"
       ]
     },
+    "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android": {
+      "tasks": [
+        "Upload-Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android"
+      ]
+    },
+    "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_DDL1_Vulkan": {
+      "tasks": [
+        "Upload-Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_DDL1_Vulkan"
+      ]
+    },
+    "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_DDL3_Vulkan": {
+      "tasks": [
+        "Upload-Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_DDL3_Vulkan"
+      ]
+    },
+    "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_Vulkan": {
+      "tasks": [
+        "Upload-Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_Vulkan"
+      ]
+    },
+    "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android": {
+      "tasks": [
+        "Upload-Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android"
+      ]
+    },
+    "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan": {
+      "tasks": [
+        "Upload-Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan"
+      ]
+    },
     "Test-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Debug-All": {
       "tasks": [
         "Upload-Test-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Debug-All"
@@ -2216,6 +2356,11 @@
         "Upload-Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SafeStack"
       ]
     },
+    "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-Wuffs": {
+      "tasks": [
+        "Upload-Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-Wuffs"
+      ]
+    },
     "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All": {
       "tasks": [
         "Upload-Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All"
@@ -2406,26 +2551,11 @@
         "Upload-Test-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Release-All"
       ]
     },
-    "Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit": {
-      "tasks": [
-        "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit"
-      ]
-    },
     "Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit": {
       "tasks": [
         "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit"
       ]
     },
-    "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-CanvasKit": {
-      "tasks": [
-        "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-CanvasKit"
-      ]
-    },
-    "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit": {
-      "tasks": [
-        "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit"
-      ]
-    },
     "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit": {
       "tasks": [
         "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit"
@@ -2436,11 +2566,6 @@
         "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit"
       ]
     },
-    "Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Debug-All-CanvasKit": {
-      "tasks": [
-        "Upload-Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Debug-All-CanvasKit"
-      ]
-    },
     "Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit": {
       "tasks": [
         "Upload-Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit"
@@ -2466,223 +2591,133 @@
         "Upload-Test-Debian9-GCC-GCE-CPU-AVX2-x86_64-Release-All"
       ]
     },
-    "Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All": {
+    "Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All": {
       "tasks": [
-        "Upload-Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All"
+        "Upload-Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All"
       ]
     },
-    "Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All-CommandBuffer": {
+    "Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All-CommandBuffer": {
       "tasks": [
-        "Upload-Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All-CommandBuffer"
+        "Upload-Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All-CommandBuffer"
       ],
       "trigger": "master"
     },
-    "Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All": {
+    "Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All": {
       "tasks": [
-        "Upload-Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All"
+        "Upload-Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All"
       ]
     },
-    "Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts": {
+    "Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts": {
       "tasks": [
-        "Upload-Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts"
+        "Upload-Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts"
       ]
     },
-    "Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All": {
       "tasks": [
-        "Upload-Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All"
+        "Upload-Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All"
       ]
     },
-    "Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All-CommandBuffer": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN": {
       "tasks": [
-        "Upload-Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All-CommandBuffer"
+        "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN"
+      ]
+    },
+    "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-NativeFonts": {
+      "tasks": [
+        "Upload-Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-NativeFonts"
+      ]
+    },
+    "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All": {
+      "tasks": [
+        "Upload-Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All"
+      ]
+    },
+    "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All-TSAN": {
+      "tasks": [
+        "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All-TSAN"
+      ]
+    },
+    "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All": {
+      "tasks": [
+        "Upload-Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All"
+      ]
+    },
+    "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-CommandBuffer": {
+      "tasks": [
+        "Upload-Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-CommandBuffer"
       ],
       "trigger": "master"
     },
-    "Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal": {
       "tasks": [
-        "Upload-Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All"
+        "Upload-Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan": {
       "tasks": [
-        "Upload-Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All"
+        "Upload-Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-OpenCL": {
       "tasks": [
-        "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN"
+        "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-OpenCL"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-NativeFonts": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All": {
       "tasks": [
-        "Upload-Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-NativeFonts"
+        "Upload-Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-Metal": {
       "tasks": [
-        "Upload-Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All"
+        "Upload-Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-Metal"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All-TSAN": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan": {
       "tasks": [
-        "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All-TSAN"
+        "Upload-Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-TSAN": {
       "tasks": [
-        "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All"
+        "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-TSAN"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-CommandBuffer": {
+    "Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All": {
       "tasks": [
-        "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-CommandBuffer"
+        "Upload-Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All"
+      ]
+    },
+    "Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer": {
+      "tasks": [
+        "Upload-Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer"
       ],
       "trigger": "master"
     },
-    "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal": {
+    "Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All": {
       "tasks": [
-        "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal"
+        "Upload-Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan": {
+    "Test-Mac10.14-Clang-MacBookAir7.2-CPU-AVX2-x86_64-Debug-All-NativeFonts": {
       "tasks": [
-        "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan"
+        "Upload-Test-Mac10.14-Clang-MacBookAir7.2-CPU-AVX2-x86_64-Debug-All-NativeFonts"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All": {
+    "Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All": {
       "tasks": [
-        "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All"
+        "Upload-Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-Metal": {
+    "Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All-CommandBuffer": {
       "tasks": [
-        "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-Metal"
-      ]
-    },
-    "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan": {
-      "tasks": [
-        "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan"
-      ]
-    },
-    "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-TSAN": {
-      "tasks": [
-        "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-TSAN"
-      ]
-    },
-    "Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All": {
-      "tasks": [
-        "Upload-Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All"
-      ]
-    },
-    "Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer": {
-      "tasks": [
-        "Upload-Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer"
+        "Upload-Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All-CommandBuffer"
       ],
       "trigger": "master"
     },
-    "Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All": {
+    "Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All": {
       "tasks": [
-        "Upload-Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All": {
-      "tasks": [
-        "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN": {
-      "tasks": [
-        "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1": {
-      "tasks": [
-        "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan": {
-      "tasks": [
-        "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3": {
-      "tasks": [
-        "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN": {
-      "tasks": [
-        "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN_Vulkan": {
-      "tasks": [
-        "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN_Vulkan"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan": {
-      "tasks": [
-        "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan": {
-      "tasks": [
-        "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
-      "tasks": [
-        "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ASAN": {
-      "tasks": [
-        "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ASAN"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN": {
-      "tasks": [
-        "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN_Vulkan": {
-      "tasks": [
-        "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN_Vulkan"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_TSAN": {
-      "tasks": [
-        "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_TSAN"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_TSAN_Vulkan": {
-      "tasks": [
-        "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_TSAN_Vulkan"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-PreAbandonGpuContext": {
-      "tasks": [
-        "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-PreAbandonGpuContext"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan": {
-      "tasks": [
-        "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan"
-      ]
-    },
-    "Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41": {
-      "tasks": [
-        "Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41"
-      ]
-    },
-    "Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_PreAbandonGpuContext_SK_CPU_LIMIT_SSE41": {
-      "tasks": [
-        "Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_PreAbandonGpuContext_SK_CPU_LIMIT_SSE41"
-      ]
-    },
-    "Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41": {
-      "tasks": [
-        "Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41"
+        "Upload-Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All"
       ]
     },
     "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All": {
@@ -2715,11 +2750,6 @@
         "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN"
       ]
     },
-    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN_Vulkan": {
-      "tasks": [
-        "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN_Vulkan"
-      ]
-    },
     "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan": {
       "tasks": [
         "Upload-Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan"
@@ -2745,11 +2775,6 @@
         "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN"
       ]
     },
-    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN_Vulkan": {
-      "tasks": [
-        "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN_Vulkan"
-      ]
-    },
     "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_TSAN": {
       "tasks": [
         "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_TSAN"
@@ -2770,6 +2795,24 @@
         "Upload-Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan"
       ]
     },
+    "Test-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41": {
+      "priority": 0.085,
+      "tasks": [
+        "Test-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41"
+      ]
+    },
+    "Test-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_PreAbandonGpuContext_SK_CPU_LIMIT_SSE41": {
+      "priority": 0.085,
+      "tasks": [
+        "Test-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_PreAbandonGpuContext_SK_CPU_LIMIT_SSE41"
+      ]
+    },
+    "Test-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41": {
+      "priority": 0.085,
+      "tasks": [
+        "Test-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41"
+      ]
+    },
     "Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All": {
       "tasks": [
         "Upload-Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All"
@@ -3135,11 +3178,6 @@
         "Upload-Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FAAA"
       ]
     },
-    "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FDAA": {
-      "tasks": [
-        "Upload-Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FDAA"
-      ]
-    },
     "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FSAA": {
       "tasks": [
         "Upload-Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FSAA"
@@ -3160,11 +3198,6 @@
         "Upload-Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FAAA"
       ]
     },
-    "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FDAA": {
-      "tasks": [
-        "Upload-Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FDAA"
-      ]
-    },
     "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FSAA": {
       "tasks": [
         "Upload-Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FSAA"
@@ -3376,7 +3409,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Debug-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Debug-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -3388,7 +3421,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -3501,7 +3534,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Debug-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Debug-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -3513,7 +3546,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -3590,7 +3623,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         },
         {
           "name": "skia/bots/armhf_sysroot",
@@ -3641,7 +3674,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Debug-Chromebook_GLES\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Debug-Chromebook_GLES\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -3652,7 +3685,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -3775,7 +3808,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Debug-Chromecast\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Debug-Chromecast\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -3786,7 +3819,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -3899,7 +3932,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Release-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Release-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -3911,7 +3944,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -4024,7 +4057,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Release-Android_API26\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Release-Android_API26\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -4036,7 +4069,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -4149,7 +4182,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Release-Android_ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Release-Android_ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -4161,7 +4194,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -4274,7 +4307,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Release-Android_ASAN_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Release-Android_ASAN_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -4286,7 +4319,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -4399,7 +4432,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Release-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Release-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -4411,7 +4444,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -4488,7 +4521,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         },
         {
           "name": "skia/bots/armhf_sysroot",
@@ -4539,7 +4572,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Release-Chromebook_GLES\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Release-Chromebook_GLES\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -4550,7 +4583,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -4673,7 +4706,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Release-Chromecast\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Release-Chromecast\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -4684,7 +4717,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -4797,7 +4830,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Release-Flutter_Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm-Release-Flutter_Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -4809,7 +4842,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -4922,7 +4955,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm64-Debug-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm64-Debug-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -4934,7 +4967,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -5047,7 +5080,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm64-Debug-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm64-Debug-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -5059,7 +5092,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -5172,7 +5205,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm64-Release-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm64-Release-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -5184,7 +5217,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -5297,7 +5330,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm64-Release-Android_ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm64-Release-Android_ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -5309,7 +5342,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -5422,7 +5455,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm64-Release-Android_ASAN_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm64-Release-Android_ASAN_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -5434,7 +5467,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -5547,7 +5580,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm64-Release-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-arm64-Release-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -5559,7 +5592,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -5645,7 +5678,7 @@
         "-recipe",
         "android_compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-cf_x86_phone-eng-Android_Framework\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-cf_x86_phone-eng-Android_Framework\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -5656,7 +5689,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -5739,7 +5772,7 @@
         "-recipe",
         "android_compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-host-sdk-Android_Framework\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-host-sdk-Android_Framework\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -5750,7 +5783,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -5860,7 +5893,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x64-Debug-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x64-Debug-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -5872,7 +5905,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -5985,7 +6018,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x64-Release-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x64-Release-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -5997,7 +6030,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -6074,7 +6107,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -6115,7 +6148,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -6126,7 +6159,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -6239,7 +6272,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86-Debug-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86-Debug-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -6251,7 +6284,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -6364,7 +6397,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86-Debug-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86-Debug-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -6376,7 +6409,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -6489,7 +6522,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86-Release-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86-Release-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -6501,7 +6534,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -6614,7 +6647,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86-Release-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86-Release-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -6626,7 +6659,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -6743,7 +6776,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86-devrel-Android_SKQP\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86-devrel-Android_SKQP\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -6754,7 +6787,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -6831,7 +6864,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -6872,7 +6905,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -6883,7 +6916,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -6960,7 +6993,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -7001,7 +7034,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -7012,7 +7045,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -7089,7 +7122,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -7130,7 +7163,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-ASAN_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-ASAN_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -7141,7 +7174,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -7218,7 +7251,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         },
         {
           "name": "skia/bots/chromebook_x86_64_gles",
@@ -7264,7 +7297,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-Chromebook_GLES\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-Chromebook_GLES\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -7275,7 +7308,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -7352,7 +7385,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -7393,7 +7426,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-MSAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-MSAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -7404,7 +7437,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -7481,7 +7514,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         },
         {
           "name": "skia/bots/opencl_headers",
@@ -7532,7 +7565,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-OpenCL\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-OpenCL\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -7543,7 +7576,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -7620,7 +7653,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -7661,7 +7694,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -7672,7 +7705,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -7749,7 +7782,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -7790,7 +7823,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-SafeStack\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-SafeStack\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -7801,7 +7834,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -7878,7 +7911,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -7919,7 +7952,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-Static\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-Static\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -7930,7 +7963,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -8007,7 +8040,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         },
         {
           "name": "skia/bots/cmake_linux",
@@ -8053,7 +8086,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-SwiftShader\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-SwiftShader\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -8064,7 +8097,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -8141,7 +8174,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -8182,7 +8215,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-Tidy\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-Tidy\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -8193,7 +8226,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -8270,7 +8303,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -8311,7 +8344,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -8322,7 +8355,136 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "build"
+      ],
+      "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Build-Debian9-Clang-x86_64-Debug-Wuffs": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        },
+        {
+          "name": "git",
+          "path": "cache/git"
+        },
+        {
+          "name": "git_cache",
+          "path": "cache/git_cache"
+        },
+        {
+          "name": "work",
+          "path": "cache/work"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.17.1.chromium15"
+        },
+        {
+          "name": "infra/tools/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:c9c8a52bfeaf8bc00ece22fdfd447822c8fcad77"
+        },
+        {
+          "name": "infra/tools/luci/git-credential-luci/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "skia/bots/clang_linux",
+          "path": "clang_linux",
+          "version": "version:14"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "compile",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-Wuffs\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highcpu-64",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -8399,7 +8561,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -8440,7 +8602,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -8451,7 +8613,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -8528,7 +8690,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -8569,7 +8731,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -8580,7 +8742,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -8657,7 +8819,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -8698,7 +8860,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -8709,7 +8871,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -8786,7 +8948,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -8827,7 +8989,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-ASAN_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-ASAN_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -8838,7 +9000,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -8919,7 +9081,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -8960,7 +9122,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-CMake\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-CMake\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -8971,7 +9133,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia",
         "docker_installed:true"
       ],
@@ -9049,7 +9211,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         },
         {
           "name": "skia/bots/chromebook_x86_64_gles",
@@ -9095,7 +9257,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-Chromebook_GLES\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-Chromebook_GLES\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -9106,7 +9268,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -9183,7 +9345,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -9224,7 +9386,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-Fast\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-Fast\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -9235,7 +9397,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -9308,7 +9470,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -9349,7 +9511,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-NoDEPS\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-NoDEPS\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -9360,7 +9522,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -9437,7 +9599,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -9478,7 +9640,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-ParentRevision\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-ParentRevision\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -9489,7 +9651,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -9566,7 +9728,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -9607,7 +9769,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-SKNX_NO_SIMD\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-SKNX_NO_SIMD\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -9618,7 +9780,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -9695,7 +9857,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -9736,7 +9898,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-SK_CPU_LIMIT_SSE2\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-SK_CPU_LIMIT_SSE2\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -9747,7 +9909,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -9824,7 +9986,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -9865,7 +10027,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-SK_CPU_LIMIT_SSE41\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-SK_CPU_LIMIT_SSE41\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -9876,7 +10038,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -9953,7 +10115,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -9994,7 +10156,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-SK_FORCE_RASTER_PIPELINE_BLITTER\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-SK_FORCE_RASTER_PIPELINE_BLITTER\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -10005,7 +10167,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -10082,7 +10244,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -10123,7 +10285,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-Static\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-Static\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -10134,7 +10296,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -10211,7 +10373,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         },
         {
           "name": "skia/bots/cmake_linux",
@@ -10257,7 +10419,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-SwiftShader\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-SwiftShader\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -10268,7 +10430,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -10345,7 +10507,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -10386,7 +10548,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-TSAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-TSAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -10397,7 +10559,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -10474,7 +10636,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -10515,7 +10677,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-TSAN_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-TSAN_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -10526,7 +10688,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -10603,7 +10765,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -10644,7 +10806,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -10655,7 +10817,136 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "build"
+      ],
+      "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Build-Debian9-Clang-x86_64-Release-Wuffs": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        },
+        {
+          "name": "git",
+          "path": "cache/git"
+        },
+        {
+          "name": "git_cache",
+          "path": "cache/git_cache"
+        },
+        {
+          "name": "work",
+          "path": "cache/work"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.17.1.chromium15"
+        },
+        {
+          "name": "infra/tools/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:c9c8a52bfeaf8bc00ece22fdfd447822c8fcad77"
+        },
+        {
+          "name": "infra/tools/luci/git-credential-luci/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "skia/bots/clang_linux",
+          "path": "clang_linux",
+          "version": "version:14"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "compile",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Release-Wuffs\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highcpu-64",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -10772,7 +11063,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-asmjs-Debug-PathKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-asmjs-Debug-PathKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -10783,7 +11074,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia",
         "docker_installed:true"
       ],
@@ -10901,7 +11192,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-asmjs-Release-PathKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-asmjs-Release-PathKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -10912,7 +11203,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia",
         "docker_installed:true"
       ],
@@ -11030,7 +11321,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-wasm-Debug-CanvasKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-wasm-Debug-CanvasKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -11041,7 +11332,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia",
         "docker_installed:true"
       ],
@@ -11159,7 +11450,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-wasm-Debug-CanvasKit_CPU\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-wasm-Debug-CanvasKit_CPU\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -11170,7 +11461,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia",
         "docker_installed:true"
       ],
@@ -11288,7 +11579,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-wasm-Debug-PathKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-wasm-Debug-PathKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -11299,7 +11590,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia",
         "docker_installed:true"
       ],
@@ -11417,7 +11708,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-wasm-Release-CanvasKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-wasm-Release-CanvasKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -11428,7 +11719,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia",
         "docker_installed:true"
       ],
@@ -11546,7 +11837,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -11557,7 +11848,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia",
         "docker_installed:true"
       ],
@@ -11675,7 +11966,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-wasm-Release-PathKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-wasm-Release-PathKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -11686,7 +11977,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia",
         "docker_installed:true"
       ],
@@ -11805,7 +12096,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-loongson3a-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-loongson3a-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -11816,7 +12107,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -11934,7 +12225,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-loongson3a-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-loongson3a-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -11945,7 +12236,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -12063,7 +12354,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-mips64el-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-mips64el-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -12074,7 +12365,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -12192,7 +12483,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-mips64el-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-mips64el-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -12203,7 +12494,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -12316,7 +12607,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-x86-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-x86-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -12327,7 +12618,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -12440,7 +12731,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-x86-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-x86-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -12451,7 +12742,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -12564,7 +12855,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-x86_64-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-x86_64-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -12575,7 +12866,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -12688,7 +12979,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-x86_64-Debug-NoGPU\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-x86_64-Debug-NoGPU\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -12699,7 +12990,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -12812,7 +13103,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-x86_64-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-x86_64-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -12823,7 +13114,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -12936,7 +13227,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-x86_64-Release-NoGPU\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-x86_64-Release-NoGPU\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -12947,7 +13238,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -13060,7 +13351,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-x86_64-Release-SK_CPU_LIMIT_SSE41\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-x86_64-Release-SK_CPU_LIMIT_SSE41\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -13071,7 +13362,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -13184,7 +13475,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-x86_64-Release-Shared\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-GCC-x86_64-Release-Shared\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -13195,7 +13486,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -13317,7 +13608,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-arm-Debug-iOS\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-arm-Debug-iOS\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -13449,7 +13740,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-arm-Release-iOS\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-arm-Release-iOS\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -13536,7 +13827,7 @@
         {
           "name": "skia/bots/android_ndk_darwin",
           "path": "android_ndk_darwin",
-          "version": "version:7"
+          "version": "version:8"
         }
       ],
       "command": [
@@ -13577,7 +13868,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-arm64-Debug-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-arm64-Debug-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -13709,7 +14000,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-arm64-Debug-iOS\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-arm64-Debug-iOS\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -13841,7 +14132,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-arm64-Debug-iOS_Metal\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-arm64-Debug-iOS_Metal\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -13973,7 +14264,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-arm64-Release-iOS\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-arm64-Release-iOS\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -14105,7 +14396,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x64-Release-iOS\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x64-Release-iOS\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -14237,7 +14528,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -14369,7 +14660,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Debug-ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Debug-ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -14501,7 +14792,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Debug-CommandBuffer\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Debug-CommandBuffer\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -14633,7 +14924,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Debug-Metal\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Debug-Metal\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -14770,7 +15061,139 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Debug-MoltenVK_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Debug-MoltenVK_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes"
+      ],
+      "dimensions": [
+        "cpu:x86-64-E5-2697_v2",
+        "gpu:none",
+        "os:Mac-10.13.6",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "build"
+      ],
+      "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Build-Mac-Clang-x86_64-Debug-OpenCL": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        },
+        {
+          "name": "git",
+          "path": "cache/git"
+        },
+        {
+          "name": "git_cache",
+          "path": "cache/git_cache"
+        },
+        {
+          "name": "work",
+          "path": "cache/work"
+        },
+        {
+          "name": "xcode",
+          "path": "cache/Xcode.app"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.17.1.chromium15"
+        },
+        {
+          "name": "infra/tools/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:c9c8a52bfeaf8bc00ece22fdfd447822c8fcad77"
+        },
+        {
+          "name": "infra/tools/luci/git-credential-luci/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/mac_toolchain/${platform}",
+          "path": "mac_toolchain",
+          "version": "git_revision:796d2b92cff93fc2059623ce0a66284373ceea0a"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "compile",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Debug-OpenCL\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -14902,7 +15325,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -15034,7 +15457,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Release-CommandBuffer\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Release-CommandBuffer\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -15166,7 +15589,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Release-Metal\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Release-Metal\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -15303,7 +15726,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Release-MoltenVK_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Release-MoltenVK_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -15435,7 +15858,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Release-TSAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Mac-Clang-x86_64-Release-TSAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -15527,7 +15950,7 @@
         {
           "name": "skia/bots/android_ndk_windows",
           "path": "n",
-          "version": "version:8"
+          "version": "version:9"
         }
       ],
       "command": [
@@ -15568,7 +15991,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-arm64-Release-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-arm64-Release-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -15662,7 +16085,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:8"
+          "version": "version:9"
         }
       ],
       "command": [
@@ -15703,7 +16126,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -15798,7 +16221,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:8"
+          "version": "version:9"
         }
       ],
       "command": [
@@ -15839,7 +16262,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86-Debug-Exceptions\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86-Debug-Exceptions\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -15934,7 +16357,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:8"
+          "version": "version:9"
         }
       ],
       "command": [
@@ -15975,7 +16398,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -16070,7 +16493,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:8"
+          "version": "version:9"
         }
       ],
       "command": [
@@ -16111,7 +16534,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -16206,7 +16629,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:8"
+          "version": "version:9"
         }
       ],
       "command": [
@@ -16247,7 +16670,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Debug-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Debug-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -16342,7 +16765,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:8"
+          "version": "version:9"
         },
         {
           "name": "skia/bots/opencl_headers",
@@ -16388,7 +16811,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Debug-OpenCL\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Debug-OpenCL\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -16483,7 +16906,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:8"
+          "version": "version:9"
         }
       ],
       "command": [
@@ -16524,7 +16947,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Debug-UBSAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Debug-UBSAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -16619,7 +17042,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:8"
+          "version": "version:9"
         }
       ],
       "command": [
@@ -16660,7 +17083,143 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Debug-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Debug-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Housekeeper-PerCommit-IsolateWinToolchain"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "image:windows-server-2016-dc-v20190108",
+        "machine_type:n1-highcpu-64",
+        "os:Windows-2016Server-14393",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "build"
+      ],
+      "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Build-Win-Clang-x86_64-Debug-Wuffs": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        },
+        {
+          "name": "git",
+          "path": "cache/git"
+        },
+        {
+          "name": "git_cache",
+          "path": "cache/git_cache"
+        },
+        {
+          "name": "work",
+          "path": "cache/work"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "infra/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.17.1.chromium15"
+        },
+        {
+          "name": "infra/tools/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:c9c8a52bfeaf8bc00ece22fdfd447822c8fcad77"
+        },
+        {
+          "name": "infra/tools/luci/git-credential-luci/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "skia/bots/clang_win",
+          "path": "clang_win",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "compile",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Debug-Wuffs\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -16755,7 +17314,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:8"
+          "version": "version:9"
         }
       ],
       "command": [
@@ -16796,7 +17355,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -16891,7 +17450,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:8"
+          "version": "version:9"
         }
       ],
       "command": [
@@ -16932,7 +17491,143 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Release-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Release-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Housekeeper-PerCommit-IsolateWinToolchain"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "image:windows-server-2016-dc-v20190108",
+        "machine_type:n1-highcpu-64",
+        "os:Windows-2016Server-14393",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "build"
+      ],
+      "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Build-Win-Clang-x86_64-Release-Shared": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        },
+        {
+          "name": "git",
+          "path": "cache/git"
+        },
+        {
+          "name": "git_cache",
+          "path": "cache/git_cache"
+        },
+        {
+          "name": "work",
+          "path": "cache/work"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "infra/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.17.1.chromium15"
+        },
+        {
+          "name": "infra/tools/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:c9c8a52bfeaf8bc00ece22fdfd447822c8fcad77"
+        },
+        {
+          "name": "infra/tools/luci/git-credential-luci/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "skia/bots/clang_win",
+          "path": "clang_win",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "compile",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Release-Shared\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -17027,7 +17722,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:8"
+          "version": "version:9"
         }
       ],
       "command": [
@@ -17068,7 +17763,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Release-UBSAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Release-UBSAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -17163,7 +17858,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:8"
+          "version": "version:9"
         }
       ],
       "command": [
@@ -17204,7 +17899,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Release-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-Clang-x86_64-Release-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -17335,7 +18030,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-arm64-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-arm64-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -17466,7 +18161,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-arm64-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-arm64-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -17597,7 +18292,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-x86-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-x86-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -17728,7 +18423,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-x86-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-x86-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -17859,7 +18554,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-x86_64-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-x86_64-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -17990,7 +18685,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-x86_64-Debug-MSRTC\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-x86_64-Debug-MSRTC\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -18121,7 +18816,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-x86_64-Debug-MSRTC_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-x86_64-Debug-MSRTC_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -18252,7 +18947,138 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-x86_64-Debug-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-x86_64-Debug-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Housekeeper-PerCommit-IsolateWinToolchain"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "image:windows-server-2016-dc-v20190108",
+        "machine_type:n1-highcpu-64",
+        "os:Windows-2016Server-14393",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "build"
+      ],
+      "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Build-Win-MSVC-x86_64-Debug-Wuffs": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        },
+        {
+          "name": "git",
+          "path": "cache/git"
+        },
+        {
+          "name": "git_cache",
+          "path": "cache/git_cache"
+        },
+        {
+          "name": "work",
+          "path": "cache/work"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "infra/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.17.1.chromium15"
+        },
+        {
+          "name": "infra/tools/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:c9c8a52bfeaf8bc00ece22fdfd447822c8fcad77"
+        },
+        {
+          "name": "infra/tools/luci/git-credential-luci/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "compile",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-x86_64-Debug-Wuffs\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -18383,7 +19209,138 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-x86_64-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-x86_64-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Housekeeper-PerCommit-IsolateWinToolchain"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "image:windows-server-2016-dc-v20190108",
+        "machine_type:n1-highcpu-64",
+        "os:Windows-2016Server-14393",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "build"
+      ],
+      "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Build-Win-MSVC-x86_64-Release-Shared": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        },
+        {
+          "name": "git",
+          "path": "cache/git"
+        },
+        {
+          "name": "git_cache",
+          "path": "cache/git_cache"
+        },
+        {
+          "name": "work",
+          "path": "cache/work"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "infra/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.17.1.chromium15"
+        },
+        {
+          "name": "infra/tools/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:c9c8a52bfeaf8bc00ece22fdfd447822c8fcad77"
+        },
+        {
+          "name": "infra/tools/luci/git-credential-luci/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "compile",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-x86_64-Release-Shared\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -18514,7 +19471,7 @@
         "-recipe",
         "compile",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-x86_64-Release-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-x86_64-Release-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -18618,7 +19575,7 @@
         "-recipe",
         "compute_buildstats",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-Clang-arm-Release-Flutter_Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-Clang-arm-Release-Flutter_Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -18630,8 +19587,112 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
-        "pool:Skia"
+        "os:Debian-9.8",
+        "pool:Skia",
+        "docker_installed:true"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "perf"
+      ]
+    },
+    "BuildStats-Debian9-Clang-x86_64-Release": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "skia/bots/bloaty",
+          "path": "bloaty",
+          "version": "version:1"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "compute_buildstats",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-Clang-x86_64-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86_64-Release"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-standard-16",
+        "os:Debian-9.8",
+        "pool:Skia",
+        "docker_installed:true"
       ],
       "env_prefixes": {
         "PATH": [
@@ -18720,7 +19781,7 @@
         "-recipe",
         "compute_buildstats",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-asmjs-Release-PathKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-asmjs-Release-PathKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -18732,7 +19793,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia",
         "docker_installed:true"
       ],
@@ -18823,7 +19884,7 @@
         "-recipe",
         "compute_buildstats",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Debug-CanvasKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Debug-CanvasKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -18835,7 +19896,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia",
         "docker_installed:true"
       ],
@@ -18926,7 +19987,7 @@
         "-recipe",
         "compute_buildstats",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Debug-CanvasKit_CPU\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Debug-CanvasKit_CPU\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -18938,7 +19999,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia",
         "docker_installed:true"
       ],
@@ -19029,7 +20090,7 @@
         "-recipe",
         "compute_buildstats",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Release-CanvasKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Release-CanvasKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -19041,7 +20102,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia",
         "docker_installed:true"
       ],
@@ -19132,7 +20193,7 @@
         "-recipe",
         "compute_buildstats",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Release-CanvasKit_CPU\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Release-CanvasKit_CPU\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -19144,7 +20205,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia",
         "docker_installed:true"
       ],
@@ -19235,7 +20296,7 @@
         "-recipe",
         "compute_buildstats",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Release-PathKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Release-PathKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -19247,7 +20308,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia",
         "docker_installed:true"
       ],
@@ -19360,7 +20421,7 @@
         "-recipe",
         "calmbench",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Calmbench-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Calmbench-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -19374,7 +20435,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -19397,7 +20458,7 @@
         "perf"
       ]
     },
-    "Calmbench-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
+    "Calmbench-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -19486,7 +20547,7 @@
         "-recipe",
         "calmbench",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Calmbench-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Calmbench-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -19498,10 +20559,10 @@
         "Housekeeper-PerCommit-IsolateSVG"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
+        "gpu:10de:1cb3-415.27",
+        "os:Ubuntu-18.04",
         "pool:Skia",
-        "rack:1"
+        "rack:2"
       ],
       "env_prefixes": {
         "PATH": [
@@ -19523,121 +20584,6 @@
         "perf"
       ]
     },
-    "Housekeeper-Nightly-Bookmaker": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        },
-        {
-          "name": "work",
-          "path": "cache/work"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "infra/git/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "version:2.17.1.chromium15"
-        },
-        {
-          "name": "infra/tools/git/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:c9c8a52bfeaf8bc00ece22fdfd447822c8fcad77"
-        },
-        {
-          "name": "infra/tools/luci/git-credential-luci/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "bookmaker",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Housekeeper-Nightly-Bookmaker\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-GCC-x86_64-Release",
-        "Housekeeper-PerCommit-IsolateGoDeps"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "gpu:none",
-        "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 7200000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 7200000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "service_account": "skia-bookmaker@skia-swarming-bots.iam.gserviceaccount.com"
-    },
     "Housekeeper-Nightly-RecreateSKPs_Canary": {
       "caches": [
         {
@@ -19724,7 +20670,7 @@
         "-recipe",
         "recreate_skps",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Housekeeper-Nightly-RecreateSKPs_Canary\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Housekeeper-Nightly-RecreateSKPs_Canary\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -19734,7 +20680,7 @@
       ],
       "dimensions": [
         "pool:SkiaCT",
-        "os:Debian-9.4"
+        "os:Debian-9.8"
       ],
       "env_prefixes": {
         "PATH": [
@@ -19840,7 +20786,7 @@
         "-recipe",
         "update_go_deps",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Housekeeper-Nightly-UpdateGoDEPS\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Housekeeper-Nightly-UpdateGoDEPS\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -19852,7 +20798,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -19922,6 +20868,11 @@
           "name": "infra/tools/luci/git-credential-luci/${platform}",
           "path": "cipd_bin_packages",
           "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",
+          "path": "recipe_bundle",
+          "version": "refs/heads/master"
         }
       ],
       "command": [
@@ -19962,19 +20913,15 @@
         "-recipe",
         "run_presubmit",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Housekeeper-OnDemand-Presubmit\",\"category\":\"cq\",\"patch_gerrit_url\":\"https://skia-review.googlesource.com\",\"patch_issue\":\"<(ISSUE)\",\"patch_project\":\"skia\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"reason\":\"CQ\",\"repo_name\":\"skia\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Housekeeper-OnDemand-Presubmit\",\"category\":\"cq\",\"patch_gerrit_url\":\"https://skia-review.googlesource.com\",\"patch_issue\":\"<(ISSUE)\",\"patch_project\":\"skia\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"reason\":\"CQ\",\"repo_name\":\"skia\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations",
-        "-repository",
-        "https://chromium.googlesource.com/chromium/tools/build",
-        "-revision",
-        "HEAD"
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -20084,7 +21031,7 @@
         "-recipe",
         "housekeeper",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Housekeeper-PerCommit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Housekeeper-PerCommit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -20095,7 +21042,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -20148,7 +21095,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -20168,6 +21115,10 @@
         {
           "name": "work",
           "path": "cache/work"
+        },
+        {
+          "name": "go_cache",
+          "path": "cache/go_cache"
         }
       ],
       "cipd_packages": [
@@ -20185,6 +21136,11 @@
           "name": "infra/tools/luci/vpython/${platform}",
           "path": "cipd_bin_packages",
           "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "skia/bots/go",
+          "path": "go",
+          "version": "version:6"
         }
       ],
       "command": [
@@ -20225,18 +21181,19 @@
         "-recipe",
         "check_generated_files",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Housekeeper-PerCommit-CheckGeneratedFiles\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Housekeeper-PerCommit-CheckGeneratedFiles\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes"
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Housekeeper-PerCommit-IsolateGoDeps"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highcpu-64",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -20355,7 +21312,7 @@
         "-recipe",
         "infra",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Housekeeper-PerCommit-InfraTests\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Housekeeper-PerCommit-InfraTests\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -20367,7 +21324,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -20393,7 +21350,7 @@
         {
           "name": "skia/bots/android_ndk_linux",
           "path": "android_ndk_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -20406,7 +21363,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "isolate": "empty.isolate"
@@ -20416,7 +21373,7 @@
         {
           "name": "skia/bots/go_deps",
           "path": "go_deps",
-          "version": "version:144"
+          "version": "version:208"
         }
       ],
       "command": [
@@ -20429,7 +21386,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "isolate": "empty.isolate"
@@ -20439,7 +21396,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         }
       ],
       "command": [
@@ -20452,7 +21409,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "isolate": "empty.isolate"
@@ -20475,7 +21432,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "isolate": "empty.isolate"
@@ -20498,7 +21455,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "isolate": "empty.isolate"
@@ -20521,7 +21478,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "isolate": "empty.isolate"
@@ -20612,7 +21569,7 @@
         "-recipe",
         "recreate_skps",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Housekeeper-Weekly-RecreateSKPs\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Housekeeper-Weekly-RecreateSKPs\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -20622,7 +21579,7 @@
       ],
       "dimensions": [
         "pool:SkiaCT",
-        "os:Debian-9.4"
+        "os:Debian-9.8"
       ],
       "env_prefixes": {
         "PATH": [
@@ -20704,7 +21661,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -20804,7 +21761,112 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm-Release-Android",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:MOB30Q",
+        "device_type:sprout",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "perf_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "perf"
+      ]
+    },
+    "Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android_SkottieTracing": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "skia/bots/lottie-samples",
+          "path": "lottie-samples",
+          "version": "version:1"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "perf_skottietrace",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android_SkottieTracing\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -20904,7 +21966,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -21004,7 +22066,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -21104,7 +22166,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android_NoGPUThreads\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android_NoGPUThreads\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -21204,7 +22266,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -21304,7 +22366,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -21404,7 +22466,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -21504,7 +22566,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -21604,7 +22666,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -21704,7 +22766,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Debug-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Debug-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -21804,7 +22866,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -21904,7 +22966,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -22004,7 +23066,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -22104,7 +23166,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -22204,7 +23266,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-MotoG4-GPU-Adreno405-arm-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-MotoG4-GPU-Adreno405-arm-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -22304,7 +23366,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -22404,7 +23466,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android_NoGPUThreads\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android_NoGPUThreads\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -22504,7 +23566,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -22604,7 +23666,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -22704,7 +23766,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -22804,7 +23866,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -22904,7 +23966,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan_NoGPUThreads\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan_NoGPUThreads\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -23004,7 +24066,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5-CPU-Snapdragon800-arm-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5-CPU-Snapdragon800-arm-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -23104,7 +24166,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5-CPU-Snapdragon800-arm-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5-CPU-Snapdragon800-arm-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -23204,7 +24266,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -23304,7 +24366,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -23404,7 +24466,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-CPU-Snapdragon808-arm64-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-CPU-Snapdragon808-arm64-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -23504,7 +24566,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-CPU-Snapdragon808-arm64-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-CPU-Snapdragon808-arm64-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -23604,7 +24666,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm-Release-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm-Release-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -23704,7 +24766,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -23804,7 +24866,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -23904,7 +24966,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -24004,7 +25066,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -24104,7 +25166,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -24204,7 +25266,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -24304,7 +25366,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -24404,7 +25466,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -24504,7 +25566,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -24604,7 +25666,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -24704,7 +25766,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -24804,7 +25866,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -24904,7 +25966,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -25004,7 +26066,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -25104,7 +26166,7 @@
         "-recipe",
         "skpbench",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_CCPR_Skpbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_CCPR_Skpbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -25202,7 +26264,7 @@
         "-recipe",
         "skpbench",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Skpbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Skpbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -25300,7 +26362,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -25400,7 +26462,7 @@
         "-recipe",
         "skpbench",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Vulkan_Skpbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Vulkan_Skpbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -25498,7 +26560,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -25598,7 +26660,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -25698,7 +26760,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -25798,7 +26860,7 @@
         "-recipe",
         "skpbench",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_CCPR_Skpbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_CCPR_Skpbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -25896,7 +26958,7 @@
         "-recipe",
         "skpbench",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Skpbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Skpbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -25994,7 +27056,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -26094,7 +27156,7 @@
         "-recipe",
         "skpbench",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan_Skpbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan_Skpbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -26130,6 +27192,700 @@
         "perf"
       ]
     },
+    "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "perf",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm64-Debug-Android",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:PQ1A.190105.004",
+        "device_type:blueline",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "perf_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "perf"
+      ]
+    },
+    "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "perf",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm64-Debug-Android_Vulkan",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:PQ1A.190105.004",
+        "device_type:blueline",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "perf_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "perf"
+      ]
+    },
+    "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "perf",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm64-Release-Android",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:PQ1A.190105.004",
+        "device_type:blueline",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "perf_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "perf"
+      ]
+    },
+    "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_CCPR_Skpbench": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "skpbench",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_CCPR_Skpbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm64-Release-Android",
+        "Housekeeper-PerCommit-IsolateSKP"
+      ],
+      "dimensions": [
+        "device_os:PQ1A.190105.004",
+        "device_type:blueline",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "skpbench_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "perf"
+      ]
+    },
+    "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Skpbench": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "skpbench",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Skpbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm64-Release-Android",
+        "Housekeeper-PerCommit-IsolateSKP"
+      ],
+      "dimensions": [
+        "device_os:PQ1A.190105.004",
+        "device_type:blueline",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "skpbench_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "perf"
+      ]
+    },
+    "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "perf",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm64-Release-Android_Vulkan",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:PQ1A.190105.004",
+        "device_type:blueline",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "perf_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "perf"
+      ]
+    },
+    "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan_Skpbench": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "skpbench",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan_Skpbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm64-Release-Android_Vulkan",
+        "Housekeeper-PerCommit-IsolateSKP"
+      ],
+      "dimensions": [
+        "device_os:PQ1A.190105.004",
+        "device_type:blueline",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "skpbench_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "perf"
+      ]
+    },
     "Perf-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Debug-All": {
       "caches": [
         {
@@ -26192,7 +27948,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -26292,7 +28048,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -26392,7 +28148,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-AcerChromebook13_CB5_311-GPU-TegraK1-arm-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-AcerChromebook13_CB5_311-GPU-TegraK1-arm-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -26492,7 +28248,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-AcerChromebook13_CB5_311-GPU-TegraK1-arm-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-AcerChromebook13_CB5_311-GPU-TegraK1-arm-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -26592,7 +28348,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -26692,7 +28448,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -26792,7 +28548,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-Pixelbook-GPU-IntelHDGraphics615-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-Pixelbook-GPU-IntelHDGraphics615-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -26892,7 +28648,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-Pixelbook-GPU-IntelHDGraphics615-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-Pixelbook-GPU-IntelHDGraphics615-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -26992,7 +28748,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-SamsungChromebook2012-GPU-MaliT604-arm-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-SamsungChromebook2012-GPU-MaliT604-arm-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -27092,7 +28848,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-SamsungChromebook2012-GPU-MaliT604-arm-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-SamsungChromebook2012-GPU-MaliT604-arm-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -27192,7 +28948,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -27292,7 +29048,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -27392,7 +29148,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -27490,7 +29246,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -27588,7 +29344,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -27686,7 +29442,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -27753,7 +29509,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -27799,7 +29555,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -27810,7 +29566,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -27865,7 +29621,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -27911,7 +29667,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -27922,7 +29678,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -27977,7 +29733,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -27987,7 +29743,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -28028,7 +29784,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -28039,7 +29795,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -28094,7 +29850,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -28140,7 +29896,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -28151,7 +29907,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -28206,7 +29962,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -28216,7 +29972,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -28257,7 +30013,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -28268,7 +30024,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -28323,7 +30079,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -28369,7 +30125,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SK_USE_DISCARDABLE_SCALEDIMAGECACHE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SK_USE_DISCARDABLE_SCALEDIMAGECACHE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -28380,7 +30136,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -28435,7 +30191,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -28481,7 +30237,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SafeStack\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SafeStack\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -28492,7 +30248,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -28547,7 +30303,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -28593,7 +30349,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -28604,7 +30360,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -28659,7 +30415,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -28669,7 +30425,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -28710,7 +30466,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -28721,7 +30477,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -28776,7 +30532,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -28822,7 +30578,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-BonusConfigs\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-BonusConfigs\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -28833,7 +30589,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -28888,7 +30644,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -28934,7 +30690,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Fast\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Fast\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -28945,7 +30701,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -29000,7 +30756,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -29046,7 +30802,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_FORCE_RASTER_PIPELINE_BLITTER\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_FORCE_RASTER_PIPELINE_BLITTER\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -29057,7 +30813,236 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "perf_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "perf"
+      ]
+    },
+    "Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SkottieTracing": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:181"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        },
+        {
+          "name": "skia/bots/lottie-samples",
+          "path": "lottie-samples",
+          "version": "version:1"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "perf_skottietrace",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SkottieTracing\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86_64-Release"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "machine_type:n1-standard-16",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "perf_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "perf"
+      ]
+    },
+    "Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Wuffs": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:181"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "perf",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Wuffs\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86_64-Release-Wuffs"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "machine_type:n1-standard-16",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -29112,7 +31097,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -29158,7 +31143,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX512-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX512-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -29168,7 +31153,7 @@
       ],
       "dimensions": [
         "cpu:x86-64-Skylake_GCE",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -29223,7 +31208,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -29269,7 +31254,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX512-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX512-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -29279,7 +31264,7 @@
       ],
       "dimensions": [
         "cpu:x86-64-Skylake_GCE",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -29334,7 +31319,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -29344,7 +31329,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -29385,7 +31370,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -29450,7 +31435,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -29465,7 +31450,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -29506,7 +31491,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -29571,7 +31556,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -29581,7 +31566,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -29622,7 +31607,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -29687,7 +31672,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -29702,7 +31687,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -29743,7 +31728,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -29808,7 +31793,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -29818,7 +31803,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -29859,7 +31844,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -29924,7 +31909,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -29939,7 +31924,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -29980,7 +31965,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -30045,7 +32030,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -30055,7 +32040,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -30096,7 +32081,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -30161,7 +32146,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -30171,7 +32156,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -30212,7 +32197,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -30277,7 +32262,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -30287,7 +32272,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -30328,7 +32313,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -30393,7 +32378,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -30403,7 +32388,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -30444,7 +32429,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -30540,7 +32525,7 @@
         "-recipe",
         "perf_pathkit",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -30551,7 +32536,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -30637,7 +32622,7 @@
         "-recipe",
         "perf_canvaskit",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -30648,7 +32633,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -30734,7 +32719,7 @@
         "-recipe",
         "perf_pathkit",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -30745,7 +32730,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -30831,7 +32816,7 @@
         "-recipe",
         "perf_canvaskit",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -30843,7 +32828,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia",
         "docker_installed:true"
       ],
@@ -30899,7 +32884,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -30945,7 +32930,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-GCC-GCE-CPU-AVX2-x86-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-GCC-GCE-CPU-AVX2-x86-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -30956,7 +32941,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -31011,7 +32996,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -31057,7 +33042,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-GCC-GCE-CPU-AVX2-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-GCC-GCE-CPU-AVX2-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -31068,7 +33053,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -31092,7 +33077,7 @@
         "perf"
       ]
     },
-    "Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All": {
+    "Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All": {
       "caches": [
         {
           "name": "vpython",
@@ -31123,7 +33108,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -31169,7 +33154,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -31203,7 +33188,7 @@
         "perf"
       ]
     },
-    "Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All": {
+    "Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -31234,7 +33219,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -31280,7 +33265,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -31314,7 +33299,7 @@
         "perf"
       ]
     },
-    "Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer": {
+    "Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer": {
       "caches": [
         {
           "name": "vpython",
@@ -31345,7 +33330,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -31391,7 +33376,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -31425,7 +33410,7 @@
         "perf"
       ]
     },
-    "Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All": {
+    "Perf-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All": {
       "caches": [
         {
           "name": "vpython",
@@ -31456,7 +33441,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -31502,340 +33487,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Mac-Clang-x86_64-Debug"
-      ],
-      "dimensions": [
-        "gpu:8086:1626",
-        "os:Mac-10.13.6",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "perf_skia_bundled.isolate",
-      "max_attempts": 2,
-      "outputs": [
-        "perf"
-      ]
-    },
-    "Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "perf",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Mac-Clang-x86_64-Release"
-      ],
-      "dimensions": [
-        "gpu:8086:1626",
-        "os:Mac-10.13.6",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "perf_skia_bundled.isolate",
-      "max_attempts": 2,
-      "outputs": [
-        "perf"
-      ]
-    },
-    "Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-CommandBuffer": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "perf",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-CommandBuffer\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Mac-Clang-x86_64-Release-CommandBuffer"
-      ],
-      "dimensions": [
-        "gpu:8086:1626",
-        "os:Mac-10.13.6",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "perf_skia_bundled.isolate",
-      "max_attempts": 2,
-      "outputs": [
-        "perf"
-      ]
-    },
-    "Perf-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "perf",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -31869,7 +33521,7 @@
         "perf"
       ]
     },
-    "Perf-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN": {
+    "Perf-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN": {
       "caches": [
         {
           "name": "vpython",
@@ -31900,7 +33552,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -31946,7 +33598,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -31980,7 +33632,7 @@
         "perf"
       ]
     },
-    "Perf-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All": {
+    "Perf-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -32011,7 +33663,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -32057,7 +33709,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -32091,7 +33743,7 @@
         "perf"
       ]
     },
-    "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All": {
+    "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All": {
       "caches": [
         {
           "name": "vpython",
@@ -32122,7 +33774,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -32168,7 +33820,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -32202,7 +33854,7 @@
         "perf"
       ]
     },
-    "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan": {
+    "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan": {
       "caches": [
         {
           "name": "vpython",
@@ -32233,7 +33885,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -32279,7 +33931,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -32313,7 +33965,7 @@
         "perf"
       ]
     },
-    "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All": {
+    "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -32344,7 +33996,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -32390,7 +34042,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -32424,7 +34076,7 @@
         "perf"
       ]
     },
-    "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-CommandBuffer": {
+    "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-CommandBuffer": {
       "caches": [
         {
           "name": "vpython",
@@ -32455,7 +34107,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -32501,7 +34153,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-CommandBuffer\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-CommandBuffer\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -32535,7 +34187,7 @@
         "perf"
       ]
     },
-    "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan": {
+    "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan": {
       "caches": [
         {
           "name": "vpython",
@@ -32566,7 +34218,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -32612,7 +34264,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -32646,7 +34298,7 @@
         "perf"
       ]
     },
-    "Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All": {
+    "Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All": {
       "caches": [
         {
           "name": "vpython",
@@ -32677,7 +34329,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -32723,7 +34375,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -32758,7 +34410,7 @@
         "perf"
       ]
     },
-    "Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All": {
+    "Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -32789,7 +34441,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -32835,7 +34487,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -32870,7 +34522,7 @@
         "perf"
       ]
     },
-    "Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer": {
+    "Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer": {
       "caches": [
         {
           "name": "vpython",
@@ -32901,7 +34553,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -32947,7 +34599,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -32982,7 +34634,7 @@
         "perf"
       ]
     },
-    "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All": {
+    "Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All": {
       "caches": [
         {
           "name": "vpython",
@@ -33013,7 +34665,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -33059,19 +34711,18 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Debug"
+        "Build-Mac-Clang-x86_64-Debug"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia",
-        "rack:1"
+        "gpu:8086:1626",
+        "os:Mac-10.14.3",
+        "pool:Skia"
       ],
       "env_prefixes": {
         "PATH": [
@@ -33094,7 +34745,7 @@
         "perf"
       ]
     },
-    "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN": {
+    "Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -33125,17 +34776,12 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
           "path": "svg",
           "version": "version:9"
-        },
-        {
-          "name": "skia/bots/clang_linux",
-          "path": "clang_linux",
-          "version": "version:13"
         }
       ],
       "command": [
@@ -33176,136 +34822,18 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Debug-ASAN"
+        "Build-Mac-Clang-x86_64-Release"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia",
-        "rack:1"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "perf_skia_bundled.isolate",
-      "max_attempts": 1,
-      "outputs": [
-        "perf"
-      ]
-    },
-    "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/linux_vulkan_sdk",
-          "path": "linux_vulkan_sdk",
-          "version": "version:2"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "perf",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Debug-Vulkan"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia",
-        "rack:1"
+        "gpu:8086:1626",
+        "os:Mac-10.14.3",
+        "pool:Skia"
       ],
       "env_prefixes": {
         "PATH": [
@@ -33328,7 +34856,7 @@
         "perf"
       ]
     },
-    "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
+    "Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-CommandBuffer": {
       "caches": [
         {
           "name": "vpython",
@@ -33359,7 +34887,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -33405,19 +34933,18 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-CommandBuffer\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Release"
+        "Build-Mac-Clang-x86_64-Release-CommandBuffer"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia",
-        "rack:1"
+        "gpu:8086:1626",
+        "os:Mac-10.14.3",
+        "pool:Skia"
       ],
       "env_prefixes": {
         "PATH": [
@@ -33440,476 +34967,6 @@
         "perf"
       ]
     },
-    "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ASAN": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/clang_linux",
-          "path": "clang_linux",
-          "version": "version:13"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "perf",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Release-ASAN"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia",
-        "rack:1"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "perf_skia_bundled.isolate",
-      "max_attempts": 1,
-      "outputs": [
-        "perf"
-      ]
-    },
-    "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/linux_vulkan_sdk",
-          "path": "linux_vulkan_sdk",
-          "version": "version:2"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "perf",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Release-Vulkan"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia",
-        "rack:1"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "perf_skia_bundled.isolate",
-      "max_attempts": 2,
-      "outputs": [
-        "perf"
-      ]
-    },
-    "Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/valgrind",
-          "path": "valgrind",
-          "version": "version:7"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "perf",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-GCC-x86_64-Release-SK_CPU_LIMIT_SSE41"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia",
-        "valgrind:1",
-        "rack:1"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 32400000000000,
-      "expiration_ns": 172800000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 32400000000000,
-      "isolate": "perf_skia_bundled.isolate",
-      "max_attempts": 1,
-      "outputs": [
-        "perf"
-      ]
-    },
-    "Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/valgrind",
-          "path": "valgrind",
-          "version": "version:7"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "perf",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-GCC-x86_64-Release-SK_CPU_LIMIT_SSE41"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia",
-        "valgrind:1",
-        "rack:1"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 32400000000000,
-      "expiration_ns": 172800000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 32400000000000,
-      "isolate": "perf_skia_bundled.isolate",
-      "max_attempts": 1,
-      "outputs": [
-        "perf"
-      ]
-    },
     "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All": {
       "caches": [
         {
@@ -33941,7 +34998,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -33987,7 +35044,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -33996,7 +35053,7 @@
         "Build-Debian9-Clang-x86_64-Debug"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia",
         "rack:2"
@@ -34053,7 +35110,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -34063,7 +35120,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -34104,7 +35161,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -34113,7 +35170,7 @@
         "Build-Debian9-Clang-x86_64-Debug-ASAN"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia",
         "rack:2"
@@ -34170,7 +35227,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -34221,7 +35278,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -34230,7 +35287,7 @@
         "Build-Debian9-Clang-x86_64-Debug-Vulkan"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia",
         "rack:2"
@@ -34287,7 +35344,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -34333,7 +35390,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -34342,7 +35399,7 @@
         "Build-Debian9-Clang-x86_64-Release"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia",
         "rack:2"
@@ -34399,7 +35456,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -34409,7 +35466,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -34450,7 +35507,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -34459,7 +35516,7 @@
         "Build-Debian9-Clang-x86_64-Release-ASAN"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia",
         "rack:2"
@@ -34516,7 +35573,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -34567,7 +35624,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -34576,7 +35633,7 @@
         "Build-Debian9-Clang-x86_64-Release-Vulkan"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia",
         "rack:2"
@@ -34602,6 +35659,124 @@
         "perf"
       ]
     },
+    "Perf-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:181"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        },
+        {
+          "name": "skia/bots/valgrind",
+          "path": "valgrind",
+          "version": "version:7"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "perf",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-GCC-x86_64-Release-SK_CPU_LIMIT_SSE41"
+      ],
+      "dimensions": [
+        "gpu:10de:1cb3-415.27",
+        "os:Ubuntu-18.04",
+        "pool:Skia",
+        "valgrind:1",
+        "rack:2"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 32400000000000,
+      "expiration_ns": 172800000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 32400000000000,
+      "isolate": "perf_skia_bundled.isolate",
+      "max_attempts": 1,
+      "outputs": [
+        "perf"
+      ]
+    },
     "Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All": {
       "caches": [
         {
@@ -34638,7 +35813,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -34684,7 +35859,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -34694,7 +35869,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -34754,7 +35929,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -34800,7 +35975,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -34810,7 +35985,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -34870,7 +36045,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -34916,7 +36091,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -34926,7 +36101,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -34986,7 +36161,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -35032,7 +36207,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -35042,7 +36217,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -35102,7 +36277,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -35148,7 +36323,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -35158,7 +36333,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -35218,7 +36393,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -35264,7 +36439,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -35274,7 +36449,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -35334,7 +36509,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -35380,7 +36555,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-GT610-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-GT610-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -35450,7 +36625,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -35496,7 +36671,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-GT610-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-GT610-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -35566,7 +36741,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -35612,7 +36787,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -35682,7 +36857,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -35728,7 +36903,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -35798,7 +36973,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -35844,7 +37019,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -35915,7 +37090,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -35961,7 +37136,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -36032,7 +37207,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -36078,7 +37253,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -36149,7 +37324,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -36195,7 +37370,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -36266,7 +37441,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -36312,7 +37487,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -36383,7 +37558,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -36429,7 +37604,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -36531,7 +37706,7 @@
         "-recipe",
         "skpbench",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -36634,7 +37809,7 @@
         "-recipe",
         "skpbench",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench_DDLRecord_9x9\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench_DDLRecord_9x9\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -36737,7 +37912,7 @@
         "-recipe",
         "skpbench",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench_DDLTotal_9x9\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench_DDLTotal_9x9\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -36809,7 +37984,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -36855,7 +38030,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -36865,7 +38040,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b-20.19.15.4963",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -36925,7 +38100,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -36971,7 +38146,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -36981,7 +38156,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b-20.19.15.4963",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -37041,7 +38216,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -37087,7 +38262,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -37097,7 +38272,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b-20.19.15.4963",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -37157,7 +38332,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -37203,7 +38378,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -37213,7 +38388,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b-20.19.15.4963",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -37273,7 +38448,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -37319,7 +38494,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -37329,7 +38504,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -37389,7 +38564,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -37435,7 +38610,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -37445,7 +38620,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -37505,7 +38680,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -37551,7 +38726,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -37561,7 +38736,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -37621,7 +38796,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -37667,7 +38842,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -37677,7 +38852,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -37737,7 +38912,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -37783,7 +38958,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -37793,7 +38968,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -37853,7 +39028,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -37899,7 +39074,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -37909,7 +39084,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -37969,7 +39144,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -38015,7 +39190,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -38025,7 +39200,7 @@
       ],
       "dimensions": [
         "gpu:8086:3ea5-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -38085,7 +39260,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -38131,7 +39306,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -38141,7 +39316,7 @@
       ],
       "dimensions": [
         "gpu:8086:3ea5-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -38201,7 +39376,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -38247,7 +39422,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -38257,7 +39432,7 @@
       ],
       "dimensions": [
         "gpu:8086:3ea5-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -38317,7 +39492,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -38363,7 +39538,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -38373,7 +39548,7 @@
       ],
       "dimensions": [
         "gpu:8086:3ea5-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -38433,7 +39608,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -38479,7 +39654,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -38489,7 +39664,7 @@
       ],
       "dimensions": [
         "gpu:8086:3ea5-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -38549,7 +39724,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -38595,7 +39770,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -38605,7 +39780,7 @@
       ],
       "dimensions": [
         "gpu:8086:3ea5-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -38665,7 +39840,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -38711,7 +39886,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -38721,7 +39896,7 @@
       ],
       "dimensions": [
         "gpu:8086:0a16-20.19.15.4963",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -38781,7 +39956,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -38827,7 +40002,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -38837,7 +40012,7 @@
       ],
       "dimensions": [
         "gpu:8086:0a16-20.19.15.4963",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -38897,7 +40072,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -38943,7 +40118,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -38953,7 +40128,7 @@
       ],
       "dimensions": [
         "gpu:8086:0a16-20.19.15.4963",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -39013,7 +40188,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -39059,7 +40234,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -39069,7 +40244,7 @@
       ],
       "dimensions": [
         "gpu:8086:0a16-20.19.15.4963",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -39129,7 +40304,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -39175,7 +40350,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -39185,7 +40360,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -39245,7 +40420,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -39291,7 +40466,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -39301,7 +40476,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -39361,7 +40536,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -39407,7 +40582,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -39417,7 +40592,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -39477,7 +40652,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -39523,7 +40698,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -39533,7 +40708,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -39593,7 +40768,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -39639,7 +40814,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -39649,7 +40824,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -39709,7 +40884,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -39755,7 +40930,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -39765,7 +40940,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -39825,7 +41000,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -39871,7 +41046,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -39881,7 +41056,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -39941,7 +41116,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -39987,7 +41162,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -39997,7 +41172,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -40057,7 +41232,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -40103,7 +41278,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -40113,7 +41288,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -40173,7 +41348,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -40219,7 +41394,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -40229,7 +41404,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -40289,7 +41464,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -40335,7 +41510,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -40345,7 +41520,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -40405,7 +41580,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -40451,7 +41626,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -40461,7 +41636,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -40521,7 +41696,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -40567,7 +41742,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -40577,7 +41752,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -40637,7 +41812,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -40683,7 +41858,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -40693,7 +41868,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -40753,7 +41928,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -40799,7 +41974,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -40809,7 +41984,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -40869,7 +42044,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -40915,7 +42090,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -40925,7 +42100,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -40985,7 +42160,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -41031,7 +42206,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -41041,7 +42216,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -41101,7 +42276,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -41147,7 +42322,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -41157,7 +42332,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -41217,7 +42392,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -41263,7 +42438,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -41334,7 +42509,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -41380,7 +42555,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -41451,7 +42626,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -41497,7 +42672,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -41568,7 +42743,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -41614,7 +42789,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -41685,7 +42860,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -41731,7 +42906,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win2016-Clang-GCE-CPU-AVX2-x86-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win2016-Clang-GCE-CPU-AVX2-x86-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -41803,7 +42978,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -41849,7 +43024,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -41921,7 +43096,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -41967,7 +43142,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-UBSAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-UBSAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -42039,7 +43214,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -42085,7 +43260,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -42157,7 +43332,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -42203,7 +43378,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-UBSAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-UBSAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -42275,7 +43450,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -42321,7 +43496,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win2016-MSVC-GCE-CPU-AVX2-x86-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win2016-MSVC-GCE-CPU-AVX2-x86-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -42393,7 +43568,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -42439,7 +43614,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -42511,7 +43686,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -42557,7 +43732,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win7-Clang-Golo-CPU-AVX-x86-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win7-Clang-Golo-CPU-AVX-x86-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -42627,7 +43802,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -42673,7 +43848,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win7-Clang-Golo-CPU-AVX-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win7-Clang-Golo-CPU-AVX-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -42743,7 +43918,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -42789,7 +43964,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win8-Clang-Golo-CPU-AVX-x86-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win8-Clang-Golo-CPU-AVX-x86-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -42859,7 +44034,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -42905,7 +44080,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win8-Clang-Golo-CPU-AVX-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win8-Clang-Golo-CPU-AVX-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -43001,7 +44176,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -43100,7 +44275,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -43199,7 +44374,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-iOS-Clang-iPhone6-GPU-PowerVRGX6450-arm64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-iOS-Clang-iPhone6-GPU-PowerVRGX6450-arm64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -43298,7 +44473,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-iOS-Clang-iPhone6-GPU-PowerVRGX6450-arm64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-iOS-Clang-iPhone6-GPU-PowerVRGX6450-arm64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -43397,7 +44572,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -43496,7 +44671,7 @@
         "-recipe",
         "perf",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -43595,7 +44770,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -43695,7 +44870,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -43795,7 +44970,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -43895,7 +45070,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android_NoGPUThreads\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android_NoGPUThreads\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -43995,7 +45170,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -44095,7 +45270,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android_NativeFonts\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android_NativeFonts\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -44195,7 +45370,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android_NoGPUThreads\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android_NoGPUThreads\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -44295,7 +45470,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -44395,7 +45570,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -44495,7 +45670,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -44595,7 +45770,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -44695,7 +45870,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -44795,7 +45970,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Debug-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Debug-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -44895,7 +46070,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -44995,7 +46170,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -45095,7 +46270,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -45195,7 +46370,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Debug-All-Android_NativeFonts\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Debug-All-Android_NativeFonts\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -45295,7 +46470,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -45395,7 +46570,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -45495,7 +46670,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Debug-All-Android_NoGPUThreads\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Debug-All-Android_NoGPUThreads\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -45595,7 +46770,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -45695,7 +46870,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android_NoGPUThreads\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android_NoGPUThreads\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -45795,7 +46970,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -45895,7 +47070,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_CCPR\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_CCPR\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -45995,7 +47170,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -46095,7 +47270,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_Vulkan_NoGPUThreads\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_Vulkan_NoGPUThreads\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -46195,7 +47370,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -46295,7 +47470,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -46395,7 +47570,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan_NoGPUThreads\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan_NoGPUThreads\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -46495,7 +47670,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5-CPU-Snapdragon800-arm-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5-CPU-Snapdragon800-arm-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -46595,7 +47770,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5-CPU-Snapdragon800-arm-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5-CPU-Snapdragon800-arm-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -46695,7 +47870,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -46795,7 +47970,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -46895,7 +48070,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-CPU-Snapdragon808-arm64-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-CPU-Snapdragon808-arm64-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -46995,7 +48170,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-CPU-Snapdragon808-arm64-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-CPU-Snapdragon808-arm64-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -47095,7 +48270,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm-Debug-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm-Debug-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -47195,7 +48370,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm-Release-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm-Release-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -47295,7 +48470,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -47395,7 +48570,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -47495,7 +48670,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -47595,7 +48770,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -47633,7 +48808,7 @@
         "test"
       ]
     },
-    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-All-Android": {
+    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_0_4-Android": {
       "caches": [
         {
           "name": "vpython",
@@ -47695,7 +48870,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_0_4-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -47733,7 +48908,7 @@
         "test"
       ]
     },
-    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android": {
+    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_1_4-Android": {
       "caches": [
         {
           "name": "vpython",
@@ -47795,7 +48970,607 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_1_4-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm-Debug-Android",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:LMY47V_1836172",
+        "device_type:grouper",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_2_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_2_4-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm-Debug-Android",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:LMY47V_1836172",
+        "device_type:grouper",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_3_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_3_4-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm-Debug-Android",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:LMY47V_1836172",
+        "device_type:grouper",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_0_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_0_4-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm-Release-Android",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:LMY47V_1836172",
+        "device_type:grouper",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_1_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_1_4-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm-Release-Android",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:LMY47V_1836172",
+        "device_type:grouper",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_2_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_2_4-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm-Release-Android",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:LMY47V_1836172",
+        "device_type:grouper",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_3_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_3_4-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -47895,7 +49670,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -47995,7 +49770,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -48033,7 +49808,7 @@
         "test"
       ]
     },
-    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-All-Android": {
+    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_0_4-Android": {
       "caches": [
         {
           "name": "vpython",
@@ -48095,7 +49870,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_0_4-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -48133,7 +49908,7 @@
         "test"
       ]
     },
-    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-All-Android": {
+    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_1_4-Android": {
       "caches": [
         {
           "name": "vpython",
@@ -48195,7 +49970,607 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_1_4-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86-Debug-Android",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:OPR2.170623.027",
+        "device_type:fugu",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 21600000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 21600000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_2_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_2_4-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86-Debug-Android",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:OPR2.170623.027",
+        "device_type:fugu",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 21600000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 21600000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_3_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_3_4-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86-Debug-Android",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:OPR2.170623.027",
+        "device_type:fugu",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 21600000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 21600000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_0_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_0_4-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86-Release-Android",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:OPR2.170623.027",
+        "device_type:fugu",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_1_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_1_4-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86-Release-Android",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:OPR2.170623.027",
+        "device_type:fugu",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_2_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_2_4-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86-Release-Android",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:OPR2.170623.027",
+        "device_type:fugu",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_3_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_3_4-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -48295,7 +50670,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -48395,7 +50770,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -48495,7 +50870,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-CPU-Snapdragon821-arm-Release-All-Android_ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-CPU-Snapdragon821-arm-Release-All-Android_ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -48596,7 +50971,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-CPU-Snapdragon821-arm64-Release-All-Android_ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-CPU-Snapdragon821-arm64-Release-All-Android_ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -48697,7 +51072,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm-Release-All-Android_ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm-Release-All-Android_ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -48798,7 +51173,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm-Release-All-Android_ASAN_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm-Release-All-Android_ASAN_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -48899,7 +51274,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -48999,7 +51374,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_CCPR\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_CCPR\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -49099,7 +51474,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -49199,7 +51574,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -49299,7 +51674,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -49400,7 +51775,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_ASAN_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_ASAN_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -49501,7 +51876,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -49601,7 +51976,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -49701,7 +52076,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android_DDL1_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android_DDL1_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -49801,7 +52176,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android_DDL3_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android_DDL3_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -49901,7 +52276,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -50001,7 +52376,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -50101,7 +52476,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -50139,6 +52514,606 @@
         "test"
       ]
     },
+    "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm64-Debug-Android",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:PQ1A.190105.004",
+        "device_type:blueline",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_DDL1_Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_DDL1_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm64-Debug-Android_Vulkan",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:PQ1A.190105.004",
+        "device_type:blueline",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_DDL3_Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_DDL3_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm64-Debug-Android_Vulkan",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:PQ1A.190105.004",
+        "device_type:blueline",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm64-Debug-Android_Vulkan",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:PQ1A.190105.004",
+        "device_type:blueline",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm64-Release-Android",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:PQ1A.190105.004",
+        "device_type:blueline",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-arm64-Release-Android_Vulkan",
+        "Housekeeper-PerCommit-IsolateSKP",
+        "Housekeeper-PerCommit-IsolateSVG",
+        "Housekeeper-PerCommit-IsolateSkImage"
+      ],
+      "dimensions": [
+        "device_os:PQ1A.190105.004",
+        "device_type:blueline",
+        "os:Android",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
     "Test-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Debug-All": {
       "caches": [
         {
@@ -50201,7 +53176,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -50301,7 +53276,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -50401,7 +53376,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-AcerChromebook13_CB5_311-GPU-TegraK1-arm-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-AcerChromebook13_CB5_311-GPU-TegraK1-arm-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -50501,7 +53476,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-AcerChromebook13_CB5_311-GPU-TegraK1-arm-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-AcerChromebook13_CB5_311-GPU-TegraK1-arm-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -50601,7 +53576,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -50701,7 +53676,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -50801,7 +53776,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-Pixelbook-GPU-IntelHDGraphics615-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-Pixelbook-GPU-IntelHDGraphics615-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -50901,7 +53876,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-Pixelbook-GPU-IntelHDGraphics615-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-Pixelbook-GPU-IntelHDGraphics615-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -51001,7 +53976,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-SamsungChromebook2012-GPU-MaliT604-arm-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-SamsungChromebook2012-GPU-MaliT604-arm-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -51101,7 +54076,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-SamsungChromebook2012-GPU-MaliT604-arm-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-SamsungChromebook2012-GPU-MaliT604-arm-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -51201,7 +54176,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -51301,7 +54276,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -51401,7 +54376,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -51499,7 +54474,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -51597,7 +54572,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -51695,7 +54670,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -51762,7 +54737,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -51808,7 +54783,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -51819,7 +54794,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -51874,7 +54849,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -51920,7 +54895,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -51931,7 +54906,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -51986,7 +54961,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -51996,7 +54971,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -52037,7 +55012,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -52048,7 +55023,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -52103,7 +55078,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -52149,7 +55124,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -52160,7 +55135,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -52215,7 +55190,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -52225,7 +55200,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -52266,7 +55241,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -52277,7 +55252,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -52332,7 +55307,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -52378,7 +55353,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-NativeFonts\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-NativeFonts\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -52389,7 +55364,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -52444,7 +55419,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -52490,7 +55465,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SK_USE_DISCARDABLE_SCALEDIMAGECACHE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SK_USE_DISCARDABLE_SCALEDIMAGECACHE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -52501,7 +55476,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -52556,7 +55531,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -52602,7 +55577,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SafeStack\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SafeStack\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -52613,7 +55588,119 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-Wuffs": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:181"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-Wuffs\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86_64-Debug-Wuffs"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "machine_type:n1-standard-16",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -52668,7 +55755,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -52714,7 +55801,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -52725,7 +55812,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -52780,7 +55867,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -52790,7 +55877,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -52831,7 +55918,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -52842,7 +55929,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -52897,7 +55984,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -52943,7 +56030,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-BonusConfigs\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-BonusConfigs\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -52954,7 +56041,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -53009,7 +56096,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -53055,7 +56142,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Fast\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Fast\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -53066,7 +56153,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -53121,7 +56208,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -53167,7 +56254,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SKNX_NO_SIMD\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SKNX_NO_SIMD\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -53178,7 +56265,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -53233,7 +56320,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -53279,7 +56366,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_CPU_LIMIT_SSE2\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_CPU_LIMIT_SSE2\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -53290,7 +56377,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -53345,7 +56432,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -53391,7 +56478,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_CPU_LIMIT_SSE41\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_CPU_LIMIT_SSE41\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -53402,7 +56489,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -53457,7 +56544,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -53503,7 +56590,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_FORCE_RASTER_PIPELINE_BLITTER\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_FORCE_RASTER_PIPELINE_BLITTER\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -53514,7 +56601,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -53569,7 +56656,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -53579,7 +56666,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -53620,7 +56707,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -53631,7 +56718,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -53686,7 +56773,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -53732,7 +56819,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX512-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX512-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -53742,7 +56829,7 @@
       ],
       "dimensions": [
         "cpu:x86-64-Skylake_GCE",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -53797,7 +56884,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -53843,7 +56930,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX512-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX512-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -53853,7 +56940,7 @@
       ],
       "dimensions": [
         "cpu:x86-64-Skylake_GCE",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -53908,7 +56995,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -53954,7 +57041,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Debug-All-SwiftShader\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Debug-All-SwiftShader\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -53965,7 +57052,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -54020,7 +57107,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -54066,7 +57153,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Release-All-SwiftShader\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Release-All-SwiftShader\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -54077,7 +57164,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -54132,7 +57219,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -54142,7 +57229,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -54183,7 +57270,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -54248,7 +57335,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -54263,7 +57350,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -54304,7 +57391,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -54369,7 +57456,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -54379,7 +57466,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -54420,7 +57507,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -54485,7 +57572,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -54500,7 +57587,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -54541,7 +57628,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -54606,7 +57693,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -54652,7 +57739,7 @@
         "-recipe",
         "test_skqp_emulator",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-CPU-Emulator-x86-devrel-All-Android_SKQP\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-CPU-Emulator-x86-devrel-All-Android_SKQP\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -54719,7 +57806,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -54729,7 +57816,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -54770,7 +57857,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -54835,7 +57922,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -54845,7 +57932,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -54886,7 +57973,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL1\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL1\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -54951,7 +58038,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -54966,7 +58053,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -55007,7 +58094,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL1_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL1_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -55072,7 +58159,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -55082,7 +58169,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -55123,7 +58210,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL3\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL3\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -55188,7 +58275,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -55198,12 +58285,12 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         },
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -55244,7 +58331,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL3_ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL3_ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -55309,7 +58396,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -55319,7 +58406,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         },
         {
           "name": "skia/bots/linux_vulkan_sdk",
@@ -55329,7 +58416,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -55370,7 +58457,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL3_ASAN_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL3_ASAN_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -55435,7 +58522,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -55450,7 +58537,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -55491,7 +58578,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL3_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL3_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -55556,7 +58643,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -55566,7 +58653,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         },
         {
           "name": "skia/bots/opencl_ocl_icd_linux",
@@ -55617,7 +58704,7 @@
         "-recipe",
         "compute_test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-OpenCL\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-OpenCL\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -55682,7 +58769,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -55697,7 +58784,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -55738,7 +58825,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -55803,7 +58890,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -55813,7 +58900,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -55854,7 +58941,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -55919,7 +59006,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -55929,12 +59016,12 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         },
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -55975,7 +59062,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-DDL3_ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-DDL3_ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -56040,7 +59127,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -56050,7 +59137,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         },
         {
           "name": "skia/bots/linux_vulkan_sdk",
@@ -56060,7 +59147,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -56101,7 +59188,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-DDL3_ASAN_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-DDL3_ASAN_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -56166,7 +59253,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -56176,12 +59263,12 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         },
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -56222,7 +59309,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-DDL3_TSAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-DDL3_TSAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -56287,7 +59374,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -56297,7 +59384,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         },
         {
           "name": "skia/bots/linux_vulkan_sdk",
@@ -56307,7 +59394,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -56348,7 +59435,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-DDL3_TSAN_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-DDL3_TSAN_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -56413,7 +59500,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -56423,12 +59510,12 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         },
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -56469,7 +59556,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-TSAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-TSAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -56534,7 +59621,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -56549,7 +59636,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -56590,7 +59677,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -56655,7 +59742,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -56665,7 +59752,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -56706,7 +59793,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -56771,7 +59858,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -56781,7 +59868,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -56822,7 +59909,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -56887,7 +59974,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -56897,7 +59984,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -56938,7 +60025,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -57003,7 +60090,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -57013,7 +60100,7 @@
         {
           "name": "skia/bots/mesa_intel_driver_linux",
           "path": "mesa_intel_driver_linux",
-          "version": "version:0"
+          "version": "version:5"
         }
       ],
       "command": [
@@ -57054,7 +60141,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -57088,103 +60175,6 @@
         "test"
       ]
     },
-    "Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test_pathkit",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-EMCC-asmjs-Debug-PathKit"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "machine_type:n1-standard-16",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "outputs": [
-        "test"
-      ]
-    },
     "Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit": {
       "caches": [
         {
@@ -57247,7 +60237,7 @@
         "-recipe",
         "test_pathkit",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -57258,201 +60248,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-CanvasKit": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test_canvaskit",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-CanvasKit\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-EMCC-wasm-Debug-CanvasKit_CPU"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "machine_type:n1-standard-16",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test_pathkit",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-EMCC-wasm-Debug-PathKit"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -57538,7 +60334,7 @@
         "-recipe",
         "test_canvaskit",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -57549,7 +60345,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -57635,7 +60431,7 @@
         "-recipe",
         "test_pathkit",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -57646,7 +60442,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -57670,105 +60466,6 @@
         "test"
       ]
     },
-    "Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Debug-All-CanvasKit": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test_canvaskit",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Debug-All-CanvasKit\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-EMCC-wasm-Debug-CanvasKit"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "gpu:none",
-        "machine_type:n1-standard-16",
-        "os:Debian-9.4",
-        "pool:Skia",
-        "docker_installed:true"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "outputs": [
-        "test"
-      ]
-    },
     "Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit": {
       "caches": [
         {
@@ -57831,7 +60528,7 @@
         "-recipe",
         "test_canvaskit",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -57843,7 +60540,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia",
         "docker_installed:true"
       ],
@@ -57899,7 +60596,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -57945,7 +60642,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-GCC-GCE-CPU-AVX2-x86-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-GCC-GCE-CPU-AVX2-x86-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -57956,7 +60653,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -58011,7 +60708,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -58057,7 +60754,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-GCC-GCE-CPU-AVX2-x86-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-GCC-GCE-CPU-AVX2-x86-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -58068,7 +60765,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -58123,7 +60820,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -58169,7 +60866,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-GCC-GCE-CPU-AVX2-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-GCC-GCE-CPU-AVX2-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -58180,7 +60877,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -58235,7 +60932,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -58281,7 +60978,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-GCC-GCE-CPU-AVX2-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-GCC-GCE-CPU-AVX2-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -58292,7 +60989,7 @@
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "machine_type:n1-standard-16",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -58316,7 +61013,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All": {
+    "Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All": {
       "caches": [
         {
           "name": "vpython",
@@ -58347,7 +61044,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -58393,7 +61090,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -58427,7 +61124,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All-CommandBuffer": {
+    "Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All-CommandBuffer": {
       "caches": [
         {
           "name": "vpython",
@@ -58458,7 +61155,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -58504,7 +61201,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All-CommandBuffer\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All-CommandBuffer\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -58538,7 +61235,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All": {
+    "Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -58569,7 +61266,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -58615,7 +61312,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -58649,7 +61346,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts": {
+    "Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts": {
       "caches": [
         {
           "name": "vpython",
@@ -58680,7 +61377,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -58726,7 +61423,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -58760,7 +61457,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All": {
       "caches": [
         {
           "name": "vpython",
@@ -58791,7 +61488,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -58837,340 +61534,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Mac-Clang-x86_64-Debug"
-      ],
-      "dimensions": [
-        "gpu:8086:1626",
-        "os:Mac-10.13.6",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All-CommandBuffer": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All-CommandBuffer\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Mac-Clang-x86_64-Debug-CommandBuffer"
-      ],
-      "dimensions": [
-        "gpu:8086:1626",
-        "os:Mac-10.13.6",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Mac-Clang-x86_64-Release"
-      ],
-      "dimensions": [
-        "gpu:8086:1626",
-        "os:Mac-10.13.6",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -59204,7 +61568,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN": {
       "caches": [
         {
           "name": "vpython",
@@ -59235,7 +61599,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -59281,7 +61645,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -59315,7 +61679,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-NativeFonts": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-NativeFonts": {
       "caches": [
         {
           "name": "vpython",
@@ -59346,7 +61710,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -59392,7 +61756,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-NativeFonts\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-NativeFonts\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -59426,7 +61790,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -59457,7 +61821,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -59503,7 +61867,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -59537,7 +61901,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All-TSAN": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All-TSAN": {
       "caches": [
         {
           "name": "vpython",
@@ -59568,7 +61932,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -59614,7 +61978,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All-TSAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All-TSAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -59648,7 +62012,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All": {
       "caches": [
         {
           "name": "vpython",
@@ -59679,7 +62043,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -59725,7 +62089,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -59759,7 +62123,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-CommandBuffer": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-CommandBuffer": {
       "caches": [
         {
           "name": "vpython",
@@ -59790,7 +62154,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -59836,7 +62200,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-CommandBuffer\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-CommandBuffer\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -59870,7 +62234,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal": {
       "caches": [
         {
           "name": "vpython",
@@ -59901,7 +62265,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -59947,7 +62311,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -59981,7 +62345,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan": {
       "caches": [
         {
           "name": "vpython",
@@ -60012,7 +62376,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -60058,7 +62422,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -60092,7 +62456,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-OpenCL": {
       "caches": [
         {
           "name": "vpython",
@@ -60123,7 +62487,118 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "compute_test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-OpenCL\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Mac-Clang-x86_64-Debug-OpenCL"
+      ],
+      "dimensions": [
+        "gpu:1002:6821-4.0.20-3.2.8",
+        "os:Mac-10.13.6",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -60169,7 +62644,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -60203,7 +62678,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-Metal": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-Metal": {
       "caches": [
         {
           "name": "vpython",
@@ -60234,7 +62709,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -60280,7 +62755,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-Metal\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-Metal\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -60314,7 +62789,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan": {
       "caches": [
         {
           "name": "vpython",
@@ -60345,7 +62820,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -60391,7 +62866,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -60425,7 +62900,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-TSAN": {
+    "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-TSAN": {
       "caches": [
         {
           "name": "vpython",
@@ -60456,7 +62931,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -60502,7 +62977,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-TSAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-TSAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -60536,7 +63011,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All": {
+    "Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All": {
       "caches": [
         {
           "name": "vpython",
@@ -60567,7 +63042,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -60613,7 +63088,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -60648,7 +63123,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer": {
+    "Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer": {
       "caches": [
         {
           "name": "vpython",
@@ -60679,7 +63154,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -60725,7 +63200,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -60760,7 +63235,7 @@
         "test"
       ]
     },
-    "Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All": {
+    "Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -60791,7 +63266,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -60837,7 +63312,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -60872,7 +63347,7 @@
         "test"
       ]
     },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All": {
+    "Test-Mac10.14-Clang-MacBookAir7.2-CPU-AVX2-x86_64-Debug-All-NativeFonts": {
       "caches": [
         {
           "name": "vpython",
@@ -60903,7 +63378,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -60949,17 +63424,17 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.14-Clang-MacBookAir7.2-CPU-AVX2-x86_64-Debug-All-NativeFonts\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Debug"
+        "Build-Mac-Clang-x86_64-Debug"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
+        "cpu:x86-64-i5-5350U",
+        "os:Mac-10.14.3",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -60983,7 +63458,7 @@
         "test"
       ]
     },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN": {
+    "Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All": {
       "caches": [
         {
           "name": "vpython",
@@ -61014,123 +63489,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/clang_linux",
-          "path": "clang_linux",
-          "version": "version:13"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Debug-ASAN"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 1,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -61176,17 +63535,17 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Debug"
+        "Build-Mac-Clang-x86_64-Debug"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
+        "gpu:8086:1626",
+        "os:Mac-10.14.3",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -61210,7 +63569,7 @@
         "test"
       ]
     },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan": {
+    "Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All-CommandBuffer": {
       "caches": [
         {
           "name": "vpython",
@@ -61241,17 +63600,12 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
           "path": "svg",
           "version": "version:9"
-        },
-        {
-          "name": "skia/bots/linux_vulkan_sdk",
-          "path": "linux_vulkan_sdk",
-          "version": "version:2"
         }
       ],
       "command": [
@@ -61292,17 +63646,17 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All-CommandBuffer\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Debug-Vulkan"
+        "Build-Mac-Clang-x86_64-Debug-CommandBuffer"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
+        "gpu:8086:1626",
+        "os:Mac-10.14.3",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -61326,7 +63680,7 @@
         "test"
       ]
     },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3": {
+    "Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -61357,7 +63711,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -61403,17 +63757,17 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Debug"
+        "Build-Mac-Clang-x86_64-Release"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
+        "gpu:8086:1626",
+        "os:Mac-10.14.3",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -61437,1754 +63791,6 @@
         "test"
       ]
     },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/clang_linux",
-          "path": "clang_linux",
-          "version": "version:13"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Debug-ASAN"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 1,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN_Vulkan": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/clang_linux",
-          "path": "clang_linux",
-          "version": "version:13"
-        },
-        {
-          "name": "skia/bots/linux_vulkan_sdk",
-          "path": "linux_vulkan_sdk",
-          "version": "version:2"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Debug-ASAN_Vulkan"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 1,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/linux_vulkan_sdk",
-          "path": "linux_vulkan_sdk",
-          "version": "version:2"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Debug-Vulkan"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/linux_vulkan_sdk",
-          "path": "linux_vulkan_sdk",
-          "version": "version:2"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Debug-Vulkan"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Release"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ASAN": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/clang_linux",
-          "path": "clang_linux",
-          "version": "version:13"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Release-ASAN"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 1,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/clang_linux",
-          "path": "clang_linux",
-          "version": "version:13"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Release-ASAN"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 1,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN_Vulkan": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/clang_linux",
-          "path": "clang_linux",
-          "version": "version:13"
-        },
-        {
-          "name": "skia/bots/linux_vulkan_sdk",
-          "path": "linux_vulkan_sdk",
-          "version": "version:2"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Release-ASAN_Vulkan"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 1,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_TSAN": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/clang_linux",
-          "path": "clang_linux",
-          "version": "version:13"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_TSAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Release-TSAN"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 1,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_TSAN_Vulkan": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/clang_linux",
-          "path": "clang_linux",
-          "version": "version:13"
-        },
-        {
-          "name": "skia/bots/linux_vulkan_sdk",
-          "path": "linux_vulkan_sdk",
-          "version": "version:2"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_TSAN_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Release-TSAN_Vulkan"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 1,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-PreAbandonGpuContext": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-PreAbandonGpuContext\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Release"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/linux_vulkan_sdk",
-          "path": "linux_vulkan_sdk",
-          "version": "version:2"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Release-Vulkan"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/valgrind",
-          "path": "valgrind",
-          "version": "version:7"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-GCC-x86_64-Release-SK_CPU_LIMIT_SSE41"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia",
-        "valgrind:1"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 32400000000000,
-      "expiration_ns": 172800000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 32400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 1,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_PreAbandonGpuContext_SK_CPU_LIMIT_SSE41": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/valgrind",
-          "path": "valgrind",
-          "version": "version:7"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_PreAbandonGpuContext_SK_CPU_LIMIT_SSE41\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-GCC-x86_64-Release-SK_CPU_LIMIT_SSE41"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia",
-        "valgrind:1"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 32400000000000,
-      "expiration_ns": 172800000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 32400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 1,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/valgrind",
-          "path": "valgrind",
-          "version": "version:7"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-GCC-x86_64-Release-SK_CPU_LIMIT_SSE41"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-384.59",
-        "os:Ubuntu-17.04",
-        "pool:Skia",
-        "valgrind:1"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 32400000000000,
-      "expiration_ns": 172800000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 32400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 1,
-      "outputs": [
-        "test"
-      ]
-    },
     "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All": {
       "caches": [
         {
@@ -63216,7 +63822,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -63262,7 +63868,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -63271,7 +63877,7 @@
         "Build-Debian9-Clang-x86_64-Debug"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia"
       ],
@@ -63327,7 +63933,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -63337,7 +63943,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -63378,7 +63984,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -63387,7 +63993,7 @@
         "Build-Debian9-Clang-x86_64-Debug-ASAN"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia"
       ],
@@ -63443,7 +64049,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -63489,7 +64095,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -63498,7 +64104,7 @@
         "Build-Debian9-Clang-x86_64-Debug"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia"
       ],
@@ -63554,7 +64160,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -63605,7 +64211,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -63614,7 +64220,7 @@
         "Build-Debian9-Clang-x86_64-Debug-Vulkan"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia"
       ],
@@ -63670,7 +64276,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -63716,7 +64322,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -63725,7 +64331,7 @@
         "Build-Debian9-Clang-x86_64-Debug"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia"
       ],
@@ -63781,7 +64387,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -63791,7 +64397,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -63832,7 +64438,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -63841,128 +64447,7 @@
         "Build-Debian9-Clang-x86_64-Debug-ASAN"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
-        "os:Ubuntu-18.04",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 1,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN_Vulkan": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/clang_linux",
-          "path": "clang_linux",
-          "version": "version:13"
-        },
-        {
-          "name": "skia/bots/linux_vulkan_sdk",
-          "path": "linux_vulkan_sdk",
-          "version": "version:2"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Debug-ASAN_Vulkan"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia"
       ],
@@ -64018,7 +64503,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -64069,7 +64554,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -64078,7 +64563,7 @@
         "Build-Debian9-Clang-x86_64-Debug-Vulkan"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia"
       ],
@@ -64134,7 +64619,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -64185,7 +64670,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -64194,7 +64679,7 @@
         "Build-Debian9-Clang-x86_64-Debug-Vulkan"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia"
       ],
@@ -64250,7 +64735,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -64296,7 +64781,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -64305,7 +64790,7 @@
         "Build-Debian9-Clang-x86_64-Release"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia"
       ],
@@ -64361,7 +64846,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -64371,7 +64856,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -64412,7 +64897,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -64421,7 +64906,7 @@
         "Build-Debian9-Clang-x86_64-Release-ASAN"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia"
       ],
@@ -64477,7 +64962,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -64487,7 +64972,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -64528,7 +65013,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -64537,128 +65022,7 @@
         "Build-Debian9-Clang-x86_64-Release-ASAN"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
-        "os:Ubuntu-18.04",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 1,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN_Vulkan": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        },
-        {
-          "name": "skia/bots/clang_linux",
-          "path": "clang_linux",
-          "version": "version:13"
-        },
-        {
-          "name": "skia/bots/linux_vulkan_sdk",
-          "path": "linux_vulkan_sdk",
-          "version": "version:2"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-Clang-x86_64-Release-ASAN_Vulkan"
-      ],
-      "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia"
       ],
@@ -64714,7 +65078,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -64724,7 +65088,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         }
       ],
       "command": [
@@ -64765,7 +65129,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_TSAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_TSAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -64774,7 +65138,7 @@
         "Build-Debian9-Clang-x86_64-Release-TSAN"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia"
       ],
@@ -64830,7 +65194,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -64840,7 +65204,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:13"
+          "version": "version:14"
         },
         {
           "name": "skia/bots/linux_vulkan_sdk",
@@ -64886,7 +65250,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_TSAN_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_TSAN_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -64895,7 +65259,7 @@
         "Build-Debian9-Clang-x86_64-Release-TSAN_Vulkan"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia"
       ],
@@ -64951,7 +65315,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -64997,7 +65361,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-PreAbandonGpuContext\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-PreAbandonGpuContext\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -65006,7 +65370,7 @@
         "Build-Debian9-Clang-x86_64-Release"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia"
       ],
@@ -65062,7 +65426,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -65113,7 +65477,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -65122,7 +65486,7 @@
         "Build-Debian9-Clang-x86_64-Release-Vulkan"
       ],
       "dimensions": [
-        "gpu:10de:1cb3-390.87",
+        "gpu:10de:1cb3-415.27",
         "os:Ubuntu-18.04",
         "pool:Skia"
       ],
@@ -65147,6 +65511,357 @@
         "test"
       ]
     },
+    "Test-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:181"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        },
+        {
+          "name": "skia/bots/valgrind",
+          "path": "valgrind",
+          "version": "version:7"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-GCC-x86_64-Release-SK_CPU_LIMIT_SSE41"
+      ],
+      "dimensions": [
+        "gpu:10de:1cb3-415.27",
+        "os:Ubuntu-18.04",
+        "pool:Skia",
+        "valgrind:1"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 32400000000000,
+      "expiration_ns": 172800000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 32400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 1,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_PreAbandonGpuContext_SK_CPU_LIMIT_SSE41": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:181"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        },
+        {
+          "name": "skia/bots/valgrind",
+          "path": "valgrind",
+          "version": "version:7"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_PreAbandonGpuContext_SK_CPU_LIMIT_SSE41\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-GCC-x86_64-Release-SK_CPU_LIMIT_SSE41"
+      ],
+      "dimensions": [
+        "gpu:10de:1cb3-415.27",
+        "os:Ubuntu-18.04",
+        "pool:Skia",
+        "valgrind:1"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 32400000000000,
+      "expiration_ns": 172800000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 32400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 1,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:181"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        },
+        {
+          "name": "skia/bots/valgrind",
+          "path": "valgrind",
+          "version": "version:7"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-GCC-x86_64-Release-SK_CPU_LIMIT_SSE41"
+      ],
+      "dimensions": [
+        "gpu:10de:1cb3-415.27",
+        "os:Ubuntu-18.04",
+        "pool:Skia",
+        "valgrind:1"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 32400000000000,
+      "expiration_ns": 172800000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 32400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 1,
+      "outputs": [
+        "test"
+      ]
+    },
     "Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All": {
       "caches": [
         {
@@ -65183,7 +65898,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -65229,7 +65944,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -65239,7 +65954,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -65299,7 +66014,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -65345,7 +66060,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -65355,7 +66070,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -65415,7 +66130,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -65461,7 +66176,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -65471,7 +66186,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -65531,7 +66246,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -65577,7 +66292,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -65587,7 +66302,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -65647,7 +66362,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -65693,7 +66408,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -65703,7 +66418,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -65763,7 +66478,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -65809,7 +66524,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -65819,7 +66534,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -65879,7 +66594,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -65925,7 +66640,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-GT610-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-GT610-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -65995,7 +66710,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -66041,7 +66756,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-GT610-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-GT610-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -66111,7 +66826,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -66157,7 +66872,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -66227,7 +66942,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -66273,7 +66988,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -66343,7 +67058,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -66389,7 +67104,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -66459,7 +67174,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -66505,7 +67220,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -66575,7 +67290,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -66621,7 +67336,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-BonusConfigs\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-BonusConfigs\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -66691,7 +67406,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -66737,7 +67452,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -66807,7 +67522,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -66858,7 +67573,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan_ProcDump\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan_ProcDump\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -66928,7 +67643,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -66974,7 +67689,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -67044,7 +67759,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -67090,7 +67805,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -67160,7 +67875,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -67206,7 +67921,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-BonusConfigs\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-BonusConfigs\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -67276,7 +67991,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -67322,7 +68037,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ReleaseAndAbandonGpuContext\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ReleaseAndAbandonGpuContext\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -67392,7 +68107,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -67438,7 +68153,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -67508,7 +68223,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -67559,7 +68274,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_ProcDump\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_ProcDump\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -67629,7 +68344,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -67675,7 +68390,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-CPU-AVX2-x86_64-Debug-All-NativeFonts\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-CPU-AVX2-x86_64-Debug-All-NativeFonts\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -67685,7 +68400,7 @@
       ],
       "dimensions": [
         "cpu:x86-64-i7-5557U",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -67745,7 +68460,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -67791,7 +68506,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -67801,7 +68516,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b-20.19.15.4963",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -67861,7 +68576,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -67907,7 +68622,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -67917,7 +68632,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b-20.19.15.4963",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -67977,7 +68692,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -68023,7 +68738,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -68033,7 +68748,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b-20.19.15.4963",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -68093,7 +68808,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -68139,7 +68854,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -68149,7 +68864,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b-20.19.15.4963",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -68209,7 +68924,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -68255,7 +68970,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -68265,7 +68980,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -68325,7 +69040,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -68371,7 +69086,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -68381,7 +69096,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -68441,7 +69156,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -68487,7 +69202,7 @@
         "-recipe",
         "compute_test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-OpenCL\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-OpenCL\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -68497,7 +69212,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -68557,7 +69272,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -68603,7 +69318,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -68613,7 +69328,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -68673,7 +69388,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -68719,7 +69434,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -68729,7 +69444,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -68789,7 +69504,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -68835,7 +69550,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -68845,7 +69560,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -68905,7 +69620,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -68951,7 +69666,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-NativeFonts\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-NativeFonts\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -68961,7 +69676,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -69021,7 +69736,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -69067,7 +69782,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -69077,7 +69792,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -69137,7 +69852,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -69183,7 +69898,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -69193,7 +69908,7 @@
       ],
       "dimensions": [
         "gpu:8086:3ea5-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -69253,7 +69968,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -69299,7 +70014,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -69309,7 +70024,7 @@
       ],
       "dimensions": [
         "gpu:8086:3ea5-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -69369,7 +70084,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -69415,7 +70130,7 @@
         "-recipe",
         "compute_test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-OpenCL\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-OpenCL\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -69425,7 +70140,7 @@
       ],
       "dimensions": [
         "gpu:8086:3ea5-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -69485,7 +70200,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -69531,7 +70246,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -69541,7 +70256,7 @@
       ],
       "dimensions": [
         "gpu:8086:3ea5-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -69601,7 +70316,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -69647,7 +70362,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -69657,7 +70372,7 @@
       ],
       "dimensions": [
         "gpu:8086:3ea5-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -69717,7 +70432,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -69763,7 +70478,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -69773,7 +70488,7 @@
       ],
       "dimensions": [
         "gpu:8086:3ea5-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -69833,7 +70548,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -69879,7 +70594,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -69889,7 +70604,7 @@
       ],
       "dimensions": [
         "gpu:8086:3ea5-25.20.100.6444",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -69949,7 +70664,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -69995,7 +70710,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -70005,7 +70720,7 @@
       ],
       "dimensions": [
         "gpu:8086:0a16-20.19.15.4963",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -70065,7 +70780,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -70111,7 +70826,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -70121,7 +70836,7 @@
       ],
       "dimensions": [
         "gpu:8086:0a16-20.19.15.4963",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -70181,7 +70896,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -70227,7 +70942,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -70237,7 +70952,7 @@
       ],
       "dimensions": [
         "gpu:8086:0a16-20.19.15.4963",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -70297,7 +71012,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -70343,7 +71058,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -70353,7 +71068,7 @@
       ],
       "dimensions": [
         "gpu:8086:0a16-20.19.15.4963",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -70413,7 +71128,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -70459,7 +71174,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -70469,7 +71184,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -70529,7 +71244,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -70575,7 +71290,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -70585,7 +71300,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -70645,7 +71360,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -70691,7 +71406,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -70701,7 +71416,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -70761,7 +71476,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -70807,7 +71522,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -70817,7 +71532,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -70877,7 +71592,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -70923,7 +71638,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -70933,7 +71648,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -70993,7 +71708,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -71039,7 +71754,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -71049,7 +71764,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -71109,7 +71824,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -71155,7 +71870,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -71165,7 +71880,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -71225,7 +71940,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -71271,7 +71986,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -71281,7 +71996,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -71341,7 +72056,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -71387,7 +72102,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -71397,7 +72112,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -71457,7 +72172,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -71503,7 +72218,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -71513,7 +72228,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -71573,7 +72288,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -71619,7 +72334,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -71629,7 +72344,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -71689,7 +72404,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -71735,7 +72450,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -71745,7 +72460,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -71805,7 +72520,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -71851,7 +72566,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -71861,7 +72576,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -71921,7 +72636,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -71967,7 +72682,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -71977,7 +72692,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -72037,7 +72752,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -72083,7 +72798,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -72093,7 +72808,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -72153,7 +72868,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -72199,7 +72914,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -72209,7 +72924,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -72269,7 +72984,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -72315,7 +73030,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -72325,7 +73040,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -72385,7 +73100,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -72431,7 +73146,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -72441,7 +73156,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17763.195",
+        "os:Windows-10-17763.379",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -72501,7 +73216,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -72547,7 +73262,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -72617,7 +73332,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -72663,7 +73378,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All-MSRTC\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All-MSRTC\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -72733,7 +73448,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -72779,7 +73494,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All-MSRTC_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All-MSRTC_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -72849,7 +73564,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -72895,7 +73610,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -72965,7 +73680,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -73011,7 +73726,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -73081,7 +73796,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -73127,7 +73842,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -73197,7 +73912,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -73243,7 +73958,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -73315,7 +74030,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -73361,7 +74076,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -73433,7 +74148,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -73479,7 +74194,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -73551,7 +74266,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -73597,125 +74312,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FAAA\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Win-Clang-x86_64-Debug"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "image:windows-server-2016-dc-v20190108",
-        "machine_type:n1-standard-16",
-        "os:Windows-2016Server-14393",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FDAA": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "infra/python/cpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "version:2.7.14.chromium14"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FDAA\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FAAA\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -73787,7 +74384,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -73833,7 +74430,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FSAA\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FSAA\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -73905,7 +74502,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -73951,7 +74548,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-UBSAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-UBSAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -74023,7 +74620,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -74069,7 +74666,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -74141,7 +74738,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -74187,125 +74784,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FAAA\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Win-Clang-x86_64-Release"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "image:windows-server-2016-dc-v20190108",
-        "machine_type:n1-standard-16",
-        "os:Windows-2016Server-14393",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 14400000000000,
-      "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
-      "outputs": [
-        "test"
-      ]
-    },
-    "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FDAA": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "infra/python/cpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "version:2.7.14.chromium14"
-        },
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:39"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:169"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:9"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "test",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FDAA\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FAAA\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -74377,7 +74856,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -74423,7 +74902,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FSAA\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FSAA\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -74495,7 +74974,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -74541,7 +75020,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-UBSAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-UBSAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -74613,7 +75092,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -74659,7 +75138,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-MSVC-GCE-CPU-AVX2-x86-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-MSVC-GCE-CPU-AVX2-x86-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -74731,7 +75210,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -74777,7 +75256,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-MSVC-GCE-CPU-AVX2-x86-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-MSVC-GCE-CPU-AVX2-x86-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -74849,7 +75328,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -74895,7 +75374,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -74967,7 +75446,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -75013,7 +75492,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Debug-All-MSRTC\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Debug-All-MSRTC\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -75085,7 +75564,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -75131,7 +75610,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -75203,7 +75682,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -75249,7 +75728,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -75319,7 +75798,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -75365,7 +75844,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -75435,7 +75914,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -75481,7 +75960,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -75551,7 +76030,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -75597,7 +76076,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86_64-Debug-All-NativeFonts\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86_64-Debug-All-NativeFonts\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -75667,7 +76146,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -75713,7 +76192,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86_64-Debug-All-NativeFonts_GDI\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86_64-Debug-All-NativeFonts_GDI\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -75783,7 +76262,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -75829,7 +76308,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -75899,7 +76378,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -75945,7 +76424,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win8-Clang-Golo-CPU-AVX-x86-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win8-Clang-Golo-CPU-AVX-x86-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -76015,7 +76494,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -76061,7 +76540,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win8-Clang-Golo-CPU-AVX-x86-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win8-Clang-Golo-CPU-AVX-x86-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -76131,7 +76610,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -76177,7 +76656,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win8-Clang-Golo-CPU-AVX-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win8-Clang-Golo-CPU-AVX-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -76247,7 +76726,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:169"
+          "version": "version:181"
         },
         {
           "name": "skia/bots/svg",
@@ -76293,7 +76772,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win8-Clang-Golo-CPU-AVX-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win8-Clang-Golo-CPU-AVX-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -76389,7 +76868,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -76488,7 +76967,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -76587,7 +77066,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone6-GPU-PowerVRGX6450-arm64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone6-GPU-PowerVRGX6450-arm64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -76686,7 +77165,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone6-GPU-PowerVRGX6450-arm64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone6-GPU-PowerVRGX6450-arm64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -76785,7 +77264,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -76884,7 +77363,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Debug-All-Metal\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Debug-All-Metal\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -76983,7 +77462,7 @@
         "-recipe",
         "test",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -77082,7 +77561,7 @@
         "-recipe",
         "upload_skiaserve",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Upload-Build-Debian9-Clang-arm-Release-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Upload-Build-Debian9-Clang-arm-Release-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -77094,7 +77573,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -77177,7 +77656,7 @@
         "-recipe",
         "upload_skiaserve",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Upload-Build-Debian9-Clang-arm64-Release-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Upload-Build-Debian9-Clang-arm64-Release-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -77189,7 +77668,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -77272,7 +77751,7 @@
         "-recipe",
         "upload_skiaserve",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Upload-Build-Debian9-Clang-x64-Release-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Upload-Build-Debian9-Clang-x64-Release-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -77284,7 +77763,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -77367,7 +77846,7 @@
         "-recipe",
         "upload_skiaserve",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Upload-Build-Debian9-Clang-x86-Release-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Upload-Build-Debian9-Clang-x86-Release-Android\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -77379,7 +77858,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -77467,7 +77946,7 @@
         "-recipe",
         "upload_buildstats_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-Clang-arm-Release-Flutter_Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-Clang-arm-Release-Flutter_Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -77479,7 +77958,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -77567,7 +78046,7 @@
         "-recipe",
         "upload_buildstats_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-asmjs-Release-PathKit\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-asmjs-Release-PathKit\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -77579,7 +78058,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -77667,7 +78146,7 @@
         "-recipe",
         "upload_buildstats_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Release-CanvasKit\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Release-CanvasKit\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -77679,7 +78158,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -77767,7 +78246,7 @@
         "-recipe",
         "upload_buildstats_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Release-CanvasKit_CPU\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Release-CanvasKit_CPU\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -77779,7 +78258,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -77867,7 +78346,7 @@
         "-recipe",
         "upload_buildstats_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Release-PathKit\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Release-PathKit\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -77879,7 +78358,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -77967,7 +78446,7 @@
         "-recipe",
         "upload_calmbench_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Calmbench-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"gs_bucket\":\"skia-calmbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Calmbench-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"gs_bucket\":\"skia-calmbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -77979,7 +78458,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -78000,7 +78479,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-calmbench-upload@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Calmbench-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
+    "Upload-Calmbench-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -78067,19 +78546,19 @@
         "-recipe",
         "upload_calmbench_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Calmbench-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gs_bucket\":\"skia-calmbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Calmbench-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gs_bucket\":\"skia-calmbench\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Calmbench-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All"
+        "Calmbench-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -78167,7 +78646,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -78179,7 +78658,107 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android_SkottieTracing": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_nano_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android_SkottieTracing\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android_SkottieTracing"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -78267,7 +78846,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -78279,7 +78858,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -78367,7 +78946,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android_NoGPUThreads\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android_NoGPUThreads\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -78379,7 +78958,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -78467,7 +79046,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -78479,7 +79058,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -78567,7 +79146,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -78579,7 +79158,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -78667,7 +79246,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -78679,7 +79258,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -78767,7 +79346,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -78779,7 +79358,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -78867,7 +79446,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -78879,7 +79458,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -78967,7 +79546,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -78979,7 +79558,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -79067,7 +79646,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android_NoGPUThreads\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android_NoGPUThreads\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -79079,7 +79658,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -79167,7 +79746,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -79179,7 +79758,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -79267,7 +79846,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -79279,7 +79858,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -79367,7 +79946,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan_NoGPUThreads\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan_NoGPUThreads\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -79379,7 +79958,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -79467,7 +80046,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5-CPU-Snapdragon800-arm-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5-CPU-Snapdragon800-arm-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -79479,7 +80058,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -79567,7 +80146,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -79579,7 +80158,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -79667,7 +80246,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-CPU-Snapdragon808-arm64-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-CPU-Snapdragon808-arm64-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -79679,7 +80258,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -79767,7 +80346,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -79779,7 +80358,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -79867,7 +80446,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -79879,7 +80458,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -79967,7 +80546,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -79979,7 +80558,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -80067,7 +80646,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -80079,7 +80658,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -80167,7 +80746,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -80179,7 +80758,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -80267,7 +80846,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -80279,7 +80858,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -80367,7 +80946,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -80379,7 +80958,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -80467,7 +81046,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_CCPR_Skpbench\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_CCPR_Skpbench\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -80479,7 +81058,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -80567,7 +81146,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Skpbench\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Skpbench\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -80579,7 +81158,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -80667,7 +81246,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -80679,7 +81258,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -80767,7 +81346,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Vulkan_Skpbench\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Vulkan_Skpbench\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -80779,7 +81358,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -80867,7 +81446,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -80879,7 +81458,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -80967,7 +81546,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_CCPR_Skpbench\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_CCPR_Skpbench\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -80979,7 +81558,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -81067,7 +81646,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Skpbench\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Skpbench\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -81079,7 +81658,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -81167,7 +81746,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -81179,7 +81758,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -81267,7 +81846,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan_Skpbench\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan_Skpbench\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -81279,7 +81858,507 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_nano_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_CCPR_Skpbench": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_nano_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_CCPR_Skpbench\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_CCPR_Skpbench"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Skpbench": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_nano_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Skpbench\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Skpbench"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_nano_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan_Skpbench": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_nano_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan_Skpbench\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Perf-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan_Skpbench"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -81367,7 +82446,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -81379,7 +82458,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -81467,7 +82546,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-AcerChromebook13_CB5_311-GPU-TegraK1-arm-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-AcerChromebook13_CB5_311-GPU-TegraK1-arm-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -81479,7 +82558,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -81567,7 +82646,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -81579,7 +82658,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -81667,7 +82746,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-Pixelbook-GPU-IntelHDGraphics615-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-Pixelbook-GPU-IntelHDGraphics615-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -81679,7 +82758,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -81767,7 +82846,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-SamsungChromebook2012-GPU-MaliT604-arm-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-SamsungChromebook2012-GPU-MaliT604-arm-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -81779,7 +82858,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -81867,7 +82946,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -81879,7 +82958,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -81967,7 +83046,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -81979,7 +83058,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -82067,7 +83146,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -82079,7 +83158,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -82167,7 +83246,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -82179,7 +83258,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -82267,7 +83346,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-BonusConfigs\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-BonusConfigs\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -82279,7 +83358,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -82367,7 +83446,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Fast\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Fast\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -82379,7 +83458,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -82467,7 +83546,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_FORCE_RASTER_PIPELINE_BLITTER\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_FORCE_RASTER_PIPELINE_BLITTER\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -82479,7 +83558,207 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SkottieTracing": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_nano_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SkottieTracing\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SkottieTracing"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Wuffs": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_nano_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Wuffs\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Wuffs"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -82567,7 +83846,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX512-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-GCE-CPU-AVX512-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -82579,7 +83858,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -82667,7 +83946,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -82679,7 +83958,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -82767,7 +84046,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -82779,7 +84058,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -82867,7 +84146,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -82879,7 +84158,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -82967,7 +84246,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -82979,7 +84258,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -83067,7 +84346,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -83079,7 +84358,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -83167,7 +84446,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -83179,7 +84458,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -83267,7 +84546,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -83279,7 +84558,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -83367,7 +84646,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -83379,7 +84658,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -83467,7 +84746,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -83479,7 +84758,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -83567,7 +84846,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -83579,7 +84858,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -83600,7 +84879,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All": {
+    "Upload-Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -83667,19 +84946,19 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All"
+        "Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -83700,7 +84979,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer": {
+    "Upload-Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer": {
       "caches": [
         {
           "name": "vpython",
@@ -83767,19 +85046,19 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer"
+        "Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -83800,7 +85079,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All": {
+    "Upload-Perf-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -83867,19 +85146,19 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All"
+        "Perf-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -83900,7 +85179,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-CommandBuffer": {
+    "Upload-Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -83967,19 +85246,19 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-CommandBuffer\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-CommandBuffer"
+        "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -84000,7 +85279,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Perf-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All": {
+    "Upload-Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-CommandBuffer": {
       "caches": [
         {
           "name": "vpython",
@@ -84067,19 +85346,19 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-CommandBuffer\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Perf-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All"
+        "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-CommandBuffer"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -84100,7 +85379,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All": {
+    "Upload-Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan": {
       "caches": [
         {
           "name": "vpython",
@@ -84167,19 +85446,19 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All"
+        "Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -84200,7 +85479,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-CommandBuffer": {
+    "Upload-Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -84267,19 +85546,19 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-CommandBuffer\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-CommandBuffer"
+        "Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -84300,7 +85579,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan": {
+    "Upload-Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer": {
       "caches": [
         {
           "name": "vpython",
@@ -84367,19 +85646,19 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan"
+        "Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -84400,7 +85679,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All": {
+    "Upload-Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -84467,19 +85746,19 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All"
+        "Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -84500,7 +85779,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer": {
+    "Upload-Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-CommandBuffer": {
       "caches": [
         {
           "name": "vpython",
@@ -84567,219 +85846,19 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-CommandBuffer\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer"
+        "Perf-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-CommandBuffer"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 3600000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 3600000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
-    },
-    "Upload-Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "infra/gsutil",
-          "path": "cipd_bin_packages",
-          "version": "version:4.28"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "upload_nano_results",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "gpu:none",
-        "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 3600000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 3600000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
-    },
-    "Upload-Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "infra/gsutil",
-          "path": "cipd_bin_packages",
-          "version": "version:4.28"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "upload_nano_results",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "gpu:none",
-        "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -84867,7 +85946,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -84879,7 +85958,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -84967,7 +86046,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -84979,7 +86058,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -85072,7 +86151,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -85084,7 +86163,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -85177,7 +86256,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -85189,7 +86268,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -85282,7 +86361,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -85294,7 +86373,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -85387,7 +86466,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -85399,7 +86478,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -85492,7 +86571,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -85504,7 +86583,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -85597,7 +86676,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -85609,7 +86688,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -85702,7 +86781,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -85714,7 +86793,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -85807,7 +86886,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -85819,7 +86898,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -85912,7 +86991,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -85924,7 +87003,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -86017,7 +87096,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench_DDLRecord_9x9\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench_DDLRecord_9x9\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -86029,7 +87108,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -86122,7 +87201,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench_DDLTotal_9x9\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench_DDLTotal_9x9\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -86134,7 +87213,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -86227,7 +87306,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -86239,7 +87318,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -86332,7 +87411,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -86344,7 +87423,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -86437,7 +87516,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -86449,7 +87528,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -86542,7 +87621,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -86554,7 +87633,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -86647,7 +87726,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -86659,7 +87738,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -86752,7 +87831,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -86764,7 +87843,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -86857,7 +87936,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -86869,7 +87948,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -86962,7 +88041,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -86974,7 +88053,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -87067,7 +88146,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -87079,7 +88158,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -87172,7 +88251,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -87184,7 +88263,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -87277,7 +88356,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -87289,7 +88368,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -87382,7 +88461,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -87394,7 +88473,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -87487,7 +88566,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -87499,7 +88578,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -87592,7 +88671,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -87604,7 +88683,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -87697,7 +88776,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -87709,7 +88788,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -87802,7 +88881,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -87814,7 +88893,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -87907,7 +88986,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -87919,7 +88998,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -88012,7 +89091,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -88024,7 +89103,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -88117,7 +89196,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -88129,7 +89208,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -88222,7 +89301,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -88234,7 +89313,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -88327,7 +89406,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -88339,7 +89418,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -88432,7 +89511,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -88444,7 +89523,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -88532,7 +89611,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -88544,7 +89623,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -88632,7 +89711,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-iOS-Clang-iPhone6-GPU-PowerVRGX6450-arm64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-iOS-Clang-iPhone6-GPU-PowerVRGX6450-arm64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -88644,7 +89723,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -88732,7 +89811,7 @@
         "-recipe",
         "upload_nano_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -88744,7 +89823,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -88832,7 +89911,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -88844,7 +89923,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -88932,7 +90011,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -88944,7 +90023,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -89032,7 +90111,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -89044,7 +90123,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -89132,7 +90211,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android_NoGPUThreads\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android_NoGPUThreads\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -89144,7 +90223,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -89232,7 +90311,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -89244,7 +90323,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -89332,7 +90411,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android_NativeFonts\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android_NativeFonts\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -89344,7 +90423,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -89432,7 +90511,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android_NoGPUThreads\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android_NoGPUThreads\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -89444,7 +90523,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -89532,7 +90611,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -89544,7 +90623,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -89632,7 +90711,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -89644,7 +90723,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -89732,7 +90811,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -89744,7 +90823,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -89832,7 +90911,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -89844,7 +90923,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -89932,7 +91011,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -89944,7 +91023,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -90032,7 +91111,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Debug-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Debug-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -90044,7 +91123,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -90132,7 +91211,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -90144,7 +91223,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -90232,7 +91311,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -90244,7 +91323,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -90332,7 +91411,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -90344,7 +91423,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -90432,7 +91511,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Debug-All-Android_NativeFonts\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Debug-All-Android_NativeFonts\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -90444,7 +91523,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -90532,7 +91611,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -90544,7 +91623,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -90632,7 +91711,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -90644,7 +91723,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -90732,7 +91811,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Debug-All-Android_NoGPUThreads\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Debug-All-Android_NoGPUThreads\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -90744,7 +91823,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -90832,7 +91911,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -90844,7 +91923,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -90932,7 +92011,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android_NoGPUThreads\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android_NoGPUThreads\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -90944,7 +92023,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -91032,7 +92111,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -91044,7 +92123,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -91132,7 +92211,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_CCPR\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_CCPR\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -91144,7 +92223,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -91232,7 +92311,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -91244,7 +92323,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -91332,7 +92411,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_Vulkan_NoGPUThreads\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_Vulkan_NoGPUThreads\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -91344,7 +92423,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -91432,7 +92511,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -91444,7 +92523,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -91532,7 +92611,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -91544,7 +92623,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -91632,7 +92711,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan_NoGPUThreads\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan_NoGPUThreads\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -91644,7 +92723,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -91732,7 +92811,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5-CPU-Snapdragon800-arm-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5-CPU-Snapdragon800-arm-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -91744,7 +92823,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -91832,7 +92911,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5-CPU-Snapdragon800-arm-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5-CPU-Snapdragon800-arm-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -91844,7 +92923,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -91932,7 +93011,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -91944,7 +93023,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -92032,7 +93111,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -92044,7 +93123,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -92132,7 +93211,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-CPU-Snapdragon808-arm64-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-CPU-Snapdragon808-arm64-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -92144,7 +93223,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -92232,7 +93311,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-CPU-Snapdragon808-arm64-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-CPU-Snapdragon808-arm64-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -92244,7 +93323,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -92332,7 +93411,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm-Debug-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm-Debug-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -92344,7 +93423,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -92432,7 +93511,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -92444,7 +93523,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -92532,7 +93611,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -92544,7 +93623,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -92632,7 +93711,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -92644,7 +93723,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -92732,7 +93811,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -92744,7 +93823,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -92832,7 +93911,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -92844,7 +93923,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -92865,7 +93944,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-All-Android": {
+    "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_0_4-Android": {
       "caches": [
         {
           "name": "vpython",
@@ -92932,19 +94011,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_0_4-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-All-Android"
+        "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_0_4-Android"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -92965,7 +94044,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android": {
+    "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_1_4-Android": {
       "caches": [
         {
           "name": "vpython",
@@ -93032,19 +94111,619 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_1_4-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android"
+        "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_1_4-Android"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_2_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_2_4-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_2_4-Android"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_3_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_3_4-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-Shard_3_4-Android"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_0_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_0_4-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_0_4-Android"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_1_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_1_4-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_1_4-Android"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_2_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_2_4-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_2_4-Android"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_3_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_3_4-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-Shard_3_4-Android"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -93132,7 +94811,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -93144,7 +94823,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -93232,7 +94911,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -93244,7 +94923,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -93265,7 +94944,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-All-Android": {
+    "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_0_4-Android": {
       "caches": [
         {
           "name": "vpython",
@@ -93332,19 +95011,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_0_4-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-All-Android"
+        "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_0_4-Android"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -93365,7 +95044,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-All-Android": {
+    "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_1_4-Android": {
       "caches": [
         {
           "name": "vpython",
@@ -93432,19 +95111,619 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_1_4-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-All-Android"
+        "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_1_4-Android"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_2_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_2_4-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_2_4-Android"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_3_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_3_4-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Shard_3_4-Android"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_0_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_0_4-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_0_4-Android"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_1_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_1_4-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_1_4-Android"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_2_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_2_4-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_2_4-Android"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_3_4-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_3_4-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Shard_3_4-Android"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -93532,7 +95811,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -93544,7 +95823,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -93632,7 +95911,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -93644,7 +95923,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -93732,7 +96011,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -93744,7 +96023,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -93832,7 +96111,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_CCPR\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_CCPR\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -93844,7 +96123,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -93932,7 +96211,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -93944,7 +96223,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -94032,7 +96311,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -94044,7 +96323,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -94132,7 +96411,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -94144,7 +96423,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -94232,7 +96511,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -94244,7 +96523,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -94332,7 +96611,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android_DDL1_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android_DDL1_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -94344,7 +96623,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -94432,7 +96711,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android_DDL3_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android_DDL3_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -94444,7 +96723,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -94532,7 +96811,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -94544,7 +96823,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -94632,7 +96911,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -94644,7 +96923,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -94732,7 +97011,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -94744,7 +97023,607 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_DDL1_Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_DDL1_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_DDL1_Vulkan"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_DDL3_Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_DDL3_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_DDL3_Vulkan"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Debug-All-Android_Vulkan"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Android-Clang-Pixel3-GPU-Adreno630-arm64-Release-All-Android_Vulkan"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -94832,7 +97711,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -94844,7 +97723,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -94932,7 +97811,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -94944,7 +97823,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -95032,7 +97911,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-AcerChromebook13_CB5_311-GPU-TegraK1-arm-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-AcerChromebook13_CB5_311-GPU-TegraK1-arm-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -95044,7 +97923,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -95132,7 +98011,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-AcerChromebook13_CB5_311-GPU-TegraK1-arm-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-AcerChromebook13_CB5_311-GPU-TegraK1-arm-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -95144,7 +98023,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -95232,7 +98111,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -95244,7 +98123,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -95332,7 +98211,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -95344,7 +98223,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -95432,7 +98311,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-Pixelbook-GPU-IntelHDGraphics615-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-Pixelbook-GPU-IntelHDGraphics615-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -95444,7 +98323,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -95532,7 +98411,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-Pixelbook-GPU-IntelHDGraphics615-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-Pixelbook-GPU-IntelHDGraphics615-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -95544,7 +98423,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -95632,7 +98511,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-SamsungChromebook2012-GPU-MaliT604-arm-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-SamsungChromebook2012-GPU-MaliT604-arm-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -95644,7 +98523,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -95732,7 +98611,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-SamsungChromebook2012-GPU-MaliT604-arm-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-SamsungChromebook2012-GPU-MaliT604-arm-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -95744,7 +98623,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -95832,7 +98711,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -95844,7 +98723,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -95932,7 +98811,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -95944,7 +98823,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -96032,7 +98911,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -96044,7 +98923,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -96132,7 +99011,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -96144,7 +99023,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -96232,7 +99111,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -96244,7 +99123,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -96332,7 +99211,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -96344,7 +99223,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -96432,7 +99311,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -96444,7 +99323,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -96532,7 +99411,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -96544,7 +99423,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -96632,7 +99511,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -96644,7 +99523,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -96732,7 +99611,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-NativeFonts\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-NativeFonts\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -96744,7 +99623,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -96832,7 +99711,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SK_USE_DISCARDABLE_SCALEDIMAGECACHE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SK_USE_DISCARDABLE_SCALEDIMAGECACHE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -96844,7 +99723,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -96932,7 +99811,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SafeStack\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SafeStack\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -96944,7 +99823,107 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-Wuffs": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-Wuffs\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-Wuffs"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -97032,7 +100011,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -97044,7 +100023,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -97132,7 +100111,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-BonusConfigs\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-BonusConfigs\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -97144,7 +100123,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -97232,7 +100211,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Fast\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Fast\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -97244,7 +100223,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -97332,7 +100311,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SKNX_NO_SIMD\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SKNX_NO_SIMD\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -97344,7 +100323,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -97432,7 +100411,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_CPU_LIMIT_SSE2\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_CPU_LIMIT_SSE2\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -97444,7 +100423,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -97532,7 +100511,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_CPU_LIMIT_SSE41\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_CPU_LIMIT_SSE41\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -97544,7 +100523,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -97632,7 +100611,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_FORCE_RASTER_PIPELINE_BLITTER\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_FORCE_RASTER_PIPELINE_BLITTER\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -97644,7 +100623,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -97732,7 +100711,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX512-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX512-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -97744,7 +100723,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -97832,7 +100811,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX512-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-CPU-AVX512-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -97844,7 +100823,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -97932,7 +100911,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Debug-All-SwiftShader\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Debug-All-SwiftShader\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -97944,7 +100923,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -98032,7 +101011,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Release-All-SwiftShader\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Release-All-SwiftShader\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -98044,7 +101023,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -98132,7 +101111,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -98144,7 +101123,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -98232,7 +101211,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -98244,7 +101223,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -98332,7 +101311,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -98344,7 +101323,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -98432,7 +101411,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -98444,7 +101423,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -98532,7 +101511,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -98544,7 +101523,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -98632,7 +101611,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL1\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL1\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -98644,7 +101623,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -98732,7 +101711,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL1_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL1_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -98744,7 +101723,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -98832,7 +101811,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL3\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL3\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -98844,7 +101823,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -98932,7 +101911,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL3_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-DDL3_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -98944,7 +101923,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -99032,7 +102011,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -99044,7 +102023,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -99132,7 +102111,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -99144,7 +102123,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -99232,7 +102211,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -99244,7 +102223,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -99332,7 +102311,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -99344,7 +102323,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -99432,7 +102411,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -99444,7 +102423,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -99532,7 +102511,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -99544,7 +102523,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -99632,7 +102611,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -99644,107 +102623,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 3600000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 3600000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
-    },
-    "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "infra/gsutil",
-          "path": "cipd_bin_packages",
-          "version": "version:4.28"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "upload_dm_results",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "gpu:none",
-        "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -99832,7 +102711,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -99844,207 +102723,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 3600000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 3600000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
-    },
-    "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-CanvasKit": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "infra/gsutil",
-          "path": "cipd_bin_packages",
-          "version": "version:4.28"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "upload_dm_results",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-CanvasKit\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-CanvasKit"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "gpu:none",
-        "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 3600000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 3600000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
-    },
-    "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "infra/gsutil",
-          "path": "cipd_bin_packages",
-          "version": "version:4.28"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "upload_dm_results",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "gpu:none",
-        "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -100132,7 +102811,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -100144,7 +102823,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -100232,7 +102911,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -100244,107 +102923,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 3600000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 3600000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
-    },
-    "Upload-Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Debug-All-CanvasKit": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "infra/gsutil",
-          "path": "cipd_bin_packages",
-          "version": "version:4.28"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "upload_dm_results",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Debug-All-CanvasKit\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Debug-All-CanvasKit"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "gpu:none",
-        "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -100432,7 +103011,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -100444,7 +103023,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -100532,7 +103111,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-GCC-GCE-CPU-AVX2-x86-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-GCC-GCE-CPU-AVX2-x86-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -100544,7 +103123,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -100632,7 +103211,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-GCC-GCE-CPU-AVX2-x86-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-GCC-GCE-CPU-AVX2-x86-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -100644,7 +103223,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -100732,7 +103311,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-GCC-GCE-CPU-AVX2-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-GCC-GCE-CPU-AVX2-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -100744,7 +103323,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -100832,7 +103411,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-GCC-GCE-CPU-AVX2-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-GCC-GCE-CPU-AVX2-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -100844,7 +103423,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -100865,7 +103444,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All": {
+    "Upload-Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All": {
       "caches": [
         {
           "name": "vpython",
@@ -100932,19 +103511,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All"
+        "Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -100965,7 +103544,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All-CommandBuffer": {
+    "Upload-Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All-CommandBuffer": {
       "caches": [
         {
           "name": "vpython",
@@ -101032,19 +103611,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All-CommandBuffer\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All-CommandBuffer\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All-CommandBuffer"
+        "Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All-CommandBuffer"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -101065,7 +103644,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All": {
+    "Upload-Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -101132,19 +103711,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All"
+        "Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -101165,7 +103744,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts": {
+    "Upload-Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts": {
       "caches": [
         {
           "name": "vpython",
@@ -101232,19 +103811,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts"
+        "Test-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -101265,7 +103844,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All": {
+    "Upload-Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All": {
       "caches": [
         {
           "name": "vpython",
@@ -101332,19 +103911,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All"
+        "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -101365,7 +103944,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All-CommandBuffer": {
+    "Upload-Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-NativeFonts": {
       "caches": [
         {
           "name": "vpython",
@@ -101432,19 +104011,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All-CommandBuffer\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-NativeFonts\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All-CommandBuffer"
+        "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-NativeFonts"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -101465,7 +104044,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All": {
+    "Upload-Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -101532,19 +104111,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All"
+        "Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -101565,7 +104144,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All": {
+    "Upload-Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All": {
       "caches": [
         {
           "name": "vpython",
@@ -101632,19 +104211,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All"
+        "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -101665,7 +104244,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-NativeFonts": {
+    "Upload-Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-CommandBuffer": {
       "caches": [
         {
           "name": "vpython",
@@ -101732,19 +104311,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-NativeFonts\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-CommandBuffer\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-NativeFonts"
+        "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-CommandBuffer"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -101765,7 +104344,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All": {
+    "Upload-Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal": {
       "caches": [
         {
           "name": "vpython",
@@ -101832,19 +104411,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All"
+        "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -101865,7 +104444,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All": {
+    "Upload-Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan": {
       "caches": [
         {
           "name": "vpython",
@@ -101932,19 +104511,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All"
+        "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -101965,7 +104544,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-CommandBuffer": {
+    "Upload-Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -102032,19 +104611,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-CommandBuffer\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-CommandBuffer"
+        "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -102065,7 +104644,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal": {
+    "Upload-Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-Metal": {
       "caches": [
         {
           "name": "vpython",
@@ -102132,19 +104711,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-Metal\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal"
+        "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-Metal"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -102165,7 +104744,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan": {
+    "Upload-Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan": {
       "caches": [
         {
           "name": "vpython",
@@ -102232,19 +104811,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan"
+        "Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -102265,7 +104844,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All": {
+    "Upload-Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All": {
       "caches": [
         {
           "name": "vpython",
@@ -102332,19 +104911,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All"
+        "Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -102365,7 +104944,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-Metal": {
+    "Upload-Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer": {
       "caches": [
         {
           "name": "vpython",
@@ -102432,19 +105011,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-Metal\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-Metal"
+        "Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -102465,7 +105044,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan": {
+    "Upload-Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -102532,19 +105111,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan"
+        "Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -102565,7 +105144,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All": {
+    "Upload-Test-Mac10.14-Clang-MacBookAir7.2-CPU-AVX2-x86_64-Debug-All-NativeFonts": {
       "caches": [
         {
           "name": "vpython",
@@ -102632,19 +105211,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.14-Clang-MacBookAir7.2-CPU-AVX2-x86_64-Debug-All-NativeFonts\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All"
+        "Test-Mac10.14-Clang-MacBookAir7.2-CPU-AVX2-x86_64-Debug-All-NativeFonts"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -102665,7 +105244,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer": {
+    "Upload-Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All": {
       "caches": [
         {
           "name": "vpython",
@@ -102732,19 +105311,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer"
+        "Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -102765,7 +105344,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All": {
+    "Upload-Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All-CommandBuffer": {
       "caches": [
         {
           "name": "vpython",
@@ -102832,19 +105411,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All-CommandBuffer\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All"
+        "Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All-CommandBuffer"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -102865,7 +105444,7 @@
       "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
-    "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All": {
+    "Upload-Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All": {
       "caches": [
         {
           "name": "vpython",
@@ -102932,719 +105511,19 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All"
+        "Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 3600000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 3600000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
-    },
-    "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "infra/gsutil",
-          "path": "cipd_bin_packages",
-          "version": "version:4.28"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "upload_dm_results",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "gpu:none",
-        "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 3600000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 3600000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
-    },
-    "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "infra/gsutil",
-          "path": "cipd_bin_packages",
-          "version": "version:4.28"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "upload_dm_results",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "gpu:none",
-        "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 3600000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 3600000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
-    },
-    "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "infra/gsutil",
-          "path": "cipd_bin_packages",
-          "version": "version:4.28"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "upload_dm_results",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "gpu:none",
-        "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 3600000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 3600000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
-    },
-    "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "infra/gsutil",
-          "path": "cipd_bin_packages",
-          "version": "version:4.28"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "upload_dm_results",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "gpu:none",
-        "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 3600000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 3600000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
-    },
-    "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "infra/gsutil",
-          "path": "cipd_bin_packages",
-          "version": "version:4.28"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "upload_dm_results",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "gpu:none",
-        "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 3600000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 3600000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
-    },
-    "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "infra/gsutil",
-          "path": "cipd_bin_packages",
-          "version": "version:4.28"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "upload_dm_results",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "gpu:none",
-        "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 3600000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 3600000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
-    },
-    "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "infra/gsutil",
-          "path": "cipd_bin_packages",
-          "version": "version:4.28"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "upload_dm_results",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "gpu:none",
-        "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -103732,7 +105611,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -103744,7 +105623,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -103832,7 +105711,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -103844,7 +105723,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -103932,7 +105811,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -103944,7 +105823,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -104032,7 +105911,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -104044,7 +105923,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -104132,7 +106011,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -104144,7 +106023,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -104232,7 +106111,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -104244,7 +106123,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -104332,7 +106211,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -104344,7 +106223,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -104432,7 +106311,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -104444,7 +106323,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -104537,7 +106416,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -104549,7 +106428,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -104642,7 +106521,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -104654,7 +106533,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -104747,7 +106626,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -104759,7 +106638,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -104852,7 +106731,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -104864,7 +106743,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -104957,7 +106836,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -104969,7 +106848,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -105062,7 +106941,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -105074,7 +106953,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -105167,7 +107046,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-GT610-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-GT610-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -105179,7 +107058,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -105272,7 +107151,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-GT610-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-GT610-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -105284,7 +107163,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -105377,7 +107256,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -105389,7 +107268,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -105482,7 +107361,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -105494,7 +107373,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -105587,7 +107466,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -105599,7 +107478,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -105692,7 +107571,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -105704,7 +107583,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -105797,7 +107676,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-BonusConfigs\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-BonusConfigs\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -105809,7 +107688,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -105902,7 +107781,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -105914,7 +107793,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -106007,7 +107886,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan_ProcDump\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan_ProcDump\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -106019,7 +107898,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -106112,7 +107991,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -106124,7 +108003,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -106217,7 +108096,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -106229,7 +108108,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -106322,7 +108201,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-BonusConfigs\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-BonusConfigs\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -106334,7 +108213,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -106427,7 +108306,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -106439,7 +108318,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -106532,7 +108411,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_ProcDump\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_ProcDump\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -106544,7 +108423,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -106637,7 +108516,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-CPU-AVX2-x86_64-Debug-All-NativeFonts\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-CPU-AVX2-x86_64-Debug-All-NativeFonts\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -106649,7 +108528,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -106742,7 +108621,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -106754,7 +108633,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -106847,7 +108726,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -106859,7 +108738,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -106952,7 +108831,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -106964,7 +108843,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -107057,7 +108936,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -107069,7 +108948,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -107162,7 +109041,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -107174,7 +109053,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -107267,7 +109146,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -107279,7 +109158,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -107372,7 +109251,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -107384,7 +109263,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -107477,7 +109356,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -107489,7 +109368,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -107582,7 +109461,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -107594,7 +109473,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -107687,7 +109566,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-NativeFonts\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-NativeFonts\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -107699,7 +109578,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -107792,7 +109671,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -107804,7 +109683,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -107897,7 +109776,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -107909,7 +109788,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -108002,7 +109881,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -108014,7 +109893,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -108107,7 +109986,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -108119,7 +109998,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -108212,7 +110091,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -108224,7 +110103,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -108317,7 +110196,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -108329,7 +110208,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -108422,7 +110301,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -108434,7 +110313,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -108527,7 +110406,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -108539,7 +110418,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -108632,7 +110511,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -108644,7 +110523,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -108737,7 +110616,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -108749,7 +110628,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -108842,7 +110721,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -108854,7 +110733,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -108947,7 +110826,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -108959,7 +110838,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -109052,7 +110931,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -109064,7 +110943,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -109157,7 +111036,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -109169,7 +111048,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -109262,7 +111141,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -109274,7 +111153,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -109367,7 +111246,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -109379,7 +111258,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -109472,7 +111351,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -109484,7 +111363,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -109577,7 +111456,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -109589,7 +111468,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -109682,7 +111561,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -109694,7 +111573,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -109787,7 +111666,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -109799,7 +111678,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -109892,7 +111771,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -109904,7 +111783,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -109997,7 +111876,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -110009,7 +111888,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -110102,7 +111981,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -110114,7 +111993,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -110207,7 +112086,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -110219,7 +112098,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -110312,7 +112191,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -110324,7 +112203,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -110417,7 +112296,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -110429,7 +112308,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -110522,7 +112401,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -110534,7 +112413,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -110627,7 +112506,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -110639,7 +112518,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -110732,7 +112611,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -110744,7 +112623,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -110837,7 +112716,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -110849,7 +112728,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -110942,7 +112821,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -110954,7 +112833,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -111047,7 +112926,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -111059,7 +112938,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -111152,7 +113031,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -111164,7 +113043,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -111257,7 +113136,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -111269,7 +113148,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -111362,7 +113241,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -111374,7 +113253,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -111467,7 +113346,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -111479,7 +113358,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -111572,7 +113451,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FAAA\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FAAA\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -111584,112 +113463,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 3600000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 3600000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
-    },
-    "Upload-Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FDAA": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "infra/python/cpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "version:2.7.14.chromium14"
-        },
-        {
-          "name": "infra/gsutil",
-          "path": "cipd_bin_packages",
-          "version": "version:4.28"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "upload_dm_results",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FDAA\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FDAA"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "gpu:none",
-        "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -111782,7 +113556,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FSAA\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FSAA\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -111794,7 +113568,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -111887,7 +113661,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -111899,7 +113673,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -111992,7 +113766,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FAAA\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FAAA\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -112004,112 +113778,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "env_prefixes": {
-        "PATH": [
-          "cipd_bin_packages",
-          "cipd_bin_packages/bin"
-        ],
-        "VPYTHON_VIRTUALENV_ROOT": [
-          "cache/vpython"
-        ]
-      },
-      "execution_timeout_ns": 3600000000000,
-      "extra_tags": {
-        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      },
-      "io_timeout_ns": 3600000000000,
-      "isolate": "swarm_recipe.isolate",
-      "max_attempts": 2,
-      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
-    },
-    "Upload-Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FDAA": {
-      "caches": [
-        {
-          "name": "vpython",
-          "path": "cache/vpython"
-        }
-      ],
-      "cipd_packages": [
-        {
-          "name": "infra/tools/luci/kitchen/${platform}",
-          "path": ".",
-          "version": "git_revision:d8f38ca9494b5af249942631f9cee45927f6b4bc"
-        },
-        {
-          "name": "infra/tools/luci-auth/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
-        },
-        {
-          "name": "infra/tools/luci/vpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "git_revision:96f81e737868d43124b4661cf1c325296ca04944"
-        },
-        {
-          "name": "infra/python/cpython/${platform}",
-          "path": "cipd_bin_packages",
-          "version": "version:2.7.14.chromium14"
-        },
-        {
-          "name": "infra/gsutil",
-          "path": "cipd_bin_packages",
-          "version": "version:4.28"
-        }
-      ],
-      "command": [
-        "./kitchen${EXECUTABLE_SUFFIX}",
-        "cook",
-        "-checkout-dir",
-        "recipe_bundle",
-        "-mode",
-        "swarming",
-        "-luci-system-account",
-        "system",
-        "-cache-dir",
-        "cache",
-        "-temp-dir",
-        "tmp",
-        "-known-gerrit-host",
-        "android.googlesource.com",
-        "-known-gerrit-host",
-        "boringssl.googlesource.com",
-        "-known-gerrit-host",
-        "chromium.googlesource.com",
-        "-known-gerrit-host",
-        "dart.googlesource.com",
-        "-known-gerrit-host",
-        "fuchsia.googlesource.com",
-        "-known-gerrit-host",
-        "go.googlesource.com",
-        "-known-gerrit-host",
-        "llvm.googlesource.com",
-        "-known-gerrit-host",
-        "skia.googlesource.com",
-        "-known-gerrit-host",
-        "webrtc.googlesource.com",
-        "-output-result-json",
-        "${ISOLATED_OUTDIR}/build_result_filename",
-        "-workdir",
-        ".",
-        "-recipe",
-        "upload_dm_results",
-        "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FDAA\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
-        "-logdog-annotation-url",
-        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
-      ],
-      "dependencies": [
-        "Housekeeper-PerCommit-BundleRecipes",
-        "Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FDAA"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "gpu:none",
-        "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -112202,7 +113871,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FSAA\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FSAA\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -112214,7 +113883,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -112307,7 +113976,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-MSVC-GCE-CPU-AVX2-x86-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-MSVC-GCE-CPU-AVX2-x86-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -112319,7 +113988,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -112412,7 +114081,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-MSVC-GCE-CPU-AVX2-x86-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-MSVC-GCE-CPU-AVX2-x86-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -112424,7 +114093,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -112517,7 +114186,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -112529,7 +114198,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -112622,7 +114291,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -112634,7 +114303,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -112727,7 +114396,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -112739,7 +114408,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -112832,7 +114501,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -112844,7 +114513,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -112937,7 +114606,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -112949,7 +114618,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -113042,7 +114711,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86_64-Debug-All-NativeFonts\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86_64-Debug-All-NativeFonts\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -113054,7 +114723,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -113147,7 +114816,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86_64-Debug-All-NativeFonts_GDI\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86_64-Debug-All-NativeFonts_GDI\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -113159,7 +114828,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -113252,7 +114921,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win7-Clang-Golo-CPU-AVX-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -113264,7 +114933,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -113357,7 +115026,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win8-Clang-Golo-CPU-AVX-x86-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win8-Clang-Golo-CPU-AVX-x86-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -113369,7 +115038,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -113462,7 +115131,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win8-Clang-Golo-CPU-AVX-x86-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win8-Clang-Golo-CPU-AVX-x86-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -113474,7 +115143,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -113567,7 +115236,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win8-Clang-Golo-CPU-AVX-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win8-Clang-Golo-CPU-AVX-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -113579,7 +115248,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -113672,7 +115341,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win8-Clang-Golo-CPU-AVX-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win8-Clang-Golo-CPU-AVX-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -113684,7 +115353,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -113772,7 +115441,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -113784,7 +115453,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -113872,7 +115541,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -113884,7 +115553,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -113972,7 +115641,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone6-GPU-PowerVRGX6450-arm64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone6-GPU-PowerVRGX6450-arm64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -113984,7 +115653,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -114072,7 +115741,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone6-GPU-PowerVRGX6450-arm64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone6-GPU-PowerVRGX6450-arm64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -114084,7 +115753,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -114172,7 +115841,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -114184,7 +115853,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -114272,7 +115941,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Debug-All-Metal\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Debug-All-Metal\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -114284,7 +115953,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -114372,7 +116041,7 @@
         "-recipe",
         "upload_dm_results",
         "-properties",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\",\"task_id\":\"<(TASK_ID)\"}",
         "-logdog-annotation-url",
         "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
       ],
@@ -114384,7 +116053,7 @@
         "cpu:x86-64-Haswell_GCE",
         "gpu:none",
         "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
+        "os:Debian-9.8",
         "pool:Skia"
       ],
       "env_prefixes": {
diff --git a/infra/bots/test_skia_bundled.isolate b/infra/bots/test_skia_bundled.isolate
index 8504083..6001f3e 100644
--- a/infra/bots/test_skia_bundled.isolate
+++ b/infra/bots/test_skia_bundled.isolate
@@ -1,6 +1,5 @@
 {
   'includes': [
-    'android_bin.isolate',
     'assets.isolate',
     'ios_bin.isolate',
     'resources.isolate',
diff --git a/infra/bots/upload_md.py b/infra/bots/upload_md.py
deleted file mode 100644
index 3d5c377..0000000
--- a/infra/bots/upload_md.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# 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.
-
-"""Update and upload markdown files using the output of fiddlecli."""
-
-
-import argparse
-import os
-import subprocess
-import sys
-
-import git_utils
-
-
-SKIA_REPO = 'https://skia.googlesource.com/skia.git'
-COMMIT_MSG = '''Update markdown files
-
-Automatic commit by the Housekeeper-Nightly-Bookmaker bot.
-
-TBR=rmistry@google.com
-NO_MERGE_BUILDS
-'''
-CC_LIST = ['rmistry@google.com', 'caryclark@google.com']
-
-
-def main():
-  parser = argparse.ArgumentParser()
-  parser.add_argument("--bookmaker_binary")
-  parser.add_argument("--fiddlecli_output")
-  args = parser.parse_args()
-
-  with git_utils.NewGitCheckout(repository=SKIA_REPO):
-    with git_utils.GitBranch(branch_name='update_md_files',
-                             commit_msg=COMMIT_MSG,
-                             commit_queue=False,
-                             upload=False,
-                             cc_list=CC_LIST) as git_branch:
-      # Run bookmaker binary.
-      cmd = [args.bookmaker_binary,
-             '-a', 'docs/status.json',
-             '-f', args.fiddlecli_output,
-             '-r', 'site/user/api',
-             ]
-      try:
-        subprocess.check_call(cmd)
-      except subprocess.CalledProcessError as e:
-        print >> sys.stderr, (
-            'Running %s failed, not uploading markdowns update:\n\n%s' % (
-                cmd, e.output))
-        sys.exit(1)
-
-      # Verify that only files in the expected directory are going to be
-      # committed and uploaded.
-      diff_files = subprocess.check_output(['git', 'diff', '--name-only'])
-      for diff_file in diff_files.split():
-        if not diff_file.startswith('site/user/api/'):
-          print >> sys.stderr, (
-            'Some files in %s were not in the site/user/api dir. '
-            'Not uploading them' % diff_files)
-          sys.exit(1)
-      if diff_files:
-        subprocess.check_call(['git', 'add', '-u'])
-        git_branch.commit_and_upload(True)
-      else:
-        print 'No changes so nothing to upload.'
-
-
-if '__main__' == __name__:
-  main()
diff --git a/infra/canvaskit/build_canvaskit.sh b/infra/canvaskit/build_canvaskit.sh
index dcf44a7..74b8a6d 100755
--- a/infra/canvaskit/build_canvaskit.sh
+++ b/infra/canvaskit/build_canvaskit.sh
@@ -4,7 +4,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-# This assumes it is being run inside a docker container of emsdk-base
+# This assumes it is being run inside a docker container of emsdk-release
 # and a Skia checkout has been mounted at /SRC and the output directory
 # is mounted at /OUT
 
@@ -20,7 +20,7 @@
 
 #BASE_DIR is the dir this script is in ($SKIA_ROOT/infra/canvaskit)
 BASE_DIR=`cd $(dirname ${BASH_SOURCE[0]}) && pwd`
-CANVASKIT_DIR=$BASE_DIR/../../experimental/canvaskit
+CANVASKIT_DIR=$BASE_DIR/../../modules/canvaskit
 
 BUILD_DIR=/OUT $CANVASKIT_DIR/compile.sh $@
 
diff --git a/infra/canvaskit/docker/Makefile b/infra/canvaskit/docker/Makefile
new file mode 100644
index 0000000..9245d21
--- /dev/null
+++ b/infra/canvaskit/docker/Makefile
@@ -0,0 +1,6 @@
+EMSDK_VERSION=1.38.27_v1
+
+publish_canvaskit_emsdk:
+	docker build -t canvaskit-emsdk ./canvaskit-emsdk/
+	docker tag canvaskit-emsdk gcr.io/skia-public/canvaskit-emsdk:${EMSDK_VERSION}
+	docker push gcr.io/skia-public/canvaskit-emsdk:${EMSDK_VERSION}
diff --git a/infra/canvaskit/docker/canvaskit-emsdk/Dockerfile b/infra/canvaskit/docker/canvaskit-emsdk/Dockerfile
new file mode 100644
index 0000000..0e3467f
--- /dev/null
+++ b/infra/canvaskit/docker/canvaskit-emsdk/Dockerfile
@@ -0,0 +1,7 @@
+# A Docker image that augments the Emscripten SDK Docker image
+# with anything needed to build Canvaskit
+
+FROM gcr.io/skia-public/emsdk-release:1.38.27_v1
+
+RUN apt-get update && apt-get upgrade -y && apt-get install -y \
+  libfreetype6-dev
\ No newline at end of file
diff --git a/infra/canvaskit/perf_canvaskit.sh b/infra/canvaskit/perf_canvaskit.sh
index 8db1179..f87d25a 100755
--- a/infra/canvaskit/perf_canvaskit.sh
+++ b/infra/canvaskit/perf_canvaskit.sh
@@ -9,20 +9,20 @@
 # is mounted at /OUT
 
 # For example:
-# docker run -v $SKIA_ROOT:/SRC -v /tmp/dockerout:/OUT gcr.io/skia-public/perf-karma-chrome-tests:68.0.3440.106_v1 /SRC/infra/canvaskit/perf_canvaskit.sh
+# docker run -v $SKIA_ROOT:/SRC -v /tmp/dockerout:/OUT gcr.io/skia-public/perf-karma-chrome-tests:72.0.3626.121_v1 /SRC/infra/canvaskit/perf_canvaskit.sh
 
 set -ex
 
 #BASE_DIR is the dir this script is in ($SKIA_ROOT/infra/pathkit)
 BASE_DIR=`cd $(dirname ${BASH_SOURCE[0]}) && pwd`
-PATHKIT_DIR=$BASE_DIR/../../experimental/canvaskit
+CANVASKIT_DIR=$BASE_DIR/../../modules/canvaskit
 
 # Start the aggregator in the background
 /opt/perf-aggregator $@ &
 # Run the tests 10 times to get a wide set of data
 for i in `seq 1 10`;
 do
-    npx karma start $PATHKIT_DIR/karma.bench.conf.js --single-run
+    npx karma start $CANVASKIT_DIR/karma.bench.conf.js --single-run
 done
 # Tell the aggregator to dump the json
 # This curl command gets the HTTP code and stores it into $CODE
diff --git a/infra/canvaskit/test_canvaskit.sh b/infra/canvaskit/test_canvaskit.sh
index ec6ae22..cbdf1d7 100755
--- a/infra/canvaskit/test_canvaskit.sh
+++ b/infra/canvaskit/test_canvaskit.sh
@@ -9,13 +9,13 @@
 # is mounted at /OUT
 
 # For example:
-# docker run -v $SKIA_ROOT:/SRC -v /tmp/dockerout:/OUT gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v6 /SRC/infra/canvaskit/test_canvaskit.sh
+# docker run -v $SKIA_ROOT:/SRC -v /tmp/dockerout:/OUT gcr.io/skia-public/gold-karma-chrome-tests:72.0.3626.121_v1 /SRC/infra/canvaskit/test_canvaskit.sh
 
 set -ex
 
 #BASE_DIR is the dir this script is in ($SKIA_ROOT/infra/canvaskit)
 BASE_DIR=`cd $(dirname ${BASH_SOURCE[0]}) && pwd`
-CANVASKIT_DIR=$BASE_DIR/../../experimental/canvaskit
+CANVASKIT_DIR=$BASE_DIR/../../modules/canvaskit
 
 # Start the aggregator in the background
 /opt/gold-aggregator $@ &
diff --git a/infra/cmake/build_skia.sh b/infra/cmake/build_skia.sh
index d1839d7..6e78dfa 100755
--- a/infra/cmake/build_skia.sh
+++ b/infra/cmake/build_skia.sh
@@ -4,7 +4,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-# This assumes it is being run inside a docker container of emsdk-base
+# This assumes it is being run inside a docker container of emsdk-release
 # and a Skia checkout has been mounted at /SRC and the output directory
 # has been mounted at /OUT
 
@@ -23,6 +23,7 @@
 mkdir --mode=0777 -p $SKIA_DIR/out/CMAKE
 
 cd $SKIA_DIR
+./bin/fetch-gn
 gn gen out/CMAKE --args='is_debug=false' --ide=json --json-ide-script=../../gn/gn_to_cmake.py
 
 cd $SKIA_DIR/out/CMAKE
diff --git a/infra/config/recipes.cfg b/infra/config/recipes.cfg
index 9f96b7c..88cd317 100644
--- a/infra/config/recipes.cfg
+++ b/infra/config/recipes.cfg
@@ -14,15 +14,16 @@
   "deps": {
     "depot_tools": {
       "branch": "master",
-      "revision": "06d1040fab75709fd0cbea3d3cbfa8774cb826f0",
+      "revision": "1701094c07bafa3992d7f36f1cf57f504187e35d",
       "url": "https://chromium.googlesource.com/chromium/tools/depot_tools.git"
     },
     "recipe_engine": {
       "branch": "master",
-      "revision": "3fd6d5382c9a43f9bcd7ed8cf855b0f60889cc0e",
+      "revision": "a8a96dca3e5e81e2bfff4b93a2384c8b40a58068",
       "url": "https://chromium.googlesource.com/infra/luci/recipes-py.git"
     }
   },
   "project_id": "skia",
-  "recipes_path": "infra/bots"
+  "recipes_path": "infra/bots",
+  "repo_name": "skia"
 }
diff --git a/infra/cross-compile/docker/cross-linux-arm64/Dockerfile b/infra/cross-compile/docker/cross-linux-arm64/Dockerfile
new file mode 100644
index 0000000..a7a8474
--- /dev/null
+++ b/infra/cross-compile/docker/cross-linux-arm64/Dockerfile
@@ -0,0 +1,14 @@
+FROM launcher.gcr.io/google/clang-debian9 AS build
+
+RUN apt-get update && apt-get upgrade -y && apt-get install -y \
+  binutils-aarch64-linux-gnu \
+  git \
+  libc6-dev-arm64-cross \
+  libegl1-mesa-dev \
+  libstdc++-6-dev-arm64-cross \
+  python
+
+RUN cd /opt \
+ && git clone 'https://chromium.googlesource.com/chromium/tools/depot_tools.git'
+
+ENV PATH="/opt/depot_tools:${PATH}"
diff --git a/infra/pathkit/Makefile b/infra/pathkit/Makefile
deleted file mode 100644
index 2a0c260..0000000
--- a/infra/pathkit/Makefile
+++ /dev/null
@@ -1,27 +0,0 @@
-gold_docker_image: aggregator
-	# Set the build context to the current work dir, so we can copy
-	# the built binary to where we need it.
-	docker build -t gold-karma-chrome-tests -f ./docker/gold-karma-chrome-tests/Dockerfile .
-
-perf_docker_image: aggregator
-	# Set the build context to the current work dir, so we can copy
-	# the built binary to where we need it.
-	docker build -t perf-karma-chrome-tests -f ./docker/perf-karma-chrome-tests/Dockerfile .
-
-aggregator:
-	mkdir -p ./tmp
-	CGO_ENABLED=0 GOOS=linux go build -o ./tmp/gold-aggregator -a ./gold/
-	mkdir -p ./tmp
-	CGO_ENABLED=0 GOOS=linux go build -o ./tmp/perf-aggregator -a ./perf/
-
-# Can check CHROME_VERSION with
-# docker run karma-chrome-tests /usr/bin/google-chrome-stable --version
-CHROME_VERSION=68.0.3440.106_v6
-
-publish_gold_karma_chrome_tests: gold_docker_image
-	docker tag gold-karma-chrome-tests gcr.io/skia-public/gold-karma-chrome-tests:${CHROME_VERSION}
-	docker push gcr.io/skia-public/gold-karma-chrome-tests:${CHROME_VERSION}
-
-publish_perf_karma_chrome_tests: perf_docker_image
-	docker tag perf-karma-chrome-tests gcr.io/skia-public/perf-karma-chrome-tests:${CHROME_VERSION}
-	docker push gcr.io/skia-public/perf-karma-chrome-tests:${CHROME_VERSION}
\ No newline at end of file
diff --git a/infra/pathkit/build_pathkit.sh b/infra/pathkit/build_pathkit.sh
index 7431670..cb4adb1 100755
--- a/infra/pathkit/build_pathkit.sh
+++ b/infra/pathkit/build_pathkit.sh
@@ -4,7 +4,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-# This assumes it is being run inside a docker container of emsdk-base
+# This assumes it is being run inside a docker container of emsdk-release
 # and a Skia checkout has been mounted at /SRC and the output directory
 # is mounted at /OUT
 
diff --git a/infra/pathkit/docker/Makefile b/infra/pathkit/docker/Makefile
deleted file mode 100644
index 23c848c..0000000
--- a/infra/pathkit/docker/Makefile
+++ /dev/null
@@ -1,18 +0,0 @@
-EMSDK_VERSION=1.38.16_v1
-
-# Can check CHROME_VERSION with
-# docker run karma-chrome-tests /usr/bin/google-chrome-stable --version
-CHROME_VERSION=68.0.3440.106_v5
-
-publish_emsdk_base:
-	docker build --no-cache -t emsdk-base ./emsdk-base/
-	docker tag emsdk-base gcr.io/skia-public/emsdk-release:${EMSDK_VERSION}
-	docker push gcr.io/skia-public/emsdk-release:${EMSDK_VERSION}
-	docker tag emsdk-base gcr.io/skia-public/emsdk-release:prod
-	docker push gcr.io/skia-public/emsdk-release:prod
-
-publish_karma_chrome_tests:
-	docker build --no-cache -t karma-chrome-tests ./karma-chrome-tests/
-	docker tag karma-chrome-tests gcr.io/skia-public/karma-chrome-tests:${CHROME_VERSION}
-	docker push gcr.io/skia-public/karma-chrome-tests:${CHROME_VERSION}
-
diff --git a/infra/pathkit/docker/README.md b/infra/pathkit/docker/README.md
deleted file mode 100644
index e4f4acc..0000000
--- a/infra/pathkit/docker/README.md
+++ /dev/null
@@ -1,110 +0,0 @@
-Docker
-======
-
-Docker files to ease working with PathKit and WASM.
-
-emsdk-base
-----------
-
-This image has an Emscripten SDK environment that can be used for
-compiling projects (e.g. Skia's PathKit) to WASM/asm.js.
-
-This image is standalone and does not have any extra dependencies that make
-it Skia-exclusive.
-
-It gets manually pushed anytime there's an update to the Dockerfile or relevant
-installed libraries.
-
-    make publish_emsdk_base
-
-For testing the image locally, the following flow can be helpful:
-
-    docker build -t emsdk-base ./emsdk-base/
-    # Run bash in it to poke around and make sure things are properly installed
-    docker run -it emsdk-release /bin/bash
-    # Compile PathKit with the local image
-    docker run -v $SKIA_ROOT:/SRC -v $SKIA_ROOT/out/dockerpathkit:/OUT emsdk-base /SRC/infra/pathkit/build_pathkit.sh
-
-karma-chrome-tests
-------------------
-
-This image has Google Chrome and karma/jasmine installed on it, which can
-be used to run JS tests.
-
-This image is standalone and does not have any extra dependencies that make
-it Skia-exclusive.
-
-It gets manually pushed anytime there's an update to the Dockerfile or relevant
-installed libraries.
-
-    make publish_karma_chrome_tests
-
-Of note, some versions (generally before Chrome 60) run out of space on /dev/shm when
-using the default Docker settings.  To be safe, it is recommended to run the container
-with the flag --shm-size=2gb.
-
-For testing the image locally, the following can be helpful:
-
-    docker build -t karma-chrome-tests ./karma-chrome-tests/
-    # Run bash in it to poke around and make sure things are properly installed
-    docker run -it --shm-size=2gb karma-chrome-tests /bin/bash
-    # Run the tests (but not capturing Gold output) with the local source repo
-    docker run --shm-size=2gb -v $SKIA_ROOT:/SRC karma-chrome-tests karma start /SRC/infra/pathkit/karma-docker.conf.js --single-run
-
-gold-karma-chrome-tests
-------------------
-
-This image has Google Chrome and karma/jasmine installed on it, which can
-be used to run JS tests.
-
-This image assumes the runner wants to collect the output images and JSON data
-specific to Skia Infra's Gold tool (image correctness).
-
-It gets manually pushed anytime there's an update to the Dockerfile or the parent
-image (karma-chrome-tests).
-
-    # Run the following from $SKIA_ROOT/infra/pathkit
-    make publish_gold_karma_chrome_tests
-
-Of note, some versions (generally before Chrome 60) run out of space on /dev/shm when
-using the default Docker settings.  To be safe, it is recommended to run the container
-with the flag --shm-size=2gb.
-
-For testing the image locally, the following can be helpful:
-
-    # Run the following from $SKIA_ROOT/infra/pathkit
-    make gold-docker-image
-    # Run bash in it to poke around and make sure things are properly installed
-    docker run -it --shm-size=2gb gold-karma-chrome-tests /bin/bash
-    # Run the tests and collect Gold output with the local source repo
-    mkdir -p -m 0777 /tmp/dockergold
-    docker run --shm-size=2gb -v $SKIA_ROOT:/SRC -v /tmp/dockergold:/OUT gold-karma-chrome-tests /SRC/infra/pathkit/test_pathkit.sh
-
-perf-karma-chrome-tests
-------------------
-
-This image has Google Chrome and karma/jasmine installed on it, which can
-be used to run JS tests.
-
-This image assumes the runner wants to collect the output images and JSON data
-specific to Skia Infra's Perf tool.
-
-It gets manually pushed anytime there's an update to the Dockerfile or the parent
-image (karma-chrome-tests).
-
-    # Run the following from $SKIA_ROOT/infra/pathkit
-    make publish_perf_karma_chrome_tests
-
-Of note, some versions (generally before Chrome 60) run out of space on /dev/shm when
-using the default Docker settings.  To be safe, it is recommended to run the container
-with the flag --shm-size=2gb.
-
-For testing the image locally, the following can be helpful:
-
-    # Run the following from $SKIA_ROOT/infra/pathkit
-    make perf-docker-image
-    # Run bash in it to poke around and make sure things are properly installed
-    docker run -it --shm-size=2gb perf-karma-chrome-tests /bin/bash
-    # Run the tests and collect Perf output with the local source repo
-    mkdir -p -m 0777 /tmp/dockerperf
-    docker run --shm-size=2gb -v $SKIA_ROOT:/SRC -v /tmp/dockerperf:/OUT perf-karma-chrome-tests /SRC/infra/pathkit/perf_pathkit.sh
diff --git a/infra/pathkit/docker/emsdk-base/Dockerfile b/infra/pathkit/docker/emsdk-base/Dockerfile
deleted file mode 100644
index 27300f8..0000000
--- a/infra/pathkit/docker/emsdk-base/Dockerfile
+++ /dev/null
@@ -1,28 +0,0 @@
-# A Docker image that has the Emscripten SDK installed to /opt/emsdk
-# Use this image to compile C/C++ code to WASM.
-
-FROM launcher.gcr.io/google/clang-debian9 AS build
-RUN apt-get update && apt-get upgrade -y && apt-get install -y \
-  git \
-  python \
-  nodejs \
-  default-jre
-
-RUN cd /opt \
-  && git clone https://github.com/juj/emsdk.git
-
-WORKDIR /opt/emsdk
-
-RUN ./emsdk update-tags
-
-# These versions were available and worked on my local desktop as of Nov 6 2018.
-RUN ./emsdk install emscripten-1.38.16 node-8.9.1-64bit clang-e1.38.16-64bit
-
-RUN ./emsdk activate emscripten-1.38.16 node-8.9.1-64bit clang-e1.38.16-64bit
-
-RUN /bin/bash -c "source ./emsdk_env.sh"
-
-ENV EMSDK=/opt/emsdk
-
-RUN mkdir -p /OUT /SRC
-
diff --git a/infra/pathkit/docker/gold-karma-chrome-tests/Dockerfile b/infra/pathkit/docker/gold-karma-chrome-tests/Dockerfile
deleted file mode 100644
index 592f796..0000000
--- a/infra/pathkit/docker/gold-karma-chrome-tests/Dockerfile
+++ /dev/null
@@ -1,9 +0,0 @@
-# Docker container with Chrome, and karma/jasmine, to be used to run JS tests and
-# collect output for Skia Infra's Gold tool (correctness checking).
-#
-# Tests will be run as non-root (user skia, in fact), so /OUT should have permissions
-# 777 so as to be able to create output there.
-
-FROM gcr.io/skia-public/karma-chrome-tests:68.0.3440.106_v5
-
-COPY /tmp/gold-aggregator /opt/gold-aggregator
\ No newline at end of file
diff --git a/infra/pathkit/docker/perf-karma-chrome-tests/Dockerfile b/infra/pathkit/docker/perf-karma-chrome-tests/Dockerfile
deleted file mode 100644
index f47a95a..0000000
--- a/infra/pathkit/docker/perf-karma-chrome-tests/Dockerfile
+++ /dev/null
@@ -1,9 +0,0 @@
-# Docker container with Chrome, and karma/jasmine, to be used to run JS tests and
-# collect output for Skia Infra's Perf tool.
-#
-# Tests will be run as non-root (user skia, in fact), so /OUT should have permissions
-# 777 so as to be able to create output there.
-
-FROM gcr.io/skia-public/karma-chrome-tests:68.0.3440.106_v5
-
-COPY /tmp/perf-aggregator /opt/perf-aggregator
\ No newline at end of file
diff --git a/infra/pathkit/gold/wasm_gold_aggregator.go b/infra/pathkit/gold/wasm_gold_aggregator.go
deleted file mode 100644
index 7f1e883..0000000
--- a/infra/pathkit/gold/wasm_gold_aggregator.go
+++ /dev/null
@@ -1,243 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package main
-
-// This server runs along side the karma tests and listens for POST requests
-// when any test case reports it has output for Gold. See testReporter.js
-// for the browser side part.
-
-import (
-	"bytes"
-	"crypto/md5"
-	"encoding/base64"
-	"encoding/json"
-	"flag"
-	"fmt"
-	"image"
-	"image/png"
-	"io/ioutil"
-	"log"
-	"net/http"
-	"os"
-	"path"
-	"strings"
-
-	"go.skia.org/infra/golden/go/goldingestion"
-	"go.skia.org/infra/golden/go/jsonio"
-)
-
-// This allows us to use upload_dm_results.py out of the box
-const JSON_FILENAME = "dm.json"
-
-var (
-	outDir = flag.String("out_dir", "/OUT/", "location to dump the Gold JSON and pngs")
-	port   = flag.String("port", "8081", "Port to listen on.")
-
-	botId            = flag.String("bot_id", "", "swarming bot id")
-	browser          = flag.String("browser", "Chrome", "Browser Key")
-	buildBucketID    = flag.Int64("buildbucket_build_id", 0, "Buildbucket build id key")
-	builder          = flag.String("builder", "", "Builder, like 'Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit'")
-	compiledLanguage = flag.String("compiled_language", "wasm", "wasm or asm.js")
-	config           = flag.String("config", "Release", "Configuration (e.g. Debug/Release) key")
-	gitHash          = flag.String("git_hash", "-", "The git commit hash of the version being tested")
-	hostOS           = flag.String("host_os", "Debian9", "OS Key")
-	issue            = flag.Int64("issue", 0, "issue (if tryjob)")
-	patchset         = flag.Int64("patchset", 0, "patchset (if tryjob)")
-	taskId           = flag.String("task_id", "", "swarming task id")
-	sourceType       = flag.String("source_type", "pathkit", "Gold Source type, like pathkit,canvaskit")
-)
-
-// Received from the JS side.
-type reportBody struct {
-	// e.g. "canvas" or "svg"
-	OutputType string `json:"output_type"`
-	// a base64 encoded PNG image.
-	Data string `json:"data"`
-	// a name describing the test. Should be unique enough to allow use of grep.
-	TestName string `json:"test_name"`
-}
-
-// The keys to be used at the top level for all Results.
-var defaultKeys map[string]string
-
-// contains all the results reported in through report_gold_data
-var results []*jsonio.Result
-
-func main() {
-	flag.Parse()
-
-	cpuGPU := "CPU"
-	if strings.Index(*builder, "-GPU-") != -1 {
-		cpuGPU = "GPU"
-	}
-	defaultKeys = map[string]string{
-		"arch":              "WASM",
-		"browser":           *browser,
-		"compiled_language": *compiledLanguage,
-		"compiler":          "emsdk",
-		"configuration":     *config,
-		"cpu_or_gpu":        cpuGPU,
-		"cpu_or_gpu_value":  "Browser",
-		"os":                *hostOS,
-		"source_type":       *sourceType,
-	}
-
-	results = []*jsonio.Result{}
-
-	http.HandleFunc("/report_gold_data", reporter)
-	http.HandleFunc("/dump_json", dumpJSON)
-
-	fmt.Printf("Waiting for gold ingestion on port %s\n", *port)
-
-	log.Fatal(http.ListenAndServe(":"+*port, nil))
-}
-
-// reporter handles when the client reports a test has Gold output.
-// It writes the corresponding PNG to disk and appends a Result, assuming
-// no errors.
-func reporter(w http.ResponseWriter, r *http.Request) {
-	if r.Method != "POST" {
-		http.Error(w, "Only POST accepted", 400)
-		return
-	}
-	defer r.Body.Close()
-
-	body, err := ioutil.ReadAll(r.Body)
-	if err != nil {
-		http.Error(w, "Malformed body", 400)
-		return
-	}
-
-	testOutput := reportBody{}
-	if err := json.Unmarshal(body, &testOutput); err != nil {
-		fmt.Println(err)
-		http.Error(w, "Could not unmarshal JSON", 400)
-		return
-	}
-
-	hash := ""
-	if hash, err = writeBase64EncodedPNG(testOutput.Data); err != nil {
-		fmt.Println(err)
-		http.Error(w, "Could not write image to disk", 500)
-		return
-	}
-
-	if _, err := w.Write([]byte("Accepted")); err != nil {
-		fmt.Printf("Could not write response: %s\n", err)
-		return
-	}
-
-	results = append(results, &jsonio.Result{
-		Digest: hash,
-		Key: map[string]string{
-			"name":   testOutput.TestName,
-			"config": testOutput.OutputType,
-		},
-		Options: map[string]string{
-			"ext": "png",
-		},
-	})
-}
-
-// createOutputFile creates a file and set permissions correctly.
-func createOutputFile(p string) (*os.File, error) {
-	outputFile, err := os.Create(p)
-	if err != nil {
-		return nil, fmt.Errorf("Could not open file %s on disk: %s", p, err)
-	}
-	// Make this accessible (and deletable) by all users
-	if err = outputFile.Chmod(0666); err != nil {
-		return nil, fmt.Errorf("Could not change permissions of file %s: %s", p, err)
-	}
-	return outputFile, nil
-}
-
-// dumpJSON writes out a JSON file with all the results, typically at the end of
-// all the tests.
-func dumpJSON(w http.ResponseWriter, r *http.Request) {
-	if r.Method != "POST" {
-		http.Error(w, "Only POST accepted", 400)
-		return
-	}
-
-	p := path.Join(*outDir, JSON_FILENAME)
-	outputFile, err := createOutputFile(p)
-	defer outputFile.Close()
-	if err != nil {
-		fmt.Println(err)
-		http.Error(w, "Could not open json file on disk", 500)
-		return
-	}
-
-	dmresults := goldingestion.DMResults{
-		GoldResults: &jsonio.GoldResults{
-			BuildBucketID:  *buildBucketID,
-			Builder:        *builder,
-			GitHash:        *gitHash,
-			Issue:          *issue,
-			Key:            defaultKeys,
-			Patchset:       *patchset,
-			Results:        results,
-			SwarmingBotID:  *botId,
-			SwarmingTaskID: *taskId,
-		},
-	}
-
-	enc := json.NewEncoder(outputFile)
-	enc.SetIndent("", "  ") // Make it human readable.
-	if err := enc.Encode(&dmresults); err != nil {
-		fmt.Println(err)
-		http.Error(w, "Could not write json to disk", 500)
-		return
-	}
-	fmt.Println("JSON Written")
-}
-
-// writeBase64EncodedPNG writes a PNG to disk and returns the md5 of the
-// decoded PNG bytes and any error. This hash is what will be used as
-// the gold digest and the file name.
-func writeBase64EncodedPNG(data string) (string, error) {
-	// data starts with something like data:image/png;base64,[data]
-	// https://en.wikipedia.org/wiki/Data_URI_scheme
-	start := strings.Index(data, ",")
-	b := bytes.NewBufferString(data[start+1:])
-	pngReader := base64.NewDecoder(base64.StdEncoding, b)
-
-	pngBytes, err := ioutil.ReadAll(pngReader)
-	if err != nil {
-		return "", fmt.Errorf("Could not decode base 64 encoding %s", err)
-	}
-
-	// compute the hash of the pixel values, like DM does
-	img, err := png.Decode(bytes.NewBuffer(pngBytes))
-	if err != nil {
-		return "", fmt.Errorf("Not a valid png: %s", err)
-	}
-	hash := ""
-	switch img.(type) {
-	case *image.NRGBA:
-		i := img.(*image.NRGBA)
-		hash = fmt.Sprintf("%x", md5.Sum(i.Pix))
-	case *image.RGBA:
-		i := img.(*image.RGBA)
-		hash = fmt.Sprintf("%x", md5.Sum(i.Pix))
-	case *image.RGBA64:
-		i := img.(*image.RGBA64)
-		hash = fmt.Sprintf("%x", md5.Sum(i.Pix))
-	default:
-		return "", fmt.Errorf("Unknown type of image")
-	}
-
-	p := path.Join(*outDir, hash+".png")
-	outputFile, err := createOutputFile(p)
-	defer outputFile.Close()
-	if err != nil {
-		return "", fmt.Errorf("Could not create png file %s: %s", p, err)
-	}
-	if _, err = outputFile.Write(pngBytes); err != nil {
-		return "", fmt.Errorf("Could not write to file %s: %s", p, err)
-	}
-	return hash, nil
-}
diff --git a/infra/pathkit/perf/wasm_perf_aggregator.go b/infra/pathkit/perf/wasm_perf_aggregator.go
deleted file mode 100644
index 4a0cd48..0000000
--- a/infra/pathkit/perf/wasm_perf_aggregator.go
+++ /dev/null
@@ -1,206 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package main
-
-// This server runs along side the karma tests and listens for POST requests
-// when any test case reports it has output for Perf. See perfReporter.js
-// for the browser side part.
-
-// Unlike the gold ingester, the perf ingester allows multiple reports
-// of the same benchmark and will output the average of these results
-// on a call to dump
-
-import (
-	"encoding/json"
-	"flag"
-	"fmt"
-	"io/ioutil"
-	"log"
-	"net/http"
-	"os"
-	"path"
-	"strconv"
-	"strings"
-
-	"github.com/google/uuid"
-	"go.skia.org/infra/perf/go/ingestcommon"
-)
-
-// upload_nano_results looks for anything*.json
-// We add the random UUID to avoid name clashes when uploading to
-// the perf bucket (which uploads to folders based on Month/Day/Hour, which can
-// easily have duplication if multiple perf tasks run in an hour.)
-var JSON_FILENAME = fmt.Sprintf("%s_browser_bench.json", uuid.New().String())
-
-var (
-	outDir = flag.String("out_dir", "/OUT/", "location to dump the Perf JSON")
-	port   = flag.String("port", "8081", "Port to listen on.")
-
-	botId            = flag.String("bot_id", "", "swarming bot id")
-	browser          = flag.String("browser", "Chrome", "Browser Key")
-	buildBucketID    = flag.Int64("buildbucket_build_id", 0, "Buildbucket build id key")
-	builder          = flag.String("builder", "", "Builder, like 'Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit'")
-	compiledLanguage = flag.String("compiled_language", "wasm", "wasm or asm.js")
-	config           = flag.String("config", "Release", "Configuration (e.g. Debug/Release) key")
-	gitHash          = flag.String("git_hash", "-", "The git commit hash of the version being tested")
-	hostOS           = flag.String("host_os", "Debian9", "OS Key")
-	issue            = flag.Int64("issue", 0, "issue (if tryjob)")
-	patch_storage    = flag.String("patch_storage", "", "patch storage (if tryjob)")
-	patchset         = flag.Int64("patchset", 0, "patchset (if tryjob)")
-	taskId           = flag.String("task_id", "", "swarming task id")
-	sourceType       = flag.String("source_type", "pathkit", "Gold Source type, like pathkit,canvaskit")
-)
-
-// Received from the JS side.
-type reportBody struct {
-	// a name describing the benchmark. Should be unique enough to allow use of grep.
-	BenchName string `json:"bench_name"`
-	// The number of microseconds of the task.
-	TimeMicroSeconds float64 `json:"time_us"`
-}
-
-// The keys to be used at the top level for all Results.
-var defaultKeys map[string]string
-
-// contains all the results reported in through report_perf_data
-var results map[string][]reportBody
-
-type BenchData struct {
-	Hash         string                               `json:"gitHash"`
-	Issue        string                               `json:"issue"`
-	PatchSet     string                               `json:"patchset"`
-	Key          map[string]string                    `json:"key"`
-	Options      map[string]string                    `json:"options,omitempty"`
-	Results      map[string]ingestcommon.BenchResults `json:"results"`
-	PatchStorage string                               `json:"patch_storage,omitempty"`
-
-	SwarmingTaskID string `json:"swarming_task_id,omitempty"`
-	SwarmingBotID  string `json:"swarming_bot_id,omitempty"`
-}
-
-func main() {
-	flag.Parse()
-
-	cpuGPU := "CPU"
-	if strings.Index(*builder, "-GPU-") != -1 {
-		cpuGPU = "GPU"
-	}
-	defaultKeys = map[string]string{
-		"arch":              "WASM",
-		"browser":           *browser,
-		"compiled_language": *compiledLanguage,
-		"compiler":          "emsdk",
-		"configuration":     *config,
-		"cpu_or_gpu":        cpuGPU,
-		"cpu_or_gpu_value":  "Browser",
-		"os":                *hostOS,
-		"source_type":       *sourceType,
-	}
-
-	results = make(map[string][]reportBody)
-
-	http.HandleFunc("/report_perf_data", reporter)
-	http.HandleFunc("/dump_json", dumpJSON)
-
-	fmt.Printf("Waiting for perf ingestion on port %s\n", *port)
-
-	log.Fatal(http.ListenAndServe(":"+*port, nil))
-}
-
-// reporter handles when the client reports a test has a benchmark.
-func reporter(w http.ResponseWriter, r *http.Request) {
-	if r.Method != "POST" {
-		http.Error(w, "Only POST accepted", 400)
-		return
-	}
-	defer r.Body.Close()
-
-	body, err := ioutil.ReadAll(r.Body)
-	if err != nil {
-		http.Error(w, "Malformed body", 400)
-		return
-	}
-
-	benchOutput := reportBody{}
-	if err := json.Unmarshal(body, &benchOutput); err != nil {
-		fmt.Println(err)
-		http.Error(w, "Could not unmarshal JSON", 400)
-		return
-	}
-
-	if _, err := w.Write([]byte("Accepted")); err != nil {
-		fmt.Printf("Could not write response: %s\n", err)
-		return
-	}
-
-	results[benchOutput.BenchName] = append(results[benchOutput.BenchName], benchOutput)
-}
-
-// createOutputFile creates a file and set permissions correctly.
-func createOutputFile(p string) (*os.File, error) {
-	outputFile, err := os.Create(p)
-	if err != nil {
-		return nil, fmt.Errorf("Could not open file %s on disk: %s", p, err)
-	}
-	// Make this accessible (and deletable) by all users
-	if err = outputFile.Chmod(0666); err != nil {
-		return nil, fmt.Errorf("Could not change permissions of file %s: %s", p, err)
-	}
-	return outputFile, nil
-}
-
-// dumpJSON writes out a JSON file with all the results, typically at the end of
-// all the tests. If there is more than one result per benchmark, we report the average.
-func dumpJSON(w http.ResponseWriter, r *http.Request) {
-	if r.Method != "POST" {
-		http.Error(w, "Only POST accepted", 400)
-		return
-	}
-
-	p := path.Join(*outDir, JSON_FILENAME)
-	outputFile, err := createOutputFile(p)
-	defer outputFile.Close()
-	if err != nil {
-		fmt.Println(err)
-		http.Error(w, "Could not open json file on disk", 500)
-		return
-	}
-
-	benchData := BenchData{
-		Hash:           *gitHash,
-		Issue:          strconv.FormatInt(*issue, 10),
-		PatchStorage:   *patch_storage,
-		PatchSet:       strconv.FormatInt(*patchset, 10),
-		Key:            defaultKeys,
-		SwarmingBotID:  *botId,
-		SwarmingTaskID: *taskId,
-	}
-
-	allResults := make(map[string]ingestcommon.BenchResults)
-	for name, benches := range results {
-		samples := []float64{}
-		total := float64(0)
-		for _, t := range benches {
-			samples = append(samples, t.TimeMicroSeconds)
-			total += t.TimeMicroSeconds
-		}
-		allResults[name] = map[string]ingestcommon.BenchResult{
-			"default": map[string]interface{}{
-				"average_us": total / float64(len(benches)),
-				"samples":    samples,
-			},
-		}
-	}
-	benchData.Results = allResults
-
-	enc := json.NewEncoder(outputFile)
-	enc.SetIndent("", "  ") // Make it human readable.
-	if err := enc.Encode(&benchData); err != nil {
-		fmt.Println(err)
-		http.Error(w, "Could not write json to disk", 500)
-		return
-	}
-	fmt.Println("JSON Written")
-}
diff --git a/infra/pathkit/perf_pathkit.sh b/infra/pathkit/perf_pathkit.sh
index 55ab29d..b4471d4 100755
--- a/infra/pathkit/perf_pathkit.sh
+++ b/infra/pathkit/perf_pathkit.sh
@@ -9,7 +9,7 @@
 # is mounted at /OUT
 
 # For example:
-# docker run -v $SKIA_ROOT:/SRC -v /tmp/dockerout:/OUT gcr.io/skia-public/perf-karma-chrome-tests:68.0.3440.106_v1 /SRC/infra/pathkit/perf_pathkit.sh
+# docker run -v $SKIA_ROOT:/SRC -v /tmp/dockerout:/OUT gcr.io/skia-public/perf-karma-chrome-tests:72.0.3626.121_v1 /SRC/infra/pathkit/perf_pathkit.sh
 
 set -ex
 
diff --git a/infra/pathkit/test_pathkit.sh b/infra/pathkit/test_pathkit.sh
index bfd0405..b94577fe 100755
--- a/infra/pathkit/test_pathkit.sh
+++ b/infra/pathkit/test_pathkit.sh
@@ -9,7 +9,7 @@
 # is mounted at /OUT
 
 # For example:
-# docker run -v $SKIA_ROOT:/SRC -v /tmp/dockerout:/OUT gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v1 /SRC/infra/pathkit/test_pathkit.sh
+# docker run -v $SKIA_ROOT:/SRC -v /tmp/dockerout:/OUT gcr.io/skia-public/gold-karma-chrome-tests:72.0.3626.121_v1 /SRC/infra/pathkit/test_pathkit.sh
 
 set -ex
 
diff --git a/infra/pathkit/.gitignore b/infra/wasm-common/.gitignore
similarity index 100%
rename from infra/pathkit/.gitignore
rename to infra/wasm-common/.gitignore
diff --git a/infra/wasm-common/Makefile b/infra/wasm-common/Makefile
new file mode 100644
index 0000000..8ca5de9
--- /dev/null
+++ b/infra/wasm-common/Makefile
@@ -0,0 +1,27 @@
+gold_docker_image: aggregator
+	# Set the build context to the current work dir, so we can copy
+	# the built binary to where we need it.
+	docker build -t gold-karma-chrome-tests -f ./docker/gold-karma-chrome-tests/Dockerfile .
+
+perf_docker_image: aggregator
+	# Set the build context to the current work dir, so we can copy
+	# the built binary to where we need it.
+	docker build -t perf-karma-chrome-tests -f ./docker/perf-karma-chrome-tests/Dockerfile .
+
+aggregator:
+	mkdir -p ./tmp
+	CGO_ENABLED=0 GOOS=linux go build -o ./tmp/gold-aggregator -a ./gold/
+	mkdir -p ./tmp
+	CGO_ENABLED=0 GOOS=linux go build -o ./tmp/perf-aggregator -a ./perf/
+
+# Can check CHROME_VERSION with
+# docker run karma-chrome-tests /usr/bin/google-chrome-stable --version
+CHROME_VERSION=72.0.3626.121_v1
+
+publish_gold_karma_chrome_tests: gold_docker_image
+	docker tag gold-karma-chrome-tests gcr.io/skia-public/gold-karma-chrome-tests:${CHROME_VERSION}
+	docker push gcr.io/skia-public/gold-karma-chrome-tests:${CHROME_VERSION}
+
+publish_perf_karma_chrome_tests: perf_docker_image
+	docker tag perf-karma-chrome-tests gcr.io/skia-public/perf-karma-chrome-tests:${CHROME_VERSION}
+	docker push gcr.io/skia-public/perf-karma-chrome-tests:${CHROME_VERSION}
diff --git a/infra/wasm-common/README.md b/infra/wasm-common/README.md
new file mode 100644
index 0000000..4e7ff25
--- /dev/null
+++ b/infra/wasm-common/README.md
@@ -0,0 +1,4 @@
+This directory contains docker images that will be run on swarming to
+build/test/perf WASM code.
+
+For docker images that run on Cloud Build, see $SKIA_ROOT/docker.
\ No newline at end of file
diff --git a/infra/wasm-common/docker/Makefile b/infra/wasm-common/docker/Makefile
new file mode 100644
index 0000000..2af691e
--- /dev/null
+++ b/infra/wasm-common/docker/Makefile
@@ -0,0 +1,18 @@
+EMSDK_VERSION=1.38.27_v1
+
+# Can check CHROME_VERSION with
+# docker run karma-chrome-tests /usr/bin/google-chrome-stable --version
+CHROME_VERSION=72.0.3626.121_v1
+
+publish_emsdk_base:
+	docker build --no-cache -t emsdk-release ./emsdk-release/
+	docker tag emsdk-release gcr.io/skia-public/emsdk-release:${EMSDK_VERSION}
+	docker push gcr.io/skia-public/emsdk-release:${EMSDK_VERSION}
+	docker tag emsdk-release gcr.io/skia-public/emsdk-release:prod
+	docker push gcr.io/skia-public/emsdk-release:prod
+
+publish_karma_chrome_tests:
+	docker build --no-cache -t karma-chrome-tests ./karma-chrome-tests/
+	docker tag karma-chrome-tests gcr.io/skia-public/karma-chrome-tests:${CHROME_VERSION}
+	docker push gcr.io/skia-public/karma-chrome-tests:${CHROME_VERSION}
+
diff --git a/infra/wasm-common/docker/README.md b/infra/wasm-common/docker/README.md
new file mode 100644
index 0000000..008eb4b
--- /dev/null
+++ b/infra/wasm-common/docker/README.md
@@ -0,0 +1,110 @@
+Docker
+======
+
+Docker files to ease working with PathKit and WASM.
+
+emsdk-release
+----------
+
+This image has an Emscripten SDK environment that can be used for
+compiling projects (e.g. Skia's PathKit) to WASM/asm.js.
+
+This image is standalone and does not have any extra dependencies that make
+it Skia-exclusive.
+
+It gets manually pushed anytime there's an update to the Dockerfile or relevant
+installed libraries.
+
+    make publish_emsdk_base
+
+For testing the image locally, the following flow can be helpful:
+
+    docker build -t emsdk-release ./emsdk-release/
+    # Run bash in it to poke around and make sure things are properly installed
+    docker run -it emsdk-release /bin/bash
+    # Compile PathKit with the local image
+    docker run -v $SKIA_ROOT:/SRC -v $SKIA_ROOT/out/dockerpathkit:/OUT emsdk-release /SRC/infra/pathkit/build_pathkit.sh
+
+karma-chrome-tests
+------------------
+
+This image has Google Chrome and karma/jasmine installed on it, which can
+be used to run JS tests.
+
+This image is standalone and does not have any extra dependencies that make
+it Skia-exclusive.
+
+It gets manually pushed anytime there's an update to the Dockerfile or relevant
+installed libraries.
+
+    make publish_karma_chrome_tests
+
+Of note, some versions (generally before Chrome 60) run out of space on /dev/shm when
+using the default Docker settings.  To be safe, it is recommended to run the container
+with the flag --shm-size=2gb.
+
+For testing the image locally, the following can be helpful:
+
+    docker build -t karma-chrome-tests ./karma-chrome-tests/
+    # Run bash in it to poke around and make sure things are properly installed
+    docker run -it --shm-size=2gb karma-chrome-tests /bin/bash
+    # Run the tests (but not capturing Gold output) with the local source repo
+    docker run --shm-size=2gb -v $SKIA_ROOT:/SRC karma-chrome-tests karma start /SRC/infra/pathkit/karma-docker.conf.js --single-run
+
+gold-karma-chrome-tests
+------------------
+
+This image has Google Chrome and karma/jasmine installed on it, which can
+be used to run JS tests.
+
+This image assumes the runner wants to collect the output images and JSON data
+specific to Skia Infra's Gold tool (image correctness).
+
+It gets manually pushed anytime there's an update to the Dockerfile or the parent
+image (karma-chrome-tests).
+
+    # Run the following from $SKIA_ROOT/infra/pathkit
+    make publish_gold_karma_chrome_tests
+
+Of note, some versions (generally before Chrome 60) run out of space on /dev/shm when
+using the default Docker settings.  To be safe, it is recommended to run the container
+with the flag --shm-size=2gb.
+
+For testing the image locally, the following can be helpful:
+
+    # Run the following from $SKIA_ROOT/infra/pathkit
+    make gold-docker-image
+    # Run bash in it to poke around and make sure things are properly installed
+    docker run -it --shm-size=2gb gold-karma-chrome-tests /bin/bash
+    # Run the tests and collect Gold output with the local source repo
+    mkdir -p -m 0777 /tmp/dockergold
+    docker run --shm-size=2gb -v $SKIA_ROOT:/SRC -v /tmp/dockergold:/OUT gold-karma-chrome-tests /SRC/infra/pathkit/test_pathkit.sh
+
+perf-karma-chrome-tests
+------------------
+
+This image has Google Chrome and karma/jasmine installed on it, which can
+be used to run JS tests.
+
+This image assumes the runner wants to collect the output images and JSON data
+specific to Skia Infra's Perf tool.
+
+It gets manually pushed anytime there's an update to the Dockerfile or the parent
+image (karma-chrome-tests).
+
+    # Run the following from $SKIA_ROOT/infra/pathkit
+    make publish_perf_karma_chrome_tests
+
+Of note, some versions (generally before Chrome 60) run out of space on /dev/shm when
+using the default Docker settings.  To be safe, it is recommended to run the container
+with the flag --shm-size=2gb.
+
+For testing the image locally, the following can be helpful:
+
+    # Run the following from $SKIA_ROOT/infra/pathkit
+    make perf-docker-image
+    # Run bash in it to poke around and make sure things are properly installed
+    docker run -it --shm-size=2gb perf-karma-chrome-tests /bin/bash
+    # Run the tests and collect Perf output with the local source repo
+    mkdir -p -m 0777 /tmp/dockerperf
+    docker run --shm-size=2gb -v $SKIA_ROOT:/SRC -v /tmp/dockerperf:/OUT perf-karma-chrome-tests /SRC/infra/pathkit/perf_pathkit.sh
diff --git a/infra/wasm-common/docker/emsdk-base/Dockerfile b/infra/wasm-common/docker/emsdk-base/Dockerfile
new file mode 100644
index 0000000..592a10c
--- /dev/null
+++ b/infra/wasm-common/docker/emsdk-base/Dockerfile
@@ -0,0 +1,28 @@
+# A Docker image that has the Emscripten SDK installed to /opt/emsdk
+# Use this image to compile C/C++ code to WASM.
+
+FROM launcher.gcr.io/google/clang-debian9
+RUN apt-get update && apt-get upgrade -y && apt-get install -y \
+  git \
+  python \
+  nodejs \
+  default-jre
+
+RUN cd /opt \
+  && git clone https://github.com/juj/emsdk.git
+
+WORKDIR /opt/emsdk
+
+RUN ./emsdk update-tags
+
+# These versions were available and worked on my local desktop as of Nov 6 2018.
+RUN ./emsdk install sdk-1.38.27-64bit
+
+RUN ./emsdk activate sdk-1.38.27-64bit
+
+RUN /bin/bash -c "source ./emsdk_env.sh"
+
+ENV EMSDK=/opt/emsdk
+
+RUN mkdir -p /OUT /SRC
+
diff --git a/infra/wasm-common/docker/gold-karma-chrome-tests/Dockerfile b/infra/wasm-common/docker/gold-karma-chrome-tests/Dockerfile
new file mode 100644
index 0000000..32f50d6
--- /dev/null
+++ b/infra/wasm-common/docker/gold-karma-chrome-tests/Dockerfile
@@ -0,0 +1,9 @@
+# Docker container with Chrome, and karma/jasmine, to be used to run JS tests and
+# collect output for Skia Infra's Gold tool (correctness checking).
+#
+# Tests will be run as non-root (user skia, in fact), so /OUT should have permissions
+# 777 so as to be able to create output there.
+
+FROM gcr.io/skia-public/karma-chrome-tests:72.0.3626.121_v1
+
+COPY /tmp/gold-aggregator /opt/gold-aggregator
\ No newline at end of file
diff --git a/infra/pathkit/docker/karma-chrome-tests/Dockerfile b/infra/wasm-common/docker/karma-chrome-tests/Dockerfile
similarity index 100%
rename from infra/pathkit/docker/karma-chrome-tests/Dockerfile
rename to infra/wasm-common/docker/karma-chrome-tests/Dockerfile
diff --git a/infra/wasm-common/docker/perf-karma-chrome-tests/Dockerfile b/infra/wasm-common/docker/perf-karma-chrome-tests/Dockerfile
new file mode 100644
index 0000000..cc7c499
--- /dev/null
+++ b/infra/wasm-common/docker/perf-karma-chrome-tests/Dockerfile
@@ -0,0 +1,9 @@
+# Docker container with Chrome, and karma/jasmine, to be used to run JS tests and
+# collect output for Skia Infra's Perf tool.
+#
+# Tests will be run as non-root (user skia, in fact), so /OUT should have permissions
+# 777 so as to be able to create output there.
+
+FROM gcr.io/skia-public/karma-chrome-tests:72.0.3626.121_v1
+
+COPY /tmp/perf-aggregator /opt/perf-aggregator
\ No newline at end of file
diff --git a/infra/wasm-common/gold/wasm_gold_aggregator.go b/infra/wasm-common/gold/wasm_gold_aggregator.go
new file mode 100644
index 0000000..e5a5f1c
--- /dev/null
+++ b/infra/wasm-common/gold/wasm_gold_aggregator.go
@@ -0,0 +1,246 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package main
+
+// This server runs along side the karma tests and listens for POST requests
+// when any test case reports it has output for Gold. See testReporter.js
+// for the browser side part.
+
+import (
+	"bytes"
+	"crypto/md5"
+	"encoding/base64"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"image"
+	"image/png"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	"path"
+	"strings"
+	"sync"
+
+	"go.skia.org/infra/golden/go/goldingestion"
+	"go.skia.org/infra/golden/go/jsonio"
+)
+
+// This allows us to use upload_dm_results.py out of the box
+const JSON_FILENAME = "dm.json"
+
+var (
+	outDir = flag.String("out_dir", "/OUT/", "location to dump the Gold JSON and pngs")
+	port   = flag.String("port", "8081", "Port to listen on.")
+
+	botId            = flag.String("bot_id", "", "swarming bot id (deprecated/unused)")
+	browser          = flag.String("browser", "Chrome", "Browser Key")
+	buildBucketID    = flag.Int64("buildbucket_build_id", 0, "Buildbucket build id key")
+	builder          = flag.String("builder", "", "Builder, like 'Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit'")
+	compiledLanguage = flag.String("compiled_language", "wasm", "wasm or asm.js")
+	config           = flag.String("config", "Release", "Configuration (e.g. Debug/Release) key")
+	gitHash          = flag.String("git_hash", "-", "The git commit hash of the version being tested")
+	hostOS           = flag.String("host_os", "Debian9", "OS Key")
+	issue            = flag.Int64("issue", 0, "issue (if tryjob)")
+	patchset         = flag.Int64("patchset", 0, "patchset (if tryjob)")
+	taskId           = flag.String("task_id", "", "swarming task id")
+	sourceType       = flag.String("source_type", "pathkit", "Gold Source type, like pathkit,canvaskit")
+)
+
+// Received from the JS side.
+type reportBody struct {
+	// e.g. "canvas" or "svg"
+	OutputType string `json:"output_type"`
+	// a base64 encoded PNG image.
+	Data string `json:"data"`
+	// a name describing the test. Should be unique enough to allow use of grep.
+	TestName string `json:"test_name"`
+}
+
+// The keys to be used at the top level for all Results.
+var defaultKeys map[string]string
+
+// contains all the results reported in through report_gold_data
+var results []*jsonio.Result
+var resultsMutex sync.Mutex
+
+func main() {
+	flag.Parse()
+
+	cpuGPU := "CPU"
+	if strings.Index(*builder, "-GPU-") != -1 {
+		cpuGPU = "GPU"
+	}
+	defaultKeys = map[string]string{
+		"arch":              "WASM",
+		"browser":           *browser,
+		"compiled_language": *compiledLanguage,
+		"compiler":          "emsdk",
+		"configuration":     *config,
+		"cpu_or_gpu":        cpuGPU,
+		"cpu_or_gpu_value":  "Browser",
+		"os":                *hostOS,
+		"source_type":       *sourceType,
+	}
+
+	results = []*jsonio.Result{}
+
+	http.HandleFunc("/report_gold_data", reporter)
+	http.HandleFunc("/dump_json", dumpJSON)
+
+	fmt.Printf("Waiting for gold ingestion on port %s\n", *port)
+
+	log.Fatal(http.ListenAndServe(":"+*port, nil))
+}
+
+// reporter handles when the client reports a test has Gold output.
+// It writes the corresponding PNG to disk and appends a Result, assuming
+// no errors.
+func reporter(w http.ResponseWriter, r *http.Request) {
+	if r.Method != "POST" {
+		http.Error(w, "Only POST accepted", 400)
+		return
+	}
+	defer r.Body.Close()
+
+	body, err := ioutil.ReadAll(r.Body)
+	if err != nil {
+		http.Error(w, "Malformed body", 400)
+		return
+	}
+
+	testOutput := reportBody{}
+	if err := json.Unmarshal(body, &testOutput); err != nil {
+		fmt.Println(err)
+		http.Error(w, "Could not unmarshal JSON", 400)
+		return
+	}
+
+	hash := ""
+	if hash, err = writeBase64EncodedPNG(testOutput.Data); err != nil {
+		fmt.Println(err)
+		http.Error(w, "Could not write image to disk", 500)
+		return
+	}
+
+	if _, err := w.Write([]byte("Accepted")); err != nil {
+		fmt.Printf("Could not write response: %s\n", err)
+		return
+	}
+
+	resultsMutex.Lock()
+ 	defer resultsMutex.Unlock()
+	results = append(results, &jsonio.Result{
+		Digest: hash,
+		Key: map[string]string{
+			"name":   testOutput.TestName,
+			"config": testOutput.OutputType,
+		},
+		Options: map[string]string{
+			"ext": "png",
+		},
+	})
+}
+
+// createOutputFile creates a file and set permissions correctly.
+func createOutputFile(p string) (*os.File, error) {
+	outputFile, err := os.Create(p)
+	if err != nil {
+		return nil, fmt.Errorf("Could not open file %s on disk: %s", p, err)
+	}
+	// Make this accessible (and deletable) by all users
+	if err = outputFile.Chmod(0666); err != nil {
+		return nil, fmt.Errorf("Could not change permissions of file %s: %s", p, err)
+	}
+	return outputFile, nil
+}
+
+// dumpJSON writes out a JSON file with all the results, typically at the end of
+// all the tests.
+func dumpJSON(w http.ResponseWriter, r *http.Request) {
+	if r.Method != "POST" {
+		http.Error(w, "Only POST accepted", 400)
+		return
+	}
+
+	p := path.Join(*outDir, JSON_FILENAME)
+	outputFile, err := createOutputFile(p)
+	defer outputFile.Close()
+	if err != nil {
+		fmt.Println(err)
+		http.Error(w, "Could not open json file on disk", 500)
+		return
+	}
+
+	dmresults := goldingestion.DMResults{
+		GoldResults: &jsonio.GoldResults{
+			BuildBucketID: *buildBucketID,
+			Builder:       *builder,
+			GitHash:       *gitHash,
+			Issue:         *issue,
+			Key:           defaultKeys,
+			Patchset:      *patchset,
+			Results:       results,
+			TaskID:        *taskId,
+		},
+	}
+
+	enc := json.NewEncoder(outputFile)
+	enc.SetIndent("", "  ") // Make it human readable.
+	if err := enc.Encode(&dmresults); err != nil {
+		fmt.Println(err)
+		http.Error(w, "Could not write json to disk", 500)
+		return
+	}
+	fmt.Println("JSON Written")
+}
+
+// writeBase64EncodedPNG writes a PNG to disk and returns the md5 of the
+// decoded PNG bytes and any error. This hash is what will be used as
+// the gold digest and the file name.
+func writeBase64EncodedPNG(data string) (string, error) {
+	// data starts with something like data:image/png;base64,[data]
+	// https://en.wikipedia.org/wiki/Data_URI_scheme
+	start := strings.Index(data, ",")
+	b := bytes.NewBufferString(data[start+1:])
+	pngReader := base64.NewDecoder(base64.StdEncoding, b)
+
+	pngBytes, err := ioutil.ReadAll(pngReader)
+	if err != nil {
+		return "", fmt.Errorf("Could not decode base 64 encoding %s", err)
+	}
+
+	// compute the hash of the pixel values, like DM does
+	img, err := png.Decode(bytes.NewBuffer(pngBytes))
+	if err != nil {
+		return "", fmt.Errorf("Not a valid png: %s", err)
+	}
+	hash := ""
+	switch img.(type) {
+	case *image.NRGBA:
+		i := img.(*image.NRGBA)
+		hash = fmt.Sprintf("%x", md5.Sum(i.Pix))
+	case *image.RGBA:
+		i := img.(*image.RGBA)
+		hash = fmt.Sprintf("%x", md5.Sum(i.Pix))
+	case *image.RGBA64:
+		i := img.(*image.RGBA64)
+		hash = fmt.Sprintf("%x", md5.Sum(i.Pix))
+	default:
+		return "", fmt.Errorf("Unknown type of image")
+	}
+
+	p := path.Join(*outDir, hash+".png")
+	outputFile, err := createOutputFile(p)
+	defer outputFile.Close()
+	if err != nil {
+		return "", fmt.Errorf("Could not create png file %s: %s", p, err)
+	}
+	if _, err = outputFile.Write(pngBytes); err != nil {
+		return "", fmt.Errorf("Could not write to file %s: %s", p, err)
+	}
+	return hash, nil
+}
diff --git a/infra/wasm-common/perf/wasm_perf_aggregator.go b/infra/wasm-common/perf/wasm_perf_aggregator.go
new file mode 100644
index 0000000..fe81984
--- /dev/null
+++ b/infra/wasm-common/perf/wasm_perf_aggregator.go
@@ -0,0 +1,209 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package main
+
+// This server runs along side the karma tests and listens for POST requests
+// when any test case reports it has output for Perf. See perfReporter.js
+// for the browser side part.
+
+// Unlike the gold ingester, the perf ingester allows multiple reports
+// of the same benchmark and will output the average of these results
+// on a call to dump
+
+import (
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	"path"
+	"strconv"
+	"strings"
+	"sync"
+
+	"github.com/google/uuid"
+	"go.skia.org/infra/perf/go/ingestcommon"
+)
+
+// upload_nano_results looks for anything*.json
+// We add the random UUID to avoid name clashes when uploading to
+// the perf bucket (which uploads to folders based on Month/Day/Hour, which can
+// easily have duplication if multiple perf tasks run in an hour.)
+var JSON_FILENAME = fmt.Sprintf("%s_browser_bench.json", uuid.New().String())
+
+var (
+	outDir = flag.String("out_dir", "/OUT/", "location to dump the Perf JSON")
+	port   = flag.String("port", "8081", "Port to listen on.")
+
+	botId            = flag.String("bot_id", "", "swarming bot id")
+	browser          = flag.String("browser", "Chrome", "Browser Key")
+	buildBucketID    = flag.Int64("buildbucket_build_id", 0, "Buildbucket build id key")
+	builder          = flag.String("builder", "", "Builder, like 'Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit'")
+	compiledLanguage = flag.String("compiled_language", "wasm", "wasm or asm.js")
+	config           = flag.String("config", "Release", "Configuration (e.g. Debug/Release) key")
+	gitHash          = flag.String("git_hash", "-", "The git commit hash of the version being tested")
+	hostOS           = flag.String("host_os", "Debian9", "OS Key")
+	issue            = flag.Int64("issue", 0, "issue (if tryjob)")
+	patch_storage    = flag.String("patch_storage", "", "patch storage (if tryjob)")
+	patchset         = flag.Int64("patchset", 0, "patchset (if tryjob)")
+	taskId           = flag.String("task_id", "", "swarming task id")
+	sourceType       = flag.String("source_type", "pathkit", "Gold Source type, like pathkit,canvaskit")
+)
+
+// Received from the JS side.
+type reportBody struct {
+	// a name describing the benchmark. Should be unique enough to allow use of grep.
+	BenchName string `json:"bench_name"`
+	// The number of microseconds of the task.
+	TimeMicroSeconds float64 `json:"time_us"`
+}
+
+// The keys to be used at the top level for all Results.
+var defaultKeys map[string]string
+
+// contains all the results reported in through report_perf_data
+var results map[string][]reportBody
+var resultsMutex sync.Mutex
+
+type BenchData struct {
+	Hash         string                               `json:"gitHash"`
+	Issue        string                               `json:"issue"`
+	PatchSet     string                               `json:"patchset"`
+	Key          map[string]string                    `json:"key"`
+	Options      map[string]string                    `json:"options,omitempty"`
+	Results      map[string]ingestcommon.BenchResults `json:"results"`
+	PatchStorage string                               `json:"patch_storage,omitempty"`
+
+	SwarmingTaskID string `json:"swarming_task_id,omitempty"`
+	SwarmingBotID  string `json:"swarming_bot_id,omitempty"`
+}
+
+func main() {
+	flag.Parse()
+
+	cpuGPU := "CPU"
+	if strings.Index(*builder, "-GPU-") != -1 {
+		cpuGPU = "GPU"
+	}
+	defaultKeys = map[string]string{
+		"arch":              "WASM",
+		"browser":           *browser,
+		"compiled_language": *compiledLanguage,
+		"compiler":          "emsdk",
+		"configuration":     *config,
+		"cpu_or_gpu":        cpuGPU,
+		"cpu_or_gpu_value":  "Browser",
+		"os":                *hostOS,
+		"source_type":       *sourceType,
+	}
+
+	results = make(map[string][]reportBody)
+
+	http.HandleFunc("/report_perf_data", reporter)
+	http.HandleFunc("/dump_json", dumpJSON)
+
+	fmt.Printf("Waiting for perf ingestion on port %s\n", *port)
+
+	log.Fatal(http.ListenAndServe(":"+*port, nil))
+}
+
+// reporter handles when the client reports a test has a benchmark.
+func reporter(w http.ResponseWriter, r *http.Request) {
+	if r.Method != "POST" {
+		http.Error(w, "Only POST accepted", 400)
+		return
+	}
+	defer r.Body.Close()
+
+	body, err := ioutil.ReadAll(r.Body)
+	if err != nil {
+		http.Error(w, "Malformed body", 400)
+		return
+	}
+
+	benchOutput := reportBody{}
+	if err := json.Unmarshal(body, &benchOutput); err != nil {
+		fmt.Println(err)
+		http.Error(w, "Could not unmarshal JSON", 400)
+		return
+	}
+
+	if _, err := w.Write([]byte("Accepted")); err != nil {
+		fmt.Printf("Could not write response: %s\n", err)
+		return
+	}
+	resultsMutex.Lock()
+ 	defer resultsMutex.Unlock()
+	results[benchOutput.BenchName] = append(results[benchOutput.BenchName], benchOutput)
+}
+
+// createOutputFile creates a file and set permissions correctly.
+func createOutputFile(p string) (*os.File, error) {
+	outputFile, err := os.Create(p)
+	if err != nil {
+		return nil, fmt.Errorf("Could not open file %s on disk: %s", p, err)
+	}
+	// Make this accessible (and deletable) by all users
+	if err = outputFile.Chmod(0666); err != nil {
+		return nil, fmt.Errorf("Could not change permissions of file %s: %s", p, err)
+	}
+	return outputFile, nil
+}
+
+// dumpJSON writes out a JSON file with all the results, typically at the end of
+// all the tests. If there is more than one result per benchmark, we report the average.
+func dumpJSON(w http.ResponseWriter, r *http.Request) {
+	if r.Method != "POST" {
+		http.Error(w, "Only POST accepted", 400)
+		return
+	}
+
+	p := path.Join(*outDir, JSON_FILENAME)
+	outputFile, err := createOutputFile(p)
+	defer outputFile.Close()
+	if err != nil {
+		fmt.Println(err)
+		http.Error(w, "Could not open json file on disk", 500)
+		return
+	}
+
+	benchData := BenchData{
+		Hash:           *gitHash,
+		Issue:          strconv.FormatInt(*issue, 10),
+		PatchStorage:   *patch_storage,
+		PatchSet:       strconv.FormatInt(*patchset, 10),
+		Key:            defaultKeys,
+		SwarmingBotID:  *botId,
+		SwarmingTaskID: *taskId,
+	}
+
+	allResults := make(map[string]ingestcommon.BenchResults)
+	for name, benches := range results {
+		samples := []float64{}
+		total := float64(0)
+		for _, t := range benches {
+			samples = append(samples, t.TimeMicroSeconds)
+			total += t.TimeMicroSeconds
+		}
+		allResults[name] = map[string]ingestcommon.BenchResult{
+			"default": map[string]interface{}{
+				"average_us": total / float64(len(benches)),
+				"samples":    samples,
+			},
+		}
+	}
+	benchData.Results = allResults
+
+	enc := json.NewEncoder(outputFile)
+	enc.SetIndent("", "  ") // Make it human readable.
+	if err := enc.Encode(&benchData); err != nil {
+		fmt.Println(err)
+		http.Error(w, "Could not write json to disk", 500)
+		return
+	}
+	fmt.Println("JSON Written")
+}
diff --git a/experimental/canvaskit/.gitignore b/modules/canvaskit/.gitignore
similarity index 100%
rename from experimental/canvaskit/.gitignore
rename to modules/canvaskit/.gitignore
diff --git a/modules/canvaskit/CHANGELOG.md b/modules/canvaskit/CHANGELOG.md
new file mode 100644
index 0000000..0b56535
--- /dev/null
+++ b/modules/canvaskit/CHANGELOG.md
@@ -0,0 +1,141 @@
+# CanvasKit Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+### Added
+ - `SkSurface.grContext` now exposed. `GrContext` has new methods for monitoring/setting
+   the cache limits; tweaking these may lead to better performance in some cases.
+   `getResourceCacheLimitBytes`, `setResourceCacheLimitBytes`, `getResourceCacheUsageBytes`
+ - `SkCanvas.drawAtlas` for efficiently drawing multiple sprites from a sprite sheet with
+   a set of transforms, color blends, etc.
+ - `SkColorBuilder`, `RSXFormBuilder`, `SkRectBuilder` which increase performance by
+   reducing the amount of malloc/free calls per frame, given that the array size is fixed.
+ - Basic `SkPicture` support. `SkSurface.captureFrameAsSkPicture` is a helper function to
+   capture an `SkPicture`, which can be dumped to disk (for debugging) with
+   `SkPicture.DEBUGONLY_saveAsFile`.
+
+### Changed
+ - Better `GrGLCaps` support for WebGL - this shouldn't have any impacts on APIs or
+   correctness, except by perhaps fixing a few bugs in various surface types.
+ - Use unsigned ints for SkColor on the JS side - this shouldn't have any impacts
+   unless clients have pre-computed colors, in which case, they will need to re-compute them.
+ - [breaking] Moved `CanvasKit.MakeImageShader` to `SkImage.makeShader` - removed clampUnpremul
+   as argument.
+
+## [0.5.1] - 2019-03-21
+
+### Added
+ - `SkPathMeasure`, `RSXFormBuilder`, `SkFont.getWidths`, `SkTextBlob.MakeFromRSXform`
+   which were needed to add the helper function `SkTextBlob.MakeOnPath`.
+ - `SkSurface.requestAnimationFrame` - wrapper around window.requestAnimationFrame that
+   takes care of the setup/tear down required to use CanvasKit optimally. The callback
+   has an `SkCanvas` as the first parameter - callers should draw on that.
+
+### Changed
+ - Location in Skia Git repo now `modules/canvaskit` (was `experimental/canvaskit`)
+
+### Fixed
+ - Extern bug in `CanvasKit.SkMatrix.invert`
+ - Fallback to CPU now properly refreshes the canvas to get access to the
+   CanvasRenderingContext2D.
+ - Compile flags for better WebGL1 support for some graphics cards.
+ - Antialias bug on large oval paths <https://crbug.com/skia/8873>
+
+### Deprecated
+ - `SkCanvas.flush` will be removed soon - client should only call `SkSurface.flush`
+
+
+## [0.5.0] - 2019-03-08
+
+### Added
+ - isVolitile option to `CanvasKit.MakeSkVertices`. The previous (and current default) behavior
+   was for this to be true; some applications may go faster if set to false.
+ - `SkCanvas.saveLayer(rect, paint)`
+ - `SkCanvas.restoreToCount(int)` which can be used with the output of .save() and .saveLayer().
+ - Optional particles library from modules/particles. `See CanvasKit.MakeParticles(json)`;
+ - More public APIs for working with Surfaces/Contexts `GetWebGLContext`,
+   `MakeGrContext`, `MakeOnScreenGLSurface`, `MakeRenderTarget`.
+ - `SkSurface.getSurface()` and `SkCanvas.getSurface()` for making compatible surfaces (typically
+   used as a workspace and then "saved" with `surface.makeImageSnapshot()`)
+
+### Breaking
+ -  `CanvasKit.MakeWebGLCanvasSurface` no longer takes a webgl context as a first arg, only a
+    canvas or an id of a canvas. If users want to manage their own GL contexts, they should build
+    the `SkSurface` themselves with `GetWebGLContext` -> `MakeGrContext` ->
+    `MakeOnScreenGLSurface`.
+
+## [0.4.1] - 2019-03-01
+
+### Added
+ - Optional arguments to `MakeManagedAnimation` for supplying external assets (like images, fonts).
+
+## [0.4.0] - 2019-02-25
+
+### Added
+ - `SkPath.addRoundRect`, `SkPath.reset`, `SkPath.rewind` exposed.
+ - `SkCanvas.drawArc`, `SkCanvas.drawLine`, `SkCanvas.drawOval`, `SkCanvas.drawRoundRect` exposed.
+ - Can import/export a SkPath to an array of commands. See `CanvasKit.MakePathFromCmds` and
+   `SkPath.toCmds`.
+ - `SkCanvas.drawTextBlob()` and `SkCanvas.SkTextBlob.MakeFromText()` to draw text to a canvas.
+ - `CanvasKit.TextEncoding` enum. For use with `SkTextBlob`.
+ - Text shaping with `ShapedText` object and `SkCanvas.drawText`. At compile time, one can choose
+   between using Harfbuzz/ICU (default) or a primitive one ("primitive_shaper") which just does
+   line breaking. Using Harfbuzz/ICU substantially increases code size (4.3 MB to 6.4 MB).
+
+### Changed
+ - `SkCanvas.drawText()` now requires an `SkFont` object for raw strings.
+
+
+### Removed
+ -  `SkPaint.setTextSize()`, `SkPaint.getTextSize()`, `SkPaint.setTypeface()`
+   which should be replaced by using `SkFont`.
+ - Deprecated `CanvasKitInit().then()` interface (see 0.3.1 notes)
+
+
+### Fixed
+ - Potential bug in `ready()` if already loaded.
+
+## [0.3.1] - 2019-01-04
+### Added
+ - `SkFont` now exposed.
+ - `MakeCanvasSurface` can now take a canvas element directly.
+ - `MakeWebGLCanvasSurface` can now take a WebGL context as an integer and use it directly.
+
+### Changed
+ - `CanvasKitInit(...).then()` is no longer the recommended way to initialize things.
+It will be removed in 0.4.0. Use `CanvasKitInit(...).ready()`, which returns a real Promise.
+
+### Removed
+- `SkPaint.measureText` - use `SkFont.measureText` instead.
+
+## [0.3.0] - 2018-12-18
+
+### Added
+- Add Canvas2D JS layer. This mirrors the HTML Canvas API. This may be omitted at compile time
+    it by adding `no_canvas` to the `compile.sh` invocation.
+- `CanvasKit.FontMgr.DefaultRef()` and `fontmgr.MakeTypefaceFromData` to load fonts.
+- Exposed `SkPath.setVolatile`. Some animations see performance improvements by setting
+their paths' volatility to true.
+
+### Fixed
+- `SkPath.addRect` now correctly draws counter-clockwise vs clockwise.
+
+### Changed
+- `CanvasKit.MakeImageShader` no longer takes encoded bytes, but an `SkImage`, created from
+    `CanvasKit.MakeImageFromEncoded`. Additionally, the optional parameters `clampIfUnpremul`
+    and `localMatrix` have been exposed.
+- `SkPath.arcTo` now takes `startAngle`, `sweepAngle`, `forceMoveTo` as additional parameters.
+- `SkPath.stroke` has a new option `precision`  It defaults to 1.0.
+- CanvasKit comes with one font (NotoMono) instead of the Skia TestTypeface. Clients are encouraged
+  to use the new `fontmgr.MakeTypefaceFromData` for more font variety.
+
+### Removed
+- `CanvasKit.initFonts()` - no longer needed.
+
+
+## [0.2.1] - 2018-11-20
+Beginning of Changelog history
diff --git a/modules/canvaskit/Makefile b/modules/canvaskit/Makefile
new file mode 100644
index 0000000..ff2e301
--- /dev/null
+++ b/modules/canvaskit/Makefile
@@ -0,0 +1,70 @@
+clean:
+	rm -rf ../../out/canvaskit_wasm
+	rm -rf ./canvaskit/bin
+	$(MAKE) release
+
+release:
+	# Does an incremental build where possible.
+	./compile.sh
+	mkdir -p ./canvaskit/bin
+	cp ../../out/canvaskit_wasm/canvaskit.js   ./canvaskit/bin
+	cp ../../out/canvaskit_wasm/canvaskit.wasm ./canvaskit/bin
+
+release_cpu:
+	# Does an incremental build where possible.
+	./compile.sh cpu_only
+	mkdir -p ./canvaskit/bin
+	cp ../../out/canvaskit_wasm/canvaskit.js   ./canvaskit/bin
+	cp ../../out/canvaskit_wasm/canvaskit.wasm ./canvaskit/bin
+
+debug:
+	# Does an incremental build where possible.
+	./compile.sh debug
+	mkdir -p ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_debug/canvaskit.js   ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_debug/canvaskit.wasm ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_debug/canvaskit.wasm.map ./canvaskit/bin
+
+debug_cpu:
+	# Does an incremental build where possible.
+	./compile.sh debug cpu_only
+	mkdir -p ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_debug/canvaskit.js   ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_debug/canvaskit.wasm ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_debug/canvaskit.wasm.map ./canvaskit/bin
+
+profile:
+	./compile.sh profiling
+	mkdir -p ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_profile/canvaskit.js       ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_profile/canvaskit.wasm     ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_profile/canvaskit.wasm.map ./canvaskit/bin
+
+profile_cpu:
+	./compile.sh profiling cpu_only
+	mkdir -p ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_profile/canvaskit.js       ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_profile/canvaskit.wasm     ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_profile/canvaskit.wasm.map ./canvaskit/bin
+
+local-example:
+	rm -rf node_modules/canvaskit
+	mkdir -p node_modules
+	ln -s -T ../canvaskit node_modules/canvaskit
+	echo "Go check out http://localhost:8000/canvaskit/example.html"
+	python serve.py
+
+test-continuous:
+	echo "Assuming npm install has been run by user"
+	echo "Also assuming make debug or release has also been run by a user (if needed)"
+	npx karma start ./karma.conf.js --no-single-run --watch-poll
+
+node-example:
+	node ./canvaskit/node.example.js --expose-wasm
+
+docker-compile:
+	mkdir -p ${SKIA_ROOT}/out/canvaskit_wasm_docker
+	docker run --rm --volume ${SKIA_ROOT}:/SRC \
+               --volume ${SKIA_ROOT}/out/canvaskit_wasm_docker:/OUT \
+               gcr.io/skia-public/canvaskit-emsdk:1.38.27_v1 \
+               /SRC/infra/canvaskit/build_canvaskit.sh
diff --git a/modules/canvaskit/WasmAliases.h b/modules/canvaskit/WasmAliases.h
new file mode 100644
index 0000000..ecab181
--- /dev/null
+++ b/modules/canvaskit/WasmAliases.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef WasmAliases_DEFINED
+#define WasmAliases_DEFINED
+
+#include <emscripten.h>
+#include <emscripten/bind.h>
+
+using namespace emscripten;
+
+// Self-documenting types
+using JSArray = emscripten::val;
+using JSObject = emscripten::val;
+using JSString = emscripten::val;
+using SkPathOrNull = emscripten::val;
+using Uint8Array = emscripten::val;
+
+#endif
diff --git a/experimental/canvaskit/canvaskit/.gitignore b/modules/canvaskit/canvaskit/.gitignore
similarity index 100%
rename from experimental/canvaskit/canvaskit/.gitignore
rename to modules/canvaskit/canvaskit/.gitignore
diff --git a/experimental/canvaskit/canvaskit/CODE_OF_CONDUCT.md b/modules/canvaskit/canvaskit/CODE_OF_CONDUCT.md
similarity index 100%
rename from experimental/canvaskit/canvaskit/CODE_OF_CONDUCT.md
rename to modules/canvaskit/canvaskit/CODE_OF_CONDUCT.md
diff --git a/experimental/canvaskit/canvaskit/CONTRIBUTING.md b/modules/canvaskit/canvaskit/CONTRIBUTING.md
similarity index 100%
rename from experimental/canvaskit/canvaskit/CONTRIBUTING.md
rename to modules/canvaskit/canvaskit/CONTRIBUTING.md
diff --git a/experimental/canvaskit/canvaskit/LICENSE b/modules/canvaskit/canvaskit/LICENSE
similarity index 100%
rename from experimental/canvaskit/canvaskit/LICENSE
rename to modules/canvaskit/canvaskit/LICENSE
diff --git a/modules/canvaskit/canvaskit/NotoSerif-Regular.ttf b/modules/canvaskit/canvaskit/NotoSerif-Regular.ttf
new file mode 100644
index 0000000..a1c6f10
--- /dev/null
+++ b/modules/canvaskit/canvaskit/NotoSerif-Regular.ttf
Binary files differ
diff --git a/modules/canvaskit/canvaskit/README.md b/modules/canvaskit/canvaskit/README.md
new file mode 100644
index 0000000..d71507e
--- /dev/null
+++ b/modules/canvaskit/canvaskit/README.md
@@ -0,0 +1,109 @@
+A WASM version of Skia's Canvas API.
+
+See https://skia.org/user/modules/canvaskit for more background information.
+
+# Getting Started
+
+## Browser
+To use the library, run `npm install canvaskit-wasm` and then simply include it:
+
+    <script src="/node_modules/canvaskit-wasm/bin/canvaskit.js"></script>
+    CanvasKitInit({
+        locateFile: (file) => '/node_modules/canvaskit-wasm/bin/'+file,
+    }).ready().then((CanvasKit) => {
+        // Code goes here using CanvasKit
+    });
+
+As with all npm packages, there's a freely available CDN via unpkg.com:
+
+    <script src="https://unpkg.com/canvaskit-wasm@0.3.0/bin/canvaskit.js"></script>
+    CanvasKitInit({
+         locateFile: (file) => 'https://unpkg.com/canvaskit-wasm@0.3.0/bin/'+file,
+    }).ready().then(...)
+
+## Node
+To use CanvasKit in Node, it's similar to the browser:
+
+    const CanvasKitInit = require('/node_modules/canvaskit-wasm/bin/canvaskit.js');
+    CanvasKitInit({
+        locateFile: (file) => __dirname + '/bin/'+file,
+    }).ready().then((CanvasKit) => {
+        // Code goes here using CanvasKit
+    });
+
+With node, you also need to supply the `--expose-wasm` flag.
+
+## WebPack
+
+WebPack's support for WASM is still somewhat experimental, but CanvasKit can be
+used with a few configuration changes.
+
+In the JS code, use require():
+
+    const CanvasKitInit = require('canvaskit-wasm/bin/canvaskit.js')
+    CanvasKitInit().ready().then((CanvasKit) => {
+        // Code goes here using CanvasKit
+    });
+
+Since WebPack does not expose the entire `/node_modules/` directory, but instead
+packages only the needed pieces, we have to copy canvaskit.wasm into the build directory.
+One such solution is to use [CopyWebpackPlugin](https://github.com/webpack-contrib/copy-webpack-plugin).
+For example, add the following plugin:
+
+    config.plugins.push(
+        new CopyWebpackPlugin([
+            { from: 'node_modules/canvaskit-wasm/bin/canvaskit.wasm' }
+        ])
+    );
+
+If webpack gives an error similar to:
+
+    ERROR in ./node_modules/canvaskit-wasm/bin/canvaskit.js
+    Module not found: Error: Can't resolve 'fs' in '...'
+
+Then, add the following configuration change to the node section of the config:
+
+    config.node = {
+        fs: 'empty'
+    };
+
+
+# Using the CanvasKit API
+
+See `example.html` and `node.example.js` for demos of how to use the core API.
+
+See `extra.html` for some optional add-ins like an animation player (Skottie)
+and a particles system.
+
+More detailed docs will be coming soon.
+
+## Drop-in Canvas2D replacement
+For environments where an HTML canvas is not available (e.g. Node, headless servers),
+CanvasKit has an optional API (included by default) that mirrors the HTML canvas.
+
+    let skcanvas = CanvasKit.MakeCanvas(600, 600);
+
+    let ctx = skcanvas.getContext('2d');
+    let rgradient = ctx.createRadialGradient(200, 300, 10, 100, 100, 300);
+
+    // Add three color stops
+    rgradient.addColorStop(0, 'red');
+    rgradient.addColorStop(0.7, 'white');
+    rgradient.addColorStop(1, 'blue');
+
+    ctx.fillStyle = rgradient;
+    ctx.globalAlpha = 0.7;
+    ctx.fillRect(0, 0, 600, 600);
+
+    let imgData = skcanvas.toDataURL();
+    // imgData is now a base64 encoded image.
+
+See more examples in `example.html` and `node.example.js`.
+
+
+# Filing bugs
+
+Please file bugs at [skbug.com](skbug.com).
+It may be convenient to use [our online fiddle](jsfiddle.skia.org/canvaskit) to demonstrate any issues encountered.
+
+See CONTRIBUTING.md for more information on sending pull requests.
\ No newline at end of file
diff --git a/experimental/canvaskit/canvaskit/Roboto-Regular.ttf b/modules/canvaskit/canvaskit/Roboto-Regular.ttf
similarity index 100%
rename from experimental/canvaskit/canvaskit/Roboto-Regular.ttf
rename to modules/canvaskit/canvaskit/Roboto-Regular.ttf
Binary files differ
diff --git a/experimental/canvaskit/canvaskit/Roboto-Regular.woff b/modules/canvaskit/canvaskit/Roboto-Regular.woff
similarity index 100%
rename from experimental/canvaskit/canvaskit/Roboto-Regular.woff
rename to modules/canvaskit/canvaskit/Roboto-Regular.woff
Binary files differ
diff --git a/modules/canvaskit/canvaskit/example.html b/modules/canvaskit/canvaskit/example.html
new file mode 100644
index 0000000..2778bd2
--- /dev/null
+++ b/modules/canvaskit/canvaskit/example.html
@@ -0,0 +1,1276 @@
+<!DOCTYPE html>
+<title>CanvasKit (Skia via Web Assembly)</title>
+<meta charset="utf-8" />
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+<style>
+  canvas, img {
+    border: 1px dashed #AAA;
+    width: 300px;
+    height: 300px;
+  }
+
+</style>
+
+<h2>Drop in replacement for HTML Canvas (e.g. node.js)</h2>
+<img id=api1 width=300 height=300>
+<canvas id=api1_c width=300 height=300></canvas>
+<img id=api2 width=300 height=300>
+<canvas id=api2_c width=300 height=300></canvas>
+<img id=api3 width=300 height=300>
+<canvas id=api3_c width=300 height=300></canvas>
+<img id=api4 width=300 height=300>
+<canvas id=api4_c width=300 height=300></canvas>
+<img id=api5 width=300 height=300>
+<canvas id=api5_c width=300 height=300></canvas>
+<img id=api6 width=300 height=300>
+<canvas id=api6_c width=300 height=300></canvas>
+<img id=api7 width=300 height=300>
+<canvas id=api7_c width=300 height=300></canvas>
+<img id=api8 width=300 height=300>
+<canvas id=api8_c width=300 height=300></canvas>
+
+<h2> CanvasKit expands the functionality of a stock HTML canvas</h2>
+<canvas id=vertex1 width=300 height=300></canvas>
+<canvas id=vertex2 width=300 height=300></canvas>
+<canvas id=gradient1 width=300 height=300></canvas>
+<canvas id=patheffect width=300 height=300></canvas>
+<canvas id=paths width=200 height=200></canvas>
+<canvas id=ink width=300 height=300></canvas>
+<canvas id=surfaces width=300 height=300></canvas>
+<canvas id=atlas width=300 height=300></canvas>
+
+<h2> CanvasKit can allow for text shaping (e.g. breaking, kerning)</h2>
+<canvas id=shape1 width=600 height=600></canvas>
+<canvas id=shape2 width=600 height=600></canvas>
+<canvas id=textonpath width=300 height=300></canvas>
+
+<script type="text/javascript" src="/node_modules/canvaskit/bin/canvaskit.js"></script>
+
+<script type="text/javascript" charset="utf-8">
+
+  var CanvasKit = null;
+
+  var robotoData = null;
+  var notoserifData = null;
+
+  var bonesImageData = null;
+  var mandrillData = null;
+  CanvasKitInit({
+    locateFile: (file) => '/node_modules/canvaskit/bin/'+file,
+  }).ready().then((CK) => {
+    CanvasKit = CK;
+    DrawingExample(CanvasKit, robotoData);
+    PathExample(CanvasKit);
+    InkExample(CanvasKit);
+
+    CanvasAPI1(CanvasKit);
+    CanvasAPI2(CanvasKit);
+    CanvasAPI3(CanvasKit);
+    CanvasAPI4(CanvasKit);
+    CanvasAPI5(CanvasKit);
+    CanvasAPI6(CanvasKit);
+    CanvasAPI7(CanvasKit);
+    CanvasAPI8(CanvasKit);
+
+    VertexAPI1(CanvasKit);
+    VertexAPI2(CanvasKit, bonesImageData);
+
+    GradiantAPI1(CanvasKit);
+
+    TextShapingAPI1(CanvasKit, notoserifData);
+    TextShapingAPI2(CanvasKit, notoserifData);
+    TextOnPathAPI1(CanvasKit);
+
+    SurfaceAPI1(CanvasKit);
+
+    AtlasAPI1(CanvasKit, mandrillData);
+  });
+
+  fetch('https://storage.googleapis.com/skia-cdn/misc/bones.jpg').then((resp) => {
+    resp.arrayBuffer().then((buffer) => {
+      bonesImageData = buffer;
+      VertexAPI2(CanvasKit, bonesImageData);
+    });
+  });
+
+  fetch('./Roboto-Regular.woff').then((resp) => {
+    resp.arrayBuffer().then((buffer) => {
+      robotoData = buffer;
+      DrawingExample(CanvasKit, robotoData);
+    });
+  });
+
+  fetch('./NotoSerif-Regular.ttf').then((resp) => {
+    resp.arrayBuffer().then((buffer) => {
+      notoserifData = buffer;
+      TextShapingAPI1(CanvasKit, notoserifData);
+      TextShapingAPI2(CanvasKit, notoserifData);
+    });
+  });
+
+  // Mandrill test image
+  fetch('./test.png').then((response) => response.arrayBuffer()).then((buffer) => {
+    mandrillData = buffer;
+    AtlasAPI1(CanvasKit, mandrillData);
+  });
+
+  function DrawingExample(CanvasKit, robotoData) {
+    if (!robotoData || !CanvasKit) {
+      return;
+    }
+    const surface = CanvasKit.MakeCanvasSurface('patheffect');
+    if (!surface) {
+      console.error('Could not make surface');
+      return;
+    }
+
+    const paint = new CanvasKit.SkPaint();
+
+    const fontMgr = CanvasKit.SkFontMgr.RefDefault();
+    const roboto = fontMgr.MakeTypefaceFromData(robotoData);
+
+    const textPaint = new CanvasKit.SkPaint();
+    textPaint.setColor(CanvasKit.RED);
+    textPaint.setAntiAlias(true);
+
+    const textFont = new CanvasKit.SkFont(roboto, 30);
+
+    let i = 0;
+
+    let X = 128;
+    let Y = 128;
+
+    function drawFrame(canvas) {
+      const path = starPath(CanvasKit, X, Y);
+      // Some animations see performance improvements by marking their
+      // paths as volatile.
+      path.setIsVolatile(true);
+      const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], i/5);
+      i++;
+
+      paint.setPathEffect(dpe);
+      paint.setStyle(CanvasKit.PaintStyle.Stroke);
+      paint.setStrokeWidth(5.0 + -3 * Math.cos(i/30));
+      paint.setAntiAlias(true);
+      paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
+
+      canvas.clear(CanvasKit.TRANSPARENT);
+
+      canvas.drawPath(path, paint);
+      canvas.drawText('Try Clicking!', 10, 280, textPaint, textFont);
+
+      dpe.delete();
+      path.delete();
+      surface.requestAnimationFrame(drawFrame);
+    }
+    surface.requestAnimationFrame(drawFrame);
+
+    // Make animation interactive
+    let interact = (e) => {
+      if (!e.pressure) {
+        return;
+      }
+      X = e.offsetX;
+      Y = e.offsetY;
+    };
+    document.getElementById('patheffect').addEventListener('pointermove', interact);
+    document.getElementById('patheffect').addEventListener('pointerdown', interact);
+    preventScrolling(document.getElementById('patheffect'));
+    // A client would need to delete this if it didn't go on for ever.
+    // paint.delete();
+    // textPaint.delete();
+    // textFont.delete();
+  }
+
+  function PathExample(CanvasKit) {
+    const surface = CanvasKit.MakeSWCanvasSurface('paths');
+    if (!surface) {
+      console.error('Could not make surface');
+      return;
+    }
+
+    function drawFrame(canvas) {
+      const paint = new CanvasKit.SkPaint();
+      paint.setStrokeWidth(1.0);
+      paint.setAntiAlias(true);
+      paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
+      paint.setStyle(CanvasKit.PaintStyle.Stroke);
+
+      const path = new CanvasKit.SkPath();
+      path.moveTo(20, 5);
+      path.lineTo(30, 20);
+      path.lineTo(40, 10);
+      path.lineTo(50, 20);
+      path.lineTo(60, 0);
+      path.lineTo(20, 5);
+
+      path.moveTo(20, 80);
+      path.cubicTo(90, 10, 160, 150, 190, 10);
+
+      path.moveTo(36, 148);
+      path.quadTo(66, 188, 120, 136);
+      path.lineTo(36, 148);
+
+      path.moveTo(150, 180);
+      path.arcTo(150, 100, 50, 200, 20);
+      path.lineTo(160, 160);
+
+      path.moveTo(20, 120);
+      path.lineTo(20, 120);
+
+      canvas.drawPath(path, paint);
+
+      let rrect = new CanvasKit.SkPath()
+                               .addRoundRect(100, 10, 140, 62,
+                                             10, 4, true);
+
+      canvas.drawPath(rrect, paint);
+
+      path.delete();
+      rrect.delete();
+      paint.delete();
+      // Intentionally just draw frame once
+    }
+    surface.requestAnimationFrame(drawFrame);
+  }
+
+  function preventScrolling(canvas) {
+    canvas.addEventListener('touchmove', (e) => {
+      // Prevents touch events in the canvas from scrolling the canvas.
+      e.preventDefault();
+      e.stopPropagation();
+    });
+  }
+
+  function InkExample(CanvasKit) {
+    const surface = CanvasKit.MakeCanvasSurface('ink');
+    if (!surface) {
+      console.error('Could not make surface');
+      return;
+    }
+
+    let paint = new CanvasKit.SkPaint();
+    paint.setAntiAlias(true);
+    paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
+    paint.setStyle(CanvasKit.PaintStyle.Stroke);
+    paint.setStrokeWidth(4.0);
+    paint.setPathEffect(CanvasKit.MakeSkCornerPathEffect(50));
+
+    // Draw I N K
+    let path = new CanvasKit.SkPath();
+    path.moveTo(80, 30);
+    path.lineTo(80, 80);
+
+    path.moveTo(100, 80);
+    path.lineTo(100, 15);
+    path.lineTo(130, 95);
+    path.lineTo(130, 30);
+
+    path.moveTo(150, 30);
+    path.lineTo(150, 80);
+    path.moveTo(170, 30);
+    path.lineTo(150, 55);
+    path.lineTo(170, 80);
+
+    let paths = [path];
+    let paints = [paint];
+
+    function drawFrame(canvas) {
+      canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
+
+      for (let i = 0; i < paints.length && i < paths.length; i++) {
+        canvas.drawPath(paths[i], paints[i]);
+      }
+
+      surface.requestAnimationFrame(drawFrame);
+    }
+
+    let hold = false;
+    let interact = (e) => {
+      let type = e.type;
+      if (type === 'lostpointercapture' || type === 'pointerup' || !e.pressure ) {
+        hold = false;
+        return;
+      }
+      if (hold) {
+        path.lineTo(e.offsetX, e.offsetY);
+      } else {
+        paint = paint.copy();
+        paint.setColor(CanvasKit.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() + .2));
+        paints.push(paint);
+        path = new CanvasKit.SkPath();
+        paths.push(path);
+        path.moveTo(e.offsetX, e.offsetY);
+      }
+      hold = true;
+    };
+    document.getElementById('ink').addEventListener('pointermove', interact);
+    document.getElementById('ink').addEventListener('pointerdown', interact);
+    document.getElementById('ink').addEventListener('lostpointercapture', interact);
+    document.getElementById('ink').addEventListener('pointerup', interact);
+    preventScrolling(document.getElementById('ink'));
+    surface.requestAnimationFrame(drawFrame);
+  }
+
+  function starPath(CanvasKit, X=128, Y=128, R=116) {
+    let p = new CanvasKit.SkPath();
+    p.moveTo(X + R, Y);
+    for (let i = 1; i < 8; i++) {
+      let a = 2.6927937 * i;
+      p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
+    }
+    return p;
+  }
+
+  function CanvasAPI1(CanvasKit) {
+    let skcanvas = CanvasKit.MakeCanvas(300, 300);
+    let realCanvas = document.getElementById('api1_c');
+
+    let skPromise   = fetch('./test.png')
+                        // if clients want to use a Blob, they are responsible
+                        // for reading it themselves.
+                        .then((response) => response.arrayBuffer())
+                        .then((buffer) => {
+                          skcanvas._img = skcanvas.decodeImage(buffer);
+                        });
+    let realPromise = fetch('./test.png')
+                        .then((response) => response.blob())
+                        .then((blob) => createImageBitmap(blob))
+                        .then((bitmap) => {
+                          realCanvas._img = bitmap;
+                        });
+
+    let realFontLoaded = new FontFace('Bungee', 'url(/tests/assets/Bungee-Regular.ttf)', {
+      'family': 'Bungee',
+      'style': 'normal',
+      'weight': '400',
+    }).load().then((font) => {
+      document.fonts.add(font);
+    });
+
+    let skFontLoaded = fetch('/tests/assets/Bungee-Regular.ttf').then(
+                             (response) => response.arrayBuffer()).then(
+                             (buffer) => {
+                                // loadFont is synchronous
+                                skcanvas.loadFont(buffer, {
+                                  'family': 'Bungee',
+                                  'style': 'normal',
+                                  'weight': '400',
+                                });
+                              });
+
+    Promise.all([realPromise, skPromise, realFontLoaded, skFontLoaded]).then(() => {
+      for (let canvas of [skcanvas, realCanvas]) {
+        let ctx = canvas.getContext('2d');
+        ctx.fillStyle = '#EEE';
+        ctx.fillRect(0, 0, 300, 300);
+        ctx.fillStyle = 'black';
+        ctx.font = '26px Bungee';
+        ctx.rotate(.1);
+        let text = ctx.measureText('Awesome');
+        ctx.fillText('Awesome ', 25, 100);
+        ctx.strokeText('Groovy!', 35+text.width, 100);
+
+        // Draw line under Awesome
+        ctx.strokeStyle = 'rgba(125,0,0,0.5)';
+        ctx.beginPath();
+        ctx.lineWidth = 6;
+        ctx.moveTo(25, 105);
+        ctx.lineTo(25 + text.width, 105);
+        ctx.stroke();
+
+        // squished vertically
+        ctx.globalAlpha = 0.7
+        ctx.imageSmoothingQuality = 'medium';
+        ctx.drawImage(canvas._img, 150, 150, 150, 100);
+        ctx.rotate(-.2);
+        ctx.imageSmoothingEnabled = false;
+        ctx.drawImage(canvas._img, 100, 150, 400, 350, 10, 200, 150, 100);
+
+        let idata = ctx.getImageData(80, 220, 40, 45);
+        ctx.putImageData(idata, 250, 10);
+        ctx.putImageData(idata, 200, 10, 20, 10, 20, 30);
+        ctx.resetTransform();
+        ctx.strokeStyle = 'black';
+        ctx.lineWidth = 1;
+        ctx.strokeRect(200, 10, 40, 45);
+
+        idata = ctx.createImageData(10, 20);
+        ctx.putImageData(idata, 10, 10);
+      }
+
+      document.getElementById('api1').src = skcanvas.toDataURL();
+      skcanvas.dispose();
+    });
+
+  }
+
+  function CanvasAPI2(CanvasKit) {
+    let skcanvas = CanvasKit.MakeCanvas(300, 300);
+    let realCanvas = document.getElementById('api2_c');
+    realCanvas.width = 300;
+    realCanvas.height = 300;
+
+    // svg data for a clock
+    skcanvas._path = skcanvas.makePath2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
+    realCanvas._path = new Path2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
+
+    for (let canvas of [skcanvas, realCanvas]) {
+      let ctx = canvas.getContext('2d');
+      ctx.scale(1.5, 1.5);
+      ctx.moveTo(20, 5);
+      ctx.lineTo(30, 20);
+      ctx.lineTo(40, 10);
+      ctx.lineTo(50, 20);
+      ctx.lineTo(60, 0);
+      ctx.lineTo(20, 5);
+
+      ctx.moveTo(20, 80);
+      ctx.bezierCurveTo(90, 10, 160, 150, 190, 10);
+
+      ctx.moveTo(36, 148);
+      ctx.quadraticCurveTo(66, 188, 120, 136);
+      ctx.lineTo(36, 148);
+
+      ctx.rect(5, 170, 20, 25);
+
+      ctx.moveTo(150, 180);
+      ctx.arcTo(150, 100, 50, 200, 20);
+      ctx.lineTo(160, 160);
+
+      ctx.moveTo(20, 120);
+      ctx.arc(20, 120, 18, 0, 1.75 * Math.PI);
+      ctx.lineTo(20, 120);
+
+      ctx.moveTo(150, 5);
+      ctx.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI);
+
+      ctx.lineWidth = 4/3;
+      ctx.stroke();
+
+      // make a clock
+      ctx.stroke(canvas._path);
+
+      // Test edgecases and draw direction
+      ctx.beginPath();
+      ctx.arc(50, 100, 10, Math.PI, -Math.PI/2);
+      ctx.stroke();
+      ctx.beginPath();
+      ctx.arc(75, 100, 10, Math.PI, -Math.PI/2, true);
+      ctx.stroke();
+      ctx.beginPath();
+      ctx.arc(100, 100, 10, Math.PI, 100.1 * Math.PI, true);
+      ctx.stroke();
+      ctx.beginPath();
+      ctx.arc(125, 100, 10, Math.PI, 100.1 * Math.PI, false);
+      ctx.stroke();
+      ctx.beginPath();
+      ctx.ellipse(155, 100, 10, 15, Math.PI/8, 100.1 * Math.PI, Math.PI, true);
+      ctx.stroke();
+      ctx.beginPath();
+      ctx.ellipse(180, 100, 10, 15, Math.PI/8, Math.PI, 100.1 * Math.PI, true);
+      ctx.stroke();
+    }
+    document.getElementById('api2').src = skcanvas.toDataURL();
+    skcanvas.dispose();
+  }
+
+  function CanvasAPI3(CanvasKit) {
+    let skcanvas = CanvasKit.MakeCanvas(300, 300);
+    let realCanvas = document.getElementById('api3_c');
+    realCanvas.width = 300;
+    realCanvas.height = 300;
+
+    for (let canvas of [skcanvas, realCanvas]) {
+      let ctx = canvas.getContext('2d');
+      ctx.rect(10, 10, 20, 20);
+
+      ctx.scale(2.0, 4.0);
+      ctx.rect(30, 10, 20, 20);
+      ctx.resetTransform();
+
+      ctx.rotate(Math.PI / 3);
+      ctx.rect(50, 10, 20, 20);
+      ctx.resetTransform();
+
+      ctx.translate(30, -2);
+      ctx.rect(70, 10, 20, 20);
+      ctx.resetTransform();
+
+      ctx.translate(60, 0);
+      ctx.rotate(Math.PI / 6);
+      ctx.transform(1.5, 0, 0, 0.5, 0, 0, 0); // effectively scale
+      ctx.rect(90, 10, 20, 20);
+      ctx.resetTransform();
+
+      ctx.save();
+      ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
+      ctx.rect(110, 10, 20, 20);
+      ctx.lineTo(110, 0);
+      ctx.restore();
+      ctx.lineTo(220, 120);
+
+      ctx.scale(3.0, 3.0);
+      ctx.font = '6pt Noto Mono';
+      ctx.fillText('This text should be huge', 10, 80);
+      ctx.resetTransform();
+
+      ctx.strokeStyle = 'black';
+      ctx.lineWidth = 2;
+      ctx.stroke();
+
+      ctx.beginPath();
+      ctx.moveTo(250, 30);
+      ctx.lineTo(250, 80);
+      ctx.scale(3.0, 3.0);
+      ctx.lineTo(280/3, 90/3);
+      ctx.closePath();
+      ctx.strokeStyle = 'black';
+      ctx.lineWidth = 5;
+      ctx.stroke();
+
+    }
+    document.getElementById('api3').src = skcanvas.toDataURL();
+    skcanvas.dispose();
+  }
+
+  function CanvasAPI4(CanvasKit) {
+    let skcanvas = CanvasKit.MakeCanvas(300, 300);
+    let realCanvas = document.getElementById('api4_c');
+    realCanvas.width = 300;
+    realCanvas.height = 300;
+
+    for (let canvas of [skcanvas, realCanvas]) {
+      let ctx = canvas.getContext('2d');
+
+      ctx.strokeStyle = '#000';
+      ctx.fillStyle = '#CCC';
+      ctx.shadowColor = 'rebeccapurple';
+      ctx.shadowBlur = 1;
+      ctx.shadowOffsetX = 3;
+      ctx.shadowOffsetY = -8;
+      ctx.rect(10, 10, 30, 30);
+
+      ctx.save();
+      ctx.strokeStyle = '#C00';
+      ctx.fillStyle = '#00C';
+      ctx.shadowBlur = 0;
+      ctx.shadowColor = 'transparent';
+
+      ctx.stroke();
+
+      ctx.restore();
+      ctx.fill();
+
+      ctx.beginPath();
+      ctx.moveTo(36, 148);
+      ctx.quadraticCurveTo(66, 188, 120, 136);
+      ctx.closePath();
+      ctx.stroke();
+
+      ctx.beginPath();
+      ctx.shadowColor = '#993366AA';
+      ctx.shadowOffsetX = 8;
+      ctx.shadowBlur = 5;
+      ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
+      ctx.rect(110, 10, 20, 20);
+      ctx.lineTo(110, 0);
+      ctx.resetTransform();
+      ctx.lineTo(220, 120);
+      ctx.stroke();
+
+      ctx.fillStyle = 'green';
+      ctx.font = '16pt Noto Mono';
+      ctx.fillText('This should be shadowed', 20, 80);
+
+      ctx.beginPath();
+      ctx.lineWidth = 6;
+      ctx.ellipse(10, 290, 30, 30, 0, 0, Math.PI * 2);
+      ctx.scale(2, 1);
+      ctx.moveTo(10, 290)
+      ctx.ellipse(10, 290, 30, 60, 0, 0, Math.PI * 2);
+      ctx.resetTransform();
+      ctx.scale(3, 1);
+      ctx.moveTo(10, 290)
+      ctx.ellipse(10, 290, 30, 90, 0, 0, Math.PI * 2);
+      ctx.stroke();
+    }
+    document.getElementById('api4').src = skcanvas.toDataURL();
+    skcanvas.dispose();
+  }
+
+  function CanvasAPI5(CanvasKit) {
+    let skcanvas = CanvasKit.MakeCanvas(600, 600);
+    let realCanvas = document.getElementById('api5_c');
+    realCanvas.width = 600;
+    realCanvas.height = 600;
+
+    for (let canvas of [skcanvas, realCanvas]) {
+      let ctx = canvas.getContext('2d');
+      ctx.scale(1.1, 1.1);
+      ctx.translate(10, 10);
+      // Shouldn't impact the fillRect calls
+      ctx.setLineDash([5, 3]);
+
+      ctx.fillStyle = 'rgba(200, 0, 100, 0.81)';
+      ctx.fillRect(20, 30, 100, 100);
+
+      ctx.globalAlpha = 0.81;
+      ctx.fillStyle = 'rgba(200, 0, 100, 1.0)';
+      ctx.fillRect(120, 30, 100, 100);
+      // This shouldn't do anything
+      ctx.globalAlpha = 0.1;
+
+      ctx.fillStyle = 'rgba(200, 0, 100, 0.9)';
+      ctx.globalAlpha = 0.9;
+      // Intentional no-op to check ordering
+      ctx.clearRect(220, 30, 100, 100);
+      ctx.fillRect(220, 30, 100, 100);
+
+      ctx.fillRect(320, 30, 100, 100);
+      ctx.clearRect(330, 40, 80, 80);
+
+      ctx.strokeStyle = 'blue';
+      ctx.lineWidth = 3;
+      ctx.setLineDash([5, 3]);
+      ctx.strokeRect(20, 150, 100, 100);
+      ctx.setLineDash([50, 30]);
+      ctx.strokeRect(125, 150, 100, 100);
+      ctx.lineDashOffset = 25;
+      ctx.strokeRect(230, 150, 100, 100);
+      ctx.setLineDash([2, 5, 9]);
+      ctx.strokeRect(335, 150, 100, 100);
+
+      ctx.setLineDash([5, 2]);
+      ctx.moveTo(336, 400);
+      ctx.quadraticCurveTo(366, 488, 120, 450);
+      ctx.lineTo(300, 400);
+      ctx.stroke();
+
+      ctx.font = '36pt Noto Mono';
+      ctx.strokeText('Dashed', 20, 350);
+      ctx.fillText('Not Dashed', 20, 400);
+
+    }
+    document.getElementById('api5').src = skcanvas.toDataURL();
+    skcanvas.dispose();
+  }
+
+  function CanvasAPI6(CanvasKit) {
+    let skcanvas = CanvasKit.MakeCanvas(600, 600);
+    let realCanvas = document.getElementById('api6_c');
+    realCanvas.width = 600;
+    realCanvas.height = 600;
+
+    for (let canvas of [skcanvas, realCanvas]) {
+      let ctx = canvas.getContext('2d');
+
+      let rgradient = ctx.createRadialGradient(200, 300, 10, 100, 100, 300);
+
+      // Add three color stops
+      rgradient.addColorStop(0, 'red');
+      rgradient.addColorStop(0.7, 'white');
+      rgradient.addColorStop(1, 'blue');
+
+      ctx.fillStyle = rgradient;
+      ctx.globalAlpha = 0.7;
+      ctx.fillRect(0, 0, 600, 600);
+      ctx.globalAlpha = 0.95;
+
+      ctx.beginPath();
+      ctx.arc(300, 100, 90, 0, Math.PI*1.66);
+      ctx.closePath();
+      ctx.strokeStyle = 'yellow';
+      ctx.lineWidth = 5;
+      ctx.stroke();
+      ctx.save();
+      ctx.clip();
+
+      let lgradient = ctx.createLinearGradient(200, 20, 420, 40);
+
+      // Add three color stops
+      lgradient.addColorStop(0, 'green');
+      lgradient.addColorStop(0.5, 'cyan');
+      lgradient.addColorStop(1, 'orange');
+
+      ctx.fillStyle = lgradient;
+
+      ctx.fillRect(200, 30, 200, 300);
+
+      ctx.restore();
+      ctx.fillRect(550, 550, 40, 40);
+
+    }
+    document.getElementById('api6').src = skcanvas.toDataURL();
+    skcanvas.dispose();
+  }
+
+  function CanvasAPI7(CanvasKit) {
+    let skcanvas = CanvasKit.MakeCanvas(300, 300);
+    let realCanvas = document.getElementById('api7_c');
+
+    let skPromise   = fetch('./test.png')
+                        // if clients want to use a Blob, they are responsible
+                        // for reading it themselves.
+                        .then((response) => response.arrayBuffer())
+                        .then((buffer) => {
+                          skcanvas._img = skcanvas.decodeImage(buffer);
+                        });
+    let realPromise = fetch('./test.png')
+                        .then((response) => response.blob())
+                        .then((blob) => createImageBitmap(blob))
+                        .then((bitmap) => {
+                          realCanvas._img = bitmap;
+                        });
+
+
+    Promise.all([realPromise, skPromise]).then(() => {
+      for (let canvas of [skcanvas, realCanvas]) {
+        let ctx = canvas.getContext('2d');
+        ctx.fillStyle = '#EEE';
+        ctx.fillRect(0, 0, 300, 300);
+        ctx.lineWidth = 20;
+        ctx.scale(0.1, 0.2);
+
+        let pattern = ctx.createPattern(canvas._img, 'repeat');
+        ctx.fillStyle = pattern;
+        ctx.fillRect(0, 0, 1500, 750);
+
+        pattern = ctx.createPattern(canvas._img, 'repeat-x');
+        ctx.fillStyle = pattern;
+        ctx.fillRect(1500, 0, 3000, 750);
+
+        ctx.globalAlpha = 0.7
+        pattern = ctx.createPattern(canvas._img, 'repeat-y');
+        ctx.fillStyle = pattern;
+        ctx.fillRect(0, 750, 1500, 1500);
+        ctx.strokeRect(0, 750, 1500, 1500);
+
+        pattern = ctx.createPattern(canvas._img, 'no-repeat');
+        ctx.fillStyle = pattern;
+        pattern.setTransform({a: 1, b: -.1, c:.1, d: 0.5, e: 1800, f:800});
+        ctx.fillRect(0, 0, 3000, 1500);
+      }
+
+      document.getElementById('api7').src = skcanvas.toDataURL();
+      skcanvas.dispose();
+    });
+  }
+
+  function CanvasAPI8(CanvasKit) {
+    let skcanvas = CanvasKit.MakeCanvas(300, 300);
+    let realCanvas = document.getElementById('api8_c');
+
+    function drawPoint(ctx, x, y, color) {
+      ctx.fillStyle = color;
+      ctx.fillRect(x, y, 1, 1);
+    }
+    const IN = 'purple';
+    const OUT = 'orange';
+    const SCALE = 4;
+
+    const pts = [[3, 3], [4, 4], [5, 5], [10, 10], [8, 10], [6, 10],
+                 [6.5, 9], [15, 10], [17, 10], [17, 11], [24, 24],
+                 [25, 25], [26, 26], [27, 27]];
+
+    const tests = [
+      {
+        xOffset: 0,
+        yOffset: 0,
+        fillType: 'nonzero',
+        strokeWidth: 0,
+        testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'nonzero'),
+      },
+      {
+        xOffset: 30,
+        yOffset: 0,
+        fillType: 'evenodd',
+        strokeWidth: 0,
+        testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'evenodd'),
+      },
+      {
+        xOffset: 0,
+        yOffset: 30,
+        fillType: null,
+        strokeWidth: 1,
+        testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
+      },
+      {
+        xOffset: 30,
+        yOffset: 30,
+        fillType: null,
+        strokeWidth: 2,
+        testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
+      },
+    ];
+
+    for (let canvas of [skcanvas, realCanvas]) {
+      let ctx = canvas.getContext('2d');
+      ctx.font = '11px Noto Mono';
+      // Draw some visual aids
+      ctx.fillText('path-nonzero', 30, 15);
+      ctx.fillText('path-evenodd', 150, 15);
+      ctx.fillText('stroke-1px-wide', 30, 130);
+      ctx.fillText('stroke-2px-wide', 150, 130);
+      ctx.fillText('purple is IN, orange is OUT', 10, 280);
+
+      // Scale up to make single pixels easier to see
+      ctx.scale(SCALE, SCALE);
+      for (let test of tests) {
+        ctx.beginPath();
+        let xOffset = test.xOffset;
+        let yOffset = test.yOffset;
+
+        ctx.fillStyle = '#AAA';
+        ctx.lineWidth = test.strokeWidth;
+        ctx.rect(5+xOffset, 5+yOffset, 20, 20);
+        ctx.arc(15+xOffset, 15+yOffset, 8, 0, Math.PI*2, false);
+        if (test.fillType) {
+          ctx.fill(test.fillType);
+        } else {
+          ctx.stroke();
+        }
+
+        for (let pt of pts) {
+          let [x, y] = pt;
+          x += xOffset;
+          y += yOffset;
+          // naively apply transform when querying because the points queried
+          // ignore the CTM.
+          if (test.testFn(ctx, x, y)) {
+            drawPoint(ctx, x, y, IN);
+          } else {
+            drawPoint(ctx, x, y, OUT);
+          }
+        }
+      }
+    }
+
+    document.getElementById('api8').src = skcanvas.toDataURL();
+    skcanvas.dispose();
+  }
+
+  function VertexAPI1(CanvasKit) {
+    const surface = CanvasKit.MakeCanvasSurface('vertex1');
+    if (!surface) {
+      console.error('Could not make surface');
+      return;
+    }
+    const canvas = surface.getCanvas();
+    let paint = new CanvasKit.SkPaint();
+
+    // See https://fiddle.skia.org/c/f48b22eaad1bb7adcc3faaa321754af6
+    // for original c++ version.
+    let points = [[ 0, 0 ], [ 250, 0 ], [ 100, 100 ], [ 0, 250 ]];
+    let colors = [CanvasKit.RED, CanvasKit.BLUE,
+                  CanvasKit.YELLOW, CanvasKit.CYAN];
+    let vertices = CanvasKit.MakeSkVertices(CanvasKit.VertexMode.TriangleFan,
+                                            points, null, colors,
+                                            false /*isVolatile*/);
+
+    canvas.drawVertices(vertices, CanvasKit.BlendMode.Src, paint);
+
+    vertices.delete();
+
+    // See https://fiddle.skia.org/c/e8bdae9bea3227758989028424fcac3d
+    // for original c++ version.
+    points   = [[ 300, 300 ], [ 50, 300 ], [ 200, 200 ], [ 300, 50 ]];
+    let texs = [[   0,   0 ], [  0, 250 ], [ 250, 250 ], [ 250,  0 ]];
+    vertices = CanvasKit.MakeSkVertices(CanvasKit.VertexMode.TriangleFan,
+                                            points, texs, colors);
+
+    let shader = CanvasKit.MakeLinearGradientShader([0, 0], [250, 0],
+            colors, null, CanvasKit.TileMode.Clamp);
+    paint.setShader(shader);
+
+    canvas.drawVertices(vertices, CanvasKit.BlendMode.Darken, paint);
+    surface.flush();
+
+    shader.delete();
+    paint.delete();
+    surface.delete();
+  }
+
+  // bonesImageData is passed in as raw, encoded bytes.
+  function VertexAPI2(CanvasKit, bonesImageData) {
+    if (!CanvasKit || !bonesImageData) {
+      return;
+    }
+    const surface = CanvasKit.MakeCanvasSurface('vertex2');
+    if (!surface) {
+      console.error('Could not make surface');
+      return;
+    }
+    let paint = new CanvasKit.SkPaint();
+    let bonesImage = CanvasKit.MakeImageFromEncoded(bonesImageData);
+
+    let shader = bonesImage.makeShader(CanvasKit.TileMode.Clamp,
+                                       CanvasKit.TileMode.Clamp);
+
+    // comment this out to see just the triangles move.
+    paint.setShader(shader);
+
+    // points is the destination location on the canvas  We want the output
+    // to be a 280x280 box (to start).
+    let points   = [[ 0, 0 ],  [ 280, 0 ], [ 280, 280 ], [ 0, 280 ]];
+    // texs is the coordinates of the source in the texture
+    // (provided by the image shader). The image is 334x226 px big.
+    let texs     = [[ 0, 0 ],  [ 334, 0 ], [ 334, 226 ], [ 0, 226 ]];
+    let boneidxs = [[1,0,0,0], [2,0,0,0],  [3,0,0,0],    [2,3,0,0]];
+    let bonewts  = [[1,0,0,0], [1,0,0,0],  [1,0,0,0],    [.5,.5,0,0]];
+    let vertices = CanvasKit.MakeSkVertices(CanvasKit.VertexMode.TriangleFan,
+                                            points, texs, null, boneidxs, bonewts);
+
+    function drawFrame(canvas) {
+      let now = Date.now();
+      let bones = [
+        [[1,0, // world bone (move 10px down and to the right to center)
+          0,1,
+          10,10]],
+        [[1,0, // identity bone (bone for vertices that are static)
+          0,1,
+          0,0]],
+        [[1,0, // ossilate in x bone
+          0,1,
+          10*Math.sin(now/500),0]],
+        [[1,0, // ossilate in y bone
+          0,1,
+          0,30*Math.cos(now/500)]],
+      ];
+      let tVerts = vertices.applyBones(bones);
+      canvas.clear(CanvasKit.TRANSPARENT);
+      canvas.drawVertices(tVerts, CanvasKit.BlendMode.Src, paint);
+
+      tVerts.delete();
+      surface.requestAnimationFrame(drawFrame);
+    }
+    surface.requestAnimationFrame(drawFrame);
+    //tVerts.delete();
+    //vertices.delete();
+
+    // bonesImage && bonesImage.delete();
+    //shader && shader.delete();
+    //paint.delete();
+    //surface.delete();
+
+  }
+
+  function GradiantAPI1(CanvasKit) {
+    const surface = CanvasKit.MakeSWCanvasSurface('gradient1');
+    if (!surface) {
+      console.error('Could not make surface');
+      return;
+    }
+    const canvas = surface.getCanvas();
+    let paint = new CanvasKit.SkPaint();
+
+    // See https://fiddle.skia.org/c/f48b22eaad1bb7adcc3faaa321754af6
+    // for original c++ version.
+    let points = [[ 0, 0 ], [ 250, 0 ], [ 100, 100 ], [ 0, 250 ]];
+    let colors = [CanvasKit.BLUE, CanvasKit.YELLOW, CanvasKit.RED];
+    let pos =    [0, .7, 1.0];
+    let transform = [2, 0, 0,
+                     0, 2, 0,
+                     0, 0, 1]
+    let shader = CanvasKit.MakeRadialGradientShader([150,150], 130, colors,
+                              pos, CanvasKit.TileMode.Mirror, transform);
+
+    paint.setShader(shader);
+    const textFont = new CanvasKit.SkFont(null, 75);
+    const textBlob = CanvasKit.SkTextBlob.MakeFromText('Radial', textFont);
+
+    canvas.drawTextBlob(textBlob, 10, 200, paint);
+    paint.delete()
+    textFont.delete();
+    textBlob.delete();
+    surface.flush();
+  }
+
+  function TextShapingAPI1(CanvasKit, notoserifData) {
+    if (!notoserifData || !CanvasKit) {
+      return;
+    }
+    const surface = CanvasKit.MakeSWCanvasSurface('shape1');
+    if (!surface) {
+      console.error('Could not make surface');
+      return;
+    }
+    const canvas = surface.getCanvas();
+    const paint = new CanvasKit.SkPaint();
+    paint.setColor(CanvasKit.BLUE);
+    paint.setStyle(CanvasKit.PaintStyle.Stroke);
+
+    const fontMgr = CanvasKit.SkFontMgr.RefDefault();
+    const notoSerif = fontMgr.MakeTypefaceFromData(notoserifData);
+
+    const textPaint = new CanvasKit.SkPaint();
+    const textFont = new CanvasKit.SkFont(notoSerif, 20);
+
+    canvas.drawRect(CanvasKit.LTRBRect(30, 30, 200, 200), paint);
+    canvas.drawText('This text is not shaped, and overflows the boundry',
+                    35, 50, textPaint, textFont);
+
+    const shapedText = new CanvasKit.ShapedText({
+      font: textFont,
+      leftToRight: true,
+      text: 'This text *is* shaped, and wraps to the right width.',
+      width: 160,
+    });
+    const textBoxX = 35;
+    const textBoxY = 55;
+    canvas.drawText(shapedText, textBoxX, textBoxY, textPaint);
+    const bounds = shapedText.getBounds();
+
+    bounds.fLeft += textBoxX;
+    bounds.fRight += textBoxX;
+    bounds.fTop += textBoxY;
+    bounds.fBottom += textBoxY
+
+    canvas.drawRect(bounds, paint);
+    const SHAPE_TEST_TEXT = 'VAVAVAVAVAFIfi';
+    const textFont2 = new CanvasKit.SkFont(notoSerif, 60);
+    const shapedText2 = new CanvasKit.ShapedText({
+      font: textFont2,
+      leftToRight: true,
+      text: SHAPE_TEST_TEXT,
+      width: 600,
+    });
+
+    canvas.drawText('no kerning ↓', 10, 240, textPaint, textFont);
+    canvas.drawText(SHAPE_TEST_TEXT, 10, 300, textPaint, textFont2);
+    canvas.drawText(shapedText2, 10, 300, textPaint);
+    canvas.drawText('kerning ↑', 10, 390, textPaint, textFont);
+
+    surface.flush();
+
+    paint.delete();
+    notoSerif.delete();
+    textPaint.delete();
+    textFont.delete();
+    shapedText.delete();
+    textFont2.delete();
+    shapedText2.delete();
+
+    surface.delete();
+  }
+
+  function TextShapingAPI2(CanvasKit, notoserifData) {
+    if (!notoserifData || !CanvasKit) {
+      return;
+    }
+    const surface = CanvasKit.MakeSWCanvasSurface('shape2');
+    if (!surface) {
+      console.error('Could not make surface');
+      return;
+    }
+    const paint = new CanvasKit.SkPaint();
+    paint.setColor(CanvasKit.BLUE);
+    paint.setStyle(CanvasKit.PaintStyle.Stroke);
+
+    const fontMgr = CanvasKit.SkFontMgr.RefDefault();
+    const notoSerif = fontMgr.MakeTypefaceFromData(notoserifData);
+
+    const textPaint = new CanvasKit.SkPaint();
+    const bigFont = new CanvasKit.SkFont(notoSerif, 40);
+    const smallFont = new CanvasKit.SkFont(notoSerif, 25);
+
+    const TEXT = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris ac leo vitae ipsum hendrerit euismod quis rutrum nibh. Quisque non suscipit urna. Donec enim urna, facilisis vitae volutpat in, mattis at elit. Sed quis augue et dolor dignissim fringilla. Sed non massa eu neque tristique malesuada. ';
+
+    let X = 240;
+    let Y = 190;
+
+    function drawFrame(canvas) {
+      canvas.clear(CanvasKit.TRANSPARENT);
+
+      const shapedText = new CanvasKit.ShapedText({
+        font: smallFont,
+        leftToRight: true,
+        text: TEXT,
+        width: (X * 2) - 10,
+      });
+
+      canvas.drawRect(CanvasKit.LTRBRect(10, 10, X*2, Y*2), paint);
+      canvas.drawText(shapedText, 10, 40, textPaint, smallFont);
+      canvas.drawText('Try Clicking!', 10, 480, textPaint, bigFont);
+
+      shapedText.delete();
+
+      surface.requestAnimationFrame(drawFrame);
+    }
+    surface.requestAnimationFrame(drawFrame);
+
+    // Make animation interactive
+    let interact = (e) => {
+      if (!e.pressure) {
+        return;
+      }
+      X = e.offsetX;
+      Y = e.offsetY;
+    };
+    document.getElementById('shape2').addEventListener('pointermove', interact);
+    document.getElementById('shape2').addEventListener('pointerdown', interact);
+    preventScrolling(document.getElementById('shape2'));
+  }
+
+  function TextOnPathAPI1(CanvasKit) {
+    const surface = CanvasKit.MakeSWCanvasSurface('textonpath');
+    if (!surface) {
+      console.error('Could not make surface');
+      return;
+    }
+    const canvas = surface.getCanvas();
+    const paint = new CanvasKit.SkPaint();
+    paint.setStyle(CanvasKit.PaintStyle.Stroke);
+    paint.setAntiAlias(true);
+
+    const font = new CanvasKit.SkFont(null, 24);
+    const fontPaint = new CanvasKit.SkPaint();
+    fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
+    fontPaint.setAntiAlias(true);
+
+    const arc = new CanvasKit.SkPath();
+    arc.arcTo(CanvasKit.LTRBRect(20, 40, 280, 300), -160, 140, true);
+    arc.lineTo(210, 140);
+    arc.arcTo(CanvasKit.LTRBRect(20, 0, 280, 260), 160, -140, true);
+
+    const str = 'This téxt should follow the curve across contours...';
+    const textBlob = CanvasKit.SkTextBlob.MakeOnPath(str, arc, font);
+
+    canvas.drawPath(arc, paint);
+    canvas.drawTextBlob(textBlob, 0, 0, fontPaint);
+
+    surface.flush();
+
+    textBlob.delete();
+    arc.delete();
+    paint.delete();
+    font.delete();
+    fontPaint.delete();
+  }
+
+  function SurfaceAPI1(CanvasKit) {
+    const surface = CanvasKit.MakeCanvasSurface('surfaces');
+    if (!surface) {
+      console.error('Could not make surface');
+      return;
+    }
+    const grContext = surface.grContext;
+
+    // create a subsurface as a temporary workspace.
+    const subSurface = surface.makeSurface({
+      width: 50,
+      height: 50,
+      alphaType: CanvasKit.AlphaType.Premul,
+      colorType: CanvasKit.ColorType.RGBA_8888,
+    });
+
+    if (!subSurface) {
+      console.error('Could not make subsurface');
+      return;
+    }
+
+    // draw a small "scene"
+    const paint = new CanvasKit.SkPaint();
+    paint.setColor(CanvasKit.Color(139, 228, 135, 0.95)); // greenish
+    paint.setStyle(CanvasKit.PaintStyle.Fill);
+    paint.setAntiAlias(true);
+
+    const subCanvas = subSurface.getCanvas();
+    subCanvas.clear(CanvasKit.BLACK);
+    subCanvas.drawRect(CanvasKit.LTRBRect(5, 15, 45, 40), paint);
+
+    paint.setColor(CanvasKit.Color(214, 93, 244)); // purplish
+    for (let i = 0; i < 10; i++) {
+      const x = Math.random() * 50;
+      const y = Math.random() * 50;
+
+      subCanvas.drawOval(CanvasKit.XYWHRect(x, y, 6, 6), paint);
+    }
+
+    // Snap it off as an SkImage - this image will be in the form the
+    // parent surface prefers (e.g. Texture for GPU / Raster for CPU).
+    const img = subSurface.makeImageSnapshot();
+
+    // clean up the temporary surface (which also cleans up subCanvas)
+    subSurface.delete();
+    paint.delete();
+
+    // Make it repeat a bunch with a shader
+    const pattern = img.makeShader(CanvasKit.TileMode.Repeat, CanvasKit.TileMode.Mirror);
+    const patternPaint = new CanvasKit.SkPaint();
+    patternPaint.setShader(pattern);
+
+    let i = 0;
+
+    function drawFrame(canvas) {
+      i++;
+      canvas.clear(CanvasKit.WHITE);
+
+      canvas.drawOval(CanvasKit.LTRBRect(i % 60, i % 60, 300 - (i% 60), 300 - (i % 60)), patternPaint);
+      surface.requestAnimationFrame(drawFrame);
+      // if (grContext) {
+      //   console.log(grContext.getResourceCacheUsageBytes() + ' bytes used in the GPU');
+      // }
+    }
+    surface.requestAnimationFrame(drawFrame);
+  }
+
+  function AtlasAPI1(CanvasKit, imgData) {
+    if (!CanvasKit || !imgData) {
+      return;
+    }
+    const surface = CanvasKit.MakeCanvasSurface('atlas');
+    if (!surface) {
+      console.error('Could not make surface');
+      return;
+    }
+    const img = CanvasKit.MakeImageFromEncoded(imgData);
+
+    const paint = new CanvasKit.SkPaint();
+    paint.setColor(CanvasKit.Color(0, 0, 0, 0.8));
+
+    const srcs = new CanvasKit.SkRectBuilder();
+    srcs.push(0, 0, 250, 250 ); // LTRB
+    srcs.push(250, 0, 500, 250 );
+
+    const dsts = new CanvasKit.RSXFormBuilder();
+    dsts.push(.5, 0, 0, 0); // scos, ssin, tx, ty
+    dsts.push(0, .8, 200, 100);
+
+    const colors = new CanvasKit.SkColorBuilder();
+    colors.push(CanvasKit.Color(85, 170, 10, 0.5)); // light green
+    colors.push(CanvasKit.Color(51, 51, 191, 0.5)); // light blue
+
+    let i = 0;
+
+    function drawFrame(canvas) {
+      canvas.clear(CanvasKit.WHITE);
+      i++;
+      let scale = 0.5 + Math.sin(i/40)/4;
+
+      // update the coordinates of existing sprites - note that this
+      // does not require a full re-copy of the full array; they are
+      // updated in-place.
+      dsts.set(0, 0.5, 0, (2*i)%200, (5*Math.round(i/200)) % 200);
+      dsts.set(1, scale*Math.sin(i/20), scale*Math.cos(i/20), 200, 100);
+
+      canvas.drawAtlas(img, srcs, dsts, paint, CanvasKit.BlendMode.Plus, colors);
+
+      // Skip drawing the 17th frame to the screen, and instead draw it to
+      // an SkPicture - a format which can be useful for debugging.
+      // The C++ version of Skia can open a SKP created by the WASM version,
+      // which can aid performance diagnosis.
+      //if (i === 17) {
+      //   const pic = surface.captureFrameAsSkPicture(drawFrame, "frame17.skp");
+      //   pic.DEBUGONLY_saveAsFile("frame17.skp");
+      //   pic.delete();
+      // } else {
+        surface.requestAnimationFrame(drawFrame);
+       // }
+    }
+    surface.requestAnimationFrame(drawFrame);
+
+  }
+</script>
diff --git a/modules/canvaskit/canvaskit/extra.html b/modules/canvaskit/canvaskit/extra.html
new file mode 100644
index 0000000..53b4788
--- /dev/null
+++ b/modules/canvaskit/canvaskit/extra.html
@@ -0,0 +1,371 @@
+<!DOCTYPE html>
+<title>CanvasKit Extra features (Skia via Web Assembly)</title>
+<meta charset="utf-8" />
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+<style>
+  canvas {
+    border: 1px dashed #AAA;
+    width: 300px;
+    height: 300px;
+  }
+
+</style>
+
+<h2> Skottie </h2>
+<canvas id=sk_legos width=300 height=300></canvas>
+<canvas id=sk_drinks width=500 height=500></canvas>
+<canvas id=sk_party width=500 height=500></canvas>
+<canvas id=sk_onboarding width=500 height=500></canvas>
+<canvas id=sk_animated_gif width=500 height=500
+        title='This is an animated gif being animated in Skottie'></canvas>
+<canvas id=sk_webfont width=500 height=500
+        title='This shows loading of a custom font (e.g. WebFont)'></canvas>
+
+<h2> Particles </h2>
+<canvas id=particles width=500 height=500></canvas>
+
+<script type="text/javascript" src="/node_modules/canvaskit/bin/canvaskit.js"></script>
+
+<script type="text/javascript" charset="utf-8">
+
+  var CanvasKit = null;
+  var legoJSON = null;
+  var drinksJSON = null;
+  var confettiJSON = null;
+  var onboardingJSON = null;
+  var multiFrameJSON = null;
+  var webfontJSON = null;
+  var fullBounds = {fLeft: 0, fTop: 0, fRight: 500, fBottom: 500};
+
+  var robotoData = null;
+  var notoserifData = null;
+
+  var bonesImageData = null;
+  var flightAnimGif = null;
+  CanvasKitInit({
+    locateFile: (file) => '/node_modules/canvaskit/bin/'+file,
+  }).ready().then((CK) => {
+    CanvasKit = CK;
+    // Set bounds to fix the 4:3 resolution of the legos
+    SkottieExample(CanvasKit, 'sk_legos', legoJSON,
+                  {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
+    // Re-size to fit
+    SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds);
+    SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds);
+    SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds);
+    SkottieExample(CanvasKit, 'sk_animated_gif', multiFrameJSON, fullBounds, {
+      'image_0.png': flightAnimGif,
+    });
+    SkottieExample(CanvasKit, 'sk_webfont', webfontJSON, fullBounds, {
+      'Roboto-Regular': robotoData,
+    });
+
+    ParticlesAPI1(CanvasKit);
+  });
+
+  fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => {
+    resp.text().then((str) => {
+      legoJSON = str;
+      SkottieExample(CanvasKit, 'sk_legos', legoJSON,
+                    {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
+    });
+  });
+
+  fetch('https://storage.googleapis.com/skia-cdn/misc/drinks.json').then((resp) => {
+    resp.text().then((str) => {
+      drinksJSON = str;
+      SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds);
+    });
+  });
+
+  fetch('https://storage.googleapis.com/skia-cdn/misc/confetti.json').then((resp) => {
+    resp.text().then((str) => {
+      confettiJSON = str;
+      SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds);
+    });
+  });
+
+  fetch('https://storage.googleapis.com/skia-cdn/misc/onboarding.json').then((resp) => {
+    resp.text().then((str) => {
+      onboardingJSON = str;
+      SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds);
+    });
+  });
+
+  fetch('https://storage.googleapis.com/skia-cdn/misc/skottie_sample_multiframe.json').then((resp) => {
+    resp.text().then((str) => {
+      multiFrameJSON = str;
+      SkottieExample(CanvasKit, 'sk_animated_gif', multiFrameJSON, fullBounds, {
+        'image_0.png': flightAnimGif,
+      });
+    });
+  });
+
+  fetch('https://storage.googleapis.com/skia-cdn/misc/skottie_sample_webfont.json').then((resp) => {
+    resp.text().then((str) => {
+      webfontJSON = str;
+      SkottieExample(CanvasKit, 'sk_webfont', webfontJSON, fullBounds, {
+        'Roboto-Regular': robotoData,
+      });
+    });
+  });
+
+  fetch('https://storage.googleapis.com/skia-cdn/misc/flightAnim.gif').then((resp) => {
+    resp.arrayBuffer().then((buffer) => {
+      flightAnimGif = buffer;
+      SkottieExample(CanvasKit, 'sk_animated_gif', multiFrameJSON, fullBounds, {
+        'image_0.png': flightAnimGif,
+      });
+    });
+  });
+
+  fetch('./Roboto-Regular.woff').then((resp) => {
+    resp.arrayBuffer().then((buffer) => {
+      robotoData = buffer;
+      SkottieExample(CanvasKit, 'sk_webfont', webfontJSON, fullBounds, {
+        'Roboto-Regular': robotoData,
+      });
+    });
+  });
+
+  function SkottieExample(CanvasKit, id, jsonStr, bounds, assets) {
+    if (!CanvasKit || !jsonStr) {
+      return;
+    }
+    const animation = CanvasKit.MakeManagedAnimation(jsonStr, assets);
+    const duration = animation.duration() * 1000;
+    const size = animation.size();
+    let c = document.getElementById(id);
+    bounds = bounds || {fLeft: 0, fTop: 0, fRight: size.w, fBottom: size.h};
+
+    // Basic managed animation test.
+    if (id === 'sk_drinks') {
+      animation.setColor('BACKGROUND_FILL', CanvasKit.Color(0, 163, 199, 1.0));
+    }
+
+    const surface = CanvasKit.MakeCanvasSurface(id);
+    if (!surface) {
+      console.error('Could not make surface');
+      return;
+    }
+
+    let firstFrame = Date.now();
+
+    function drawFrame(canvas) {
+      let seek = ((Date.now() - firstFrame) / duration) % 1.0;
+      animation.seek(seek);
+      canvas.clear(CanvasKit.WHITE);
+      animation.render(canvas, bounds);
+      surface.requestAnimationFrame(drawFrame);
+    }
+    surface.requestAnimationFrame(drawFrame);
+
+    //animation.delete();
+    return surface;
+  }
+
+  function ParticlesAPI1(CanvasKit) {
+    const surface = CanvasKit.MakeCanvasSurface('particles');
+    if (!surface) {
+      console.error('Could not make surface');
+      return;
+    }
+    const context = CanvasKit.currentContext();
+    const canvas = surface.getCanvas();
+
+    const particles = CanvasKit.MakeParticles(JSON.stringify(snowfall));
+    particles.start(Date.now() / 1000.0, true);
+
+    function drawFrame(canvas) {
+      canvas.clear(CanvasKit.BLACK);
+
+      particles.update(Date.now() / 1000.0);
+      particles.draw(canvas);
+      surface.requestAnimationFrame(drawFrame);
+    }
+    surface.requestAnimationFrame(drawFrame);
+  }
+
+const snowfall = {
+   "MaxCount": 4096,
+   "Duration": 1,
+   "Rate": 30,
+   "Life": {
+      "Input": {
+         "Source": "Age",
+         "TileMode": "Repeat",
+         "Left": 0,
+         "Right": 1
+      },
+      "XValues": [],
+      "Segments": [
+         {
+            "Type": "Constant",
+            "Ranged": false,
+            "Bidirectional": false,
+            "A0": 10
+         }
+      ]
+   },
+   "Drawable": {
+      "Type": "SkCircleDrawable",
+      "Radius": 1
+   },
+   "Spawn": [
+      {
+         "Type": "SkLinearVelocityAffector",
+         "Enabled": true,
+         "Force": false,
+         "Frame": "World",
+         "Angle": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": true,
+                  "Bidirectional": false,
+                  "A0": 170,
+                  "A1": 190
+               }
+            ]
+         },
+         "Strength": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": true,
+                  "Bidirectional": false,
+                  "A0": 10,
+                  "A1": 30
+               }
+            ]
+         }
+      },
+      {
+         "Type": "SkPositionOnPathAffector",
+         "Enabled": true,
+         "Input": {
+            "Source": "Random",
+            "TileMode": "Clamp",
+            "Left": 0,
+            "Right": 1
+         },
+         "SetHeading": false,
+         "Path": "h500"
+      }
+   ],
+   "Update": [
+      {
+         "Type": "SkSizeAffector",
+         "Enabled": true,
+         "Curve": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Cubic",
+                  "Ranged": true,
+                  "Bidirectional": false,
+                  "A0": 10,
+                  "B0": 10,
+                  "C0": 10,
+                  "D0": 0,
+                  "A1": 10,
+                  "B1": 0,
+                  "C1": 0,
+                  "D1": 0
+               }
+            ]
+         }
+      }
+   ]
+};
+
+  function SurfaceAPI1(CanvasKit) {
+    const surface = CanvasKit.MakeCanvasSurface('surfaces');
+    if (!surface) {
+      console.error('Could not make surface');
+      return;
+    }
+    const context = CanvasKit.currentContext();
+    const canvas = surface.getCanvas();
+
+    // create a subsurface as a temporary workspace.
+    const subSurface = surface.makeSurface({
+      width: 50,
+      height: 50,
+      alphaType: CanvasKit.AlphaType.Premul,
+      colorType: CanvasKit.ColorType.RGBA_8888,
+    });
+
+    if (!subSurface) {
+      console.error('Could not make subsurface');
+      return;
+    }
+
+    // draw a small "scene"
+    const paint = new CanvasKit.SkPaint();
+    paint.setColor(CanvasKit.Color(139, 228, 135, 0.95)); // greenish
+    paint.setStyle(CanvasKit.PaintStyle.Fill);
+    paint.setAntiAlias(true);
+
+    const subCanvas = subSurface.getCanvas();
+    subCanvas.clear(CanvasKit.BLACK);
+    subCanvas.drawRect(CanvasKit.LTRBRect(5, 15, 45, 40), paint);
+
+    paint.setColor(CanvasKit.Color(214, 93, 244)); // purplish
+    for (let i = 0; i < 10; i++) {
+      const x = Math.random() * 50;
+      const y = Math.random() * 50;
+
+      subCanvas.drawOval(CanvasKit.XYWHRect(x, y, 6, 6), paint);
+    }
+
+    // Snap it off as an SkImage - this image will be in the form the
+    // parent surface prefers (e.g. Texture for GPU / Raster for CPU).
+    const img = subSurface.makeImageSnapshot();
+
+    // clean up the temporary surface
+    subSurface.delete();
+    paint.delete();
+
+    // Make it repeat a bunch with a shader
+    const pattern = img.makeShader(CanvasKit.TileMode.Repeat, CanvasKit.TileMode.Mirror);
+    const patternPaint = new CanvasKit.SkPaint();
+    patternPaint.setShader(pattern);
+
+    let i = 0;
+
+    function drawFrame() {
+      i++;
+      CanvasKit.setCurrentContext(context);
+      canvas.clear(CanvasKit.WHITE);
+
+      canvas.drawOval(CanvasKit.LTRBRect(i % 60, i % 60, 300 - (i% 60), 300 - (i % 60)), patternPaint);
+      surface.flush();
+      window.requestAnimationFrame(drawFrame);
+    }
+    window.requestAnimationFrame(drawFrame);
+
+  }
+</script>
diff --git a/modules/canvaskit/canvaskit/node.example.js b/modules/canvaskit/canvaskit/node.example.js
new file mode 100644
index 0000000..3d227753
--- /dev/null
+++ b/modules/canvaskit/canvaskit/node.example.js
@@ -0,0 +1,114 @@
+const CanvasKitInit = require('./bin/canvaskit.js');
+const fs = require('fs');
+const path = require('path');
+
+CanvasKitInit({
+  locateFile: (file) => __dirname + '/bin/'+file,
+}).ready().then((CanvasKit) => {
+  let canvas = CanvasKit.MakeCanvas(300, 300);
+
+  let img = fs.readFileSync(path.join(__dirname, 'test.png'));
+  img = canvas.decodeImage(img);
+
+  let fontData = fs.readFileSync(path.join(__dirname, './Roboto-Regular.woff'));
+  canvas.loadFont(fontData, {
+                                'family': 'Roboto',
+                                'style': 'normal',
+                                'weight': '400',
+                              });
+
+  let ctx = canvas.getContext('2d');
+  ctx.font = '30px Roboto';
+  ctx.rotate(.1);
+  let text = ctx.measureText('Awesome');
+  ctx.fillText('Awesome ', 50, 100);
+  ctx.strokeText('Groovy!', 60+text.width, 100);
+
+  // Draw line under Awesome
+  ctx.strokeStyle = 'rgba(125,0,0,0.5)';
+  ctx.beginPath();
+  ctx.lineWidth = 6;
+  ctx.lineTo(50, 102);
+  ctx.lineTo(50 + text.width, 102);
+  ctx.stroke();
+
+  // squished vertically
+  ctx.globalAlpha = 0.7
+  ctx.imageSmoothingQuality = 'medium';
+  ctx.drawImage(img, 150, 150, 150, 100);
+  ctx.rotate(-.2);
+  ctx.imageSmoothingEnabled = false;
+  ctx.drawImage(img, 100, 150, 400, 350, 10, 200, 150, 100);
+
+  console.log('<img src="' + canvas.toDataURL() + '" />');
+
+  fancyAPI(CanvasKit);
+});
+
+function fancyAPI(CanvasKit) {
+  let surface = CanvasKit.MakeSurface(300, 300);
+  const canvas = surface.getCanvas();
+
+  const paint = new CanvasKit.SkPaint();
+
+  const fontMgr = CanvasKit.SkFontMgr.RefDefault();
+  let robotoData = fs.readFileSync(path.join(__dirname, './Roboto-Regular.woff'));
+  const roboto = fontMgr.MakeTypefaceFromData(robotoData);
+
+  const textPaint = new CanvasKit.SkPaint();
+  textPaint.setColor(CanvasKit.Color(40, 0, 0));
+  textPaint.setAntiAlias(true);
+
+  const textFont = new CanvasKit.SkFont(roboto, 30);
+
+  const skpath = starPath(CanvasKit);
+  const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], 1);
+
+  paint.setPathEffect(dpe);
+  paint.setStyle(CanvasKit.PaintStyle.Stroke);
+  paint.setStrokeWidth(5.0);
+  paint.setAntiAlias(true);
+  paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
+
+  canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
+
+  canvas.drawPath(skpath, paint);
+  canvas.drawText('Try Clicking!', 10, 280, textPaint, textFont);
+
+  surface.flush();
+
+  const img = surface.makeImageSnapshot()
+  if (!img) {
+    console.error('no snapshot');
+    return;
+  }
+  const png = img.encodeToData()
+  if (!png) {
+    console.error('encoding failure');
+    return
+  }
+  const pngBytes = CanvasKit.getSkDataBytes(png);
+  // See https://stackoverflow.com/a/12713326
+  let b64encoded = Buffer.from(pngBytes).toString('base64');
+  console.log(`<img src="data:image/png;base64,${b64encoded}" />`);
+
+  // These delete calls free up memeory in the C++ WASM memory block.
+  dpe.delete();
+  skpath.delete();
+  textPaint.delete();
+  paint.delete();
+  roboto.delete();
+  textFont.delete();
+
+  surface.dispose();
+}
+
+function starPath(CanvasKit, X=128, Y=128, R=116) {
+  let p = new CanvasKit.SkPath();
+  p.moveTo(X + R, Y);
+  for (let i = 1; i < 8; i++) {
+    let a = 2.6927937 * i;
+    p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
+  }
+  return p;
+}
\ No newline at end of file
diff --git a/modules/canvaskit/canvaskit/package.json b/modules/canvaskit/canvaskit/package.json
new file mode 100644
index 0000000..66d7c3d
--- /dev/null
+++ b/modules/canvaskit/canvaskit/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "canvaskit-wasm",
+  "version": "0.5.1",
+  "description": "A WASM version of Skia's Canvas API",
+  "main": "bin/canvaskit.js",
+  "homepage": "https://github.com/google/skia/tree/master/modules/canvaskit",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "license": "BSD-3-Clause"
+}
diff --git a/experimental/canvaskit/canvaskit/test.png b/modules/canvaskit/canvaskit/test.png
similarity index 100%
rename from experimental/canvaskit/canvaskit/test.png
rename to modules/canvaskit/canvaskit/test.png
Binary files differ
diff --git a/modules/canvaskit/canvaskit_bindings.cpp b/modules/canvaskit/canvaskit_bindings.cpp
new file mode 100644
index 0000000..832e37e
--- /dev/null
+++ b/modules/canvaskit/canvaskit_bindings.cpp
@@ -0,0 +1,1329 @@
+/*
+ * Copyright 2018 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBlendMode.h"
+#include "SkBlurTypes.h"
+#include "SkCanvas.h"
+#include "SkColor.h"
+#include "SkCornerPathEffect.h"
+#include "SkDashPathEffect.h"
+#include "SkData.h"
+#include "SkDiscretePathEffect.h"
+#include "SkEncodedImageFormat.h"
+#include "SkFilterQuality.h"
+#include "SkFont.h"
+#include "SkFontMgr.h"
+#include "SkFontMgrPriv.h"
+#include "SkFontTypes.h"
+#include "SkGradientShader.h"
+#include "SkImage.h"
+#include "SkImageInfo.h"
+#include "SkMakeUnique.h"
+#include "SkMaskFilter.h"
+#include "SkPaint.h"
+#include "SkParsePath.h"
+#include "SkPath.h"
+#include "SkPathEffect.h"
+#include "SkPathMeasure.h"
+#include "SkPathOps.h"
+#include "SkPicture.h"
+#include "SkPictureRecorder.h"
+#include "SkScalar.h"
+#include "SkShader.h"
+#include "SkShadowUtils.h"
+#include "SkShaper.h"
+#include "SkString.h"
+#include "SkStrokeRec.h"
+#include "SkSurface.h"
+#include "SkSurfaceProps.h"
+#include "SkTextBlob.h"
+#include "SkTrimPathEffect.h"
+#include "SkTypeface.h"
+#include "SkTypes.h"
+#include "SkVertices.h"
+
+#include <iostream>
+#include <string>
+
+#include "WasmAliases.h"
+#include <emscripten.h>
+#include <emscripten/bind.h>
+
+#if SK_SUPPORT_GPU
+#include "GrBackendSurface.h"
+#include "GrContext.h"
+#include "GrGLInterface.h"
+#include "GrGLTypes.h"
+
+#include <GL/gl.h>
+#include <emscripten/html5.h>
+#endif
+
+// Aliases for less typing
+using BoneIndices = SkVertices::BoneIndices;
+using BoneWeights = SkVertices::BoneWeights;
+using Bone        = SkVertices::Bone;
+
+struct SimpleMatrix {
+    SkScalar scaleX, skewX,  transX;
+    SkScalar skewY,  scaleY, transY;
+    SkScalar pers0,  pers1,  pers2;
+};
+
+SkMatrix toSkMatrix(const SimpleMatrix& sm) {
+    return SkMatrix::MakeAll(sm.scaleX, sm.skewX , sm.transX,
+                             sm.skewY , sm.scaleY, sm.transY,
+                             sm.pers0 , sm.pers1 , sm.pers2);
+}
+
+SimpleMatrix toSimpleSkMatrix(const SkMatrix& sm) {
+    SimpleMatrix m {sm[0], sm[1], sm[2],
+                    sm[3], sm[4], sm[5],
+                    sm[6], sm[7], sm[8]};
+    return m;
+}
+
+struct SimpleImageInfo {
+    int width;
+    int height;
+    SkColorType colorType;
+    SkAlphaType alphaType;
+    // TODO color spaces?
+};
+
+SkImageInfo toSkImageInfo(const SimpleImageInfo& sii) {
+    return SkImageInfo::Make(sii.width, sii.height, sii.colorType, sii.alphaType);
+}
+
+#if SK_SUPPORT_GPU
+sk_sp<GrContext> MakeGrContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context)
+{
+    EMSCRIPTEN_RESULT r = emscripten_webgl_make_context_current(context);
+    if (r < 0) {
+        printf("failed to make webgl context current %d\n", r);
+        return nullptr;
+    }
+    // setup GrContext
+    auto interface = GrGLMakeNativeInterface();
+    // setup contexts
+    sk_sp<GrContext> grContext(GrContext::MakeGL(interface));
+    return grContext;
+}
+
+sk_sp<SkSurface> MakeOnScreenGLSurface(sk_sp<GrContext> grContext, int width, int height) {
+    glClearColor(0, 0, 0, 0);
+    glClearStencil(0);
+    glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+
+    // Wrap the frame buffer object attached to the screen in a Skia render
+    // target so Skia can render to it
+    GrGLint buffer;
+    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &buffer);
+    GrGLFramebufferInfo info;
+    info.fFBOID = (GrGLuint) buffer;
+    SkColorType colorType;
+
+    info.fFormat = GL_RGBA8;
+    colorType = kRGBA_8888_SkColorType;
+
+    GrBackendRenderTarget target(width, height, 0, 8, info);
+
+    sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(grContext.get(), target,
+                                                                    kBottomLeft_GrSurfaceOrigin,
+                                                                    colorType, nullptr, nullptr));
+    return surface;
+}
+
+sk_sp<SkSurface> MakeRenderTarget(sk_sp<GrContext> grContext, int width, int height) {
+    SkImageInfo info = SkImageInfo::MakeN32(width, height, SkAlphaType::kPremul_SkAlphaType);
+
+    sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(grContext.get(),
+                             SkBudgeted::kYes,
+                             info, 0,
+                             kBottomLeft_GrSurfaceOrigin,
+                             nullptr, true));
+    return surface;
+}
+
+sk_sp<SkSurface> MakeRenderTarget(sk_sp<GrContext> grContext, SimpleImageInfo sii) {
+    sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(grContext.get(),
+                             SkBudgeted::kYes,
+                             toSkImageInfo(sii), 0,
+                             kBottomLeft_GrSurfaceOrigin,
+                             nullptr, true));
+    return surface;
+}
+#endif
+
+
+//========================================================================================
+// Path things
+//========================================================================================
+
+// All these Apply* methods are simple wrappers to avoid returning an object.
+// The default WASM bindings produce code that will leak if a return value
+// isn't assigned to a JS variable and has delete() called on it.
+// These Apply methods, combined with the smarter binding code allow for chainable
+// commands that don't leak if the return value is ignored (i.e. when used intuitively).
+
+void ApplyAddArc(SkPath& orig, const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
+    orig.addArc(oval, startAngle, sweepAngle);
+}
+
+void ApplyAddPath(SkPath& orig, const SkPath& newPath,
+                   SkScalar scaleX, SkScalar skewX,  SkScalar transX,
+                   SkScalar skewY,  SkScalar scaleY, SkScalar transY,
+                   SkScalar pers0, SkScalar pers1, SkScalar pers2,
+                   bool extendPath) {
+    SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
+                                   skewY , scaleY, transY,
+                                   pers0 , pers1 , pers2);
+    orig.addPath(newPath, m, extendPath ? SkPath::kExtend_AddPathMode :
+                                          SkPath::kAppend_AddPathMode);
+}
+
+void ApplyAddRect(SkPath& path, SkScalar left, SkScalar top,
+                  SkScalar right, SkScalar bottom, bool ccw) {
+    path.addRect(left, top, right, bottom,
+                 ccw ? SkPath::Direction::kCCW_Direction :
+                 SkPath::Direction::kCW_Direction);
+}
+
+void ApplyAddRoundRect(SkPath& path, SkScalar left, SkScalar top,
+                  SkScalar right, SkScalar bottom, uintptr_t /* SkScalar*  */ rPtr,
+                  bool ccw) {
+    // See comment below for uintptr_t explanation
+    const SkScalar* radii = reinterpret_cast<const SkScalar*>(rPtr);
+    path.addRoundRect(SkRect::MakeLTRB(left, top, right, bottom), radii,
+                      ccw ? SkPath::Direction::kCCW_Direction : SkPath::Direction::kCW_Direction);
+}
+
+
+void ApplyArcTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
+                SkScalar radius) {
+    p.arcTo(x1, y1, x2, y2, radius);
+}
+
+void ApplyArcToAngle(SkPath& p, SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool forceMoveTo) {
+    p.arcTo(oval, startAngle, sweepAngle, forceMoveTo);
+}
+
+void ApplyClose(SkPath& p) {
+    p.close();
+}
+
+void ApplyConicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
+                  SkScalar w) {
+    p.conicTo(x1, y1, x2, y2, w);
+}
+
+void ApplyCubicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
+                  SkScalar x3, SkScalar y3) {
+    p.cubicTo(x1, y1, x2, y2, x3, y3);
+}
+
+void ApplyLineTo(SkPath& p, SkScalar x, SkScalar y) {
+    p.lineTo(x, y);
+}
+
+void ApplyMoveTo(SkPath& p, SkScalar x, SkScalar y) {
+    p.moveTo(x, y);
+}
+
+void ApplyReset(SkPath& p) {
+    p.reset();
+}
+
+void ApplyRewind(SkPath& p) {
+    p.rewind();
+}
+
+void ApplyQuadTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
+    p.quadTo(x1, y1, x2, y2);
+}
+
+void ApplyTransform(SkPath& orig,
+                    SkScalar scaleX, SkScalar skewX,  SkScalar transX,
+                    SkScalar skewY,  SkScalar scaleY, SkScalar transY,
+                    SkScalar pers0, SkScalar pers1, SkScalar pers2) {
+    SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
+                                   skewY , scaleY, transY,
+                                   pers0 , pers1 , pers2);
+    orig.transform(m);
+}
+
+bool EMSCRIPTEN_KEEPALIVE ApplySimplify(SkPath& path) {
+    return Simplify(path, &path);
+}
+
+bool EMSCRIPTEN_KEEPALIVE ApplyPathOp(SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
+    return Op(pathOne, pathTwo, op, &pathOne);
+}
+
+JSString EMSCRIPTEN_KEEPALIVE ToSVGString(const SkPath& path) {
+    SkString s;
+    SkParsePath::ToSVGString(path, &s);
+    return emscripten::val(s.c_str());
+}
+
+SkPathOrNull EMSCRIPTEN_KEEPALIVE MakePathFromSVGString(std::string str) {
+    SkPath path;
+    if (SkParsePath::FromSVGString(str.c_str(), &path)) {
+        return emscripten::val(path);
+    }
+    return emscripten::val::null();
+}
+
+SkPathOrNull EMSCRIPTEN_KEEPALIVE MakePathFromOp(const SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
+    SkPath out;
+    if (Op(pathOne, pathTwo, op, &out)) {
+        return emscripten::val(out);
+    }
+    return emscripten::val::null();
+}
+
+SkPath EMSCRIPTEN_KEEPALIVE CopyPath(const SkPath& a) {
+    SkPath copy(a);
+    return copy;
+}
+
+bool EMSCRIPTEN_KEEPALIVE Equals(const SkPath& a, const SkPath& b) {
+    return a == b;
+}
+
+// =================================================================================
+// Creating/Exporting Paths with cmd arrays
+// =================================================================================
+
+static const int MOVE = 0;
+static const int LINE = 1;
+static const int QUAD = 2;
+static const int CONIC = 3;
+static const int CUBIC = 4;
+static const int CLOSE = 5;
+
+template <typename VisitFunc>
+void VisitPath(const SkPath& p, VisitFunc&& f) {
+    SkPath::RawIter iter(p);
+    SkPoint pts[4];
+    SkPath::Verb verb;
+    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+        f(verb, pts, iter);
+    }
+}
+
+JSArray EMSCRIPTEN_KEEPALIVE ToCmds(const SkPath& path) {
+    JSArray cmds = emscripten::val::array();
+
+    VisitPath(path, [&cmds](SkPath::Verb verb, const SkPoint pts[4], SkPath::RawIter iter) {
+        JSArray cmd = emscripten::val::array();
+        switch (verb) {
+        case SkPath::kMove_Verb:
+            cmd.call<void>("push", MOVE, pts[0].x(), pts[0].y());
+            break;
+        case SkPath::kLine_Verb:
+            cmd.call<void>("push", LINE, pts[1].x(), pts[1].y());
+            break;
+        case SkPath::kQuad_Verb:
+            cmd.call<void>("push", QUAD, pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
+            break;
+        case SkPath::kConic_Verb:
+            cmd.call<void>("push", CONIC,
+                           pts[1].x(), pts[1].y(),
+                           pts[2].x(), pts[2].y(), iter.conicWeight());
+            break;
+        case SkPath::kCubic_Verb:
+            cmd.call<void>("push", CUBIC,
+                           pts[1].x(), pts[1].y(),
+                           pts[2].x(), pts[2].y(),
+                           pts[3].x(), pts[3].y());
+            break;
+        case SkPath::kClose_Verb:
+            cmd.call<void>("push", CLOSE);
+            break;
+        case SkPath::kDone_Verb:
+            SkASSERT(false);
+            break;
+        }
+        cmds.call<void>("push", cmd);
+    });
+    return cmds;
+}
+
+// This type signature is a mess, but it's necessary. See, we can't use "bind" (EMSCRIPTEN_BINDINGS)
+// and pointers to primitive types (Only bound types like SkPoint). We could if we used
+// cwrap (see https://becominghuman.ai/passing-and-returning-webassembly-array-parameters-a0f572c65d97)
+// but that requires us to stick to C code and, AFAIK, doesn't allow us to return nice things like
+// SkPath or SkOpBuilder.
+//
+// So, basically, if we are using C++ and EMSCRIPTEN_BINDINGS, we can't have primative pointers
+// in our function type signatures. (this gives an error message like "Cannot call foo due to unbound
+// types Pi, Pf").  But, we can just pretend they are numbers and cast them to be pointers and
+// the compiler is happy.
+SkPathOrNull EMSCRIPTEN_KEEPALIVE MakePathFromCmds(uintptr_t /* float* */ cptr, int numCmds) {
+    const auto* cmds = reinterpret_cast<const float*>(cptr);
+    SkPath path;
+    float x1, y1, x2, y2, x3, y3;
+
+    // if there are not enough arguments, bail with the path we've constructed so far.
+    #define CHECK_NUM_ARGS(n) \
+        if ((i + n) > numCmds) { \
+            SkDebugf("Not enough args to match the verbs. Saw %d commands\n", numCmds); \
+            return emscripten::val::null(); \
+        }
+
+    for(int i = 0; i < numCmds;){
+         switch (sk_float_floor2int(cmds[i++])) {
+            case MOVE:
+                CHECK_NUM_ARGS(2);
+                x1 = cmds[i++], y1 = cmds[i++];
+                path.moveTo(x1, y1);
+                break;
+            case LINE:
+                CHECK_NUM_ARGS(2);
+                x1 = cmds[i++], y1 = cmds[i++];
+                path.lineTo(x1, y1);
+                break;
+            case QUAD:
+                CHECK_NUM_ARGS(4);
+                x1 = cmds[i++], y1 = cmds[i++];
+                x2 = cmds[i++], y2 = cmds[i++];
+                path.quadTo(x1, y1, x2, y2);
+                break;
+            case CONIC:
+                CHECK_NUM_ARGS(5);
+                x1 = cmds[i++], y1 = cmds[i++];
+                x2 = cmds[i++], y2 = cmds[i++];
+                x3 = cmds[i++]; // weight
+                path.conicTo(x1, y1, x2, y2, x3);
+                break;
+            case CUBIC:
+                CHECK_NUM_ARGS(6);
+                x1 = cmds[i++], y1 = cmds[i++];
+                x2 = cmds[i++], y2 = cmds[i++];
+                x3 = cmds[i++], y3 = cmds[i++];
+                path.cubicTo(x1, y1, x2, y2, x3, y3);
+                break;
+            case CLOSE:
+                path.close();
+                break;
+            default:
+                SkDebugf("  path: UNKNOWN command %f, aborting dump...\n", cmds[i-1]);
+                return emscripten::val::null();
+        }
+    }
+
+    #undef CHECK_NUM_ARGS
+
+    return emscripten::val(path);
+}
+
+//========================================================================================
+// Path Effects
+//========================================================================================
+
+bool ApplyDash(SkPath& path, SkScalar on, SkScalar off, SkScalar phase) {
+    SkScalar intervals[] = { on, off };
+    auto pe = SkDashPathEffect::Make(intervals, 2, phase);
+    if (!pe) {
+        SkDebugf("Invalid args to dash()\n");
+        return false;
+    }
+    SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
+    if (pe->filterPath(&path, path, &rec, nullptr)) {
+        return true;
+    }
+    SkDebugf("Could not make dashed path\n");
+    return false;
+}
+
+bool ApplyTrim(SkPath& path, SkScalar startT, SkScalar stopT, bool isComplement) {
+    auto mode = isComplement ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal;
+    auto pe = SkTrimPathEffect::Make(startT, stopT, mode);
+    if (!pe) {
+        SkDebugf("Invalid args to trim(): startT and stopT must be in [0,1]\n");
+        return false;
+    }
+    SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
+    if (pe->filterPath(&path, path, &rec, nullptr)) {
+        return true;
+    }
+    SkDebugf("Could not trim path\n");
+    return false;
+}
+
+struct StrokeOpts {
+    // Default values are set in interface.js which allows clients
+    // to set any number of them. Otherwise, the binding code complains if
+    // any are omitted.
+    SkScalar width;
+    SkScalar miter_limit;
+    SkPaint::Join join;
+    SkPaint::Cap cap;
+    float precision;
+};
+
+bool ApplyStroke(SkPath& path, StrokeOpts opts) {
+    SkPaint p;
+    p.setStyle(SkPaint::kStroke_Style);
+    p.setStrokeCap(opts.cap);
+    p.setStrokeJoin(opts.join);
+    p.setStrokeWidth(opts.width);
+    p.setStrokeMiter(opts.miter_limit);
+
+    return p.getFillPath(path, &path, nullptr, opts.precision);
+}
+
+// to map from raw memory to a uint8array
+Uint8Array getSkDataBytes(const SkData *data) {
+    return Uint8Array(typed_memory_view(data->size(), data->bytes()));
+}
+
+// Text Shaping abstraction
+
+struct ShapedTextOpts {
+    SkFont font;
+    bool leftToRight;
+    std::string text;
+    SkScalar width;
+};
+
+std::unique_ptr<SkShaper> shaper;
+
+static sk_sp<SkTextBlob> do_shaping(const ShapedTextOpts& opts, SkPoint* pt) {
+    SkTextBlobBuilderRunHandler builder(opts.text.c_str(), {0, 0});
+    if (!shaper) {
+        shaper = SkShaper::Make();
+    }
+    shaper->shape(opts.text.c_str(), opts.text.length(),
+                  opts.font, opts.leftToRight,
+                  opts.width, &builder);
+    *pt = builder.endPoint();
+    return builder.makeBlob();
+}
+
+class ShapedText {
+public:
+    ShapedText(ShapedTextOpts opts) : fOpts(opts) {}
+
+    SkRect getBounds() {
+        this->init();
+        return SkRect::MakeLTRB(0, 0, fOpts.width, fPoint.y());
+    }
+
+    SkTextBlob* blob() {
+        this->init();
+        return fBlob.get();
+    }
+private:
+    const ShapedTextOpts fOpts;
+    SkPoint fPoint;
+    sk_sp<SkTextBlob> fBlob;
+
+    void init() {
+        if (!fBlob) {
+            fBlob = do_shaping(fOpts, &fPoint);
+        }
+    }
+};
+
+void drawShapedText(SkCanvas& canvas, ShapedText st, SkScalar x,
+                     SkScalar y, SkPaint paint) {
+    canvas.drawTextBlob(st.blob(), x, y, paint);
+}
+
+// This is simpler than dealing with an SkPoint and SkVector
+struct PosTan {
+    SkScalar px, py, tx, ty;
+};
+
+// These objects have private destructors / delete mthods - I don't think
+// we need to do anything other than tell emscripten to do nothing.
+namespace emscripten {
+    namespace internal {
+        template<typename ClassType>
+        void raw_destructor(ClassType *);
+
+        template<>
+        void raw_destructor<SkData>(SkData *ptr) {
+        }
+
+        template<>
+        void raw_destructor<SkTypeface>(SkTypeface *ptr) {
+        }
+
+        template<>
+        void raw_destructor<SkVertices>(SkVertices *ptr) {
+        }
+
+        template<>
+        void raw_destructor<SkTextBlob>(SkTextBlob *ptr) {
+        }
+    }
+}
+
+// Some timesignatures below have uintptr_t instead of a pointer to a primative
+// type (e.g. SkScalar). This is necessary because we can't use "bind" (EMSCRIPTEN_BINDINGS)
+// and pointers to primitive types (Only bound types like SkPoint). We could if we used
+// cwrap (see https://becominghuman.ai/passing-and-returning-webassembly-array-parameters-a0f572c65d97)
+// but that requires us to stick to C code and, AFAIK, doesn't allow us to return nice things like
+// SkPath or SkCanvas.
+//
+// So, basically, if we are using C++ and EMSCRIPTEN_BINDINGS, we can't have primative pointers
+// in our function type signatures. (this gives an error message like "Cannot call foo due to unbound
+// types Pi, Pf").  But, we can just pretend they are numbers and cast them to be pointers and
+// the compiler is happy.
+EMSCRIPTEN_BINDINGS(Skia) {
+#if SK_SUPPORT_GPU
+    function("currentContext", &emscripten_webgl_get_current_context);
+    function("setCurrentContext", &emscripten_webgl_make_context_current);
+    function("MakeGrContext", &MakeGrContext);
+    function("MakeOnScreenGLSurface", &MakeOnScreenGLSurface);
+    function("MakeRenderTarget", select_overload<sk_sp<SkSurface>(sk_sp<GrContext>, int, int)>(&MakeRenderTarget));
+    function("MakeRenderTarget", select_overload<sk_sp<SkSurface>(sk_sp<GrContext>, SimpleImageInfo)>(&MakeRenderTarget));
+
+    constant("gpu", true);
+#endif
+    function("_decodeImage", optional_override([](uintptr_t /* uint8_t*  */ iptr,
+                                                  size_t length)->sk_sp<SkImage> {
+        uint8_t* imgData = reinterpret_cast<uint8_t*>(iptr);
+        sk_sp<SkData> bytes = SkData::MakeFromMalloc(imgData, length);
+        return SkImage::MakeFromEncoded(std::move(bytes));
+    }), allow_raw_pointers());
+    function("_getRasterDirectSurface", optional_override([](const SimpleImageInfo ii,
+                                                             uintptr_t /* uint8_t*  */ pPtr,
+                                                             size_t rowBytes)->sk_sp<SkSurface> {
+        uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
+        SkImageInfo imageInfo = toSkImageInfo(ii);
+        return SkSurface::MakeRasterDirect(imageInfo, pixels, rowBytes, nullptr);
+    }), allow_raw_pointers());
+    function("_getRasterN32PremulSurface", optional_override([](int width, int height)->sk_sp<SkSurface> {
+        return SkSurface::MakeRasterN32Premul(width, height, nullptr);
+    }), allow_raw_pointers());
+
+    function("getSkDataBytes", &getSkDataBytes, allow_raw_pointers());
+    function("MakeSkCornerPathEffect", &SkCornerPathEffect::Make, allow_raw_pointers());
+    function("MakeSkDiscretePathEffect", &SkDiscretePathEffect::Make, allow_raw_pointers());
+    function("MakeBlurMaskFilter", optional_override([](SkBlurStyle style, SkScalar sigma, bool respectCTM)->sk_sp<SkMaskFilter> {
+        // Adds a little helper because emscripten doesn't expose default params.
+        return SkMaskFilter::MakeBlur(style, sigma, respectCTM);
+    }), allow_raw_pointers());
+    function("_MakePathFromCmds", &MakePathFromCmds);
+    function("MakePathFromOp", &MakePathFromOp);
+    function("MakePathFromSVGString", &MakePathFromSVGString);
+
+    // These won't be called directly, there's a JS helper to deal with typed arrays.
+    function("_MakeSkDashPathEffect", optional_override([](uintptr_t /* float* */ cptr, int count, SkScalar phase)->sk_sp<SkPathEffect> {
+        // See comment above for uintptr_t explanation
+        const float* intervals = reinterpret_cast<const float*>(cptr);
+        return SkDashPathEffect::Make(intervals, count, phase);
+    }), allow_raw_pointers());
+    function("_MakeImage", optional_override([](SimpleImageInfo ii,
+                                                uintptr_t /* uint8_t*  */ pPtr, int plen,
+                                                size_t rowBytes)->sk_sp<SkImage> {
+        // See comment above for uintptr_t explanation
+        uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
+        SkImageInfo info = toSkImageInfo(ii);
+        sk_sp<SkData> pixelData = SkData::MakeFromMalloc(pixels, plen);
+
+        return SkImage::MakeRasterData(info, pixelData, rowBytes);
+    }), allow_raw_pointers());
+    function("_MakeLinearGradientShader", optional_override([](SkPoint start, SkPoint end,
+                                uintptr_t /* SkColor*  */ cPtr, uintptr_t /* SkScalar*  */ pPtr,
+                                int count, SkTileMode mode, uint32_t flags)->sk_sp<SkShader> {
+        SkPoint points[] = { start, end };
+        // See comment above for uintptr_t explanation
+        const SkColor*  colors    = reinterpret_cast<const SkColor*> (cPtr);
+        const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
+
+        return SkGradientShader::MakeLinear(points, colors, positions, count,
+                                            mode, flags, nullptr);
+    }), allow_raw_pointers());
+    function("_MakeLinearGradientShader", optional_override([](SkPoint start, SkPoint end,
+                                uintptr_t /* SkColor*  */ cPtr, uintptr_t /* SkScalar*  */ pPtr,
+                                int count, SkTileMode mode, uint32_t flags,
+                                const SimpleMatrix& lm)->sk_sp<SkShader> {
+        SkPoint points[] = { start, end };
+        // See comment above for uintptr_t explanation
+        const SkColor*  colors    = reinterpret_cast<const SkColor*> (cPtr);
+        const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
+
+        SkMatrix localMatrix = toSkMatrix(lm);
+
+        return SkGradientShader::MakeLinear(points, colors, positions, count,
+                                            mode, flags, &localMatrix);
+    }), allow_raw_pointers());
+    function("_MakeRadialGradientShader", optional_override([](SkPoint center, SkScalar radius,
+                                uintptr_t /* SkColor*  */ cPtr, uintptr_t /* SkScalar*  */ pPtr,
+                                int count, SkTileMode mode, uint32_t flags)->sk_sp<SkShader> {
+        // See comment above for uintptr_t explanation
+        const SkColor*  colors    = reinterpret_cast<const SkColor*> (cPtr);
+        const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
+
+        return SkGradientShader::MakeRadial(center, radius, colors, positions, count,
+                                            mode, flags, nullptr);
+    }), allow_raw_pointers());
+    function("_MakeRadialGradientShader", optional_override([](SkPoint center, SkScalar radius,
+                                uintptr_t /* SkColor*  */ cPtr, uintptr_t /* SkScalar*  */ pPtr,
+                                int count, SkTileMode mode, uint32_t flags,
+                                const SimpleMatrix& lm)->sk_sp<SkShader> {
+        // See comment above for uintptr_t explanation
+        const SkColor*  colors    = reinterpret_cast<const SkColor*> (cPtr);
+        const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
+
+        SkMatrix localMatrix = toSkMatrix(lm);
+        return SkGradientShader::MakeRadial(center, radius, colors, positions, count,
+                                            mode, flags, &localMatrix);
+    }), allow_raw_pointers());
+    function("_MakeTwoPointConicalGradientShader", optional_override([](
+                SkPoint start, SkScalar startRadius,
+                SkPoint end, SkScalar endRadius,
+                uintptr_t /* SkColor*  */ cPtr, uintptr_t /* SkScalar*  */ pPtr,
+                int count, SkTileMode mode, uint32_t flags)->sk_sp<SkShader> {
+        // See comment above for uintptr_t explanation
+        const SkColor*  colors    = reinterpret_cast<const SkColor*> (cPtr);
+        const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
+
+        return SkGradientShader::MakeTwoPointConical(start, startRadius, end, endRadius,
+                                                     colors, positions, count, mode,
+                                                     flags, nullptr);
+    }), allow_raw_pointers());
+    function("_MakeTwoPointConicalGradientShader", optional_override([](
+                SkPoint start, SkScalar startRadius,
+                SkPoint end, SkScalar endRadius,
+                uintptr_t /* SkColor*  */ cPtr, uintptr_t /* SkScalar*  */ pPtr,
+                int count, SkTileMode mode, uint32_t flags,
+                const SimpleMatrix& lm)->sk_sp<SkShader> {
+        // See comment above for uintptr_t explanation
+        const SkColor*  colors    = reinterpret_cast<const SkColor*> (cPtr);
+        const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
+
+        SkMatrix localMatrix = toSkMatrix(lm);
+        return SkGradientShader::MakeTwoPointConical(start, startRadius, end, endRadius,
+                                                     colors, positions, count, mode,
+                                                     flags, &localMatrix);
+    }), allow_raw_pointers());
+
+    function("_MakeSkVertices", optional_override([](SkVertices::VertexMode mode, int vertexCount,
+                                uintptr_t /* SkPoint*     */ pPtr,  uintptr_t /* SkPoint*     */ tPtr,
+                                uintptr_t /* SkColor*     */ cPtr,
+                                uintptr_t /* BoneIndices* */ biPtr, uintptr_t /* BoneWeights* */ bwPtr,
+                                int indexCount,                     uintptr_t /* uint16_t  *  */ iPtr,
+                                bool isVolatile)->sk_sp<SkVertices> {
+        // See comment above for uintptr_t explanation
+        const SkPoint* positions       = reinterpret_cast<const SkPoint*>(pPtr);
+        const SkPoint* texs            = reinterpret_cast<const SkPoint*>(tPtr);
+        const SkColor* colors          = reinterpret_cast<const SkColor*>(cPtr);
+        const BoneIndices* boneIndices = reinterpret_cast<const BoneIndices*>(biPtr);
+        const BoneWeights* boneWeights = reinterpret_cast<const BoneWeights*>(bwPtr);
+        const uint16_t* indices        = reinterpret_cast<const uint16_t*>(iPtr);
+
+        return SkVertices::MakeCopy(mode, vertexCount, positions, texs, colors,
+                                    boneIndices, boneWeights, indexCount, indices, isVolatile);
+    }), allow_raw_pointers());
+
+#if SK_SUPPORT_GPU
+    class_<GrContext>("GrContext")
+        .smart_ptr<sk_sp<GrContext>>("sk_sp<GrContext>")
+        .function("getResourceCacheLimitBytes", optional_override([](GrContext& self)->size_t {
+            int maxResources = 0;// ignored
+            size_t currMax = 0;
+            self.getResourceCacheLimits(&maxResources, &currMax);
+            return currMax;
+        }))
+        .function("getResourceCacheUsageBytes", optional_override([](GrContext& self)->size_t {
+            int usedResources = 0;// ignored
+            size_t currUsage = 0;
+            self.getResourceCacheUsage(&usedResources, &currUsage);
+            return currUsage;
+        }))
+        .function("setResourceCacheLimitBytes", optional_override([](GrContext& self, size_t maxResourceBytes)->void {
+            int maxResources = 0;
+            size_t currMax = 0; // ignored
+            self.getResourceCacheLimits(&maxResources, &currMax);
+            self.setResourceCacheLimits(maxResources, maxResourceBytes);
+        }));
+#endif
+
+    class_<SkCanvas>("SkCanvas")
+        .constructor<>()
+        .function("clear", &SkCanvas::clear)
+        .function("clipPath", select_overload<void (const SkPath&, SkClipOp, bool)>(&SkCanvas::clipPath))
+        .function("clipRect", select_overload<void (const SkRect&, SkClipOp, bool)>(&SkCanvas::clipRect))
+        .function("concat", optional_override([](SkCanvas& self, const SimpleMatrix& m) {
+            self.concat(toSkMatrix(m));
+        }))
+        .function("drawArc", &SkCanvas::drawArc)
+        .function("_drawAtlas", optional_override([](SkCanvas& self,
+                const sk_sp<SkImage>& atlas, uintptr_t /* SkRSXform* */ xptr,
+                uintptr_t /* SkRect* */ rptr, uintptr_t /* SkColor* */ cptr, int count,
+                SkBlendMode mode, const SkPaint* paint)->void {
+            // See comment above for uintptr_t explanation
+            const SkRSXform* dstXforms = reinterpret_cast<const SkRSXform*>(xptr);
+            const SkRect* srcRects = reinterpret_cast<const SkRect*>(rptr);
+            const SkColor* colors = nullptr;
+            if (cptr) {
+                colors = reinterpret_cast<const SkColor*>(cptr);
+            }
+            self.drawAtlas(atlas, dstXforms, srcRects, colors, count, mode, nullptr, paint);
+        }), allow_raw_pointers())
+        .function("drawImage", select_overload<void (const sk_sp<SkImage>&, SkScalar, SkScalar, const SkPaint*)>(&SkCanvas::drawImage), allow_raw_pointers())
+        .function("drawImageRect", optional_override([](SkCanvas& self, const sk_sp<SkImage>& image,
+                                                        SkRect src, SkRect dst,
+                                                        const SkPaint* paint, bool fastSample)->void {
+            self.drawImageRect(image, src, dst, paint,
+                               fastSample ? SkCanvas::kFast_SrcRectConstraint :
+                                            SkCanvas::kStrict_SrcRectConstraint);
+        }), allow_raw_pointers())
+        .function("drawLine", select_overload<void (SkScalar, SkScalar, SkScalar, SkScalar, const SkPaint&)>(&SkCanvas::drawLine))
+        .function("drawOval", &SkCanvas::drawOval)
+        .function("drawPaint", &SkCanvas::drawPaint)
+        .function("drawPath", &SkCanvas::drawPath)
+        // Of note, picture is *not* what is colloquially thought of as a "picture", what we call
+        // a bitmap. An SkPicture is a series of draw commands.
+        .function("drawPicture",  select_overload<void (const sk_sp<SkPicture>&)>(&SkCanvas::drawPicture))
+        .function("drawRect", &SkCanvas::drawRect)
+        .function("drawRoundRect", &SkCanvas::drawRoundRect)
+        .function("drawShadow", optional_override([](SkCanvas& self, const SkPath& path,
+                                                     const SkPoint3& zPlaneParams,
+                                                     const SkPoint3& lightPos, SkScalar lightRadius,
+                                                     SkColor ambientColor, SkColor spotColor,
+                                                     uint32_t flags) {
+            SkShadowUtils::DrawShadow(&self, path, zPlaneParams, lightPos, lightRadius,
+                                      ambientColor, spotColor, flags);
+        }))
+        .function("_drawShapedText", &drawShapedText)
+        .function("_drawSimpleText", optional_override([](SkCanvas& self, uintptr_t /* char* */ sptr,
+                                                          size_t len, SkScalar x, SkScalar y, const SkFont& font,
+                                                          const SkPaint& paint) {
+            // See comment above for uintptr_t explanation
+            const char* str = reinterpret_cast<const char*>(sptr);
+
+            self.drawSimpleText(str, len, SkTextEncoding::kUTF8, x, y, font, paint);
+        }))
+        .function("drawTextBlob", select_overload<void (const sk_sp<SkTextBlob>&, SkScalar, SkScalar, const SkPaint&)>(&SkCanvas::drawTextBlob))
+        .function("drawVertices", select_overload<void (const sk_sp<SkVertices>&, SkBlendMode, const SkPaint&)>(&SkCanvas::drawVertices))
+        .function("flush", &SkCanvas::flush)
+        .function("getTotalMatrix", optional_override([](const SkCanvas& self)->SimpleMatrix {
+            SkMatrix m = self.getTotalMatrix();
+            return toSimpleSkMatrix(m);
+        }))
+        .function("makeSurface", optional_override([](SkCanvas& self, SimpleImageInfo sii)->sk_sp<SkSurface> {
+            return self.makeSurface(toSkImageInfo(sii), nullptr);
+        }), allow_raw_pointers())
+        .function("_readPixels", optional_override([](SkCanvas& self, SimpleImageInfo di,
+                                                      uintptr_t /* uint8_t* */ pPtr,
+                                                      size_t dstRowBytes, int srcX, int srcY) {
+            uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
+            SkImageInfo dstInfo = toSkImageInfo(di);
+
+            return self.readPixels(dstInfo, pixels, dstRowBytes, srcX, srcY);
+        }))
+        .function("restore", &SkCanvas::restore)
+        .function("restoreToCount", &SkCanvas::restoreToCount)
+        .function("rotate", select_overload<void (SkScalar, SkScalar, SkScalar)>(&SkCanvas::rotate))
+        .function("save", &SkCanvas::save)
+        .function("saveLayer", select_overload<int (const SkRect&, const SkPaint*)>(&SkCanvas::saveLayer),
+                               allow_raw_pointers())
+        .function("scale", &SkCanvas::scale)
+        .function("skew", &SkCanvas::skew)
+        .function("translate", &SkCanvas::translate)
+        .function("_writePixels", optional_override([](SkCanvas& self, SimpleImageInfo di,
+                                                       uintptr_t /* uint8_t* */ pPtr,
+                                                       size_t srcRowBytes, int dstX, int dstY) {
+            uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
+            SkImageInfo dstInfo = toSkImageInfo(di);
+
+            return self.writePixels(dstInfo, pixels, srcRowBytes, dstX, dstY);
+        }))
+        ;
+
+    class_<SkData>("SkData")
+        .smart_ptr<sk_sp<SkData>>("sk_sp<SkData>>")
+        .function("size", &SkData::size);
+
+    class_<SkFont>("SkFont")
+        .constructor<>()
+        .constructor<sk_sp<SkTypeface>>()
+        .constructor<sk_sp<SkTypeface>, SkScalar>()
+        .constructor<sk_sp<SkTypeface>, SkScalar, SkScalar, SkScalar>()
+        .function("getScaleX", &SkFont::getScaleX)
+        .function("getSize", &SkFont::getSize)
+        .function("getSkewX", &SkFont::getSkewX)
+        .function("getTypeface", &SkFont::getTypeface, allow_raw_pointers())
+        .function("_getWidths", optional_override([](SkFont& self, uintptr_t /* char* */ sptr,
+                                                     size_t strLen, size_t expectedCodePoints,
+                                                     uintptr_t /* SkScalar* */ wptr) -> bool {
+            char* str = reinterpret_cast<char*>(sptr);
+            SkScalar* widths = reinterpret_cast<SkScalar*>(wptr);
+
+            SkGlyphID* glyphStorage = new SkGlyphID[expectedCodePoints];
+            int actualCodePoints = self.textToGlyphs(str, strLen, SkTextEncoding::kUTF8,
+                                                     glyphStorage, expectedCodePoints);
+            if (actualCodePoints != expectedCodePoints) {
+                SkDebugf("Actually %d glyphs, expected only %d\n",
+                         actualCodePoints, expectedCodePoints);
+                return false;
+            }
+
+            self.getWidths(glyphStorage, actualCodePoints, widths);
+            delete[] glyphStorage;
+            return true;
+        }))
+        .function("measureText", optional_override([](SkFont& self, std::string text) {
+            // TODO(kjlubick): This does not work well for non-ascii
+            // Need to maybe add a helper in interface.js that supports UTF-8
+            // Otherwise, go with std::wstring and set UTF-32 encoding.
+            return self.measureText(text.c_str(), text.length(), SkTextEncoding::kUTF8);
+        }))
+        .function("setScaleX", &SkFont::setScaleX)
+        .function("setSize", &SkFont::setSize)
+        .function("setSkewX", &SkFont::setSkewX)
+        .function("setTypeface", &SkFont::setTypeface, allow_raw_pointers());
+
+    class_<ShapedText>("ShapedText")
+        .constructor<ShapedTextOpts>()
+        .function("getBounds", &ShapedText::getBounds);
+
+    class_<SkFontMgr>("SkFontMgr")
+        .smart_ptr<sk_sp<SkFontMgr>>("sk_sp<SkFontMgr>")
+        .class_function("RefDefault", &SkFontMgr::RefDefault)
+#ifdef SK_DEBUG
+        .function("dumpFamilies", optional_override([](SkFontMgr& self) {
+            int numFam = self.countFamilies();
+            SkDebugf("There are %d font families\n");
+            for (int i = 0 ; i< numFam; i++) {
+                SkString s;
+                self.getFamilyName(i, &s);
+                SkDebugf("\t%s", s.c_str());
+            }
+        }))
+#endif
+        .function("countFamilies", &SkFontMgr::countFamilies)
+        .function("_makeTypefaceFromData", optional_override([](SkFontMgr& self,
+                                                uintptr_t /* uint8_t*  */ fPtr,
+                                                int flen)->sk_sp<SkTypeface> {
+        // See comment above for uintptr_t explanation
+        uint8_t* font = reinterpret_cast<uint8_t*>(fPtr);
+        sk_sp<SkData> fontData = SkData::MakeFromMalloc(font, flen);
+
+        return self.makeFromData(fontData);
+    }), allow_raw_pointers());
+
+    class_<SkImage>("SkImage")
+        .smart_ptr<sk_sp<SkImage>>("sk_sp<SkImage>")
+        .function("height", &SkImage::height)
+        .function("width", &SkImage::width)
+        .function("_encodeToData", select_overload<sk_sp<SkData>()const>(&SkImage::encodeToData))
+        .function("_encodeToDataWithFormat", select_overload<sk_sp<SkData>(SkEncodedImageFormat encodedImageFormat, int quality)const>(&SkImage::encodeToData))
+            // Allow localMatrix to be optional, so we have 2 declarations of these shaders
+        .function("_makeShader", optional_override([](sk_sp<SkImage> self,
+                                SkTileMode tx, SkTileMode ty)->sk_sp<SkShader> {
+            return self->makeShader(tx, ty, nullptr);
+        }), allow_raw_pointers())
+        .function("_makeShader", optional_override([](sk_sp<SkImage> self,
+                                 SkTileMode tx, SkTileMode ty,
+                                 const SimpleMatrix& lm)->sk_sp<SkShader> {
+            SkMatrix localMatrix = toSkMatrix(lm);
+
+            return self->makeShader(tx, ty, &localMatrix);
+        }), allow_raw_pointers());
+
+    class_<SkMaskFilter>("SkMaskFilter")
+        .smart_ptr<sk_sp<SkMaskFilter>>("sk_sp<SkMaskFilter>");
+
+    class_<SkPaint>("SkPaint")
+        .constructor<>()
+        .function("copy", optional_override([](const SkPaint& self)->SkPaint {
+            SkPaint p(self);
+            return p;
+        }))
+        .function("getBlendMode", &SkPaint::getBlendMode)
+        .function("getColor", &SkPaint::getColor)
+        .function("getFilterQuality", &SkPaint::getFilterQuality)
+        .function("getStrokeCap", &SkPaint::getStrokeCap)
+        .function("getStrokeJoin", &SkPaint::getStrokeJoin)
+        .function("getStrokeMiter", &SkPaint::getStrokeMiter)
+        .function("getStrokeWidth", &SkPaint::getStrokeWidth)
+        .function("setAntiAlias", &SkPaint::setAntiAlias)
+        .function("setBlendMode", &SkPaint::setBlendMode)
+        .function("setColor", &SkPaint::setColor)
+        .function("setFilterQuality", &SkPaint::setFilterQuality)
+        .function("setMaskFilter", &SkPaint::setMaskFilter)
+        .function("setPathEffect", &SkPaint::setPathEffect)
+        .function("setShader", &SkPaint::setShader)
+        .function("setStrokeCap", &SkPaint::setStrokeCap)
+        .function("setStrokeJoin", &SkPaint::setStrokeJoin)
+        .function("setStrokeMiter", &SkPaint::setStrokeMiter)
+        .function("setStrokeWidth", &SkPaint::setStrokeWidth)
+        .function("setStyle", &SkPaint::setStyle);
+
+    class_<SkPathEffect>("SkPathEffect")
+        .smart_ptr<sk_sp<SkPathEffect>>("sk_sp<SkPathEffect>");
+
+    class_<SkPath>("SkPath")
+        .constructor<>()
+        .constructor<const SkPath&>()
+        .function("_addArc", &ApplyAddArc)
+        // interface.js has 3 overloads of addPath
+        .function("_addPath", &ApplyAddPath)
+        // interface.js has 4 overloads of addRect
+        .function("_addRect", &ApplyAddRect)
+        // interface.js has 4 overloads of addRoundRect
+        .function("_addRoundRect", &ApplyAddRoundRect)
+        .function("_arcTo", &ApplyArcTo)
+        .function("_arcTo", &ApplyArcToAngle)
+        .function("_close", &ApplyClose)
+        .function("_conicTo", &ApplyConicTo)
+        .function("countPoints", &SkPath::countPoints)
+        .function("contains", &SkPath::contains)
+        .function("_cubicTo", &ApplyCubicTo)
+        .function("getPoint", &SkPath::getPoint)
+        .function("isEmpty",  &SkPath::isEmpty)
+        .function("isVolatile", &SkPath::isVolatile)
+        .function("_lineTo", &ApplyLineTo)
+        .function("_moveTo", &ApplyMoveTo)
+        .function("reset", &ApplyReset)
+        .function("rewind", &ApplyRewind)
+        .function("_quadTo", &ApplyQuadTo)
+        .function("setIsVolatile", &SkPath::setIsVolatile)
+        .function("_transform", select_overload<void(SkPath&, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&ApplyTransform))
+
+        // PathEffects
+        .function("_dash", &ApplyDash)
+        .function("_trim", &ApplyTrim)
+        .function("_stroke", &ApplyStroke)
+
+        // PathOps
+        .function("_simplify", &ApplySimplify)
+        .function("_op", &ApplyPathOp)
+
+        // Exporting
+        .function("toSVGString", &ToSVGString)
+        .function("toCmds", &ToCmds)
+
+        .function("setFillType", &SkPath::setFillType)
+        .function("getFillType", &SkPath::getFillType)
+        .function("getBounds", &SkPath::getBounds)
+        .function("computeTightBounds", &SkPath::computeTightBounds)
+        .function("equals", &Equals)
+        .function("copy", &CopyPath)
+#ifdef SK_DEBUG
+        .function("dump", select_overload<void() const>(&SkPath::dump))
+        .function("dumpHex", select_overload<void() const>(&SkPath::dumpHex))
+#endif
+        ;
+
+    class_<SkPathMeasure>("SkPathMeasure")
+        .constructor<const SkPath&, bool, SkScalar>()
+        .function("getLength", &SkPathMeasure::getLength)
+        .function("getPosTan", optional_override([](SkPathMeasure& self,
+                                                    SkScalar distance) -> PosTan {
+            SkPoint p{0, 0};
+            SkVector v{0, 0};
+            if (!self.getPosTan(distance, &p, &v)) {
+                SkDebugf("zero-length path in getPosTan\n");
+            }
+            return PosTan{p.x(), p.y(), v.x(), v.y()};
+        }))
+        .function("isClosed", &SkPathMeasure::isClosed)
+        .function("nextContour", &SkPathMeasure::nextContour);
+
+    class_<SkPictureRecorder>("SkPictureRecorder")
+        .constructor<>()
+        .function("beginRecording", optional_override([](SkPictureRecorder& self,
+                                                         const SkRect& bounds) -> SkCanvas* {
+            return self.beginRecording(bounds, nullptr, 0);
+        }), allow_raw_pointers())
+        .function("finishRecordingAsPicture", optional_override([](SkPictureRecorder& self)
+                                                                   -> sk_sp<SkPicture> {
+            return self.finishRecordingAsPicture(0);
+        }), allow_raw_pointers());
+
+    class_<SkPicture>("SkPicture")
+        .smart_ptr<sk_sp<SkPicture>>("sk_sp<SkPicture>")
+        // The serialized format of an SkPicture (informally called an "skp"), is not something
+        // that clients should ever rely on. It is useful when filing bug reports, but that's
+        // about it. The format may change at anytime and no promises are made for backwards
+        // or forward compatibility.
+        .function("DEBUGONLY_serialize", optional_override([](SkPicture& self) -> sk_sp<SkData> {
+            // Emscripten doesn't play well with optional arguments, which we don't
+            // want to expose anyway.
+            return self.serialize();
+        }), allow_raw_pointers());
+
+    class_<SkShader>("SkShader")
+        .smart_ptr<sk_sp<SkShader>>("sk_sp<SkShader>");
+
+    class_<SkSurface>("SkSurface")
+        .smart_ptr<sk_sp<SkSurface>>("sk_sp<SkSurface>")
+        .function("_flush", select_overload<void()>(&SkSurface::flush))
+        .function("getCanvas", &SkSurface::getCanvas, allow_raw_pointers())
+        .function("height", &SkSurface::height)
+        .function("makeImageSnapshot", select_overload<sk_sp<SkImage>()>(&SkSurface::makeImageSnapshot))
+        .function("makeImageSnapshot", select_overload<sk_sp<SkImage>(const SkIRect& bounds)>(&SkSurface::makeImageSnapshot))
+        .function("makeSurface", optional_override([](SkSurface& self, SimpleImageInfo sii)->sk_sp<SkSurface> {
+            return self.makeSurface(toSkImageInfo(sii));
+        }), allow_raw_pointers())
+        .function("width", &SkSurface::width);
+
+    class_<SkTextBlob>("SkTextBlob")
+        .smart_ptr<sk_sp<SkTextBlob>>("sk_sp<SkTextBlob>>")
+        .class_function("_MakeFromRSXform", optional_override([](uintptr_t /* char* */ sptr,
+                                                              size_t strBtyes,
+                                                              uintptr_t /* SkRSXform* */ xptr,
+                                                              const SkFont& font,
+                                                              SkTextEncoding encoding)->sk_sp<SkTextBlob> {
+            // See comment above for uintptr_t explanation
+            const char* str = reinterpret_cast<const char*>(sptr);
+            const SkRSXform* xforms = reinterpret_cast<const SkRSXform*>(xptr);
+
+            return SkTextBlob::MakeFromRSXform(str, strBtyes, xforms, font, encoding);
+        }), allow_raw_pointers())
+        .class_function("_MakeFromText", optional_override([](uintptr_t /* char* */ sptr,
+                                                              size_t len, const SkFont& font,
+                                                              SkTextEncoding encoding)->sk_sp<SkTextBlob> {
+            // See comment above for uintptr_t explanation
+            const char* str = reinterpret_cast<const char*>(sptr);
+            return SkTextBlob::MakeFromText(str, len, font, encoding);
+        }), allow_raw_pointers());
+
+
+    class_<SkTypeface>("SkTypeface")
+        .smart_ptr<sk_sp<SkTypeface>>("sk_sp<SkTypeface>");
+
+    class_<SkVertices>("SkVertices")
+        .smart_ptr<sk_sp<SkVertices>>("sk_sp<SkVertices>")
+        .function("_applyBones", optional_override([](SkVertices& self, uintptr_t /* Bone* */ bptr, int boneCount)->sk_sp<SkVertices> {
+            // See comment above for uintptr_t explanation
+            const Bone* bones = reinterpret_cast<const Bone*>(bptr);
+            return self.applyBones(bones, boneCount);
+        }))
+        .function("bounds", &SkVertices::bounds)
+        .function("mode", &SkVertices::mode)
+        .function("uniqueID", &SkVertices::uniqueID)
+#ifdef SK_DEBUG
+        .function("dumpPositions", optional_override([](SkVertices& self)->void {
+            auto pos = self.positions();
+            for(int i = 0; i< self.vertexCount(); i++) {
+                SkDebugf("position[%d] = (%f, %f)\n", i, pos[i].x(), pos[i].y());
+            }
+        }))
+#endif
+        .function("vertexCount", &SkVertices::vertexCount);
+
+    enum_<SkAlphaType>("AlphaType")
+        .value("Opaque",   SkAlphaType::kOpaque_SkAlphaType)
+        .value("Premul",   SkAlphaType::kPremul_SkAlphaType)
+        .value("Unpremul", SkAlphaType::kUnpremul_SkAlphaType);
+
+    enum_<SkBlendMode>("BlendMode")
+        .value("Clear",      SkBlendMode::kClear)
+        .value("Src",        SkBlendMode::kSrc)
+        .value("Dst",        SkBlendMode::kDst)
+        .value("SrcOver",    SkBlendMode::kSrcOver)
+        .value("DstOver",    SkBlendMode::kDstOver)
+        .value("SrcIn",      SkBlendMode::kSrcIn)
+        .value("DstIn",      SkBlendMode::kDstIn)
+        .value("SrcOut",     SkBlendMode::kSrcOut)
+        .value("DstOut",     SkBlendMode::kDstOut)
+        .value("SrcATop",    SkBlendMode::kSrcATop)
+        .value("DstATop",    SkBlendMode::kDstATop)
+        .value("Xor",        SkBlendMode::kXor)
+        .value("Plus",       SkBlendMode::kPlus)
+        .value("Modulate",   SkBlendMode::kModulate)
+        .value("Screen",     SkBlendMode::kScreen)
+        .value("Overlay",    SkBlendMode::kOverlay)
+        .value("Darken",     SkBlendMode::kDarken)
+        .value("Lighten",    SkBlendMode::kLighten)
+        .value("ColorDodge", SkBlendMode::kColorDodge)
+        .value("ColorBurn",  SkBlendMode::kColorBurn)
+        .value("HardLight",  SkBlendMode::kHardLight)
+        .value("SoftLight",  SkBlendMode::kSoftLight)
+        .value("Difference", SkBlendMode::kDifference)
+        .value("Exclusion",  SkBlendMode::kExclusion)
+        .value("Multiply",   SkBlendMode::kMultiply)
+        .value("Hue",        SkBlendMode::kHue)
+        .value("Saturation", SkBlendMode::kSaturation)
+        .value("Color",      SkBlendMode::kColor)
+        .value("Luminosity", SkBlendMode::kLuminosity);
+
+    enum_<SkBlurStyle>("BlurStyle")
+        .value("Normal", SkBlurStyle::kNormal_SkBlurStyle)
+        .value("Solid",  SkBlurStyle::kSolid_SkBlurStyle)
+        .value("Outer",  SkBlurStyle::kOuter_SkBlurStyle)
+        .value("Inner",  SkBlurStyle::kInner_SkBlurStyle);
+
+    enum_<SkClipOp>("ClipOp")
+        .value("Difference", SkClipOp::kDifference)
+        .value("Intersect",  SkClipOp::kIntersect);
+
+    enum_<SkColorType>("ColorType")
+        .value("Alpha_8", SkColorType::kAlpha_8_SkColorType)
+        .value("RGB_565", SkColorType::kRGB_565_SkColorType)
+        .value("ARGB_4444", SkColorType::kARGB_4444_SkColorType)
+        .value("RGBA_8888", SkColorType::kRGBA_8888_SkColorType)
+        .value("RGB_888x", SkColorType::kRGB_888x_SkColorType)
+        .value("BGRA_8888", SkColorType::kBGRA_8888_SkColorType)
+        .value("RGBA_1010102", SkColorType::kRGBA_1010102_SkColorType)
+        .value("RGB_101010x", SkColorType::kRGB_101010x_SkColorType)
+        .value("Gray_8", SkColorType::kGray_8_SkColorType)
+        .value("RGBA_F16", SkColorType::kRGBA_F16_SkColorType)
+        .value("RGBA_F32", SkColorType::kRGBA_F32_SkColorType);
+
+    enum_<SkPath::FillType>("FillType")
+        .value("Winding",           SkPath::FillType::kWinding_FillType)
+        .value("EvenOdd",           SkPath::FillType::kEvenOdd_FillType)
+        .value("InverseWinding",    SkPath::FillType::kInverseWinding_FillType)
+        .value("InverseEvenOdd",    SkPath::FillType::kInverseEvenOdd_FillType);
+
+    enum_<SkFilterQuality>("FilterQuality")
+        .value("None",   SkFilterQuality::kNone_SkFilterQuality)
+        .value("Low",    SkFilterQuality::kLow_SkFilterQuality)
+        .value("Medium", SkFilterQuality::kMedium_SkFilterQuality)
+        .value("High",   SkFilterQuality::kHigh_SkFilterQuality);
+
+    enum_<SkEncodedImageFormat>("ImageFormat")
+        .value("PNG",  SkEncodedImageFormat::kPNG)
+        .value("JPEG", SkEncodedImageFormat::kJPEG);
+
+    enum_<SkPaint::Style>("PaintStyle")
+        .value("Fill",            SkPaint::Style::kFill_Style)
+        .value("Stroke",          SkPaint::Style::kStroke_Style)
+        .value("StrokeAndFill",   SkPaint::Style::kStrokeAndFill_Style);
+
+    enum_<SkPathOp>("PathOp")
+        .value("Difference",         SkPathOp::kDifference_SkPathOp)
+        .value("Intersect",          SkPathOp::kIntersect_SkPathOp)
+        .value("Union",              SkPathOp::kUnion_SkPathOp)
+        .value("XOR",                SkPathOp::kXOR_SkPathOp)
+        .value("ReverseDifference",  SkPathOp::kReverseDifference_SkPathOp);
+
+    enum_<SkPaint::Cap>("StrokeCap")
+        .value("Butt",   SkPaint::Cap::kButt_Cap)
+        .value("Round",  SkPaint::Cap::kRound_Cap)
+        .value("Square", SkPaint::Cap::kSquare_Cap);
+
+    enum_<SkPaint::Join>("StrokeJoin")
+        .value("Miter", SkPaint::Join::kMiter_Join)
+        .value("Round", SkPaint::Join::kRound_Join)
+        .value("Bevel", SkPaint::Join::kBevel_Join);
+
+    enum_<SkTextEncoding>("TextEncoding")
+        .value("UTF8",    SkTextEncoding::kUTF8)
+        .value("UTF16",   SkTextEncoding::kUTF16)
+        .value("UTF32",   SkTextEncoding::kUTF32)
+        .value("GlyphID", SkTextEncoding::kGlyphID);
+
+    enum_<SkTileMode>("TileMode")
+        .value("Clamp",    SkTileMode::kClamp)
+        .value("Repeat",   SkTileMode::kRepeat)
+        .value("Mirror",   SkTileMode::kMirror)
+        .value("Decal",    SkTileMode::kDecal);
+
+    enum_<SkVertices::VertexMode>("VertexMode")
+        .value("Triangles",       SkVertices::VertexMode::kTriangles_VertexMode)
+        .value("TrianglesStrip",  SkVertices::VertexMode::kTriangleStrip_VertexMode)
+        .value("TriangleFan",     SkVertices::VertexMode::kTriangleFan_VertexMode);
+
+
+    // A value object is much simpler than a class - it is returned as a JS
+    // object and does not require delete().
+    // https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#value-types
+    value_object<ShapedTextOpts>("ShapedTextOpts")
+        .field("font",        &ShapedTextOpts::font)
+        .field("leftToRight", &ShapedTextOpts::leftToRight)
+        .field("text",        &ShapedTextOpts::text)
+        .field("width",       &ShapedTextOpts::width);
+
+    value_object<SkRect>("SkRect")
+        .field("fLeft",   &SkRect::fLeft)
+        .field("fTop",    &SkRect::fTop)
+        .field("fRight",  &SkRect::fRight)
+        .field("fBottom", &SkRect::fBottom);
+
+    value_object<SkIRect>("SkIRect")
+        .field("fLeft",   &SkIRect::fLeft)
+        .field("fTop",    &SkIRect::fTop)
+        .field("fRight",  &SkIRect::fRight)
+        .field("fBottom", &SkIRect::fBottom);
+
+    value_object<SimpleImageInfo>("SkImageInfo")
+        .field("width",     &SimpleImageInfo::width)
+        .field("height",    &SimpleImageInfo::height)
+        .field("colorType", &SimpleImageInfo::colorType)
+        .field("alphaType", &SimpleImageInfo::alphaType);
+
+    // SkPoints can be represented by [x, y]
+    value_array<SkPoint>("SkPoint")
+        .element(&SkPoint::fX)
+        .element(&SkPoint::fY);
+
+    // SkPoint3s can be represented by [x, y, z]
+    value_array<SkPoint3>("SkPoint3")
+        .element(&SkPoint3::fX)
+        .element(&SkPoint3::fY)
+        .element(&SkPoint3::fZ);
+
+    // PosTan can be represented by [px, py, tx, ty]
+    value_array<PosTan>("PosTan")
+        .element(&PosTan::px)
+        .element(&PosTan::py)
+        .element(&PosTan::tx)
+        .element(&PosTan::ty);
+
+    // {"w": Number, "h", Number}
+    value_object<SkSize>("SkSize")
+        .field("w",   &SkSize::fWidth)
+        .field("h",   &SkSize::fHeight);
+
+    value_object<SkISize>("SkISize")
+        .field("w",   &SkISize::fWidth)
+        .field("h",   &SkISize::fHeight);
+
+    value_object<StrokeOpts>("StrokeOpts")
+        .field("width",       &StrokeOpts::width)
+        .field("miter_limit", &StrokeOpts::miter_limit)
+        .field("join",        &StrokeOpts::join)
+        .field("cap",         &StrokeOpts::cap)
+        .field("precision",   &StrokeOpts::precision);
+
+    // Allows clients to supply a 1D array of 9 elements and the bindings
+    // will automatically turn it into a 3x3 2D matrix.
+    // e.g. path.transform([0,1,2,3,4,5,6,7,8])
+    // This is likely simpler for the client than exposing SkMatrix
+    // directly and requiring them to do a lot of .delete().
+    value_array<SimpleMatrix>("SkMatrix")
+        .element(&SimpleMatrix::scaleX)
+        .element(&SimpleMatrix::skewX)
+        .element(&SimpleMatrix::transX)
+
+        .element(&SimpleMatrix::skewY)
+        .element(&SimpleMatrix::scaleY)
+        .element(&SimpleMatrix::transY)
+
+        .element(&SimpleMatrix::pers0)
+        .element(&SimpleMatrix::pers1)
+        .element(&SimpleMatrix::pers2);
+
+    constant("TRANSPARENT", SK_ColorTRANSPARENT);
+    constant("RED",         SK_ColorRED);
+    constant("BLUE",        SK_ColorBLUE);
+    constant("YELLOW",      SK_ColorYELLOW);
+    constant("CYAN",        SK_ColorCYAN);
+    constant("BLACK",       SK_ColorBLACK);
+    constant("WHITE",       SK_ColorWHITE);
+    // TODO(?)
+
+    constant("MOVE_VERB",  MOVE);
+    constant("LINE_VERB",  LINE);
+    constant("QUAD_VERB",  QUAD);
+    constant("CONIC_VERB", CONIC);
+    constant("CUBIC_VERB", CUBIC);
+    constant("CLOSE_VERB", CLOSE);
+}
diff --git a/modules/canvaskit/compile.sh b/modules/canvaskit/compile.sh
new file mode 100755
index 0000000..6df90b3
--- /dev/null
+++ b/modules/canvaskit/compile.sh
@@ -0,0 +1,256 @@
+#!/bin/bash
+# Copyright 2018 Google LLC
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+set -ex
+
+BASE_DIR=`cd $(dirname ${BASH_SOURCE[0]}) && pwd`
+# This expects the environment variable EMSDK to be set
+if [[ ! -d $EMSDK ]]; then
+  echo "Be sure to set the EMSDK environment variable."
+  exit 1
+fi
+
+# Navigate to SKIA_HOME from where this file is located.
+pushd $BASE_DIR/../..
+
+source $EMSDK/emsdk_env.sh
+EMCC=`which emcc`
+EMCXX=`which em++`
+
+RELEASE_CONF="-Oz --closure 1 --llvm-lto 3 -DSK_RELEASE --pre-js $BASE_DIR/release.js \
+              -DGR_GL_CHECK_ALLOC_WITH_GET_ERROR=0"
+EXTRA_CFLAGS="\"-DSK_RELEASE\", \"-DGR_GL_CHECK_ALLOC_WITH_GET_ERROR=0\","
+if [[ $@ == *debug* ]]; then
+  echo "Building a Debug build"
+  EXTRA_CFLAGS="\"-DSK_DEBUG\""
+  RELEASE_CONF="-O0 --js-opts 0 -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=1 -s GL_ASSERTIONS=1 -g4 \
+                --source-map-base /node_modules/canvaskit/bin/ -DSK_DEBUG --pre-js $BASE_DIR/debug.js"
+  BUILD_DIR=${BUILD_DIR:="out/canvaskit_wasm_debug"}
+elif [[ $@ == *profiling* ]]; then
+  echo "Building a build for profiling"
+  RELEASE_CONF="-O3 --source-map-base /node_modules/canvaskit/bin/ --profiling -g4 -DSK_RELEASE \
+                --pre-js $BASE_DIR/release.js -DGR_GL_CHECK_ALLOC_WITH_GET_ERROR=0"
+  BUILD_DIR=${BUILD_DIR:="out/canvaskit_wasm_profile"}
+else
+  BUILD_DIR=${BUILD_DIR:="out/canvaskit_wasm"}
+fi
+
+mkdir -p $BUILD_DIR
+
+GN_GPU="skia_enable_gpu=true skia_gl_standard = \"webgl\""
+GN_GPU_FLAGS="\"-DSK_DISABLE_LEGACY_SHADERCONTEXT\","
+WASM_GPU="-lEGL -lGLESv2 -DSK_SUPPORT_GPU=1 \
+          -DSK_DISABLE_LEGACY_SHADERCONTEXT --pre-js $BASE_DIR/cpu.js --pre-js $BASE_DIR/gpu.js"
+if [[ $@ == *cpu* ]]; then
+  echo "Using the CPU backend instead of the GPU backend"
+  GN_GPU="skia_enable_gpu=false"
+  GN_GPU_FLAGS=""
+  WASM_GPU="-DSK_SUPPORT_GPU=0 --pre-js $BASE_DIR/cpu.js"
+fi
+
+SKOTTIE_JS="--pre-js $BASE_DIR/skottie.js"
+SKOTTIE_BINDINGS="$BASE_DIR/skottie_bindings.cpp"
+
+SKOTTIE_LIB="$BUILD_DIR/libskottie.a \
+             $BUILD_DIR/libsksg.a"
+
+if [[ $@ == *no_skottie* ]]; then
+  echo "Omitting Skottie"
+  SKOTTIE_JS=""
+  SKOTTIE_LIB=""
+  SKOTTIE_BINDINGS=""
+fi
+
+MANAGED_SKOTTIE_BINDINGS="\
+  -DSK_INCLUDE_MANAGED_SKOTTIE=1 \
+  modules/skottie/utils/SkottieUtils.cpp"
+if [[ $@ == *no_managed_skottie* ]]; then
+  echo "Omitting managed Skottie"
+  MANAGED_SKOTTIE_BINDINGS="-DSK_INCLUDE_MANAGED_SKOTTIE=0"
+fi
+
+PARTICLES_BINDINGS="$BASE_DIR/particles_bindings.cpp"
+PARTICLES_LIB="$BUILD_DIR/libparticles.a"
+
+if [[ $@ == *no_particles* ]]; then
+  echo "Omitting Particles"
+  PARTICLES_BINDINGS=""
+  PARTICLES_LIB=""
+fi
+
+HTML_CANVAS_API="--pre-js $BASE_DIR/htmlcanvas/preamble.js \
+--pre-js $BASE_DIR/htmlcanvas/util.js \
+--pre-js $BASE_DIR/htmlcanvas/color.js \
+--pre-js $BASE_DIR/htmlcanvas/font.js \
+--pre-js $BASE_DIR/htmlcanvas/canvas2dcontext.js \
+--pre-js $BASE_DIR/htmlcanvas/htmlcanvas.js \
+--pre-js $BASE_DIR/htmlcanvas/imagedata.js \
+--pre-js $BASE_DIR/htmlcanvas/lineargradient.js \
+--pre-js $BASE_DIR/htmlcanvas/path2d.js \
+--pre-js $BASE_DIR/htmlcanvas/pattern.js \
+--pre-js $BASE_DIR/htmlcanvas/radialgradient.js \
+--pre-js $BASE_DIR/htmlcanvas/postamble.js "
+if [[ $@ == *no_canvas* ]]; then
+  echo "Omitting bindings for HTML Canvas API"
+  HTML_CANVAS_API=""
+fi
+
+BUILTIN_FONT="$BASE_DIR/fonts/NotoMono-Regular.ttf.cpp"
+if [[ $@ == *no_font* ]]; then
+  echo "Omitting the built-in font(s)"
+  BUILTIN_FONT=""
+else
+  # Generate the font's binary file (which is covered by .gitignore)
+  python tools/embed_resources.py \
+      --name SK_EMBEDDED_FONTS \
+      --input $BASE_DIR/fonts/NotoMono-Regular.ttf \
+      --output $BASE_DIR/fonts/NotoMono-Regular.ttf.cpp \
+      --align 4
+fi
+
+GN_SHAPER="skia_use_icu=true skia_use_system_icu=false skia_use_system_harfbuzz=false"
+SHAPER_LIB="$BUILD_DIR/libharfbuzz.a \
+            $BUILD_DIR/libicu.a"
+SHAPER_TARGETS="libharfbuzz.a libicu.a"
+if [[ $@ == *primitive_shaper* ]]; then
+  echo "Using the primitive shaper instead of the harfbuzz/icu one"
+  GN_SHAPER="skia_use_icu=false skia_use_harfbuzz=false"
+  SHAPER_LIB=""
+  SHAPER_TARGETS=""
+fi
+
+# Turn off exiting while we check for ninja (which may not be on PATH)
+set +e
+NINJA=`which ninja`
+if [[ -z $NINJA ]]; then
+  git clone "https://chromium.googlesource.com/chromium/tools/depot_tools.git" --depth 1 $BUILD_DIR/depot_tools
+  NINJA=$BUILD_DIR/depot_tools/ninja
+fi
+# Re-enable error checking
+set -e
+
+./bin/fetch-gn
+
+echo "Compiling bitcode"
+
+# Inspired by https://github.com/Zubnix/skia-wasm-port/blob/master/build_bindings.sh
+./bin/gn gen ${BUILD_DIR} \
+  --args="cc=\"${EMCC}\" \
+  cxx=\"${EMCXX}\" \
+  extra_cflags_cc=[\"-frtti\"] \
+  extra_cflags=[\"-s\",\"USE_FREETYPE=1\",\"-s\",\"USE_LIBPNG=1\", \"-s\", \"WARN_UNALIGNED=1\",
+    \"-DSKNX_NO_SIMD\", \"-DSK_DISABLE_AAA\", \"-DSK_DISABLE_READBUFFER\",
+    \"-DSK_DISABLE_EFFECT_DESERIALIZATION\",
+    ${GN_GPU_FLAGS}
+    ${EXTRA_CFLAGS}
+  ] \
+  is_debug=false \
+  is_official_build=true \
+  is_component_build=false \
+  target_cpu=\"wasm\" \
+  \
+  skia_use_angle = false \
+  skia_use_dng_sdk=false \
+  skia_use_egl=true \
+  skia_use_expat=false \
+  skia_use_fontconfig=false \
+  skia_use_freetype=true \
+  skia_use_libheif=false \
+  skia_use_libjpeg_turbo=true \
+  skia_use_libpng=true \
+  skia_use_libwebp=false \
+  skia_use_lua=false \
+  skia_use_piex=false \
+  skia_use_system_libpng=true \
+  skia_use_system_freetype2=true \
+  skia_use_system_libjpeg_turbo = false \
+  skia_use_vulkan=false \
+  skia_use_wuffs = true \
+  skia_use_zlib=true \
+  \
+  ${GN_SHAPER} \
+  ${GN_GPU} \
+  \
+  skia_enable_skshaper=true \
+  skia_enable_ccpr=false \
+  skia_enable_nvpr=false \
+  skia_enable_skpicture=true \
+  skia_enable_fontmgr_empty=false \
+  skia_enable_pdf=false"
+
+# Build all the libs, we'll link the appropriate ones down below
+${NINJA} -C ${BUILD_DIR} libskia.a libskottie.a libsksg.a libskshaper.a libparticles.a $SHAPER_TARGETS
+
+export EMCC_CLOSURE_ARGS="--externs $BASE_DIR/externs.js "
+
+echo "Generating final wasm"
+
+# Emscripten prefers that the .a files go last in order, otherwise, it
+# may drop symbols that it incorrectly thinks aren't used. One day,
+# Emscripten will use LLD, which may relax this requirement.
+#
+# Setting -s USE_WEBGL2=1 does two things:
+#  1. Allows users to try to create a WebGL2 context for use with CanvasKit
+#     (this is not supported, only WebGL1 [initially])
+#  2. Makes WebGL1 work better on some graphics cards (for reasons that aren't
+#     super clear, but might have to do with extensions).
+${EMCXX} \
+    $RELEASE_CONF \
+    -Iexperimental \
+    -Iinclude/c \
+    -Iinclude/codec \
+    -Iinclude/config \
+    -Iinclude/core \
+    -Iinclude/effects \
+    -Iinclude/gpu \
+    -Iinclude/gpu/gl \
+    -Iinclude/pathops \
+    -Iinclude/private \
+    -Iinclude/utils/ \
+    -Imodules/skottie/include \
+    -Imodules/skottie/utils \
+    -Imodules/sksg/include \
+    -Imodules/skshaper/include \
+    -Imodules/particles/include \
+    -Isrc/core/ \
+    -Isrc/utils/ \
+    -Ithird_party/icu \
+    -Itools \
+    -DSK_DISABLE_READBUFFER \
+    -DSK_DISABLE_AAA \
+    $WASM_GPU \
+    -std=c++14 \
+    --bind \
+    --pre-js $BASE_DIR/preamble.js \
+    --pre-js $BASE_DIR/helper.js \
+    --pre-js $BASE_DIR/interface.js \
+    $SKOTTIE_JS \
+    $HTML_CANVAS_API \
+    --pre-js $BASE_DIR/postamble.js \
+    --post-js $BASE_DIR/ready.js \
+    $BUILTIN_FONT \
+    $BASE_DIR/canvaskit_bindings.cpp \
+    $PARTICLES_BINDINGS \
+    $SKOTTIE_BINDINGS \
+    $MANAGED_SKOTTIE_BINDINGS \
+    $SKOTTIE_LIB \
+    $PARTICLES_LIB \
+    $BUILD_DIR/libskshaper.a \
+    $SHAPER_LIB \
+    $BUILD_DIR/libskia.a \
+    -s ALLOW_MEMORY_GROWTH=1 \
+    -s EXPORT_NAME="CanvasKitInit" \
+    -s FORCE_FILESYSTEM=0 \
+    -s MODULARIZE=1 \
+    -s NO_EXIT_RUNTIME=1 \
+    -s STRICT=1 \
+    -s TOTAL_MEMORY=128MB \
+    -s USE_FREETYPE=1 \
+    -s USE_LIBPNG=1 \
+    -s WARN_UNALIGNED=1 \
+    -s USE_WEBGL2=0 \
+    -s WASM=1 \
+    -o $BUILD_DIR/canvaskit.js
diff --git a/experimental/canvaskit/cpu.js b/modules/canvaskit/cpu.js
similarity index 100%
rename from experimental/canvaskit/cpu.js
rename to modules/canvaskit/cpu.js
diff --git a/experimental/canvaskit/debug.js b/modules/canvaskit/debug.js
similarity index 100%
rename from experimental/canvaskit/debug.js
rename to modules/canvaskit/debug.js
diff --git a/modules/canvaskit/externs.js b/modules/canvaskit/externs.js
new file mode 100644
index 0000000..2c36c31
--- /dev/null
+++ b/modules/canvaskit/externs.js
@@ -0,0 +1,653 @@
+/*
+ * This externs file prevents the Closure JS compiler from minifying away
+ * names of objects created by Emscripten.
+ * Basically, by defining empty objects and functions here, Closure will
+ * know not to rename them.  This is needed because of our pre-js files,
+ * that is, the JS we hand-write to bundle into the output. That JS will be
+ * hit by the closure compiler and thus needs to know about what functions
+ * have special names and should not be minified.
+ *
+ * Emscripten does not support automatically generating an externs file, so we
+ * do it by hand. The general process is to write some JS code, and then put any
+ * calls to CanvasKit or related things in here. Running ./compile.sh and then
+ * looking at the minified results or running the Release trybot should
+ * verify nothing was missed. Optionally, looking directly at the minified
+ * pathkit.js can be useful when developing locally.
+ *
+ * Docs:
+ *   https://github.com/cljsjs/packages/wiki/Creating-Externs
+ *   https://github.com/google/closure-compiler/wiki/Types-in-the-Closure-Type-System
+ *
+ * Example externs:
+ *   https://github.com/google/closure-compiler/tree/master/externs
+ */
+
+var CanvasKit = {
+	// public API (i.e. things we declare in the pre-js file)
+	Color: function() {},
+	/** @return {CanvasKit.SkRect} */
+	LTRBRect: function() {},
+	/** @return {CanvasKit.SkRect} */
+	XYWHRect: function() {},
+	/** @return {ImageData} */
+	ImageData: function() {},
+
+	GetWebGLContext: function() {},
+	MakeBlurMaskFilter: function() {},
+	MakeCanvas: function() {},
+	MakeCanvasSurface: function() {},
+	MakeGrContext: function() {},
+	/** @return {CanvasKit.SkImage} */
+	MakeImageFromEncoded: function() {},
+	/** @return {LinearCanvasGradient} */
+	MakeLinearGradientShader: function() {},
+	MakeOnScreenGLSurface: function() {},
+	MakePathFromCmds: function() {},
+	MakePathFromOp: function() {},
+	MakePathFromSVGString: function() {},
+	MakeRadialGradientShader: function() {},
+	MakeRenderTarget: function() {},
+	MakeSWCanvasSurface: function() {},
+	MakeManagedAnimation: function() {},
+	MakeSkDashPathEffect: function() {},
+	MakeSkVertices: function() {},
+	MakeSurface: function() {},
+	/** @return {RadialCanvasGradient} */
+	MakeTwoPointConicalGradientShader: function() {},
+	MakeWebGLCanvasSurface: function() {},
+	currentContext: function() {},
+	getColorComponents: function() {},
+	getSkDataBytes: function() {},
+	multiplyByAlpha: function() {},
+	setCurrentContext: function() {},
+
+	// private API (i.e. things declared in the bindings that we use
+	// in the pre-js file)
+	_MakeImage: function() {},
+	_MakeLinearGradientShader: function() {},
+	_MakePathFromCmds: function() {},
+	_MakeRadialGradientShader: function() {},
+	_MakeManagedAnimation: function() {},
+	_MakeSkDashPathEffect: function() {},
+	_MakeSkVertices: function() {},
+	_MakeTwoPointConicalGradientShader: function() {},
+	_decodeImage: function() {},
+	_drawShapedText: function() {},
+	_getRasterDirectSurface: function() {},
+	_getRasterN32PremulSurface: function() {},
+
+	// The testing object is meant to expose internal functions
+	// for more fine-grained testing, e.g. parseColor
+	_testing: {},
+
+	// Objects and properties on CanvasKit
+
+	GrContext: {
+		// public API (from C++ bindings)
+		getResourceCacheLimitBytes: function() {},
+		getResourceCacheUsageBytes: function() {},
+		setResourceCacheLimitBytes: function() {},
+	},
+
+	RSXFormBuilder: function() {},
+	SkColorBuilder: function() {},
+	SkRectBuilder: function() {},
+
+	ShapedText: {
+		// public API (from C++ bindings)
+		getBounds: function() {},
+	},
+
+	SkCanvas: {
+		// public API (from C++ bindings)
+		clear: function() {},
+		clipPath: function() {},
+		clipRect: function() {},
+		concat: function() {},
+		drawArc: function() {},
+		drawImage: function() {},
+		drawImageRect: function() {},
+		drawLine: function() {},
+		drawOval: function() {},
+		drawPaint: function() {},
+		drawPath: function() {},
+		drawPicture: function() {},
+		drawRect: function() {},
+		drawRoundRect: function() {},
+		drawShadow: function() {},
+		drawText: function() {},
+		drawTextBlob: function() {},
+		drawVertices: function() {},
+		flush: function() {},
+		getTotalMatrix: function() {},
+		makeSurface: function() {},
+		restore: function() {},
+		restoreToCount: function() {},
+		rotate: function() {},
+		save: function() {},
+		saveLayer: function() {},
+		scale: function() {},
+		skew: function() {},
+		translate: function() {},
+
+		// private API
+		_drawAtlas: function() {},
+		_drawSimpleText: function() {},
+		_readPixels: function() {},
+		_writePixels: function() {},
+		delete: function() {},
+	},
+
+	SkFont: {
+		// public API (from C++ bindings)
+		getScaleX: function() {},
+		getSize: function() {},
+		getSkewX: function() {},
+		getTypeface: function() {},
+		measureText: function() {},
+		setScaleX: function() {},
+		setSize: function() {},
+		setSkewX: function() {},
+		setTypeface: function() {},
+		// private API (from C++ bindings)
+		_getWidths: function() {},
+	},
+
+	SkFontMgr: {
+		// public API (from C++ bindings)
+		RefDefault: function() {},
+		countFamilies: function() {},
+
+		// private API
+		_makeTypefaceFromData: function() {},
+	},
+
+	SkImage: {
+		// public API (from C++ bindings)
+		height: function() {},
+		width: function() {},
+		// private API
+		_encodeToData: function() {},
+		_encodeToDataWithFormat: function() {},
+		_makeShader: function() {},
+	},
+
+	SkMatrix: {
+		identity: function() {},
+		invert: function() {},
+		mapPoints: function() {},
+		multiply: function() {},
+		rotated: function() {},
+		scaled: function() {},
+		skewed: function() {},
+		translated: function() {},
+	},
+
+	SkPaint: {
+		// public API (from C++ bindings)
+		/** @return {CanvasKit.SkPaint} */
+		copy: function() {},
+		getBlendMode: function() {},
+		getColor: function() {},
+		getFilterQuality: function() {},
+		getStrokeCap: function() {},
+		getStrokeJoin: function() {},
+		getStrokeMiter: function() {},
+		getStrokeWidth: function() {},
+		setAntiAlias: function() {},
+		setBlendMode: function() {},
+		setColor: function() {},
+		setFilterQuality: function() {},
+		setMaskFilter: function() {},
+		setPathEffect: function() {},
+		setShader: function() {},
+		setStrokeCap: function() {},
+		setStrokeJoin: function() {},
+		setStrokeMiter: function() {},
+		setStrokeWidth: function() {},
+		setStyle: function() {},
+
+		//private API
+		delete: function() {},
+	},
+
+	SkPath: {
+		// public API (from C++ bindings)
+		computeTightBounds: function() {},
+		contains: function() {},
+		/** @return {CanvasKit.SkPath} */
+		copy: function() {},
+		countPoints: function() {},
+		equals: function() {},
+		getBounds: function() {},
+		getFillType: function() {},
+		getPoint: function() {},
+		isEmpty: function() {},
+		isVolatile: function() {},
+		reset: function() {},
+		rewind: function() {},
+		setFillType: function() {},
+		setIsVolatile: function() {},
+		toSVGString: function() {},
+
+		// private API
+		_addArc: function() {},
+		_addPath: function() {},
+		_addRect: function() {},
+		_addRoundRect: function() {},
+		_arc: function() {},
+		_arcTo: function() {},
+		_close: function() {},
+		_conicTo: function() {},
+		_cubicTo: function() {},
+		_dash: function() {},
+		_lineTo: function() {},
+		_moveTo: function() {},
+		_op: function() {},
+		_quadTo: function() {},
+		_rect: function() {},
+		_simplify: function() {},
+		_stroke: function() {},
+		_transform: function() {},
+		_trim: function() {},
+		delete: function() {},
+		dump: function() {},
+		dumpHex: function() {},
+	},
+
+	SkPathMeasure: {
+		getLength: function() {},
+		getPosTan: function() {},
+		isClosed: function() {},
+		nextContour: function() {},
+	},
+
+	SkPicture: {
+		DEBUGONLY_serialize: function() {},
+	},
+
+	SkPictureRecorder: {
+		beginRecording: function() {},
+		finishRecordingAsPicture: function() {},
+	},
+
+	SkRect: {
+		fLeft: {},
+		fTop: {},
+		fRight: {},
+		fBottom: {},
+	},
+
+	SkSurface: {
+		// public API (from C++ bindings)
+		/** @return {CanvasKit.SkCanvas} */
+		getCanvas: function() {},
+		/** @return {CanvasKit.SkImage} */
+		makeImageSnapshot: function() {},
+		makeSurface: function() {},
+		grContext: {},
+
+		// private API
+		_flush: function() {},
+		_getRasterN32PremulSurface: function() {},
+		delete: function() {},
+	},
+
+	SkTextBlob: {
+		// public API (both C++ and JS bindings)
+		MakeFromRSXform: function() {},
+		MakeFromText: function() {},
+		MakeOnPath: function() {},
+		// private API (from C++ bindings)
+		_MakeFromRSXform: function() {},
+		_MakeFromText: function() {},
+	},
+
+	SkVertices: {
+		// public API (from C++ bindings)
+		bounds: function() {},
+		mode: function() {},
+		uniqueID: function() {},
+		vertexCount: function() {},
+
+		// private API
+		/** @return {CanvasKit.SkVertices} */
+		_applyBones: function() {},
+	},
+
+	// Constants and Enums
+	gpu: {},
+	skottie: {},
+
+	TRANSPARENT: {},
+	RED: {},
+	BLUE: {},
+	YELLOW: {},
+	CYAN: {},
+	BLACK: {},
+	WHITE: {},
+
+	MOVE_VERB: {},
+	LINE_VERB: {},
+	QUAD_VERB: {},
+	CONIC_VERB: {},
+	CUBIC_VERB: {},
+	CLOSE_VERB: {},
+
+	AlphaType: {
+		Opaque: {},
+		Premul: {},
+		Unpremul: {},
+	},
+
+	BlendMode: {
+		Clear: {},
+		Src: {},
+		Dst: {},
+		SrcOver: {},
+		DstOver: {},
+		SrcIn: {},
+		DstIn: {},
+		SrcOut: {},
+		DstOut: {},
+		SrcATop: {},
+		DstATop: {},
+		Xor: {},
+		Plus: {},
+		Modulate: {},
+		Screen: {},
+		Overlay: {},
+		Darken: {},
+		Lighten: {},
+		ColorDodge: {},
+		ColorBurn: {},
+		HardLight: {},
+		SoftLight: {},
+		Difference: {},
+		Exclusion: {},
+		Multiply: {},
+		Hue: {},
+		Saturation: {},
+		Color: {},
+		Luminosity: {},
+	},
+
+	BlurStyle: {
+		Normal: {},
+		Solid: {},
+		Outer: {},
+		Inner: {},
+	},
+
+	ClipOp: {
+		Difference: {},
+		Intersect: {},
+	},
+
+	ColorType: {
+		Alpha_8: {},
+		RGB_565: {},
+		ARGB_4444: {},
+		RGBA_8888: {},
+		RGB_888x: {},
+		BGRA_8888: {},
+		RGBA_1010102: {},
+		RGB_101010x: {},
+		Gray_8: {},
+		RGBA_F16: {},
+		RGBA_F32: {},
+	},
+
+	FillType: {
+		Winding: {},
+		EvenOdd: {},
+		InverseWinding: {},
+		InverseEvenOdd: {},
+	},
+
+	FilterQuality: {
+		None: {},
+		Low: {},
+		Medium: {},
+		High: {},
+	},
+
+	ImageFormat: {
+		PNG: {},
+		JPEG: {},
+	},
+
+	PaintStyle: {
+		Fill: {},
+		Stroke: {},
+		StrokeAndFill: {},
+	},
+
+	PathOp: {
+		Difference: {},
+		Intersect: {},
+		Union: {},
+		XOR: {},
+		ReverseDifference: {},
+	},
+
+	StrokeCap: {
+		Butt: {},
+		Round: {},
+		Square: {},
+	},
+
+	StrokeJoin: {
+		Miter: {},
+		Round: {},
+		Bevel: {},
+	},
+
+	TextEncoding: {
+		UTF8: {},
+		UTF16: {},
+		UTF32: {},
+		GlyphID: {},
+	},
+
+	TileMode: {
+		Clamp: {},
+		Repeat: {},
+		Mirror: {},
+		Decal: {},
+	},
+
+	VertexMode: {
+		Triangles: {},
+		TrianglesStrip: {},
+		TriangleFan: {},
+	},
+
+	// Things Enscriptem adds for us
+
+	/** Represents the heap of the WASM code
+	 * @type {ArrayBuffer}
+	 */
+	buffer: {},
+	/**
+	 * @type {Float32Array}
+	 */
+	HEAPF32: {},
+	/**
+	 * @type {Uint8Array}
+	 */
+	HEAPU8: {},
+	/**
+	 * @type {Uint16Array}
+	 */
+	HEAPU16: {},
+	/**
+	 * @type {Int32Array}
+	 */
+	HEAP32: {},
+	/**
+	 * @type {Uint32Array}
+	 */
+	HEAPU32: {},
+	_malloc: function() {},
+	_free: function() {},
+	onRuntimeInitialized: function() {},
+};
+
+// Public API things that are newly declared in the JS should go here.
+// It's not enough to declare them above, because closure can still erase them
+// unless they go on the prototype.
+CanvasKit.SkPath.prototype.addArc = function() {};
+CanvasKit.SkPath.prototype.addPath = function() {};
+CanvasKit.SkPath.prototype.addRect = function() {};
+CanvasKit.SkPath.prototype.addRoundRect = function() {};
+CanvasKit.SkPath.prototype.arc = function() {};
+CanvasKit.SkPath.prototype.arcTo = function() {};
+CanvasKit.SkPath.prototype.close = function() {};
+CanvasKit.SkPath.prototype.conicTo = function() {};
+CanvasKit.SkPath.prototype.cubicTo = function() {};
+CanvasKit.SkPath.prototype.dash = function() {};
+CanvasKit.SkPath.prototype.lineTo = function() {};
+CanvasKit.SkPath.prototype.moveTo = function() {};
+CanvasKit.SkPath.prototype.op = function() {};
+CanvasKit.SkPath.prototype.quadTo = function() {};
+CanvasKit.SkPath.prototype.rect = function() {};
+CanvasKit.SkPath.prototype.simplify = function() {};
+CanvasKit.SkPath.prototype.stroke = function() {};
+CanvasKit.SkPath.prototype.transform = function() {};
+CanvasKit.SkPath.prototype.trim = function() {};
+
+CanvasKit.SkPicture.prototype.DEBUGONLY_saveAsFile = function() {};
+
+CanvasKit.SkSurface.prototype.dispose = function() {};
+CanvasKit.SkSurface.prototype.flush = function() {};
+CanvasKit.SkSurface.prototype.requestAnimationFrame = function() {};
+CanvasKit.SkSurface.prototype.captureFrameAsSkPicture = function() {};
+
+/** @return {CanvasKit.SkVertices} */
+CanvasKit.SkVertices.prototype.applyBones = function() {};
+
+CanvasKit.SkImage.prototype.encodeToData = function() {};
+CanvasKit.SkImage.prototype.makeShader = function() {};
+
+CanvasKit.SkCanvas.prototype.drawAtlas = function() {};
+CanvasKit.SkCanvas.prototype.drawText = function() {};
+/** @return {Uint8Array} */
+CanvasKit.SkCanvas.prototype.readPixels = function() {};
+CanvasKit.SkCanvas.prototype.writePixels = function() {};
+
+CanvasKit.SkFontMgr.prototype.MakeTypefaceFromData = function() {};
+
+CanvasKit.SkFont.prototype.getWidths = function() {};
+
+CanvasKit.RSXFormBuilder.prototype.build = function() {};
+CanvasKit.RSXFormBuilder.prototype.delete = function() {};
+CanvasKit.RSXFormBuilder.prototype.push = function() {};
+CanvasKit.RSXFormBuilder.prototype.set = function() {};
+
+CanvasKit.SkColorBuilder.prototype.build = function() {};
+CanvasKit.SkColorBuilder.prototype.delete = function() {};
+CanvasKit.SkColorBuilder.prototype.push = function() {};
+CanvasKit.SkColorBuilder.prototype.set = function() {};
+
+// Define StrokeOpts object
+var StrokeOpts = {};
+StrokeOpts.prototype.width;
+StrokeOpts.prototype.miter_limit;
+StrokeOpts.prototype.cap;
+StrokeOpts.prototype.join;
+StrokeOpts.prototype.precision;
+
+// Define everything created in the canvas2d spec here
+var HTMLCanvas = {};
+HTMLCanvas.prototype.decodeImage = function() {};
+HTMLCanvas.prototype.dispose = function() {};
+HTMLCanvas.prototype.getContext = function() {};
+HTMLCanvas.prototype.loadFont = function() {};
+HTMLCanvas.prototype.makePath2D = function() {};
+HTMLCanvas.prototype.toDataURL = function() {};
+
+var CanvasRenderingContext2D = {};
+CanvasRenderingContext2D.prototype.addHitRegion = function() {};
+CanvasRenderingContext2D.prototype.arc = function() {};
+CanvasRenderingContext2D.prototype.arcTo = function() {};
+CanvasRenderingContext2D.prototype.beginPath = function() {};
+CanvasRenderingContext2D.prototype.bezierCurveTo = function() {};
+CanvasRenderingContext2D.prototype.clearHitRegions = function() {};
+CanvasRenderingContext2D.prototype.clearRect = function() {};
+CanvasRenderingContext2D.prototype.clip = function() {};
+CanvasRenderingContext2D.prototype.closePath = function() {};
+CanvasRenderingContext2D.prototype.createImageData = function() {};
+CanvasRenderingContext2D.prototype.createLinearGradient = function() {};
+CanvasRenderingContext2D.prototype.createPattern = function() {};
+CanvasRenderingContext2D.prototype.createRadialGradient = function() {};
+CanvasRenderingContext2D.prototype.drawFocusIfNeeded = function() {};
+CanvasRenderingContext2D.prototype.drawImage = function() {};
+CanvasRenderingContext2D.prototype.ellipse = function() {};
+CanvasRenderingContext2D.prototype.fill = function() {};
+CanvasRenderingContext2D.prototype.fillRect = function() {};
+CanvasRenderingContext2D.prototype.fillText = function() {};
+CanvasRenderingContext2D.prototype.getImageData = function() {};
+CanvasRenderingContext2D.prototype.getLineDash = function() {};
+CanvasRenderingContext2D.prototype.isPointInPath = function() {};
+CanvasRenderingContext2D.prototype.isPointInStroke = function() {};
+CanvasRenderingContext2D.prototype.lineTo = function() {};
+CanvasRenderingContext2D.prototype.measureText = function() {};
+CanvasRenderingContext2D.prototype.moveTo = function() {};
+CanvasRenderingContext2D.prototype.putImageData = function() {};
+CanvasRenderingContext2D.prototype.quadraticCurveTo = function() {};
+CanvasRenderingContext2D.prototype.rect = function() {};
+CanvasRenderingContext2D.prototype.removeHitRegion = function() {};
+CanvasRenderingContext2D.prototype.resetTransform = function() {};
+CanvasRenderingContext2D.prototype.restore = function() {};
+CanvasRenderingContext2D.prototype.rotate = function() {};
+CanvasRenderingContext2D.prototype.save = function() {};
+CanvasRenderingContext2D.prototype.scale = function() {};
+CanvasRenderingContext2D.prototype.scrollPathIntoView = function() {};
+CanvasRenderingContext2D.prototype.setLineDash = function() {};
+CanvasRenderingContext2D.prototype.setTransform = function() {};
+CanvasRenderingContext2D.prototype.stroke = function() {};
+CanvasRenderingContext2D.prototype.strokeRect = function() {};
+CanvasRenderingContext2D.prototype.strokeText = function() {};
+CanvasRenderingContext2D.prototype.transform = function() {};
+CanvasRenderingContext2D.prototype.translate = function() {};
+
+var Path2D = {};
+Path2D.prototype.addPath = function() {};
+Path2D.prototype.arc = function() {};
+Path2D.prototype.arcTo = function() {};
+Path2D.prototype.bezierCurveTo = function() {};
+Path2D.prototype.closePath = function() {};
+Path2D.prototype.ellipse = function() {};
+Path2D.prototype.lineTo = function() {};
+Path2D.prototype.moveTo = function() {};
+Path2D.prototype.quadraticCurveTo = function() {};
+Path2D.prototype.rect = function() {};
+
+var LinearCanvasGradient = {};
+LinearCanvasGradient.prototype.addColorStop = function() {};
+var RadialCanvasGradient = {};
+RadialCanvasGradient.prototype.addColorStop = function() {};
+var CanvasPattern = {};
+CanvasPattern.prototype.setTransform = function() {};
+
+var ImageData = {
+	/**
+	 * @type {Uint8ClampedArray}
+	 */
+	data: {},
+	height: {},
+	width: {},
+};
+
+var DOMMatrix = {
+	a: {},
+	b: {},
+	c: {},
+	d: {},
+	e: {},
+	f: {},
+};
+
+// Not sure why this is needed - might be a bug in emsdk that this isn't properly declared.
+function loadWebAssemblyModule() {};
diff --git a/experimental/canvaskit/fonts/NotoMono-Regular.ttf b/modules/canvaskit/fonts/NotoMono-Regular.ttf
similarity index 100%
rename from experimental/canvaskit/fonts/NotoMono-Regular.ttf
rename to modules/canvaskit/fonts/NotoMono-Regular.ttf
Binary files differ
diff --git a/modules/canvaskit/fonts/README.md b/modules/canvaskit/fonts/README.md
new file mode 100644
index 0000000..e47e1a3
--- /dev/null
+++ b/modules/canvaskit/fonts/README.md
@@ -0,0 +1,3 @@
+To generate the files in here:
+
+    python tools/embed_resources.py --name SK_EMBEDDED_FONTS --input ~/Downloads/NotoMono-Regular.ttf --output modules/canvaskit/NotoMono-Regular.ttf.cpp --align 4
\ No newline at end of file
diff --git a/modules/canvaskit/gpu.js b/modules/canvaskit/gpu.js
new file mode 100644
index 0000000..160593a
--- /dev/null
+++ b/modules/canvaskit/gpu.js
@@ -0,0 +1,104 @@
+// Adds compile-time JS functions to augment the CanvasKit interface.
+// Specifically, anything that should only be on the GPU version of canvaskit.
+(function(CanvasKit){
+    CanvasKit._extraInitializations = CanvasKit._extraInitializations || [];
+    CanvasKit._extraInitializations.push(function() {
+      function get(obj, attr, defaultValue) {
+        if (obj && obj.hasOwnProperty(attr)) {
+          return obj[attr];
+        }
+        return defaultValue;
+      }
+
+      function makeWebGLContext(canvas, attrs) {
+        // These defaults come from the emscripten _emscripten_webgl_create_context
+        var contextAttributes = {
+          alpha: get(attrs, 'alpha', 1),
+          depth: get(attrs, 'depth', 1),
+          stencil: get(attrs, 'stencil', 0),
+          antialias: get(attrs, 'antialias', 1),
+          premultipliedAlpha: get(attrs, 'premultipliedAlpha', 1),
+          preserveDrawingBuffer: get(attrs, 'preserveDrawingBuffer', 0),
+          preferLowPowerToHighPerformance: get(attrs, 'preferLowPowerToHighPerformance', 0),
+          failIfMajorPerformanceCaveat: get(attrs, 'failIfMajorPerformanceCaveat', 0),
+          majorVersion: get(attrs, 'majorVersion', 1),
+          minorVersion: get(attrs, 'minorVersion', 0),
+          enableExtensionsByDefault: get(attrs, 'enableExtensionsByDefault', 1),
+          explicitSwapControl: get(attrs, 'explicitSwapControl', 0),
+          renderViaOffscreenBackBuffer: get(attrs, 'renderViaOffscreenBackBuffer', 0),
+        };
+        if (!canvas) {
+          SkDebug('null canvas passed into makeWebGLContext');
+          return 0;
+        }
+        // This check is from the emscripten version
+        if (contextAttributes['explicitSwapControl']) {
+          SkDebug('explicitSwapControl is not supported');
+          return 0;
+        }
+        // GL is an enscripten provided helper
+        // See https://github.com/emscripten-core/emscripten/blob/incoming/src/library_webgl.js
+        return GL.createContext(canvas, contextAttributes);
+      }
+
+      CanvasKit.GetWebGLContext = function(canvas, attrs) {
+        return makeWebGLContext(canvas, attrs);
+      };
+
+      // arg can be of types:
+      //  - String - in which case it is interpreted as an id of a
+      //          canvas element.
+      //  - HTMLCanvasElement - in which the provided canvas element will
+      //          be used directly.
+      // Width and height can be provided to override those on the canvas
+      // element, or specify a height for when a context is provided.
+      CanvasKit.MakeWebGLCanvasSurface = function(arg, width, height) {
+        var canvas = arg;
+        if (canvas.tagName !== 'CANVAS') {
+          canvas = document.getElementById(arg);
+          if (!canvas) {
+            throw 'Canvas with id ' + arg + ' was not found';
+          }
+        }
+        // we are ok with all the defaults
+        var ctx = this.GetWebGLContext(canvas);
+
+        if (!ctx || ctx < 0) {
+          throw 'failed to create webgl context: err ' + ctx;
+        }
+
+        if (!canvas && (!width || !height)) {
+          throw 'height and width must be provided with context';
+        }
+
+        var grcontext = this.MakeGrContext(ctx);
+
+        // Bump the default resource cache limit.
+        var RESOURCE_CACHE_BYTES = 256 * 1024 * 1024;
+        grcontext.setResourceCacheLimitBytes(RESOURCE_CACHE_BYTES);
+
+        // Maybe better to use clientWidth/height.  See:
+        // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
+        var surface = this.MakeOnScreenGLSurface(grcontext,
+                                                 width  || canvas.width,
+                                                 height || canvas.height);
+        if (!surface) {
+          SkDebug('falling back from GPU implementation to a SW based one');
+          // we need to throw away the old canvas (which was locked to
+          // a webGL context) and create a new one so we can
+          var newCanvas = canvas.cloneNode(true);
+          var parent = canvas.parentNode;
+          parent.replaceChild(newCanvas, canvas);
+          // add a class so the user can detect that it was replaced.
+          newCanvas.classList.add('ck-replaced');
+
+          return CanvasKit.MakeSWCanvasSurface(newCanvas);
+        }
+        surface._context = ctx;
+        surface.grContext = grcontext;
+        return surface;
+      };
+      // Default to trying WebGL first.
+      CanvasKit.MakeCanvasSurface = CanvasKit.MakeWebGLCanvasSurface;
+    });
+}(Module)); // When this file is loaded in, the high level object is "Module";
diff --git a/modules/canvaskit/helper.js b/modules/canvaskit/helper.js
new file mode 100644
index 0000000..d6510e6
--- /dev/null
+++ b/modules/canvaskit/helper.js
@@ -0,0 +1,387 @@
+// helper JS that could be used anywhere in the glue code
+
+function clamp(c) {
+  return Math.round(Math.max(0, Math.min(c || 0, 255)));
+}
+
+// Colors are just a 32 bit number with 8 bits each of a, r, g, b
+// The API is the same as CSS's representation of color rgba(), that is
+// r,g,b are 0-255, and a is 0.0 to 1.0.
+// if a is omitted, it will be assumed to be 1.0
+CanvasKit.Color = function(r, g, b, a) {
+  if (a === undefined) {
+      a = 1;
+  }
+  // The >>> 0 converts the signed int to an unsigned int. Skia's
+  // SkColor object is an unsigned int.
+  // https://stackoverflow.com/a/14891172
+  return ((clamp(a*255) << 24) | (clamp(r) << 16) | (clamp(g) << 8) | (clamp(b) << 0)) >>> 0;
+}
+
+// returns [r, g, b, a] from a color
+// where a is scaled between 0 and 1.0
+CanvasKit.getColorComponents = function(color) {
+  return [
+     (color >> 16) & 0xFF,
+     (color >>  8) & 0xFF,
+     (color >>  0) & 0xFF,
+    ((color >> 24) & 0xFF) / 255,
+  ]
+}
+
+CanvasKit.multiplyByAlpha = function(color, alpha) {
+  if (alpha === 1) {
+    return color;
+  }
+  // extract as int from 0 to 255
+  var a = (color >> 24) & 0xFF;
+  a *= alpha;
+  // mask off the old alpha
+  color &= 0xFFFFFF;
+  // back to unsigned int to match SkColor.
+  return (clamp(a) << 24 | color) >>> 0;
+}
+
+function radiansToDegrees(rad) {
+  return (rad / Math.PI) * 180;
+}
+
+function degreesToRadians(deg) {
+  return (deg / 180) * Math.PI;
+}
+
+// See https://stackoverflow.com/a/31090240
+// This contraption keeps closure from minifying away the check
+// if btoa is defined *and* prevents runtime "btoa" or "window" is not defined.
+// Defined outside any scopes to make it available in all files.
+var isNode = !(new Function("try {return this===window;}catch(e){ return false;}")());
+
+function almostEqual(floata, floatb) {
+  return Math.abs(floata - floatb) < 0.00001;
+}
+
+
+var nullptr = 0; // emscripten doesn't like to take null as uintptr_t
+
+// arr can be a normal JS array or a TypedArray
+// dest is something like CanvasKit.HEAPF32
+function copy1dArray(arr, dest) {
+  if (!arr || !arr.length) {
+    return nullptr;
+  }
+  var ptr = CanvasKit._malloc(arr.length * dest.BYTES_PER_ELEMENT);
+  // In c++ terms, the WASM heap is a uint8_t*, a long buffer/array of single
+  // byte elements. When we run _malloc, we always get an offset/pointer into
+  // that block of memory.
+  // CanvasKit exposes some different views to make it easier to work with
+  // different types. HEAPF32 for example, exposes it as a float*
+  // However, to make the ptr line up, we have to do some pointer arithmetic.
+  // Concretely, we need to convert ptr to go from an index into a 1-byte-wide
+  // buffer to an index into a 4-byte-wide buffer (in the case of HEAPF32)
+  // and thus we divide ptr by 4.
+  dest.set(arr, ptr / dest.BYTES_PER_ELEMENT);
+  return ptr;
+}
+
+// arr should be a non-jagged 2d JS array (TypedArrays can't be nested
+//     inside themselves.)
+// dest is something like CanvasKit.HEAPF32
+function copy2dArray(arr, dest) {
+  if (!arr || !arr.length) {
+    return nullptr;
+  }
+  var ptr = CanvasKit._malloc(arr.length * arr[0].length * dest.BYTES_PER_ELEMENT);
+  var idx = 0;
+  var adjustedPtr = ptr / dest.BYTES_PER_ELEMENT;
+  for (var r = 0; r < arr.length; r++) {
+    for (var c = 0; c < arr[0].length; c++) {
+      dest[adjustedPtr + idx] = arr[r][c];
+      idx++;
+    }
+  }
+  return ptr;
+}
+
+// arr should be a non-jagged 3d JS array (TypedArrays can't be nested
+//     inside themselves.)
+// dest is something like CanvasKit.HEAPF32
+function copy3dArray(arr, dest) {
+  if (!arr || !arr.length || !arr[0].length) {
+    return nullptr;
+  }
+  var ptr = CanvasKit._malloc(arr.length * arr[0].length * arr[0][0].length * dest.BYTES_PER_ELEMENT);
+  var idx = 0;
+  var adjustedPtr = ptr / dest.BYTES_PER_ELEMENT;
+  for (var x = 0; x < arr.length; x++) {
+    for (var y = 0; y < arr[0].length; y++) {
+      for (var z = 0; z < arr[0][0].length; z++) {
+        dest[adjustedPtr + idx] = arr[x][y][z];
+        idx++;
+      }
+    }
+  }
+  return ptr;
+}
+
+// Caching the Float32Arrays can save having to reallocate them
+// over and over again.
+var Float32ArrayCache = {};
+
+// Takes a 2D array of commands and puts them into the WASM heap
+// as a 1D array. This allows them to referenced from the C++ code.
+// Returns a 2 element array, with the first item being essentially a
+// pointer to the array and the second item being the length of
+// the new 1D array.
+//
+// Example usage:
+// let cmds = [
+//   [CanvasKit.MOVE_VERB, 0, 10],
+//   [CanvasKit.LINE_VERB, 30, 40],
+//   [CanvasKit.QUAD_VERB, 20, 50, 45, 60],
+// ];
+function loadCmdsTypedArray(arr) {
+  var len = 0;
+  for (var r = 0; r < arr.length; r++) {
+    len += arr[r].length;
+  }
+
+  var ta;
+  if (Float32ArrayCache[len]) {
+    ta = Float32ArrayCache[len];
+  } else {
+    ta = new Float32Array(len);
+    Float32ArrayCache[len] = ta;
+  }
+  // Flatten into a 1d array
+  var i = 0;
+  for (var r = 0; r < arr.length; r++) {
+    for (var c = 0; c < arr[r].length; c++) {
+      var item = arr[r][c];
+      ta[i] = item;
+      i++;
+    }
+  }
+
+  var ptr = copy1dArray(ta, CanvasKit.HEAPF32);
+  return [ptr, len];
+}
+
+function saveBytesToFile(bytes, fileName) {
+  if (!isNode) {
+    // https://stackoverflow.com/a/32094834
+    var blob = new Blob([bytes], {type: 'application/octet-stream'});
+    url = window.URL.createObjectURL(blob);
+    var a = document.createElement('a');
+    document.body.appendChild(a);
+    a.href = url;
+    a.download = fileName;
+    a.click();
+    // clean up after because FF might not download it synchronously
+    setTimeout(function() {
+      URL.revokeObjectURL(url);
+      a.remove();
+    }, 50);
+  } else {
+    var fs = require('fs');
+    // https://stackoverflow.com/a/42006750
+    // https://stackoverflow.com/a/47018122
+    fs.writeFile(fileName, new Buffer(bytes), function(err) {
+      if (err) throw err;
+    });
+  }
+}
+/**
+ * Generic helper for dealing with an array of four floats.
+ */
+CanvasKit.FourFloatArrayHelper = function() {
+  this._floats = [];
+  this._ptr = null;
+
+  Object.defineProperty(this, 'length', {
+    enumerable: true,
+    get: function() {
+      return this._floats.length / 4;
+    },
+  });
+}
+
+/**
+ * push the four floats onto the end of the array - if build() has already
+ * been called, the call will return without modifying anything.
+ */
+CanvasKit.FourFloatArrayHelper.prototype.push = function(f1, f2, f3, f4) {
+  if (this._ptr) {
+    SkDebug('Cannot push more points - already built');
+    return;
+  }
+  this._floats.push(f1, f2, f3, f4);
+}
+
+/**
+ * Set the four floats at a given index - if build() has already
+ * been called, the WASM memory will be written to directly.
+ */
+CanvasKit.FourFloatArrayHelper.prototype.set = function(idx, f1, f2, f3, f4) {
+  if (idx < 0 || idx >= this._floats.length/4) {
+    SkDebug('Cannot set index ' + idx + ', it is out of range', this._floats.length/4);
+    return;
+  }
+  idx *= 4;
+  var BYTES_PER_ELEMENT = 4;
+  if (this._ptr) {
+    // convert this._ptr from uint8_t* to SkScalar* by dividing by 4
+    var floatPtr = (this._ptr / BYTES_PER_ELEMENT) + idx;
+    CanvasKit.HEAPF32[floatPtr]     = f1;
+    CanvasKit.HEAPF32[floatPtr + 1] = f2;
+    CanvasKit.HEAPF32[floatPtr + 2] = f3;
+    CanvasKit.HEAPF32[floatPtr + 3] = f4;
+    return;
+  }
+  this._floats[idx]     = f1;
+  this._floats[idx + 1] = f2;
+  this._floats[idx + 2] = f3;
+  this._floats[idx + 3] = f4;
+}
+
+/**
+ * Copies the float data to the WASM memory and returns a pointer
+ * to that allocated memory. Once build has been called, this
+ * float array cannot be made bigger.
+ */
+CanvasKit.FourFloatArrayHelper.prototype.build = function() {
+  if (this._ptr) {
+    return this._ptr;
+  }
+  this._ptr = copy1dArray(this._floats, CanvasKit.HEAPF32);
+  return this._ptr;
+}
+
+/**
+ * Frees the wasm memory associated with this array. Of note,
+ * the points are not removed, so push/set/build can all
+ * be called to make a newly allocated (possibly bigger)
+ * float array.
+ */
+CanvasKit.FourFloatArrayHelper.prototype.delete = function() {
+  if (this._ptr) {
+    CanvasKit._free(this._ptr);
+    this._ptr = null;
+  }
+}
+
+/**
+ * Generic helper for dealing with an array of unsigned ints.
+ */
+CanvasKit.OneUIntArrayHelper = function() {
+  this._uints = [];
+  this._ptr = null;
+
+  Object.defineProperty(this, 'length', {
+    enumerable: true,
+    get: function() {
+      return this._uints.length;
+    },
+  });
+}
+
+/**
+ * push the unsigned int onto the end of the array - if build() has already
+ * been called, the call will return without modifying anything.
+ */
+CanvasKit.OneUIntArrayHelper.prototype.push = function(u) {
+  if (this._ptr) {
+    SkDebug('Cannot push more points - already built');
+    return;
+  }
+  this._uints.push(u);
+}
+
+/**
+ * Set the uint at a given index - if build() has already
+ * been called, the WASM memory will be written to directly.
+ */
+CanvasKit.OneUIntArrayHelper.prototype.set = function(idx, u) {
+  if (idx < 0 || idx >= this._uints.length) {
+    SkDebug('Cannot set index ' + idx + ', it is out of range', this._uints.length);
+    return;
+  }
+  idx *= 4;
+  var BYTES_PER_ELEMENT = 4;
+  if (this._ptr) {
+    // convert this._ptr from uint8_t* to SkScalar* by dividing by 4
+    var uintPtr = (this._ptr / BYTES_PER_ELEMENT) + idx;
+    CanvasKit.HEAPU32[uintPtr] = u;
+    return;
+  }
+  this._uints[idx] = u;
+}
+
+/**
+ * Copies the uint data to the WASM memory and returns a pointer
+ * to that allocated memory. Once build has been called, this
+ * unit array cannot be made bigger.
+ */
+CanvasKit.OneUIntArrayHelper.prototype.build = function() {
+  if (this._ptr) {
+    return this._ptr;
+  }
+  this._ptr = copy1dArray(this._uints, CanvasKit.HEAPU32);
+  return this._ptr;
+}
+
+/**
+ * Frees the wasm memory associated with this array. Of note,
+ * the points are not removed, so push/set/build can all
+ * be called to make a newly allocated (possibly bigger)
+ * uint array.
+ */
+CanvasKit.OneUIntArrayHelper.prototype.delete = function() {
+  if (this._ptr) {
+    CanvasKit._free(this._ptr);
+    this._ptr = null;
+  }
+}
+
+/**
+ * Helper for building an array of SkRects (which are just structs
+ * of 4 floats).
+ *
+ * It can be more performant to use this helper, as
+ * the C++-side array is only allocated once (on the first call)
+ * to build. Subsequent set() operations operate directly on
+ * the C++-side array, avoiding having to re-allocate (and free)
+ * the array every time.
+ *
+ * Input points are taken as left, top, right, bottom
+ */
+CanvasKit.SkRectBuilder = CanvasKit.FourFloatArrayHelper;
+/**
+ * Helper for building an array of RSXForms (which are just structs
+ * of 4 floats).
+ *
+ * It can be more performant to use this helper, as
+ * the C++-side array is only allocated once (on the first call)
+ * to build. Subsequent set() operations operate directly on
+ * the C++-side array, avoiding having to re-allocate (and free)
+ * the array every time.
+ *
+ *  An RSXForm is a compressed form of a rotation+scale matrix.
+ *
+ *  [ scos    -ssin    tx ]
+ *  [ ssin     scos    ty ]
+ *  [    0        0     1 ]
+ *
+ * Input points are taken as scos, ssin, tx, ty
+ */
+CanvasKit.RSXFormBuilder = CanvasKit.FourFloatArrayHelper;
+
+/**
+ * Helper for building an array of SkColor
+ *
+ * It can be more performant to use this helper, as
+ * the C++-side array is only allocated once (on the first call)
+ * to build. Subsequent set() operations operate directly on
+ * the C++-side array, avoiding having to re-allocate (and free)
+ * the array every time.
+ */
+CanvasKit.SkColorBuilder = CanvasKit.OneUIntArrayHelper;
diff --git a/experimental/canvaskit/htmlcanvas/_namedcolors.js b/modules/canvaskit/htmlcanvas/_namedcolors.js
similarity index 100%
rename from experimental/canvaskit/htmlcanvas/_namedcolors.js
rename to modules/canvaskit/htmlcanvas/_namedcolors.js
diff --git a/experimental/canvaskit/htmlcanvas/canvas2dcontext.js b/modules/canvaskit/htmlcanvas/canvas2dcontext.js
similarity index 100%
rename from experimental/canvaskit/htmlcanvas/canvas2dcontext.js
rename to modules/canvaskit/htmlcanvas/canvas2dcontext.js
diff --git a/modules/canvaskit/htmlcanvas/color.js b/modules/canvaskit/htmlcanvas/color.js
new file mode 100644
index 0000000..49a56f7
--- /dev/null
+++ b/modules/canvaskit/htmlcanvas/color.js
@@ -0,0 +1,96 @@
+// Functions dealing with parsing/stringifying color go here.
+
+// Create the following with
+// node ./htmlcanvas/_namedcolors.js --expose-wasm
+// JS/closure doesn't have a constexpr like thing which
+// would really help here. Since we don't, we pre-compute
+// the map, which saves (a tiny amount of) startup time
+// and (a small amount of) code size.
+/* @dict */
+var colorMap = {'aliceblue':4293982463,'antiquewhite':4294634455,'aqua':4278255615,'aquamarine':4286578644,'azure':4293984255,'beige':4294309340,'bisque':4294960324,'black':4278190080,'blanchedalmond':4294962125,'blue':4278190335,'blueviolet':4287245282,'brown':4289014314,'burlywood':4292786311,'cadetblue':4284456608,'chartreuse':4286578432,'chocolate':4291979550,'coral':4294934352,'cornflowerblue':4284782061,'cornsilk':4294965468,'crimson':4292613180,'cyan':4278255615,'darkblue':4278190219,'darkcyan':4278225803,'darkgoldenrod':4290283019,'darkgray':4289309097,'darkgreen':4278215680,'darkgrey':4289309097,'darkkhaki':4290623339,'darkmagenta':4287299723,'darkolivegreen':4283788079,'darkorange':4294937600,'darkorchid':4288230092,'darkred':4287299584,'darksalmon':4293498490,'darkseagreen':4287609999,'darkslateblue':4282924427,'darkslategray':4281290575,'darkslategrey':4281290575,'darkturquoise':4278243025,'darkviolet':4287889619,'deeppink':4294907027,'deepskyblue':4278239231,'dimgray':4285098345,'dimgrey':4285098345,'dodgerblue':4280193279,'firebrick':4289864226,'floralwhite':4294966000,'forestgreen':4280453922,'fuchsia':4294902015,'gainsboro':4292664540,'ghostwhite':4294506751,'gold':4294956800,'goldenrod':4292519200,'gray':4286611584,'green':4278222848,'greenyellow':4289593135,'grey':4286611584,'honeydew':4293984240,'hotpink':4294928820,'indianred':4291648604,'indigo':4283105410,'ivory':4294967280,'khaki':4293977740,'lavender':4293322490,'lavenderblush':4294963445,'lawngreen':4286381056,'lemonchiffon':4294965965,'lightblue':4289583334,'lightcoral':4293951616,'lightcyan':4292935679,'lightgoldenrodyellow':4294638290,'lightgray':4292072403,'lightgreen':4287688336,'lightgrey':4292072403,'lightpink':4294948545,'lightsalmon':4294942842,'lightseagreen':4280332970,'lightskyblue':4287090426,'lightslategray':4286023833,'lightslategrey':4286023833,'lightsteelblue':4289774814,'lightyellow':4294967264,'lime':4278255360,'limegreen':4281519410,'linen':4294635750,'magenta':4294902015,'maroon':4286578688,'mediumaquamarine':4284927402,'mediumblue':4278190285,'mediumorchid':4290401747,'mediumpurple':4287852763,'mediumseagreen':4282168177,'mediumslateblue':4286277870,'mediumspringgreen':4278254234,'mediumturquoise':4282962380,'mediumvioletred':4291237253,'midnightblue':4279834992,'mintcream':4294311930,'mistyrose':4294960353,'moccasin':4294960309,'navajowhite':4294958765,'navy':4278190208,'oldlace':4294833638,'olive':4286611456,'olivedrab':4285238819,'orange':4294944000,'orangered':4294919424,'orchid':4292505814,'palegoldenrod':4293847210,'palegreen':4288215960,'paleturquoise':4289720046,'palevioletred':4292571283,'papayawhip':4294963157,'peachpuff':4294957753,'peru':4291659071,'pink':4294951115,'plum':4292714717,'powderblue':4289781990,'purple':4286578816,'rebeccapurple':4284887961,'red':4294901760,'rosybrown':4290547599,'royalblue':4282477025,'saddlebrown':4287317267,'salmon':4294606962,'sandybrown':4294222944,'seagreen':4281240407,'seashell':4294964718,'sienna':4288696877,'silver':4290822336,'skyblue':4287090411,'slateblue':4285160141,'slategray':4285563024,'slategrey':4285563024,'snow':4294966010,'springgreen':4278255487,'steelblue':4282811060,'tan':4291998860,'teal':4278222976,'thistle':4292394968,'transparent':0,'tomato':4294927175,'turquoise':4282441936,'violet':4293821166,'wheat':4294303411,'white':4294967295,'whitesmoke':4294309365,'yellow':4294967040,'yellowgreen':4288335154};
+function colorToString(skcolor) {
+  // https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color
+  var components = CanvasKit.getColorComponents(skcolor);
+  var r = components[0];
+  var g = components[1];
+  var b = components[2];
+  var a = components[3];
+  if (a === 1.0) {
+    // hex
+    r = r.toString(16).toLowerCase();
+    g = g.toString(16).toLowerCase();
+    b = b.toString(16).toLowerCase();
+    r = (r.length === 1 ? '0'+r: r);
+    g = (g.length === 1 ? '0'+g: g);
+    b = (b.length === 1 ? '0'+b: b);
+    return '#'+r+g+b;
+  } else {
+    a = (a === 0 || a === 1) ? a : a.toFixed(8);
+    return 'rgba('+r+', '+g+', '+b+', '+a+')';
+  }
+}
+
+function valueOrPercent(aStr) {
+  var a = parseFloat(aStr) || 1;
+  if (aStr && aStr.indexOf('%') !== -1) {
+    return a / 100;
+  }
+  return a;
+}
+
+function parseColor(colorStr) {
+  colorStr = colorStr.toLowerCase();
+  // See https://drafts.csswg.org/css-color/#typedef-hex-color
+  if (colorStr.startsWith('#')) {
+    var r, g, b, a = 255;
+    switch (colorStr.length) {
+      case 9: // 8 hex chars #RRGGBBAA
+        a = parseInt(colorStr.slice(7, 9), 16);
+      case 7: // 6 hex chars #RRGGBB
+        r = parseInt(colorStr.slice(1, 3), 16);
+        g = parseInt(colorStr.slice(3, 5), 16);
+        b = parseInt(colorStr.slice(5, 7), 16);
+        break;
+      case 5: // 4 hex chars #RGBA
+        // multiplying by 17 is the same effect as
+        // appending another character of the same value
+        // e.g. e => ee == 14 => 238
+        a = parseInt(colorStr.slice(4, 5), 16) * 17;
+      case 4: // 6 hex chars #RGB
+        r = parseInt(colorStr.slice(1, 2), 16) * 17;
+        g = parseInt(colorStr.slice(2, 3), 16) * 17;
+        b = parseInt(colorStr.slice(3, 4), 16) * 17;
+        break;
+    }
+    return CanvasKit.Color(r, g, b, a/255);
+
+  } else if (colorStr.startsWith('rgba')) {
+    // Trim off rgba( and the closing )
+    colorStr = colorStr.slice(5, -1);
+    var nums = colorStr.split(',');
+    return CanvasKit.Color(+nums[0], +nums[1], +nums[2],
+                           valueOrPercent(nums[3]));
+  } else if (colorStr.startsWith('rgb')) {
+    // Trim off rgba( and the closing )
+    colorStr = colorStr.slice(4, -1);
+    var nums = colorStr.split(',');
+    // rgb can take 3 or 4 arguments
+    return CanvasKit.Color(+nums[0], +nums[1], +nums[2],
+                           valueOrPercent(nums[3]));
+  } else if (colorStr.startsWith('gray(')) {
+    // TODO
+  } else if (colorStr.startsWith('hsl')) {
+    // TODO
+  } else {
+    // Try for named color
+    var nc = colorMap[colorStr];
+    if (nc !== undefined) {
+      return nc;
+    }
+  }
+  SkDebug('unrecognized color ' + colorStr);
+  return CanvasKit.BLACK;
+}
+
+CanvasKit._testing['parseColor'] = parseColor;
+CanvasKit._testing['colorToString'] = colorToString;
diff --git a/experimental/canvaskit/htmlcanvas/font.js b/modules/canvaskit/htmlcanvas/font.js
similarity index 100%
rename from experimental/canvaskit/htmlcanvas/font.js
rename to modules/canvaskit/htmlcanvas/font.js
diff --git a/experimental/canvaskit/htmlcanvas/htmlcanvas.js b/modules/canvaskit/htmlcanvas/htmlcanvas.js
similarity index 100%
rename from experimental/canvaskit/htmlcanvas/htmlcanvas.js
rename to modules/canvaskit/htmlcanvas/htmlcanvas.js
diff --git a/experimental/canvaskit/htmlcanvas/imagedata.js b/modules/canvaskit/htmlcanvas/imagedata.js
similarity index 100%
rename from experimental/canvaskit/htmlcanvas/imagedata.js
rename to modules/canvaskit/htmlcanvas/imagedata.js
diff --git a/experimental/canvaskit/htmlcanvas/lineargradient.js b/modules/canvaskit/htmlcanvas/lineargradient.js
similarity index 100%
rename from experimental/canvaskit/htmlcanvas/lineargradient.js
rename to modules/canvaskit/htmlcanvas/lineargradient.js
diff --git a/experimental/canvaskit/htmlcanvas/path2d.js b/modules/canvaskit/htmlcanvas/path2d.js
similarity index 100%
rename from experimental/canvaskit/htmlcanvas/path2d.js
rename to modules/canvaskit/htmlcanvas/path2d.js
diff --git a/modules/canvaskit/htmlcanvas/pattern.js b/modules/canvaskit/htmlcanvas/pattern.js
new file mode 100644
index 0000000..056f79f
--- /dev/null
+++ b/modules/canvaskit/htmlcanvas/pattern.js
@@ -0,0 +1,68 @@
+function CanvasPattern(image, repetition) {
+  this._shader = null;
+  // image should be an SkImage returned from HTMLCanvas.decodeImage()
+  this._image = image;
+  this._transform = CanvasKit.SkMatrix.identity();
+
+  if (repetition === '') {
+    repetition = 'repeat';
+  }
+  switch(repetition) {
+    case 'repeat-x':
+      this._tileX = CanvasKit.TileMode.Repeat;
+      // Skia's 'clamp' mode repeats the last row/column
+      // which looks very very strange.
+      // Decal mode does just transparent copying, which
+      // is exactly what the spec wants.
+      this._tileY = CanvasKit.TileMode.Decal;
+      break;
+    case 'repeat-y':
+      this._tileX = CanvasKit.TileMode.Decal;
+      this._tileY = CanvasKit.TileMode.Repeat;
+      break;
+    case 'repeat':
+      this._tileX = CanvasKit.TileMode.Repeat;
+      this._tileY = CanvasKit.TileMode.Repeat;
+      break;
+    case 'no-repeat':
+      this._tileX = CanvasKit.TileMode.Decal;
+      this._tileY = CanvasKit.TileMode.Decal;
+      break;
+    default:
+      throw 'invalid repetition mode ' + repetition;
+  }
+
+  // Takes a DOMMatrix like object. e.g. the identity would be:
+  // {a:1, b: 0, c: 0, d: 1, e: 0, f: 0}
+  // @param {DOMMatrix} m
+  this.setTransform = function(m) {
+    var t = [m.a, m.c, m.e,
+             m.b, m.d, m.f,
+               0,   0,   1];
+    if (allAreFinite(t)) {
+      this._transform = t;
+    }
+  }
+
+  this._copy = function() {
+    var cp = new CanvasPattern()
+    cp._tileX = this._tileX;
+    cp._tileY = this._tileY;
+    return cp;
+  }
+
+  this._dispose = function() {
+    if (this._shader) {
+      this._shader.delete();
+      this._shader = null;
+    }
+  }
+
+  this._getShader = function(currentTransform) {
+    // Ignore currentTransform since it will be applied later
+    this._dispose();
+    this._shader = this._image.makeShader(this._tileX, this._tileY, this._transform);
+    return this._shader;
+  }
+
+}
\ No newline at end of file
diff --git a/modules/canvaskit/htmlcanvas/postamble.js b/modules/canvaskit/htmlcanvas/postamble.js
new file mode 100644
index 0000000..8d2471e
--- /dev/null
+++ b/modules/canvaskit/htmlcanvas/postamble.js
@@ -0,0 +1,2 @@
+// This closes the scope started in preamble.js
+}());
diff --git a/modules/canvaskit/htmlcanvas/preamble.js b/modules/canvaskit/htmlcanvas/preamble.js
new file mode 100644
index 0000000..3711bf3
--- /dev/null
+++ b/modules/canvaskit/htmlcanvas/preamble.js
@@ -0,0 +1,17 @@
+// Adds compile-time JS functions to augment the CanvasKit interface.
+// Specifically, the code that emulates the HTML Canvas interface
+// (which is called HTMLCanvas or similar to avoid confusion with
+// SkCanvas).
+(function() {
+
+  // This allows us to expose internal functions (e.g. color
+  // parsing) for unit-testing, even in the minified version.
+  // Our tests are not minified like CanvasKit is, so the names
+  // would get lost otherwise.
+  CanvasKit._testing = {};
+
+// This intentionally dangles because we want all the htmlcanvas
+// JS code to be in the same scope, but JS doesn't support
+// namespaces like C++ does. Thus, we simply include this
+// preamble.js file, all the source .js files and then postamble.js
+// to bundle everything in the same scope.
\ No newline at end of file
diff --git a/experimental/canvaskit/htmlcanvas/radialgradient.js b/modules/canvaskit/htmlcanvas/radialgradient.js
similarity index 100%
rename from experimental/canvaskit/htmlcanvas/radialgradient.js
rename to modules/canvaskit/htmlcanvas/radialgradient.js
diff --git a/experimental/canvaskit/htmlcanvas/util.js b/modules/canvaskit/htmlcanvas/util.js
similarity index 100%
rename from experimental/canvaskit/htmlcanvas/util.js
rename to modules/canvaskit/htmlcanvas/util.js
diff --git a/modules/canvaskit/interface.js b/modules/canvaskit/interface.js
new file mode 100644
index 0000000..43f2506
--- /dev/null
+++ b/modules/canvaskit/interface.js
@@ -0,0 +1,888 @@
+// Adds JS functions to augment the CanvasKit interface.
+// For example, if there is a wrapper around the C++ call or logic to allow
+// chaining, it should go here.
+
+// CanvasKit.onRuntimeInitialized is called after the WASM library has loaded.
+// Anything that modifies an exposed class (e.g. SkPath) should be set
+// after onRuntimeInitialized, otherwise, it can happen outside of that scope.
+CanvasKit.onRuntimeInitialized = function() {
+  // All calls to 'this' need to go in externs.js so closure doesn't minify them away.
+
+  // Add some helpers for matrices. This is ported from SkMatrix.cpp
+  // to save complexity and overhead of going back and forth between
+  // C++ and JS layers.
+  // I would have liked to use something like DOMMatrix, except it
+  // isn't widely supported (would need polyfills) and it doesn't
+  // have a mapPoints() function (which could maybe be tacked on here).
+  // If DOMMatrix catches on, it would be worth re-considering this usage.
+  CanvasKit.SkMatrix = {};
+  function sdot(a, b, c, d, e, f) {
+    e = e || 0;
+    f = f || 0;
+    return a * b + c * d + e * f;
+  }
+
+  CanvasKit.SkMatrix.identity = function() {
+    return [
+      1, 0, 0,
+      0, 1, 0,
+      0, 0, 1,
+    ];
+  };
+
+  // Return the inverse (if it exists) of this matrix.
+  // Otherwise, return the identity.
+  CanvasKit.SkMatrix.invert = function(m) {
+    var det = m[0]*m[4]*m[8] + m[1]*m[5]*m[6] + m[2]*m[3]*m[7]
+            - m[2]*m[4]*m[6] - m[1]*m[3]*m[8] - m[0]*m[5]*m[7];
+    if (!det) {
+      SkDebug('Warning, uninvertible matrix');
+      return CanvasKit.SkMatrix.identity();
+    }
+    return [
+      (m[4]*m[8] - m[5]*m[7])/det, (m[2]*m[7] - m[1]*m[8])/det, (m[1]*m[5] - m[2]*m[4])/det,
+      (m[5]*m[6] - m[3]*m[8])/det, (m[0]*m[8] - m[2]*m[6])/det, (m[2]*m[3] - m[0]*m[5])/det,
+      (m[3]*m[7] - m[4]*m[6])/det, (m[1]*m[6] - m[0]*m[7])/det, (m[0]*m[4] - m[1]*m[3])/det,
+    ];
+  };
+
+  // Maps the given points according to the passed in matrix.
+  // Results are done in place.
+  // See SkMatrix.h::mapPoints for the docs on the math.
+  CanvasKit.SkMatrix.mapPoints = function(matrix, ptArr) {
+    if (ptArr.length % 2) {
+      throw 'mapPoints requires an even length arr';
+    }
+    for (var i = 0; i < ptArr.length; i+=2) {
+      var x = ptArr[i], y = ptArr[i+1];
+      // Gx+Hy+I
+      var denom  = matrix[6]*x + matrix[7]*y + matrix[8];
+      // Ax+By+C
+      var xTrans = matrix[0]*x + matrix[1]*y + matrix[2];
+      // Dx+Ey+F
+      var yTrans = matrix[3]*x + matrix[4]*y + matrix[5];
+      ptArr[i]   = xTrans/denom;
+      ptArr[i+1] = yTrans/denom;
+    }
+    return ptArr;
+  };
+
+  CanvasKit.SkMatrix.multiply = function(m1, m2) {
+    var result = [0,0,0, 0,0,0, 0,0,0];
+    for (var r = 0; r < 3; r++) {
+      for (var c = 0; c < 3; c++) {
+        // m1 and m2 are 1D arrays pretending to be 2D arrays
+        result[3*r + c] = sdot(m1[3*r + 0], m2[3*0 + c],
+                               m1[3*r + 1], m2[3*1 + c],
+                               m1[3*r + 2], m2[3*2 + c]);
+      }
+    }
+    return result;
+  }
+
+  // Return a matrix representing a rotation by n radians.
+  // px, py optionally say which point the rotation should be around
+  // with the default being (0, 0);
+  CanvasKit.SkMatrix.rotated = function(radians, px, py) {
+    px = px || 0;
+    py = py || 0;
+    var sinV = Math.sin(radians);
+    var cosV = Math.cos(radians);
+    return [
+      cosV, -sinV, sdot( sinV, py, 1 - cosV, px),
+      sinV,  cosV, sdot(-sinV, px, 1 - cosV, py),
+      0,        0,                             1,
+    ];
+  };
+
+  CanvasKit.SkMatrix.scaled = function(sx, sy, px, py) {
+    px = px || 0;
+    py = py || 0;
+    return [
+      sx, 0, px - sx * px,
+      0, sy, py - sy * py,
+      0,  0,            1,
+    ];
+  };
+
+  CanvasKit.SkMatrix.skewed = function(kx, ky, px, py) {
+    px = px || 0;
+    py = py || 0;
+    return [
+      1, kx, -kx * px,
+      ky, 1, -ky * py,
+      0,  0,        1,
+    ];
+  };
+
+  CanvasKit.SkMatrix.translated = function(dx, dy) {
+    return [
+      1, 0, dx,
+      0, 1, dy,
+      0, 0,  1,
+    ];
+  };
+
+  CanvasKit.SkPath.prototype.addArc = function(oval, startAngle, sweepAngle) {
+    // see arc() for the HTMLCanvas version
+    // note input angles are degrees.
+    this._addArc(oval, startAngle, sweepAngle);
+    return this;
+  };
+
+  CanvasKit.SkPath.prototype.addPath = function() {
+    // Takes 1, 2, 7, or 10 required args, where the first arg is always the path.
+    // The last arg is optional and chooses between add or extend mode.
+    // The options for the remaining args are:
+    //   - an array of 6 or 9 parameters (perspective is optional)
+    //   - the 9 parameters of a full matrix or
+    //     the 6 non-perspective params of a matrix.
+    var args = Array.prototype.slice.call(arguments);
+    var path = args[0];
+    var extend = false;
+    if (typeof args[args.length-1] === "boolean") {
+      extend = args.pop();
+    }
+    if (args.length === 1) {
+      // Add path, unchanged.  Use identity matrix
+      this._addPath(path, 1, 0, 0,
+                          0, 1, 0,
+                          0, 0, 1,
+                          extend);
+    } else if (args.length === 2) {
+      // User provided the 9 params of a full matrix as an array.
+      var a = args[1];
+      this._addPath(path, a[0],      a[1],      a[2],
+                          a[3],      a[4],      a[5],
+                          a[6] || 0, a[7] || 0, a[8] || 1,
+                          extend);
+    } else if (args.length === 7 || args.length === 10) {
+      // User provided the 9 params of a (full) matrix directly.
+      // (or just the 6 non perspective ones)
+      // These are in the same order as what Skia expects.
+      var a = args;
+      this._addPath(path, a[1],      a[2],      a[3],
+                          a[4],      a[5],      a[6],
+                          a[7] || 0, a[8] || 0, a[9] || 1,
+                          extend);
+    } else {
+      SkDebug('addPath expected to take 1, 2, 7, or 10 required args. Got ' + args.length);
+      return null;
+    }
+    return this;
+  };
+
+  CanvasKit.SkPath.prototype.addRect = function() {
+    // Takes 1, 2, 4 or 5 args
+    //  - SkRect
+    //  - SkRect, isCCW
+    //  - left, top, right, bottom
+    //  - left, top, right, bottom, isCCW
+    if (arguments.length === 1 || arguments.length === 2) {
+      var r = arguments[0];
+      var ccw = arguments[1] || false;
+      this._addRect(r.fLeft, r.fTop, r.fRight, r.fBottom, ccw);
+    } else if (arguments.length === 4 || arguments.length === 5) {
+      var a = arguments;
+      this._addRect(a[0], a[1], a[2], a[3], a[4] || false);
+    } else {
+      SkDebug('addRect expected to take 1, 2, 4, or 5 args. Got ' + arguments.length);
+      return null;
+    }
+    return this;
+  };
+
+  CanvasKit.SkPath.prototype.addRoundRect = function() {
+    // Takes 3, 4, 6 or 7 args
+    //  - SkRect, radii, ccw
+    //  - SkRect, rx, ry, ccw
+    //  - left, top, right, bottom, radii, ccw
+    //  - left, top, right, bottom, rx, ry, ccw
+    var args = arguments;
+    if (args.length === 3 || args.length === 6) {
+      var radii = args[args.length-2];
+    } else if (args.length === 6 || args.length === 7){
+      // duplicate the given (rx, ry) pairs for each corner.
+      var rx = args[args.length-3];
+      var ry = args[args.length-2];
+      var radii = [rx, ry, rx, ry, rx, ry, rx, ry];
+    } else {
+      SkDebug('addRoundRect expected to take 3, 4, 6, or 7 args. Got ' + args.length);
+      return null;
+    }
+    if (radii.length !== 8) {
+      SkDebug('addRoundRect needs 8 radii provided. Got ' + radii.length);
+      return null;
+    }
+    var rptr = copy1dArray(radii, CanvasKit.HEAPF32);
+    if (args.length === 3 || args.length === 4) {
+      var r = args[0];
+      var ccw = args[args.length - 1];
+      this._addRoundRect(r.fLeft, r.fTop, r.fRight, r.fBottom, rptr, ccw);
+    } else if (args.length === 6 || args.length === 7) {
+      var a = args;
+      this._addRoundRect(a[0], a[1], a[2], a[3], rptr, ccw);
+    }
+    CanvasKit._free(rptr);
+    return this;
+  };
+
+  CanvasKit.SkPath.prototype.arc = function(x, y, radius, startAngle, endAngle, ccw) {
+    // emulates the HTMLCanvas behavior.  See addArc() for the SkPath version.
+    // Note input angles are radians.
+    var bounds = CanvasKit.LTRBRect(x-radius, y-radius, x+radius, y+radius);
+    var sweep = radiansToDegrees(endAngle - startAngle) - (360 * !!ccw);
+    var temp = new CanvasKit.SkPath();
+    temp.addArc(bounds, radiansToDegrees(startAngle), sweep);
+    this.addPath(temp, true);
+    temp.delete();
+    return this;
+  };
+
+  CanvasKit.SkPath.prototype.arcTo = function() {
+    // takes 4, 5 or 7 args
+    // - 5 x1, y1, x2, y2, radius
+    // - 4 oval (as Rect), startAngle, sweepAngle, forceMoveTo
+    // - 7 x1, y1, x2, y2, startAngle, sweepAngle, forceMoveTo
+    var args = arguments;
+    if (args.length === 5) {
+      this._arcTo(args[0], args[1], args[2], args[3], args[4]);
+    } else if (args.length === 4) {
+      this._arcTo(args[0], args[1], args[2], args[3]);
+    } else if (args.length === 7) {
+      this._arcTo(CanvasKit.LTRBRect(args[0], args[1], args[2], args[3]),
+                  args[4], args[5], args[6]);
+    } else {
+      throw 'Invalid args for arcTo. Expected 4, 5, or 7, got '+ args.length;
+    }
+
+    return this;
+  };
+
+  CanvasKit.SkPath.prototype.close = function() {
+    this._close();
+    return this;
+  };
+
+  CanvasKit.SkPath.prototype.conicTo = function(x1, y1, x2, y2, w) {
+    this._conicTo(x1, y1, x2, y2, w);
+    return this;
+  };
+
+  CanvasKit.SkPath.prototype.cubicTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
+    this._cubicTo(cp1x, cp1y, cp2x, cp2y, x, y);
+    return this;
+  };
+
+  CanvasKit.SkPath.prototype.dash = function(on, off, phase) {
+    if (this._dash(on, off, phase)) {
+      return this;
+    }
+    return null;
+  };
+
+  CanvasKit.SkPath.prototype.lineTo = function(x, y) {
+    this._lineTo(x, y);
+    return this;
+  };
+
+  CanvasKit.SkPath.prototype.moveTo = function(x, y) {
+    this._moveTo(x, y);
+    return this;
+  };
+
+  CanvasKit.SkPath.prototype.op = function(otherPath, op) {
+    if (this._op(otherPath, op)) {
+      return this;
+    }
+    return null;
+  };
+
+  CanvasKit.SkPath.prototype.quadTo = function(cpx, cpy, x, y) {
+    this._quadTo(cpx, cpy, x, y);
+    return this;
+  };
+
+  CanvasKit.SkPath.prototype.simplify = function() {
+    if (this._simplify()) {
+      return this;
+    }
+    return null;
+  };
+
+  CanvasKit.SkPath.prototype.stroke = function(opts) {
+    // Fill out any missing values with the default values.
+    /**
+     * See externs.js for this definition
+     * @type {StrokeOpts}
+     */
+    opts = opts || {};
+    opts.width = opts.width || 1;
+    opts.miter_limit = opts.miter_limit || 4;
+    opts.cap = opts.cap || CanvasKit.StrokeCap.Butt;
+    opts.join = opts.join || CanvasKit.StrokeJoin.Miter;
+    opts.precision = opts.precision || 1;
+    if (this._stroke(opts)) {
+      return this;
+    }
+    return null;
+  };
+
+  CanvasKit.SkPath.prototype.transform = function() {
+    // Takes 1 or 9 args
+    if (arguments.length === 1) {
+      // argument 1 should be a 6 or 9 element array.
+      var a = arguments[0];
+      this._transform(a[0], a[1], a[2],
+                      a[3], a[4], a[5],
+                      a[6] || 0, a[7] || 0, a[8] || 1);
+    } else if (arguments.length === 6 || arguments.length === 9) {
+      // these arguments are the 6 or 9 members of the matrix
+      var a = arguments;
+      this._transform(a[0], a[1], a[2],
+                      a[3], a[4], a[5],
+                      a[6] || 0, a[7] || 0, a[8] || 1);
+    } else {
+      throw 'transform expected to take 1 or 9 arguments. Got ' + arguments.length;
+    }
+    return this;
+  };
+  // isComplement is optional, defaults to false
+  CanvasKit.SkPath.prototype.trim = function(startT, stopT, isComplement) {
+    if (this._trim(startT, stopT, !!isComplement)) {
+      return this;
+    }
+    return null;
+  };
+
+  // bones should be a 3d array.
+  // Each bone is a 3x2 transformation matrix in column major order:
+  // | scaleX   skewX transX |
+  // |  skewY  scaleY transY |
+  // and bones is an array of those matrices.
+  // Returns a copy of this (SkVertices) with the bones applied.
+  CanvasKit.SkVertices.prototype.applyBones = function(bones) {
+    var bPtr = copy3dArray(bones, CanvasKit.HEAPF32);
+    var vert = this._applyBones(bPtr, bones.length);
+    CanvasKit._free(bPtr);
+    return vert;
+  }
+
+  CanvasKit.SkImage.prototype.encodeToData = function() {
+    if (!arguments.length) {
+      return this._encodeToData();
+    }
+
+    if (arguments.length === 2) {
+      var a = arguments;
+      return this._encodeToDataWithFormat(a[0], a[1]);
+    }
+
+    throw 'encodeToData expected to take 0 or 2 arguments. Got ' + arguments.length;
+  }
+
+  CanvasKit.SkImage.prototype.makeShader = function(xTileMode, yTileMode, localMatrix) {
+    if (localMatrix) {
+      // Add perspective args if not provided.
+      if (localMatrix.length === 6) {
+        localMatrix.push(0, 0, 1);
+      }
+      return this._makeShader(xTileMode, yTileMode, localMatrix);
+    } else {
+      return this._makeShader(xTileMode, yTileMode);
+    }
+  }
+
+  // atlas is an SkImage, e.g. from CanvasKit.MakeImageFromEncoded
+  // srcRects and dstXforms should be CanvasKit.SkRectBuilder and CanvasKit.RSXFormBuilder
+  // or just arrays of floats in groups of 4.
+  // colors, if provided, should be a CanvasKit.SkColorBuilder or array of SkColor
+  // (from CanvasKit.Color)
+  CanvasKit.SkCanvas.prototype.drawAtlas = function(atlas, srcRects, dstXforms, paint,
+                                       /*optional*/ blendMode, colors) {
+    if (!atlas || !paint || !srcRects || !dstXforms) {
+      SkDebug('Doing nothing since missing a required input');
+      return;
+    }
+    if (srcRects.length !== dstXforms.length || (colors && colors.length !== dstXforms.length)) {
+      SkDebug('Doing nothing since input arrays length mismatches');
+    }
+    if (!blendMode) {
+      blendMode = CanvasKit.BlendMode.SrcOver;
+    }
+
+    var srcRectPtr;
+    if (srcRects.build) {
+      srcRectPtr = srcRects.build();
+    } else {
+      srcRectPtr = copy1dArray(srcRects, CanvasKit.HEAPF32);
+    }
+
+    var dstXformPtr;
+    if (dstXforms.build) {
+      dstXformPtr = dstXforms.build();
+    } else {
+      dstXformPtr = copy1dArray(dstXforms, CanvasKit.HEAPF32);
+    }
+
+    var colorPtr = 0; // enscriptem doesn't like undefined for nullptr
+    if (colors) {
+      if (colors.build) {
+        colorPtr = colors.build();
+      } else {
+        colorPtr = copy1dArray(colors, CanvasKit.HEAPU32);
+      }
+    }
+
+    this._drawAtlas(atlas, dstXformPtr, srcRectPtr, colorPtr, dstXforms.length,
+                    blendMode, paint);
+
+    if (srcRectPtr && !srcRects.build) {
+      CanvasKit._free(srcRectPtr);
+    }
+    if (dstXformPtr && !dstXforms.build) {
+      CanvasKit._free(dstXformPtr);
+    }
+    if (colorPtr && !colors.build) {
+      CanvasKit._free(colorPtr);
+    }
+
+  }
+
+  // str can be either a text string or a ShapedText object
+  CanvasKit.SkCanvas.prototype.drawText = function(str, x, y, paint, font) {
+    if (typeof str === 'string') {
+      // lengthBytesUTF8 and stringToUTF8Array are defined in the emscripten
+      // JS.  See https://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#stringToUTF8
+      // Add 1 for null terminator
+      var strLen = lengthBytesUTF8(str) + 1;
+      var strPtr = CanvasKit._malloc(strLen);
+
+      stringToUTF8(str, strPtr, strLen);
+      this._drawSimpleText(strPtr, strLen, x, y, font, paint);
+    } else {
+      this._drawShapedText(str, x, y, paint);
+    }
+  }
+
+  // returns Uint8Array
+  CanvasKit.SkCanvas.prototype.readPixels = function(x, y, w, h, alphaType,
+                                                     colorType, dstRowBytes) {
+    // supply defaults (which are compatible with HTMLCanvas's getImageData)
+    alphaType = alphaType || CanvasKit.AlphaType.Unpremul;
+    colorType = colorType || CanvasKit.ColorType.RGBA_8888;
+    dstRowBytes = dstRowBytes || (4 * w);
+
+    var len = h * dstRowBytes
+    var pptr = CanvasKit._malloc(len);
+    var ok = this._readPixels({
+      'width': w,
+      'height': h,
+      'colorType': colorType,
+      'alphaType': alphaType,
+    }, pptr, dstRowBytes, x, y);
+    if (!ok) {
+      CanvasKit._free(pptr);
+      return null;
+    }
+
+    // The first typed array is just a view into memory. Because we will
+    // be free-ing that, we call slice to make a persistent copy.
+    var pixels = new Uint8Array(CanvasKit.HEAPU8.buffer, pptr, len).slice();
+    CanvasKit._free(pptr);
+    return pixels;
+  }
+
+  // pixels is a TypedArray. No matter the input size, it will be treated as
+  // a Uint8Array (essentially, a byte array).
+  CanvasKit.SkCanvas.prototype.writePixels = function(pixels, srcWidth, srcHeight,
+                                                      destX, destY, alphaType, colorType) {
+    if (pixels.byteLength % (srcWidth * srcHeight)) {
+      throw 'pixels length must be a multiple of the srcWidth * srcHeight';
+    }
+    var bytesPerPixel = pixels.byteLength / (srcWidth * srcHeight);
+    // supply defaults (which are compatible with HTMLCanvas's putImageData)
+    alphaType = alphaType || CanvasKit.AlphaType.Unpremul;
+    colorType = colorType || CanvasKit.ColorType.RGBA_8888;
+    var srcRowBytes = bytesPerPixel * srcWidth;
+
+    var pptr = CanvasKit._malloc(pixels.byteLength);
+    CanvasKit.HEAPU8.set(pixels, pptr);
+
+    var ok = this._writePixels({
+      'width': srcWidth,
+      'height': srcHeight,
+      'colorType': colorType,
+      'alphaType': alphaType,
+    }, pptr, srcRowBytes, destX, destY);
+
+    CanvasKit._free(pptr);
+    return ok;
+  }
+
+  // Returns an array of the widths of the glyphs in this string.
+  CanvasKit.SkFont.prototype.getWidths = function(str) {
+    // add 1 for null terminator
+    var codePoints = str.length + 1;
+    // lengthBytesUTF8 and stringToUTF8Array are defined in the emscripten
+    // JS.  See https://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#stringToUTF8
+    // Add 1 for null terminator
+    var strBytes = lengthBytesUTF8(str) + 1;
+    var strPtr = CanvasKit._malloc(strBytes);
+    stringToUTF8(str, strPtr, strBytes);
+
+    var bytesPerFloat = 4;
+    // allocate widths == numCodePoints
+    var widthPtr = CanvasKit._malloc(codePoints * bytesPerFloat);
+    if (!this._getWidths(strPtr, strBytes, codePoints, widthPtr)) {
+      SkDebug('Could not compute widths');
+      CanvasKit._free(strPtr);
+      CanvasKit._free(widthPtr);
+      return null;
+    }
+    // reminder, this shouldn't copy the data, just is a nice way to
+    // wrap 4 bytes together into a float.
+    var widths = new Float32Array(CanvasKit.buffer, widthPtr, codePoints);
+    // This copies the data so we can free the CanvasKit memory
+    var retVal = Array.from(widths);
+    CanvasKit._free(strPtr);
+    CanvasKit._free(widthPtr);
+    return retVal;
+  }
+
+  // fontData should be an arrayBuffer
+  CanvasKit.SkFontMgr.prototype.MakeTypefaceFromData = function(fontData) {
+    var data = new Uint8Array(fontData);
+
+    var fptr = CanvasKit._malloc(data.byteLength);
+    CanvasKit.HEAPU8.set(data, fptr);
+    var font = this._makeTypefaceFromData(fptr, data.byteLength);
+    if (!font) {
+      SkDebug('Could not decode font data');
+      // We do not need to free the data since the C++ will do that for us
+      // when the font is deleted (or fails to decode);
+      return null;
+    }
+    return font;
+  }
+
+  // The serialized format of an SkPicture (informally called an "skp"), is not something
+  // that clients should ever rely on. It is useful when filing bug reports, but that's
+  // about it. The format may change at anytime and no promises are made for backwards
+  // or forward compatibility.
+  CanvasKit.SkPicture.prototype.DEBUGONLY_saveAsFile = function(skpName) {
+    var data = this.DEBUGONLY_serialize();
+    if (!data) {
+      SkDebug('Could not serialize to skpicture.');
+      return;
+    }
+    var bytes = CanvasKit.getSkDataBytes(data);
+    saveBytesToFile(bytes, skpName);
+    data.delete();
+  }
+
+  CanvasKit.SkSurface.prototype.captureFrameAsSkPicture = function(drawFrame) {
+    // Set up SkPictureRecorder
+    var spr = new CanvasKit.SkPictureRecorder();
+    var canvas = spr.beginRecording(
+                    CanvasKit.LTRBRect(0, 0, this.width(), this.height()));
+    drawFrame(canvas);
+    var pic = spr.finishRecordingAsPicture();
+    spr.delete();
+    // TODO: do we need to clean up the memory for canvas?
+    // If we delete it here, saveAsFile doesn't work correctly.
+    return pic;
+  }
+
+  CanvasKit.SkSurface.prototype.requestAnimationFrame = function(callback, dirtyRect) {
+    if (!this._cached_canvas) {
+      this._cached_canvas = this.getCanvas();
+    }
+    window.requestAnimationFrame(function() {
+      if (this._context !== undefined) {
+        CanvasKit.setCurrentContext(this._context);
+      }
+
+      callback(this._cached_canvas);
+
+      this.flush();
+    }.bind(this));
+  }
+
+  CanvasKit.SkTextBlob.MakeOnPath = function(str, path, font, initialOffset) {
+    if (!str || !str.length) {
+      SkDebug('ignoring 0 length string');
+      return;
+    }
+    if (!path || !path.countPoints()) {
+      SkDebug('ignoring empty path');
+      return;
+    }
+    if (path.countPoints() === 1) {
+      SkDebug('path has 1 point, returning normal textblob');
+      return this.MakeFromText(str, font);
+    }
+
+    if (!initialOffset) {
+      initialOffset = 0;
+    }
+
+    var widths = font.getWidths(str);
+
+    var rsx = new CanvasKit.RSXFormBuilder();
+    var meas = new CanvasKit.SkPathMeasure(path, false, 1);
+    var dist = initialOffset;
+    for (var i = 0; i < str.length; i++) {
+      var width = widths[i];
+      dist += width/2;
+      if (dist > meas.getLength()) {
+        // jump to next contour
+        if (!meas.nextContour()) {
+          // We have come to the end of the path - terminate the string
+          // right here.
+          str = str.substring(0, i);
+          break;
+        }
+        dist = width/2;
+      }
+
+      // Gives us the (x, y) coordinates as well as the cos/sin of the tangent
+      // line at that position.
+      var xycs = meas.getPosTan(dist);
+      var cx = xycs[0];
+      var cy = xycs[1];
+      var cosT = xycs[2];
+      var sinT = xycs[3];
+
+      var adjustedX = cx - (width/2 * cosT);
+      var adjustedY = cy - (width/2 * sinT);
+
+      rsx.push(cosT, sinT, adjustedX, adjustedY);
+      dist += width/2;
+    }
+    var retVal = this.MakeFromRSXform(str, rsx, font);
+    rsx.delete();
+    meas.delete();
+    return retVal;
+  }
+
+  CanvasKit.SkTextBlob.MakeFromRSXform = function(str, rsxBuilder, font) {
+    // lengthBytesUTF8 and stringToUTF8Array are defined in the emscripten
+    // JS.  See https://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#stringToUTF8
+    // Add 1 for null terminator
+    var strLen = lengthBytesUTF8(str) + 1;
+    var strPtr = CanvasKit._malloc(strLen);
+    // Add 1 for the null terminator.
+    stringToUTF8(str, strPtr, strLen);
+    var rptr = rsxBuilder.build();
+
+    var blob = CanvasKit.SkTextBlob._MakeFromRSXform(strPtr, strLen - 1,
+                          rptr, font, CanvasKit.TextEncoding.UTF8);
+    if (!blob) {
+      SkDebug('Could not make textblob from string "' + str + '"');
+      return null;
+    }
+
+    var origDelete = blob.delete.bind(blob);
+    blob.delete = function() {
+      CanvasKit._free(strPtr);
+      origDelete();
+    }
+    return blob;
+  }
+
+  CanvasKit.SkTextBlob.MakeFromText = function(str, font) {
+    // lengthBytesUTF8 and stringToUTF8Array are defined in the emscripten
+    // JS.  See https://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#stringToUTF8
+    // Add 1 for null terminator
+    var strLen = lengthBytesUTF8(str) + 1;
+    var strPtr = CanvasKit._malloc(strLen);
+    // Add 1 for the null terminator.
+    stringToUTF8(str, strPtr, strLen);
+
+    var blob = CanvasKit.SkTextBlob._MakeFromText(strPtr, strLen - 1, font, CanvasKit.TextEncoding.UTF8);
+    if (!blob) {
+      SkDebug('Could not make textblob from string "' + str + '"');
+      return null;
+    }
+
+    var origDelete = blob.delete.bind(blob);
+    blob.delete = function() {
+      CanvasKit._free(strPtr);
+      origDelete();
+    }
+    return blob;
+  }
+
+  // Run through the JS files that are added at compile time.
+  if (CanvasKit._extraInitializations) {
+    CanvasKit._extraInitializations.forEach(function(init) {
+      init();
+    });
+  }
+}; // end CanvasKit.onRuntimeInitialized, that is, anything changing prototypes or dynamic.
+
+CanvasKit.LTRBRect = function(l, t, r, b) {
+  return {
+    fLeft: l,
+    fTop: t,
+    fRight: r,
+    fBottom: b,
+  };
+}
+
+CanvasKit.XYWHRect = function(x, y, w, h) {
+  return {
+    fLeft: x,
+    fTop: y,
+    fRight: x+w,
+    fBottom: y+h,
+  };
+}
+
+CanvasKit.MakePathFromCmds = function(cmds) {
+  var ptrLen = loadCmdsTypedArray(cmds);
+  var path = CanvasKit._MakePathFromCmds(ptrLen[0], ptrLen[1]);
+  CanvasKit._free(ptrLen[0]);
+  return path;
+}
+
+CanvasKit.MakeSkDashPathEffect = function(intervals, phase) {
+  if (!phase) {
+    phase = 0;
+  }
+  if (!intervals.length || intervals.length % 2 === 1) {
+    throw 'Intervals array must have even length';
+  }
+  var ptr = copy1dArray(intervals, CanvasKit.HEAPF32);
+  var dpe = CanvasKit._MakeSkDashPathEffect(ptr, intervals.length, phase);
+  CanvasKit._free(ptr);
+  return dpe;
+}
+
+// data is a TypedArray or ArrayBuffer e.g. from fetch().then(resp.arrayBuffer())
+CanvasKit.MakeImageFromEncoded = function(data) {
+  data = new Uint8Array(data);
+
+  var iptr = CanvasKit._malloc(data.byteLength);
+  CanvasKit.HEAPU8.set(data, iptr);
+  var img = CanvasKit._decodeImage(iptr, data.byteLength);
+  if (!img) {
+    SkDebug('Could not decode image');
+    return null;
+  }
+  return img;
+}
+
+// pixels is a Uint8Array
+CanvasKit.MakeImage = function(pixels, width, height, alphaType, colorType) {
+  var bytesPerPixel = pixels.byteLength / (width * height);
+  var info = {
+    'width': width,
+    'height': height,
+    'alphaType': alphaType,
+    'colorType': colorType,
+  };
+  var pptr = CanvasKit._malloc(pixels.byteLength);
+  CanvasKit.HEAPU8.set(pixels, pptr);
+  // No need to _free iptr, Image takes it with SkData::MakeFromMalloc
+
+  return CanvasKit._MakeImage(info, pptr, pixels.byteLength, width * bytesPerPixel);
+}
+
+CanvasKit.MakeLinearGradientShader = function(start, end, colors, pos, mode, localMatrix, flags) {
+  var colorPtr = copy1dArray(colors, CanvasKit.HEAPU32);
+  var posPtr =   copy1dArray(pos,    CanvasKit.HEAPF32);
+  flags = flags || 0;
+
+  if (localMatrix) {
+    // Add perspective args if not provided.
+    if (localMatrix.length === 6) {
+      localMatrix.push(0, 0, 1);
+    }
+    var lgs = CanvasKit._MakeLinearGradientShader(start, end, colorPtr, posPtr,
+                                                  colors.length, mode, flags, localMatrix);
+  } else {
+    var lgs = CanvasKit._MakeLinearGradientShader(start, end, colorPtr, posPtr,
+                                                  colors.length, mode, flags);
+  }
+
+  CanvasKit._free(colorPtr);
+  CanvasKit._free(posPtr);
+  return lgs;
+}
+
+CanvasKit.MakeRadialGradientShader = function(center, radius, colors, pos, mode, localMatrix, flags) {
+  var colorPtr = copy1dArray(colors, CanvasKit.HEAPU32);
+  var posPtr =   copy1dArray(pos,    CanvasKit.HEAPF32);
+  flags = flags || 0;
+
+  if (localMatrix) {
+    // Add perspective args if not provided.
+    if (localMatrix.length === 6) {
+      localMatrix.push(0, 0, 1);
+    }
+    var rgs = CanvasKit._MakeRadialGradientShader(center, radius, colorPtr, posPtr,
+                                                  colors.length, mode, flags, localMatrix);
+  } else {
+    var rgs = CanvasKit._MakeRadialGradientShader(center, radius, colorPtr, posPtr,
+                                                  colors.length, mode, flags);
+  }
+
+  CanvasKit._free(colorPtr);
+  CanvasKit._free(posPtr);
+  return rgs;
+}
+
+CanvasKit.MakeTwoPointConicalGradientShader = function(start, startRadius, end, endRadius,
+                                                       colors, pos, mode, localMatrix, flags) {
+  var colorPtr = copy1dArray(colors, CanvasKit.HEAPU32);
+  var posPtr =   copy1dArray(pos,    CanvasKit.HEAPF32);
+  flags = flags || 0;
+
+  if (localMatrix) {
+    // Add perspective args if not provided.
+    if (localMatrix.length === 6) {
+      localMatrix.push(0, 0, 1);
+    }
+    var rgs = CanvasKit._MakeTwoPointConicalGradientShader(
+                        start, startRadius, end, endRadius,
+                        colorPtr, posPtr, colors.length, mode, flags, localMatrix);
+  } else {
+    var rgs = CanvasKit._MakeTwoPointConicalGradientShader(
+                        start, startRadius, end, endRadius,
+                        colorPtr, posPtr, colors.length, mode, flags);
+  }
+
+  CanvasKit._free(colorPtr);
+  CanvasKit._free(posPtr);
+  return rgs;
+}
+
+CanvasKit.MakeSkVertices = function(mode, positions, textureCoordinates, colors,
+                                    boneIndices, boneWeights, indices, isVolatile) {
+  var positionPtr = copy2dArray(positions,          CanvasKit.HEAPF32);
+  var texPtr =      copy2dArray(textureCoordinates, CanvasKit.HEAPF32);
+  var colorPtr =    copy1dArray(colors,             CanvasKit.HEAPU32);
+
+  var boneIdxPtr =  copy2dArray(boneIndices,        CanvasKit.HEAP32);
+  var boneWtPtr  =  copy2dArray(boneWeights,        CanvasKit.HEAPF32);
+  var idxPtr =      copy1dArray(indices,            CanvasKit.HEAPU16);
+
+  // Default isVolitile to true if not set
+  isVolatile = isVolatile === undefined ? true : isVolatile;
+
+  var idxCount = (indices && indices.length) || 0;
+  // _MakeVertices will copy all the values in, so we are free to release
+  // the memory after.
+  var vertices = CanvasKit._MakeSkVertices(mode, positions.length, positionPtr,
+                                           texPtr, colorPtr, boneIdxPtr, boneWtPtr,
+                                           idxCount, idxPtr, isVolatile);
+  positionPtr && CanvasKit._free(positionPtr);
+  texPtr && CanvasKit._free(texPtr);
+  colorPtr && CanvasKit._free(colorPtr);
+  idxPtr && CanvasKit._free(idxPtr);
+  boneIdxPtr && CanvasKit._free(boneIdxPtr);
+  boneWtPtr && CanvasKit._free(boneWtPtr);
+  return vertices;
+};
diff --git a/modules/canvaskit/karma.bench.conf.js b/modules/canvaskit/karma.bench.conf.js
new file mode 100644
index 0000000..d4900b8
--- /dev/null
+++ b/modules/canvaskit/karma.bench.conf.js
@@ -0,0 +1,91 @@
+const isDocker = require('is-docker')();
+
+module.exports = function(config) {
+  // Set the default values to be what are needed when testing the
+  // WebAssembly build locally.
+  let cfg = {
+    // frameworks to use
+    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+    frameworks: ['jasmine'],
+
+    // list of files / patterns to load in the browser
+    files: [
+      { pattern: 'canvaskit/bin/canvaskit.wasm', included:false, served:true},
+      { pattern: 'perf/assets/*', included:false, served:true},
+      '../../modules/pathkit/perf/perfReporter.js',
+      'canvaskit/bin/canvaskit.js',
+      'tests/canvaskitinit.js',
+      'perf/*.bench.js'
+    ],
+
+    proxies: {
+      '/canvaskit/': '/base/canvaskit/bin/',
+      '/assets/': '/base/perf/assets/'
+    },
+
+    // test results reporter to use
+    // possible values: 'dots', 'progress'
+    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+    reporters: ['progress'],
+
+    // web server port
+    port: 4444,
+
+    // enable / disable colors in the output (reporters and logs)
+    colors: true,
+
+    // level of logging
+    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+    logLevel: config.LOG_INFO,
+
+    // enable / disable watching file and executing tests whenever any file changes
+    autoWatch: true,
+
+    browserDisconnectTimeout: 20000,
+    browserNoActivityTimeout: 20000,
+
+    // start these browsers
+    browsers: ['Chrome'],
+
+    // Continuous Integration mode
+    // if true, Karma captures browsers, runs the tests and exits
+    singleRun: false,
+
+    // Concurrency level
+    // how many browser should be started simultaneous
+    concurrency: Infinity,
+  };
+
+  if (isDocker) {
+    // See https://hackernoon.com/running-karma-tests-with-headless-chrome-inside-docker-ae4aceb06ed3
+    cfg.browsers = ['ChromeHeadlessNoSandbox'],
+    cfg.customLaunchers = {
+        ChromeHeadlessNoSandbox: {
+            base: 'ChromeHeadless',
+            flags: [
+            // Without this flag, we see an error:
+            // Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permitted
+                '--no-sandbox'
+            ],
+        },
+    };
+  }
+
+  if (process.env.ASM_JS) {
+    console.log('asm.js is under test');
+    cfg.files = [
+      { pattern: 'npm-asmjs/bin/pathkit.js.mem', included:false, served:true},
+      'perf/perfReporter.js',
+      'npm-asmjs/bin/pathkit.js',
+      'perf/*.bench.js'
+    ];
+
+    cfg.proxies = {
+      '/pathkit/': '/base/npm-asmjs/bin/'
+    };
+  } else {
+    console.log('wasm is under test');
+  }
+
+  config.set(cfg);
+}
diff --git a/modules/canvaskit/karma.conf.js b/modules/canvaskit/karma.conf.js
new file mode 100644
index 0000000..e815e04
--- /dev/null
+++ b/modules/canvaskit/karma.conf.js
@@ -0,0 +1,76 @@
+const isDocker = require('is-docker')();
+
+module.exports = function(config) {
+  // Set the default values to be what are needed when testing the
+  // WebAssembly build locally.
+  let cfg = {
+    // frameworks to use
+    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+    frameworks: ['jasmine'],
+
+    // list of files / patterns to load in the browser
+    files: [
+      { pattern: 'canvaskit/bin/canvaskit.wasm', included:false, served:true},
+      { pattern: 'tests/assets/*', included:false, served:true},
+      '../../modules/pathkit/tests/testReporter.js',
+      'canvaskit/bin/canvaskit.js',
+      'tests/canvaskitinit.js',
+      'tests/util.js',
+      'tests/*.spec.js'
+    ],
+
+    proxies: {
+      '/assets/': '/base/tests/assets/',
+      '/canvaskit/': '/base/canvaskit/bin/',
+    },
+
+    // test results reporter to use
+    // possible values: 'dots', 'progress'
+    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+    reporters: ['progress'],
+
+    // web server port
+    port: 4444,
+
+    // enable / disable colors in the output (reporters and logs)
+    colors: true,
+
+    // level of logging
+    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+    logLevel: config.LOG_INFO,
+
+    // enable / disable watching file and executing tests whenever any file changes
+    autoWatch: true,
+
+    browserDisconnectTimeout: 20000,
+    browserNoActivityTimeout: 20000,
+
+    // start these browsers
+    browsers: ['Chrome'],
+
+    // Continuous Integration mode
+    // if true, Karma captures browsers, runs the tests and exits
+    singleRun: false,
+
+    // Concurrency level
+    // how many browser should be started simultaneous
+    concurrency: Infinity,
+  };
+
+  if (isDocker) {
+    // See https://hackernoon.com/running-karma-tests-with-headless-chrome-inside-docker-ae4aceb06ed3
+    cfg.browsers = ['ChromeHeadlessNoSandbox'],
+    cfg.customLaunchers = {
+        ChromeHeadlessNoSandbox: {
+            base: 'ChromeHeadless',
+            flags: [
+            // Without this flag, we see an error:
+            // Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permitted
+                '--no-sandbox'
+            ],
+        },
+    };
+  }
+
+  config.set(cfg);
+}
diff --git a/experimental/canvaskit/package.json b/modules/canvaskit/package.json
similarity index 100%
rename from experimental/canvaskit/package.json
rename to modules/canvaskit/package.json
diff --git a/modules/canvaskit/particles_bindings.cpp b/modules/canvaskit/particles_bindings.cpp
new file mode 100644
index 0000000..df7fff6
--- /dev/null
+++ b/modules/canvaskit/particles_bindings.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCanvas.h"
+#include "SkTypes.h"
+#include "SkParticleAffector.h"
+#include "SkParticleDrawable.h"
+#include "SkParticleEffect.h"
+#include "SkParticleSerialization.h"
+#include "SkRandom.h"
+
+#include <string>
+
+#include <emscripten.h>
+#include <emscripten/bind.h>
+
+using namespace emscripten;
+
+EMSCRIPTEN_BINDINGS(Particles) {
+    class_<SkParticleEffect>("SkParticleEffect")
+        .smart_ptr<sk_sp<SkParticleEffect>>("sk_sp<SkParticleEffect>")
+        .function("draw", &SkParticleEffect::draw, allow_raw_pointers())
+        .function("start", select_overload<void (double, bool)>(&SkParticleEffect::start))
+        .function("update", select_overload<void (double)>(&SkParticleEffect::update));
+
+    function("MakeParticles", optional_override([](std::string json)->sk_sp<SkParticleEffect> {
+        static bool didInit = false;
+        if (!didInit) {
+            REGISTER_REFLECTED(SkReflected);
+            SkParticleAffector::RegisterAffectorTypes();
+            SkParticleDrawable::RegisterDrawableTypes();
+            didInit = true;
+        }
+        SkRandom r;
+        sk_sp<SkParticleEffectParams> params(new SkParticleEffectParams());
+        skjson::DOM dom(json.c_str(), json.length());
+        SkFromJsonVisitor fromJson(dom.root());
+        params->visitFields(&fromJson);
+        return sk_sp<SkParticleEffect>(new SkParticleEffect(std::move(params), r));
+    }));
+    constant("particles", true);
+
+}
diff --git a/modules/canvaskit/perf/animation.bench.js b/modules/canvaskit/perf/animation.bench.js
new file mode 100644
index 0000000..3c94375
--- /dev/null
+++ b/modules/canvaskit/perf/animation.bench.js
@@ -0,0 +1,149 @@
+describe('CanvasKit\'s Animation', function() {
+
+    const LOTTIE_ANIMATIONS = ['lego_loader', 'drinks', 'confetti', 'onboarding'];
+
+    let container = document.createElement('div');
+    document.body.appendChild(container);
+
+
+    beforeEach(function() {
+        container.innerHTML = `
+            <canvas width=600 height=600 id=test></canvas>`;
+    });
+
+    afterEach(function() {
+        container.innerHTML = '';
+    });
+
+    function fetchAndText(url) {
+        return new Promise(function(resolve, reject) {
+            fetch(url).then((resp) => {
+                    resp.text().then((str) => {
+                        expect(str).toBeTruthy();
+                        resolve(str);
+                    });
+                }).catch(reject);
+        });
+    }
+
+    LOTTIE_ANIMATIONS.forEach((animStr) => {
+        let promises = [fetchAndText(`/assets/${animStr}.json`), LoadCanvasKit];
+
+        it(`animation loading for ${animStr}`, function(done) {
+            let jsonStr = '';
+            function setup(ctx) {
+                expect(jsonStr).toBeTruthy();
+            }
+
+            function test(ctx) {
+                const animation = CanvasKit.MakeAnimation(jsonStr);
+                animation.delete();
+            }
+
+            function teardown(ctx) {}
+
+            Promise.all(promises).then((responses) => {
+                // The result from the first promise, that is, the JSON string
+                // fetched by fetchAndText
+                jsonStr = responses[0];
+                benchmarkAndReport(`${animStr}_animation_load`, setup, test, teardown).then(() => {
+                    done();
+                }).catch(reportError(done));
+            });
+        });
+
+        it(`animation frames in order for ${animStr}`, function(done) {
+            let jsonStr = '';
+            function setup(ctx) {
+                expect(jsonStr).toBeTruthy();
+                ctx.animation = CanvasKit.MakeAnimation(jsonStr);
+                expect(ctx.animation).toBeTruthy();
+                ctx.timer = 0;
+            }
+
+            function test(ctx) {
+                ctx.animation.seek(ctx.timer);
+                ctx.timer += 0.01;
+                if (ctx.timer > 1.0) {
+                    ctx.timer = 0;
+                }
+            }
+
+            function teardown(ctx) {
+                ctx.animation.delete();
+            }
+
+            Promise.all(promises).then((responses) => {
+                // The result from the first promise, that is, the JSON string
+                // fetched by fetchAndText
+                jsonStr = responses[0];
+                benchmarkAndReport(`${animStr}_animation_in_order`, setup, test, teardown).then(() => {
+                    done();
+                }).catch(reportError(done));
+            });
+        });
+
+        it(`animation frames in random order for ${animStr}`, function(done) {
+            let jsonStr = '';
+            function setup(ctx) {
+                expect(jsonStr).toBeTruthy();
+                ctx.animation = CanvasKit.MakeAnimation(jsonStr);
+                expect(ctx.animation).toBeTruthy();
+            }
+
+            function test(ctx) {
+                ctx.animation.seek(Math.random());
+            }
+
+            function teardown(ctx) {
+                ctx.animation.delete();
+            }
+
+            Promise.all(promises).then((responses) => {
+                // The result from the first promise, that is, the JSON string
+                // fetched by fetchAndText
+                jsonStr = responses[0];
+                benchmarkAndReport(`${animStr}_animation_random_order`, setup, test, teardown).then(() => {
+                    done();
+                }).catch(reportError(done));
+            });
+        });
+
+        it(`renders to an HTML canvas ${animStr}`, function(done) {
+            let jsonStr = '';
+            function setup(ctx) {
+                expect(jsonStr).toBeTruthy();
+                ctx.animation = CanvasKit.MakeAnimation(jsonStr);
+                expect(ctx.animation).toBeTruthy();
+                ctx.animation.seek(0.5);
+                ctx.surface = CanvasKit.MakeCanvasSurface('test');
+                ctx.canvas = ctx.surface.getCanvas();
+                ctx.clear = CanvasKit.Color(255, 255, 255, 0.0); // transparent
+            }
+
+            function test(ctx) {
+                // This emulates what would need to be done do accurately
+                // draw one frame.
+                ctx.canvas.clear(ctx.clear);
+                ctx.animation.render(ctx.canvas);
+                ctx.surface.flush();
+            }
+
+            function teardown(ctx) {
+                ctx.animation.delete();
+                ctx.surface.dispose(); // ctx.canvas will also be cleaned up
+            }
+
+            Promise.all(promises).then((responses) => {
+                // The result from the first promise, that is, the JSON string
+                // fetched by fetchAndText
+                jsonStr = responses[0];
+                benchmarkAndReport(`${animStr}_animation_render_flush`, setup, test, teardown).then(() => {
+                    done();
+                }).catch(reportError(done));
+            });
+        });
+
+    });
+
+});
diff --git a/experimental/canvaskit/perf/assets/confetti.json b/modules/canvaskit/perf/assets/confetti.json
similarity index 100%
rename from experimental/canvaskit/perf/assets/confetti.json
rename to modules/canvaskit/perf/assets/confetti.json
diff --git a/experimental/canvaskit/perf/assets/drinks.json b/modules/canvaskit/perf/assets/drinks.json
similarity index 100%
rename from experimental/canvaskit/perf/assets/drinks.json
rename to modules/canvaskit/perf/assets/drinks.json
diff --git a/experimental/canvaskit/perf/assets/lego_loader.json b/modules/canvaskit/perf/assets/lego_loader.json
similarity index 100%
rename from experimental/canvaskit/perf/assets/lego_loader.json
rename to modules/canvaskit/perf/assets/lego_loader.json
diff --git a/experimental/canvaskit/perf/assets/onboarding.json b/modules/canvaskit/perf/assets/onboarding.json
similarity index 100%
rename from experimental/canvaskit/perf/assets/onboarding.json
rename to modules/canvaskit/perf/assets/onboarding.json
diff --git a/experimental/canvaskit/htmlcanvas/postamble.js b/modules/canvaskit/postamble.js
similarity index 100%
rename from experimental/canvaskit/htmlcanvas/postamble.js
rename to modules/canvaskit/postamble.js
diff --git a/modules/canvaskit/preamble.js b/modules/canvaskit/preamble.js
new file mode 100644
index 0000000..c6fd434
--- /dev/null
+++ b/modules/canvaskit/preamble.js
@@ -0,0 +1,9 @@
+// Adds compile-time JS functions to augment the CanvasKit interface.
+(function(CanvasKit) {
+
+
+// This intentionally dangles because we want all the
+// JS code to be in the same scope, but JS doesn't support
+// namespaces like C++ does. Thus, we simply include this
+// preamble.js file, all the source .js files and then postamble.js
+// to bundle everything in the same scope.
\ No newline at end of file
diff --git a/modules/canvaskit/ready.js b/modules/canvaskit/ready.js
new file mode 100644
index 0000000..60f2486
--- /dev/null
+++ b/modules/canvaskit/ready.js
@@ -0,0 +1,16 @@
+// See https://github.com/kripken/emscripten/issues/5820#issuecomment-385722568
+// for context on why the .then() that comes with Module breaks things (e.g. infinite loops)
+// and why the below fixes it.
+Module['ready'] = function() {
+  return new Promise(function (resolve, reject) {
+    Module['onAbort'] = reject;
+    if (runtimeInitialized) {
+      resolve(Module);
+    } else {
+      addOnPostRun(function() {
+        resolve(Module);
+      });
+    }
+  });
+}
+delete Module['then'];
\ No newline at end of file
diff --git a/experimental/canvaskit/release.js b/modules/canvaskit/release.js
similarity index 100%
rename from experimental/canvaskit/release.js
rename to modules/canvaskit/release.js
diff --git a/experimental/canvaskit/serve.py b/modules/canvaskit/serve.py
similarity index 100%
copy from experimental/canvaskit/serve.py
copy to modules/canvaskit/serve.py
diff --git a/modules/canvaskit/skottie.js b/modules/canvaskit/skottie.js
new file mode 100644
index 0000000..33e691a
--- /dev/null
+++ b/modules/canvaskit/skottie.js
@@ -0,0 +1,53 @@
+// Adds compile-time JS functions to augment the CanvasKit interface.
+// Specifically, anything that should only be on the Skottie builds of canvaskit.
+
+
+CanvasKit.MakeManagedAnimation = function(json, assets) {
+  if (!CanvasKit._MakeManagedAnimation) {
+    throw 'Not compiled with MakeManagedAnimation';
+  }
+  if (!assets) {
+    return CanvasKit._MakeManagedAnimation(json, 0, nullptr, nullptr, nullptr);
+  }
+  var assetNamePtrs = [];
+  var assetDataPtrs = [];
+  var assetSizes    = [];
+
+  var assetKeys = Object.keys(assets || {});
+  for (var i = 0; i < assetKeys.length; i++) {
+    var key = assetKeys[i];
+    var buffer = assets[key];
+    var data = new Uint8Array(buffer);
+
+    var iptr = CanvasKit._malloc(data.byteLength);
+    CanvasKit.HEAPU8.set(data, iptr);
+    assetDataPtrs.push(iptr);
+    assetSizes.push(data.byteLength);
+
+    // lengthBytesUTF8 and stringToUTF8Array are defined in the emscripten
+    // JS.  See https://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#stringToUTF8
+    // Add 1 for null terminator
+    var strLen = lengthBytesUTF8(key) + 1;
+    var strPtr = CanvasKit._malloc(strLen);
+
+    stringToUTF8(key, strPtr, strLen);
+    assetNamePtrs.push(strPtr);
+  }
+
+  // Not entirely sure if it matters, but the uintptr_t are 32 bits
+  // we want to copy our array of uintptr_t into the right size memory.
+  var namesPtr      = copy1dArray(assetNamePtrs, CanvasKit.HEAPU32);
+  var assetsPtr     = copy1dArray(assetDataPtrs, CanvasKit.HEAPU32);
+  var assetSizesPtr = copy1dArray(assetSizes,    CanvasKit.HEAPU32);
+
+  var anim = CanvasKit._MakeManagedAnimation(json, assetKeys.length, namesPtr,
+                                             assetsPtr, assetSizesPtr);
+
+  // We leave the asset data arrays and string data live and assume
+  // it is now owned by the C++ code
+  CanvasKit._free(namesPtr);
+  CanvasKit._free(assetsPtr);
+  CanvasKit._free(assetSizesPtr);
+
+  return anim;
+};
diff --git a/modules/canvaskit/skottie_bindings.cpp b/modules/canvaskit/skottie_bindings.cpp
new file mode 100644
index 0000000..3335b80
--- /dev/null
+++ b/modules/canvaskit/skottie_bindings.cpp
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCanvas.h"
+#include "SkImage.h"
+#include "SkMakeUnique.h"
+#include "SkTypes.h"
+#include "SkString.h"
+#include "Skottie.h"
+
+#include <string>
+#include <vector>
+
+#include <emscripten.h>
+#include <emscripten/bind.h>
+#include "WasmAliases.h"
+
+#if SK_INCLUDE_MANAGED_SKOTTIE
+#include "SkottieProperty.h"
+#include "SkottieUtils.h"
+#endif // SK_INCLUDE_MANAGED_SKOTTIE
+
+using namespace emscripten;
+
+#if SK_INCLUDE_MANAGED_SKOTTIE
+namespace {
+
+class SkottieAssetProvider : public skottie::ResourceProvider {
+public:
+    ~SkottieAssetProvider() override = default;
+
+    // Tried using a map, but that gave strange errors like
+    // https://emscripten.org/docs/porting/guidelines/function_pointer_issues.html
+    // Not entirely sure why, but perhaps the iterator in the map was
+    // confusing enscripten.
+    using AssetVec = std::vector<std::pair<SkString, sk_sp<SkData>>>;
+
+    static sk_sp<SkottieAssetProvider> Make(AssetVec assets) {
+        if (assets.empty()) {
+            return nullptr;
+        }
+
+        return sk_sp<SkottieAssetProvider>(new SkottieAssetProvider(std::move(assets)));
+    }
+
+    sk_sp<skottie::ImageAsset> loadImageAsset(const char[] /* path */,
+                                              const char name[]) const override {
+        // For CK/Skottie we ignore paths and identify images based solely on name.
+        if (auto data = this->findAsset(name)) {
+            return skottie_utils::MultiFrameImageAsset::Make(std::move(data), true /* predecode */);
+        }
+
+        return nullptr;
+    }
+
+    sk_sp<SkData> loadFont(const char name[], const char[] /* url */) const override {
+        // Same as images paths, we ignore font URLs.
+        return this->findAsset(name);
+    }
+
+private:
+    explicit SkottieAssetProvider(AssetVec assets) : fAssets(std::move(assets)) {}
+
+    sk_sp<SkData> findAsset(const char name[]) const {
+        for (const auto& asset : fAssets) {
+            if (asset.first.equals(name)) {
+                return asset.second;
+            }
+        }
+
+        SkDebugf("Could not find %s\n", name);
+        return nullptr;
+    }
+
+    const AssetVec fAssets;
+};
+
+class ManagedAnimation final : public SkRefCnt {
+public:
+    static sk_sp<ManagedAnimation> Make(const std::string& json, sk_sp<SkottieAssetProvider> ap) {
+        auto mgr = skstd::make_unique<skottie_utils::CustomPropertyManager>();
+        auto animation = skottie::Animation::Builder()
+                            .setMarkerObserver(mgr->getMarkerObserver())
+                            .setPropertyObserver(mgr->getPropertyObserver())
+                            .setResourceProvider(ap)
+                            .make(json.c_str(), json.size());
+
+        return animation
+            ? sk_sp<ManagedAnimation>(new ManagedAnimation(std::move(animation), std::move(mgr)))
+            : nullptr;
+    }
+
+    ~ManagedAnimation() override = default;
+
+    // skottie::Animation API
+    void render(SkCanvas* canvas) const { fAnimation->render(canvas, nullptr); }
+    void render(SkCanvas* canvas, const SkRect& dst) const { fAnimation->render(canvas, &dst); }
+    void seek(SkScalar t) { fAnimation->seek(t); }
+    SkScalar duration() const { return fAnimation->duration(); }
+    const SkSize&      size() const { return fAnimation->size(); }
+    std::string version() const { return std::string(fAnimation->version().c_str()); }
+
+    // CustomPropertyManager API
+    JSArray getColorProps() const {
+        JSArray props = emscripten::val::array();
+
+        for (const auto& cp : fPropMgr->getColorProps()) {
+            JSObject prop = emscripten::val::object();
+            prop.set("key", cp);
+            prop.set("value", fPropMgr->getColor(cp));
+            props.call<void>("push", prop);
+        }
+
+        return props;
+    }
+
+    JSArray getOpacityProps() const {
+        JSArray props = emscripten::val::array();
+
+        for (const auto& op : fPropMgr->getOpacityProps()) {
+            JSObject prop = emscripten::val::object();
+            prop.set("key", op);
+            prop.set("value", fPropMgr->getOpacity(op));
+            props.call<void>("push", prop);
+        }
+
+        return props;
+    }
+
+    bool setColor(const std::string& key, SkColor c) {
+        return fPropMgr->setColor(key, c);
+    }
+
+    bool setOpacity(const std::string& key, float o) {
+        return fPropMgr->setOpacity(key, o);
+    }
+
+    JSArray getMarkers() const {
+        JSArray markers = emscripten::val::array();
+        for (const auto& m : fPropMgr->markers()) {
+            JSObject marker = emscripten::val::object();
+            marker.set("name", m.name);
+            marker.set("t0"  , m.t0);
+            marker.set("t1"  , m.t1);
+            markers.call<void>("push", marker);
+        }
+        return markers;
+    }
+
+private:
+    ManagedAnimation(sk_sp<skottie::Animation> animation,
+                     std::unique_ptr<skottie_utils::CustomPropertyManager> propMgr)
+        : fAnimation(std::move(animation))
+        , fPropMgr(std::move(propMgr)) {}
+
+    sk_sp<skottie::Animation>                             fAnimation;
+    std::unique_ptr<skottie_utils::CustomPropertyManager> fPropMgr;
+};
+
+} // anonymous ns
+#endif // SK_INCLUDE_MANAGED_SKOTTIE
+
+EMSCRIPTEN_BINDINGS(Skottie) {
+    // Animation things (may eventually go in own library)
+    class_<skottie::Animation>("Animation")
+        .smart_ptr<sk_sp<skottie::Animation>>("sk_sp<Animation>")
+        .function("version", optional_override([](skottie::Animation& self)->std::string {
+            return std::string(self.version().c_str());
+        }))
+        .function("size", &skottie::Animation::size)
+        .function("duration", &skottie::Animation::duration)
+        .function("seek", &skottie::Animation::seek)
+        .function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas)->void {
+            self.render(canvas, nullptr);
+        }), allow_raw_pointers())
+        .function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas,
+                                                 const SkRect r)->void {
+            self.render(canvas, &r);
+        }), allow_raw_pointers());
+
+    function("MakeAnimation", optional_override([](std::string json)->sk_sp<skottie::Animation> {
+        return skottie::Animation::Make(json.c_str(), json.length());
+    }));
+    constant("skottie", true);
+
+#if SK_INCLUDE_MANAGED_SKOTTIE
+    class_<ManagedAnimation>("ManagedAnimation")
+        .smart_ptr<sk_sp<ManagedAnimation>>("sk_sp<ManagedAnimation>")
+        .function("version"   , &ManagedAnimation::version)
+        .function("size"      , &ManagedAnimation::size)
+        .function("duration"  , &ManagedAnimation::duration)
+        .function("seek"      , &ManagedAnimation::seek)
+        .function("render"    , select_overload<void(SkCanvas*) const>(&ManagedAnimation::render), allow_raw_pointers())
+        .function("render"    , select_overload<void(SkCanvas*, const SkRect&) const>
+                                    (&ManagedAnimation::render), allow_raw_pointers())
+        .function("setColor"  , &ManagedAnimation::setColor)
+        .function("setOpacity", &ManagedAnimation::setOpacity)
+        .function("getMarkers", &ManagedAnimation::getMarkers)
+        .function("getColorProps"  , &ManagedAnimation::getColorProps)
+        .function("getOpacityProps", &ManagedAnimation::getOpacityProps);
+
+    function("_MakeManagedAnimation", optional_override([](std::string json,
+                                                           size_t assetCount,
+                                                           uintptr_t /* char**     */ nptr,
+                                                           uintptr_t /* uint8_t**  */ dptr,
+                                                           uintptr_t /* size_t*    */ sptr)
+                                                        ->sk_sp<ManagedAnimation> {
+        const auto assetNames = reinterpret_cast<char**   >(nptr);
+        const auto assetDatas = reinterpret_cast<uint8_t**>(dptr);
+        const auto assetSizes = reinterpret_cast<size_t*  >(sptr);
+
+        SkottieAssetProvider::AssetVec assets;
+        assets.reserve(assetCount);
+
+        for (size_t i = 0; i < assetCount; i++) {
+            auto name  = SkString(assetNames[i]);
+            auto bytes = SkData::MakeFromMalloc(assetDatas[i], assetSizes[i]);
+            assets.push_back(std::make_pair(std::move(name), std::move(bytes)));
+        }
+
+        return ManagedAnimation::Make(json, SkottieAssetProvider::Make(std::move(assets)));
+    }));
+    constant("managed_skottie", true);
+#endif // SK_INCLUDE_MANAGED_SKOTTIE
+}
diff --git a/experimental/canvaskit/tests/assets/Bungee-Regular.ttf b/modules/canvaskit/tests/assets/Bungee-Regular.ttf
similarity index 100%
rename from experimental/canvaskit/tests/assets/Bungee-Regular.ttf
rename to modules/canvaskit/tests/assets/Bungee-Regular.ttf
Binary files differ
diff --git a/modules/canvaskit/tests/assets/NotoSerif-Regular.ttf b/modules/canvaskit/tests/assets/NotoSerif-Regular.ttf
new file mode 100644
index 0000000..a1c6f10
--- /dev/null
+++ b/modules/canvaskit/tests/assets/NotoSerif-Regular.ttf
Binary files differ
diff --git a/experimental/canvaskit/tests/assets/brickwork-texture.jpg b/modules/canvaskit/tests/assets/brickwork-texture.jpg
similarity index 100%
rename from experimental/canvaskit/tests/assets/brickwork-texture.jpg
rename to modules/canvaskit/tests/assets/brickwork-texture.jpg
Binary files differ
diff --git a/experimental/canvaskit/tests/assets/color_wheel.gif b/modules/canvaskit/tests/assets/color_wheel.gif
similarity index 100%
rename from experimental/canvaskit/tests/assets/color_wheel.gif
rename to modules/canvaskit/tests/assets/color_wheel.gif
Binary files differ
diff --git a/experimental/canvaskit/tests/assets/mandrill_512.png b/modules/canvaskit/tests/assets/mandrill_512.png
similarity index 100%
rename from experimental/canvaskit/tests/assets/mandrill_512.png
rename to modules/canvaskit/tests/assets/mandrill_512.png
Binary files differ
diff --git a/modules/canvaskit/tests/canvas2d.spec.js b/modules/canvaskit/tests/canvas2d.spec.js
new file mode 100644
index 0000000..f19db09
--- /dev/null
+++ b/modules/canvaskit/tests/canvas2d.spec.js
@@ -0,0 +1,875 @@
+describe('CanvasKit\'s Canvas 2d Behavior', function() {
+
+    let container = document.createElement('div');
+    document.body.appendChild(container);
+    const CANVAS_WIDTH = 600;
+    const CANVAS_HEIGHT = 600;
+
+    beforeEach(function() {
+        container.innerHTML = `
+            <canvas width=600 height=600 id=test></canvas>`;
+    });
+
+    afterEach(function() {
+        container.innerHTML = '';
+    });
+
+    describe('color strings', function() {
+        function hex(s) {
+            return parseInt(s, 16);
+        }
+
+        it('parses hex color strings', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                const parseColor = CanvasKit._testing.parseColor;
+                expect(parseColor('#FED')).toEqual(
+                    CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), 1));
+                expect(parseColor('#FEDC')).toEqual(
+                    CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), hex('CC')/255));
+                expect(parseColor('#fed')).toEqual(
+                    CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), 1));
+                expect(parseColor('#fedc')).toEqual(
+                    CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), hex('CC')/255));
+                done();
+            }));
+        });
+        it('parses rgba color strings', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                const parseColor = CanvasKit._testing.parseColor;
+                expect(parseColor('rgba(117, 33, 64, 0.75)')).toEqual(
+                    CanvasKit.Color(117, 33, 64, 0.75));
+                expect(parseColor('rgb(117, 33, 64, 0.75)')).toEqual(
+                    CanvasKit.Color(117, 33, 64, 0.75));
+                expect(parseColor('rgba(117,33,64)')).toEqual(
+                    CanvasKit.Color(117, 33, 64, 1.0));
+                expect(parseColor('rgb(117,33, 64)')).toEqual(
+                    CanvasKit.Color(117, 33, 64, 1.0));
+                done();
+            }));
+        });
+        it('parses named color strings', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                const parseColor = CanvasKit._testing.parseColor;
+                expect(parseColor('grey')).toEqual(
+                    CanvasKit.Color(128, 128, 128, 1.0));
+                expect(parseColor('blanchedalmond')).toEqual(
+                    CanvasKit.Color(255, 235, 205, 1.0));
+                expect(parseColor('transparent')).toEqual(
+                    CanvasKit.Color(0, 0, 0, 0));
+                done();
+            }));
+        });
+
+        it('properly produces color strings', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                const colorToString = CanvasKit._testing.colorToString;
+
+                expect(colorToString(CanvasKit.Color(102, 51, 153, 1.0))).toEqual('#663399');
+
+                expect(colorToString(CanvasKit.Color(255, 235, 205, 0.5))).toEqual(
+                                               'rgba(255, 235, 205, 0.50196078)');
+
+                done();
+            }));
+        });
+
+        it('can multiply colors by alpha', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                const multiplyByAlpha = CanvasKit.multiplyByAlpha;
+
+                const testCases = [
+                    {
+                        inColor:  CanvasKit.Color(102, 51, 153, 1.0),
+                        inAlpha:  1.0,
+                        outColor: CanvasKit.Color(102, 51, 153, 1.0),
+                    },
+                    {
+                        inColor:  CanvasKit.Color(102, 51, 153, 1.0),
+                        inAlpha:  0.8,
+                        outColor: CanvasKit.Color(102, 51, 153, 0.8),
+                    },
+                    {
+                        inColor:  CanvasKit.Color(102, 51, 153, 0.8),
+                        inAlpha:  0.7,
+                        outColor: CanvasKit.Color(102, 51, 153, 0.56),
+                    },
+                    {
+                        inColor:  CanvasKit.Color(102, 51, 153, 0.8),
+                        inAlpha:  1000,
+                        outColor: CanvasKit.Color(102, 51, 153, 1.0),
+                    },
+                ];
+
+                for (let tc of testCases) {
+                    // Print out the test case if the two don't match.
+                    expect(multiplyByAlpha(tc.inColor, tc.inAlpha))
+                          .toBe(tc.outColor, JSON.stringify(tc));
+                }
+
+                done();
+            }));
+        });
+    }); // end describe('color string parsing')
+
+    describe('fonts', function() {
+        it('can parse font sizes', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                const parseFontString = CanvasKit._testing.parseFontString;
+
+                const tests = [{
+                        'input': '10px monospace',
+                        'output': {
+                            'style': '',
+                            'variant': '',
+                            'weight': '',
+                            'sizePx': 10,
+                            'family': 'monospace',
+                        }
+                    },
+                    {
+                        'input': '15pt Arial',
+                        'output': {
+                            'style': '',
+                            'variant': '',
+                            'weight': '',
+                            'sizePx': 20,
+                            'family': 'Arial',
+                        }
+                    },
+                    {
+                        'input': '1.5in Arial, san-serif ',
+                        'output': {
+                            'style': '',
+                            'variant': '',
+                            'weight': '',
+                            'sizePx': 144,
+                            'family': 'Arial, san-serif',
+                        }
+                    },
+                    {
+                        'input': '1.5em SuperFont',
+                        'output': {
+                            'style': '',
+                            'variant': '',
+                            'weight': '',
+                            'sizePx': 24,
+                            'family': 'SuperFont',
+                        }
+                    },
+                ];
+
+                for (let i = 0; i < tests.length; i++) {
+                    expect(parseFontString(tests[i].input)).toEqual(tests[i].output);
+                }
+
+                done();
+            }));
+        });
+
+        it('can parse font attributes', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                const parseFontString = CanvasKit._testing.parseFontString;
+
+                const tests = [{
+                        'input': 'bold 10px monospace',
+                        'output': {
+                            'style': '',
+                            'variant': '',
+                            'weight': 'bold',
+                            'sizePx': 10,
+                            'family': 'monospace',
+                        }
+                    },
+                    {
+                        'input': 'italic bold 10px monospace',
+                        'output': {
+                            'style': 'italic',
+                            'variant': '',
+                            'weight': 'bold',
+                            'sizePx': 10,
+                            'family': 'monospace',
+                        }
+                    },
+                    {
+                        'input': 'italic small-caps bold 10px monospace',
+                        'output': {
+                            'style': 'italic',
+                            'variant': 'small-caps',
+                            'weight': 'bold',
+                            'sizePx': 10,
+                            'family': 'monospace',
+                        }
+                    },
+                    {
+                        'input': 'small-caps bold 10px monospace',
+                        'output': {
+                            'style': '',
+                            'variant': 'small-caps',
+                            'weight': 'bold',
+                            'sizePx': 10,
+                            'family': 'monospace',
+                        }
+                    },
+                    {
+                        'input': 'italic 10px monospace',
+                        'output': {
+                            'style': 'italic',
+                            'variant': '',
+                            'weight': '',
+                            'sizePx': 10,
+                            'family': 'monospace',
+                        }
+                    },
+                    {
+                        'input': 'small-caps 10px monospace',
+                        'output': {
+                            'style': '',
+                            'variant': 'small-caps',
+                            'weight': '',
+                            'sizePx': 10,
+                            'family': 'monospace',
+                        }
+                    },
+                    {
+                        'input': 'normal bold 10px monospace',
+                        'output': {
+                            'style': 'normal',
+                            'variant': '',
+                            'weight': 'bold',
+                            'sizePx': 10,
+                            'family': 'monospace',
+                        }
+                    },
+                ];
+
+                for (let i = 0; i < tests.length; i++) {
+                    expect(parseFontString(tests[i].input)).toEqual(tests[i].output);
+                }
+
+                done();
+            }));
+        });
+    });
+
+    function multipleCanvasTest(testname, done, test) {
+        const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
+        skcanvas._config = 'software_canvas';
+        const realCanvas = document.getElementById('test');
+        realCanvas._config = 'html_canvas';
+        realCanvas.width = CANVAS_WIDTH;
+        realCanvas.height = CANVAS_HEIGHT;
+
+        let promises = [];
+
+        for (let canvas of [skcanvas, realCanvas]) {
+            test(canvas);
+            // canvas has .toDataURL (even though skcanvas is not a real Canvas)
+            // so this will work.
+            promises.push(reportCanvas(canvas, testname, canvas._config));
+        }
+        Promise.all(promises).then(() => {
+            skcanvas.dispose();
+            done();
+        }).catch(reportError(done));
+    }
+
+    describe('CanvasContext2D API', function() {
+        it('supports all the line types', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                multipleCanvasTest('all_line_drawing_operations', done, (canvas) => {
+                    let ctx = canvas.getContext('2d');
+                    ctx.scale(3.0, 3.0);
+                    ctx.moveTo(20, 5);
+                    ctx.lineTo(30, 20);
+                    ctx.lineTo(40, 10);
+                    ctx.lineTo(50, 20);
+                    ctx.lineTo(60, 0);
+                    ctx.lineTo(20, 5);
+
+                    ctx.moveTo(20, 80);
+                    ctx.bezierCurveTo(90, 10, 160, 150, 190, 10);
+
+                    ctx.moveTo(36, 148);
+                    ctx.quadraticCurveTo(66, 188, 120, 136);
+                    ctx.lineTo(36, 148);
+
+                    ctx.rect(5, 170, 20, 25);
+
+                    ctx.moveTo(150, 180);
+                    ctx.arcTo(150, 100, 50, 200, 20);
+                    ctx.lineTo(160, 160);
+
+                    ctx.moveTo(20, 120);
+                    ctx.arc(20, 120, 18, 0, 1.75 * Math.PI);
+                    ctx.lineTo(20, 120);
+
+                    ctx.moveTo(150, 5);
+                    ctx.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI)
+
+                    ctx.lineWidth = 2;
+                    ctx.stroke();
+
+                    // Test edgecases and draw direction
+                    ctx.beginPath();
+                    ctx.arc(50, 100, 10, Math.PI, -Math.PI/2);
+                    ctx.stroke();
+                    ctx.beginPath();
+                    ctx.arc(75, 100, 10, Math.PI, -Math.PI/2, true);
+                    ctx.stroke();
+                    ctx.beginPath();
+                    ctx.arc(100, 100, 10, Math.PI, 100.1 * Math.PI, true);
+                    ctx.stroke();
+                    ctx.beginPath();
+                    ctx.arc(125, 100, 10, Math.PI, 100.1 * Math.PI, false);
+                    ctx.stroke();
+                    ctx.beginPath();
+                    ctx.ellipse(155, 100, 10, 15, Math.PI/8, 100.1 * Math.PI, Math.PI, true);
+                    ctx.stroke();
+                    ctx.beginPath();
+                    ctx.ellipse(180, 100, 10, 15, Math.PI/8, Math.PI, 100.1 * Math.PI, true);
+                    ctx.stroke();
+                });
+            }));
+        });
+
+        it('handles all the transforms as specified', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                multipleCanvasTest('all_matrix_operations', done, (canvas) => {
+                    let ctx = canvas.getContext('2d');
+                    ctx.rect(10, 10, 20, 20);
+
+                    ctx.scale(2.0, 4.0);
+                    ctx.rect(30, 10, 20, 20);
+                    ctx.resetTransform();
+
+                    ctx.rotate(Math.PI / 3);
+                    ctx.rect(50, 10, 20, 20);
+                    ctx.resetTransform();
+
+                    ctx.translate(30, -2);
+                    ctx.rect(70, 10, 20, 20);
+                    ctx.resetTransform();
+
+                    ctx.translate(60, 0);
+                    ctx.rotate(Math.PI / 6);
+                    ctx.transform(1.5, 0, 0, 0.5, 0, 0, 0); // effectively scale
+                    ctx.rect(90, 10, 20, 20);
+                    ctx.resetTransform();
+
+                    ctx.save();
+                    ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
+                    ctx.rect(110, 10, 20, 20);
+                    ctx.lineTo(110, 0);
+                    ctx.restore();
+                    ctx.lineTo(220, 120);
+
+                    ctx.scale(3.0, 3.0);
+                    ctx.font = '6pt Noto Mono';
+                    ctx.fillText('This text should be huge', 10, 80);
+                    ctx.resetTransform();
+
+                    ctx.strokeStyle = 'black';
+                    ctx.lineWidth = 2;
+                    ctx.stroke();
+
+                    ctx.beginPath();
+                    ctx.moveTo(250, 30);
+                    ctx.lineTo(250, 80);
+                    ctx.scale(3.0, 3.0);
+                    ctx.lineTo(280/3, 90/3);
+                    ctx.closePath();
+                    ctx.strokeStyle = 'black';
+                    ctx.lineWidth = 5;
+                    ctx.stroke();
+                });
+            }));
+        });
+
+        it('properly saves and restores states, even when drawing shadows', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                multipleCanvasTest('shadows_and_save_restore', done, (canvas) => {
+                    let ctx = canvas.getContext('2d');
+                    ctx.strokeStyle = '#000';
+                    ctx.fillStyle = '#CCC';
+                    ctx.shadowColor = 'rebeccapurple';
+                    ctx.shadowBlur = 1;
+                    ctx.shadowOffsetX = 3;
+                    ctx.shadowOffsetY = -8;
+                    ctx.rect(10, 10, 30, 30);
+
+                    ctx.save();
+                    ctx.strokeStyle = '#C00';
+                    ctx.fillStyle = '#00C';
+                    ctx.shadowBlur = 0;
+                    ctx.shadowColor = 'transparent';
+
+                    ctx.stroke();
+
+                    ctx.restore();
+                    ctx.fill();
+
+                    ctx.beginPath();
+                    ctx.moveTo(36, 148);
+                    ctx.quadraticCurveTo(66, 188, 120, 136);
+                    ctx.closePath();
+                    ctx.stroke();
+
+                    ctx.beginPath();
+                    ctx.shadowColor = '#993366AA';
+                    ctx.shadowOffsetX = 8;
+                    ctx.shadowBlur = 5;
+                    ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
+                    ctx.rect(110, 10, 20, 20);
+                    ctx.lineTo(110, 0);
+                    ctx.resetTransform();
+                    ctx.lineTo(220, 120);
+                    ctx.stroke();
+
+                    ctx.fillStyle = 'green';
+                    ctx.font = '16pt Noto Mono';
+                    ctx.fillText('This should be shadowed', 20, 80);
+
+                    ctx.beginPath();
+                    ctx.lineWidth = 6;
+                    ctx.ellipse(10, 290, 30, 30, 0, 0, Math.PI * 2);
+                    ctx.scale(2, 1);
+                    ctx.moveTo(10, 290)
+                    ctx.ellipse(10, 290, 30, 60, 0, 0, Math.PI * 2);
+                    ctx.resetTransform();
+                    ctx.shadowColor = '#993366AA';
+                    ctx.scale(3, 1);
+                    ctx.moveTo(10, 290)
+                    ctx.ellipse(10, 290, 30, 90, 0, 0, Math.PI * 2);
+                    ctx.stroke();
+                });
+            }));
+        });
+
+        it('fills/strokes rects and supports some global settings', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                multipleCanvasTest('global_dashed_rects', done, (canvas) => {
+                    let ctx = canvas.getContext('2d');
+                    ctx.scale(1.1, 1.1);
+                    ctx.translate(10, 10);
+                    // Shouldn't impact the fillRect calls
+                    ctx.setLineDash([5, 3]);
+
+                    ctx.fillStyle = 'rgba(200, 0, 100, 0.81)';
+                    ctx.fillRect(20, 30, 100, 100);
+
+                    ctx.globalAlpha = 0.81;
+                    ctx.fillStyle = 'rgba(200, 0, 100, 1.0)';
+                    ctx.fillRect(120, 30, 100, 100);
+                    // This shouldn't do anything
+                    ctx.globalAlpha = 0.1;
+
+                    ctx.fillStyle = 'rgba(200, 0, 100, 0.9)';
+                    ctx.globalAlpha = 0.9;
+                    // Intentional no-op to check ordering
+                    ctx.clearRect(220, 30, 100, 100);
+                    ctx.fillRect(220, 30, 100, 100);
+
+                    ctx.fillRect(320, 30, 100, 100);
+                    ctx.clearRect(330, 40, 80, 80);
+
+                    ctx.strokeStyle = 'blue';
+                    ctx.lineWidth = 3;
+                    ctx.setLineDash([5, 3]);
+                    ctx.strokeRect(20, 150, 100, 100);
+                    ctx.setLineDash([50, 30]);
+                    ctx.strokeRect(125, 150, 100, 100);
+                    ctx.lineDashOffset = 25;
+                    ctx.strokeRect(230, 150, 100, 100);
+                    ctx.setLineDash([2, 5, 9]);
+                    ctx.strokeRect(335, 150, 100, 100);
+
+                    ctx.setLineDash([5, 2]);
+                    ctx.moveTo(336, 400);
+                    ctx.quadraticCurveTo(366, 488, 120, 450);
+                    ctx.lineTo(300, 400);
+                    ctx.stroke();
+
+                    ctx.font = '36pt Noto Mono';
+                    ctx.strokeText('Dashed', 20, 350);
+                    ctx.fillText('Not Dashed', 20, 400);
+                });
+            }));
+        });
+
+        it('supports gradients, which respect clip/save/restore', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                multipleCanvasTest('gradients_clip', done, (canvas) => {
+                    const ctx = canvas.getContext('2d');
+
+                    const rgradient = ctx.createRadialGradient(200, 300, 10, 100, 100, 300);
+
+                    rgradient.addColorStop(0, 'red');
+                    rgradient.addColorStop(.7, 'white');
+                    rgradient.addColorStop(1, 'blue');
+
+                    ctx.fillStyle = rgradient;
+                    ctx.globalAlpha = 0.7;
+                    ctx.fillRect(0,0,600,600);
+                    ctx.globalAlpha = 0.95;
+
+                    ctx.beginPath();
+                    ctx.arc(300, 100, 90, 0, Math.PI*1.66);
+                    ctx.closePath();
+                    ctx.strokeStyle = 'yellow';
+                    ctx.lineWidth = 5;
+                    ctx.stroke();
+                    ctx.save();
+                    ctx.clip();
+
+                    const lgradient = ctx.createLinearGradient(200, 20, 420, 40);
+
+                    lgradient.addColorStop(0, 'green');
+                    lgradient.addColorStop(.5, 'cyan');
+                    lgradient.addColorStop(1, 'orange');
+
+                    ctx.fillStyle = lgradient;
+
+                    ctx.fillRect(200, 30, 200, 300);
+
+                    ctx.restore();
+                    ctx.fillRect(550, 550, 40, 40);
+                });
+            }));
+        });
+
+        it('can draw png images', function(done) {
+            let skImageData = null;
+            let htmlImage = null;
+            let skPromise = fetch('/assets/mandrill_512.png')
+                .then((response) => response.arrayBuffer())
+                .then((buffer) => {
+                    skImageData = buffer;
+
+                });
+            let realPromise = fetch('/assets/mandrill_512.png')
+                .then((response) => response.blob())
+                .then((blob) => createImageBitmap(blob))
+                .then((bitmap) => {
+                    htmlImage = bitmap;
+                });
+            LoadCanvasKit.then(catchException(done, () => {
+                Promise.all([realPromise, skPromise]).then(() => {
+                    multipleCanvasTest('draw_image', done, (canvas) => {
+                        let ctx = canvas.getContext('2d');
+                        let img = htmlImage;
+                        if (canvas._config == 'software_canvas') {
+                            img = canvas.decodeImage(skImageData);
+                        }
+                        ctx.drawImage(img, 30, -200);
+
+                        ctx.globalAlpha = 0.7
+                        ctx.rotate(.1);
+                        ctx.imageSmoothingQuality = 'medium';
+                        ctx.drawImage(img, 200, 350, 150, 100);
+                        ctx.rotate(-.2);
+                        ctx.imageSmoothingEnabled = false;
+                        ctx.drawImage(img, 100, 150, 400, 350, 10, 400, 150, 100);
+                    });
+                });
+            }));
+        });
+
+        it('can get and put pixels', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                multipleCanvasTest('get_put_imagedata', done, (canvas) => {
+                    let ctx = canvas.getContext('2d');
+                    // Make a gradient so we see if the pixels copying worked
+                    let grad = ctx.createLinearGradient(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
+                    grad.addColorStop(0, 'yellow');
+                    grad.addColorStop(1, 'red');
+                    ctx.fillStyle = grad;
+                    ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
+
+                    let iData = ctx.getImageData(400, 100, 200, 150);
+                    expect(iData.width).toBe(200);
+                    expect(iData.height).toBe(150);
+                    expect(iData.data.byteLength).toBe(200*150*4);
+                    ctx.putImageData(iData, 10, 10);
+                    ctx.putImageData(iData, 350, 350, 100, 75, 45, 40);
+                    ctx.strokeRect(350, 350, 200, 150);
+
+                    let box = ctx.createImageData(20, 40);
+                    ctx.putImageData(box, 10, 300);
+                    let biggerBox = ctx.createImageData(iData);
+                    ctx.putImageData(biggerBox, 10, 350);
+                    expect(biggerBox.width).toBe(iData.width);
+                    expect(biggerBox.height).toBe(iData.height);
+                });
+            }));
+        });
+
+        it('can make patterns', function(done) {
+            let skImageData = null;
+            let htmlImage = null;
+            let skPromise = fetch('/assets/mandrill_512.png')
+                .then((response) => response.arrayBuffer())
+                .then((buffer) => {
+                    skImageData = buffer;
+
+                });
+            let realPromise = fetch('/assets/mandrill_512.png')
+                .then((response) => response.blob())
+                .then((blob) => createImageBitmap(blob))
+                .then((bitmap) => {
+                    htmlImage = bitmap;
+                });
+            LoadCanvasKit.then(catchException(done, () => {
+                Promise.all([realPromise, skPromise]).then(() => {
+                    multipleCanvasTest('draw_patterns', done, (canvas) => {
+                        let ctx = canvas.getContext('2d');
+                        let img = htmlImage;
+                        if (canvas._config == 'software_canvas') {
+                            img = canvas.decodeImage(skImageData);
+                        }
+                        ctx.fillStyle = '#EEE';
+                        ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
+                        ctx.lineWidth = 20;
+                        ctx.scale(0.2, 0.4);
+
+                        let pattern = ctx.createPattern(img, 'repeat');
+                        ctx.fillStyle = pattern;
+                        ctx.fillRect(0, 0, 1500, 750);
+
+                        pattern = ctx.createPattern(img, 'repeat-x');
+                        ctx.fillStyle = pattern;
+                        ctx.fillRect(1500, 0, 3000, 750);
+
+                        ctx.globalAlpha = 0.7
+                        pattern = ctx.createPattern(img, 'repeat-y');
+                        ctx.fillStyle = pattern;
+                        ctx.fillRect(0, 750, 1500, 1500);
+                        ctx.strokeRect(0, 750, 1500, 1500);
+
+                        pattern = ctx.createPattern(img, 'no-repeat');
+                        ctx.fillStyle = pattern;
+                        pattern.setTransform({a: 1, b: -.1, c:.1, d: 0.5, e: 1800, f:800});
+                        ctx.fillRect(0, 0, 3000, 1500);
+                    });
+                });
+            }));
+        });
+
+        it('can get and put pixels', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                function drawPoint(ctx, x, y, color) {
+                    ctx.fillStyle = color;
+                    ctx.fillRect(x, y, 1, 1);
+                }
+                const IN = 'purple';
+                const OUT = 'orange';
+                const SCALE = 8;
+
+                // Check to see if these points are in or out on each of the
+                // test configurations.
+                const pts = [[3, 3], [4, 4], [5, 5], [10, 10], [8, 10], [6, 10],
+                             [6.5, 9], [15, 10], [17, 10], [17, 11], [24, 24],
+                             [25, 25], [26, 26], [27, 27]];
+                const tests = [
+                    {
+                        xOffset: 0,
+                        yOffset: 0,
+                        fillType: 'nonzero',
+                        strokeWidth: 0,
+                        testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'nonzero'),
+                    },
+                    {
+                        xOffset: 30,
+                        yOffset: 0,
+                        fillType: 'evenodd',
+                        strokeWidth: 0,
+                        testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'evenodd'),
+                    },
+                    {
+                        xOffset: 0,
+                        yOffset: 30,
+                        fillType: null,
+                        strokeWidth: 1,
+                        testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
+                    },
+                    {
+                        xOffset: 30,
+                        yOffset: 30,
+                        fillType: null,
+                        strokeWidth: 2,
+                        testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
+                    },
+                ];
+                multipleCanvasTest('points_in_path_stroke', done, (canvas) => {
+                    let ctx = canvas.getContext('2d');
+                    ctx.font = '20px Noto Mono';
+                    // Draw some visual aids
+                    ctx.fillText('path-nonzero', 60, 30);
+                    ctx.fillText('path-evenodd', 300, 30);
+                    ctx.fillText('stroke-1px-wide', 60, 260);
+                    ctx.fillText('stroke-2px-wide', 300, 260);
+                    ctx.fillText('purple is IN, orange is OUT', 20, 560);
+
+                    // Scale up to make single pixels easier to see
+                    ctx.scale(SCALE, SCALE);
+                    for (let test of tests) {
+                        ctx.beginPath();
+                        let xOffset = test.xOffset;
+                        let yOffset = test.yOffset;
+
+                        ctx.fillStyle = '#AAA';
+                        ctx.lineWidth = test.strokeWidth;
+                        ctx.rect(5+xOffset, 5+yOffset, 20, 20);
+                        ctx.arc(15+xOffset, 15+yOffset, 8, 0, Math.PI*2, false);
+                        if (test.fillType) {
+                            ctx.fill(test.fillType);
+                        } else {
+                            ctx.stroke();
+                        }
+
+                        for (let pt of pts) {
+                            let [x, y] = pt;
+                            x += xOffset;
+                            y += yOffset;
+                            // naively apply transform when querying because the points queried
+                            // ignore the CTM.
+                            if (test.testFn(ctx, x, y)) {
+                              drawPoint(ctx, x, y, IN);
+                            } else {
+                              drawPoint(ctx, x, y, OUT);
+                            }
+                        }
+                    }
+                });
+            }));
+        });
+
+        it('can load custom fonts', function(done) {
+            let realFontLoaded = new FontFace('BungeeNonSystem', 'url(/assets/Bungee-Regular.ttf)', {
+                'family': 'BungeeNonSystem', //Make sure the canvas does not use the system font
+                'style': 'normal',
+                'weight': '400',
+            }).load().then((font) => {
+                document.fonts.add(font);
+            });
+
+            let fontBuffer = null;
+
+            let skFontLoaded = fetch('/assets/Bungee-Regular.ttf').then(
+                (response) => response.arrayBuffer()).then(
+                (buffer) => {
+                    fontBuffer = buffer;
+                });
+
+            LoadCanvasKit.then(catchException(done, () => {
+                Promise.all([realFontLoaded, skFontLoaded]).then(() => {
+                    multipleCanvasTest('custom_font', done, (canvas) => {
+                        if (canvas.loadFont) {
+                            canvas.loadFont(fontBuffer, {
+                                'family': 'BungeeNonSystem',
+                                'style': 'normal',
+                                'weight': '400',
+                            });
+                        }
+                        let ctx = canvas.getContext('2d');
+
+                        ctx.font = '20px monospace';
+                        ctx.fillText('20 px monospace', 10, 30);
+
+                        ctx.font = '2.0em BungeeNonSystem';
+                        ctx.fillText('2.0em Bungee filled', 10, 80);
+                        ctx.strokeText('2.0em Bungee stroked', 10, 130);
+
+                        ctx.font = '40pt monospace';
+                        ctx.strokeText('40pt monospace', 10, 200);
+
+                        // bold wasn't defined, so should fallback to just the 400 weight
+                        ctx.font = 'bold 45px BungeeNonSystem';
+                        ctx.fillText('45px Bungee filled', 10, 260);
+                    });
+                });
+            }));
+        });
+        it('can read default properties', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
+                const realCanvas = document.getElementById('test');
+                realCanvas.width = CANVAS_WIDTH;
+                realCanvas.height = CANVAS_HEIGHT;
+
+                const skcontext = skcanvas.getContext('2d');
+                const realContext = realCanvas.getContext('2d');
+                // The skia canvas only comes with a monospace font by default
+                // Set the html canvas to be monospace too.
+                realContext.font = '10px monospace';
+
+                const toTest = ['font', 'lineWidth', 'strokeStyle', 'lineCap',
+                                'lineJoin', 'miterLimit', 'shadowOffsetY',
+                                'shadowBlur', 'shadowColor', 'shadowOffsetX',
+                                'globalAlpha', 'globalCompositeOperation',
+                                'lineDashOffset', 'imageSmoothingEnabled',
+                                'imageFilterQuality'];
+
+                // Compare all the default values of the properties of skcanvas
+                // to the default values on the properties of a real canvas.
+                for(let attr of toTest) {
+                    expect(skcontext[attr]).toBe(realContext[attr], attr);
+                }
+
+                skcanvas.dispose();
+                done();
+            }));
+        });
+    }); // end describe('CanvasContext2D API')
+
+    describe('Path2D API', function() {
+        it('supports all the line types', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                multipleCanvasTest('path2d_line_drawing_operations', done, (canvas) => {
+                    let ctx = canvas.getContext('2d');
+                    let clock;
+                    let path;
+                    if (canvas.makePath2D) {
+                        clock = canvas.makePath2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
+                        path = canvas.makePath2D();
+                    } else {
+                        clock = new Path2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z')
+                        path = new Path2D();
+                    }
+                    path.moveTo(20, 5);
+                    path.lineTo(30, 20);
+                    path.lineTo(40, 10);
+                    path.lineTo(50, 20);
+                    path.lineTo(60, 0);
+                    path.lineTo(20, 5);
+
+                    path.moveTo(20, 80);
+                    path.bezierCurveTo(90, 10, 160, 150, 190, 10);
+
+                    path.moveTo(36, 148);
+                    path.quadraticCurveTo(66, 188, 120, 136);
+                    path.lineTo(36, 148);
+
+                    path.rect(5, 170, 20, 25);
+
+                    path.moveTo(150, 180);
+                    path.arcTo(150, 100, 50, 200, 20);
+                    path.lineTo(160, 160);
+
+                    path.moveTo(20, 120);
+                    path.arc(20, 120, 18, 0, 1.75 * Math.PI);
+                    path.lineTo(20, 120);
+
+                    path.moveTo(150, 5);
+                    path.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI)
+
+                    ctx.lineWidth = 2;
+                    ctx.scale(3.0, 3.0);
+                    ctx.stroke(path);
+                    ctx.stroke(clock);
+                });
+            }));
+        });
+    }); // end describe('Path2D API')
+
+
+});
diff --git a/modules/canvaskit/tests/canvaskitinit.js b/modules/canvaskit/tests/canvaskitinit.js
new file mode 100644
index 0000000..2742f45
--- /dev/null
+++ b/modules/canvaskit/tests/canvaskitinit.js
@@ -0,0 +1,18 @@
+// The increased timeout is especially needed with larger binaries
+// like in the debug/gpu build
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
+
+let CanvasKit = null;
+const LoadCanvasKit = new Promise(function(resolve, reject) {
+    console.log('canvaskit loading', new Date());
+    CanvasKitInit({
+        locateFile: (file) => '/canvaskit/'+file,
+    }).ready().then((loaded) => {
+        console.log('canvaskit loaded', new Date());
+        CanvasKit = loaded;
+        resolve();
+    }).catch((e) => {
+        console.error('canvaskit failed to load', new Date(), e);
+        reject();
+    });
+});
\ No newline at end of file
diff --git a/modules/canvaskit/tests/core.spec.js b/modules/canvaskit/tests/core.spec.js
new file mode 100644
index 0000000..7656d31
--- /dev/null
+++ b/modules/canvaskit/tests/core.spec.js
@@ -0,0 +1,50 @@
+describe('Core canvas behavior', function() {
+    let container = document.createElement('div');
+    document.body.appendChild(container);
+    const CANVAS_WIDTH = 600;
+    const CANVAS_HEIGHT = 600;
+
+    beforeEach(function() {
+        container.innerHTML = `
+            <canvas width=600 height=600 id=test></canvas>
+            <canvas width=600 height=600 id=report></canvas>`;
+    });
+
+    afterEach(function() {
+        container.innerHTML = '';
+    });
+
+    it('can draw an SkPicture', function(done) {
+        LoadCanvasKit.then(catchException(done, () => {
+            // This is taken from example.html
+            const surface = CanvasKit.MakeCanvasSurface('test');
+            expect(surface).toBeTruthy('Could not make surface')
+            if (!surface) {
+                done();
+                return;
+            }
+            const spr = new CanvasKit.SkPictureRecorder();
+            const rcanvas = spr.beginRecording(
+                            CanvasKit.LTRBRect(0, 0, surface.width(), surface.height()));
+            const paint = new CanvasKit.SkPaint();
+            paint.setStrokeWidth(2.0);
+            paint.setAntiAlias(true);
+            paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
+            paint.setStyle(CanvasKit.PaintStyle.Stroke);
+
+            rcanvas.drawRoundRect(CanvasKit.LTRBRect(5, 35, 45, 80), 15, 10, paint);
+
+            const font = new CanvasKit.SkFont(null, 20);
+            rcanvas.drawText('this picture has a round rect', 5, 100, paint, font);
+            const pic = spr.finishRecordingAsPicture();
+            spr.delete();
+
+
+            const canvas = surface.getCanvas();
+            canvas.drawPicture(pic);
+
+            reportSurface(surface, 'picture_test', done);
+        }));
+    });
+
+});
diff --git a/modules/canvaskit/tests/font.spec.js b/modules/canvaskit/tests/font.spec.js
new file mode 100644
index 0000000..dbe0e23
--- /dev/null
+++ b/modules/canvaskit/tests/font.spec.js
@@ -0,0 +1,190 @@
+describe('CanvasKit\'s Path Behavior', function() {
+    let container = document.createElement('div');
+    document.body.appendChild(container);
+    const CANVAS_WIDTH = 600;
+    const CANVAS_HEIGHT = 600;
+
+    beforeEach(function() {
+        container.innerHTML = `
+            <canvas width=600 height=600 id=test></canvas>
+            <canvas width=600 height=600 id=report></canvas>`;
+    });
+
+    afterEach(function() {
+        container.innerHTML = '';
+    });
+
+    let notSerifFontBuffer = null;
+    // This font is known to support kerning
+    const notoSerifFontLoaded = fetch('/assets/NotoSerif-Regular.ttf').then(
+        (response) => response.arrayBuffer()).then(
+        (buffer) => {
+            notSerifFontBuffer = buffer;
+        });
+
+    it('can draw shaped and unshaped text', function(done) {
+        LoadCanvasKit.then(catchException(done, () => {
+            notoSerifFontLoaded.then(() => {
+                // This is taken from example.html
+                const surface = CanvasKit.MakeCanvasSurface('test');
+                expect(surface).toBeTruthy('Could not make surface')
+                if (!surface) {
+                    done();
+                    return;
+                }
+                const canvas = surface.getCanvas();
+                const paint = new CanvasKit.SkPaint();
+
+                paint.setColor(CanvasKit.BLUE);
+                paint.setStyle(CanvasKit.PaintStyle.Stroke);
+
+                const fontMgr = CanvasKit.SkFontMgr.RefDefault();
+                const notoSerif = fontMgr.MakeTypefaceFromData(notSerifFontBuffer);
+
+                const textPaint = new CanvasKit.SkPaint();
+                // use the built-in monospace typeface.
+                const textFont = new CanvasKit.SkFont(notoSerif, 20);
+
+                canvas.drawRect(CanvasKit.LTRBRect(30, 30, 200, 200), paint);
+                canvas.drawText('This text is not shaped, and overflows the boundry',
+                                35, 50, textPaint, textFont);
+
+                const shapedText = new CanvasKit.ShapedText({
+                  font: textFont,
+                  leftToRight: true,
+                  text: 'This text *is* shaped, and wraps to the right width.',
+                  width: 160,
+                });
+                const textBoxX = 35;
+                const textBoxY = 55;
+                canvas.drawText(shapedText, textBoxX, textBoxY, textPaint);
+                const bounds = shapedText.getBounds();
+
+                bounds.fLeft += textBoxX;
+                bounds.fRight += textBoxX;
+                bounds.fTop += textBoxY;
+                bounds.fBottom += textBoxY
+
+                canvas.drawRect(bounds, paint);
+                const SHAPE_TEST_TEXT = 'VAVAVAVAVAFIfi';
+                const textFont2 = new CanvasKit.SkFont(notoSerif, 60);
+                const shapedText2 = new CanvasKit.ShapedText({
+                  font: textFont2,
+                  leftToRight: true,
+                  text: SHAPE_TEST_TEXT,
+                  width: 600,
+                });
+
+                canvas.drawText('no kerning ↓', 10, 240, textPaint, textFont);
+                canvas.drawText(SHAPE_TEST_TEXT, 10, 300, textPaint, textFont2);
+                canvas.drawText(shapedText2, 10, 300, textPaint);
+                canvas.drawText('kerning ↑', 10, 390, textPaint, textFont);
+
+                surface.flush();
+
+                paint.delete();
+                notoSerif.delete();
+                textPaint.delete();
+                textFont.delete();
+                shapedText.delete();
+                textFont2.delete();
+                shapedText2.delete();
+                fontMgr.delete();
+                reportSurface(surface, 'text_shaping', done);
+            });
+        }));
+    });
+
+    it('can draw text following a path', function(done) {
+        LoadCanvasKit.then(catchException(done, () => {
+            const surface = CanvasKit.MakeCanvasSurface('test');
+            expect(surface).toBeTruthy('Could not make surface')
+            if (!surface) {
+                done();
+                return;
+            }
+            const canvas = surface.getCanvas();
+            const paint = new CanvasKit.SkPaint();
+            paint.setAntiAlias(true);
+            paint.setStyle(CanvasKit.PaintStyle.Stroke);
+
+            const font = new CanvasKit.SkFont(null, 24);
+            const fontPaint = new CanvasKit.SkPaint();
+            fontPaint.setAntiAlias(true);
+            fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
+
+
+            const arc = new CanvasKit.SkPath();
+            arc.arcTo(CanvasKit.LTRBRect(20, 40, 280, 300), -160, 140, true);
+            arc.lineTo(210, 140);
+            arc.arcTo(CanvasKit.LTRBRect(20, 0, 280, 260), 160, -140, true);
+
+            // Only 1 dot should show up in the image, because we run out of path.
+            const str = 'This téxt should follow the curve across contours...';
+            const textBlob = CanvasKit.SkTextBlob.MakeOnPath(str, arc, font);
+
+            canvas.drawPath(arc, paint);
+            canvas.drawTextBlob(textBlob, 0, 0, fontPaint);
+
+            surface.flush();
+
+            textBlob.delete();
+            arc.delete();
+            paint.delete();
+            font.delete();
+            fontPaint.delete();
+
+            reportSurface(surface, 'monospace_text_on_path', done);
+        }));
+    });
+
+    it('can draw text following a path with a non-serif font', function(done) {
+        LoadCanvasKit.then(catchException(done, () => {
+            notoSerifFontLoaded.then(() => {
+                const surface = CanvasKit.MakeCanvasSurface('test');
+                expect(surface).toBeTruthy('Could not make surface')
+                if (!surface) {
+                    done();
+                    return;
+                }
+                const fontMgr = CanvasKit.SkFontMgr.RefDefault();
+                const notoSerif = fontMgr.MakeTypefaceFromData(notSerifFontBuffer);
+
+                const canvas = surface.getCanvas();
+                const paint = new CanvasKit.SkPaint();
+                paint.setAntiAlias(true);
+                paint.setStyle(CanvasKit.PaintStyle.Stroke);
+
+                const font = new CanvasKit.SkFont(notoSerif, 24);
+                const fontPaint = new CanvasKit.SkPaint();
+                fontPaint.setAntiAlias(true);
+                fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
+
+
+                const arc = new CanvasKit.SkPath();
+                arc.arcTo(CanvasKit.LTRBRect(20, 40, 280, 300), -160, 140, true);
+                arc.lineTo(210, 140);
+                arc.arcTo(CanvasKit.LTRBRect(20, 0, 280, 260), 160, -140, true);
+
+                const str = 'This téxt should follow the curve across contours...';
+                const textBlob = CanvasKit.SkTextBlob.MakeOnPath(str, arc, font, 60.5);
+
+                canvas.drawPath(arc, paint);
+                canvas.drawTextBlob(textBlob, 0, 0, fontPaint);
+
+                surface.flush();
+
+                textBlob.delete();
+                arc.delete();
+                paint.delete();
+                notoSerif.delete();
+                font.delete();
+                fontPaint.delete();
+                fontMgr.delete();
+                reportSurface(surface, 'serif_text_on_path', done);
+            });
+        }));
+    });
+
+    // TODO more tests
+});
diff --git a/experimental/canvaskit/tests/matrix.spec.js b/modules/canvaskit/tests/matrix.spec.js
similarity index 100%
rename from experimental/canvaskit/tests/matrix.spec.js
rename to modules/canvaskit/tests/matrix.spec.js
diff --git a/modules/canvaskit/tests/path.spec.js b/modules/canvaskit/tests/path.spec.js
new file mode 100644
index 0000000..47ed8a1
--- /dev/null
+++ b/modules/canvaskit/tests/path.spec.js
@@ -0,0 +1,205 @@
+describe('CanvasKit\'s Path Behavior', function() {
+    let container = document.createElement('div');
+    document.body.appendChild(container);
+    const CANVAS_WIDTH = 600;
+    const CANVAS_HEIGHT = 600;
+
+    beforeEach(function() {
+        container.innerHTML = `
+            <canvas width=600 height=600 id=test></canvas>
+            <canvas width=600 height=600 id=report></canvas>`;
+    });
+
+    afterEach(function() {
+        container.innerHTML = '';
+    });
+
+    it('can draw a path', function(done) {
+        LoadCanvasKit.then(catchException(done, () => {
+            // This is taken from example.html
+            const surface = CanvasKit.MakeCanvasSurface('test');
+            expect(surface).toBeTruthy('Could not make surface')
+            if (!surface) {
+                done();
+                return;
+            }
+            const canvas = surface.getCanvas();
+            const paint = new CanvasKit.SkPaint();
+            paint.setStrokeWidth(1.0);
+            paint.setAntiAlias(true);
+            paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
+            paint.setStyle(CanvasKit.PaintStyle.Stroke);
+
+            const path = new CanvasKit.SkPath();
+            path.moveTo(20, 5);
+            path.lineTo(30, 20);
+            path.lineTo(40, 10);
+            path.lineTo(50, 20);
+            path.lineTo(60, 0);
+            path.lineTo(20, 5);
+
+            path.moveTo(20, 80);
+            path.cubicTo(90, 10, 160, 150, 190, 10);
+
+            path.moveTo(36, 148);
+            path.quadTo(66, 188, 120, 136);
+            path.lineTo(36, 148);
+
+            path.moveTo(150, 180);
+            path.arcTo(150, 100, 50, 200, 20);
+            path.lineTo(160, 160);
+
+            path.moveTo(20, 120);
+            path.lineTo(20, 120);
+
+            path.transform([2, 0, 0,
+                            0, 2, 0,
+                            0, 0, 1 ])
+
+            canvas.drawPath(path, paint);
+
+            let rrect = new CanvasKit.SkPath()
+                               .addRoundRect(100, 10, 140, 62,
+                                             10, 4, true);
+
+            canvas.drawPath(rrect, paint);
+            rrect.delete();
+
+            surface.flush();
+
+            path.delete();
+            paint.delete();
+
+            reportSurface(surface, 'path_api_example', done);
+        }));
+        // See PathKit for more tests, since they share implementation
+    });
+
+    it('can draw directly to a canvas', function(done) {
+        LoadCanvasKit.then(catchException(done, () => {
+            // This is taken from example.html
+            const surface = CanvasKit.MakeCanvasSurface('test');
+            expect(surface).toBeTruthy('Could not make surface')
+            if (!surface) {
+                done();
+                return;
+            }
+            const canvas = surface.getCanvas();
+            const paint = new CanvasKit.SkPaint();
+            paint.setStrokeWidth(2.0);
+            paint.setAntiAlias(true);
+            paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
+            paint.setStyle(CanvasKit.PaintStyle.Stroke);
+
+            canvas.drawLine(3, 10, 30, 15, paint);
+            canvas.drawRoundRect(CanvasKit.LTRBRect(5, 35, 45, 80), 15, 10, paint);
+
+            canvas.drawOval(CanvasKit.LTRBRect(5, 35, 45, 80), paint);
+
+            canvas.drawArc(CanvasKit.LTRBRect(55, 35, 95, 80), 15, 270, true, paint);
+
+            const font = new CanvasKit.SkFont(null, 20);
+            canvas.drawText('this is ascii text', 5, 100, paint, font);
+
+            const blob = CanvasKit.SkTextBlob.MakeFromText('Unicode chars 💩 é É ص', font);
+            canvas.drawTextBlob(blob, 5, 130, paint);
+
+            surface.flush();
+            font.delete();
+            blob.delete();
+            paint.delete();
+
+            reportSurface(surface, 'canvas_api_example', done);
+        }));
+        // See canvas2d for more API tests
+    });
+
+    function starPath(CanvasKit, X=128, Y=128, R=116) {
+        let p = new CanvasKit.SkPath();
+        p.moveTo(X + R, Y);
+        for (let i = 1; i < 8; i++) {
+          let a = 2.6927937 * i;
+          p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
+        }
+        return p;
+      }
+
+    it('can apply an effect and draw text', function(done) {
+        LoadCanvasKit.then(catchException(done, () => {
+            const surface = CanvasKit.MakeCanvasSurface('test');
+            expect(surface).toBeTruthy('Could not make surface')
+            if (!surface) {
+                done();
+                return;
+            }
+            const canvas = surface.getCanvas();
+            const path = starPath(CanvasKit);
+
+            const paint = new CanvasKit.SkPaint();
+
+            const textPaint = new CanvasKit.SkPaint();
+            textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
+            textPaint.setAntiAlias(true);
+
+            const textFont = new CanvasKit.SkFont(null, 30);
+
+            const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], 1);
+
+            paint.setPathEffect(dpe);
+            paint.setStyle(CanvasKit.PaintStyle.Stroke);
+            paint.setStrokeWidth(5.0);
+            paint.setAntiAlias(true);
+            paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
+
+            canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
+
+            canvas.drawPath(path, paint);
+            canvas.drawText('This is text', 10, 280, textPaint, textFont);
+            surface.flush();
+            dpe.delete();
+            path.delete();
+
+            reportSurface(surface, 'effect_and_text_example', done);
+        }));
+    });
+
+    it('can create a path from an SVG string', function(done) {
+        LoadCanvasKit.then(catchException(done, () => {
+            //.This is a parallelagram from
+            // https://upload.wikimedia.org/wikipedia/commons/e/e7/Simple_parallelogram.svg
+            let path = CanvasKit.MakePathFromSVGString('M 205,5 L 795,5 L 595,295 L 5,295 L 205,5 z');
+
+            let cmds = path.toCmds();
+            expect(cmds).toBeTruthy();
+            // 1 move, 4 lines, 1 close
+            // each element in cmds is an array, with index 0 being the verb, and the rest being args
+            expect(cmds.length).toBe(6);
+            expect(cmds).toEqual([[CanvasKit.MOVE_VERB, 205, 5],
+                                  [CanvasKit.LINE_VERB, 795, 5],
+                                  [CanvasKit.LINE_VERB, 595, 295],
+                                  [CanvasKit.LINE_VERB, 5, 295],
+                                  [CanvasKit.LINE_VERB, 205, 5],
+                                  [CanvasKit.CLOSE_VERB]]);
+            path.delete();
+            done();
+        }));
+    });
+
+     it('can create an SVG string from a path', function(done) {
+        LoadCanvasKit.then(catchException(done, () => {
+            let cmds = [[CanvasKit.MOVE_VERB, 205, 5],
+                       [CanvasKit.LINE_VERB, 795, 5],
+                       [CanvasKit.LINE_VERB, 595, 295],
+                       [CanvasKit.LINE_VERB, 5, 295],
+                       [CanvasKit.LINE_VERB, 205, 5],
+                       [CanvasKit.CLOSE_VERB]];
+            let path = CanvasKit.MakePathFromCmds(cmds);
+
+            let svgStr = path.toSVGString();
+            // We output it in terse form, which is different than Wikipedia's version
+            expect(svgStr).toEqual('M205 5L795 5L595 295L5 295L205 5Z');
+            path.delete();
+            done();
+        }));
+    });
+});
diff --git a/modules/canvaskit/tests/util.js b/modules/canvaskit/tests/util.js
new file mode 100644
index 0000000..5668a68
--- /dev/null
+++ b/modules/canvaskit/tests/util.js
@@ -0,0 +1,19 @@
+// The size of the golden images (DMs)
+const CANVAS_WIDTH = 600;
+const CANVAS_HEIGHT = 600;
+
+function reportSurface(surface, testname, done) {
+    // In docker, the webgl canvas is blank, but the surface has the pixel
+    // data. So, we copy it out and draw it to a normal canvas to take a picture.
+    // To be consistent across CPU and GPU, we just do it for all configurations
+    // (even though the CPU canvas shows up after flush just fine).
+    let pixels = surface.getCanvas().readPixels(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
+    pixels = new Uint8ClampedArray(pixels.buffer);
+    const imageData = new ImageData(pixels, CANVAS_WIDTH, CANVAS_HEIGHT);
+
+    let reportingCanvas =  document.getElementById('report');
+    reportingCanvas.getContext('2d').putImageData(imageData, 0, 0);
+    reportCanvas(reportingCanvas, testname).then(() => {
+        done();
+    }).catch(reportError(done));
+}
\ No newline at end of file
diff --git a/modules/particles/BUILD.gn b/modules/particles/BUILD.gn
new file mode 100644
index 0000000..3a3cfa1
--- /dev/null
+++ b/modules/particles/BUILD.gn
@@ -0,0 +1,30 @@
+# Copyright 2019 Google LLC
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+declare_args() {
+  skia_enable_particles = !(is_win && is_component_build)
+}
+
+import("../../gn/skia.gni")
+
+config("public_config") {
+  if (skia_enable_particles) {
+    include_dirs = [ "include" ]
+  }
+}
+
+static_library("particles") {
+  if (skia_enable_particles) {
+    import("particles.gni")
+    public_configs = [ ":public_config" ]
+    include_dirs = [ "../../tools/timer" ]
+    deps = [
+      "../..:skia",
+    ]
+    sources = skia_particle_sources
+    sources += [ "../../src/utils/SkTextUtils.cpp" ]
+    configs += [ "../../:skia_private" ]
+  }
+}
diff --git a/modules/particles/include/SkCurve.h b/modules/particles/include/SkCurve.h
new file mode 100644
index 0000000..b77bb2e
--- /dev/null
+++ b/modules/particles/include/SkCurve.h
@@ -0,0 +1,129 @@
+/*
+* Copyright 2019 Google LLC
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#ifndef SkCurve_DEFINED
+#define SkCurve_DEFINED
+
+#include "SkColor.h"
+#include "SkParticleData.h"
+#include "SkScalar.h"
+#include "SkTArray.h"
+
+class SkFieldVisitor;
+
+/**
+ * SkCurve implements a keyframed 1D function, useful for animating values over time. This pattern
+ * is common in digital content creation tools. An SkCurve might represent rotation, scale, opacity,
+ * or any other scalar quantity.
+ *
+ * An SkCurve has a logical domain of [0, 1], and is made of one or more SkCurveSegments.
+ * Each segment describes the behavior of the curve in some sub-domain. For an SkCurve with N
+ * segments, there are (N - 1) intermediate x-values that subdivide the domain. The first and last
+ * x-values are implicitly 0 and 1:
+ *
+ * 0    ...    x[0]    ...    x[1]   ...      ...    1
+ *   Segment_0      Segment_1     ...     Segment_N-1
+ *
+ * Each segment describes a function over [0, 1] - x-values are re-normalized to the segment's
+ * domain when being evaluated. The segments are cubic polynomials, defined by four values (fMin).
+ * These are the values at x=0 and x=1, as well as control points at x=1/3 and x=2/3.
+ *
+ * For segments with fConstant == true, only the first value is used (fMin[0]).
+ *
+ * Each segment has two additional features for creating interesting (and varied) animation:
+ *   - A segment can be ranged. Ranged segments have two sets of coefficients, and a random value
+ *     taken from the particle's SkRandom is used to lerp betwen them. Typically, the SkRandom is
+ *     in the same state at each call, so this value is stable. That causes a ranged SkCurve to
+ *     produce a single smooth cubic function somewhere within the range defined by fMin and fMax.
+ *   - A segment can be bidirectional. In that case, after a value is computed, it will be negated
+ *     50% of the time.
+ */
+
+enum SkCurveSegmentType {
+    kConstant_SegmentType,
+    kLinear_SegmentType,
+    kCubic_SegmentType,
+};
+
+struct SkCurveSegment {
+    SkScalar eval(SkScalar x, SkScalar t, bool negate) const;
+    void visitFields(SkFieldVisitor* v);
+
+    void setConstant(SkScalar c) {
+        fType   = kConstant_SegmentType;
+        fRanged = false;
+        fMin[0] = c;
+    }
+
+    SkScalar fMin[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
+    SkScalar fMax[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
+
+    int  fType          = kConstant_SegmentType;
+    bool fRanged        = false;
+    bool fBidirectional = false;
+};
+
+struct SkCurve {
+    SkCurve(SkScalar c = 0.0f) {
+        fSegments.push_back().setConstant(c);
+    }
+
+    SkScalar eval(const SkParticleUpdateParams& params, SkParticleState& ps) const;
+    void visitFields(SkFieldVisitor* v);
+
+    // Parameters that determine our x-value during evaluation
+    SkParticleValue                fInput;
+
+    // It should always be true that (fXValues.count() + 1) == fSegments.count()
+    SkTArray<SkScalar, true>       fXValues;
+    SkTArray<SkCurveSegment, true> fSegments;
+};
+
+/**
+ * SkColorCurve is similar to SkCurve, but keyframes 4D values - specifically colors. Because
+ * negative colors rarely make sense, SkColorCurves do not support bidirectional segments, but
+ * support all other features (including cubic interpolation).
+ */
+
+struct SkColorCurveSegment {
+    SkColorCurveSegment() {
+        for (int i = 0; i < 4; ++i) {
+            fMin[i] = { 1.0f, 1.0f, 1.0f, 1.0f };
+            fMax[i] = { 1.0f, 1.0f, 1.0f, 1.0f };
+        }
+    }
+
+    SkColor4f eval(SkScalar x, SkScalar t) const;
+    void visitFields(SkFieldVisitor* v);
+
+    void setConstant(SkColor4f c) {
+        fType   = kConstant_SegmentType;
+        fRanged = false;
+        fMin[0] = c;
+    }
+
+    SkColor4f fMin[4];
+    SkColor4f fMax[4];
+
+    int  fType   = kConstant_SegmentType;
+    bool fRanged = false;
+};
+
+struct SkColorCurve {
+    SkColorCurve(SkColor4f c = { 1.0f, 1.0f, 1.0f, 1.0f }) {
+        fSegments.push_back().setConstant(c);
+    }
+
+    SkColor4f eval(const SkParticleUpdateParams& params, SkParticleState& ps) const;
+    void visitFields(SkFieldVisitor* v);
+
+    SkParticleValue                     fInput;
+    SkTArray<SkScalar, true>            fXValues;
+    SkTArray<SkColorCurveSegment, true> fSegments;
+};
+
+#endif // SkCurve_DEFINED
diff --git a/modules/particles/include/SkParticleAffector.h b/modules/particles/include/SkParticleAffector.h
new file mode 100644
index 0000000..634d555
--- /dev/null
+++ b/modules/particles/include/SkParticleAffector.h
@@ -0,0 +1,54 @@
+/*
+* Copyright 2019 Google LLC
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#ifndef SkParticleAffector_DEFINED
+#define SkParticleAffector_DEFINED
+
+#include "SkReflected.h"
+
+#include "SkParticleData.h"
+#include "SkPoint.h"
+
+struct SkColorCurve;
+struct SkCurve;
+
+class SkParticleAffector : public SkReflected {
+public:
+    REFLECTED_ABSTRACT(SkParticleAffector, SkReflected)
+
+    void apply(const SkParticleUpdateParams& params, SkParticleState ps[], int count);
+    void visitFields(SkFieldVisitor* v) override;
+
+    static void RegisterAffectorTypes();
+
+    // Affectors that can set the linear or angular velocity. Both have a 'force' option to apply
+    // the resulting value as a force, rather than directly setting the velocity.
+    static sk_sp<SkParticleAffector> MakeLinearVelocity(const SkCurve& angle,
+                                                        const SkCurve& strength,
+                                                        bool force,
+                                                        SkParticleFrame frame);
+    static sk_sp<SkParticleAffector> MakeAngularVelocity(const SkCurve& strength,
+                                                         bool force);
+
+    // Set the orientation of a particle, relative to the world, local, or velocity frame.
+    static sk_sp<SkParticleAffector> MakeOrientation(const SkCurve& angle,
+                                                     SkParticleFrame frame);
+
+    static sk_sp<SkParticleAffector> MakePointForce(SkPoint point, SkScalar constant,
+                                                    SkScalar invSquare);
+
+    static sk_sp<SkParticleAffector> MakeSize(const SkCurve& curve);
+    static sk_sp<SkParticleAffector> MakeFrame(const SkCurve& curve);
+    static sk_sp<SkParticleAffector> MakeColor(const SkColorCurve& curve);
+
+private:
+    virtual void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) = 0;
+
+    bool fEnabled = true;
+};
+
+#endif // SkParticleAffector_DEFINED
diff --git a/modules/particles/include/SkParticleData.h b/modules/particles/include/SkParticleData.h
new file mode 100644
index 0000000..906f811
--- /dev/null
+++ b/modules/particles/include/SkParticleData.h
@@ -0,0 +1,139 @@
+/*
+* Copyright 2019 Google LLC
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#ifndef SkParticleData_DEFINED
+#define SkParticleData_DEFINED
+
+#include "SkColor.h"
+#include "SkPoint.h"
+#include "SkRandom.h"
+#include "SkReflected.h"
+#include "SkRSXform.h"
+
+/*
+ *  Various structs used to communicate particle information among emitters, affectors, etc.
+ */
+
+enum SkParticleFrame {
+    kWorld_ParticleFrame,     // "Up" is { 0, -1 }
+    kLocal_ParticleFrame,     // "Up" is particle's heading
+    kVelocity_ParticleFrame,  // "Up" is particle's direction of travel
+};
+
+static constexpr SkFieldVisitor::EnumStringMapping gParticleFrameMapping[] = {
+    { kWorld_ParticleFrame,    "World" },
+    { kLocal_ParticleFrame,    "Local" },
+    { kVelocity_ParticleFrame, "Velocity" },
+};
+
+struct SkParticlePose {
+    SkPoint  fPosition;
+    SkVector fHeading;
+    SkScalar fScale;
+
+    SkRSXform asRSXform(SkPoint ofs) const {
+        const float s =  fHeading.fX * fScale;
+        const float c = -fHeading.fY * fScale;
+        return SkRSXform::Make(c, s,
+                               fPosition.fX + -c * ofs.fX +  s * ofs.fY,
+                               fPosition.fY + -s * ofs.fX + -c * ofs.fY);
+    }
+};
+
+struct SkParticleVelocity {
+    SkVector fLinear;
+    SkScalar fAngular;
+};
+
+struct SkParticleState {
+    float              fAge;          // Normalized age [0, 1]
+    float              fInvLifetime;  // 1 / Lifetime
+    SkParticlePose     fPose;
+    SkParticleVelocity fVelocity;
+    SkColor4f          fColor;
+    SkScalar           fFrame;        // Parameter to drawable for animated sprites, etc.
+    SkRandom           fRandom;
+
+    SkVector getFrameHeading(SkParticleFrame frame) const {
+        switch (frame) {
+            case kLocal_ParticleFrame:
+                return fPose.fHeading;
+            case kVelocity_ParticleFrame: {
+                SkVector heading = fVelocity.fLinear;
+                if (!heading.normalize()) {
+                    heading.set(0, -1);
+                }
+                return heading;
+            }
+            case kWorld_ParticleFrame:
+            default:
+                return SkVector{ 0, -1 };
+        }
+    }
+};
+
+struct SkParticleUpdateParams {
+    float fDeltaTime;
+    float fEffectAge;
+    int   fAgeSource;
+};
+
+/**
+ * SkParticleValue selects a specific value to be used when evaluating a curve, position on a path,
+ * or any other affector that needs a scalar float input. An SkParticleValue starts with a source
+ * value taken from the state of the effect or particle. That can be adjusted using a scale and
+ * bias, and then reduced into the desired range (typically [0, 1]) via a chosen tile mode.
+ */
+struct SkParticleValue {
+    enum Source {
+        // Either the particle or effect age, depending on spawn or update
+        kAge_Source,
+
+        kRandom_Source,
+        kParticleAge_Source,
+        kEffectAge_Source,
+        kPositionX_Source,
+        kPositionY_Source,
+        kHeadingX_Source,
+        kHeadingY_Source,
+        kScale_Source,
+        kVelocityX_Source,
+        kVelocityY_Source,
+        kRotation_Source,
+        kColorR_Source,
+        kColorG_Source,
+        kColorB_Source,
+        kColorA_Source,
+        kSpriteFrame_Source,
+    };
+
+    enum TileMode {
+        kClamp_TileMode,
+        kRepeat_TileMode,
+        kMirror_TileMode,
+    };
+
+    void visitFields(SkFieldVisitor* v);
+    float eval(const SkParticleUpdateParams& params, SkParticleState& ps) const;
+
+    int   fSource   = kAge_Source;
+    int   fFrame    = kWorld_ParticleFrame;
+    int   fTileMode = kRepeat_TileMode;
+
+    // We map fLeft -> 0 and fRight -> 1. This is easier to work with and reason about.
+    float fLeft     = 0.0f;
+    float fRight    = 1.0f;
+
+    // Cached from the above
+    float fScale    = 1.0f;
+    float fBias     = 0.0f;
+
+private:
+    float getSourceValue(const SkParticleUpdateParams& params, SkParticleState& ps) const;
+};
+
+#endif // SkParticleData_DEFINED
diff --git a/modules/particles/include/SkParticleDrawable.h b/modules/particles/include/SkParticleDrawable.h
new file mode 100644
index 0000000..a47b24d
--- /dev/null
+++ b/modules/particles/include/SkParticleDrawable.h
@@ -0,0 +1,31 @@
+/*
+* Copyright 2019 Google LLC
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#ifndef SkParticleDrawable_DEFINED
+#define SkParticleDrawable_DEFINED
+
+#include "SkReflected.h"
+
+class SkCanvas;
+struct SkParticleState;
+class SkPaint;
+class SkString;
+
+class SkParticleDrawable : public SkReflected {
+public:
+    REFLECTED_ABSTRACT(SkParticleDrawable, SkReflected)
+
+    virtual void draw(SkCanvas* canvas, const SkParticleState particles[], int count,
+                      const SkPaint* paint) = 0;
+
+    static void RegisterDrawableTypes();
+
+    static sk_sp<SkParticleDrawable> MakeCircle(int radius);
+    static sk_sp<SkParticleDrawable> MakeImage(const SkString& path, int cols, int rows);
+};
+
+#endif // SkParticleEffect_DEFINED
diff --git a/modules/particles/include/SkParticleEffect.h b/modules/particles/include/SkParticleEffect.h
new file mode 100644
index 0000000..1af9c68
--- /dev/null
+++ b/modules/particles/include/SkParticleEffect.h
@@ -0,0 +1,74 @@
+/*
+* Copyright 2019 Google LLC
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#ifndef SkParticleEffect_DEFINED
+#define SkParticleEffect_DEFINED
+
+#include "SkAutoMalloc.h"
+#include "SkCurve.h"
+#include "SkRandom.h"
+#include "SkRefCnt.h"
+#include "SkTArray.h"
+
+class SkCanvas;
+class SkFieldVisitor;
+class SkParticleAffector;
+class SkParticleDrawable;
+struct SkParticleState;
+
+class SkParticleEffectParams : public SkRefCnt {
+public:
+    int       fMaxCount = 128;
+    float     fEffectDuration = 1.0f;
+    float     fRate = 8.0f;
+    SkCurve   fLifetime = 1.0f;
+
+    // Drawable (image, sprite sheet, etc.)
+    sk_sp<SkParticleDrawable> fDrawable;
+
+    // Rules that configure particles at spawn time
+    SkTArray<sk_sp<SkParticleAffector>> fSpawnAffectors;
+
+    // Rules that update existing particles over their lifetime
+    SkTArray<sk_sp<SkParticleAffector>> fUpdateAffectors;
+
+    void visitFields(SkFieldVisitor* v);
+};
+
+class SkParticleEffect : public SkRefCnt {
+public:
+    SkParticleEffect(sk_sp<SkParticleEffectParams> params, const SkRandom& random);
+
+    void start(double now, bool looping = false);
+    void update(double now);
+    void draw(SkCanvas* canvas);
+
+    bool isAlive() const { return fSpawnTime >= 0; }
+    int getCount() const { return fCount; }
+
+private:
+    void setCapacity(int capacity);
+
+    sk_sp<SkParticleEffectParams> fParams;
+
+    SkRandom fRandom;
+
+    bool   fLooping;
+    double fSpawnTime;
+
+    int    fCount;
+    double fLastTime;
+    float  fSpawnRemainder;
+
+    SkAutoTMalloc<SkParticleState> fParticles;
+    SkAutoTMalloc<SkRandom>        fStableRandoms;
+
+    // Cached
+    int fCapacity;
+};
+
+#endif // SkParticleEffect_DEFINED
diff --git a/modules/particles/include/SkParticleSerialization.h b/modules/particles/include/SkParticleSerialization.h
new file mode 100644
index 0000000..dbcfe66
--- /dev/null
+++ b/modules/particles/include/SkParticleSerialization.h
@@ -0,0 +1,203 @@
+/*
+* Copyright 2019 Google LLC
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#ifndef SkParticleSerialization_DEFINED
+#define SkParticleSerialization_DEFINED
+
+#include "SkReflected.h"
+
+#include "SkJSON.h"
+#include "SkJSONWriter.h"
+#include "SkTArray.h"
+
+class SkToJsonVisitor : public SkFieldVisitor {
+public:
+    SkToJsonVisitor(SkJSONWriter& writer) : fWriter(writer) {}
+
+    // Primitives
+    void visit(const char* name, float& f) override {
+        fWriter.appendFloat(name, f);
+    }
+    void visit(const char* name, int& i) override {
+        fWriter.appendS32(name, i);
+    }
+    void visit(const char* name, bool& b) override {
+        fWriter.appendBool(name, b);
+    }
+    void visit(const char* name, SkString& s) override {
+        fWriter.appendString(name, s.c_str());
+    }
+    void visit(const char* name, int& i, const EnumStringMapping* map, int count) override {
+        fWriter.appendString(name, EnumToString(i, map, count));
+    }
+
+    // Compound types
+    void visit(const char* name, SkPoint& p) override {
+        fWriter.beginObject(name, false);
+        fWriter.appendFloat("x", p.fX);
+        fWriter.appendFloat("y", p.fY);
+        fWriter.endObject();
+    }
+
+    void visit(const char* name, SkColor4f& c) override {
+        fWriter.beginArray(name, false);
+        fWriter.appendFloat(c.fR);
+        fWriter.appendFloat(c.fG);
+        fWriter.appendFloat(c.fB);
+        fWriter.appendFloat(c.fA);
+        fWriter.endArray();
+    }
+
+    void visit(sk_sp<SkReflected>& e, const SkReflected::Type* baseType) override {
+        fWriter.appendString("Type", e ? e->getType()->fName : "Null");
+    }
+
+    void enterObject(const char* name) override { fWriter.beginObject(name); }
+    void exitObject()                  override { fWriter.endObject(); }
+
+    int enterArray(const char* name, int oldCount) override {
+        fWriter.beginArray(name);
+        return oldCount;
+    }
+    ArrayEdit exitArray() override {
+        fWriter.endArray();
+        return ArrayEdit();
+    }
+
+private:
+    SkJSONWriter& fWriter;
+};
+
+class SkFromJsonVisitor : public SkFieldVisitor {
+public:
+    SkFromJsonVisitor(const skjson::Value& v) : fRoot(v) {
+        fStack.push_back(&fRoot);
+    }
+
+    void visit(const char* name, float& f) override {
+        TryParse(get(name), f);
+    }
+    void visit(const char* name, int& i) override {
+        TryParse(get(name), i);
+    }
+    void visit(const char* name, bool& b) override {
+        TryParse(get(name), b);
+    }
+    void visit(const char* name, SkString& s) override {
+        TryParse(get(name), s);
+    }
+    void visit(const char* name, int& i, const EnumStringMapping* map, int count) override {
+        SkString str;
+        if (TryParse(get(name), str)) {
+            i = StringToEnum(str.c_str(), map, count);
+        }
+    }
+
+    void visit(const char* name, SkPoint& p) override {
+        if (const skjson::ObjectValue* obj = get(name)) {
+            TryParse((*obj)["x"], p.fX);
+            TryParse((*obj)["y"], p.fY);
+        }
+    }
+
+    void visit(const char* name, SkColor4f& c) override {
+        const skjson::ArrayValue* arr = get(name);
+        if (arr && arr->size() == 4) {
+            TryParse((*arr)[0], c.fR);
+            TryParse((*arr)[1], c.fG);
+            TryParse((*arr)[2], c.fB);
+            TryParse((*arr)[3], c.fA);
+        }
+    }
+
+    void visit(sk_sp<SkReflected>& e, const SkReflected::Type* baseType) override {
+        const skjson::StringValue* typeString = get("Type");
+        const char* type = typeString ? typeString->begin() : "Null";
+        e = SkReflected::CreateInstance(type);
+    }
+
+    void enterObject(const char* name) override {
+        fStack.push_back((const skjson::ObjectValue*)get(name));
+    }
+    void exitObject() override {
+        fStack.pop_back();
+    }
+
+    int enterArray(const char* name, int oldCount) override {
+        const skjson::ArrayValue* arrVal = get(name);
+        fStack.push_back(arrVal);
+        fArrayIndexStack.push_back(0);
+        return arrVal ? arrVal->size() : 0;
+    }
+    ArrayEdit exitArray() override {
+        fStack.pop_back();
+        fArrayIndexStack.pop_back();
+        return ArrayEdit();
+    }
+
+private:
+    const skjson::Value& get(const char* name) {
+        if (const skjson::Value* cur = fStack.back()) {
+            if (cur->is<skjson::ArrayValue>()) {
+                SkASSERT(!name);
+                return cur->as<skjson::ArrayValue>()[fArrayIndexStack.back()++];
+            } else if (!name) {
+                return *cur;
+            } else if (cur->is<skjson::ObjectValue>()) {
+                return cur->as<skjson::ObjectValue>()[name];
+            }
+        }
+        static skjson::NullValue gNull;
+        return gNull;
+    }
+
+    static bool TryParse(const skjson::Value& v, float& f) {
+        if (const skjson::NumberValue* num = v) {
+            f = static_cast<float>(**num);
+            return true;
+        }
+        return false;
+    }
+
+    static bool TryParse(const skjson::Value& v, int& i) {
+        if (const skjson::NumberValue* num = v) {
+            double dbl = **num;
+            i = static_cast<int>(dbl);
+            return static_cast<double>(i) == dbl;
+        }
+        return false;
+    }
+
+    static bool TryParse(const skjson::Value& v, SkString& s) {
+        if (const skjson::StringValue* str = v) {
+            s.set(str->begin(), str->size());
+            return true;
+        }
+        return false;
+    }
+
+    static bool TryParse(const skjson::Value& v, bool& b) {
+        switch (v.getType()) {
+        case skjson::Value::Type::kNumber:
+            b = SkToBool(*v.as<skjson::NumberValue>());
+            return true;
+        case skjson::Value::Type::kBool:
+            b = *v.as<skjson::BoolValue>();
+            return true;
+        default:
+            break;
+        }
+
+        return false;
+    }
+
+    const skjson::Value& fRoot;
+    SkSTArray<16, const skjson::Value*, true> fStack;
+    SkSTArray<16, size_t, true>               fArrayIndexStack;
+};
+
+#endif // SkParticleSerialization_DEFINED
diff --git a/modules/particles/include/SkReflected.h b/modules/particles/include/SkReflected.h
new file mode 100644
index 0000000..1a0d43b
--- /dev/null
+++ b/modules/particles/include/SkReflected.h
@@ -0,0 +1,272 @@
+/*
+* Copyright 2019 Google LLC
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#ifndef SkReflected_DEFINED
+#define SkReflected_DEFINED
+
+#include "SkColor.h"
+#include "SkRefCnt.h"
+#include "SkTArray.h"
+
+#include <string.h>
+
+struct SkCurve;
+class SkFieldVisitor;
+struct SkPoint;
+class SkString;
+
+/**
+ * Classes and macros for a lightweight reflection system.
+ *
+ * Classes that derive from SkReflected have several features:
+ *   - Access to an SkReflected::Type instance, via static GetType() or virtual getType()
+ *     The Type instance can be used to create additional instances (fFactory), get the name
+ *     of the type, and answer queries of the form "is X derived from Y".
+ *   - Given a string containing a type name, SkReflected can create an instance of that type.
+ *   - SkReflected::VisitTypes can be used to enumerate all Types.
+ *
+ * Together, this simplifies the implementation of serialization and other dynamic type factories.
+ *
+ * Finally, all SkReflected-derived types must implement visitFields, which provides field-level
+ * reflection, in conjunction with SkFieldVisitor. See SkFieldVisitor, below.
+ *
+ * To create a new reflected class:
+ *   - Derive the class (directly or indirectly) from SkReflected.
+ *   - Ensure that the class can be default constructed.
+ *   - In the public area of the class declaration, add REFLECTED(<ClassName>, <BaseClassName>).
+ *     If the class is abstract, use REFLECTED_ABSTRACT(<ClassName>, <BaseClassName>) instead.
+ *   - Add a one-time call to REGISTER_REFLECTED(<ClassName>) at initialization time.
+ *   - Implement visitFields(), as described below.
+ */
+class SkReflected : public SkRefCnt {
+public:
+    typedef sk_sp<SkReflected>(*Factory)();
+    struct Type {
+        const char* fName;
+        const Type* fBase;
+        Factory     fFactory;
+        bool        fRegistered = false;
+
+        bool isDerivedFrom(const Type* t) const {
+            const Type* base = fBase;
+            while (base) {
+                if (base == t) {
+                    return true;
+                }
+                base = base->fBase;
+            }
+            return false;
+        }
+    };
+
+    virtual const Type* getType() const = 0;
+    static const Type* GetType() {
+        static Type gType{ "SkReflected", nullptr, nullptr };
+        RegisterOnce(&gType);
+        return &gType;
+    }
+
+    bool isOfType(const Type* t) const {
+        const Type* thisType = this->getType();
+        return thisType == t || thisType->isDerivedFrom(t);
+    }
+
+    static sk_sp<SkReflected> CreateInstance(const char* name) {
+        for (const Type* type : gTypes) {
+            if (0 == strcmp(name, type->fName)) {
+                return type->fFactory();
+            }
+        }
+        return nullptr;
+    }
+
+    virtual void visitFields(SkFieldVisitor*) = 0;
+
+    static void VisitTypes(std::function<void(const Type*)> visitor);
+
+protected:
+    static void RegisterOnce(Type* type) {
+        if (!type->fRegistered) {
+            gTypes.push_back(type);
+            type->fRegistered = true;
+        }
+    }
+
+private:
+    static SkSTArray<16, const Type*, true> gTypes;
+};
+
+#define REFLECTED(TYPE, BASE)                                    \
+    static sk_sp<SkReflected> CreateProc() {                     \
+        return sk_sp<SkReflected>(new TYPE());                   \
+    }                                                            \
+    static const Type* GetType() {                               \
+        static Type gType{ #TYPE, BASE::GetType(), CreateProc }; \
+        RegisterOnce(&gType);                                    \
+        return &gType;                                           \
+    }                                                            \
+    const Type* getType() const override { return GetType(); }
+
+#define REFLECTED_ABSTRACT(TYPE, BASE)                          \
+    static const Type* GetType() {                              \
+        static Type gType{ #TYPE, BASE::GetType(), nullptr };   \
+        RegisterOnce(&gType);                                   \
+        return &gType;                                          \
+    }                                                           \
+    const Type* getType() const override { return GetType(); }
+
+#define REGISTER_REFLECTED(TYPE) TYPE::GetType()
+
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * SkFieldVisitor is an interface that can be implemented by any class to visit all fields of
+ * SkReflected types, and of types that implement the visitFields() function.
+ *
+ * Classes implementing the interface must supply implementations of virtual functions that visit
+ * basic types (float, int, bool, SkString, etc...), as well as helper methods for entering the
+ * scope of an object or array.
+ *
+ * All visit functions supply a field name, and a non-constant reference to an actual field.
+ * This allows visitors to serialize or deserialize collections of objects, or perform edits on
+ * existing objects.
+ *
+ * Classes that implement visitFields (typically derived from SkReflected) should simply call
+ * visit() for each of their fields, passing a (unique) field name, and the actual field. If your
+ * class has derived fields, it's best to only visit() the fields that you would serialize, then
+ * enforce any constraints afterwards.
+ *
+ * See SkParticleSerialization.h for example visitors that perform serialization to and from JSON.
+ */
+class SkFieldVisitor {
+public:
+    virtual ~SkFieldVisitor() {}
+
+    // Visit functions for primitive types, to be implemented by derived visitors.
+    virtual void visit(const char*, float&) = 0;
+    virtual void visit(const char*, int&) = 0;
+    virtual void visit(const char*, bool&) = 0;
+    virtual void visit(const char*, SkString&) = 0;
+
+    virtual void visit(const char*, SkPoint&) = 0;
+    virtual void visit(const char*, SkColor4f&) = 0;
+
+    // Accommodation for enums, where caller can supply a value <-> string map
+    struct EnumStringMapping {
+        int         fValue;
+        const char* fName;
+    };
+    virtual void visit(const char*, int&, const EnumStringMapping*, int count) = 0;
+
+    // Specific virtual signature for SkCurve, to allow for heavily customized UI in SkGuiVisitor.
+    virtual void visit(const char* name, SkCurve& c);
+
+    // Default visit function for structs with no special behavior. It is assumed that any such
+    // struct implements visitFields(SkFieldVisitor*) to recursively visit each of its fields.
+    template <typename T>
+    void visit(const char* name, T& value) {
+        this->enterObject(name);
+        value.visitFields(this);
+        this->exitObject();
+    }
+
+    // Specialization for SkTArrays. In conjunction with the enterArray/exitArray virtuals, this
+    // allows visitors to resize an array (for deserialization), and apply a single edit operation
+    // (remove or move a single element). Each element of the array is visited as normal.
+    template <typename T, bool MEM_MOVE>
+    void visit(const char* name, SkTArray<T, MEM_MOVE>& arr) {
+        arr.resize_back(this->enterArray(name, arr.count()));
+        for (int i = 0; i < arr.count(); ++i) {
+            this->visit(nullptr, arr[i]);
+        }
+        this->exitArray().apply(arr);
+    }
+
+    // Specialization for sk_sp pointers to types derived from SkReflected. Those types are known
+    // to implement visitFields. This allows the visitor to modify the contents of the object, or
+    // even replace it with an entirely new object. The virtual function uses SkReflected as a
+    // common type, but uses SkReflected::Type to communicate the required base-class. In this way,
+    // the new object can be verified to match the type of the original (templated) pointer.
+    template <typename T>
+    void visit(const char* name, sk_sp<T>& obj) {
+        this->enterObject(name);
+
+        sk_sp<SkReflected> newObj = obj;
+        this->visit(newObj, T::GetType());
+        if (newObj != obj) {
+            if (!newObj || newObj->isOfType(T::GetType())) {
+                obj.reset(static_cast<T*>(newObj.release()));
+            } else {
+                obj.reset();
+            }
+        }
+
+        if (obj) {
+            obj->visitFields(this);
+        }
+        this->exitObject();
+    }
+
+protected:
+    // Helper struct to allow exitArray to specify a single operation performed on the array.
+    struct ArrayEdit {
+        enum class Verb {
+            kNone,
+            kRemove,
+            kMoveForward,
+        };
+
+        Verb fVerb = Verb::kNone;
+        int fIndex = 0;
+
+        template <typename T, bool MEM_MOVE>
+        void apply(SkTArray<T, MEM_MOVE>& arr) const {
+            switch (fVerb) {
+            case Verb::kNone:
+                break;
+            case Verb::kRemove:
+                for (int i = fIndex; i < arr.count() - 1; ++i) {
+                    arr[i] = arr[i + 1];
+                }
+                arr.pop_back();
+                break;
+            case Verb::kMoveForward:
+                if (fIndex > 0 && fIndex < arr.count()) {
+                    std::swap(arr[fIndex - 1], arr[fIndex]);
+                }
+                break;
+            }
+        }
+    };
+
+    static const char* EnumToString(int value, const EnumStringMapping* map, int count) {
+        for (int i = 0; i < count; ++i) {
+            if (map[i].fValue == value) {
+                return map[i].fName;
+            }
+        }
+        return nullptr;
+    }
+    static int StringToEnum(const char* str, const EnumStringMapping* map, int count) {
+        for (int i = 0; i < count; ++i) {
+            if (0 == strcmp(str, map[i].fName)) {
+                return map[i].fValue;
+            }
+        }
+        return -1;
+    }
+
+    virtual void enterObject(const char* name) = 0;
+    virtual void exitObject() = 0;
+
+    virtual int enterArray(const char* name, int oldCount) = 0;
+    virtual ArrayEdit exitArray() = 0;
+
+    virtual void visit(sk_sp<SkReflected>&, const SkReflected::Type* baseType) = 0;
+};
+
+#endif // SkReflected_DEFINED
diff --git a/modules/particles/particles.gni b/modules/particles/particles.gni
new file mode 100644
index 0000000..44ed52b
--- /dev/null
+++ b/modules/particles/particles.gni
@@ -0,0 +1,15 @@
+# Copyright 2019 Google LLC
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Things are easiest for everyone if these source paths are absolute.
+_src = get_path_info("src", "abspath")
+
+skia_particle_sources = [
+  "$_src/SkCurve.cpp",
+  "$_src/SkParticleAffector.cpp",
+  "$_src/SkParticleDrawable.cpp",
+  "$_src/SkParticleEffect.cpp",
+  "$_src/SkReflected.cpp",
+]
diff --git a/modules/particles/src/SkCurve.cpp b/modules/particles/src/SkCurve.cpp
new file mode 100644
index 0000000..5dcfb60d
--- /dev/null
+++ b/modules/particles/src/SkCurve.cpp
@@ -0,0 +1,190 @@
+/*
+* Copyright 2019 Google LLC
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include "SkCurve.h"
+
+#include "SkParticleData.h"
+#include "SkRandom.h"
+#include "SkReflected.h"
+
+constexpr SkFieldVisitor::EnumStringMapping gCurveSegmentTypeMapping[] = {
+    { kConstant_SegmentType, "Constant" },
+    { kLinear_SegmentType,   "Linear" },
+    { kCubic_SegmentType,    "Cubic" },
+};
+
+static SkColor4f operator+(SkColor4f c1, SkColor4f c2) {
+    return { c1.fR + c2.fR, c1.fG + c2.fG, c1.fB + c2.fB, c1.fA + c2.fA };
+}
+
+static SkColor4f operator-(SkColor4f c1, SkColor4f c2) {
+    return { c1.fR - c2.fR, c1.fG - c2.fG, c1.fB - c2.fB, c1.fA - c2.fA };
+}
+
+template <typename T>
+static T eval_cubic(const T* pts, SkScalar x) {
+    SkScalar ix = (1 - x);
+    return pts[0]*(ix*ix*ix) + pts[1]*(3*ix*ix*x) + pts[2]*(3*ix*x*x) + pts[3]*(x*x*x);
+}
+
+template <typename T>
+static T eval_segment(const T* pts, SkScalar x, int type) {
+    switch (type) {
+        case kLinear_SegmentType:
+            return pts[0] + (pts[3] - pts[0]) * x;
+        case kCubic_SegmentType:
+            return eval_cubic(pts, x);
+        case kConstant_SegmentType:
+        default:
+            return pts[0];
+    }
+}
+
+SkScalar SkCurveSegment::eval(SkScalar x, SkScalar t, bool negate) const {
+    SkScalar result = eval_segment(fMin, x, fType);
+    if (fRanged) {
+        result += (eval_segment(fMax, x, fType) - result) * t;
+    }
+    if (fBidirectional && negate) {
+        result = -result;
+    }
+    return result;
+}
+
+void SkCurveSegment::visitFields(SkFieldVisitor* v) {
+    v->visit("Type", fType, gCurveSegmentTypeMapping, SK_ARRAY_COUNT(gCurveSegmentTypeMapping));
+    v->visit("Ranged", fRanged);
+    v->visit("Bidirectional", fBidirectional);
+    v->visit("A0", fMin[0]);
+    if (fType == kCubic_SegmentType) {
+        v->visit("B0", fMin[1]);
+        v->visit("C0", fMin[2]);
+    }
+    if (fType != kConstant_SegmentType) {
+        v->visit("D0", fMin[3]);
+    }
+    if (fRanged) {
+        v->visit("A1", fMax[0]);
+        if (fType == kCubic_SegmentType) {
+            v->visit("B1", fMax[1]);
+            v->visit("C1", fMax[2]);
+        }
+        if (fType != kConstant_SegmentType) {
+            v->visit("D1", fMax[3]);
+        }
+    }
+}
+
+SkScalar SkCurve::eval(const SkParticleUpdateParams& params, SkParticleState& ps) const {
+    SkASSERT(fSegments.count() == fXValues.count() + 1);
+
+    float x = fInput.eval(params, ps);
+
+    int i = 0;
+    for (; i < fXValues.count(); ++i) {
+        if (x <= fXValues[i]) {
+            break;
+        }
+    }
+
+    SkScalar rangeMin = (i == 0) ? 0.0f : fXValues[i - 1];
+    SkScalar rangeMax = (i == fXValues.count()) ? 1.0f : fXValues[i];
+    SkScalar segmentX = (x - rangeMin) / (rangeMax - rangeMin);
+    if (!SkScalarIsFinite(segmentX)) {
+        segmentX = rangeMin;
+    }
+    SkASSERT(0.0f <= segmentX && segmentX <= 1.0f);
+
+    // Always pull t and negate here, so that the stable generator behaves consistently, even if
+    // our segments use an inconsistent feature-set.
+    SkScalar t = ps.fRandom.nextF();
+    bool negate = ps.fRandom.nextBool();
+    return fSegments[i].eval(segmentX, t, negate);
+}
+
+void SkCurve::visitFields(SkFieldVisitor* v) {
+    v->visit("Input", fInput);
+    v->visit("XValues", fXValues);
+    v->visit("Segments", fSegments);
+
+    // Validate and fixup
+    if (fSegments.empty()) {
+        fSegments.push_back().setConstant(0.0f);
+    }
+    fXValues.resize_back(fSegments.count() - 1);
+    for (int i = 0; i < fXValues.count(); ++i) {
+        fXValues[i] = SkTPin(fXValues[i], i > 0 ? fXValues[i - 1] : 0.0f, 1.0f);
+    }
+}
+
+SkColor4f SkColorCurveSegment::eval(SkScalar x, SkScalar t) const {
+    SkColor4f result = eval_segment(fMin, x, fType);
+    if (fRanged) {
+        result = result + (eval_segment(fMax, x, fType) - result) * t;
+    }
+    return result;
+}
+
+void SkColorCurveSegment::visitFields(SkFieldVisitor* v) {
+    v->visit("Type", fType, gCurveSegmentTypeMapping, SK_ARRAY_COUNT(gCurveSegmentTypeMapping));
+    v->visit("Ranged", fRanged);
+    v->visit("A0", fMin[0]);
+    if (fType == kCubic_SegmentType) {
+        v->visit("B0", fMin[1]);
+        v->visit("C0", fMin[2]);
+    }
+    if (fType != kConstant_SegmentType) {
+        v->visit("D0", fMin[3]);
+    }
+    if (fRanged) {
+        v->visit("A1", fMax[0]);
+        if (fType == kCubic_SegmentType) {
+            v->visit("B1", fMax[1]);
+            v->visit("C1", fMax[2]);
+        }
+        if (fType != kConstant_SegmentType) {
+            v->visit("D1", fMax[3]);
+        }
+    }
+}
+
+SkColor4f SkColorCurve::eval(const SkParticleUpdateParams& params, SkParticleState& ps) const {
+    SkASSERT(fSegments.count() == fXValues.count() + 1);
+
+    float x = fInput.eval(params, ps);
+
+    int i = 0;
+    for (; i < fXValues.count(); ++i) {
+        if (x <= fXValues[i]) {
+            break;
+        }
+    }
+
+    SkScalar rangeMin = (i == 0) ? 0.0f : fXValues[i - 1];
+    SkScalar rangeMax = (i == fXValues.count()) ? 1.0f : fXValues[i];
+    SkScalar segmentX = (x - rangeMin) / (rangeMax - rangeMin);
+    if (!SkScalarIsFinite(segmentX)) {
+        segmentX = rangeMin;
+    }
+    SkASSERT(0.0f <= segmentX && segmentX <= 1.0f);
+    return fSegments[i].eval(segmentX, ps.fRandom.nextF());
+}
+
+void SkColorCurve::visitFields(SkFieldVisitor* v) {
+    v->visit("Input", fInput);
+    v->visit("XValues", fXValues);
+    v->visit("Segments", fSegments);
+
+    // Validate and fixup
+    if (fSegments.empty()) {
+        fSegments.push_back().setConstant(SkColor4f{ 1.0f, 1.0f, 1.0f, 1.0f });
+    }
+    fXValues.resize_back(fSegments.count() - 1);
+    for (int i = 0; i < fXValues.count(); ++i) {
+        fXValues[i] = SkTPin(fXValues[i], i > 0 ? fXValues[i - 1] : 0.0f, 1.0f);
+    }
+}
diff --git a/modules/particles/src/SkParticleAffector.cpp b/modules/particles/src/SkParticleAffector.cpp
new file mode 100644
index 0000000..82ea43f
--- /dev/null
+++ b/modules/particles/src/SkParticleAffector.cpp
@@ -0,0 +1,482 @@
+/*
+* Copyright 2019 Google LLC
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include "SkParticleAffector.h"
+
+#include "SkContourMeasure.h"
+#include "SkCurve.h"
+#include "SkParsePath.h"
+#include "SkParticleData.h"
+#include "SkPath.h"
+#include "SkRandom.h"
+#include "SkTextUtils.h"
+
+
+void SkParticleAffector::apply(const SkParticleUpdateParams& params,
+                               SkParticleState ps[], int count) {
+    if (fEnabled) {
+        this->onApply(params, ps, count);
+    }
+}
+
+void SkParticleAffector::visitFields(SkFieldVisitor* v) {
+    v->visit("Enabled", fEnabled);
+}
+
+class SkLinearVelocityAffector : public SkParticleAffector {
+public:
+    SkLinearVelocityAffector(const SkCurve& angle = 0.0f,
+                             const SkCurve& strength = 0.0f,
+                             bool force = true,
+                             SkParticleFrame frame = kWorld_ParticleFrame)
+        : fAngle(angle)
+        , fStrength(strength)
+        , fForce(force)
+        , fFrame(frame) {}
+
+    REFLECTED(SkLinearVelocityAffector, SkParticleAffector)
+
+    void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
+        for (int i = 0; i < count; ++i) {
+            float angle = fAngle.eval(params, ps[i]);
+            SkScalar rad = SkDegreesToRadians(angle);
+            SkScalar s_local = SkScalarSin(rad),
+                     c_local = SkScalarCos(rad);
+            SkVector heading = ps[i].getFrameHeading(static_cast<SkParticleFrame>(fFrame));
+            SkScalar c = heading.fX * c_local - heading.fY * s_local;
+            SkScalar s = heading.fX * s_local + heading.fY * c_local;
+            float strength = fStrength.eval(params, ps[i]);
+            SkVector force = { c * strength, s * strength };
+            if (fForce) {
+                ps[i].fVelocity.fLinear += force * params.fDeltaTime;
+            } else {
+                ps[i].fVelocity.fLinear = force;
+            }
+        }
+    }
+
+    void visitFields(SkFieldVisitor* v) override {
+        SkParticleAffector::visitFields(v);
+        v->visit("Force", fForce);
+        v->visit("Frame", fFrame, gParticleFrameMapping, SK_ARRAY_COUNT(gParticleFrameMapping));
+        v->visit("Angle", fAngle);
+        v->visit("Strength", fStrength);
+    }
+
+private:
+    SkCurve fAngle;
+    SkCurve fStrength;
+    bool fForce;
+    int  fFrame;
+};
+
+class SkAngularVelocityAffector : public SkParticleAffector {
+public:
+    SkAngularVelocityAffector(const SkCurve& strength = 0.0f, bool force = true)
+        : fStrength(strength)
+        , fForce(force) {}
+
+    REFLECTED(SkAngularVelocityAffector, SkParticleAffector)
+
+    void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
+        for (int i = 0; i < count; ++i) {
+            float strength = fStrength.eval(params, ps[i]);
+            if (fForce) {
+                ps[i].fVelocity.fAngular += strength * params.fDeltaTime;
+            } else {
+                ps[i].fVelocity.fAngular = strength;
+            }
+        }
+    }
+
+    void visitFields(SkFieldVisitor* v) override {
+        SkParticleAffector::visitFields(v);
+        v->visit("Force", fForce);
+        v->visit("Strength", fStrength);
+    }
+
+private:
+    SkCurve fStrength;
+    bool    fForce;
+};
+
+class SkPointForceAffector : public SkParticleAffector {
+public:
+    SkPointForceAffector(SkPoint point = { 0.0f, 0.0f }, SkScalar constant = 0.0f,
+                         SkScalar invSquare = 0.0f)
+            : fPoint(point), fConstant(constant), fInvSquare(invSquare) {}
+
+    REFLECTED(SkPointForceAffector, SkParticleAffector)
+
+    void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
+        for (int i = 0; i < count; ++i) {
+            SkVector toPoint = fPoint - ps[i].fPose.fPosition;
+            SkScalar lenSquare = toPoint.dot(toPoint);
+            toPoint.normalize();
+            ps[i].fVelocity.fLinear +=
+                    toPoint * (fConstant + (fInvSquare / lenSquare)) * params.fDeltaTime;
+        }
+    }
+
+    void visitFields(SkFieldVisitor* v) override {
+        SkParticleAffector::visitFields(v);
+        v->visit("Point", fPoint);
+        v->visit("Constant", fConstant);
+        v->visit("InvSquare", fInvSquare);
+    }
+
+private:
+    SkPoint  fPoint;
+    SkScalar fConstant;
+    SkScalar fInvSquare;
+};
+
+class SkOrientationAffector : public SkParticleAffector {
+public:
+    SkOrientationAffector(const SkCurve& angle = 0.0f,
+                          SkParticleFrame frame = kLocal_ParticleFrame)
+        : fAngle(angle)
+        , fFrame(frame) {}
+
+    REFLECTED(SkOrientationAffector, SkParticleAffector)
+
+    void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
+        for (int i = 0; i < count; ++i) {
+            float angle = fAngle.eval(params, ps[i]);
+            SkScalar rad = SkDegreesToRadians(angle);
+            SkScalar s_local = SkScalarSin(rad),
+                     c_local = SkScalarCos(rad);
+            SkVector heading = ps[i].getFrameHeading(static_cast<SkParticleFrame>(fFrame));
+            ps[i].fPose.fHeading.set(heading.fX * c_local - heading.fY * s_local,
+                                     heading.fX * s_local + heading.fY * c_local);
+        }
+    }
+
+    void visitFields(SkFieldVisitor *v) override {
+        SkParticleAffector::visitFields(v);
+        v->visit("Frame", fFrame, gParticleFrameMapping, SK_ARRAY_COUNT(gParticleFrameMapping));
+        v->visit("Angle", fAngle);
+    }
+
+private:
+    SkCurve fAngle;
+    int     fFrame;
+};
+
+class SkPositionInCircleAffector : public SkParticleAffector {
+public:
+    SkPositionInCircleAffector(const SkCurve& x = 0.0f, const SkCurve& y = 0.0f,
+                               const SkCurve& radius = 0.0f, bool setHeading = true)
+        : fX(x)
+        , fY(y)
+        , fRadius(radius)
+        , fSetHeading(setHeading) {}
+
+    REFLECTED(SkPositionInCircleAffector, SkParticleAffector)
+
+    void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
+        for (int i = 0; i < count; ++i) {
+            SkVector v;
+            do {
+                v.fX = ps[i].fRandom.nextSScalar1();
+                v.fY = ps[i].fRandom.nextSScalar1();
+            } while (v.dot(v) > 1);
+
+            SkPoint center = { fX.eval(params, ps[i]), fY.eval(params, ps[i]) };
+            SkScalar radius = fRadius.eval(params, ps[i]);
+            ps[i].fPose.fPosition = center + (v * radius);
+            if (fSetHeading) {
+                if (!v.normalize()) {
+                    v.set(0, -1);
+                }
+                ps[i].fPose.fHeading = v;
+            }
+        }
+    }
+
+    void visitFields(SkFieldVisitor* v) override {
+        SkParticleAffector::visitFields(v);
+        v->visit("SetHeading", fSetHeading);
+        v->visit("X", fX);
+        v->visit("Y", fY);
+        v->visit("Radius", fRadius);
+    }
+
+private:
+    SkCurve fX;
+    SkCurve fY;
+    SkCurve fRadius;
+    bool    fSetHeading;
+};
+
+class SkPositionOnPathAffector : public SkParticleAffector {
+public:
+    SkPositionOnPathAffector(const char* path = "", bool setHeading = true,
+                             SkParticleValue input = SkParticleValue())
+            : fPath(path)
+            , fInput(input)
+            , fSetHeading(setHeading) {
+        this->rebuild();
+    }
+
+    REFLECTED(SkPositionOnPathAffector, SkParticleAffector)
+
+    void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
+        if (fContours.empty()) {
+            return;
+        }
+
+        for (int i = 0; i < count; ++i) {
+            float t = fInput.eval(params, ps[i]);
+            SkScalar len = fTotalLength * t;
+            int idx = 0;
+            while (idx < fContours.count() && len > fContours[idx]->length()) {
+                len -= fContours[idx++]->length();
+            }
+            SkVector localXAxis;
+            if (!fContours[idx]->getPosTan(len, &ps[i].fPose.fPosition, &localXAxis)) {
+                ps[i].fPose.fPosition = { 0, 0 };
+                localXAxis = { 1, 0 };
+            }
+            if (fSetHeading) {
+                ps[i].fPose.fHeading.set(localXAxis.fY, -localXAxis.fX);
+            }
+        }
+    }
+
+    void visitFields(SkFieldVisitor* v) override {
+        SkString oldPath = fPath;
+
+        SkParticleAffector::visitFields(v);
+        v->visit("Input", fInput);
+        v->visit("SetHeading", fSetHeading);
+        v->visit("Path", fPath);
+
+        if (fPath != oldPath) {
+            this->rebuild();
+        }
+    }
+
+private:
+    SkString        fPath;
+    SkParticleValue fInput;
+    bool            fSetHeading;
+
+    void rebuild() {
+        SkPath path;
+        if (!SkParsePath::FromSVGString(fPath.c_str(), &path)) {
+            return;
+        }
+
+        fTotalLength = 0;
+        fContours.reset();
+
+        SkContourMeasureIter iter(path, false);
+        while (auto contour = iter.next()) {
+            fContours.push_back(contour);
+            fTotalLength += contour->length();
+        }
+    }
+
+    // Cached
+    SkScalar                          fTotalLength;
+    SkTArray<sk_sp<SkContourMeasure>> fContours;
+};
+
+class SkPositionOnTextAffector : public SkParticleAffector {
+public:
+    SkPositionOnTextAffector(const char* text = "", SkScalar fontSize = 96, bool setHeading = true,
+                             SkParticleValue input = SkParticleValue())
+            : fText(text)
+            , fFontSize(fontSize)
+            , fInput(input)
+            , fSetHeading(setHeading) {
+        this->rebuild();
+    }
+
+    REFLECTED(SkPositionOnTextAffector, SkParticleAffector)
+
+    void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
+        if (fContours.empty()) {
+            return;
+        }
+
+        // TODO: Refactor to share code with PositionOnPathAffector
+        for (int i = 0; i < count; ++i) {
+            float t = fInput.eval(params, ps[i]);
+            SkScalar len = fTotalLength * t;
+            int idx = 0;
+            while (idx < fContours.count() && len > fContours[idx]->length()) {
+                len -= fContours[idx++]->length();
+            }
+            SkVector localXAxis;
+            if (!fContours[idx]->getPosTan(len, &ps[i].fPose.fPosition, &localXAxis)) {
+                ps[i].fPose.fPosition = { 0, 0 };
+                localXAxis = { 1, 0 };
+            }
+            if (fSetHeading) {
+                ps[i].fPose.fHeading.set(localXAxis.fY, -localXAxis.fX);
+            }
+        }
+    }
+
+    void visitFields(SkFieldVisitor* v) override {
+        SkString oldText = fText;
+        SkScalar oldSize = fFontSize;
+
+        SkParticleAffector::visitFields(v);
+        v->visit("Input", fInput);
+        v->visit("SetHeading", fSetHeading);
+        v->visit("Text", fText);
+        v->visit("FontSize", fFontSize);
+
+        if (fText != oldText || fFontSize != oldSize) {
+            this->rebuild();
+        }
+    }
+
+private:
+    SkString        fText;
+    SkScalar        fFontSize;
+    SkParticleValue fInput;
+    bool            fSetHeading;
+
+    void rebuild() {
+        fTotalLength = 0;
+        fContours.reset();
+
+        if (fText.isEmpty()) {
+            return;
+        }
+
+        // Use the font manager's default font
+        SkFont font(nullptr, fFontSize);
+        SkPath path;
+        SkTextUtils::GetPath(fText.c_str(), fText.size(), kUTF8_SkTextEncoding, 0, 0, font, &path);
+        SkContourMeasureIter iter(path, false);
+        while (auto contour = iter.next()) {
+            fContours.push_back(contour);
+            fTotalLength += contour->length();
+        }
+    }
+
+    // Cached
+    SkScalar                          fTotalLength;
+    SkTArray<sk_sp<SkContourMeasure>> fContours;
+};
+
+class SkSizeAffector : public SkParticleAffector {
+public:
+    SkSizeAffector(const SkCurve& curve = 1.0f) : fCurve(curve) {}
+
+    REFLECTED(SkSizeAffector, SkParticleAffector)
+
+    void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
+        for (int i = 0; i < count; ++i) {
+            ps[i].fPose.fScale = fCurve.eval(params, ps[i]);
+        }
+    }
+
+    void visitFields(SkFieldVisitor* v) override {
+        SkParticleAffector::visitFields(v);
+        v->visit("Curve", fCurve);
+    }
+
+private:
+    SkCurve fCurve;
+};
+
+class SkFrameAffector : public SkParticleAffector {
+public:
+    SkFrameAffector(const SkCurve& curve = 1.0f) : fCurve(curve) {}
+
+    REFLECTED(SkFrameAffector, SkParticleAffector)
+
+    void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
+        for (int i = 0; i < count; ++i) {
+            ps[i].fFrame = fCurve.eval(params, ps[i]);
+        }
+    }
+
+    void visitFields(SkFieldVisitor* v) override {
+        SkParticleAffector::visitFields(v);
+        v->visit("Curve", fCurve);
+    }
+
+private:
+    SkCurve fCurve;
+};
+
+class SkColorAffector : public SkParticleAffector {
+public:
+    SkColorAffector(const SkColorCurve& curve = SkColor4f{ 1.0f, 1.0f, 1.0f, 1.0f })
+        : fCurve(curve) {}
+
+    REFLECTED(SkColorAffector, SkParticleAffector)
+
+    void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
+        for (int i = 0; i < count; ++i) {
+            ps[i].fColor = fCurve.eval(params, ps[i]);
+        }
+    }
+
+    void visitFields(SkFieldVisitor* v) override {
+        SkParticleAffector::visitFields(v);
+        v->visit("Curve", fCurve);
+    }
+
+private:
+    SkColorCurve fCurve;
+};
+
+void SkParticleAffector::RegisterAffectorTypes() {
+    REGISTER_REFLECTED(SkParticleAffector);
+    REGISTER_REFLECTED(SkLinearVelocityAffector);
+    REGISTER_REFLECTED(SkAngularVelocityAffector);
+    REGISTER_REFLECTED(SkPointForceAffector);
+    REGISTER_REFLECTED(SkOrientationAffector);
+    REGISTER_REFLECTED(SkPositionInCircleAffector);
+    REGISTER_REFLECTED(SkPositionOnPathAffector);
+    REGISTER_REFLECTED(SkPositionOnTextAffector);
+    REGISTER_REFLECTED(SkSizeAffector);
+    REGISTER_REFLECTED(SkFrameAffector);
+    REGISTER_REFLECTED(SkColorAffector);
+}
+
+sk_sp<SkParticleAffector> SkParticleAffector::MakeLinearVelocity(const SkCurve& angle,
+                                                                 const SkCurve& strength,
+                                                                 bool force,
+                                                                 SkParticleFrame frame) {
+    return sk_sp<SkParticleAffector>(new SkLinearVelocityAffector(angle, strength, force, frame));
+}
+
+sk_sp<SkParticleAffector> SkParticleAffector::MakeAngularVelocity(const SkCurve& strength,
+                                                                  bool force) {
+    return sk_sp<SkParticleAffector>(new SkAngularVelocityAffector(strength, force));
+}
+
+sk_sp<SkParticleAffector> SkParticleAffector::MakePointForce(SkPoint point, SkScalar constant,
+                                                             SkScalar invSquare) {
+    return sk_sp<SkParticleAffector>(new SkPointForceAffector(point, constant, invSquare));
+}
+
+sk_sp<SkParticleAffector> SkParticleAffector::MakeOrientation(const SkCurve& angle,
+                                                              SkParticleFrame frame) {
+    return sk_sp<SkParticleAffector>(new SkOrientationAffector(angle, frame));
+}
+
+sk_sp<SkParticleAffector> SkParticleAffector::MakeSize(const SkCurve& curve) {
+    return sk_sp<SkParticleAffector>(new SkSizeAffector(curve));
+}
+
+sk_sp<SkParticleAffector> SkParticleAffector::MakeFrame(const SkCurve& curve) {
+    return sk_sp<SkParticleAffector>(new SkFrameAffector(curve));
+}
+
+sk_sp<SkParticleAffector> SkParticleAffector::MakeColor(const SkColorCurve& curve) {
+    return sk_sp<SkParticleAffector>(new SkColorAffector(curve));
+}
diff --git a/modules/particles/src/SkParticleDrawable.cpp b/modules/particles/src/SkParticleDrawable.cpp
new file mode 100644
index 0000000..9c31cc0
--- /dev/null
+++ b/modules/particles/src/SkParticleDrawable.cpp
@@ -0,0 +1,164 @@
+/*
+* Copyright 2019 Google LLC
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include "SkParticleDrawable.h"
+
+#include "SkAutoMalloc.h"
+#include "SkCanvas.h"
+#include "SkImage.h"
+#include "SkPaint.h"
+#include "SkParticleData.h"
+#include "SkRect.h"
+#include "SkSurface.h"
+#include "SkString.h"
+#include "SkRSXform.h"
+
+static sk_sp<SkImage> make_circle_image(int radius) {
+    auto surface = SkSurface::MakeRasterN32Premul(radius * 2, radius * 2);
+    surface->getCanvas()->clear(SK_ColorTRANSPARENT);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setColor(SK_ColorWHITE);
+    surface->getCanvas()->drawCircle(radius, radius, radius, paint);
+    return surface->makeImageSnapshot();
+}
+
+struct DrawAtlasArrays {
+    DrawAtlasArrays(const SkParticleState particles[], int count, SkPoint center)
+            : fXforms(count)
+            , fRects(count)
+            , fColors(count) {
+        for (int i = 0; i < count; ++i) {
+            fXforms[i] = particles[i].fPose.asRSXform(center);
+            fColors[i] = particles[i].fColor.toSkColor();
+        }
+    }
+
+    SkAutoTMalloc<SkRSXform> fXforms;
+    SkAutoTMalloc<SkRect>    fRects;
+    SkAutoTMalloc<SkColor>   fColors;
+};
+
+class SkCircleDrawable : public SkParticleDrawable {
+public:
+    SkCircleDrawable(int radius = 1)
+            : fRadius(radius) {
+        this->rebuild();
+    }
+
+    REFLECTED(SkCircleDrawable, SkParticleDrawable)
+
+    void draw(SkCanvas* canvas, const SkParticleState particles[], int count,
+              const SkPaint* paint) override {
+        SkPoint center = { SkIntToScalar(fRadius), SkIntToScalar(fRadius) };
+        DrawAtlasArrays arrays(particles, count, center);
+        for (int i = 0; i < count; ++i) {
+            arrays.fRects[i].set(0.0f, 0.0f, fImage->width(), fImage->height());
+        }
+        canvas->drawAtlas(fImage, arrays.fXforms.get(), arrays.fRects.get(), arrays.fColors.get(),
+                          count, SkBlendMode::kModulate, nullptr, paint);
+    }
+
+    void visitFields(SkFieldVisitor* v) override {
+        v->visit("Radius", fRadius);
+        this->rebuild();
+    }
+
+private:
+    int fRadius;
+
+    void rebuild() {
+        fRadius = SkTMax(fRadius, 1);
+        if (!fImage || fImage->width() != 2 * fRadius) {
+            fImage = make_circle_image(fRadius);
+        }
+    }
+
+    // Cached
+    sk_sp<SkImage> fImage;
+};
+
+class SkImageDrawable : public SkParticleDrawable {
+public:
+    SkImageDrawable(const SkString& path = SkString(), int cols = 1, int rows = 1)
+            : fPath(path)
+            , fCols(cols)
+            , fRows(rows) {
+        this->rebuild();
+    }
+
+    REFLECTED(SkImageDrawable, SkParticleDrawable)
+
+    void draw(SkCanvas* canvas, const SkParticleState particles[], int count,
+              const SkPaint* paint) override {
+        SkRect baseRect = getBaseRect();
+        SkPoint center = { baseRect.width() * 0.5f, baseRect.height() * 0.5f };
+        DrawAtlasArrays arrays(particles, count, center);
+
+        int frameCount = fCols * fRows;
+        for (int i = 0; i < count; ++i) {
+            int frame = static_cast<int>(particles[i].fFrame * frameCount + 0.5f);
+            frame = SkTPin(frame, 0, frameCount - 1);
+            int row = frame / fCols;
+            int col = frame % fCols;
+            arrays.fRects[i] = baseRect.makeOffset(col * baseRect.width(), row * baseRect.height());
+        }
+        canvas->drawAtlas(fImage, arrays.fXforms.get(), arrays.fRects.get(), arrays.fColors.get(),
+                          count, SkBlendMode::kModulate, nullptr, paint);
+    }
+
+    void visitFields(SkFieldVisitor* v) override {
+        SkString oldPath = fPath;
+
+        v->visit("Path", fPath);
+        v->visit("Columns", fCols);
+        v->visit("Rows", fRows);
+
+        fCols = SkTMax(fCols, 1);
+        fRows = SkTMax(fRows, 1);
+        if (oldPath != fPath) {
+            this->rebuild();
+        }
+    }
+
+private:
+    SkString fPath;
+    int      fCols;
+    int      fRows;
+
+    SkRect getBaseRect() const {
+        return SkRect::MakeWH(static_cast<float>(fImage->width()) / fCols,
+                              static_cast<float>(fImage->height() / fRows));
+    }
+
+    void rebuild() {
+        fImage = SkImage::MakeFromEncoded(SkData::MakeFromFileName(fPath.c_str()));
+        if (!fImage) {
+            if (!fPath.isEmpty()) {
+                SkDebugf("Could not load image \"%s\"\n", fPath.c_str());
+            }
+            fImage = make_circle_image(1);
+        }
+    }
+
+    // Cached
+    sk_sp<SkImage> fImage;
+};
+
+void SkParticleDrawable::RegisterDrawableTypes() {
+    REGISTER_REFLECTED(SkParticleDrawable);
+    REGISTER_REFLECTED(SkCircleDrawable);
+    REGISTER_REFLECTED(SkImageDrawable);
+}
+
+sk_sp<SkParticleDrawable> SkParticleDrawable::MakeCircle(int radius) {
+    return sk_sp<SkParticleDrawable>(new SkCircleDrawable(radius));
+}
+
+sk_sp<SkParticleDrawable> SkParticleDrawable::MakeImage(const SkString& path, int cols, int rows) {
+    return sk_sp<SkParticleDrawable>(new SkImageDrawable(path, cols, rows));
+}
diff --git a/modules/particles/src/SkParticleEffect.cpp b/modules/particles/src/SkParticleEffect.cpp
new file mode 100644
index 0000000..1cd0eab
--- /dev/null
+++ b/modules/particles/src/SkParticleEffect.cpp
@@ -0,0 +1,284 @@
+/*
+* Copyright 2019 Google LLC
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include "SkParticleEffect.h"
+
+#include "SkCanvas.h"
+#include "SkColorData.h"
+#include "SkPaint.h"
+#include "SkParticleAffector.h"
+#include "SkParticleDrawable.h"
+#include "SkReflected.h"
+#include "SkRSXform.h"
+
+void SkParticleEffectParams::visitFields(SkFieldVisitor* v) {
+    v->visit("MaxCount", fMaxCount);
+    v->visit("Duration", fEffectDuration);
+    v->visit("Rate", fRate);
+    v->visit("Life", fLifetime);
+
+    v->visit("Drawable", fDrawable);
+
+    v->visit("Spawn", fSpawnAffectors);
+    v->visit("Update", fUpdateAffectors);
+}
+
+SkParticleEffect::SkParticleEffect(sk_sp<SkParticleEffectParams> params, const SkRandom& random)
+        : fParams(std::move(params))
+        , fRandom(random)
+        , fLooping(false)
+        , fSpawnTime(-1.0)
+        , fCount(0)
+        , fLastTime(-1.0)
+        , fSpawnRemainder(0.0f) {
+    this->setCapacity(fParams->fMaxCount);
+}
+
+void SkParticleEffect::start(double now, bool looping) {
+    fCount = 0;
+    fLastTime = fSpawnTime = now;
+    fSpawnRemainder = 0.0f;
+    fLooping = looping;
+}
+
+void SkParticleEffect::update(double now) {
+    if (!this->isAlive() || !fParams->fDrawable) {
+        return;
+    }
+
+    float deltaTime = static_cast<float>(now - fLastTime);
+    if (deltaTime <= 0.0f) {
+        return;
+    }
+    fLastTime = now;
+
+    // Handle user edits to fMaxCount
+    if (fParams->fMaxCount != fCapacity) {
+        this->setCapacity(fParams->fMaxCount);
+    }
+
+    float effectAge = static_cast<float>((now - fSpawnTime) / fParams->fEffectDuration);
+    effectAge = fLooping ? fmodf(effectAge, 1.0f) : SkTPin(effectAge, 0.0f, 1.0f);
+
+    SkParticleUpdateParams updateParams;
+    updateParams.fDeltaTime = deltaTime;
+    updateParams.fEffectAge = effectAge;
+
+    // During spawn, values that refer to kAge_Source get the *effect* age
+    updateParams.fAgeSource = SkParticleValue::kEffectAge_Source;
+
+    // Advance age for existing particles, and remove any that have reached their end of life
+    for (int i = 0; i < fCount; ++i) {
+        fParticles[i].fAge += fParticles[i].fInvLifetime * deltaTime;
+        if (fParticles[i].fAge > 1.0f) {
+            // NOTE: This is fast, but doesn't preserve drawing order. Could be a problem...
+            fParticles[i]     = fParticles[fCount - 1];
+            fStableRandoms[i] = fStableRandoms[fCount - 1];
+            --i;
+            --fCount;
+        }
+    }
+
+    // Spawn new particles
+    float desired = fParams->fRate * deltaTime + fSpawnRemainder;
+    int numToSpawn = sk_float_round2int(desired);
+    fSpawnRemainder = desired - numToSpawn;
+    numToSpawn = SkTPin(numToSpawn, 0, fParams->fMaxCount - fCount);
+    if (numToSpawn) {
+        const int spawnBase = fCount;
+
+        for (int i = 0; i < numToSpawn; ++i) {
+            // Mutate our SkRandom so each particle definitely gets a different generator
+            fRandom.nextU();
+            fParticles[fCount].fAge = 0.0f;
+            fParticles[fCount].fPose.fPosition = { 0.0f, 0.0f };
+            fParticles[fCount].fPose.fHeading = { 0.0f, -1.0f };
+            fParticles[fCount].fPose.fScale = 1.0f;
+            fParticles[fCount].fVelocity.fLinear = { 0.0f, 0.0f };
+            fParticles[fCount].fVelocity.fAngular = 0.0f;
+            fParticles[fCount].fColor = { 1.0f, 1.0f, 1.0f, 1.0f };
+            fParticles[fCount].fFrame = 0.0f;
+            fParticles[fCount].fRandom = fRandom;
+            fCount++;
+        }
+
+        // Apply spawn affectors
+        for (auto affector : fParams->fSpawnAffectors) {
+            if (affector) {
+                affector->apply(updateParams, fParticles + spawnBase, numToSpawn);
+            }
+        }
+
+        // Now stash copies of the random generators and compute particle lifetimes
+        // (so the curve can refer to spawn-computed source values)
+        for (int i = spawnBase; i < fCount; ++i) {
+            fParticles[i].fInvLifetime =
+                sk_ieee_float_divide(1.0f, fParams->fLifetime.eval(updateParams, fParticles[i]));
+            fStableRandoms[i] = fParticles[i].fRandom;
+        }
+    }
+
+    // Restore all stable random generators so update affectors get consistent behavior each frame
+    for (int i = 0; i < fCount; ++i) {
+        fParticles[i].fRandom = fStableRandoms[i];
+    }
+
+    // During update, values that refer to kAge_Source get the *particle* age
+    updateParams.fAgeSource = SkParticleValue::kParticleAge_Source;
+
+    // Apply update rules
+    for (auto affector : fParams->fUpdateAffectors) {
+        if (affector) {
+            affector->apply(updateParams, fParticles, fCount);
+        }
+    }
+
+    // Do fixed-function update work (integration of position and orientation)
+    for (int i = 0; i < fCount; ++i) {
+        fParticles[i].fPose.fPosition += fParticles[i].fVelocity.fLinear * deltaTime;
+
+        SkScalar s = SkScalarSin(fParticles[i].fVelocity.fAngular * deltaTime),
+                 c = SkScalarCos(fParticles[i].fVelocity.fAngular * deltaTime);
+        SkVector oldHeading = fParticles[i].fPose.fHeading;
+        fParticles[i].fPose.fHeading = { oldHeading.fX * c - oldHeading.fY * s,
+                                         oldHeading.fX * s + oldHeading.fY * c };
+    }
+
+    // Mark effect as dead if we've reached the end (and are not looping)
+    if (!fLooping && (now - fSpawnTime) > fParams->fEffectDuration) {
+        fSpawnTime = -1.0;
+    }
+}
+
+void SkParticleEffect::draw(SkCanvas* canvas) {
+    if (this->isAlive() && fParams->fDrawable) {
+        SkPaint paint;
+        paint.setFilterQuality(SkFilterQuality::kMedium_SkFilterQuality);
+        fParams->fDrawable->draw(canvas, fParticles.get(), fCount, &paint);
+    }
+}
+
+void SkParticleEffect::setCapacity(int capacity) {
+    fParticles.realloc(capacity);
+    fStableRandoms.realloc(capacity);
+
+    fCapacity = capacity;
+    fCount = SkTMin(fCount, fCapacity);
+}
+
+constexpr SkFieldVisitor::EnumStringMapping gValueSourceMapping[] = {
+    { SkParticleValue::kAge_Source,          "Age" },
+    { SkParticleValue::kRandom_Source,       "Random" },
+    { SkParticleValue::kParticleAge_Source,  "ParticleAge" },
+    { SkParticleValue::kEffectAge_Source,    "EffectAge" },
+    { SkParticleValue::kPositionX_Source,    "PositionX" },
+    { SkParticleValue::kPositionY_Source,    "PositionY" },
+    { SkParticleValue::kHeadingX_Source,     "HeadingX" },
+    { SkParticleValue::kHeadingY_Source,     "HeadingY" },
+    { SkParticleValue::kScale_Source,        "Scale" },
+    { SkParticleValue::kVelocityX_Source,    "VelocityX" },
+    { SkParticleValue::kVelocityY_Source,    "VelocityY" },
+    { SkParticleValue::kRotation_Source,     "Rotation" },
+    { SkParticleValue::kColorR_Source,       "ColorR" },
+    { SkParticleValue::kColorG_Source,       "ColorG" },
+    { SkParticleValue::kColorB_Source,       "ColorB" },
+    { SkParticleValue::kColorA_Source,       "ColorA" },
+    { SkParticleValue::kSpriteFrame_Source,  "SpriteFrame" },
+};
+
+constexpr SkFieldVisitor::EnumStringMapping gValueTileModeMapping[] = {
+    { SkParticleValue::kClamp_TileMode,  "Clamp" },
+    { SkParticleValue::kRepeat_TileMode, "Repeat" },
+    { SkParticleValue::kMirror_TileMode, "Mirror" },
+};
+
+static bool source_needs_frame(int source) {
+    switch (source) {
+        case SkParticleValue::kHeadingX_Source:
+        case SkParticleValue::kHeadingY_Source:
+        case SkParticleValue::kVelocityX_Source:
+        case SkParticleValue::kVelocityY_Source:
+            return true;
+        default:
+            return false;
+    }
+}
+
+void SkParticleValue::visitFields(SkFieldVisitor* v) {
+    v->visit("Source", fSource, gValueSourceMapping, SK_ARRAY_COUNT(gValueSourceMapping));
+    if (source_needs_frame(fSource)) {
+        v->visit("Frame", fFrame, gParticleFrameMapping, SK_ARRAY_COUNT(gParticleFrameMapping));
+    }
+    v->visit("TileMode", fTileMode, gValueTileModeMapping, SK_ARRAY_COUNT(gValueTileModeMapping));
+    v->visit("Left", fLeft);
+    v->visit("Right", fRight);
+
+    // Re-compute cached evaluation parameters
+    fScale = sk_float_isfinite(1.0f / (fRight - fLeft)) ? 1.0f / (fRight - fLeft) : 0;
+    fBias = -fLeft * fScale;
+}
+
+float SkParticleValue::getSourceValue(const SkParticleUpdateParams& params,
+                                      SkParticleState& ps) const {
+    switch ((kAge_Source == fSource) ? params.fAgeSource : fSource) {
+        // Do all the simple (non-frame-dependent) sources first:
+        case kRandom_Source:      return ps.fRandom.nextF();
+        case kParticleAge_Source: return ps.fAge;
+        case kEffectAge_Source:   return params.fEffectAge;
+
+        case kPositionX_Source:   return ps.fPose.fPosition.fX;
+        case kPositionY_Source:   return ps.fPose.fPosition.fY;
+        case kScale_Source:       return ps.fPose.fScale;
+        case kRotation_Source:    return ps.fVelocity.fAngular;
+
+        case kColorR_Source:      return ps.fColor.fR;
+        case kColorG_Source:      return ps.fColor.fG;
+        case kColorB_Source:      return ps.fColor.fB;
+        case kColorA_Source:      return ps.fColor.fA;
+        case kSpriteFrame_Source: return ps.fFrame;
+    }
+
+    SkASSERT(source_needs_frame(fSource));
+    SkVector frameUp = ps.getFrameHeading(static_cast<SkParticleFrame>(fFrame));
+    SkVector frameRight = { -frameUp.fY, frameUp.fX };
+
+    switch (fSource) {
+        case kHeadingX_Source:  return ps.fPose.fHeading.dot(frameRight);
+        case kHeadingY_Source:  return ps.fPose.fHeading.dot(frameUp);
+        case kVelocityX_Source: return ps.fVelocity.fLinear.dot(frameRight);
+        case kVelocityY_Source: return ps.fVelocity.fLinear.dot(frameUp);
+    }
+
+    SkDEBUGFAIL("Unreachable");
+    return 0.0f;
+}
+
+float SkParticleValue::eval(const SkParticleUpdateParams& params, SkParticleState& ps) const {
+    float v = this->getSourceValue(params, ps);
+    v = (v * fScale) + fBias;
+
+    switch (fTileMode) {
+        case kClamp_TileMode:
+            v = SkTPin(v, 0.0f, 1.0f);
+            break;
+        case kRepeat_TileMode:
+            v = sk_float_mod(v, 1.0f);
+            if (v < 0) {
+                v += 1.0f;
+            }
+            break;
+        case kMirror_TileMode:
+            v = sk_float_mod(v, 2.0f);
+            if (v < 0) {
+                v += 2.0f;
+            }
+            v = 1.0f - sk_float_abs(v - 1.0f);
+            break;
+    }
+
+    return v;
+}
diff --git a/modules/particles/src/SkReflected.cpp b/modules/particles/src/SkReflected.cpp
new file mode 100644
index 0000000..938bb87
--- /dev/null
+++ b/modules/particles/src/SkReflected.cpp
@@ -0,0 +1,24 @@
+/*
+* Copyright 2019 Google LLC
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include "SkReflected.h"
+
+#include "SkCurve.h"
+
+SkSTArray<16, const SkReflected::Type*, true> SkReflected::gTypes;
+
+void SkReflected::VisitTypes(std::function<void(const Type*)> visitor) {
+    for (const Type* type : gTypes) {
+        visitor(type);
+    }
+}
+
+void SkFieldVisitor::visit(const char* name, SkCurve& c) {
+    this->enterObject(name);
+    c.visitFields(this);
+    this->exitObject();
+}
diff --git a/modules/pathkit/CHANGELOG.md b/modules/pathkit/CHANGELOG.md
index 6cde21c..1867982 100644
--- a/modules/pathkit/CHANGELOG.md
+++ b/modules/pathkit/CHANGELOG.md
@@ -6,9 +6,16 @@
 
 ## [Unreleased]
 
+
+
+## [0.6.0] 2019-02-25
+
 ### Fixed
  - Potential bug in `ready()` if already loaded.
 
+### Removed
+ - Deprecated `PathKitInit.then()` see 0.5.1 notes.
+
 ## [0.5.1] 2019-01-04
 
 ### Changed
diff --git a/modules/pathkit/Makefile b/modules/pathkit/Makefile
index e3123a2..31e5f86 100644
--- a/modules/pathkit/Makefile
+++ b/modules/pathkit/Makefile
@@ -57,28 +57,28 @@
 	mkdir -p $$SKIA_ROOT/out/dockerbuild
 
 	docker run --rm -v $$SKIA_ROOT:/SRC -v $$SKIA_ROOT/out/dockerbuild:/OUT \
-gcr.io/skia-public/emsdk-release:1.38.16_v1 /SRC/infra/pathkit/build_pathkit.sh
+gcr.io/skia-public/emsdk-release:prod /SRC/infra/pathkit/build_pathkit.sh
 	cp ../../out/dockerbuild/pathkit.js   ./npm-wasm/bin/test/pathkit.js
 	cp ../../out/dockerbuild/pathkit.wasm ./npm-wasm/bin/test/pathkit.wasm
 
 	docker run --rm -v $$SKIA_ROOT:/SRC -v $$SKIA_ROOT/out/dockerbuild:/OUT \
-gcr.io/skia-public/emsdk-release:1.38.16_v1 /SRC/infra/pathkit/build_pathkit.sh asm.js
+gcr.io/skia-public/emsdk-release:prod /SRC/infra/pathkit/build_pathkit.sh asm.js
 	cp ../../out/dockerbuild/pathkit.js     ./npm-asmjs/bin/test/pathkit.js
 	cp ../../out/dockerbuild/pathkit.js.mem ./npm-asmjs/bin/test/pathkit.js.mem
 
-	docker run --shm-size=2gb -v $$SKIA_ROOT:/SRC gcr.io/skia-public/karma-chrome-tests:68.0.3440.106_v4 \
+	docker run --shm-size=2gb -v $$SKIA_ROOT:/SRC gcr.io/skia-public/karma-chrome-tests:72.0.3626.121_v1 \
 karma start /SRC/modules/pathkit/karma.conf.js --single-run
-	docker run --shm-size=2gb -v $$SKIA_ROOT:/SRC -e ASM_JS=1 gcr.io/skia-public/karma-chrome-tests:68.0.3440.106_v4 \
+	docker run --shm-size=2gb -v $$SKIA_ROOT:/SRC -e ASM_JS=1 gcr.io/skia-public/karma-chrome-tests:72.0.3626.121_v1 \
 karma start /SRC/modules/pathkit/karma.conf.js --single-run
 
 test-docker-continuous:
 	echo "Assuming make npm-test has also been run by a user (if needed)"
-	docker run --shm-size=2gb -v $$SKIA_ROOT:/SRC gcr.io/skia-public/karma-chrome-tests:68.0.3440.106_v4 \
+	docker run --shm-size=2gb -v $$SKIA_ROOT:/SRC gcr.io/skia-public/karma-chrome-tests:72.0.3626.121_v1 \
 karma start /SRC/modules/pathkit/karma.conf.js --no-single-run
 
 test-docker-continuous-asmjs:
 	echo "Assuming make npm-test has also been run by a user (if needed)"
-	docker run --shm-size=2gb -v $$SKIA_ROOT:/SRC -e ASM_JS=1 gcr.io/skia-public/karma-chrome-tests:68.0.3440.106_v4 \
+	docker run --shm-size=2gb -v $$SKIA_ROOT:/SRC -e ASM_JS=1 gcr.io/skia-public/karma-chrome-tests:72.0.3626.121_v1 \
 karma start /SRC/modules/pathkit/karma.conf.js --no-single-run
 
 npm-test:
diff --git a/modules/pathkit/compile.sh b/modules/pathkit/compile.sh
index e019de1..81d374e 100755
--- a/modules/pathkit/compile.sh
+++ b/modules/pathkit/compile.sh
@@ -124,7 +124,6 @@
 -DSK_DISABLE_READBUFFER=1 \
 -fno-rtti -fno-exceptions -DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0 \
 $WASM_CONF \
--s BINARYEN_IGNORE_IMPLICIT_TRAPS=1 \
 -s ERROR_ON_MISSING_LIBRARIES=1 \
 -s ERROR_ON_UNDEFINED_SYMBOLS=1 \
 -s EXPORT_NAME="PathKitInit" \
diff --git a/modules/pathkit/karma.bench.conf.js b/modules/pathkit/karma.bench.conf.js
index a2808a6..315903b 100644
--- a/modules/pathkit/karma.bench.conf.js
+++ b/modules/pathkit/karma.bench.conf.js
@@ -13,6 +13,7 @@
       { pattern: 'npm-wasm/bin/pathkit.wasm', included:false, served:true},
       'perf/perfReporter.js',
       'npm-wasm/bin/pathkit.js',
+      'tests/pathkitinit.js',
       'perf/*.bench.js'
     ],
 
@@ -38,8 +39,8 @@
     // enable / disable watching file and executing tests whenever any file changes
     autoWatch: true,
 
-    browserDisconnectTimeout: 10000,
-    browserNoActivityTimeout: 10000,
+    browserDisconnectTimeout: 20000,
+    browserNoActivityTimeout: 20000,
 
     // start these browsers
     browsers: ['Chrome'],
@@ -74,6 +75,7 @@
       { pattern: 'npm-asmjs/bin/pathkit.js.mem', included:false, served:true},
       'perf/perfReporter.js',
       'npm-asmjs/bin/pathkit.js',
+      'tests/pathkitinit.js',
       'perf/*.bench.js'
     ];
 
diff --git a/modules/pathkit/karma.conf.js b/modules/pathkit/karma.conf.js
index 649d21d..47ff2d2 100644
--- a/modules/pathkit/karma.conf.js
+++ b/modules/pathkit/karma.conf.js
@@ -14,6 +14,7 @@
       { pattern: 'tests/*.json', included:false, served:true},
       'tests/testReporter.js',
       'npm-wasm/bin/test/pathkit.js',
+      'tests/pathkitinit.js',
       'tests/*.spec.js'
     ],
 
@@ -39,8 +40,8 @@
     // enable / disable watching file and executing tests whenever any file changes
     autoWatch: true,
 
-    browserDisconnectTimeout: 15000,
-    browserNoActivityTimeout: 15000,
+    browserDisconnectTimeout: 20000,
+    browserNoActivityTimeout: 20000,
 
     // start these browsers
     browsers: ['Chrome'],
@@ -76,6 +77,7 @@
       { pattern: 'tests/*.json', included:false, served:true},
       'tests/testReporter.js',
       'npm-asmjs/bin/test/pathkit.js',
+      'tests/pathkitinit.js',
       'tests/*.spec.js'
     ];
 
diff --git a/modules/pathkit/npm-asmjs/package.json b/modules/pathkit/npm-asmjs/package.json
index 70f773c..9a88f7f 100644
--- a/modules/pathkit/npm-asmjs/package.json
+++ b/modules/pathkit/npm-asmjs/package.json
@@ -1,6 +1,6 @@
 {
   "name": "pathkit-asmjs",
-  "version": "0.5.1",
+  "version": "0.6.0",
   "description": "A asm.js version of Skia's PathOps toolkit",
   "main": "bin/pathkit.js",
   "homepage": "https://github.com/google/skia/tree/master/modules/pathkit",
diff --git a/modules/pathkit/npm-wasm/package.json b/modules/pathkit/npm-wasm/package.json
index a09c0b3..23fc0a6 100644
--- a/modules/pathkit/npm-wasm/package.json
+++ b/modules/pathkit/npm-wasm/package.json
@@ -1,6 +1,6 @@
 {
   "name": "pathkit-wasm",
-  "version": "0.5.1",
+  "version": "0.6.0",
   "description": "A WASM version of Skia's PathOps toolkit",
   "main": "bin/pathkit.js",
   "homepage": "https://github.com/google/skia/tree/master/modules/pathkit",
diff --git a/modules/pathkit/pathkit_wasm_bindings.cpp b/modules/pathkit/pathkit_wasm_bindings.cpp
index c8d821f..0837ee0 100644
--- a/modules/pathkit/pathkit_wasm_bindings.cpp
+++ b/modules/pathkit/pathkit_wasm_bindings.cpp
@@ -226,7 +226,7 @@
     // Wrapping it in val automatically turns it into a JS string.
     // Not too sure on performance implications, but is is simpler than
     // returning a raw pointer to const char * and then using
-    // Pointer_stringify() on the calling side.
+    // UTF8ToString() on the calling side.
     return emscripten::val(s.c_str());
 }
 
diff --git a/modules/pathkit/perf/effects.bench.js b/modules/pathkit/perf/effects.bench.js
index d1be9ec..30d6e35 100644
--- a/modules/pathkit/perf/effects.bench.js
+++ b/modules/pathkit/perf/effects.bench.js
@@ -1,20 +1,4 @@
-
-
 describe('PathKit\'s Effects', function() {
-    // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
-    var PathKit = null;
-    const LoadPathKit = new Promise(function(resolve, reject) {
-        if (PathKit) {
-            resolve();
-        } else {
-            PathKitInit({
-                locateFile: (file) => '/pathkit/'+file,
-            }).then((_PathKit) => {
-                PathKit = _PathKit;
-                resolve();
-            });
-        }
-    });
 
     // see https://fiddle.skia.org/c/@discrete_path
     function drawStar(X=128, Y=128, R=116) {
diff --git a/modules/pathkit/perf/path.bench.js b/modules/pathkit/perf/path.bench.js
index 35d8b25..01fd6f9 100644
--- a/modules/pathkit/perf/path.bench.js
+++ b/modules/pathkit/perf/path.bench.js
@@ -1,21 +1,4 @@
-
-
 describe('PathKit\'s Path Behavior', function() {
-    // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
-    var PathKit = null;
-    const LoadPathKit = new Promise(function(resolve, reject) {
-        if (PathKit) {
-            resolve();
-        } else {
-            PathKitInit({
-                locateFile: (file) => '/pathkit/'+file,
-            }).then((_PathKit) => {
-                PathKit = _PathKit;
-                resolve();
-            });
-        }
-    });
-
     function drawPath() {
         let path = PathKit.NewPath();
         path.moveTo(20, 5);
diff --git a/modules/pathkit/perf/pathops.bench.js b/modules/pathkit/perf/pathops.bench.js
index 2da6553..31d7e05 100644
--- a/modules/pathkit/perf/pathops.bench.js
+++ b/modules/pathkit/perf/pathops.bench.js
@@ -1,21 +1,4 @@
-
-
 describe('PathKit\'s Pathops', function() {
-    // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
-    var PathKit = null;
-    const LoadPathKit = new Promise(function(resolve, reject) {
-        if (PathKit) {
-            resolve();
-        } else {
-            PathKitInit({
-                locateFile: (file) => '/pathkit/'+file,
-            }).then((_PathKit) => {
-                PathKit = _PathKit;
-                resolve();
-            });
-        }
-    });
-
     // see https://fiddle.skia.org/c/@discrete_path
     function drawStar(X=128, Y=128, R=116) {
         let p = PathKit.NewPath();
diff --git a/modules/pathkit/perf/perfReporter.js b/modules/pathkit/perf/perfReporter.js
index d74110c..c60cfbc 100644
--- a/modules/pathkit/perf/perfReporter.js
+++ b/modules/pathkit/perf/perfReporter.js
@@ -3,47 +3,52 @@
 // Typically used for debugging.
 const fail_on_no_perf = false;
 
-
 function benchmarkAndReport(benchName, setupFn, testFn, teardownFn) {
-    let ctx = {};
-    // warmup 3 times (arbitrary choice)
-    setupFn(ctx);
-    testFn(ctx);
-    testFn(ctx);
-    testFn(ctx);
-    teardownFn(ctx);
-
-    ctx = {};
-    setupFn(ctx);
-    let start = Date.now();
-    let now = start;
-    times = 0;
-    // See how many times we can do it in 100ms (arbitrary choice)
-    while (now - start < 100) {
+    try {
+        let ctx = {};
+        // warmup 3 times (arbitrary choice)
+        setupFn(ctx);
         testFn(ctx);
-        now = Date.now();
-        times++;
-    }
-
-    teardownFn(ctx);
-
-    // Try to make it go for 2 seconds (arbitrarily chosen)
-    // Since the pre-try took 100ms, multiply by 20 to get
-    // approximate tries in 2s
-    let goalTimes = times * 20;
-    setupFn(ctx);
-    start = Date.now();
-    times = 0;
-    while (times < goalTimes) {
         testFn(ctx);
-        times++;
-    }
-    let end = Date.now();
-    teardownFn(ctx);
+        testFn(ctx);
+        teardownFn(ctx);
 
-    let us = (end - start) * 1000 / times;
-    console.log(benchName, `${us} microseconds`)
-    return _report(us, benchName);
+        ctx = {};
+        setupFn(ctx);
+        let start = Date.now();
+        let now = start;
+        times = 0;
+        // See how many times we can do it in 100ms (arbitrary choice)
+        while (now - start < 100) {
+            testFn(ctx);
+            now = Date.now();
+            times++;
+        }
+
+        teardownFn(ctx);
+
+        // Try to make it go for 2 seconds (arbitrarily chosen)
+        // Since the pre-try took 100ms, multiply by 20 to get
+        // approximate tries in 2s (unless now - start >> 100 ms)
+        let goalTimes = times * 20;
+        ctx = {};
+        setupFn(ctx);
+        times = 0;
+        start = Date.now();
+        while (times < goalTimes) {
+            testFn(ctx);
+            times++;
+        }
+        const end = Date.now();
+        teardownFn(ctx);
+
+        const us = (end - start) * 1000 / times;
+        console.log(benchName, `${us} microseconds`)
+        return _report(us, benchName);
+    } catch(e) {
+        console.error('caught error', e);
+        return Promise.reject(e);
+    }
 }
 
 
diff --git a/modules/pathkit/ready.js b/modules/pathkit/ready.js
index e054f62..60f2486 100644
--- a/modules/pathkit/ready.js
+++ b/modules/pathkit/ready.js
@@ -3,7 +3,6 @@
 // and why the below fixes it.
 Module['ready'] = function() {
   return new Promise(function (resolve, reject) {
-    delete Module['then'];
     Module['onAbort'] = reject;
     if (runtimeInitialized) {
       resolve(Module);
@@ -14,5 +13,4 @@
     }
   });
 }
-// TODO(kjlubick): Shut .then() entirely off in 0.6.0 by uncommenting below.
-// delete Module['then'];
\ No newline at end of file
+delete Module['then'];
\ No newline at end of file
diff --git a/modules/pathkit/tests/effects.spec.js b/modules/pathkit/tests/effects.spec.js
index 29cdbd5..575c95a 100644
--- a/modules/pathkit/tests/effects.spec.js
+++ b/modules/pathkit/tests/effects.spec.js
@@ -1,21 +1,5 @@
-jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
 
 describe('PathKit\'s Path Behavior', function() {
-    // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
-    var PathKit = null;
-    const LoadPathKit = new Promise(function(resolve, reject) {
-        if (PathKit) {
-            resolve();
-        } else {
-            PathKitInit({
-                locateFile: (file) => '/pathkit/'+file,
-            }).ready().then((_PathKit) => {
-                PathKit = _PathKit;
-                resolve();
-            });
-        }
-    });
-
     // see https://fiddle.skia.org/c/@discrete_path
     function drawStar() {
         let path = PathKit.NewPath();
diff --git a/modules/pathkit/tests/path.spec.js b/modules/pathkit/tests/path.spec.js
index 80a779a..aa674ba 100644
--- a/modules/pathkit/tests/path.spec.js
+++ b/modules/pathkit/tests/path.spec.js
@@ -1,20 +1,4 @@
-jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
-
 describe('PathKit\'s Path Behavior', function() {
-    // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
-    var PathKit = null;
-    const LoadPathKit = new Promise(function(resolve, reject) {
-        if (PathKit) {
-            resolve();
-        } else {
-            PathKitInit({
-                locateFile: (file) => '/pathkit/'+file,
-            }).ready().then((_PathKit) => {
-                PathKit = _PathKit;
-                resolve();
-            });
-        }
-    });
 
     describe('Basic Path Features', function() {
         function drawSimplePath() {
diff --git a/modules/pathkit/tests/path2d.spec.js b/modules/pathkit/tests/path2d.spec.js
index 6c2b3ef..b60db85 100644
--- a/modules/pathkit/tests/path2d.spec.js
+++ b/modules/pathkit/tests/path2d.spec.js
@@ -1,21 +1,4 @@
-jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
-
 describe('PathKit\'s Path2D API', function() {
-    // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
-    var PathKit = null;
-    const LoadPathKit = new Promise(function(resolve, reject) {
-        if (PathKit) {
-            resolve();
-        } else {
-            PathKitInit({
-                locateFile: (file) => '/pathkit/'+file,
-            }).ready().then((_PathKit) => {
-                PathKit = _PathKit;
-                resolve();
-            });
-        }
-    });
-
     it('can do everything in the Path2D API w/o crashing', function(done) {
         LoadPathKit.then(catchException(done, () => {
             // This is taken from example.html
diff --git a/modules/pathkit/tests/pathkitinit.js b/modules/pathkit/tests/pathkitinit.js
new file mode 100644
index 0000000..0fc9b71
--- /dev/null
+++ b/modules/pathkit/tests/pathkitinit.js
@@ -0,0 +1,16 @@
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
+
+let PathKit = null;
+const LoadPathKit = new Promise(function(resolve, reject) {
+    console.log('pathkit loading', new Date());
+    PathKitInit({
+        locateFile: (file) => '/pathkit/'+file,
+    }).ready().then((_PathKit) => {
+        console.log('pathkit loaded', new Date());
+        PathKit = _PathKit;
+        resolve();
+    }).catch((e) => {
+        console.error('pathkit failed to load', new Date(), e);
+        reject();
+    });
+});
\ No newline at end of file
diff --git a/modules/pathkit/tests/pathops.spec.js b/modules/pathkit/tests/pathops.spec.js
index 720bd8c..089f9bc 100644
--- a/modules/pathkit/tests/pathops.spec.js
+++ b/modules/pathkit/tests/pathops.spec.js
@@ -1,5 +1,3 @@
-jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
-
 var dumpErrors = false;
 var container;
 
@@ -74,36 +72,27 @@
 }
 
 describe('PathKit\'s PathOps Behavior', function() {
-    // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
-    var PathKit = null;
     var PATHOP_MAP = {};
     var FILLTYPE_MAP = {};
-    const LoadPathKit = new Promise(function(resolve, reject) {
-        if (PathKit) {
-            resolve();
-        } else {
-            PathKitInit({
-                locateFile: (file) => '/pathkit/'+file,
-            }).ready().then((_PathKit) => {
-                PathKit = _PathKit;
-                PATHOP_MAP = {
-                    'kIntersect_SkPathOp':         PathKit.PathOp.INTERSECT,
-                    'kDifference_SkPathOp':        PathKit.PathOp.DIFFERENCE,
-                    'kUnion_SkPathOp':             PathKit.PathOp.UNION,
-                    'kXOR_SkPathOp':               PathKit.PathOp.XOR,
-                    'kXOR_PathOp':                 PathKit.PathOp.XOR,
-                    'kReverseDifference_SkPathOp': PathKit.PathOp.REVERSE_DIFFERENCE,
-                };
-                FILLTYPE_MAP = {
-                    'kWinding_FillType':        PathKit.FillType.WINDING,
-                    'kEvenOdd_FillType':        PathKit.FillType.EVENODD,
-                    'kInverseWinding_FillType': PathKit.FillType.INVERSE_WINDING,
-                    'kInverseEvenOdd_FillType': PathKit.FillType.INVERSE_EVENODD,
-                };
-                resolve();
-            });
+
+    function init() {
+        if (PathKit && !PATHOP_MAP['kIntersect_SkPathOp']) {
+            PATHOP_MAP = {
+                'kIntersect_SkPathOp':         PathKit.PathOp.INTERSECT,
+                'kDifference_SkPathOp':        PathKit.PathOp.DIFFERENCE,
+                'kUnion_SkPathOp':             PathKit.PathOp.UNION,
+                'kXOR_SkPathOp':               PathKit.PathOp.XOR,
+                'kXOR_PathOp':                 PathKit.PathOp.XOR,
+                'kReverseDifference_SkPathOp': PathKit.PathOp.REVERSE_DIFFERENCE,
+            };
+            FILLTYPE_MAP = {
+                'kWinding_FillType':        PathKit.FillType.WINDING,
+                'kEvenOdd_FillType':        PathKit.FillType.EVENODD,
+                'kInverseWinding_FillType': PathKit.FillType.INVERSE_WINDING,
+                'kInverseEvenOdd_FillType': PathKit.FillType.INVERSE_EVENODD,
+            };
         }
-    });
+    }
 
     function getFillType(str) {
         let e = FILLTYPE_MAP[str];
@@ -119,6 +108,7 @@
 
     it('combines two paths with .op() and matches what we see from C++', function(done) {
         LoadPathKit.then(catchException(done, () => {
+            init();
             // Test JSON created with:
             // ./out/Clang/pathops_unittest -J ./modules/pathkit/tests/PathOpsOp.json -m PathOpsOp$
             fetch('/base/tests/PathOpsOp.json').then((r) => {
@@ -177,6 +167,7 @@
 
     it('simplifies a path with .simplify() and matches what we see from C++', function(done) {
         LoadPathKit.then(catchException(done, () => {
+            init();
             // Test JSON created with:
             // ./out/Clang/pathops_unittest -J ./modules/pathkit/tests/PathOpsSimplify.json -m PathOpsSimplify$
             fetch('/base/tests/PathOpsSimplify.json').then((r) => {
diff --git a/modules/pathkit/tests/svg.spec.js b/modules/pathkit/tests/svg.spec.js
index 02cfa4e..f1d75dd 100644
--- a/modules/pathkit/tests/svg.spec.js
+++ b/modules/pathkit/tests/svg.spec.js
@@ -1,20 +1,4 @@
-jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
 describe('PathKit\'s SVG Behavior', function() {
-    // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
-    var PathKit = null;
-    const LoadPathKit = new Promise(function(resolve, reject) {
-        if (PathKit) {
-            resolve();
-        } else {
-            PathKitInit({
-                locateFile: (file) => '/pathkit/'+file,
-            }).ready().then((_PathKit) => {
-                PathKit = _PathKit;
-                resolve();
-            });
-        }
-    });
-
     it('can create a path from an SVG string', function(done) {
         LoadPathKit.then(catchException(done, () => {
             //.This is a parallelagram from
diff --git a/modules/pathkit/tests/util.spec.js b/modules/pathkit/tests/util.spec.js
index ed65dc5..3e7f5c9 100644
--- a/modules/pathkit/tests/util.spec.js
+++ b/modules/pathkit/tests/util.spec.js
@@ -1,21 +1,5 @@
 // Tests for util-related things
-jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
 describe('PathKit\'s CubicMap Behavior', function() {
-    // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
-    var PathKit = null;
-    const LoadPathKit = new Promise(function(resolve, reject) {
-        if (PathKit) {
-            resolve();
-        } else {
-            PathKitInit({
-                locateFile: (file) => '/pathkit/'+file,
-            }).ready().then((_PathKit) => {
-                PathKit = _PathKit;
-                resolve();
-            });
-        }
-    });
-
     it('computes YFromX correctly', function(done) {
         LoadPathKit.then(catchException(done, () => {
             // Spot check a few points
diff --git a/modules/skottie/BUILD.gn b/modules/skottie/BUILD.gn
index ef105e5..8d921e8 100644
--- a/modules/skottie/BUILD.gn
+++ b/modules/skottie/BUILD.gn
@@ -3,19 +3,20 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("../../gn/skia.gni")
+
 declare_args() {
-  skia_enable_skottie = true
+  skia_enable_skottie = !(is_win && is_component_build)
 }
 
-config("public_config") {
-  if (skia_enable_skottie) {
+if (skia_enable_skottie) {
+  config("public_config") {
     defines = [ "SK_ENABLE_SKOTTIE" ]
     include_dirs = [ "include" ]
   }
-}
 
-source_set("skottie") {
-  if (skia_enable_skottie) {
+  component("skottie") {
+    check_includes = false
     import("skottie.gni")
     public_configs = [ ":public_config" ]
     public = skia_skottie_public
@@ -24,16 +25,16 @@
     deps = [
       "../..:skia",
       "../sksg",
+      "../skshaper",
     ]
   }
-}
 
-if (defined(is_skia_standalone)) {
-  config("utils_config") {
-    include_dirs = [ "utils" ]
-  }
-  source_set("utils") {
-    if (skia_enable_skottie) {
+  if (defined(is_skia_standalone)) {
+    config("utils_config") {
+      include_dirs = [ "utils" ]
+    }
+    source_set("utils") {
+      check_includes = false
       testonly = true
 
       public_configs = [ ":utils_config" ]
@@ -46,95 +47,105 @@
         "../..:skia",
       ]
     }
-  }
 
-  source_set("tests") {
-    if (skia_enable_skottie) {
-      testonly = true
+    if (skia_enable_tools) {
+      source_set("tests") {
+        testonly = true
 
-      configs += [
-        "../..:skia_private",
-        "../..:tests_config",
-      ]
-      sources = [
-        "src/SkottieTest.cpp",
-      ]
+        configs += [
+          "../..:skia_private",
+          "../..:tests_config",
+        ]
+        sources = [
+          "src/SkottieTest.cpp",
+        ]
 
-      deps = [
-        ":skottie",
-        "../..:gpu_tool_utils",
-        "../..:skia",
-      ]
+        deps = [
+          ":skottie",
+          "../..:gpu_tool_utils",
+          "../..:skia",
+        ]
+      }
+
+      source_set("fuzz") {
+        check_includes = false
+        testonly = true
+
+        configs += [ "../..:skia_private" ]
+        include_dirs = [
+          "../../tools",
+          "../../tools/flags",
+          "../../tools/fonts",
+        ]
+        sources = [
+          "../../tools/Resources.cpp",
+          "../../tools/fonts/TestFontMgr.cpp",
+          "../../tools/fonts/TestSVGTypeface.cpp",
+          "../../tools/fonts/TestTypeface.cpp",
+          "fuzz/FuzzSkottieJSON.cpp",
+        ]
+
+        deps = [
+          "../..:experimental_svg_model",
+          "../..:skia",
+        ]
+
+        public_deps = [
+          ":skottie",
+        ]
+      }
+
+      source_set("tool") {
+        check_includes = false
+        testonly = true
+
+        configs += [ "../..:skia_private" ]
+        sources = [
+          "src/SkottieTool.cpp",
+        ]
+
+        deps = [
+          "../..:flags",
+          "../..:skia",
+        ]
+
+        public_deps = [
+          ":skottie",
+          ":utils",
+        ]
+      }
+
+      source_set("gm") {
+        check_includes = false
+        testonly = true
+
+        # would be nice to have a gm_config
+        include_dirs = [ "../../gm" ]
+
+        configs += [ "../..:skia_private" ]
+        sources = [
+          "gm/SkottieGM.cpp",
+        ]
+
+        deps = [
+          ":skottie",
+          ":utils",
+          "../..:gpu_tool_utils",
+          "../..:skia",
+          "../..:tool_utils",
+        ]
+      }
     }
   }
-
-  source_set("fuzz") {
-    if (skia_enable_skottie) {
-      testonly = true
-
-      configs += [ "../..:skia_private" ]
-      include_dirs = [
-        "../../tools",
-        "../../tools/flags",
-        "../../tools/fonts",
-      ]
-      sources = [
-        "../../tools/Resources.cpp",
-        "../../tools/fonts/SkTestFontMgr.cpp",
-        "../../tools/fonts/SkTestSVGTypeface.cpp",
-        "../../tools/fonts/SkTestTypeface.cpp",
-        "fuzz/FuzzSkottieJSON.cpp",
-      ]
-
-      deps = [
-        "../..:experimental_svg_model",
-        "../..:skia",
-      ]
-
-      public_deps = [
-        ":skottie",
-      ]
-    }
+} else {
+  group("skottie") {
   }
-
-  source_set("tool") {
-    testonly = true
-
-    configs += [ "../..:skia_private" ]
-    sources = [
-      "src/SkottieTool.cpp",
-    ]
-
-    deps = [
-      "../..:flags",
-      "../..:skia",
-    ]
-
-    public_deps = [
-      ":skottie",
-      ":utils",
-    ]
+  group("fuzz") {
   }
-
-  source_set("gm") {
-    if (skia_enable_skottie) {
-      testonly = true
-
-      # would be nice to have a gm_config
-      include_dirs = [ "../../gm" ]
-
-      configs += [ "../..:skia_private" ]
-      sources = [
-        "gm/SkottieGM.cpp",
-      ]
-
-      deps = [
-        ":skottie",
-        ":utils",
-        "../..:gpu_tool_utils",
-        "../..:skia",
-        "../..:tool_utils",
-      ]
-    }
+  group("gm") {
+  }
+  group("tests") {
+  }
+  group("utils") {
   }
 }
diff --git a/modules/skottie/fuzz/FuzzSkottieJSON.cpp b/modules/skottie/fuzz/FuzzSkottieJSON.cpp
index 252fd8d..6c93bce 100644
--- a/modules/skottie/fuzz/FuzzSkottieJSON.cpp
+++ b/modules/skottie/fuzz/FuzzSkottieJSON.cpp
@@ -8,8 +8,8 @@
 #include "SkData.h"
 #include "SkFontMgrPriv.h"
 #include "SkStream.h"
-#include "SkTestFontMgr.h"
 #include "Skottie.h"
+#include "TestFontMgr.h"
 
 void FuzzSkottieJSON(sk_sp<SkData> bytes) {
     SkMemoryStream stream(bytes);
@@ -22,7 +22,7 @@
 
 #if defined(IS_FUZZING_WITH_LIBFUZZER)
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
-    gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
+    gSkFontMgr_DefaultFactory = &ToolUtils::MakePortableFontMgr;
     auto bytes = SkData::MakeWithoutCopy(data, size);
     FuzzSkottieJSON(bytes);
     return 0;
diff --git a/modules/skottie/gm/SkottieGM.cpp b/modules/skottie/gm/SkottieGM.cpp
index 09908b8..e63c39d 100644
--- a/modules/skottie/gm/SkottieGM.cpp
+++ b/modules/skottie/gm/SkottieGM.cpp
@@ -5,15 +5,15 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
+#include "AnimTimer.h"
 #include "Resources.h"
 #include "SkAnimCodecPlayer.h"
-#include "SkAnimTimer.h"
 #include "SkColor.h"
 #include "SkMakeUnique.h"
 #include "Skottie.h"
 #include "SkottieProperty.h"
 #include "SkottieUtils.h"
+#include "gm.h"
 
 #include <cmath>
 #include <vector>
@@ -61,17 +61,18 @@
         }
     }
 
-    void onDraw(SkCanvas* canvas) override {
+    DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
         if (!fAnimation) {
-            DrawFailureMessage(canvas, "No animation");
-            return;
+            *errorMsg = "No animation";
+            return DrawResult::kFail;
         }
 
         auto dest = SkRect::MakeWH(kSize, kSize);
         fAnimation->render(canvas, &dest);
+        return DrawResult::kOk;
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         if (!fAnimation) {
             return false;
         }
@@ -113,17 +114,18 @@
         }
     }
 
-    void onDraw(SkCanvas* canvas) override {
+    DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
         if (!fAnimation) {
-            DrawFailureMessage(canvas, "No animation");
-            return;
+            *errorMsg = "No animation";
+            return DrawResult::kFail;
         }
 
         auto dest = SkRect::MakeWH(kSize, kSize);
         fAnimation->render(canvas, &dest);
+        return DrawResult::kOk;
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         if (!fAnimation) {
             return false;
         }
@@ -185,17 +187,18 @@
         }
     }
 
-    void onDraw(SkCanvas* canvas) override {
+    DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
         if (!fAnimation) {
-            DrawFailureMessage(canvas, "No animation");
-            return;
+            *errorMsg = "No animation";
+            return DrawResult::kFail;
         }
 
         auto dest = SkRect::MakeWH(kSize, kSize);
         fAnimation->render(canvas, &dest);
+        return DrawResult::kOk;
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         if (!fAnimation) {
             return false;
         }
diff --git a/modules/skottie/include/Skottie.h b/modules/skottie/include/Skottie.h
index 15b175e..dfd7381 100644
--- a/modules/skottie/include/Skottie.h
+++ b/modules/skottie/include/Skottie.h
@@ -190,13 +190,23 @@
 
     ~Animation();
 
+    enum RenderFlag : uint32_t {
+        // When rendering into a known transparent buffer, clients can pass
+        // this flag to avoid some unnecessary compositing overhead for
+        // animations using layer blend modes.
+        kSkipTopLevelIsolation = 0x01,
+    };
+    using RenderFlags = uint32_t;
+
     /**
      * Draws the current animation frame.
      *
      * @param canvas   destination canvas
      * @param dst      optional destination rect
+     * @param flags    optional RenderFlags
      */
     void render(SkCanvas* canvas, const SkRect* dst = nullptr) const;
+    void render(SkCanvas* canvas, const SkRect* dst, RenderFlags) const;
 
     /**
      * Updates the animation state for |t|.
@@ -217,8 +227,12 @@
     void setShowInval(bool show);
 
 private:
+    enum Flags : uint32_t {
+        kRequiresTopLevelIsolation = 1 << 0, // Needs to draw into a layer due to layer blending.
+    };
+
     Animation(std::unique_ptr<sksg::Scene>, SkString ver, const SkSize& size,
-              SkScalar inPoint, SkScalar outPoint, SkScalar duration);
+              SkScalar inPoint, SkScalar outPoint, SkScalar duration, uint32_t flags = 0);
 
     std::unique_ptr<sksg::Scene> fScene;
     const SkString               fVersion;
@@ -226,6 +240,7 @@
     const SkScalar               fInPoint,
                                  fOutPoint,
                                  fDuration;
+    const uint32_t               fFlags;
 
     typedef SkNVRefCnt<Animation> INHERITED;
 };
diff --git a/modules/skottie/skottie.gni b/modules/skottie/skottie.gni
index 6cb5a38..ed95ee3 100644
--- a/modules/skottie/skottie.gni
+++ b/modules/skottie/skottie.gni
@@ -25,6 +25,7 @@
   "$_src/SkottiePrecompLayer.cpp",
   "$_src/SkottieProperty.cpp",
   "$_src/SkottieShapeLayer.cpp",
+  "$_src/SkottieShaper.cpp",
   "$_src/SkottieTextLayer.cpp",
   "$_src/SkottieValue.cpp",
   "$_src/SkottieValue.h",
diff --git a/modules/skottie/src/Skottie.cpp b/modules/skottie/src/Skottie.cpp
index 1720517..9441356 100644
--- a/modules/skottie/src/Skottie.cpp
+++ b/modules/skottie/src/Skottie.cpp
@@ -14,15 +14,15 @@
 #include "SkMakeUnique.h"
 #include "SkPaint.h"
 #include "SkPoint.h"
-#include "SkSGColor.h"
 #include "SkSGInvalidationController.h"
 #include "SkSGOpacityEffect.h"
+#include "SkSGPaint.h"
 #include "SkSGPath.h"
+#include "SkSGRenderEffect.h"
 #include "SkSGScene.h"
 #include "SkSGTransform.h"
 #include "SkStream.h"
 #include "SkTArray.h"
-#include "SkTime.h"
 #include "SkTo.h"
 #include "SkottieAdapter.h"
 #include "SkottieJson.h"
@@ -31,6 +31,7 @@
 #include "SkottieValue.h"
 #include "SkTraceEvent.h"
 
+#include <chrono>
 #include <cmath>
 
 #include "stdlib.h"
@@ -116,12 +117,15 @@
 
 sk_sp<sksg::Transform> AnimationBuilder::attachMatrix3D(const skjson::ObjectValue& t,
                                                         AnimatorScope* ascope,
-                                                        sk_sp<sksg::Transform> parent) const {
+                                                        sk_sp<sksg::Transform> parent,
+                                                        sk_sp<TransformAdapter3D> adapter) const {
     static const VectorValue g_default_vec_0   = {  0,   0,   0},
                              g_default_vec_100 = {100, 100, 100};
 
-    auto matrix = sksg::Matrix<SkMatrix44>::Make(SkMatrix::I());
-    auto adapter = sk_make_sp<TransformAdapter3D>(matrix);
+    if (!adapter) {
+        // Default to TransformAdapter3D (we only use external adapters for cameras).
+        adapter = sk_make_sp<TransformAdapter3D>();
+    }
 
     auto bound = this->bindProperty<VectorValue>(t["a"], ascope,
             [adapter](const VectorValue& a) {
@@ -164,7 +168,7 @@
     // TODO: dispatch 3D transform properties
 
     return (bound)
-        ? sksg::Transform::MakeConcat(std::move(parent), std::move(matrix))
+        ? sksg::Transform::MakeConcat(std::move(parent), adapter->refTransform())
         : parent;
 }
 
@@ -187,6 +191,52 @@
     return (bound || dispatched) ? std::move(opacityNode) : childNode;
 }
 
+namespace  {
+
+static SkBlendMode GetBlendMode(const skjson::ObjectValue& jobject,
+                                const AnimationBuilder* abuilder) {
+    static constexpr SkBlendMode kBlendModeMap[] = {
+        SkBlendMode::kSrcOver,    // 0:'normal'
+        SkBlendMode::kMultiply,   // 1:'multiply'
+        SkBlendMode::kScreen,     // 2:'screen'
+        SkBlendMode::kOverlay,    // 3:'overlay
+        SkBlendMode::kDarken,     // 4:'darken'
+        SkBlendMode::kLighten,    // 5:'lighten'
+        SkBlendMode::kColorDodge, // 6:'color-dodge'
+        SkBlendMode::kColorBurn,  // 7:'color-burn'
+        SkBlendMode::kHardLight,  // 8:'hard-light'
+        SkBlendMode::kSoftLight,  // 9:'soft-light'
+        SkBlendMode::kDifference, // 10:'difference'
+        SkBlendMode::kExclusion,  // 11:'exclusion'
+        SkBlendMode::kHue,        // 12:'hue'
+        SkBlendMode::kSaturation, // 13:'saturation'
+        SkBlendMode::kColor,      // 14:'color'
+        SkBlendMode::kLuminosity, // 15:'luminosity'
+    };
+
+    const auto bm_index = ParseDefault<size_t>(jobject["bm"], 0);
+    if (bm_index >= SK_ARRAY_COUNT(kBlendModeMap)) {
+            abuilder->log(Logger::Level::kWarning, &jobject,
+                          "Unsupported blend mode %lu\n", bm_index);
+            return SkBlendMode::kSrcOver;
+    }
+
+    return kBlendModeMap[bm_index];
+}
+
+} // namespace
+
+sk_sp<sksg::RenderNode> AnimationBuilder::attachBlendMode(const skjson::ObjectValue& jobject,
+                                                          sk_sp<sksg::RenderNode> child) const {
+    const auto bm = GetBlendMode(jobject, this);
+    if (bm != SkBlendMode::kSrcOver) {
+        fHasNontrivialBlending = true;
+        child = sksg::BlendModeEffect::Make(std::move(child), bm);
+    }
+
+    return child;
+}
+
 sk_sp<sksg::Path> AnimationBuilder::attachPath(const skjson::Value& jpath,
                                                AnimatorScope* ascope) const {
     auto path_node = sksg::Path::Make();
@@ -219,15 +269,17 @@
                                    sk_sp<PropertyObserver> pobserver, sk_sp<Logger> logger,
                                    sk_sp<MarkerObserver> mobserver,
                                    Animation::Builder::Stats* stats,
-                                   float duration, float framerate)
+                                   const SkSize& size, float duration, float framerate)
     : fResourceProvider(std::move(rp))
     , fLazyFontMgr(std::move(fontmgr))
     , fPropertyObserver(std::move(pobserver))
     , fLogger(std::move(logger))
     , fMarkerObserver(std::move(mobserver))
     , fStats(stats)
+    , fSize(size)
     , fDuration(duration)
-    , fFrameRate(framerate) {}
+    , fFrameRate(framerate)
+    , fHasNontrivialBlending(false) {}
 
 std::unique_ptr<sksg::Scene> AnimationBuilder::parse(const skjson::ObjectValue& jroot) {
     this->dispatchMarkers(jroot["markers"]);
@@ -411,7 +463,7 @@
     memset(&fStats, 0, sizeof(struct Stats));
 
     fStats.fJsonSize = data_len;
-    const auto t0 = SkTime::GetMSecs();
+    const auto t0 = std::chrono::steady_clock::now();
 
     const skjson::DOM dom(data, data_len);
     if (!dom.root().is<skjson::ObjectValue>()) {
@@ -423,8 +475,8 @@
     }
     const auto& json = dom.root().as<skjson::ObjectValue>();
 
-    const auto t1 = SkTime::GetMSecs();
-    fStats.fJsonParseTimeMS = t1 - t0;
+    const auto t1 = std::chrono::steady_clock::now();
+    fStats.fJsonParseTimeMS = std::chrono::duration<float, std::milli>{t1-t0}.count();
 
     const auto version  = ParseDefault<SkString>(json["v"], SkString());
     const auto size     = SkSize::Make(ParseDefault<float>(json["w"], 0.0f),
@@ -451,19 +503,29 @@
                                        std::move(fPropertyObserver),
                                        std::move(fLogger),
                                        std::move(fMarkerObserver),
-                                       &fStats, duration, fps);
+                                       &fStats, size, duration, fps);
     auto scene = builder.parse(json);
 
-    const auto t2 = SkTime::GetMSecs();
-    fStats.fSceneParseTimeMS = t2 - t1;
-    fStats.fTotalLoadTimeMS  = t2 - t0;
+    const auto t2 = std::chrono::steady_clock::now();
+    fStats.fSceneParseTimeMS = std::chrono::duration<float, std::milli>{t2-t1}.count();
+    fStats.fTotalLoadTimeMS  = std::chrono::duration<float, std::milli>{t2-t0}.count();
 
     if (!scene && fLogger) {
         fLogger->log(Logger::Level::kError, "Could not parse animation.\n");
     }
 
-    return sk_sp<Animation>(
-        new Animation(std::move(scene), std::move(version), size, inPoint, outPoint, duration));
+    uint32_t flags = 0;
+    if (builder.hasNontrivialBlending()) {
+        flags |= Flags::kRequiresTopLevelIsolation;
+    }
+
+    return sk_sp<Animation>(new Animation(std::move(scene),
+                                          std::move(version),
+                                          size,
+                                          inPoint,
+                                          outPoint,
+                                          duration,
+                                          flags));
 }
 
 sk_sp<Animation> Animation::Builder::makeFromFile(const char path[]) {
@@ -474,13 +536,14 @@
 }
 
 Animation::Animation(std::unique_ptr<sksg::Scene> scene, SkString version, const SkSize& size,
-                     SkScalar inPoint, SkScalar outPoint, SkScalar duration)
+                     SkScalar inPoint, SkScalar outPoint, SkScalar duration, uint32_t flags)
     : fScene(std::move(scene))
     , fVersion(std::move(version))
     , fSize(size)
     , fInPoint(inPoint)
     , fOutPoint(outPoint)
-    , fDuration(duration) {
+    , fDuration(duration)
+    , fFlags(flags) {
 
     // In case the client calls render before the first tick.
     this->seek(0);
@@ -495,17 +558,31 @@
 }
 
 void Animation::render(SkCanvas* canvas, const SkRect* dstR) const {
+    this->render(canvas, dstR, 0);
+}
+
+void Animation::render(SkCanvas* canvas, const SkRect* dstR, RenderFlags renderFlags) const {
     TRACE_EVENT0("skottie", TRACE_FUNC);
 
     if (!fScene)
         return;
 
     SkAutoCanvasRestore restore(canvas, true);
+
     const SkRect srcR = SkRect::MakeSize(this->size());
     if (dstR) {
         canvas->concat(SkMatrix::MakeRectToRect(srcR, *dstR, SkMatrix::kCenter_ScaleToFit));
     }
+
+    if ((fFlags & Flags::kRequiresTopLevelIsolation) &&
+        !(renderFlags & RenderFlag::kSkipTopLevelIsolation)) {
+        // The animation uses non-trivial blending, and needs
+        // to be rendered into a separate/transparent layer.
+        canvas->saveLayer(srcR, nullptr);
+    }
+
     canvas->clipRect(srcR);
+
     fScene->render(canvas);
 }
 
diff --git a/modules/skottie/src/SkottieAdapter.cpp b/modules/skottie/src/SkottieAdapter.cpp
index fed35b3..3fdda99 100644
--- a/modules/skottie/src/SkottieAdapter.cpp
+++ b/modules/skottie/src/SkottieAdapter.cpp
@@ -7,24 +7,26 @@
 
 #include "SkottieAdapter.h"
 
+#include "Sk3D.h"
 #include "SkFont.h"
 #include "SkMatrix.h"
 #include "SkMatrix44.h"
 #include "SkPath.h"
 #include "SkRRect.h"
-#include "SkSGColor.h"
+#include "SkSGColorFilter.h"
 #include "SkSGDraw.h"
 #include "SkSGGradient.h"
 #include "SkSGGroup.h"
+#include "SkSGPaint.h"
 #include "SkSGPath.h"
 #include "SkSGRect.h"
+#include "SkSGRenderEffect.h"
 #include "SkSGText.h"
 #include "SkSGTransform.h"
 #include "SkSGTrimEffect.h"
-#include "SkTextBlob.h"
-#include "SkTextUtils.h"
+#include "SkTableColorFilter.h"
 #include "SkTo.h"
-#include "SkUTF.h"
+#include "SkottieShaper.h"
 #include "SkottieValue.h"
 
 #include <cmath>
@@ -73,11 +75,15 @@
     fZ = v.size() > 2 ? v[2] : 0;
 }
 
-TransformAdapter3D::TransformAdapter3D(sk_sp<sksg::Matrix<SkMatrix44>> matrix)
-    : fMatrixNode(std::move(matrix)) {}
+TransformAdapter3D::TransformAdapter3D()
+    : fMatrixNode(sksg::Matrix<SkMatrix44>::Make(SkMatrix::I())) {}
 
 TransformAdapter3D::~TransformAdapter3D() = default;
 
+sk_sp<sksg::Transform> TransformAdapter3D::refTransform() const {
+    return fMatrixNode;
+}
+
 SkMatrix44 TransformAdapter3D::totalMatrix() const {
     SkMatrix44 t;
 
@@ -102,6 +108,62 @@
     fMatrixNode->setMatrix(this->totalMatrix());
 }
 
+CameraAdapter:: CameraAdapter(const SkSize& viewport_size)
+    : fViewportSize(viewport_size) {}
+
+CameraAdapter::~CameraAdapter() = default;
+
+SkMatrix44 CameraAdapter::totalMatrix() const {
+    // Camera parameters:
+    //
+    //   * location          -> position attribute
+    //   * point of interest -> anchor point attribute
+    //   * orientation       -> rotation attribute
+    //
+    // Note: the orientation is specified post position/POI adjustment.
+    //
+    SkPoint3 pos = { this->getPosition().fX,
+                     this->getPosition().fY,
+                    -this->getPosition().fZ },
+             poi = { this->getAnchorPoint().fX,
+                     this->getAnchorPoint().fY,
+                    -this->getAnchorPoint().fZ },
+              up = { 0, 1, 0 };
+
+    SkMatrix44 cam_t;
+    Sk3LookAt(&cam_t, pos, poi, up);
+
+    {
+        SkMatrix44 rot;
+        rot.setRotateDegreesAbout(1, 0, 0, this->getRotation().fX);
+        cam_t.postConcat(rot);
+        rot.setRotateDegreesAbout(0, 1, 0, this->getRotation().fY);
+        cam_t.postConcat(rot);
+        rot.setRotateDegreesAbout(0, 0, 1, this->getRotation().fZ);
+        cam_t.postConcat(rot);
+    }
+
+    // View parameters:
+    //
+    //   * size     -> composition size (TODO: AE seems to base it on width only?)
+    //   * distance -> "zoom" camera attribute
+    //
+    const auto view_size     = SkTMax(fViewportSize.width(), fViewportSize.height()),
+               view_distance = this->getZoom(),
+               view_angle    = std::atan(view_size * 0.5f / view_distance);
+
+    SkMatrix44 view_t;
+    Sk3Perspective(&view_t, 0, view_distance, 2 * view_angle);
+    view_t.postScale(view_size * 0.5f, view_size * 0.5f, 1);
+
+    SkMatrix44 t;
+    t.setTranslate(fViewportSize.width() * 0.5f, fViewportSize.height() * 0.5f, 0);
+    t.preConcat(view_t);
+    t.preConcat(cam_t);
+
+    return t;
+}
+
 RepeaterAdapter::RepeaterAdapter(sk_sp<sksg::RenderNode> repeater_node, Composite composite)
     : fRepeaterNode(repeater_node)
     , fComposite(composite)
@@ -226,6 +288,58 @@
     grad->setEndRadius(SkPoint::Distance(this->startPoint(), this->endPoint()));
 }
 
+GradientRampEffectAdapter::GradientRampEffectAdapter(sk_sp<sksg::RenderNode> child)
+    : fRoot(sksg::ShaderEffect::Make(std::move(child))) {}
+
+GradientRampEffectAdapter::~GradientRampEffectAdapter() = default;
+
+void GradientRampEffectAdapter::apply() {
+    // This adapter manages a SG fragment with the following structure:
+    //
+    // - ShaderEffect [fRoot]
+    //     \  GradientShader [fGradient]
+    //     \  child/wrapped fragment
+    //
+    // The gradient shader is updated based on the (animatable) intance type (linear/radial).
+
+    auto update_gradient = [this] (InstanceType new_type) {
+        if (new_type != fInstanceType) {
+            fGradient = new_type == InstanceType::kLinear
+                    ? sk_sp<sksg::Gradient>(sksg::LinearGradient::Make())
+                    : sk_sp<sksg::Gradient>(sksg::RadialGradient::Make());
+
+            fRoot->setShader(fGradient);
+            fInstanceType = new_type;
+        }
+
+        fGradient->setColorStops({ {0, fStartColor}, {1, fEndColor} });
+    };
+
+    static constexpr int kLinearShapeValue = 1;
+    const auto instance_type = (SkScalarRoundToInt(fShape) == kLinearShapeValue)
+            ? InstanceType::kLinear
+            : InstanceType::kRadial;
+
+    // Sync the gradient shader instance if needed.
+    update_gradient(instance_type);
+
+    // Sync instance-dependent gradient params.
+    if (instance_type == InstanceType::kLinear) {
+        auto* lg = static_cast<sksg::LinearGradient*>(fGradient.get());
+        lg->setStartPoint(fStartPoint);
+        lg->setEndPoint(fEndPoint);
+    } else {
+        SkASSERT(instance_type == InstanceType::kRadial);
+
+        auto* rg = static_cast<sksg::RadialGradient*>(fGradient.get());
+        rg->setStartCenter(fStartPoint);
+        rg->setEndCenter(fStartPoint);
+        rg->setEndRadius(SkPoint::Distance(fStartPoint, fEndPoint));
+    }
+
+    // TODO: blend, scatter
+}
+
 TrimEffectAdapter::TrimEffectAdapter(sk_sp<sksg::TrimEffect> trimEffect)
     : fTrimEffect(std::move(trimEffect)) {
     SkASSERT(fTrimEffect);
@@ -262,6 +376,160 @@
     fTrimEffect->setMode(mode);
 }
 
+DropShadowEffectAdapter::DropShadowEffectAdapter(sk_sp<sksg::DropShadowImageFilter> dropShadow)
+    : fDropShadow(std::move(dropShadow)) {
+    SkASSERT(fDropShadow);
+}
+
+DropShadowEffectAdapter::~DropShadowEffectAdapter() = default;
+
+void DropShadowEffectAdapter::apply() {
+    // fColor -> RGB, fOpacity -> A
+    fDropShadow->setColor(SkColorSetA(fColor, SkTPin(SkScalarRoundToInt(fOpacity), 0, 255)));
+
+    // The offset is specified in terms of a bearing angle + distance.
+    SkScalar rad = SkDegreesToRadians(90 - fDirection);
+    fDropShadow->setOffset(SkVector::Make( fDistance * SkScalarCos(rad),
+                                          -fDistance * SkScalarSin(rad)));
+
+    // Close enough to AE.
+    static constexpr SkScalar kSoftnessToSigmaFactor = 0.3f;
+    const auto sigma = fSoftness * kSoftnessToSigmaFactor;
+    fDropShadow->setSigma(SkVector::Make(sigma, sigma));
+
+    fDropShadow->setMode(fShadowOnly ? sksg::DropShadowImageFilter::Mode::kShadowOnly
+                                     : sksg::DropShadowImageFilter::Mode::kShadowAndForeground);
+}
+
+GaussianBlurEffectAdapter::GaussianBlurEffectAdapter(sk_sp<sksg::BlurImageFilter> blur)
+    : fBlur(std::move(blur)) {
+    SkASSERT(fBlur);
+}
+
+GaussianBlurEffectAdapter::~GaussianBlurEffectAdapter() = default;
+
+void GaussianBlurEffectAdapter::apply() {
+    static constexpr SkVector kDimensionsMap[] = {
+        { 1, 1 }, // 1 -> horizontal and vertical
+        { 1, 0 }, // 2 -> horizontal
+        { 0, 1 }, // 3 -> vertical
+    };
+
+    const auto dim_index = SkTPin<size_t>(static_cast<size_t>(fDimensions),
+                                          1, SK_ARRAY_COUNT(kDimensionsMap)) - 1;
+
+    // Close enough to AE.
+    static constexpr SkScalar kBlurrinessToSigmaFactor = 0.3f;
+    const auto sigma = fBlurriness * kBlurrinessToSigmaFactor;
+
+    fBlur->setSigma({ sigma * kDimensionsMap[dim_index].x(),
+                      sigma * kDimensionsMap[dim_index].y() });
+
+    static constexpr SkBlurImageFilter::TileMode kRepeatEdgeMap[] = {
+        SkBlurImageFilter::kClampToBlack_TileMode, // 0 -> repeat edge pixels: off
+        SkBlurImageFilter::       kClamp_TileMode, // 1 -> repeat edge pixels: on
+    };
+
+    const auto repeat_index = SkTPin<size_t>(static_cast<size_t>(fRepeatEdge),
+                                             0, SK_ARRAY_COUNT(kRepeatEdgeMap) - 1);
+    fBlur->setTileMode(kRepeatEdgeMap[repeat_index]);
+}
+
+
+// Levels color correction effect.
+//
+// Maps the selected channels from [inBlack...inWhite] to [outBlack, outWhite],
+// based on a gamma exponent.
+//
+// For [i0..i1] -> [o0..o1]:
+//
+//   c' = o0 + (o1 - o0) * ((c - i0) / (i1 - i0)) ^ G
+//
+// The output is optionally clipped to the output range.
+//
+// In/out intervals are clampped to [0..1].  Inversion is allowed.
+LevelsEffectAdapter::LevelsEffectAdapter(sk_sp<sksg::RenderNode> child)
+    : fEffect(sksg::ExternalColorFilter::Make(std::move(child))) {
+    SkASSERT(fEffect);
+}
+
+LevelsEffectAdapter::~LevelsEffectAdapter() = default;
+
+void LevelsEffectAdapter::apply() {
+    enum LottieChannel {
+        kRGB_Channel = 1,
+          kR_Channel = 2,
+          kG_Channel = 3,
+          kB_Channel = 4,
+          kA_Channel = 5,
+    };
+
+    const auto channel = SkScalarTruncToInt(fChannel);
+    if (channel < kRGB_Channel || channel > kA_Channel) {
+        fEffect->setColorFilter(nullptr);
+        return;
+    }
+
+    auto in_0 = SkTPin(fInBlack,  0.0f, 1.0f),
+         in_1 = SkTPin(fInWhite,  0.0f, 1.0f),
+        out_0 = SkTPin(fOutBlack, 0.0f, 1.0f),
+        out_1 = SkTPin(fOutWhite, 0.0f, 1.0f),
+            g = 1 / SkTMax(fGamma, 0.0f);
+
+    float clip[] = {0, 1};
+    const auto kLottieDoClip = 1;
+    if (SkScalarTruncToInt(fClipBlack) == kLottieDoClip) {
+        const auto idx = fOutBlack <= fOutWhite ? 0 : 1;
+        clip[idx] = out_0;
+    }
+    if (SkScalarTruncToInt(fClipWhite) == kLottieDoClip) {
+        const auto idx = fOutBlack <= fOutWhite ? 1 : 0;
+        clip[idx] = out_1;
+    }
+    SkASSERT(clip[0] <= clip[1]);
+
+    auto dIn  =  in_1 -  in_0,
+         dOut = out_1 - out_0;
+
+    if (SkScalarNearlyZero(dIn)) {
+        // Degenerate dIn == 0 makes the arithmetic below explode.
+        //
+        // We could specialize the builder to deal with that case, or we could just
+        // nudge by epsilon to make it all work.  The latter approach is simpler
+        // and doesn't have any noticeable downsides.
+        //
+        // Also nudge in_0 towards 0.5, in case it was sqashed against an extremity.
+        // This allows for some abrupt transition when the output interval is not
+        // collapsed, and produces results closer to AE.
+        static constexpr auto kEpsilon = 2 * SK_ScalarNearlyZero;
+        dIn  += std::copysign(kEpsilon, dIn);
+        in_0 += std::copysign(kEpsilon, .5f - in_0);
+        SkASSERT(!SkScalarNearlyZero(dIn));
+    }
+
+    uint8_t lut[256];
+
+    auto t =      -in_0 / dIn,
+        dT = 1 / 255.0f / dIn;
+
+    // TODO: is linear gamma common-enough to warrant a fast path?
+    for (size_t i = 0; i < 256; ++i) {
+        const auto out = out_0 + dOut * std::pow(std::max(t, 0.0f), g);
+        SkASSERT(!SkScalarIsNaN(out));
+
+        lut[i] = static_cast<uint8_t>(std::round(SkTPin(out, clip[0], clip[1]) * 255));
+
+        t += dT;
+    }
+
+    fEffect->setColorFilter(SkTableColorFilter::MakeARGB(
+        channel == kA_Channel                            ? lut : nullptr,
+        channel == kR_Channel || channel == kRGB_Channel ? lut : nullptr,
+        channel == kG_Channel || channel == kRGB_Channel ? lut : nullptr,
+        channel == kB_Channel || channel == kRGB_Channel ? lut : nullptr
+    ));
+}
+
 TextAdapter::TextAdapter(sk_sp<sksg::Group> root)
     : fRoot(std::move(root))
     , fTextNode(sksg::TextBlob::Make())
@@ -290,71 +558,36 @@
 
 TextAdapter::~TextAdapter() = default;
 
-sk_sp<SkTextBlob> TextAdapter::makeBlob() const {
-    SkFont font(fText.fTypeface, fText.fTextSize);
-    font.setHinting(kNo_SkFontHinting);
-    font.setSubpixel(true);
-    font.setEdging(SkFont::Edging::kAntiAlias);
-
-    const auto align_fract = [](SkTextUtils::Align align) {
-        switch (align) {
-        case SkTextUtils::kLeft_Align:   return  0.0f;
-        case SkTextUtils::kCenter_Align: return -0.5f;
-        case SkTextUtils::kRight_Align:  return -1.0f;
-        }
-        return 0.0f; // go home, msvc...
-    }(fText.fAlign);
-
-    const auto line_spacing = font.getSpacing();
-    float y_off             = 0;
-    SkSTArray<256, SkGlyphID, true> line_glyph_buffer;
-    SkTextBlobBuilder builder;
-
-    const auto& push_line = [&](const char* start, const char* end) {
-        if (end > start) {
-            const auto len   = SkToSizeT(end - start);
-            line_glyph_buffer.reset(font.countText(start, len, kUTF8_SkTextEncoding));
-            SkAssertResult(font.textToGlyphs(start, len, kUTF8_SkTextEncoding, line_glyph_buffer.data(),
-                    line_glyph_buffer.count())
-                           == line_glyph_buffer.count());
-
-            const auto x_off = align_fract != 0
-                    ? align_fract * font.measureText(start, len, kUTF8_SkTextEncoding)
-                    : 0;
-            const auto& buf  = builder.allocRun(font, line_glyph_buffer.count(), x_off, y_off);
-            if (!buf.glyphs) {
-                return;
-            }
-
-            memcpy(buf.glyphs, line_glyph_buffer.data(),
-                   SkToSizeT(line_glyph_buffer.count()) * sizeof(SkGlyphID));
-
-            y_off += line_spacing;
-        }
-    };
-
-    const auto& is_line_break = [](SkUnichar uch) {
-        // TODO: other explicit breaks?
-        return uch == '\r';
-    };
-
-    const char* ptr        = fText.fText.c_str();
-    const char* line_start = ptr;
-    const char* end        = ptr + fText.fText.size();
-
-    while (ptr < end) {
-        if (is_line_break(SkUTF::NextUTF8(&ptr, end))) {
-            push_line(line_start, ptr - 1);
-            line_start = ptr;
-        }
-    }
-    push_line(line_start, ptr);
-
-    return builder.make();
-}
-
 void TextAdapter::apply() {
-    fTextNode->setBlob(this->makeBlob());
+    const Shaper::TextDesc text_desc = {
+        fText.fTypeface,
+        fText.fTextSize,
+        fText.fHAlign,
+        fText.fVAlign,
+    };
+    const auto shape_result = Shaper::Shape(fText.fText, text_desc, fText.fBox);
+
+#if (0)
+    // Enable for text box debugging/visualization.
+    auto box_color = sksg::Color::Make(0xffff0000);
+    box_color->setStyle(SkPaint::kStroke_Style);
+    box_color->setStrokeWidth(1);
+    box_color->setAntiAlias(true);
+
+    auto bounds_color = sksg::Color::Make(0xff00ff00);
+    bounds_color->setStyle(SkPaint::kStroke_Style);
+    bounds_color->setStrokeWidth(1);
+    bounds_color->setAntiAlias(true);
+
+    fRoot->addChild(sksg::Draw::Make(sksg::Rect::Make(fText.fBox),
+                                     std::move(box_color)));
+    fRoot->addChild(sksg::Draw::Make(sksg::Rect::Make(shape_result.computeBounds()),
+                                     std::move(bounds_color)));
+#endif
+
+    fTextNode->setBlob(shape_result.fBlob);
+    fTextNode->setPosition(shape_result.fPos);
+
     fFillColor->setColor(fText.fFillColor);
     fStrokeColor->setColor(fText.fStrokeColor);
     fStrokeColor->setStrokeWidth(fText.fStrokeWidth);
diff --git a/modules/skottie/src/SkottieAdapter.h b/modules/skottie/src/SkottieAdapter.h
index 817ed66..b5e0cb9 100644
--- a/modules/skottie/src/SkottieAdapter.h
+++ b/modules/skottie/src/SkottieAdapter.h
@@ -15,8 +15,11 @@
 
 namespace sksg {
 
+class BlurImageFilter;
 class Color;
 class Draw;
+class DropShadowImageFilter;
+class ExternalColorFilter;
 class Gradient;
 class Group;
 class LinearGradient;
@@ -26,12 +29,18 @@
 class RadialGradient;
 class RenderNode;
 class RRect;
+class ShaderEffect;
 class TextBlob;
+class Transform;
 class TransformEffect;
 class TrimEffect;
 
 };
 
+namespace skjson {
+    class ObjectValue;
+}
+
 namespace skottie {
 
 #define ADAPTER_PROPERTY(p_name, p_type, p_default) \
@@ -106,10 +115,10 @@
     sk_sp<sksg::Matrix<SkMatrix>> fMatrixNode;
 };
 
-class TransformAdapter3D final : public SkNVRefCnt<TransformAdapter3D> {
+class TransformAdapter3D : public SkRefCnt {
 public:
-    explicit TransformAdapter3D(sk_sp<sksg::Matrix<SkMatrix44>>);
-    ~TransformAdapter3D();
+    TransformAdapter3D();
+    ~TransformAdapter3D() override;
 
     struct Vec3 {
         float fX, fY, fZ;
@@ -127,12 +136,32 @@
     ADAPTER_PROPERTY(Rotation   , Vec3, Vec3({  0,   0,   0}))
     ADAPTER_PROPERTY(Scale      , Vec3, Vec3({100, 100, 100}))
 
-    SkMatrix44 totalMatrix() const;
+    sk_sp<sksg::Transform> refTransform() const;
 
-private:
+protected:
     void apply();
 
+private:
+    virtual SkMatrix44 totalMatrix() const;
+
     sk_sp<sksg::Matrix<SkMatrix44>> fMatrixNode;
+
+    using INHERITED = SkRefCnt;
+};
+
+class CameraAdapter final : public TransformAdapter3D {
+public:
+    explicit CameraAdapter(const SkSize& viewport_size);
+    ~CameraAdapter() override;
+
+    ADAPTER_PROPERTY(Zoom, SkScalar, 0)
+
+private:
+    SkMatrix44 totalMatrix() const override;
+
+    const SkSize fViewportSize;
+
+    using INHERITED = TransformAdapter3D;
 };
 
 class RepeaterAdapter final : public SkNVRefCnt<RepeaterAdapter> {
@@ -206,6 +235,37 @@
     using INHERITED = GradientAdapter;
 };
 
+class GradientRampEffectAdapter final : public SkNVRefCnt<GradientRampEffectAdapter> {
+public:
+    explicit GradientRampEffectAdapter(sk_sp<sksg::RenderNode> child);
+    ~GradientRampEffectAdapter();
+
+    ADAPTER_PROPERTY(StartPoint, SkPoint , SkPoint::Make(0, 0))
+    ADAPTER_PROPERTY(EndPoint  , SkPoint , SkPoint::Make(0, 0))
+    ADAPTER_PROPERTY(StartColor, SkColor ,       SK_ColorBLACK)
+    ADAPTER_PROPERTY(EndColor  , SkColor ,       SK_ColorBLACK)
+    ADAPTER_PROPERTY(Blend     , SkScalar,                   0)
+    ADAPTER_PROPERTY(Scatter   , SkScalar,                   0)
+
+    // Really an enum: 1 -> linear, 7 -> radial (?!)
+    ADAPTER_PROPERTY(Shape     , SkScalar,                   0)
+
+    const sk_sp<sksg::ShaderEffect>& root() const { return fRoot; }
+
+private:
+    enum class InstanceType {
+        kNone,
+        kLinear,
+        kRadial,
+    };
+
+    void apply();
+
+    sk_sp<sksg::ShaderEffect> fRoot;
+    sk_sp<sksg::Gradient>     fGradient;
+    InstanceType              fInstanceType = InstanceType::kNone;
+};
+
 class TrimEffectAdapter final : public SkNVRefCnt<TrimEffectAdapter> {
 public:
     explicit TrimEffectAdapter(sk_sp<sksg::TrimEffect>);
@@ -221,6 +281,79 @@
     sk_sp<sksg::TrimEffect> fTrimEffect;
 };
 
+class DropShadowEffectAdapter final : public SkNVRefCnt<DropShadowEffectAdapter> {
+public:
+    explicit DropShadowEffectAdapter(sk_sp<sksg::DropShadowImageFilter>);
+    ~DropShadowEffectAdapter();
+
+    ADAPTER_PROPERTY(Color     , SkColor , SK_ColorBLACK)
+    ADAPTER_PROPERTY(Opacity   , SkScalar,           255)
+    ADAPTER_PROPERTY(Direction , SkScalar,             0)
+    ADAPTER_PROPERTY(Distance  , SkScalar,             0)
+    ADAPTER_PROPERTY(Softness  , SkScalar,             0)
+    ADAPTER_PROPERTY(ShadowOnly, bool    ,         false)
+
+private:
+    void apply();
+
+    const sk_sp<sksg::DropShadowImageFilter> fDropShadow;
+};
+
+class GaussianBlurEffectAdapter final : public SkNVRefCnt<GaussianBlurEffectAdapter> {
+public:
+    explicit GaussianBlurEffectAdapter(sk_sp<sksg::BlurImageFilter>);
+    ~GaussianBlurEffectAdapter();
+
+    // AE/BM model properties.  These are all animatable/interpolatable.
+
+    // Controls the blur sigma.
+    ADAPTER_PROPERTY(Blurriness, SkScalar, 0)
+
+    // Enum selecting the blur dimensionality:
+    //
+    //   1 -> horizontal & vertical
+    //   2 -> horizontal
+    //   3 -> vertical
+    //
+    ADAPTER_PROPERTY(Dimensions, SkScalar, 1)
+
+    // Enum selecting edge behavior:
+    //
+    //   0 -> clamp
+    //   1 -> repeat
+    //
+    ADAPTER_PROPERTY(RepeatEdge, SkScalar, 0)
+
+private:
+    void apply();
+
+    const sk_sp<sksg::BlurImageFilter> fBlur;
+};
+
+class LevelsEffectAdapter final : public SkNVRefCnt<LevelsEffectAdapter> {
+public:
+    explicit LevelsEffectAdapter(sk_sp<sksg::RenderNode> child);
+    ~LevelsEffectAdapter();
+
+    // 1: RGB, 2: R, 3: G, 4: B, 5: A
+    ADAPTER_PROPERTY(  Channel, SkScalar, 1)
+    ADAPTER_PROPERTY(  InBlack, SkScalar, 0)
+    ADAPTER_PROPERTY(  InWhite, SkScalar, 1)
+    ADAPTER_PROPERTY( OutBlack, SkScalar, 0)
+    ADAPTER_PROPERTY( OutWhite, SkScalar, 1)
+    ADAPTER_PROPERTY(    Gamma, SkScalar, 1)
+    // 1: clip, 2,3: don't clip
+    ADAPTER_PROPERTY(ClipBlack, SkScalar, 1)
+    ADAPTER_PROPERTY(ClipWhite, SkScalar, 1)
+
+    const sk_sp<sksg::ExternalColorFilter>& root() const { return fEffect; }
+
+private:
+    void apply();
+
+    sk_sp<sksg::ExternalColorFilter> fEffect;
+};
+
 class TextAdapter final : public SkNVRefCnt<TextAdapter> {
 public:
     explicit TextAdapter(sk_sp<sksg::Group> root);
@@ -232,7 +365,6 @@
 
 private:
     void apply();
-    sk_sp<SkTextBlob> makeBlob() const;
 
     sk_sp<sksg::Group>     fRoot;
     sk_sp<sksg::TextBlob>  fTextNode;
diff --git a/modules/skottie/src/SkottieAnimator.cpp b/modules/skottie/src/SkottieAnimator.cpp
index eb75212..189f421 100644
--- a/modules/skottie/src/SkottieAnimator.cpp
+++ b/modules/skottie/src/SkottieAnimator.cpp
@@ -57,12 +57,38 @@
 
         return rec.cmidx < 0
             ? lt
-            : SkTPin(fCubicMaps[rec.cmidx].computeYFromX(lt), 0.0f, 1.0f);
+            : fCubicMaps[rec.cmidx].computeYFromX(lt);
     }
 
     virtual int parseValue(const skjson::Value&, const AnimationBuilder* abuilder) = 0;
 
     void parseKeyFrames(const skjson::ArrayValue& jframes, const AnimationBuilder* abuilder) {
+        // Logically, a keyframe is defined as a (t0, t1, v0, v1) tuple: a given value
+        // is interpolated in the [v0..v1] interval over the [t0..t1] time span.
+        //
+        // There are three interestingly-different keyframe formats handled here.
+        //
+        // 1) Legacy keyframe format
+        //
+        //      - normal keyframes specify t0 ("t"), v0 ("s") and v1 ("e")
+        //      - last frame only specifies a t0
+        //      - t1[frame] == t0[frame + 1]
+        //      - the last entry (where we cannot determine t1) is ignored
+        //
+        // 2) Regular (new) keyframe format
+        //
+        //      - all keyframes specify t0 ("t") and v0 ("s")
+        //      - t1[frame] == t0[frame + 1]
+        //      - v1[frame] == v0[frame + 1]
+        //      - the last entry (where we cannot determine t1/v1) is ignored
+        //
+        // 3) Text value keyframe format
+        //
+        //      - similar to case #2, all keyframes specify t0 & v0
+        //      - unlike case #2, all keyframes are assumed to be constant (v1 == v0),
+        //        and the last frame is not discarded (its t1 is assumed -> inf)
+        //
+
         for (const skjson::ObjectValue* jframe : jframes) {
             if (!jframe) continue;
 
@@ -70,6 +96,9 @@
             if (!Parse<float>((*jframe)["t"], &t0))
                 continue;
 
+            const auto v0_idx = this->parseValue((*jframe)["s"], abuilder),
+                       v1_idx = this->parseValue((*jframe)["e"], abuilder);
+
             if (!fRecs.empty()) {
                 if (fRecs.back().t1 >= t0) {
                     abuilder->log(Logger::Level::kWarning, nullptr,
@@ -77,20 +106,25 @@
                                   t0, fRecs.back().t1);
                     continue;
                 }
-                // Back-fill t1 in prev interval.  Note: we do this even if we end up discarding
-                // the current interval (to support "t"-only final frames).
-                fRecs.back().t1 = t0;
+
+                // Back-fill t1 and v1 (if needed).
+                auto& prev = fRecs.back();
+                prev.t1 = t0;
+
+                // Previous keyframe did not specify an end value (case #2, #3).
+                if (prev.vidx1 < 0) {
+                    // If this frame has no v0, we're in case #3 (constant text value),
+                    // otherwise case #2 (v0 for current frame is the same as prev frame v1).
+                    prev.vidx1 = v0_idx < 0 ? prev.vidx0 : v0_idx;
+                }
             }
 
-            // Required start value.
-            const auto v0_idx = this->parseValue((*jframe)["s"], abuilder);
+            // Start value 's' is required.
             if (v0_idx < 0)
                 continue;
 
-            // Optional end value.
-            const auto v1_idx = this->parseValue((*jframe)["e"], abuilder);
-            if (v1_idx < 0) {
-                // Constant keyframe.
+            if ((v1_idx < 0) && ParseDefault((*jframe)["h"], false)) {
+                // Constant keyframe ("h": true).
                 fRecs.push_back({t0, t0, v0_idx, v0_idx, -1 });
                 continue;
             }
@@ -105,17 +139,26 @@
             if (c0 != kDefaultC0 || c1 != kDefaultC1) {
                 // TODO: is it worth de-duping these?
                 cm_idx = SkToInt(fCubicMaps.size());
-                fCubicMaps.emplace_back();
-                // TODO: why do we have to plug these inverted?
-                fCubicMaps.back().setPts(c1, c0);
+                fCubicMaps.emplace_back(c1, c0);
             }
 
             fRecs.push_back({t0, t0, v0_idx, v1_idx, cm_idx });
         }
 
-        // If we couldn't determine a valid t1 for the last frame, discard it.
-        if (!fRecs.empty() && !fRecs.back().isValid()) {
-            fRecs.pop_back();
+        if (!fRecs.empty()) {
+            auto& last = fRecs.back();
+
+            // If the last entry has only a v0, we're in case #3 - make it a constant frame.
+            if (last.vidx0 >= 0 && last.vidx1 < 0) {
+                last.vidx1 = last.vidx0;
+                last.t1 = last.t0;
+            }
+
+            // If we couldn't determine a valid t1 for the last frame, discard it
+            // (most likely the last frame entry for all 3 cases).
+            if (!last.isValid()) {
+                fRecs.pop_back();
+            }
         }
 
         fRecs.shrink_to_fit();
diff --git a/modules/skottie/src/SkottieLayer.cpp b/modules/skottie/src/SkottieLayer.cpp
index 3cc4122..9e69979 100644
--- a/modules/skottie/src/SkottieLayer.cpp
+++ b/modules/skottie/src/SkottieLayer.cpp
@@ -12,17 +12,18 @@
 #include "SkImage.h"
 #include "SkJSON.h"
 #include "SkMakeUnique.h"
+#include "SkottieAdapter.h"
 #include "SkottieJson.h"
 #include "SkottieValue.h"
 #include "SkParse.h"
 #include "SkSGClipEffect.h"
-#include "SkSGColor.h"
 #include "SkSGDraw.h"
 #include "SkSGGroup.h"
 #include "SkSGImage.h"
 #include "SkSGMaskEffect.h"
 #include "SkSGMerge.h"
 #include "SkSGOpacityEffect.h"
+#include "SkSGPaint.h"
 #include "SkSGPath.h"
 #include "SkSGRect.h"
 #include "SkSGTransform.h"
@@ -171,6 +172,8 @@
     return sksg::MaskEffect::Make(std::move(childNode), std::move(maskNode));
 }
 
+static constexpr int kCameraLayerType = 13;
+
 } // namespace
 
 sk_sp<sksg::RenderNode> AnimationBuilder::attachNestedAnimation(const char* name,
@@ -187,9 +190,13 @@
             return SkRect::MakeSize(fAnimation->size());
         }
 
+        const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; }
+
         void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
             const auto local_scope =
-                ScopedRenderContext(canvas, ctx).setIsolation(this->bounds(), true);
+                ScopedRenderContext(canvas, ctx).setIsolation(this->bounds(),
+                                                              canvas->getTotalMatrix(),
+                                                              true);
             fAnimation->render(canvas);
         }
 
@@ -288,8 +295,11 @@
 
     const SkColor color = 0xff000000 | c;
 
+    auto solid_paint = sksg::Color::Make(color);
+    solid_paint->setAntiAlias(true);
+
     return sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeSize(size)),
-                            sksg::Color::Make(color));
+                            std::move(solid_paint));
 }
 
 const AnimationBuilder::ImageAssetInfo*
@@ -335,6 +345,7 @@
     }
 
     auto image_node = sksg::Image::Make(image);
+    image_node->setQuality(kMedium_SkFilterQuality);
 
     if (asset_info->fAsset->isMultiFrame()) {
         class MultiFrameAnimator final : public sksg::Animator {
@@ -403,9 +414,13 @@
     AnimatorScope*                          fScope;
     SkTHashMap<int, sk_sp<sksg::Transform>> fLayerMatrixMap;
     sk_sp<sksg::RenderNode>                 fCurrentMatte;
+    sk_sp<sksg::Transform>                  fCameraTransform;
+
+    enum class TransformType { kLayer, kCamera };
 
     sk_sp<sksg::Transform> attachLayerTransform(const skjson::ObjectValue& jlayer,
-                                                const AnimationBuilder* abuilder) {
+                                                const AnimationBuilder* abuilder,
+                                                TransformType type = TransformType::kLayer) {
         const auto layer_index = ParseDefault<int>(jlayer["ind"], -1);
         if (layer_index < 0)
             return nullptr;
@@ -413,7 +428,7 @@
         if (auto* m = fLayerMatrixMap.find(layer_index))
             return *m;
 
-        return this->attachLayerTransformImpl(jlayer, abuilder, layer_index);
+        return this->attachLayerTransformImpl(jlayer, abuilder, type, layer_index);
     }
 
 private:
@@ -431,16 +446,47 @@
             if (!l) continue;
 
             if (ParseDefault<int>((*l)["ind"], -1) == parent_index) {
-                return this->attachLayerTransformImpl(*l, abuilder, parent_index);
+                const auto parent_type = ParseDefault<int>((*l)["ty"], -1) == kCameraLayerType
+                        ? TransformType::kCamera
+                        : TransformType::kLayer;
+                return this->attachLayerTransformImpl(*l, abuilder, parent_type, parent_index);
             }
         }
 
         return nullptr;
     }
 
+    sk_sp<sksg::Transform> attachTransformNode(const skjson::ObjectValue& jlayer,
+                                               const AnimationBuilder* abuilder,
+                                               sk_sp<sksg::Transform> parent_transform,
+                                               TransformType type) const {
+        const skjson::ObjectValue* jtransform = jlayer["ks"];
+        if (!jtransform) {
+            return nullptr;
+        }
+
+        if (type == TransformType::kCamera) {
+            auto camera_adapter = sk_make_sp<CameraAdapter>(abuilder->fSize);
+
+            abuilder->bindProperty<ScalarValue>(jlayer["pe"], fScope,
+                [camera_adapter] (const ScalarValue& pe) {
+                    // 'pe' (perspective?) corresponds to AE's "zoom" camera property.
+                    camera_adapter->setZoom(pe);
+                });
+
+            return abuilder->attachMatrix3D(*jtransform, fScope,
+                                            std::move(parent_transform),
+                                            std::move(camera_adapter));
+        }
+
+        return (ParseDefault<int>(jlayer["ddd"], 0) == 0)
+                ? abuilder->attachMatrix2D(*jtransform, fScope, std::move(parent_transform))
+                : abuilder->attachMatrix3D(*jtransform, fScope, std::move(parent_transform));
+    }
+
     sk_sp<sksg::Transform> attachLayerTransformImpl(const skjson::ObjectValue& jlayer,
                                                     const AnimationBuilder* abuilder,
-                                                    int layer_index) {
+                                                    TransformType type, int layer_index) {
         SkASSERT(!fLayerMatrixMap.find(layer_index));
 
         // Add a stub entry to break recursion cycles.
@@ -448,20 +494,19 @@
 
         auto parent_matrix = this->attachParentLayerTransform(jlayer, abuilder, layer_index);
 
-        if (const skjson::ObjectValue* jtransform = jlayer["ks"]) {
-            auto transform_node = (ParseDefault<int>(jlayer["ddd"], 0) == 0)
-                ? abuilder->attachMatrix2D(*jtransform, fScope, std::move(parent_matrix))
-                : abuilder->attachMatrix3D(*jtransform, fScope, std::move(parent_matrix));
-
-            return *fLayerMatrixMap.set(layer_index, std::move(transform_node));
-        }
-        return nullptr;
+        return *fLayerMatrixMap.set(layer_index, this->attachTransformNode(jlayer,
+                                                                           abuilder,
+                                                                           std::move(parent_matrix),
+                                                                           type));
     }
 };
 
 sk_sp<sksg::RenderNode> AnimationBuilder::attachLayer(const skjson::ObjectValue* jlayer,
-                                                     AttachLayerContext* layerCtx) const {
-    if (!jlayer) return nullptr;
+                                                      AttachLayerContext* layerCtx) const {
+    if (!jlayer || ParseDefault<bool>((*jlayer)["hd"], false)) {
+        // Ignore hidden layers.
+        return nullptr;
+    }
 
     const LayerInfo layer_info = {
         ParseDefault<float>((*jlayer)["ip"], 0.0f),
@@ -475,27 +520,50 @@
 
     const AutoPropertyTracker apt(this, *jlayer);
 
-    using LayerAttacher = sk_sp<sksg::RenderNode> (AnimationBuilder::*)(const skjson::ObjectValue&,
-                                                                        const LayerInfo&,
-                                                                        AnimatorScope*) const;
-    static constexpr LayerAttacher gLayerAttachers[] = {
-        &AnimationBuilder::attachPrecompLayer,  // 'ty': 0
-        &AnimationBuilder::attachSolidLayer,    // 'ty': 1
-        &AnimationBuilder::attachImageLayer,    // 'ty': 2
-        &AnimationBuilder::attachNullLayer,     // 'ty': 3
-        &AnimationBuilder::attachShapeLayer,    // 'ty': 4
-        &AnimationBuilder::attachTextLayer,     // 'ty': 5
+    using LayerBuilder = sk_sp<sksg::RenderNode> (AnimationBuilder::*)(const skjson::ObjectValue&,
+                                                                       const LayerInfo&,
+                                                                       AnimatorScope*) const;
+    enum : uint32_t {
+        kTransformEffects = 1, // The layer transform applies to its effects also.
     };
 
-    int type = ParseDefault<int>((*jlayer)["ty"], -1);
-    if (type < 0 || type >= SkTo<int>(SK_ARRAY_COUNT(gLayerAttachers))) {
+    static constexpr struct {
+        LayerBuilder fBuilder;
+        uint32_t     fFlags;
+    } gLayerBuildInfo[] = {
+        { &AnimationBuilder::attachPrecompLayer,                 0 },  // 'ty': 0 -> precomp
+        { &AnimationBuilder::attachSolidLayer  , kTransformEffects },  // 'ty': 1 -> solid
+        { &AnimationBuilder::attachImageLayer  ,                 0 },  // 'ty': 2 -> image
+        { &AnimationBuilder::attachNullLayer   ,                 0 },  // 'ty': 3 -> null
+        { &AnimationBuilder::attachShapeLayer  ,                 0 },  // 'ty': 4 -> shape
+        { &AnimationBuilder::attachTextLayer   ,                 0 },  // 'ty': 5 -> text
+    };
+
+    const auto type = ParseDefault<int>((*jlayer)["ty"], -1);
+
+    if (type == kCameraLayerType) {
+        // Camera layers are special: they don't build normal SG fragments, but drive a root-level
+        // transform.
+        if (layerCtx->fCameraTransform) {
+            this->log(Logger::Level::kWarning, jlayer, "Ignoring duplicate camera layer.");
+        } else {
+            layerCtx->fCameraTransform =
+                    layerCtx->attachLayerTransform(*jlayer, this,
+                                                   AttachLayerContext::TransformType::kCamera);
+        }
         return nullptr;
     }
 
+    if (type < 0 || type >= SkTo<int>(SK_ARRAY_COUNT(gLayerBuildInfo))) {
+        return nullptr;
+    }
+
+    const auto& build_info = gLayerBuildInfo[type];
+
     AnimatorScope layer_animators;
 
-    // Layer content.
-    auto layer = (this->*(gLayerAttachers[type]))(*jlayer, layer_info, &layer_animators);
+    // Build the layer content fragment.
+    auto layer = (this->*(build_info.fBuilder))(*jlayer, layer_info, &layer_animators);
 
     // Clip layers with explicit dimensions.
     float w = 0, h = 0;
@@ -509,7 +577,24 @@
     layer = AttachMask((*jlayer)["masksProperties"], this, &layer_animators, std::move(layer));
 
     // Optional layer transform.
-    if (auto layer_transform = layerCtx->attachLayerTransform(*jlayer, this)) {
+    auto layer_transform = layerCtx->attachLayerTransform(*jlayer, this);
+
+    // Does the transform apply to effects also?
+    // (AE quirk: it doesn't - except for solid layers)
+    const auto transform_effects = (build_info.fFlags & kTransformEffects);
+
+    // Attach the transform before effects, when needed.
+    if (layer_transform && !transform_effects) {
+        layer = sksg::TransformEffect::Make(std::move(layer), layer_transform);
+    }
+
+    // Optional layer effects.
+    if (const skjson::ArrayValue* jeffects = (*jlayer)["ef"]) {
+        layer = this->attachLayerEffects(*jeffects, &layer_animators, std::move(layer));
+    }
+
+    // Attach the transform after effects, when needed.
+    if (layer_transform && transform_effects) {
         layer = sksg::TransformEffect::Make(std::move(layer), std::move(layer_transform));
     }
 
@@ -519,10 +604,8 @@
         layer = this->attachOpacity(*jtransform, &layer_animators, std::move(layer));
     }
 
-    // Optional layer effects.
-    if (const skjson::ArrayValue* jeffects = (*jlayer)["ef"]) {
-        layer = this->attachLayerEffects(*jeffects, &layer_animators, std::move(layer));
-    }
+    // Optional blend mode.
+    layer = this->attachBlendMode(*jlayer, std::move(layer));
 
     class LayerController final : public sksg::GroupAnimator {
     public:
@@ -587,9 +670,9 @@
     return std::move(controller_node);
 }
 
-sk_sp<sksg::RenderNode> AnimationBuilder::attachComposition(const skjson::ObjectValue& comp,
+sk_sp<sksg::RenderNode> AnimationBuilder::attachComposition(const skjson::ObjectValue& jcomp,
                                                             AnimatorScope* scope) const {
-    const skjson::ArrayValue* jlayers = comp["layers"];
+    const skjson::ArrayValue* jlayers = jcomp["layers"];
     if (!jlayers) return nullptr;
 
     std::vector<sk_sp<sksg::RenderNode>> layers;
@@ -606,11 +689,22 @@
         return nullptr;
     }
 
-    // Layers are painted in bottom->top order.
-    std::reverse(layers.begin(), layers.end());
-    layers.shrink_to_fit();
+    sk_sp<sksg::RenderNode> comp;
+    if (layers.size() == 1) {
+        comp = std::move(layers[0]);
+    } else {
+        // Layers are painted in bottom->top order.
+        std::reverse(layers.begin(), layers.end());
+        layers.shrink_to_fit();
+        comp = sksg::Group::Make(std::move(layers));
+    }
 
-    return sksg::Group::Make(std::move(layers));
+    // Optional camera.
+    if (layerCtx.fCameraTransform) {
+        comp = sksg::TransformEffect::Make(std::move(comp), std::move(layerCtx.fCameraTransform));
+    }
+
+    return comp;
 }
 
 } // namespace internal
diff --git a/modules/skottie/src/SkottieLayerEffect.cpp b/modules/skottie/src/SkottieLayerEffect.cpp
index 16aa6a7..5cc3a65 100644
--- a/modules/skottie/src/SkottieLayerEffect.cpp
+++ b/modules/skottie/src/SkottieLayerEffect.cpp
@@ -8,24 +8,173 @@
 #include "SkottiePriv.h"
 
 #include "SkJSON.h"
+#include "SkottieAdapter.h"
 #include "SkottieJson.h"
 #include "SkottieValue.h"
-#include "SkSGColor.h"
 #include "SkSGColorFilter.h"
+#include "SkSGPaint.h"
+#include "SkSGRenderEffect.h"
 
 namespace skottie {
 namespace internal {
 
 namespace {
 
-sk_sp<sksg::RenderNode> AttachFillLayerEffect(const skjson::ArrayValue* jeffect_props,
+sk_sp<sksg::RenderNode> AttachGradientLayerEffect(const skjson::ArrayValue& jprops,
+                                                  const AnimationBuilder* abuilder,
+                                                  AnimatorScope* ascope,
+                                                  sk_sp<sksg::RenderNode> layer) {
+    enum : size_t {
+        kStartPoint_Index  = 0,
+        kStartColor_Index  = 1,
+        kEndPoint_Index    = 2,
+        kEndColor_Index    = 3,
+        kRampShape_Index   = 4,
+        kRampScatter_Index = 5,
+        kBlendRatio_Index  = 6,
+
+        kMax_Index        = kBlendRatio_Index,
+    };
+
+    if (jprops.size() <= kMax_Index) {
+        return nullptr;
+    }
+
+    const skjson::ObjectValue* p0 = jprops[ kStartPoint_Index];
+    const skjson::ObjectValue* p1 = jprops[   kEndPoint_Index];
+    const skjson::ObjectValue* c0 = jprops[ kStartColor_Index];
+    const skjson::ObjectValue* c1 = jprops[   kEndColor_Index];
+    const skjson::ObjectValue* sh = jprops[  kRampShape_Index];
+    const skjson::ObjectValue* bl = jprops[ kBlendRatio_Index];
+    const skjson::ObjectValue* sc = jprops[kRampScatter_Index];
+
+    if (!p0 || !p1 || !c0 || !c1 || !sh || !bl || !sc) {
+        return nullptr;
+    }
+
+    auto adapter = sk_make_sp<GradientRampEffectAdapter>(std::move(layer));
+
+    abuilder->bindProperty<VectorValue>((*p0)["v"], ascope,
+        [adapter](const VectorValue& p0) {
+            adapter->setStartPoint(ValueTraits<VectorValue>::As<SkPoint>(p0));
+        });
+    abuilder->bindProperty<VectorValue>((*p1)["v"], ascope,
+        [adapter](const VectorValue& p1) {
+            adapter->setEndPoint(ValueTraits<VectorValue>::As<SkPoint>(p1));
+        });
+    abuilder->bindProperty<VectorValue>((*c0)["v"], ascope,
+        [adapter](const VectorValue& c0) {
+            adapter->setStartColor(ValueTraits<VectorValue>::As<SkColor>(c0));
+        });
+    abuilder->bindProperty<VectorValue>((*c1)["v"], ascope,
+        [adapter](const VectorValue& c1) {
+            adapter->setEndColor(ValueTraits<VectorValue>::As<SkColor>(c1));
+        });
+    abuilder->bindProperty<ScalarValue>((*sh)["v"], ascope,
+        [adapter](const ScalarValue& shape) {
+            adapter->setShape(shape);
+        });
+    abuilder->bindProperty<ScalarValue>((*sh)["v"], ascope,
+        [adapter](const ScalarValue& blend) {
+            adapter->setBlend(blend);
+        });
+    abuilder->bindProperty<ScalarValue>((*sc)["v"], ascope,
+        [adapter](const ScalarValue& scatter) {
+            adapter->setScatter(scatter);
+        });
+
+    return adapter->root();
+}
+
+sk_sp<sksg::RenderNode> AttachTintLayerEffect(const skjson::ArrayValue& jprops,
                                               const AnimationBuilder* abuilder,
                                               AnimatorScope* ascope,
                                               sk_sp<sksg::RenderNode> layer) {
-    if (!jeffect_props) return nullptr;
+    enum : size_t {
+        kMapBlackTo_Index = 0,
+        kMapWhiteTo_Index = 1,
+        kAmount_Index     = 2,
+        // kOpacity_Index    = 3, // currently unused (not exported)
 
-    // Effect properties are index-based.
-    enum {
+        kMax_Index        = kAmount_Index,
+    };
+
+    if (jprops.size() <= kMax_Index) {
+        return nullptr;
+    }
+
+    const skjson::ObjectValue* color0_prop = jprops[kMapBlackTo_Index];
+    const skjson::ObjectValue* color1_prop = jprops[kMapWhiteTo_Index];
+    const skjson::ObjectValue* amount_prop = jprops[    kAmount_Index];
+
+    if (!color0_prop || !color1_prop || !amount_prop) {
+        return nullptr;
+    }
+
+    auto tint_node =
+            sksg::GradientColorFilter::Make(std::move(layer),
+                                            abuilder->attachColor(*color0_prop, ascope, "v"),
+                                            abuilder->attachColor(*color1_prop, ascope, "v"));
+    if (!tint_node) {
+        return nullptr;
+    }
+
+    abuilder->bindProperty<ScalarValue>((*amount_prop)["v"], ascope,
+        [tint_node](const ScalarValue& w) {
+            tint_node->setWeight(w / 100); // 100-based
+        });
+
+    return std::move(tint_node);
+}
+
+sk_sp<sksg::RenderNode> AttachTritoneLayerEffect(const skjson::ArrayValue& jprops,
+                                                 const AnimationBuilder* abuilder,
+                                                 AnimatorScope* ascope,
+                                                 sk_sp<sksg::RenderNode> layer) {
+    enum : size_t {
+        kHiColor_Index     = 0,
+        kMiColor_Index     = 1,
+        kLoColor_Index     = 2,
+        kBlendAmount_Index = 3,
+
+        kMax_Index         = kBlendAmount_Index,
+    };
+
+    if (jprops.size() <= kMax_Index) {
+        return nullptr;
+    }
+
+    const skjson::ObjectValue* hicolor_prop = jprops[    kHiColor_Index];
+    const skjson::ObjectValue* micolor_prop = jprops[    kMiColor_Index];
+    const skjson::ObjectValue* locolor_prop = jprops[    kLoColor_Index];
+    const skjson::ObjectValue*   blend_prop = jprops[kBlendAmount_Index];
+
+    if (!hicolor_prop || !micolor_prop || !locolor_prop || !blend_prop) {
+        return nullptr;
+    }
+
+    auto tritone_node =
+            sksg::GradientColorFilter::Make(std::move(layer), {
+                                            abuilder->attachColor(*locolor_prop, ascope, "v"),
+                                            abuilder->attachColor(*micolor_prop, ascope, "v"),
+                                            abuilder->attachColor(*hicolor_prop, ascope, "v") });
+    if (!tritone_node) {
+        return nullptr;
+    }
+
+    abuilder->bindProperty<ScalarValue>((*blend_prop)["v"], ascope,
+        [tritone_node](const ScalarValue& w) {
+            tritone_node->setWeight((100 - w) / 100); // 100-based, inverted (!?).
+        });
+
+    return std::move(tritone_node);
+}
+
+sk_sp<sksg::RenderNode> AttachFillLayerEffect(const skjson::ArrayValue& jprops,
+                                              const AnimationBuilder* abuilder,
+                                              AnimatorScope* ascope,
+                                              sk_sp<sksg::RenderNode> layer) {
+    enum : size_t {
         kFillMask_Index = 0,
         kAllMasks_Index = 1,
         kColor_Index    = 2,
@@ -37,12 +186,12 @@
         kMax_Index      = kOpacity_Index,
     };
 
-    if (jeffect_props->size() <= kMax_Index) {
+    if (jprops.size() <= kMax_Index) {
         return nullptr;
     }
 
-    const skjson::ObjectValue*   color_prop = (*jeffect_props)[  kColor_Index];
-    const skjson::ObjectValue* opacity_prop = (*jeffect_props)[kOpacity_Index];
+    const skjson::ObjectValue*   color_prop = jprops[  kColor_Index];
+    const skjson::ObjectValue* opacity_prop = jprops[kOpacity_Index];
     if (!color_prop || !opacity_prop) {
         return nullptr;
     }
@@ -59,28 +208,267 @@
             color_node->setColor(SkColorSetA(c, a));
         });
 
-    return sksg::ColorModeFilter::Make(std::move(layer),
+    return sksg::ModeColorFilter::Make(std::move(layer),
                                        std::move(color_node),
                                        SkBlendMode::kSrcIn);
 }
 
+sk_sp<sksg::RenderNode> AttachDropShadowLayerEffect(const skjson::ArrayValue& jprops,
+                                                    const AnimationBuilder* abuilder,
+                                                    AnimatorScope* ascope,
+                                                    sk_sp<sksg::RenderNode> layer) {
+    enum : size_t {
+        kShadowColor_Index = 0,
+        kOpacity_Index     = 1,
+        kDirection_Index   = 2,
+        kDistance_Index    = 3,
+        kSoftness_Index    = 4,
+        kShadowOnly_Index  = 5,
+
+        kMax_Index         = kShadowOnly_Index,
+    };
+
+    if (jprops.size() <= kMax_Index) {
+        return nullptr;
+    }
+
+    const skjson::ObjectValue*       color_prop = jprops[kShadowColor_Index];
+    const skjson::ObjectValue*     opacity_prop = jprops[    kOpacity_Index];
+    const skjson::ObjectValue*   direction_prop = jprops[  kDirection_Index];
+    const skjson::ObjectValue*    distance_prop = jprops[   kDistance_Index];
+    const skjson::ObjectValue*    softness_prop = jprops[   kSoftness_Index];
+    const skjson::ObjectValue* shadow_only_prop = jprops[ kShadowOnly_Index];
+
+    if (!color_prop ||
+        !opacity_prop ||
+        !direction_prop ||
+        !distance_prop ||
+        !softness_prop ||
+        !shadow_only_prop) {
+        return nullptr;
+    }
+
+    auto shadow_effect  = sksg::DropShadowImageFilter::Make();
+    auto shadow_adapter = sk_make_sp<DropShadowEffectAdapter>(shadow_effect);
+
+    abuilder->bindProperty<VectorValue>((*color_prop)["v"], ascope,
+        [shadow_adapter](const VectorValue& c) {
+            shadow_adapter->setColor(ValueTraits<VectorValue>::As<SkColor>(c));
+        });
+    abuilder->bindProperty<ScalarValue>((*opacity_prop)["v"], ascope,
+        [shadow_adapter](const ScalarValue& o) {
+            shadow_adapter->setOpacity(o);
+        });
+    abuilder->bindProperty<ScalarValue>((*direction_prop)["v"], ascope,
+        [shadow_adapter](const ScalarValue& d) {
+            shadow_adapter->setDirection(d);
+        });
+    abuilder->bindProperty<ScalarValue>((*distance_prop)["v"], ascope,
+        [shadow_adapter](const ScalarValue& d) {
+            shadow_adapter->setDistance(d);
+        });
+    abuilder->bindProperty<ScalarValue>((*softness_prop)["v"], ascope,
+        [shadow_adapter](const ScalarValue& s) {
+            shadow_adapter->setSoftness(s);
+        });
+    abuilder->bindProperty<ScalarValue>((*shadow_only_prop)["v"], ascope,
+        [shadow_adapter](const ScalarValue& s) {
+            shadow_adapter->setShadowOnly(SkToBool(s));
+        });
+
+    return sksg::ImageFilterEffect::Make(std::move(layer), std::move(shadow_effect));
+}
+
+sk_sp<sksg::RenderNode> AttachGaussianBlurLayerEffect(const skjson::ArrayValue& jprops,
+                                                      const AnimationBuilder* abuilder,
+                                                      AnimatorScope* ascope,
+                                                      sk_sp<sksg::RenderNode> layer) {
+    enum : size_t {
+        kBlurriness_Index = 0,
+        kDimensions_Index = 1,
+        kRepeatEdge_Index = 2,
+
+        kMax_Index        = kRepeatEdge_Index,
+    };
+
+    if (jprops.size() <= kMax_Index) {
+        return nullptr;
+    }
+
+    const skjson::ObjectValue* blurriness_prop = jprops[kBlurriness_Index];
+    const skjson::ObjectValue* dimensions_prop = jprops[kDimensions_Index];
+    const skjson::ObjectValue* repeatedge_prop = jprops[kRepeatEdge_Index];
+
+    if (!blurriness_prop || !dimensions_prop || !repeatedge_prop) {
+        return nullptr;
+    }
+
+    auto blur_effect   = sksg::BlurImageFilter::Make();
+    auto blur_addapter = sk_make_sp<GaussianBlurEffectAdapter>(blur_effect);
+
+    abuilder->bindProperty<ScalarValue>((*blurriness_prop)["v"], ascope,
+        [blur_addapter](const ScalarValue& b) {
+            blur_addapter->setBlurriness(b);
+        });
+    abuilder->bindProperty<ScalarValue>((*dimensions_prop)["v"], ascope,
+        [blur_addapter](const ScalarValue& d) {
+            blur_addapter->setDimensions(d);
+        });
+    abuilder->bindProperty<ScalarValue>((*repeatedge_prop)["v"], ascope,
+        [blur_addapter](const ScalarValue& r) {
+            blur_addapter->setRepeatEdge(r);
+        });
+
+    return sksg::ImageFilterEffect::Make(std::move(layer), std::move(blur_effect));
+}
+
+sk_sp<sksg::RenderNode> AttachLevelsLayerEffect(const skjson::ArrayValue& jprops,
+                                                const AnimationBuilder* abuilder,
+                                                AnimatorScope* ascope,
+                                                sk_sp<sksg::RenderNode> layer) {
+    enum : size_t {
+        kChannel_Index        = 0,
+        // ???                = 1,
+        kInputBlack_Index     = 2,
+        kInputWhite_Index     = 3,
+        kGamma_Index          = 4,
+        kOutputBlack_Index    = 5,
+        kOutputWhite_Index    = 6,
+        kClipToOutBlack_Index = 7,
+        kClipToOutWhite_Index = 8,
+
+        kMax_Index        = kClipToOutWhite_Index,
+    };
+
+    if (jprops.size() <= kMax_Index) {
+        return nullptr;
+    }
+
+    const skjson::ObjectValue*    channel_prop = jprops[       kChannel_Index];
+    const skjson::ObjectValue*     iblack_prop = jprops[    kInputBlack_Index];
+    const skjson::ObjectValue*     iwhite_prop = jprops[    kInputWhite_Index];
+    const skjson::ObjectValue*      gamma_prop = jprops[         kGamma_Index];
+    const skjson::ObjectValue*     oblack_prop = jprops[   kOutputBlack_Index];
+    const skjson::ObjectValue*     owhite_prop = jprops[   kOutputWhite_Index];
+    const skjson::ObjectValue* clip_black_prop = jprops[kClipToOutBlack_Index];
+    const skjson::ObjectValue* clip_white_prop = jprops[kClipToOutWhite_Index];
+
+    if (!channel_prop || !iblack_prop || !iwhite_prop || !gamma_prop ||
+        !oblack_prop || !owhite_prop || !clip_black_prop || !clip_white_prop) {
+        return nullptr;
+    }
+
+    auto adapter = sk_make_sp<LevelsEffectAdapter>(std::move(layer));
+
+    abuilder->bindProperty<ScalarValue>((*channel_prop)["v"], ascope,
+        [adapter](const ScalarValue& channel) {
+            adapter->setChannel(channel);
+        });
+    abuilder->bindProperty<ScalarValue>((*iblack_prop)["v"], ascope,
+        [adapter](const ScalarValue& ib) {
+            adapter->setInBlack(ib);
+        });
+    abuilder->bindProperty<ScalarValue>((*iwhite_prop)["v"], ascope,
+        [adapter](const ScalarValue& iw) {
+            adapter->setInWhite(iw);
+        });
+    abuilder->bindProperty<ScalarValue>((*oblack_prop)["v"], ascope,
+        [adapter](const ScalarValue& ob) {
+            adapter->setOutBlack(ob);
+        });
+    abuilder->bindProperty<ScalarValue>((*owhite_prop)["v"], ascope,
+        [adapter](const ScalarValue& ow) {
+            adapter->setOutWhite(ow);
+        });
+    abuilder->bindProperty<ScalarValue>((*gamma_prop)["v"], ascope,
+        [adapter](const ScalarValue& g) {
+            adapter->setGamma(g);
+        });
+
+    abuilder->bindProperty<ScalarValue>((*clip_black_prop)["v"], ascope,
+        [adapter](const ScalarValue& cb) {
+            adapter->setClipBlack(cb);
+        });
+    abuilder->bindProperty<ScalarValue>((*clip_white_prop)["v"], ascope,
+        [adapter](const ScalarValue& cw) {
+            adapter->setClipWhite(cw);
+        });
+
+    return adapter->root();
+}
+
+using EffectBuilderT = sk_sp<sksg::RenderNode> (*)(const skjson::ArrayValue&,
+                                                   const AnimationBuilder*,
+                                                   AnimatorScope*,
+                                                   sk_sp<sksg::RenderNode>);
+
+EffectBuilderT FindEffectBuilder(const AnimationBuilder* abuilder,
+                                 const skjson::ObjectValue& jeffect) {
+    // First, try assigned types.
+    enum : int32_t {
+        kTint_Effect         = 20,
+        kFill_Effect         = 21,
+        kTritone_Effect      = 23,
+        kDropShadow_Effect   = 25,
+        kGaussianBlur_Effect = 29,
+    };
+
+    const auto ty = ParseDefault<int>(jeffect["ty"], -1);
+
+    switch (ty) {
+    case kTint_Effect:
+        return AttachTintLayerEffect;
+    case kFill_Effect:
+        return AttachFillLayerEffect;
+    case kTritone_Effect:
+        return AttachTritoneLayerEffect;
+    case kDropShadow_Effect:
+        return AttachDropShadowLayerEffect;
+    case kGaussianBlur_Effect:
+        return AttachGaussianBlurLayerEffect;
+    default:
+        break;
+    }
+
+    // Some effects don't have an assigned type, but the data is still present.
+    // Try a name-based lookup.
+
+    if (const skjson::StringValue* mn = jeffect["mn"]) {
+        if (!strcmp(mn->begin(), "ADBE Ramp")) {
+            return AttachGradientLayerEffect;
+        }
+        if (!strcmp(mn->begin(), "ADBE Easy Levels2")) {
+            return AttachLevelsLayerEffect;
+        }
+    }
+
+    abuilder->log(Logger::Level::kWarning, nullptr, "Unsupported layer effect type: %d.", ty);
+
+    return nullptr;
+}
+
 } // namespace
 
 sk_sp<sksg::RenderNode> AnimationBuilder::attachLayerEffects(const skjson::ArrayValue& jeffects,
                                                              AnimatorScope* ascope,
                                                              sk_sp<sksg::RenderNode> layer) const {
-    for (const skjson::ObjectValue* jeffect : jeffects) {
-        if (!jeffect) continue;
+    if (!layer) {
+        return nullptr;
+    }
 
-        switch (const auto ty = ParseDefault<int>((*jeffect)["ty"], -1)) {
-        case 21: // Fill
-            layer = AttachFillLayerEffect((*jeffect)["ef"], this, ascope, std::move(layer));
-            break;
-        default:
-            this->log(Logger::Level::kWarning, nullptr, "Unsupported layer effect type: %d.", ty);
-            break;
+    for (const skjson::ObjectValue* jeffect : jeffects) {
+        if (!jeffect) {
+            continue;
         }
 
+        const auto builder = FindEffectBuilder(this, *jeffect);
+        const skjson::ArrayValue* jprops = (*jeffect)["ef"];
+        if (!builder || !jprops) {
+            continue;
+        }
+
+        layer = builder(*jprops, this, ascope, std::move(layer));
+
         if (!layer) {
             this->log(Logger::Level::kError, jeffect, "Invalid layer effect.");
             return nullptr;
diff --git a/modules/skottie/src/SkottiePriv.h b/modules/skottie/src/SkottiePriv.h
index 659d222..979f694 100644
--- a/modules/skottie/src/SkottiePriv.h
+++ b/modules/skottie/src/SkottiePriv.h
@@ -37,6 +37,9 @@
 
 namespace skottie {
 
+class TransformAdapter2D;
+class TransformAdapter3D;
+
 namespace internal {
 
 using AnimatorScope = sksg::AnimatorList;
@@ -45,7 +48,8 @@
 public:
     AnimationBuilder(sk_sp<ResourceProvider>, sk_sp<SkFontMgr>, sk_sp<PropertyObserver>,
                      sk_sp<Logger>, sk_sp<MarkerObserver>,
-                     Animation::Builder::Stats*, float duration, float framerate);
+                     Animation::Builder::Stats*, const SkSize& size,
+                     float duration, float framerate);
 
     std::unique_ptr<sksg::Scene> parse(const skjson::ObjectValue&);
 
@@ -74,11 +78,14 @@
     sk_sp<sksg::Transform> attachMatrix2D(const skjson::ObjectValue&, AnimatorScope*,
                                           sk_sp<sksg::Transform>) const;
     sk_sp<sksg::Transform> attachMatrix3D(const skjson::ObjectValue&, AnimatorScope*,
-                                          sk_sp<sksg::Transform>) const;
+                                          sk_sp<sksg::Transform>,
+                                          sk_sp<TransformAdapter3D> = nullptr) const;
     sk_sp<sksg::RenderNode> attachOpacity(const skjson::ObjectValue&, AnimatorScope*,
                                       sk_sp<sksg::RenderNode>) const;
     sk_sp<sksg::Path> attachPath(const skjson::Value&, AnimatorScope*) const;
 
+    bool hasNontrivialBlending() const { return fHasNontrivialBlending; }
+
 private:
     struct AttachLayerContext;
     struct AttachShapeContext;
@@ -96,6 +103,9 @@
     sk_sp<sksg::RenderNode> attachLayerEffects(const skjson::ArrayValue& jeffects, AnimatorScope*,
                                                sk_sp<sksg::RenderNode>) const;
 
+    sk_sp<sksg::RenderNode> attachBlendMode(const skjson::ObjectValue&,
+                                            sk_sp<sksg::RenderNode>) const;
+
     sk_sp<sksg::RenderNode> attachShape(const skjson::ArrayValue*, AttachShapeContext*) const;
     sk_sp<sksg::RenderNode> attachAssetRef(const skjson::ObjectValue&, AnimatorScope*,
         const std::function<sk_sp<sksg::RenderNode>(const skjson::ObjectValue&,
@@ -169,10 +179,12 @@
     sk_sp<Logger>              fLogger;
     sk_sp<MarkerObserver>      fMarkerObserver;
     Animation::Builder::Stats* fStats;
+    const SkSize               fSize;
     const float                fDuration,
                                fFrameRate;
-
     mutable const char*        fPropertyObserverContext;
+    mutable bool               fHasNontrivialBlending : 1;
+
 
     struct LayerInfo {
         float fInPoint,
diff --git a/modules/skottie/src/SkottieProperty.cpp b/modules/skottie/src/SkottieProperty.cpp
index 143b97a6..c5d7ed4 100644
--- a/modules/skottie/src/SkottieProperty.cpp
+++ b/modules/skottie/src/SkottieProperty.cpp
@@ -8,8 +8,8 @@
 #include "SkottieProperty.h"
 
 #include "SkottieAdapter.h"
-#include "SkSGColor.h"
 #include "SkSGOpacityEffect.h"
+#include "SkSGPaint.h"
 
 namespace skottie {
 
diff --git a/modules/skottie/src/SkottieShapeLayer.cpp b/modules/skottie/src/SkottieShapeLayer.cpp
index 48e20d2..63c6c8f 100644
--- a/modules/skottie/src/SkottieShapeLayer.cpp
+++ b/modules/skottie/src/SkottieShapeLayer.cpp
@@ -12,14 +12,15 @@
 #include "SkottieJson.h"
 #include "SkottieValue.h"
 #include "SkPath.h"
-#include "SkSGColor.h"
 #include "SkSGDraw.h"
 #include "SkSGGeometryTransform.h"
 #include "SkSGGradient.h"
 #include "SkSGGroup.h"
 #include "SkSGMerge.h"
+#include "SkSGPaint.h"
 #include "SkSGPath.h"
 #include "SkSGRect.h"
+#include "SkSGRenderEffect.h"
 #include "SkSGRoundEffect.h"
 #include "SkSGTransform.h"
 #include "SkSGTrimEffect.h"
@@ -145,8 +146,8 @@
     return std::move(path_node);
 }
 
-sk_sp<sksg::Gradient> AttachGradient(const skjson::ObjectValue& jgrad,
-                                     const AnimationBuilder* abuilder, AnimatorScope* ascope) {
+sk_sp<sksg::ShaderPaint> AttachGradient(const skjson::ObjectValue& jgrad,
+                                        const AnimationBuilder* abuilder, AnimatorScope* ascope) {
     const skjson::ObjectValue* stops = jgrad["g"];
     if (!stops)
         return nullptr;
@@ -183,7 +184,7 @@
             adapter->setEndPoint(ValueTraits<VectorValue>::As<SkPoint>(e));
         });
 
-    return gradient_node;
+    return sksg::ShaderPaint::Make(std::move(gradient_node));
 }
 
 sk_sp<sksg::PaintNode> AttachPaint(const skjson::ObjectValue& jpaint,
@@ -563,6 +564,11 @@
             continue;
         }
 
+        if (ParseDefault<bool>((*shape)["hd"], false)) {
+            // Ignore hidden shapes.
+            continue;
+        }
+
         recs.push_back({ *shape, *info });
 
         switch (info->fShapeType) {
@@ -587,6 +593,12 @@
     //
     std::vector<sk_sp<sksg::GeometryNode>> geos;
     std::vector<sk_sp<sksg::RenderNode  >> draws;
+
+    const auto add_draw = [this, &draws](sk_sp<sksg::RenderNode> draw, const ShapeRec& rec) {
+        // All draws can have an optional blend mode.
+        draws.push_back(this->attachBlendMode(rec.fJson, std::move(draw)));
+    };
+
     for (auto rec = recs.rbegin(); rec != recs.rend(); ++rec) {
         const AutoPropertyTracker apt(this, rec->fJson);
 
@@ -620,7 +632,7 @@
                                              ctx->fGeometryEffectStack,
                                              ctx->fCommittedAnimators);
             if (auto subgroup = this->attachShape(rec->fJson["it"], &groupShapeCtx)) {
-                draws.push_back(std::move(subgroup));
+                add_draw(std::move(subgroup), *rec);
                 SkASSERT(groupShapeCtx.fCommittedAnimators >= ctx->fCommittedAnimators);
                 ctx->fCommittedAnimators = groupShapeCtx.fCommittedAnimators;
             }
@@ -647,7 +659,7 @@
                 : drawGeos[0];
 
             SkASSERT(geo);
-            draws.push_back(sksg::Draw::Make(std::move(geo), std::move(paint)));
+            add_draw(sksg::Draw::Make(std::move(geo), std::move(paint)), *rec);
             ctx->fCommittedAnimators = ctx->fScope->size();
         } break;
         case ShapeType::kDrawEffect: {
diff --git a/modules/skottie/src/SkottieShaper.cpp b/modules/skottie/src/SkottieShaper.cpp
new file mode 100644
index 0000000..d801455
--- /dev/null
+++ b/modules/skottie/src/SkottieShaper.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkottieShaper.h"
+
+#include "SkFontMetrics.h"
+#include "SkShaper.h"
+#include "SkTextBlob.h"
+#include "SkTextBlobPriv.h"
+#include "SkUTF.h"
+
+namespace skottie {
+namespace {
+
+SkRect ComputeBlobBounds(const sk_sp<SkTextBlob>& blob) {
+    auto bounds = SkRect::MakeEmpty();
+
+    if (!blob) {
+        return bounds;
+    }
+
+    SkAutoSTArray<16, SkRect> glyphBounds;
+
+    SkTextBlobRunIterator it(blob.get());
+
+    for (SkTextBlobRunIterator it(blob.get()); !it.done(); it.next()) {
+        glyphBounds.reset(SkToInt(it.glyphCount()));
+        it.font().getBounds(it.glyphs(), it.glyphCount(), glyphBounds.get(), nullptr);
+
+        SkASSERT(it.positioning() == SkTextBlobRunIterator::kFull_Positioning);
+        for (uint32_t i = 0; i < it.glyphCount(); ++i) {
+            bounds.join(glyphBounds[i].makeOffset(it.pos()[i * 2    ],
+                                                  it.pos()[i * 2 + 1]));
+        }
+    }
+
+    return bounds;
+}
+
+// Helper for interfacing with SkShaper: buffers shaper-fed runs and performs
+// per-line position adjustments (for external line breaking, horizontal alignment, etc).
+class BlobMaker final : public SkShaper::RunHandler {
+public:
+    BlobMaker(const Shaper::TextDesc& desc, const SkRect& box)
+        : fDesc(desc)
+        , fBox(box)
+        , fHAlignFactor(HAlignFactor(fDesc.fHAlign))
+        , fFont(fDesc.fTypeface, fDesc.fTextSize)
+        , fShaper(SkShaper::Make()) {
+        fFont.setHinting(kNo_SkFontHinting);
+        fFont.setSubpixel(true);
+        fFont.setEdging(SkFont::Edging::kAntiAlias);
+    }
+
+    void beginLine() override {
+        fCurrentPosition = fOffset;
+        fPendingLineAdvance  = { 0, 0 };
+        fMaxRunAscent = 0;
+        fMaxRunDescent = 0;
+        fMaxRunLeading = 0;
+    }
+
+    void runInfo(const RunInfo& info) override {
+        fPendingLineAdvance += info.fAdvance;
+        SkFontMetrics metrics;
+        info.fFont.getMetrics(&metrics);
+        fMaxRunAscent = SkTMin(fMaxRunAscent, metrics.fAscent);
+        fMaxRunDescent = SkTMax(fMaxRunDescent, metrics.fDescent);
+        fMaxRunLeading = SkTMax(fMaxRunLeading, metrics.fLeading);
+    }
+
+    void commitRunInfo() override {}
+
+    Buffer runBuffer(const RunInfo& info) override {
+        int glyphCount = SkTFitsIn<int>(info.glyphCount) ? info.glyphCount : INT_MAX;
+
+        SkFontMetrics metrics;
+        info.fFont.getMetrics(&metrics);
+
+        const auto& blobBuffer = fBuilder.allocRunPos(info.fFont, glyphCount);
+
+        SkVector alignmentOffset { fHAlignFactor * (fPendingLineAdvance.x() - fBox.width()), 0 };
+
+        return {
+            blobBuffer.glyphs,
+            blobBuffer.points(),
+            nullptr,
+            nullptr,
+            fCurrentPosition + alignmentOffset
+        };
+    }
+
+    void commitRunBuffer(const RunInfo& info) override {
+        fCurrentPosition += info.fAdvance;
+    }
+
+    void commitLine() override {
+        fOffset += { 0, fMaxRunDescent + fMaxRunLeading - fMaxRunAscent };
+    }
+
+    Shaper::Result makeBlob() {
+        auto blob = fBuilder.make();
+
+        SkPoint pos {fBox.x(), fBox.y()};
+
+        // By default, first line is vertical-aligned on a baseline of 0.
+        // Perform additional adjustments based on VAlign.
+        switch (fDesc.fVAlign) {
+        case Shaper::VAlign::kTop: {
+            pos.offset(0, -ComputeBlobBounds(blob).fTop);
+        } break;
+        case Shaper::VAlign::kTopBaseline:
+            // Default behavior.
+            break;
+        case Shaper::VAlign::kCenter: {
+            const auto bounds = ComputeBlobBounds(blob).makeOffset(pos.x(), pos.y());
+            pos.offset(0, fBox.centerY() - bounds.centerY());
+        } break;
+        }
+
+        return {
+            std::move(blob),
+            pos
+        };
+    }
+
+    void shapeLine(const char* start, const char* end) {
+        if (!fShaper) {
+            return;
+        }
+
+        // When no text box is present, text is laid out on a single infinite line
+        // (modulo explicit line breaks).
+        const auto shape_width = fBox.isEmpty() ? SK_ScalarMax
+                                                : fBox.width();
+
+        fShaper->shape(start, SkToSizeT(end - start), fFont, true, shape_width, this);
+    }
+
+private:
+    static float HAlignFactor(SkTextUtils::Align align) {
+        switch (align) {
+        case SkTextUtils::kLeft_Align:   return  0.0f;
+        case SkTextUtils::kCenter_Align: return -0.5f;
+        case SkTextUtils::kRight_Align:  return -1.0f;
+        }
+        return 0.0f; // go home, msvc...
+    }
+
+    struct Run {
+        SkFont                          fFont;
+        SkShaper::RunHandler::RunInfo   fInfo;
+        SkSTArray<128, SkGlyphID, true> fGlyphs;
+        SkSTArray<128, SkPoint  , true> fPositions;
+
+        Run(const SkFont& font, const SkShaper::RunHandler::RunInfo& info, int count)
+            : fFont(font)
+            , fInfo(info)
+            , fGlyphs   (count)
+            , fPositions(count) {
+            fGlyphs   .push_back_n(count);
+            fPositions.push_back_n(count);
+        }
+
+        size_t size() const {
+            SkASSERT(fGlyphs.size() == fPositions.size());
+            return fGlyphs.size();
+        }
+    };
+
+    const Shaper::TextDesc&   fDesc;
+    const SkRect&             fBox;
+    const float               fHAlignFactor;
+
+    SkFont                    fFont;
+    SkTextBlobBuilder         fBuilder;
+    std::unique_ptr<SkShaper> fShaper;
+
+    SkScalar fMaxRunAscent;
+    SkScalar fMaxRunDescent;
+    SkScalar fMaxRunLeading;
+    SkPoint  fCurrentPosition{ 0, 0 };
+    SkPoint  fOffset{ 0, 0 };
+    SkVector fPendingLineAdvance{ 0, 0 };
+};
+
+Shaper::Result ShapeImpl(const SkString& txt, const Shaper::TextDesc& desc, const SkRect& box) {
+    const auto& is_line_break = [](SkUnichar uch) {
+        // TODO: other explicit breaks?
+        return uch == '\r';
+    };
+
+    const char* ptr        = txt.c_str();
+    const char* line_start = ptr;
+    const char* end        = ptr + txt.size();
+
+    BlobMaker blobMaker(desc, box);
+    while (ptr < end) {
+        if (is_line_break(SkUTF::NextUTF8(&ptr, end))) {
+            blobMaker.shapeLine(line_start, ptr - 1);
+            line_start = ptr;
+        }
+    }
+    blobMaker.shapeLine(line_start, ptr);
+
+    return blobMaker.makeBlob();
+}
+
+} // namespace
+
+Shaper::Result Shaper::Shape(const SkString& txt, const TextDesc& desc, const SkPoint& point) {
+    return ShapeImpl(txt, desc, SkRect::MakeEmpty().makeOffset(point.x(), point.y()));
+}
+
+Shaper::Result Shaper::Shape(const SkString& txt, const TextDesc& desc, const SkRect& box) {
+    return ShapeImpl(txt, desc, box);
+}
+
+SkRect Shaper::Result::computeBounds() const {
+    return ComputeBlobBounds(fBlob).makeOffset(fPos.x(), fPos.y());
+}
+
+} // namespace skottie
diff --git a/modules/skottie/src/SkottieShaper.h b/modules/skottie/src/SkottieShaper.h
new file mode 100644
index 0000000..816141d
--- /dev/null
+++ b/modules/skottie/src/SkottieShaper.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkottieShaper_DEFINED
+#define SkottieShaper_DEFINED
+
+#include "SkPoint.h"
+#include "SkTextUtils.h"
+
+class SkTextBlob;
+
+namespace skottie {
+
+// Helper implementing After Effects text shaping semantics on top of SkShaper.
+
+class Shaper final {
+public:
+    struct Result {
+        // For now, shaping produces a single text blob.  This will eventually
+        // evolve into an array of <blob,pos> tuples to support animating
+        // per-character properties.
+        sk_sp<SkTextBlob> fBlob;
+        SkPoint           fPos;
+
+        SkRect computeBounds() const;
+    };
+
+    enum class VAlign : uint8_t {
+        // Align the first line visual top with the text box top.
+        kTop,
+        // Align the first line baseline with the text box top.
+        kTopBaseline,
+        // Align the center of the shaped text bounds with the center of the text box.
+        kCenter,
+    };
+
+    struct TextDesc {
+        const sk_sp<SkTypeface>&  fTypeface;
+        SkScalar                  fTextSize;
+        SkTextUtils::Align        fHAlign;
+        VAlign                    fVAlign;
+    };
+
+    // Performs text layout along an infinite horizontal line, starting at |textPoint|.
+    // Only explicit line breaks (\r) are observed.
+    static Result Shape(const SkString& text, const TextDesc& desc, const SkPoint& textPoint);
+
+    // Performs text layout within |textBox|, injecting line breaks as needed to ensure
+    // horizontal fitting.  The result is *not* guaranteed to fit vertically (it may extend
+    // below the box bottom).
+    static Result Shape(const SkString& text, const TextDesc& desc, const SkRect& textBox);
+
+private:
+    Shaper() = delete;
+};
+
+} // namespace skottie
+
+#endif // SkottieShaper_DEFINED
diff --git a/modules/skottie/src/SkottieTest.cpp b/modules/skottie/src/SkottieTest.cpp
index b29d735..34a4073 100644
--- a/modules/skottie/src/SkottieTest.cpp
+++ b/modules/skottie/src/SkottieTest.cpp
@@ -8,10 +8,14 @@
 #include "SkMatrix.h"
 #include "Skottie.h"
 #include "SkottieProperty.h"
+#include "SkottieShaper.h"
 #include "SkStream.h"
+#include "SkTextBlob.h"
+#include "SkTypeface.h"
 
 #include "Test.h"
 
+#include <cmath>
 #include <tuple>
 #include <vector>
 
@@ -217,3 +221,124 @@
     REPORTER_ASSERT(reporter, std::get<1>(observer->fMarkers[1]) == 0.75f);
     REPORTER_ASSERT(reporter, std::get<2>(observer->fMarkers[1]) == 0.75f);
 }
+
+DEF_TEST(Skottie_Shaper_HAlign, reporter) {
+    auto typeface = SkTypeface::MakeDefault();
+    REPORTER_ASSERT(reporter, typeface);
+
+    static constexpr struct {
+        SkScalar text_size,
+                 tolerance;
+    } kTestSizes[] = {
+        // These gross tolerances are required for the test to pass on NativeFonts bots.
+        // Might be worth investigating why we need so much slack.
+        {  5, 2.0f },
+        { 10, 2.0f },
+        { 15, 2.4f },
+        { 25, 4.4f },
+    };
+
+    static constexpr struct {
+        SkTextUtils::Align align;
+        SkScalar           l_selector,
+                           r_selector;
+    } kTestAligns[] = {
+        { SkTextUtils::  kLeft_Align, 0.0f, 1.0f },
+        { SkTextUtils::kCenter_Align, 0.5f, 0.5f },
+        { SkTextUtils:: kRight_Align, 1.0f, 0.0f },
+    };
+
+    const SkString text("Foo, bar.\rBaz.");
+    const SkPoint  text_point = SkPoint::Make(100, 100);
+
+    for (const auto& tsize : kTestSizes) {
+        for (const auto& talign : kTestAligns) {
+            const skottie::Shaper::TextDesc desc = {
+                typeface,
+                tsize.text_size,
+                talign.align,
+                skottie::Shaper::VAlign::kTopBaseline,
+            };
+
+            const auto shape_result = skottie::Shaper::Shape(text, desc, text_point);
+            REPORTER_ASSERT(reporter, shape_result.fBlob);
+
+            const auto shape_bounds = shape_result.computeBounds();
+            REPORTER_ASSERT(reporter, !shape_bounds.isEmpty());
+
+            const auto expected_l = text_point.x() - shape_bounds.width() * talign.l_selector;
+            REPORTER_ASSERT(reporter,
+                            std::fabs(shape_bounds.left() - expected_l) < tsize.tolerance,
+                            "%f %f %f %f %d", shape_bounds.left(), expected_l, tsize.tolerance,
+                                              tsize.text_size, talign.align);
+
+            const auto expected_r = text_point.x() + shape_bounds.width() * talign.r_selector;
+            REPORTER_ASSERT(reporter,
+                            std::fabs(shape_bounds.right() - expected_r) < tsize.tolerance,
+                            "%f %f %f %f %d", shape_bounds.right(), expected_r, tsize.tolerance,
+                                              tsize.text_size, talign.align);
+
+        }
+    }
+}
+
+DEF_TEST(Skottie_Shaper_VAlign, reporter) {
+    auto typeface = SkTypeface::MakeDefault();
+    REPORTER_ASSERT(reporter, typeface);
+
+    static constexpr struct {
+        SkScalar text_size,
+                 tolerance;
+    } kTestSizes[] = {
+        // These gross tolerances are required for the test to pass on NativeFonts bots.
+        // Might be worth investigating why we need so much slack.
+        {  5, 2.0f },
+        { 10, 2.0f },
+        { 15, 2.4f },
+        { 25, 4.4f },
+    };
+
+    struct {
+        skottie::Shaper::VAlign align;
+        SkScalar                topFactor;
+    } kTestAligns[] = {
+        { skottie::Shaper::VAlign::kTop   , 0.0f },
+        { skottie::Shaper::VAlign::kCenter, 0.5f },
+        // TODO: any way to test kTopBaseline?
+    };
+
+    const SkString text("Foo, bar.\rBaz.");
+    const auto text_box = SkRect::MakeXYWH(100, 100, 1000, 1000); // large-enough to avoid breaks.
+
+
+    for (const auto& tsize : kTestSizes) {
+        for (const auto& talign : kTestAligns) {
+            const skottie::Shaper::TextDesc desc = {
+                typeface,
+                tsize.text_size,
+                SkTextUtils::Align::kCenter_Align,
+                talign.align,
+            };
+
+            const auto shape_result = skottie::Shaper::Shape(text, desc, text_box);
+            REPORTER_ASSERT(reporter, shape_result.fBlob);
+
+            const auto shape_bounds = shape_result.computeBounds();
+            REPORTER_ASSERT(reporter, !shape_bounds.isEmpty());
+
+            const auto v_diff = text_box.height() - shape_bounds.height();
+
+            const auto expected_t = text_box.top() + v_diff * talign.topFactor;
+            REPORTER_ASSERT(reporter,
+                            std::fabs(shape_bounds.top() - expected_t) < tsize.tolerance,
+                            "%f %f %f %f %d", shape_bounds.top(), expected_t, tsize.tolerance,
+                                              tsize.text_size, SkToU32(talign.align));
+
+            const auto expected_b = text_box.bottom() - v_diff * (1 - talign.topFactor);
+            REPORTER_ASSERT(reporter,
+                            std::fabs(shape_bounds.bottom() - expected_b) < tsize.tolerance,
+                            "%f %f %f %f %d", shape_bounds.bottom(), expected_b, tsize.tolerance,
+                                              tsize.text_size, SkToU32(talign.align));
+        }
+    }
+}
diff --git a/modules/skottie/src/SkottieTextLayer.cpp b/modules/skottie/src/SkottieTextLayer.cpp
index 68929c4..b617b31 100644
--- a/modules/skottie/src/SkottieTextLayer.cpp
+++ b/modules/skottie/src/SkottieTextLayer.cpp
@@ -13,9 +13,9 @@
 #include "SkottieAdapter.h"
 #include "SkottieJson.h"
 #include "SkottieValue.h"
-#include "SkSGColor.h"
 #include "SkSGDraw.h"
 #include "SkSGGroup.h"
+#include "SkSGPaint.h"
 #include "SkSGText.h"
 #include "SkTypes.h"
 
diff --git a/modules/skottie/src/SkottieTool.cpp b/modules/skottie/src/SkottieTool.cpp
index 4babf33..44c724a 100644
--- a/modules/skottie/src/SkottieTool.cpp
+++ b/modules/skottie/src/SkottieTool.cpp
@@ -5,30 +5,30 @@
  * found in the LICENSE file.
  */
 
+#include "CommandLineFlags.h"
 #include "SkCanvas.h"
-#include "SkCommandLineFlags.h"
 #include "SkGraphics.h"
 #include "SkMakeUnique.h"
 #include "SkOSFile.h"
 #include "SkOSPath.h"
-#include "Skottie.h"
-#include "SkottieUtils.h"
 #include "SkPictureRecorder.h"
 #include "SkStream.h"
 #include "SkSurface.h"
+#include "Skottie.h"
+#include "SkottieUtils.h"
 
 #include <vector>
 
-DEFINE_string2(input    , i, nullptr, "Input .json file.");
-DEFINE_string2(writePath, w, nullptr, "Output directory.  Frames are names [0-9]{6}.png.");
-DEFINE_string2(format   , f, "png"  , "Output format (png or skp)");
+static DEFINE_string2(input    , i, nullptr, "Input .json file.");
+static DEFINE_string2(writePath, w, nullptr, "Output directory.  Frames are names [0-9]{6}.png.");
+static DEFINE_string2(format   , f, "png"  , "Output format (png or skp)");
 
-DEFINE_double(t0,   0, "Timeline start [0..1].");
-DEFINE_double(t1,   1, "Timeline stop [0..1].");
-DEFINE_double(fps, 30, "Decode frames per second.");
+static DEFINE_double(t0,   0, "Timeline start [0..1].");
+static DEFINE_double(t1,   1, "Timeline stop [0..1].");
+static DEFINE_double(fps, 30, "Decode frames per second.");
 
-DEFINE_int32(width , 800, "Render width.");
-DEFINE_int32(height, 600, "Render height.");
+static DEFINE_int(width , 800, "Render width.");
+static DEFINE_int(height, 600, "Render height.");
 
 namespace {
 
@@ -157,7 +157,7 @@
 } // namespace
 
 int main(int argc, char** argv) {
-    SkCommandLineFlags::Parse(argc, argv);
+    CommandLineFlags::Parse(argc, argv);
     SkAutoGraphics ag;
 
     if (FLAGS_input.isEmpty() || FLAGS_writePath.isEmpty()) {
diff --git a/modules/skottie/src/SkottieValue.cpp b/modules/skottie/src/SkottieValue.cpp
index 7e80377..edba830 100644
--- a/modules/skottie/src/SkottieValue.cpp
+++ b/modules/skottie/src/SkottieValue.cpp
@@ -30,7 +30,6 @@
 template <>
 void ValueTraits<ScalarValue>::Lerp(const ScalarValue& v0, const ScalarValue& v1, float t,
                                     ScalarValue* result) {
-    SkASSERT(t >= 0 && t <= 1);
     *result = v0 + (v1 - v0) * t;
 }
 
@@ -183,7 +182,6 @@
 template <>
 void ValueTraits<ShapeValue>::Lerp(const ShapeValue& v0, const ShapeValue& v1, float t,
                                    ShapeValue* result) {
-    SkASSERT(t >= 0 && t <= 1);
     SkASSERT(v0.fVertices.size() == v1.fVertices.size());
     SkASSERT(v0.fClosed == v1.fClosed);
 
@@ -268,8 +266,38 @@
         SkTextUtils::kRight_Align, // 'j': 1
         SkTextUtils::kCenter_Align // 'j': 2
     };
-    v->fAlign = gAlignMap[SkTMin<size_t>(ParseDefault<size_t>((*jtxt)["j"], 0),
-                                         SK_ARRAY_COUNT(gAlignMap))];
+    v->fHAlign = gAlignMap[SkTMin<size_t>(ParseDefault<size_t>((*jtxt)["j"], 0),
+                                          SK_ARRAY_COUNT(gAlignMap))];
+
+    // Optional text box size.
+    if (const skjson::ArrayValue* jsz = (*jtxt)["sz"]) {
+        if (jsz->size() == 2) {
+            v->fBox.setWH(ParseDefault<SkScalar>((*jsz)[0], 0),
+                          ParseDefault<SkScalar>((*jsz)[1], 0));
+        }
+    }
+
+    // Optional text box position.
+    if (const skjson::ArrayValue* jps = (*jtxt)["ps"]) {
+        if (jps->size() == 2) {
+            v->fBox.offset(ParseDefault<SkScalar>((*jps)[0], 0),
+                           ParseDefault<SkScalar>((*jps)[1], 0));
+        }
+    }
+
+    // In point mode, the text is baseline-aligned.
+    v->fVAlign = v->fBox.isEmpty() ? Shaper::VAlign::kTopBaseline
+                                   : Shaper::VAlign::kTop;
+
+    // Skia vertical alignment extension "sk_vj":
+    enum {
+        kDefault_VJ = 0, // AE default alignment.
+         kCenter_VJ = 1, // Center-aligned.
+    };
+
+    if (ParseDefault<int>((*jtxt)["sk_vj"], kDefault_VJ) == kCenter_VJ) {
+        v->fVAlign = Shaper::VAlign::kCenter;
+    }
 
     const auto& parse_color = [] (const skjson::ArrayValue* jcolor,
                                   const internal::AnimationBuilder* abuilder,
diff --git a/modules/skottie/src/SkottieValue.h b/modules/skottie/src/SkottieValue.h
index 1c14568..7779dda 100644
--- a/modules/skottie/src/SkottieValue.h
+++ b/modules/skottie/src/SkottieValue.h
@@ -9,6 +9,7 @@
 #define SkottieValue_DEFINED
 
 #include "SkColor.h"
+#include "SkottieShaper.h"
 #include "SkPaint.h"
 #include "SkPath.h"
 #include "SkScalar.h"
@@ -71,22 +72,26 @@
 };
 
 struct TextValue {
-    sk_sp<SkTypeface> fTypeface;
-    SkString          fText;
-    float             fTextSize    = 0,
-                      fStrokeWidth = 0;
-    SkTextUtils::Align fAlign       = SkTextUtils::kLeft_Align;
-    SkColor           fFillColor   = SK_ColorTRANSPARENT,
-                      fStrokeColor = SK_ColorTRANSPARENT;
-    bool              fHasFill   : 1,
-                      fHasStroke : 1;
+    sk_sp<SkTypeface>  fTypeface;
+    SkString           fText;
+    float              fTextSize    = 0,
+                       fStrokeWidth = 0;
+    SkTextUtils::Align fHAlign      = SkTextUtils::kLeft_Align;
+    Shaper::VAlign     fVAlign      = Shaper::VAlign::kTop;
+    SkRect             fBox         = SkRect::MakeEmpty();
+    SkColor            fFillColor   = SK_ColorTRANSPARENT,
+                       fStrokeColor = SK_ColorTRANSPARENT;
+    bool               fHasFill   : 1,
+                       fHasStroke : 1;
 
     bool operator==(const TextValue& other) const {
         return fTypeface == other.fTypeface
             && fText == other.fText
             && fTextSize == other.fTextSize
             && fStrokeWidth == other.fStrokeWidth
-            && fAlign == other.fAlign
+            && fHAlign == other.fHAlign
+            && fVAlign == other.fVAlign
+            && fBox == other.fBox
             && fFillColor == other.fFillColor
             && fStrokeColor == other.fStrokeColor
             && fHasFill == other.fHasFill
diff --git a/modules/skottie/tests/bezier-extranormal.json b/modules/skottie/tests/bezier-extranormal.json
new file mode 100644
index 0000000..cc7f818
--- /dev/null
+++ b/modules/skottie/tests/bezier-extranormal.json
@@ -0,0 +1,254 @@
+{
+  "assets": [],
+  "ddd": 0,
+  "fr": 60,
+  "h": 500,
+  "ip": 0,
+  "layers": [
+    {
+      "ao": 0,
+      "bm": 0,
+      "ddd": 0,
+      "ind": 1,
+      "ip": 0,
+      "ks": {
+        "a": {
+          "a": 0,
+          "ix": 1,
+          "k": [
+            0,
+            0,
+            0
+          ]
+        },
+        "o": {
+          "a": 0,
+          "ix": 11,
+          "k": 100
+        },
+        "p": {
+          "s": true,
+          "x": {
+            "a": 0,
+            "ix": 3,
+            "k": 250
+          },
+          "y": {
+            "a": 1,
+            "ix": 4,
+            "k": [
+              {
+                "i": {
+                  "x": [
+                    0.978
+                  ],
+                  "y": [
+                    2.189
+                  ]
+                },
+                "o": {
+                  "x": [
+                    0.29
+                  ],
+                  "y": [
+                    -1.57
+                  ]
+                },
+                "s": [
+                  400
+                ],
+                "t": 0
+              },
+              {
+                "s": [
+                  100
+                ],
+                "t": 238
+              }
+            ]
+          }
+        },
+        "r": {
+          "a": 0,
+          "ix": 10,
+          "k": 0
+        },
+        "s": {
+          "a": 0,
+          "ix": 6,
+          "k": [
+            100,
+            100,
+            100
+          ]
+        }
+      },
+      "nm": "Shape Layer 1",
+      "op": 300,
+      "shapes": [
+        {
+          "d": 1,
+          "hd": false,
+          "mn": "ADBE Vector Shape - Ellipse",
+          "nm": "Ellipse Path 1",
+          "p": {
+            "a": 0,
+            "ix": 3,
+            "k": [
+              0,
+              0
+            ]
+          },
+          "s": {
+            "a": 0,
+            "ix": 2,
+            "k": [
+              50,
+              50
+            ]
+          },
+          "ty": "el"
+        },
+        {
+          "bm": 0,
+          "c": {
+            "a": 0,
+            "ix": 4,
+            "k": [
+              1,
+              0,
+              0,
+              1
+            ]
+          },
+          "hd": false,
+          "mn": "ADBE Vector Graphic - Fill",
+          "nm": "Fill 1",
+          "o": {
+            "a": 0,
+            "ix": 5,
+            "k": 100
+          },
+          "r": 1,
+          "ty": "fl"
+        }
+      ],
+      "sr": 1,
+      "st": 0,
+      "ty": 4
+    },
+    {
+      "ao": 0,
+      "bm": 0,
+      "ddd": 0,
+      "ind": 2,
+      "ip": 0,
+      "ks": {
+        "a": {
+          "a": 0,
+          "ix": 1,
+          "k": [
+            250,
+            1,
+            0
+          ]
+        },
+        "o": {
+          "a": 0,
+          "ix": 11,
+          "k": 100
+        },
+        "p": {
+          "a": 0,
+          "ix": 2,
+          "k": [
+            250,
+            400,
+            0
+          ]
+        },
+        "r": {
+          "a": 0,
+          "ix": 10,
+          "k": 0
+        },
+        "s": {
+          "a": 0,
+          "ix": 6,
+          "k": [
+            100,
+            100,
+            100
+          ]
+        }
+      },
+      "nm": "Green Solid 1",
+      "op": 300,
+      "sc": "#00ff00",
+      "sh": 2,
+      "sr": 1,
+      "st": 0,
+      "sw": 500,
+      "ty": 1
+    },
+    {
+      "ao": 0,
+      "bm": 0,
+      "ddd": 0,
+      "ind": 3,
+      "ip": 0,
+      "ks": {
+        "a": {
+          "a": 0,
+          "ix": 1,
+          "k": [
+            250,
+            1,
+            0
+          ]
+        },
+        "o": {
+          "a": 0,
+          "ix": 11,
+          "k": 100
+        },
+        "p": {
+          "a": 0,
+          "ix": 2,
+          "k": [
+            250,
+            100,
+            0
+          ]
+        },
+        "r": {
+          "a": 0,
+          "ix": 10,
+          "k": 0
+        },
+        "s": {
+          "a": 0,
+          "ix": 6,
+          "k": [
+            100,
+            100,
+            100
+          ]
+        }
+      },
+      "nm": "Green Solid 1",
+      "op": 300,
+      "sc": "#00ff00",
+      "sh": 2,
+      "sr": 1,
+      "st": 0,
+      "sw": 500,
+      "ty": 1
+    }
+  ],
+  "markers": [],
+  "nm": "negative bezier",
+  "op": 300,
+  "v": "5.5.0",
+  "w": 500
+}
\ No newline at end of file
diff --git a/modules/skottie/tests/gradient-ramp.json b/modules/skottie/tests/gradient-ramp.json
new file mode 100644
index 0000000..536f6f5
--- /dev/null
+++ b/modules/skottie/tests/gradient-ramp.json
@@ -0,0 +1 @@
+{"v":"5.5.0","fr":60,"ip":0,"op":300,"w":500,"h":500,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":1,"nm":"solid-linear","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[125,125,0],"ix":2},"a":{"a":0,"k":[100,100,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Gradient Ramp","np":10,"mn":"ADBE Ramp","ix":1,"en":1,"ef":[{"ty":3,"nm":"Start of Ramp","mn":"ADBE Ramp-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[50,50],"to":[16.667,0],"ti":[-16.667,-16.667]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75,"s":[150,50],"to":[16.667,16.667],"ti":[16.667,-16.667]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":150,"s":[150,150],"to":[-16.667,16.667],"ti":[16.667,16.667]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":225,"s":[50,150],"to":[-16.667,-16.667],"ti":[0,16.667]},{"t":299,"s":[50,50]}],"ix":1}},{"ty":2,"nm":"Start Color","mn":"ADBE Ramp-0002","ix":2,"v":{"a":0,"k":[1,0,0,1],"ix":2}},{"ty":3,"nm":"End of Ramp","mn":"ADBE Ramp-0003","ix":3,"v":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[150,150],"to":[-16.667,0],"ti":[16.667,16.667]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75,"s":[50,150],"to":[-16.667,-16.667],"ti":[-16.667,16.667]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":150,"s":[50,50],"to":[16.667,-16.667],"ti":[-16.667,-16.667]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":225,"s":[150,50],"to":[16.667,16.667],"ti":[0,-16.667]},{"t":299,"s":[150,150]}],"ix":3}},{"ty":2,"nm":"End Color","mn":"ADBE Ramp-0004","ix":4,"v":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[1,1,0,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[0,0,1,1]},{"t":299,"s":[1,1,0,1]}],"ix":4}},{"ty":7,"nm":"Ramp Shape","mn":"ADBE Ramp-0005","ix":5,"v":{"a":0,"k":1,"ix":5}},{"ty":0,"nm":"Ramp Scatter","mn":"ADBE Ramp-0006","ix":6,"v":{"a":0,"k":0,"ix":6}},{"ty":0,"nm":"Blend With Original","mn":"ADBE Ramp-0007","ix":7,"v":{"a":0,"k":0,"ix":7}},{"ty":6,"nm":"","mn":"ADBE Ramp-0008","ix":8,"v":0}]}],"sw":200,"sh":200,"sc":"#00ff00","ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":1,"nm":"solid-radial","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[375,125,0],"ix":2},"a":{"a":0,"k":[100,100,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Gradient Ramp","np":10,"mn":"ADBE Ramp","ix":1,"en":1,"ef":[{"ty":3,"nm":"Start of Ramp","mn":"ADBE Ramp-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[50,50],"to":[16.667,0],"ti":[-16.667,-16.667]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75,"s":[150,50],"to":[16.667,16.667],"ti":[16.667,-16.667]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":150,"s":[150,150],"to":[-16.667,16.667],"ti":[16.667,16.667]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":225,"s":[50,150],"to":[-16.667,-16.667],"ti":[0,16.667]},{"t":299,"s":[50,50]}],"ix":1}},{"ty":2,"nm":"Start Color","mn":"ADBE Ramp-0002","ix":2,"v":{"a":0,"k":[1,0,0,1],"ix":2}},{"ty":3,"nm":"End of Ramp","mn":"ADBE Ramp-0003","ix":3,"v":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[150,150],"to":[-16.667,0],"ti":[16.667,16.667]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75,"s":[50,150],"to":[-16.667,-16.667],"ti":[-16.667,16.667]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":150,"s":[50,50],"to":[16.667,-16.667],"ti":[-16.667,-16.667]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":225,"s":[150,50],"to":[16.667,16.667],"ti":[0,-16.667]},{"t":299,"s":[150,150]}],"ix":3}},{"ty":2,"nm":"End Color","mn":"ADBE Ramp-0004","ix":4,"v":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[1,1,0,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[0,0,1,1]},{"t":299,"s":[1,1,0,1]}],"ix":4}},{"ty":7,"nm":"Ramp Shape","mn":"ADBE Ramp-0005","ix":5,"v":{"a":0,"k":2,"ix":5}},{"ty":0,"nm":"Ramp Scatter","mn":"ADBE Ramp-0006","ix":6,"v":{"a":0,"k":0,"ix":6}},{"ty":0,"nm":"Blend With Original","mn":"ADBE Ramp-0007","ix":7,"v":{"a":0,"k":0,"ix":7}},{"ty":6,"nm":"","mn":"ADBE Ramp-0008","ix":8,"v":0}]}],"sw":200,"sh":200,"sc":"#00ff00","ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"shapes-linear","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,375,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Gradient Ramp","np":10,"mn":"ADBE Ramp","ix":1,"en":1,"ef":[{"ty":3,"nm":"Start of Ramp","mn":"ADBE Ramp-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[25,375],"to":[37.5,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":150,"s":[250,375],"to":[0,0],"ti":[37.5,0]},{"t":299,"s":[25,375]}],"ix":1}},{"ty":2,"nm":"Start Color","mn":"ADBE Ramp-0002","ix":2,"v":{"a":0,"k":[1,0.291544109583,0.868933796883,1],"ix":2}},{"ty":3,"nm":"End of Ramp","mn":"ADBE Ramp-0003","ix":3,"v":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[250,375],"to":[-37.5,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":150,"s":[25,375],"to":[0,0],"ti":[-37.5,0]},{"t":299,"s":[250,375]}],"ix":3}},{"ty":2,"nm":"End Color","mn":"ADBE Ramp-0004","ix":4,"v":{"a":0,"k":[0,0.996721804142,0,1],"ix":4}},{"ty":7,"nm":"Ramp Shape","mn":"ADBE Ramp-0005","ix":5,"v":{"a":0,"k":1,"ix":5}},{"ty":0,"nm":"Ramp Scatter","mn":"ADBE Ramp-0006","ix":6,"v":{"a":0,"k":0,"ix":6}},{"ty":0,"nm":"Blend With Original","mn":"ADBE Ramp-0007","ix":7,"v":{"a":0,"k":0,"ix":7}},{"ty":6,"nm":"","mn":"ADBE Ramp-0008","ix":8,"v":0}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[100,100],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"d":1,"ty":"el","s":{"a":0,"k":[146,146],"ix":2},"p":{"a":0,"k":[89,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"shapes-radial","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[325,375,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Gradient Ramp","np":10,"mn":"ADBE Ramp","ix":1,"en":1,"ef":[{"ty":3,"nm":"Start of Ramp","mn":"ADBE Ramp-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[275,375],"to":[33.333,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":150,"s":[475,375],"to":[0,0],"ti":[33.333,0]},{"t":299,"s":[275,375]}],"ix":1}},{"ty":2,"nm":"Start Color","mn":"ADBE Ramp-0002","ix":2,"v":{"a":0,"k":[1,0.291544109583,0.868933796883,1],"ix":2}},{"ty":3,"nm":"End of Ramp","mn":"ADBE Ramp-0003","ix":3,"v":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[475,375],"to":[-33.333,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":150,"s":[275,375],"to":[0,0],"ti":[-33.333,0]},{"t":299,"s":[475,375]}],"ix":3}},{"ty":2,"nm":"End Color","mn":"ADBE Ramp-0004","ix":4,"v":{"a":0,"k":[0,0.996721804142,0,1],"ix":4}},{"ty":7,"nm":"Ramp Shape","mn":"ADBE Ramp-0005","ix":5,"v":{"a":0,"k":2,"ix":5}},{"ty":0,"nm":"Ramp Scatter","mn":"ADBE Ramp-0006","ix":6,"v":{"a":0,"k":0,"ix":6}},{"ty":0,"nm":"Blend With Original","mn":"ADBE Ramp-0007","ix":7,"v":{"a":0,"k":0,"ix":7}},{"ty":6,"nm":"","mn":"ADBE Ramp-0008","ix":8,"v":0}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[100,100],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"d":1,"ty":"el","s":{"a":0,"k":[146,146],"ix":2},"p":{"a":0,"k":[89,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":300,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/modules/skottie/tests/hidden-shapes-layers.json b/modules/skottie/tests/hidden-shapes-layers.json
new file mode 100644
index 0000000..6ee850d
--- /dev/null
+++ b/modules/skottie/tests/hidden-shapes-layers.json
@@ -0,0 +1 @@
+{"v":"5.4.4","fr":60,"ip":0,"op":300,"w":500,"h":500,"nm":"Hidden shapes and layers","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Hidden stroke","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[100,100,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[100,100],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":83,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":true},{"ty":"fl","c":{"a":0,"k":[0,1,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Hidden fill","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,102,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[50,50],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":true},{"ty":"st","c":{"a":0,"k":[0,1,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":50,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Hidden geometry","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[400,100,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[100,100],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":true},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[100,100],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0,1,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Hidden group","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[100,250,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[100,100],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":true},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":true},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":true},{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[100,100],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0,1,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":1,"nm":"Hidden layer","hd":true,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2},"a":{"a":0,"k":[50,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"sw":100,"sh":100,"sc":"#ff0000","ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":1,"nm":"Visible layer","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2},"a":{"a":0,"k":[50,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"sw":100,"sh":100,"sc":"#00ff00","ip":0,"op":300,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/modules/skottie/tests/levels-effect.json b/modules/skottie/tests/levels-effect.json
new file mode 100644
index 0000000..1f5d7aa
--- /dev/null
+++ b/modules/skottie/tests/levels-effect.json
@@ -0,0 +1 @@
+{"v":"5.5.0","fr":60,"ip":0,"op":300,"w":500,"h":500,"nm":"ColorCorrection","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":1,"nm":"White Solid 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2},"a":{"a":0,"k":[50,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Gradient Ramp","np":10,"mn":"ADBE Ramp","ix":1,"en":1,"ef":[{"ty":3,"nm":"Start of Ramp","mn":"ADBE Ramp-0001","ix":1,"v":{"a":0,"k":[50,50],"ix":1}},{"ty":2,"nm":"Start Color","mn":"ADBE Ramp-0002","ix":2,"v":{"a":0,"k":[1,0,0,1],"ix":2}},{"ty":3,"nm":"End of Ramp","mn":"ADBE Ramp-0003","ix":3,"v":{"a":0,"k":[50,100],"ix":3}},{"ty":2,"nm":"End Color","mn":"ADBE Ramp-0004","ix":4,"v":{"a":0,"k":[0,1,0,1],"ix":4}},{"ty":7,"nm":"Ramp Shape","mn":"ADBE Ramp-0005","ix":5,"v":{"a":0,"k":2,"ix":5}},{"ty":0,"nm":"Ramp Scatter","mn":"ADBE Ramp-0006","ix":6,"v":{"a":0,"k":0,"ix":6}},{"ty":0,"nm":"Blend With Original","mn":"ADBE Ramp-0007","ix":7,"v":{"a":0,"k":0,"ix":7}},{"ty":6,"nm":"","mn":"ADBE Ramp-0008","ix":8,"v":0}]}],"sw":100,"sh":100,"sc":"#ffffff","ip":0,"op":300,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"gamma 0","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[70,70,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Levels","np":11,"mn":"ADBE Easy Levels2","ix":1,"en":1,"ef":[{"ty":7,"nm":"Channel:","mn":"ADBE Easy Levels2-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{},{"ty":0,"nm":"Input Black","mn":"ADBE Easy Levels2-0003","ix":3,"v":{"a":0,"k":0,"ix":3}},{"ty":0,"nm":"Input White","mn":"ADBE Easy Levels2-0004","ix":4,"v":{"a":0,"k":1,"ix":4}},{"ty":0,"nm":"Gamma","mn":"ADBE Easy Levels2-0005","ix":5,"v":{"a":0,"k":0,"ix":5}},{"ty":0,"nm":"Output Black","mn":"ADBE Easy Levels2-0006","ix":6,"v":{"a":0,"k":0,"ix":6}},{"ty":0,"nm":"Output White","mn":"ADBE Easy Levels2-0007","ix":7,"v":{"a":0,"k":1,"ix":7}},{"ty":7,"nm":"Clip To Output Black","mn":"ADBE Easy Levels2-0008","ix":8,"v":{"a":0,"k":2,"ix":8}},{"ty":7,"nm":"Clip To Output White","mn":"ADBE Easy Levels2-0009","ix":9,"v":{"a":0,"k":2,"ix":9}}]}],"w":500,"h":500,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"gamma 1","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[190,70,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Levels","np":11,"mn":"ADBE Easy Levels2","ix":1,"en":1,"ef":[{"ty":7,"nm":"Channel:","mn":"ADBE Easy Levels2-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{},{"ty":0,"nm":"Input Black","mn":"ADBE Easy Levels2-0003","ix":3,"v":{"a":0,"k":0,"ix":3}},{"ty":0,"nm":"Input White","mn":"ADBE Easy Levels2-0004","ix":4,"v":{"a":0,"k":1,"ix":4}},{"ty":0,"nm":"Gamma","mn":"ADBE Easy Levels2-0005","ix":5,"v":{"a":0,"k":1,"ix":5}},{"ty":0,"nm":"Output Black","mn":"ADBE Easy Levels2-0006","ix":6,"v":{"a":0,"k":0,"ix":6}},{"ty":0,"nm":"Output White","mn":"ADBE Easy Levels2-0007","ix":7,"v":{"a":0,"k":1,"ix":7}},{"ty":7,"nm":"Clip To Output Black","mn":"ADBE Easy Levels2-0008","ix":8,"v":{"a":0,"k":2,"ix":8}},{"ty":7,"nm":"Clip To Output White","mn":"ADBE Easy Levels2-0009","ix":9,"v":{"a":0,"k":2,"ix":9}}]}],"w":500,"h":500,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"gamma 2","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[310,70,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Levels","np":11,"mn":"ADBE Easy Levels2","ix":1,"en":1,"ef":[{"ty":7,"nm":"Channel:","mn":"ADBE Easy Levels2-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{},{"ty":0,"nm":"Input Black","mn":"ADBE Easy Levels2-0003","ix":3,"v":{"a":0,"k":0,"ix":3}},{"ty":0,"nm":"Input White","mn":"ADBE Easy Levels2-0004","ix":4,"v":{"a":0,"k":1,"ix":4}},{"ty":0,"nm":"Gamma","mn":"ADBE Easy Levels2-0005","ix":5,"v":{"a":0,"k":2.5,"ix":5}},{"ty":0,"nm":"Output Black","mn":"ADBE Easy Levels2-0006","ix":6,"v":{"a":0,"k":0,"ix":6}},{"ty":0,"nm":"Output White","mn":"ADBE Easy Levels2-0007","ix":7,"v":{"a":0,"k":1,"ix":7}},{"ty":7,"nm":"Clip To Output Black","mn":"ADBE Easy Levels2-0008","ix":8,"v":{"a":0,"k":2,"ix":8}},{"ty":7,"nm":"Clip To Output White","mn":"ADBE Easy Levels2-0009","ix":9,"v":{"a":0,"k":2,"ix":9}}]}],"w":500,"h":500,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"gamma 3","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[430,70,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Levels","np":11,"mn":"ADBE Easy Levels2","ix":1,"en":1,"ef":[{"ty":7,"nm":"Channel:","mn":"ADBE Easy Levels2-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{},{"ty":0,"nm":"Input Black","mn":"ADBE Easy Levels2-0003","ix":3,"v":{"a":0,"k":0,"ix":3}},{"ty":0,"nm":"Input White","mn":"ADBE Easy Levels2-0004","ix":4,"v":{"a":0,"k":1,"ix":4}},{"ty":0,"nm":"Gamma","mn":"ADBE Easy Levels2-0005","ix":5,"v":{"a":0,"k":5,"ix":5}},{"ty":0,"nm":"Output Black","mn":"ADBE Easy Levels2-0006","ix":6,"v":{"a":0,"k":0,"ix":6}},{"ty":0,"nm":"Output White","mn":"ADBE Easy Levels2-0007","ix":7,"v":{"a":0,"k":1,"ix":7}},{"ty":7,"nm":"Clip To Output Black","mn":"ADBE Easy Levels2-0008","ix":8,"v":{"a":0,"k":2,"ix":8}},{"ty":7,"nm":"Clip To Output White","mn":"ADBE Easy Levels2-0009","ix":9,"v":{"a":0,"k":2,"ix":9}}]}],"w":500,"h":500,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"indeg 0","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[70,190,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Levels","np":11,"mn":"ADBE Easy Levels2","ix":1,"en":1,"ef":[{"ty":7,"nm":"Channel:","mn":"ADBE Easy Levels2-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{},{"ty":0,"nm":"Input Black","mn":"ADBE Easy Levels2-0003","ix":3,"v":{"a":0,"k":0,"ix":3}},{"ty":0,"nm":"Input White","mn":"ADBE Easy Levels2-0004","ix":4,"v":{"a":0,"k":0,"ix":4}},{"ty":0,"nm":"Gamma","mn":"ADBE Easy Levels2-0005","ix":5,"v":{"a":0,"k":1,"ix":5}},{"ty":0,"nm":"Output Black","mn":"ADBE Easy Levels2-0006","ix":6,"v":{"a":0,"k":0.502,"ix":6}},{"ty":0,"nm":"Output White","mn":"ADBE Easy Levels2-0007","ix":7,"v":{"a":0,"k":1,"ix":7}},{"ty":7,"nm":"Clip To Output Black","mn":"ADBE Easy Levels2-0008","ix":8,"v":{"a":0,"k":2,"ix":8}},{"ty":7,"nm":"Clip To Output White","mn":"ADBE Easy Levels2-0009","ix":9,"v":{"a":0,"k":2,"ix":9}}]}],"w":500,"h":500,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"indeg 1","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[190,190,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Levels","np":11,"mn":"ADBE Easy Levels2","ix":1,"en":1,"ef":[{"ty":7,"nm":"Channel:","mn":"ADBE Easy Levels2-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{},{"ty":0,"nm":"Input Black","mn":"ADBE Easy Levels2-0003","ix":3,"v":{"a":0,"k":0.502,"ix":3}},{"ty":0,"nm":"Input White","mn":"ADBE Easy Levels2-0004","ix":4,"v":{"a":0,"k":0.502,"ix":4}},{"ty":0,"nm":"Gamma","mn":"ADBE Easy Levels2-0005","ix":5,"v":{"a":0,"k":1,"ix":5}},{"ty":0,"nm":"Output Black","mn":"ADBE Easy Levels2-0006","ix":6,"v":{"a":0,"k":0.502,"ix":6}},{"ty":0,"nm":"Output White","mn":"ADBE Easy Levels2-0007","ix":7,"v":{"a":0,"k":1,"ix":7}},{"ty":7,"nm":"Clip To Output Black","mn":"ADBE Easy Levels2-0008","ix":8,"v":{"a":0,"k":2,"ix":8}},{"ty":7,"nm":"Clip To Output White","mn":"ADBE Easy Levels2-0009","ix":9,"v":{"a":0,"k":2,"ix":9}}]}],"w":500,"h":500,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"indeg 2","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[310,190,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Levels","np":11,"mn":"ADBE Easy Levels2","ix":1,"en":1,"ef":[{"ty":7,"nm":"Channel:","mn":"ADBE Easy Levels2-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{},{"ty":0,"nm":"Input Black","mn":"ADBE Easy Levels2-0003","ix":3,"v":{"a":0,"k":1,"ix":3}},{"ty":0,"nm":"Input White","mn":"ADBE Easy Levels2-0004","ix":4,"v":{"a":0,"k":1,"ix":4}},{"ty":0,"nm":"Gamma","mn":"ADBE Easy Levels2-0005","ix":5,"v":{"a":0,"k":1,"ix":5}},{"ty":0,"nm":"Output Black","mn":"ADBE Easy Levels2-0006","ix":6,"v":{"a":0,"k":0.502,"ix":6}},{"ty":0,"nm":"Output White","mn":"ADBE Easy Levels2-0007","ix":7,"v":{"a":0,"k":1,"ix":7}},{"ty":7,"nm":"Clip To Output Black","mn":"ADBE Easy Levels2-0008","ix":8,"v":{"a":0,"k":2,"ix":8}},{"ty":7,"nm":"Clip To Output White","mn":"ADBE Easy Levels2-0009","ix":9,"v":{"a":0,"k":2,"ix":9}}]}],"w":500,"h":500,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":0,"nm":"indeg 3","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[430,190,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Levels","np":11,"mn":"ADBE Easy Levels2","ix":1,"en":1,"ef":[{"ty":7,"nm":"Channel:","mn":"ADBE Easy Levels2-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{},{"ty":0,"nm":"Input Black","mn":"ADBE Easy Levels2-0003","ix":3,"v":{"a":0,"k":1,"ix":3}},{"ty":0,"nm":"Input White","mn":"ADBE Easy Levels2-0004","ix":4,"v":{"a":0,"k":1,"ix":4}},{"ty":0,"nm":"Gamma","mn":"ADBE Easy Levels2-0005","ix":5,"v":{"a":0,"k":1,"ix":5}},{"ty":0,"nm":"Output Black","mn":"ADBE Easy Levels2-0006","ix":6,"v":{"a":0,"k":0.502,"ix":6}},{"ty":0,"nm":"Output White","mn":"ADBE Easy Levels2-0007","ix":7,"v":{"a":0,"k":0.502,"ix":7}},{"ty":7,"nm":"Clip To Output Black","mn":"ADBE Easy Levels2-0008","ix":8,"v":{"a":0,"k":2,"ix":8}},{"ty":7,"nm":"Clip To Output White","mn":"ADBE Easy Levels2-0009","ix":9,"v":{"a":0,"k":2,"ix":9}}]}],"w":500,"h":500,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":0,"nm":"inv 0","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[70,310,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Levels","np":11,"mn":"ADBE Easy Levels2","ix":1,"en":1,"ef":[{"ty":7,"nm":"Channel:","mn":"ADBE Easy Levels2-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{},{"ty":0,"nm":"Input Black","mn":"ADBE Easy Levels2-0003","ix":3,"v":{"a":0,"k":0.502,"ix":3}},{"ty":0,"nm":"Input White","mn":"ADBE Easy Levels2-0004","ix":4,"v":{"a":0,"k":0,"ix":4}},{"ty":0,"nm":"Gamma","mn":"ADBE Easy Levels2-0005","ix":5,"v":{"a":0,"k":2,"ix":5}},{"ty":0,"nm":"Output Black","mn":"ADBE Easy Levels2-0006","ix":6,"v":{"a":0,"k":0.502,"ix":6}},{"ty":0,"nm":"Output White","mn":"ADBE Easy Levels2-0007","ix":7,"v":{"a":0,"k":1,"ix":7}},{"ty":7,"nm":"Clip To Output Black","mn":"ADBE Easy Levels2-0008","ix":8,"v":{"a":0,"k":2,"ix":8}},{"ty":7,"nm":"Clip To Output White","mn":"ADBE Easy Levels2-0009","ix":9,"v":{"a":0,"k":2,"ix":9}}]}],"w":500,"h":500,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":0,"nm":"inv 1","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[190,310,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Levels","np":11,"mn":"ADBE Easy Levels2","ix":1,"en":1,"ef":[{"ty":7,"nm":"Channel:","mn":"ADBE Easy Levels2-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{},{"ty":0,"nm":"Input Black","mn":"ADBE Easy Levels2-0003","ix":3,"v":{"a":0,"k":0,"ix":3}},{"ty":0,"nm":"Input White","mn":"ADBE Easy Levels2-0004","ix":4,"v":{"a":0,"k":0.502,"ix":4}},{"ty":0,"nm":"Gamma","mn":"ADBE Easy Levels2-0005","ix":5,"v":{"a":0,"k":2,"ix":5}},{"ty":0,"nm":"Output Black","mn":"ADBE Easy Levels2-0006","ix":6,"v":{"a":0,"k":1,"ix":6}},{"ty":0,"nm":"Output White","mn":"ADBE Easy Levels2-0007","ix":7,"v":{"a":0,"k":0.502,"ix":7}},{"ty":7,"nm":"Clip To Output Black","mn":"ADBE Easy Levels2-0008","ix":8,"v":{"a":0,"k":2,"ix":8}},{"ty":7,"nm":"Clip To Output White","mn":"ADBE Easy Levels2-0009","ix":9,"v":{"a":0,"k":2,"ix":9}}]}],"w":500,"h":500,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":0,"nm":"inv 2","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[310,310,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Levels","np":11,"mn":"ADBE Easy Levels2","ix":1,"en":1,"ef":[{"ty":7,"nm":"Channel:","mn":"ADBE Easy Levels2-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{},{"ty":0,"nm":"Input Black","mn":"ADBE Easy Levels2-0003","ix":3,"v":{"a":0,"k":0.502,"ix":3}},{"ty":0,"nm":"Input White","mn":"ADBE Easy Levels2-0004","ix":4,"v":{"a":0,"k":0,"ix":4}},{"ty":0,"nm":"Gamma","mn":"ADBE Easy Levels2-0005","ix":5,"v":{"a":0,"k":2,"ix":5}},{"ty":0,"nm":"Output Black","mn":"ADBE Easy Levels2-0006","ix":6,"v":{"a":0,"k":1,"ix":6}},{"ty":0,"nm":"Output White","mn":"ADBE Easy Levels2-0007","ix":7,"v":{"a":0,"k":0.502,"ix":7}},{"ty":7,"nm":"Clip To Output Black","mn":"ADBE Easy Levels2-0008","ix":8,"v":{"a":0,"k":2,"ix":8}},{"ty":7,"nm":"Clip To Output White","mn":"ADBE Easy Levels2-0009","ix":9,"v":{"a":0,"k":2,"ix":9}}]}],"w":500,"h":500,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":0,"nm":"inv 3","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[430,310,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Levels","np":11,"mn":"ADBE Easy Levels2","ix":1,"en":1,"ef":[{"ty":7,"nm":"Channel:","mn":"ADBE Easy Levels2-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{},{"ty":0,"nm":"Input Black","mn":"ADBE Easy Levels2-0003","ix":3,"v":{"a":0,"k":0.737,"ix":3}},{"ty":0,"nm":"Input White","mn":"ADBE Easy Levels2-0004","ix":4,"v":{"a":0,"k":0.285,"ix":4}},{"ty":0,"nm":"Gamma","mn":"ADBE Easy Levels2-0005","ix":5,"v":{"a":0,"k":3,"ix":5}},{"ty":0,"nm":"Output Black","mn":"ADBE Easy Levels2-0006","ix":6,"v":{"a":0,"k":0,"ix":6}},{"ty":0,"nm":"Output White","mn":"ADBE Easy Levels2-0007","ix":7,"v":{"a":0,"k":1,"ix":7}},{"ty":7,"nm":"Clip To Output Black","mn":"ADBE Easy Levels2-0008","ix":8,"v":{"a":0,"k":2,"ix":8}},{"ty":7,"nm":"Clip To Output White","mn":"ADBE Easy Levels2-0009","ix":9,"v":{"a":0,"k":2,"ix":9}}]}],"w":500,"h":500,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":13,"ty":0,"nm":"clip 0","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[70,430,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Levels","np":11,"mn":"ADBE Easy Levels2","ix":1,"en":1,"ef":[{"ty":7,"nm":"Channel:","mn":"ADBE Easy Levels2-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{},{"ty":0,"nm":"Input Black","mn":"ADBE Easy Levels2-0003","ix":3,"v":{"a":0,"k":0.502,"ix":3}},{"ty":0,"nm":"Input White","mn":"ADBE Easy Levels2-0004","ix":4,"v":{"a":0,"k":0,"ix":4}},{"ty":0,"nm":"Gamma","mn":"ADBE Easy Levels2-0005","ix":5,"v":{"a":0,"k":2,"ix":5}},{"ty":0,"nm":"Output Black","mn":"ADBE Easy Levels2-0006","ix":6,"v":{"a":0,"k":0.502,"ix":6}},{"ty":0,"nm":"Output White","mn":"ADBE Easy Levels2-0007","ix":7,"v":{"a":0,"k":1,"ix":7}},{"ty":7,"nm":"Clip To Output Black","mn":"ADBE Easy Levels2-0008","ix":8,"v":{"a":0,"k":1,"ix":8}},{"ty":7,"nm":"Clip To Output White","mn":"ADBE Easy Levels2-0009","ix":9,"v":{"a":0,"k":1,"ix":9}}]}],"w":500,"h":500,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":0,"nm":"clip 1","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[190,430,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Levels","np":11,"mn":"ADBE Easy Levels2","ix":1,"en":1,"ef":[{"ty":7,"nm":"Channel:","mn":"ADBE Easy Levels2-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{},{"ty":0,"nm":"Input Black","mn":"ADBE Easy Levels2-0003","ix":3,"v":{"a":0,"k":0,"ix":3}},{"ty":0,"nm":"Input White","mn":"ADBE Easy Levels2-0004","ix":4,"v":{"a":0,"k":0.502,"ix":4}},{"ty":0,"nm":"Gamma","mn":"ADBE Easy Levels2-0005","ix":5,"v":{"a":0,"k":2,"ix":5}},{"ty":0,"nm":"Output Black","mn":"ADBE Easy Levels2-0006","ix":6,"v":{"a":0,"k":1,"ix":6}},{"ty":0,"nm":"Output White","mn":"ADBE Easy Levels2-0007","ix":7,"v":{"a":0,"k":0.502,"ix":7}},{"ty":7,"nm":"Clip To Output Black","mn":"ADBE Easy Levels2-0008","ix":8,"v":{"a":0,"k":1,"ix":8}},{"ty":7,"nm":"Clip To Output White","mn":"ADBE Easy Levels2-0009","ix":9,"v":{"a":0,"k":1,"ix":9}}]}],"w":500,"h":500,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":15,"ty":0,"nm":"clip 2","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[310,430,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Levels","np":11,"mn":"ADBE Easy Levels2","ix":1,"en":1,"ef":[{"ty":7,"nm":"Channel:","mn":"ADBE Easy Levels2-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{},{"ty":0,"nm":"Input Black","mn":"ADBE Easy Levels2-0003","ix":3,"v":{"a":0,"k":0.502,"ix":3}},{"ty":0,"nm":"Input White","mn":"ADBE Easy Levels2-0004","ix":4,"v":{"a":0,"k":0,"ix":4}},{"ty":0,"nm":"Gamma","mn":"ADBE Easy Levels2-0005","ix":5,"v":{"a":0,"k":2,"ix":5}},{"ty":0,"nm":"Output Black","mn":"ADBE Easy Levels2-0006","ix":6,"v":{"a":0,"k":1,"ix":6}},{"ty":0,"nm":"Output White","mn":"ADBE Easy Levels2-0007","ix":7,"v":{"a":0,"k":0.502,"ix":7}},{"ty":7,"nm":"Clip To Output Black","mn":"ADBE Easy Levels2-0008","ix":8,"v":{"a":0,"k":1,"ix":8}},{"ty":7,"nm":"Clip To Output White","mn":"ADBE Easy Levels2-0009","ix":9,"v":{"a":0,"k":1,"ix":9}}]}],"w":500,"h":500,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":16,"ty":0,"nm":"clip 3","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[430,430,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Levels","np":11,"mn":"ADBE Easy Levels2","ix":1,"en":1,"ef":[{"ty":7,"nm":"Channel:","mn":"ADBE Easy Levels2-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{},{"ty":0,"nm":"Input Black","mn":"ADBE Easy Levels2-0003","ix":3,"v":{"a":0,"k":0.737,"ix":3}},{"ty":0,"nm":"Input White","mn":"ADBE Easy Levels2-0004","ix":4,"v":{"a":0,"k":0.285,"ix":4}},{"ty":0,"nm":"Gamma","mn":"ADBE Easy Levels2-0005","ix":5,"v":{"a":0,"k":3,"ix":5}},{"ty":0,"nm":"Output Black","mn":"ADBE Easy Levels2-0006","ix":6,"v":{"a":0,"k":0,"ix":6}},{"ty":0,"nm":"Output White","mn":"ADBE Easy Levels2-0007","ix":7,"v":{"a":0,"k":1,"ix":7}},{"ty":7,"nm":"Clip To Output Black","mn":"ADBE Easy Levels2-0008","ix":8,"v":{"a":0,"k":1,"ix":8}},{"ty":7,"nm":"Clip To Output White","mn":"ADBE Easy Levels2-0009","ix":9,"v":{"a":0,"k":1,"ix":9}}]}],"w":500,"h":500,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":17,"ty":1,"nm":"bg","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"sw":500,"sh":500,"sc":"#ffffff","ip":0,"op":300,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/modules/skottie/tests/text-valign.json b/modules/skottie/tests/text-valign.json
new file mode 100644
index 0000000..2b644d3
--- /dev/null
+++ b/modules/skottie/tests/text-valign.json
@@ -0,0 +1,628 @@
+{
+  "assets": [],
+  "ddd": 0,
+  "fonts": {
+    "list": [
+      {
+        "ascent": 75,
+        "fClass": "",
+        "fFamily": "Roboto",
+        "fName": "Roboto-Medium",
+        "fPath": "https://fonts.googleapis.com/css?family=Roboto",
+        "fStyle": "Medium",
+        "fWeight": "",
+        "origin": 3
+      }
+    ]
+  },
+  "fr": 60,
+  "h": 500,
+  "ip": 0,
+  "layers": [
+    {
+      "ao": 0,
+      "bm": 0,
+      "ddd": 0,
+      "ind": 1,
+      "ip": 0,
+      "ks": {
+        "a": {
+          "a": 0,
+          "ix": 1,
+          "k": [
+            0,
+            0,
+            0
+          ]
+        },
+        "o": {
+          "a": 0,
+          "ix": 11,
+          "k": 100
+        },
+        "p": {
+          "a": 0,
+          "ix": 2,
+          "k": [
+            335.5,
+            36.5,
+            0
+          ]
+        },
+        "r": {
+          "a": 0,
+          "ix": 10,
+          "k": 0
+        },
+        "s": {
+          "a": 0,
+          "ix": 6,
+          "k": [
+            110.095,
+            100,
+            100
+          ]
+        }
+      },
+      "nm": "Left-aligned 123 abcde fghi",
+      "op": 300,
+      "sr": 1,
+      "st": 0,
+      "t": {
+        "a": [],
+        "d": {
+          "k": [
+            {
+              "s": {
+                "f": "Roboto-Medium",
+                "fc": [
+                  0.973,
+                  0,
+                  0
+                ],
+                "j": 1,
+                "sk_vj": 1,
+                "lh": 45.6000061035156,
+                "ls": 0,
+                "ps": [
+                  -281.343902587891,
+                  -20.5
+                ],
+                "s": 38,
+                "sz": [
+                  181.052062988281,
+                  137
+                ],
+                "t": "Left-aligned 123 abcde fghi",
+                "tr": -100
+              },
+              "t": 0
+            }
+          ]
+        },
+        "m": {
+          "a": {
+            "a": 0,
+            "ix": 2,
+            "k": [
+              0,
+              0
+            ]
+          },
+          "g": 1
+        },
+        "p": {}
+      },
+      "ty": 5
+    },
+    {
+      "ao": 0,
+      "bm": 0,
+      "ddd": 0,
+      "ind": 2,
+      "ip": 0,
+      "ks": {
+        "a": {
+          "a": 0,
+          "ix": 1,
+          "k": [
+            0,
+            0,
+            0
+          ]
+        },
+        "o": {
+          "a": 0,
+          "ix": 11,
+          "k": 100
+        },
+        "p": {
+          "a": 0,
+          "ix": 2,
+          "k": [
+            161.5,
+            84.5,
+            0
+          ]
+        },
+        "r": {
+          "a": 0,
+          "ix": 10,
+          "k": 0
+        },
+        "s": {
+          "a": 0,
+          "ix": 6,
+          "k": [
+            47.482,
+            343.902,
+            100
+          ]
+        }
+      },
+      "nm": "Shape Layer 2",
+      "op": 300,
+      "shapes": [
+        {
+          "d": 1,
+          "hd": false,
+          "mn": "ADBE Vector Shape - Rect",
+          "nm": "Rectangle Path 1",
+          "p": {
+            "a": 0,
+            "ix": 3,
+            "k": [
+              -71,
+              0
+            ]
+          },
+          "r": {
+            "a": 0,
+            "ix": 4,
+            "k": 0
+          },
+          "s": {
+            "a": 0,
+            "ix": 2,
+            "k": [
+              420,
+              41
+            ]
+          },
+          "ty": "rc"
+        },
+        {
+          "bm": 0,
+          "c": {
+            "a": 0,
+            "ix": 4,
+            "k": [
+              0.21953125298,
+              0.21953125298,
+              0.21953125298,
+              1
+            ]
+          },
+          "hd": false,
+          "mn": "ADBE Vector Graphic - Fill",
+          "nm": "Fill 1",
+          "o": {
+            "a": 0,
+            "ix": 5,
+            "k": 100
+          },
+          "r": 1,
+          "ty": "fl"
+        }
+      ],
+      "sr": 1,
+      "st": 0,
+      "ty": 4
+    },
+    {
+      "ao": 0,
+      "bm": 0,
+      "ddd": 0,
+      "ind": 3,
+      "ip": 0,
+      "ks": {
+        "a": {
+          "a": 0,
+          "ix": 1,
+          "k": [
+            0,
+            0,
+            0
+          ]
+        },
+        "o": {
+          "a": 0,
+          "ix": 11,
+          "k": 100
+        },
+        "p": {
+          "a": 0,
+          "ix": 2,
+          "k": [
+            457.5,
+            203.5,
+            0
+          ]
+        },
+        "r": {
+          "a": 0,
+          "ix": 10,
+          "k": 0
+        },
+        "s": {
+          "a": 0,
+          "ix": 6,
+          "k": [
+            110.095,
+            100,
+            100
+          ]
+        }
+      },
+      "nm": "Center-aligned 123 abcde fghi",
+      "op": 300,
+      "sr": 1,
+      "st": 0,
+      "t": {
+        "a": [],
+        "d": {
+          "k": [
+            {
+              "s": {
+                "f": "Roboto-Medium",
+                "fc": [
+                  0.973,
+                  0,
+                  0
+                ],
+                "j": 2,
+                "sk_vj": 1,
+                "lh": 45.6000061035156,
+                "ls": 0,
+                "ps": [
+                  -281.343902587891,
+                  -20.5
+                ],
+                "s": 38,
+                "sz": [
+                  181.052062988281,
+                  137
+                ],
+                "t": "Center-aligned 123 abcde fghi",
+                "tr": -100
+              },
+              "t": 0
+            }
+          ]
+        },
+        "m": {
+          "a": {
+            "a": 0,
+            "ix": 2,
+            "k": [
+              0,
+              0
+            ]
+          },
+          "g": 1
+        },
+        "p": {}
+      },
+      "ty": 5
+    },
+    {
+      "ao": 0,
+      "bm": 0,
+      "ddd": 0,
+      "ind": 4,
+      "ip": 0,
+      "ks": {
+        "a": {
+          "a": 0,
+          "ix": 1,
+          "k": [
+            0,
+            0,
+            0
+          ]
+        },
+        "o": {
+          "a": 0,
+          "ix": 11,
+          "k": 100
+        },
+        "p": {
+          "a": 0,
+          "ix": 2,
+          "k": [
+            283.5,
+            251.5,
+            0
+          ]
+        },
+        "r": {
+          "a": 0,
+          "ix": 10,
+          "k": 0
+        },
+        "s": {
+          "a": 0,
+          "ix": 6,
+          "k": [
+            47.482,
+            343.902,
+            100
+          ]
+        }
+      },
+      "nm": "Shape Layer 3",
+      "op": 300,
+      "shapes": [
+        {
+          "d": 1,
+          "hd": false,
+          "mn": "ADBE Vector Shape - Rect",
+          "nm": "Rectangle Path 1",
+          "p": {
+            "a": 0,
+            "ix": 3,
+            "k": [
+              -71,
+              0
+            ]
+          },
+          "r": {
+            "a": 0,
+            "ix": 4,
+            "k": 0
+          },
+          "s": {
+            "a": 0,
+            "ix": 2,
+            "k": [
+              420,
+              41
+            ]
+          },
+          "ty": "rc"
+        },
+        {
+          "bm": 0,
+          "c": {
+            "a": 0,
+            "ix": 4,
+            "k": [
+              0.21953125298,
+              0.21953125298,
+              0.21953125298,
+              1
+            ]
+          },
+          "hd": false,
+          "mn": "ADBE Vector Graphic - Fill",
+          "nm": "Fill 1",
+          "o": {
+            "a": 0,
+            "ix": 5,
+            "k": 100
+          },
+          "r": 1,
+          "ty": "fl"
+        }
+      ],
+      "sr": 1,
+      "st": 0,
+      "ty": 4
+    },
+    {
+      "ao": 0,
+      "bm": 0,
+      "ddd": 0,
+      "ind": 5,
+      "ip": 0,
+      "ks": {
+        "a": {
+          "a": 0,
+          "ix": 1,
+          "k": [
+            0,
+            0,
+            0
+          ]
+        },
+        "o": {
+          "a": 0,
+          "ix": 11,
+          "k": 100
+        },
+        "p": {
+          "a": 0,
+          "ix": 2,
+          "k": [
+            580.5,
+            369.5,
+            0
+          ]
+        },
+        "r": {
+          "a": 0,
+          "ix": 10,
+          "k": 0
+        },
+        "s": {
+          "a": 0,
+          "ix": 6,
+          "k": [
+            110.095,
+            100,
+            100
+          ]
+        }
+      },
+      "nm": "Right-aligned 123 abcde fghi",
+      "op": 300,
+      "sr": 1,
+      "st": 0,
+      "t": {
+        "a": [],
+        "d": {
+          "k": [
+            {
+              "s": {
+                "f": "Roboto-Medium",
+                "fc": [
+                  0.973,
+                  0,
+                  0
+                ],
+                "j": 0,
+                "sk_vj": 1,
+                "lh": 45.6000061035156,
+                "ls": 0,
+                "ps": [
+                  -281.343902587891,
+                  -20.5
+                ],
+                "s": 38,
+                "sz": [
+                  181.052062988281,
+                  137
+                ],
+                "t": "Right-aligned 123 abcde fghi",
+                "tr": -100
+              },
+              "t": 0
+            }
+          ]
+        },
+        "m": {
+          "a": {
+            "a": 0,
+            "ix": 2,
+            "k": [
+              0,
+              0
+            ]
+          },
+          "g": 1
+        },
+        "p": {}
+      },
+      "ty": 5
+    },
+    {
+      "ao": 0,
+      "bm": 0,
+      "ddd": 0,
+      "ind": 6,
+      "ip": 0,
+      "ks": {
+        "a": {
+          "a": 0,
+          "ix": 1,
+          "k": [
+            0,
+            0,
+            0
+          ]
+        },
+        "o": {
+          "a": 0,
+          "ix": 11,
+          "k": 100
+        },
+        "p": {
+          "a": 0,
+          "ix": 2,
+          "k": [
+            406.5,
+            417.5,
+            0
+          ]
+        },
+        "r": {
+          "a": 0,
+          "ix": 10,
+          "k": 0
+        },
+        "s": {
+          "a": 0,
+          "ix": 6,
+          "k": [
+            47.482,
+            343.902,
+            100
+          ]
+        }
+      },
+      "nm": "Shape Layer 4",
+      "op": 300,
+      "shapes": [
+        {
+          "d": 1,
+          "hd": false,
+          "mn": "ADBE Vector Shape - Rect",
+          "nm": "Rectangle Path 1",
+          "p": {
+            "a": 0,
+            "ix": 3,
+            "k": [
+              -71,
+              0
+            ]
+          },
+          "r": {
+            "a": 0,
+            "ix": 4,
+            "k": 0
+          },
+          "s": {
+            "a": 0,
+            "ix": 2,
+            "k": [
+              420,
+              41
+            ]
+          },
+          "ty": "rc"
+        },
+        {
+          "bm": 0,
+          "c": {
+            "a": 0,
+            "ix": 4,
+            "k": [
+              0.21953125298,
+              0.21953125298,
+              0.21953125298,
+              1
+            ]
+          },
+          "hd": false,
+          "mn": "ADBE Vector Graphic - Fill",
+          "nm": "Fill 1",
+          "o": {
+            "a": 0,
+            "ix": 5,
+            "k": 100
+          },
+          "r": 1,
+          "ty": "fl"
+        }
+      ],
+      "sr": 1,
+      "st": 0,
+      "ty": 4
+    }
+  ],
+  "markers": [],
+  "nm": "Comp 1",
+  "op": 300,
+  "v": "5.5.0",
+  "w": 500
+}
diff --git a/modules/skottie/utils/SkottieUtils.cpp b/modules/skottie/utils/SkottieUtils.cpp
index 4e28a41..b6857dd 100644
--- a/modules/skottie/utils/SkottieUtils.cpp
+++ b/modules/skottie/utils/SkottieUtils.cpp
@@ -15,19 +15,24 @@
 #include "SkOSFile.h"
 #include "SkOSPath.h"
 
+#include <cmath>
+
 namespace skottie_utils {
 
-sk_sp<MultiFrameImageAsset> MultiFrameImageAsset::Make(sk_sp<SkData> data) {
+sk_sp<MultiFrameImageAsset> MultiFrameImageAsset::Make(sk_sp<SkData> data, bool predecode) {
     if (auto codec = SkCodec::MakeFromData(std::move(data))) {
         return sk_sp<MultiFrameImageAsset>(
-              new MultiFrameImageAsset(skstd::make_unique<SkAnimCodecPlayer>(std::move(codec))));
+              new MultiFrameImageAsset(skstd::make_unique<SkAnimCodecPlayer>(std::move(codec)),
+                                                                             predecode));
     }
 
     return nullptr;
 }
 
-MultiFrameImageAsset::MultiFrameImageAsset(std::unique_ptr<SkAnimCodecPlayer> player)
-    : fPlayer(std::move(player)) {
+MultiFrameImageAsset::MultiFrameImageAsset(std::unique_ptr<SkAnimCodecPlayer> player,
+                                           bool predecode)
+    : fPlayer(std::move(player))
+    , fPreDecode(predecode) {
     SkASSERT(fPlayer);
 }
 
@@ -36,8 +41,40 @@
 }
 
 sk_sp<SkImage> MultiFrameImageAsset::getFrame(float t) {
+    auto decode = [](sk_sp<SkImage> image) {
+        SkASSERT(image->isLazyGenerated());
+
+        static constexpr size_t kMaxArea = 2048 * 2048;
+        const auto image_area = SkToSizeT(image->width() * image->height());
+
+        if (image_area > kMaxArea) {
+            // When the image is too large, decode and scale down to a reasonable size.
+            const auto scale = std::sqrt(static_cast<float>(kMaxArea) / image_area);
+            const auto info  = SkImageInfo::MakeN32Premul(scale * image->width(),
+                                                          scale * image->height());
+            SkBitmap bm;
+            if (bm.tryAllocPixels(info, info.minRowBytes()) &&
+                    image->scalePixels(bm.pixmap(),
+                                       SkFilterQuality::kMedium_SkFilterQuality,
+                                       SkImage::kDisallow_CachingHint)) {
+                image = SkImage::MakeFromBitmap(bm);
+            }
+        } else {
+            // When the image size is OK, just force-decode.
+            image = image->makeRasterImage();
+        }
+
+        return image;
+    };
+
     fPlayer->seek(static_cast<uint32_t>(t * 1000));
-    return fPlayer->getFrame();
+    auto frame = fPlayer->getFrame();
+
+    if (fPreDecode && frame && frame->isLazyGenerated()) {
+        frame = decode(std::move(frame));
+    }
+
+    return frame;
 }
 
 sk_sp<FileResourceProvider> FileResourceProvider::Make(SkString base_dir) {
diff --git a/modules/skottie/utils/SkottieUtils.h b/modules/skottie/utils/SkottieUtils.h
index cb28b5d..fe7d2b8 100644
--- a/modules/skottie/utils/SkottieUtils.h
+++ b/modules/skottie/utils/SkottieUtils.h
@@ -27,16 +27,24 @@
 
 class MultiFrameImageAsset final : public skottie::ImageAsset {
 public:
-    static sk_sp<MultiFrameImageAsset> Make(sk_sp<SkData>);
+    /**
+    * By default, images are decoded on-the-fly, at rasterization time.
+    * Large images may cause jank as decoding is expensive (and can thrash internal caches).
+    *
+    * Pass |predecode| true to force-decode all images upfront, at the cost of potentially more RAM
+    * and slower animation build times.
+    */
+    static sk_sp<MultiFrameImageAsset> Make(sk_sp<SkData>, bool predecode = false);
 
     bool isMultiFrame() override;
 
     sk_sp<SkImage> getFrame(float t) override;
 
 private:
-    explicit MultiFrameImageAsset(std::unique_ptr<SkAnimCodecPlayer>);
+    explicit MultiFrameImageAsset(std::unique_ptr<SkAnimCodecPlayer>, bool predecode);
 
     std::unique_ptr<SkAnimCodecPlayer> fPlayer;
+    bool                               fPreDecode;
 
     using INHERITED = skottie::ImageAsset;
 };
diff --git a/modules/sksg/BUILD.gn b/modules/sksg/BUILD.gn
index 48e380c..93845f6 100644
--- a/modules/sksg/BUILD.gn
+++ b/modules/sksg/BUILD.gn
@@ -3,11 +3,14 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("../../gn/skia.gni")
+
 config("public_config") {
   include_dirs = [ "include" ]
 }
 
 component("sksg") {
+  check_includes = false
   import("sksg.gni")
   public_configs = [ ":public_config" ]
   sources = skia_sksg_sources
@@ -17,7 +20,7 @@
   ]
 }
 
-if (defined(is_skia_standalone)) {
+if (defined(is_skia_standalone) && skia_enable_tools) {
   source_set("tests") {
     testonly = true
 
diff --git a/modules/sksg/include/SkSGClipEffect.h b/modules/sksg/include/SkSGClipEffect.h
index 52ba7c7..309127f 100644
--- a/modules/sksg/include/SkSGClipEffect.h
+++ b/modules/sksg/include/SkSGClipEffect.h
@@ -33,6 +33,7 @@
     ClipEffect(sk_sp<RenderNode>, sk_sp<GeometryNode>, bool aa);
 
     void onRender(SkCanvas*, const RenderContext*) const override;
+    const RenderNode* onNodeAt(const SkPoint&)     const override;
 
     SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
 
diff --git a/modules/sksg/include/SkSGColor.h b/modules/sksg/include/SkSGColor.h
deleted file mode 100644
index a19921c..0000000
--- a/modules/sksg/include/SkSGColor.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkSGColor_DEFINED
-#define SkSGColor_DEFINED
-
-#include "SkSGPaintNode.h"
-
-#include "SkColor.h"
-
-namespace sksg {
-
-/**
- * Concrete Paint node, wrapping an SkColor.
- */
-class Color : public PaintNode {
-public:
-    static sk_sp<Color> Make(SkColor c) { return sk_sp<Color>(new Color(c)); }
-
-    SG_ATTRIBUTE(Color, SkColor, fColor)
-
-protected:
-    void onApplyToPaint(SkPaint*) const override;
-
-private:
-    explicit Color(SkColor);
-
-    SkColor fColor;
-};
-
-} // namespace sksg
-
-#endif // SkSGColor_DEFINED
diff --git a/modules/sksg/include/SkSGColorFilter.h b/modules/sksg/include/SkSGColorFilter.h
index a9e12e7..7c02958 100644
--- a/modules/sksg/include/SkSGColorFilter.h
+++ b/modules/sksg/include/SkSGColorFilter.h
@@ -12,6 +12,8 @@
 
 #include "SkBlendMode.h"
 
+#include <vector>
+
 class SkColorFilter;
 
 namespace sksg {
@@ -20,49 +22,100 @@
 
 /**
  * Base class for nodes which apply a color filter when rendering their descendants.
- *
  */
 class ColorFilter : public EffectNode {
 protected:
     explicit ColorFilter(sk_sp<RenderNode>);
 
     void onRender(SkCanvas*, const RenderContext*) const final;
+    const RenderNode* onNodeAt(const SkPoint&)     const final;
 
-    sk_sp<SkColorFilter> fColorFilter;
+    SkRect onRevalidate(InvalidationController*, const SkMatrix&) final;
+
+    virtual sk_sp<SkColorFilter> onRevalidateFilter() = 0;
 
 private:
+    sk_sp<SkColorFilter> fColorFilter;
+
     typedef EffectNode INHERITED;
 };
 
 /**
- * Concrete SkModeColorFilter Effect node.
+ * Wrapper for externally-managed SkColorFilters.
  *
+ * Allows attaching non-sksg color filters to the render tree.
  */
-class ColorModeFilter final : public ColorFilter {
+class ExternalColorFilter final : public EffectNode {
 public:
-    ~ColorModeFilter() override;
+    static sk_sp<ExternalColorFilter> Make(sk_sp<RenderNode> child);
 
-    static sk_sp<ColorModeFilter> Make(sk_sp<RenderNode> child, sk_sp<Color> color,
-                                       SkBlendMode mode) {
-        return (child && color)
-            ? sk_sp<ColorModeFilter>(new ColorModeFilter(std::move(child), std::move(color), mode))
-            : nullptr;
-    }
+    ~ExternalColorFilter() override;
 
-    SG_ATTRIBUTE(Mode , SkBlendMode, fMode )
+    SG_ATTRIBUTE(ColorFilter, sk_sp<SkColorFilter>, fColorFilter)
 
 protected:
-    SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+    void onRender(SkCanvas*, const RenderContext*) const override;
 
 private:
-    ColorModeFilter(sk_sp<RenderNode>, sk_sp<Color>, SkBlendMode);
+    explicit ExternalColorFilter(sk_sp<RenderNode>);
 
-    sk_sp<Color> fColor;
-    SkBlendMode  fMode;
+    sk_sp<SkColorFilter> fColorFilter;
+
+    using INHERITED = EffectNode;
+};
+
+/**
+ * Concrete SkModeColorFilter Effect node.
+ */
+class ModeColorFilter final : public ColorFilter {
+public:
+    ~ModeColorFilter() override;
+
+    static sk_sp<ModeColorFilter> Make(sk_sp<RenderNode> child,
+                                       sk_sp<Color> color,
+                                       SkBlendMode mode);
+
+protected:
+    sk_sp<SkColorFilter> onRevalidateFilter() override;
+
+private:
+    ModeColorFilter(sk_sp<RenderNode>, sk_sp<Color>, SkBlendMode);
+
+    const sk_sp<Color> fColor;
+    const SkBlendMode  fMode;
 
     typedef ColorFilter INHERITED;
 };
 
+/**
+ * Tint/multi-tone color effect: maps RGB colors to the [C0,C1][C1,C2]..[Cn-1,Cn] gradient
+ * based on input luminance (where the colors are evenly distributed across the luminance domain),
+ * then mixes with the input based on weight.  Leaves alpha unchanged.
+ */
+class GradientColorFilter final : public ColorFilter {
+public:
+    ~GradientColorFilter() override;
+
+    static sk_sp<GradientColorFilter> Make(sk_sp<RenderNode> child,
+                                           sk_sp<Color> c0, sk_sp<Color> c1);
+    static sk_sp<GradientColorFilter> Make(sk_sp<RenderNode> child,
+                                           std::vector<sk_sp<Color>>);
+
+    SG_ATTRIBUTE(Weight, float, fWeight)
+
+protected:
+    sk_sp<SkColorFilter> onRevalidateFilter() override;
+
+private:
+    GradientColorFilter(sk_sp<RenderNode>, std::vector<sk_sp<Color>>);
+
+    const std::vector<sk_sp<Color>> fColors;
+
+    float                           fWeight = 0;
+
+    using INHERITED = ColorFilter;
+};
+
 } // namespace sksg
 
 #endif // SkSGColorFilter_DEFINED
diff --git a/modules/sksg/include/SkSGDraw.h b/modules/sksg/include/SkSGDraw.h
index 018dd1a..d7d0959 100644
--- a/modules/sksg/include/SkSGDraw.h
+++ b/modules/sksg/include/SkSGDraw.h
@@ -8,13 +8,12 @@
 #ifndef SkSGDraw_DEFINED
 #define SkSGDraw_DEFINED
 
+#include "SkSGGeometryNode.h"
+#include "SkSGPaint.h"
 #include "SkSGRenderNode.h"
 
 namespace sksg {
 
-class GeometryNode;
-class PaintNode;
-
 /**
  * Concrete rendering node.
  *
@@ -33,6 +32,7 @@
     ~Draw() override;
 
     void onRender(SkCanvas*, const RenderContext*) const override;
+    const RenderNode* onNodeAt(const SkPoint&)     const override;
 
     SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
 
diff --git a/modules/sksg/include/SkSGEffectNode.h b/modules/sksg/include/SkSGEffectNode.h
index 575432d..1fe9468 100644
--- a/modules/sksg/include/SkSGEffectNode.h
+++ b/modules/sksg/include/SkSGEffectNode.h
@@ -20,13 +20,16 @@
  */
 class EffectNode : public RenderNode {
 protected:
-    explicit EffectNode(sk_sp<RenderNode>);
+    explicit EffectNode(sk_sp<RenderNode>, uint32_t inval_traits = 0);
     ~EffectNode() override;
 
     void onRender(SkCanvas*, const RenderContext*) const override;
+    const RenderNode* onNodeAt(const SkPoint&)     const override;
 
     SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
 
+    const sk_sp<RenderNode>& getChild() const { return fChild; }
+
 private:
     sk_sp<RenderNode> fChild;
 
diff --git a/modules/sksg/include/SkSGGeometryNode.h b/modules/sksg/include/SkSGGeometryNode.h
index 7ce3aa9..66be09d 100644
--- a/modules/sksg/include/SkSGGeometryNode.h
+++ b/modules/sksg/include/SkSGGeometryNode.h
@@ -27,6 +27,8 @@
     void clip(SkCanvas*, bool antiAlias) const;
     void draw(SkCanvas*, const SkPaint&) const;
 
+    bool contains(const SkPoint&) const;
+
     SkPath asPath() const;
 
 protected:
@@ -36,6 +38,8 @@
 
     virtual void onDraw(SkCanvas*, const SkPaint&) const = 0;
 
+    virtual bool onContains(const SkPoint&) const = 0;
+
     virtual SkPath onAsPath() const = 0;
 
 private:
diff --git a/modules/sksg/include/SkSGGeometryTransform.h b/modules/sksg/include/SkSGGeometryTransform.h
index bf297eb..f9907ed 100644
--- a/modules/sksg/include/SkSGGeometryTransform.h
+++ b/modules/sksg/include/SkSGGeometryTransform.h
@@ -37,6 +37,7 @@
 protected:
     void onClip(SkCanvas*, bool antiAlias) const override;
     void onDraw(SkCanvas*, const SkPaint&) const override;
+    bool onContains(const SkPoint&)        const override;
 
     SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
     SkPath onAsPath() const override;
diff --git a/modules/sksg/include/SkSGGradient.h b/modules/sksg/include/SkSGGradient.h
index d69cb14..c58b77d 100644
--- a/modules/sksg/include/SkSGGradient.h
+++ b/modules/sksg/include/SkSGGradient.h
@@ -8,7 +8,7 @@
 #ifndef SkSGGradient_DEFINED
 #define SkSGGradient_DEFINED
 
-#include "SkSGPaintNode.h"
+#include "SkSGRenderEffect.h"
 
 #include "SkColor.h"
 #include "SkPoint.h"
@@ -22,7 +22,7 @@
 /**
  * Gradient base class.
  */
-class Gradient : public PaintNode {
+class Gradient : public Shader {
 public:
     struct ColorStop {
         SkScalar fPosition;
@@ -34,10 +34,10 @@
     };
 
     SG_ATTRIBUTE(ColorStops, std::vector<ColorStop>, fColorStops)
-    SG_ATTRIBUTE(TileMode  , SkShader::TileMode    , fTileMode  )
+    SG_ATTRIBUTE(TileMode  , SkTileMode            , fTileMode  )
 
 protected:
-    void onApplyToPaint(SkPaint*) const final;
+    sk_sp<SkShader> onRevalidateShader() final;
 
     virtual sk_sp<SkShader> onMakeShader(const std::vector<SkColor>& colors,
                                          const std::vector<SkScalar>& positions) const = 0;
@@ -47,9 +47,9 @@
 
 private:
     std::vector<ColorStop> fColorStops;
-    SkShader::TileMode     fTileMode = SkShader::kClamp_TileMode;
+    SkTileMode             fTileMode = SkTileMode::kClamp;
 
-    using INHERITED = PaintNode;
+    using INHERITED = Shader;
 };
 
 class LinearGradient final : public Gradient {
diff --git a/modules/sksg/include/SkSGGroup.h b/modules/sksg/include/SkSGGroup.h
index bb53555..ccb9cf8 100644
--- a/modules/sksg/include/SkSGGroup.h
+++ b/modules/sksg/include/SkSGGroup.h
@@ -39,6 +39,8 @@
     ~Group() override;
 
     void onRender(SkCanvas*, const RenderContext*) const override;
+    const RenderNode* onNodeAt(const SkPoint&)     const override;
+
     SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
 
 private:
diff --git a/modules/sksg/include/SkSGImage.h b/modules/sksg/include/SkSGImage.h
index b6aef47..75868c7 100644
--- a/modules/sksg/include/SkSGImage.h
+++ b/modules/sksg/include/SkSGImage.h
@@ -34,12 +34,13 @@
     explicit Image(sk_sp<SkImage>);
 
     void onRender(SkCanvas*, const RenderContext*) const override;
+    const RenderNode* onNodeAt(const SkPoint&)     const override;
 
     SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
 
 private:
     sk_sp<SkImage>  fImage;
-    SkFilterQuality fQuality   = kLow_SkFilterQuality;
+    SkFilterQuality fQuality   = kNone_SkFilterQuality;
     bool            fAntiAlias = true;
 
     typedef RenderNode INHERITED;
diff --git a/modules/sksg/include/SkSGMaskEffect.h b/modules/sksg/include/SkSGMaskEffect.h
index f668777..9837d0e 100644
--- a/modules/sksg/include/SkSGMaskEffect.h
+++ b/modules/sksg/include/SkSGMaskEffect.h
@@ -36,6 +36,7 @@
     MaskEffect(sk_sp<RenderNode>, sk_sp<RenderNode> mask, Mode);
 
     void onRender(SkCanvas*, const RenderContext*) const override;
+    const RenderNode* onNodeAt(const SkPoint&)     const override;
 
     SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
 
diff --git a/modules/sksg/include/SkSGMerge.h b/modules/sksg/include/SkSGMerge.h
index c4957f4..b37530a 100644
--- a/modules/sksg/include/SkSGMerge.h
+++ b/modules/sksg/include/SkSGMerge.h
@@ -50,6 +50,7 @@
 protected:
     void onClip(SkCanvas*, bool antiAlias) const override;
     void onDraw(SkCanvas*, const SkPaint&) const override;
+    bool onContains(const SkPoint&)        const override;
 
     SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
     SkPath onAsPath() const override;
diff --git a/modules/sksg/include/SkSGNode.h b/modules/sksg/include/SkSGNode.h
index 15db2e2..68bcbab 100644
--- a/modules/sksg/include/SkSGNode.h
+++ b/modules/sksg/include/SkSGNode.h
@@ -39,7 +39,10 @@
     enum InvalTraits {
         // Nodes with this trait never generate direct damage -- instead,
         // the damage bubbles up to ancestors.
-        kBubbleDamage_Trait = 1 << 0,
+        kBubbleDamage_Trait   = 1 << 0,
+
+        // Nodes with this trait obscure the descendants' damage and always override it.
+        kOverrideDamage_Trait = 1 << 1,
     };
 
     explicit Node(uint32_t invalTraits);
diff --git a/modules/sksg/include/SkSGOpacityEffect.h b/modules/sksg/include/SkSGOpacityEffect.h
index a982304..8e1105c 100644
--- a/modules/sksg/include/SkSGOpacityEffect.h
+++ b/modules/sksg/include/SkSGOpacityEffect.h
@@ -28,6 +28,7 @@
     OpacityEffect(sk_sp<RenderNode>, float);
 
     void onRender(SkCanvas*, const RenderContext*) const override;
+    const RenderNode* onNodeAt(const SkPoint&)     const override;
 
     SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
 
diff --git a/modules/sksg/include/SkSGPaint.h b/modules/sksg/include/SkSGPaint.h
new file mode 100644
index 0000000..52dd7b4
--- /dev/null
+++ b/modules/sksg/include/SkSGPaint.h
@@ -0,0 +1,101 @@
+/*
+ * 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 SkSGPaint_DEFINED
+#define SkSGPaint_DEFINED
+
+#include "SkSGNode.h"
+
+#include "SkColor.h"
+#include "SkPaint.h"
+
+namespace sksg {
+
+class Shader;
+
+/**
+ * Base class for nodes which provide a 'paint' (as opposed to geometry) for
+ * drawing (e.g. colors, gradients, patterns).
+ *
+ * Roughly equivalent to Skia's SkPaint.
+ */
+class PaintNode : public Node {
+public:
+    SkPaint makePaint() const;
+
+    SG_ATTRIBUTE(AntiAlias  , bool          , fAntiAlias  )
+    SG_ATTRIBUTE(Opacity    , SkScalar      , fOpacity    )
+    SG_ATTRIBUTE(BlendMode  , SkBlendMode   , fBlendMode  )
+    SG_ATTRIBUTE(StrokeWidth, SkScalar      , fStrokeWidth)
+    SG_ATTRIBUTE(StrokeMiter, SkScalar      , fStrokeMiter)
+    SG_ATTRIBUTE(Style      , SkPaint::Style, fStyle      )
+    SG_ATTRIBUTE(StrokeJoin , SkPaint::Join , fStrokeJoin )
+    SG_ATTRIBUTE(StrokeCap  , SkPaint::Cap  , fStrokeCap  )
+
+protected:
+    PaintNode();
+
+    virtual void onApplyToPaint(SkPaint*) const = 0;
+
+private:
+    SkScalar       fOpacity     = 1,
+                   fStrokeWidth = 1,
+                   fStrokeMiter = 4;
+    bool           fAntiAlias   = false;
+    SkBlendMode    fBlendMode   = SkBlendMode::kSrcOver;
+    SkPaint::Style fStyle       = SkPaint::kFill_Style;
+    SkPaint::Join  fStrokeJoin  = SkPaint::kMiter_Join;
+    SkPaint::Cap   fStrokeCap   = SkPaint::kButt_Cap;
+
+    typedef Node INHERITED;
+};
+
+/**
+ * Concrete Paint node, wrapping an SkColor.
+ */
+class Color : public PaintNode {
+public:
+    static sk_sp<Color> Make(SkColor c);
+
+    SG_ATTRIBUTE(Color, SkColor, fColor)
+
+protected:
+    SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+
+    void onApplyToPaint(SkPaint*) const override;
+
+private:
+    explicit Color(SkColor);
+
+    SkColor fColor;
+};
+
+/**
+ * Shader-based paint.
+ */
+class ShaderPaint final : public PaintNode {
+public:
+    ~ShaderPaint() override;
+
+    static sk_sp<ShaderPaint> Make(sk_sp<Shader>);
+
+protected:
+    SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+
+    void onApplyToPaint(SkPaint*) const override;
+
+private:
+    explicit ShaderPaint(sk_sp<Shader>);
+
+    const sk_sp<Shader> fShader;
+
+    using INHERITED = PaintNode;
+};
+
+} // namespace sksg
+
+#endif // SkSGPaint_DEFINED
diff --git a/modules/sksg/include/SkSGPaintNode.h b/modules/sksg/include/SkSGPaintNode.h
deleted file mode 100644
index dc4af49..0000000
--- a/modules/sksg/include/SkSGPaintNode.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkSGPaintNode_DEFINED
-#define SkSGPaintNode_DEFINED
-
-#include "SkSGNode.h"
-
-#include "SkPaint.h"
-
-namespace sksg {
-
-/**
- * Base class for nodes which provide a 'paint' (as opposed to geometry) for
- * drawing (e.g. colors, gradients, patterns).
- *
- * Roughly equivalent to Skia's SkPaint.
- */
-class PaintNode : public Node {
-public:
-    SkPaint makePaint() const;
-
-    SG_ATTRIBUTE(AntiAlias  , bool          , fAntiAlias  )
-    SG_ATTRIBUTE(Opacity    , SkScalar      , fOpacity    )
-    SG_ATTRIBUTE(BlendMode  , SkBlendMode   , fBlendMode  )
-    SG_ATTRIBUTE(StrokeWidth, SkScalar      , fStrokeWidth)
-    SG_ATTRIBUTE(StrokeMiter, SkScalar      , fStrokeMiter)
-    SG_ATTRIBUTE(Style      , SkPaint::Style, fStyle      )
-    SG_ATTRIBUTE(StrokeJoin , SkPaint::Join , fStrokeJoin )
-    SG_ATTRIBUTE(StrokeCap  , SkPaint::Cap  , fStrokeCap  )
-
-protected:
-    PaintNode();
-
-    virtual void onApplyToPaint(SkPaint*) const = 0;
-
-    SkRect onRevalidate(InvalidationController*, const SkMatrix&) final;
-
-private:
-    SkScalar       fOpacity     = 1,
-                   fStrokeWidth = 1,
-                   fStrokeMiter = 4;
-    bool           fAntiAlias   = false;
-    SkBlendMode    fBlendMode   = SkBlendMode::kSrcOver;
-    SkPaint::Style fStyle       = SkPaint::kFill_Style;
-    SkPaint::Join  fStrokeJoin  = SkPaint::kMiter_Join;
-    SkPaint::Cap   fStrokeCap   = SkPaint::kButt_Cap;
-
-    typedef Node INHERITED;
-};
-
-} // namespace sksg
-
-#endif // SkSGGeometryNode_DEFINED
diff --git a/modules/sksg/include/SkSGPath.h b/modules/sksg/include/SkSGPath.h
index 1a87188..55d16fa 100644
--- a/modules/sksg/include/SkSGPath.h
+++ b/modules/sksg/include/SkSGPath.h
@@ -31,6 +31,7 @@
 protected:
     void onClip(SkCanvas*, bool antiAlias) const override;
     void onDraw(SkCanvas*, const SkPaint&) const override;
+    bool onContains(const SkPoint&)        const override;
 
     SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
     SkPath onAsPath() const override;
diff --git a/modules/sksg/include/SkSGPlane.h b/modules/sksg/include/SkSGPlane.h
index c0a2637..f8338a1 100644
--- a/modules/sksg/include/SkSGPlane.h
+++ b/modules/sksg/include/SkSGPlane.h
@@ -25,6 +25,7 @@
 protected:
     void onClip(SkCanvas*, bool antiAlias) const override;
     void onDraw(SkCanvas*, const SkPaint&) const override;
+    bool onContains(const SkPoint&)        const override;
 
     SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
     SkPath onAsPath() const override;
diff --git a/modules/sksg/include/SkSGRect.h b/modules/sksg/include/SkSGRect.h
index 72133cf..a4666b4 100644
--- a/modules/sksg/include/SkSGRect.h
+++ b/modules/sksg/include/SkSGRect.h
@@ -38,6 +38,7 @@
 protected:
     void onClip(SkCanvas*, bool antiAlias) const override;
     void onDraw(SkCanvas*, const SkPaint&) const override;
+    bool onContains(const SkPoint&)        const override;
 
     SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
     SkPath onAsPath() const override;
@@ -80,6 +81,7 @@
 protected:
     void onClip(SkCanvas*, bool antiAlias) const override;
     void onDraw(SkCanvas*, const SkPaint&) const override;
+    bool onContains(const SkPoint&)        const override;
 
     SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
     SkPath onAsPath() const override;
diff --git a/modules/sksg/include/SkSGRenderEffect.h b/modules/sksg/include/SkSGRenderEffect.h
new file mode 100644
index 0000000..3d9a830
--- /dev/null
+++ b/modules/sksg/include/SkSGRenderEffect.h
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkSGRenderEffect_DEFINED
+#define SkSGRenderEffect_DEFINED
+
+#include "SkSGEffectNode.h"
+
+#include "SkBlendMode.h"
+#include "SkBlurImageFilter.h"
+#include "SkColor.h"
+
+#include <memory>
+#include <vector>
+
+// TODO: merge EffectNode.h with this header
+
+class SkImageFilter;
+class SkShader;
+
+namespace sksg {
+
+/**
+ * Shader base class.
+ */
+class Shader : public Node {
+public:
+    ~Shader() override;
+
+    const sk_sp<SkShader>& getShader() const {
+        SkASSERT(!this->hasInval());
+        return fShader;
+    }
+
+protected:
+    Shader();
+
+    SkRect onRevalidate(InvalidationController*, const SkMatrix&) final;
+
+    virtual sk_sp<SkShader> onRevalidateShader() = 0;
+
+private:
+    sk_sp<SkShader> fShader;
+
+    using INHERITED = Node;
+};
+
+/**
+ * Attaches a shader to the render DAG.
+ */
+class ShaderEffect final : public EffectNode {
+public:
+    ~ShaderEffect() override;
+
+    static sk_sp<ShaderEffect> Make(sk_sp<RenderNode> child, sk_sp<Shader> shader = 0);
+
+    void setShader(sk_sp<Shader>);
+
+protected:
+    void onRender(SkCanvas*, const RenderContext*) const override;
+
+    SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+
+private:
+    ShaderEffect(sk_sp<RenderNode> child, sk_sp<Shader> shader);
+
+    sk_sp<Shader> fShader;
+
+    using INHERITED = EffectNode;
+};
+
+/**
+ * ImageFilter base class.
+ */
+class ImageFilter : public Node {
+public:
+    ~ImageFilter() override;
+
+    const sk_sp<SkImageFilter>& getFilter() const {
+        SkASSERT(!this->hasInval());
+        return fFilter;
+    }
+
+protected:
+    explicit ImageFilter(sk_sp<ImageFilter> input = 0);
+
+    using InputsT = std::vector<sk_sp<ImageFilter>>;
+    explicit ImageFilter(std::unique_ptr<InputsT> inputs);
+
+    SkRect onRevalidate(InvalidationController*, const SkMatrix&) final;
+
+    virtual sk_sp<SkImageFilter> onRevalidateFilter() = 0;
+
+    sk_sp<SkImageFilter> refInput(size_t) const;
+
+private:
+    const std::unique_ptr<InputsT> fInputs;
+
+    sk_sp<SkImageFilter>           fFilter;
+
+    using INHERITED = Node;
+};
+
+/**
+ * Attaches an ImageFilter (chain) to the render DAG.
+ */
+class ImageFilterEffect final : public EffectNode {
+public:
+    ~ImageFilterEffect() override;
+
+    static sk_sp<RenderNode> Make(sk_sp<RenderNode> child, sk_sp<ImageFilter> filter);
+
+protected:
+    void onRender(SkCanvas*, const RenderContext*) const override;
+    const RenderNode* onNodeAt(const SkPoint&)     const override;
+
+    SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+
+private:
+    ImageFilterEffect(sk_sp<RenderNode> child, sk_sp<ImageFilter> filter);
+
+    sk_sp<ImageFilter> fImageFilter;
+
+    using INHERITED = EffectNode;
+};
+
+/**
+ * SkDropShadowImageFilter node.
+ */
+class DropShadowImageFilter final : public ImageFilter {
+public:
+    ~DropShadowImageFilter() override;
+
+    static sk_sp<DropShadowImageFilter> Make(sk_sp<ImageFilter> input = nullptr);
+
+    enum class Mode { kShadowAndForeground, kShadowOnly };
+
+    SG_ATTRIBUTE(Offset, SkVector, fOffset)
+    SG_ATTRIBUTE(Sigma , SkVector, fSigma )
+    SG_ATTRIBUTE(Color , SkColor , fColor )
+    SG_ATTRIBUTE(Mode  , Mode    , fMode  )
+
+protected:
+    sk_sp<SkImageFilter> onRevalidateFilter() override;
+
+private:
+    explicit DropShadowImageFilter(sk_sp<ImageFilter> input);
+
+    SkVector             fOffset = { 0, 0 },
+                         fSigma  = { 0, 0 };
+    SkColor              fColor  = SK_ColorBLACK;
+    Mode                 fMode   = Mode::kShadowAndForeground;
+
+    using INHERITED = ImageFilter;
+};
+
+/**
+ * SkBlurImageFilter node.
+ */
+class BlurImageFilter final : public ImageFilter {
+public:
+    ~BlurImageFilter() override;
+
+    static sk_sp<BlurImageFilter> Make(sk_sp<ImageFilter> input = nullptr);
+
+    SG_ATTRIBUTE(Sigma   , SkVector                   , fSigma   )
+    SG_ATTRIBUTE(TileMode, SkBlurImageFilter::TileMode, fTileMode)
+
+protected:
+    sk_sp<SkImageFilter> onRevalidateFilter() override;
+
+private:
+    explicit BlurImageFilter(sk_sp<ImageFilter> input);
+
+    SkVector                    fSigma    = { 0, 0 };
+    SkBlurImageFilter::TileMode fTileMode = SkBlurImageFilter::kClamp_TileMode;
+
+    using INHERITED = ImageFilter;
+};
+
+/**
+ * Applies a SkBlendMode to descendant render nodes.
+ */
+class BlendModeEffect final : public EffectNode {
+public:
+    ~BlendModeEffect() override;
+
+    static sk_sp<BlendModeEffect> Make(sk_sp<RenderNode> child,
+                                       SkBlendMode = SkBlendMode::kSrcOver);
+
+    SG_ATTRIBUTE(Mode, SkBlendMode, fMode)
+
+protected:
+    void onRender(SkCanvas*, const RenderContext*) const override;
+    const RenderNode* onNodeAt(const SkPoint&)     const override;
+
+private:
+    BlendModeEffect(sk_sp<RenderNode>, SkBlendMode);
+
+    SkBlendMode fMode;
+
+    using INHERITED = EffectNode;
+};
+
+} // namespace sksg
+
+#endif // SkSGRenderEffect_DEFINED
diff --git a/modules/sksg/include/SkSGRenderNode.h b/modules/sksg/include/SkSGRenderNode.h
index 5d9356f..b76ea9d 100644
--- a/modules/sksg/include/SkSGRenderNode.h
+++ b/modules/sksg/include/SkSGRenderNode.h
@@ -10,9 +10,12 @@
 
 #include "SkSGNode.h"
 
+#include "SkBlendMode.h"
 #include "SkColorFilter.h"
+#include "SkShader.h"
 
 class SkCanvas;
+class SkImageFilter;
 class SkPaint;
 
 namespace sksg {
@@ -28,20 +31,30 @@
     // Render the node and its descendants to the canvas.
     void render(SkCanvas*, const RenderContext* = nullptr) const;
 
+    // Perform a front-to-back hit-test, and return the RenderNode located at |point|.
+    // Normally, hit-testing stops at leaf Draw nodes.
+    const RenderNode* nodeAt(const SkPoint& point) const;
+
 protected:
-    RenderNode();
+    explicit RenderNode(uint32_t inval_traits = 0);
 
     virtual void onRender(SkCanvas*, const RenderContext*) const = 0;
+    virtual const RenderNode* onNodeAt(const SkPoint& p)   const = 0;
 
     // Paint property overrides.
     // These are deferred until we can determine whether they can be applied to the individual
     // draw paints, or whether they require content isolation (applied to a layer).
     struct RenderContext {
         sk_sp<SkColorFilter> fColorFilter;
-        float                fOpacity = 1;
+        sk_sp<SkShader>      fShader;
+        SkMatrix             fShaderCTM = SkMatrix::I();
+        float                fOpacity   = 1;
+        SkBlendMode          fBlendMode = SkBlendMode::kSrcOver;
 
-        // Returns true if the paint was modified.
-        bool modulatePaint(SkPaint*) const;
+        // Returns true if the paint overrides require a layer when applied to non-atomic draws.
+        bool requiresIsolation() const;
+
+        void modulatePaint(const SkMatrix& ctm, SkPaint*) const;
     };
 
     class ScopedRenderContext final {
@@ -67,10 +80,18 @@
         // Add (cumulative) paint overrides to a render node sub-DAG.
         ScopedRenderContext&& modulateOpacity(float opacity);
         ScopedRenderContext&& modulateColorFilter(sk_sp<SkColorFilter>);
+        ScopedRenderContext&& modulateShader(sk_sp<SkShader>, const SkMatrix& shader_ctm);
+        ScopedRenderContext&& modulateBlendMode(SkBlendMode);
 
         // Force content isolation for a node sub-DAG by applying the RenderContext
         // overrides via a layer.
-        ScopedRenderContext&& setIsolation(const SkRect& bounds, bool do_isolate);
+        ScopedRenderContext&& setIsolation(const SkRect& bounds, const SkMatrix& ctm,
+                                           bool do_isolate);
+
+        // Similarly, force content isolation by applying the RenderContext overrides and
+        // an image filter via a single layer.
+        ScopedRenderContext&& setFilterIsolation(const SkRect& bounds, const SkMatrix& ctm,
+                                                 sk_sp<SkImageFilter>);
 
     private:
         // stack-only
@@ -87,6 +108,8 @@
     };
 
 private:
+    friend class ImageFilterEffect;
+
     typedef Node INHERITED;
 };
 
diff --git a/modules/sksg/include/SkSGRoundEffect.h b/modules/sksg/include/SkSGRoundEffect.h
index 67124ca..4787a8c 100644
--- a/modules/sksg/include/SkSGRoundEffect.h
+++ b/modules/sksg/include/SkSGRoundEffect.h
@@ -30,6 +30,7 @@
 protected:
     void onClip(SkCanvas*, bool antiAlias) const override;
     void onDraw(SkCanvas*, const SkPaint&) const override;
+    bool onContains(const SkPoint&)        const override;
 
     SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
     SkPath onAsPath() const override;
diff --git a/modules/sksg/include/SkSGScene.h b/modules/sksg/include/SkSGScene.h
index 95bee30..9261cf4 100644
--- a/modules/sksg/include/SkSGScene.h
+++ b/modules/sksg/include/SkSGScene.h
@@ -15,6 +15,7 @@
 #include <vector>
 
 class SkCanvas;
+struct SkPoint;
 
 namespace sksg {
 
@@ -67,6 +68,7 @@
 
     void render(SkCanvas*) const;
     void animate(float t);
+    const RenderNode* nodeAt(const SkPoint&) const;
 
     void setShowInval(bool show) { fShowInval = show; }
 
diff --git a/modules/sksg/include/SkSGText.h b/modules/sksg/include/SkSGText.h
index 64ec1b2..e2cf788 100644
--- a/modules/sksg/include/SkSGText.h
+++ b/modules/sksg/include/SkSGText.h
@@ -45,6 +45,7 @@
 protected:
     void onClip(SkCanvas*, bool antiAlias) const override;
     void onDraw(SkCanvas*, const SkPaint&) const override;
+    bool onContains(const SkPoint&)        const override;
 
     SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
     SkPath onAsPath() const override;
@@ -83,6 +84,7 @@
 protected:
     void onClip(SkCanvas*, bool antiAlias) const override;
     void onDraw(SkCanvas*, const SkPaint&) const override;
+    bool onContains(const SkPoint&)        const override;
 
     SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
     SkPath onAsPath() const override;
diff --git a/modules/sksg/include/SkSGTransform.h b/modules/sksg/include/SkSGTransform.h
index 3153a7e..d8408da 100644
--- a/modules/sksg/include/SkSGTransform.h
+++ b/modules/sksg/include/SkSGTransform.h
@@ -97,6 +97,7 @@
 
 protected:
     void onRender(SkCanvas*, const RenderContext*) const override;
+    const RenderNode* onNodeAt(const SkPoint&)     const override;
 
     SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
 
diff --git a/modules/sksg/include/SkSGTrimEffect.h b/modules/sksg/include/SkSGTrimEffect.h
index 18f1592..ea2fd253 100644
--- a/modules/sksg/include/SkSGTrimEffect.h
+++ b/modules/sksg/include/SkSGTrimEffect.h
@@ -36,6 +36,7 @@
 protected:
     void onClip(SkCanvas*, bool antiAlias) const override;
     void onDraw(SkCanvas*, const SkPaint&) const override;
+    bool onContains(const SkPoint&)        const override;
 
     SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
     SkPath onAsPath() const override;
diff --git a/modules/sksg/samples/SampleSVGPong.cpp b/modules/sksg/samples/SampleSVGPong.cpp
index 7869607..bb2ba1a 100644
--- a/modules/sksg/samples/SampleSVGPong.cpp
+++ b/modules/sksg/samples/SampleSVGPong.cpp
@@ -5,15 +5,14 @@
  * found in the LICENSE file.
  */
 
+#include "AnimTimer.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
-#include "SkColor.h"
-#include "SkRandom.h"
 #include "SkRRect.h"
+#include "SkRandom.h"
 
-#include "SkSGColor.h"
 #include "SkSGDraw.h"
 #include "SkSGGroup.h"
+#include "SkSGPaint.h"
 #include "SkSGPath.h"
 #include "SkSGRect.h"
 #include "SkSGScene.h"
@@ -192,7 +191,7 @@
         fScene->render(canvas);
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         // onAnimate may fire before the first draw.
         if (fScene) {
             SkScalar dt = (timer.msec() - fLastTick) * fTimeScale;
diff --git a/modules/sksg/sksg.gni b/modules/sksg/sksg.gni
index 2e4e83b..c202c17 100644
--- a/modules/sksg/sksg.gni
+++ b/modules/sksg/sksg.gni
@@ -8,7 +8,6 @@
 
 skia_sksg_sources = [
   "$_src/SkSGClipEffect.cpp",
-  "$_src/SkSGColor.cpp",
   "$_src/SkSGColorFilter.cpp",
   "$_src/SkSGDraw.cpp",
   "$_src/SkSGEffectNode.cpp",
@@ -22,10 +21,11 @@
   "$_src/SkSGMerge.cpp",
   "$_src/SkSGNode.cpp",
   "$_src/SkSGOpacityEffect.cpp",
-  "$_src/SkSGPaintNode.cpp",
+  "$_src/SkSGPaint.cpp",
   "$_src/SkSGPath.cpp",
   "$_src/SkSGPlane.cpp",
   "$_src/SkSGRect.cpp",
+  "$_src/SkSGRenderEffect.cpp",
   "$_src/SkSGRenderNode.cpp",
   "$_src/SkSGRoundEffect.cpp",
   "$_src/SkSGScene.cpp",
diff --git a/modules/sksg/src/SkSGClipEffect.cpp b/modules/sksg/src/SkSGClipEffect.cpp
index 045605d..4969e3b 100644
--- a/modules/sksg/src/SkSGClipEffect.cpp
+++ b/modules/sksg/src/SkSGClipEffect.cpp
@@ -25,9 +25,6 @@
 }
 
 void ClipEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
-    if (this->bounds().isEmpty())
-        return;
-
     SkAutoCanvasRestore acr(canvas, !fNoop);
     if (!fNoop) {
         fClipNode->clip(canvas, fAntiAlias);
@@ -36,6 +33,10 @@
     this->INHERITED::onRender(canvas, ctx);
 }
 
+const RenderNode* ClipEffect::onNodeAt(const SkPoint& p) const {
+    return fClipNode->contains(p) ? this->INHERITED::onNodeAt(p) : nullptr;
+}
+
 SkRect ClipEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
     SkASSERT(this->hasInval());
 
diff --git a/modules/sksg/src/SkSGColor.cpp b/modules/sksg/src/SkSGColor.cpp
deleted file mode 100644
index d5d4d1c..0000000
--- a/modules/sksg/src/SkSGColor.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkSGColor.h"
-
-namespace sksg {
-
-Color::Color(SkColor c) : fColor(c) {}
-
-void Color::onApplyToPaint(SkPaint* paint) const {
-    paint->setColor(fColor);
-}
-
-} // namespace sksg
diff --git a/modules/sksg/src/SkSGColorFilter.cpp b/modules/sksg/src/SkSGColorFilter.cpp
index 3ad2287..f247f05 100644
--- a/modules/sksg/src/SkSGColorFilter.cpp
+++ b/modules/sksg/src/SkSGColorFilter.cpp
@@ -7,8 +7,12 @@
 
 #include "SkSGColorFilter.h"
 
+#include "SkColorData.h"
 #include "SkColorFilter.h"
-#include "SkSGColor.h"
+#include "SkSGPaint.h"
+#include "SkTableColorFilter.h"
+
+#include <cmath>
 
 namespace sksg {
 
@@ -16,32 +20,204 @@
     : INHERITED(std::move(child)) {}
 
 void ColorFilter::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
-    if (this->bounds().isEmpty())
-        return;
-
     const auto local_ctx = ScopedRenderContext(canvas, ctx).modulateColorFilter(fColorFilter);
 
     this->INHERITED::onRender(canvas, local_ctx);
 }
 
-ColorModeFilter::ColorModeFilter(sk_sp<RenderNode> child, sk_sp<Color> color, SkBlendMode mode)
+const RenderNode* ColorFilter::onNodeAt(const SkPoint& p) const {
+    // TODO: we likely need to do something more sophisticated than delegate to descendants here.
+    return this->INHERITED::onNodeAt(p);
+}
+
+SkRect ColorFilter::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+    SkASSERT(this->hasInval());
+
+    fColorFilter = this->onRevalidateFilter();
+
+    return this->INHERITED::onRevalidate(ic, ctm);
+}
+
+sk_sp<ExternalColorFilter> ExternalColorFilter::Make(sk_sp<RenderNode> child) {
+    return child ? sk_sp<ExternalColorFilter>(new ExternalColorFilter(std::move(child)))
+                 : nullptr;
+}
+
+ExternalColorFilter::ExternalColorFilter(sk_sp<RenderNode> child) : INHERITED(std::move(child)) {}
+
+ExternalColorFilter::~ExternalColorFilter() = default;
+
+void ExternalColorFilter::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
+    const auto local_ctx = ScopedRenderContext(canvas, ctx).modulateColorFilter(fColorFilter);
+
+    this->INHERITED::onRender(canvas, local_ctx);
+}
+
+sk_sp<ModeColorFilter> ModeColorFilter::Make(sk_sp<RenderNode> child, sk_sp<Color> color,
+                                             SkBlendMode mode) {
+    return (child && color) ? sk_sp<ModeColorFilter>(new ModeColorFilter(std::move(child),
+                                                                         std::move(color), mode))
+                            : nullptr;
+}
+
+ModeColorFilter::ModeColorFilter(sk_sp<RenderNode> child, sk_sp<Color> color, SkBlendMode mode)
     : INHERITED(std::move(child))
     , fColor(std::move(color))
     , fMode(mode) {
     this->observeInval(fColor);
 }
 
-ColorModeFilter::~ColorModeFilter() {
+ModeColorFilter::~ModeColorFilter() {
     this->unobserveInval(fColor);
 }
 
-SkRect ColorModeFilter::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
-    SkASSERT(this->hasInval());
+sk_sp<SkColorFilter> ModeColorFilter::onRevalidateFilter() {
+    fColor->revalidate(nullptr, SkMatrix::I());
+    return SkColorFilters::Blend(fColor->getColor(), fMode);
+}
 
-    fColor->revalidate(ic, ctm);
-    fColorFilter = SkColorFilter::MakeModeFilter(fColor->getColor(), fMode);
+sk_sp<GradientColorFilter> GradientColorFilter::Make(sk_sp<RenderNode> child,
+                                                     sk_sp<Color> c0, sk_sp<Color> c1) {
+    return Make(std::move(child), { std::move(c0), std::move(c1) });
+}
 
-    return this->INHERITED::onRevalidate(ic, ctm);
+sk_sp<GradientColorFilter> GradientColorFilter::Make(sk_sp<RenderNode> child,
+                                                     std::vector<sk_sp<Color>> colors) {
+    return (child && colors.size() > 1)
+        ? sk_sp<GradientColorFilter>(new GradientColorFilter(std::move(child), std::move(colors)))
+        : nullptr;
+}
+
+GradientColorFilter::GradientColorFilter(sk_sp<RenderNode> child, std::vector<sk_sp<Color>> colors)
+    : INHERITED(std::move(child))
+    , fColors(std::move(colors)) {
+    for (const auto& color : fColors) {
+        this->observeInval(color);
+    }
+}
+
+GradientColorFilter::~GradientColorFilter() {
+    for (const auto& color : fColors) {
+        this->unobserveInval(color);
+    }
+}
+
+namespace  {
+
+sk_sp<SkColorFilter> Make2ColorGradient(const sk_sp<Color>& color0, const sk_sp<Color>& color1) {
+    const auto c0 = SkColor4f::FromColor(color0->getColor()),
+               c1 = SkColor4f::FromColor(color1->getColor());
+
+    const auto dR = c1.fR - c0.fR,
+               dG = c1.fG - c0.fG,
+               dB = c1.fB - c0.fB;
+
+    // A 2-color gradient can be expressed as a color matrix (and combined with the luminance
+    // calculation).  First, the luminance:
+    //
+    //   L = [r,g,b] . [kR,kG,kB]
+    //
+    // We can compute it using a color matrix (result stored in R):
+    //
+    //   | kR, kG, kB,  0,  0 |    r' = L
+    //   |  0,  0,  0,  0,  0 |    g' = 0
+    //   |  0,  0,  0,  0,  0 |    b' = 0
+    //   |  0,  0,  0,  1,  0 |    a' = a
+    //
+    // Then we want to interpolate component-wise, based on L:
+    //
+    //   r' = c0.r + (c1.r - c0.r) * L = c0.r + dR*L
+    //   g' = c0.g + (c1.g - c0.g) * L = c0.g + dG*L
+    //   b' = c0.b + (c1.b - c0.b) * L = c0.b + dB*L
+    //   a' = a
+    //
+    // This can be expressed as another color matrix (when L is stored in R):
+    //
+    //  | dR,  0,  0,  0, c0.r |
+    //  | dG,  0,  0,  0, c0.g |
+    //  | dB,  0,  0,  0, c0.b |
+    //  |  0,  0,  0,  1,    0 |
+    //
+    // Composing these two, we get the total tint matrix:
+
+    const SkScalar tint_matrix[] = {
+        dR*SK_LUM_COEFF_R, dR*SK_LUM_COEFF_G, dR*SK_LUM_COEFF_B, 0, c0.fR * 255,
+        dG*SK_LUM_COEFF_R, dG*SK_LUM_COEFF_G, dG*SK_LUM_COEFF_B, 0, c0.fG * 255,
+        dB*SK_LUM_COEFF_R, dB*SK_LUM_COEFF_G, dB*SK_LUM_COEFF_B, 0, c0.fB * 255,
+                        0,                 0,                 0, 1,           0,
+    };
+
+    return SkColorFilters::MatrixRowMajor255(tint_matrix);
+}
+
+sk_sp<SkColorFilter> MakeNColorGradient(const std::vector<sk_sp<Color>>& colors) {
+    // For N colors, we build a gradient color table.
+    uint8_t rTable[256], gTable[256], bTable[256];
+
+    SkASSERT(colors.size() > 2);
+    const auto span_count = colors.size() - 1;
+
+    size_t span_start = 0;
+    for (size_t i = 0; i < span_count; ++i) {
+        const auto span_stop = static_cast<size_t>(std::round((i + 1) * 255.0f / span_count)),
+                   span_size = span_stop - span_start;
+        if (span_start > span_stop) {
+            // Degenerate case.
+            continue;
+        }
+        SkASSERT(span_stop <= 255);
+
+        // Fill the gradient in [span_start,span_stop] -> [c0,c1]
+        const SkColor c0 = colors[i    ]->getColor(),
+                      c1 = colors[i + 1]->getColor();
+        float r = SkColorGetR(c0),
+              g = SkColorGetG(c0),
+              b = SkColorGetB(c0);
+        const float dR = (SkColorGetR(c1) - r) / span_size,
+                    dG = (SkColorGetG(c1) - g) / span_size,
+                    dB = (SkColorGetB(c1) - b) / span_size;
+
+        for (size_t j = span_start; j <= span_stop; ++j) {
+            rTable[j] = static_cast<uint8_t>(std::round(r));
+            gTable[j] = static_cast<uint8_t>(std::round(g));
+            bTable[j] = static_cast<uint8_t>(std::round(b));
+            r += dR;
+            g += dG;
+            b += dB;
+        }
+
+        // Ensure we always advance.
+        span_start = span_stop + 1;
+    }
+    SkASSERT(span_start == 256);
+
+    const SkScalar luminance_matrix[] = {
+        SK_LUM_COEFF_R, SK_LUM_COEFF_G, SK_LUM_COEFF_B,  0,  0,  // r' = L
+        SK_LUM_COEFF_R, SK_LUM_COEFF_G, SK_LUM_COEFF_B,  0,  0,  // g' = L
+        SK_LUM_COEFF_R, SK_LUM_COEFF_G, SK_LUM_COEFF_B,  0,  0,  // b' = L
+                     0,              0,              0,  1,  0,  // a' = a
+    };
+
+    return SkTableColorFilter::MakeARGB(nullptr, rTable, gTable, bTable)
+            ->makeComposed(SkColorFilters::MatrixRowMajor255(luminance_matrix));
+}
+
+} // namespace
+
+sk_sp<SkColorFilter> GradientColorFilter::onRevalidateFilter() {
+    for (const auto& color : fColors) {
+        color->revalidate(nullptr, SkMatrix::I());
+    }
+
+    if (fWeight <= 0) {
+        return nullptr;
+    }
+
+    SkASSERT(fColors.size() > 1);
+    auto gradientCF = (fColors.size() > 2) ? MakeNColorGradient(fColors)
+                                           : Make2ColorGradient(fColors[0], fColors[1]);
+
+    return SkColorFilters::Lerp(fWeight, nullptr, std::move(gradientCF));
 }
 
 } // namespace sksg
diff --git a/modules/sksg/src/SkSGDraw.cpp b/modules/sksg/src/SkSGDraw.cpp
index 24b358e..797b58a 100644
--- a/modules/sksg/src/SkSGDraw.cpp
+++ b/modules/sksg/src/SkSGDraw.cpp
@@ -7,9 +7,11 @@
 
 #include "SkSGDraw.h"
 
+#include "SkCanvas.h"
+#include "SkPath.h"
 #include "SkSGGeometryNode.h"
 #include "SkSGInvalidationController.h"
-#include "SkSGPaintNode.h"
+#include "SkSGPaint.h"
 #include "SkTLazy.h"
 
 namespace sksg {
@@ -29,7 +31,7 @@
 void Draw::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
     auto paint = fPaint->makePaint();
     if (ctx) {
-        ctx->modulatePaint(&paint);
+        ctx->modulatePaint(canvas->getTotalMatrix(), &paint);
     }
 
     const auto skipDraw = paint.nothingToDraw() ||
@@ -40,6 +42,25 @@
     }
 }
 
+const RenderNode* Draw::onNodeAt(const SkPoint& p) const {
+    const auto paint = fPaint->makePaint();
+
+    if (!paint.getAlpha()) {
+        return nullptr;
+    }
+
+    if (paint.getStyle() == SkPaint::Style::kFill_Style && fGeometry->contains(p)) {
+        return this;
+    }
+
+    SkPath stroke_path;
+    if (!paint.getFillPath(fGeometry->asPath(), &stroke_path)) {
+        return nullptr;
+    }
+
+    return stroke_path.contains(p.x(), p.y()) ? this : nullptr;
+}
+
 SkRect Draw::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
     SkASSERT(this->hasInval());
 
diff --git a/modules/sksg/src/SkSGEffectNode.cpp b/modules/sksg/src/SkSGEffectNode.cpp
index 272d509..8ea2eea 100644
--- a/modules/sksg/src/SkSGEffectNode.cpp
+++ b/modules/sksg/src/SkSGEffectNode.cpp
@@ -9,8 +9,9 @@
 
 namespace sksg {
 
-EffectNode::EffectNode(sk_sp<RenderNode> child)
-    : fChild(std::move(child)) {
+EffectNode::EffectNode(sk_sp<RenderNode> child, uint32_t inval_traits)
+    : INHERITED(inval_traits)
+    , fChild(std::move(child)) {
     this->observeInval(fChild);
 }
 
@@ -22,6 +23,10 @@
     fChild->render(canvas, ctx);
 }
 
+const RenderNode* EffectNode::onNodeAt(const SkPoint& p) const {
+    return fChild->nodeAt(p);
+}
+
 SkRect EffectNode::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
     SkASSERT(this->hasInval());
 
diff --git a/modules/sksg/src/SkSGGeometryNode.cpp b/modules/sksg/src/SkSGGeometryNode.cpp
index 6b78c48..78ef6e6 100644
--- a/modules/sksg/src/SkSGGeometryNode.cpp
+++ b/modules/sksg/src/SkSGGeometryNode.cpp
@@ -24,6 +24,11 @@
     this->onDraw(canvas, paint);
 }
 
+bool GeometryNode::contains(const SkPoint& p) const {
+    SkASSERT(!this->hasInval());
+    return this->bounds().contains(p.x(), p.y()) ? this->onContains(p) : false;
+}
+
 SkPath GeometryNode::asPath() const {
     SkASSERT(!this->hasInval());
     return this->onAsPath();
diff --git a/modules/sksg/src/SkSGGeometryTransform.cpp b/modules/sksg/src/SkSGGeometryTransform.cpp
index 6e71dc6..224d46b 100644
--- a/modules/sksg/src/SkSGGeometryTransform.cpp
+++ b/modules/sksg/src/SkSGGeometryTransform.cpp
@@ -33,6 +33,10 @@
     canvas->drawPath(fTransformedPath, paint);
 }
 
+bool GeometryTransform::onContains(const SkPoint& p) const {
+    return fTransformedPath.contains(p.x(), p.y());
+}
+
 SkRect GeometryTransform::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
     SkASSERT(this->hasInval());
 
diff --git a/modules/sksg/src/SkSGGradient.cpp b/modules/sksg/src/SkSGGradient.cpp
index 98e7f39..d2575e0 100644
--- a/modules/sksg/src/SkSGGradient.cpp
+++ b/modules/sksg/src/SkSGGradient.cpp
@@ -12,10 +12,9 @@
 
 namespace sksg {
 
-void Gradient::onApplyToPaint(SkPaint* paint) const {
+sk_sp<SkShader> Gradient::onRevalidateShader() {
     if (fColorStops.empty()) {
-        paint->setShader(nullptr);
-        return;
+        return nullptr;
     }
 
     std::vector<SkColor>  colors;
@@ -31,7 +30,7 @@
     }
 
     // TODO: detect even stop distributions, pass null for positions.
-    paint->setShader(this->onMakeShader(colors, positions));
+    return this->onMakeShader(colors, positions);
 }
 
 sk_sp<SkShader> LinearGradient::onMakeShader(const std::vector<SkColor>& colors,
diff --git a/modules/sksg/src/SkSGGroup.cpp b/modules/sksg/src/SkSGGroup.cpp
index fff7436..27e4622 100644
--- a/modules/sksg/src/SkSGGroup.cpp
+++ b/modules/sksg/src/SkSGGroup.cpp
@@ -7,6 +7,8 @@
 
 #include "SkSGGroup.h"
 
+#include "SkCanvas.h"
+
 #include <algorithm>
 
 namespace sksg {
@@ -59,13 +61,25 @@
     //   a) it is fragile because it relies on all leaf render nodes being atomic draws
     //   b) could be improved by e.g. detecting all leaf render draws are non-overlapping
     const auto isolate = fChildren.size() > 1;
-    const auto local_ctx = ScopedRenderContext(canvas, ctx).setIsolation(this->bounds(), isolate);
+    const auto local_ctx = ScopedRenderContext(canvas, ctx).setIsolation(this->bounds(),
+                                                                         canvas->getTotalMatrix(),
+                                                                         isolate);
 
     for (const auto& child : fChildren) {
         child->render(canvas, local_ctx);
     }
 }
 
+const RenderNode* Group::onNodeAt(const SkPoint& p) const {
+    for (auto it = fChildren.crbegin(); it != fChildren.crend(); ++it) {
+        if (const auto* node = (*it)->nodeAt(p)) {
+            return node;
+        }
+    }
+
+    return nullptr;
+}
+
 SkRect Group::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
     SkASSERT(this->hasInval());
 
diff --git a/modules/sksg/src/SkSGImage.cpp b/modules/sksg/src/SkSGImage.cpp
index 59da5d7..768f920 100644
--- a/modules/sksg/src/SkSGImage.cpp
+++ b/modules/sksg/src/SkSGImage.cpp
@@ -24,12 +24,17 @@
     paint.setFilterQuality(fQuality);
 
     if (ctx) {
-        ctx->modulatePaint(&paint);
+        ctx->modulatePaint(canvas->getTotalMatrix(), &paint);
     }
 
     canvas->drawImage(fImage, 0, 0, &paint);
 }
 
+const RenderNode* Image::onNodeAt(const SkPoint& p) const {
+    SkASSERT(this->bounds().contains(p.x(), p.y()));
+    return this;
+}
+
 SkRect Image::onRevalidate(InvalidationController*, const SkMatrix& ctm) {
     return fImage ? SkRect::Make(fImage->bounds()) : SkRect::MakeEmpty();
 }
diff --git a/modules/sksg/src/SkSGMaskEffect.cpp b/modules/sksg/src/SkSGMaskEffect.cpp
index ff869fd..0bcddcd 100644
--- a/modules/sksg/src/SkSGMaskEffect.cpp
+++ b/modules/sksg/src/SkSGMaskEffect.cpp
@@ -23,16 +23,12 @@
 }
 
 void MaskEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
-    if (this->bounds().isEmpty())
-        return;
-
     SkAutoCanvasRestore acr(canvas, false);
 
     canvas->saveLayer(this->bounds(), nullptr);
     // Note: the paint overrides in ctx don't apply to the mask.
     fMaskNode->render(canvas);
 
-
     SkPaint p;
     p.setBlendMode(fMaskMode == Mode::kNormal ? SkBlendMode::kSrcIn : SkBlendMode::kSrcOut);
     canvas->saveLayer(this->bounds(), &p);
@@ -40,6 +36,11 @@
     this->INHERITED::onRender(canvas, ctx);
 }
 
+const RenderNode* MaskEffect::onNodeAt(const SkPoint& p) const {
+    const auto mask_hit = (!!fMaskNode->nodeAt(p) == (fMaskMode == Mode::kNormal));
+
+    return mask_hit ? this->INHERITED::onNodeAt(p) : nullptr;
+}
 
 SkRect MaskEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
     SkASSERT(this->hasInval());
diff --git a/modules/sksg/src/SkSGMerge.cpp b/modules/sksg/src/SkSGMerge.cpp
index 48e7b9b..a882647 100644
--- a/modules/sksg/src/SkSGMerge.cpp
+++ b/modules/sksg/src/SkSGMerge.cpp
@@ -33,6 +33,10 @@
     canvas->drawPath(fMerged, paint);
 }
 
+bool Merge::onContains(const SkPoint& p) const {
+    return fMerged.contains(p.x(), p.y());
+}
+
 SkPath Merge::onAsPath() const {
     return fMerged;
 }
diff --git a/modules/sksg/src/SkSGNode.cpp b/modules/sksg/src/SkSGNode.cpp
index 338bffc..1d4f63d 100644
--- a/modules/sksg/src/SkSGNode.cpp
+++ b/modules/sksg/src/SkSGNode.cpp
@@ -135,16 +135,20 @@
         return fBounds;
     }
 
-    SkRect prevBounds;
-    if (fFlags & kDamage_Flag) {
-        prevBounds = fBounds;
-    }
+    const auto generate_damage =
+            ic && ((fFlags & kDamage_Flag) || (fInvalTraits & kOverrideDamage_Trait));
+    if (!generate_damage) {
+        // Trivial transitive revalidation.
+        fBounds = this->onRevalidate(ic, ctm);
+    } else {
+        // Revalidate and emit damage for old-bounds, new-bounds.
+        const auto prev_bounds = fBounds;
 
-    fBounds = this->onRevalidate(ic, ctm);
+        auto* ic_override = (fInvalTraits & kOverrideDamage_Trait) ? nullptr : ic;
+        fBounds = this->onRevalidate(ic_override, ctm);
 
-    if (fFlags & kDamage_Flag) {
-        ic->inval(prevBounds, ctm);
-        if (fBounds != prevBounds) {
+        ic->inval(prev_bounds, ctm);
+        if (fBounds != prev_bounds) {
             ic->inval(fBounds, ctm);
         }
     }
diff --git a/modules/sksg/src/SkSGOpacityEffect.cpp b/modules/sksg/src/SkSGOpacityEffect.cpp
index cc72c14..d6abfa2 100644
--- a/modules/sksg/src/SkSGOpacityEffect.cpp
+++ b/modules/sksg/src/SkSGOpacityEffect.cpp
@@ -23,6 +23,10 @@
     this->INHERITED::onRender(canvas, local_context);
 }
 
+const RenderNode* OpacityEffect::onNodeAt(const SkPoint& p) const {
+    return (fOpacity > 0) ? this->INHERITED::onNodeAt(p) : nullptr;
+}
+
 SkRect OpacityEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
     SkASSERT(this->hasInval());
 
diff --git a/modules/sksg/src/SkSGPaint.cpp b/modules/sksg/src/SkSGPaint.cpp
new file mode 100644
index 0000000..16e0657
--- /dev/null
+++ b/modules/sksg/src/SkSGPaint.cpp
@@ -0,0 +1,78 @@
+/*
+ * 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 "SkSGPaint.h"
+
+#include "SkSGRenderEffect.h"
+
+namespace sksg {
+
+// Paint nodes don't generate damage on their own, but via their aggregation ancestor Draw nodes.
+PaintNode::PaintNode() : INHERITED(kBubbleDamage_Trait) {}
+
+SkPaint PaintNode::makePaint() const {
+    SkASSERT(!this->hasInval());
+
+    SkPaint paint;
+
+    paint.setAntiAlias(fAntiAlias);
+    paint.setBlendMode(fBlendMode);
+    paint.setStyle(fStyle);
+    paint.setStrokeWidth(fStrokeWidth);
+    paint.setStrokeMiter(fStrokeMiter);
+    paint.setStrokeJoin(fStrokeJoin);
+    paint.setStrokeCap(fStrokeCap);
+
+    this->onApplyToPaint(&paint);
+
+    // Compose opacity on top of the subclass value.
+    paint.setAlpha(SkScalarRoundToInt(paint.getAlpha() * SkTPin<SkScalar>(fOpacity, 0, 1)));
+
+    return paint;
+}
+
+sk_sp<Color> Color::Make(SkColor c) {
+    return sk_sp<Color>(new Color(c));
+}
+
+Color::Color(SkColor c) : fColor(c) {}
+
+SkRect Color::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+    SkASSERT(this->hasInval());
+
+    return SkRect::MakeEmpty();
+}
+
+void Color::onApplyToPaint(SkPaint* paint) const {
+    paint->setColor(fColor);
+}
+
+sk_sp<ShaderPaint> ShaderPaint::Make(sk_sp<Shader> sh) {
+    return sh ? sk_sp<ShaderPaint>(new ShaderPaint(std::move(sh)))
+              : nullptr;
+}
+
+ShaderPaint::ShaderPaint(sk_sp<Shader> sh)
+    : fShader(std::move(sh)) {
+    this->observeInval(fShader);
+}
+
+ShaderPaint::~ShaderPaint() {
+    this->unobserveInval(fShader);
+}
+
+SkRect ShaderPaint::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+    SkASSERT(this->hasInval());
+
+    return fShader->revalidate(ic, ctm);
+}
+
+void ShaderPaint::onApplyToPaint(SkPaint* paint) const {
+    paint->setShader(fShader->getShader());
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGPaintNode.cpp b/modules/sksg/src/SkSGPaintNode.cpp
deleted file mode 100644
index c929fc6..0000000
--- a/modules/sksg/src/SkSGPaintNode.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkSGPaintNode.h"
-
-namespace sksg {
-
-// Paint nodes don't generate damage on their own, but via their aggregation ancestor Draw nodes.
-PaintNode::PaintNode() : INHERITED(kBubbleDamage_Trait) {}
-
-SkPaint PaintNode::makePaint() const {
-    SkASSERT(!this->hasInval());
-
-    SkPaint paint;
-
-    paint.setAntiAlias(fAntiAlias);
-    paint.setBlendMode(fBlendMode);
-    paint.setStyle(fStyle);
-    paint.setStrokeWidth(fStrokeWidth);
-    paint.setStrokeMiter(fStrokeMiter);
-    paint.setStrokeJoin(fStrokeJoin);
-    paint.setStrokeCap(fStrokeCap);
-
-    this->onApplyToPaint(&paint);
-
-    // Compose opacity on top of the subclass value.
-    paint.setAlpha(SkScalarRoundToInt(paint.getAlpha() * SkTPin<SkScalar>(fOpacity, 0, 1)));
-
-    return paint;
-}
-
-SkRect PaintNode::onRevalidate(InvalidationController*, const SkMatrix&) {
-    SkASSERT(this->hasInval());
-
-    return SkRect::MakeEmpty();
-}
-
-} // namespace sksg
diff --git a/modules/sksg/src/SkSGPath.cpp b/modules/sksg/src/SkSGPath.cpp
index 230442d..3b63182 100644
--- a/modules/sksg/src/SkSGPath.cpp
+++ b/modules/sksg/src/SkSGPath.cpp
@@ -23,6 +23,10 @@
     canvas->drawPath(fPath, paint);
 }
 
+bool Path::onContains(const SkPoint& p) const {
+    return fPath.contains(p.x(), p.y());
+}
+
 SkRect Path::onRevalidate(InvalidationController*, const SkMatrix&) {
     SkASSERT(this->hasInval());
 
diff --git a/modules/sksg/src/SkSGPlane.cpp b/modules/sksg/src/SkSGPlane.cpp
index 806fcc7..989f40c 100644
--- a/modules/sksg/src/SkSGPlane.cpp
+++ b/modules/sksg/src/SkSGPlane.cpp
@@ -20,6 +20,8 @@
     canvas->drawPaint(paint);
 }
 
+bool Plane::onContains(const SkPoint&) const { return true; }
+
 SkRect Plane::onRevalidate(InvalidationController*, const SkMatrix&) {
     SkASSERT(this->hasInval());
 
diff --git a/modules/sksg/src/SkSGRect.cpp b/modules/sksg/src/SkSGRect.cpp
index 64739d9..091751b 100644
--- a/modules/sksg/src/SkSGRect.cpp
+++ b/modules/sksg/src/SkSGRect.cpp
@@ -23,6 +23,10 @@
     canvas->drawRect(fRect, paint);
 }
 
+bool Rect::onContains(const SkPoint& p) const {
+    return fRect.contains(p.x(), p.y());
+}
+
 SkRect Rect::onRevalidate(InvalidationController*, const SkMatrix&) {
     SkASSERT(this->hasInval());
 
@@ -45,6 +49,22 @@
     canvas->drawRRect(fRRect, paint);
 }
 
+bool RRect::onContains(const SkPoint& p) const {
+    if (!fRRect.rect().contains(p.x(), p.y())) {
+        return false;
+    }
+
+    if (fRRect.isRect()) {
+        return true;
+    }
+
+    // TODO: no SkRRect::contains(x, y)
+    return fRRect.contains(SkRect::MakeLTRB(p.x() - SK_ScalarNearlyZero,
+                                            p.y() - SK_ScalarNearlyZero,
+                                            p.x() + SK_ScalarNearlyZero,
+                                            p.y() + SK_ScalarNearlyZero));
+}
+
 SkRect RRect::onRevalidate(InvalidationController*, const SkMatrix&) {
     SkASSERT(this->hasInval());
 
diff --git a/modules/sksg/src/SkSGRenderEffect.cpp b/modules/sksg/src/SkSGRenderEffect.cpp
new file mode 100644
index 0000000..7765b2e
--- /dev/null
+++ b/modules/sksg/src/SkSGRenderEffect.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkSGRenderEffect.h"
+
+#include "SkCanvas.h"
+#include "SkDropShadowImageFilter.h"
+#include "SkMakeUnique.h"
+#include "SkShader.h"
+
+namespace sksg {
+
+sk_sp<ShaderEffect> ShaderEffect::Make(sk_sp<RenderNode> child, sk_sp<Shader> shader) {
+    return child ? sk_sp<ShaderEffect>(new ShaderEffect(std::move(child), std::move(shader)))
+                 : nullptr;
+}
+
+ShaderEffect::ShaderEffect(sk_sp<RenderNode> child, sk_sp<Shader> shader)
+    : INHERITED(std::move(child))
+    , fShader(std::move(shader)) {
+    if (fShader) {
+        this->observeInval(fShader);
+    }
+}
+
+ShaderEffect::~ShaderEffect() {
+    if (fShader) {
+        this->unobserveInval(fShader);
+    }
+}
+
+void ShaderEffect::setShader(sk_sp<Shader> sh) {
+    if (fShader) {
+        this->unobserveInval(fShader);
+    }
+
+    fShader = std::move(sh);
+
+    if (fShader) {
+        this->observeInval(fShader);
+    }
+}
+SkRect ShaderEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+    if (fShader) {
+        fShader->revalidate(ic, ctm);
+    }
+
+    return this->INHERITED::onRevalidate(ic, ctm);
+}
+
+void ShaderEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
+    const auto local_ctx = ScopedRenderContext(canvas, ctx)
+            .modulateShader(fShader ? fShader->getShader() : nullptr, canvas->getTotalMatrix());
+
+    this->INHERITED::onRender(canvas, local_ctx);
+}
+
+Shader::Shader() : INHERITED(kBubbleDamage_Trait) {}
+
+Shader::~Shader() = default;
+
+SkRect Shader::onRevalidate(InvalidationController*, const SkMatrix&) {
+    SkASSERT(this->hasInval());
+
+    fShader = this->onRevalidateShader();
+    return SkRect::MakeEmpty();
+}
+
+sk_sp<RenderNode> ImageFilterEffect::Make(sk_sp<RenderNode> child, sk_sp<ImageFilter> filter) {
+    return filter ? sk_sp<RenderNode>(new ImageFilterEffect(std::move(child), std::move(filter)))
+                  : child;
+}
+
+ImageFilterEffect::ImageFilterEffect(sk_sp<RenderNode> child, sk_sp<ImageFilter> filter)
+    // filters always override descendent damage
+    : INHERITED(std::move(child), kOverrideDamage_Trait)
+    , fImageFilter(std::move(filter)) {
+    this->observeInval(fImageFilter);
+}
+
+ImageFilterEffect::~ImageFilterEffect() {
+    this->unobserveInval(fImageFilter);
+}
+
+SkRect ImageFilterEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+    // FIXME: image filter effects should replace the descendents' damage!
+    fImageFilter->revalidate(ic, ctm);
+
+    const auto& filter = fImageFilter->getFilter();
+    SkASSERT(!filter || filter->canComputeFastBounds());
+
+    const auto content_bounds = this->INHERITED::onRevalidate(ic, ctm);
+
+    return filter ? filter->computeFastBounds(content_bounds)
+                  : content_bounds;
+}
+
+const RenderNode* ImageFilterEffect::onNodeAt(const SkPoint& p) const {
+    // TODO: map p through the filter DAG and dispatch to descendants?
+    // For now, image filters occlude hit-testing.
+    SkASSERT(this->bounds().contains(p.x(), p.y()));
+    return this;
+}
+
+void ImageFilterEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
+    // Note: we're using the source content bounds for saveLayer, not our local/filtered bounds.
+    const auto filter_ctx =
+        ScopedRenderContext(canvas, ctx).setFilterIsolation(this->getChild()->bounds(),
+                                                            canvas->getTotalMatrix(),
+                                                            fImageFilter->getFilter());
+    this->INHERITED::onRender(canvas, filter_ctx);
+}
+
+ImageFilter::ImageFilter(sk_sp<ImageFilter> input)
+    : ImageFilter(input ? skstd::make_unique<InputsT>(1, std::move(input)) : nullptr) {}
+
+ImageFilter::ImageFilter(std::unique_ptr<InputsT> inputs)
+    : INHERITED(kBubbleDamage_Trait)
+    , fInputs(std::move(inputs)) {
+    if (fInputs) {
+        for (const auto& input : *fInputs) {
+            this->observeInval(input);
+        }
+    }
+}
+
+ImageFilter::~ImageFilter() {
+    if (fInputs) {
+        for (const auto& input : *fInputs) {
+            this->unobserveInval(input);
+        }
+    }
+}
+
+sk_sp<SkImageFilter> ImageFilter::refInput(size_t i) const {
+    return (fInputs && i < fInputs->size()) ? (*fInputs)[i]->getFilter() : nullptr;
+}
+
+SkRect ImageFilter::onRevalidate(InvalidationController*, const SkMatrix&) {
+    SkASSERT(this->hasInval());
+
+    fFilter = this->onRevalidateFilter();
+    return SkRect::MakeEmpty();
+}
+
+sk_sp<DropShadowImageFilter> DropShadowImageFilter::Make(sk_sp<ImageFilter> input) {
+    return sk_sp<DropShadowImageFilter>(new DropShadowImageFilter(std::move(input)));
+}
+
+DropShadowImageFilter::DropShadowImageFilter(sk_sp<ImageFilter> input)
+    : INHERITED(std::move(input)) {}
+
+DropShadowImageFilter::~DropShadowImageFilter() = default;
+
+sk_sp<SkImageFilter> DropShadowImageFilter::onRevalidateFilter() {
+    const auto mode = (fMode == Mode::kShadowOnly)
+            ? SkDropShadowImageFilter::kDrawShadowOnly_ShadowMode
+            : SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode;
+
+    return SkDropShadowImageFilter::Make(fOffset.x(), fOffset.y(),
+                                         fSigma.x(), fSigma.y(),
+                                         fColor, mode, this->refInput(0));
+}
+
+sk_sp<BlurImageFilter> BlurImageFilter::Make(sk_sp<ImageFilter> input) {
+    return sk_sp<BlurImageFilter>(new BlurImageFilter(std::move(input)));
+}
+
+BlurImageFilter::BlurImageFilter(sk_sp<ImageFilter> input)
+    : INHERITED(std::move(input)) {}
+
+BlurImageFilter::~BlurImageFilter() = default;
+
+sk_sp<SkImageFilter> BlurImageFilter::onRevalidateFilter() {
+    return SkBlurImageFilter::Make(fSigma.x(), fSigma.y(), this->refInput(0), nullptr, fTileMode);
+}
+
+sk_sp<BlendModeEffect> BlendModeEffect::Make(sk_sp<RenderNode> child, SkBlendMode mode) {
+    return child ? sk_sp<BlendModeEffect>(new BlendModeEffect(std::move(child), mode))
+                 : nullptr;
+}
+
+BlendModeEffect::BlendModeEffect(sk_sp<RenderNode> child, SkBlendMode mode)
+    : INHERITED(std::move(child))
+    , fMode(mode) {}
+
+BlendModeEffect::~BlendModeEffect() = default;
+
+void BlendModeEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
+    const auto local_ctx = ScopedRenderContext(canvas, ctx).modulateBlendMode(fMode);
+
+    this->INHERITED::onRender(canvas, local_ctx);
+}
+
+const RenderNode* BlendModeEffect::onNodeAt(const SkPoint& p) const {
+    // TODO: we likely need to do something more sophisticated than delegate to descendants here.
+    return this->INHERITED::onNodeAt(p);
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGRenderNode.cpp b/modules/sksg/src/SkSGRenderNode.cpp
index 64d337f..961966b 100644
--- a/modules/sksg/src/SkSGRenderNode.cpp
+++ b/modules/sksg/src/SkSGRenderNode.cpp
@@ -8,29 +8,63 @@
 #include "SkSGRenderNode.h"
 
 #include "SkCanvas.h"
+#include "SkImageFilter.h"
 #include "SkPaint.h"
 
 namespace sksg {
 
-RenderNode::RenderNode() : INHERITED(0) {}
+RenderNode::RenderNode(uint32_t inval_traits) : INHERITED(inval_traits) {}
 
 void RenderNode::render(SkCanvas* canvas, const RenderContext* ctx) const {
     SkASSERT(!this->hasInval());
-    this->onRender(canvas, ctx);
+    if (!this->bounds().isEmpty()) {
+        this->onRender(canvas, ctx);
+    }
 }
 
-bool RenderNode::RenderContext::modulatePaint(SkPaint* paint) const {
-    const auto initial_alpha = paint->getAlpha(),
-                       alpha = SkToU8(sk_float_round2int(initial_alpha * fOpacity));
+const RenderNode* RenderNode::nodeAt(const SkPoint& p) const {
+    return this->bounds().contains(p.x(), p.y()) ? this->onNodeAt(p) : nullptr;
+}
 
-    if (alpha != initial_alpha || fColorFilter) {
-        paint->setAlpha(alpha);
-        paint->setColorFilter(SkColorFilter::MakeComposeFilter(fColorFilter,
-                                                               paint->refColorFilter()));
-        return true;
+static SkAlpha ScaleAlpha(SkAlpha alpha, float opacity) {
+   return SkToU8(sk_float_round2int(alpha * opacity));
+}
+
+bool RenderNode::RenderContext::requiresIsolation() const {
+    // Note: fShader is never applied on isolation layers.
+    return ScaleAlpha(SK_AlphaOPAQUE, fOpacity) != SK_AlphaOPAQUE
+        || fColorFilter
+        || fBlendMode != SkBlendMode::kSrcOver;
+}
+
+void RenderNode::RenderContext::modulatePaint(const SkMatrix& ctm, SkPaint* paint) const {
+    paint->setAlpha(ScaleAlpha(paint->getAlpha(), fOpacity));
+    paint->setColorFilter(SkColorFilters::Compose(fColorFilter, paint->refColorFilter()));
+    if (fShader) {
+        if (fShaderCTM != ctm) {
+            // The shader is declared to operate under a specific transform, but due to the
+            // deferral mechanism, other transformations might have been pushed to the state.
+            // We want to undo these transforms:
+            //
+            //   shaderCTM x T = ctm
+            //
+            //   =>  T = Inv(shaderCTM) x ctm
+            //
+            //   =>  Inv(T) = Inv(Inv(shaderCTM) x ctm)
+            //
+            //   =>  Inv(T) = Inv(ctm) x shaderCTM
+
+            SkMatrix inv_ctm;
+            if (ctm.invert(&inv_ctm)) {
+                paint->setShader(
+                            fShader->makeWithLocalMatrix(SkMatrix::Concat(inv_ctm, fShaderCTM)));
+            }
+        } else {
+            // No intervening transforms.
+            paint->setShader(fShader);
+        }
     }
-
-    return false;
+    paint->setBlendMode(fBlendMode);
 }
 
 RenderNode::ScopedRenderContext::ScopedRenderContext(SkCanvas* canvas, const RenderContext* ctx)
@@ -53,20 +87,55 @@
 
 RenderNode::ScopedRenderContext&&
 RenderNode::ScopedRenderContext::modulateColorFilter(sk_sp<SkColorFilter> cf) {
-    fCtx.fColorFilter = SkColorFilter::MakeComposeFilter(std::move(fCtx.fColorFilter),
-                                                         std::move(cf));
+    fCtx.fColorFilter = SkColorFilters::Compose(std::move(fCtx.fColorFilter), std::move(cf));
     return std::move(*this);
 }
 
 RenderNode::ScopedRenderContext&&
-RenderNode::ScopedRenderContext::setIsolation(const SkRect& bounds, bool isolation) {
-    if (isolation) {
-        SkPaint layer_paint;
-        if (fCtx.modulatePaint(&layer_paint)) {
-            fCanvas->saveLayer(bounds, &layer_paint);
-            fCtx = RenderContext();
-        }
+RenderNode::ScopedRenderContext::modulateShader(sk_sp<SkShader> sh, const SkMatrix& shader_ctm) {
+    // Topmost shader takes precedence.
+    if (!fCtx.fShader) {
+        fCtx.fShader = std::move(sh);
+        fCtx.fShaderCTM = shader_ctm;
     }
+
+    return std::move(*this);
+}
+
+RenderNode::ScopedRenderContext&&
+RenderNode::ScopedRenderContext::modulateBlendMode(SkBlendMode mode) {
+    fCtx.fBlendMode = mode;
+    return std::move(*this);
+}
+
+RenderNode::ScopedRenderContext&&
+RenderNode::ScopedRenderContext::setIsolation(const SkRect& bounds, const SkMatrix& ctm,
+                                              bool isolation) {
+    if (isolation && fCtx.requiresIsolation()) {
+        SkPaint layer_paint;
+        fCtx.modulatePaint(ctm, &layer_paint);
+        fCanvas->saveLayer(bounds, &layer_paint);
+
+        // Reset only the props applied via isolation layers.
+        fCtx.fColorFilter = nullptr;
+        fCtx.fOpacity     = 1;
+        fCtx.fBlendMode   = SkBlendMode::kSrcOver;
+    }
+
+    return std::move(*this);
+}
+
+RenderNode::ScopedRenderContext&&
+RenderNode::ScopedRenderContext::setFilterIsolation(const SkRect& bounds, const SkMatrix& ctm,
+                                                    sk_sp<SkImageFilter> filter) {
+    SkPaint layer_paint;
+    fCtx.modulatePaint(ctm, &layer_paint);
+
+    SkASSERT(!layer_paint.getImageFilter());
+    layer_paint.setImageFilter(std::move(filter));
+    fCanvas->saveLayer(bounds, &layer_paint);
+    fCtx = RenderContext();
+
     return std::move(*this);
 }
 
diff --git a/modules/sksg/src/SkSGRoundEffect.cpp b/modules/sksg/src/SkSGRoundEffect.cpp
index e47c361..14b8e1d 100644
--- a/modules/sksg/src/SkSGRoundEffect.cpp
+++ b/modules/sksg/src/SkSGRoundEffect.cpp
@@ -32,6 +32,10 @@
     canvas->drawPath(fRoundedPath, paint);
 }
 
+bool RoundEffect::onContains(const SkPoint& p) const {
+    return fRoundedPath.contains(p.x(), p.y());
+}
+
 SkPath RoundEffect::onAsPath() const {
     return fRoundedPath;
 }
diff --git a/modules/sksg/src/SkSGScene.cpp b/modules/sksg/src/SkSGScene.cpp
index 8d7e0b3..7d27bb2 100644
--- a/modules/sksg/src/SkSGScene.cpp
+++ b/modules/sksg/src/SkSGScene.cpp
@@ -42,8 +42,10 @@
 Scene::~Scene() = default;
 
 void Scene::render(SkCanvas* canvas) const {
+    // TODO: externalize the inval controller.
+    // TODO: relocate the revalidation to tick()?
     InvalidationController ic;
-    fRoot->revalidate(&ic, SkMatrix::I());
+    fRoot->revalidate(fShowInval ? &ic : nullptr, SkMatrix::I());
     fRoot->render(canvas);
 
     if (fShowInval) {
@@ -67,4 +69,8 @@
     }
 }
 
+const RenderNode* Scene::nodeAt(const SkPoint& p) const {
+    return fRoot->nodeAt(p);
+}
+
 } // namespace sksg
diff --git a/modules/sksg/src/SkSGText.cpp b/modules/sksg/src/SkSGText.cpp
index b70b192..6e1959e 100644
--- a/modules/sksg/src/SkSGText.cpp
+++ b/modules/sksg/src/SkSGText.cpp
@@ -73,6 +73,10 @@
     canvas->drawTextBlob(fBlob, aligned_pos.x(), aligned_pos.y(), paint);
 }
 
+bool Text::onContains(const SkPoint& p) const {
+    return this->asPath().contains(p.x(), p.y());
+}
+
 SkPath Text::onAsPath() const {
     // TODO
     return SkPath();
@@ -92,13 +96,18 @@
 TextBlob::~TextBlob() = default;
 
 SkRect TextBlob::onRevalidate(InvalidationController*, const SkMatrix&) {
-    return fBlob ? fBlob->bounds() : SkRect::MakeEmpty();
+    return fBlob ? fBlob->bounds().makeOffset(fPosition.x(), fPosition.y())
+                 : SkRect::MakeEmpty();
 }
 
 void TextBlob::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
     canvas->drawTextBlob(fBlob, fPosition.x(), fPosition.y(), paint);
 }
 
+bool TextBlob::onContains(const SkPoint& p) const {
+    return this->asPath().contains(p.x(), p.y());
+}
+
 SkPath TextBlob::onAsPath() const {
     // TODO
     return SkPath();
diff --git a/modules/sksg/src/SkSGTransform.cpp b/modules/sksg/src/SkSGTransform.cpp
index 6834fc9..fff1ba4 100644
--- a/modules/sksg/src/SkSGTransform.cpp
+++ b/modules/sksg/src/SkSGTransform.cpp
@@ -96,6 +96,15 @@
     this->INHERITED::onRender(canvas, ctx);
 }
 
+const RenderNode* TransformEffect::onNodeAt(const SkPoint& p) const {
+    const auto m = TransformPriv::As<SkMatrix>(fTransform);
+
+    SkPoint mapped_p;
+    m.mapPoints(&mapped_p, &p, 1);
+
+    return this->INHERITED::onNodeAt(mapped_p);
+}
+
 SkRect TransformEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
     SkASSERT(this->hasInval());
 
diff --git a/modules/sksg/src/SkSGTrimEffect.cpp b/modules/sksg/src/SkSGTrimEffect.cpp
index a2ec3c6..53f8f28 100644
--- a/modules/sksg/src/SkSGTrimEffect.cpp
+++ b/modules/sksg/src/SkSGTrimEffect.cpp
@@ -32,6 +32,10 @@
     canvas->drawPath(fTrimmedPath, paint);
 }
 
+bool TrimEffect::onContains(const SkPoint& p) const {
+    return fTrimmedPath.contains(p.x(), p.y());
+}
+
 SkPath TrimEffect::onAsPath() const {
     return fTrimmedPath;
 }
diff --git a/modules/sksg/tests/SGTest.cpp b/modules/sksg/tests/SGTest.cpp
index 25e62ef..e3ecfdb 100644
--- a/modules/sksg/tests/SGTest.cpp
+++ b/modules/sksg/tests/SGTest.cpp
@@ -11,11 +11,12 @@
 
 #include "SkRect.h"
 #include "SkRectPriv.h"
-#include "SkSGColor.h"
 #include "SkSGDraw.h"
 #include "SkSGGroup.h"
 #include "SkSGInvalidationController.h"
+#include "SkSGPaint.h"
 #include "SkSGRect.h"
+#include "SkSGRenderEffect.h"
 #include "SkSGTransform.h"
 #include "SkTo.h"
 
@@ -55,6 +56,23 @@
     }
 }
 
+struct HitTest {
+    const SkPoint           pt;
+    sk_sp<sksg::RenderNode> node;
+};
+
+static void check_hittest(skiatest::Reporter* reporter, const sk_sp<sksg::RenderNode>& root,
+                          const std::vector<HitTest>& tests) {
+    for (const auto& tst : tests) {
+        const auto* node = root->nodeAt(tst.pt);
+        if (node != tst.node.get()) {
+            SkDebugf("*** nodeAt(%f, %f) - expected %p, got %p\n",
+                     tst.pt.x(), tst.pt.y(), tst.node.get(), node);
+        }
+        REPORTER_ASSERT(reporter, tst.node.get() == node);
+    }
+}
+
 static void inval_test1(skiatest::Reporter* reporter) {
     auto color  = sksg::Color::Make(0xff000000);
     auto r1     = sksg::Rect::Make(SkRect::MakeWH(100, 100)),
@@ -62,9 +80,11 @@
     auto grp    = sksg::Group::Make();
     auto matrix = sksg::Matrix<SkMatrix>::Make(SkMatrix::I());
     auto root   = sksg::TransformEffect::Make(grp, matrix);
+    auto d1     = sksg::Draw::Make(r1, color),
+         d2     = sksg::Draw::Make(r2, color);
 
-    grp->addChild(sksg::Draw::Make(r1, color));
-    grp->addChild(sksg::Draw::Make(r2, color));
+    grp->addChild(d1);
+    grp->addChild(d2);
 
     {
         // Initial revalidation.
@@ -72,6 +92,15 @@
                     SkRect::MakeWH(100, 100),
                     SkRectPriv::MakeLargeS32(),
                     nullptr);
+
+        check_hittest(reporter, root, {
+                          {{  -1,   0 }, nullptr },
+                          {{   0,  -1 }, nullptr },
+                          {{ 100,   0 }, nullptr },
+                          {{   0, 100 }, nullptr },
+                          {{   0,   0 },      d2 },
+                          {{  99,  99 },      d2 },
+                      });
     }
 
     {
@@ -82,6 +111,22 @@
                     SkRect::MakeWH(300, 200),
                     SkRect::MakeWH(300, 200),
                     &damage);
+
+        check_hittest(reporter, root, {
+                          {{  -1,   0 }, nullptr },
+                          {{   0,  -1 }, nullptr },
+                          {{ 100,   0 }, nullptr },
+                          {{   0, 100 }, nullptr },
+                          {{   0,   0 },      d1 },
+                          {{  99,  99 },      d1 },
+
+                          {{ 199, 100 }, nullptr },
+                          {{ 200,  99 }, nullptr },
+                          {{ 300, 100 }, nullptr },
+                          {{ 200, 200 }, nullptr },
+                          {{ 200, 100 },      d2 },
+                          {{ 299, 199 },      d2 },
+                      });
     }
 
     {
@@ -102,6 +147,22 @@
                     SkRect::MakeWH(300, 200),
                     SkRect::MakeWH(100, 100),
                     &damage);
+
+        check_hittest(reporter, root, {
+                          {{  -1,   0 }, nullptr },
+                          {{   0,  -1 }, nullptr },
+                          {{  50,   0 }, nullptr },
+                          {{   0, 100 }, nullptr },
+                          {{   0,   0 },      d1 },
+                          {{  49,  99 },      d1 },
+
+                          {{ 199, 100 }, nullptr },
+                          {{ 200,  99 }, nullptr },
+                          {{ 300, 100 }, nullptr },
+                          {{ 200, 200 }, nullptr },
+                          {{ 200, 100 },      d2 },
+                          {{ 299, 199 },      d2 },
+                      });
     }
 
     {
@@ -112,6 +173,22 @@
                     SkRect::MakeWH(600, 400),
                     SkRect::MakeWH(600, 400),
                     &damage);
+
+        check_hittest(reporter, root, {
+                          {{  -1,   0 }, nullptr },
+                          {{   0,  -1 }, nullptr },
+                          {{  25,   0 }, nullptr },
+                          {{   0,  50 }, nullptr },
+                          {{   0,   0 },      d1 },
+                          {{  24,  49 },      d1 },
+
+                          {{  99,  50 }, nullptr },
+                          {{ 100,  49 }, nullptr },
+                          {{ 150,  50 }, nullptr },
+                          {{ 100, 100 }, nullptr },
+                          {{ 100,  50 },      d2 },
+                          {{ 149,  99 },      d2 },
+                      });
     }
 
     {
@@ -122,6 +199,22 @@
                     SkRect::MakeWH(500, 400),
                     SkRect::MakeLTRB(400, 200, 600, 400),
                     &damage);
+
+        check_hittest(reporter, root, {
+                          {{  -1,   0 }, nullptr },
+                          {{   0,  -1 }, nullptr },
+                          {{  25,   0 }, nullptr },
+                          {{   0,  50 }, nullptr },
+                          {{   0,   0 },      d1 },
+                          {{  24,  49 },      d1 },
+
+                          {{  99,  50 }, nullptr },
+                          {{ 100,  49 }, nullptr },
+                          {{ 125,  50 }, nullptr },
+                          {{ 100, 100 }, nullptr },
+                          {{ 100,  50 },      d2 },
+                          {{ 124,  99 },      d2 },
+                      });
     }
 }
 
@@ -192,6 +285,49 @@
     }
 }
 
+static void inval_test3(skiatest::Reporter* reporter) {
+    auto color1 = sksg::Color::Make(0xff000000),
+         color2 = sksg::Color::Make(0xff000000);
+    auto group  = sksg::Group::Make();
+
+    group->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeWH(100, 100)),
+                                     color1));
+    group->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeXYWH(200, 0, 100, 100)),
+                                     color2));
+    auto filter = sksg::DropShadowImageFilter::Make();
+    filter->setOffset({50, 75});
+    auto root = sksg::ImageFilterEffect::Make(group, filter);
+
+    {
+        // Initial revalidation.
+        check_inval(reporter, root,
+                    SkRect::MakeXYWH(0, 0, 350, 175),
+                    SkRectPriv::MakeLargeS32(),
+                    nullptr);
+    }
+
+    {
+        // Shadow-only.
+        filter->setMode(sksg::DropShadowImageFilter::Mode::kShadowOnly);
+        std::vector<SkRect> damage = { {0, 0, 350, 175}, { 50, 75, 350, 175} };
+        check_inval(reporter, root,
+                    SkRect::MakeLTRB(50, 75, 350, 175),
+                    SkRect::MakeLTRB(0, 0, 350, 175),
+                    &damage);
+    }
+
+    {
+        // Content change -> single/full filter bounds inval.
+        color1->setColor(0xffff0000);
+        std::vector<SkRect> damage = { { 50, 75, 350, 175} };
+        check_inval(reporter, root,
+                    SkRect::MakeLTRB(50, 75, 350, 175),
+                    SkRect::MakeLTRB(50, 75, 350, 175),
+                    &damage);
+    }
+
+}
+
 static void inval_group_remove(skiatest::Reporter* reporter) {
     auto draw = sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeWH(100, 100)),
                                  sksg::Color::Make(SK_ColorBLACK));
@@ -206,6 +342,7 @@
 DEF_TEST(SGInvalidation, reporter) {
     inval_test1(reporter);
     inval_test2(reporter);
+    inval_test3(reporter);
     inval_group_remove(reporter);
 }
 
diff --git a/modules/skshaper/BUILD.gn b/modules/skshaper/BUILD.gn
index 3452110..a672d98 100644
--- a/modules/skshaper/BUILD.gn
+++ b/modules/skshaper/BUILD.gn
@@ -3,30 +3,32 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-declare_args() {
-  skia_enable_skshaper = true
-}
-
 import("../../gn/skia.gni")
 
-config("public_config") {
-  if (skia_enable_skshaper) {
-    include_dirs = [ "include" ]
-  }
+declare_args() {
+  skia_enable_skshaper = !(is_win && is_component_build)
 }
 
-source_set("skshaper") {
-  if (skia_enable_skshaper) {
+if (skia_enable_skshaper) {
+  config("public_config") {
+    include_dirs = [ "include" ]
+    defines = []
+    if (skia_use_icu) {
+      defines += [ "SK_SHAPER_HARFBUZZ_AVAILABLE" ]
+    }
+  }
+
+  component("skshaper") {
+    check_includes = false
     import("skshaper.gni")
     public_configs = [ ":public_config" ]
     public = skia_shaper_public
     deps = [
       "../..:skia",
     ]
-    if (target_cpu == "wasm" || !skia_use_icu) {
-      sources = skia_shaper_primitive_sources
-    } else {
-      sources = skia_shaper_harfbuzz_sources
+    sources = skia_shaper_primitive_sources
+    if (skia_use_icu && skia_use_harfbuzz) {
+      sources += skia_shaper_harfbuzz_sources
       deps += [
         "//third_party/harfbuzz",
         "//third_party/icu",
@@ -34,4 +36,7 @@
     }
     configs += [ "../../:skia_private" ]
   }
+} else {
+  group("skshaper") {
+  }
 }
diff --git a/modules/skshaper/include/SkShaper.h b/modules/skshaper/include/SkShaper.h
index 6d30a75..b42ec04 100644
--- a/modules/skshaper/include/SkShaper.h
+++ b/modules/skshaper/include/SkShaper.h
@@ -8,13 +8,16 @@
 #ifndef SkShaper_DEFINED
 #define SkShaper_DEFINED
 
+#include "SkPoint.h"
+#include "SkRefCnt.h"
+#include "SkScalar.h"
+#include "SkTextBlob.h"
+#include "SkTypes.h"
+
 #include <memory>
 
-#include "SkPoint.h"
-#include "SkTextBlob.h"
-#include "SkTypeface.h"
-
 class SkFont;
+class SkFontMgr;
 
 /**
    Shapes text using HarfBuzz and places the shaped text into a
@@ -24,47 +27,130 @@
  */
 class SkShaper {
 public:
-    SkShaper(sk_sp<SkTypeface> face);
-    ~SkShaper();
+    static std::unique_ptr<SkShaper> MakePrimitive();
+    #ifdef SK_SHAPER_HARFBUZZ_AVAILABLE
+    static std::unique_ptr<SkShaper> MakeShaperDrivenWrapper();
+    static std::unique_ptr<SkShaper> MakeShapeThenWrap();
+    #endif
+
+    static std::unique_ptr<SkShaper> Make();
+
+    SkShaper();
+    virtual ~SkShaper();
+
+    class RunIterator {
+    public:
+        virtual ~RunIterator() = default;
+        /** Set state to that of current run and move iterator to end of that run. */
+        virtual void consume() = 0;
+        /** Offset to one past the last (utf8) element in the current run. */
+        virtual size_t endOfCurrentRun() const = 0;
+        /** Return true if consume should no longer be called. */
+        virtual bool atEnd() const = 0;
+    };
+
+    class FontRunIterator : public RunIterator {
+    public:
+        virtual const SkFont& currentFont() const = 0;
+    };
+    static std::unique_ptr<FontRunIterator>
+    MakeFontMgrRunIterator(const char* utf8, size_t utf8Bytes,
+                           const SkFont& font, sk_sp<SkFontMgr> fallback);
+
+    class BiDiRunIterator : public RunIterator {
+    public:
+        /** The unicode bidi embedding level (even ltr, odd rtl) */
+        virtual uint8_t currentLevel() const = 0;
+    };
+    #ifdef SK_SHAPER_HARFBUZZ_AVAILABLE
+    static std::unique_ptr<SkShaper::BiDiRunIterator>
+    MakeIcuBiDiRunIterator(const char* utf8, size_t utf8Bytes, uint8_t bidiLevel);
+    #endif
+
+    class ScriptRunIterator : public RunIterator {
+    public:
+        /** Should be iso15924 codes. */
+        virtual SkFourByteTag currentScript() const = 0;
+    };
+    #ifdef SK_SHAPER_HARFBUZZ_AVAILABLE
+    static std::unique_ptr<SkShaper::ScriptRunIterator>
+    MakeHbIcuScriptRunIterator(const char* utf8, size_t utf8Bytes);
+    #endif
+
+    class LanguageRunIterator : public RunIterator {
+    public:
+        /** Should be BCP-47, c locale names may also work. */
+        virtual const char* currentLanguage() const = 0;
+    };
+    static std::unique_ptr<SkShaper::LanguageRunIterator>
+    MakeStdLanguageRunIterator(const char* utf8, size_t utf8Bytes);
 
     class RunHandler {
     public:
         virtual ~RunHandler() = default;
 
+        struct Range {
+            constexpr Range() : fBegin(0), fSize(0) {}
+            constexpr Range(size_t begin, size_t size) : fBegin(begin), fSize(size) {}
+            size_t fBegin;
+            size_t fSize;
+            constexpr size_t begin() const { return fBegin; }
+            constexpr size_t end() const { return begin() + size(); }
+            constexpr size_t size() const { return fSize; }
+        };
+
         struct RunInfo {
-            size_t   fLineIndex;
+            const SkFont& fFont;
+            uint8_t fBidiLevel;
             SkVector fAdvance;
-            SkScalar fAscent,
-                     fDescent,
-                     fLeading;
+            size_t glyphCount;
+            Range utf8Range;
         };
 
         struct Buffer {
-            SkGlyphID* glyphs;    // required
-            SkPoint*   positions; // required
-            char*      utf8text;  // optional
-            uint32_t*  clusters;  // optional
+            SkGlyphID* glyphs;  // required
+            SkPoint* positions; // required
+            SkPoint* offsets;   // optional
+            uint32_t* clusters; // optional
+            SkPoint point;
         };
 
-        virtual Buffer newRunBuffer(const RunInfo&, const SkFont&, int glyphCount,
-                                    int utf8textCount) = 0;
+        /** Called when beginning a line. */
+        virtual void beginLine() = 0;
+
+        /** Called once for each run in a line. Can compute baselines and offsets. */
+        virtual void runInfo(const RunInfo&) = 0;
+
+        /** Called after all runInfo calls for a line. */
+        virtual void commitRunInfo() = 0;
+
+        /** Called for each run in a line after commitRunInfo. The buffer will be filled out. */
+        virtual Buffer runBuffer(const RunInfo&) = 0;
+
+        /** Called after each runBuffer is filled out. */
+        virtual void commitRunBuffer(const RunInfo&) = 0;
+
+        /** Called when ending a line. */
+        virtual void commitLine() = 0;
     };
 
-    bool good() const;
-    SkPoint shape(RunHandler* handler,
-                  const SkFont& srcFont,
-                  const char* utf8text,
-                  size_t textBytes,
-                  bool leftToRight,
-                  SkPoint point,
-                  SkScalar width) const;
+    virtual void shape(const char* utf8, size_t utf8Bytes,
+                       const SkFont& srcFont,
+                       bool leftToRight,
+                       SkScalar width,
+                       RunHandler*) const = 0;
+
+    virtual void shape(const char* utf8, size_t utf8Bytes,
+                       FontRunIterator&,
+                       BiDiRunIterator&,
+                       ScriptRunIterator&,
+                       LanguageRunIterator&,
+                       SkScalar width,
+                       RunHandler*) const = 0;
 
 private:
     SkShaper(const SkShaper&) = delete;
     SkShaper& operator=(const SkShaper&) = delete;
-
-    struct Impl;
-    std::unique_ptr<Impl> fImpl;
 };
 
 /**
@@ -72,12 +158,30 @@
  */
 class SkTextBlobBuilderRunHandler final : public SkShaper::RunHandler {
 public:
+    SkTextBlobBuilderRunHandler(const char* utf8Text, SkPoint offset)
+        : fUtf8Text(utf8Text)
+        , fOffset(offset) {}
     sk_sp<SkTextBlob> makeBlob();
+    SkPoint endPoint() { return fOffset; }
 
-    SkShaper::RunHandler::Buffer newRunBuffer(const RunInfo&, const SkFont&, int, int) override;
+    void beginLine() override;
+    void runInfo(const RunInfo&) override;
+    void commitRunInfo() override;
+    Buffer runBuffer(const RunInfo&) override;
+    void commitRunBuffer(const RunInfo&) override;
+    void commitLine() override;
 
 private:
     SkTextBlobBuilder fBuilder;
+    char const * const fUtf8Text;
+    uint32_t* fClusters;
+    int fClusterOffset;
+    int fGlyphCount;
+    SkScalar fMaxRunAscent;
+    SkScalar fMaxRunDescent;
+    SkScalar fMaxRunLeading;
+    SkPoint fCurrentPosition;
+    SkPoint fOffset;
 };
 
 #endif  // SkShaper_DEFINED
diff --git a/modules/skshaper/skshaper.gni b/modules/skshaper/skshaper.gni
index c0f5fd0..ed66e92 100644
--- a/modules/skshaper/skshaper.gni
+++ b/modules/skshaper/skshaper.gni
@@ -9,12 +9,8 @@
 
 skia_shaper_public = [ "$_include/SkShaper.h" ]
 
-skia_shaper_harfbuzz_sources = [
-  "$_src/SkShaper.cpp",
-  "$_src/SkShaper_harfbuzz.cpp",
-]
-
 skia_shaper_primitive_sources = [
   "$_src/SkShaper.cpp",
   "$_src/SkShaper_primitive.cpp",
 ]
+skia_shaper_harfbuzz_sources = [ "$_src/SkShaper_harfbuzz.cpp" ]
diff --git a/modules/skshaper/src/SkShaper.cpp b/modules/skshaper/src/SkShaper.cpp
index e08b7b3..26f1001 100644
--- a/modules/skshaper/src/SkShaper.cpp
+++ b/modules/skshaper/src/SkShaper.cpp
@@ -5,20 +5,206 @@
  * found in the LICENSE file.
  */
 
+#include "SkFont.h"
+#include "SkFontMetrics.h"
+#include "SkFontMgr.h"
+#include "SkMakeUnique.h"
 #include "SkShaper.h"
-
+#include "SkString.h"
+#include "SkTFitsIn.h"
 #include "SkTextBlobPriv.h"
+#include "SkTypeface.h"
+#include "SkUTF.h"
 
-SkShaper::RunHandler::Buffer SkTextBlobBuilderRunHandler::newRunBuffer(const RunInfo&,
-                                                                       const SkFont& font,
-                                                                       int glyphCount,
-                                                                       int textCount) {
-    const auto& runBuffer = SkTextBlobBuilderPriv::AllocRunTextPos(&fBuilder, font, glyphCount,
-                                                                   textCount, SkString());
+#include <limits.h>
+#include <string.h>
+#include <locale>
+#include <string>
+#include <utility>
+
+std::unique_ptr<SkShaper> SkShaper::Make() {
+#ifdef SK_SHAPER_HARFBUZZ_AVAILABLE
+    std::unique_ptr<SkShaper> shaper = SkShaper::MakeShaperDrivenWrapper();
+    if (shaper) {
+        return shaper;
+    }
+#endif
+    return SkShaper::MakePrimitive();
+}
+
+SkShaper::SkShaper() {}
+SkShaper::~SkShaper() {}
+
+/** Replaces invalid utf-8 sequences with REPLACEMENT CHARACTER U+FFFD. */
+static inline SkUnichar utf8_next(const char** ptr, const char* end) {
+    SkUnichar val = SkUTF::NextUTF8(ptr, end);
+    return val < 0 ? 0xFFFD : val;
+}
+
+class FontMgrRunIterator final : public SkShaper::FontRunIterator {
+public:
+    FontMgrRunIterator(const char* utf8, size_t utf8Bytes, const SkFont& font,
+                       sk_sp<SkFontMgr> fallbackMgr)
+        : fCurrent(utf8), fBegin(utf8), fEnd(fCurrent + utf8Bytes)
+        , fFallbackMgr(std::move(fallbackMgr))
+        , fFont(font)
+        , fFallbackFont(fFont)
+        , fCurrentFont(nullptr)
+    {
+        fFont.setTypeface(font.refTypefaceOrDefault());
+        fFallbackFont.setTypeface(nullptr);
+    }
+    void consume() override {
+        SkASSERT(fCurrent < fEnd);
+        SkUnichar u = utf8_next(&fCurrent, fEnd);
+        // If the starting typeface can handle this character, use it.
+        if (fFont.unicharToGlyph(u)) {
+            fCurrentFont = &fFont;
+        // If the current fallback can handle this character, use it.
+        } else if (fFallbackFont.getTypeface() && fFallbackFont.unicharToGlyph(u)) {
+            fCurrentFont = &fFallbackFont;
+        // If not, try to find a fallback typeface
+        } else {
+            sk_sp<SkTypeface> candidate(fFallbackMgr->matchFamilyStyleCharacter(
+                nullptr, fFont.getTypeface()->fontStyle(), nullptr, 0, u));
+            if (candidate) {
+                fFallbackFont.setTypeface(std::move(candidate));
+                fCurrentFont = &fFallbackFont;
+            } else {
+                fCurrentFont = &fFont;
+            }
+        }
+
+        while (fCurrent < fEnd) {
+            const char* prev = fCurrent;
+            u = utf8_next(&fCurrent, fEnd);
+
+            // End run if not using initial typeface and initial typeface has this character.
+            if (fCurrentFont->getTypeface() != fFont.getTypeface() && fFont.unicharToGlyph(u)) {
+                fCurrent = prev;
+                return;
+            }
+
+            // End run if current typeface does not have this character and some other font does.
+            if (!fCurrentFont->unicharToGlyph(u)) {
+                sk_sp<SkTypeface> candidate(fFallbackMgr->matchFamilyStyleCharacter(
+                    nullptr, fFont.getTypeface()->fontStyle(), nullptr, 0, u));
+                if (candidate) {
+                    fCurrent = prev;
+                    return;
+                }
+            }
+        }
+    }
+    size_t endOfCurrentRun() const override {
+        return fCurrent - fBegin;
+    }
+    bool atEnd() const override {
+        return fCurrent == fEnd;
+    }
+
+    const SkFont& currentFont() const override {
+        return *fCurrentFont;
+    }
+
+private:
+    char const * fCurrent;
+    char const * const fBegin;
+    char const * const fEnd;
+    sk_sp<SkFontMgr> fFallbackMgr;
+    SkFont fFont;
+    SkFont fFallbackFont;
+    SkFont* fCurrentFont;
+};
+
+std::unique_ptr<SkShaper::FontRunIterator>
+SkShaper::MakeFontMgrRunIterator(const char* utf8, size_t utf8Bytes,
+                                 const SkFont& font, sk_sp<SkFontMgr> fallback)
+{
+    return skstd::make_unique<FontMgrRunIterator>(utf8, utf8Bytes, font, std::move(fallback));
+}
+
+class StdLanguageRunIterator final : public SkShaper::LanguageRunIterator {
+public:
+    StdLanguageRunIterator(const char* utf8, size_t utf8Bytes)
+        : fCurrent(utf8), fBegin(utf8), fEnd(fCurrent + utf8Bytes)
+        , fLanguage(std::locale().name().c_str())
+    { }
+    void consume() override {
+        // Ideally something like cld2/3 could be used, or user signals.
+        SkASSERT(fCurrent < fEnd);
+        fCurrent = fEnd;
+    }
+    size_t endOfCurrentRun() const override {
+        return fCurrent - fBegin;
+    }
+    bool atEnd() const override {
+        return fCurrent == fEnd;
+    }
+
+    const char* currentLanguage() const override {
+        return fLanguage.c_str();
+    }
+private:
+    char const * fCurrent;
+    char const * const fBegin;
+    char const * const fEnd;
+    const SkString fLanguage;
+};
+
+std::unique_ptr<SkShaper::LanguageRunIterator>
+SkShaper::MakeStdLanguageRunIterator(const char* utf8, size_t utf8Bytes) {
+    return skstd::make_unique<StdLanguageRunIterator>(utf8, utf8Bytes);
+}
+
+void SkTextBlobBuilderRunHandler::beginLine() {
+    fCurrentPosition = fOffset;
+    fMaxRunAscent = 0;
+    fMaxRunDescent = 0;
+    fMaxRunLeading = 0;
+}
+void SkTextBlobBuilderRunHandler::runInfo(const RunInfo& info) {
+    SkFontMetrics metrics;
+    info.fFont.getMetrics(&metrics);
+    fMaxRunAscent = SkTMin(fMaxRunAscent, metrics.fAscent);
+    fMaxRunDescent = SkTMax(fMaxRunDescent, metrics.fDescent);
+    fMaxRunLeading = SkTMax(fMaxRunLeading, metrics.fLeading);
+}
+
+void SkTextBlobBuilderRunHandler::commitRunInfo() {
+    fCurrentPosition.fY -= fMaxRunAscent;
+}
+
+SkShaper::RunHandler::Buffer SkTextBlobBuilderRunHandler::runBuffer(const RunInfo& info) {
+    int glyphCount = SkTFitsIn<int>(info.glyphCount) ? info.glyphCount : INT_MAX;
+    int utf8RangeSize = SkTFitsIn<int>(info.utf8Range.size()) ? info.utf8Range.size() : INT_MAX;
+
+    const auto& runBuffer = SkTextBlobBuilderPriv::AllocRunTextPos(&fBuilder, info.fFont, glyphCount,
+                                                                   utf8RangeSize, SkString());
+    if (runBuffer.utf8text && fUtf8Text) {
+        memcpy(runBuffer.utf8text, fUtf8Text + info.utf8Range.begin(), utf8RangeSize);
+    }
+    fClusters = runBuffer.clusters;
+    fGlyphCount = glyphCount;
+    fClusterOffset = info.utf8Range.begin();
+
     return { runBuffer.glyphs,
              runBuffer.points(),
-             runBuffer.utf8text,
-             runBuffer.clusters };
+             nullptr,
+             runBuffer.clusters,
+             fCurrentPosition };
+}
+
+void SkTextBlobBuilderRunHandler::commitRunBuffer(const RunInfo& info) {
+    SkASSERT(0 <= fClusterOffset);
+    for (int i = 0; i < fGlyphCount; ++i) {
+        SkASSERT(fClusters[i] >= (unsigned)fClusterOffset);
+        fClusters[i] -= fClusterOffset;
+    }
+    fCurrentPosition += info.fAdvance;
+}
+void SkTextBlobBuilderRunHandler::commitLine() {
+    fOffset += { 0, fMaxRunDescent + fMaxRunLeading - fMaxRunAscent };
 }
 
 sk_sp<SkTextBlob> SkTextBlobBuilderRunHandler::makeBlob() {
diff --git a/modules/skshaper/src/SkShaper_harfbuzz.cpp b/modules/skshaper/src/SkShaper_harfbuzz.cpp
index a74d0c0..ae30461 100644
--- a/modules/skshaper/src/SkShaper_harfbuzz.cpp
+++ b/modules/skshaper/src/SkShaper_harfbuzz.cpp
@@ -10,17 +10,19 @@
 #include "SkFontArguments.h"
 #include "SkFontMetrics.h"
 #include "SkFontMgr.h"
+#include "SkFontTypes.h"
+#include "SkMakeUnique.h"
 #include "SkMalloc.h"
+#include "SkPaint.h"
 #include "SkPoint.h"
+#include "SkRect.h"
 #include "SkRefCnt.h"
 #include "SkScalar.h"
 #include "SkShaper.h"
 #include "SkStream.h"
-#include "SkString.h"
 #include "SkTArray.h"
 #include "SkTDPQueue.h"
 #include "SkTFitsIn.h"
-#include "SkTLazy.h"
 #include "SkTemplates.h"
 #include "SkTo.h"
 #include "SkTypeface.h"
@@ -28,17 +30,20 @@
 #include "SkUTF.h"
 
 #include <hb.h>
+#include <hb-icu.h>
 #include <hb-ot.h>
-#include <unicode/ubrk.h>
 #include <unicode/ubidi.h>
-#include <unicode/ustring.h>
+#include <unicode/ubrk.h>
+#include <unicode/umachine.h>
 #include <unicode/urename.h>
+#include <unicode/uscript.h>
+#include <unicode/ustring.h>
 #include <unicode/utext.h>
 #include <unicode/utypes.h>
 
 #include <cstring>
-#include <locale>
 #include <memory>
+#include <type_traits>
 #include <utility>
 
 #if defined(SK_USING_THIRD_PARTY_ICU)
@@ -77,113 +82,256 @@
     return blob;
 }
 
-HBFont create_hb_font(SkTypeface* tf) {
-    if (!tf) {
+hb_position_t skhb_position(SkScalar value) {
+    // Treat HarfBuzz hb_position_t as 16.16 fixed-point.
+    constexpr int kHbPosition1 = 1 << 16;
+    return SkScalarRoundToInt(value * kHbPosition1);
+}
+
+hb_bool_t skhb_glyph(hb_font_t* hb_font,
+                     void* font_data,
+                     hb_codepoint_t unicode,
+                     hb_codepoint_t variation_selector,
+                     hb_codepoint_t* glyph,
+                     void* user_data) {
+    SkFont& font = *reinterpret_cast<SkFont*>(font_data);
+
+    *glyph = font.unicharToGlyph(unicode);
+    return *glyph != 0;
+}
+
+hb_bool_t skhb_nominal_glyph(hb_font_t* hb_font,
+                             void* font_data,
+                             hb_codepoint_t unicode,
+                             hb_codepoint_t* glyph,
+                             void* user_data) {
+  return skhb_glyph(hb_font, font_data, unicode, 0, glyph, user_data);
+}
+
+unsigned skhb_nominal_glyphs(hb_font_t *hb_font, void *font_data,
+                             unsigned int count,
+                             const hb_codepoint_t *unicodes,
+                             unsigned int unicode_stride,
+                             hb_codepoint_t *glyphs,
+                             unsigned int glyph_stride,
+                             void *user_data) {
+    SkFont& font = *reinterpret_cast<SkFont*>(font_data);
+
+    // Batch call textToGlyphs since entry cost is not cheap.
+    // Copy requred because textToGlyphs is dense and hb is strided.
+    SkAutoSTMalloc<256, SkUnichar> unicode(count);
+    for (unsigned i = 0; i < count; i++) {
+        unicode[i] = *unicodes;
+        unicodes = SkTAddOffset<const hb_codepoint_t>(unicodes, unicode_stride);
+    }
+    SkAutoSTMalloc<256, SkGlyphID> glyph(count);
+    font.textToGlyphs(unicode.get(), count * sizeof(SkUnichar), kUTF32_SkTextEncoding,
+                        glyph.get(), count);
+
+    // Copy the results back to the sparse array.
+    for (unsigned i = 0; i < count; i++) {
+        *glyphs = glyph[i];
+        glyphs = SkTAddOffset<hb_codepoint_t>(glyphs, glyph_stride);
+    }
+    // TODO: supposed to return index of first 0?
+    return count;
+}
+
+hb_position_t skhb_glyph_h_advance(hb_font_t* hb_font,
+                                   void* font_data,
+                                   hb_codepoint_t codepoint,
+                                   void* user_data) {
+    SkFont& font = *reinterpret_cast<SkFont*>(font_data);
+
+    SkScalar advance;
+    SkGlyphID glyph = SkTo<SkGlyphID>(codepoint);
+
+    font.getWidths(&glyph, 1, &advance);
+    if (!font.isSubpixel()) {
+        advance = SkScalarRoundToInt(advance);
+    }
+    return skhb_position(advance);
+}
+
+void skhb_glyph_h_advances(hb_font_t* hb_font,
+                           void* font_data,
+                           unsigned count,
+                           const hb_codepoint_t* glyphs,
+                           unsigned int glyph_stride,
+                           hb_position_t* advances,
+                           unsigned int advance_stride,
+                           void* user_data) {
+    SkFont& font = *reinterpret_cast<SkFont*>(font_data);
+
+    // Batch call getWidths since entry cost is not cheap.
+    // Copy requred because getWidths is dense and hb is strided.
+    SkAutoSTMalloc<256, SkGlyphID> glyph(count);
+    for (unsigned i = 0; i < count; i++) {
+        glyph[i] = *glyphs;
+        glyphs = SkTAddOffset<const hb_codepoint_t>(glyphs, glyph_stride);
+    }
+    SkAutoSTMalloc<256, SkScalar> advance(count);
+    font.getWidths(glyph.get(), count, advance.get());
+
+    if (!font.isSubpixel()) {
+        for (unsigned i = 0; i < count; i++) {
+            advance[i] = SkScalarRoundToInt(advance[i]);
+        }
+    }
+
+    // Copy the results back to the sparse array.
+    for (unsigned i = 0; i < count; i++) {
+        *advances = skhb_position(advance[i]);
+        advances = SkTAddOffset<hb_position_t>(advances, advance_stride);
+    }
+}
+
+// HarfBuzz callback to retrieve glyph extents, mainly used by HarfBuzz for
+// fallback mark positioning, i.e. the situation when the font does not have
+// mark anchors or other mark positioning rules, but instead HarfBuzz is
+// supposed to heuristically place combining marks around base glyphs. HarfBuzz
+// does this by measuring "ink boxes" of glyphs, and placing them according to
+// Unicode mark classes. Above, below, centered or left or right, etc.
+hb_bool_t skhb_glyph_extents(hb_font_t* hb_font,
+                             void* font_data,
+                             hb_codepoint_t codepoint,
+                             hb_glyph_extents_t* extents,
+                             void* user_data) {
+    SkFont& font = *reinterpret_cast<SkFont*>(font_data);
+
+    SkASSERT(codepoint < 0xFFFFu);
+    SkASSERT(extents);
+
+    SkRect sk_bounds;
+    SkGlyphID glyph = codepoint;
+
+    font.getWidths(&glyph, 1, nullptr, &sk_bounds);
+    if (!font.isSubpixel()) {
+        sk_bounds.set(sk_bounds.roundOut());
+    }
+
+    // Skia is y-down but HarfBuzz is y-up.
+    extents->x_bearing = skhb_position(sk_bounds.fLeft);
+    extents->y_bearing = skhb_position(-sk_bounds.fTop);
+    extents->width = skhb_position(sk_bounds.width());
+    extents->height = skhb_position(-sk_bounds.height());
+    return true;
+}
+
+#define SK_HB_VERSION_CHECK(x, y, z) \
+    (HB_VERSION_MAJOR >  (x)) || \
+    (HB_VERSION_MAJOR == (x) && HB_VERSION_MINOR >  (y)) || \
+    (HB_VERSION_MAJOR == (x) && HB_VERSION_MINOR == (y) && HB_VERSION_MICRO >= (z))
+
+hb_font_funcs_t* skhb_get_font_funcs() {
+    static hb_font_funcs_t* const funcs = []{
+        // HarfBuzz will use the default (parent) implementation if they aren't set.
+        hb_font_funcs_t* const funcs = hb_font_funcs_create();
+        hb_font_funcs_set_variation_glyph_func(funcs, skhb_glyph, nullptr, nullptr);
+        hb_font_funcs_set_nominal_glyph_func(funcs, skhb_nominal_glyph, nullptr, nullptr);
+#if SK_HB_VERSION_CHECK(2, 0, 0)
+        hb_font_funcs_set_nominal_glyphs_func(funcs, skhb_nominal_glyphs, nullptr, nullptr);
+#else
+        sk_ignore_unused_variable(skhb_nominal_glyphs);
+#endif
+        hb_font_funcs_set_glyph_h_advance_func(funcs, skhb_glyph_h_advance, nullptr, nullptr);
+#if SK_HB_VERSION_CHECK(1, 8, 6)
+        hb_font_funcs_set_glyph_h_advances_func(funcs, skhb_glyph_h_advances, nullptr, nullptr);
+#else
+        sk_ignore_unused_variable(skhb_glyph_h_advances);
+#endif
+        hb_font_funcs_set_glyph_extents_func(funcs, skhb_glyph_extents, nullptr, nullptr);
+        hb_font_funcs_make_immutable(funcs);
+        return funcs;
+    }();
+    SkASSERT(funcs);
+    return funcs;
+}
+
+hb_blob_t* skhb_get_table(hb_face_t* face, hb_tag_t tag, void* user_data) {
+    SkTypeface& typeface = *reinterpret_cast<SkTypeface*>(user_data);
+
+    const size_t tableSize = typeface.getTableSize(tag);
+    if (!tableSize) {
         return nullptr;
     }
+
+    void* buffer = sk_malloc_throw(tableSize);
+    if (!buffer) {
+        return nullptr;
+    }
+
+    size_t actualSize = typeface.getTableData(tag, 0, tableSize, buffer);
+    if (tableSize != actualSize) {
+        sk_free(buffer);
+        return nullptr;
+    }
+
+    return hb_blob_create(reinterpret_cast<char*>(buffer), tableSize,
+                          HB_MEMORY_MODE_WRITABLE, buffer, sk_free);
+}
+
+HBFont create_hb_font(const SkFont& font) {
     int index;
-    std::unique_ptr<SkStreamAsset> typefaceAsset(tf->openStream(&index));
+    std::unique_ptr<SkStreamAsset> typefaceAsset = font.getTypeface()->openStream(&index);
+    HBFace face;
     if (!typefaceAsset) {
-        SkString name;
-        tf->getFamilyName(&name);
-        SkDebugf("Typeface '%s' has no data :(\n", name.c_str());
-        return nullptr;
+        face.reset(hb_face_create_for_tables(
+            skhb_get_table,
+            reinterpret_cast<void *>(font.refTypeface().release()),
+            [](void* user_data){ SkSafeUnref(reinterpret_cast<SkTypeface*>(user_data)); }));
+    } else {
+        HBBlob blob(stream_to_blob(std::move(typefaceAsset)));
+        face.reset(hb_face_create(blob.get(), (unsigned)index));
     }
-    HBBlob blob(stream_to_blob(std::move(typefaceAsset)));
-    HBFace face(hb_face_create(blob.get(), (unsigned)index));
     SkASSERT(face);
     if (!face) {
         return nullptr;
     }
     hb_face_set_index(face.get(), (unsigned)index);
-    hb_face_set_upem(face.get(), tf->getUnitsPerEm());
+    hb_face_set_upem(face.get(), font.getTypeface()->getUnitsPerEm());
 
-    HBFont font(hb_font_create(face.get()));
-    SkASSERT(font);
-    if (!font) {
+    HBFont otFont(hb_font_create(face.get()));
+    SkASSERT(otFont);
+    if (!otFont) {
         return nullptr;
     }
-    hb_ot_font_set_funcs(font.get());
-    int axis_count = tf->getVariationDesignPosition(nullptr, 0);
+    hb_ot_font_set_funcs(otFont.get());
+    int axis_count = font.getTypeface()->getVariationDesignPosition(nullptr, 0);
     if (axis_count > 0) {
         SkAutoSTMalloc<4, SkFontArguments::VariationPosition::Coordinate> axis_values(axis_count);
-        if (tf->getVariationDesignPosition(axis_values, axis_count) == axis_count) {
-            hb_font_set_variations(font.get(),
+        if (font.getTypeface()->getVariationDesignPosition(axis_values, axis_count) == axis_count) {
+            hb_font_set_variations(otFont.get(),
                                    reinterpret_cast<hb_variation_t*>(axis_values.get()),
                                    axis_count);
         }
     }
-    return font;
+
+    // Creating a sub font means that non-available functions
+    // are found from the parent.
+    HBFont skFont(hb_font_create_sub_font(otFont.get()));
+    hb_font_set_funcs(skFont.get(), skhb_get_font_funcs(),
+                      reinterpret_cast<void *>(new SkFont(font)),
+                      [](void* user_data){ delete reinterpret_cast<SkFont*>(user_data); });
+    int scale = skhb_position(font.getSize());
+    hb_font_set_scale(skFont.get(), scale, scale);
+
+    return skFont;
 }
 
-/** this version replaces invalid utf-8 sequences with code point U+FFFD. */
+/** Replaces invalid utf-8 sequences with REPLACEMENT CHARACTER U+FFFD. */
 static inline SkUnichar utf8_next(const char** ptr, const char* end) {
     SkUnichar val = SkUTF::NextUTF8(ptr, end);
-    if (val < 0) {
-        return 0xFFFD;  // REPLACEMENT CHARACTER
-    }
-    return val;
+    return val < 0 ? 0xFFFD : val;
 }
 
-class RunIterator {
+class IcuBiDiRunIterator final : public SkShaper::BiDiRunIterator {
 public:
-    virtual ~RunIterator() {}
-    virtual void consume() = 0;
-    // Pointer one past the last (utf8) element in the current run.
-    virtual const char* endOfCurrentRun() const = 0;
-    virtual bool atEnd() const = 0;
-    bool operator<(const RunIterator& that) const {
-        return this->endOfCurrentRun() < that.endOfCurrentRun();
-    }
-};
-
-class BiDiRunIterator : public RunIterator {
-public:
-    static SkTLazy<BiDiRunIterator> Make(const char* utf8, size_t utf8Bytes, UBiDiLevel level) {
-        SkTLazy<BiDiRunIterator> ret;
-
-        // ubidi only accepts utf16 (though internally it basically works on utf32 chars).
-        // We want an ubidi_setPara(UBiDi*, UText*, UBiDiLevel, UBiDiLevel*, UErrorCode*);
-        if (!SkTFitsIn<int32_t>(utf8Bytes)) {
-            SkDebugf("Bidi error: text too long");
-            return ret;
-        }
-
-        UErrorCode status = U_ZERO_ERROR;
-
-        // Getting the length like this seems to always set U_BUFFER_OVERFLOW_ERROR
-        int32_t utf16Units;
-        u_strFromUTF8(nullptr, 0, &utf16Units, utf8, utf8Bytes, &status);
-        status = U_ZERO_ERROR;
-        std::unique_ptr<UChar[]> utf16(new UChar[utf16Units]);
-        u_strFromUTF8(utf16.get(), utf16Units, nullptr, utf8, utf8Bytes, &status);
-        if (U_FAILURE(status)) {
-            SkDebugf("Invalid utf8 input: %s", u_errorName(status));
-            return ret;
-        }
-
-        ICUBiDi bidi(ubidi_openSized(utf16Units, 0, &status));
-        if (U_FAILURE(status)) {
-            SkDebugf("Bidi error: %s", u_errorName(status));
-            return ret;
-        }
-        SkASSERT(bidi);
-
-        // The required lifetime of utf16 isn't well documented.
-        // It appears it isn't used after ubidi_setPara except through ubidi_getText.
-        ubidi_setPara(bidi.get(), utf16.get(), utf16Units, level, nullptr, &status);
-        if (U_FAILURE(status)) {
-            SkDebugf("Bidi error: %s", u_errorName(status));
-            return ret;
-        }
-
-        ret.init(utf8, utf8 + utf8Bytes, std::move(bidi));
-        return ret;
-    }
-    BiDiRunIterator(const char* utf8, const char* end, ICUBiDi bidi)
+    IcuBiDiRunIterator(const char* utf8, const char* end, ICUBiDi bidi)
         : fBidi(std::move(bidi))
         , fEndOfCurrentRun(utf8)
-        , fEndOfAllRuns(end)
+        , fBegin(utf8)
+        , fEnd(end)
         , fUTF16LogicalPosition(0)
         , fLevel(UBIDI_DEFAULT_LTR)
     {}
@@ -191,7 +339,7 @@
         SkASSERT(fUTF16LogicalPosition < ubidi_getLength(fBidi.get()));
         int32_t endPosition = ubidi_getLength(fBidi.get());
         fLevel = ubidi_getLevelAt(fBidi.get(), fUTF16LogicalPosition);
-        SkUnichar u = utf8_next(&fEndOfCurrentRun, fEndOfAllRuns);
+        SkUnichar u = utf8_next(&fEndOfCurrentRun, fEnd);
         fUTF16LogicalPosition += SkUTF::ToUTF16(u);
         UBiDiLevel level;
         while (fUTF16LogicalPosition < endPosition) {
@@ -199,50 +347,53 @@
             if (level != fLevel) {
                 break;
             }
-            u = utf8_next(&fEndOfCurrentRun, fEndOfAllRuns);
+            u = utf8_next(&fEndOfCurrentRun, fEnd);
             fUTF16LogicalPosition += SkUTF::ToUTF16(u);
         }
     }
-    const char* endOfCurrentRun() const override {
-        return fEndOfCurrentRun;
+    size_t endOfCurrentRun() const override {
+        return fEndOfCurrentRun - fBegin;
     }
     bool atEnd() const override {
         return fUTF16LogicalPosition == ubidi_getLength(fBidi.get());
     }
 
-    UBiDiLevel currentLevel() const {
+    UBiDiLevel currentLevel() const override {
         return fLevel;
     }
 private:
     ICUBiDi fBidi;
-    const char* fEndOfCurrentRun;
-    const char* fEndOfAllRuns;
+    char const * fEndOfCurrentRun;
+    char const * const fBegin;
+    char const * const fEnd;
     int32_t fUTF16LogicalPosition;
     UBiDiLevel fLevel;
 };
 
-class ScriptRunIterator : public RunIterator {
+class HbIcuScriptRunIterator final : public SkShaper::ScriptRunIterator {
 public:
-    static SkTLazy<ScriptRunIterator> Make(const char* utf8, size_t utf8Bytes,
-                                           hb_unicode_funcs_t* hbUnicode)
-    {
-        SkTLazy<ScriptRunIterator> ret;
-        ret.init(utf8, utf8Bytes, hbUnicode);
-        return ret;
-    }
-    ScriptRunIterator(const char* utf8, size_t utf8Bytes, hb_unicode_funcs_t* hbUnicode)
-        : fCurrent(utf8), fEnd(fCurrent + utf8Bytes)
-        , fHBUnicode(hbUnicode)
+    HbIcuScriptRunIterator(const char* utf8, size_t utf8Bytes)
+        : fCurrent(utf8), fBegin(utf8), fEnd(fCurrent + utf8Bytes)
         , fCurrentScript(HB_SCRIPT_UNKNOWN)
     {}
+    static hb_script_t hb_script_from_icu(SkUnichar u) {
+        UErrorCode status = U_ZERO_ERROR;
+        UScriptCode scriptCode = uscript_getScript(u, &status);
+
+        if (U_FAILURE (status)) {
+            return HB_SCRIPT_UNKNOWN;
+        }
+
+        return hb_icu_script_to_script(scriptCode);
+    }
     void consume() override {
         SkASSERT(fCurrent < fEnd);
         SkUnichar u = utf8_next(&fCurrent, fEnd);
-        fCurrentScript = hb_unicode_script(fHBUnicode, u);
+        fCurrentScript = hb_script_from_icu(u);
         while (fCurrent < fEnd) {
             const char* prev = fCurrent;
             u = utf8_next(&fCurrent, fEnd);
-            const hb_script_t script = hb_unicode_script(fHBUnicode, u);
+            const hb_script_t script = hb_script_from_icu(u);
             if (script != fCurrentScript) {
                 if (fCurrentScript == HB_SCRIPT_INHERITED || fCurrentScript == HB_SCRIPT_COMMON) {
                     fCurrentScript = script;
@@ -258,161 +409,38 @@
             fCurrentScript = HB_SCRIPT_COMMON;
         }
     }
-    const char* endOfCurrentRun() const override {
-        return fCurrent;
+    size_t endOfCurrentRun() const override {
+        return fCurrent - fBegin;
     }
     bool atEnd() const override {
         return fCurrent == fEnd;
     }
 
-    hb_script_t currentScript() const {
-        return fCurrentScript;
+    SkFourByteTag currentScript() const override {
+        return SkSetFourByteTag(HB_UNTAG(fCurrentScript));
     }
 private:
-    const char* fCurrent;
-    const char* fEnd;
-    hb_unicode_funcs_t* fHBUnicode;
+    char const * fCurrent;
+    char const * const fBegin;
+    char const * const fEnd;
     hb_script_t fCurrentScript;
 };
 
-class FontRunIterator : public RunIterator {
-public:
-    static SkTLazy<FontRunIterator> Make(const char* utf8, size_t utf8Bytes,
-                                         SkFont font,
-                                         sk_sp<SkFontMgr> fallbackMgr)
-    {
-        SkTLazy<FontRunIterator> ret;
-        font.setTypeface(font.refTypefaceOrDefault());
-        HBFont hbFont = create_hb_font(font.getTypeface());
-        if (!hbFont) {
-            SkDebugf("create_hb_font failed!\n");
-            return ret;
-        }
-        ret.init(utf8, utf8Bytes, std::move(font), std::move(hbFont), std::move(fallbackMgr));
-        return ret;
-    }
-    FontRunIterator(const char* utf8, size_t utf8Bytes, SkFont font,
-                    HBFont hbFont, sk_sp<SkFontMgr> fallbackMgr)
-        : fCurrent(utf8), fEnd(fCurrent + utf8Bytes)
-        , fFallbackMgr(std::move(fallbackMgr))
-        , fHBFont(std::move(hbFont)), fFont(std::move(font))
-        , fFallbackHBFont(nullptr), fFallbackFont(fFont)
-        , fCurrentHBFont(fHBFont.get()), fCurrentFont(&fFont)
-    {
-        fFallbackFont.setTypeface(nullptr);
-    }
-    void consume() override {
-        SkASSERT(fCurrent < fEnd);
-        SkUnichar u = utf8_next(&fCurrent, fEnd);
-        // If the starting typeface can handle this character, use it.
-        if (fFont.getTypeface()->charsToGlyphs(&u, SkTypeface::kUTF32_Encoding, nullptr, 1)) {
-            fCurrentFont = &fFont;
-            fCurrentHBFont = fHBFont.get();
-        // If the current fallback can handle this character, use it.
-        } else if (fFallbackFont.getTypeface() &&
-                   fFallbackFont.getTypeface()->charsToGlyphs(&u, SkTypeface::kUTF32_Encoding, nullptr, 1))
-        {
-            fCurrentFont = &fFallbackFont;
-            fCurrentHBFont = fFallbackHBFont.get();
-        // If not, try to find a fallback typeface
-        } else {
-            fFallbackFont.setTypeface(sk_ref_sp(fFallbackMgr->matchFamilyStyleCharacter(
-                nullptr, fFont.getTypeface()->fontStyle(), nullptr, 0, u)));
-            fFallbackHBFont = create_hb_font(fFallbackFont.getTypeface());
-            fCurrentFont = &fFallbackFont;
-            fCurrentHBFont = fFallbackHBFont.get();
-        }
-
-        while (fCurrent < fEnd) {
-            const char* prev = fCurrent;
-            u = utf8_next(&fCurrent, fEnd);
-
-            // If not using initial typeface and initial typeface has this character, stop fallback.
-            if (fCurrentFont->getTypeface() != fFont.getTypeface() &&
-                fFont.getTypeface()->charsToGlyphs(&u, SkTypeface::kUTF32_Encoding, nullptr, 1))
-            {
-                fCurrent = prev;
-                return;
-            }
-            // If the current typeface cannot handle this character, stop using it.
-            if (!fCurrentFont->getTypeface()->charsToGlyphs(&u, SkTypeface::kUTF32_Encoding, nullptr, 1)) {
-                fCurrent = prev;
-                return;
-            }
-        }
-    }
-    const char* endOfCurrentRun() const override {
-        return fCurrent;
-    }
-    bool atEnd() const override {
-        return fCurrent == fEnd;
-    }
-
-    SkFont* currentFont() const {
-        return fCurrentFont;
-    }
-    hb_font_t* currentHBFont() const {
-        return fCurrentHBFont;
-    }
-private:
-    const char* fCurrent;
-    const char* fEnd;
-    sk_sp<SkFontMgr> fFallbackMgr;
-    HBFont fHBFont;
-    SkFont fFont;
-    HBFont fFallbackHBFont;
-    SkFont fFallbackFont;
-    hb_font_t* fCurrentHBFont;
-    SkFont* fCurrentFont;
-};
-
-class LanguageRunIterator : public RunIterator {
-public:
-    static SkTLazy<LanguageRunIterator> Make(const char* utf8, size_t utf8Bytes) {
-        SkTLazy<LanguageRunIterator> ret;
-        ret.init(utf8, utf8Bytes);
-        return ret;
-    }
-    LanguageRunIterator(const char* utf8, size_t utf8Bytes)
-        : fCurrent(utf8), fEnd(fCurrent + utf8Bytes)
-    , fLanguage(hb_language_from_string(std::locale().name().c_str(), -1))
-    { }
-    void consume() override {
-        // Ideally something like cld2/3 could be used, or user signals.
-        SkASSERT(fCurrent < fEnd);
-        fCurrent = fEnd;
-    }
-    const char* endOfCurrentRun() const override {
-        return fCurrent;
-    }
-    bool atEnd() const override {
-        return fCurrent == fEnd;
-    }
-
-    hb_language_t currentLanguage() const {
-        return fLanguage;
-    }
-private:
-    const char* fCurrent;
-    const char* fEnd;
-    hb_language_t fLanguage;
-};
-
 class RunIteratorQueue {
 public:
-    void insert(RunIterator* runIterator) {
+    void insert(SkShaper::RunIterator* runIterator) {
         fRunIterators.insert(runIterator);
     }
 
     bool advanceRuns() {
-        const RunIterator* leastRun = fRunIterators.peek();
+        const SkShaper::RunIterator* leastRun = fRunIterators.peek();
         if (leastRun->atEnd()) {
             SkASSERT(this->allRunsAreAtEnd());
             return false;
         }
-        const char* leastEnd = leastRun->endOfCurrentRun();
-        RunIterator* currentRun = nullptr;
-        SkDEBUGCODE(const char* previousEndOfCurrentRun);
+        const size_t leastEnd = leastRun->endOfCurrentRun();
+        SkShaper::RunIterator* currentRun = nullptr;
+        SkDEBUGCODE(size_t previousEndOfCurrentRun);
         while ((currentRun = fRunIterators.peek())->endOfCurrentRun() <= leastEnd) {
             fRunIterators.pop();
             SkDEBUGCODE(previousEndOfCurrentRun = currentRun->endOfCurrentRun());
@@ -423,7 +451,7 @@
         return true;
     }
 
-    const char* endOfCurrentRun() const {
+    size_t endOfCurrentRun() const {
         return fRunIterators.peek()->endOfCurrentRun();
     }
 
@@ -437,10 +465,10 @@
         return true;
     }
 
-    static bool CompareRunIterator(RunIterator* const& a, RunIterator* const& b) {
-        return *a < *b;
+    static bool CompareRunIterator(SkShaper::RunIterator* const& a, SkShaper::RunIterator* const& b) {
+        return a->endOfCurrentRun() < b->endOfCurrentRun();
     }
-    SkTDPQueue<RunIterator*, CompareRunIterator> fRunIterators;
+    SkTDPQueue<SkShaper::RunIterator*, CompareRunIterator> fRunIterators;
 };
 
 struct ShapedGlyph {
@@ -455,69 +483,60 @@
     bool fUnsafeToBreak;
 };
 struct ShapedRun {
-    ShapedRun(const char* utf8Start, const char* utf8End, int numGlyphs, const SkFont& font,
-              UBiDiLevel level, std::unique_ptr<ShapedGlyph[]> glyphs)
-        : fUtf8Start(utf8Start), fUtf8End(utf8End), fNumGlyphs(numGlyphs), fFont(font)
-        , fLevel(level), fGlyphs(std::move(glyphs))
+    ShapedRun(SkShaper::RunHandler::Range utf8Range, const SkFont& font, UBiDiLevel level,
+              std::unique_ptr<ShapedGlyph[]> glyphs, size_t numGlyphs, SkVector advance = {0, 0})
+        : fUtf8Range(utf8Range), fFont(font), fLevel(level)
+        , fGlyphs(std::move(glyphs)), fNumGlyphs(numGlyphs), fAdvance(advance)
     {}
 
-    const char* fUtf8Start;
-    const char* fUtf8End;
-    int fNumGlyphs;
+    SkShaper::RunHandler::Range fUtf8Range;
     SkFont fFont;
     UBiDiLevel fLevel;
     std::unique_ptr<ShapedGlyph[]> fGlyphs;
-    SkVector fAdvance = { 0, 0 };
+    size_t fNumGlyphs;
+    SkVector fAdvance;
 };
 struct ShapedLine {
     SkTArray<ShapedRun> runs;
     SkVector fAdvance = { 0, 0 };
 };
 
-static constexpr bool is_LTR(UBiDiLevel level) {
+constexpr bool is_LTR(UBiDiLevel level) {
     return (level & 1) == 0;
 }
 
-static void append(SkShaper::RunHandler* handler, const SkShaper::RunHandler::RunInfo& runInfo,
-                   const ShapedRun& run, int start, int end,
-                   SkPoint* p) {
-    unsigned len = end - start;
+void append(SkShaper::RunHandler* handler, const SkShaper::RunHandler::RunInfo& runInfo,
+                   const ShapedRun& run, size_t startGlyphIndex, size_t endGlyphIndex) {
+    SkASSERT(startGlyphIndex <= endGlyphIndex);
+    const size_t glyphLen = endGlyphIndex - startGlyphIndex;
 
-    const auto buffer = handler->newRunBuffer(runInfo, run.fFont, len, run.fUtf8End - run.fUtf8Start);
+    const auto buffer = handler->runBuffer(runInfo);
     SkASSERT(buffer.glyphs);
     SkASSERT(buffer.positions);
 
-    if (buffer.utf8text) {
-        memcpy(buffer.utf8text, run.fUtf8Start, run.fUtf8End - run.fUtf8Start);
-    }
-
-    for (unsigned i = 0; i < len; i++) {
+    SkVector advance = {0,0};
+    for (size_t i = 0; i < glyphLen; i++) {
         // Glyphs are in logical order, but output ltr since PDF readers seem to expect that.
-        const ShapedGlyph& glyph = run.fGlyphs[is_LTR(run.fLevel) ? start + i : end - 1 - i];
+        const ShapedGlyph& glyph = run.fGlyphs[is_LTR(run.fLevel) ? startGlyphIndex + i
+                                                                  : endGlyphIndex - 1 - i];
         buffer.glyphs[i] = glyph.fID;
-        buffer.positions[i] = SkPoint::Make(p->fX + glyph.fOffset.fX, p->fY - glyph.fOffset.fY);
+        if (buffer.offsets) {
+            buffer.positions[i] = advance + buffer.point;
+            buffer.offsets[i] = glyph.fOffset; //TODO: invert glyph.fOffset.fY?
+        } else {
+            buffer.positions[i] = advance + buffer.point + glyph.fOffset; //TODO: invert glyph.fOffset.fY?
+        }
         if (buffer.clusters) {
             buffer.clusters[i] = glyph.fCluster;
         }
-        p->fX += glyph.fAdvance.fX;
-        p->fY += glyph.fAdvance.fY;
+        advance += glyph.fAdvance;
     }
+    handler->commitRunBuffer(runInfo);
 }
 
-static void emit(const ShapedLine& line, SkShaper::RunHandler* handler,
-                 SkPoint point, SkPoint& currentPoint, size_t& lineIndex)
-{
+void emit(const ShapedLine& line, SkShaper::RunHandler* handler) {
     // Reorder the runs and glyphs per line and write them out.
-    SkScalar maxAscent = 0;
-    SkScalar maxDescent = 0;
-    SkScalar maxLeading = 0;
-    for (const ShapedRun& run : line.runs) {
-        SkFontMetrics metrics;
-        run.fFont.getMetrics(&metrics);
-        maxAscent = SkTMin(maxAscent, metrics.fAscent);
-        maxDescent = SkTMax(maxDescent, metrics.fDescent);
-        maxLeading = SkTMax(maxLeading, metrics.fLeading);
-    }
+    handler->beginLine();
 
     int numRuns = line.runs.size();
     SkAutoSTMalloc<4, UBiDiLevel> runLevels(numRuns);
@@ -527,26 +546,35 @@
     SkAutoSTMalloc<4, int32_t> logicalFromVisual(numRuns);
     ubidi_reorderVisual(runLevels, numRuns, logicalFromVisual);
 
-    currentPoint.fY -= maxAscent;
-
     for (int i = 0; i < numRuns; ++i) {
         int logicalIndex = logicalFromVisual[i];
 
         const auto& run = line.runs[logicalIndex];
         const SkShaper::RunHandler::RunInfo info = {
-            lineIndex,
+            run.fFont,
+            run.fLevel,
             run.fAdvance,
-            maxAscent,
-            maxDescent,
-            maxLeading,
+            run.fNumGlyphs,
+            run.fUtf8Range
         };
-        append(handler, info, run, 0, run.fNumGlyphs, &currentPoint);
+        handler->runInfo(info);
+    }
+    handler->commitRunInfo();
+    for (int i = 0; i < numRuns; ++i) {
+        int logicalIndex = logicalFromVisual[i];
+
+        const auto& run = line.runs[logicalIndex];
+        const SkShaper::RunHandler::RunInfo info = {
+            run.fFont,
+            run.fLevel,
+            run.fAdvance,
+            run.fNumGlyphs,
+            run.fUtf8Range
+        };
+        append(handler, info, run, 0, run.fNumGlyphs);
     }
 
-    currentPoint.fY += maxDescent + maxLeading;
-    currentPoint.fX = point.fX;
-
-    lineIndex++;
+    handler->commitLine();
 }
 
 struct ShapedRunGlyphIterator {
@@ -593,165 +621,190 @@
 
     const SkTArray<ShapedRun>* fRuns;
     int fRunIndex;
-    int fGlyphIndex;
+    size_t fGlyphIndex;
 };
 
-}  // namespace
-
-struct SkShaper::Impl {
-    HBFont fHarfBuzzFont;
-    HBBuffer fBuffer;
-    sk_sp<SkTypeface> fTypeface;
+class ShaperHarfBuzz : public SkShaper {
+public:
+    ShaperHarfBuzz(HBBuffer, ICUBrk line, ICUBrk grapheme);
+protected:
     ICUBrk fLineBreakIterator;
     ICUBrk fGraphemeBreakIterator;
 
-    SkPoint shapeCorrect(RunHandler* handler,
-                         const char* utf8,
-                         size_t utf8Bytes,
-                         SkPoint point,
-                         SkScalar width,
-                         RunIteratorQueue& runSegmenter,
-                         const BiDiRunIterator* bidi,
-                         const LanguageRunIterator* language,
-                         const ScriptRunIterator* script,
-                         const FontRunIterator* font) const;
-
-    SkPoint shapeOk(RunHandler* handler,
-                    const char* utf8,
-                    size_t utf8Bytes,
-                    SkPoint point,
-                    SkScalar width,
-                    RunIteratorQueue& runSegmenter,
-                    const BiDiRunIterator* bidi,
-                    const LanguageRunIterator* language,
-                    const ScriptRunIterator* script,
-                    const FontRunIterator* font) const;
-
-    ShapedRun shape(const char* utf8,
-                    size_t utf8Bytes,
+    ShapedRun shape(const char* utf8, size_t utf8Bytes,
                     const char* utf8Start,
                     const char* utf8End,
-                    const BiDiRunIterator* bidi,
-                    const LanguageRunIterator* language,
-                    const ScriptRunIterator* script,
-                    const FontRunIterator* font) const;
+                    const BiDiRunIterator&,
+                    const LanguageRunIterator&,
+                    const ScriptRunIterator&,
+                    const FontRunIterator&) const;
+private:
+    HBBuffer fBuffer;
+
+    void shape(const char* utf8, size_t utf8Bytes,
+               const SkFont&,
+               bool leftToRight,
+               SkScalar width,
+               RunHandler*) const override;
+
+    void shape(const char* utf8Text, size_t textBytes,
+               FontRunIterator&,
+               BiDiRunIterator&,
+               ScriptRunIterator&,
+               LanguageRunIterator&,
+               SkScalar width,
+               RunHandler*) const override;
+
+    virtual void wrap(char const * const utf8, size_t utf8Bytes,
+                      const BiDiRunIterator&,
+                      const LanguageRunIterator&,
+                      const ScriptRunIterator&,
+                      const FontRunIterator&,
+                      RunIteratorQueue& runSegmenter,
+                      SkScalar width,
+                      RunHandler*) const = 0;
 };
 
-SkShaper::SkShaper(sk_sp<SkTypeface> tf) : fImpl(new Impl) {
-#if defined(SK_USING_THIRD_PARTY_ICU)
+class ShaperDrivenWrapper : public ShaperHarfBuzz {
+public:
+    using ShaperHarfBuzz::ShaperHarfBuzz;
+private:
+    void wrap(char const * const utf8, size_t utf8Bytes,
+              const BiDiRunIterator&,
+              const LanguageRunIterator&,
+              const ScriptRunIterator&,
+              const FontRunIterator&,
+              RunIteratorQueue& runSegmenter,
+              SkScalar width,
+              RunHandler*) const override;
+};
+
+class ShapeThenWrap : public ShaperHarfBuzz {
+public:
+    using ShaperHarfBuzz::ShaperHarfBuzz;
+private:
+    void wrap(char const * const utf8, size_t utf8Bytes,
+              const BiDiRunIterator&,
+              const LanguageRunIterator&,
+              const ScriptRunIterator&,
+              const FontRunIterator&,
+              RunIteratorQueue& runSegmenter,
+              SkScalar width,
+              RunHandler*) const override;
+};
+
+static std::unique_ptr<SkShaper> MakeHarfBuzz(bool correct) {
+    #if defined(SK_USING_THIRD_PARTY_ICU)
     if (!SkLoadICU()) {
-        SkDebugf("SkLoadICU() failed!\n");
-        return;
+        SkDEBUGF("SkLoadICU() failed!\n");
+        return nullptr;
     }
-#endif
-    fImpl->fTypeface = tf ? std::move(tf) : SkTypeface::MakeDefault();
-    fImpl->fHarfBuzzFont = create_hb_font(fImpl->fTypeface.get());
-    if (!fImpl->fHarfBuzzFont) {
-        SkDebugf("create_hb_font failed!\n");
+    #endif
+    HBBuffer buffer(hb_buffer_create());
+    if (!buffer) {
+        SkDEBUGF("Could not create hb_buffer");
+        return nullptr;
     }
-    fImpl->fBuffer.reset(hb_buffer_create());
-    SkASSERT(fImpl->fBuffer);
 
     UErrorCode status = U_ZERO_ERROR;
-    fImpl->fLineBreakIterator.reset(ubrk_open(UBRK_LINE, "th", nullptr, 0, &status));
-    if (U_FAILURE(status)) {
-        SkDebugf("Could not create line break iterator: %s", u_errorName(status));
-        SK_ABORT("");
+    ICUBrk lineBreakIterator(ubrk_open(UBRK_LINE, "th", nullptr, 0, &status));
+    if (!lineBreakIterator || U_FAILURE(status)) {
+        SkDEBUGF("Could not create line break iterator: %s", u_errorName(status));
+        return nullptr;
     }
 
-    fImpl->fGraphemeBreakIterator.reset(ubrk_open(UBRK_CHARACTER, "th", nullptr, 0, &status));
-    if (U_FAILURE(status)) {
-        SkDebugf("Could not create grapheme break iterator: %s", u_errorName(status));
-        SK_ABORT("");
+    ICUBrk graphemeBreakIterator(ubrk_open(UBRK_CHARACTER, "th", nullptr, 0, &status));
+    if (!graphemeBreakIterator || U_FAILURE(status)) {
+        SkDEBUGF("Could not create grapheme break iterator: %s", u_errorName(status));
+        return nullptr;
     }
 
+    if (correct) {
+        return skstd::make_unique<ShaperDrivenWrapper>(
+            std::move(buffer), std::move(lineBreakIterator), std::move(graphemeBreakIterator));
+    } else {
+        return skstd::make_unique<ShapeThenWrap>(
+            std::move(buffer), std::move(lineBreakIterator), std::move(graphemeBreakIterator));
+    }
 }
 
-SkShaper::~SkShaper() {}
+ShaperHarfBuzz::ShaperHarfBuzz(HBBuffer buffer, ICUBrk line, ICUBrk grapheme)
+    : fLineBreakIterator(std::move(line))
+    , fGraphemeBreakIterator(std::move(grapheme))
+    , fBuffer(std::move(buffer))
+{}
 
-bool SkShaper::good() const {
-    return fImpl->fBuffer &&
-           fImpl->fLineBreakIterator &&
-           fImpl->fGraphemeBreakIterator;
-}
-
-SkPoint SkShaper::shape(RunHandler* handler,
-                        const SkFont& srcFont,
-                        const char* utf8,
-                        size_t utf8Bytes,
-                        bool leftToRight,
-                        SkPoint point,
-                        SkScalar width) const
+void ShaperHarfBuzz::shape(const char* utf8, size_t utf8Bytes,
+                           const SkFont& srcFont,
+                           bool leftToRight,
+                           SkScalar width,
+                           RunHandler* handler) const
 {
     SkASSERT(handler);
     sk_sp<SkFontMgr> fontMgr = SkFontMgr::RefDefault();
     UBiDiLevel defaultLevel = leftToRight ? UBIDI_DEFAULT_LTR : UBIDI_DEFAULT_RTL;
 
-    RunIteratorQueue runSegmenter;
-
-    SkTLazy<BiDiRunIterator> maybeBidi(BiDiRunIterator::Make(utf8, utf8Bytes, defaultLevel));
-    BiDiRunIterator* bidi = maybeBidi.getMaybeNull();
+    std::unique_ptr<BiDiRunIterator> bidi(MakeIcuBiDiRunIterator(utf8, utf8Bytes, defaultLevel));
     if (!bidi) {
-        return point;
+        return;
     }
-    runSegmenter.insert(bidi);
 
-    SkTLazy<LanguageRunIterator> maybeLanguage(LanguageRunIterator::Make(utf8, utf8Bytes));
-    LanguageRunIterator* language = maybeLanguage.getMaybeNull();
+    std::unique_ptr<LanguageRunIterator> language(MakeStdLanguageRunIterator(utf8, utf8Bytes));
     if (!language) {
-        return point;
+        return;
     }
-    runSegmenter.insert(language);
 
-    hb_unicode_funcs_t* hbUnicode = hb_buffer_get_unicode_funcs(fImpl->fBuffer.get());
-    SkTLazy<ScriptRunIterator> maybeScript(ScriptRunIterator::Make(utf8, utf8Bytes, hbUnicode));
-    ScriptRunIterator* script = maybeScript.getMaybeNull();
+    std::unique_ptr<ScriptRunIterator> script(MakeHbIcuScriptRunIterator(utf8, utf8Bytes));
     if (!script) {
-        return point;
+        return;
     }
-    runSegmenter.insert(script);
 
-    SkTLazy<FontRunIterator> maybeFont(FontRunIterator::Make(utf8, utf8Bytes,
-                                                             srcFont, std::move(fontMgr)));
-    FontRunIterator* font = maybeFont.getMaybeNull();
+    std::unique_ptr<FontRunIterator> font(MakeFontMgrRunIterator(utf8, utf8Bytes,
+                                                                 srcFont, std::move(fontMgr)));
     if (!font) {
-        return point;
+        return;
     }
-    runSegmenter.insert(font);
 
-    if (true) {
-        return fImpl->shapeCorrect(handler, utf8, utf8Bytes, point, width,
-                                   runSegmenter, bidi, language, script, font);
-    } else {
-        return fImpl->shapeOk(handler, utf8, utf8Bytes, point, width,
-                              runSegmenter, bidi, language, script, font);
-    }
+    this->shape(utf8, utf8Bytes, *font, *bidi, *script, *language, width, handler);
 }
 
-SkPoint SkShaper::Impl::shapeCorrect(RunHandler* handler,
-                                     const char* utf8,
-                                     size_t utf8Bytes,
-                                     SkPoint point,
-                                     SkScalar width,
-                                     RunIteratorQueue& runSegmenter,
-                                     const BiDiRunIterator* bidi,
-                                     const LanguageRunIterator* language,
-                                     const ScriptRunIterator* script,
-                                     const FontRunIterator* font) const
+void ShaperHarfBuzz::shape(const char* utf8, size_t utf8Bytes,
+                           FontRunIterator& font,
+                           BiDiRunIterator& bidi,
+                           ScriptRunIterator& script,
+                           LanguageRunIterator& language,
+                           SkScalar width,
+                           RunHandler* handler) const
+{
+    RunIteratorQueue runSegmenter;
+    runSegmenter.insert(&font);
+    runSegmenter.insert(&bidi);
+    runSegmenter.insert(&script);
+    runSegmenter.insert(&language);
+
+    this->wrap(utf8, utf8Bytes, bidi, language, script, font, runSegmenter, width, handler);
+}
+
+void ShaperDrivenWrapper::wrap(char const * const utf8, size_t utf8Bytes,
+                               const BiDiRunIterator& bidi,
+                               const LanguageRunIterator& language,
+                               const ScriptRunIterator& script,
+                               const FontRunIterator& font,
+                               RunIteratorQueue& runSegmenter,
+                               SkScalar width,
+                               RunHandler* handler) const
 {
     ShapedLine line;
-    size_t lineIndex = 0;
-    SkPoint currentPoint = point;
 
     const char* utf8Start = nullptr;
     const char* utf8End = utf8;
     while (runSegmenter.advanceRuns()) {  // For each item
         utf8Start = utf8End;
-        utf8End = runSegmenter.endOfCurrentRun();
+        utf8End = utf8 + runSegmenter.endOfCurrentRun();
 
-        ShapedRun model(nullptr, nullptr, 0, SkFont(), 0, nullptr);
+        ShapedRun model(RunHandler::Range(), SkFont(), 0, nullptr, 0);
         bool modelNeedsRegenerated = true;
-        int modelOffset = 0;
+        int modelGlyphOffset = 0;
 
         struct TextProps {
             int glyphLen = 0;
@@ -760,7 +813,7 @@
         // map from character position to [safe to break, glyph position, advance]
         std::unique_ptr<TextProps[]> modelText;
         int modelTextOffset = 0;
-        SkVector modelTextAdvanceOffset = {0, 0};
+        SkVector modelAdvanceOffset = {0, 0};
 
         while (utf8Start < utf8End) {  // While there are still code points left in this item
             size_t utf8runLength = utf8End - utf8Start;
@@ -768,15 +821,17 @@
                 model = shape(utf8, utf8Bytes,
                               utf8Start, utf8End,
                               bidi, language, script, font);
-                modelOffset = 0;
+                modelGlyphOffset = 0;
 
                 SkVector advance = {0, 0};
                 modelText.reset(new TextProps[utf8runLength + 1]());
-                for (int i = 0; i < model.fNumGlyphs; ++i) {
-                    SkASSERT(model.fGlyphs[i].fCluster < utf8runLength);
+                size_t modelStartCluster = utf8Start - utf8;
+                for (size_t i = 0; i < model.fNumGlyphs; ++i) {
+                    SkASSERT(modelStartCluster <= model.fGlyphs[i].fCluster);
+                    SkASSERT(                     model.fGlyphs[i].fCluster < (size_t)(utf8End - utf8));
                     if (!model.fGlyphs[i].fUnsafeToBreak) {
-                        modelText[model.fGlyphs[i].fCluster].glyphLen = i;
-                        modelText[model.fGlyphs[i].fCluster].advance = advance;
+                        modelText[model.fGlyphs[i].fCluster - modelStartCluster].glyphLen = i;
+                        modelText[model.fGlyphs[i].fCluster - modelStartCluster].advance = advance;
                     }
                     advance += model.fGlyphs[i].fAdvance;
                 }
@@ -784,7 +839,7 @@
                 modelText[utf8runLength].glyphLen = model.fNumGlyphs;
                 modelText[utf8runLength].advance = model.fAdvance;
                 modelTextOffset = 0;
-                modelTextAdvanceOffset = {0, 0};
+                modelAdvanceOffset = {0, 0};
                 modelNeedsRegenerated = false;
             }
 
@@ -798,17 +853,19 @@
                 std::unique_ptr<UText, SkFunctionWrapper<UText*, UText, utext_close>> autoClose(&utf8UText);
                 if (U_FAILURE(status)) {
                     SkDebugf("Could not create utf8UText: %s", u_errorName(status));
-                    return point;
+                    return;
                 }
                 ubrk_setUText(&breakIterator, &utf8UText, &status);
                 if (U_FAILURE(status)) {
                     SkDebugf("Could not setText on break iterator: %s", u_errorName(status));
-                    return point;
+                    return;
                 }
             }
 
-            ShapedRun best(nullptr, nullptr, 0, SkFont(), 0, nullptr);
-            best.fAdvance = { SK_ScalarNegativeInfinity, SK_ScalarNegativeInfinity };
+            ShapedRun best(RunHandler::Range(), SkFont(), 0, nullptr, 0,
+                           { SK_ScalarNegativeInfinity, SK_ScalarNegativeInfinity });
+            bool bestIsInvalid = true;
+            bool bestUsesModelForGlyphs = false;
             SkScalar widthLeft = width - line.fAdvance.fX;
 
             for (int32_t breakIteratorCurrent = ubrk_next(&breakIterator);
@@ -818,81 +875,75 @@
                 // TODO: if past a safe to break, future safe to break will be at least as long
 
                 // TODO: adjust breakIteratorCurrent by ignorable whitespace
-                ShapedRun candidate = modelText[breakIteratorCurrent + modelTextOffset].glyphLen
-                                    ? ShapedRun(utf8Start, utf8Start + breakIteratorCurrent,
-                                                modelText[breakIteratorCurrent + modelTextOffset].glyphLen - modelOffset,
-                                                *font->currentFont(),
-                                                bidi->currentLevel(),
-                                                std::unique_ptr<ShapedGlyph[]>())
-                                    : shape(utf8, utf8Bytes,
-                                            utf8Start, utf8Start + breakIteratorCurrent,
-                                            bidi, language, script, font);
-                if (!candidate.fUtf8Start) {
-                    //report error
-                    return point;
-                }
-                if (!candidate.fGlyphs) {
-                    candidate.fAdvance = modelText[breakIteratorCurrent + modelTextOffset].advance - modelTextAdvanceOffset;
-                }
+                bool candidateUsesModelForGlyphs = false;
+                ShapedRun candidate = [&](const TextProps& props){
+                    if (props.glyphLen) {
+                        candidateUsesModelForGlyphs = true;
+                        return ShapedRun(RunHandler::Range(utf8Start - utf8, breakIteratorCurrent),
+                                         font.currentFont(), bidi.currentLevel(),
+                                         std::unique_ptr<ShapedGlyph[]>(),
+                                         props.glyphLen - modelGlyphOffset,
+                                         props.advance - modelAdvanceOffset);
+                    } else {
+                        return shape(utf8, utf8Bytes,
+                                     utf8Start, utf8Start + breakIteratorCurrent,
+                                     bidi, language, script, font);
+                    }
+                }(modelText[breakIteratorCurrent + modelTextOffset]);
                 auto score = [widthLeft](const ShapedRun& run) -> SkScalar {
                     if (run.fAdvance.fX < widthLeft) {
-                        if (run.fUtf8Start == nullptr) {
-                            return SK_ScalarNegativeInfinity;
-                        } else {
-                            return run.fUtf8End - run.fUtf8Start;
-                        }
+                        return run.fUtf8Range.size();
                     } else {
                         return widthLeft - run.fAdvance.fX;
                     }
                 };
-                if (score(best) < score(candidate)) {
+                if (bestIsInvalid || score(best) < score(candidate)) {
                     best = std::move(candidate);
+                    bestIsInvalid = false;
+                    bestUsesModelForGlyphs = candidateUsesModelForGlyphs;
                 }
             }
 
             // If nothing fit (best score is negative) and the line is not empty
             if (width < line.fAdvance.fX + best.fAdvance.fX && !line.runs.empty()) {
-                emit(line, handler, point, currentPoint, lineIndex);
+                emit(line, handler);
                 line.runs.reset();
                 line.fAdvance = {0, 0};
             } else {
-                if (!best.fGlyphs) {
+                if (bestUsesModelForGlyphs) {
                     best.fGlyphs.reset(new ShapedGlyph[best.fNumGlyphs]);
-                    memcpy(best.fGlyphs.get(), model.fGlyphs.get() + modelOffset,
+                    memcpy(best.fGlyphs.get(), model.fGlyphs.get() + modelGlyphOffset,
                            best.fNumGlyphs * sizeof(ShapedGlyph));
-                    modelOffset += best.fNumGlyphs;
-                    modelTextOffset += best.fUtf8End - best.fUtf8Start;
-                    modelTextAdvanceOffset += best.fAdvance;
+                    modelGlyphOffset += best.fNumGlyphs;
+                    modelTextOffset += best.fUtf8Range.size();
+                    modelAdvanceOffset += best.fAdvance;
                 } else {
                     modelNeedsRegenerated = true;
                 }
-                utf8Start = best.fUtf8End;
+                utf8Start += best.fUtf8Range.size();
                 line.fAdvance += best.fAdvance;
                 line.runs.emplace_back(std::move(best));
 
                 // If item broken, emit line (prevent remainder from accidentally fitting)
                 if (utf8Start != utf8End) {
-                    emit(line, handler, point, currentPoint, lineIndex);
+                    emit(line, handler);
                     line.runs.reset();
                     line.fAdvance = {0, 0};
                 }
             }
         }
     }
-    emit(line, handler, point, currentPoint, lineIndex);
-    return currentPoint;
+    emit(line, handler);
 }
 
-SkPoint SkShaper::Impl::shapeOk(RunHandler* handler,
-                                const char* utf8,
-                                size_t utf8Bytes,
-                                SkPoint point,
-                                SkScalar width,
-                                RunIteratorQueue& runSegmenter,
-                                const BiDiRunIterator* bidi,
-                                const LanguageRunIterator* language,
-                                const ScriptRunIterator* script,
-                                const FontRunIterator* font) const
+void ShapeThenWrap::wrap(char const * const utf8, size_t utf8Bytes,
+                         const BiDiRunIterator& bidi,
+                         const LanguageRunIterator& language,
+                         const ScriptRunIterator& script,
+                         const FontRunIterator& font,
+                         RunIteratorQueue& runSegmenter,
+                         SkScalar width,
+                         RunHandler* handler) const
 {
     SkTArray<ShapedRun> runs;
 {
@@ -905,18 +956,18 @@
         std::unique_ptr<UText, SkFunctionWrapper<UText*, UText, utext_close>> autoClose(&utf8UText);
         if (U_FAILURE(status)) {
             SkDebugf("Could not create utf8UText: %s", u_errorName(status));
-            return point;
+            return;
         }
 
         ubrk_setUText(&lineBreakIterator, &utf8UText, &status);
         if (U_FAILURE(status)) {
             SkDebugf("Could not setText on line break iterator: %s", u_errorName(status));
-            return point;
+            return;
         }
         ubrk_setUText(&graphemeBreakIterator, &utf8UText, &status);
         if (U_FAILURE(status)) {
             SkDebugf("Could not setText on grapheme break iterator: %s", u_errorName(status));
-            return point;
+            return;
         }
     }
 
@@ -924,18 +975,17 @@
     const char* utf8End = utf8;
     while (runSegmenter.advanceRuns()) {
         utf8Start = utf8End;
-        utf8End = runSegmenter.endOfCurrentRun();
+        utf8End = utf8 + runSegmenter.endOfCurrentRun();
 
         runs.emplace_back(shape(utf8, utf8Bytes,
                                 utf8Start, utf8End,
                                 bidi, language, script, font));
         ShapedRun& run = runs.back();
 
-        int32_t clusterOffset = utf8Start - utf8;
         uint32_t previousCluster = 0xFFFFFFFF;
-        for (int i = 0; i < run.fNumGlyphs; ++i) {
+        for (size_t i = 0; i < run.fNumGlyphs; ++i) {
             ShapedGlyph& glyph = run.fGlyphs[i];
-            int32_t glyphCluster = glyph.fCluster + clusterOffset;
+            int32_t glyphCluster = glyph.fCluster;
 
             int32_t lineBreakIteratorCurrent = ubrk_current(&lineBreakIterator);
             while (lineBreakIteratorCurrent != UBRK_DONE &&
@@ -1032,26 +1082,18 @@
 }
 
 // Reorder the runs and glyphs per line and write them out.
-    SkPoint currentPoint = point;
 {
     ShapedRunGlyphIterator previousBreak(runs);
     ShapedRunGlyphIterator glyphIterator(runs);
-    SkScalar maxAscent = 0;
-    SkScalar maxDescent = 0;
-    SkScalar maxLeading = 0;
     int previousRunIndex = -1;
-    size_t lineIndex = 0;
     while (glyphIterator.current()) {
         int runIndex = glyphIterator.fRunIndex;
-        int glyphIndex = glyphIterator.fGlyphIndex;
+        size_t glyphIndex = glyphIterator.fGlyphIndex;
         ShapedGlyph* nextGlyph = glyphIterator.next();
 
         if (previousRunIndex != runIndex) {
             SkFontMetrics metrics;
             runs[runIndex].fFont.getMetrics(&metrics);
-            maxAscent = SkTMin(maxAscent, metrics.fAscent);
-            maxDescent = SkTMax(maxDescent, metrics.fDescent);
-            maxLeading = SkTMax(maxLeading, metrics.fLeading);
             previousRunIndex = runIndex;
         }
 
@@ -1060,8 +1102,6 @@
             continue;
         }
 
-        currentPoint.fY -= maxAscent;
-
         int numRuns = runIndex - previousBreak.fRunIndex + 1;
         SkAutoSTMalloc<4, UBiDiLevel> runLevels(numRuns);
         for (int i = 0; i < numRuns; ++i) {
@@ -1073,52 +1113,62 @@
         // step through the runs in reverse visual order and the glyphs in reverse logical order
         // until a visible glyph is found and force them to the end of the visual line.
 
+        handler->beginLine();
         for (int i = 0; i < numRuns; ++i) {
             int logicalIndex = previousBreak.fRunIndex + logicalFromVisual[i];
-
-            int startGlyphIndex = (logicalIndex == previousBreak.fRunIndex)
-                                ? previousBreak.fGlyphIndex
-                                : 0;
-            int endGlyphIndex = (logicalIndex == runIndex)
-                              ? glyphIndex + 1
-                              : runs[logicalIndex].fNumGlyphs;
-
             const auto& run = runs[logicalIndex];
             const RunHandler::RunInfo info = {
-                lineIndex,
+                run.fFont,
+                run.fLevel,
                 run.fAdvance,
-                maxAscent,
-                maxDescent,
-                maxLeading,
+                run.fNumGlyphs,
+                run.fUtf8Range
             };
-            append(handler, info, run, startGlyphIndex, endGlyphIndex, &currentPoint);
+            handler->runInfo(info);
+        }
+        handler->commitRunInfo();
+        for (int i = 0; i < numRuns; ++i) {
+            int logicalIndex = previousBreak.fRunIndex + logicalFromVisual[i];
+            const auto& run = runs[logicalIndex];
+            const RunHandler::RunInfo info = {
+                run.fFont,
+                run.fLevel,
+                run.fAdvance,
+                run.fNumGlyphs,
+                run.fUtf8Range
+            };
+
+            size_t startGlyphIndex = (logicalIndex == previousBreak.fRunIndex)
+                                   ? previousBreak.fGlyphIndex
+                                   : 0;
+            size_t endGlyphIndex = (logicalIndex == runIndex)
+                                 ? glyphIndex + 1
+                                 : run.fNumGlyphs;
+
+            append(handler, info, run, startGlyphIndex, endGlyphIndex);
         }
 
-        currentPoint.fY += maxDescent + maxLeading;
-        currentPoint.fX = point.fX;
-        maxAscent = 0;
-        maxDescent = 0;
-        maxLeading = 0;
+        handler->commitLine();
+
         previousRunIndex = -1;
-        ++lineIndex;
         previousBreak = glyphIterator;
     }
 }
-
-    return currentPoint;
 }
 
 
-ShapedRun SkShaper::Impl::shape(const char* utf8,
-                                const size_t utf8Bytes,
-                                const char* utf8Start,
-                                const char* utf8End,
-                                const BiDiRunIterator* bidi,
-                                const LanguageRunIterator* language,
-                                const ScriptRunIterator* script,
-                                const FontRunIterator* font) const
+ShapedRun ShaperHarfBuzz::shape(char const * const utf8,
+                                  size_t const utf8Bytes,
+                                  char const * const utf8Start,
+                                  char const *  const utf8End,
+                                  const BiDiRunIterator& bidi,
+                                  const LanguageRunIterator& language,
+                                  const ScriptRunIterator& script,
+                                  const FontRunIterator& font) const
 {
-    ShapedRun run(nullptr, nullptr, 0, SkFont(), 0, nullptr);
+    size_t utf8runLength = utf8End - utf8Start;
+    ShapedRun run(RunHandler::Range(utf8Start - utf8, utf8runLength),
+                  font.currentFont(), bidi.currentLevel(), nullptr, 0);
 
     hb_buffer_t* buffer = fBuffer.get();
     SkAutoTCallVProc<hb_buffer_t, hb_buffer_clear_contents> autoClearBuffer(buffer);
@@ -1134,7 +1184,7 @@
     // Populate the hb_buffer directly with utf8 cluster indexes.
     const char* utf8Current = utf8Start;
     while (utf8Current < utf8End) {
-        unsigned int cluster = utf8Current - utf8Start;
+        unsigned int cluster = utf8Current - utf8;
         hb_codepoint_t u = utf8_next(&utf8Current, utf8End);
         hb_buffer_add(buffer, u, cluster);
     }
@@ -1142,24 +1192,21 @@
     // Add postcontext.
     hb_buffer_add_utf8(buffer, utf8Current, utf8 + utf8Bytes - utf8Current, 0, 0);
 
-    size_t utf8runLength = utf8End - utf8Start;
-    if (!SkTFitsIn<int>(utf8runLength)) {
-        SkDebugf("Shaping error: utf8 too long");
-        return run;
-    }
-    hb_direction_t direction = is_LTR(bidi->currentLevel()) ? HB_DIRECTION_LTR:HB_DIRECTION_RTL;
+    hb_direction_t direction = is_LTR(bidi.currentLevel()) ? HB_DIRECTION_LTR:HB_DIRECTION_RTL;
     hb_buffer_set_direction(buffer, direction);
-    hb_buffer_set_script(buffer, script->currentScript());
-    hb_buffer_set_language(buffer, language->currentLanguage());
+    hb_buffer_set_script(buffer, hb_script_from_iso15924_tag((hb_tag_t)script.currentScript()));
+    hb_buffer_set_language(buffer, hb_language_from_string(language.currentLanguage(), -1));
     hb_buffer_guess_segment_properties(buffer);
     // TODO: features
-    if (!font->currentHBFont()) {
+
+    // TODO: how to cache hbface (typeface) / hbfont (font)
+    HBFont hbFont(create_hb_font(font.currentFont()));
+    if (!hbFont) {
         return run;
     }
-    hb_shape(font->currentHBFont(), buffer, nullptr, 0);
+    hb_shape(hbFont.get(), buffer, nullptr, 0);
     unsigned len = hb_buffer_get_length(buffer);
     if (len == 0) {
-        // TODO: this isn't an error, make it look different
         return run;
     }
 
@@ -1171,16 +1218,11 @@
     hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer, nullptr);
     hb_glyph_position_t* pos = hb_buffer_get_glyph_positions(buffer, nullptr);
 
-    if (!SkTFitsIn<int>(len)) {
-        SkDebugf("Shaping error: too many glyphs");
-        return run;
-    }
-
-    run = ShapedRun(utf8Start, utf8End, len, *font->currentFont(),
-                    bidi->currentLevel(),
-                    std::unique_ptr<ShapedGlyph[]>(new ShapedGlyph[len]));
+    run = ShapedRun(RunHandler::Range(utf8Start - utf8, utf8runLength),
+                    font.currentFont(), bidi.currentLevel(),
+                    std::unique_ptr<ShapedGlyph[]>(new ShapedGlyph[len]), len);
     int scaleX, scaleY;
-    hb_font_get_scale(font->currentHBFont(), &scaleX, &scaleY);
+    hb_font_get_scale(hbFont.get(), &scaleX, &scaleY);
     double textSizeY = run.fFont.getSize() / scaleY;
     double textSizeX = run.fFont.getSize() / scaleX * run.fFont.getScaleX();
     SkVector runAdvance = { 0, 0 };
@@ -1198,7 +1240,11 @@
         SkPaint p;
         run.fFont.getWidthsBounds(&glyph.fID, 1, &advance, &bounds, &p);
         glyph.fHasVisual = !bounds.isEmpty(); //!font->currentTypeface()->glyphBoundsAreZero(glyph.fID);
+#if SK_HB_VERSION_CHECK(1, 5, 0)
         glyph.fUnsafeToBreak = info[i].mask & HB_GLYPH_FLAG_UNSAFE_TO_BREAK;
+#else
+        glyph.fUnsafeToBreak = false;
+#endif
         glyph.fMustLineBreakBefore = false;
 
         runAdvance += glyph.fAdvance;
@@ -1207,3 +1253,57 @@
 
     return run;
 }
+
+}  // namespace
+
+std::unique_ptr<SkShaper::BiDiRunIterator>
+SkShaper::MakeIcuBiDiRunIterator(const char* utf8, size_t utf8Bytes, uint8_t bidiLevel) {
+    // ubidi only accepts utf16 (though internally it basically works on utf32 chars).
+    // We want an ubidi_setPara(UBiDi*, UText*, UBiDiLevel, UBiDiLevel*, UErrorCode*);
+    if (!SkTFitsIn<int32_t>(utf8Bytes)) {
+        SkDEBUGF("Bidi error: text too long");
+        return nullptr;
+    }
+
+    UErrorCode status = U_ZERO_ERROR;
+
+    // Getting the length like this seems to always set U_BUFFER_OVERFLOW_ERROR
+    int32_t utf16Units;
+    u_strFromUTF8(nullptr, 0, &utf16Units, utf8, utf8Bytes, &status);
+    status = U_ZERO_ERROR;
+    std::unique_ptr<UChar[]> utf16(new UChar[utf16Units]);
+    u_strFromUTF8(utf16.get(), utf16Units, nullptr, utf8, utf8Bytes, &status);
+    if (U_FAILURE(status)) {
+        SkDEBUGF("Invalid utf8 input: %s", u_errorName(status));
+        return nullptr;
+    }
+
+    ICUBiDi bidi(ubidi_openSized(utf16Units, 0, &status));
+    if (U_FAILURE(status)) {
+        SkDEBUGF("Bidi error: %s", u_errorName(status));
+        return nullptr;
+    }
+    SkASSERT(bidi);
+
+    // The required lifetime of utf16 isn't well documented.
+    // It appears it isn't used after ubidi_setPara except through ubidi_getText.
+    ubidi_setPara(bidi.get(), utf16.get(), utf16Units, bidiLevel, nullptr, &status);
+    if (U_FAILURE(status)) {
+        SkDEBUGF("Bidi error: %s", u_errorName(status));
+        return nullptr;
+    }
+
+    return skstd::make_unique<IcuBiDiRunIterator>(utf8, utf8 + utf8Bytes, std::move(bidi));
+}
+
+std::unique_ptr<SkShaper::ScriptRunIterator>
+SkShaper::MakeHbIcuScriptRunIterator(const char* utf8, size_t utf8Bytes) {
+    return skstd::make_unique<HbIcuScriptRunIterator>(utf8, utf8Bytes);
+}
+
+std::unique_ptr<SkShaper> SkShaper::MakeShaperDrivenWrapper() {
+    return MakeHarfBuzz(true);
+}
+std::unique_ptr<SkShaper> SkShaper::MakeShapeThenWrap() {
+    return MakeHarfBuzz(false);
+}
diff --git a/modules/skshaper/src/SkShaper_primitive.cpp b/modules/skshaper/src/SkShaper_primitive.cpp
index c63c876..30b7c7a 100644
--- a/modules/skshaper/src/SkShaper_primitive.cpp
+++ b/modules/skshaper/src/SkShaper_primitive.cpp
@@ -5,78 +5,190 @@
  * found in the LICENSE file.
  */
 
-#include "SkShaper.h"
 #include "SkFontMetrics.h"
+#include "SkMakeUnique.h"
+#include "SkShaper.h"
 #include "SkStream.h"
 #include "SkTo.h"
 #include "SkTypeface.h"
+#include "SkUTF.h"
 
-struct SkShaper::Impl {
-    sk_sp<SkTypeface> fTypeface;
+class SkShaperPrimitive : public SkShaper {
+public:
+    SkShaperPrimitive() {}
+private:
+    void shape(const char* utf8, size_t utf8Bytes,
+               const SkFont& srcFont,
+               bool leftToRight,
+               SkScalar width,
+               RunHandler*) const override;
+
+    void shape(const char* utf8, size_t utf8Bytes,
+               FontRunIterator&,
+               BiDiRunIterator&,
+               ScriptRunIterator&,
+               LanguageRunIterator&,
+               SkScalar width,
+               RunHandler*) const override;
 };
 
-SkShaper::SkShaper(sk_sp<SkTypeface> tf) : fImpl(new Impl) {
-    fImpl->fTypeface = tf ? std::move(tf) : SkTypeface::MakeDefault();
+std::unique_ptr<SkShaper> SkShaper::MakePrimitive() {
+    return skstd::make_unique<SkShaperPrimitive>();
 }
 
-SkShaper::~SkShaper() {}
-
-bool SkShaper::good() const { return true; }
-
-// This example only uses public API, so we don't use SkUTF8_NextUnichar.
-unsigned utf8_lead_byte_to_count(const char* ptr) {
-    uint8_t c = *(const uint8_t*)ptr;
-    SkASSERT(c <= 0xF7);
-    SkASSERT((c & 0xC0) != 0x80);
-    return (((0xE5 << 24) >> ((unsigned)c >> 4 << 1)) & 3) + 1;
+static inline bool is_breaking_whitespace(SkUnichar c) {
+    switch (c) {
+        case 0x0020: // SPACE
+        //case 0x00A0: // NO-BREAK SPACE
+        case 0x1680: // OGHAM SPACE MARK
+        case 0x180E: // MONGOLIAN VOWEL SEPARATOR
+        case 0x2000: // EN QUAD
+        case 0x2001: // EM QUAD
+        case 0x2002: // EN SPACE (nut)
+        case 0x2003: // EM SPACE (mutton)
+        case 0x2004: // THREE-PER-EM SPACE (thick space)
+        case 0x2005: // FOUR-PER-EM SPACE (mid space)
+        case 0x2006: // SIX-PER-EM SPACE
+        case 0x2007: // FIGURE SPACE
+        case 0x2008: // PUNCTUATION SPACE
+        case 0x2009: // THIN SPACE
+        case 0x200A: // HAIR SPACE
+        case 0x200B: // ZERO WIDTH SPACE
+        case 0x202F: // NARROW NO-BREAK SPACE
+        case 0x205F: // MEDIUM MATHEMATICAL SPACE
+        case 0x3000: // IDEOGRAPHIC SPACE
+        //case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE
+            return true;
+        default:
+            return false;
+    }
 }
 
-SkPoint SkShaper::shape(RunHandler* handler,
-                        const SkFont& srcFont,
-                        const char* utf8text,
-                        size_t textBytes,
-                        bool leftToRight,
-                        SkPoint point,
-                        SkScalar width) const {
-    sk_ignore_unused_variable(leftToRight);
-    sk_ignore_unused_variable(width);
+static size_t linebreak(const char text[], const char stop[],
+                        const SkFont& font, SkScalar width,
+                        SkScalar* advance,
+                        size_t* trailing)
+{
+    SkScalar accumulatedWidth = 0;
+    int glyphIndex = 0;
+    const char* start = text;
+    const char* word_start = text;
+    bool prevWS = true;
+    *trailing = 0;
 
-    SkFont font(srcFont);
-    font.setTypeface(fImpl->fTypeface);
-    int glyphCount = font.countText(utf8text, textBytes, SkTextEncoding::kUTF8);
-    if (glyphCount <= 0) {
-        return point;
-    }
+    while (text < stop) {
+        const char* prevText = text;
+        SkUnichar uni = SkUTF::NextUTF8(&text, stop);
+        accumulatedWidth += advance[glyphIndex++];
+        bool currWS = is_breaking_whitespace(uni);
 
-    SkFontMetrics metrics;
-    font.getMetrics(&metrics);
-    point.fY -= metrics.fAscent;
+        if (!currWS && prevWS) {
+            word_start = prevText;
+        }
+        prevWS = currWS;
 
-    const RunHandler::RunInfo info = {
-        0,
-        { font.measureText(utf8text, textBytes, SkTextEncoding::kUTF8), 0 },
-        metrics.fAscent,
-        metrics.fDescent,
-        metrics.fLeading,
-    };
-    const auto buffer = handler->newRunBuffer(info, font, glyphCount, textBytes);
-    SkAssertResult(font.textToGlyphs(utf8text, textBytes, SkTextEncoding::kUTF8, buffer.glyphs,
-                                     glyphCount) == glyphCount);
-    font.getPos(buffer.glyphs, glyphCount, buffer.positions, point);
-
-    if (buffer.utf8text) {
-        memcpy(buffer.utf8text, utf8text, textBytes);
-    }
-
-    if (buffer.clusters) {
-        const char* txtPtr = utf8text;
-        for (int i = 0; i < glyphCount; ++i) {
-            // Each charater maps to exactly one glyph via SkGlyphCache::unicharToGlyph().
-            buffer.clusters[i] = SkToU32(txtPtr - utf8text);
-            txtPtr += utf8_lead_byte_to_count(txtPtr);
-            SkASSERT(txtPtr <= utf8text + textBytes);
+        if (width < accumulatedWidth) {
+            if (currWS) {
+                // eat the rest of the whitespace
+                const char* next = text;
+                while (next < stop && is_breaking_whitespace(SkUTF::NextUTF8(&next, stop))) {
+                    text = next;
+                }
+                if (trailing) {
+                    *trailing = text - prevText;
+                }
+            } else {
+                // backup until a whitespace (or 1 char)
+                if (word_start == start) {
+                    if (prevText > start) {
+                        text = prevText;
+                    }
+                } else {
+                    text = word_start;
+                }
+            }
+            break;
         }
     }
 
-    return point + SkVector::Make(0, metrics.fDescent + metrics.fLeading);
+    return text - start;
+}
+
+void SkShaperPrimitive::shape(const char* utf8, size_t utf8Bytes,
+                              FontRunIterator& font,
+                              BiDiRunIterator& bidi,
+                              ScriptRunIterator&,
+                              LanguageRunIterator& ,
+                              SkScalar width,
+                              RunHandler* handler) const
+{
+    font.consume();
+    bidi.consume();
+    return this->shape(utf8, utf8Bytes, font.currentFont(), (bidi.currentLevel() % 2) == 0,
+                       width, handler);
+}
+
+void SkShaperPrimitive::shape(const char* utf8, size_t utf8Bytes,
+                              const SkFont& font,
+                              bool leftToRight,
+                              SkScalar width,
+                              RunHandler* handler) const {
+    sk_ignore_unused_variable(leftToRight);
+
+    int glyphCount = font.countText(utf8, utf8Bytes, SkTextEncoding::kUTF8);
+    if (glyphCount <= 0) {
+        return;
+    }
+
+    std::unique_ptr<SkGlyphID[]> glyphs(new SkGlyphID[glyphCount]);
+    font.textToGlyphs(utf8, utf8Bytes, SkTextEncoding::kUTF8, glyphs.get(), glyphCount);
+
+    std::unique_ptr<SkScalar[]> advances(new SkScalar[glyphCount]);
+    font.getWidthsBounds(glyphs.get(), glyphCount, advances.get(), nullptr, nullptr);
+
+    size_t glyphOffset = 0;
+    size_t utf8Offset = 0;
+    while (0 < utf8Bytes) {
+        size_t bytesCollapsed;
+        size_t bytesConsumed = linebreak(utf8, utf8 + utf8Bytes, font, width,
+                                         advances.get() + glyphOffset, &bytesCollapsed);
+        size_t bytesVisible = bytesConsumed - bytesCollapsed;
+
+        size_t numGlyphs = SkUTF::CountUTF8(utf8, bytesVisible);
+        const RunHandler::RunInfo info = {
+            font,
+            0,
+            { font.measureText(utf8, bytesVisible, SkTextEncoding::kUTF8), 0 },
+            numGlyphs,
+            RunHandler::Range(utf8Offset, bytesVisible)
+        };
+        handler->beginLine();
+        handler->runInfo(info);
+        handler->commitRunInfo();
+        const auto buffer = handler->runBuffer(info);
+
+        memcpy(buffer.glyphs, glyphs.get() + glyphOffset, numGlyphs * sizeof(SkGlyphID));
+        SkPoint position = buffer.point;
+        for (size_t i = 0; i < numGlyphs; ++i) {
+            buffer.positions[i] = position;
+            position.fX += advances[i + glyphOffset];
+        }
+        if (buffer.clusters) {
+            const char* txtPtr = utf8;
+            for (size_t i = 0; i < numGlyphs; ++i) {
+                // Each character maps to exactly one glyph.
+                buffer.clusters[i] = SkToU32(txtPtr - utf8 + utf8Offset);
+                SkUTF::NextUTF8(&txtPtr, utf8 + utf8Bytes);
+            }
+        }
+        handler->commitRunBuffer(info);
+        handler->commitLine();
+
+        glyphOffset += SkUTF::CountUTF8(utf8, bytesConsumed);
+        utf8Offset += bytesConsumed;
+        utf8 += bytesConsumed;
+        utf8Bytes -= bytesConsumed;
+    }
+
+    return;
 }
diff --git a/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.cc b/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.cc
index aafc0d0..34fb87a 100644
--- a/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.cc
+++ b/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.cc
@@ -17,29 +17,29 @@
 #include "hello_ar_application.h"
 #include <gtx/string_cast.hpp>
 
-#include "anchor_wrapper.h"
-#include "plane_renderer.h"
-#include "pending_anchor.h"
-#include "util.h"
-#include "SkCanvas.h"
-#include "GrContext.h"
-#include "gl/GrGLTypes.h"
-#include "SkSurface.h"
-#include "SkTypeface.h"
-#include "SkFontStyle.h"
+#include <math.h> /* acos */
+#include "AnimTimer.h"
 #include "GrBackendSurface.h"
-#include "SkMatrix44.h"
-#include "SkMatrix.h"
-#include "SkTextBlob.h"
-#include "glm.h"
-#include "SkPoint3.h"
-#include "Sk3D.h"
-#include <math.h>       /* acos */
-#include "SkShaper.h"
-#include "Skottie.h"
-#include "SkAnimTimer.h"
+#include "GrContext.h"
 #include "Resources.h"
+#include "Sk3D.h"
+#include "SkCanvas.h"
+#include "SkFontStyle.h"
+#include "SkMatrix.h"
+#include "SkMatrix44.h"
+#include "SkPoint3.h"
+#include "SkShaper.h"
 #include "SkStream.h"
+#include "SkSurface.h"
+#include "SkTextBlob.h"
+#include "SkTypeface.h"
+#include "Skottie.h"
+#include "anchor_wrapper.h"
+#include "gl/GrGLTypes.h"
+#include "glm.h"
+#include "pending_anchor.h"
+#include "plane_renderer.h"
+#include "util.h"
 
 namespace hello_ar {
     namespace {
diff --git a/platform_tools/android/apps/skottie/src/main/cpp/native-lib.cpp b/platform_tools/android/apps/skottie/src/main/cpp/native-lib.cpp
index e39f733..a04b1bb 100644
--- a/platform_tools/android/apps/skottie/src/main/cpp/native-lib.cpp
+++ b/platform_tools/android/apps/skottie/src/main/cpp/native-lib.cpp
@@ -69,7 +69,6 @@
 
     GrContextOptions options;
     options.fDisableDistanceFieldPaths = true;
-    options.fDisableCoverageCountingPaths = true;
     sk_sp<GrContext> grContext = GrContext::MakeGL(std::move(glInterface), options);
     if (!grContext.get()) {
         return 0;
diff --git a/platform_tools/android/apps/skqp/build.gradle b/platform_tools/android/apps/skqp/build.gradle
index 8d0d956..991e671 100644
--- a/platform_tools/android/apps/skqp/build.gradle
+++ b/platform_tools/android/apps/skqp/build.gradle
@@ -8,7 +8,7 @@
 
 dependencies {
     implementation 'com.android.support:design:26.+'
-    implementation 'com.android.support.test:runner:0.5'
+    implementation 'androidx.test:runner:1.1.0'
 }
 
 android {
diff --git a/platform_tools/android/apps/skqp/src/main/AndroidManifest.xml b/platform_tools/android/apps/skqp/src/main/AndroidManifest.xml
index 0459522..f978635 100644
--- a/platform_tools/android/apps/skqp/src/main/AndroidManifest.xml
+++ b/platform_tools/android/apps/skqp/src/main/AndroidManifest.xml
@@ -29,6 +29,6 @@
       </activity>
       <uses-library android:name="android.test.runner" />
   </application>
-  <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+  <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                    android:targetPackage="org.skia.skqp"></instrumentation>
 </manifest>
diff --git a/platform_tools/android/apps/skqp/src/main/assets/files.checksum b/platform_tools/android/apps/skqp/src/main/assets/files.checksum
index c2fdab7..32daa50 100644
--- a/platform_tools/android/apps/skqp/src/main/assets/files.checksum
+++ b/platform_tools/android/apps/skqp/src/main/assets/files.checksum
@@ -1 +1 @@
-66d0a6fbfe69cb7cdf18f3f536e89fb6
+f220187a1dfcfa03856e10eaf196bfea
diff --git a/platform_tools/android/apps/skqp/src/main/assets/skqp/rendertests.txt b/platform_tools/android/apps/skqp/src/main/assets/skqp/rendertests.txt
index 67d7fb5..e45b5a2 100644
--- a/platform_tools/android/apps/skqp/src/main/assets/skqp/rendertests.txt
+++ b/platform_tools/android/apps/skqp/src/main/assets/skqp/rendertests.txt
@@ -25,11 +25,11 @@
 annotated_text,0
 arccirclegap,-1
 arcofzorro,-1
-arcs_as_paths,0
 arcto,-1
 arithmode,0
-atlastext,0
+atlastext,-1
 b_119394958,-1
+backdrop_hintrect_clipping,-1
 badpaint,-1
 bezier_conic_effects,-1
 bezier_quad_effects,-1
@@ -169,8 +169,13 @@
 composeshader,0
 composeshader_alpha,0
 composeshader_bitmap,0
-composeshader_bitmap2,-1
+composeshader_bitmap2,0
 composeshader_grid,0
+compositor_quads_color,-1
+compositor_quads_debug,-1
+compositor_quads_filter,-1
+compositor_quads_image,-1
+compositor_quads_shader,-1
 concavepaths,-1
 conicpaths,-1
 const_color_processor,-1
@@ -195,11 +200,13 @@
 crbug_899512,0
 crbug_905548,0
 crbug_918512,0
+crbug_938592,0
+crbug_947055,-1
 croppedrects,0
 cross_context_image,-1
 cubicclosepath,-1
 cubicpath,-1
-daa,0
+daa,-1
 dash_line_zero_off_interval,-1
 dashcircle,-1
 dashcircle2,-1
@@ -259,9 +266,8 @@
 encode-srgb-jpg,0
 encode-srgb-png,0
 encode-srgb-webp,0
-etc1,0
 extractalpha,0
-extractbitmap,-1
+extractbitmap,0
 fadefilter,0
 fancy_gradients,-1
 fancyblobunderline,-1
@@ -375,7 +381,7 @@
 image_from_yuv_textures,-1
 image_scale_aligned,-1
 image_subset,0
-imagealphathreshold,0
+imagealphathreshold,-1
 imagealphathreshold_crop,0
 imagealphathreshold_image,-1
 imagealphathreshold_surface,0
@@ -411,6 +417,7 @@
 jpg-color-cube,0
 largecircle,-1
 largeglyphblur,0
+largeovals,-1
 lattice,-1
 lattice2,0
 lattice_alpha,0
@@ -435,6 +442,7 @@
 longpathdash,-1
 longwavyline,-1
 lumafilter,-1
+macaa_colors,-1
 maddash,-1
 makeRasterImage,0
 makecolorspace,0
@@ -453,7 +461,10 @@
 mipmap_gray8_srgb,0
 mipmap_srgb,0
 mixedtextblobs,-1
+mixerCF,0
 mixershader,0
+mixershader2,0
+mixershader_shadermixer,-1
 modecolorfilters,0
 morphology,-1
 multipicturedraw_biglayer_simple,0
@@ -482,7 +493,7 @@
 onebadarc,0
 orientation,0
 ovals,-1
-ovals_as_paths,0
+overdraw_canvas,0
 overdrawcolorfilter,0
 p3_ovals,-1
 parsedpaths,-1
@@ -502,7 +513,6 @@
 pathopsskpclip,-1
 pdf_crbug_772685,0
 pdf_never_embed,-1
-perlinnoise,0
 perlinnoise_localmatrix,0
 persp_images,-1
 persp_shaders_aa,-1
@@ -532,14 +542,12 @@
 radial_gradient3_nodither,0
 radial_gradient4,-1
 radial_gradient4_nodither,-1
-radial_gradient_precision,0
 readpixels,0
-readpixelscodec,0
-readpixelspicture,0
+readpixelscodec,-1
+readpixelspicture,-1
 recordopts,0
 rectangle_texture,-1
 rects,-1
-rects_as_paths,-1
 repeated_bitmap,0
 repeated_bitmap_jpg,-1
 resizeimagefilter,-1
@@ -553,8 +561,15 @@
 rrect_draw_aa,-1
 rrect_draw_bw,0
 rrect_effect,-1
+runtimecolorfilter,-1
+runtimecolorfilter_interpreted,-1
+samplelocations_hwgrad_botleft,-1
+samplelocations_hwgrad_topleft,-1
+samplelocations_swgrad_botleft,-1
+samplelocations_swgrad_topleft,-1
 save_behind,-1
 savelayer_clipmask,-1
+savelayer_clipmask_maskfilter,0
 savelayer_clipped,0
 savelayer_coverage,-1
 savelayer_initfromprev,-1
@@ -567,7 +582,7 @@
 scaled_tilemodes,0
 scaled_tilemodes_npot,-1
 scaledemoji,0
-scaledemoji_rendering,-1
+scaledemoji_rendering,0
 scaledemojipos,0
 scaledstrokes,-1
 scalepixels_unpremul,0
@@ -608,6 +623,7 @@
 skbug_4868,-1
 skbug_5321,0
 skbug_8664,-1
+skbug_8955,0
 skinning,-1
 skinning_cached,-1
 skinning_cpu,-1
@@ -615,7 +631,7 @@
 skottie_colorize,-1
 skottie_multiframe,-1
 skottie_webfont,-1
-small_color_stop,0
+small_color_stop,-1
 smallarc,-1
 smallpaths,-1
 spritebitmap,0
@@ -675,7 +691,6 @@
 tilemodes_npot,-1
 tinyanglearcs,-1
 tinybitmap,0
-tosrgb_colorfilter,0
 transparency_check,0
 trickycubicstrokes,-1
 trimpatheffect,-1
@@ -697,7 +712,7 @@
 wacky_yuv_formats,0
 wacky_yuv_formats_cs,0
 windowrectangles,-1
-windowrectangles_mask,0
+windowrectangles_mask,-1
 xfermodeimagefilter,-1
 xfermodes,-1
 xfermodes2,0
@@ -707,7 +722,6 @@
 yuv_to_rgb_effect,-1
 zeroPath,-1
 zero_control_stroke,-1
-zero_length_paths_aa,0
 zero_length_paths_bw,-1
 zero_length_paths_dbl_aa,-1
 zero_length_paths_dbl_bw,-1
diff --git a/platform_tools/android/apps/skqp/src/main/assets/skqp/unittests.txt b/platform_tools/android/apps/skqp/src/main/assets/skqp/unittests.txt
index 039d8b1..248ae5a 100644
--- a/platform_tools/android/apps/skqp/src/main/assets/skqp/unittests.txt
+++ b/platform_tools/android/apps/skqp/src/main/assets/skqp/unittests.txt
@@ -103,6 +103,7 @@
 LazyProxyTest
 OnFlushCallbackTest
 OpChainTest
+OverbudgetFlush
 OverdrawSurface_Gpu
 PinnedImageTest
 PorterDuffNoDualSourceBlending
@@ -111,10 +112,9 @@
 ProcessorCloneTest
 ProcessorOptimizationValidationTest
 ProcessorRefTest
-PromiseImageTestDelayedRelease
-PromiseImageTestNoDelayedRelease
+PromiseImageNullFulfill
+PromiseImageTest
 PromiseImageTextureFullCache
-PromiseImageTextureReuse
 PromiseImageTextureReuseDifferentConfig
 PromiseImageTextureShutdown
 ProxyRefTest
@@ -126,6 +126,7 @@
 ReadWriteAlpha
 RectangleTexture
 ReimportImageTextureWithMipLevels
+ResourceAllocatorOverBudgetTest
 ResourceAllocatorStressTest
 ResourceAllocatorTest
 ResourceCacheCache
@@ -179,17 +180,23 @@
 TessellatingPathRendererTests
 TestGpuAllContexts
 TestGpuFactory
-TestGpuNullContext
 TestGpuRenderingContexts
+TestMockContext
 TextBlobAbnormal
 TextBlobCache
 TextBlobStressAbnormal
 TextBlobStressCache
+TextureBindingsResetTest
+TextureIdleProcCacheManipulationTest
+TextureIdleProcFlushTest
+TextureIdleProcRerefTest
 TextureIdleProcTest
+TextureIdleStateTest
 TextureProxyTest
 TextureStripAtlasManagerColorFilterTest
 TextureStripAtlasManagerGradientTest
-TransferPixelsTest
+TransferPixelsFromTest
+TransferPixelsToTest
 UnpremulTextureImage
 VertexAttributeCount
 WrappedProxyTest
@@ -204,4 +211,3 @@
 makeBackendTexture
 skbug5221_GPU
 skbug6653
-skbug6653_noExplicitResourceAllocation
diff --git a/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPRunner.java b/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPRunner.java
index 7827f3e..8e360c8 100644
--- a/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPRunner.java
+++ b/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPRunner.java
@@ -10,8 +10,8 @@
 import android.content.Context;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
-import android.support.test.InstrumentationRegistry;
 import android.util.Log;
+import androidx.test.InstrumentationRegistry;
 import java.io.File;
 import java.io.IOException;
 import java.text.SimpleDateFormat;
diff --git a/public.bzl b/public.bzl
index 76ea8bd..d34c81df 100644
--- a/public.bzl
+++ b/public.bzl
@@ -278,10 +278,14 @@
         ]
     return native.glob(["src/codec/*.cpp", "third_party/etc1/*.cpp", "third_party/gif/*.cpp"], exclude = exclude)
 
-# Platform-dependent SRCS for google3-default platform.
-BASE_SRCS_UNIX = struct(
+GL_SRCS_UNIX = struct(
     include = [
         "src/gpu/gl/GrGLMakeNativeInterface_none.cpp",
+    ],
+    exclude = [],
+)
+PORTS_SRCS_UNIX = struct(
+    include = [
         "src/ports/**/*.cpp",
         "src/ports/**/*.h",
     ],
@@ -298,7 +302,6 @@
         "src/ports/SkFontMgr_custom_embedded_factory.cpp",
         "src/ports/SkFontMgr_custom_empty_factory.cpp",
         "src/ports/SkFontMgr_empty_factory.cpp",
-        "src/ports/SkFontMgr_fontconfig.cpp",
         "src/ports/SkFontMgr_fontconfig_factory.cpp",
         "src/ports/SkFontMgr_fuchsia.cpp",
         "src/ports/SkImageGenerator_none.cpp",
@@ -306,10 +309,14 @@
     ],
 )
 
-# Platform-dependent SRCS for google3-default Android.
-BASE_SRCS_ANDROID = struct(
+GL_SRCS_ANDROID = struct(
     include = [
         "src/gpu/gl/android/*.cpp",
+    ],
+    exclude = [],
+)
+PORTS_SRCS_ANDROID = struct(
+    include = [
         "src/ports/**/*.cpp",
         "src/ports/**/*.h",
     ],
@@ -334,10 +341,14 @@
     ],
 )
 
-# Platform-dependent SRCS for google3-default iOS.
-BASE_SRCS_IOS = struct(
+GL_SRCS_IOS = struct(
     include = [
         "src/gpu/gl/iOS/GrGLMakeNativeInterface_iOS.cpp",
+    ],
+    exclude = [],
+)
+PORTS_SRCS_IOS = struct(
+    include = [
         "src/ports/**/*.cpp",
         "src/ports/**/*.h",
         "src/utils/mac/*.cpp",
@@ -366,20 +377,32 @@
     ],
 )
 
-################################################################################
-## skia_srcs()
-################################################################################
-def skia_srcs(os_conditions):
-    """Sources to be compiled into the skia library."""
-    return skia_glob(BASE_SRCS_ALL) + skia_select(
+def base_srcs():
+    return skia_glob(BASE_SRCS_ALL)
+
+def ports_srcs(os_conditions):
+    return skia_select(
         os_conditions,
         [
-            skia_glob(BASE_SRCS_UNIX),
-            skia_glob(BASE_SRCS_ANDROID),
-            skia_glob(BASE_SRCS_IOS),
+            skia_glob(PORTS_SRCS_UNIX),
+            skia_glob(PORTS_SRCS_ANDROID),
+            skia_glob(PORTS_SRCS_IOS),
         ],
     )
 
+def gl_srcs(os_conditions):
+    return skia_select(
+        os_conditions,
+        [
+            skia_glob(GL_SRCS_UNIX),
+            skia_glob(GL_SRCS_ANDROID),
+            skia_glob(GL_SRCS_IOS),
+        ],
+    )
+
+def skia_srcs(os_conditions):
+    return base_srcs() + ports_srcs(os_conditions) + gl_srcs(os_conditions)
+
 ################################################################################
 ## INCLUDES
 ################################################################################
@@ -444,6 +467,8 @@
         "tools/DDLPromiseImageHelper.h",
         "tools/DDLTileHelper.cpp",
         "tools/DDLTileHelper.h",
+        "tools/HashAndEncode.cpp",
+        "tools/HashAndEncode.h",
         "tools/ProcStats.cpp",
         "tools/ProcStats.h",
         "tools/Registry.h",
@@ -456,15 +481,15 @@
         "tools/debugger/*.h",
         "tools/flags/*.cpp",
         "tools/flags/*.h",
-        "tools/fonts/SkRandomScalerContext.cpp",
-        "tools/fonts/SkRandomScalerContext.h",
-        "tools/fonts/SkTestFontMgr.cpp",
-        "tools/fonts/SkTestFontMgr.h",
-        "tools/fonts/SkTestSVGTypeface.cpp",
-        "tools/fonts/SkTestSVGTypeface.h",
-        "tools/fonts/SkTestTypeface.cpp",
-        "tools/fonts/SkTestTypeface.h",
-        "tools/fonts/sk_tool_utils_font.cpp",
+        "tools/fonts/RandomScalerContext.cpp",
+        "tools/fonts/RandomScalerContext.h",
+        "tools/fonts/TestFontMgr.cpp",
+        "tools/fonts/TestFontMgr.h",
+        "tools/fonts/TestSVGTypeface.cpp",
+        "tools/fonts/TestSVGTypeface.h",
+        "tools/fonts/TestTypeface.cpp",
+        "tools/fonts/TestTypeface.h",
+        "tools/fonts/ToolUtilsFont.cpp",
         "tools/fonts/test_font_monospace.inc",
         "tools/fonts/test_font_sans_serif.inc",
         "tools/fonts/test_font_serif.inc",
@@ -473,9 +498,8 @@
         "tools/gpu/**/*.h",
         "tools/random_parse_path.cpp",
         "tools/random_parse_path.h",
-        "tools/sk_pixel_iter.h",
-        "tools/sk_tool_utils.cpp",
-        "tools/sk_tool_utils.h",
+        "tools/ToolUtils.cpp",
+        "tools/ToolUtils.h",
         "tools/timer/*.cpp",
         "tools/timer/*.h",
         "tools/trace/*.cpp",
@@ -507,7 +531,7 @@
     return skia_glob(DM_SRCS_ALL) + skia_select(
         os_conditions,
         [
-            [],
+            ["tests/FontMgrFontConfigTest.cpp"],
             ["tests/FontMgrAndroidParserTest.cpp"],
             [],
         ],
@@ -674,8 +698,8 @@
     "modules/skottie/utils/SkottieUtils.cpp",
     "modules/skottie/utils/SkottieUtils.h",
     # TODO(benjaminwagner): Add "flags" target.
-    "tools/flags/SkCommandLineFlags.cpp",
-    "tools/flags/SkCommandLineFlags.h",
+    "tools/flags/CommandLineFlags.cpp",
+    "tools/flags/CommandLineFlags.h",
 ]
 
 ################################################################################
@@ -690,6 +714,7 @@
     "modules/skshaper/include/SkShaper.h",
     "modules/skshaper/src/SkShaper.cpp",
     "modules/skshaper/src/SkShaper_harfbuzz.cpp",
+    "modules/skshaper/src/SkShaper_primitive.cpp",
 ]
 
 SKSHAPER_PRIMITIVE_SRCS = [
diff --git a/resources/fonts/planetcbdt.ttf b/resources/fonts/planetcbdt.ttf
new file mode 100644
index 0000000..3ec9704
--- /dev/null
+++ b/resources/fonts/planetcbdt.ttf
Binary files differ
diff --git a/resources/fonts/planetcolr.ttf b/resources/fonts/planetcolr.ttf
new file mode 100644
index 0000000..f075e92
--- /dev/null
+++ b/resources/fonts/planetcolr.ttf
Binary files differ
diff --git a/resources/fonts/planetsbix.ttf b/resources/fonts/planetsbix.ttf
new file mode 100644
index 0000000..9d011ae
--- /dev/null
+++ b/resources/fonts/planetsbix.ttf
Binary files differ
diff --git a/resources/fonts/svg/planets/earth.svg b/resources/fonts/svg/planets/earth.svg
new file mode 100644
index 0000000..e358542
--- /dev/null
+++ b/resources/fonts/svg/planets/earth.svg
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="202"
+   height="206"
+   viewBox="0 0 53.445832 54.504168"
+   version="1.1"
+   id="svg6113"
+   sodipodi:docname="earth.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <defs
+     id="defs6107" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="2.8284271"
+     inkscape:cx="32.654706"
+     inkscape:cy="63.021213"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:window-width="2560"
+     inkscape:window-height="1531"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata6110">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-242.49582)">
+    <g
+       id="g7528"
+       transform="translate(-62.308084,-1.0840405)">
+      <path
+         style="fill:#cdcdd1;stroke-width:1.26999998"
+         d="m 79.681789,296.42155 c -6.05393,-2.15911 -9.160485,-4.882 -13.760607,-12.06115 -3.036883,-4.7395 -3.617063,-6.98277 -3.613078,-13.97 0.0034,-6.02381 0.692126,-9.45121 2.492364,-12.40372 5.785955,-9.48937 14.171874,-14.33514 24.80789,-14.33514 15.576172,0 26.073922,10.87702 26.007112,26.94671 -0.0806,19.37548 -17.968548,32.23047 -35.933681,25.8233 z"
+         id="path6895"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#babcc2;stroke-width:1.26999998"
+         d="m 82.792704,296.2549 c -7.395051,-2.30413 -13.613997,-7.22478 -16.6485,-13.1729 -2.944233,-5.77117 -3.677122,-18.42664 -1.330832,-22.98068 2.584048,-5.01551 9.216147,-11.42545 13.837583,-13.37405 9.290216,-3.91716 23.553275,-1.32436 29.620075,5.38447 3.22171,3.56265 7.31278,13.27026 7.34444,17.42745 0.08,10.50904 -7.56975,22.05556 -16.81306,25.37753 -8.461856,3.04111 -10.103694,3.17835 -16.009706,1.33818 z m 7.450654,-18.31336 c 0,-0.6985 -0.5715,-1.27 -1.27,-1.27 -0.6985,0 -1.27,0.5715 -1.27,1.27 0,0.6985 0.5715,1.27 1.27,1.27 0.6985,0 1.27,-0.5715 1.27,-1.27 z"
+         id="path6893"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#adacaf;stroke-width:1.26999998"
+         d="M 82.176907,295.75285 C 63.85897,289.9328 57.504001,266.83911 70.355175,252.79317 c 3.830323,-4.18643 12.943213,-7.87163 19.465217,-7.87163 9.339105,0 20.010848,7.19363 23.202178,15.64018 2.36152,6.2503 1.63715,16.4381 -1.60531,22.57757 -5.1334,9.71986 -19.221071,15.79693 -29.240353,12.61356 z m 6.161451,-5.11131 c 0.431697,-0.6985 -0.106075,-1.27 -1.195048,-1.27 -1.088973,0 -1.979952,0.5715 -1.979952,1.27 0,0.6985 0.537772,1.27 1.195049,1.27 0.657277,0 1.548254,-0.5715 1.979951,-1.27 z m 2.457754,-12.56692 c 0.386462,-0.62531 -0.133007,-1.4576 -1.154377,-1.84954 -1.984593,-0.76156 -3.679495,0.82201 -2.327183,2.17432 1.148094,1.14809 2.658345,1.00721 3.48156,-0.32478 z"
+         id="path6891"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#9699a3;stroke-width:1.26999998"
+         d="M 85.899737,295.88557 C 85.523799,295.27729 83.48459,294.43328 81.36816,294.01 72.365087,292.20938 63.573357,280.2073 63.573357,269.71732 c 0,-12.61352 12.094706,-23.52578 26.075024,-23.52578 11.390329,0 18.718449,4.59278 22.759369,14.26404 5.88862,14.09347 -1.43299,29.51833 -16.358128,34.46261 -7.097296,2.35114 -9.172441,2.54892 -10.149885,0.96738 z m 7.429687,-3.83013 c 1.764609,-2.8552 -9.74192,-6.25066 -13.246066,-3.90878 -1.648745,1.10189 -1.573383,1.52615 0.560235,3.15401 2.680974,2.04547 11.561585,2.57384 12.685831,0.75477 z M 88.83851,280.62756 c 1.697282,-1.305 2.645878,-2.97544 2.147548,-3.78176 -1.308953,-2.11793 -4.5527,-1.76126 -4.5527,0.5006 0,1.06967 -0.853967,2.27255 -1.897705,2.67307 -1.043738,0.40052 -1.581511,1.23984 -1.195048,1.86515 1.006078,1.62787 2.058827,1.38716 5.497905,-1.25706 z"
+         id="path6889"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#a49594;stroke-width:1.26999998"
+         d="m 88.045029,294.84144 c 1.235668,-0.322 2.950168,-0.30082 3.81,0.0471 0.859831,0.34789 -0.151171,0.61134 -2.246671,0.58546 -2.0955,-0.0259 -2.798998,-0.31051 -1.563329,-0.63251 z m 8.024255,-2.12601 c 0.865984,-2.25672 -4.165413,-5.12881 -11.223426,-6.40671 -2.619375,-0.47425 -4.7625,-1.40788 -4.7625,-2.07473 0,-0.66685 -0.517996,-1.21245 -1.151103,-1.21245 -0.633107,0 -0.86661,1.0879 -0.518895,2.41756 0.791011,3.02483 -3.928788,4.8669 -6.660734,2.59959 -3.123125,-2.59197 -6.909269,-12.65206 -6.909269,-18.35849 0,-13.87616 10.522605,-23.63029 25.237599,-23.39447 13.955564,0.22364 24.292404,10.90172 24.292404,25.09435 0,9.01191 -7.82929,19.83446 -16.18451,22.3721 -2.20914,0.67096 -2.685469,0.43797 -2.119566,-1.03675 z M 88.83851,280.62756 c 1.697282,-1.305 2.645878,-2.97544 2.147548,-3.78176 -1.308953,-2.11793 -4.5527,-1.76126 -4.5527,0.5006 0,1.06967 -0.853967,2.27255 -1.897705,2.67307 -1.043738,0.40052 -1.581511,1.23984 -1.195048,1.86515 1.006078,1.62787 2.058827,1.38716 5.497905,-1.25706 z"
+         id="path6887"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#908c92;stroke-width:1.26999998"
+         d="m 88.045029,294.84144 c 1.235668,-0.322 2.950168,-0.30082 3.81,0.0471 0.859831,0.34789 -0.151171,0.61134 -2.246671,0.58546 -2.0955,-0.0259 -2.798998,-0.31051 -1.563329,-0.63251 z m 8.024255,-2.12601 c 0.865517,-2.2555 -4.262671,-5.23728 -11.267311,-6.55135 -2.595238,-0.48687 -4.718615,-1.39312 -4.718615,-2.01388 0,-0.62076 -0.517996,-1.12866 -1.151103,-1.12866 -0.633107,0 -0.86661,1.0879 -0.518895,2.41756 0.791011,3.02483 -3.928788,4.8669 -6.660734,2.59959 -3.123125,-2.59197 -6.909269,-12.65206 -6.909269,-18.35849 0,-10.79765 4.888515,-17.30463 14.331356,-19.07612 2.498486,-0.46872 4.850103,-1.65329 5.225815,-2.63238 0.943945,-2.45988 10.348991,-2.14807 16.182812,0.53653 8.15933,3.75473 13.79002,13.09368 13.79002,22.87185 0,9.01191 -7.82929,19.83446 -16.18451,22.3721 -2.20914,0.67096 -2.685469,0.43797 -2.119566,-1.03675 z m -6.660322,-11.74777 c 3.723751,-3.45285 3.445701,-4.68355 -1.388104,-6.14396 -2.184576,-0.66002 -2.8575,-0.35276 -2.8575,1.30475 0,1.19244 -0.944561,2.85875 -2.099024,3.70292 -2.191821,1.60269 -1.400951,4.46017 1.234453,4.46017 0.839039,0 3.138618,-1.49575 5.110175,-3.32388 z"
+         id="path6885"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#95827c;stroke-width:1.26999998"
+         d="m 99.133359,290.89242 c 0,-0.56052 -2.896126,-2.06055 -6.435837,-3.33341 l -6.435837,-2.3143 3.454451,-3.60567 c 3.916058,-4.08748 2.969935,-6.74444 -2.794,-7.84629 -2.621472,-0.50112 -3.131445,-0.19097 -2.658667,1.61693 0.633695,2.42326 -3.088923,6.34186 -6.024677,6.34186 -2.42064,0 -3.64052,2.13974 -1.850562,3.24599 1.598075,0.98767 -0.825254,3.05062 -3.623387,3.08456 -2.363261,0.0287 -7.921486,-13.47954 -7.921486,-19.25165 0,-8.9898 6.262475,-16.5889 15.13312,-18.36302 2.154285,-0.43086 3.916881,-1.36023 3.916881,-2.06528 0,-0.8043 3.403599,-1.03837 9.135512,-0.62825 8.67601,0.62076 9.37345,0.90872 13.86592,5.72489 4.64868,4.98363 7.46959,11.82603 7.45908,18.09276 -0.008,4.84934 -5.56367,15.71542 -9.27976,18.1503 -3.5577,2.33109 -5.940751,2.79263 -5.940751,1.15058 z M 86.142316,262.93966 c 0.276491,-0.82946 -0.402166,-1.50812 -1.508125,-1.50812 -1.105958,0 -2.010833,0.90487 -2.010833,2.01083 0,2.24273 2.734004,1.85216 3.518958,-0.50271 z"
+         id="path6883"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#7d818f;stroke-width:1.26999998"
+         d="m 99.133359,290.89242 c 0,-0.56052 -2.896126,-2.06055 -6.435837,-3.33341 l -6.435837,-2.3143 3.454451,-3.60567 c 2.886998,-3.01337 3.188482,-3.92614 1.835331,-5.55658 -0.890515,-1.07301 -2.658413,-1.95092 -3.928662,-1.95092 -1.270249,0 -2.66275,-0.5715 -3.094447,-1.27 -0.431697,-0.6985 -1.335849,-1.27 -2.009229,-1.27 -0.673379,0 -0.365107,1.31133 0.68505,2.91407 1.745018,2.66324 1.691628,3.10052 -0.620241,5.08 -1.391287,1.19126 -3.346603,2.16593 -4.345146,2.16593 -2.42064,0 -3.64052,2.13974 -1.850562,3.24599 1.010571,0.62457 0.924065,1.19974 -0.296783,1.97331 -3.423334,2.16913 -4.983813,1.00929 -8.089104,-6.01233 -6.516399,-14.73473 -2.991737,-24.71874 10.137588,-28.71589 4.148467,-1.26298 7.941188,-2.94114 8.428268,-3.72926 0.585554,-0.94744 3.402008,-1.17493 8.312881,-0.67143 6.5525,0.67181 7.97595,1.34843 12.0857,5.74479 4.58383,4.90351 7.39756,11.76151 7.38709,18.00482 -0.008,4.84934 -5.56367,15.71542 -9.27976,18.1503 -3.5577,2.33109 -5.940751,2.79263 -5.940751,1.15058 z M 86.666885,263.33117 c 0.796989,-1.7492 1.097701,-3.53174 0.668249,-3.96119 -1.126624,-1.12662 -4.711776,2.78881 -4.711776,5.14584 0,3.07493 2.426663,2.36398 4.043527,-1.18465 z"
+         id="path6881"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#837e84;stroke-width:1.26999998"
+         d="m 100.40336,290.71334 c 0,-0.73799 -0.695269,-1.3418 -1.545041,-1.3418 -0.849773,0 -4.016843,-0.94399 -7.037935,-2.09776 l -5.492894,-2.09775 3.421548,-3.57133 c 2.858766,-2.98391 3.155232,-3.89222 1.802429,-5.52224 -0.890515,-1.07301 -2.658413,-1.95092 -3.928662,-1.95092 -1.270249,0 -2.66275,-0.5715 -3.094447,-1.27 -0.431697,-0.6985 -1.335849,-1.27 -2.009229,-1.27 -0.673379,0 -0.365107,1.31133 0.68505,2.91407 1.745018,2.66324 1.691628,3.10052 -0.620241,5.08 -1.391287,1.19126 -3.666082,2.16593 -5.055098,2.16593 -1.440697,0 -2.525482,0.85046 -2.525482,1.97995 0,1.08897 -0.522688,1.65691 -1.16153,1.26209 -0.63884,-0.39483 -1.429554,7e-5 -1.75714,0.87754 -0.373985,1.00177 -0.911691,0.65028 -1.445059,-0.94458 -0.467198,-1.397 -1.964072,-4.826 -3.326388,-7.62 -3.215073,-6.59385 -3.097904,-11.51742 0.421976,-17.73198 2.439161,-4.30648 3.938419,-5.36332 10.398729,-7.33012 4.151758,-1.26398 7.942714,-2.93575 8.424345,-3.71505 0.549,-0.8883 3.330213,-1.18105 7.454985,-0.78471 10.654884,1.0238 16.847654,7.08823 19.322964,18.9225 1.40444,6.71456 1.2939,7.54876 -1.85261,13.97998 -3.05242,6.23894 -11.08027,13.53205 -11.08027,10.06618 z M 86.666885,263.33117 c 0.796989,-1.7492 1.097701,-3.53174 0.668249,-3.96119 -1.126624,-1.12662 -4.711776,2.78881 -4.711776,5.14584 0,3.07493 2.426663,2.36398 4.043527,-1.18465 z"
+         id="path6879"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#897673;stroke-width:1.26999998"
+         d="m 99.941446,289.99964 c -0.400604,-1.04396 -1.576666,-1.8981 -2.613471,-1.8981 -5.930649,0 -10.856963,-5.46948 -6.449617,-7.16074 3.16316,-1.21381 2.201836,-5.18333 -1.5875,-6.55512 -1.920875,-0.69539 -4.559109,-1.6952 -5.862744,-2.22181 -2.115872,-0.85472 -2.208636,-0.65551 -0.86437,1.85628 1.851979,3.46045 -0.120379,6.46139 -4.246738,6.46139 -1.612333,0 -3.590287,1.4255 -4.843272,3.49053 -1.971697,3.24953 -2.171981,3.31529 -2.900744,0.9525 -0.430546,-1.39592 -1.897433,-4.82403 -3.259749,-7.61803 -3.129859,-6.41909 -3.126153,-10.8798 0.01416,-17.03531 2.37259,-4.65066 9.42597,-8.99969 14.595948,-8.99969 0.983722,0 2.654596,-0.86601 3.713054,-1.92447 3.196613,-3.19661 15.364787,-1.1281 20.647157,3.50988 4.15012,3.64385 8.0898,13.06755 8.0898,19.35074 0,7.83968 -12.38968,23.11392 -14.43191,17.79195 z m -12.915894,-25.9808 c 2.115582,-2.33769 1.963799,-6.4464 -0.234835,-6.35691 -2.789008,0.11351 -5.615008,3.74918 -4.831961,6.21634 1.042324,3.28408 2.19307,3.316 5.066796,0.14057 z"
+         id="path6877"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#757885;stroke-width:1.26999998"
+         d="m 99.941446,289.99964 c -0.400604,-1.04396 -1.576666,-1.8981 -2.613471,-1.8981 -5.930649,0 -10.856963,-5.46948 -6.449617,-7.16074 3.073564,-1.17943 2.281558,-5.39636 -1.176533,-6.26429 -1.694843,-0.42537 -4.064362,-1.85942 -5.265598,-3.18677 -2.080204,-2.2986 -2.024254,-2.57318 1.176533,-5.77397 5.58521,-5.58521 3.758381,-10.71849 -2.207116,-6.20187 -3.28921,2.49034 -4.070872,11.68546 -1.257622,14.79407 1.709215,1.88866 1.800557,2.67177 0.495106,4.24475 -0.880429,1.06085 -2.827184,1.92882 -4.326122,1.92882 -1.612333,0 -3.590287,1.4255 -4.843272,3.49053 -1.971697,3.24953 -2.171981,3.31529 -2.900744,0.9525 -0.430546,-1.39592 -1.897433,-4.82403 -3.259749,-7.61803 -3.121169,-6.40126 -3.126626,-10.87887 -0.02068,-16.96703 2.151781,-4.21784 3.424145,-5.0853 10.477501,-7.1433 4.415562,-1.28835 8.724807,-3.19051 9.576099,-4.22701 1.225554,-1.4922 1.932646,-1.56517 3.39623,-0.3505 1.144861,0.95015 3.512623,1.22185 6.220647,0.71382 3.724012,-0.69863 5.054252,-0.2214 8.972572,3.21892 4.50047,3.95147 8.43775,13.1236 8.43775,19.65625 0,7.83968 -12.38968,23.11392 -14.431916,17.79195 z"
+         id="path6875"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#6e6a73;stroke-width:1.26999998"
+         d="m 101.67336,289.41622 c 0,-0.72307 -1.17138,-1.31468 -2.603069,-1.31468 -1.431688,0 -4.340478,-0.89845 -6.463979,-1.99656 -3.657917,-1.89158 -3.748906,-2.12031 -1.730613,-4.3505 2.756042,-3.0454 1.994192,-6.88087 -1.465189,-7.37639 -1.491545,-0.21365 -3.712351,-1.49394 -4.935125,-2.84509 -2.131922,-2.35574 -2.085214,-2.59464 1.137375,-5.81723 3.902332,-3.90233 4.458852,-8.09423 1.074598,-8.09423 -1.2573,0 -2.907297,0.6213 -3.666658,1.38066 -0.99421,0.99421 -1.619496,0.99421 -2.233951,0 -0.963783,-1.55943 -5.783391,-1.90262 -5.783391,-0.41181 0,0.53286 1.285875,1.53697 2.8575,2.23134 2.349887,1.03822 2.790876,2.11061 2.482455,6.03681 -0.215199,2.73946 0.427723,5.92043 1.508467,7.4634 2.508255,3.58104 0.36104,5.75957 -6.261977,6.3533 -4.011032,0.35958 -5.177008,-0.0628 -6.296593,-2.28075 -0.75571,-1.49713 -2.130859,-3.63399 -3.055887,-4.74858 -2.08194,-2.50859 -1.058138,-8.76584 2.347809,-14.3493 1.903344,-3.12021 4.143244,-4.54675 9.838806,-6.26611 4.05593,-1.2244 8.070933,-3.06311 8.922225,-4.08602 1.104134,-1.32672 2.014234,-1.4811 3.175,-0.53857 0.894958,0.7267 4.103932,1.24855 7.131054,1.15967 4.828853,-0.14179 5.954083,0.38124 9.174883,4.26467 2.01906,2.43446 4.32266,6.34771 5.1191,8.69611 2.11537,6.23739 0.88586,16.79553 -2.51147,21.56664 -3.11294,4.37172 -7.76137,7.5599 -7.76137,5.32322 z m 4.73604,-7.16197 c -0.42201,-0.42201 -1.51738,-0.46713 -2.43417,-0.10027 -1.01312,0.40542 -0.71218,0.70635 0.7673,0.76729 1.33879,0.0551 2.08888,-0.24502 1.66687,-0.66703 z"
+         id="path6873"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#645d63;stroke-width:1.26999998"
+         d="m 98.063195,286.83787 c -5.889597,-1.39245 -7.122279,-2.9137 -5.228427,-6.45239 1.353533,-2.5291 1.184765,-3.20994 -1.293042,-5.21635 -1.567368,-1.26917 -3.573719,-2.30759 -4.458558,-2.30759 -0.88484,0 -2.309511,-0.84431 -3.165935,-1.87623 -1.280804,-1.54328 -0.85765,-2.46481 2.384496,-5.1929 2.746632,-2.31114 3.941629,-4.43752 3.941629,-7.01377 0,-3.92964 -1.214207,-4.49601 -5.137432,-2.39636 -2.599065,1.39097 -8.832568,0.41037 -8.832568,-1.38947 0,-0.59585 2.428875,-1.73277 5.3975,-2.52649 2.968625,-0.79372 6.060997,-1.9865 6.871935,-2.65062 0.943897,-0.77301 1.771189,-0.72734 2.299152,0.12692 0.497173,0.80445 2.842377,1.01179 5.904952,0.52206 4.549513,-0.7275 5.527453,-0.3789 9.361173,3.33689 7.53341,7.30167 8.93104,20.55814 3.1531,29.90705 -2.79584,4.52377 -3.94334,4.84444 -11.197975,3.12925 z m 8.733095,-4.45133 c -0.12959,-1.80388 -6.72804,-3.34789 -8.165278,-1.91065 -0.750965,0.75096 -0.05125,1.66233 1.874558,2.44159 3.6419,1.47366 6.4179,1.23937 6.29072,-0.53094 z m -30.722318,-3.21063 c -1.300727,-0.75927 -1.850421,-2.19955 -1.439378,-3.77138 0.372062,-1.42277 0.04031,-2.54299 -0.753115,-2.54299 -0.779967,0 -1.418121,1.42875 -1.418121,3.175 0,4.30253 -1.365235,3.91416 -4.715825,-1.34152 -2.904971,-4.55669 -2.735364,-8.52633 0.314294,-7.35607 1.34128,0.5147 1.575352,-0.33686 1.024927,-3.72873 -0.885652,-5.45764 1.049293,-6.8889 5.183355,-3.83408 3.572147,2.6396 5.813249,6.87494 5.813249,10.98616 0,1.57368 0.661578,3.52282 1.470175,4.33141 1.113568,1.11357 1.075847,1.94531 -0.155504,3.429 -1.87555,2.2599 -2.45069,2.33047 -5.324057,0.6532 z"
+         id="path6871"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#65585b;stroke-width:1.26999998"
+         d="m 101.03836,287.24798 c -7.739638,-1.70306 -11.227975,-4.29404 -8.255001,-6.13144 2.594912,-1.60375 1.224771,-5.42558 -2.497892,-6.96756 -6.718888,-2.78305 -7.413705,-4.86847 -2.819912,-8.46365 3.123316,-2.44436 4.047803,-4.14562 4.047803,-7.44882 0,-2.98411 0.855051,-4.87984 2.822583,-6.25796 4.399567,-3.08157 7.557439,-2.37094 12.086539,2.71992 7.36372,8.27704 8.51832,19.82031 2.89293,28.92238 -2.7445,4.44071 -3.35798,4.70955 -8.27705,3.62713 z m 6.71284,-5.33874 c -0.21826,-1.13449 -1.5322,-2.13836 -2.91984,-2.23084 -1.38765,-0.0925 -3.34631,-0.37822 -4.35258,-0.635 -3.539095,-0.90309 -2.780575,3.42834 0.83408,4.7629 4.35361,1.60738 6.96456,0.83806 6.43834,-1.89706 z m -2.26784,-23.0177 c 0,-0.6985 -0.89098,-1.27 -1.97995,-1.27 -1.08898,0 -1.62675,0.5715 -1.19505,1.27 0.4317,0.6985 1.32267,1.27 1.97995,1.27 0.65728,0 1.19505,-0.5715 1.19505,-1.27 z m -29.633335,19.47333 c -0.465667,-0.46566 -0.846667,-1.89441 -0.846667,-3.175 0,-1.28058 -0.895762,-2.32833 -1.990583,-2.32833 -1.412637,0 -1.756521,0.73746 -1.184418,2.54 1.182477,3.72565 -1.016228,3.09604 -4.192917,-1.20066 -2.933729,-3.96808 -2.589866,-8.01937 0.577501,-6.80394 1.540317,0.59108 1.734457,-0.10076 1.050841,-3.74475 -1.108392,-5.90824 1.136929,-7.16966 5.318602,-2.98799 2.212352,2.21235 3.04295,4.1267 2.571954,5.92779 -0.48497,1.85453 -0.192305,2.46703 0.966343,2.02242 1.714369,-0.65787 3.802357,7.37792 2.475072,9.52551 -0.791224,1.28022 -3.559247,1.41143 -4.745728,0.22495 z m 3.652568,-23.19558 c -0.979152,-1.5843 0.283123,-2.23601 6.305437,-3.25546 l 5.070328,-0.85831 -3.81,2.63964 c -4.143633,2.87079 -6.427555,3.3158 -7.565765,1.47413 z"
+         id="path6869"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#535666;stroke-width:1.26999998"
+         d="m 101.03836,287.24798 c -7.739638,-1.70306 -11.227975,-4.29404 -8.255001,-6.13144 2.706199,-1.67253 1.139285,-5.3069 -3.175001,-7.36424 -5.267823,-2.51204 -5.561371,-4.47923 -1.27,-8.51076 1.74625,-1.64052 3.175,-4.23482 3.175,-5.76511 0,-3.88324 4.719712,-9.47489 7.997416,-9.47489 1.551516,0 4.539566,2.03056 6.911706,4.69693 7.36372,8.27704 8.51832,19.82031 2.89293,28.92238 -2.7445,4.44071 -3.35798,4.70955 -8.27705,3.62713 z m 6.71284,-5.33874 c -0.21826,-1.13449 -1.5322,-2.13836 -2.91984,-2.23084 -1.38765,-0.0925 -3.34631,-0.37822 -4.35258,-0.635 -3.539095,-0.90309 -2.780575,3.42834 0.83408,4.7629 4.35361,1.60738 6.96456,0.83806 6.43834,-1.89706 z m -2.26784,-23.0177 c 0,-0.6985 -0.89098,-1.27 -1.97995,-1.27 -1.08898,0 -1.62675,0.5715 -1.19505,1.27 0.4317,0.6985 1.32267,1.27 1.97995,1.27 0.65728,0 1.19505,-0.5715 1.19505,-1.27 z m -29.633335,19.47333 c -0.465667,-0.46566 -0.846667,-1.89441 -0.846667,-3.175 0,-1.28058 -0.895762,-2.32833 -1.990583,-2.32833 -1.412637,0 -1.756521,0.73746 -1.184418,2.54 1.182477,3.72565 -1.016228,3.09604 -4.192917,-1.20066 -2.933729,-3.96808 -2.589866,-8.01937 0.577501,-6.80394 1.540317,0.59108 1.734457,-0.10076 1.050841,-3.74475 -0.530009,-2.82519 -0.306957,-4.7983 0.606231,-5.36268 2.06975,-1.27918 7.887824,4.91537 6.938989,7.38799 -0.428437,1.11649 0.464713,3.75181 2.004356,5.91404 1.527435,2.14508 2.455982,4.73712 2.063436,5.76008 -0.739396,1.92683 -3.547212,2.49281 -5.026769,1.01325 z m 4.233333,-23.28333 c 0,-0.6985 1.176729,-1.27 2.614952,-1.27 1.438223,0 2.261745,0.5715 1.830048,1.27 -0.431697,0.6985 -1.608424,1.27 -2.614952,1.27 -1.006527,0 -1.830048,-0.5715 -1.830048,-1.27 z"
+         id="path6867"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#4a4a58;stroke-width:1.26999998"
+         d="m 96.311939,284.85403 c -2.922116,-1.58584 -3.339339,-2.4612 -2.69903,-5.66274 0.685435,-3.42718 0.320057,-3.96966 -3.846083,-5.71039 -2.531907,-1.0579 -4.603468,-2.68445 -4.603468,-3.61457 0,-2.46446 4.228643,-5.89479 7.266626,-5.89479 1.441893,0 2.968517,-0.90399 3.392499,-2.00887 1.118161,-2.91388 1.027577,-3.07113 -1.769124,-3.07113 -3.056411,0 -3.180799,-1.05997 -0.562986,-4.79742 2.455579,-3.50583 10.008537,-3.97624 11.265127,-0.70162 0.76461,1.99254 -1.07094,4.16599 -3.55515,4.20959 -1.573296,0.0276 1.77511,3.82945 3.37273,3.82945 2.01092,0 8.53028,7.79486 8.53028,10.19922 0,3.28854 -2.65589,10.12078 -3.93424,10.12078 -0.63017,0 -1.14576,-0.49673 -1.14576,-1.10383 0,-1.28319 -8.769532,-2.93053 -10.361814,-1.94644 -1.608345,0.99401 1.266397,5.59027 3.496444,5.59027 1.0644,0 1.58207,0.5715 1.15037,1.27 -1.01941,1.64943 -1.8296,1.55384 -5.996421,-0.70751 z m -19.44344,-7.54749 c -0.327327,-1.04775 -0.887836,-2.82709 -1.245573,-3.95409 -0.430911,-1.35751 -1.543822,-1.81546 -3.297494,-1.35686 -3.02591,0.79129 -6.212075,-0.13702 -6.212075,-1.80993 0,-0.62377 1.206603,-1.13412 2.68134,-1.13412 2.376557,0 2.586553,-0.50526 1.847453,-4.445 -0.458638,-2.44475 -0.379366,-4.42682 0.17616,-4.40461 2.359588,0.0944 5.455048,3.62549 5.455048,6.22283 0,1.54451 0.85725,3.66546 1.905,4.71321 2.15584,2.15584 2.592064,8.07357 0.595141,8.07357 -0.720422,0 -1.577672,-0.85725 -1.905,-1.905 z m 31.472361,-15.5197 c -1.80303,-0.64394 -2.18632,-4.1653 -0.45338,-4.1653 1.06896,0 3.13512,3.85233 2.38827,4.4529 -0.19106,0.15365 -1.06177,0.0242 -1.93489,-0.2876 z m -27.656782,-6.76077 c -0.412843,-0.66799 0.426105,-1.21453 1.864328,-1.21453 3.066498,0 3.375857,0.70626 0.750624,1.71366 -1.02538,0.39347 -2.202109,0.16887 -2.614952,-0.49913 z"
+         id="path6865"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#3d4357;stroke-width:1.26999998"
+         d="m 94.717245,280.39052 c 0.917875,-2.89196 -2.374409,-7.52898 -5.345579,-7.52898 -2.808125,0 -3.433788,-3.6975 -1.06801,-6.31165 1.198092,-1.32388 3.01835,-2.08472 4.045016,-1.69075 1.080866,0.41477 2.694817,-0.46604 3.833938,-2.09237 2.666104,-3.80639 7.19214,-2.57004 12.73182,3.47788 3.54068,3.86552 4.12705,5.30594 3.45756,8.49343 -1.08953,5.18728 -2.54031,6.50123 -4.9605,4.49266 -1.09423,-0.90814 -2.35174,-1.28892 -2.79447,-0.8462 -0.44273,0.44273 -1.56464,0.17448 -2.49314,-0.5961 -2.357895,-1.95688 -5.530521,-0.43483 -5.530521,2.65324 0,1.41892 -0.610012,2.57986 -1.355583,2.57986 -0.793268,0 -1.009192,-1.09138 -0.520531,-2.63102 z m -18.443887,-5.5384 c 0,-1.91828 -0.913416,-3.37932 -2.40955,-3.85418 -1.643774,-0.52171 -2.201564,-1.5601 -1.755109,-3.26734 0.359942,-1.37643 -0.127385,-3.4638 -1.082951,-4.63861 -1.527426,-1.87788 -1.456247,-2.03054 0.588979,-1.26323 1.279505,0.48003 2.56538,0.87278 2.8575,0.87278 0.292122,0 0.531131,1.62592 0.531131,3.61316 0,2.00028 1.133837,4.63928 2.54,5.91184 2.997135,2.71237 3.330762,5.715 0.635,5.715 -1.171262,0 -1.905,-1.18994 -1.905,-3.08942 z M 66.113357,270.3614 c 0,-0.67658 0.85725,-0.90119 1.905,-0.49913 1.04775,0.40206 1.905,0.95563 1.905,1.23014 0,0.27452 -0.85725,0.49913 -1.905,0.49913 -1.04775,0 -1.905,-0.55357 -1.905,-1.23014 z m 28.109625,-15.39098 c 2.430777,-3.68057 8.370368,-4.81874 9.519488,-1.82416 1.06502,2.77539 0.29934,3.29964 -6.066158,4.15344 l -5.47682,0.73459 z"
+         id="path6863"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#303447;stroke-width:1.26999998"
+         d="m 108.89305,279.59128 c -0.46115,-0.74616 -3.50488,-1.75651 -6.76385,-2.24522 -3.258966,-0.48872 -8.135906,-2.28401 -10.837643,-3.98954 -4.296384,-2.71219 -4.738566,-3.4255 -3.526917,-5.68949 1.09777,-2.0512 2.157871,-2.41884 5.107027,-1.7711 3.145618,0.6909 3.721692,0.42503 3.721692,-1.71765 0,-5.24841 5.505761,-3.99293 11.747501,2.67879 3.49356,3.73422 4.15846,7.02299 2.35231,11.63517 -0.58044,1.48222 -1.29403,1.9179 -1.80012,1.09904 z m -31.24943,-3.66057 c 0.06093,-1.47948 0.361876,-1.78042 0.767293,-0.7673 0.366866,0.91678 0.321746,2.01216 -0.100267,2.43417 -0.422011,0.42201 -0.722174,-0.32808 -0.667029,-1.66687 z m 17.598291,-21.38603 c 1.995944,-2.40497 8.158779,-2.75149 7.391979,-0.41564 -0.28663,0.87312 -2.44113,1.77454 -4.787788,2.00314 -3.761324,0.36641 -4.069753,0.17839 -2.604191,-1.5875 z"
+         id="path6861"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#232638;stroke-width:1.26999998"
+         d="m 108.40148,276.38473 c -0.6896,-0.89001 -1.61035,-2.61832 -2.04611,-3.84069 -1.0928,-3.06547 -4.68201,-2.82207 -4.68201,0.3175 0,3.13183 -3.626557,3.35683 -9.034069,0.5605 -4.948896,-2.55918 -4.102484,-4.89919 1.955846,-5.40718 4.022881,-0.33733 4.68759,-0.79497 4.148557,-2.85624 -1.143883,-4.37421 1.572066,-4.00082 6.141666,0.84438 4.15134,4.40169 6.7857,9.98418 5.3765,11.39337 -0.3336,0.3336 -1.17077,-0.12164 -1.86038,-1.01164 z"
+         id="path6859"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>
diff --git a/resources/fonts/svg/planets/jupiter.svg b/resources/fonts/svg/planets/jupiter.svg
new file mode 100644
index 0000000..3b7c970
--- /dev/null
+++ b/resources/fonts/svg/planets/jupiter.svg
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="2136"
+   height="1968"
+   viewBox="0 0 565.14999 520.70001"
+   version="1.1"
+   id="svg6944"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)"
+   sodipodi:docname="jupiter.svg">
+  <defs
+     id="defs6938" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.35"
+     inkscape:cx="103.3565"
+     inkscape:cy="560"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:window-width="1969"
+     inkscape:window-height="1275"
+     inkscape:window-x="197"
+     inkscape:window-y="123"
+     inkscape:window-maximized="0" />
+  <metadata
+     id="metadata6941">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,223.70002)">
+    <g
+       id="g12968"
+       transform="translate(274.33353,-150.70357)">
+      <path
+         sodipodi:nodetypes="ccccccccc"
+         style="fill:#fdfdfc;stroke-width:1.26999998"
+         d="M -246.28928,189.65475 -158.5988,29.846418 0.75593809,-11.730963 169.18211,44.209513 245.5333,210.82142 196.39639,366.85 10.2507,407.22512 -166.91428,357.02262 Z"
+         id="path12910"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#ecead1;stroke-width:1.26999998"
+         d="m -26.57929,446.41863 c -25.13919,-1.53995 -34.68747,-3.41584 -58.42,-11.47741 -18.69531,-6.35051 -40.51131,-15.54041 -45.42365,-19.13454 -2.28177,-1.66946 -6.75997,-4.19047 -9.95157,-5.60227 -12.28884,-5.43594 -33.58768,-21.45149 -51.93977,-39.05584 -5.588,-5.36031 -12.446,-11.569 -15.24,-13.79706 -2.794,-2.22808 -7.08025,-6.66716 -9.525,-9.86465 -2.44475,-3.19749 -6.58812,-8.38872 -9.2075,-11.53606 -2.61937,-3.14734 -4.7625,-6.22259 -4.7625,-6.83391 0,-0.61131 -3.18311,-5.86293 -7.07357,-11.67025 -9.86878,-14.73121 -18.30185,-32.60832 -20.14935,-42.71425 -0.8531,-4.6665 -3.10769,-11.74413 -5.0102,-15.72808 -1.9025,-3.98395 -4.04717,-10.84195 -4.76593,-15.24 -0.71876,-4.39805 -2.45382,-13.13996 -3.85569,-19.42646 -2.01524,-9.03708 -2.52116,-15.68408 -2.41659,-31.75 0.13224,-20.31709 0.74145,-24.63322 6.36354,-45.085 1.44011,-5.23875 2.97777,-13.81125 3.41704,-19.05 0.91804,-10.94878 5.86626,-26.43706 11.96939,-37.465002 4.75868,-8.59861 7.13411,-13.31896 11.96363,-23.77364 9.87861,-21.38468 31.00891,-44.263571 70.08798,-75.887961 5.50737,-4.456791 27.57968,-18.653961 33.97122,-21.850691 4.25343,-2.127359 8.63639,-4.661567 9.73991,-5.631573 1.10351,-0.970005 7.39001,-4.088031 13.97,-6.928945 6.57998,-2.840915 14.82111,-6.53702 18.31361,-8.21357 52.12541,-25.022419 146.15422,-24.010527 203.83499,2.193573 13.09225,5.947746 43.4355,21.454912 47.72202,24.388727 2.14887,1.470743 8.14962,5.504736 13.335,8.964428 50.64798,33.792364 102.77296,106.423512 102.77296,143.204214 0,6.32781 3.03598,20.60437 6.69445,31.48044 3.77547,11.22387 4.05246,49.21922 0.48144,66.04 -1.26047,5.93725 -3.17145,15.06209 -4.24662,20.27741 -12.11467,58.76477 -53.04172,116.81193 -108.73249,154.21618 -8.94753,6.00952 -16.77587,10.92642 -17.39632,10.92642 -0.62044,0 -6.32275,2.82252 -12.67179,6.27227 -15.61267,8.48315 -41.98416,18.52515 -62.88365,23.94546 -2.794,0.72462 -7.366,1.95614 -10.16,2.7367 -4.94932,1.38268 -32.87161,3.46277 -55.88,4.1628 -6.2865,0.19128 -22.00274,-0.29988 -34.92499,-1.09146 z m 57.6709,-143.79117 c 0.78751,-1.27423 -3.93533,-5.79083 -7.3037,-6.98474 -2.36055,-0.83669 -9.72721,3.35302 -9.72721,5.53226 0,1.9925 15.86113,3.34521 17.03091,1.45248 z m 33.53992,-36.79659 c 0.30503,-2.07298 -0.24699,-2.75802 -2.2225,-2.75802 -2.75193,0 -3.15408,0.96218 -1.83685,4.39481 1.06709,2.78081 3.55705,1.77682 4.05935,-1.63679 z M 31.2057,233.36327 c 8.382,0.0154 17.59478,0.48518 20.47285,1.04393 5.52057,1.07178 8.18131,-0.50588 5.83103,-3.45747 -1.13249,-1.42228 -1.84748,-4.25822 -3.62155,-14.36485 -0.71219,-4.05723 3.78889,-9.39203 7.92425,-9.39203 0.92898,0 2.28067,-1.10543 3.00374,-2.45651 2.63576,-4.92496 1.75463,-5.54656 -6.87984,-4.85347 -5.62877,0.45183 -9.25302,0.10859 -11.57518,-1.09612 -5.09807,-2.64492 -9.4403,-2.28333 -9.4403,0.7861 0,2.66803 -0.56522,2.83767 -10.16,3.04908 -5.15175,0.11354 -5.71777,0.42802 -5.74303,3.19121 -0.0211,2.30751 -0.80598,3.15597 -3.175,3.4322 -6.56282,0.7652 -12.09367,2.12184 -13.62332,3.34162 -1.068,0.85165 -2.83168,0.74916 -5.3975,-0.31364 -3.0406,-1.25944 -4.58571,-1.25779 -7.64213,0.008 -2.35512,0.97553 -5.65759,1.2449 -8.57249,0.69923 -10.37121,-1.9415 -21.16566,-2.03398 -25.69883,-0.22017 -2.49578,0.99861 -7.30734,2.23098 -10.69237,2.7386 -3.38504,0.50761 -7.23094,1.99927 -8.54646,3.31479 -2.12913,2.12913 -2.77312,2.20119 -5.86286,0.65602 -11.18473,-5.59344 -33.37942,-4.15682 -26.31815,1.70351 1.04069,0.86369 1.89215,2.57847 1.89215,3.81064 0,2.27768 9.96484,4.85412 12.5632,3.24825 0.62735,-0.38773 1.46834,0.149 1.86886,1.19274 0.80617,2.10145 5.88794,2.64322 5.88794,0.6283 0,-0.6985 3.10952,-1.27 6.91004,-1.27 3.80053,0 7.26326,0.5715 7.69496,1.27 0.45198,0.73132 7.18229,1.27 15.86728,1.27 8.29529,0 16.40545,0.49474 18.02254,1.09942 1.6171,0.60468 5.79768,0.64752 9.29018,0.0952 3.4925,-0.55231 8.63599,-1.36274 11.42999,-1.80093 3.78655,-0.59385 6.21178,-0.16521 9.525,1.68344 2.56551,1.43145 5.519,2.15353 6.985,1.7077 1.397,-0.42484 9.398,-0.75984 17.78,-0.74442 z m 8.9287,-21.02781 c -0.45908,-0.74281 0.0173,-1.67745 1.05844,-2.077 2.68982,-1.03218 7.79286,-0.12891 7.79286,1.37939 0,1.75067 -7.81959,2.36697 -8.8513,0.69761 z m 50.95189,14.47809 c 1.62603,-0.33072 3.26795,-2.09686 4.05899,-4.36604 1.44409,-4.14251 1.67574,-3.98201 -8.05958,-5.58385 -8.23692,-1.35528 -11.60416,-2.3275 -15.875,-4.58358 -3.85964,-2.03885 -4.76335,-2.11491 -6.86245,-0.5776 -2.69894,1.97664 -3.23599,1.61703 8.13245,5.44548 8.68502,2.92478 9.64241,6.46615 2.2225,8.2209 -11.9706,2.83099 3.05365,4.15585 16.38309,1.44469 z m -150.6195,-25.45583 c 0.92678,-2.52586 0.6453,-2.6298 -5.57753,-2.05933 -7.75679,0.71109 -8.0482,0.88339 -5.89072,3.48299 2.4559,2.95918 10.21345,1.99617 11.46825,-1.42366 z M 62.6382,125.28473 c 11.06545,-2.90612 -4.60583,-8.03967 -17.23272,-5.64503 -3.41648,0.64793 -6.8528,0.78186 -7.63627,0.29766 -1.13757,-0.70306 -15.02523,-1.67557 -26.24851,-1.83811 -1.99198,-0.0288 -4.99445,0.44665 -13.11236,2.07659 -10.97312,2.20322 -2.47931,4.57262 16.60486,4.63202 7.15963,0.0222 13.0175,0.5412 13.0175,1.15314 0,1.27786 29.35074,0.70431 34.6075,-0.67627 z m -98.11811,-1.87844 c 1.47276,-0.93111 1.55541,-1.53665 0.37132,-2.72073 -1.18407,-1.18408 -1.95591,-1.16382 -3.4679,0.0911 -3.28746,2.72834 -0.6296,4.98548 3.09658,2.62971 z"
+         id="path12908"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#cfd3cb;stroke-width:1.26999998"
+         d="m -24.67429,446.35033 c -36.13639,-2.52322 -88.59868,-18.43241 -108.75515,-32.97997 -3.23682,-2.33613 -6.62393,-4.2475 -7.52687,-4.2475 -2.41079,0 -22.48421,-13.53702 -29.76797,-20.07479 -1.397,-1.25392 -5.3975,-4.52897 -8.89,-7.2779 -3.4925,-2.74893 -9.49325,-8.19051 -13.335,-12.09242 -3.84175,-3.90191 -8.52128,-8.04301 -10.39894,-9.20245 -1.87767,-1.15943 -6.85887,-6.26141 -11.06933,-11.33774 -4.21046,-5.07635 -9.38931,-11.29944 -11.50856,-13.8291 -2.11924,-2.52967 -3.85317,-5.04708 -3.85317,-5.59425 0,-0.54716 -3.6924,-6.54126 -8.20534,-13.32023 -10.65717,-16.00832 -18.19548,-31.93731 -19.90337,-42.05724 -0.75081,-4.44889 -2.96485,-11.35543 -4.92008,-15.34785 -1.95524,-3.99244 -4.20545,-11.42194 -5.00048,-16.51 -0.79503,-5.08808 -2.42206,-13.25154 -3.61562,-18.14104 -3.4616,-14.18069 -3.03313,-55.91163 0.67154,-65.405 4.28086,-10.96986 6.92729,-22.82525 6.93254,-31.05612 0.006,-8.84706 5.1493,-24.65529 11.93704,-36.686502 2.42726,-4.30231 5.58325,-10.39413 7.01331,-13.53738 8.58244,-18.86405 8.66504,-18.99458 25.31542,-40.005 16.87896,-21.298846 60.59972,-56.625491 84.28132,-68.099829 4.13121,-2.001675 8.42008,-4.461849 9.53082,-5.467052 1.11074,-1.005206 6.87466,-3.900835 12.80871,-6.434734 5.93405,-2.533899 12.50368,-5.475007 14.59918,-6.535795 4.95129,-2.506449 16.66823,-6.942535 21.55845,-8.162126 12.59425,-3.140919 24.55665,-6.485429 27.13421,-7.586315 9.13999,-3.903734 57.37338,-6.312484 76.40233,-3.815493 48.34727,6.34416 65.1011,11.110451 100.33,28.542859 17.68817,8.75269 28.3061,14.647136 33.01999,18.330758 2.44475,1.910425 7.44176,5.295062 11.10445,7.521416 46.59429,28.322157 99.68746,102.619821 101.38492,141.876313 0.42602,9.85241 3.56869,25.28845 6.46407,31.75 5.22817,11.66758 2.56729,63.15944 -4.71399,91.2229 -7.25463,27.96074 -32.82394,80.99904 -41.69083,86.47907 -0.75192,0.46471 -3.39785,3.90435 -5.87986,7.64366 -12.98109,19.55685 -52.67945,51.91581 -82.92096,67.59061 -7.47604,3.87499 -14.16429,7.50117 -14.86279,8.0582 -3.89583,3.10677 -45.93692,17.7115 -62.01555,21.5437 -19.76409,4.71061 -58.97332,6.52631 -91.65444,4.24434 z M 35.0157,307.94635 c 32.41181,-0.68979 39.28214,-1.11162 41.91,-2.57321 1.397,-0.777 4.96887,-2.1314 7.9375,-3.00979 6.04214,-1.78779 6.18717,-2.0379 3.44884,-5.94743 -1.50577,-2.14978 -2.77561,-2.61671 -5.58715,-2.05441 -2.93561,0.58713 -3.80238,0.21131 -4.48693,-1.94547 -1.10852,-3.49265 -7.49809,-3.63558 -10.41156,-0.23291 -1.86907,2.18293 -2.38262,2.2482 -4.8693,0.61886 -2.31655,-1.51786 -4.8923,-1.59163 -15.45454,-0.44258 -10.64354,1.1579 -13.91505,1.05745 -20.41472,-0.62678 -5.04284,-1.30673 -8.77187,-1.62386 -10.70108,-0.91006 -1.62859,0.60258 -3.62306,0.86173 -4.43217,0.57589 -0.80912,-0.28582 -1.84296,0.0819 -2.29746,0.81735 -1.302,2.10669 -15.5288,1.60272 -19.99803,-0.7084 -5.47544,-2.83147 -18.2095,-1.43498 -22.37905,2.45421 -3.21942,3.00292 -16.24679,3.81449 -19.5498,1.21788 -2.4165,-1.89968 -9.66178,-2.99026 -10.53025,-1.58505 -0.41756,0.67562 -2.4579,1.2284 -4.5341,1.2284 -2.07651,0 -4.08401,0.80553 -4.46194,1.79039 -0.37788,0.98472 -2.07443,2.1386 -3.77015,2.56419 -10.90525,2.73705 0.58042,7.45781 19.1419,7.86757 23.97582,0.5293 37.50781,1.36415 38.47703,2.37384 0.49112,0.51163 8.32247,0.48085 17.40296,-0.0685 9.0805,-0.54926 25.0825,-1.18108 35.56,-1.40407 z m 29.80032,-37.17001 c 2.8516,-5.32824 1.67568,-10.24349 -2.45064,-10.24349 -5.65242,0 -7.28844,3.97397 -3.65773,8.88477 3.30093,4.46476 4.32425,4.69239 6.10837,1.35872 z m 16.94773,-7.04925 c 5.97392,-2.49605 6.78335,-4.14216 2.90558,-5.90899 -3.93641,-1.79356 -9.64863,1.33471 -9.64863,5.28403 0,3.00817 0.85057,3.087 6.74305,0.62496 z M 13.1082,255.89537 c 3.46225,-0.56483 4.7625,-1.40431 4.7625,-3.07478 0,-2.72295 -2.02699,-4.30524 -3.1622,-2.46845 -0.43873,0.70989 -3.58774,1.29071 -6.9978,1.29071 -6.44781,0 -8.3796,1.26868 -7.08174,4.65086 0.55229,1.43923 1.72458,1.76656 4.24004,1.1839 1.91219,-0.44291 5.61983,-1.15492 8.2392,-1.58224 z m -23.46954,-5.28138 c 0.67335,-2.02006 -4.09206,-2.98285 -7.79521,-1.57492 -3.06657,1.16592 -4.33106,3.73214 -2.66159,5.40159 0.98611,0.98613 9.94543,-2.29255 10.4568,-3.82667 z M 31.8407,236.27593 c 6.63575,-0.0387 15.90859,0.30786 20.60631,0.77023 7.60594,0.7486 9.32208,0.4713 15.67095,-2.53219 6.93321,-3.27994 7.25254,-3.31454 11.5907,-1.25594 4.37156,2.07443 5.74082,2.06391 18.68825,-0.14359 4.96122,-0.84588 4.96122,-0.84588 1.92398,-3.13687 -4.0872,-3.08299 -2.2746,-4.46363 3.58412,-2.72998 2.62388,0.77643 7.25051,1.07166 10.28142,0.65607 4.83972,-0.66362 5.8911,-0.3454 8.63454,2.61326 2.49846,2.69449 3.60315,3.11236 5.51827,2.08741 1.31696,-0.70481 3.66362,-1.28148 5.2148,-1.28148 2.74186,0 2.75623,-0.0709 0.5168,-2.54536 -2.41696,-2.67072 -11.13035,-5.71807 -14.90778,-5.21374 -1.53751,0.20528 -1.98043,-0.30968 -1.45162,-1.68774 0.44423,-1.15767 2.27241,-1.98316 4.39204,-1.98316 1.99708,0 5.5078,-0.82546 7.80162,-1.83436 2.29383,-1.0089 6.25854,-2.16744 8.81048,-2.57454 4.74313,-0.75666 7.26574,-3.87847 4.98057,-6.16364 -0.8304,-0.83041 -1.91868,-0.70934 -3.20271,0.35632 -6.09575,5.05902 -14.85429,-2.59812 -9.49595,-8.30181 3.9851,-4.24194 2.4278,-5.0236 -6.64641,-3.33608 -12.46931,2.31891 -14.22666,-0.74413 -2.34038,-4.07924 4.191,-1.17594 7.62,-2.71851 7.62,-3.42794 0,-0.70942 -1.00013,-0.97466 -2.2225,-0.58941 -1.22238,0.38526 -7.00563,0.97842 -12.85167,1.31813 -5.84604,0.33973 -11.6988,0.95716 -13.00613,1.3721 -3.54132,1.12397 -5.19937,-0.36133 -3.64517,-3.26539 1.65857,-3.09905 -0.41511,-3.63022 -15.89953,-4.0726 -11.43,-0.32654 -11.43,-0.32654 -13.335,3.87753 -1.905,4.20407 -1.905,4.20407 -11.43,3.5061 -5.23875,-0.38388 -10.78255,-0.71284 -12.31956,-0.73101 -1.537,-0.0182 -3.11919,-0.5583 -3.51597,-1.2003 -4.85036,-7.84803 -32.42447,-4.05243 -32.42447,4.46325 0,0.83153 -0.85725,2.22334 -1.905,3.09289 -1.04775,0.86956 -1.905,2.90605 -1.905,4.52556 0,3.77444 -1.79616,4.85189 -5.12152,3.07222 -2.15351,-1.15251 -3.68245,-0.79528 -8.38095,1.95824 -6.06668,3.5553 -8.54033,3.3956 -5.27785,-0.34073 1.07108,-1.22666 2.23563,-4.94491 2.58788,-8.26279 0.87087,-8.20299 -6.13964,-9.22492 -10.6652,-1.55468 -2.88108,4.88307 -5.8242,6.95218 -9.88886,6.95218 -4.20187,0 -3.45666,-6.20687 1.2822,-10.67959 4.52819,-4.2739 -1.99846,-9.51553 -8.68352,-6.97386 -4.15554,1.57993 -18.13649,1.43511 -22.93471,-0.23756 -2.91415,-1.01587 -5.16287,-0.98455 -8.50509,0.11849 -2.49893,0.82473 -5.90257,1.64868 -7.56366,1.83103 -1.66109,0.18233 -3.92583,1.23719 -5.03276,2.34414 -1.10693,1.10693 -4.01244,2.73574 -6.45665,3.61959 -3.26387,1.18022 -5.1454,3.05575 -7.08497,7.06238 -2.64044,5.45442 -4.11866,6.56499 -5.58349,4.19484 -0.47658,-0.77111 -2.70499,-0.27801 -5.73935,1.27 -3.29129,1.67908 -7.47587,2.53056 -12.43657,2.53056 -5.65339,0 -8.19034,0.6193 -10.40492,2.54 -2.09472,1.81673 -4.77187,2.54553 -9.40182,2.55944 -8.04986,0.0241 -10.84417,1.45421 -8.4585,4.32876 0.91374,1.10098 3.16298,2.00531 4.99832,2.00962 1.83536,0.004 5.0515,0.91059 7.147,2.01397 2.0955,1.10336 6.85527,2.32744 10.57727,2.72014 4.03222,0.42544 8.54624,1.92125 11.1689,3.70104 5.42956,3.6846 9.37926,3.85651 10.27085,0.44703 0.83654,-3.19891 2.83361,-3.19996 8.96379,-0.005 5.36468,2.79623 23.00241,6.2517 26.19472,5.1319 2.23308,-0.78332 35.99988,-0.3007 52.59442,0.75173 5.87181,0.37239 12.21002,0.0903 14.29368,-0.63601 2.80768,-0.97876 4.77419,-0.85327 7.86505,0.50187 2.41856,1.0604 6.22015,1.54704 9.20131,1.17785 2.794,-0.346 10.50925,-0.6608 17.145,-0.69953 z m 32.34028,-13.91544 c -0.50363,-0.81488 -0.22603,-1.90785 0.61688,-2.4288 1.9613,-1.21215 9.00271,0.19577 9.3352,1.86656 0.36228,1.82057 -8.85265,2.34117 -9.95208,0.56224 z M 86.4507,212.34781 c 0,-0.65728 0.5715,-1.54826 1.27,-1.97996 2.94082,-1.81753 1.39838,-6.02258 -1.905,-5.19348 -5.7262,1.43719 -3.53898,-2.39953 2.36103,-4.14159 6.19305,-1.8286 17.86614,-1.43258 12.43913,0.422 -1.83938,0.62857 -3.88787,3.13664 -5.35237,6.55318 -2.21753,5.17331 -8.81279,8.42112 -8.81279,4.33985 z m -170.30155,-3.39644 c -1.47303,-1.77487 -1.37974,-1.98892 0.69175,-1.5875 1.31688,0.25519 2.63219,1.17836 2.92291,2.05148 0.75433,2.26554 -1.60019,1.96332 -3.61466,-0.46398 z m 66.97397,-29.13483 c 6.08308,-1.21277 5.01321,-6.51228 -2.29047,-11.34562 -7.52523,-4.97997 -12.60919,-0.78585 -6.14194,5.06693 3.26583,2.95553 3.20918,4.16987 -0.21802,4.67417 -1.51692,0.22321 -2.98336,1.09039 -3.25876,1.92708 -0.47816,1.45269 3.55246,1.34352 11.90919,-0.32256 z m 29.87955,-47.58297 c 4.16102,-0.76921 8.24884,-0.84783 10.01976,-0.19273 1.78741,0.66122 4.80279,0.58893 7.49229,-0.17961 4.36527,-1.24738 10.48716,-1.60704 42.65539,-2.5059 8.41193,-0.23507 16.0714,-0.90758 17.02106,-1.4945 0.94964,-0.58692 4.57644,-0.96626 8.05954,-0.84298 11.56345,0.40927 14.69549,-2.71091 6.59751,-6.57256 -6.42652,-3.0646 -13.65735,-3.92641 -18.77438,-2.23763 -5.08873,1.67942 -11.05314,0.63895 -11.05314,-1.92822 0,-2.49944 -5.88306,-6.05509 -7.88165,-4.76356 -1.91933,1.24032 -58.78105,2.53939 -77.84334,1.77842 -16.54116,-0.66033 -41.23972,1.10509 -45.37455,3.24328 -2.85996,1.47894 -5.6363,1.78358 -10.60134,1.16324 -5.61827,-0.70196 -7.12222,-0.44486 -9.1274,1.56032 -1.3204,1.3204 -3.35722,2.76777 -4.52623,3.21637 -2.99622,1.14976 -2.63922,3.01575 0.73202,3.82611 1.57162,0.37777 7.37641,1.90957 12.89953,3.40397 9.51047,2.57328 10.36905,2.61014 16.22027,0.6963 6.72079,-2.19826 36.25098,-1.04458 42.95269,1.67808 4.18143,1.69875 11.85611,1.75541 20.53197,0.1516 z"
+         id="path12906"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#e6daab;stroke-width:1.26999998"
+         d="m -15.78429,446.98961 c -38.66981,-2.2294 -102.91432,-20.97427 -116.89265,-34.10622 -1.43008,-1.34348 -4.5872,-3.16784 -7.01582,-4.05412 -5.24202,-1.91296 -30.39652,-18.54364 -30.39652,-20.09638 0,-0.59291 -2.58766,-2.85836 -5.75034,-5.03434 -3.16269,-2.17599 -10.28033,-8.34708 -15.81698,-13.71351 -5.53665,-5.36645 -12.37649,-11.67881 -15.19965,-14.02746 -4.27422,-3.55583 -15.62338,-17.18307 -21.15043,-25.39586 -19.35852,-28.7654 -26.56041,-42.74306 -29.11264,-56.50282 -0.92642,-4.99458 -3.19478,-12.01489 -5.04079,-15.60069 -1.84601,-3.58582 -3.94578,-10.44382 -4.66616,-15.24 -0.72037,-4.7962 -2.46788,-13.57811 -3.88335,-19.51536 -4.13975,-17.36434 -3.16975,-57.20174 1.61029,-66.13332 2.88016,-5.38162 6.24149,-21.21668 6.27016,-29.53836 0.0337,-9.77148 4.01537,-22.97915 10.27845,-34.094532 2.09457,-3.71733 5.8204,-11.04504 8.27961,-16.28379 6.3063,-13.43398 9.43983,-19.19274 12.48315,-22.94134 1.45403,-1.79098 3.78668,-4.86915 5.18368,-6.84038 13.2539,-18.701794 42.74355,-46.195291 65.405,-60.977726 3.14325,-2.050394 6.858,-4.706309 8.255,-5.902033 4.37479,-3.744486 32.85339,-19.180461 48.89499,-26.50207 15.29796,-6.982205 20.24447,-8.811068 33.02,-12.20843 6.63575,-1.764627 15.21704,-4.307188 19.06954,-5.650136 14.60206,-5.090151 53.40407,-7.646122 76.18045,-5.018168 46.71094,5.389537 66.81678,11.37283 107.94999,32.124817 55.2528,27.87541 104.06599,73.357646 119.8726,111.692596 1.51214,3.66733 4.82739,10.38262 7.3672,14.92287 6.84988,12.245052 12.62071,29.713852 12.35964,37.413712 -0.25792,7.60657 4.13626,28.06281 7.2972,33.97077 7.0731,13.21999 1.12187,71.72544 -10.88931,107.05052 C 234.9876,366.40155 132.26579,441.81479 25.91503,446.64529 15.55341,447.11591 5.3612,447.5836 3.2657,447.6846 c -2.0955,0.10097 -10.668,-0.21176 -19.04999,-0.69499 z m -29.845,-106.3718 c 0,-0.65727 -0.59887,-1.56517 -1.33082,-2.01755 -1.86399,-1.152 -5.34821,0.38276 -4.38973,1.93363 0.99894,1.6163 5.72055,1.68557 5.72055,0.0839 z M -84.90954,309.3197 c 1.07497,-1.29525 2.82875,-1.77091 4.91801,-1.33388 13.1935,2.75986 18.80908,3.19514 32.41093,2.5123 8.40747,-0.42207 21.5479,-0.65181 29.20095,-0.51053 7.65305,0.1413 21.94055,-0.39265 31.74999,-1.18654 12.54349,-1.01518 20.23959,-1.05515 25.93839,-0.13474 4.93662,0.79732 8.94595,0.85762 10.26007,0.15432 1.32078,-0.70686 5.77936,-0.61678 11.50049,0.23236 7.67972,1.13984 11.50632,1.00603 21.48994,-0.75143 27.74115,-4.88342 26.44251,-4.46159 27.29388,-8.8657 0.67458,-3.48956 5.23677,-9.06262 14.45858,-17.66224 2.68618,-2.50494 22.55721,-11.02106 25.801,-11.05752 1.13685,-0.0128 2.85135,-0.97298 3.81,-2.13377 3.51832,-4.26016 -13.24571,0.14835 -23.65699,6.22118 -1.397,0.81486 -4.2545,2.38783 -6.35,3.49548 -2.0955,1.10765 -5.39073,3.06434 -7.32274,4.34821 -4.71766,3.13501 -18.73652,5.95752 -28.87226,5.81307 -4.54025,-0.0648 -12.827,0.4492 -18.415,1.14202 -34.02895,4.21903 -34.13638,4.22445 -42.21917,2.12999 -5.77617,-1.49675 -8.61811,-1.67096 -11.20635,-0.68691 -2.96226,1.12625 -3.246,1.05134 -1.94688,-0.514 0.98445,-1.18619 1.0708,-2.11046 0.24487,-2.62092 -0.70173,-0.43369 -2.22396,0.65843 -3.38274,2.42693 -2.5833,3.94263 -14.48576,4.60916 -21.11257,1.18231 -5.28241,-2.73163 -18.83199,-1.44222 -22.03485,2.09692 -3.53619,3.90745 -9.85498,4.384 -18.732,1.41275 -6.71327,-2.24703 -9.9352,-2.54084 -21.75599,-1.98395 -8.26639,0.38944 -16.27314,0.0786 -19.80627,-0.76902 -5.10892,-1.22561 -7.02715,-1.05333 -13.84518,1.2435 -6.68057,2.25052 -8.56001,2.43285 -12.05404,1.16939 -6.55582,-2.37061 -16.84036,0.42449 -12.17382,3.30857 0.6985,0.4317 1.27,1.67416 1.27,2.76102 0,1.75067 -0.32598,1.7651 -2.8575,0.12649 -3.09741,-2.00493 -11.71868,-3.76295 -12.85939,-2.62224 -3.67345,3.67345 11.20857,8.9747 20.69509,7.37196 4.59431,-0.7762 19.51873,1.17144 22.30891,2.91133 0.70834,0.44171 2.45536,0.65248 3.88227,0.46838 1.42691,-0.1841 3.99866,0.22593 5.715,0.91118 4.44848,1.77606 5.76475,1.67182 7.65537,-0.60624 z M 64.6067,271.70885 c 6.46205,-6.46205 -1.73585,-14.79189 -8.42468,-8.56029 -2.89372,2.69592 -2.68433,5.00429 0.45394,5.00429 0.98196,0 2.39709,1.143 3.14474,2.54 1.56453,2.92334 2.68315,3.15885 4.826,1.016 z m 21.73216,-10.65884 c 1.52159,-1.40367 1.2756,-1.89001 -1.62419,-3.21125 -4.09246,-1.86465 -10.04152,1.64799 -10.73827,6.34045 -0.44705,3.01087 8.03351,0.86425 12.36246,-3.1292 z M 13.1082,255.89537 c 3.46225,-0.56483 4.7625,-1.40431 4.7625,-3.07478 0,-2.72295 -2.02699,-4.30524 -3.1622,-2.46845 -0.43873,0.70989 -3.58774,1.29071 -6.9978,1.29071 -6.44781,0 -8.3796,1.26868 -7.08174,4.65086 0.55229,1.43923 1.72458,1.76656 4.24004,1.1839 1.91219,-0.44291 5.61983,-1.15492 8.2392,-1.58224 z m -23.46954,-5.28138 c 0.67335,-2.02006 -4.09206,-2.98285 -7.79521,-1.57492 -3.06657,1.16592 -4.33106,3.73214 -2.66159,5.40159 0.98611,0.98613 9.94543,-2.29255 10.4568,-3.82667 z M 31.8407,236.27593 c 6.63575,-0.0387 15.90859,0.30786 20.60631,0.77023 7.60594,0.7486 9.32208,0.4713 15.67095,-2.53219 6.93321,-3.27994 7.25254,-3.31454 11.5907,-1.25594 5.60741,2.66089 9.4755,2.19973 31.99769,-3.81481 8.11064,-2.16595 8.11064,-2.16595 11.17454,1.13832 2.52958,2.72805 3.5081,3.06653 5.6112,1.94099 1.40103,-0.7498 3.80197,-1.2632 5.33546,-1.1409 5.51188,0.43962 6.1406,0.10579 4.73782,-2.51529 -0.72307,-1.35107 -1.31468,-2.77982 -1.31468,-3.175 0,-1.743 7.44791,-0.48036 12.28799,2.08317 4.66752,2.47213 5.68307,2.6053 8.47466,1.11129 1.7821,-0.95376 4.57239,-1.34586 6.36885,-0.89498 2.33737,0.58665 3.66048,0.14099 4.91994,-1.65714 1.16672,-1.66572 3.54811,-2.58969 7.37816,-2.8627 7.10253,-0.50628 8.81643,-4.85154 2.8668,-7.2682 -1.95877,-0.79563 -4.99015,-2.25406 -6.7364,-3.24096 -1.74625,-0.9869 -5.03701,-2.47416 -7.31281,-3.30501 -3.44219,-1.2567 -4.27012,-2.33804 -4.92482,-6.4323 -0.43285,-2.70692 -1.42833,-4.95675 -2.21219,-4.99965 -0.78384,-0.0429 -6.85443,-0.28086 -13.49018,-0.5288 -18.30298,-0.6839 -17.69078,-0.58304 -13.96999,-2.30152 5.5565,-2.56633 6.82937,-6.52529 2.5361,-7.88792 -1.54619,-0.49074 -3.32508,-0.0263 -4.53572,1.18442 -1.69182,1.69181 -6.37471,2.64414 -11.33538,2.30517 -0.6985,-0.0478 -5.55625,-1.03614 -10.795,-2.19646 -19.93905,-4.41629 -34.45538,-5.1748 -37.0769,-1.93736 -1.54419,1.90699 -3.26574,2.66064 -5.223,2.28648 -1.60577,-0.30696 -3.66883,0.24397 -4.58459,1.22428 -1.01122,1.08254 -2.85486,1.48377 -4.69525,1.02186 -1.67187,-0.41961 -3.03026,-0.19448 -3.03026,0.50222 0,0.77581 -1.1019,0.85546 -2.8575,0.20654 -1.57162,-0.58093 -3.59109,-0.7985 -4.4877,-0.4835 -0.8966,0.31499 -2.39473,-0.0618 -3.32916,-0.8373 -7.03033,-5.83465 -22.97015,-5.99132 -29.18027,-0.28679 -2.47403,2.2726 -3.84549,2.84737 -4.23334,1.77417 -0.60608,-1.67713 -5.13787,-2.11184 -8.51315,-0.81662 -3.30619,1.2687 -2.28243,2.85181 2.11862,3.27616 4.1275,0.39796 4.1275,0.39796 0.41438,2.91109 -2.65532,1.79716 -3.86132,2.06852 -4.23333,0.9525 -0.28612,-0.85834 -1.37066,-1.56063 -2.41009,-1.56063 -1.03943,0 -2.80337,-0.91348 -3.91984,-2.02997 -1.60604,-1.60604 -3.97406,-1.91481 -11.33929,-1.47857 -8.70729,0.51572 -11.24499,-0.22524 -7.88451,-2.30214 2.57411,-1.59088 -7.8971,-5.50996 -11.28264,-4.22277 -4.15554,1.57993 -18.13649,1.43511 -22.93471,-0.23756 -2.91415,-1.01587 -5.16287,-0.98455 -8.50509,0.11849 -2.49893,0.82473 -5.90257,1.64868 -7.56366,1.83103 -1.66109,0.18233 -3.96065,1.27201 -5.11015,2.42152 -1.14949,1.14949 -2.47779,2.13397 -2.95178,2.18774 -6.65064,0.75433 -10.14312,0.38662 -12.18963,-1.28339 -2.23044,-1.82012 -2.37202,-1.80844 -1.66413,0.13729 1.19039,3.27198 -4.49028,6.41843 -9.79221,5.42377 -3.51407,-0.65924 -4.55538,-0.33384 -5.68476,1.77643 -0.92183,1.72245 -3.21626,2.85847 -6.88424,3.40852 -6.2467,0.93675 -7.26618,1.49332 -9.23266,5.04057 -1.73164,3.12362 -19.34182,6.1415 -28.87428,4.94822 -12.18787,-1.52567 -27.9954,1.63737 -19.20667,3.84319 1.77,0.44425 3.81335,2.11394 4.54076,3.71045 2.67898,5.87972 7.67033,7.09644 31.75626,7.74113 16.61223,0.44464 23.70562,1.13308 27.55838,2.67466 4.74654,1.89918 12.17105,1.98923 15.09352,0.18305 0.63504,-0.39249 3.6956,-0.11253 6.80125,0.622 11.12919,2.6324 22.58848,4.32585 24.56963,3.63091 2.22706,-0.78122 36.01731,-0.29651 52.58695,0.75434 5.87181,0.37239 12.21002,0.0903 14.29368,-0.636 2.80768,-0.97877 4.77419,-0.85328 7.86505,0.50186 2.41856,1.0604 6.22015,1.54704 9.20131,1.17785 2.794,-0.346 10.50925,-0.66079 17.145,-0.69953 z m 63.39118,-34.92918 c -1.21492,-1.46389 -0.65511,-1.7739 3.20326,-1.7739 4.79866,0 5.26363,1.71863 0.78204,2.8906 -1.3822,0.36145 -3.17558,-0.14106 -3.9853,-1.1167 z m 20.0205,-2.60554 c -1.55526,-1.55526 -0.90135,-2.91385 1.04332,-2.16763 1.09451,0.42 1.905,0.0248 1.905,-0.92885 0,-1.29102 0.33867,-1.32121 1.524,-0.13588 0.8382,0.8382 2.64795,1.524 4.02167,1.524 3.58523,0 2.16829,0.8015 -3.09649,1.75155 -2.49469,0.45018 -4.92356,0.43073 -5.3975,-0.0432 z M -16.87688,179.81654 c 6.08308,-1.21277 5.01321,-6.51228 -2.29047,-11.34562 -7.52523,-4.97997 -12.60919,-0.78585 -6.14194,5.06693 3.26583,2.95553 3.20918,4.16987 -0.21802,4.67417 -1.51692,0.22321 -2.98336,1.09039 -3.25876,1.92708 -0.47816,1.45269 3.55246,1.34352 11.90919,-0.32256 z m 29.87955,-47.58297 c 4.16102,-0.76921 8.24884,-0.84783 10.01976,-0.19273 1.78741,0.66122 4.80279,0.58893 7.49229,-0.17961 2.47554,-0.70739 9.64448,-1.46878 15.93098,-1.69199 21.60819,-0.76719 47.00679,-1.857 57.785,-2.47946 5.93725,-0.34287 16.79575,-0.64377 24.13,-0.66866 16.24199,-0.0551 21.58999,-1.94517 21.58999,-7.6302 0,-5.49806 -6.35262,-7.61184 -24.76499,-8.24028 -29.81029,-1.01749 -91.14121,0.17843 -105.72432,2.06158 -10.8266,1.39805 -14.88289,1.39521 -24.13,-0.0169 -11.20686,-1.71138 -20.41694,-1.79567 -39.05567,-0.35747 -5.588,0.43118 -12.16025,0.71707 -14.605,0.63529 -6.08538,-0.20352 -45.81715,-0.13059 -55.4672,0.10185 -4.3132,0.10389 -7.56559,0.6364 -7.22754,1.18339 0.58729,0.95024 -4.09945,1.52861 -8.68145,1.07131 -1.18866,-0.11862 -3.5924,0.72207 -5.34165,1.86823 -1.74926,1.14615 -3.8368,1.67827 -4.63899,1.18249 -0.8022,-0.49579 -5.82795,-0.18008 -11.16836,0.70157 -31.84869,5.25793 2.0295,8.88793 68.39519,7.32847 3.14325,-0.0739 10.23391,1.13664 15.75703,2.69 9.55439,2.68713 10.34204,2.72616 16.22027,0.80348 6.72079,-2.19826 36.25098,-1.04458 42.95269,1.67808 4.18143,1.69875 11.85611,1.75542 20.53197,0.1516 z M -37.37429,75.112848 c 2.78714,-3.07976 4.32073,-3.19541 6.30936,-0.47581 1.8639,2.54905 15.42837,3.45002 22.89568,1.52078 2.79128,-0.72116 6.07955,-1.50465 7.30728,-1.74108 1.22772,-0.23644 2.80158,-0.78176 3.49745,-1.21184 0.69587,-0.43007 1.26522,0.13904 1.26522,1.2647 0,1.67255 1.45088,2.0318 7.9375,1.96547 4.36563,-0.0447 12.5095,-0.0919 18.0975,-0.1049 8.65683,-0.0202 10.44747,-0.40028 12.10303,-2.56868 1.886,-2.47025 2.13611,-2.48254 8.52459,-0.4193 6.88877,2.22483 16.75604,-0.577 11.63557,-3.30394 -3.65304,-1.94545 -11.31803,-2.62945 -13.91157,-1.24143 -2.00687,1.07405 -3.12259,1.01099 -4.65798,-0.26329 -2.47173,-2.05135 -58.27735,-2.67403 -68.30363,-0.76213 -3.4925,0.66599 -8.0645,1.1092 -10.16,0.98495 -30.99077,-1.83776 -44.13799,3.79529 -17.78,7.61798 10.75216,1.55939 12.84388,1.38625 15.24,-1.26143 z"
+         id="path12904"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#ceccaf;stroke-width:1.26999998"
+         d="m -15.78429,446.98961 c -38.66981,-2.2294 -102.91432,-20.97427 -116.89265,-34.10622 -1.43008,-1.34348 -4.5872,-3.16784 -7.01582,-4.05412 -5.24202,-1.91296 -30.39652,-18.54364 -30.39652,-20.09638 0,-0.59291 -2.58766,-2.85836 -5.75034,-5.03434 -3.16269,-2.17599 -10.28033,-8.34708 -15.81698,-13.71351 -5.53665,-5.36645 -12.37649,-11.67881 -15.19965,-14.02746 -4.27422,-3.55583 -15.62338,-17.18307 -21.15043,-25.39586 -19.35852,-28.7654 -26.56041,-42.74306 -29.11264,-56.50282 -0.92642,-4.99458 -3.19478,-12.01489 -5.04079,-15.60069 -1.84601,-3.58582 -3.94578,-10.44382 -4.66616,-15.24 -0.72037,-4.7962 -2.46788,-13.57811 -3.88335,-19.51536 -4.13975,-17.36434 -3.16975,-57.20174 1.61029,-66.13332 2.88016,-5.38162 6.24149,-21.21668 6.27016,-29.53836 0.0337,-9.77148 4.01537,-22.97915 10.27845,-34.094532 2.09457,-3.71733 5.8204,-11.04504 8.27961,-16.28379 6.3063,-13.43398 9.43983,-19.19274 12.48315,-22.94134 1.45403,-1.79098 3.78668,-4.86915 5.18368,-6.84038 13.2539,-18.701794 42.74355,-46.195291 65.405,-60.977726 3.14325,-2.050394 6.858,-4.706309 8.255,-5.902033 4.37479,-3.744486 32.85339,-19.180461 48.89499,-26.50207 15.29796,-6.982205 20.24447,-8.811068 33.02,-12.20843 6.63575,-1.764627 15.21704,-4.307188 19.06954,-5.650136 14.60206,-5.090151 53.40407,-7.646122 76.18045,-5.018168 46.71094,5.389537 66.81678,11.37283 107.94999,32.124817 55.2528,27.87541 104.06599,73.357646 119.8726,111.692596 1.51214,3.66733 4.82739,10.38262 7.3672,14.92287 6.84988,12.245052 12.62071,29.713852 12.35964,37.413712 -0.25792,7.60657 4.13626,28.06281 7.2972,33.97077 7.0731,13.21999 1.12187,71.72544 -10.88931,107.05052 C 234.9876,366.40155 132.26579,441.81479 25.91503,446.64529 15.55341,447.11591 5.3612,447.5836 3.2657,447.6846 c -2.0955,0.10097 -10.668,-0.21176 -19.04999,-0.69499 z m -29.845,-106.3718 c 0,-0.65727 -0.59887,-1.56517 -1.33082,-2.01755 -1.86399,-1.152 -5.34821,0.38276 -4.38973,1.93363 0.99894,1.6163 5.72055,1.68557 5.72055,0.0839 z M 26.7607,310.80742 c 14.31925,-0.4926 30.32125,-0.84973 35.56,-0.79363 8.59019,0.0919 18.07449,-1.16323 39.5742,-5.23753 6.64755,-1.25975 7.24715,-1.66211 7.95821,-5.3404 0.67458,-3.48956 5.23677,-9.06262 14.45858,-17.66224 2.68618,-2.50494 22.55721,-11.02106 25.801,-11.05752 1.13685,-0.0128 2.85135,-0.97298 3.81,-2.13377 2.37468,-2.87537 -6.05792,-1.82499 -14.17227,1.76534 -3.1211,1.38097 -6.53198,2.88325 -7.57973,3.3384 -1.04774,0.45514 -4.76249,2.52638 -8.25499,4.60276 -12.20454,7.25589 -13.46372,7.75161 -21.6258,8.5138 -8.2192,0.76752 -8.2192,0.76752 -8.2192,-2.61835 0,-7.27099 -10.99183,-6.9041 -13.32426,0.44474 -0.52555,1.65587 -1.91875,2.57383 -3.90633,2.57383 -3.13552,0 -4.13489,-1.74395 -1.81941,-3.175 2.48529,-1.53598 1.21277,-3.175 -2.46505,-3.175 -4.43889,0 -5.99462,1.90301 -3.75845,4.59743 2.53994,3.06043 -0.81661,4.36079 -10.2865,3.98508 -8.61108,-0.34163 -8.43307,-0.17414 -6.69729,-6.30173 0.52575,-1.85604 -0.0839,-2.057 -4.28233,-1.41127 -6.03327,0.92796 -8.42938,2.70048 -7.65478,5.66259 1.04503,3.99619 -2.08348,5.16386 -8.23748,3.07453 -3.06547,-1.04077 -8.98898,-1.86836 -13.16336,-1.83911 -4.17436,0.0292 -9.59001,0.0597 -12.03476,0.0676 -2.44475,0.008 -9.95568,-0.36784 -16.69094,-0.83501 -8.25906,-0.57285 -12.52154,-0.40347 -13.09246,0.52028 -0.46557,0.75331 -1.98415,1.36967 -3.37462,1.36967 -1.39046,0 -4.07135,0.78806 -5.95754,1.75124 -6.16183,3.14654 -15.10803,3.46522 -22.15109,0.78907 -4.38345,-1.66557 -7.10048,-2.06392 -8.60082,-1.26097 -3.954,2.11613 -22.08808,2.96498 -28.82332,1.34923 -5.58129,-1.33894 -7.25932,-1.20361 -14.24134,1.14846 -6.68057,2.25053 -8.56001,2.43285 -12.05404,1.16939 -6.55582,-2.37061 -16.84036,0.4245 -12.17382,3.30858 0.6985,0.4317 1.27,1.67415 1.27,2.76102 0,1.75067 -0.32598,1.76509 -2.8575,0.12649 -3.09741,-2.00494 -11.71868,-3.76296 -12.85939,-2.62225 -3.6608,3.6608 11.19957,8.97622 20.63722,7.38174 5.08356,-0.85887 21.32492,1.43331 22.39224,3.16027 0.34648,0.56061 1.73463,0.73042 3.08479,0.37734 1.35016,-0.35307 4.52868,0.0425 7.06337,0.879 3.96258,1.30777 4.94715,1.2145 7.0244,-0.66538 1.70046,-1.5389 3.46326,-1.9332 5.95287,-1.33155 10.60592,2.5631 17.62116,3.14261 31.43069,2.59645 19.58253,-0.7745 25.62019,-0.68673 28.60203,0.41577 2.75653,1.0192 9.42446,0.97995 45.73927,-0.26931 z m -50.26887,-24.37544 c 2.28669,-0.87748 2.67061,-3.75025 0.66669,-4.98874 -2.08618,-1.28933 -7.54781,0.52183 -7.54781,2.50298 0,2.59824 3.39346,3.8241 6.88112,2.48576 z m 48.79501,-7.54388 c 1.85672,-4.07504 1.84339,-4.38525 -0.1884,-4.38525 -3.09071,0 -6.13353,3.14414 -5.25599,5.43101 1.21692,3.17123 3.74419,2.68579 5.44439,-1.04576 z m 41.28717,-10.10501 c 2.51791,-6.12271 5.17213,-7.38636 6.3172,-3.00759 0.58672,2.24359 1.31297,2.55364 4.27562,1.82535 1.96288,-0.48253 6.71212,-1.55585 10.55387,-2.38516 8.48412,-1.83144 10.16,-2.82857 10.16,-6.04503 0,-1.36525 0.69274,-3.59153 1.53943,-4.94728 2.28063,-3.65187 0.34007,-4.5143 -3.44983,-1.53318 -2.63328,2.07134 -4.66913,2.50282 -9.90938,2.10021 -3.62737,-0.27869 -7.02385,-0.5294 -7.54772,-0.55712 -0.52388,-0.0277 -0.9525,-0.90769 -0.9525,-1.95544 0,-3.38127 -3.62461,-2.0459 -4.04315,1.48956 -0.22102,1.86702 -1.11662,3.62927 -1.99021,3.91612 -1.00696,0.33066 -1.30588,-0.21459 -0.81663,-1.48955 0.97835,-2.54954 -1.22357,-2.62014 -3.31001,-0.10618 -0.86956,1.04775 -2.6916,1.905 -4.049,1.905 -8.16243,0 -11.09344,6.43674 -5.59421,12.28535 5.13247,5.45856 6.01866,5.30828 8.81652,-1.49511 z m 44.69929,-3.48774 c 1.12554,-4.60969 -0.79022,-4.43216 -4.36621,0.40464 -3.62038,4.89683 -2.71745,9.43456 1.10299,5.54313 1.32576,-1.3504 2.79421,-4.02689 3.26322,-5.94777 z m -151.00327,-0.85384 c 2.44716,1.30966 3.35694,1.25477 5.20083,-0.31378 1.64431,-1.39877 4.91955,-1.83313 12.39485,-1.64383 5.588,0.14152 11.43058,-0.21962 12.98352,-0.80252 1.80594,-0.67786 4.73913,-0.38396 8.13878,0.8155 4.84381,1.709 5.52899,1.6819 7.72502,-0.30547 1.95005,-1.76478 2.86922,-1.89387 4.81818,-0.67673 1.65857,1.0358 4.21393,1.16677 8.2076,0.42067 3.18957,-0.59587 6.08331,-0.62369 6.43057,-0.0618 3.36033,5.43714 20.74923,1.92135 23.80131,-4.81229 2.83165,-6.24734 -3.88127,-11.0066 -7.4287,-5.26673 -0.38016,0.6151 -1.83419,1.11836 -3.23119,1.11836 -1.397,0 -2.1868,-0.5715 -1.7551,-1.27 0.4317,-0.6985 1.7936,-1.27 3.02644,-1.27 1.29395,0 3.2919,-1.99461 4.72625,-4.71835 3.2798,-6.22817 8.54316,-7.13018 55.98374,-9.5941 7.46434,-0.38767 9.05825,-1.31955 6.83631,-3.99682 -1.2946,-1.5599 -0.8084,-1.99869 3.00794,-2.71464 5.54739,-1.0407 8.78819,1.1486 7.97755,5.38917 -0.71181,3.72363 0.41512,3.69962 11.27733,-0.24026 4.81439,-1.74625 8.91889,-3.175 9.12111,-3.175 0.20224,0 -0.22392,-1.10543 -0.947,-2.45651 -0.72307,-1.35108 -1.31468,-2.77983 -1.31468,-3.175 0,-1.743 7.44791,-0.48036 12.28799,2.08317 4.66752,2.47213 5.68307,2.6053 8.47466,1.11129 1.7821,-0.95376 4.57239,-1.34586 6.36885,-0.89498 2.33737,0.58665 3.66048,0.14099 4.91994,-1.65714 1.16672,-1.66572 3.54811,-2.58969 7.37816,-2.86271 7.10253,-0.50627 8.81643,-4.85154 2.8668,-7.26819 -1.95877,-0.79563 -4.99015,-2.25406 -6.7364,-3.24097 -1.74625,-0.98689 -5.03701,-2.47415 -7.31281,-3.30501 -3.44219,-1.25669 -4.27012,-2.33803 -4.92482,-6.43229 -0.84577,-5.28917 -1.19532,-5.45835 -10.28745,-4.97931 -7.55236,0.39793 -15.63402,-3.47345 -14.76462,-7.07271 0.96745,-4.00519 -2.30376,-4.61447 -15.50309,-2.88759 -13.98971,1.83029 -17.53388,1.1 -13.05856,-2.69079 3.42474,-2.90089 0.1434,-7.43096 -3.96221,-5.47003 -1.06779,0.51 -6.22768,0.70029 -11.46643,0.42287 -15.50932,-0.8213 -25.79822,-0.69441 -29.66056,0.36584 -3.62948,0.99632 -3.80922,1.86628 -1.52553,7.38368 1.02295,2.47148 -2.28268,4.08819 -5.96765,2.91862 -2.05044,-0.65077 -2.37868,-1.57934 -1.80592,-5.10879 0.84732,-5.22141 -1.91153,-5.86012 -7.23274,-1.67445 -2.4899,1.95855 -4.33509,2.42373 -7.15627,1.80409 -4.27528,-0.93901 -4.89044,-2.43791 -1.57883,-3.84703 3.69001,-1.57012 -2.69729,-2.86425 -14.9225,-3.02345 -16.79003,-0.21863 -20.65995,-1.12522 -19.77939,-4.63365 0.87426,-3.48334 -1.11641,-4.41036 -5.73069,-2.66866 -3.4362,1.29703 -3.23215,1.37934 -18.68544,-7.53733 -4.8747,-2.81274 -7.86671,-0.62551 -7.8825,5.76226 -0.009,3.62946 -5.68016,7.69477 -10.73438,7.69477 -1.60413,0 -3.5815,0.80118 -4.39418,1.7804 -3.05046,3.67558 -7.65918,3.54245 -15.87181,-0.45847 -9.35182,-4.5559 -10.15668,-4.63224 -14.31902,-1.35814 -3.40596,2.67913 -25.54781,3.51275 -31.75074,1.19538 -6.25879,-2.33825 -21.92052,-2.87639 -24.22609,-0.83242 -1.21041,1.07306 -3.29639,2.1529 -4.6355,2.39963 -3.4598,0.63748 -4.10506,3.62362 -0.78301,3.62362 1.48989,0 4.32256,0.83445 6.29482,1.85435 1.97225,1.01989 3.86184,1.57842 4.19909,1.24117 1.21765,-1.21766 25.53542,3.15937 26.54897,4.77863 1.81478,2.8993 -3.92,6.21819 -9.28185,5.3717 -3.82441,-0.60377 -4.91384,-0.27502 -6.0358,1.82138 -0.90721,1.69513 -3.22505,2.83644 -6.87066,3.38314 -6.2467,0.93675 -7.26618,1.49333 -9.23266,5.04058 -1.73164,3.12361 -19.34182,6.14149 -28.87428,4.94821 -12.18787,-1.52566 -27.9954,1.63737 -19.20667,3.8432 1.77,0.44425 3.81335,2.11394 4.54076,3.71044 2.46428,5.40851 7.97998,6.92732 28.36748,7.81128 10.43978,0.45265 20.04894,1.39432 21.35367,2.09259 1.60996,0.86162 2.89789,0.83336 4.00801,-0.088 1.19869,-0.99483 3.1819,-0.53546 7.42234,1.71921 5.78659,3.07675 5.78659,3.07675 10.37038,0.61999 4.58379,-2.45676 4.58379,-2.45676 9.95897,0.41516 6.35533,3.39561 8.29362,3.62987 30.45769,3.681 1.57162,0.004 6.57225,0.2635 11.1125,0.57749 12.34565,0.85381 15.54795,0.71849 16.98414,-0.7177 1.76869,-1.76869 5.54888,-1.6498 6.16342,0.19384 0.35682,1.07044 7.55343,1.56761 25.22665,1.74277 13.60008,0.1348 26.47067,0.62796 28.60132,1.09593 2.13064,0.46797 6.67036,0.22416 10.08823,-0.54182 5.44411,-1.22005 6.41525,-1.1179 7.83529,0.82412 2.48744,3.40178 1.9819,4.26886 -2.50656,4.29919 -2.27012,0.0154 -6.43123,1.07437 -9.24691,2.35341 -3.69932,1.68043 -5.9751,2.00896 -8.20418,1.18438 -4.38477,-1.62203 -40.06075,-1.4176 -45.13988,0.25867 -2.91575,0.96228 -6.34622,1.01723 -10.97563,0.17582 -7.066,-1.28429 -10.43519,-0.3977 -13.33769,3.50977 -1.35346,1.8221 -1.57929,1.80223 -2.35838,-0.20744 -1.37652,-3.55084 -7.5044,-2.85326 -10.12948,1.15311 -2.17008,3.31197 -2.25064,3.32894 -4.27356,0.8995 -2.9525,-3.5458 -9.32957,-6.57551 -9.95049,-4.72743 -0.26844,0.79897 -1.48327,1.19243 -2.69962,0.87434 -3.0105,-0.78726 -6.05472,3.01813 -7.1068,8.88377 -0.81683,4.5541 -0.68676,4.83456 2.06165,4.445 2.2567,-0.31987 3.02366,-1.34622 3.32439,-4.44871 0.30524,-3.14901 3.79798,-8.73478 4.12699,-6.60008 0.0401,0.26015 0.33032,2.96369 0.64494,6.00786 0.79044,7.64784 0.0442,7.35949 15.89001,6.13963 10.75737,-0.82813 14.99487,-0.70784 17.23147,0.48916 z M 0.0907,196.39785 c 0,-1.09497 1.14617,-1.905 2.6955,-1.905 2.25202,0 2.43539,0.31344 1.1145,1.905 -0.86955,1.04775 -2.08253,1.905 -2.69549,1.905 -0.61298,0 -1.11451,-0.85725 -1.11451,-1.905 z m -7.62,-0.49912 c 0,-1.42753 3.30343,-2.75913 4.32084,-1.7417 1.52878,1.52877 0.69464,2.87582 -1.78084,2.87582 -1.397,0 -2.54,-0.51036 -2.54,-1.13412 z m 30.26511,-61.3457 c 4.42206,0.12611 9.27981,-0.44139 10.795,-1.26119 1.51519,-0.81979 8.75564,-1.5967 16.08989,-1.72646 29.1905,-0.51641 38.83059,-1.00526 40.95234,-2.07668 2.51938,-1.27221 18.16186,-2.2721 37.78766,-2.41545 23.59692,-0.17234 31.60594,-9.86982 12.06499,-14.60854 -6.65612,-1.61414 -35.26848,-2.23751 -50.97546,-1.11061 -7.7442,0.55562 -11.496,0.25663 -14.96432,-1.19253 -3.43282,-1.43432 -5.49323,-1.60542 -7.97416,-0.66217 -2.47938,0.94265 -3.62539,0.84823 -4.42731,-0.36484 -0.84516,-1.27843 -1.46518,-1.1361 -2.83247,0.65018 -1.94973,2.54724 -14.87857,2.29123 -15.74623,-0.31178 -0.56813,-1.70438 -4.04504,-1.76464 -4.04504,-0.0701 0,0.6985 -0.89098,1.27 -1.97995,1.27 -1.08898,0 -1.62675,-0.5715 -1.19505,-1.27 1.6605,-2.68673 -0.77303,-3.7055 -4.81058,-2.01389 -11.27206,4.72269 -20.81604,6.09106 -19.55453,2.80365 0.95529,-2.48948 -1.60397,-4.59976 -5.57839,-4.59976 -3.3111,0 -6.93787,2.7003 -5.52473,4.11343 0.3876,0.3876 -2.31853,0.43985 -6.01362,0.11608 -3.69509,-0.32374 -9.0178,10e-4 -11.82825,0.72353 -4.8354,1.24167 -31.70614,3.11942 -41.30489,2.88644 -6.9712,-0.1692 -46.76889,-0.0669 -55.4672,0.14257 -4.3132,0.10389 -7.56559,0.6364 -7.22754,1.18339 0.58729,0.95024 -4.09945,1.52861 -8.68145,1.07131 -1.18866,-0.11862 -3.5924,0.72207 -5.34165,1.86823 -1.74926,1.14615 -3.83679,1.67827 -4.63899,1.18249 -3.47137,-2.14543 -24.68423,3.01475 -22.27937,5.41961 2.3589,2.35889 60.20309,4.78572 70.22284,2.94617 2.9269,-0.53736 5.7844,-0.57314 6.35,-0.0795 2.99706,2.61573 28.6403,6.76546 32.711,5.29348 5.77245,-2.08734 38.94037,-0.81597 45.78735,1.75509 3.7369,1.40321 7.26255,1.66972 13.335,1.008 4.54025,-0.49475 11.87306,-0.79634 16.29511,-0.67019 z m -43.93734,-28.17364 c 3.08226,-2.34055 1.50539,-3.32974 -5.27193,-3.3071 -7.59347,0.0254 -10.85678,1.87654 -6.42526,3.64484 4.41493,1.76168 9.11101,1.6261 11.69719,-0.33774 z M -37.37429,75.112848 c 2.78714,-3.07976 4.32073,-3.19541 6.30936,-0.47581 1.8639,2.54905 15.42837,3.45002 22.89568,1.52078 2.79128,-0.72116 6.07955,-1.50465 7.30728,-1.74108 1.22772,-0.23644 2.80158,-0.78176 3.49745,-1.21184 0.69587,-0.43007 1.26522,0.13904 1.26522,1.2647 0,1.67255 1.45088,2.0318 7.9375,1.96547 4.36563,-0.0447 12.5095,-0.0919 18.0975,-0.1049 8.65683,-0.0202 10.44747,-0.40028 12.10303,-2.56868 1.886,-2.47025 2.13611,-2.48254 8.52459,-0.4193 6.88877,2.22483 16.75604,-0.577 11.63557,-3.30394 -3.65304,-1.94545 -11.31803,-2.62945 -13.91157,-1.24143 -2.00687,1.07405 -3.12259,1.01099 -4.65798,-0.26329 -2.47173,-2.05135 -58.27735,-2.67403 -68.30363,-0.76213 -3.4925,0.66599 -8.0645,1.1092 -10.16,0.98495 -30.99077,-1.83776 -44.13799,3.79529 -17.78,7.61798 10.75216,1.55939 12.84388,1.38625 15.24,-1.26143 z"
+         id="path12902"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#d2c693;stroke-width:1.26999998"
+         d="m -15.78429,446.98961 c -38.66981,-2.2294 -102.91432,-20.97427 -116.89265,-34.10622 -1.43008,-1.34348 -4.5872,-3.16784 -7.01582,-4.05412 -5.24202,-1.91296 -30.39652,-18.54364 -30.39652,-20.09638 0,-0.59291 -2.58766,-2.85836 -5.75034,-5.03434 -3.16269,-2.17599 -10.28033,-8.34708 -15.81698,-13.71351 -5.53665,-5.36645 -12.37649,-11.67881 -15.19965,-14.02746 -4.27422,-3.55583 -15.62338,-17.18307 -21.15043,-25.39586 -19.35852,-28.7654 -26.56041,-42.74306 -29.11264,-56.50282 -0.92642,-4.99458 -3.19478,-12.01489 -5.04079,-15.60069 -1.84601,-3.58582 -3.94578,-10.44382 -4.66616,-15.24 -0.72037,-4.7962 -2.46788,-13.57811 -3.88335,-19.51536 -4.13975,-17.36434 -3.16975,-57.20174 1.61029,-66.13332 2.88016,-5.38162 6.24149,-21.21668 6.27016,-29.53836 0.0337,-9.77148 4.01537,-22.97915 10.27845,-34.094532 2.09457,-3.71733 5.8204,-11.04504 8.27961,-16.28379 6.3063,-13.43398 9.43983,-19.19274 12.48315,-22.94134 1.45403,-1.79098 3.78668,-4.86915 5.18368,-6.84038 13.2539,-18.701794 42.74355,-46.195291 65.405,-60.977726 3.14325,-2.050394 6.858,-4.706309 8.255,-5.902033 4.37479,-3.744486 32.85339,-19.180461 48.89499,-26.50207 15.29796,-6.982205 20.24447,-8.811068 33.02,-12.20843 6.63575,-1.764627 15.21704,-4.307188 19.06954,-5.650136 14.60206,-5.090151 53.40407,-7.646122 76.18045,-5.018168 46.71094,5.389537 66.81678,11.37283 107.94999,32.124817 55.2528,27.87541 104.06599,73.357646 119.8726,111.692596 1.51214,3.66733 4.82739,10.38262 7.3672,14.92287 6.84988,12.245052 12.62071,29.713852 12.35964,37.413712 -0.25792,7.60657 4.13626,28.06281 7.2972,33.97077 7.0731,13.21999 1.12187,71.72544 -10.88931,107.05052 C 234.9876,366.40155 132.26579,441.81479 25.91503,446.64529 15.55341,447.11591 5.3612,447.5836 3.2657,447.6846 c -2.0955,0.10097 -10.668,-0.21176 -19.04999,-0.69499 z m -29.845,-106.3718 c 0,-0.65727 -0.59887,-1.56517 -1.33082,-2.01755 -1.86399,-1.152 -5.34821,0.38276 -4.38973,1.93363 0.99894,1.6163 5.72055,1.68557 5.72055,0.0839 z m 73.02499,-8.29937 c 0,-4.10039 -5.70992,-5.31028 -11.50885,-2.43864 -7.50132,3.71464 -6.7275,5.03566 2.9226,4.98931 7.69079,-0.037 8.58625,-0.30296 8.58625,-2.55067 z m 29.19541,-1.20869 c 2.49876,-1.6957 5.23876,-3.3743 6.0889,-3.73022 6.2992,-2.63719 -9.44138,-5.92933 -16.15163,-3.37811 -3.3321,1.26687 -4.5618,5.11144 -1.63492,5.11144 0.89251,0 2.60141,1.143 3.79756,2.54 2.69913,3.15234 2.42696,3.17106 7.90009,-0.54311 z M 13.11238,312.41581 c 6.45883,-0.71609 14.02932,-1.18256 16.82332,-1.0366 16.03867,0.83786 56.32733,0.0345 59.44703,-1.18537 1.96187,-0.76714 7.74244,-1.45756 12.84572,-1.53427 11.75531,-0.17671 12.78201,-0.77413 10.20563,-5.9386 -2.55543,-5.12248 -1.53714,-7.05952 8.13231,-15.46975 7.00895,-6.09619 25.84484,-16.55837 29.81138,-16.55837 4.02855,0 4.85589,-3.45191 1.01163,-4.22075 -4.46792,-0.89359 -12.01789,1.03034 -14.15675,3.6075 -0.85963,1.03578 -2.72863,1.88325 -4.15335,1.88325 -1.42469,0 -6.97007,2.56765 -12.32305,5.70589 -5.55098,3.25431 -12.6306,6.25546 -16.47746,6.985 -8.7935,1.66765 -8.3124,1.75752 -9.96105,-1.86085 -1.8404,-4.03924 -6.84135,-5.34248 -10.54534,-2.7481 -1.99831,1.39966 -4.40636,1.75471 -8.29039,1.22234 -6.05456,-0.82987 -14.58098,1.73342 -11.6963,3.51625 2.40702,1.48761 0.95238,2.3108 -5.69288,3.22163 -4.05851,0.55629 -5.93213,0.35744 -5.93213,-0.62954 0,-0.79343 0.5715,-1.44262 1.27,-1.44262 0.6985,0 1.27,-1.17801 1.27,-2.61781 0,-2.39888 -0.50453,-2.5448 -6.0325,-1.7448 -6.8573,0.99239 -9.2075,2.42927 -9.2075,5.62936 0,2.71385 -6.71859,4.38503 -10.18047,2.53229 -1.2939,-0.69246 -3.69802,-1.25904 -5.34251,-1.25904 -1.64447,0 -5.0978,-1.07463 -7.67405,-2.38809 -3.91138,-1.99413 -4.92065,-2.10307 -6.118,-0.66035 -0.78864,0.95025 -2.65926,1.75461 -4.15693,1.78745 -1.49766,0.0329 -4.15179,0.0954 -5.89804,0.13906 -13.96126,0.34889 -20.20591,-0.62197 -20.57789,-3.19924 -1.00918,-6.992 -11.8071,-5.42332 -11.8071,1.71529 0,9.00187 -23.16887,10.63676 -26.53213,1.87221 -0.92716,-2.41613 -2.79425,-2.27797 -8.94441,0.66188 -6.64045,3.17423 -9.10411,3.40878 -23.03137,2.19272 -11.77412,-1.02807 -16.09331,-0.21168 -21.13943,3.99561 -1.89268,1.57805 -2.83056,1.55402 -7.17385,-0.18381 -6.52857,-2.61222 -16.59869,0.50527 -11.60381,3.59227 0.6985,0.4317 1.27,1.67416 1.27,2.76102 0,1.75067 -0.32598,1.7651 -2.8575,0.12649 -3.09741,-2.00493 -11.71868,-3.76295 -12.85939,-2.62224 -3.67345,3.67345 11.20857,8.9747 20.69509,7.37196 4.43605,-0.74947 19.46782,1.1483 22.13061,2.794 1.88972,1.16791 11.1793,1.90665 21.43818,1.70482 5.3966,-0.10617 10.82585,0.34759 12.065,1.00835 1.23916,0.66076 16.79166,1.55848 34.56112,1.99494 17.76946,0.43648 32.5653,1.05076 32.87964,1.36512 0.31434,0.31433 1.06519,0.26642 1.66856,-0.10643 0.60337,-0.37291 6.38152,-1.2639 12.84036,-1.97999 z M 31.8407,283.94227 c 0,-1.31258 -0.75698,-1.75503 -2.2225,-1.29899 -3.66327,1.13992 -5.90447,-1.01011 -4.25862,-4.08542 2.51335,-4.69624 0.3724,-5.60723 -4.74842,-2.02047 -2.90472,2.03454 -2.90472,2.03454 0.66991,5.67972 3.98455,4.06319 10.55963,5.13738 10.55963,1.72516 z m 11.96316,-5.53329 c 3.86518,-3.63168 4.24434,-6.44613 0.8684,-6.44613 -1.85802,0 -10.29156,7.62976 -10.29156,9.3107 0,1.63601 6.8492,-0.44611 9.42316,-2.86457 z m 35.73668,-1.48722 c 0.47286,-0.76511 0.11811,-2.77691 -0.78839,-4.47067 -2.32498,-4.34426 -0.40166,-5.7935 9.9226,-7.4767 7.6938,-1.25436 8.60884,-1.7156 8.94128,-4.50694 0.20368,-1.71028 1.01417,-4.39461 1.80108,-5.96518 2.02415,-4.03993 0.44253,-4.87143 -3.44681,-1.81207 -2.63328,2.07134 -4.66913,2.50282 -9.90938,2.10021 -3.62737,-0.27869 -7.02385,-0.5294 -7.54772,-0.55712 -0.52388,-0.0277 -0.9525,-0.90769 -0.9525,-1.95544 0,-3.38127 -3.62461,-2.0459 -4.04315,1.48956 -0.22102,1.86702 -1.11662,3.62927 -1.99021,3.91612 -1.00696,0.33066 -1.30588,-0.21459 -0.81663,-1.48955 0.97835,-2.54954 -1.22357,-2.62014 -3.31001,-0.10618 -0.86956,1.04775 -2.76619,1.905 -4.21474,1.905 -3.82365,0 -12.45966,6.29722 -11.31858,8.25329 3.41263,5.85001 14.23074,11.86471 14.35994,7.98389 0.19808,-5.94981 3.50455,-12.53298 3.61676,-7.20095 0.15209,7.22753 7.01578,14.23011 9.69646,9.89268 z m 32.31016,-12.64972 c 0,-2.9438 -0.0494,-2.95181 -4.37084,-0.70983 -6.42684,3.3343 -8.64659,7.94466 -4.20166,8.72679 3.369,0.59283 8.5725,-4.27346 8.5725,-8.01696 z m -151.58067,0.16947 c 2.44716,1.30966 3.35694,1.25477 5.20083,-0.31378 1.64431,-1.39877 4.91955,-1.83313 12.39485,-1.64383 5.588,0.14152 11.43058,-0.21962 12.98352,-0.80252 1.80594,-0.67786 4.73913,-0.38396 8.13878,0.8155 4.84381,1.709 5.52899,1.6819 7.72502,-0.30547 1.95005,-1.76478 2.86922,-1.89387 4.81818,-0.67673 1.65857,1.0358 4.21393,1.16677 8.2076,0.42067 3.18957,-0.59587 6.08331,-0.62369 6.43057,-0.0618 3.36033,5.43714 20.74923,1.92135 23.80131,-4.81229 2.83165,-6.24734 -3.88127,-11.0066 -7.4287,-5.26673 -0.38016,0.6151 -1.83419,1.11836 -3.23119,1.11836 -3.68846,0 -1.82809,-2.08091 2.66347,-2.97922 2.99112,-0.59822 3.83643,-1.43958 3.83643,-3.81853 0,-5.78742 3.75669,-6.81905 31.115,-8.5446 9.779,-0.61677 21.36017,-1.35447 25.73592,-1.63931 4.37575,-0.28485 8.80165,-1.2198 9.83533,-2.07767 1.53357,-1.27276 2.31084,-1.02698 4.22399,1.33568 2.20528,2.72339 2.67738,2.801 7.94468,1.30593 3.08004,-0.87423 7.88607,-1.96702 10.68007,-2.42841 3.98735,-0.65844 5.16729,-1.45349 5.48583,-3.69637 0.50517,-3.55695 3.22049,-3.64856 9.73286,-0.32837 4.95364,2.52551 4.9646,2.52551 7.65288,0 3.22463,-3.02938 4.85911,-3.17174 6.98642,-0.60847 1.96157,2.36353 5.06701,0.31274 5.06701,-3.34616 0,-3.42961 8.11747,-3.31578 11.08968,0.1555 2.2191,2.59172 6.69032,3.49767 6.69032,1.35559 0,-0.65144 -1.143,-1.54719 -2.54,-1.99059 -3.54583,-1.1254 -3.19326,-2.44502 1.75664,-6.57504 8.01785,-6.68982 7.26925,-16.67289 -1.13207,-15.0968 -2.48519,0.46623 -5.51936,-0.019 -8.0675,-1.29018 -4.76427,-2.37672 -7.79707,-1.64484 -7.79707,1.8816 0,2.99307 -4.91712,4.98388 -6.39671,2.58986 -0.54978,-0.88958 -0.45603,-3.0471 0.20833,-4.79451 3.77049,-9.91716 -9.28419,-22.42071 -22.83653,-21.87241 -5.34055,0.21607 -11.71033,0.35535 -14.15508,0.30951 -2.44475,-0.0458 -29.13709,-0.36484 -59.31632,-0.70892 -62.98752,-0.71815 -66.94793,-1.03658 -65.87866,-5.29686 0.88013,-3.50675 -1.09912,-4.43705 -5.7251,-2.69094 -3.4362,1.29703 -3.23215,1.37934 -18.68544,-7.53733 -4.8747,-2.81273 -7.86671,-0.6255 -7.8825,5.76227 -0.009,3.62945 -5.68016,7.69476 -10.73438,7.69476 -1.60413,0 -3.5815,0.80118 -4.39418,1.7804 -3.05046,3.67559 -7.65918,3.54245 -15.87181,-0.45847 -9.35182,-4.55589 -10.15668,-4.63223 -14.31902,-1.35814 -3.40596,2.67913 -25.54781,3.51275 -31.75074,1.19538 -5.80261,-2.16781 -21.82568,-2.86978 -24.01582,-1.05213 -1.14218,0.94794 -5.86667,2.92377 -10.49886,4.39074 -8.42215,2.66722 -8.42215,2.66722 -2.07215,3.58919 8.52178,1.23731 18.55763,4.82014 18.01993,6.43321 -0.23909,0.71728 0.70454,2.22667 2.09696,3.35417 3.44286,2.78785 1.9825,5.79685 -3.18855,6.56981 -4.54008,0.67867 -6.82205,-1.12905 -4.68137,-3.70844 2.0496,-2.46961 0.12129,-4.47741 -2.79905,-2.91447 -3.21093,1.71844 -3.16436,5.91496 0.0771,6.94375 6.63327,2.10532 1.8489,6.45828 -8.255,7.51066 -4.191,0.43651 -9.98199,1.12196 -12.86887,1.52322 -3.2005,0.44486 -5.58392,0.18743 -6.10745,-0.65966 -0.47223,-0.76407 -2.14439,-1.38922 -3.71591,-1.38922 -1.57152,0 -3.96667,-0.83907 -5.32255,-1.86461 -3.65538,-2.76479 -8.18023,-2.48606 -8.18023,0.5039 0,1.34711 -0.92529,3.37456 -2.05619,4.50547 -1.13091,1.13091 -1.84528,2.19901 -1.5875,2.37357 0.25778,0.17455 1.98365,0.96652 3.83527,1.75992 2.55818,1.09616 3.57276,2.663 4.22527,6.52515 1.24883,7.39177 17.75834,12.70474 23.04644,7.41664 1.34108,-1.34108 7.40137,-1.08737 8.85966,0.37092 1.16896,1.16895 14.82129,2.14047 40.51204,2.88287 5.588,0.16148 11.01725,0.84789 12.065,1.52533 1.71309,1.10765 39.77072,2.47471 50.8,1.82478 2.44475,-0.14406 5.87375,-0.19373 7.62,-0.11036 1.74625,0.0833 14.3433,0.38263 27.99345,0.66508 13.65015,0.28246 26.5549,0.89495 28.67722,1.36107 2.12232,0.46614 6.65522,0.22084 10.07309,-0.54513 5.44411,-1.22005 6.41525,-1.11791 7.83529,0.82411 2.48744,3.40179 1.9819,4.26887 -2.50656,4.2992 -2.27012,0.0154 -6.43123,1.07436 -9.24691,2.35341 -3.69932,1.68042 -5.9751,2.00896 -8.20418,1.18437 -4.38477,-1.62203 -40.06075,-1.4176 -45.13988,0.25868 -2.91381,0.96164 -6.34636,1.0172 -10.96622,0.17752 -7.08716,-1.28812 -13.70863,0.61649 -13.79569,3.96824 -0.0236,0.91098 -0.73906,0.6345 -1.72752,-0.66761 -3.01374,-3.97013 -10.78058,-2.20743 -12.59074,2.8575 -0.44003,1.23125 -0.71629,0.89437 -0.78108,-0.9525 -0.0757,-2.15554 -0.86183,-2.8575 -3.20032,-2.8575 -1.70502,0 -3.45325,-0.5715 -3.88495,-1.27 -1.82363,-2.9507 -13.7828,-0.53163 -16.76649,3.39147 -1.51887,1.9971 -2.96427,1.77724 -6.87339,-1.0455 -2.93145,-2.1168 -6.20512,-0.41625 -6.20512,3.22332 0,3.93901 1.99553,4.62088 7.7066,2.63336 5.44626,-1.89538 8.87513,-0.49628 7.90217,3.22434 -0.50109,1.91617 -0.0408,2.54299 1.8676,2.54299 3.42064,0 5.77741,-3.10861 4.76797,-6.28905 -0.57396,-1.80839 -0.262,-2.60095 1.02374,-2.60095 1.13073,0 2.19569,1.84669 2.74087,4.75283 0.49041,2.61406 1.08104,5.05928 1.31253,5.43384 0.23149,0.37457 6.84036,0.18683 14.68637,-0.41718 10.75737,-0.82813 14.99487,-0.70783 17.23147,0.48917 z m -79.46548,-70.47059 c -2.01371,-2.42637 0.11938,-3.59063 4.61026,-2.51634 5.05778,1.20992 5.96859,1.93274 4.00399,3.17758 -2.63375,1.66883 -6.9558,1.33707 -8.61425,-0.66124 z m 14.11907,59.86044 c 3.12067,-4.91781 -8.90723,-7.84638 -13.70685,-3.33736 -5.16758,4.85468 -4.01733,6.15973 4.83751,5.48848 4.33594,-0.32869 8.32716,-1.29669 8.86934,-2.15112 z m -63.5015,-54.57601 c 3.3751,-2.68318 4.19257,-12.3825 1.04361,-12.3825 -1.44773,0 -2.55501,0.84286 -2.55501,1.94486 0,1.06968 -0.8355,2.26547 -1.85666,2.65733 -2.62309,1.00656 -3.71811,5.3364 -1.90276,7.52376 1.87138,2.25489 2.70607,2.29551 5.27082,0.25655 z M 22.73581,134.55303 c 4.42206,0.12611 9.27313,-0.43053 10.78015,-1.23707 1.72192,-0.92154 12.18344,-1.6258 28.15488,-1.89536 13.97818,-0.23591 27.41511,-0.79035 29.85986,-1.23208 2.44475,-0.44172 14.44625,-0.94744 26.67,-1.12383 27.38777,-0.39519 24.66249,-0.0497 28.50956,-3.61497 9.09394,-8.42797 -0.0861,-14.56472 -20.88075,-13.95853 -9.49046,0.27666 -11.13025,-0.009 -13.65039,-2.37635 -2.75981,-2.59272 -3.36186,-2.63957 -16.51881,-1.28567 -16.83597,1.73249 -57.00222,1.27907 -58.94673,-0.66544 -0.99204,-0.99204 -2.49295,-0.91411 -5.34845,0.27768 -11.20703,4.67746 -20.69909,6.02049 -19.44454,2.7512 0.95529,-2.48948 -1.60397,-4.59976 -5.57839,-4.59976 -3.32661,0 -6.93556,2.70261 -5.5111,4.12706 0.3951,0.39509 -2.03874,0.38672 -5.40852,-0.0186 -3.36978,-0.40532 -8.26082,-0.65208 -10.86898,-0.54833 -4.19242,0.16678 -4.70766,-0.13904 -4.445,-2.63824 0.26473,-2.51886 -0.35547,-2.86886 -5.69216,-3.21219 -7.26297,-0.46726 -12.04967,1.58793 -9.85566,4.23156 1.71909,2.07136 -3.34908,3.89551 -7.67796,2.76348 -1.38993,-0.36347 -5.52413,0.15093 -9.18713,1.14312 -5.31209,1.43887 -8.3847,1.52222 -15.1819,0.41184 -4.68705,-0.76567 -9.1664,-0.9938 -9.95412,-0.50697 -0.7877,0.48683 -13.46534,1.05918 -28.17252,1.2719 -24.71863,0.35751 -27.00938,0.58567 -30.29903,3.01782 -2.01514,1.48985 -4.45224,2.28819 -5.61874,1.84056 -1.133,-0.43477 -3.30733,-0.1548 -4.83184,0.62216 -1.52451,0.77696 -5.58356,1.8586 -9.02012,2.40363 -16.71923,2.65164 0.63718,8.8504 25.57397,9.13361 8.92888,0.10147 18.36738,0.51979 20.97444,0.92973 2.60707,0.40994 9.36778,0.0254 15.02379,-0.85441 9.48791,-1.47595 11.31529,-1.37334 23.61586,1.32613 11.46633,2.51639 14.16267,2.71912 19.2662,1.44861 7.9458,-1.97806 37.22731,-0.82838 44.03402,1.72891 3.73793,1.40434 7.26087,1.6709 13.335,1.00901 4.54025,-0.49476 11.87306,-0.79635 16.29511,-0.6702 z m -44.2351,-52.455182 c 1.88376,-2.26979 6.51925,-2.49775 8.59436,-0.42263 1.12982,1.12982 1.6756,0.9788 2.29488,-0.635 1.02944,-2.68268 6.69526,-2.76377 12.86905,-0.1842 4.17419,1.74408 5.2326,1.73191 10.82158,-0.12446 5.38837,-1.78974 7.26996,-1.84393 14.45257,-0.41626 7.21444,1.43401 9.27794,1.36951 16.33356,-0.51055 4.44174,-1.18355 9.41084,-2.18354 11.04244,-2.22218 4.10099,-0.0972 9.75391,-7.07484 6.47942,-7.99788 -6.61534,-1.86476 -10.6198,-2.11596 -13.04456,-0.81827 -2.02937,1.08609 -3.14249,1.07185 -4.50335,-0.0575 -0.99423,-0.82513 -6.52981,-1.69917 -12.30132,-1.94229 -12.01477,-0.50613 -19.55022,-1.37434 -21.29674,-2.45375 -0.66517,-0.41109 -2.40669,-0.10668 -3.87004,0.67649 -1.46335,0.78316 -9.61903,1.60288 -18.12374,1.8216 -8.8408,0.22735 -16.93432,1.07857 -18.89839,1.98757 -2.7568,1.27591 -4.20296,1.27193 -7.32227,-0.0202 -3.67894,-1.52387 -20.88625,-1.0321 -27.02495,0.77235 -3.08291,0.90621 -2.7972,4.29213 0.36218,4.29213 1.42157,0 3.85045,0.77005 5.3975,1.71122 1.54705,0.94117 8.24207,2.43252 14.87782,3.31412 6.63575,0.88158 13.49375,2.37466 15.24,3.31792 4.14954,2.24147 5.70142,2.22348 7.62,-0.0883 z m 72.16082,-18.647 c -0.45954,-3.13888 -6.17908,-5.31903 -10.97355,-4.18286 -4.24759,1.00659 -2.69213,3.70894 2.41052,4.18791 2.39097,0.22442 4.89013,0.9388 5.5537,1.5875 2.06757,2.02122 3.43263,1.29882 3.00933,-1.59255 z M 21.6807,59.630118 c 0,-1.87372 -3.5246,-1.06282 -4.31325,0.99233 -1.25502,3.27057 0.66551,5.02352 2.60211,2.37506 0.94113,-1.28707 1.71114,-2.8024 1.71114,-3.36739 z"
+         id="path12900"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#bbc3ad;stroke-width:1.26999998"
+         d="m -15.78429,446.98961 c -38.66981,-2.2294 -102.91432,-20.97427 -116.89265,-34.10622 -1.43008,-1.34348 -4.5872,-3.16784 -7.01582,-4.05412 -5.24202,-1.91296 -30.39652,-18.54364 -30.39652,-20.09638 0,-0.59291 -2.58766,-2.85836 -5.75034,-5.03434 -3.16269,-2.17599 -10.28033,-8.34708 -15.81698,-13.71351 -5.53665,-5.36645 -12.37649,-11.67881 -15.19965,-14.02746 -4.27422,-3.55583 -15.62338,-17.18307 -21.15043,-25.39586 -19.35852,-28.7654 -26.56041,-42.74306 -29.11264,-56.50282 -0.92642,-4.99458 -3.19478,-12.01489 -5.04079,-15.60069 -1.84601,-3.58582 -3.94578,-10.44382 -4.66616,-15.24 -0.72037,-4.7962 -2.46788,-13.57811 -3.88335,-19.51536 -4.13975,-17.36434 -3.16975,-57.20174 1.61029,-66.13332 2.88016,-5.38162 6.24149,-21.21668 6.27016,-29.53836 0.0337,-9.77148 4.01537,-22.97915 10.27845,-34.094532 2.09457,-3.71733 5.8204,-11.04504 8.27961,-16.28379 6.3063,-13.43398 9.43983,-19.19274 12.48315,-22.94134 1.45403,-1.79098 3.78668,-4.86915 5.18368,-6.84038 13.2539,-18.701794 42.74355,-46.195291 65.405,-60.977726 3.14325,-2.050394 6.858,-4.706309 8.255,-5.902033 4.37479,-3.744486 32.85339,-19.180461 48.89499,-26.50207 15.29796,-6.982205 20.24447,-8.811068 33.02,-12.20843 6.63575,-1.764627 15.21704,-4.307188 19.06954,-5.650136 14.60206,-5.090151 53.40407,-7.646122 76.18045,-5.018168 46.71094,5.389537 66.81678,11.37283 107.94999,32.124817 55.2528,27.87541 104.06599,73.357646 119.8726,111.692596 1.51214,3.66733 4.82739,10.38262 7.3672,14.92287 6.84988,12.245052 12.62071,29.713852 12.35964,37.413712 -0.25792,7.60657 4.13626,28.06281 7.2972,33.97077 7.0731,13.21999 1.12187,71.72544 -10.88931,107.05052 C 234.9876,366.40155 132.26579,441.81479 25.91503,446.64529 15.55341,447.11591 5.3612,447.5836 3.2657,447.6846 c -2.0955,0.10097 -10.668,-0.21176 -19.04999,-0.69499 z m -29.845,-106.3718 c 0,-0.65727 -0.59887,-1.56517 -1.33082,-2.01755 -1.86399,-1.152 -5.34821,0.38276 -4.38973,1.93363 0.99894,1.6163 5.72055,1.68557 5.72055,0.0839 z m 73.02499,-8.29937 c 0,-4.10039 -5.70992,-5.31028 -11.50885,-2.43864 -7.50132,3.71464 -6.7275,5.03566 2.9226,4.98931 7.69079,-0.037 8.58625,-0.30296 8.58625,-2.55067 z m 29.19541,-1.20869 c 2.49876,-1.6957 5.23876,-3.3743 6.0889,-3.73022 1.06423,-0.44554 0.77781,-1.26534 -0.91955,-2.63189 -2.80648,-2.25952 -12.32042,-2.65715 -16.03325,-0.67011 -2.75097,1.47228 -3.34095,5.03533 -0.83375,5.03533 0.89251,0 2.60141,1.143 3.79756,2.54 2.69913,3.15234 2.42696,3.17106 7.90009,-0.54311 z M 8.9807,314.30436 c 5.23875,-0.24861 17.81175,-0.70313 27.94,-1.01002 42.07872,-1.27502 49.06177,-1.68163 52.46203,-3.05479 1.96187,-0.79228 7.74244,-1.50327 12.84572,-1.57998 11.75531,-0.17671 12.78201,-0.77413 10.20563,-5.9386 -2.41127,-4.8335 -1.45139,-7.11524 5.40059,-12.83779 2.64606,-2.20992 5.96784,-5.13911 7.38175,-6.50931 2.9408,-2.84995 22.44737,-12.68102 25.16135,-12.68102 0.99106,0 2.45256,-0.78398 3.2478,-1.74217 3.58623,-4.32116 -11.31147,-3.67854 -15.9666,0.68872 -1.52168,1.42761 -3.52193,2.32239 -4.445,1.98842 -1.56413,-0.56593 -14.16114,5.45772 -16.85477,8.05962 -1.6182,1.56309 -5.7785,1.48779 -5.7785,-0.10465 0,-2.28921 -3.37162,-1.33255 -6.93166,1.9668 -3.71781,3.44553 -6.51203,3.52093 -6.23516,0.16822 0.10414,-1.26075 -1.46824,-2.57666 -4.1275,-3.4543 -4.87628,-1.60931 -4.82591,-1.47908 -2.77055,-7.16305 1.06781,-2.95302 2.32448,-4.21767 4.191,-4.21767 1.46623,0 3.35167,-0.6858 4.18987,-1.524 1.18533,-1.18533 1.86267,-1.18533 3.048,0 1.18533,1.18533 1.18533,1.86267 0,3.048 -2.15865,2.15865 -1.90546,3.26249 1.11967,4.88147 3.61974,1.93724 12.59633,-3.79681 12.59633,-8.04625 0,-1.5811 1.20495,-3.90644 2.67766,-5.16741 2.06189,-1.76545 2.49997,-3.18111 1.905,-6.156 -0.42496,-2.12476 -0.77266,-4.64906 -0.77266,-5.6095 0,-2.37292 -2.47558,-2.19821 -5.31939,0.37539 -2.20797,1.99819 -2.55452,1.9203 -5.95599,-1.33849 -2.96572,-2.84135 -4.26775,-3.28825 -7.28061,-2.49897 -2.01796,0.52865 -4.68271,0.807 -5.92167,0.61856 -1.23897,-0.18843 -4.22891,0.87877 -6.64434,2.37159 -5.10728,3.15648 -6.978,2.50893 -6.978,-2.41542 0,-4.79922 4.17775,-6.58217 18.30188,-7.81069 8.27308,-0.71961 11.62046,-1.52683 12.4456,-3.00129 1.95443,-3.49239 3.92065,-2.32005 3.08835,1.84141 -1.11037,5.55187 2.00174,6.20411 9.89181,2.07314 3.56268,-1.86532 7.50625,-3.39148 8.76347,-3.39148 3.74586,0 7.19888,-2.45812 7.19888,-5.12468 0,-3.22698 3.12967,-3.21585 9.50369,0.0338 4.95364,2.52551 4.9646,2.52551 7.65288,0 3.22463,-3.02939 4.85911,-3.17174 6.98642,-0.60847 1.96157,2.36353 5.06701,0.31273 5.06701,-3.34616 0,-3.42961 8.11747,-3.31578 11.08968,0.1555 2.2191,2.59171 6.69032,3.49767 6.69032,1.35558 0,-0.65143 -1.143,-1.54719 -2.54,-1.99058 -3.55313,-1.12772 -3.19213,-2.45067 1.79418,-6.57505 8.10105,-6.70068 7.1031,-16.96913 -1.46624,-15.08697 -2.21765,0.48707 -5.16382,5.7e-4 -7.72058,-1.27492 -4.81048,-2.39975 -7.84736,-1.6813 -7.84736,1.85653 0,2.99307 -4.91712,4.98388 -6.39671,2.58986 -0.54978,-0.88958 -0.46127,-3.0333 0.19667,-4.76385 1.60471,-4.22072 -0.76837,-11.18508 -4.68593,-13.75197 -1.67161,-1.09528 -4.28592,-4.00849 -5.80955,-6.4738 -2.88154,-4.66241 -20.15128,-5.68744 -22.82888,-1.35497 -0.71754,1.16099 -1.69096,1.16572 -4.20459,0.0204 -1.93366,-0.88103 -5.5981,-1.17134 -8.96325,-0.71009 -3.4169,0.46834 -6.36113,0.22517 -7.36542,-0.60831 -2.81406,-2.33546 -48.31496,-2.74024 -57.24864,-0.50928 -4.67817,1.16826 -7.7504,1.40288 -8.25499,0.63042 -0.93029,-1.42418 -16.10915,-3.26915 -28.1087,-3.41658 -8.16787,-0.10033 -8.2513,-0.13726 -7.90449,-3.49786 0.45175,-4.37748 -2.34466,-5.30868 -12.76953,-4.25224 -6.62509,0.67137 -8.61877,0.37814 -12.34597,-1.81584 -9.4409,-5.55726 -9.68214,-5.61342 -13.17562,-3.0674 -1.79001,1.30456 -3.27252,3.64462 -3.29448,5.20014 -0.0541,3.83827 -5.40913,7.21829 -9.72129,6.13601 -2.49617,-0.62651 -4.11947,-0.11773 -6.36965,1.99614 -3.01975,2.83692 -3.01975,2.83692 -6.55829,0.22077 -2.2093,-1.63341 -6.61827,-2.95807 -11.7371,-3.52639 -4.50921,-0.50062 -9.46434,-1.71336 -11.01139,-2.69499 -1.54705,-0.98162 -4.1188,-1.80294 -5.715,-1.82515 -1.5962,-0.0222 -4.01154,-0.87948 -5.36741,-1.905 -5.61897,-4.24997 -8.11555,-2.39208 -4.58559,3.41246 4.12665,6.78572 -5.9442,8.84466 -17.53536,3.58504 -7.1959,-3.26523 -12.985,-3.17705 -19.59691,0.29846 -2.9408,1.5458 -8.73353,3.97468 -12.87274,5.3975 -9.94749,3.41935 -10.25856,5.19504 -1.09242,6.23588 8.89895,1.0105 19.61631,4.63188 19.01819,6.42622 -0.23909,0.71729 0.70454,2.22667 2.09696,3.35417 3.44286,2.78786 1.9825,5.79686 -3.18855,6.56982 -4.54008,0.67866 -6.82205,-1.12906 -4.68137,-3.70844 2.0496,-2.46962 0.12129,-4.47741 -2.79905,-2.91447 -3.21093,1.71843 -3.16436,5.91496 0.0771,6.94375 6.63327,2.10531 1.8489,6.45828 -8.255,7.51065 -4.191,0.43651 -9.98199,1.12197 -12.86887,1.52322 -3.2005,0.44486 -5.58392,0.18743 -6.10745,-0.65966 -0.47223,-0.76408 -2.14439,-1.38923 -3.71591,-1.38923 -1.57152,0 -3.96667,-0.83907 -5.32255,-1.86461 -3.65538,-2.76479 -8.18023,-2.48606 -8.18023,0.5039 0,1.34711 -0.92529,3.37456 -2.05619,4.50547 -1.13091,1.13091 -1.84528,2.19901 -1.5875,2.37357 0.25778,0.17455 1.98365,0.96652 3.83526,1.75992 2.55819,1.09616 3.57277,2.663 4.22528,6.52515 1.24883,7.39177 17.75834,12.70474 23.04644,7.41664 1.56593,-1.56594 6.80044,-0.9758 10.01247,1.1288 2.33119,1.52746 4.64019,1.81652 9.34336,1.16967 4.00474,-0.5508 6.55154,-0.36634 7.09556,0.51391 0.51969,0.84086 2.78531,1.06339 5.84509,0.57411 3.11293,-0.49777 5.67874,-0.23272 6.80635,0.70311 1.39461,1.15743 3.55983,1.14063 9.43675,-0.0733 8.55375,-1.76674 12.35576,-0.32286 10.93521,4.15289 -0.89553,2.82157 5.02558,3.33629 8.42993,0.73282 2.84856,-2.17845 22.74198,-1.3549 22.74198,0.94147 0,1.82912 -3.85084,3.18227 -9.13255,3.20908 -1.87965,0.0102 -3.77075,0.58883 -4.20245,1.28733 -0.4317,0.6985 -1.93175,1.27 -3.33345,1.27 -1.40169,0 -3.79684,0.70718 -5.32254,1.57151 -1.5257,0.86433 -5.56621,1.5787 -8.97889,1.5875 -6.07328,0.0156 -8.77302,3.52255 -10.90436,14.16454 -0.0197,0.0982 1.82162,0.47366 4.09174,0.83434 2.27013,0.36068 6.4135,1.26681 9.2075,2.01365 2.794,0.74682 12.08087,1.3715 20.6375,1.38816 14.54274,0.0283 15.5575,0.19109 15.5575,2.49535 0,3.80013 2.0471,4.19138 4.25992,0.81418 2.8232,-4.30874 36.94365,-7.03365 40.72495,-3.25235 0.68372,0.68371 3.1767,1.24312 5.53995,1.24312 2.88763,0 4.11431,0.52065 3.74029,1.5875 -0.30609,0.87313 -2.06822,1.423 -3.91583,1.22193 -3.88815,-0.42312 -4.30206,0.66402 -1.13679,2.98581 2.1649,1.588 9.4853,-1.40209 11.8665,-4.84697 0.41471,-0.59994 3.98658,-1.42444 7.9375,-1.83221 8.40512,-0.86749 9.70331,1.37833 3.75457,6.49522 -3.36204,2.89191 -3.38579,3.01531 -1.21722,6.32496 3.63241,5.54376 0.39982,7.94743 -4.32882,3.21879 -2.01835,-2.01835 -3.02512,-2.29291 -4.35637,-1.18806 -3.06175,2.54103 -11.23327,1.05124 -12.0185,-2.19115 -0.68516,-2.82917 -0.68516,-2.82917 -5.84415,0.10846 -5.11783,2.91413 -5.1938,2.91868 -9.525,0.56969 -7.82449,-4.24357 -14.38806,-2.76249 -15.161,3.4211 -0.9787,7.82959 -18.35049,9.59901 -23.9798,2.44249 -2.74561,-3.49047 -4.77334,-3.84682 -7.6702,-1.34794 -4.76654,4.11172 -12.98249,5.5266 -24.865,4.28207 -11.93416,-1.24996 -17.8444,-0.24496 -22.49734,3.82553 -1.9371,1.69461 -2.72864,1.68419 -7.17385,-0.0944 -6.52857,-2.61223 -16.59869,0.50527 -11.60381,3.59227 0.6985,0.4317 1.27,1.67415 1.27,2.76102 0,1.75067 -0.32598,1.7651 -2.8575,0.12649 -3.09741,-2.00494 -11.71868,-3.76296 -12.85939,-2.62224 -3.67345,3.67345 11.20857,8.97469 20.69509,7.37195 4.43605,-0.74946 19.46782,1.14831 22.13061,2.794 1.48417,0.91727 9.31559,1.70857 22.42119,2.26547 36.07935,1.53312 73.59215,3.95462 74.44168,4.80533 0.54486,0.5456 1.87775,0.60153 2.96198,0.12421 1.08423,-0.47724 6.25758,-1.07113 11.49633,-1.31976 z m 22.86,-28.85839 c 0,-1.99518 -0.89813,-3.26174 -2.63502,-3.71594 -2.20015,-0.57536 -2.40974,-1.11 -1.27,-3.23963 0.75076,-1.40282 1.36502,-4.2689 1.36502,-6.3691 0,-4.58738 1.31396,-5.61482 5.28022,-4.12887 2.08513,0.7812 4.76269,0.41404 8.95318,-1.22769 5.9784,-2.34217 5.9784,-2.34217 12.35528,3.96797 6.94625,6.87353 10.22997,7.97847 10.26076,3.45264 0.0192,-2.82253 2.02,-7.15485 2.76901,-5.99572 5.3809,8.32736 5.1224,11.32876 -0.97958,11.37378 -1.44985,0.0102 -3.97835,1.44819 -5.61887,3.19444 -1.64052,1.74625 -3.75453,3.175 -4.69781,3.175 -0.94329,0 -2.25827,-1.42875 -2.92219,-3.175 -0.97249,-2.55783 -2.07727,-3.175 -5.68357,-3.175 -4.63641,0 -5.51017,-0.99825 -3.0378,-3.47063 4.50003,-4.50003 -1.17543,-6.54549 -6.26912,-2.25942 -4.25302,3.57866 -6.34387,8.57959 -3.06028,7.31955 1.48654,-0.57043 1.76796,-0.20339 1.12626,1.46889 -0.47053,1.22615 -0.85549,3.03986 -0.85549,4.03049 0,0.99061 -1.143,1.80112 -2.54,1.80112 -1.82952,0 -2.54,-0.84667 -2.54,-3.02688 z m -80.64175,-38.62602 c -2.85714,-0.63566 -8.37517,-6.06683 -7.2783,-7.16371 1.22769,-1.22767 6.56848,-0.77962 9.02988,0.75755 1.84236,1.15058 3.81584,0.99324 9.06126,-0.72245 6.6842,-2.18629 7.39461,-2.03822 9.16577,1.91037 0.18597,0.41462 4.33865,0.23085 9.22815,-0.40838 10.6389,-1.39088 19.89329,-1.26644 21.45619,0.28851 1.77941,1.77034 -2.98981,2.47224 -26.53619,3.90539 -1.397,0.085 -5.3975,-0.12561 -8.89,-0.46801 -3.69914,-0.36269 -7.67544,0.0218 -9.525,0.92126 -1.74625,0.84912 -4.31654,1.28987 -5.71176,0.97947 z m 101.19842,-0.52843 c -4.08737,-4.08737 0.83776,-6.03697 15.32083,-6.06468 12.45075,-0.0239 6.2678,3.62247 -6.6675,3.93206 -1.74625,0.0418 -4.06862,0.87773 -5.16084,1.85764 -1.53184,1.37432 -2.33029,1.43718 -3.49249,0.27498 z M 11.5207,243.78099 c -6.63501,-2.11054 2.10629,-3.9857 10.55829,-2.26493 3.36231,0.68455 7.64856,0.88334 9.525,0.44175 2.80732,-0.66066 3.12986,-0.50324 1.82068,0.88862 -2.05568,2.18549 -16.08907,2.78424 -21.90397,0.93456 z m -130.71615,-49.81007 c -2.01371,-2.42637 0.11938,-3.59063 4.61026,-2.51634 5.05778,1.20992 5.96859,1.93274 4.00399,3.17758 -2.63375,1.66883 -6.9558,1.33707 -8.61425,-0.66124 z m 10.11676,63.99613 c 7.42795,-3.78947 6.72776,-9.10595 -1.39581,-10.5981 -8.38193,-1.53961 -17.04272,10.20202 -9.07261,12.29995 4.09395,1.07763 5.45338,0.85663 10.46842,-1.70185 z m -23.1646,-1.4982 c 2.32789,-2.32788 1.8308,-3.556 -1.43932,-3.556 -3.82235,0 -4.99846,1.23292 -3.22015,3.37565 1.74377,2.10113 2.70163,2.1382 4.65947,0.18035 z m -10.541,-1.651 c 0,-0.6985 -1.28587,-1.45262 -2.85749,-1.67582 -1.96618,-0.27925 -2.8575,0.24349 -2.8575,1.67582 0,1.43234 0.89132,1.95508 2.8575,1.67583 1.57162,-0.2232 2.85749,-0.97733 2.85749,-1.67583 z m -25.7568,-55.5625 c 3.05755,-2.38676 4.57974,-15.59637 1.66848,-14.47923 -1.18493,0.45471 -2.66123,0.82673 -3.28069,0.82673 -0.61944,0 -0.78503,0.88924 -0.36797,1.9761 0.44094,1.14907 -0.37302,3.03008 -1.94485,4.49448 -5.07579,4.7288 -1.44443,11.37338 3.92503,7.18192 z M 21.6807,135.87351 c 3.84175,0.0921 9.94644,-0.72361 13.56598,-1.81262 4.78108,-1.43848 7.25406,-1.61979 9.04196,-0.66294 1.68094,0.89961 4.97116,0.91283 10.38034,0.0417 4.35565,-0.70146 14.86001,-1.49934 23.34304,-1.77306 8.48303,-0.27374 20.28143,-0.80147 26.21868,-1.17276 5.93725,-0.37128 15.367,-0.70906 20.955,-0.75064 13.87424,-0.10325 17.87268,-0.90903 21.5858,-4.35025 9.031,-8.36963 -0.21304,-14.50603 -20.94199,-13.90176 -8.93663,0.26051 -11.22324,-0.0815 -13.335,-1.99503 -2.65836,-2.40871 -5.12858,-3.15627 -17.78881,-5.38344 -5.48291,-0.96456 -9.35889,-0.94039 -13.82018,0.0861 -4.32853,0.99599 -6.68379,1.02531 -7.80219,0.0972 -1.94492,-1.61413 -6.12798,-2.21496 -23.46263,-3.37 -20.02099,-1.334032 -22.97952,-1.082032 -25.42951,2.16601 -1.84195,2.44192 -2.84069,2.689 -7.71076,1.90766 -3.0771,-0.49369 -8.97478,-0.99081 -13.10595,-1.10471 -4.71913,-0.13012 -7.70901,-0.7972 -8.04333,-1.79459 -0.57691,-1.72113 -19.82166,-1.97294 -36.47365,-0.47725 -8.51312,0.76465 -8.88997,0.94114 -9.95109,4.6603 -1.64178,5.75437 -6.41551,7.42024 -16.18623,5.64848 -4.31343,-0.78218 -8.77901,-1.04719 -9.92352,-0.58891 -1.14453,0.45826 -14.02145,1.01953 -28.61539,1.24722 -24.48561,0.38206 -26.80923,0.61719 -30.09315,3.0451 -2.01514,1.48985 -4.45224,2.28818 -5.61874,1.84056 -1.133,-0.43478 -3.30733,-0.1548 -4.83184,0.62216 -1.52451,0.77696 -5.58356,1.85859 -9.02012,2.40362 -16.0542,2.54618 0.45326,8.81068 24.07873,9.13776 8.13635,0.11265 17.3763,0.68733 20.5332,1.27711 3.19488,0.59686 9.72475,0.57194 14.72736,-0.0561 6.76189,-0.84905 11.83682,-0.62745 20.49328,0.89485 6.32815,1.11285 13.26548,2.30497 15.41629,2.64915 2.15082,0.34419 6.72282,-0.17262 10.16,-1.14845 4.08691,-1.16028 8.22702,-1.45191 11.96443,-0.84275 3.14325,0.51231 10.287,0.98244 15.875,1.04474 5.65293,0.063 12.41353,0.95968 15.23999,2.0213 3.67925,1.38191 7.35624,1.67628 13.335,1.06751 4.54025,-0.46229 11.39825,-0.7652 15.24,-0.67313 z m -66.03999,-26.47066 c 0,-0.6985 1.46248,-1.27 3.24995,-1.27 1.84323,0 2.91021,0.54971 2.46505,1.27 -0.4317,0.6985 -1.89418,1.27 -3.24996,1.27 -1.35577,0 -2.46504,-0.5715 -2.46504,-1.27 z m 22.86,-27.305002 c 1.88376,-2.26979 6.51925,-2.49775 8.59436,-0.42263 1.12982,1.12982 1.6756,0.9788 2.29488,-0.635 1.12048,-2.91996 7.6936,-2.72825 13.51962,0.39431 2.57743,1.38142 4.98311,2.03131 5.34598,1.44421 1.98423,-3.21056 12.12862,-4.27134 20.56994,-2.15097 7.69383,1.93264 8.82941,1.94609 12.61548,0.14948 2.28833,-1.08587 6.88622,-2.34791 10.21755,-2.80452 6.36183,-0.87199 13.40645,-6.50915 10.24051,-8.19453 -3.38964,-1.80448 -11.11399,-2.41817 -13.61171,-1.08143 -1.96283,1.05047 -3.10214,1.02795 -4.44666,-0.0879 -0.99423,-0.82513 -6.52981,-1.69917 -12.30132,-1.94229 -12.01477,-0.50613 -19.55022,-1.37434 -21.29674,-2.45375 -0.66517,-0.41109 -2.40669,-0.10668 -3.87004,0.67649 -1.46335,0.78316 -9.61903,1.60288 -18.12374,1.8216 -8.8408,0.22735 -16.93432,1.07857 -18.89839,1.98757 -2.7568,1.27591 -4.20296,1.27193 -7.32227,-0.0202 -3.67894,-1.52387 -20.88625,-1.0321 -27.02495,0.77235 -3.08291,0.90621 -2.7972,4.29213 0.36218,4.29213 1.42157,0 3.85045,0.77005 5.3975,1.71122 1.54705,0.94117 8.24207,2.43252 14.87782,3.31412 6.63575,0.88158 13.49375,2.37466 15.24,3.31792 4.14954,2.24147 5.70142,2.22348 7.62,-0.0883 z m 71.89461,-19.93937 c -0.99485,-4.97426 -2.55384,-6.05113 -8.79647,-6.07619 -7.05465,-0.0283 -6.68672,6.72289 0.40185,7.37361 2.44475,0.22442 4.98793,0.9388 5.6515,1.5875 2.38121,2.32784 3.5416,1.10747 2.74312,-2.88492 z m -29.09437,-2.7899 c -0.2277,-0.68309 -1.01989,-1.44395 -1.76043,-1.69079 -1.55996,-0.51998 -3.18767,4.57656 -2.0497,6.41785 0.90446,1.46343 4.41983,-2.89794 3.81013,-4.72706 z"
+         id="path12898"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#b2b5ac;stroke-width:1.26999998"
+         d="m -15.57612,446.9757 c -33.74553,-1.98038 -61.6487,-9.03014 -99.26817,-25.0802 -10.68561,-4.55893 -13.00025,-5.74058 -15.25582,-7.78829 -1.4057,-1.27616 -5.34844,-3.55559 -8.76164,-5.06542 -12.65444,-5.59766 -29.42911,-18.07658 -50.25297,-37.38393 -6.78028,-6.2865 -14.27818,-12.94513 -16.66201,-14.79694 -2.38383,-1.85181 -6.61648,-6.32647 -9.40589,-9.94369 -2.78941,-3.61723 -6.84656,-8.67436 -9.01588,-11.23807 -2.16932,-2.56372 -4.67949,-6.09005 -5.57817,-7.8363 -0.89868,-1.74625 -5.22331,-8.60425 -9.61029,-15.24001 -9.4685,-14.32206 -16.45884,-29.78754 -17.70247,-39.16509 -0.49454,-3.72905 -2.79563,-10.88474 -5.11352,-15.90152 -2.59268,-5.61152 -4.56006,-12.28772 -5.11275,-17.3499 -0.49411,-4.52567 -1.98704,-12.51474 -3.31762,-17.75349 -4.75094,-18.70543 -3.1647,-51.50019 3.37857,-69.85 2.37493,-6.66021 3.67988,-13.53571 4.33877,-22.86 0.92428,-13.07997 7.16278,-33.303222 12.14492,-39.370002 2.21964,-2.70286 6.24168,-10.46646 11.45609,-22.11325 2.47429,-5.52655 6.9383,-12.95605 9.92002,-16.51001 2.98172,-3.55396 6.32826,-7.89049 7.43676,-9.63674 7.81628,-12.313194 39.69712,-40.997469 61.39391,-55.238181 4.191,-2.750767 8.763,-5.981815 10.15999,-7.180104 1.39701,-1.19829 6.25475,-4.011619 10.795,-6.251845 4.54025,-2.240224 10.541,-5.61367 13.335,-7.496547 6.15489,-4.147783 36.90077,-18.125194 45.72,-20.78482 38.74525,-11.684435 46.62845,-13.242086 72.38999,-14.303601 35.29168,-1.454209 85.49973,7.391352 113.49413,19.995185 21.8044,9.816928 43.1224,21.059731 54.14586,28.555748 4.191,2.849902 10.76325,7.266825 14.605,9.815383 30.83752,20.457138 60.29613,52.116162 74.62003,80.193782 19.33093,37.892412 22.20362,45.164642 22.57538,57.150002 0.3322,10.71043 3.47218,25.62225 7.22043,34.29 2.47198,5.7164 2.74964,8.63099 2.78261,29.21 0.1209,75.47441 -40.0732,154.81557 -100.55791,198.49609 -11.53843,8.33275 -27.06549,18.0394 -34.9193,21.82959 -4.35392,2.10117 -10.48799,5.32562 -13.63124,7.16541 -8.19821,4.7986 -42.71129,17.98907 -54.60999,20.8713 -32.72925,7.928 -60.4921,10.48125 -93.13682,8.56546 z M 34.02402,341.01201 c 6.03038,-8.24705 -9.43776,-16.47507 -20.81899,-11.07432 -8.68873,4.12307 -10.174,11.13944 -2.83413,13.3884 5.66535,1.73587 21.84949,0.15251 23.65312,-2.31408 z m -78.6294,-0.74128 c 0.27959,-1.46725 -0.67822,-2.45147 -2.93751,-3.01852 -4.3046,-1.08038 -6.7726,1.11069 -4.4302,3.93311 2.11583,2.54943 6.81888,1.96562 7.36771,-0.91459 z M 61.92789,333.3578 c 5.7236,-2.71602 6.66101,-5.29141 3.00389,-8.25276 -3.71999,-3.01227 -16.01057,-3.23548 -20.10849,-0.36518 -9.27617,6.49729 5.68665,14.03612 17.1046,8.61794 z m -65.77962,-9.21622 c 2.41601,-1.5298 0.79924,-5.18872 -2.29276,-5.18872 -3.27727,0 -4.53827,2.17556 -2.60864,4.50062 1.77146,2.13448 2.46368,2.23166 4.9014,0.6881 z M 8.9807,314.30436 c 5.23875,-0.24861 17.81175,-0.69551 27.94,-0.9931 10.12825,-0.2976 26.13025,-0.94892 35.56,-1.44738 9.42975,-0.49847 23.57437,-0.79079 31.4325,-0.6496 15.19685,0.27305 15.89954,-0.0505 12.2401,-5.6355 -5.17721,-7.90144 4.76395,-21.48169 20.23326,-27.63994 23.57966,-9.38693 30.93442,-11.11415 35.93345,-8.43874 3.56524,1.90804 5.58812,0.62509 2.95509,-1.87419 -5.1005,-4.84137 -25.32396,-4.48737 -36.59921,0.64066 -13.38062,6.08556 -17.46916,8.12216 -20.67124,10.29684 -3.69052,2.50641 -7.42395,3.01907 -7.42395,1.01944 0,-2.28921 -3.37162,-1.33254 -6.93166,1.9668 -3.71781,3.44554 -6.51203,3.52094 -6.23516,0.16823 0.10414,-1.26076 -1.46824,-2.57667 -4.1275,-3.4543 -4.87628,-1.60932 -4.82591,-1.47908 -2.77055,-7.16306 1.10933,-3.0678 2.30074,-4.21767 4.37004,-4.21767 1.56471,0 3.19813,-0.5715 3.62983,-1.27 0.4317,-0.6985 1.38381,-1.27 2.11583,-1.27 1.20452,0 1.07436,0.74331 -1.01674,5.80629 -0.30923,0.7487 0.96753,2.05829 2.83723,2.91019 4.42293,2.01523 13.20868,-3.18442 13.20868,-7.81725 0,-1.5811 1.20495,-3.90645 2.67766,-5.16742 2.06189,-1.76545 2.49997,-3.18111 1.905,-6.15599 -0.42496,-2.12483 -0.77266,-4.64913 -0.77266,-5.60957 0,-2.37292 -2.47558,-2.19821 -5.31939,0.37539 -2.20797,1.99819 -2.55452,1.9203 -5.95599,-1.33849 -2.96572,-2.84135 -4.26775,-3.28825 -7.28061,-2.49897 -2.01796,0.52865 -4.68271,0.807 -5.92167,0.61856 -1.23897,-0.18843 -4.22891,0.87877 -6.64434,2.37159 -5.10728,3.15648 -6.978,2.50893 -6.978,-2.41542 0,-4.73494 4.10748,-6.55078 17.78,-7.86015 5.93725,-0.56859 12.14593,-1.43134 13.79709,-1.91721 2.64113,-0.77719 2.89783,-0.55489 2.13493,1.84877 -1.42464,4.48865 2.67687,4.87348 10.16372,0.95361 3.96599,-2.07646 8.73922,-3.49818 11.89069,-3.54168 3.85284,-0.0532 5.61678,-0.73 6.57662,-2.52349 1.32909,-2.48344 5.90987,-2.20954 7.62742,0.45606 0.85848,1.33236 7.16027,0.0616 8.22317,-1.65826 0.38481,-0.62263 2.98418,-0.76675 5.77639,-0.32026 4.42397,0.70743 5.16503,0.47413 5.76349,-1.8144 0.9861,-3.77086 3.61156,-4.30187 7.22324,-1.46093 4.19688,3.30126 12.95409,3.48707 11.62003,0.24653 -2.61923,-6.36225 -2.50722,-6.50927 7.40558,-9.72001 2.19995,-0.71255 2.18386,-0.92035 -0.3175,-4.1003 -3.12746,-3.97594 -3.30558,-5.94955 -0.72988,-8.08719 4.07472,-3.38172 2.21366,-5.08 -5.56688,-5.08 -6.3975,0 -7.56714,-0.3643 -8.13442,-2.53358 -0.89425,-3.41959 -4.23949,-4.49941 -7.8887,-2.54642 -3.49072,1.86817 -6.985,0.93513 -6.985,-1.86514 0,-1.06967 0.46227,-1.94486 1.02727,-1.94486 0.56499,0 2.11504,-0.7954 3.44455,-1.76757 3.34435,-2.44544 0.47498,-5.8482 -4.94482,-5.86404 -2.1846,-0.006 -6.8295,-1.41726 -10.322,-3.13528 -6.71403,-3.30275 -12.74292,-3.83576 -22.225,-1.96488 -3.36981,0.66488 -10.9265,0.64695 -18.41499,-0.0437 -18.05384,-1.6651 -54.98468,-2.24164 -60.76678,-0.94864 -3.0349,0.67865 -9.77278,0.50397 -17.78,-0.46096 -7.09127,-0.85454 -16.60797,-1.58436 -21.14822,-1.62181 -8.175,-0.0674 -8.25161,-0.10097 -7.90449,-3.46454 0.45175,-4.37747 -2.34466,-5.30868 -12.76953,-4.25224 -6.62509,0.67137 -8.61877,0.37814 -12.34597,-1.81583 -9.4409,-5.55727 -9.68214,-5.61343 -13.17562,-3.06741 -1.79001,1.30456 -3.27252,3.64462 -3.29448,5.20014 -0.0541,3.83827 -5.40913,7.21829 -9.72129,6.13601 -2.49617,-0.6265 -4.11947,-0.11773 -6.36965,1.99615 -3.01975,2.83691 -3.01975,2.83691 -6.55829,0.22076 -2.2093,-1.63341 -6.61827,-2.95807 -11.7371,-3.52638 -4.50921,-0.50062 -9.46434,-1.71337 -11.01139,-2.69499 -1.54705,-0.98162 -4.1188,-1.80295 -5.715,-1.82516 -1.5962,-0.0222 -4.01154,-0.87947 -5.36741,-1.905 -5.61897,-4.24996 -8.11555,-2.39208 -4.58559,3.41247 4.12665,6.78571 -5.9442,8.84466 -17.53536,3.58503 -7.24606,-3.28798 -13.60074,-3.15572 -19.86382,0.41342 -2.794,1.59223 -8.43958,3.94763 -12.54573,5.23426 -4.97621,1.55924 -7.67244,3.12975 -8.08562,4.70975 -0.69128,2.64346 -0.71371,2.6548 -6.03864,3.05279 -2.0955,0.15662 -4.7302,-0.52058 -5.85489,-1.50489 -1.64174,-1.43681 -2.76845,-1.49672 -5.715,-0.30389 -2.01856,0.81715 -4.52736,1.64344 -5.57511,1.8362 -9.70368,1.78518 -14.73789,6.01754 -7.9375,6.67322 4.63577,0.44696 5.42035,3.45662 2.34691,9.00273 -0.92981,1.67788 -1.48442,3.96388 -1.23246,5.08 0.8321,3.68596 -8.06654,8.27093 -10.01065,5.15792 -2.36738,-3.79079 -10.16236,-3.52993 -9.60713,0.3215 0.27812,1.92926 1.70467,3.186 4.53333,3.99372 4.23273,1.20864 5.16256,2.56155 2.77477,4.03729 -2.2674,1.40133 2.66551,5.66387 6.55461,5.66387 1.94602,0 3.89142,-0.5715 4.32312,-1.27 0.43169,-0.6985 0.17967,-1.27 -0.56005,-1.27 -2.71928,0 -1.24457,-2.26309 1.86165,-2.85688 2.61614,-0.5001 3.07574,-0.20066 2.49594,1.62613 -1.59503,5.02548 7.28647,8.17819 24.14245,8.56996 24.78403,0.57604 29.56234,0.86562 30.55945,1.85205 0.52053,0.51494 3.14136,0.58526 5.8241,0.15628 3.04247,-0.48651 5.57329,-0.20268 6.72648,0.75438 1.42327,1.18122 3.45686,1.20052 8.83582,0.0838 8.30426,-1.72396 12.82212,-0.21082 11.5296,3.86156 -0.92681,2.92013 4.9118,3.50052 8.39757,0.83477 2.84856,-2.17844 22.74198,-1.35489 22.74198,0.94148 0,1.82912 -3.85084,3.18227 -9.13255,3.20908 -1.87965,0.0102 -3.77075,0.58883 -4.20245,1.28733 -0.4317,0.6985 -1.93175,1.27 -3.33345,1.27 -1.40169,0 -3.79684,0.70718 -5.32254,1.57151 -1.5257,0.86433 -5.56621,1.5787 -8.97889,1.5875 -6.07328,0.0156 -8.77302,3.52255 -10.90436,14.16454 -0.0197,0.0982 1.82162,0.47366 4.09174,0.83434 2.27013,0.36068 6.4135,1.26681 9.2075,2.01365 2.794,0.74682 12.08087,1.3715 20.6375,1.38816 14.54274,0.0283 15.5575,0.19108 15.5575,2.49535 0,3.80013 2.0471,4.19138 4.25992,0.81418 2.8232,-4.30874 36.94365,-7.03365 40.72495,-3.25236 0.68372,0.68372 3.1767,1.24313 5.53995,1.24313 2.88763,0 4.11431,0.52065 3.74029,1.5875 -0.30609,0.87313 -2.06822,1.423 -3.91583,1.22193 -3.88815,-0.42312 -4.30206,0.66402 -1.13679,2.98581 2.1649,1.588 9.4853,-1.40209 11.8665,-4.84697 0.41471,-0.59995 3.98658,-1.42444 7.9375,-1.83221 8.40512,-0.86749 9.70331,1.37833 3.75457,6.49522 -3.36204,2.89191 -3.38579,3.01531 -1.21722,6.32496 3.08188,4.70354 1.46045,6.38137 -2.98832,3.09224 -2.97539,-2.19979 -4.05109,-2.42739 -5.55879,-1.17611 -3.17342,2.63369 -11.3543,1.23626 -12.15658,-2.07655 -0.68516,-2.82918 -0.68516,-2.82918 -5.84415,0.10846 -5.11783,2.91413 -5.1938,2.91868 -9.525,0.56968 -7.82449,-4.24356 -14.38806,-2.76249 -15.161,3.42111 -0.9787,7.82958 -18.35049,9.59901 -23.9798,2.44248 -2.98661,-3.79684 -3.15957,-3.7952 -10.84294,0.10287 -6.72786,3.41327 -12.38835,3.89866 -20.42226,1.75122 -9.62972,-2.574 -12.95895,-2.69648 -16.16658,-0.59477 -1.71365,1.12282 -5.34944,2.02564 -8.07956,2.00625 -27.27566,-0.19365 -37.97994,0.44023 -40.33001,2.38823 -1.15867,0.96044 -3.53366,1.74625 -5.27776,1.74625 -1.84395,-9e-5 -3.17108,0.73074 -3.17108,1.74616 0,0.96044 0.66937,1.74625 1.48749,1.74625 0.81813,0 2.38975,0.78361 3.4925,1.74135 2.4362,2.11584 16.87468,7.14865 20.50855,7.14865 1.44014,0 3.71871,0.58885 5.06347,1.30855 1.81302,0.97029 3.48914,0.76862 6.48424,-0.7802 3.64213,-1.88342 4.36112,-1.89868 7.31397,-0.15507 1.80113,1.06353 5.56077,2.34709 8.35477,2.85237 2.794,0.50527 6.14148,1.46791 7.43885,2.13918 1.57146,0.81308 2.82204,0.75729 3.7465,-0.16717 1.58672,-1.58673 6.33542,-1.85264 7.77094,-0.43516 0.53054,0.52388 5.91216,0.96642 11.95915,0.98344 35.09219,0.0988 68.4808,1.00353 70.68455,1.91542 1.397,0.57805 3.39725,0.71827 4.445,0.3116 1.04775,-0.40668 6.19125,-0.94282 11.43,-1.19145 z m 22.86,-27.05683 c 0,-0.67392 1.143,-1.83703 2.54,-2.58468 2.15326,-1.15238 2.54,-0.96582 2.54,1.22533 0,1.738 -0.83203,2.58467 -2.54,2.58467 -1.397,0 -2.54,-0.55139 -2.54,-1.22532 z m 23.97125,-1.94968 c -0.0874,-0.34925 -0.23894,-1.06362 -0.33694,-1.5875 -0.5276,-2.82009 -2.67697,-4.1275 -6.78564,-4.1275 -4.59143,0 -4.59143,0 -1.66938,-3.11038 1.64049,-1.74621 2.59831,-3.95404 2.1839,-5.03399 -1.14246,-2.97721 -7.16711,-0.30161 -12.8684,5.71498 -4.62572,4.88152 -9.54376,6.25855 -8.40725,2.35397 0.2929,-1.00627 0.78189,-3.97628 1.08666,-6.60004 0.65194,-5.61273 1.56098,-6.41544 5.56602,-4.91496 2.08353,0.78059 4.75766,0.41604 8.92559,-1.21681 5.95083,-2.33131 5.95083,-2.33131 6.35,0.47546 0.26838,1.88699 1.43965,2.95086 3.57419,3.24646 1.74625,0.24182 3.65808,1.2885 4.24851,2.32597 0.59043,1.03746 3.44793,2.94236 6.35,4.23311 2.90207,1.29075 4.37598,2.38718 3.27536,2.43652 -1.1006,0.0494 -3.34335,1.51846 -4.98387,3.26471 -2.68682,2.86001 -6.09614,4.19046 -6.50875,2.54 z m -104.613,-38.4779 c -2.85714,-0.63566 -8.37517,-6.06683 -7.2783,-7.16371 1.22769,-1.22767 6.56848,-0.77962 9.02988,0.75755 1.84236,1.15058 3.81584,0.99324 9.06126,-0.72245 6.6842,-2.18629 7.39461,-2.03822 9.16577,1.91037 0.18597,0.41462 4.33865,0.23085 9.22815,-0.40838 10.6389,-1.39088 19.89329,-1.26644 21.45619,0.28851 1.77941,1.77034 -2.98981,2.47224 -26.53619,3.90539 -1.397,0.085 -5.3975,-0.12561 -8.89,-0.46801 -3.69914,-0.36269 -7.67544,0.0218 -9.525,0.92126 -1.74625,0.84912 -4.31654,1.28987 -5.71176,0.97947 z m 101.19842,-0.52843 c -4.08737,-4.08737 0.83776,-6.03697 15.32083,-6.06468 12.45075,-0.0239 6.2678,3.62247 -6.6675,3.93206 -1.74625,0.0418 -4.06862,0.87773 -5.16084,1.85764 -1.53184,1.37432 -2.33029,1.43718 -3.49249,0.27498 z M 11.5207,243.78099 c -6.63501,-2.11054 2.10629,-3.9857 10.55829,-2.26493 3.36231,0.68455 7.64856,0.88334 9.525,0.44175 2.80732,-0.66066 3.12986,-0.50324 1.82068,0.88862 -2.05568,2.18549 -16.08907,2.78424 -21.90397,0.93456 z m -170.19625,-35.97045 c -1.60901,-1.71272 -4.01263,-3.45908 -5.34137,-3.8808 -1.96957,-0.62512 -2.2573,-1.50898 -1.55747,-4.78433 1.24017,-5.80428 2.82002,-7.19256 8.18511,-7.19256 2.62294,0 5.36158,-0.71437 6.08586,-1.5875 1.32917,-1.60231 13.61855,-1.99141 15.13089,-0.47907 0.42956,0.42956 -0.16799,1.82963 -1.32787,3.11129 -1.15989,1.28166 -2.10889,2.97956 -2.10889,3.7731 0,2.0582 -4.27851,4.07218 -8.65099,4.07218 -4.42071,0 -7.15334,2.78482 -6.22312,6.34198 1.00288,3.83503 -0.90905,4.1204 -4.19215,0.62571 z m 15.25626,-1.81273 c 0,-1.87927 1.85687,-2.55581 4.85961,-1.77057 4.04266,1.05717 3.36723,2.96561 -1.04961,2.96561 -2.0955,0 -3.81,-0.53777 -3.81,-1.19504 z m 34.18396,52.04915 c 8.41672,-4.2939 7.85928,-9.11326 -1.23917,-10.71334 -8.40059,-1.47738 -17.02099,10.24308 -9.07261,12.33528 4.08106,1.07424 5.4484,0.85917 10.31178,-1.62194 z m -23.00796,-1.57811 c 2.32789,-2.32788 1.8308,-3.556 -1.43932,-3.556 -3.82235,0 -4.99846,1.23292 -3.22015,3.37565 1.74377,2.10113 2.70163,2.1382 4.65947,0.18035 z m -10.541,-1.651 c 0,-0.6985 -1.28587,-1.45262 -2.85749,-1.67582 -1.96618,-0.27925 -2.8575,0.24349 -2.8575,1.67582 0,1.43234 0.89132,1.95508 2.8575,1.67583 1.57162,-0.2232 2.85749,-0.97733 2.85749,-1.67583 z M 21.6807,135.87351 c 3.84175,0.0921 9.94644,-0.72361 13.56598,-1.81262 4.78108,-1.43848 7.25406,-1.61979 9.04196,-0.66294 1.68094,0.89961 4.97116,0.91283 10.38034,0.0417 4.35565,-0.70146 14.86001,-1.49934 23.34304,-1.77306 8.48303,-0.27374 20.28143,-0.80629 26.21868,-1.18348 5.93725,-0.37717 16.39631,-0.71342 23.24235,-0.74721 8.94336,-0.0442 14.48481,-0.76423 19.685,-2.55792 3.9807,-1.37305 9.23789,-2.43989 11.68264,-2.37075 7.9534,0.22493 11.43,-1.98309 11.43,-7.2593 0,-4.55946 -0.0542,-4.60759 -4.29765,-3.81152 -3.25848,0.61129 -4.48322,0.32266 -5.06508,-1.19367 -0.56481,-1.47184 -2.36423,-1.95169 -6.81486,-1.81731 -7.99391,0.24138 -42.42582,-3.55378 -58.7524,-6.47581 -6.16435,-1.10324 -9.82526,-1.1161 -14.45518,-0.0508 -4.32853,0.99599 -6.68379,1.02531 -7.80219,0.0972 -1.94492,-1.61413 -6.12798,-2.21496 -23.46263,-3.37 -20.02099,-1.334032 -22.97952,-1.082022 -25.42951,2.16601 -1.84195,2.44192 -2.84069,2.689 -7.71076,1.90766 -3.0771,-0.49369 -8.97478,-0.9908 -13.10595,-1.10471 -4.71913,-0.13011 -7.70901,-0.79719 -8.04333,-1.79459 -0.57691,-1.72113 -19.82166,-1.97294 -36.47365,-0.47725 -8.60091,0.77254 -8.88034,0.90736 -9.99792,4.82359 -1.34248,4.70432 -5.34947,6.2437 -11.44425,4.39661 -7.4585,-2.26038 -36.87235,-2.90769 -41.63835,-0.91633 -3.0349,1.26806 -5.91851,1.48972 -9.90246,0.76121 -3.28334,-0.6004 -8.82653,-0.47413 -13.32977,0.30366 -4.23997,0.73232 -10.52746,1.24365 -13.97221,1.13629 -3.44473,-0.10731 -11.63955,1.23579 -18.21068,2.98476 -6.57114,1.74902 -12.95101,3.18 -14.1775,3.18 -3.33302,0 -6.80967,4.43454 -5.84918,7.46076 0.91104,2.87044 8.94036,4.71499 17.83054,4.09614 2.794,-0.19449 7.08024,-0.33061 9.52499,-0.30249 2.44475,0.0281 11.58875,0.12319 20.32,0.21127 8.73125,0.0881 18.36877,0.62593 21.41674,1.19522 3.04795,0.56929 9.58612,0.52723 14.52926,-0.0935 6.76189,-0.84906 11.83682,-0.62746 20.49328,0.89484 6.32815,1.11285 13.26548,2.30497 15.41629,2.64915 2.15082,0.34419 6.72282,-0.17261 10.16,-1.14844 4.08691,-1.16029 8.22702,-1.45192 11.96443,-0.84276 3.14325,0.51232 10.287,0.98244 15.875,1.04474 5.65293,0.063 12.41353,0.95969 15.23999,2.0213 3.67925,1.38192 7.35624,1.67628 13.335,1.06752 4.54025,-0.4623 11.39825,-0.76521 15.24,-0.67314 z m -66.03999,-26.47066 c 0,-0.6985 1.46248,-1.27 3.24995,-1.27 1.84323,0 2.91021,0.54971 2.46505,1.27 -0.4317,0.6985 -1.89418,1.27 -3.24996,1.27 -1.35577,0 -2.46504,-0.5715 -2.46504,-1.27 z m 24.63191,-24.876162 c 0.6085,-1.5857 1.84585,-1.83501 5.79542,-1.16774 3.0179,0.50988 6.78767,0.20985 9.44724,-0.75185 3.6009,-1.30211 5.38501,-1.28361 9.57524,0.0993 4.6225,1.52557 5.40796,1.4659 7.67174,-0.58279 2.0905,-1.89188 3.55803,-2.10285 8.57771,-1.23313 3.33066,0.57707 9.19898,0.93319 13.04073,0.79138 3.84175,-0.1418 16.7005,-0.50274 28.575,-0.80206 23.51465,-0.59275 28.54189,-1.00167 31.00131,-2.52167 0.89045,-0.55032 4.89206,-1.12252 8.89247,-1.27153 9.71343,-0.36181 14.47227,-4.31344 8.04872,-6.68345 -1.22238,-0.45101 -2.60581,-1.39361 -3.07431,-2.09466 -0.7187,-1.07549 -4.34087,-1.61906 -13.43569,-2.01628 -0.87313,-0.0381 -1.5875,-0.91566 -1.5875,-1.95005 0,-1.48951 -1.15213,-1.72279 -5.53898,-1.1215 -4.79969,0.65786 -5.81034,0.37177 -7.57215,-2.14356 -2.10957,-3.01184 -6.78761,-4.05869 -16.73387,-3.74467 -9.28945,0.2933 -13.91399,0.13125 -20.0025,-0.70087 -4.04227,-0.55245 -6.0325,-0.36327 -6.0325,0.57343 0,0.76883 -1.7145,1.39788 -3.81,1.39788 -2.0955,0 -3.81,-0.51454 -3.81,-1.14344 0,-0.62889 -2.71462,-1.04193 -6.0325,-0.91786 -3.31787,0.12408 -7.84324,-0.14793 -10.05635,-0.60445 -2.69304,-0.5555 -5.73782,-0.0626 -9.20617,1.49018 -4.99671,2.2371 -23.04717,2.78671 -25.07051,0.76337 -0.40527,-0.40527 -1.58628,0.11253 -2.62448,1.15078 -1.07805,1.07806 -2.92815,1.55739 -4.31374,1.11761 -1.33437,-0.42351 -4.43059,-0.10846 -6.88048,0.70005 -3.86744,1.27637 -5.04903,1.11921 -8.96756,-1.19279 -2.48225,-1.46456 -5.16735,-2.25856 -5.96685,-1.76443 -0.79952,0.49412 -3.01475,0.77946 -4.92274,0.63407 -2.37853,-0.18124 -4.69935,0.9383 -7.38263,3.56128 -3.16999,3.09877 -4.99107,3.82563 -9.58477,3.82563 -5.29958,0 -5.67121,0.2223 -5.67121,3.3925 0,3.59671 3.17171,8.0375 5.74056,8.0375 0.84529,0 3.11074,1.23798 5.03433,2.75108 2.89663,2.2785 3.9153,2.49014 5.92984,1.23204 1.45726,-0.91007 4.83331,-1.25461 8.42133,-0.85942 3.29391,0.3628 9.34649,0.92909 13.45016,1.25844 4.10366,0.32935 8.67566,1.44838 10.16,2.48673 3.58186,2.50564 7.95515,2.50809 8.91569,0.005 z m -34.51004,-21.05447 c 0.65602,-1.96805 4.64693,-2.29912 5.11563,-0.42437 0.17462,0.6985 -1.03203,1.46097 -2.68145,1.69437 -2.05603,0.29093 -2.82139,-0.10833 -2.43418,-1.27 z m -27.72275,18.52176 c 1.70307,-4.43812 0.84953,-6.88113 -2.40412,-6.88113 -2.63961,0 -3.175,0.60678 -3.175,3.59833 0,5.34239 3.90669,7.64112 5.57912,3.2828 z"
+         id="path12896"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#c6ba8f;stroke-width:1.26999998"
+         d="m -27.21429,445.79952 c -19.07069,-1.89892 -41.0257,-6.89539 -58.38933,-13.28811 -15.51423,-5.71183 -22.7765,-8.47192 -26.70067,-10.14783 -13.29928,-5.67977 -16.7857,-7.41822 -19.06733,-9.50761 -1.40653,-1.28803 -4.73982,-3.25379 -7.40731,-4.36834 -6.49882,-2.71538 -24.70561,-14.65954 -29.42507,-19.30366 -2.08466,-2.05138 -6.64778,-5.80696 -10.14028,-8.34573 -3.4925,-2.53877 -9.2075,-7.82855 -12.7,-11.75507 -3.4925,-3.92653 -7.81937,-7.9157 -9.61526,-8.86483 -1.7959,-0.94913 -6.20503,-4.76427 -9.79807,-8.47808 -7.63274,-7.88932 -11.38588,-13.02777 -25.03667,-34.27783 -0.6985,-1.08734 -3.50345,-5.34019 -6.23323,-9.45078 -7.23803,-10.89926 -14.72177,-28.43785 -14.72177,-34.50134 0,-3.21978 -1.68353,-8.63886 -4.57599,-14.72957 -3.08723,-6.50082 -5.12319,-13.25798 -6.25789,-20.76932 -0.92503,-6.12345 -2.40626,-13.70532 -3.29161,-16.84857 -2.9959,-10.63632 -2.21848,-54.05726 1.09144,-60.96 4.04761,-8.44117 6.65631,-20.08126 7.20528,-32.15014 0.56093,-12.33213 6.25996,-28.463482 14.82545,-41.964112 2.72206,-4.29042 4.95462,-8.42066 4.96126,-9.17833 0.0225,-2.56548 8.75769,-18.91038 12.96997,-24.26876 2.3029,-2.92949 7.32008,-9.43152 11.14928,-14.44897 8.59218,-11.258423 37.05324,-36.545019 53.64406,-47.66075 6.80071,-4.55643 12.70601,-8.836286 13.12287,-9.51079 0.41687,-0.674505 5.42961,-3.57185 11.13941,-6.438546 5.70981,-2.866696 12.66746,-6.757365 15.46146,-8.645933 6.8141,-4.6059 37.80842,-18.536524 46.43345,-20.869848 3.79861,-1.027636 10.0498,-2.719761 13.89155,-3.760278 3.84175,-1.040518 9.62158,-2.788402 12.84409,-3.884186 8.4721,-2.880867 36.13945,-5.831904 53.82312,-5.740846 16.66827,0.08583 24.44512,0.835329 49.96597,4.815495 16.62824,2.593296 19.02042,3.164424 33.3572,7.963963 3.91347,1.310115 7.75856,2.382027 8.54465,2.382027 3.75132,0 54.51507,25.137414 59.73674,29.580701 1.14027,0.970291 6.64521,4.531613 12.23321,7.914048 33.94888,20.549374 71.55445,60.096023 84.50824,88.870253 1.57227,3.4925 5.57769,11.77925 8.90093,18.415 8.03384,16.041732 12.66931,30.968342 11.74872,37.831872 -0.43594,3.25019 0.0836,7.66819 1.36101,11.57365 1.13635,3.47421 2.07864,7.63811 2.09398,9.25311 0.0154,1.615 1.2012,5.50812 2.63523,8.65137 10.53574,23.09325 -0.3365,95.57402 -20.479,136.525 -22.48237,45.70811 -71.27513,96.78241 -107.84054,112.88318 -1.78671,0.78674 -8.10632,4.0844 -14.04357,7.32812 C 90.7921,439.66337 28.17547,451.31482 -27.21429,445.79952 Z M 28.3117,344.99032 c 14.38006,-2.24819 6.75804,-18.15526 -8.05624,-16.81328 -14.85415,1.34561 -20.23919,13.74467 -7.46476,17.18761 4.34698,1.17159 5.86747,1.13492 15.521,-0.37433 z m -72.5875,-1.95214 c 3.29557,-1.76374 3.09364,-3.2893 -0.77051,-5.8212 -3.6653,-2.40158 -9.47298,-0.80725 -9.47298,2.60054 0,3.69214 5.89771,5.54645 10.24349,3.22066 z m 106.48107,-8.58882 c 10.38151,-5.18289 7.73869,-10.60745 -5.5811,-11.45559 -13.45643,-0.85683 -19.02382,4.91732 -10.4919,10.88159 4.82241,3.37112 10.09321,3.55936 16.073,0.574 z m 38.21543,-5.3285 c 0,-1.89773 -3.74006,-2.44026 -7.58421,-1.10018 -5.56075,1.93849 -4.7237,3.0853 1.86921,2.56094 3.14325,-0.24998 5.715,-0.90734 5.715,-1.46076 z m -97.79,-4.453 c 0,-1.04775 -1.08186,-1.905 -2.40412,-1.905 -1.32227,0 -2.74847,-0.89734 -3.16932,-1.99408 -1.13042,-2.94582 -6.49156,-1.82341 -6.49156,1.35908 0,2.07498 0.74247,2.48277 4.0556,2.22741 2.23056,-0.17192 4.40742,0.25671 4.83744,0.9525 1.18374,1.91533 3.17196,1.51421 3.17196,-0.63991 z m 6.35,-10.3635 c 20.86036,-0.98999 48.08042,-1.79467 56.21123,-1.66172 4.72242,0.0772 13.86642,-0.51126 20.32,-1.30771 7.909,-0.97609 14.83856,-1.06334 21.25877,-0.26768 12.45901,1.54404 13.43494,1.11958 10.0433,-4.36822 -15.52978,-25.1277 50.57337,-47.87454 73.01947,-25.12682 3.55972,3.60756 7.76991,7.23396 9.35597,8.05866 5.64214,2.93372 7.5356,-1.98776 2.12795,-5.53098 -1.78382,-1.16881 -6.3403,-4.92975 -10.12552,-8.35767 -14.67664,-13.29123 -33.75698,-16.00732 -52.51598,-7.47565 -13.38062,6.08556 -17.46916,8.12216 -20.67124,10.29684 -3.69052,2.50641 -7.42395,3.01907 -7.42395,1.01944 0,-2.28921 -3.37162,-1.33254 -6.93166,1.9668 -3.71781,3.44554 -6.51203,3.52094 -6.23516,0.16823 0.10414,-1.26076 -1.46824,-2.57667 -4.1275,-3.4543 -4.87628,-1.60932 -4.82591,-1.47908 -2.77055,-7.16306 1.10933,-3.0678 2.30074,-4.21767 4.37004,-4.21767 1.56471,0 3.19813,-0.5715 3.62983,-1.27 0.4317,-0.6985 1.38381,-1.27 2.11583,-1.27 1.20452,0 1.07436,0.74331 -1.01674,5.80629 -0.30923,0.7487 0.96753,2.05829 2.83723,2.91019 4.42293,2.01523 13.20868,-3.18442 13.20868,-7.81725 0,-1.5811 1.20495,-3.90645 2.67766,-5.16742 2.06189,-1.76545 2.49997,-3.18111 1.905,-6.15599 -0.42496,-2.12483 -0.77266,-4.64913 -0.77266,-5.60957 0,-2.37292 -2.47558,-2.19821 -5.31939,0.37539 -2.20797,1.99819 -2.55452,1.9203 -5.95599,-1.33849 -2.96572,-2.84135 -4.26775,-3.28825 -7.28061,-2.49897 -2.01796,0.52865 -4.68271,0.807 -5.92167,0.61856 -1.23897,-0.18843 -4.22891,0.87877 -6.64434,2.37159 -5.10728,3.15648 -6.978,2.50893 -6.978,-2.41542 0,-4.73494 4.10748,-6.55078 17.78,-7.86015 5.93725,-0.56859 12.14593,-1.43134 13.79709,-1.91721 2.64113,-0.77719 2.89783,-0.55489 2.13493,1.84877 -1.41012,4.44292 2.66215,4.87772 9.9955,1.06724 6.08962,-3.16426 10.35433,-4.05425 25.50747,-5.32308 3.4925,-0.29245 8.636,-0.82541 11.43,-1.18438 2.794,-0.35895 10.22049,-1.04511 16.50331,-1.52477 10.19201,-0.77813 11.76786,-1.24098 14.61995,-4.29417 2.23634,-2.394 5.08548,-3.70526 9.48415,-4.36488 14.71246,-2.20627 20.1591,-7.36616 10.93421,-10.35852 -5.25697,-1.70524 -7.90824,-5.05694 -6.96692,-8.80747 1.1863,-4.72658 -7.65044,-13.16785 -14.45968,-13.81256 -2.99475,-0.28354 -6.15435,-1.10425 -7.02133,-1.82377 -0.86697,-0.71953 -6.13197,-1.65822 -11.70001,-2.08597 -6.80014,-0.52238 -12.28675,-1.79875 -16.71253,-3.88785 -6.92151,-3.26716 -12.50521,-3.74256 -21.98615,-1.87191 -3.36981,0.66488 -10.9265,0.64695 -18.41499,-0.0437 -18.05384,-1.6651 -54.98468,-2.24164 -60.76678,-0.94864 -3.0349,0.67865 -9.77278,0.50397 -17.78,-0.46096 -7.09127,-0.85454 -16.60797,-1.58436 -21.14822,-1.62181 -8.175,-0.0674 -8.25161,-0.10097 -7.90449,-3.46454 0.45175,-4.37747 -2.34466,-5.30868 -12.76953,-4.25224 -6.62509,0.67137 -8.61877,0.37814 -12.34597,-1.81583 -9.4409,-5.55727 -9.68214,-5.61343 -13.17562,-3.06741 -1.79001,1.30456 -3.27252,3.64462 -3.29448,5.20014 -0.0541,3.83827 -5.40913,7.21829 -9.72129,6.13601 -2.49617,-0.6265 -4.11947,-0.11773 -6.36965,1.99615 -3.01975,2.83691 -3.01975,2.83691 -6.55829,0.22076 -2.2093,-1.63341 -6.61827,-2.95807 -11.7371,-3.52638 -4.50921,-0.50062 -9.46434,-1.71337 -11.01139,-2.69499 -1.54705,-0.98162 -4.1188,-1.80295 -5.715,-1.82516 -1.5962,-0.0222 -4.01154,-0.87947 -5.36741,-1.905 -5.61897,-4.24996 -8.11555,-2.39208 -4.58559,3.41247 4.12665,6.78571 -5.9442,8.84466 -17.53536,3.58503 -7.24606,-3.28798 -13.60074,-3.15572 -19.86382,0.41342 -2.794,1.59223 -8.43958,3.94763 -12.54573,5.23426 -4.97621,1.55924 -7.67244,3.12975 -8.08562,4.70975 -0.69128,2.64346 -0.71371,2.6548 -6.03864,3.05279 -2.0955,0.15662 -4.7302,-0.52058 -5.85489,-1.50489 -1.65214,-1.44592 -2.74978,-1.4994 -5.715,-0.27846 -2.01856,0.83116 -4.116,1.41315 -4.66096,1.29332 -0.54497,-0.11976 -2.54522,0.5763 -4.445,1.54696 -1.89979,0.97065 -5.4544,2.18199 -7.89915,2.69186 -2.44475,0.50986 -6.26425,2.29319 -8.48777,3.96296 -2.22352,1.66973 -4.8914,3.03589 -5.92863,3.03589 -3.76961,0 -6.03279,1.66078 -7.6997,5.65025 -1.04087,2.49116 -3.24135,4.71002 -5.66768,5.71505 -10.43064,4.32051 2.5777,15.12573 22.42673,18.6285 5.78487,1.02086 11.08409,1.50622 11.77603,1.07857 0.69195,-0.42765 4.09429,0.16449 7.56076,1.31587 4.2553,1.41338 10.33116,2.0595 18.70397,1.98901 17.41401,-0.14662 27.88421,0.38935 28.99073,1.48401 0.52053,0.51495 3.14136,0.58527 5.8241,0.15629 3.04247,-0.48652 5.57329,-0.20268 6.72648,0.75438 1.42327,1.18121 3.45686,1.20051 8.83582,0.0838 8.30426,-1.72397 12.82212,-0.21082 11.5296,3.86156 -0.92681,2.92012 4.9118,3.50051 8.39757,0.83477 2.84856,-2.17844 22.74198,-1.3549 22.74198,0.94148 0,1.82911 -3.85084,3.18226 -9.13255,3.20907 -1.87965,0.0102 -3.77075,0.58884 -4.20245,1.28734 -0.4317,0.6985 -1.93175,1.27 -3.33345,1.27 -1.40169,0 -3.79684,0.70717 -5.32254,1.57151 -1.5257,0.86432 -5.56621,1.5787 -8.97889,1.5875 -6.07328,0.0156 -8.77302,3.52254 -10.90436,14.16454 -0.0197,0.0982 1.82162,0.47365 4.09174,0.83433 2.27013,0.36068 6.4135,1.26682 9.2075,2.01365 2.794,0.74683 12.08087,1.3715 20.6375,1.38816 14.54274,0.0283 15.5575,0.19109 15.5575,2.49535 0,3.80013 2.0471,4.19138 4.25992,0.81418 2.8232,-4.30874 36.94365,-7.03365 40.72495,-3.25235 0.68372,0.68372 3.1767,1.24313 5.53995,1.24313 2.88763,0 4.11431,0.52065 3.74029,1.5875 -0.30609,0.87312 -2.06822,1.42299 -3.91583,1.22193 -3.88815,-0.42313 -4.30206,0.66402 -1.13679,2.9858 2.1649,1.588 9.4853,-1.40209 11.8665,-4.84696 0.41471,-0.59995 3.98658,-1.42445 7.9375,-1.83222 8.40512,-0.86749 9.70331,1.37833 3.75457,6.49523 -3.36204,2.8919 -3.38579,3.01531 -1.21722,6.32495 3.08188,4.70355 1.46045,6.38137 -2.98832,3.09225 -2.97539,-2.19979 -4.05109,-2.42739 -5.55879,-1.17611 -3.17342,2.63369 -11.3543,1.23626 -12.15658,-2.07655 -0.68516,-2.82918 -0.68516,-2.82918 -5.84415,0.10846 -5.11783,2.91412 -5.1938,2.91867 -9.525,0.56968 -7.82449,-4.24357 -14.38806,-2.76249 -15.161,3.4211 -0.9787,7.82959 -18.35049,9.59902 -23.9798,2.44249 -2.99649,-3.80942 -3.16305,-3.8062 -10.9452,0.2113 -7.30792,3.77268 -18.18011,3.67899 -26.37159,-0.22725 -4.36096,-2.0796 -4.64956,-2.0567 -9.15245,0.72623 -3.8876,2.40268 -5.62424,2.72273 -10.59773,1.95309 -3.27152,-0.50626 -7.94848,-0.77233 -10.39323,-0.59126 -6.82819,0.50571 -17.19519,0.32617 -30.71332,-0.53192 -12.29832,-0.78065 -12.29832,-0.78065 -16.00141,2.92244 -2.0367,2.0367 -5.75811,4.08861 -8.2698,4.55981 -6.96374,1.30641 -3.84028,5.28805 5.01637,6.39461 4.06976,0.50848 7.48522,1.76069 8.81739,3.23272 2.32282,2.56668 7.46858,3.23164 8.76577,1.13274 1.13394,-1.83474 5.97857,-1.56091 11.23412,0.635 2.50761,1.04775 6.61548,1.905 9.12858,1.905 2.51311,0 5.63262,0.56907 6.93224,1.26462 1.72866,0.92515 3.38388,0.6787 6.16613,-0.91809 3.59634,-2.06401 4.05268,-2.06372 8.39163,0.005 2.52367,1.20345 5.97094,2.1881 7.66063,2.1881 1.68968,0 4.51087,0.65552 6.2693,1.45671 2.47988,1.12991 3.64221,1.08735 5.18094,-0.18968 1.62609,-1.34953 2.90586,-1.34209 7.09758,0.0413 3.8159,1.25937 6.14233,1.34654 9.16632,0.34351 2.22889,-0.73932 6.33853,-1.14657 9.13253,-0.90501 2.794,0.24155 17.5014,0.47326 32.6831,0.51493 15.18171,0.0417 28.89771,0.59632 30.48,1.23259 1.58229,0.63625 3.73414,0.81986 4.78189,0.40801 1.04775,-0.41185 6.19125,-0.95222 11.43,-1.20085 z m 22.86,-27.05683 c 0,-0.67392 1.143,-1.83703 2.54,-2.58468 2.15326,-1.15238 2.54,-0.96582 2.54,1.22533 0,1.738 -0.83203,2.58467 -2.54,2.58467 -1.397,0 -2.54,-0.55139 -2.54,-1.22532 z m 23.97125,-1.94968 c -0.0874,-0.34925 -0.23894,-1.06362 -0.33694,-1.5875 -0.5276,-2.82009 -2.67697,-4.1275 -6.78564,-4.1275 -4.59143,0 -4.59143,0 -1.51254,-3.30974 4.36318,-4.69037 8.93473,-6.28282 10.41956,-3.62957 0.63598,1.13644 3.53075,3.12233 6.43282,4.41308 2.90207,1.29075 4.37598,2.38718 3.27536,2.43652 -1.1006,0.0494 -3.34335,1.51846 -4.98387,3.26471 -2.68682,2.86001 -6.09614,4.19046 -6.50875,2.54 z m -28.2575,-4.18814 c -0.34438,-0.55722 -0.10109,-1.8431 0.5406,-2.8575 0.64172,-1.0144 1.17552,-3.668 1.18621,-5.8969 0.0231,-4.82036 1.2915,-5.86453 5.29966,-4.36288 2.09368,0.7844 4.79023,0.40315 9.10419,-1.2872 7.67039,-3.00552 8.38707,0.58381 0.83859,4.19985 -2.43541,1.16665 -6.3318,4.16793 -8.65867,6.66949 -4.09052,4.3976 -7.01002,5.63949 -8.31058,3.53514 z m -76.3555,-34.28976 c -2.85714,-0.63566 -8.37517,-6.06683 -7.2783,-7.16371 1.22769,-1.22767 6.56848,-0.77962 9.02988,0.75755 1.84236,1.15058 3.81584,0.99324 9.06126,-0.72245 6.6842,-2.18629 7.39461,-2.03822 9.16577,1.91037 0.18597,0.41462 4.33865,0.23085 9.22815,-0.40838 10.6389,-1.39088 19.89329,-1.26644 21.45619,0.28851 1.77941,1.77034 -2.98981,2.47224 -26.53619,3.90539 -1.397,0.085 -5.3975,-0.12561 -8.89,-0.46801 -3.69914,-0.36269 -7.67544,0.0218 -9.525,0.92126 -1.74625,0.84912 -4.31654,1.28987 -5.71176,0.97947 z m 101.19842,-0.52843 c -4.08737,-4.08737 0.83776,-6.03697 15.32083,-6.06468 12.45075,-0.0239 6.2678,3.62247 -6.6675,3.93206 -1.74625,0.0418 -4.06862,0.87773 -5.16084,1.85764 -1.53184,1.37432 -2.33029,1.43718 -3.49249,0.27498 z M 11.5207,243.78099 c -6.63501,-2.11054 2.10629,-3.9857 10.55829,-2.26493 3.36231,0.68455 7.64856,0.88334 9.525,0.44175 2.80732,-0.66066 3.12986,-0.50324 1.82068,0.88862 -2.05568,2.18549 -16.08907,2.78424 -21.90397,0.93456 z m -202.35609,-39.97758 c -1.12957,-1.12957 2.89763,-5.50056 5.06794,-5.50056 2.11971,0 2.22596,2.78421 0.18417,4.826 -1.68751,1.68752 -3.94873,1.97794 -5.25211,0.67456 z m 81.60006,54.24355 c 8.41672,-4.2939 7.85928,-9.11326 -1.23917,-10.71334 -8.40059,-1.47738 -17.02099,10.24308 -9.07261,12.33528 4.08106,1.07424 5.4484,0.85917 10.31178,-1.62194 z m -23.00796,-1.57811 c 2.32789,-2.32788 1.8308,-3.556 -1.43932,-3.556 -3.82235,0 -4.99846,1.23292 -3.22015,3.37565 1.74377,2.10113 2.70163,2.1382 4.65947,0.18035 z m -10.541,-1.651 c 0,-0.6985 -1.28587,-1.45262 -2.85749,-1.67582 -1.96618,-0.27925 -2.8575,0.24349 -2.8575,1.67582 0,1.43234 0.89132,1.95508 2.8575,1.67583 1.57162,-0.2232 2.85749,-0.97733 2.85749,-1.67583 z m -73.02499,-32.2295 c 0,-1.65533 -3.2802,-4.07196 -4.23333,-3.11883 -0.95313,0.95313 1.4635,4.23333 3.11883,4.23333 0.61298,0 1.1145,-0.50152 1.1145,-1.1145 z m 429.75909,-14.86437 c 1.05408,-2.74686 1.0803,-2.69398 -1.69559,-3.41988 -3.25444,-0.85105 -4.08009,0.14846 -2.52416,3.05573 1.55296,2.90174 3.19173,3.04315 4.21975,0.36415 z M 21.6807,135.87351 c 3.84175,0.0921 9.94644,-0.72361 13.56598,-1.81262 4.78108,-1.43848 7.25406,-1.61979 9.04196,-0.66294 1.68094,0.89961 4.97116,0.91283 10.38034,0.0417 4.35565,-0.70146 14.86001,-1.49934 23.34304,-1.77306 8.48303,-0.27374 20.28143,-0.80584 26.21868,-1.18246 5.93725,-0.37662 16.21116,-0.71996 22.83093,-0.76299 6.61976,-0.043 15.27474,-0.9175 19.23331,-1.94327 3.95856,-1.02576 9.72579,-1.86503 12.81606,-1.86503 9.54908,0 22.7728,-4.53942 29.1633,-10.01113 5.20589,-4.45743 2.61537,-5.62463 -15.83374,-7.13406 -4.04531,-0.33097 -8.50795,-1.21876 -9.91698,-1.97284 -1.50461,-0.80525 -4.2345,-0.97963 -6.61487,-0.42257 -5.54567,1.29781 -50.05709,0.76288 -52.71336,-0.6335 -3.98552,-2.09516 -17.43769,-2.93916 -22.99502,-1.44273 -3.89304,1.04829 -5.83437,1.06446 -7.04626,0.0587 -2.01681,-1.6738 -6.07956,-2.26572 -23.53337,-3.42871 -20.02099,-1.334032 -22.97952,-1.082032 -25.42951,2.16601 -1.84195,2.44192 -2.84069,2.689 -7.71076,1.90766 -3.0771,-0.49369 -8.97478,-0.99081 -13.10595,-1.10471 -4.71913,-0.13012 -7.70901,-0.7972 -8.04333,-1.79459 -0.57691,-1.72113 -19.82166,-1.97294 -36.47365,-0.47725 -8.61832,0.7741 -8.87834,0.90031 -10.00769,4.85762 -1.30472,4.57181 -6.45067,6.62103 -10.79204,4.2976 -1.37054,-0.7335 -5.06074,-1.48592 -8.20044,-1.67205 -3.13969,-0.18613 -5.47587,-0.72054 -5.19149,-1.18758 1.70851,-2.80598 -23.78261,-2.97155 -29.16265,-0.18942 -2.40553,1.24395 -5.30385,1.6386 -8.29273,1.12918 -2.52711,-0.43071 -5.73775,-0.79183 -7.13475,-0.80249 -1.397,-0.0102 -7.96925,-0.0784 -14.605,-0.15048 -28.04917,-0.3048 -34.85866,0.4692 -44.88588,5.102 -4.77999,2.2085 -10.21507,4.01542 -12.07797,4.01542 -4.41098,0 -11.02049,5.91219 -9.14112,8.17671 3.07297,3.70269 17.32799,6.1233 30.88397,5.24431 7.1478,-0.46348 14.99626,-0.84331 17.44101,-0.84408 21.84459,-0.006 37.10036,0.488 41.73673,1.35396 3.04795,0.56929 9.58612,0.52723 14.52926,-0.0935 6.76189,-0.84906 11.83682,-0.62746 20.49328,0.89484 6.32815,1.11285 13.26548,2.30497 15.41629,2.64915 2.15082,0.34419 6.72282,-0.17261 10.16,-1.14844 4.08691,-1.16029 8.22702,-1.45192 11.96443,-0.84276 3.14325,0.51232 10.287,0.98244 15.875,1.04474 5.65293,0.063 12.41353,0.95969 15.23999,2.0213 3.67925,1.38192 7.35624,1.67628 13.335,1.06752 4.54025,-0.4623 11.39825,-0.76521 15.24,-0.67314 z m -66.03999,-26.47066 c 0,-0.6985 1.46248,-1.27 3.24995,-1.27 1.84323,0 2.91021,0.54971 2.46505,1.27 -0.4317,0.6985 -1.89418,1.27 -3.24996,1.27 -1.35577,0 -2.46504,-0.5715 -2.46504,-1.27 z m 24.63191,-24.876162 c 0.6085,-1.5857 1.84585,-1.83501 5.79542,-1.16774 3.0179,0.50988 6.78767,0.20985 9.44724,-0.75185 3.6009,-1.30211 5.38501,-1.28361 9.57524,0.0993 4.6225,1.52557 5.40796,1.4659 7.67174,-0.58279 2.0905,-1.89188 3.55803,-2.10285 8.57771,-1.23313 3.33066,0.57707 9.19898,0.93157 13.04073,0.78777 3.84175,-0.14382 17.55775,-0.43112 30.48,-0.63846 12.92225,-0.20733 28.6385,-1.03591 34.925,-1.84127 6.2865,-0.80537 16.85925,-2.07291 23.495,-2.81675 19.46626,-2.18211 25.61112,-3.67969 25.13983,-6.12691 -0.64683,-3.35872 -6.58307,-5.39187 -15.88524,-5.44064 -5.55531,-0.0291 -9.70276,-0.74427 -11.66645,-2.01163 -2.33069,-1.50421 -7.44793,-2.0421 -21.77033,-2.28835 -10.29789,-0.17705 -19.4651,-0.78026 -20.37154,-1.34048 -2.01635,-1.24618 -8.9567,-2.05648 -15.77127,-1.84133 -9.28945,0.2933 -13.91399,0.13125 -20.0025,-0.70087 -4.04227,-0.55245 -6.0325,-0.36327 -6.0325,0.57343 0,0.76883 -1.7145,1.39788 -3.81,1.39788 -2.0955,0 -3.81,-0.51454 -3.81,-1.14344 0,-0.62889 -2.71462,-1.04193 -6.0325,-0.91786 -3.31787,0.12408 -7.84324,-0.14793 -10.05635,-0.60445 -2.69304,-0.5555 -5.73782,-0.0626 -9.20617,1.49018 -4.99671,2.2371 -23.04717,2.78671 -25.07051,0.76337 -0.40527,-0.40527 -1.48573,0.0114 -2.40105,0.92735 -1.99639,1.99638 -20.36382,1.50538 -22.91582,-0.6126 -1.12709,-0.93539 -2.37746,-0.94624 -4.07942,-0.0354 -1.34982,0.7224 -2.7932,0.97444 -3.20754,0.56012 -0.41434,-0.41434 -3.48986,-0.0328 -6.83448,0.84786 -3.34463,0.88066 -9.7959,1.76186 -14.33615,1.95821 -4.54025,0.19636 -12.54125,1.02202 -17.78,1.83481 -5.23875,0.81279 -14.43813,1.57658 -20.44307,1.69731 -11.7118,0.23547 -15.30908,1.75039 -14.20269,5.9812 0.3586,1.37132 1.01398,2.54109 1.45638,2.5995 0.44241,0.0584 3.37613,0.29717 6.51938,0.53057 3.14325,0.23341 7.55643,1.16214 9.80704,2.06385 3.68416,1.47606 4.47024,1.35988 7.88608,-1.16557 6.04224,-4.46722 9.98333,-3.11277 9.17407,3.15289 -0.78562,6.08281 9.65814,8.18039 14.43715,2.89964 2.93174,-3.23953 6.21795,-3.17067 11.74651,0.24616 4.16919,2.57672 4.55813,2.6136 6.04975,0.57366 1.26564,-1.73084 2.86451,-2.03933 7.75926,-1.49706 3.39082,0.37565 9.52269,0.95246 13.62636,1.28181 4.10366,0.32935 8.67566,1.44837 10.16,2.48672 3.58186,2.50565 7.95515,2.5081 8.91569,0.005 z m -118.92881,-14.17634 c 0.30674,-0.92127 -0.55037,-1.5875 -2.04231,-1.5875 -2.77738,0 -4.44894,1.65852 -3.06569,3.04176 1.27783,1.27784 4.50415,0.3593 5.108,-1.45426 z"
+         id="path12894"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#c7b078;stroke-width:1.26999998"
+         d="m -27.21429,445.79952 c -19.07069,-1.89892 -41.0257,-6.89539 -58.38933,-13.28811 -15.51423,-5.71183 -22.7765,-8.47192 -26.70067,-10.14783 -13.29928,-5.67977 -16.7857,-7.41822 -19.06733,-9.50761 -1.40653,-1.28803 -4.73982,-3.25379 -7.40731,-4.36834 -6.49882,-2.71538 -24.70561,-14.65954 -29.42507,-19.30366 -2.08466,-2.05138 -6.64778,-5.80696 -10.14028,-8.34573 -3.4925,-2.53877 -9.2075,-7.82855 -12.7,-11.75507 -3.4925,-3.92653 -7.81937,-7.9157 -9.61526,-8.86483 -1.7959,-0.94913 -6.20503,-4.76427 -9.79807,-8.47808 -7.63274,-7.88932 -11.38588,-13.02777 -25.03667,-34.27783 -0.6985,-1.08734 -3.50345,-5.34019 -6.23323,-9.45078 -7.23803,-10.89926 -14.72177,-28.43785 -14.72177,-34.50134 0,-3.21978 -1.68353,-8.63886 -4.57599,-14.72957 -3.08723,-6.50082 -5.12319,-13.25798 -6.25789,-20.76932 -0.92503,-6.12345 -2.40626,-13.70532 -3.29161,-16.84857 -2.9959,-10.63632 -2.21848,-54.05726 1.09144,-60.96 4.04761,-8.44117 6.65631,-20.08126 7.20528,-32.15014 0.56093,-12.33213 6.25996,-28.463482 14.82545,-41.964112 2.72206,-4.29042 4.95462,-8.42066 4.96126,-9.17833 0.0225,-2.56548 8.75769,-18.91038 12.96997,-24.26876 2.3029,-2.92949 7.32008,-9.43152 11.14928,-14.44897 8.59218,-11.258423 37.05324,-36.545019 53.64406,-47.66075 6.80071,-4.55643 12.70601,-8.836286 13.12287,-9.51079 0.41687,-0.674505 5.42961,-3.57185 11.13941,-6.438546 5.70981,-2.866696 12.66746,-6.757365 15.46146,-8.645933 6.8141,-4.6059 37.80842,-18.536524 46.43345,-20.869848 3.79861,-1.027636 10.0498,-2.719761 13.89155,-3.760278 3.84175,-1.040518 9.62158,-2.788402 12.84409,-3.884186 8.4721,-2.880867 36.13945,-5.831904 53.82312,-5.740846 16.66827,0.08583 24.44512,0.835329 49.96597,4.815495 16.62824,2.593296 19.02042,3.164424 33.3572,7.963963 3.91347,1.310115 7.75856,2.382027 8.54465,2.382027 3.75132,0 54.51507,25.137414 59.73674,29.580701 1.14027,0.970291 6.64521,4.531613 12.23321,7.914048 33.94888,20.549374 71.55445,60.096023 84.50824,88.870253 1.57227,3.4925 5.57769,11.77925 8.90093,18.415 8.03384,16.041732 12.66931,30.968342 11.74872,37.831872 -0.43594,3.25019 0.0836,7.66819 1.36101,11.57365 1.13635,3.47421 2.07864,7.63811 2.09398,9.25311 0.0154,1.615 1.2012,5.50812 2.63523,8.65137 10.53574,23.09325 -0.3365,95.57402 -20.479,136.525 -22.48237,45.70811 -71.27513,96.78241 -107.84054,112.88318 -1.78671,0.78674 -8.10632,4.0844 -14.04357,7.32812 -43.91859,23.99422 -106.53522,35.64567 -161.92498,30.13037 z m 58.7531,-101.5992 c 8.43846,-3.13226 4.51435,-14.65691 -5.88984,-17.29777 -16.90576,-4.29114 -30.11128,13.48726 -13.49327,18.16579 5.1776,1.45766 14.2069,1.05331 19.38311,-0.86802 z m -75.81461,-1.16214 c 3.29557,-1.76374 3.09364,-3.2893 -0.77051,-5.8212 -3.6653,-2.40158 -9.47298,-0.80725 -9.47298,2.60054 0,3.69214 5.89771,5.54645 10.24349,3.22066 z m 104.94925,-7.82089 c 8.29743,-3.5401 9.99773,-7.27511 4.84795,-10.64938 -8.32474,-5.45458 -22.26668,-4.67684 -25.03388,1.39649 -3.2098,7.04476 10.56359,13.35825 20.18593,9.25289 z m -94.87274,-5.46943 c 0,-1.05833 -1.12889,-1.905 -2.54,-1.905 -2.53015,0 -3.30343,1.35323 -1.69333,2.96332 1.54677,1.54678 4.23333,0.87514 4.23333,-1.05832 z m 134.61999,-0.627 c 0,-1.89773 -3.74006,-2.44026 -7.58421,-1.10018 -5.56075,1.93849 -4.7237,3.0853 1.86921,2.56094 3.14325,-0.24998 5.715,-0.90734 5.715,-1.46076 z m -97.79,-3.90149 c 0,-0.74443 -0.55948,-2.39892 -1.24331,-3.67665 -2.39606,-4.47708 1.16093,-5.26294 26.87217,-5.93692 33.4128,-0.87589 46.0403,-1.43676 55.01614,-2.44361 4.191,-0.47013 13.77954,-0.99472 21.30785,-1.16575 15.77057,-0.35828 15.76766,-0.35698 12.95222,-5.80146 -4.62986,-8.95314 1.59962,-17.59585 18.39711,-25.52399 5.96355,-2.81469 12.95628,-6.21123 15.53943,-7.54788 10.37207,-5.36703 28.71463,-1.32706 38.36116,8.4491 3.55972,3.60756 7.76991,7.23396 9.35597,8.05866 5.64214,2.93372 7.5356,-1.98776 2.12795,-5.53098 -1.78382,-1.16881 -6.33214,-4.92237 -10.1074,-8.34125 -10.72757,-9.71495 -18.09734,-12.80688 -31.7343,-13.31393 -15.4373,-0.57399 -14.80079,-0.4392 -13.52716,-2.86436 2.76022,-5.25583 3.19485,-13.47637 0.65358,-12.36199 -3.07374,1.34791 -10.81772,0.92619 -12.42027,-0.67636 -2.24723,-2.24723 -7.2805,-1.43439 -7.99316,1.29085 -0.84674,3.23793 -2.90798,3.31157 -2.90798,0.10389 0,-1.33988 -0.3031,-3.226 -0.67356,-4.19139 -1.06219,-2.76803 6.71958,-6.19392 24.28561,-10.69163 1.57804,-0.40405 3.53468,0.19731 4.8182,1.48084 2.03487,2.03487 2.31199,2.02466 4.7424,-0.17484 2.73981,-2.4795 8.79896,-3.23382 24.96097,-3.10745 8.65584,0.0677 9.31293,-0.12319 10.4167,-3.02638 1.4351,-3.77459 3.99594,-5.21208 11.45967,-6.43267 14.29191,-2.33728 19.2994,-7.35255 10.26662,-10.2826 -5.25697,-1.70524 -7.90824,-5.05693 -6.96692,-8.80747 1.17205,-4.66979 -7.64928,-13.16778 -14.32457,-13.79954 -6.48097,-0.61336 -8.1988,-1.57843 -7.74131,-4.34902 0.39197,-2.37378 -4.8521,-3.78145 -6.80649,-1.82706 -1.13416,1.13417 -12.13386,-1.7237 -21.48149,-5.58116 -4.33578,-1.78924 -9.95231,-1.81862 -41.21083,-0.21562 -2.794,0.14328 -15.77932,-0.19379 -28.85627,-0.74905 -17.33891,-0.73622 -25.51262,-0.59301 -30.18945,0.52894 -5.07417,1.21727 -6.81027,1.20895 -8.31499,-0.0399 -1.28887,-1.06967 -4.24771,-1.33558 -9.18055,-0.82506 -15.16838,1.56981 -30.83063,-0.36899 -29.99144,-3.71258 0.9151,-3.64605 -1.58901,-4.34745 -14.1173,-3.95427 -7.93131,0.24892 -9.46683,-0.1124 -14.23908,-3.35023 -6.16672,-4.18397 -16.24707,-1.44107 -17.11495,4.65704 -0.30732,2.15931 -1.39112,2.8738 -4.84096,3.19139 -2.96926,0.27334 -5.35543,1.53826 -7.18724,3.81 -2.74224,3.4008 -2.74224,3.4008 -6.36008,0.73257 -3.3742,-2.48854 -6.10459,-3.33464 -16.62695,-5.15235 -2.27661,-0.39328 -4.45345,-1.22342 -4.83746,-1.84474 -0.384,-0.62132 -2.69501,-1.12968 -5.1356,-1.12968 -2.64061,0 -6.32941,-1.28563 -9.10986,-3.175 -8.3004,-5.64027 -11.3342,-4.07163 -6.03,3.11781 5.09257,6.90261 -4.47088,9.33609 -15.19781,3.86719 -6.2996,-3.21173 -10.62186,-3.17871 -20.955,0.16007 -4.54025,1.46703 -12.2555,2.80048 -17.145,2.96324 -10.13661,0.33744 -13.18065,0.93806 -14.38905,2.83916 -0.46802,0.7363 -3.99419,2.61101 -7.83594,4.16602 -9.87642,3.99765 -16.4242,7.50714 -21.77276,11.66982 -2.54527,1.98093 -5.4989,3.60169 -6.56363,3.60169 -3.82054,0 -6.07596,1.64441 -7.74971,5.65025 -1.04087,2.49116 -3.24135,4.71002 -5.66768,5.71505 -10.31317,4.27186 2.54889,15.10569 22.04513,18.56881 5.575,0.99029 11.16689,1.47345 12.42643,1.07368 1.25954,-0.39976 4.16078,0.37691 6.4472,1.72592 4.42176,2.60891 21.00385,3.57189 25.08616,1.45684 1.28299,-0.66472 3.07593,-0.31445 4.88823,0.95493 2.15182,1.50719 4.52705,1.809 9.57128,1.21615 9.33554,-1.09722 11.9351,1.71178 5.93199,6.40992 -2.80873,2.19818 -3.86748,3.9136 -3.37437,5.46728 0.56992,1.79563 -0.21351,2.43894 -3.78645,3.10923 -5.65156,1.06023 -7.43816,0.1002 -5.73526,-3.08164 2.06671,-3.8617 1.49419,-5.08642 -2.37782,-5.08642 -7.40408,0 -11.7613,1.5585 -12.92666,4.62362 -0.62657,1.64801 -1.70475,2.99638 -2.39597,2.99638 -2.09144,0 -3.69132,4.25646 -2.98624,7.94486 0.68311,3.57343 5.59797,3.97049 17.93826,1.44914 2.17902,-0.44521 3.13615,0.0561 3.61695,1.89477 0.60547,2.31535 1.46606,2.448 11.64186,1.79442 7.77211,-0.49919 13.21825,-0.13366 18.60805,1.24895 4.191,1.0751 9.1357,1.66325 10.98821,1.307 2.24754,-0.43221 4.14904,0.13311 5.715,1.69907 2.10369,2.10369 4.23189,2.30684 20.54411,1.96116 18.19731,-0.38564 18.19731,-0.38564 17.95691,2.54 -0.62434,7.5984 2.77627,8.59069 5.67392,1.65563 1.80708,-4.32496 2.04809,-4.445 8.92427,-4.445 3.88688,0 8.19013,-0.70138 9.5628,-1.55863 1.88099,-1.17469 3.41241,-1.21262 6.21705,-0.15399 2.04672,0.77256 5.19259,1.12338 6.99083,0.77963 5.79877,-1.10851 8.30508,0.66459 4.74026,3.35354 -3.00005,2.26295 -3.00005,2.26295 2.47911,4.45527 7.81418,3.12663 4.30777,4.69112 -8.35247,3.72673 -19.10623,-1.45542 -18.90792,-1.50947 -17.13557,4.67035 1.42595,4.97198 0.0726,7.19997 -3.24548,5.34307 -1.54695,-0.86573 -3.20452,-0.67813 -5.5199,0.62468 -4.27778,2.40703 -9.83666,0.44773 -11.55539,-4.07287 -1.81713,-4.7794 -9.62593,-3.45391 -16.82338,2.85564 -2.53183,2.21948 -17.82441,1.55847 -22.55838,-0.97508 -2.1165,-1.13271 -4.38998,-0.85876 -10.92736,1.31675 -7.03783,2.34203 -8.81115,2.51659 -12.02825,1.18402 -2.99728,-1.24151 -5.32698,-1.23331 -11.32649,0.0399 -4.28974,0.91035 -9.35184,1.20069 -11.71642,0.67202 -12.73747,-2.84784 -26.09092,-2.04308 -29.93827,1.80428 -2.02836,2.02836 -5.74295,4.07344 -8.25464,4.54464 -6.96374,1.30641 -3.84028,5.28805 5.01637,6.39461 4.06976,0.50849 7.48522,1.76069 8.81739,3.23273 2.32282,2.56668 7.46858,3.23164 8.76577,1.13273 1.13394,-1.83474 5.97857,-1.5609 11.23412,0.635 2.50761,1.04775 6.61548,1.905 9.12858,1.905 2.51311,0 5.63262,0.56908 6.93224,1.26462 1.72866,0.92516 3.38388,0.6787 6.16613,-0.91808 3.59634,-2.06402 4.05268,-2.06373 8.39163,0.005 2.52367,1.20345 5.97094,2.18809 7.66063,2.18809 1.68968,0 4.51087,0.65553 6.2693,1.45672 2.47988,1.1299 3.64221,1.08735 5.18094,-0.18969 1.62609,-1.34953 2.90586,-1.34208 7.09758,0.0413 4.12839,1.36249 5.95445,1.38372 9.47641,0.11011 2.39945,-0.86766 4.99399,-1.18735 5.76564,-0.71044 1.41673,0.87559 22.55936,1.83606 49.4118,2.2447 14.60499,0.22225 14.60499,0.22225 14.60499,4.66725 0,4.14319 0.27276,4.445 4.01734,4.445 2.20954,0 4.44791,0.42862 4.97416,0.9525 1.39163,1.38533 3.0735,1.1659 3.0735,-0.40099 z m -3.57685,-44.04663 c 0.30111,-2.61509 1.60924,-4.05671 5.21768,-5.7501 2.64871,-1.243 5.13873,-3.10146 5.53338,-4.12989 1.3238,-3.44975 9.47214,-2.4702 8.21534,0.9876 -1.19259,3.28111 -8.36001,8.5725 -11.61185,8.5725 -1.20818,0 -2.90814,0.85725 -3.7777,1.905 -2.45057,2.95277 -4.0193,2.25758 -3.57685,-1.58511 z m 56.57036,-1.5077 c -0.47379,-1.81179 -1.37659,-2.26591 -3.43468,-1.7277 -2.36817,0.61929 -2.72683,0.27143 -2.3629,-2.29171 0.43127,-3.03727 4.48262,-4.94798 5.99189,-2.82591 0.43295,0.60875 2.45788,2.17182 4.49986,3.47352 3.5283,2.24917 3.59508,2.45268 1.34476,4.09816 -3.28667,2.40326 -5.28332,2.16311 -6.03893,-0.72636 z m -26.32351,-1.7046 c 0,-2.79777 3.61352,-6.93079 6.63091,-7.58422 1.59176,-0.34471 4.03709,-1.07531 5.43409,-1.62358 1.56984,-0.61609 1.08475,0.16375 -1.27,2.04173 -2.0955,1.67121 -4.8516,4.33231 -6.12467,5.91353 -2.56261,3.18289 -4.67033,3.74816 -4.67033,1.25254 z m 62.97727,-2.00594 c -1.10365,-1.78572 0.594,-5.44956 2.2667,-4.89199 1.8799,0.62663 1.84877,4.99443 -0.0401,5.62405 -0.80911,0.26971 -1.81109,-0.0597 -2.22661,-0.73206 z m 16.44614,-1.52998 c 0.45833,-0.74159 2.23515,-1.88131 3.94848,-2.53272 1.71334,-0.65141 3.66949,-2.77451 4.34698,-4.718 1.48236,-4.25228 16.92159,-9.54258 16.14955,-5.53368 -0.36142,1.87668 -12.73862,8.98578 -15.72409,9.03146 -0.76487,0.0114 -2.42506,1.16427 -3.68933,2.56127 -2.3665,2.61495 -6.51905,3.59842 -5.03159,1.19167 z m -229.9184,-32.62453 c 0,-5.05309 3.84058,-7.26377 7.4357,-4.2801 1.09751,0.91086 3.89151,0.77181 9.1376,-0.45473 4.92092,-1.15052 7.99598,-1.33421 8.81608,-0.52666 2.59638,2.55665 -3.62914,4.39329 -12.81713,3.7813 -2.64684,-0.1763 -5.20502,0.80404 -7.80333,2.99038 -4.88704,4.11218 -4.76892,4.14959 -4.76892,-1.51019 z m 20.955,3.793 c -3.27191,-0.77475 10.45681,-3.10371 28.575,-4.84749 7.40945,-0.71311 8.62254,-0.57183 7.28401,0.84836 -2.42338,2.57123 -29.25304,5.56337 -35.85901,3.99913 z m 49.96825,-0.98991 c -1.07523,-2.802 -0.28691,-3.14207 5.21438,-2.24934 4.4644,0.72447 4.68343,0.93945 2.54807,2.50086 -3.12276,2.28342 -6.83587,2.1631 -7.76245,-0.25152 z m 12.19865,-0.61923 c -1.28187,-0.79224 -0.85375,-1.44635 1.71167,-2.61525 2.91644,-1.32881 3.79305,-1.22215 5.94245,0.72303 1.80677,1.63511 2.97799,1.90849 4.13265,0.96463 0.88785,-0.72576 7.88379,-1.33588 15.58133,-1.35885 10.29178,-0.0307 12.96821,0.26299 10.16518,1.11555 -5.11617,1.55613 -35.38223,2.50032 -37.53328,1.17089 z m 49.59309,-1.231 c 0,-1.52556 5.97485,-1.52556 9.525,0 1.91114,0.82125 1.04643,1.11358 -3.4925,1.18074 -3.31787,0.0491 -6.0325,-0.48224 -6.0325,-1.18074 z m 77.13672,-1.47839 c 1.73851,-2.12246 10.49328,-2.97592 10.49328,-1.02293 0,0.67722 -1.28588,1.23912 -2.8575,1.24866 -1.57163,0.0102 -4.28625,0.3944 -6.0325,0.85524 -2.72234,0.71844 -2.95092,0.56433 -1.60328,-1.08097 z m -279.49281,-37.47105 c -1.12957,-1.12957 2.89763,-5.50056 5.06794,-5.50056 2.11971,0 2.22596,2.78421 0.18417,4.826 -1.68751,1.68752 -3.94873,1.97794 -5.25211,0.67456 z m -24.97389,18.78494 c 0,-1.65533 -3.2802,-4.07196 -4.23333,-3.11883 -0.95313,0.95313 1.4635,4.23333 3.11883,4.23333 0.61298,0 1.1145,-0.50152 1.1145,-1.1145 z m 429.75909,-14.86437 c 1.05408,-2.74686 1.0803,-2.69398 -1.69559,-3.41988 -3.25444,-0.85105 -4.08009,0.14846 -2.52416,3.05573 1.55296,2.90174 3.19173,3.04315 4.21975,0.36415 z M 22.82991,135.92034 c 3.77532,0.12433 9.60342,-0.68385 12.95135,-1.79585 4.3623,-1.44893 6.76679,-1.65807 8.48574,-0.73812 1.5606,0.83522 5.25351,0.8794 10.57009,0.12649 4.49431,-0.63647 14.73183,-1.19718 22.75004,-1.246 8.01822,-0.0488 18.29332,-0.31612 22.83357,-0.594 4.54025,-0.27788 15.39875,-0.60966 24.13,-0.7373 12.14291,-0.17752 17.30424,-0.7952 21.95451,-2.62739 3.34373,-1.31742 8.81214,-2.39531 12.15199,-2.39531 9.97989,0 23.10737,-4.43734 29.6171,-10.01113 5.20589,-4.45743 2.61537,-5.62462 -15.83374,-7.13406 -4.04531,-0.33097 -8.53509,-1.23328 -9.97729,-2.00511 -1.75306,-0.93821 -4.36649,-1.02525 -7.88487,-0.2626 -8.15391,1.76746 -36.46011,1.01448 -37.70701,-1.00306 -0.59807,-0.9677 -4.1091,-1.83357 -8.42074,-2.07669 -17.40881,-0.98158 -61.37232,-2.85707 -75.8134,-3.23419 -4.63981,-0.12116 -6.61006,0.41497 -7.87323,2.14245 -1.48948,2.03699 -2.68588,2.17758 -10.32154,1.21285 -4.75023,-0.60017 -10.27962,-0.94376 -12.28754,-0.76354 -2.2348,0.20059 -4.24627,-0.48674 -5.18616,-1.77211 -1.30095,-1.779152 -3.65236,-2.035842 -15.39923,-1.681062 -7.62512,0.23029 -19.57884,0.27465 -26.56384,0.0986 -14.04643,-0.35411 -23.90974,0.661162 -22.86386,2.353432 0.37415,0.60541 0.15312,1.42656 -0.4912,1.82476 -0.64432,0.39821 -0.87273,1.86645 -0.50759,3.26276 0.79263,3.03101 0.80726,3.02924 -5.79438,0.70005 -6.90837,-2.43741 -25.02237,-2.32428 -29.88322,0.18665 -2.36907,1.22377 -5.28108,1.6145 -8.255,1.10763 -2.52711,-0.4307 -5.73775,-0.79183 -7.13475,-0.80248 -1.397,-0.0102 -7.96925,-0.0784 -14.605,-0.15049 -28.04917,-0.3048 -34.85866,0.4692 -44.88588,5.102 -4.77999,2.20845 -10.21507,4.01537 -12.07797,4.01537 -4.7245,0 -11.21681,6.05711 -8.87318,8.27838 3.64079,3.45068 15.76613,5.64849 31.03292,5.62493 8.31552,-0.0128 17.97662,0.12827 21.46912,0.31354 3.4925,0.18527 11.49349,0.18451 17.77999,-0.001 6.2865,-0.18619 14.859,0.28345 19.05,1.04369 5.97333,1.08354 9.46031,0.99246 16.13609,-0.4214 8.72095,-1.84703 20.29566,-0.83512 32.8677,2.87341 3.4831,1.02746 6.00242,0.90732 10.715,-0.51096 7.39193,-2.22464 36.06901,-1.30666 44.35505,1.41985 3.49192,1.14901 8.00386,1.4295 14.03615,0.87255 4.8895,-0.45143 11.9789,-0.71908 15.75421,-0.5948 z M -11.76426,85.249458 c 3.60802,0.0707 6.92787,-0.46656 7.37745,-1.19401 1.22838,-1.98755 7.7547,-1.58987 9.55751,0.5824 2.34254,2.82259 7.98341,2.37833 9.95851,-0.78429 1.91621,-3.06836 7.36964,-2.79928 14.40574,0.7108 4.91605,2.45244 6.16847,2.46193 10.73308,0.0813 4.44387,-2.31764 9.78178,-2.4487 13.25036,-0.32533 2.18829,1.33961 2.79038,1.24045 3.85588,-0.635 1.81015,-3.18622 8.08234,-2.83502 11.3406,0.635 2.68312,2.8575 2.68312,2.8575 4.92209,0.11545 1.65958,-2.03253 4.1823,-2.93476 9.74885,-3.48661 4.13044,-0.40949 11.79614,-1.26445 17.03489,-1.89992 5.23875,-0.63547 15.24,-1.79978 22.225,-2.58737 20.13939,-2.27079 26.24951,-3.74221 25.77483,-6.20703 -0.64683,-3.35871 -6.58307,-5.39187 -15.88524,-5.44064 -5.55531,-0.0291 -9.70276,-0.74427 -11.66645,-2.01163 -2.33025,-1.50392 -7.44526,-2.04204 -21.74738,-2.28794 -17.14206,-0.29473 -18.92552,-0.54655 -21.40063,-3.02165 -1.48507,-1.48507 -3.12876,-2.77307 -3.65263,-2.86221 -5.19309,-0.88368 -8.32123,-1.22106 -14.36653,-1.54949 -3.88521,-0.21107 -7.68078,-0.76494 -8.4346,-1.23082 -1.456,-0.89986 -20.60158,-2.41488 -28.70241,-2.27127 -2.65707,0.0471 -6.00409,0.90728 -7.43782,1.91151 -2.29225,1.60554 -3.24176,1.55558 -7.86897,-0.41406 -6.18316,-2.63197 -8.17449,-2.22002 -9.78842,2.02492 -1.66969,4.39164 -9.61353,3.93001 -9.14122,-0.53121 0.22188,-2.09572 -0.36215,-2.8575 -2.19075,-2.8575 -2.09297,0 -2.37094,0.6117 -1.73127,3.81 0.77809,3.89044 -2.09831,5.6178 -4.06252,2.43965 -1.19263,-1.9297 -13.32918,-0.13794 -16.1864,2.38966 -1.98359,1.75475 -2.96965,1.79936 -7.62,0.34479 -7.01614,-2.19456 -14.0963,-1.92737 -18.07908,0.68226 -2.05525,1.34665 -6.51434,2.23547 -12.38221,2.46811 -5.04367,0.19997 -13.45654,1.02461 -18.69529,1.83254 -5.23875,0.80794 -14.43813,1.56775 -20.44307,1.68848 -11.7118,0.23547 -15.30908,1.75039 -14.20269,5.98121 0.3586,1.37132 1.01398,2.54109 1.45638,2.5995 0.44241,0.0584 3.37613,0.29716 6.51938,0.53056 3.14325,0.23342 7.55643,1.16214 9.80704,2.06385 3.68416,1.47606 4.47024,1.35988 7.88608,-1.16556 6.04224,-4.46723 9.98333,-3.11277 9.17407,3.15289 -0.78562,6.0828 9.65814,8.18038 14.43715,2.89963 2.91568,-3.22178 6.21505,-3.17246 11.62949,0.17386 4.14041,2.55891 4.45065,2.58004 6.79057,0.46243 2.09238,-1.89357 3.24742,-2.01824 7.87628,-0.85008 2.98338,0.7529 9.11405,1.69135 13.62372,2.08546 4.50966,0.3941 8.79591,1.32454 9.525,2.06763 0.76334,0.77801 3.21096,1.01176 5.7706,0.55109 2.44475,-0.43999 7.39701,-0.74209 11.00503,-0.67136 z m -126.89193,-14.89911 c 0.30674,-0.92127 -0.55037,-1.5875 -2.04231,-1.5875 -2.77738,0 -4.44894,1.65852 -3.06569,3.04176 1.27783,1.27784 4.50415,0.3593 5.108,-1.45426 z"
+         id="path12892"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#aeae8e;stroke-width:1.26999998"
+         d="m -27.21429,445.79952 c -19.07069,-1.89892 -41.0257,-6.89539 -58.38933,-13.28811 -15.51423,-5.71183 -22.7765,-8.47192 -26.70067,-10.14783 -13.29928,-5.67977 -16.7857,-7.41822 -19.06733,-9.50761 -1.40653,-1.28803 -4.73982,-3.25379 -7.40731,-4.36834 -6.49882,-2.71538 -24.70561,-14.65954 -29.42507,-19.30366 -2.08466,-2.05138 -6.64778,-5.80696 -10.14028,-8.34573 -3.4925,-2.53877 -9.2075,-7.82855 -12.7,-11.75507 -3.4925,-3.92653 -7.81937,-7.9157 -9.61526,-8.86483 -1.7959,-0.94913 -6.20503,-4.76427 -9.79807,-8.47808 -7.63274,-7.88932 -11.38588,-13.02777 -25.03667,-34.27783 -0.6985,-1.08734 -3.50345,-5.34019 -6.23323,-9.45078 -7.23803,-10.89926 -14.72177,-28.43785 -14.72177,-34.50134 0,-3.21978 -1.68353,-8.63886 -4.57599,-14.72957 -3.08723,-6.50082 -5.12319,-13.25798 -6.25789,-20.76932 -0.92503,-6.12345 -2.40626,-13.70532 -3.29161,-16.84857 -2.9959,-10.63632 -2.21848,-54.05726 1.09144,-60.96 4.04761,-8.44117 6.65631,-20.08126 7.20528,-32.15014 0.56093,-12.33213 6.25996,-28.463482 14.82545,-41.964112 2.72206,-4.29042 4.95462,-8.42066 4.96126,-9.17833 0.0225,-2.56548 8.75769,-18.91038 12.96997,-24.26876 2.3029,-2.92949 7.32008,-9.43152 11.14928,-14.44897 8.59218,-11.258423 37.05324,-36.545019 53.64406,-47.66075 6.80071,-4.55643 12.70601,-8.836286 13.12287,-9.51079 0.41687,-0.674505 5.42961,-3.57185 11.13941,-6.438546 5.70981,-2.866696 12.66746,-6.757365 15.46146,-8.645933 6.8141,-4.6059 37.80842,-18.536524 46.43345,-20.869848 3.79861,-1.027636 10.0498,-2.719761 13.89155,-3.760278 3.84175,-1.040518 9.62158,-2.788402 12.84409,-3.884186 8.4721,-2.880867 36.13945,-5.831904 53.82312,-5.740846 16.66827,0.08583 24.44512,0.835329 49.96597,4.815495 16.62824,2.593296 19.02042,3.164424 33.3572,7.963963 3.91347,1.310115 7.75856,2.382027 8.54465,2.382027 3.75132,0 54.51507,25.137414 59.73674,29.580701 1.14027,0.970291 6.64521,4.531613 12.23321,7.914048 33.94888,20.549374 71.55445,60.096023 84.50824,88.870253 1.57227,3.4925 5.57769,11.77925 8.90093,18.415 8.03384,16.041732 12.66931,30.968342 11.74872,37.831872 -0.43594,3.25019 0.0836,7.66819 1.36101,11.57365 1.13635,3.47421 2.07864,7.63811 2.09398,9.25311 0.0154,1.615 1.2012,5.50812 2.63523,8.65137 10.53574,23.09325 -0.3365,95.57402 -20.479,136.525 -22.48237,45.70811 -71.27513,96.78241 -107.84054,112.88318 -1.78671,0.78674 -8.10632,4.0844 -14.04357,7.32812 -43.91859,23.99422 -106.53522,35.64567 -161.92498,30.13037 z m 58.7531,-101.5992 c 8.43846,-3.13226 4.51435,-14.65691 -5.88984,-17.29777 -16.90576,-4.29114 -30.11128,13.48726 -13.49327,18.16579 5.1776,1.45766 14.2069,1.05331 19.38311,-0.86802 z m -75.81461,-1.16214 c 3.29557,-1.76374 3.09364,-3.2893 -0.77051,-5.8212 -3.6653,-2.40158 -9.47298,-0.80725 -9.47298,2.60054 0,3.69214 5.89771,5.54645 10.24349,3.22066 z m 104.94925,-7.82089 c 8.29743,-3.5401 9.99773,-7.27511 4.84795,-10.64938 -8.32474,-5.45458 -22.26668,-4.67684 -25.03388,1.39649 -3.2098,7.04476 10.56359,13.35825 20.18593,9.25289 z m -94.87274,-5.46943 c 0,-1.05833 -1.12889,-1.905 -2.54,-1.905 -2.53015,0 -3.30343,1.35323 -1.69333,2.96332 1.54677,1.54678 4.23333,0.87514 4.23333,-1.05832 z m 134.61999,-0.627 c 0,-1.89773 -3.74006,-2.44026 -7.58421,-1.10018 -5.56075,1.93849 -4.7237,3.0853 1.86921,2.56094 3.14325,-0.24998 5.715,-0.90734 5.715,-1.46076 z m -97.79,-3.90149 c 0,-0.74443 -0.55948,-2.39892 -1.24331,-3.67665 -2.39606,-4.47708 1.16093,-5.26294 26.87217,-5.93692 33.4128,-0.87589 46.0403,-1.43676 55.01614,-2.44361 4.191,-0.47013 13.77954,-0.99472 21.30785,-1.16575 15.76308,-0.35811 15.76633,-0.35956 12.9634,-5.77984 -2.29566,-4.43933 -1.66229,-12.66375 0.97525,-12.66375 0.81202,0 3.50075,-1.88753 5.97495,-4.19451 4.19993,-3.9161 9.10924,-6.01623 22.72104,-9.71977 4.52986,-1.2325 5.40648,-1.94622 4.87705,-3.97077 -2.53938,-9.71059 26.87068,-5.10917 37.73823,5.90441 3.55972,3.60756 7.76991,7.23396 9.35597,8.05866 5.64214,2.93372 7.5356,-1.98776 2.12795,-5.53098 -1.78382,-1.16881 -6.3403,-4.92975 -10.12552,-8.35767 -7.37287,-6.6769 -15.4553,-11.39937 -19.50986,-11.39937 -1.37494,0 -3.38447,-0.88458 -4.46562,-1.96573 -1.32642,-1.32641 -2.6983,-1.61338 -4.21821,-0.88234 -2.05218,0.98704 -4.46448,1.07005 -14.52467,0.49979 -3.5408,-0.20071 -3.40937,-3.15264 0.48169,-10.81895 1.62237,-3.19642 -0.46325,-5.2716 -3.63066,-3.61248 -2.62389,1.37444 -10.25833,0.60239 -11.33935,-1.14672 -1.38838,-2.24646 -7.1105,-1.2069 -7.79651,1.41643 -0.8546,3.26796 -2.90798,3.29888 -2.90798,0.0438 0,-4.23652 7.49711,-10.67637 14.54135,-12.49071 3.52751,-0.90854 7.60931,-1.95804 9.0707,-2.33222 1.57804,-0.40405 3.53468,0.1973 4.8182,1.48083 2.03487,2.03487 2.31199,2.02466 4.7424,-0.17484 2.73981,-2.4795 8.79896,-3.23382 24.96097,-3.10745 8.65584,0.0677 9.31293,-0.12319 10.4167,-3.02637 1.4351,-3.77459 3.99594,-5.21208 11.45967,-6.43268 14.29191,-2.33728 19.2994,-7.35255 10.26662,-10.28259 -5.25697,-1.70524 -7.90824,-5.05694 -6.96692,-8.80748 1.17205,-4.66979 -7.64928,-13.16778 -14.32457,-13.79954 -6.48097,-0.61336 -8.1988,-1.57843 -7.74131,-4.34901 0.39197,-2.37379 -4.8521,-3.78145 -6.80649,-1.82706 -1.14145,1.14145 -12.70494,-1.85495 -21.10017,-5.46761 -5.28071,-2.2724 -31.89151,-2.68012 -35.8724,-0.5496 -2.13905,1.14478 -5.92515,1.12444 -16.31753,-0.0876 -17.10565,-1.99506 -39.95236,-1.95011 -48.47887,0.0954 -5.7673,1.38356 -6.81824,1.31914 -8.66817,-0.5313 -2.78562,-2.78637 -0.29135,-4.50703 6.57315,-4.53445 9.37472,-0.0375 4.47289,-3.14492 -6.23989,-3.95572 -5.53213,-0.4187 -11.23918,-1.13822 -12.68231,-1.59893 -1.44314,-0.46071 -5.63153,-0.26727 -9.30755,0.42987 -4.96772,0.94212 -9.67403,0.79209 -18.33113,-0.58435 -7.90674,-1.25712 -13.23308,-1.4869 -16.58463,-0.71544 -4.21418,0.97001 -5.65185,0.6853 -9.81727,-1.94422 -5.12375,-3.23448 -9.32405,-3.94889 -10.64554,-1.81065 -0.4317,0.6985 -3.03718,1.27 -5.78996,1.27 -5.30818,0 -6.25171,1.19435 -3.04557,3.8552 5.74866,4.77097 -12.44763,7.92496 -30.53447,5.2926 -8.50316,-1.23755 -8.95132,-4.64548 -0.635,-4.82872 7.68418,-0.16932 8.71462,-0.49009 5.715,-1.77908 -3.56073,-1.5301 -14.29435,-1.5301 -15.24,0 -1.20123,1.94363 -4.23415,1.53303 -10.6559,-1.44262 -3.21975,-1.49194 -7.13911,-3.30256 -8.70968,-4.02359 -4.68409,-2.15039 -4.65812,-0.15702 0.0903,6.92747 2.46844,3.6829 4.20961,7.14675 3.86925,7.69746 -1.32966,2.15144 -10.14183,0.93166 -14.46161,-2.0018 -3.93134,-2.66967 -14.70923,-5.88696 -19.72121,-5.88696 -1.01135,0 -4.16126,2.09817 -6.99977,4.66259 -4.85852,4.38939 -5.69591,4.67561 -14.29116,4.88462 -10.30884,0.2507 -13.40265,0.8355 -14.62927,2.76526 -0.46802,0.7363 -3.99419,2.61101 -7.83594,4.16602 -9.87642,3.99765 -16.4242,7.50714 -21.77276,11.66982 -2.54527,1.98093 -5.4989,3.60169 -6.56363,3.60169 -3.82054,0 -6.07596,1.64441 -7.74971,5.65025 -1.04087,2.49116 -3.24135,4.71002 -5.66768,5.71505 -10.31317,4.27186 2.54889,15.10569 22.04513,18.56881 5.575,0.99029 11.16689,1.47345 12.42643,1.07368 1.25954,-0.39976 4.16078,0.37691 6.4472,1.72592 4.42176,2.60891 21.00385,3.57189 25.08616,1.45684 1.28299,-0.66472 3.07593,-0.31445 4.88823,0.95493 2.15182,1.50719 4.52705,1.809 9.57128,1.21615 9.33554,-1.09722 11.9351,1.71178 5.93199,6.40992 -2.80873,2.19818 -3.86748,3.9136 -3.37437,5.46728 0.56992,1.79563 -0.21351,2.43894 -3.78645,3.10923 -5.65156,1.06023 -7.43816,0.1002 -5.73526,-3.08164 2.06671,-3.8617 1.49419,-5.08642 -2.37782,-5.08642 -6.42954,0 -11.78648,1.57198 -12.51702,3.67308 -0.39048,1.12307 -1.84651,2.95926 -3.23562,4.08045 -5.10275,4.11857 -4.0254,12.56647 1.60259,12.56647 2.59725,0 6.37918,0.5604 8.40429,1.24533 5.22971,1.76879 20.37319,3.81766 37.05168,5.01297 10.34976,0.74174 14.95462,1.60088 16.61,3.09898 1.74756,1.58153 4.9001,2.07272 13.30283,2.07272 6.495,0 11.0125,0.52098 11.0125,1.27 0,1.91849 3.62083,1.52535 7.59998,-0.82519 4.27457,-2.52505 7.25561,-1.62123 8.89765,2.69764 0.67072,1.76416 1.67799,3.20755 2.23835,3.20755 1.94866,0 6.66402,-3.96447 6.66402,-5.60282 0,-1.10446 2.83008,-1.93093 8.5725,-2.50342 4.71487,-0.47005 10.33771,-1.17578 12.4952,-1.56829 2.52392,-0.45916 4.56629,-0.0701 5.72757,1.09124 0.99268,0.99268 3.22797,2.18197 4.9673,2.64286 4.58557,1.21509 6.78587,2.91736 5.8106,4.4954 -1.03455,1.67394 -19.64514,0.83107 -21.44756,-0.97136 -0.76876,-0.76875 -2.19638,-0.66463 -3.90609,0.28492 -1.48199,0.82306 -2.82762,1.53432 -2.99031,1.58059 -0.16267,0.0462 0.26042,2.02346 0.94022,4.39378 1.42595,4.97198 0.0726,7.19997 -3.24548,5.34307 -1.54695,-0.86573 -3.20452,-0.67813 -5.5199,0.62468 -4.27778,2.40703 -9.83666,0.44773 -11.55539,-4.07287 -1.81713,-4.7794 -9.62593,-3.45391 -16.82338,2.85564 -2.53183,2.21948 -17.82441,1.55847 -22.55838,-0.97508 -2.1165,-1.13271 -4.38998,-0.85876 -10.92736,1.31675 -7.03783,2.34203 -8.81115,2.51659 -12.02825,1.18402 -2.99728,-1.24151 -5.32698,-1.23331 -11.32649,0.0399 -4.28974,0.91035 -9.35184,1.20069 -11.71642,0.67202 -12.73747,-2.84784 -26.09092,-2.04308 -29.93827,1.80428 -2.02836,2.02836 -5.74295,4.07344 -8.25464,4.54464 -6.96374,1.30641 -3.84028,5.28805 5.01637,6.39461 4.06976,0.50849 7.48522,1.76069 8.81739,3.23273 2.32282,2.56668 7.46858,3.23164 8.76577,1.13273 1.13394,-1.83474 5.97857,-1.5609 11.23412,0.635 2.50761,1.04775 6.61548,1.905 9.12858,1.905 2.51311,0 5.63262,0.56908 6.93224,1.26462 1.72866,0.92516 3.38388,0.6787 6.16613,-0.91808 3.59634,-2.06402 4.05268,-2.06373 8.39163,0.005 2.52367,1.20345 5.97094,2.18809 7.66063,2.18809 1.68968,0 4.51087,0.65553 6.2693,1.45672 2.47988,1.1299 3.64221,1.08735 5.18094,-0.18969 1.62609,-1.34953 2.90586,-1.34208 7.09758,0.0413 4.12839,1.36249 5.95445,1.38372 9.47641,0.11011 2.39945,-0.86766 4.99399,-1.18735 5.76564,-0.71044 1.41673,0.87559 22.55936,1.83606 49.4118,2.2447 14.60499,0.22225 14.60499,0.22225 14.60499,4.66725 0,4.14319 0.27276,4.445 4.01734,4.445 2.20954,0 4.44791,0.42862 4.97416,0.9525 1.39163,1.38533 3.0735,1.1659 3.0735,-0.40099 z m 52.99351,-45.55433 c -0.47379,-1.81179 -1.37659,-2.26591 -3.43468,-1.7277 -2.36817,0.61929 -2.72683,0.27143 -2.3629,-2.29171 0.43127,-3.03727 4.48262,-4.94798 5.99189,-2.82591 0.43295,0.60875 2.45788,2.17182 4.49986,3.47352 3.5283,2.24917 3.59508,2.45268 1.34476,4.09816 -3.28667,2.40326 -5.28332,2.16311 -6.03893,-0.72636 z M 5.68663,279.3902 c -1.07762,-1.74364 2.8312,-4.88735 6.07681,-4.88735 3.39811,0 2.78687,3.17922 -0.93749,4.87614 -3.97779,1.81241 -4.02604,1.81251 -5.13932,0.0114 z m 86.44469,-3.67297 c -1.19019,-1.92577 2.03573,-5.43602 3.57819,-3.89357 1.42431,1.42432 0.31621,5.21919 -1.524,5.21919 -0.6792,0 -1.60358,-0.59653 -2.05419,-1.32562 z m 27.20973,-7.88579 c 1.05035,-2.83935 1.4259,-3.0995 9.43533,-6.53576 7.0176,-3.01075 6.04487,1.1428 -1.1597,4.95193 -8.60395,4.54899 -9.43107,4.7073 -8.27563,1.58383 z m -240.26832,-23.80859 c 0.39017,-1.49202 1.73231,-2.54 3.25294,-2.54 2.50826,0 2.51728,0.079 0.29004,2.54 -1.26427,1.397 -2.72808,2.54 -3.25294,2.54 -0.52484,0 -0.65537,-1.143 -0.29004,-2.54 z m 1.63798,-4.94412 c 0,-1.49144 3.71386,-2.8852 5.21532,-1.95725 0.64686,0.39979 0.84732,1.25889 0.44544,1.90913 -0.93384,1.51098 -5.66076,1.55117 -5.66076,0.0481 z m 12.1577,0.0141 c -1.0358,-1.67596 9.76236,-4.13792 11.31947,-2.58081 0.84214,0.84214 0.63175,1.66797 -0.62779,2.46427 -2.4007,1.51778 -9.77599,1.59817 -10.69168,0.11659 z m -83.7038,-35.28942 c -1.12957,-1.12957 2.89763,-5.50056 5.06794,-5.50056 2.11971,0 2.22596,2.78421 0.18417,4.826 -1.68751,1.68752 -3.94873,1.97794 -5.25211,0.67456 z m -24.97389,18.78494 c 0,-1.65533 -3.2802,-4.07196 -4.23333,-3.11883 -0.95313,0.95313 1.4635,4.23333 3.11883,4.23333 0.61298,0 1.1145,-0.50152 1.1145,-1.1145 z m 429.75909,-14.86437 c 1.05408,-2.74686 1.0803,-2.69398 -1.69559,-3.41988 -3.25444,-0.85105 -4.08009,0.14846 -2.52416,3.05573 1.55296,2.90174 3.19173,3.04315 4.21975,0.36415 z M 14.05639,138.61506 c 3.88584,-1.98242 5.61185,-2.24883 7.8869,-1.21733 2.10794,0.95571 5.33823,0.8104 11.69454,-0.52607 5.82066,-1.22385 9.80999,-1.45575 11.83017,-0.68767 4.65238,1.76883 11.27593,1.3827 13.0551,-0.76107 1.31216,-1.58106 4.42178,-1.82883 17.61799,-1.40372 8.81354,0.28392 16.88186,0.0702 17.92961,-0.47477 1.62324,-0.84439 27.318,-2.86089 33.02,-2.59135 8.37176,0.39573 13.34369,-0.25101 18.96579,-2.46704 3.59053,-1.41525 9.26086,-2.57319 12.60071,-2.57319 9.97989,0 23.10737,-4.43734 29.6171,-10.01113 5.20589,-4.45743 2.61537,-5.62463 -15.83374,-7.13406 -4.04531,-0.33097 -8.53509,-1.23328 -9.97729,-2.00511 -1.75306,-0.93822 -4.36649,-1.02525 -7.88487,-0.2626 -6.6904,1.45022 -36.41792,1.04191 -37.48197,-0.51482 -1.17522,-1.71935 -11.99192,-5.31984 -21.75573,-7.241672 -1.74625,-0.34371 -13.17625,-0.99012 -25.4,-1.43643 -45.63578,-1.66629 -51.16352,-1.63033 -57.56899,0.37461 -4.96827,1.55509 -7.38184,1.64921 -12.7,0.49524 -6.64873,-1.44269 -69.159,-1.98603 -72.68804,-0.63182 -2.90739,1.11567 -2.11039,2.967792 1.27708,2.967792 3.29057,0 5.35051,1.87679 3.3899,3.08851 -0.64432,0.39821 -0.87273,1.86645 -0.50759,3.26276 0.79263,3.03101 0.80726,3.02924 -5.79438,0.70005 -6.90837,-2.43741 -25.02237,-2.32428 -29.88322,0.18665 -2.36907,1.22377 -5.28108,1.6145 -8.255,1.10763 -2.52711,-0.4307 -5.73775,-0.79183 -7.13475,-0.80248 -1.397,-0.0102 -7.96925,-0.0784 -14.605,-0.15049 -28.04917,-0.3048 -34.85866,0.4692 -44.88588,5.102 -4.77999,2.20845 -10.21507,4.01537 -12.07797,4.01537 -4.7245,0 -11.21681,6.05711 -8.87318,8.27838 4.74417,4.49646 16.30694,5.8618 47.42204,5.59962 11.26288,-0.0949 38.96418,1.38592 42.54499,2.27431 2.44475,0.60652 7.90513,0.6405 12.13417,0.0754 8.0027,-1.06917 26.70864,0.33283 35.49083,2.66003 3.99041,1.05743 6.66089,0.90679 12.45067,-0.70231 6.30518,-1.75234 8.34733,-1.81418 14.12673,-0.42772 3.73913,0.897 10.37236,1.34967 14.85433,1.01367 5.25771,-0.39414 9.88013,0.0307 13.17826,1.21123 2.794,1.00006 7.65175,2.2751 10.795,2.83343 3.14325,0.55832 5.88065,1.08235 6.0831,1.1645 0.20245,0.0822 2.60661,-0.99263 5.34259,-2.38841 z M -11.76426,85.249458 c 3.60802,0.0707 6.92787,-0.46656 7.37745,-1.19401 1.22838,-1.98755 7.7547,-1.58987 9.55751,0.5824 2.34254,2.82259 7.98341,2.37833 9.95851,-0.78429 1.91621,-3.06836 7.36964,-2.79928 14.40574,0.7108 4.91605,2.45244 6.16847,2.46193 10.73308,0.0813 4.44387,-2.31764 9.78178,-2.4487 13.25036,-0.32533 2.18829,1.33961 2.79038,1.24045 3.85588,-0.635 1.81015,-3.18622 8.08234,-2.83502 11.3406,0.635 2.68312,2.8575 2.68312,2.8575 4.92209,0.11545 1.65958,-2.03253 4.1823,-2.93476 9.74885,-3.48661 4.13044,-0.40949 11.79614,-1.26445 17.03489,-1.89992 5.23875,-0.63547 15.24,-1.79978 22.225,-2.58737 20.13939,-2.27079 26.24951,-3.74221 25.77483,-6.20703 -0.64683,-3.35871 -6.58307,-5.39187 -15.88524,-5.44064 -5.55531,-0.0291 -9.70276,-0.74427 -11.66645,-2.01163 -2.33025,-1.50392 -7.44526,-2.04204 -21.74738,-2.28794 -17.14206,-0.29473 -18.92552,-0.54655 -21.40063,-3.02165 -1.48507,-1.48507 -3.12876,-2.77307 -3.65263,-2.86221 -5.19309,-0.88368 -8.32123,-1.22106 -14.36653,-1.54949 -3.88521,-0.21107 -7.68078,-0.76494 -8.4346,-1.23082 -1.456,-0.89986 -20.60158,-2.41488 -28.70241,-2.27127 -2.65707,0.0471 -6.00409,0.90728 -7.43782,1.91151 -2.29225,1.60554 -3.24176,1.55558 -7.86897,-0.41406 -6.18316,-2.63197 -8.17449,-2.22002 -9.78842,2.02492 -1.66969,4.39164 -9.61353,3.93001 -9.14122,-0.53121 0.22188,-2.09572 -0.36215,-2.8575 -2.19075,-2.8575 -2.09297,0 -2.37094,0.6117 -1.73127,3.81 0.77809,3.89044 -2.09831,5.6178 -4.06252,2.43965 -1.19263,-1.9297 -13.32918,-0.13794 -16.1864,2.38966 -1.98359,1.75475 -2.96965,1.79936 -7.62,0.34479 -7.01614,-2.19456 -14.0963,-1.92737 -18.07908,0.68226 -2.05525,1.34665 -6.51434,2.23547 -12.38221,2.46811 -5.04367,0.19997 -13.45654,1.02461 -18.69529,1.83254 -5.23875,0.80794 -14.43813,1.56775 -20.44307,1.68848 -11.7118,0.23547 -15.30908,1.75039 -14.20269,5.98121 0.3586,1.37132 1.01398,2.54109 1.45638,2.5995 0.44241,0.0584 3.37613,0.29716 6.51938,0.53056 3.14325,0.23342 7.55643,1.16214 9.80704,2.06385 3.68416,1.47606 4.47024,1.35988 7.88608,-1.16556 6.04224,-4.46723 9.98333,-3.11277 9.17407,3.15289 -0.78562,6.0828 9.65814,8.18038 14.43715,2.89963 2.91568,-3.22178 6.21505,-3.17246 11.62949,0.17386 4.14041,2.55891 4.45065,2.58004 6.79057,0.46243 2.09238,-1.89357 3.24742,-2.01824 7.87628,-0.85008 2.98338,0.7529 9.11405,1.69135 13.62372,2.08546 4.50966,0.3941 8.79591,1.32454 9.525,2.06763 0.76334,0.77801 3.21096,1.01176 5.7706,0.55109 2.44475,-0.43999 7.39701,-0.74209 11.00503,-0.67136 z m -126.89193,-14.89911 c 0.30674,-0.92127 -0.55037,-1.5875 -2.04231,-1.5875 -2.77738,0 -4.44894,1.65852 -3.06569,3.04176 1.27783,1.27784 4.50415,0.3593 5.108,-1.45426 z"
+         id="path12890"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#b2a577;stroke-width:1.26999998"
+         d="m -30.38929,445.25488 c -27.73352,-3.30938 -41.51601,-7.05191 -75.565,-20.51906 -16.77665,-6.63554 -19.89089,-8.0905 -24.38913,-11.39443 -2.23803,-1.64382 -6.52428,-4.12097 -9.525,-5.5048 -7.65911,-3.53209 -22.20219,-13.08427 -27.58954,-18.12134 -2.49498,-2.33276 -6.82232,-5.8716 -9.61632,-7.86411 -2.794,-1.99249 -9.1336,-7.67284 -14.088,-12.623 -4.9544,-4.95015 -11.33148,-10.71478 -14.17128,-12.81028 -6.4978,-4.79475 -15.49446,-16.08721 -26.35072,-33.075 -2.794,-4.37202 -7.29088,-11.26585 -9.99306,-15.3196 -7.07799,-10.61824 -13.5729,-25.53688 -14.31684,-32.88541 -0.35357,-3.4925 -2.66641,-10.80132 -5.13965,-16.24184 -3.01224,-6.62618 -4.84218,-12.97165 -5.54299,-19.22079 -0.57541,-5.13092 -2.06443,-12.6811 -3.30892,-16.77816 -3.63786,-11.97641 -3.3024,-54.78433 0.4913,-62.69421 4.45415,-9.28692 6.75813,-19.28033 7.24826,-31.43895 0.51252,-12.71402 5.98214,-28.881392 14.02979,-41.469962 2.27351,-3.55634 5.96649,-10.87111 8.2066,-16.25503 2.24012,-5.38393 5.72706,-11.95618 7.74876,-14.605 13.07852,-17.13548 17.66076,-22.866685 22.16675,-27.724908 6.02371,-6.494585 30.05498,-27.714093 35.73112,-31.550429 2.18962,-1.479898 7.33312,-4.947202 11.43,-7.705117 4.09688,-2.757917 9.10844,-6.366894 11.1368,-8.01995 2.02836,-1.653055 7.74336,-5.044027 12.7,-7.535494 4.95665,-2.491466 11.29807,-6.074634 14.09207,-7.962595 6.81668,-4.606156 37.81139,-18.536224 46.43345,-20.868744 3.79861,-1.027636 10.0498,-2.719761 13.89155,-3.760278 3.84175,-1.040518 9.62158,-2.788402 12.84409,-3.884186 13.74994,-4.675554 56.14853,-7.18521 77.9609,-4.614659 14.84624,1.749603 42.51673,6.251367 46.78319,7.611245 21.06978,6.715701 28.95509,9.514401 35.61579,12.640981 24.92536,11.700126 41.54558,20.333663 45.0678,23.410905 1.14027,0.996215 7.21671,4.977246 13.50321,8.846736 33.59144,20.676332 65.44154,53.752373 80.96403,84.080433 19.057,37.233812 24.62487,51.788872 22.56231,58.980572 -0.84123,2.93323 -0.50519,5.84592 1.2951,11.22522 1.33711,3.99532 2.4577,8.69296 2.49021,10.43921 0.0325,1.74625 1.23243,5.74675 2.66646,8.89 5.62187,12.32253 4.72701,48.29691 -2.02804,81.53023 -14.64893,72.06939 -65.62183,137.39753 -133.98507,171.71858 -6.63575,3.33142 -12.6914,6.57476 -13.45702,7.20742 -3.02407,2.49899 -43.89657,16.88291 -57.66297,20.29287 -28.85254,7.14681 -68.62613,9.35608 -100.32999,5.57293 z m 59.58424,-98.3317 c 5.23869,-1.50243 10.26575,-5.97906 10.26575,-9.1417 0,-2.78832 0.92568,-2.86352 6.51273,-0.52911 3.90746,1.63264 6.73633,1.64013 14.66189,0.0389 8.96863,-1.81215 13.57641,-11.70716 6.76538,-14.52838 -3.80248,-1.57505 -5.44215,-6.2975 -2.2225,-6.40107 1.83364,-0.0589 24.11363,-0.98673 37.1475,-1.54684 4.8895,-0.21012 12.46187,-0.0498 16.8275,0.35635 7.34526,0.68329 10.47936,-0.40275 6.0582,-2.09932 -3.13611,-1.20344 -7.0107,-8.74174 -7.0107,-13.63984 0,-8.27838 8.69186,-14.21825 28.89249,-19.74462 4.01755,-1.0991 5.3975,-2.11458 5.3975,-3.97189 0,-2.22224 0.83376,-2.47641 7.62,-2.32301 4.191,0.0947 7.62,0.61554 7.62,1.15734 0,0.54181 1.10525,0.69607 2.45611,0.34282 12.60541,-3.2964 25.78927,17.59269 16.78581,26.59615 -1.99167,1.99168 -1.83725,3.49392 0.35913,3.49392 1.00159,0 3.43046,-2.3864 5.3975,-5.30312 1.96706,-2.91672 5.29095,-6.24909 7.38645,-7.40526 9.62402,-5.30997 -8.51056,-24.60272 -27.88028,-29.66086 -4.11922,-1.07568 -7.98189,-1.65146 -8.58372,-1.27951 -2.43539,1.50516 -11.33275,-0.96904 -9.86115,-2.74221 1.07408,-1.29417 0.71605,-2.39314 -1.50237,-4.61156 -1.60187,-1.60187 -2.7598,-4.07753 -2.5732,-5.50148 0.35013,-2.67186 -2.73951,-3.2163 -20.40177,-3.5951 -5.66554,-0.12154 -5.72823,-0.30963 -1.43039,-4.29273 2.9624,-2.74546 4.61859,-3.11933 14.92699,-3.36973 6.37718,-0.1549 12.26983,-0.69878 13.09475,-1.20861 0.82492,-0.50982 4.73219,-1.17501 8.68282,-1.47817 3.95061,-0.30317 7.95027,-1.18805 8.88814,-1.9664 1.27422,-1.05751 2.45467,-0.92411 4.67055,0.52779 3.91985,2.56837 12.14859,0.28763 16.7066,-4.63055 2.8436,-3.06828 3.84548,-3.37709 8.76765,-2.70243 4.17503,0.57225 6.06604,0.25286 7.60307,-1.28419 1.12523,-1.12523 3.65833,-2.08605 5.62909,-2.13515 8.33274,-0.20756 8.80407,-1.55192 2.49205,-7.10799 -6.08621,-5.3573 -5.2037,-11.26905 1.30918,-8.76981 4.80126,1.84241 7.00175,-4.31937 2.80394,-7.8516 -2.4552,-2.06591 -3.65695,-4.26743 -3.65695,-6.69927 0,-4.98827 -1.6277,-5.54187 -5.1792,-1.76147 -4.13664,4.40327 -12.565,2.32094 -12.62275,-3.11859 -0.11137,-10.49878 -9.06604,-11.55405 -98.72054,-11.63392 -31.60713,-0.0282 -57.4675,-0.5406 -57.4675,-1.13876 0,-1.58088 3.37528,-2.62932 8.4647,-2.62932 9.06265,0 3.73265,-3.14961 -6.66126,-3.93627 -5.53213,-0.41871 -11.23918,-1.13823 -12.68231,-1.59893 -1.44314,-0.46072 -5.63153,-0.26728 -9.30755,0.42987 -4.96772,0.94211 -9.67403,0.79208 -18.33113,-0.58436 -7.90674,-1.25712 -13.23308,-1.4869 -16.58463,-0.71544 -4.21418,0.97001 -5.65185,0.68531 -9.81727,-1.94422 -5.12375,-3.23448 -9.32405,-3.94888 -10.64554,-1.81065 -0.4317,0.6985 -3.03718,1.27 -5.78996,1.27 -5.51887,0 -5.98656,0.62044 -2.97563,3.94748 3.33223,3.68207 1.20408,4.59909 -12.48728,5.38074 -15.4413,0.88154 -24.46713,-0.21711 -24.46713,-2.97822 0,-1.39245 1.53764,-1.93888 5.715,-2.03092 7.68418,-0.16931 8.71462,-0.49009 5.715,-1.77908 -3.56073,-1.53009 -14.29435,-1.53009 -15.24,0 -1.48333,2.40009 -6.35707,1.03778 -18.71526,-5.23131 -5.44043,-2.75982 -5.80553,0.86216 -0.80311,7.96736 1.30535,1.85408 2.37337,3.9972 2.37337,4.7625 0,2.32832 -10.10356,1.60283 -13.77395,-0.98904 -3.03662,-2.14433 -14.04171,-5.23951 -18.62936,-5.23951 -0.98908,0 -3.67557,1.63738 -5.96999,3.6386 -3.86847,3.37414 -5.11226,3.68291 -17.11147,4.24801 -9.06839,0.42709 -13.89957,1.23829 -16.14782,2.7114 -1.76442,1.1561 -4.33742,2.10199 -5.71777,2.10199 -1.38035,0 -2.8687,0.58082 -3.30744,1.29072 -0.43873,0.70989 -1.33304,0.95985 -1.98735,0.55547 -0.65431,-0.40438 -3.18619,-0.0133 -5.62642,0.86911 -5.62924,2.03557 -9.10921,-0.23106 -5.00516,-3.26004 2.8973,-2.13835 1.67874,-3.92406 -3.56076,-5.21807 -2.16874,-0.53561 -3.4925,-0.34831 -3.4925,0.49415 0,0.74619 -2.286,2.04161 -5.08,2.87871 -4.04713,1.21255 -5.08,2.12667 -5.08,4.49598 0,2.36092 -0.68503,2.97397 -3.32312,2.97397 -2.73897,0 -3.20186,0.4637 -2.6333,2.63788 0.5314,2.03208 -0.33942,3.24514 -3.79187,5.28215 -4.15751,2.45299 -4.4271,2.98565 -3.72699,7.36393 0.58881,3.68218 0.14904,5.48964 -2.00059,8.22245 -2.85773,3.63302 -2.10983,5.33084 3.27347,7.43107 0.79759,0.31118 0.16928,1.46278 -1.4501,2.65782 -3.98884,2.94362 -3.5887,4.33142 1.905,6.6071 2.61937,1.08504 5.55537,2.61976 6.52444,3.41048 0.96907,0.79073 3.54082,1.55679 5.715,1.70237 2.17418,0.14558 11.09681,1.1968 19.82806,2.33603 19.6764,2.56732 23.40735,2.8741 43.12434,3.54584 16.26517,0.55415 18.99833,1.56494 13.89564,5.139 -2.12872,1.49102 -3.41157,1.63721 -5.33477,0.60795 -3.23582,-1.73176 -18.37518,-0.31628 -24.64746,2.30444 -2.68829,1.12324 -6.21542,1.62146 -8.39418,1.18571 -5.22296,-1.0446 -18.89626,3.97844 -18.3716,6.74901 0.46496,2.45537 6.79644,3.43557 9.21676,1.42687 1.86785,-1.55017 3.94037,0.30275 2.65851,2.37683 -0.90502,1.46435 2.28128,2.67307 7.04649,2.67307 2.1582,0 4.05584,0.80746 4.477,1.905 0.46967,1.22393 2.43087,1.905 5.48563,1.905 2.61503,0 6.41151,0.5604 8.43663,1.24533 5.2297,1.76879 20.37318,3.81766 37.05167,5.01297 10.34976,0.74174 14.95462,1.60088 16.61,3.09898 1.74756,1.58153 4.9001,2.07272 13.30283,2.07272 6.495,0 11.0125,0.52098 11.0125,1.27 0,1.89205 4.2448,1.53453 7.05976,-0.59461 2.90583,-2.19785 6.42639,-2.48572 7.57962,-0.61976 1.4256,2.30666 -2.04378,4.98377 -9.1273,7.043 -4.39923,1.27889 -7.57735,3.09448 -8.86992,5.06719 -2.65021,4.04474 -10.22932,4.16565 -17.15418,0.27366 -4.31755,-2.42659 -5.48525,-2.59616 -8.74543,-1.27 -12.19997,4.96269 -11.01718,4.85207 -16.99051,1.589 -5.08255,-2.77647 -5.84671,-2.88177 -8.55892,-1.17959 -3.04571,1.91147 -26.83804,2.2492 -38.53811,0.54702 -4.69503,-0.68304 -5.35671,-0.39062 -8.73129,3.85867 -2.00821,2.52875 -5.53005,5.38272 -7.8263,6.34215 -3.21564,1.34359 -4.02311,2.3253 -3.51397,4.27226 1.02522,3.92041 4.0061,5.88095 6.53777,4.29989 2.49453,-1.55786 7.16703,-0.15656 10.99314,3.29688 3.81907,3.4471 10.70694,4.3782 13.5246,1.82825 2.16507,-1.95937 2.63812,-1.88155 7.29437,1.1998 3.86973,2.56086 5.9717,3.13398 9.43377,2.57217 3.02146,-0.49033 5.39783,-0.0622 7.38635,1.33056 3.6789,2.57681 7.44655,2.65193 7.44655,0.14847 0,-2.71603 4.27814,-2.35606 6.93869,0.58383 1.47495,1.6298 1.98849,3.54016 1.48785,5.53486 -0.67734,2.69874 -0.40489,2.9773 2.38948,2.44312 1.73469,-0.33161 3.15398,-1.32356 3.15398,-2.20436 0,-3.40929 3.59329,-4.88658 6.79045,-2.79171 2.76337,1.81062 3.19134,1.76302 5.57446,-0.62009 2.31643,-2.31645 3.03355,-2.41777 6.98889,-0.98749 3.33011,1.20419 5.39567,1.24936 8.44942,0.18482 2.58867,-0.90241 5.13616,-0.99291 7.08565,-0.25173 2.67934,1.0187 23.16951,1.61525 47.44801,1.38145 8.04187,-0.0775 8.04187,-0.0775 4.68266,3.91473 -4.422,5.25525 -1.88638,9.34568 3.57504,5.76721 2.93172,-1.92094 3.0806,-1.90336 2.50553,0.29571 -0.94014,3.59512 1.29843,4.05493 6.59488,1.35465 4.58352,-2.33682 6.985,-2.36562 6.985,-0.0838 0,0.56996 -1.64023,1.36433 -3.64493,1.76528 -2.09172,0.41834 -3.91028,1.7436 -4.26758,3.10997 -0.56763,2.1706 -1.4371,2.29395 -9.84043,1.39606 -10.89608,-1.16425 -11.15995,-1.33853 -8.40131,-5.54876 3.17874,-4.85136 1.54064,-6.10327 -6.70177,-5.12178 -14.46798,1.72281 -21.60786,5.71451 -12.18962,6.81487 2.00942,0.23476 4.80713,1.23488 6.21715,2.2225 1.41002,0.9876 4.30974,1.79565 6.44384,1.79565 2.30855,0 4.76104,1.02879 6.05498,2.54 2.59688,3.03294 17.80735,3.76258 19.59907,0.94017 0.55858,-0.87992 3.44448,-3.17402 6.4131,-5.09802 5.23012,-3.38971 5.71808,-4.30126 5.93222,-11.08215 0.0944,-2.98578 0.51656,-3.17683 7.08528,-3.20559 4.03857,-0.0177 8.80635,-0.95417 11.30279,-2.22008 2.57609,-1.30631 4.88138,-1.74495 5.715,-1.08742 0.76846,0.60613 3.54034,1.79237 6.15971,2.63607 5.055,1.62822 5.16783,1.89118 3.95632,9.21925 -0.77822,4.70723 -0.77822,4.70723 -4.99266,1.49271 -11.45399,-8.73638 -31.08334,-2.70495 -31.01171,9.52883 0.0446,7.61179 13.4551,11.55064 26.5448,7.79656 z m -44.84337,-26.1692 c -1.11369,-2.90223 -1.02293,-3.07112 1.65023,-3.07112 1.79584,0 2.24956,0.65596 1.75688,2.54 -0.7975,3.04961 -2.34793,3.29131 -3.40711,0.53112 z M -52.16979,288.4422 c -1.31579,-0.76819 -2.0955,-2.87899 -2.0955,-5.67284 0,-2.46685 0.53419,-4.11928 1.19885,-3.7085 1.70152,1.0516 7.43715,-2.97589 7.43715,-5.22228 0,-1.3552 1.49785,-1.88434 5.3975,-1.90678 2.96862,-0.017 7.95056,-0.61935 11.07099,-1.3384 6.00541,-1.38386 11.47151,-0.70481 11.47151,1.42508 0,1.72778 -5.8664,3.89697 -12.065,4.46121 -13.32212,1.21266 -18.415,2.1198 -18.415,3.28013 0,0.69531 1.14582,0.90053 2.54626,0.45605 3.23015,-1.02522 4.34511,1.58769 2.617,6.13297 -1.36218,3.5828 -5.136,4.44489 -9.16376,2.09336 z M 5.1707,278.35753 c 0,-1.50722 4.20294,-3.85468 6.90151,-3.85468 3.04407,0 2.36672,1.95997 -1.24626,3.60614 -3.93972,1.79506 -5.65525,1.87046 -5.65525,0.24854 z m 114.3,-10.59769 c 0,-2.23925 2.2203,-4.0864 6.03425,-5.02012 3.51896,-0.86149 2.70994,0.62968 -2.26266,4.17048 -2.89769,2.06334 -3.77159,2.2602 -3.77159,0.84964 z m -328.66883,-79.766 c 0.32184,-0.96579 1.96636,-1.80082 3.6545,-1.85562 1.68814,-0.0547 4.92672,-0.52883 7.19685,-1.0534 4.07128,-0.94079 5.97804,0.80616 2.38487,2.185 -5.09888,1.95661 -13.80566,2.43288 -13.23622,0.72402 z m 167.95299,153.29898 c 3.02688,-4.95615 -9.33423,-9.58845 -13.76447,-5.15821 -2.60903,2.60902 -1.2356,5.60917 3.47441,7.58957 4.3193,1.81613 8.26543,0.88372 10.29006,-2.43136 z m 144.3923,-0.19905 c 9.72871,-4.38704 5.36918,-17.06091 -5.86857,-17.06091 -10.09693,0 -23.45214,13.53775 -18.36939,18.62049 2.02829,2.0283 18.65379,0.95854 24.23796,-1.55958 z M -53.28607,330.43246 c 1.02752,-0.64982 1.53794,-1.51177 1.13427,-1.91545 -1.00044,-1.00044 -4.90749,0.5932 -4.90749,2.00172 0,1.44752 1.3985,1.41556 3.77322,-0.0862 z m 240.70388,-77.52304 c 0.51242,-0.8291 1.58519,-0.84627 3.07452,-0.0491 1.60826,0.86071 2.99951,0.68517 4.68542,-0.59118 1.31887,-0.99847 3.96957,-1.83357 5.89044,-1.8558 6.20696,-0.0717 3.70186,-2.31913 -3.175,-2.84833 -4.17538,-0.32132 -7.60383,0.10757 -9.17259,1.14742 -1.37778,0.9133 -4.02243,1.66053 -5.87698,1.66053 -1.85456,0 -4.08337,0.85725 -4.95293,1.905 -1.36122,1.64018 -0.86361,1.905 3.57955,1.905 2.83829,0 5.51471,-0.57305 5.94757,-1.27343 z M 14.05639,138.61506 c 3.88584,-1.98242 5.61185,-2.24883 7.8869,-1.21733 2.10794,0.95571 5.33823,0.8104 11.69454,-0.52607 5.82066,-1.22385 9.80999,-1.45575 11.83017,-0.68767 4.65238,1.76883 11.27593,1.3827 13.0551,-0.76107 1.31216,-1.58106 4.42178,-1.82883 17.61799,-1.40372 8.81354,0.28392 16.88186,0.0702 17.92961,-0.47477 1.62324,-0.84439 27.318,-2.86089 33.02,-2.59135 7.89656,0.37326 13.70321,-0.18092 19.47428,-1.85861 3.69025,-1.07278 9.2478,-1.89386 12.35014,-1.82462 16.0796,0.35891 44.50214,-15.06496 31.73574,-17.22184 -3.36388,-0.56832 -6.04609,-2.031 -7.79066,-4.24844 -2.66056,-3.38172 -2.66056,-3.38172 -36.28679,-3.07731 -20.41581,0.18481 -35.71262,-0.22116 -38.93698,-1.03339 -12.17446,-3.066772 -16.61247,-3.580212 -37.75259,-4.367632 -45.21549,-1.68417 -51.0978,-1.64732 -57.51213,0.3604 -4.96662,1.55458 -7.38359,1.64909 -12.7,0.49665 -10.40673,-2.25589 -71.41916,-1.84585 -73.76074,0.49573 -2.0194,2.019402 -5.14435,2.541362 -3.98508,0.66562 0.86643,-1.40193 -17.20358,-0.40427 -22.80018,1.258822 -6.90527,2.05197 -25.14172,3.57386 -50.79999,4.23943 -20.34564,0.52776 -23.7497,1.02745 -27.305,4.00817 -1.04775,0.87842 -5.04825,2.60161 -8.89,3.82933 -3.84175,1.22771 -8.34881,3.56538 -10.01568,5.19483 -1.66688,1.62945 -4.1355,2.96263 -5.48583,2.96263 -3.46746,0 -2.525,4.80724 1.53151,7.81187 4.03215,2.98659 22.27617,5.39115 31.75,4.18467 11.86989,-1.51163 50.78615,-1.47595 57.57392,0.0528 4.07204,0.9171 9.14492,1.05539 14.14429,0.38557 8.10479,-1.08589 26.74917,0.29839 35.59678,2.64292 3.99041,1.05743 6.66089,0.90679 12.45067,-0.70231 6.30518,-1.75235 8.34733,-1.81418 14.12673,-0.42772 3.73913,0.897 10.37236,1.34966 14.85433,1.01367 5.25771,-0.39414 9.88013,0.0307 13.17826,1.21123 2.794,1.00006 7.65175,2.2751 10.795,2.83343 3.14325,0.55832 5.88065,1.08235 6.0831,1.1645 0.20245,0.0822 2.60661,-0.99263 5.34259,-2.38841 z M -73.56929,87.177848 c 1.66046,-5.03123 2.56108,-5.32188 7.15786,-2.30995 2.66625,1.747 3.94661,1.87532 7.04443,0.70602 2.34665,-0.88576 4.88891,-1.00969 6.6872,-0.32597 1.59569,0.60667 6.83522,1.00335 11.64339,0.88148 6.55019,-0.16603 9.52944,0.33567 11.8822,2.00092 3.51515,2.48797 10.84951,2.9757 12.1877,0.81048 0.56755,-0.91833 3.06081,-1.12177 7.13083,-0.58187 5.47195,0.72588 6.54177,0.47996 8.51553,-1.95754 2.25736,-2.78771 2.25736,-2.78771 5.96161,-0.12611 6.73897,4.84207 13.18656,4.01297 15.24558,-1.96042 0.0612,-0.17765 1.84948,0.54409 3.97386,1.60387 5.18467,2.58646 24.2317,4.95936 26.26473,3.27209 0.84273,-0.6994 2.68542,-0.97009 4.09487,-0.60151 1.4833,0.3879 3.25939,-0.16939 4.21692,-1.32315 2.12561,-2.56119 5.65098,-2.54912 7.78871,0.0267 1.76835,2.13073 14.47107,1.55902 16.88786,-0.76008 0.60956,-0.58491 2.45001,-0.45216 4.08989,0.29502 2.25636,1.02807 3.48754,0.93862 5.06164,-0.36777 1.77547,-1.47351 2.31286,-1.40787 3.67012,0.44829 1.92025,2.62612 3.68522,2.76356 5.61583,0.43731 1.0734,-1.29337 4.76363,-1.67702 14.44301,-1.50155 9.94654,0.18033 13.7318,-0.23208 16.11071,-1.75525 1.71022,-1.09502 6.56797,-3.43427 10.795,-5.19833 13.24763,-5.52859 11.77343,-9.17744 -6.23527,-15.43318 -3.36038,-1.16729 -5.25798,-2.69213 -5.715,-4.59234 -0.81793,-3.40073 -7.10057,-6.96375 -10.42088,-5.90993 -1.3606,0.43184 -4.24702,-0.41082 -6.7894,-1.98209 -3.36383,-2.07897 -5.41142,-2.51445 -8.55326,-1.81909 -7.77847,1.72154 -11.18495,0.9389 -15.12582,-3.47516 -4.08328,-4.57357 -7.88054,-5.03739 -38.92032,-4.75393 -6.79577,0.062 -11.2374,-0.47796 -12.49144,-1.51871 -1.25276,-1.03972 -6.77047,-1.71281 -15.48953,-1.88954 -7.44896,-0.15101 -16.11532,-0.34056 -19.25857,-0.42124 -3.14325,-0.0807 -10.00125,0.44806 -15.23999,1.17499 -15.55848,2.1589 -25.28984,2.31057 -26.86001,0.41864 -2.17961,-2.62628 -5.97385,-1.97767 -9.59106,1.63955 -2.76033,2.76032 -4.13319,3.20595 -8.19728,2.66084 -3.9547,-0.53043 -4.88535,-0.25569 -4.9011,1.44689 -0.0396,4.27655 -1.73163,5.3538 -5.92385,3.77141 -3.57381,-1.34896 -4.32436,-1.13094 -8.79631,2.55522 -3.75332,3.0938 -6.11342,4.03002 -10.15925,4.03002 -3.13522,0 -5.91059,0.7717 -6.85114,1.905 -0.86956,1.04775 -2.58423,1.905 -3.81041,1.905 -1.22617,0 -4.28108,0.85725 -6.7887,1.905 -5.55054,2.31916 -10.59221,2.46065 -11.31353,0.3175 -0.41868,-1.24396 -0.98269,-1.21741 -2.60632,0.12268 -1.13962,0.94063 -4.07246,1.89614 -6.51742,2.12338 -2.44497,0.22723 -5.17736,0.88638 -6.072,1.46478 -0.89464,0.57839 -6.19862,1.62312 -11.78662,2.32162 -9.65136,1.20643 -10.17939,1.4374 -10.54751,4.61386 -0.54633,4.71437 4.5131,6.18118 21.32037,6.18118 7.46284,0 13.96048,0.39167 14.43918,0.87038 1.6062,1.60618 10.29891,4.27607 15.53691,4.77201 2.85392,0.27021 7.14017,1.68082 9.525,3.13471 2.38484,1.45387 5.87394,2.64555 7.75359,2.64815 1.87966,0.003 3.77076,0.57625 4.20246,1.27475 1.61516,2.61339 4.29623,1.1239 5.715,-3.175 z"
+         id="path12888"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#919689;stroke-width:1.26999998"
+         d="m -30.38929,445.25488 c -27.73352,-3.30938 -41.51601,-7.05191 -75.565,-20.51906 -16.77665,-6.63554 -19.89089,-8.0905 -24.38913,-11.39443 -2.23803,-1.64382 -6.52428,-4.12097 -9.525,-5.5048 -7.65911,-3.53209 -22.20219,-13.08427 -27.58954,-18.12134 -2.49498,-2.33276 -6.82232,-5.8716 -9.61632,-7.86411 -2.794,-1.99249 -9.1336,-7.67284 -14.088,-12.623 -4.9544,-4.95015 -11.33148,-10.71478 -14.17128,-12.81028 -6.4978,-4.79475 -15.49446,-16.08721 -26.35072,-33.075 -2.794,-4.37202 -7.29088,-11.26585 -9.99306,-15.3196 -7.07799,-10.61824 -13.5729,-25.53688 -14.31684,-32.88541 -0.35357,-3.4925 -2.66641,-10.80132 -5.13965,-16.24184 -3.01224,-6.62618 -4.84218,-12.97165 -5.54299,-19.22079 -0.57541,-5.13092 -2.06443,-12.6811 -3.30892,-16.77816 -3.63786,-11.97641 -3.3024,-54.78433 0.4913,-62.69421 4.45415,-9.28692 6.75813,-19.28033 7.24826,-31.43895 0.51252,-12.71402 5.98214,-28.881392 14.02979,-41.469962 2.27351,-3.55634 5.96649,-10.87111 8.2066,-16.25503 2.24012,-5.38393 5.72706,-11.95618 7.74876,-14.605 13.07852,-17.13548 17.66076,-22.866685 22.16675,-27.724908 6.02371,-6.494585 30.05498,-27.714093 35.73112,-31.550429 2.18962,-1.479898 7.33312,-4.947202 11.43,-7.705117 4.09688,-2.757917 9.10844,-6.366894 11.1368,-8.01995 2.02836,-1.653055 7.74336,-5.044027 12.7,-7.535494 4.95665,-2.491466 11.29807,-6.074634 14.09207,-7.962595 6.81668,-4.606156 37.81139,-18.536224 46.43345,-20.868744 3.79861,-1.027636 10.0498,-2.719761 13.89155,-3.760278 3.84175,-1.040518 9.62158,-2.788402 12.84409,-3.884186 13.74994,-4.675554 56.14853,-7.18521 77.9609,-4.614659 14.84624,1.749603 42.51673,6.251367 46.78319,7.611245 21.06978,6.715701 28.95509,9.514401 35.61579,12.640981 24.92536,11.700126 41.54558,20.333663 45.0678,23.410905 1.14027,0.996215 7.21671,4.977246 13.50321,8.846736 33.59144,20.676332 65.44154,53.752373 80.96403,84.080433 19.057,37.233812 24.62487,51.788872 22.56231,58.980572 -0.84123,2.93323 -0.50519,5.84592 1.2951,11.22522 1.33711,3.99532 2.4577,8.69296 2.49021,10.43921 0.0325,1.74625 1.23243,5.74675 2.66646,8.89 5.62187,12.32253 4.72701,48.29691 -2.02804,81.53023 -14.64893,72.06939 -65.62183,137.39753 -133.98507,171.71858 -6.63575,3.33142 -12.6914,6.57476 -13.45702,7.20742 -3.02407,2.49899 -43.89657,16.88291 -57.66297,20.29287 -28.85254,7.14681 -68.62613,9.35608 -100.32999,5.57293 z m 60.43055,-98.52109 c 3.43113,-0.86397 6.71194,-2.7472 8.2968,-4.7625 2.37308,-3.01757 3.48962,-3.34668 11.78347,-3.47321 14.71667,-0.22451 28.06769,-11.27236 19.79846,-16.38303 -3.99067,-2.46636 21.20297,-5.94201 27.82853,-3.83914 3.43537,1.09034 3.46236,3.21695 0.0409,3.21695 -9.83753,0 -24.71204,15.87867 -19.27615,20.57746 8.02834,6.93973 33.98846,-2.08206 29.59786,-10.28601 -0.73726,-1.37745 -1.34042,-3.50534 -1.34042,-4.72861 0,-1.22328 -1.16204,-3.46107 -2.58231,-4.97288 -4.15671,-4.42462 -0.58228,-6.26479 12.6198,-6.49693 6.00462,-0.10553 11.42321,-0.24845 12.04128,-0.3175 0.61807,-0.0691 -0.39347,-1.31899 -2.24788,-2.77768 -7.94213,-6.24728 -9.0956,-14.29129 -3.00263,-20.93987 4.84209,-5.28365 14.92173,-7.84691 14.92173,-3.79462 0,2.31195 2.49397,1.98343 8.89,-1.17103 5.68626,-2.8044 9.80454,-3.20507 14.8059,-1.4405 9.40919,3.31976 15.59602,3.79249 18.24614,1.39417 4.61474,-4.17628 10.77349,4.87178 7.49096,11.00525 -0.6943,1.29732 -0.8422,2.35874 -0.32868,2.35874 0.51353,0 0.24788,0.6858 -0.59032,1.524 -2.0488,2.04881 -1.92289,3.556 0.29705,3.556 1.00159,0 3.43046,-2.3864 5.3975,-5.30312 1.96706,-2.91672 5.29095,-6.24909 7.38645,-7.40526 5.64999,-3.11734 5.45912,-5.8935 -0.82983,-12.07003 -3.03609,-2.98182 -8.20022,-8.28286 -11.47583,-11.7801 -3.27563,-3.49722 -6.70463,-6.65838 -7.62,-7.02477 -1.56901,-0.62803 12.76548,-1.5148 18.99344,-1.175 3.76771,0.20558 7.03145,-4.70364 5.96141,-8.967 -0.98853,-3.93865 -11.84452,-5.48965 -16.76688,-2.39548 -1.56623,0.98453 -3.65788,1.47914 -4.6481,1.09916 -0.99022,-0.37997 -3.85165,-0.0613 -6.35871,0.70811 -5.45567,1.67439 -7.04637,0.61201 -4.04664,-2.70264 2.95165,-3.26156 1.13432,-4.05135 -6.53328,-2.83926 -6.40019,1.01173 -13.03058,-0.83051 -13.03058,-3.62054 0,-2.68108 8.25897,-7.43146 11.74515,-6.75554 2.15268,0.41736 4.75435,-0.15166 6.78911,-1.48488 3.14194,-2.05868 3.4089,-2.04352 5.60943,0.31848 2.8913,3.10342 13.38586,1.08026 15.52741,-2.99342 2.30263,-4.38004 4.86737,-5.55799 10.43874,-4.79435 3.92,0.5373 5.84166,0.19271 7.35285,-1.31848 1.12523,-1.12523 3.65833,-2.08605 5.62909,-2.13515 8.33274,-0.20756 8.80407,-1.55192 2.49205,-7.10799 -6.08621,-5.3573 -5.2037,-11.26905 1.30918,-8.76981 4.80126,1.84241 7.00175,-4.31937 2.80394,-7.8516 -2.4552,-2.06591 -3.65695,-4.26743 -3.65695,-6.69927 0,-4.98827 -1.6277,-5.54187 -5.1792,-1.76147 -4.15436,4.42212 -12.6008,2.30806 -12.6008,-3.15384 0,-1.15625 -1.00012,-2.99932 -2.2225,-4.09573 -1.22237,-1.09641 -2.80352,-2.99716 -3.51368,-4.22389 -1.78957,-3.09136 -19.05197,-6.05198 -26.8226,-4.60027 -4.30081,0.80348 -7.22505,0.54568 -11.67233,-1.02904 -7.14436,-2.5297 -43.55337,-2.65462 -46.55952,-0.15974 -2.0437,1.69612 -47.63936,0.34541 -47.63936,-1.41125 0,-2.31285 -8.12099,-5.06559 -14.99277,-5.08205 -17.28743,-0.0414 -40.90786,-0.85679 -48.76408,-1.68333 -5.15639,-0.54251 -9.41191,-0.38717 -10.795,0.39402 -1.74406,0.98505 -3.58632,0.52868 -7.80226,-1.93277 -3.98504,-2.32665 -6.45607,-2.98422 -8.89,-2.3657 -1.85398,0.47113 -6.07688,0.78472 -9.38422,0.69687 -5.98755,-0.15905 -6.01017,-0.1439 -5.27479,3.53299 0.87419,4.37099 -2.91173,6.23536 -6.75034,3.3242 -2.55983,-1.94133 -5.98197,-2.41869 -4.91047,-0.68496 1.18518,1.91766 -2.74937,4.15963 -6.50176,3.7048 -3.5693,-0.43263 -3.5693,-0.43263 0.80439,-3.21214 5.14021,-3.26666 4.39973,-3.68168 -8.18369,-4.5868 -6.26561,-0.45068 -9.57186,-0.14054 -11.19976,1.05061 -2.73815,2.00351 -6.27009,1.18628 -15.84705,-3.66673 -9.10991,-4.61635 -12.50659,-1.56789 -6.60819,5.93071 5.2989,6.73646 1.0287,7.99189 -9.525,2.80035 -8.01709,-3.94375 -18.29628,-4.1963 -26.56329,-0.65263 -3.55628,1.52439 -10.3873,2.81597 -17.56779,3.32162 -6.4619,0.45504 -12.89191,1.49991 -14.28891,2.3219 -3.39996,2.00056 -12.28927,1.4883 -16.03109,-0.92381 -8.01118,-5.16425 -20.87329,-3.04633 -24.32094,4.00477 -0.87426,1.78803 -3.50865,3.86215 -6.00297,4.72628 -4.445,1.53991 -4.445,1.53991 -1.07587,2.8791 5.29988,2.10664 5.39725,3.81981 0.3852,6.77699 -4.33731,2.55909 -4.59867,3.05989 -3.89461,7.46284 0.58881,3.68218 0.14904,5.48964 -2.00059,8.22245 -2.85773,3.63302 -2.10983,5.33084 3.27347,7.43107 0.79759,0.31118 0.16928,1.46278 -1.4501,2.65782 -3.98884,2.94362 -3.5887,4.33142 1.905,6.6071 2.61937,1.08504 5.55537,2.61976 6.52444,3.41048 0.96907,0.79073 3.54082,1.56102 5.715,1.71179 2.17418,0.15074 7.66781,0.90468 12.20806,1.67539 4.54025,0.77073 10.541,1.53448 13.335,1.69726 2.794,0.16277 6.7945,0.81132 8.89,1.4412 2.0955,0.6299 5.81025,1.21495 8.255,1.30012 2.44475,0.0852 7.84542,0.26207 12.0015,0.39311 14.37054,0.4531 1.5386,5.36907 -15.1765,5.81419 -7.41089,0.19734 -13.13317,2.72964 -21.13473,9.35277 -3.65674,3.02681 -1.48391,6.66129 3.98235,6.66129 2.627,0 5.43821,0.79748 6.24712,1.77215 0.80891,0.97469 3.45064,2.14358 5.87051,2.59756 2.41986,0.45396 4.39975,1.371 4.39975,2.03784 0,0.66685 3.57187,1.22395 7.9375,1.23799 4.36562,0.0141 11.65225,0.6203 16.19249,1.34725 4.54025,0.72693 11.1125,1.60272 14.605,1.94619 3.4925,0.34349 7.64171,1.46872 9.22044,2.50052 1.95009,1.27451 3.5789,1.50185 5.08,0.70905 2.84087,-1.50039 12.45516,0.0187 17.77853,2.80903 3.08502,1.61706 4.76157,1.8363 6.58401,0.86097 5.46317,-2.92381 10.98754,-1.28327 8.88817,2.63945 -0.97692,1.8254 -0.68604,2.69904 1.31468,3.94852 2.99609,1.87109 3.33951,4.23612 0.75891,5.22639 -2.51621,0.96556 -10.29007,-1.34509 -12.17779,-3.61965 -2.13173,-2.56859 -5.37269,-2.36653 -12.71687,0.79286 -6.22079,2.67611 -6.22079,2.67611 -12.15065,-0.19448 -5.14886,-2.49252 -6.47761,-2.67253 -10.08874,-1.36673 -3.96443,1.43355 -5.8026,1.5603 -20.06982,1.38377 -3.38202,-0.0419 -6.88439,0.53412 -7.78302,1.27992 -1.1318,0.93931 -2.68242,0.95735 -5.04604,0.0587 -6.42746,-2.44372 -9.98799,-1.6028 -13.88309,3.27889 -2.00821,2.51686 -5.53005,5.3611 -7.8263,6.32053 -3.21564,1.34359 -4.02311,2.3253 -3.51397,4.27226 1.02522,3.92041 4.0061,5.88095 6.53777,4.29989 2.49453,-1.55786 7.16703,-0.15655 10.99314,3.29688 3.81907,3.4471 10.70694,4.3782 13.5246,1.82825 2.16507,-1.95936 2.63812,-1.88155 7.29437,1.1998 3.86973,2.56086 5.9717,3.13398 9.43377,2.57217 3.02146,-0.49032 5.39783,-0.0622 7.38635,1.33056 3.6789,2.57681 7.44655,2.65193 7.44655,0.14847 0,-1.04775 0.82484,-1.905 1.83299,-1.905 2.44385,0 5.78701,4.93216 5.78701,8.53758 0,4.12381 5.99393,4.15562 6.57917,0.0349 0.45014,-3.1695 5.44937,-4.67196 7.78242,-2.33891 0.97817,0.97817 2.17398,0.61205 4.19155,-1.28337 2.53273,-2.37937 3.27191,-2.48963 7.19059,-1.07262 3.28781,1.18889 5.36407,1.23202 8.39776,0.17447 4.62348,-1.61175 7.76851,-0.82778 7.76851,1.93649 0,1.47989 0.78193,1.67923 3.4925,0.89036 11.82973,-3.44288 41.97084,0.72513 30.7975,4.25878 -2.44475,0.77316 -8.69581,1.71986 -13.89122,2.10375 -9.20758,0.68037 -15.94,3.26855 -8.5022,3.26855 3.06317,0 3.3985,0.36575 2.61081,2.84754 -0.79165,2.49423 -0.65336,2.63971 1.11451,1.17251 1.11006,-0.92126 3.33562,-1.44744 4.9457,-1.16927 2.91742,0.50405 2.91607,0.51054 -0.3919,1.9052 -6.09282,2.56878 -5.37576,7.7731 1.4143,10.26472 6.99445,2.56662 12.93228,-3.35444 7.65187,-7.63027 -3.40648,-2.7584 -2.36665,-4.85639 1.27865,-2.57986 1.35575,0.84668 4.24,1.53943 6.40943,1.53943 2.16942,0 5.39751,1.143 7.1735,2.54 3.84263,3.02261 18.54787,3.69098 20.28594,0.92201 3.90129,-6.21524 9.2706,-6.34894 9.2706,-0.23085 0,7.97161 13.69982,11.81196 28.68056,8.03977 z m -56.30305,-12.16721 c -3.42643,-0.65996 -3.45382,-0.87734 -0.6983,-5.54205 2.00697,-3.39755 2.31805,-3.51148 4.41246,-1.61608 2.05932,1.86366 2.51776,1.86586 5.32655,0.0255 2.88013,-1.88713 3.03486,-1.86676 2.4619,0.32428 -0.94014,3.59512 1.29843,4.05493 6.59488,1.35465 4.68812,-2.39014 6.985,-2.36549 6.985,0.0749 0,0.65727 -1.14858,1.19504 -2.55243,1.19504 -1.40383,0 -3.53109,1.143 -4.72724,2.54 -2.14761,2.50823 -9.696,3.20516 -17.80282,1.64372 z m 184.14998,-78.7462 c -2.38977,-0.87189 -1.91008,-3.36508 0.99671,-5.18039 1.5436,-0.96401 3.34577,-1.18768 4.47544,-0.55548 2.32362,1.30036 -2.82822,6.70047 -5.47215,5.73587 z m -14.68437,-13.88773 c -1.48513,-1.48514 -0.96582,-2.9898 1.03187,-2.9898 1.04775,0 1.905,-0.5715 1.905,-1.27 0,-0.6985 0.55309,-1.27 1.22908,-1.27 1.53019,0 -0.33604,5.41733 -2.06474,5.99355 -0.67545,0.22516 -1.621,0.0165 -2.10121,-0.46375 z m -352.40195,-53.93881 c 0.32184,-0.96579 1.96636,-1.80082 3.6545,-1.85562 1.68814,-0.0547 4.92672,-0.52883 7.19685,-1.0534 4.07128,-0.94079 5.97804,0.80616 2.38487,2.185 -5.09888,1.95661 -13.80566,2.43288 -13.23622,0.72402 z m 18.10163,-6.28548 c -1.09285,-1.76826 -0.46425,-2.30257 4.32474,-3.67604 2.92408,-0.83862 3.98248,-0.72695 3.98248,0.42019 0,3.75863 -6.43722,6.28157 -8.30722,3.25585 z m 121.21054,148.11779 c 0.98901,-0.61125 1.01425,-1.59485 0.0855,-3.33037 -1.46446,-2.73637 -5.03922,-3.35755 -5.03791,-0.87542 5.1e-4,0.98621 -1.79216,1.5875 -4.733,1.5875 -3.34212,0 -4.85347,0.60274 -5.14077,2.05018 -0.42417,2.13698 11.54563,2.59564 14.82626,0.56811 z M 137.4779,294.90013 c 1.08849,-2.9612 1.03421,-3.00318 -1.23176,-0.9525 -1.30375,1.17987 -3.03982,2.14522 -3.85794,2.14522 -0.81813,0 -1.4875,0.61126 -1.4875,1.35833 0,2.62607 5.45139,0.51167 6.5772,-2.55105 z M -53.80953,158.25747 c -2.75533,-2.08404 -4.51976,-2.39879 -4.51976,-0.80629 0,1.77768 1.56198,2.75368 4.33917,2.71129 2.5995,-0.0396 2.60266,-0.073 0.18059,-1.905 z m -21.09599,-1.7968 c 1.44208,-0.91234 1.10023,-1.34866 -1.58467,-2.02253 -3.99562,-1.00284 -7.74908,0.10211 -6.60965,1.94575 0.98212,1.58907 5.73352,1.6336 8.19432,0.0768 z m 36.89623,-10.86282 c 0,-1.04775 -0.85725,-1.905 -1.905,-1.905 -1.04775,0 -1.905,0.85725 -1.905,1.905 0,1.04775 0.85725,1.905 1.905,1.905 1.04775,0 1.905,-0.85725 1.905,-1.905 z m 52.47098,-5.93768 c 3.10044,-1.66834 14.93307,-2.53026 31.76497,-2.31388 7.55358,0.0972 11.86538,-0.39782 13.21696,-1.51715 1.4329,-1.18669 7.11915,-1.68141 19.90403,-1.73171 9.84392,-0.0387 23.8988,-0.5104 31.23305,-1.04817 7.33425,-0.53776 16.764,-0.90731 20.955,-0.82124 4.19099,0.0861 10.12073,-0.72812 13.17717,-1.80934 3.05645,-1.0812 8.5176,-1.96583 12.13589,-1.96583 16.50109,0 46.39698,-16.27694 33.80372,-18.40456 -5.09098,-0.86012 -6.94876,-2.30754 -7.01322,-5.46412 -0.0245,-1.19783 -3.65083,-1.81312 -13.36857,-2.26827 -23.2894,-1.09082 -29.42159,-1.56878 -48.25999,-3.761592 -22.34859,-2.60139 -40.50711,-3.25821 -92.71,-3.35348 -40.2141,-0.0734 -45.71999,-0.58173 -45.71999,-4.2214 0,-2.98983 3.43242,-3.69774 9.00767,-1.85774 4.53366,1.49624 5.17351,1.42781 6.74505,-0.72139 1.69555,-2.3188 1.86038,-2.31847 7.03344,0.0142 2.91346,1.31379 7.12232,2.2862 9.35301,2.16091 13.26511,-0.745 25.41191,-0.32952 26.6531,0.91166 1.9632,1.9632 5.37934,1.75199 7.20298,-0.44538 1.01952,-1.22843 2.7922,-1.5999 5.26998,-1.10435 2.58239,0.51648 4.48749,0.0645 6.17828,-1.46558 2.34216,-2.11963 2.57026,-2.0662 5.44454,1.27533 2.62515,3.05194 3.3536,3.2984 5.83058,1.97276 1.55686,-0.8332 4.66342,-1.33876 6.90349,-1.12348 3.3758,0.32443 4.1409,-0.0867 4.47035,-2.40241 0.35032,-2.46231 0.76028,-2.62852 3.4549,-1.40077 2.29044,1.0436 3.53075,1.0002 4.94437,-0.17301 1.03785,-0.86133 2.48351,-1.19737 3.21262,-0.74676 1.8262,1.12865 1.66605,4.13034 -0.26187,4.90828 -0.87313,0.35231 -0.1514,0.68569 1.60384,0.74083 2.15015,0.0676 3.60036,-0.83202 4.445,-2.75723 1.18433,-2.69949 1.99106,-2.85907 14.58866,-2.88572 9.31826,-0.0197 14.29136,-0.58434 16.50999,-1.87446 1.74625,-1.01543 6.604,-3.3033 10.795,-5.08415 13.15564,-5.59012 11.67717,-9.21867 -6.30077,-15.46373 -3.36038,-1.16729 -5.25798,-2.69213 -5.715,-4.59234 -0.81793,-3.40073 -7.10057,-6.96376 -10.42088,-5.90993 -1.36078,0.4319 -4.24768,-0.41123 -6.79171,-1.98352 -3.81411,-2.35726 -5.20846,-2.56761 -10.10181,-1.52396 -6.37176,1.35896 -11.11845,-0.29481 -14.88156,-5.18482 -2.27187,-2.9522 -9.51082,-3.59461 -37.61372,-3.33797 -6.79577,0.062 -11.2374,-0.47796 -12.49144,-1.51871 -1.25276,-1.03972 -6.77047,-1.71281 -15.48953,-1.88954 -7.44896,-0.15096 -16.11532,-0.34051 -19.25857,-0.42119 -3.14325,-0.0807 -10.00125,0.44806 -15.23999,1.17499 -15.55848,2.1589 -25.28984,2.31057 -26.86001,0.41864 -2.17961,-2.62628 -5.97385,-1.97767 -9.59106,1.63955 -2.76033,2.76032 -4.13319,3.20595 -8.19728,2.66084 -3.9547,-0.53043 -4.88535,-0.25569 -4.9011,1.44689 -0.0396,4.27655 -1.73163,5.3538 -5.92385,3.77141 -3.57381,-1.34896 -4.32436,-1.13094 -8.79631,2.55522 -3.75332,3.0938 -6.11342,4.03002 -10.15925,4.03002 -3.13522,0 -5.91059,0.7717 -6.85114,1.905 -0.86956,1.04775 -2.58423,1.905 -3.81041,1.905 -1.22617,0 -4.28108,0.85725 -6.7887,1.905 -5.55054,2.31916 -10.59221,2.46065 -11.31353,0.3175 -0.41868,-1.24396 -0.98269,-1.21741 -2.60632,0.12268 -1.13962,0.94063 -4.07246,1.89614 -6.51742,2.12338 -2.44497,0.22723 -5.17736,0.88638 -6.072,1.46478 -0.89464,0.57839 -6.19862,1.62312 -11.78662,2.32162 -9.65136,1.20643 -10.17939,1.4374 -10.54751,4.61386 -0.54633,4.71437 4.5131,6.18118 21.32037,6.18118 7.46284,0 13.96048,0.39167 14.43918,0.87038 1.59056,1.59056 10.27151,4.32908 15.88031,5.00966 3.04278,0.36921 7.32903,1.76686 9.525,3.10588 2.19596,1.33903 5.60403,2.43673 7.57348,2.43933 2.98596,0.004 3.51342,0.47945 3.175,2.86225 -0.39304,2.76743 -0.98627,2.88426 -18.82083,3.70653 -17.63329,0.81301 -29.5783,2.60997 -34.83089,5.239832 -2.35189,1.17756 -17.21844,3.10195 -30.6499,3.96747 -7.52078,0.48465 -11.88176,1.66996 -14.5292,3.94905 -1.04775,0.90197 -5.04825,2.64444 -8.89,3.87216 -3.84175,1.22771 -8.34881,3.56538 -10.01568,5.19483 -1.66688,1.62945 -4.09575,2.96263 -5.3975,2.96263 -1.45388,0 -2.36682,0.90382 -2.36682,2.34316 0,6.96881 17.93611,12.30067 37.465,11.13719 28.60307,-1.70408 50.51029,-1.72936 56.81746,-0.0655 5.25872,1.38722 7.83771,1.36031 15.80967,-0.16498 5.23924,-1.00243 10.18672,-1.40419 10.99437,-0.89281 0.80767,0.51139 5.75474,1.43101 10.99349,2.04358 9.19532,1.07523 11.78006,1.15434 31.115,0.95223 18.85939,-0.19712 33.19898,0.58326 37.46499,2.03891 8.747,2.98463 15.62839,3.49253 19.45099,1.4356 z M -62.9749,94.430868 c -12.29059,-0.43533 -13.86821,-2.53558 -8.58948,-11.43495 0.12141,-0.20456 2.27649,0.60844 4.78922,1.80668 3.68492,1.75722 5.18824,1.89628 7.7722,0.71895 2.18762,-0.99674 4.23249,-1.06847 6.44789,-0.22618 1.78436,0.67841 6.78725,1.09375 11.11753,0.92297 6.44715,-0.25425 8.59887,0.2071 11.8793,2.54701 2.20332,1.57163 4.91794,2.8575 6.0325,2.8575 4.32164,0 1.7608,2.55031 -2.73605,2.72481 -10.20219,0.39589 -26.82212,0.43355 -36.71311,0.0832 z"
+         id="path12886"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#aa976c;stroke-width:1.26999998"
+         d="m -18.32429,446.55289 c -0.6985,-0.26926 -8.3998,-1.39439 -17.11399,-2.50028 -28.32573,-3.59473 -90.23846,-23.98818 -94.68926,-31.18974 -0.50431,-0.81598 -3.84413,-2.78021 -7.42183,-4.36497 -13.11492,-5.80931 -34.27248,-21.53639 -50.41498,-37.47504 -5.30575,-5.23875 -12.01497,-11.16609 -14.90937,-13.17186 -8.35234,-5.78801 -15.62897,-15.23647 -37.78164,-49.05815 -9.62038,-14.68795 -15.74117,-29.61071 -15.77884,-38.46952 -0.008,-1.94948 -1.77759,-6.93107 -3.93176,-11.07019 -2.29149,-4.40295 -4.44617,-11.10031 -5.19274,-16.14047 -0.70183,-4.73815 -2.34177,-13.18682 -3.64433,-18.77482 -4.66326,-20.00553 -4.18951,-54.37101 0.90166,-65.405 4.6637,-10.10756 5.91102,-15.59043 6.78958,-29.845 1.04181,-16.90329 4.33394,-26.56682 14.43533,-42.372602 1.94823,-3.04843 4.286,-7.65063 5.19503,-10.22712 5.44228,-15.42506 26.26682,-43.826962 41.49267,-56.59049 1.64696,-1.380615 8.42373,-7.127585 15.05948,-12.771044 6.63575,-5.643459 17.06365,-13.566377 23.17312,-17.606486 6.10947,-4.040107 11.43906,-7.881112 11.84353,-8.535566 0.40448,-0.654454 5.1889,-3.42909 10.63206,-6.165857 5.44317,-2.736768 12.40082,-6.633725 15.46147,-8.659904 10.33169,-6.839694 38.99021,-19.006599 54.45981,-23.120794 3.14325,-0.835959 10.13805,-2.898185 15.54401,-4.582727 18.4222,-5.740513 56.54074,-8.424837 79.07098,-5.568216 20.23955,2.566183 42.48329,6.140779 46.50435,7.473307 2.36261,0.782937 9.43915,3.08065 15.72565,5.106028 12.15104,3.914811 18.93466,6.779309 37.76305,15.946083 14.61041,7.113205 24.43105,12.567617 29.47827,16.372313 2.13327,1.608102 7.94263,5.28411 12.90971,8.168908 9.29849,5.40042 12.9282,8.129162 30.33896,22.808233 23.69829,19.980106 48.53615,51.516362 54.07606,68.659562 0.83115,2.57201 3.67486,8.39112 6.31936,12.93137 7.34648,12.612972 12.62574,28.960622 12.4215,38.464162 -0.19002,8.84145 3.57577,25.8974 7.21009,32.65584 7.80794,14.5198 1.41847,72.93981 -12.18908,111.44654 -7.42375,21.00782 -23.50368,51.03384 -33.39932,62.36644 -2.21977,2.54211 -5.67874,7.06653 -7.68658,10.05427 -7.11689,10.59014 -43.2131,43.04109 -56.59703,50.88146 -3.14325,1.84132 -8.48705,5.21164 -11.87512,7.48959 -3.38807,2.27793 -6.67869,4.14171 -7.31251,4.14171 -0.6338,0 -6.1485,2.83709 -12.25487,6.30465 -20.29127,11.52257 -58.71063,24.35866 -89.84249,30.01681 -10.41071,1.89213 -61.19414,3.75707 -64.76999,2.37857 z M 8.66357,364.99103 c 0.69439,-2.08551 -3.28483,-3.57421 -4.99158,-1.86746 -0.73038,0.73038 -0.92443,1.98091 -0.43122,2.77895 1.12211,1.81561 4.71592,1.21154 5.4228,-0.91149 z M -6.25818,354.83036 c -6.1e-4,-0.87313 -1.35989,-1.5875 -3.02062,-1.5875 -1.66071,0 -3.73094,-0.85725 -4.60049,-1.905 -1.81588,-2.18799 -7.07229,-2.54868 -8.29564,-0.56924 -0.45405,0.73467 -2.34516,1.21998 -4.20246,1.07848 -12.05552,-0.91848 -17.14679,1.41387 -7.5044,3.43781 8.44454,1.77253 27.62496,1.45691 27.62361,-0.45455 z m 35.68687,-6.84289 c 5.61201,-1.87898 12.83319,-7.26457 11.85745,-8.84335 -0.48263,-0.7809 2.23815,-0.9258 7.41842,-0.39505 13.52056,1.38521 23.77614,-3.57411 23.77614,-11.49751 0,-2.65282 -0.73645,-4.49955 -1.98549,-4.97886 -6.05953,-2.32526 20.32741,-6.195 27.25331,-3.99679 3.43537,1.09034 3.46236,3.21695 0.0409,3.21695 -6.44033,0 -21.49871,12.4201 -21.49871,17.73201 0,8.97827 21.89176,10.00951 32.0675,1.51058 3.79524,-3.16984 0.96057,-14.49286 -4.83165,-19.29999 -4.7336,-3.92853 3.03151,-6.49324 14.50398,-4.79047 12.08524,1.79371 14.83496,0.73501 9.31375,-3.58599 -8.68861,-6.79986 -10.05156,-14.62687 -3.74532,-21.50816 4.84209,-5.28365 14.92173,-7.84691 14.92173,-3.79462 0,2.31195 2.49397,1.98343 8.89,-1.17103 5.68626,-2.8044 9.80454,-3.20507 14.8059,-1.4405 9.40919,3.31976 15.59602,3.79249 18.24614,1.39417 3.31533,-3.00032 5.91711,-1.62535 7.45349,3.93896 1.58644,5.74557 -0.18222,9.74981 -6.91902,15.66481 -4.15889,3.65156 -5.52788,7.42305 -1.5497,4.26936 0.89001,-0.70555 6.47594,-1.99936 12.41319,-2.87515 8.8683,-1.30813 11.47501,-2.23398 14.605,-5.18736 2.0955,-1.97725 6.38175,-4.95409 9.525,-6.61518 8.0627,-4.26085 7.6196,-12.34145 -0.67673,-12.34145 -7.09194,0 -18.41899,-6.20149 -26.83067,-14.68964 -3.60381,-3.63657 -7.31856,-6.91748 -8.255,-7.29089 -1.4607,-0.58248 2.77945,-0.89394 19.2524,-1.41419 2.2734,-0.0718 3.77129,-1.29427 5.27486,-4.30495 1.17984,-2.36248 2.46593,-3.61233 2.93542,-2.85268 0.50277,0.81349 2.10543,-0.15712 4.02367,-2.43682 4.23014,-5.02723 3.04139,-6.41575 -4.70702,-5.49807 -4.18764,0.49597 -7.44755,0.17926 -9.50862,-0.9238 -2.76736,-1.48104 -3.57627,-1.35325 -6.8759,1.08627 -2.05675,1.52062 -4.76283,2.76829 -6.01349,2.7726 -1.25065,0.004 -3.95084,0.88229 -6.00042,1.95109 -5.03825,2.6273 -8.27674,1.48117 -5.25364,-1.85931 2.92101,-3.22769 1.08167,-4.01366 -6.56428,-2.805 -6.27249,0.99154 -13.03058,-0.81762 -13.03058,-3.48833 0,-3.55403 5.33728,-5.46903 16.599,-5.95566 14.68496,-0.63455 19.64101,-1.55818 24.84901,-4.63097 3.12185,-1.84195 4.72082,-2.11881 6.55807,-1.13555 1.80603,0.96657 4.14023,0.65032 9.09457,-1.23218 5.13581,-1.95146 7.07156,-2.19504 8.43715,-1.06169 1.34491,1.11616 2.21036,1.10714 3.58948,-0.0375 0.99951,-0.82951 5.00001,-1.8367 8.89,-2.23818 3.89,-0.40149 7.14415,-1.22151 7.23147,-1.82229 0.0874,-0.60078 0.23018,-1.6162 0.3175,-2.2565 0.0874,-0.64028 0.58737,-1.59278 1.11125,-2.11665 1.57688,-1.57689 1.07579,-5.19584 -0.9525,-6.87918 -2.45232,-2.03523 -2.45232,-5.86822 0,-6.80927 2.68836,-1.03162 2.36442,-3.66287 -0.78267,-6.35749 -2.11607,-1.81183 -2.53615,-3.23496 -1.97524,-6.69148 0.60091,-3.70295 0.27611,-4.55401 -2.07482,-5.4366 -5.73626,-2.15349 -16.93258,-5.08034 -21.95506,-5.73932 -2.8588,-0.37509 -7.87409,-1.86583 -11.1451,-3.31276 -3.27103,-1.44692 -9.50452,-2.89551 -13.85221,-3.21908 -4.34769,-0.32356 -8.79441,-0.86524 -9.88159,-1.20372 -1.08717,-0.33847 -4.70342,-0.10604 -8.0361,0.51661 -4.65994,0.87058 -7.42993,0.64676 -11.99332,-0.96907 -7.14436,-2.5297 -43.55337,-2.65462 -46.55952,-0.15974 -2.0437,1.69616 -47.63936,0.34545 -47.63936,-1.41121 0,-2.31285 -8.12099,-5.06559 -14.99277,-5.08205 -17.28743,-0.0414 -40.90786,-0.85679 -48.76408,-1.68333 -5.15639,-0.54251 -9.41191,-0.38717 -10.795,0.39402 -1.74406,0.98505 -3.58632,0.52868 -7.80226,-1.93277 -3.98504,-2.32665 -6.45607,-2.98422 -8.89,-2.3657 -1.85398,0.47113 -6.07688,0.78472 -9.38422,0.69687 -5.98755,-0.15905 -6.01017,-0.1439 -5.27479,3.53299 0.87419,4.37099 -2.91173,6.23536 -6.75034,3.3242 -2.55983,-1.94133 -5.98197,-2.41869 -4.91047,-0.68496 1.18518,1.91766 -2.74937,4.15963 -6.50176,3.7048 -3.5693,-0.43263 -3.5693,-0.43263 0.80439,-3.21214 5.14021,-3.26666 4.39973,-3.68168 -8.18369,-4.5868 -6.26561,-0.45068 -9.57186,-0.14054 -11.19976,1.05061 -2.73815,2.00351 -6.27009,1.18628 -15.84705,-3.66673 -9.10991,-4.61635 -12.50659,-1.56789 -6.60819,5.93071 5.2989,6.73646 1.0287,7.99189 -9.525,2.80035 -8.01709,-3.94375 -18.29628,-4.1963 -26.56329,-0.65263 -3.55628,1.52439 -10.3873,2.81597 -17.56779,3.32162 -6.4619,0.45504 -12.89191,1.49991 -14.28891,2.3219 -3.41915,2.01185 -12.30809,1.48311 -16.03109,-0.95356 -7.31814,-4.78966 -23.97391,-2.37119 -23.97391,3.48108 0,2.93111 -3.59218,4.90111 -9.78186,5.36449 -5.99682,0.44895 -10.17382,2.77278 -7.04564,3.91978 2.13684,0.78352 2.03352,5.45042 -0.12066,5.45042 -3.0591,0 -6.23903,4.61804 -4.95567,7.19685 0.63724,1.28049 1.01905,6.75728 0.84845,12.17065 -0.17059,5.41338 0.19615,9.8425 0.81499,9.8425 0.61884,0 2.01177,1.7145 3.09539,3.81 1.68945,3.26704 2.69199,3.81 7.03485,3.81 2.78554,0 6.05699,0.57149 7.26988,1.26997 2.70526,1.55789 42.04449,5.52794 60.56177,6.11178 14.62598,0.46115 1.75914,5.24774 -15.8115,5.88203 -4.54025,0.1639 -11.31235,1.33152 -15.04911,2.5947 -4.91226,1.66056 -7.04102,1.8972 -7.68555,0.85433 -1.00981,-1.63391 -8.38884,0.50889 -11.32855,3.28969 -0.92302,0.87313 -2.49464,1.5875 -3.4925,1.5875 -0.99786,0 -1.81429,0.5715 -1.81429,1.27 0,1.3406 0.69533,1.45605 10.78688,1.79092 4.8718,0.16166 6.43896,0.71446 6.76063,2.38475 0.30774,1.59802 1.6993,2.17433 5.2501,2.17433 2.65726,0 5.49322,0.79748 6.30213,1.77215 0.80891,0.97469 3.45064,2.14358 5.87051,2.59756 2.41986,0.45396 4.39975,1.371 4.39975,2.03784 0,0.66685 3.57187,1.22395 7.9375,1.23799 4.36562,0.0141 11.65225,0.6203 16.19249,1.34725 4.54025,0.72693 11.1125,1.60272 14.605,1.94619 3.4925,0.34349 7.64171,1.46872 9.22044,2.50052 1.95009,1.27451 3.5789,1.50185 5.08,0.70905 2.84087,-1.50039 12.45516,0.0187 17.77853,2.80903 3.08502,1.61706 4.76157,1.8363 6.58401,0.86097 5.46317,-2.92381 10.98754,-1.28327 8.88817,2.63945 -0.97321,1.81846 -0.69291,2.69475 1.25011,3.9082 1.86101,1.16221 2.15489,1.98553 1.14366,3.20398 -2.14401,2.58338 -7.48689,1.925 -12.48466,-1.53846 -4.58627,-3.17828 -4.58627,-3.17828 -11.2983,0.0597 -7.20767,3.47709 -13.97039,2.84858 -16.41848,-1.52591 -0.95199,-1.70111 -1.59808,-1.6938 -5.8093,0.0658 -2.79535,1.16798 -8.21855,1.94381 -13.22618,1.89212 -4.66839,-0.0481 -12.03847,-0.005 -16.37794,0.096 -4.33947,0.10097 -9.85619,-0.18528 -12.25939,-0.63613 -3.7117,-0.69632 -4.49711,-0.41746 -5.21755,1.85246 -1.42429,4.48755 -7.76199,6.90201 -15.18263,5.7841 -16.51303,-2.48764 -21.1065,-0.99587 -19.28468,6.26283 1.00688,4.01174 13.96945,10.91468 20.49912,10.91639 2.1147,5.4e-4 5.14613,0.91241 6.73651,2.02636 1.83585,1.28587 4.07232,1.72901 6.12546,1.21371 1.77862,-0.44641 5.1611,-0.1756 7.51663,0.60179 2.35553,0.7774 7.58436,1.54145 11.61963,1.69788 4.60934,0.17868 8.65528,1.15801 10.88338,2.63432 3.4873,2.31062 3.5889,2.30441 6.08197,-0.37159 3.13995,-3.37034 2.74091,-3.39441 9.51226,0.57387 6.89694,4.04187 7.40585,4.80989 5.35902,8.08739 -1.14873,1.83942 -1.24037,3.19988 -0.3144,4.66785 1.80115,2.85541 3.62299,2.43689 4.43899,-1.01976 1.01346,-4.29309 7.69301,-5.88852 13.35306,-3.18943 4.57877,2.18346 5.31875,4.34698 2.98085,8.71536 -1.08728,2.03161 -1.04433,2.392 0.19388,1.62673 0.87483,-0.54066 5.36096,-1.15948 9.96921,-1.37513 14.38253,-0.67307 22.4126,-1.40595 25.24117,-2.3037 3.89419,-1.23598 7.55998,1.17498 8.53478,5.61321 0.46134,2.10044 2.0637,4.67694 3.56081,5.72556 7.18889,5.0353 19.46078,-1.5815 12.95186,-6.98342 -3.00181,-2.49128 -2.22038,-4.17494 0.91758,-1.97701 1.55241,1.08735 4.56866,1.97701 6.70276,1.97701 2.30855,0 4.76104,1.02879 6.05498,2.54 1.47415,1.72167 3.91774,2.6017 7.58474,2.73152 10.21909,0.36175 12.95343,-0.33456 16.83993,-4.28844 3.81,-3.87604 3.81,-3.87604 4.52725,1.35686 1.2127,8.84759 15.03522,13.25067 27.98574,8.91467 z M -56.3261,325.14398 c 0.26985,-0.43663 -1.78497,-1.15776 -4.56628,-1.60251 -9.92587,-1.58721 -2.70106,-4.83568 11.45309,-5.14962 7.08437,-0.15714 16.43428,2.96668 13.61013,4.54715 -2.56455,1.43521 -12.80127,1.74366 -14.89627,0.44887 -1.16546,-0.72028 -1.47289,-0.4876 -0.99638,0.75415 0.46961,1.22378 -0.23211,1.79584 -2.20291,1.79584 -1.59061,0 -2.67124,-0.35724 -2.40138,-0.79388 z m -35.34069,-2.8064 c -4.16752,-3.29977 -2.40472,-4.3076 7.22692,-4.13174 5.19719,0.0949 9.78299,0.5061 10.1907,0.91379 3.03037,3.03039 -13.74111,6.12897 -17.41762,3.21795 z m 249.31276,-67.42904 c -1.47483,-1.77704 -1.37591,-2.33718 0.68878,-3.89999 4.53685,-3.43407 6.85189,-1.21046 3.30285,3.17241 -2.07504,2.56257 -2.417,2.6249 -3.99163,0.72758 z m -14.44215,-12.97589 c -1.53368,-1.53369 -0.94557,-2.67461 1.74083,-3.37712 1.91306,-0.50028 2.50898,-0.14255 2.2225,1.33415 -0.43714,2.25314 -2.62525,3.38105 -3.96333,2.04297 z m -13.77086,89.86929 c 2.63973,-1.67301 -1.39321,-8.56155 -5.4685,-9.3406 -1.77307,-0.33895 -3.22376,-0.12395 -3.22376,0.47763 0,0.60163 0.63345,1.09389 1.40768,1.09389 0.85923,0 1.10378,1.21081 0.62767,3.10779 -1.0407,4.14647 3.00233,6.97748 6.65691,4.66129 z m 8.04494,-36.90181 c 1.08849,-2.9612 1.03421,-3.00318 -1.23176,-0.9525 -1.30375,1.17987 -3.03982,2.14522 -3.85794,2.14522 -0.81813,0 -1.4875,0.61126 -1.4875,1.35833 0,2.62607 5.45139,0.51167 6.5772,-2.55105 z M -53.80953,158.25747 c -2.75533,-2.08404 -4.51976,-2.39879 -4.51976,-0.80629 0,1.77768 1.56198,2.75368 4.33917,2.71129 2.5995,-0.0396 2.60266,-0.073 0.18059,-1.905 z m -21.09599,-1.7968 c 1.44208,-0.91234 1.10023,-1.34866 -1.58467,-2.02253 -3.99562,-1.00284 -7.74908,0.10211 -6.60965,1.94575 0.98212,1.58907 5.73352,1.6336 8.19432,0.0768 z m 36.89623,-10.86282 c 0,-1.04775 -0.85725,-1.905 -1.905,-1.905 -1.04775,0 -1.905,0.85725 -1.905,1.905 0,1.04775 0.85725,1.905 1.905,1.905 1.04775,0 1.905,-0.85725 1.905,-1.905 z m 52.47098,-5.93768 c 3.10044,-1.66834 14.93307,-2.53026 31.76497,-2.31388 7.55358,0.0972 11.86538,-0.39782 13.21696,-1.51715 1.4329,-1.18669 7.11915,-1.68141 19.90403,-1.73171 9.84392,-0.0387 23.8988,-0.5104 31.23305,-1.04817 7.33425,-0.53776 16.764,-0.90731 20.955,-0.82124 4.19099,0.0861 10.12073,-0.72812 13.17717,-1.80934 3.05645,-1.0812 8.35005,-1.96583 11.76358,-1.96583 7.03896,0 22.1976,-3.88254 28.95478,-7.41611 2.48845,-1.3013 6.37035,-3.34441 8.62645,-4.54024 4.10199,-2.17423 4.10199,-2.17423 9.01086,2.98606 4.12509,4.33637 5.71081,5.16029 9.9315,5.16029 8.71902,0 11.27323,-5.51325 3.15558,-6.81131 -6.87805,-1.09985 -9.23159,-4.13154 -4.58023,-5.89998 1.91444,-0.72787 4.50021,-0.99984 5.74616,-0.60439 2.66969,0.84732 4.48311,-1.3208 2.25597,-2.69724 -0.84401,-0.52164 -6.29047,-1.32616 -12.10323,-1.78784 -8.33844,-0.66228 -11.18522,-1.41864 -13.49048,-3.58432 -3.86103,-3.627252 -7.19084,-3.575502 -7.24537,0.11264 -0.0423,2.8575 -0.0423,2.8575 -2.41747,0.0102 -2.36785,-2.83804 -10.16339,-3.34289 -39.45028,-2.55484 -1.74625,0.047 -11.7475,-0.926562 -22.22499,-2.163412 -22.93017,-2.70689 -40.69706,-3.3658 -93.345,-3.46188 -40.55079,-0.074 -45.71999,-0.56722 -45.71999,-4.36247 0,-1.82971 1.0352,-2.00916 7.61999,-1.32091 4.191,0.43805 7.62,0.29486 7.62,-0.31819 0,-2.21274 3.76094,-2.48621 7.6322,-0.55497 2.17384,1.08445 6.06078,1.91629 8.63762,1.84852 19.69628,-0.51799 25.86115,-0.26759 27.53098,1.11825 1.73414,1.4392 9.69154,0.39421 17.1592,-2.25341 2.71977,-0.96429 5.39167,-0.33449 7.83185,1.84605 1.50349,1.34349 2.79145,1.54093 4.445,0.68136 1.28049,-0.66563 11.00582,-1.19772 21.61185,-1.18244 12.77729,0.0184 20.60006,-0.51745 23.18511,-1.58821 2.15552,-0.89285 5.65254,-1.26579 7.81454,-0.83339 4.09439,0.81887 13.07614,-0.84348 19.56664,-3.62142 3.83091,-1.63963 19.4252,-0.15914 23.0336,2.18676 0.79398,0.51618 2.14983,0.0875 3.01301,-0.9525 0.86317,-1.03966 3.4297,-1.89062 5.70339,-1.89062 5.16872,0 7.52828,-2.6811 6.57117,-7.46665 -0.80849,-4.04247 1.44958,-8.18441 4.11361,-7.54551 0.93374,0.22394 1.99001,-0.39829 2.34726,-1.38271 0.45022,-1.2406 0.0114,-1.54514 -1.42875,-0.99236 -1.26739,0.48634 -2.07829,0.15352 -2.07829,-0.85303 0,-2.32488 -4.07785,-4.06513 -16.51,-7.04577 -17.55749,-4.20944 -25.95821,-6.63156 -27.2415,-7.85435 -0.66357,-0.63229 -2.17845,-1.14962 -3.36638,-1.14962 -1.18795,0 -5.21702,-1.42317 -8.9535,-3.16259 -3.7365,-1.73943 -8.84817,-3.16818 -11.35928,-3.175 -5.10611,-0.0139 -9.55817,-3.74492 -8.31969,-6.97233 0.62436,-1.627042 -0.29839,-1.945423 -5.24347,-1.809197 -3.30064,0.09093 -7.51591,-0.434804 -9.36727,-1.16829 -6.15291,-2.437712 -44.37074,-0.484047 -47.51575,2.428967 -3.52506,3.26502 -4.55982,3.09416 -9.42636,-1.556566 -4.16993,-3.985005 -4.16993,-3.985005 -14.47585,-3.039165 -14.50072,1.330822 -73.18443,2.315445 -83.93658,1.408327 -2.77786,-0.234358 -7.8308,0.399507 -11.22874,1.408588 -4.49077,1.333615 -7.17037,1.488776 -9.81136,0.568123 -2.94582,-1.026917 -4.22331,-0.802456 -6.75159,1.186283 -3.55116,2.79334 -6.19371,3.07949 -9.48087,1.02663 -3.77143,-2.355297 -14.19402,4.38732 -11.28245,7.29889 0.37572,0.37572 1.94775,0.006 3.4934,-0.8209 3.11462,-1.66689 5.37246,0.19091 4.13888,3.40556 -0.75552,1.96888 -4.19162,2.32356 -4.19162,0.43267 0,-0.6985 -2.896,-1.27 -6.43558,-1.27 -5.76551,0 -6.3551,0.25357 -5.66262,2.43541 1.06213,3.34645 -7.01087,9.42866 -13.15006,9.90729 -2.5282,0.1971 -7.45424,0.55358 -10.94674,0.79216 -3.4925,0.2386 -9.49325,1.37457 -13.335,2.52439 -3.84174,1.14984 -9.27099,2.10613 -12.06499,2.12512 -6.53699,0.0445 -8.76682,1.46233 -7.6808,4.88408 1.92779,6.07394 7.26322,7.97455 27.75463,9.88696 10.61289,0.99046 19.86766,1.99446 20.56616,2.2311 0.6985,0.23665 2.2517,-0.0743 3.45155,-0.69088 1.52317,-0.78281 4.2061,-0.11265 8.88999,2.22043 3.96233,1.9737 8.57189,3.26674 11.2606,3.15873 2.5521,-0.10249 7.57401,1.22382 11.43,3.01876 3.78282,1.76091 7.92561,3.20722 9.20619,3.21404 1.29313,0.006 2.09447,0.71824 1.80233,1.59991 -0.37114,1.12014 -6.10414,1.85019 -19.47333,2.47978 -18.22283,0.85816 -23.72419,1.7263 -36.02498,5.684922 -3.75059,1.20701 -11.33606,1.81317 -22.58228,1.80455 -15.55584,-0.0114 -17.41209,0.24089 -22.799,3.10513 -7.85211,4.17502 -14.6529,4.14635 -17.97401,-0.0758 -2.64638,-3.36434 -4.11803,-2.73689 -12.68527,5.40832 -0.92301,0.87755 -2.35176,1.61351 -3.175,1.63545 -6.24582,0.16655 -7.01691,10.1505 -0.90624,11.73392 2.41816,0.62662 6.02942,2.30193 8.02501,3.72291 6.90346,4.9157 23.96564,6.04888 68.65828,4.55993 15.63802,-0.52099 23.10139,-0.2623 27.74062,0.96151 5.33367,1.40699 7.86737,1.38331 15.87268,-0.14836 5.23924,-1.00243 10.18672,-1.40419 10.99437,-0.89281 0.80767,0.51139 5.75474,1.43101 10.99349,2.04358 9.19532,1.07523 11.78006,1.15434 31.115,0.95223 18.85939,-0.19711 33.19898,0.58326 37.46499,2.03891 8.747,2.98463 15.62839,3.49253 19.45099,1.4356 z M -63.21607,94.426988 c -10.60178,-0.37088 -10.60178,-0.37088 -9.75264,-3.81 1.264,-5.11925 2.3959,-5.68983 7.97269,-4.01898 3.33054,0.99785 5.81988,1.10425 7.24008,0.30946 1.42993,-0.80023 4.33535,-0.64811 8.5621,0.44829 4.35897,1.13071 7.14198,1.26124 8.73323,0.40963 1.647,-0.88144 3.9438,-0.6536 7.79092,0.77285 3.00046,1.11253 6.74127,2.32831 8.3129,2.70174 4.4772,1.06383 3.34932,2.90378 -1.905,3.10768 -10.24027,0.39736 -26.8439,0.433 -36.95428,0.0794 z"
+         id="path12884"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#ae8858;stroke-width:1.26999998"
+         d="m -18.32429,446.55289 c -0.6985,-0.26926 -8.3998,-1.39439 -17.11399,-2.50028 -28.32573,-3.59473 -90.23846,-23.98818 -94.68926,-31.18974 -0.50431,-0.81598 -3.84413,-2.78021 -7.42183,-4.36497 -13.11492,-5.80931 -34.27248,-21.53639 -50.41498,-37.47504 -5.30575,-5.23875 -12.01497,-11.16609 -14.90937,-13.17186 -8.35234,-5.78801 -15.62897,-15.23647 -37.78164,-49.05815 -9.62038,-14.68795 -15.74117,-29.61071 -15.77884,-38.46952 -0.008,-1.94948 -1.77759,-6.93107 -3.93176,-11.07019 -2.29149,-4.40295 -4.44617,-11.10031 -5.19274,-16.14047 -0.70183,-4.73815 -2.34177,-13.18682 -3.64433,-18.77482 -4.66326,-20.00553 -4.18951,-54.37101 0.90166,-65.405 4.6637,-10.10756 5.91102,-15.59043 6.78958,-29.845 1.04181,-16.90329 4.33394,-26.56682 14.43533,-42.372602 1.94823,-3.04843 4.286,-7.65063 5.19503,-10.22712 5.44228,-15.42506 26.26682,-43.826962 41.49267,-56.59049 1.64696,-1.380615 8.42373,-7.127585 15.05948,-12.771044 6.63575,-5.643459 17.06365,-13.566377 23.17312,-17.606486 6.10947,-4.040107 11.43906,-7.881112 11.84353,-8.535566 0.40448,-0.654454 5.1889,-3.42909 10.63206,-6.165857 5.44317,-2.736768 12.40082,-6.633725 15.46147,-8.659904 10.33169,-6.839694 38.99021,-19.006599 54.45981,-23.120794 3.14325,-0.835959 10.13805,-2.898185 15.54401,-4.582727 18.4222,-5.740513 56.54074,-8.424837 79.07098,-5.568216 20.23955,2.566183 42.48329,6.140779 46.50435,7.473307 2.36261,0.782937 9.43915,3.08065 15.72565,5.106028 12.15104,3.914811 18.93466,6.779309 37.76305,15.946083 14.61041,7.113205 24.43105,12.567617 29.47827,16.372313 2.13327,1.608102 7.94263,5.28411 12.90971,8.168908 9.29849,5.40042 12.9282,8.129162 30.33896,22.808233 23.69829,19.980106 48.53615,51.516362 54.07606,68.659562 0.83115,2.57201 3.67486,8.39112 6.31936,12.93137 7.34648,12.612972 12.62574,28.960622 12.4215,38.464162 -0.19002,8.84145 3.57577,25.8974 7.21009,32.65584 7.80794,14.5198 1.41847,72.93981 -12.18908,111.44654 -7.42375,21.00782 -23.50368,51.03384 -33.39932,62.36644 -2.21977,2.54211 -5.67874,7.06653 -7.68658,10.05427 -7.11689,10.59014 -43.2131,43.04109 -56.59703,50.88146 -3.14325,1.84132 -8.48705,5.21164 -11.87512,7.48959 -3.38807,2.27793 -6.67869,4.14171 -7.31251,4.14171 -0.6338,0 -6.1485,2.83709 -12.25487,6.30465 -20.29127,11.52257 -58.71063,24.35866 -89.84249,30.01681 -10.41071,1.89213 -61.19414,3.75707 -64.76999,2.37857 z M 8.66357,364.99103 c 0.69439,-2.08551 -3.28483,-3.57421 -4.99158,-1.86746 -0.73038,0.73038 -0.92443,1.98091 -0.43122,2.77895 1.12211,1.81561 4.71592,1.21154 5.4228,-0.91149 z M -6.25818,354.83036 c -6.1e-4,-0.87313 -1.35989,-1.5875 -3.02062,-1.5875 -1.66071,0 -3.73094,-0.85725 -4.60049,-1.905 -1.81588,-2.18799 -7.07229,-2.54868 -8.29564,-0.56924 -0.45405,0.73467 -2.34516,1.21998 -4.20246,1.07848 -12.05552,-0.91848 -17.14679,1.41387 -7.5044,3.43781 8.44454,1.77253 27.62496,1.45691 27.62361,-0.45455 z m 35.68687,-6.82448 c 2.37436,-0.78484 6.42662,-3.07084 9.00504,-5.08 3.99251,-3.11106 5.83564,-3.65302 12.42329,-3.65302 13.45151,0 21.62368,-4.88912 21.62368,-12.93669 0,-5.34839 0.27569,-5.55465 8.89,-6.65117 9.76173,-1.24258 10.21773,0.62248 2.0919,8.55581 -11.6298,11.35432 -8.23865,19.67087 7.4331,18.22908 16.85666,-1.55078 22.39845,-7.01095 17.85529,-17.5923 -0.73018,-1.70066 0.13765,-2.45683 4.22509,-3.68145 5.46117,-1.63621 9.03462,-0.29436 9.03462,3.39254 0,2.26174 4.38001,4.52308 6.893,3.55875 2.38102,-0.91369 2.40765,-1.13421 0.5683,-4.70556 -3.32208,-6.45023 -6.40569,-8.56379 -11.18798,-7.66843 -4.05154,0.75856 -4.35103,0.63378 -2.84446,-1.18513 1.29189,-1.5597 3.09971,-1.86527 7.7658,-1.31264 3.86763,0.45806 6.40291,0.20094 6.96357,-0.70625 0.48492,-0.78462 2.27669,-1.42656 3.98172,-1.42656 4.34618,0 3.87067,-2.16894 -0.69539,-3.1718 -2.0875,-0.4585 -4.15931,-1.42238 -4.60404,-2.14197 -0.53149,-0.85998 0.22517,-0.95537 2.20793,-0.27836 5.4871,1.87354 18.91029,0.64028 18.87952,-1.73457 -0.006,-0.51056 -2.57837,-2.05651 -5.715,-3.43544 -10.46887,-4.6023 -6.87421,-8.87128 9.91472,-11.77459 16.85812,-2.91527 23.42197,2.07597 12.45199,9.46864 -6.66911,4.49431 -0.66067,3.81062 7.92619,-0.90192 3.81829,-2.0955 7.16773,-3.81 7.44321,-3.81 1.74891,0 -0.97807,9.24958 -3.18213,10.79335 -1.4513,1.01652 -2.25838,2.22856 -1.79352,2.69342 0.46484,0.46486 1.57336,0.26792 2.46337,-0.43763 0.89001,-0.70555 6.47594,-1.99936 12.41319,-2.87515 8.8683,-1.30813 11.47501,-2.23398 14.605,-5.18736 2.0955,-1.97725 6.38175,-4.95409 9.525,-6.61518 7.95441,-4.20364 7.64326,-12.34145 -0.47187,-12.34145 -5.52732,0 -17.90234,-5.6311 -16.68909,-7.59416 0.32508,-0.52601 -0.53846,-1.97858 -1.91899,-3.22794 -3.55982,-3.22159 -3.17895,-3.80836 2.56995,-3.95911 2.794,-0.0733 5.08,0.427 5.08,1.11168 0,1.79107 6.69625,-0.063 8.44034,-2.33697 2.05235,-2.67588 1.01492,-10.91925 -1.61633,-12.84326 -2.0617,-1.50755 -2.01653,-1.81144 0.69897,-4.70196 4.13021,-4.39641 2.53437,-5.68326 -6.07927,-4.90221 -8.38105,0.75997 -10.7001,-0.81288 -7.08628,-4.80609 4.10794,-4.53923 -0.75925,-2.66752 -5.66311,2.17778 -3.00976,2.97384 -6.83581,5.18164 -10.66406,6.15368 -6.33123,1.60756 -7.61459,1.00646 -3.43026,-1.60671 3.23581,-2.02078 3.26784,-4.10473 0.0631,-4.10473 -1.36229,0 -2.96459,-0.87144 -3.56065,-1.93656 -1.18258,-2.11317 -6.47301,-2.6181 -12.2257,-1.16685 -2.82992,0.71392 -3.52561,0.52274 -3.07245,-0.84434 0.79676,-2.40369 16.19974,-4.5477 17.50451,-2.43652 1.29864,2.10122 10.70186,-0.48707 16.74443,-4.60904 3.6742,-2.50635 5.17837,-2.89928 7.12036,-1.85996 1.84724,0.98862 4.12652,0.68524 9.12704,-1.21481 5.13582,-1.95146 7.07156,-2.19504 8.43716,-1.06169 1.3449,1.11616 2.21035,1.10714 3.58947,-0.0375 0.99952,-0.82951 5.00002,-1.8367 8.89,-2.23818 3.89,-0.40149 7.14416,-1.22151 7.23147,-1.82229 0.0874,-0.60078 0.23019,-1.6162 0.3175,-2.2565 0.0874,-0.64028 0.58738,-1.59278 1.11125,-2.11665 1.57688,-1.57689 1.07579,-5.19584 -0.9525,-6.87918 -2.45232,-2.03523 -2.45232,-5.86822 0,-6.80927 2.68836,-1.03162 2.36442,-3.66287 -0.78266,-6.35749 -2.0999,-1.79798 -2.5348,-3.24334 -1.98863,-6.60898 0.38448,-2.36925 0.16914,-4.63522 -0.47853,-5.0355 -2.05376,-1.2693 -14.27909,-4.51852 -21.24029,-5.64518 -8.9457,-1.44788 -20.53783,-6.31452 -19.77514,-8.30207 0.75575,-1.96945 -3.20902,-3.57056 -5.10822,-2.06286 -0.78459,0.62286 -7.27894,1.09169 -14.43188,1.04186 -7.15295,-0.0498 -12.31301,-0.51851 -11.46678,-1.04151 3.70191,-2.2879 -2.14579,-3.01733 -27.0202,-3.3704 -25.55203,-0.36271 -26.57666,-0.28179 -28.14061,2.2225 -0.91802,1.46999 -2.97495,2.5989 -4.73528,2.5989 -2.92202,0 -2.96911,-0.10464 -0.77047,-1.71234 3.7024,-2.70727 1.41679,-4.54031 -4.07203,-3.26574 -6.59116,1.53055 -22.18134,1.42141 -32.07427,-0.22455 -10.53216,-1.75231 -20.66944,-2.27463 -55.4388,-2.85648 -26.37525,-0.44139 -28.418,-0.64647 -32.68067,-3.28094 -4.05904,-2.50864 -5.01681,-2.64021 -8.92677,-1.22634 -4.82407,1.74439 -15.51145,0.004 -22.96875,-3.74058 -5.01354,-2.51742 -28.74812,-4.26327 -33.83776,-2.48901 -7.02347,2.44839 -1.98919,4.21177 11.26909,3.94726 11.93438,-0.23809 16.85367,0.82156 16.85367,3.63042 0,0.94088 -3.10177,1.19327 -9.56629,0.77841 -5.26147,-0.33767 -9.86718,-0.12708 -10.23495,0.46798 -1.40797,2.27815 -9.69126,0.94728 -16.0955,-2.58604 -9.19815,-5.07473 -30.14327,-6.94822 -30.14327,-2.69621 0,3.35664 5.11196,5.36218 12.50841,4.90733 4.74668,-0.29189 6.03353,0.18201 8.60166,3.16764 5.66816,6.58962 1.2067,8.49405 -5.89659,2.51704 -4.58689,-3.85963 -22.35019,-5.99392 -25.83253,-3.10383 -0.96049,0.79713 -5.10775,1.66117 -9.21614,1.92009 -4.10839,0.25892 -9.87945,1.12552 -12.82458,1.92579 -4.18844,1.13812 -5.92956,1.1011 -7.99374,-0.16992 -2.30418,-1.41879 -2.92178,-1.25276 -4.86809,1.30865 -2.73823,3.60361 -10.49968,4.08706 -16.42477,1.02308 -9.32677,-4.82306 -26.95362,-1.30409 -26.95362,5.38093 0,1.43294 -2.19333,3.01676 -6.46767,4.67032 -6.71083,2.59615 -9.03057,4.53267 -6.45718,5.39047 2.07212,0.6907 1.88626,5.36661 -0.21331,5.36661 -3.05911,0 -6.23903,4.61804 -4.95567,7.19685 0.63724,1.28049 1.01904,6.75728 0.84845,12.17065 -0.17059,5.41338 0.19615,9.8425 0.81499,9.8425 0.61884,0 2.01177,1.7145 3.09539,3.81 1.72294,3.3318 2.65157,3.81 7.39861,3.81 2.98562,0 5.97131,0.53056 6.63489,1.17901 0.66357,0.64845 9.49325,1.8724 19.6215,2.71988 10.12825,0.84748 18.95792,2.03144 19.6215,2.631 0.66357,0.59956 4.37832,1.15773 8.255,1.24037 13.32166,0.28399 15.83325,1.59074 6.4135,3.33685 -4.54025,0.8416 -9.46955,1.21055 -10.954,0.81987 -1.48825,-0.39168 -4.90652,0.39611 -7.62,1.75614 -6.66737,3.34175 -12.11027,4.55128 -13.12108,2.91575 -0.93509,-1.51301 -8.46811,0.76442 -11.25814,3.40363 -0.92301,0.87313 -2.49464,1.5875 -3.4925,1.5875 -0.99785,0 -1.81428,0.5715 -1.81428,1.27 0,0.6985 1.13626,1.27 2.52503,1.27 1.84929,0 2.44406,0.75078 2.2225,2.80545 -0.20797,1.92857 0.49138,2.96521 2.23747,3.31656 1.397,0.28112 2.99702,1.30467 3.55561,2.27456 0.64837,1.12581 3.26174,1.76343 7.22754,1.76343 3.41657,0 6.56515,0.5715 6.99685,1.27 0.43169,0.6985 3.07474,1.27 5.87344,1.27 2.7987,0 6.23299,0.6125 7.63177,1.3611 1.39878,0.7486 6.07608,1.46298 10.39401,1.5875 7.09373,0.20457 7.8899,0.50196 8.2566,3.0839 0.40913,2.88071 3.80491,3.59461 15.46917,3.25214 36.33403,-1.0668 34.06733,-1.18219 37.13291,1.89036 5.75965,5.77276 -14.65283,10.46304 -21.61965,4.96767 -1.11312,-0.87803 -3.77616,-0.77268 -8.3786,0.33143 -5.98479,1.43574 -7.12593,1.37018 -10.10282,-0.58036 -3.14409,-2.06008 -3.40806,-2.06313 -4.18125,-0.0483 -0.64747,1.68729 -1.76548,1.97426 -5.18921,1.33197 -2.68396,-0.50351 -5.06842,-0.23399 -6.19396,0.70013 -1.25144,1.0386 -4.40367,1.25513 -9.96997,0.68485 -7.45064,-0.76335 -8.20926,-0.61552 -8.95869,1.74573 -1.39698,4.4015 -7.78646,6.8014 -15.15327,5.69161 -16.51303,-2.48763 -21.1065,-0.99587 -19.28468,6.26283 1.00688,4.01174 13.96945,10.91469 20.49912,10.91639 2.1147,5.5e-4 5.14613,0.91242 6.73651,2.02636 1.83585,1.28588 4.07232,1.72902 6.12546,1.21371 1.77862,-0.4464 5.1611,-0.1756 7.51663,0.60179 2.35553,0.77741 7.58436,1.54145 11.61963,1.69788 4.60934,0.17869 8.65528,1.15801 10.88338,2.63432 3.4873,2.31063 3.5889,2.30442 6.08197,-0.37159 3.13995,-3.37034 2.74091,-3.3944 9.51226,0.57388 6.89694,4.04186 7.40585,4.80988 5.35902,8.08738 -1.14873,1.83942 -1.24037,3.19988 -0.3144,4.66785 1.80706,2.86478 3.62479,2.43375 4.44123,-1.05312 0.58705,-2.50726 1.59984,-2.98648 6.98499,-3.30505 8.69516,-0.51436 11.93577,1.86392 9.89755,7.2638 -0.98871,2.61938 -1.02964,3.64301 -0.12332,3.08285 0.74629,-0.46124 5.12727,-1.0116 9.73551,-1.22305 15.12592,-0.69403 24.16349,-1.56039 25.61129,-2.45519 2.40049,-1.48358 7.36479,2.11527 8.20696,5.9496 0.9903,4.50882 5.01092,7.43958 10.20612,7.43958 4.50034,0 8.88123,-3.93281 7.72814,-6.93769 -1.15024,-2.99748 7.37324,-2.53691 11.65653,0.62986 5.72876,4.23545 19.75399,3.94871 23.86792,-0.48798 3.41037,-3.67793 4.53158,-4.22953 3.61139,-1.77669 -3.01052,8.02487 16.89628,15.29131 30.01326,10.95553 z m -121.09548,-25.6683 c -4.16752,-3.29977 -2.40472,-4.3076 7.22692,-4.13174 5.19719,0.0949 9.78299,0.5061 10.1907,0.91379 3.03037,3.03039 -13.74111,6.12897 -17.41762,3.21795 z m 27.94,0.73277 c -4.07756,-0.90584 -2.28919,-2.68186 3.58798,-3.5632 3.42915,-0.51423 6.33421,-0.3476 7.0884,0.40659 0.85721,0.85721 2.69051,0.84437 5.56886,-0.039 4.21946,-1.295 8.20226,0.15079 8.20226,2.97748 0,1.02227 -20.02347,1.20096 -24.4475,0.21816 z m -22.86,-37.23922 c -2.71055,-1.57875 -2.8589,-4.1391 -0.36001,-6.21301 1.52099,-1.2623 2.4311,-0.88469 4.96347,2.05935 4.31851,5.02057 1.4024,7.65175 -4.60346,4.15366 z M 35.05196,148.83153 c -0.77834,-1.2594 -8.29126,-2.48618 -8.29126,-1.35389 0,1.08675 6.73514,3.54091 7.95822,2.89982 0.53526,-0.28056 0.68514,-0.97624 0.33304,-1.54593 z m -71.47375,-1.18113 c 0.52387,-0.0812 0.9525,-1.17574 0.9525,-2.43242 0,-2.76625 7.66828,-5.88889 13.55693,-5.5206 2.21757,0.1387 4.39443,-0.33434 4.83745,-1.05118 1.25149,-2.02494 7.03664,-1.54657 14.95541,1.23665 8.72863,3.06786 17.05753,3.28904 20.10205,0.53378 1.76982,-1.60167 2.76538,-1.71267 4.93699,-0.55045 1.52691,0.81717 4.39798,1.08726 6.545,0.61568 2.85554,-0.62718 4.23604,-0.27777 5.44728,1.3787 1.51819,2.07626 1.81753,2.02364 4.66018,-0.81901 2.63854,-2.63854 4.01014,-2.98177 10.43633,-2.6116 5.58889,0.32194 7.97542,-0.0959 9.75703,-1.70822 2.1093,-1.9089 4.14679,-2.00666 19.28441,-0.92536 9.78054,0.69863 17.52612,0.73655 18.34767,0.0898 0.78195,-0.61557 10.69281,-1.41779 22.02413,-1.78272 13.07822,-0.42119 22.53102,-1.33551 25.88326,-2.50358 2.90448,-1.01205 7.94995,-1.8484 11.21217,-1.85857 6.65181,-0.0207 25.97627,-6.31581 32.80369,-10.68605 7.82817,-5.01081 8.60252,-4.98157 13.8384,0.52247 4.03413,4.24076 5.63575,5.0651 9.84095,5.0651 8.71902,0 11.27323,-5.51324 3.15558,-6.81131 -6.87805,-1.09985 -9.23159,-4.13154 -4.58023,-5.89998 1.91444,-0.72787 4.50021,-0.99984 5.74616,-0.60439 2.66969,0.84732 4.48311,-1.3208 2.25597,-2.69724 -0.84401,-0.52164 -6.29047,-1.32616 -12.10323,-1.78784 -8.23522,-0.65408 -11.1969,-1.4296 -13.41407,-3.51254 -2.53209,-2.37878 -4.51843,-2.69789 -18.0386,-2.89795 -8.35626,-0.1237 -16.64057,-0.798622 -18.40956,-1.499922 -1.769,-0.7013 -4.35937,-1.7325 -5.75637,-2.29157 -2.97407,-1.19018 -15.7736,-2.6787 -26.81472,-3.11843 -10.38924,-0.41377 -13.63644,-2.14982 -8.21864,-4.39395 2.18239,-0.90397 5.67566,-1.28092 7.84672,-0.8467 4.09439,0.81887 13.07614,-0.84349 19.56664,-3.62142 3.83091,-1.63964 19.4252,-0.15915 23.0336,2.18676 0.79398,0.51618 2.14983,0.0875 3.01301,-0.9525 0.86317,-1.04002 3.4297,-1.89098 5.70339,-1.89098 5.16872,0 7.52828,-2.6811 6.57117,-7.46665 -0.80849,-4.04247 1.44958,-8.18441 4.11361,-7.54551 0.93374,0.22394 1.99001,-0.39829 2.34726,-1.38271 0.45022,-1.2406 0.0114,-1.54514 -1.42875,-0.99236 -1.26739,0.48634 -2.07829,0.15352 -2.07829,-0.85303 0,-2.32488 -4.07785,-4.06513 -16.51,-7.04577 -17.55749,-4.20944 -25.95821,-6.63156 -27.2415,-7.85435 -0.66357,-0.63229 -2.17845,-1.14962 -3.36638,-1.14962 -1.18795,0 -5.21702,-1.42317 -8.9535,-3.16259 -3.7365,-1.73943 -8.84817,-3.16818 -11.35928,-3.175 -5.10611,-0.0139 -9.55817,-3.74492 -8.31969,-6.97233 0.62436,-1.627042 -0.29839,-1.945423 -5.24347,-1.809197 -3.30064,0.09093 -7.51591,-0.434804 -9.36727,-1.16829 -6.15291,-2.437712 -44.37074,-0.484047 -47.51575,2.428967 -3.52506,3.26502 -4.55982,3.09416 -9.42636,-1.556566 -4.16993,-3.985005 -4.16993,-3.985005 -14.47585,-3.039165 -14.50072,1.330822 -73.18443,2.315445 -83.93658,1.408327 -2.77786,-0.234358 -7.8308,0.399507 -11.22874,1.408588 -4.49077,1.333615 -7.17037,1.488776 -9.81136,0.568123 -2.94582,-1.026917 -4.22331,-0.802456 -6.75159,1.186283 -3.55116,2.79334 -6.19371,3.07949 -9.48087,1.02663 -3.77143,-2.355297 -14.19402,4.38732 -11.28245,7.29889 0.37572,0.37572 1.94775,0.006 3.4934,-0.8209 3.11462,-1.66689 5.37246,0.19091 4.13888,3.40556 -0.75552,1.96888 -4.19162,2.32356 -4.19162,0.43267 0,-0.6985 -2.896,-1.27 -6.43558,-1.27 -5.76551,0 -6.3551,0.25357 -5.66262,2.43541 1.06213,3.34645 -7.01086,9.42866 -13.15006,9.90729 -2.5282,0.1971 -7.45424,0.55358 -10.94674,0.79216 -3.49249,0.2386 -9.49324,1.37457 -13.33499,2.52439 -3.84175,1.14984 -9.271,2.10613 -12.065,2.12512 -6.53699,0.0445 -8.76682,1.46233 -7.6808,4.88408 1.92779,6.07394 7.26322,7.97455 27.75463,9.88696 10.6129,0.99046 19.86766,1.99446 20.56616,2.2311 0.6985,0.23665 2.2517,-0.0743 3.45155,-0.69088 1.52354,-0.783 4.20494,-0.11189 8.88999,2.22514 3.94074,1.9657 8.53322,3.26039 11.13135,3.13808 4.39866,-0.20706 13.99211,3.25288 13.99211,5.04636 0,1.70529 -5.90899,2.46992 -24.33773,3.14931 -28.3665,1.04575 -33.24785,1.60531 -35.02928,4.01553 -0.8701,1.17723 -2.5846,2.26482 -3.81,2.41686 -1.22539,0.152062 -4.22446,0.903462 -6.6646,1.669792 -2.44014,0.76631 -7.12477,1.3933 -10.4103,1.3933 -3.9329,0 -8.01367,1.08467 -11.94503,3.175 -7.95994,4.23234 -14.74257,4.22525 -18.081,-0.0189 -2.64638,-3.36433 -4.11803,-2.73689 -12.68527,5.40832 -0.92301,0.87756 -2.35176,1.61351 -3.175,1.63545 -6.24582,0.16655 -7.01691,10.1505 -0.90624,11.73393 2.41816,0.62662 5.84629,2.15392 7.61806,3.39402 9.51442,6.65936 61.52849,10.43907 81.00889,5.88669 3.99111,-0.93268 6.96936,-0.80952 11.43,0.4727 9.84709,2.83056 15.35659,3.01366 22.55468,0.74958 5.26266,-1.65531 7.40247,-1.81624 9.46593,-0.71191 1.46638,0.78478 5.93443,1.65799 9.92901,1.94047 3.99459,0.28248 12.18794,0.89203 18.20745,1.35455 7.53562,0.57902 11.21799,0.39853 11.82245,-0.57948 1.29836,-2.10075 5.80798,-1.72451 5.80798,0.4846 0,1.04775 0.0428,2.31934 0.0952,2.82575 0.24399,2.35862 -2.39756,1.39538 -3.05364,-1.11351 -0.7418,-2.83663 -0.7418,-2.83663 -3.91107,0.58307 -1.74309,1.88083 -4.02007,3.41969 -5.05994,3.41969 -2.53444,0 -4.01292,2.59104 -2.13926,3.74903 1.20287,0.74341 11.33237,0.87832 15.65616,0.20852 z m -26.3525,-54.672942 c -9.26526,-0.65624 -10.12269,-0.94329 -9.73637,-3.25961 0.68742,-4.12149 2.14779,-4.72779 7.51464,-3.11984 3.33054,0.99785 5.81988,1.10425 7.24008,0.30946 1.42993,-0.80023 4.33535,-0.64811 8.5621,0.44829 4.15398,1.07754 7.17354,1.24435 8.60988,0.47565 2.84396,-1.52205 11.46467,0.76328 11.46467,3.03923 0,2.96778 -11.41249,3.6822 -33.655,2.10682 z m 71.11999,0.25385 c -4.28386,-0.61948 -8.83164,-4.07982 -7.33238,-5.57908 1.07743,-1.07743 13.04478,4.02099 13.04639,5.55812 5.5e-4,0.52388 -0.42763,0.86688 -0.95151,0.76222 -0.52387,-0.10465 -2.667,-0.43823 -4.7625,-0.74126 z m 21.2725,0.0479 c -4.74623,-0.93519 -3.20463,-3.10842 1.905,-2.68554 2.61938,0.2168 4.64981,0.96567 4.51208,1.66417 -0.29141,1.47795 -2.39814,1.81326 -6.41708,1.02137 z m -43.92082,-1.23304 c -3.21878,-3.21876 4.64507,-4.20587 8.75309,-1.09871 2.29869,1.73864 2.11491,1.86733 -2.72059,1.905 -2.85222,0.0222 -5.56685,-0.34062 -6.0325,-0.80629 z m 99.48332,-0.42329 c 0,-0.6985 1.143,-1.27 2.54,-1.27 1.397,0 2.54,0.5715 2.54,1.27 0,0.6985 -1.143,1.27 -2.54,1.27 -1.397,0 -2.54,-0.5715 -2.54,-1.27 z"
+         id="path12882"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#8e8d72;stroke-width:1.26999998"
+         d="m -18.32429,446.55289 c -0.6985,-0.26926 -8.3998,-1.39439 -17.11399,-2.50028 -28.32573,-3.59473 -90.23846,-23.98818 -94.68926,-31.18974 -0.50431,-0.81598 -3.84413,-2.78021 -7.42183,-4.36497 -13.11492,-5.80931 -34.27248,-21.53639 -50.41498,-37.47504 -5.30575,-5.23875 -12.01497,-11.16609 -14.90937,-13.17186 -8.35234,-5.78801 -15.62897,-15.23647 -37.78164,-49.05815 -9.62038,-14.68795 -15.74117,-29.61071 -15.77884,-38.46952 -0.008,-1.94948 -1.77759,-6.93107 -3.93176,-11.07019 -2.29149,-4.40295 -4.44617,-11.10031 -5.19274,-16.14047 -0.70183,-4.73815 -2.34177,-13.18682 -3.64433,-18.77482 -4.66326,-20.00553 -4.18951,-54.37101 0.90166,-65.405 4.6637,-10.10756 5.91102,-15.59043 6.78958,-29.845 1.04181,-16.90329 4.33394,-26.56682 14.43533,-42.372602 1.94823,-3.04843 4.286,-7.65063 5.19503,-10.22712 5.44228,-15.42506 26.26682,-43.826962 41.49267,-56.59049 1.64696,-1.380615 8.42373,-7.127585 15.05948,-12.771044 6.63575,-5.643459 17.06365,-13.566377 23.17312,-17.606486 6.10947,-4.040107 11.43906,-7.881112 11.84353,-8.535566 0.40448,-0.654454 5.1889,-3.42909 10.63206,-6.165857 5.44317,-2.736768 12.40082,-6.633725 15.46147,-8.659904 10.33169,-6.839694 38.99021,-19.006599 54.45981,-23.120794 3.14325,-0.835959 10.13805,-2.898185 15.54401,-4.582727 18.4222,-5.740513 56.54074,-8.424837 79.07098,-5.568216 20.23955,2.566183 42.48329,6.140779 46.50435,7.473307 2.36261,0.782937 9.43915,3.08065 15.72565,5.106028 12.15104,3.914811 18.93466,6.779309 37.76305,15.946083 14.61041,7.113205 24.43105,12.567617 29.47827,16.372313 2.13327,1.608102 7.94263,5.28411 12.90971,8.168908 9.29849,5.40042 12.9282,8.129162 30.33896,22.808233 23.69829,19.980106 48.53615,51.516362 54.07606,68.659562 0.83115,2.57201 3.67486,8.39112 6.31936,12.93137 7.34648,12.612972 12.62574,28.960622 12.4215,38.464162 -0.19002,8.84145 3.57577,25.8974 7.21009,32.65584 7.80794,14.5198 1.41847,72.93981 -12.18908,111.44654 -7.42375,21.00782 -23.50368,51.03384 -33.39932,62.36644 -2.21977,2.54211 -5.67874,7.06653 -7.68658,10.05427 -7.11689,10.59014 -43.2131,43.04109 -56.59703,50.88146 -3.14325,1.84132 -8.48705,5.21164 -11.87512,7.48959 -3.38807,2.27793 -6.67869,4.14171 -7.31251,4.14171 -0.6338,0 -6.1485,2.83709 -12.25487,6.30465 -20.29127,11.52257 -58.71063,24.35866 -89.84249,30.01681 -10.41071,1.89213 -61.19414,3.75707 -64.76999,2.37857 z M 8.66357,364.99103 c 0.69439,-2.08551 -3.28483,-3.57421 -4.99158,-1.86746 -0.73038,0.73038 -0.92443,1.98091 -0.43122,2.77895 1.12211,1.81561 4.71592,1.21154 5.4228,-0.91149 z M -6.25818,354.83036 c -6.1e-4,-0.87313 -1.35989,-1.5875 -3.02062,-1.5875 -1.66071,0 -3.73094,-0.85725 -4.60049,-1.905 -1.81588,-2.18799 -7.07229,-2.54868 -8.29564,-0.56924 -0.45405,0.73467 -2.34516,1.21998 -4.20246,1.07848 -12.05552,-0.91848 -17.14679,1.41387 -7.5044,3.43781 8.44454,1.77253 27.62496,1.45691 27.62361,-0.45455 z m 35.68687,-6.82448 c 2.37436,-0.78484 6.42662,-3.07084 9.00504,-5.08 3.99251,-3.11106 5.83564,-3.65302 12.42329,-3.65302 13.45151,0 21.62368,-4.88912 21.62368,-12.93669 0,-5.34839 0.27569,-5.55465 8.89,-6.65117 9.76173,-1.24258 10.21773,0.62248 2.0919,8.55581 -11.6298,11.35432 -8.23865,19.67087 7.4331,18.22908 16.85666,-1.55078 22.39845,-7.01095 17.85529,-17.5923 -0.73018,-1.70066 0.13765,-2.45683 4.22509,-3.68145 5.46117,-1.63621 9.03462,-0.29436 9.03462,3.39254 0,2.26174 4.38001,4.52308 6.893,3.55875 2.38102,-0.91369 2.40765,-1.13421 0.5683,-4.70556 -3.32208,-6.45023 -6.40569,-8.56379 -11.18798,-7.66843 -4.05154,0.75856 -4.35103,0.63378 -2.84446,-1.18513 1.29189,-1.5597 3.09971,-1.86527 7.7658,-1.31264 3.86763,0.45806 6.40291,0.20094 6.96357,-0.70625 0.48492,-0.78462 2.27669,-1.42656 3.98172,-1.42656 1.70503,0 3.10004,-0.63346 3.10004,-1.40769 0,-0.8732 1.22458,-1.10032 3.22526,-0.59818 3.27279,0.82141 8.96302,-1.89251 7.35,-3.50554 -0.46123,-0.46123 0.54254,-0.85677 2.23057,-0.87898 1.68805,-0.0222 4.17853,-0.87948 5.53441,-1.905 1.35588,-1.02554 4.6068,-1.86462 7.22428,-1.86462 2.89842,0 7.47325,-1.48958 11.70136,-3.81 3.81829,-2.0955 7.16773,-3.81 7.44321,-3.81 1.74891,0 -0.97807,9.24958 -3.18213,10.79335 -1.4513,1.01652 -2.25838,2.22856 -1.79352,2.69342 0.46484,0.46486 1.57336,0.26792 2.46337,-0.43763 0.89001,-0.70555 6.47594,-1.99936 12.41319,-2.87515 8.8683,-1.30813 11.47501,-2.23398 14.605,-5.18736 2.0955,-1.97725 6.38175,-4.95409 9.525,-6.61518 7.97707,-4.2156 7.63854,-12.33931 -0.51434,-12.34247 -3.35135,-0.001 -14.24682,-4.09213 -16.25951,-6.10483 -1.1722,-1.1722 -0.89325,-1.51415 1.23517,-1.51415 1.51213,0 3.35741,-1.13625 4.10065,-2.525 0.74325,-1.38875 2.77867,-2.88324 4.52318,-3.32109 5.56949,-1.39785 7.16555,-11.83237 2.34863,-15.35459 -2.11647,-1.54759 -2.07902,-1.81634 0.6592,-4.73104 4.13021,-4.39641 2.53437,-5.68326 -6.07927,-4.90221 -8.01992,0.72722 -11.38944,-1.15453 -7.35274,-4.10624 2.41828,-1.76829 2.72571,-3.15983 0.69809,-3.15983 -0.77051,0 -3.51313,2.08699 -6.09474,4.63776 -3.00976,2.97384 -6.83581,5.18164 -10.66406,6.15368 -6.33123,1.60756 -7.61459,1.00646 -3.43026,-1.60671 3.23581,-2.02078 3.26784,-4.10473 0.0631,-4.10473 -1.36229,0 -2.96459,-0.87144 -3.56065,-1.93656 -1.18258,-2.11317 -6.47301,-2.6181 -12.2257,-1.16685 -2.82992,0.71392 -3.52561,0.52274 -3.07245,-0.84434 0.79676,-2.40369 16.19974,-4.5477 17.50451,-2.43652 1.29864,2.10122 10.70186,-0.48707 16.74443,-4.60904 3.6742,-2.50635 5.17837,-2.89928 7.12036,-1.85996 1.84724,0.98862 4.12652,0.68524 9.12704,-1.21481 5.13582,-1.95146 7.07156,-2.19504 8.43716,-1.06169 1.3449,1.11616 2.21035,1.10714 3.58947,-0.0375 0.99952,-0.82951 5.00002,-1.8367 8.89,-2.23818 3.89,-0.40149 7.14416,-1.22151 7.23147,-1.82229 0.0874,-0.60078 0.23019,-1.6162 0.3175,-2.2565 0.0874,-0.64028 0.58738,-1.59278 1.11125,-2.11665 1.57688,-1.57689 1.07579,-5.19584 -0.9525,-6.87918 -2.45232,-2.03523 -2.45232,-5.86822 0,-6.80927 2.68836,-1.03162 2.36442,-3.66287 -0.78266,-6.35749 -2.0999,-1.79798 -2.5348,-3.24334 -1.98863,-6.60898 0.38448,-2.36925 0.16914,-4.63522 -0.47853,-5.0355 -2.05376,-1.2693 -14.27909,-4.51852 -21.24029,-5.64518 -8.9457,-1.44788 -20.53783,-6.31452 -19.77514,-8.30207 0.75575,-1.96945 -3.20902,-3.57056 -5.10822,-2.06286 -0.78459,0.62286 -7.37414,1.09126 -14.64344,1.04088 -7.2693,-0.0504 -12.55568,-0.31199 -11.7475,-0.58138 2.94358,-0.98121 1.36906,-2.81815 -2.41554,-2.81815 -2.13672,0 -3.58224,-0.4898 -3.21226,-1.08842 0.71757,-1.16106 -15.53087,-4.28481 -17.13403,-3.294 -1.53625,0.94946 -33.31442,-2.00747 -36.09375,-3.35849 -1.397,-0.67907 -3.683,-1.08198 -5.08,-0.89535 -9.29722,1.24202 -26.50622,-3.35022 -24.76272,-6.60798 0.69223,-1.29347 0.94599,-3.16643 0.5639,-4.16214 -0.79339,-2.06758 15.96376,-1.73769 22.80083,0.44885 8.6346,2.7614 14.36052,0.59764 10.29793,-3.89148 -5.70854,-6.30788 -0.16879,-9.55906 18.49511,-10.85445 7.84816,-0.54472 15.72211,-1.88645 19.32446,-3.29291 3.34373,-1.30549 8.29883,-2.37363 11.01131,-2.37363 5.79546,0 25.42424,-6.58377 31.80417,-10.66757 7.82817,-5.01081 8.60253,-4.98157 13.83841,0.52247 4.03412,4.24076 5.63575,5.0651 9.84095,5.0651 8.71901,0 11.27323,-5.51324 3.15558,-6.81131 -6.87806,-1.09985 -9.23159,-4.13154 -4.58023,-5.89998 1.91443,-0.72787 4.5002,-0.99984 5.74615,-0.60439 2.66969,0.84732 4.48311,-1.3208 2.25598,-2.69724 -0.84402,-0.52164 -6.29048,-1.32616 -12.10324,-1.78784 -8.23521,-0.65408 -11.19689,-1.4296 -13.41407,-3.51254 -2.53209,-2.37878 -4.51843,-2.69789 -18.0386,-2.89795 -8.35625,-0.1237 -16.64056,-0.798622 -18.40956,-1.499922 -1.769,-0.7013 -4.35936,-1.7325 -5.75636,-2.29157 -2.97408,-1.19018 -15.77361,-2.6787 -26.81473,-3.11843 -10.38924,-0.41377 -13.63643,-2.14982 -8.21864,-4.39395 2.18239,-0.90397 5.67567,-1.28092 7.84672,-0.8467 4.09439,0.81887 13.07615,-0.84349 19.56665,-3.62142 3.8309,-1.63964 19.42519,-0.15915 23.03359,2.18676 0.79398,0.51618 2.14983,0.0875 3.01301,-0.9525 0.86316,-1.04002 3.42969,-1.89098 5.70338,-1.89098 5.16872,0 7.52828,-2.6811 6.57117,-7.46665 -0.80849,-4.04247 1.44958,-8.18441 4.11361,-7.54551 0.93374,0.22394 1.99001,-0.39829 2.34726,-1.38271 0.45022,-1.2406 0.0114,-1.54514 -1.42875,-0.99236 -1.26739,0.48634 -2.07829,0.15352 -2.07829,-0.85303 0,-2.32488 -4.07785,-4.06513 -16.51,-7.04577 -17.55749,-4.20944 -25.95821,-6.63156 -27.2415,-7.85435 -0.66357,-0.63229 -2.17845,-1.14962 -3.36638,-1.14962 -1.18795,0 -5.21702,-1.42317 -8.9535,-3.16259 -3.7365,-1.73943 -8.84817,-3.16818 -11.35928,-3.175 -5.10611,-0.0139 -9.55817,-3.74492 -8.31969,-6.97233 0.62436,-1.627042 -0.29839,-1.945423 -5.24347,-1.809197 -3.30064,0.09093 -7.51591,-0.434804 -9.36727,-1.16829 -6.15291,-2.437712 -44.37074,-0.484047 -47.51575,2.428967 -3.52506,3.26502 -4.55982,3.09416 -9.42636,-1.556566 -4.16993,-3.985005 -4.16993,-3.985005 -14.47585,-3.039165 -14.50072,1.330822 -73.18443,2.315445 -83.93658,1.408327 -2.77786,-0.234358 -7.8308,0.399507 -11.22874,1.408588 -4.49077,1.333615 -7.17037,1.488776 -9.81136,0.568123 -2.94582,-1.026917 -4.22331,-0.802456 -6.75159,1.186283 -3.55116,2.79334 -6.19371,3.07949 -9.48087,1.02663 -3.77143,-2.355297 -14.19402,4.38732 -11.28245,7.29889 0.37572,0.37572 1.94775,0.006 3.4934,-0.8209 3.11462,-1.66689 5.37246,0.19091 4.13888,3.40556 -0.75552,1.96888 -4.19162,2.32356 -4.19162,0.43267 0,-0.6985 -2.896,-1.27 -6.43558,-1.27 -5.76551,0 -6.3551,0.25357 -5.66262,2.43541 1.06213,3.34645 -7.01086,9.42866 -13.15006,9.90729 -2.5282,0.1971 -7.45424,0.55358 -10.94674,0.79216 -3.49249,0.2386 -9.49324,1.37457 -13.33499,2.52439 -3.84175,1.14984 -9.271,2.10613 -12.065,2.12512 -6.53699,0.0445 -8.76682,1.46233 -7.6808,4.88408 1.92779,6.07394 7.26322,7.97455 27.75463,9.88696 10.6129,0.99046 19.86766,1.99446 20.56616,2.2311 0.6985,0.23665 2.2517,-0.0743 3.45155,-0.69088 1.52354,-0.783 4.20494,-0.11189 8.88999,2.22514 3.94074,1.9657 8.53322,3.26039 11.13135,3.13808 4.39866,-0.20706 13.99211,3.25288 13.99211,5.04636 0,2.06916 -34.95508,4.77802 -52.85091,4.09568 -4.43927,-0.16925 -5.44372,0.27881 -6.67291,2.97655 -0.79584,1.74669 -1.91096,2.889032 -2.47806,2.538542 -0.56709,-0.350482 -3.27928,0.16372 -6.02709,1.14265 -2.74781,0.97894 -7.96616,1.80497 -11.59632,1.83563 -4.51841,0.0382 -8.48381,1.05721 -12.57165,3.23074 -7.95994,4.23234 -14.74257,4.22525 -18.081,-0.0189 -2.64638,-3.36433 -4.11803,-2.73689 -12.68527,5.40832 -0.92301,0.87756 -2.35176,1.61351 -3.175,1.63545 -6.24582,0.16655 -7.01691,10.1505 -0.90624,11.73393 2.41816,0.62662 5.84629,2.16492 7.61806,3.41846 1.77177,1.25354 6.6504,3.27713 10.8414,4.49686 4.191,1.21975 8.763,2.7161 10.16,3.32524 2.21103,0.9641 20.01175,1.34909 44.15034,0.95487 6.82756,-0.11151 7.59785,0.18341 11.44289,4.3812 2.26739,2.47544 5.75837,5.12272 7.7577,5.88287 3.63516,1.38208 3.63516,1.38208 0.30461,4.46874 -1.83179,1.69766 -3.33055,4.29885 -3.33055,5.78042 0,1.88018 -0.67124,2.56582 -2.2225,2.27016 -2.67284,-0.50943 -3.64424,-5.53943 -1.55576,-8.0559 2.68736,-3.23807 -4.34422,-5.40778 -8.86705,-2.73608 -1.99188,1.17668 -5.39078,2.13938 -7.55313,2.13938 -2.16236,0 -3.93155,0.5514 -3.93155,1.22532 0,1.87509 4.47528,3.85468 8.71433,3.85468 4.85915,0 10.11218,5.8679 7.52258,8.40308 -2.7091,2.65218 -14.41154,2.87415 -13.98073,0.26519 0.8742,-5.29414 -5.63681,-4.20088 -8.86462,1.48847 -2.37848,4.19232 -5.33868,4.93306 -7.10446,1.77778 -1.54231,-2.75596 -5.96695,-1.74648 -5.96982,1.36203 -0.003,3.30494 -6.59458,4.65034 -12.2629,2.50298 -14.08759,-5.33687 -15.4692,-5.67492 -19.84123,-4.85473 -4.86984,0.91359 -10.44315,5.86671 -10.44315,9.28107 0,1.3208 -2.35077,2.9844 -6.46767,4.57705 -6.71083,2.59615 -9.03057,4.53267 -6.45718,5.39047 2.07212,0.6907 1.88626,5.36661 -0.21331,5.36661 -3.05911,0 -6.23903,4.61804 -4.95567,7.19685 0.63724,1.28049 1.01904,6.75728 0.84845,12.17065 -0.17059,5.41338 0.19615,9.8425 0.81499,9.8425 0.61884,0 2.01177,1.7145 3.09539,3.81 1.72294,3.3318 2.65157,3.81 7.39861,3.81 2.98562,0 5.97131,0.53056 6.63489,1.17901 0.66357,0.64845 9.49325,1.8724 19.6215,2.71988 10.12825,0.84748 18.95792,2.03144 19.6215,2.631 0.66357,0.59956 4.37832,1.15773 8.255,1.24037 13.32166,0.28399 15.83325,1.59074 6.4135,3.33685 -4.54025,0.8416 -9.46955,1.21055 -10.954,0.81987 -1.48825,-0.39168 -4.90652,0.39611 -7.62,1.75614 -6.66737,3.34175 -12.11027,4.55128 -13.12108,2.91575 -0.93509,-1.51301 -8.46811,0.76442 -11.25814,3.40363 -0.92301,0.87313 -2.49464,1.5875 -3.4925,1.5875 -0.99785,0 -1.81428,0.5715 -1.81428,1.27 0,0.6985 1.13626,1.27 2.52503,1.27 1.84929,0 2.44406,0.75078 2.2225,2.80545 -0.19473,1.80576 0.48791,2.97002 1.91571,3.2673 1.22003,0.25403 3.57884,1.67377 5.24179,3.15501 1.90476,1.69661 5.02519,2.79605 8.4329,2.9712 2.97514,0.15291 5.75982,0.78466 6.1882,1.40386 0.99958,1.44488 10.29096,4.16382 16.98705,4.97093 2.8565,0.34431 6.29798,1.21705 7.64774,1.93941 2.14352,1.14718 21.96725,1.4509 48.23067,0.73894 8.18354,-0.22185 15.30279,4.06867 11.0984,6.68862 -5.8002,3.61436 -18.50486,4.12169 -22.58674,0.90195 -1.11312,-0.87803 -3.77616,-0.77268 -8.3786,0.33143 -5.98479,1.43574 -7.12593,1.37018 -10.10282,-0.58036 -3.14409,-2.06008 -3.40806,-2.06313 -4.18125,-0.0483 -0.64747,1.68729 -1.76548,1.97426 -5.18921,1.33197 -2.68396,-0.50351 -5.06842,-0.23399 -6.19396,0.70013 -1.25144,1.0386 -4.40367,1.25513 -9.96997,0.68485 -7.45064,-0.76335 -8.20926,-0.61552 -8.95869,1.74573 -1.39698,4.4015 -7.78646,6.8014 -15.15327,5.69161 -16.51303,-2.48763 -21.1065,-0.99587 -19.28468,6.26283 1.00688,4.01174 13.96945,10.91469 20.49912,10.91639 2.1147,5.5e-4 5.14613,0.91242 6.73651,2.02636 1.83585,1.28588 4.07232,1.72902 6.12546,1.21371 1.77862,-0.4464 5.1611,-0.1756 7.51663,0.60179 2.35553,0.77741 7.58436,1.54145 11.61963,1.69788 4.60934,0.17869 8.65528,1.15801 10.88338,2.63432 3.4873,2.31063 3.5889,2.30442 6.08197,-0.37159 3.13995,-3.37034 2.74091,-3.3944 9.51226,0.57388 6.89694,4.04186 7.40585,4.80988 5.35902,8.08738 -1.14873,1.83942 -1.24037,3.19988 -0.3144,4.66785 1.80706,2.86478 3.62479,2.43375 4.44123,-1.05312 0.58705,-2.50726 1.59984,-2.98648 6.98499,-3.30505 8.69516,-0.51436 11.93577,1.86392 9.89755,7.2638 -0.98871,2.61938 -1.02964,3.64301 -0.12332,3.08285 0.74629,-0.46124 5.12727,-1.0116 9.73551,-1.22305 15.12592,-0.69403 24.16349,-1.56039 25.61129,-2.45519 2.40049,-1.48358 7.36479,2.11527 8.20696,5.9496 0.9903,4.50882 5.01092,7.43958 10.20612,7.43958 4.50034,0 8.88123,-3.93281 7.72814,-6.93769 -1.15024,-2.99748 7.37324,-2.53691 11.65653,0.62986 5.72876,4.23545 19.75399,3.94871 23.86792,-0.48798 3.41037,-3.67793 4.53158,-4.22953 3.61139,-1.77669 -3.01052,8.02487 16.89628,15.29131 30.01326,10.95553 z m -121.09548,-25.6683 c -4.16752,-3.29977 -2.40472,-4.3076 7.22692,-4.13174 5.19719,0.0949 9.78299,0.5061 10.1907,0.91379 3.03037,3.03039 -13.74111,6.12897 -17.41762,3.21795 z m 27.94,0.73277 c -4.07756,-0.90584 -2.28919,-2.68186 3.58798,-3.5632 3.42915,-0.51423 6.33421,-0.3476 7.0884,0.40659 0.85721,0.85721 2.69051,0.84437 5.56886,-0.039 4.21946,-1.295 8.20226,0.15079 8.20226,2.97748 0,1.02227 -20.02347,1.20096 -24.4475,0.21816 z m -22.86,-37.23922 c -2.71055,-1.57875 -2.8589,-4.1391 -0.36001,-6.21301 1.52099,-1.2623 2.4311,-0.88469 4.96347,2.05935 4.31851,5.02057 1.4024,7.65175 -4.60346,4.15366 z M 102.9607,171.63285 c 2.48479,-1.6058 5.08,-1.6058 5.08,0 0,0.6985 -1.57163,1.26125 -3.4925,1.25056 -2.72154,-0.0151 -3.07197,-0.29123 -1.5875,-1.25056 z m -210.84836,-2.30759 c -2.51391,-2.03564 -2.65975,-2.66263 -1.23745,-5.32021 1.6123,-3.01261 1.6123,-3.01261 5.24906,1.05763 5.70405,6.38394 2.63518,9.64482 -4.01161,4.26258 z m -8.43831,-18.74108 c -1.08251,-2.82099 3.1361,-6.0138 6.56168,-4.96614 1.397,0.42726 5.11175,1.05971 8.255,1.40544 11.02309,1.21247 12.69995,1.87873 8.255,3.27996 -7.92738,2.49903 -22.15402,2.67213 -23.07168,0.28074 z m 211.03167,-7.8962 c -4.80528,-0.58089 -7.44689,-2.85493 -3.1115,-2.67853 4.46725,0.18174 9.6575,1.77651 8.1787,2.51298 -0.69147,0.34436 -2.9717,0.41886 -5.0672,0.16555 z M -62.77429,92.977458 c -9.26526,-0.65624 -10.12269,-0.94329 -9.73637,-3.25961 0.68742,-4.12149 2.14779,-4.72779 7.51464,-3.11984 3.33054,0.99785 5.81988,1.10425 7.24008,0.30946 1.42993,-0.80023 4.33535,-0.64811 8.5621,0.44829 4.15398,1.07754 7.17354,1.24435 8.60988,0.47565 2.84396,-1.52205 11.46467,0.76328 11.46467,3.03923 0,2.96778 -11.41249,3.6822 -33.655,2.10682 z m 71.11999,0.25385 c -4.28386,-0.61948 -8.83164,-4.07982 -7.33238,-5.57908 1.07743,-1.07743 13.04478,4.02099 13.04639,5.55812 5.5e-4,0.52388 -0.42763,0.86688 -0.95151,0.76222 -0.52387,-0.10465 -2.667,-0.43823 -4.7625,-0.74126 z m 21.2725,0.0479 c -4.74623,-0.93519 -3.20463,-3.10842 1.905,-2.68554 2.61938,0.2168 4.64981,0.96567 4.51208,1.66417 -0.29141,1.47795 -2.39814,1.81326 -6.41708,1.02137 z m -43.92082,-1.23304 c -3.21878,-3.21876 4.64507,-4.20587 8.75309,-1.09871 2.29869,1.73864 2.11491,1.86733 -2.72059,1.905 -2.85222,0.0222 -5.56685,-0.34062 -6.0325,-0.80629 z m 99.48332,-0.42329 c 0,-0.6985 1.143,-1.27 2.54,-1.27 1.397,0 2.54,0.5715 2.54,1.27 0,0.6985 -1.143,1.27 -2.54,1.27 -1.397,0 -2.54,-0.5715 -2.54,-1.27 z"
+         id="path12880"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#7c8383;stroke-width:1.26999998"
+         d="m -18.32429,446.55289 c -0.6985,-0.26926 -8.3998,-1.39439 -17.11399,-2.50028 -28.32573,-3.59473 -90.23846,-23.98818 -94.68926,-31.18974 -0.50431,-0.81598 -3.84413,-2.78021 -7.42183,-4.36497 -13.11492,-5.80931 -34.27248,-21.53639 -50.41498,-37.47504 -5.30575,-5.23875 -12.01497,-11.16609 -14.90937,-13.17186 -8.35234,-5.78801 -15.62897,-15.23647 -37.78164,-49.05815 -9.62038,-14.68795 -15.74117,-29.61071 -15.77884,-38.46952 -0.008,-1.94948 -1.77759,-6.93107 -3.93176,-11.07019 -2.29149,-4.40295 -4.44617,-11.10031 -5.19274,-16.14047 -0.70183,-4.73815 -2.34177,-13.18682 -3.64433,-18.77482 -4.66326,-20.00553 -4.18951,-54.37101 0.90166,-65.405 4.6637,-10.10756 5.91102,-15.59043 6.78958,-29.845 1.04181,-16.90329 4.33394,-26.56682 14.43533,-42.372602 1.94823,-3.04843 4.286,-7.65063 5.19503,-10.22712 5.44228,-15.42506 26.26682,-43.826962 41.49267,-56.59049 1.64696,-1.380615 8.42373,-7.127585 15.05948,-12.771044 6.63575,-5.643459 17.06365,-13.566377 23.17312,-17.606486 6.10947,-4.040107 11.43906,-7.881112 11.84353,-8.535566 0.40448,-0.654454 5.1889,-3.42909 10.63206,-6.165857 5.44317,-2.736768 12.40082,-6.633725 15.46147,-8.659904 10.33169,-6.839694 38.99021,-19.006599 54.45981,-23.120794 3.14325,-0.835959 10.13805,-2.898185 15.54401,-4.582727 18.4222,-5.740513 56.54074,-8.424837 79.07098,-5.568216 20.23955,2.566183 42.48329,6.140779 46.50435,7.473307 2.36261,0.782937 9.43915,3.08065 15.72565,5.106028 12.15104,3.914811 18.93466,6.779309 37.76305,15.946083 14.61041,7.113205 24.43105,12.567617 29.47827,16.372313 2.13327,1.608102 7.94263,5.28411 12.90971,8.168908 9.29849,5.40042 12.9282,8.129162 30.33896,22.808233 23.69829,19.980106 48.53615,51.516362 54.07606,68.659562 0.83115,2.57201 3.67486,8.39112 6.31936,12.93137 7.34648,12.612972 12.62574,28.960622 12.4215,38.464162 -0.19002,8.84145 3.57577,25.8974 7.21009,32.65584 7.80794,14.5198 1.41847,72.93981 -12.18908,111.44654 -7.42375,21.00782 -23.50368,51.03384 -33.39932,62.36644 -2.21977,2.54211 -5.67874,7.06653 -7.68658,10.05427 -7.11689,10.59014 -43.2131,43.04109 -56.59703,50.88146 -3.14325,1.84132 -8.48705,5.21164 -11.87512,7.48959 -3.38807,2.27793 -6.67869,4.14171 -7.31251,4.14171 -0.6338,0 -6.1485,2.83709 -12.25487,6.30465 -20.29127,11.52257 -58.71063,24.35866 -89.84249,30.01681 -10.41071,1.89213 -61.19414,3.75707 -64.76999,2.37857 z m 8.001,-79.59403 c 2.95047,-2.95048 1.80958,-6.096 -2.21105,-6.096 -2.05428,0 -4.08826,-0.5715 -4.51995,-1.27 -0.4317,-0.6985 -2.00848,-1.27 -3.50396,-1.27 -1.78717,0 -2.28983,-0.42921 -1.46668,-1.25236 0.82315,-0.82315 3.75301,-0.541 8.54881,0.82329 8.51719,2.42293 11.9243,0.41494 9.61262,-5.66523 -1.32888,-3.49522 -0.4961,-5.50169 1.90161,-4.58162 0.97274,0.37328 1.44086,1.87343 1.11559,3.57503 -0.36151,1.89103 0.15618,3.20685 1.45826,3.70651 2.69248,1.03319 5.82874,-0.26875 5.82874,-2.41967 0,-1.34205 2.36034,-1.6794 10.4775,-1.49748 11.22591,0.25157 23.05481,-1.58278 25.41292,-3.94089 2.07916,-2.07917 2.94222,-1.82185 5.14007,1.53248 1.86079,2.83993 1.81923,2.95457 -0.88368,2.43788 -2.96615,-0.56701 -6.60593,2.50249 -5.35929,4.5196 0.39391,0.63735 3.08276,1.53725 5.97525,1.99978 5.01811,0.80243 5.22479,1.01238 4.51079,4.58237 -0.74827,3.74139 -0.74827,3.74139 5.24658,2.97735 6.81771,-0.86893 9.41853,-2.74731 9.70723,-7.01082 0.21014,-3.10297 0.44118,-3.16985 6.23287,-1.80408 3.00464,0.70855 3.52383,0.47451 3.16052,-1.42466 -0.31976,-1.67149 -2.01217,-2.4543 -6.39937,-2.95994 -7.06257,-0.814 -6.78436,-0.57344 -7.72756,-6.68141 -0.59065,-3.82495 -0.29798,-4.99203 1.40775,-5.61367 1.18438,-0.43165 3.51637,-1.83054 5.18219,-3.10864 4.3435,-3.33256 6.14834,-2.95076 5.54314,1.17261 -0.95802,6.52747 4.45579,9.84622 15.98137,9.79686 15.65574,-0.0671 24.2591,-5.93079 22.49542,-15.33203 -0.70539,-3.76006 -0.4767,-4.31126 1.78875,-4.31126 1.89065,0 2.59755,0.82429 2.59755,3.02893 0,6.46801 9.47435,9.06031 16.7968,4.59582 2.287,-1.3944 5.04909,-2.5374 6.13796,-2.54 4.76613,-0.0114 6.95241,-3.22709 4.61719,-6.7911 -3.20434,-4.89043 -2.73077,-5.68545 3.88054,-6.51444 3.31788,-0.41603 10.3875,-1.8798 15.71027,-3.25281 5.32277,-1.37303 11.3521,-2.4964 13.3985,-2.4964 2.0464,0 4.26366,-0.51297 4.92723,-1.13992 0.66358,-0.62696 3.4925,-1.40712 6.2865,-1.73369 7.48668,-0.87507 13.32602,-3.58889 17.70457,-8.22818 2.137,-2.26423 6.45718,-5.4503 9.60043,-7.08017 9.52144,-4.93713 6.84649,-12.12098 -6.26951,-16.83743 -7.67592,-2.76022 -7.66765,-4.67271 0.0251,-5.82375 15.78045,-2.36116 25.36031,-11.76382 16.95941,-16.64571 -0.9217,-0.53563 -2.05201,-3.48247 -2.5118,-6.54856 -0.88651,-5.91171 -8.1928,-9.73632 -14.67149,-7.68007 -2.79928,0.88846 -7.50176,-0.32922 -7.50176,-1.94253 0,-0.60435 1.17574,-1.472 2.61275,-1.92809 1.43701,-0.45609 3.14126,-2.21935 3.78722,-3.91835 1.54105,-4.05326 7.57886,-6.52205 9.13781,-3.73634 1.54385,2.75869 2.5853,2.48859 7.1223,-1.84722 3.0731,-2.93681 5.15369,-3.81 9.07836,-3.81 2.80035,0 5.16299,-0.5715 5.25031,-1.27 0.0874,-0.6985 0.23018,-1.79387 0.3175,-2.43417 0.0874,-0.64028 0.58737,-1.59278 1.11125,-2.11666 1.57688,-1.57688 1.07579,-5.19583 -0.9525,-6.87917 -3.97753,-3.30106 -0.64548,-8.76419 5.3975,-8.84961 4.71952,-0.0667 -1.50194,-7.53271 -6.35,-7.62026 -2.85896,-0.0516 -2.94814,-4.58782 -0.12129,-6.16981 2.53256,-1.41728 1.48414,-5.04473 -1.7549,-6.07179 -1.23821,-0.39262 -4.25155,-1.52307 -6.6963,-2.51211 -2.44475,-0.98904 -8.54276,-2.36081 -13.55113,-3.04837 -11.17814,-1.53458 -15.41127,-3.35958 -15.91084,-6.85955 -0.36429,-2.55222 -1.12858,-2.69006 -15.62303,-2.81779 -8.382,-0.0739 -20.38349,-0.83859 -26.66999,-1.6994 -6.2865,-0.8608 -15.4305,-1.73044 -20.32,-1.93252 -16.65686,-0.6884 -31.74688,-2.2483 -33.22861,-3.43494 -0.81325,-0.65127 -2.81349,-1.0232 -4.445,-0.82651 -9.93078,1.19721 -26.96955,-3.2548 -25.18911,-6.58158 0.69223,-1.29347 0.94599,-3.16643 0.5639,-4.16215 -0.79339,-2.06757 15.96376,-1.73769 22.80083,0.44886 8.6346,2.7614 14.36052,0.59764 10.29793,-3.89148 -5.70854,-6.30788 -0.16879,-9.55906 18.49511,-10.85445 7.84816,-0.54472 15.72211,-1.88645 19.32446,-3.29291 3.34373,-1.3055 8.81214,-2.37363 12.152,-2.37363 7.30702,0 16.91703,-2.66283 28.30499,-7.84302 8.4365,-3.83763 8.4365,-3.83763 15.22305,0.7327 7.4029,4.98541 9.7594,5.34606 19.75166,3.02295 8.62638,-2.00556 9.82197,-8.67306 2.01711,-11.24889 -2.81684,-0.92964 -4.09476,-2.03456 -3.66085,-3.16529 1.01735,-2.65116 -1.52444,-3.57248 -11.90202,-4.31416 -9.50452,-0.67929 -9.50452,-0.67929 -6.44242,-3.52338 4.19626,-3.897492 -0.58512,-6.115332 -15.67436,-7.270532 -2.02474,-0.15501 -4.39614,-1.2594 -5.2698,-2.45419 -1.00809,-1.37866 -2.91117,-2.03149 -5.20868,-1.78677 -2.90206,0.30909 -3.6202,-0.11824 -3.6202,-2.15442 0,-2.08298 1.07582,-2.6637 5.97913,-3.22745 6.77895,-0.77942 8.27467,-1.71793 6.44555,-4.04435 -3.7106,-4.7195 -2.20872,-14.48153 2.0701,-13.45536 0.93374,0.22394 1.99001,-0.39829 2.34727,-1.38271 0.45021,-1.2406 0.0114,-1.54514 -1.42875,-0.99236 -1.26741,0.48634 -2.07831,0.15352 -2.07831,-0.85303 0,-2.21455 -4.208,-4.16337 -12.99238,-6.01703 -6.72175,-1.41841 -7.2203,-1.76307 -6.52983,-4.51418 0.6563,-2.61488 0.29523,-2.97853 -2.95737,-2.97853 -2.03773,0 -4.8143,-0.83907 -6.17018,-1.86461 -1.35588,-1.02554 -3.20275,-1.88279 -4.10416,-1.905 -0.90141,-0.0222 -3.29628,-1.26573 -5.32196,-2.76337 -3.02688,-2.23788 -4.73289,-2.57908 -9.57618,-1.91523 -6.7849,0.92997 -10.76793,-0.67403 -10.76793,-4.33633 0,-3.23885 -3.42621,-4.576132 -6.61776,-2.58298 -2.52618,1.57763 -3.61134,1.14242 -8.39047,-3.36498 -0.92577,-0.873125 -4.36078,-1.5875 -7.63337,-1.5875 -3.2726,0 -7.94664,-0.625098 -10.38678,-1.389106 -2.44014,-0.764008 -12.64532,-1.3451 -22.67816,-1.291317 -13.5061,0.0724 -19.85469,-0.429843 -24.45555,-1.934713 -4.72202,-1.54449 -6.63536,-1.682825 -7.96882,-0.576146 -1.32344,1.098356 -2.59335,1.074287 -5.16604,-0.0979 -2.69491,-1.22788 -3.93821,-1.225172 -5.92068,0.0129 -1.69602,1.059185 -11.44239,1.776353 -30.0684,2.212523 -15.15743,0.354946 -31.62218,1.172596 -36.58831,1.817002 -5.32536,0.691017 -10.64507,0.708262 -12.96806,0.04204 -4.17103,-1.196239 -13.61953,-0.09202 -22.8391,2.669146 -4.57247,1.369408 -5.64374,1.350389 -6.5502,-0.116294 -0.87646,-1.418138 -1.78044,-1.464855 -4.72712,-0.244298 -3.32675,1.377984 -10.59689,2.252318 -25.70574,3.091463 -3.09076,0.17166 -5.85301,1.01244 -6.13833,1.8684 -0.68248,2.04747 -2.93976,2.01963 -2.5296,-0.0312 0.77152,-3.857558 -6.58265,-2.407521 -10.51721,2.07371 -2.46746,2.81027 -4.62912,4.18451 -5.84704,3.71715 -2.41864,-0.92812 -9.35324,6.39128 -9.35324,9.87225 0,1.41391 -1.35411,3.10581 -3.13518,3.91733 -1.72435,0.78566 -2.87445,1.85035 -2.55577,2.36597 0.75678,1.22451 -9.43169,3.77109 -15.08757,3.77109 -6.83898,0 -8.43148,1.06792 -8.43148,5.65404 0,7.26863 2.71365,8.47683 25.5033,11.35492 27.42807,3.46387 28.69603,11.42799 2.08742,13.11122 -7.75211,0.49039 -13.21317,1.53594 -16.14192,3.09047 -2.4344,1.29214 -6.54302,2.34935 -9.13025,2.34935 -3.66317,0 -6.00285,1.124092 -10.57355,5.080002 -7.21617,6.24556 -11.31227,6.61044 -14.34657,1.27803 -2.82372,-4.96235 -3.37921,-4.85537 -9.84887,1.89697 -3.01167,3.14325 -6.22924,5.73296 -7.15016,5.7549 -2.81822,0.0672 -6.43245,6.15991 -5.60642,9.45108 2.70106,10.76188 26.93181,15.30074 77.29236,14.47828 6.82756,-0.11151 7.59785,0.18341 11.44289,4.38121 2.26739,2.47543 5.75837,5.12271 7.7577,5.88286 3.63516,1.38208 3.63516,1.38208 0.30461,4.46874 -1.83179,1.69766 -3.33055,4.29885 -3.33055,5.78042 0,1.88018 -0.67124,2.56582 -2.2225,2.27016 -2.67284,-0.50943 -3.64424,-5.53943 -1.55576,-8.0559 2.68736,-3.23807 -4.34422,-5.40777 -8.86705,-2.73608 -1.99188,1.17663 -5.39078,2.13933 -7.55313,2.13933 -2.16236,0 -3.93155,0.5514 -3.93155,1.22532 0,1.81933 4.42955,3.85468 8.38901,3.85468 3.76758,0 8.12098,3.16915 8.12098,5.91184 0,2.05421 -13.35853,2.45499 -14.605,0.43816 -1.26503,-2.04687 -4.30162,-1.46648 -6.64467,1.27 -1.5658,1.82873 -3.78506,2.5526 -7.92507,2.58501 -3.16264,0.0247 -7.179,0.86175 -8.92525,1.85999 -4.14619,2.37018 -9.95186,2.35757 -19.21102,-0.0417 -7.63718,-1.97901 -16.41594,-1.02729 -18.27531,1.98122 -0.41996,0.67946 -2.21505,1.23543 -3.98909,1.23543 -1.94786,0 -4.63831,1.51241 -6.79223,3.81819 -1.96169,2.1 -5.33424,4.17169 -7.49453,4.60375 -4.43966,0.88794 -5.07206,3.44869 -1.75646,7.11239 2.07055,2.28793 1.98156,2.54784 -1.91692,5.59887 -3.89678,3.04969 -4.00266,3.35838 -2.26047,6.59067 3.25412,6.03734 4.5424,11.85653 3.73823,16.88557 -0.5399,3.37633 -0.31699,4.92056 0.71029,4.92056 0.82341,0 2.35797,2.06031 3.41012,4.57847 1.73744,4.15827 2.28512,4.5211 5.9677,3.95355 2.27344,-0.35037 4.89157,0.0663 5.95969,0.9485 1.04775,0.86536 6.70122,2.22704 12.56328,3.02596 5.86205,0.79892 11.3703,2.04351 12.24056,2.76576 0.91684,0.7609 3.7784,0.962 6.80422,0.47815 3.52273,-0.56331 5.22194,-0.36895 5.22194,0.59729 0,0.78779 -0.71438,1.50043 -1.5875,1.58368 -0.87313,0.0833 -4.73075,0.37493 -8.5725,0.64818 -13.05846,0.92879 -15.87157,1.25894 -19.685,2.3103 -2.0955,0.57771 -4.3815,1.48834 -5.08,2.02359 -0.6985,0.53526 -2.69875,1.17083 -4.445,1.41238 -2.30521,0.31887 -3.26693,1.30899 -3.51057,3.61419 -0.57902,5.47835 25.53646,18.2124 32.14775,15.67541 1.03134,-0.39577 2.58226,-0.15312 3.44649,0.53919 1.96935,1.57762 14.92894,5.33474 21.078,6.11072 7.30194,0.92148 9.06832,1.79705 9.06832,4.49506 0,3.48204 -7.8954,6.82128 -11.52965,4.8763 -2.21291,-1.18432 -3.21557,-0.68965 -7.01991,3.46333 -4.43827,4.84499 -4.43827,4.84499 -17.45425,4.45183 -14.49564,-0.43786 -17.53088,1.36945 -15.55061,9.25949 0.91799,3.65753 13.89316,10.81575 19.60838,10.81767 1.63842,5.5e-4 4.66623,1.20246 6.72847,2.6709 2.71462,1.93298 4.87194,2.47049 7.81605,1.94744 2.59855,-0.46165 4.95645,-0.05 6.53176,1.14011 1.40451,1.06115 5.22964,1.86789 8.89,1.87496 7.32998,0.0141 18.3534,4.53536 21.29399,8.73363 1.02773,1.46729 2.2058,2.3306 2.61794,1.91847 0.41214,-0.41214 3.21837,0.93988 6.23608,3.00448 13.01935,8.90734 12.3619,8.80151 49.14074,7.91035 9.88174,-0.23945 11.00475,0.003 15.875,3.41655 10.90024,7.64164 18.43477,8.14518 24.33428,1.6263 2.67749,-2.95859 4.80406,-3.16291 6.88042,-0.66105 1.96535,2.3681 -5.4993,6.40396 -14.28009,7.72073 -5.70088,0.85488 -12.38566,6.0544 -10.37233,8.06773 0.39701,0.39702 1.16508,0.005 1.70681,-0.87189 1.51095,-2.44475 5.1931,-0.37615 4.87285,2.73754 -0.37268,3.62361 4.71,4.4237 8.17601,1.287 2.44824,-2.21563 2.60316,-2.20333 4.65776,0.36959 3.46618,4.34058 10.3803,5.01271 10.3803,1.00909 0,-3.15517 0.0428,-3.14275 4.48732,1.30175 4.65969,4.65968 7.89043,5.57158 10.49868,2.96333 z m -104.2035,-34.10508 c -5.82946,-3.48098 0.82761,-8.56915 7.95889,-6.08317 2.96726,1.0344 3.83202,2.03057 3.54001,4.07801 -0.44197,3.09887 -7.58218,4.34397 -11.4989,2.00516 z m -18.76957,-12.0406 c -2.2054,-2.65734 -0.3662,-4.0779 2.09418,-1.6175 1.18769,1.18769 1.49102,2.30913 0.74867,2.76793 -0.68639,0.4242 -1.96567,-0.0935 -2.84285,-1.15043 z m 279.92604,-8.83362 c 1.31669,-0.99687 5.03144,-2.15008 8.255,-2.56268 6.03856,-0.77289 14.51944,-2.53466 18.715,-3.88775 1.77123,-0.57123 1.50382,0.0465 -1.03784,2.39715 -3.78349,3.49926 -13.13046,5.9686 -22.35616,5.90619 -5.44647,-0.0368 -5.76005,-0.19934 -3.576,-1.85291 z M -116.32597,150.58418 c -1.08251,-2.82099 3.1361,-6.0138 6.56168,-4.96614 1.397,0.42726 5.11175,1.05971 8.255,1.40544 11.02309,1.21247 12.69995,1.87873 8.255,3.27996 -7.92738,2.49903 -22.15402,2.67213 -23.07168,0.28074 z m 211.03167,-7.8962 c -4.80528,-0.58089 -7.44689,-2.85493 -3.1115,-2.67853 4.46725,0.18174 9.6575,1.77651 8.1787,2.51298 -0.69147,0.34436 -2.9717,0.41886 -5.0672,0.16555 z m 65.40499,-51.084572 c -1.65313,-1.08522 -1.66217,-1.40676 -0.0684,-2.43194 2.16413,-1.3921 6.88208,0.67372 5.74404,2.5151 -0.95371,1.54316 -3.24943,1.50952 -5.67561,-0.0832 z m -285.59519,-1.12802 c -2.27018,-1.4379 -0.70956,-3.93254 2.46017,-3.93254 1.61837,0 2.46504,0.87241 2.46504,2.54 0,2.67853 -2.00145,3.24442 -4.92521,1.39254 z m 9.00857,-0.42816 c -2.19159,-2.6407 0.31388,-5.79004 3.18299,-4.00096 1.24226,0.77462 2.25865,2.19555 2.25865,3.1576 0,2.36217 -3.70464,2.93632 -5.44164,0.84336 z M 41.2387,35.234848 c -0.46489,-1.211503 -0.21662,-2.831383 0.55173,-3.599733 1.016,-1.016 1.62753,-0.796254 2.24227,0.805733 0.4649,1.211502 0.21663,2.831382 -0.55172,3.59973 -1.016,1.016 -1.62754,0.79626 -2.24228,-0.80573 z M 8.9807,367.22956 c 2.72977,-1.74017 -0.0199,-5.7317 -3.94841,-5.7317 -2.14663,0 -3.15139,0.80598 -3.42834,2.75008 -0.35269,2.47593 0.50265,3.19536 4.83675,4.06815 0.34925,0.0704 1.49225,-0.4186 2.54,-1.08653 z"
+         id="path12878"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#7b836f;stroke-width:1.26999998"
+         d="m -21.49929,445.80433 c -2.44475,-0.49261 -9.5885,-1.52038 -15.875,-2.28393 -26.96446,-3.2751 -82.86635,-22.41183 -94.79856,-32.45212 -2.10221,-1.76889 -5.2952,-3.58587 -7.09555,-4.03772 -3.94366,-0.9898 -24.08944,-14.28934 -27.65055,-18.25394 -1.39443,-1.55242 -5.67858,-4.98746 -9.52033,-7.63339 -3.84175,-2.64593 -10.12825,-8.33463 -13.97,-12.64154 -3.84175,-4.30693 -8.49358,-8.62691 -10.33741,-9.59997 -4.77654,-2.52078 -14.46463,-12.50777 -19.51425,-20.11633 -2.37175,-3.57364 -5.93095,-8.78353 -7.90934,-11.57753 -1.97839,-2.794 -4.72138,-6.87406 -6.09553,-9.06678 -1.37416,-2.19273 -4.69612,-7.28968 -7.38214,-11.32658 -6.70698,-10.08012 -13.05163,-25.19595 -14.0954,-33.58165 -0.47818,-3.84175 -2.55525,-10.414 -4.61571,-14.605 -2.06045,-4.191 -4.33531,-11.33475 -5.05522,-15.875 -0.71992,-4.54025 -2.28343,-12.54125 -3.47447,-17.78 -3.48098,-15.31098 -3.12552,-57.03594 0.55177,-64.77 4.0455,-8.50846 6.7695,-20.88714 7.26624,-33.02 0.50265,-12.27728 5.16797,-26.14113 12.7619,-37.924362 2.41364,-3.74514 6.18569,-11.26796 8.38235,-16.71738 6.37524,-15.81549 24.83848,-39.01525 45.82252,-57.577709 17.02613,-15.061301 20.13353,-17.455467 34.65045,-26.697263 4.26067,-2.712442 8.32551,-5.688344 9.03295,-6.613116 0.70746,-0.924771 5.57254,-3.781091 10.81128,-6.347377 5.23875,-2.566285 11.811,-6.226881 14.605,-8.134657 2.794,-1.907776 9.93775,-5.587854 15.875,-8.177949 5.93725,-2.590096 12.12114,-5.565781 13.74199,-6.612633 3.95453,-2.554127 11.14731,-5.040052 25.62801,-8.857411 6.63575,-1.749295 15.20825,-4.256887 19.05,-5.572426 6.038,-2.067603 20.47576,-4.518503 28.575,-4.85078 51.56025,-2.1153 93.01167,4.092518 125.88348,18.852479 17.89618,8.035658 41.07213,19.966169 48.00145,24.710169 4.31625,2.955022 10.67763,6.950065 14.13639,8.877873 14.1454,7.884214 26.00399,17.619927 48.19866,39.570373 16.74613,16.561857 27.68799,32.440057 39.37,57.131477 0.6985,1.47636 3.06207,6.04836 5.25236,10.16 7.32692,13.754082 12.11353,29.388582 11.21576,36.634152 -0.51941,4.19217 -0.15043,8.37397 1.06914,12.11654 1.02425,3.14325 2.19798,7.57236 2.60826,9.84246 0.41028,2.27012 1.94106,6.75088 3.40174,9.95726 5.07789,11.14666 4.13992,44.35927 -2.17888,77.15146 -13.25529,68.7901 -62.56037,135.41623 -124.23838,167.88378 -4.8895,2.57385 -12.43919,6.65469 -16.7771,9.06851 -21.10475,11.74373 -62.02992,24.91507 -90.53789,29.13864 -14.04615,2.08101 -57.11795,3.15123 -64.76999,1.60937 z m 11.176,-78.84547 c 0.83819,-0.8382 1.52399,-2.83845 1.52399,-4.445 0,-1.60655 0.65138,-2.921 1.4475,-2.921 2.72992,0 4.70383,-4.16722 3.4883,-7.3643 -1.32888,-3.49522 -0.4961,-5.50169 1.90161,-4.58162 0.97274,0.37328 1.44086,1.87343 1.11559,3.57503 -0.36151,1.89103 0.15618,3.20685 1.45826,3.70651 2.69248,1.03319 5.82874,-0.26875 5.82874,-2.41967 0,-1.34205 2.36034,-1.6794 10.4775,-1.49748 11.22591,0.25157 23.05481,-1.58278 25.41292,-3.94089 2.07916,-2.07917 2.94222,-1.82185 5.14007,1.53248 1.86079,2.83993 1.81923,2.95457 -0.88368,2.43788 -2.96615,-0.56701 -6.60593,2.50249 -5.35929,4.5196 0.39391,0.63735 3.08276,1.53725 5.97525,1.99978 5.01811,0.80243 5.22479,1.01238 4.51079,4.58237 -0.74827,3.74139 -0.74827,3.74139 5.24658,2.97735 6.81771,-0.86893 9.41853,-2.74731 9.70723,-7.01082 0.21014,-3.10297 0.44118,-3.16985 6.23287,-1.80408 3.00464,0.70855 3.52383,0.47451 3.16052,-1.42466 -0.31976,-1.67149 -2.01217,-2.4543 -6.39937,-2.95994 -7.06257,-0.814 -6.78436,-0.57344 -7.72756,-6.68141 -0.59065,-3.82495 -0.29798,-4.99203 1.40775,-5.61367 1.18438,-0.43165 3.51637,-1.83054 5.18219,-3.10864 4.3435,-3.33256 6.14834,-2.95076 5.54314,1.17261 -0.95802,6.52747 4.45579,9.84622 15.98137,9.79686 15.65574,-0.0671 24.2591,-5.93079 22.49542,-15.33203 -0.70539,-3.76006 -0.4767,-4.31126 1.78875,-4.31126 1.89065,0 2.59755,0.82429 2.59755,3.02893 0,6.46801 9.47435,9.06031 16.7968,4.59582 2.287,-1.3944 5.04909,-2.5374 6.13796,-2.54 4.76613,-0.0114 6.95241,-3.22709 4.61719,-6.7911 -3.20434,-4.89043 -2.73077,-5.68545 3.88054,-6.51444 3.31788,-0.41603 10.3875,-1.8798 15.71027,-3.25281 5.32277,-1.37303 11.3521,-2.4964 13.3985,-2.4964 2.0464,0 4.26366,-0.51297 4.92723,-1.13992 0.66358,-0.62696 3.4925,-1.4138 6.2865,-1.74853 6.71993,-0.8051 25.33166,-7.54388 29.64703,-10.73438 1.88215,-1.39153 4.60652,-2.90598 6.05415,-3.36544 9.23208,-2.93015 9.84635,-10.70449 1.00441,-12.7117 -8.13454,-1.84661 -22.09664,-7.67017 -21.66031,-9.03445 0.22331,-0.69818 2.93021,-1.64712 6.01536,-2.10872 15.78045,-2.36116 25.36031,-11.76382 16.95941,-16.64571 -0.9217,-0.53563 -2.05201,-3.48247 -2.5118,-6.54856 -0.88651,-5.91171 -8.1928,-9.73632 -14.67149,-7.68007 -2.79928,0.88846 -7.50176,-0.32922 -7.50176,-1.94253 0,-0.60435 1.17574,-1.472 2.61275,-1.92809 1.43701,-0.45609 3.14126,-2.21935 3.78722,-3.91835 1.54105,-4.05326 7.57886,-6.52205 9.13781,-3.73634 1.47119,2.62887 2.93831,2.4726 5.8194,-0.61989 1.76841,-1.89818 4.05354,-2.5965 8.62197,-2.63485 21.24674,-0.17836 29.59195,-11.05199 17.73136,-23.1036 -6.48991,-6.59444 -6.99273,-7.52682 -6.54814,-12.14255 0.43859,-4.55331 0.12497,-5.1707 -3.51291,-6.91598 -8.84349,-4.24265 -14.57572,-6.02001 -23.26059,-7.2123 -11.17815,-1.53458 -15.41127,-3.35958 -15.91084,-6.85955 -0.3643,-2.55222 -1.12858,-2.69006 -15.62303,-2.81779 -8.382,-0.0739 -20.3835,-0.83859 -26.67,-1.6994 -6.2865,-0.8608 -15.4305,-1.73044 -20.32,-1.93252 -16.65686,-0.6884 -31.74687,-2.2483 -33.2286,-3.43494 -0.81325,-0.65127 -2.8135,-1.0232 -4.445,-0.82651 -9.93078,1.19721 -26.96956,-3.2548 -25.18912,-6.58158 0.69224,-1.29347 0.946,-3.16643 0.56391,-4.16215 -0.7934,-2.06757 15.96376,-1.73769 22.80083,0.44886 8.6346,2.7614 14.36052,0.59764 10.29793,-3.89148 -5.70855,-6.30788 -0.16879,-9.55906 18.49511,-10.85445 7.84816,-0.54472 15.7221,-1.88645 19.32446,-3.29291 3.34373,-1.3055 8.81214,-2.37363 12.15199,-2.37363 7.3031,0 16.91602,-2.66241 28.28539,-7.83399 8.4169,-3.82858 8.4169,-3.82858 15.53865,0.89999 6.3252,4.1997 7.72756,4.63901 12.5381,3.92768 13.57553,-2.00736 16.81312,-2.79489 16.35695,-3.97868 -1.90689,-4.94854 -3.37762,-7.03347 -5.41834,-7.68117 -3.44826,-1.09444 -2.87695,-2.76985 1.78024,-5.22068 5.61669,-2.95578 -0.12167,-4.69107 -15.87537,-4.80077 -11.89794,-0.0828 -12.29444,-0.20284 -8.89162,-2.69104 3.59519,-2.628872 -3.31108,-5.206172 -16.69683,-6.230962 -2.02474,-0.15501 -4.39615,-1.2594 -5.2698,-2.45419 -1.00809,-1.37866 -2.91117,-2.03149 -5.20868,-1.78677 -2.90206,0.30909 -3.6202,-0.11824 -3.6202,-2.15442 0,-1.89817 0.96275,-2.628 3.81,-2.88825 7.45726,-0.68161 9.525,-1.56323 9.525,-4.06119 0,-4.49783 2.81831,-8.92556 5.68124,-8.92556 1.50346,0 4.37614,-1.7145 6.38376,-3.81 2.00762,-2.0955 4.50746,-3.81 5.55521,-3.81 1.30551,0 0.75631,-1.19898 -1.74521,-3.81 -2.95916,-3.0887 -4.69491,-3.81 -9.1684,-3.81 -3.03499,0 -7.56987,-0.85725 -10.07748,-1.905 -2.50763,-1.04775 -6.74179,-1.905 -9.40927,-1.905 -2.66747,0 -5.20315,-0.5715 -5.63485,-1.27 -0.4317,-0.6985 -2.35644,-1.27 -4.27722,-1.27 -1.92077,0 -4.60166,-0.83907 -5.95754,-1.86461 -1.35588,-1.02554 -3.20275,-1.88279 -4.10416,-1.905 -0.90141,-0.0222 -3.29138,-1.2621 -5.31104,-2.7553 -2.97594,-2.20021 -4.83071,-2.58432 -9.78333,-2.02609 -6.92571,0.78063 -10.5717,-0.61207 -10.5717,-4.03818 0,-3.03023 -3.32935,-5.747997 -7.49769,-6.120417 C 100.95382,31.877938 92.86648,30.853405 91.45004,29.977996 90.52396,29.405648 81.52593,29.030254 71.45444,29.143788 57.65021,29.2994 51.61299,28.849897 46.92861,27.317712 c -4.72202,-1.54449 -6.63536,-1.682825 -7.96882,-0.576146 -1.32344,1.098356 -2.59335,1.074287 -5.16604,-0.0979 -2.69491,-1.22788 -3.93821,-1.225172 -5.92068,0.0129 -1.69602,1.059185 -11.44239,1.776353 -30.0684,2.212523 -15.15743,0.354946 -31.62218,1.172596 -36.58831,1.817002 -5.32536,0.691017 -10.64507,0.708262 -12.96806,0.04204 -2.37619,-0.681485 -6.85525,-0.632887 -11.29064,0.122504 -4.04358,0.688659 -13.9242,1.456587 -21.95695,1.706507 -8.03275,0.24992 -16.22458,1.073035 -18.20408,1.829144 -1.97948,0.756108 -4.03336,1.106331 -4.56417,0.778271 -1.47888,-0.913997 -10.25175,1.805551 -10.25175,3.177981 0,0.66567 -0.5715,1.21031 -1.27,1.21031 -0.6985,0 -1.12712,-0.71438 -0.9525,-1.5875 0.80507,-4.025326 -7.47788,-2.429825 -10.93829,2.10698 -2.2426,2.94022 -4.07634,4.20177 -5.38021,3.70143 -2.44251,-0.93727 -9.39899,6.31221 -9.39899,9.79486 0,3.35271 -5.60577,5.8512 -13.97,6.22642 -3.84175,0.17234 -8.41375,0.47316 -10.16,0.6685 -1.74625,0.19533 -4.67997,0.42808 -6.51938,0.51723 -5.15316,0.24974 -6.11602,7.78208 -0.99479,7.78208 1.51934,0 2.43417,0.88063 2.43417,2.34316 0,5.34255 4.09678,6.88239 25.5033,9.5858 27.42807,3.46387 28.69603,11.42799 2.08742,13.11122 -7.75211,0.49039 -13.21317,1.53594 -16.14192,3.09047 -2.4344,1.29214 -6.54302,2.34935 -9.13025,2.34935 -3.66317,0 -6.00285,1.124092 -10.57355,5.080002 -7.21617,6.24556 -11.31227,6.61044 -14.34657,1.27803 -2.13441,-3.75097 -4.5414,-4.86511 -5.86918,-2.71671 -0.37436,0.60573 -4.55146,2.35499 -9.28245,3.88725 -19.50666,6.31777 -26.69078,19.13198 -10.73358,19.14534 2.81806,0.003 8.19925,1.78508 13.56178,4.49283 12.70248,6.41401 23.38407,7.49375 67.01034,6.77378 6.82824,-0.11265 7.59747,0.18173 11.44289,4.37995 2.26739,2.47543 5.75837,5.12271 7.7577,5.88286 3.63516,1.38208 3.63516,1.38208 0.30461,4.46874 -1.83179,1.69766 -3.33055,4.29885 -3.33055,5.78042 0,1.88018 -0.67124,2.56582 -2.2225,2.27016 -2.67284,-0.50943 -3.64424,-5.53943 -1.55576,-8.0559 2.68736,-3.23807 -4.34422,-5.40777 -8.86705,-2.73608 -1.99188,1.17663 -5.39078,2.13933 -7.55313,2.13933 -2.16236,0 -3.93155,0.5514 -3.93155,1.22532 0,1.81933 4.42955,3.85468 8.38901,3.85468 3.76758,0 8.12098,3.16915 8.12098,5.91184 0,2.05421 -13.35853,2.45499 -14.60499,0.43816 -1.26504,-2.04687 -4.30163,-1.46648 -6.64468,1.27 -1.5658,1.82873 -3.78506,2.5526 -7.92507,2.58501 -3.16264,0.0247 -7.179,0.86175 -8.92525,1.85999 -4.14619,2.37018 -9.95186,2.35757 -19.21102,-0.0417 -7.63718,-1.97901 -16.41594,-1.02729 -18.27531,1.98122 -0.41996,0.67946 -2.21505,1.23543 -3.98909,1.23543 -1.94786,0 -4.63831,1.51241 -6.79223,3.81819 -1.96169,2.1 -5.33424,4.17169 -7.49453,4.60375 -4.15165,0.83033 -4.84171,2.73743 -2.54399,7.03075 1.14424,2.13803 0.81683,3.17751 -1.89101,6.0039 -3.08127,3.21615 -3.15581,3.58813 -1.26119,6.29308 2.14188,3.05797 -2.09619,11.66483 -6.59365,13.39066 -6.49897,2.4939 4.84446,15.21582 14.73722,16.52814 3.52437,0.46752 6.99675,0.48614 7.7164,0.0414 0.71965,-0.44477 2.27257,0.0199 3.45093,1.0327 1.17836,1.01275 6.9387,2.49502 12.80075,3.29394 5.86205,0.79892 11.3703,2.0435 12.24056,2.76575 0.91684,0.76091 3.7784,0.962 6.80422,0.47816 3.52274,-0.56334 5.22194,-0.36898 5.22194,0.59726 0,0.78779 -0.71438,1.50043 -1.5875,1.58368 -0.87313,0.0833 -4.73075,0.37493 -8.5725,0.64818 -13.05846,0.92879 -15.87157,1.25894 -19.685,2.3103 -2.0955,0.57771 -4.3815,1.48834 -5.08,2.02359 -0.6985,0.53526 -2.69875,1.17083 -4.445,1.41238 -2.30521,0.31887 -3.26693,1.30899 -3.51057,3.61419 -0.57902,5.47835 25.53646,18.2124 32.14775,15.67541 1.03134,-0.39577 2.58226,-0.15312 3.44649,0.53919 1.96935,1.57762 14.92894,5.33474 21.078,6.11072 7.30194,0.92148 9.06833,1.79705 9.06833,4.49506 0,3.48204 -7.89541,6.82128 -11.52966,4.8763 -2.21183,-1.18374 -3.21248,-0.69295 -6.98544,3.42615 -4.40379,4.80782 -4.40379,4.80782 -22.28763,4.43134 -19.04562,-0.40094 -23.96727,1.13103 -23.96727,7.46038 0,3.92968 13.16887,12.1205 17.0762,10.62112 1.09072,-0.41855 4.56404,0.45217 7.71849,1.93493 3.15444,1.48276 7.38166,2.57049 9.39382,2.41718 2.01216,-0.15333 4.57574,0.4825 5.69686,1.41294 1.31561,1.09187 3.72466,1.43154 6.79401,0.95794 3.16824,-0.48884 5.57849,-0.11874 7.22085,1.10879 1.38756,1.03708 5.27382,1.84798 8.89,1.85496 7.32998,0.0141 18.3534,4.53536 21.29399,8.73363 1.02773,1.46729 2.2058,2.3306 2.61794,1.91847 0.41214,-0.41214 3.21837,0.93988 6.23608,3.00448 13.01935,8.90734 12.3619,8.80151 49.14074,7.91035 9.88174,-0.23945 11.00475,0.003 15.875,3.41655 10.90024,7.64164 18.43477,8.14518 24.33428,1.6263 2.67749,-2.95859 4.80406,-3.16291 6.88042,-0.66105 1.96535,2.3681 -5.4993,6.40396 -14.28009,7.72073 -5.97905,0.8966 -11.95827,5.74499 -10.33135,8.37741 1.17474,1.90076 3.39871,1.23593 2.62348,-0.78427 -0.40206,-1.04775 -0.24742,-1.905 0.34362,-1.905 0.59105,0 1.78609,0.85725 2.65565,1.905 1.26135,1.51983 1.26135,1.905 0,1.905 -1.26135,0 -1.26135,0.38516 0,1.905 2.06227,2.48489 6.20734,2.42777 9.02747,-0.12446 2.52268,-2.28299 6.12231,-1.18237 3.78318,1.15675 -2.61828,2.61828 -1.36076,3.41201 4.96935,3.13658 5.71249,-0.24855 6.35,-0.57156 6.35,-3.21744 0,-2.78313 0.24108,-2.70007 4.48732,1.54619 4.65969,4.65968 7.89043,5.57158 10.49868,2.96333 z m -104.2035,-34.10508 c -5.82946,-3.48098 0.82761,-8.56915 7.95889,-6.08317 2.96726,1.0344 3.83202,2.03057 3.54001,4.07801 -0.44197,3.09887 -7.58218,4.34397 -11.4989,2.00516 z m -18.76957,-12.0406 c -2.2054,-2.65734 -0.3662,-4.0779 2.09418,-1.6175 1.18769,1.18769 1.49102,2.30913 0.74867,2.76793 -0.68639,0.4242 -1.96567,-0.0935 -2.84285,-1.15043 z m 279.92604,-8.83362 c 1.31669,-0.99687 5.03144,-2.15008 8.255,-2.56268 6.03856,-0.77289 14.51944,-2.53466 18.715,-3.88775 1.77123,-0.57123 1.50382,0.0465 -1.03784,2.39715 -3.78349,3.49926 -13.13046,5.9686 -22.35616,5.90619 -5.44647,-0.0368 -5.76005,-0.19934 -3.576,-1.85291 z M -116.32597,150.58418 c -1.08251,-2.82099 3.1361,-6.0138 6.56168,-4.96614 1.397,0.42726 5.11175,1.05971 8.255,1.40544 11.02309,1.21247 12.69995,1.87873 8.255,3.27996 -7.92738,2.49903 -22.15402,2.67213 -23.07168,0.28074 z m 211.03167,-7.8962 c -4.80528,-0.58089 -7.44689,-2.85493 -3.1115,-2.67853 4.46725,0.18174 9.6575,1.77651 8.1787,2.51298 -0.69147,0.34436 -2.9717,0.41886 -5.0672,0.16555 z m 65.40499,-51.084572 c -1.65313,-1.08522 -1.66217,-1.40676 -0.0684,-2.43194 2.16413,-1.3921 6.88208,0.67372 5.74404,2.5151 -0.95371,1.54316 -3.24943,1.50952 -5.67561,-0.0832 z m -285.59519,-1.12802 c -2.27018,-1.4379 -0.70956,-3.93254 2.46017,-3.93254 1.61837,0 2.46504,0.87241 2.46504,2.54 0,2.67853 -2.00145,3.24442 -4.92521,1.39254 z m 9.00857,-0.42816 c -2.19159,-2.6407 0.31388,-5.79004 3.18299,-4.00096 1.24226,0.77462 2.25865,2.19555 2.25865,3.1576 0,2.36217 -3.70464,2.93632 -5.44164,0.84336 z M 41.2387,35.234848 c -0.46489,-1.211503 -0.21662,-2.831383 0.55173,-3.599733 1.016,-1.016 1.62753,-0.796254 2.24227,0.805733 0.4649,1.211502 0.21663,2.831382 -0.55172,3.59973 -1.016,1.016 -1.62754,0.79626 -2.24228,-0.80573 z M 8.9807,367.22956 c 2.72977,-1.74017 -0.0199,-5.7317 -3.94841,-5.7317 -2.14663,0 -3.15139,0.80598 -3.42834,2.75008 -0.35269,2.47593 0.50265,3.19536 4.83675,4.06815 0.34925,0.0704 1.49225,-0.4186 2.54,-1.08653 z m 139.69999,-5.7317 c 0,-2.13748 -0.2326,-2.22338 -3.07112,-1.13413 -2.83769,1.08893 -2.49687,3.03913 0.53112,3.03913 1.41111,0 2.54,-0.84667 2.54,-1.905 z m -307.76648,-24.21537 c 0.95247,-2.48209 -0.47758,-3.9327 -4.97099,-5.04245 -2.89128,-0.71406 -3.4925,-0.44531 -3.4925,1.56113 0,4.88416 6.84477,7.69963 8.46349,3.48132 z"
+         id="path12876"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#8f7552;stroke-width:1.26999998"
+         d="m -21.49929,445.80433 c -2.44475,-0.49261 -9.5885,-1.52038 -15.875,-2.28393 -24.36978,-2.95995 -92.075,-25.55783 -92.075,-30.73176 0,-1.15763 -9.22591,-6.20578 -11.34157,-6.20578 -2.23074,0 -20.77152,-12.94892 -27.6349,-19.30027 -3.62531,-3.35486 -7.24482,-6.09973 -8.04335,-6.09973 -0.79854,0 -6.45479,-5.00063 -12.56946,-11.1125 -6.11467,-6.11188 -13.16848,-12.57943 -15.67515,-14.37234 -15.75442,-11.26846 -48.91565,-64.90744 -50.98243,-82.46517 -0.45223,-3.84175 -2.56105,-10.46695 -4.68627,-14.72267 -2.40182,-4.80963 -4.34552,-11.29757 -5.1364,-17.145 -0.6998,-5.17404 -2.23156,-12.83633 -3.40391,-17.02733 -3.46336,-12.38105 -3.08983,-56.37353 0.54456,-64.135 1.47185,-3.14325 3.30922,-8.001 4.08304,-10.795 0.77381,-2.794 1.81533,-6.50875 2.31447,-8.255 0.49914,-1.74625 0.83727,-6.88975 0.7514,-11.43 -0.23862,-12.61674 5.35568,-29.15121 14.68732,-43.409692 1.97446,-3.01692 5.13969,-9.5373 7.03384,-14.48971 2.06468,-5.39828 6.53271,-13.0275 11.15659,-19.05 4.24197,-5.52508 9.92399,-12.99675 12.6267,-16.6037 6.49968,-8.67428 35.96174,-34.28186 50.18954,-43.623317 6.22654,-4.088123 12.50763,-8.596282 13.95796,-10.018134 1.45034,-1.421851 6.87959,-4.678458 12.065,-7.236904 5.18541,-2.558447 11.71402,-6.212631 14.50802,-8.120407 2.794,-1.907776 9.93775,-5.587854 15.875,-8.177949 5.93725,-2.590096 12.12114,-5.565781 13.74199,-6.612633 3.95453,-2.554127 11.14731,-5.040052 25.62801,-8.85741 6.63575,-1.749296 15.20825,-4.256888 19.05,-5.572427 6.038,-2.067603 20.47576,-4.518503 28.575,-4.85078 51.56025,-2.1153 93.01167,4.092518 125.88348,18.852479 17.80631,7.995306 41.05944,19.956527 47.94011,24.660003 4.28251,2.92743 11.78689,7.658684 16.67639,10.513897 19.15133,11.183379 48.74829,38.558344 64.23405,59.411682 8.88265,11.96151 18.95095,29.11255 18.95095,32.2824 0,0.78703 2.53652,5.84695 5.6367,11.24427 8.36309,14.559872 14.06756,31.947952 12.50312,38.111362 -0.9518,3.74977 -0.71272,6.35743 1.07776,11.75533 1.2655,3.81516 2.31699,8.23974 2.33665,9.83241 0.0197,1.59268 1.4271,6.20143 3.12762,10.24168 8.56635,20.35276 3.8118,69.27231 -10.8813,111.95755 -18.60016,54.03574 -64.33587,107.44456 -114.76555,134.01987 -4.8895,2.57665 -12.319,6.585 -16.51,8.90745 -13.26663,7.35171 -49.35492,20.68691 -67.30999,24.87211 -23.99703,5.59354 -73.62945,8.99105 -88.26499,6.04205 z m 49.32425,-73.37274 c 0.33805,-2.39112 -0.0193,-2.86413 -1.68187,-2.22614 -1.17495,0.45087 -2.95942,-0.0382 -4.02414,-1.10291 -2.37068,-2.3707 -3.76417,-1.60294 -4.54978,2.5067 -0.78736,4.11881 -0.0663,4.69127 5.33614,4.23656 3.6353,-0.30597 4.57213,-0.95614 4.91965,-3.41421 z m -32.99188,-0.91926 c 2.1374,-1.49709 4.33532,-1.82543 8.14338,-1.2165 4.9179,0.78641 5.48726,0.5371 9.9679,-4.36465 4.24303,-4.64181 5.0273,-5.0268 7.32145,-3.59408 3.31298,2.06899 4.58369,0.76816 2.50346,-2.56281 -2.01903,-3.23299 -0.87634,-4.53228 3.1068,-3.53257 2.01981,0.50695 3.43135,2.11574 4.19621,4.78264 0.63391,2.21031 2.00551,4.42259 3.04799,4.91616 1.04249,0.49358 4.50072,2.19599 7.68497,3.78314 5.5041,2.74343 5.98626,2.78398 9.77943,0.82245 2.19444,-1.1348 5.67356,-2.06325 7.73138,-2.06325 12.76999,0 33.53796,-14.89572 21.31165,-15.28567 -2.70858,-0.0864 -8.67006,-5.95795 -8.22527,-8.10119 0.38494,-1.85484 1.01883,-1.71003 4.8175,1.10049 9.06093,6.70391 33.49499,2.37152 37.51942,-6.65254 1.45937,-3.27237 3.49979,-3.59495 5.73135,-0.90609 0.95206,1.14716 3.73079,1.905 6.985,1.905 3.25421,0 6.03293,-0.75784 6.98499,-1.905 0.86956,-1.04775 2.95989,-1.905 4.64519,-1.905 1.68529,0 4.5305,-0.96077 6.32267,-2.13505 2.63707,-1.72788 3.96292,-1.86916 6.95215,-0.74086 3.50705,1.32376 3.88326,1.11857 7.44768,-4.06214 3.80252,-5.52678 6.88425,-7.65654 9.09859,-6.28801 0.74386,0.45974 0.63883,1.4489 -0.27012,2.54412 -0.95745,1.15367 -1.01225,1.79194 -0.15383,1.79194 0.73332,0 2.55881,-1.55796 4.05663,-3.46213 3.42857,-4.35873 1.85668,-5.00871 -6.98396,-2.88787 -9.15933,2.19729 -11.78204,0.80766 -3.175,-1.68225 13.5603,-3.92282 13.7201,-3.93032 14.18452,-0.66506 0.48018,3.37609 3.90408,4.49197 7.88991,2.57137 3.11962,-1.5032 9.87431,-3.29445 14.48275,-3.84063 1.20222,-0.14248 1.2929,-0.82742 0.34419,-2.60007 -1.72124,-3.21618 -0.27484,-4.24273 11.0884,-7.86975 22.79066,-7.27452 29.87092,-23.36653 11.70023,-26.59222 -18.30012,-3.24869 -22.47167,-6.15306 -11.1614,-7.77093 3.17479,-0.45413 6.94731,-1.45452 8.38338,-2.22308 1.43607,-0.76856 4.27859,-1.39739 6.31672,-1.39739 3.75266,0 11.75139,-4.52784 12.60622,-7.136 0.29386,-0.89657 -0.72461,-1.19347 -2.75959,-0.80446 -3.34481,0.63941 -4.64331,-2.70513 -2.11904,-5.45804 0.60847,-0.66357 1.43245,-4.15326 1.83107,-7.75488 0.89096,-8.05013 0.74033,-8.11082 -18.49486,-7.45206 -7.50888,0.25715 -13.6525,0.0323 -13.6525,-0.49964 0,-1.64651 6.25061,-3.91492 10.78756,-3.91492 2.37492,0 6.9763,-1.4134 10.22527,-3.14091 3.24898,-1.72749 7.99411,-3.47811 10.5447,-3.89026 18.39616,-2.97259 29.15984,-12.32739 21.82333,-18.96684 -1.86825,-1.69076 -2.19309,-2.83353 -1.2868,-4.52696 1.22936,-2.29707 -1.25876,-10.4284 -3.64163,-11.9011 -0.61384,-0.37937 -1.11607,-2.91868 -1.11607,-5.64291 0,-6.25651 -0.97658,-7.81102 -4.90711,-7.81102 -1.72745,0 -5.2656,-0.90899 -7.86254,-2.01996 -2.59694,-1.11098 -7.57921,-2.21931 -11.07171,-2.46296 -11.59659,-0.80899 -20.1813,-2.96987 -22.19623,-5.58707 -1.75539,-2.28007 -3.39821,-2.4988 -17.80877,-2.37109 -8.73125,0.0773 -21.0185,-0.57107 -27.305,-1.44101 -6.2865,-0.86994 -15.4305,-1.74704 -20.32,-1.94912 -16.65686,-0.6884 -31.74687,-2.2483 -33.2286,-3.43494 -0.81325,-0.65127 -2.8135,-1.0232 -4.445,-0.82651 -9.93078,1.19721 -26.96956,-3.2548 -25.18912,-6.58158 0.69224,-1.29347 0.946,-3.16643 0.56391,-4.16215 -0.7934,-2.06757 15.96376,-1.73769 22.80083,0.44886 8.6346,2.7614 14.36052,0.59764 10.29793,-3.89148 -5.70855,-6.30788 -0.16879,-9.55906 18.49511,-10.85445 7.84816,-0.54472 15.7221,-1.88645 19.32446,-3.29291 3.34373,-1.3055 8.81214,-2.37363 12.15199,-2.37363 7.3031,0 16.91602,-2.66241 28.28539,-7.83399 8.4169,-3.82858 8.4169,-3.82858 15.78108,1.05021 14.24205,9.43536 42.45394,4.83324 35.02802,-5.71401 -1.30777,-1.85746 -2.79357,-4.71526 -3.30177,-6.35065 -0.5163,-1.6615 -2.73572,-3.73042 -5.03021,-4.68912 -2.25842,-0.94363 -4.84508,-2.60596 -5.74813,-3.69406 -0.90304,-1.08811 -3.43776,-1.97838 -5.6327,-1.97838 -2.19494,0 -6.12339,-1.143002 -8.72989,-2.540002 -2.60651,-1.397 -5.81751,-2.54 -7.13557,-2.54 -1.36666,0 -2.79086,-1.22794 -3.31423,-2.8575 -0.87321,-2.71888 -0.94864,-2.73427 -1.55478,-0.3175 -1.14322,4.5582 -2.80822,5.68845 -6.37953,4.33062 -1.80349,-0.68567 -4.42072,-1.08332 -5.81608,-0.88365 -1.39535,0.19967 -2.80954,-0.43809 -3.14265,-1.41724 -0.46688,-1.37239 0.28497,-1.60215 3.2816,-1.00282 6.46175,1.29235 10.58292,-6.18291 5.65196,-10.25191 -4.23394,-3.49382 -2.8835,-4.78757 6.31836,-6.05315 11.93381,-1.64132 8.51247,-19.48655 -3.77354,-19.68218 -1.22037,-0.0194 -4.46377,-1.73393 -7.20758,-3.81 -2.74381,-2.07607 -5.91563,-3.77467 -7.0485,-3.77467 -1.13286,0 -2.60268,-0.54162 -3.26626,-1.20361 -2.89863,-2.89169 -13.4297,-7.54597 -15.38032,-6.79744 -1.31756,0.5056 -1.80528,0.30693 -1.29065,-0.52574 0.90526,-1.46474 -3.61599,-3.54068 -10.57053,-4.85347 -2.44475,-0.46149 -5.71077,-1.640221 -7.25782,-2.619405 -1.54705,-0.979185 -4.63118,-1.780335 -6.85364,-1.780335 -2.22246,0 -9.52085,-1.755406 -16.21865,-3.900903 -6.6978,-2.145496 -13.54776,-3.63902 -15.22214,-3.318942 -4.76623,0.911123 -22.14063,-1.624545 -24.27789,-3.543181 -1.98535,-1.782265 -6.86473,-1.26991 -16.4109,1.72321 -3.09808,0.971375 -5.91344,0.956685 -9.91829,-0.05175 -3.07574,-0.77448 -8.64195,-1.093388 -12.36935,-0.708686 -4.51087,0.465564 -7.56446,0.150441 -9.1317,-0.942368 -5.67162,-3.954717 -14.84239,-3.707022 -18.5928,0.502174 -4.36791,4.902237 -9.24941,5.21136 -11.29788,0.715443 -1.7391,-3.816886 -9.74932,-4.546925 -12.57201,-1.145792 -1.15853,1.395949 -2.94718,1.824235 -5.73152,1.372398 -2.22608,-0.361243 -6.2531,-0.09778 -8.94892,0.585479 -2.6958,0.683258 -13.06378,1.490941 -23.03992,1.794852 -16.23927,0.494709 -18.53679,0.847076 -21.94295,3.365363 -3.21731,2.37866 -4.36422,2.601531 -7.43089,1.443994 -2.47082,-0.93263 -5.79821,-0.980063 -10.44204,-0.148857 -3.74862,0.670975 -13.10217,1.400574 -20.78567,1.621334 -23.16673,0.665622 -37.465,3.860394 -37.465,8.371092 0,0.73675 -1.10016,0.97713 -2.47504,0.54076 -1.59784,-0.50714 -3.39826,0.13768 -5.07999,1.81942 -1.43273,1.43273 -3.40107,2.60496 -4.37409,2.60496 -2.17431,0 -3.61004,3.4067 -2.676,6.34961 1.00552,3.16811 -1.49595,4.65685 -10.79487,6.42449 -11.85264,2.2531 -20.40027,6.2759 -13.335,6.2759 1.07012,0 1.905,1.13422 1.905,2.58807 0,2.0503 -0.70604,2.51211 -3.39789,2.2225 -2.79201,-0.30038 -3.85019,0.48227 -5.93443,4.38917 -3.39681,6.36731 -2.20337,7.01179 7.05701,3.81088 8.08092,-2.79322 9.89531,-2.72443 9.89531,0.37513 0,1.70878 4.49797,3.61471 7.06687,2.99447 0.65347,-0.15779 2.61688,0.11722 4.36313,0.61126 3.00942,0.8513 9.73172,1.64573 16.32837,1.92968 2.04902,0.0881 2.77305,0.70302 2.29497,1.94885 -0.70598,1.83975 -0.025,2.08124 10.90415,3.86695 10.36264,1.69314 4.64124,4.15793 -12.3825,5.33443 -7.3696,0.50931 -13.00236,1.6245 -15.85619,3.13926 -2.4344,1.29214 -6.49919,2.34935 -9.03287,2.34935 -3.21412,0 -6.06684,1.160402 -9.43704,3.838702 -8.62335,6.85297 -11.51584,6.49858 -16.56767,-2.02986 -3.98665,-6.730222 -33.56122,11.96366 -33.56122,21.21381 0,5.91492 0.20825,6.03738 10.76125,6.3277 6.29977,0.17332 10.14757,1.04915 14.29316,3.25337 9.01342,4.79249 23.61127,6.00633 64.18093,5.3368 6.82824,-0.11265 7.59747,0.18173 11.44289,4.37995 2.26739,2.47543 5.75837,5.12271 7.7577,5.88286 3.63516,1.38208 3.63516,1.38208 0.30461,4.46874 -1.83179,1.69766 -3.33055,4.29885 -3.33055,5.78042 0,1.88018 -0.67124,2.56582 -2.2225,2.27016 -2.67284,-0.50943 -3.64424,-5.53943 -1.55576,-8.0559 2.68736,-3.23807 -4.34422,-5.40777 -8.86705,-2.73608 -1.99188,1.17663 -5.39078,2.13933 -7.55313,2.13933 -2.16236,0 -3.93156,0.5514 -3.93156,1.22532 0,1.81933 4.42956,3.85468 8.38901,3.85468 3.76759,0 8.12099,3.16915 8.12099,5.91184 0,2.05421 -13.35853,2.45499 -14.605,0.43816 -1.26503,-2.04687 -4.30162,-1.46648 -6.64467,1.27 -1.5552,1.81634 -3.74642,2.54 -7.69106,2.54 -3.03394,0 -6.75241,0.66158 -8.26328,1.47017 -4.58948,2.45622 -11.47363,2.63024 -19.70201,0.49805 -7.86526,-2.03809 -12.6871,-1.71742 -17.38897,1.15645 -1.397,0.85387 -4.70061,1.89579 -7.34136,2.31538 -11.65013,1.85106 -21.57888,8.19646 -20.64172,13.192 0.71226,3.79665 0.3244,5.09313 -2.36277,7.89794 -6.09234,6.35903 -5.2752,33.16001 1.01103,33.16001 0.89988,0 2.65899,1.13023 3.90913,2.51162 1.25013,1.38138 3.48184,2.51789 4.95933,2.52557 1.4775,0.008 5.11524,0.0246 8.08386,0.0376 2.96862,0.013 5.3975,0.5101 5.3975,1.10476 0,0.59467 4.42912,1.81278 9.8425,2.70691 5.41337,0.89413 10.414,2.19354 11.1125,2.88758 0.84194,0.83658 -0.18247,0.98971 -3.03932,0.45435 -2.37012,-0.44415 -4.98672,-0.24535 -5.81467,0.44179 -1.5343,1.27335 -2.2873,1.4769 -11.82964,3.19763 -5.8209,1.04967 -7.09229,2.50688 -8.36828,9.59135 -0.88988,4.94076 4.90825,8.74411 10.58399,6.94269 1.36243,-0.43241 3.78906,0.0723 5.39253,1.12141 3.84427,2.5154 17.10566,5.92623 19.48757,5.01221 1.03134,-0.39577 2.58226,-0.15312 3.44649,0.53919 1.96935,1.57762 14.92894,5.33474 21.078,6.11072 7.12979,0.8997 9.06832,1.78858 9.06832,4.15807 0,1.20025 -1.00012,2.30264 -2.2225,2.44973 -1.22237,0.1471 -4.79424,0.52963 -7.93749,0.85006 -4.32195,0.44059 -6.40934,1.44783 -8.56351,4.13219 -2.8485,3.54958 -2.8485,3.54958 -24.765,3.54958 -18.86124,0 -21.84542,0.26557 -21.40668,1.905 0.2804,1.04775 0.56615,3.85523 0.635,6.23883 0.15667,5.42374 9.01793,12.59135 15.73411,12.72686 2.29841,0.0464 5.84157,1.31356 7.87369,2.81597 3.9728,2.93722 12.03509,4.86941 22.28374,5.34048 3.53778,0.16261 7.78231,1.26477 9.49759,2.46619 2.25361,1.57849 4.67076,1.9959 8.84365,1.52721 6.63081,-0.74478 13.74903,2.17622 15.91675,6.53152 2.55838,5.14019 4.74743,6.80295 8.95621,6.80295 2.31799,0 4.53719,0.52204 4.93154,1.16012 2.05549,3.32586 12.79916,6.82288 21.47336,6.98947 11.25325,0.21612 12.52636,0.95142 11.93562,6.89345 -0.46124,4.63934 -0.46468,4.64175 -6.10628,4.29111 -5.36617,-0.33353 -5.49474,-0.26381 -2.60448,1.41253 2.90321,1.68384 7.36832,1.34406 12.98843,-0.98835 2.56871,-1.06606 6.35251,2.48671 5.37141,5.04342 -1.07489,2.80112 1.24596,2.24917 3.11294,-0.74033 1.20561,-1.93049 2.35793,-2.37596 4.43298,-1.71372 1.54216,0.49217 4.8805,0.8155 7.41853,0.71851 3.99937,-0.15284 4.82419,0.32964 6.18657,3.61872 1.17741,2.8425 2.40992,3.7997 4.91039,3.81354 1.83613,0.0102 5.62443,0.80136 8.41843,1.75819 2.794,0.95685 10.50925,2.13933 17.145,2.62773 7.93143,0.58379 13.58789,1.70363 16.51,3.26859 5.48187,2.93586 5.16657,2.92006 8.71237,0.43649 z m 0.38444,-4.68527 c -1.20059,-1.44662 -1.00656,-2.45281 0.93474,-4.84738 3.10798,-3.83368 3.85234,-2.92533 2.01632,2.46056 -1.19383,3.50208 -1.69146,3.90454 -2.95106,2.38682 z M -84.77499,349.0366 c -5.42673,-5.77648 8.74126,-10.84558 17.2382,-6.16757 3.24895,1.7887 3.876,3.41641 0.87204,2.26369 -2.44725,-0.9391 -8.86793,2.23569 -9.88578,4.88816 -1.09643,2.85726 -5.06131,2.38275 -8.22446,-0.98428 z m -28.4818,-16.21547 c -3.0467,-1.77455 -2.80166,-5.80057 0.40409,-6.6389 3.93493,-1.02901 10.07341,1.52367 10.07341,4.18899 0,3.63204 -6.02795,5.04153 -10.4775,2.44991 z m 259.88647,-20.84157 c 1.31669,-0.99687 5.03144,-2.15008 8.255,-2.56268 6.03856,-0.77289 14.51944,-2.53466 18.715,-3.88775 1.77123,-0.57123 1.50382,0.0465 -1.03784,2.39715 -3.78349,3.49926 -13.13046,5.9686 -22.35616,5.90619 -5.44647,-0.0368 -5.76005,-0.19934 -3.576,-1.85291 z M -116.32597,150.58418 c -1.08251,-2.82099 3.1361,-6.0138 6.56168,-4.96614 1.397,0.42726 5.11175,1.05971 8.255,1.40544 11.02309,1.21247 12.69995,1.87873 8.255,3.27996 -7.92738,2.49903 -22.15402,2.67213 -23.07168,0.28074 z m 211.03167,-7.8962 c -4.80528,-0.58089 -7.44689,-2.85493 -3.1115,-2.67853 4.46725,0.18174 9.6575,1.77651 8.1787,2.51298 -0.69147,0.34436 -2.9717,0.41886 -5.0672,0.16555 z m -218.52892,220.85878 c 1.07146,-1.73365 -2.79535,-4.33722 -5.11793,-3.44595 -1.67256,0.64182 -1.76661,1.15068 -0.49566,2.68207 1.74767,2.10583 4.54867,2.48696 5.61359,0.76388 z m 272.50391,-2.14202 c 0,-1.18268 -0.86407,-1.83055 -2.11725,-1.5875 -3.18614,0.61792 -3.62363,3.58562 -0.52858,3.58562 1.55998,0 2.64583,-0.82003 2.64583,-1.99812 z M -155.082,338.34741 c 0.79681,-5.60628 -10.08514,-12.62604 -14.88635,-9.60294 -5.94184,3.74129 1.44662,13.51117 9.70172,12.82874 3.83075,-0.31669 4.86239,-0.95856 5.18463,-3.2258 z"
+         id="path12874"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#6f7668;stroke-width:1.26999998"
+         d="m -21.49929,445.80433 c -2.44475,-0.49261 -9.5885,-1.52038 -15.875,-2.28393 -24.36978,-2.95995 -92.075,-25.55783 -92.075,-30.73176 0,-1.15763 -9.22591,-6.20578 -11.34157,-6.20578 -2.23074,0 -20.77152,-12.94892 -27.6349,-19.30027 -3.62531,-3.35486 -7.24482,-6.09973 -8.04335,-6.09973 -0.79854,0 -6.45479,-5.00063 -12.56946,-11.1125 -6.11467,-6.11188 -13.16848,-12.57943 -15.67515,-14.37234 -15.75442,-11.26846 -48.91565,-64.90744 -50.98243,-82.46517 -0.45223,-3.84175 -2.56105,-10.46695 -4.68627,-14.72267 -2.40182,-4.80963 -4.34552,-11.29757 -5.1364,-17.145 -0.6998,-5.17404 -2.23156,-12.83633 -3.40391,-17.02733 -3.46336,-12.38105 -3.08983,-56.37353 0.54456,-64.135 1.47185,-3.14325 3.30922,-8.001 4.08304,-10.795 0.77381,-2.794 1.81533,-6.50875 2.31447,-8.255 0.49914,-1.74625 0.83727,-6.88975 0.7514,-11.43 -0.23862,-12.61674 5.35568,-29.15121 14.68732,-43.409692 1.97446,-3.01692 5.13969,-9.5373 7.03384,-14.48971 2.06468,-5.39828 6.53271,-13.0275 11.15659,-19.05 4.24197,-5.52508 9.92399,-12.99675 12.6267,-16.6037 6.49968,-8.67428 35.96174,-34.28186 50.18954,-43.623317 6.22654,-4.088123 12.50763,-8.596282 13.95796,-10.018134 1.45034,-1.421851 6.87959,-4.678458 12.065,-7.236904 5.18541,-2.558447 11.71402,-6.212631 14.50802,-8.120407 2.794,-1.907776 9.93775,-5.587854 15.875,-8.177949 5.93725,-2.590096 12.12114,-5.565781 13.74199,-6.612633 3.95453,-2.554127 11.14731,-5.040052 25.62801,-8.85741 6.63575,-1.749296 15.20825,-4.256888 19.05,-5.572427 6.038,-2.067603 20.47576,-4.518503 28.575,-4.85078 51.56025,-2.1153 93.01167,4.092518 125.88348,18.852479 17.80631,7.995306 41.05944,19.956527 47.94011,24.660003 4.28251,2.92743 11.78689,7.658684 16.67639,10.513897 19.15133,11.183379 48.74829,38.558344 64.23405,59.411682 8.88265,11.96151 18.95095,29.11255 18.95095,32.2824 0,0.78703 2.53652,5.84695 5.6367,11.24427 8.36309,14.559872 14.06756,31.947952 12.50312,38.111362 -0.9518,3.74977 -0.71272,6.35743 1.07776,11.75533 1.2655,3.81516 2.31699,8.23974 2.33665,9.83241 0.0197,1.59268 1.4271,6.20143 3.12762,10.24168 8.56635,20.35276 3.8118,69.27231 -10.8813,111.95755 -18.60016,54.03574 -64.33587,107.44456 -114.76555,134.01987 -4.8895,2.57665 -12.319,6.585 -16.51,8.90745 -13.26663,7.35171 -49.35492,20.68691 -67.30999,24.87211 -23.99703,5.59354 -73.62945,8.99105 -88.26499,6.04205 z m 49.32425,-73.37274 c 0.33805,-2.39112 -0.0193,-2.86413 -1.68187,-2.22614 -1.17495,0.45087 -2.95942,-0.0382 -4.02414,-1.10291 -2.37068,-2.3707 -3.76417,-1.60294 -4.54978,2.5067 -0.78736,4.11881 -0.0663,4.69127 5.33614,4.23656 3.6353,-0.30597 4.57213,-0.95614 4.91965,-3.41421 z m -32.99188,-0.91926 c 2.1374,-1.49709 4.33532,-1.82543 8.14338,-1.2165 4.9179,0.78641 5.48726,0.5371 9.9679,-4.36465 4.24303,-4.64181 5.0273,-5.0268 7.32145,-3.59408 3.31298,2.06899 4.58369,0.76816 2.50346,-2.56281 -2.01903,-3.23299 -0.87634,-4.53228 3.1068,-3.53257 2.01981,0.50695 3.43135,2.11574 4.19621,4.78264 0.63391,2.21031 2.00551,4.42259 3.04799,4.91616 1.04249,0.49358 4.50072,2.19599 7.68497,3.78314 5.5041,2.74343 5.98626,2.78398 9.77943,0.82245 2.19444,-1.1348 5.67356,-2.06325 7.73138,-2.06325 12.76999,0 33.53796,-14.89572 21.31165,-15.28567 -2.70858,-0.0864 -8.67006,-5.95795 -8.22527,-8.10119 0.38494,-1.85484 1.01883,-1.71003 4.8175,1.10049 9.06093,6.70391 33.49499,2.37152 37.51942,-6.65254 1.45937,-3.27237 3.49979,-3.59495 5.73135,-0.90609 0.95206,1.14716 3.73079,1.905 6.985,1.905 3.25421,0 6.03293,-0.75784 6.98499,-1.905 0.86956,-1.04775 2.95989,-1.905 4.64519,-1.905 1.68529,0 4.5305,-0.96077 6.32267,-2.13505 2.63707,-1.72788 3.96292,-1.86916 6.95215,-0.74086 3.50705,1.32376 3.88326,1.11857 7.44768,-4.06214 3.80252,-5.52678 6.88425,-7.65654 9.09859,-6.28801 0.74386,0.45974 0.63883,1.4489 -0.27012,2.54412 -0.95745,1.15367 -1.01225,1.79194 -0.15383,1.79194 0.73332,0 2.55881,-1.55796 4.05663,-3.46213 3.42857,-4.35873 1.85668,-5.00871 -6.98396,-2.88787 -9.15933,2.19729 -11.78204,0.80766 -3.175,-1.68225 13.5603,-3.92282 13.7201,-3.93032 14.18452,-0.66506 0.48018,3.37609 3.90408,4.49197 7.88991,2.57137 3.11962,-1.5032 9.87431,-3.29445 14.48275,-3.84063 1.20222,-0.14248 1.2929,-0.82742 0.34419,-2.60007 -1.72124,-3.21618 -0.27484,-4.24273 11.0884,-7.86975 9.58554,-3.05959 23.13023,-13.85534 23.13023,-18.43588 0,-4.07408 -2.60339,-6.63519 -7.60344,-7.47994 -5.60992,-0.94779 -6.59153,-3.25665 -2.57932,-6.06691 11.25389,-7.88254 17.38024,-14.94344 12.08776,-13.93172 -3.29622,0.63011 -4.57088,-2.73886 -2.06871,-5.46767 0.60847,-0.66357 1.4388,-4.20687 1.84518,-7.874 0.80131,-7.23114 1.13151,-6.90377 -7.39647,-7.3332 -2.0955,-0.10554 -5.38163,-0.28466 -7.3025,-0.39809 -8.83121,-0.52143 3.34727,-9.20271 16.29473,-11.61549 11.15813,-2.07934 12.7252,-2.71191 17.457,-7.04683 5.23492,-4.7958 5.85,-8.0185 2.16663,-11.3519 -1.86825,-1.69076 -2.19309,-2.83353 -1.2868,-4.52696 1.22936,-2.29707 -1.25876,-10.4284 -3.64163,-11.9011 -0.61384,-0.37937 -1.11607,-2.91868 -1.11607,-5.64291 0,-6.25651 -0.97658,-7.81102 -4.90711,-7.81102 -1.72745,0 -5.2656,-0.90899 -7.86254,-2.01996 -2.59694,-1.11098 -7.57921,-2.21931 -11.07171,-2.46296 -19.24831,-1.34278 -22.47946,-3.04731 -23.40063,-12.34458 -0.0519,-0.52387 -2.06089,-0.9525 -4.46441,-0.9525 -2.40353,0 -4.71169,-0.55278 -5.12925,-1.22841 -0.41756,-0.67561 -2.50938,-0.87835 -4.6485,-0.45053 -4.3809,0.87618 -6.97119,-1.54309 -4.64859,-4.34166 1.87493,-2.25914 0.25842,-3.60536 -2.03937,-1.69837 -1.49663,1.24209 -2.12977,1.0048 -3.33681,-1.25054 -1.55246,-2.90081 -0.10782,-4.93642 3.53506,-4.98104 3.91072,-0.0479 1.26829,-3.76473 -3.175,-4.46591 -2.61937,-0.41336 -6.58859,-1.65539 -8.82048,-2.7601 -2.2319,-1.1047 -4.92822,-1.6746 -5.99183,-1.26646 -1.06361,0.40815 -2.31026,0.13305 -2.77032,-0.61134 -1.43239,-2.31769 5.48615,-6.99339 13.77675,-9.31064 4.36336,-1.21958 10.05374,-3.29462 12.64528,-4.6112 5.9857,-3.04093 7.44685,-3.00402 14.72527,0.37197 6.55913,3.04235 10.47557,3.26267 23.74283,1.33563 14.15016,-2.05528 16.575,-4.08639 12.236,-10.24919 -1.30777,-1.85746 -2.78807,-4.69792 -3.28954,-6.31213 -0.50147,-1.61419 -2.43383,-3.58064 -4.29411,-4.36986 -1.86029,-0.78923 -4.52535,-2.39547 -5.92235,-3.56943 -1.397,-1.17396 -4.89619,-2.42951 -7.77598,-2.79011 -3.06386,-0.383662 -8.06769,-2.562632 -12.06189,-5.252492 -3.75426,-2.52826 -7.01782,-4.02114 -7.25236,-3.31751 -0.43151,1.29452 -6.56477,1.85476 -6.56477,0.59964 0,-0.37382 0.88061,-2.02365 1.95689,-3.66627 1.85016,-2.82372 1.76359,-3.13166 -1.5875,-5.64636 -4.34538,-3.26083 -2.85924,-4.66691 6.25799,-5.92084 11.93381,-1.64132 8.51247,-19.48655 -3.77354,-19.68218 -1.22037,-0.0194 -4.46377,-1.73393 -7.20758,-3.81 -2.74381,-2.07607 -5.91563,-3.77467 -7.0485,-3.77467 -1.13286,0 -2.60268,-0.54162 -3.26626,-1.20361 -2.89863,-2.89169 -13.4297,-7.54597 -15.38032,-6.79744 -1.31756,0.5056 -1.80528,0.30693 -1.29065,-0.52574 0.90526,-1.46474 -3.61599,-3.54068 -10.57053,-4.85347 -2.44475,-0.46149 -5.71077,-1.640221 -7.25782,-2.619405 -1.54705,-0.979185 -4.63118,-1.780335 -6.85364,-1.780335 -2.22246,0 -9.52085,-1.755406 -16.21865,-3.900903 -6.6978,-2.145496 -13.54776,-3.63902 -15.22214,-3.318942 -4.76623,0.911123 -22.14063,-1.624545 -24.27789,-3.543181 -1.98535,-1.782265 -6.86473,-1.26991 -16.4109,1.72321 -3.09808,0.971375 -5.91344,0.956685 -9.91829,-0.05175 -3.07574,-0.77448 -8.64195,-1.093388 -12.36935,-0.708686 -4.51087,0.465564 -7.56446,0.150441 -9.1317,-0.942368 -5.67162,-3.954717 -14.84239,-3.707022 -18.5928,0.502174 -4.36791,4.902237 -9.24941,5.21136 -11.29788,0.715443 -1.7391,-3.816886 -9.74932,-4.546925 -12.57201,-1.145792 -1.15853,1.395949 -2.94718,1.824235 -5.73152,1.372398 -2.22608,-0.361243 -6.2531,-0.09778 -8.94892,0.585479 -2.6958,0.683258 -13.06378,1.490941 -23.03992,1.794852 -16.23927,0.494709 -18.53679,0.847076 -21.94295,3.365363 -3.21731,2.37866 -4.36422,2.601531 -7.43089,1.443994 -2.47082,-0.93263 -5.79821,-0.980063 -10.44204,-0.148857 -3.74862,0.670975 -13.10217,1.400574 -20.78567,1.621334 -23.16673,0.665622 -37.465,3.860394 -37.465,8.371092 0,0.73675 -1.10016,0.97713 -2.47504,0.54076 -1.59784,-0.50714 -3.39826,0.13768 -5.07999,1.81942 -1.43273,1.43273 -3.40107,2.60496 -4.37409,2.60496 -2.17431,0 -3.61004,3.4067 -2.676,6.34961 1.00552,3.16811 -1.49595,4.65685 -10.79487,6.42449 -11.85264,2.2531 -20.40027,6.2759 -13.335,6.2759 1.07012,0 1.905,1.13422 1.905,2.58807 0,2.0503 -0.70604,2.51211 -3.39789,2.2225 -2.79201,-0.30038 -3.85019,0.48227 -5.93443,4.38917 -3.39681,6.36731 -2.20337,7.01179 7.05701,3.81088 8.08092,-2.79322 9.89531,-2.72443 9.89531,0.37513 0,1.84518 5.86678,4.0308 7.21112,2.68646 0.40856,-0.40856 1.70106,-0.23003 2.87221,0.39676 1.17115,0.62678 6.06667,1.48159 10.87894,1.89957 19.32061,1.67817 7.13354,8.61177 -17.78727,10.11975 -7.08653,0.4288 -9.525,1.88851 -9.525,5.70184 0,9.038522 -9.84681,13.088382 -14.13828,5.814872 -3.28806,-5.572852 -2.58561,-5.584952 -16.10715,0.27742 -20.15886,8.74001 -26.63695,23.14566 -11.02957,24.52699 7.53226,0.66664 12.13165,2.57393 18.52495,7.68203 1.80672,1.44352 5.74019,2.9539 8.74104,3.3564 3.85761,0.51741 5.63614,1.42035 6.07067,3.08199 0.44255,1.69234 1.79633,2.35017 4.83651,2.35017 2.32206,0 4.54496,0.5227 4.93979,1.16153 0.98815,1.59887 -7.16477,6.45405 -10.87787,6.47792 -4.35497,0.0281 -6.3132,1.56171 -4.8825,3.82407 2.86302,4.52728 1.44631,5.86887 -7.98509,7.56167 -5.06413,0.90894 -10.47327,2.42935 -12.02032,3.37872 -1.54705,0.94935 -4.01443,1.72609 -5.48308,1.72609 -2.08556,0 -2.49507,0.552 -1.87018,2.52085 0.67067,2.11308 -0.36576,3.03646 -6.40703,5.70819 -7.20713,3.18734 -7.20713,3.18734 -6.34247,7.79632 0.72669,3.87364 0.35172,5.14435 -2.35027,7.96463 -6.09234,6.35903 -5.2752,33.16001 1.01103,33.16001 0.89988,0 2.65899,1.13023 3.90913,2.51162 1.25013,1.38138 3.48184,2.51789 4.95933,2.52557 1.4775,0.008 5.11524,0.0246 8.08386,0.0376 2.96862,0.013 5.3975,0.52294 5.3975,1.13329 0,0.61035 4.54482,1.8478 10.09959,2.74987 5.55478,0.90208 9.79293,2.13634 9.41812,2.74279 -0.37481,0.60645 -1.8038,0.74643 -3.17554,0.31106 -1.37173,-0.43537 -3.24145,-0.17131 -4.15493,0.58682 -0.91348,0.75811 -3.31493,1.65545 -5.33656,1.99407 -13.14812,2.20235 -13.42263,2.4037 -14.96881,10.9796 -0.7544,4.18429 -0.53262,4.69247 2.36539,5.41983 1.75882,0.44142 3.05833,1.38124 2.8878,2.08845 -0.92204,3.82382 20.86962,12.08167 35.21873,13.34601 13.59401,1.19779 14.87324,2.27024 10.72131,8.98821 -2.87966,4.65939 -6.8907,6.29287 -12.30107,5.00959 -9.58289,-2.27297 -36.68164,-0.56087 -34.96954,2.20937 0.50926,0.824 0.9472,3.4491 0.97322,5.83358 0.0591,5.41661 8.88469,12.59179 15.65621,12.72842 2.29841,0.0464 5.84157,1.31356 7.87369,2.81597 3.9728,2.93722 12.03509,4.86941 22.28374,5.34048 3.53778,0.16261 7.78231,1.26477 9.49759,2.46619 2.25361,1.57849 4.67076,1.9959 8.84365,1.52721 6.63081,-0.74478 13.74903,2.17622 15.91675,6.53152 2.55838,5.14019 4.74743,6.80295 8.95621,6.80295 2.31799,0 4.53719,0.52204 4.93154,1.16012 2.05549,3.32586 12.79916,6.82288 21.47336,6.98947 11.25325,0.21612 12.52636,0.95142 11.93562,6.89345 -0.46124,4.63934 -0.46468,4.64175 -6.10628,4.29111 -5.36617,-0.33353 -5.49474,-0.26381 -2.60448,1.41253 2.90321,1.68384 7.36832,1.34406 12.98843,-0.98835 2.56871,-1.06605 6.35251,2.48671 5.37141,5.04342 -1.07489,2.80113 1.24596,2.24917 3.11294,-0.74033 1.20561,-1.93049 2.35793,-2.37596 4.43298,-1.71372 1.54216,0.49217 4.8805,0.8155 7.41853,0.71851 3.99937,-0.15284 4.82419,0.32964 6.18657,3.61872 1.17741,2.8425 2.40992,3.7997 4.91039,3.81355 1.83613,0.0102 5.62443,0.80135 8.41843,1.75818 2.794,0.95685 10.50925,2.13933 17.145,2.62773 7.93143,0.58379 13.58789,1.70363 16.51,3.26859 5.48187,2.93586 5.16657,2.92006 8.71237,0.43649 z m 0.38444,-4.68527 c -1.20059,-1.44662 -1.00656,-2.45281 0.93474,-4.84738 3.10798,-3.83368 3.85234,-2.92533 2.01632,2.46056 -1.19383,3.50208 -1.69146,3.90454 -2.95106,2.38682 z M -84.77499,349.0366 c -5.42673,-5.77648 8.74126,-10.84558 17.2382,-6.16757 3.24895,1.7887 3.876,3.41641 0.87204,2.26369 -2.44725,-0.9391 -8.86793,2.23569 -9.88578,4.88816 -1.09643,2.85726 -5.06131,2.38275 -8.22446,-0.98428 z m -28.50851,-17.50042 c -3.04064,-1.77019 -1.58076,-3.69332 2.80366,-3.69332 3.60003,0 7.12929,2.68792 5.57287,4.24435 -1.242,1.24199 -5.81318,0.9413 -8.37653,-0.55103 z m -52.99578,-53.08745 c 0,-1.63774 3.47214,-2.7258 5.82148,-1.82428 1.61953,0.62148 1.6814,0.98112 0.3175,1.84569 -2.24301,1.42185 -6.13898,1.40826 -6.13898,-0.0214 z m 42.45606,85.09803 c 1.07146,-1.73365 -2.79535,-4.33722 -5.11793,-3.44595 -1.67256,0.64182 -1.76661,1.15068 -0.49566,2.68207 1.74767,2.10583 4.54867,2.48696 5.61359,0.76388 z m 272.50391,-2.14202 c 0,-1.18268 -0.86407,-1.83055 -2.11725,-1.5875 -3.18614,0.61792 -3.62363,3.58562 -0.52858,3.58562 1.55998,0 2.64583,-0.82003 2.64583,-1.99812 z M -155.082,338.34741 c 0.79681,-5.60628 -10.08514,-12.62604 -14.88635,-9.60294 -5.94184,3.74129 1.44662,13.51117 9.70172,12.82874 3.83075,-0.31669 4.86239,-0.95856 5.18463,-3.2258 z"
+         id="path12872"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#6b6b56;stroke-width:1.26999998"
+         d="m -20.07554,445.76095 c -1.22979,-0.45479 -6.1966,-1.20329 -11.03736,-1.66332 -18.39228,-1.74788 -51.5415,-10.63402 -71.03139,-19.04102 -2.0955,-0.9039 -8.382,-3.52755 -13.97,-5.83034 -5.588,-2.30279 -11.58875,-5.37524 -13.335,-6.82765 -4.03281,-3.35422 -8.42252,-5.81576 -10.3714,-5.81576 -2.30666,0 -22.29038,-13.89805 -28.76121,-20.0025 -3.14679,-2.96863 -6.25775,-5.3975 -6.91325,-5.3975 -1.15946,0 -20.18342,-17.27165 -33.66958,-30.56826 -6.92087,-6.82358 -7.61697,-7.75709 -25.50315,-34.20174 -12.3469,-18.25485 -20.5114,-35.33207 -20.5114,-42.90255 0,-3.22894 -1.69173,-8.65614 -4.62089,-14.8241 -3.28134,-6.90959 -4.8902,-12.37814 -5.54993,-18.86433 -0.51097,-5.02371 -1.9933,-12.56303 -3.29406,-16.75403 -3.7102,-11.9541 -3.51159,-55.01063 0.28705,-62.23 5.29296,-10.05934 8.4896,-23.23295 7.69705,-31.72015 -0.90261,-9.66585 3.5052,-23.60834 12.18909,-38.555762 3.11033,-5.35375 7.47605,-14.10881 9.7016,-19.45569 3.49317,-8.39232 10.27844,-19.07817 14.79395,-23.2984 0.74738,-0.6985 2.25815,-2.69875 3.35728,-4.445 4.02714,-6.39815 16.0082,-19.152464 24.7136,-26.308609 4.91525,-4.040516 12.0201,-9.99732 15.78854,-13.237343 7.45019,-6.405504 23.39024,-17.604049 25.05768,-17.604049 0.56995,0 2.36928,-1.418915 3.9985,-3.153146 1.62923,-1.73423 7.2439,-5.240476 12.47706,-7.791659 5.23316,-2.551181 11.96439,-6.291699 14.9583,-8.312262 2.9939,-2.020561 9.90897,-5.697488 15.36681,-8.170947 5.45785,-2.47346 12.78086,-5.869125 16.27336,-7.545923 3.4925,-1.676797 9.49325,-3.888454 13.335,-4.914792 3.84175,-1.026339 12.1285,-3.369435 18.415,-5.206882 6.2865,-1.837447 13.43025,-3.829331 15.875,-4.426411 7.91685,-1.933527 31.80394,-3.819626 47.52807,-3.752763 50.4532,0.214538 109.06002,17.572271 147.0928,43.564853 4.36927,2.986069 11.41446,7.275137 15.656,9.531264 30.2824,16.107597 85.63311,76.598369 85.63311,93.585299 0,1.14745 1.07061,3.60325 2.37914,5.45732 8.64985,12.25614 17.37883,37.156122 15.20826,43.382622 -1.06798,3.06363 -0.93323,5.4364 0.59889,10.54575 1.08731,3.62597 2.3677,8.59293 2.84531,11.03768 0.4776,2.44475 2.06732,6.731 3.53271,9.525 6.99736,13.34161 2.29328,72.7391 -8.24753,104.14 -21.09228,62.83348 -66.8141,116.98829 -121.72678,144.17814 -3.84175,1.90223 -9.93396,5.19957 -13.53825,7.32742 -3.60429,2.12786 -10.52144,5.30023 -15.37145,7.04972 -4.85001,1.74949 -12.47379,4.73308 -16.94174,6.63021 -29.65911,12.59345 -103.81999,22.98616 -120.36479,16.86761 z m 15.42207,-60.25726 c 0.41754,-0.41754 0.44605,-1.57512 0.0634,-2.57241 -0.73849,-1.92446 -4.20918,-0.0964 -4.20918,2.21708 0,1.25884 2.98635,1.5148 4.14582,0.35533 z m 17.15204,-3.2667 c 1.13447,-1.13447 2.69957,-1.0853 6.61826,0.20799 5.38412,1.77692 11.45387,-1.36477 11.45387,-5.9285 0,-1.24757 2.45151,-1.68362 9.46525,-1.68362 5.20588,0 10.93637,-0.6703 12.73444,-1.48955 2.50072,-1.1394 3.61936,-1.1394 4.75877,0 0.81925,0.81925 2.77026,1.48955 4.33558,1.48955 1.56533,0 3.19926,0.5715 3.63096,1.27 1.49568,2.42006 2.89618,1.24942 3.71278,-3.10343 0.99976,-5.32918 5.31197,-7.38573 7.94271,-3.78797 1.30779,1.78851 2.03087,1.96804 3.20351,0.7954 1.17264,-1.17265 1.00236,-1.88896 -0.75444,-3.17357 -2.82769,-2.06765 -1.27127,-3.48398 6.53294,-5.94495 3.81176,-1.20199 5.3975,-2.39778 5.3975,-4.07019 0,-2.8003 5.59206,-5.28629 9.99701,-4.44423 2.168,0.41444 4.40234,-0.67295 7.92835,-3.85847 4.05561,-3.66401 5.04588,-4.08044 5.693,-2.39407 0.43086,1.12281 1.35489,2.04148 2.05339,2.04148 0.6985,0 0.95707,-0.81547 0.57461,-1.81215 -1.05016,-2.73669 1.69539,-4.04605 8.04364,-3.83605 3.14325,0.10401 7.14374,-0.50447 8.88999,-1.35212 1.74625,-0.84765 3.94517,-1.26763 4.88649,-0.93327 0.94133,0.33435 3.41393,-0.65075 5.49467,-2.1891 3.48199,-2.57436 3.90628,-2.62866 5.32957,-0.68221 2.40222,3.28523 10.20234,2.1384 10.7301,-1.5776 0.43819,-3.08532 2.76917,-4.00147 2.76917,-1.08838 0,4.07296 8.41129,4.41697 13.33333,0.5453 3.2697,-2.57196 3.62647,-2.6143 5.09582,-0.60485 1.48479,2.03059 1.82024,2.02505 5.5696,-0.0919 3.243,-1.83109 4.18915,-1.93653 5.05575,-0.56345 3.3051,5.23677 11.76261,-0.98236 9.5462,-7.01968 -0.97629,-2.65936 -0.84469,-4.68362 0.52037,-8.00452 3.12007,-7.59047 3.50353,-8.07321 7.32466,-9.22127 13.93736,-4.18747 19.30716,-6.91849 25.61362,-13.02678 11.28329,-10.92873 9.44821,-13.84425 -10.45259,-16.6066 -2.78754,-0.38693 -5.35563,-0.99089 -5.70689,-1.34213 -1.07348,-1.0735 7.85382,-7.80512 14.71316,-11.09444 7.90399,-3.79029 10.4669,-12.97554 5.54799,-19.88351 -1.37556,-1.93178 -2.50102,-4.42424 -2.50102,-5.5388 0,-1.15924 -1.0549,-2.02645 -2.46505,-2.02645 -1.35577,0 -2.79224,-0.52941 -3.19214,-1.17646 -0.39991,-0.64707 -3.86264,-0.85286 -7.69496,-0.45733 -12.61219,1.30169 -2.99367,-4.9816 10.06412,-6.57437 4.12883,-0.50364 8.41508,-1.41312 9.525,-2.02108 1.10991,-0.60795 5.23299,-2.51528 9.16239,-4.23851 11.17299,-4.89989 12.15583,-13.9418 3.43474,-31.599 -0.67376,-1.36415 -0.58491,-3.23691 0.22541,-4.75103 4.38136,-8.18663 -9.13963,-15.22222 -29.25404,-15.22222 -4.31221,0 -6.35258,-0.77475 -9.19781,-3.4925 -3.40684,-3.25418 -3.76777,-3.34085 -5.2895,-1.27 -2.5131,3.41995 -5.49172,2.65951 -8.64399,-2.20678 -2.28338,-3.52496 -3.69657,-4.43249 -6.92171,-4.445 -2.2289,-0.009 -4.39419,-0.5685 -4.81175,-1.24413 -0.41756,-0.67561 -2.50938,-0.87835 -4.6485,-0.45053 -4.3809,0.87618 -6.97119,-1.54309 -4.64859,-4.34166 1.87493,-2.25914 0.25842,-3.60536 -2.03937,-1.69837 -1.49663,1.24209 -2.12977,1.0048 -3.33681,-1.25054 -1.55246,-2.90081 -0.10782,-4.93642 3.53506,-4.98104 3.91072,-0.0479 1.26829,-3.76473 -3.175,-4.46591 -2.61937,-0.41336 -6.58859,-1.65539 -8.82048,-2.7601 -2.2319,-1.1047 -4.92822,-1.6746 -5.99183,-1.26646 -1.06361,0.40815 -2.31026,0.13305 -2.77032,-0.61134 -1.43239,-2.31769 5.48615,-6.99339 13.77675,-9.31064 4.36336,-1.21958 10.05374,-3.29462 12.64528,-4.6112 6.00483,-3.05064 7.46783,-3.00759 14.72527,0.43325 15.97082,7.57198 47.73705,0.73509 43.6001,-9.38383 -0.95299,-2.33102 -2.01528,-5.79145 -2.36065,-7.68986 -0.64978,-3.57184 -2.82304,-6.07726 -5.68599,-6.55504 -0.90785,-0.15152 -2.94297,-0.98593 -4.5225,-1.85427 -1.57953,-0.868342 -3.57978,-1.447402 -4.445,-1.286812 -1.82466,0.33867 -8.52736,-1.04798 -14.90813,-3.08419 -4.58384,-1.46277 -9.25454,-4.72305 -6.76629,-4.72305 2.16949,0 2.92531,-7.96314 0.96757,-10.19388 -1.46941,-1.67432 -0.84329,-2.5246 4.58855,-6.23127 8.70081,-5.93739 7.57559,-17.86485 -1.68534,-17.86485 -2.24779,0 -19.51624,-8.72464 -21.13934,-10.68035 -0.85834,-1.03424 -4.32593,-2.79893 -7.70573,-3.92152 -3.3798,-1.12259 -8.23426,-4.01141 -10.78768,-6.4196 -3.58225,-3.378499 -5.72417,-4.37853 -9.37816,-4.37853 -2.60457,0 -5.387,-0.651415 -6.18316,-1.44759 -1.40108,-1.401073 -10.93972,-3.548229 -18.74542,-4.219606 -2.0955,-0.180236 -7.23899,-1.606239 -11.42999,-3.168896 C 114.68748,19.182443 109.0846,18.203045 97.08974,18.18557 87.70254,18.17189 86.4786,17.875351 83.49498,14.89172 80.30279,11.699535 78.67346,11.314388 67.0832,11.012235 62.52305,10.893351 60.66777,12.120253 63.81919,13.170726 69.44992,15.047636 63.14017,17.645973 53.844,17.27848 39.09683,16.695502 26.45684,12.047053 34.149,10.035506 36.39521,9.448111 38.68947,3.287382 37.42433,1.240342 36.37468,-0.458034 29.05218,-1.346582 27.65118,0.05442 26.88201,0.823582 25.12612,1.452897 23.7492,1.452897 c -2.52614,0 -7.1485,3.360071 -7.1485,5.196369 0,0.545986 1.85269,1.171785 4.1171,1.390666 6.58032,0.636063 6.46626,4.842965 -0.1313,4.842965 -2.79974,0 -6.62591,0.583785 -8.5026,1.297299 -2.18881,0.832184 -3.93912,0.85995 -4.88198,0.07744 -1.4693,-1.219408 -12.01977,-1.836598 -19.14134,-1.119748 -4.11813,0.414528 -6.28315,-3.435613 -3.05411,-5.431266 0.92161,-0.569595 1.18536,-1.495326 0.58608,-2.05718 -0.59926,-0.561854 -1.34471,-0.307179 -1.65655,0.565946 -0.31182,0.873125 -2.18709,1.5875 -4.16726,1.5875 -8.18322,0 -10.24739,3.530666 -3.54354,6.061083 5.16873,1.950982 3.70403,3.5822 -5.09287,5.671842 -3.63089,0.862496 -6.60162,2.004427 -6.60162,2.537626 0,2.007037 -2.53177,0.843694 -5.96813,-2.742345 -4.33175,-4.520426 -12.62636,-8.212683 -13.88277,-6.179766 -0.47101,0.762107 0.67846,2.503706 2.55439,3.87022 2.69885,1.96598 3.10444,2.853675 1.94314,4.252957 -1.99953,2.409268 -4.95006,2.221697 -7.1433,-0.454113 -1.00193,-1.222375 -1.49684,-1.568009 -1.0998,-0.768077 1.09537,2.206914 -2.36421,2.981686 -7.26805,1.627674 -2.5403,-0.701412 -5.62766,-0.750412 -7.34125,-0.116509 -1.6265,0.60168 -4.9827,0.635665 -7.45825,0.07553 -2.47553,-0.560142 -5.8721,-0.675815 -7.5479,-0.257053 -2.00655,0.501412 -2.75835,0.294465 -2.20179,-0.606078 0.46482,-0.752105 0.20042,-1.765924 -0.58756,-2.252927 -1.57845,-0.975535 -3.90227,1.876983 -2.87853,3.533429 1.52127,2.461464 -9.14564,5.21561 -18.96019,4.895433 -6.35299,-0.207252 -10.56176,0.230584 -11.75775,1.223155 -1.14515,0.950396 -4.04058,1.300485 -7.51594,0.908761 -3.87028,-0.436236 -6.5216,-0.02894 -8.4067,1.291434 -1.51443,1.060746 -4.35535,2.115137 -6.31316,2.343089 -1.95781,0.227954 -5.4568,1.571117 -7.77555,2.984809 -2.31875,1.413696 -5.4318,2.572486 -6.9179,2.575096 -1.4861,0.003 -3.41345,0.86199 -4.283,1.90974 -0.86956,1.04775 -2.54236,1.905 -3.71733,1.905 -1.17497,0 -4.32008,2.00025 -6.98912,4.445 -2.66905,2.44475 -4.42525,4.445 -3.90268,4.445 2.81237,0 -2.95977,3.98851 -7.44631,5.14535 -5.69098,1.46741 -13.21948,4.62098 -15.67937,6.56783 -1.17038,0.92629 -0.88301,2.10705 1.19778,4.92147 2.12048,2.86811 2.39602,4.04437 1.22231,5.21808 -5.63542,5.63542 -1.33884,8.08443 13.08972,7.46102 8.89063,-0.38415 9.13749,-0.36852 18.97603,1.20107 4.84881,0.77356 9.81186,1.93943 11.029,2.59083 1.21714,0.6514 3.35982,1.18435 4.76152,1.18435 2.68109,0 4.78187,2.14991 3.42582,3.50595 -0.77804,0.77805 -15.35624,2.93268 -22.95237,3.39234 -6.94447,0.4202 -9.525,1.90701 -9.525,5.48794 0,8.395232 -10.25742,12.313812 -13.28781,5.076272 -0.95051,-2.27012 -2.08075,-4.477282 -2.51166,-4.904792 -1.2001,-1.19066 -14.14979,2.1492 -19.84699,5.118752 -2.78947,1.45394 -5.84575,2.64354 -6.79175,2.64354 -2.6397,0 -15.98179,11.4769 -15.98179,13.74759 0,5.73793 7.6971,10.38241 17.20632,10.38241 7.68518,0 9.75419,0.48856 13.40337,3.16497 5.98177,4.3872 11.88093,6.99503 15.82341,6.99503 2.26662,0 3.54744,0.81354 3.99892,2.54 0.49618,1.89742 1.7323,2.54 4.88615,2.54 2.32206,0 4.54496,0.5227 4.93979,1.16153 0.98815,1.59887 -7.16477,6.45405 -10.87786,6.47792 -4.35498,0.0281 -6.31321,1.56171 -4.88251,3.82407 2.86303,4.52728 1.44631,5.86887 -7.98509,7.56167 -5.06413,0.90894 -10.47327,2.42935 -12.02032,3.37872 -1.54705,0.94935 -3.83845,1.72609 -5.092,1.72609 -1.25356,0 -2.99301,1.143 -3.86545,2.54 -0.87244,1.397 -2.61707,2.54 -3.87695,2.54 -6.07007,0 -12.88936,4.95458 -12.11923,8.80526 0.66707,3.33535 0.38265,3.74001 -2.89186,4.11445 -3.23697,0.37016 -3.58849,0.84754 -3.23426,4.39228 0.21858,2.18735 -0.49579,6.11478 -1.5875,8.72761 -5.61815,13.44613 -0.0352,28.5704 10.54646,28.5704 1.1789,0 3.32605,0.82834 4.77145,1.84073 1.44541,1.0124 3.71569,1.85999 5.04509,1.88352 9.5523,0.16912 10.87936,0.4182 11.55087,2.16813 0.506,1.31861 1.70606,1.67298 3.84633,1.13579 3.29892,-0.82798 7.97487,0.1513 7.97487,1.67015 0,2.25786 -11.43423,4.30808 -15.23843,2.73233 -4.18975,-1.73545 -11.69857,-0.83444 -13.76156,1.65131 -2.33484,2.81331 -0.26446,12.62852 2.51074,11.90279 1.29759,-0.33932 2.35925,-0.1222 2.35925,0.48251 0,0.6047 3.85763,3.06328 8.5725,5.46352 4.71487,2.40022 10.8585,5.52798 13.6525,6.95057 6.68912,3.40582 13.30295,5.05391 24.0988,6.00517 13.89223,1.22406 15.07463,2.30309 9.9238,9.05618 -3.4424,4.51321 -3.4424,4.51321 -13.6525,3.71569 -13.97326,-1.09147 -37.67106,-1.16107 -40.33806,-0.11849 -6.88234,2.69048 -2.29251,15.69816 6.53506,18.5205 2.49859,0.79884 5.86719,2.66075 7.48576,4.13758 2.10303,1.91886 4.64004,2.69368 8.89,2.71507 3.39894,0.0171 7.85171,1.06806 10.39214,2.45275 5.88625,3.20838 13.22454,4.84476 20.73,4.62262 4.19554,-0.1242 7.44397,0.63391 10.31206,2.40649 2.90378,1.79462 5.34812,2.35553 7.97531,1.83009 5.07756,-1.0155 13.75226,3.38954 14.78602,7.50842 0.53621,2.13642 2.45447,3.6908 6.42713,5.20798 9.83126,3.75459 12.69212,6.7215 10.17928,10.55659 -1.15131,1.7571 -1.50198,2.82929 -0.77929,2.38264 0.72268,-0.44665 1.67295,-0.23126 2.11168,0.47864 0.43874,0.70989 2.21587,1.29071 3.94918,1.29071 1.73331,0 4.73061,1.16751 6.66067,2.59446 4.98153,3.68301 19.02865,6.80317 22.8302,5.07107 2.63879,-1.20232 2.86187,-1.07278 2.12898,1.23636 -1.07324,3.38146 5.64225,6.33811 14.39588,6.33811 5.09502,0 6.9956,0.68477 10.51829,3.78972 5.08311,4.4803 7.55998,4.86622 7.14212,1.11277 -0.41702,-3.74603 2.18679,-4.31818 5.3331,-1.17186 1.44065,1.44065 3.73398,2.61937 5.09627,2.61937 1.36229,0 2.98295,0.90427 3.60145,2.00948 0.74547,1.33207 2.37222,1.84082 4.82561,1.50914 3.70105,-0.5003 3.70105,-0.5003 -0.74395,-2.59473 -4.445,-2.09445 -4.445,-2.09445 6.11599,-2.14416 10.67065,-0.0502 15.47401,1.24882 15.47401,4.18486 0,2.89922 8.04682,4.86113 14.1582,3.45193 11.26622,-2.59783 13.72234,-1.91046 8.65383,2.42189 -4.39704,3.75839 -4.39704,3.75839 -0.52354,3.81 2.13043,0.0285 4.54215,-0.61703 5.35937,-1.43425 z M -113.2835,331.53618 c -3.04064,-1.77019 -1.58076,-3.69332 2.80366,-3.69332 3.60003,0 7.12929,2.68792 5.57287,4.24435 -1.242,1.24199 -5.81318,0.9413 -8.37653,-0.55103 z m -7.27579,32.91918 c 0,-2.79365 -4.19827,-6.1325 -7.71099,-6.1325 -4.20126,0 -5.38252,2.3428 -2.72145,5.3975 2.18529,2.50854 10.43244,3.08958 10.43244,0.735 z m 270.38579,-1.20937 c 2.66617,-2.48392 2.7326,-2.85902 0.8412,-4.75041 -2.67598,-2.67598 -8.75731,0.32792 -9.37783,4.63222 -0.54478,3.77891 4.53186,3.84919 8.53663,0.11824 z m -303.19841,-22.01612 c 1.76276,-3.86881 -0.85712,-8.46156 -6.94388,-12.17289 -7.01611,-4.278 -13.58349,-2.72974 -13.58349,3.20229 0,9.0181 17.10074,16.49124 20.52737,8.9706 z M 223.94651,90.863678 c 1.2359,-1.23588 -0.46099,-4.32083 -2.3767,-4.32083 -0.97301,0 -1.76912,0.762 -1.76912,1.69333 0,2.81283 2.42415,4.34919 4.14582,2.6275 z M -3.01591,6.422189 c 1.30675,-2.114346 -1.04622,-3.704008 -4.24455,-2.867624 -2.26262,0.591688 -2.55807,1.11517 -1.4101,2.498404 1.74087,2.097613 4.47607,2.276208 5.65465,0.36922 z"
+         id="path12870"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#596557;stroke-width:1.26999998"
+         d="m -20.07554,445.76095 c -1.22979,-0.45479 -6.1966,-1.20329 -11.03736,-1.66332 -18.39228,-1.74788 -51.5415,-10.63402 -71.03139,-19.04102 -2.0955,-0.9039 -8.382,-3.54006 -13.97,-5.85813 -5.588,-2.31807 -12.02785,-5.81087 -14.31079,-7.7618 -2.28294,-1.95092 -5.71194,-3.9092 -7.62,-4.35173 -4.52141,-1.04863 -23.22791,-13.6095 -30.53682,-20.50459 -3.14679,-2.96863 -6.25775,-5.3975 -6.91325,-5.3975 -1.06452,0 -15.16697,-12.7982 -33.50098,-30.40267 -6.71165,-6.44459 -7.4964,-7.49515 -25.67175,-34.36733 -12.50952,-18.49528 -20.5114,-35.31432 -20.5114,-43.11268 0,-3.51601 -1.55588,-8.49472 -4.61552,-14.76935 -3.33621,-6.8418 -4.87453,-12.01234 -5.54994,-18.6542 -0.51392,-5.05382 -1.99866,-12.61778 -3.29942,-16.80878 -3.68456,-11.87147 -3.53932,-55.23161 0.20631,-61.595 5.13624,-8.72585 7.38294,-17.88583 7.63172,-31.115 0.27934,-14.85415 4.10491,-26.54852 13.86154,-42.373312 3.15442,-5.11632 5.73531,-9.83229 5.73531,-10.47992 0,-4.52491 11.42114,-24.19696 17.23386,-29.68404 0.74738,-0.7055 2.25815,-2.71148 3.35728,-4.45773 4.02714,-6.39815 16.0082,-19.152464 24.7136,-26.308609 4.91525,-4.040516 12.0201,-9.99732 15.78854,-13.237343 7.45019,-6.405504 23.39024,-17.604049 25.05768,-17.604049 0.56995,0 2.36928,-1.418915 3.9985,-3.153146 1.62923,-1.73423 7.2439,-5.240476 12.47706,-7.791659 5.23316,-2.551181 11.96439,-6.291699 14.9583,-8.312262 2.9939,-2.020561 9.90897,-5.697488 15.36681,-8.170947 5.45785,-2.47346 12.78086,-5.869125 16.27336,-7.545923 3.4925,-1.676797 9.49325,-3.888454 13.335,-4.914792 3.84175,-1.026339 12.1285,-3.369435 18.415,-5.206882 6.2865,-1.837447 13.43025,-3.829331 15.875,-4.426411 7.91685,-1.933527 31.80394,-3.819626 47.52807,-3.752763 50.4532,0.214538 109.06002,17.572271 147.0928,43.564853 4.36927,2.986069 11.41446,7.275137 15.656,9.531264 30.50936,16.228316 79.6043,69.596149 85.15898,92.570609 0.46449,1.92118 1.83181,4.92181 3.03847,6.66806 8.7844,12.71248 17.05609,36.736722 14.97893,43.504722 -1.06026,3.45464 -0.92748,5.97536 0.59365,11.2701 1.07764,3.7511 2.63456,9.35062 3.45982,12.44336 0.82524,3.09276 2.4472,7.06791 3.60435,8.83369 3.85174,5.87765 2.95449,49.47928 -1.49274,72.53813 -14.39527,74.63951 -64.33331,141.94502 -129.11835,174.02314 -3.84175,1.90223 -9.93396,5.19957 -13.53825,7.32742 -3.60429,2.12786 -10.52144,5.30023 -15.37145,7.04972 -4.85001,1.74949 -12.47379,4.73308 -16.94174,6.63021 -29.65911,12.59345 -103.81999,22.98616 -120.36479,16.86761 z m 6.00575,-54.73559 c 0.87312,-0.87313 3.13055,-1.69354 5.01649,-1.82313 1.88595,-0.12959 6.35512,-0.62378 9.93149,-1.09822 4.28653,-0.56864 6.80066,-0.38015 7.37745,0.55313 1.4194,2.29661 3.05833,1.62645 3.49423,-1.4288 0.46557,-3.26327 3.94951,-4.35128 7.87526,-2.45938 8.35223,4.0251 24.97146,2.53436 22.53338,-2.02124 -1.05729,-1.97554 -0.94475,-2.92475 0.48771,-4.11359 2.88182,-2.3917 5.48861,-0.70454 4.76445,3.08362 -0.7303,3.82032 3.25284,4.70438 6.04383,1.34143 0.9224,-1.11143 2.60379,-1.61661 3.95413,-1.18802 1.29214,0.41011 3.06883,0.2896 3.9482,-0.26778 0.87938,-0.55739 4.45637,-1.1229 7.94887,-1.25668 3.8389,-0.14705 8.86109,-1.50708 12.7,-3.43922 3.4925,-1.75778 8.20737,-3.87025 10.4775,-4.69438 2.93516,-1.06554 4.1275,-2.2977 4.1275,-4.26532 0,-3.37392 2.25242,-5.81492 5.3657,-5.81492 1.27473,0 6.28967,-2.00025 11.1443,-4.445 4.85462,-2.44475 9.99521,-4.445 11.42353,-4.445 1.42832,0 4.9405,-1.47943 7.80485,-3.28762 4.10615,-2.59209 6.13809,-3.1131 9.60478,-2.46274 2.87765,0.53985 5.34792,0.20861 7.14935,-0.95865 1.51387,-0.98093 5.32423,-2.17481 8.46748,-2.65307 9.24441,-1.40658 18.242,-4.70789 20.75573,-7.61545 2.01317,-2.32862 2.23367,-2.37766 1.57574,-0.35046 -0.68805,2.11995 -0.22092,2.27974 4.64427,1.5887 2.97509,-0.42258 6.83801,-0.76127 8.58426,-0.75262 8.21781,0.0406 11.89081,-3.84988 12.00683,-12.71809 0.0532,-4.06442 0.37961,-4.445 3.81235,-4.445 3.0072,0 4.4855,-1.08677 7.42965,-5.46184 2.02151,-3.00401 6.04712,-6.94709 8.94582,-8.76241 6.92959,-4.33966 17.26147,-14.46395 17.30273,-16.95507 0.0363,-2.19152 -2.78451,-8.78933 -3.77738,-8.83522 -4.91903,-0.22741 -3.94961,-3.03129 3.40681,-9.85367 8.79856,-8.15981 11.95826,-15.81362 7.05738,-17.09523 -1.82401,-0.477 -2.66562,-2.1976 -3.28491,-6.71581 -1.07921,-7.87364 -2.57374,-9.60136 -7.46014,-8.62408 -4.0667,0.81333 -4.72861,-1.68714 -0.91355,-3.45105 1.00617,-0.46522 3.10752,-1.50617 4.66967,-2.31323 1.56215,-0.80707 3.73493,-1.4674 4.82839,-1.4674 13.46384,0 18.70786,-17.7347 10.41477,-35.22158 -0.92834,-1.95749 -0.9407,-3.72863 -0.0399,-5.715 2.9236,-6.44693 -8.25316,-17.48342 -17.70566,-17.48342 -6.72839,0 -9.75206,-2.86785 -4.71067,-4.46792 4.32868,-1.37387 2.48727,-5.03073 -2.63128,-5.22545 -9.93158,-0.37784 -18.88001,-3.78857 -25.07349,-9.55683 -7.21329,-6.71809 -10.25541,-7.95223 -24.74141,-10.03732 -18.86641,-2.71559 -19.0126,-5.96203 -0.60469,-13.42801 12.60527,-5.11252 12.09552,-5.09015 20.31836,-0.89177 11.71807,5.98293 26.2927,5.60678 39.32779,-1.01499 5.32895,-2.70709 6.68575,-9.49948 2.89788,-14.50747 -1.02552,-1.35588 -1.88277,-3.41668 -1.905,-4.57957 -0.0396,-2.07775 -11.52921,-11.464472 -13.59155,-11.104062 -3.54548,0.61964 -4.81707,-0.23294 -2.72236,-1.82527 4.19105,-3.18588 -2.51583,-9.34727 -8.25686,-7.5853 -5.86337,1.79953 -6.48622,0.99107 -2.98951,-3.88028 6.53927,-9.11001 3.33275,-26.19576 -4.91622,-26.19576 -0.88746,0 -4.00771,-2.00025 -6.93388,-4.445 -2.92617,-2.44475 -5.80831,-4.445 -6.40472,-4.445 -0.59643,0 -2.62606,-1.42875 -4.51029,-3.175 -1.88424,-1.74625 -4.12971,-3.175 -4.98994,-3.175 -0.86022,0 -2.19873,-0.76476 -2.97446,-1.69946 -0.77573,-0.9347 -3.84608,-2.41859 -6.823,-3.29753 -2.97693,-0.87894 -7.19455,-2.68594 -9.37249,-4.015545 -2.17794,-1.329606 -5.03544,-2.421231 -6.35,-2.425833 -1.31456,-0.0046 -4.1046,-1.143837 -6.2001,-2.531632 -2.0955,-1.387796 -5.85132,-2.52703 -8.34626,-2.531632 -2.49494,-0.0046 -7.35269,-0.914411 -10.795,-2.021795 -3.44231,-1.107384 -7.03747,-2.257147 -7.98926,-2.555028 -1.38508,-0.433484 -1.41742,-0.918889 -0.162,-2.431574 1.31934,-1.589701 1.0513,-1.889972 -1.68702,-1.889972 -1.79055,0 -3.63284,0.610461 -4.09396,1.356581 -0.46114,0.746119 -1.78073,0.973502 -2.93245,0.505295 C 117.24775,17.574196 98.01075,15.315509 91.76098,15.279032 89.28885,15.264602 86.23937,14.118076 84.38527,12.505953 80.40359,9.043929 72.49912,6.651456 69.2465,7.923848 c -1.36445,0.53376 -4.05246,1.010643 -5.97333,1.059737 -3.75967,0.09609 -4.85543,3.104508 -1.5875,4.358527 1.91844,0.736175 2.72593,4.637216 0.9525,4.601614 -3.36326,-0.06751 -11.04076,-3.894738 -10.64017,-5.30411 0.25976,-0.913972 0.75505,-2.661894 1.10062,-3.884269 0.92114,-3.258369 -1.59676,-2.709457 -3.93401,0.857629 -1.40664,2.146801 -1.57516,3.227795 -0.55621,3.567451 2.12767,0.709223 -0.009,3.51242 -2.67756,3.51242 -6.54393,0 -9.98751,-3.545825 -8.94908,-9.214782 1.11905,-6.109052 -0.16337,-8.565218 -4.47208,-8.565218 -7.03017,0 -18.8576,6.347483 -17.65703,9.476092 C 16.21966,11.95135 8.90489,15.163482 6.76492,11.9405 5.35347,9.814739 4.11005,9.348473 1.33348,9.903784 -1.99674,10.569829 -2.29813,10.303125 -2.67588,6.355907 -3.00707,2.895059 -3.56472,2.167425 -5.62433,2.508609 c -1.397,0.23142 -4.40267,0.723188 -6.67927,1.09282 -2.2766,0.369631 -4.45344,1.180413 -4.83745,1.801738 -0.384,0.621325 -2.29037,1.12968 -4.23639,1.12968 -5.361,0 -8.82256,4.320856 -5.9789,7.463085 1.88488,2.082752 1.91406,2.476183 0.2316,3.121805 -1.14822,0.44061 -2.71343,-0.386816 -3.87864,-2.050381 -2.67486,-3.818906 -6.55937,-1.532061 -5.94391,3.49923 0.30376,2.483107 -0.0629,3.319128 -1.27,2.895783 -0.97622,-0.342374 -1.62212,-2.248315 -1.52067,-4.48723 0.16087,-3.549917 -0.17405,-3.893716 -3.81,-3.910908 -2.1925,-0.01041 -6.16554,-0.407905 -8.82897,-0.883416 -4.23942,-0.756873 -4.93019,-0.529701 -5.54562,1.823733 -0.38667,1.478565 -1.16934,2.688299 -1.73931,2.688299 -0.56996,0 -1.36488,1.035266 -1.76648,2.300591 -1.59522,5.026119 -19.87372,2.229186 -24.00825,-3.673691 -1.15003,-1.641891 -2.15277,-2.113381 -2.68008,-1.260174 -0.4634,0.749801 -1.90519,1.363274 -3.20397,1.363274 -1.29878,0 -4.59225,2.143125 -7.3188,4.7625 -5.6264,5.405214 -19.37988,7.935458 -19.37988,3.565341 0,-2.291839 -2.24321,-2.510665 -7.04036,-0.686793 -2.62623,0.998493 -3.07914,1.672489 -1.9987,2.974352 2.06103,2.483388 0.60535,3.174104 -5.08181,2.411295 -3.00448,-0.402985 -6.31799,-0.01257 -8.13163,0.958025 -2.47866,1.326539 -3.43123,1.330815 -5.00768,0.02248 -1.63215,-1.354566 -2.87576,-0.854424 -7.69521,3.094751 -5.78503,4.74039 -13.51155,7.83496 -15.89085,6.36447 -2.69134,-1.66334 -17.73375,8.16393 -17.73375,11.58554 0,2.35701 -1.06594,3.65778 -4.1275,5.03678 -2.27013,1.02253 -5.2705,2.55223 -6.6675,3.39932 -1.397,0.84711 -3.874,2.31204 -5.50443,3.25541 -2.73007,1.57961 -2.80538,1.89098 -0.9525,3.93837 2.71949,3.00501 2.5302,4.76639 -0.6002,5.585 -2.29791,0.60092 -2.5497,1.46181 -2.09316,7.15663 0.63498,7.92063 1.10278,8.45127 5.12636,5.81491 4.14553,-2.71625 13.98958,-2.20361 14.55059,0.75773 0.31975,1.68783 -1.78862,2.33928 -11.90398,3.67809 -10.09864,1.33661 -12.40243,2.04719 -12.90823,3.9814 -0.33863,1.29492 -1.19593,2.3544 -1.90511,2.3544 -0.70919,0 -3.0985,1.91818 -5.30959,4.26262 -2.21109,2.344442 -5.42182,5.008422 -7.13496,5.919962 -5.15563,2.74323 -15.17979,11.68352 -15.17979,13.53846 0,2.66933 6.37651,11.68774 8.71444,12.32498 1.14431,0.3119 5.08094,1.29544 8.74806,2.18563 3.66712,0.89019 6.6675,2.25916 6.6675,3.04216 0,0.78299 3.85762,2.72802 8.5725,4.32229 13.52072,4.57184 18.70607,9.0139 10.52218,9.0139 -2.46553,0 -4.23457,0.87681 -5.12468,2.54 -0.8243,1.54022 -2.65916,2.54 -4.66155,2.54 -1.8162,0 -4.63965,1.28588 -6.27433,2.8575 -6.9617,6.69317 -8.00206,7.28155 -10.94884,6.19208 -4.43445,-1.63948 -6.05639,-0.48264 -5.21583,3.72016 0.57938,2.89691 0.22364,3.86208 -1.63711,4.4418 -10.89037,3.39284 -14.71123,6.60029 -11.26734,9.45846 3.38561,2.80981 2.36403,5.48931 -1.77707,4.66109 -4.39507,-0.87901 -5.01789,1.48661 -6.02778,22.89474 -0.63806,13.52589 3.06482,19.43417 12.17993,19.43417 1.3863,0 3.11194,1.10502 3.83475,2.4556 0.72281,1.35058 2.88604,2.64621 4.80718,2.87918 3.41363,0.41395 3.37132,0.4881 -1.86205,3.26354 -13.447,7.13145 -4.64245,29.50168 11.61136,29.50168 10.13922,0 17.36554,14.04309 7.70282,14.96909 -13.80354,1.3228 -16.89077,2.6919 -17.76715,7.87917 -1.07668,6.37279 2.87974,12.14141 9.73612,14.19564 2.627,0.78707 6.03935,2.77542 7.583,4.41857 1.86975,1.99025 4.23505,2.98753 7.0857,2.98753 2.80455,0 5.45893,1.09406 7.70314,3.17501 2.68673,2.49128 4.79432,3.18369 9.78707,3.21538 3.50457,0.0222 7.44543,0.85975 8.77258,1.86432 1.32527,1.00316 2.88718,1.52877 3.47089,1.16801 0.58372,-0.36076 3.16961,-0.26039 5.74643,0.22302 4.68512,0.87893 4.68512,0.87893 0.3391,2.92283 -5.11089,2.4036 -6.50587,8.41007 -3.14462,13.53998 4.54543,6.93721 18.82322,10.56643 23.8111,6.05247 2.59338,-2.34699 4.02801,-1.5626 6.60186,3.60958 1.20096,2.41333 2.82309,3.10996 8.72601,3.74739 10.71498,1.15704 15.71803,6.20202 6.1505,6.20202 -5.63806,0 -7.23547,2.17464 -4.48349,6.10364 4.08828,5.83683 13.46788,4.56345 12.27691,-1.66671 -0.57464,-3.00601 -0.30748,-3.16693 5.25765,-3.16693 4.36493,0 5.86305,0.48676 5.86305,1.905 0,2.13536 3.23234,2.59713 4.445,0.635 2.58185,-4.17753 12.33724,0.44699 11.81844,5.60249 -0.43059,4.27896 8.15692,7.10086 15.42763,5.06958 3.80351,-1.06262 9.0572,-0.69264 10.85393,0.76437 2.47978,2.01089 16.05707,6.19398 17.85936,5.50237 3.46177,-1.32841 7.84074,0.77662 9.38845,4.51312 1.54711,3.73506 8.47764,6.44307 11.58098,4.5251 2.28142,-1.41 7.32181,0.58828 8.16215,3.23591 0.91655,2.88783 3.19379,3.27932 5.52356,0.94956 z m -3.65217,-6.09354 c -0.86745,-1.62082 -2.20043,-2.34754 -3.53718,-1.92839 -1.88971,0.59254 -5.32015,-3.88877 -5.32015,-6.94991 0,-2.51382 6.24461,-3.55341 7.00242,-1.16576 0.43278,1.36356 2.18249,2.82947 3.88828,3.2576 4.87106,1.22255 5.86609,9.3875 1.14403,9.3875 -0.98195,0 -2.41178,-1.17047 -3.1774,-2.60104 z m -69.72317,-16.37951 c -4.92962,-1.23929 -4.51964,-3.44128 0.53093,-2.85164 4.69682,0.54832 6.81923,2.12652 4.13012,3.0711 -0.86911,0.30528 -2.96659,0.20653 -4.66105,-0.21946 z m -97.03749,-86.00613 c -1.61009,-1.61009 -0.83681,-2.96333 1.69334,-2.96333 1.41111,0 2.54,0.84668 2.54,1.905 0,1.93347 -2.68657,2.6051 -4.23334,1.05833 z m -8.17562,-183.541452 c 0.78495,-2.35486 3.51896,-2.74544 3.51896,-0.50271 0,1.10596 -0.90488,2.010832 -2.01083,2.010832 -1.10596,0 -1.78462,-0.678662 -1.50813,-1.508122 z m 11.74718,-12.50521 c -1.55649,-2.51845 1.3697,-4.15685 4.46527,-2.50015 1.5434,0.826 3.92339,1.20966 5.28886,0.85258 1.54671,-0.40447 2.29844,-0.0884 1.99396,0.83834 -0.65069,1.98056 -10.60059,2.66593 -11.74809,0.80923 z M 149.71122,363.35338 c 3.26263,-3.0396 3.52256,-4.82509 0.85166,-5.85002 -2.52893,-0.97044 -9.11952,1.80047 -10.05005,4.22538 -1.80334,4.69941 4.67067,5.84287 9.19839,1.62464 z"
+         id="path12868"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#4b554c;stroke-width:1.26999998"
+         d="m -16.94502,445.77605 c 0.3716,-0.60126 -1.21547,-1.09694 -3.52682,-1.10151 -20.23195,-0.04 -56.69895,-9.5342 -89.92745,-23.41255 -15.8823,-6.63348 -17.78,-7.55605 -17.78,-8.6438 0,-0.60272 -3.28612,-2.59205 -7.3025,-4.42073 -4.01637,-1.82869 -8.73124,-4.26898 -10.47749,-5.42289 -12.26678,-8.10574 -16.67586,-11.36773 -22.64485,-16.75342 -3.72341,-3.35956 -7.39449,-6.10829 -8.15795,-6.10829 -0.76346,0 -6.03742,-4.71488 -11.71992,-10.4775 -5.68249,-5.76263 -12.59518,-12.05958 -15.36153,-13.99322 -2.76635,-1.93364 -6.91091,-5.93414 -9.21013,-8.89 -7.1359,-9.17382 -18.75379,-25.53049 -20.64099,-29.06018 -0.9898,-1.85124 -3.89365,-6.42324 -6.45302,-10.16 -7.20302,-10.51665 -13.77364,-25.44855 -14.53725,-33.03628 -0.37516,-3.72781 -2.65547,-11.12219 -5.06736,-16.43196 -2.94939,-6.49308 -4.77886,-12.93856 -5.58736,-19.685 -0.66116,-5.51698 -2.14851,-12.88837 -3.30523,-16.38087 -3.56835,-10.774 -3.01945,-52.9057 0.78646,-60.3659 5.20088,-10.19457 7.86468,-21.43462 7.66529,-32.3441 -0.23988,-13.12453 4.75963,-28.16386 13.83049,-41.604322 2.04588,-3.03143 4.90238,-8.51195 6.34778,-12.17893 10.01501,-25.40832 42.47727,-60.907959 75.73635,-82.822759 6.23382,-4.107535 12.72521,-8.677549 14.42531,-10.155586 1.70009,-1.478039 6.80951,-4.44421 11.35424,-6.591494 4.54473,-2.147284 10.58324,-5.465134 13.41891,-7.373002 6.82596,-4.592581 35.23566,-18.095842 43.25575,-20.559657 3.4925,-1.072914 9.779,-2.890628 13.97,-4.039365 4.191,-1.148736 11.08789,-3.199247 15.32641,-4.556689 15.68118,-5.022107 54.56749,-7.755739 75.47858,-5.305998 25.25981,2.959193 45.85767,6.694531 56.94703,10.327111 6.5241,2.137128 12.31374,3.885687 12.86585,3.885687 0.5521,0 8.23729,3.429 17.07819,7.62 8.84092,4.191 16.44717,7.62 16.90281,7.62 0.95509,0 15.78529,8.612621 25.11111,14.583253 3.4925,2.235989 10.922,6.840148 16.51,10.231463 10.42222,6.325168 18.56865,12.605323 27.94141,21.540285 2.93091,2.794 7.27283,6.464563 9.64875,8.156808 13.37006,9.522828 45.59484,54.065193 45.59484,63.022973 0,0.89294 1.98456,4.63693 4.41015,8.32 8.22492,12.488912 14.46042,32.786012 12.44959,40.524572 -1.10087,4.23662 -0.33692,8.85201 2.36049,14.26092 0.60487,1.2129 1.10535,3.4989 1.11217,5.08 0.006,1.5811 1.54055,6.01798 3.40828,9.85973 3.30963,6.8076 3.39721,7.59784 3.4484,31.115 0.0528,24.27729 -0.1946,26.44996 -6.36418,55.88 -5.70149,27.19717 -27.65943,74.0287 -42.27808,90.17001 -1.89784,2.0955 -5.04244,6.20095 -6.98803,9.12321 -1.94557,2.92227 -7.05784,8.57742 -11.3606,12.567 -4.30275,3.98959 -12.26852,11.44673 -17.70169,16.57142 -5.43318,5.12469 -13.43418,11.74382 -17.78,14.70916 -9.38606,6.40454 -27.65434,17.51421 -28.79963,17.51421 -0.44424,0 -5.0679,2.55266 -10.27479,5.67259 -5.20689,3.11992 -12.89607,6.88474 -17.08707,8.36625 -4.191,1.48152 -12.192,4.59756 -17.78,6.92451 -5.588,2.32696 -13.30325,5.11744 -17.145,6.20105 -3.84175,1.08361 -10.9855,3.08624 -15.875,4.45028 -10.60757,2.95923 -30.98404,6.48532 -37.47713,6.48532 -2.55449,0 -4.98732,0.34278 -5.40628,0.76174 -1.15318,1.15318 -30.79482,1.67435 -32.57717,0.57279 -0.85485,-0.52832 -3.78435,-0.55278 -6.51002,-0.0544 -2.72567,0.49842 -4.65173,0.41429 -4.28012,-0.18698 z M 12.7907,400.28312 c 3.90647,-1.05951 15.28388,-2.2554 23.83973,-2.50583 1.79473,-0.0526 3.43134,-1.2841 4.33135,-3.25943 0.79565,-1.74625 1.9465,-3.175 2.55746,-3.175 0.61096,0 0.51768,1.10831 -0.20729,2.46292 -2.39154,4.46862 7.67486,4.05067 12.02375,-0.49922 2.50335,-2.61906 7.72615,-4.47913 13.94526,-4.96654 5.27436,-0.41337 7.13102,-1.29188 11.17519,-5.28776 3.65658,-3.61292 5.20949,-4.43247 6.31124,-3.33072 2.0563,2.05631 7.96588,1.81847 9.80249,-0.3945 2.57091,-3.09776 6.36537,-3.6409 8.96978,-1.28392 2.28333,2.0664 2.59505,1.96812 6.57649,-2.07314 2.29875,-2.33329 4.62407,-4.10986 5.16739,-3.94795 1.04459,0.3113 1.50711,-0.76675 2.73448,-6.37357 0.48196,-2.20169 2.26094,-4.23922 4.86858,-5.57619 2.26,-1.15872 5.69755,-3.43238 7.63898,-5.05256 1.94144,-1.62019 5.65619,-3.26996 8.255,-3.66616 7.37418,-1.12423 19.12642,-3.99801 28.85511,-7.05596 5.32027,-1.67229 14.24459,-3.1317 22.225,-3.6345 16.11978,-1.01564 24.09843,-6.31629 21.82068,-14.49667 -0.15948,-0.5728 2.00937,-3.65363 4.81968,-6.84631 2.8103,-3.19267 5.11844,-6.72452 5.12918,-7.84856 0.0114,-1.21571 3.42825,-3.84457 8.4331,-6.4887 15.35422,-8.11184 21.33601,-20.01974 12.63543,-25.15321 -6.13363,-3.61893 -5.81576,-4.9584 2.80839,-11.83446 8.24987,-6.57764 10.42276,-18.45805 4.88776,-26.7241 -2.77551,-4.145 -1.68253,-6.48577 6.65737,-14.25774 3.07814,-2.86851 6.28563,-5.89204 7.12774,-6.71894 2.15031,-2.11142 3.51729,-13.85349 1.95306,-16.77629 -0.70701,-1.32106 -1.55216,-6.5467 -1.87811,-11.61255 -0.77763,-12.08605 -4.23628,-15.99405 -17.96569,-20.29981 -8.05425,-2.52594 -9.46329,-3.38131 -9.16093,-5.56127 0.41477,-2.99021 -1.44719,-3.70755 -15.34208,-5.91064 -8.43318,-1.33712 -9.99174,-2.06407 -15.90373,-7.41799 -7.83598,-7.09631 -10.726,-8.28489 -25.21581,-10.37052 -18.86641,-2.71559 -19.0126,-5.96203 -0.60469,-13.42801 12.67797,-5.14201 12.00078,-5.12269 20.96387,-0.59814 19.98092,10.08634 62.2422,-3.19487 47.93896,-15.06551 -1.0207,-0.8471 -2.24071,-2.77446 -2.71112,-4.28301 -1.20648,-3.86903 -10.3238,-14.525792 -13.21158,-15.442342 -1.32635,-0.42097 -2.75957,-1.8619 -3.18493,-3.20206 -0.42535,-1.34017 -2.45483,-4.02371 -4.50996,-5.96342 -3.60084,-3.39862 -5.04258,-7.97999 -2.51127,-7.97999 10.67446,0 -4.04316,-22.74607 -19.32116,-29.86083 -3.31696,-1.54466 -8.8646,-5.45913 -12.32809,-8.69882 -3.46349,-3.239694 -7.12034,-5.89035 -8.12633,-5.89035 -1.67017,0 -14.3676,-5.676328 -20.50474,-9.166542 -8.28204,-4.71003 -14.43033,-6.763727 -25.43063,-8.494517 C 130.27147,12.878764 120.91624,10.606681 118.94639,9.371048 118.18701,8.894712 116.9942,8.458666 116.2957,8.40206 115.5972,8.34546 113.3112,7.563864 111.2157,6.665193 108.88818,5.667018 105.00265,5.306709 101.22984,5.73919 93.53138,6.62167 86.90676,4.087731 85.20405,-0.390724 83.8447,-3.966105 82.64344,-4.391895 80.35946,-2.107912 c -1.17899,1.17899 -1.92781,0.965666 -3.3435,-0.9525 -2.0186,-2.73505 -3.9807,-1.65593 -4.74246,2.608259 -0.70168,3.927826 -5.41283,3.868638 -8.54031,-0.10729 -2.47552,-3.147118 -3.2083,-3.35923 -9.16705,-2.653505 -5.73088,0.678739 -6.55199,0.478148 -7.1327,-1.742471 -0.80859,-3.092096 -6.06304,-4.593644 -8.62739,-2.465427 -1.1055,0.917491 -3.44967,1.258332 -5.64062,0.820144 -2.06192,-0.412387 -6.27213,0.0095 -9.356,0.937677 -3.89833,1.173226 -6.30254,1.298244 -7.88932,0.410241 -5.4203,-3.033349 -10.70954,-3.305647 -12.57159,-0.647202 -1.77992,2.541179 -4.74038,3.132083 -20.1143,4.014779 -3.33381,0.191411 -6.73423,1.020779 -7.5565,1.843039 -2.23705,2.237057 -4.41902,1.807614 -5.15716,-1.015 -0.7557,-2.889809 -18.90524,-2.454748 -24.91283,0.597184 -2.9683,1.507946 -5.23312,1.174655 -13.46202,-1.981082 -5.24407,-2.011069 -8.20616,-2.140197 -9.63381,-0.419974 -0.8097,0.975636 -3.20938,1.773887 -5.33262,1.773887 -3.96227,0 -13.3391,4.724718 -18.31007,9.225919 -1.78562,1.616881 -3.78955,2.272539 -5.54369,1.813822 -1.5897,-0.415715 -5.76241,0.620208 -9.9035,2.458655 -3.93597,1.747384 -9.72806,3.402891 -12.87131,3.678904 -3.14325,0.276013 -7.4295,0.763856 -9.525,1.084095 -2.0955,0.320238 -4.26459,0.628682 -4.82022,0.685428 -0.55562,0.05674 -2.413,1.50595 -4.1275,3.22045 -1.7145,1.7145 -3.11727,2.538022 -3.11727,1.830048 0,-2.593315 -2.61396,0.434374 -5.11509,5.924697 -2.8067,6.161094 -3.27232,6.418673 -6.78892,3.755643 -4.97043,-3.763974 -27.25904,5.68674 -27.50824,11.66392 -0.0875,2.09837 -4.75474,6.53634 -9.57052,9.10039 -15.51167,8.25879 -20.62446,15.77467 -14.27902,20.99033 4.64269,3.81607 7.24237,12.25536 4.32736,14.04777 -1.18511,0.72872 -4.19776,3.18231 -6.69478,5.45243 -2.49703,2.27013 -5.56206,4.1275 -6.81118,4.1275 -3.0933,0 -19.5746,12.450542 -22.06735,16.670432 -3.33945,5.65322 -1.9258,11.26957 2.83658,11.26957 0.99047,0 3.75355,1.7145 6.14019,3.81 2.64288,2.32048 5.74954,3.81 7.94641,3.81 5.97146,0 11.54456,1.49287 11.54456,3.09246 0,0.82774 3.85762,2.80937 8.5725,4.40364 13.52072,4.57184 18.70607,9.0139 10.52218,9.0139 -2.46553,0 -4.23457,0.87681 -5.12468,2.54 -0.88448,1.65267 -2.65916,2.54 -5.08,2.54 -2.31748,0 -4.19385,0.88421 -4.97542,2.34459 -1.29922,2.42763 -12.40386,7.36946 -13.68372,6.0896 -1.38664,-1.38664 -5.47086,1.83132 -5.47086,4.31049 0,2.02825 -0.87455,2.49532 -4.67233,2.49532 -11.5819,0 -21.61837,10.37826 -17.90917,18.51907 1.03575,2.27322 1.03008,3.84688 -0.0214,5.93928 -2.69835,5.36965 -4.14774,17.51576 -2.72199,22.8106 1.38351,5.13796 15.01809,15.71117 18.56453,14.39622 0.82206,-0.3048 2.22923,0.33094 3.12706,1.41275 1.83112,2.20636 -0.85949,4.98658 -6.09915,6.3023 -3.60973,0.90642 -5.02216,6.38781 -2.39423,9.29162 1.08166,1.19525 1.96667,3.23432 1.96667,4.53131 0,3.0089 4.86446,8.23685 7.66415,8.23685 1.17777,0 4.26069,1.4382 6.85094,3.19598 2.59025,1.7578 5.2166,2.88263 5.83632,2.49961 1.44346,-0.89212 10.36596,2.80732 11.65213,4.83118 4.61607,7.26361 -2.11433,10.17375 -20.11048,8.6955 -2.76486,-0.22712 -4.89872,2.86795 -3.51777,5.10237 0.41994,0.67948 -0.21334,2.31486 -1.4073,3.63416 -3.83568,4.23838 3.3678,13.57296 12.71701,16.47924 2.794,0.86854 7.08025,3.41059 9.525,5.64899 6.85633,6.27763 10.831,8.42397 12.50319,6.75177 2.23683,-2.23682 7.37407,1.23758 11.01429,7.44914 3.87515,6.61246 4.64999,7.13862 8.43273,5.72643 2.64567,-0.98771 3.2558,-0.53258 5.51478,4.11374 2.67034,5.49239 9.05234,10.61117 14.52956,11.65369 1.78774,0.34027 5.82219,1.26153 8.96544,2.04727 3.14325,0.78572 9.07006,2.07817 13.17068,2.87213 6.50494,1.25947 7.19323,1.66136 5.3975,3.15168 -5.49965,4.5643 1.08634,12.82713 10.22402,12.82713 5.16322,0 5.61585,-0.27272 6.3233,-3.81 0.9277,-4.6385 2.85989,-4.8232 6.78201,-0.64831 2.87394,3.05919 2.89603,3.2437 0.6811,5.69117 -3.06727,3.3893 -0.49289,6.91161 4.33415,5.93008 3.36859,-0.68498 4.74129,-0.38012 19.98618,4.43868 9.11394,2.88086 9.11394,2.88086 11.43,0.15654 4.56834,-5.37361 13.04366,-2.86628 12.0977,3.57898 -0.64695,4.40795 1.22597,4.83876 3.85069,0.88573 2.64559,-3.98446 8.30618,-1.46279 9.43978,4.2052 0.69957,3.49786 1.18293,3.86742 4.25481,3.25305 2.63097,-0.5262 4.19257,0.0867 6.48433,2.54525 2.64718,2.8398 3.75108,3.15312 8.96862,2.54563 3.27243,-0.38101 6.12993,-0.18153 6.35,0.44329 0.22007,0.62483 5.54363,1.65952 11.83013,2.29931 15.96365,1.62466 18.44673,1.65485 23.49499,0.28566 z m -193.31208,-74.69271 c -3.71914,-4.33386 -3.65475,-5.31649 0.20449,-3.12018 3.97675,2.26318 4.689,3.45125 2.77734,4.63272 -0.60515,0.37401 -1.94697,-0.30664 -2.98183,-1.51254 z m 327.17189,41.1216 c 8.17841,-3.41715 10.29611,-12.20683 2.93801,-12.1944 -7.01675,0.0114 -14.36109,8.09211 -11.01724,12.12119 1.89592,2.28445 2.76807,2.29235 8.07923,0.0732 z"
+         id="path12866"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#564b38;stroke-width:1.26999998"
+         d="m -4.9893,445.86076 c 0,-0.43494 -6.44008,-1.15861 -14.31129,-1.60818 -32.2454,-1.84166 -99.84436,-21.90523 -109.4076,-32.47249 -1.14751,-1.26798 -4.49831,-3.30004 -7.44624,-4.5157 -6.52364,-2.69019 -6.17282,-2.4902 -17.04024,-9.71374 -4.84652,-3.22145 -10.77334,-7.8543 -13.17073,-10.2952 -2.39739,-2.44091 -6.69889,-5.89986 -9.55891,-7.68657 -2.86001,-1.78672 -9.71801,-7.58896 -15.24,-12.89391 -5.52198,-5.30494 -12.68461,-12.14764 -15.91694,-15.20601 -6.44615,-6.09923 -18.70746,-22.22077 -25.85762,-33.99841 -0.70697,-1.16451 -3.97444,-6.06063 -7.26104,-10.88026 -3.2866,-4.81962 -6.60232,-10.43908 -7.36827,-12.48769 -0.76594,-2.04861 -2.45191,-6.01075 -3.74658,-8.80475 -1.29467,-2.794 -2.7406,-8.509 -3.21316,-12.7 -0.48815,-4.32918 -2.2261,-10.08811 -4.02428,-13.335 -3.67382,-6.63361 -6.77995,-17.62705 -6.80346,-24.07932 -0.009,-2.47262 -1.17793,-8.20175 -2.59761,-12.73141 -4.54316,-14.4956 -3.18666,-57.81129 1.98959,-63.53098 1.04192,-1.1513 1.8944,-2.68822 1.8944,-3.41534 0,-0.72713 1.18785,-4.96838 2.63967,-9.42501 2.09883,-6.44274 2.48347,-9.93223 1.87734,-17.03123 -1.03587,-12.13211 2.77268,-23.46655 14.10537,-41.978302 3.02919,-4.94814 5.50762,-9.6467 5.50762,-10.44129 0,-4.52042 7.74795,-18.00008 16.30275,-28.36306 15.48869,-18.76245 20.95644,-24.631479 29.7486,-31.931864 18.08733,-15.018417 28.46179,-23.029991 34.07256,-26.312197 2.98512,-1.74625 7.22578,-4.719652 9.42368,-6.60756 2.1979,-1.887907 7.92351,-5.25034 12.72357,-7.472072 4.80007,-2.221732 11.32759,-5.841731 14.50561,-8.044443 3.17802,-2.202711 9.49297,-5.620061 14.03322,-7.594112 4.54025,-1.974052 10.541,-4.883022 13.335,-6.464381 2.794,-1.581358 9.36625,-4.166311 14.605,-5.744339 5.23875,-1.578029 15.52575,-4.742557 22.86,-7.032286 40.60164,-12.675696 101.62444,-9.767902 149.22499,7.110712 15.54417,5.511789 51.34231,23.143578 59.05499,29.086581 2.44475,1.883798 8.25754,5.528625 12.9173,8.099614 30.85982,17.026666 69.35628,56.086637 82.89236,84.105727 6.01538,12.45156 7.47367,15.37233 13.35761,26.75353 7.43785,14.386942 10.8951,26.677052 8.98795,31.951172 -1.73573,4.80012 -0.85007,12.0384 1.96472,16.05709 0.99002,1.41346 1.80564,4.01439 1.81246,5.77986 0.006,1.76545 1.48592,6.35316 3.28686,10.19491 13.61124,29.03517 -3.17068,108.34108 -33.4587,158.11501 -15.76431,25.90633 -67.56244,78.0615 -77.57431,78.10904 -0.46819,0.003 -4.28025,2.2864 -8.47125,5.07596 -4.191,2.78955 -8.06175,5.07374 -8.60167,5.07596 -0.53993,0.003 -5.4554,2.60345 -10.92327,5.78049 -5.46787,3.17705 -14.16005,7.2435 -19.31597,9.03655 -5.15592,1.79305 -12.5277,4.6708 -16.38174,6.39501 -8.58723,3.84172 -22.79979,8.19668 -38.12234,11.68132 -14.13695,3.215 -20.72942,4.10206 -35.8775,4.82753 -8.90587,0.42652 -16.1925,1.15131 -16.1925,1.61064 0,0.45934 -3.429,0.81401 -7.62,0.78818 -4.191,-0.0259 -7.62,-0.40285 -7.62,-0.83778 z m 15.24,-38.63564 c 18.83319,-1.17154 31.56397,-0.99343 37.465,0.52416 5.37867,1.38324 13.38958,-0.44492 21.59,-4.92705 2.0955,-1.14533 5.66716,-2.20555 7.93703,-2.35604 4.65238,-0.30844 14.28797,-4.90989 14.28797,-6.82319 0,-0.75318 -1.97537,-0.91967 -4.94966,-0.41717 -5.09108,0.86014 -8.41889,-1.16998 -5.18953,-3.16584 3.49005,-2.15697 16.62211,-3.99783 18.34123,-2.57109 1.23597,1.02577 3.32368,0.73857 7.98387,-1.09831 5.83177,-2.29866 6.46718,-2.33036 8.37954,-0.41799 1.91236,1.91235 2.34309,1.80693 5.68038,-1.39041 2.26451,-2.16954 6.6501,-4.2309 11.76057,-5.52784 7.98223,-2.02573 15.14359,-6.02928 15.14359,-8.466 0,-0.67476 2.33207,-3.22301 5.18238,-5.66276 2.85031,-2.43975 5.69366,-5.7807 6.31857,-7.42432 0.6644,-1.74751 2.17912,-2.98841 3.64787,-2.98841 2.98873,0 7.96579,-2.07084 14.06118,-5.85053 4.93347,-3.05918 15.24,-5.12488 15.24,-3.05449 0,0.70676 -0.52489,1.28502 -1.16642,1.28502 -1.12627,0 -6.45358,7.72435 -6.45358,9.35739 0,1.79274 5.56458,0.63052 9.12721,-1.9063 2.85064,-2.02985 3.66065,-3.46046 3.23121,-5.70694 -0.67872,-3.55054 0.54007,-4.68561 7.32658,-6.82325 12.36054,-3.89339 22.79453,-14.22078 26.1542,-25.88697 1.39779,-4.85375 3.2505,-7.45339 8.52715,-11.96486 13.73368,-11.74218 18.12325,-28.7805 6.83297,-26.52244 -9.86397,1.97279 -8.14547,-3.73665 2.73728,-9.09421 10.40507,-5.1224 11.16509,-6.13793 11.25597,-15.04038 0.10922,-10.69653 -0.85239,-15.0299 -3.28878,-14.82066 -2.58603,0.2221 -2.69596,-1.91469 -0.14879,-2.89212 1.04775,-0.40207 1.905,-1.64686 1.905,-2.76622 0,-1.11937 2.49134,-4.34435 5.53632,-7.16662 6.82335,-6.32434 9.48115,-16.81008 7.2193,-28.48227 -0.72909,-3.76246 -1.32651,-8.90596 -1.32759,-11.43 -0.004,-9.58225 -3.0654,-12.67262 -17.14303,-17.30591 -6.985,-2.29893 -14.4145,-4.77962 -16.51,-5.51265 -2.0955,-0.73302 -8.01725,-1.99522 -13.15942,-2.80488 -8.44089,-1.32906 -9.98634,-2.04892 -15.90373,-7.40772 -7.83598,-7.09631 -10.726,-8.28489 -25.21581,-10.37052 -18.86641,-2.71559 -19.0126,-5.96203 -0.60469,-13.42801 12.69969,-5.15081 11.97904,-5.13366 21.15132,-0.50351 15.89155,8.02203 54.68733,0.80466 54.68733,-10.17375 0,-4.87735 -4.64544,-13.60812 -7.76784,-14.59914 -2.76807,-0.87854 -7.90799,-8.377792 -9.12484,-13.313342 -0.83044,-3.36823 -4.76979,-8.25388 -7.34818,-9.11334 -1.14811,-0.38271 -1.17297,-1.16252 -0.10097,-3.16558 3.60886,-6.74322 -8.0275,-24.40157 -22.64817,-34.36892 -3.84175,-2.61904 -7.81752,-5.90489 -8.83505,-7.30189 -4.2477,-5.831838 -8.63925,-9.184972 -13.1337,-10.028135 -3.34547,-0.627616 -6.95295,-2.970228 -12.03695,-7.816538 -6.38378,-6.085315 -7.89092,-6.920328 -12.49075,-6.920328 -3.15885,0 -6.4308,-0.933007 -8.25977,-2.355306 -4.98891,-3.87959 -11.92994,-5.675812 -14.8605,-3.845644 -2.0555,1.283678 -3.6101,1.073742 -8.89984,-1.201842 -3.52512,-1.516464 -7.51925,-2.757208 -8.87587,-2.757208 -1.3566,0 -3.16546,-0.842127 -4.01967,-1.871393 -2.09318,-2.522133 -9.98093,-3.923573 -11.80676,-2.097743 -3.2162,3.216204 -8.40904,1.663708 -10.40379,-3.110409 -1.89675,-4.539547 -1.89675,-4.539547 -8.61122,-3.700627 -5.81668,0.726745 -7.03509,0.484665 -9.11214,-1.810455 -1.3187,-1.457154 -3.47001,-2.649373 -4.78067,-2.649373 -1.31067,0 -2.71198,-0.85725 -3.11404,-1.905 -0.99202,-2.58517 -7.93289,-2.494214 -8.93493,0.117081 -0.60678,1.581224 -1.91332,1.856194 -5.99265,1.261193 -3.48935,-0.508948 -6.42118,-0.127199 -8.85436,1.152913 -4.58638,2.412938 -4.61733,2.409315 -4.61733,-0.540605 0,-1.34993 -1.11376,-2.807914 -2.47504,-3.239964 -1.36126,-0.43205 -2.81177,-1.846516 -3.22333,-3.143258 -1.07432,-3.384862 -4.35608,-4.223649 -7.77089,-1.98617 -1.62325,1.063595 -4.38012,1.93381 -6.12637,1.93381 -1.84349,0 -3.72849,1.03421 -4.49481,2.466092 -1.34795,2.518659 -2.27827,2.533687 -10.1216,0.163497 -3.46056,-1.045752 -4.79942,-0.928398 -6.45,0.565362 -2.73734,2.477255 -9.06743,2.393333 -14.94125,-0.198083 -3.61847,-1.596405 -8.39104,-2.049358 -20.4261,-1.93859 -16.34166,0.150405 -18.44758,0.768852 -19.70716,5.787424 -0.70212,2.797481 -1.25073,2.926419 -11.99852,2.820015 -7.92277,-0.07844 -12.54345,0.495811 -15.58134,1.936411 -2.37521,1.126353 -7.61733,3.650327 -11.64913,5.608833 -5.22519,2.538204 -7.72243,3.169055 -8.69497,2.196516 -3.14625,-3.146245 -16.69474,-0.404513 -20.80448,4.210081 -3.2182,3.613552 -4.82286,4.350479 -10.33093,4.744396 -4.10641,0.293676 -7.99166,1.51361 -10.49298,3.294706 -2.98036,2.1222 -4.93242,2.617423 -7.81859,1.983514 -4.60502,-1.011433 -7.5525,1.008337 -7.5525,5.17538 0,3.568577 -2.26124,5.298608 -3.31984,2.539942 -1.10046,-2.867763 -4.8318,-2.441226 -8.96521,1.024835 -1.97447,1.655682 -6.73319,4.321297 -10.57494,5.923589 -9.46553,3.947819 -16.46342,9.389011 -20.58108,16.002791 -1.88806,3.0326 -6.14726,7.37782 -9.46488,9.65605 -16.27955,11.17926 -16.53616,22.53342 -0.70613,31.24369 3.27863,1.80402 2.59702,3.63692 -1.72614,4.64167 -9.47478,2.20205 -12.21496,3.18208 -15.56317,5.56623 -2.08214,1.48261 -4.10013,2.69565 -4.48442,2.69565 -0.3843,0 -2.12748,1.42875 -3.87372,3.175002 -1.74625,1.74625 -3.98229,3.175 -4.96897,3.175 -2.63134,0 -8.67569,9.80586 -9.40751,15.26198 -0.71696,5.34538 2.82458,9.13009 7.41918,7.92856 1.54774,-0.40473 3.65905,0.41249 5.47632,2.11972 2.46703,2.31766 12.73938,4.94693 20.09802,5.1442 0.52387,0.0141 0.9525,0.77421 0.9525,1.68925 0,0.92102 3.82686,2.95772 8.5725,4.56239 13.52072,4.57184 18.70607,9.0139 10.52218,9.0139 -2.46553,0 -4.23457,0.87681 -5.12468,2.54 -0.88448,1.65267 -2.65916,2.54 -5.08,2.54 -2.31748,0 -4.19385,0.88421 -4.97542,2.34459 -1.32697,2.47947 -12.4162,7.35712 -13.73286,6.04046 -0.42568,-0.42568 -2.07357,0.0776 -3.66198,1.11835 -3.14083,2.05795 -11.46354,2.64963 -17.84854,1.26889 -8.44358,-1.8259 -11.54204,5.52119 -12.53134,29.7144 -0.35359,8.64692 -0.94637,16.2127 -1.31728,16.81286 -2.88377,4.66603 13.84469,21.82914 22.31742,22.89728 1.04775,0.1321 -1.524,0.57272 -5.715,0.97917 -16.5423,1.60431 -6.84542,21.98021 12.4148,26.08702 1.93865,0.41338 4.69746,1.92424 6.1307,3.35748 1.43324,1.43324 3.40285,2.30007 4.37691,1.92629 3.14492,-1.20683 9.48796,1.92841 11.40618,5.63784 3.34493,6.46838 2.98121,6.6579 -11.53792,6.01217 -15.00763,-0.66746 -18.34567,1.04434 -18.34567,9.40797 0,6.62916 9.33684,18.79523 14.42439,18.79523 1.36339,0 4.65351,2.286 7.31138,5.08 2.65787,2.79401 5.42688,5.08001 6.15336,5.08001 0.72648,0 1.32087,0.5715 1.32087,1.27 0,0.6985 1.15525,1.27 2.56723,1.27 1.52373,0 3.17092,1.32496 4.05245,3.25972 1.01387,2.2252 2.21438,3.06903 3.78277,2.65889 1.61945,-0.42349 2.29755,0.10905 2.29755,1.80436 0,1.39072 2.0087,3.69083 4.7625,5.45342 2.61937,1.67652 7.87613,5.9813 11.68168,9.56616 4.94215,4.65555 8.29193,6.73744 11.72352,7.28616 2.64239,0.42255 5.45661,1.55418 6.25382,2.51477 0.79722,0.96059 2.09388,1.74653 2.88148,1.74655 0.7876,3e-5 2.92384,1.8574 4.7472,4.1275 3.26102,4.06002 12.6729,8.16736 15.17367,6.62181 0.65786,-0.40659 1.19612,0.7007 1.19612,2.46064 0,1.75993 0.71438,3.80196 1.5875,4.53783 4.05246,3.41545 11.43521,7.65603 13.3914,7.6919 1.19135,0.0218 4.88262,1.37098 8.20281,2.99809 3.47959,1.70521 7.3654,2.70436 9.17345,2.35873 2.13719,-0.40855 4.37515,0.44243 7.02302,2.67046 3.27495,2.75568 4.35991,3.05427 6.89728,1.89816 4.73075,-2.15547 26.11454,5.77946 26.11454,9.69037 0,5.78566 44.18101,11.5168 49.30234,6.39547 1.28087,-1.28088 3.69451,-0.61153 6.46761,1.79357 2.16176,1.87488 3.61283,2.19435 6.22682,1.37089 1.87452,-0.59053 9.40897,-1.44695 16.74322,-1.90319 z m 58.42,-9.53226 c 0,-0.6985 0.85725,-1.27 1.905,-1.27 1.04775,0 1.905,-0.5715 1.905,-1.27 0,-0.6985 0.66007,-1.27 1.46684,-1.27 0.83183,0 0.47173,1.09958 -0.83184,2.54 -2.48307,2.74376 -4.445,3.30431 -4.445,1.27 z m -253.99998,-51.91451 c 0,-1.60084 -3.72806,-4.47078 -4.63259,-3.56624 -1.30055,1.30055 0.64039,4.68075 2.68773,4.68075 1.06967,0 1.94486,-0.50153 1.94486,-1.11451 z"
+         id="path12864"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#384747;stroke-width:1.26999998"
+         d="m -4.9893,445.86076 c 0,-0.43494 -6.44008,-1.15861 -14.31129,-1.60818 -32.2454,-1.84166 -99.84436,-21.90523 -109.4076,-32.47249 -1.14751,-1.26798 -4.49831,-3.30004 -7.44624,-4.5157 -6.52364,-2.69019 -6.17282,-2.4902 -17.04024,-9.71374 -4.84652,-3.22145 -10.77334,-7.8543 -13.17073,-10.2952 -2.39739,-2.44091 -6.69889,-5.89986 -9.55891,-7.68657 -2.86001,-1.78672 -9.71801,-7.58896 -15.24,-12.89391 -5.52198,-5.30494 -12.68461,-12.14764 -15.91694,-15.20601 -6.44615,-6.09923 -18.70746,-22.22077 -25.85762,-33.99841 -0.70697,-1.16451 -3.97444,-6.06063 -7.26104,-10.88026 -3.2866,-4.81962 -6.60232,-10.43908 -7.36827,-12.48769 -0.76594,-2.04861 -2.45191,-6.01075 -3.74658,-8.80475 -1.29467,-2.794 -2.7406,-8.509 -3.21316,-12.7 -0.48815,-4.32918 -2.2261,-10.08811 -4.02428,-13.335 -3.67382,-6.63361 -6.77995,-17.62705 -6.80346,-24.07932 -0.009,-2.47262 -1.17793,-8.20175 -2.59761,-12.73141 -4.54316,-14.4956 -3.18666,-57.81129 1.98959,-63.53098 1.04192,-1.1513 1.8944,-2.68822 1.8944,-3.41534 0,-0.72713 1.18785,-4.96838 2.63967,-9.42501 2.09883,-6.44274 2.48347,-9.93223 1.87734,-17.03123 -1.03587,-12.13211 2.77268,-23.46655 14.10537,-41.978302 3.02919,-4.94814 5.50762,-9.6467 5.50762,-10.44129 0,-4.52042 7.74795,-18.00008 16.30275,-28.36306 15.48869,-18.76245 20.95644,-24.631479 29.7486,-31.931864 18.08733,-15.018417 28.46179,-23.029991 34.07256,-26.312197 2.98512,-1.74625 7.22578,-4.719652 9.42368,-6.60756 2.1979,-1.887907 7.92351,-5.25034 12.72357,-7.472072 4.80007,-2.221732 11.32759,-5.841731 14.50561,-8.044443 3.17802,-2.202711 9.49297,-5.620061 14.03322,-7.594112 4.54025,-1.974052 10.541,-4.883022 13.335,-6.464381 2.794,-1.581358 9.36625,-4.166311 14.605,-5.744339 5.23875,-1.578029 15.52575,-4.742557 22.86,-7.032286 40.60164,-12.675696 101.62444,-9.767902 149.22499,7.110712 15.54417,5.511789 51.34231,23.143578 59.05499,29.086581 2.44475,1.883798 8.25754,5.528625 12.9173,8.099614 30.85982,17.026666 69.35628,56.086637 82.89236,84.105727 6.01538,12.45156 7.47367,15.37233 13.35761,26.75353 7.43785,14.386942 10.8951,26.677052 8.98795,31.951172 -1.73573,4.80012 -0.85007,12.0384 1.96472,16.05709 0.99002,1.41346 1.80564,4.01439 1.81246,5.77986 0.006,1.76545 1.48592,6.35316 3.28686,10.19491 13.61124,29.03517 -3.17068,108.34108 -33.4587,158.11501 -15.76431,25.90633 -67.56244,78.0615 -77.57431,78.10904 -0.46819,0.003 -4.28025,2.2864 -8.47125,5.07596 -4.191,2.78955 -8.06175,5.07374 -8.60167,5.07596 -0.53993,0.003 -5.4554,2.60345 -10.92327,5.78049 -5.46787,3.17705 -14.16005,7.2435 -19.31597,9.03655 -5.15592,1.79305 -12.5277,4.6708 -16.38174,6.39501 -8.58723,3.84172 -22.79979,8.19668 -38.12234,11.68132 -14.13695,3.215 -20.72942,4.10206 -35.8775,4.82753 -8.90587,0.42652 -16.1925,1.15131 -16.1925,1.61064 0,0.45934 -3.429,0.81401 -7.62,0.78818 -4.191,-0.0259 -7.62,-0.40285 -7.62,-0.83778 z m 15.24,-38.63564 c 18.83319,-1.17154 31.56397,-0.99343 37.465,0.52416 5.37867,1.38324 13.38958,-0.44492 21.59,-4.92705 2.0955,-1.14533 5.66716,-2.20555 7.93703,-2.35604 4.65238,-0.30844 14.28797,-4.90989 14.28797,-6.82319 0,-0.75318 -1.97537,-0.91967 -4.94966,-0.41717 -5.09108,0.86014 -8.41889,-1.16998 -5.18953,-3.16584 3.49005,-2.15697 16.62211,-3.99783 18.34123,-2.57109 1.23597,1.02577 3.32368,0.73857 7.98387,-1.09831 5.83177,-2.29866 6.46718,-2.33036 8.37954,-0.41799 1.91236,1.91235 2.34309,1.80693 5.68038,-1.39041 2.26451,-2.16954 6.6501,-4.2309 11.76057,-5.52784 7.98223,-2.02573 15.14359,-6.02928 15.14359,-8.466 0,-0.67476 2.33207,-3.22301 5.18238,-5.66276 2.85031,-2.43975 5.69366,-5.7807 6.31857,-7.42432 0.6644,-1.74751 2.17912,-2.98841 3.64787,-2.98841 2.98873,0 7.96579,-2.07084 14.06118,-5.85053 4.93347,-3.05918 15.24,-5.12488 15.24,-3.05449 0,0.70676 -0.52489,1.28502 -1.16642,1.28502 -1.12627,0 -6.45358,7.72435 -6.45358,9.35739 0,1.79274 5.56458,0.63052 9.12721,-1.9063 2.85064,-2.02985 3.66065,-3.46046 3.23121,-5.70694 -0.67872,-3.55054 0.54007,-4.68561 7.32658,-6.82325 12.36054,-3.89339 22.79453,-14.22078 26.1542,-25.88697 1.39779,-4.85375 3.2505,-7.45339 8.52715,-11.96486 9.81338,-8.39036 12.96859,-14.40464 12.60321,-24.02345 -0.26429,-6.95753 0.13391,-8.36308 3.87449,-13.67614 4.62293,-6.56631 5.36258,-12.25685 3.5273,-27.13755 -0.99568,-8.07307 -0.99568,-8.07307 4.68281,-13.335 6.97419,-6.46261 9.64335,-16.83715 7.36146,-28.61278 -0.72909,-3.76246 -1.32651,-8.90596 -1.32759,-11.43 -0.004,-8.81313 -3.38411,-12.87442 -13.23414,-15.90026 -9.63182,-2.95881 -10.35638,-3.74195 -8.34841,-9.02333 1.03281,-2.71646 1.7133,-3.21979 2.83571,-2.09737 2.10615,2.10615 6.04881,1.82704 6.04881,-0.4282 0,-1.05833 -1.12889,-1.905 -2.54,-1.905 -3.79796,0 -7.23963,-4.16813 -8.00527,-9.69504 -0.37409,-2.70048 -1.04016,-6.33871 -1.48013,-8.08496 -0.6552,-2.60041 0.36055,-4.43533 5.61271,-10.13926 8.11433,-8.81228 7.49268,-22.02447 -1.16557,-24.77249 -2.6733,-0.84848 -8.16139,-8.731102 -9.31096,-13.373522 -0.83438,-3.36949 -4.77506,-8.25449 -7.35164,-9.11334 -1.14811,-0.38271 -1.17297,-1.16252 -0.10097,-3.16558 3.60886,-6.74322 -8.0275,-24.40157 -22.64817,-34.36892 -3.84175,-2.61904 -7.81752,-5.90489 -8.83505,-7.30189 -4.2477,-5.831838 -8.63925,-9.184972 -13.1337,-10.028135 -3.34547,-0.627616 -6.95295,-2.970228 -12.03695,-7.816538 -6.38378,-6.085315 -7.89092,-6.920328 -12.49075,-6.920328 -3.15885,0 -6.4308,-0.933007 -8.25977,-2.355306 -4.98891,-3.87959 -11.92994,-5.675812 -14.8605,-3.845644 -2.0555,1.283678 -3.6101,1.073742 -8.89984,-1.201842 -3.52512,-1.516464 -7.51925,-2.757208 -8.87587,-2.757208 -1.3566,0 -3.16546,-0.842127 -4.01967,-1.871393 -2.09318,-2.522133 -9.98093,-3.923573 -11.80676,-2.097743 -3.2162,3.216204 -8.40904,1.663708 -10.40379,-3.110409 -1.89675,-4.539547 -1.89675,-4.539547 -8.61122,-3.700627 -5.81668,0.726745 -7.03509,0.484665 -9.11214,-1.810455 -1.3187,-1.457154 -3.47001,-2.649373 -4.78067,-2.649373 -1.31067,0 -2.71198,-0.85725 -3.11404,-1.905 -0.99202,-2.58517 -7.93289,-2.494214 -8.93493,0.117081 -0.60678,1.581224 -1.91332,1.856194 -5.99265,1.261193 -3.48935,-0.508948 -6.42118,-0.127199 -8.85436,1.152913 -4.58638,2.412938 -4.61733,2.409315 -4.61733,-0.540605 0,-1.34993 -1.11376,-2.807914 -2.47504,-3.239964 -1.36126,-0.43205 -2.81177,-1.846516 -3.22333,-3.143258 -1.07432,-3.384862 -4.35608,-4.223649 -7.77089,-1.98617 -1.62325,1.063595 -4.38012,1.93381 -6.12637,1.93381 -1.84349,0 -3.72849,1.03421 -4.49481,2.466092 -1.34795,2.518659 -2.27827,2.533687 -10.1216,0.163497 -3.46056,-1.045752 -4.79942,-0.928398 -6.45,0.565362 -2.73734,2.477255 -9.06743,2.393333 -14.94125,-0.198083 -3.61847,-1.596405 -8.39104,-2.049358 -20.4261,-1.93859 -16.34166,0.150405 -18.44758,0.768852 -19.70716,5.787424 -0.70212,2.797481 -1.25073,2.926419 -11.99852,2.820015 -7.92277,-0.07844 -12.54345,0.495811 -15.58134,1.936411 -2.37521,1.126353 -7.61733,3.650327 -11.64913,5.608833 -5.22519,2.538204 -7.72243,3.169055 -8.69497,2.196516 -3.14625,-3.146245 -16.69474,-0.404513 -20.80448,4.210081 -3.2182,3.613552 -4.82286,4.350479 -10.33093,4.744396 -4.10641,0.293676 -7.99166,1.51361 -10.49298,3.294706 -2.98036,2.1222 -4.93242,2.617423 -7.81859,1.983514 -4.60502,-1.011433 -7.55249,1.008337 -7.55249,5.17538 0,3.568577 -2.26125,5.298608 -3.31985,2.539942 -1.10046,-2.867763 -4.83179,-2.441226 -8.96521,1.024835 -1.97447,1.655682 -6.73319,4.321297 -10.57494,5.923589 -9.46553,3.947819 -16.46342,9.389011 -20.58108,16.002791 -1.88806,3.0326 -6.14726,7.37782 -9.46488,9.65605 -13.35607,9.17169 -16.20581,16.09205 -9.48286,23.02839 5.04083,5.20081 5.05318,5.13631 -1.74618,9.121 -3.3974,1.991 -5.715,4.27108 -5.715,5.62246 0,1.25029 -1.39162,3.18509 -3.09248,4.29954 -1.70087,1.11445 -4.53239,3.46617 -6.29227,5.226062 -1.75989,1.75989 -4.00708,3.19979 -4.99376,3.19979 -2.49641,0 -8.53449,9.50259 -9.39072,14.77889 -0.73877,4.55251 3.13656,10.62111 6.7825,10.62111 2.08805,0 4.09246,3.5831 3.47553,6.21288 -0.28657,1.22157 1.297,3.31787 3.94111,5.21712 5.17018,3.71374 5.3991,4.50415 1.85396,6.40145 -3.93656,2.10679 -6.93201,5.44242 -8.9195,9.93248 -0.97769,2.20873 -2.86896,4.36227 -4.20283,4.78563 -8.84577,2.80753 -11.83391,10.57665 -12.82203,33.33713 -0.3754,8.64692 -0.98602,16.2127 -1.35693,16.81286 -1.72568,2.7922 4.51862,12.60144 10.2022,16.02676 6.31318,3.80474 7.03683,6.75724 1.94855,7.95007 -10.65802,2.49855 6.31398,34.29362 18.30572,34.29362 3.4619,0 6.42269,4.01287 3.9725,5.38408 -1.31473,0.73575 -2.67527,0.6763 -3.68414,-0.16098 -2.26377,-1.87877 -9.69741,7.98783 -9.69741,12.87127 0,6.57368 9.36337,18.73563 14.42439,18.73563 1.36339,0 4.65351,2.286 7.31138,5.08 2.65787,2.79401 5.42688,5.08001 6.15336,5.08001 0.72648,0 1.32087,0.5715 1.32087,1.27 0,0.6985 1.15525,1.27 2.56723,1.27 1.52373,0 3.17092,1.32496 4.05245,3.25972 1.01387,2.2252 2.21438,3.06903 3.78277,2.65889 1.61945,-0.42349 2.29755,0.10905 2.29755,1.80436 0,1.39246 2.00526,3.68452 4.7625,5.44365 2.61937,1.67117 7.56633,5.70646 10.99323,8.96734 7.28423,6.93131 9.63197,8.34604 13.85027,8.34604 1.70664,0 3.81444,0.85725 4.684,1.905 0.86955,1.04775 2.2254,1.90501 3.013,1.90503 0.7876,3e-5 2.92384,1.8574 4.7472,4.1275 3.26102,4.06002 12.6729,8.16736 15.17367,6.62181 0.65786,-0.40659 1.19612,0.7007 1.19612,2.46064 0,1.75993 0.71438,3.80196 1.5875,4.53783 4.05246,3.41545 11.43521,7.65603 13.3914,7.6919 1.19135,0.0218 4.88262,1.37098 8.20281,2.99809 3.47959,1.70521 7.3654,2.70436 9.17345,2.35873 2.13719,-0.40855 4.37515,0.44243 7.02302,2.67046 3.27495,2.75568 4.35991,3.05427 6.89728,1.89816 4.73075,-2.15547 26.11454,5.77946 26.11454,9.69037 0,5.78566 44.18101,11.5168 49.30234,6.39547 1.28087,-1.28088 3.69451,-0.61153 6.46761,1.79357 2.16176,1.87488 3.61283,2.19435 6.22682,1.37089 1.87452,-0.59053 9.40897,-1.44695 16.74322,-1.90319 z m 58.42,-9.53226 c 0,-0.6985 0.85725,-1.27 1.905,-1.27 1.04775,0 1.905,-0.5715 1.905,-1.27 0,-0.6985 0.66007,-1.27 1.46684,-1.27 0.83183,0 0.47173,1.09958 -0.83184,2.54 -2.48307,2.74376 -4.445,3.30431 -4.445,1.27 z m -253.99998,-51.91451 c 0,-1.60084 -3.72806,-4.47078 -4.63259,-3.56624 -1.30055,1.30055 0.64039,4.68075 2.68773,4.68075 1.06967,0 1.94486,-0.50153 1.94486,-1.11451 z"
+         id="path12862"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#38433c;stroke-width:1.26999998"
+         d="m -4.3543,445.95286 c 0.48878,-0.79086 -1.99845,-1.27135 -6.59254,-1.27358 -25.33176,-0.0127 -57.72994,-7.36029 -89.29245,-20.25182 -3.14325,-1.28384 -8.28675,-3.33789 -11.43,-4.56454 -10.29013,-4.01573 -16.51,-7.13649 -16.51,-8.28372 0,-1.11577 -9.63312,-6.26634 -11.71996,-6.26634 -0.59559,0 -2.63137,-1.04317 -4.52396,-2.31815 -1.89259,-1.27498 -6.29857,-4.23241 -9.79108,-6.57208 -3.4925,-2.33967 -9.24065,-6.94009 -12.77367,-10.22316 -3.53302,-3.28306 -8.17722,-6.87601 -10.32046,-7.98432 -2.14323,-1.10831 -7.25358,-5.61671 -11.35632,-10.01865 -4.10275,-4.40196 -9.88663,-9.64381 -12.85307,-11.64857 -2.96645,-2.00476 -7.72203,-6.56891 -10.56797,-10.14255 -8.17573,-10.26626 -20.2335,-27.25236 -20.2335,-28.50346 0,-0.6269 -1.7145,-3.29721 -3.81,-5.93407 -2.0955,-2.63685 -3.81,-5.57292 -3.81,-6.52462 0,-0.95171 -0.60364,-1.73038 -1.34141,-1.73038 -0.73778,0 -2.51174,-2.71462 -3.94214,-6.0325 -1.4304,-3.31787 -3.9179,-8.89 -5.52777,-12.3825 -1.60988,-3.4925 -3.29455,-9.42538 -3.74371,-13.18416 -0.44917,-3.75879 -2.53487,-10.2379 -4.63489,-14.39801 -2.46829,-4.88967 -4.19968,-10.78007 -4.8972,-16.66083 -0.59344,-5.00335 -2.11227,-12.81175 -3.37516,-17.352 -4.1431,-14.89486 -3.43384,-55.87691 1.10978,-64.12489 1.92088,-3.48695 3.50082,-7.2017 3.51099,-8.255 0.0102,-1.05331 0.92586,-4.48686 2.03488,-7.63011 1.43396,-4.06421 1.80428,-8.06107 1.28206,-13.83708 -1.00881,-11.1579 4.15306,-27.05871 13.53375,-41.689862 1.89078,-2.94905 5.65013,-10.49353 8.35412,-16.76549 2.70399,-6.27194 7.15404,-14.14608 9.889,-17.49806 2.73496,-3.35198 7.3726,-9.23776 10.30586,-13.07951 11.67924,-15.296523 42.04416,-41.603793 59.35684,-51.425006 3.49249,-1.981242 7.20725,-4.594397 8.255,-5.807011 1.92267,-2.225209 15.92631,-10.077985 17.97179,-10.077985 0.60734,0 3.81302,-1.896915 7.12373,-4.215366 3.31072,-2.318452 9.44847,-5.82619 13.63947,-7.794974 4.191,-1.968785 12.192,-5.728217 17.78,-8.354295 5.588,-2.626078 16.11949,-6.298045 23.40332,-8.159926 7.28382,-1.861881 16.42782,-4.451426 20.32,-5.754546 27.38852,-9.169816 94.88479,-5.573108 134.07667,7.144612 16.76383,5.439843 58.18219,25.395641 66.70061,32.137046 2.45883,1.945903 6.88012,4.767249 9.82509,6.269657 11.3176,5.77381 34.59929,22.962241 34.59929,25.544 0,0.631411 3.11995,3.218925 6.93322,5.750033 14.58403,9.680329 47.67678,53.835351 47.67678,63.614221 0,1.1779 1.92863,5.05829 4.28587,8.62308 5.30432,8.02167 13.9417,30.546162 13.68645,35.691462 -0.10388,2.0955 -0.38067,6.86438 -0.61493,10.59752 -0.29794,4.7477 0.1363,7.34974 1.44492,8.65836 1.02898,1.02898 2.06483,3.53677 2.3019,5.57288 0.23707,2.03611 1.82951,6.99809 3.53875,11.02663 8.46921,19.96119 -0.96202,97.13642 -14.9105,122.01171 -1.64133,2.92711 -1.64133,2.92711 -3.11968,-0.3175 -0.81307,-1.78453 -2.40978,-3.2446 -3.54824,-3.2446 -3.04919,0 -1.36673,-5.56026 4.1251,-13.63282 4.74507,-6.97487 6.89835,-25.48617 3.11147,-26.74848 -2.64009,-0.88002 -1.22714,-9.09805 1.58226,-9.20285 11.50755,-0.42926 15.97038,-22.6674 10.31386,-51.39365 -0.69817,-3.54559 -6.16359,-6.44844 -15.64143,-8.30765 -8.83216,-1.73255 -9.86906,-2.81539 -7.83044,-8.17734 1.04049,-2.73671 1.72342,-3.24643 2.84583,-2.12401 2.10615,2.10615 6.04881,1.82704 6.04881,-0.4282 0,-1.05833 -1.12889,-1.905 -2.54,-1.905 -3.60152,0 -7.62,-4.47324 -7.62,-8.48233 0,-1.87128 -0.65904,-5.22487 -1.46453,-7.45241 -1.78601,-4.93913 3.88851,-13.13368 11.94203,-17.24539 5.0658,-2.58635 6.70848,-8.81589 3.23944,-12.28493 -0.83768,-0.83768 -2.13747,-3.38085 -2.88843,-5.6515 -1.26266,-3.81787 -1.74518,-4.10648 -6.41444,-3.83653 -7.19161,0.41579 -14.57303,-10.623522 -8.62863,-12.904602 1.76474,-0.6772 1.31889,-1.4925 -2.56837,-4.69672 -4.53498,-3.73812 -4.67481,-4.09599 -3.8955,-9.97068 1.54239,-11.62715 -1.91188,-16.4463 -22.31482,-31.13208 -2.90531,-2.09119 -6.42666,-5.6537 -7.82526,-7.916674 -4.28431,-6.932147 -8.49888,-9.495338 -14.06627,-8.554733 -4.63339,0.782805 -4.92015,0.623697 -6.74423,-3.741927 -1.04779,-2.507723 -2.48268,-4.559497 -3.18865,-4.559497 -0.70596,0 -4.19142,-2.00025 -7.74546,-4.445 -3.55405,-2.44475 -6.96927,-4.445 -7.58938,-4.445 -0.62013,0 -1.1275,-0.768675 -1.1275,-1.708165 0,-3.563874 -4.61845,-5.700968 -15.00361,-6.942601 -6.88352,-0.822983 -11.42131,-2.043113 -13.03717,-3.505449 -1.49317,-1.351288 -5.2554,-2.473352 -9.46299,-2.822278 -6.29588,-0.522104 -7.36864,-1.064334 -10.88203,-5.500355 -2.1437,-2.706634 -4.79952,-4.921152 -5.90183,-4.921152 -1.10231,0 -4.41091,-1.410422 -7.35244,-3.134271 -2.94152,-1.723849 -7.71927,-3.513416 -10.61722,-3.976818 -2.89795,-0.4634 -5.57581,-1.338978 -5.9508,-1.945729 -0.37499,-0.606751 -2.2926,-1.103183 -4.26135,-1.103183 -1.96875,0 -4.23847,-0.793962 -5.04383,-1.764361 -0.80537,-0.970398 -2.73272,-2.070288 -4.28301,-2.444199 -1.55029,-0.373911 -6.67913,-2.097465 -11.39742,-3.830119 -4.71828,-1.732655 -9.59761,-2.883833 -10.84293,-2.558172 -1.24532,0.32566 -7.6719,-0.22845 -14.28129,-1.231356 -18.43904,-2.797931 -24.39416,-2.975187 -47.93929,-1.426933 -7.88273,0.518345 -14.63158,1.426801 -14.99746,2.018792 -0.36587,0.591992 -0.06,1.076348 0.67973,1.076348 0.73972,0 1.34496,0.636391 1.34496,1.414201 0,0.954601 -1.84161,1.138036 -5.66664,0.564435 -3.11666,-0.46737 -6.68853,-0.303029 -7.9375,0.365203 -1.24897,0.668231 -5.41411,1.610859 -9.25586,2.094728 -3.84175,0.483867 -7.67435,1.446837 -8.51688,2.139931 -0.84251,0.693094 -2.11161,0.901865 -2.8202,0.463936 -0.70858,-0.437929 -2.9834,0.08033 -5.05518,1.151678 -2.07176,1.071353 -4.58395,1.634358 -5.58265,1.251124 -4.06555,-1.560099 -17.72769,4.547574 -17.985,8.040212 -0.0909,1.234316 -0.60064,2.948536 -1.13267,3.809381 -1.15119,1.86267 -10.97742,2.126888 -10.97742,0.295172 0,-2.817229 -5.21941,-1.00056 -9.35262,3.25528 -3.70148,3.811286 -5.51315,4.640693 -11.48234,5.256768 -3.89808,0.402319 -7.72829,1.503668 -8.51155,2.447444 -0.84167,1.014143 -2.62566,1.414388 -4.36192,0.978615 -2.32177,-0.582728 -3.33122,0.01902 -4.81402,2.869777 -3.06641,5.895337 -7.93967,9.626291 -13.17597,10.087492 -6.2846,0.553534 -14.0217,3.921345 -18.78657,8.177442 -4.56467,4.077264 -10.15907,7.129774 -14.92442,8.143313 -3.44463,0.732635 -17.84862,12.85556 -22.89097,19.26586 -1.42332,1.80947 -6.46509,5.77518 -11.20394,8.8127 -13.10219,8.3983 -14.20613,15.17866 -3.65531,22.45075 6.38062,4.39779 6.32381,5.19078 -0.66536,9.28671 -3.3974,1.991 -5.715,4.27108 -5.715,5.62246 0,1.25029 -1.56069,3.29587 -3.4682,4.54571 -1.90751,1.24985 -4.37467,3.39191 -5.48259,4.760132 -1.70892,2.11042 -2.44714,2.25608 -4.86799,0.96049 -3.41064,-1.82532 -5.38307,-0.54719 -9.64578,6.25042 -6.24895,9.96501 -5.21109,15.71248 3.63511,20.13048 5.6465,2.81999 6.30471,3.67555 6.02246,7.82816 -0.0712,1.04775 1.84749,3.33375 4.26379,5.08 5.14292,3.71677 5.36825,4.50627 1.82707,6.40145 -3.95253,2.11532 -6.93715,5.45024 -8.92038,9.96733 -0.99427,2.26459 -3.0385,4.36952 -4.63599,4.77366 -1.57162,0.39761 -2.8575,1.19395 -2.8575,1.76966 0,0.5757 -1.00012,1.36624 -2.2225,1.75673 -7.40872,2.36674 -11.13799,13.47666 -12.39501,36.92617 -0.8367,15.60838 2.03248,21.04098 14.38311,27.23349 5.93602,2.97627 6.43087,5.55473 1.18588,6.17902 -6.36345,0.75742 -8.09185,5.98002 -3.90797,11.8085 1.97532,2.75177 4.38176,6.56152 5.34763,8.46611 3.30805,6.52303 12.62701,14.25788 17.17791,14.25788 1.01174,0 2.78846,0.94895 3.94828,2.10876 2.70099,2.70099 -0.81184,5.47803 -3.79631,3.00114 -1.31335,-1.08998 -1.99284,-1.02117 -2.791,0.28264 -0.58026,0.94785 -3.47169,3.40841 -6.4254,5.46792 -6.45338,4.49967 -6.67426,6.60409 -1.61839,15.41959 4.78912,8.3504 12.85662,14.99495 18.20614,14.99495 3.14274,0 4.33955,0.78466 5.7957,3.79981 1.0093,2.08988 2.59488,4.55958 3.52351,5.4882 0.92863,0.92864 1.68842,2.14766 1.68842,2.70896 0,0.56129 1.73174,1.36689 3.84832,1.79019 2.11657,0.42332 5.11695,2.07437 6.6675,3.66901 1.55055,1.59462 4.34266,3.67652 6.2047,4.62643 3.38775,1.72824 4.81959,4.66155 4.85214,9.94023 0.0187,3.03941 3.35381,5.44218 7.55385,5.44218 3.02978,0 3.15406,-0.44561 1.16228,-4.16728 -1.28167,-2.39481 -0.96253,-2.31069 2.59723,0.68466 2.23214,1.87822 6.30142,4.08696 9.04283,4.90831 2.7414,0.82134 8.2362,4.24287 12.21064,7.6034 3.97445,3.36052 7.69915,5.81778 8.27712,5.46058 0.90296,-0.55807 9.72783,7.11292 19.49142,16.94283 1.56102,1.57162 3.66358,2.8575 4.67236,2.8575 1.00877,0 2.94349,0.83907 4.29937,1.86461 1.35588,1.02553 3.3856,1.88278 4.5105,1.905 1.12491,0.0222 4.15067,1.46914 6.7239,3.21539 2.57325,1.74625 6.10041,3.175 7.83815,3.175 1.98137,0 3.50392,0.93097 4.0831,2.49664 1.45494,3.93307 9.1632,8.93336 13.77136,8.93336 2.26783,0 4.4614,0.54702 4.87462,1.21563 0.41322,0.66859 2.30432,1.16125 4.20245,1.0948 5.65464,-0.19797 15.43821,2.30537 17.97216,4.59857 2.0583,1.86272 3.3608,1.95915 10.05825,0.74468 5.69817,-1.03327 8.29531,-1.01947 10.01301,0.0532 1.79217,1.11923 3.40183,0.99607 7.0828,-0.54195 5.82735,-2.4348 10.5174,-1.05838 12.29126,3.60724 1.68054,4.42014 7.65532,3.49594 17.51135,-2.70875 6.39018,-4.02283 20.28402,-4.99058 23.82946,-1.6598 2.51285,2.3607 2.981,2.40596 6.4069,0.61938 6.15819,-3.21145 10.90008,-2.62331 9.93246,1.23194 -0.81179,3.23446 0.26115,3.82056 5.98417,3.26882 1.13336,-0.10922 2.36573,0.96793 2.7386,2.39376 0.78057,2.98494 4.95371,3.47321 7.14263,0.83573 1.59256,-1.91886 -0.48866,-3.32326 -4.92491,-3.32326 -1.90643,0 -3.842,-2.29168 -3.842,-4.54888 0,-0.29211 2.67405,-0.53112 5.94233,-0.53112 6.00156,0 11.84633,-1.40663 18.82267,-4.52997 2.0955,-0.93816 6.6675,-2.06348 10.16,-2.5007 12.8671,-1.61085 14.605,-2.64979 14.605,-8.73116 0,-6.42538 0.75796,-7.46157 4.13573,-5.65384 1.84607,0.98798 3.33141,0.927 5.48426,-0.22517 2.32572,-1.24469 3.7408,-1.25361 6.66585,-0.042 2.04235,0.84597 5.64292,1.20217 8.00125,0.79157 2.35836,-0.41062 6.43219,-0.99556 9.05296,-1.29986 4.73681,-0.55 14.08117,-6.81121 15.43655,-10.3433 0.37053,-0.96556 1.46029,-1.75556 2.4217,-1.75556 2.60123,0 6.36856,-4.70184 5.52619,-6.89703 -0.9384,-2.44543 1.47369,-3.99279 3.33016,-2.13631 2.39301,2.39301 3.61606,1.56532 2.03961,-1.38028 -1.27759,-2.38718 -1.09932,-3.05641 1.22532,-4.5999 1.49622,-0.99346 2.72041,-2.49613 2.72041,-3.33929 0,-0.84315 2.91606,-2.89881 6.48013,-4.56814 3.56406,-1.66931 8.2471,-4.33876 10.40673,-5.93209 7.15046,-5.27543 11.80739,-3.98226 7.74821,2.15158 -6.28161,9.49217 -1.45597,11.69711 12.01736,5.49099 9.56655,-4.40659 9.73189,-4.65233 7.21961,-10.72981 -0.66748,-1.61467 0.24622,-2.45994 3.9834,-3.68506 13.32532,-4.36832 24.74623,-16.88574 24.74623,-27.12211 0,-4.88555 0.4181,-5.5899 4.64373,-7.82324 2.55406,-1.34987 6.80965,-4.39351 9.45687,-6.76365 5.8563,-5.24333 5.28943,-2.71737 -2.35944,10.51344 -12.54021,21.6917 -23.71931,36.65476 -35.62351,47.68151 -4.09438,3.79257 -12.15038,11.29063 -17.90224,16.66232 -5.75185,5.37171 -14.32435,12.32064 -19.04999,15.44207 -10.7057,7.07146 -26.21346,16.24503 -27.46194,16.24503 -0.51544,0 -3.91008,1.88104 -7.54366,4.1801 -6.90538,4.36921 -21.53822,11.0599 -24.18853,11.0599 -0.84678,0 -5.57014,1.93158 -10.49634,4.29239 -22.33043,10.70157 -60.49018,18.56761 -90.07527,18.56761 -8.86089,0 -14.56134,0.49718 -14.56134,1.27 0,0.6985 -0.89098,1.27 -1.97995,1.27 -1.08897,0 -1.62674,-0.5715 -1.19505,-1.27 z"
+         id="path12860"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#253444;stroke-width:1.26999998"
+         d="m -29.11929,443.18184 c -23.37146,-2.77912 -44.27318,-8.39597 -69.85,-18.77051 -3.14325,-1.27496 -8.28675,-3.3373 -11.43,-4.58297 -11.98108,-4.74807 -16.00865,-6.77653 -18.14732,-9.13972 -1.21516,-1.34275 -4.63,-3.20166 -7.58853,-4.13093 -2.95853,-0.92928 -6.52215,-2.64008 -7.91914,-3.80178 -1.397,-1.16171 -4.826,-3.47599 -7.62,-5.14287 -2.794,-1.66687 -8.5839,-6.28149 -12.86644,-10.25471 -4.28254,-3.97321 -9.71179,-8.20473 -12.065,-9.40335 -2.35321,-1.19864 -8.1839,-6.39196 -12.95709,-11.54073 -4.77319,-5.14879 -9.34519,-9.36414 -10.16,-9.36748 -1.40531,-0.006 -7.36425,-5.40544 -12.91147,-11.6997 -2.64106,-2.99672 -9.8723,-13.45569 -16.61219,-24.02716 -2.1517,-3.37493 -4.75582,-7.14555 -5.78693,-8.37917 -5.56188,-6.65415 -4.85922,-9.27171 0.98549,-3.67113 3.22646,3.0917 7.50707,5.94941 9.51246,6.35049 3.04215,0.60843 3.64617,1.36012 3.64617,4.53756 0,2.82873 1.4925,5.30084 5.80233,9.61066 3.19128,3.19128 6.62326,7.7671 7.62662,10.16848 2.12276,5.08047 4.80553,7.04562 14.85498,10.8814 5.74807,2.19398 7.17971,3.26753 6.63711,4.97709 -0.80556,2.53812 6.66752,8.87755 10.46509,8.87755 1.30658,0 4.3019,1.98743 6.65625,4.41651 4.73979,4.89021 12.18132,7.80332 23.14092,9.05887 8.7512,1.00255 13.56814,4.91616 11.29005,9.17279 -1.47591,2.75778 -0.64883,6.56183 1.42668,6.56183 0.69756,0 2.94017,1.40679 4.98357,3.12619 2.04339,1.7194 5.76537,3.69226 8.27104,4.3841 2.50567,0.69185 5.48831,2.0836 6.62807,3.09278 2.39576,2.12129 16.79617,6.16118 32.86978,9.22131 1.57162,0.2992 2.8575,1.32104 2.8575,2.27073 0,2.85903 8.79235,5.547 27.61991,8.44389 22.5616,3.47142 27.72371,3.9766 46.67508,4.56771 11.176,0.3486 21.74875,0.68952 23.495,0.7576 6.25203,0.24376 16.03249,-0.71196 25.4,-2.48199 5.23875,-0.98989 12.3812,-2.08454 15.87213,-2.43256 7.61609,-0.75926 12.70287,-2.98848 12.70287,-5.56687 0,-3.05354 3.36378,-4.73436 10.80487,-5.399 6.62665,-0.59191 17.00811,-3.87647 19.61163,-6.20488 0.66357,-0.59345 2.37807,-1.08115 3.81,-1.08376 1.43192,-0.003 4.47469,-1.14561 6.76169,-2.54 2.28701,-1.39439 5.18728,-2.53525 6.44506,-2.53525 6.36304,0 33.55739,-11.7007 37.09154,-15.9591 0.83117,-1.0015 2.9454,-1.8209 4.6983,-1.8209 6.28893,0 14.48259,-6.82406 9.5194,-7.92818 -1.60481,-0.35702 -0.36942,-1.67104 4.445,-4.72796 3.66713,-2.32844 6.6675,-4.58469 6.6675,-5.01388 0,-0.42919 3.00038,-2.092 6.6675,-3.69514 15.40046,-6.73255 18.0275,-8.50952 20.71153,-14.00959 2.49858,-5.12002 14.18007,-16.69525 16.84851,-16.69525 0.7,0 3.11265,-2.42888 5.36144,-5.3975 6.2481,-8.24812 3.13203,-1.44209 -3.63167,7.93219 -15.01446,20.80951 -52.51676,54.60494 -70.94993,63.93699 -3.6177,1.83151 -6.92749,3.89611 -7.3551,4.588 -0.42762,0.6919 -3.46844,2.44397 -6.75738,3.89348 -3.28894,1.44952 -8.03614,4.05506 -10.54934,5.7901 -4.55801,3.1467 -14.8797,7.51035 -22.10055,9.34331 -2.0955,0.53194 -7.34223,2.75678 -11.6594,4.9441 -7.39696,3.74772 -13.83999,5.87379 -22.6306,7.46764 -2.0955,0.37994 -4.51878,1.25582 -5.38504,1.94641 -7.75949,6.1858 -71.96117,10.6479 -101.92995,7.08426 z M 238.85069,317.90441 c 0,-2.86811 4.75843,-10.94952 8.35614,-14.19156 3.52335,-3.175 3.52335,-3.175 -0.64395,4.445 -4.61112,8.43151 -7.71219,12.35062 -7.71219,9.74656 z M -245.2,296.41035 c -1.47823,-3.31787 -3.18667,-6.88975 -3.79652,-7.9375 -2.75752,-4.73751 -4.00168,-8.79605 -4.86655,-15.875 -0.52087,-4.26325 -2.70961,-11.31656 -4.96856,-16.01139 -2.72273,-5.65874 -4.42876,-11.65559 -5.23884,-18.415 -0.6607,-5.51299 -2.13668,-12.88111 -3.27996,-16.37361 -4.14083,-12.6495 -2.97053,-52.86332 1.81234,-62.27538 2.13959,-4.21044 4.27768,-10.07872 4.75131,-13.04062 0.47362,-2.96191 1.34793,-5.68615 1.94291,-6.05386 0.59497,-0.36772 0.73155,-4.81164 0.3035,-9.87538 -1.13722,-13.45339 3.19116,-27.03028 13.75552,-43.147062 2.66507,-4.06578 4.84557,-7.88574 4.84557,-8.4888 0,-2.55163 7.09131,-17.46337 10.08337,-21.20351 1.78839,-2.23553 4.23199,-5.37878 5.43021,-6.985 2.26138,-3.03139 9.01716,-11.70002 13.99844,-17.962028 1.58014,-1.986399 7.09124,-7.129899 12.2469,-11.43 5.15565,-4.300099 11.44215,-9.818613 13.97,-12.263363 2.52784,-2.44475 6.23954,-5.588 8.24821,-6.985 2.00867,-1.397 5.15192,-3.725918 6.985,-5.175376 1.83308,-1.449456 6.51402,-4.431717 10.4021,-6.627246 3.88808,-2.195529 8.17433,-5.221224 9.525,-6.723765 1.35067,-1.502542 6.57609,-4.665485 11.61203,-7.028762 5.03595,-2.363277 11.32245,-5.775912 13.97,-7.583635 7.44135,-5.080881 37.4172,-19.316139 45.00859,-21.374171 15.28657,-4.144203 26.1245,-7.29134 30.92514,-8.9801 33.59251,-11.8171 113.39562,-2.479632 157.47999,18.426157 7.6835,3.643686 15.30052,7.133293 16.92672,7.754681 3.67772,1.405297 20.43166,11.534069 27.25852,16.479422 2.79997,2.028287 5.59061,3.687795 6.20143,3.687795 1.60547,0 17.69886,10.196966 20.94363,13.270139 1.51267,1.432674 7.20498,6.319611 12.64957,10.859861 23.9326,19.957424 48.47835,50.086061 54.3509,66.712941 1.22618,3.47163 2.75024,7.31219 3.38681,8.53456 2.51938,4.83782 4.39593,7.90917 5.7275,9.3742 0.76731,0.84421 3.3233,6.867062 5.67997,13.384112 4.24998,11.75271 4.64996,15.2012 3.32782,28.69168 -0.5565,5.67826 -0.28998,6.87778 1.7736,7.98218 1.58164,0.84646 2.42852,2.65914 2.42852,5.19808 0,2.1441 1.45557,6.79636 3.23461,10.33835 3.11269,6.19723 6.67089,46.50604 3.87882,43.94094 -0.47173,-0.43337 -1.1089,-7.1511 -1.41595,-14.9283 -0.62739,-15.89137 -2.34358,-20.77381 -7.99448,-22.74372 -2.52001,-0.87848 -4.49281,-3.07572 -6.3951,-7.12269 -1.513,-3.21878 -3.31949,-5.85233 -4.01441,-5.85233 -0.69493,0 -1.26349,-1.49381 -1.26349,-3.31959 0,-1.82576 -1.26856,-5.57666 -2.81901,-8.33531 -3.51042,-6.24595 -1.85138,-10.21496 5.99526,-14.34281 7.95344,-4.18404 5.32499,-18.31105 -5.7691,-31.006892 -2.06644,-2.36478 -3.75715,-4.68515 -3.75715,-5.1564 0,-0.47125 -1.85738,-2.75657 -4.1275,-5.07851 -3.47,-3.5492 -4.1275,-5.17066 -4.1275,-10.1787 0,-10.00069 -26.99565,-43.173947 -43.815,-53.841485 -3.14325,-1.993582 -7.04361,-4.851082 -8.66747,-6.35 -2.99282,-2.76255 -15.19131,-9.745509 -31.3017,-17.918489 -5.33922,-2.708644 -10.69729,-4.491817 -13.49699,-4.491817 -2.55356,0 -5.31783,-0.813326 -6.14282,-1.807391 -0.84849,-1.022365 -2.84668,-1.563192 -4.60051,-1.245162 -2.82361,0.512017 -3.72439,-0.469198 -3.26896,-3.560787 0.0728,-0.494086 -2.00265,-1.219126 -4.6121,-1.611201 -9.03346,-1.357294 -17.45557,-3.493027 -19.98444,-5.067786 -1.397,-0.869929 -5.81712,-1.989783 -9.82249,-2.488564 -4.00536,-0.498782 -7.61097,-1.438379 -8.01244,-2.087993 -0.40148,-0.649614 -2.34077,-1.181117 -4.30952,-1.181117 -1.96875,0 -4.23847,-0.793962 -5.04383,-1.764361 -0.80537,-0.970398 -2.73272,-2.070288 -4.28301,-2.444199 -1.55029,-0.373911 -6.67913,-2.097465 -11.39742,-3.830119 -4.71828,-1.732655 -9.59761,-2.883833 -10.84293,-2.558172 -1.24532,0.32566 -7.6719,-0.22845 -14.28129,-1.231356 -18.43904,-2.797931 -24.39416,-2.975187 -47.93929,-1.426933 -7.88273,0.518345 -14.63158,1.426801 -14.99746,2.018792 -0.36587,0.591992 -0.06,1.076348 0.67973,1.076348 0.73972,0 1.34496,0.636391 1.34496,1.414201 0,0.954601 -1.84161,1.138036 -5.66664,0.564435 -3.11666,-0.46737 -6.68853,-0.303029 -7.9375,0.365203 -1.24897,0.668231 -5.41411,1.613004 -9.25586,2.099494 -3.84175,0.48649 -7.5565,1.253581 -8.255,1.704647 -0.6985,0.451067 -2.413,0.954029 -3.81,1.117694 -21.11024,2.473164 -25.40507,3.847698 -29.99999,9.601332 -2.24672,2.813273 -7.12553,2.211606 -5.39671,-0.665535 0.6826,-1.136018 0.34991,-1.102372 -1.15351,0.116649 -2.50063,2.027606 -10.45697,4.237823 -14.75325,4.098349 -1.67391,-0.05434 -3.7805,0.512879 -4.68133,1.260491 -1.14581,0.950951 -2.38272,0.96065 -4.11739,0.03228 -1.95889,-1.048372 -3.37596,-0.692507 -6.74869,1.694787 -10.61187,7.511354 -19.40419,12.493992 -22.49631,12.748762 -4.63338,0.381764 -9.07782,1.914221 -9.07782,3.13007 0,1.611343 -9.43233,7.707138 -11.92564,7.707138 -1.24527,0 -4.64355,1.622256 -7.55174,3.605015 -2.90819,1.982758 -7.69836,5.191168 -10.64482,7.1298 -2.94647,1.938631 -6.79544,5.410406 -8.55329,7.715055 -4.61248,6.047275 -7.10448,8.332975 -8.28777,7.601655 -1.49782,-0.925707 -14.0732,11.81456 -24.04212,24.35736 -3.77771,4.75306 -4.25512,6.25086 -4.45286,13.97 -0.22147,8.64612 -0.22147,8.64612 -7.95735,16.38292 -14.8723,14.874082 -19.6573,29.224822 -12.41676,37.239242 2.4737,2.73809 4.74411,6.25817 5.04536,7.82242 0.30125,1.56425 1.70951,4.08076 3.12948,5.59224 2.78949,2.96929 0.54414,6.60292 -5.36998,8.69019 -1.22237,0.43142 -2.2225,2.0252 -2.2225,3.54174 0,3.05312 -2.42985,5.36469 -8.41972,8.00984 -9.12756,4.03077 -10.9814,53.98968 -2.33818,63.01126 3.50509,3.65852 5.40882,10.62277 3.26585,11.9472 -4.63456,2.86431 1.73551,18.84204 11.15172,27.97133 7.50261,7.27399 7.54458,7.44637 3.32533,13.65555 -4.03432,5.93704 -3.98851,7.07807 0.65328,16.27342 2.10556,4.17107 3.24113,7.58377 2.5235,7.58377 -0.71764,0 -2.51426,-2.71462 -3.9925,-6.0325 z m 503.69738,-18.43294 c -1.18021,-4.70234 0.0331,-8.86886 3.93204,-13.50245 2.39211,-2.84286 3.3754,-5.65701 3.69518,-10.57538 1.07904,-16.59683 2.68385,-25.03297 5.31101,-27.91883 1.45848,-1.60209 3.01546,-3.77015 3.45996,-4.8179 1.03207,-2.43276 0.20784,3.99907 -2.324,18.13524 -1.84101,10.27903 -9.35402,35.27766 -12.0501,40.09532 -0.95731,1.71061 -1.29943,1.47128 -2.02409,-1.416 z"
+         id="path12858"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#2a3233;stroke-width:1.26999998"
+         d="m -29.11929,443.18184 c -23.37146,-2.77912 -44.27318,-8.39597 -69.85,-18.77051 -3.14325,-1.27496 -8.28675,-3.3373 -11.43,-4.58297 -11.98108,-4.74807 -16.00865,-6.77653 -18.14732,-9.13972 -1.21516,-1.34275 -4.63,-3.20166 -7.58853,-4.13093 -2.95853,-0.92928 -6.52215,-2.64008 -7.91914,-3.80178 -1.397,-1.16171 -4.826,-3.47599 -7.62,-5.14287 -2.794,-1.66687 -8.5839,-6.28149 -12.86644,-10.25471 -4.28254,-3.97321 -9.71179,-8.20473 -12.065,-9.40335 -2.35321,-1.19864 -8.1839,-6.39196 -12.95709,-11.54073 -4.77319,-5.14879 -9.34519,-9.36414 -10.16,-9.36748 -1.40531,-0.006 -7.36425,-5.40544 -12.91147,-11.6997 -2.64106,-2.99672 -9.8723,-13.45569 -16.61219,-24.02716 -2.1517,-3.37493 -4.77946,-7.14555 -5.83946,-8.37917 -1.06,-1.2336 -2.31297,-3.32387 -2.78438,-4.64503 -0.94501,-2.64846 0.10796,-2.06765 6.82103,3.76251 1.74625,1.51658 4.74663,3.12768 6.6675,3.58022 2.83632,0.6682 3.4925,1.5278 3.4925,4.57517 0,2.76231 1.53097,5.28334 5.80233,9.55471 3.19128,3.19128 6.62326,7.7671 7.62662,10.16848 2.12276,5.08047 4.80553,7.04562 14.85498,10.8814 5.74807,2.19398 7.17971,3.26753 6.63711,4.97709 -0.80556,2.53812 6.66752,8.87755 10.46509,8.87755 1.30658,0 4.3019,1.98743 6.65625,4.41651 4.73979,4.89021 12.18132,7.80332 23.14092,9.05887 8.7512,1.00255 13.56814,4.91616 11.29005,9.17279 -1.47591,2.75778 -0.64883,6.56183 1.42668,6.56183 0.69756,0 2.94017,1.40679 4.98357,3.12619 2.04339,1.7194 5.76537,3.69226 8.27104,4.3841 2.50567,0.69185 5.48831,2.0836 6.62807,3.09278 2.39576,2.12129 16.79617,6.16118 32.86978,9.22131 1.57162,0.2992 2.8575,1.29864 2.8575,2.22096 0,3.05675 6.10347,4.75918 34.29,9.56442 11.54769,1.96866 32.71772,3.73482 46.98999,3.92028 6.63575,0.0862 13.7795,0.22456 15.875,0.30741 6.85658,0.27111 16.33977,-0.62317 26.035,-2.45513 5.23875,-0.98989 12.3812,-2.08454 15.87213,-2.43256 7.61609,-0.75926 12.70287,-2.98848 12.70287,-5.56687 0,-3.05354 3.36378,-4.73436 10.80487,-5.399 6.62665,-0.59191 17.00811,-3.87647 19.61163,-6.20488 0.66357,-0.59345 2.37807,-1.08115 3.81,-1.08376 1.43192,-0.003 4.47469,-1.14561 6.76169,-2.54 2.28701,-1.39439 5.18728,-2.53525 6.44506,-2.53525 6.36304,0 33.55739,-11.7007 37.09154,-15.9591 0.83117,-1.0015 2.9454,-1.8209 4.6983,-1.8209 6.28893,0 14.48259,-6.82406 9.5194,-7.92818 -1.60481,-0.35702 -0.36942,-1.67104 4.445,-4.72796 3.66713,-2.32844 6.6675,-4.58469 6.6675,-5.01388 0,-0.42919 3.00038,-2.092 6.6675,-3.69514 15.40046,-6.73255 18.0275,-8.50952 20.71153,-14.00959 2.49858,-5.12002 14.18007,-16.69525 16.84851,-16.69525 0.7,0 3.11265,-2.42888 5.36144,-5.3975 6.2481,-8.24812 3.13203,-1.44209 -3.63167,7.93219 -15.01446,20.80951 -52.51676,54.60494 -70.94993,63.93699 -3.6177,1.83151 -6.92749,3.89611 -7.3551,4.588 -0.42762,0.6919 -3.46844,2.44397 -6.75738,3.89348 -3.28894,1.44952 -8.03614,4.05506 -10.54934,5.7901 -4.55801,3.1467 -14.8797,7.51035 -22.10055,9.34331 -2.0955,0.53194 -7.34223,2.75678 -11.6594,4.9441 -7.39696,3.74772 -13.83999,5.87379 -22.6306,7.46764 -2.0955,0.37994 -4.51878,1.25582 -5.38504,1.94641 -7.75949,6.1858 -71.96117,10.6479 -101.92995,7.08426 z M 238.85069,317.90441 c 0,-2.86811 4.75843,-10.94952 8.35614,-14.19156 3.52335,-3.175 3.52335,-3.175 -0.64395,4.445 -4.61112,8.43151 -7.71219,12.35062 -7.71219,9.74656 z m -490.8133,-36.12094 c -1.07067,-2.56246 -1.94667,-6.90407 -1.94667,-9.64801 0,-3.07826 -1.71334,-8.59678 -4.47362,-14.40915 -3.13591,-6.60333 -4.83473,-12.43409 -5.68124,-19.49933 -0.66418,-5.54351 -2.14301,-12.93663 -3.28629,-16.42913 -4.14083,-12.6495 -2.97053,-52.86332 1.81234,-62.27538 2.13959,-4.21044 4.27768,-10.07872 4.75131,-13.04062 0.47362,-2.96191 1.34793,-5.68615 1.94291,-6.05386 0.59497,-0.36772 0.73155,-4.81164 0.3035,-9.87538 -1.13722,-13.45339 3.19116,-27.03028 13.75552,-43.147062 2.66507,-4.06578 4.84557,-7.88574 4.84557,-8.4888 0,-2.55163 7.09131,-17.46337 10.08337,-21.20351 1.78839,-2.23553 4.23199,-5.37878 5.43021,-6.985 2.26138,-3.03139 9.01716,-11.70002 13.99844,-17.962028 1.58014,-1.986399 7.09124,-7.129899 12.2469,-11.43 5.15565,-4.300099 11.44215,-9.818613 13.97,-12.263363 2.52784,-2.44475 6.23954,-5.588 8.24821,-6.985 2.00867,-1.397 5.15192,-3.725918 6.985,-5.175376 1.83308,-1.449456 6.51402,-4.431717 10.4021,-6.627246 3.88808,-2.195529 8.17433,-5.221224 9.525,-6.723765 1.35067,-1.502542 6.56545,-4.660505 11.5884,-7.017695 5.02295,-2.357191 11.5952,-5.92614 14.605,-7.930999 12.82791,-8.544796 42.82122,-21.76992 49.37208,-21.76992 1.96646,0 3.92858,-0.5715 4.36028,-1.27 0.4317,-0.6985 1.8742,-1.27 3.20557,-1.27 1.33136,0 5.04948,-0.892509 8.26249,-1.983351 10.0605,-3.415627 9.11892,-1.2574 -1.71751,3.9368 -7.3182,3.507811 -10.38555,5.654162 -10.38555,7.267174 0,1.259002 -0.92691,2.644783 -2.0598,3.079511 -1.77911,0.68271 -1.67892,1.077715 0.7353,2.898764 2.79144,2.105582 2.78829,2.110594 -2.40337,3.823998 -2.85918,0.94361 -5.83179,2.47873 -6.60582,3.411379 -0.77402,0.93265 -2.41054,1.695725 -3.63672,1.695725 -3.16519,0 -10.71382,3.322296 -12.53528,5.517023 -0.84617,1.019574 -3.08668,2.241959 -4.9789,2.71641 -1.89223,0.474453 -4.50836,2.00819 -5.81366,3.408306 -1.39183,1.492956 -3.36329,2.286759 -4.76739,1.919579 -2.58603,-0.676262 -11.60513,3.153377 -15.71936,6.67467 -5.31888,4.55234 -16.07019,10.5552 -19.39461,10.828741 -4.72435,0.388733 -9.18038,1.911941 -9.18038,3.138134 0,1.611343 -9.43234,7.707138 -11.92565,7.707138 -1.24527,0 -4.64355,1.622256 -7.55174,3.605015 -2.90818,1.982758 -7.69836,5.191168 -10.64482,7.1298 -2.94647,1.938631 -6.79544,5.410406 -8.55328,7.715055 -4.61249,6.047275 -7.10449,8.332971 -8.28778,7.601651 -1.11322,-0.688005 -4.80582,2.64626 -14.15297,12.77951 -3.22918,3.50076 -7.13528,7.20431 -8.68023,8.23012 -3.47675,2.30847 -6.66256,13.73665 -5.62979,20.19523 0.68827,4.30423 0.23917,5.11761 -6.72267,12.17549 -17.82022,18.066072 -21.92018,29.452062 -13.82991,38.407022 2.55427,2.82728 4.8906,6.42032 5.19184,7.98457 0.30125,1.56425 1.70951,4.08076 3.12948,5.59224 2.78949,2.96929 0.54414,6.60292 -5.36998,8.69019 -1.22237,0.43142 -2.2225,2.0252 -2.2225,3.54174 0,3.05221 -2.4297,5.36455 -8.4137,8.00733 -9.59229,4.23634 -11.29868,53.08775 -2.20132,63.02099 3.40096,3.71344 5.21455,10.64731 3.12297,11.93998 -4.63456,2.86431 1.73551,18.84204 11.15172,27.97133 7.71747,7.48231 7.7096,7.42479 2.097,15.31902 -4.40333,6.19339 -4.40333,6.19339 -6.35,1.53434 z m 510.45999,-3.80606 c -1.18021,-4.70234 0.0331,-8.86886 3.93204,-13.50245 2.39211,-2.84286 3.3754,-5.65701 3.69518,-10.57538 1.07904,-16.59683 2.68385,-25.03297 5.31101,-27.91883 1.45848,-1.60209 3.01546,-3.77015 3.45996,-4.8179 1.03207,-2.43276 0.20784,3.99907 -2.324,18.13524 -1.84101,10.27903 -9.35402,35.27766 -12.0501,40.09532 -0.95731,1.71061 -1.29943,1.47128 -2.02409,-1.416 z m 17.8251,-85.93868 c -0.49381,-6.23927 -0.54404,-11.91659 -0.11163,-12.61628 1.69247,-2.7385 -3.36576,-11.8527 -7.54783,-13.60008 -2.83875,-1.18611 -4.91078,-3.46495 -6.79479,-7.47301 -1.49079,-3.17158 -3.27913,-5.76651 -3.97405,-5.76651 -0.69493,0 -1.26349,-1.49381 -1.26349,-3.31959 0,-1.82576 -1.26856,-5.57666 -2.81901,-8.33531 -3.51215,-6.24903 -1.84253,-10.23931 5.98366,-14.30053 3.71742,-1.92908 5.26764,-3.643 5.81992,-6.43452 0.75282,-3.80505 0.75282,-3.80505 1.72493,0.87467 0.55663,2.6796 0.40659,6.47921 -0.35104,8.89 -1.86747,5.94227 -1.20566,12.43565 1.41704,13.90339 1.33246,0.74568 2.1945,2.75222 2.1945,5.10806 0,2.13398 1.45557,6.77794 3.23461,10.31993 2.87915,5.73226 3.31954,8.50892 4.00745,25.26693 0.84993,20.70532 -0.10262,25.39399 -1.52027,7.48285 z m -15.82519,-88.03338 c -0.63278,-1.57162 -2.76177,-4.589642 -4.73107,-6.706702 -1.96928,-2.11707 -4.97366,-5.83182 -6.67637,-8.255 -1.70271,-2.42319 -4.14909,-5.16485 -5.43642,-6.09258 -1.7867,-1.2876 -2.15746,-2.66294 -1.56673,-5.81179 1.95984,-10.44686 -23.25806,-42.497183 -43.87601,-55.763568 -3.14325,-2.02249 -7.04361,-4.903639 -8.66747,-6.402557 -1.62386,-1.498919 -7.33886,-5.133113 -12.7,-8.075989 -20.4697,-11.236374 -27.44912,-14.334317 -32.29405,-14.334317 -2.93914,0 -5.10185,-0.687057 -5.50988,-1.750408 -0.36943,-0.962724 -6.20938,-4.439005 -12.97765,-7.725067 -21.49048,-10.433842 -23.2924,-11.435303 -24.46812,-13.598797 -0.63343,-1.1656 -2.92914,-2.509664 -5.10157,-2.986809 -2.17243,-0.477146 -5.08231,-1.999974 -6.46639,-3.384063 -1.38409,-1.384088 -2.82979,-2.203255 -3.21268,-1.820373 -1.6172,1.617199 -5.3934,-2.107939 -6.13293,-6.050004 -0.73238,-3.90386 -1.71099,-4.679174 -11.5676,-9.164419 -12.60914,-5.737791 -12.03383,-6.794305 1.74548,-3.205382 16.1726,4.212275 24.36214,7.177302 39.08787,14.15177 7.6835,3.639095 15.30052,7.124947 16.92672,7.746335 3.67772,1.405297 20.43166,11.534069 27.25852,16.479422 2.79997,2.028287 5.59061,3.687795 6.20143,3.687795 1.60547,0 17.69886,10.196966 20.94363,13.270139 1.51267,1.432674 7.20498,6.319611 12.64957,10.859861 23.82477,19.8675 48.53053,50.211041 54.38015,66.789521 1.21008,3.42952 2.81022,7.37848 3.55585,8.77548 3.38678,6.34545 4.19763,7.66703 5.12563,8.35406 1.24987,0.92534 6.21673,13.870942 5.32187,13.870942 -0.36369,0 -1.17899,-1.28587 -1.81178,-2.8575 z M -46.58239,-59.824654 c 0.80014,-2.403081 4.7631,-2.744725 4.7631,-0.410622 0,1.178097 -1.08585,1.998122 -2.64583,1.998122 -1.55192,0 -2.4273,-0.656356 -2.11727,-1.5875 z m 97.47309,0.3175 c -1.57747,-1.019439 -1.4683,-1.231106 0.635,-1.231106 1.397,0 3.39725,0.553998 4.445,1.231106 1.57747,1.019439 1.4683,1.231105 -0.635,1.231105 -1.397,0 -3.39725,-0.553997 -4.445,-1.231105 z"
+         id="path12856"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>
diff --git a/resources/fonts/svg/planets/mars.svg b/resources/fonts/svg/planets/mars.svg
new file mode 100644
index 0000000..b8d4e88
--- /dev/null
+++ b/resources/fonts/svg/planets/mars.svg
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="107"
+   height="106"
+   viewBox="0 0 28.310416 28.045834"
+   version="1.1"
+   id="svg9974"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)"
+   sodipodi:docname="mars.svg">
+  <defs
+     id="defs9968" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1.979899"
+     inkscape:cx="384.97001"
+     inkscape:cy="172.00904"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:window-width="2560"
+     inkscape:window-height="1531"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata9971">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-268.95415)">
+    <g
+       id="g10618"
+       transform="translate(-2.2902209,2.6285183)">
+      <path
+         style="fill:#d4d1cc;stroke-width:1.26999998"
+         d="M 6.60883,291.3079 C 2.82881,288.12723 2.54,287.32024 2.54,279.93894 c 0,-10.85043 2.14969,-12.78665 14.18508,-12.77648 7.91282,0.007 9.08437,0.33689 11.26486,3.175 3.64652,4.74629 3.39598,15.30771 -0.46975,19.80189 -2.37404,2.75998 -4.33359,3.55491 -9.90117,4.01662 -6.13551,0.5088 -7.41373,0.17816 -11.01019,-2.84807 z"
+         id="path10597"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#cbb48e;stroke-width:1.26999998"
+         d="m 11.8274,293.05985 c -5.72845,-1.65738 -9.29618,-6.69573 -9.24701,-13.05862 0.0939,-12.15188 14.20013,-18.09532 22.72192,-9.57354 3.26762,3.26762 3.90107,4.8798 3.8673,9.8425 -0.0222,3.26414 -0.79516,6.92887 -1.71766,8.14384 -1.37764,1.8144 -9.93855,6.34066 -11.21866,5.93143 -0.19706,-0.063 -2.17971,-0.64152 -4.40589,-1.28561 z"
+         id="path10595"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#ccac76;stroke-width:1.26999998"
+         d="M 11.8274,293.05985 C 6.08233,291.39767 2.54,286.37092 2.54,279.88056 c 0,-5.66173 2.07101,-8.52648 7.32094,-10.12678 1.58177,-0.48216 2.54817,-1.40701 2.14755,-2.05522 -0.40062,-0.64821 1.39748,-1.17856 3.99578,-1.17856 5.95942,0 11.36402,5.55756 12.61705,12.97415 1.12859,6.68008 -0.99773,10.49851 -7.23619,12.99464 -2.70959,1.08417 -5.05784,1.91967 -5.21833,1.85667 -0.16049,-0.063 -2.11322,-0.64152 -4.3394,-1.28561 z M 12.7,272.87 c 0,-0.6985 -0.85725,-1.27 -1.905,-1.27 -1.04775,0 -1.905,0.5715 -1.905,1.27 0,0.6985 0.85725,1.27 1.905,1.27 1.04775,0 1.905,-0.5715 1.905,-1.27 z"
+         id="path10593"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#aaa499;stroke-width:1.26999998"
+         d="m 11.8274,293.05985 c -8.68856,-2.51381 -12.74282,-15.46417 -6.55723,-20.94552 2.72738,-2.41686 2.7293,-2.41577 1.8808,1.07317 -0.82167,3.37862 -0.68021,3.4925 4.33833,3.4925 2.85322,0 6.2331,0.55949 7.51083,1.24331 1.68767,0.90321 2.74958,0.72951 3.88202,-0.635 2.62298,-3.1605 4.89876,-2.11284 5.79566,2.66805 1.14196,6.08719 -1.18695,10.0894 -7.29268,12.53243 -2.70959,1.08417 -5.05784,1.91967 -5.21833,1.85667 -0.16049,-0.063 -2.11322,-0.64152 -4.3394,-1.28561 z M 18.70968,269.06 c -1.90132,-2.22057 -1.9254,-2.54 -0.19154,-2.54 1.91488,0 5.61186,2.86851 5.61186,4.35429 0,1.67685 -3.40777,0.5362 -5.42032,-1.81429 z m -6.24781,-2.13755 c 0.91679,-0.36687 2.01216,-0.32175 2.43417,0.10026 0.42201,0.42201 -0.32808,0.72217 -1.66687,0.66703 -1.47948,-0.0609 -1.78042,-0.36188 -0.7673,-0.76729 z"
+         id="path10591"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#aa926a;stroke-width:1.26999998"
+         d="M 10.5775,291.38815 C 8.36237,290.39735 5.93349,288.43477 5.18,287.02684 3.20659,283.3395 3.56627,276.45797 5.88674,273.505 c 1.9635,-2.49868 1.98211,-2.49352 1.14403,0.3175 -0.78704,2.63984 -0.4521,2.8575 4.39714,2.8575 2.88699,0 6.29449,0.55949 7.57222,1.24331 1.68767,0.90321 2.74958,0.72951 3.88202,-0.635 3.02261,-3.64202 5.05785,-2.18724 5.05785,3.61531 0,9.24409 -8.56821,14.41809 -17.3625,10.48453 z M 18.70968,269.06 c -2.07817,-2.42713 -2.05309,-2.54 0.56442,-2.54 1.50658,0 2.36712,0.37212 1.91231,0.82693 -0.45481,0.45481 0.16431,1.57963 1.37583,2.49961 1.83203,1.39116 1.87621,1.67948 0.26251,1.71307 -1.06714,0.0222 -2.91892,-1.10261 -4.11507,-2.49961 z m -6.24781,-2.13755 c 0.91679,-0.36687 2.01216,-0.32175 2.43417,0.10026 0.42201,0.42201 -0.32808,0.72217 -1.66687,0.66703 -1.47948,-0.0609 -1.78042,-0.36188 -0.7673,-0.76729 z"
+         id="path10589"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#8a7656;stroke-width:1.26999998"
+         d="m 12.065,291.97838 c -3.93948,-1.62947 -8.53329,-7.20045 -8.04182,-9.75242 0.35226,-1.82915 1.74224,-2.33088 6.78244,-2.44821 3.48666,-0.0812 7.60833,0.41566 9.15927,1.10406 2.00275,0.88893 3.37846,0.78805 4.74747,-0.34813 1.57488,-1.30703 2.06618,-1.23858 2.68502,0.3741 1.34829,3.51358 -0.0184,6.41616 -4.41648,9.38006 -4.54319,3.06165 -6.76804,3.40621 -10.9159,1.69054 z m 6.74687,-25.05593 c 0.91679,-0.36687 2.01216,-0.32175 2.43417,0.10026 0.42201,0.42201 -0.32808,0.72217 -1.66687,0.66703 -1.47948,-0.0609 -1.78042,-0.36188 -0.7673,-0.76729 z"
+         id="path10587"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#6d6350;stroke-width:1.26999998"
+         d="M 8.5725,289.41168 C 6.65162,287.89586 5.08,285.82899 5.08,284.81863 c 0,-1.88631 0.90501,-1.81904 11.37884,0.84579 3.68432,0.9374 4.86352,0.67667 7.3025,-1.61463 L 26.67,281.31724 v 3.24087 c 0,4.27229 -2.62778,6.27185 -9.20819,7.00683 -4.22402,0.47178 -6.15577,0.004 -8.88931,-2.15326 z"
+         id="path10585"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#514939;stroke-width:1.26999998"
+         d="m 11.7475,290.61042 c -1.57163,-0.63171 -2.8575,-1.73849 -2.8575,-2.4595 0,-1.51946 2.42452,-1.56171 9.525,-0.16597 l 5.08,0.99857 -3.57518,1.46824 c -4.11918,1.69165 -4.34743,1.69608 -8.17232,0.15866 z"
+         id="path10583"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>
diff --git a/resources/fonts/svg/planets/mercury.svg b/resources/fonts/svg/planets/mercury.svg
new file mode 100644
index 0000000..ccc0981
--- /dev/null
+++ b/resources/fonts/svg/planets/mercury.svg
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="86"
+   height="80"
+   viewBox="0 0 22.754166 21.166667"
+   version="1.1"
+   id="svg8530"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)"
+   sodipodi:docname="mercury.svg">
+  <defs
+     id="defs8524" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="7.9195959"
+     inkscape:cx="53.982195"
+     inkscape:cy="68.084828"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:window-width="2560"
+     inkscape:window-height="1531"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata8527">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-275.83331)">
+    <g
+       id="g9228"
+       transform="translate(-0.06681738,0.46772167)">
+      <path
+         style="fill:#d7cfc5;stroke-width:1.29455757"
+         d="m 3.23515,294.12412 c -3.73643,-3.88233 -4.09546,-11.10046 -0.81348,-16.355 0.83702,-1.3401 3.59405,-1.94636 9.00132,-1.97938 6.2828,-0.0383 8.14483,0.44925 9.59964,2.51389 2.62951,3.73177 2.32289,13.03727 -0.5176,15.70824 -3.09181,2.90731 -14.50844,2.98152 -17.26988,0.11225 z"
+         id="path9206"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#d7c4ac;stroke-width:1.26999998"
+         d="m 3.755,292.70515 c -5.41253,-6.88091 -0.57806,-16.8279 8.17875,-16.8279 6.03835,0 9.62378,3.96688 9.62378,10.64764 0,6.9289 -2.79394,9.67236 -9.85032,9.67236 -4.16574,0 -5.75392,-0.69743 -7.95221,-3.4921 z"
+         id="path9204"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#cfb290;stroke-width:1.26999998"
+         d="m 6.60742,294.6349 c -3.00827,-1.44979 -4.94979,-6.19503 -3.4967,-8.54619 0.46328,-0.74959 1.37433,-1.03409 2.02456,-0.63222 0.65024,0.40186 1.18225,-0.11269 1.18225,-1.14346 0,-1.03076 -0.71438,-2.12399 -1.5875,-2.4294 -1.14179,-0.39938 -0.90824,-1.32052 0.83183,-3.28083 5.60212,-6.31117 15.99567,-1.36482 15.99567,7.61242 0,7.93353 -7.3591,12.07807 -14.95011,8.41968 z"
+         id="path9202"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#b7a490;stroke-width:1.26999998"
+         d="m 6.21901,293.83285 -2.91992,-2.36441 5.2974,-3.03309 c 5.62029,-3.21798 7.82406,-6.2081 4.57547,-6.2081 -2.24785,0 -5.58443,-2.981 -5.58443,-4.98929 0,-2.42172 7.50792,-1.44137 10.78534,1.40832 2.31073,2.00915 3.18466,4.03947 3.18466,7.39862 0,8.57709 -8.82834,13.05957 -15.33852,7.78795 z"
+         id="path9200"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#c4a37b;stroke-width:1.26999998"
+         d="m 10.76253,295.27826 c -2.87661,-0.70708 -5.715,-2.75158 -5.715,-4.11653 0,-0.50298 1.98604,-1.98406 4.41342,-3.29129 4.81356,-2.59227 6.81987,-5.64319 3.71101,-5.64319 -2.16087,0 -4.77471,-2.37851 -5.26693,-4.79274 -0.19913,-0.97668 1.22132,-1.55402 3.81,-1.54858 3.24272,0.007 4.74004,0.90844 6.985,4.20609 1.57162,2.30857 2.8575,5.47389 2.8575,7.03404 0,2.76916 -5.23464,9.21701 -7.24477,8.92387 -0.55563,-0.081 -2.15323,-0.42828 -3.55023,-0.77167 z"
+         id="path9198"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#ae9b88;stroke-width:1.26999998"
+         d="m 11.08003,295.32181 c -2.16073,-0.56466 -3.17957,-2.92584 -1.27,-2.94324 1.50683,-0.0137 6.6675,-8.17745 6.6675,-10.54741 0,-1.22173 -0.98814,-2.14391 -2.29727,-2.14391 -1.26349,0 -3.40662,-0.83907 -4.7625,-1.86461 -2.31997,-1.75474 -2.20332,-1.86648 1.97977,-1.89632 3.62458,-0.0259 4.97241,0.74301 7.3025,4.1657 3.68816,5.41757 3.61423,9.11395 -0.25977,12.98796 -3.17322,3.17322 -3.46088,3.26083 -7.36023,2.24183 z"
+         id="path9196"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#af916f;stroke-width:1.26999998"
+         d="m 8.85753,293.65725 c 0,-0.6985 0.42862,-1.27391 0.9525,-1.27868 1.50683,-0.0137 6.6675,-8.17745 6.6675,-10.54741 0,-1.17977 -0.99949,-2.16207 -2.2225,-2.1843 -1.22238,-0.0222 -3.33186,-0.87947 -4.68774,-1.905 -2.30875,-1.74625 -2.16292,-1.86461 2.29746,-1.86461 2.684,0 4.4373,0.52648 4.01716,1.20628 -0.41004,0.66345 0.41348,1.82656 1.83005,2.58468 3.41819,1.82936 3.57819,10.14801 0.25307,13.1572 -2.5254,2.28546 -9.1075,2.88664 -9.1075,0.83184 z"
+         id="path9194"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#917457;stroke-width:1.26999998"
+         d="m 12.66753,294.22636 c 0,-1.45537 5.75643,-10.70997 6.6675,-10.71933 1.94516,-0.02 0.8703,7.29094 -1.37,9.31838 -2.24414,2.03092 -5.2975,2.8384 -5.2975,1.40095 z M 9.82484,276.24903 c 1.57977,-0.30378 3.86577,-0.29224 5.08,0.0256 1.21423,0.31787 -0.0783,0.56641 -2.87231,0.55232 -2.794,-0.0141 -3.78746,-0.27418 -2.20769,-0.57795 z"
+         id="path9192"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>
diff --git a/resources/fonts/svg/planets/neptune.svg b/resources/fonts/svg/planets/neptune.svg
new file mode 100644
index 0000000..db3aa82
--- /dev/null
+++ b/resources/fonts/svg/planets/neptune.svg
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="717"
+   height="720"
+   viewBox="0 0 189.70625 190.50001"
+   version="1.1"
+   id="svg11332"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)"
+   sodipodi:docname="neptune.svg">
+  <defs
+     id="defs11326" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.35"
+     inkscape:cx="-137.14286"
+     inkscape:cy="560"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:window-width="2560"
+     inkscape:window-height="1531"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata11329">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-106.49998)">
+    <g
+       id="g11980"
+       transform="translate(275.1117,-13.076593)">
+      <path
+         style="fill:#b4c7d7;stroke-width:1.26999998"
+         d="m -211.14242,306.48365 c -14.7503,-5.13489 -25.04203,-11.8041 -37.38825,-24.22822 -8.05749,-8.10834 -12.16131,-13.49762 -16.0347,-21.05734 -9.1333,-17.82549 -9.91483,-21.42934 -9.91483,-45.71999 0,-24.29066 0.78153,-27.89451 9.91483,-45.72 6.5476,-12.77898 22.70456,-29.57929 35.7537,-37.17737 32.96281,-19.19315 75.90497,-16.60208 106.92086,6.45146 15.28075,11.3579 29.14055,32.34829 34.12454,51.68091 3.27425,12.70066 2.88796,39.82065 -0.73265,51.43499 -5.06562,16.24979 -12.22085,28.17744 -24.07376,40.13052 -12.3676,12.47212 -23.86048,19.78501 -38.61361,24.56977 -8.99926,2.91865 -11.99833,3.24301 -29.845,3.22772 -18.45318,-0.0158 -20.5585,-0.26698 -30.11113,-3.59245 z"
+         id="path11957"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#92a5ca;stroke-width:1.26999998"
+         d="m -200.99263,308.20698 c -10.93496,-2.3791 -19.87352,-6.36569 -31.83407,-14.19801 -39.03981,-25.56503 -53.16175,-76.62803 -32.95779,-119.17087 13.26988,-27.942 37.84745,-46.41703 70.93101,-53.31904 12.74086,-2.65804 19.8716,-2.40238 34.6218,1.24132 20.03199,4.94843 32.03198,11.57063 45.83018,25.29139 9.88613,9.83064 12.48509,13.33909 17.49069,23.61133 9.68115,19.86725 12.78253,37.93548 9.74512,56.77381 -5.32453,33.02327 -24.66063,59.09247 -54.01599,72.82516 -5.588,2.61412 -14.27331,5.67051 -19.3007,6.79198 -10.89624,2.43066 -29.71469,2.50169 -40.51025,0.15293 z m 20.51572,-97.84927 c -1.35588,-1.02554 -3.12468,-1.86461 -3.93067,-1.86461 -0.80598,0 -2.49983,-1.143 -3.7641,-2.54 -1.26426,-1.397 -3.0625,-2.54 -3.99607,-2.54 -0.93357,0 -2.41122,-1.143 -3.28366,-2.54 -1.63567,-2.61913 -4.78527,-3.48444 -4.78527,-1.31469 0,0.67393 1.143,1.83703 2.54,2.58469 3.68837,1.97395 3.11248,3.81 -1.19504,3.81 -3.55582,0 -6.20372,1.91456 -4.71157,3.40671 1.42204,1.42205 9.32398,2.68592 17.33661,2.7729 7.52171,0.0816 8.03602,-0.076 5.78977,-1.775 z"
+         id="path11955"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#8599c7;stroke-width:1.26999998"
+         d="m -191.34668,309.36626 c -25.59772,-4.4037 -41.44393,-12.62815 -57.77132,-29.98427 -7.24626,-7.70282 -11.03769,-13.12671 -15.29891,-21.88616 -6.31409,-12.97936 -10.69479,-30.19029 -10.69479,-42.01773 0,-11.95893 4.37804,-29.0329 10.86233,-42.36213 4.8543,-9.97859 7.67669,-13.84319 16.82521,-23.0382 15.43411,-15.51254 28.08875,-22.38589 51.58874,-28.02037 13.36802,-3.20519 18.51849,-3.09867 34.07331,0.70471 31.23817,7.63817 51.76,23.66488 65.05584,50.80599 7.09615,14.48553 9.45721,24.63475 9.48082,40.75434 0.0236,16.08015 -3.05793,28.88935 -10.66574,44.33565 -11.4762,23.3004 -34.14979,41.6028 -59.71365,48.20162 -7.6843,1.98356 -28.00654,3.49321 -33.74184,2.50655 z m -59.70247,-54.50314 c 1.27364,-1.53463 1.16744,-2.04389 -0.56486,-2.70863 -2.50223,-0.9602 -5.17021,1.22104 -3.93605,3.21795 1.17194,1.89625 2.64258,1.72983 4.50091,-0.50932 z m 27.86262,-21.20864 c 1.8804,-1.17433 3.52446,-1.14269 7.41947,0.14278 5.2675,1.73843 8.38121,0.9443 11.62569,-2.96507 1.31448,-1.58384 1.13095,-2.59026 -1.02431,-5.61704 -2.59373,-3.64256 -2.59579,-3.71455 -0.16876,-5.91098 1.35133,-1.22293 2.33676,-2.86984 2.18986,-3.65979 -0.1797,-0.96631 4.27078,-1.64407 13.60101,-2.07128 14.9474,-0.68442 18.33939,-2.42766 10.01016,-5.1445 -2.65555,-0.86619 -6.19121,-2.63478 -7.85704,-3.9302 -1.66582,-1.29542 -3.74322,-2.3553 -4.61643,-2.3553 -0.87322,0 -2.52382,-1.42875 -3.66801,-3.175 -1.14419,-1.74625 -2.92442,-3.175 -3.95606,-3.175 -2.27669,0 -2.52465,5.19307 -0.35173,7.366 2.21799,2.21798 -3.93355,2.00207 -12.30623,-0.43193 -4.52252,-1.31473 -6.93535,-2.74825 -7.3602,-4.37285 -0.83605,-3.19707 2.82446,-6.37122 7.34743,-6.37122 1.9855,0 4.51225,-0.78734 5.615,-1.74965 1.10275,-0.9623 4.60347,-2.1229 7.77938,-2.57911 5.05919,-0.72673 6.13412,-0.44654 8.67888,2.26223 2.66252,2.83413 3.7427,3.06597 12.96594,2.78296 5.53379,-0.16981 10.35005,-0.59735 10.70279,-0.95009 1.02269,-1.02269 -11.96873,-4.84634 -16.46616,-4.84634 -2.24812,0 -4.51337,-0.42588 -5.0339,-0.9464 -0.52052,-0.52053 -8.71107,-0.75391 -18.20123,-0.51864 -15.26947,0.37855 -17.7787,0.75673 -21.80777,3.28676 -2.50411,1.57245 -5.2673,2.61546 -6.14042,2.31781 -0.99999,-0.34091 -1.5875,0.673 -1.5875,2.73963 0,2.06963 -0.70328,3.28084 -1.905,3.28084 -1.04775,0 -1.905,0.85725 -1.905,1.905 0,1.27 1.27,1.905 3.81,1.905 4.29732,0 4.31554,0.0988 1.99068,10.795 -1.55495,7.15399 -1.49759,7.79475 0.93792,10.47749 1.57105,1.73054 4.07079,2.8575 6.33828,2.8575 3.18412,0 3.50765,0.28494 2.16312,1.905 -0.86956,1.04775 -2.65404,1.905 -3.96551,1.905 -2.80888,0 -2.90757,0.46689 -0.809,3.82724 1.82931,2.92918 2.66129,3.07088 5.95465,1.01415 z m 81.22393,-16.15432 c 1.41092,-0.89355 1.23891,-1.39243 -0.86901,-2.52056 -3.55895,-1.90469 -6.60507,-1.77241 -6.60507,0.28683 0,3.3598 3.86982,4.51635 7.47408,2.23373 z m -96.39955,-8.40276 c -0.88356,-1.06462 -1.86801,-1.67415 -2.18767,-1.35449 -0.88074,0.88074 0.90874,3.29019 2.4436,3.29019 0.84213,0 0.74579,-0.72871 -0.25593,-1.9357 z m 55.58797,-41.98577 c 4.47533,-0.90356 3.38045,-2.65158 -3.02866,-4.83539 -10.2449,-3.49078 -21.25003,1.81181 -11.44638,5.5152 2.52999,0.95572 7.53962,0.72045 14.47504,-0.67981 z"
+         id="path11953"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#8b9cb8;stroke-width:1.26999998"
+         d="m -197.43937,308.10949 c -28.25693,-4.89203 -54.31447,-24.58322 -66.97754,-50.61366 -6.31409,-12.97936 -10.69479,-30.19029 -10.69479,-42.01773 0,-11.95893 4.37804,-29.0329 10.86233,-42.36213 9.72384,-19.98852 29.53377,-37.88284 50.70906,-45.80553 10.43205,-3.90313 26.85602,-7.69783 33.36321,-7.70844 8.82269,-0.0144 30.57653,5.93376 41.53542,11.35703 45.55006,22.54154 64.13232,77.65973 42.11042,124.90678 -7.97676,17.11381 -20.31088,30.77715 -36.44092,40.36809 -19.10788,11.36156 -42.52249,15.67479 -64.46719,11.87559 z m -45.52006,-53.34338 c 3.9128,-1.79134 7.47321,-3.8379 7.91202,-4.5479 0.43881,-0.71001 2.08815,-0.96708 3.6652,-0.57126 1.57706,0.39582 5.65808,-0.0866 9.06895,-1.07196 3.41087,-0.98538 10.48783,-2.16938 15.72658,-2.63111 10.11702,-0.89167 19.9102,-4.72049 20.82017,-8.14001 1.95103,-7.33169 1.90331,-8.72065 -0.37026,-10.77822 -2.0259,-1.8334 -2.18449,-2.87092 -1.09498,-7.16324 1.19693,-4.71555 1.55291,-5.01391 5.88205,-4.93005 2.53441,0.0491 5.62266,0.49028 6.86277,0.98043 3.41737,1.35068 5.79872,6.80009 4.50264,10.30372 -1.46946,3.97233 -0.76837,4.34828 2.47462,1.32697 4.76411,-4.43844 6.73441,-2.03872 8.32965,10.14503 0.3635,2.77632 4.48653,3.10827 7.27401,0.58563 1.9787,-1.7907 2.77451,-1.89877 4.35051,-0.59082 1.58079,1.31194 1.92882,1.25351 1.92882,-0.32384 0,-1.05855 -0.85725,-2.25359 -1.905,-2.65565 -2.69598,-1.03455 -2.36164,-3.03464 0.66801,-3.99621 1.8773,-0.59583 2.47824,-1.73501 2.2225,-4.21308 -0.28329,-2.74506 0.43581,-3.76179 3.74951,-5.30145 4.742,-2.20329 5.18924,-4.21929 1.61498,-7.27967 -3.36676,-2.88271 -3.21815,-4.36892 0.34736,-3.47404 1.58803,0.39858 4.22507,0.22982 5.86007,-0.375 2.5406,-0.93983 2.72087,-1.36929 1.24016,-2.95435 -3.68749,-3.94728 -9.98759,-12.4913 -9.98759,-13.54486 0,-0.60877 -1.7145,-2.74946 -3.81,-4.75707 -2.0955,-2.00762 -3.81,-4.36814 -3.81,-5.24559 0,-0.87746 -1.2723,-2.48654 -2.82734,-3.57573 -1.55503,-1.08919 -4.08711,-4.06771 -5.62684,-6.61894 -1.79698,-2.97747 -4.53657,-5.30182 -7.65016,-6.49064 -24.20186,-9.24068 -24.76402,-9.29572 -47.9051,-4.69041 -19.57523,3.89567 -27.49448,6.4624 -29.71353,9.63054 -0.91908,1.31217 -2.87013,2.38577 -4.33568,2.38577 -4.04191,0 -12.8025,9.7783 -11.91798,13.30249 0.4268,1.70051 -0.59776,5.53967 -2.56869,9.6252 -2.91037,6.03291 -3.27003,8.30113 -3.20171,20.19184 0.0585,10.18032 0.82044,16.02554 3.17456,24.35333 5.16051,18.25551 6.20275,18.97169 19.04972,13.09015 z m 57.45397,-50.39639 c -5.25845,-2.44318 -8.60564,-4.88401 -9.55503,-6.96769 -1.82136,-3.99744 -1.16553,-4.69549 3.33717,-3.55206 1.9534,0.49606 8.40939,1.04815 14.34664,1.22686 10.70851,0.32234 22.86,3.99944 22.86,6.91755 0,0.66636 -1.85737,2.3363 -4.1275,3.71098 -6.35548,3.84859 -16.82359,3.32808 -26.86128,-1.33564 z m 24.00378,26.98337 c 0.4317,-0.6985 -0.1398,-1.27 -1.27,-1.27 -1.13019,0 -1.70169,0.5715 -1.27,1.27 0.4317,0.6985 1.0032,1.27 1.27,1.27 0.2668,0 0.8383,-0.5715 1.27,-1.27 z m -43.815,-94.069 c 6.63575,-0.74981 12.60793,-1.89378 13.2715,-2.54214 0.66358,-0.64837 0.94933,-1.22284 0.635,-1.2766 -7.57697,-1.29592 -28.00803,2.15857 -30.85433,5.21687 -1.15088,1.23659 -0.78271,1.40275 1.70784,0.77079 1.74625,-0.44309 8.60424,-1.41911 15.23999,-2.16892 z"
+         id="path11951"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#7890c2;stroke-width:1.26999998"
+         d="m -205.15373,306.39744 c -32.42297,-9.53605 -58.89796,-37.19718 -65.58531,-68.52367 -1.13837,-5.33262 -2.60028,-11.45075 -3.2487,-13.59584 -1.5147,-5.01096 -1.49474,-10.56059 0.0624,-17.35411 0.68275,-2.97865 2.14611,-9.38572 3.25192,-14.23794 6.05289,-26.55964 25.7241,-50.3145 51.44629,-62.1263 11.15491,-5.12241 31.87209,-10.96648 38.87606,-10.96648 6.23605,0 26.82236,5.18281 36.97905,9.30984 40.8473,16.59774 64.16967,63.27241 53.61504,107.29887 -7.88209,32.87854 -29.59435,57.15149 -61.19159,68.40837 -8.03609,2.86295 -11.75689,3.36499 -27.69813,3.73724 -15.17454,0.35435 -19.83894,0.0114 -26.50705,-1.94998 z m -37.8057,-51.63133 c 3.9128,-1.79134 7.47321,-3.8379 7.91202,-4.5479 0.43881,-0.71001 2.08815,-0.96708 3.6652,-0.57126 1.57706,0.39582 5.65808,-0.0866 9.06895,-1.07196 3.41087,-0.98538 10.48783,-2.16938 15.72658,-2.63111 10.11702,-0.89167 19.9102,-4.72049 20.82017,-8.14001 1.95103,-7.33169 1.90331,-8.72065 -0.37026,-10.77822 -2.0259,-1.8334 -2.18449,-2.87092 -1.09498,-7.16324 1.19693,-4.71555 1.55291,-5.01391 5.88205,-4.93005 2.53441,0.0491 5.62266,0.49028 6.86277,0.98043 3.41737,1.35068 5.79872,6.80009 4.50264,10.30372 -1.46946,3.97233 -0.76837,4.34828 2.47462,1.32697 4.76411,-4.43844 6.73441,-2.03872 8.32965,10.14503 0.3635,2.77632 4.48653,3.10827 7.27401,0.58563 1.9787,-1.7907 2.77451,-1.89877 4.35051,-0.59082 1.58079,1.31194 1.92882,1.25351 1.92882,-0.32384 0,-1.05855 -0.85725,-2.25359 -1.905,-2.65565 -2.69598,-1.03455 -2.36164,-3.03464 0.66801,-3.99621 1.8773,-0.59583 2.47824,-1.73501 2.2225,-4.21308 -0.28329,-2.74506 0.43581,-3.76179 3.74951,-5.30145 4.742,-2.20329 5.18924,-4.21929 1.61498,-7.27967 -3.36676,-2.88271 -3.21815,-4.36892 0.34736,-3.47404 1.58803,0.39858 4.22507,0.22982 5.86007,-0.375 2.5406,-0.93983 2.72087,-1.36929 1.24016,-2.95435 -3.68749,-3.94728 -9.98759,-12.4913 -9.98759,-13.54486 0,-0.60877 -1.7145,-2.74946 -3.81,-4.75707 -2.0955,-2.00762 -3.81,-4.36814 -3.81,-5.24559 0,-0.87746 -1.2723,-2.48654 -2.82734,-3.57573 -1.55503,-1.08919 -4.08711,-4.06771 -5.62684,-6.61894 -1.79698,-2.97747 -4.53657,-5.30182 -7.65016,-6.49064 -24.20186,-9.24068 -24.76402,-9.29572 -47.9051,-4.69041 -19.57523,3.89567 -27.49448,6.4624 -29.71353,9.63054 -0.91908,1.31217 -2.87013,2.38577 -4.33568,2.38577 -4.04191,0 -12.8025,9.7783 -11.91798,13.30249 0.4268,1.70051 -0.59776,5.53967 -2.56869,9.6252 -2.91037,6.03291 -3.27003,8.30113 -3.20171,20.19184 0.0585,10.18032 0.82044,16.02554 3.17456,24.35333 5.16051,18.25551 6.20275,18.97169 19.04972,13.09015 z m 57.45397,-50.39639 c -5.25845,-2.44318 -8.60564,-4.88401 -9.55503,-6.96769 -1.82136,-3.99744 -1.16553,-4.69549 3.33717,-3.55206 1.9534,0.49606 8.40939,1.04815 14.34664,1.22686 10.70851,0.32234 22.86,3.99944 22.86,6.91755 0,0.66636 -1.85737,2.3363 -4.1275,3.71098 -6.35548,3.84859 -16.82359,3.32808 -26.86128,-1.33564 z m 24.00378,26.98337 c 0.4317,-0.6985 -0.1398,-1.27 -1.27,-1.27 -1.13019,0 -1.70169,0.5715 -1.27,1.27 0.4317,0.6985 1.0032,1.27 1.27,1.27 0.2668,0 0.8383,-0.5715 1.27,-1.27 z m -43.815,-94.069 c 6.63575,-0.74981 12.60793,-1.89378 13.2715,-2.54214 0.66358,-0.64837 0.94933,-1.22284 0.635,-1.2766 -7.57697,-1.29592 -28.00803,2.15857 -30.85433,5.21687 -1.15088,1.23659 -0.78271,1.40275 1.70784,0.77079 1.74625,-0.44309 8.60424,-1.41911 15.23999,-2.16892 z"
+         id="path11949"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#6a88bb;stroke-width:1.26999998"
+         d="m -203.41168,306.31781 c -11.78328,-3.13526 -17.39017,-5.60573 -27.37867,-12.06344 -21.48272,-13.88884 -35.96821,-35.83209 -41.52331,-62.90128 -3.42678,-16.69821 -1.74164,-31.86913 5.65059,-50.87074 5.11457,-13.14692 11.50284,-22.31332 23.15509,-33.22479 11.52811,-10.79521 22.89995,-17.25223 38.8263,-22.04587 21.15014,-6.36593 24.17661,-6.56263 40.33113,-2.62121 37.21068,9.07876 60.77503,29.64084 72.24747,63.04262 3.79787,11.05742 4.14761,13.39076 4.18783,27.94 0.0356,12.84861 -0.51015,17.79818 -2.86228,25.96313 -9.6388,33.45913 -34.82643,58.54021 -67.07067,66.78689 -12.88648,3.2958 -33.16573,3.29344 -45.56348,-0.005 z m 16.3925,-19.02637 c 2.72938,-0.6331 6.35667,-2.54524 8.06065,-4.24922 l 3.09816,-3.09815 -4.61049,-4.6105 c -2.53576,-2.53576 -5.16293,-4.61048 -5.83815,-4.61048 -0.67522,0 -1.22767,-0.81874 -1.22767,-1.81941 0,-5.41033 -22.12311,-5.17496 -25.33471,0.26954 -1.1109,1.88326 -1.45781,1.92697 -2.513,0.31665 -2.28183,-3.4823 -2.97759,-0.15456 -0.88824,4.24841 1.1421,2.4068 1.93129,5.6325 1.75375,7.16821 -0.36283,3.13848 1.22823,4.26063 8.5672,6.04234 6.73542,1.63517 12.8805,1.74637 18.9325,0.34261 z m -32.519,-21.60442 c 2.58308,-0.71995 10.4115,-2.19359 17.3965,-3.27476 24.31585,-3.76369 27.04696,-4.4666 26.43577,-6.80381 -0.49835,-1.90569 1.0064,-2.13716 14.08611,-2.16688 11.5649,-0.0263 15.1197,-0.45838 16.9061,-2.05508 1.2441,-1.112 3.66443,-2.2168 5.37849,-2.4551 2.64562,-0.36783 3.40913,-1.58456 5.05357,-8.0533 1.06539,-4.191 2.9491,-8.36678 4.18602,-9.27951 1.23692,-0.91273 2.24894,-2.73638 2.24894,-4.05255 0,-1.31617 0.5715,-2.74624 1.27,-3.17794 0.6985,-0.43169 1.27,-2.67217 1.27,-4.97883 0,-3.78872 0.30677,-4.1353 3.175,-3.587 3.07735,0.58827 4.41744,-1.31139 1.98542,-2.81446 -2.05359,-1.2692 -5.16042,-8.54702 -5.16042,-12.08845 0,-1.93025 -1.30087,-4.6156 -3.01692,-6.22775 -1.92135,-1.80501 -3.59894,-5.57678 -4.61961,-10.3864 -1.26251,-5.94923 -2.62876,-8.54799 -6.43716,-12.24416 -2.65898,-2.58061 -6.50395,-5.57873 -8.5444,-6.66248 -2.04045,-1.08375 -6.50148,-3.47171 -9.9134,-5.30658 -7.95925,-4.28032 -24.73951,-8.71647 -38.94515,-10.29582 -9.23249,-1.02645 -12.22414,-0.88902 -17.38363,0.79856 -3.42759,1.12111 -7.22703,2.03838 -8.44319,2.03838 -2.65452,0 -10.29379,2.32498 -13.82153,4.20652 -1.397,0.7451 -4.54025,2.34271 -6.985,3.55025 -9.54563,4.71487 -19.3786,18.63608 -22.9066,32.43043 -2.72873,10.66922 -3.34754,29.89228 -1.30839,40.64467 3.9085,20.60942 15.78298,37.27812 26.55627,37.27812 1.37279,0 2.7328,1.11021 3.10539,2.53501 0.62284,2.38176 1.05807,2.2691 7.19911,-1.86352 3.59491,-2.4192 8.64963,-4.9876 11.23271,-5.70756 z m 33.9065,-64.19145 -5.08,-1.52722 5.25794,-0.52038 c 8.57176,-0.84835 16.96706,0.1983 16.96706,2.11529 0,2.1523 -9.89052,2.11325 -17.145,-0.0677 z m -29.845,-60.79086 c 5.93725,-1.02622 16.79575,-2.56352 24.13,-3.41624 17.07863,-1.98564 18.6345,-2.81559 10.4034,-5.54953 -10.82285,-3.59477 -31.69348,-0.52849 -45.01089,6.61293 -6.30029,3.37851 -8.09129,6.16097 -3.175,4.93264 1.57162,-0.39268 7.71525,-1.55358 13.65249,-2.5798 z"
+         id="path11947"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#5b82b7;stroke-width:1.26999998"
+         d="m -202.14168,306.40241 c -25.69165,-7.01642 -44.28794,-21.03602 -57.23033,-43.14551 -3.09906,-5.29413 -5.60884,-10.20915 -5.57727,-10.92225 0.0316,-0.71311 1.31925,1.11312 2.86154,4.05827 3.00565,5.73963 17.28688,23.22017 18.97037,23.22017 0.55812,0 6.00547,2.76436 12.10523,6.14302 17.15093,9.4999 24.10477,11.10014 41.33848,9.51291 15.94519,-1.46856 30.07698,-6.52862 30.07698,-10.76941 0,-1.22117 0.99003,-2.77436 2.20006,-3.45152 1.23243,-0.6897 2.7712,-3.92925 3.49863,-7.36561 1.14681,-5.41751 1.7431,-6.21531 5.10245,-6.82692 5.34121,-0.97241 11.17283,-3.86585 16.63158,-8.252 8.112,-6.51807 8.99077,-7.68842 9.8615,-13.13372 0.46787,-2.92583 1.98357,-7.43344 3.36822,-10.01691 1.38466,-2.58347 2.51756,-5.71995 2.51756,-6.96996 0,-1.25001 1.39644,-2.80367 3.10319,-3.45257 2.73881,-1.04129 2.97431,-1.62926 2.00551,-5.00721 -0.60372,-2.10505 -1.55845,-4.56035 -2.12165,-5.45623 -0.56318,-0.89588 -1.41696,-4.77211 -1.89726,-8.61386 -2.55695,-20.4516 -6.47523,-29.10101 -18.83018,-41.56678 -7.7892,-7.85907 -10.56346,-9.84714 -19.08961,-13.67985 -14.87618,-6.68722 -16.95898,-9.77886 -7.27133,-10.79337 3.33501,-0.34925 6.08326,-1.13004 6.1072,-1.7351 0.0694,-1.75416 -24.1192,-13.5049 -27.79954,-13.5049 -6.69153,0 -29.07322,5.55149 -38.66382,9.59006 -5.41338,2.27956 -9.8425,3.87559 -9.8425,3.54675 0,-1.53485 24.37209,-11.85698 31.3597,-13.28155 4.32558,-0.88186 7.61818,-2.00226 7.31687,-2.48978 -1.65405,-2.67632 20.6388,-2.56893 24.18842,0.11652 0.6985,0.52845 3.84175,1.37145 6.985,1.87333 9.03837,1.44317 23.06907,7.36431 32.83546,13.85698 21.67901,14.41218 35.9747,38.36596 39.72999,66.57136 1.24701,9.36611 1.15064,13.06884 -0.61163,23.49966 -6.32544,37.43991 -29.07147,64.77707 -63.69882,76.55598 -13.77584,4.68602 -36.16383,5.54031 -49.53,1.89 z m -64.6131,-57.95341 c -1.22552,-3.19366 -0.86399,-4.01772 0.63314,-1.44316 0.74129,1.27477 1.08026,2.58529 0.75327,2.91228 -0.32698,0.32699 -0.95087,-0.33413 -1.38641,-1.46912 z m -3.16887,-9.98964 c -0.78816,-3.13006 -1.37564,-7.00958 -1.3055,-8.62115 0.0701,-1.61156 0.92912,0.73527 1.90885,5.2152 2.03586,9.30914 1.55979,11.99657 -0.60335,3.40595 z m -2.25223,-22.98126 c -0.004,-6.2865 0.21089,-9.01837 0.47724,-6.07082 0.26635,2.94754 0.26951,8.09104 0.007,11.43 -0.26248,3.33895 -0.4804,0.92731 -0.48428,-5.35918 z m 1.06679,-15.24 c 0.0259,-2.0955 0.31052,-2.799 0.63252,-1.56333 0.322,1.23566 0.30082,2.95016 -0.0471,3.81 -0.34789,0.85983 -0.61134,-0.15117 -0.58546,-2.24667 z m 1.12268,-6.45584 c 0.0609,-1.47947 0.36187,-1.78041 0.76729,-0.76729 0.36687,0.91678 0.32175,2.01216 -0.10026,2.43417 -0.42201,0.42201 -0.72217,-0.32809 -0.66703,-1.66688 z m 4.94957,-15.05482 c 3.44487,-9.06069 8.66089,-17.01072 16.74332,-25.51941 11.23254,-11.82494 14.02653,-12.4725 3.93684,-0.91243 -10.26548,11.76147 -14.75491,17.79092 -17.9668,24.13 -2.97988,5.88116 -4.59522,7.25152 -2.71336,2.30184 z"
+         id="path11945"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#507bb1;stroke-width:1.26999998"
+         d="m -199.60168,306.82943 c -10.90275,-2.3967 -29.74798,-11.40265 -37.51491,-17.92798 -7.33303,-6.16079 -15.76241,-15.48936 -14.83466,-16.41711 0.39139,-0.3914 1.71659,0.82214 2.94488,2.69674 3.79745,5.79565 24.04935,18.06485 35.93044,21.76773 10.04764,3.13146 11.65824,3.27992 26.58473,2.4506 12.7823,-0.7102 17.37635,-1.50878 23.49527,-4.0842 9.16059,-3.85564 16.11596,-9.23831 20.50224,-15.86643 1.78918,-2.70363 3.95931,-4.93574 4.82253,-4.96025 2.01972,-0.0574 17.98751,-14.9932 19.77972,-18.50144 0.75048,-1.46904 2.97704,-7.52673 4.94793,-13.46151 1.97089,-5.93478 4.53904,-11.74613 5.70702,-12.91411 2.40273,-2.40273 2.2041,-7.31716 -1.09523,-27.0977 -1.07282,-6.43188 -1.62505,-12.22106 -1.22718,-12.86484 0.39788,-0.64378 -0.62486,-3.70628 -2.27278,-6.80556 -1.6479,-3.09929 -3.41693,-6.96071 -3.93117,-8.58094 -0.51424,-1.62023 -4.62542,-7.29703 -9.13596,-12.6151 -4.51053,-5.31808 -8.70802,-10.92523 -9.32774,-12.46034 -2.2973,-5.6906 -10.0027,-12.52849 -18.57139,-16.48054 -15.747,-7.26283 -21.4993,-9.17703 -30.3956,-10.11475 -8.76548,-0.92394 -8.77504,-0.92919 -3.70814,-2.03926 5.87474,-1.28705 18.79783,-0.0662 17.7222,1.67419 -0.39991,0.64706 0.61531,1.17647 2.25603,1.17647 10.54749,0 33.43911,9.86697 45.6692,19.68479 13.59631,10.91457 26.0979,31.35411 30.73255,50.24635 2.93313,11.95635 2.6819,31.27487 -0.56976,43.81217 -8.70783,33.57445 -31.32353,57.52301 -64.06022,67.8356 -11.77058,3.70793 -32.13604,4.55436 -44.45,1.84742 z m -3.81,-180.70063 c 3.76321,-1.62148 13.11489,-3.02905 10.795,-1.62481 -1.04775,0.6342 -4.47675,1.50548 -7.62,1.93617 -3.66529,0.50221 -4.80401,0.39054 -3.175,-0.31136 z"
+         id="path11943"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#336fa6;stroke-width:1.26999998"
+         d="m -197.06168,306.68997 c -5.6877,-1.27297 -18.53815,-5.78706 -18.00161,-6.32359 0.3166,-0.3166 4.00747,0.31364 8.20194,1.40054 10.86071,2.81429 30.53753,2.57741 42.6213,-0.51309 11.20625,-2.86608 18.4253,-6.70089 30.04337,-15.95929 17.465,-13.91777 23.75254,-22.63827 29.32093,-40.6666 1.37261,-4.444 3.23198,-8.82022 4.13192,-9.72493 1.80824,-1.8178 1.98327,-35.23804 0.22145,-42.28491 -5.36573,-21.46184 -16.75029,-40.91054 -30.22854,-51.64066 -3.64142,-2.89895 -10.62126,-7.20424 -15.51076,-9.56731 -4.8895,-2.36307 -7.46125,-3.9155 -5.715,-3.44984 14.58015,3.88796 29.70366,13.40402 40.53102,25.50302 7.54064,8.42627 16.82229,26.24105 20.06915,38.51979 3.28965,12.44057 2.97476,33.44449 -0.69494,46.35499 -9.64987,33.94941 -33.91577,58.56637 -65.75927,66.71056 -9.25114,2.36605 -31.78123,3.30864 -39.23096,1.64132 z m 6.6675,-185.39797 c 0.87313,-0.35231 2.30188,-0.35231 3.175,0 0.87313,0.35232 0.15875,0.64057 -1.5875,0.64057 -1.74625,0 -2.46062,-0.28825 -1.5875,-0.64057 z m 17.85938,-0.0265 c 0.91678,-0.36687 2.01215,-0.32175 2.43416,0.10026 0.42201,0.42201 -0.32808,0.72217 -1.66687,0.66703 -1.47948,-0.0609 -1.78041,-0.36188 -0.76729,-0.76729 z m -9.58021,-1.28255 c 1.23567,-0.322 2.95017,-0.30082 3.81,0.0471 0.85983,0.34788 -0.15117,0.61134 -2.24667,0.58546 -2.0955,-0.0259 -2.799,-0.31052 -1.56333,-0.63252 z"
+         id="path11941"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>
diff --git a/resources/fonts/svg/planets/pluto.svg b/resources/fonts/svg/planets/pluto.svg
new file mode 100644
index 0000000..83c02f2
--- /dev/null
+++ b/resources/fonts/svg/planets/pluto.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="39"
+   height="38"
+   viewBox="0 0 10.31875 10.054167"
+   version="1.1"
+   id="svg8"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)"
+   sodipodi:docname="pluto.svg">
+  <defs
+     id="defs2" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.98994949"
+     inkscape:cx="-300.18335"
+     inkscape:cy="177.71542"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:window-width="2560"
+     inkscape:window-height="1531"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata5">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-286.94582)">
+    <ellipse
+       style="fill:#fefefe;fill-opacity:1;stroke:none;stroke-width:10.85999966;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="path901"
+       cx="5.1024837"
+       cy="292.04178"
+       rx="5.1024837"
+       ry="4.9582186" />
+  </g>
+</svg>
diff --git a/resources/fonts/svg/planets/saturn.svg b/resources/fonts/svg/planets/saturn.svg
new file mode 100644
index 0000000..adb0825
--- /dev/null
+++ b/resources/fonts/svg/planets/saturn.svg
@@ -0,0 +1,216 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="2866"
+   height="3058"
+   viewBox="0 0 758.29582 809.09584"
+   version="1.1"
+   id="svg7767"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)"
+   sodipodi:docname="saturn.svg">
+  <defs
+     id="defs7761" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.35"
+     inkscape:cx="1148.7329"
+     inkscape:cy="1257.8961"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:window-width="2560"
+     inkscape:window-height="1531"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata7764">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,512.09585)">
+    <g
+       id="g8503">
+      <path
+         style="fill:#d1d0cf;stroke-width:1.26999998"
+         d="m 48.895,285.72147 c -6.04243,-2.77469 -8.7502,-5.60691 -11.38157,-11.90466 -5.09113,-12.1848 0.0572,-41.08919 12.59396,-70.70646 6.48427,-15.31868 22.06512,-46.42415 28.6719,-57.24035 3.62661,-5.93725 8.20396,-13.93825 10.1719,-17.78 1.967943,-3.84175 6.45306,-11.557 9.96693,-17.145 3.51387,-5.588 9.69816,-15.49445 13.74286,-22.01436 6.1419,-9.90048 26.51154,-40.27566 33.99491,-50.69314 2.65257,-3.69261 6.36974,-3.61031 14.10136,0.31218 3.43625,1.74333 7.03585,3.17208 7.99908,3.175 0.96324,0.003 5.7199,1.43685 10.57036,3.18652 9.51165,3.43105 17.52329,8.73567 17.52329,11.60243 0,0.98017 -1.77252,5.62722 -3.93893,10.32676 -6.73465,14.60927 -14.05971,36.25481 -13.4133,39.63628 0.33342,1.74421 0.0427,4.22442 -0.64624,5.51156 -0.96331,1.79996 -0.46988,3.46956 2.13722,7.2316 3.77309,5.44459 8.53249,7.68698 13.32125,6.27631 1.74625,-0.5144 6.46113,-1.61096 10.4775,-2.43679 4.01638,-0.82583 7.3025,-2.03202 7.3025,-2.68043 0,-0.6484 1.35168,-1.17892 3.00373,-1.17892 1.65205,0 4.36668,-1.06445 6.0325,-2.36544 1.66582,-1.30098 5.31477,-3.5938 8.10877,-5.09515 5.67511,-3.04948 16.04736,-10.31034 19.12511,-13.3881 1.10623,-1.10622 2.68487,-2.03327 3.50811,-2.0601 0.82322,-0.0268 4.2805,-2.50223 7.68282,-5.50087 7.28973,-6.4248 7.24528,-9.49525 -0.18146,-12.53454 -11.5499,-4.72662 -39.9849,-31.85991 -51.72284,-49.35499 -7.15614,-10.66605 -17.48939,-30.21188 -19.73823,-37.33581 -0.88199,-2.794 -2.30905,-6.223 -3.17124,-7.62 -2.18348,-3.53785 -6.09409,-16.43375 -10.20572,-33.655 -3.1853,-13.34137 -3.4915,-16.8026 -3.53901,-40.005 -0.0312,-15.25916 0.58044,-28.69577 1.53213,-33.65499 14.38173,-74.94289 56.38086,-136.77536 118.36099,-174.25491 28.73238,-17.37454 57.54206,-27.44578 91.87814,-32.11864 9.44349,-1.28518 44.64442,-0.91426 55.38619,0.5836 5.23874,0.7305 13.58178,2.26767 18.54008,3.41592 l 9.01509,2.08773 22.0999,-19.58821 c 71.12847,-63.04454 127.4728,-104.63184 174.00358,-128.43064 25.6795,-13.13413 36.33039,-16.51621 53.83632,-17.09519 12.7743,-0.42249 14.40449,-0.2098 19.0464,2.48503 34.17996,19.84299 -5.1861,108.99694 -110.33723,249.88531 -4.95255,6.63575 -13.23074,17.36701 -18.39597,23.84723 l -9.39133,11.78224 2.72492,9.80776 c 4.75215,17.10427 6.42755,27.06284 7.95302,47.27277 1.26469,16.755 1.18044,22.34817 -0.56605,37.57738 -3.81864,33.29827 -8.44602,49.77257 -21.72858,77.35761 -21.09883,43.81777 -48.13189,74.78138 -88.38016,101.23045 -32.77111,21.53547 -67.34818,34.15085 -103.40806,37.72824 -23.2359,2.30516 -33.10364,1.90749 -63.13192,-2.54428 l -10.965,-1.62559 -12.52999,11.27237 c -6.89151,6.19981 -12.71025,11.70317 -12.93055,12.22968 -0.71268,1.70341 -49.42555,45.52647 -60.87914,54.76809 -6.11068,4.93056 -15.11082,12.48175 -20.00032,16.78046 -10.71323,9.41875 -41.95004,33.22912 -55.93204,42.63442 -5.55937,3.73963 -14.67995,9.88018 -20.26795,13.64567 -14.34251,9.66472 -46.307497,25.11323 -58.30565,28.17879 -12.49871,3.19347 -22.624,3.25499 -29.32434,0.17817 z m 63.00078,-50.52442 c 4.81481,-1.16211 8.75421,-2.62459 8.75421,-3.24994 0,-0.62536 -0.59688,-0.76812 -1.3264,-0.31725 -0.72952,0.45087 -5.27351,0.84091 -10.09775,0.86676 -16.574412,0.0888 -24.70723,-8.03191 -24.74925,-24.71248 -0.0119,-4.71399 -0.40473,-8.57161 -0.87301,-8.5725 -1.38403,-0.003 -2.69076,16.05797 -1.72526,21.20454 1.45618,7.76206 4.27474,11.84299 9.963395,14.42573 6.558715,2.97775 9.008495,3.02114 20.054065,0.35514 z m 473.2789,-509.69704 c 2.46219,-5.23875 5.17244,-10.38225 6.02281,-11.43 2.00716,-2.47307 6.97246,-17.46093 6.97246,-21.04651 0,-1.52532 0.79681,-4.70047 1.77067,-7.0559 2.01807,-4.8809 1.81715,-12.89733 -0.44408,-17.7179 -4.51549,-9.62632 -24.19784,-6.21681 -49.80027,8.62672 -3.95929,2.29547 -7.54495,4.17359 -7.96812,4.17359 -0.42318,0 -3.58242,1.85737 -7.02051,4.1275 -3.43809,2.27012 -8.85358,5.7439 -12.03438,7.71951 -3.18082,1.97562 -5.7833,4.02444 -5.7833,4.55297 0,0.52852 5.00062,4.696 11.1125,9.26105 18.44738,13.77866 37.49051,33.99617 40.26994,42.75338 0.86478,2.72469 1.41852,2.24966 6.68424,-5.73431 3.15775,-4.7878 7.75586,-12.99135 10.21804,-18.2301 z m 115.8429,-133.53068 c 1.39951,-5.83788 2.54857,-12.9221 2.55346,-15.74272 0.0127,-7.27981 -4.7045,-15.96125 -10.15109,-18.68213 -7.99468,-3.99378 -24.71692,-2.17482 -38.31298,4.16749 -3.02611,1.41163 -4.35902,2.22621 -2.96202,1.81017 22.46727,-6.69097 37.85748,-4.99319 43.83474,4.83566 1.3461,2.21346 2.81676,6.4873 3.26816,9.49743 0.74989,5.00067 -0.84688,20.59451 -2.71097,26.47484 -0.6666,2.10278 -0.58904,2.14785 0.58371,0.33926 0.74383,-1.14712 2.49748,-6.86212 3.89699,-12.7 z"
+         id="path8436"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#f3cb83;stroke-width:1.26999998"
+         d="M 53.03403,286.37461 C 42.62876,282.50383 38.065,276.5956 36.12918,264.48964 33.08625,245.46021 54.25887,189.04459 82.07666,142.06 c 3.51522,-5.93725 8.679798,-14.7955 11.476836,-19.685 4.855398,-8.48771 7.749194,-13.19328 25.646554,-41.7035 4.31355,-6.87141 11.71518,-18.01566 16.44807,-24.765 4.73289,-6.74932 9.59416,-13.84312 10.80283,-15.764 2.82457,-4.48893 7.77191,-4.60046 14.08321,-0.3175 2.57325,1.74625 5.93912,3.17225 7.47973,3.16888 4.67738,-0.0102 20.601,6.35051 24.1603,9.65088 3.75661,3.48333 3.98938,2.36739 -4.21356,20.20024 -1.92782,4.191 -4.26457,9.906 -5.19277,12.7 -0.92821,2.794 -2.07095,5.6515 -2.53944,6.35 -1.54576,2.30467 -4.763,15.11398 -5.64743,22.48496 -0.74142,6.17925 -0.45997,7.90726 1.91344,11.7475 1.53049,2.4764 3.73623,4.47257 4.90164,4.43593 1.6878,-0.0531 1.53753,-0.47217 -0.73858,-2.0599 -2.76727,-1.93035 -3.94052,-5.49349 -1.80888,-5.49349 1.55193,0 6.57138,5.64043 6.57138,7.38435 0,2.27198 5.86879,1.80542 8.33699,-0.66277 1.19263,-1.19262 4.23382,-2.22293 6.75821,-2.28958 4.57963,-0.12091 18.2285,-6.29858 24.07279,-10.8957 1.6965,-1.33447 3.76482,-2.4263 4.59629,-2.4263 1.47147,0 5.26957,-2.706 27.08784,-19.29901 8.80222,-6.69417 10.46699,-8.52584 10.16,-11.1786 -0.26571,-2.2962 -1.77044,-3.82455 -5.44713,-5.53268 C 248.01231,72.08285 220.36867,45.99628 209.09921,29.14651 202.56719,19.38001 193.07137,2.38575 190.58364,-3.99 c -0.95391,-2.44475 -3.35585,-8.23874 -5.33763,-12.87554 -1.98179,-4.63679 -5.75041,-16.92404 -8.3747,-27.305 l -4.77144,-18.87446 0.0462,-27.305 c 0.0494,-29.22916 1.21046,-37.63072 8.87919,-64.2534 9.99516,-34.69905 35.39645,-77.11005 62.3925,-104.17296 38.63869,-38.73441 84.34592,-62.12371 136.32558,-69.76052 21.41884,-3.14684 51.37243,-2.10279 71.43268,2.48984 l 14.93106,3.41833 18.72394,-16.72553 c 102.80077,-91.82887 187.66222,-146.87536 230.47058,-149.49775 8.81805,-0.54019 10.54464,-0.24519 16.97314,2.89999 6.29111,3.07795 7.56023,4.36447 10.49529,10.63918 2.67737,5.72378 3.31221,8.899 3.20456,16.02783 -0.29701,19.66777 -13.41698,53.15408 -37.56786,95.885 -22.38132,39.59998 -68.42303,106.56509 -103.69182,150.81396 l -9.36457,11.74896 3.29834,11.74604 c 7.13081,25.39415 9.80051,47.7635 8.52566,71.43604 -2.54576,47.27199 -19.84681,92.72117 -51.24552,134.61999 -38.48369,51.3531 -105.30238,88.00821 -170.2978,93.42123 -18.39514,1.532 -30.63556,0.75158 -55.06727,-3.51094 l -11.70122,-2.04148 -11.15878,9.96602 c -6.13732,5.48131 -14.58777,13.43771 -18.77877,17.68089 -10.51762,10.64858 -43.45636,40.26942 -56.515,50.82226 -5.93725,4.79798 -14.7955,12.23136 -19.685,16.51864 -13.04347,11.43696 -43.66024,34.36461 -66.0191,49.439 -41.09583,27.70691 -78.80074,42.64607 -93.67185,37.11399 z m 66.5979,-52.38767 c 5.79868,-1.82768 13.40056,-4.75086 16.89306,-6.49597 7.466,-3.73055 27.305,-15.53181 27.305,-16.24242 0,-0.27003 -7.37042,3.2056 -16.37871,7.72363 -20.25316,10.15778 -31.76046,13.32827 -43.28717,11.92649 -7.170212,-0.87198 -8.756036,-1.57401 -12.306071,-5.44778 -7.533269,-8.22021 -8.387789,-20.83427 -2.792929,-41.22769 3.811373,-13.89254 2.156012,-13.88578 -2.45175,0.0102 -14.5139,43.77012 -3.00732,61.10869 33.01857,49.75372 z M 170.81499,139.52 c -0.4317,-0.6985 -1.3564,-1.27 -2.0549,-1.27 -0.6985,0 -0.9168,0.5715 -0.4851,1.27 0.43169,0.6985 1.35639,1.27 2.05489,1.27 0.6985,0 0.9168,-0.5715 0.48511,-1.27 z m 406.15214,-395.35769 c 15.19184,-26.33502 22.86572,-42.63452 24.3612,-51.74374 0.51047,-3.10946 1.79091,-6.63066 2.84541,-7.82489 1.34954,-1.52839 1.62489,-3.22063 0.92987,-5.715 -0.54307,-1.94902 -0.86879,-4.49569 -0.7238,-5.65925 0.14497,-1.16357 -0.57822,-4.13028 -1.60707,-6.59269 -1.50939,-3.61246 -2.89107,-4.75187 -7.15436,-5.89984 -13.1184,-3.53243 -30.90985,3.30581 -64.33828,24.72874 -2.21366,1.41865 -6.91683,4.29387 -10.45148,6.38937 -3.53464,2.0955 -6.24936,4.3815 -6.03273,5.08 0.21664,0.6985 5.49131,5.12737 11.72148,9.84193 22.48347,17.01391 41.1219,37.80315 41.21825,45.97467 l 0.0457,3.8734 3.19901,-3.81 c 1.75944,-2.0955 4.45352,-5.98471 5.98681,-8.6427 z m 89.78282,-74.5423 c 0.74765,-1.397 1.07362,-2.54 0.72437,-2.54 -0.34925,0 -1.24672,1.143 -1.99437,2.54 -0.74765,1.397 -1.07362,2.54 -0.72437,2.54 0.34925,0 1.24672,-1.143 1.99437,-2.54 z m 5.75882,-10.4775 2.01407,-4.1275 -2.58821,3.175 c -2.49194,3.05691 -3.30527,5.08 -2.0423,5.08 0.33132,0 1.50871,-1.85738 2.61644,-4.1275 z m -65.44882,1.65951 c 0,-1.36801 -5.03404,-5.15201 -6.85396,-5.15201 -0.66483,0 0.13344,1.42875 1.77396,3.175 3.08429,3.28307 5.08,4.05976 5.08,1.97701 z m 10.32783,-9.15557 c -1.58903,-4.17951 -4.03775,-6.00723 -9.17887,-6.85112 l -4.32396,-0.70975 4.00656,1.86005 c 2.2036,1.02303 5.30625,3.618 6.89478,5.7666 3.29638,4.45858 4.31189,4.43291 2.60149,-0.0658 z m 65.61947,-11.30412 c 6.73704,-12.62428 15.03899,-32.66706 18.11008,-43.7218 3.35541,-12.07818 3.34528,-27.77235 -0.0215,-33.29402 -4.29888,-7.05038 -8.12943,-8.65641 -20.32551,-8.52182 -12.52469,0.13821 -23.84765,3.56272 -42.59536,12.8825 -12.47929,6.20365 -40.3374,23.28376 -39.44004,24.18112 0.30734,0.30733 2.16333,-0.57565 4.12441,-1.96219 5.64548,-3.99148 31.88261,-17.59334 41.90625,-21.72504 13.41845,-5.53103 26.94366,-8.2475 35.64881,-7.15986 5.99654,0.74922 7.99121,1.68561 11.88498,5.57938 3.85027,3.85027 4.83319,5.91271 5.54925,11.64391 1.57773,12.62769 -3.63856,33.03383 -14.26774,55.8155 -9.83223,21.07355 -10.11402,24.15973 -0.57362,6.28232 z"
+         id="path8434"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#efc57c;stroke-width:1.26999998"
+         d="M 53.03403,286.37461 C 42.62876,282.50383 38.065,276.5956 36.12918,264.48964 33.08625,245.46021 54.25887,189.04459 82.07666,142.06 c 3.51522,-5.93725 8.679798,-14.7955 11.476836,-19.685 4.855398,-8.48771 7.749194,-13.19328 25.646554,-41.7035 4.31355,-6.87141 11.71518,-18.01566 16.44807,-24.765 4.73289,-6.74932 9.59416,-13.84312 10.80283,-15.764 2.82457,-4.48893 7.77191,-4.60046 14.08321,-0.3175 2.57325,1.74625 5.93912,3.17225 7.47973,3.16888 4.67738,-0.0102 20.601,6.35051 24.1603,9.65088 3.75661,3.48333 3.98938,2.36739 -4.21356,20.20024 -1.92782,4.191 -4.26457,9.906 -5.19277,12.7 -0.92821,2.794 -2.07095,5.6515 -2.53944,6.35 -1.54576,2.30467 -4.763,15.11398 -5.64743,22.48496 -0.74142,6.17925 -0.45997,7.90726 1.91344,11.7475 1.53049,2.4764 3.73623,4.47257 4.90164,4.43593 1.6878,-0.0531 1.53753,-0.47217 -0.73858,-2.0599 -2.76727,-1.93035 -3.94052,-5.49349 -1.80888,-5.49349 1.55193,0 6.57138,5.64043 6.57138,7.38435 0,2.27198 5.86879,1.80542 8.33699,-0.66277 1.19263,-1.19262 4.23382,-2.22293 6.75821,-2.28958 4.57963,-0.12091 18.2285,-6.29858 24.07279,-10.8957 1.6965,-1.33447 3.76482,-2.4263 4.59629,-2.4263 1.47147,0 5.26957,-2.706 27.08784,-19.29901 8.80222,-6.69417 10.46699,-8.52584 10.16,-11.1786 -0.26571,-2.2962 -1.77044,-3.82455 -5.44713,-5.53268 C 248.01231,72.08285 220.36867,45.99628 209.09921,29.14651 202.56719,19.38001 193.07137,2.38575 190.58364,-3.99 c -0.95391,-2.44475 -3.35585,-8.23874 -5.33763,-12.87554 -1.98179,-4.63679 -5.75041,-16.92404 -8.3747,-27.305 l -4.77144,-18.87446 0.0462,-27.305 c 0.0494,-29.22916 1.21046,-37.63072 8.87919,-64.2534 9.99516,-34.69905 35.39645,-77.11005 62.3925,-104.17296 38.63869,-38.73441 84.34592,-62.12371 136.32558,-69.76052 21.41884,-3.14684 51.37243,-2.10279 71.43268,2.48984 l 14.93106,3.41833 18.72394,-16.72553 c 102.80077,-91.82887 187.66222,-146.87536 230.47058,-149.49775 8.81805,-0.54019 10.54464,-0.24519 16.97314,2.89999 6.29111,3.07795 7.56023,4.36447 10.49529,10.63918 2.67737,5.72378 3.31221,8.899 3.20456,16.02783 -0.29701,19.66777 -13.41698,53.15408 -37.56786,95.885 -22.38132,39.59998 -68.42303,106.56509 -103.69182,150.81396 l -9.36457,11.74896 3.29834,11.74604 c 7.13081,25.39415 9.80051,47.7635 8.52566,71.43604 -2.54576,47.27199 -19.84681,92.72117 -51.24552,134.61999 -38.48369,51.3531 -105.30238,88.00821 -170.2978,93.42123 -18.39514,1.532 -30.63556,0.75158 -55.06727,-3.51094 l -11.70122,-2.04148 -11.15878,9.96602 c -6.13732,5.48131 -14.58777,13.43771 -18.77877,17.68089 -10.51762,10.64858 -43.45636,40.26942 -56.515,50.82226 -5.93725,4.79798 -14.7955,12.23136 -19.685,16.51864 -13.04347,11.43696 -43.66024,34.36461 -66.0191,49.439 -41.09583,27.70691 -78.80074,42.64607 -93.67185,37.11399 z m 66.5979,-52.38767 c 5.79868,-1.82768 13.40056,-4.75086 16.89306,-6.49597 7.466,-3.73055 27.305,-15.53181 27.305,-16.24242 0,-0.27003 -7.37042,3.2056 -16.37871,7.72363 -20.25316,10.15778 -31.76046,13.32827 -43.28717,11.92649 -7.170212,-0.87198 -8.756036,-1.57401 -12.306071,-5.44778 -7.533269,-8.22021 -8.387789,-20.83427 -2.792929,-41.22769 3.811373,-13.89254 2.156012,-13.88578 -2.45175,0.0102 -14.5139,43.77012 -3.00732,61.10869 33.01857,49.75372 z M 170.81499,139.52 c -0.4317,-0.6985 -1.3564,-1.27 -2.0549,-1.27 -0.6985,0 -0.9168,0.5715 -0.4851,1.27 0.43169,0.6985 1.35639,1.27 2.05489,1.27 0.6985,0 0.9168,-0.5715 0.48511,-1.27 z M 376.14687,-46.535 c 1.3736,-1.74625 3.26937,-3.175 4.21283,-3.175 0.94347,0 3.48217,-1.48664 5.64158,-3.30367 2.15942,-1.81703 4.75847,-2.98431 5.77569,-2.59396 1.01722,0.39034 2.81657,-0.25736 3.99855,-1.43933 1.18197,-1.18198 3.39929,-1.98522 4.92739,-1.78498 1.81525,0.23789 3.4926,-0.726 4.83887,-2.78066 1.13328,-1.72961 3.1487,-3.49013 4.4787,-3.91226 2.63862,-0.83746 31.79088,-32.42288 44.00448,-47.67726 4.191,-5.23441 8.68731,-10.66366 9.9918,-12.065 1.30448,-1.40133 3.22192,-4.54812 4.26097,-6.99287 1.03905,-2.44475 4.30803,-8.15975 7.2644,-12.7 2.95638,-4.54025 6.68676,-12.54125 8.28974,-17.78 1.603,-5.23875 4.02075,-11.09663 5.37279,-13.0175 1.35204,-1.92088 1.99072,-3.57572 1.41928,-3.67742 -6.22231,-1.10745 -15.19184,-0.45205 -16.39856,1.19824 -0.77054,1.05375 -2.08101,1.4939 -2.91441,0.97882 -1.8352,-1.13421 -0.71345,-5.25673 2.57678,-9.46983 2.50064,-3.20206 7.91496,-19.1429 6.86016,-20.1977 -1.1904,-1.19041 -9.65866,6.30126 -14.75972,13.05758 -2.83012,3.74847 -6.4608,7.6771 -8.06819,8.7303 -1.60737,1.0532 -5.499,5.3424 -8.64805,9.53155 -5.9904,7.969 -19.81668,23.83451 -27.94948,32.07172 -2.61938,2.65301 -4.7625,5.31472 -4.7625,5.91491 0,0.6002 -1.28588,2.24833 -2.8575,3.66253 -1.57163,1.4142 -7.41721,7.94663 -12.9902,14.51653 -5.57297,6.56989 -13.0884,15.08851 -16.70095,18.93026 -9.08857,9.66517 -21.14175,24.5217 -26.78934,33.02 -1.16048,1.74625 -3.9267,4.8895 -6.14714,6.985 -4.41882,4.17016 -6.17933,7.76861 -4.35295,8.89737 1.63234,1.00884 9.92752,-3.45655 15.13932,-8.14967 2.39593,-2.15748 4.69859,-3.9227 5.11703,-3.9227 0.41843,0 2.84101,-2.0205 5.38348,-4.49002 2.9097,-2.82617 4.94219,-3.97305 5.48489,-3.09496 0.75748,1.22562 -1.98025,5.58162 -4.83335,7.69036 -0.55095,0.4072 -0.39209,2.34383 0.35302,4.30363 0.74512,1.95979 1.05797,3.86004 0.69524,4.22278 -0.36273,0.36274 -0.023,1.42647 0.75497,2.36386 1.04443,1.25844 1.03082,1.70435 -0.0519,1.70435 -1.60457,0 -3.96653,5.13727 -3.06551,6.6675 0.92203,1.56594 1.8213,1.11659 4.44783,-2.2225 z m -5.3069,-8.255 c 0,-0.6985 -0.25202,-1.27 -0.56004,-1.27 -0.30803,0 -0.91326,0.5715 -1.34496,1.27 -0.4317,0.6985 -0.17968,1.27 0.56005,1.27 0.73972,0 1.34495,-0.5715 1.34495,-1.27 z m 206.12716,-201.04769 c 15.19184,-26.33502 22.86572,-42.63452 24.3612,-51.74374 0.51047,-3.10946 1.79091,-6.63066 2.84541,-7.82489 1.34954,-1.52839 1.62489,-3.22063 0.92987,-5.715 -0.54307,-1.94902 -0.86879,-4.49569 -0.7238,-5.65925 0.14497,-1.16357 -0.57822,-4.13028 -1.60707,-6.59269 -1.50939,-3.61246 -2.89107,-4.75187 -7.15436,-5.89984 -13.1184,-3.53243 -30.90985,3.30581 -64.33828,24.72874 -2.21366,1.41865 -6.91683,4.29387 -10.45148,6.38937 -3.53464,2.0955 -6.24936,4.3815 -6.03273,5.08 0.21664,0.6985 5.49131,5.12737 11.72148,9.84193 22.48347,17.01391 41.1219,37.80315 41.21825,45.97467 l 0.0457,3.8734 3.19901,-3.81 c 1.75944,-2.0955 4.45352,-5.98471 5.98681,-8.6427 z m 89.78282,-74.5423 c 0.74765,-1.397 1.07362,-2.54 0.72437,-2.54 -0.34925,0 -1.24672,1.143 -1.99437,2.54 -0.74765,1.397 -1.07362,2.54 -0.72437,2.54 0.34925,0 1.24672,-1.143 1.99437,-2.54 z m 5.75882,-10.4775 2.01407,-4.1275 -2.58821,3.175 c -2.49194,3.05691 -3.30527,5.08 -2.0423,5.08 0.33132,0 1.50871,-1.85738 2.61644,-4.1275 z m -65.44882,1.65951 c 0,-1.36801 -5.03404,-5.15201 -6.85396,-5.15201 -0.66483,0 0.13344,1.42875 1.77396,3.175 3.08429,3.28307 5.08,4.05976 5.08,1.97701 z m 10.32783,-9.15557 c -1.58903,-4.17951 -4.03775,-6.00723 -9.17887,-6.85112 l -4.32396,-0.70975 4.00656,1.86005 c 2.2036,1.02303 5.30625,3.618 6.89478,5.7666 3.29638,4.45858 4.31189,4.43291 2.60149,-0.0658 z m 65.61947,-11.30412 c 6.73704,-12.62428 15.03899,-32.66706 18.11008,-43.7218 3.35541,-12.07818 3.34528,-27.77235 -0.0215,-33.29402 -4.29888,-7.05038 -8.12943,-8.65641 -20.32551,-8.52182 -12.52469,0.13821 -23.84765,3.56272 -42.59536,12.8825 -12.47929,6.20365 -40.3374,23.28376 -39.44004,24.18112 0.30734,0.30733 2.16333,-0.57565 4.12441,-1.96219 5.64548,-3.99148 31.88261,-17.59334 41.90625,-21.72504 13.41845,-5.53103 26.94366,-8.2475 35.64881,-7.15986 5.99654,0.74922 7.99121,1.68561 11.88498,5.57938 3.85027,3.85027 4.83319,5.91271 5.54925,11.64391 1.57773,12.62769 -3.63856,33.03383 -14.26774,55.8155 -9.83223,21.07355 -10.11402,24.15973 -0.57362,6.28232 z"
+         id="path8432"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#e4bb77;stroke-width:1.26999998"
+         d="M 53.03403,286.37461 C 42.62876,282.50383 38.065,276.5956 36.12918,264.48964 33.08625,245.46021 54.25887,189.04459 82.07666,142.06 c 3.51522,-5.93725 8.679798,-14.7955 11.476836,-19.685 4.855398,-8.48771 7.749194,-13.19328 25.646554,-41.7035 4.31355,-6.87141 11.71518,-18.01566 16.44807,-24.765 4.73289,-6.74932 9.59416,-13.84312 10.80283,-15.764 2.82457,-4.48893 7.77191,-4.60046 14.08321,-0.3175 2.57325,1.74625 5.93912,3.17225 7.47973,3.16888 4.67738,-0.0102 20.601,6.35051 24.1603,9.65088 3.75661,3.48333 3.98938,2.36739 -4.21356,20.20024 -1.92782,4.191 -4.26457,9.906 -5.19277,12.7 -0.92821,2.794 -2.07095,5.6515 -2.53944,6.35 -1.54576,2.30467 -4.763,15.11398 -5.64743,22.48496 -0.74142,6.17925 -0.45997,7.90726 1.91344,11.7475 1.53049,2.4764 3.73623,4.47257 4.90164,4.43593 1.6878,-0.0531 1.53753,-0.47217 -0.73858,-2.0599 -2.76727,-1.93035 -3.94052,-5.49349 -1.80888,-5.49349 1.55193,0 6.57138,5.64043 6.57138,7.38435 0,2.27198 5.86879,1.80542 8.33699,-0.66277 1.19263,-1.19262 4.23382,-2.22293 6.75821,-2.28958 4.57963,-0.12091 18.2285,-6.29858 24.07279,-10.8957 1.6965,-1.33447 3.76482,-2.4263 4.59629,-2.4263 1.47147,0 5.26957,-2.706 27.08784,-19.29901 8.80222,-6.69417 10.46699,-8.52584 10.16,-11.1786 -0.26571,-2.2962 -1.77044,-3.82455 -5.44713,-5.53268 C 248.01231,72.08285 220.36867,45.99628 209.09921,29.14651 202.56719,19.38001 193.07137,2.38575 190.58364,-3.99 c -0.95391,-2.44475 -3.35585,-8.23874 -5.33763,-12.87554 -1.98179,-4.63679 -5.75041,-16.92404 -8.3747,-27.305 l -4.77144,-18.87446 0.0462,-27.305 c 0.0494,-29.22916 1.21046,-37.63072 8.87919,-64.2534 9.99516,-34.69905 35.39645,-77.11005 62.3925,-104.17296 38.63869,-38.73441 84.34592,-62.12371 136.32558,-69.76052 21.41884,-3.14684 51.37243,-2.10279 71.43268,2.48984 l 14.93106,3.41833 18.72394,-16.72553 c 102.80077,-91.82887 187.66222,-146.87536 230.47058,-149.49775 8.81805,-0.54019 10.54464,-0.24519 16.97314,2.89999 6.29111,3.07795 7.56023,4.36447 10.49529,10.63918 2.67737,5.72378 3.31221,8.899 3.20456,16.02783 -0.29701,19.66777 -13.41698,53.15408 -37.56786,95.885 -22.38132,39.59998 -68.42303,106.56509 -103.69182,150.81396 l -9.36457,11.74896 3.29834,11.74604 c 7.13081,25.39415 9.80051,47.7635 8.52566,71.43604 -2.54576,47.27199 -19.84681,92.72117 -51.24552,134.61999 -38.48369,51.3531 -105.30238,88.00821 -170.2978,93.42123 -18.39514,1.532 -30.63556,0.75158 -55.06727,-3.51094 l -11.70122,-2.04148 -11.15878,9.96602 c -6.13732,5.48131 -14.58777,13.43771 -18.77877,17.68089 -10.51762,10.64858 -43.45636,40.26942 -56.515,50.82226 -5.93725,4.79798 -14.7955,12.23136 -19.685,16.51864 -13.04347,11.43696 -43.66024,34.36461 -66.0191,49.439 -41.09583,27.70691 -78.80074,42.64607 -93.67185,37.11399 z m 66.5979,-52.38767 c 5.79868,-1.82768 13.40056,-4.75086 16.89306,-6.49597 7.466,-3.73055 27.305,-15.53181 27.305,-16.24242 0,-0.27003 -7.37042,3.2056 -16.37871,7.72363 -20.25316,10.15778 -31.76046,13.32827 -43.28717,11.92649 -7.170212,-0.87198 -8.756036,-1.57401 -12.306071,-5.44778 -7.533269,-8.22021 -8.387789,-20.83427 -2.792929,-41.22769 3.811373,-13.89254 2.156012,-13.88578 -2.45175,0.0102 -14.5139,43.77012 -3.00732,61.10869 33.01857,49.75372 z M 170.81499,139.52 c -0.4317,-0.6985 -1.3564,-1.27 -2.0549,-1.27 -0.6985,0 -0.9168,0.5715 -0.4851,1.27 0.43169,0.6985 1.35639,1.27 2.05489,1.27 0.6985,0 0.9168,-0.5715 0.48511,-1.27 z M 357.56847,-27.96658 c 0.66358,-0.61412 3.4925,-1.81152 6.2865,-2.66088 2.794,-0.84936 6.38721,-3.06152 7.9849,-4.91591 l 2.90491,-3.37163 2.41383,2.90094 2.41384,2.90093 12.46127,-11.79093 c 24.18745,-22.88635 60.29,-62.23902 76.06334,-82.91094 3.73085,-4.8895 11.03923,-14.4172 16.24086,-21.17266 5.20163,-6.75547 9.83453,-13.4706 10.29535,-14.9225 0.46082,-1.45191 1.48514,-2.63983 2.27628,-2.63983 1.02367,0 0.99899,-0.43942 -0.0856,-1.524 -2.36199,-2.36199 -1.8031,-9.28778 1.15393,-14.29958 2.16303,-3.66605 2.37732,-4.90098 1.1145,-6.4226 -0.85988,-1.0361 -2.28073,-1.88382 -3.15742,-1.88382 -0.87669,0 -2.29547,-0.84524 -3.15285,-1.87832 -1.13245,-1.3645 -2.19434,-1.53821 -3.88201,-0.635 -1.27773,0.68383 -2.93223,1.24332 -3.67665,1.24332 -2.21247,0 -1.54104,-8.19929 1.15862,-14.14882 1.38166,-3.0449 2.52466,-7.07808 2.54,-8.96262 0.0268,-3.29741 -0.12344,-3.37369 -3.99063,-2.02557 -4.21369,1.4689 -10.4015,7.24859 -23.55652,22.0028 -4.33952,4.86707 -10.07183,11.13521 -12.73846,13.92921 -2.66665,2.794 -5.97412,6.5621 -7.34996,8.37355 -8.81665,11.6081 -52.48396,62.56626 -56.48974,65.92144 -0.75522,0.63255 -18.16782,20.204 -24.14816,27.14208 -1.729,2.0059 -6.28688,7.20214 -10.12863,11.5472 -9.79619,11.07965 -23.56129,28.79696 -24.58892,31.64889 -0.77587,2.15319 -0.35154,2.32463 4.23206,1.70985 2.85771,-0.3833 6.84897,-2.15969 9.09076,-4.04604 5.27245,-4.43647 9.45808,-3.95309 9.10089,1.05101 -0.1431,2.00496 -1.14323,4.62114 -2.2225,5.8137 -1.07926,1.19257 -1.96229,2.51219 -1.96229,2.93249 0,1.77546 11.63563,2.72573 13.3985,1.09424 z m 219.39866,-227.87111 c 15.19184,-26.33502 22.86572,-42.63452 24.3612,-51.74374 0.51047,-3.10946 1.79091,-6.63066 2.84541,-7.82489 1.34954,-1.52839 1.62489,-3.22063 0.92987,-5.715 -0.54307,-1.94902 -0.86879,-4.49569 -0.7238,-5.65925 0.14497,-1.16357 -0.57822,-4.13028 -1.60707,-6.59269 -1.50939,-3.61246 -2.89107,-4.75187 -7.15436,-5.89984 -13.1184,-3.53243 -30.90985,3.30581 -64.33828,24.72874 -2.21366,1.41865 -6.91683,4.29387 -10.45148,6.38937 -3.53464,2.0955 -6.24936,4.3815 -6.03273,5.08 0.21664,0.6985 5.49131,5.12737 11.72148,9.84193 22.48347,17.01391 41.1219,37.80315 41.21825,45.97467 l 0.0457,3.8734 3.19901,-3.81 c 1.75944,-2.0955 4.45352,-5.98471 5.98681,-8.6427 z m 89.78282,-74.5423 c 0.74765,-1.397 1.07362,-2.54 0.72437,-2.54 -0.34925,0 -1.24672,1.143 -1.99437,2.54 -0.74765,1.397 -1.07362,2.54 -0.72437,2.54 0.34925,0 1.24672,-1.143 1.99437,-2.54 z m 5.75882,-10.4775 2.01407,-4.1275 -2.58821,3.175 c -2.49194,3.05691 -3.30527,5.08 -2.0423,5.08 0.33132,0 1.50871,-1.85738 2.61644,-4.1275 z m -65.44882,1.65951 c 0,-1.36801 -5.03404,-5.15201 -6.85396,-5.15201 -0.66483,0 0.13344,1.42875 1.77396,3.175 3.08429,3.28307 5.08,4.05976 5.08,1.97701 z m 10.32783,-9.15557 c -1.58903,-4.17951 -4.03775,-6.00723 -9.17887,-6.85112 l -4.32396,-0.70975 4.00656,1.86005 c 2.2036,1.02303 5.30625,3.618 6.89478,5.7666 3.29638,4.45858 4.31189,4.43291 2.60149,-0.0658 z m 65.61947,-11.30412 c 6.73704,-12.62428 15.03899,-32.66706 18.11008,-43.7218 3.35541,-12.07818 3.34528,-27.77235 -0.0215,-33.29402 -4.29888,-7.05038 -8.12943,-8.65641 -20.32551,-8.52182 -12.52469,0.13821 -23.84765,3.56272 -42.59536,12.8825 -12.47929,6.20365 -40.3374,23.28376 -39.44004,24.18112 0.30734,0.30733 2.16333,-0.57565 4.12441,-1.96219 5.64548,-3.99148 31.88261,-17.59334 41.90625,-21.72504 13.41845,-5.53103 26.94366,-8.2475 35.64881,-7.15986 5.99654,0.74922 7.99121,1.68561 11.88498,5.57938 3.85027,3.85027 4.83319,5.91271 5.54925,11.64391 1.57773,12.62769 -3.63856,33.03383 -14.26774,55.8155 -9.83223,21.07355 -10.11402,24.15973 -0.57362,6.28232 z"
+         id="path8430"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#b3b1ae;stroke-width:1.26999998"
+         d="M 53.03403,286.37461 C 42.62876,282.50383 38.065,276.5956 36.12918,264.48964 33.08625,245.46021 54.25887,189.04459 82.07666,142.06 c 3.51522,-5.93725 8.679798,-14.7955 11.476836,-19.685 4.855398,-8.48771 7.749194,-13.19328 25.646554,-41.7035 4.31355,-6.87141 11.71518,-18.01566 16.44807,-24.765 4.73289,-6.74932 9.59416,-13.84312 10.80283,-15.764 2.82457,-4.48893 7.77191,-4.60046 14.08321,-0.3175 2.57325,1.74625 5.93912,3.17225 7.47973,3.16888 4.67738,-0.0102 20.601,6.35051 24.1603,9.65088 3.75661,3.48333 3.98938,2.36739 -4.21356,20.20024 -1.92782,4.191 -4.26457,9.906 -5.19277,12.7 -0.92821,2.794 -2.07095,5.6515 -2.53944,6.35 -1.54576,2.30467 -4.763,15.11398 -5.64743,22.48496 -0.74142,6.17925 -0.45997,7.90726 1.91344,11.7475 1.53049,2.4764 3.73623,4.47257 4.90164,4.43593 1.6878,-0.0531 1.53753,-0.47217 -0.73858,-2.0599 -2.76727,-1.93035 -3.94052,-5.49349 -1.80888,-5.49349 1.55193,0 6.57138,5.64043 6.57138,7.38435 0,2.27198 5.86879,1.80542 8.33699,-0.66277 1.19263,-1.19262 4.23382,-2.22293 6.75821,-2.28958 4.57963,-0.12091 18.2285,-6.29858 24.07279,-10.8957 1.6965,-1.33447 3.76482,-2.4263 4.59629,-2.4263 1.47147,0 5.26957,-2.706 27.08784,-19.29901 8.80222,-6.69417 10.46699,-8.52584 10.16,-11.1786 -0.26571,-2.2962 -1.77044,-3.82455 -5.44713,-5.53268 C 248.01231,72.08285 220.36867,45.99628 209.09921,29.14651 202.56719,19.38001 193.07137,2.38575 190.58364,-3.99 c -0.95391,-2.44475 -3.35585,-8.23874 -5.33763,-12.87554 -1.98179,-4.63679 -5.75041,-16.92404 -8.3747,-27.305 l -4.77144,-18.87446 0.0462,-27.305 c 0.0494,-29.22916 1.21046,-37.63072 8.87919,-64.2534 9.99516,-34.69905 35.39645,-77.11005 62.3925,-104.17296 38.63869,-38.73441 84.34592,-62.12371 136.32558,-69.76052 21.41884,-3.14684 51.37243,-2.10279 71.43268,2.48984 l 14.93106,3.41833 18.72394,-16.72553 c 102.80077,-91.82887 187.66222,-146.87536 230.47058,-149.49775 8.81805,-0.54019 10.54464,-0.24519 16.97314,2.89999 6.29111,3.07795 7.56023,4.36447 10.49529,10.63918 2.67737,5.72378 3.31221,8.899 3.20456,16.02783 -0.29701,19.66777 -13.41698,53.15408 -37.56786,95.885 -22.38132,39.59998 -68.42303,106.56509 -103.69182,150.81396 l -9.36457,11.74896 3.29834,11.74604 c 7.13081,25.39415 9.80051,47.7635 8.52566,71.43604 -2.54576,47.27199 -19.84681,92.72117 -51.24552,134.61999 -38.48369,51.3531 -105.30238,88.00821 -170.2978,93.42123 -18.39514,1.532 -30.63556,0.75158 -55.06727,-3.51094 l -11.70122,-2.04148 -11.15878,9.96602 c -6.13732,5.48131 -14.58777,13.43771 -18.77877,17.68089 -10.51762,10.64858 -43.45636,40.26942 -56.515,50.82226 -5.93725,4.79798 -14.7955,12.23136 -19.685,16.51864 -13.04347,11.43696 -43.66024,34.36461 -66.0191,49.439 -41.09583,27.70691 -78.80074,42.64607 -93.67185,37.11399 z m 66.5979,-52.38767 c 5.79868,-1.82768 13.40056,-4.75086 16.89306,-6.49597 7.466,-3.73055 27.305,-15.53181 27.305,-16.24242 0,-0.27003 -7.37042,3.2056 -16.37871,7.72363 -20.25316,10.15778 -31.76046,13.32827 -43.28717,11.92649 -7.170212,-0.87198 -8.756036,-1.57401 -12.306071,-5.44778 -7.533269,-8.22021 -8.387789,-20.83427 -2.792929,-41.22769 3.811373,-13.89254 2.156012,-13.88578 -2.45175,0.0102 -14.5139,43.77012 -3.00732,61.10869 33.01857,49.75372 z M 170.81499,139.52 c -0.4317,-0.6985 -1.3564,-1.27 -2.0549,-1.27 -0.6985,0 -0.9168,0.5715 -0.4851,1.27 0.43169,0.6985 1.35639,1.27 2.05489,1.27 0.6985,0 0.9168,-0.5715 0.48511,-1.27 z M 342.62327,-11.10342 c 2.59693,-1.11839 7.44183,-2.18747 10.76642,-2.37575 4.16932,-0.23611 7.70859,-1.47684 11.40766,-3.99908 9.06723,-6.18253 56.50969,-54.35981 79.26408,-80.49175 36.25966,-41.64185 73.49936,-90.72559 70.32276,-92.68885 -0.69397,-0.42889 -1.21318,-3.32373 -1.15382,-6.43297 0.18994,-9.94853 -0.52114,-17.71817 -1.62155,-17.71817 -0.58787,0 -1.06886,0.5715 -1.06886,1.27 0,2.65627 -3.49775,1.0948 -7.38632,-3.29742 -3.06092,-3.45735 -4.04368,-5.76143 -4.04368,-9.48031 0,-10.6461 -8.71144,-8.84556 -19.685,4.06864 -14.48532,17.04699 -17.83054,20.76522 -30.0884,33.44352 -19.17741,19.83515 -28.96465,30.33106 -28.98816,31.08711 -0.0114,0.3814 -4.58386,4.9016 -10.16,10.0449 -5.78434,5.33533 -10.13843,10.38046 -10.13843,11.7475 0,1.31783 -0.81875,2.39606 -1.81942,2.39606 -1.00068,0 -2.16546,1.09029 -2.58841,2.42286 -0.42295,1.33259 -3.30254,4.75824 -6.39911,7.61255 -3.09656,2.85431 -6.50051,6.87282 -7.56433,8.93003 -1.06382,2.0572 -2.97268,4.48944 -4.24191,5.40497 -1.26921,0.91551 -4.80239,5.09358 -7.85148,9.28458 -5.31305,7.30282 -14.53148,18.42266 -31.94603,38.53528 -21.56413,24.90512 -30.37632,37.63276 -28.33853,40.92997 0.5124,0.82908 2.20336,1.02575 4.21027,0.48967 9.53115,-2.5459 18.0003,0.41837 20.3357,7.11766 1.5007,4.30493 2.34563,4.46849 8.77655,1.699 z m 234.34386,-244.73427 c 15.19184,-26.33502 22.86572,-42.63452 24.3612,-51.74374 0.51047,-3.10946 1.79091,-6.63066 2.84541,-7.82489 1.34954,-1.52839 1.62489,-3.22063 0.92987,-5.715 -0.54307,-1.94902 -0.86879,-4.49569 -0.7238,-5.65925 0.14497,-1.16357 -0.57822,-4.13028 -1.60707,-6.59269 -1.50939,-3.61246 -2.89107,-4.75187 -7.15436,-5.89984 -13.1184,-3.53243 -30.90985,3.30581 -64.33828,24.72874 -2.21366,1.41865 -6.91683,4.29387 -10.45148,6.38937 -3.53464,2.0955 -6.24936,4.3815 -6.03273,5.08 0.21664,0.6985 5.49131,5.12737 11.72148,9.84193 22.48347,17.01391 41.1219,37.80315 41.21825,45.97467 l 0.0457,3.8734 3.19901,-3.81 c 1.75944,-2.0955 4.45352,-5.98471 5.98681,-8.6427 z m 89.78282,-74.5423 c 0.74765,-1.397 1.07362,-2.54 0.72437,-2.54 -0.34925,0 -1.24672,1.143 -1.99437,2.54 -0.74765,1.397 -1.07362,2.54 -0.72437,2.54 0.34925,0 1.24672,-1.143 1.99437,-2.54 z m 5.75882,-10.4775 2.01407,-4.1275 -2.58821,3.175 c -2.49194,3.05691 -3.30527,5.08 -2.0423,5.08 0.33132,0 1.50871,-1.85738 2.61644,-4.1275 z m -65.44882,1.65951 c 0,-1.36801 -5.03404,-5.15201 -6.85396,-5.15201 -0.66483,0 0.13344,1.42875 1.77396,3.175 3.08429,3.28307 5.08,4.05976 5.08,1.97701 z m 10.32783,-9.15557 c -1.58903,-4.17951 -4.03775,-6.00723 -9.17887,-6.85112 l -4.32396,-0.70975 4.00656,1.86005 c 2.2036,1.02303 5.30625,3.618 6.89478,5.7666 3.29638,4.45858 4.31189,4.43291 2.60149,-0.0658 z m 65.61947,-11.30412 c 6.73704,-12.62428 15.03899,-32.66706 18.11008,-43.7218 3.35541,-12.07818 3.34528,-27.77235 -0.0215,-33.29402 -4.29888,-7.05038 -8.12943,-8.65641 -20.32551,-8.52182 -12.52469,0.13821 -23.84765,3.56272 -42.59536,12.8825 -12.47929,6.20365 -40.3374,23.28376 -39.44004,24.18112 0.30734,0.30733 2.16333,-0.57565 4.12441,-1.96219 5.64548,-3.99148 31.88261,-17.59334 41.90625,-21.72504 13.41845,-5.53103 26.94366,-8.2475 35.64881,-7.15986 5.99654,0.74922 7.99121,1.68561 11.88498,5.57938 3.85027,3.85027 4.83319,5.91271 5.54925,11.64391 1.57773,12.62769 -3.63856,33.03383 -14.26774,55.8155 -9.83223,21.07355 -10.11402,24.15973 -0.57362,6.28232 z"
+         id="path8428"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#d6b277;stroke-width:1.26999998"
+         d="M 51.435,285.01316 C 45.15376,282.6926 39.953,277.08029 38.18547,270.71519 29.72376,240.24359 64.4959,163.39819 133.18002,60.78 c 16.87373,-25.21036 16.4878,-24.80954 22.10326,-22.95626 2.43627,0.80403 5.40963,2.54482 6.60747,3.86843 1.19784,1.32359 2.9705,2.10238 3.93926,1.73063 2.80992,-1.07827 21.1655,5.79661 24.8635,9.31235 l 3.36852,3.20251 -2.48562,5.91368 c -1.36708,3.2525 -3.55587,8.19966 -4.86396,10.99366 -8.57116,18.30747 -13.26154,34.03811 -13.31676,44.66188 -0.034,6.55989 0.45637,8.36152 3.01991,11.09305 5.83121,6.21335 15.98949,5.63721 32.37321,-1.83607 4.26039,-1.94336 10.31792,-5.28708 13.46117,-7.4305 3.14325,-2.14342 6.20921,-3.90753 6.81326,-3.92024 3.03333,-0.0639 38.18769,-27.07231 38.70001,-29.73253 0.46402,-2.40947 -4.34446,-8.39059 -6.74554,-8.39059 -6.03461,0 -42.02265,-34.01297 -51.73422,-48.895 -8.67801,-13.29815 -16.98433,-28.85151 -19.45956,-36.43749 -1.21002,-3.70837 -2.69894,-7.37621 -3.30871,-8.15075 -2.08445,-2.64762 -6.22983,-16.29015 -10.17327,-33.48037 -3.54867,-15.4693 -3.94961,-19.33278 -4.02363,-38.77139 -0.14743,-38.72659 7.28775,-69.28421 25.77215,-105.92011 29.55499,-58.57769 80.62328,-103.72286 139.49634,-123.31699 23.78341,-7.91559 37.14243,-10.25486 63.10235,-11.04971 23.91549,-0.73226 37.33739,0.38831 55.62711,4.64421 l 9.91132,2.30629 22.70262,-20.08059 c 92.40099,-81.72915 165.84183,-131.73959 210.76143,-143.52053 20.78032,-5.45001 35.76148,-2.25583 41.9883,8.95244 2.65212,4.7738 3.17216,7.38825 3.15773,15.875 -0.04,23.53453 -13.04097,55.93195 -43.16273,107.55816 -20.77525,35.60711 -65.25436,99.25787 -99.95099,143.03253 -4.0502,5.10989 -7.364,9.44955 -7.364,9.64368 0,0.19415 1.14989,3.69518 2.55531,7.7801 7.10996,20.66549 10.96498,54.53869 8.9112,78.30077 -3.15903,36.54981 -9.23775,58.716 -24.47377,89.24428 -12.80333,25.65395 -29.11172,47.83815 -49.73337,67.65198 -39.39988,37.85645 -90.05749,61.48763 -142.52097,66.48439 -17.61204,1.67742 -34.74836,0.85233 -55.11141,-2.65352 -8.02008,-1.3808 -14.71004,-2.51432 -14.8666,-2.51896 -0.50429,-0.0149 -24.85023,22.25381 -33.37036,30.52319 -10.52421,10.21449 -43.38134,39.84482 -50.64176,45.66839 -3.04793,2.44475 -11.61705,9.60502 -19.04246,15.9117 -19.14333,16.25914 -47.10731,37.32995 -70.24449,52.92908 -42.08894,28.37648 -77.98417,42.07203 -94.38627,36.01238 z m 64.65416,-49.19004 c 12.73813,-3.43711 36.39178,-15.37167 54.72583,-27.61216 13.09684,-8.74395 17.61672,-12.02948 32.70453,-23.77319 9.50928,-7.40164 5.96982,-6.53869 -4.3541,1.06157 -35.84656,26.38946 -69.21793,43.69333 -86.96283,45.09231 -30.53146,2.40707 -34.2821,-23.6487 -11.06228,-76.84977 10.7522,-24.63534 10.94184,-25.10444 9.60016,-23.74688 -2.37353,2.40162 -13.840892,25.41647 -18.983683,38.1 -7.578767,18.69132 -10.726467,30.42244 -11.438157,42.62874 -0.84593,14.50852 2.00415,21.64521 10.114064,25.32593 7.07478,3.21092 13.114686,3.15758 25.656466,-0.22655 z m 59.80583,-94.27173 c -2.0955,-0.92354 -5.38163,-3.2651 -7.3025,-5.20347 -3.54245,-3.57468 -4.60618,-2.55186 -2.13948,2.05721 1.54603,2.8888 5.45745,4.74987 10.07698,4.79466 3.05611,0.0296 3.03234,-0.0321 -0.635,-1.6484 z M 342.62327,-11.10342 c 2.59693,-1.11839 7.44183,-2.18747 10.76642,-2.37575 4.16932,-0.23611 7.70859,-1.47684 11.40766,-3.99908 9.06723,-6.18253 56.50969,-54.35981 79.26408,-80.49175 36.25966,-41.64185 73.49936,-90.72559 70.32276,-92.68885 -0.69397,-0.42889 -1.21318,-3.32373 -1.15382,-6.43297 0.18994,-9.94853 -0.52114,-17.71817 -1.62155,-17.71817 -0.58787,0 -1.06886,0.5715 -1.06886,1.27 0,2.65627 -3.49775,1.0948 -7.38632,-3.29742 -3.06092,-3.45735 -4.04368,-5.76143 -4.04368,-9.48031 0,-10.6461 -8.71144,-8.84556 -19.685,4.06864 -14.48532,17.04699 -17.83054,20.76522 -30.0884,33.44352 -19.17741,19.83515 -28.96465,30.33106 -28.98816,31.08711 -0.0114,0.3814 -4.58386,4.9016 -10.16,10.0449 -5.78434,5.33533 -10.13843,10.38046 -10.13843,11.7475 0,1.31783 -0.81875,2.39606 -1.81942,2.39606 -1.00068,0 -2.16546,1.09029 -2.58841,2.42286 -0.42295,1.33259 -3.30254,4.75824 -6.39911,7.61255 -3.09656,2.85431 -6.50051,6.87282 -7.56433,8.93003 -1.06382,2.0572 -2.97268,4.48944 -4.24191,5.40497 -1.26921,0.91551 -4.80239,5.09358 -7.85148,9.28458 -5.31305,7.30282 -14.53148,18.42266 -31.94603,38.53528 -21.56413,24.90512 -30.37632,37.63276 -28.33853,40.92997 0.5124,0.82908 2.20336,1.02575 4.21027,0.48967 9.53115,-2.5459 18.0003,0.41837 20.3357,7.11766 1.5007,4.30493 2.34563,4.46849 8.77655,1.699 z m 228.15084,-232.93076 c 3.19321,-4.1988 5.80584,-8.15544 5.80584,-8.79255 0,-0.63711 1.33112,-2.77386 2.95805,-4.74832 3.81432,-4.62916 22.56604,-42.85758 23.87591,-48.67494 0.55047,-2.44475 2.09018,-8.50106 3.42156,-13.45846 2.5655,-9.55261 2.17003,-18.86204 -0.96153,-22.63535 -6.52888,-7.86681 -29.66163,-0.75616 -62.94898,19.34959 -26.28231,15.87465 -29.94693,18.34241 -29.45349,19.83401 0.24658,0.74537 5.54573,5.20859 11.7759,9.91828 14.21854,10.7485 28.86537,24.7686 35.99081,34.45078 4.95679,6.73541 5.41547,7.96506 4.7586,12.75742 -0.39982,2.91696 -1.10081,6.27785 -1.55776,7.46864 -1.52011,3.96135 0.46936,2.24384 6.33509,-5.4691 z m 80.53114,-60.08085 c 32.08827,-47.93853 53.50611,-95.55569 53.53579,-119.02336 0.0409,-32.32052 -36.06091,-30.44131 -93.18093,4.85035 -20.2122,12.48813 -57.6047,39.2498 -64.65713,46.27488 -1.24958,1.24474 -2.67833,2.26361 -3.175,2.26416 -0.49666,5.4e-4 -3.0098,1.85792 -5.58475,4.1275 -2.57495,2.26958 -5.7182,4.77385 -6.985,5.56506 -1.2668,0.7912 -1.89752,1.8637 -1.40161,2.38332 0.49591,0.51964 3.63916,-1.31463 6.985,-4.07613 21.53812,-17.77655 61.46891,-45.13963 82.91833,-56.82081 17.47303,-9.51567 34.28327,-16.48008 46.15225,-19.12068 8.88699,-1.97717 10.09775,-1.98192 16.68995,-0.0655 8.80406,2.55941 11.40916,5.09239 13.91472,13.52952 1.57431,5.3013 1.7094,8.23495 0.67687,14.69843 -3.72289,23.3043 -20.97503,60.96332 -47.20294,103.03742 -4.05397,6.50326 -7.37085,12.10912 -7.37085,12.45745 0,1.81859 3.22721,-1.92745 8.6853,-10.0816 z m -147.11529,-31.38964 c 0,-1.80421 -0.60792,-1.52762 -3.29566,1.49948 -2.40637,2.71019 -2.40402,2.71675 0.43816,1.22532 1.57163,-0.82473 2.8575,-2.05088 2.8575,-2.7248 z m 115.55055,-5.39361 c 0.0102,-1.94331 -0.81723,-5.53576 -1.83985,-7.98321 -2.20522,-5.27782 -9.25807,-8.89144 -14.51359,-7.43619 -3.30564,0.91532 -3.30342,0.91984 1.13288,2.3149 8.80715,2.76952 12.68947,7.44954 12.71944,15.33288 0.0144,3.77023 0.34311,4.6141 1.25056,3.20991 0.67711,-1.04775 1.23986,-3.49498 1.25056,-5.43829 z m -107.93055,-0.27671 c 0.88193,-1.06266 1.01945,-1.905 0.31101,-1.905 -0.6985,0 -1.98145,0.85725 -2.85101,1.905 -0.88193,1.06266 -1.01945,1.905 -0.31101,1.905 0.6985,0 1.98145,-0.85725 2.85101,-1.905 z m 7.366,-5.969 c 2.39724,-2.39724 1.78131,-3.78308 -1.016,-2.286 -1.397,0.74765 -2.54,1.91075 -2.54,2.58468 0,1.68197 1.71979,1.53752 3.556,-0.29868 z m 7.62,-6.35 c 2.42806,-2.42806 1.76484,-3.72793 -1.016,-1.99127 -2.56593,1.60246 -3.39522,3.51527 -1.524,3.51527 0.5588,0 1.7018,-0.6858 2.54,-1.524 z"
+         id="path8426"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#caae82;stroke-width:1.26999998"
+         d="M 51.435,285.01316 C 45.15376,282.6926 39.953,277.08029 38.18547,270.71519 29.72376,240.24359 64.4959,163.39819 133.18002,60.78 c 16.87373,-25.21036 16.4878,-24.80954 22.10326,-22.95626 2.43627,0.80403 5.40963,2.54482 6.60747,3.86843 1.19784,1.32359 2.9705,2.10238 3.93926,1.73063 2.80992,-1.07827 21.1655,5.79661 24.8635,9.31235 l 3.36852,3.20251 -2.48562,5.91368 c -1.36708,3.2525 -3.55587,8.19966 -4.86396,10.99366 -8.57116,18.30747 -13.26154,34.03811 -13.31676,44.66188 -0.034,6.55989 0.45637,8.36152 3.01991,11.09305 5.83121,6.21335 15.98949,5.63721 32.37321,-1.83607 4.26039,-1.94336 10.31792,-5.28708 13.46117,-7.4305 3.14325,-2.14342 6.20921,-3.90753 6.81326,-3.92024 3.03333,-0.0639 38.18769,-27.07231 38.70001,-29.73253 0.46402,-2.40947 -4.34446,-8.39059 -6.74554,-8.39059 -6.03461,0 -42.02265,-34.01297 -51.73422,-48.895 -8.67801,-13.29815 -16.98433,-28.85151 -19.45956,-36.43749 -1.21002,-3.70837 -2.69894,-7.37621 -3.30871,-8.15075 -2.08445,-2.64762 -6.22983,-16.29015 -10.17327,-33.48037 -3.54867,-15.4693 -3.94961,-19.33278 -4.02363,-38.77139 -0.14743,-38.72659 7.28775,-69.28421 25.77215,-105.92011 29.55499,-58.57769 80.62328,-103.72286 139.49634,-123.31699 23.78341,-7.91559 37.14243,-10.25486 63.10235,-11.04971 23.91549,-0.73226 37.33739,0.38831 55.62711,4.64421 l 9.91132,2.30629 22.70262,-20.08059 c 92.40099,-81.72915 165.84183,-131.73959 210.76143,-143.52053 20.78032,-5.45001 35.76148,-2.25583 41.9883,8.95244 2.65212,4.7738 3.17216,7.38825 3.15773,15.875 -0.04,23.53453 -13.04097,55.93195 -43.16273,107.55816 -20.77525,35.60711 -65.25436,99.25787 -99.95099,143.03253 -4.0502,5.10989 -7.364,9.44955 -7.364,9.64368 0,0.19415 1.14989,3.69518 2.55531,7.7801 7.10996,20.66549 10.96498,54.53869 8.9112,78.30077 -3.15903,36.54981 -9.23775,58.716 -24.47377,89.24428 -12.80333,25.65395 -29.11172,47.83815 -49.73337,67.65198 -39.39988,37.85645 -90.05749,61.48763 -142.52097,66.48439 -17.61204,1.67742 -34.74836,0.85233 -55.11141,-2.65352 -8.02008,-1.3808 -14.71004,-2.51432 -14.8666,-2.51896 -0.50429,-0.0149 -24.85023,22.25381 -33.37036,30.52319 -10.52421,10.21449 -43.38134,39.84482 -50.64176,45.66839 -3.04793,2.44475 -11.61705,9.60502 -19.04246,15.9117 -19.14333,16.25914 -47.10731,37.32995 -70.24449,52.92908 -42.08894,28.37648 -77.98417,42.07203 -94.38627,36.01238 z m 64.65416,-49.19004 c 12.73813,-3.43711 36.39178,-15.37167 54.72583,-27.61216 13.09684,-8.74395 17.61672,-12.02948 32.70453,-23.77319 9.50928,-7.40164 5.96982,-6.53869 -4.3541,1.06157 -35.84656,26.38946 -69.21793,43.69333 -86.96283,45.09231 -30.53146,2.40707 -34.2821,-23.6487 -11.06228,-76.84977 10.7522,-24.63534 10.94184,-25.10444 9.60016,-23.74688 -2.37353,2.40162 -13.840892,25.41647 -18.983683,38.1 -7.578767,18.69132 -10.726467,30.42244 -11.438157,42.62874 -0.84593,14.50852 2.00415,21.64521 10.114064,25.32593 7.07478,3.21092 13.114686,3.15758 25.656466,-0.22655 z m 59.80583,-94.27173 c -2.0955,-0.92354 -5.38163,-3.2651 -7.3025,-5.20347 -3.54245,-3.57468 -4.60618,-2.55186 -2.13948,2.05721 1.54603,2.8888 5.45745,4.74987 10.07698,4.79466 3.05611,0.0296 3.03234,-0.0321 -0.635,-1.6484 z M 342.39192,5.02839 c 7.56626,-5.62401 49.09271,-45.57019 68.12588,-65.53339 41.8035,-43.84614 83.53458,-94.4667 106.21492,-128.84066 8.98663,-13.61998 9.62656,-15.05049 10.10684,-22.59295 0.37471,-5.8849 0.005,-8.63695 -1.37287,-10.22435 -1.03707,-1.19462 -2.24504,-3.60078 -2.68439,-5.34703 -0.45451,-1.80649 -2.90152,-4.45995 -5.67716,-6.15611 -3.95161,-2.41479 -5.17576,-4.14152 -6.4439,-9.08952 -0.86103,-3.35962 -2.67531,-7.11274 -4.03171,-8.34027 -2.81845,-2.55065 -1.31001,-3.55842 -19.1312,12.78122 -15.28389,14.01328 -23.57805,20.96468 -25.01428,20.96468 -0.72909,0 -3.92642,3.37991 -7.10516,7.51093 -3.17875,4.13102 -6.21367,7.70289 -6.74429,7.9375 -0.53062,0.23461 -6.39286,6.71307 -13.0272,14.39657 -6.63434,7.6835 -16.92018,19.43592 -22.85743,26.11649 -5.93725,6.68057 -14.11698,16.11032 -18.17717,20.955 -20.58465,24.56186 -31.42452,37.25715 -38.33783,44.8999 -4.191,4.63321 -10.19175,11.77446 -13.335,15.86945 -3.14325,4.09499 -7.18789,9.22377 -8.9881,11.3973 -1.8002,2.17351 -5.11379,6.66648 -7.36351,9.98435 -2.24972,3.31788 -4.63445,6.0325 -5.2994,6.0325 -0.66494,0 -1.20899,1.15577 -1.20899,2.56838 0,1.4126 -2.15365,4.98448 -4.78589,7.9375 -12.50644,14.03052 -24.8208,32.70261 -23.62417,35.82098 0.81765,2.13078 2.17498,2.38493 10.68102,1.99997 5.16448,-0.23373 5.95935,0.11354 7.62,3.32861 3.5936,6.95746 5.87826,8.63399 9.51051,6.97903 4.67343,-2.12936 11.95227,-1.89096 12.83926,0.42053 1.11598,2.90818 4.64208,2.28862 10.11122,-1.77661 z m 121.03102,-8.23034 c 0.79969,-0.96356 3.17408,-2.09596 5.27642,-2.51643 4.16701,-0.8334 18.7493,-14.0045 22.8898,-20.67467 1.36158,-2.19342 5.65277,-6.38684 9.536,-9.3187 4.79924,-3.62346 7.81381,-7.08829 9.41289,-10.81881 1.29388,-3.01849 3.48298,-5.84696 4.86465,-6.28549 1.38168,-0.43852 3.48922,-2.85634 4.68342,-5.37291 1.95117,-4.1118 5.03641,-8.62847 13.60764,-19.92104 3.75341,-4.94512 6.13183,-9.7 4.852,-9.7 -1.72465,0 -0.85244,-4.32366 1.2042,-5.96933 1.59046,-1.27265 1.43319,-1.40547 -0.9525,-0.80438 -1.57163,0.39598 -2.8575,1.32006 -2.8575,2.05351 0,0.73345 -0.32741,1.00613 -0.72757,0.60596 -0.40017,-0.40016 -3.86701,2.36209 -7.7041,6.13833 -3.83709,3.77626 -7.62179,6.86591 -8.41044,6.86591 -2.23156,0 -8.55789,6.58382 -8.55789,8.90624 0,2.72822 -5.06387,7.42342 -8.58605,7.96095 -3.4744,0.53023 -4.05394,1.11394 -4.76338,4.79772 -0.83258,4.32327 -4.0185,7.19516 -5.8975,5.31617 -1.13342,-1.13343 -0.30859,-2.78972 3.48344,-6.9949 2.73267,-3.03039 6.68299,-7.74805 8.77849,-10.48368 2.0955,-2.73562 4.66725,-5.90697 5.715,-7.04746 2.75163,-2.99517 10.44467,-17.69597 9.25728,-17.68997 -1.12125,0.005 -7.41459,6.61939 -18.78228,19.73844 -4.191,4.83668 -11.07171,12.51716 -15.29046,17.06774 -4.21874,4.55056 -9.71661,10.8455 -12.2175,13.98875 -2.50087,3.14325 -7.22106,8.5725 -10.48931,12.065 -6.20693,6.63283 -10.31495,13.07112 -9.10938,14.27668 1.22059,1.22059 10.92997,-6.27632 14.42407,-11.13726 1.84024,-2.56009 4.93922,-5.56917 6.88662,-6.68683 l 3.54075,-2.03211 -2.17691,3.42477 c -1.19729,1.88361 -2.905,3.88178 -3.79488,4.44036 -0.88991,0.55858 -1.618,1.84446 -1.618,2.8575 0,1.01304 -0.81064,1.84189 -1.80142,1.84189 -3.29779,0 -7.70878,9.01972 -7.91876,16.1925 -0.19861,6.78446 0.63877,8.05366 3.24316,4.91555 z m 16.63702,-38.75217 c 0,-0.62377 0.85725,-1.46308 1.905,-1.86515 1.16041,-0.44528 1.905,-0.003 1.905,1.13413 0,1.02583 -0.85725,1.86514 -1.905,1.86514 -1.04775,0 -1.905,-0.51036 -1.905,-1.13412 z M 320.14565,-95.05943 c 3.48313,-2.05754 3.75853,-2.05191 5.33885,0.10934 1.56893,2.14564 2.10781,2.01763 9.22338,-2.19118 4.87518,-2.88364 8.90921,-6.5181 11.36709,-10.24118 2.0955,-3.17417 5.62693,-6.64875 7.84761,-7.72131 2.22069,-1.07254 9.65018,-8.05845 16.51,-15.52423 6.85982,-7.46579 17.75876,-19.21375 24.21989,-26.10656 6.46112,-6.89282 11.7475,-13.00665 11.7475,-13.58629 0,-1.41962 8.37574,-13.8602 11.99691,-17.81914 2.66991,-2.91897 13.40309,-20.56534 13.40309,-22.03597 0,-0.35079 -0.98852,-0.82817 -2.1967,-1.06085 -1.75158,-0.33732 -2.1172,-1.52411 -1.80436,-5.85693 0.21578,-2.98864 1.20156,-6.98356 2.19059,-8.87758 1.72259,-3.29875 1.66019,-3.46106 -1.48294,-3.85638 -1.80467,-0.22698 -4.27581,0.4874 -5.4914,1.5875 -1.21561,1.10011 -2.87957,2.0002 -3.69769,2.0002 -0.81812,0 -1.4875,0.82352 -1.4875,1.83004 0,1.00653 -0.56496,2.17922 -1.25546,2.60596 -0.73392,0.4536 -0.95465,-0.17472 -0.53132,-1.51254 0.39826,-1.25866 -0.31724,-0.70844 -1.59003,1.22269 -1.27278,1.93114 -2.91499,3.28059 -3.64937,2.99878 -0.73437,-0.2818 -2.33798,0.22085 -3.56355,1.11701 -2.37263,1.73491 -7.19027,1.14477 -7.19027,-0.88078 0,-1.61955 -3.29366,-4.84116 -4.94943,-4.84116 -0.72647,0 -1.0079,-0.81556 -0.6254,-1.81234 0.50256,-1.30965 -0.0127,-1.62719 -1.85701,-1.14486 -1.40384,0.36712 -2.86171,1.47339 -3.23968,2.45838 -1.13622,2.96091 1.52829,8.0854 4.86334,9.3534 2.77659,1.05564 3.04428,1.72974 2.38654,6.00971 -0.87909,5.72049 -2.43422,7.99571 -5.46511,7.99571 -1.40306,0 -2.4977,-1.29092 -2.9615,-3.4925 -0.87015,-4.13053 -6.10865,-10.4775 -8.64766,-10.4775 -0.98195,0 -3.80012,1.66072 -6.2626,3.6905 l -4.47723,3.69052 -1.49351,-3.37302 c -1.19019,-2.68799 -2.36272,-3.373 -5.7735,-3.373 -6.28389,0 -8.86916,2.646 -7.85696,8.04152 0.91267,4.86495 -0.48741,6.51143 -5.56466,6.54403 -3.55489,0.0229 -9.03563,5.4396 -9.03563,8.9302 0,1.33643 -1.10684,3.02224 -2.45966,3.74624 -1.48367,0.79405 -2.61752,2.95349 -2.8575,5.44219 -0.33298,3.45326 -0.97524,4.1921 -3.93992,4.53246 -2.76326,0.31723 -3.70569,1.22476 -4.28625,4.1275 -0.40929,2.04647 -1.24423,3.72086 -1.85542,3.72086 -0.61118,0 -1.11125,0.58285 -1.11125,1.29523 0,0.71238 -1.42875,3.55853 -3.175,6.32477 -1.74625,2.76623 -3.175,6.15015 -3.175,7.5198 0,1.36966 -0.57149,2.8435 -1.26999,3.2752 -0.6985,0.43169 -1.27,2.44556 -1.27,4.47526 0,2.39058 -0.89462,4.16916 -2.54,5.04973 -2.66388,1.42567 -3.61423,7.62 -1.16909,7.62 0.86252,0 1.06058,1.41289 0.53409,3.81 -1.02961,4.68778 0.8121,4.96503 4.2181,0.635 3.30918,-4.20695 7.54582,-4.18175 8.60822,0.0512 0.95367,3.79972 -2.35074,8.20383 -6.15535,8.20383 -1.35318,0 -3.80175,1.05515 -5.44125,2.34477 -2.50717,1.97215 -2.71065,2.62809 -1.28037,4.1275 2.38856,2.50402 3.70153,9.59371 1.97437,10.66116 -0.76999,0.47588 -2.23221,0.17456 -3.24935,-0.6696 -1.30247,-1.08097 -1.84937,-1.1133 -1.84937,-0.10934 0,0.78402 0.5715,1.42549 1.27,1.42549 0.6985,0 1.27,1.905 1.27,4.23333 0,2.32835 0.381,4.61435 0.84667,5.08 1.492,1.49201 5.64013,0.90946 9.419,-1.32276 z m 61.47521,-99.45339 c -1.16199,-1.88013 -0.0594,-2.84118 2.65248,-2.31194 1.98405,0.3872 2.00509,0.59311 0.21246,2.07894 -1.42819,1.18375 -2.2367,1.24951 -2.86494,0.233 z m -76.39651,21.39719 c 0.37106,-1.41896 1.20206,-2.25398 1.84665,-1.85559 0.6446,0.39838 4.21316,-2.11588 7.93015,-5.58723 3.71698,-3.47134 7.22855,-6.31154 7.80349,-6.31154 0.57493,0 1.04533,-1.143 1.04533,-2.54 0,-1.60546 0.84668,-2.54 2.30117,-2.54 2.71599,0 7.07574,-5.51611 8.32212,-10.52945 0.48464,-1.94943 1.3463,-3.25698 1.91476,-2.90564 1.65537,1.02307 15.38124,-15.62114 14.58764,-17.68922 -1.03274,-2.69127 -6.56312,0.58251 -10.61569,6.2841 -1.74625,2.4568 -4.17512,4.80317 -5.3975,5.21415 -1.22237,0.41097 -2.2225,1.26032 -2.2225,1.88745 0,0.62712 -1.7145,3.09293 -3.81,5.47958 -2.0955,2.38663 -3.81,4.97827 -3.81,5.75919 0,0.78091 -0.5715,1.41984 -1.27,1.41984 -0.6985,0 -1.27,0.71702 -1.27,1.59339 0,0.87637 -2.51974,3.591 -5.59944,6.0325 -3.07968,2.44151 -6.54099,6.01073 -7.69179,7.93161 -1.1508,1.92087 -2.66529,3.4925 -3.36551,3.4925 -0.70024,0 -0.94166,0.53637 -0.53649,1.19194 0.40516,0.65558 -0.23255,1.73434 -1.41714,2.39727 -2.55852,1.43182 -3.51442,5.42629 -1.0745,4.49001 0.91002,-0.34922 1.95818,-1.7959 2.32925,-3.21486 z m 80.80945,-48.62374 c 1.92079,-2.31441 0.67299,-4.50062 -2.56879,-4.50062 -1.78271,0 -2.46504,0.87884 -2.46504,3.175 0,3.54002 2.62282,4.23072 5.03383,1.32562 z m 184.74031,-22.29481 c 3.19321,-4.1988 5.80584,-8.15544 5.80584,-8.79255 0,-0.63711 1.33112,-2.77386 2.95805,-4.74832 3.81432,-4.62916 22.56604,-42.85758 23.87591,-48.67494 0.55047,-2.44475 2.09018,-8.50106 3.42156,-13.45846 2.5655,-9.55261 2.17003,-18.86204 -0.96153,-22.63535 -6.52888,-7.86681 -29.66163,-0.75616 -62.94898,19.34959 -26.28231,15.87465 -29.94693,18.34241 -29.45349,19.83401 0.24658,0.74537 5.54573,5.20859 11.7759,9.91828 14.21854,10.7485 28.86537,24.7686 35.99081,34.45078 4.95679,6.73541 5.41547,7.96506 4.7586,12.75742 -0.39982,2.91696 -1.10081,6.27785 -1.55776,7.46864 -1.52011,3.96135 0.46936,2.24384 6.33509,-5.4691 z m 80.53114,-60.08085 c 32.08827,-47.93853 53.50611,-95.55569 53.53579,-119.02336 0.0409,-32.32052 -36.06091,-30.44131 -93.18093,4.85035 -20.2122,12.48813 -57.6047,39.2498 -64.65713,46.27488 -1.24958,1.24474 -2.67833,2.26361 -3.175,2.26416 -0.49666,5.4e-4 -3.0098,1.85792 -5.58475,4.1275 -2.57495,2.26958 -5.7182,4.77385 -6.985,5.56506 -1.2668,0.7912 -1.89752,1.8637 -1.40161,2.38332 0.49591,0.51964 3.63916,-1.31463 6.985,-4.07613 21.53812,-17.77655 61.46891,-45.13963 82.91833,-56.82081 17.47303,-9.51567 34.28327,-16.48008 46.15225,-19.12068 8.88699,-1.97717 10.09775,-1.98192 16.68995,-0.0655 8.80406,2.55941 11.40916,5.09239 13.91472,13.52952 1.57431,5.3013 1.7094,8.23495 0.67687,14.69843 -3.72289,23.3043 -20.97503,60.96332 -47.20294,103.03742 -4.05397,6.50326 -7.37085,12.10912 -7.37085,12.45745 0,1.81859 3.22721,-1.92745 8.6853,-10.0816 z m -147.11529,-31.38964 c 0,-1.80421 -0.60792,-1.52762 -3.29566,1.49948 -2.40637,2.71019 -2.40402,2.71675 0.43816,1.22532 1.57163,-0.82473 2.8575,-2.05088 2.8575,-2.7248 z m 115.55055,-5.39361 c 0.0102,-1.94331 -0.81723,-5.53576 -1.83985,-7.98321 -2.20522,-5.27782 -9.25807,-8.89144 -14.51359,-7.43619 -3.30564,0.91532 -3.30342,0.91984 1.13288,2.3149 8.80715,2.76952 12.68947,7.44954 12.71944,15.33288 0.0144,3.77023 0.34311,4.6141 1.25056,3.20991 0.67711,-1.04775 1.23986,-3.49498 1.25056,-5.43829 z m -107.93055,-0.27671 c 0.88193,-1.06266 1.01945,-1.905 0.31101,-1.905 -0.6985,0 -1.98145,0.85725 -2.85101,1.905 -0.88193,1.06266 -1.01945,1.905 -0.31101,1.905 0.6985,0 1.98145,-0.85725 2.85101,-1.905 z m 7.366,-5.969 c 2.39724,-2.39724 1.78131,-3.78308 -1.016,-2.286 -1.397,0.74765 -2.54,1.91075 -2.54,2.58468 0,1.68197 1.71979,1.53752 3.556,-0.29868 z m 7.62,-6.35 c 2.42806,-2.42806 1.76484,-3.72793 -1.016,-1.99127 -2.56593,1.60246 -3.39522,3.51527 -1.524,3.51527 0.5588,0 1.7018,-0.6858 2.54,-1.524 z"
+         id="path8424"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#c8a976;stroke-width:1.26999998"
+         d="M 51.435,285.01316 C 45.15376,282.6926 39.953,277.08029 38.18547,270.71519 29.72376,240.24359 64.4959,163.39819 133.18002,60.78 c 16.87373,-25.21036 16.4878,-24.80954 22.10326,-22.95626 2.43627,0.80403 5.40963,2.54482 6.60747,3.86843 1.19784,1.32359 2.9705,2.10238 3.93926,1.73063 2.80992,-1.07827 21.1655,5.79661 24.8635,9.31235 l 3.36852,3.20251 -2.48562,5.91368 c -1.36708,3.2525 -3.55587,8.19966 -4.86396,10.99366 -8.57116,18.30747 -13.26154,34.03811 -13.31676,44.66188 -0.034,6.55989 0.45637,8.36152 3.01991,11.09305 5.83121,6.21335 15.98949,5.63721 32.37321,-1.83607 4.26039,-1.94336 10.31792,-5.28708 13.46117,-7.4305 3.14325,-2.14342 6.20921,-3.90753 6.81326,-3.92024 3.03333,-0.0639 38.18769,-27.07231 38.70001,-29.73253 0.46402,-2.40947 -4.34446,-8.39059 -6.74554,-8.39059 -6.03461,0 -42.02265,-34.01297 -51.73422,-48.895 -8.67801,-13.29815 -16.98433,-28.85151 -19.45956,-36.43749 -1.21002,-3.70837 -2.69894,-7.37621 -3.30871,-8.15075 -2.08445,-2.64762 -6.22983,-16.29015 -10.17327,-33.48037 -3.54867,-15.4693 -3.94961,-19.33278 -4.02363,-38.77139 -0.14743,-38.72659 7.28775,-69.28421 25.77215,-105.92011 29.55499,-58.57769 80.62328,-103.72286 139.49634,-123.31699 23.78341,-7.91559 37.14243,-10.25486 63.10235,-11.04971 23.91549,-0.73226 37.33739,0.38831 55.62711,4.64421 l 9.91132,2.30629 22.70262,-20.08059 c 92.40099,-81.72915 165.84183,-131.73959 210.76143,-143.52053 20.78032,-5.45001 35.76148,-2.25583 41.9883,8.95244 2.65212,4.7738 3.17216,7.38825 3.15773,15.875 -0.04,23.53453 -13.04097,55.93195 -43.16273,107.55816 -20.77525,35.60711 -65.25436,99.25787 -99.95099,143.03253 -4.0502,5.10989 -7.364,9.44955 -7.364,9.64368 0,0.19415 1.14989,3.69518 2.55531,7.7801 7.10996,20.66549 10.96498,54.53869 8.9112,78.30077 -3.15903,36.54981 -9.23775,58.716 -24.47377,89.24428 -12.80333,25.65395 -29.11172,47.83815 -49.73337,67.65198 -39.39988,37.85645 -90.05749,61.48763 -142.52097,66.48439 -17.61204,1.67742 -34.74836,0.85233 -55.11141,-2.65352 -8.02008,-1.3808 -14.71004,-2.51432 -14.8666,-2.51896 -0.50429,-0.0149 -24.85023,22.25381 -33.37036,30.52319 -10.52421,10.21449 -43.38134,39.84482 -50.64176,45.66839 -3.04793,2.44475 -11.61705,9.60502 -19.04246,15.9117 -19.14333,16.25914 -47.10731,37.32995 -70.24449,52.92908 -42.08894,28.37648 -77.98417,42.07203 -94.38627,36.01238 z m 64.65416,-49.19004 c 12.73813,-3.43711 36.39178,-15.37167 54.72583,-27.61216 13.09684,-8.74395 17.61672,-12.02948 32.70453,-23.77319 9.50928,-7.40164 5.96982,-6.53869 -4.3541,1.06157 -35.84656,26.38946 -69.21793,43.69333 -86.96283,45.09231 -30.53146,2.40707 -34.2821,-23.6487 -11.06228,-76.84977 10.7522,-24.63534 10.94184,-25.10444 9.60016,-23.74688 -2.37353,2.40162 -13.840892,25.41647 -18.983683,38.1 -7.578767,18.69132 -10.726467,30.42244 -11.438157,42.62874 -0.84593,14.50852 2.00415,21.64521 10.114064,25.32593 7.07478,3.21092 13.114686,3.15758 25.656466,-0.22655 z m 59.80583,-94.27173 c -2.0955,-0.92354 -5.38163,-3.2651 -7.3025,-5.20347 -3.54245,-3.57468 -4.60618,-2.55186 -2.13948,2.05721 1.54603,2.8888 5.45745,4.74987 10.07698,4.79466 3.05611,0.0296 3.03234,-0.0321 -0.635,-1.6484 z M 457.83496,12.13477 c 2.0955,-1.42558 7.54436,-6.06549 12.10858,-10.31087 18.52157,-17.22779 22.85208,-20.41808 27.73509,-20.43246 3.82242,-0.0114 4.56827,-0.44352 4.38271,-2.54 -0.22059,-2.49226 -0.19911,-2.4931 1.5875,-0.0612 2.19971,2.99419 2.30895,4.51977 0.32362,4.51977 -2.38757,0 -6.1325,3.99795 -6.1325,6.54684 0,1.28873 -0.61912,2.34316 -1.37583,2.34316 -0.75672,0 -1.11968,0.71438 -0.80661,1.5875 0.31306,0.87313 0.0734,2.55295 -0.5326,3.73293 -3.74069,7.2837 7.78096,0.13166 19.67172,-12.21119 11.0646,-11.48529 26.99325,-32.80044 31.86396,-42.63924 l 2.2005,-4.445 -3.98494,4.1275 c -2.19172,2.27013 -4.56561,4.1275 -5.27531,4.1275 -2.22292,0 -10.01089,6.56369 -10.01089,8.43717 0,0.98213 -1.42875,2.32892 -3.175,2.99283 -1.74625,0.66392 -3.175,1.79279 -3.175,2.50857 0,0.71579 -0.85725,1.30143 -1.905,1.30143 -1.04775,0 -1.905,0.81062 -1.905,1.80137 0,0.99075 -2.14313,4.06821 -4.7625,6.83877 -2.61938,2.77059 -5.37261,6.0658 -6.11829,7.32271 -1.29815,2.18816 -1.49875,2.18088 -4.71943,-0.17127 -4.08362,-2.98241 -3.72346,-5.08113 0.81642,-4.75749 3.28867,0.23446 4.89864,-2.68747 2.01112,-3.64998 -2.59414,-0.86471 0.28711,-5.08428 2.98921,-4.37766 3.51937,0.92034 7.98199,-2.9331 7.47921,-6.45828 -0.26792,-1.87858 1.10647,-4.32504 4.12626,-7.34483 2.4904,-2.49041 4.528,-4.78361 4.528,-5.09601 0,-0.3124 2.00025,-3.6949 4.445,-7.51666 2.44475,-3.82176 4.445,-7.34773 4.445,-7.83548 0,-0.48777 2.43141,-3.22984 5.40315,-6.09352 2.97174,-2.86367 6.19491,-7.49267 7.16261,-10.28667 0.96769,-2.794 3.39402,-6.95726 5.39185,-9.25169 1.99781,-2.29442 3.63239,-4.76413 3.63239,-5.4882 0,-0.72409 0.85725,-2.17379 1.905,-3.22154 1.04775,-1.04775 1.905,-3.17862 1.905,-4.73528 0,-1.55665 0.67627,-3.50656 1.50282,-4.33311 0.82657,-0.82657 1.11473,-1.89095 0.64038,-2.3653 -1.42049,-1.4205 -7.42877,1.9669 -8.11012,4.57237 -0.36878,1.41024 -1.6414,2.34511 -3.0481,2.23915 -2.71084,-0.20421 -3.20998,-3.4184 -0.89098,-5.73739 0.8382,-0.8382 1.03538,-1.524 0.43816,-1.524 -0.59721,0 -0.22687,-1.00013 0.82296,-2.2225 3.1574,-3.67631 3.5349,-6.12924 0.973,-6.32222 -3.09902,-0.23345 -8.83577,5.67549 -9.69862,9.9897 -0.38199,1.90995 -1.56944,3.80837 -2.63878,4.21872 -2.75687,1.05791 -3.29261,0.0396 -1.12145,-2.13154 1.96234,-1.96234 0.97142,-5.2859 -1.12779,-3.78262 -2.11304,1.51317 -4.53565,6.6421 -3.74308,7.92449 0.41495,0.6714 -0.56582,1.7381 -2.17949,2.37046 -3.52012,1.37945 -21.54482,18.88426 -32.14391,31.21678 -4.191,4.87641 -11.07171,12.58939 -15.29046,17.13997 -4.21874,4.55056 -9.71661,10.8455 -12.2175,13.98875 -2.50087,3.14325 -7.22106,8.5725 -10.48931,12.065 -9.74029,10.40863 -12.6727,18.27287 -4.58353,12.2923 4.98295,-3.68407 5.86989,-3.49159 2.66798,0.57898 -1.4477,1.84044 -2.63218,4.16017 -2.63218,5.15495 0,0.99477 -0.45591,1.52691 -1.01314,1.18252 -1.53464,-0.94846 -3.30757,0.71136 -5.09275,4.76788 -2.2445,5.1002 -0.91274,5.6747 5.11406,2.20611 4.30487,-2.47756 5.04701,-2.6185 4.3599,-0.8279 -0.45544,1.18684 -0.83125,3.11129 -0.83517,4.27654 -0.004,1.16523 -2.57564,4.8311 -5.715,8.14636 -8.92195,9.42189 -6.65604,12.77705 3.8171,5.65203 z m 7.26442,-37.69024 c 0.86401,-1.38351 2.42704,-2.84399 3.47338,-3.24551 1.04634,-0.40153 0.53039,0.73044 -1.14655,2.51547 -3.65564,3.89125 -4.45831,4.14309 -2.32683,0.73004 z M 541.65496,-99.24 c 0.4317,-0.6985 1.03693,-1.27 1.34495,-1.27 0.30803,0 0.56005,0.5715 0.56005,1.27 0,0.6985 -0.60523,1.27 -1.34496,1.27 -0.73972,0 -0.99174,-0.5715 -0.56004,-1.27 z M 342.39192,5.02839 c 7.56626,-5.62401 49.09271,-45.57019 68.12588,-65.53339 41.8035,-43.84614 83.53458,-94.4667 106.21492,-128.84066 8.98663,-13.61998 9.62656,-15.05049 10.10684,-22.59295 0.37471,-5.8849 0.005,-8.63695 -1.37287,-10.22435 -1.03707,-1.19462 -2.24504,-3.60078 -2.68439,-5.34703 -0.45451,-1.80649 -2.90152,-4.45995 -5.67716,-6.15611 -3.95161,-2.41479 -5.17576,-4.14152 -6.4439,-9.08952 -0.86103,-3.35962 -2.67531,-7.11274 -4.03171,-8.34027 -2.81845,-2.55065 -1.31001,-3.55842 -19.1312,12.78122 -15.28389,14.01328 -23.57805,20.96468 -25.01428,20.96468 -0.72909,0 -3.92642,3.37991 -7.10516,7.51093 -3.17875,4.13102 -6.21367,7.70289 -6.74429,7.9375 -0.53062,0.23461 -6.39286,6.71307 -13.0272,14.39657 -6.63434,7.6835 -16.92018,19.43592 -22.85743,26.11649 -5.93725,6.68057 -14.11698,16.11032 -18.17717,20.955 -20.58465,24.56186 -31.42452,37.25715 -38.33783,44.8999 -4.191,4.63321 -10.19175,11.77446 -13.335,15.86945 -3.14325,4.09499 -7.18789,9.22377 -8.9881,11.3973 -1.8002,2.17351 -5.11379,6.66648 -7.36351,9.98435 -2.24972,3.31788 -4.63445,6.0325 -5.2994,6.0325 -0.66494,0 -1.20899,1.15577 -1.20899,2.56838 0,1.4126 -2.15365,4.98448 -4.78589,7.9375 -12.50644,14.03052 -24.8208,32.70261 -23.62417,35.82098 0.81765,2.13078 2.17498,2.38493 10.68102,1.99997 5.16448,-0.23373 5.95935,0.11354 7.62,3.32861 3.5936,6.95746 5.87826,8.63399 9.51051,6.97903 4.67343,-2.12936 11.95227,-1.89096 12.83926,0.42053 1.11598,2.90818 4.64208,2.28862 10.11122,-1.77661 z M 320.14565,-95.05943 c 3.48313,-2.05754 3.75853,-2.05191 5.33885,0.10934 1.56893,2.14564 2.10781,2.01763 9.22338,-2.19118 4.87518,-2.88364 8.90921,-6.5181 11.36709,-10.24118 2.0955,-3.17417 5.62693,-6.64875 7.84761,-7.72131 2.22069,-1.07254 9.65018,-8.05845 16.51,-15.52423 6.85982,-7.46579 17.75876,-19.21375 24.21989,-26.10656 6.46112,-6.89282 11.7475,-13.00665 11.7475,-13.58629 0,-1.41962 8.37574,-13.8602 11.99691,-17.81914 2.66991,-2.91897 13.40309,-20.56534 13.40309,-22.03597 0,-0.35079 -0.98852,-0.82817 -2.1967,-1.06085 -1.75158,-0.33732 -2.1172,-1.52411 -1.80436,-5.85693 0.21578,-2.98864 1.20156,-6.98356 2.19059,-8.87758 1.72259,-3.29875 1.66019,-3.46106 -1.48294,-3.85638 -1.80467,-0.22698 -4.27581,0.4874 -5.4914,1.5875 -1.21561,1.10011 -2.87957,2.0002 -3.69769,2.0002 -0.81812,0 -1.4875,0.82352 -1.4875,1.83004 0,1.00653 -0.56496,2.17922 -1.25546,2.60596 -0.73392,0.4536 -0.95465,-0.17472 -0.53132,-1.51254 0.39826,-1.25866 -0.31724,-0.70844 -1.59003,1.22269 -1.27278,1.93114 -2.91499,3.28059 -3.64937,2.99878 -0.73437,-0.2818 -2.33798,0.22085 -3.56355,1.11701 -2.66135,1.94602 -7.19027,1.04843 -7.19027,-1.42506 0,-0.96628 -0.5715,-1.75688 -1.27,-1.75688 -0.6985,0 -1.27,-1.10927 -1.27,-2.46505 0,-1.35577 -0.47233,-2.75696 -1.04961,-3.11375 -1.70854,-1.05594 -6.33937,0.60493 -7.08191,2.53998 -1.14239,2.97701 1.53565,8.08821 4.91007,9.37116 2.77309,1.05432 3.06984,1.73339 2.34872,5.37472 -1.21334,6.12684 -6.30405,9.30759 -7.78515,4.86428 -0.3089,-0.9267 -0.0589,-1.37529 0.55562,-0.99686 0.61449,0.37842 1.23712,-0.97078 1.38363,-2.99822 0.22536,-3.11868 -0.20309,-3.68626 -2.78255,-3.68626 -1.6769,0 -3.40212,-0.5715 -3.83382,-1.27 -1.46826,-2.3757 -6.39385,-1.28024 -10.88351,2.4205 l -4.47723,3.69052 -1.49351,-3.37302 c -1.19019,-2.68799 -2.36272,-3.373 -5.7735,-3.373 -6.28389,0 -8.86916,2.646 -7.85696,8.04152 0.91267,4.86495 -0.48741,6.51143 -5.56466,6.54403 -3.55489,0.0229 -9.03563,5.4396 -9.03563,8.9302 0,1.33643 -1.10684,3.02224 -2.45966,3.74624 -1.48367,0.79405 -2.61752,2.95349 -2.8575,5.44219 -0.33298,3.45326 -0.97524,4.1921 -3.93992,4.53246 -2.76326,0.31723 -3.70569,1.22476 -4.28625,4.1275 -0.40929,2.04647 -1.24423,3.72086 -1.85542,3.72086 -0.61118,0 -1.11125,0.58285 -1.11125,1.29523 0,0.71238 -1.42875,3.55853 -3.175,6.32477 -1.74625,2.76623 -3.175,6.15015 -3.175,7.5198 0,1.36966 -0.57149,2.8435 -1.26999,3.2752 -0.6985,0.43169 -1.27,2.44556 -1.27,4.47526 0,2.39058 -0.89462,4.16916 -2.54,5.04973 -2.66388,1.42567 -3.61423,7.62 -1.16909,7.62 0.86252,0 1.06058,1.41289 0.53409,3.81 -1.02961,4.68778 0.8121,4.96503 4.2181,0.635 3.30918,-4.20695 7.54582,-4.18175 8.60822,0.0512 0.95367,3.79972 -2.35074,8.20383 -6.15535,8.20383 -1.35318,0 -3.80175,1.05515 -5.44125,2.34477 -2.50717,1.97215 -2.71065,2.62809 -1.28037,4.1275 2.38856,2.50402 3.70153,9.59371 1.97437,10.66116 -0.76999,0.47588 -2.23221,0.17456 -3.24935,-0.6696 -1.30247,-1.08097 -1.84937,-1.1133 -1.84937,-0.10934 0,0.78402 0.5715,1.42549 1.27,1.42549 0.6985,0 1.27,1.905 1.27,4.23333 0,2.32835 0.381,4.61435 0.84667,5.08 1.492,1.49201 5.64013,0.90946 9.419,-1.32276 z m -40.54108,-53.83591 c 3.36307,-1.77095 17.82278,-14.49743 31.60276,-27.81465 3.97524,-3.84175 10.45226,-10.00616 14.39339,-13.69867 3.94113,-3.69252 8.51784,-8.83602 10.17048,-11.43 1.65263,-2.59399 5.332,-7.34235 8.17638,-10.55192 9.64475,-10.88306 28.65274,-42.06373 30.04292,-49.28236 0.47824,-2.4833 0.34333,-2.51846 -2.50429,-0.65263 -1.65536,1.08463 -3.93182,3.37932 -5.05879,5.09929 -1.12697,1.71998 -2.6208,2.77386 -3.31962,2.34197 -0.69881,-0.43189 -3.73376,1.48523 -6.74432,4.26027 -3.01056,2.77504 -6.67157,5.4257 -8.13557,5.89036 -1.464,0.46465 -3.9367,2.90762 -5.4949,5.42884 -3.10428,5.02284 -31.45267,38.83396 -35.97706,42.90985 -3.7365,3.36609 -12.60425,14.45958 -14.61298,18.28074 -0.87917,1.6724 -3.74972,5.38715 -6.37897,8.255 -6.7042,7.31254 -11.55534,14.80169 -10.5947,16.35604 0.47106,0.76218 0.1953,0.93383 -0.65547,0.40802 -1.61746,-0.99964 -4.50062,5.03394 -3.31905,6.94576 0.69221,1.12003 1.682,0.79684 8.40979,-2.74591 z m 106.42923,-72.84403 c 1.92079,-2.31441 0.67299,-4.50062 -2.56879,-4.50062 -1.78271,0 -2.46504,0.87884 -2.46504,3.175 0,3.54002 2.62282,4.23072 5.03383,1.32562 z m 184.74031,-22.29481 c 3.19321,-4.1988 5.80584,-8.15544 5.80584,-8.79255 0,-0.63711 1.33112,-2.77386 2.95805,-4.74832 3.81432,-4.62916 22.56604,-42.85758 23.87591,-48.67494 0.55047,-2.44475 2.09018,-8.50106 3.42156,-13.45846 2.5655,-9.55261 2.17003,-18.86204 -0.96153,-22.63535 -6.52888,-7.86681 -29.66163,-0.75616 -62.94898,19.34959 -26.28231,15.87465 -29.94693,18.34241 -29.45349,19.83401 0.24658,0.74537 5.54573,5.20859 11.7759,9.91828 14.21854,10.7485 28.86537,24.7686 35.99081,34.45078 4.95679,6.73541 5.41547,7.96506 4.7586,12.75742 -0.39982,2.91696 -1.10081,6.27785 -1.55776,7.46864 -1.52011,3.96135 0.46936,2.24384 6.33509,-5.4691 z m 80.53114,-60.08085 c 32.08827,-47.93853 53.50611,-95.55569 53.53579,-119.02336 0.0409,-32.32052 -36.06091,-30.44131 -93.18093,4.85035 -20.2122,12.48813 -57.6047,39.2498 -64.65713,46.27488 -1.24958,1.24474 -2.67833,2.26361 -3.175,2.26416 -0.49666,5.4e-4 -3.0098,1.85792 -5.58475,4.1275 -2.57495,2.26958 -5.7182,4.77385 -6.985,5.56506 -1.2668,0.7912 -1.89752,1.8637 -1.40161,2.38332 0.49591,0.51964 3.63916,-1.31463 6.985,-4.07613 21.53812,-17.77655 61.46891,-45.13963 82.91833,-56.82081 17.47303,-9.51567 34.28327,-16.48008 46.15225,-19.12068 8.88699,-1.97717 10.09775,-1.98192 16.68995,-0.0655 8.80406,2.55941 11.40916,5.09239 13.91472,13.52952 1.57431,5.3013 1.7094,8.23495 0.67687,14.69843 -3.72289,23.3043 -20.97503,60.96332 -47.20294,103.03742 -4.05397,6.50326 -7.37085,12.10912 -7.37085,12.45745 0,1.81859 3.22721,-1.92745 8.6853,-10.0816 z m -147.11529,-31.38964 c 0,-1.80421 -0.60792,-1.52762 -3.29566,1.49948 -2.40637,2.71019 -2.40402,2.71675 0.43816,1.22532 1.57163,-0.82473 2.8575,-2.05088 2.8575,-2.7248 z m 115.55055,-5.39361 c 0.0102,-1.94331 -0.81723,-5.53576 -1.83985,-7.98321 -2.20522,-5.27782 -9.25807,-8.89144 -14.51359,-7.43619 -3.30564,0.91532 -3.30342,0.91984 1.13288,2.3149 8.80715,2.76952 12.68947,7.44954 12.71944,15.33288 0.0144,3.77023 0.34311,4.6141 1.25056,3.20991 0.67711,-1.04775 1.23986,-3.49498 1.25056,-5.43829 z m -107.93055,-0.27671 c 0.88193,-1.06266 1.01945,-1.905 0.31101,-1.905 -0.6985,0 -1.98145,0.85725 -2.85101,1.905 -0.88193,1.06266 -1.01945,1.905 -0.31101,1.905 0.6985,0 1.98145,-0.85725 2.85101,-1.905 z m 7.366,-5.969 c 2.39724,-2.39724 1.78131,-3.78308 -1.016,-2.286 -1.397,0.74765 -2.54,1.91075 -2.54,2.58468 0,1.68197 1.71979,1.53752 3.556,-0.29868 z m 7.62,-6.35 c 2.42806,-2.42806 1.76484,-3.72793 -1.016,-1.99127 -2.56593,1.60246 -3.39522,3.51527 -1.524,3.51527 0.5588,0 1.7018,-0.6858 2.54,-1.524 z"
+         id="path8422"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#b4a488;stroke-width:1.26999998"
+         d="M 51.435,285.01316 C 45.15376,282.6926 39.953,277.08029 38.18547,270.71519 29.72376,240.24359 64.4959,163.39819 133.18002,60.78 c 16.87373,-25.21036 16.4878,-24.80954 22.10326,-22.95626 2.43627,0.80403 5.40963,2.54482 6.60747,3.86843 1.19784,1.32359 2.9705,2.10238 3.93926,1.73063 2.80992,-1.07827 21.1655,5.79661 24.8635,9.31235 l 3.36852,3.20251 -2.48562,5.91368 c -1.36708,3.2525 -3.55587,8.19966 -4.86396,10.99366 -8.57116,18.30747 -13.26154,34.03811 -13.31676,44.66188 -0.034,6.55989 0.45637,8.36152 3.01991,11.09305 5.83121,6.21335 15.98949,5.63721 32.37321,-1.83607 4.26039,-1.94336 10.31792,-5.28708 13.46117,-7.4305 3.14325,-2.14342 6.20921,-3.90753 6.81326,-3.92024 3.03333,-0.0639 38.18769,-27.07231 38.70001,-29.73253 0.46402,-2.40947 -4.34446,-8.39059 -6.74554,-8.39059 -6.03461,0 -42.02265,-34.01297 -51.73422,-48.895 -8.67801,-13.29815 -16.98433,-28.85151 -19.45956,-36.43749 -1.21002,-3.70837 -2.69894,-7.37621 -3.30871,-8.15075 -2.08445,-2.64762 -6.22983,-16.29015 -10.17327,-33.48037 -3.54867,-15.4693 -3.94961,-19.33278 -4.02363,-38.77139 -0.14743,-38.72659 7.28775,-69.28421 25.77215,-105.92011 29.55499,-58.57769 80.62328,-103.72286 139.49634,-123.31699 23.78341,-7.91559 37.14243,-10.25486 63.10235,-11.04971 23.91549,-0.73226 37.33739,0.38831 55.62711,4.64421 l 9.91132,2.30629 22.70262,-20.08059 c 92.40099,-81.72915 165.84183,-131.73959 210.76143,-143.52053 20.78032,-5.45001 35.76148,-2.25583 41.9883,8.95244 2.65212,4.7738 3.17216,7.38825 3.15773,15.875 -0.04,23.53453 -13.04097,55.93195 -43.16273,107.55816 -20.77525,35.60711 -65.25436,99.25787 -99.95099,143.03253 -4.0502,5.10989 -7.364,9.44955 -7.364,9.64368 0,0.19415 1.14989,3.69518 2.55531,7.7801 7.10996,20.66549 10.96498,54.53869 8.9112,78.30077 -3.15903,36.54981 -9.23775,58.716 -24.47377,89.24428 -12.80333,25.65395 -29.11172,47.83815 -49.73337,67.65198 -39.39988,37.85645 -90.05749,61.48763 -142.52097,66.48439 -17.61204,1.67742 -34.74836,0.85233 -55.11141,-2.65352 -8.02008,-1.3808 -14.71004,-2.51432 -14.8666,-2.51896 -0.50429,-0.0149 -24.85023,22.25381 -33.37036,30.52319 -10.52421,10.21449 -43.38134,39.84482 -50.64176,45.66839 -3.04793,2.44475 -11.61705,9.60502 -19.04246,15.9117 -19.14333,16.25914 -47.10731,37.32995 -70.24449,52.92908 -42.08894,28.37648 -77.98417,42.07203 -94.38627,36.01238 z m 64.65416,-49.19004 c 12.73813,-3.43711 36.39178,-15.37167 54.72583,-27.61216 13.09684,-8.74395 17.61672,-12.02948 32.70453,-23.77319 9.50928,-7.40164 5.96982,-6.53869 -4.3541,1.06157 -35.84656,26.38946 -69.21793,43.69333 -86.96283,45.09231 -30.53146,2.40707 -34.2821,-23.6487 -11.06228,-76.84977 10.7522,-24.63534 10.94184,-25.10444 9.60016,-23.74688 -2.37353,2.40162 -13.840892,25.41647 -18.983683,38.1 -7.578767,18.69132 -10.726467,30.42244 -11.438157,42.62874 -0.84593,14.50852 2.00415,21.64521 10.114064,25.32593 7.07478,3.21092 13.114686,3.15758 25.656466,-0.22655 z m 59.80583,-94.27173 c -2.0955,-0.92354 -5.38163,-3.2651 -7.3025,-5.20347 -3.54245,-3.57468 -4.60618,-2.55186 -2.13948,2.05721 1.54603,2.8888 5.45745,4.74987 10.07698,4.79466 3.05611,0.0296 3.03234,-0.0321 -0.635,-1.6484 z M 431.60392,31.79538 c 1.85407,-1.27305 6.80005,-4.20623 10.99104,-6.51818 4.191,-2.31194 13.40616,-9.54777 20.47812,-16.07959 11.13499,-10.28454 13.72611,-12.06221 19.33511,-13.26513 3.56235,-0.76398 8.49183,-2.75635 10.95439,-4.4275 3.40766,-2.3125 4.47738,-2.59243 4.47738,-1.1717 0,1.02669 -0.61912,1.86672 -1.37583,1.86672 -0.75672,0 -1.11968,0.71438 -0.80661,1.5875 0.31306,0.87313 0.0734,2.55295 -0.5326,3.73293 -3.74069,7.2837 7.78096,0.13166 19.67172,-12.21119 11.0646,-11.48529 26.99325,-32.80044 31.86396,-42.63924 l 2.2005,-4.445 -3.98494,4.1275 c -2.19172,2.27013 -4.55061,4.1275 -5.242,4.1275 -2.31172,0 -9.03896,5.68073 -10.15175,8.5725 -0.64048,1.66445 -2.17652,2.8575 -3.67898,2.8575 -2.39357,0 -2.30434,-0.37971 1.23857,-5.27055 2.09988,-2.8988 3.81796,-5.94243 3.81796,-6.76361 0,-0.82119 1.43517,-2.84135 3.18927,-4.48925 1.7541,-1.64789 3.80044,-5.31938 4.54744,-8.15887 0.74699,-2.83949 2.74081,-6.47172 4.43073,-8.07161 1.87528,-1.77539 3.07256,-4.3443 3.07256,-6.59255 0,-2.026 0.48172,-3.98137 1.07049,-4.34526 1.2403,-0.76654 12.89951,-20.57725 12.89951,-21.91815 0,-0.49401 1.13145,-3.23547 2.51434,-6.09215 1.38289,-2.85667 2.86671,-6.95575 3.29738,-9.10909 0.75593,-3.77968 0.60117,-3.94422 -4.47313,-4.75563 -4.46787,-0.71445 -5.91473,-0.33817 -9.64738,2.50885 -5.02531,3.833 -5.13193,3.68049 -2.32723,-3.32911 1.13518,-2.83712 1.84417,-5.3782 1.57553,-5.64684 -0.26865,-0.26864 -2.64099,0.18369 -5.27186,1.00519 -3.19405,0.99734 -5.29748,2.65408 -6.33055,4.98613 -0.85093,1.92088 -1.91956,3.49251 -2.37474,3.49251 -0.45517,0 -2.59473,2.71462 -4.75458,6.0325 -2.15986,3.31787 -6.21024,8.26644 -9.00086,10.9968 -2.79062,2.73037 -5.44632,6.15937 -5.90157,7.62 -0.45523,1.46064 -1.20166,2.94144 -1.65873,3.29069 -0.82804,0.63272 -18.4213,20.82196 -23.05804,26.46039 -1.34122,1.63096 -2.43858,3.29692 -2.43858,3.70214 0,0.40521 -3.28612,4.24523 -7.3025,8.53336 -4.01637,4.28814 -15.3035,16.88916 -25.0825,28.00228 -9.779,11.11311 -18.92299,21.41398 -20.31999,22.89081 -15.02714,15.88596 -19.685,21.1377 -19.685,22.19482 0,2.48669 2.51922,1.11505 10.39089,-5.65747 4.41325,-3.79702 8.50136,-6.43443 9.08468,-5.86092 1.13062,1.11161 -2.12533,8.43214 -4.07293,9.15735 -0.63842,0.23773 -2.60579,3.86124 -4.37191,8.05224 -2.67751,6.35375 -2.89522,7.6913 -1.31006,8.04909 1.62156,0.366 1.57794,0.78609 -0.29665,2.8575 -2.86812,3.16923 -0.20165,3.23899 4.34993,0.11379 z m 71.53666,-54.84215 c -3.86076,-3.21767 -2.43394,-5.47564 2.75778,-4.36422 5.10368,1.09257 5.11718,1.10934 3.42982,4.26222 -1.72889,3.23045 -2.42023,3.24184 -6.1876,0.10198 z M 520.69996,-39.55 c 0,-0.6985 0.5715,-1.27 1.27,-1.27 0.6985,0 1.27,0.5715 1.27,1.27 0,0.6985 -0.5715,1.27 -1.27,1.27 -0.6985,0 -1.27,-0.5715 -1.27,-1.27 z M 340.72853,8.34292 c 15.28692,-13.09419 76.26159,-74.98021 98.49083,-99.96292 39.76467,-44.69019 81.9905,-100.14932 92.30689,-121.23526 3.01995,-6.17253 3.10138,-6.82476 1.28668,-10.30468 -3.35623,-6.43599 -2.31267,-8.75858 2.12918,-4.73876 2.25088,2.03703 2.33027,2.02016 1.49446,-0.3175 -1.26664,-3.54265 -4.45854,-6.9698 -5.74872,-6.17242 -0.60645,0.3748 -1.65893,-0.78167 -2.33883,-2.56996 -0.67991,-1.78827 -1.85556,-3.25141 -2.61258,-3.25141 -0.75701,0 -0.9991,-0.61047 -0.53798,-1.35658 0.46114,-0.74612 1.63786,-1.04981 2.61496,-0.67487 0.9771,0.37496 1.77654,0.0822 1.77654,-0.65058 0,-0.73276 -2.24889,-2.21569 -4.99754,-3.29539 -4.22002,-1.65767 -5.4285,-3.0249 -7.7675,-8.78784 -2.70512,-6.66499 -3.84187,-7.51303 -9.38127,-6.99863 -2.25962,0.20984 -17.20549,14.9052 -17.21816,16.92958 -0.003,0.48439 -1.72003,1.89348 -3.81553,3.13133 -2.0955,1.23784 -3.81,3.03693 -3.81,3.99798 0,0.96105 -2.66837,4.12396 -5.9297,7.02868 -11.44004,10.1891 -22.7815,22.05828 -37.86372,39.62547 -8.37012,9.74922 -20.08587,23.19177 -26.035,29.87234 -5.94911,6.68057 -14.13855,16.11032 -18.19874,20.955 -20.58465,24.56186 -31.42452,37.25715 -38.33783,44.8999 -4.191,4.63321 -10.19175,11.77446 -13.335,15.86945 -3.14325,4.09499 -7.18789,9.22377 -8.9881,11.3973 -1.8002,2.17351 -5.11379,6.66648 -7.36351,9.98435 -2.24972,3.31788 -4.63445,6.0325 -5.2994,6.0325 -0.66494,0 -1.20899,1.15577 -1.20899,2.56838 0,1.4126 -2.16353,4.98448 -4.80786,7.9375 -8.67796,9.69101 -21.7322,27.07163 -25.5158,33.97213 -1.34891,2.46014 -2.74304,6.31608 -3.09807,8.56874 -0.6136,3.89334 -0.47841,4.05588 2.73562,3.28937 1.85962,-0.44349 4.08923,-1.36382 4.9547,-2.04518 2.1399,-1.68469 10.49142,0.0152 10.49142,2.13554 0,0.93578 1.44696,2.64948 3.21547,3.80825 1.7685,1.15878 2.9178,2.5885 2.55398,3.17717 -1.34545,2.17696 2.92867,4.0492 7.35027,3.21969 3.44419,-0.64613 4.94012,-0.28587 6.76376,1.62891 2.94419,3.0913 4.1664,2.50723 16.04507,-7.66758 z m -42.6935,-82.63793 c 1.97447,-1.64725 5.87595,-3.73996 8.66995,-4.65045 8.7519,-2.852 14.66308,-5.96927 19.27152,-10.16286 2.44809,-2.2277 5.9427,-4.82424 7.76578,-5.77008 8.17681,-4.24218 69.33557,-68.25947 72.71781,-76.11659 0.60136,-1.397 2.89009,-4.2545 5.08607,-6.35 2.19598,-2.0955 5.01887,-5.81025 6.2731,-8.255 1.25424,-2.44475 3.57106,-5.588 5.1485,-6.985 1.57745,-1.397 5.31506,-6.29614 8.30579,-10.88699 2.99074,-4.59085 6.33292,-9.08995 7.42706,-9.99801 1.09414,-0.90805 1.98936,-2.45839 1.98936,-3.44519 0,-2.0808 3.73694,-8.27658 5.77823,-9.58019 0.76681,-0.48971 2.13379,-3.34721 3.03774,-6.35 0.90393,-3.0028 1.9554,-6.31687 2.33658,-7.36462 0.38119,-1.04775 0.74243,-2.37993 0.80275,-2.9604 0.0603,-0.58047 1.53844,-1.8461 3.28469,-2.81249 l 3.175,-1.75711 -3.8735,-0.045 c -2.13042,-0.0248 -4.62925,0.66937 -5.55296,1.54249 -1.38435,1.30856 -1.42897,1.10008 -0.254,-1.18651 1.67032,-3.25051 1.88867,-8.02099 0.36714,-8.02099 -2.05368,0 -2.71275,1.69166 -1.31515,3.37565 0.86305,1.03991 0.91938,1.70435 0.14448,1.70435 -0.6985,0 -1.98145,-0.85725 -2.85101,-1.905 -1.74224,-2.09927 -5.80052,-2.55152 -6.90209,-0.76915 -0.88252,1.42793 -10.8779,2.45726 -10.8779,1.12018 0,-0.54234 -0.83136,-0.98607 -1.84746,-0.98607 -1.10857,0 -2.18354,1.79152 -2.68779,4.47942 -1.40468,7.48758 -8.16475,8.43173 -8.16475,1.14033 0,-1.51923 -0.36557,-4.59011 -0.81238,-6.82415 -0.70578,-3.52891 -1.21425,-3.96105 -3.87498,-3.29325 -2.45246,0.61553 -2.98639,0.31783 -2.68012,-1.49435 0.62676,-3.70833 -4.83076,-3.54599 -9.37642,0.27892 -2.24804,1.8916 -4.97647,3.10312 -6.24272,2.77199 -1.23713,-0.32352 -2.50547,0.0793 -2.81852,0.89504 -0.31305,0.81578 -2.3502,1.61895 -4.52702,1.78483 -3.17025,0.24156 -3.99575,-0.20789 -4.14834,-2.25861 -0.10478,-1.40809 -0.24765,-3.27454 -0.3175,-4.14767 -0.18383,-2.29786 -2.80965,-4.7625 -5.07395,-4.7625 -1.07436,0 -4.09968,2.14313 -6.72294,4.7625 -2.62325,2.61938 -5.3026,4.59011 -5.95411,4.37941 -0.6515,-0.21069 -3.23913,1.5837 -5.75027,3.98757 -2.51116,2.40386 -6.41343,5.30055 -8.67173,6.43708 -2.2583,1.13654 -4.97133,3.7398 -6.02895,5.78501 -2.00006,3.86769 -30.59683,38.14563 -35.53407,42.59343 -3.7365,3.3661 -12.60425,14.45959 -14.61298,18.28075 -0.87917,1.6724 -3.7361,5.38715 -6.34873,8.255 -9.50548,10.43399 -17.77081,24.44339 -16.05713,27.21618 0.82738,1.33872 8.39287,2.7542 8.39287,1.57026 0,-0.41409 2.71462,-2.58647 6.0325,-4.82751 3.31787,-2.24103 7.48766,-5.19507 9.2662,-6.56454 4.23599,-3.26171 7.80916,-1.6169 5.29577,2.43777 -1.83443,2.95935 -1.08099,5.06709 1.81131,5.06709 3.22049,0 1.202,2.90877 -3.02381,4.35751 -5.15611,1.76766 -6.55652,3.26249 -3.05642,3.26249 3.91337,0 6.17624,5.24364 4.16644,9.65468 -1.24424,2.73081 -2.07939,3.1607 -4.80922,2.47557 -3.02642,-0.75959 -3.24997,-0.54901 -2.66512,2.5104 0.40952,2.14233 0.0114,3.58002 -1.10964,4.01033 -1.00379,0.38519 -1.74801,2.48637 -1.74801,4.93522 0,2.96465 -0.77418,4.67877 -2.54,5.62381 -3.25984,1.74461 -3.29232,5.07999 -0.0495,5.07999 2.51316,0 6.32448,-3.31589 9.85332,-8.57249 1.05505,-1.57163 2.47429,-2.8575 3.15385,-2.8575 1.51467,0 -0.0625,10.60773 -1.66741,11.21495 -0.6105,0.23097 -0.52294,1.51685 0.19454,2.8575 0.7175,1.34065 0.88438,2.43754 0.37085,2.43754 -0.51352,0 -0.31398,0.6197 0.44342,1.3771 1.9785,1.9785 0.69908,5.50677 -2.51222,6.9279 -1.51623,0.67101 -3.33842,2.37487 -4.04933,3.78634 -0.7109,1.4115 -2.56957,4.34479 -4.13036,6.51842 l -2.83781,3.95206 4.10781,2.02517 c 5.28057,2.60336 5.18288,2.61283 9.40787,-0.912 z m 5.7145,-81.66216 c 0.53525,-2.77928 4.22545,-3.17247 4.22545,-0.45022 0,1.03818 -1.05203,2.09021 -2.33783,2.33784 -1.69723,0.32684 -2.21446,-0.19039 -1.88762,-1.88762 z m 69.63044,-89.89581 c 0,-1.85205 4.64377,-5.78701 6.82941,-5.78701 0.65135,0 0.23843,1.04512 -0.91757,2.3225 -1.15602,1.27737 -2.10184,2.99187 -2.10184,3.81 0,0.81812 -0.85725,1.4875 -1.905,1.4875 -1.04775,0 -1.905,-0.82484 -1.905,-1.83299 z m 9.20862,-1.97701 c 0.61567,-2.29074 8.57138,-3.41642 8.57138,-1.21279 0,0.97638 -1.15964,1.19709 -3.37203,0.64182 -2.17591,-0.54613 -3.78693,-0.2571 -4.54168,0.81482 -0.91153,1.29454 -1.05667,1.24073 -0.65767,-0.24385 z m 18.73138,-9.45005 c 0,-1.08897 0.5715,-1.97995 1.27,-1.97995 0.6985,0 1.27,0.53777 1.27,1.19504 0,0.65728 -0.5715,1.54826 -1.27,1.97996 -0.6985,0.43169 -1.27,-0.10605 -1.27,-1.19505 z M 283.20998,-93.66087 c 0,-0.97301 -0.85725,-1.76913 -1.905,-1.76913 -1.04821,0 -1.905,1.12446 -1.905,2.50015 0,1.70057 0.60923,2.26636 1.905,1.76912 1.04775,-0.40206 1.905,-1.52712 1.905,-2.50014 z m -5.08,-4.30913 c 0,-0.6985 -0.5715,-1.27 -1.27,-1.27 -0.6985,0 -1.27,0.5715 -1.27,1.27 0,0.6985 0.5715,1.27 1.27,1.27 0.6985,0 1.27,-0.5715 1.27,-1.27 z m 292.64413,-146.06418 c 3.19321,-4.1988 5.80584,-8.15544 5.80584,-8.79255 0,-0.63711 1.33112,-2.77386 2.95805,-4.74832 3.81432,-4.62916 22.56604,-42.85758 23.87591,-48.67494 0.55047,-2.44475 2.09018,-8.50106 3.42156,-13.45846 2.5655,-9.55261 2.17003,-18.86204 -0.96153,-22.63535 -6.52888,-7.86681 -29.66163,-0.75616 -62.94898,19.34959 -26.28231,15.87465 -29.94693,18.34241 -29.45349,19.83401 0.24658,0.74537 5.54573,5.20859 11.7759,9.91828 14.21854,10.7485 28.86537,24.7686 35.99081,34.45078 4.95679,6.73541 5.41547,7.96506 4.7586,12.75742 -0.39982,2.91696 -1.10081,6.27785 -1.55776,7.46864 -1.52011,3.96135 0.46936,2.24384 6.33509,-5.4691 z m 80.53114,-60.08085 c 32.08827,-47.93853 53.50611,-95.55569 53.53579,-119.02336 0.0409,-32.32052 -36.06091,-30.44131 -93.18093,4.85035 -20.2122,12.48813 -57.6047,39.2498 -64.65713,46.27488 -1.24958,1.24474 -2.67833,2.26361 -3.175,2.26416 -0.49666,5.4e-4 -3.0098,1.85792 -5.58475,4.1275 -2.57495,2.26958 -5.7182,4.77385 -6.985,5.56506 -1.2668,0.7912 -1.89752,1.8637 -1.40161,2.38332 0.49591,0.51964 3.63916,-1.31463 6.985,-4.07613 21.53812,-17.77655 61.46891,-45.13963 82.91833,-56.82081 17.47303,-9.51567 34.28327,-16.48008 46.15225,-19.12068 8.88699,-1.97717 10.09775,-1.98192 16.68995,-0.0655 8.80406,2.55941 11.40916,5.09239 13.91472,13.52952 1.57431,5.3013 1.7094,8.23495 0.67687,14.69843 -3.72289,23.3043 -20.97503,60.96332 -47.20294,103.03742 -4.05397,6.50326 -7.37085,12.10912 -7.37085,12.45745 0,1.81859 3.22721,-1.92745 8.6853,-10.0816 z m -147.11529,-31.38964 c 0,-1.80421 -0.60792,-1.52762 -3.29566,1.49948 -2.40637,2.71019 -2.40402,2.71675 0.43816,1.22532 1.57163,-0.82473 2.8575,-2.05088 2.8575,-2.7248 z m 115.55055,-5.39361 c 0.0102,-1.94331 -0.81723,-5.53576 -1.83985,-7.98321 -2.20522,-5.27782 -9.25807,-8.89144 -14.51359,-7.43619 -3.30564,0.91532 -3.30342,0.91984 1.13288,2.3149 8.80715,2.76952 12.68947,7.44954 12.71944,15.33288 0.0144,3.77023 0.34311,4.6141 1.25056,3.20991 0.67711,-1.04775 1.23986,-3.49498 1.25056,-5.43829 z m -107.93055,-0.27671 c 0.88193,-1.06266 1.01945,-1.905 0.31101,-1.905 -0.6985,0 -1.98145,0.85725 -2.85101,1.905 -0.88193,1.06266 -1.01945,1.905 -0.31101,1.905 0.6985,0 1.98145,-0.85725 2.85101,-1.905 z m 7.366,-5.969 c 2.39724,-2.39724 1.78131,-3.78308 -1.016,-2.286 -1.397,0.74765 -2.54,1.91075 -2.54,2.58468 0,1.68197 1.71979,1.53752 3.556,-0.29868 z m 7.62,-6.35 c 2.42806,-2.42806 1.76484,-3.72793 -1.016,-1.99127 -2.56593,1.60246 -3.39522,3.51527 -1.524,3.51527 0.5588,0 1.7018,-0.6858 2.54,-1.524 z"
+         id="path8420"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#bba279;stroke-width:1.26999998"
+         d="M 51.435,285.01316 C 45.15376,282.6926 39.953,277.08029 38.18547,270.71519 29.72376,240.24359 64.4959,163.39819 133.18002,60.78 c 16.87373,-25.21036 16.4878,-24.80954 22.10326,-22.95626 2.43627,0.80403 5.40963,2.54482 6.60747,3.86843 1.19784,1.32359 2.9705,2.10238 3.93926,1.73063 2.80992,-1.07827 21.1655,5.79661 24.8635,9.31235 l 3.36852,3.20251 -2.48562,5.91368 c -1.36708,3.2525 -3.55587,8.19966 -4.86396,10.99366 -8.57116,18.30747 -13.26154,34.03811 -13.31676,44.66188 -0.034,6.55989 0.45637,8.36152 3.01991,11.09305 5.83121,6.21335 15.98949,5.63721 32.37321,-1.83607 4.26039,-1.94336 10.31792,-5.28708 13.46117,-7.4305 3.14325,-2.14342 6.20921,-3.90753 6.81326,-3.92024 3.03333,-0.0639 38.18769,-27.07231 38.70001,-29.73253 0.46402,-2.40947 -4.34446,-8.39059 -6.74554,-8.39059 -6.03461,0 -42.02265,-34.01297 -51.73422,-48.895 -8.67801,-13.29815 -16.98433,-28.85151 -19.45956,-36.43749 -1.21002,-3.70837 -2.69894,-7.37621 -3.30871,-8.15075 -2.08445,-2.64762 -6.22983,-16.29015 -10.17327,-33.48037 -3.54867,-15.4693 -3.94961,-19.33278 -4.02363,-38.77139 -0.14743,-38.72659 7.28775,-69.28421 25.77215,-105.92011 29.55499,-58.57769 80.62328,-103.72286 139.49634,-123.31699 23.78341,-7.91559 37.14243,-10.25486 63.10235,-11.04971 23.91549,-0.73226 37.33739,0.38831 55.62711,4.64421 l 9.91132,2.30629 22.70262,-20.08059 c 92.40099,-81.72915 165.84183,-131.73959 210.76143,-143.52053 20.78032,-5.45001 35.76148,-2.25583 41.9883,8.95244 2.65212,4.7738 3.17216,7.38825 3.15773,15.875 -0.04,23.53453 -13.04097,55.93195 -43.16273,107.55816 -20.77525,35.60711 -65.25436,99.25787 -99.95099,143.03253 -4.0502,5.10989 -7.364,9.44955 -7.364,9.64368 0,0.19415 1.14989,3.69518 2.55531,7.7801 7.10996,20.66549 10.96498,54.53869 8.9112,78.30077 -3.15903,36.54981 -9.23775,58.716 -24.47377,89.24428 -12.80333,25.65395 -29.11172,47.83815 -49.73337,67.65198 -39.39988,37.85645 -90.05749,61.48763 -142.52097,66.48439 -17.61204,1.67742 -34.74836,0.85233 -55.11141,-2.65352 -8.02008,-1.3808 -14.71004,-2.51432 -14.8666,-2.51896 -0.50429,-0.0149 -24.85023,22.25381 -33.37036,30.52319 -10.52421,10.21449 -43.38134,39.84482 -50.64176,45.66839 -3.04793,2.44475 -11.61705,9.60502 -19.04246,15.9117 -19.14333,16.25914 -47.10731,37.32995 -70.24449,52.92908 -42.08894,28.37648 -77.98417,42.07203 -94.38627,36.01238 z m 64.65416,-49.19004 c 12.73813,-3.43711 36.39178,-15.37167 54.72583,-27.61216 13.09684,-8.74395 17.61672,-12.02948 32.70453,-23.77319 9.50928,-7.40164 5.96982,-6.53869 -4.3541,1.06157 -35.84656,26.38946 -69.21793,43.69333 -86.96283,45.09231 -30.53146,2.40707 -34.2821,-23.6487 -11.06228,-76.84977 10.7522,-24.63534 10.94184,-25.10444 9.60016,-23.74688 -2.37353,2.40162 -13.840892,25.41647 -18.983683,38.1 -7.578767,18.69132 -10.726467,30.42244 -11.438157,42.62874 -0.84593,14.50852 2.00415,21.64521 10.114064,25.32593 7.07478,3.21092 13.114686,3.15758 25.656466,-0.22655 z m 59.80583,-94.27173 c -2.0955,-0.92354 -5.38163,-3.2651 -7.3025,-5.20347 -3.54245,-3.57468 -4.60618,-2.55186 -2.13948,2.05721 1.54603,2.8888 5.45745,4.74987 10.07698,4.79466 3.05611,0.0296 3.03234,-0.0321 -0.635,-1.6484 z M 431.60392,31.79538 c 1.85407,-1.27305 6.80005,-4.20623 10.99104,-6.51818 4.191,-2.31194 13.40616,-9.54777 20.47812,-16.07959 11.13499,-10.28454 13.72611,-12.06221 19.33511,-13.26513 3.56235,-0.76398 8.49183,-2.75635 10.95439,-4.4275 3.40766,-2.3125 4.47738,-2.59243 4.47738,-1.1717 0,1.02669 -0.61912,1.86672 -1.37583,1.86672 -0.75672,0 -1.11968,0.71438 -0.80661,1.5875 0.31306,0.87313 0.0734,2.55295 -0.5326,3.73293 -1.92349,3.74535 0.93489,3.66306 6.2396,-0.17964 2.81151,-2.03666 5.48839,-4.31226 5.9486,-5.0569 0.46021,-0.74464 1.38694,-1.35389 2.0594,-1.35389 0.67246,0 -1.92059,3.14325 -5.76234,6.985 -3.84175,3.84175 -7.8955,6.985 -9.00833,6.985 -2.1554,0 -3.06636,0.78979 -8.46943,7.34289 -1.89004,2.29235 -3.88667,4.2529 -4.43695,4.35679 -0.55028,0.10388 -1.02367,2.05658 -1.05199,4.33933 -0.0541,4.36232 0.22767,4.33875 5.76648,-0.48236 1.04775,-0.91198 5.61975,-4.4343 10.16,-7.82738 10.33471,-7.72344 23.55991,-20.42687 29.68625,-28.51504 2.57487,-3.39942 6.32719,-7.97142 8.33849,-10.16 13.48242,-14.67082 16.81228,-18.59649 17.03397,-20.0818 0.1377,-0.92259 2.09102,-4.34881 4.34072,-7.61384 4.90012,-7.11162 7.16421,-12.47859 5.26418,-12.47859 -0.72243,0 -0.92261,-0.63251 -0.44482,-1.40558 0.61822,-1.00032 1.14818,-0.9534 1.83797,0.1627 1.54329,2.4971 3.62286,0.28423 4.54298,-4.83417 0.86002,-4.78414 1.69504,-6.84428 6.64712,-16.39956 3.61052,-6.96667 3.6761,-11.45616 0.16357,-11.19738 -3.61435,0.26626 -7.56043,3.93185 -7.56043,7.02299 0,1.55357 -1.2065,3.04258 -3.02573,3.73425 -4.95434,1.88364 -8.0049,7.5478 -8.92236,16.56675 -1.08129,10.62942 -1.43944,11.59507 -5.35698,14.44346 l -3.27122,2.37844 1.5185,-3.01344 c 1.22474,-2.43046 0.74568,-2.21494 -2.47622,1.11404 -2.19711,2.27013 -4.5604,4.1275 -5.25179,4.1275 -2.31172,0 -9.03896,5.68073 -10.15175,8.5725 -0.64048,1.66445 -2.17652,2.8575 -3.67898,2.8575 -2.39357,0 -2.30434,-0.37971 1.23857,-5.27055 2.09988,-2.8988 3.81796,-5.94243 3.81796,-6.76361 0,-0.82119 1.43517,-2.84135 3.18927,-4.48925 1.7541,-1.64789 3.80044,-5.31938 4.54744,-8.15887 0.74699,-2.83949 2.74081,-6.47172 4.43073,-8.07161 1.87528,-1.77539 3.07256,-4.3443 3.07256,-6.59255 0,-2.026 0.48172,-3.98137 1.07049,-4.34526 1.2403,-0.76654 12.89951,-20.57725 12.89951,-21.91815 0,-0.49401 1.13145,-3.23547 2.51434,-6.09215 1.38289,-2.85667 2.86671,-6.95575 3.29738,-9.10909 0.75593,-3.77968 0.60117,-3.94422 -4.47313,-4.75563 -4.46787,-0.71445 -5.91473,-0.33817 -9.64738,2.50885 -5.02531,3.833 -5.13193,3.68049 -2.32723,-3.32911 1.13518,-2.83712 1.84417,-5.3782 1.57553,-5.64684 -0.26865,-0.26864 -2.64099,0.18369 -5.27186,1.00519 -3.19405,0.99734 -5.29748,2.65408 -6.33055,4.98613 -0.85093,1.92088 -1.91956,3.49251 -2.37474,3.49251 -0.45517,0 -2.59473,2.71462 -4.75458,6.0325 -2.15986,3.31787 -6.21024,8.26644 -9.00086,10.9968 -2.79062,2.73037 -5.44632,6.15937 -5.90157,7.62 -0.45523,1.46064 -1.20166,2.94144 -1.65873,3.29069 -0.82804,0.63272 -18.4213,20.82196 -23.05804,26.46039 -1.34122,1.63096 -2.43858,3.29692 -2.43858,3.70214 0,0.40521 -3.28612,4.24523 -7.3025,8.53336 -4.01637,4.28814 -15.3035,16.88916 -25.0825,28.00228 -9.779,11.11311 -18.92299,21.41398 -20.31999,22.89081 -15.02714,15.88596 -19.685,21.1377 -19.685,22.19482 0,2.48669 2.51922,1.11505 10.39089,-5.65747 4.41325,-3.79702 8.50136,-6.43443 9.08468,-5.86092 1.13062,1.11161 -2.12533,8.43214 -4.07293,9.15735 -0.63842,0.23773 -2.60579,3.86124 -4.37191,8.05224 -2.67751,6.35375 -2.89522,7.6913 -1.31006,8.04909 1.62156,0.366 1.57794,0.78609 -0.29665,2.8575 -2.86812,3.16923 -0.20165,3.23899 4.34993,0.11379 z m 82.22061,-44.99288 c 1.32455,-1.57162 4.70881,-5.42925 7.52059,-8.5725 2.81178,-3.14325 6.37053,-7.4295 7.90835,-9.525 1.53781,-2.0955 2.81413,-3.09003 2.83625,-2.21006 0.0221,0.87995 -1.10276,2.57859 -2.49976,3.77474 -1.397,1.19615 -2.54,3.20364 -2.54,4.46111 0,1.25745 -2.98246,5.13094 -6.62767,8.60775 -6.74687,6.43513 -10.97081,8.6528 -6.59776,3.46396 z m -10.68395,-9.84927 c -3.86076,-3.21767 -2.43394,-5.47564 2.75778,-4.36422 5.10368,1.09257 5.11718,1.10934 3.42982,4.26222 -1.72889,3.23045 -2.42023,3.24184 -6.1876,0.10198 z M 520.69996,-39.55 c 0,-0.6985 0.5715,-1.27 1.27,-1.27 0.6985,0 1.27,0.5715 1.27,1.27 0,0.6985 -0.5715,1.27 -1.27,1.27 -0.6985,0 -1.27,-0.5715 -1.27,-1.27 z m 17.78,-4.9301 c 0,-0.6985 0.5715,-1.6232 1.27,-2.0549 0.6985,-0.43169 1.27,-0.21339 1.27,0.48511 0,0.6985 -0.5715,1.6232 -1.27,2.05489 -0.6985,0.4317 -1.27,0.2134 -1.27,-0.4851 z M 340.72853,8.34292 c 15.28692,-13.09419 76.26159,-74.98021 98.49083,-99.96292 39.76467,-44.69019 81.9905,-100.14932 92.30689,-121.23526 3.01995,-6.17253 3.10138,-6.82476 1.28668,-10.30468 -3.35623,-6.43599 -2.31267,-8.75858 2.12918,-4.73876 2.25088,2.03703 2.33027,2.02016 1.49446,-0.3175 -1.26664,-3.54265 -4.45854,-6.9698 -5.74872,-6.17242 -0.60645,0.3748 -1.65893,-0.78167 -2.33883,-2.56996 -0.67991,-1.78827 -1.85556,-3.25141 -2.61258,-3.25141 -0.75701,0 -0.9991,-0.61047 -0.53798,-1.35658 0.46114,-0.74612 1.63786,-1.04981 2.61496,-0.67487 0.9771,0.37496 1.77654,0.0822 1.77654,-0.65058 0,-0.73276 -2.24889,-2.21569 -4.99754,-3.29539 -4.22002,-1.65767 -5.4285,-3.0249 -7.7675,-8.78784 -2.70512,-6.66499 -3.84187,-7.51303 -9.38127,-6.99863 -2.25962,0.20984 -17.20549,14.9052 -17.21816,16.92958 -0.003,0.48439 -1.72003,1.89348 -3.81553,3.13133 -2.0955,1.23784 -3.81,3.03693 -3.81,3.99798 0,0.96105 -2.66837,4.12396 -5.9297,7.02868 -11.44004,10.1891 -22.7815,22.05828 -37.86372,39.62547 -8.37012,9.74922 -20.08587,23.19177 -26.035,29.87234 -5.94911,6.68057 -14.13855,16.11032 -18.19874,20.955 -20.58465,24.56186 -31.42452,37.25715 -38.33783,44.8999 -4.191,4.63321 -10.19175,11.77446 -13.335,15.86945 -3.14325,4.09499 -7.18789,9.22377 -8.9881,11.3973 -1.8002,2.17351 -5.11379,6.66648 -7.36351,9.98435 -2.24972,3.31788 -4.63445,6.0325 -5.2994,6.0325 -0.66494,0 -1.20899,1.15577 -1.20899,2.56838 0,1.4126 -2.16353,4.98448 -4.80786,7.9375 -8.67796,9.69101 -21.7322,27.07163 -25.5158,33.97213 -1.34891,2.46014 -2.74304,6.31608 -3.09807,8.56874 -0.6136,3.89334 -0.47841,4.05588 2.73562,3.28937 1.85962,-0.44349 4.08923,-1.36382 4.9547,-2.04518 2.1399,-1.68469 10.49142,0.0152 10.49142,2.13554 0,0.93578 1.44696,2.64948 3.21547,3.80825 1.7685,1.15878 2.9178,2.5885 2.55398,3.17717 -1.34545,2.17696 2.92867,4.0492 7.35027,3.21969 3.44419,-0.64613 4.94012,-0.28587 6.76376,1.62891 2.94419,3.0913 4.1664,2.50723 16.04507,-7.66758 z m -42.6935,-82.63793 c 1.97447,-1.64725 5.87595,-3.73996 8.66995,-4.65045 8.7519,-2.852 14.66308,-5.96927 19.27152,-10.16286 2.44809,-2.2277 5.9427,-4.82424 7.76578,-5.77008 8.17681,-4.24218 69.33557,-68.25947 72.71781,-76.11659 0.60136,-1.397 2.89009,-4.2545 5.08607,-6.35 2.19598,-2.0955 5.01887,-5.81025 6.2731,-8.255 1.25424,-2.44475 3.57106,-5.588 5.1485,-6.985 1.57745,-1.397 5.31506,-6.29614 8.30579,-10.88699 2.99074,-4.59085 6.33292,-9.08995 7.42706,-9.99801 1.09414,-0.90805 1.98936,-2.45839 1.98936,-3.44519 0,-2.0808 3.73694,-8.27658 5.77823,-9.58019 0.76681,-0.48971 2.13379,-3.34721 3.03774,-6.35 0.90393,-3.0028 1.9554,-6.31687 2.33658,-7.36462 0.38119,-1.04775 0.74243,-2.37993 0.80275,-2.9604 0.0603,-0.58047 1.53844,-1.8461 3.28469,-2.81249 l 3.175,-1.75711 -3.8735,-0.045 c -2.13042,-0.0248 -4.62925,0.66937 -5.55296,1.54249 -1.38435,1.30856 -1.42897,1.10008 -0.254,-1.18651 1.67032,-3.25051 1.88867,-8.02099 0.36714,-8.02099 -2.05368,0 -2.71275,1.69166 -1.31515,3.37565 0.86305,1.03991 0.91938,1.70435 0.14448,1.70435 -0.6985,0 -1.98145,-0.85725 -2.85101,-1.905 -1.74224,-2.09927 -5.80052,-2.55152 -6.90209,-0.76915 -0.88252,1.42793 -10.8779,2.45726 -10.8779,1.12018 0,-0.54234 -0.83136,-0.98607 -1.84746,-0.98607 -1.10857,0 -2.18354,1.79152 -2.68779,4.47942 -1.40468,7.48758 -8.16475,8.43173 -8.16475,1.14033 0,-1.51923 -0.36557,-4.59011 -0.81238,-6.82415 -0.70578,-3.52891 -1.21425,-3.96105 -3.87498,-3.29325 -2.45246,0.61553 -2.98639,0.31783 -2.68012,-1.49435 0.62676,-3.70833 -4.83076,-3.54599 -9.37642,0.27892 -2.24804,1.8916 -4.97647,3.10312 -6.24272,2.77199 -1.23713,-0.32352 -2.50547,0.0793 -2.81852,0.89504 -0.31305,0.81578 -2.3502,1.61895 -4.52702,1.78483 -3.17025,0.24156 -3.99575,-0.20789 -4.14834,-2.25861 -0.10478,-1.40809 -0.24765,-3.27454 -0.3175,-4.14767 -0.18383,-2.29786 -2.80965,-4.7625 -5.07395,-4.7625 -1.07436,0 -4.09968,2.14313 -6.72294,4.7625 -2.62325,2.61938 -5.3026,4.59011 -5.95411,4.37941 -0.6515,-0.21069 -3.31183,1.69413 -5.91183,4.23296 -4.57809,4.4704 -4.70747,4.51254 -4.10016,1.3356 0.3449,-1.80426 0.0589,-3.28047 -0.63545,-3.28047 -2.08998,0 -4.77558,4.03977 -4.35115,6.5451 0.21849,1.2897 -0.203,2.3449 -0.93665,2.3449 -0.73364,0 -2.86211,2.42888 -4.72994,5.3975 -5.05166,8.02886 -28.60601,35.2425 -30.50359,35.2425 -0.9041,0 -2.00242,1.42875 -2.44071,3.175 -0.43828,1.74625 -1.20386,3.175 -1.70129,3.175 -1.12164,0 -13.05114,14.79152 -15.26355,18.92542 -0.8922,1.66709 -3.75978,5.37748 -6.37241,8.24533 -9.50548,10.43399 -17.77081,24.44339 -16.05713,27.21618 0.82738,1.33872 8.39287,2.7542 8.39287,1.57026 0,-0.41409 2.71462,-2.58647 6.0325,-4.82751 3.31787,-2.24103 7.48766,-5.19507 9.2662,-6.56454 4.23599,-3.26171 7.80916,-1.6169 5.29577,2.43777 -1.83443,2.95935 -1.08099,5.06709 1.81131,5.06709 3.22049,0 1.202,2.90877 -3.02381,4.35751 -5.15611,1.76766 -6.55652,3.26249 -3.05642,3.26249 3.91337,0 6.17624,5.24364 4.16644,9.65468 -1.24424,2.73081 -2.07939,3.1607 -4.80922,2.47557 -3.02642,-0.75959 -3.24997,-0.54901 -2.66512,2.5104 0.40952,2.14233 0.0114,3.58002 -1.10964,4.01033 -1.00379,0.38519 -1.74801,2.48637 -1.74801,4.93522 0,2.96465 -0.77418,4.67877 -2.54,5.62381 -3.25984,1.74461 -3.29232,5.07999 -0.0495,5.07999 2.51316,0 6.32448,-3.31589 9.85332,-8.57249 1.05505,-1.57163 2.47429,-2.8575 3.15385,-2.8575 1.51467,0 -0.0625,10.60773 -1.66741,11.21495 -0.6105,0.23097 -0.52294,1.51685 0.19454,2.8575 0.7175,1.34065 0.88438,2.43754 0.37085,2.43754 -0.51352,0 -0.31398,0.6197 0.44342,1.3771 1.9785,1.9785 0.69908,5.50677 -2.51222,6.9279 -1.51623,0.67101 -3.33842,2.37487 -4.04933,3.78634 -0.7109,1.4115 -2.56957,4.34479 -4.13036,6.51842 l -2.83781,3.95206 4.10781,2.02517 c 5.28057,2.60336 5.18288,2.61283 9.40787,-0.912 z m 5.7145,-81.66216 c 0.53525,-2.77928 4.22545,-3.17247 4.22545,-0.45022 0,1.03818 -1.05203,2.09021 -2.33783,2.33784 -1.69723,0.32684 -2.21446,-0.19039 -1.88762,-1.88762 z m 69.63044,-89.89581 c 0,-1.85205 4.64377,-5.78701 6.82941,-5.78701 0.65135,0 0.23843,1.04512 -0.91757,2.3225 -1.15602,1.27737 -2.10184,2.99187 -2.10184,3.81 0,0.81812 -0.85725,1.4875 -1.905,1.4875 -1.04775,0 -1.905,-0.82484 -1.905,-1.83299 z m 9.20862,-1.97701 c 0.61567,-2.29074 8.57138,-3.41642 8.57138,-1.21279 0,0.97638 -1.15964,1.19709 -3.37203,0.64182 -2.17591,-0.54613 -3.78693,-0.2571 -4.54168,0.81482 -0.91153,1.29454 -1.05667,1.24073 -0.65767,-0.24385 z m 18.73138,-9.45005 c 0,-1.08897 0.5715,-1.97995 1.27,-1.97995 0.6985,0 1.27,0.53777 1.27,1.19504 0,0.65728 -0.5715,1.54826 -1.27,1.97996 -0.6985,0.43169 -1.27,-0.10605 -1.27,-1.19505 z M 283.20998,-93.66087 c 0,-0.97301 -0.85725,-1.76913 -1.905,-1.76913 -1.04821,0 -1.905,1.12446 -1.905,2.50015 0,1.70057 0.60923,2.26636 1.905,1.76912 1.04775,-0.40206 1.905,-1.52712 1.905,-2.50014 z m -5.08,-4.30913 c 0,-0.6985 -0.5715,-1.27 -1.27,-1.27 -0.6985,0 -1.27,0.5715 -1.27,1.27 0,0.6985 0.5715,1.27 1.27,1.27 0.6985,0 1.27,-0.5715 1.27,-1.27 z m 292.64413,-146.06418 c 3.19321,-4.1988 5.80584,-8.15544 5.80584,-8.79255 0,-0.63711 1.33112,-2.77386 2.95805,-4.74832 3.81432,-4.62916 22.56604,-42.85758 23.87591,-48.67494 0.55047,-2.44475 2.09018,-8.50106 3.42156,-13.45846 2.5655,-9.55261 2.17003,-18.86204 -0.96153,-22.63535 -6.52888,-7.86681 -29.66163,-0.75616 -62.94898,19.34959 -26.28231,15.87465 -29.94693,18.34241 -29.45349,19.83401 0.24658,0.74537 5.54573,5.20859 11.7759,9.91828 14.21854,10.7485 28.86537,24.7686 35.99081,34.45078 4.95679,6.73541 5.41547,7.96506 4.7586,12.75742 -0.39982,2.91696 -1.10081,6.27785 -1.55776,7.46864 -1.52011,3.96135 0.46936,2.24384 6.33509,-5.4691 z m -213.21563,-12.77239 c 0.46113,-0.74612 1.59607,-1.06585 2.5221,-0.71049 2.71741,1.04276 4.80216,-2.37595 2.57569,-4.22376 -2.16484,-1.79666 -4.5163,-0.75755 -4.5163,1.99573 0,1.04774 -0.50674,1.59181 -1.12609,1.20903 -0.61936,-0.38279 -1.83234,0.155 -2.69552,1.19505 -1.27604,1.53755 -1.19825,1.89102 0.41616,1.89102 1.09205,0 2.36283,-0.61047 2.82396,-1.35658 z m 293.74677,-47.30846 c 16.26132,-24.29372 34.4893,-57.39519 43.36257,-78.74516 24.95001,-60.03212 2.34502,-79.56171 -57.41749,-49.60586 -19.73598,9.89262 -37.98584,21.24627 -60.03538,37.34935 -25.73002,18.79099 -29.84499,21.95057 -29.84499,22.91575 0,0.55538 -1.00013,1.3303 -2.2225,1.72203 -1.22238,0.39172 -4.32928,2.57336 -6.90423,4.84807 -2.57495,2.27472 -5.7182,4.7832 -6.985,5.57441 -1.2668,0.7912 -1.89752,1.8637 -1.40161,2.38332 0.49591,0.51964 3.63916,-1.31463 6.985,-4.07613 21.53812,-17.77655 61.46891,-45.13963 82.91833,-56.82081 17.47303,-9.51567 34.28327,-16.48008 46.15225,-19.12068 8.88699,-1.97717 10.09775,-1.98192 16.68995,-0.0655 8.80406,2.55941 11.40916,5.09239 13.91472,13.52952 1.57431,5.3013 1.7094,8.23495 0.67687,14.69843 -3.67101,22.97959 -19.72954,58.10525 -46.53094,101.77955 -4.42357,7.20843 -8.04285,13.38579 -8.04285,13.72746 0,1.80655 3.24054,-1.9595 8.6853,-10.09373 z m -147.11529,-31.43433 c 0,-0.81688 -0.90625,-0.78499 -2.54,0.0894 -1.397,0.74764 -2.54,1.93087 -2.54,2.62937 0,0.81687 0.90625,0.78498 2.54,-0.0894 1.397,-0.74765 2.54,-1.93087 2.54,-2.62937 z m 115.55055,-5.34892 c 0.0102,-1.94331 -0.81723,-5.53576 -1.83985,-7.98321 -2.20522,-5.27782 -9.25807,-8.89144 -14.51359,-7.43619 -3.30564,0.91532 -3.30342,0.91984 1.13288,2.3149 8.80715,2.76952 12.68947,7.44954 12.71944,15.33288 0.0144,3.77023 0.34311,4.6141 1.25056,3.20991 0.67711,-1.04775 1.23986,-3.49498 1.25056,-5.43829 z m -107.93055,-0.27671 c 1.71614,-2.06783 1.30565,-2.32631 -1.80113,-1.13412 -1.10488,0.42397 -2.00887,1.28122 -2.00887,1.905 0,1.7567 2.05784,1.34034 3.81,-0.77088 z m 7.366,-5.969 c 2.39724,-2.39724 1.78131,-3.78308 -1.016,-2.286 -1.397,0.74765 -2.54,1.91075 -2.54,2.58468 0,1.68197 1.71979,1.53752 3.556,-0.29868 z m 7.62,-6.35 c 2.39724,-2.39724 1.78131,-3.78308 -1.016,-2.286 -1.397,0.74765 -2.54,1.91075 -2.54,2.58468 0,1.68197 1.71979,1.53752 3.556,-0.29868 z"
+         id="path8418"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#c7a46a;stroke-width:1.26999998"
+         d="M 51.435,285.01316 C 45.15376,282.6926 39.953,277.08029 38.18547,270.71519 29.72376,240.24359 64.4959,163.39819 133.18002,60.78 c 16.87373,-25.21036 16.4878,-24.80954 22.10326,-22.95626 2.43627,0.80403 5.40963,2.54482 6.60747,3.86843 1.19784,1.32359 2.9705,2.10238 3.93926,1.73063 2.80992,-1.07827 21.1655,5.79661 24.8635,9.31235 l 3.36852,3.20251 -2.48562,5.91368 c -1.36708,3.2525 -3.55587,8.19966 -4.86396,10.99366 -8.57116,18.30747 -13.26154,34.03811 -13.31676,44.66188 -0.034,6.55989 0.45637,8.36152 3.01991,11.09305 5.83121,6.21335 15.98949,5.63721 32.37321,-1.83607 4.26039,-1.94336 10.31792,-5.28708 13.46117,-7.4305 3.14325,-2.14342 6.20921,-3.90753 6.81326,-3.92024 3.03333,-0.0639 38.18769,-27.07231 38.70001,-29.73253 0.46402,-2.40947 -4.34446,-8.39059 -6.74554,-8.39059 -6.03461,0 -42.02265,-34.01297 -51.73422,-48.895 -8.67801,-13.29815 -16.98433,-28.85151 -19.45956,-36.43749 -1.21002,-3.70837 -2.69894,-7.37621 -3.30871,-8.15075 -2.08445,-2.64762 -6.22983,-16.29015 -10.17327,-33.48037 -3.54867,-15.4693 -3.94961,-19.33278 -4.02363,-38.77139 -0.14743,-38.72659 7.28775,-69.28421 25.77215,-105.92011 29.55499,-58.57769 80.62328,-103.72286 139.49634,-123.31699 23.78341,-7.91559 37.14243,-10.25486 63.10235,-11.04971 23.91549,-0.73226 37.33739,0.38831 55.62711,4.64421 l 9.91132,2.30629 22.70262,-20.08059 c 92.40099,-81.72915 165.84183,-131.73959 210.76143,-143.52053 20.78032,-5.45001 35.76148,-2.25583 41.9883,8.95244 2.65212,4.7738 3.17216,7.38825 3.15773,15.875 -0.04,23.53453 -13.04097,55.93195 -43.16273,107.55816 -20.77525,35.60711 -65.25436,99.25787 -99.95099,143.03253 -4.0502,5.10989 -7.364,9.44955 -7.364,9.64368 0,0.19415 1.14989,3.69518 2.55531,7.7801 7.10996,20.66549 10.96498,54.53869 8.9112,78.30077 -3.15903,36.54981 -9.23775,58.716 -24.47377,89.24428 -12.80333,25.65395 -29.11172,47.83815 -49.73337,67.65198 -39.39988,37.85645 -90.05749,61.48763 -142.52097,66.48439 -17.61204,1.67742 -34.74836,0.85233 -55.11141,-2.65352 -8.02008,-1.3808 -14.71004,-2.51432 -14.8666,-2.51896 -0.50429,-0.0149 -24.85023,22.25381 -33.37036,30.52319 -10.52421,10.21449 -43.38134,39.84482 -50.64176,45.66839 -3.04793,2.44475 -11.61705,9.60502 -19.04246,15.9117 -19.14333,16.25914 -47.10731,37.32995 -70.24449,52.92908 -42.08894,28.37648 -77.98417,42.07203 -94.38627,36.01238 z m 64.65416,-49.19004 c 12.73813,-3.43711 36.39178,-15.37167 54.72583,-27.61216 13.09684,-8.74395 17.61672,-12.02948 32.70453,-23.77319 9.50928,-7.40164 5.96982,-6.53869 -4.3541,1.06157 -35.84656,26.38946 -69.21793,43.69333 -86.96283,45.09231 -30.53146,2.40707 -34.2821,-23.6487 -11.06228,-76.84977 10.7522,-24.63534 10.94184,-25.10444 9.60016,-23.74688 -2.37353,2.40162 -13.840892,25.41647 -18.983683,38.1 -7.578767,18.69132 -10.726467,30.42244 -11.438157,42.62874 -0.84593,14.50852 2.00415,21.64521 10.114064,25.32593 7.07478,3.21092 13.114686,3.15758 25.656466,-0.22655 z m 59.80583,-94.27173 c -2.0955,-0.92354 -5.38163,-3.2651 -7.3025,-5.20347 -3.54245,-3.57468 -4.60618,-2.55186 -2.13948,2.05721 1.54603,2.8888 5.45745,4.74987 10.07698,4.79466 3.05611,0.0296 3.03234,-0.0321 -0.635,-1.6484 z M 415.75264,48.70718 c 2.00072,-1.04344 4.17651,-1.89718 4.83511,-1.89718 0.65858,0 7.12702,-3.94377 14.37429,-8.76391 13.17731,-8.76422 20.41987,-11.43737 26.10199,-9.63393 2.10083,0.66678 2.53075,1.53637 1.93436,3.91258 -0.42175,1.6804 -0.55799,3.05526 -0.30274,3.05526 0.83994,0 9.09529,-4.22954 9.74431,-4.99239 0.34925,-0.41052 3.20675,-2.39417 6.35,-4.40813 3.14325,-2.01397 6.57225,-4.4424 7.62,-5.39653 4.94882,-4.50655 20.17041,-15.86974 24.27213,-18.11956 4.85327,-2.66206 23.98787,-21.20337 23.98787,-23.24412 0,-0.66056 1.92175,-3.0673 4.27055,-5.3483 3.81963,-3.70936 14.47521,-21.04097 12.93612,-21.04097 -0.33971,0 1.65055,-3.23799 4.42284,-7.19554 2.77227,-3.95754 5.04049,-7.93794 5.04049,-8.84532 0,-0.90737 1.11841,-2.24832 2.48535,-2.97989 1.45554,-0.77899 2.83902,-3.29704 3.33893,-6.07718 0.86536,-4.81265 1.69751,-6.86764 6.65258,-16.42868 4.98025,-9.60963 3.09481,-13.62161 -4.46766,-9.50661 -2.53902,1.38156 -2.72853,1.26215 -2.13939,-1.34796 0.35552,-1.5751 1.1061,-6.00707 1.66795,-9.84881 0.56186,-3.84175 2.75536,-12.08482 4.87444,-18.31791 3.89732,-11.46366 3.90218,-13.03664 0.0566,-18.39452 -1.65535,-2.30634 -2.87026,-2.79202 -5.3975,-2.15773 -2.96237,0.74351 -3.26134,0.46706 -3.26134,-3.01565 0,-2.10881 -0.46298,-3.83419 -1.02883,-3.83419 -0.56586,0 -6.86233,5.92802 -13.99216,13.1734 -9.20522,9.35439 -12.73341,13.77251 -12.17028,15.24 0.43615,1.13663 0.21995,2.0666 -0.48047,2.0666 -0.70041,0 -3.04063,2.71462 -5.20048,6.0325 -2.15986,3.31787 -6.21024,8.26644 -9.00086,10.9968 -2.79062,2.73037 -5.44632,6.15937 -5.90157,7.62 -0.45523,1.46064 -1.20166,2.94144 -1.65873,3.29069 -0.82804,0.63272 -18.4213,20.82196 -23.05804,26.46039 -1.34122,1.63096 -2.43858,3.29692 -2.43858,3.70214 0,0.40521 -3.28612,4.24523 -7.3025,8.53336 -4.01637,4.28814 -15.3035,16.89111 -25.0825,28.0066 -9.779,11.11549 -22.06624,24.7041 -27.30499,30.19691 -5.23875,5.49282 -9.94633,10.73398 -10.46129,11.64702 -0.51494,0.91304 -2.3022,2.00291 -3.97165,2.42192 -1.66947,0.41901 -4.03675,2.34017 -5.26064,4.26924 -1.22387,1.92908 -3.54364,4.93617 -5.15503,6.68242 -1.6114,1.74625 -3.51484,4.72381 -4.22988,6.61679 -1.25964,3.33472 -1.19742,3.40275 2.00034,2.18697 4.36434,-1.65932 4.77151,-0.98937 2.76729,4.5532 -1.49934,4.14634 -1.44248,4.83375 0.51841,6.26759 1.21134,0.88576 2.20245,2.5476 2.20245,3.69296 0,2.58135 2.08873,2.63337 6.81267,0.16967 z M 346.57434,3.13341 c 22.49497,-21.04206 72.35315,-72.06373 92.59265,-94.75341 39.54141,-44.32825 81.99968,-100.06103 92.35926,-121.23526 3.01995,-6.17253 3.10138,-6.82476 1.28668,-10.30468 -1.99276,-3.82135 -2.52757,-6.89005 -1.20079,-6.89005 0.4137,0 2.24648,1.17541 4.07283,2.61202 l 3.32064,2.61201 1.05524,-2.85259 c 0.82224,-2.22271 0.34358,-3.56425 -2.16782,-6.07565 -2.82081,-2.8208 -3.22307,-2.92988 -3.22307,-0.87393 0,1.82122 -0.38302,2.03127 -1.70435,0.93466 -0.93739,-0.77796 -2.0644,-1.05443 -2.50447,-0.61438 -0.44007,0.44007 -1.37347,-0.70788 -2.07421,-2.55101 -0.70077,-1.84312 -1.97405,-3.35113 -2.82954,-3.35113 -1.14427,0 -1.18096,-0.37448 -0.13884,-1.4166 1.04213,-1.04212 2.42988,-0.91111 5.2498,0.49566 l 3.83319,1.91225 -2.63489,-5.42342 c -1.68171,-3.46146 -4.0559,-6.05278 -6.56292,-7.16316 -2.83229,-1.25443 -4.28797,-3.04029 -5.2184,-6.40204 -0.7097,-2.56426 -2.51391,-6.0688 -4.00936,-7.78786 -3.75103,-4.3119 -7.75441,-3.26075 -14.59688,3.83268 -3.25857,3.37809 -4.40055,5.16106 -2.77012,4.32498 4.69135,-2.40569 4.38112,-1.66069 -2.16755,5.20518 -3.4768,3.64523 -6.32146,7.04192 -6.32146,7.54821 0,0.50628 -1.7145,1.93329 -3.81,3.17114 -2.0955,1.23784 -3.81,3.03693 -3.81,3.99798 0,0.96105 -2.66837,4.12396 -5.9297,7.02868 -11.44004,10.1891 -22.7815,22.05828 -37.86372,39.62547 -8.37012,9.74922 -20.08587,23.19177 -26.035,29.87234 -5.94911,6.68057 -14.13855,16.11032 -18.19874,20.955 -20.58465,24.56186 -31.42452,37.25715 -38.33783,44.8999 -4.191,4.63321 -10.19175,11.77446 -13.335,15.86945 -3.14325,4.09499 -7.18789,9.22377 -8.9881,11.3973 -1.8002,2.17351 -5.11379,6.66648 -7.36351,9.98435 -2.24972,3.31788 -4.63445,6.0325 -5.2994,6.0325 -0.66494,0 -1.20899,1.15577 -1.20899,2.56838 0,1.4126 -2.16353,4.98448 -4.80786,7.9375 -8.67796,9.69101 -21.7322,27.07163 -25.5158,33.97213 -1.34891,2.46014 -2.74304,6.31608 -3.09807,8.56874 -0.6136,3.89334 -0.47841,4.05588 2.73562,3.28937 1.85962,-0.44349 4.08923,-1.36382 4.9547,-2.04518 2.1399,-1.68469 10.49142,0.0152 10.49142,2.13554 0,0.93578 1.44696,2.64948 3.21547,3.80825 1.7685,1.15878 2.9178,2.5885 2.55398,3.17717 -1.34545,2.17696 2.92867,4.0492 7.35027,3.21969 3.45299,-0.64777 4.93546,-0.28726 6.76376,1.64487 2.32023,2.452 2.38068,2.45111 6.65088,-0.0969 2.37014,-1.4143 9.22813,-7.17253 15.24,-12.79609 z m -73.52436,-63.56345 c 0,-0.30803 -0.5715,-0.91326 -1.27,-1.34496 -0.6985,-0.43169 -1.27,-0.17968 -1.27,0.56005 0,0.73972 0.5715,1.34495 1.27,1.34495 0.6985,0 1.27,-0.25201 1.27,-0.56004 z m 0.86755,-8.56808 c -0.40541,-1.01312 -0.70635,-0.71219 -0.76729,0.7673 -0.0551,1.33878 0.24501,2.08888 0.66703,1.66687 0.42201,-0.42201 0.46712,-1.5174 0.10021,-2.43417 z m 24.1175,-5.29689 c 1.97447,-1.64725 5.87595,-3.73996 8.66995,-4.65045 8.7519,-2.852 14.66308,-5.96927 19.27152,-10.16286 2.44809,-2.2277 5.9427,-4.82424 7.76578,-5.77008 8.17681,-4.24218 69.33557,-68.25947 72.71781,-76.11659 0.60136,-1.397 2.89009,-4.2545 5.08607,-6.35 2.19598,-2.0955 5.01887,-5.81025 6.2731,-8.255 1.25424,-2.44475 3.57106,-5.588 5.1485,-6.985 1.57745,-1.397 5.31506,-6.29614 8.30579,-10.88699 2.99074,-4.59085 6.33292,-9.08995 7.42706,-9.99801 1.09414,-0.90805 1.98936,-2.45839 1.98936,-3.44519 0,-2.0808 3.73694,-8.27658 5.77823,-9.58019 0.76681,-0.48971 2.13379,-3.34721 3.03774,-6.35 0.90393,-3.0028 1.95261,-6.31687 2.3304,-7.36462 0.37778,-1.04775 0.58681,-2.36163 0.4645,-2.91973 -0.1223,-0.55812 2.32373,-2.14101 5.43564,-3.51755 4.38237,-1.93853 5.5488,-3.06982 5.17366,-5.01777 -0.26639,-1.38322 0.0543,-2.51495 0.71275,-2.51495 0.65839,0 1.19707,-1.42875 1.19707,-3.175 0,-3.30473 -2.90597,-4.39519 -4.56391,-1.71261 -1.14336,1.85002 -4.32609,-2.20746 -4.32609,-5.51513 0,-2.67806 -4.84432,-3.02184 -13.72342,-0.97389 -4.35463,1.0044 -6.02888,0.86874 -8.22008,-0.66603 -1.51342,-1.06005 -4.0484,-1.92734 -5.63331,-1.92734 -1.58491,0 -3.59547,-1.143 -4.46791,-2.54 -2.32469,-3.72241 -14.80694,-3.6612 -18.20104,0.0893 -1.85321,2.04777 -3.30111,2.45635 -6.54684,1.84745 -2.55064,-0.47852 -4.16739,-0.2668 -4.16739,0.54572 0,0.73015 -1.05236,1.32755 -2.33858,1.32755 -1.93773,0 -2.37812,-1.11735 -2.5693,-6.51869 -0.29757,-8.40756 -0.83341,-9.10132 -5.26112,-6.81165 -2.02117,1.04518 -4.86038,1.90034 -6.30936,1.90034 -1.6299,0 -3.0873,1.19093 -3.8219,3.12308 -0.65306,1.7177 -3.35355,4.24408 -6.00107,5.61421 -5.10437,2.64158 -12.92131,10.36643 -25.21474,24.91771 -4.13083,4.8895 -11.53488,13.462 -16.45345,19.05 -4.91858,5.588 -11.2109,13.01749 -13.98294,16.50999 -2.77203,3.4925 -6.84784,8.35025 -9.05735,10.795 -2.2095,2.44475 -8.41881,9.5885 -13.79847,15.875 -5.37966,6.2865 -11.29274,13.1445 -13.14019,15.24 -8.95909,10.16195 -19.17939,23.19807 -20.41582,26.0406 -0.75823,1.74318 -3.02719,6.19246 -5.04212,9.8873 -3.83144,7.02582 -5.83606,12.96711 -4.37515,12.96711 0.47125,0 2.62526,-1.33043 4.78669,-2.9565 2.16143,-1.62608 6.93025,-4.5744 10.59738,-6.55184 3.66712,-1.97744 6.6675,-4.17127 6.6675,-4.87518 0,-0.7039 0.36228,-0.91754 0.80506,-0.47475 0.44279,0.44279 2.19376,-0.28729 3.89107,-1.62237 1.71451,-1.34864 4.03767,-2.0937 5.22742,-1.67649 1.74475,0.61185 1.84737,0.43401 0.55395,-0.95996 -2.61446,-2.81772 -1.88002,-4.22103 4.445,-8.49324 3.31787,-2.24103 7.48766,-5.19507 9.2662,-6.56454 4.23599,-3.26171 7.80916,-1.6169 5.29577,2.43777 -1.83443,2.95935 -1.08099,5.06709 1.81131,5.06709 3.22049,0 1.202,2.90877 -3.02381,4.35751 -5.15611,1.76767 -6.55652,3.26249 -3.05642,3.26249 3.91337,0 6.17624,5.24364 4.16644,9.65468 -1.24424,2.73081 -2.07939,3.1607 -4.80922,2.47557 -3.02642,-0.75959 -3.24997,-0.54901 -2.66512,2.5104 0.40952,2.14233 0.0114,3.58002 -1.10964,4.01033 -1.00379,0.38519 -1.74801,2.48637 -1.74801,4.93522 0,2.96465 -0.77418,4.67877 -2.54,5.62381 -3.25984,1.74461 -3.29233,5.07999 -0.0495,5.07999 2.51316,0 6.32448,-3.31589 9.85332,-8.57249 1.05505,-1.57163 2.47429,-2.8575 3.15385,-2.8575 1.51467,0 -0.0625,10.60773 -1.66741,11.21495 -0.6105,0.23097 -0.52294,1.51685 0.19454,2.8575 0.7175,1.34065 0.88438,2.43754 0.37085,2.43754 -0.51352,0 -0.31398,0.6197 0.44342,1.3771 1.9785,1.9785 0.69908,5.50677 -2.51222,6.9279 -1.51623,0.67101 -3.33842,2.37487 -4.04933,3.78634 -0.7109,1.4115 -2.56957,4.34479 -4.13036,6.51842 l -2.83781,3.95206 4.10781,2.02517 c 5.28057,2.60336 5.18288,2.61283 9.40787,-0.912 z m 5.7145,-81.66216 c 0.53525,-2.77928 4.22545,-3.17247 4.22545,-0.45022 0,1.03818 -1.05203,2.09021 -2.33783,2.33784 -1.69723,0.32685 -2.21446,-0.19039 -1.88762,-1.88762 z m 69.63044,-89.89581 c 0,-1.85205 4.64377,-5.78701 6.82941,-5.78701 0.65135,0 0.23843,1.04512 -0.91757,2.3225 -1.15602,1.27737 -2.10184,2.99187 -2.10184,3.81 0,0.81812 -0.85725,1.4875 -1.905,1.4875 -1.04775,0 -1.905,-0.82484 -1.905,-1.83299 z m 9.20862,-1.97701 c 0.61567,-2.29074 8.57138,-3.41642 8.57138,-1.21279 0,0.97638 -1.15964,1.19709 -3.37203,0.64182 -2.17591,-0.54613 -3.78693,-0.2571 -4.54168,0.81482 -0.91153,1.29454 -1.05667,1.24073 -0.65767,-0.24385 z m -1.58862,-7.7755 c 0,-2.25202 0.31344,-2.43539 1.905,-1.1145 1.04775,0.86955 1.905,2.08253 1.905,2.6955 0,0.61297 -0.85725,1.1145 -1.905,1.1145 -1.09497,0 -1.905,-1.14617 -1.905,-2.6955 z m 20.32,-1.67455 c 0,-1.08897 0.5715,-1.97995 1.27,-1.97995 0.6985,0 1.27,0.53777 1.27,1.19504 0,0.65728 -0.5715,1.54826 -1.27,1.97996 -0.6985,0.43169 -1.27,-0.10605 -1.27,-1.19505 z M 276.85998,-75.72536 c 0,-0.189 -0.85725,-1.05508 -1.905,-1.92464 -1.59004,-1.31961 -1.905,-1.14194 -1.905,1.07464 0,1.83881 0.58595,2.43079 1.905,1.92463 1.04775,-0.40206 1.905,-0.88564 1.905,-1.07463 z m 2.66488,-13.47671 c 4.6412,-4.53692 4.80288,-6.78382 0.40879,-5.68097 -2.79813,0.70228 -3.16535,0.47263 -2.51598,-1.57337 0.62633,-1.97339 0.0846,-2.41489 -3.07366,-2.50469 -3.6207,-0.103 -3.83403,0.1632 -3.83403,4.78312 0,5.33766 4.10715,14.2803 4.88738,10.64143 0.24329,-1.13475 2.10067,-3.68423 4.1275,-5.66552 z m -0.89576,-26.0168 c 1.65399,-4.31021 0.92418,-5.71753 -1.76912,-3.41145 -1.397,1.19615 -2.54,2.90505 -2.54,3.79757 0,2.31527 3.38903,2.01159 4.30912,-0.38612 z m -0.17324,-14.34013 c 0.47308,-1.80905 0.0692,-2.2344 -1.68592,-1.77543 -1.83265,0.47925 -1.99568,0.95931 -0.79151,2.33069 1.92219,2.18909 1.74966,2.22776 2.47743,-0.55526 z m 292.31825,-114.47518 c 3.19321,-4.1988 5.80584,-8.15544 5.80584,-8.79255 0,-0.63711 1.33112,-2.77386 2.95805,-4.74832 3.81432,-4.62916 22.56604,-42.85758 23.87591,-48.67494 0.55047,-2.44475 2.09018,-8.50106 3.42156,-13.45846 2.5655,-9.55261 2.17003,-18.86204 -0.96153,-22.63535 -6.52888,-7.86681 -29.66163,-0.75616 -62.94898,19.34959 -26.28231,15.87465 -29.94693,18.34241 -29.45349,19.83401 0.24658,0.74537 5.54573,5.20859 11.7759,9.91828 14.21854,10.7485 28.86537,24.7686 35.99081,34.45078 4.95679,6.73541 5.41547,7.96506 4.7586,12.75742 -0.39982,2.91696 -1.10081,6.27785 -1.55776,7.46864 -1.52011,3.96135 0.46936,2.24384 6.33509,-5.4691 z m 80.53114,-60.08085 c 16.26132,-24.29372 34.4893,-57.39519 43.36257,-78.74516 24.95001,-60.03212 2.34502,-79.56171 -57.41749,-49.60586 -19.73598,9.89262 -37.98584,21.24627 -60.03538,37.34935 -25.73002,18.79099 -29.84499,21.95057 -29.84499,22.91575 0,0.55538 -1.00013,1.3303 -2.2225,1.72203 -1.22238,0.39172 -4.32928,2.57336 -6.90423,4.84807 -2.57495,2.27472 -5.7182,4.7832 -6.985,5.57441 -1.2668,0.7912 -1.89752,1.8637 -1.40161,2.38332 0.49591,0.51964 3.63916,-1.31463 6.985,-4.07613 21.53812,-17.77655 61.46891,-45.13963 82.91833,-56.82081 17.47303,-9.51567 34.28327,-16.48008 46.15225,-19.12068 8.88699,-1.97717 10.09775,-1.98192 16.68995,-0.0655 8.80406,2.55941 11.40916,5.09239 13.91472,13.52952 1.57431,5.3013 1.7094,8.23495 0.67687,14.69843 -3.67101,22.97959 -19.72954,58.10525 -46.53094,101.77955 -4.42357,7.20843 -8.04285,13.38579 -8.04285,13.72746 0,1.80655 3.24054,-1.9595 8.6853,-10.09373 z m -147.11529,-31.43433 c 0,-0.81688 -0.90625,-0.78499 -2.54,0.0894 -1.397,0.74764 -2.54,1.93087 -2.54,2.62937 0,0.81687 0.90625,0.78498 2.54,-0.0894 1.397,-0.74765 2.54,-1.93087 2.54,-2.62937 z m 115.55055,-5.34892 c 0.0102,-1.94331 -0.81723,-5.53576 -1.83985,-7.98321 -2.20522,-5.27782 -9.25807,-8.89144 -14.51359,-7.43619 -3.30564,0.91532 -3.30342,0.91984 1.13288,2.3149 8.80715,2.76952 12.68947,7.44954 12.71944,15.33288 0.0144,3.77023 0.34311,4.6141 1.25056,3.20991 0.67711,-1.04775 1.23986,-3.49498 1.25056,-5.43829 z m -107.93055,-0.27671 c 1.71614,-2.06783 1.30565,-2.32631 -1.80113,-1.13412 -1.10488,0.42397 -2.00887,1.28122 -2.00887,1.905 0,1.7567 2.05784,1.34034 3.81,-0.77088 z m 7.366,-5.969 c 2.39724,-2.39724 1.78131,-3.78308 -1.016,-2.286 -1.397,0.74765 -2.54,1.91075 -2.54,2.58468 0,1.68197 1.71979,1.53752 3.556,-0.29868 z m 7.62,-6.35 c 2.39724,-2.39724 1.78131,-3.78308 -1.016,-2.286 -1.397,0.74765 -2.54,1.91075 -2.54,2.58468 0,1.68197 1.71979,1.53752 3.556,-0.29868 z"
+         id="path8416"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#a69c8c;stroke-width:1.26999998"
+         d="M 51.435,285.01316 C 45.15376,282.6926 39.953,277.08029 38.18547,270.71519 29.72376,240.24359 64.4959,163.39819 133.18002,60.78 c 16.87373,-25.21036 16.4878,-24.80954 22.10326,-22.95626 2.43627,0.80403 5.40963,2.54482 6.60747,3.86843 1.19784,1.32359 2.9705,2.10238 3.93926,1.73063 2.80992,-1.07827 21.1655,5.79661 24.8635,9.31235 l 3.36852,3.20251 -2.48562,5.91368 c -1.36708,3.2525 -3.55587,8.19966 -4.86396,10.99366 -8.57116,18.30747 -13.26154,34.03811 -13.31676,44.66188 -0.034,6.55989 0.45637,8.36152 3.01991,11.09305 5.83121,6.21335 15.98949,5.63721 32.37321,-1.83607 4.26039,-1.94336 10.31792,-5.28708 13.46117,-7.4305 3.14325,-2.14342 6.20921,-3.90753 6.81326,-3.92024 3.03333,-0.0639 38.18769,-27.07231 38.70001,-29.73253 0.46402,-2.40947 -4.34446,-8.39059 -6.74554,-8.39059 -6.03461,0 -42.02265,-34.01297 -51.73422,-48.895 -8.67801,-13.29815 -16.98433,-28.85151 -19.45956,-36.43749 -1.21002,-3.70837 -2.69894,-7.37621 -3.30871,-8.15075 -2.08445,-2.64762 -6.22983,-16.29015 -10.17327,-33.48037 -3.54867,-15.4693 -3.94961,-19.33278 -4.02363,-38.77139 -0.14743,-38.72659 7.28775,-69.28421 25.77215,-105.92011 29.55499,-58.57769 80.62328,-103.72286 139.49634,-123.31699 23.78341,-7.91559 37.14243,-10.25486 63.10235,-11.04971 23.91549,-0.73226 37.33739,0.38831 55.62711,4.64421 l 9.91132,2.30629 22.70262,-20.08059 c 92.40099,-81.72915 165.84183,-131.73959 210.76143,-143.52053 20.78032,-5.45001 35.76148,-2.25583 41.9883,8.95244 2.65212,4.7738 3.17216,7.38825 3.15773,15.875 -0.04,23.53453 -13.04097,55.93195 -43.16273,107.55816 -20.77525,35.60711 -65.25436,99.25787 -99.95099,143.03253 -4.0502,5.10989 -7.364,9.44955 -7.364,9.64368 0,0.19415 1.14989,3.69518 2.55531,7.7801 7.10996,20.66549 10.96498,54.53869 8.9112,78.30077 -3.15903,36.54981 -9.23775,58.716 -24.47377,89.24428 -12.80333,25.65395 -29.11172,47.83815 -49.73337,67.65198 -39.39988,37.85645 -90.05749,61.48763 -142.52097,66.48439 -17.61204,1.67742 -34.74836,0.85233 -55.11141,-2.65352 -8.02008,-1.3808 -14.71004,-2.51432 -14.8666,-2.51896 -0.50429,-0.0149 -24.85023,22.25381 -33.37036,30.52319 -10.52421,10.21449 -43.38134,39.84482 -50.64176,45.66839 -3.04793,2.44475 -11.61705,9.60502 -19.04246,15.9117 -19.14333,16.25914 -47.10731,37.32995 -70.24449,52.92908 -42.08894,28.37648 -77.98417,42.07203 -94.38627,36.01238 z m 64.65416,-49.19004 c 12.73813,-3.43711 36.39178,-15.37167 54.72583,-27.61216 13.09684,-8.74395 17.61672,-12.02948 32.70453,-23.77319 9.50928,-7.40164 5.96982,-6.53869 -4.3541,1.06157 -35.84656,26.38946 -69.21793,43.69333 -86.96283,45.09231 -30.53146,2.40707 -34.2821,-23.6487 -11.06228,-76.84977 10.7522,-24.63534 10.94184,-25.10444 9.60016,-23.74688 -2.37353,2.40162 -13.840892,25.41647 -18.983683,38.1 -7.578767,18.69132 -10.726467,30.42244 -11.438157,42.62874 -0.84593,14.50852 2.00415,21.64521 10.114064,25.32593 7.07478,3.21092 13.114686,3.15758 25.656466,-0.22655 z m 59.80583,-94.27173 c -2.0955,-0.92354 -5.38163,-3.2651 -7.3025,-5.20347 -3.54245,-3.57468 -4.60618,-2.55186 -2.13948,2.05721 1.54603,2.8888 5.45745,4.74987 10.07698,4.79466 3.05611,0.0296 3.03234,-0.0321 -0.635,-1.6484 z M 415.75264,48.70718 c 2.00072,-1.04344 4.17651,-1.89718 4.83511,-1.89718 0.65858,0 7.12702,-3.94377 14.37429,-8.76391 13.17731,-8.76422 20.41987,-11.43737 26.10199,-9.63393 2.10083,0.66678 2.53075,1.53637 1.93436,3.91258 -0.42175,1.6804 -0.55799,3.05526 -0.30274,3.05526 0.83994,0 9.09529,-4.22954 9.74431,-4.99239 0.34925,-0.41052 3.20675,-2.39417 6.35,-4.40813 3.14325,-2.01397 6.57225,-4.4424 7.62,-5.39653 4.94882,-4.50655 20.17041,-15.86974 24.27213,-18.11956 4.85327,-2.66206 23.98787,-21.20337 23.98787,-23.24412 0,-0.66056 1.92175,-3.0673 4.27055,-5.3483 3.81963,-3.70936 14.47521,-21.04097 12.93612,-21.04097 -0.33971,0 1.65055,-3.23799 4.42284,-7.19554 2.77227,-3.95754 5.04049,-7.93794 5.04049,-8.84532 0,-0.90737 1.11841,-2.24832 2.48535,-2.97989 1.45554,-0.77899 2.83902,-3.29704 3.33893,-6.07718 0.86536,-4.81265 1.69751,-6.86764 6.65258,-16.42868 4.98025,-9.60963 3.09481,-13.62161 -4.46766,-9.50661 -2.53902,1.38156 -2.72853,1.26215 -2.13939,-1.34796 0.35552,-1.5751 1.1061,-6.00707 1.66795,-9.84881 0.56186,-3.84175 2.75536,-12.08482 4.87444,-18.31791 3.89732,-11.46366 3.90218,-13.03664 0.0566,-18.39452 -1.65535,-2.30634 -2.87026,-2.79202 -5.3975,-2.15773 -2.96237,0.74351 -3.26134,0.46706 -3.26134,-3.01565 0,-2.10881 -0.46298,-3.83419 -1.02883,-3.83419 -2.04145,0 -23.50606,23.01586 -75.3319,80.77612 -4.45184,4.96161 -17.52402,19.14337 -29.04927,31.51503 -11.52525,12.37165 -23.92771,26.19123 -27.56103,30.71017 -3.63331,4.51895 -8.63394,10.58544 -11.1125,13.48108 -2.47855,2.89563 -4.50646,6.05908 -4.50646,7.02986 0,1.99037 -6.4934,12.01399 -10.90117,16.82773 -1.59898,1.74625 -3.49228,4.72381 -4.20732,6.61679 -1.25964,3.33472 -1.19742,3.40275 2.00034,2.18697 4.36434,-1.65932 4.77151,-0.98937 2.76729,4.5532 -1.49934,4.14634 -1.44248,4.83375 0.51841,6.26759 1.21134,0.88576 2.20245,2.5476 2.20245,3.69296 0,2.58135 2.08873,2.63337 6.81267,0.16967 z M 331.10426,17.17236 C 345.06929,5.5135 387.61494,-35.52779 410.9706,-59.87 c 54.60009,-56.90638 106.72459,-123.14394 124.44669,-158.14141 l 5.99555,-11.83999 -3.05394,-3.64923 c -1.67966,-2.00709 -4.40164,-6.5472 -6.04883,-10.08915 -2.09935,-4.5142 -4.18195,-6.96548 -6.96463,-8.19756 -2.87567,-1.27324 -4.32535,-3.04259 -5.26011,-6.41996 -0.7097,-2.56426 -2.53526,-6.09335 -4.0568,-7.84242 -3.61533,-4.15596 -6.97122,-3.3355 -14.37857,3.51531 -3.14325,2.90708 -8.28675,6.6445 -11.43,8.30537 -8.63883,4.5647 -13.84445,9.40201 -28.06997,26.08405 -16.80085,19.70208 -25.26228,29.01238 -25.82601,28.41696 -0.24837,-0.26233 4.92509,-9.33521 11.49658,-20.16196 10.90097,-17.95971 16.9994,-31.67586 16.9994,-38.23379 0,-2.63391 -3.12053,-3.27925 -4.56391,-0.94382 -1.14336,1.85002 -4.32609,-2.20746 -4.32609,-5.51513 0,-2.67806 -4.84432,-3.02184 -13.72342,-0.97389 -4.35463,1.0044 -6.02888,0.86874 -8.22008,-0.66603 -1.51342,-1.06005 -4.0484,-1.92734 -5.63331,-1.92734 -1.58491,0 -3.59547,-1.143 -4.46791,-2.54 -2.32469,-3.72241 -14.80694,-3.6612 -18.20104,0.0893 -1.85321,2.04777 -3.30111,2.45635 -6.54684,1.84745 -2.55064,-0.47852 -4.16739,-0.2668 -4.16739,0.54572 0,0.73015 -1.05236,1.32755 -2.33858,1.32755 -1.93773,0 -2.37812,-1.11735 -2.5693,-6.51869 -0.29757,-8.40756 -0.83341,-9.10132 -5.26112,-6.81165 -2.02117,1.04518 -4.86038,1.90034 -6.30936,1.90034 -1.6299,0 -3.0873,1.19093 -3.8219,3.12308 -0.65306,1.7177 -3.35355,4.24408 -6.00107,5.61421 -5.10437,2.64158 -12.92131,10.36643 -25.21474,24.91771 -4.13083,4.8895 -11.53488,13.462 -16.45345,19.05 -4.91858,5.588 -11.2109,13.01749 -13.98294,16.51 -2.77203,3.4925 -6.84784,8.35024 -9.05735,10.79499 -2.2095,2.44475 -8.41881,9.5885 -13.79847,15.875 -5.37966,6.28651 -11.29274,13.14451 -13.14019,15.24 -8.95909,10.16195 -19.17939,23.19807 -20.41582,26.04061 -0.75823,1.74317 -3.02719,6.19245 -5.04212,9.88729 -3.83144,7.02582 -5.83606,12.96711 -4.37515,12.96711 0.47125,0 2.62526,-1.31871 4.78669,-2.93045 2.16143,-1.61175 7.35888,-4.83395 11.54988,-7.16047 4.191,-2.32651 8.97304,-5.28627 10.62677,-6.57725 1.65372,-1.29097 3.5111,-2.03553 4.1275,-1.65458 2.10175,1.29895 1.12714,4.43202 -1.73677,5.58317 -1.57163,0.63171 -2.04788,1.1848 -1.05833,1.22907 0.98953,0.0443 1.43197,0.4477 0.98318,0.89648 -0.44878,0.44879 -1.62029,0.14845 -2.60335,-0.66741 -1.3944,-1.15726 -1.95176,-1.05502 -2.53506,0.46505 -1.22283,3.18663 1.37828,8.27639 4.22963,8.27639 3.00131,0 3.43194,1.93803 0.66643,2.99927 -1.04775,0.40205 -1.905,1.66734 -1.905,2.81174 0,1.34155 -0.687,1.81711 -1.93381,1.33867 -2.38451,-0.91503 -6.95619,3.438 -6.95619,6.62348 0,1.28874 1.143,3.37756 2.54,4.64183 1.78769,1.61785 2.54,3.91157 2.54,7.74425 0,3.20547 1.06934,7.25797 2.59952,9.85143 1.42973,2.42322 2.08288,4.72516 1.45142,5.1154 -0.63144,0.39026 -1.14809,3.90221 -1.14809,7.80432 0,3.90211 -0.65314,7.74789 -1.45142,8.54617 -0.79828,0.79829 -1.45143,1.84201 -1.45143,2.31938 0,1.85116 7.19461,2.11525 11.26727,0.41357 2.35561,-0.98423 4.44449,-1.78952 4.64198,-1.78952 0.19747,0 0.0524,2.286 -0.32233,5.08 -0.38559,2.87485 -0.15165,5.08 0.53895,5.08 0.67117,0 4.31829,-2.26141 8.10472,-5.02535 3.78642,-2.76395 8.59891,-5.81849 10.69441,-6.78787 2.0955,-0.96936 6.95325,-4.59344 10.79499,-8.05349 11.01701,-9.92239 26.67,-22.31401 26.67,-21.11318 0,1.59585 -3.28812,5.71945 -13.81071,17.31986 -25.12136,27.69452 -38.25928,45.53629 -38.25928,51.9575 0,1.79188 -2.04319,5.9347 -4.54042,9.20624 C 271.90754,4.09519 270.82364,11.25 282.35137,11.25 c 4.32127,0 5.65813,0.76656 10.06185,5.76945 4.80501,5.45878 5.36071,5.73282 10.32011,5.08914 3.73944,-0.48534 6.51554,0.005 9.68665,1.71309 2.44475,1.31637 5.13768,2.45526 5.98429,2.53086 0.8466,0.0756 6.5616,-4.05548 12.69999,-9.18018 z m 239.66985,-261.20654 c 3.19321,-4.1988 5.80584,-8.15544 5.80584,-8.79255 0,-0.63711 1.33112,-2.77386 2.95805,-4.74832 3.81432,-4.62916 22.56604,-42.85758 23.87591,-48.67494 0.55047,-2.44475 2.09018,-8.50106 3.42156,-13.45846 2.5655,-9.55261 2.17003,-18.86204 -0.96153,-22.63535 -6.52888,-7.86681 -29.66163,-0.75616 -62.94898,19.34959 -26.28231,15.87465 -29.94693,18.34241 -29.45349,19.83401 0.24658,0.74537 5.54573,5.20859 11.7759,9.91828 14.21854,10.7485 28.86537,24.7686 35.99081,34.45078 4.95679,6.73541 5.41547,7.96506 4.7586,12.75742 -0.39982,2.91696 -1.10081,6.27785 -1.55776,7.46864 -1.52011,3.96135 0.46936,2.24384 6.33509,-5.4691 z m 80.53114,-60.08085 c 16.26132,-24.29372 34.4893,-57.39519 43.36257,-78.74516 24.95001,-60.03212 2.34502,-79.56171 -57.41749,-49.60586 -19.73598,9.89262 -37.98584,21.24627 -60.03538,37.34935 -25.73002,18.79099 -29.84499,21.95057 -29.84499,22.91575 0,0.55538 -1.00013,1.3303 -2.2225,1.72203 -1.22238,0.39172 -4.32928,2.57336 -6.90423,4.84807 -2.57495,2.27472 -5.7182,4.7832 -6.985,5.57441 -1.2668,0.7912 -1.89752,1.8637 -1.40161,2.38332 0.49591,0.51964 3.63916,-1.31463 6.985,-4.07613 21.53812,-17.77655 61.46891,-45.13963 82.91833,-56.82081 17.47303,-9.51567 34.28327,-16.48008 46.15225,-19.12068 8.88699,-1.97717 10.09775,-1.98192 16.68995,-0.0655 8.80406,2.55941 11.40916,5.09239 13.91472,13.52952 1.57431,5.3013 1.7094,8.23495 0.67687,14.69843 -3.67101,22.97959 -19.72954,58.10525 -46.53094,101.77955 -4.42357,7.20843 -8.04285,13.38579 -8.04285,13.72746 0,1.80655 3.24054,-1.9595 8.6853,-10.09373 z m -147.11529,-31.43433 c 0,-0.81688 -0.90625,-0.78499 -2.54,0.0894 -1.397,0.74764 -2.54,1.93087 -2.54,2.62937 0,0.81687 0.90625,0.78498 2.54,-0.0894 1.397,-0.74765 2.54,-1.93087 2.54,-2.62937 z m 115.55055,-5.34892 c 0.0102,-1.94331 -0.81723,-5.53576 -1.83985,-7.98321 -2.20522,-5.27782 -9.25807,-8.89144 -14.51359,-7.43619 -3.30564,0.91532 -3.30342,0.91984 1.13288,2.3149 8.80715,2.76952 12.68947,7.44954 12.71944,15.33288 0.0144,3.77023 0.34311,4.6141 1.25056,3.20991 0.67711,-1.04775 1.23986,-3.49498 1.25056,-5.43829 z m -107.93055,-0.27671 c 1.71614,-2.06783 1.30565,-2.32631 -1.80113,-1.13412 -1.10488,0.42397 -2.00887,1.28122 -2.00887,1.905 0,1.7567 2.05784,1.34034 3.81,-0.77088 z m 7.366,-5.969 c 2.39724,-2.39724 1.78131,-3.78308 -1.016,-2.286 -1.397,0.74765 -2.54,1.91075 -2.54,2.58468 0,1.68197 1.71979,1.53752 3.556,-0.29868 z m 7.62,-6.35 c 2.39724,-2.39724 1.78131,-3.78308 -1.016,-2.286 -1.397,0.74765 -2.54,1.91075 -2.54,2.58468 0,1.68197 1.71979,1.53752 3.556,-0.29868 z"
+         id="path8414"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#b39c75;stroke-width:1.26999998"
+         d="m 52.18926,285.27542 c -7.07558,-2.3825 -12.3804,-8.71833 -14.2338,-17.0002 -2.7948,-12.48848 2.56992,-36.52221 14.71078,-65.90367 5.52445,-13.36943 24.22387,-51.19555 26.67601,-53.96155 0.92886,-1.04775 4.89299,-7.7922 8.80918,-14.98767 9.02085,-16.57461 31.45621,-52.30371 48.66101,-77.4944 9.54923,-13.98166 13.82419,-19.25165 15.19118,-18.72709 1.04629,0.4015 3.29307,1.07904 4.99285,1.50566 1.69978,0.42662 3.66705,1.85293 4.3717,3.16959 0.87165,1.62869 2.66931,2.39391 5.62372,2.39391 5.61784,0 20.64094,5.4584 23.84693,8.6644 2.47021,2.4702 2.46957,2.53539 -0.0912,9.29413 -1.41838,3.74356 -4.13293,10.23547 -6.03233,14.42647 -12.96848,28.61478 -15.34105,48.43841 -6.53549,54.60607 6.06305,4.24672 20.23851,1.45905 36.22021,-7.12289 16.31935,-8.76324 18.24184,-9.89454 22.79034,-13.41114 5.02293,-3.8834 7.45057,-5.6114 15.05138,-10.71364 2.7133,-1.82138 7.7357,-5.85089 11.16086,-8.95448 6.13396,-5.55805 6.19631,-5.69057 4.1473,-8.81775 -1.14414,-1.74617 -4.2323,-4.12616 -6.86256,-5.28886 -12.3017,-5.43787 -38.36242,-30.1934 -50.93748,-48.3864 -7.41092,-10.72167 -20.51987,-35.19641 -20.51987,-38.31099 0,-1.07214 -1.46395,-4.8383 -3.2532,-8.36923 -3.73326,-7.36724 -11.49425,-37.45131 -12.57395,-48.74069 -1.75355,-18.33507 -1.81552,-22.51499 -0.5742,-38.735 2.11767,-27.67115 7.88354,-50.16461 19.58958,-76.4215 23.64615,-53.03886 62.22531,-94.74111 112.38176,-121.47932 30.73622,-16.38536 60.66152,-24.21368 96.59308,-25.26827 23.80648,-0.69872 37.98232,0.55157 54.91273,4.84322 l 9.90082,2.50974 21.84918,-19.44055 c 63.97322,-56.92095 119.09297,-98.55512 162.69474,-122.8899 36.31002,-20.26516 61.84053,-27.61359 78.5471,-22.60818 7.01565,2.10194 11.55865,7.13158 14.29265,15.82366 2.02598,6.44111 2.14486,8.63819 0.9166,16.9386 -4.17149,28.19027 -20.15667,63.72874 -50.84645,113.04251 -24.77133,39.80374 -64.74535,96.34692 -92.73873,131.17874 l -6.93067,8.62376 3.35665,11.06124 c 16.18822,53.34561 10.37924,112.12276 -16.35098,165.44474 -5.30616,10.58485 -9.69843,17.72759 -21.04069,34.21657 -6.67267,9.70049 -17.84515,22.68903 -27.35332,31.79954 -40.26575,38.58172 -91.69934,62.52522 -144.61772,67.32284 -18.56824,1.6834 -32.16674,0.99407 -54.1949,-2.74725 l -15.06565,-2.55879 -12.35972,11.07496 c -6.79785,6.09124 -15.50297,14.29129 -19.34472,18.22233 -7.79514,7.97634 -44.47724,41.08944 -53.34,48.15027 -3.14325,2.50418 -11.14425,9.18473 -17.78,14.84567 -22.3369,19.05555 -52.2134,41.14115 -81.16706,60.00111 -32.49132,21.16435 -68.52154,33.90125 -82.54366,29.17968 z M 123.82499,233.67 c 23.10865,-8.1142 57.80323,-30.32047 97.15499,-62.18405 22.56137,-18.26821 37.12227,-31.23353 35.6343,-31.72951 -0.78092,-0.26031 -2.97446,1.03892 -4.87457,2.88717 -3.18832,3.1013 -10.04458,8.71846 -26.32343,21.56609 -3.48771,2.75259 -7.19855,5.74995 -8.2463,6.66081 -13.29959,11.56204 -47.99087,34.9144 -67.03007,45.12113 -17.78679,9.53533 -26.66216,12.61665 -38.4474,13.34804 -12.993719,0.80639 -17.708329,-1.01685 -21.328958,-8.24835 -3.365372,-6.72168 -4.114662,-13.40939 -2.657152,-23.71631 1.52305,-10.77039 2.251143,-13.17581 10.400382,-34.36002 6.769768,-17.59822 15.442238,-35.61782 26.626008,-55.32331 3.79053,-6.67882 7.29316,-13.18903 7.78361,-14.46714 2.41471,-6.29264 -3.61599,2.081 -11.25453,15.62692 -4.58813,8.13645 -9.21483,16.22228 -10.28155,17.96853 -3.95035,6.4668 -17.453543,35.27101 -21.433197,45.72 -7.644493,20.0714 -8.748033,25.09956 -8.827543,40.22168 -0.0653,12.41633 0.25364,14.63507 2.55569,17.78 6.795516,9.28362 20.09732,10.30983 40.54972,3.12832 z m 55.88,-91.61 c -0.4317,-0.6985 -2.12073,-1.27 -3.75339,-1.27 -1.81898,0 -4.52513,-1.77292 -6.98835,-4.57836 -2.21092,-2.51811 -4.28636,-4.31187 -4.61209,-3.98614 -1.1767,1.17668 1.92383,7.08394 4.76125,9.07135 3.17626,2.22473 11.88359,2.85206 10.59258,0.76315 z m 85.94249,-8.45184 c 1.27738,-1.156 2.3225,-2.60485 2.3225,-3.21967 0,-1.69545 -4.67548,1.16551 -5.52573,3.38123 -0.9705,2.52909 0.30096,2.46496 3.20323,-0.16156 z m 11.2125,-9.96316 c 1.28705,-1.55081 1.22316,-1.905 -0.34363,-1.905 -1.05854,0 -2.25357,0.85725 -2.65564,1.905 -0.40206,1.04775 -0.24742,1.905 0.34362,1.905 0.59105,0 1.78609,-0.85725 2.65565,-1.905 z M 138.47394,83.55383 c 1.40122,-2.7466 2.2709,-4.99383 1.93264,-4.99383 -0.85936,0 -7.05659,9.24744 -7.05659,10.52976 0,2.16422 2.66395,-0.71391 5.12395,-5.53593 z m 277.2787,-34.84665 c 2.00072,-1.04344 4.17651,-1.89718 4.83511,-1.89718 0.65858,0 7.12702,-3.94377 14.37429,-8.76391 13.17731,-8.76422 20.41987,-11.43737 26.10199,-9.63393 2.10083,0.66678 2.53075,1.53637 1.93436,3.91258 -0.42175,1.6804 -0.55799,3.05526 -0.30274,3.05526 0.83994,0 9.09529,-4.22954 9.74431,-4.99239 0.34925,-0.41052 3.20675,-2.39417 6.35,-4.40813 3.14325,-2.01397 6.57225,-4.4424 7.62,-5.39653 4.94882,-4.50655 20.17041,-15.86974 24.27213,-18.11956 4.85327,-2.66206 23.98787,-21.20337 23.98787,-23.24412 0,-0.66056 1.92175,-3.0673 4.27055,-5.3483 3.93153,-3.81803 14.45516,-21.04097 12.85655,-21.04097 -0.38347,0 1.60681,-3.11885 4.42284,-6.93077 2.81603,-3.81193 5.12006,-7.79231 5.12006,-8.84532 0,-1.05299 1.143,-2.52626 2.54,-3.27391 1.397,-0.74764 2.55271,-2.05362 2.56824,-2.90217 0.0155,-0.84855 1.14582,-2.97158 2.51175,-4.71783 1.36594,-1.74625 2.49623,-5.10778 2.51176,-7.4701 0.0155,-2.3623 1.20532,-6.19964 2.64397,-8.52742 1.43865,-2.32779 3.11284,-5.96565 3.72042,-8.08415 1.0111,-3.5255 0.90144,-3.74303 -1.29428,-2.56792 -1.31943,0.70615 -2.85075,1.15476 -3.40292,0.99693 -0.55217,-0.15782 -2.25755,0.40879 -3.78974,1.25915 -2.54596,1.41302 -2.73015,1.29958 -2.13939,-1.31767 0.35552,-1.5751 1.1061,-6.00707 1.66795,-9.84881 0.56186,-3.84175 2.75536,-12.08482 4.87444,-18.31791 3.89732,-11.46366 3.90218,-13.03664 0.0566,-18.39452 -1.65535,-2.30634 -2.87026,-2.79202 -5.3975,-2.15773 -2.96237,0.74351 -3.26134,0.46706 -3.26134,-3.01565 0,-2.10881 -0.46298,-3.83419 -1.02883,-3.83419 -2.04145,0 -23.50606,23.01586 -75.3319,80.77612 -4.45184,4.96161 -17.52402,19.14337 -29.04927,31.51503 -11.52525,12.37165 -23.92771,26.19123 -27.56103,30.71017 -3.63331,4.51895 -8.63394,10.58544 -11.1125,13.48108 -2.47855,2.89563 -4.50646,6.05908 -4.50646,7.02986 0,1.99037 -6.4934,12.01399 -10.90117,16.82773 -1.59898,1.74625 -3.49228,4.72381 -4.20732,6.61679 -1.25964,3.33472 -1.19742,3.40275 2.00034,2.18697 4.36434,-1.65932 4.77151,-0.98937 2.76729,4.5532 -1.49934,4.14634 -1.44248,4.83375 0.51841,6.26759 1.21134,0.88576 2.20245,2.5476 2.20245,3.69296 0,2.58135 2.08873,2.63337 6.81267,0.16967 z M 331.10426,17.17236 C 345.06929,5.5135 387.61494,-35.52779 410.9706,-59.87 c 54.60009,-56.90638 106.72459,-123.14394 124.44669,-158.14141 l 5.99555,-11.83999 -3.05394,-3.64923 c -1.67966,-2.00709 -4.40164,-6.5472 -6.04883,-10.08915 -2.09935,-4.5142 -4.18195,-6.96548 -6.96463,-8.19756 -2.87567,-1.27324 -4.32535,-3.04259 -5.26011,-6.41996 -0.7097,-2.56426 -2.53526,-6.09335 -4.0568,-7.84242 -3.61533,-4.15596 -6.97122,-3.3355 -14.37857,3.51531 -3.14325,2.90708 -8.28675,6.6445 -11.43,8.30537 -8.63883,4.5647 -13.84445,9.40201 -28.06997,26.08405 -16.80085,19.70208 -25.26228,29.01238 -25.82601,28.41696 -0.24837,-0.26233 4.92509,-9.33521 11.49658,-20.16196 10.90097,-17.95971 16.9994,-31.67586 16.9994,-38.23379 0,-2.63391 -3.12053,-3.27925 -4.56391,-0.94382 -1.14336,1.85002 -4.32609,-2.20746 -4.32609,-5.51513 0,-2.67806 -4.84432,-3.02184 -13.72342,-0.97389 -4.35463,1.0044 -6.02888,0.86874 -8.22008,-0.66603 -1.51342,-1.06005 -4.0484,-1.92734 -5.63331,-1.92734 -1.58491,0 -3.59547,-1.143 -4.46791,-2.54 -2.32469,-3.72241 -14.80694,-3.6612 -18.20104,0.0893 -1.85321,2.04777 -3.30111,2.45635 -6.54684,1.84745 -2.55064,-0.47852 -4.16739,-0.2668 -4.16739,0.54572 0,0.73015 -1.05236,1.32755 -2.33858,1.32755 -1.93773,0 -2.37812,-1.11735 -2.5693,-6.51869 -0.29757,-8.40756 -0.83341,-9.10132 -5.26112,-6.81165 -2.02117,1.04518 -4.86038,1.90034 -6.30936,1.90034 -1.6299,0 -3.0873,1.19093 -3.8219,3.12308 -0.65306,1.7177 -3.35355,4.24408 -6.00107,5.61421 -5.10437,2.64158 -12.92131,10.36643 -25.21474,24.91771 -4.13083,4.8895 -11.53488,13.462 -16.45345,19.05 -4.91858,5.588 -11.2109,13.01749 -13.98294,16.51 -2.77203,3.4925 -6.84784,8.35024 -9.05735,10.79499 -2.2095,2.44475 -8.41881,9.5885 -13.79847,15.875 -5.37966,6.28651 -11.29274,13.14451 -13.14019,15.24 -8.95909,10.16195 -19.17939,23.19807 -20.41582,26.04061 -0.75823,1.74317 -3.02719,6.19245 -5.04212,9.88729 -3.83144,7.02582 -5.83606,12.96711 -4.37515,12.96711 0.47125,0 2.62526,-1.31871 4.78669,-2.93045 2.16143,-1.61175 7.35888,-4.83395 11.54988,-7.16047 4.191,-2.32651 8.97304,-5.28627 10.62677,-6.57725 1.65372,-1.29097 3.5111,-2.03553 4.1275,-1.65458 2.10175,1.29895 1.12714,4.43202 -1.73677,5.58317 -1.57163,0.63171 -2.04788,1.1848 -1.05833,1.22907 0.98953,0.0443 1.43197,0.4477 0.98318,0.89648 -0.44878,0.44879 -1.62029,0.14845 -2.60335,-0.66741 -1.3944,-1.15726 -1.95176,-1.05502 -2.53506,0.46505 -1.22283,3.18663 1.37828,8.27639 4.22963,8.27639 3.00131,0 3.43194,1.93803 0.66643,2.99927 -1.04775,0.40205 -1.905,1.66734 -1.905,2.81174 0,1.34155 -0.687,1.81711 -1.93381,1.33867 -2.38451,-0.91503 -6.95619,3.438 -6.95619,6.62348 0,1.28874 1.143,3.37756 2.54,4.64183 1.78769,1.61785 2.54,3.91157 2.54,7.74425 0,3.20547 1.06934,7.25797 2.59952,9.85143 1.42973,2.42322 2.08288,4.72516 1.45142,5.1154 -0.63144,0.39026 -1.14809,3.90221 -1.14809,7.80432 0,3.90211 -0.65314,7.74789 -1.45142,8.54617 -0.79828,0.79829 -1.45143,1.84201 -1.45143,2.31938 0,1.85116 7.19461,2.11525 11.26727,0.41357 2.35561,-0.98423 4.44449,-1.78952 4.64198,-1.78952 0.19747,0 0.0524,2.286 -0.32233,5.08 -0.38559,2.87485 -0.15165,5.08 0.53895,5.08 0.67117,0 4.31829,-2.26141 8.10472,-5.02535 3.78642,-2.76395 8.59891,-5.81849 10.69441,-6.78787 2.0955,-0.96936 6.95325,-4.59344 10.79499,-8.05349 11.01701,-9.92239 26.67,-22.31401 26.67,-21.11318 0,1.59585 -3.28812,5.71945 -13.81071,17.31986 -25.12136,27.69452 -38.25928,45.53629 -38.25928,51.9575 0,1.79188 -2.04319,5.9347 -4.54042,9.20624 C 271.90754,4.09519 270.82364,11.25 282.35137,11.25 c 4.32127,0 5.65813,0.76656 10.06185,5.76945 4.80501,5.45878 5.36071,5.73282 10.32011,5.08914 3.73944,-0.48534 6.51554,0.005 9.68665,1.71309 2.44475,1.31637 5.13768,2.45526 5.98429,2.53086 0.8466,0.0756 6.5616,-4.05548 12.69999,-9.18018 z m 238.25105,-257.74622 c 6.55693,-9.82731 9.60111,-14.21463 12.175,-17.54684 1.40439,-1.81813 3.41692,-5.81199 4.47229,-8.87524 1.05539,-3.06326 2.51029,-5.76669 3.23312,-6.00764 0.72282,-0.24094 1.31423,-1.46348 1.31423,-2.71674 0,-1.25327 0.76476,-2.91336 1.69945,-3.68909 0.93471,-0.77572 2.42798,-3.84608 3.31839,-6.823 0.89041,-2.97693 2.70206,-6.93611 4.02591,-8.7982 1.32385,-1.8621 2.7463,-5.2911 3.16099,-7.62 0.41469,-2.32891 1.92848,-7.37763 3.36399,-11.21938 1.76847,-4.73291 2.61116,-10.02089 2.61361,-16.40097 0.004,-8.67028 -0.26952,-9.65092 -3.44518,-12.3825 -8.6896,-7.47447 -30.23853,-0.72832 -63.97378,20.02774 -24.80535,15.26181 -29.44951,18.25084 -29.47701,18.97172 -0.0145,0.38005 6.11726,5.32035 13.62614,10.97845 15.9623,12.02793 26.47717,22.01691 35.06005,33.30651 6.50142,8.55174 6.43756,8.22371 3.91558,20.11155 -1.18835,5.60152 0.62548,5.11595 4.91722,-1.31637 z m 47.5736,-13.36801 c 0.76194,-2.28582 -1.86309,-1.80549 -2.74894,0.503 -0.47464,1.23686 -0.19081,1.81747 0.73724,1.50812 0.82995,-0.27664 1.73522,-1.18166 2.0117,-2.01112 z m 15.4933,-22.21531 c 5.57662,-7.81979 11.81898,-16.50381 13.87193,-19.29781 13.13187,-17.87215 37.99566,-61.93308 47.05806,-83.39121 8.66909,-20.52684 11.84285,-33.38765 11.28898,-45.74556 -0.4906,-10.94647 -2.62184,-15.05787 -10.08399,-19.45323 -3.37749,-1.98941 -6.37935,-2.50289 -13.84829,-2.36878 -24.92324,0.44749 -62.72624,20.21462 -121.90894,63.74599 -5.23875,3.85332 -10.0965,7.62181 -10.795,8.37441 -0.6985,0.75261 -5.55625,4.61767 -10.795,8.58903 -5.23875,3.97135 -11.811,9.26613 -14.605,11.76617 -7.47104,6.68497 -11.10244,9.78044 -16.46276,14.03318 -4.50862,3.57702 -8.47867,7.1027 -17.25296,15.32189 -2.02412,1.89606 -3.38479,3.92542 -3.0237,4.50965 0.36108,0.58425 11.43188,-7.96761 24.60176,-19.00415 66.48812,-55.7179 121.85666,-90.76742 154.0181,-97.49688 14.37861,-3.00858 25.70713,-0.37336 29.39788,6.83849 10.31507,20.15606 -7.44832,64.83222 -55.56384,139.7469 -18.95639,29.51461 -19.28504,30.05742 -17.54946,28.98477 0.83211,-0.51427 6.07561,-7.33305 11.65223,-15.15286 z m -15.60471,-53.19094 c -0.40541,-1.01311 -0.70635,-0.71219 -0.76728,0.7673 -0.0551,1.33878 0.24502,2.08888 0.66703,1.66687 0.42201,-0.42202 0.46712,-1.51739 0.1002,-2.43417 z m 2.84845,-11.82687 c 0.17407,-10.46408 -9.34569,-18.13464 -18.4881,-14.89677 l -3.6429,1.29017 5.59436,0.96459 c 3.21369,0.55411 7.21504,2.41268 9.40282,4.36746 3.20931,2.86752 3.90696,4.58525 4.43453,10.9187 0.69317,8.32129 2.54706,6.50525 2.69929,-2.64415 z"
+         id="path8412"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#b79969;stroke-width:1.26999998"
+         d="m 52.18926,285.27542 c -7.07558,-2.3825 -12.3804,-8.71833 -14.2338,-17.0002 -2.7948,-12.48848 2.56992,-36.52221 14.71078,-65.90367 5.52445,-13.36943 24.22387,-51.19555 26.67601,-53.96155 0.92886,-1.04775 4.89299,-7.7922 8.80918,-14.98767 9.02085,-16.57461 31.45621,-52.30371 48.66101,-77.4944 9.54923,-13.98166 13.82419,-19.25165 15.19118,-18.72709 1.04629,0.4015 3.29307,1.07904 4.99285,1.50566 1.69978,0.42662 3.66705,1.85293 4.3717,3.16959 0.87165,1.62869 2.66931,2.39391 5.62372,2.39391 5.61784,0 20.64094,5.4584 23.84693,8.6644 2.47021,2.4702 2.46957,2.53539 -0.0912,9.29413 -1.41838,3.74356 -4.13293,10.23547 -6.03233,14.42647 -12.96848,28.61478 -15.34105,48.43841 -6.53549,54.60607 6.06305,4.24672 20.23851,1.45905 36.22021,-7.12289 16.31935,-8.76324 18.24184,-9.89454 22.79034,-13.41114 5.02293,-3.8834 7.45057,-5.6114 15.05138,-10.71364 2.7133,-1.82138 7.7357,-5.85089 11.16086,-8.95448 6.13396,-5.55805 6.19631,-5.69057 4.1473,-8.81775 -1.14414,-1.74617 -4.2323,-4.12616 -6.86256,-5.28886 -12.3017,-5.43787 -38.36242,-30.1934 -50.93748,-48.3864 -7.41092,-10.72167 -20.51987,-35.19641 -20.51987,-38.31099 0,-1.07214 -1.46395,-4.8383 -3.2532,-8.36923 -3.73326,-7.36724 -11.49425,-37.45131 -12.57395,-48.74069 -1.75355,-18.33507 -1.81552,-22.51499 -0.5742,-38.735 2.11767,-27.67115 7.88354,-50.16461 19.58958,-76.4215 23.64615,-53.03886 62.22531,-94.74111 112.38176,-121.47932 30.73622,-16.38536 60.66152,-24.21368 96.59308,-25.26827 23.80648,-0.69872 37.98232,0.55157 54.91273,4.84322 l 9.90082,2.50974 21.84918,-19.44055 c 63.97322,-56.92095 119.09297,-98.55512 162.69474,-122.8899 36.31002,-20.26516 61.84053,-27.61359 78.5471,-22.60818 7.01565,2.10194 11.55865,7.13158 14.29265,15.82366 2.02598,6.44111 2.14486,8.63819 0.9166,16.9386 -4.17149,28.19027 -20.15667,63.72874 -50.84645,113.04251 -24.77133,39.80374 -64.74535,96.34692 -92.73873,131.17874 l -6.93067,8.62376 3.35665,11.06124 c 16.18822,53.34561 10.37924,112.12276 -16.35098,165.44474 -5.30616,10.58485 -9.69843,17.72759 -21.04069,34.21657 -6.67267,9.70049 -17.84515,22.68903 -27.35332,31.79954 -40.26575,38.58172 -91.69934,62.52522 -144.61772,67.32284 -18.56824,1.6834 -32.16674,0.99407 -54.1949,-2.74725 l -15.06565,-2.55879 -12.35972,11.07496 c -6.79785,6.09124 -15.50297,14.29129 -19.34472,18.22233 -7.79514,7.97634 -44.47724,41.08944 -53.34,48.15027 -3.14325,2.50418 -11.14425,9.18473 -17.78,14.84567 -22.3369,19.05555 -52.2134,41.14115 -81.16706,60.00111 -32.49132,21.16435 -68.52154,33.90125 -82.54366,29.17968 z M 123.82499,233.67 c 23.10865,-8.1142 57.80323,-30.32047 97.15499,-62.18405 22.56137,-18.26821 37.12227,-31.23353 35.6343,-31.72951 -0.78092,-0.26031 -2.97446,1.03892 -4.87457,2.88717 -3.18832,3.1013 -10.04458,8.71846 -26.32343,21.56609 -3.48771,2.75259 -7.19855,5.74995 -8.2463,6.66081 -13.29959,11.56204 -47.99087,34.9144 -67.03007,45.12113 -17.78679,9.53533 -26.66216,12.61665 -38.4474,13.34804 -12.993719,0.80639 -17.708329,-1.01685 -21.328958,-8.24835 -3.365372,-6.72168 -4.114662,-13.40939 -2.657152,-23.71631 1.52305,-10.77039 2.251143,-13.17581 10.400382,-34.36002 6.769768,-17.59822 15.442238,-35.61782 26.626008,-55.32331 3.79053,-6.67882 7.29316,-13.18903 7.78361,-14.46714 2.41471,-6.29264 -3.61599,2.081 -11.25453,15.62692 -4.58813,8.13645 -9.21483,16.22228 -10.28155,17.96853 -3.95035,6.4668 -17.453543,35.27101 -21.433197,45.72 -7.644493,20.0714 -8.748033,25.09956 -8.827543,40.22168 -0.0653,12.41633 0.25364,14.63507 2.55569,17.78 6.795516,9.28362 20.09732,10.30983 40.54972,3.12832 z m 55.88,-91.61 c -0.4317,-0.6985 -2.12073,-1.27 -3.75339,-1.27 -1.81898,0 -4.52513,-1.77292 -6.98835,-4.57836 -2.21092,-2.51811 -4.28636,-4.31187 -4.61209,-3.98614 -1.1767,1.17668 1.92383,7.08394 4.76125,9.07135 3.17626,2.22473 11.88359,2.85206 10.59258,0.76315 z m 85.94249,-8.45184 c 1.27738,-1.156 2.3225,-2.60485 2.3225,-3.21967 0,-1.69545 -4.67548,1.16551 -5.52573,3.38123 -0.9705,2.52909 0.30096,2.46496 3.20323,-0.16156 z m 11.2125,-9.96316 c 1.28705,-1.55081 1.22316,-1.905 -0.34363,-1.905 -1.05854,0 -2.25357,0.85725 -2.65564,1.905 -0.40206,1.04775 -0.24742,1.905 0.34362,1.905 0.59105,0 1.78609,-0.85725 2.65565,-1.905 z M 138.47394,83.55383 c 1.40122,-2.7466 2.2709,-4.99383 1.93264,-4.99383 -0.85936,0 -7.05659,9.24744 -7.05659,10.52976 0,2.16422 2.66395,-0.71391 5.12395,-5.53593 z M 405.76497,58.2178 c 2.794,-1.25375 9.55499,-4.37695 15.02444,-6.94043 5.46944,-2.56348 10.63688,-4.39517 11.4832,-4.0704 0.84631,0.32476 2.62417,-0.2032 3.95079,-1.17324 3.66691,-2.68131 7.64184,-2.14772 9.47175,1.27148 1.59286,2.97629 1.67099,2.98862 4.0246,0.635 1.32012,-1.32011 3.63815,-2.40021 5.15117,-2.40021 1.51303,0 3.65616,-0.85424 4.7625,-1.89832 4.44913,-4.19869 9.7852,-7.96221 12.48904,-8.80849 1.57163,-0.49189 2.8575,-1.38724 2.8575,-1.98967 0,-0.60244 2.6183,-2.40297 5.81844,-4.0012 3.20015,-1.59823 6.37205,-3.94029 7.04868,-5.20458 0.8792,-1.6428 2.47708,-2.21476 5.59982,-2.00445 2.79063,0.18794 4.94748,-0.44491 5.96883,-1.75135 0.87957,-1.12509 6.45698,-6.13251 12.39423,-11.12761 13.41176,-11.28351 21.5784,-19.1885 21.62762,-20.93468 0.0207,-0.73405 3.02106,-4.55114 6.66749,-8.48241 3.64644,-3.93127 8.58081,-10.50352 10.96526,-14.605 2.38445,-4.10148 5.23991,-8.88599 6.34544,-10.63224 4.24655,-6.70763 5.2759,-10.20836 3.23224,-10.99258 -1.46477,-0.56208 -0.88972,-1.82532 2.57837,-5.66408 2.45497,-2.71736 4.46358,-5.97278 4.46358,-7.23426 0,-1.26149 0.83907,-3.40296 1.86461,-4.75884 1.02552,-1.35588 1.88277,-3.90244 1.905,-5.65904 0.0222,-1.75659 1.12679,-5.22082 2.45464,-7.69829 1.32784,-2.47745 2.97568,-6.53775 3.66184,-9.02289 1.0661,-3.86109 0.94584,-4.49635 -0.82674,-4.36672 -1.60597,0.11748 -2.00447,-0.85199 -1.76487,-4.2933 0.17023,-2.44474 0.52374,-8.15974 0.78558,-12.69999 0.26185,-4.54025 1.33182,-10.54101 2.37771,-13.33501 1.0459,-2.794 2.64667,-9.25347 3.55727,-14.35437 1.57558,-8.82589 1.52979,-9.40023 -0.94689,-11.8769 -2.49362,-2.49364 -2.74177,-2.51129 -5.93034,-0.42206 -3.41782,2.23944 -3.76456,1.95816 -3.3934,-2.75287 0.11862,-1.50491 -0.33871,-3.07876 -1.01616,-3.49745 -1.66599,-1.02964 -14.69026,12.28311 -20.01359,20.45688 -2.36526,3.63177 -5.09263,7.06077 -6.06082,7.62 -0.96818,0.55922 -3.92016,3.87428 -6.55997,7.36678 -2.63979,3.4925 -10.90398,12.92225 -18.36484,20.955 -7.46087,8.03274 -19.22596,20.95743 -26.14463,28.72151 -6.91867,7.76409 -22.00915,24.23186 -33.5344,36.59503 -11.52525,12.36318 -23.92771,26.17584 -27.56103,30.69478 -3.63331,4.51895 -8.63394,10.58544 -11.1125,13.48108 -2.47855,2.89563 -4.50646,6.05908 -4.50646,7.02986 0,3.34167 -8.03605,13.43119 -21.8008,27.37163 -13.40031,13.57136 -15.64452,16.63414 -9.95874,13.59121 4.31267,-2.30807 5.945,-0.81482 6.04086,5.52615 0.048,3.1717 0.42749,6.31735 0.84342,6.99035 0.83646,1.3534 5.78119,0.0702 14.08026,-3.65381 z M 331.10426,17.17236 C 345.06929,5.5135 387.61494,-35.52779 410.9706,-59.87 465.62773,-116.83584 517.694,-183.01157 535.45869,-218.09317 l 6.03697,-11.92176 -2.68924,-2.87502 c -1.47908,-1.58128 -4.36647,-6.42881 -6.41643,-10.7723 -2.04996,-4.34349 -4.1675,-7.48674 -4.70564,-6.985 -0.53815,0.50174 -0.33945,-0.31752 0.44155,-1.82058 1.25039,-2.40645 1.12095,-2.65464 -1.08369,-2.07811 -1.37702,0.36009 -2.19063,1.16125 -1.80802,1.78033 1.08441,1.75462 -1.52041,1.32181 -3.03608,-0.50448 -0.79428,-0.95703 -0.90465,-2.35529 -0.26734,-3.38648 0.6017,-0.97358 0.59767,-3.03952 -0.009,-4.63528 -0.74393,-1.95668 -0.60639,-3.04183 0.42935,-3.38758 0.99978,-0.33374 0.26332,-2.32838 -2.14134,-5.79963 -3.84684,-5.55311 -4.5925,-6.0225 -6.55828,-4.12848 -0.66357,0.63936 -2.63525,1.35374 -4.3815,1.5875 -1.76591,0.23641 -3.18989,1.2705 -3.20856,2.33005 -0.0526,2.98719 -1.89912,5.20613 -3.54669,4.2621 -0.82486,-0.47263 -1.08801,-0.40359 -0.58478,0.15343 1.69434,1.87539 -4.01237,7.97775 -10.74425,11.48917 -9.49184,4.95103 -14.30991,9.37162 -29.03569,26.6403 -16.80085,19.70208 -25.26228,29.01238 -25.826,28.41696 -0.24853,-0.26233 4.92493,-9.33521 11.49642,-20.16196 11.45378,-18.8705 16.9994,-31.67473 16.9994,-39.24978 0,-1.88222 0.51839,-3.96515 1.15198,-4.62872 0.63359,-0.66358 1.74345,-3.39643 2.46634,-6.073 l 1.31436,-4.86649 -5.75551,-2.54594 c -3.93827,-1.74209 -7.82467,-2.42553 -12.30884,-2.16455 -3.60433,0.20975 -7.74681,-0.28294 -9.2055,-1.09487 -1.4587,-0.81194 -3.88758,-1.15319 -5.3975,-0.75833 -1.50993,0.39485 -2.74532,0.14642 -2.74532,-0.55208 0,-0.6985 -1.28588,-1.6884 -2.8575,-2.19978 -1.57163,-0.51138 -3.28613,-1.07809 -3.81,-1.25937 -0.52388,-0.18128 -1.84348,-0.40103 -2.93246,-0.48834 -1.17065,-0.0939 -1.60459,-0.76608 -1.06166,-1.64458 0.62608,-1.01301 0.21274,-1.21507 -1.29891,-0.635 -1.21947,0.46796 -2.92874,0.85083 -3.79838,0.85083 -1.79844,0 -1.16191,7.76182 0.73737,8.99153 0.62565,0.4051 0.77131,1.69097 0.32367,2.8575 -0.56591,1.47474 -2.3085,2.12097 -5.71938,2.12097 -2.69802,0 -6.87,1.10339 -9.27105,2.45197 -2.42312,1.361 -5.54823,2.14271 -7.02362,1.75688 -1.52548,-0.39892 -2.65808,-0.0908 -2.65808,0.72303 0,0.77997 -1.143,1.41812 -2.54,1.41812 -1.397,0 -2.54,-0.5715 -2.54,-1.27 0,-0.6985 0.56062,-1.27 1.24581,-1.27 0.68519,0 1.59401,-1.3874 2.01961,-3.08311 0.97565,-3.88735 4.43922,-5.33615 6.3874,-2.67185 1.2792,1.74939 2.05598,1.41215 6.12899,-2.66085 4.72823,-4.72824 5.22523,-8.49252 1.05435,-7.98565 -1.22714,0.14914 -4.35594,-0.67153 -6.95287,-1.82369 -6.31349,-2.80106 -8.47658,-2.65008 -13.79339,0.96279 -2.47485,1.6817 -6.57005,3.92268 -9.10047,4.97995 -2.69401,1.12563 -5.43871,3.54279 -6.62269,5.83236 -1.11209,2.15053 -2.50713,3.91005 -3.10011,3.91005 -0.59299,0 -6.48979,5.42925 -13.10401,12.065 -7.65652,7.68144 -13.0063,12.065 -14.72438,12.065 -1.87692,0 -2.47595,-0.58001 -1.96751,-1.905 1.39752,-3.64187 -2.71175,-1.90505 -7.47823,3.16073 -2.61937,2.78386 -4.7625,5.50491 -4.7625,6.04677 0,0.54185 -1.28333,1.54933 -2.85187,2.23884 -1.56853,0.68951 -3.61508,2.85683 -4.54789,4.81626 -1.31605,2.76442 -2.07586,3.24739 -3.39134,2.15563 -1.30342,-1.08174 -1.88653,-0.8044 -2.52267,1.1999 -0.45507,1.43378 -1.44025,2.60687 -2.18931,2.60687 -1.66604,0 -4.72121,3.85851 -4.77653,6.0325 -0.0222,0.87312 -1.04051,2.6442 -2.26288,3.93573 -8.61236,9.09956 -11.63696,12.8021 -13.64368,16.70177 -1.25803,2.44475 -4.77504,7.36412 -7.81558,10.93191 -6.09898,7.15664 -6.80925,9.38809 -2.98824,9.38809 1.397,0 2.54,0.19794 2.54,0.43987 0,1.64439 -5.05373,9.23472 -11.54525,17.34013 -4.19565,5.23875 -11.0022,14.097 -15.12567,19.68499 -4.12346,5.588 -8.89758,11.88907 -10.60916,14.00238 -2.3578,2.91119 -8.43992,17.65949 -8.43992,20.46563 0,0.21642 1.85738,0.063 4.1275,-0.34095 2.57444,-0.4581 5.82976,-2.59654 8.65151,-5.68325 2.63683,-2.88443 5.69083,-4.9488 7.32117,-4.9488 1.76138,0 3.67633,-1.44568 5.1714,-3.90414 2.46907,-4.06006 16.48628,-14.11979 18.19931,-13.06108 1.97786,1.22238 0.89883,4.37934 -1.87839,5.49564 -1.57163,0.63171 -2.04788,1.1848 -1.05833,1.22907 0.98953,0.0443 1.43198,0.4477 0.98318,0.89648 -0.44878,0.44879 -1.62029,0.14845 -2.60335,-0.66741 -1.3944,-1.15726 -1.95176,-1.05502 -2.53506,0.46505 -1.22283,3.18663 1.37828,8.27639 4.22963,8.27639 3.00131,0 3.43194,1.93803 0.66643,2.99927 -1.04775,0.40205 -1.905,1.66734 -1.905,2.81174 0,1.34155 -0.687,1.81711 -1.93381,1.33867 -2.38451,-0.91503 -6.95619,3.438 -6.95619,6.62348 0,1.28874 1.143,3.37756 2.54,4.64183 1.78769,1.61785 2.54,3.91157 2.54,7.74425 0,3.20547 1.06934,7.25797 2.59952,9.85143 1.42973,2.42322 2.08288,4.72516 1.45142,5.1154 -0.63144,0.39026 -1.14809,3.90221 -1.14809,7.80432 0,3.90211 -0.65314,7.74789 -1.45142,8.54617 -0.79828,0.79829 -1.45143,1.84201 -1.45143,2.31938 0,1.85116 7.19461,2.11525 11.26727,0.41357 2.35561,-0.98423 4.44449,-1.78952 4.64198,-1.78952 0.19747,0 0.0524,2.286 -0.32233,5.08 -0.38559,2.87485 -0.15165,5.08 0.53895,5.08 0.67117,0 4.31829,-2.26141 8.10472,-5.02535 3.78642,-2.76395 8.59891,-5.81849 10.69441,-6.78787 2.0955,-0.96936 6.95325,-4.59344 10.79499,-8.05349 11.01701,-9.92239 26.67,-22.31401 26.67,-21.11318 0,1.59585 -3.28812,5.71945 -13.81071,17.31986 -25.12136,27.69452 -38.25928,45.53629 -38.25928,51.9575 0,1.79188 -2.04319,5.9347 -4.54042,9.20624 C 271.90743,4.09519 270.82353,11.25 282.35126,11.25 c 4.32127,0 5.65813,0.76656 10.06185,5.76945 4.80501,5.45878 5.36071,5.73282 10.32011,5.08914 3.73944,-0.48534 6.51554,0.005 9.68665,1.71309 2.44475,1.31637 5.13768,2.45526 5.98429,2.53086 0.8466,0.0756 6.5616,-4.05548 12.69999,-9.18018 z m 176.8957,-287.13424 c 0,-1.17809 1.08585,-1.99811 2.64583,-1.99811 3.09505,0 2.65756,2.96769 -0.52858,3.58561 -1.25318,0.24306 -2.11725,-0.40482 -2.11725,-1.5875 z m -71.00839,232.73995 c 38.04238,-40.00971 60.40295,-65.13157 70.46182,-79.16306 4.51223,-6.2943 4.48168,-6.27745 -3.48313,1.92081 -4.41942,4.54893 -14.3218,15.3929 -22.0053,24.0977 -7.6835,8.70481 -19.685,21.8646 -26.67,29.24401 -28.6447,30.26213 -41.93501,44.6594 -42.37443,45.90382 -1.29027,3.65404 6.08919,-3.09151 24.07104,-22.00328 z m 132.36374,-203.35193 c 6.55693,-9.82731 9.60111,-14.21463 12.175,-17.54684 1.40439,-1.81813 3.41692,-5.81199 4.47229,-8.87524 1.05539,-3.06326 2.51029,-5.76669 3.23312,-6.00764 0.72282,-0.24094 1.31423,-1.46348 1.31423,-2.71674 0,-1.25327 0.76476,-2.91336 1.69945,-3.68909 0.93471,-0.77572 2.42798,-3.84608 3.31839,-6.823 0.89041,-2.97693 2.70206,-6.93611 4.02591,-8.7982 1.32385,-1.8621 2.7463,-5.2911 3.16099,-7.62 0.41469,-2.32891 1.92848,-7.37763 3.36399,-11.21938 1.76847,-4.73291 2.61116,-10.02089 2.61361,-16.40097 0.004,-8.67028 -0.26952,-9.65092 -3.44518,-12.3825 -8.6896,-7.47447 -30.23853,-0.72832 -63.97378,20.02774 -24.80535,15.26181 -29.44951,18.25084 -29.47701,18.97172 -0.0145,0.38005 6.11726,5.32035 13.62614,10.97845 15.9623,12.02793 26.47717,22.01691 35.06005,33.30651 6.50142,8.55174 6.43756,8.22371 3.91558,20.11155 -1.18835,5.60152 0.62548,5.11595 4.91722,-1.31637 z m 47.5736,-13.36801 c 0.76194,-2.28582 -1.86309,-1.80549 -2.74894,0.503 -0.47464,1.23686 -0.19081,1.81747 0.73724,1.50812 0.82995,-0.27664 1.73522,-1.18166 2.0117,-2.01112 z m 15.4933,-22.21531 c 5.57662,-7.81979 11.81898,-16.50381 13.87193,-19.29781 13.13187,-17.87215 37.99566,-61.93308 47.05806,-83.39121 8.66909,-20.52684 11.84285,-33.38765 11.28898,-45.74556 -0.4906,-10.94647 -2.62184,-15.05787 -10.08399,-19.45323 -3.37749,-1.98941 -6.37935,-2.50289 -13.84829,-2.36878 -24.92324,0.44749 -62.72624,20.21462 -121.90894,63.74599 -5.23875,3.85332 -10.0965,7.62181 -10.795,8.37441 -0.6985,0.75261 -5.55625,4.61767 -10.795,8.58903 -5.23875,3.97135 -11.811,9.26613 -14.605,11.76617 -7.47104,6.68497 -11.10244,9.78044 -16.46276,14.03318 -4.50862,3.57702 -8.47867,7.1027 -17.25296,15.32189 -2.02412,1.89606 -3.38479,3.92542 -3.0237,4.50965 0.36108,0.58425 11.43188,-7.96761 24.60176,-19.00415 66.48812,-55.7179 121.85666,-90.76742 154.0181,-97.49688 14.37861,-3.00858 25.70713,-0.37336 29.39788,6.83849 10.31507,20.15606 -7.44832,64.83222 -55.56384,139.7469 -18.95639,29.51461 -19.28504,30.05742 -17.54946,28.98477 0.83211,-0.51427 6.07561,-7.33305 11.65223,-15.15286 z m -15.60471,-53.19094 c -0.40541,-1.01311 -0.70635,-0.71219 -0.76728,0.7673 -0.0551,1.33878 0.24502,2.08888 0.66703,1.66687 0.42201,-0.42202 0.46712,-1.51739 0.1002,-2.43417 z m 2.84845,-11.82687 c 0.17407,-10.46408 -9.34569,-18.13464 -18.4881,-14.89677 l -3.6429,1.29017 5.59436,0.96459 c 3.21369,0.55411 7.21504,2.41268 9.40282,4.36746 3.20931,2.86752 3.90696,4.58525 4.43453,10.9187 0.69317,8.32129 2.54706,6.50525 2.69929,-2.64415 z"
+         id="path8410"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#a89576;stroke-width:1.26999998"
+         d="m 52.18926,285.27542 c -7.07558,-2.3825 -12.3804,-8.71833 -14.2338,-17.0002 -2.7948,-12.48848 2.56992,-36.52221 14.71078,-65.90367 5.52445,-13.36943 24.22387,-51.19555 26.67601,-53.96155 0.92886,-1.04775 4.89299,-7.7922 8.80918,-14.98767 9.02085,-16.57461 31.45621,-52.30371 48.66101,-77.4944 9.54923,-13.98166 13.82419,-19.25165 15.19118,-18.72709 1.04629,0.4015 3.29307,1.07904 4.99285,1.50566 1.69978,0.42662 3.66705,1.85293 4.3717,3.16959 0.87165,1.62869 2.66931,2.39391 5.62372,2.39391 5.61784,0 20.64094,5.4584 23.84693,8.6644 2.47021,2.4702 2.46957,2.53539 -0.0912,9.29413 -1.41838,3.74356 -4.13293,10.23547 -6.03233,14.42647 -12.96848,28.61478 -15.34105,48.43841 -6.53549,54.60607 6.06305,4.24672 20.23851,1.45905 36.22021,-7.12289 16.31935,-8.76324 18.24184,-9.89454 22.79034,-13.41114 5.02293,-3.8834 7.45057,-5.6114 15.05138,-10.71364 2.7133,-1.82138 7.7357,-5.85089 11.16086,-8.95448 6.13396,-5.55805 6.19631,-5.69057 4.1473,-8.81775 -1.14414,-1.74617 -4.2323,-4.12616 -6.86256,-5.28886 -12.3017,-5.43787 -38.36242,-30.1934 -50.93748,-48.3864 -7.41092,-10.72167 -20.51987,-35.19641 -20.51987,-38.31099 0,-1.07214 -1.46395,-4.8383 -3.2532,-8.36923 -3.73326,-7.36724 -11.49425,-37.45131 -12.57395,-48.74069 -1.75355,-18.33507 -1.81552,-22.51499 -0.5742,-38.735 2.11767,-27.67115 7.88354,-50.16461 19.58958,-76.4215 23.64615,-53.03886 62.22531,-94.74111 112.38176,-121.47932 30.73622,-16.38536 60.66152,-24.21368 96.59308,-25.26827 23.80648,-0.69872 37.98232,0.55157 54.91273,4.84322 l 9.90082,2.50974 21.84918,-19.44055 c 63.97322,-56.92095 119.09297,-98.55512 162.69474,-122.8899 36.31002,-20.26516 61.84053,-27.61359 78.5471,-22.60818 7.01565,2.10194 11.55865,7.13158 14.29265,15.82366 2.02598,6.44111 2.14486,8.63819 0.9166,16.9386 -4.17149,28.19027 -20.15667,63.72874 -50.84645,113.04251 -24.77133,39.80374 -64.74535,96.34692 -92.73873,131.17874 l -6.93067,8.62376 3.35665,11.06124 c 16.18822,53.34561 10.37924,112.12276 -16.35098,165.44474 -5.30616,10.58485 -9.69843,17.72759 -21.04069,34.21657 -6.67267,9.70049 -17.84515,22.68903 -27.35332,31.79954 -40.26575,38.58172 -91.69934,62.52522 -144.61772,67.32284 -18.56824,1.6834 -32.16674,0.99407 -54.1949,-2.74725 l -15.06565,-2.55879 -12.35972,11.07496 c -6.79785,6.09124 -15.50297,14.29129 -19.34472,18.22233 -7.79514,7.97634 -44.47724,41.08944 -53.34,48.15027 -3.14325,2.50418 -11.14425,9.18473 -17.78,14.84567 -22.3369,19.05555 -52.2134,41.14115 -81.16706,60.00111 -32.49132,21.16435 -68.52154,33.90125 -82.54366,29.17968 z M 123.82499,233.67 c 23.10865,-8.1142 57.80323,-30.32047 97.15499,-62.18405 22.56137,-18.26821 37.12227,-31.23353 35.6343,-31.72951 -0.78092,-0.26031 -2.97446,1.03892 -4.87457,2.88717 -3.18832,3.1013 -10.04458,8.71846 -26.32343,21.56609 -3.48771,2.75259 -7.19855,5.74995 -8.2463,6.66081 -13.29959,11.56204 -47.99087,34.9144 -67.03007,45.12113 -17.78679,9.53533 -26.66216,12.61665 -38.4474,13.34804 -12.993719,0.80639 -17.708329,-1.01685 -21.328958,-8.24835 -3.365372,-6.72168 -4.114662,-13.40939 -2.657152,-23.71631 1.52305,-10.77039 2.251143,-13.17581 10.400382,-34.36002 6.769768,-17.59822 15.442238,-35.61782 26.626008,-55.32331 3.79053,-6.67882 7.29316,-13.18903 7.78361,-14.46714 2.41471,-6.29264 -3.61599,2.081 -11.25453,15.62692 -4.58813,8.13645 -9.21483,16.22228 -10.28155,17.96853 -3.95035,6.4668 -17.453543,35.27101 -21.433197,45.72 -7.644493,20.0714 -8.748033,25.09956 -8.827543,40.22168 -0.0653,12.41633 0.25364,14.63507 2.55569,17.78 6.795516,9.28362 20.09732,10.30983 40.54972,3.12832 z m 55.88,-91.61 c -0.4317,-0.6985 -2.12073,-1.27 -3.75339,-1.27 -1.81898,0 -4.52513,-1.77292 -6.98835,-4.57836 -2.21092,-2.51811 -4.28636,-4.31187 -4.61209,-3.98614 -1.1767,1.17668 1.92383,7.08394 4.76125,9.07135 3.17626,2.22473 11.88359,2.85206 10.59258,0.76315 z m 85.94249,-8.45184 c 1.27738,-1.156 2.3225,-2.60485 2.3225,-3.21967 0,-1.69545 -4.67548,1.16551 -5.52573,3.38123 -0.9705,2.52909 0.30096,2.46496 3.20323,-0.16156 z m 11.2125,-9.96316 c 1.28705,-1.55081 1.22316,-1.905 -0.34363,-1.905 -1.05854,0 -2.25357,0.85725 -2.65564,1.905 -0.40206,1.04775 -0.24742,1.905 0.34362,1.905 0.59105,0 1.78609,-0.85725 2.65565,-1.905 z M 138.47394,83.55383 c 1.40122,-2.7466 2.2709,-4.99383 1.93264,-4.99383 -0.85936,0 -7.05659,9.24744 -7.05659,10.52976 0,2.16422 2.66395,-0.71391 5.12395,-5.53593 z M 405.76497,58.2178 c 2.794,-1.25375 9.55499,-4.37695 15.02444,-6.94043 5.46944,-2.56348 10.63688,-4.39517 11.4832,-4.0704 0.84631,0.32476 2.62417,-0.2032 3.95079,-1.17324 3.66691,-2.68131 7.64184,-2.14772 9.47175,1.27148 1.59286,2.97629 1.67099,2.98862 4.0246,0.635 1.32012,-1.32011 3.63815,-2.40021 5.15117,-2.40021 1.51303,0 3.65616,-0.85424 4.7625,-1.89832 4.44913,-4.19869 9.7852,-7.96221 12.48904,-8.80849 1.57163,-0.49189 2.8575,-1.38724 2.8575,-1.98967 0,-0.60244 2.6183,-2.40297 5.81844,-4.0012 3.20015,-1.59823 6.37205,-3.94029 7.04868,-5.20458 0.8792,-1.6428 2.47708,-2.21476 5.59982,-2.00445 2.79063,0.18794 4.94748,-0.44491 5.96883,-1.75135 0.87957,-1.12509 6.45698,-6.13251 12.39423,-11.12761 13.41176,-11.28351 21.5784,-19.1885 21.62762,-20.93468 0.0207,-0.73405 3.02106,-4.55114 6.66749,-8.48241 3.64644,-3.93127 8.58081,-10.50352 10.96526,-14.605 2.38445,-4.10148 5.23991,-8.88599 6.34544,-10.63224 4.24655,-6.70763 5.2759,-10.20836 3.23224,-10.99258 -1.46477,-0.56208 -0.88972,-1.82532 2.57837,-5.66408 2.45497,-2.71736 4.46358,-5.97278 4.46358,-7.23426 0,-1.26149 0.83907,-3.40296 1.86461,-4.75884 1.02552,-1.35588 1.88277,-3.90244 1.905,-5.65904 0.0222,-1.75659 1.12679,-5.22082 2.45464,-7.69829 1.32784,-2.47745 2.97568,-6.53775 3.66184,-9.02289 1.0661,-3.86109 0.94584,-4.49635 -0.82674,-4.36672 -1.60597,0.11748 -2.00447,-0.85199 -1.76487,-4.2933 0.17023,-2.44474 0.52374,-8.15974 0.78558,-12.69999 0.26185,-4.54025 1.33182,-10.54101 2.37771,-13.33501 1.0459,-2.794 2.64667,-9.25347 3.55727,-14.35437 1.57558,-8.82589 1.52979,-9.40023 -0.94689,-11.8769 -2.49362,-2.49364 -2.74177,-2.51129 -5.93034,-0.42206 -3.41782,2.23944 -3.76456,1.95816 -3.3934,-2.75287 0.33377,-4.23646 -1.6855,-4.46179 -5.74916,-0.64151 -4.32488,4.06585 -29.2924,31.57641 -58.96671,64.97271 -11.48206,12.92225 -36.9614,40.92575 -56.62073,62.23 -41.46717,44.93678 -52.80892,57.82171 -63.03515,71.61184 -7.46501,10.06661 -8.47245,13.18417 -4.02096,12.44302 7.31655,-1.21817 7.4502,-1.1382 7.67938,4.59447 0.39918,9.98446 1.27145,10.21862 15.04393,4.03847 z m -88.265,-27.89791 C 341.64688,11.05211 407.25731,-53.55526 438.7536,-89.08 c 34.43653,-38.84101 63.19539,-75.12429 82.54351,-104.13999 12.86137,-19.28776 26.07285,-42.4233 26.07285,-45.65799 0,-0.89743 -1.7145,-3.58441 -3.81,-5.97104 -2.0955,-2.38665 -3.81,-5.19576 -3.81,-6.2425 0,-2.1519 -6.46219,-8.9172 -11.81575,-12.36998 -1.95842,-1.26306 -3.91608,-3.26331 -4.35037,-4.445 -1.08142,-2.94247 -6.04035,-7.48491 -9.301,-8.51979 -1.84414,-0.58532 -3.58277,-0.0138 -5.53377,1.8191 -2.08848,1.96202 -3.83242,2.47792 -6.55946,1.94044 -2.05986,-0.40597 -5.62201,0.0753 -7.99628,1.08043 -4.31461,1.82649 -7.18679,5.04137 -22.63383,25.33443 -4.64842,6.1067 -8.64221,10.91255 -8.87512,10.67964 -0.2329,-0.23289 3.0747,-7.73634 7.35022,-16.67432 6.94039,-14.50892 10.59638,-26.06342 8.24679,-26.06342 -0.46365,0 -2.86405,0.84445 -5.33423,1.87655 -4.30516,1.79881 -4.61201,1.7462 -7.40734,-1.27 -3.18183,-3.43326 -8.3347,-4.06342 -15.77593,-1.92931 -3.22372,0.92456 -4.93463,0.76494 -7.11517,-0.6638 -1.57897,-1.03458 -3.79114,-1.52791 -4.91592,-1.0963 -1.33095,0.51074 -3.32909,-0.62106 -5.72222,-3.2412 -3.53961,-3.87539 -12.6884,-7.31972 -14.14853,-5.32664 -0.36689,0.50081 -2.64144,1.09644 -5.05454,1.32362 -2.4131,0.22717 -5.55635,0.64875 -6.985,0.93684 -1.42865,0.2881 -5.2772,-0.15209 -8.55234,-0.97816 -5.57773,-1.40685 -6.27232,-1.29184 -10.96878,1.81612 -2.75767,1.82495 -7.04093,4.165 -9.51834,5.20013 -2.60327,1.08771 -5.35776,3.53232 -6.52634,5.7921 -1.11209,2.15053 -2.50713,3.91005 -3.10011,3.91005 -0.59299,0 -6.48979,5.42925 -13.10401,12.065 -7.65652,7.68144 -13.0063,12.065 -14.72438,12.065 -1.87692,0 -2.47595,-0.58001 -1.96751,-1.905 1.39752,-3.64187 -2.71175,-1.90505 -7.47823,3.16073 -2.61937,2.78386 -4.7625,5.50491 -4.7625,6.04677 0,0.54185 -1.28333,1.54933 -2.85187,2.23884 -1.56853,0.68951 -3.61508,2.85683 -4.54789,4.81626 -1.31605,2.76442 -2.07586,3.24739 -3.39134,2.15563 -1.30342,-1.08174 -1.88653,-0.8044 -2.52267,1.1999 -0.45507,1.43378 -1.44025,2.60687 -2.18931,2.60687 -1.66604,0 -4.72121,3.85851 -4.77653,6.0325 -0.0222,0.87312 -1.04051,2.6442 -2.26288,3.93573 -8.61236,9.09956 -11.63696,12.8021 -13.64368,16.70177 -1.25803,2.44475 -4.77504,7.36412 -7.81558,10.93191 -6.09898,7.15664 -6.80925,9.38809 -2.98824,9.38809 1.397,0 2.54,0.19794 2.54,0.43987 0,1.64439 -5.05373,9.23472 -11.54525,17.34013 -4.19565,5.23875 -11.0022,14.097 -15.12566,19.68499 -4.12347,5.588 -8.90835,11.9014 -10.6331,14.02977 -1.72474,2.12837 -4.401,7.84337 -5.94725,12.7 -1.54624,4.85663 -3.3013,9.45491 -3.90012,10.21841 -1.63014,2.0784 4.77308,4.66682 7.68697,3.10735 3.24847,-1.73853 3.79641,-0.61829 2.33586,4.77555 -0.69981,2.58441 -1.2208,6.12767 -1.15776,7.87392 0.24673,6.83422 1.7638,15.24 2.75052,15.24 1.40808,0 5.04879,16.32051 4.15835,18.64098 -0.39054,1.0177 0.0235,3.22108 0.92012,4.8964 1.28861,2.40778 1.30441,3.43859 0.0754,4.91946 -1.25795,1.51572 -1.0002,2.62358 1.34985,5.80219 1.59756,2.16081 4.15733,4.24316 5.68838,4.62742 1.53105,0.38428 3.08846,1.86402 3.46093,3.28832 0.55493,2.12208 1.32181,2.43284 4.24748,1.72116 4.12565,-1.00358 6.09412,-2.3192 27.19899,-18.17843 15.10995,-11.35434 22.26362,-15.18104 16.61902,-8.89 -1.41012,1.57163 -5.41287,6.00075 -8.89499,9.8425 -6.84602,7.55309 -12.68222,15.13034 -18.58895,24.13439 -2.06041,3.14083 -4.70444,6.85361 -5.87559,8.25061 -1.17116,1.397 -2.52067,4.73133 -2.99893,7.4096 -0.47824,2.67829 -2.50232,7.53604 -4.49794,10.795 -6.68038,10.90943 -6.31283,19.2604 0.84768,19.2604 3.46512,0 7.33184,1.80367 10.80647,5.04077 2.2208,2.069 9.72811,5.71303 18.48203,8.97117 2.27012,0.84492 4.1275,2.33881 4.1275,3.31975 0,2.54662 4.85958,-0.13883 15.24,-8.4218 z m 119.4916,-67.54182 c 38.04238,-40.00971 60.40295,-65.13157 70.46182,-79.16306 4.51223,-6.2943 4.48168,-6.27745 -3.48313,1.92081 -4.41942,4.54893 -14.3218,15.3929 -22.0053,24.0977 -7.6835,8.70481 -19.685,21.8646 -26.67,29.24401 -28.6447,30.26213 -41.93501,44.6594 -42.37443,45.90382 -1.29027,3.65404 6.08919,-3.09151 24.07104,-22.00328 z m 132.36374,-203.35193 c 6.55693,-9.82731 9.60111,-14.21463 12.175,-17.54684 1.40439,-1.81813 3.41692,-5.81199 4.47229,-8.87524 1.05539,-3.06326 2.51029,-5.76669 3.23312,-6.00764 0.72282,-0.24094 1.31423,-1.46348 1.31423,-2.71674 0,-1.25327 0.76476,-2.91336 1.69945,-3.68909 0.93471,-0.77572 2.42798,-3.84608 3.31839,-6.823 0.89041,-2.97693 2.70206,-6.93611 4.02591,-8.7982 1.32385,-1.8621 2.7463,-5.2911 3.16099,-7.62 0.41469,-2.32891 1.92848,-7.37763 3.36399,-11.21938 1.76847,-4.73291 2.61116,-10.02089 2.61361,-16.40097 0.004,-8.67028 -0.26952,-9.65092 -3.44518,-12.3825 -8.6896,-7.47447 -30.23853,-0.72832 -63.97378,20.02774 -24.80535,15.26181 -29.44951,18.25084 -29.47701,18.97172 -0.0145,0.38005 6.11726,5.32035 13.62614,10.97845 15.9623,12.02793 26.47717,22.01691 35.06005,33.30651 6.50142,8.55174 6.43756,8.22371 3.91558,20.11155 -1.18835,5.60152 0.62548,5.11595 4.91722,-1.31637 z m 47.5736,-13.36801 c 0.76194,-2.28582 -1.86309,-1.80549 -2.74894,0.503 -0.47464,1.23686 -0.19081,1.81747 0.73724,1.50812 0.82995,-0.27664 1.73522,-1.18166 2.0117,-2.01112 z m 15.4933,-22.21531 c 5.57662,-7.81979 11.81898,-16.50381 13.87193,-19.29781 13.13187,-17.87215 37.99566,-61.93308 47.05806,-83.39121 8.66909,-20.52684 11.84285,-33.38765 11.28898,-45.74556 -0.4906,-10.94647 -2.62184,-15.05787 -10.08399,-19.45323 -3.37749,-1.98941 -6.37935,-2.50289 -13.84829,-2.36878 -24.92324,0.44749 -62.72624,20.21462 -121.90894,63.74599 -5.23875,3.85332 -10.0965,7.62181 -10.795,8.37441 -0.6985,0.75261 -5.55625,4.61767 -10.795,8.58903 -5.23875,3.97135 -11.811,9.26613 -14.605,11.76617 -7.47104,6.68497 -11.10244,9.78044 -16.46276,14.03318 -4.50862,3.57702 -8.47867,7.1027 -17.25296,15.32189 -2.02412,1.89606 -3.38479,3.92542 -3.0237,4.50965 0.36108,0.58425 11.43188,-7.96761 24.60176,-19.00415 66.48812,-55.7179 121.85666,-90.76742 154.0181,-97.49688 14.37861,-3.00858 25.70713,-0.37336 29.39788,6.83849 10.31507,20.15606 -7.44832,64.83222 -55.56384,139.7469 -18.95639,29.51461 -19.28504,30.05742 -17.54946,28.98477 0.83211,-0.51427 6.07561,-7.33305 11.65223,-15.15286 z m -15.60471,-53.19094 c -0.40541,-1.01311 -0.70635,-0.71219 -0.76728,0.7673 -0.0551,1.33878 0.24502,2.08888 0.66703,1.66687 0.42201,-0.42202 0.46712,-1.51739 0.1002,-2.43417 z m 2.84845,-11.82687 c 0.17407,-10.46408 -9.34569,-18.13464 -18.4881,-14.89677 l -3.6429,1.29017 5.59436,0.96459 c 3.21369,0.55411 7.21504,2.41268 9.40282,4.36746 3.20931,2.86752 3.90696,4.58525 4.43453,10.9187 0.69317,8.32129 2.54706,6.50525 2.69929,-2.64415 z"
+         id="path8408"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#928f8a;stroke-width:1.26999998"
+         d="m 52.18926,285.27542 c -7.07558,-2.3825 -12.3804,-8.71833 -14.2338,-17.0002 -2.7948,-12.48848 2.56992,-36.52221 14.71078,-65.90367 5.52445,-13.36943 24.22387,-51.19555 26.67601,-53.96155 0.92886,-1.04775 4.89299,-7.7922 8.80918,-14.98767 9.02085,-16.57461 31.45621,-52.30371 48.66101,-77.4944 9.54923,-13.98166 13.82419,-19.25165 15.19118,-18.72709 1.04629,0.4015 3.29307,1.07904 4.99285,1.50566 1.69978,0.42662 3.66705,1.85293 4.3717,3.16959 0.87165,1.62869 2.66931,2.39391 5.62372,2.39391 5.61784,0 20.64094,5.4584 23.84693,8.6644 2.47021,2.4702 2.46957,2.53539 -0.0912,9.29413 -1.41838,3.74356 -4.13293,10.23547 -6.03233,14.42647 -12.96848,28.61478 -15.34105,48.43841 -6.53549,54.60607 6.06305,4.24672 20.23851,1.45905 36.22021,-7.12289 16.31935,-8.76324 18.24184,-9.89454 22.79034,-13.41114 5.02293,-3.8834 7.45057,-5.6114 15.05138,-10.71364 2.7133,-1.82138 7.7357,-5.85089 11.16086,-8.95448 6.13396,-5.55805 6.19631,-5.69057 4.1473,-8.81775 -1.14414,-1.74617 -4.2323,-4.12616 -6.86256,-5.28886 -12.3017,-5.43787 -38.36242,-30.1934 -50.93748,-48.3864 -7.41092,-10.72167 -20.51987,-35.19641 -20.51987,-38.31099 0,-1.07214 -1.46395,-4.8383 -3.2532,-8.36923 -3.73326,-7.36724 -11.49425,-37.45131 -12.57395,-48.74069 -1.75355,-18.33507 -1.81552,-22.51499 -0.5742,-38.735 2.11767,-27.67115 7.88354,-50.16461 19.58958,-76.4215 23.64615,-53.03886 62.22531,-94.74111 112.38176,-121.47932 30.73622,-16.38536 60.66152,-24.21368 96.59308,-25.26827 23.80648,-0.69872 37.98232,0.55157 54.91273,4.84322 l 9.90082,2.50974 21.84918,-19.44055 c 63.97322,-56.92095 119.09297,-98.55512 162.69474,-122.8899 36.31002,-20.26516 61.84053,-27.61359 78.5471,-22.60818 7.01565,2.10194 11.55865,7.13158 14.29265,15.82366 2.02598,6.44111 2.14486,8.63819 0.9166,16.9386 -4.17149,28.19027 -20.15667,63.72874 -50.84645,113.04251 -24.77133,39.80374 -64.74535,96.34692 -92.73873,131.17874 l -6.93067,8.62376 3.35665,11.06124 c 16.18822,53.34561 10.37924,112.12276 -16.35098,165.44474 -5.30616,10.58485 -9.69843,17.72759 -21.04069,34.21657 -6.67267,9.70049 -17.84515,22.68903 -27.35332,31.79954 -40.26575,38.58172 -91.69934,62.52522 -144.61772,67.32284 -18.56824,1.6834 -32.16674,0.99407 -54.1949,-2.74725 l -15.06565,-2.55879 -12.35972,11.07496 c -6.79785,6.09124 -15.50297,14.29129 -19.34472,18.22233 -7.79514,7.97634 -44.47724,41.08944 -53.34,48.15027 -3.14325,2.50418 -11.14425,9.18473 -17.78,14.84567 -22.3369,19.05555 -52.2134,41.14115 -81.16706,60.00111 -32.49132,21.16435 -68.52154,33.90125 -82.54366,29.17968 z M 123.82499,233.67 c 23.10865,-8.1142 57.80323,-30.32047 97.15499,-62.18405 22.56137,-18.26821 37.12227,-31.23353 35.6343,-31.72951 -0.78092,-0.26031 -2.97446,1.03892 -4.87457,2.88717 -3.18832,3.1013 -10.04458,8.71846 -26.32343,21.56609 -3.48771,2.75259 -7.19855,5.74995 -8.2463,6.66081 -13.29959,11.56204 -47.99087,34.9144 -67.03007,45.12113 -17.78679,9.53533 -26.66216,12.61665 -38.4474,13.34804 -12.993719,0.80639 -17.708329,-1.01685 -21.328958,-8.24835 -3.365372,-6.72168 -4.114662,-13.40939 -2.657152,-23.71631 1.52305,-10.77039 2.251143,-13.17581 10.400382,-34.36002 6.769768,-17.59822 15.442238,-35.61782 26.626008,-55.32331 3.79053,-6.67882 7.29316,-13.18903 7.78361,-14.46714 2.41471,-6.29264 -3.61599,2.081 -11.25453,15.62692 -4.58813,8.13645 -9.21483,16.22228 -10.28155,17.96853 -3.95035,6.4668 -17.453543,35.27101 -21.433197,45.72 -7.644493,20.0714 -8.748033,25.09956 -8.827543,40.22168 -0.0653,12.41633 0.25364,14.63507 2.55569,17.78 6.795516,9.28362 20.09732,10.30983 40.54972,3.12832 z m 55.88,-91.61 c -0.4317,-0.6985 -2.12073,-1.27 -3.75339,-1.27 -1.81898,0 -4.52513,-1.77292 -6.98835,-4.57836 -2.21092,-2.51811 -4.28636,-4.31187 -4.61209,-3.98614 -1.1767,1.17668 1.92383,7.08394 4.76125,9.07135 3.17626,2.22473 11.88359,2.85206 10.59258,0.76315 z m 85.94249,-8.45184 c 1.27738,-1.156 2.3225,-2.60485 2.3225,-3.21967 0,-1.69545 -4.67548,1.16551 -5.52573,3.38123 -0.9705,2.52909 0.30096,2.46496 3.20323,-0.16156 z m 11.2125,-9.96316 c 1.28705,-1.55081 1.22316,-1.905 -0.34363,-1.905 -1.05854,0 -2.25357,0.85725 -2.65564,1.905 -0.40206,1.04775 -0.24742,1.905 0.34362,1.905 0.59105,0 1.78609,-0.85725 2.65565,-1.905 z M 138.47394,83.55383 c 1.40122,-2.7466 2.2709,-4.99383 1.93264,-4.99383 -0.85936,0 -7.05659,9.24744 -7.05659,10.52976 0,2.16422 2.66395,-0.71391 5.12395,-5.53593 z M 400.67285,67.72356 c 4.6999,-1.76903 9.5128,-2.90935 10.69532,-2.53404 1.18252,0.37532 3.26306,-0.15936 4.62342,-1.18817 3.68778,-2.78899 8.84531,-2.40808 10.98278,0.81113 1.993,3.00161 1.68966,3.06949 12.42046,-2.77914 3.82792,-2.08633 7.49766,-3.79334 8.15499,-3.79334 0.65733,0 2.09739,-0.75864 3.20014,-1.6859 2.3926,-2.01179 12.43654,-5.03863 13.84048,-4.17096 0.96422,0.59593 6.89282,-2.62418 13.3128,-7.23084 1.75736,-1.261 4.35068,-2.37975 5.76298,-2.48614 1.41227,-0.10643 2.28789,-0.64629 1.9458,-1.1998 -0.34207,-0.55349 0.75555,-1.00636 2.43918,-1.00636 1.68362,0 5.10617,-1.57162 7.60567,-3.4925 23.65415,-18.17832 53.71859,-47.44402 57.4672,-55.94044 1.29508,-2.93537 2.76099,-5.33706 3.25756,-5.33706 0.49659,0 3.00424,-4.14337 5.57257,-9.2075 2.56835,-5.06412 5.41922,-10.06475 6.33528,-11.1125 2.03635,-2.32905 8.29047,-17.8586 8.29047,-20.58593 0,-1.08442 1.40217,-5.69687 3.11592,-10.24989 2.18142,-5.7955 2.89389,-9.6465 2.37558,-12.84047 -0.40719,-2.50921 -0.14744,-5.28142 0.57722,-6.16046 1.82452,-2.21319 6.20073,-14.97625 7.23587,-21.1032 0.5011,-2.96604 0.27893,-6.14918 -0.53541,-7.67078 -2.74576,-5.1305 -3.80524,-14.95201 -2.49234,-23.10434 1.92097,-11.92811 0.48493,-20.15729 -4.49422,-25.754 l -4.1301,-4.64237 -3.17328,2.4961 c -1.74529,1.37285 -8.83224,9.23666 -15.74876,17.47511 -19.99279,23.81398 -70.36848,80.23255 -109.20088,122.30023 -61.13371,66.22701 -74.18958,82.6095 -74.18958,93.09301 0,5.40476 1.43944,6.52852 7.87603,6.14873 3.97031,-0.23426 4.82397,0.1365 4.82397,2.09514 0,4.93531 1.02367,5.00807 12.05288,0.85668 z M 317.49997,30.31989 C 341.64688,11.05211 407.25731,-53.55526 438.7536,-89.08 c 47.06365,-53.08319 84.6867,-103.17538 102.14307,-135.99563 7.09245,-13.33476 7.8092,-16.87003 3.84986,-18.98902 -1.44288,-0.77219 -3.6594,-3.79269 -4.92559,-6.71217 -2.25896,-5.20853 -6.20405,-9.45834 -11.32359,-12.19823 -1.41882,-0.75933 -4.18229,-4.25386 -6.14103,-7.76563 -4.25829,-7.63452 -7.1769,-8.69201 -14.1588,-5.1301 -2.847,1.45244 -6.27653,2.64079 -7.62115,2.64079 -4.95041,0 -10.79201,3.61593 -15.61493,9.6656 -2.72265,3.41517 -8.75353,11.20579 -13.40194,17.3125 -4.64842,6.10671 -8.64221,10.91255 -8.87512,10.67964 -0.2329,-0.23289 3.0747,-7.73634 7.35022,-16.67432 10.16958,-21.25955 11.06241,-27.06539 4.02374,-26.16531 -0.85614,0.10947 -3.42789,-0.80465 -5.715,-2.0314 -2.28711,-1.22675 -8.15888,-2.61945 -13.04838,-3.09488 -10.86902,-1.05687 -22.72797,-3.90306 -28.2641,-6.78349 -3.52031,-1.83161 -4.83546,-1.91949 -8.85808,-0.59191 -10.61728,3.50402 -18.98753,1.65623 -19.87455,-4.38742 -0.63507,-4.32705 -3.54673,-4.5253 -11.02435,-0.75061 -3.97732,2.00776 -11.99231,7.67821 -17.8111,12.60103 -10.50682,8.889 -12.59281,10.15467 -12.59281,7.64071 0,-6.66751 -25.72808,8.93839 -40.1717,24.36697 -4.23081,4.51932 -8.83818,9.15557 -18.55476,18.67105 -3.3118,3.24326 -6.02643,6.56973 -6.0325,7.39216 -0.006,0.82243 -1.43978,2.61917 -3.18603,3.99278 -1.74625,1.3736 -3.18008,2.98671 -3.18629,3.58467 -0.006,0.59797 -2.72082,4.33948 -6.0325,8.31447 -8.71693,10.46289 -13.60037,17.33472 -17.33231,24.38946 -1.80875,3.41918 -3.78684,6.84818 -4.39575,7.62 -0.60893,0.77181 -1.83976,3.6893 -2.7352,6.4833 -0.89544,2.794 -2.46152,6.36146 -3.48016,7.92769 -1.01864,1.56623 -1.50799,3.74433 -1.08746,4.84022 0.68417,1.78295 1.16233,1.7576 4.54556,-0.24093 4.7215,-2.78906 6.39093,-1.40295 3.13906,2.60634 -27.03775,33.3353 -28.34411,35.35807 -29.29368,45.35826 -0.5493,5.78488 -0.48985,5.89537 3.61512,6.71927 5.47536,1.09895 7.581,1.22885 9.88861,0.61002 2.17629,-0.58362 2.07329,3.47343 -0.23838,9.38885 -1.01452,2.59613 -0.99826,4.40333 0.0606,6.72725 0.79628,1.74765 1.44778,5.1509 1.44778,7.56278 0,2.41188 0.52485,4.38524 1.16633,4.38524 1.49928,0 5.22373,16.19784 4.28622,18.64098 -0.39054,1.0177 0.0235,3.22108 0.92013,4.8964 1.2886,2.40778 1.3044,3.43859 0.0754,4.91946 -1.25795,1.51572 -1.0002,2.62358 1.34984,5.80219 1.59756,2.16081 4.15733,4.24316 5.68838,4.62742 1.53105,0.38428 3.08846,1.86402 3.46093,3.28832 0.55494,2.12208 1.32182,2.43284 4.24749,1.72116 4.12565,-1.00358 6.09412,-2.3192 27.19899,-18.17843 15.10995,-11.35434 22.26362,-15.18104 16.61901,-8.89 -1.41012,1.57163 -5.41286,6.00075 -8.89499,9.8425 -6.84602,7.55309 -12.68221,15.13034 -18.58894,24.13439 -2.06041,3.14083 -4.70444,6.85361 -5.8756,8.25061 -1.17115,1.397 -2.52067,4.73133 -2.99892,7.4096 -0.47825,2.67829 -2.50232,7.53604 -4.49795,10.795 -6.68038,10.90943 -6.31283,19.2604 0.84769,19.2604 3.46512,0 7.33183,1.80367 10.80647,5.04077 2.22079,2.069 9.7281,5.71303 18.48202,8.97117 2.27013,0.84492 4.1275,2.33881 4.1275,3.31975 0,2.54662 4.85958,-0.13883 15.24,-8.4218 z m 62.47285,-6.97371 c 0.60777,0.60777 50.13796,-49.2769 61.83951,-62.28208 3.76054,-4.17951 12.66971,-13.8856 19.79814,-21.5691 7.12842,-7.6835 17.1961,-18.76839 22.37264,-24.63308 5.17652,-5.86471 11.53876,-13.00846 14.13831,-15.875 2.59955,-2.86656 4.74267,-5.64054 4.7625,-6.16442 0.0198,-0.52387 1.32192,-2.23798 2.89354,-3.80913 7.42871,-7.42647 26.36212,-31.66219 29.54791,-37.82282 1.05144,-2.03329 2.95712,-4.71357 4.23482,-5.95621 2.36711,-2.30212 4.48493,-6.70433 3.22533,-6.70433 -1.09008,0 -9.15201,8.68605 -19.53983,21.05252 -14.78863,17.60555 -61.25924,69.54657 -75.47318,84.35773 -32.72908,34.10422 -66.61192,70.45348 -70.26504,75.37981 -3.47858,4.69096 -3.65573,6.41651 -0.48448,4.7193 1.30515,-0.6985 2.63258,-1.01043 2.94983,-0.69319 z m 167.39714,-188.91755 c 0,-0.74334 -0.80697,-0.61991 -1.905,0.29138 -1.04775,0.86955 -1.905,1.77885 -1.905,2.02064 0,0.2418 0.85725,0.11062 1.905,-0.29137 1.04775,-0.40207 1.905,-1.31135 1.905,-2.02065 z m 21.98535,-75.00249 c 6.55693,-9.82731 9.60111,-14.21463 12.175,-17.54684 1.40439,-1.81813 3.41692,-5.81199 4.47229,-8.87524 1.05539,-3.06326 2.51029,-5.76669 3.23312,-6.00764 0.72282,-0.24094 1.31423,-1.46348 1.31423,-2.71674 0,-1.25327 0.76476,-2.91336 1.69945,-3.68909 0.93471,-0.77572 2.42798,-3.84608 3.31839,-6.823 0.89041,-2.97693 2.70206,-6.93611 4.02591,-8.7982 1.32385,-1.8621 2.7463,-5.2911 3.16099,-7.62 0.41469,-2.32891 1.92848,-7.37763 3.36399,-11.21938 1.76847,-4.73291 2.61116,-10.02089 2.61361,-16.40097 0.004,-8.67028 -0.26952,-9.65092 -3.44518,-12.3825 -8.6896,-7.47447 -30.23853,-0.72832 -63.97378,20.02774 -24.80535,15.26181 -29.44951,18.25084 -29.47701,18.97172 -0.0145,0.38005 6.11726,5.32035 13.62614,10.97845 15.9623,12.02793 26.47717,22.01691 35.06005,33.30651 6.50142,8.55174 6.43756,8.22371 3.91558,20.11155 -1.18835,5.60152 0.62548,5.11595 4.91722,-1.31637 z m 47.5736,-13.36801 c 0.76194,-2.28582 -1.86309,-1.80549 -2.74894,0.503 -0.47464,1.23686 -0.19081,1.81747 0.73724,1.50812 0.82995,-0.27664 1.73522,-1.18166 2.0117,-2.01112 z m 15.4933,-22.21531 c 5.57662,-7.81979 11.81898,-16.50381 13.87193,-19.29781 13.13187,-17.87215 37.99566,-61.93308 47.05806,-83.39121 8.66909,-20.52684 11.84285,-33.38765 11.28898,-45.74556 -0.4906,-10.94647 -2.62184,-15.05787 -10.08399,-19.45323 -3.37749,-1.98941 -6.37935,-2.50289 -13.84829,-2.36878 -24.92324,0.44749 -62.72624,20.21462 -121.90894,63.74599 -5.23875,3.85332 -10.0965,7.62181 -10.795,8.37441 -0.6985,0.75261 -5.55625,4.61767 -10.795,8.58903 -5.23875,3.97135 -11.811,9.26613 -14.605,11.76617 -7.47104,6.68497 -11.10244,9.78044 -16.46276,14.03318 -4.50862,3.57702 -8.47867,7.1027 -17.25296,15.32189 -2.02412,1.89606 -3.38479,3.92542 -3.0237,4.50965 0.36108,0.58425 11.43188,-7.96761 24.60176,-19.00415 88.7669,-74.38781 152.96482,-108.95773 178.15192,-95.93297 9.65949,4.99512 10.61064,21.53662 2.65714,46.21058 -4.33656,13.45318 -20.96115,46.58459 -35.36781,70.485 -11.46825,19.02566 -15.59871,25.59709 -29.13631,46.355 -7.15326,10.96847 -7.72659,12.02136 -5.94704,10.92154 0.80173,-0.49549 6.02039,-7.29892 11.59701,-15.11873 z m -15.60471,-53.19094 c -0.40541,-1.01311 -0.70635,-0.71219 -0.76728,0.7673 -0.0551,1.33878 0.24502,2.08888 0.66703,1.66687 0.42201,-0.42202 0.46712,-1.51739 0.1002,-2.43417 z m 2.84845,-11.82687 c 0.17407,-10.46408 -9.34569,-18.13464 -18.4881,-14.89677 l -3.6429,1.29017 5.59436,0.96459 c 3.21369,0.55411 7.21504,2.41268 9.40282,4.36746 3.20931,2.86752 3.90696,4.58525 4.43453,10.9187 0.69317,8.32129 2.54706,6.50525 2.69929,-2.64415 z"
+         id="path8406"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#a38c67;stroke-width:1.26999998"
+         d="M 54.26514,285.1549 C 38.91405,280.3799 35.04431,268.15205 40.5074,241.68234 44.67732,221.4783 59.98411,183.91806 74.63666,157.935 100.66064,111.78723 135.93857,55.47669 148.05484,40.74507 c 2.82286,-3.43219 6.40871,-3.40722 11.17344,0.0779 2.95109,2.15848 2.9701,2.11923 -11.82435,24.40211 -30.04353,45.25054 -51.834362,86.80677 -62.49395,119.17918 -6.07978,18.46384 -7.37402,30.33406 -4.41663,40.50715 2.47436,8.5115 6.37933,11.84115 16.336532,13.9297 6.012188,1.26107 8.032308,1.04848 18.771178,-1.97541 20.85696,-5.87297 44.32966,-18.87513 76.80392,-42.54374 23.23694,-16.93606 57.37882,-44.92762 75.565,-61.95271 2.44475,-2.28866 6.0983,-5.51437 8.11898,-7.16823 2.0207,-1.65387 3.42174,-3.76379 3.11343,-4.68871 -0.50108,-1.50321 -9.01987,5.20908 -27.46766,21.64281 -2.29363,2.04323 -4.58551,3.71496 -5.09305,3.71496 -0.50755,0 -3.20294,2.17188 -5.98976,4.82639 -21.48565,20.46566 -68.21337,53.61793 -93.96693,66.66746 -19.89318,10.08001 -24.22242,11.45689 -36.41023,11.57997 -9.3152,0.0941 -11.563647,-0.30029 -14.605,-2.56158 -6.020893,-4.47661 -8.27844,-9.69867 -8.31868,-19.24244 -0.10416,-24.70635 16.60035,-63.7192 55.43475,-129.4661 14.08792,-23.85093 16.65085,-27.98512 18.9366,-30.5462 1.40268,-1.57163 3.60443,-2.8575 4.89278,-2.8575 4.0746,0 20.60279,6.31357 23.12126,8.83205 2.15504,2.15502 2.23785,2.87349 0.76476,6.63483 -3.73374,9.53364 -9.07672,22.15033 -10.33048,24.39399 -3.80857,6.81553 -7.9793,23.52967 -8.03096,32.18392 -0.0519,8.70604 0.15947,9.39421 4.09712,13.335 3.82128,3.8243 4.78209,4.15041 12.065,4.09491 6.11829,-0.0466 10.07347,-1.01777 17.43807,-4.28168 12.14997,-5.38471 26.10992,-13.57535 38.60437,-22.65011 2.16735,-1.57415 5.94088,-4.2692 8.38563,-5.98901 12.14855,-8.54615 14.46798,-10.40062 16.08012,-12.85675 1.53831,-2.34362 1.41641,-3.07497 -1.03102,-6.18639 -1.52656,-1.9407 -4.62126,-4.29974 -6.87711,-5.2423 C 249.927,71.95287 220.02977,43.31491 209.31567,27.12503 201.1898,14.84617 193.53288,0.3803 190.51214,-8.39966 c -1.39629,-4.05837 -3.01482,-8.01255 -3.59674,-8.78707 -1.99155,-2.65073 -5.23216,-12.84142 -9.33266,-29.34824 -3.96113,-15.94582 -4.1021,-17.42139 -4.1254,-43.18 -0.0185,-20.42066 0.49315,-29.1995 2.18344,-37.465 10.82473,-52.93279 35.78563,-99.18686 72.43214,-134.22114 28.81923,-27.55138 59.6176,-46.21867 93.56799,-56.71278 26.41471,-8.16483 32.98802,-9.07802 65.39406,-9.08489 25.76587,-0.005 31.01804,0.35388 44.54428,3.04763 l 15.33427,3.0538 19.59073,-17.5151 c 119.65352,-106.97636 212.02026,-161.94611 245.4586,-146.07857 11.94119,5.66646 15.23993,22.59717 9.054,46.46945 -11.40301,44.00565 -67.89477,136.47338 -136.77777,223.88273 -9.96494,12.64506 -10.82878,14.64572 -8.71929,20.19408 2.93201,7.71179 7.75517,33.59286 9.07004,48.66987 1.08834,12.47935 1.08718,20.72091 -0.005,33.02 -4.49649,50.65298 -19.27475,88.19215 -51.44948,130.68989 -21.59464,28.52315 -59.83038,57.91374 -93.29642,71.71403 -19.91918,8.21401 -45.79618,15.86756 -58.51935,17.30806 -27.86664,3.15502 -40.02905,3.01122 -64.53646,-0.76299 l -19.15252,-2.94955 -12.44801,11.67846 c -30.13241,28.26962 -64.34765,59.21511 -74.78262,67.63607 -6.22896,5.02674 -15.32588,12.65358 -20.21538,16.94856 -12.84018,11.27889 -43.06498,33.93919 -64.91905,48.67141 -29.63153,19.97515 -53.012855,31.36157 -74.75155,36.40312 -9.72103,2.25445 -9.87016,2.25695 -16.24924,0.27273 z M 180.87111,143.82912 c 3.44521,-1.32204 2.20306,-3.03912 -2.19852,-3.03912 -6.41419,0 -14.86967,-10.38215 -12.81539,-15.73551 0.37726,-0.98316 0.0754,-2.16485 -0.67064,-2.62598 -0.90379,-0.55857 -1.35657,1.67277 -1.35657,6.68526 0,10.61477 3.30046,14.52852 12.91362,15.31328 1.16525,0.0951 3.02263,-0.17394 4.1275,-0.59793 z M 191.76998,140.79 c 1.57747,-1.01944 1.4683,-1.2311 -0.635,-1.2311 -1.397,0 -3.39724,0.55399 -4.44499,1.2311 -1.57747,1.01944 -1.4683,1.2311 0.635,1.2311 1.397,0 3.39724,-0.55398 4.44499,-1.2311 z M 400.67285,67.72356 c 4.6999,-1.76903 9.5128,-2.90935 10.69532,-2.53404 1.18252,0.37532 3.26306,-0.15936 4.62342,-1.18817 3.68778,-2.78899 8.84531,-2.40808 10.98278,0.81113 1.993,3.00161 1.68966,3.06949 12.42046,-2.77914 3.82792,-2.08633 7.49766,-3.79334 8.15499,-3.79334 0.65733,0 2.09739,-0.75864 3.20014,-1.6859 2.3926,-2.01179 12.43654,-5.03863 13.84048,-4.17096 0.96422,0.59593 6.89282,-2.62418 13.3128,-7.23084 1.75736,-1.261 4.35068,-2.37975 5.76298,-2.48614 1.41227,-0.10643 2.28789,-0.64629 1.9458,-1.1998 -0.34207,-0.55349 0.75555,-1.00636 2.43918,-1.00636 1.68362,0 5.10617,-1.57162 7.60567,-3.4925 23.65415,-18.17832 53.71859,-47.44402 57.4672,-55.94044 1.29508,-2.93537 2.76099,-5.33706 3.25756,-5.33706 0.49659,0 3.00424,-4.14337 5.57257,-9.2075 2.56835,-5.06412 5.41922,-10.06475 6.33528,-11.1125 2.03635,-2.32905 8.29047,-17.8586 8.29047,-20.58593 0,-1.08442 1.40217,-5.69687 3.11592,-10.24989 2.18142,-5.7955 2.89389,-9.6465 2.37558,-12.84047 -0.40719,-2.50921 -0.14744,-5.28142 0.57722,-6.16046 1.82452,-2.21319 6.20073,-14.97625 7.23587,-21.1032 0.5011,-2.96604 0.27893,-6.14918 -0.53541,-7.67078 -2.74576,-5.1305 -3.80524,-14.95201 -2.49234,-23.10434 1.92097,-11.92811 0.48493,-20.15729 -4.49422,-25.754 l -4.1301,-4.64237 -3.17328,2.4961 c -1.74529,1.37285 -8.83224,9.23666 -15.74876,17.47511 -19.99279,23.81398 -70.36848,80.23255 -109.20088,122.30023 -61.13371,66.22701 -74.18958,82.6095 -74.18958,93.09301 0,5.40476 1.43944,6.52852 7.87603,6.14873 3.97031,-0.23426 4.82397,0.1365 4.82397,2.09514 0,4.93531 1.02367,5.00807 12.05288,0.85668 z M 317.49997,30.31989 C 341.64688,11.05211 407.25731,-53.55526 438.7536,-89.08 c 47.06365,-53.08319 84.6867,-103.17538 102.14307,-135.99563 7.09245,-13.33476 7.8092,-16.87003 3.84986,-18.98902 -1.44288,-0.77219 -3.6594,-3.79269 -4.92559,-6.71217 -2.25896,-5.20853 -6.20405,-9.45834 -11.32359,-12.19823 -1.41882,-0.75933 -4.18229,-4.25386 -6.14103,-7.76563 -4.25829,-7.63452 -7.1769,-8.69201 -14.1588,-5.1301 -2.847,1.45244 -6.27653,2.64079 -7.62115,2.64079 -4.95041,0 -10.79201,3.61593 -15.61493,9.6656 -2.72265,3.41517 -8.75353,11.20579 -13.40194,17.3125 -4.64842,6.10671 -8.64221,10.91255 -8.87512,10.67964 -0.2329,-0.23289 3.0747,-7.73634 7.35022,-16.67432 10.16958,-21.25955 11.06241,-27.06539 4.02374,-26.16531 -0.85614,0.10947 -3.42789,-0.80465 -5.715,-2.0314 -2.28711,-1.22675 -8.15888,-2.61945 -13.04838,-3.09488 -10.86902,-1.05687 -22.72797,-3.90306 -28.2641,-6.78349 -3.52031,-1.83161 -4.83546,-1.91949 -8.85808,-0.59191 -10.61728,3.50402 -18.98753,1.65623 -19.87455,-4.38742 -0.63507,-4.32705 -3.54673,-4.5253 -11.02435,-0.75061 -3.97732,2.00776 -11.99231,7.67821 -17.8111,12.60103 -10.50682,8.889 -12.59281,10.15467 -12.59281,7.64071 0,-6.66751 -25.72808,8.93839 -40.1717,24.36697 -4.23081,4.51932 -8.83818,9.15557 -18.55476,18.67105 -3.3118,3.24326 -6.02643,6.56973 -6.0325,7.39216 -0.006,0.82243 -1.43978,2.61917 -3.18603,3.99278 -1.74625,1.3736 -3.18008,2.98671 -3.18629,3.58467 -0.006,0.59797 -2.72082,4.33948 -6.0325,8.31447 -8.71693,10.46289 -13.60037,17.33472 -17.33231,24.38946 -1.80875,3.41918 -3.78684,6.84818 -4.39575,7.62 -0.60893,0.77181 -1.83976,3.6893 -2.7352,6.4833 -0.89544,2.794 -2.46152,6.36146 -3.48016,7.92769 -1.01864,1.56623 -1.50799,3.74433 -1.08746,4.84022 0.68417,1.78295 1.16233,1.7576 4.54556,-0.24093 4.7215,-2.78906 6.39093,-1.40295 3.13906,2.60634 -27.03775,33.3353 -28.34411,35.35807 -29.29368,45.35826 -0.5493,5.78488 -0.48985,5.89537 3.61512,6.71927 5.47536,1.09895 7.581,1.22885 9.88861,0.61002 2.17629,-0.58362 2.07329,3.47343 -0.23838,9.38885 -1.01452,2.59613 -0.99826,4.40333 0.0606,6.72725 0.79628,1.74765 1.44778,5.1509 1.44778,7.56278 0,2.41188 0.52485,4.38524 1.16633,4.38524 1.49928,0 5.22373,16.19784 4.28622,18.64098 -0.39054,1.0177 0.0235,3.22108 0.92013,4.8964 1.2886,2.40778 1.3044,3.43859 0.0754,4.91946 -1.25795,1.51572 -1.0002,2.62358 1.34984,5.80219 1.59756,2.16081 4.15733,4.24316 5.68838,4.62742 1.53105,0.38428 3.08846,1.86402 3.46093,3.28832 0.55494,2.12208 1.32182,2.43284 4.24749,1.72116 4.12565,-1.00358 6.09412,-2.3192 27.19899,-18.17843 15.10995,-11.35434 22.26362,-15.18104 16.61901,-8.89 -1.41012,1.57163 -5.41286,6.00075 -8.89499,9.8425 -6.84602,7.55309 -12.68221,15.13034 -18.58894,24.13439 -2.06041,3.14083 -4.70444,6.85361 -5.8756,8.25061 -1.17115,1.397 -2.52067,4.73133 -2.99892,7.4096 -0.47825,2.67829 -2.50232,7.53604 -4.49795,10.795 -6.68038,10.90943 -6.31283,19.2604 0.84769,19.2604 3.46512,0 7.33183,1.80367 10.80647,5.04077 2.22079,2.069 9.7281,5.71303 18.48202,8.97117 2.27013,0.84492 4.1275,2.33881 4.1275,3.31975 0,2.54662 4.85958,-0.13883 15.24,-8.4218 z m 62.47285,-6.97371 c 0.60777,0.60777 50.13796,-49.2769 61.83951,-62.28208 3.76054,-4.17951 12.66971,-13.8856 19.79814,-21.5691 7.12842,-7.6835 17.1961,-18.76839 22.37264,-24.63308 5.17652,-5.86471 11.53876,-13.00846 14.13831,-15.875 2.59955,-2.86656 4.74267,-5.64054 4.7625,-6.16442 0.0198,-0.52387 1.32192,-2.23798 2.89354,-3.80913 7.42871,-7.42647 26.36212,-31.66219 29.54791,-37.82282 1.05144,-2.03329 2.95712,-4.71357 4.23482,-5.95621 2.36711,-2.30212 4.48493,-6.70433 3.22533,-6.70433 -1.09008,0 -9.15201,8.68605 -19.53983,21.05252 -14.78863,17.60555 -61.25924,69.54657 -75.47318,84.35773 -32.72908,34.10422 -66.61192,70.45348 -70.26504,75.37981 -3.47858,4.69096 -3.65573,6.41651 -0.48448,4.7193 1.30515,-0.6985 2.63258,-1.01043 2.94983,-0.69319 z m 167.39714,-188.91755 c 0,-0.74334 -0.80697,-0.61991 -1.905,0.29138 -1.04775,0.86955 -1.905,1.77885 -1.905,2.02064 0,0.2418 0.85725,0.11062 1.905,-0.29137 1.04775,-0.40207 1.905,-1.31135 1.905,-2.02065 z m 15.94506,-63.83877 c 7.95376,-10.79338 22.44935,-32.20988 24.50159,-36.19985 2.04326,-3.97251 5.06659,-9.36045 8.80914,-15.69902 1.80549,-3.05788 2.58996,-5.86682 2.08381,-7.46154 -0.50875,-1.60293 -0.32316,-2.25557 0.49722,-1.74855 1.38949,0.85875 5.06583,-3.86305 7.5976,-9.75815 1.94861,-4.53729 1.93005,-4.70274 -0.52755,-4.70274 -1.38406,0 -1.81756,-0.72426 -1.33027,-2.2225 6.72568,-20.67918 7.07081,-29.11544 1.4581,-35.64062 -2.65867,-3.09088 -3.76136,-3.41188 -11.72036,-3.41188 -6.27574,0 -10.57019,0.79186 -15.03247,2.77184 -3.43578,1.52452 -9.67588,4.27088 -13.86687,6.10302 -4.191,1.83215 -12.06899,6.01407 -17.50665,9.29316 -5.43766,3.27909 -13.22193,7.96223 -17.2984,10.40698 -14.90293,8.93762 -20.29423,12.86964 -19.68363,14.35579 0.34331,0.83557 6.06722,5.49508 12.71981,10.3545 13.88258,10.14058 29.29081,24.74222 37.1585,35.21331 l 5.42386,7.21862 -2.21728,8.02138 c -1.2195,4.41178 -3.02342,9.28214 -4.00871,10.82304 -0.98529,1.54092 -1.4471,3.3588 -1.02625,4.03976 0.42085,0.68095 0.18435,1.59706 -0.52554,2.03579 -0.70989,0.43874 -1.29071,1.44979 -1.29071,2.24676 0,1.66011 0.48298,1.1559 5.78506,-6.0391 z m 43.727,-9.68548 c 0.0102,-0.43485 2.73256,-4.26479 6.05043,-8.51099 5.9048,-7.55694 10.38052,-13.82353 30.99954,-43.40338 29.2161,-41.91315 54.60456,-91.87645 60.19802,-118.46689 2.58395,-12.28364 2.2771,-18.31429 -1.3256,-26.05292 -2.64227,-5.67563 -4.06603,-7.13855 -9.12556,-9.37662 -14.01174,-6.19806 -38.65146,0.62518 -72.38406,20.04462 -21.93128,12.62557 -42.00584,26.54573 -80.32961,55.7024 -1.80413,1.37257 -8.13797,6.48408 -14.07522,11.3589 -33.47512,27.48501 -45.50021,38.85051 -41.1052,38.85051 0.44265,0 6.30052,-4.55345 13.0175,-10.11875 15.58775,-12.91514 17.25,-14.3224 24.9127,-21.09112 3.4925,-3.08503 6.69517,-5.61164 7.11705,-5.61465 0.42187,-0.003 2.23443,-1.29136 4.0279,-2.86298 5.46566,-4.78962 12.58249,-10.41198 14.74604,-11.64952 1.12696,-0.64462 7.47826,-5.26193 14.11401,-10.26069 40.35084,-30.39662 83.0442,-53.0853 107.31499,-57.03076 6.76555,-1.09981 17.98918,2.58186 21.23822,6.96676 13.70937,18.50209 -9.26336,74.21124 -64.53959,156.50937 -2.72725,4.06046 -6.38738,9.54441 -8.13363,12.18655 -10.3942,15.72679 -13.63472,20.45508 -18.00152,26.26619 -2.67216,3.55598 -4.85848,6.93908 -4.85848,7.51799 0,1.19247 10.11436,-9.74159 10.14207,-10.96402 z m 10.80493,-88.28351 c 2.26357,-8.69046 2.47139,-18.16582 0.49361,-22.50656 -3.55539,-7.80324 -16.03391,-9.16059 -30.04302,-3.26793 -8.37135,3.52124 -7.78906,4.54113 1.08766,1.90503 21.78967,-6.47083 30.81295,3.02406 24.65975,25.9486 -1.03116,3.84175 -1.85876,8.128 -1.83907,9.525 0.0561,3.98739 3.33888,-2.76544 5.64107,-11.60414 z"
+         id="path8404"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#ac8d5c;stroke-width:1.26999998"
+         d="M 54.26514,285.1549 C 38.91405,280.3799 35.04431,268.15205 40.5074,241.68234 44.67732,221.4783 59.98411,183.91806 74.63666,157.935 100.66064,111.78723 135.93857,55.47669 148.05484,40.74507 c 2.82286,-3.43219 6.40871,-3.40722 11.17344,0.0779 2.95109,2.15848 2.9701,2.11923 -11.82435,24.40211 -30.04353,45.25054 -51.834362,86.80677 -62.49395,119.17918 -6.07978,18.46384 -7.37402,30.33406 -4.41663,40.50715 2.47436,8.5115 6.37933,11.84115 16.336532,13.9297 6.012188,1.26107 8.032308,1.04848 18.771178,-1.97541 20.85696,-5.87297 44.32966,-18.87513 76.80392,-42.54374 23.23694,-16.93606 57.37882,-44.92762 75.565,-61.95271 2.44475,-2.28866 6.0983,-5.51437 8.11898,-7.16823 2.0207,-1.65387 3.42174,-3.76379 3.11343,-4.68871 -0.50108,-1.50321 -9.01987,5.20908 -27.46766,21.64281 -2.29363,2.04323 -4.58551,3.71496 -5.09305,3.71496 -0.50755,0 -3.20294,2.17188 -5.98976,4.82639 -21.48565,20.46566 -68.21337,53.61793 -93.96693,66.66746 -19.89318,10.08001 -24.22242,11.45689 -36.41023,11.57997 -9.3152,0.0941 -11.563647,-0.30029 -14.605,-2.56158 -6.020893,-4.47661 -8.27844,-9.69867 -8.31868,-19.24244 -0.10416,-24.70635 16.60035,-63.7192 55.43475,-129.4661 14.08792,-23.85093 16.65085,-27.98512 18.9366,-30.5462 1.40268,-1.57163 3.60443,-2.8575 4.89278,-2.8575 4.0746,0 20.60279,6.31357 23.12126,8.83205 2.15504,2.15502 2.23785,2.87349 0.76476,6.63483 -3.73374,9.53364 -9.07672,22.15033 -10.33048,24.39399 -3.80857,6.81553 -7.9793,23.52967 -8.03096,32.18392 -0.0519,8.70604 0.15947,9.39421 4.09712,13.335 3.82128,3.8243 4.78209,4.15041 12.065,4.09491 6.11829,-0.0466 10.07347,-1.01777 17.43807,-4.28168 12.14997,-5.38471 26.10992,-13.57535 38.60437,-22.65011 2.16735,-1.57415 5.94088,-4.2692 8.38563,-5.98901 12.14855,-8.54615 14.46798,-10.40062 16.08012,-12.85675 1.53831,-2.34362 1.41641,-3.07497 -1.03102,-6.18639 -1.52656,-1.9407 -4.62126,-4.29974 -6.87711,-5.2423 C 249.927,71.95287 220.02977,43.31491 209.31567,27.12503 201.1898,14.84617 193.53288,0.3803 190.51214,-8.39966 c -1.39629,-4.05837 -3.01482,-8.01255 -3.59674,-8.78707 -1.99155,-2.65073 -5.23216,-12.84142 -9.33266,-29.34824 -3.96113,-15.94582 -4.1021,-17.42139 -4.1254,-43.18 -0.0185,-20.42066 0.49315,-29.1995 2.18344,-37.465 10.82473,-52.93279 35.78563,-99.18686 72.43214,-134.22114 28.81923,-27.55138 59.6176,-46.21867 93.56799,-56.71278 26.41471,-8.16483 32.98802,-9.07802 65.39406,-9.08489 25.76587,-0.005 31.01804,0.35388 44.54428,3.04763 l 15.33427,3.0538 19.59073,-17.5151 c 119.65352,-106.97636 212.02026,-161.94611 245.4586,-146.07857 11.94119,5.66646 15.23993,22.59717 9.054,46.46945 -11.40301,44.00565 -67.89477,136.47338 -136.77777,223.88273 -9.96494,12.64506 -10.82878,14.64572 -8.71929,20.19408 2.93201,7.71179 7.75517,33.59286 9.07004,48.66987 1.08834,12.47935 1.08718,20.72091 -0.005,33.02 -4.49649,50.65298 -19.27475,88.19215 -51.44948,130.68989 -21.59464,28.52315 -59.83038,57.91374 -93.29642,71.71403 -19.91918,8.21401 -45.79618,15.86756 -58.51935,17.30806 -27.86664,3.15502 -40.02905,3.01122 -64.53646,-0.76299 l -19.15252,-2.94955 -12.44801,11.67846 c -30.13241,28.26962 -64.34765,59.21511 -74.78262,67.63607 -6.22896,5.02674 -15.32588,12.65358 -20.21538,16.94856 -12.84018,11.27889 -43.06498,33.93919 -64.91905,48.67141 -29.63153,19.97515 -53.012855,31.36157 -74.75155,36.40312 -9.72103,2.25445 -9.87016,2.25695 -16.24924,0.27273 z M 180.87111,143.82912 c 3.44521,-1.32204 2.20306,-3.03912 -2.19852,-3.03912 -6.41419,0 -14.86967,-10.38215 -12.81539,-15.73551 0.37726,-0.98316 0.0754,-2.16485 -0.67064,-2.62598 -0.90379,-0.55857 -1.35657,1.67277 -1.35657,6.68526 0,10.61477 3.30046,14.52852 12.91362,15.31328 1.16525,0.0951 3.02263,-0.17394 4.1275,-0.59793 z M 191.76998,140.79 c 1.57747,-1.01944 1.4683,-1.2311 -0.635,-1.2311 -1.397,0 -3.39724,0.55399 -4.44499,1.2311 -1.57747,1.01944 -1.4683,1.2311 0.635,1.2311 1.397,0 3.39724,-0.55398 4.44499,-1.2311 z M 385.49394,74.64189 c 8.89052,-4.67957 21.38103,-9.03433 27.24485,-9.49878 3.13712,-0.24848 7.04287,-1.16842 8.67949,-2.0443 2.63222,-1.40873 3.23768,-1.24512 5.24615,1.41757 1.37653,1.8249 2.95957,2.67546 4.02053,2.16021 0.9625,-0.46745 4.88194,-2.55691 8.70987,-4.64325 3.82792,-2.08633 7.61557,-3.79334 8.41699,-3.79334 0.80142,0 2.11143,-0.78837 2.91112,-1.75195 1.51099,-1.82063 6.57706,-3.29858 13.94556,-4.06842 2.45819,-0.25682 6.75465,-2.366 10.05092,-4.9341 5.20789,-4.05742 7.75325,-5.13936 10.83244,-4.60445 0.57579,0.10008 0.68494,-0.40381 0.24255,-1.11961 -0.46423,-0.75113 0.49009,-1.30147 2.25679,-1.30147 1.68362,0 5.10617,-1.57162 7.60567,-3.4925 23.65415,-18.17832 53.71859,-47.44402 57.4672,-55.94044 1.29508,-2.93537 2.76099,-5.33706 3.25756,-5.33706 0.49659,0 3.00424,-4.14337 5.57257,-9.2075 2.56835,-5.06412 5.41922,-10.06475 6.33528,-11.1125 2.03635,-2.32905 8.29047,-17.8586 8.29047,-20.58593 0,-1.08442 1.40217,-5.69687 3.11592,-10.24989 2.18142,-5.7955 2.89389,-9.6465 2.37558,-12.84047 -0.40719,-2.50921 -0.14744,-5.28142 0.57722,-6.16046 1.7976,-2.18051 6.1948,-14.94114 7.20113,-20.8976 0.55942,-3.31119 0.13686,-6.48087 -1.32348,-9.92746 -1.53126,-3.61398 -2.03698,-7.78948 -1.76683,-14.58819 0.20815,-5.23875 0.31677,-13.62929 0.24137,-18.64565 -0.12975,-8.63204 -0.37626,-9.36693 -4.60154,-13.71779 l -4.46443,-4.59715 -5.9344,5.46279 c -3.26392,3.00454 -13.67445,14.32106 -23.1345,25.1478 -9.46006,10.82675 -20.66756,23.42326 -24.90556,27.99225 -14.9226,16.08802 -38.18142,43.57003 -37.8365,44.70661 0.19192,0.63247 -0.1223,0.85872 -0.69818,0.50278 -1.30073,-0.8039 -45.50308,47.25337 -45.02181,48.94821 0.19192,0.67593 -0.14406,0.92425 -0.74662,0.55184 -1.12047,-0.69248 -8.73816,7.31064 -40.96206,43.03456 -21.88899,24.26647 -29.58132,33.32625 -35.85277,42.22625 -5.1401,7.29448 -4.41806,8.20711 4.13434,5.22572 3.32048,-1.15753 4.35138,-1.09493 5.22882,0.3175 0.63987,1.03 0.58334,1.93477 -0.13638,2.18261 -1.86152,0.64106 -1.47344,3.59611 0.76642,5.83596 2.71057,2.71057 6.65534,2.50727 12.65825,-0.6524 z m -3.22397,-32.83693 c 0,-0.65728 0.56496,-1.54422 1.25546,-1.97097 0.71991,-0.44493 0.93281,0.0649 0.49912,1.19505 -0.84428,2.20017 -1.75458,2.60272 -1.75458,0.77592 z m -77.27525,-0.21933 c 4.81822,-3.5719 9.34593,-7.5884 10.06156,-8.92556 0.71562,-1.33717 1.79819,-2.12401 2.40572,-1.74854 0.60752,0.37547 2.23273,-0.75152 3.61156,-2.50442 1.37883,-1.75291 2.98372,-3.18711 3.56641,-3.18711 0.58268,0 3.15579,-2.14312 5.71803,-4.7625 2.56224,-2.61937 9.86033,-9.62025 16.21799,-15.5575 19.05317,-17.79325 71.26241,-70.67764 85.8844,-86.995 49.24916,-54.95941 86.45646,-103.53217 106.039,-138.42999 2.35173,-4.191 4.74173,-7.81168 5.31114,-8.04597 0.56939,-0.23429 0.74069,-0.90259 0.38067,-1.48512 -0.36002,-0.58252 0.31978,-2.03352 1.51069,-3.22443 1.1909,-1.1909 3.16738,-5.0946 4.39215,-8.67488 2.14167,-6.26054 2.13774,-6.58249 -0.10261,-8.4146 -1.28122,-1.04775 -3.87749,-4.40736 -5.76949,-7.46579 -3.758,-6.07483 -9.27293,-11.48087 -11.77659,-11.54408 -0.87429,-0.0221 -3.65743,-2.9184 -6.18477,-6.43626 -5.41263,-7.53398 -10.43131,-10.60832 -14.69675,-9.00293 -1.61088,0.60629 -4.71064,0.83634 -6.88837,0.51122 -5.09637,-0.76086 -11.17068,2.38497 -17.47179,9.04849 -3.03584,3.21046 -4.58618,4.2323 -4.04661,2.66718 2.50077,-7.25392 3.36807,-14.55962 2.02613,-17.06706 -1.46348,-2.73455 -5.673,-3.54359 -7.02823,-1.35077 -1.28787,2.08381 -4.52988,1.4465 -6.78143,-1.33308 -1.7608,-2.17376 -3.94572,-2.69239 -13.24763,-3.14455 -6.1265,-0.29779 -12.28193,-1.15308 -13.67875,-1.90064 -1.39683,-0.74756 -3.02299,-1.06049 -3.6137,-0.69542 -0.59072,0.36509 -2.07347,-0.44059 -3.29502,-1.79038 -2.42895,-2.68397 -6.49416,-3.40253 -24.9313,-4.40684 -10.91725,-0.59469 -12.51537,-0.39827 -15.11682,1.85796 -1.59995,1.38762 -3.6788,2.52295 -4.61966,2.52295 -0.94087,0 -1.71068,0.45762 -1.71068,1.01692 0,0.55932 -2.50829,2.23556 -5.57398,3.72499 -3.06568,1.48942 -7.65838,4.53818 -10.20599,6.77501 -2.5476,2.23683 -6.34719,4.26666 -8.4435,4.51074 -2.09631,0.24409 -5.07599,1.12213 -6.6215,1.95119 -1.54551,0.82907 -5.2389,1.38292 -8.20753,1.23076 -2.96862,-0.15216 -5.3975,0.27021 -5.3975,0.93858 0,0.66838 -2.42887,2.39363 -5.3975,3.83392 -2.96862,1.44028 -7.66506,5.23158 -10.4365,8.42512 l -5.03902,5.80644 -6.54869,-0.81821 c -6.44096,-0.80474 -6.64043,-0.72731 -12.1236,4.70567 -3.06619,3.03812 -10.62663,10.09587 -16.80096,15.68387 -6.17435,5.588 -12.25447,12.16025 -13.51139,14.605 -1.25692,2.44475 -5.50056,7.874 -9.43032,12.065 -5.45627,5.81897 -7.02022,8.3357 -6.61701,10.64811 0.2904,1.66546 -0.59578,4.93278 -1.96929,7.2607 -1.3735,2.32793 -2.17247,4.75815 -1.77548,5.40049 0.39699,0.64234 -0.12916,1.87412 -1.16921,2.7373 -1.04006,0.86317 -1.89102,2.39779 -1.89102,3.41025 0,2.46467 -5.74366,12.14575 -8.90134,15.00341 -1.39077,1.25862 -2.52866,3.59485 -2.52866,5.19162 0,1.59677 -0.57967,3.26147 -1.28815,3.69933 -3.74843,2.31666 -5.96689,10.36253 -5.8332,21.1557 0.0831,6.70698 -0.35979,10.92413 -1.13365,10.795 -0.6985,-0.11659 -1.45083,1.35971 -1.67185,3.28058 -0.22102,1.92088 -0.87344,3.49251 -1.44982,3.49251 -0.57639,0 -1.42257,1.49247 -1.88039,3.31663 -0.45783,1.82413 -2.71457,5.11026 -5.01496,7.3025 -2.3004,2.19222 -5.18493,6.63834 -6.41005,9.88023 -2.10402,5.56763 -2.10717,6.07803 -0.0566,9.2075 1.19397,1.82223 3.24813,3.31313 4.56479,3.31313 1.31666,0 2.4255,0.42863 2.46405,0.9525 0.0386,0.52388 0.26526,5.02308 0.50374,9.99822 0.23849,4.97515 1.09061,10.27333 1.89362,11.77375 0.80299,1.50042 1.13736,3.25006 0.74302,3.88811 -0.39433,0.63803 0.49858,3.49211 1.98423,6.3424 2.58955,4.96816 3.20748,6.89074 6.71718,20.89915 1.05149,4.19685 2.83874,7.30441 5.4781,9.525 2.15458,1.81273 4.14606,4.43887 4.42547,5.83587 0.70132,3.50631 7.19378,2.87399 14.50911,-1.41309 5.35998,-3.14115 7.95941,-5.63528 3.36291,-3.22666 -1.26577,0.66327 -3.45258,0.70013 -5.08,0.0856 -2.22036,-0.83842 -2.8575,-2.07923 -2.8575,-5.56494 0,-5.03706 3.03334,-6.32308 4.2191,-1.78872 0.58453,2.23525 1.31682,2.54846 4.27562,1.82873 4.12564,-1.00358 6.09412,-2.3192 27.19899,-18.17843 15.10995,-11.35434 22.26361,-15.18104 16.61901,-8.89 -1.41012,1.57163 -5.41287,6.00075 -8.89499,9.8425 -6.84602,7.55309 -12.68222,15.13034 -18.58895,24.13438 -2.06041,3.14084 -4.70443,6.85362 -5.87559,8.25062 -1.17116,1.397 -2.52067,4.73132 -2.99893,7.4096 -0.47824,2.67829 -2.50232,7.53604 -4.49794,10.795 -6.68038,10.90943 -6.31283,19.2604 0.84768,19.2604 3.46512,0 7.33184,1.80367 10.80647,5.04077 2.2208,2.069 9.72811,5.71303 18.48203,8.97117 2.27012,0.84491 4.1275,2.32413 4.1275,3.28714 0,2.25961 -1.15627,2.20578 -7.14741,-0.33269 -2.70467,-1.14599 -6.5905,-2.28962 -8.63514,-2.5414 -4.16305,-0.51263 -9.61746,-3.66834 -9.61746,-5.56425 0,-0.6824 -0.76868,-1.24073 -1.70816,-1.24073 -0.9395,0 -2.74257,-1.143 -4.00684,-2.54 -1.26427,-1.397 -3.06342,-2.54 -3.99812,-2.54 -3.86108,0 -11.797,-4.48537 -12.2869,-6.94455 -0.31674,-1.58988 -0.58046,-1.03589 -0.68703,1.44318 -0.26985,6.27785 0.858,7.75785 7.62474,10.00539 3.46829,1.15198 6.99029,3.24635 7.82665,4.65414 0.83637,1.4078 6.23553,4.82995 11.99816,7.6048 5.76262,2.77485 10.4775,5.56761 10.4775,6.20611 0,2.69029 4.68556,0.75229 12.89474,-5.33344 z M 237.54998,-77.24291 c -2.31968,-1.62477 -2.63288,-2.69631 -1.92079,-6.5717 1.22869,-6.68687 1.27244,-10.59408 0.1546,-13.80375 -0.83322,-2.39244 -0.34193,-3.09227 3.21894,-4.58527 2.31125,-0.96906 4.57138,-1.55896 5.02253,-1.31091 0.45116,0.24806 0.24058,2.59718 -0.46797,5.22028 -1.42413,5.27231 -1.48197,8.56447 -0.28706,16.33631 0.93635,6.09002 -1.21981,7.86727 -5.72025,4.71504 z m 224.77037,-158.02945 c -0.0222,-0.42755 3.26879,-7.68591 7.31334,-16.12969 6.81025,-14.21772 7.47667,-15.15547 9.01726,-12.68859 0.9178,1.46963 2.03347,2.11159 2.48877,1.43205 0.45388,-0.67742 1.90975,-1.63305 3.23526,-2.12364 1.97044,-0.72927 1.78972,-0.13328 -0.99089,3.26764 -1.8705,2.28778 -6.66975,8.42772 -10.66502,13.64432 -9.96113,13.00623 -10.35285,13.48078 -10.39872,12.59791 z m -156.88537,-5.90958 c 0.6985,-0.51318 1.46875,-2.3618 1.71168,-4.10805 0.56306,-4.04758 1.15906,-4.62226 2.94327,-2.83805 1.68598,1.68598 -1.76661,7.93105 -4.36673,7.8986 -0.97058,-0.0127 -1.07928,-0.37132 -0.28822,-0.9525 z m 9.525,-13.5581 c 0,-2.08653 1.42599,-3.18126 5.05405,-3.87996 2.39345,-0.46094 2.41892,-0.32623 0.49371,2.61201 -2.22835,3.40089 -5.54776,4.15955 -5.54776,1.26795 z m 48.52395,294.3073 c 2.64882,-2.58651 4.81604,-5.57927 4.81604,-6.65056 0,-1.91407 -11.30144,8.40727 -11.38843,10.4008 -0.0851,1.9494 1.88895,0.82301 6.57239,-3.75024 z M 397.58409,6.695 c 11.77135,-11.58574 21.99067,-20.70146 22.7096,-20.25714 0.71893,0.44432 2.44379,-1.0778 3.83299,-3.38249 3.65129,-6.05748 15.50985,-18.81143 16.76787,-18.03392 0.58859,0.36376 1.57963,-0.65887 2.20228,-2.27253 1.04201,-2.70043 6.85969,-8.64892 8.45871,-8.64892 1.041,0 6.58062,-6.45359 9.61816,-11.20507 1.48382,-2.32102 4.11233,-4.75785 5.84115,-5.41514 2.07953,-0.79063 2.89197,-1.85009 2.40062,-3.13051 -0.44543,-1.16078 0.13829,-2.26909 1.4584,-2.76909 3.11761,-1.1808 11.74096,-10.37395 13.11094,-13.97724 0.63271,-1.66418 1.72404,-2.67126 2.42515,-2.23795 0.70112,0.43332 1.82565,-0.53242 2.49897,-2.14608 1.61515,-3.87085 4.46392,-7.37892 5.99216,-7.37892 2.9712,0 7.40795,-6.59472 7.9217,-11.77472 0.0535,-0.53885 1.52592,-1.63071 3.27217,-2.42635 1.74625,-0.79566 3.175,-1.94954 3.175,-2.56421 0,-1.96708 9.30059,-14.25126 29.18392,-38.54601 7.31476,-8.93764 10.9112,-13.79156 17.35912,-23.4287 2.5704,-3.84175 5.05006,-7.27075 5.51034,-7.62 0.46029,-0.34925 1.25329,-1.778 1.76222,-3.175 1.02343,-2.80929 -5.4864,3.90452 -22.61973,23.32852 -6.24201,7.07657 -11.78359,12.86647 -12.3146,12.86647 -0.53103,0 -2.9666,3.01478 -5.41238,6.69951 -8.48559,12.78409 -95.12522,107.56264 -90.23393,98.71049 0.9119,-1.65031 0.76124,-1.66856 -1.12686,-0.13652 -1.19872,0.97267 -1.89586,2.0521 -1.5492,2.39875 0.96641,0.96641 -14.94177,18.99657 -38.87565,44.06123 -11.71817,12.2718 -14.58819,15.84062 -16.24641,20.20208 -0.87901,2.31198 -0.84691,3.32446 0.10541,3.32446 0.75318,0 11.0005,-9.47925 22.77186,-21.065 z M 260.34998,5.92727 c 0,-2.5963 -2.31569,-0.65169 -2.90692,2.44108 -0.62015,3.24418 -0.54763,3.31057 1.12196,1.02726 0.98172,-1.34259 1.78496,-2.90334 1.78496,-3.46834 z m 302.96504,-235.33741 c 7.95376,-10.79338 22.44935,-32.20988 24.50159,-36.19985 2.04326,-3.97251 5.06659,-9.36045 8.80914,-15.69902 1.80549,-3.05788 2.58996,-5.86682 2.08381,-7.46154 -0.50875,-1.60293 -0.32316,-2.25557 0.49722,-1.74855 1.38949,0.85875 5.06583,-3.86305 7.5976,-9.75815 1.94861,-4.53729 1.93005,-4.70274 -0.52755,-4.70274 -1.38406,0 -1.81756,-0.72426 -1.33027,-2.2225 6.72568,-20.67918 7.07081,-29.11544 1.4581,-35.64062 -2.65867,-3.09088 -3.76136,-3.41188 -11.72036,-3.41188 -6.27574,0 -10.57019,0.79186 -15.03247,2.77184 -3.43578,1.52452 -9.67588,4.27088 -13.86687,6.10302 -4.191,1.83215 -12.06899,6.01407 -17.50665,9.29316 -5.43766,3.27909 -13.22193,7.96223 -17.2984,10.40698 -14.90293,8.93762 -20.29423,12.86964 -19.68363,14.35579 0.34331,0.83557 6.06722,5.49508 12.71981,10.3545 13.88258,10.14058 29.29081,24.74222 37.1585,35.21331 l 5.42386,7.21862 -2.21728,8.02138 c -1.2195,4.41178 -3.02342,9.28214 -4.00871,10.82304 -0.98529,1.54092 -1.4471,3.3588 -1.02625,4.03976 0.42085,0.68095 0.18435,1.59706 -0.52554,2.03579 -0.70989,0.43874 -1.29071,1.44979 -1.29071,2.24676 0,1.66011 0.48298,1.1559 5.78506,-6.0391 z m 43.727,-9.68548 c 0.0102,-0.43485 2.73256,-4.26479 6.05043,-8.51099 5.9048,-7.55693 10.38052,-13.82353 30.99954,-43.40338 29.2161,-41.91315 54.60456,-91.87645 60.19802,-118.46689 2.58395,-12.28364 2.2771,-18.31429 -1.3256,-26.05292 -2.64227,-5.67563 -4.06603,-7.13855 -9.12556,-9.37662 -14.01174,-6.19806 -38.65146,0.62518 -72.38406,20.04462 -21.93128,12.62557 -42.00584,26.54573 -80.32961,55.7024 -1.80413,1.37257 -8.13797,6.48408 -14.07522,11.3589 -33.47512,27.48501 -45.50021,38.85051 -41.1052,38.85051 0.44265,0 6.30052,-4.55345 13.0175,-10.11875 15.58775,-12.91514 17.25,-14.3224 24.9127,-21.09112 3.4925,-3.08503 6.69517,-5.61164 7.11705,-5.61465 0.42187,-0.003 2.23443,-1.29136 4.0279,-2.86298 5.46566,-4.78962 12.58249,-10.41198 14.74604,-11.64952 1.12696,-0.64462 7.47826,-5.26193 14.11401,-10.26069 40.35084,-30.39662 83.0442,-53.0853 107.31499,-57.03076 6.76555,-1.09981 17.98918,2.58186 21.23822,6.96676 13.70937,18.50209 -9.26336,74.21124 -64.53959,156.50937 -2.72725,4.06046 -6.38738,9.54441 -8.13363,12.18655 -10.3942,15.72679 -13.63472,20.45508 -18.00152,26.26619 -2.67216,3.55598 -4.85848,6.93908 -4.85848,7.51799 0,1.19247 10.11436,-9.74159 10.14207,-10.96402 z m 10.80493,-88.28351 c 2.26357,-8.69046 2.47139,-18.16582 0.49361,-22.50656 -3.55539,-7.80324 -16.03391,-9.16059 -30.04302,-3.26793 -8.37135,3.52124 -7.78906,4.54113 1.08766,1.90503 21.78967,-6.47083 30.81295,3.02406 24.65975,25.9486 -1.03116,3.84175 -1.85876,8.128 -1.83907,9.525 0.0561,3.98739 3.33888,-2.76544 5.64107,-11.60414 z"
+         id="path8402"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#988973;stroke-width:1.26999998"
+         d="M 54.26514,285.1549 C 38.91405,280.3799 35.04431,268.15205 40.5074,241.68234 44.67732,221.4783 59.98411,183.91806 74.63666,157.935 100.66064,111.78723 135.93857,55.47669 148.05484,40.74507 c 2.82286,-3.43219 6.40871,-3.40722 11.17344,0.0779 2.95109,2.15848 2.9701,2.11923 -11.82435,24.40211 -30.04353,45.25054 -51.834362,86.80677 -62.49395,119.17918 -6.07978,18.46384 -7.37402,30.33406 -4.41663,40.50715 2.47436,8.5115 6.37933,11.84115 16.336532,13.9297 6.012188,1.26107 8.032308,1.04848 18.771178,-1.97541 20.85696,-5.87297 44.32966,-18.87513 76.80392,-42.54374 23.23694,-16.93606 57.37882,-44.92762 75.565,-61.95271 2.44475,-2.28866 6.0983,-5.51437 8.11898,-7.16823 2.0207,-1.65387 3.42174,-3.76379 3.11343,-4.68871 -0.50108,-1.50321 -9.01987,5.20908 -27.46766,21.64281 -2.29363,2.04323 -4.58551,3.71496 -5.09305,3.71496 -0.50755,0 -3.20294,2.17188 -5.98976,4.82639 -21.48565,20.46566 -68.21337,53.61793 -93.96693,66.66746 -19.89318,10.08001 -24.22242,11.45689 -36.41023,11.57997 -9.3152,0.0941 -11.563647,-0.30029 -14.605,-2.56158 -6.020893,-4.47661 -8.27844,-9.69867 -8.31868,-19.24244 -0.10416,-24.70635 16.60035,-63.7192 55.43475,-129.4661 14.08792,-23.85093 16.65085,-27.98512 18.9366,-30.5462 1.40268,-1.57163 3.60443,-2.8575 4.89278,-2.8575 4.0746,0 20.60279,6.31357 23.12126,8.83205 2.15504,2.15502 2.23785,2.87349 0.76476,6.63483 -3.73374,9.53364 -9.07672,22.15033 -10.33048,24.39399 -3.80857,6.81553 -7.9793,23.52967 -8.03096,32.18392 -0.0519,8.70604 0.15947,9.39421 4.09712,13.335 3.82128,3.8243 4.78209,4.15041 12.065,4.09491 6.11829,-0.0466 10.07347,-1.01777 17.43807,-4.28168 12.14997,-5.38471 26.10992,-13.57535 38.60437,-22.65011 2.16735,-1.57415 5.94088,-4.2692 8.38563,-5.98901 12.14855,-8.54615 14.46798,-10.40062 16.08012,-12.85675 1.53831,-2.34362 1.41641,-3.07497 -1.03102,-6.18639 -1.52656,-1.9407 -4.62126,-4.29974 -6.87711,-5.2423 C 249.927,71.95287 220.02977,43.31491 209.31567,27.12503 201.1898,14.84617 193.53288,0.3803 190.51214,-8.39966 c -1.39629,-4.05837 -3.01482,-8.01255 -3.59674,-8.78707 -1.99155,-2.65073 -5.23216,-12.84142 -9.33266,-29.34824 -3.96113,-15.94582 -4.1021,-17.42139 -4.1254,-43.18 -0.0185,-20.42066 0.49315,-29.1995 2.18344,-37.465 10.82473,-52.93279 35.78563,-99.18686 72.43214,-134.22114 28.81923,-27.55138 59.6176,-46.21867 93.56799,-56.71278 26.41471,-8.16483 32.98802,-9.07802 65.39406,-9.08489 25.76587,-0.005 31.01804,0.35388 44.54428,3.04763 l 15.33427,3.0538 19.59073,-17.5151 c 119.65352,-106.97636 212.02026,-161.94611 245.4586,-146.07857 11.94119,5.66646 15.23993,22.59717 9.054,46.46945 -11.40301,44.00565 -67.89477,136.47338 -136.77777,223.88273 -9.96494,12.64506 -10.82878,14.64572 -8.71929,20.19408 2.93201,7.71179 7.75517,33.59286 9.07004,48.66987 1.08834,12.47935 1.08718,20.72091 -0.005,33.02 -4.49649,50.65298 -19.27475,88.19215 -51.44948,130.68989 -21.59464,28.52315 -59.83038,57.91374 -93.29642,71.71403 -19.91918,8.21401 -45.79618,15.86756 -58.51935,17.30806 -27.86664,3.15502 -40.02905,3.01122 -64.53646,-0.76299 l -19.15252,-2.94955 -12.44801,11.67846 c -30.13241,28.26962 -64.34765,59.21511 -74.78262,67.63607 -6.22896,5.02674 -15.32588,12.65358 -20.21538,16.94856 -12.84018,11.27889 -43.06498,33.93919 -64.91905,48.67141 -29.63153,19.97515 -53.012855,31.36157 -74.75155,36.40312 -9.72103,2.25445 -9.87016,2.25695 -16.24924,0.27273 z M 180.87111,143.82912 c 3.44521,-1.32204 2.20306,-3.03912 -2.19852,-3.03912 -6.41419,0 -14.86967,-10.38215 -12.81539,-15.73551 0.37726,-0.98316 0.0754,-2.16485 -0.67064,-2.62598 -0.90379,-0.55857 -1.35657,1.67277 -1.35657,6.68526 0,10.61477 3.30046,14.52852 12.91362,15.31328 1.16525,0.0951 3.02263,-0.17394 4.1275,-0.59793 z M 191.76998,140.79 c 1.57747,-1.01944 1.4683,-1.2311 -0.635,-1.2311 -1.397,0 -3.39724,0.55399 -4.44499,1.2311 -1.57747,1.01944 -1.4683,1.2311 0.635,1.2311 1.397,0 3.39724,-0.55398 4.44499,-1.2311 z M 385.49394,74.64189 c 8.89052,-4.67957 21.38103,-9.03433 27.24485,-9.49878 3.13712,-0.24848 7.04287,-1.16842 8.67949,-2.0443 2.63222,-1.40873 3.23768,-1.24512 5.24615,1.41757 1.37653,1.8249 2.95957,2.67546 4.02053,2.16021 0.9625,-0.46745 4.88194,-2.55691 8.70987,-4.64325 3.82792,-2.08633 7.61557,-3.79334 8.41699,-3.79334 0.80142,0 2.11143,-0.78837 2.91112,-1.75195 1.51099,-1.82063 6.57706,-3.29858 13.94556,-4.06842 2.45819,-0.25682 6.75465,-2.366 10.05092,-4.9341 5.20789,-4.05742 7.75325,-5.13936 10.83244,-4.60445 0.57579,0.10008 0.68494,-0.40381 0.24255,-1.11961 -0.46423,-0.75113 0.49009,-1.30147 2.25679,-1.30147 1.68362,0 5.10617,-1.57162 7.60567,-3.4925 23.65415,-18.17832 53.71859,-47.44402 57.4672,-55.94044 1.29508,-2.93537 2.76099,-5.33706 3.25756,-5.33706 0.49659,0 3.00424,-4.14337 5.57257,-9.2075 2.56835,-5.06412 5.41922,-10.06475 6.33528,-11.1125 2.03635,-2.32905 8.29047,-17.8586 8.29047,-20.58593 0,-1.08442 1.40217,-5.69687 3.11592,-10.24989 2.18142,-5.7955 2.89389,-9.6465 2.37558,-12.84047 -0.40719,-2.50921 -0.14744,-5.28142 0.57722,-6.16046 1.7976,-2.18051 6.1948,-14.94114 7.20113,-20.8976 0.55942,-3.31119 0.13686,-6.48087 -1.32348,-9.92746 -1.53126,-3.61398 -2.03698,-7.78948 -1.76683,-14.58819 0.20815,-5.23875 0.31677,-13.62929 0.24137,-18.64565 -0.12975,-8.63204 -0.37626,-9.36693 -4.60154,-13.71779 l -4.46443,-4.59715 -5.9344,5.46279 c -3.26392,3.00454 -13.67445,14.32106 -23.1345,25.1478 -9.46006,10.82675 -20.66756,23.42867 -24.90556,28.00428 -4.23802,4.57561 -12.84693,14.29111 -19.13092,21.59 -11.62285,13.5 -23.39669,26.40099 -65.42722,71.69072 -45.38115,48.90017 -67.92389,74.42203 -76.5598,86.6775 -5.1401,7.29448 -4.41806,8.20711 4.13434,5.22572 3.32048,-1.15753 4.35138,-1.09493 5.22882,0.3175 0.63987,1.03 0.58334,1.93477 -0.13638,2.18261 -1.86152,0.64106 -1.47344,3.59611 0.76642,5.83596 2.71057,2.71057 6.65534,2.50727 12.65825,-0.6524 z M 303.41457,42.76576 c 20.48966,-15.3353 62.74435,-54.53835 99.07123,-91.91628 50.18322,-51.63519 103.2434,-116.42588 127.42807,-155.59979 17.58218,-28.47932 24.51732,-43.66913 20.62055,-45.16446 -1.00549,-0.38584 -3.58218,-3.55219 -5.72599,-7.03634 -4.14561,-6.73746 -9.74098,-12.36255 -12.36308,-12.42875 -0.87429,-0.0221 -3.65743,-2.9184 -6.18477,-6.43626 -5.41263,-7.53398 -10.43131,-10.60832 -14.69675,-9.00293 -1.61088,0.60629 -4.71064,0.83634 -6.88837,0.51122 -5.09637,-0.76086 -11.17068,2.38497 -17.47179,9.04849 -3.03584,3.21046 -4.58618,4.2323 -4.04661,2.66718 2.50077,-7.25392 3.36807,-14.55962 2.02613,-17.06706 -1.46348,-2.73455 -5.673,-3.54359 -7.02823,-1.35077 -1.28787,2.08381 -4.52988,1.4465 -6.78143,-1.33308 -1.7608,-2.17376 -3.94572,-2.69239 -13.24763,-3.14455 -6.1265,-0.29779 -12.28193,-1.15308 -13.67875,-1.90064 -1.39683,-0.74756 -3.02299,-1.06049 -3.6137,-0.69542 -0.59072,0.36509 -2.07347,-0.44059 -3.29502,-1.79038 -2.42895,-2.68397 -6.49416,-3.40253 -24.9313,-4.40684 -10.91725,-0.59469 -12.51537,-0.39827 -15.11682,1.85796 -1.59995,1.38762 -3.6788,2.52295 -4.61966,2.52295 -0.94087,0 -1.71068,0.45762 -1.71068,1.01692 0,0.55932 -2.50829,2.23556 -5.57398,3.72499 -3.06568,1.48942 -7.65838,4.53818 -10.20599,6.77501 -2.5476,2.23683 -6.34719,4.26666 -8.4435,4.51074 -2.09631,0.24409 -5.07599,1.12213 -6.6215,1.95119 -1.54551,0.82907 -5.2389,1.38292 -8.20753,1.23076 -2.96862,-0.15216 -5.3975,0.27021 -5.3975,0.93858 0,0.66838 -2.42887,2.39363 -5.3975,3.83392 -2.96862,1.44028 -7.66506,5.23158 -10.4365,8.42512 l -5.03902,5.80644 -6.54869,-0.81821 c -6.44096,-0.80474 -6.64043,-0.72731 -12.1236,4.70567 -3.06619,3.03812 -10.62663,10.09587 -16.80096,15.68387 -6.17435,5.588 -12.25447,12.16025 -13.51139,14.605 -1.25692,2.44475 -5.50056,7.874 -9.43032,12.065 -5.45627,5.81897 -7.02022,8.3357 -6.61701,10.64811 0.2904,1.66546 -0.59578,4.93278 -1.96929,7.2607 -1.3735,2.32793 -2.17247,4.75815 -1.77548,5.40049 0.39699,0.64234 -0.12916,1.87412 -1.16921,2.7373 -1.04006,0.86317 -1.89102,2.39779 -1.89102,3.41025 0,2.46467 -5.74366,12.14575 -8.90134,15.00341 -1.39077,1.25862 -2.52866,3.59485 -2.52866,5.19162 0,1.59677 -0.57967,3.26147 -1.28815,3.69933 -3.74843,2.31666 -5.96689,10.36253 -5.8332,21.1557 0.0831,6.70698 -0.35979,10.92413 -1.13365,10.795 -0.6985,-0.11659 -1.45083,1.35971 -1.67185,3.28058 -0.22102,1.92088 -0.87344,3.49251 -1.44982,3.49251 -0.57639,0 -1.42257,1.49247 -1.88039,3.31663 -0.45783,1.82413 -2.71457,5.11026 -5.01496,7.3025 -2.3004,2.19222 -5.18493,6.63834 -6.41005,9.88023 -2.10402,5.56763 -2.10717,6.07803 -0.0566,9.2075 1.19397,1.82223 3.24813,3.31313 4.56479,3.31313 1.31666,0 2.4255,0.42863 2.46405,0.9525 0.0386,0.52388 0.26526,5.02308 0.50374,9.99822 0.23849,4.97515 1.09061,10.27333 1.89362,11.77375 0.80299,1.50042 1.13736,3.25006 0.74302,3.88811 -0.39433,0.63803 0.49858,3.49211 1.98423,6.3424 2.58955,4.96816 3.20748,6.89074 6.71718,20.89915 1.05149,4.19685 2.83874,7.30441 5.4781,9.525 2.15458,1.81273 4.14629,4.43887 4.42598,5.83587 0.73558,3.67394 6.33589,2.9154 15.877,-2.15046 4.31639,-2.29179 8.2587,-3.75618 8.76069,-3.2542 0.50198,0.50199 -1.15104,3.5616 -3.67338,6.79914 -4.38759,5.63166 -7.21162,13.0648 -9.10363,23.96163 -0.48094,2.76987 -1.68267,6.59911 -2.67054,8.50945 -1.88317,3.64163 -2.31088,8.40027 -1.05029,11.68532 0.41021,1.06899 3.58354,2.88615 7.05185,4.03814 3.46829,1.15198 6.99029,3.24635 7.82665,4.65414 0.83637,1.4078 6.23554,4.82995 11.99816,7.6048 5.76263,2.77485 10.4775,5.56761 10.4775,6.20611 0,2.5653 4.58801,0.88115 11.31459,-4.15331 z m 2.02041,-283.9477 c 0.6985,-0.51318 1.46875,-2.3618 1.71168,-4.10805 0.56306,-4.04758 1.15906,-4.62226 2.94327,-2.83805 1.68598,1.68598 -1.76661,7.93105 -4.36673,7.8986 -0.97058,-0.0127 -1.07928,-0.37132 -0.28822,-0.9525 z m 9.525,-13.5581 c 0,-2.08653 1.42599,-3.18126 5.05405,-3.87996 2.39345,-0.46094 2.41892,-0.32623 0.49371,2.61201 -2.22835,3.40089 -5.54776,4.15955 -5.54776,1.26795 z m 48.52395,294.3073 c 2.64882,-2.58651 4.81604,-5.57927 4.81604,-6.65056 0,-1.91407 -11.30144,8.40727 -11.38843,10.4008 -0.0851,1.9494 1.88895,0.82301 6.57239,-3.75024 z M 397.58409,6.695 c 11.77135,-11.58574 21.99067,-20.70146 22.7096,-20.25714 0.71893,0.44432 2.44379,-1.0778 3.83299,-3.38249 3.65129,-6.05748 15.50985,-18.81143 16.76787,-18.03392 0.58859,0.36376 1.57963,-0.65887 2.20228,-2.27253 1.04201,-2.70043 6.85969,-8.64892 8.45871,-8.64892 1.041,0 6.58062,-6.45359 9.61816,-11.20507 1.48382,-2.32102 4.11233,-4.75785 5.84115,-5.41514 2.07953,-0.79063 2.89197,-1.85009 2.40062,-3.13051 -0.44543,-1.16078 0.13829,-2.26909 1.4584,-2.76909 3.11761,-1.1808 11.74096,-10.37395 13.11094,-13.97724 0.63271,-1.66418 1.72404,-2.67126 2.42515,-2.23795 0.70112,0.43332 1.82565,-0.53242 2.49897,-2.14608 1.61515,-3.87085 4.46392,-7.37892 5.99216,-7.37892 2.9712,0 7.40795,-6.59472 7.9217,-11.77472 0.0535,-0.53885 1.52592,-1.63071 3.27217,-2.42635 1.74625,-0.79566 3.175,-1.94954 3.175,-2.56421 0,-1.96708 9.30059,-14.25126 29.18392,-38.54601 7.31476,-8.93764 10.9112,-13.79156 17.35912,-23.4287 2.5704,-3.84175 5.05006,-7.27075 5.51034,-7.62 0.46029,-0.34925 1.25329,-1.778 1.76222,-3.175 1.02343,-2.80929 -5.4864,3.90452 -22.61973,23.32852 -6.24201,7.07657 -11.78359,12.86647 -12.3146,12.86647 -0.53103,0 -2.9666,3.01478 -5.41238,6.69951 -8.48559,12.78409 -95.12522,107.56264 -90.23393,98.71049 0.9119,-1.65031 0.76124,-1.66856 -1.12686,-0.13652 -1.19872,0.97267 -1.89586,2.0521 -1.5492,2.39875 0.96641,0.96641 -14.94177,18.99657 -38.87565,44.06123 -11.71817,12.2718 -14.58819,15.84062 -16.24641,20.20208 -0.87901,2.31198 -0.84691,3.32446 0.10541,3.32446 0.75318,0 11.0005,-9.47925 22.77186,-21.065 z m 165.73093,-236.10514 c 7.95376,-10.79338 22.44935,-32.20988 24.50159,-36.19985 2.04326,-3.97251 5.06659,-9.36045 8.80914,-15.69902 1.80549,-3.05788 2.58996,-5.86682 2.08381,-7.46154 -0.50875,-1.60293 -0.32316,-2.25557 0.49722,-1.74855 1.38949,0.85875 5.06583,-3.86305 7.5976,-9.75815 1.94861,-4.53729 1.93005,-4.70274 -0.52755,-4.70274 -1.38406,0 -1.81756,-0.72426 -1.33027,-2.2225 6.72568,-20.67918 7.07081,-29.11544 1.4581,-35.64062 -2.65867,-3.09088 -3.76136,-3.41188 -11.72036,-3.41188 -6.27574,0 -10.57019,0.79186 -15.03247,2.77184 -3.43578,1.52452 -9.67588,4.27088 -13.86687,6.10302 -4.191,1.83215 -12.06899,6.01407 -17.50665,9.29316 -5.43766,3.27909 -13.22193,7.96223 -17.2984,10.40698 -14.90293,8.93762 -20.29423,12.86964 -19.68363,14.35579 0.34331,0.83557 6.06722,5.49508 12.71981,10.3545 13.88258,10.14058 29.29081,24.74222 37.1585,35.21331 l 5.42386,7.21862 -2.21728,8.02138 c -1.2195,4.41178 -3.02342,9.28214 -4.00871,10.82304 -0.98529,1.54092 -1.4471,3.3588 -1.02625,4.03976 0.42085,0.68095 0.18435,1.59706 -0.52554,2.03579 -0.70989,0.43874 -1.29071,1.44979 -1.29071,2.24676 0,1.66011 0.48298,1.1559 5.78506,-6.0391 z m 43.727,-9.68548 c 0.0102,-0.43485 2.73256,-4.26479 6.05043,-8.51099 5.9048,-7.55693 10.38052,-13.82353 30.99954,-43.40338 29.2161,-41.91315 54.60456,-91.87645 60.19802,-118.46689 2.58395,-12.28364 2.2771,-18.31429 -1.3256,-26.05292 -2.64227,-5.67563 -4.06603,-7.13855 -9.12556,-9.37662 -14.01174,-6.19806 -38.65146,0.62518 -72.38406,20.04462 -21.93128,12.62557 -42.00584,26.54573 -80.32961,55.7024 -1.80413,1.37257 -8.13797,6.48408 -14.07522,11.3589 -33.47512,27.48501 -45.50021,38.85051 -41.1052,38.85051 0.44265,0 6.30052,-4.55345 13.0175,-10.11875 15.58775,-12.91514 17.25,-14.3224 24.9127,-21.09112 3.4925,-3.08503 6.69517,-5.61164 7.11705,-5.61465 0.42187,-0.003 2.23443,-1.29136 4.0279,-2.86298 5.46566,-4.78962 12.58249,-10.41198 14.74604,-11.64952 1.12696,-0.64462 7.47826,-5.26193 14.11401,-10.26069 40.35084,-30.39662 83.0442,-53.0853 107.31499,-57.03076 6.76555,-1.09981 17.98918,2.58186 21.23822,6.96676 13.70937,18.50209 -9.26336,74.21124 -64.53959,156.50937 -2.72725,4.06046 -6.38738,9.54441 -8.13363,12.18655 -10.3942,15.72679 -13.63472,20.45508 -18.00152,26.26619 -2.67216,3.55598 -4.85848,6.93908 -4.85848,7.51799 0,1.19247 10.11436,-9.74159 10.14207,-10.96402 z m 10.80493,-88.28351 c 2.26357,-8.69046 2.47139,-18.16582 0.49361,-22.50656 -3.55539,-7.80324 -16.03391,-9.16059 -30.04302,-3.26793 -8.37135,3.52124 -7.78906,4.54113 1.08766,1.90503 21.78967,-6.47083 30.81295,3.02406 24.65975,25.9486 -1.03116,3.84175 -1.85876,8.128 -1.83907,9.525 0.0561,3.98739 3.33888,-2.76544 5.64107,-11.60414 z"
+         id="path8400"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#958469;stroke-width:1.26999998"
+         d="m 52.28585,284.74 c -1.62754,-0.35382 -5.15242,-2.83657 -7.83307,-5.51722 -4.11164,-4.11164 -5.01585,-6.00993 -5.78144,-12.1375 -1.76583,-14.13317 3.48659,-37.77977 14.15956,-63.74697 5.80281,-14.11813 21.52936,-45.65747 29.33294,-58.82675 C 85.6532,138.62295 92.383351,127.23275 97.119742,119.2 107.82405,101.04587 122.73201,77.51423 132.52132,63.32 c 4.09473,-5.93725 9.42377,-13.82807 11.84232,-17.53517 2.41855,-3.7071 5.14944,-7.02876 6.06861,-7.38149 0.91917,-0.35272 3.76739,0.23452 6.32935,1.30498 5.56713,2.32609 6.20039,0.66403 -9.23804,24.24668 -43.809,66.9195 -70.10527,125.13783 -68.5498,151.765 1.2245,20.96152 13.402366,27.84801 37.23123,21.054 26.27972,-7.49279 60.97218,-28.99371 108.58499,-67.2964 23.90162,-19.22794 54.61,-46.5043 54.61,-48.50671 0,-0.97399 -1.17673,-1.77089 -2.61496,-1.77089 -1.43822,0 -2.28175,-0.5391 -1.87453,-1.198 0.40721,-0.6589 8.58482,-7.94078 18.17244,-16.18193 9.58762,-8.24116 27.61062,-24.33635 40.05111,-35.76709 12.4405,-11.43074 22.9177,-20.48454 23.28268,-20.11956 0.36499,0.36499 -4.09859,5.94136 -9.91907,12.39194 -15.47006,17.1448 -17.45898,19.48892 -17.51606,20.64432 -0.20003,4.0487 38.32594,-34.46625 92.65838,-92.63189 5.23875,-5.60835 14.6685,-15.59232 20.95499,-22.18659 24.58843,-25.79222 50.35202,-54.37703 63.8379,-70.82839 4.37683,-5.3393 10.48202,-12.5653 13.56706,-16.0578 8.15414,-9.23108 24.61575,-29.23213 31.68226,-38.49433 3.36296,-4.40789 8.34046,-10.58626 11.06113,-13.72974 4.35295,-5.02945 6.97447,-12.04411 3.75827,-10.05638 -1.71135,1.05767 0.46713,-5.92316 3.10456,-9.94839 1.20227,-1.8349 1.59822,-3.33616 0.8799,-3.33616 -1.15549,0 -12.86262,13.5281 -35.59652,41.1333 -17.48202,21.22798 -57.55956,65.88992 -83.40456,92.94508 -2.0955,2.19361 -9.52201,10.4165 -16.50335,18.27306 -6.98136,7.85658 -14.69661,16.09799 -17.145,18.31425 -2.44841,2.21627 -13.96878,14.30076 -25.60082,26.85443 -11.63206,12.55369 -31.06306,32.21774 -43.18,43.69792 -27.36474,25.92667 -75.98848,75.28255 -75.28584,76.41944 0.28693,0.46428 -1.04354,2.31925 -2.95663,4.12217 -1.91309,1.80292 -5.81943,5.58255 -8.68076,8.39918 -2.86132,2.81665 -5.84459,5.12116 -6.62947,5.12116 -0.78489,0 -2.7063,1.73025 -4.26979,3.84499 -1.8317,2.4775 -3.76485,3.71963 -5.43553,3.4925 -1.88394,-0.25612 -2.70807,0.46177 -3.01445,2.62589 -0.31889,2.25229 -1.02811,2.81979 -2.90921,2.32789 -1.57204,-0.4111 -3.75381,0.53751 -5.92832,2.57757 -1.89245,1.77545 -2.42688,2.50385 -1.18761,1.61864 2.64464,-1.88904 4.41095,-0.87114 1.95785,1.12831 -0.91908,0.74911 -3.24836,2.66414 -5.1762,4.25562 -1.92783,1.59147 -5.00841,4.03659 -6.8457,5.43359 -1.8373,1.397 -4.61177,3.82588 -6.16548,5.3975 -1.55371,1.57163 -3.47377,2.8575 -4.2668,2.8575 -0.79302,0 -1.44187,0.45391 -1.44187,1.0087 0,2.70775 -41.5867,31.09407 -59.30262,40.47887 -24.47293,12.96427 -31.85075,15.30837 -45.47237,14.44759 -5.69584,-0.35993 -5.28069,-0.50483 4.02995,-1.40654 5.70897,-0.5529 11.99547,-1.92107 13.97,-3.04038 3.45237,-1.95707 3.19452,-2.00477 -6.72305,-1.24404 -12.17149,0.93361 -16.48755,-0.67214 -20.616476,-7.67014 -2.985417,-5.05991 -3.986602,-16.30779 -2.376509,-26.69906 1.267618,-8.18098 -0.740871,-4.80673 -2.645843,4.445 -3.375282,16.3925 -3.952572,17.83257 -3.383552,8.44042 0.96698,-15.96105 11.36369,-43.78467 28.77984,-77.02042 12.8804,-24.58006 44.05598,-76.0401 47.39021,-78.22478 2.53811,-1.66303 3.86162,-1.52545 12.95308,1.34654 5.56937,1.75937 11.15128,4.33163 12.40423,5.71613 2.52027,2.78487 2.75141,1.80376 -4.64746,19.72711 -2.01841,4.8895 -4.27231,9.96814 -5.00866,11.28587 -3.80857,6.81553 -7.9793,23.52967 -8.03096,32.18392 -0.0519,8.70604 0.15947,9.39422 4.09712,13.335 3.82128,3.8243 4.78209,4.15041 12.065,4.09491 6.11829,-0.0466 10.07347,-1.01776 17.43807,-4.28168 12.14997,-5.38471 26.10992,-13.57534 38.60437,-22.6501 2.16735,-1.57415 5.94088,-4.26921 8.38563,-5.98902 12.14855,-8.54614 14.46798,-10.40061 16.08012,-12.85674 1.53831,-2.34362 1.41641,-3.07498 -1.03102,-6.1864 -1.52656,-1.9407 -4.62126,-4.29974 -6.87711,-5.24229 C 249.927,71.95284 220.02977,43.31489 209.31567,27.125 201.1898,14.84615 193.53288,0.38028 190.51214,-8.39969 c -1.39629,-4.05836 -3.01482,-8.01254 -3.59674,-8.78707 -1.99155,-2.65073 -5.23216,-12.84141 -9.33266,-29.34824 -3.96113,-15.94581 -4.1021,-17.42139 -4.1254,-43.18 -0.0185,-20.42066 0.49315,-29.19949 2.18344,-37.465 10.82473,-52.93278 35.78563,-99.18685 72.43214,-134.22114 28.81923,-27.55138 59.6176,-46.21866 93.56799,-56.71277 26.41471,-8.16483 32.98802,-9.07803 65.39406,-9.0849 25.76587,-0.005 31.01804,0.35389 44.54428,3.04763 l 15.33427,3.05381 19.59073,-17.50705 c 86.94037,-77.69343 160.1964,-128.79178 205.75347,-143.51939 11.50949,-3.72077 15.67717,-4.46618 24.97105,-4.46618 13.63571,0 18.58511,2.19622 23.18117,10.2863 2.77546,4.88542 3.15901,7.02706 3.0479,17.0187 -0.19451,17.49065 -5.37977,34.28128 -19.80607,64.135 -21.22774,43.92857 -66.45956,112.58062 -116.10325,176.21941 l -14.27312,18.29686 1.73567,4.91941 c 3.53793,10.02754 8.15976,34.09065 9.54603,49.70037 3.42017,38.51178 -5.57916,84.75939 -23.37966,120.1483 -22.43712,44.6069 -59.57136,83.20023 -102.97579,107.02198 -20.39656,11.19428 -58.0116,23.9643 -76.88168,26.10075 -27.85312,3.15348 -40.02981,3.0111 -64.47754,-0.75393 l -19.09361,-2.94046 -12.50692,11.46215 c -6.8788,6.30418 -15.65783,14.53745 -19.50895,18.29616 -11.81424,11.53073 -44.46445,40.73296 -55.86297,49.96362 -5.93725,4.80807 -14.7955,12.25186 -19.685,16.54178 -9.85158,8.6435 -44.92459,35.27109 -57.78499,43.87061 -22.55792,15.08407 -27.09295,17.88126 -40.64973,25.07262 C 88.0005,281.36798 65.08487,287.52243 52.28585,284.74 Z m 82.33414,-64.67298 3.175,-1.91298 -3.175,0.84161 c -1.74625,0.46288 -4.03225,1.32659 -5.08,1.91935 -1.69333,0.95801 -1.69333,1.07705 0,1.07138 1.04775,-0.004 3.33375,-0.86723 5.08,-1.91936 z m 46.25112,-76.2379 c 3.44521,-1.32204 2.20306,-3.03912 -2.19852,-3.03912 -6.41419,0 -14.86967,-10.38215 -12.81539,-15.73551 0.37726,-0.98316 0.0754,-2.16485 -0.67064,-2.62598 -0.90379,-0.55857 -1.35657,1.67277 -1.35657,6.68526 0,10.61477 3.30046,14.52852 12.91362,15.31328 1.16525,0.0951 3.02263,-0.17394 4.1275,-0.59793 z M 191.76998,140.79 c 1.57747,-1.01944 1.4683,-1.2311 -0.635,-1.2311 -1.397,0 -3.39724,0.55399 -4.44499,1.2311 -1.57747,1.01944 -1.4683,1.2311 0.635,1.2311 1.397,0 3.39724,-0.55398 4.44499,-1.2311 z M 399.47082,75.48734 c 2.82472,-0.22005 6.42403,-0.80894 7.99845,-1.30863 1.69386,-0.53762 3.62468,-0.27608 4.7292,0.6406 2.48646,2.06357 12.47499,0.83435 19.71121,-2.42572 12.05744,-5.43212 14.96485,-6.53359 17.24603,-6.53359 1.30177,0 4.5668,-1.17909 7.25562,-2.62021 2.68881,-1.44112 5.63907,-2.33229 6.55612,-1.98037 2.28833,0.8781 9.08686,-1.66391 15.18751,-5.67871 2.794,-1.83872 7.47111,-4.55667 10.39358,-6.03991 2.92246,-1.48324 6.06571,-3.73767 6.985,-5.00985 0.91928,-1.27218 4.31816,-4.11808 7.55307,-6.32422 3.23489,-2.20614 10.37864,-8.21991 15.875,-13.36392 5.49634,-5.144 11.38975,-10.44963 13.09647,-11.79028 12.3906,-9.73295 25.47188,-24.71404 25.47188,-29.17119 0,-1.01274 0.5715,-1.84134 1.27,-1.84134 0.6985,0 1.27,-0.65521 1.27,-1.45604 0,-0.80083 2.02394,-4.50526 4.49766,-8.23207 4.99246,-7.52147 20.07814,-37.26723 23.03172,-45.41365 1.04155,-2.87278 2.35966,-8.08074 2.92914,-11.57324 0.56947,-3.4925 2.22416,-13.208 3.6771,-21.59 3.11533,-17.97231 3.37036,-27.88408 0.84218,-32.7308 -1.07579,-2.06237 -1.83187,-7.06011 -1.82526,-12.065 0.0165,-12.48398 -4.95772,-31.43199 -8.8894,-33.8619 -1.10988,-0.68595 -9.12345,7.46617 -19.26825,19.60136 -10.52964,12.59557 -32.50824,37.74954 -41.11541,47.05562 -4.23197,4.57561 -12.83594,14.29111 -19.11993,21.59 -11.62285,13.5 -23.39669,26.40099 -65.42722,71.69072 -45.38115,48.90017 -67.92389,74.42203 -76.5598,86.6775 L 358.1442,68.4 h 3.20011 c 2.15313,0 3.65842,1.00589 4.60094,3.07449 0.80823,1.77385 2.12606,2.7962 3.11515,2.41665 0.94286,-0.36181 2.68456,0.31242 3.87042,1.49828 1.81321,1.81321 3.68684,2.02428 11.78014,1.32708 5.29321,-0.456 11.93514,-1.00911 14.75986,-1.22916 z M 303.41457,42.76576 c 20.48966,-15.3353 62.74435,-54.53835 99.07123,-91.91628 50.18322,-51.63519 103.2434,-116.42588 127.42807,-155.59979 17.58218,-28.47932 24.51732,-43.66913 20.62055,-45.16446 -1.00549,-0.38584 -3.58218,-3.55219 -5.72599,-7.03634 -4.14561,-6.73746 -9.74098,-12.36255 -12.36308,-12.42875 -0.87429,-0.0221 -3.65743,-2.9184 -6.18477,-6.43626 -5.41263,-7.53398 -10.43131,-10.60832 -14.69675,-9.00293 -1.61088,0.60629 -4.71064,0.83633 -6.88837,0.51121 -5.09637,-0.76085 -11.17068,2.38498 -17.47179,9.0485 -3.03584,3.21046 -4.58618,4.2323 -4.04661,2.66718 2.50077,-7.25392 3.36807,-14.55962 2.02613,-17.06706 -1.46348,-2.73455 -5.673,-3.54359 -7.02823,-1.35077 -1.28787,2.08381 -4.52988,1.4465 -6.78143,-1.33308 -1.7608,-2.17376 -3.94572,-2.69239 -13.24763,-3.14455 -6.1265,-0.29779 -12.28193,-1.15308 -13.67875,-1.90064 -1.39683,-0.74756 -3.02299,-1.06049 -3.6137,-0.69542 -0.59072,0.36509 -2.07347,-0.44059 -3.29502,-1.79038 -2.38964,-2.64053 -6.64513,-3.42309 -23.51846,-4.32496 -5.93725,-0.31733 -11.68894,-0.92903 -12.78152,-1.35931 -4.08539,-1.60887 -16.44291,2.89588 -23.18052,8.45013 -3.62063,2.98471 -7.03998,6.14934 -7.59857,7.03248 -1.09249,1.72729 -4.68675,2.22484 -4.71883,0.65323 -0.0102,-0.52388 -0.50953,-1.72588 -1.1085,-2.67113 -1.33373,-2.10476 -5.13247,-0.42164 -10.93706,4.84591 -2.0955,1.90162 -5.81025,4.20633 -8.255,5.12156 -2.44475,0.91522 -7.3025,4.34172 -10.795,7.61442 -9.84288,9.22341 -13.31677,11.44174 -17.91784,11.44174 -3.28727,0 -5.46492,1.21314 -9.68884,5.3975 -2.99669,2.96862 -10.50026,9.9695 -16.67459,15.5575 -6.17435,5.588 -12.25447,12.16025 -13.51139,14.605 -1.25692,2.44475 -5.50056,7.874 -9.43032,12.065 -5.45627,5.81897 -7.02022,8.3357 -6.61701,10.64811 0.2904,1.66546 -0.59578,4.93278 -1.96929,7.2607 -1.3735,2.32792 -2.17247,4.75815 -1.77548,5.40049 0.39699,0.64234 -0.12916,1.87412 -1.16921,2.7373 -1.04006,0.86317 -1.89102,2.39779 -1.89102,3.41025 0,2.46467 -5.74366,12.14575 -8.90134,15.00341 -1.39077,1.25862 -2.52866,2.95559 -2.52866,3.77105 0,0.81544 -1.67222,4.58449 -3.71604,8.37566 -3.21766,5.96854 -3.70922,8.21474 -3.66505,16.74781 0.0503,9.72093 -0.0243,9.95238 -5.49144,17.03885 -3.04835,3.95124 -5.56963,8.08909 -5.60284,9.19522 -0.0333,1.10613 -1.31839,4.0114 -2.85595,6.45615 -4.86164,7.73008 -5.79119,12.09873 -3.36293,15.80475 1.16951,1.78488 3.20365,3.24524 4.52031,3.24524 1.31666,0 2.4255,0.42863 2.46406,0.9525 0.0386,0.52388 0.26525,5.02308 0.50373,9.99822 0.23849,4.97515 1.09061,10.27333 1.89362,11.77375 0.803,1.50042 1.13736,3.25006 0.74303,3.88811 -0.39434,0.63803 0.49857,3.49211 1.98422,6.3424 2.58956,4.96816 3.20749,6.89074 6.71718,20.89915 1.0515,4.19685 2.83874,7.30441 5.47811,9.525 2.15458,1.81273 4.14628,4.43887 4.42597,5.83587 0.73559,3.67394 6.33589,2.9154 15.87701,-2.15046 4.31639,-2.29179 8.25869,-3.75618 8.76069,-3.2542 0.50198,0.50199 -1.15104,3.5616 -3.67339,6.79914 -4.38758,5.63166 -7.21162,13.0648 -9.10362,23.96163 -0.48094,2.76987 -1.68268,6.59911 -2.67055,8.50945 -1.88317,3.64163 -2.31088,8.40027 -1.05029,11.68532 0.41021,1.06899 3.58355,2.88615 7.05185,4.03814 3.4683,1.15198 6.9903,3.24635 7.82666,4.65414 0.83637,1.4078 6.23553,4.82995 11.99816,7.6048 5.76262,2.77485 10.4775,5.56761 10.4775,6.20611 0,2.5653 4.58801,0.88115 11.31459,-4.15331 z m 259.90045,-272.1759 c 7.95376,-10.79338 22.44935,-32.20988 24.50159,-36.19985 2.04326,-3.97251 5.06659,-9.36045 8.80914,-15.69902 1.80549,-3.05788 2.58996,-5.86682 2.08381,-7.46154 -0.50875,-1.60293 -0.32316,-2.25557 0.49722,-1.74855 1.38949,0.85875 5.06583,-3.86305 7.5976,-9.75815 1.94861,-4.53729 1.93005,-4.70274 -0.52755,-4.70274 -1.38406,0 -1.81756,-0.72426 -1.33027,-2.2225 6.72568,-20.67918 7.07081,-29.11544 1.4581,-35.64062 -2.65867,-3.09088 -3.76136,-3.41188 -11.72036,-3.41188 -6.27574,0 -10.57019,0.79186 -15.03247,2.77184 -3.43578,1.52452 -9.67588,4.27088 -13.86687,6.10302 -4.191,1.83215 -12.06899,6.01407 -17.50665,9.29316 -5.43766,3.27909 -13.22193,7.96223 -17.2984,10.40698 -14.87501,8.92088 -20.29511,12.86882 -19.70894,14.35579 0.3294,0.83557 6.18712,5.6313 13.01717,10.65721 13.80862,10.1611 28.49165,24.10596 36.82369,34.97244 l 5.48688,7.15587 -2.21741,8.02185 c -1.21957,4.412 -3.02355,9.28258 -4.00884,10.82348 -0.98529,1.54092 -1.4471,3.3588 -1.02625,4.03976 0.42085,0.68095 0.18435,1.59706 -0.52554,2.03579 -0.70989,0.43874 -1.29071,1.44979 -1.29071,2.24676 0,1.66011 0.48298,1.1559 5.78506,-6.0391 z m 36.94578,-0.32235 c 6.16212,-7.88964 32.75498,-44.49855 38.54915,-53.06849 3.84175,-5.6822 9.3319,-13.71138 12.20033,-17.84264 8.68303,-12.50575 24.44159,-39.49529 32.9202,-56.38217 25.21689,-50.22451 28.65305,-78.1819 10.73428,-87.3367 -21.57358,-11.02206 -66.87036,9.53065 -136.4998,61.93466 -20.53661,15.4561 -52.84668,41.91013 -67.945,55.63028 -5.48143,4.98109 -6.65731,6.68619 -5.4627,7.92129 1.20316,1.24392 2.53442,0.71891 6.35,-2.50423 2.65524,-2.24295 6.4337,-5.32829 8.39659,-6.8563 1.9629,-1.52801 5.09053,-4.34983 6.95032,-6.2707 1.85977,-1.92088 4.00833,-3.4925 4.77457,-3.4925 0.76625,0 2.63125,-1.28588 4.14446,-2.8575 1.51322,-1.57163 4.49314,-4.28625 6.62208,-6.0325 2.12893,-1.74625 5.30961,-4.46088 7.06816,-6.0325 1.75856,-1.57163 4.02519,-2.8575 5.03695,-2.8575 1.01176,0 1.83957,-0.85725 1.83957,-1.905 0,-1.04775 0.52295,-1.905 1.16211,-1.905 0.63917,0 2.49654,-1.14122 4.1275,-2.53605 7.94808,-6.79732 9.26056,-7.61097 11.25559,-6.97778 1.1804,0.37464 2.87922,-0.20207 3.77513,-1.28157 1.3909,-1.67593 1.27791,-1.8384 -0.77321,-1.11175 -3.59568,1.27382 -1.45966,-0.56793 13.32502,-11.48925 38.34309,-28.32372 76.26678,-48.82899 96.66785,-52.26813 3.35469,-0.56552 3.97773,-0.41634 2.54,0.60816 -1.47536,1.05132 -0.70634,1.36186 3.40982,1.37692 12.73637,0.0466 19.45018,7.13278 19.45018,20.52896 0,4.98378 1.88219,6.69499 2.93455,2.66799 0.37998,-1.45404 0.63174,-1.08516 0.72813,1.06685 0.48163,10.75378 -13.4932,46.18355 -27.75681,70.37065 -2.05959,3.4925 -4.15035,7.20725 -4.64615,8.255 -3.67647,7.76955 -36.85444,59.88026 -48.1109,75.565 -10.12776,14.112 -18.39882,26.11056 -18.39882,26.69058 0,1.47791 3.04695,0.41984 4.63085,-1.60808 z m 94.72379,-187.0075 c 0,-2.44475 0.26136,-3.44488 0.5808,-2.2225 0.31943,1.22237 0.31943,3.22262 0,4.445 -0.31944,1.22237 -0.5808,0.22225 -0.5808,-2.2225 z m -2.56645,-9.8425 c -1.28245,-4.27725 -1.23947,-4.51132 0.42192,-2.29727 1.61457,2.15165 2.60733,7.05977 1.42797,7.05977 -0.23206,0 -1.06451,-2.14313 -1.84989,-4.7625 z m -18.97653,-10.0876 c 1.23568,-0.322 2.95018,-0.30082 3.81,0.0471 0.85983,0.34789 -0.15116,0.61134 -2.24666,0.58546 -2.0955,-0.0259 -2.79901,-0.31051 -1.56334,-0.63251 z m -55.59466,109.29096 c 2.26357,-8.69046 2.47139,-18.16582 0.49361,-22.50656 -3.55539,-7.80324 -16.03391,-9.16059 -30.04302,-3.26793 -8.37135,3.52124 -7.78906,4.54113 1.08766,1.90503 21.78967,-6.47083 30.81295,3.02406 24.65975,25.9486 -1.03116,3.84175 -1.85876,8.128 -1.83907,9.525 0.0561,3.98739 3.33888,-2.76544 5.64107,-11.60414 z m 69.32425,-59.51586 c 3.8798,-10.66467 4.66394,-14.43013 2.11578,-10.16 -0.83364,1.397 -1.53151,2.82575 -1.55084,3.175 -0.0193,0.34925 -1.42473,2.58462 -3.12311,4.9675 -1.6984,2.38287 -3.52417,6.66911 -4.05728,9.525 -0.53312,2.85588 -1.31364,6.907 -1.7345,9.0025 -0.6967,3.46894 -0.52266,3.35525 1.94428,-1.27 1.49021,-2.794 4.37277,-9.652 6.40567,-15.24 z M 89.14758,213.815 c 0.0259,-2.0955 0.310511,-2.799 0.632511,-1.56333 0.322,1.23567 0.300824,2.95017 -0.04705,3.81 C 89.385154,216.9215 89.1217,215.9105 89.14758,213.815 Z"
+         id="path8398"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#9b825b;stroke-width:1.26999998"
+         d="m 52.28585,284.74 c -1.62754,-0.35382 -5.15242,-2.83657 -7.83307,-5.51722 -4.11164,-4.11164 -5.01585,-6.00993 -5.78144,-12.1375 -1.76583,-14.13317 3.48659,-37.77977 14.15956,-63.74697 5.80281,-14.11813 21.52936,-45.65747 29.33294,-58.82675 C 85.6532,138.62295 92.383351,127.23275 97.119742,119.2 107.82405,101.04587 122.73201,77.51423 132.52132,63.32 c 4.09473,-5.93725 9.42377,-13.82807 11.84232,-17.53517 2.41855,-3.7071 5.14944,-7.02876 6.06861,-7.38149 0.91917,-0.35272 3.76739,0.23452 6.32935,1.30498 5.06618,2.11678 5.03248,2.71804 -0.61164,10.91168 -1.2029,1.74625 -6.05708,9.15839 -10.78709,16.47141 C 102.73493,132.99812 77.4484,189.60832 78.97376,215.72 c 1.2245,20.96152 13.402366,27.84801 37.23123,21.054 32.4687,-9.25736 81.22493,-42.15636 143.82749,-97.04972 18.16628,-15.9292 21.37856,-19.64226 17.97468,-20.77688 -1.70029,-0.56676 11.61333,-13.55904 28.56147,-27.87212 27.82233,-23.49656 28.73753,-23.94472 15.37634,-7.52963 -4.54025,5.57801 -8.25499,10.473 -8.25499,10.87777 0,1.62381 6.21341,-2.68627 15.23999,-10.57156 5.23875,-4.57638 24.95539,-23.78407 43.81476,-42.68377 C 391.60409,22.2684 406.15131,7.948 405.07188,9.345 c -1.07943,1.397 -8.84141,10.25525 -17.24884,19.685 -21.31847,23.91072 -38.57307,44.78781 -38.57307,46.67121 0,0.87383 1.46227,1.58879 3.24947,1.58879 1.78721,0 5.50906,1.81586 8.27079,4.03525 l 5.02131,4.03523 9.82672,-1.69617 c 16.33006,-2.81873 22.45902,-2.99292 26.7751,-0.76097 3.64494,1.88487 4.4906,1.84201 12.53017,-0.63496 4.74179,-1.46095 10.4982,-3.46449 12.79203,-4.45232 2.29382,-0.98783 6.00857,-1.79594 8.255,-1.79579 11.20447,7.1e-4 50.53218,-18.20026 61.53248,-28.4774 3.68064,-3.43868 8.47179,-7.11408 10.64701,-8.16757 4.90904,-2.37752 31.84908,-29.29354 42.04398,-42.0065 10.96689,-13.67562 19.43628,-26.6144 25.22183,-38.53167 2.79084,-5.74867 5.6246,-11.4467 6.29723,-12.66229 1.65632,-2.99329 9.91267,-27.03616 11.45238,-33.34984 2.52319,-10.34653 4.95963,-47.70913 3.54214,-54.31866 -0.96467,-4.49807 -0.94761,-6.64438 0.0608,-7.65283 2.01463,-2.01462 1.72639,-4.82093 -0.67553,-6.57726 -1.48154,-1.08332 -2.255,-4.21854 -2.69773,-10.93501 -0.64322,-9.75852 -3.90199,-18.45178 -7.42342,-19.80308 -2.64532,-1.01511 -5.87599,2.24842 -34.48345,34.83435 -13.64413,15.54162 -25.04393,28.2575 -25.33288,28.2575 -0.95058,0 1.22953,-3.04814 9.08335,-12.7 4.26283,-5.23875 9.16485,-11.32796 10.89338,-13.53157 1.72854,-2.20361 10.08187,-12.83495 18.56297,-23.6252 17.72273,-22.54813 25.05697,-33.18178 24.34968,-35.30367 -0.30875,-0.92625 -2.05788,-0.17476 -4.6292,1.98886 -2.27311,1.9127 -4.49959,3.11097 -4.94774,2.66282 -1.09955,-1.09954 6.48456,-16.15044 10.6067,-21.04932 2.38978,-2.8401 3.99727,-3.71145 5.79459,-3.141 1.61586,0.51285 3.12501,-0.0296 4.30742,-1.54823 6.44291,-8.27517 32.83149,-44.60234 38.63337,-53.18368 3.84175,-5.68219 9.3319,-13.71138 12.20033,-17.84264 8.68303,-12.50575 24.44159,-39.49529 32.9202,-56.38217 25.21689,-50.22451 28.65305,-78.1819 10.73428,-87.3367 -17.97661,-9.18434 -54.55825,4.50016 -106.28004,39.75735 -13.87178,9.45598 -37.19965,26.44169 -42.27242,30.77982 -1.05454,0.90181 -6.51303,5.18806 -12.13,9.525 -10.47463,8.08761 -13.41959,10.55406 -19.37928,16.23041 -1.88543,1.79579 -4.4037,3.80506 -5.59615,4.46504 -1.19246,0.65997 -5.47871,4.14517 -9.525,7.74488 -4.0463,3.59971 -9.30969,8.21628 -11.69642,10.25903 -4.63379,3.96597 -4.73526,6.71319 -0.33496,9.06816 3.14871,1.68514 10.68099,-3.13972 22.20029,-14.22056 14.6318,-14.07483 28.2796,-23.74774 33.52861,-23.76349 1.25706,-0.004 5.08273,-2.90566 8.50149,-6.44865 3.41877,-3.54299 9.41952,-7.99864 13.335,-9.90144 12.54806,-6.09796 19.04519,-10.41477 19.94579,-13.25231 1.10686,-3.48738 3.47464,-5.40282 10.66828,-8.63021 3.14325,-1.4102 7.25441,-4.24172 9.13591,-6.29226 2.96845,-3.23516 4.32124,-3.72827 10.22814,-3.72827 4.57639,0 8.86592,-0.99413 13.0891,-3.03348 7.03501,-3.39718 11.8392,-4.54768 22.39828,-5.36394 9.34417,-0.72233 16.26857,-3.58417 16.26857,-6.72375 0,-3.00929 3.24156,-4.32242 6.442,-2.60959 2.87944,1.54102 3.38006,7.57076 0.62858,7.57076 -3.34689,0 -4.46722,12.57427 -1.52096,17.07084 3.09249,4.71973 2.07694,18.02356 -1.89607,24.83916 -9.03238,15.49473 -10.63272,20.57885 -6.10493,19.3948 2.57698,-0.67388 2.58818,-0.59048 0.57062,4.2464 -1.13189,2.71358 -3.67589,6.6483 -5.65334,8.7438 -5.02619,5.32623 -10.2459,14.06225 -10.2459,17.14814 0,1.42648 -2.286,5.07363 -5.08,8.10475 -2.794,3.03114 -5.08,6.65979 -5.08,8.06369 0,3.0069 -10.08156,18.78711 -17.36299,27.17749 -2.80851,3.23624 -6.45597,8.09399 -8.10549,10.795 -1.6495,2.70101 -5.09894,7.59155 -7.66544,10.86788 -2.56649,3.27632 -5.96533,8.70557 -7.55295,12.06499 -5.75916,12.18634 -12.44302,22.42189 -25.98813,39.79775 -6.97461,8.94715 -30.08145,36.02387 -39.52901,46.32031 -5.12731,5.588 -12.20041,14.1605 -15.71801,19.05 -3.51761,4.8895 -16.83018,19.46275 -29.58349,32.38499 -28.727,29.10753 -48.1021,49.3411 -55.27496,57.72407 -3.01702,3.52601 -8.8242,10.1531 -12.90485,14.72688 -4.08065,4.57377 -9.85929,11.40267 -12.8414,15.17535 -2.98213,3.77266 -9.44622,10.60324 -14.36465,15.17905 -4.91843,4.57581 -11.53033,11.09854 -14.6931,14.49494 -3.16278,3.39641 -7.03638,6.6684 -8.60801,7.27107 -1.57162,0.60268 -2.8575,1.91633 -2.8575,2.91922 0,1.95591 -17.58239,19.42335 -36.195,35.95836 -6.2865,5.58479 -23.95423,22.52845 -39.26161,37.6526 -15.3074,15.12415 -28.23234,27.49846 -28.72209,27.49846 -0.48976,0 -5.39628,4.04684 -10.90338,8.993 -5.5071,4.94614 -13.96794,11.56988 -18.80185,14.7194 -10.79288,7.03205 -37.56606,29.48916 -37.56606,31.51006 0,0.81022 -2.56213,4.14712 -5.69363,7.41534 -5.85665,6.11233 -11.10143,7.80072 -12.54513,4.0385 -0.63942,-1.66629 -1.25029,-1.67938 -4.89837,-0.10491 -2.29233,0.98932 -9.31136,3.90866 -15.59786,6.48741 -6.2865,2.57874 -13.21196,5.76922 -15.3899,7.08994 -5.21174,3.16042 -9.1732,3.03373 -12.83953,-0.41061 -2.57221,-2.41645 -3.35677,-2.5848 -5.57887,-1.19709 -2.09989,1.3114 -2.96194,1.30285 -4.58795,-0.0456 -5.46603,-4.53296 -6.94645,-16.30482 -3.9621,-31.50545 2.41552,-12.30335 15.02196,-43.78039 24.25416,-60.56028 11.40701,-20.73266 14.61663,-27.22945 15.66807,-31.71469 0.46546,-1.98552 3.07579,-5.93235 5.80075,-8.77075 6.91379,-7.20162 11.89555,-20.87428 7.60573,-20.87428 -0.5196,0 -3.99938,4.58273 -7.73287,10.18384 -7.58099,11.37332 -10.38036,13.13294 -3.31045,2.08088 2.48488,-3.88448 4.51796,-7.36937 4.51796,-7.74419 0,-2.36661 4.90224,-5.79053 8.29065,-5.79053 5.5409,0 18.97002,4.76436 21.59034,7.65977 2.43551,2.69122 2.65621,1.77294 -4.72157,19.64523 -2.01841,4.8895 -4.27231,9.96814 -5.00866,11.28587 -3.80857,6.81553 -7.9793,23.52967 -8.03096,32.18392 -0.0519,8.70604 0.15947,9.39422 4.09712,13.335 3.82128,3.8243 4.78209,4.15041 12.065,4.09491 6.11829,-0.0466 10.07347,-1.01776 17.43807,-4.28168 12.14997,-5.38471 26.10992,-13.57534 38.60437,-22.6501 2.16736,-1.57415 5.94088,-4.26921 8.38563,-5.98902 12.14855,-8.54614 14.46798,-10.40061 16.08012,-12.85674 1.53831,-2.34362 1.41642,-3.07498 -1.03101,-6.1864 -1.52657,-1.9407 -4.62127,-4.29974 -6.87712,-5.24229 C 249.927,71.95284 220.02977,43.31489 209.31567,27.125 201.1898,14.84615 193.53288,0.38028 190.51214,-8.39969 c -1.39629,-4.05836 -3.01482,-8.01254 -3.59674,-8.78707 -1.99155,-2.65073 -5.23216,-12.84141 -9.33266,-29.34824 -3.96113,-15.94581 -4.1021,-17.42139 -4.1254,-43.18 -0.0185,-20.42066 0.49315,-29.19949 2.18344,-37.465 10.82473,-52.93278 35.78563,-99.18685 72.43214,-134.22114 28.81923,-27.55138 59.6176,-46.21866 93.56799,-56.71277 26.41471,-8.16483 32.98802,-9.07803 65.39406,-9.0849 25.76587,-0.005 31.01804,0.35389 44.54428,3.04763 l 15.33427,3.05381 19.59073,-17.50705 c 86.94037,-77.69343 160.1964,-128.79178 205.75347,-143.51939 11.50949,-3.72077 15.67717,-4.46618 24.97105,-4.46618 13.63571,0 18.58511,2.19622 23.18117,10.2863 2.77546,4.88542 3.15901,7.02706 3.0479,17.0187 -0.19451,17.49065 -5.37977,34.28128 -19.80607,64.135 -21.22774,43.92857 -66.45956,112.58062 -116.10325,176.21941 l -14.27312,18.29686 1.73567,4.91941 c 3.53793,10.02754 8.15976,34.09065 9.54603,49.70037 3.42017,38.51178 -5.57916,84.75939 -23.37966,120.1483 -22.43712,44.6069 -59.57136,83.20023 -102.97579,107.02198 -20.39656,11.19428 -58.0116,23.9643 -76.88168,26.10075 -27.85312,3.15348 -40.02981,3.0111 -64.47754,-0.75393 l -19.09361,-2.94046 -12.50692,11.46215 c -6.8788,6.30418 -15.65783,14.53745 -19.50895,18.29616 -11.81424,11.53073 -44.46445,40.73296 -55.86297,49.96362 -5.93725,4.80807 -14.7955,12.25186 -19.685,16.54178 -9.85158,8.6435 -44.92459,35.27109 -57.78499,43.87061 -22.55792,15.08407 -27.09295,17.88126 -40.64973,25.07262 C 88.0005,281.36798 65.08487,287.52243 52.28585,284.74 Z M 180.87111,143.82912 c 3.44521,-1.32204 2.20306,-3.03912 -2.19852,-3.03912 -6.41419,0 -14.86967,-10.38215 -12.81539,-15.73551 0.37726,-0.98316 0.0754,-2.16485 -0.67064,-2.62598 -0.90379,-0.55857 -1.35657,1.67277 -1.35657,6.68526 0,10.61477 3.30046,14.52852 12.91362,15.31328 1.16525,0.0951 3.02263,-0.17394 4.1275,-0.59793 z M 191.76998,140.79 c 1.57747,-1.01944 1.4683,-1.2311 -0.635,-1.2311 -1.397,0 -3.39724,0.55399 -4.44499,1.2311 -1.57747,1.01944 -1.4683,1.2311 0.635,1.2311 1.397,0 3.39724,-0.55398 4.44499,-1.2311 z M 303.41457,42.76576 c 13.48134,-10.08999 29.97656,-24.61244 54.69059,-48.14984 60.38724,-57.51219 111.35339,-114.60517 152.37347,-170.69091 20.47062,-27.98896 43.24133,-68.12663 43.24133,-76.22098 0,-2.70167 -2.16086,-5.02052 -27.40336,-29.40695 -3.73917,-3.61237 -7.81845,-6.84859 -9.06507,-7.19161 -10.85014,-2.98558 -15.54009,-3.51034 -18.35329,-2.05356 -4.82502,2.49856 -7.40828,5.24968 -7.40828,7.88967 0,2.23507 -7.3947,13.01705 -8.30398,12.10779 -0.58719,-0.5872 2.86639,-13.20817 4.39669,-16.06757 3.65158,-6.82303 0.86879,-12.15562 -6.52383,-12.50148 -2.99414,-0.14008 -10.58738,-1.40355 -16.87388,-2.80771 -6.2865,-1.40417 -12.93051,-2.57544 -14.76447,-2.60282 -1.83396,-0.0274 -6.60994,-1.49836 -10.6133,-3.26883 -7.45496,-3.29693 -7.18628,-3.27277 -35.44636,-3.18756 -6.46186,0.0194 -8.96422,0.82284 -18.31987,5.88135 -9.34497,5.05273 -11.51481,5.75019 -15.77164,5.0695 -6.78214,-1.08452 -14.49348,1.41074 -21.28935,6.88883 -9.87822,7.96277 -17.26532,12.33074 -30.76641,18.19209 -9.18104,3.98586 -24.24437,17.95244 -45.26754,41.9717 -11.69219,13.35848 -22.64607,26.28839 -24.34195,28.73314 -1.69588,2.44475 -4.03023,5.588 -5.18743,6.985 -1.15721,1.397 -2.40977,4.54025 -2.78348,6.985 -0.81514,5.33256 -6.96895,22.99996 -8.6039,24.7015 -0.6376,0.66357 -1.15928,1.8703 -1.15928,2.68161 0,0.81132 -2.3481,5.69764 -5.218,10.8585 -6.37053,11.45598 -9.78185,23.90441 -7.09006,25.8727 2.75799,2.01669 1.51563,8.08472 -2.99447,14.62569 -2.05503,2.98039 -3.86746,8.02572 -6.21208,17.29276 -1.35476,5.35467 0.4723,8.58317 3.83799,6.78191 1.37927,-0.73816 2.21243,-1.81997 1.85148,-2.404 -0.36096,-0.58405 0.47972,-1.35897 1.86816,-1.72206 1.38845,-0.36309 2.84449,-0.14232 3.23567,0.49061 0.39117,0.63293 1.75934,1.15077 3.04039,1.15077 2.79855,0 3.8229,2.21866 3.00347,6.50524 -0.33322,1.74308 0.0367,6.27886 0.82196,10.0795 2.15805,10.4446 2.3564,14.24487 0.91007,17.43684 -0.72434,1.59861 -1.97721,2.65322 -2.78412,2.34358 -0.80692,-0.30964 -1.46713,0.21463 -1.46713,1.16506 0,0.95043 0.57338,1.37369 1.27417,0.94059 0.75426,-0.46617 0.94963,0.0582 0.47879,1.28519 -0.43744,1.13996 -0.23466,2.41919 0.45064,2.84273 0.68531,0.42354 1.16416,0.29897 1.06415,-0.27683 -0.5212,-3.0003 0.41882,-4.16862 3.17078,-3.94087 2.68198,0.22197 2.97286,0.73941 2.40004,4.26932 -0.36596,2.25504 -0.0399,4.39499 0.74358,4.87916 0.90987,0.56232 1.10912,0.066 0.57263,-1.42604 -0.45255,-1.25866 -0.35047,-1.85956 0.22684,-1.33534 0.57729,0.52421 1.15901,1.95296 1.29269,3.175 0.13369,1.22203 0.89541,3.8307 1.69272,5.79705 0.79731,1.96634 1.44964,4.05867 1.44964,4.64961 0,0.59093 1.85738,2.62762 4.1275,4.52598 2.27013,1.89834 4.4535,4.66378 4.85195,6.14539 1.02534,3.81274 6.31405,3.15333 16.01354,-1.99662 4.31639,-2.29179 8.25869,-3.75618 8.76069,-3.2542 0.50198,0.50199 -1.15104,3.5616 -3.67339,6.79914 -4.38758,5.63166 -7.21162,13.0648 -9.10362,23.96163 -0.48094,2.76987 -1.68268,6.59911 -2.67055,8.50945 -1.88317,3.64163 -2.31088,8.40027 -1.05029,11.68532 0.41021,1.06899 3.58355,2.88615 7.05185,4.03814 3.4683,1.15198 6.9903,3.24635 7.82666,4.65414 0.83637,1.4078 6.23553,4.82995 11.99816,7.6048 5.76262,2.77485 10.4775,5.56761 10.4775,6.20611 0,2.5653 4.58801,0.88115 11.31459,-4.15331 z M 230.06048,-41.455 c 0,-0.66357 -0.61436,-1.41128 -1.36525,-1.66157 -0.75089,-0.25031 -1.36525,0.4974 -1.36525,1.66157 0,1.16418 0.61436,1.91189 1.36525,1.66158 0.75089,-0.25029 1.36525,-0.998 1.36525,-1.66158 z m 333.25454,-187.95514 c 7.95376,-10.79338 22.44935,-32.20988 24.50159,-36.19985 2.04326,-3.97251 5.06659,-9.36045 8.80914,-15.69902 1.80549,-3.05788 2.58996,-5.86682 2.08381,-7.46154 -0.50875,-1.60293 -0.32316,-2.25557 0.49722,-1.74855 1.38949,0.85875 5.06583,-3.86305 7.5976,-9.75815 1.94861,-4.53729 1.93005,-4.70274 -0.52755,-4.70274 -1.38406,0 -1.81756,-0.72426 -1.33027,-2.2225 6.72568,-20.67918 7.07081,-29.11544 1.4581,-35.64062 -2.65867,-3.09088 -3.76136,-3.41188 -11.72036,-3.41188 -6.27574,0 -10.57019,0.79186 -15.03247,2.77184 -3.43578,1.52452 -9.67588,4.27088 -13.86687,6.10302 -4.191,1.83215 -12.06899,6.01407 -17.50665,9.29316 -5.43766,3.27909 -13.22193,7.96223 -17.2984,10.40698 -14.87501,8.92088 -20.29511,12.86882 -19.70894,14.35579 0.3294,0.83557 6.18712,5.6313 13.01717,10.65721 13.80862,10.1611 28.49165,24.10596 36.82369,34.97244 l 5.48688,7.15587 -2.21741,8.02185 c -1.21957,4.412 -3.02355,9.28258 -4.00884,10.82348 -0.98529,1.54092 -1.4471,3.3588 -1.02625,4.03976 0.42085,0.68095 0.18435,1.59706 -0.52554,2.03579 -0.70989,0.43874 -1.29071,1.44979 -1.29071,2.24676 0,1.66011 0.48298,1.1559 5.78506,-6.0391 z m 54.53193,-97.96899 c 2.26357,-8.69046 2.47139,-18.16582 0.49361,-22.50656 -3.55539,-7.80324 -16.03391,-9.16059 -30.04302,-3.26793 -8.37135,3.52124 -7.78906,4.54113 1.08766,1.90503 21.78967,-6.47083 30.81295,3.02406 24.65975,25.9486 -1.03116,3.84175 -1.85876,8.128 -1.83907,9.525 0.0561,3.98739 3.33888,-2.76544 5.64107,-11.60414 z M 114.06187,227.55245 c 0.91678,-0.36686 2.01215,-0.32175 2.43416,0.1002 0.42201,0.42201 -0.32808,0.72218 -1.66687,0.66703 -1.47948,-0.061 -1.78041,-0.36187 -0.76729,-0.76728 z m 5.42395,-0.85879 c 0.29105,-0.25099 2.24367,-0.83985 4.33917,-1.30858 2.78246,-0.62239 3.29623,-0.50457 1.905,0.43689 -1.71497,1.16054 -7.51859,1.97073 -6.24417,0.87169 z M 104.91188,200.48 c -0.74758,-3.07562 -0.63979,-8.89 0.16481,-8.89 1.37316,0 2.62865,6.77517 1.70386,9.19485 l -1.09694,2.87015 z m 67.16293,0.0165 c 1.15196,-1.86395 5.72518,-4.10456 5.72518,-2.80505 0,0.61096 -1.46517,1.77841 -3.25592,2.59432 -1.85973,0.84736 -2.91858,0.9377 -2.46926,0.21069 z m -83.07455,-3.2973 c 0.0609,-1.47949 0.361871,-1.78041 0.767288,-0.7673 0.366866,0.91678 0.321747,2.01217 -0.100267,2.43418 -0.422011,0.422 -0.722171,-0.3281 -0.667031,-1.66688 z m 90.06973,-1.04856 c 0,-0.2418 0.85725,-1.15109 1.905,-2.02065 1.09802,-0.91129 1.905,-1.03472 1.905,-0.29137 0,0.70929 -0.85725,1.61857 -1.905,2.02064 -1.04775,0.40206 -1.905,0.53317 -1.905,0.29138 z m -88.699471,-5.83065 c 0,-1.74625 0.288256,-2.46062 0.640571,-1.5875 0.352313,0.87313 0.352313,2.30188 0,3.175 -0.352315,0.87313 -0.640571,0.15875 -0.640571,-1.5875 z m 13.293221,-2.28314 c -0.34438,-0.55723 -0.10112,-1.8431 0.5406,-2.8575 0.91096,-1.43999 1.17102,-1.21788 1.1862,1.01314 0.02,2.93755 -0.62524,3.62671 -1.7268,1.84436 z m 88.74124,-1.52686 c 0.4317,-0.6985 1.3564,-1.27 2.0549,-1.27 0.6985,0 0.9168,0.5715 0.4851,1.27 -0.43169,0.6985 -1.35639,1.27 -2.05489,1.27 -0.6985,0 -0.9168,-0.5715 -0.48511,-1.27 z m -99.594725,-5.82083 c 0.06094,-1.47948 0.361875,-1.78041 0.767292,-0.76729 0.366867,0.91677 0.321747,2.01216 -0.100266,2.43417 -0.422011,0.42201 -0.722174,-0.32809 -0.667029,-1.66688 z m 1.98737,-5.60389 c 0.451946,-1.74334 1.882536,-5.45809 3.179088,-8.255 1.296552,-2.7969 1.987593,-3.6589 1.535647,-1.91556 -0.451946,1.74334 -1.882536,5.45809 -3.179088,8.255 -1.296552,2.79691 -1.987593,3.65891 -1.535647,1.91556 z m 6.911075,-16.51528 c -0.0384,-1.04775 0.48418,-2.76225 1.16129,-3.81 1.50028,-2.32152 1.50028,-0.31634 0,3.175 -0.71968,1.6748 -1.11525,1.89111 -1.16129,0.635 z m 3.74019,-9.525 c 0,-0.6985 0.55399,-2.12725 1.2311,-3.175 0.67711,-1.04775 1.23111,-1.3335 1.23111,-0.635 0,0.6985 -0.554,2.12725 -1.23111,3.175 -0.67711,1.04775 -1.2311,1.3335 -1.2311,0.635 z m 142.20109,-5.715 c 0,-0.6985 0.60523,-1.27 1.34496,-1.27 0.73972,0 0.99174,0.5715 0.56004,1.27 -0.4317,0.6985 -1.03693,1.27 -1.34496,1.27 -0.30802,0 -0.56004,-0.5715 -0.56004,-1.27 z m -139.36405,-0.9525 c 0.26721,-0.52387 2.5469,-5.23875 5.06598,-10.4775 2.51908,-5.23875 5.49909,-10.668 6.62225,-12.065 1.12316,-1.397 -0.84227,3.31788 -4.36761,10.4775 -3.52535,7.15963 -6.72399,13.0175 -7.10809,13.0175 -0.3841,0 -0.47975,-0.42862 -0.21253,-0.9525 z m 17.44406,-33.3375 c 0.74765,-1.397 1.64512,-2.54 1.99437,-2.54 0.34925,0 0.0232,1.143 -0.72437,2.54 -0.74765,1.397 -1.64512,2.54 -1.99437,2.54 -0.34925,0 -0.0233,-1.143 0.72437,-2.54 z m 5.08,-8.89 c 0.74765,-1.397 1.64512,-2.54 1.99437,-2.54 0.34925,0 0.0232,1.143 -0.72437,2.54 -0.74765,1.397 -1.64512,2.54 -1.99437,2.54 -0.34925,0 -0.0232,-1.143 0.72437,-2.54 z m 10.87665,-18.15227 c 0.47692,-1.2525 1.18705,-1.95735 1.57807,-1.56633 0.39102,0.39102 8.2e-4,1.41578 -0.86713,2.27727 -1.25427,1.24493 -1.40015,1.09906 -0.71094,-0.71094 z m 4.24933,-8.30467 c 0.68493,-1.27983 2.24025,-3.13721 3.45626,-4.1275 1.95583,-1.59281 1.93223,-1.32433 -0.20448,2.32694 -2.50406,4.27899 -5.4517,5.91114 -3.25178,1.80056 z M 411.29352,1.4075 c 2.31544,-2.96862 4.48653,-5.3975 4.82464,-5.3975 0.9917,0 -4.31239,7.04607 -6.77706,9.00279 -1.24161,0.98573 -0.36301,-0.63666 1.95242,-3.60529 z m 10.07293,-10.59538 c 2.2943,-2.49633 4.99986,-4.53879 6.01233,-4.53879 1.39005,0 1.49671,0.41468 0.43552,1.69334 -0.77293,0.93133 -2.01291,1.69333 -2.75548,1.69333 -0.74258,0 -2.81573,1.28046 -4.607,2.84545 -1.87328,1.63664 -1.48472,0.91727 0.91463,-1.69333 z M 443.22996,-30.025 c 2.36955,-2.44475 4.59401,-4.445 4.94326,-4.445 0.34925,0 -1.30371,2.00025 -3.67326,4.445 -2.36954,2.44475 -4.594,4.445 -4.94325,4.445 -0.34925,0 1.30371,-2.00025 3.67325,-4.445 z m 211.85186,-402.82255 c 0.91678,-0.36686 2.01217,-0.32174 2.43417,0.10027 0.42201,0.42201 -0.32809,0.72217 -1.66687,0.66703 -1.47949,-0.0609 -1.78041,-0.36187 -0.7673,-0.76729 z m 18.35979,-3.82254 c 1.23568,-0.322 2.95018,-0.30082 3.81,0.0471 0.85983,0.34789 -0.15116,0.61134 -2.24666,0.58546 -2.0955,-0.0259 -2.79901,-0.31051 -1.56334,-0.63251 z"
+         id="path8396"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#887a64;stroke-width:1.26999998"
+         d="m 52.28585,284.74 c -1.62754,-0.35382 -5.15242,-2.83657 -7.83307,-5.51722 -4.11164,-4.11164 -5.01585,-6.00993 -5.78144,-12.1375 -1.76583,-14.13317 3.48659,-37.77977 14.15956,-63.74697 5.80281,-14.11813 21.52936,-45.65747 29.33294,-58.82675 C 85.6532,138.62295 92.383351,127.23275 97.119742,119.2 107.82405,101.04587 122.73201,77.51423 132.52132,63.32 c 4.09473,-5.93725 9.42377,-13.82807 11.84232,-17.53517 2.41855,-3.7071 5.14944,-7.02876 6.06861,-7.38149 0.91917,-0.35272 3.76739,0.23452 6.32935,1.30498 5.06618,2.11678 5.03248,2.71804 -0.61164,10.91168 -1.2029,1.74625 -6.05708,9.15839 -10.78709,16.47141 C 102.73493,132.99812 77.4484,189.60832 78.97376,215.72 c 1.2245,20.96152 13.402366,27.84801 37.23123,21.054 32.4687,-9.25736 81.22493,-42.15636 143.82749,-97.04972 18.16628,-15.9292 21.37856,-19.64226 17.97468,-20.77688 -1.70029,-0.56676 11.61333,-13.55904 28.56147,-27.87212 27.82233,-23.49656 28.73753,-23.94472 15.37634,-7.52963 -4.54025,5.57801 -8.25499,10.473 -8.25499,10.87777 0,1.62381 6.21341,-2.68627 15.23999,-10.57156 5.23875,-4.57638 24.95539,-23.78407 43.81476,-42.68377 C 391.60409,22.2684 406.15131,7.948 405.07188,9.345 c -1.07943,1.397 -8.84141,10.25525 -17.24884,19.685 -21.31847,23.91072 -38.57307,44.78781 -38.57307,46.67121 0,0.87383 1.46227,1.58879 3.24947,1.58879 1.78721,0 5.50906,1.81586 8.27079,4.03525 l 5.02131,4.03523 9.82672,-1.69617 c 16.33006,-2.81873 22.45902,-2.99292 26.7751,-0.76097 3.64494,1.88487 4.4906,1.84201 12.53017,-0.63496 4.74179,-1.46095 10.4982,-3.46449 12.79203,-4.45232 2.29382,-0.98783 6.00857,-1.79594 8.255,-1.79579 11.20447,7.1e-4 50.53218,-18.20026 61.53248,-28.4774 3.68064,-3.43868 8.47179,-7.11408 10.64701,-8.16757 4.90904,-2.37752 31.84908,-29.29354 42.04398,-42.0065 10.96689,-13.67562 19.43628,-26.6144 25.22183,-38.53167 2.79084,-5.74867 5.6246,-11.4467 6.29723,-12.66229 1.65632,-2.99329 9.91267,-27.03616 11.45238,-33.34984 2.52319,-10.34653 4.95963,-47.70913 3.54214,-54.31866 -0.96467,-4.49807 -0.94761,-6.64438 0.0608,-7.65283 2.01463,-2.01462 1.72639,-4.82093 -0.67553,-6.57726 -1.48154,-1.08332 -2.255,-4.21854 -2.69773,-10.93501 -0.64322,-9.75852 -3.90199,-18.45178 -7.42342,-19.80308 -2.64532,-1.01511 -5.87599,2.24842 -34.48345,34.83435 -13.64413,15.54162 -25.04393,28.2575 -25.33288,28.2575 -0.95058,0 1.22953,-3.04814 9.08335,-12.7 4.26283,-5.23875 9.16485,-11.32796 10.89338,-13.53157 1.72854,-2.20361 10.08187,-12.83495 18.56297,-23.6252 17.72273,-22.54813 25.05697,-33.18178 24.34968,-35.30367 -0.30875,-0.92625 -2.05788,-0.17476 -4.6292,1.98886 -2.27311,1.9127 -4.49959,3.11097 -4.94774,2.66282 -1.154,-1.15398 6.45112,-15.83266 10.78926,-20.8244 2.54441,-2.92777 4.29002,-3.92529 5.83296,-3.33321 1.40518,0.53922 2.87213,-0.0169 4.05423,-1.53684 6.55006,-8.42224 32.86082,-44.6421 38.66563,-53.22778 3.84175,-5.68219 9.3319,-13.71138 12.20033,-17.84264 8.68303,-12.50575 24.44159,-39.49529 32.9202,-56.38217 25.21689,-50.22451 28.65305,-78.1819 10.73428,-87.3367 -17.97661,-9.18434 -54.55825,4.50016 -106.28004,39.75735 -13.87178,9.45598 -37.19965,26.44169 -42.27242,30.77982 -1.05454,0.90181 -6.51303,5.18806 -12.13,9.525 -10.47463,8.08761 -13.41959,10.55406 -19.37928,16.23041 -1.88543,1.79579 -4.4037,3.80506 -5.59615,4.46504 -1.19246,0.65997 -5.47871,4.14517 -9.525,7.74488 -4.0463,3.59971 -9.30969,8.21628 -11.69642,10.25903 -4.63379,3.96597 -4.73526,6.71319 -0.33496,9.06816 3.14871,1.68514 10.68099,-3.13972 22.20029,-14.22056 14.6318,-14.07483 28.2796,-23.74774 33.52861,-23.76349 1.25706,-0.004 5.08273,-2.90566 8.50149,-6.44865 3.41877,-3.54299 9.41952,-7.99864 13.335,-9.90144 12.54806,-6.09796 19.04519,-10.41477 19.94579,-13.25231 1.10686,-3.48738 3.47464,-5.40282 10.66828,-8.63021 3.14325,-1.4102 7.25441,-4.24172 9.13591,-6.29226 2.96845,-3.23516 4.32124,-3.72827 10.22814,-3.72827 4.57639,0 8.86592,-0.99413 13.0891,-3.03348 7.63916,-3.68892 12.60626,-4.79141 23.56914,-5.23137 9.11095,-0.36563 15.09771,-3.13556 15.09771,-6.98534 0,-2.89607 3.29494,-4.16483 6.442,-2.48057 2.87944,1.54102 3.38006,7.57076 0.62858,7.57076 -3.34689,0 -4.46722,12.57427 -1.52096,17.07084 3.09395,4.72196 2.07702,18.02438 -1.89888,24.83916 -3.94623,6.76393 -10.00074,18.67043 -10.00074,19.66698 0,0.38577 1.46624,0.33341 3.2583,-0.11634 l 3.25831,-0.81777 -2.05268,4.92108 c -1.12896,2.70656 -3.67058,6.63555 -5.64803,8.73105 -5.02619,5.32623 -10.2459,14.06225 -10.2459,17.14814 0,1.42648 -2.286,5.07363 -5.08,8.10475 -2.794,3.03114 -5.08,6.65979 -5.08,8.06369 0,2.95518 -10.46508,19.43335 -16.87773,26.57542 -2.40888,2.6829 -6.46996,8.02125 -9.0246,11.863 -8.28241,12.45529 -15.7989,23.39775 -19.11468,27.82701 -1.78364,2.38259 -3.24299,5.1256 -3.24299,6.09556 0,3.76276 -3.78168,9.58219 -18.415,28.33793 -7.36133,9.43512 -34.72936,41.32637 -41.93393,48.8645 -4.00554,4.191 -8.20494,9.04875 -9.33199,10.795 -6.04291,9.36292 -12.5537,16.74613 -33.59802,38.09999 -29.00531,29.43196 -48.15695,49.44428 -55.24153,57.72407 -3.01702,3.52601 -8.8242,10.1531 -12.90485,14.72688 -4.08065,4.57377 -9.85929,11.40267 -12.8414,15.17535 -2.98213,3.77266 -9.44622,10.60324 -14.36465,15.17905 -4.91843,4.57581 -11.53033,11.09854 -14.6931,14.49494 -3.16278,3.39641 -7.03638,6.6684 -8.60801,7.27107 -1.57162,0.60268 -2.8575,1.91633 -2.8575,2.91922 0,1.95591 -17.58239,19.42335 -36.195,35.95836 -6.2865,5.58479 -23.95423,22.52845 -39.26161,37.6526 -15.3074,15.12415 -28.23234,27.49846 -28.72209,27.49846 -0.48976,0 -5.39628,4.04684 -10.90338,8.993 -5.5071,4.94614 -13.96794,11.56988 -18.80185,14.7194 -10.79288,7.03205 -37.56606,29.48916 -37.56606,31.51006 0,0.81022 -2.56213,4.14712 -5.69363,7.41534 -5.85665,6.11233 -11.10143,7.80072 -12.54513,4.0385 -0.63942,-1.66629 -1.25029,-1.67938 -4.89837,-0.10491 -2.29233,0.98932 -9.31136,3.90866 -15.59786,6.48741 -6.2865,2.57874 -13.21196,5.76922 -15.3899,7.08994 -5.21174,3.16042 -9.1732,3.03373 -12.83953,-0.41061 -2.57221,-2.41645 -3.35677,-2.5848 -5.57887,-1.19709 -2.09989,1.3114 -2.96194,1.30285 -4.58795,-0.0456 -5.46603,-4.53296 -6.94645,-16.30482 -3.9621,-31.50545 2.41552,-12.30335 15.02196,-43.78039 24.25416,-60.56028 11.40701,-20.73266 14.61663,-27.22945 15.66807,-31.71469 0.46546,-1.98552 3.07579,-5.93235 5.80075,-8.77075 6.91379,-7.20162 11.89555,-20.87428 7.60573,-20.87428 -0.5196,0 -3.99938,4.58273 -7.73287,10.18384 -7.58099,11.37332 -10.38036,13.13294 -3.31045,2.08088 2.48488,-3.88448 4.51796,-7.36937 4.51796,-7.74419 0,-2.36661 4.90224,-5.79053 8.29065,-5.79053 5.5409,0 18.97002,4.76436 21.59034,7.65977 2.43551,2.69122 2.65621,1.77294 -4.72157,19.64523 -2.01841,4.8895 -4.27231,9.96814 -5.00866,11.28587 -3.80857,6.81553 -7.9793,23.52967 -8.03096,32.18392 -0.0519,8.70604 0.15947,9.39422 4.09712,13.335 3.82128,3.8243 4.78209,4.15041 12.065,4.09491 6.11829,-0.0466 10.07347,-1.01776 17.43807,-4.28168 12.14997,-5.38471 26.10992,-13.57534 38.60437,-22.6501 2.16736,-1.57415 5.94088,-4.26921 8.38563,-5.98902 12.14855,-8.54614 14.46798,-10.40061 16.08012,-12.85674 1.53831,-2.34362 1.41642,-3.07498 -1.03101,-6.1864 -1.52657,-1.9407 -4.62127,-4.29974 -6.87712,-5.24229 C 249.927,71.95284 220.02977,43.31489 209.31567,27.125 201.1898,14.84615 193.53288,0.38028 190.51214,-8.39969 c -1.39629,-4.05836 -3.01482,-8.01254 -3.59674,-8.78707 -1.99155,-2.65073 -5.23216,-12.84141 -9.33266,-29.34824 -3.96113,-15.94581 -4.1021,-17.42139 -4.1254,-43.18 -0.0185,-20.42066 0.49315,-29.19949 2.18344,-37.465 10.82473,-52.93278 35.78563,-99.18685 72.43214,-134.22114 28.81923,-27.55138 59.6176,-46.21866 93.56799,-56.71277 26.41471,-8.16483 32.98802,-9.07803 65.39406,-9.0849 25.76587,-0.005 31.01804,0.35389 44.54428,3.04763 l 15.33427,3.05381 19.59073,-17.50705 c 86.94037,-77.69343 160.1964,-128.79178 205.75347,-143.51939 11.50949,-3.72077 15.67717,-4.46618 24.97105,-4.46618 13.63571,0 18.58511,2.19622 23.18117,10.2863 2.77546,4.88542 3.15901,7.02706 3.0479,17.0187 -0.19451,17.49065 -5.37977,34.28128 -19.80607,64.135 -21.22774,43.92857 -66.45956,112.58062 -116.10325,176.21941 l -14.27312,18.29686 1.73567,4.91941 c 3.53793,10.02754 8.15976,34.09065 9.54603,49.70037 3.42017,38.51178 -5.57916,84.75939 -23.37966,120.1483 -22.43712,44.6069 -59.57136,83.20023 -102.97579,107.02198 -20.39656,11.19428 -58.0116,23.9643 -76.88168,26.10075 -27.85312,3.15348 -40.02981,3.0111 -64.47754,-0.75393 l -19.09361,-2.94046 -12.50692,11.46215 c -6.8788,6.30418 -15.65783,14.53745 -19.50895,18.29616 -11.81424,11.53073 -44.46445,40.73296 -55.86297,49.96362 -5.93725,4.80807 -14.7955,12.25186 -19.685,16.54178 -9.85158,8.6435 -44.92459,35.27109 -57.78499,43.87061 -22.55792,15.08407 -27.09295,17.88126 -40.64973,25.07262 C 88.0005,281.36798 65.08487,287.52243 52.28585,284.74 Z M 180.87111,143.82912 c 3.44521,-1.32204 2.20306,-3.03912 -2.19852,-3.03912 -6.41419,0 -14.86967,-10.38215 -12.81539,-15.73551 0.37726,-0.98316 0.0754,-2.16485 -0.67064,-2.62598 -0.90379,-0.55857 -1.35657,1.67277 -1.35657,6.68526 0,10.61477 3.30046,14.52852 12.91362,15.31328 1.16525,0.0951 3.02263,-0.17394 4.1275,-0.59793 z M 191.76998,140.79 c 1.57747,-1.01944 1.4683,-1.2311 -0.635,-1.2311 -1.397,0 -3.39724,0.55399 -4.44499,1.2311 -1.57747,1.01944 -1.4683,1.2311 0.635,1.2311 1.397,0 3.39724,-0.55398 4.44499,-1.2311 z M 297.9333,48.23959 c 36.15026,-25.51224 124.68134,-113.49317 172.06576,-170.99648 13.92872,-16.90318 35.62255,-44.90268 45.95716,-59.31537 19.73477,-27.5222 37.71176,-60.25114 37.74823,-68.72449 0.0187,-4.34579 -0.49423,-4.92462 -27.38785,-30.90618 -3.73917,-3.61236 -7.81845,-6.84858 -9.06507,-7.1916 -12.90709,-3.55158 -15.53049,-3.6783 -20.11769,-0.97178 -2.48549,1.46647 -4.99006,2.66632 -5.56571,2.66632 -0.57564,0 -1.69421,-2.57175 -2.48568,-5.715 -0.79149,-3.14325 -2.14521,-5.81268 -3.00828,-5.93206 -0.86306,-0.11938 -3.56946,-0.3668 -6.01421,-0.54982 -2.44475,-0.18302 -9.5885,-1.49169 -15.875,-2.90816 -6.2865,-1.41647 -12.93051,-2.5978 -14.76447,-2.62518 -1.83396,-0.0274 -6.60994,-1.49836 -10.6133,-3.26883 -7.45496,-3.29693 -7.18628,-3.27277 -35.44636,-3.18756 -6.46186,0.0194 -8.96422,0.82284 -18.31987,5.88135 -9.34497,5.05273 -11.51481,5.75019 -15.77164,5.06949 -6.78214,-1.08451 -14.49348,1.41075 -21.28935,6.88884 -9.87822,7.96277 -17.26532,12.33073 -30.76641,18.19209 -9.18104,3.98586 -24.24437,17.95244 -45.26754,41.9717 -11.69219,13.35848 -22.64607,26.28839 -24.34195,28.73314 -1.69588,2.44475 -4.03023,5.588 -5.18743,6.985 -1.15721,1.397 -2.40977,4.54025 -2.78348,6.985 -0.81514,5.33256 -6.96895,22.99996 -8.6039,24.7015 -0.6376,0.66357 -1.15928,1.8703 -1.15928,2.68161 0,0.81132 -2.3481,5.69764 -5.218,10.8585 -6.37053,11.45598 -9.78185,23.90441 -7.09006,25.8727 2.75799,2.01669 1.51563,8.08472 -2.99447,14.62569 -2.07089,3.00339 -3.8882,8.08227 -6.25372,17.4774 -0.83251,3.30649 -0.48169,4.97231 1.73855,8.255 2.33346,3.45012 2.59723,4.88728 1.63741,8.92178 -1.84357,7.7493 -0.0272,15.36899 5.2823,22.15906 3.79072,4.84779 8.33428,14.56851 8.46796,18.11675 0.0132,0.34925 0.36874,1.2065 0.79017,1.905 0.42145,0.6985 1.17179,3.556 1.66746,6.35 0.49566,2.794 2.24417,6.50875 3.88558,8.255 1.65397,1.75963 3.16658,5.15653 3.3931,7.62 0.45101,4.90495 1.77985,5.2853 11.66252,3.3381 l 4.9038,-0.96621 -4.11631,7.19455 c -3.68665,6.44358 -4.0475,7.95332 -3.45717,14.46365 0.36252,3.998 0.0994,8.26918 -0.58458,9.4915 -2.25083,4.02201 -0.0293,7.69353 7.59339,12.54976 4.08854,2.60469 9.07477,6.4487 11.0805,8.54223 2.00573,2.09354 4.56856,3.80642 5.69517,3.80642 1.12662,0 2.59132,0.50247 3.2549,1.11659 0.66357,0.61412 3.3039,1.78634 5.86741,2.60496 2.5635,0.81861 5.3481,2.3164 6.188,3.32841 2.51266,3.02757 5.44892,2.15678 14.66941,-4.35037 z m 265.38172,-277.64973 c 7.95376,-10.79338 22.44935,-32.20988 24.50159,-36.19985 2.04326,-3.97251 5.06659,-9.36045 8.80914,-15.69902 1.80549,-3.05788 2.58996,-5.86682 2.08381,-7.46154 -0.50875,-1.60293 -0.32316,-2.25557 0.49722,-1.74855 1.38949,0.85875 5.06583,-3.86305 7.5976,-9.75815 1.94861,-4.53729 1.93005,-4.70274 -0.52755,-4.70274 -1.38406,0 -1.81756,-0.72426 -1.33027,-2.2225 6.72568,-20.67918 7.07081,-29.11544 1.4581,-35.64062 -2.65867,-3.09088 -3.76136,-3.41188 -11.72036,-3.41188 -6.27574,0 -10.57019,0.79186 -15.03247,2.77184 -3.43578,1.52452 -9.67588,4.27088 -13.86687,6.10302 -4.191,1.83215 -12.06899,6.01407 -17.50665,9.29316 -5.43766,3.27909 -13.22193,7.96223 -17.2984,10.40698 -14.87501,8.92088 -20.29511,12.86882 -19.70894,14.35579 0.3294,0.83557 6.18712,5.6313 13.01717,10.65721 13.80862,10.1611 28.49165,24.10596 36.82369,34.97244 l 5.48688,7.15587 -2.21741,8.02185 c -1.21957,4.412 -3.02355,9.28258 -4.00884,10.82348 -0.98529,1.54092 -1.4471,3.3588 -1.02625,4.03976 0.42085,0.68095 0.18435,1.59706 -0.52554,2.03579 -0.70989,0.43874 -1.29071,1.44979 -1.29071,2.24676 0,1.66011 0.48298,1.1559 5.78506,-6.0391 z m 54.53193,-97.96899 c 2.26357,-8.69046 2.47139,-18.16582 0.49361,-22.50656 -3.55539,-7.80324 -16.03391,-9.16059 -30.04302,-3.26793 -8.37135,3.52124 -7.78906,4.54113 1.08766,1.90503 21.78967,-6.47083 30.81295,3.02406 24.65975,25.9486 -1.03116,3.84175 -1.85876,8.128 -1.83907,9.525 0.0561,3.98739 3.33888,-2.76544 5.64107,-11.60414 z M 114.06187,227.55245 c 0.91678,-0.36686 2.01215,-0.32175 2.43416,0.1002 0.42201,0.42201 -0.32808,0.72218 -1.66687,0.66703 -1.47948,-0.061 -1.78041,-0.36187 -0.76729,-0.76728 z m 5.42395,-0.85879 c 0.29105,-0.25099 2.24367,-0.83985 4.33917,-1.30858 2.78246,-0.62239 3.29623,-0.50457 1.905,0.43689 -1.71497,1.16054 -7.51859,1.97073 -6.24417,0.87169 z M 104.91188,200.48 c -0.74758,-3.07562 -0.63979,-8.89 0.16481,-8.89 1.37316,0 2.62865,6.77517 1.70386,9.19485 l -1.09694,2.87015 z m 67.16293,0.0165 c 1.15196,-1.86395 5.72518,-4.10456 5.72518,-2.80505 0,0.61096 -1.46517,1.77841 -3.25592,2.59432 -1.85973,0.84736 -2.91858,0.9377 -2.46926,0.21069 z m -83.07455,-3.2973 c 0.0609,-1.47949 0.361871,-1.78041 0.767288,-0.7673 0.366866,0.91678 0.321747,2.01217 -0.100267,2.43418 -0.422011,0.422 -0.722171,-0.3281 -0.667031,-1.66688 z m 90.06973,-1.04856 c 0,-0.2418 0.85725,-1.15109 1.905,-2.02065 1.09802,-0.91129 1.905,-1.03472 1.905,-0.29137 0,0.70929 -0.85725,1.61857 -1.905,2.02064 -1.04775,0.40206 -1.905,0.53317 -1.905,0.29138 z m -88.699471,-5.83065 c 0,-1.74625 0.288256,-2.46062 0.640571,-1.5875 0.352313,0.87313 0.352313,2.30188 0,3.175 -0.352315,0.87313 -0.640571,0.15875 -0.640571,-1.5875 z m 13.293221,-2.28314 c -0.34438,-0.55723 -0.10112,-1.8431 0.5406,-2.8575 0.91096,-1.43999 1.17102,-1.21788 1.1862,1.01314 0.02,2.93755 -0.62524,3.62671 -1.7268,1.84436 z m 88.74124,-1.52686 c 0.4317,-0.6985 1.3564,-1.27 2.0549,-1.27 0.6985,0 0.9168,0.5715 0.4851,1.27 -0.43169,0.6985 -1.35639,1.27 -2.05489,1.27 -0.6985,0 -0.9168,-0.5715 -0.48511,-1.27 z m -99.594725,-5.82083 c 0.06094,-1.47948 0.361875,-1.78041 0.767292,-0.76729 0.366867,0.91677 0.321747,2.01216 -0.100266,2.43417 -0.422011,0.42201 -0.722174,-0.32809 -0.667029,-1.66688 z m 1.98737,-5.60389 c 0.451946,-1.74334 1.882536,-5.45809 3.179088,-8.255 1.296552,-2.7969 1.987593,-3.6589 1.535647,-1.91556 -0.451946,1.74334 -1.882536,5.45809 -3.179088,8.255 -1.296552,2.79691 -1.987593,3.65891 -1.535647,1.91556 z m 6.911075,-16.51528 c -0.0384,-1.04775 0.48418,-2.76225 1.16129,-3.81 1.50028,-2.32152 1.50028,-0.31634 0,3.175 -0.71968,1.6748 -1.11525,1.89111 -1.16129,0.635 z m 3.74019,-9.525 c 0,-0.6985 0.55399,-2.12725 1.2311,-3.175 0.67711,-1.04775 1.23111,-1.3335 1.23111,-0.635 0,0.6985 -0.554,2.12725 -1.23111,3.175 -0.67711,1.04775 -1.2311,1.3335 -1.2311,0.635 z m 142.20109,-5.715 c 0,-0.6985 0.60523,-1.27 1.34496,-1.27 0.73972,0 0.99174,0.5715 0.56004,1.27 -0.4317,0.6985 -1.03693,1.27 -1.34496,1.27 -0.30802,0 -0.56004,-0.5715 -0.56004,-1.27 z m -139.36405,-0.9525 c 0.26721,-0.52387 2.5469,-5.23875 5.06598,-10.4775 2.51908,-5.23875 5.49909,-10.668 6.62225,-12.065 1.12316,-1.397 -0.84227,3.31788 -4.36761,10.4775 -3.52535,7.15963 -6.72399,13.0175 -7.10809,13.0175 -0.3841,0 -0.47975,-0.42862 -0.21253,-0.9525 z m 17.44406,-33.3375 c 0.74765,-1.397 1.64512,-2.54 1.99437,-2.54 0.34925,0 0.0232,1.143 -0.72437,2.54 -0.74765,1.397 -1.64512,2.54 -1.99437,2.54 -0.34925,0 -0.0233,-1.143 0.72437,-2.54 z m 5.08,-8.89 c 0.74765,-1.397 1.64512,-2.54 1.99437,-2.54 0.34925,0 0.0232,1.143 -0.72437,2.54 -0.74765,1.397 -1.64512,2.54 -1.99437,2.54 -0.34925,0 -0.0232,-1.143 0.72437,-2.54 z m 10.87665,-18.15227 c 0.47692,-1.2525 1.18705,-1.95735 1.57807,-1.56633 0.39102,0.39102 8.2e-4,1.41578 -0.86713,2.27727 -1.25427,1.24493 -1.40015,1.09906 -0.71094,-0.71094 z m 4.24933,-8.30467 c 0.68493,-1.27983 2.24025,-3.13721 3.45626,-4.1275 1.95583,-1.59281 1.93223,-1.32433 -0.20448,2.32694 -2.50406,4.27899 -5.4517,5.91114 -3.25178,1.80056 z M 411.29352,1.4075 c 2.31544,-2.96862 4.48653,-5.3975 4.82464,-5.3975 0.9917,0 -4.31239,7.04607 -6.77706,9.00279 -1.24161,0.98573 -0.36301,-0.63666 1.95242,-3.60529 z m 9.07645,-9.8425 c 1.64052,-1.74625 3.26851,-3.175 3.61776,-3.175 0.34925,0 -0.70724,1.42875 -2.34776,3.175 -1.64053,1.74625 -3.26851,3.175 -3.61776,3.175 -0.34925,0 0.70723,-1.42875 2.34776,-3.175 z m 234.71185,-424.41255 c 0.91678,-0.36686 2.01217,-0.32174 2.43417,0.10027 0.42201,0.42201 -0.32809,0.72217 -1.66687,0.66703 -1.47949,-0.0609 -1.78041,-0.36187 -0.7673,-0.76729 z m 18.35979,-3.82254 c 1.23568,-0.322 2.95018,-0.30082 3.81,0.0471 0.85983,0.34789 -0.15116,0.61134 -2.24666,0.58546 -2.0955,-0.0259 -2.79901,-0.31051 -1.56334,-0.63251 z"
+         id="path8394"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#86755a;stroke-width:1.26999998"
+         d="m 51.435,283.66127 c -33.32897,-11.79138 -2.18798,-95.40843 83.00625,-222.8811 16.63523,-24.89064 15.30106,-23.52637 20.99424,-21.46769 5.05865,1.82923 6.52714,5.48572 1.727,4.30016 -2.51937,-0.62225 -4.38117,1.59152 -15.73382,18.70829 -25.88222,39.02356 -58.17296,96.80556 -64.07856,114.66407 -0.92394,2.794 -2.08273,5.6515 -2.57508,6.35 -1.14348,1.62225 -6.05579,19.73815 -7.48273,27.59526 -0.63398,3.49082 -0.60358,7.70346 0.0726,10.06113 0.79762,2.78115 0.70161,4.61208 -0.30546,5.82553 -1.1315,1.36337 -0.96328,2.62523 0.73275,5.49637 1.21078,2.04969 1.86868,3.72671 1.46201,3.72671 -1.07389,0 2.72951,7.03227 5.06836,9.37111 2.97891,2.97893 10.67468,4.4952 17.117448,3.3726 3.143249,-0.54768 4.541335,-1.06732 3.106856,-1.15474 -4.144989,-0.25263 -14.958524,-6.24546 -17.418354,-9.65321 -9.06231,-12.55451 -2.6088,-43.34866 18.171393,-86.70826 4.770297,-9.95362 8.982417,-18.0975 9.360257,-18.0975 0.93448,0 -0.91756,4.68267 -5.357935,13.54695 C 94.200609,156.90126 86.71491,176.09707 82.7938,189.05 c -4.36752,14.42759 -4.77363,33.85085 -0.84974,40.64 3.13907,5.43123 11.931745,10.16 18.89154,10.16 17.68421,0 51.62497,-16.32412 89.02022,-42.81509 20.69193,-14.65826 65.08475,-50.35605 77.01288,-61.92869 2.53755,-2.46192 5.39505,-4.48012 6.35,-4.48489 2.61932,-0.0131 7.86447,-8.43416 7.09236,-11.38673 -0.49813,-1.90484 1.45296,-4.59088 7.81786,-10.76276 8.45724,-8.20076 21.46842,-18.83152 26.04789,-21.28238 4.04669,-2.16572 -5.06169,9.78054 -17.60329,23.08792 -10.63967,11.28931 -9.62813,10.83707 4.41647,-1.97451 3.14325,-2.86729 8.40798,-6.74333 11.69941,-8.61342 5.42578,-3.08276 13.19452,-9.73693 32.43309,-27.77995 3.66712,-3.43925 6.6675,-5.83151 6.6675,-5.31615 0,0.51537 -2.34992,3.98296 -5.22205,7.70576 -5.33961,6.92115 -5.81355,10.61089 -1.36293,10.61089 1.31161,0 4.32991,0.99418 6.70735,2.20928 3.35218,1.7133 8.0292,2.26707 20.83263,2.46665 9.08049,0.14155 19.65324,0.37335 23.49499,0.51512 6.63931,0.245 13.43239,-0.94908 38.1,-6.69719 14.18679,-3.30585 17.823,-4.72297 37.9102,-14.77453 20.67538,-10.34591 36.04551,-22.25381 58.68808,-45.46817 28.08007,-28.78919 50.44162,-62.47674 59.12112,-89.06567 8.41352,-25.77413 9.86618,-34.78151 10.08689,-62.54489 0.15698,-19.74761 -0.19899,-26.65397 -1.47414,-28.60009 -0.92462,-1.41114 -2.29039,-7.94418 -3.03506,-14.51788 -0.89801,-7.92718 -2.23128,-13.3717 -3.95911,-16.16741 -2.81068,-4.54776 -3.78515,-4.92156 -7.56148,-2.90054 -3.11,1.66443 -3.14853,-0.0872 -0.088,-4.0009 1.30267,-1.66582 3.90873,-6.07785 5.79122,-9.8045 1.8825,-3.72664 10.9949,-16.87114 20.24978,-29.21 58.97042,-78.62086 89.91224,-130.02608 103.62924,-172.16452 0.88628,-2.72263 1.84123,-10.61497 2.12212,-17.53853 0.60851,-14.99904 -1.73975,-22.14724 -8.662,-26.3676 -6.23433,-3.80094 -6.57533,-3.19856 -1.76024,3.10954 9.05427,11.86175 7.0909,30.07373 -6.81414,63.20704 -5.71848,13.62615 -7.18539,13.62805 -1.80016,0.003 27.24156,-68.92675 -7.07914,-83.98693 -84.64432,-37.14278 -24.31678,14.6857 -66.06693,44.84684 -87.46294,63.18499 -3.05137,2.61528 -6.48037,5.29076 -7.62,5.94552 -1.13963,0.65476 -3.11365,2.51214 -4.38673,4.1275 -1.27307,1.61536 -3.00318,2.93703 -3.84469,2.93703 -0.84151,0 -3.04892,1.57162 -4.90537,3.4925 -1.85643,1.92087 -5.26982,5.07557 -7.5853,7.01042 -14.03624,11.72895 -13.72275,11.30078 -11.72614,16.01565 1.72169,4.06562 2.0875,4.25746 6.88999,3.61332 2.88184,-0.38654 4.7651,-0.18406 4.36153,0.46892 -1.46321,2.36751 2.75503,0.93868 6.42466,-2.17619 3.55726,-3.0195 3.60634,-3.17481 0.73994,-2.34129 l -3.07007,0.89276 2.67166,-3.39646 c 3.40392,-4.32737 19.48843,-16.25202 20.49146,-15.19185 0.41636,0.44006 -0.34389,1.44747 -1.68943,2.23866 -3.51743,2.06831 -14.74635,12.60911 -14.11007,13.24538 0.58,0.58001 12.21946,-7.83946 42.76001,-30.93066 9.92102,-7.5011 26.5344,-18.13677 41.43607,-26.52681 13.13355,-7.39453 15.97194,-9.66385 13.23028,-10.57773 -3.31459,-1.10486 -1.13822,-3.90619 5.0245,-6.4673 7.06198,-2.93483 10.07898,-3.33544 11.95914,-1.58798 0.66358,0.61674 5.207,1.45135 10.0965,1.8547 12.61383,1.04053 16.11723,2.33237 19.3675,7.14155 3.42519,5.06799 3.87929,15.87698 1.00884,24.01273 -1.01677,2.88182 -1.62861,5.23968 -1.35964,5.23968 0.26896,0 -0.5733,3.71475 -1.8717,8.255 -3.33374,11.65747 -0.48424,10.92887 5.74428,-1.46877 2.95071,-5.87325 3.05564,-5.95036 2.32197,-1.70623 -0.4226,2.44475 -0.68693,6.01516 -0.58739,7.93425 0.19938,3.84387 -4.45386,13.35972 -13.19847,26.99075 -3.13671,4.8895 -7.7043,12.37265 -10.1502,16.62925 -5.15766,8.97589 -9.53019,13.67298 -9.53019,10.23758 0,-1.15601 -0.42862,-2.08499 -0.9525,-2.06441 -1.51601,0.0596 -38.79423,57.15917 -37.90056,58.05285 0.44315,0.44314 2.45262,-1.15876 4.4655,-3.55978 5.03697,-6.00823 6.21951,-3.90048 1.96695,3.50588 -1.92593,3.35425 -4.51858,8.12186 -5.76145,10.59471 -1.24288,2.47283 -6.73348,9.55991 -12.20135,15.74906 -5.46787,6.18914 -12.51333,14.49353 -15.65658,18.45421 -11.05784,13.93347 -20.79437,24.16565 -22.9951,24.16565 -3.24461,0 -16.84376,14.27121 -29.0749,30.5118 -7.58695,10.07399 -29.93455,35.72932 -45.8236,52.60606 -5.29574,5.62492 -14.116,15.08488 -19.60058,21.02213 -45.5493,49.3086 -137.81854,141.7606 -164.46655,164.79231 -11.07476,9.57183 -53.49456,42.83665 -60.96307,47.80611 -4.55336,3.02974 -9.05246,6.44078 -9.99801,7.5801 -0.94554,1.13931 -2.17875,2.07148 -2.74045,2.07148 -1.68276,0 -20.52318,12.74926 -25.7852,17.44877 -3.3145,2.96018 -5.71375,4.15472 -7.43879,3.70361 -1.40048,-0.36623 -7.30083,0.10376 -13.11189,1.04448 -17.75034,2.87341 -23.54693,-0.24178 -26.57806,-14.28355 -0.87408,-4.04918 3.1424,-27.03275 5.71068,-32.67831 0.63552,-1.397 2.35765,-5.68325 3.82694,-9.525 3.21266,-8.40008 19.91712,-41.87068 28.88155,-57.86969 8.26667,-14.75375 8.9743,-17.42448 5.10957,-19.28457 -4.21745,-2.02986 -0.39275,-1.89231 7.76719,0.27932 3.79209,1.00921 7.99993,2.94013 9.35075,4.29095 2.42805,2.42805 2.42676,2.5336 -0.11366,9.26252 -1.41331,3.74356 -4.39132,10.80697 -6.61781,15.69647 -10.13166,22.24983 -13.60848,40.80767 -9.16048,48.895 3.40105,6.18377 10.83855,8.59383 20.35853,6.59699 11.64613,-2.44281 32.9679,-13.88176 52.71155,-28.27932 6.15649,-4.48946 12.29619,-8.7849 13.64375,-9.54542 3.40195,-1.91992 7.56714,-5.80574 8.90728,-8.30981 1.49365,-2.7909 -2.95853,-8.45881 -8.44522,-10.7513 C 250.05706,72.0072 219.89268,43.10781 209.31562,27.125 201.18975,14.84615 193.53283,0.38028 190.51209,-8.39969 c -1.39629,-4.05836 -3.01482,-8.01254 -3.59674,-8.78707 -2.03774,-2.71221 -5.26546,-12.97912 -9.22651,-29.34824 -9.4432,-39.02425 -5.07698,-86.91648 11.57072,-126.91685 19.14839,-46.0089 45.39767,-80.25862 83.51993,-108.97572 28.31657,-21.33058 55.52145,-33.73097 91.71043,-41.80302 11.86478,-2.64646 16.55547,-2.9744 42.545,-2.9744 25.76108,0 31.0231,0.36106 44.56577,3.05799 l 15.35578,3.058 24.01421,-21.34038 c 84.24255,-74.86264 153.99785,-123.26285 201.79778,-140.0188 12.92837,-4.53195 31.09543,-5.40018 39.0811,-1.86774 8.72322,3.8587 11.77593,10.2814 11.5954,24.39593 -0.32794,25.63808 -15.76631,62.24053 -49.85892,118.20932 -20.56154,33.75524 -63.49491,94.58361 -90.68397,128.48181 l -9.92665,12.37614 3.08129,9.84886 c 13.52489,43.23036 11.74402,96.11555 -4.77317,141.74501 -16.84776,46.5427 -50.93182,89.56046 -93.44358,117.93593 -10.12825,6.76034 -23.27275,14.54645 -29.21,17.30247 -20.44218,9.48911 -51.8051,19.38956 -67.30999,21.24801 -26.57559,3.1854 -40.07727,3.03333 -64.49039,-0.72638 l -19.10647,-2.94245 -12.46477,11.2578 c -6.85562,6.1918 -15.6212,14.43692 -19.47906,18.32247 -9.99033,10.06206 -43.16899,39.82649 -55.9093,50.15601 -5.93725,4.81377 -14.50975,11.9902 -19.05,15.94762 -17.71628,15.44208 -52.87239,41.17774 -78.60764,57.5439 -10.65783,6.77777 -40.142812,21.68456 -47.12235,23.82375 -16.37879,5.02002 -25.70077,5.8651 -33.65499,3.05099 z M 182.31975,142.73539 c 2.29935,-1.73914 2.11802,-1.86732 -2.69466,-1.905 -3.94287,-0.0309 -5.94573,-0.86414 -8.4917,-3.53289 -4.02336,-4.2174 -6.10217,-9.30521 -5.20451,-12.73787 0.36431,-1.39313 0.0412,-2.91695 -0.71825,-3.38625 -0.93946,-0.58062 -1.38064,1.75381 -1.38064,7.30539 0,6.26249 0.59621,8.91666 2.56531,11.41995 3.61514,4.59591 11.6955,6.03529 15.92445,2.83667 z m 10.16,-2.64615 c 2.36873,-1.89361 2.34094,-1.93731 -0.70977,-1.11638 -1.74625,0.46991 -4.03224,1.33938 -5.07999,1.93214 -1.52143,0.86076 -1.37851,1.08555 0.70976,1.11638 1.43812,0.0212 3.72412,-0.84822 5.08,-1.93214 z M 307.08411,41.31121 c 21.29876,-17.03386 40.5254,-34.81668 77.12812,-71.33621 45.12573,-45.02317 73.29255,-76.37477 105.54158,-117.475 38.92283,-49.60575 62.29086,-87.75295 64.75122,-105.70329 0.69907,-5.10024 0.46002,-5.98067 -1.97816,-7.28554 -1.52534,-0.81633 -8.88176,-7.49929 -16.34762,-14.85101 -14.8141,-14.58764 -20.29565,-18.34995 -30.36454,-20.84095 -5.8871,-1.45644 -6.89474,-1.40093 -8.99,0.49525 -3.51536,3.18135 -4.96071,2.62257 -5.87911,-2.27295 -0.96397,-5.13836 -5.06457,-8.52589 -9.09581,-7.51411 -1.5528,0.38972 -3.91527,0.12421 -5.24998,-0.59016 -1.33469,-0.71431 -7.81729,-2.22234 -14.40579,-3.3512 -6.58848,-1.12884 -15.73138,-3.1576 -20.31757,-4.50833 -6.44065,-1.89692 -12.79986,-2.50812 -27.94,-2.68539 -19.06303,-0.22319 -19.78444,-0.13468 -26.26159,3.22222 -4.85893,2.51822 -8.29354,3.39128 -12.7,3.22827 -6.55332,-0.24241 -17.21315,3.28039 -24.28247,8.02478 -14.59678,9.79622 -18.93052,12.56327 -21.76242,13.89509 -1.74625,0.82126 -7.31431,3.4288 -12.37345,5.79453 -9.97383,4.6639 -10.97596,5.66171 -47.95154,47.74461 -36.22102,41.22411 -38.735,44.57041 -38.735,51.55917 0,1.14508 -1.143,4.32388 -2.54,7.06402 -1.397,2.74014 -2.54,5.97397 -2.54,7.18633 0,1.21234 -1.42875,4.89138 -3.175,8.17561 -1.74625,3.28425 -3.175,6.78162 -3.175,7.77194 0,0.99032 -1.43595,3.68321 -3.19099,5.98419 -3.27422,4.29273 -6.10132,12.46625 -7.60829,21.99663 -0.67839,4.29026 -0.44027,5.55309 1.16312,6.16838 2.73806,1.05068 2.52052,4.18538 -0.60768,8.75692 -4.13543,6.04347 -5.1895,9.64228 -5.15733,17.60819 0.027,6.68292 0.42828,7.80372 3.90563,10.90828 3.30243,2.94841 3.88379,4.38238 3.93128,9.69681 0.0306,3.43019 0.92959,8.52271 1.99762,11.31671 1.06804,2.794 1.9421,5.93725 1.94237,6.985 0.001,4.64161 1.38016,8.60502 3.99803,11.49107 5.09578,5.61776 7.24624,9.93879 7.24624,14.56016 0,3.22913 0.907,5.36272 3.1233,7.3472 1.88899,1.69137 3.30675,4.55324 3.58747,7.24157 0.43171,4.1342 0.76832,4.44216 4.8142,4.40425 2.39252,-0.0225 6.55674,-0.48886 9.25383,-1.03656 l 4.9038,-0.9958 -4.11631,7.19455 c -3.68665,6.44358 -4.0475,7.95332 -3.45717,14.46365 0.36252,3.998 0.0994,8.26918 -0.58458,9.4915 -2.25083,4.02201 -0.0293,7.69353 7.59339,12.54976 4.08854,2.60469 9.07478,6.4487 11.0805,8.54223 2.00573,2.09354 4.40487,3.80642 5.33141,3.80642 0.92654,0 2.67699,0.55709 3.88989,1.23796 1.2129,0.68089 4.34839,2.08048 6.96777,3.11021 3.41046,1.34071 4.7625,2.63422 4.7625,4.5563 0,2.6257 0.13939,2.60818 6.41163,-0.80559 3.5264,-1.91931 11.38452,-7.46676 17.4625,-12.32767 z m 256.19924,-270.86543 c 2.77289,-3.91832 7.04749,-9.7762 9.49914,-13.0175 7.3336,-9.69569 12.70557,-18.33009 20.65463,-33.19827 2.05393,-3.84175 4.24481,-7.52793 4.86862,-8.1915 0.62383,-0.66358 1.13421,-2.16523 1.13421,-3.337 0,-1.17177 0.93533,-2.90675 2.07852,-3.85551 1.9758,-1.63977 6.81148,-11.50171 6.81148,-13.89142 0,-0.61867 -0.94911,-0.76064 -2.10914,-0.3155 -1.64084,0.62964 -1.9362,0.26833 -1.33027,-1.62736 6.82668,-21.35765 7.16061,-29.29687 1.50806,-35.85483 -2.71981,-3.15546 -3.64066,-3.40143 -12.25224,-3.27284 -6.78441,0.10134 -11.20705,0.96172 -16.29641,3.17044 -10.56829,4.58651 -35.93234,17.63015 -38.15962,19.62385 -1.08054,0.96722 -4.79529,3.25342 -8.255,5.08044 -3.45971,1.82702 -7.43337,4.32276 -8.83037,5.54611 -1.397,1.22332 -4.68313,3.50629 -7.3025,5.07325 -2.61938,1.56697 -4.7625,3.29843 -4.7625,3.84769 0,0.54927 6.14362,5.60195 13.6525,11.22818 17.4214,13.05346 29.45747,24.67278 38.10097,36.7817 3.51192,4.91993 3.74949,5.87069 2.66882,10.68042 -1.13857,5.06739 -5.09593,15.77392 -7.55186,20.43138 -2.5821,4.89672 1.13344,1.79562 5.87296,-4.90173 z m 17.59119,0.0733 c 0.43369,-1.1302 0.22078,-1.63998 -0.49913,-1.19505 -0.6905,0.42675 -1.25546,1.31369 -1.25546,1.97096 0,1.82681 0.9103,1.42426 1.75459,-0.77591 z m 37.18588,-98.35905 c 4.97011,-20.91533 -0.54709,-30.51316 -16.58842,-28.85755 -6.37654,0.65811 -20.53101,5.70193 -19.34096,6.89197 0.30161,0.30161 3.56598,-0.34775 7.25416,-1.44302 21.76016,-6.46208 30.60548,2.69125 24.65975,25.51846 -1.04775,4.02259 -1.88211,8.47959 -1.85415,9.90446 0.0808,4.11337 3.89004,-3.68376 5.86962,-12.01432 z M 146.04999,191.72588 c 0,-1.40178 3.29523,-2.76732 4.28631,-1.77624 0.39855,0.39856 0.42069,1.21642 0.0491,1.8175 -0.8916,1.44263 -4.33552,1.40985 -4.33552,-0.0413 z m -20.74333,-3.52255 c -1.54262,-1.54262 -0.8775,-2.89545 1.05833,-2.1526 1.04775,0.40207 1.905,1.24138 1.905,1.86515 0,1.33147 -1.7496,1.50119 -2.96333,0.28745 z M 107.94999,126.82 c 0.74765,-1.397 1.64512,-2.54 1.99437,-2.54 0.34925,0 0.0233,1.143 -0.72437,2.54 -0.74765,1.397 -1.64512,2.54 -1.99437,2.54 -0.34925,0 -0.0233,-1.143 0.72437,-2.54 z m 477.51996,-342.19418 c 0,-4.23097 4.83614,-9.17807 9.8425,-10.06834 2.27013,-0.40368 4.1275,-0.42439 4.1275,-0.046 0,1.06078 -6.60417,9.84672 -8.74277,11.63102 -3.00509,2.50726 -5.22723,1.86251 -5.22723,-1.5167 z m 15.875,-14.67581 c 0.4317,-0.6985 1.03693,-1.27 1.34496,-1.27 0.30802,0 0.56004,0.5715 0.56004,1.27 0,0.6985 -0.60523,1.27 -1.34495,1.27 -0.73973,0 -0.99175,-0.5715 -0.56005,-1.27 z m 6.985,-9.3695 c 0,-0.26372 1.00013,-1.26384 2.2225,-2.2225 2.0143,-1.57972 2.05922,-1.5348 0.4795,0.4795 -1.65909,2.1155 -2.702,2.78826 -2.702,1.743 z m 6.985,-9.6805 c 0.4317,-0.6985 1.03693,-1.27 1.34496,-1.27 0.30802,0 0.56004,0.5715 0.56004,1.27 0,0.6985 -0.60523,1.27 -1.34495,1.27 -0.73973,0 -0.99175,-0.5715 -0.56005,-1.27 z m 9.525,-12.29151 c 0,-0.70665 1.28588,-2.37353 2.8575,-3.70416 l 2.8575,-2.41933 -2.52646,3.175 c -1.38956,1.74625 -2.67543,3.41312 -2.8575,3.70417 -0.18207,0.29103 -0.33104,-0.049 -0.33104,-0.75568 z m -3.81,-24.46354 c 0,-0.65727 0.56496,-1.54422 1.25546,-1.97096 0.71991,-0.44493 0.93281,0.0649 0.49912,1.19504 -0.84428,2.20018 -1.75458,2.60273 -1.75458,0.77592 z m 45.72,-39.13394 c 0,-0.6985 0.85725,-1.98146 1.905,-2.85101 1.06266,-0.88193 1.905,-1.01946 1.905,-0.31101 0,0.6985 -0.85725,1.98145 -1.905,2.85101 -1.06266,0.88192 -1.905,1.01945 -1.905,0.31101 z m -7.22398,-41.0593 c 0.44954,-1.80582 1.18397,-7.23507 1.63207,-12.065 0.81596,-8.79517 -0.34034,-13.54454 -4.86006,-19.96204 -1.34989,-1.9167 -1.25907,-2.49192 0.55672,-3.52609 1.19336,-0.67967 1.88173,-1.98633 1.52971,-2.9037 -0.35203,-0.91737 0.30505,-2.4523 1.46017,-3.41097 3.57176,-2.9643 6.00932,1.77726 5.09385,9.90859 -0.57617,5.11758 -0.35265,6.6675 0.96157,6.6675 3.0346,0 4.65545,5.62278 3.17373,11.00981 -1.77331,6.44714 -5.42301,13.49234 -8.15663,15.74515 -2.01858,1.66355 -2.13819,1.53774 -1.39113,-1.46325 z m 5.95398,-43.07171 c 0,-0.6985 0.60523,-1.27 1.34495,-1.27 0.73973,0 0.99175,0.5715 0.56005,1.27 -0.4317,0.6985 -1.03693,1.27 -1.34496,1.27 -0.30802,0 -0.56004,-0.5715 -0.56004,-1.27 z"
+         id="path8392"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#767067;stroke-width:1.26999998"
+         d="m 51.435,283.66127 c -33.32897,-11.79138 -2.18798,-95.40843 83.00625,-222.8811 16.63523,-24.89064 15.30106,-23.52637 20.99424,-21.46769 2.52147,0.91179 4.5845,2.40506 4.5845,3.31841 0,1.33821 -0.48918,1.35514 -2.51947,0.0873 -3.20567,-2.00197 -3.16427,-2.04675 -16.70025,18.06191 -38.73973,57.5506 -69.54871,116.76897 -74.00186,142.24 -0.18318,1.04775 -0.76808,4.35364 -1.29979,7.34643 -0.5547,3.12225 -0.45288,6.0993 0.2389,6.985 2.2336,2.85971 2.64481,7.34924 0.969,10.57937 -0.9157,1.76499 -1.9689,2.58282 -2.34046,1.81739 -0.37156,-0.76543 -0.50412,-0.66303 -0.29457,0.22756 0.20955,0.89059 1.00688,1.48207 1.77184,1.31439 0.76495,-0.16767 1.9995,0.83246 2.74343,2.2225 0.74393,1.39005 1.01018,2.52736 0.59166,2.52736 -0.41852,0 0.4915,2.22843 2.02228,4.95207 4.06213,7.22761 7.95954,8.70171 20.874293,7.89524 l 5.079999,-0.31722 -5.187022,-1.62391 c -11.53964,-3.61278 -16.40343,-8.48362 -18.26309,-18.28954 -2.85312,-15.04446 4.71608,-42.16992 21.595008,-77.38919 4.770302,-9.95362 8.982412,-18.0975 9.360252,-18.0975 0.93448,0 -0.91756,4.68267 -5.35793,13.54695 -5.101616,10.18431 -12.58732,29.38012 -16.50842,42.33305 -4.36752,14.42759 -4.77364,33.85085 -0.84974,40.64 3.13906,5.43123 11.93174,10.16 18.89153,10.16 12.57034,0 41.58057,-11.86008 64.89941,-26.53245 22.59954,-14.21978 54.83171,-38.4871 81.91499,-61.6731 6.63575,-5.68087 13.91825,-11.62609 16.18332,-13.21159 3.61137,-2.52789 4.02653,-2.59933 3.3727,-0.5803 -0.60015,1.85324 -0.49082,1.94261 0.56037,0.45808 0.71831,-1.0144 1.00878,-2.32531 0.64548,-2.91313 -0.74912,-1.21209 1.84894,-4.76751 3.48377,-4.76751 0.59105,0 0.74568,0.85725 0.34363,1.905 -1.1416,2.97496 0.27114,2.28378 5.50255,-2.69209 2.65825,-2.52841 3.81983,-4.25577 2.58129,-3.83859 -1.93844,0.65294 -2.05513,0.39088 -0.83833,-1.88274 0.77746,-1.45269 1.13207,-3.71766 0.78802,-5.03327 -0.44936,-1.71838 1.76309,-4.70821 7.85612,-10.61647 8.45725,-8.20076 21.46842,-18.83152 26.04789,-21.28238 4.6682,-2.49834 -5.92522,10.79006 -19.19087,24.07304 -7.15027,7.15963 -12.38208,13.0175 -11.62622,13.0175 0.75585,0 4.03175,-2.5384 7.27978,-5.64089 9.98014,-9.53298 16.80876,-15.07782 30.35713,-24.65003 l 13.02163,-9.20007 7.93337,0.378 c 6.79681,0.32384 8.11147,0.78387 9.17654,3.21116 1.69727,3.86804 3.41437,4.29461 11.1515,2.77025 6.59921,-1.30016 36.18747,-1.8946 37.33205,-0.75002 0.32773,0.32772 3.84653,-0.20827 7.81956,-1.1911 3.97304,-0.98283 11.50996,-2.32313 16.74871,-2.97845 10.1058,-1.26415 11.84347,-1.61547 15.875,-3.20951 15.87471,-6.27676 25.17165,-9.89307 26.03499,-10.12708 9.17918,-2.48798 35.23219,-20.78686 53.59448,-37.64319 2.58472,-2.37274 6.72809,-5.94198 9.2075,-7.93165 2.47941,-1.98967 4.50802,-4.41854 4.50802,-5.3975 0,-0.97895 0.47457,-1.63704 1.05462,-1.46242 1.2773,0.38455 13.43183,-12.80284 14.50775,-15.74058 0.42061,-1.14845 3.29399,-5.4347 6.3853,-9.525 23.16256,-30.64792 32.61653,-49.94166 41.18677,-84.05424 3.08908,-12.2956 3.42409,-15.96747 3.61185,-39.58708 0.15698,-19.74761 -0.19898,-26.65397 -1.47414,-28.60009 -0.92461,-1.41114 -2.29038,-7.94418 -3.03506,-14.51788 -1.52861,-13.49384 -4.9563,-21.04518 -9.06508,-19.97071 -3.04359,0.79591 -2.98047,-0.0553 0.40726,-5.49191 1.52339,-2.44475 3.95014,-6.59569 5.39277,-9.22429 1.44262,-2.62862 8.67609,-12.64138 16.07437,-22.25058 23.91528,-31.06217 53.60255,-73.71479 69.13969,-99.33513 20.98556,-34.60464 37.33543,-69.10319 35.28383,-74.44958 -0.46808,-1.21981 -0.0127,-2.71296 1.07543,-3.52157 2.74446,-2.04061 5.20047,-12.48231 3.22361,-13.70519 -0.98038,-0.60645 -1.40569,-3.71353 -1.19233,-8.71054 0.42055,-9.8497 -2.86858,-17.86741 -8.81776,-21.4945 -6.23433,-3.80094 -6.57533,-3.19856 -1.76024,3.10954 9.05427,11.86175 7.0909,30.07373 -6.81414,63.20704 -5.71848,13.62615 -7.18539,13.62805 -1.80016,0.003 24.13197,-61.05876 0.0699,-80.49706 -61.74923,-49.8835 -15.22503,7.53961 -22.63902,11.67176 -33.05512,18.4231 -4.54025,2.94283 -11.684,7.55708 -15.875,10.25391 -4.191,2.69682 -11.75785,7.92857 -16.81521,11.62612 -5.05739,3.69736 -9.82474,6.72263 -10.59414,6.72263 -0.7694,0 -2.91805,1.18192 -4.77478,2.62649 -1.85673,1.44457 -9.37661,7.14524 -16.71086,12.66816 -16.2477,12.235 -59.52396,48.43993 -59.6204,49.87843 -0.0382,0.57094 1.04045,0.68576 2.39717,0.25516 1.81877,-0.57725 2.92906,0.0993 4.22649,2.5756 2.27342,4.33899 13.60247,8.53057 17.32873,6.41139 1.34446,-0.76463 13.49164,-9.72035 26.99374,-19.9016 49.2702,-37.1522 78.3986,-53.50234 98.36839,-55.21543 15.65032,-1.34255 21.80215,5.23108 20.34507,21.74005 -2.28413,25.87976 -33.92233,85.29674 -74.98732,140.82726 -27.36846,37.00874 -42.04031,56.04569 -54.79024,71.09068 C 468.50513,-95.52254 461.3324,-87.48828 414.78248,-38.28 302.79613,80.10162 224.37827,148.27857 172.71999,172.17058 c -8.79069,4.06571 -10.25262,4.36122 -18.96317,3.83331 -8.54117,-0.51765 -9.76221,-0.9347 -12.84783,-4.38809 -5.97711,-6.68956 -5.99561,-20.37017 -0.0528,-39.0808 3.68672,-11.60749 17.36645,-41.07119 28.54884,-61.48913 8.41467,-15.36437 9.7864,-18.66974 8.56174,-20.63074 -1.67798,-2.68687 -1.20447,-2.7856 5.04032,-1.05092 2.51467,0.69853 5.57001,2.37269 6.78964,3.72038 2.11309,2.33495 2.09751,2.76817 -0.33103,9.20036 -1.40168,3.71253 -4.37019,10.75055 -6.59668,15.64005 -10.13167,22.24983 -13.60848,40.80767 -9.16049,48.895 3.40105,6.18377 10.83855,8.59383 20.35854,6.59699 11.64612,-2.44281 32.9679,-13.88176 52.71155,-28.27932 6.15648,-4.48946 12.29618,-8.7849 13.64374,-9.54542 3.40195,-1.91992 7.56715,-5.80574 8.90729,-8.30981 1.49364,-2.7909 -2.95853,-8.45881 -8.44522,-10.7513 C 250.05707,72.0072 219.8927,43.10781 209.31563,27.125 201.18977,14.84615 193.53285,0.38028 190.5121,-8.39969 c -1.39629,-4.05836 -3.01481,-8.01254 -3.59674,-8.78707 -2.03774,-2.71221 -5.26546,-12.97912 -9.22651,-29.34824 -9.4432,-39.02425 -5.07698,-86.91648 11.57073,-126.91685 19.14838,-46.0089 45.39767,-80.25862 83.51993,-108.97572 28.31656,-21.33058 55.52145,-33.73097 91.71042,-41.80302 11.86479,-2.64646 16.55547,-2.9744 42.545,-2.9744 25.76108,0 31.0231,0.36106 44.56577,3.05799 l 15.35578,3.058 24.01422,-21.34038 c 84.24253,-74.86264 153.99783,-123.26285 201.79776,-140.0188 12.92837,-4.53195 31.09543,-5.40018 39.0811,-1.86774 8.72322,3.8587 11.77593,10.2814 11.5954,24.39593 -0.32794,25.63808 -15.76631,62.24053 -49.85892,118.20932 -20.56154,33.75524 -63.49491,94.58361 -90.68397,128.48181 l -9.92665,12.37614 3.08129,9.84886 c 13.52489,43.23036 11.74402,96.11555 -4.77317,141.74501 -16.84776,46.5427 -50.93182,89.56046 -93.44358,117.93593 -10.12825,6.76034 -23.27275,14.54645 -29.21,17.30247 -20.44218,9.48911 -51.8051,19.38956 -67.30999,21.24801 -26.57559,3.1854 -40.07727,3.03333 -64.49039,-0.72638 l -19.10647,-2.94245 -12.46477,11.2578 c -6.85562,6.1918 -15.6212,14.43692 -19.47906,18.32247 -9.99033,10.06206 -43.16899,39.82649 -55.9093,50.15601 -5.93725,4.81377 -14.50975,11.9902 -19.05,15.94762 -17.71628,15.44208 -52.87239,41.17774 -78.60764,57.5439 -10.65783,6.77777 -40.142812,21.68456 -47.12235,23.82375 -16.37879,5.02002 -25.70077,5.8651 -33.65499,3.05099 z M 182.31975,142.73539 c 2.29935,-1.73914 2.11802,-1.86732 -2.69466,-1.905 -3.94287,-0.0309 -5.94573,-0.86414 -8.4917,-3.53289 -4.02336,-4.2174 -6.10217,-9.30521 -5.20451,-12.73787 0.36431,-1.39313 0.0412,-2.91695 -0.71825,-3.38625 -0.93946,-0.58062 -1.38064,1.75381 -1.38064,7.30539 0,6.26249 0.59621,8.91666 2.56531,11.41995 3.61514,4.59591 11.6955,6.03529 15.92445,2.83667 z m 10.16,-2.64615 c 2.36873,-1.89361 2.34094,-1.93731 -0.70977,-1.11638 -1.74625,0.46991 -4.03224,1.33938 -5.07999,1.93214 -1.52143,0.86076 -1.37851,1.08555 0.70976,1.11638 1.43812,0.0212 3.72412,-0.84822 5.08,-1.93214 z m 94.37518,-82.79085 c 18.5826,-10.99301 51.6577,-40.35066 101.20792,-89.83275 47.63825,-47.57278 80.64775,-85.05454 116.34735,-132.11063 28.92261,-38.12326 51.71039,-77.98096 51.82297,-90.64268 0.0185,-2.07855 -3.2219,-6.34346 -10.45071,-13.75498 -18.97906,-19.45874 -32.13388,-27.88054 -47.04107,-30.11601 -4.23053,-0.6344 -6.34116,-1.73619 -7.90862,-4.12844 -1.87951,-2.86849 -2.76687,-3.17875 -7.33775,-2.56567 -2.85984,0.38359 -6.20484,0.15948 -7.43336,-0.498 -1.22852,-0.65749 -8.51685,-2.26746 -16.19627,-3.5777 -7.96227,-1.35851 -15.7649,-3.47827 -18.1565,-4.93262 -5.07287,-3.08483 -10.57741,-3.98566 -26.41892,-4.3235 -12.53931,-0.26741 -17.41568,0.78814 -27.89004,6.03718 -4.92859,2.46986 -6.64192,2.72436 -11.80968,1.75429 -3.74653,-0.7033 -7.27862,-0.66559 -9.29017,0.0992 -1.785,0.67865 -4.52924,1.23391 -6.09831,1.23391 -1.56906,0 -3.9637,1.03492 -5.32141,2.29981 -2.91133,2.71233 -14.37012,8.76695 -25.95039,13.71175 -11.9166,5.08839 -24.52951,15.62445 -34.11282,28.49573 -6.82133,9.16172 -35.85842,42.23369 -55.09669,62.75274 -15.85854,16.91432 -23.78479,27.94665 -23.80856,33.13847 -0.006,1.43192 -0.5395,3.14643 -1.18432,3.81 -0.64483,0.66357 -1.84399,4.92125 -2.6648,9.4615 -0.82081,4.54025 -3.2936,11.684 -5.49509,15.875 -3.6228,6.89676 -4.08076,9.06648 -4.82507,22.86 -0.4523,8.382 -1.77743,18.38325 -2.94472,22.225 -3.2941,10.84138 -3.48521,13.41186 -1.37295,18.4672 1.08077,2.58664 1.96503,7.81756 1.96503,11.62424 0,6.58547 2.98905,14.04547 8.29746,20.70856 0.83472,1.04775 3.36236,7.62 5.61698,14.605 2.25461,6.985 5.47545,15.20086 7.15743,18.25745 1.68197,3.05662 3.05813,6.55851 3.05813,7.78201 0,3.334 2.98426,5.16872 7.78146,4.78404 3.3263,-0.26674 4.14538,0.0822 3.66526,1.5615 -0.65873,2.02959 -0.57345,13.51345 0.16875,22.72271 0.36812,4.56762 1.22608,6.17755 4.73765,8.89 2.35846,1.82175 8.30398,6.6132 13.21228,10.64763 4.90829,4.03445 10.87172,8.25952 13.25204,9.38906 2.38033,1.12954 5.29305,3.3828 6.47272,5.00724 2.91777,4.0179 4.72134,3.79725 14.04479,-1.71826 z m 276.42842,-286.85261 c 2.77289,-3.91832 7.04749,-9.7762 9.49914,-13.0175 7.3336,-9.69569 12.70557,-18.33009 20.65463,-33.19827 2.05393,-3.84175 4.24481,-7.52793 4.86862,-8.1915 0.62383,-0.66358 1.13421,-2.16523 1.13421,-3.337 0,-1.17177 0.93533,-2.90675 2.07852,-3.85551 1.9758,-1.63977 6.81148,-11.50171 6.81148,-13.89142 0,-0.61867 -0.94911,-0.76064 -2.10914,-0.3155 -1.64084,0.62964 -1.9362,0.26833 -1.33027,-1.62736 6.82668,-21.35765 7.16061,-29.29687 1.50806,-35.85483 -2.71981,-3.15546 -3.64066,-3.40143 -12.25224,-3.27284 -6.78441,0.10134 -11.20705,0.96172 -16.29641,3.17044 -10.56829,4.58651 -35.93234,17.63015 -38.15962,19.62385 -1.08054,0.96722 -4.79529,3.25342 -8.255,5.08044 -3.45971,1.82702 -7.43337,4.32276 -8.83037,5.54611 -1.397,1.22332 -4.68313,3.50629 -7.3025,5.07325 -2.61938,1.56697 -4.7625,3.29843 -4.7625,3.84769 0,0.54927 6.14362,5.60195 13.6525,11.22818 17.4214,13.05346 29.45747,24.67278 38.10097,36.7817 3.51192,4.91993 3.74949,5.87069 2.66882,10.68042 -1.13857,5.06739 -5.09593,15.77392 -7.55186,20.43138 -2.5821,4.89672 1.13344,1.79562 5.87296,-4.90173 z m 54.77707,-98.28577 c 4.97011,-20.91533 -0.54709,-30.51316 -16.58842,-28.85755 -6.37654,0.65811 -20.53101,5.70193 -19.34096,6.89197 0.30161,0.30161 3.56598,-0.34775 7.25416,-1.44302 21.76016,-6.46208 30.60548,2.69125 24.65975,25.51846 -1.04775,4.02259 -1.88211,8.47959 -1.85415,9.90446 0.0808,4.11337 3.89004,-3.68376 5.86962,-12.01432 z M 107.94999,126.82 c 0.74765,-1.397 1.64512,-2.54 1.99437,-2.54 0.34925,0 0.0233,1.143 -0.72437,2.54 -0.74765,1.397 -1.64512,2.54 -1.99437,2.54 -0.34925,0 -0.0233,-1.143 0.72437,-2.54 z m 426.71997,-294.40239 c 0,-1.54946 7.48575,-8.4152 8.29395,-7.607 0.86982,0.86984 -5.54847,8.6394 -7.13684,8.6394 -0.63641,0 -1.15711,-0.46458 -1.15711,-1.0324 z m 50.79999,-45.87493 c 0,-5.13324 4.63541,-10.16689 11.1565,-12.11498 2.73617,-0.8174 2.66381,-0.51739 -1.27,5.26526 -2.29432,3.37263 -4.743,6.55192 -5.4415,7.0651 -1.79805,1.32101 -4.445,1.19275 -4.445,-0.21538 z m 15.875,-16.59267 c 0.4317,-0.6985 1.03693,-1.27 1.34496,-1.27 0.30802,0 0.56004,0.5715 0.56004,1.27 0,0.6985 -0.60523,1.27 -1.34495,1.27 -0.73973,0 -0.99175,-0.5715 -0.56005,-1.27 z m 6.985,-9.3695 c 0,-0.26372 1.00013,-1.26384 2.2225,-2.2225 2.0143,-1.57972 2.05922,-1.5348 0.4795,0.4795 -1.65909,2.1155 -2.702,2.78826 -2.702,1.743 z m 6.985,-9.6805 c 0.4317,-0.6985 1.03693,-1.27 1.34496,-1.27 0.30802,0 0.56004,0.5715 0.56004,1.27 0,0.6985 -0.60523,1.27 -1.34495,1.27 -0.73973,0 -0.99175,-0.5715 -0.56005,-1.27 z m 9.525,-12.29151 c 0,-0.70665 1.28588,-2.37353 2.8575,-3.70416 l 2.8575,-2.41933 -2.52646,3.175 c -1.38956,1.74625 -2.67543,3.41312 -2.8575,3.70417 -0.18207,0.29103 -0.33104,-0.049 -0.33104,-0.75568 z m 10.36128,-52.16099 c 2.94997,-4.01638 6.74095,-9.19742 8.42439,-11.51342 3.86427,-5.31623 4.82706,-4.50062 2.18517,1.85114 -1.9494,4.68686 -12.75596,16.96478 -14.93174,16.96478 -0.57277,0 1.37223,-3.28613 4.32218,-7.3025 z m 31.54872,-11.43649 c 0,-0.6985 0.85725,-1.98146 1.905,-2.85101 1.06266,-0.88193 1.905,-1.01946 1.905,-0.31101 0,0.6985 -0.85725,1.98145 -1.905,2.85101 -1.06266,0.88192 -1.905,1.01945 -1.905,0.31101 z m -2.33948,-76.51101 c 0,-1.74625 0.28826,-2.46063 0.64057,-1.5875 0.35231,0.87312 0.35231,2.30187 0,3.175 -0.35231,0.87312 -0.64057,0.15875 -0.64057,-1.5875 z m 1.06948,-7.62 c 0,-0.6985 0.60523,-1.27 1.34495,-1.27 0.73973,0 0.99175,0.5715 0.56005,1.27 -0.4317,0.6985 -1.03693,1.27 -1.34496,1.27 -0.30802,0 -0.56004,-0.5715 -0.56004,-1.27 z"
+         id="path8390"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#756a57;stroke-width:1.26999998"
+         d="M 52.93843,283.80174 C 36.60027,277.73301 34.3919,261.27112 45.29627,226.83482 57.55192,188.13118 85.39514,136.13061 130.46874,67.765 c 10.1315,-15.367 19.03707,-28.32874 19.79014,-28.80388 0.75309,-0.47514 2.42334,-0.29975 3.71167,0.38974 2.26537,1.21238 2.26213,1.32434 -0.0982,3.40388 -1.34234,1.18265 -7.23709,9.29401 -13.0994,18.02526 -56.34357,83.91709 -87.2834,155.00922 -77.6966,178.52765 1.98603,4.87215 2.74911,5.60907 5.18617,5.00833 1.61923,-0.39913 2.8575,-0.11658 2.8575,0.65192 0,2.2258 10.39034,6.25423 16.20857,6.28421 2.977468,0.0154 9.272771,-1.04155 13.98956,-2.34864 8.45168,-2.34208 22.29248,-7.78619 21.5875,-8.49116 -0.19891,-0.19891 -4.93118,0.73173 -10.51615,2.0681 -13.257844,3.17235 -19.178594,3.07946 -26.00354,-0.40791 -8.328,-4.2554 -10.69252,-8.27674 -11.23986,-19.11562 -0.48318,-9.5685 0.79502,-18.73175 4.28995,-30.75386 2.73917,-9.42242 3.65453,-8.27954 1.1104,1.38642 -2.92294,11.1052 -2.09759,32.14369 1.44704,36.88566 8.564675,11.45773 21.58565,12.2886 47.08316,3.00438 6.74217,-2.45498 25.21078,-11.93678 34.11836,-17.51639 7.41159,-4.64253 22.65465,-14.62957 24.13,-15.80962 8.33915,-6.67003 21.99019,-15.62674 23.20151,-15.22297 0.85132,0.28378 2.10873,-0.95924 2.79424,-2.76227 0.68551,-1.80302 1.60742,-3.27823 2.04869,-3.27823 0.44126,0 0.47462,0.85395 0.074,1.89765 -1.41152,3.67839 32.20832,-23.3705 65.49943,-52.69765 13.87597,-12.22375 28.67881,-24.60129 32.89523,-27.50567 4.21642,-2.90436 10.43962,-7.61924 13.82934,-10.4775 6.65151,-5.60862 8.55595,-6.25539 9.69317,-3.29183 0.40207,1.04775 1.95575,1.91119 3.45265,1.91874 1.49688,0.008 5.54273,0.86953 8.99076,1.91548 3.44804,1.04595 7.25757,1.58802 8.46563,1.2046 1.20806,-0.38343 2.54462,-0.13381 2.97015,0.55469 0.42551,0.68851 4.67021,1.21517 9.43264,1.17036 4.76244,-0.0448 9.96687,0.61848 11.56539,1.47399 3.4266,1.83385 16.84906,1.89188 20.22074,0.0874 1.29219,-0.69155 4.85074,-1.84964 7.90787,-2.57351 3.05714,-0.72388 9.55893,-2.44038 14.44843,-3.81447 4.8895,-1.37408 10.49702,-2.51497 12.46115,-2.5353 3.62071,-0.0375 8.73944,-2.14855 29.62916,-12.2196 6.38568,-3.07856 11.90792,-5.59739 12.27165,-5.59739 0.36374,0 3.45292,-1.85737 6.86487,-4.1275 22.37971,-14.89026 60.53488,-52.67806 78.81381,-78.05492 8.76288,-12.16562 20.55061,-33.03044 25.14095,-44.50062 1.88676,-4.71457 5.08575,-14.85845 7.10886,-22.54195 3.24052,-12.3071 3.58727,-13.06297 2.913,-6.35 -2.56908,25.57758 -16.55793,60.52901 -35.32689,88.265 -21.05806,31.11872 -37.23616,47.26104 -68.08717,67.93661 -32.77044,21.96193 -70.16926,35.32491 -108.61938,38.81082 -18.45411,1.67305 -31.31016,1.03928 -53.99658,-2.66189 -8.47759,-1.38308 -16.08421,-2.10865 -16.90362,-1.61238 -2.13863,1.29527 -11.11858,9.45586 -24.8853,22.61468 -27.55424,26.33752 -49.44889,46.13804 -60.12657,54.37571 -4.35486,3.35971 -10.77542,8.64826 -14.26792,11.75233 -14.27551,12.68781 -17.23779,15.11343 -34.29,28.0779 -40.40487,30.71902 -74.01098,50.9463 -98.56688,59.32669 -15.82284,5.4 -25.2444,6.6042 -32.00968,4.09126 z M 146.52531,175.4744 c -7.12328,-1.39763 -11.41934,-12.02167 -9.8869,-24.44998 2.03873,-16.53442 13.94669,-45.61563 32.696,-79.84911 5.47219,-9.99141 9.63349,-18.98949 9.24736,-19.99574 -0.9044,-2.35686 1.85716,-2.35295 7.51401,0.0102 5.53261,2.31167 5.54572,4.13303 0.10744,14.92952 -2.3589,4.6831 -4.68767,10.11235 -5.17503,12.065 -0.48738,1.95266 -1.7986,4.56275 -2.91384,5.8002 -1.11524,1.23746 -2.17246,3.52346 -2.34939,5.08 -0.17692,1.55655 -1.49612,7.97358 -2.93154,14.26008 -1.43542,6.2865 -2.61966,12.98223 -2.63167,14.87941 -0.03,4.73093 3.32851,11.29368 7.28564,14.23701 3.57388,2.65827 13.76809,3.46819 19.36256,1.53835 2.1786,-0.75154 2.77645,-0.68331 1.905,0.21741 -0.6985,0.72196 -4.72407,2.46707 -8.94571,3.87803 -7.41392,2.47791 -13.81758,2.34672 -18.21852,-0.37321 -3.75249,-2.31916 -5.24839,-8.23565 -4.39738,-17.39217 0.9548,-10.27333 -0.82625,-11.29461 -3.20718,-1.83904 -6.33606,25.16274 8.57451,33.06231 36.67379,19.42968 14.69054,-7.12725 35.16383,-20.72025 49.49594,-32.86224 1.72752,-1.46353 4.44215,-3.25384 6.0325,-3.97845 1.59035,-0.72462 2.89155,-1.71953 2.89155,-2.21091 0,-0.49138 2.32016,-2.5013 5.15591,-4.46647 7.58708,-5.25785 8.31468,-7.36158 4.34575,-12.56511 -1.81728,-2.38259 -4.99307,-4.92073 -7.0573,-5.64033 -10.07119,-3.51088 -40.50199,-32.15051 -51.08167,-48.07502 -7.43381,-11.18935 -16.62894,-28.30236 -19.31878,-35.95407 -1.09488,-3.1146 -3.15839,-8.08246 -4.58557,-11.03968 -3.96057,-8.20668 -11.05174,-38.66321 -12.6457,-54.31323 -3.82491,-37.55426 5.23787,-81.04596 25.11196,-120.51061 31.96281,-63.46967 89.27576,-110.71859 154.29323,-127.19967 19.26828,-4.88425 35.44821,-6.9115 55.00716,-6.89207 19.71327,0.0195 29.87408,1.15608 47.26437,5.28654 l 11.70435,2.77996 6.07565,-5.81709 c 3.34159,-3.1994 9.50462,-8.84197 13.69562,-12.53905 4.191,-3.69708 9.04875,-8.001 10.795,-9.56427 3.27241,-2.92952 24.22917,-21.08819 28.35921,-24.57276 1.27832,-1.07854 3.69625,-3.20941 5.37317,-4.73529 1.67693,-1.52586 5.20304,-4.38011 7.83579,-6.34277 2.63276,-1.96265 5.07258,-3.92107 5.42183,-4.35204 1.25889,-1.55344 26.34593,-21.15969 40.64,-31.76133 70.34319,-52.17175 123.64072,-75.63133 146.09842,-64.30657 27.92616,14.08234 6.2095,76.21569 -62.69894,179.3875 -17.86829,26.75294 -44.77858,63.78488 -65.79194,90.53797 -7.71134,9.81766 -10.10657,13.76615 -9.63012,15.875 0.34882,1.54386 1.83108,7.37903 3.29391,12.96703 1.46284,5.588 2.60218,11.303 2.53189,12.7 l -0.12783,2.54 -0.56257,-2.54 c -2.3613,-10.66135 -6.21562,-21.92278 -8.07359,-23.58919 -2.11511,-1.89701 -2.05744,-2.33687 1.12839,-8.60778 1.84658,-3.63474 9.41055,-14.46236 16.80883,-24.0614 35.84378,-46.50613 65.72461,-91.37332 86.44247,-129.79663 17.9339,-33.26019 24.73934,-51.38319 25.99117,-69.215 0.6081,-8.66212 0.49406,-9.09159 -4.44938,-16.75424 -6.65626,-10.31764 -10.71043,-12.47705 -23.01068,-12.2564 -5.23875,0.094 -11.811,1.04742 -14.605,2.11877 l -5.08,1.9479 10.16,0.11037 c 11.9709,0.13005 18.60607,3.10835 22.78922,10.2293 2.15091,3.66148 2.58544,6.45758 2.46695,15.8743 -0.20197,16.05046 -2.63377,21.76469 -2.8401,6.67364 -0.17434,-12.75081 -1.96148,-18.06665 -7.69613,-22.89204 -11.16212,-9.3923 -31.20274,-6.82119 -61.12309,7.84176 -15.51004,7.60095 -24.10939,12.39074 -35.51185,19.77989 -4.54025,2.94222 -11.684,7.55598 -15.875,10.2528 -4.191,2.69682 -11.75785,7.92858 -16.81521,11.62613 -5.05739,3.69755 -9.82474,6.72282 -10.59414,6.72282 -0.7694,0 -2.91805,1.17319 -4.77478,2.60711 -11.66753,9.01056 -23.12238,17.56027 -27.50586,20.52991 -2.794,1.89284 -7.366,5.39548 -10.16,7.78367 -2.794,2.38818 -11.08075,9.33636 -18.415,15.44042 -17.87784,14.8791 -23.20984,19.75237 -22.4467,20.51551 0.35153,0.35154 1.84292,-0.005 3.31418,-0.79245 3.41449,-1.82739 5.16252,-0.72842 5.16252,3.24564 0,1.72694 0.71438,3.34034 1.5875,3.58529 0.87313,0.24498 3.94188,1.15118 6.81946,2.01379 6.72368,2.01557 5.938,2.45787 36.26455,-20.41526 49.24375,-37.14101 78.38993,-53.50209 98.36261,-55.21543 15.65032,-1.34255 21.80215,5.23108 20.34507,21.74005 -2.28413,25.87976 -33.92233,85.29674 -74.98732,140.82726 -27.36846,37.00925 -42.04031,56.0462 -54.79024,71.09119 C 468.50513,-95.52254 461.3324,-87.48828 414.78248,-38.28 302.88827,80.00421 223.92472,148.66384 172.87894,172.05763 c -9.17033,4.20267 -17.10505,5.23141 -26.35363,3.41677 z m 23.63523,-70.00185 c -0.0152,-2.67305 -0.28764,-3.00255 -1.25055,-1.51255 -1.62167,2.50934 -1.62167,6.71725 0,5.715 0.6985,-0.4317 1.26125,-2.3228 1.25055,-4.20245 z M 286.85493,57.29839 c 18.5826,-10.99301 51.6577,-40.35066 101.20792,-89.83275 47.63825,-47.57278 80.64775,-85.05454 116.34735,-132.11063 28.91268,-38.11018 51.71012,-77.98067 51.82297,-90.63335 0.0183,-2.0472 -3.10667,-6.23253 -9.81571,-13.14653 -20.00328,-20.61438 -36.44364,-31.48193 -48.46655,-32.03777 -4.41459,-0.2041 -5.81528,-0.83007 -6.59919,-2.94918 -1.31628,-3.55824 -5.68604,-6.16999 -11.92676,-7.12849 -8.28092,-1.27185 -12.29892,-2.08723 -19.9032,-4.03896 -3.96175,-1.01684 -9.9625,-2.14989 -13.33499,-2.51787 -20.90849,-2.28141 -28.28862,-2.68026 -42.89762,-2.31826 -13.35772,0.33099 -16.75001,0.80554 -18.75972,2.62429 -1.4979,1.35559 -4.7649,2.30091 -8.3889,2.42737 -6.93768,0.24208 -18.77409,3.50908 -21.68675,5.98582 -3.59424,3.05632 -17.03497,10.59349 -25.52381,14.31302 -10.78696,4.72648 -21.95008,12.92629 -29.84499,21.92247 -3.4925,3.97967 -8.77888,9.43002 -11.7475,12.11188 -2.96863,2.68187 -5.3975,5.60114 -5.3975,6.48727 0,0.88614 -1.45476,2.56435 -3.23279,3.72937 -5.56347,3.64532 -21.17557,20.21348 -22.9072,24.31002 -0.9093,2.1511 -2.61927,4.71282 -3.79995,5.69269 -1.18069,0.97988 -5.2026,5.79214 -8.93759,10.6939 -3.73499,4.90177 -9.70303,12.34131 -13.2623,16.53231 -7.64775,9.00511 -13.87157,19.27794 -13.88825,22.9235 -0.006,1.43192 -0.5395,3.14642 -1.18432,3.81 -0.64483,0.66357 -1.84399,4.92125 -2.6648,9.4615 -0.82081,4.54025 -3.2936,11.684 -5.49509,15.875 -3.6228,6.89676 -4.08076,9.06647 -4.82507,22.86 -0.4523,8.382 -1.77743,18.38324 -2.94472,22.22499 -3.2941,10.84139 -3.48521,13.41187 -1.37295,18.4672 1.08077,2.58665 1.96503,7.81756 1.96503,11.62425 0,6.58547 2.98905,14.04546 8.29746,20.70855 0.83472,1.04775 3.36236,7.62 5.61698,14.605 2.25461,6.985 5.47545,15.20086 7.15743,18.25746 1.68197,3.05661 3.05813,6.55851 3.05813,7.782 0,3.33401 2.98426,5.16872 7.78146,4.78404 3.3263,-0.26674 4.14538,0.0822 3.66526,1.5615 -0.65873,2.02959 -0.57345,13.51345 0.16875,22.72272 0.36812,4.56761 1.22608,6.17754 4.73765,8.89 2.35846,1.82175 8.30398,6.61319 13.21228,10.64763 4.90829,4.03444 10.87172,8.25952 13.25204,9.38905 2.38033,1.12954 5.29305,3.3828 6.47272,5.00725 2.91777,4.01789 4.72134,3.79725 14.04479,-1.71826 z m 266.86503,-272.88407 c 0,-0.97037 -0.47977,-1.76431 -1.06614,-1.76431 -1.21176,0 -4.03553,5.68598 -3.97161,7.99725 0.0584,2.1137 5.03775,-4.04693 5.03775,-6.23294 z m 4.50263,-5.27626 c 1.20011,-0.84343 18.0311,-24.57244 24.0829,-33.95305 11.7067,-18.14599 31.69675,-56.94862 35.33051,-68.58 8.5567,-27.38922 -1.51414,-39.53797 -26.31009,-31.73869 -9.12834,2.87122 -30.71413,14.88892 -30.30345,16.87119 0.18079,0.87267 -1.04955,1.5325 -2.8575,1.5325 -1.74625,0 -3.175,0.50217 -3.175,1.11592 0,0.61375 -1.85738,1.84711 -4.1275,2.74079 -2.27013,0.89366 -7.10335,3.8205 -10.74051,6.50407 -3.63715,2.68356 -7.06615,4.89373 -7.62,4.91146 -0.55384,0.0178 -4.39051,2.84625 -8.52592,6.28558 -4.13542,3.43932 -9.13604,6.92315 -11.1125,7.74183 -1.97647,0.81868 -3.59357,2.20691 -3.59357,3.08498 0,0.87806 3.14325,3.95199 6.985,6.83092 3.84175,2.87895 9.98537,7.49409 13.6525,10.25587 14.17232,10.67347 35.2425,33.23914 35.2425,37.74392 0,3.1337 -2.37543,9.89143 -7.14923,20.33851 -3.91802,8.57431 -3.84863,11.17492 0.22186,8.3142 z m 51.37245,-97.43586 c 3.42058,-11.96114 2.81594,-19.85971 -1.89679,-24.77875 -3.84533,-4.01366 -3.56123,-4.69368 1.03269,-2.47174 2.54206,1.22953 2.51764,1.11875 -0.29206,-1.32496 -1.68634,-1.46668 -3.40084,-2.1602 -3.81,-1.54116 -0.40917,0.61902 -4.45868,1.07705 -8.99893,1.01783 -7.56864,-0.0987 -12.3001,1.00328 -20.00058,4.65833 -1.57057,0.74548 -3.18969,1.02131 -3.59805,0.61296 -0.80354,-0.80353 7.93398,-4.92745 16.86272,-7.95884 3.05507,-1.03722 8.72191,-1.88586 12.59299,-1.88586 6.08585,0 7.54067,0.50237 10.75061,3.71231 2.21521,2.2152 3.71231,5.05513 3.71231,7.04207 0,4.54427 -6.0975,28.87112 -7.6175,30.39112 -0.67368,0.67368 -0.10553,-2.68931 1.26259,-7.47331 z m -328.2626,436.5453 c -0.66437,-1.67924 1.39785,-4.343 8.43662,-10.89765 C 298.1394,99.55522 307.38101,92.53 309.26442,92.53 c 0.3877,0 0.0282,1.28588 -0.79901,2.8575 -2.12773,4.04276 -22.8587,25.0825 -24.71439,25.0825 -0.8466,0 -1.93497,-1.00012 -2.41858,-2.2225 z M 600.95754,-98.605 c 0.0259,-2.0955 0.31051,-2.799 0.63251,-1.56333 0.322,1.23567 0.30082,2.95017 -0.0471,3.81 -0.34789,0.85983 -0.61134,-0.15117 -0.58545,-2.24667 z m 1.36831,-12.06499 c 0,-3.14325 0.24767,-4.42913 0.55038,-2.8575 0.30272,1.57162 0.30272,4.14337 0,5.715 -0.30271,1.57162 -0.55038,0.28575 -0.55038,-2.8575 z m 1.15194,-19.05 c 0.0259,-7.82756 0.0879,-7.99637 1.19814,-3.26023 0.72348,3.0863 0.71304,6.24046 -0.0273,8.255 -0.95942,2.61062 -1.19271,1.61541 -1.17084,-4.99477 z m -1.25025,-18.415 c 0.0259,-2.0955 0.31051,-2.79901 0.63251,-1.56334 0.322,1.23568 0.30082,2.95017 -0.0471,3.81 -0.34789,0.85983 -0.61134,-0.15117 -0.58545,-2.24666 z m -2.08445,-13.95835 c -0.49153,-3.27769 -0.67832,-6.17481 -0.4151,-6.43803 0.97048,-0.97047 3.09777,9.50282 2.21898,10.92473 -0.50061,0.81002 -1.31237,-1.20899 -1.80388,-4.4867 z m -13.40314,-54.42482 c 0,-3.37301 3.58909,-7.18183 6.7675,-7.18183 2.83198,0 2.67577,2.00591 -0.50397,6.47145 -3.18877,4.47819 -6.26353,4.82692 -6.26353,0.71038 z m 46.99,-93.40603 c 0,-1.48567 11.00637,-17.50408 12.78063,-18.60064 2.31576,-1.43121 1.12254,1.13468 -4.48786,9.65076 -5.63154,8.54811 -8.29277,11.42023 -8.29277,8.94988 z m 71.22026,-96.12663 c 0.061,-1.47948 0.36187,-1.78042 0.76728,-0.7673 0.36687,0.91678 0.32176,2.01216 -0.1002,2.43417 -0.42201,0.42201 -0.72217,-0.32808 -0.66703,-1.66687 z"
+         id="path8388"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#6b6256;stroke-width:1.26999998"
+         d="m 52.93843,283.80174 c -8.49167,-3.1542 -12.51776,-8.31974 -14.15186,-18.15714 -1.9505,-11.7421 5.17723,-39.57731 17.21355,-67.22235 6.91788,-15.889 23.21266,-48.24553 25.81574,-51.26228 0.91355,-1.05874 4.88161,-7.78933 8.817906,-14.9569 10.007294,-18.22216 18.848974,-32.40818 44.477864,-71.36246 7.60466,-11.55858 14.45788,-21.42043 15.22938,-21.91522 1.85806,-1.19163 4.43314,1.30758 2.61712,2.54 -4.02508,2.73156 -35.83065,51.87168 -54.77062,84.62149 -20.03979,34.65156 -38.78351,75.9299 -41.18805,90.70639 -2.69219,16.54416 -2.72593,19.03954 -0.36399,26.92455 4.43613,14.80946 14.04835,17.88145 36.074523,11.52913 20.536967,-5.92283 38.677467,-14.67429 74.294997,-35.8419 33.87759,-20.13357 75.38278,-53.0137 132.07999,-104.63288 16.04186,-14.6051 29.64344,-23.9355 34.89243,-23.9355 1.97207,0 6.35803,1.16868 9.74658,2.59708 6.09074,2.56747 16.38853,3.31336 46.80098,3.38991 12.35877,0.0311 33.7369,-3.42319 45.085,-7.28486 32.54741,-11.07569 35.55029,-12.59403 60.32499,-30.50243 49.70436,-35.9288 89.94875,-91.71121 101.71864,-140.99136 2.6829,-11.23326 2.97486,-11.88646 2.33305,-5.22001 -2.43844,25.32815 -16.49754,60.48673 -35.29513,88.265 -21.05806,31.11872 -37.23615,47.26105 -68.08717,67.93662 -32.77044,21.96193 -70.16926,35.32491 -108.61938,38.81082 -18.4198,1.66994 -31.4191,1.03407 -53.58026,-2.62091 -17.97971,-2.96534 -16.34903,-2.88316 -18.40152,-0.92742 -0.92302,0.8795 -6.73189,6.06354 -12.9086,11.52006 -6.1767,5.45653 -13.0337,12.02356 -15.23777,14.5934 -2.20406,2.56983 -4.86565,5.19584 -5.91462,5.83555 -1.04897,0.63971 -5.05047,4.16579 -8.89222,7.83575 -3.84175,3.66994 -13.2715,12.27503 -20.955,19.12241 -7.6835,6.84738 -14.25575,12.83631 -14.605,13.30874 -0.34925,0.47242 -4.064,3.40079 -8.255,6.50746 -4.191,3.10668 -9.906,7.71522 -12.7,10.24121 -11.80527,10.67285 -16.11278,14.21139 -32.79024,26.93656 -60.65156,46.27809 -111.2399,70.87935 -130.80631,63.61149 z m 37.580874,-40.0679 C 77.02784,240.40676 72.44995,228.37774 76.51435,206.93403 l 1.58436,-8.35903 0.63814,14.19728 c 0.6856,15.25304 1.83126,18.18663 9.27205,23.74198 8.380859,6.25721 31.55632,2.67768 53.3373,-8.23815 2.93634,-1.47157 4.76404,-2.06654 4.06158,-1.32213 -2.21944,2.35192 -21.5366,11.31407 -29.98116,13.90967 -10.08603,3.10015 -19.551691,4.19092 -24.907316,2.87019 z M 155.60356,165.7639 c -6.35255,-1.942 -10.82357,-8.13894 -10.82357,-15.00171 0,-6.55518 1.60495,-6.18097 3.68356,0.85884 0.93394,3.16308 2.82215,6.35262 4.19603,7.0879 3.37845,1.8081 4.47758,0.35875 2.3162,-3.05419 -5.84746,-9.23341 -7.70564,-20.06364 -5.10648,-29.76257 3.14414,-11.73251 12.78761,-34.28472 22.86946,-53.48249 5.60599,-10.67489 9.86166,-19.94457 9.45703,-20.59928 -0.9862,-1.59569 2.78359,-1.51557 5.84768,0.12433 3.42424,1.8326 3.09207,4.39302 -1.84021,14.18504 -2.3589,4.6831 -4.68767,10.11235 -5.17504,12.065 -0.48737,1.95266 -1.7986,4.56275 -2.91383,5.8002 -1.11524,1.23747 -2.17247,3.52347 -2.34939,5.08 -0.17692,1.55655 -1.49612,7.97358 -2.93154,14.26008 -1.43542,6.2865 -2.61967,12.98224 -2.63167,14.87941 -0.03,4.73093 3.32851,11.29368 7.28564,14.23701 3.57388,2.65827 13.76809,3.46819 19.36255,1.53835 2.17861,-0.75154 2.77645,-0.68331 1.905,0.21741 -0.6985,0.72196 -4.72407,2.46708 -8.9457,3.87803 -7.41393,2.47791 -13.81758,2.34672 -18.21853,-0.37321 -3.75248,-2.31916 -5.24839,-8.23565 -4.39737,-17.39217 0.9548,-10.27333 -0.82625,-11.2946 -3.20718,-1.83904 -6.33606,25.16274 8.5745,33.06231 36.67378,19.42968 14.69055,-7.12725 35.16384,-20.72025 49.49595,-32.86224 1.72752,-1.46353 4.44214,-3.25384 6.0325,-3.97845 1.59035,-0.72462 2.89155,-1.71953 2.89155,-2.2109 0,-0.49139 2.32016,-2.50131 5.15591,-4.46648 7.58708,-5.25785 8.31467,-7.36158 4.34575,-12.56511 -1.81728,-2.38259 -4.99307,-4.92073 -7.0573,-5.64033 -10.07119,-3.51084 -40.50199,-32.15047 -51.08167,-48.07498 -7.43381,-11.18935 -16.62894,-28.30236 -19.31878,-35.95407 -1.09488,-3.1146 -3.15839,-8.08246 -4.58557,-11.03968 -3.96057,-8.20668 -11.05174,-38.66321 -12.64571,-54.31322 -3.8249,-37.55427 5.23788,-81.04597 25.11197,-120.51062 31.96281,-63.46967 89.27576,-110.71859 154.29322,-127.19967 19.26829,-4.88425 35.44822,-6.9115 55.00717,-6.89207 19.71327,0.0196 29.87408,1.15608 47.26436,5.28654 l 11.70436,2.77996 6.07564,-5.81709 c 3.3416,-3.1994 9.50463,-8.84197 13.69563,-12.53905 4.191,-3.69708 9.04875,-8.001 10.795,-9.56427 1.74625,-1.56327 8.3185,-7.23691 14.605,-12.60807 6.2865,-5.37116 13.43025,-11.58544 15.875,-13.80951 2.44475,-2.22407 7.01102,-6.01755 10.14726,-8.42996 3.13625,-2.41241 6.2795,-4.9903 6.985,-5.72866 0.70551,-0.73836 3.4893,-3.02436 6.18621,-5.08 2.69689,-2.05564 8.41189,-6.48949 12.7,-9.853 56.26138,-44.13054 103.73886,-72.33203 135.99496,-80.78071 42.2929,-11.07756 54.22017,13.9356 32.04895,67.21113 -18.85331,45.30282 -54.69157,102.9328 -110.56823,177.8 -14.2335,19.07096 -27.73898,35.69852 -28.43237,35.00511 -0.79642,-0.79642 2.52114,-5.99322 8.564,-13.41511 33.06233,-40.60744 79.97067,-110.71256 102.39791,-153.035 12.70021,-23.96658 23.04742,-50.5397 23.48972,-60.325 0.26504,-5.86325 -1.29312,-15.17231 -3.13231,-18.71372 -1.00296,-1.93121 -3.73662,-4.93202 -6.07483,-6.66849 -3.98625,-2.96038 -5.08262,-3.13492 -17.58629,-2.79959 -19.48425,0.52254 -34.16325,4.68807 -58.23771,16.5264 -33.63253,16.53841 -68.88723,39.35696 -109.40227,70.81046 -15.4313,11.97996 -57.67201,47.82722 -59.12212,50.17355 -1.56348,2.52976 1.7444,4.4546 4.59548,2.67406 3.2899,-2.05457 4.56907,-1.19168 5.26526,3.55178 0.81149,5.52902 1.07527,5.55061 -32.65362,-2.67164 -22.60996,-5.51174 -47.77589,-6.55731 -60.82429,-2.52708 -4.76204,1.47085 -11.42766,2.67427 -14.81251,2.67427 -8.00572,0 -19.18224,2.95047 -23.67495,6.24992 -1.95703,1.43725 -10.39871,5.75473 -18.75927,9.59441 -14.22514,6.53304 -16.01889,7.78333 -27.94,19.475 -7.00644,6.87157 -16.45371,15.59099 -20.99396,19.37648 -4.54025,3.78549 -8.73534,7.64247 -9.32244,8.57107 -0.58708,0.92858 -2.80739,3.21829 -4.93403,5.08823 -3.31957,2.91887 -9.54063,10.18306 -27.28447,31.85942 -1.94923,2.38124 -4.84091,6.87044 -6.42594,9.97601 -2.86243,5.60837 -6.91271,11.03456 -18.23494,24.42946 -7.158,8.46834 -12.08021,17.98609 -13.94276,26.96017 -2.02076,9.73637 -5.0616,18.77193 -8.00158,23.77597 -2.2016,3.74724 -2.59724,6.77916 -2.73446,20.955 -0.0895,9.24743 -0.81001,18.15426 -1.62991,20.14903 -1.83075,4.45417 -1.87298,12.66744 -0.12128,23.58339 1.86729,11.63616 3.51543,16.47363 7.62205,22.37143 1.94544,2.794 5.3379,10.795 7.5388,17.78 2.20088,6.985 5.19427,14.94275 6.65195,17.68386 1.45768,2.74113 3.00025,6.74163 3.42792,8.89 0.55859,2.80604 1.93137,4.41779 4.87433,5.72284 3.62985,1.60965 4.09674,2.39551 4.09674,6.89547 0,2.79332 1.22213,8.69687 2.71584,13.11897 2.43531,7.2097 3.8216,9.02666 13.4209,17.59034 5.8878,5.25256 13.09529,11.02926 16.01667,12.83709 2.92137,1.80782 7.12631,5.00462 9.34432,7.10398 3.68899,3.49166 4.37929,3.69619 8.09862,2.39962 16.8386,-5.86996 66.464,-48.95221 126.99402,-110.24978 41.41518,-41.94032 56.05504,-58.61071 94.55461,-107.6692 33.13911,-42.22786 59.51276,-87.72066 59.53454,-102.69315 l 0.009,-6.11996 4.15308,4.84996 c 2.2842,2.66748 4.17454,6.12427 4.20077,7.68176 0.0513,3.04757 -2.30194,9.76352 -7.10154,20.26706 -3.91802,8.57431 -3.84863,11.17492 0.22186,8.3142 1.20011,-0.84343 18.0311,-24.57244 24.0829,-33.95305 11.7067,-18.14599 31.69675,-56.94862 35.33051,-68.58 8.5567,-27.38922 -1.51414,-39.53797 -26.31009,-31.73869 -9.12834,2.87122 -30.71414,14.88892 -30.30346,16.87119 0.1808,0.87267 -1.04954,1.5325 -2.85749,1.5325 -1.74625,0 -3.175,0.50217 -3.175,1.11592 0,0.61375 -1.85738,1.84711 -4.1275,2.74079 -2.27013,0.89366 -7.10336,3.8205 -10.74051,6.50407 -3.63715,2.68356 -7.06615,4.89373 -7.62,4.91146 -0.55384,0.0178 -4.39051,2.84625 -8.52593,6.28558 -4.13541,3.43932 -9.13603,6.92315 -11.11249,7.74183 -4.52579,1.87463 -4.55688,4.07677 -0.10338,7.32091 6.13759,4.47092 2.30221,4.15603 -5.32568,-0.43726 -4.04918,-2.43831 -8.65839,-4.4333 -10.24263,-4.4333 -3.08587,0 -9.37847,-4.35741 -8.11932,-5.62236 0.41662,-0.41854 2.81355,0.098 5.3265,1.14807 2.51296,1.04997 5.60108,1.53661 6.8625,1.08141 1.26142,-0.45521 10.86599,-7.23505 21.34349,-15.06633 51.75937,-38.68689 80.14734,-54.40139 98.42499,-54.48431 19.44359,-0.0883 22.54799,12.69786 10.61013,43.69976 -6.14678,15.96281 -22.98497,50.32924 -30.59369,62.44114 -9.06079,14.42336 -26.05418,39.84853 -32.55513,48.70836 -17.51044,23.86392 -38.18474,50.9211 -48.52733,63.50926 -6.31287,7.6835 -14.30195,17.44403 -17.75351,21.69008 -21.8508,26.88044 -55.52631,64.15588 -96.28677,106.57991 -15.43553,16.0655 -32.6443,33.97906 -38.24169,39.80791 -14.26923,14.85924 -65.4815,63.22256 -84.47209,79.77285 -32.47682,28.30357 -60.73536,50.50576 -75.92335,59.65148 -3.68961,2.22177 -7.40436,4.60949 -8.255,5.30606 -0.85066,0.69657 -7.26165,4.10477 -14.24664,7.57379 -13.71435,6.81105 -19.74123,7.83272 -29.18143,4.94681 z m 14.55698,-60.29135 c -0.0152,-2.67305 -0.28764,-3.00255 -1.25055,-1.51255 -1.62167,2.50934 -1.62167,6.71725 0,5.715 0.6985,-0.4317 1.26125,-2.3228 1.25055,-4.20245 z m 383.55942,-321.05823 c 0,-0.97037 -0.47977,-1.76431 -1.06614,-1.76431 -1.21176,0 -4.03553,5.68598 -3.97161,7.99725 0.0584,2.1137 5.03775,-4.04693 5.03775,-6.23294 z m 81.96898,-143.79151 c -0.45343,-2.56179 -1.12019,-5.6642 -1.48171,-6.89423 -1.0276,-3.49642 -7.33378,-6.02933 -14.70171,-5.90501 l -6.73057,0.11354 6.16372,1.69333 c 8.11951,2.23064 11.72906,5.84872 13.59509,13.62725 1.23532,5.14936 1.7717,5.96818 2.75666,4.20815 0.67261,-1.20188 0.85195,-4.28125 0.39852,-6.84304 z M 281.93998,117.21982 c 0,-3.13484 20.99203,-22.11207 24.49217,-22.14145 2.1161,-0.0178 -1.87125,5.00971 -11.15247,14.06164 -10.03556,9.78762 -13.3397,11.78893 -13.3397,8.07981 z M 600.91047,-97.97 c 0,-1.74625 0.28827,-2.46062 0.64058,-1.5875 0.35231,0.87313 0.35231,2.30188 0,3.175 -0.35231,0.87313 -0.64058,0.15875 -0.64058,-1.5875 z m 1.27,-10.15999 c 0,-1.74625 0.28827,-2.46063 0.64058,-1.5875 0.35231,0.87312 0.35231,2.30187 0,3.17499 -0.35231,0.87313 -0.64058,0.15875 -0.64058,-1.58749 z m 1.24411,-22.22501 0.0974,-7.62 1.2057,5.38215 c 0.79639,3.55501 0.76332,6.14185 -0.0974,7.62 -0.98904,1.69852 -1.27962,0.40141 -1.2057,-5.38215 z m -1.24411,-18.41499 c 0,-1.74625 0.28827,-2.46063 0.64058,-1.5875 0.35231,0.87312 0.35231,2.30187 0,3.17499 -0.35231,0.87313 -0.64058,0.15875 -0.64058,-1.58749 z m -2.03738,-13.32335 c -0.49153,-3.27769 -0.67832,-6.17481 -0.4151,-6.43803 0.97048,-0.97047 3.09777,9.50282 2.21898,10.92473 -0.50061,0.81002 -1.31237,-1.20899 -1.80388,-4.4867 z m -2.48114,-13.47365 c -0.46543,-2.91065 -0.59827,-5.54006 -0.29521,-5.84313 0.30307,-0.30306 0.95027,1.81199 1.43821,4.70013 0.48795,2.88813 0.62079,5.51754 0.29521,5.84313 -0.32559,0.32557 -0.97278,-1.78949 -1.43821,-4.70013 z m -3.81,-14.605 c -1.22768,-3.89466 -1.93518,-7.37814 -1.57223,-7.7411 0.36295,-0.36295 1.60386,2.58714 2.75756,6.55577 2.73109,9.39457 1.72085,10.4048 -1.18533,1.18533 z m -7.112,-26.34617 c 0,-2.03268 4.02396,-5.91183 6.1325,-5.91183 2.01562,0 1.8698,1.42903 -0.48951,4.79741 -2.18042,3.113 -5.64299,3.7968 -5.64299,1.11442 z M 546.09526,-271.33 c -1.63793,-1.74349 -2.39551,-3.16999 -1.68351,-3.16999 1.62219,0 6.12686,4.87468 5.28377,5.71778 -0.34222,0.34221 -1.96234,-0.80429 -3.60026,-2.54779 z m 63.49978,-46.9678 c 3.42058,-11.96114 2.81594,-19.85971 -1.89679,-24.77875 -3.84533,-4.01366 -3.56123,-4.69368 1.03269,-2.47174 2.54206,1.22953 2.51764,1.11875 -0.29206,-1.32496 -1.68634,-1.46668 -3.40084,-2.1602 -3.81,-1.54116 -0.40917,0.61902 -4.45868,1.07705 -8.99893,1.01783 -7.56864,-0.0987 -12.3001,1.00328 -20.00058,4.65833 -1.57057,0.74548 -3.18969,1.02131 -3.59805,0.61296 -0.80354,-0.80353 7.93398,-4.92745 16.86272,-7.95884 3.05507,-1.03722 8.72191,-1.88586 12.59299,-1.88586 6.08585,0 7.54067,0.50237 10.75061,3.71231 2.21521,2.2152 3.71231,5.05513 3.71231,7.04207 0,4.54427 -6.0975,28.87112 -7.6175,30.39112 -0.67368,0.67368 -0.10553,-2.68931 1.26259,-7.47331 z m 97.4774,-101.40356 c -0.52387,-1.27951 -0.9525,-4.29824 -0.9525,-6.70829 0,-9.69812 -6.82151,-18.2015 -16.74505,-20.87363 -4.7495,-1.27891 -17.3003,-0.95121 -25.16494,0.65706 -1.74625,0.35709 -0.60325,-0.27871 2.54,-1.41291 3.59681,-1.29786 10.18672,-2.1166 17.78,-2.20902 10.58443,-0.12882 12.55229,0.20075 16.03592,2.68565 6.09231,4.34569 8.2697,10.17588 7.83238,20.97203 -0.22858,5.64257 -0.7426,8.31353 -1.32581,6.88911 z"
+         id="path8386"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#645b4c;stroke-width:1.26999998"
+         d="m 56.515,283.80267 c -7.77854,-1.89814 -12.47695,-4.86428 -14.40378,-9.0932 -5.92893,-13.01259 -0.4352,-42.06794 14.39537,-76.13447 6.68153,-15.3478 21.84892,-45.53164 25.52751,-50.80099 1.46251,-2.09495 6.02224,-9.80976 10.132733,-17.14401 4.110495,-7.33425 8.343147,-14.76375 9.405897,-16.51 1.06274,-1.74625 3.07526,-5.11627 4.47226,-7.48893 9.2348,-15.68439 43.92694,-67.44107 45.20527,-67.44107 0.63234,0 1.14973,0.32566 1.14973,0.72367 0,0.93641 -6.30167,10.12347 -15.18835,22.14272 -8.57632,11.59951 -35.92165,55.93174 -47.427539,76.8895 C 63.38004,187.04039 49.53,224.3991 49.53,247.52619 v 10.04453 l 4.9644,2.39853 c 2.73041,1.3192 5.62684,1.98913 6.43649,1.48875 0.80965,-0.5004 4.34284,-0.38804 7.85153,0.24969 11.20957,2.0374 31.096119,-3.94823 59.48757,-17.90512 8.26402,-4.06249 20.3822,-10.86321 40.98819,-23.00257 4.76338,-2.80619 15.65599,-10.17572 33.9418,-22.96376 17.69604,-12.37558 64.93603,-52.52708 93.79831,-79.72364 8.63131,-8.13318 16.20369,-14.9781 16.8275,-15.21093 3.09123,-1.15379 0.4789,2.69061 -4.75812,7.00222 -3.24076,2.66811 -11.3993,10.28036 -18.13007,16.91611 -15.75077,15.52839 -47.23286,43.98815 -61.06762,55.20501 -5.93725,4.81377 -14.53339,12.03156 -19.10254,16.03954 -16.2508,14.25491 -46.00348,36.22826 -75.51245,55.76846 -19.70641,13.04916 -45.802048,25.27213 -60.96,28.55316 -4.191,0.90716 -8.47725,1.94563 -9.525,2.30771 -1.04775,0.36207 -4.76249,-0.039 -8.25499,-0.89121 z m 32.05546,-41.8821 c -9.66364,-3.60178 -12.44101,-8.88323 -12.20942,-23.2175 l 0.16104,-9.96807 1.0402,8.01252 c 1.22625,9.44556 4.33988,16.28709 8.941,19.64592 4.814372,3.51451 17.87903,4.80526 26.41462,2.60971 9.80204,-2.52132 11.09015,-1.34373 1.47978,1.35279 -11.19771,3.14192 -20.141606,3.68375 -25.82722,1.56463 z m 67.93837,-88.9708 -4.74384,-4.35022 0.0715,-11.84228 c 0.0596,-9.86286 0.76698,-13.93566 4.23253,-24.3665 C 160.86518,97.95485 183.70307,51.89 186.06386,51.89 c 0.87182,0 2.25317,0.80495 3.06968,1.7888 1.43308,1.72675 0.2241,5.16344 -5.84884,16.6262 -12.43926,23.47923 -20.72737,46.53718 -20.71996,57.6441 0.0145,21.7821 17.33749,22.77502 51.72664,2.96487 16.00371,-9.21907 17.53215,-10.26261 34.36312,-23.46147 4.2309,-3.31787 8.30746,-6.0325 9.05901,-6.0325 0.75156,0 1.36647,-0.50283 1.36647,-1.11741 0,-0.61458 3.14325,-3.49059 6.985,-6.39114 3.84175,-2.90056 6.985,-6.00036 6.985,-6.88846 0,-2.24786 -7.36236,-9.00525 -12.7,-11.65644 C 249.35151,69.90364 225.58009,47.9171 214.37111,32.84 205.47963,20.88016 195.07367,2.53981 191.64587,-7.21294 c -1.44315,-4.10601 -3.36878,-8.91821 -4.27919,-10.69377 -2.81421,-5.48858 -5.45822,-14.29642 -9.01348,-30.0262 -16.22445,-71.78285 11.28486,-155.74508 69.4351,-211.92546 36.84163,-35.59362 75.20639,-55.7376 123.05167,-64.61008 11.50769,-2.13399 9.722,-2.0332 35.77162,-2.01914 20.97558,0.0114 38.56122,1.90079 51.22337,5.50366 9.35672,2.66235 5.16603,2.39786 -9.08471,-0.57336 -19.36642,-4.0378 -46.11709,-4.65745 -62.03528,-1.43694 -6.985,1.41317 -15.67186,2.95094 -19.30413,3.41728 -4.25179,0.54587 -12.39434,3.68973 -22.85999,8.82629 -8.94074,4.38813 -18.82763,9.15522 -21.97088,10.59353 -3.14325,1.43831 -8.28674,5.27948 -11.42999,8.53595 -3.14325,3.25646 -13.716,12.38048 -23.495,20.27561 -27.1943,21.95547 -45.08367,42.52323 -60.77177,69.87058 -4.80844,8.382 -10.48036,17.43689 -12.60428,20.12199 -7.39945,9.35453 -10.18676,14.20945 -11.77985,20.51801 -3.20356,12.68605 -7.27059,25.49809 -9.47414,29.84564 -3.03971,5.99725 -4.90657,18.88949 -3.39289,23.43058 0.79704,2.3911 0.67967,5.73191 -0.35398,10.0755 -1.71872,7.22242 -1.19107,23.74077 1.14041,35.70083 0.81619,4.18691 3.68833,12.18791 6.38253,17.78 2.69421,5.59209 6.33265,14.76463 8.08543,20.3834 1.75279,5.61878 4.66213,12.99053 6.46522,16.38165 1.80308,3.39113 3.27832,7.15361 3.27832,8.36104 0,2.70212 2.09209,4.43743 6.6675,5.53043 3.02197,0.72192 3.4925,1.46062 3.4925,5.48309 0,2.55683 1.21543,8.24703 2.70095,12.64489 2.40713,7.12628 3.89227,9.08278 13.6525,17.98569 6.02335,5.49427 13.23755,11.43688 16.03155,13.2058 2.794,1.76892 6.65603,5.00277 8.58229,7.18633 1.92627,2.18356 4.21682,3.97012 5.09011,3.97012 2.52156,0 13.15232,-6.01594 22.99292,-13.01164 39.37291,-27.99037 138.17254,-125.44928 179.23466,-176.80245 34.33508,-42.94019 50.16062,-64.74756 65.01222,-89.5859 11.91278,-19.92337 19.44278,-37.05521 19.44278,-44.23519 v -3.19757 l 3.15316,2.96225 c 1.73425,1.62924 3.49089,4.02628 3.90364,5.32676 1.09955,3.46437 -2.70243,13.90177 -10.41814,28.6004 -3.73926,7.12341 -6.79866,13.26704 -6.79866,13.6525 0,0.38547 0.84563,0.70085 1.87919,0.70085 1.03355,0 2.99203,-2.18136 4.35218,-4.84747 1.36014,-2.66609 3.42107,-5.6343 4.57984,-6.596 3.76722,-3.12651 27.99263,-39.59397 36.69043,-55.23153 17.89929,-32.18074 24.59922,-48.51962 25.74612,-62.78619 0.58893,-7.32585 0.30375,-8.87143 -2.3578,-12.77881 -1.92929,-2.83233 -4.91219,-5.10168 -8.22177,-6.25499 -4.59703,-1.60197 -6.19789,-1.56382 -13.92859,0.33191 -8.86557,2.17403 -25.64435,9.76843 -33.7803,15.28963 -2.45829,1.66824 -7.25842,4.638 -10.66696,6.59949 -3.40854,1.96148 -11.82272,7.60117 -18.69817,12.53264 -6.87545,4.93148 -15.2855,10.82795 -18.68901,13.10327 -4.6881,3.1341 -5.98314,4.67341 -5.34229,6.35 0.79105,2.0696 0.64419,2.07953 -2.26599,0.15315 -6.13762,-4.0628 -5.61111,-4.62391 24.98343,-26.62607 49.42004,-35.54061 83.04148,-48.02987 95.18267,-35.3572 3.13086,3.26793 3.71238,5.01726 4.13109,12.42724 0.62376,11.03917 -1.74015,20.22607 -9.88757,38.42596 -19.82715,44.29031 -55.63151,97.24722 -113.67331,168.12997 -17.87309,21.82727 -50.43092,59.13052 -69.25318,79.34711 -20.34465,21.85173 -89.48266,93.165 -99.22168,102.34344 -48.20272,45.42815 -79.08032,72.02763 -105.63206,90.99664 -29.11371,20.79931 -49.17669,30.2078 -64.41613,30.2078 -6.91545,0 -8.07021,-0.40606 -12.37127,-4.35023 z m 388.29288,-355.89508 c -0.0366,-1.38694 -4.9863,4.92303 -5.02351,6.40409 -0.0155,0.61807 1.11476,-0.32933 2.51176,-2.10532 1.397,-1.77599 2.52728,-3.71044 2.51175,-4.29877 z M 171.05259,135.95801 c -2.59514,-2.70876 -3.4126,-4.83827 -3.4126,-8.89 0,-2.9304 0.51036,-5.32801 1.13412,-5.32801 1.60538,0 2.61194,3.14221 1.8932,5.91001 -0.7373,2.83925 3.12903,8.2742 4.14628,5.8285 0.49026,-1.17865 1.61109,-0.92213 4.28249,0.98008 1.99016,1.41711 4.08431,2.49647 4.65368,2.39859 0.56938,-0.0979 1.89248,-0.1016 2.94023,-0.008 4.14056,0.36916 -1.88874,2.632 -7.0649,2.65151 -4.01053,0.0151 -5.92004,-0.77399 -8.5725,-3.54255 z m 111.49554,-18.07145 c -1.07287,-1.73594 1.50907,-4.65067 12.66333,-14.29554 5.34538,-4.62204 9.03375,-7.01031 9.35199,-6.05558 1.00401,3.01203 -20.46558,22.85865 -22.01532,20.35112 z m 73.68684,-9.69823 c -14.03714,-1.73566 -38.73352,-5.95046 -38.72632,-6.60922 0.0127,-1.14368 7.74631,-6.10132 11.04866,-7.08258 1.55934,-0.46333 6.34747,0.25564 10.79499,1.62098 5.02711,1.54325 16.0587,3.04493 29.73188,4.04724 20.76553,1.52222 22.47202,1.46573 38.1,-1.26127 19.43575,-3.39142 46.6782,-12.17211 60.81078,-19.60031 34.85387,-18.31947 76.57865,-57.59576 101.30281,-95.35817 8.1493,-12.4468 22.52218,-39.70596 22.52218,-42.71486 0,-0.83791 0.35075,-1.17273 0.77944,-0.74403 1.334,1.334 -6.84806,22.14903 -14.77804,37.59516 -14.08626,27.4374 -38.45483,58.15807 -59.67004,75.22405 -21.50139,17.29618 -48.56668,32.55342 -73.66769,41.52793 -30.15837,10.78269 -65.6338,16.15135 -88.24865,13.35508 z M 594.0032,-206.23749 c 19.60043,-25.4159 32.41535,-41.5925 32.94913,-41.5925 0.96489,0 -3.44179,6.73883 -7.19238,10.99879 -1.74625,1.98341 -4.25917,5.32071 -5.58428,7.41621 -1.32512,2.0955 -3.17166,4.66725 -4.10346,5.715 -3.71128,4.17315 -7.63705,9.05922 -11.19338,13.93145 -2.05486,2.8152 -4.26796,5.11855 -4.918,5.11855 -0.70658,0 -0.68953,-0.63843 0.0424,-1.5875 z m -5.99325,-10.99686 c 0,-2.49007 3.37905,-6.04992 4.59165,-4.83733 1.17917,1.17917 -0.73246,5.70106 -2.735,6.4695 -1.21569,0.46651 -1.85665,-0.0969 -1.85665,-1.63217 z m 40.17797,-33.45314 c 0.52684,-0.87313 4.5655,-6.58964 8.9748,-12.70336 60.83118,-84.34544 93.03483,-145.65088 95.2713,-181.36603 l 0.6808,-10.87189 -9.60912,-4.68561 c -8.02299,-3.91218 -10.95114,-4.68561 -17.73929,-4.68561 -29.42602,0 -92.59447,30.67542 -146.34074,71.06495 -25.26969,18.98982 -66.22789,53.00221 -82.09973,68.17706 -4.64695,4.44289 -9.5047,8.06924 -10.79499,8.05854 -1.87055,-0.0155 -1.98267,-0.24944 -0.55322,-1.15431 2.19926,-1.39219 12.8684,-10.75771 18.32144,-16.08281 7.22771,-7.05815 26.50668,-23.87579 46.56079,-40.61643 98.12667,-81.91353 169.38004,-120.4588 199.77663,-108.07132 8.89433,3.6247 11.72093,9.43164 11.53837,23.70432 -0.22949,17.94036 -6.52676,37.56667 -21.84078,68.06975 -6.4559,12.85909 -17.58416,33.22756 -21.7885,39.88025 -0.66216,1.04775 -3.90068,6.19125 -7.19672,11.43 -19.72291,31.34769 -53.87768,81.26087 -59.80857,87.40321 -0.89124,0.92301 -1.62044,2.20889 -1.62044,2.8575 0,0.6486 -0.60523,1.17929 -1.34496,1.17929 -0.74015,0 -0.9142,-0.71386 -0.38707,-1.5875 z m -149.10691,-65.96063 c 0.78495,-2.35486 3.51895,-2.74543 3.51895,-0.5027 0,1.10595 -0.90487,2.01083 -2.01083,2.01083 -1.10596,0 -1.78461,-0.67867 -1.50812,-1.50813 z m 132.803,-21.56733 c -0.35799,-7.46971 -0.61539,-8.04696 -4.63169,-10.38688 -2.68891,-1.5666 -4.55686,-2.00101 -5.08,-1.18145 -0.45505,0.71292 -4.3657,1.54683 -8.69034,1.85314 -8.95753,0.63448 -9.45103,-0.51569 -1.12124,-2.61314 7.06752,-1.77963 14.11563,-1.8279 17.30956,-0.11862 3.97717,2.12852 6.18482,7.75738 5.38403,13.72767 -1.09954,8.19769 -2.74797,7.53174 -3.17032,-1.28077 z m 95.6062,-87.03728 c -0.29165,-11.83238 -10.99624,-23.12143 -22.00776,-23.20937 -1.92088,-0.0153 -3.4925,-0.679 -3.4925,-1.47479 0,-1.99351 11.40341,-0.55882 16.49788,2.07563 2.16394,1.11902 5.02418,3.82653 6.35609,6.01668 2.47826,4.07515 4.61978,15.97426 3.41658,18.98371 -0.36851,0.92174 -0.71515,-0.15461 -0.77029,-2.39186 z m -32.7786,-24.11736 c 1.23568,-0.322 2.95018,-0.30082 3.81,0.047 0.85983,0.34789 -0.15116,0.61134 -2.24666,0.58546 -2.0955,-0.0259 -2.79901,-0.31051 -1.56334,-0.63251 z"
+         id="path8384"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#565048;stroke-width:1.26999998"
+         d="m 60.0075,283.42455 c 1.22237,-0.31943 3.22262,-0.31943 4.44499,0 1.22238,0.31944 0.22225,0.58081 -2.22249,0.58081 -2.44475,0 -3.44488,-0.26137 -2.2225,-0.58081 z m 9.60437,-1.2621 c 0.91678,-0.36686 2.01216,-0.32175 2.43417,0.1002 0.42201,0.42201 -0.32809,0.72218 -1.66688,0.66703 -1.47948,-0.061 -1.78041,-0.36187 -0.76729,-0.76728 z m -3.21144,-1.64903 c -13.89906,-1.58287 -21.15533,-8.80956 -22.80146,-22.70853 -0.87746,-7.40881 1.66683,-27.28407 4.15242,-32.43751 0.97823,-2.02819 1.77861,-4.77655 1.77861,-6.10746 0,-1.33089 0.45342,-2.13959 1.0076,-1.79709 0.55418,0.34251 -0.0831,7.23486 -1.41616,15.31634 -2.70619,16.40578 -2.2177,25.75864 1.6801,32.16808 4.03734,6.63887 8.13139,8.50245 18.30034,8.33022 10.29361,-0.17436 26.776667,-5.18217 26.793023,-8.14016 0.0061,-1.10999 1.351175,-1.79231 3.533125,-1.79231 1.937772,0 3.933562,-0.63033 4.435092,-1.40071 0.50153,-0.77039 4.05512,-2.76909 7.89687,-4.44156 25.75486,-11.21213 52.57734,-27.62948 85.08006,-52.07533 8.72579,-6.56283 16.93327,-12.72372 18.23883,-13.69089 2.29067,-1.69696 2.29815,-1.64314 0.21381,1.53797 -1.18797,1.81306 -3.23689,3.63829 -4.55319,4.05606 -1.31629,0.41778 -3.51245,2.18241 -4.88033,3.92139 -1.36789,1.739 -4.66892,4.63522 -7.33563,6.43605 -2.66671,1.80083 -5.13431,3.66674 -5.48356,4.14646 -1.06719,1.46588 -26.61106,20.50276 -38.73499,28.86772 -26.38556,18.20483 -47.56252,29.9563 -66.03021,36.64133 -6.71044,2.42907 -12.60108,4.33832 -13.09029,4.24275 -0.48922,-0.0956 -4.44205,-0.57835 -8.78406,-1.07282 z m 19.06872,-40.61201 c -5.01236,-3.05622 -7.8298,-8.29365 -8.72651,-16.22203 l -0.68476,-6.05438 2.4169,6.67046 c 3.9156,10.80673 9.30683,15.09441 21.171108,16.83751 l 6.399102,0.94015 -8.254998,0.1537 C 91.637146,242.34137 88.49962,241.7492 85.46915,239.90141 Z M 50.8,213.3299 c 0,-0.6985 0.5715,-1.6232 1.27,-2.0549 0.6985,-0.4317 1.27,-0.2134 1.27,0.4851 0,0.6985 -0.5715,1.6232 -1.27,2.0549 -0.6985,0.4317 -1.27,0.2134 -1.27,-0.4851 z m 6.35,-15.31494 c 0,-0.65728 0.56495,-1.54422 1.25545,-1.97097 0.71991,-0.44493 0.93282,0.0649 0.49912,1.19505 -0.84428,2.20017 -1.75457,2.60272 -1.75457,0.77592 z m 2.57889,-4.51996 c 0,-0.6985 0.554,-2.12725 1.23111,-3.175 0.6771,-1.04775 1.2311,-1.3335 1.2311,-0.635 0,0.6985 -0.554,2.12725 -1.2311,3.175 -0.67711,1.04775 -1.23111,1.3335 -1.23111,0.635 z m 158.71109,-4.1395 c 0,-0.53048 1.28588,-1.75444 2.8575,-2.71991 2.84736,-1.74916 2.8489,-1.74573 0.43817,0.96452 -2.58837,2.90992 -3.29567,3.28665 -3.29567,1.75539 z m 8.89,-7.12447 c 0,-0.29015 5.00063,-4.6482 11.1125,-9.68458 10.91989,-8.99833 10.97821,-9.0274 3.36501,-1.67731 -7.10572,6.86014 -14.47751,12.64549 -14.47751,11.36189 z M 68.68196,173.81 c 1.85142,-3.88751 2.43803,-4.60799 2.43803,-2.9944 0,0.68141 -0.88877,2.34829 -1.97506,3.70417 -1.8623,2.32449 -1.88873,2.28396 -0.46297,-0.70977 z m 185.95302,-15.24 c 1.26427,-1.397 2.58442,-2.54 2.93367,-2.54 0.34925,0 -0.3994,1.143 -1.66367,2.54 -1.26427,1.397 -2.58442,2.54 -2.93367,2.54 -0.34925,0 0.3994,-1.143 1.66367,-2.54 z m -97.45245,-6.24858 c -4.13012,-3.69299 -4.14735,-3.75835 -4.10185,-15.5575 0.0384,-9.93058 0.70857,-13.80224 4.14001,-23.91392 4.11487,-12.12555 27.73987,-60.96 29.49108,-60.96 0.51546,0 1.54272,0.72961 2.2828,1.62134 1.25294,1.50971 -1.33221,8.20009 -6.97977,18.06366 -4.04943,7.07243 -11.95388,25.23864 -16.02539,36.83 -3.68073,10.47885 -3.8923,28.3567 -0.38154,32.24109 3.6414,4.02891 9.99122,5.31455 18.35007,3.71532 13.79603,-2.63948 39.5619,-16.95695 62.42204,-34.68641 3.14325,-2.43779 7.71525,-5.82518 10.16,-7.52755 2.44475,-1.70236 7.55527,-5.89367 11.35671,-9.31401 l 6.91171,-6.21879 -5.51017,-4.97982 c -3.03058,-2.73891 -6.9981,-5.60816 -8.8167,-6.37613 C 250.75446,71.1511 220.74242,42.59843 211.22257,28.395 203.38165,16.69649 194.28921,0.14078 191.76059,-7.04182 c -1.25338,-3.56025 -3.68557,-9.61643 -5.40488,-13.45818 -1.71931,-3.84175 -4.98413,-14.70025 -7.25515,-24.13 -7.33933,-30.47431 -6.8285,-66.59552 1.40445,-99.30822 14.91803,-59.27509 54.92591,-114.36054 107.7799,-148.39835 29.38607,-18.92454 66.30394,-31.38338 99.06506,-33.43191 12.28454,-0.76815 8.4697,0.25378 -12.75704,3.41738 -13.98342,2.08405 -16.78838,2.88838 -27.24796,7.81335 -3.4925,1.64446 -11.39615,5.30921 -17.56366,8.14388 -7.01895,3.22599 -13.82708,7.54243 -18.20003,11.53907 -3.8425,3.51183 -9.94061,8.37212 -13.55133,10.80065 -3.61074,2.42853 -9.01868,6.7147 -12.01767,9.52483 -2.99898,2.81013 -5.92828,5.10933 -6.50958,5.10933 -1.29344,0 -20.91281,20.021 -32.35763,33.02 -12.10427,13.74801 -21.18053,26.78309 -28.73953,41.275 -3.6434,6.985 -7.37687,13.56329 -8.2966,14.61842 -8.40193,9.63878 -11.48859,15.83974 -16.32028,32.78675 -2.36626,8.2996 -5.29383,17.00757 -6.50567,19.35105 -2.12784,4.11477 -2.64267,11.72307 -3.34816,49.47956 -0.16785,8.98336 3.45198,26.89835 6.77902,33.55017 1.41784,2.83473 5.04456,12.01204 8.05936,20.39404 8.23787,22.90353 12.05143,31.38812 15.2328,33.89058 1.58319,1.24533 3.25487,3.84492 3.71483,5.77683 0.45997,1.93193 2.86778,7.67226 5.35068,12.7563 3.70785,7.59224 6.47687,10.99964 15.49893,19.07205 6.04149,5.40559 12.98478,11.19497 15.42953,12.8653 2.44475,1.67033 6.24627,4.71852 8.44782,6.77375 3.30104,3.08163 4.68375,3.58722 7.88665,2.88375 4.76419,-1.04639 22.89555,-12.31138 34.28696,-21.30243 24.18516,-19.08897 44.04995,-35.76098 55.42356,-46.51558 19.94014,-18.8549 63.867,-64.57968 86.62951,-90.17523 18.26465,-20.53787 53.46906,-62.75906 58.78548,-70.50233 1.04775,-1.52603 7.08725,-9.89773 13.42112,-18.60379 14.4444,-19.85421 25.95678,-37.50985 27.23554,-41.76908 0.53999,-1.79856 2.95586,-7.84211 5.36859,-13.43011 2.41274,-5.588 5.10258,-13.16038 5.97744,-16.8275 0.87486,-3.66713 1.96227,-6.64955 2.41648,-6.6276 0.4542,0.022 1.89828,1.51122 3.20906,3.30949 2.10633,2.88969 2.20187,3.8749 0.82233,8.47937 -0.85848,2.86539 -4.93132,11.75124 -9.05073,19.74634 -4.11941,7.99511 -7.48983,15.30569 -7.48983,16.24573 0,2.71875 2.22433,1.98991 4.70321,-1.54108 1.25498,-1.78764 4.32207,-5.93102 6.81575,-9.2075 16.34849,-21.48055 36.64488,-54.74452 49.35428,-80.88725 9.82492,-20.20945 11.97334,-27.14394 12.07109,-38.96204 0.0753,-9.10029 -0.16762,-10.01166 -3.63429,-13.63669 -4.55347,-4.76146 -10.14528,-5.94155 -19.37275,-4.08841 -8.70611,1.74844 -28.7323,11.11794 -39.87041,18.65389 -4.59147,3.10653 -8.71313,5.64825 -9.15925,5.64825 -1.06584,0 -20.62013,13.69685 -28.91511,20.25366 -3.58084,2.83048 -6.98996,5.14634 -7.57585,5.14634 -0.58588,0 -2.00005,0.93481 -3.1426,2.07735 -1.60521,1.6052 -2.41962,1.73396 -3.58322,0.56643 -1.74015,-1.74601 -2.11262,-1.41863 19.44915,-17.09501 45.05467,-32.75678 77.67243,-48.89776 93.00151,-46.02201 19.33064,3.62646 17.47338,25.73671 -6.01401,71.59549 -14.28373,27.88879 -28.52664,50.24786 -55.61255,87.30275 -15.8116,21.63107 -18.87496,25.57201 -43.30152,55.7062 -38.23214,47.16566 -65.90512,77.87675 -126.03367,139.8704 C 279.22158,97.1389 203.42669,156.03 168.33508,156.03 c -5.94724,0 -7.63131,-0.56 -11.15255,-3.70858 z m 387.61918,-354.8896 c -0.0155,-0.79575 -1.14582,-0.0181 -2.51175,1.72819 -3.2465,4.15041 -3.20812,6.61841 0.0423,2.71681 1.37376,-1.64902 2.48503,-3.64927 2.4695,-4.445 z M 265.42998,149.2005 c 0,-0.26371 1.00012,-1.26384 2.2225,-2.2225 2.0143,-1.57971 2.05922,-1.53479 0.4795,0.4795 -1.65909,2.1155 -2.702,2.78826 -2.702,1.743 z m -94.5021,-13.58795 c -3.26159,-3.87617 -4.42649,-10.47896 -2.10218,-11.91545 0.65213,-0.40305 1.13661,0.9937 1.07661,3.10386 -0.0599,2.11017 0.92087,4.97476 2.17968,6.36574 2.60845,2.88229 9.94866,5.26218 13.11524,4.25229 1.82194,-0.58106 1.85396,-0.48163 0.22276,0.69176 -1.04775,0.7537 -3.99733,1.37911 -6.55461,1.38981 -3.6461,0.0152 -5.35921,-0.82388 -7.9375,-3.88801 z m 112.50648,-19.41437 c 0.55807,-2.35372 18.85495,-18.98217 19.82232,-18.0148 0.86091,0.86091 -17.4595,19.74662 -19.15554,19.74662 -0.59257,0 -0.89262,-0.77932 -0.66678,-1.73182 z m 68.99061,-8.51169 c -13.30989,-1.52666 -32.29777,-5.09791 -32.98079,-6.20305 -0.33067,-0.53503 1.36844,-2.0335 3.77579,-3.32994 3.94629,-2.12522 4.97367,-2.18942 10.44099,-0.65246 12.25505,3.44509 20.57907,4.24698 45.43401,4.37687 29.25301,0.1529 36.50226,-1.01264 63.39899,-10.1933 18.24858,-6.22879 30.35882,-12.31803 45.821,-23.0396 40.92322,-28.37642 67.9111,-57.85337 91.22243,-99.63583 9.711,-17.40569 10.15827,-15.74139 0.95345,3.54785 -5.08133,10.64822 -13.36404,24.35771 -22.96088,38.00467 -12.62907,17.95888 -35.75186,40.67324 -54.46514,53.50308 -23.51215,16.11996 -32.03474,20.40256 -66.81985,33.57705 -9.86714,3.73706 -33.72672,8.6931 -48.67532,10.11066 -15.15389,1.43704 -22.15531,1.4239 -35.14468,-0.066 z M 144.77999,48.15496 c 0,-0.65728 0.56496,-1.54422 1.25546,-1.97097 0.71991,-0.44493 0.93281,0.0649 0.49912,1.19505 -0.84428,2.20017 -1.75458,2.60272 -1.75458,0.77592 z m 443.22996,-265.25095 c 0,-1.33562 2.75838,-4.064 4.10868,-4.064 0.58338,0 0.44897,1.143 -0.29868,2.54 -1.34234,2.50817 -3.81,3.49524 -3.81,1.524 z M 444.26184,-323.62754 c 0.91677,-0.36687 2.01216,-0.32176 2.43417,0.1002 0.42201,0.42201 -0.32809,0.72217 -1.66687,0.66703 -1.47949,-0.061 -1.78042,-0.36187 -0.7673,-0.76728 z m -7.62,-1.27 c 0.91678,-0.36687 2.01216,-0.32176 2.43417,0.1002 0.42201,0.42201 -0.32809,0.72217 -1.66687,0.66703 -1.47949,-0.061 -1.78042,-0.36187 -0.7673,-0.76728 z m -12.12021,-1.28255 c 1.23568,-0.322 2.95018,-0.30082 3.81,0.0471 0.85983,0.34789 -0.15116,0.61134 -2.24666,0.58545 -2.0955,-0.0259 -2.79901,-0.31051 -1.56334,-0.63251 z m 188.40264,-11.83246 c 0.75008,-4.69079 0.44354,-5.44227 -3.88542,-9.525 -2.58487,-2.43784 -4.33574,-4.43244 -3.89083,-4.43244 1.61704,0 6.39476,3.00416 7.90405,4.96994 1.87008,2.43573 2.15365,14.08006 0.34287,14.08006 -0.78843,0 -0.97031,-1.96799 -0.47067,-5.09256 z m -115.08431,-8.87744 c 0,-0.6985 0.60523,-1.27 1.34496,-1.27 0.73972,0 0.99174,0.5715 0.56004,1.27 -0.4317,0.6985 -1.03693,1.27 -1.34495,1.27 -0.30803,0 -0.56005,-0.5715 -0.56005,-1.27 z m 94.61499,-3.28982 c 7.02616,-2.20602 10.53293,-2.29018 8.44571,-0.20268 -0.87301,0.87312 -4.27344,1.63478 -7.5565,1.69258 l -5.96921,0.10503 5.08,-1.59498 z m -60.32499,-25.17613 c 0,-1.11945 8.23058,-8.35219 9.51129,-8.35817 0.69096,-0.004 2.69121,-1.4451 4.445,-3.20414 1.75378,-1.75904 7.76071,-6.71965 13.34871,-11.02356 5.588,-4.30392 10.44574,-8.22249 10.79499,-8.70795 1.59793,-2.22113 30.52398,-23.11904 48.895,-35.32469 32.21153,-21.40124 63.00838,-36.90241 82.4025,-41.47617 7.78238,-1.83534 27.60198,-2.50065 23.2367,-0.78002 -1.97918,0.78012 -1.62054,1.35962 2.49921,4.03824 10.23895,6.65728 13.42662,16.57549 10.58296,32.92828 -0.91521,5.26302 -1.71293,11.4265 -1.7727,13.69663 -0.0598,2.27012 -0.57103,4.1275 -1.13613,4.1275 -0.5651,0 -0.62413,0.71437 -0.13118,1.5875 0.66461,1.1772 0.50195,1.22493 -0.62939,0.18467 -1.18435,-1.08902 -1.18873,-3.3375 -0.0196,-10.05115 1.95062,-11.20127 0.8206,-24.87111 -2.496,-30.1941 -3.23227,-5.18763 -6.42728,-7.11265 -13.3887,-8.06682 -4.62969,-0.63457 -6.91406,-0.093 -13.51009,3.2031 -4.3661,2.18174 -8.66964,3.9668 -9.56348,3.9668 -0.89384,0 -9.23219,2.5042 -18.52966,5.56489 -24.69989,8.13111 -53.53591,22.88565 -81.67448,41.79042 -14.85981,9.9835 -42.55103,30.57633 -51.43499,38.25009 -6.86136,5.9267 -11.43,9.06385 -11.43,7.84865 z m 172.23826,-61.38655 c -2.62119,-5.03842 -7.44718,-9.1672 -13.05037,-11.16502 -2.68559,-0.95754 -3.89435,-1.76608 -2.68611,-1.79674 7.19206,-0.1825 16.07388,6.59776 17.81114,13.59676 0.95645,3.85333 0.12854,3.59992 -2.07466,-0.635 z"
+         id="path8382"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#4f483b;stroke-width:1.26999998"
+         d="m 59.28843,277.38001 c -7.86183,-2.80388 -11.75259,-7.25382 -13.6333,-15.59263 -1.07149,-4.75083 0.65502,-5.76898 1.83182,-1.08026 1.25757,5.01057 6.79951,10.55924 12.85699,12.87262 12.98871,4.96043 38.098925,-2.69862 71.18769,-21.71348 6.93735,-3.98663 13.18486,-7.53228 13.88336,-7.87922 0.6985,-0.34694 -0.97694,1.4378 -3.7232,3.96608 -2.74626,2.52828 -5.47224,4.59688 -6.05773,4.59688 -0.58551,0 -3.19631,1.33647 -5.80181,2.96995 -16.89373,10.59122 -34.183488,18.15148 -49.45415,21.62472 -10.90911,2.48122 -14.67459,2.52325 -21.08967,0.23534 z m 24.56834,-40.0204 c -1.02752,-0.64982 -1.53758,-1.51214 -1.13347,-1.91624 0.40411,-0.40412 1.45766,0.13631 2.34122,1.20093 1.8321,2.20754 1.48081,2.4156 -1.20775,0.71531 z m 78.82217,-82.99114 c -2.37932,-0.83241 -5.48637,-3.0785 -6.90454,-4.99133 -7.29304,-9.83683 0.83005,-40.20663 20.80994,-77.80214 8.55345,-16.09473 10.78907,-19.4249 12.13611,-18.07788 0.53246,0.53248 -2.78293,8.53258 -7.36755,17.77801 -13.94426,28.12024 -19.25336,43.48982 -19.2903,55.84429 -0.0286,9.57921 2.31007,14.17511 8.83985,17.37173 10.92514,5.34834 40.44494,-7.63831 72.63711,-31.95524 7.16901,-5.41525 13.37004,-9.84591 13.78006,-9.84591 1.28024,0 19.54036,-16.76056 19.54036,-17.93566 0,-0.61289 -1.14859,-1.11434 -2.55244,-1.11434 -1.40383,0 -3.54695,-1.1894 -4.76249,-2.64313 -1.21553,-1.45372 -4.48288,-3.8175 -7.26075,-5.25283 C 249.14873,68.95687 227.84861,49.76322 215.99553,34.03298 198.58916,10.93299 185.98025,-17.12147 178.35771,-49.71 c -7.75829,-33.16886 -4.40881,-79.20586 8.26984,-113.66499 21.66747,-58.88974 62.41694,-107.33117 114.64867,-136.29004 11.29168,-6.26045 43.64163,-18.96043 44.95397,-17.64807 0.20231,0.20231 -6.23892,3.13 -14.31387,6.50597 -9.45322,3.9522 -16.85521,8.00493 -20.78492,11.38014 -3.35677,2.8831 -10.29793,7.92666 -15.42481,11.20792 -15.70202,10.04945 -45.67961,39.39755 -60.03893,58.77821 -15.75831,21.26883 -20.37121,28.37002 -27.10969,41.73321 -3.93066,7.79496 -8.61444,16.22205 -10.40841,18.72686 -1.79395,2.50482 -4.59552,9.07707 -6.22569,14.605 -1.63016,5.52795 -4.68626,15.48004 -6.79132,22.1158 -3.52442,11.10992 -3.86891,13.85002 -4.35167,34.61437 -0.73071,31.42799 0.82243,39.44068 14.83035,76.51062 6.73635,17.82682 11.14224,27.49566 13.88322,30.46719 1.2951,1.40405 5.01787,7.69631 8.27279,13.98281 6.22321,12.01937 15.40855,22.574 28.50489,32.75418 3.78243,2.9402 9.2756,7.48895 12.20702,10.10832 2.93143,2.61938 6.61114,4.7625 8.17714,4.7625 4.33034,0 18.05937,-7.77268 34.83333,-19.72087 8.30756,-5.91752 15.65973,-10.75913 16.33817,-10.75913 4.04532,0 66.27092,-61.00484 106.63098,-104.53905 46.08854,-49.7132 84.52232,-97.17112 113.73593,-140.44079 9.59669,-14.21411 15.19838,-24.97519 16.7934,-32.2608 4.50545,-20.57965 4.48774,-20.54074 7.63077,-16.75361 0.94888,1.14333 1.30877,3.64832 0.89262,6.2128 -0.63095,3.88809 -5.66857,16.29865 -8.55726,21.08146 -6.65246,11.01452 -8.85427,15.21885 -8.85427,16.90714 0,1.09612 -0.53646,2.3245 -1.19215,2.72974 -1.6543,1.02242 -9.36875,14.67556 -8.68083,15.36346 1.01536,1.01538 9.9353,-8.77147 10.67286,-11.71012 0.39745,-1.58356 1.66594,-3.19303 2.81887,-3.57661 1.15294,-0.38357 4.2563,-3.97593 6.89636,-7.98301 2.64007,-4.00709 10.5094,-15.57235 17.48741,-25.7006 17.54272,-25.46245 37.65058,-62.52294 43.75546,-80.645 3.26834,-9.70194 3.9786,-24.60138 1.40957,-29.56934 -3.7763,-7.30255 -12.00108,-9.46197 -24.90756,-6.53949 -14.36826,3.25346 -52.48283,25.31545 -75.56499,43.73953 -5.98255,4.77525 -13.335,8.55451 -13.335,6.85437 0,-1.52884 21.44597,-17.39693 41.91,-31.00963 19.58529,-13.02815 36.9503,-22.30398 49.75392,-26.57693 52.68186,-17.58152 40.40002,36.70586 -31.58958,139.63031 -1.95765,2.79885 -5.04565,7.0851 -6.86223,9.525 -1.8166,2.4399 -7.50231,10.15118 -12.63493,17.13618 -14.09653,19.18402 -47.6399,60.81933 -68.74761,85.33215 -53.50049,62.13118 -147.64498,157.56308 -197.25455,199.95198 -56.37482,48.16946 -99.82469,72.88272 -117.35604,66.74933 z m 358.02102,-323.53342 c 0,-0.65727 -0.5715,-1.19504 -1.27,-1.19504 -0.6985,0 -1.27,0.89098 -1.27,1.97995 0,1.08898 0.5715,1.62674 1.27,1.19505 0.6985,-0.4317 1.27,-1.32268 1.27,-1.97996 z m 6.35,-8.81504 c 0,-0.6985 -0.25202,-1.27 -0.56005,-1.27 -0.30802,0 -0.91325,0.5715 -1.34495,1.27 -0.4317,0.6985 -0.17968,1.27 0.56004,1.27 0.73973,0 1.34496,-0.5715 1.34496,-1.27 z m 5.715,-7.62 c 0.4317,-0.6985 0.17968,-1.27 -0.56005,-1.27 -0.73972,0 -1.34495,0.5715 -1.34495,1.27 0,0.6985 0.25202,1.27 0.56004,1.27 0.30803,0 0.91326,-0.5715 1.34496,-1.27 z M 173.36398,136.99454 c -0.44493,-0.71991 0.0649,-0.93281 1.19504,-0.49912 2.20018,0.84428 2.60273,1.75458 0.77592,1.75458 -0.65727,0 -1.54422,-0.56496 -1.97096,-1.25546 z M 284.52533,114.9682 c 1.02281,-0.93049 5.28865,-4.76577 9.47965,-8.52283 l 7.62,-6.83102 -8.21831,8.52282 c -4.52007,4.68756 -8.78591,8.52283 -9.47965,8.52283 -0.69373,0 -0.42448,-0.76131 0.59831,-1.6918 z m 71.07464,-8.02196 c -1.74625,-0.26282 -9.7364,-1.37586 -17.7559,-2.47344 -15.51379,-2.12325 -17.90548,-2.81936 -15.0935,-4.39303 0.97173,-0.5438 6.903,0.0767 13.1806,1.37889 6.27759,1.3022 17.98605,2.75817 26.0188,3.23552 17.66861,1.04994 23.00467,3.05569 7.62,2.86425 -5.93725,-0.0739 -12.22375,-0.34936 -13.97,-0.61219 z m 28.91525,-1.36664 c 2.28263,-0.28266 5.71163,-0.27683 7.61999,0.0129 1.90837,0.28979 0.0408,0.52104 -4.15024,0.51392 -4.191,-0.008 -5.75239,-0.24422 -3.46975,-0.52687 z m 15.21725,-1.19069 c 0.87312,-0.35231 2.30187,-0.35231 3.175,0 0.87312,0.35231 0.15875,0.64057 -1.5875,0.64057 -1.74625,0 -2.46063,-0.28826 -1.5875,-0.64057 z m 70.65566,-22.4111 C 505.94222,59.50262 526.21784,42.95074 549.24925,17.6 c 8.7167,-9.59447 8.78206,-9.39637 0.66071,2.00189 -13.56527,19.03872 -43.23866,43.76747 -71.24204,59.37055 -7.7015,4.29116 -13.83662,6.51807 -8.27979,3.00537 z m 158.26182,-529.1978 c 0,-0.6985 0.60523,-1.27 1.34496,-1.27 0.73972,0 0.99174,0.5715 0.56004,1.27 -0.4317,0.6985 -1.03693,1.27 -1.34496,1.27 -0.30802,0 -0.56004,-0.5715 -0.56004,-1.27 z m 5.715,-3.10643 c 1.397,-1.03782 3.15191,-2.40475 3.89981,-3.03764 3.12701,-2.64612 32.75198,-18.31835 41.25459,-21.82455 12.65108,-5.21689 32.9452,-8.42756 40.39601,-6.39094 7.31809,2.00034 15.22662,9.43767 16.61714,15.62706 0.61824,2.75187 0.59025,4.7625 -0.0663,4.7625 -0.62494,0 -1.13626,-0.94618 -1.13626,-2.10262 0,-2.99311 -5.86256,-10.28792 -9.90814,-12.32876 -12.86819,-6.49148 -37.27348,-0.61589 -75.20842,18.10648 -10.11364,4.99147 -17.24543,8.22628 -15.84843,7.18847 z"
+         id="path8380"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#423d34;stroke-width:1.26999998"
+         d="m 54.78427,274.06681 c -2.34891,-1.43203 -3.99256,-2.88186 -3.65256,-3.22185 0.6202,-0.6202 9.82829,4.36922 9.82829,5.32549 0,1.09901 -2.09067,0.38687 -6.17573,-2.10364 z M 161.28999,153.14376 c -1.74625,-0.86835 -4.27529,-3.82857 -5.6201,-6.57826 -2.20404,-4.50659 -2.30522,-5.95905 -1.02636,-14.73332 0.7803,-5.35362 2.98691,-14.52935 4.90359,-20.39052 4.01471,-12.277 26.92832,-58.56416 28.50786,-57.58794 0.5812,0.3592 -2.83793,8.49393 -7.59809,18.07719 -13.5938,27.3673 -18.56195,42.30225 -18.47024,55.52409 0.0731,10.55197 2.42511,15.03175 9.03752,17.21404 13.19753,4.35558 39.50454,-7.05844 72.62403,-31.50993 15.39152,-11.36326 20.87908,-13.70349 7.55125,-3.22032 -42.02375,33.05436 -76.75714,49.74509 -89.90946,43.20497 z m 203.51748,-46.29891 c 2.61938,-0.27399 6.90563,-0.27399 9.525,0 2.61938,0.274 0.47625,0.49817 -4.7625,0.49817 -5.23875,0 -7.38187,-0.22417 -4.7625,-0.49817 z m -13.95254,-1.25617 c 1.93048,-0.29193 4.78798,-0.28412 6.35,0.0173 1.56203,0.30144 -0.0175,0.54028 -3.50996,0.53076 -3.4925,-0.0102 -4.77051,-0.25619 -2.84004,-0.5481 z m -10.15329,-1.23877 c 1.23567,-0.322 2.95017,-0.30083 3.81,0.0471 0.85983,0.34789 -0.15117,0.61134 -2.24667,0.58546 -2.0955,-0.0259 -2.799,-0.31052 -1.56333,-0.63251 z m -12.40667,-1.31292 c -2.0955,-0.29833 -4.35292,-1.06996 -5.0165,-1.71471 -1.5968,-1.55151 -0.32081,-1.44582 8.1915,0.67852 6.65123,1.65989 4.99724,2.19969 -3.175,1.03619 z m -66.67499,-2.54409 c 0,-0.72642 1.47584,-2.28778 3.27963,-3.46968 9.93549,-6.50998 17.21385,-16.08869 10.63942,-14.00205 -1.8908,0.60013 -3.95091,-0.40631 -7.62366,-3.72441 -2.76799,-2.50072 -5.76068,-4.54676 -6.65043,-4.54676 -5.06441,0 -37.42741,-29.9556 -47.28038,-43.76331 -8.20342,-11.49606 -19.28724,-31.47469 -22.11599,-39.86414 -1.33106,-3.94766 -3.62668,-9.60866 -5.10138,-12.58 -3.65195,-7.35826 -10.05145,-36.11371 -11.65769,-52.38255 -2.90915,-29.46551 2.91428,-67.62766 14.81021,-97.05431 23.35609,-57.77536 62.00995,-101.77272 113.64402,-129.35415 5.91868,-3.16158 12.19,-6.15572 13.93624,-6.65365 2.68929,-0.76683 2.49501,-0.40829 -1.27,2.34363 -2.44474,1.78693 -9.58849,6.37789 -15.87499,10.20213 -10.80824,6.57493 -15.10833,10.06236 -39.2055,31.79617 -11.70695,10.55877 -27.93356,29.43953 -36.12614,42.03519 -3.18028,4.8895 -7.97154,11.81101 -10.64725,15.38113 -2.67573,3.57011 -7.51877,12.42836 -10.76233,19.685 -3.24354,7.25663 -7.23051,15.17684 -8.85991,17.6005 -2.93818,4.37037 -5.69371,12.08259 -9.99653,27.97837 -1.229,4.54025 -3.1044,10.82675 -4.16755,13.97 -1.49698,4.42583 -1.95264,12.30409 -2.01999,34.92499 l -0.087,29.21 6.66265,19.685 c 8.71962,25.7624 17.5854,47.44221 22.42301,54.83177 2.13758,3.26522 5.88678,9.55119 8.33152,13.96882 8.11292,14.6599 41.7856,45.49941 49.67939,45.49941 1.98801,0 8.45207,-2.23493 14.36458,-4.96652 6.48999,-2.99838 10.93282,-4.41813 11.21131,-3.58269 1.44926,4.3478 37.74275,-28.38203 88.57472,-79.87756 47.39426,-48.01293 70.43258,-73.29824 106.87565,-117.29955 15.35296,-18.53715 21.0614,-25.81977 30.87061,-39.38367 1.768,-2.44475 4.5025,-6.22586 6.07664,-8.40248 1.57414,-2.17661 5.91124,-8.36671 9.63799,-13.75577 7.42275,-10.73369 10.20805,-11.95098 4.85942,-2.12375 -3.93906,7.23731 -4.03621,7.772 -1.41222,7.772 2.30309,0 1.58421,1.50326 -3.66072,7.65506 -2.76402,3.24193 -3.8451,3.81923 -4.6657,2.49146 -0.88217,-1.42735 -1.41656,-1.439 -3.06988,-0.0668 -2.80133,2.32489 -2.49582,4.31577 0.54043,3.52176 2.48439,-0.64968 2.48053,-0.57814 -0.17002,3.14422 -1.90966,2.68186 -2.99985,3.34372 -3.68407,2.23662 -1.39196,-2.25228 -5.03814,-0.47047 -5.03814,2.46198 0,1.36072 0.55988,2.66453 1.24418,2.89737 1.37607,0.46821 -20.40213,27.99282 -42.81132,54.10752 -24.05635,28.03427 -54.83446,60.84595 -100.97731,107.64912 -44.11677,44.7481 -70.27966,69.59881 -94.94553,90.18363 -7.33425,6.12076 -14.33512,11.9866 -15.5575,13.03519 -1.22237,1.0486 -2.2225,1.31219 -2.2225,0.58576 z m 271.83819,-286.20167 c 0.77967,-1.45682 1.09934,-2.96701 0.71038,-3.35597 -1.04894,-1.04894 -4.57859,2.23145 -4.57859,4.25524 0,2.62931 2.26109,2.10366 3.86821,-0.89927 z m 14.62843,-18.0435 c 0.47693,-1.2525 1.18706,-1.95735 1.57808,-1.56633 0.39102,0.39102 8.1e-4,1.41579 -0.86713,2.27728 -1.25428,1.24493 -1.40015,1.09905 -0.71095,-0.71095 z m 12.75143,-43.55389 c -0.42244,-1.10087 -0.76807,-3.16781 -0.76807,-4.59321 0,-2.15968 0.31751,-2.32812 1.905,-1.01062 2.09984,1.7427 2.55136,5.80062 0.76807,6.90275 -0.6253,0.38646 -1.48255,-0.19805 -1.905,-1.29892 z m 34.79192,-26.33007 c 0,-0.50283 1.31208,-3.046 2.91572,-5.6515 4.8453,-7.87233 15.77429,-30.8736 18.84543,-39.66226 2.1657,-6.19763 2.8995,-11.17486 2.94425,-19.9706 0.0568,-11.15032 -0.11049,-11.88565 -3.46503,-15.24 -3.25685,-3.25664 -4.26997,-3.52035 -13.335,-3.47102 -8.05347,0.0438 -11.59164,0.78357 -19.7569,4.13073 -15.65975,6.41936 -52.01985,28.33165 -63.36657,38.18763 -4.19197,3.64123 -9.87189,7.10954 -9.87189,6.02803 0,-0.82386 32.95231,-24.57324 44.68553,-32.20575 13.58256,-8.8355 33.05581,-18.60867 43.39574,-21.77932 9.36742,-2.87244 19.80804,-3.47122 23.74907,-1.36206 9.616,5.14635 7.79995,24.55489 -4.92806,52.66736 -7.46873,16.49622 -21.81229,41.70087 -21.81229,38.32876 z"
+         id="path8378"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#36332e;stroke-width:1.26999998"
+         d="m 162.9068,152.13791 c -5.67257,-1.72376 -7.24173,-5.325 -7.15813,-16.42791 0.0561,-7.45321 1.06868,-12.4809 4.65524,-23.11466 2.52093,-7.4743 6.0696,-16.33255 7.88591,-19.685 1.81632,-3.35243 2.92752,-4.95234 2.46933,-3.55534 -0.4582,1.397 -2.56676,7.6835 -4.68571,13.97 -4.98941,14.80254 -5.68631,31.38526 -1.55229,36.93687 6.7926,9.12188 19.52565,8.08618 42.97823,-3.49582 5.50792,-2.72008 10.21856,-4.74144 10.46807,-4.49191 0.78352,0.78351 -18.08509,11.12365 -28.39564,15.56103 -10.87085,4.67852 -20.37925,6.21282 -26.66501,4.30274 z m 9.81319,-69.69295 c 0,-0.65728 0.56496,-1.54422 1.25545,-1.97097 0.71992,-0.44493 0.93282,0.0649 0.49913,1.19505 -0.84429,2.20017 -1.75458,2.60272 -1.75458,0.77592 z m 107.26904,-0.14932 c -0.47851,-0.77423 -2.40632,-0.99832 -4.54585,-0.52839 -3.06885,0.67403 -4.34446,0.14209 -7.33421,-3.05847 l -3.61925,-3.8744 7.00715,-2.03704 c 5.63679,-1.63866 7.20427,-1.71809 8.01507,-0.40617 3.56168,5.7629 37.20598,-23.5265 98.67917,-85.90617 42.53364,-43.16083 67.35,-70.05692 94.64483,-102.57651 16.57338,-19.74586 19.68314,-22.60611 20.83174,-19.16032 0.47206,1.41616 -26.47858,33.19085 -47.7795,56.33183 -48.70693,52.91448 -117.41796,122.00876 -146.8032,147.62206 -5.588,4.87072 -11.68031,10.22431 -13.53844,11.89689 -3.80313,3.42332 -4.38131,3.59984 -5.55751,1.69669 z M 204.71268,15.36085 C 196.53909,1.19846 191.78522,-8.98736 191.82867,-12.245 c 0.0187,-1.397 2.15918,2.31775 4.75676,8.255 2.5976,5.93725 7.19979,14.9826 10.22713,20.10076 3.02733,5.11817 5.24735,9.56265 4.93338,9.87663 -0.31398,0.31397 -3.47894,-4.46798 -7.03326,-10.62654 z M 188.70015,-18.2775 c -2.24509,-3.4521 -8.38984,-24.27667 -8.28624,-28.08207 0.0406,-1.49348 1.97382,3.28532 4.29592,10.61957 2.32208,7.33425 4.84642,14.90663 5.60962,16.8275 1.6803,4.22903 0.91362,4.52967 -1.6193,0.635 z M 175.50757,-73.205 c 0.0259,-2.0955 0.31052,-2.799 0.63251,-1.56333 0.322,1.23567 0.30083,2.95017 -0.0471,3.81 -0.34789,0.85983 -0.61134,-0.15117 -0.58545,-2.24667 z m 0.14595,-27.94 c 0.008,-4.191 0.24422,-5.75238 0.52687,-3.46975 0.28265,2.28264 0.27683,5.71164 -0.0129,7.62 -0.28979,1.90837 -0.52104,0.0408 -0.51392,-4.15025 z m 3.5038,-29.42722 c -0.20426,-3.47848 5.55909,-24.83723 9.70809,-35.97777 17.7142,-47.56465 46.56233,-85.63172 88.40924,-116.66201 12.34022,-9.15052 9.3716,-5.08172 -5.15563,7.06632 -15.71751,13.14338 -28.95025,26.37212 -39.12253,39.11069 -11.67784,14.62398 -25.96067,37.17503 -32.55658,51.40336 -3.40806,7.35165 -6.85813,14.50964 -7.66683,15.90664 -3.62324,6.25901 -4.94481,9.85471 -9.10213,24.76499 -2.43446,8.73126 -4.4656,15.20575 -4.51363,14.38778 z m 314.10176,-8.77665 c 0.42399,-1.10487 0.77088,-2.24787 0.77088,-2.54 0,-0.29211 0.85725,-0.53113 1.905,-0.53113 2.2964,0 2.45518,1.48183 0.381,3.556 -2.17021,2.17022 -3.9664,1.88532 -3.05688,-0.48487 z m 32.52088,-43.63617 c 0,-0.65727 0.5715,-1.54826 1.27,-1.97995 0.6985,-0.4317 1.27,0.10604 1.27,1.19504 0,1.08898 -0.5715,1.97996 -1.27,1.97996 -0.6985,0 -1.27,-0.53777 -1.27,-1.19505 z M 288.28998,-290.28562 c 0,-0.34925 1.143,-1.24672 2.54,-1.99437 1.397,-0.74765 2.54,-1.07362 2.54,-0.72437 0,0.34925 -1.143,1.24672 -2.54,1.99437 -1.397,0.74765 -2.54,1.07362 -2.54,0.72437 z m 328.35652,-24.21937 c 5.18665,-15.35039 6.53882,-27.16026 3.90065,-34.06823 -5.01489,-13.13128 -20.59897,-13.58862 -45.77613,-1.34337 -10.61774,5.16407 -12.39496,4.83454 -2.88182,-0.53433 21.2483,-11.9918 42.88485,-17.31547 50.03098,-12.31013 4.02633,2.82016 5.89415,9.85149 4.9153,18.50349 -0.90319,7.983 -5.47189,22.63495 -8.92078,28.60915 -1.16954,2.02588 -1.74023,2.54042 -1.2682,1.14342 z"
+         id="path8376"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>
diff --git a/resources/fonts/svg/planets/uranus.svg b/resources/fonts/svg/planets/uranus.svg
new file mode 100644
index 0000000..7ae4f32
--- /dev/null
+++ b/resources/fonts/svg/planets/uranus.svg
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="754"
+   height="749"
+   viewBox="0 0 199.49583 198.17292"
+   version="1.1"
+   id="svg10645"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)"
+   sodipodi:docname="uranus.svg">
+  <defs
+     id="defs10639" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.35"
+     inkscape:cx="-137.14286"
+     inkscape:cy="560"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:window-width="2560"
+     inkscape:window-height="1531"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata10642">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-98.827067)">
+    <g
+       id="g11305"
+       transform="translate(185.85862,40.714962)">
+      <g
+         id="g11293">
+        <path
+           inkscape:connector-curvature="0"
+           id="path11270"
+           d="m -110.30857,254.92865 c -15.70082,-2.38177 -32.10669,-11.51211 -45.9984,-25.59944 -13.12924,-13.31412 -14.08463,-14.59418 -20.69711,-27.7304 -7.90723,-15.70833 -8.83713,-20.39583 -8.85441,-44.63372 -0.017,-23.83962 1.60791,-31.59583 9.80583,-46.80628 11.84207,-21.971843 33.64786,-40.816103 56.16592,-48.537813 7.49658,-2.57067 10.72738,-2.84889 33.07317,-2.84816 27.24098,8.9e-4 30.55021,0.62834 46.99,8.90957 22.36788,11.26738 38.81269,30.14011 48.63145,55.811403 3.64626,9.53321 3.91816,11.45276 4.40722,31.115 0.51168,20.57177 0.45024,21.17583 -3.35996,33.03034 -5.51952,17.17265 -17.6159,36.41534 -27.68688,44.04376 -2.59284,1.96399 -6.1574,4.89753 -7.92123,6.51898 -6.15761,5.66053 -28.03121,15.39716 -37.5656,16.72164 -10.22532,1.42046 -37.64025,1.42346 -46.99,0.005 z"
+           style="fill:#b0daf2;stroke-width:1.26999998" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path11268"
+           d="m -110.30857,254.40263 c -19.53603,-4.48846 -31.95043,-11.87606 -47.92518,-28.51948 -9.96145,-10.37841 -13.05915,-14.62318 -17.84564,-24.45383 -8.19156,-16.82408 -10.26989,-27.62963 -9.45959,-49.18182 0.69583,-18.50788 2.77573,-26.89026 10.44333,-42.08869 7.94105,-15.740423 24.51295,-32.301703 40.96481,-40.938543 17.72947,-9.30757 22.96971,-10.45153 47.95227,-10.46819 20.32321,-0.0135 22.19563,0.1932 31.9118,3.52361 29.67251,10.17085 51.22527,31.74642 62.81776,62.884263 3.75036,10.0736 4.15752,12.61104 4.67853,29.15639 0.53583,17.01626 0.36003,18.79811 -2.87988,29.18965 -10.88059,34.89782 -31.58752,56.83303 -64.75385,68.59487 -7.71341,2.73541 -11.56632,3.22134 -28.59936,3.6069 -13.65142,0.30901 -22.02057,-0.0911 -27.305,-1.30513 z m 28.8925,-40.02636 c 3.31788,-1.68724 6.0325,-3.82364 6.0325,-4.74758 0,-0.92393 0.88639,-1.67988 1.96975,-1.67988 2.93836,0 10.71721,-9.07904 10.72419,-12.51666 0.003,-1.64543 1.46526,-4.72583 3.2487,-6.84533 1.80065,-2.13995 2.91732,-4.70141 2.5111,-5.76003 -0.40235,-1.04851 -1.24493,-6.82636 -1.87239,-12.83968 -1.42508,-13.65729 -2.28454,-15.73697 -9.83915,-23.80789 -5.44706,-5.81934 -6.79358,-6.59226 -12.4572,-7.15056 -8.14712,-0.80312 -9.525,-0.21859 -9.525,4.04075 0,1.91891 -1.42635,6.30041 -3.16968,9.73666 -1.74333,3.43626 -3.18083,7.39074 -3.19445,8.78774 -0.0345,3.53838 -1.15285,4.73826 -6.89425,7.39682 -4.88287,2.26102 -5.05112,2.2577 -6.985,-0.13785 -2.73583,-3.38894 -17.48234,-10.43397 -21.84019,-10.43397 -2.50205,0 -4.25495,1.04385 -5.82965,3.47154 -1.23848,1.90935 -3.2519,4.4811 -4.47428,5.715 -3.49875,3.53175 -2.88355,12.6752 1.46714,21.80547 4.0749,8.55148 12.03676,18.53799 14.77964,18.53799 0.96875,0 2.37307,1.143 3.12072,2.54 0.74766,1.397 2.05364,2.55939 2.90219,2.58309 0.84855,0.0238 3.82881,1.17554 6.62281,2.55962 7.40917,3.67035 24.2907,3.02237 32.7025,-1.25525 z"
+           style="fill:#a9d6f1;stroke-width:1.26999998" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path11266"
+           d="m -112.21265,253.64201 c -5.23824,-1.4076 -14.38224,-4.9382 -20.32,-7.8458 -9.0123,-4.41314 -12.90169,-7.39925 -23.54182,-18.07447 -10.55236,-10.58715 -13.79063,-14.81768 -18.8164,-24.58212 -8.19219,-15.91635 -9.73804,-22.09083 -10.5763,-42.24408 -0.61454,-14.77474 -0.34448,-18.67648 1.90968,-27.59048 5.00314,-19.78474 13.97838,-34.485993 30.60039,-50.122783 10.65999,-10.02816 21.54191,-16.45135 36.29853,-21.42569 7.90467,-2.66459 11.21258,-2.99874 29.845,-3.01476 23.39748,-0.0201 29.1524,1.14259 45.085,9.1089 22.15185,11.07593 38.23211,28.42007 47.75138,51.504543 5.82394,14.12323 6.52008,18.22981 6.4652,38.13914 -0.0466,16.90891 -0.39175,19.79196 -3.49707,29.20999 -1.89494,5.74718 -4.57682,12.73541 -5.95973,15.52941 -7.99996,16.16299 -26.93218,35.37861 -42.40917,43.04402 -16.95366,8.39677 -23.72049,9.99879 -44.26061,10.47852 -16.05939,0.37508 -20.54516,0.0432 -28.57408,-2.11434 z m 25.54324,-16.46658 c 6.01597,-1.10223 12.37409,-3.38103 22.45736,-8.04886 12.26956,-5.67994 27.22401,-27.87702 29.43428,-43.68968 1.7082,-12.22088 -0.60125,-28.65904 -5.37481,-38.25655 -5.5355,-11.12943 -19.05585,-24.40036 -30.15099,-29.59478 -4.54025,-2.12561 -9.31613,-4.42339 -10.61308,-5.10618 -1.29694,-0.68279 -8.44069,-1.56104 -15.875,-1.95167 -12.65406,-0.6649 -14.08442,-0.48631 -22.40692,2.79766 -27.3678,10.79903 -42.25767,29.27005 -44.65364,55.39324 -0.93075,10.14793 0.88338,18.70584 6.63664,31.30753 6.10059,13.36244 15.45536,22.67527 30.1574,30.02223 15.13536,7.5635 26.78588,9.61938 40.38876,7.12706 z"
+           style="fill:#93c8ea;stroke-width:1.26999998" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path11264"
+           d="m -105.22857,255.35809 c -10.21815,-2.68775 -26.19953,-8.65921 -30.13527,-11.26006 -2.6044,-1.72107 -5.2559,-3.12922 -5.89223,-3.12922 -1.50524,0 -22.90348,-21.13637 -26.38043,-26.05758 -5.56431,-7.87564 -14.24242,-26.98422 -16.13923,-35.53742 -2.92303,-13.18078 -2.05745,-39.05992 1.69913,-50.8 10.26439,-32.078423 35.19518,-57.143903 66.68803,-67.048333 7.56065,-2.3778 11.83358,-2.79631 28.575,-2.79878 17.10456,-0.003 20.92644,0.38695 29.15531,2.97099 25.37439,7.9681 44.07011,22.45198 56.79883,44.002973 10.4075,17.62093 13.07176,28.14306 13.09079,51.70009 0.0135,16.62532 -0.34837,19.82005 -3.30798,29.21 -6.12689,19.43874 -14.98127,32.80225 -30.51729,46.05831 -10.7404,9.16421 -20.35324,14.58551 -34.3178,19.35405 -8.72686,2.97999 -12.20549,3.43377 -28.36186,3.69984 -10.12825,0.16679 -19.558,0.003 -20.955,-0.36486 z m 33.02,-10.30048 c 15.07847,-4.78213 24.49543,-11.39213 35.52956,-24.93906 6.97503,-8.56346 11.44598,-18.25653 14.69303,-31.85474 8.76627,-36.71199 -14.89191,-76.43438 -53.39759,-89.655213 -17.65328,-6.06121 -41.26261,-4.33617 -57.65415,4.212553 -29.2205,15.23945 -43.88246,40.02118 -42.28635,71.47266 1.27777,25.17865 13.03825,45.18997 35.69314,60.73449 9.35747,6.42057 12.73659,7.97365 24.24236,11.14213 11.19825,3.08377 31.60614,2.55783 43.18,-1.11282 z"
+           style="fill:#86bade;stroke-width:1.26999998" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path11262"
+           d="m -103.63927,254.62669 c -0.29005,-0.87016 -1.3378,-1.59743 -2.32833,-1.61613 -2.85919,-0.054 -16.74193,-4.50913 -16.0882,-5.16285 0.32492,-0.32493 3.83501,0.25425 7.80018,1.28705 19.08716,4.97157 36.30863,3.13802 55.254,-5.88286 19.78261,-9.41951 35.24984,-29.02915 41.3403,-52.41202 2.28994,-8.79168 2.56784,-25.56883 0.58334,-35.21606 -1.25072,-6.08008 -10.45044,-27.39898 -12.49492,-28.95501 -0.45888,-0.34925 -3.59503,-4.064 -6.96921,-8.255 -6.37313,-7.91594 -22.69765,-19.619883 -31.22146,-22.384383 -2.44475,-0.7929 -5.0165,-1.86336 -5.715,-2.3788 -3.22403,-2.37909 -19.49779,-4.1332 -31.85404,-3.43348 -14.35919,0.81315 -24.95255,4.17883 -36.7541,11.677343 -8.25511,5.24516 -21.11671,17.1721 -23.50208,21.79415 -1.02615,1.98834 -2.21849,3.90092 -2.64965,4.25017 -1.5822,1.28163 -6.01342,11.03109 -8.61334,18.95083 -6.29029,19.16119 -0.90274,50.50341 11.23713,65.37222 1.5051,1.84344 1.55805,2.27816 0.22584,1.85408 -3.64578,-1.16054 -14.51788,-23.14318 -17.18264,-34.74213 -3.07459,-13.38279 -2.89047,-34.52182 0.40929,-46.99 8.73124,-32.991093 32.64813,-58.194783 66.49066,-70.068053 13.54461,-4.75197 36.87324,-5.5924 51.63254,-1.86008 34.16181,8.63879 58.47839,30.33557 71.43563,63.739293 4.14291,10.68038 4.16153,10.82922 4.12245,32.95384 -0.0384,21.7587 -0.12256,22.44592 -4.01079,32.75453 -6.12289,16.23333 -17.82084,34.06436 -26.15115,39.86187 -2.21966,1.54479 -6.23415,4.65683 -8.92111,6.91565 -7.4605,6.27177 -32.15806,16.98795 -39.15208,16.98795 -1.27466,0 -2.31756,0.5715 -2.31756,1.27 0,1.9881 -33.94101,1.68198 -34.6057,-0.31212 z m 22.26152,-0.61591 c -2.94755,-0.26634 -8.09105,-0.26952 -11.43,-0.008 -3.33895,0.26306 -0.92732,0.48099 5.35918,0.48485 6.2865,0.004 9.01837,-0.21088 6.07082,-0.47723 z m -48.93332,-8.79824 c -2.61937,-1.08647 -4.7625,-2.45661 -4.7625,-3.04476 0,-1.15494 8.74829,2.1049 10.7315,3.99884 1.77141,1.69166 -0.44408,1.33755 -5.969,-0.95408 z m -11.7475,-6.78373 c -0.43169,-0.6985 -0.2134,-1.27 0.4851,-1.27 0.6985,0 1.62321,0.5715 2.0549,1.27 0.4317,0.6985 0.21341,1.27 -0.48509,1.27 -0.6985,0 -1.62321,-0.5715 -2.05491,-1.27 z m -6.985,-5.08 c -0.74765,-1.397 -0.85633,-2.54 -0.24151,-2.54 0.61481,0 2.15225,1.143 3.41651,2.54 2.02887,2.24187 2.05722,2.54 0.24152,2.54 -1.13143,0 -2.66886,-1.143 -3.41652,-2.54 z m -13.99547,-14.6357 c -0.88356,-1.06462 -1.28559,-2.25657 -0.8934,-2.64876 0.3922,-0.3922 1.35099,0.47887 2.13066,1.93568 1.6635,3.10829 1.04597,3.46421 -1.23726,0.71308 z"
+           style="fill:#7cb7e0;stroke-width:1.26999998" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path11260"
+           d="m -103.32357,255.01377 c 0,-2.02386 2.6827,-2.98026 4.17466,-1.4883 1.66924,1.66925 24.25593,1.96458 23.22948,0.30374 -0.37717,-0.61027 2.12432,-1.48236 5.55887,-1.93797 10.4935,-1.39201 25.37195,-9.78857 35.87642,-20.24663 11.04649,-10.99769 17.61356,-22.30163 22.18243,-38.18273 2.96383,-10.30212 3.21658,-12.85904 2.57886,-26.08977 -0.80314,-16.66257 -3.25201,-25.73826 -10.24062,-37.95234 -10.37958,-18.14053 -21.57225,-28.70196 -40.61146,-38.321113 -10.53029,-5.3202 -15.05596,-6.83984 -24.60562,-8.26217 -21.21643,-3.15998 -39.52329,0.30576 -57.70472,10.92428 -15.96127,9.321893 -27.42741,22.213833 -34.91624,39.258043 -3.44793,7.84732 -3.62596,9.16445 -3.60488,26.67 0.0185,15.39473 0.47963,19.86327 2.81139,27.24527 1.53407,4.85666 2.5402,9.0793 2.23586,9.38364 -1.62485,1.62485 -8.64479,-23.95341 -8.13575,-29.64391 0.45686,-5.10712 0.42254,-11.49163 -0.099,-18.415 -0.53104,-7.04983 3.73809,-23.1582 9.08773,-34.29 11.2253,-23.358153 33.16191,-42.295233 59.83464,-51.653053 13.54461,-4.75197 36.87324,-5.5924 51.63254,-1.86008 34.16181,8.63879 58.47839,30.33557 71.43563,63.739293 4.14291,10.68038 4.16153,10.82922 4.12245,32.95384 -0.0384,21.7587 -0.12256,22.44592 -4.01079,32.75453 -6.12289,16.23333 -17.82084,34.06436 -26.15115,39.86187 -2.21966,1.54479 -6.23415,4.65683 -8.92111,6.91565 -7.4605,6.27177 -32.15806,16.98795 -39.15208,16.98795 -1.27466,0 -2.31756,0.5715 -2.31756,1.27 0,0.78395 -6.56166,1.27 -17.145,1.27 -9.86646,0 -17.145,-0.50733 -17.145,-1.19504 z m -45.085,-20.39496 c -0.43169,-0.6985 -0.2134,-1.27 0.4851,-1.27 0.6985,0 1.62321,0.5715 2.0549,1.27 0.4317,0.6985 0.21341,1.27 -0.48509,1.27 -0.6985,0 -1.62321,-0.5715 -2.05491,-1.27 z"
+           style="fill:#70abd8;stroke-width:1.26999998" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path11258"
+           d="m -101.73607,255.33336 c 1.22238,-0.31943 3.22263,-0.31943 4.445,0 1.22238,0.31944 0.22225,0.58081 -2.2225,0.58081 -2.44475,0 -3.44487,-0.26137 -2.2225,-0.58081 z m 26.04981,-0.0227 c 1.57977,-0.30377 3.86577,-0.29224 5.08,0.0256 1.21423,0.31787 -0.0783,0.56642 -2.87231,0.55232 -2.794,-0.0141 -3.78746,-0.27418 -2.20769,-0.57795 z m 51.22419,-25.54176 c 9.29493,-9.49976 16.76247,-23.23118 20.84497,-38.33002 3.1934,-11.81053 3.00152,-35.85818 -0.37495,-46.99 -7.46045,-24.59629 -20.83829,-42.84035 -41.54652,-56.659183 -17.5232,-11.69343 -35.23903,-16.42034 -57.64517,-15.38079 -24.5276,1.13799 -40.17695,7.92871 -56.97233,24.72202 -10.93499,10.933653 -13.12752,12.294363 -6.6675,4.137913 2.0955,-2.645783 3.81,-5.184483 3.81,-5.641553 0,-2.58057 19.48445,-19.29575 28.217,-24.20659 10.0109,-5.62974 24.09681,-10.79186 29.44778,-10.79186 1.53297,0 3.41898,-0.63175 4.19112,-1.4039 0.93283,-0.93282 6.66445,-1.22931 17.0815,-0.88358 13.51789,0.44863 17.33965,1.08674 27.7426,4.63209 30.90429,10.53225 52.43294,31.48663 63.48382,61.790393 3.92858,10.77296 4.18117,12.48856 4.39405,29.845 0.20791,16.95156 -0.0605,19.35734 -3.37648,30.27284 -1.98129,6.52182 -4.8094,14.22387 -6.2847,17.11569 -5.15047,10.09577 -20.13284,28.81147 -23.06427,28.81147 -0.63915,0 -3.11481,1.7145 -5.50145,3.81 -7.7354,6.79178 -6.47357,4.03574 2.22053,-4.84998 z"
+           style="fill:#5391c8;stroke-width:1.26999998" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path11256"
+           d="m -101.73607,255.33336 c 1.22238,-0.31943 3.22263,-0.31943 4.445,0 1.22238,0.31944 0.22225,0.58081 -2.2225,0.58081 -2.44475,0 -3.44487,-0.26137 -2.2225,-0.58081 z m 26.67,0 c 1.22238,-0.31943 3.22263,-0.31943 4.445,0 1.22238,0.31944 0.22225,0.58081 -2.2225,0.58081 -2.44475,0 -3.44487,-0.26137 -2.2225,-0.58081 z m 54.2925,-24.7413 c 0,-0.40273 2.286,-3.23309 5.08,-6.28969 2.794,-3.05658 5.08,-4.9593 5.08,-4.22823 0,0.73105 -2.286,3.56141 -5.08,6.28967 -2.794,2.72826 -5.08,4.63098 -5.08,4.22825 z m 11.59603,-13.11825 c 0.26594,-0.6985 2.12364,-4.39301 4.12824,-8.21002 5.77401,-10.99449 9.43661,-21.84668 10.34243,-30.64439 0.46071,-4.47457 0.98837,-9.27859 1.17258,-10.67559 1.28447,-9.74079 -5.01237,-38.15412 -10.65506,-48.07904 -9.6234,-16.92658 -12.80139,-20.893723 -26.34902,-32.891983 -17.16665,-15.20341 -40.58501,-24.08346 -63.25407,-23.98544 -12.15538,0.0526 -21.72215,2.08917 -34.21802,7.28445 -11.7238,4.87429 -11.65306,4.85089 -10.23811,3.38619 2.86182,-2.96243 17.08217,-9.27068 25.4,-11.26763 4.8895,-1.17387 10.11146,-2.68404 11.60435,-3.35592 1.49289,-0.67189 9.49389,-0.98347 17.78,-0.69241 12.78518,0.4491 16.89192,1.15087 27.13065,4.63619 30.91258,10.52279 52.52619,31.5705 63.44678,61.785593 3.97994,11.0117 4.13286,12.1504 4.17868,31.115 0.043,17.83265 -0.26711,20.6734 -3.29653,30.18876 -3.68987,11.58983 -7.14563,18.95487 -12.0546,25.69124 -3.96621,5.44265 -5.78987,7.47893 -5.1183,5.715 z M -162.37857,93.648807 c 0.4317,-0.6985 1.03693,-1.27 1.34496,-1.27 0.30802,0 0.56004,0.5715 0.56004,1.27 0,0.6985 -0.60523,1.27 -1.34495,1.27 -0.73972,0 -0.99174,-0.5715 -0.56005,-1.27 z m 12.7,-12.7 c 0.4317,-0.6985 1.35641,-1.27 2.05491,-1.27 0.6985,0 0.91679,0.5715 0.48509,1.27 -0.43169,0.6985 -1.3564,1.27 -2.0549,1.27 -0.6985,0 -0.91679,-0.5715 -0.4851,-1.27 z m 5.715,-3.67412 c 0,-0.62377 0.93809,-1.49411 2.08464,-1.93408 1.74304,-0.66886 1.82161,-0.48302 0.47949,1.13413 -1.82896,2.20376 -2.56413,2.43311 -2.56413,0.79995 z"
+           style="fill:#558bb7;stroke-width:1.26999998" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path11254"
+           d="m -102.37107,255.36771 c 0.87313,-0.35231 2.30188,-0.35231 3.175,0 0.87313,0.35232 0.15875,0.64058 -1.5875,0.64058 -1.74625,0 -2.46062,-0.28826 -1.5875,-0.64058 z m 28.59917,-0.039 c 1.23567,-0.322 2.95017,-0.30083 3.81,0.0471 0.85984,0.34789 -0.15117,0.61134 -2.24667,0.58546 -2.0955,-0.0259 -2.79899,-0.31052 -1.56333,-0.63251 z M 5.63467,183.81879 c 0.49322,-3.14325 0.84506,-13.1445 0.78188,-22.225 -0.10378,-14.91656 -0.49917,-17.67444 -4.09665,-28.575 -5.06583,-15.34974 -11.29741,-25.87478 -22.3374,-37.727523 -20.04898,-21.52496 -45.86309,-33.34969 -72.94029,-33.41193 -5.24649,-0.0121 -7.80308,-0.43613 -6.72957,-1.11626 1.4616,-0.92601 1.4616,-1.20404 0,-1.78529 -0.95216,-0.37866 5.01765,-0.73359 13.26625,-0.78874 9.69919,-0.0648 14.72016,0.3484 14.21254,1.16974 -0.43169,0.6985 0.36233,1.27 1.76449,1.27 4.03716,0 22.95611,6.30287 30.62051,10.20125 20.75352,10.55597 35.66529,27.92507 45.8062,53.354673 l 4.28258,10.73908 -0.0634,21.59 c -0.0566,19.29989 -0.34978,22.19621 -2.76365,27.305 l -2.7003,5.715 0.89676,-5.715 z M -83.32107,59.703657 c -2.61937,-0.274 -6.90562,-0.274 -9.525,0 -2.61937,0.27399 -0.47625,0.49817 4.7625,0.49817 5.23875,0 7.38188,-0.22418 4.7625,-0.49817 z"
+           style="fill:#2f78b0;stroke-width:1.26999998" />
+      </g>
+    </g>
+  </g>
+</svg>
diff --git a/resources/fonts/svg/planets/venus.svg b/resources/fonts/svg/planets/venus.svg
new file mode 100644
index 0000000..bb7dfc5
--- /dev/null
+++ b/resources/fonts/svg/planets/venus.svg
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="200"
+   height="200"
+   viewBox="0 0 52.916665 52.916668"
+   version="1.1"
+   id="svg9255"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)"
+   sodipodi:docname="venus.svg">
+  <defs
+     id="defs9249" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="3.959798"
+     inkscape:cx="54.489603"
+     inkscape:cy="70.802335"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:window-width="2560"
+     inkscape:window-height="1531"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata9252">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-244.08332)">
+    <g
+       id="g9947"
+       transform="translate(-1.7372519,1.8708867)">
+      <path
+         sodipodi:nodetypes="ccssscsccccc"
+         style="fill:#e5dad2;stroke-width:1.26999998"
+         d="m 14.17186,290.89089 c -4.6118105,-3.7516 -6.7170008,-5.84447 -9.2031491,-10.66781 -1.6466132,-3.19456 -2.428711,-7.32512 -2.428711,-12.82695 0,-6.98925 0.5250616,-8.83987 3.7847933,-13.3398 6.1681128,-8.51483 9.0309508,-9.76134 22.4187218,-9.76134 13.010637,0 16.221229,1.40482 21.738982,9.51209 2.448653,3.59781 2.723466,5.86021 2.8575,15.80596 0.08147,6.04508 -0.0063,9.14695 -1.5368,12.2756 -1.728052,2.21581 -2.644818,3.10474 -4.861132,5.63409 -2.302564,2.28926 -3.11546,2.89928 -5.596123,4.56501 -4.297815,1.69447 -5.713925,2.26333 -11.231376,2.01608 -8.07487,0.0197 -13.236367,-1.44885 -15.942706,-3.21293 z"
+         id="path9894"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#dac8b5;stroke-width:1.26999998"
+         d="m 18.414999,292.65497 c -2.0955,-0.93808 -5.63841,-3.63034 -7.873134,-5.98279 -10.74675682,-11.31292 -10.52117839,-26.49217 0.549926,-37.0047 8.559096,-8.12727 26.411157,-8.12727 34.963487,0 5.863571,5.57214 7.284719,9.47246 7.284719,19.99282 0,9.54537 -0.03989,9.65986 -5.3975,15.49055 -2.968624,3.23075 -7.236519,6.64698 -9.484208,7.59162 -5.173091,2.17409 -15.08799,2.13081 -20.04329,-0.0875 z"
+         id="path9892"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#cdb49d;stroke-width:1.26999998"
+         d="M 25.342539,293.22677 C 16.691234,292.40152 12.382319,289.69985 7.5403755,282.0649 0.5956857,271.11426 3.1867674,255.75932 13.278186,248.06222 c 8.486508,-6.47297 25.288819,-5.53318 32.199506,1.80099 6.023443,6.39256 7.852599,10.62039 7.857558,18.1616 0.0057,8.62539 -1.527264,13.13544 -6.108547,17.97197 -6.148217,6.49078 -11.409968,8.22913 -21.884164,7.22999 z"
+         id="path9890"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#d1a255;stroke-width:1.26999998"
+         d="M 19.076114,291.77263 C 11.336087,288.68445 5.5350575,279.84804 4.4739875,269.52995 3.8225155,263.19489 4.1443184,261.33 6.6161746,257.11572 11.485242,248.81443 17.918738,244.5389 26.47176,243.92023 c 11.468591,-0.82957 18.577182,3.46602 24.180267,14.61167 4.756404,9.46143 2.991117,19.95007 -4.598823,27.3244 -2.628014,2.55335 -6.387036,5.24799 -8.353383,5.98808 -4.403228,1.65727 -14.386574,1.61881 -18.623707,-0.0718 z"
+         id="path9888"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#b58d67;stroke-width:1.26999998"
+         d="m 18.352036,291.40361 c -4.30766,-1.8763 -10.7320364,-8.84782 -10.7320364,-11.64606 0,-0.99416 -0.6334551,-1.80756 -1.4076781,-1.80756 -0.8782279,0 -1.099022,-1.22978 -0.5870373,-3.26969 0.4513525,-1.79833 0.1412545,-5.14851 -0.6891068,-7.44483 -1.3319834,-3.68354 -1.1507117,-4.81792 1.5395373,-9.63428 1.6771066,-3.00253 5.2011843,-7.1889 7.8312843,-9.30304 10.977957,-8.82438 31.962498,-3.89199 34.603754,8.13357 0.430381,1.95952 1.603027,4.24373 2.605879,5.07602 2.106372,1.74813 2.36421,8.72946 0.561989,15.21667 -1.415026,5.09348 -9.038637,13.11006 -14.3788,15.11997 -4.86827,1.8323 -14.640305,1.60968 -19.347785,-0.44077 z m -4.982654,-17.31925 c 1.024424,-1.65755 -1.655888,-5.02437 -3.9998873,-5.02437 -1.7849907,0 -2.4362582,3.9699 -0.9028284,5.50333 1.279146,1.27915 3.9791647,1.01537 4.9027157,-0.47896 z m 26.635616,-6.31176 c 0,-2.72225 -3.690198,-2.32906 -4.22544,0.45022 -0.326855,1.69723 0.190385,2.21447 1.887607,1.88761 1.285808,-0.24762 2.337833,-1.29965 2.337833,-2.33783 z"
+         id="path9886"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#c98c2f;stroke-width:1.26999998"
+         d="m 19.049999,291.09069 c -2.94294,-1.17036 -8.934036,-7.07268 -10.7601523,-10.6007 -2.8271249,-5.46195 -3.6976275,-18.23791 -1.5260465,-22.39707 4.2971498,-8.23019 13.8261178,-14.44423 22.0840158,-14.40145 15.535168,0.0805 27.801179,19.85867 21.468619,34.61681 -0.964461,2.24769 -3.843359,6.07694 -6.397553,8.50943 -4.328566,4.12233 -5.266139,4.44337 -13.803936,4.72665 -5.037971,0.16715 -10.017197,-0.037 -11.064947,-0.45367 z m -5.680617,-17.00633 c 1.024424,-1.65755 -1.655888,-5.02437 -3.9998873,-5.02437 -1.7849907,0 -2.4362582,3.9699 -0.9028284,5.50333 1.279146,1.27915 3.9791647,1.01537 4.9027157,-0.47896 z m 26.635616,-6.31176 c 0,-2.72225 -3.690198,-2.32906 -4.22544,0.45022 -0.326855,1.69723 0.190385,2.21447 1.887607,1.88761 1.285808,-0.24762 2.337833,-1.29965 2.337833,-2.33783 z"
+         id="path9884"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#c07e25;stroke-width:1.26999998"
+         d="m 19.049999,291.09322 c -3.002704,-1.19014 -10.1599995,-8.38785 -10.1599995,-10.21736 0,-0.91073 1.04775,-1.65587 2.3283335,-1.65587 1.280583,0 2.740492,-0.41216 3.244242,-0.91591 0.503749,-0.50375 4.326396,-0.98751 8.494768,-1.07502 6.011198,-0.1262 8.218671,-0.76019 10.672033,-3.065 2.554817,-2.40013 3.964927,-2.76648 8.101914,-2.10495 4.908541,0.7849 5.030294,0.71056 6.086224,-3.71635 1.405246,-5.89141 3.505236,-6.17874 4.03997,-0.55277 0.654083,6.88162 -1.771749,12.91185 -7.408273,18.41577 -4.948589,4.83217 -5.522093,5.04588 -14.324593,5.33811 -5.04329,0.16742 -10.026869,-0.0354 -11.074619,-0.45065 z m -0.635,-17.22982 c -1.397,-0.80815 -3.618014,-2.8373 -4.935585,-4.50924 -1.55553,-1.9739 -3.25283,-2.81571 -4.8401879,-2.40061 -5.1425763,1.34481 -4.4865159,-6.51735 1.0976152,-13.15371 10.9730767,-13.04076 25.1159697,-13.57604 35.6199837,-1.34814 3.285548,3.82476 3.691662,6.44829 0.998173,6.44829 -1.04775,0 -1.905,1.10927 -1.905,2.46505 0,4.28356 -2.711599,4.39471 -5.959785,0.2443 -3.181459,-4.06515 -6.740215,-5.29449 -6.740215,-2.32835 0,0.90805 0.616179,2.26718 1.369286,3.02029 1.887423,1.88742 0.955138,8.21307 -1.369286,9.29072 -1.04775,0.48577 -3.18312,1.54353 -4.745267,2.3506 -3.518381,1.81773 -5.339628,1.80094 -8.589732,-0.0792 z"
+         id="path9882"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#a47349;stroke-width:1.26999998"
+         d="m 19.049999,291.09322 c -2.447714,-0.97017 -10.1599995,-8.24398 -10.1599995,-9.58236 0,-0.56148 1.4287495,-1.02087 3.1749995,-1.02087 1.74625,0 3.175,-0.6858 3.175,-1.524 0,-1.15097 0.358481,-1.16552 1.464559,-0.0594 0.892574,0.89258 2.256573,1.0453 3.4925,0.39105 1.115367,-0.59043 4.298738,-1.37532 7.074156,-1.7442 2.775417,-0.36888 5.2541,-1.29435 5.508184,-2.0566 1.027786,-3.08336 5.207604,-4.02046 9.056062,-2.03035 5.527124,2.85819 6.18387,2.5106 7.125922,-3.77145 1.040005,-6.93526 2.832902,-7.52174 2.911852,-0.9525 0.08858,7.37098 -1.930287,12.09877 -7.48093,17.51883 -4.883721,4.76883 -5.483599,4.99093 -14.267686,5.28254 -5.04329,0.16742 -10.026869,-0.0354 -11.074619,-0.45065 z m -0.635,-18.49982 c -1.397,-0.80815 -3.618014,-2.8373 -4.935585,-4.50924 -1.514478,-1.9218 -3.265684,-2.81235 -4.7612353,-2.42126 -5.0877973,1.33049 -4.5201133,-5.2806 1.0186626,-11.86306 10.9527017,-13.01655 26.0751797,-13.49926 35.7949487,-1.14257 2.43537,3.09607 2.432394,3.13675 -0.498121,6.81197 l -2.948122,3.6973 -3.898851,-4.17374 c -2.498844,-2.67502 -4.619814,-3.89708 -5.906645,-3.40327 -1.104286,0.42375 -2.246802,0.77046 -2.538922,0.77046 -1.51096,0 -0.206973,3.70969 2.008869,5.715 1.397,1.26427 2.54,3.31227 2.54,4.55112 0,2.97509 -3.429561,6.31015 -5.62329,5.46834 -0.956883,-0.36719 -2.117072,-0.0572 -2.578199,0.68896 -1.083758,1.75356 -4.4582,1.67 -7.67351,-0.19001 z"
+         id="path9880"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#b57426;stroke-width:1.26999998"
+         d="m 19.684999,290.11995 c -2.0955,-0.93535 -5.488547,-3.48473 -7.540105,-5.66529 l -3.7301061,-3.96467 h 3.4126061 c 1.876933,0 3.412605,-0.6858 3.412605,-1.524 0,-1.15097 0.358481,-1.16552 1.464559,-0.0594 0.892574,0.89258 2.256573,1.0453 3.4925,0.39105 1.115367,-0.59043 4.298738,-1.37532 7.074156,-1.7442 2.775417,-0.36888 5.2541,-1.29435 5.508184,-2.0566 1.027786,-3.08336 5.207604,-4.02046 9.056062,-2.03035 5.527124,2.85819 6.18387,2.5106 7.125922,-3.77145 1.187648,-7.91981 3.323936,-7.20693 2.534777,0.84584 -1.547226,15.78828 -17.839159,25.81562 -31.81116,19.57911 z m -1.27,-17.52655 c -1.397,-0.80815 -3.618014,-2.8373 -4.935585,-4.50924 -1.514478,-1.9218 -3.265684,-2.81235 -4.7612353,-2.42126 -3.4870622,0.91189 -4.037115,-1.09335 -1.6688926,-6.084 1.7656737,-3.72089 6.6771499,-8.82059 14.1696109,-14.71265 1.936116,-1.52256 14.734444,-1.60015 13.797193,-0.0837 -0.381595,0.61743 0.946287,1.80196 2.95085,2.63228 2.004563,0.83031 5.097521,3.03976 6.873241,4.90989 l 3.228582,3.40022 -2.991608,3.72077 -2.991608,3.72078 -3.898851,-4.17374 c -2.498844,-2.67502 -4.619814,-3.89708 -5.906645,-3.40327 -1.104286,0.42375 -2.246802,0.77046 -2.538922,0.77046 -1.51096,0 -0.206973,3.70969 2.008869,5.715 1.397,1.26427 2.54,3.31227 2.54,4.55112 0,2.97509 -3.429561,6.31015 -5.62329,5.46834 -0.956883,-0.36719 -2.117072,-0.0572 -2.578199,0.68896 -1.083758,1.75356 -4.4582,1.67 -7.67351,-0.19001 z"
+         id="path9878"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#ae681a;stroke-width:1.26999998"
+         d="m 20.32065,290.49321 c -6.290813,-2.50849 -12.0236912,-9.89877 -6.259637,-8.06933 1.575615,0.50008 3.48603,0.0895 4.762499,-1.02356 1.172318,-1.02224 4.417487,-2.26303 7.211487,-2.75731 2.793999,-0.49429 6.327736,-2.05922 7.852747,-3.47762 l 2.772748,-2.57892 6.434752,2.90815 c 3.539113,1.59948 6.434751,3.13015 6.434751,3.40149 0,0.27135 -1.458254,2.40523 -3.240566,4.74196 -5.368698,7.03872 -17.486418,10.23751 -25.968781,6.85514 z m -0.846481,-17.98572 c -1.512956,-0.84838 -4.003368,-3.13962 -5.534247,-5.09166 -1.799291,-2.2943 -3.745389,-3.41687 -5.5041705,-3.175 -5.3252821,0.73234 -0.969546,-8.29099 7.1369445,-14.78488 2.261767,-1.81184 4.802557,-3.85713 5.646201,-4.54511 1.682135,-1.37173 15.284678,-1.77109 13.922328,-0.40874 -0.463159,0.46316 0.904857,1.9045 3.040035,3.20298 3.011841,1.83161 3.694486,2.95212 3.045065,4.99826 -0.710282,2.23791 -0.433346,2.50926 1.828298,1.79144 3.083488,-0.97866 3.184584,-0.66384 1.191521,3.71045 -1.842739,4.04437 -2.776763,4.05472 -5.394361,0.0598 -1.525546,-2.32828 -3.201493,-3.175 -6.284395,-3.175 -2.312232,0 -4.569997,0.36594 -5.017258,0.8132 -0.834433,0.83444 2.710229,8.08741 4.642327,9.49898 2.153147,1.57307 0.156596,5.56416 -2.995433,5.98786 -1.739114,0.23377 -3.70495,0.94815 -4.368525,1.5875 -1.518867,1.46343 -1.978162,1.42311 -5.35433,-0.47004 z m 30.156091,-0.37833 c 0.06094,-1.47948 0.361875,-1.78042 0.767292,-0.76729 0.366866,0.91678 0.321747,2.01215 -0.100266,2.43416 -0.422011,0.42201 -0.722174,-0.32808 -0.66703,-1.66687 z"
+         id="path9876"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#a45b16;stroke-width:1.26999998"
+         d="m 19.367499,290.02659 c -2.270125,-0.98669 -4.1275,-2.21893 -4.1275,-2.7383 0,-0.51937 1.535673,-2.54721 3.412606,-4.50631 2.514685,-2.62476 4.628483,-3.56199 8.03362,-3.56199 3.286278,0 5.482043,-0.91652 7.60199,-3.1731 2.558724,-2.72364 3.264685,-2.92972 4.983879,-1.45492 1.101598,0.94501 3.802471,2.48683 6.001942,3.42627 l 3.999038,1.70807 -3.112105,4.08018 c -5.55471,7.2826 -17.805536,10.12663 -26.79347,6.2201 z m 0.3175,-18.65252 c -3.516922,-1.96432 -3.530127,-1.98202 -2.600945,-3.48547 0.911826,-1.47536 7.045945,1.88289 7.045945,3.85745 0,1.47737 -1.333773,1.36575 -4.445,-0.37198 z m 8.572499,-0.68629 c -2.562029,-0.91903 -1.819561,-4.16779 0.9525,-4.16779 1.397,0 2.54,0.82352 2.54,1.83005 0,2.20881 -1.343995,3.10843 -3.4925,2.33774 z M 6.1057371,260.96689 c 0.5892269,-2.25321 9.3400339,-11.68562 14.5834899,-15.7194 2.38987,-1.83852 15.398454,-2.19876 14.284012,-0.39555 -0.405165,0.65557 0.275482,1.78119 1.512548,2.50138 1.237065,0.72019 1.552772,1.32681 0.70157,1.34805 -0.851204,0.0212 -1.227866,0.87194 -0.83703,1.89044 0.976141,2.54379 -5.252399,4.05203 -7.845646,1.89982 -1.471196,-1.22098 -2.29894,-1.25538 -3.412507,-0.14182 -0.808597,0.8086 -2.498875,1.47018 -3.756175,1.47018 -3.307514,0 -2.815552,3.55779 0.558526,4.03917 3.675355,0.52437 4.713913,4.85083 1.164428,4.85083 -1.390007,0 -2.817153,-0.71437 -3.171435,-1.5875 -0.484361,-1.19371 -0.9718,-1.11495 -1.965025,0.3175 -0.820842,1.18384 -3.431319,1.92812 -6.895732,1.96607 -4.9335629,0.0541 -5.4996403,-0.22654 -4.9210239,-2.43917 z m 34.5342609,-3.3369 c 0,-0.6985 0.5715,-1.27 1.27,-1.27 0.6985,0 1.27,0.5715 1.27,1.27 0,0.6985 -0.5715,1.27 -1.27,1.27 -0.6985,0 -1.27,-0.5715 -1.27,-1.27 z"
+         id="path9874"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#955729;stroke-width:1.26999998"
+         d="m 19.267861,289.98469 -4.227136,-1.83587 4.192671,-3.82942 c 3.113033,-2.84331 5.427917,-3.82941 8.989636,-3.82941 2.638332,0 4.796966,-0.46227 4.796966,-1.02726 0,-0.565 0.784155,-2.09967 1.742567,-3.41037 1.670679,-2.28479 1.950531,-2.21839 6.783707,1.60956 4.005033,3.17204 5.384512,3.67721 6.711789,2.45786 0.918856,-0.84413 0.122682,0.55809 -1.769274,3.11605 -5.808594,7.85333 -17.820657,10.83147 -27.220926,6.74886 z M 6.0196987,261.2959 c 0.6585267,-2.51821 9.1327323,-11.78896 14.6695283,-16.04841 2.574807,-1.9808 15.392731,-2.1895 14.182261,-0.23092 -0.461127,0.74612 -1.637854,1.04981 -2.614952,0.67486 -2.482778,-0.95273 -2.217487,0.53777 0.445962,2.50555 1.758075,1.29889 1.897039,1.97207 0.665009,3.2215 -1.239944,1.25746 -2.088765,1.0987 -4.163255,-0.77869 -2.543281,-2.30164 -2.719108,-2.29318 -7.332509,0.3525 -3.479128,1.99521 -4.618483,3.41655 -4.316585,5.38492 0.595723,3.8841 -2.024496,6.45412 -5.651967,5.54369 -1.675397,-0.4205 -3.8510419,-0.0966 -4.8347671,0.71986 -1.4699517,1.21995 -1.6567832,0.98036 -1.0487252,-1.34486 z"
+         id="path9872"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#924a13;stroke-width:1.26999998"
+         d="m 19.222535,289.28679 c -2.637997,-1.05627 -2.792161,-1.47045 -1.27,-3.412 3.196529,-4.07725 5.514699,-5.30377 10.101109,-5.34441 3.609385,-0.032 4.859881,-0.70153 6.005273,-3.21539 1.81811,-3.99032 2.401336,-3.94544 7.851081,0.60407 3.683955,3.07541 4.211274,4.1024 3.079892,5.99833 -3.37304,5.65242 -17.63715,8.62477 -25.767355,5.3694 z m -8.745036,-27.47308 c -3.6081015,-0.69495 -3.5791971,-1.09277 0.464604,-6.39447 6.342602,-8.31558 15.847922,-11.58438 21.51766,-7.39975 1.950833,1.43984 2.140245,2.14909 0.907744,3.39899 -1.239944,1.25746 -2.088765,1.0987 -4.163255,-0.77869 -2.543281,-2.30164 -2.719108,-2.29318 -7.332509,0.3525 -3.479128,1.99521 -4.618483,3.41655 -4.316585,5.38492 0.395963,2.58168 -1.789512,6.51434 -3.423164,6.15983 -0.438347,-0.0951 -2.08287,-0.42062 -3.654495,-0.72333 z m 14.627744,-17.80413 c 2.282634,-0.28265 5.711634,-0.27682 7.62,0.013 1.908365,0.28978 0.04076,0.52104 -4.150245,0.51392 -4.190999,-0.007 -5.752389,-0.24423 -3.469755,-0.52688 z"
+         id="path9870"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#853c10;stroke-width:1.26999998"
+         d="m 23.473071,289.34451 c -0.762809,-1.23425 4.012815,-6.1817 6.054427,-6.27227 0.523875,-0.0232 0.9525,0.98645 0.9525,2.24375 0,1.2573 0.6858,2.9718 1.524,3.81 1.185333,1.18533 0.496434,1.524 -3.100048,1.524 -2.543227,0 -4.987122,-0.58747 -5.430879,-1.30548 z m 14.653518,-3.10748 c -0.88418,-1.06537 -2.394873,-1.93704 -3.357096,-1.93704 -0.962222,0 -1.749495,-0.32332 -1.749495,-0.71849 0,-0.67524 2.467599,-5.77191 3.236838,-6.68549 0.644956,-0.76597 6.699113,3.71829 7.365875,5.45585 0.360795,0.94021 -0.366525,2.63485 -1.616266,3.76585 -2.031422,1.83841 -2.442644,1.85106 -3.879856,0.11933 z m -26.020157,-26 c -1.116634,-1.80675 0.307316,-3.87704 2.66664,-3.87704 1.112711,0 1.750126,0.89225 1.512549,2.11726 -0.494693,2.55075 -3.030435,3.6185 -4.179189,1.75978 z m 6.943567,-11.43868 c 0,-1.24949 4.919092,-3.1826 5.816075,-2.28562 0.299563,0.29956 -0.886507,1.19674 -2.635708,1.99373 -1.749202,0.79699 -3.180367,0.92834 -3.180367,0.29189 z m 4.1275,-4.7438 c 1.222375,-0.31944 3.222625,-0.31944 4.445,0 1.222374,0.31944 0.22225,0.5808 -2.2225,0.5808 -2.44475,0 -3.444875,-0.26136 -2.2225,-0.5808 z m 9.604374,0.008 c 0.916781,-0.36686 2.012156,-0.32174 2.434167,0.10027 0.42201,0.42201 -0.328084,0.72217 -1.666875,0.66702 -1.479477,-0.0609 -1.780413,-0.36187 -0.767292,-0.76728 z"
+         id="path9868"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:#6c3610;stroke-width:1.26999998"
+         d="m 12.699999,258.89999 c 0,-0.6985 0.5715,-1.27 1.27,-1.27 0.6985,0 1.27,0.5715 1.27,1.27 0,0.6985 -0.5715,1.27 -1.27,1.27 -0.6985,0 -1.27,-0.5715 -1.27,-1.27 z"
+         id="path9864"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>
diff --git a/resources/images/example_1.png b/resources/images/example_1.png
new file mode 100644
index 0000000..6608834
--- /dev/null
+++ b/resources/images/example_1.png
Binary files differ
diff --git a/resources/images/example_2.png b/resources/images/example_2.png
new file mode 100644
index 0000000..85428dc
--- /dev/null
+++ b/resources/images/example_2.png
Binary files differ
diff --git a/resources/images/example_3.png b/resources/images/example_3.png
new file mode 100644
index 0000000..188cae5
--- /dev/null
+++ b/resources/images/example_3.png
Binary files differ
diff --git a/resources/images/example_4.png b/resources/images/example_4.png
new file mode 100644
index 0000000..9a16234
--- /dev/null
+++ b/resources/images/example_4.png
Binary files differ
diff --git a/resources/images/example_5.png b/resources/images/example_5.png
new file mode 100644
index 0000000..a8e58cc
--- /dev/null
+++ b/resources/images/example_5.png
Binary files differ
diff --git a/resources/images/example_6.png b/resources/images/example_6.png
new file mode 100644
index 0000000..2eea6a7
--- /dev/null
+++ b/resources/images/example_6.png
Binary files differ
diff --git a/resources/images/explosion_sprites.png b/resources/images/explosion_sprites.png
new file mode 100644
index 0000000..addc212
--- /dev/null
+++ b/resources/images/explosion_sprites.png
Binary files differ
diff --git a/resources/particles/default.json b/resources/particles/default.json
new file mode 100644
index 0000000..6c5f5cc
--- /dev/null
+++ b/resources/particles/default.json
@@ -0,0 +1,107 @@
+{
+   "MaxCount": 4096,
+   "Duration": 1,
+   "Rate": 1000,
+   "Life": {
+      "Input": {
+         "Source": "Age",
+         "TileMode": "Repeat",
+         "Left": 0,
+         "Right": 1
+      },
+      "XValues": [],
+      "Segments": [
+         {
+            "Type": "Constant",
+            "Ranged": true,
+            "Bidirectional": false,
+            "A0": 1,
+            "A1": 3
+         }
+      ]
+   },
+   "Drawable": {
+      "Type": "SkCircleDrawable",
+      "Radius": 1
+   },
+   "Spawn": [
+      {
+         "Type": "SkLinearVelocityAffector",
+         "Enabled": true,
+         "Force": false,
+         "Frame": "World",
+         "Angle": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": true,
+                  "Bidirectional": false,
+                  "A0": -30,
+                  "A1": 30
+               }
+            ]
+         },
+         "Strength": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": true,
+                  "Bidirectional": false,
+                  "A0": 10,
+                  "A1": 30
+               }
+            ]
+         }
+      },
+      {
+         "Type": "SkPositionOnTextAffector",
+         "Enabled": true,
+         "Input": {
+            "Source": "Random",
+            "TileMode": "Clamp",
+            "Left": 0,
+            "Right": 1
+         },
+         "SetHeading": true,
+         "Text": "SKIA",
+         "FontSize": 96
+      }
+   ],
+   "Update": [
+      {
+         "Type": "SkColorAffector",
+         "Enabled": true,
+         "Curve": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Linear",
+                  "Ranged": false,
+                  "A0": [ 1, 0.196078, 0.0784314, 1 ],
+                  "D0": [ 1, 0.784314, 0.0784314, 1 ]
+               }
+            ]
+         }
+      }
+   ]
+}
\ No newline at end of file
diff --git a/resources/particles/explosion.json b/resources/particles/explosion.json
new file mode 100644
index 0000000..4261672
--- /dev/null
+++ b/resources/particles/explosion.json
@@ -0,0 +1,118 @@
+{
+   "MaxCount": 32,
+   "Duration": 1,
+   "Rate": 8,
+   "Life": {
+      "Input": {
+         "Source": "Age",
+         "TileMode": "Repeat",
+         "Left": 0,
+         "Right": 1
+      },
+      "XValues": [],
+      "Segments": [
+         {
+            "Type": "Constant",
+            "Ranged": true,
+            "Bidirectional": false,
+            "A0": 1,
+            "A1": 3
+         }
+      ]
+   },
+   "Drawable": {
+      "Type": "SkImageDrawable",
+      "Path": "resources/images/explosion_sprites.png",
+      "Columns": 4,
+      "Rows": 4
+   },
+   "Spawn": [
+      {
+         "Type": "SkPositionInCircleAffector",
+         "Enabled": true,
+         "SetHeading": true,
+         "X": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": false,
+                  "Bidirectional": false,
+                  "A0": 0
+               }
+            ]
+         },
+         "Y": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": false,
+                  "Bidirectional": false,
+                  "A0": 0
+               }
+            ]
+         },
+         "Radius": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": false,
+                  "Bidirectional": false,
+                  "A0": 60
+               }
+            ]
+         }
+      }
+   ],
+   "Update": [
+      {
+         "Type": "SkPointForceAffector",
+         "Enabled": true,
+         "Point": { "x": 200, "y": 200 },
+         "Constant": 0,
+         "InvSquare": -50
+      },
+      {
+         "Type": "SkFrameAffector",
+         "Enabled": true,
+         "Curve": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Linear",
+                  "Ranged": false,
+                  "Bidirectional": false,
+                  "A0": 0,
+                  "D0": 1
+               }
+            ]
+         }
+      }
+   ]
+}
\ No newline at end of file
diff --git a/resources/particles/penguin_cannon.json b/resources/particles/penguin_cannon.json
new file mode 100644
index 0000000..2f612b6
--- /dev/null
+++ b/resources/particles/penguin_cannon.json
@@ -0,0 +1,136 @@
+{
+   "MaxCount": 32,
+   "Duration": 1,
+   "Rate": 0.5,
+   "Life": {
+      "Input": {
+         "Source": "Age",
+         "TileMode": "Repeat",
+         "Left": 0,
+         "Right": 1
+      },
+      "XValues": [],
+      "Segments": [
+         {
+            "Type": "Constant",
+            "Ranged": false,
+            "Bidirectional": false,
+            "A0": 20
+         }
+      ]
+   },
+   "Drawable": {
+      "Type": "SkImageDrawable",
+      "Path": "resources/images/baby_tux.png",
+      "Columns": 1,
+      "Rows": 1
+   },
+   "Spawn": [
+      {
+         "Type": "SkLinearVelocityAffector",
+         "Enabled": true,
+         "Force": false,
+         "Frame": "World",
+         "Angle": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": true,
+                  "Bidirectional": false,
+                  "A0": 10,
+                  "A1": 70
+               }
+            ]
+         },
+         "Strength": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": true,
+                  "Bidirectional": false,
+                  "A0": 140,
+                  "A1": 200
+               }
+            ]
+         }
+      }
+   ],
+   "Update": [
+      {
+         "Type": "SkLinearVelocityAffector",
+         "Enabled": true,
+         "Force": true,
+         "Frame": "World",
+         "Angle": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": false,
+                  "Bidirectional": false,
+                  "A0": 180
+               }
+            ]
+         },
+         "Strength": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": false,
+                  "Bidirectional": false,
+                  "A0": 50
+               }
+            ]
+         }
+      },
+      {
+         "Type": "SkOrientationAffector",
+         "Enabled": true,
+         "Frame": "Velocity",
+         "Angle": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": false,
+                  "Bidirectional": false,
+                  "A0": 0
+               }
+            ]
+         }
+      }
+   ]
+}
\ No newline at end of file
diff --git a/resources/particles/snowfall.json b/resources/particles/snowfall.json
new file mode 100644
index 0000000..1059601
--- /dev/null
+++ b/resources/particles/snowfall.json
@@ -0,0 +1,112 @@
+{
+   "MaxCount": 4096,
+   "Duration": 1,
+   "Rate": 30,
+   "Life": {
+      "Input": {
+         "Source": "Age",
+         "TileMode": "Repeat",
+         "Left": 0,
+         "Right": 1
+      },
+      "XValues": [],
+      "Segments": [
+         {
+            "Type": "Constant",
+            "Ranged": false,
+            "Bidirectional": false,
+            "A0": 10
+         }
+      ]
+   },
+   "Drawable": {
+      "Type": "SkCircleDrawable",
+      "Radius": 1
+   },
+   "Spawn": [
+      {
+         "Type": "SkLinearVelocityAffector",
+         "Enabled": true,
+         "Force": false,
+         "Frame": "World",
+         "Angle": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": true,
+                  "Bidirectional": false,
+                  "A0": 170,
+                  "A1": 190
+               }
+            ]
+         },
+         "Strength": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": true,
+                  "Bidirectional": false,
+                  "A0": 10,
+                  "A1": 30
+               }
+            ]
+         }
+      },
+      {
+         "Type": "SkPositionOnPathAffector",
+         "Enabled": true,
+         "Input": {
+            "Source": "Random",
+            "TileMode": "Clamp",
+            "Left": 0,
+            "Right": 1
+         },
+         "SetHeading": false,
+         "Path": "h500"
+      }
+   ],
+   "Update": [
+      {
+         "Type": "SkSizeAffector",
+         "Enabled": true,
+         "Curve": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Cubic",
+                  "Ranged": true,
+                  "Bidirectional": false,
+                  "A0": 10,
+                  "B0": 10,
+                  "C0": 10,
+                  "D0": 0,
+                  "A1": 10,
+                  "B1": 0,
+                  "C1": 0,
+                  "D1": 0
+               }
+            ]
+         }
+      }
+   ]
+}
\ No newline at end of file
diff --git a/resources/particles/spiral.json b/resources/particles/spiral.json
new file mode 100644
index 0000000..9fda70d
--- /dev/null
+++ b/resources/particles/spiral.json
@@ -0,0 +1,118 @@
+{
+   "MaxCount": 800,
+   "Duration": 4,
+   "Rate": 120,
+   "Life": {
+      "Input": {
+         "Source": "Age",
+         "TileMode": "Repeat",
+         "Left": 0,
+         "Right": 1
+      },
+      "XValues": [],
+      "Segments": [
+         {
+            "Type": "Constant",
+            "Ranged": true,
+            "Bidirectional": false,
+            "A0": 2,
+            "A1": 3
+         }
+      ]
+   },
+   "Drawable": {
+      "Type": "SkCircleDrawable",
+      "Radius": 2
+   },
+   "Spawn": [
+      {
+         "Type": "SkLinearVelocityAffector",
+         "Enabled": true,
+         "Force": false,
+         "Frame": "World",
+         "Angle": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Linear",
+                  "Ranged": false,
+                  "Bidirectional": false,
+                  "A0": 0,
+                  "D0": 1080
+               }
+            ]
+         },
+         "Strength": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": true,
+                  "Bidirectional": false,
+                  "A0": 50,
+                  "A1": 60
+               }
+            ]
+         }
+      }
+   ],
+   "Update": [
+      {
+         "Type": "SkSizeAffector",
+         "Enabled": true,
+         "Curve": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Linear",
+                  "Ranged": false,
+                  "Bidirectional": false,
+                  "A0": 0.5,
+                  "D0": 2
+               }
+            ]
+         }
+      },
+      {
+         "Type": "SkColorAffector",
+         "Enabled": true,
+         "Curve": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Linear",
+                  "Ranged": true,
+                  "A0": [ 0.0999616, 0.140218, 0.784314, 1 ],
+                  "D0": [ 0.523837, 0.886396, 0.980392, 1 ],
+                  "A1": [ 0.378665, 0.121107, 0.705882, 1 ],
+                  "D1": [ 0.934257, 0.229599, 0.955882, 1 ]
+               }
+            ]
+         }
+      }
+   ]
+}
\ No newline at end of file
diff --git a/resources/particles/swirl.json b/resources/particles/swirl.json
new file mode 100644
index 0000000..231a92d
--- /dev/null
+++ b/resources/particles/swirl.json
@@ -0,0 +1,173 @@
+{
+   "MaxCount": 4096,
+   "Duration": 1,
+   "Rate": 400,
+   "Life": {
+      "Input": {
+         "Source": "Age",
+         "TileMode": "Repeat",
+         "Left": 0,
+         "Right": 1
+      },
+      "XValues": [],
+      "Segments": [
+         {
+            "Type": "Constant",
+            "Ranged": true,
+            "Bidirectional": false,
+            "A0": 1,
+            "A1": 3
+         }
+      ]
+   },
+   "Drawable": {
+      "Type": "SkCircleDrawable",
+      "Radius": 2
+   },
+   "Spawn": [
+      {
+         "Type": "SkPositionOnPathAffector",
+         "Enabled": true,
+         "Input": {
+            "Source": "Random",
+            "TileMode": "Clamp",
+            "Left": 0,
+            "Right": 1
+         },
+         "SetHeading": true,
+         "Path": "h50"
+      },
+      {
+         "Type": "SkLinearVelocityAffector",
+         "Enabled": true,
+         "Force": false,
+         "Frame": "World",
+         "Angle": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": true,
+                  "Bidirectional": false,
+                  "A0": -10,
+                  "A1": 10
+               }
+            ]
+         },
+         "Strength": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": true,
+                  "Bidirectional": false,
+                  "A0": 50,
+                  "A1": 60
+               }
+            ]
+         }
+      }
+   ],
+   "Update": [
+      {
+         "Type": "SkLinearVelocityAffector",
+         "Enabled": true,
+         "Force": true,
+         "Frame": "World",
+         "Angle": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": false,
+                  "Bidirectional": false,
+                  "A0": 90
+               }
+            ]
+         },
+         "Strength": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Cubic",
+                  "Ranged": false,
+                  "Bidirectional": true,
+                  "A0": 180,
+                  "B0": -90,
+                  "C0": -120,
+                  "D0": -200
+               }
+            ]
+         }
+      },
+      {
+         "Type": "SkSizeAffector",
+         "Enabled": true,
+         "Curve": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Linear",
+                  "Ranged": false,
+                  "Bidirectional": false,
+                  "A0": 3,
+                  "D0": 1.5
+               }
+            ]
+         }
+      },
+      {
+         "Type": "SkColorAffector",
+         "Enabled": true,
+         "Curve": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Linear",
+                  "Ranged": true,
+                  "A0": [ 0.0999616, 0.140218, 0.784314, 1 ],
+                  "D0": [ 0.523837, 0.886396, 0.980392, 1 ],
+                  "A1": [ 0.378665, 0.121107, 0.705882, 1 ],
+                  "D1": [ 0.934257, 0.229599, 0.955882, 1 ]
+               }
+            ]
+         }
+      }
+   ]
+}
\ No newline at end of file
diff --git a/resources/particles/warp.json b/resources/particles/warp.json
new file mode 100644
index 0000000..cc079fd
--- /dev/null
+++ b/resources/particles/warp.json
@@ -0,0 +1,115 @@
+{
+   "MaxCount": 4096,
+   "Duration": 1,
+   "Rate": 90,
+   "Life": {
+      "Input": {
+         "Source": "Age",
+         "TileMode": "Repeat",
+         "Left": 0,
+         "Right": 1
+      },
+      "XValues": [],
+      "Segments": [
+         {
+            "Type": "Constant",
+            "Ranged": false,
+            "Bidirectional": false,
+            "A0": 30
+         }
+      ]
+   },
+   "Drawable": {
+      "Type": "SkCircleDrawable",
+      "Radius": 2
+   },
+   "Spawn": [
+      {
+         "Type": "SkPositionInCircleAffector",
+         "Enabled": true,
+         "SetHeading": true,
+         "X": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": false,
+                  "Bidirectional": false,
+                  "A0": 0
+               }
+            ]
+         },
+         "Y": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": false,
+                  "Bidirectional": false,
+                  "A0": 0
+               }
+            ]
+         },
+         "Radius": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Constant",
+                  "Ranged": false,
+                  "Bidirectional": false,
+                  "A0": 40
+               }
+            ]
+         }
+      }
+   ],
+   "Update": [
+      {
+         "Type": "SkPointForceAffector",
+         "Enabled": true,
+         "Point": { "x": 0, "y": 0 },
+         "Constant": -10,
+         "InvSquare": 0
+      },
+      {
+         "Type": "SkSizeAffector",
+         "Enabled": true,
+         "Curve": {
+            "Input": {
+               "Source": "Age",
+               "TileMode": "Repeat",
+               "Left": 0,
+               "Right": 1
+            },
+            "XValues": [],
+            "Segments": [
+               {
+                  "Type": "Linear",
+                  "Ranged": false,
+                  "Bidirectional": false,
+                  "A0": 0.25,
+                  "D0": 3
+               }
+            ]
+         }
+      }
+   ]
+}
\ No newline at end of file
diff --git a/resources/text/arabic.txt b/resources/text/arabic.txt
new file mode 100644
index 0000000..eed93d8
--- /dev/null
+++ b/resources/text/arabic.txt
@@ -0,0 +1,3 @@
+المادة 1 يولد جميع الناس أحرارًا متساوين في الكرامة والحقوق. وقد وهبوا عقلاً وضميرًا وعليهم أن يعامل بعضهم بعضًا بروح الإخاء.
+
+المادة 2 لكل إنسان حق التمتع بكافة الحقوق والحريات الواردة في هذا الإعلان، دون أي تمييز، كالتمييز بسبب العنصر أو اللون أو الجنس أو اللغة أو الدين أو الرأي السياسي أو أي رأي آخر، أو الأصل الوطني أو الإجتماعي أو الثروة أو الميلاد أو أي وضع آخر، دون أية تفرقة بين الرجال والنساء. وفضلاً عما تقدم فلن يكون هناك أي تمييز أساسه الوضع السياسي أو القانوني أو الدولي لبلد أو البقعة التي ينتمي إليها الفرد سواء كان هذا البلد أو تلك البقعة مستقلاً أو تحت الوصاية أو غير متمتع بالحكم الذاتي أو كانت سيادته خاضعة لأي قيد من القيود.
diff --git a/resources/text/armenian.txt b/resources/text/armenian.txt
new file mode 100644
index 0000000..3c44e7a
--- /dev/null
+++ b/resources/text/armenian.txt
@@ -0,0 +1,3 @@
+Հոդված 1 Բոլոր մարդիկ ծնվում են ազատ ու հավասար իրենց արժանապատվությամբ ու իրավունքներով։ Նրանք ունեն բանականություն ու խիղճ և միմյանց պետք է եղբայրաբար վերաբերվեն։
+
+Հոդված 2 Ամեն ոք ունի այս Հռչակագրում բերված բոլոր իրավունքներն ու ազատությունները առանց որևէ խտրության՝ հիմնված ցեղային, մաշկի գույնի, սեռի, լեզվի, կրոնի, քաղաքական կամ այլ համոզմունքների, ազգային կամ սոցիալական ծագման, ունեցվածքի, դասային պատկանելության կամ որևէ այլ կարգավիճակի վրա։ Ավելին, ոչ մի խտրականություն չպետք է լինի հիմնված երկրի կամ տարածքի, քաղաքական, իրավական, կամ միջազգային կարգավիճակի վրա, լինի դա անկախ, խնամարկյալ, ոչինքնակառավարվող կամ ինքնիշխանության որևէ այլ սահմանափակումով պետական կազմավորում, որին պատկանում է մարդը։
diff --git a/resources/text/balinese.txt b/resources/text/balinese.txt
new file mode 100644
index 0000000..adfea81
--- /dev/null
+++ b/resources/text/balinese.txt
@@ -0,0 +1 @@
+ᬫᬓᬲᬫᬶ​ᬫᬦᬸᬲᬦᬾ​ᬓᭂᬫ᭄ᬩᬲᬶᬦ᭄ᬫᬳᬃᬤᬶᬓ​ᬮᬦ᭄ᬧᬢᭂᬄ​ᬲᬚ᭄ᬭᭀᬦᬶᬂ​ᬓᬳᬦᬦ᭄ᬮᬦ᭄ᬓᬸᬲ᭟​ᬳᬶᬧᬸᬦ᭄ᬓᬵᬦᬸᬕ᭄ᬭᬳᬶᬦ᭄ᬯᬶᬯᬾᬓ​ᬮᬦ᭄ᬩᬸᬤ᭄ᬥᬶ᭟​ᬧᬦ᭄ᬢᬭᬦᬶᬂ​ᬫᬦᬸᬲ​ᬫᬗ᭄ᬤᬦᬾ​ᬧᬭᬲ᭄ᬧᬭᭀᬲ᭄ᬫᬲᭂᬫᭂᬢᭀᬦᬦ᭄᭟
diff --git a/resources/text/bengali.txt b/resources/text/bengali.txt
new file mode 100644
index 0000000..1787966
--- /dev/null
+++ b/resources/text/bengali.txt
@@ -0,0 +1,3 @@
+ধারা ১ সমস্ত মানুষ স্বাধীনভাবে সমান মর্যাদা এবং অধিকার নিয়ে জন্মগ্রহণ করে। তাঁদের বিবেক এবং বুদ্ধি আছে; সুতরাং সকলেরই একে অপরের প্রতি ভ্রাতৃত্বসুলভ মনোভাব নিয়ে আচরণ করা উচিত।
+
+ধারা ২ এ ঘোষণায় উল্লেখিত স্বাধীনতা এবং অধিকারসমূহে গোত্র, ধর্ম, বর্ণ, শিক্ষা, ভাষা, রাজনৈতিক বা অন্যবিধ মতামত, জাতীয় বা সামাজিক উত্‍পত্তি, জন্ম, সম্পত্তি বা অন্য কোন মর্যাদা নির্বিশেষে প্রত্যেকের‌ই সমান অধিকার থাকবে। কোন দেশ বা ভূখণ্ডের রাজনৈতিক, সীমানাগত বা আন্তর্জাতিক মর্যাদার ভিত্তিতে তার কোন অধিবাসীর প্রতি কোনরূপ বৈষম্য করা হবেনা; সে দেশ বা ভূখণ্ড স্বাধীন‌ই হোক, হোক অছিভূক্ত, অস্বায়ত্বশাসিত কিংবা সার্বভৌমত্বের অন্য কোন সীমাবদ্ধতায় বিরাজমান।
diff --git a/resources/text/buginese.txt b/resources/text/buginese.txt
new file mode 100644
index 0000000..8d76006
--- /dev/null
+++ b/resources/text/buginese.txt
@@ -0,0 +1 @@
+ᨔᨗᨔᨗᨊᨗᨊ ᨑᨘᨄ ᨈᨕᨘ ᨑᨗ ᨍᨍᨗᨕᨂᨗ ᨑᨗᨒᨗᨊᨚᨕᨙ ᨊᨄᨘᨊᨕᨗ ᨆᨊᨙᨂᨗ ᨑᨗᨕᨔᨙᨂᨙ ᨕᨒᨙᨅᨗᨑᨙ᨞ ᨊᨄᨘᨊᨕᨗ ᨑᨗᨕᨔᨙᨂᨙ ᨕᨀᨒᨙ᨞ ᨊᨄᨘᨊᨕᨗ ᨑᨗᨕᨔᨙᨂᨙ ᨕᨈᨗ ᨆᨑᨙᨊᨗ ᨊ ᨔᨗᨅᨚᨒᨙ ᨅᨚᨒᨙᨊ ᨄᨉ ᨔᨗᨄᨀᨈᨕᨘ ᨄᨉ ᨆᨔᨒᨔᨘᨑᨙ᨞
diff --git a/resources/text/cherokee.txt b/resources/text/cherokee.txt
new file mode 100644
index 0000000..316a4bc
--- /dev/null
+++ b/resources/text/cherokee.txt
@@ -0,0 +1,3 @@
+Ꭰꮿꮩꮈ 1 Ꮒꭶꮣ ꭰꮒᏼꮻ ꭴꮎꮥꮕꭲ ꭴꮎꮪꮣꮄꮣ ꭰꮄ ꭱꮷꮃꭽꮙ ꮎꭲ ꭰꮲꮙꮩꮧ ꭰꮄ ꭴꮒꮂ ꭲᏻꮎꮫꮧꭲ. Ꮎꮝꭹꮎꮓ ꭴꮅꮝꭺꮈꮤꮕꭹ ꭴꮰꮿꮝꮧ ꮕᏸꮅꮫꭹ ꭰꮄ ꭰꮣꮕꮦꮯꮣꮝꮧ ꭰꮄ ꭱꮅꮝꮧ ꮟᏼꮻꭽ ꮒꮪꮎꮣꮫꮎꮥꭼꭹ ꮎ ꮧꮎꮣꮕꮯ ꭰꮣꮕꮩ ꭼꮧ.
+
+Ꭰꮿꮩꮈ 2 Ꮒꭶꮫ ꭰꮒᏼꮻ ꭴꮎꮣꮒꮬ ꮎꭲ ꮒꭶꮣ ꭴꮒꮂ ꭲᏻꮎꮫꮑꮧꭲ ꭰꮄ ꮩꭿ ꭰꮥꮧꭲ ꮎꭲ ꮥꭶꭷꮕꭹ ꭿꭰ ꮧꭶꮓꮳꮃꮕꭲ, ꭴꮎꮴꮅꮫ ꮔꮎꮰꮿꮝꮫꮎ ꮎꭲ ꮒꭶꭵꮙ ꮷꮣꮄꮕꮣ, ꮥꭷꮑꭲꮝꮤꮕꭿ ꮷꮎꮣꮄꮕꮣ ꭰꮒᏼꮻ, ꮧꭸꭶꭶꮕꮧꭲ, ꭰꭸꮿ ꭰꮄ ꭰꮝꭶꮿ, ꭶꮼꮒꭿꮝꮧ, ꮷꮎꮑꮅꮧ, ꮧꮎꮩꭹꮿꮝꭹ ꭰꮄ ꮠꭲ ꮎꮒꮅꮝꭼꭹ, ꭰᏸꮅ ꭴꮎꮩꮲꭿ ꭰꮄ ᏼꮻ ꮒꮩꮣᏻꮎꮣꮄꮕꭹ, ꮔꮕꮏꮕ, ꭴꮥꮕ ꭰꮄ ꮠꭲ ꮔꮝꮧꮣꮕꭲ. Ꭴꮧꮧꮲꭲꭸꮝꮩꮧ, ꮭ ꮔꮎꮰꮿꮝꮫꮎ ꭴꮩꭿᏻꮢꮎ ꮎꮝꭹꮓ ꮧꮎꮩꭹꮿꮝꭹ ꮒꮣᏻꮅꮝꮩꮤꮕ ꮎꮝꭹ ꭴꮩꮲꮕꭲ, ꭲᏻꮎꮫꮑꮅꮣꮝꮧ ꭴꮒꮂꭹ ꭰꮄ ꭰᏸꮅ ꮪꮎꮩꮲꮢ ꮔꮝꮧꮣꮕ ꮎꮝꭹ ꮒꭼꮎꮫꭲ ꭰꮄ ꮝꭶꮪꭹ ꮎꮝꭹꮓ ꭰꮒᏼꮻ ꭰꮎꮑꮈꭹ, ꭲᏻꮓꮝꮚ ꮎꮝꭹꮎꭲ ꭴꮎꮣꮴꮅꮣ, ꭶꭸꭶꮕꮨ ꭸꮢꭲ, ꭼꮒꭼꭼ-ꭴꮹꮢ-ꭴꭶꮞꮝꮧꮥꭹ ꭽꮻꮒꮧꮲ ꮒꭶꭵ ꮠꭲ ꮕꮒᏺꭲꮝꮣꮑꮂꮎ ꮎꭲ ꭴꮒꮂ ꭴꮎꮣꮴꮅꭶꮿ.
diff --git a/resources/text/cyrillic.txt b/resources/text/cyrillic.txt
new file mode 100644
index 0000000..4637346
--- /dev/null
+++ b/resources/text/cyrillic.txt
@@ -0,0 +1,3 @@
+Статья 1 Все люди рождаются свободными и равными в своем достоинстве и правах. Они наделены разумом и совестью и должны поступать в отношении друг друга в духе братства.
+
+Статья 2 Каждый человек должен обладать всеми правами и всеми свободами, провозглашенными настоящей Декларацией, без какого бы то ни было различия, как-то в отношении расы, цвета кожи, пола, языка, религии, политических или иных убеждений, национального или социального происхождения, имущественного, сословного или иного положения. Кроме того, не должно проводиться никакого различия на основе политического, правового или международного статуса страны или территории, к которой человек принадлежит, независимо от того, является ли эта территория независимой, подопечной, несамоуправляющейся или как-либо иначе ограниченной в своем суверенитете.
diff --git a/resources/text/devanagari.txt b/resources/text/devanagari.txt
new file mode 100644
index 0000000..82e59da
--- /dev/null
+++ b/resources/text/devanagari.txt
@@ -0,0 +1,3 @@
+अनुच्छेद १. सभी मनुष्यों को गौरव और अधिकारों के मामले में जन्मजात स्वतन्त्रता और समानता प्राप्त है । उन्हें बुद्धि और अन्तरात्मा की देन प्राप्त है और परस्पर उन्हें भाईचारे के भाव से बर्ताव करना चाहिए ।
+
+अनुच्छेद २. सभी को इस घोषणा में सन्निहित सभी अधिकारों और आज़ादियों को प्राप्त करने का हक़ है और इस मामले में जाति, वर्ण, लिंग, भाषा, धर्म, राजनीति या अन्य विचार-प्रणाली, किसी देश या समाज विशेष में जन्म, सम्पत्ति या किसी प्रकार की अन्य मर्यादा आदि के कारण भेदभाव का विचार न किया जाएगा । इसके अतिरिक्त, चाहे कोई देश या प्रदेश स्वतन्त्र हो, संरक्षित हो, या स्त्रशासन रहित हो या परिमित प्रभुसत्ता वाला हो, उस देश या प्रदेश की राजनैतिक, क्षेत्रीय या अन्तर्राष्ट्रीय स्थिति के आधार पर वहां के निवासियों के प्रति कोई फ़रक़ न रखा जाएगा ।
diff --git a/resources/text/emoji.txt b/resources/text/emoji.txt
new file mode 100644
index 0000000..6a70fe1
--- /dev/null
+++ b/resources/text/emoji.txt
@@ -0,0 +1,38 @@
+☀ BLACK SUN WITH RAYS  | ☕ HOT BEVERAGE  | ☘ SHAMROCK  | ☪ STAR AND CRESCENT  | ⚽ SOCCER BALL
+⛄ SNOWMAN WITHOUT SNOW  | ✔ HEAVY CHECK MARK  | ❤ HEAVY BLACK HEART  | 🌇 SUNSET OVER BUILDINGS  | 🌎 EARTH GLOBE AMERICAS
+🌭 HOT DOG  | 🌮 TACO  | 🌯 BURRITO  | 🌰 CHESTNUT  | 🌱 SEEDLING
+🌶 HOT PEPPER  | 🌽 EAR OF MAIZE  | 🍂 FALLEN LEAF  | 🍄 MUSHROOM  | 🍅 TOMATO
+🍆 AUBERGINE  | 🍇 GRAPES  | 🍈 MELON  | 🍉 WATERMELON  | 🍊 TANGERINE
+🍋 LEMON  | 🍌 BANANA  | 🍍 PINEAPPLE  | 🍎 RED APPLE  | 🍏 GREEN APPLE
+🍐 PEAR  | 🍑 PEACH  | 🍒 CHERRIES  | 🍓 STRAWBERRY  | 🍔 HAMBURGER
+🍕 SLICE OF PIZZA  | 🍖 MEAT ON BONE  | 🍗 POULTRY LEG  | 🍘 RICE CRACKER  | 🍙 RICE BALL
+🍚 COOKED RICE  | 🍛 CURRY AND RICE  | 🍜 STEAMING BOWL  | 🍝 SPAGHETTI  | 🍞 BREAD
+🍟 FRENCH FRIES  | 🍠 ROASTED SWEET POTATO  | 🍡 DANGO  | 🍢 ODEN  | 🍣 SUSHI
+🍤 FRIED SHRIMP  | 🍥 FISH CAKE WITH SWIRL DESIGN  | 🍦 SOFT ICE CREAM  | 🍧 SHAVED ICE  | 🍨 ICE CREAM
+🍩 DOUGHNUT  | 🍪 COOKIE  | 🍫 CHOCOLATE BAR  | 🍬 CANDY  | 🍭 LOLLIPOP
+🍮 CUSTARD  | 🍯 HONEY POT  | 🍰 SHORTCAKE  | 🍱 BENTO BOX
+🍲 POT OF FOOD  | 🍳 COOKING  | 🍴 FORK AND KNIFE  | 🍵 TEACUP WITHOUT HANDLE
+🍶 SAKE BOTTLE AND CUP  | 🍷 WINE GLASS  | 🍸 COCKTAIL GLASS  | 🍹 TROPICAL DRINK
+🍺 BEER MUG  | 🍻 CLINKING BEER MUGS  | 🍼 BABY BOTTLE  | 🍽 FORK AND KNIFE WITH PLATE
+🍾 BOTTLE WITH POPPING CORK  | 🍿 POPCORN  | 🎁 WRAPPED PRESENT  | 🎂 BIRTHDAY CAKE
+🎃 JACK-O-LANTERN  | 🎄 CHRISTMAS TREE  | 🎅 FATHER CHRISTMAS  | 🎉 PARTY POPPER
+🎊 CONFETTI BALL  | 🎌 CROSSED FLAGS  | 🎓 GRADUATION CAP
+🎥 MOVIE CAMERA  | 🎿 SKI AND SKI BOOT  | 🏈 AMERICAN FOOTBALL  | 🏊 SWIMMER
+🐉 DRAGON  | 🐦 BIRD  | 🐰 RABBIT FACE  | 🐻 BEAR FACE
+👍 THUMBS UP SIGN  | 👑 CROWN  | 👨 MAN  | 👩 WOMAN
+👰 BRIDE WITH VEIL  | 👼 BABY ANGEL  | 💕 TWO HEARTS  | 💘 HEART WITH ARROW
+💡 ELECTRIC LIGHT BULB  | 💩 PILE OF POO  | 💪 FLEXED BICEPS  | 💬 SPEECH BALLOON
+🔍 LEFT-POINTING MAGNIFYING GLASS  | 🔣 INPUT SYMBOL FOR SYMBOLS  | 🔥 FIRE  | 🕉 OM SYMBOL
+🕎 MENORAH WITH NINE BRANCHES  | 🕯 CANDLE  | 😀 GRINNING FACE  | 😂 FACE WITH TEARS OF JOY
+😃 SMILING FACE WITH OPEN MOUTH  | 😊 SMILING FACE WITH SMILING EYES  | 😍 SMILING FACE WITH HEART-SHAPED EYES  | 😎 SMILING FACE WITH SUNGLASSES
+😘 FACE THROWING A KISS  | 😳 FLUSHED FACE  | 🛍 SHOPPING BAGS  | 🤔 THINKING FACE
+🥂 CLINKING GLASSES  | 🥃 TUMBLER GLASS  | 🥄 SPOON  | 🥐 CROISSANT
+🥑 AVOCADO  | 🥒 CUCUMBER  | 🥓 BACON  | 🥔 POTATO
+🥕 CARROT  | 🥖 BAGUETTE BREAD  | 🥗 GREEN SALAD  | 🥘 SHALLOW PAN OF FOOD
+🥙 STUFFED FLATBREAD  | 🥛 GLASS OF MILK  | 🥜 PEANUTS  | 🥝 KIWIFRUIT
+🥞 PANCAKES  | 🥟 DUMPLING  | 🥠 FORTUNE COOKIE  | 🥡 TAKEOUT BOX
+🥢 CHOPSTICKS  | 🥣 BOWL WITH SPOON  | 🥤 CUP WITH STRAW  | 🥥 COCONUT
+🥦 BROCCOLI  | 🥧 PIE  | 🥨 PRETZEL  | 🥩 CUT OF MEAT
+🥪 SANDWICH  | 🥫 CANNED FOOD  | 🥬 LEAFY GREEN  | 🥭 MANGO
+🥮 MOON CAKE  | 🥯 BAGEL  | 🥰 SMILING FACE WITH SMILING EYES AND THREE HEARTS  | 🦃 TURKEY
+🦄 UNICORN FACE  | 🧀 CHEESE WEDGE  | 🧁 CUPCAKE  | 🧂 SALT SHAKER  |
diff --git a/resources/text/english.txt b/resources/text/english.txt
new file mode 100644
index 0000000..26fda33
--- /dev/null
+++ b/resources/text/english.txt
@@ -0,0 +1,3 @@
+Article 1.  All human beings are born free and equal in dignity and rights. They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood.
+
+Article 2.  Everyone is entitled to all the rights and freedoms set forth in this Declaration, without distinction of any kind, such as race, colour, sex, language, religion, political or other opinion, national or social origin, property, birth or other status. Furthermore, no distinction shall be made on the basis of the political, jurisdictional or international status of the country or territory to which a person belongs, whether it be independent, trust, non-self-governing or under any other limitation of sovereignty.
diff --git a/resources/text/ethiopic.txt b/resources/text/ethiopic.txt
new file mode 100644
index 0000000..2824444
--- /dev/null
+++ b/resources/text/ethiopic.txt
@@ -0,0 +1,3 @@
+አንቀጽ፡፩፤ የሰው፡ልጅ፡ሁሉ፡ሲወለድ፡ነጻና፡በክብርና፡በመብትም፡እኩልነት፡ያለው፡ነው።፡የተፈጥሮ፡ማስተዋልና፡ሕሊና፡ስላለው፡አንዱ፡ሌላውን፡በወንድማማችነት፡መንፈስ፡መመልከት፡ይገባዋል።
+
+አንቀጽ፡፪፤ እያንዳንዱ፡ሰው፡የዘር፡የቀለም፡የጾታ፡የቋንቋ፡የሃይማኖት፡የፖለቲካ፡ወይም፡የሌላ፡ዓይነት፡አስተሳሰብ፡የብሔራዊ፡ወይም፡የኀብረተሰብ፡ታሪክ፡የሀብት፡የትውልድ፡ወይም፡የሌላ፡ደረጃ፡ልዩነት፡ሳይኖሩ፡በዚሁ፡ውሳኔ፡የተዘረዘሩት፡መብቶችንና፡ነጻነቶች፡ሁሉ፡እንዲከበሩለት፡ይገባል። ከዚህም፡በተቀረ፡አንድ፡ሰው፡ከሚኖርበት፡አገር፡ወይም፡ግዛት፡የፖለቲካ፡የአገዛዝ፡ወይም፡የኢንተርናሽናል፡አቋም፡የተነሳ፡አገሩ፡ነጻም፡ሆነ፡በሞግዚትነት፡አስተዳደር፡ወይም፡እራሱን፡ችሎ፡የማይተዳደር፡አገር፡ተወላጅ፡ቢሆንም፡በማንኛውም፡ዓይነት፡ገደብ፡ያለው፡አገዛዝ፡ሥር፡ቢሆንም፡ልዩነት፡አይፈጸምበትም።
diff --git a/resources/text/greek.txt b/resources/text/greek.txt
new file mode 100644
index 0000000..c1201ef
--- /dev/null
+++ b/resources/text/greek.txt
@@ -0,0 +1,3 @@
+ΑΡΘΡΟ 1 Όλοι οι άνθρωποι γεννιούνται ελεύθεροι και ίσοι στην αξιοπρέπεια και τα δικαιώματα. Είναι προικισμένοι με λογική και συνείδηση, και οφείλουν να συμπεριφέρονται μεταξύ τους με πνεύμα αδελφοσύνης.
+
+ΑΡΘΡΟ 2 Κάθε άνθρωπος δικαιούται να επικαλείται όλα τα δικαιώματα και όλες τις ελευθερίες που προκηρύσσει η παρούσα Διακήρυξη, χωρίς καμία απολύτως διάκριση, ειδικότερα ως προς τη φυλή, το χρώμα, το φύλο, τη γλώσσα, τις θρησκείες, τις πολιτικές ή οποιεσδήποτε άλλες πεποιθήσεις, την εθνική ή κοινωνική καταγωγή, την περιουσία, τη γέννηση ή οποιαδήποτε άλλη κατάσταση. Δεν θα μπορεί ακόμα να γίνεται καμία διάκριση εξαιτίας του πολιτικού, νομικού ή διεθνούς καθεστώτος της χώρας από την οποία προέρχεται κανείς, είτε πρόκειται για χώρα ή εδαφική περιοχή ανεξάρτητη, υπό κηδεμονία ή υπεξουσία, ή που βρίσκεται υπό οποιονδήποτε άλλον περιορισμό κυριαρχίας.
diff --git a/resources/text/han-simplified.txt b/resources/text/han-simplified.txt
new file mode 100644
index 0000000..f6d752e
--- /dev/null
+++ b/resources/text/han-simplified.txt
@@ -0,0 +1,3 @@
+第一条 人人生而自由,在尊严和权利上一律平等。他们赋有理性和良心,并应以兄弟关系的精神相对待。
+
+第二条 人人有资格享有本宣言所载的一切权利和自由,不分种族、肤色、性别、语言、宗教、政治或其他见解、国籍或社会出身、财产、出生或其他身分等任何区别。并且不得因一人所属的国家或领土的政治的、行政的或者国际的地位之不同而有所区别,无论该领土是独立领土、托管领土、非自治领土或者处于其他任何主权受限制的情况之下。
diff --git a/resources/text/han-traditional.txt b/resources/text/han-traditional.txt
new file mode 100644
index 0000000..a01181a
--- /dev/null
+++ b/resources/text/han-traditional.txt
@@ -0,0 +1,3 @@
+第一條 人人生而自由,在尊嚴和權利上一律平等。他們賦有理性和良心,並應以兄弟關係的精神相對待。
+
+第二條 人人有資格享受本宣言所載的一切權利和自由,不分種族、膚色、性別、語言、宗教、政治或其他見解、國籍或社會出身、財產、出生或其他身分等任何區別。
diff --git a/resources/text/hangul.txt b/resources/text/hangul.txt
new file mode 100644
index 0000000..4cab553
--- /dev/null
+++ b/resources/text/hangul.txt
@@ -0,0 +1,3 @@
+제 1 조 모든 인간은 태어날 때부터 자유로우며 그 존엄과 권리에 있어 동등하다. 인간은 천부적으로 이성과 양심을 부여받았으며 서로 형제애의 정신으로 행동하여야 한다.
+
+제 2 조 모든 사람은 인종, 피부색, 성, 언어, 종교, 정치적 또는 기타의 견해, 민족적 또는 사회적 출신, 재산, 출생 또는 기타의 신분과 같은 어떠한 종류의 차별이 없이, 이 선언에 규정된 모든 권리와 자유를 향유할 자격이 있다. 더 나아가 개인이 속한 국가 또는 영토가 독립국, 신탁통치지역, 비자치지역이거나 또는 주권에 대한 여타의 제약을 받느냐에 관계없이, 그 국가 또는 영토의 정치적, 법적 또는 국제적 지위에 근거하여 차별이 있어서는 아니된다.
diff --git a/resources/text/hebrew.txt b/resources/text/hebrew.txt
new file mode 100644
index 0000000..c19552f
--- /dev/null
+++ b/resources/text/hebrew.txt
@@ -0,0 +1,3 @@
+סעיף א. כל בני אדם נולדו בני חורין ושווים בערכם ובזכויותיהם. כולם חוננו בתבונה ובמצפון, לפיכך חובה עליהם לנהוג איש ברעהו ברוח של אחוה.
+
+סעיף ב. כל אדם זכאי לזכויות ולחרויות שנקבעו בהכרזש זו ללא הפליה כלשהיא מטעמי גזע, צבע, מין, לשון, דח, דעה פוליטית או דעה בבעיות אחרות, בגלל מוצא לאומי או חברתי, קנין, לידה או מעמד אחר. גדולה מזו, לא יופלה אדם על פי מעמדה המדיני, על פי סמכותה או על פי מעמדה הבינלאומי של המדינה או הארץ שאליה הוא שייך, דין שהארץ היא עצמאית, ובין שהיא נתונה לנאמנות, בין שהיא נטולת שלטון עצמי ובין שריבונותה מוגבלת כל הגבלה אחרת.
diff --git a/resources/text/javanese.txt b/resources/text/javanese.txt
new file mode 100644
index 0000000..b3f7cff
--- /dev/null
+++ b/resources/text/javanese.txt
@@ -0,0 +1 @@
+꧋ꦱ​ꦧꦼ​ꦤ꧀ꦲꦸ​ꦮꦺꦴꦁ​ꦏ​ꦭ​ꦲꦶ​ꦫ​ꦏꦺ​ꦏ​ꦤ꧀ꦛꦶ​ꦩꦂ​ꦢꦶ​ꦏ​ꦭ​ꦤ꧀ꦢꦂ​ꦧꦺ​ꦩꦂ​ꦠ​ꦧ​ꦠ꧀ꦭ​ꦤ꧀ꦲ​ꦏ꧀ꦲ​ꦏ꧀ꦏꦁ​ꦥ​ꦝ꧉​ꦏ​ꦧꦺꦃ​ꦥꦶ​ꦤ​ꦫꦶ​ꦔ​ꦤ꧀ꦲ​ꦏ​ꦭ꧀ꦭ​ꦤ꧀ꦏ​ꦭ꧀ꦧꦸ​ꦱꦂ​ꦠ​ꦏ​ꦲ​ꦗ​ꦧ꧀ꦥ​ꦱꦿ​ꦮꦸꦁ​ꦔ​ꦤ꧀ꦲꦁ​ꦒꦺꦴ​ꦤꦺ​ꦩꦼ​ꦩꦶ​ꦠꦿ​ꦤ꧀ꦱꦶ​ꦗꦶ​ꦭ​ꦤ꧀ꦱꦶ​ꦗꦶ​ꦤꦺ​ꦏ​ꦤ꧀ꦛꦶ​ꦗꦶ​ꦮꦺꦴ​ꦱꦸ​ꦩ​ꦢꦸ​ꦭꦸꦂ꧉
diff --git a/resources/text/kana.txt b/resources/text/kana.txt
new file mode 100644
index 0000000..e1d0436
--- /dev/null
+++ b/resources/text/kana.txt
@@ -0,0 +1,3 @@
+第1条 すべての人間は、生まれながらにして自由であり、かつ、尊厳と権利とについて平等である。人間は、理性と良心とを授けられており、互いに同胞の精神をもって行動しなければならない。
+
+第2条 すべて人は、人種、皮膚の色、性、言語、宗教、政治上その他の意見、国民的もしくは社会的出身、財産、門地その他の地位又はこれに類するいかなる自由による差別をも受けることなく、この宣言に掲げるすべての権利と自由とを享有することができる。 さらに、個人の属する国又は地域が独立国であると、信託統治地域であると、非自治地域であると、又は他のなんらかの主権制限の下にあるとを問わず、その国又は地域の政治上、管轄上又は国際上の地位に基ずくいかなる差別もしてはならない。
diff --git a/resources/text/khmer.txt b/resources/text/khmer.txt
new file mode 100644
index 0000000..9dde498
--- /dev/null
+++ b/resources/text/khmer.txt
@@ -0,0 +1,3 @@
+មាត្រា ១ មនុស្សទាំងអស់ កើតមកមានសេរីភាព និងសមភាព ក្នុងផ្នែកសេចក្ដីថ្លៃថ្នូរនិងសិទ្ធិ។ មនុស្ស មានវិចារណញ្ញាណនិងសតិសម្បជញ្ញៈជាប់ពីកំណើត ហើយគប្បីប្រព្រឹត្ដចំពោះគ្នាទៅវិញទៅមក ក្នុង ស្មារតីភាតរភាពជាបងប្អូន។
+
+មាត្រា ២ មនុស្សម្នាក់ៗ អាចប្រើប្រាស់សិទ្ធិនិងសេរីភាពទាំងអស់ ដែលមានចែងក្នុងសេចក្ដីប្រកាសនេះ ដោយគ្មានការប្រកាន់បែងចែកបែបណាមួយ មានជាអាទិ៍ ពូជសាសន៍ ពណ៌សម្បុរ ភេទ ភាសា សាសនា មតិនយោបាយ ឬមតិផ្សេងៗទៀត ដើមកំណើតជាតិ ឬសង្គម ទ្រព្យសម្បត្ដិ កំណើត ឬស្ថានភាព ដទៃៗទៀតឡើយ។ លើសពីនេះ មិនត្រូវធ្វើការប្រកាន់បែងចែកណាមួយ ដោយសំអាងទៅលើឋានៈខាងនយោបាយ ខាងដែនសមត្ថកិច្ច ឬខាងអន្ដរជាតិរបស់ប្រទេស ឬដែនដីដែលបុគ្គលណាម្នាក់រស់នៅ ទោះបីជាប្រទេស ឬដែនដីនោះឯករាជ្យក្ដី ស្ថិតក្រោមអាណាព្យាបាលក្ដី ឬគ្មានស្វ័យគ្រប់គ្រងក្ដី ឬស្ថិតក្រោមការដាក់ កម្រិតផ្សេងទៀតណាមួយ ដល់អធិបតេយ្យភាពក្ដី។
diff --git a/resources/text/lao.txt b/resources/text/lao.txt
new file mode 100644
index 0000000..5a550e0
--- /dev/null
+++ b/resources/text/lao.txt
@@ -0,0 +1,3 @@
+ມາດຕາ 1: ມະນຸດເກີດມາມີສິດເສລີພາບ ແລະ ສະເໝີໜ້າກັນໃນທາງກຽດຕິສັກ ແລະ ທາງສິດດ້ວຍມະນຸດມີສະຕິສຳປັດຊັນຍະ(ຮູ້ດີຮູ້ຊົ່ວ)ແລະມີມະໂນທຳຈື່ງຕ້ອງປະພຶດຕົນຕໍ່ກັນໃນທາງພີ່ນ້ອງ.
+
+ມາດຕາ 2: ຂໍ້ 1.ຄົນຜູ້ໃດກໍ່ອ້າງຕົນໄດ້ວ່າ:ມີສິດ ແລະ ເສລີພາບທຸກຢ່າງທີ່ໄດ້ປ່າວຮ້ອງຢູ່ໃນປະກາດສະບັບນີ້ໂດຍບໍ່ເລືອກໜ້າ ບໍ່ຈຳກັດເຊື້ອຊາດ,ຜິວເນື້ອ,ເພດ,ສາສະໜາ ຄວາມຄິດເຫັນໃນດ້ານການເມືອງ ຫຼື ອື່ນໆ ກຳເນີດແຫ່ງຊາດຫຼື ສັງຄົມຖານະການມີຊັບສົມບັດມາກ ຫຼື ນ້ອຍ,ມີຕະກຸນ ຫຼື ຖານະອື່ນໆ. ຂໍ້ 2.ອີກປະການໜື່ງ ຈະບໍ່ຈຳກັດຢ່າງໃດໃນການແຕກຕ່າງກັນອັນເນື່ອງມາຈາກລະບຽບການເມືອງການປົກຄອງ ຫຼື ລະຫວ່າງຊາດຂອງປະເທດ ຫຼື ດິນແດນ ຊື່ງບຸກຄົນຜູ້ໃດຜູ້ໜື່ງສັງກັດຢູ່;ດິນແດນນັ້ນຈຳເປັນເອກະລາດຢູ່ໃນຄວາມອາລັກຂາຂອງມະຫາອຳນາດ ຫຼື ບໍ່ມີອິດສະຫຼະ ຫຼື ຖືກລົດອະທິປະໄຕລົງໂດຍຈຳກັດກໍ່ຕາມ.
diff --git a/resources/text/mandaic.txt b/resources/text/mandaic.txt
new file mode 100644
index 0000000..b14e76f
--- /dev/null
+++ b/resources/text/mandaic.txt
@@ -0,0 +1,3 @@
+ࡊࡋ ࡁࡓ ࡀࡍࡀࡔࡀ ࡌࡉࡕࡋࡉࡓ ࡔࡀࡅࡉࡀ ࡁࡏࡒࡀࡓࡀ ࡅࡀࡂࡓࡉࡀ࡞ ࡁࡉࡍࡕࡀ ࡅࡕࡉࡓࡕࡀ ࡏࡕࡄࡉࡁࡋࡅࡍ ࡅࡋࡅࡀࡕ ࡄࡓࡀࡓࡉࡀ ࡈࡀࡁࡅࡕࡀ ࡀࡁࡓࡉࡍ ࡀࡊࡅࡀࡕ ࡖࡍࡉࡄࡅࡍ ࡀࡄࡉࡀ࡞
+
+ࡈࡅࡁࡀࡊ ࡈࡅࡁࡀࡊ ࡍࡉࡔࡌࡀ ࡖࡍࡐࡀࡒࡕ ࡌࡉࡍࡇ ࡌࡍ ࡀࡋࡌࡀ ࡍࡐࡀࡒࡕࡇ ࡋࡒࡉࡋࡅࡌࡀ ࡅࡋࡐࡀࡂࡓࡀ ࡎࡀࡓࡉࡀ ࡖࡄࡅࡉࡕࡁࡇ ࡋࡃࡀࡅࡓࡀ ࡖࡃࡅࡓ ࡁࡉࡔ࡙ࡉࡀ ࡋࡀࡕࡓࡀ ࡖࡊࡅࡋࡇ ࡄࡀࡈࡉࡀ ࡋࡀࡋࡌࡀ ࡖࡄࡔࡅࡊࡀ ࡖࡎࡉࡍࡀ ࡒࡉࡍࡀ ࡅࡐࡋࡅࡂࡉࡀ
diff --git a/resources/text/myanmar.txt b/resources/text/myanmar.txt
new file mode 100644
index 0000000..a56beb5
--- /dev/null
+++ b/resources/text/myanmar.txt
@@ -0,0 +1,3 @@
+အပိုဒ် ၁ လူတိုင်းသည် တူညီ လွတ်လပ်သော ဂုဏ်သိက္ခာဖြင့် လည်းကောင်း၊ တူညီလွတ်လပ်သော အခွင့်အရေးများဖြင့် လည်းကောင်း၊ မွေးဖွားလာသူများ ဖြစ်သည်။ ထိုသူတို့၌ ပိုင်းခြား ဝေဖန်တတ်သော ဉာဏ်နှင့် ကျင့်ဝတ် သိတတ်သော စိတ်တို့ရှိကြ၍ ထိုသူတို့သည် အချင်းချင်း မေတ္တာထား၍ ဆက်ဆံကျင့်သုံးသင့်၏။
+
+အပိုဒ် ၂ လူတိုင်းသည် လူ့အခွင့် အရေး ကြေညာစာတမ်းတွင် ဖော်ပြထားသည့် အခွင့်အရေး အားလုံး၊ လွတ်လပ်ခွင့် အားလုံးတို့ကို ပိုင်ဆိုင် ခံစားခွင့်ရှိသည်။ လူမျိုးနွယ်အားဖြင့် ဖြစ်စေ၊ အသားအရောင်အားဖြင့် ဖြစ်စေ၊ ကျား၊ မ၊ သဘာဝအားဖြင့် ဖြစ်စေ၊ ဘာသာစကားအားဖြင့် ဖြစ်စေ၊ ကိုးကွယ်သည့် ဘာသာအားဖြင့် ဖြစ်စေ၊ နိုင်ငံရေးယူဆချက်၊ သို့တည်းမဟုတ် အခြားယူဆချက်အားဖြင့် ဖြစ်စေ၊ နိုင်ငံနှင့် ဆိုင်သော၊ သို့တည်းမဟုတ် လူမှုအဆင့်အတန်းနှင့် ဆိုင်သော ဇစ်မြစ် အားဖြင့်ဖြစ်စေ၊ ပစ္စည်း ဥစ္စာ ဂုဏ်အားဖြင့် ဖြစ်စေ၊ မျိုးရိုးဇာတိအားဖြင့် ဖြစ်စေ၊ အခြား အဆင့်အတန်း အားဖြင့် ဖြစ်စေ ခွဲခြားခြင်းမရှိစေရ။ ထို့ပြင် လူတစ်ဦး တစ်ယောက် နေထိုင်ရာ နိုင်ငံ၏ သို့တည်းမဟုတ် နယ်မြေဒေသ၏ နိုင်ငံရေးဆိုင်ရာ ဖြစ်စေ စီရင် ပိုင်ခွင့်ဆိုင်ရာ ဖြစ်စေ တိုင်းပြည် အချင်းချင်း ဆိုင်ရာဖြစ်စေ၊ အဆင့်အတန်း တစ်ခုခုကို အခြေပြု၍ သော်လည်းကောင်း၊ ဒေသနယ်မြေတစ်ခုသည် အချုပ်အခြာ အာဏာပိုင် လွတ်လပ်သည့် နယ်မြေ၊ သို့တည်းမဟုတ် ကုလသမဂ္ဂ ထိန်းသိမ်း စောင့်ရှောက် ထားရသည့် နယ်မြေ၊ သို့တည်းမဟုတ် ကိုယ်ပိုင် အုပ်ချုပ်ခွင့် အာဏာတို့ တစိတ်တဒေသလောက်သာ ရရှိသည့် နယ်မြေ စသဖြင့် ယင်းသို့ သော နယ်မြေများ ဖြစ်သည်၊ ဖြစ်သည် ဟူသော အကြောင်းကို အထောက်အထား ပြု၍ သော်လည်းကောင်း ခွဲခြားခြင်း လုံးဝ မရှိစေရ။
diff --git a/resources/text/newtailue.txt b/resources/text/newtailue.txt
new file mode 100644
index 0000000..28f7cca
--- /dev/null
+++ b/resources/text/newtailue.txt
@@ -0,0 +1,3 @@
+ᦝᧂ​ᦑᦸᦰ​ᦵᦑᦲᧈ​ᦓᦲᦰ​ᦗ​ᦻᦗᦲᧈ​ᦈᧅ​ᦶᦐ​ᦏᦲᧈ​ᦏᦾᧉ​ᦟᧁᧈ​ᦺᦞᧉ​ᦺᦃ
+
+ᦂᦱᧇ​ᦵᦂᧂ​ᦆᧄ​ᦂᦸᧃᧈ​ᦵᦓᦲ,​ᦅᧄ​ᦙᦲ​ᦺᦞᧉ​ᦈᧅ​ᦺᦃ​ᦉᦱᧃ​ᦍᦸ​ᦍᦹᧃᧈ
diff --git a/resources/text/nko.txt b/resources/text/nko.txt
new file mode 100644
index 0000000..9f42306
--- /dev/null
+++ b/resources/text/nko.txt
@@ -0,0 +1,3 @@
+ߞߏ ߡߍ߲ ߞߵߊ߬ ߞߍ߫ ߊ߲ ߛߋ߫ ߘߊ߫ ߞߊ߬ ߕߟߋ߬ߓߊ߰ߓߟߐߟߐ ߘߊߦߟߍ߬ ߒߞߏ ߦߋ߫ ߸ ߊ߲ ߧߴߊ߲ ߞߊߘߊ߲߫ ߏ߬ ߘߐ߫ ߞߙߊߕߊߕߊ߫ ߞߊ߬ ߒ߬ ߓߊߘߋ߲ ߕߐ߬ߡߊ ߟߎ߬ ߟߊߞߍ߫ ߊ߬ ߞߊ߬ߟߊߡߊ߬߸ ߞߵߊ߬ߟߎ߬ ߡߊߛߊ߬ߡߊ߲߫ ߞߵߊ߬ߟߎ߬ ߡߊߘߏ߲߬ ߒ߬ ߠߊ߫ ߒߞߏ ߛߘߊߟߊ߫ ߞߏߓߊ ߣߌ߲߬ ߞߊߡߊ߲ ߞߣߐ߫߸ ߏ߬ ߘߏ߲߬ ߕߴߛߋ߫ ߘߊߓߊ߲߫ ߠߊ߫ ߝߋߎ߫ ߝߏ߫ ߊ߬ߟߎ߬ ߦߋ߫ ߛߋ߫ ߞߊ߬ ߓߟߐߟߐ ߞߐߜߍ ߟߎ߬ ߡߊߝߟߍ߫ ߟߴߊ߬ߟߎ߫ ߖߘߍ߬ ߦߋ߫ ߊ߬ߟߎ߬ ߟߊ߫ ߕߟߋ߬ߓߊ߮ ߟߎ߬ ߟߊ߫ ߣߐ߰ߦߊ߫ ߘߐ߫ ߸ ߣߌ߲߬ ߘߏ߲߬ ߕߊ߬ߣߍ߲߫ ߒߞߏ ߟߊ߫ ߖߘߍ߬ߛߐߘߐ߲ ߛߌߟߊ߫ ߓߐߣߍ߲ ߠߎ߫ ߓߎ߭ ߟߋ߫ ߡߊ߬.
+
+ߡߊ߲߬ߘߋ߲߬ ߛߊ߲ߘߊ ߘߏ߫ ߟߋ߬ ߞߊ߲߫ ߞߏ߫: ߌ ߓߊ߯ ߌ ߢߊ ߟߐ߬ ߕߋ߬ߟߋ ߘߐ߫ ߞߵߌ ߕߊ߯ ߦߙߐ ߡߊߝߟߍ߫߸ ߛߎ߫ ߕߍ߫ ߞߏ߬ ߌ ߡߊ߬. ߒ߬ߓߊ߬ ߊ߲ ߧߋ߫ ߒ߬ ߠߞߊߟߌߦߊ߫ ߛߊ߫ ߒ߬ ߘߌ߫ ߞߍ߫ ߒ߬ ߘߎߢߊߘߐߕߍ߯ ߢߐ߲߮ ߠߎ߬ ߘߐ߫ ߞߊ߬ ߞߍ߫ ߞߎߟߎ߲߫ ߞߋߟߋ߲߫ ߞߣߐ߫ ߏ߬ ߘߐ߫.
diff --git a/resources/text/sinhala.txt b/resources/text/sinhala.txt
new file mode 100644
index 0000000..aca7371
--- /dev/null
+++ b/resources/text/sinhala.txt
@@ -0,0 +1,3 @@
+1 වන වගන්තිය සියලු මනුෂ්‍යයෝ නිදහස්ව උපත ලබා ඇත. ගරුත්වයෙන් හා අයිතිවාසිකම්වලින් සමාන වෙති. යුක්ති අයුක්ති පිළිබඳ හැඟීමෙන් හා හෘදය සාක්ෂියෙන් යුත් ඔවුන්, ඔවුනොවුන්ට සැළකිය යුත්තේ සහෝදරත්වය පිළිබඳ හැඟීමෙනි.
+
+2 වන වගන්තිය ජාති, වංශ, වර්ණ, ස්ත්‍රී පුරුෂ භාවය, භාෂාව, ආගම්, දේශපාලන ආදී කවර බේදයක් හෝ සමාජ, ජාතික, දේපළ, උපත ආදී කවර තත්ත්වයක විශේෂයක් හෝ නොමැතිව මේ ප්‍රකාශනයේ සඳහන් සියලු හිමිකම්වලට හා ස්වාධීනත්වයන්ට සෑම පුද්ගලයකුම උරුම වන්නේය. තවද යම් පුද්ගලයකු අයත්වන රටේ දේශපාලන, නීතිමය හෝ ජාත්‍යන්තර තත්ත්වයන් පිළිබඳ කිසිදු විශේෂයක් ද ඒ රටේ ස්වාධීන, භාරකාර, අස්වාධීන ආදී කවර තත්ත්වයක් පිළිබඳ විශේෂයක් ද නොමැතිව මේ හිමිකම් ඔහු සතු වන්නේය.
diff --git a/resources/text/sundanese.txt b/resources/text/sundanese.txt
new file mode 100644
index 0000000..21e1a3f
--- /dev/null
+++ b/resources/text/sundanese.txt
@@ -0,0 +1 @@
+ᮞᮊᮥᮙ᮪ᮔ ᮏᮜ᮪ᮙ ᮌᮥᮘᮢᮌ᮪ ᮊ ᮃᮜᮙ᮪ ᮓᮥᮑ ᮒᮨᮂᮞᮤᮖᮒ᮪ᮔ ᮙᮨᮛ᮪ᮓᮤᮊ ᮏᮩᮀ ᮘᮧᮌ ᮙᮛ᮪ᮒᮘᮒ᮪ ᮊᮒᮥᮒ᮪ ᮠᮊ᮪-ᮠᮊ᮪ ᮃᮔᮥ ᮞᮛᮥᮃ. ᮙᮛᮔᮨᮂᮔ ᮓᮤᮘᮨᮛᮨ ᮃᮊᮜ᮪ ᮏᮩᮀ ᮠᮒᮨ ᮔᮥᮛᮔᮤ, ᮎᮙ᮪ᮕᮥᮁ-ᮌᮅᮜ᮪ ᮏᮩᮀ ᮞᮞᮙᮔ ᮃᮚ ᮓᮤᮔ ᮞᮥᮙᮔᮨᮒ᮪ ᮓᮥᮓᮥᮜᮥᮛᮔ᮪.
diff --git a/resources/text/syriac.txt b/resources/text/syriac.txt
new file mode 100644
index 0000000..504a66b
--- /dev/null
+++ b/resources/text/syriac.txt
@@ -0,0 +1,9 @@
+ܩܘ݂ܝܵܡܵܐ ܕܟܠ ܚܕܵܐ ܐܘ݂ܡܬܵܐ ܬܸܠܝܵܐ ܝܠܹܗ ܒܠܸܫܵܢܘ݁ܗ، ܘܠܸܫܵܢܵܐ ܒܟܬܝ݂ܵܒ݂ܵܬܘ݂ܗܝ ܘܒܣܸܦܪܵܝܘ݂ܬܘ݂ܗܝ. ܚܲܕ ܠܸܫܵܢܵܐ ܕܠܐ ܟܬܝܼ̈ܒܹܬܵܐ، ܐܲܝܟ ܚܲܕ ܟܲܪܡܵܐ ܝܠܹܗ ܕܠܵܐ ܢܵܛܘܿܪܹ̈ܐ. ܐܵܗܵܐ ܒܸܬ ܦܵܐܹܣ ܐ݇ܟ݂ܝܼܠܵܐ ܒܓܸܠܹ̈ܐ ܫܹܐܕܵܢܹ̈ܐ، ܘܠܸܫܵܢܵܐ ܒܚܵܒܪܹ̈ܐ ܢܘ݂ܼܟܪ݂̈ܵܝܹܐ.
+
+ܐܵܗܵܐ ܠܸܫܵܢܲܢ، ܐܵܦܸܢ ܡܘܼܣܟܸܢܵܐ، ܐܝܼܢܵܐ ܡܵܪܹܐ ܛܘܼܗܡܵܐ ܝܠܹܗ، ܘܐܝܼܬ ܠܹܗ ܕܝܼܠܵܝܵܬܹ̈ܐ ܚܩܝܼܪܹ̈ܐ. ܐܸܢ ܦܵܝܫܝܼ ܒܘܼܓ̰ܪܹ̈ܐ، ܟܹܐ ܗܵܘܝܼ ܡܲܦܬܘܼܝܹܐ ܘܓܲܪܘܘܼܣܹܐ ܒܣܸܕܪܵܐ ܕܐܵܢ ܠܸܫܵܢܹ̈ܐ ܣܸܦܪ̈ܵܝܹܐ ܘܪܗܸܛܪ̈ܵܝܹܐ ܕܕܘܼܢܝܹܐ.
+
+ܘܐ̄ܢܵܫ̈ܝܼܢ ܐܵܡܪܝܼܢ ܕܲܐܟܙܢܵܐ ܩܲܕ݂ܡܵܝܲܬ݂ ܪܟ݂ܲܒ݂ ܐܵܬ݂ܘ̈ܵܬ݂ܲܐ ܥܸܒ݂ܪ̈ܵܝܵܬ݂ܲܐ ܘܲܒ݂ܗܹܝܢ ܣܡ ܢܡܘܣܐ ܆ ܗܟܢܐ ܘܫܝܠܡܘܢ ܪܟܒ ܐܬܘ̈ܬܐ ܕܣܦܪ̈ܐ ܐܚܪ̈ܢܐ ܘܝܗ݂ܒ ܠܥܡ̈ܡܐ ܕܐܬܝܩܪ ܡܢܗܘܢ ܂ ܘܩܕܡܝܬ ܕܣܘܪܝܝܐ ܗ̇ܘ ܕܝܗܒ ܠܚܝܪܡ ܕܨܘܪ
+
+ܣܷܦ݂ܪ̈ܶܐ ܗܳܠܷܝܢ ܕܰܠܘܳܬܱܢ ܂ ܘܒ݂ܳܐܬ݂ܱܪ̈ܘܳܬ݂ܳܐ ܕܰܚܕ݂ܳܪ̈ܰܝܢ ܂ ܡܢܷܗܘܿܢ ܡܱ̇ܢ ܡܫܰܡܠܷܝܢ ܘܓܡܝܪܝܢ݂ ܡܢܗܘܢ ܕܝܢ݂ ܚ݁ܣܝܪܝܢ ܘܒܨܝܪܝܢ ܂ ܘܣܦܪ̈ܐ ܡܫ̈ܡܠܝܐ ܠܟܠ ܛܘܦܣܐ ܡܬܠܬܡܢܐ ܒܠܫܢܐ ܐܬܘܬܐ ܕܡܬܪܫܡܐ ܒܟܬܒܐ݂ ܠܗܘܢ ܡܫܬܟܚܐ ܂
+
+ܐܟܡܐ ܕܠܝܘܢܝܐ ܘܪܘܡܝܐ ܘܐܓܘܦܛܝܐ ܘܐܪܡܢܝܐ ܂ ܘܣܦܪ̈ܐ ܚܣܝܪ̈ܐ݂ ܠܐ ܗܘܐ ܠܟܠ ܛܘܦܣܐ ܡܬܠܬܡܢܐ ܒܠܫܢܐ ܒܝܬܝܐ݂ ܨܘܪܬܐ ܕܡܬܟܬܒܐ݂ ܠܗܘܢ ܡܫܬܟܚܐ ܐܟܡܐ ܕܠܥܒܪܝܐ݀ ܘܣܘܪܝܝܐ ܘܐܪܒܝܐ
diff --git a/resources/text/taitham.txt b/resources/text/taitham.txt
new file mode 100644
index 0000000..c77b9e4
--- /dev/null
+++ b/resources/text/taitham.txt
@@ -0,0 +1,3 @@
+ᨡᩳ᩶ 1 ᨣᩢ᩠ᨶᩉᩮᩖᩨᨠᩥ᩠ᨶ ᨣᩢᩐᩢᩣᨡᩣ᩠ᨿᨸᩮ᩠ᨶᨦᩫ᩠ᨶ ᨠᩮ᩠ᨷᩉᩬᨾᩋᩬᨾᩅᩱᩢᨯ᩠᩶ᨦᨶᩦ᩶ ᨴᩩᨠᪧᨸᩦᨾᩣᨷᩢᨡᩣ᩠ᨯ ᨧᩥ᩠᩵ᨦᨠ᩠ᨴᩣᩴᩉᩨ᩶ᨡᩮᩢᩣᨻᩳ᩵ᨾᩯ᩵ᩃᩪᨠ ᨷᩢᨯᩱᩢᨠᩢᩢ᩠ᨶᩈᩢ᩠ᨦᩈᩢ᩠ᨠᨩᩮᩨᩬ.
+
+ᨡᩳ᩶ 2 ᨴᩩᨠᨤ᩠ᨶᩫᨾᩦᩈᩥᨴ᩠ᨵᩥᩓᩢᨻ᩠ᨦᩈᩁᨽᩣ᩠ᨷ ᨲᩣ᩠ᨾᨴᩦ᩵ᨯᩱᩢᨠ᩵ᩣ᩠ᩅᩅᩱᩢᨶᩱᨡᩳ᩶ᨠᨲᩥᨠᩣᩋ᩠ᨶᩢᨶᩦ᩶ ᨯᩰ᩠ᨿᨷᩢᨯᩱᩢᨲᩯ᩠ᨠᨲ᩵ᩣ᩠ᨦᨠ᩠ᨶᩢ ᨷᩢᩅᩤ᩵ᩋ᩠ᨶᩢᨯᩱ ᩉᩮ᩠ᨾᩨᩁᩅᩤ᩵ ᨩᩮ᩠ᩋᩨ᩶ᨩᩣ᩠ᨲ ᨹ᩠ᩅᩥ ᨻ᩠ᨿᨯ ᨽᩣᩈᩣ ᩈᩣᩈᨶᩣ ᨣ᩠ᩅᩣ᩠ᨾᨣ᩠ᨯᩧᩉ᩠ᨶᩢᨴᩤ᩠ᨦᨠᩣ᩠ᩁᨾᩮ᩠ᨦᩨ ᩉᩕᩨᨴᩤ᩠ᨦᩋ᩠ᨶᩨ᩵ᪧ ᨩᩣ᩠ᨲᨩᩮ᩠ᩋᩨ᩶ ᩈ᩠ᨦᩢᨣ᩠ᨾᩫ ᩈᩫ᩠ᨾᨷ᩠ᨲᩢ ᨩᩣ᩠ᨲᨠᩮ᩠ᨯᩨ ᩉᩕᩨᩈᨳᩣᨶᩋ᩠ᨶᩨ᩵ᪧ ᨳᩯ᩠ᨦᩢᨷᩕᨠᩣ᩠ᩁᩉ᩠ᨶᩧ᩵ᨦ ᨷᩢᨾᩦᨣ᩠ᩅᩣ᩠ᨾᨲᩯ᩠ᨠᨲ᩵ᩣ᩠ᨦᨠ᩠ᨶᩢ ᨷᩢᩅᩤ᩵ᨴᩤ᩠ᨦᨠᩣ᩠ᩁᨾᩮ᩠ᨦᩨ ᨠᩣ᩠ᩁᩈᩣ᩠ᩁᨠᩣ᩠ᩁᩃᩩᨾ ᨠᩣ᩠ᨦᨲ᩵ᩣ᩠ᨦᨷᩤ᩠ᨶᩢᨲ᩵ᩣ᩠ᨦᨾᩮ᩠ᨦᩨ ᩉᩕᩨᨠᩣ᩠ᩁᨶᩱᨯ᩠ᨶᩥᨯᩯ᩠ᨶᨴᩦ᩵ᨤ᩠ᨶᩫᩋᩣᩈᩱ᩠ᨿᩀᩪ᩵ ᨷᩢᩅᩤ᩵ᨯ᩠ᨶᩥᨯᩯ᩠ᨶᨶᩦ᩶ᨧᩢᨸᩮ᩠ᨶᩑᨠᩁᩣ᩠ᨩ ᩀᩪ᩵ᨶᩱᨣ᩠ᩅᩣ᩠ᨾᨸ᩠ᨠᩫᨣᩕ᩠ᩋᨦᨡ᩠ᩋᨦᨲ᩠ᨶᩫ ᩉᩕᩨᩀᩪ᩵ᨲᩱᩢᩋᩴᩣᨶᩣ᩠ᨧᨲ᩠ᨶᩫᨯᩱᪧᨴ᩠ᨦᩢᩈᩢ᩠ᨿᨦ
diff --git a/resources/text/tamil.txt b/resources/text/tamil.txt
new file mode 100644
index 0000000..46885f8
--- /dev/null
+++ b/resources/text/tamil.txt
@@ -0,0 +1,3 @@
+உறுப்புரை 1 மனிதப் பிறிவியினர் சகலரும் சுதந்திரமாகவே பிறக்கின்றனர்; அவர்கள் மதிப்பிலும், உரிமைகளிலும் சமமானவர்கள், அவர்கள் நியாயத்தையும் மனச்சாட்சியையும் இயற்பண்பாகப் பெற்றவர்கள். அவர்கள் ஒருவருடனொருவர் சகோதர உணர்வுப் பாங்கில் நடந்துகொள்ளல் வேண்டும்.
+
+உறுப்புரை 2 இனம், நிறம், பால், மொழி, மதம், அரசியல் அல்லது வேறு அபிப்பிராயமுடைமை, தேசிய அல்லது சமூகத் தோற்றம், ஆதனம், பிறப்பு அல்லது பிற அந்தஸ்து என்பன போன்ற எத்தகைய வேறுபாடுமின்றி, இப்பிரகடனத்தில் தரப்பட்டுள்ள எல்லா உரிமைகளுக்கும் சுதந்திரங்களுக்கும் எல்லோரும் உரித்துடையவராவர். மேலும், எவரும் அவருக்குரித்துள்ள நாட்டின் அல்லது ஆள்புலத்தின் அரசியல், நியாயாதிக்க அல்லது நாட்டிடை அந்தஸ்தின் அடிப்படையில் — அது தனியாட்சி நாடாக, நம்பிக்கைப் பொறுப்பு நாடாக, தன்னாட்சியற்ற நாடாக அல்லது இறைமை வேறேதேனும் வகையில் மட்டப்படுத்தப்பட்ட நாடாக இருப்பினுஞ்சரி — வேறுபாடெதுவும் காட்டப்படுதலாகாது.
diff --git a/resources/text/thaana.txt b/resources/text/thaana.txt
new file mode 100644
index 0000000..bdf5d4b
--- /dev/null
+++ b/resources/text/thaana.txt
@@ -0,0 +1,3 @@
+1 ވަނަ މާއްދާ ހުރިހާ އިންސާނުންވެސް ދުނިޔެއަށް އުފަންވަނީ، މިނިވަންކަމުގައި، ހަމަހަމަ ޙައްޤުތަކަކާއެކު، ހަމަހަމަ ދަރަޖައެއްގައި ކަމޭހިތެވިގެންވާ ބައެއްގެ ގޮތުގައެވެ. ހެޔޮ ވިސްނުމާއި، ހެޔޮބުއްދީގެ ބާރު އެމީހުންނަށް ލިބިގެންވެއެވެ. އަދި އެކަކު އަނެކަކާމެދު އެމީހުން މުޢާމަލާތް ކުރަންވާނީ، އުޚުއްވަތްތެރިކަމުގެ ރޫޙެއްގައެވެ.
+
+2 ވަނަ މާއްދާ ހަމަ ކޮންމެ މީހަކަށްމެ، މިޤަރާރުގައި ބަޔާންކޮށްފައިވާ ހުރިހާ ޙައްޤުތަކަކާއި މިނިވަންކަމުގެ މިންގަނޑުތަކެއް ހޯދުމާއި، ލިބިގަތުމުގެ ޙައްޤު ލިބިގެންވެއެވެ. އެޙައްޤުތަކާއި އެމިންގަނޑުތައް ލިބިދެނީ، ނަސްލާއި، ކުލައާއި، ޖިންސާއި، ބަހާއި، ދީނާއި، ސިޔާސީގޮތުން ނުވަތަ އެހެންވެސް ކަމަކާ ގުޅޭގޮތުން ވިސްނުން ގެންގުޅޭ ގޮތާއި، ވަކި ޤައުމަކަށް ނުވަތަ މުޖުތަމަޢަކަށް ނިސްބަތްވުމާއި، މުދާ ލިބިހުރުމާއި، އުފަންވީ ޢާއިލާއެއްގެ ސަބަބުން ޤަދަރުވެރިވުމާއި، އެހެންވެސް ސަބަބަކާ ހުރެ ޤަދަރުވެރިވުންފަދަ، އެއްވެސް ބާވަތެއްގެ މިންގަނޑަކުން ތަފާތު ކުރުމެއް ނެތިއެވެ. އަދި މީހަކު ނިސްބަތްވާ ޤައުމަކީ، ނުވަތަ ސަރަޙައްދަކީ ސިޔާސީ ގޮތުން، ނުވަތަ އެޤައުމުގެ ބާރު ހިނގާ ސަރަޙައްދުގެ މިންވަރުގެ ގޮތުން، ނުވަތަ އެޤައުމުގެ ބައިނަލްއަޤްވާމީ ހައިސިއްޔަތުގެ ގޮތުން ނަމަވެސް، އެއްވެސް ތަފާތުކުރުމެއް ގެންގުޅެގެން ނުވާނޭ ގޮތުންނެވެ. އެޤައުމަކީ، ނުވަތަ މީހަކު ނިސްބަތްވާ އެސަރަޙައްދަކީ، މިނިވަން ޤައުމަކަށް ވިޔަސް، ނުވަތަ އެކުވެރި ދައުލަތްތަކުގެ ބެލުމުގެ ދަށުން އެހެން ޤައުމަކުންބަލަހައްޓަމުންދާ ޤައުމެއް ކަމުގައިވިޔަސް، ނުވަތަ އަމިއްލަ ވެރިކަމެއް ނެތް ޤައުމެއް ކަމުގައި ވިޔަސް، ނުވަތަ އެހެންވެސް ގޮތަކުން ސިޔާދަތީ ބާރު މަޙްދޫދު ކުރެވިގެންވާ ޤައުމަކަށް ވީނަމަވެހެވެ.
diff --git a/resources/text/thai.txt b/resources/text/thai.txt
new file mode 100644
index 0000000..13fd7ce
--- /dev/null
+++ b/resources/text/thai.txt
@@ -0,0 +1,3 @@
+ข้อ 1 มนุษย์ทั้งหลายเกิดมามีอิสระและเสมอภาคกันในเกียรติศักด[เกียรติศักดิ์]และสิทธิ ต่างมีเหตุผลและมโนธรรม และควรปฏิบัติต่อกันด้วยเจตนารมณ์แห่งภราดรภาพ
+
+ข้อ 2 ทุกคนย่อมมีสิทธิและอิสรภาพบรรดาที่กำหนดไว้ในปฏิญญานี้ โดยปราศจากความแตกต่างไม่ว่าชนิดใด ๆ ดังเช่น เชื้อชาติ ผิว เพศ ภาษา ศาสนา ความคิดเห็นทางการเมืองหรือทางอื่น เผ่าพันธุ์แห่งชาติ หรือสังคม ทรัพย์สิน กำเนิด หรือสถานะอื่น ๆ อนึ่งจะไม่มีความแตกต่างใด ๆ ตามมูลฐานแห่งสถานะทางการเมือง ทางการศาล หรือทางการระหว่างประเทศของประเทศหรือดินแดนที่บุคคลสังกัด ไม่ว่าดินแดนนี้จะเป็นเอกราช อยู่ในความพิทักษ์มิได้ปกครองตนเอง หรืออยู่ภายใต้การจำกัดอธิปไตยใด ๆ ทั้งสิ้น
diff --git a/resources/text/tibetan.txt b/resources/text/tibetan.txt
new file mode 100644
index 0000000..de247ee
--- /dev/null
+++ b/resources/text/tibetan.txt
@@ -0,0 +1,3 @@
+དོན་ཚན་དང་པོ། འགྲོ་བ་མིའི་རིགས་རྒྱུད་ཡོངས་ལ་སྐྱེས་ཙམ་ཉིད་ནས་ཆེ་མཐོངས་དང༌། ཐོབ་ཐངགི་རང་དབང་འདྲ་མཉམ་དུ་ཡོད་ལ། ཁོང་ཚོར་རང་བྱུང་གི་བློ་རྩལ་དང་བསམ་ཚུལ་བཟང་པོ་འདོན་པའི་འོས་བབས་ཀྱང་ཡོད། དེ་བཞིན་ཕན་ཚུན་གཅིག་གིས་གཅིག་ལ་བུ་སྤུན་གྱི་འདུ་ཤེས་འཛིན་པའི་བྱ་སྤྱོད་ཀྱང་ལག་ལེན་བསྟར་དགོས་པ་ཡིན༎
+
+དོན་ཚན་གཉིས་པ། སྐྱེ་བོ་རེ་རེར་གསལ་བསྒྲགས་འདི་ནང་བཀོད་པའི་ཐོབ་ཐང་དང་རང་དབང་སྟེ། མི་རིགས་དང། ཤ་མདོག། ཕོ་མོ། སྐད་ཡིག། ཆོས་ལུགས། སྲིད་དོན་བཅས་སམ། འདོད་ཚུལ་གཞནདག་དང༌། རྒྱལ་ཁབ་དང་སྤྱི་ཚོགས་ཀྱི་འབྱུང་ཁུངས་། མཁར་དབང༌། རིགས་རྒྱུད། དེ་མིན་གནས་ཚུལ་འདི་རིགས་གང་ཡང་རུང་བར་དབྱེ་འབྱེད་མེད པའི་ཐོབ་དབང་ཡོད༎ ད་དུང་རྒྱལ་ཁབ་བམ། ས་གནས་གང་ཞིག་རང་བཙན་ཡིན་པ་དང༌། ལྟ་རྟོགས་འོག་ཏུ་གནས་པ། རང་གཞུང་རང་སྐྱོང་མ་ཡིན་པ། གཞན་དག་བདག་དབང་ཚད་འཛིན་ཡོད་པ་བཅས་ཇི་ལྟར་ཡང་དེ་དག་གི་སྐྱེ་བོ་གང་ཞིག་སྐིད་དོན་དང། ཁྲིམས་དབང། རྒྱལ་སྤྱིའི་གནས་སྟངས་བཅས་ཀྱི་ཐོག་ཏུ་ཁྱད་པརམི་དབྱེ་བ་ཡིན༎
diff --git a/resources/text/tifnagh.txt b/resources/text/tifnagh.txt
new file mode 100644
index 0000000..9da1090
--- /dev/null
+++ b/resources/text/tifnagh.txt
@@ -0,0 +1,3 @@
+ⴰⵎⴰⴳⵔⴰⴷ 1 ⴰⵔ ⴷ ⵜⵜⵍⴰⵍⴰⵏ ⵎⵉⴷⴷⵏ ⴳⴰⵏ ⵉⵍⴻⵍⵍⵉⵜⵏ ⵎⴳⴰⴷⴷⴰⵏ ⵖ ⵡⴰⴷⴷⵓⵔ ⴷ ⵉⵣⵔⴼⴰⵏ, ⵢⵉⵍⵉ ⴰⴽⵯ ⴷⴰⵔⵙⵏ ⵓⵏⵍⵍⵉ ⴷ ⵓⴼⵔⴰⴽ, ⵉⵍⵍⴰ ⴼⵍⵍⴰ ⵙⵏ ⴰⴷ ⵜⵜⵎⵢⴰⵡⴰⵙⵏ ⵏⴳⵔⴰⵜⵙⵏ ⵙ ⵜⴰⴳⵎⴰⵜ.
+
+ⴰⵎⴰⴳⵔⴰⴷ 2 ⴽⵓ ⵢⴰⵏ ⵉⵥⴹⴰⵕ ⴰⴷ ⵉⵟⵟⴼ ⴽⵓⵍⵍⵓ ⵉⵣⵔⴼⴰⵏ ⴷ ⵜⴷⵔⴼⵉⵢⵉⵏ ⵍⵍⵉ ⵉⵍⵍⴰⵏ ⵖ ⵓⵍⵖⵓ ⴰⴷ, ⴰⴷ ⵓⵔ ⵢⵉⵍⵉ ⵓⵙⵏⵓⵃⵢⵓ, ⵣⵓⵏⴷ ⵡⵉⵏ ⵓⵥⵓⵕ, ⵏⵖ ⴰⴽⵍⵓ, ⵏⵖ ⴰⵏⴰⵡ, ⵏⵖ ⵜⵓⵜⵍⴰⵢⵜ, ⵏⵖ ⴰⵙⴳⴷ, ⵏⵖ ⵜⴰⵏⵏⴰⵢⵜ ⵜⴰⵙⵔⵜⴰⵏⵜ ⵏⵖ ⵜⴰⵏⵏⴰⵢⵜ ⵢⴰⴹⵏ, ⵏⵖ ⵎⴰⴷ ⵉⵥⵍⵉⵏ ⵙ ⴰⵙⵓⵔⵙ ⴰⵎⴰⴷⴰⵏ, ⵏⵖ ⵡⵉⵏ ⴰⵢⴷⴰ ⵏⵖ ⵡⵉⵏ ⵜⵍⴰⵍⵉⵜ ⵏⵖ ⴰⵙⵓⵔⵙ ⵢⴰⴹⵏ. ⴰⵎⵔ ⴰⵙⵏⵓⵃⵢⵓ ⵏⴳⵔ ⵉⵔⴳⴰⵣⵏ ⵜⵉⵎⵖⴰⵔⵉⵏ. ⵓⵔ ⴷ ⵉⵇⵇⴰⵏ ⴰⴷ ⵢⵉⵍⵉ ⵓⵙⵏⵓⵃⵢⵓ ⵉⵟⵟⴼⵏ ⵙ ⵡⴰⴷⴷⴰⴷ ⴰⵙⵔⵜⴰⵏ, ⵏⵖ ⴰⵣⵔⴼⴰⵏ ⵏⵖ ⴰⵎⴰⴹⵍⴰⵏ ⵏ ⵜⴰⵎⵓⵔⵜ ⵏⵖ ⴰⴽⴰⵍ ⵖ ⵉⴷⴷⵔ ⵓⴼⴳⴰⵏ, ⴰⴷ ⵜⴳ ⵜⵎⵓⵔⵜ ⴰⴷ ⵏⵖ ⴰⴽⴰⵍ ⴰⴷ ⴰⴷⵔⴼⵉ, ⵏⵖ ⴰⵎⵙⵏⴰⵍ ⵏⵖ ⵡⴰⵔⴰⵙⵉⵎⴰⵏ ⵏⵖ ⴰⵙ ⵉⵜⵜⵓⴳⴰ ⴽⵔⴰ ⵏ ⵓⵡⵜⵜⵓ.
diff --git a/resources/text/vai.txt b/resources/text/vai.txt
new file mode 100644
index 0000000..ff61812
--- /dev/null
+++ b/resources/text/vai.txt
@@ -0,0 +1,3 @@
+ꕢꕎꕌ ꔖꔜꕯꕊ (1) ꕉꕜꕮ ꔔꘋ ꖸ ꔰ ꗋꘋ ꕮꕨ ꔔꘋ ꖸ ꕎ ꕉꖸꕊ ꕴꖃ ꕃꔤꘂ ꗱ, ꕉꖷ ꗪꗡ ꔻꔤ ꗏꗒꗡ ꕎ ꗪ ꕉꖸꕊ ꖏꕎ. ꕉꕡ ꖏ ꗳꕮꕊ ꗏ ꕪ ꗓ ꕉꖷ ꕉꖸ ꕘꕞ ꗪ. ꖏꖷ ꕉꖸꔧ ꖏ ꖸ ꕚꕌꘂ ꗷꔤ ꕞ ꘃꖷ ꘉꔧ ꗠꖻ ꕞ ꖴꘋ ꔳꕩ ꕉꖸ ꗳ.
+
+ꕢꕎꕌ ꗱꕞꕯꕊ (2) ꗞ ꗏꗒ ꔰ ꕚ ꖷ ꗋꖺꕒꕌ ꖸ ꔰ ꕞ ꕺꖃ ꘈꗢ ꗏ ꗷꔤ ꕞ ꘃꖷ ꗞ ꗏ ꗓꖺ ꔰ ꔇꔀ ꕉ ꕮ, ꕉꔤ ꔳ ꗞ ꔱꔤꕮ ꖏ ꖺ ꗞ ꗬꔤꕮ ꖺ ꗞ ꕺꖃ ꕮꔧ ꕮꔕ, ꕉꔤ ꔳ ꖷꖬ ꖏ ꖺ ꕪꔤ, ꕉꔤ ꕢ ꔽꔤ ꕮ ꖺ ꔵꘉ, ꖺ ꕐꕌꔳ, ꖺ ꖏ ꘀꗫ ꕃꔤ ꖺ ꗞꗢ ꗃꕎ ꕸꖃ ꖷ ꗏ ꖺ ꗞ ꖷ ꖸ ꗏ, ꕉꔤ ꔳ ꔻꔤ ꗞꖻ ꖏ ꖺ ꔌꘋ ꕴꕱ ꗞꖻ, ꕉꔤ ꕢ ꕴꖃ ꕃꔤ ꕮ ꔧ ꖏ ꕮꔕ ꗷꔤ ꔰ. ꕉꔤ ꕒꕢ ꕉ ꔫꔤ ꕞ, ꗞ ꕮ ꗞ ꖸ ꗏ ꗓꖺ ꕮꕨ ꔻꔤ ꖏ ꗱ, ꖺ ꕢꕎꕌ ꖏ ꖴꕮ ꖺ ꗷꔤ ꖷ ꖦꖕꕰꕊ ꗪ ꗞꗢ ꕞ ꕴꖃ ꕸꖃꔀ ꗱ ꗡꕯ, ꖺ ꕐꕌꔳ ꖷ ꗏ ꗞꗢ ꗃꕎ, ꕉꔤ ꔳ ꕸꖃ ꖴꘋ ꖏ, ꔧ ꗨꗡ ꕉꕌ ꕸꖃꔀ ꗪ ꕸꖃ ꕮꔕ ꗛꖺ, ꔧ ꗨꗡ ꕉꕌ ꕸꖃꔀ ꗪ ꖴꗷ ꕢꕮ ꕒꕩ ꗏ ꖺ ꗷꔤ ꕮꔕ ꔰ ꕞ.
diff --git a/samplecode/PerlinPatch.cpp b/samplecode/PerlinPatch.cpp
index c962e19..dc6ce3f 100644
--- a/samplecode/PerlinPatch.cpp
+++ b/samplecode/PerlinPatch.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "AnimTimer.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
 #include "SkCanvas.h"
 #include "SkGradientShader.h"
 #include "SkPatchUtils.h"
@@ -104,7 +104,7 @@
                                                   colors,
                                                   nullptr,
                                                   3,
-                                                  SkShader::kMirror_TileMode,
+                                                  SkTileMode::kMirror,
                                                   0,
                                                   nullptr);
     }
@@ -125,12 +125,11 @@
         return this->INHERITED::onQuery(evt);
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         fSeed += 0.005f;
         return true;
     }
 
-
     void onDrawContent(SkCanvas* canvas) override {
         if (!canvas->getTotalMatrix().invert(&fInvMatrix)) {
             return;
@@ -150,7 +149,7 @@
         SkScalar scaleFreq = 2.0;
         fShader1 = SkPerlinNoiseShader::MakeImprovedNoise(fXFreq/scaleFreq, fYFreq/scaleFreq, 4,
                                                              fSeed);
-        fShaderCompose = SkShader::MakeComposeShader(fShader0, fShader1, SkBlendMode::kSrcOver);
+        fShaderCompose = SkShaders::Blend(SkBlendMode::kSrcOver, fShader0, fShader1);
 
         paint.setShader(fShaderCompose);
 
diff --git a/samplecode/Sample.h b/samplecode/Sample.h
index e906e31..ecf3189 100644
--- a/samplecode/Sample.h
+++ b/samplecode/Sample.h
@@ -16,7 +16,7 @@
 #include "SkRefCnt.h"
 #include "SkString.h"
 
-class SkAnimTimer;
+class AnimTimer;
 class SkCanvas;
 class Sample;
 
@@ -81,7 +81,7 @@
     static void DoClickUp(Click*, int x, int y, unsigned modi);
 
     void setBGColor(SkColor color) { fBGColor = color; }
-    bool animate(const SkAnimTimer& timer) { return this->onAnimate(timer); }
+    bool animate(const AnimTimer& timer) { return this->onAnimate(timer); }
 
     class Event {
     public:
@@ -209,7 +209,7 @@
 
     virtual void onDrawBackground(SkCanvas*);
     virtual void onDrawContent(SkCanvas*) = 0;
-    virtual bool onAnimate(const SkAnimTimer&) { return false; }
+    virtual bool onAnimate(const AnimTimer&) { return false; }
     virtual void onOnceBeforeDraw() {}
 
 private:
diff --git a/samplecode/Sample2PtRadial.cpp b/samplecode/Sample2PtRadial.cpp
index 8d1fcc4..5a93bd0 100644
--- a/samplecode/Sample2PtRadial.cpp
+++ b/samplecode/Sample2PtRadial.cpp
@@ -33,7 +33,7 @@
         SkPaint paint;
         paint.setShader(SkGradientShader::MakeTwoPointConical(c0, r0, c1, r1, colors,
                                                              nullptr, 2,
-                                                             SkShader::kClamp_TileMode));
+                                                             SkTileMode::kClamp));
         canvas->drawPaint(paint);
     }
 
diff --git a/samplecode/SampleAARectModes.cpp b/samplecode/SampleAARectModes.cpp
index b10a9ef..c876712 100644
--- a/samplecode/SampleAARectModes.cpp
+++ b/samplecode/SampleAARectModes.cpp
@@ -65,10 +65,7 @@
     SkMatrix m;
     m.setScale(SkIntToScalar(6), SkIntToScalar(6));
 
-    return SkShader::MakeBitmapShader(bm,
-                                      SkShader::kRepeat_TileMode,
-                                      SkShader::kRepeat_TileMode,
-                                      &m);
+    return bm.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &m);
 }
 
 class AARectsModesView : public Sample {
diff --git a/samplecode/SampleAARects.cpp b/samplecode/SampleAARects.cpp
index 211b411..d556482 100644
--- a/samplecode/SampleAARects.cpp
+++ b/samplecode/SampleAARects.cpp
@@ -59,8 +59,7 @@
         SkPaint bluePaint;
         bluePaint.setARGB(0xff, 0x0, 0x0, 0xff);
         SkPaint bmpPaint;
-        bmpPaint.setShader(SkShader::MakeBitmapShader(fBitmap, SkShader::kRepeat_TileMode,
-                                                      SkShader::kRepeat_TileMode));
+        bmpPaint.setShader(fBitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat));
         bluePaint.setStrokeWidth(3);
         bmpPaint.setStrokeWidth(3);
 
diff --git a/samplecode/SampleAndroidShadows.cpp b/samplecode/SampleAndroidShadows.cpp
index fff2f94..8449b34 100644
--- a/samplecode/SampleAndroidShadows.cpp
+++ b/samplecode/SampleAndroidShadows.cpp
@@ -5,19 +5,19 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
+#include "AnimTimer.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
 #include "SkBlurMask.h"
 #include "SkBlurMaskFilter.h"
-#include "SkColorFilter.h"
 #include "SkCamera.h"
 #include "SkCanvas.h"
+#include "SkColorFilter.h"
 #include "SkPath.h"
 #include "SkPathOps.h"
 #include "SkPoint3.h"
 #include "SkShadowUtils.h"
 #include "SkUTF.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 ////////////////////////////////////////////////////////////////////////////
 
@@ -341,7 +341,7 @@
                                lightPos, kLightWidth, .5f);
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         fAnimTranslate = timer.pingPong(30, 0, 125, -125);
         fAnimAngle = timer.pingPong(15, 0, 0, 20);
         if (fDoAlphaAnimation) {
diff --git a/samplecode/SampleAnimBlur.cpp b/samplecode/SampleAnimBlur.cpp
index 2d433ee..21d886d 100644
--- a/samplecode/SampleAnimBlur.cpp
+++ b/samplecode/SampleAnimBlur.cpp
@@ -5,10 +5,10 @@
  * found in the LICENSE file.
  */
 
+#include "AnimTimer.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
-#include "SkColorPriv.h"
 #include "SkCanvas.h"
+#include "SkColorPriv.h"
 #include "SkMaskFilter.h"
 #include "SkRandom.h"
 
@@ -55,7 +55,7 @@
         }
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         fBlurSigma = get_anim_sin(timer.secs(), 100, 4, 5);
         fCircleRadius = 3 + get_anim_sin(timer.secs(), 150, 25, 3);
         return true;
diff --git a/samplecode/SampleAnimatedImage.cpp b/samplecode/SampleAnimatedImage.cpp
index 8f73935..9f66f3a 100644
--- a/samplecode/SampleAnimatedImage.cpp
+++ b/samplecode/SampleAnimatedImage.cpp
@@ -5,9 +5,9 @@
  * found in the LICENSE file.
  */
 
+#include "AnimTimer.h"
 #include "SkAndroidCodec.h"
 #include "SkAnimatedImage.h"
-#include "SkAnimTimer.h"
 #include "SkCanvas.h"
 #include "SkFont.h"
 #include "SkPaint.h"
@@ -56,7 +56,7 @@
         canvas->drawDrawable(fDrawable.get(), fImage->getBounds().width(), 0);
     }
 
-    bool onAnimate(const SkAnimTimer& animTimer) override {
+    bool onAnimate(const AnimTimer& animTimer) override {
         if (!fImage) {
             return false;
         }
diff --git a/samplecode/SampleAnimatedText.cpp b/samplecode/SampleAnimatedText.cpp
index cfc071a..e927ffc 100644
--- a/samplecode/SampleAnimatedText.cpp
+++ b/samplecode/SampleAnimatedText.cpp
@@ -129,7 +129,7 @@
         canvas->drawString(modeString, 768.f, 540.f, font, paint);
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         // We add noise to the scale and rotation animations to
         // keep the font atlas from falling into a steady state
         fRotation += (1.0f + gRand.nextRangeF(-0.1f, 0.1f));
diff --git a/samplecode/SampleArc.cpp b/samplecode/SampleArc.cpp
index 58e4057..54bada0 100644
--- a/samplecode/SampleArc.cpp
+++ b/samplecode/SampleArc.cpp
@@ -5,8 +5,9 @@
  * found in the LICENSE file.
  */
 
+#include "AnimTimer.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
+#include "Sk1DPathEffect.h"
 #include "SkCanvas.h"
 #include "SkColorFilter.h"
 #include "SkColorPriv.h"
@@ -22,7 +23,6 @@
 #include "SkString.h"
 #include "SkTextUtils.h"
 #include "SkUTF.h"
-#include "Sk1DPathEffect.h"
 
 #include "SkParsePath.h"
 static void testparse() {
@@ -187,7 +187,7 @@
         canvas->drawDrawable(fRootDrawable.get());
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         SkScalar angle = SkDoubleToScalar(fmod(timer.secs() * 360 / 24, 360));
         fAnimatingDrawable->setSweep(angle);
         return true;
diff --git a/samplecode/SampleAtlas.cpp b/samplecode/SampleAtlas.cpp
index 5464204..a2ef015 100644
--- a/samplecode/SampleAtlas.cpp
+++ b/samplecode/SampleAtlas.cpp
@@ -5,13 +5,13 @@
  * found in the LICENSE file.
  */
 
+#include "AnimTimer.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
 #include "SkCanvas.h"
 #include "SkDrawable.h"
 #include "SkPath.h"
-#include "SkRandom.h"
 #include "SkRSXform.h"
+#include "SkRandom.h"
 #include "SkSurface.h"
 #include "SkTextUtils.h"
 
@@ -234,12 +234,10 @@
         canvas->drawDrawable(fDrawable.get());
     }
 
-    bool onAnimate(const SkAnimTimer&) override {
-        return true;
-    }
+    bool onAnimate(const AnimTimer&) override { return true; }
 #if 0
     // TODO: switch over to use this for our animation
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         SkScalar angle = SkDoubleToScalar(fmod(timer.secs() * 360 / 24, 360));
         fAnimatingDrawable->setSweep(angle);
         return true;
diff --git a/samplecode/SampleBigGradient.cpp b/samplecode/SampleBigGradient.cpp
index c921c37..dbfc332 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
@@ -13,7 +13,7 @@
 static sk_sp<SkShader> make_grad(SkScalar w, SkScalar h) {
     SkColor colors[] = { 0xFF000000, 0xFF333333 };
     SkPoint pts[] = { { 0, 0 }, { w, h } };
-    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode);
+    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
 }
 
 class BigGradientView : public Sample {
diff --git a/samplecode/SampleBitmapRect.cpp b/samplecode/SampleBitmapRect.cpp
index 9dcf98c..0d9b7f8 100644
--- a/samplecode/SampleBitmapRect.cpp
+++ b/samplecode/SampleBitmapRect.cpp
@@ -5,21 +5,21 @@
  * found in the LICENSE file.
  */
 
+#include "AnimTimer.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
 #include "SkBitmap.h"
 #include "SkCanvas.h"
+#include "SkColorFilter.h"
+#include "SkColorPriv.h"
 #include "SkFont.h"
 #include "SkGradientShader.h"
 #include "SkGraphics.h"
 #include "SkPath.h"
 #include "SkRegion.h"
 #include "SkShader.h"
-#include "SkUTF.h"
-#include "SkColorPriv.h"
-#include "SkColorFilter.h"
 #include "SkTime.h"
 #include "SkTypeface.h"
+#include "SkUTF.h"
 
 #include "SkOSFile.h"
 #include "SkStream.h"
@@ -36,16 +36,13 @@
     paint.setAntiAlias(true);
     const SkPoint pts[] = { { 0, 0 }, { SCALAR_SIZE, SCALAR_SIZE } };
     const SkColor colors[] = { SK_ColorWHITE, SK_ColorBLUE };
-    paint.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, 2,
-                                                   SkShader::kClamp_TileMode));
+    paint.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp));
     canvas.drawCircle(SCALAR_SIZE/2, SCALAR_SIZE/2, SCALAR_SIZE/2, paint);
 }
 
 static SkPoint unit_vec(int degrees) {
     SkScalar rad = SkDegreesToRadians(SkIntToScalar(degrees));
-    SkScalar s, c;
-    s = SkScalarSinCos(rad, &c);
-    return SkPoint::Make(c, s);
+    return SkPoint::Make(SkScalarCos(rad), SkScalarSin(rad));
 }
 
 static void bounce(SkScalar* value, SkScalar* delta, SkScalar min, SkScalar max) {
@@ -136,7 +133,7 @@
         }
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         if (timer.isStopped()) {
             this->resetBounce();
         } else if (timer.isRunning()) {
@@ -232,7 +229,7 @@
         }
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         if (timer.isStopped()) {
             this->resetBounce();
         } else if (timer.isRunning()) {
diff --git a/samplecode/SampleCCPRGeometry.cpp b/samplecode/SampleCCPRGeometry.cpp
index 994fd05..1fe801f 100644
--- a/samplecode/SampleCCPRGeometry.cpp
+++ b/samplecode/SampleCCPRGeometry.cpp
@@ -9,6 +9,7 @@
 
 #if SK_SUPPORT_GPU
 
+#include "GrClip.h"
 #include "GrContextPriv.h"
 #include "GrMemoryPool.h"
 #include "GrPathUtils.h"
@@ -17,13 +18,16 @@
 #include "GrResourceProvider.h"
 #include "Sample.h"
 #include "SkCanvas.h"
+#include "SkMakeUnique.h"
 #include "SkPaint.h"
 #include "SkPath.h"
 #include "SkRectPriv.h"
 #include "ccpr/GrCCCoverageProcessor.h"
 #include "ccpr/GrCCFillGeometry.h"
 #include "ccpr/GrCCStroker.h"
-#include "gl/GrGLGpu.cpp"
+#include "ccpr/GrGSCoverageProcessor.h"
+#include "ccpr/GrVSCoverageProcessor.h"
+#include "gl/GrGLGpu.h"
 #include "glsl/GrGLSLFragmentProcessor.h"
 #include "ops/GrDrawOp.h"
 
@@ -90,7 +94,8 @@
 
 private:
     FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
-    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*) override {
+    GrProcessorSet::Analysis finalize(
+            const GrCaps&, const GrAppliedClip*, GrFSAAType, GrClampType) override {
         return GrProcessorSet::EmptySetAnalysis();
     }
     void onPrepare(GrOpFlushState*) override {}
@@ -335,35 +340,40 @@
 
     GrPipeline pipeline(GrScissorTest::kDisabled, SkBlendMode::kPlus);
 
+    std::unique_ptr<GrCCCoverageProcessor> proc;
+    if (state->caps().shaderCaps()->geometryShaderSupport()) {
+        proc = skstd::make_unique<GrGSCoverageProcessor>();
+    } else {
+        proc = skstd::make_unique<GrVSCoverageProcessor>();
+    }
+
     if (!fView->fDoStroke) {
-        GrCCCoverageProcessor proc(rp, fView->fPrimitiveType);
-        SkDEBUGCODE(proc.enableDebugBloat(kDebugBloat));
+        proc->reset(fView->fPrimitiveType, rp);
+        SkDEBUGCODE(proc->enableDebugBloat(kDebugBloat));
 
         SkSTArray<1, GrMesh> mesh;
         if (PrimitiveType::kCubics == fView->fPrimitiveType ||
             PrimitiveType::kConics == fView->fPrimitiveType) {
-            sk_sp<GrBuffer> instBuff(
+            sk_sp<GrGpuBuffer> instBuff(
                     rp->createBuffer(fView->fQuadPointInstances.count() * sizeof(QuadPointInstance),
                                      GrGpuBufferType::kVertex, kDynamic_GrAccessPattern,
-                                     GrResourceProvider::Flags::kRequireGpuMemory,
                                      fView->fQuadPointInstances.begin()));
             if (!fView->fQuadPointInstances.empty() && instBuff) {
-                proc.appendMesh(std::move(instBuff), fView->fQuadPointInstances.count(), 0, &mesh);
+                proc->appendMesh(std::move(instBuff), fView->fQuadPointInstances.count(), 0, &mesh);
             }
         } else {
-            sk_sp<GrBuffer> instBuff(
+            sk_sp<GrGpuBuffer> instBuff(
                     rp->createBuffer(fView->fTriPointInstances.count() * sizeof(TriPointInstance),
                                      GrGpuBufferType::kVertex, kDynamic_GrAccessPattern,
-                                     GrResourceProvider::Flags::kRequireGpuMemory,
                                      fView->fTriPointInstances.begin()));
             if (!fView->fTriPointInstances.empty() && instBuff) {
-                proc.appendMesh(std::move(instBuff), fView->fTriPointInstances.count(), 0, &mesh);
+                proc->appendMesh(std::move(instBuff), fView->fTriPointInstances.count(), 0, &mesh);
             }
         }
 
         if (!mesh.empty()) {
             SkASSERT(1 == mesh.count());
-            proc.draw(state, pipeline, nullptr, mesh.begin(), 1, this->bounds());
+            proc->draw(state, pipeline, nullptr, mesh.begin(), 1, this->bounds());
         }
     } else if (PrimitiveType::kConics != fView->fPrimitiveType) {  // No conic stroke support yet.
         GrCCStroker stroker(0,0,0);
@@ -384,7 +394,7 @@
 
         SkIRect ibounds;
         this->bounds().roundOut(&ibounds);
-        stroker.drawStrokes(state, batchID, ibounds);
+        stroker.drawStrokes(state, proc.get(), batchID, ibounds);
     }
 
     if (glGpu) {
diff --git a/samplecode/SampleCamera.cpp b/samplecode/SampleCamera.cpp
index 6584867..3b3b98c 100644
--- a/samplecode/SampleCamera.cpp
+++ b/samplecode/SampleCamera.cpp
@@ -5,11 +5,11 @@
  * found in the LICENSE file.
  */
 
+#include "AnimTimer.h"
 #include "DecodeFile.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
-#include "SkCanvas.h"
 #include "SkCamera.h"
+#include "SkCanvas.h"
 #include "SkEmbossMaskFilter.h"
 #include "SkGradientShader.h"
 #include "SkPath.h"
@@ -39,10 +39,7 @@
                 SkMatrix matrix;
                 matrix.setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
 
-                fShaders.push_back(SkShader::MakeBitmapShader(bm,
-                                                           SkShader::kClamp_TileMode,
-                                                           SkShader::kClamp_TileMode,
-                                                           &matrix));
+                fShaders.push_back(bm.makeShader(&matrix));
             } else {
                 break;
             }
@@ -83,7 +80,7 @@
         }
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         if (timer.isStopped()) {
             fRY = 0;
         } else {
diff --git a/samplecode/SampleChineseFling.cpp b/samplecode/SampleChineseFling.cpp
index b123942..d5e2479 100644
--- a/samplecode/SampleChineseFling.cpp
+++ b/samplecode/SampleChineseFling.cpp
@@ -7,7 +7,7 @@
 
 #include "Resources.h"
 #include "Sample.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 #include "SkCanvas.h"
 #include "SkFontMetrics.h"
@@ -92,8 +92,13 @@
             this->createRandomWord(glyphs);
 
             SkTextBlobBuilder builder;
-            sk_tool_utils::add_to_text_blob_w_len(&builder, (const char*) glyphs, kWordLength*4,
-                                                  kUTF32_SkTextEncoding, font, 0, 0);
+            ToolUtils::add_to_text_blob_w_len(&builder,
+                                              (const char*)glyphs,
+                                              kWordLength * 4,
+                                              kUTF32_SkTextEncoding,
+                                              font,
+                                              0,
+                                              0);
 
             fBlobs.emplace_back(builder.make());
         }
@@ -216,9 +221,13 @@
                 auto currentLineLength = SkTMin(45, paragraphLength - 45);
                 this->createRandomLine(glyphs, currentLineLength);
 
-                sk_tool_utils::add_to_text_blob_w_len(&builder, (const char*) glyphs,
-                                                      currentLineLength*4, kUTF32_SkTextEncoding,
-                                                      font, 0, y);
+                ToolUtils::add_to_text_blob_w_len(&builder,
+                                                  (const char*)glyphs,
+                                                  currentLineLength * 4,
+                                                  kUTF32_SkTextEncoding,
+                                                  font,
+                                                  0,
+                                                  y);
                 y += fMetrics.fDescent - fMetrics.fAscent + fMetrics.fLeading;
                 paragraphLength -= 45;
             }
diff --git a/samplecode/SampleCircle.cpp b/samplecode/SampleCircle.cpp
index 3063324..fd9f157 100644
--- a/samplecode/SampleCircle.cpp
+++ b/samplecode/SampleCircle.cpp
@@ -79,8 +79,7 @@
         SkScalar angle = 0;
         for (int i = 1; i < n; i++) {
             angle += step;
-            SkScalar c, s = SkScalarSinCos(angle, &c);
-            path->lineTo(c, s);
+            path->lineTo(SkScalarCos(angle), SkScalarSin(angle));
         }
         path->close();
     }
diff --git a/samplecode/SampleClamp.cpp b/samplecode/SampleClamp.cpp
deleted file mode 100644
index 4e6e0cb..0000000
--- a/samplecode/SampleClamp.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2011 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-#include "Sample.h"
-#include "SkCanvas.h"
-#include "SkGraphics.h"
-#include "SkRandom.h"
-#include "SkGradientShader.h"
-#include "SkPicture.h"
-
-static sk_sp<SkShader> make_linear() {
-    SkPoint pts[] = { 0, 0, SK_Scalar1/500, SK_Scalar1/500 };
-    SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
-    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode);
-}
-
-class ClampView : public Sample {
-    sk_sp<SkShader> fGrad;
-
-public:
-    ClampView() {
-        fGrad = make_linear();
-    }
-
-protected:
-    virtual bool onQuery(Sample::Event* evt) {
-        if (Sample::TitleQ(*evt)) {
-            Sample::TitleR(evt, "Clamp");
-            return true;
-        }
-        return this->INHERITED::onQuery(evt);
-    }
-
-    virtual void onDrawContent(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setShader(fGrad);
-
-//        canvas->translate(this->width()/2, this->height()/2);
-        canvas->translate(64, 64);
-        canvas->drawPaint(paint);
-
-        SkPicture pic;
-        SkCanvas* c = pic.beginRecording(100, 100, 0);
-        SkCanvas::LayerIter layerIterator(c, false);
-        layerIterator.next();
-        layerIterator.done();
-    }
-
-private:
-    typedef Sample INHERITED;
-};
-
-//////////////////////////////////////////////////////////////////////////////
-
-static Sample* MyFactory() { return new ClampView; }
-static SampleRegister reg(MyFactory);
diff --git a/samplecode/SampleClock.cpp b/samplecode/SampleClock.cpp
index dc240b3..2bd65e2 100644
--- a/samplecode/SampleClock.cpp
+++ b/samplecode/SampleClock.cpp
@@ -216,9 +216,7 @@
         canvas->restore();
     }
 
-    bool onAnimate(const SkAnimTimer&) override {
-        return true;
-    }
+    bool onAnimate(const AnimTimer&) override { return true; }
 
 private:
 
diff --git a/samplecode/SampleColorFilter.cpp b/samplecode/SampleColorFilter.cpp
index 8a44fc4..9c28231 100644
--- a/samplecode/SampleColorFilter.cpp
+++ b/samplecode/SampleColorFilter.cpp
@@ -4,12 +4,12 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
-#include "sk_tool_utils.h"
 #include "Sample.h"
 #include "SkCanvas.h"
 #include "SkColorFilter.h"
 #include "SkPaint.h"
 #include "SkShader.h"
+#include "ToolUtils.h"
 
 static int inflate5To8(int x) {
     return (x << 3) | (x >> 2);
@@ -116,8 +116,7 @@
 protected:
     void onOnceBeforeDraw() override {
         fBitmap = createBitmap(N);
-        fShader = sk_tool_utils::create_checkerboard_shader(
-                0xFFCCCCCC, 0xFFFFFFFF, 12);
+        fShader = ToolUtils::create_checkerboard_shader(0xFFCCCCCC, 0xFFFFFFFF, 12);
 
         if (false) { // avoid bit rot, suppress warning
             test_5bits();
@@ -180,7 +179,7 @@
 
         for (size_t y = 0; y < SK_ARRAY_COUNT(gColors); y++) {
             for (size_t x = 0; x < SK_ARRAY_COUNT(gModes); x++) {
-                paint.setColorFilter(SkColorFilter::MakeModeFilter(gColors[y], gModes[x]));
+                paint.setColorFilter(SkColorFilters::Blend(gColors[y], gModes[x]));
                 canvas->drawBitmap(fBitmap, x * N * 1.25f, y * N * scale, &paint);
             }
         }
diff --git a/samplecode/SampleCowboy.cpp b/samplecode/SampleCowboy.cpp
index 06bbf88..e598f87 100644
--- a/samplecode/SampleCowboy.cpp
+++ b/samplecode/SampleCowboy.cpp
@@ -105,7 +105,7 @@
         return this->INHERITED::onQuery(evt);
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         if (!fDom) {
             return false;
         }
diff --git a/samplecode/SampleCusp.cpp b/samplecode/SampleCusp.cpp
index 32ba90d..d978e33025 100644
--- a/samplecode/SampleCusp.cpp
+++ b/samplecode/SampleCusp.cpp
@@ -5,13 +5,13 @@
  * found in the LICENSE file.
  */
 
+#include <string>
+#include "AnimTimer.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
 #include "SkCanvas.h"
 #include "SkFont.h"
 #include "SkGeometry.h"
 #include "SkPath.h"
-#include <string>
 
 // This draws an animation where every cubic has a cusp, to test drawing a circle
 // at the cusp point. Create a unit square. A cubic with its control points
@@ -171,7 +171,7 @@
         SkDebugf("");
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         curTime = timer.msec();
         if (!start) {
             start = curTime;
diff --git a/samplecode/SampleDegenerateQuads.cpp b/samplecode/SampleDegenerateQuads.cpp
new file mode 100644
index 0000000..72ee612
--- /dev/null
+++ b/samplecode/SampleDegenerateQuads.cpp
@@ -0,0 +1,512 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Sample.h"
+
+#include "GrQuad.h"
+#include "ops/GrQuadPerEdgeAA.h"
+
+#include "SkCanvas.h"
+#include "SkDashPathEffect.h"
+#include "SkPaint.h"
+#include "SkPathOps.h"
+
+// Draw a line through the two points, outset by a fixed length in screen space
+static void draw_extended_line(SkCanvas* canvas, const SkPaint paint,
+                              const SkPoint& p0, const SkPoint& p1) {
+    SkVector v = p1 - p0;
+    v.setLength(v.length() + 3.f);
+    canvas->drawLine(p1 - v, p0 + v, paint);
+
+    // Draw normal vector too
+    SkPaint normalPaint = paint;
+    normalPaint.setPathEffect(nullptr);
+    normalPaint.setStrokeWidth(paint.getStrokeWidth() / 4.f);
+
+    SkVector n = {v.fY, -v.fX};
+    n.setLength(.25f);
+    SkPoint m = (p0 + p1) * 0.5f;
+    canvas->drawLine(m, m + n, normalPaint);
+}
+
+static void make_aa_line(const SkPoint& p0, const SkPoint& p1, bool aaOn,
+                         bool outset, SkPoint line[2]) {
+    SkVector n = {0.f, 0.f};
+    if (aaOn) {
+        SkVector v = p1 - p0;
+        n = outset ? SkVector::Make(v.fY, -v.fX) : SkVector::Make(-v.fY, v.fX);
+        n.setLength(0.5f);
+    }
+
+    line[0] = p0 + n;
+    line[1] = p1 + n;
+}
+
+// To the line through l0-l1, not capped at the end points of the segment
+static SkScalar signed_distance(const SkPoint& p, const SkPoint& l0, const SkPoint& l1) {
+    SkVector v = l1 - l0;
+    v.normalize();
+    SkVector n = {v.fY, -v.fX};
+    SkScalar c = -n.dot(l0);
+    return n.dot(p) + c;
+}
+
+static SkScalar get_area_coverage(const bool edgeAA[4], const SkPoint corners[4],
+                                  const SkPoint& point) {
+    SkPath shape;
+    shape.addPoly(corners, 4, true);
+    SkPath pixel;
+    pixel.addRect(SkRect::MakeXYWH(point.fX - 0.5f, point.fY - 0.5f, 1.f, 1.f));
+
+    SkPath intersection;
+    if (!Op(shape, pixel, kIntersect_SkPathOp, &intersection) || intersection.isEmpty()) {
+        return 0.f;
+    }
+
+    // Calculate area of the convex polygon
+    SkScalar area = 0.f;
+    for (int i = 0; i < intersection.countPoints(); ++i) {
+        SkPoint p0 = intersection.getPoint(i);
+        SkPoint p1 = intersection.getPoint((i + 1) % intersection.countPoints());
+        SkScalar det = p0.fX * p1.fY - p1.fX * p0.fY;
+        area += det;
+    }
+
+    // Scale by 1/2, then take abs value (this area formula is signed based on point winding, but
+    // since it's convex, just make it positive).
+    area = SkScalarAbs(0.5f * area);
+
+    // Now account for the edge AA. If the pixel center is outside of a non-AA edge, turn of its
+    // coverage. If the pixel only intersects non-AA edges, then set coverage to 1.
+    bool needsNonAA = false;
+    SkScalar edgeD[4];
+    for (int i = 0; i < 4; ++i) {
+        SkPoint e0 = corners[i];
+        SkPoint e1 = corners[(i + 1) % 4];
+        edgeD[i] = -signed_distance(point, e0, e1);
+        if (!edgeAA[i]) {
+            if (edgeD[i] < -1e-4f) {
+                return 0.f; // Outside of non-AA line
+            }
+            needsNonAA = true;
+        }
+    }
+    // Otherwise inside the shape, so check if any AA edge exerts influence over nonAA
+    if (needsNonAA) {
+        for (int i = 0; i < 4; i++) {
+            if (edgeAA[i] && edgeD[i] < 0.5f) {
+                needsNonAA = false;
+                break;
+            }
+        }
+    }
+    return needsNonAA ? 1.f : area;
+}
+
+// FIXME take into account max coverage properly,
+static SkScalar get_edge_dist_coverage(const bool edgeAA[4], const SkPoint corners[4],
+                                       const SkPoint outsetLines[8], const SkPoint insetLines[8],
+                                       const SkPoint& point) {
+    bool flip = false;
+    // If the quad has been inverted, the original corners will not all be on the negative side of
+    // every outset line. When that happens, calculate coverage using the "inset" lines and flip
+    // the signed distance
+    for (int i = 0; i < 4; ++i) {
+        for (int j = 0; j < 4; ++j) {
+            SkScalar d = signed_distance(corners[i], outsetLines[j * 2], outsetLines[j * 2 + 1]);
+            if (d >= 0.f) {
+                flip = true;
+                break;
+            }
+        }
+        if (flip) {
+            break;
+        }
+    }
+
+    const SkPoint* lines = flip ? insetLines : outsetLines;
+
+    SkScalar minCoverage = 1.f;
+    for (int i = 0; i < 4; ++i) {
+        // Multiply by negative 1 so that outside points have negative distances
+        SkScalar d = (flip ? 1 : -1) * signed_distance(point, lines[i * 2], lines[i * 2 + 1]);
+        if (!edgeAA[i] && d >= -1e-4f) {
+            d = 1.f;
+        }
+        if (d < minCoverage) {
+            minCoverage = d;
+            if (minCoverage < 0.f) {
+                break; // Outside the shape
+            }
+        }
+    }
+    return minCoverage < 0.f ? 0.f : minCoverage;
+}
+
+static bool inside_triangle(const SkPoint& point, const SkPoint& t0, const SkPoint& t1,
+                            const SkPoint& t2, SkScalar bary[3]) {
+    // Check sign of t0 to (t1,t2). If it is positive, that means the normals point into the
+    // triangle otherwise the normals point outside the triangle so update edge distances as
+    // necessary
+    bool flip = signed_distance(t0, t1, t2) < 0.f;
+
+    SkScalar d0 = (flip ? -1 : 1) * signed_distance(point, t0, t1);
+    SkScalar d1 = (flip ? -1 : 1) * signed_distance(point, t1, t2);
+    SkScalar d2 = (flip ? -1 : 1) * signed_distance(point, t2, t0);
+    // Be a little forgiving
+    if (d0 < -1e-4f || d1 < -1e-4f || d2 < -1e-4f) {
+        return false;
+    }
+
+    // Inside, so calculate barycentric coords from the sideline distances
+    SkScalar d01 = (t0 - t1).length();
+    SkScalar d12 = (t1 - t2).length();
+    SkScalar d20 = (t2 - t0).length();
+
+    if (SkScalarNearlyZero(d12) || SkScalarNearlyZero(d20) || SkScalarNearlyZero(d01)) {
+        // Empty degenerate triangle
+        return false;
+    }
+
+    // Coordinates for a vertex use distances to the opposite edge
+    bary[0] = d1 * d12;
+    bary[1] = d2 * d20;
+    bary[2] = d0 * d01;
+    // And normalize
+    SkScalar sum = bary[0] + bary[1] + bary[2];
+    bary[0] /= sum;
+    bary[1] /= sum;
+    bary[2] /= sum;
+
+    return true;
+}
+
+static SkScalar get_framed_coverage(const SkPoint outer[4], const SkScalar outerCoverages[4],
+                                    const SkPoint inner[4], const SkScalar innerCoverages[4],
+                                    const SkPoint& point) {
+    // Triangles are ordered clock wise. Indices >= 4 refer to inner[i - 4]. Otherwise its outer[i].
+    static const int kFrameTris[] = {
+        0, 1, 4,   4, 1, 5,
+        1, 2, 5,   5, 2, 6,
+        2, 3, 6,   6, 3, 7,
+        3, 0, 7,   7, 0, 4,
+        4, 5, 7,   7, 5, 6
+    };
+    static const int kNumTris = 10;
+
+    SkScalar bary[3];
+    for (int i = 0; i < kNumTris; ++i) {
+        int i0 = kFrameTris[i * 3];
+        int i1 = kFrameTris[i * 3 + 1];
+        int i2 = kFrameTris[i * 3 + 2];
+
+        SkPoint t0 = i0 >= 4 ? inner[i0 - 4] : outer[i0];
+        SkPoint t1 = i1 >= 4 ? inner[i1 - 4] : outer[i1];
+        SkPoint t2 = i2 >= 4 ? inner[i2 - 4] : outer[i2];
+        if (inside_triangle(point, t0, t1, t2, bary)) {
+            // Calculate coverage by barycentric interpolation of coverages
+            SkScalar c0 = i0 >= 4 ? innerCoverages[i0 - 4] : outerCoverages[i0];
+            SkScalar c1 = i1 >= 4 ? innerCoverages[i1 - 4] : outerCoverages[i1];
+            SkScalar c2 = i2 >= 4 ? innerCoverages[i2 - 4] : outerCoverages[i2];
+
+            return bary[0] * c0 + bary[1] * c1 + bary[2] * c2;
+        }
+    }
+    // Not inside any triangle
+    return 0.f;
+}
+
+static constexpr SkScalar kViewScale = 100.f;
+static constexpr SkScalar kViewOffset = 200.f;
+
+class DegenerateQuadSample : public Sample {
+public:
+    DegenerateQuadSample(const SkRect& rect)
+            : fOuterRect(rect)
+            , fCoverageMode(CoverageMode::kArea) {
+        fOuterRect.toQuad(fCorners);
+        for (int i = 0; i < 4; ++i) {
+            fEdgeAA[i] = true;
+        }
+    }
+
+    void onDrawContent(SkCanvas* canvas) override {
+        static const SkScalar kDotParams[2] = {1.f / kViewScale, 12.f / kViewScale};
+        sk_sp<SkPathEffect> dots = SkDashPathEffect::Make(kDotParams, 2, 0.f);
+        static const SkScalar kDashParams[2] = {8.f / kViewScale, 12.f / kViewScale};
+        sk_sp<SkPathEffect> dashes = SkDashPathEffect::Make(kDashParams, 2, 0.f);
+
+        SkPaint circlePaint;
+        circlePaint.setAntiAlias(true);
+
+        SkPaint linePaint;
+        linePaint.setAntiAlias(true);
+        linePaint.setStyle(SkPaint::kStroke_Style);
+        linePaint.setStrokeWidth(4.f / kViewScale);
+        linePaint.setStrokeJoin(SkPaint::kRound_Join);
+        linePaint.setStrokeCap(SkPaint::kRound_Cap);
+
+        canvas->translate(kViewOffset, kViewOffset);
+        canvas->scale(kViewScale, kViewScale);
+
+        // Draw the outer rectangle as a dotted line
+        linePaint.setPathEffect(dots);
+        canvas->drawRect(fOuterRect, linePaint);
+
+        bool valid = this->isValid();
+
+        if (valid) {
+            SkPoint outsets[8];
+            SkPoint insets[8];
+            // Calculate inset and outset lines for edge-distance visualization
+            for (int i = 0; i < 4; ++i) {
+                make_aa_line(fCorners[i], fCorners[(i + 1) % 4], fEdgeAA[i], true, outsets + i * 2);
+                make_aa_line(fCorners[i], fCorners[(i + 1) % 4], fEdgeAA[i], false, insets + i * 2);
+            }
+
+            // Calculate inner and outer meshes for GPU visualization
+            SkPoint gpuOutset[4];
+            SkScalar gpuOutsetCoverage[4];
+            SkPoint gpuInset[4];
+            SkScalar gpuInsetCoverage[4];
+            this->getTessellatedPoints(gpuInset, gpuInsetCoverage, gpuOutset, gpuOutsetCoverage);
+
+            // Visualize the coverage values across the clamping rectangle, but test pixels outside
+            // of the "outer" rect since some quad edges can be outset extra far.
+            SkPaint pixelPaint;
+            pixelPaint.setAntiAlias(true);
+            SkRect covRect = fOuterRect.makeOutset(2.f, 2.f);
+            for (SkScalar py = covRect.fTop; py < covRect.fBottom; py += 1.f) {
+                for (SkScalar px = covRect.fLeft; px < covRect.fRight; px += 1.f) {
+                    // px and py are the top-left corner of the current pixel, so get center's
+                    // coordinate
+                    SkPoint pixelCenter = {px + 0.5f, py + 0.5f};
+                    SkScalar coverage;
+                    if (fCoverageMode == CoverageMode::kArea) {
+                        coverage = get_area_coverage(fEdgeAA, fCorners, pixelCenter);
+                    } else if (fCoverageMode == CoverageMode::kEdgeDistance) {
+                        coverage = get_edge_dist_coverage(fEdgeAA, fCorners, outsets, insets,
+                                                          pixelCenter);
+                    } else {
+                        SkASSERT(fCoverageMode == CoverageMode::kGPUMesh);
+                        coverage = get_framed_coverage(gpuOutset, gpuOutsetCoverage,
+                                                       gpuInset, gpuInsetCoverage, pixelCenter);
+                    }
+
+                    SkRect pixelRect = SkRect::MakeXYWH(px, py, 1.f, 1.f);
+                    pixelRect.inset(0.1f, 0.1f);
+
+                    SkScalar a = 1.f - 0.5f * coverage;
+                    pixelPaint.setColor4f({a, a, a, 1.f}, nullptr);
+                    canvas->drawRect(pixelRect, pixelPaint);
+
+                    pixelPaint.setColor(coverage > 0.f ? SK_ColorGREEN : SK_ColorRED);
+                    pixelRect.inset(0.38f, 0.38f);
+                    canvas->drawRect(pixelRect, pixelPaint);
+                }
+            }
+
+            linePaint.setPathEffect(dashes);
+            // Draw the inset/outset "infinite" lines
+            if (fCoverageMode == CoverageMode::kEdgeDistance) {
+                for (int i = 0; i < 4; ++i) {
+                    if (fEdgeAA[i]) {
+                        linePaint.setColor(SK_ColorBLUE);
+                        draw_extended_line(canvas, linePaint, outsets[i * 2], outsets[i * 2 + 1]);
+                        linePaint.setColor(SK_ColorGREEN);
+                        draw_extended_line(canvas, linePaint, insets[i * 2], insets[i * 2 + 1]);
+                    } else {
+                        // Both outset and inset are the same line, so only draw one in cyan
+                        linePaint.setColor(SK_ColorCYAN);
+                        draw_extended_line(canvas, linePaint, outsets[i * 2], outsets[i * 2 + 1]);
+                    }
+                }
+            }
+
+            linePaint.setPathEffect(nullptr);
+            // What is tessellated using GrQuadPerEdgeAA
+            if (fCoverageMode == CoverageMode::kGPUMesh) {
+                SkPath outsetPath;
+                outsetPath.addPoly(gpuOutset, 4, true);
+                linePaint.setColor(SK_ColorBLUE);
+                canvas->drawPath(outsetPath, linePaint);
+
+                SkPath insetPath;
+                insetPath.addPoly(gpuInset, 4, true);
+                linePaint.setColor(SK_ColorGREEN);
+                canvas->drawPath(insetPath, linePaint);
+            }
+
+            // Draw the edges of the true quad as a solid line
+            SkPath path;
+            path.addPoly(fCorners, 4, true);
+            linePaint.setColor(SK_ColorBLACK);
+            canvas->drawPath(path, linePaint);
+        } else {
+            // Draw the edges of the true quad as a solid *red* line
+            SkPath path;
+            path.addPoly(fCorners, 4, true);
+            linePaint.setColor(SK_ColorRED);
+            linePaint.setPathEffect(nullptr);
+            canvas->drawPath(path, linePaint);
+        }
+
+        // Draw the four clickable corners as circles
+        circlePaint.setColor(valid ? SK_ColorBLACK : SK_ColorRED);
+        for (int i = 0; i < 4; ++i) {
+            canvas->drawCircle(fCorners[i], 5.f / kViewScale, circlePaint);
+        }
+    }
+
+    Sample::Click* onFindClickHandler(SkScalar x, SkScalar y,
+                                      unsigned) override;
+    bool onClick(Sample::Click*) override;
+    bool onQuery(Sample::Event* evt) override;
+
+private:
+    class Click;
+
+    enum class CoverageMode {
+        kArea, kEdgeDistance, kGPUMesh
+    };
+
+    const SkRect fOuterRect;
+    SkPoint fCorners[4]; // TL, TR, BR, BL
+    bool fEdgeAA[4]; // T, R, B, L
+    CoverageMode fCoverageMode;
+
+    bool isValid() const {
+        SkPath path;
+        path.addPoly(fCorners, 4, true);
+        return path.isConvex();
+    }
+
+    void getTessellatedPoints(SkPoint inset[4], SkScalar insetCoverage[4], SkPoint outset[4],
+                              SkScalar outsetCoverage[4]) const {
+        // Fixed vertex spec for extracting the picture frame geometry
+        static const GrQuadPerEdgeAA::VertexSpec kSpec =
+            {GrQuadType::kStandard, GrQuadPerEdgeAA::ColorType::kNone,
+             GrQuadType::kRect, false, GrQuadPerEdgeAA::Domain::kNo,
+             GrAAType::kCoverage, false};
+        static const GrPerspQuad kIgnored(SkRect::MakeEmpty());
+
+        GrQuadAAFlags flags = GrQuadAAFlags::kNone;
+        flags |= fEdgeAA[0] ? GrQuadAAFlags::kTop : GrQuadAAFlags::kNone;
+        flags |= fEdgeAA[1] ? GrQuadAAFlags::kRight : GrQuadAAFlags::kNone;
+        flags |= fEdgeAA[2] ? GrQuadAAFlags::kBottom : GrQuadAAFlags::kNone;
+        flags |= fEdgeAA[3] ? GrQuadAAFlags::kLeft : GrQuadAAFlags::kNone;
+
+        GrPerspQuad quad = GrPerspQuad::MakeFromSkQuad(fCorners, SkMatrix::I());
+
+        float vertices[24]; // 2 quads, with x, y, and coverage
+        GrQuadPerEdgeAA::Tessellate(vertices, kSpec, quad, {1.f, 1.f, 1.f, 1.f},
+                GrPerspQuad(SkRect::MakeEmpty()), SkRect::MakeEmpty(), flags);
+
+        // The first quad in vertices is the inset, then the outset, but they
+        // are ordered TL, BL, TR, BR so un-interleave coverage and re-arrange
+        inset[0] = {vertices[0], vertices[1]}; // TL
+        insetCoverage[0] = vertices[2];
+        inset[3] = {vertices[3], vertices[4]}; // BL
+        insetCoverage[3] = vertices[5];
+        inset[1] = {vertices[6], vertices[7]}; // TR
+        insetCoverage[1] = vertices[8];
+        inset[2] = {vertices[9], vertices[10]}; // BR
+        insetCoverage[2] = vertices[11];
+
+        outset[0] = {vertices[12], vertices[13]}; // TL
+        outsetCoverage[0] = vertices[14];
+        outset[3] = {vertices[15], vertices[16]}; // BL
+        outsetCoverage[3] = vertices[17];
+        outset[1] = {vertices[18], vertices[19]}; // TR
+        outsetCoverage[1] = vertices[20];
+        outset[2] = {vertices[21], vertices[22]}; // BR
+        outsetCoverage[2] = vertices[23];
+    }
+
+    typedef Sample INHERITED;
+};
+
+class DegenerateQuadSample::Click : public Sample::Click {
+public:
+    Click(Sample* target, const SkRect& clamp, int index)
+            : Sample::Click(target)
+            , fOuterRect(clamp)
+            , fIndex(index) {}
+
+    void doClick(SkPoint points[4]) {
+        if (fIndex >= 0) {
+            this->drag(&points[fIndex]);
+        } else {
+            for (int i = 0; i < 4; ++i) {
+                this->drag(&points[i]);
+            }
+        }
+    }
+
+private:
+    SkRect fOuterRect;
+    int fIndex;
+
+    void drag(SkPoint* point) {
+        SkIPoint delta = fICurr - fIPrev;
+        *point += SkPoint::Make(delta.x() / kViewScale, delta.y() / kViewScale);
+        point->fX = SkMinScalar(fOuterRect.fRight, SkMaxScalar(point->fX, fOuterRect.fLeft));
+        point->fY = SkMinScalar(fOuterRect.fBottom, SkMaxScalar(point->fY, fOuterRect.fTop));
+    }
+};
+
+Sample::Click* DegenerateQuadSample::onFindClickHandler(SkScalar x, SkScalar y, unsigned) {
+    SkPoint inCTM = SkPoint::Make((x - kViewOffset) / kViewScale, (y - kViewOffset) / kViewScale);
+    for (int i = 0; i < 4; ++i) {
+        if ((fCorners[i] - inCTM).length() < 10.f / kViewScale) {
+            return new Click(this, fOuterRect, i);
+        }
+    }
+    return new Click(this, fOuterRect, -1);
+}
+
+bool DegenerateQuadSample::onClick(Sample::Click* click) {
+    Click* myClick = (Click*) click;
+    myClick->doClick(fCorners);
+    return true;
+}
+
+bool DegenerateQuadSample::onQuery(Sample::Event* event) {
+    if (Sample::TitleQ(*event)) {
+        Sample::TitleR(event, "DegenerateQuad");
+        return true;
+    }
+    SkUnichar code;
+    if (Sample::CharQ(*event, &code)) {
+        switch(code) {
+            case '1':
+                fEdgeAA[0] = !fEdgeAA[0];
+                return true;
+            case '2':
+                fEdgeAA[1] = !fEdgeAA[1];
+                return true;
+            case '3':
+                fEdgeAA[2] = !fEdgeAA[2];
+                return true;
+            case '4':
+                fEdgeAA[3] = !fEdgeAA[3];
+                return true;
+            case 'q':
+                fCoverageMode = CoverageMode::kArea;
+                return true;
+            case 'w':
+                fCoverageMode = CoverageMode::kEdgeDistance;
+                return true;
+            case 'e':
+                fCoverageMode = CoverageMode::kGPUMesh;
+                return true;
+        }
+    }
+    return this->INHERITED::onQuery(event);
+}
+
+DEF_SAMPLE(return new DegenerateQuadSample(SkRect::MakeWH(4.f, 4.f));)
diff --git a/samplecode/SampleDegenerateTwoPtRadials.cpp b/samplecode/SampleDegenerateTwoPtRadials.cpp
index 7392c66..6721669 100644
--- a/samplecode/SampleDegenerateTwoPtRadials.cpp
+++ b/samplecode/SampleDegenerateTwoPtRadials.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "AnimTimer.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
 #include "SkCanvas.h"
 #include "SkFont.h"
 #include "SkGradientShader.h"
@@ -30,7 +30,7 @@
     SkPaint paint;
     paint.setShader(SkGradientShader::MakeTwoPointConical(c0, r0, c1, r1, colors,
                                                           pos, SK_ARRAY_COUNT(pos),
-                                                          SkShader::kClamp_TileMode));
+                                                          SkTileMode::kClamp));
     canvas->drawRect(rect, paint);
 }
 
@@ -73,7 +73,7 @@
                            SkFont(), SkPaint());
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         fTime = SkDoubleToScalar(timer.secs() / 15);
         return true;
     }
diff --git a/samplecode/SampleEffects.cpp b/samplecode/SampleEffects.cpp
index 4540cd7..31a4d37 100644
--- a/samplecode/SampleEffects.cpp
+++ b/samplecode/SampleEffects.cpp
@@ -36,7 +36,7 @@
     SkColor colors[] = { SK_ColorRED, COLOR, SK_ColorBLUE };
     SkPoint pts[] = { { 3, 0 }, { 7, 5 } };
     paint->setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, SK_ARRAY_COUNT(colors),
-                                                  SkShader::kMirror_TileMode));
+                                                  SkTileMode::kMirror));
 }
 
 static void paint_proc5(SkPaint* paint) {
diff --git a/samplecode/SampleEmboss.cpp b/samplecode/SampleEmboss.cpp
index 0217d9e..1e08fe1 100644
--- a/samplecode/SampleEmboss.cpp
+++ b/samplecode/SampleEmboss.cpp
@@ -48,7 +48,7 @@
         paint.setStyle(SkPaint::kStroke_Style);
         paint.setStrokeWidth(SkIntToScalar(10));
         paint.setMaskFilter(SkEmbossMaskFilter::Make(SkBlurMask::ConvertRadiusToSigma(4), fLight));
-        paint.setShader(SkShader::MakeColorShader(SK_ColorBLUE));
+        paint.setShader(SkShaders::Color(SK_ColorBLUE));
         paint.setDither(true);
 
         canvas->drawCircle(SkIntToScalar(50), SkIntToScalar(50),
diff --git a/samplecode/SampleFatBits.cpp b/samplecode/SampleFatBits.cpp
index 1e988e7..06735a2 100644
--- a/samplecode/SampleFatBits.cpp
+++ b/samplecode/SampleFatBits.cpp
@@ -23,7 +23,7 @@
 #include "SkString.h"
 #include "SkSurface.h"
 #include "SkTypes.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 class SkEvent;
 
@@ -105,8 +105,8 @@
         fBounds.set(0, 0, SkIntToScalar(width * zoom), SkIntToScalar(height * zoom));
         fMatrix.setScale(SkIntToScalar(zoom), SkIntToScalar(zoom));
         fInverse.setScale(SK_Scalar1 / zoom, SK_Scalar1 / zoom);
-        fShader0 = sk_tool_utils::create_checkerboard_shader(0xFFDDDDDD, 0xFFFFFFFF, zoom);
-        fShader1 = SkShader::MakeColorShader(SK_ColorWHITE);
+        fShader0 = ToolUtils::create_checkerboard_shader(0xFFDDDDDD, 0xFFFFFFFF, zoom);
+        fShader1 = SkShaders::Color(SK_ColorWHITE);
         fShader = fShader0;
 
         SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
diff --git a/samplecode/SampleFilterQuality.cpp b/samplecode/SampleFilterQuality.cpp
index 69ade73..6d1d465 100644
--- a/samplecode/SampleFilterQuality.cpp
+++ b/samplecode/SampleFilterQuality.cpp
@@ -5,17 +5,17 @@
  * found in the LICENSE file.
  */
 
+#include "AnimTimer.h"
 #include "Resources.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
 #include "SkCanvas.h"
-#include "SkInterpolator.h"
+#include "SkData.h"
 #include "SkFont.h"
 #include "SkGradientShader.h"
-#include "SkData.h"
+#include "SkInterpolator.h"
 #include "SkPath.h"
-#include "SkSurface.h"
 #include "SkRandom.h"
+#include "SkSurface.h"
 #include "SkTime.h"
 
 static sk_sp<SkSurface> make_surface(SkCanvas* canvas, const SkImageInfo& info) {
@@ -283,7 +283,7 @@
         canvas->drawString(SkStringPrintf("%.8g", trans[1]     ), textX, 250, font, paint);
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         fCurrTime = timer.msec();
         return true;
     }
diff --git a/samplecode/SampleFlutterAnimate.cpp b/samplecode/SampleFlutterAnimate.cpp
index bffee4a..0953fe8 100644
--- a/samplecode/SampleFlutterAnimate.cpp
+++ b/samplecode/SampleFlutterAnimate.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
+#include "AnimTimer.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
 #include "SkCanvas.h"
-#include "SkFont.h"
-#include "SkColorPriv.h"
 #include "SkColorFilter.h"
+#include "SkColorPriv.h"
+#include "SkFont.h"
 #include "SkImage.h"
 #include "SkRandom.h"
 #include "SkTime.h"
@@ -64,7 +64,7 @@
         }
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         fCurrTime = timer.secs() - fResetTime;
         if (fCurrTime > kDuration) {
             this->initChars();
diff --git a/samplecode/SampleFuzz.cpp b/samplecode/SampleFuzz.cpp
deleted file mode 100644
index bf29de6..0000000
--- a/samplecode/SampleFuzz.cpp
+++ /dev/null
@@ -1,390 +0,0 @@
-/*
- * Copyright 2011 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "Sample.h"
-#include "SkCanvas.h"
-#include "SkMaskFilter.h"
-#include "SkPaint.h"
-#include "SkPath.h"
-#include "SkMatrix.h"
-#include "SkColor.h"
-#include "SkRandom.h"
-
-static void set2x3(SkMatrix* m, float a, float b, float c, float d, float e, float f) {
-    m->reset();
-    m->set(0, a);
-    m->set(1, b);
-    m->set(2, c);
-    m->set(3, d);
-    m->set(4, e);
-    m->set(5, f);
-}
-
-static SkRandom gRand;
-static bool return_large;
-static bool return_undef;
-static bool quick;
-static bool scale_large;
-static int scval = 1;
-static float transval = 0;
-
-static int R(float x) {
-  return (int)floor(SkScalarToFloat(gRand.nextUScalar1()) * x);
-}
-
-#if defined _WIN32
-#pragma warning ( push )
-// we are intentionally causing an overflow here
-//      (warning C4756: overflow in constant arithmetic)
-#pragma warning ( disable : 4756 )
-#endif
-
-static float huge() {
-    double d = 1e100;
-    float f = (float)d;
-    return f;
-}
-
-#if defined _WIN32
-#pragma warning ( pop )
-#endif
-
-static float make_number() {
-  float v = 0;
-  int sel;
-
-  if (return_large == true && R(3) == 1) {
-      sel = R(6);
-  } else {
-      sel = R(4);
-  }
-
-  if (return_undef == false && sel == 0) {
-      sel = 1;
-  }
-
-  if (R(2) == 1) {
-      v = (float)R(100);
-  } else {
-
-      switch (sel) {
-        case 0: break;
-        case 1: v = 0; break;
-        case 2: v = 0.000001f; break;
-        case 3: v = 10000; break;
-        case 4: v = 2000000000; break;
-        case 5: v = huge(); break;
-      }
-
-  }
-
-  if (R(4) == 1) {
-      v = -v;
-  }
-
-  return v;
-}
-
-static SkColor make_color() {
-  if (R(2) == 1) return 0xFFC0F0A0; else return 0xFF000090;
-}
-
-
-static SkColor make_fill() {
-#if 0
-  int sel;
-
-  if (quick == true) sel = 0; else sel = R(6);
-
-  switch (sel) {
-
-    case 0:
-    case 1:
-    case 2:
-      return make_color();
-      break;
-
-    case 3:
-      var r = ctx.createLinearGradient(make_number(),make_number(),make_number(),make_number());
-      for (i=0;i<4;i++)
-        r.addColorStop(make_number(),make_color());
-      return r;
-      break;
-
-    case 4:
-      var r = ctx.createRadialGradient(make_number(),make_number(),make_number(),make_number(),make_number(),make_number());
-      for (i=0;i<4;i++)
-        r.addColorStop(make_number(),make_color());
-      return r;
-      break;
-
-    case 5:
-      var r = ctx.createPattern(imgObj,"repeat");
-      if (R(6) == 0)
-        r.addColorStop(make_number(),make_color());
-      return r;
-      break;
-  }
-#else
-    return make_color();
-#endif
-}
-
-
-static void do_fuzz(SkCanvas* canvas) {
-    SkPath path;
-    SkPaint paint;
-    paint.setAntiAlias(true);
-
-  for (int i=0;i<100;i++) {
-  switch (R(33)) {
-
-    case 0:
-          paint.setColor(make_fill());
-      break;
-
-    case 1:
-      paint.setAlpha(gRand.nextU() & 0xFF);
-      break;
-
-      case 2: {
-          SkBlendMode mode;
-          switch (R(3)) {
-            case 0: mode = SkBlendMode::kSrc; break;
-            case 1: mode = SkBlendMode::kXor; break;
-            case 2:
-            default:  // silence warning
-              mode = SkBlendMode::kSrcOver; break;
-          }
-          paint.setBlendMode(mode);
-      }
-      break;
-
-    case 3:
-      switch (R(2)) {
-          case 0: paint.setStrokeCap(SkPaint::kRound_Cap); break;
-        case 1: paint.setStrokeCap(SkPaint::kButt_Cap); break;
-      }
-      break;
-
-    case 4:
-      switch (R(2)) {
-          case 0: paint.setStrokeJoin(SkPaint::kRound_Join); break;
-        case 1: paint.setStrokeJoin(SkPaint::kMiter_Join); break;
-      }
-      break;
-
-    case 5:
-      paint.setStrokeWidth(make_number());
-      break;
-
-    case 6:
-      paint.setStrokeMiter(make_number());
-      break;
-
-    case 7:
-      if (quick == true) break;
-      paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, make_number()));
-      break;
-
-    case 8:
-      if (quick == true) break;
-      //ctx.shadowColor = make_fill();
-      break;
-
-    case 9:
-      if (quick == true) break;
-      //ctx.shadowOffsetX = make_number();
-      //ctx.shadowOffsetY = make_number();
-      break;
-
-    case 10:
-      canvas->restore();
-      break;
-
-    case 11:
-      canvas->rotate(make_number());
-      break;
-
-    case 12:
-      canvas->save();
-      break;
-
-    case 13:
-      canvas->scale(-1,-1);
-      break;
-
-    case 14:
-
-      if (quick == true) break;
-
-      if (transval == 0) {
-        transval = make_number();
-        canvas->translate(transval,0);
-      } else {
-        canvas->translate(-transval,0);
-        transval = 0;
-      }
-
-      break;
-
-          case 15: {
-              SkRect r;
-              r.set(make_number(),make_number(),make_number(),make_number());
-              SkPaint::Style s = paint.getStyle();
-              paint.setStyle(SkPaint::kFill_Style);
-              canvas->drawRect(r, paint);
-              paint.setStyle(s);
-              // clearrect
-          } break;
-
-    case 16:
-      if (quick == true) break;
-//      ctx.drawImage(imgObj,make_number(),make_number(),make_number(),make_number(),make_number(),make_number(),make_number(),make_number());
-      break;
-
-          case 17: {
-          SkRect r;
-          r.set(make_number(),make_number(),make_number(),make_number());
-              SkPaint::Style s = paint.getStyle();
-              paint.setStyle(SkPaint::kFill_Style);
-          canvas->drawRect(r, paint);
-              paint.setStyle(s);
-          } break;
-
-    case 18:
-          path.reset();
-      break;
-
-    case 19:
-      // ctx.clip() is evil.
-      break;
-
-    case 20:
-          path.close();
-      break;
-
-          case 21: {
-          SkPaint::Style s = paint.getStyle();
-          paint.setStyle(SkPaint::kFill_Style);
-          canvas->drawPath(path, paint);
-          paint.setStyle(s);
-          } break;
-
-          case 22: {
-              SkPaint::Style s = paint.getStyle();
-              paint.setStyle(SkPaint::kFill_Style);
-              canvas->drawPath(path, paint);
-              paint.setStyle(s);
-          } break;
-
-          case 23: {
-              SkRect r;
-              r.set(make_number(),make_number(),make_number(),make_number());
-              SkPaint::Style s = paint.getStyle();
-              paint.setStyle(SkPaint::kStroke_Style);
-              canvas->drawRect(r, paint);
-              paint.setStyle(s);
-          } break;
-
-    case 24:
-      if (quick == true) break;
-      //ctx.arc(make_number(),make_number(),make_number(),make_number(),make_number(),true);
-      break;
-
-    case 25:
-      if (quick == true) break;
-      //ctx.arcTo(make_number(),make_number(),make_number(),make_number(),make_number());
-      break;
-
-    case 26:
-      if (quick == true) break;
-      //ctx.bezierCurveTo(make_number(),make_number(),make_number(),make_number(),make_number(),make_number());
-      break;
-
-    case 27:
-      path.lineTo(make_number(),make_number());
-      break;
-
-    case 28:
-      path.moveTo(make_number(),make_number());
-      break;
-
-    case 29:
-      if (quick == true) break;
-      path.quadTo(make_number(),make_number(),make_number(),make_number());
-      break;
-
-          case 30: {
-      if (quick == true) break;
-              SkMatrix matrix;
-      set2x3(&matrix, make_number(),make_number(),make_number(),make_number(),make_number(),make_number());
-              canvas->concat(matrix);
-          } break;
-
-          case 31: {
-      if (quick == true) break;
-          SkMatrix matrix;
-          set2x3(&matrix, make_number(),make_number(),make_number(),make_number(),make_number(),make_number());
-          canvas->setMatrix(matrix);
-          } break;
-
-    case 32:
-
-      if (scale_large == true) {
-
-        switch (scval) {
-          case 0: canvas->scale(-1000000000,1);
-                  canvas->scale(-1000000000,1);
-                  scval = 1; break;
-          case 1: canvas->scale(-.000000001f,1); scval = 2; break;
-          case 2: canvas->scale(-.000000001f,1); scval = 0; break;
-        }
-
-      }
-
-      break;
-
-
-
-  }
-  }
-
-}
-
-//////////////////////////////////////////////////////////////////////////////
-
-class FuzzView : public Sample {
-public:
-    FuzzView() {
-        this->setBGColor(0xFFDDDDDD);
-    }
-
-protected:
-    virtual bool onQuery(Sample::Event* evt) {
-        if (Sample::TitleQ(*evt)) {
-            Sample::TitleR(evt, "Fuzzer");
-            return true;
-        }
-        return this->INHERITED::onQuery(evt);
-    }
-
-    void drawBG(SkCanvas* canvas) {
-        canvas->drawColor(0xFFDDDDDD);
-    }
-
-    virtual void onDrawContent(SkCanvas* canvas) {
-        do_fuzz(canvas);
-    }
-
-private:
-    typedef Sample INHERITED;
-};
-
-//////////////////////////////////////////////////////////////////////////////
-
-DEF_SAMPLE( return new FuzzView(); )
diff --git a/samplecode/SampleGlyphTransform.cpp b/samplecode/SampleGlyphTransform.cpp
index ff42251..497a6be 100644
--- a/samplecode/SampleGlyphTransform.cpp
+++ b/samplecode/SampleGlyphTransform.cpp
@@ -5,13 +5,13 @@
  * found in the LICENSE file.
  */
 #include "Sample.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
-#include "SkAnimTimer.h"
+#include "AnimTimer.h"
 #include "SkCanvas.h"
 #include "SkPath.h"
-#include "SkRandom.h"
 #include "SkRRect.h"
+#include "SkRandom.h"
 #include "SkTypeface.h"
 
 #include <cmath>
@@ -26,8 +26,8 @@
 
 protected:
     void onOnceBeforeDraw() override {
-        fEmojiFont.fTypeface = sk_tool_utils::emoji_typeface();
-        fEmojiFont.fText = sk_tool_utils::emoji_sample_text();
+        fEmojiFont.fTypeface = ToolUtils::emoji_typeface();
+        fEmojiFont.fText     = ToolUtils::emoji_sample_text();
     }
 
     bool onQuery(Sample::Event* evt) override {
@@ -60,7 +60,7 @@
                                font, paint);
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         constexpr SkScalar maxt = 100000;
         double t = timer.pingPong(20, 0, 0, maxt); // d3 t is in milliseconds
 
diff --git a/samplecode/SampleGradients.cpp b/samplecode/SampleGradients.cpp
index 2ec26a5..a0ae5d4 100644
--- a/samplecode/SampleGradients.cpp
+++ b/samplecode/SampleGradients.cpp
@@ -11,7 +11,7 @@
 static sk_sp<SkShader> setgrad(const SkRect& r, SkColor c0, SkColor c1) {
     SkColor colors[] = { c0, c1 };
     SkPoint pts[] = { { r.fLeft, r.fTop }, { r.fRight, r.fTop } };
-    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode);
+    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
 }
 
 static void test_alphagradients(SkCanvas* canvas) {
@@ -61,11 +61,11 @@
     { 5, gColors, gPos2 }
 };
 
-static sk_sp<SkShader> MakeLinear(const SkPoint pts[2], const GradData& data, SkShader::TileMode tm) {
+static sk_sp<SkShader> MakeLinear(const SkPoint pts[2], const GradData& data, SkTileMode tm) {
     return SkGradientShader::MakeLinear(pts, data.fColors, data.fPos, data.fCount, tm);
 }
 
-static sk_sp<SkShader> MakeRadial(const SkPoint pts[2], const GradData& data, SkShader::TileMode tm) {
+static sk_sp<SkShader> MakeRadial(const SkPoint pts[2], const GradData& data, SkTileMode tm) {
     SkPoint center;
     center.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
@@ -73,14 +73,14 @@
                                         data.fPos, data.fCount, tm);
 }
 
-static sk_sp<SkShader> MakeSweep(const SkPoint pts[2], const GradData& data, SkShader::TileMode tm) {
+static sk_sp<SkShader> MakeSweep(const SkPoint pts[2], const GradData& data, SkTileMode tm) {
     SkPoint center;
     center.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
     return SkGradientShader::MakeSweep(center.fX, center.fY, data.fColors, data.fPos, data.fCount);
 }
 
-static sk_sp<SkShader> Make2Conical(const SkPoint pts[2], const GradData& data, SkShader::TileMode tm) {
+static sk_sp<SkShader> Make2Conical(const SkPoint pts[2], const GradData& data, SkTileMode tm) {
     SkPoint center0, center1;
     center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                 SkScalarAve(pts[0].fY, pts[1].fY));
@@ -93,7 +93,7 @@
 }
 
 static sk_sp<SkShader> Make2ConicalConcentric(const SkPoint pts[2], const GradData& data,
-                                       SkShader::TileMode tm) {
+                                       SkTileMode tm) {
     SkPoint center;
     center.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
@@ -103,7 +103,7 @@
                             data.fColors, data.fPos, data.fCount, tm);
 }
 
-typedef sk_sp<SkShader> (*GradMaker)(const SkPoint pts[2], const GradData& data, SkShader::TileMode tm);
+typedef sk_sp<SkShader> (*GradMaker)(const SkPoint pts[2], const GradData& data, SkTileMode tm);
 
 static const GradMaker gGradMakers[] = {
     MakeLinear, MakeRadial, MakeSweep, Make2Conical, Make2ConicalConcentric
@@ -138,12 +138,12 @@
         canvas->save();
         canvas->translate(SkIntToScalar(20), SkIntToScalar(10));
 
-        for (int tm = 0; tm < SkShader::kTileModeCount; ++tm) {
+        for (int tm = 0; tm < kSkTileModeCount; ++tm) {
             canvas->save();
             for (size_t i = 0; i < SK_ARRAY_COUNT(gGradData); i++) {
                 canvas->save();
                 for (size_t j = 0; j < SK_ARRAY_COUNT(gGradMakers); j++) {
-                    paint.setShader(gGradMakers[j](pts, gGradData[i], (SkShader::TileMode)tm));
+                    paint.setShader(gGradMakers[j](pts, gGradData[i], (SkTileMode)tm));
                     canvas->drawRect(r, paint);
                     canvas->translate(0, SkIntToScalar(120));
                 }
diff --git a/samplecode/SampleHT.cpp b/samplecode/SampleHT.cpp
index b12aa27..05b9aec 100644
--- a/samplecode/SampleHT.cpp
+++ b/samplecode/SampleHT.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "AnimTimer.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
 #include "SkCanvas.h"
 #include "SkDrawable.h"
 #include "SkInterpolator.h"
@@ -154,7 +154,7 @@
         canvas->drawDrawable(fRoot.get());
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         fTime = timer.msec();
         for (int i = 0; i < N; ++i) {
             fArray[i].fDrawable->setTime(fTime);
diff --git a/samplecode/SampleHairModes.cpp b/samplecode/SampleHairModes.cpp
index d162bba..781d212 100644
--- a/samplecode/SampleHairModes.cpp
+++ b/samplecode/SampleHairModes.cpp
@@ -64,8 +64,7 @@
     SkMatrix m;
     m.setScale(SkIntToScalar(6), SkIntToScalar(6));
 
-    return SkShader::MakeBitmapShader(bm, SkShader::kRepeat_TileMode,
-                                      SkShader::kRepeat_TileMode, &m);
+    return bm.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &m);
 }
 
 class HairModesView : public Sample {
diff --git a/samplecode/SampleHairline.cpp b/samplecode/SampleHairline.cpp
index 1ebbc1e..f6ec0ca 100644
--- a/samplecode/SampleHairline.cpp
+++ b/samplecode/SampleHairline.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "AnimTimer.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
 #include "SkBitmap.h"
 #include "SkCanvas.h"
 #include "SkColorFilter.h"
@@ -216,7 +216,7 @@
         canvas->drawBitmap(bm2, SkIntToScalar(10), SkIntToScalar(10), nullptr);
     }
 
-    bool onAnimate(const SkAnimTimer&) override {
+    bool onAnimate(const AnimTimer&) override {
         if (fDoAA) {
             fProcIndex = cycle_hairproc_index(fProcIndex);
             // todo: signal that we want to rebuild our TITLE
diff --git a/samplecode/SampleLayers.cpp b/samplecode/SampleLayers.cpp
index 33cbe4d..b4b51a9 100644
--- a/samplecode/SampleLayers.cpp
+++ b/samplecode/SampleLayers.cpp
@@ -27,7 +27,7 @@
     SkColor colors[] = { 0, SK_ColorWHITE };
     SkPoint pts[] = { { 0, 0 }, { 0, SK_Scalar1*20 } };
     paint->setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, 2,
-                                                  SkShader::kClamp_TileMode, 0, &localMatrix));
+                                                  SkTileMode::kClamp, 0, &localMatrix));
     paint->setBlendMode(SkBlendMode::kDstIn);
 }
 
@@ -184,8 +184,8 @@
 #include "SkMatrixConvolutionImageFilter.h"
 #include "SkMorphologyImageFilter.h"
 
+#include "AnimTimer.h"
 #include "Resources.h"
-#include "SkAnimTimer.h"
 
 class BackdropView : public Sample {
     SkPoint fCenter;
@@ -231,7 +231,7 @@
         canvas->restore();
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         fAngle = SkDoubleToScalar(fmod(timer.secs() * 360 / 5, 360));
         return true;
     }
diff --git a/samplecode/SampleLighting.cpp b/samplecode/SampleLighting.cpp
index ef0694b..154b7c3 100644
--- a/samplecode/SampleLighting.cpp
+++ b/samplecode/SampleLighting.cpp
@@ -38,20 +38,14 @@
 
             fRect = SkRect::MakeIWH(diffuseBitmap.width(), diffuseBitmap.height());
 
-            fDiffuseShader = SkShader::MakeBitmapShader(diffuseBitmap,
-                                                        SkShader::kClamp_TileMode,
-                                                        SkShader::kClamp_TileMode,
-                                                        nullptr);
+            fDiffuseShader = diffuseBitmap.makeShader();
         }
 
         {
             SkBitmap normalBitmap;
             SkAssertResult(GetResourceAsBitmap("images/brickwork_normal-map.jpg", &normalBitmap));
 
-            sk_sp<SkShader> normalMap = SkShader::MakeBitmapShader(normalBitmap,
-                                                                   SkShader::kClamp_TileMode,
-                                                                   SkShader::kClamp_TileMode,
-                                                                   nullptr);
+            sk_sp<SkShader> normalMap = normalBitmap.makeShader();
             fNormalSource = SkNormalSource::MakeFromNormalMap(std::move(normalMap), SkMatrix::I());
         }
     }
@@ -81,7 +75,7 @@
         return this->INHERITED::onFindClickHandler(x, y, modi);
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         fLightAngle += 0.015f;
         fColorFactor += 0.01f;
         if (fColorFactor > 1.0f) {
diff --git a/samplecode/SampleLitAtlas.cpp b/samplecode/SampleLitAtlas.cpp
index 534b66b..b2d7f97 100644
--- a/samplecode/SampleLitAtlas.cpp
+++ b/samplecode/SampleLitAtlas.cpp
@@ -5,18 +5,18 @@
  * found in the LICENSE file.
  */
 
+#include "AnimTimer.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
 #include "SkBitmapProcShader.h"
 #include "SkCanvas.h"
 #include "SkDrawable.h"
 #include "SkLightingShader.h"
 #include "SkLights.h"
 #include "SkNormalSource.h"
-#include "SkRandom.h"
 #include "SkRSXform.h"
+#include "SkRandom.h"
 
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 // A crude normal mapped asteroids-like sample
 class DrawLitAtlasDrawable : public SkDrawable {
@@ -42,8 +42,9 @@
     }
 
     void rotateLight() {
-        SkScalar c;
-        SkScalar s = SkScalarSinCos(SK_ScalarPI/6.0f, &c);
+        SkScalar r = SK_ScalarPI / 6.0f,
+                 s = SkScalarSin(r),
+                 c = SkScalarCos(r);
 
         SkScalar newX = c * fLightDir.fX - s * fLightDir.fY;
         SkScalar newY = s * fLightDir.fX + c * fLightDir.fY;
@@ -65,8 +66,8 @@
     }
 
     void thrust() {
-        SkScalar c;
-        SkScalar s = SkScalarSinCos(fShip.rot(), &c);
+        SkScalar s = SkScalarSin(fShip.rot()),
+                 c = SkScalarCos(fShip.rot());
 
         SkVector newVel = fShip.velocity();
         newVel.fX += s;
@@ -131,12 +132,10 @@
             SkMatrix m;
             m.setRSXform(xforms[i]);
 
-            sk_sp<SkShader> normalMap = SkShader::MakeBitmapShader(fAtlas, SkShader::kClamp_TileMode,
-                    SkShader::kClamp_TileMode, &normalMat);
+            sk_sp<SkShader> normalMap = fAtlas.makeShader(&normalMat);
             sk_sp<SkNormalSource> normalSource = SkNormalSource::MakeFromNormalMap(
                     std::move(normalMap), m);
-            sk_sp<SkShader> diffuseShader = SkShader::MakeBitmapShader(fAtlas,
-                    SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, &diffMat);
+            sk_sp<SkShader> diffuseShader = fAtlas.makeShader(&diffMat);
             paint.setShader(SkLightingShader::Make(std::move(diffuseShader),
                     std::move(normalSource), fLights));
 
@@ -251,9 +250,8 @@
                 }
             }
 
-            sk_tool_utils::create_hemi_normal_map(&atlas,
-                                                  SkIRect::MakeXYWH(kNormXOff, kBigYOff,
-                                                                    kBigSize, kBigSize));
+            ToolUtils::create_hemi_normal_map(
+                    &atlas, SkIRect::MakeXYWH(kNormXOff, kBigYOff, kBigSize, kBigSize));
         }
 
         // medium asteroid
@@ -264,9 +262,8 @@
                 }
             }
 
-            sk_tool_utils::create_frustum_normal_map(&atlas,
-                                                     SkIRect::MakeXYWH(kNormXOff, kMedYOff,
-                                                                       kMedSize, kMedSize));
+            ToolUtils::create_frustum_normal_map(
+                    &atlas, SkIRect::MakeXYWH(kNormXOff, kMedYOff, kMedSize, kMedSize));
         }
 
         // small asteroid
@@ -285,9 +282,8 @@
                 }
             }
 
-            sk_tool_utils::create_hemi_normal_map(&atlas,
-                                                  SkIRect::MakeXYWH(kNormXOff, kSmYOff,
-                                                                    kSmSize, kSmSize));
+            ToolUtils::create_hemi_normal_map(
+                    &atlas, SkIRect::MakeXYWH(kNormXOff, kSmYOff, kSmSize, kSmSize));
         }
 
         // ship
@@ -314,9 +310,8 @@
                 }
             }
 
-            sk_tool_utils::create_tetra_normal_map(&atlas,
-                                                   SkIRect::MakeXYWH(kNormXOff, kShipYOff,
-                                                                     kMedSize, kMedSize));
+            ToolUtils::create_tetra_normal_map(
+                    &atlas, SkIRect::MakeXYWH(kNormXOff, kShipYOff, kMedSize, kMedSize));
         }
 
         return atlas;
@@ -486,9 +481,7 @@
         canvas->drawDrawable(fDrawable.get());
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
-        return true;
-    }
+    bool onAnimate(const AnimTimer& timer) override { return true; }
 
 private:
     sk_sp<DrawLitAtlasDrawable> fDrawable;
diff --git a/samplecode/SampleMegaStroke.cpp b/samplecode/SampleMegaStroke.cpp
index a8f349a..3248ab9 100644
--- a/samplecode/SampleMegaStroke.cpp
+++ b/samplecode/SampleMegaStroke.cpp
@@ -78,9 +78,7 @@
         fClip.set(0, 0, 950, 600);
     }
 
-    bool onAnimate(const SkAnimTimer& ) override {
-        return true;
-    }
+    bool onAnimate(const AnimTimer&) override { return true; }
 
 private:
     SkPath      fMegaPath;
diff --git a/samplecode/SampleMixer.cpp b/samplecode/SampleMixer.cpp
new file mode 100644
index 0000000..f9c5702
--- /dev/null
+++ b/samplecode/SampleMixer.cpp
@@ -0,0 +1,190 @@
+/*
+ * 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 "Sample.h"
+#include "SkCanvas.h"
+#include "SkColorFilter.h"
+#include "SkGradientShader.h"
+#include "SkImage.h"
+#include "SkPath.h"
+#include "SkRegion.h"
+#include "SkShader.h"
+#include "SkUtils.h"
+#include "Resources.h"
+
+const SkScalar gMat[] = {
+    .3f, .6f, .1f, 0, 0,
+    .3f, .6f, .1f, 0, 0,
+    .3f, .6f, .1f, 0, 0,
+      0,   0,   0, 1, 0,
+};
+
+class MixerView : public Sample {
+    sk_sp<SkImage>          fImg;
+    sk_sp<SkColorFilter>    fCF0;
+    sk_sp<SkColorFilter>    fCF1;
+
+    float fWeight = 0;
+    float fDW = 0.02f;
+
+public:
+    MixerView() {}
+
+protected:
+    bool onQuery(Event* evt) override {
+        if (Sample::TitleQ(*evt)) {
+            Sample::TitleR(evt, "Mixer");
+            return true;
+        }
+        return this->INHERITED::onQuery(evt);
+    }
+
+    void dodraw(SkCanvas* canvas, sk_sp<SkColorFilter> cf0, sk_sp<SkColorFilter> cf1, float gap) {
+        SkPaint paint;
+        paint.setColorFilter(cf0);
+        canvas->drawImage(fImg, 0, 0, &paint);
+
+        paint.setColorFilter(SkColorFilters::Lerp(fWeight, cf0, cf1));
+        canvas->drawImage(fImg, fImg->width() + gap * fWeight, 0, &paint);
+
+        paint.setColorFilter(cf1);
+        canvas->drawImage(fImg, 2*fImg->width() + gap, 0, &paint);
+    }
+
+    void onDrawContent(SkCanvas* canvas) override {
+        if (!fImg) {
+            fImg = GetResourceAsImage("images/mandrill_256.png");
+            fCF0 = SkColorFilters::MatrixRowMajor255(gMat);
+            fCF1 = SkColorFilters::Blend(0xFF44CC88, SkBlendMode::kScreen);
+        }
+
+        float gap = fImg->width() * 3;
+
+        canvas->translate(10, 10);
+        dodraw(canvas, nullptr, fCF1, gap);
+        canvas->translate(0, fImg->height() + 10);
+        dodraw(canvas, fCF0, nullptr, gap);
+        canvas->translate(0, fImg->height() + 10);
+        dodraw(canvas, fCF0, fCF1, gap);
+
+        fWeight += fDW;
+        if (fWeight > 1 || fWeight < 0) {
+            fDW = -fDW;
+        }
+    }
+
+    virtual Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned) override {
+        return fRect.contains(SkScalarRoundToInt(x),
+                              SkScalarRoundToInt(y)) ? new Click(this) : nullptr;
+    }
+
+    bool onClick(Click* click) override {
+        fRect.offset(click->fICurr.fX - click->fIPrev.fX,
+                     click->fICurr.fY - click->fIPrev.fY);
+        return true;
+    }
+
+private:
+    SkIRect fRect;
+
+    typedef Sample INHERITED;
+};
+DEF_SAMPLE( return new MixerView; )
+
+//////////////////////////////////////////////////////////////////////////////
+
+#include "SkMaskFilter.h"
+#include "SkSurface.h"
+
+static sk_sp<SkShader> make_resource_shader(const char path[], int size) {
+    auto img = GetResourceAsImage(path);
+    if (!img) {
+        return nullptr;
+    }
+    SkRect src = SkRect::MakeIWH(img->width(), img->height());
+    SkRect dst = SkRect::MakeIWH(size, size);
+    SkMatrix m;
+    m.setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
+    return img->makeShader(&m);
+}
+
+class ShaderMixerView : public Sample {
+    sk_sp<SkShader>     fSH0;
+    sk_sp<SkShader>     fSH1;
+    sk_sp<SkSurface>    fSurface;
+    SkBlendMode         fMode = SkBlendMode::kClear;
+
+    enum { SIZE = 256 };
+
+    const SkRect fRect = SkRect::MakeXYWH(10, 10 + SIZE + 10, SIZE, SIZE);
+
+public:
+    ShaderMixerView() {}
+
+    void onOnceBeforeDraw() override {
+        fSH0 = make_resource_shader("images/mandrill_256.png", SIZE);
+        fSH1 = make_resource_shader("images/baby_tux.png", SIZE);
+    }
+
+protected:
+    bool onQuery(Event* evt) override {
+        if (Sample::TitleQ(*evt)) {
+            Sample::TitleR(evt, "ShaderMixer");
+            return true;
+        }
+        return this->INHERITED::onQuery(evt);
+    }
+
+    void onDrawContent(SkCanvas* canvas) override {
+        if (!fSurface) {
+            fSurface = canvas->makeSurface(SkImageInfo::MakeN32Premul(SIZE, SIZE));
+        }
+
+        SkPaint paint;
+        const SkRect r = SkRect::MakeIWH(SIZE, SIZE);
+
+        canvas->translate(10, 10);
+
+        canvas->save();
+        paint.setShader(fSH0); canvas->drawRect(r, paint);
+        canvas->translate(SIZE + 10.f, 0);
+        paint.setShader(fSH1); canvas->drawRect(r, paint);
+        canvas->restore();
+
+        canvas->translate(0, SIZE + 10.f);
+
+        auto sh = fSurface->makeImageSnapshot()->makeShader();
+
+        canvas->save();
+        paint.setShader(sh); canvas->drawRect(r, paint);
+        canvas->translate(SIZE + 10.f, 0);
+        paint.setShader(SkShaders::Lerp(sh, fSH0, fSH1)); canvas->drawRect(r, paint);
+        canvas->restore();
+    }
+
+    virtual Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned) override {
+        fMode = (fMode == SkBlendMode::kSrcOver) ? SkBlendMode::kClear : SkBlendMode::kSrcOver;
+        return fRect.contains(SkScalarRoundToInt(x),
+                              SkScalarRoundToInt(y)) ? new Click(this) : nullptr;
+    }
+
+    bool onClick(Click* click) override {
+        SkPaint p;
+        p.setAntiAlias(true);
+        p.setColor(SK_ColorRED);
+        p.setBlendMode(fMode);
+        p.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 12));
+        SkScalar x = click->fCurr.fX - fRect.fLeft;
+        SkScalar y = click->fCurr.fY - fRect.fTop;
+        fSurface->getCanvas()->drawCircle(x, y, 10, p);
+        return true;
+    }
+
+private:
+    typedef Sample INHERITED;
+};
+DEF_SAMPLE( return new ShaderMixerView; )
diff --git a/samplecode/SampleNima.cpp b/samplecode/SampleNima.cpp
index 6912518..3ecd764 100644
--- a/samplecode/SampleNima.cpp
+++ b/samplecode/SampleNima.cpp
@@ -7,8 +7,8 @@
 
 #include "Sample.h"
 
+#include "AnimTimer.h"
 #include "Resources.h"
-#include "SkAnimTimer.h"
 #include "nima/NimaActor.h"
 
 #include <nima/Animation/ActorAnimationInstance.hpp>
@@ -54,7 +54,7 @@
         canvas->restore();
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         if (fActor) {
             float time = std::fmod(timer.secs(), fActor->duration());
             fActor->seek(time);
diff --git a/samplecode/SamplePatch.cpp b/samplecode/SamplePatch.cpp
index ba306d4..9bd9dbd 100644
--- a/samplecode/SamplePatch.cpp
+++ b/samplecode/SamplePatch.cpp
@@ -5,21 +5,21 @@
  * found in the LICENSE file.
  */
 
+#include "AnimTimer.h"
 #include "DecodeFile.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
 #include "SkCanvas.h"
+#include "SkColorFilter.h"
+#include "SkColorPriv.h"
 #include "SkGradientShader.h"
 #include "SkGraphics.h"
 #include "SkPath.h"
 #include "SkRandom.h"
 #include "SkRegion.h"
 #include "SkShader.h"
-#include "SkUTF.h"
-#include "SkColorPriv.h"
-#include "SkColorFilter.h"
 #include "SkTime.h"
 #include "SkTypeface.h"
+#include "SkUTF.h"
 #include "SkVertices.h"
 
 #include "SkOSFile.h"
@@ -33,8 +33,7 @@
 //    decode_file("/skimages/progressivejpg.jpg", &bm);
     decode_file("/skimages/logo.png", &bm);
     size->set(bm.width(), bm.height());
-    return SkShader::MakeBitmapShader(bm, SkShader::kClamp_TileMode,
-                                       SkShader::kClamp_TileMode);
+    return bm.makeShader();
 }
 
 static sk_sp<SkShader> make_shader1(const SkIPoint& size) {
@@ -42,7 +41,7 @@
                       { SkIntToScalar(size.fX), SkIntToScalar(size.fY) } };
     SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorRED };
     return SkGradientShader::MakeLinear(pts, colors, nullptr,
-                    SK_ARRAY_COUNT(colors), SkShader::kMirror_TileMode);
+                    SK_ARRAY_COUNT(colors), SkTileMode::kMirror);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -292,7 +291,7 @@
         drawpatches(canvas, paint, nu, nv, &patch);
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         fAngle = timer.scaled(60, 360);
         return true;
     }
@@ -326,7 +325,205 @@
 private:
     typedef Sample INHERITED;
 };
+DEF_SAMPLE( return new PatchView(); )
 
 //////////////////////////////////////////////////////////////////////////////
 
-DEF_SAMPLE( return new PatchView(); )
+#include "SkContourMeasure.h"
+#include "SkTDArray.h"
+
+static sk_sp<SkVertices> make_verts(const SkPath& path, SkScalar width) {
+    auto meas = SkContourMeasureIter(path, false).next();
+    if (!meas) {
+        return nullptr;
+    }
+
+    const SkPoint src[2] = {
+        { 0, -width/2 }, { 0, width/2 },
+    };
+    SkTDArray<SkPoint> pts;
+
+    const SkScalar step = 2;
+    for (SkScalar distance = 0; distance < meas->length(); distance += step) {
+        SkMatrix mx;
+        if (!meas->getMatrix(distance, &mx)) {
+            continue;
+        }
+        SkPoint* dst = pts.append(2);
+        mx.mapPoints(dst, src, 2);
+    }
+
+    int vertCount = pts.count();
+    int indexCount = 0; // no texture
+    unsigned flags = SkVertices::kHasColors_BuilderFlag |
+                     SkVertices::kIsNonVolatile_BuilderFlag;
+    SkVertices::Builder builder(SkVertices::kTriangleStrip_VertexMode,
+                                vertCount, indexCount, flags);
+    memcpy(builder.positions(), pts.begin(), vertCount * sizeof(SkPoint));
+    SkRandom rand;
+    for (int i = 0; i < vertCount; ++i) {
+        builder.colors()[i] = rand.nextU() | 0xFF000000;
+    }
+    SkDebugf("vert count = %d\n", vertCount);
+
+    return builder.detach();
+}
+
+class PseudoInkView : public Sample {
+    enum { N = 100 };
+    SkPath            fPath;
+    sk_sp<SkVertices> fVertices[N];
+    SkPaint           fSkeletonP, fStrokeP, fVertsP;
+    bool              fDirty = true;
+
+public:
+    PseudoInkView() {
+        fSkeletonP.setStyle(SkPaint::kStroke_Style);
+        fSkeletonP.setAntiAlias(true);
+
+        fStrokeP.setStyle(SkPaint::kStroke_Style);
+        fStrokeP.setStrokeWidth(30);
+        fStrokeP.setColor(0x44888888);
+    }
+
+protected:
+    bool onQuery(Sample::Event* evt)  override {
+        if (Sample::TitleQ(*evt)) {
+            Sample::TitleR(evt, "PseudoInk");
+            return true;
+        }
+        return this->INHERITED::onQuery(evt);
+    }
+
+    bool onAnimate(const AnimTimer& timer) override { return true; }
+
+    void onDrawContent(SkCanvas* canvas) override {
+        if (fDirty) {
+            for (int i = 0; i < N; ++i) {
+                fVertices[i] = make_verts(fPath, 30);
+            }
+            fDirty = false;
+        }
+        for (int i = 0; i < N; ++i) {
+            canvas->drawVertices(fVertices[i], SkBlendMode::kSrc, fVertsP);
+            canvas->translate(1, 1);
+        }
+//        canvas->drawPath(fPath, fStrokeP);
+ //       canvas->drawPath(fPath, fSkeletonP);
+    }
+
+    Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned modi) override {
+        Click* click = new Click(this);
+        fPath.reset();
+        fPath.moveTo(x, y);
+        return click;
+    }
+
+    bool onClick(Click* click) override {
+        switch (click->fState) {
+            case Click::kMoved_State:
+                fPath.lineTo(click->fCurr);
+                fDirty = true;
+                break;
+            default:
+                break;
+        }
+        return true;
+    }
+
+private:
+    typedef Sample INHERITED;
+};
+DEF_SAMPLE( return new PseudoInkView(); )
+
+#include "SkOpPathEffect.h"
+// Show stroking options using patheffects (and pathops)
+// and why strokeandfill is a hacks
+class ManyStrokesView : public Sample {
+    SkPath              fPath;
+    sk_sp<SkPathEffect> fPE[6];
+
+public:
+    ManyStrokesView() {
+        fPE[0] = SkStrokePathEffect::Make(20, SkPaint::kRound_Join, SkPaint::kRound_Cap);
+
+        auto p0 = SkStrokePathEffect::Make(25, SkPaint::kRound_Join, SkPaint::kRound_Cap);
+        auto p1 = SkStrokePathEffect::Make(20, SkPaint::kRound_Join, SkPaint::kRound_Cap);
+        fPE[1] = SkMergePathEffect::Make(p0, p1, SkPathOp::kDifference_SkPathOp);
+
+        fPE[2] = SkMergePathEffect::Make(nullptr, p1, SkPathOp::kDifference_SkPathOp);
+        fPE[3] = SkMergePathEffect::Make(nullptr, p1, SkPathOp::kUnion_SkPathOp);
+        fPE[4] = SkMergePathEffect::Make(p0, nullptr, SkPathOp::kDifference_SkPathOp);
+        fPE[5] = SkMergePathEffect::Make(p0, nullptr, SkPathOp::kIntersect_SkPathOp);
+    }
+
+protected:
+    bool onQuery(Sample::Event* evt)  override {
+        if (Sample::TitleQ(*evt)) {
+            Sample::TitleR(evt, "ManyStrokes");
+            return true;
+        }
+        return this->INHERITED::onQuery(evt);
+    }
+
+    bool onAnimate(const AnimTimer& timer) override { return true; }
+
+    void dodraw(SkCanvas* canvas, sk_sp<SkPathEffect> pe, SkScalar x, SkScalar y,
+                const SkPaint* ptr = nullptr) {
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setPathEffect(pe);
+        canvas->save();
+        canvas->translate(x, y);
+        canvas->drawPath(fPath, ptr ? *ptr : paint);
+
+        paint.setPathEffect(nullptr);
+        paint.setStyle(SkPaint::kStroke_Style);
+        paint.setColor(SK_ColorGREEN);
+        canvas->drawPath(fPath, paint);
+
+        canvas->restore();
+    }
+
+    void onDrawContent(SkCanvas* canvas) override {
+        SkPaint p;
+        p.setColor(0);
+        this->dodraw(canvas, nullptr, 0, 0, &p);
+
+        this->dodraw(canvas, fPE[0], 300, 0);
+        this->dodraw(canvas, fPE[1], 0, 300);
+        this->dodraw(canvas, fPE[2], 300, 300);
+        this->dodraw(canvas, fPE[3], 600, 300);
+        this->dodraw(canvas, fPE[4], 900, 0);
+        this->dodraw(canvas, fPE[5], 900, 300);
+
+        p.setColor(SK_ColorBLACK);
+        p.setStyle(SkPaint::kStrokeAndFill_Style);
+        p.setStrokeJoin(SkPaint::kRound_Join);
+        p.setStrokeCap(SkPaint::kRound_Cap);
+        p.setStrokeWidth(20);
+        this->dodraw(canvas, nullptr, 600, 0, &p);
+    }
+
+    Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned modi) override {
+        Click* click = new Click(this);
+        fPath.reset();
+        fPath.moveTo(x, y);
+        return click;
+    }
+
+    bool onClick(Click* click) override {
+        switch (click->fState) {
+            case Click::kMoved_State:
+                fPath.lineTo(click->fCurr);
+                break;
+            default:
+                break;
+        }
+        return true;
+    }
+
+private:
+    typedef Sample INHERITED;
+};
+DEF_SAMPLE( return new ManyStrokesView(); )
diff --git a/samplecode/SamplePath.cpp b/samplecode/SamplePath.cpp
index 74de725..0cd31ab 100644
--- a/samplecode/SamplePath.cpp
+++ b/samplecode/SamplePath.cpp
@@ -5,22 +5,22 @@
  * found in the LICENSE file.
  */
 
+#include "AnimTimer.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
 #include "SkBitmap.h"
 #include "SkCanvas.h"
+#include "SkColorFilter.h"
+#include "SkColorPriv.h"
+#include "SkFont.h"
 #include "SkGradientShader.h"
 #include "SkGraphics.h"
-#include "SkFont.h"
+#include "SkParsePath.h"
 #include "SkPath.h"
 #include "SkRegion.h"
 #include "SkShader.h"
-#include "SkUTF.h"
-#include "SkColorPriv.h"
-#include "SkColorFilter.h"
-#include "SkParsePath.h"
 #include "SkTime.h"
 #include "SkTypeface.h"
+#include "SkUTF.h"
 
 #include "SkGeometry.h"
 
@@ -183,7 +183,7 @@
         }
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         SkScalar currSecs = timer.scaled(100);
         SkScalar delta = currSecs - fPrevSecs;
         fPrevSecs = currSecs;
diff --git a/samplecode/SamplePathEffects.cpp b/samplecode/SamplePathEffects.cpp
index e59b64d..e4a7422 100644
--- a/samplecode/SamplePathEffects.cpp
+++ b/samplecode/SamplePathEffects.cpp
@@ -5,19 +5,19 @@
  * found in the LICENSE file.
  */
 
+#include "AnimTimer.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
+#include "Sk1DPathEffect.h"
 #include "SkCanvas.h"
+#include "SkColorPriv.h"
+#include "SkCornerPathEffect.h"
 #include "SkGradientShader.h"
 #include "SkPath.h"
+#include "SkPathMeasure.h"
+#include "SkRandom.h"
 #include "SkRegion.h"
 #include "SkShader.h"
 #include "SkUTF.h"
-#include "Sk1DPathEffect.h"
-#include "SkCornerPathEffect.h"
-#include "SkPathMeasure.h"
-#include "SkRandom.h"
-#include "SkColorPriv.h"
 
 #define CORNER_RADIUS   12
 
@@ -138,7 +138,7 @@
         canvas->drawPath(fPath, paint);
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         fPhase = timer.scaled(40);
         return true;
     }
diff --git a/samplecode/SamplePathFill.cpp b/samplecode/SamplePathFill.cpp
deleted file mode 100644
index 990daaa..0000000
--- a/samplecode/SamplePathFill.cpp
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright 2011 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-#include "Sample.h"
-#include "SkCanvas.h"
-#include "SkGraphics.h"
-#include "SkRandom.h"
-#include "SkBlurDrawLooper.h"
-#include "SkGradientShader.h"
-
-typedef SkScalar (*MakePathProc)(SkPath*);
-
-static SkScalar make_frame(SkPath* path) {
-    SkRect r = { 10, 10, 630, 470 };
-    path->addRoundRect(r, 15, 15);
-
-    SkPaint paint;
-    paint.setStyle(SkPaint::kStroke_Style);
-    paint.setStrokeWidth(5);
-    paint.getFillPath(*path, path);
-    return 15;
-}
-
-static SkScalar make_triangle(SkPath* path) {
-    static const int gCoord[] = {
-        10, 20, 15, 5, 30, 30
-    };
-    path->moveTo(SkIntToScalar(gCoord[0]), SkIntToScalar(gCoord[1]));
-    path->lineTo(SkIntToScalar(gCoord[2]), SkIntToScalar(gCoord[3]));
-    path->lineTo(SkIntToScalar(gCoord[4]), SkIntToScalar(gCoord[5]));
-    path->close();
-    path->offset(10, 0);
-    return SkIntToScalar(30);
-}
-
-static SkScalar make_rect(SkPath* path) {
-    SkRect r = { 10, 10, 30, 30 };
-    path->addRect(r);
-    path->offset(10, 0);
-    return SkIntToScalar(30);
-}
-
-static SkScalar make_oval(SkPath* path) {
-    SkRect r = { 10, 10, 30, 30 };
-    path->addOval(r);
-    path->offset(10, 0);
-    return SkIntToScalar(30);
-}
-
-static SkScalar make_sawtooth(SkPath* path) {
-    SkScalar x = SkIntToScalar(20);
-    SkScalar y = SkIntToScalar(20);
-    const SkScalar x0 = x;
-    const SkScalar dx = SK_Scalar1 * 5;
-    const SkScalar dy = SK_Scalar1 * 10;
-
-    path->moveTo(x, y);
-    for (int i = 0; i < 32; i++) {
-        x += dx;
-        path->lineTo(x, y - dy);
-        x += dx;
-        path->lineTo(x, y + dy);
-    }
-    path->lineTo(x, y + 2 * dy);
-    path->lineTo(x0, y + 2 * dy);
-    path->close();
-    return SkIntToScalar(30);
-}
-
-static SkScalar make_star(SkPath* path, int n) {
-    const SkScalar c = SkIntToScalar(45);
-    const SkScalar r = SkIntToScalar(20);
-
-    SkScalar rad = -SK_ScalarPI / 2;
-    const SkScalar drad = (n >> 1) * SK_ScalarPI * 2 / n;
-
-    path->moveTo(c, c - r);
-    for (int i = 1; i < n; i++) {
-        rad += drad;
-        SkScalar cosV, sinV = SkScalarSinCos(rad, &cosV);
-        path->lineTo(c + cosV * r, c + sinV * r);
-    }
-    path->close();
-    return r * 2 * 6 / 5;
-}
-
-static SkScalar make_star_5(SkPath* path) { return make_star(path, 5); }
-static SkScalar make_star_13(SkPath* path) { return make_star(path, 13); }
-
-static const MakePathProc gProcs[] = {
-    make_frame,
-    make_triangle,
-    make_rect,
-    make_oval,
-    make_sawtooth,
-    make_star_5,
-    make_star_13
-};
-
-#define N   SK_ARRAY_COUNT(gProcs)
-
-class PathFillView : public Sample {
-    SkPath  fPath[N];
-    SkScalar fDY[N];
-
-public:
-    PathFillView() {
-        for (size_t i = 0; i < N; i++) {
-            fDY[i] = gProcs[i](&fPath[i]);
-        }
-        this->setBGColor(0xFFDDDDDD);
-    }
-
-protected:
-    virtual bool onQuery(Sample::Event* evt) {
-        if (Sample::TitleQ(*evt)) {
-            Sample::TitleR(evt, "PathFill");
-            return true;
-        }
-        return this->INHERITED::onQuery(evt);
-    }
-
-    virtual void onDrawContent(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-
-        for (size_t i = 0; i < N; i++) {
-            canvas->drawPath(fPath[i], paint);
-            canvas->translate(0, fDY[i]);
-        }
-    }
-
-private:
-    typedef Sample INHERITED;
-};
-
-//////////////////////////////////////////////////////////////////////////////
-
-static Sample* MyFactory() { return new PathFillView; }
-static SampleRegister reg(MyFactory);
diff --git a/samplecode/SamplePathFuzz.cpp b/samplecode/SamplePathFuzz.cpp
deleted file mode 100644
index 35951d7..0000000
--- a/samplecode/SamplePathFuzz.cpp
+++ /dev/null
@@ -1,697 +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 "Sample.h"
-#include "SkCanvas.h"
-#include "SkPaint.h"
-#include "SkPath.h"
-#include "SkMatrix.h"
-#include "SkColor.h"
-#include "SkTDArray.h"
-#include "SkRandom.h"
-#include "SkRRect.h"
-
-enum RandomAddPath {
-    kMoveToPath,
-    kRMoveToPath,
-    kLineToPath,
-    kRLineToPath,
-    kQuadToPath,
-    kRQuadToPath,
-    kConicToPath,
-    kRConicToPath,
-    kCubicToPath,
-    kRCubicToPath,
-    kArcToPath,
-    kArcTo2Path,
-    kClosePath,
-    kAddArc,
-    kAddRoundRect1,
-    kAddRoundRect2,
-    kAddRRect,
-    kAddPoly,
-    kAddPath1,
-    kAddPath2,
-    kAddPath3,
-    kReverseAddPath,
-};
-
-const int kRandomAddPath_Last = kReverseAddPath;
-
-const char* gRandomAddPathNames[] = {
-    "kMoveToPath",
-    "kRMoveToPath",
-    "kLineToPath",
-    "kRLineToPath",
-    "kQuadToPath",
-    "kRQuadToPath",
-    "kConicToPath",
-    "kRConicToPath",
-    "kCubicToPath",
-    "kRCubicToPath",
-    "kArcToPath",
-    "kArcTo2Path",
-    "kClosePath",
-    "kAddArc",
-    "kAddRoundRect1",
-    "kAddRoundRect2",
-    "kAddRRect",
-    "kAddPoly",
-    "kAddPath1",
-    "kAddPath2",
-    "kAddPath3",
-    "kReverseAddPath",
-};
-
-enum RandomSetRRect {
-    kSetEmpty,
-    kSetRect,
-    kSetOval,
-    kSetRectXY,
-    kSetNinePatch,
-    kSetRectRadii,
-};
-
-const char* gRandomSetRRectNames[] = {
-    "kSetEmpty",
-    "kSetRect",
-    "kSetOval",
-    "kSetRectXY",
-    "kSetNinePatch",
-    "kSetRectRadii",
-};
-
-int kRandomSetRRect_Last = kSetRectRadii;
-
-enum RandomSetMatrix {
-    kSetIdentity,
-    kSetTranslate,
-    kSetTranslateX,
-    kSetTranslateY,
-    kSetScale,
-    kSetScaleTranslate,
-    kSetScaleX,
-    kSetScaleY,
-    kSetSkew,
-    kSetSkewTranslate,
-    kSetSkewX,
-    kSetSkewY,
-    kSetRotate,
-    kSetRotateTranslate,
-    kSetPerspectiveX,
-    kSetPerspectiveY,
-    kSetAll,
-};
-
-int kRandomSetMatrix_Last = kSetAll;
-
-const char* gRandomSetMatrixNames[] = {
-    "kSetIdentity",
-    "kSetTranslate",
-    "kSetTranslateX",
-    "kSetTranslateY",
-    "kSetScale",
-    "kSetScaleTranslate",
-    "kSetScaleX",
-    "kSetScaleY",
-    "kSetSkew",
-    "kSetSkewTranslate",
-    "kSetSkewX",
-    "kSetSkewY",
-    "kSetRotate",
-    "kSetRotateTranslate",
-    "kSetPerspectiveX",
-    "kSetPerspectiveY",
-    "kSetAll",
-};
-
-class FuzzPath {
-public:
-    FuzzPath()
-        : fFloatMin(0)
-        , fFloatMax(800)
-        , fAddCount(0)
-        , fPrintName(false)
-        , fStrokeOnly(false)
-        , fValidate(false)
-    {
-        fTab = "                                                                                  ";
-    }
-    void randomize() {
-        fPathDepth = 0;
-        fPathDepthLimit = fRand.nextRangeU(1, 2);
-        fPathContourCount = fRand.nextRangeU(1, 4);
-        fPathSegmentLimit = fRand.nextRangeU(1, 8);
-        fClip = makePath();
-        SkASSERT(!fPathDepth);
-        fMatrix = makeMatrix();
-        fPaint = makePaint();
-        fPathDepthLimit = fRand.nextRangeU(1, 3);
-        fPathContourCount = fRand.nextRangeU(1, 6);
-        fPathSegmentLimit = fRand.nextRangeU(1, 16);
-        fPath = makePath();
-        SkASSERT(!fPathDepth);
-    }
-
-    const SkPath& getClip() const {
-        return fClip;
-    }
-
-    const SkMatrix& getMatrix() const {
-        return fMatrix;
-    }
-
-    const SkPaint& getPaint() const {
-        return fPaint;
-    }
-
-    const SkPath& getPath() const {
-        return fPath;
-    }
-
-    void setSeed(int seed) {
-        fRand.setSeed(seed);
-    }
-
-    void setStrokeOnly() {
-        fStrokeOnly = true;
-    }
-
-private:
-
-SkPath::AddPathMode makeAddPathMode() {
-    return (SkPath::AddPathMode) fRand.nextRangeU(SkPath::kAppend_AddPathMode,
-        SkPath::kExtend_AddPathMode);
-}
-
-RandomAddPath makeAddPathType() {
-    return (RandomAddPath) fRand.nextRangeU(0, kRandomAddPath_Last);
-}
-
-SkScalar makeAngle() {
-    SkScalar angle;
-    angle = fRand.nextF();
-    return angle;
-}
-
-bool makeBool() {
-    return fRand.nextBool();
-}
-
-SkPath::Direction makeDirection() {
-    return (SkPath::Direction) fRand.nextRangeU(SkPath::kCW_Direction, SkPath::kCCW_Direction);
-}
-
-SkMatrix makeMatrix() {
-    SkMatrix matrix;
-    matrix.reset();
-    RandomSetMatrix setMatrix = (RandomSetMatrix) fRand.nextRangeU(0, kRandomSetMatrix_Last);
-    if (fPrintName) {
-        SkDebugf("%.*s%s\n", fPathDepth * 3, fTab, gRandomSetMatrixNames[setMatrix]);
-    }
-    switch (setMatrix) {
-        case kSetIdentity:
-            break;
-        case kSetTranslateX:
-            matrix.setTranslateX(makeScalar());
-            break;
-        case kSetTranslateY:
-            matrix.setTranslateY(makeScalar());
-            break;
-        case kSetTranslate:
-            matrix.setTranslate(makeScalar(), makeScalar());
-            break;
-        case kSetScaleX:
-            matrix.setScaleX(makeScalar());
-            break;
-        case kSetScaleY:
-            matrix.setScaleY(makeScalar());
-            break;
-        case kSetScale:
-            matrix.setScale(makeScalar(), makeScalar());
-            break;
-        case kSetScaleTranslate:
-            matrix.setScale(makeScalar(), makeScalar(), makeScalar(), makeScalar());
-            break;
-        case kSetSkewX:
-            matrix.setSkewX(makeScalar());
-            break;
-        case kSetSkewY:
-            matrix.setSkewY(makeScalar());
-            break;
-        case kSetSkew:
-            matrix.setSkew(makeScalar(), makeScalar());
-            break;
-        case kSetSkewTranslate:
-            matrix.setSkew(makeScalar(), makeScalar(), makeScalar(), makeScalar());
-            break;
-        case kSetRotate:
-            matrix.setRotate(makeScalar());
-            break;
-        case kSetRotateTranslate:
-            matrix.setRotate(makeScalar(), makeScalar(), makeScalar());
-            break;
-        case kSetPerspectiveX:
-            matrix.setPerspX(makeScalar());
-            break;
-        case kSetPerspectiveY:
-            matrix.setPerspY(makeScalar());
-            break;
-        case kSetAll:
-            matrix.setAll(makeScalar(), makeScalar(), makeScalar(),
-                          makeScalar(), makeScalar(), makeScalar(),
-                          makeScalar(), makeScalar(), makeScalar());
-            break;
-    }
-    return matrix;
-}
-
-SkPaint makePaint() {
-    SkPaint paint;
-    bool antiAlias = fRand.nextBool();
-    paint.setAntiAlias(antiAlias);
-    SkPaint::Style style = fStrokeOnly ? SkPaint::kStroke_Style :
-        (SkPaint::Style) fRand.nextRangeU(SkPaint::kFill_Style, SkPaint::kStrokeAndFill_Style);
-    paint.setStyle(style);
-    SkColor color = (SkColor) fRand.nextU();
-    paint.setColor(color);
-    SkScalar width = fRand.nextRangeF(0, 10);
-    paint.setStrokeWidth(width);
-    SkScalar miter = makeScalar();
-    paint.setStrokeMiter(miter);
-    SkPaint::Cap cap = (SkPaint::Cap) fRand.nextRangeU(SkPaint::kButt_Cap, SkPaint::kSquare_Cap);
-    paint.setStrokeCap(cap);
-    SkPaint::Join join = (SkPaint::Join) fRand.nextRangeU(SkPaint::kMiter_Join,
-        SkPaint::kBevel_Join);
-    paint.setStrokeJoin(join);
-    return paint;
-}
-
-SkPoint makePoint() {
-    SkPoint result;
-    makeScalarArray(2, &result.fX);
-    return result;
-}
-
-void makePointArray(size_t arrayCount, SkPoint* points) {
-    for (size_t index = 0; index < arrayCount; ++index) {
-        points[index] = makePoint();
-    }
-}
-
-void makePointArray(SkTDArray<SkPoint>* points) {
-    size_t arrayCount = fRand.nextRangeU(1, 10);
-    for (size_t index = 0; index < arrayCount; ++index) {
-        *points->append() = makePoint();
-    }
-}
-
-SkRect makeRect() {
-    SkRect result;
-    makeScalarArray(4, &result.fLeft);
-    return result;
-}
-
-SkRRect makeRRect() {
-    SkRRect rrect;
-    RandomSetRRect rrectType = makeSetRRectType();
-    if (fPrintName) {
-        SkDebugf("%.*s%s\n", fPathDepth * 3, fTab, gRandomSetRRectNames[rrectType]);
-    }
-    switch (rrectType) {
-        case kSetEmpty:
-            rrect.setEmpty();
-            break;
-        case kSetRect: {
-            SkRect rect = makeRect();
-            rrect.setRect(rect);
-            } break;
-        case kSetOval: {
-            SkRect oval = makeRect();
-            rrect.setOval(oval);
-            } break;
-        case kSetRectXY: {
-            SkRect rect = makeRect();
-            SkScalar xRad = makeScalar();
-            SkScalar yRad = makeScalar();
-            rrect.setRectXY(rect, xRad, yRad);
-            } break;
-        case kSetNinePatch: {
-            SkRect rect = makeRect();
-            SkScalar leftRad = makeScalar();
-            SkScalar topRad = makeScalar();
-            SkScalar rightRad = makeScalar();
-            SkScalar bottomRad = makeScalar();
-            rrect.setNinePatch(rect, leftRad, topRad, rightRad, bottomRad);
-            SkDebugf("");  // keep locals in scope
-            } break;
-        case kSetRectRadii: {
-            SkRect rect = makeRect();
-            SkVector radii[4];
-            makeVectorArray(SK_ARRAY_COUNT(radii), radii);
-            rrect.setRectRadii(rect, radii);
-            } break;
-    }
-    return rrect;
-}
-
-SkPath makePath() {
-    SkPath path;
-    for (uint32_t cIndex = 0; cIndex < fPathContourCount; ++cIndex) {
-        uint32_t segments = makeSegmentCount();
-        for (uint32_t sIndex = 0; sIndex < segments; ++sIndex) {
-            RandomAddPath addPathType = makeAddPathType();
-            ++fAddCount;
-            if (fPrintName) {
-                SkDebugf("%.*s%s\n", fPathDepth * 3, fTab,
-                        gRandomAddPathNames[addPathType]);
-            }
-            switch (addPathType) {
-                case kAddArc: {
-                    SkRect oval = makeRect();
-                    SkScalar startAngle = makeAngle();
-                    SkScalar sweepAngle = makeAngle();
-                    path.addArc(oval, startAngle, sweepAngle);
-                    validate(path);
-                    } break;
-                case kAddRoundRect1: {
-                    SkRect rect = makeRect();
-                    SkScalar rx = makeScalar(), ry = makeScalar();
-                    SkPath::Direction dir = makeDirection();
-                    path.addRoundRect(rect, rx, ry, dir);
-                    validate(path);
-                    } break;
-                case kAddRoundRect2: {
-                    SkRect rect = makeRect();
-                    SkScalar radii[8];
-                    makeScalarArray(SK_ARRAY_COUNT(radii), radii);
-                    SkPath::Direction dir = makeDirection();
-                    path.addRoundRect(rect, radii, dir);
-                    validate(path);
-                    } break;
-                case kAddRRect: {
-                    SkRRect rrect = makeRRect();
-                    SkPath::Direction dir = makeDirection();
-                    path.addRRect(rrect, dir);
-                    validate(path);
-                    } break;
-                case kAddPoly: {
-                    SkTDArray<SkPoint> points;
-                    makePointArray(&points);
-                    bool close = makeBool();
-                    path.addPoly(&points[0], points.count(), close);
-                    validate(path);
-                    } break;
-                case kAddPath1:
-                    if (fPathDepth < fPathDepthLimit) {
-                        ++fPathDepth;
-                        SkPath src = makePath();
-                        validate(src);
-                        SkScalar dx = makeScalar();
-                        SkScalar dy = makeScalar();
-                        SkPath::AddPathMode mode = makeAddPathMode();
-                        path.addPath(src, dx, dy, mode);
-                        --fPathDepth;
-                        validate(path);
-                    }
-                    break;
-                case kAddPath2:
-                    if (fPathDepth < fPathDepthLimit) {
-                        ++fPathDepth;
-                        SkPath src = makePath();
-                        validate(src);
-                        SkPath::AddPathMode mode = makeAddPathMode();
-                        path.addPath(src, mode);
-                        --fPathDepth;
-                        validate(path);
-                    }
-                    break;
-                case kAddPath3:
-                    if (fPathDepth < fPathDepthLimit) {
-                        ++fPathDepth;
-                        SkPath src = makePath();
-                        validate(src);
-                        SkMatrix matrix = makeMatrix();
-                        SkPath::AddPathMode mode = makeAddPathMode();
-                        path.addPath(src, matrix, mode);
-                        --fPathDepth;
-                        validate(path);
-                    }
-                    break;
-                case kReverseAddPath:
-                    if (fPathDepth < fPathDepthLimit) {
-                        ++fPathDepth;
-                        SkPath src = makePath();
-                        validate(src);
-                        path.reverseAddPath(src);
-                        --fPathDepth;
-                        validate(path);
-                    }
-                    break;
-                case kMoveToPath: {
-                    SkScalar x = makeScalar();
-                    SkScalar y = makeScalar();
-                    path.moveTo(x, y);
-                    validate(path);
-                    } break;
-                case kRMoveToPath: {
-                    SkScalar x = makeScalar();
-                    SkScalar y = makeScalar();
-                    path.rMoveTo(x, y);
-                    validate(path);
-                    } break;
-                case kLineToPath: {
-                    SkScalar x = makeScalar();
-                    SkScalar y = makeScalar();
-                    path.lineTo(x, y);
-                    validate(path);
-                    } break;
-                case kRLineToPath: {
-                    SkScalar x = makeScalar();
-                    SkScalar y = makeScalar();
-                    path.rLineTo(x, y);
-                    validate(path);
-                    } break;
-                case kQuadToPath: {
-                    SkPoint pt[2];
-                    makePointArray(SK_ARRAY_COUNT(pt), pt);
-                    path.quadTo(pt[0], pt[1]);
-                    validate(path);
-                    } break;
-                case kRQuadToPath: {
-                    SkPoint pt[2];
-                    makePointArray(SK_ARRAY_COUNT(pt), pt);
-                    path.rQuadTo(pt[0].fX, pt[0].fY, pt[1].fX, pt[1].fY);
-                    validate(path);
-                    } break;
-                case kConicToPath: {
-                    SkPoint pt[2];
-                    makePointArray(SK_ARRAY_COUNT(pt), pt);
-                    SkScalar weight = makeScalar();
-                    path.conicTo(pt[0], pt[1], weight);
-                    validate(path);
-                    } break;
-                case kRConicToPath: {
-                    SkPoint pt[2];
-                    makePointArray(SK_ARRAY_COUNT(pt), pt);
-                    SkScalar weight = makeScalar();
-                    path.rConicTo(pt[0].fX, pt[0].fY, pt[1].fX, pt[1].fY, weight);
-                    validate(path);
-                    } break;
-                case kCubicToPath: {
-                    SkPoint pt[3];
-                    makePointArray(SK_ARRAY_COUNT(pt), pt);
-                    path.cubicTo(pt[0], pt[1], pt[2]);
-                    validate(path);
-                    } break;
-                case kRCubicToPath: {
-                    SkPoint pt[3];
-                    makePointArray(SK_ARRAY_COUNT(pt), pt);
-                    path.rCubicTo(pt[0].fX, pt[0].fY, pt[1].fX, pt[1].fY, pt[2].fX, pt[2].fY);
-                    validate(path);
-                    } break;
-                case kArcToPath: {
-                    SkPoint pt[2];
-                    makePointArray(SK_ARRAY_COUNT(pt), pt);
-                    SkScalar radius = makeScalar();
-                    path.arcTo(pt[0], pt[1], radius);
-                    validate(path);
-                    } break;
-                case kArcTo2Path: {
-                    SkRect oval = makeRect();
-                    SkScalar startAngle = makeAngle();
-                    SkScalar sweepAngle = makeAngle();
-                    bool forceMoveTo = makeBool();
-                    path.arcTo(oval, startAngle, sweepAngle, forceMoveTo);
-                    validate(path);
-                    } break;
-                case kClosePath:
-                    path.close();
-                    validate(path);
-                    break;
-            }
-        }
-    }
-    return path;
-}
-
-uint32_t makeSegmentCount() {
-    return fRand.nextRangeU(1, fPathSegmentLimit);
-}
-
-RandomSetRRect makeSetRRectType() {
-    return (RandomSetRRect) fRand.nextRangeU(0, kRandomSetRRect_Last);
-}
-
-SkScalar makeScalar() {
-    SkScalar scalar;
-    scalar = fRand.nextRangeF(fFloatMin, fFloatMax);
-    return scalar;
-}
-
-void makeScalarArray(size_t arrayCount, SkScalar* array) {
-    for (size_t index = 0; index < arrayCount; ++index) {
-        array[index] = makeScalar();
-    }
-}
-
-void makeVectorArray(size_t arrayCount, SkVector* array) {
-    for (size_t index = 0; index < arrayCount; ++index) {
-        array[index] = makeVector();
-    }
-}
-
-SkVector makeVector() {
-    SkVector result;
-    makeScalarArray(2, &result.fX);
-    return result;
-}
-
-void validate(const SkPath& path) {
-    if (fValidate) {
-        // FIXME: this could probably assert on path.isValid() instead
-        SkDEBUGCODE(path.validateRef());
-    }
-}
-
-SkRandom fRand;
-SkMatrix fMatrix;
-SkPath fClip;
-SkPaint fPaint;
-SkPath fPath;
-SkScalar fFloatMin;
-SkScalar fFloatMax;
-uint32_t fPathContourCount;
-int fPathDepth;
-int fPathDepthLimit;
-uint32_t fPathSegmentLimit;
-int fAddCount;
-bool fPrintName;
-bool fStrokeOnly;
-bool fValidate;
-const char* fTab;
-};
-
-static bool contains_only_moveTo(const SkPath& path) {
-    int verbCount = path.countVerbs();
-    if (verbCount == 0) {
-        return true;
-    }
-    SkTDArray<uint8_t> verbs;
-    verbs.setCount(verbCount);
-    SkDEBUGCODE(int getVerbResult = ) path.getVerbs(verbs.begin(), verbCount);
-    SkASSERT(getVerbResult == verbCount);
-    for (int index = 0; index < verbCount; ++index) {
-        if (verbs[index] != SkPath::kMove_Verb) {
-            return false;
-        }
-    }
-    return true;
-}
-
-#include "SkGraphics.h"
-#include "SkSurface.h"
-#include "SkTaskGroup.h"
-#include "SkTDArray.h"
-
-static void path_fuzz_stroker(SkBitmap* bitmap, int seed) {
-    SkTaskGroup().batch(100, [&](int i) {
-        int localSeed = seed + i;
-
-        FuzzPath fuzzPath;
-        fuzzPath.setStrokeOnly();
-        fuzzPath.setSeed(localSeed);
-        fuzzPath.randomize();
-        const SkPath& path = fuzzPath.getPath();
-        const SkPaint& paint = fuzzPath.getPaint();
-        const SkImageInfo& info = bitmap->info();
-        std::unique_ptr<SkCanvas> canvas(
-            SkCanvas::MakeRasterDirect(info, bitmap->getPixels(), bitmap->rowBytes()));
-        int w = info.width() / 4;
-        int h = info.height() / 4;
-        int x = localSeed / 4 % 4;
-        int y = localSeed % 4;
-        SkRect clipBounds = SkRect::MakeXYWH(SkIntToScalar(x) * w, SkIntToScalar(y) * h,
-            SkIntToScalar(w), SkIntToScalar(h));
-        canvas->save();
-            canvas->clipRect(clipBounds);
-            canvas->translate(SkIntToScalar(x) * w, SkIntToScalar(y) * h);
-            canvas->drawPath(path, paint);
-        canvas->restore();
-    });
-}
-
-class PathFuzzView : public Sample {
-public:
-    PathFuzzView()
-        : fOneDraw(false)
-    {
-    }
-protected:
-    bool onQuery(Sample::Event* evt) override {
-        if (Sample::TitleQ(*evt)) {
-            Sample::TitleR(evt, "PathFuzzer");
-            return true;
-        }
-        return this->INHERITED::onQuery(evt);
-    }
-
-    void onOnceBeforeDraw() override {
-        fIndex = 0;
-        SkImageInfo info(SkImageInfo::MakeN32Premul(SkScalarRoundToInt(width()),
-                SkScalarRoundToInt(height())));
-        offscreen.allocPixels(info);
-        path_fuzz_stroker(&offscreen, fIndex);
-    }
-
-    void onDrawContent(SkCanvas* canvas) override {
-        if (fOneDraw) {
-            fuzzPath.randomize();
-            const SkPath& path = fuzzPath.getPath();
-            const SkPaint& paint = fuzzPath.getPaint();
-            const SkPath& clip = fuzzPath.getClip();
-            const SkMatrix& matrix = fuzzPath.getMatrix();
-            if (!contains_only_moveTo(clip)) {
-                canvas->clipPath(clip);
-            }
-            canvas->setMatrix(matrix);
-            canvas->drawPath(path, paint);
-        } else {
-            path_fuzz_stroker(&offscreen, fIndex += 100);
-            canvas->drawBitmap(offscreen, 0, 0);
-        }
-    }
-
-private:
-    int fIndex;
-    SkBitmap offscreen;
-    FuzzPath fuzzPath;
-    bool fOneDraw;
-    typedef Sample INHERITED;
-};
-
-DEF_SAMPLE( return new PathFuzzView(); )
diff --git a/samplecode/SamplePathText.cpp b/samplecode/SamplePathText.cpp
index 8f4b8a1..57a8268 100644
--- a/samplecode/SamplePathText.cpp
+++ b/samplecode/SamplePathText.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
+#include "AnimTimer.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
 #include "SkCanvas.h"
 #include "SkPaint.h"
 #include "SkPath.h"
@@ -14,7 +14,7 @@
 #include "SkStrike.h"
 #include "SkStrikeCache.h"
 #include "SkTaskGroup.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // Static text from paths.
@@ -106,7 +106,7 @@
 
     Glyph      fGlyphs[kNumPaths];
     SkRandom   fRand{25};
-    SkPath     fClipPath = sk_tool_utils::make_star(SkRect{0,0,1,1}, 11, 3);
+    SkPath     fClipPath = ToolUtils::make_star(SkRect{0, 0, 1, 1}, 11, 3);
     bool       fDoClip = false;
 
     typedef Sample INHERITED;
@@ -167,7 +167,7 @@
         fLastTick = 0;
     }
 
-    bool onAnimate(const SkAnimTimer& timer) final {
+    bool onAnimate(const AnimTimer& timer) final {
         fBackgroundAnimationTask.wait();
         this->swapAnimationBuffers();
 
diff --git a/samplecode/SampleQuadStroker.cpp b/samplecode/SampleQuadStroker.cpp
index 10e9bbc..6122f4b 100644
--- a/samplecode/SampleQuadStroker.cpp
+++ b/samplecode/SampleQuadStroker.cpp
@@ -30,7 +30,7 @@
 #include "SkTemplates.h"
 #include "SkTextUtils.h"
 #include "SkTypes.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 #include <cfloat>
 
@@ -282,7 +282,7 @@
         fBounds.set(0, 0, SkIntToScalar(width * zoom), SkIntToScalar(height * zoom));
         fMatrix.setScale(SkIntToScalar(zoom), SkIntToScalar(zoom));
         fInverse.setScale(SK_Scalar1 / zoom, SK_Scalar1 / zoom);
-        fShader = sk_tool_utils::create_checkerboard_shader(0xFFCCCCCC, 0xFFFFFFFF, zoom);
+        fShader = ToolUtils::create_checkerboard_shader(0xFFCCCCCC, 0xFFFFFFFF, zoom);
 
         SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
         fMinSurface = SkSurface::MakeRaster(info);
diff --git a/samplecode/SampleRegion.cpp b/samplecode/SampleRegion.cpp
index c5eaf00..47e46af 100644
--- a/samplecode/SampleRegion.cpp
+++ b/samplecode/SampleRegion.cpp
@@ -81,7 +81,7 @@
     const SkScalar pos[] = { 0, 0.9f, SK_Scalar1 };
 
     SkPaint p;
-    p.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 3, SkShader::kClamp_TileMode));
+    p.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 3, SkTileMode::kClamp));
     p.setBlendMode(SkBlendMode::kDstIn);
     canvas->drawRect(bounds, p);
 
@@ -109,7 +109,7 @@
     const SkScalar pos[] = { 0, 0.9f, 1 };
     paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos,
                                                  SK_ARRAY_COUNT(colors),
-                                                 SkShader::kClamp_TileMode));
+                                                 SkTileMode::kClamp));
     canvas->drawSimpleText(str, len, kUTF8_SkTextEncoding, x, y, font, paint);
 
     y += 20;
diff --git a/samplecode/SampleRepeatTile.cpp b/samplecode/SampleRepeatTile.cpp
index 7eff1c7..dfde1cf 100644
--- a/samplecode/SampleRepeatTile.cpp
+++ b/samplecode/SampleRepeatTile.cpp
@@ -31,11 +31,11 @@
     canvas.drawLine(0, 0, SkIntToScalar(W), 0, paint);
 }
 
-static void make_paint(SkPaint* paint, SkShader::TileMode tm) {
+static void make_paint(SkPaint* paint, SkTileMode tm) {
     SkBitmap bm;
     make_bitmap(&bm);
 
-    paint->setShader(SkShader::MakeBitmapShader(bm, tm, tm));
+    paint->setShader(bm.makeShader(tm, tm));
 }
 
 class RepeatTileView : public Sample {
@@ -55,7 +55,7 @@
 
     void onDrawContent(SkCanvas* canvas) override {
         SkPaint paint;
-        make_paint(&paint, SkShader::kRepeat_TileMode);
+        make_paint(&paint, SkTileMode::kRepeat);
 
 //        canvas->scale(SK_Scalar1*2, SK_Scalar1);
         canvas->translate(SkIntToScalar(100), SkIntToScalar(100));
diff --git a/samplecode/SampleSG.cpp b/samplecode/SampleSG.cpp
new file mode 100644
index 0000000..dfdde81
--- /dev/null
+++ b/samplecode/SampleSG.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Sample.h"
+#include "SkCanvas.h"
+#include "SkFont.h"
+#include "SkFontMetrics.h"
+#include "SkPath.h"
+
+#include "SkSGDraw.h"
+#include "SkSGGroup.h"
+#include "SkSGPaint.h"
+#include "SkSGRect.h"
+#include "SkSGScene.h"
+
+struct PerNodeInfo {
+    // key
+    sksg::Draw* fDraw;
+
+    // value(s)
+    sksg::GeometryNode* fGeo;
+    sksg::PaintNode*    fPaint;
+};
+
+class SampleSG : public Sample {
+    SkTDArray<PerNodeInfo> fSideCar;
+    sk_sp<sksg::Group> fGroup;
+    std::unique_ptr<sksg::Scene> fScene;
+
+    PerNodeInfo* findInfo(sksg::Draw* key) {
+        for (int i = 0; i < fSideCar.count(); ++i) {
+            if (fSideCar[i].fDraw == key) {
+                return &fSideCar[i];
+            }
+        }
+        return nullptr;
+    }
+
+    void appendNode(sk_sp<sksg::Draw> d, sk_sp<sksg::GeometryNode> g, sk_sp<sksg::PaintNode> p) {
+        fGroup->addChild(d);
+        auto sc = fSideCar.append();
+        sc->fDraw  = d.get();
+        sc->fGeo   = g.get();
+        sc->fPaint = p.get();
+    }
+
+public:
+    SampleSG() {
+        fGroup = sksg::Group::Make();
+
+        fScene = sksg::Scene::Make(fGroup, sksg::AnimatorList());
+
+        auto r = sksg::Rect::Make({20, 20, 400, 300});
+        auto p = sksg::Color::Make(SK_ColorRED);
+        auto d = sksg::Draw::Make(r, p);
+        this->appendNode(d, r, p);
+
+        r = sksg::Rect::Make({60, 70, 300, 400});
+        p = sksg::Color::Make(SK_ColorBLUE);
+        d = sksg::Draw::Make(r, p);
+        this->appendNode(d, r, p);
+    }
+
+protected:
+    bool onQuery(Sample::Event* evt) override {
+        if (Sample::TitleQ(*evt)) {
+            Sample::TitleR(evt, "SceneGraph");
+            return true;
+        }
+        return this->INHERITED::onQuery(evt);
+    }
+
+    void onDrawContent(SkCanvas* canvas) override {
+        fScene->render(canvas);
+    }
+
+    Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned modi) override {
+        if (auto node = fScene->nodeAt({x, y})) {
+            Click* click = new Click(this);
+            click->fMeta.setPtr("node", (void*)node);
+            return click;
+        }
+        return this->INHERITED::onFindClickHandler(x, y, modi);
+    }
+
+    bool onClick(Click* click) override {
+        sksg::Draw* node = nullptr;
+        if (click->fMeta.findPtr("node", (void**)&node)) {
+            if (auto info = this->findInfo(node)) {
+                auto geo = info->fGeo;
+                sksg::Rect* r = (sksg::Rect*)geo;
+                SkScalar dx = click->fCurr.fX - click->fPrev.fX;
+                SkScalar dy = click->fCurr.fY - click->fPrev.fY;
+                r->setL(r->getL() + dx);
+                r->setR(r->getR() + dx);
+                r->setT(r->getT() + dy);
+                r->setB(r->getB() + dy);
+            }
+            return true;
+        }
+        return false;
+    }
+
+private:
+
+    typedef Sample INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+DEF_SAMPLE( return new SampleSG(); )
diff --git a/samplecode/SampleShaders.cpp b/samplecode/SampleShaders.cpp
index bf64888..f861bd1 100644
--- a/samplecode/SampleShaders.cpp
+++ b/samplecode/SampleShaders.cpp
@@ -27,12 +27,11 @@
     pts[1].set(0, SkIntToScalar(bm.height()));
     colors[0] = SK_ColorBLACK;
     colors[1] = SkColorSetARGB(0, 0, 0, 0);
-    auto shaderA = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode);
+    auto shaderA = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
 
-    auto shaderB = SkShader::MakeBitmapShader(bm,
-                                              SkShader::kClamp_TileMode, SkShader::kClamp_TileMode);
+    auto shaderB = bm.makeShader();
 
-    return SkShader::MakeComposeShader(std::move(shaderB), std::move(shaderA), SkBlendMode::kDstIn);
+    return SkShaders::Blend(SkBlendMode::kDstIn, std::move(shaderB), std::move(shaderA));
 }
 
 class ShaderView : public Sample {
@@ -50,16 +49,15 @@
         pts[1].set(SkIntToScalar(100), 0);
         colors[0] = SK_ColorRED;
         colors[1] = SK_ColorBLUE;
-        auto shaderA = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode);
+        auto shaderA = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
 
         pts[0].set(0, 0);
         pts[1].set(0, SkIntToScalar(100));
         colors[0] = SK_ColorBLACK;
         colors[1] = SkColorSetARGB(0x80, 0, 0, 0);
-        auto shaderB = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode);
+        auto shaderB = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
 
-        fShader = SkShader::MakeComposeShader(std::move(shaderA), std::move(shaderB),
-                                              SkBlendMode::kDstIn);
+        fShader = SkShaders::Blend(SkBlendMode::kDstIn, std::move(shaderA), std::move(shaderB));
     }
 
 protected:
diff --git a/samplecode/SampleShadowUtils.cpp b/samplecode/SampleShadowUtils.cpp
index c6ae470..c7ab6de 100644
--- a/samplecode/SampleShadowUtils.cpp
+++ b/samplecode/SampleShadowUtils.cpp
@@ -5,19 +5,19 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
+#include "AnimTimer.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
 #include "SkBlurMask.h"
 #include "SkBlurMaskFilter.h"
-#include "SkColorFilter.h"
 #include "SkCamera.h"
 #include "SkCanvas.h"
+#include "SkColorFilter.h"
 #include "SkPath.h"
 #include "SkPathOps.h"
 #include "SkPoint3.h"
 #include "SkShadowUtils.h"
 #include "SkUTF.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 ////////////////////////////////////////////////////////////////////////////
 
diff --git a/samplecode/SampleShip.cpp b/samplecode/SampleShip.cpp
index 2e95181..7e3cbd4 100644
--- a/samplecode/SampleShip.cpp
+++ b/samplecode/SampleShip.cpp
@@ -5,9 +5,9 @@
  * found in the LICENSE file.
  */
 
-#include "Sample.h"
+#include "AnimTimer.h"
 #include "Resources.h"
-#include "SkAnimTimer.h"
+#include "Sample.h"
 #include "SkCanvas.h"
 #include "SkFont.h"
 #include "SkRSXform.h"
@@ -156,7 +156,7 @@
 
 #if 0
     // TODO: switch over to use this for our animation
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         SkScalar angle = SkDoubleToScalar(fmod(timer.secs() * 360 / 24, 360));
         fAnimatingDrawable->setSweep(angle);
         return true;
diff --git a/samplecode/SampleSlides.cpp b/samplecode/SampleSlides.cpp
index f581b80..2ede187 100644
--- a/samplecode/SampleSlides.cpp
+++ b/samplecode/SampleSlides.cpp
@@ -14,7 +14,7 @@
 #include "SkPaint.h"
 #include "SkVertices.h"
 
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 #define BG_COLOR    0xFFDDDDDD
 
@@ -175,11 +175,11 @@
 { 5, gColors, gPos2 }
 };
 
-static sk_sp<SkShader> MakeLinear(const SkPoint pts[2], const GradData& data, SkShader::TileMode tm) {
+static sk_sp<SkShader> MakeLinear(const SkPoint pts[2], const GradData& data, SkTileMode tm) {
     return SkGradientShader::MakeLinear(pts, data.fColors, data.fPos, data.fCount, tm);
 }
 
-static sk_sp<SkShader> MakeRadial(const SkPoint pts[2], const GradData& data, SkShader::TileMode tm) {
+static sk_sp<SkShader> MakeRadial(const SkPoint pts[2], const GradData& data, SkTileMode tm) {
     SkPoint center;
     center.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
@@ -187,14 +187,14 @@
                                           data.fPos, data.fCount, tm);
 }
 
-static sk_sp<SkShader> MakeSweep(const SkPoint pts[2], const GradData& data, SkShader::TileMode tm) {
+static sk_sp<SkShader> MakeSweep(const SkPoint pts[2], const GradData& data, SkTileMode tm) {
     SkPoint center;
     center.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
     return SkGradientShader::MakeSweep(center.fX, center.fY, data.fColors, data.fPos, data.fCount);
 }
 
-static sk_sp<SkShader> Make2Conical(const SkPoint pts[2], const GradData& data, SkShader::TileMode tm) {
+static sk_sp<SkShader> Make2Conical(const SkPoint pts[2], const GradData& data, SkTileMode tm) {
     SkPoint center0, center1;
     center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                 SkScalarAve(pts[0].fY, pts[1].fY));
@@ -205,7 +205,7 @@
                                                   data.fColors, data.fPos, data.fCount, tm);
 }
 
-typedef sk_sp<SkShader> (*GradMaker)(const SkPoint pts[2], const GradData&, SkShader::TileMode);
+typedef sk_sp<SkShader> (*GradMaker)(const SkPoint pts[2], const GradData&, SkTileMode);
 static const GradMaker gGradMakers[] = {
     MakeLinear, MakeRadial, MakeSweep, Make2Conical
 };
@@ -215,7 +215,7 @@
         { 0, 0 },
         { SkIntToScalar(100), SkIntToScalar(100) }
     };
-    SkShader::TileMode tm = SkShader::kClamp_TileMode;
+    SkTileMode tm = SkTileMode::kClamp;
     SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
     SkPaint paint;
     paint.setAntiAlias(true);
@@ -246,8 +246,7 @@
 
     decode_file("/skimages/logo.gif", &bm);
     size->set(bm.width(), bm.height());
-    return SkShader::MakeBitmapShader(bm, SkShader::kClamp_TileMode,
-                                        SkShader::kClamp_TileMode);
+    return bm.makeShader();
 }
 
 static sk_sp<SkShader> make_shader1(const SkIPoint& size) {
@@ -255,7 +254,7 @@
                       { SkIntToScalar(size.fX), SkIntToScalar(size.fY) } };
     SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorRED };
     return SkGradientShader::MakeLinear(pts, colors, nullptr,
-                                          SK_ARRAY_COUNT(colors), SkShader::kMirror_TileMode);
+                                          SK_ARRAY_COUNT(colors), SkTileMode::kMirror);
 }
 
 class Rec {
@@ -301,8 +300,9 @@
     v[0].set(0, 0);
     t[0].set(0, 0);
     for (int i = 0; i < n; i++) {
-        SkScalar cos;
-        SkScalar sin = SkScalarSinCos(SK_ScalarPI * 2 * i / n, &cos);
+        SkScalar r   = SK_ScalarPI * 2 * i / n,
+                 sin = SkScalarSin(r),
+                 cos = SkScalarCos(r);
         v[i+1].set(cos, sin);
         t[i+1].set(i*tx/n, ty);
     }
@@ -329,8 +329,9 @@
     SkPoint* t = rec->fTexs;
 
     for (int i = 0; i < n; i++) {
-        SkScalar cos;
-        SkScalar sin = SkScalarSinCos(SK_ScalarPI * 2 * i / n, &cos);
+        SkScalar r   = SK_ScalarPI * 2 * i / n,
+                 sin = SkScalarSin(r),
+                 cos = SkScalarCos(r);
         v[i*2 + 0].set(cos/2, sin/2);
         v[i*2 + 1].set(cos, sin);
 
@@ -429,7 +430,7 @@
             canvas.restore();
             SkString str;
             str.printf("/skimages/slide_" SK_SIZE_T_SPECIFIER ".png", i);
-            sk_tool_utils::EncodeImageToFile(str.c_str(), bm, SkEncodedImageFormat::kPNG, 100);
+            ToolUtils::EncodeImageToFile(str.c_str(), bm, SkEncodedImageFormat::kPNG, 100);
         }
         this->setBGColor(BG_COLOR);
     }
diff --git a/samplecode/SampleTextBox.cpp b/samplecode/SampleTextBox.cpp
index 3b67af4..b588c46c 100644
--- a/samplecode/SampleTextBox.cpp
+++ b/samplecode/SampleTextBox.cpp
@@ -25,12 +25,6 @@
 #include "SkTypeface.h"
 #include "SkUTF.h"
 
-extern void skia_set_text_gamma(float blackGamma, float whiteGamma);
-
-#if defined(SK_BUILD_FOR_WIN) && defined(SK_FONTHOST_WIN_GDI)
-extern SkTypeface* SkCreateTypefaceFromLOGFONT(const LOGFONT&);
-#endif
-
 static const char gText[] =
     "When in the Course of human events it becomes necessary for one people "
     "to dissolve the political bands which have connected them with another "
@@ -41,20 +35,7 @@
 
 class TextBoxView : public Sample {
 public:
-    TextBoxView() {
-#if defined(SK_BUILD_FOR_WIN) && defined(SK_FONTHOST_WIN_GDI)
-        LOGFONT lf;
-        sk_bzero(&lf, sizeof(lf));
-        lf.lfHeight = 9;
-        SkTypeface* tf0 = SkCreateTypefaceFromLOGFONT(lf);
-        lf.lfHeight = 12;
-        SkTypeface* tf1 = SkCreateTypefaceFromLOGFONT(lf);
-        // we assert that different sizes should not affect which face we get
-        SkASSERT(tf0 == tf1);
-        tf0->unref();
-        tf1->unref();
-#endif
-    }
+    TextBoxView() : fShaper(SkShaper::Make()) {}
 
 protected:
     bool onQuery(Sample::Event* evt) override {
@@ -71,23 +52,20 @@
         canvas->clipRect(SkRect::MakeWH(w, h));
         canvas->drawColor(bg);
 
-        SkShaper shaper(nullptr);
-
         SkScalar margin = 20;
 
         SkPaint paint;
         paint.setColor(fg);
 
         for (int i = 9; i < 24; i += 2) {
-            SkTextBlobBuilderRunHandler builder;
+            SkTextBlobBuilderRunHandler builder(gText, { margin, margin });
             SkFont font(nullptr, SkIntToScalar(i));
             font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
 
-            SkPoint end = shaper.shape(&builder, font, gText, strlen(gText), true,
-                                       { margin, margin }, w - margin);
+            fShaper->shape(gText, strlen(gText), font, true, w - margin, &builder);
             canvas->drawTextBlob(builder.makeBlob(), 0, 0, paint);
 
-            canvas->translate(0, end.y());
+            canvas->translate(0, builder.endPoint().y());
         }
     }
 
@@ -103,6 +81,7 @@
     }
 
 private:
+    std::unique_ptr<SkShaper> fShaper;
     typedef Sample INHERITED;
 };
 
diff --git a/samplecode/SampleThinAA.cpp b/samplecode/SampleThinAA.cpp
index bbb2427..d3c7e38 100644
--- a/samplecode/SampleThinAA.cpp
+++ b/samplecode/SampleThinAA.cpp
@@ -7,7 +7,7 @@
 
 #include "Sample.h"
 
-#include "SkAnimTimer.h"
+#include "AnimTimer.h"
 #include "SkCanvas.h"
 #include "SkColorFilter.h"
 #include "SkFont.h"
@@ -221,7 +221,7 @@
                 0.f, 0.f, 0.f, 255.f, 0.f
             };
 
-            blit.setColorFilter(SkColorFilter::MakeMatrixFilterRowMajor255(kFilter));
+            blit.setColorFilter(SkColorFilters::MatrixRowMajor255(kFilter));
         }
 
         canvas->scale(scale, scale);
@@ -316,7 +316,7 @@
         this->drawShapes(canvas, "SSx64", 4, fSS16);
     }
 
-    bool onAnimate(const SkAnimTimer& timer) override {
+    bool onAnimate(const AnimTimer& timer) override {
         SkScalar t = timer.secs();
         SkScalar dt = fLastFrameTime < 0.f ? 0.f : t - fLastFrameTime;
         fLastFrameTime = t;
diff --git a/samplecode/SampleTiling.cpp b/samplecode/SampleTiling.cpp
index 14e40c4..3c219a2 100644
--- a/samplecode/SampleTiling.cpp
+++ b/samplecode/SampleTiling.cpp
@@ -37,13 +37,12 @@
 
     paint.setDither(true);
     paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos,
-                SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode));
+                SK_ARRAY_COUNT(colors), SkTileMode::kClamp));
     canvas.drawPaint(paint);
 }
 
-static void setup(SkPaint* paint, const SkBitmap& bm, bool filter,
-                  SkShader::TileMode tmx, SkShader::TileMode tmy) {
-    paint->setShader(SkShader::MakeBitmapShader(bm, tmx, tmy));
+static void setup(SkPaint* paint, const SkBitmap& bm, bool filter, SkTileMode tmx, SkTileMode tmy) {
+    paint->setShader(bm.makeShader(tmx, tmy));
     paint->setFilterQuality(filter ? kLow_SkFilterQuality : kNone_SkFilterQuality);
 }
 
@@ -89,8 +88,8 @@
         static const bool           gFilters[] = { false, true };
         static const char*          gFilterNames[] = {     "point",                     "bilinear" };
 
-        static const SkShader::TileMode gModes[] = { SkShader::kClamp_TileMode, SkShader::kRepeat_TileMode, SkShader::kMirror_TileMode };
-        static const char*          gModeNames[] = {    "C",                    "R",                   "M" };
+        static const SkTileMode gModes[] = { SkTileMode::kClamp, SkTileMode::kRepeat, SkTileMode::kMirror };
+        static const char*  gModeNames[] = {    "C",                    "R",                   "M" };
 
         SkScalar y = SkIntToScalar(24);
         SkScalar x = SkIntToScalar(10);
diff --git a/samplecode/SampleUnpremul.cpp b/samplecode/SampleUnpremul.cpp
index da7e42d..e7278ff 100644
--- a/samplecode/SampleUnpremul.cpp
+++ b/samplecode/SampleUnpremul.cpp
@@ -5,12 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "sk_tool_utils.h"
 #include "DecodeFile.h"
 #include "Resources.h"
 #include "Sample.h"
-#include "SkBlurMask.h"
 #include "SkBlurDrawLooper.h"
+#include "SkBlurMask.h"
 #include "SkCanvas.h"
 #include "SkColorPriv.h"
 #include "SkOSFile.h"
@@ -19,6 +18,7 @@
 #include "SkString.h"
 #include "SkTypes.h"
 #include "SkUTF.h"
+#include "ToolUtils.h"
 
 /**
  *  Interprets c as an unpremultiplied color, and returns the
@@ -69,7 +69,7 @@
     }
 
     void onDrawBackground(SkCanvas* canvas) override {
-        sk_tool_utils::draw_checkerboard(canvas, 0xFFCCCCCC, 0xFFFFFFFF, 12);
+        ToolUtils::draw_checkerboard(canvas, 0xFFCCCCCC, 0xFFFFFFFF, 12);
     }
 
     void onDrawContent(SkCanvas* canvas) override {
diff --git a/samplecode/SampleVertices.cpp b/samplecode/SampleVertices.cpp
index 15f2d31..a6c5ab3 100644
--- a/samplecode/SampleVertices.cpp
+++ b/samplecode/SampleVertices.cpp
@@ -34,7 +34,7 @@
     pixels[0] = pixels[2] = color0;
     pixels[1] = pixels[3] = color1;
 
-    return SkShader::MakeBitmapShader(bm, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode);
+    return bm.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat);
 }
 
 static sk_sp<SkShader> make_shader1(const SkIPoint& size) {
@@ -42,7 +42,7 @@
                       { SkIntToScalar(size.fX), SkIntToScalar(size.fY) } };
     SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorRED };
     return SkGradientShader::MakeLinear(pts, colors, nullptr,
-                    SK_ARRAY_COUNT(colors), SkShader::kMirror_TileMode);
+                    SK_ARRAY_COUNT(colors), SkTileMode::kMirror);
 }
 
 class VerticesView : public Sample {
@@ -158,8 +158,9 @@
         v[0].set(0, 0);
         t[0].set(0, 0);
         for (int i = 0; i < n; i++) {
-            SkScalar cos;
-            SkScalar sin = SkScalarSinCos(SK_ScalarPI * 2 * i / n, &cos);
+            SkScalar r   = SK_ScalarPI * 2 * i / n,
+                     sin = SkScalarSin(r),
+                     cos = SkScalarCos(r);
             v[i+1].set(cos, sin);
             t[i+1].set(i*tx/n, ty);
         }
@@ -186,8 +187,9 @@
         SkPoint* t = rec->fTexs;
 
         for (int i = 0; i < n; i++) {
-            SkScalar cos;
-            SkScalar sin = SkScalarSinCos(SK_ScalarPI * 2 * i / n, &cos);
+            SkScalar r   = SK_ScalarPI * 2 * i / n,
+                     sin = SkScalarSin(r),
+                     cos = SkScalarCos(r);
             v[i*2 + 0].set(cos/2, sin/2);
             v[i*2 + 1].set(cos, sin);
 
diff --git a/samplecode/SampleXfer.cpp b/samplecode/SampleXfer.cpp
index bc6b911..7f05e01 100644
--- a/samplecode/SampleXfer.cpp
+++ b/samplecode/SampleXfer.cpp
@@ -5,18 +5,17 @@
  * found in the LICENSE file.
  */
 
+#include "AnimTimer.h"
 #include "Sample.h"
-#include "SkAnimTimer.h"
-#include "SkDrawable.h"
 #include "SkCanvas.h"
 #include "SkDrawable.h"
+#include "SkGradientShader.h"
 #include "SkPath.h"
-#include "SkRandom.h"
 #include "SkRSXform.h"
+#include "SkRandom.h"
 #include "SkString.h"
 #include "SkSurface.h"
 #include "SkTextUtils.h"
-#include "SkGradientShader.h"
 
 const SkBlendMode gModes[] = {
     SkBlendMode::kSrcOver,
@@ -86,7 +85,7 @@
         const SkColor colors[] = { 0, c };
         fPaint.setShader(SkGradientShader::MakeRadial(SkPoint::Make(size/2, size/2), size/2,
                                                                      colors, nullptr, 2,
-                                                                     SkShader::kClamp_TileMode));
+                                                                     SkTileMode::kClamp));
         fBounds = SkRect::MakeWH(size, size);
     }
 
diff --git a/samplecode/SampleXfermodesBlur.cpp b/samplecode/SampleXfermodesBlur.cpp
index 75ad9d1..079ba3c 100644
--- a/samplecode/SampleXfermodesBlur.cpp
+++ b/samplecode/SampleXfermodesBlur.cpp
@@ -104,8 +104,7 @@
         const SkScalar h = SkIntToScalar(H);
         SkMatrix m;
         m.setScale(SkIntToScalar(6), SkIntToScalar(6));
-        auto s = SkShader::MakeBitmapShader(fBG, SkShader::kRepeat_TileMode,
-                                            SkShader::kRepeat_TileMode, &m);
+        auto s = fBG.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &m);
 
         SkFont font;
         font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
diff --git a/site/dev/contrib/directory.md b/site/dev/contrib/directory.md
index 4edab89..0694cd4 100644
--- a/site/dev/contrib/directory.md
+++ b/site/dev/contrib/directory.md
@@ -4,8 +4,7 @@
 *   Docs & Bugs
     -   [Skia.org](https://skia.org/)
     -   [Issue Tracker](https://bug.skia.org/)
-    -   [Autogenerated API
-        Documentation](https://skia-doc.commondatastorage.googleapis.com/doxygen/doxygen/html/index.html)
+    -   [Autogenerated API Documentation](https://api.skia.org)
 
 *   Code Repositories
     -   [Git repository](https://skia.googlesource.com/skia/)
diff --git a/site/dev/design/sync.md b/site/dev/design/sync.md
deleted file mode 100644
index 1172058..0000000
--- a/site/dev/design/sync.md
+++ /dev/null
@@ -1,13 +0,0 @@
-sync
-====
-
-[`sync`](https://skia.googlesource.com/skia.git/+/master/bin/sync)
-is a Python program that wraps `tools/git-sync-deps`.  Motivations for using it:
-
--  Written in Python, so it will work on all platforms.
-
--  Checks to see if the `DEPS` file has changed since it last ran
-   `gclient sync`.  If not, it skips that step.
-
--  Since running `sync` is fast when it can do nothing, it is
-   easy to do before every recompile of Skia.  This is a good habit.
diff --git a/site/dev/sheriffing/trooper.md b/site/dev/sheriffing/trooper.md
index c21af03..ea6c2c5 100644
--- a/site/dev/sheriffing/trooper.md
+++ b/site/dev/sheriffing/trooper.md
@@ -1,64 +1,5 @@
 Infra Trooper Documentation
 ===========================
 
-### Contents ###
-
-*   [What does an Infra trooper do?](#what_is_a_trooper)
-*   [View current and upcoming troopers](#view_current_upcoming_troopers)
-*   [How to swap trooper shifts](#how_to_swap)
-*   [Tips for troopers](#tips)
-
-
-<a name="what_is_a_trooper"></a>
-What does an Infra trooper do?
-------------------------------
-
-The trooper has two main jobs:
-
-1) Keep an eye on Infra alerts available [here](https://promalerts.skia.org/#/alerts?receiver=skiabot).
-
-2) Resolve the above alerts as they come in.
-
-<a name="view_current_upcoming_troopers"></a>
-View current and upcoming troopers
-----------------------------------
-
-The list of troopers is specified in the [skia-tree-status web app](http://skia-tree-status.appspot.com/trooper). The current trooper is highlighted in green.
-The banner on the top of the [status page](https://status.skia.org) also displays the current trooper.
-
-
-<a name="how_to_swap"></a>
-How to swap trooper shifts
---------------------------
-
-If you need to swap shifts with someone (because you are out sick or on vacation), please get approval from the person you want to swap with. Then make the change in the [cloud console](https://console.cloud.google.com/datastore/entities/query?project=skia-tree-status&organizationId=433637338589&ns=&kind=TrooperSchedules). Add a filter to find the dates you are looking for and then click on the entries you want to edit.
-
-Note: The above link can be used to update the sheriff/wrangler/robocop schedules as well.
-
-
-<a name="tips"></a>
-Tips for troopers
------------------
-
-- Go over the [trooper handoff doc](https://docs.google.com/document/d/1I1tB0Cv2fme4FY0lAF2gYeEbZ_0kehLIi3vf3vuPkx0/edit) to be aware of ongoing problems and any issues the previous trooper ran into. Document any notes there from your trooper week that might help the next trooper.
-
-- Make sure you are a member of
-  [MDB group chrome-skia-ninja](https://ganpati.corp.google.com/#Group_Info?name=chrome-skia-ninja@prod.google.com).
-  Valentine passwords and Chrome Golo access are based on membership in this
-  group.
-
-- Install the Skia trooper Chrome extension (available [here](https://chrome.google.com/webstore/a/google.com/detail/alerts-for-skia-troopers/fpljhfiomnfioecagooiekldeolcpief)) to be able to see alerts quickly in the browser.
-
-- See [this section](../testing/swarmingbots#connecting-to-swarming-bots) for details on how to connect to the different Skia bots.
-
-- The [chrome-infra hangout](https://goto.google.com/cit-hangout) is useful for
-  questions regarding bots managed by the Chrome Infra team and to get
-  visibility into upstream failures that cause problems for us.
-
-- If there is a problem with a bot in the Chrome Golo or Chrome infra GCE, the
-  best course of action is to
-  [file a bug](https://code.google.com/p/chromium/issues/entry?template=Build%20Infrastructure)
-  with the Chrome infra team.
-
-- Read over the [Skolo maintenance doc](https://docs.google.com/document/d/1zTR1YtrIFBo-fRWgbUgvJNVJ-s_4_sNjTrHIoX2vulo/edit) for more detail on
-  dealing with device alerts.
+The trooper handles problems with Skia's build and test infrastructure.
+Documentation for troopering is found at [go/skia-infra-trooper](http://go/skia-infra-trooper) (Googler's only).
diff --git a/site/dev/testing/automated_testing.md b/site/dev/testing/automated_testing.md
index ecdd484..16ff488 100644
--- a/site/dev/testing/automated_testing.md
+++ b/site/dev/testing/automated_testing.md
@@ -68,7 +68,7 @@
 
 * green: success
 * orange: failure
-* purple: exception (infrastructure issue)
+* purple: mishap (infrastructure issue)
 * black border, no fill: task in progress
 * blank: no task has started yet for a given revision
 
@@ -110,7 +110,7 @@
 to recipes:
 
 * If there are new GN flags or compiler options:
-  [infra/bots/recipe_modules/flavor/gn_flavor.py][gn flavor py]
+  [infra/bots/recipe_modules/build][build recipe module], probably default.py.
 * If there are modifications to dm flags: [infra/bots/recipes/test.py][test py]
 * If there are modifications to nanobench flags:
   [infra/bots/recipes/perf.py][perf py]
@@ -121,16 +121,68 @@
 appear in the PolyGerrit UI after the next successful run of the
 Housekeeper-Nightly-UpdateMetaConfig task.)
 
-If you need to do something more complicated, or if you are not sure how to add
-and configure the new jobs, please ask for help from borenet, benjaminwagner, or
-mtklein.
-
 [new bot request]:
     https://bugs.chromium.org/p/skia/issues/entry?template=New+Bot+Request
 [jobs json]: https://skia.googlesource.com/skia/+/master/infra/bots/jobs.json
-[gn flavor py]:
-    https://skia.googlesource.com/skia/+/master/infra/bots/recipe_modules/flavor/gn_flavor.py
+[build recipe module]:
+    https://skia.googlesource.com/skia/+/refs/heads/master/infra/bots/recipe_modules/build/
 [test py]:
     https://skia.googlesource.com/skia/+/master/infra/bots/recipes/test.py
 [perf py]:
     https://skia.googlesource.com/skia/+/master/infra/bots/recipes/perf.py
+
+
+Detail on Skia Tasks
+--------------------
+
+[infra/bots/gen_tasks.go][gen_tasks] reads config files:
+
+* [infra/bots/jobs.json][jobs json]
+* [infra/bots/cfg.json][cfg json]
+* [infra/bots/recipe_modules/builder_name_schema/builder_name_schema.json][builder_name_schema]
+
+Based on each job name in jobs.json, gen_tasks decides which tasks to generate (process
+function). Various helper functions return task name of the direct dependencies of the job.
+
+In gen_tasks, tasks are specified with a TaskSpec. A TaskSpec specifies how to generate and trigger
+a Swarming task.
+
+Most Skia tasks run a recipe with Kitchen. The arguments to the kitchenTask function specify the
+most common parameters for a TaskSpec that will run a recipe. More info on recipes at
+[infra/bots/recipes/README.md][recipes README] and
+[infra/bots/recipe_modules/README.md][recipe_modules README].
+
+The Swarming task is generated based on several parameters of the TaskSpec:
+
+* Isolate: specifies the isolate file. The isolate file specifies the files from the repo to place
+  on the bot before running the task. (For non-Kitchen tasks, the isolate also specifies the command
+  to run.) [More info][isolate user guide].
+* Command: the command to run, if not specified in the Isolate. (Generally this is a boilerplate
+  Kitchen command that runs a recipe; see below.)
+* CipdPackages: specifies the IDs of CIPD packages that will be placed on the bot before running the
+  task. See infra/bots/assets/README.md for more info.
+* Dependencies: specifies the names of other tasks that this task depends upon. The outputs of those
+  tasks will be placed on the bot before running this task.
+* Dimensions: specifies what kind of bot should run this task. Ask Infra team for how to set this.
+* ExecutionTimeout: total time the task is allowed to run before it is killed.
+* IoTimeout: amount of time the task can run without printing something to stdout/stderr before it
+  is killed.
+* Expiration: Mostly ignored. If the task happens to be scheduled when there are no bots that can
+  run it, it will remain pending for this long before being canceled.
+
+If you need to do something more complicated, or if you are not sure how to add
+and configure the new jobs, please ask for help from borenet, benjaminwagner, or
+mtklein.
+
+[gen_tasks]:
+	https://skia.googlesource.com/skia/+/master/infra/bots/gen_tasks.go
+[cfg json]:
+	https://skia.googlesource.com/skia/+/master/infra/bots/cfg.json
+[builder_name_schema]:
+	https://skia.googlesource.com/skia/+/master/infra/bots/recipe_modules/builder_name_schema/builder_name_schema.json
+[recipes README]:
+    https://skia.googlesource.com/skia/+/master/infra/bots/recipes/README.md
+[recipe_modules README]:
+    https://skia.googlesource.com/skia/+/master/infra/bots/recipe_modules/README.md
+[isolate user guide]:
+    https://chromium.googlesource.com/infra/luci/luci-py/+/master/appengine/isolate/doc/client/Isolate-User-Guide.md
diff --git a/site/dev/testing/fonts.md b/site/dev/testing/fonts.md
index cb2a9e5..05fdf1c 100644
--- a/site/dev/testing/fonts.md
+++ b/site/dev/testing/fonts.md
@@ -18,14 +18,14 @@
 portable typeface on the paint, call:
 
 ~~~~
-sk_tool_utils::set_portable_typeface(SkPaint* , const char* name = nullptr,
+ToolUtils::set_portable_typeface(SkPaint* , const char* name = nullptr,
 SkFontStyle style = SkFontStyle());
 ~~~~
 
 To create a portable typeface, use:
 
 ~~~~
-SkTypeface* typeface = sk_tool_utils::create_portable_typeface(const char* name,
+SkTypeface* typeface = ToolUtils::create_portable_typeface(const char* name,
 SkFontStyle style);
 ~~~~
 
diff --git a/site/dev/testing/skqp.md b/site/dev/testing/skqp.md
index 791fb8f..2c6dae7 100644
--- a/site/dev/testing/skqp.md
+++ b/site/dev/testing/skqp.md
@@ -11,6 +11,9 @@
 The listing can be found here:
 [https://storage.googleapis.com/skia-skqp/apklist](https://storage.googleapis.com/skia-skqp/apklist)
 
+If you are looking at Android CTS failures, use the most recent commit on the
+`origin/skqp/release` branch.
+
 To run tests:
 
     adb install -r skqp-universal-{APK_SHA_HERE}.apk
@@ -24,14 +27,22 @@
 Note the test's output path on the device.  It will look something like this:
 
     01-23 15:22:12.688 27158 27173 I org.skia.skqp:
-    output written to "/storage/emulated/0/Android/data/org.skia.skqp/files/output"
+    output written to "/storage/emulated/0/Android/data/org.skia.skqp/files/skqp_report_2019-02-28T102058"
 
 Retrieve and view the report with:
 
-    OUTPUT_LOCATION="/storage/emulated/0/Android/data/org.skia.skqp/files/output"
-    adb pull $OUTPUT_LOCATION /tmp/
+    OUTPUT_LOCATION="/storage/emulated/0/Android/data/org.skia.skqp/files/skqp_report_2019-02-28T102058"
+    adb pull "$OUTPUT_LOCATION" /tmp/
 
-Open the file `/tmp/output/skqp_report/report.html` .
+(Your value of `$OUTPUT_LOCATION` will differ from mine.
+
+Open the file `/tmp/output/skqp_report_2019-02-28T102058/report.html` .
+
+**Zip up that directory to attach to a bug report:**
+
+    cd /tmp
+    zip -r skqp_report_2019-02-28T102058.zip skqp_report_2019-02-28T102058
+    ls -l skqp_report_2019-02-28T102058.zip
 
 * * *
 
diff --git a/site/schedule.md b/site/schedule.md
index ad838a4..8702bf5 100644
--- a/site/schedule.md
+++ b/site/schedule.md
@@ -10,18 +10,18 @@
 After the six week period, when another branch is cut, only critical (typically
 security) fixes will be committed to any previous branch.
 
-Skia 2018 schedule:
+Skia 2019 schedule:
 
   Milestone | Branch Date (beginning of day)
   ----------|-------------------------------
-  65        | 01/18/18
-  66        | 03/01/18
-  67        | 04/12/18
-  68        | 05/24/18
-  69        | 07/19/18
-  70        | 08/30/18
-  71        | 10/11/18
-  72        | 11/29/18
+  73        | 01/24/19
+  74        | 03/07/19
+  75        | 04/18/19
+  76        | 05/30/19
+  77        | 07/25/19
+  78        | 09/05/19
+  79        | 10/17/19
+  80        | 12/05/19
 
 The current milestone is included in the headers in
 [SkMilestone.h](https://skia.googlesource.com/skia/+/master/include/core/SkMilestone.h).
diff --git a/site/user/api/METADATA b/site/user/api/METADATA
index e64f4f7..799eace 100644
--- a/site/user/api/METADATA
+++ b/site/user/api/METADATA
@@ -23,7 +23,6 @@
     "SkSurface_Reference",
     "SkTextBlob_Reference",
     "SkTextBlobBuilder_Reference",
-    "usingBookmaker",
     "skcanvas_overview",
     "skcanvas_creation",
     "SkBlendMode_Overview",
diff --git a/site/user/api/SkCanvas_Reference.md b/site/user/api/SkCanvas_Reference.md
index 11a4fe6..206a3ce 100644
--- a/site/user/api/SkCanvas_Reference.md
+++ b/site/user/api/SkCanvas_Reference.md
@@ -922,7 +922,7 @@
 Pixels are readable when <a href='undocumented#SkBaseDevice'>SkBaseDevice</a> is raster. Pixels are not readable when <a href='SkCanvas_Reference#SkCanvas'>SkCanvas</a>
 is returned from  <a href='undocumented#GPU_Surface'>GPU surface</a>, returned by <a href='undocumented#SkDocument'>SkDocument</a>::<a href='#SkDocument_beginPage'>beginPage</a>, returned by
 <a href='undocumented#SkPictureRecorder'>SkPictureRecorder</a>::<a href='#SkPictureRecorder_beginRecording'>beginRecording</a>, or <a href='SkCanvas_Reference#SkCanvas'>SkCanvas</a> is the base of a utility class
-like <a href='undocumented#SkDebugCanvas'>SkDebugCanvas</a>.
+like <a href='undocumented#DebugCanvas'>DebugCanvas</a>.
 
 <a href='#SkCanvas_peekPixels_pixmap'>pixmap</a> is valid only while <a href='SkCanvas_Reference#SkCanvas'>SkCanvas</a> is in scope and unchanged. Any
 <a href='SkCanvas_Reference#SkCanvas'>SkCanvas</a> or <a href='SkSurface_Reference#SkSurface'>SkSurface</a> call may invalidate the <a href='#SkCanvas_peekPixels_pixmap'>pixmap</a> values.
@@ -973,7 +973,7 @@
 Pixels are readable when <a href='undocumented#Device'>Device</a> is raster, or backed by a GPU.
 Pixels are not readable when <a href='SkCanvas_Reference#SkCanvas'>SkCanvas</a> is returned by <a href='undocumented#SkDocument'>SkDocument</a>::<a href='#SkDocument_beginPage'>beginPage</a>,
 returned by <a href='undocumented#SkPictureRecorder'>SkPictureRecorder</a>::<a href='#SkPictureRecorder_beginRecording'>beginRecording</a>, or <a href='SkCanvas_Reference#Canvas'>Canvas</a> is the base of a utility
-class like <a href='undocumented#SkDebugCanvas'>SkDebugCanvas</a>.
+class like <a href='undocumented#DebugCanvas'>DebugCanvas</a>.
 
 The destination  <a href='undocumented#Pixel_Storage'>pixel storage</a> must be allocated by the caller.
 
@@ -1067,7 +1067,7 @@
 Pixels are readable when <a href='undocumented#Device'>Device</a> is raster, or backed by a GPU.
 Pixels are not readable when <a href='SkCanvas_Reference#SkCanvas'>SkCanvas</a> is returned by <a href='undocumented#SkDocument'>SkDocument</a>::<a href='#SkDocument_beginPage'>beginPage</a>,
 returned by <a href='undocumented#SkPictureRecorder'>SkPictureRecorder</a>::<a href='#SkPictureRecorder_beginRecording'>beginRecording</a>, or <a href='SkCanvas_Reference#Canvas'>Canvas</a> is the base of a utility
-class like <a href='undocumented#SkDebugCanvas'>SkDebugCanvas</a>.
+class like <a href='undocumented#DebugCanvas'>DebugCanvas</a>.
 
 Caller must allocate  <a href='undocumented#Pixel_Storage'>pixel storage</a> in <a href='#SkCanvas_readPixels_2_pixmap'>pixmap</a> if needed.
 
@@ -1147,7 +1147,7 @@
 Pixels are readable when <a href='undocumented#Device'>Device</a> is raster, or backed by a GPU.
 Pixels are not readable when <a href='SkCanvas_Reference#SkCanvas'>SkCanvas</a> is returned by <a href='undocumented#SkDocument'>SkDocument</a>::<a href='#SkDocument_beginPage'>beginPage</a>,
 returned by <a href='undocumented#SkPictureRecorder'>SkPictureRecorder</a>::<a href='#SkPictureRecorder_beginRecording'>beginRecording</a>, or <a href='SkCanvas_Reference#Canvas'>Canvas</a> is the base of a utility
-class like <a href='undocumented#SkDebugCanvas'>SkDebugCanvas</a>.
+class like <a href='undocumented#DebugCanvas'>DebugCanvas</a>.
 
 Caller must allocate  <a href='undocumented#Pixel_Storage'>pixel storage</a> in <a href='#SkCanvas_readPixels_3_bitmap'>bitmap</a> if needed.
 
@@ -1227,7 +1227,7 @@
 Pixels are writable when <a href='undocumented#Device'>Device</a> is raster, or backed by a GPU.
 Pixels are not writable when <a href='SkCanvas_Reference#SkCanvas'>SkCanvas</a> is returned by <a href='undocumented#SkDocument'>SkDocument</a>::<a href='#SkDocument_beginPage'>beginPage</a>,
 returned by <a href='undocumented#SkPictureRecorder'>SkPictureRecorder</a>::<a href='#SkPictureRecorder_beginRecording'>beginRecording</a>, or <a href='SkCanvas_Reference#Canvas'>Canvas</a> is the base of a utility
-class like <a href='undocumented#SkDebugCanvas'>SkDebugCanvas</a>.
+class like <a href='undocumented#DebugCanvas'>DebugCanvas</a>.
 
 <a href='undocumented#Pixel'>Pixel</a> values are converted only if <a href='#Image_Info_Color_Type'>Color_Type</a> and <a href='#Image_Info_Alpha_Type'>Alpha_Type</a>
 do not match. Only <a href='#SkCanvas_writePixels_pixels'>pixels</a> within both source and destination rectangles
@@ -1301,7 +1301,7 @@
 Pixels are writable when <a href='undocumented#Device'>Device</a> is raster, or backed by a GPU.
 Pixels are not writable when <a href='SkCanvas_Reference#SkCanvas'>SkCanvas</a> is returned by <a href='undocumented#SkDocument'>SkDocument</a>::<a href='#SkDocument_beginPage'>beginPage</a>,
 returned by <a href='undocumented#SkPictureRecorder'>SkPictureRecorder</a>::<a href='#SkPictureRecorder_beginRecording'>beginRecording</a>, or <a href='SkCanvas_Reference#Canvas'>Canvas</a> is the base of a utility
-class like <a href='undocumented#SkDebugCanvas'>SkDebugCanvas</a>.
+class like <a href='undocumented#DebugCanvas'>DebugCanvas</a>.
 
 <a href='undocumented#Pixel'>Pixel</a> values are converted only if <a href='#Image_Info_Color_Type'>Color_Type</a> and <a href='#Image_Info_Alpha_Type'>Alpha_Type</a>
 do not match. Only pixels within both source and destination rectangles
diff --git a/site/user/api/SkPaint_Reference.md b/site/user/api/SkPaint_Reference.md
index 6fcdcde..76fccba 100644
--- a/site/user/api/SkPaint_Reference.md
+++ b/site/user/api/SkPaint_Reference.md
@@ -611,33 +611,6 @@
 
 </fiddle-embed></div>
 
-<a name='Flags'></a>
-
-<a name='SkPaint_Flags'></a>
-
----
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-    enum <a href='#SkPaint_Flags'>Flags</a> {
-        <a href='#SkPaint_kAntiAlias_Flag'>kAntiAlias_Flag</a> = 0x01,
-        <a href='#SkPaint_kDither_Flag'>kDither_Flag</a> = 0x04,
-        <a href='#SkPaint_kFakeBoldText_Flag'>kFakeBoldText_Flag</a> = 0x20,
-        <a href='#SkPaint_kLinearText_Flag'>kLinearText_Flag</a> = 0x40,
-        <a href='#SkPaint_kSubpixelText_Flag'>kSubpixelText_Flag</a> = 0x80,
-        <a href='#SkPaint_kLCDRenderText_Flag'>kLCDRenderText_Flag</a> = 0x200,
-        <a href='#SkPaint_kEmbeddedBitmapText_Flag'>kEmbeddedBitmapText_Flag</a> = 0x400,
-        <a href='#SkPaint_kAutoHinting_Flag'>kAutoHinting_Flag</a> = 0x800,
-        <a href='#SkPaint_kAllFlags'>kAllFlags</a> = 0xFFFF,
-    };
-
-</pre>
-
-The bit values stored in <a href='#SkPaint_Flags'>Flags</a>.
-The default value for <a href='#SkPaint_Flags'>Flags</a>, normally zero, can be changed at compile time
-with a custom definition of <a href='undocumented#SkPaintDefaults_Flags'>SkPaintDefaults_Flags</a>.
-All flags can be read and written explicitly; <a href='#SkPaint_Flags'>Flags</a> allows manipulating
-multiple settings at once.
-
 ### Constants
 
 <table style='border-collapse: collapse; width: 62.5em'>
diff --git a/site/user/api/catalog.htm b/site/user/api/catalog.htm
index 2c2431b..b578bb9 100644
--- a/site/user/api/catalog.htm
+++ b/site/user/api/catalog.htm
@@ -7380,14 +7380,6 @@
     "file": "SkPaint_Reference",
     "name": "SkPaint::breakText"
 },
-    "SkPaint_empty_constructor": {
-    "code": "void draw(SkCanvas* canvas) {\n    #ifndef SkUserConfig_DEFINED\n    #define SkUserConfig_DEFINED\n    #define SkPaintDefaults_Flags      0x01   // always enable antialiasing\n    #define SkPaintDefaults_TextSize   24.f   // double default font size\n    #define SkPaintDefaults_Hinting    3      // use full hinting\n    #define SkPaintDefaults_MiterLimit 10.f   // use HTML Canvas miter limit setting\n    #endif\n}",
-    "width": 256,
-    "height": 1,
-    "hash": "c4b2186d85c142a481298f7144295ffd",
-    "file": "SkPaint_Reference",
-    "name": "SkPaint::SkPaint()"
-},
     "SkPaint_getFillPath": {
     "code": "void draw(SkCanvas* canvas) {\n    SkPaint strokePaint;\n    strokePaint.setAntiAlias(true);\n    strokePaint.setStyle(SkPaint::kStroke_Style);\n    strokePaint.setStrokeWidth(.1f);\n    SkPath strokePath;\n    strokePath.moveTo(.08f, .08f);\n    strokePath.quadTo(.09f, .08f, .17f, .17f);\n    SkPath fillPath;\n    SkPaint outlinePaint(strokePaint);\n    outlinePaint.setStrokeWidth(2);\n    SkMatrix scale = SkMatrix::MakeScale(300, 300);\n    for (SkScalar precision : { 0.01f, .1f, 1.f, 10.f, 100.f } ) {\n        strokePaint.getFillPath(strokePath, &fillPath, nullptr, precision);\n        fillPath.transform(scale);\n        canvas->drawPath(fillPath, outlinePaint);\n        canvas->translate(60, 0);\n        if (1.f == precision) canvas->translate(-180, 100);\n    }\n    strokePath.transform(scale);\n    strokePaint.setStrokeWidth(30);\n    canvas->drawPath(strokePath, strokePaint);\n}\n",
     "width": 256,
@@ -9741,4 +9733,4 @@
 ></canvas >
 </body>
 </html>
-    
\ No newline at end of file
+
diff --git a/site/user/api/index.md b/site/user/api/index.md
index 102fbde..0e973cf 100644
--- a/site/user/api/index.md
+++ b/site/user/api/index.md
@@ -31,11 +31,9 @@
 
 Check out [a graphical overview of examples](api/catalog.htm)
 
-All public APIs are indexed by Doxygen. The Doxyen index is current, but the
-content is dated and incomplete. Doxygen content will be superseded by
-full references with examples.
+All public APIs are indexed by Doxygen.
 
-*   [Skia Doxygen](http://skia-doc.commondatastorage.googleapis.com/doxygen/doxygen/html/index.html)
+*   [Skia Doxygen](https://api.skia.org)
 
 ## Overview
 
diff --git a/site/user/api/undocumented.md b/site/user/api/undocumented.md
index 1d02d99..08b8b46 100644
--- a/site/user/api/undocumented.md
+++ b/site/user/api/undocumented.md
@@ -79,7 +79,7 @@
 
 ---
 
-<a name='SkDebugCanvas'></a>
+<a name='DebugCanvas'></a>
 
 ---
 
@@ -570,12 +570,6 @@
   <tr><th style='text-align: left; border: 2px solid #dddddd; padding: 8px; '>Const</th>
 <th style='text-align: center; border: 2px solid #dddddd; padding: 8px; '>Value</th>
 <th style='text-align: left; border: 2px solid #dddddd; padding: 8px; '>Description</th></tr>
-  <tr style='background-color: #f0f0f0; '>
-    <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '><a name='SkPaintDefaults_Flags'><code>SkPaintDefaults_Flags</code></a></td>
-    <td style='text-align: center; border: 2px solid #dddddd; padding: 8px; '>0</td>
-    <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '>
-</td>
-  </tr>
   <tr>
     <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '><a name='SkPaintDefaults_Hinting'><code>SkPaintDefaults_Hinting</code></a></td>
     <td style='text-align: center; border: 2px solid #dddddd; padding: 8px; '>2</td>
diff --git a/site/user/api/usingBookmaker.md b/site/user/api/usingBookmaker.md
deleted file mode 100644
index 5aaa920..0000000
--- a/site/user/api/usingBookmaker.md
+++ /dev/null
@@ -1,307 +0,0 @@
-usingBookmaker
-===
-<a href='usingBookmaker#Bookmaker'>Bookmaker</a> generates markdown files to view documentation on skia.org, and generates includes for use in C++.
-<a href='usingBookmaker#Bookmaker'>Bookmaker</a> reads canonical documentation from files suffixed with bmh in the docs directory. These bmh
-files describe how public interfaces work, and generate Skia fiddle examples to illustrate them.
-
-The docs files must be manually edited to stay current with Skia as it evolves.
-
-<a name='Installing'></a>
-
-Install
-<a href='https://golang.org/doc/install'>Go</a></a> if needed.
-Check the version. The results should be 1.10 or greater.
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-$ go version
-</pre>
-
-Get the fiddle command <a href='undocumented#Line'>line</a> interface tool.
-By default this will appear in your home directory.
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-$ go get go.skia.org/infra/fiddlek/go/fiddlecli
-</pre>
-
-Check the version. The command should work and the result should be 1.0 or greater.
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-$ ~/go/bin/fiddlecli --version
-</pre>
-
-If fiddlecli is already installed but out of date, update with:
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-$ go get -u go.skia.org/infra/fiddlek/go/fiddlecli
-</pre>
-
-Build <a href='usingBookmaker#Bookmaker'>Bookmaker</a>.
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-$ ninja -C out/skia <a href='usingBookmaker#Bookmaker'>bookmaker </a>
-</pre>
-
-<a name='Running'></a>
-
-<a href='usingBookmaker#Bookmaker'>Bookmaker</a> extracts examples, generates example hashes with fiddle, and generates web markdown
-and c++ includes.
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-$ ./out/skia/bookmaker -E && ~/go/bin/fiddlecli --quiet && ./out/skia/bookmaker
-</pre>
-
-A successful run generates:
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-cross-check...................
-</pre>
-
-<a name='Broken_Build'></a>
-
-The bots
-<a href='https://status.skia.org/repo/skia?filter=search&search_value=Housekeeper-PerCommit-Bookmaker'>Housekeeper-PerCommit-Bookmaker</a></a> and
-<a href='https://status.skia.org/repo/skia?filter=search&search_value=Housekeeper-Nightly-Bookmaker'>Housekeeper-Nightly-Bookmaker</a></a> verify that <a href='usingBookmaker#Bookmaker'>Bookmaker</a> <a href='undocumented#Data'>data</a> in docs builds without error and is consistent with include files it documents.
-
-Possible failures include:
-
-<table>  <tr>
-    <td>Public interface in include directory does not match documented interface in docs directory.</td>
-  </tr>  <tr>
-    <td>Example in <a href='usingBookmaker#Bookmaker'>bookmaker</a> bmh file does not compile, or does not produce expected output.</td>
-  </tr>  <tr>
-    <td>Undocumented but referenced interface is missing from undocumented <a href='usingBookmaker#Bookmaker'>bookmaker</a> file in docs directory.</td>
-  </tr>
-</table>
-
-Editing comments in includes or editing private interfaces will not break the bots.
-<a href='usingBookmaker#Bookmaker'>Bookmaker</a> detects that comments edited in includes do not match comments in docs; it will generate an updated include in the
-directory where it is run.
-
-If
-<a href='https://status.skia.org/repo/skia?filter=search&search_value=Housekeeper-PerCommit-Bookmaker'>Housekeeper-PerCommit-Bookmaker</a></a> bot is red, the error is usually related to an edit to an include which has not been reflected in docs.
-
-To fix this, edit the docs file corresponding to the changed include file.
-
-For instance, if the change was made to <a href='SkIRect_Reference#SkIRect'>SkIRect</a>, edit docs/SkIRect_Reference.bmh.
-Checking in the edited docs/SkIRect_Reference.bmh will fix the bot.
-
-If the interface is deprecated, private, or experimental, documentation is not
-required. Put the word "Deprecated", "Private", or "Experimental"; upper or lower
-case, in a comment just before the symbol to be ignored.
-
-If
-<a href='https://status.skia.org/repo/skia?filter=search&search_value=Housekeeper-Nightly-Bookmaker'>Housekeeper-Nightly-Bookmaker</a></a> bot is red, one of several things may have gone wrong:
-
-<table>  <tr>
-    <td>A change to include broke documentation examples.</td>
-  </tr>  <tr>
-    <td>Something changed the examples that output <a href='undocumented#Text'>text</a>.</td>
-  </tr>  <tr>
-    <td>Some interface was added, deleted, edited.</td>
-  </tr>  <tr>
-    <td>Documentation is malformed.</td>
-  </tr>
-</table>
-
-The bot output describes what changed, and includes the file and <a href='undocumented#Line'>line</a>
-where the error occurred.
-
-To regenerate the documentation, follow the Installing and Regenerate steps below.
-
-<a name='Editing_Comments'></a>
-
-Edit docs instead of include/core files to update comments if possible.
-
-The <a href='usingBookmaker#Bookmaker'>Bookmaker</a> bots do not complain if the docs file does not match the
-corresponding include comments. Running <a href='usingBookmaker#Bookmaker'>Bookmaker</a> include generation will
-report when docs and includes comments do not match.
-
-For instance, if include/core/SkSurface.h comments do not match
-docs/SkSurface_Reference.bmh, running:
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-$ ./out/dir/bookmaker -b docs -i include/core/SkSurface.h -p
-</pre>
-
-generates
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-wrote updated <a href='SkSurface_Reference#SkSurface'>SkSurface</a>.h
-</pre>
-
-The updated <a href='SkSurface_Reference#SkSurface'>SkSurface</a>.h is written to the root to avoid subsequent runs of
-<a href='usingBookmaker#Bookmaker'>Bookmaker</a> from recompiling. if <a href='SkSurface_Reference#SkSurface'>SkSurface</a>.h was not changed, it is not written,
-and <a href='usingBookmaker#Bookmaker'>Bookmaker</a> will not generate any output.
-
-<a name='Broken_Example'></a>
-
-An example may cause <a href='usingBookmaker#Bookmaker'>Bookmaker</a> or a bot running <a href='usingBookmaker#Bookmaker'>Bookmaker</a> to fail if it fails to compile.
-
-Fix the example by pasting it into <a href='https://fiddle.skia.org'>Skia Fiddle</a></a> and editing it until it runs successfully.
-
-If the example cannot be fixed, it can be commented out by changing
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-#Example
-</pre>
-
-to
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-#NoExample
-</pre>
-
-. The disabled example can contain additional markup, which will be ignored.
-
-<a name='Regenerate'></a>
-
-Complete rebuilding of all <a href='usingBookmaker#Bookmaker'>bookmaker</a> output looks like:
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-$ ./out/dir/bookmaker -a docs/status.json -e fiddle.json
-$ ~/go/bin/fiddlecli --input fiddle.json --output fiddleout.json
-$ ./out/dir/bookmaker -a docs/status.json -f fiddleout.json -r site/user/api -c
-$ ./out/dir/bookmaker -a docs/status.json -f fiddleout.json -r site/user/api
-$ ./out/dir/bookmaker -a docs/status.json -x
-$ ./out/dir/bookmaker -a docs/status.json -p
-</pre>
-
-<a name='New_Documentation'></a>
-
-Generate an starter <a href='usingBookmaker#Bookmaker'>Bookmaker</a> file from an existing include.
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-$ ./out/dir/bookmaker -i include/core/SkXXX.h -t docs
-</pre>
-
-If a method or function has an unnamed parameter, <a href='usingBookmaker#Bookmaker'>bookmaker</a> generates an error:
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-C:/puregit/include/core/SkPixmap.h(208): error: #Method missing param name
-bool erase(const <a href='SkColor4f_Reference#SkColor4f'>SkColor4f</a>&, const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>* subset = nullptr) const
-           ^
-</pre>
-
-All parameters require names to allow markdown and doxygen documents to refer to
-them. After naming all parameters, check in the include before continuing.
-
-A successful run generates
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-docs/SkXXX_Reference.bmh
-</pre>
-
-.
-
-Next, use your favorite editor to fill out
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-docs/SkXXX_Reference.bmh
-</pre>
-
-.
-
-<a name='Style'></a>
-
-Documentation consists of cross references, descriptions, and examples.
-All structs, classes, enums, their members and methods, functions, and so on,
-require descriptions. Most also require examples.
-
-All methods and functions should include examples if practical.
-It's difficult to think of a meaningful example for class destructors.
-In cases like these, change the placeholder:
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-#Example
-</pre>
-
-to:
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-#NoExample
-</pre>
-
-Descriptions start with an active verb. Descriptions are complete, punctuated
-sentences unless they describe parameters or return values. Parameters and
-returned values are described by phrases that start lower case and do not
-include trailing punctuation.
-
-Descriptions are not self-referential; they do not include the thing they
-describe. Descriptions may contain upper case or camel case references to
-definitions but otherwise should be free of jargon.
-
-Descriptions may contain code and formulas, each bracketed by markup.
-
-Similar items may be grouped into topics. Topics may include subtopics.
-
-Each <a href='undocumented#Document'>document</a> begins with one or more indices that include the contents of
-that file. A class reference includes an index listing contained topics,
-a separate listing for constructors, one for methods, and so on.
-
-Class methods contain a description, any parameters, any return value,
-an example, and any cross references.
-
-Each method must contain either one or more examples or markup indicating
-that there is no example.
-
-After editing is complete, searching for "incomplete" should fail,
-assuming "incomplete" is not the perfect word to use in a description or
-example!
-
-<a name='Adding_Documentation'></a>
-
-Generate fiddle.json from all examples, including the ones you just wrote.
-Error checking is syntatic: starting keywords are closed, keywords have the
-correct parents.
-If you run <a href='usingBookmaker#Bookmaker'>Bookmaker</a> inside Visual_Studio, you can click on errors and it
-will take you to the source <a href='undocumented#Line'>line</a> in question.
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-$ ./out/dir/bookmaker -e fiddle.json -b docs
-</pre>
-
-Once complete, run fiddlecli to generate the example hashes.
-Errors are contained by the output but aren't reported yet.
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-$ $GOPATH/bin/fiddlecli --input fiddle.json --output fiddleout.json
-</pre>
-
-Generate SkXXX.md from SkXXX.bmh and fiddleout.json.
-Error checking includes: undefined references, fiddle compiler errors,
-missing or mismatched printf output.
-Again, you can click on any errors inside Visual_Studio.
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-$ ./out/dir/bookmaker -r site/user/api -b docs -f fiddleout.json
-</pre>
-
-The original include may have changed since you started creating the markdown.
-Check to see if it is up to date.
-This reports if a method no longer exists or its parameters have changed.
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-$ ./out/dir/bookmaker -x -b docs/SkXXX.bmh -i include/core/SkXXX.h
-</pre>
-
-Generate an updated include header. Run:
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-$ ./out/dir/bookmaker -p -b docs -i include/core/SkXXX.h
-</pre>
-
-to write the updated SkXXX.h to the current directory.
-
-Once adding the file is complete, add the file to status.json in the
-Completed section. You may add it to the InProgress section during
-development, or leave status.json unchanged.
-
-If the new file has been added to status.json, you can run
-any of the above commands with -a docs/status.json in place of
--b docs or -i includes.
-
-<a name='Bugs'></a>
-
-<a href='usingBookmaker#Bookmaker'>Bookmaker</a> bugs are tracked
-<a href='https://bug.skia.org/6898'>here</a></a> .
-
diff --git a/site/user/modules/canvaskit.md b/site/user/modules/canvaskit.md
index c079729..d8398a4 100644
--- a/site/user/modules/canvaskit.md
+++ b/site/user/modules/canvaskit.md
@@ -51,11 +51,11 @@
 </style>
 
 <div id=demo>
-  <h3>An Interactive Path</h3>
+  <h3>Go beyond the HTML Canvas2D</h3>
   <figure>
     <canvas id=patheffect width=400 height=400></canvas>
     <figcaption>
-      <a href="https://jsfiddle.skia.org/canvaskit/28004d8841e7e497013263598241a3c1edc21dc1cf87a679abba307f39fa5fe6"
+      <a href="https://jsfiddle.skia.org/canvaskit/ea89749ae8c90bce807ea2e7e34fb7b09b950cee70d9db0a9cdfd2d67bd48ef0"
           target=_blank rel=noopener>
         Star JSFiddle</a>
     </figcaption>
@@ -87,6 +87,16 @@
     <canvas id=sk_onboarding width=500 height=500></canvas>
   </a>
 
+  <h3>Text Shaping using ICU and Harfbuzz</h3>
+  <figure>
+    <canvas id=shaping width=400 height=400></canvas>
+    <figcaption>
+      <a href="https://jsfiddle.skia.org/canvaskit/d5c61a106d57ff4ada119a46ddc3bfa479d343330d0c9e50da21f4ef113d0dee"
+          target=_blank rel=noopener>
+        Text Shaping JSFiddle</a>
+    </figcaption>
+  </figure>
+
 </div>
 
 <script type="text/javascript" charset="utf-8">
@@ -96,7 +106,7 @@
   var locate_file = '';
   if (window.WebAssembly && typeof window.WebAssembly.compile === 'function') {
     console.log('WebAssembly is supported!');
-    locate_file = 'https://unpkg.com/canvaskit-wasm@0.3.0/bin/';
+    locate_file = 'https://unpkg.com/canvaskit-wasm@0.4.0/bin/';
   } else {
     console.log('WebAssembly is not supported (yet) on this browser.');
     document.getElementById('demo').innerHTML = "<div>WASM not supported by your browser. Try a recent version of Chrome, Firefox, Edge, or Safari.</div>";
@@ -112,10 +122,11 @@
   var fullBounds = {fLeft: 0, fTop: 0, fRight: 500, fBottom: 500};
   CanvasKitInit({
     locateFile: (file) => locate_file + file,
-  }).then((CK) => {
+  }).ready().then((CK) => {
     CanvasKit = CK;
     DrawingExample(CanvasKit);
     InkExample(CanvasKit);
+    ShapingExample(CanvasKit);
      // Set bounds to fix the 4:3 resolution of the legos
     SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
     // Re-size to fit
@@ -173,9 +184,10 @@
 
     const textPaint = new CanvasKit.SkPaint();
     textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
-    textPaint.setTextSize(30);
     textPaint.setAntiAlias(true);
 
+    const textFont = new CanvasKit.SkFont(null, 30);
+
     let i = 0;
 
     let X = 200;
@@ -196,7 +208,7 @@
       canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
 
       canvas.drawPath(path, paint);
-      canvas.drawText('Try Clicking!', 10, 380, textPaint);
+      canvas.drawText('Try Clicking!', 10, 380, textPaint, textFont);
       canvas.flush();
       dpe.delete();
       path.delete();
@@ -216,8 +228,9 @@
     document.getElementById('patheffect').addEventListener('pointerdown', interact);
     preventScrolling(document.getElementById('patheffect'));
 
-    // A client would need to delete this if it didn't go on for ever.
-    //paint.delete();
+    // A client would need to delete this if it didn't go on forever.
+    // font.delete();
+    // paint.delete();
   }
 
   function InkExample(CanvasKit) {
@@ -294,6 +307,66 @@
     window.requestAnimationFrame(drawFrame);
   }
 
+  function ShapingExample(CanvasKit) {
+    const surface = CanvasKit.MakeCanvasSurface('shaping');
+    if (!surface) {
+      console.log('Could not make surface');
+      return;
+    }
+    const context = CanvasKit.currentContext();
+    const skcanvas = surface.getCanvas();
+    const paint = new CanvasKit.SkPaint();
+    paint.setColor(CanvasKit.BLUE);
+    paint.setStyle(CanvasKit.PaintStyle.Stroke);
+
+    const textPaint = new CanvasKit.SkPaint();
+    const bigFont = new CanvasKit.SkFont(null, 30);
+    const smallFont = new CanvasKit.SkFont(null, 14);
+
+    const TEXT = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris ac leo vitae ipsum hendrerit euismod quis rutrum nibh. Quisque non suscipit urna. Donec enim urna, facilisis vitae volutpat in, mattis at elit. Sed quis augue et dolor dignissim fringilla. Sed non massa eu neque tristique malesuada. ';
+
+    let X = 280;
+    let Y = 190;
+
+    function drawFrame() {
+      CanvasKit.setCurrentContext(context);
+      skcanvas.clear(CanvasKit.TRANSPARENT);
+
+      const shapedText = new CanvasKit.ShapedText({
+        font: smallFont,
+        leftToRight: true,
+        text: TEXT,
+        width: X - 10,
+      });
+
+      skcanvas.drawRect(CanvasKit.LTRBRect(10, 10, X, Y), paint);
+      skcanvas.drawText(shapedText, 10, 10, textPaint, smallFont);
+      skcanvas.drawText('Try dragging the box!', 10, 380, textPaint, bigFont);
+
+      surface.flush();
+
+      shapedText.delete();
+
+      window.requestAnimationFrame(drawFrame);
+    }
+    window.requestAnimationFrame(drawFrame);
+
+    // Make animation interactive
+    let interact = (e) => {
+      if (!e.pressure) {
+        return;
+      }
+      X = e.offsetX;
+      Y = e.offsetY;
+    };
+    document.getElementById('shaping').addEventListener('pointermove', interact);
+    document.getElementById('shaping').addEventListener('pointerdown', interact);
+    document.getElementById('shaping').addEventListener('lostpointercapture', interact);
+    document.getElementById('shaping').addEventListener('pointerup', interact);
+    preventScrolling(document.getElementById('shaping'));
+    window.requestAnimationFrame(drawFrame);
+  }
+
   function starPath(CanvasKit, X=128, Y=128, R=116) {
     let p = new CanvasKit.SkPath();
     p.moveTo(X + R, Y);
diff --git a/site/user/modules/pathkit.md b/site/user/modules/pathkit.md
index b081176..5e597b9 100644
--- a/site/user/modules/pathkit.md
+++ b/site/user/modules/pathkit.md
@@ -40,22 +40,21 @@
   let s = document.createElement('script');
   if (window.WebAssembly && typeof window.WebAssembly.compile === 'function') {
     console.log('WebAssembly is supported! Using the wasm version of PathKit');
-    window.__pathkit_locate_file = 'https://unpkg.com/pathkit-wasm@0.5.0/bin/';
+    window.__pathkit_locate_file = 'https://unpkg.com/pathkit-wasm@0.6.0/bin/';
   } else {
     console.log('WebAssembly is not supported (yet) on this browser. Using the asmjs version of PathKit');
-    window.__pathkit_locate_file = 'https://unpkg.com/pathkit-asmjs@0.5.0/bin/';
+    window.__pathkit_locate_file = 'https://unpkg.com/pathkit-asmjs@0.6.0/bin/';
   }
   s.src = window.__pathkit_locate_file+'pathkit.js';
   s.onload = () => {
     try {
       PathKitInit({
         locateFile: (file) => window.__pathkit_locate_file+file,
-      }).then((PathKit) => {
+      }).ready().then((PathKit) => {
         // Code goes here using PathKit
         PathEffectsExample(PathKit);
         MatrixTransformExample(PathKit);
       });
-
     }
     catch(error) {
       console.warn(error, 'falling back to image');
diff --git a/src/android/SkAndroidFrameworkUtils.cpp b/src/android/SkAndroidFrameworkUtils.cpp
index d0f4499..a13e8a2 100644
--- a/src/android/SkAndroidFrameworkUtils.cpp
+++ b/src/android/SkAndroidFrameworkUtils.cpp
@@ -8,6 +8,7 @@
 #include "SkAndroidFrameworkUtils.h"
 #include "SkCanvas.h"
 #include "SkDevice.h"
+#include "SkPaintFilterCanvas.h"
 #include "SkSurface_Base.h"
 
 #if SK_SUPPORT_GPU
@@ -67,5 +68,15 @@
 int SkAndroidFrameworkUtils::SaveBehind(SkCanvas* canvas, const SkRect* subset) {
     return canvas->only_axis_aligned_saveBehind(subset);
 }
+
+SkCanvas* SkAndroidFrameworkUtils::getBaseWrappedCanvas(SkCanvas* canvas) {
+    auto pfc = canvas->internal_private_asPaintFilterCanvas();
+    auto result = canvas;
+    while (pfc) {
+        result = pfc->proxy();
+        pfc = result->internal_private_asPaintFilterCanvas();
+    }
+    return result;
+}
 #endif // SK_BUILD_FOR_ANDROID_FRAMEWORK
 
diff --git a/src/android/SkAnimatedImage.cpp b/src/android/SkAnimatedImage.cpp
index d03040c..c2104c8 100644
--- a/src/android/SkAnimatedImage.cpp
+++ b/src/android/SkAnimatedImage.cpp
@@ -22,14 +22,24 @@
     if (!codec) {
         return nullptr;
     }
+    auto info = codec->getInfo().makeWH(scaledSize.width(), scaledSize.height());
+    return Make(std::move(codec), info, cropRect, std::move(postProcess));
+}
 
-    SkISize decodeSize = scaledSize;
-    auto decodeInfo = codec->getInfo();
-    if (codec->getEncodedFormat() == SkEncodedImageFormat::kWEBP
-            && scaledSize.width()  < decodeInfo.width()
-            && scaledSize.height() < decodeInfo.height()) {
-        // libwebp can decode to arbitrary smaller sizes.
-        decodeInfo = decodeInfo.makeWH(decodeSize.width(), decodeSize.height());
+sk_sp<SkAnimatedImage> SkAnimatedImage::Make(std::unique_ptr<SkAndroidCodec> codec,
+        const SkImageInfo& requestedInfo, SkIRect cropRect, sk_sp<SkPicture> postProcess) {
+    if (!codec) {
+        return nullptr;
+    }
+
+    auto scaledSize = requestedInfo.dimensions();
+    auto decodeInfo = requestedInfo;
+    if (codec->getEncodedFormat() != SkEncodedImageFormat::kWEBP
+            || scaledSize.width()  >= decodeInfo.width()
+            || scaledSize.height() >= decodeInfo.height()) {
+        // Only libwebp can decode to arbitrary smaller sizes.
+        auto dims = codec->getInfo().dimensions();
+        decodeInfo = decodeInfo.makeWH(dims.width(), dims.height());
     }
 
     auto image = sk_sp<SkAnimatedImage>(new SkAnimatedImage(std::move(codec), scaledSize,
diff --git a/src/atlastext/SkAtlasTextTarget.cpp b/src/atlastext/SkAtlasTextTarget.cpp
index 68dc6fa..2a84d40 100644
--- a/src/atlastext/SkAtlasTextTarget.cpp
+++ b/src/atlastext/SkAtlasTextTarget.cpp
@@ -154,7 +154,7 @@
     fColor = color;
 
     SkSurfaceProps props(SkSurfaceProps::kUseDistanceFieldFonts_Flag, kUnknown_SkPixelGeometry);
-    auto* grContext = this->context()->internal().grContext();
+    auto grContext = this->context()->internal().grContext();
     auto atlasTextContext = grContext->priv().drawingManager()->getTextContext();
     SkGlyphRunBuilder builder;
     builder.drawGlyphsWithPositions(paint, font.makeFont(),
@@ -212,14 +212,16 @@
     for (int i = 0; i < fGeoCount; ++i) {
         fGeoData[i].fColor = color4f;
     }
-    this->finalize(caps, nullptr /* applied clip */);
+    // Atlas text doesn't use MSAA, so no need to handle a GrFSAAType.
+    // Also, no need to support normalized F16 with manual clamp?
+    this->finalize(caps, nullptr /* applied clip */, GrFSAAType::kNone, GrClampType::kAuto);
 }
 
 void GrAtlasTextOp::executeForTextTarget(SkAtlasTextTarget* target) {
     FlushInfo flushInfo;
     SkExclusiveStrikePtr autoGlyphCache;
     auto& context = target->context()->internal();
-    auto glyphCache = context.grContext()->priv().getGlyphCache();
+    auto glyphCache = context.grContext()->priv().getGrStrikeCache();
     auto atlasManager = context.grContext()->priv().getAtlasManager();
     auto resourceProvider = context.grContext()->priv().resourceProvider();
 
diff --git a/src/atlastext/SkInternalAtlasTextContext.cpp b/src/atlastext/SkInternalAtlasTextContext.cpp
index ba2924e..9be17a7 100644
--- a/src/atlastext/SkInternalAtlasTextContext.cpp
+++ b/src/atlastext/SkInternalAtlasTextContext.cpp
@@ -50,7 +50,7 @@
 }
 
 GrStrikeCache* SkInternalAtlasTextContext::glyphCache() {
-    return fGrContext->priv().getGlyphCache();
+    return fGrContext->priv().getGrStrikeCache();
 }
 
 GrTextBlobCache* SkInternalAtlasTextContext::textBlobCache() {
diff --git a/src/c/sk_effects.cpp b/src/c/sk_effects.cpp
index 860709a..b18f80f 100644
--- a/src/c/sk_effects.cpp
+++ b/src/c/sk_effects.cpp
@@ -19,14 +19,14 @@
 
 const struct {
     sk_shader_tilemode_t    fC;
-    SkShader::TileMode      fSK;
+    SkTileMode              fSK;
 } gTileModeMap[] = {
-    { CLAMP_SK_SHADER_TILEMODE,     SkShader::kClamp_TileMode },
-    { REPEAT_SK_SHADER_TILEMODE,    SkShader::kRepeat_TileMode },
-    { MIRROR_SK_SHADER_TILEMODE,    SkShader::kMirror_TileMode  },
+    { CLAMP_SK_SHADER_TILEMODE,     SkTileMode::kClamp },
+    { REPEAT_SK_SHADER_TILEMODE,    SkTileMode::kRepeat },
+    { MIRROR_SK_SHADER_TILEMODE,    SkTileMode::kMirror  },
 };
 
-static bool from_c_tilemode(sk_shader_tilemode_t cMode, SkShader::TileMode* skMode) {
+static bool from_c_tilemode(sk_shader_tilemode_t cMode, SkTileMode* skMode) {
     for (size_t i = 0; i < SK_ARRAY_COUNT(gTileModeMap); ++i) {
         if (cMode == gTileModeMap[i].fC) {
             if (skMode) {
@@ -52,7 +52,7 @@
                                            int colorCount,
                                            sk_shader_tilemode_t cmode,
                                            const sk_matrix_t* cmatrix) {
-    SkShader::TileMode mode;
+    SkTileMode mode;
     if (!from_c_tilemode(cmode, &mode)) {
         return nullptr;
     }
@@ -79,7 +79,7 @@
                                            int colorCount,
                                            sk_shader_tilemode_t cmode,
                                            const sk_matrix_t* cmatrix) {
-    SkShader::TileMode mode;
+    SkTileMode mode;
     if (!from_c_tilemode(cmode, &mode)) {
         return nullptr;
     }
@@ -123,7 +123,7 @@
                                                       int colorCount,
                                                       sk_shader_tilemode_t cmode,
                                                       const sk_matrix_t* cmatrix) {
-    SkShader::TileMode mode;
+    SkTileMode mode;
     if (!from_c_tilemode(cmode, &mode)) {
         return nullptr;
     }
diff --git a/src/codec/SkAndroidCodec.cpp b/src/codec/SkAndroidCodec.cpp
index d912231..443d899 100644
--- a/src/codec/SkAndroidCodec.cpp
+++ b/src/codec/SkAndroidCodec.cpp
@@ -91,19 +91,23 @@
         case SkEncodedImageFormat::kPNG:
         case SkEncodedImageFormat::kICO:
         case SkEncodedImageFormat::kJPEG:
+#ifndef SK_HAS_WUFFS_LIBRARY
         case SkEncodedImageFormat::kGIF:
+#endif
         case SkEncodedImageFormat::kBMP:
         case SkEncodedImageFormat::kWBMP:
         case SkEncodedImageFormat::kHEIF:
             return skstd::make_unique<SkSampledCodec>(codec.release(), orientationBehavior);
-
+#ifdef SK_HAS_WUFFS_LIBRARY
+        case SkEncodedImageFormat::kGIF:
+#endif
 #ifdef SK_HAS_WEBP_LIBRARY
         case SkEncodedImageFormat::kWEBP:
 #endif
 #ifdef SK_CODEC_DECODES_RAW
         case SkEncodedImageFormat::kDNG:
 #endif
-#if defined(SK_HAS_WEBP_LIBRARY) || defined(SK_CODEC_DECODES_RAW)
+#if defined(SK_HAS_WEBP_LIBRARY) || defined(SK_CODEC_DECODES_RAW) || defined(SK_HAS_WUFFS_LIBRARY)
             return skstd::make_unique<SkAndroidCodecAdapter>(codec.release(), orientationBehavior);
 #endif
 
@@ -162,6 +166,8 @@
 sk_sp<SkColorSpace> SkAndroidCodec::computeOutputColorSpace(SkColorType outputColorType,
                                                             sk_sp<SkColorSpace> prefColorSpace) {
     switch (outputColorType) {
+        case kRGBA_F16_SkColorType:
+        case kRGB_565_SkColorType:
         case kRGBA_8888_SkColorType:
         case kBGRA_8888_SkColorType: {
             // If |prefColorSpace| is supplied, choose it.
@@ -184,12 +190,6 @@
 
             return SkColorSpace::MakeSRGB();
         }
-        case kRGBA_F16_SkColorType:
-            // Note that |prefColorSpace| is ignored, F16 is always linear sRGB.
-            return SkColorSpace::MakeSRGBLinear();
-        case kRGB_565_SkColorType:
-            // Note that |prefColorSpace| is ignored, 565 is always sRGB.
-            return SkColorSpace::MakeSRGB();
         default:
             // Color correction not supported for kGray.
             return nullptr;
diff --git a/src/codec/SkBmpCodec.cpp b/src/codec/SkBmpCodec.cpp
index e13dccb..b09d39e 100644
--- a/src/codec/SkBmpCodec.cpp
+++ b/src/codec/SkBmpCodec.cpp
@@ -520,7 +520,8 @@
 
             if (codecOut) {
                 // Check that input bit masks are valid and create the masks object
-                std::unique_ptr<SkMasks> masks(SkMasks::CreateMasks(inputMasks, bitsPerPixel));
+                SkASSERT(bitsPerPixel % 8 == 0);
+                std::unique_ptr<SkMasks> masks(SkMasks::CreateMasks(inputMasks, bitsPerPixel/8));
                 if (nullptr == masks) {
                     SkCodecPrintf("Error: invalid input masks.\n");
                     return kInvalidInput;
diff --git a/src/codec/SkCodec.cpp b/src/codec/SkCodec.cpp
index 449812d..1eecc1a 100644
--- a/src/codec/SkCodec.cpp
+++ b/src/codec/SkCodec.cpp
@@ -732,6 +732,48 @@
     return frame.getDisposalMethod() == SkCodecAnimation::DisposalMethod::kRestoreBGColor;
 }
 
+// As its name suggests, this method computes a frame's alpha (e.g. completely
+// opaque, unpremul, binary) and its required frame (a preceding frame that
+// this frame depends on, to draw the complete image at this frame's point in
+// the animation stream), and calls this frame's setter methods with that
+// computed information.
+//
+// A required frame of kNoFrame means that this frame is independent: drawing
+// the complete image at this frame's point in the animation stream does not
+// require first preparing the pixel buffer based on another frame. Instead,
+// drawing can start from an uninitialized pixel buffer.
+//
+// "Uninitialized" is from the SkCodec's caller's point of view. In the SkCodec
+// implementation, for independent frames, first party Skia code (in src/codec)
+// will typically fill the buffer with a uniform background color (e.g.
+// transparent black) before calling into third party codec-specific code (e.g.
+// libjpeg or libpng). Pixels outside of the frame's rect will remain this
+// background color after drawing this frame. For incomplete decodes, pixels
+// inside that rect may be (at least temporarily) set to that background color.
+// In an incremental decode, later passes may then overwrite that background
+// color.
+//
+// Determining kNoFrame or otherwise involves testing a number of conditions
+// sequentially. The first satisfied condition results in setting the required
+// frame to kNoFrame (an "INDx" condition) or to a non-negative frame number (a
+// "DEPx" condition), and the function returning early. Those "INDx" and "DEPx"
+// labels also map to comments in the function body.
+//
+//  - IND1: this frame is the first frame.
+//  - IND2: this frame fills out the whole image, and it is completely opaque
+//          or it overwrites (not blends with) the previous frame.
+//  - IND3: all preceding frames' disposals are kRestorePrevious.
+//  - IND4: the prevFrame's disposal is kRestoreBGColor, and it fills out the
+//          whole image or it is itself otherwise independent.
+//  - DEP5: this frame reports alpha (it is not completely opaque) and it
+//          blends with (not overwrites) the previous frame.
+//  - IND6: this frame's rect covers the rects of all preceding frames back to
+//          and including the most recent independent frame before this frame.
+//  - DEP7: unconditional.
+//
+// The "prevFrame" variable initially points to the previous frame (also known
+// as the prior frame), but that variable may iterate further backwards over
+// the course of this computation.
 void SkFrameHolder::setAlphaAndRequiredFrame(SkFrame* frame) {
     const bool reportsAlpha = frame->reportedAlpha() != SkEncodedInfo::kOpaque_Alpha;
     const auto screenRect = SkIRect::MakeWH(fScreenWidth, fScreenHeight);
@@ -740,7 +782,7 @@
     const int i = frame->frameId();
     if (0 == i) {
         frame->setHasAlpha(reportsAlpha || frameRect != screenRect);
-        frame->setRequiredFrame(SkCodec::kNoFrame);
+        frame->setRequiredFrame(SkCodec::kNoFrame);  // IND1
         return;
     }
 
@@ -748,7 +790,7 @@
     const bool blendWithPrevFrame = frame->getBlend() == SkCodecAnimation::Blend::kPriorFrame;
     if ((!reportsAlpha || !blendWithPrevFrame) && frameRect == screenRect) {
         frame->setHasAlpha(reportsAlpha);
-        frame->setRequiredFrame(SkCodec::kNoFrame);
+        frame->setRequiredFrame(SkCodec::kNoFrame);  // IND2
         return;
     }
 
@@ -757,7 +799,7 @@
         const int prevId = prevFrame->frameId();
         if (0 == prevId) {
             frame->setHasAlpha(true);
-            frame->setRequiredFrame(SkCodec::kNoFrame);
+            frame->setRequiredFrame(SkCodec::kNoFrame);  // IND3
             return;
         }
 
@@ -770,7 +812,7 @@
     if (clearPrevFrame) {
         if (prevFrameRect == screenRect || independent(*prevFrame)) {
             frame->setHasAlpha(true);
-            frame->setRequiredFrame(SkCodec::kNoFrame);
+            frame->setRequiredFrame(SkCodec::kNoFrame);  // IND4
             return;
         }
     }
@@ -780,7 +822,7 @@
         // to background color and covers its required frame (and that
         // frame is independent), prevFrame could be marked independent.
         // Would this extra complexity be worth it?
-        frame->setRequiredFrame(prevFrame->frameId());
+        frame->setRequiredFrame(prevFrame->frameId());  // DEP5
         frame->setHasAlpha(prevFrame->hasAlpha() || clearPrevFrame);
         return;
     }
@@ -788,7 +830,7 @@
     while (frameRect.contains(prevFrameRect)) {
         const int prevRequiredFrame = prevFrame->getRequiredFrame();
         if (prevRequiredFrame == SkCodec::kNoFrame) {
-            frame->setRequiredFrame(SkCodec::kNoFrame);
+            frame->setRequiredFrame(SkCodec::kNoFrame);  // IND6
             frame->setHasAlpha(true);
             return;
         }
@@ -797,21 +839,12 @@
         prevFrameRect = frame_rect_on_screen(prevFrame->frameRect(), screenRect);
     }
 
+    frame->setRequiredFrame(prevFrame->frameId());  // DEP7
     if (restore_bg(*prevFrame)) {
         frame->setHasAlpha(true);
-        if (prevFrameRect == screenRect || independent(*prevFrame)) {
-            frame->setRequiredFrame(SkCodec::kNoFrame);
-        } else {
-            // Note: As above, frame could still be independent, e.g. if
-            // prevFrame covers its required frame and that frame is
-            // independent.
-            frame->setRequiredFrame(prevFrame->frameId());
-        }
         return;
     }
-
     SkASSERT(prevFrame->getDisposalMethod() == SkCodecAnimation::DisposalMethod::kKeep);
-    frame->setRequiredFrame(prevFrame->frameId());
     frame->setHasAlpha(prevFrame->hasAlpha() || (reportsAlpha && !blendWithPrevFrame));
 }
 
diff --git a/src/codec/SkMasks.cpp b/src/codec/SkMasks.cpp
index 79892ed..2063ffa 100644
--- a/src/codec/SkMasks.cpp
+++ b/src/codec/SkMasks.cpp
@@ -86,7 +86,7 @@
  * Process an input mask to obtain the necessary information
  *
  */
-const SkMasks::MaskInfo process_mask(uint32_t mask, uint32_t bpp) {
+static const SkMasks::MaskInfo process_mask(uint32_t mask) {
     // Determine properties of the mask
     uint32_t tempMask = mask;
     uint32_t shift = 0;
@@ -116,9 +116,7 @@
         }
     }
 
-    // Save the calculated values
-    const SkMasks::MaskInfo info = { mask, shift, size };
-    return info;
+    return { mask, shift, size };
 }
 
 /*
@@ -126,29 +124,32 @@
  * Create the masks object
  *
  */
-SkMasks* SkMasks::CreateMasks(InputMasks masks, uint32_t bitsPerPixel) {
-    // Trim the input masks according to bitsPerPixel
-    if (bitsPerPixel < 32) {
-        masks.red &= (1 << bitsPerPixel) - 1;
+SkMasks* SkMasks::CreateMasks(InputMasks masks, int bytesPerPixel) {
+    SkASSERT(0 < bytesPerPixel && bytesPerPixel <= 4);
+
+    // Trim the input masks to match bytesPerPixel.
+    if (bytesPerPixel < 4) {
+        int bitsPerPixel = 8*bytesPerPixel;
+        masks.red   &= (1 << bitsPerPixel) - 1;
         masks.green &= (1 << bitsPerPixel) - 1;
-        masks.blue &= (1 << bitsPerPixel) - 1;
+        masks.blue  &= (1 << bitsPerPixel) - 1;
         masks.alpha &= (1 << bitsPerPixel) - 1;
     }
 
-    // Check that masks do not overlap
-    if (((masks.red & masks.green) | (masks.red & masks.blue) |
-            (masks.red & masks.alpha) | (masks.green & masks.blue) |
-            (masks.green & masks.alpha) | (masks.blue & masks.alpha)) != 0) {
+    // Check that masks do not overlap.
+    if (((masks.red   & masks.green) |
+         (masks.red   & masks.blue ) |
+         (masks.red   & masks.alpha) |
+         (masks.green & masks.blue ) |
+         (masks.green & masks.alpha) |
+         (masks.blue  & masks.alpha) ) != 0) {
         return nullptr;
     }
 
-    // Collect information about the masks
-    const MaskInfo red = process_mask(masks.red, bitsPerPixel);
-    const MaskInfo green = process_mask(masks.green, bitsPerPixel);
-    const MaskInfo blue = process_mask(masks.blue, bitsPerPixel);
-    const MaskInfo alpha = process_mask(masks.alpha, bitsPerPixel);
-
-    return new SkMasks(red, green, blue, alpha);
+    return new SkMasks(process_mask(masks.red  ),
+                       process_mask(masks.green),
+                       process_mask(masks.blue ),
+                       process_mask(masks.alpha));
 }
 
 
diff --git a/src/codec/SkMasks.h b/src/codec/SkMasks.h
index 9606cbf..55abb11 100644
--- a/src/codec/SkMasks.h
+++ b/src/codec/SkMasks.h
@@ -45,7 +45,7 @@
      * Create the masks object
      *
      */
-    static SkMasks* CreateMasks(InputMasks masks, uint32_t bpp);
+    static SkMasks* CreateMasks(InputMasks masks, int bytesPerPixel);
 
     /*
      *
diff --git a/src/codec/SkScalingCodec.h b/src/codec/SkScalingCodec.h
new file mode 100644
index 0000000..46aed71
--- /dev/null
+++ b/src/codec/SkScalingCodec.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCodec.h"
+
+// Helper class for an SkCodec that supports arbitrary downscaling.
+class SkScalingCodec : public SkCodec {
+protected:
+    SkScalingCodec(SkEncodedInfo&& info, XformFormat srcFormat, std::unique_ptr<SkStream> stream,
+                    SkEncodedOrigin origin = kTopLeft_SkEncodedOrigin)
+        : INHERITED(std::move(info), srcFormat, std::move(stream), origin) {}
+
+    SkISize onGetScaledDimensions(float desiredScale) const override {
+        SkISize dim = this->dimensions();
+        // SkCodec treats zero dimensional images as errors, so the minimum size
+        // that we will recommend is 1x1.
+        dim.fWidth = SkTMax(1, SkScalarRoundToInt(desiredScale * dim.fWidth));
+        dim.fHeight = SkTMax(1, SkScalarRoundToInt(desiredScale * dim.fHeight));
+        return dim;
+    }
+
+    bool onDimensionsSupported(const SkISize& requested) override {
+        SkISize dim = this->dimensions();
+        int w = requested.width();
+        int h = requested.height();
+        return 1 <= w && w <= dim.width() && 1 <= h && h <= dim.height();
+    }
+
+private:
+    typedef SkCodec INHERITED;
+};
+
diff --git a/src/codec/SkWebpCodec.cpp b/src/codec/SkWebpCodec.cpp
index c695e0a..1b61fe2 100644
--- a/src/codec/SkWebpCodec.cpp
+++ b/src/codec/SkWebpCodec.cpp
@@ -176,21 +176,6 @@
                                                     demux.release(), std::move(data), origin));
 }
 
-SkISize SkWebpCodec::onGetScaledDimensions(float desiredScale) const {
-    SkISize dim = this->dimensions();
-    // SkCodec treats zero dimensional images as errors, so the minimum size
-    // that we will recommend is 1x1.
-    dim.fWidth = SkTMax(1, SkScalarRoundToInt(desiredScale * dim.fWidth));
-    dim.fHeight = SkTMax(1, SkScalarRoundToInt(desiredScale * dim.fHeight));
-    return dim;
-}
-
-bool SkWebpCodec::onDimensionsSupported(const SkISize& dim) {
-    const SkEncodedInfo& info = this->getEncodedInfo();
-    return dim.width() >= 1 && dim.width() <= info.width()
-            && dim.height() >= 1 && dim.height() <= info.height();
-}
-
 static WEBP_CSP_MODE webp_decode_mode(SkColorType dstCT, bool premultiply) {
     switch (dstCT) {
         case kBGRA_8888_SkColorType:
diff --git a/src/codec/SkWebpCodec.h b/src/codec/SkWebpCodec.h
index b4b4677..568658f 100644
--- a/src/codec/SkWebpCodec.h
+++ b/src/codec/SkWebpCodec.h
@@ -12,6 +12,7 @@
 #include "SkEncodedImageFormat.h"
 #include "SkFrameHolder.h"
 #include "SkImageInfo.h"
+#include "SkScalingCodec.h"
 #include "SkTypes.h"
 
 #include <vector>
@@ -22,7 +23,7 @@
     void WebPDemuxDelete(WebPDemuxer* dmux);
 }
 
-class SkWebpCodec final : public SkCodec {
+class SkWebpCodec final : public SkScalingCodec {
 public:
     // Assumes IsWebp was called and returned true.
     static std::unique_ptr<SkCodec> MakeFromStream(std::unique_ptr<SkStream>, Result*);
@@ -31,10 +32,6 @@
     Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, int*) override;
     SkEncodedImageFormat onGetEncodedFormat() const override { return SkEncodedImageFormat::kWEBP; }
 
-    SkISize onGetScaledDimensions(float desiredScale) const override;
-
-    bool onDimensionsSupported(const SkISize&) override;
-
     bool onGetValidSubset(SkIRect* /* desiredSubset */) const override;
 
     int onGetFrameCount() override;
@@ -102,6 +99,6 @@
     // succeed.
     bool        fFailed;
 
-    typedef SkCodec INHERITED;
+    typedef SkScalingCodec INHERITED;
 };
 #endif // SkWebpCodec_DEFINED
diff --git a/src/codec/SkWuffsCodec.cpp b/src/codec/SkWuffsCodec.cpp
index 74e6b14..49357e9 100644
--- a/src/codec/SkWuffsCodec.cpp
+++ b/src/codec/SkWuffsCodec.cpp
@@ -8,9 +8,14 @@
 #include "SkWuffsCodec.h"
 
 #include "../private/SkMalloc.h"
+#include "SkBitmap.h"
+#include "SkDraw.h"
 #include "SkFrameHolder.h"
+#include "SkMatrix.h"
+#include "SkPaint.h"
+#include "SkRasterClip.h"
 #include "SkSampler.h"
-#include "SkSwizzler.h"
+#include "SkScalingCodec.h"
 #include "SkUtils.h"
 
 // Wuffs ships as a "single file C library" or "header file library" as per
@@ -18,8 +23,11 @@
 //
 // As we have not #define'd WUFFS_IMPLEMENTATION, the #include here is
 // including a header file, even though that file name ends in ".c".
+#if defined(WUFFS_IMPLEMENTATION)
+#error "SkWuffsCodec should not #define WUFFS_IMPLEMENTATION"
+#endif
 #include "wuffs-v0.2.c"
-#if WUFFS_VERSION_BUILD_METADATA_COMMIT_COUNT < 1556
+#if WUFFS_VERSION_BUILD_METADATA_COMMIT_COUNT < 1675
 #error "Wuffs version is too old. Upgrade to the latest version."
 #endif
 
@@ -113,48 +121,7 @@
     typedef SkFrameHolder INHERITED;
 };
 
-// SkWuffsSpySampler is a placeholder SkSampler implementation. The Skia API
-// expects to manipulate the codec's sampler (i.e. call setSampleX and
-// setSampleY) in between the startIncrementalDecode (SID) and
-// incrementalDecode (ID) calls. But creating the SkSwizzler (the real sampler)
-// requires knowing the destination buffer's dimensions, i.e. the animation
-// frame's width and height. That width and height are decoded in ID, not SID.
-//
-// To break that circle, the SkWuffsSpySampler always exists, so its methods
-// can be called between SID and ID. It doesn't actually do any sampling, it
-// merely records the arguments given to setSampleX (explicitly) and setSampleY
-// (implicitly, via the superclass' implementation). Inside ID, those recorded
-// arguments are forwarded on to the SkSwizzler (the real sampler) when that
-// SkSwizzler is created, after the frame width and height are known.
-//
-// Roughly speaking, the SkWuffsSpySampler is an eager proxy for the lazily
-// constructed real sampler. But that laziness is out of necessity.
-//
-// The "Spy" name is because it records its arguments. See
-// https://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs
-class SkWuffsSpySampler final : public SkSampler {
-public:
-    SkWuffsSpySampler(int imageWidth)
-        : INHERITED(), fFillWidth(0), fImageWidth(imageWidth), fSampleX(1) {}
-
-    void reset();
-    int  sampleX() const;
-
-    int fFillWidth;
-
-private:
-    // SkSampler overrides.
-    int fillWidth() const override;
-    int onSetSampleX(int sampleX) override;
-
-    const int fImageWidth;
-
-    int fSampleX;
-
-    typedef SkSampler INHERITED;
-};
-
-class SkWuffsCodec final : public SkCodec {
+class SkWuffsCodec final : public SkScalingCodec {
 public:
     SkWuffsCodec(SkEncodedInfo&&                                         encodedInfo,
                  std::unique_ptr<SkStream>                               stream,
@@ -181,8 +148,6 @@
     int                  onGetFrameCount() override;
     bool                 onGetFrameInfo(int, FrameInfo*) const override;
     int                  onGetRepetitionCount() override;
-    SkSampler*           getSampler(bool createIfNecessary) override;
-    bool                 conversionSupported(const SkImageInfo& dst, bool, bool) override;
 
     void   readFrames();
     Result seekFrame(int frameIndex);
@@ -192,7 +157,6 @@
     const char* decodeFrame();
     void        updateNumFullyReceivedFrames();
 
-    SkWuffsSpySampler                                       fSpySampler;
     SkWuffsFrameHolder                                      fFrameHolder;
     std::unique_ptr<SkStream>                               fStream;
     std::unique_ptr<wuffs_gif__decoder, decltype(&sk_free)> fDecoder;
@@ -208,10 +172,7 @@
     // Incremental decoding state.
     uint8_t* fIncrDecDst;
     size_t   fIncrDecRowBytes;
-
-    std::unique_ptr<SkSwizzler> fSwizzler;
-    SkPMColor                   fColorTable[256];
-    bool                        fColorTableFilled;
+    bool     fFirstCallToIncrementalDecode;
 
     uint64_t                  fNumFullyReceivedFrames;
     std::vector<SkWuffsFrame> fFrames;
@@ -231,7 +192,7 @@
 
     uint8_t fBuffer[SK_WUFFS_CODEC_BUFFER_SIZE];
 
-    typedef SkCodec INHERITED;
+    typedef SkScalingCodec INHERITED;
 };
 
 // -------------------------------- SkWuffsFrame implementation
@@ -248,13 +209,13 @@
 }
 
 SkCodec::FrameInfo SkWuffsFrame::frameInfo(bool fullyReceived) const {
-    return ((SkCodec::FrameInfo){
-        .fRequiredFrame = getRequiredFrame(),
-        .fDuration = getDuration(),
-        .fFullyReceived = fullyReceived,
-        .fAlphaType = hasAlpha() ? kUnpremul_SkAlphaType : kOpaque_SkAlphaType,
-        .fDisposalMethod = getDisposalMethod(),
-    });
+    SkCodec::FrameInfo ret;
+    ret.fRequiredFrame = getRequiredFrame();
+    ret.fDuration = getDuration();
+    ret.fFullyReceived = fullyReceived;
+    ret.fAlphaType = hasAlpha() ? kUnpremul_SkAlphaType : kOpaque_SkAlphaType;
+    ret.fDisposalMethod = getDisposalMethod();
+    return ret;
 }
 
 uint64_t SkWuffsFrame::ioPosition() const {
@@ -278,27 +239,6 @@
     return fCodec->frame(i);
 };
 
-// -------------------------------- SkWuffsSpySampler implementation
-
-void SkWuffsSpySampler::reset() {
-    fFillWidth = 0;
-    fSampleX = 1;
-    this->setSampleY(1);
-}
-
-int SkWuffsSpySampler::sampleX() const {
-    return fSampleX;
-}
-
-int SkWuffsSpySampler::fillWidth() const {
-    return fFillWidth;
-}
-
-int SkWuffsSpySampler::onSetSampleX(int sampleX) {
-    fSampleX = sampleX;
-    return get_scaled_dimension(fImageWidth, sampleX);
-}
-
 // -------------------------------- SkWuffsCodec implementation
 
 SkWuffsCodec::SkWuffsCodec(SkEncodedInfo&&                                         encodedInfo,
@@ -316,7 +256,6 @@
                 // manage the stream ourselves, as the default SkCodec behavior
                 // is too trigger-happy on rewinding the stream.
                 nullptr),
-      fSpySampler(imgcfg.pixcfg.width()),
       fFrameHolder(),
       fStream(std::move(stream)),
       fDecoder(std::move(dec)),
@@ -324,31 +263,24 @@
       fWorkbufPtr(std::move(workbuf_ptr)),
       fWorkbufLen(workbuf_len),
       fFirstFrameIOPosition(imgcfg.first_frame_io_position()),
-      fFrameConfig((wuffs_base__frame_config){}),
+      fFrameConfig(wuffs_base__null_frame_config()),
       fPixelBuffer(pixbuf),
-      fIOBuffer((wuffs_base__io_buffer){}),
+      fIOBuffer(wuffs_base__null_io_buffer()),
       fIncrDecDst(nullptr),
       fIncrDecRowBytes(0),
-      fSwizzler(nullptr),
-      fColorTableFilled(false),
+      fFirstCallToIncrementalDecode(false),
       fNumFullyReceivedFrames(0),
       fFramesComplete(false),
       fDecoderIsSuspended(false) {
     fFrameHolder.init(this, imgcfg.pixcfg.width(), imgcfg.pixcfg.height());
-    sk_memset32(fColorTable, 0, SK_ARRAY_COUNT(fColorTable));
 
     // Initialize fIOBuffer's fields, copying any outstanding data from iobuf to
     // fIOBuffer, as iobuf's backing array may not be valid for the lifetime of
     // this SkWuffsCodec object, but fIOBuffer's backing array (fBuffer) is.
     SkASSERT(iobuf.data.len == SK_WUFFS_CODEC_BUFFER_SIZE);
     memmove(fBuffer, iobuf.data.ptr, iobuf.meta.wi);
-    fIOBuffer = ((wuffs_base__io_buffer){
-        .data = ((wuffs_base__slice_u8){
-            .ptr = fBuffer,
-            .len = SK_WUFFS_CODEC_BUFFER_SIZE,
-        }),
-        .meta = iobuf.meta,
-    });
+    fIOBuffer.data = wuffs_base__make_slice_u8(fBuffer, SK_WUFFS_CODEC_BUFFER_SIZE);
+    fIOBuffer.meta = iobuf.meta;
 }
 
 const SkWuffsFrame* SkWuffsCodec::frame(int i) const {
@@ -385,46 +317,45 @@
     if (options.fSubset) {
         return SkCodec::kUnimplemented;
     }
+    if (options.fFrameIndex > 0 && SkColorTypeIsAlwaysOpaque(dstInfo.colorType())) {
+        return SkCodec::kInvalidConversion;
+    }
     SkCodec::Result result = this->seekFrame(options.fFrameIndex);
     if (result != SkCodec::kSuccess) {
         return result;
     }
 
-    fSpySampler.reset();
-    fSwizzler = nullptr;
-    fColorTableFilled = false;
-
     const char* status = this->decodeFrameConfig();
-    if (status == nullptr) {
-        fIncrDecDst = static_cast<uint8_t*>(dst);
-        fIncrDecRowBytes = rowBytes;
-        return SkCodec::kSuccess;
-    } else if (status == wuffs_base__suspension__short_read) {
+    if (status == wuffs_base__suspension__short_read) {
         return SkCodec::kIncompleteInput;
-    } else {
+    } else if (status != nullptr) {
         SkCodecPrintf("decodeFrameConfig: %s", status);
         return SkCodec::kErrorInInput;
     }
-}
 
-static bool independent_frame(SkCodec* codec, int frameIndex) {
-    if (frameIndex == 0) {
-        return true;
+    uint32_t src_bits_per_pixel =
+        wuffs_base__pixel_format__bits_per_pixel(fPixelBuffer.pixcfg.pixel_format());
+    if ((src_bits_per_pixel == 0) || (src_bits_per_pixel % 8 != 0)) {
+        return SkCodec::kInternalError;
+    }
+    size_t src_bytes_per_pixel = src_bits_per_pixel / 8;
+
+    // Zero-initialize Wuffs' buffer covering the frame rect.
+    wuffs_base__rect_ie_u32 frame_rect = fFrameConfig.bounds();
+    wuffs_base__table_u8    pixels = fPixelBuffer.plane(0);
+    for (uint32_t y = frame_rect.min_incl_y; y < frame_rect.max_excl_y; y++) {
+        sk_bzero(pixels.ptr + (y * pixels.stride) + (frame_rect.min_incl_x * src_bytes_per_pixel),
+                 frame_rect.width() * src_bytes_per_pixel);
     }
 
-    SkCodec::FrameInfo frameInfo;
-    SkAssertResult(codec->getFrameInfo(frameIndex, &frameInfo));
-    return frameInfo.fRequiredFrame == SkCodec::kNoFrame;
+    fIncrDecDst = static_cast<uint8_t*>(dst);
+    fIncrDecRowBytes = rowBytes;
+    fFirstCallToIncrementalDecode = true;
+    return SkCodec::kSuccess;
 }
 
-static void blend(uint32_t* dst, const uint32_t* src, int width) {
-    while (width --> 0) {
-        if (*src != 0) {
-            *dst = *src;
-        }
-        src++;
-        dst++;
-    }
+static SkAlphaType to_alpha_type(bool opaque) {
+    return opaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType;
 }
 
 SkCodec::Result SkWuffsCodec::onIncrementalDecode(int* rowsDecoded) {
@@ -432,60 +363,19 @@
         return SkCodec::kInternalError;
     }
 
-    // In Wuffs, a paletted image is always 1 byte per pixel.
-    static constexpr size_t src_bpp = 1;
-    wuffs_base__table_u8 pixels = fPixelBuffer.plane(0);
-    int scaledHeight = dstInfo().height();
-    const bool independent = independent_frame(this, options().fFrameIndex);
-    wuffs_base__rect_ie_u32 frame_rect = fFrameConfig.bounds();
-    if (!fSwizzler) {
-        auto bounds = SkIRect::MakeLTRB(frame_rect.min_incl_x, frame_rect.min_incl_y,
-                                        frame_rect.max_excl_x, frame_rect.max_excl_y);
-        fSwizzler = SkSwizzler::Make(this->getEncodedInfo(), fColorTable, dstInfo(),
-                                     this->options(), &bounds);
-        fSwizzler->setSampleX(fSpySampler.sampleX());
-        fSwizzler->setSampleY(fSpySampler.sampleY());
-        scaledHeight = get_scaled_dimension(dstInfo().height(), fSpySampler.sampleY());
-
-        // Zero-initialize wuffs' buffer covering the frame rect. This will later be used to
-        // determine how we write to the output, even if the image was incomplete. This ensures
-        // that we do not swizzle uninitialized memory.
-        for (uint32_t y = frame_rect.min_incl_y; y < frame_rect.max_excl_y; y++) {
-            uint8_t* s = pixels.ptr + (y * pixels.stride) + (frame_rect.min_incl_x * src_bpp);
-            sk_bzero(s, frame_rect.width() * src_bpp);
-        }
-
-        // If the frame rect does not fill the output, ensure that those pixels are not
-        // left uninitialized either.
-        if (independent && bounds != this->bounds()) {
-            auto fillInfo = dstInfo().makeWH(fSwizzler->fillWidth(), scaledHeight);
-            SkSampler::Fill(fillInfo, fIncrDecDst, fIncrDecRowBytes, options().fZeroInitialized);
-        }
-    }
-
-    // The semantics of *rowsDecoded is: say you have a 10 pixel high image
-    // (both the frame and the image). If you only decoded the first 3 rows,
-    // set this to 3, and then SkCodec (or the caller of incrementalDecode)
-    // would zero-initialize the remaining 7 (unless the memory was already
-    // zero-initialized).
-    //
-    // Now let's say that the image is still 10 pixels high, but the frame is
-    // from row 5 to 9. If you only decoded 3 rows, but you initialized the
-    // first 5, you could return 8, and the caller would zero-initialize the
-    // final 2. For GIF (where a frame can be smaller than the image and can be
-    // interlaced), we just zero-initialize all 10 rows ahead of time and
-    // return the height of the image, so the caller knows it doesn't need to
-    // do anything.
-    //
-    // Similarly, if the output is scaled, we zero-initialized all
-    // |scaledHeight| rows (the scaled image height), so we inform the caller
-    // that it doesn't need to do anything.
-    if (rowsDecoded) {
-        *rowsDecoded = scaledHeight;
-    }
-
     SkCodec::Result result = SkCodec::kSuccess;
     const char*     status = this->decodeFrame();
+    bool independent;
+    SkAlphaType alphaType;
+    const int index = options().fFrameIndex;
+    if (index == 0) {
+        independent = true;
+        alphaType = to_alpha_type(getEncodedInfo().opaque());
+    } else {
+        const SkWuffsFrame* f = this->frame(index);
+        independent = f->getRequiredFrame() == SkCodec::kNoFrame;
+        alphaType = to_alpha_type(f->reportedAlpha() == SkEncodedInfo::kOpaque_Alpha);
+    }
     if (status != nullptr) {
         if (status == wuffs_base__suspension__short_read) {
             result = SkCodec::kIncompleteInput;
@@ -496,80 +386,94 @@
 
         if (!independent) {
             // For a dependent frame, we cannot blend the partial result, since
-            // that will overwrite the contribution from prior frames with all
-            // zeroes that were written to |pixels| above.
+            // that will overwrite the contribution from prior frames.
             return result;
         }
     }
 
+    uint32_t src_bits_per_pixel =
+        wuffs_base__pixel_format__bits_per_pixel(fPixelBuffer.pixcfg.pixel_format());
+    if ((src_bits_per_pixel == 0) || (src_bits_per_pixel % 8 != 0)) {
+        return SkCodec::kInternalError;
+    }
+    size_t src_bytes_per_pixel = src_bits_per_pixel / 8;
+
+    wuffs_base__rect_ie_u32 frame_rect = fFrameConfig.bounds();
+    if (fFirstCallToIncrementalDecode) {
+        if (frame_rect.width() > (SIZE_MAX / src_bytes_per_pixel)) {
+            return SkCodec::kInternalError;
+        }
+
+        auto bounds = SkIRect::MakeLTRB(frame_rect.min_incl_x, frame_rect.min_incl_y,
+                                        frame_rect.max_excl_x, frame_rect.max_excl_y);
+
+        // If the frame rect does not fill the output, ensure that those pixels are not
+        // left uninitialized.
+        if (independent && (bounds != this->bounds() || result != kSuccess)) {
+            SkSampler::Fill(dstInfo(), fIncrDecDst, fIncrDecRowBytes,
+                            options().fZeroInitialized);
+        }
+        fFirstCallToIncrementalDecode = false;
+    } else {
+        // Existing clients intend to only show frames beyond the first if they
+        // are complete (based on FrameInfo::fFullyReceived), since it might
+        // look jarring to draw a partial frame over an existing frame. If they
+        // changed their behavior and expected to continue decoding a partial
+        // frame after the first one, we'll need to update our blending code.
+        // Otherwise, if the frame were interlaced and not independent, the
+        // second pass may have an overlapping dirty_rect with the first,
+        // resulting in blending with the first pass.
+        SkASSERT(index == 0);
+    }
+
+    if (rowsDecoded) {
+        *rowsDecoded = dstInfo().height();
+    }
+
     // If the frame's dirty rect is empty, no need to swizzle.
     wuffs_base__rect_ie_u32 dirty_rect = fDecoder->frame_dirty_rect();
     if (!dirty_rect.is_empty()) {
-        if (!fColorTableFilled) {
-            fColorTableFilled = true;
-            wuffs_base__slice_u8 palette = fPixelBuffer.palette();
-            SkASSERT(palette.len == 4 * 256);
-            auto proc = choose_pack_color_proc(false, dstInfo().colorType());
-            for (int i = 0; i < 256; i++) {
-                uint8_t* p = palette.ptr + 4 * i;
-                fColorTable[i] = proc(p[3], p[2], p[1], p[0]);
-            }
+        wuffs_base__table_u8 pixels = fPixelBuffer.plane(0);
+
+        // The Wuffs model is that the dst buffer is the image, not the frame.
+        // The expectation is that you allocate the buffer once, but re-use it
+        // for the N frames, regardless of each frame's top-left co-ordinate.
+        //
+        // To get from the start (in the X-direction) of the image to the start
+        // of the dirty_rect, we adjust s by (dirty_rect.min_incl_x * src_bytes_per_pixel).
+        uint8_t* s = pixels.ptr + (dirty_rect.min_incl_y * pixels.stride)
+                                + (dirty_rect.min_incl_x * src_bytes_per_pixel);
+
+        // Currently, this is only used for GIF, which will never have an ICC profile. When it is
+        // used for other formats that might have one, we will need to transform from profiles that
+        // do not have corresponding SkColorSpaces.
+        SkASSERT(!getEncodedInfo().profile());
+
+        auto srcInfo = getInfo().makeWH(dirty_rect.width(), dirty_rect.height())
+                                .makeAlphaType(alphaType);
+        SkBitmap src;
+        src.installPixels(srcInfo, s, pixels.stride);
+        SkPaint paint;
+        if (independent) {
+            paint.setBlendMode(SkBlendMode::kSrc);
         }
 
-        std::unique_ptr<uint8_t[]> tmpBuffer;
-        if (!independent) {
-            tmpBuffer.reset(new uint8_t[dstInfo().minRowBytes()]);
-        }
-        const int sampleY = fSwizzler->sampleY();
-        for (uint32_t y = dirty_rect.min_incl_y; y < dirty_rect.max_excl_y; y++) {
-            int dstY = y;
-            if (sampleY != 1) {
-                if (!fSwizzler->rowNeeded(y)) {
-                    continue;
-                }
-                dstY /= sampleY;
-                if (dstY >= scaledHeight) {
-                    break;
-                }
-            }
+        SkDraw draw;
+        draw.fDst.reset(dstInfo(), fIncrDecDst, fIncrDecRowBytes);
+        SkMatrix matrix = SkMatrix::MakeRectToRect(SkRect::Make(this->dimensions()),
+                                                   SkRect::Make(this->dstInfo().dimensions()),
+                                                   SkMatrix::kFill_ScaleToFit);
+        draw.fMatrix = &matrix;
+        SkRasterClip rc(SkIRect::MakeSize(this->dstInfo().dimensions()));
+        draw.fRC = &rc;
 
-            // We don't adjust d by (frame_rect.min_incl_x * dst_bpp) as we
-            // have already accounted for that in swizzleRect, above.
-            uint8_t* d = fIncrDecDst + (dstY * fIncrDecRowBytes);
-
-            // The Wuffs model is that the dst buffer is the image, not the frame.
-            // The expectation is that you allocate the buffer once, but re-use it
-            // for the N frames, regardless of each frame's top-left co-ordinate.
-            //
-            // To get from the start (in the X-direction) of the image to the start
-            // of the frame, we adjust s by (frame_rect.min_incl_x * src_bpp).
-            //
-            // We adjust (in the X-direction) by the frame rect, not the dirty
-            // rect, because the swizzler (which operates on rows) was
-            // configured with the frame rect's X range.
-            uint8_t* s = pixels.ptr + (y * pixels.stride) + (frame_rect.min_incl_x * src_bpp);
-            if (independent) {
-                fSwizzler->swizzle(d, s);
-            } else {
-                SkASSERT(tmpBuffer.get());
-                fSwizzler->swizzle(tmpBuffer.get(), s);
-                d = SkTAddOffset<uint8_t>(d, fSwizzler->swizzleOffsetBytes());
-                const auto* swizzled = SkTAddOffset<uint32_t>(tmpBuffer.get(),
-                                                              fSwizzler->swizzleOffsetBytes());
-                blend(reinterpret_cast<uint32_t*>(d), swizzled, fSwizzler->swizzleWidth());
-            }
-        }
+        SkMatrix translate = SkMatrix::MakeTrans(dirty_rect.min_incl_x, dirty_rect.min_incl_y);
+        draw.drawBitmap(src, translate, nullptr, paint);
     }
 
     if (result == SkCodec::kSuccess) {
-        fSpySampler.reset();
         fIncrDecDst = nullptr;
         fIncrDecRowBytes = 0;
-        fSwizzler = nullptr;
-        fColorTableFilled = false;
-    } else {
-        // Make fSpySampler return whatever fSwizzler would have for fillWidth.
-        fSpySampler.fFillWidth = fSwizzler->fillWidth();
     }
     return result;
 }
@@ -606,32 +510,6 @@
     return n < INT_MAX ? n : INT_MAX;
 }
 
-SkSampler* SkWuffsCodec::getSampler(bool createIfNecessary) {
-    // fIncrDst being non-nullptr means that we are between an
-    // onStartIncrementalDecode call and the matching final (successful)
-    // onIncrementalDecode call.
-    if (createIfNecessary || fIncrDecDst) {
-        return &fSpySampler;
-    }
-    return nullptr;
-}
-
-bool SkWuffsCodec::conversionSupported(const SkImageInfo& dst, bool srcIsOpaque, bool needsColorXform) {
-    if (!this->INHERITED::conversionSupported(dst, srcIsOpaque, needsColorXform)) {
-        return false;
-    }
-
-    switch (dst.colorType()) {
-        case kRGBA_8888_SkColorType:
-        case kBGRA_8888_SkColorType:
-            return true;
-        default:
-            // FIXME: Add skcms to support F16
-            // FIXME: Add support for 565 on the first frame
-            return false;
-    }
-}
-
 void SkWuffsCodec::readFrames() {
     size_t n = fFrames.size();
     int    i = n ? n - 1 : 0;
@@ -754,16 +632,16 @@
                                                      wuffs_base__image_config* imgcfg,
                                                      wuffs_base__io_buffer*    b,
                                                      SkStream*                 s) {
-    memset(decoder, 0, sizeof__wuffs_gif__decoder());
-    const char* status = decoder->check_wuffs_version(sizeof__wuffs_gif__decoder(), WUFFS_VERSION);
+    // Calling decoder->initialize will memset it to zero.
+    const char* status = decoder->initialize(sizeof__wuffs_gif__decoder(), WUFFS_VERSION, 0);
     if (status != nullptr) {
-        SkCodecPrintf("check_wuffs_version: %s", status);
+        SkCodecPrintf("initialize: %s", status);
         return SkCodec::kInternalError;
     }
     while (true) {
         status = decoder->decode_image_config(imgcfg, b->reader());
         if (status == nullptr) {
-            return SkCodec::kSuccess;
+            break;
         } else if (status != wuffs_base__suspension__short_read) {
             SkCodecPrintf("decode_image_config: %s", status);
             return SkCodec::kErrorInInput;
@@ -771,13 +649,35 @@
             return SkCodec::kIncompleteInput;
         }
     }
+
+    // A GIF image's natural color model is indexed color: 1 byte per pixel,
+    // indexing a 256-element palette.
+    //
+    // For Skia, we override that to decode to 4 bytes per pixel, BGRA or RGBA.
+    wuffs_base__pixel_format pixfmt = 0;
+    switch (kN32_SkColorType) {
+        case kBGRA_8888_SkColorType:
+            pixfmt = WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL;
+            break;
+        case kRGBA_8888_SkColorType:
+            pixfmt = WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL;
+            break;
+        default:
+            return SkCodec::kInternalError;
+    }
+    if (imgcfg) {
+        imgcfg->pixcfg.set(pixfmt, WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, imgcfg->pixcfg.width(),
+                           imgcfg->pixcfg.height());
+    }
+
+    return SkCodec::kSuccess;
 }
 
 SkCodec::Result SkWuffsCodec::resetDecoder() {
     if (!fStream->rewind()) {
         return SkCodec::kInternalError;
     }
-    fIOBuffer.meta = ((wuffs_base__io_buffer_meta){});
+    fIOBuffer.meta = wuffs_base__null_io_buffer_meta();
 
     SkCodec::Result result =
         reset_and_decode_image_config(fDecoder.get(), nullptr, &fIOBuffer, fStream.get());
@@ -806,12 +706,9 @@
 
 const char* SkWuffsCodec::decodeFrame() {
     while (true) {
-        const char* status = fDecoder->decode_frame(&fPixelBuffer, fIOBuffer.reader(),
-                                                    ((wuffs_base__slice_u8){
-                                                        .ptr = fWorkbufPtr.get(),
-                                                        .len = fWorkbufLen,
-                                                    }),
-                                                    NULL);
+        const char* status =
+            fDecoder->decode_frame(&fPixelBuffer, fIOBuffer.reader(),
+                                   wuffs_base__make_slice_u8(fWorkbufPtr.get(), fWorkbufLen), NULL);
         if ((status == wuffs_base__suspension__short_read) &&
             fill_buffer(&fIOBuffer, fStream.get())) {
             continue;
@@ -842,15 +739,11 @@
 
 std::unique_ptr<SkCodec> SkWuffsCodec_MakeFromStream(std::unique_ptr<SkStream> stream,
                                                      SkCodec::Result*          result) {
-    uint8_t                  buffer[SK_WUFFS_CODEC_BUFFER_SIZE];
-    wuffs_base__io_buffer    iobuf = ((wuffs_base__io_buffer){
-        .data = ((wuffs_base__slice_u8){
-            .ptr = buffer,
-            .len = SK_WUFFS_CODEC_BUFFER_SIZE,
-        }),
-        .meta = ((wuffs_base__io_buffer_meta){}),
-    });
-    wuffs_base__image_config imgcfg = ((wuffs_base__image_config){});
+    uint8_t               buffer[SK_WUFFS_CODEC_BUFFER_SIZE];
+    wuffs_base__io_buffer iobuf =
+        wuffs_base__make_io_buffer(wuffs_base__make_slice_u8(buffer, SK_WUFFS_CODEC_BUFFER_SIZE),
+                                   wuffs_base__null_io_buffer_meta());
+    wuffs_base__image_config imgcfg = wuffs_base__null_image_config();
 
     // Wuffs is primarily a C library, not a C++ one. Furthermore, outside of
     // the wuffs_base__etc types, the sizeof a file format specific type like
@@ -913,25 +806,27 @@
     }
     std::unique_ptr<uint8_t, decltype(&sk_free)> pixbuf_ptr(
         reinterpret_cast<uint8_t*>(pixbuf_ptr_raw), &sk_free);
-    wuffs_base__pixel_buffer pixbuf = ((wuffs_base__pixel_buffer){});
+    wuffs_base__pixel_buffer pixbuf = wuffs_base__null_pixel_buffer();
 
-    const char* status = pixbuf.set_from_slice(&imgcfg.pixcfg, ((wuffs_base__slice_u8){
-                                                                   .ptr = pixbuf_ptr.get(),
-                                                                   .len = pixbuf_len,
-                                                               }));
+    const char* status = pixbuf.set_from_slice(
+        &imgcfg.pixcfg, wuffs_base__make_slice_u8(pixbuf_ptr.get(), SkToSizeT(pixbuf_len)));
     if (status != nullptr) {
         SkCodecPrintf("set_from_slice: %s", status);
         *result = SkCodec::kInternalError;
         return nullptr;
     }
 
+    SkEncodedInfo::Color color =
+        (imgcfg.pixcfg.pixel_format() == WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL)
+            ? SkEncodedInfo::kBGRA_Color
+            : SkEncodedInfo::kRGBA_Color;
+
     // In Skia's API, the alpha we calculate here and return is only for the
     // first frame.
     SkEncodedInfo::Alpha alpha = imgcfg.first_frame_is_opaque() ? SkEncodedInfo::kOpaque_Alpha
                                                                 : SkEncodedInfo::kBinary_Alpha;
 
-    SkEncodedInfo encodedInfo =
-        SkEncodedInfo::Make(width, height, SkEncodedInfo::kPalette_Color, alpha, 8);
+    SkEncodedInfo encodedInfo = SkEncodedInfo::Make(width, height, color, alpha, 8);
 
     *result = SkCodec::kSuccess;
     return std::unique_ptr<SkCodec>(new SkWuffsCodec(
diff --git a/src/compute/common/cl/assert_cl.h b/src/compute/common/cl/assert_cl.h
index efe698f..8853a70 100644
--- a/src/compute/common/cl/assert_cl.h
+++ b/src/compute/common/cl/assert_cl.h
@@ -32,8 +32,8 @@
 //
 //
 
-#define cl(...)    assert_cl((cl##__VA_ARGS__), __FILE__, __LINE__, true);
-#define cl_ok(err) assert_cl(err,               __FILE__, __LINE__, true);
+#define cl(...)    assert_cl((cl##__VA_ARGS__), __FILE__, __LINE__, true)
+#define cl_ok(err) assert_cl(err,               __FILE__, __LINE__, true)
 
 //
 //
diff --git a/src/compute/skc/platforms/cl_12/path_builder_cl_12.c b/src/compute/skc/platforms/cl_12/path_builder_cl_12.c
index e0a9d02..e3e404b 100644
--- a/src/compute/skc/platforms/cl_12/path_builder_cl_12.c
+++ b/src/compute/skc/platforms/cl_12/path_builder_cl_12.c
@@ -529,7 +529,7 @@
                                0,NULL,NULL));
 
       // bring from back in range
-      from = ++from % impl->ring.subbufs;
+      from = (from + 1) % impl->ring.subbufs;
     }
 }
 
@@ -576,7 +576,7 @@
                                         &cl_err); cl_ok(cl_err);
 
       // bring from back in range
-      from = ++from % impl->ring.subbufs;
+      from = (from + 1) % impl->ring.subbufs;
     }
   //
   // FIXME -- when we switch to out of order queues we'll need a barrier here
diff --git a/src/core/SkBitmap.cpp b/src/core/SkBitmap.cpp
index af479cc..45c0218 100644
--- a/src/core/SkBitmap.cpp
+++ b/src/core/SkBitmap.cpp
@@ -219,6 +219,38 @@
     return allocator->allocPixelRef(this);
 }
 
+bool SkBitmap::tryAllocN32Pixels(int width, int height, bool isOpaque) {
+    SkImageInfo info = SkImageInfo::MakeN32(width, height,
+            isOpaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType);
+    return this->tryAllocPixels(info);
+}
+
+void SkBitmap::allocN32Pixels(int width, int height, bool isOpaque) {
+    SkImageInfo info = SkImageInfo::MakeN32(width, height,
+                                        isOpaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType);
+    this->allocPixels(info);
+}
+
+void SkBitmap::allocPixels() {
+    this->allocPixels((Allocator*)nullptr);
+}
+
+void SkBitmap::allocPixels(Allocator* allocator) {
+    SkASSERT_RELEASE(this->tryAllocPixels(allocator));
+}
+
+void SkBitmap::allocPixelsFlags(const SkImageInfo& info, uint32_t flags) {
+    SkASSERT_RELEASE(this->tryAllocPixelsFlags(info, flags));
+}
+
+void SkBitmap::allocPixels(const SkImageInfo& info, size_t rowBytes) {
+    SkASSERT_RELEASE(this->tryAllocPixels(info, rowBytes));
+}
+
+void SkBitmap::allocPixels(const SkImageInfo& info) {
+    this->allocPixels(info, info.minRowBytes());
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 bool SkBitmap::tryAllocPixels(const SkImageInfo& requestedInfo, size_t rowBytes) {
diff --git a/src/core/SkBitmapDevice.cpp b/src/core/SkBitmapDevice.cpp
index 9fffbb5..8c21959 100644
--- a/src/core/SkBitmapDevice.cpp
+++ b/src/core/SkBitmapDevice.cpp
@@ -19,6 +19,7 @@
 #include "SkRasterHandleAllocator.h"
 #include "SkShader.h"
 #include "SkSpecialImage.h"
+#include "SkStrikeCache.h"
 #include "SkSurface.h"
 #include "SkTLazy.h"
 #include "SkVertices.h"
@@ -210,24 +211,14 @@
     return true;
 }
 
-// TODO: unify this with the same functionality on SkDraw.
-static SkScalerContextFlags scaler_context_flags(const SkBitmap& bitmap)  {
-    // If we're doing linear blending, then we can disable the gamma hacks.
-    // Otherwise, leave them on. In either case, we still want the contrast boost:
-    // TODO: Can we be even smarter about mask gamma based on the dst transfer function?
-    if (bitmap.colorSpace() && bitmap.colorSpace()->gammaIsLinear()) {
-        return SkScalerContextFlags::kBoostContrast;
-    } else {
-        return SkScalerContextFlags::kFakeGammaAndBoostContrast;
-    }
-}
-
 SkBitmapDevice::SkBitmapDevice(const SkBitmap& bitmap)
-    : INHERITED(bitmap.info(), SkSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType))
-    , fBitmap(bitmap)
-    , fRCStack(bitmap.width(), bitmap.height())
-    , fGlyphPainter(this->surfaceProps(), bitmap.colorType(), scaler_context_flags(bitmap))
-{
+        : INHERITED(bitmap.info(), SkSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType))
+        , fBitmap(bitmap)
+        , fRCStack(bitmap.width(), bitmap.height())
+        , fGlyphPainter(this->surfaceProps(),
+                        bitmap.colorType(),
+                        bitmap.colorSpace(),
+                        SkStrikeCache::GlobalStrikeCache()) {
     SkASSERT(valid_for_bitmap_device(bitmap.info(), nullptr));
 }
 
@@ -237,12 +228,14 @@
 
 SkBitmapDevice::SkBitmapDevice(const SkBitmap& bitmap, const SkSurfaceProps& surfaceProps,
                                SkRasterHandleAllocator::Handle hndl, const SkBitmap* coverage)
-    : INHERITED(bitmap.info(), surfaceProps)
-    , fBitmap(bitmap)
-    , fRasterHandle(hndl)
-    , fRCStack(bitmap.width(), bitmap.height())
-    , fGlyphPainter(this->surfaceProps(), bitmap.colorType(), scaler_context_flags(bitmap))
-{
+        : INHERITED(bitmap.info(), surfaceProps)
+        , fBitmap(bitmap)
+        , fRasterHandle(hndl)
+        , fRCStack(bitmap.width(), bitmap.height())
+        , fGlyphPainter(this->surfaceProps(),
+                        bitmap.colorType(),
+                        bitmap.colorSpace(),
+                        SkStrikeCache::GlobalStrikeCache()) {
     SkASSERT(valid_for_bitmap_device(bitmap.info(), nullptr));
 
     if (coverage) {
@@ -399,13 +392,6 @@
     }
 }
 
-void SkBitmapDevice::drawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y,
-                                const SkPaint& paint) {
-    SkMatrix matrix = SkMatrix::MakeTrans(x, y);
-    LogDrawScaleFactor(SkMatrix::Concat(this->ctm(), matrix), paint.getFilterQuality());
-    this->drawBitmap(bitmap, matrix, nullptr, paint);
-}
-
 void SkBitmapDevice::drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix,
                                 const SkRect* dstOrNull, const SkPaint& paint) {
     const SkRect* bounds = dstOrNull;
@@ -450,7 +436,7 @@
     }
     matrix.setRectToRect(tmpSrc, dst, SkMatrix::kFill_ScaleToFit);
 
-    LogDrawScaleFactor(SkMatrix::Concat(this->ctm(), matrix), paint.getFilterQuality());
+    LogDrawScaleFactor(this->ctm(), matrix, paint.getFilterQuality());
 
     const SkRect* dstPtr = &dst;
     const SkBitmap* bitmapPtr = &bitmap;
@@ -532,15 +518,15 @@
     // if its mutable, since that precaution is not needed (give the short lifetime of the shader).
 
     // construct a shader, so we can call drawRect with the dst
-    auto s = SkMakeBitmapShader(*bitmapPtr, SkShader::kClamp_TileMode, SkShader::kClamp_TileMode,
-                                &matrix, kNever_SkCopyPixelsMode);
+    auto s = SkMakeBitmapShaderForPaint(paint, *bitmapPtr, SkTileMode::kClamp,
+                                        SkTileMode::kClamp, &matrix, kNever_SkCopyPixelsMode);
     if (!s) {
         return;
     }
 
     SkPaint paintWithShader(paint);
     paintWithShader.setStyle(SkPaint::kFill_Style);
-    paintWithShader.setShader(s);
+    paintWithShader.setShader(std::move(s));
 
     // Call ourself, in case the subclass wanted to share this setup code
     // but handle the drawRect code themselves.
@@ -580,8 +566,7 @@
         draw.fMatrix = &SkMatrix::I();
         draw.fRC = &fRCStack.rc();
         SkPaint paint(origPaint);
-        paint.setShader(SkShader::MakeBitmapShader(src->fBitmap, SkShader::kClamp_TileMode,
-                                                   SkShader::kClamp_TileMode, nullptr));
+        paint.setShader(src->fBitmap.makeShader());
         draw.drawBitmap(*src->fCoverage.get(),
                         SkMatrix::MakeTrans(SkIntToScalar(x),SkIntToScalar(y)), nullptr, paint);
     } else {
@@ -704,7 +689,10 @@
 
     SkAutoDeviceCTMRestore adctmr(this, maskMatrix);
     paint.writable()->setShader(srcImage->makeShader(&shaderMatrix));
-    this->drawImage(mask.get(), maskBounds.x(), maskBounds.y(), *paint);
+    this->drawImageRect(mask.get(), nullptr,
+                        SkRect::MakeXYWH(maskBounds.x(), maskBounds.y(),
+                                         mask->width(), mask->height()),
+                        *paint, SkCanvas::kFast_SrcRectConstraint);
 }
 
 sk_sp<SkSpecialImage> SkBitmapDevice::makeSpecial(const SkBitmap& bitmap) {
diff --git a/src/core/SkBitmapDevice.h b/src/core/SkBitmapDevice.h
index e6f64b6..2890edf 100644
--- a/src/core/SkBitmapDevice.h
+++ b/src/core/SkBitmapDevice.h
@@ -89,7 +89,6 @@
      *  path on the stack to hold the representation of the oval.
      */
     void drawPath(const SkPath&, const SkPaint&, bool pathIsMutable) override;
-    void drawBitmap(const SkBitmap&, SkScalar x, SkScalar y, const SkPaint&) override;
     void drawSprite(const SkBitmap&, int x, int y, const SkPaint&) override;
 
     /**
diff --git a/src/core/SkBitmapProcState.cpp b/src/core/SkBitmapProcState.cpp
index bb959d1..8db130c 100644
--- a/src/core/SkBitmapProcState.cpp
+++ b/src/core/SkBitmapProcState.cpp
@@ -120,7 +120,7 @@
 }
 
 SkBitmapProcInfo::SkBitmapProcInfo(const SkBitmapProvider& provider,
-                                   SkShader::TileMode tmx, SkShader::TileMode tmy)
+                                   SkTileMode tmx, SkTileMode tmy)
     : fProvider(provider)
     , fTileModeX(tmx)
     , fTileModeY(tmy)
@@ -196,8 +196,7 @@
         // We don't do this if we're either trivial (can ignore the matrix) or clamping
         // in both X and Y since clamping to width,height is just as easy as to 0xFFFF.
 
-        if (fTileModeX != SkShader::kClamp_TileMode ||
-            fTileModeY != SkShader::kClamp_TileMode) {
+        if (fTileModeX != SkTileMode::kClamp || fTileModeY != SkTileMode::kClamp) {
             fInvMatrix.postIDiv(fPixmap.width(), fPixmap.height());
         }
 
@@ -247,7 +246,7 @@
     SkASSERT(fPixmap.alphaType() == kPremul_SkAlphaType ||
              fPixmap.alphaType() == kOpaque_SkAlphaType);
     SkASSERT(fTileModeX == fTileModeY);
-    SkASSERT(fTileModeX != SkShader::kDecal_TileMode);
+    SkASSERT(fTileModeX != SkTileMode::kDecal);
     SkASSERT(fFilterQuality < kHigh_SkFilterQuality);
 
     fInvProc            = SkMatrixPriv::GetMapXYProc(fInvMatrix);
@@ -272,7 +271,7 @@
     // TODO: move this one into chooseShaderProc32() or pull all that in here.
     if (fAlphaScale == 256
             && fFilterQuality == kNone_SkFilterQuality
-            && SkShader::kClamp_TileMode == fTileModeX) {
+            && SkTileMode::kClamp == fTileModeX) {
         fShaderProc32 = Clamp_S32_opaque_D32_nofilter_DX_shaderproc;
     } else {
         fShaderProc32 = this->chooseShaderProc32();
@@ -431,8 +430,7 @@
             // chooseProcs multiples the inverse matrix by the inverse of the
             // bitmap's width and height. Since this method is going to do
             // its own tiling and sampling we need to undo that here.
-            if (SkShader::kClamp_TileMode != s.fTileModeX ||
-                SkShader::kClamp_TileMode != s.fTileModeY) {
+            if (SkTileMode::kClamp != s.fTileModeX || SkTileMode::kClamp != s.fTileModeY) {
                 yTemp = SkFractionalIntToInt(mapper.fractionalIntY() * s.fPixmap.height());
             } else {
                 yTemp = mapper.intY();
@@ -443,13 +441,13 @@
 
         const int stopY = s.fPixmap.height();
         switch (s.fTileModeY) {
-            case SkShader::kClamp_TileMode:
+            case SkTileMode::kClamp:
                 iY0 = SkClampMax(yTemp, stopY-1);
                 break;
-            case SkShader::kRepeat_TileMode:
+            case SkTileMode::kRepeat:
                 iY0 = sk_int_mod(yTemp, stopY);
                 break;
-            case SkShader::kMirror_TileMode:
+            case SkTileMode::kMirror:
             default:
                 iY0 = sk_int_mirror(yTemp, stopY);
                 break;
@@ -461,21 +459,20 @@
             int iY2;
 
             if (s.fInvType > SkMatrix::kTranslate_Mask &&
-                (SkShader::kClamp_TileMode != s.fTileModeX ||
-                 SkShader::kClamp_TileMode != s.fTileModeY)) {
+                (SkTileMode::kClamp != s.fTileModeX || SkTileMode::kClamp != s.fTileModeY)) {
                 iY2 = SkFractionalIntToInt(mapper.fractionalIntY() * s.fPixmap.height());
             } else {
                 iY2 = mapper.intY();
             }
 
             switch (s.fTileModeY) {
-            case SkShader::kClamp_TileMode:
+            case SkTileMode::kClamp:
                 iY2 = SkClampMax(iY2, stopY-1);
                 break;
-            case SkShader::kRepeat_TileMode:
+            case SkTileMode::kRepeat:
                 iY2 = sk_int_mod(iY2, stopY);
                 break;
-            case SkShader::kMirror_TileMode:
+            case SkTileMode::kMirror:
             default:
                 iY2 = sk_int_mirror(iY2, stopY);
                 break;
@@ -559,16 +556,16 @@
         return nullptr;
     }
 
-    SkShader::TileMode tx = (SkShader::TileMode)fTileModeX;
-    SkShader::TileMode ty = (SkShader::TileMode)fTileModeY;
+    SkTileMode tx = fTileModeX;
+    SkTileMode ty = fTileModeY;
 
-    if (SkShader::kClamp_TileMode == tx && SkShader::kClamp_TileMode == ty) {
+    if (SkTileMode::kClamp == tx && SkTileMode::kClamp == ty) {
         if (this->setupForTranslate()) {
             return Clamp_S32_D32_nofilter_trans_shaderproc;
         }
         return DoNothing_shaderproc;
     }
-    if (SkShader::kRepeat_TileMode == tx && SkShader::kRepeat_TileMode == ty) {
+    if (SkTileMode::kRepeat == tx && SkTileMode::kRepeat == ty) {
         if (this->setupForTranslate()) {
             return Repeat_S32_D32_nofilter_trans_shaderproc;
         }
diff --git a/src/core/SkBitmapProcState.h b/src/core/SkBitmapProcState.h
index b24157b..013c5f6 100644
--- a/src/core/SkBitmapProcState.h
+++ b/src/core/SkBitmapProcState.h
@@ -29,20 +29,20 @@
 class SkPaint;
 
 struct SkBitmapProcInfo {
-    SkBitmapProcInfo(const SkBitmapProvider&, SkShader::TileMode tmx, SkShader::TileMode tmy);
+    SkBitmapProcInfo(const SkBitmapProvider&, SkTileMode tmx, SkTileMode tmy);
     ~SkBitmapProcInfo();
 
-    const SkBitmapProvider        fProvider;
+    const SkBitmapProvider  fProvider;
 
-    SkPixmap                      fPixmap;
-    SkMatrix                      fInvMatrix;         // This changes based on tile mode.
+    SkPixmap                fPixmap;
+    SkMatrix                fInvMatrix;         // This changes based on tile mode.
     // TODO: combine fInvMatrix and fRealInvMatrix.
-    SkMatrix                      fRealInvMatrix;     // The actual inverse matrix.
-    SkColor                       fPaintColor;
-    SkShader::TileMode            fTileModeX;
-    SkShader::TileMode            fTileModeY;
-    SkFilterQuality               fFilterQuality;
-    SkMatrix::TypeMask            fInvType;
+    SkMatrix                fRealInvMatrix;     // The actual inverse matrix.
+    SkColor                 fPaintColor;
+    SkTileMode              fTileModeX;
+    SkTileMode              fTileModeY;
+    SkFilterQuality         fFilterQuality;
+    SkMatrix::TypeMask      fInvType;
 
     bool init(const SkMatrix& inverse, const SkPaint&);
 
@@ -55,7 +55,7 @@
 };
 
 struct SkBitmapProcState : public SkBitmapProcInfo {
-    SkBitmapProcState(const SkBitmapProvider& prov, SkShader::TileMode tmx, SkShader::TileMode tmy)
+    SkBitmapProcState(const SkBitmapProvider& prov, SkTileMode tmx, SkTileMode tmy)
         : SkBitmapProcInfo(prov, tmx, tmy) {}
 
     bool setup(const SkMatrix& inv, const SkPaint& paint) {
diff --git a/src/core/SkBitmapProcState_matrixProcs.cpp b/src/core/SkBitmapProcState_matrixProcs.cpp
index 24e3b18..bf53d88 100644
--- a/src/core/SkBitmapProcState_matrixProcs.cpp
+++ b/src/core/SkBitmapProcState_matrixProcs.cpp
@@ -465,7 +465,7 @@
                 dst16 += 8;
                 count -= 8;
                 fx += dx8;
-            };
+            }
             xy = (uint32_t *) dst16;
         }
 
@@ -795,22 +795,22 @@
 SkBitmapProcState::MatrixProc SkBitmapProcState::chooseMatrixProc(bool translate_only_matrix) {
     SkASSERT(fInvType <= (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask));
     SkASSERT(fTileModeX == fTileModeY);
-    SkASSERT(fTileModeX != SkShader::kDecal_TileMode);
+    SkASSERT(fTileModeX != SkTileMode::kDecal);
 
     // Check for our special case translate methods when there is no scale/affine/perspective.
     if (translate_only_matrix && kNone_SkFilterQuality == fFilterQuality) {
         switch (fTileModeX) {
             default: SkASSERT(false);
-            case SkShader::kClamp_TileMode:  return  clampx_nofilter_trans;
-            case SkShader::kRepeat_TileMode: return repeatx_nofilter_trans;
-            case SkShader::kMirror_TileMode: return mirrorx_nofilter_trans;
+            case SkTileMode::kClamp:  return  clampx_nofilter_trans;
+            case SkTileMode::kRepeat: return repeatx_nofilter_trans;
+            case SkTileMode::kMirror: return mirrorx_nofilter_trans;
         }
     }
 
     // The arrays are all [ nofilter, filter ].
     int index = fFilterQuality > kNone_SkFilterQuality ? 1 : 0;
 
-    if (fTileModeX == SkShader::kClamp_TileMode) {
+    if (fTileModeX == SkTileMode::kClamp) {
         // clamp gets special version of filterOne, working in non-normalized space (allowing decal)
         fFilterOneX = SK_Fixed1;
         fFilterOneY = SK_Fixed1;
@@ -821,7 +821,7 @@
     fFilterOneX = SK_Fixed1 / fPixmap.width();
     fFilterOneY = SK_Fixed1 / fPixmap.height();
 
-    if (fTileModeX == SkShader::kRepeat_TileMode) {
+    if (fTileModeX == SkTileMode::kRepeat) {
         return RepeatX_RepeatY_Procs[index];
     }
 
diff --git a/src/core/SkBlitter.cpp b/src/core/SkBlitter.cpp
index 7443377..874fb88 100644
--- a/src/core/SkBlitter.cpp
+++ b/src/core/SkBlitter.cpp
@@ -10,6 +10,7 @@
 #include "SkAntiRun.h"
 #include "SkArenaAlloc.h"
 #include "SkColor.h"
+#include "SkColorData.h"
 #include "SkColorFilter.h"
 #include "SkMask.h"
 #include "SkMaskFilterBase.h"
@@ -101,119 +102,6 @@
     }
 }
 
-void SkBlitter::blitCoverageDeltas(SkCoverageDeltaList* deltas, const SkIRect& clip,
-                                   bool isEvenOdd, bool isInverse, bool isConvex) {
-    int         runSize = clip.width() + 1; // +1 so we can set runs[clip.width()] = 0
-    void*       storage = this->allocBlitMemory(runSize * (sizeof(int16_t) + sizeof(SkAlpha)));
-    int16_t*    runs    = reinterpret_cast<int16_t*>(storage);
-    SkAlpha*    alphas  = reinterpret_cast<SkAlpha*>(runs + runSize);
-    runs[clip.width()]  = 0; // we must set the last run to 0 so blitAntiH can stop there
-
-    bool canUseMask = !deltas->forceRLE() &&
-                      SkCoverageDeltaMask::CanHandle(SkIRect::MakeLTRB(0, 0, clip.width(), 1));
-    const SkAntiRect& antiRect = deltas->getAntiRect();
-
-    // Only access rows within our clip. Otherwise, we'll have data race in the threaded backend.
-    int top = SkTMax(deltas->top(), clip.fTop);
-    int bottom = SkTMin(deltas->bottom(), clip.fBottom);
-    for(int y = top; y < bottom; ++y) {
-        // If antiRect is non-empty and we're in it, blit it and skip to the bottom
-        if (y >= antiRect.fY && y < antiRect.fY + antiRect.fHeight) {
-            // Clip the antiRect because of possible tilings (e.g., the threaded backend)
-            int leftOverClip = clip.fLeft - antiRect.fX;
-            int rightOverClip = antiRect.fX + antiRect.fWidth - clip.fRight;
-            int topOverClip = clip.fTop - antiRect.fY;
-            int botOverClip = antiRect.fY + antiRect.fHeight - clip.fBottom;
-
-            int rectX = antiRect.fX;
-            int rectY = antiRect.fY;
-            int width = antiRect.fWidth;
-            int height = antiRect.fHeight;
-            SkAlpha leftAlpha = antiRect.fLeftAlpha;
-            SkAlpha rightAlpha = antiRect.fRightAlpha;
-
-            if (leftOverClip > 0) {
-                rectX = clip.fLeft;
-                width -= leftOverClip;
-                leftAlpha = 0xFF;
-            }
-            if (rightOverClip > 0) {
-                width -= rightOverClip;
-                rightAlpha = 0xFF;
-            }
-            if (topOverClip > 0) {
-                rectY = clip.fTop;
-                height -= topOverClip;
-            }
-            if (botOverClip > 0) {
-                height -= botOverClip;
-            }
-
-            if (width >= 0) {
-                this->blitAntiRect(rectX, rectY, width, height, leftAlpha, rightAlpha);
-            }
-            y += antiRect.fHeight - 1; // -1 because ++y in the for loop
-            continue;
-        }
-
-        // If there are too many deltas, sorting will be slow. Using a mask is much faster.
-        // This is such an important optimization that will bring ~2x speedup for benches like
-        // path_fill_small_long_line and path_stroke_small_sawtooth.
-        if (canUseMask && !deltas->sorted(y) && deltas->count(y) << 3 >= clip.width()) {
-            // Note that deltas->left()/right() may be different than clip.fLeft/fRight because in
-            // the threaded backend, deltas are generated in the initFn with full clip, while
-            // blitCoverageDeltas is called in drawFn with a subclip. For inverse fill, the clip
-            // might be wider than deltas' bounds (which is clippedIR).
-            SkIRect rowIR = SkIRect::MakeLTRB(SkTMin(clip.fLeft, deltas->left()), y,
-                                              SkTMax(clip.fRight, deltas->right()), y + 1);
-            SkSTArenaAlloc<SkCoverageDeltaMask::MAX_SIZE> alloc;
-            SkCoverageDeltaMask mask(&alloc, rowIR);
-            for(int i = 0; i < deltas->count(y); ++i) {
-                const SkCoverageDelta& delta = deltas->getDelta(y, i);
-                mask.addDelta(delta.fX, y, delta.fDelta);
-            }
-            mask.convertCoverageToAlpha(isEvenOdd, isInverse, isConvex);
-            this->blitMask(mask.prepareSkMask(), rowIR);
-            continue;
-        }
-
-        // The normal flow of blitting deltas starts from here. First sort deltas.
-        deltas->sort(y);
-
-        int     i = 0;              // init delta index to 0
-        int     lastX = clip.fLeft; // init x to clip.fLeft
-        SkFixed coverage = 0;       // init coverage to 0
-
-        // skip deltas with x less than clip.fLeft; they may be:
-        //   1. precision errors
-        //   2. deltas generated during init-once phase (threaded backend) that has a wider
-        //      clip than the final tile clip.
-        for(; i < deltas->count(y) && deltas->getDelta(y, i).fX < clip.fLeft; ++i) {
-            coverage += deltas->getDelta(y, i).fDelta;
-        }
-        for(; i < deltas->count(y) && deltas->getDelta(y, i).fX < clip.fRight; ++i) {
-            const SkCoverageDelta& delta = deltas->getDelta(y, i);
-            SkASSERT(delta.fX >= lastX);    // delta must be x sorted
-            if (delta.fX > lastX) {         // we have proceeded to a new x (different from lastX)
-                SkAlpha alpha = isConvex ? ConvexCoverageToAlpha(coverage, isInverse)
-                                         : CoverageToAlpha(coverage, isEvenOdd, isInverse);
-                alphas[lastX - clip.fLeft]  = alpha;            // set alpha at lastX
-                runs[lastX - clip.fLeft]    = delta.fX - lastX; // set the run length
-                lastX                       = delta.fX;         // now set lastX to current x
-            }
-            coverage += delta.fDelta; // cumulate coverage with the current delta
-        }
-
-        // Set the alpha and run length from the right-most delta to the right clip boundary
-        SkAlpha alpha = isConvex ? ConvexCoverageToAlpha(coverage, isInverse)
-                                 : CoverageToAlpha(coverage, isEvenOdd, isInverse);
-        alphas[lastX - clip.fLeft]  = alpha;
-        runs[lastX - clip.fLeft]    = clip.fRight - lastX;
-
-        this->blitAntiH(clip.fLeft, y, alphas, runs); // finally blit the current row
-    }
-}
-
 void SkBlitter::blitV(int x, int y, int height, SkAlpha alpha) {
     if (alpha == 255) {
         this->blitRect(x, y, 1, height);
@@ -764,8 +652,7 @@
     const SkMaskFilterBase* mf = as_MFB(paint.getMaskFilter());
 
     // The legacy blitters cannot handle any of these complex features (anymore).
-    if (device.colorSpace()                                ||
-        device.alphaType() == kUnpremul_SkAlphaType        ||
+    if (device.alphaType() == kUnpremul_SkAlphaType        ||
         matrix.hasPerspective()                            ||
         paint.getColorFilter()                             ||
         paint.getBlendMode() > SkBlendMode::kLastCoeffMode ||
@@ -780,6 +667,15 @@
         return true;
     }
 
+    auto cs = device.colorSpace();
+    // We check (indirectly via makeContext()) later on if the shader can handle the colorspace
+    // in legacy mode, so here we just focus on if a single color needs raster-pipeline.
+    if (cs && !paint.getShader()) {
+        if (!paint.getColor4f().fitsInBytes() || !cs->isSRGB()) {
+            return true;
+        }
+    }
+
     // Only kN32 and 565 are handled by legacy blitters now, 565 mostly just for Android.
     return device.colorType() != kN32_SkColorType
         && device.colorType() != kRGB_565_SkColorType;
@@ -843,7 +739,6 @@
     }
 
     // Everything but legacy kN32_SkColorType and kRGB_565_SkColorType should already be handled.
-    SkASSERT(!device.colorSpace());
     SkASSERT(device.colorType() == kN32_SkColorType ||
              device.colorType() == kRGB_565_SkColorType);
 
diff --git a/src/core/SkBlitter.h b/src/core/SkBlitter.h
index 1a2c5f3..9b15cdc 100644
--- a/src/core/SkBlitter.h
+++ b/src/core/SkBlitter.h
@@ -10,7 +10,6 @@
 
 #include "SkAutoMalloc.h"
 #include "SkColor.h"
-#include "SkCoverageDelta.h"
 #include "SkImagePriv.h"
 #include "SkRect.h"
 #include "SkRegion.h"
@@ -33,12 +32,6 @@
 public:
     virtual ~SkBlitter();
 
-    // The actual blitter may speedup the process by rewriting this in a more efficient way.
-    // For example, one may avoid some virtual blitAntiH calls by directly calling
-    // SkBlitRow::Color32.
-    virtual void blitCoverageDeltas(SkCoverageDeltaList* deltas, const SkIRect& clip,
-                                    bool isEvenOdd, bool isInverse, bool isConvex);
-
     /// Blit a horizontal run of one or more pixels.
     virtual void blitH(int x, int y, int width) = 0;
 
diff --git a/src/core/SkBlitter_Sprite.cpp b/src/core/SkBlitter_Sprite.cpp
index a730ca1..f980b2d 100644
--- a/src/core/SkBlitter_Sprite.cpp
+++ b/src/core/SkBlitter_Sprite.cpp
@@ -56,12 +56,12 @@
 class SkSpriteBlitter_Memcpy final : public SkSpriteBlitter {
 public:
     static bool Supports(const SkPixmap& dst, const SkPixmap& src, const SkPaint& paint) {
+        // the caller has already inspected the colorspace on src and dst
+        SkASSERT(!SkColorSpaceXformSteps::Required(src.colorSpace(), dst.colorSpace()));
+
         if (dst.colorType() != src.colorType()) {
             return false;
         }
-        if (!SkColorSpace::Equals(dst.colorSpace(), src.colorSpace())) {
-            return false;
-        }
         if (paint.getMaskFilter() || paint.getColorFilter() || paint.getImageFilter()) {
             return false;
         }
@@ -184,22 +184,24 @@
 
     SkSpriteBlitter* blitter = nullptr;
 
-    if (!blitter && SkSpriteBlitter_Memcpy::Supports(dst, source, paint)) {
-        blitter = allocator->make<SkSpriteBlitter_Memcpy>(source);
-    }
-    if (!blitter && !dst.colorSpace()) {
-        switch (dst.colorType()) {
-            case kN32_SkColorType:
-                blitter = SkSpriteBlitter::ChooseL32(source, paint, allocator);
-                break;
-            case kRGB_565_SkColorType:
-                blitter = SkSpriteBlitter::ChooseL565(source, paint, allocator);
-                break;
-            case kAlpha_8_SkColorType:
-                blitter = SkSpriteBlitter::ChooseLA8(source, paint, allocator);
-                break;
-            default:
-                break;
+    if (!SkColorSpaceXformSteps::Required(source.colorSpace(), dst.colorSpace())) {
+        if (!blitter && SkSpriteBlitter_Memcpy::Supports(dst, source, paint)) {
+            blitter = allocator->make<SkSpriteBlitter_Memcpy>(source);
+        }
+        if (!blitter) {
+            switch (dst.colorType()) {
+                case kN32_SkColorType:
+                    blitter = SkSpriteBlitter::ChooseL32(source, paint, allocator);
+                    break;
+                case kRGB_565_SkColorType:
+                    blitter = SkSpriteBlitter::ChooseL565(source, paint, allocator);
+                    break;
+                case kAlpha_8_SkColorType:
+                    blitter = SkSpriteBlitter::ChooseLA8(source, paint, allocator);
+                    break;
+                default:
+                    break;
+            }
         }
     }
     if (!blitter && !paint.getMaskFilter()) {
diff --git a/src/core/SkBlurMF.cpp b/src/core/SkBlurMF.cpp
index b3a1a6a..c4fe485 100644
--- a/src/core/SkBlurMF.cpp
+++ b/src/core/SkBlurMF.cpp
@@ -20,20 +20,20 @@
 
 #if SK_SUPPORT_GPU
 #include "GrClip.h"
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrFragmentProcessor.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrResourceProvider.h"
 #include "GrShaderCaps.h"
 #include "GrShape.h"
 #include "GrStyle.h"
 #include "GrTextureProxy.h"
-#include "effects/GrCircleBlurFragmentProcessor.h"
-#include "effects/GrRectBlurEffect.h"
-#include "effects/GrRRectBlurEffect.h"
-#include "effects/GrSimpleTextureEffect.h"
 #include "effects/GrTextureDomain.h"
+#include "effects/generated/GrCircleBlurFragmentProcessor.h"
+#include "effects/generated/GrSimpleTextureEffect.h"
+#include "effects/generated/GrRectBlurEffect.h"
+#include "effects/generated/GrRRectBlurEffect.h"
 #include "glsl/GrGLSLFragmentProcessor.h"
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
 #include "glsl/GrGLSLProgramDataManager.h"
@@ -55,13 +55,13 @@
                           const SkIRect& clipBounds,
                           const SkMatrix& ctm,
                           SkIRect* maskRect) const override;
-    bool directFilterMaskGPU(GrContext*,
+    bool directFilterMaskGPU(GrRecordingContext*,
                              GrRenderTargetContext* renderTargetContext,
                              GrPaint&&,
                              const GrClip&,
                              const SkMatrix& viewMatrix,
                              const GrShape& shape) const override;
-    sk_sp<GrTextureProxy> filterMaskGPU(GrContext*,
+    sk_sp<GrTextureProxy> filterMaskGPU(GrRecordingContext*,
                                         sk_sp<GrTextureProxy> srcProxy,
                                         const SkMatrix& ctm,
                                         const SkIRect& maskRect) const override;
@@ -716,7 +716,7 @@
 
 #if SK_SUPPORT_GPU
 
-bool SkBlurMaskFilterImpl::directFilterMaskGPU(GrContext* context,
+bool SkBlurMaskFilterImpl::directFilterMaskGPU(GrRecordingContext* context,
                                                GrRenderTargetContext* renderTargetContext,
                                                GrPaint&& paint,
                                                const GrClip& clip,
@@ -868,7 +868,7 @@
     return true;
 }
 
-sk_sp<GrTextureProxy> SkBlurMaskFilterImpl::filterMaskGPU(GrContext* context,
+sk_sp<GrTextureProxy> SkBlurMaskFilterImpl::filterMaskGPU(GrRecordingContext* context,
                                                           sk_sp<GrTextureProxy> srcProxy,
                                                           const SkMatrix& ctm,
                                                           const SkIRect& maskRect) const {
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index bf9b690..0fddd6b 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -951,11 +951,15 @@
     }
 }
 
+// This is shared by all backends, but contains raster-specific thoughts. Can we defer to the
+// device to perform this?
 static SkImageInfo make_layer_info(const SkImageInfo& prev, int w, int h, const SkPaint* paint) {
     // Need to force L32 for now if we have an image filter.
-    // If filters ever support other colortypes, e.g. sRGB or F16, we can remove this check.
+    // If filters ever support other colortypes, e.g. F16, we can modify this check.
     if (paint && paint->getImageFilter()) {
-        return SkImageInfo::MakeN32Premul(w, h);
+        // TODO: can we query the imagefilter, to see if it can handle floats (so we don't always
+        //       use N32 when the layer itself was float)?
+        return SkImageInfo::MakeN32Premul(w, h, prev.refColorSpace());
     }
 
     SkColorType ct = prev.colorType();
@@ -973,6 +977,12 @@
     const SkPaint* paint = rec.fPaint;
     SaveLayerFlags saveLayerFlags = rec.fSaveLayerFlags;
 
+    // If we have a backdrop filter, then we must apply it to the entire layer (clip-bounds)
+    // regardless of any hint-rect from the caller. skbug.com/8783
+    if (rec.fBackdrop) {
+        bounds = nullptr;
+    }
+
     SkLazyPaint lazyP;
     SkImageFilter* imageFilter = paint ? paint->getImageFilter() : nullptr;
     SkMatrix stashedMatrix = fMCRec->fMatrix;
@@ -1270,6 +1280,14 @@
 
 /////////////////////////////////////////////////////////////////////////////
 
+// In our current design/features, we should never have a layer (src) in a different colorspace
+// than its parent (dst), so we assert that here. This is called out from other asserts, in case
+// we add some feature in the future to allow a given layer/imagefilter to operate in a specific
+// colorspace.
+static void check_drawdevice_colorspaces(SkColorSpace* src, SkColorSpace* dst) {
+    SkASSERT(src == dst);
+}
+
 void SkCanvas::internalDrawDevice(SkBaseDevice* srcDev, int x, int y, const SkPaint* paint,
                                   SkImage* clipImage, const SkMatrix& clipMatrix) {
     SkPaint tmp;
@@ -1281,12 +1299,16 @@
 
     while (iter.next()) {
         SkBaseDevice* dstDev = iter.fDevice;
+        check_drawdevice_colorspaces(dstDev->imageInfo().colorSpace(),
+                                     srcDev->imageInfo().colorSpace());
         paint = &looper.paint();
         SkImageFilter* filter = paint->getImageFilter();
         SkIPoint pos = { x - iter.getX(), y - iter.getY() };
         if (filter || clipImage) {
             sk_sp<SkSpecialImage> specialImage = srcDev->snapSpecial();
             if (specialImage) {
+                check_drawdevice_colorspaces(dstDev->imageInfo().colorSpace(),
+                                             specialImage->getColorSpace());
                 dstDev->drawSpecial(specialImage.get(), pos.x(), pos.y(), *paint,
                                     clipImage, clipMatrix);
             }
@@ -1854,23 +1876,6 @@
     }
 }
 
-void SkCanvas::experimental_DrawImageSetV1(const ImageSetEntry imageSet[], int cnt,
-                                           SkFilterQuality filterQuality, SkBlendMode mode) {
-    TRACE_EVENT0("skia", TRACE_FUNC);
-    RETURN_ON_NULL(imageSet);
-    RETURN_ON_FALSE(cnt);
-
-    this->onDrawImageSet(imageSet, cnt, filterQuality, mode);
-}
-
-void SkCanvas::experimental_DrawEdgeAARectV1(const SkRect& r, QuadAAFlags edgeAA, SkColor color,
-                                             SkBlendMode mode) {
-    TRACE_EVENT0("skia", TRACE_FUNC);
-    // To avoid redundant logic in our culling code and various backends, we always sort rects
-    // before passing them along.
-    this->onDrawEdgeAARect(r.makeSorted(), edgeAA, color, mode);
-}
-
 void SkCanvas::drawBitmap(const SkBitmap& bitmap, SkScalar dx, SkScalar dy, const SkPaint* paint) {
     TRACE_EVENT0("skia", TRACE_FUNC);
     if (bitmap.drawsNothing()) {
@@ -1990,6 +1995,22 @@
     LOOPER_END
 }
 
+void SkCanvas::experimental_DrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
+                                           QuadAAFlags aaFlags, SkColor color, SkBlendMode mode) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    // Make sure the rect is sorted before passing it along
+    this->onDrawEdgeAAQuad(rect.makeSorted(), clip, aaFlags, color, mode);
+}
+
+void SkCanvas::experimental_DrawEdgeAAImageSet(const ImageSetEntry imageSet[], int cnt,
+                                               const SkPoint dstClips[],
+                                               const SkMatrix preViewMatrices[],
+                                               const SkPaint* paint,
+                                               SrcRectConstraint constraint) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    this->onDrawEdgeAAImageSet(imageSet, cnt, dstClips, preViewMatrices, paint, constraint);
+}
+
 //////////////////////////////////////////////////////////////////////////////
 //  These are the virtual drawing methods
 //////////////////////////////////////////////////////////////////////////////
@@ -2081,20 +2102,6 @@
     }
 }
 
-void SkCanvas::onDrawEdgeAARect(const SkRect& r, QuadAAFlags edgeAA, SkColor color,
-                                SkBlendMode mode) {
-    SkASSERT(r.isSorted());
-
-    SkPaint paint;
-    LOOPER_BEGIN(paint, nullptr)
-
-    while (iter.next()) {
-        iter.fDevice->drawEdgeAARect(r, edgeAA, color, mode);
-    }
-
-    LOOPER_END
-}
-
 void SkCanvas::onDrawRegion(const SkRegion& region, const SkPaint& paint) {
     SkRect regionRect = SkRect::Make(region.getBounds());
     if (paint.canComputeFastBounds()) {
@@ -2301,7 +2308,9 @@
                                       SkScalarRoundToInt(pt.fY), pnt,
                                       nullptr, SkMatrix::I());
         } else {
-            iter.fDevice->drawImage(image, x, y, pnt);
+            iter.fDevice->drawImageRect(
+                    image, nullptr, SkRect::MakeXYWH(x, y, image->width(), image->height()), pnt,
+                    kStrict_SrcRectConstraint);
         }
     }
 
@@ -2377,7 +2386,9 @@
                                       SkScalarRoundToInt(pt.fY), pnt,
                                       nullptr, SkMatrix::I());
         } else {
-            iter.fDevice->drawBitmap(bitmap, x, y, looper.paint());
+            SkRect fullImage = SkRect::MakeWH(bitmap.width(), bitmap.height());
+            iter.fDevice->drawBitmapRect(bitmap, &fullImage, fullImage.makeOffset(x, y), pnt,
+                                         kStrict_SrcRectConstraint);
         }
     }
 
@@ -2486,16 +2497,6 @@
     LOOPER_END
 }
 
-void SkCanvas::onDrawImageSet(const ImageSetEntry imageSet[], int count,
-                              SkFilterQuality filterQuality, SkBlendMode mode) {
-    SkPaint paint;
-    LOOPER_BEGIN(paint, nullptr)
-    while (iter.next()) {
-        iter.fDevice->drawImageSet(imageSet, count, filterQuality, mode);
-    }
-    LOOPER_END
-}
-
 void SkCanvas::onDrawBitmapLattice(const SkBitmap& bitmap, const Lattice& lattice,
                                    const SkRect& dst, const SkPaint* paint) {
     SkPaint realPaint;
@@ -2663,6 +2664,39 @@
     LOOPER_END
 }
 
+void SkCanvas::onDrawEdgeAAQuad(const SkRect& r, const SkPoint clip[4],  QuadAAFlags edgeAA,
+                                SkColor color, SkBlendMode mode) {
+    SkASSERT(r.isSorted());
+
+    // If this used a paint, it would be a filled color with blend mode, which does not
+    // need to use an autodraw loop, so use SkDrawIter directly.
+    if (this->quickReject(r)) {
+        return;
+    }
+
+    this->predrawNotify();
+    SkDrawIter iter(this);
+    while(iter.next()) {
+        iter.fDevice->drawEdgeAAQuad(r, clip, edgeAA, color, mode);
+    }
+}
+
+void SkCanvas::onDrawEdgeAAImageSet(const ImageSetEntry imageSet[], int count,
+                                    const SkPoint dstClips[], const SkMatrix preViewMatrices[],
+                                    const SkPaint* paint, SrcRectConstraint constraint) {
+    SkPaint realPaint;
+    init_image_paint(&realPaint, paint);
+
+    // Looper is used when there are image filters, which drawEdgeAAImageSet needs to support
+    // for Chromium's RenderPassDrawQuads' filters.
+    LOOPER_BEGIN(realPaint, nullptr)
+    while (iter.next()) {
+        iter.fDevice->drawEdgeAAImageSet(
+                imageSet, count, dstClips, preViewMatrices, looper.paint(), constraint);
+    }
+    LOOPER_END
+}
+
 //////////////////////////////////////////////////////////////////////////////
 // These methods are NOT virtual, and therefore must call back into virtual
 // methods, rather than actually drawing themselves.
@@ -2815,6 +2849,32 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
+SkCanvas::ImageSetEntry::ImageSetEntry() = default;
+SkCanvas::ImageSetEntry::~ImageSetEntry() = default;
+SkCanvas::ImageSetEntry::ImageSetEntry(const ImageSetEntry&) = default;
+SkCanvas::ImageSetEntry& SkCanvas::ImageSetEntry::operator=(const ImageSetEntry&) = default;
+
+SkCanvas::ImageSetEntry::ImageSetEntry(sk_sp<const SkImage> image, const SkRect& srcRect,
+                                       const SkRect& dstRect, int matrixIndex, float alpha,
+                                       unsigned aaFlags, bool hasClip)
+                : fImage(std::move(image))
+                , fSrcRect(srcRect)
+                , fDstRect(dstRect)
+                , fMatrixIndex(matrixIndex)
+                , fAlpha(alpha)
+                , fAAFlags(aaFlags)
+                , fHasClip(hasClip) {}
+
+SkCanvas::ImageSetEntry::ImageSetEntry(sk_sp<const SkImage> image, const SkRect& srcRect,
+                                       const SkRect& dstRect, float alpha, unsigned aaFlags)
+                : fImage(std::move(image))
+                , fSrcRect(srcRect)
+                , fDstRect(dstRect)
+                , fAlpha(alpha)
+                , fAAFlags(aaFlags) {}
+
+///////////////////////////////////////////////////////////////////////////////
+
 std::unique_ptr<SkCanvas> SkCanvas::MakeRasterDirect(const SkImageInfo& info, void* pixels,
                                                      size_t rowBytes, const SkSurfaceProps* props) {
     if (!SkSurfaceValidateRasterInfo(info, rowBytes)) {
diff --git a/src/core/SkCanvasPriv.cpp b/src/core/SkCanvasPriv.cpp
index 35c9866..5249687 100644
--- a/src/core/SkCanvasPriv.cpp
+++ b/src/core/SkCanvasPriv.cpp
@@ -83,3 +83,17 @@
     buffer.writePad32(storage.get(), size);
 }
 
+void SkCanvasPriv::GetDstClipAndMatrixCounts(const SkCanvas::ImageSetEntry set[], int count,
+                                             int* totalDstClipCount, int* totalMatrixCount) {
+    int dstClipCount = 0;
+    int maxMatrixIndex = -1;
+    for (int i = 0; i < count; ++i) {
+        dstClipCount += 4 * set[i].fHasClip;
+        if (set[i].fMatrixIndex > maxMatrixIndex) {
+            maxMatrixIndex = set[i].fMatrixIndex;
+        }
+    }
+
+    *totalDstClipCount = dstClipCount;
+    *totalMatrixCount = maxMatrixIndex + 1;
+}
diff --git a/src/core/SkCanvasPriv.h b/src/core/SkCanvasPriv.h
index cb25401..e06e06a 100644
--- a/src/core/SkCanvasPriv.h
+++ b/src/core/SkCanvasPriv.h
@@ -43,6 +43,12 @@
     static int SaveBehind(SkCanvas* canvas, const SkRect* subset) {
         return canvas->only_axis_aligned_saveBehind(subset);
     }
+
+    // The experimental_DrawEdgeAAImageSet API accepts separate dstClips and preViewMatrices arrays,
+    // where entries refer into them, but no explicit size is provided. Given a set of entries,
+    // computes the minimum length for these arrays that would provide index access errors.
+    static void GetDstClipAndMatrixCounts(const SkCanvas::ImageSetEntry set[], int count,
+                                          int* totalDstClipCount, int* totalMatrixCount);
 };
 
 #endif
diff --git a/src/core/SkColorFilter.cpp b/src/core/SkColorFilter.cpp
index 2089c08..08312b6 100644
--- a/src/core/SkColorFilter.cpp
+++ b/src/core/SkColorFilter.cpp
@@ -7,9 +7,9 @@
 
 #include "SkArenaAlloc.h"
 #include "SkColorFilter.h"
+#include "SkColorFilterPriv.h"
 #include "SkColorSpacePriv.h"
 #include "SkColorSpaceXformSteps.h"
-#include "SkColorSpaceXformer.h"
 #include "SkNx.h"
 #include "SkRasterPipeline.h"
 #include "SkReadBuffer.h"
@@ -21,6 +21,7 @@
 
 #if SK_SUPPORT_GPU
 #include "GrFragmentProcessor.h"
+#include "effects/generated/GrMixerEffect.h"
 #endif
 
 bool SkColorFilter::asColorMode(SkColor*, SkBlendMode*) const {
@@ -31,36 +32,20 @@
     return false;
 }
 
-bool SkColorFilter::asComponentTable(SkBitmap*) const {
-    return false;
-}
-
 #if SK_SUPPORT_GPU
 std::unique_ptr<GrFragmentProcessor> SkColorFilter::asFragmentProcessor(
-        GrContext*, const GrColorSpaceInfo&) const {
+        GrRecordingContext*, const GrColorSpaceInfo&) const {
     return nullptr;
 }
 #endif
 
-void SkColorFilter::appendStages(SkRasterPipeline* p,
-                                 SkColorSpace* dstCS,
-                                 SkArenaAlloc* alloc,
-                                 bool shaderIsOpaque) const {
-    this->onAppendStages(p, dstCS, alloc, shaderIsOpaque);
+bool SkColorFilter::appendStages(const SkStageRec& rec, bool shaderIsOpaque) const {
+    return this->onAppendStages(rec, shaderIsOpaque);
 }
 
 SkColor SkColorFilter::filterColor(SkColor c) const {
-    const float inv255 = 1.0f / 255;
-    SkColor4f c4 = this->filterColor4f({
-        SkColorGetR(c) * inv255,
-        SkColorGetG(c) * inv255,
-        SkColorGetB(c) * inv255,
-        SkColorGetA(c) * inv255,
-    }, nullptr);
-    return SkColorSetARGB(sk_float_round2int(c4.fA*255),
-                          sk_float_round2int(c4.fR*255),
-                          sk_float_round2int(c4.fG*255),
-                          sk_float_round2int(c4.fB*255));
+    return this->filterColor4f(SkColor4f::FromColor(c), nullptr)
+        .toSkColor();
 }
 
 #include "SkRasterPipeline.h"
@@ -71,7 +56,12 @@
     SkRasterPipeline    pipeline(&alloc);
 
     pipeline.append_constant_color(&alloc, src.vec());
-    this->onAppendStages(&pipeline, colorSpace, &alloc, c.fA == 1);
+
+    SkPaint dummyPaint;
+    SkStageRec rec = {
+        &pipeline, &alloc, kRGBA_F32_SkColorType, colorSpace, dummyPaint, nullptr, SkMatrix::I()
+    };
+    this->onAppendStages(rec, c.fA == 1);
     SkRasterPipeline_MemoryCtx dstPtr = { &dst, 0 };
     pipeline.append(SkRasterPipeline::store_f32, &dstPtr);
     pipeline.run(0,0, 1,1);
@@ -98,19 +88,18 @@
         return fOuter->getFlags() & fInner->getFlags();
     }
 
-    void onAppendStages(SkRasterPipeline* p, SkColorSpace* dst, SkArenaAlloc* scratch,
-                        bool shaderIsOpaque) const override {
+    bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override {
         bool innerIsOpaque = shaderIsOpaque;
         if (!(fInner->getFlags() & kAlphaUnchanged_Flag)) {
             innerIsOpaque = false;
         }
-        fInner->appendStages(p, dst, scratch, shaderIsOpaque);
-        fOuter->appendStages(p, dst, scratch, innerIsOpaque);
+        return fInner->appendStages(rec, shaderIsOpaque) &&
+               fOuter->appendStages(rec, innerIsOpaque);
     }
 
 #if SK_SUPPORT_GPU
     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
-            GrContext* context, const GrColorSpaceInfo& dstColorSpaceInfo) const override {
+            GrRecordingContext* context, const GrColorSpaceInfo& dstColorSpaceInfo) const override {
         auto innerFP = fInner->asFragmentProcessor(context, dstColorSpaceInfo);
         auto outerFP = fOuter->asFragmentProcessor(context, dstColorSpaceInfo);
         if (!innerFP || !outerFP) {
@@ -144,15 +133,6 @@
         return fComposedFilterCount;
     }
 
-    sk_sp<SkColorFilter> onMakeColorSpace(SkColorSpaceXformer* xformer) const override {
-        auto outer = xformer->apply(fOuter.get());
-        auto inner = xformer->apply(fInner.get());
-        if (outer != fOuter || inner != fInner) {
-            return outer->makeComposed(inner);
-        }
-        return this->INHERITED::onMakeColorSpace(xformer);
-    }
-
     sk_sp<SkColorFilter> fOuter;
     sk_sp<SkColorFilter> fInner;
     const int            fComposedFilterCount;
@@ -174,12 +154,6 @@
         return sk_ref_sp(this);
     }
 
-    // Give the subclass a shot at a more optimal composition...
-    auto composition = this->onMakeComposed(inner);
-    if (composition) {
-        return composition;
-    }
-
     int count = inner->privateComposedFilterCount() + this->privateComposedFilterCount();
     if (count > SK_MAX_COMPOSE_COLORFILTER_COUNT) {
         return nullptr;
@@ -212,7 +186,7 @@
 
 #if SK_SUPPORT_GPU
     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
-            GrContext*, const GrColorSpaceInfo&) const override {
+            GrRecordingContext*, const GrColorSpaceInfo&) const override {
         // wish our caller would let us know if our input was opaque...
         GrSRGBEffect::Alpha alpha = GrSRGBEffect::Alpha::kPremul;
         switch (fDir) {
@@ -225,19 +199,19 @@
     }
 #endif
 
-    void onAppendStages(SkRasterPipeline* p, SkColorSpace*, SkArenaAlloc* alloc,
-                        bool shaderIsOpaque) const override {
+    bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override {
         if (!shaderIsOpaque) {
-            p->append(SkRasterPipeline::unpremul);
+            rec.fPipeline->append(SkRasterPipeline::unpremul);
         }
 
         // TODO: is it valuable to thread this through appendStages()?
         bool shaderIsNormalized = false;
-        fSteps.apply(p, shaderIsNormalized);
+        fSteps.apply(rec.fPipeline, shaderIsNormalized);
 
         if (!shaderIsOpaque) {
-            p->append(SkRasterPipeline::premul);
+            rec.fPipeline->append(SkRasterPipeline::premul);
         }
+        return true;
     }
 
 protected:
@@ -269,20 +243,272 @@
     return sk_ref_sp(gSingleton);
 }
 
+#ifdef SK_SUPPORT_LEGACY_COLORFILTER_FACTORIES
 sk_sp<SkColorFilter> SkColorFilter::MakeLinearToSRGBGamma() {
-    return MakeSRGBGammaCF<SkSRGBGammaColorFilter::Direction::kLinearToSRGB>();
+    return SkColorFilters::LinearToSRGBGamma();
 }
 
 sk_sp<SkColorFilter> SkColorFilter::MakeSRGBToLinearGamma() {
+    return SkColorFilters::SRGBToLinearGamma();
+}
+#endif
+
+sk_sp<SkColorFilter> SkColorFilters::LinearToSRGBGamma() {
+    return MakeSRGBGammaCF<SkSRGBGammaColorFilter::Direction::kLinearToSRGB>();
+}
+
+sk_sp<SkColorFilter> SkColorFilters::SRGBToLinearGamma() {
     return MakeSRGBGammaCF<SkSRGBGammaColorFilter::Direction::kSRGBToLinear>();
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
+class SkMixerColorFilter : public SkColorFilter {
+public:
+    SkMixerColorFilter(sk_sp<SkColorFilter> cf0, sk_sp<SkColorFilter> cf1, float weight)
+        : fCF0(std::move(cf0)), fCF1(std::move(cf1)), fWeight(weight)
+    {
+        SkASSERT(fCF0);
+        SkASSERT(fWeight >= 0 && fWeight <= 1);
+    }
+
+    uint32_t getFlags() const override {
+        uint32_t f0 = fCF0->getFlags();
+        uint32_t f1 = fCF1 ? fCF1->getFlags() : ~0U;
+        return f0 & f1;
+    }
+
+    bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override {
+        // want cf0 * (1 - w) + cf1 * w == lerp(w)
+        // which means
+        //      dr,dg,db,da <-- cf0
+        //      r,g,b,a     <-- cf1
+        struct State {
+            float     orig_rgba[4 * SkRasterPipeline_kMaxStride];
+            float filtered_rgba[4 * SkRasterPipeline_kMaxStride];
+        };
+        auto state = rec.fAlloc->make<State>();
+        SkRasterPipeline* p = rec.fPipeline;
+
+        p->append(SkRasterPipeline::store_src, state->orig_rgba);
+        if (!fCF1) {
+            fCF0->appendStages(rec, shaderIsOpaque);
+            p->append(SkRasterPipeline::move_src_dst);
+            p->append(SkRasterPipeline::load_src, state->orig_rgba);
+        } else {
+            fCF0->appendStages(rec, shaderIsOpaque);
+            p->append(SkRasterPipeline::store_src, state->filtered_rgba);
+            p->append(SkRasterPipeline::load_src, state->orig_rgba);
+            fCF1->appendStages(rec, shaderIsOpaque);
+            p->append(SkRasterPipeline::load_dst, state->filtered_rgba);
+        }
+        float* storage = rec.fAlloc->make<float>(fWeight);
+        p->append(SkRasterPipeline::lerp_1_float, storage);
+        return true;
+    }
+
+#if SK_SUPPORT_GPU
+    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
+            GrRecordingContext* context, const GrColorSpaceInfo& dstColorSpaceInfo) const override {
+        return GrMixerEffect::Make(
+                fCF0->asFragmentProcessor(context, dstColorSpaceInfo),
+                fCF1 ? fCF1->asFragmentProcessor(context, dstColorSpaceInfo) : nullptr,
+                fWeight);
+    }
+#endif
+
+protected:
+    void flatten(SkWriteBuffer& buffer) const override {
+        buffer.writeFlattenable(fCF0.get());
+        buffer.writeFlattenable(fCF1.get());
+        buffer.writeScalar(fWeight);
+    }
+
+private:
+    SK_FLATTENABLE_HOOKS(SkMixerColorFilter)
+
+    sk_sp<SkColorFilter> fCF0;
+    sk_sp<SkColorFilter> fCF1;
+    const float          fWeight;
+
+    friend class SkColorFilter;
+
+    typedef SkColorFilter INHERITED;
+};
+
+sk_sp<SkFlattenable> SkMixerColorFilter::CreateProc(SkReadBuffer& buffer) {
+    sk_sp<SkColorFilter> cf0(buffer.readColorFilter());
+    sk_sp<SkColorFilter> cf1(buffer.readColorFilter());
+    const float weight = buffer.readScalar();
+    return SkColorFilters::Lerp(weight, std::move(cf0), std::move(cf1));
+}
+
+#ifdef SK_SUPPORT_LEGACY_COLORFILTER_FACTORIES
+sk_sp<SkColorFilter> SkColorFilter::MakeLerp(sk_sp<SkColorFilter> cf0,
+                                             sk_sp<SkColorFilter> cf1,
+                                             float weight) {
+    return SkColorFilters::Lerp(weight, std::move(cf0), std::move(cf1));
+}
+#endif
+
+sk_sp<SkColorFilter> SkColorFilters::Lerp(float weight, sk_sp<SkColorFilter> cf0,
+                                                        sk_sp<SkColorFilter> cf1) {
+    if (!cf0 && !cf1) {
+        return nullptr;
+    }
+    if (SkScalarIsNaN(weight)) {
+        return nullptr;
+    }
+
+    if (cf0 == cf1) {
+        return cf0; // or cf1
+    }
+
+    if (weight <= 0) {
+        return cf0;
+    }
+    if (weight >= 1) {
+        return cf1;
+    }
+
+    return sk_sp<SkColorFilter>(cf0
+            ? new SkMixerColorFilter(std::move(cf0), std::move(cf1), weight)
+            : new SkMixerColorFilter(std::move(cf1), nullptr, 1 - weight));
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+#include "effects/GrSkSLFP.h"
+#include "GrRecordingContext.h"
+#include "SkSLByteCode.h"
+#include "SkSLInterpreter.h"
+
+class SkRuntimeColorFilter : public SkColorFilter {
+public:
+    SkRuntimeColorFilter(int index, SkString sksl, sk_sp<SkData> inputs,
+                         void (*cpuFunction)(float[4], const void*))
+        : fIndex(index)
+        , fSkSL(std::move(sksl))
+        , fInputs(std::move(inputs))
+        , fCpuFunction(cpuFunction) {}
+
+#if SK_SUPPORT_GPU
+    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
+            GrRecordingContext* context, const GrColorSpaceInfo&) const override {
+        return GrSkSLFP::Make(context, fIndex, "Runtime Color Filter", fSkSL,
+                              fInputs ? fInputs->data() : nullptr,
+                              fInputs ? fInputs->size() : 0);
+    }
+#endif
+
+    bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override {
+        if (fCpuFunction) {
+            struct CpuFuncCtx : public SkRasterPipeline_CallbackCtx {
+                SkRuntimeColorFilterFn cpuFn;
+                const void* inputs;
+            };
+            auto ctx = rec.fAlloc->make<CpuFuncCtx>();
+            ctx->inputs = fInputs->data();
+            ctx->cpuFn = fCpuFunction;
+            ctx->fn = [](SkRasterPipeline_CallbackCtx* arg, int active_pixels) {
+                auto ctx = (CpuFuncCtx*)arg;
+                for (int i = 0; i < active_pixels; i++) {
+                    ctx->cpuFn(ctx->rgba + i * 4, ctx->inputs);
+                }
+            };
+            rec.fPipeline->append(SkRasterPipeline::callback, ctx);
+        } else {
+            struct InterpreterCtx : public SkRasterPipeline_CallbackCtx {
+                SkSL::ByteCodeFunction* main;
+                std::unique_ptr<SkSL::Interpreter> interpreter;
+                const void* inputs;
+            };
+            auto ctx = rec.fAlloc->make<InterpreterCtx>();
+            ctx->inputs = fInputs->data();
+            SkSL::Compiler c;
+            std::unique_ptr<SkSL::Program> prog =
+                                                c.convertProgram(SkSL::Program::kPipelineStage_Kind,
+                                                                 SkSL::String(fSkSL.c_str()),
+                                                                 SkSL::Program::Settings());
+            if (c.errorCount()) {
+                SkDebugf("%s\n", c.errorText().c_str());
+                SkASSERT(false);
+            }
+            std::unique_ptr<SkSL::ByteCode> byteCode = c.toByteCode(*prog);
+            ctx->main = byteCode->fFunctions[0].get();
+            ctx->interpreter.reset(new SkSL::Interpreter(std::move(prog), std::move(byteCode)));
+            ctx->fn = [](SkRasterPipeline_CallbackCtx* arg, int active_pixels) {
+                auto ctx = (InterpreterCtx*)arg;
+                for (int i = 0; i < active_pixels; i++) {
+                    ctx->interpreter->run(*ctx->main,
+                                          (SkSL::Interpreter::Value*) (ctx->rgba + i * 4),
+                                          (SkSL::Interpreter::Value*) ctx->inputs);
+                }
+            };
+            rec.fPipeline->append(SkRasterPipeline::callback, ctx);
+        }
+        return true;
+    }
+
+protected:
+    void flatten(SkWriteBuffer& buffer) const override {
+        // the client is responsible for ensuring that the indices match up between flattening and
+        // unflattening; we don't have a reasonable way to enforce that at the moment
+        buffer.writeInt(fIndex);
+        buffer.writeString(fSkSL.c_str());
+        if (fInputs) {
+            buffer.writeDataAsByteArray(fInputs.get());
+        } else {
+            buffer.writeByteArray(nullptr, 0);
+        }
+    }
+
+private:
+    SK_FLATTENABLE_HOOKS(SkRuntimeColorFilter)
+
+    int fIndex;
+    SkString fSkSL;
+    sk_sp<SkData> fInputs;
+    SkRuntimeColorFilterFn fCpuFunction;
+
+    friend class SkColorFilter;
+
+    typedef SkColorFilter INHERITED;
+};
+
+sk_sp<SkFlattenable> SkRuntimeColorFilter::CreateProc(SkReadBuffer& buffer) {
+    int index = buffer.readInt();
+    SkString sksl;
+    buffer.readString(&sksl);
+    sk_sp<SkData> inputs = buffer.readByteArrayAsData();
+    return sk_sp<SkFlattenable>(new SkRuntimeColorFilter(index, std::move(sksl), std::move(inputs),
+                                                         nullptr));
+}
+
+SkRuntimeColorFilterFactory::SkRuntimeColorFilterFactory(SkString sksl,
+                                                         SkRuntimeColorFilterFn cpuFunc)
+    : fIndex(GrSkSLFP::NewIndex())
+    , fSkSL(std::move(sksl))
+    , fCpuFunc(cpuFunc) {}
+
+sk_sp<SkColorFilter> SkRuntimeColorFilterFactory::make(sk_sp<SkData> inputs) {
+    return sk_sp<SkColorFilter>(new SkRuntimeColorFilter(fIndex, fSkSL, std::move(inputs),
+                                                         fCpuFunc));
+}
+
+#endif // SK_SUPPORT_GPU
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
 #include "SkModeColorFilter.h"
 
 void SkColorFilter::RegisterFlattenables() {
     SK_REGISTER_FLATTENABLE(SkComposeColorFilter);
     SK_REGISTER_FLATTENABLE(SkModeColorFilter);
     SK_REGISTER_FLATTENABLE(SkSRGBGammaColorFilter);
+    SK_REGISTER_FLATTENABLE(SkMixerColorFilter);
+#if SK_SUPPORT_GPU
+    SK_REGISTER_FLATTENABLE(SkRuntimeColorFilter);
+#endif
 }
diff --git a/src/core/SkColorFilterPriv.h b/src/core/SkColorFilterPriv.h
new file mode 100644
index 0000000..92d40dc
--- /dev/null
+++ b/src/core/SkColorFilterPriv.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef SkColorFilterPriv_DEFINED
+#define SkColorFilterPriv_DEFINED
+
+#ifdef SK_SUPPORT_GPU
+
+#include "SkColorFilter.h"
+#include "SkString.h"
+
+using SkRuntimeColorFilterFn = void(*)(float[4], const void*);
+
+class SK_API SkRuntimeColorFilterFactory {
+public:
+    /**
+     * Creates a factory which creates runtime color filters. The SkSL must define a 'main' function
+     * with the signature 'void main(inout half4 color)'. The SkSL will be used when rendering in
+     * GPU mode, with the 'color' parameter providing the current value on input and receiving the
+     * new color value on exit. In software mode, the cpuFunc will be called with the current color
+     * and a pointer to the 'inputs' bytes. cpuFunc may be left null, in which case only GPU
+     * rendering is supported.
+     */
+    SkRuntimeColorFilterFactory(SkString sksl, SkRuntimeColorFilterFn cpuFunc = nullptr);
+
+    /**
+     * Creates a color filter instance with the specified inputs. In GPU rendering, the inputs are
+     * used to populate the values of 'in' variables. For instance, given the color filter:
+     *    in uniform float x;
+     *    in uniform float y;
+     *    void main(inout half4 color) {
+     *        ...
+     *    }
+     * The values of the x and y inputs come from the 'inputs' SkData, which are laid out as a
+     * struct with two float elements. If there are no inputs, the 'inputs' parameter may be null.
+     *
+     * In CPU rendering, a pointer to the input bytes is passed as the second parameter to
+     * 'cpuFunc'.
+     */
+    sk_sp<SkColorFilter> make(sk_sp<SkData> inputs);
+
+private:
+    int fIndex;
+    SkString fSkSL;
+    SkRuntimeColorFilterFn fCpuFunc;
+};
+
+#endif // SK_SUPPORT_GPU
+
+#endif  // SkColorFilterPriv_DEFINED
diff --git a/src/core/SkColorMatrixFilterRowMajor255.cpp b/src/core/SkColorMatrixFilterRowMajor255.cpp
index dcf8238..e006d64 100644
--- a/src/core/SkColorMatrixFilterRowMajor255.cpp
+++ b/src/core/SkColorMatrixFilterRowMajor255.cpp
@@ -15,43 +15,28 @@
 #include "SkUnPreMultiply.h"
 #include "SkWriteBuffer.h"
 
-static void transpose_and_scale01(float dst[20], const float src[20]) {
-    const float* srcR = src + 0;
-    const float* srcG = src + 5;
-    const float* srcB = src + 10;
-    const float* srcA = src + 15;
+void SkColorMatrixFilterRowMajor255::initState() {
+    const float* srcR = fMatrix + 0;
+    const float* srcG = fMatrix + 5;
+    const float* srcB = fMatrix + 10;
+    const float* srcA = fMatrix + 15;
+
+    fFlags = (srcA[0] == 0 && srcA[1] == 0 && srcA[2] == 0 && srcA[3] == 1 && srcA[4] == 0)
+        ? kAlphaUnchanged_Flag : 0;
 
     for (int i = 0; i < 16; i += 4) {
-        dst[i + 0] = *srcR++;
-        dst[i + 1] = *srcG++;
-        dst[i + 2] = *srcB++;
-        dst[i + 3] = *srcA++;
+        fTranspose[i + 0] = *srcR++;
+        fTranspose[i + 1] = *srcG++;
+        fTranspose[i + 2] = *srcB++;
+        fTranspose[i + 3] = *srcA++;
     }
     // Might as well scale these translates down to [0,1] here instead of every filter call.
-    dst[16] = *srcR * (1/255.0f);
-    dst[17] = *srcG * (1/255.0f);
-    dst[18] = *srcB * (1/255.0f);
-    dst[19] = *srcA * (1/255.0f);
+    fTranspose[16] = *srcR * (1/255.0f);
+    fTranspose[17] = *srcG * (1/255.0f);
+    fTranspose[18] = *srcB * (1/255.0f);
+    fTranspose[19] = *srcA * (1/255.0f);
 }
 
-void SkColorMatrixFilterRowMajor255::initState() {
-    transpose_and_scale01(fTranspose, fMatrix);
-
-    const float* array = fMatrix;
-
-    // check if we have to munge Alpha
-    bool changesAlpha = (array[15] || array[16] || array[17] || (array[18] - 1) || array[19]);
-    bool usesAlpha = (array[3] || array[8] || array[13]);
-
-    if (changesAlpha || usesAlpha) {
-        fFlags = changesAlpha ? 0 : kAlphaUnchanged_Flag;
-    } else {
-        fFlags = kAlphaUnchanged_Flag;
-    }
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
 SkColorMatrixFilterRowMajor255::SkColorMatrixFilterRowMajor255(const SkScalar array[20]) {
     memcpy(fMatrix, array, 20 * sizeof(SkScalar));
     this->initState();
@@ -61,8 +46,6 @@
     return this->INHERITED::getFlags() | fFlags;
 }
 
-///////////////////////////////////////////////////////////////////////////////
-
 void SkColorMatrixFilterRowMajor255::flatten(SkWriteBuffer& buffer) const {
     SkASSERT(sizeof(fMatrix)/sizeof(SkScalar) == 20);
     buffer.writeScalarArray(fMatrix, 20);
@@ -83,94 +66,17 @@
     return true;
 }
 
-///////////////////////////////////////////////////////////////////////////////
-//  This code was duplicated from src/effects/SkColorMatrix.cpp in order to be used in core.
-//////
-
-// To detect if we need to apply clamping after applying a matrix, we check if
-// any output component might go outside of [0, 255] for any combination of
-// input components in [0..255].
-// Each output component is an affine transformation of the input component, so
-// the minimum and maximum values are for any combination of minimum or maximum
-// values of input components (i.e. 0 or 255).
-// E.g. if R' = x*R + y*G + z*B + w*A + t
-// Then the maximum value will be for R=255 if x>0 or R=0 if x<0, and the
-// minimum value will be for R=0 if x>0 or R=255 if x<0.
-// Same goes for all components.
-static bool component_needs_clamping(const SkScalar row[5]) {
-    SkScalar maxValue = row[4] / 255;
-    SkScalar minValue = row[4] / 255;
-    for (int i = 0; i < 4; ++i) {
-        if (row[i] > 0)
-            maxValue += row[i];
-        else
-            minValue += row[i];
-    }
-    return (maxValue > 1) || (minValue < 0);
-}
-
-static bool needs_clamping(const SkScalar matrix[20]) {
-    return component_needs_clamping(matrix)
-        || component_needs_clamping(matrix+5)
-        || component_needs_clamping(matrix+10)
-        || component_needs_clamping(matrix+15);
-}
-
-static void set_concat(SkScalar result[20], const SkScalar outer[20], const SkScalar inner[20]) {
-    int index = 0;
-    for (int j = 0; j < 20; j += 5) {
-        for (int i = 0; i < 4; i++) {
-            result[index++] =   outer[j + 0] * inner[i + 0] +
-                                outer[j + 1] * inner[i + 5] +
-                                outer[j + 2] * inner[i + 10] +
-                                outer[j + 3] * inner[i + 15];
-        }
-        result[index++] =   outer[j + 0] * inner[4] +
-                            outer[j + 1] * inner[9] +
-                            outer[j + 2] * inner[14] +
-                            outer[j + 3] * inner[19] +
-                            outer[j + 4];
-    }
-}
-
-///////////////////////////////////////////////////////////////////////////////
-//  End duplication
-//////
-
-void SkColorMatrixFilterRowMajor255::onAppendStages(SkRasterPipeline* p,
-                                                    SkColorSpace* dst,
-                                                    SkArenaAlloc* scratch,
+bool SkColorMatrixFilterRowMajor255::onAppendStages(const SkStageRec& rec,
                                                     bool shaderIsOpaque) const {
-    bool willStayOpaque = shaderIsOpaque && (fFlags & kAlphaUnchanged_Flag);
-    bool needsClamp0 = false,
-         needsClamp1 = false;
-    for (int i = 0; i < 4; i++) {
-        SkScalar min = fTranspose[i+16],
-                 max = fTranspose[i+16];
-        (fTranspose[i+ 0] < 0 ? min : max) += fTranspose[i+ 0];
-        (fTranspose[i+ 4] < 0 ? min : max) += fTranspose[i+ 4];
-        (fTranspose[i+ 8] < 0 ? min : max) += fTranspose[i+ 8];
-        (fTranspose[i+12] < 0 ? min : max) += fTranspose[i+12];
-        needsClamp0 = needsClamp0 || min < 0;
-        needsClamp1 = needsClamp1 || max > 1;
-    }
+    const bool willStayOpaque = shaderIsOpaque && (fFlags & kAlphaUnchanged_Flag);
 
+    SkRasterPipeline* p = rec.fPipeline;
     if (!shaderIsOpaque) { p->append(SkRasterPipeline::unpremul); }
     if (           true) { p->append(SkRasterPipeline::matrix_4x5, fTranspose); }
-    if (    needsClamp0) { p->append(SkRasterPipeline::clamp_0); }
-    if (    needsClamp1) { p->append(SkRasterPipeline::clamp_1); }
+    if (           true) { p->append(SkRasterPipeline::clamp_0); }
+    if (           true) { p->append(SkRasterPipeline::clamp_1); }
     if (!willStayOpaque) { p->append(SkRasterPipeline::premul); }
-}
-
-sk_sp<SkColorFilter>
-SkColorMatrixFilterRowMajor255::onMakeComposed(sk_sp<SkColorFilter> innerFilter) const {
-    SkScalar innerMatrix[20];
-    if (innerFilter->asColorMatrix(innerMatrix) && !needs_clamping(innerMatrix)) {
-        SkScalar concat[20];
-        set_concat(concat, fMatrix, innerMatrix);
-        return sk_make_sp<SkColorMatrixFilterRowMajor255>(concat);
-    }
-    return nullptr;
+    return true;
 }
 
 #if SK_SUPPORT_GPU
@@ -288,7 +194,7 @@
 #endif
 
 std::unique_ptr<GrFragmentProcessor> SkColorMatrixFilterRowMajor255::asFragmentProcessor(
-        GrContext*, const GrColorSpaceInfo&) const {
+        GrRecordingContext*, const GrColorSpaceInfo&) const {
     return ColorMatrixEffect::Make(fMatrix);
 }
 
@@ -296,7 +202,13 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
+#ifdef SK_SUPPORT_LEGACY_COLORFILTER_FACTORIES
 sk_sp<SkColorFilter> SkColorFilter::MakeMatrixFilterRowMajor255(const SkScalar array[20]) {
+    return SkColorFilters::MatrixRowMajor255(array);
+}
+#endif
+
+sk_sp<SkColorFilter> SkColorFilters::MatrixRowMajor255(const float array[20]) {
     if (!SkScalarsAreFinite(array, 20)) {
         return nullptr;
     }
diff --git a/src/core/SkColorMatrixFilterRowMajor255.h b/src/core/SkColorMatrixFilterRowMajor255.h
index 837c630..bdc975b 100644
--- a/src/core/SkColorMatrixFilterRowMajor255.h
+++ b/src/core/SkColorMatrixFilterRowMajor255.h
@@ -21,11 +21,10 @@
 
     uint32_t getFlags() const override;
     bool asColorMatrix(SkScalar matrix[20]) const override;
-    sk_sp<SkColorFilter> onMakeComposed(sk_sp<SkColorFilter>) const override;
 
 #if SK_SUPPORT_GPU
     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
-            GrContext*, const GrColorSpaceInfo&) const override;
+            GrRecordingContext*, const GrColorSpaceInfo&) const override;
 #endif
 
 protected:
@@ -34,8 +33,7 @@
 private:
     SK_FLATTENABLE_HOOKS(SkColorMatrixFilterRowMajor255)
 
-    void onAppendStages(SkRasterPipeline*, SkColorSpace*, SkArenaAlloc*,
-                        bool shaderIsOpaque) const override;
+    bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override;
 
     SkScalar        fMatrix[20];
     float           fTranspose[20]; // for Sk4s
diff --git a/src/core/SkColorSpaceXformCanvas.cpp b/src/core/SkColorSpaceXformCanvas.cpp
deleted file mode 100644
index ceb014e..0000000
--- a/src/core/SkColorSpaceXformCanvas.cpp
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkCanvasVirtualEnforcer.h"
-#include "SkColorFilter.h"
-#include "SkColorSpaceXformCanvas.h"
-#include "SkColorSpaceXformer.h"
-#include "SkDrawShadowInfo.h"
-#include "SkGradientShader.h"
-#include "SkImageFilter.h"
-#include "SkImagePriv.h"
-#include "SkImage_Base.h"
-#include "SkMakeUnique.h"
-#include "SkNoDrawCanvas.h"
-#include "SkSurface.h"
-#include "SkTLazy.h"
-
-namespace {
-    struct MaybePaint {
-       SkTLazy<SkPaint> fStorage;
-       const SkPaint* fPaint = nullptr;
-       MaybePaint(const SkPaint* p, SkColorSpaceXformer* xformer) {
-           if (p) { fPaint = fStorage.set(xformer->apply(*p)); }
-       }
-       operator const SkPaint*() const { return fPaint; }
-    };
-};
-
-class SkColorSpaceXformCanvas : public SkCanvasVirtualEnforcer<SkNoDrawCanvas> {
-public:
-    SkColorSpaceXformCanvas(SkCanvas* target, sk_sp<SkColorSpace> targetCS,
-                            std::unique_ptr<SkColorSpaceXformer> xformer)
-        : SkCanvasVirtualEnforcer<SkNoDrawCanvas>(SkIRect::MakeSize(target->getBaseLayerSize()))
-        , fTarget(target)
-        , fTargetCS(targetCS)
-        , fXformer(std::move(xformer))
-    {
-        // Set the matrix and clip to match |fTarget|.  Otherwise, we'll answer queries for
-        // bounds/matrix differently than |fTarget| would.
-        SkCanvas::onClipRect(SkRect::Make(fTarget->getDeviceClipBounds()),
-                             SkClipOp::kIntersect, kHard_ClipEdgeStyle);
-        SkCanvas::setMatrix(fTarget->getTotalMatrix());
-    }
-
-    SkImageInfo onImageInfo() const override {
-        return fTarget->imageInfo().makeColorSpace(fTargetCS);
-    }
-
-    void onDrawPaint(const SkPaint& paint) override {
-        fTarget->drawPaint(fXformer->apply(paint));
-    }
-
-    void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
-        fTarget->drawRect(rect, fXformer->apply(paint));
-    }
-    void onDrawEdgeAARect(const SkRect& rect, SkCanvas::QuadAAFlags aa, SkColor color,
-                          SkBlendMode mode) override {
-        fTarget->experimental_DrawEdgeAARectV1(rect, aa, fXformer->apply(color), mode);
-    }
-    void onDrawOval(const SkRect& oval, const SkPaint& paint) override {
-        fTarget->drawOval(oval, fXformer->apply(paint));
-    }
-    void onDrawRRect(const SkRRect& rrect, const SkPaint& paint) override {
-        fTarget->drawRRect(rrect, fXformer->apply(paint));
-    }
-    void onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) override {
-        fTarget->drawDRRect(outer, inner, fXformer->apply(paint));
-    }
-    void onDrawPath(const SkPath& path, const SkPaint& paint) override {
-        fTarget->drawPath(path, fXformer->apply(paint));
-    }
-    void onDrawArc(const SkRect& oval, SkScalar start, SkScalar sweep, bool useCenter,
-                   const SkPaint& paint) override {
-        fTarget->drawArc(oval, start, sweep, useCenter, fXformer->apply(paint));
-    }
-    void onDrawRegion(const SkRegion& region, const SkPaint& paint) override {
-        fTarget->drawRegion(region, fXformer->apply(paint));
-    }
-    void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], const SkPoint texs[4],
-                     SkBlendMode mode, const SkPaint& paint) override {
-        SkColor xformed[4];
-        if (colors) {
-            fXformer->apply(xformed, colors, 4);
-            colors = xformed;
-        }
-
-        fTarget->drawPatch(cubics, colors, texs, mode, fXformer->apply(paint));
-    }
-    void onDrawPoints(PointMode mode, size_t count, const SkPoint* pts,
-                      const SkPaint& paint) override {
-        fTarget->drawPoints(mode, count, pts, fXformer->apply(paint));
-    }
-
-    void onDrawVerticesObject(const SkVertices* vertices, const SkVertices::Bone bones[], int boneCount,
-                              SkBlendMode mode, const SkPaint& paint) override {
-        sk_sp<SkVertices> copy;
-        if (vertices->hasColors()) {
-            int count = vertices->vertexCount();
-            SkSTArray<8, SkColor> xformed(count);
-            fXformer->apply(xformed.begin(), vertices->colors(), count);
-            copy = SkVertices::MakeCopy(vertices->mode(), count, vertices->positions(),
-                                        vertices->texCoords(), xformed.begin(),
-                                        vertices->boneIndices(), vertices->boneWeights(),
-                                        vertices->indexCount(), vertices->indices());
-            vertices = copy.get();
-        }
-
-        fTarget->drawVertices(vertices, bones, boneCount, mode, fXformer->apply(paint));
-    }
-
-    void onDrawTextBlob(const SkTextBlob* blob,
-                        SkScalar x, SkScalar y,
-                        const SkPaint& paint) override {
-        fTarget->drawTextBlob(blob, x, y, fXformer->apply(paint));
-    }
-
-    void onDrawImage(const SkImage* img,
-                     SkScalar l, SkScalar t,
-                     const SkPaint* paint) override {
-        if (!fTarget->quickReject(SkRect::Make(img->bounds()).makeOffset(l,t))) {
-            fTarget->drawImage(prepareImage(img).get(), l, t, MaybePaint(paint, fXformer.get()));
-        }
-    }
-    void onDrawImageRect(const SkImage* img,
-                         const SkRect* src, const SkRect& dst,
-                         const SkPaint* paint, SrcRectConstraint constraint) override {
-        if (!fTarget->quickReject(dst)) {
-            fTarget->drawImageRect(prepareImage(img).get(),
-                                   src ? *src : SkRect::MakeIWH(img->width(), img->height()), dst,
-                                   MaybePaint(paint, fXformer.get()), constraint);
-        }
-    }
-    void onDrawImageNine(const SkImage* img,
-                         const SkIRect& center, const SkRect& dst,
-                         const SkPaint* paint) override {
-        if (!fTarget->quickReject(dst)) {
-            fTarget->drawImageNine(prepareImage(img).get(), center, dst,
-                                   MaybePaint(paint, fXformer.get()));
-        }
-    }
-    void onDrawImageLattice(const SkImage* img,
-                            const Lattice& lattice, const SkRect& dst,
-                            const SkPaint* paint) override {
-        if (!fTarget->quickReject(dst)) {
-            SkSTArray<16, SkColor> colorBuffer;
-            int count = lattice.fRectTypes && lattice.fColors ?
-                        (lattice.fXCount + 1) * (lattice.fYCount + 1) : 0;
-            colorBuffer.reset(count);
-            fTarget->drawImageLattice(prepareImage(img).get(),
-                                      fXformer->apply(lattice, colorBuffer.begin(), count),
-                                      dst, MaybePaint(paint, fXformer.get()));
-        }
-    }
-    void onDrawImageSet(const SkCanvas::ImageSetEntry set[], int count,
-                        SkFilterQuality filterQuality, SkBlendMode mode) override {
-        SkAutoTArray<ImageSetEntry> xformedSet(count);
-        for (int i = 0; i < count; ++i) {
-            xformedSet[i].fImage = this->prepareImage(set[i].fImage.get());
-            xformedSet[i].fSrcRect = set[i].fSrcRect;
-            xformedSet[i].fDstRect = set[i].fDstRect;
-            xformedSet[i].fAlpha = set[i].fAlpha;
-            xformedSet[i].fAAFlags = set[i].fAAFlags;
-        }
-        fTarget->experimental_DrawImageSetV1(xformedSet.get(), count, filterQuality, mode);
-    }
-
-    void onDrawAtlas(const SkImage* atlas, const SkRSXform* xforms, const SkRect* tex,
-                     const SkColor* colors, int count, SkBlendMode mode,
-                     const SkRect* cull, const SkPaint* paint) override {
-        SkSTArray<8, SkColor> xformed;
-        if (colors) {
-            xformed.reset(count);
-            fXformer->apply(xformed.begin(), colors, count);
-            colors = xformed.begin();
-        }
-        fTarget->drawAtlas(prepareImage(atlas).get(), xforms, tex, colors, count, mode, cull,
-                           MaybePaint(paint, fXformer.get()));
-    }
-
-    // TODO: quick reject bitmap draw calls before transforming too?
-    void onDrawBitmap(const SkBitmap& bitmap,
-                      SkScalar l, SkScalar t,
-                      const SkPaint* paint) override {
-        if (this->skipXform(bitmap)) {
-            return fTarget->drawBitmap(bitmap, l, t, MaybePaint(paint, fXformer.get()));
-        }
-
-        fTarget->drawImage(fXformer->apply(bitmap).get(), l, t, MaybePaint(paint, fXformer.get()));
-    }
-    void onDrawBitmapRect(const SkBitmap& bitmap,
-                          const SkRect* src, const SkRect& dst,
-                          const SkPaint* paint, SrcRectConstraint constraint) override {
-        if (this->skipXform(bitmap)) {
-            return fTarget->drawBitmapRect(bitmap,
-                    src ? *src : SkRect::MakeIWH(bitmap.width(), bitmap.height()), dst,
-                    MaybePaint(paint, fXformer.get()), constraint);
-        }
-
-        fTarget->drawImageRect(fXformer->apply(bitmap).get(),
-                               src ? *src : SkRect::MakeIWH(bitmap.width(), bitmap.height()), dst,
-                               MaybePaint(paint, fXformer.get()), constraint);
-    }
-    void onDrawBitmapNine(const SkBitmap& bitmap,
-                          const SkIRect& center, const SkRect& dst,
-                          const SkPaint* paint) override {
-        if (this->skipXform(bitmap)) {
-            return fTarget->drawBitmapNine(bitmap, center, dst, MaybePaint(paint, fXformer.get()));
-        }
-
-        fTarget->drawImageNine(fXformer->apply(bitmap).get(), center, dst,
-                               MaybePaint(paint, fXformer.get()));
-
-    }
-    void onDrawBitmapLattice(const SkBitmap& bitmap,
-                             const Lattice& lattice, const SkRect& dst,
-                             const SkPaint* paint) override {
-        if (this->skipXform(bitmap)) {
-            return fTarget->drawBitmapLattice(bitmap, lattice, dst,
-                                              MaybePaint(paint, fXformer.get()));
-        }
-
-        SkSTArray<16, SkColor> colorBuffer;
-        int count = lattice.fRectTypes && lattice.fColors?
-                    (lattice.fXCount + 1) * (lattice.fYCount + 1) : 0;
-        colorBuffer.reset(count);
-        fTarget->drawImageLattice(fXformer->apply(bitmap).get(),
-                                  fXformer->apply(lattice, colorBuffer.begin(), count), dst,
-                                  MaybePaint(paint, fXformer.get()));
-    }
-    void onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) override {
-        SkDrawShadowRec newRec(rec);
-        newRec.fAmbientColor = fXformer->apply(rec.fAmbientColor);
-        newRec.fSpotColor = fXformer->apply(rec.fSpotColor);
-        fTarget->private_draw_shadow_rec(path, newRec);
-    }
-    void onDrawPicture(const SkPicture* pic,
-                       const SkMatrix* matrix,
-                       const SkPaint* paint) override {
-        SkCanvas::onDrawPicture(pic, matrix, MaybePaint(paint, fXformer.get()));
-    }
-    void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override {
-        SkCanvas::onDrawDrawable(drawable, matrix);
-    }
-
-    SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec& rec) override {
-        sk_sp<SkImageFilter> backdrop = rec.fBackdrop ? fXformer->apply(rec.fBackdrop) : nullptr;
-        sk_sp<SkImage> clipMask = rec.fClipMask ? fXformer->apply(rec.fClipMask) : nullptr;
-        fTarget->saveLayer({
-            rec.fBounds,
-            MaybePaint(rec.fPaint, fXformer.get()),
-            backdrop.get(),
-            clipMask.get(),
-            rec.fClipMatrix,
-            rec.fSaveLayerFlags,
-        });
-        return kNoLayer_SaveLayerStrategy;
-    }
-
-    // Everything from here on should be uninteresting strictly proxied state-change calls.
-    void willSave()    override { fTarget->save(); }
-    void willRestore() override { fTarget->restore(); }
-
-    void didConcat   (const SkMatrix& m) override { fTarget->concat   (m); }
-    void didSetMatrix(const SkMatrix& m) override { fTarget->setMatrix(m); }
-
-    void onClipRect(const SkRect& clip, SkClipOp op, ClipEdgeStyle style) override {
-        SkCanvas::onClipRect(clip, op, style);
-        fTarget->clipRect(clip, op, style);
-    }
-    void onClipRRect(const SkRRect& clip, SkClipOp op, ClipEdgeStyle style) override {
-        SkCanvas::onClipRRect(clip, op, style);
-        fTarget->clipRRect(clip, op, style);
-    }
-    void onClipPath(const SkPath& clip, SkClipOp op, ClipEdgeStyle style) override {
-        SkCanvas::onClipPath(clip, op, style);
-        fTarget->clipPath(clip, op, style);
-    }
-    void onClipRegion(const SkRegion& clip, SkClipOp op) override {
-        SkCanvas::onClipRegion(clip, op);
-        fTarget->clipRegion(clip, op);
-    }
-
-    void onDrawAnnotation(const SkRect& rect, const char* key, SkData* val) override {
-        fTarget->drawAnnotation(rect, key, val);
-    }
-
-    sk_sp<SkSurface> onNewSurface(const SkImageInfo& info, const SkSurfaceProps& props) override {
-        return fTarget->makeSurface(info, &props);
-    }
-
-    SkISize getBaseLayerSize() const override { return fTarget->getBaseLayerSize(); }
-    bool isClipEmpty() const override { return fTarget->isClipEmpty(); }
-    bool isClipRect() const override { return fTarget->isClipRect(); }
-    bool onPeekPixels(SkPixmap* pixmap) override { return fTarget->peekPixels(pixmap); }
-    bool onAccessTopLayerPixels(SkPixmap* pixmap) override {
-        SkImageInfo info;
-        size_t rowBytes;
-        SkIPoint* origin = nullptr;
-        void* addr = fTarget->accessTopLayerPixels(&info, &rowBytes, origin);
-        if (addr) {
-            *pixmap = SkPixmap(info, addr, rowBytes);
-            return true;
-        }
-        return false;
-    }
-
-    GrContext* getGrContext() override { return fTarget->getGrContext(); }
-    bool onGetProps(SkSurfaceProps* props) const override { return fTarget->getProps(props); }
-    void onFlush() override { return fTarget->flush(); }
-    GrRenderTargetContext* internal_private_accessTopLayerRenderTargetContext() override {
-        return fTarget->internal_private_accessTopLayerRenderTargetContext();
-    }
-
-private:
-    sk_sp<SkImage> prepareImage(const SkImage* image) {
-        GrContext* gr = fTarget->getGrContext();
-        // If fTarget is GPU-accelerated, we want to upload to a texture before applying the
-        // transform. This way, we can get cache hits in the texture cache and the transform gets
-        // applied on the GPU.
-        if (gr) {
-            sk_sp<SkImage> textureImage = image->makeTextureImage(gr, nullptr);
-            if (textureImage) {
-                return fXformer->apply(textureImage.get());
-            }
-        }
-        // TODO: Extract a sub image corresponding to the src rect in order
-        // to xform only the useful part of the image. Sub image could be reduced
-        // even further by taking into account dst_rect+ctm+clip
-        return fXformer->apply(image);
-    }
-
-    bool skipXform(const SkBitmap& bitmap) {
-        return (!bitmap.colorSpace() && fTargetCS->isSRGB()) ||
-               (SkColorSpace::Equals(bitmap.colorSpace(), fTargetCS.get())) ||
-               (kAlpha_8_SkColorType == bitmap.colorType());
-    }
-
-    SkCanvas*                            fTarget;
-    sk_sp<SkColorSpace>                  fTargetCS;
-    std::unique_ptr<SkColorSpaceXformer> fXformer;
-};
-
-std::unique_ptr<SkCanvas> SkCreateColorSpaceXformCanvas(SkCanvas* target,
-                                                        sk_sp<SkColorSpace> targetCS) {
-    std::unique_ptr<SkColorSpaceXformer> xformer = SkColorSpaceXformer::Make(targetCS);
-    if (!xformer) {
-        return nullptr;
-    }
-
-    return skstd::make_unique<SkColorSpaceXformCanvas>(target, std::move(targetCS),
-                                                       std::move(xformer));
-}
diff --git a/src/core/SkColorSpaceXformSteps.cpp b/src/core/SkColorSpaceXformSteps.cpp
index 6e31814..92d4709 100644
--- a/src/core/SkColorSpaceXformSteps.cpp
+++ b/src/core/SkColorSpaceXformSteps.cpp
@@ -10,7 +10,15 @@
 #include "SkRasterPipeline.h"
 #include "../../third_party/skcms/skcms.h"
 
-// TODO: explain
+// TODO(mtklein): explain the logic of this file
+
+bool SkColorSpaceXformSteps::Required(SkColorSpace* src, SkColorSpace* dst) {
+    // Any SkAlphaType will work fine here as long as we use the same one.
+    SkAlphaType at = kPremul_SkAlphaType;
+    return 0 != SkColorSpaceXformSteps(src, at,
+                                       dst, at).flags.mask();
+    // TODO(mtklein): quicker impl. that doesn't construct an SkColorSpaceXformSteps?
+}
 
 SkColorSpaceXformSteps::SkColorSpaceXformSteps(SkColorSpace* src, SkAlphaType srcAT,
                                                SkColorSpace* dst, SkAlphaType dstAT) {
@@ -174,3 +182,4 @@
     }
     if (flags.premul) { p->append(SkRasterPipeline::premul); }
 }
+
diff --git a/src/core/SkColorSpaceXformSteps.h b/src/core/SkColorSpaceXformSteps.h
index 9585d1e..ea05a76 100644
--- a/src/core/SkColorSpaceXformSteps.h
+++ b/src/core/SkColorSpaceXformSteps.h
@@ -14,6 +14,10 @@
 class SkRasterPipeline;
 
 struct SkColorSpaceXformSteps {
+    // Returns true if SkColorSpaceXformSteps must be applied
+    // to draw content in `src` into a destination in `dst`.
+    static bool Required(SkColorSpace* src, SkColorSpace* dst);
+
     struct Flags {
         bool unpremul         = false;
         bool linearize        = false;
@@ -37,7 +41,13 @@
     void apply(SkRasterPipeline*, bool src_is_normalized) const;
 
     void apply(SkRasterPipeline* p, SkColorType srcCT) const {
+    #if 0
         this->apply(p, srcCT < kRGBA_F16_SkColorType);
+    #else
+        // F16Norm is normalized, but to make diffing with F16 easier we
+        // intentionally take the slower, non-normalized path here.
+        this->apply(p, srcCT < kRGBA_F16Norm_SkColorType);
+    #endif
     }
 
     Flags flags;
diff --git a/src/core/SkColorSpaceXformer.cpp b/src/core/SkColorSpaceXformer.cpp
deleted file mode 100644
index 09f399c..0000000
--- a/src/core/SkColorSpaceXformer.cpp
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkColorFilter.h"
-#include "SkColorSpacePriv.h"
-#include "SkColorSpaceXformer.h"
-#include "SkDrawLooper.h"
-#include "SkGradientShader.h"
-#include "SkImage.h"
-#include "SkImage_Base.h"
-#include "SkImageFilter.h"
-#include "SkImagePriv.h"
-#include "SkShaderBase.h"
-
-SkColorSpaceXformer::SkColorSpaceXformer(sk_sp<SkColorSpace> dst)
-    : fDst(std::move(dst))
-    , fFromSRGBSteps(sk_srgb_singleton(), kUnpremul_SkAlphaType,
-                     fDst.get()         , kUnpremul_SkAlphaType)
-    , fReentryCount(0) {
-
-    SkRasterPipeline p(&fAlloc);
-    p.append(SkRasterPipeline::load_8888, &fFromSRGBSrc);
-    p.append(SkRasterPipeline::swap_rb);
-    fFromSRGBSteps.apply(&p, kBGRA_8888_SkColorType);
-    p.append(SkRasterPipeline::swap_rb);
-    p.append(SkRasterPipeline::store_8888, &fFromSRGBDst);
-    fFromSRGB = p.compile();
-}
-
-SkColorSpaceXformer::~SkColorSpaceXformer() {}
-
-std::unique_ptr<SkColorSpaceXformer> SkColorSpaceXformer::Make(sk_sp<SkColorSpace> dst) {
-    return std::unique_ptr<SkColorSpaceXformer>(new SkColorSpaceXformer{std::move(dst)});
-}
-
-// So what's up with these caches?
-//
-// We want to cache transformed objects for a couple of reasons:
-//
-// 1) to avoid redundant work - the inputs are a DAG, not a tree (e.g. same SkImage drawn multiple
-//    times in a SkPicture), so if we blindly recurse we could end up transforming the same objects
-//    repeatedly.
-//
-// 2) to avoid topology changes - we want the output to remain isomorphic with the input -- this is
-//    particularly important for image filters (to maintain their original DAG structure in order
-//    to not defeat their own/internal caching), but also for avoiding unnecessary cloning
-//    (e.g. duplicated SkImages allocated for the example in #1 above).
-//
-// The caching scope is naturaly bound by the lifetime of the SkColorSpaceXformer object, but
-// clients may choose to not discard xformers immediately - in which case, caching indefinitely
-// is problematic.  The solution is to limit the cache scope to the top level apply() call
-// (i.e. we only keep cached objects alive while transforming).
-
-class SkColorSpaceXformer::AutoCachePurge {
-public:
-    AutoCachePurge(SkColorSpaceXformer* xformer)
-        : fXformer(xformer) {
-        fXformer->fReentryCount++;
-    }
-
-    ~AutoCachePurge() {
-        SkASSERT(fXformer->fReentryCount > 0);
-        if (--fXformer->fReentryCount == 0) {
-            fXformer->purgeCaches();
-        }
-    }
-
-private:
-    SkColorSpaceXformer* fXformer;
-};
-
-template <typename T>
-sk_sp<T> SkColorSpaceXformer::cachedApply(const T* src, Cache<T>* cache,
-                                          sk_sp<T> (*applyFunc)(const T*, SkColorSpaceXformer*)) {
-    if (!src) {
-        return nullptr;
-    }
-
-    auto key = sk_ref_sp(const_cast<T*>(src));
-    if (auto* xformed = cache->find(key)) {
-        return sk_ref_sp(xformed->get());
-    }
-
-    auto xformed = applyFunc(src, this);
-    cache->set(std::move(key), xformed);
-
-    return xformed;
-}
-
-void SkColorSpaceXformer::purgeCaches() {
-    fImageCache.reset();
-    fColorFilterCache.reset();
-    fImageFilterCache.reset();
-}
-
-sk_sp<SkImage> SkColorSpaceXformer::apply(const SkImage* src) {
-    const AutoCachePurge autoPurge(this);
-    return this->cachedApply<SkImage>(src, &fImageCache,
-        [](const SkImage* img, SkColorSpaceXformer* xformer) {
-            return img->makeColorSpace(xformer->fDst);
-        });
-}
-
-sk_sp<SkImage> SkColorSpaceXformer::apply(const SkBitmap& src) {
-    const AutoCachePurge autoPurge(this);
-    sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(src, kNever_SkCopyPixelsMode);
-    if (!image) {
-        return nullptr;
-    }
-
-    sk_sp<SkImage> xformed = image->makeColorSpace(fDst);
-    // We want to be sure we don't let the kNever_SkCopyPixelsMode image escape this stack frame.
-    SkASSERT(xformed != image);
-    return xformed;
-}
-
-sk_sp<SkColorFilter> SkColorSpaceXformer::apply(const SkColorFilter* colorFilter) {
-    const AutoCachePurge autoPurge(this);
-    return this->cachedApply<SkColorFilter>(colorFilter, &fColorFilterCache,
-        [](const SkColorFilter* f, SkColorSpaceXformer* xformer) {
-            return f->makeColorSpace(xformer);
-        });
-}
-
-sk_sp<SkImageFilter> SkColorSpaceXformer::apply(const SkImageFilter* imageFilter) {
-    const AutoCachePurge autoPurge(this);
-    return this->cachedApply<SkImageFilter>(imageFilter, &fImageFilterCache,
-        [](const SkImageFilter* f, SkColorSpaceXformer* xformer) {
-            return f->makeColorSpace(xformer);
-        });
-}
-
-sk_sp<SkShader> SkColorSpaceXformer::apply(const SkShader* shader) {
-    const AutoCachePurge autoPurge(this);
-    return as_SB(shader)->makeColorSpace(this);
-}
-
-void SkColorSpaceXformer::apply(SkColor* xformed, const SkColor* srgb, int n) {
-    fFromSRGBSrc.pixels = const_cast<SkColor*>(srgb);
-    fFromSRGBDst.pixels = xformed;
-    fFromSRGB(0,0,n,1);
-}
-
-SkColor SkColorSpaceXformer::apply(SkColor srgb) {
-    SkColor xformed;
-    this->apply(&xformed, &srgb, 1);
-    return xformed;
-}
-
-SkPaint SkColorSpaceXformer::apply(const SkPaint& src) {
-    const AutoCachePurge autoPurge(this);
-
-    SkPaint dst = src;
-
-    // All SkColorSpaces have the same black point.
-    if (src.getColor() & 0xffffff) {
-        dst.setColor(this->apply(src.getColor()));
-    }
-
-    if (auto shader = src.getShader()) {
-        dst.setShader(this->apply(shader));
-    }
-
-    if (auto cf = src.getColorFilter()) {
-        dst.setColorFilter(this->apply(cf));
-    }
-
-    if (auto looper = src.getDrawLooper()) {
-        dst.setDrawLooper(looper->makeColorSpace(this));
-    }
-
-    if (auto imageFilter = src.getImageFilter()) {
-        dst.setImageFilter(this->apply(imageFilter));
-    }
-
-    return dst;
-}
-
-SkCanvas::Lattice SkColorSpaceXformer::apply(const SkCanvas::Lattice& lattice,
-                                             SkColor* colorBuffer, int count) {
-    if (count) {
-        this->apply(colorBuffer, lattice.fColors, count);
-        return {lattice.fXDivs, lattice.fYDivs, lattice.fRectTypes,
-                lattice.fXCount, lattice.fYCount, lattice.fBounds, colorBuffer};
-    }
-
-    return lattice;
-}
diff --git a/src/core/SkColorSpaceXformer.h b/src/core/SkColorSpaceXformer.h
deleted file mode 100644
index 7d277d7..0000000
--- a/src/core/SkColorSpaceXformer.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkColorSpaceXformer_DEFINED
-#define SkColorSpaceXformer_DEFINED
-
-#include "SkCanvas.h"
-#include "SkColor.h"
-#include "SkColorSpaceXformSteps.h"
-#include "SkRasterPipeline.h"
-#include "SkRefCnt.h"
-#include "SkTHash.h"
-
-class SkBitmap;
-class SkColorFilter;
-class SkColorSpace;
-class SkImage;
-class SkImageFilter;
-class SkPaint;
-class SkShader;
-
-class SkColorSpaceXformer : public SkNoncopyable {
-public:
-    static std::unique_ptr<SkColorSpaceXformer> Make(sk_sp<SkColorSpace> dst);
-
-    ~SkColorSpaceXformer();
-
-    sk_sp<SkImage> apply(const SkImage*);
-    sk_sp<SkImage> apply(const SkBitmap&);
-    sk_sp<SkColorFilter> apply(const SkColorFilter*);
-    sk_sp<SkImageFilter> apply(const SkImageFilter*);
-    sk_sp<SkShader>      apply(const SkShader*);
-    SkPaint apply(const SkPaint&);
-    void apply(SkColor dst[], const SkColor src[], int n);
-    SkColor apply(SkColor srgb);
-
-    sk_sp<SkColorSpace> dst() const { return fDst; }
-
-    SkCanvas::Lattice apply(const SkCanvas::Lattice&, SkColor*, int);
-
-private:
-    explicit SkColorSpaceXformer(sk_sp<SkColorSpace> dst);
-
-    template <typename T>
-    using Cache = SkTHashMap<sk_sp<T>, sk_sp<T>>;
-
-    template <typename T>
-    sk_sp<T> cachedApply(const T*, Cache<T>*, sk_sp<T> (*)(const T*, SkColorSpaceXformer*));
-
-    void purgeCaches();
-
-    class AutoCachePurge;
-
-    sk_sp<SkColorSpace>                                 fDst;
-    SkSTArenaAlloc<256>                                 fAlloc;
-    std::function<void(size_t, size_t, size_t, size_t)> fFromSRGB;
-    SkColorSpaceXformSteps                              fFromSRGBSteps;
-    SkRasterPipeline_MemoryCtx                          fFromSRGBSrc{nullptr,0};
-    SkRasterPipeline_MemoryCtx                          fFromSRGBDst{nullptr,0};
-
-    size_t fReentryCount; // tracks the number of nested apply() calls for cache purging.
-
-    Cache<SkImage      > fImageCache;
-    Cache<SkColorFilter> fColorFilterCache;
-    Cache<SkImageFilter> fImageFilterCache;
-};
-
-#endif
diff --git a/src/core/SkContourMeasure.cpp b/src/core/SkContourMeasure.cpp
index 5252655..cd4dc93 100644
--- a/src/core/SkContourMeasure.cpp
+++ b/src/core/SkContourMeasure.cpp
@@ -169,9 +169,6 @@
 
 SkScalar SkContourMeasureIter::compute_quad_segs(const SkPoint pts[3], SkScalar distance,
                                                  int mint, int maxt, unsigned ptIndex) {
-#if defined(IS_FUZZING_WITH_LIBFUZZER)
-    --fSubdivisionsMax;
-#endif
     if (tspan_big_enough(maxt - mint) && quad_too_curvy(pts, fTolerance)) {
         SkPoint tmp[5];
         int     halft = (mint + maxt) >> 1;
@@ -184,6 +181,7 @@
         SkScalar prevD = distance;
         distance += d;
         if (distance > prevD) {
+            SkASSERT(ptIndex < (unsigned)fPts.count());
             SkContourMeasure::Segment* seg = fSegments.append();
             seg->fDistance = distance;
             seg->fPtIndex = ptIndex;
@@ -198,9 +196,6 @@
                                                   int mint, const SkPoint& minPt,
                                                   int maxt, const SkPoint& maxPt,
                                                   unsigned ptIndex) {
-#if defined(IS_FUZZING_WITH_LIBFUZZER)
-    --fSubdivisionsMax;
-#endif
     int halft = (mint + maxt) >> 1;
     SkPoint halfPt = conic.evalAt(tValue2Scalar(halft));
     if (!halfPt.isFinite()) {
@@ -214,6 +209,7 @@
         SkScalar prevD = distance;
         distance += d;
         if (distance > prevD) {
+            SkASSERT(ptIndex < (unsigned)fPts.count());
             SkContourMeasure::Segment* seg = fSegments.append();
             seg->fDistance = distance;
             seg->fPtIndex = ptIndex;
@@ -226,9 +222,6 @@
 
 SkScalar SkContourMeasureIter::compute_cubic_segs(const SkPoint pts[4], SkScalar distance,
                                                   int mint, int maxt, unsigned ptIndex) {
-#if defined(IS_FUZZING_WITH_LIBFUZZER)
-    --fSubdivisionsMax;
-#endif
     if (tspan_big_enough(maxt - mint) && cubic_too_curvy(pts, fTolerance)) {
         SkPoint tmp[7];
         int     halft = (mint + maxt) >> 1;
@@ -241,6 +234,7 @@
         SkScalar prevD = distance;
         distance += d;
         if (distance > prevD) {
+            SkASSERT(ptIndex < (unsigned)fPts.count());
             SkContourMeasure::Segment* seg = fSegments.append();
             seg->fDistance = distance;
             seg->fPtIndex = ptIndex;
@@ -251,13 +245,29 @@
     return distance;
 }
 
+SkScalar SkContourMeasureIter::compute_line_seg(SkPoint p0, SkPoint p1, SkScalar distance,
+                                                unsigned ptIndex) {
+    SkScalar d = SkPoint::Distance(p0, p1);
+    SkASSERT(d >= 0);
+    SkScalar prevD = distance;
+    distance += d;
+    if (distance > prevD) {
+        SkASSERT((unsigned)ptIndex < (unsigned)fPts.count());
+        SkContourMeasure::Segment* seg = fSegments.append();
+        seg->fDistance = distance;
+        seg->fPtIndex = ptIndex;
+        seg->fType = kLine_SegType;
+        seg->fTValue = kMaxTValue;
+    }
+    return distance;
+}
+
 SkContourMeasure* SkContourMeasureIter::buildSegments() {
-    SkPoint         pts[4];
-    int             ptIndex = -1;
-    SkScalar        distance = 0;
-    bool            isClosed = fForceClosed;
-    bool            firstMoveTo = true;
-    SkContourMeasure::Segment*        seg;
+    SkPoint     pts[4];
+    int         ptIndex = -1;
+    SkScalar    distance = 0;
+    bool        haveSeenClose = fForceClosed;
+    bool        haveSeenMoveTo = false;
 
     /*  Note:
      *  as we accumulate distance, we have to check that the result of +=
@@ -266,40 +276,35 @@
      *
      *  We do this check below, and in compute_quad_segs and compute_cubic_segs
      */
+
     fSegments.reset();
+    fPts.reset();
+
     bool done = false;
- #if defined(IS_FUZZING_WITH_LIBFUZZER)
-    fSubdivisionsMax = 10000000;
-#endif
     do {
-        if (!firstMoveTo && fIter.peekVerb() == SkPath::kMove_Verb) {
+        if (haveSeenMoveTo && fIter.peek() == SkPath::kMove_Verb) {
             break;
         }
         switch (fIter.next(pts)) {
             case SkPath::kMove_Verb:
                 ptIndex += 1;
                 fPts.append(1, pts);
-                SkASSERT(firstMoveTo);
-                firstMoveTo = false;
+                SkASSERT(!haveSeenMoveTo);
+                haveSeenMoveTo = true;
                 break;
 
             case SkPath::kLine_Verb: {
-                SkScalar d = SkPoint::Distance(pts[0], pts[1]);
-                SkASSERT(d >= 0);
+                SkASSERT(haveSeenMoveTo);
                 SkScalar prevD = distance;
-                distance += d;
+                distance = this->compute_line_seg(pts[0], pts[1], distance, ptIndex);
                 if (distance > prevD) {
-                    seg = fSegments.append();
-                    seg->fDistance = distance;
-                    seg->fPtIndex = ptIndex;
-                    seg->fType = kLine_SegType;
-                    seg->fTValue = kMaxTValue;
                     fPts.append(1, pts + 1);
                     ptIndex++;
                 }
             } break;
 
             case SkPath::kQuad_Verb: {
+                SkASSERT(haveSeenMoveTo);
                 SkScalar prevD = distance;
                 distance = this->compute_quad_segs(pts, distance, 0, kMaxTValue, ptIndex);
                 if (distance > prevD) {
@@ -309,6 +314,7 @@
             } break;
 
             case SkPath::kConic_Verb: {
+                SkASSERT(haveSeenMoveTo);
                 const SkConic conic(pts, fIter.conicWeight());
                 SkScalar prevD = distance;
                 distance = this->compute_conic_segs(conic, distance, 0, conic.fPts[0],
@@ -324,6 +330,7 @@
             } break;
 
             case SkPath::kCubic_Verb: {
+                SkASSERT(haveSeenMoveTo);
                 SkScalar prevD = distance;
                 distance = this->compute_cubic_segs(pts, distance, 0, kMaxTValue, ptIndex);
                 if (distance > prevD) {
@@ -333,18 +340,13 @@
             } break;
 
             case SkPath::kClose_Verb:
-                isClosed = true;
+                haveSeenClose = true;
                 break;
 
             case SkPath::kDone_Verb:
                 done = true;
                 break;
         }
-#if defined(IS_FUZZING_WITH_LIBFUZZER)
-        if (fSubdivisionsMax < 0) {
-            return nullptr;
-        }
-#endif
 
     } while (!done);
 
@@ -354,11 +356,16 @@
     if (fSegments.count() == 0) {
         return nullptr;
     }
-#if defined(IS_FUZZING_WITH_LIBFUZZER)
-    if (fSubdivisionsMax < 0) {
-        return nullptr;
+
+    // Handle the close segment ourselves, since we're using RawIter
+    if (haveSeenClose) {
+        SkScalar prevD = distance;
+        SkPoint firstPt = fPts[0];
+        distance = this->compute_line_seg(fPts[ptIndex], firstPt, distance, ptIndex);
+        if (distance > prevD) {
+            *fPts.append() = firstPt;
+        }
     }
-#endif
 
 #ifdef SK_DEBUG
     {
@@ -388,7 +395,7 @@
     }
 #endif
 
-    return new SkContourMeasure(std::move(fSegments), std::move(fPts), distance, isClosed);
+    return new SkContourMeasure(std::move(fSegments), std::move(fPts), distance, haveSeenClose);
 }
 
 static void compute_pos_tan(const SkPoint pts[], unsigned segType,
@@ -441,7 +448,7 @@
     fTolerance = CHEAP_DIST_LIMIT * SkScalarInvert(resScale);
     fForceClosed = forceClosed;
 
-    fIter.setPath(fPath, forceClosed);
+    fIter.setPath(fPath);
 }
 
 SkContourMeasureIter::~SkContourMeasureIter() {}
@@ -456,13 +463,13 @@
     }
     fForceClosed = forceClosed;
 
-    fIter.setPath(fPath, forceClosed);
+    fIter.setPath(fPath);
     fSegments.reset();
     fPts.reset();
 }
 
 sk_sp<SkContourMeasure> SkContourMeasureIter::next() {
-    while (fIter.peekVerb() != SkPath::kDone_Verb) {
+    while (fIter.peek() != SkPath::kDone_Verb) {
         auto cm = this->buildSegments();
         if (cm) {
             return sk_sp<SkContourMeasure>(cm);
@@ -563,6 +570,7 @@
         return false;
     }
 
+    SkASSERT((unsigned)seg->fPtIndex < (unsigned)fPts.count());
     compute_pos_tan(&fPts[seg->fPtIndex], seg->fType, t, pos, tangent);
     return true;
 }
diff --git a/src/core/SkConvertPixels.cpp b/src/core/SkConvertPixels.cpp
index 2cf7f19..3805d3e 100644
--- a/src/core/SkConvertPixels.cpp
+++ b/src/core/SkConvertPixels.cpp
@@ -132,6 +132,7 @@
             return true;
         }
 
+        case kRGBA_F16Norm_SkColorType:
         case kRGBA_F16_SkColorType: {
             auto src64 = (const uint64_t*) src;
             for (int y = 0; y < srcInfo.height(); y++) {
@@ -173,19 +174,6 @@
 
     pipeline.append_gamut_clamp_if_normalized(dstInfo);
 
-    // We'll dither if we're decreasing precision below 32-bit.
-    float dither_rate = 0.0f;
-    if (srcInfo.bytesPerPixel() > dstInfo.bytesPerPixel()) {
-        switch (dstInfo.colorType()) {
-            case   kRGB_565_SkColorType: dither_rate = 1/63.0f; break;
-            case kARGB_4444_SkColorType: dither_rate = 1/15.0f; break;
-            default:                     dither_rate =    0.0f; break;
-        }
-    }
-    if (dither_rate > 0) {
-        pipeline.append(SkRasterPipeline::dither, &dither_rate);
-    }
-
     pipeline.append_store(dstInfo.colorType(), &dst);
     pipeline.run(0,0, srcInfo.width(), srcInfo.height());
 }
diff --git a/src/core/SkCoverageDelta.cpp b/src/core/SkCoverageDelta.cpp
deleted file mode 100644
index 8ead98d..0000000
--- a/src/core/SkCoverageDelta.cpp
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkCoverageDelta.h"
-
-SkCoverageDeltaList::SkCoverageDeltaList(SkArenaAlloc* alloc, const SkIRect& bounds, bool forceRLE) {
-    fAlloc              = alloc;
-    fBounds             = bounds;
-    fForceRLE           = forceRLE;
-
-    int top             = bounds.fTop;
-    int bottom          = bounds.fBottom;
-
-    // Init the anti-rect to be empty
-    fAntiRect.fY        = bottom;
-    fAntiRect.fHeight   = 0;
-
-    fSorted     = fAlloc->makeArrayDefault<bool>(bottom - top);
-    fCounts     = fAlloc->makeArrayDefault<int>((bottom - top) * 2);
-    fMaxCounts  = fCounts + bottom - top;
-    fRows       = fAlloc->makeArrayDefault<SkCoverageDelta*>(bottom - top) - top;
-    fRows[top]  = fAlloc->makeArrayDefault<SkCoverageDelta>(INIT_ROW_SIZE * (bottom - top));
-
-    memset(fSorted, true, bottom - top);
-    memset(fCounts, 0, sizeof(int) * (bottom - top));
-
-    // Minus top so we can directly use fCounts[y] instead of fCounts[y - fTop].
-    // Same for fMaxCounts, fRows, and fSorted.
-    fSorted    -= top;
-    fCounts    -= top;
-    fMaxCounts -= top;
-
-    for(int y = top; y < bottom; ++y) {
-        fMaxCounts[y] = INIT_ROW_SIZE;
-    }
-    for(int y = top + 1; y < bottom; ++y) {
-        fRows[y] = fRows[y - 1] + INIT_ROW_SIZE;
-    }
-}
-
-int SkCoverageDeltaMask::ExpandWidth(int width) {
-    int result = width + PADDING * 2;
-    return result + (SIMD_WIDTH - result % SIMD_WIDTH) % SIMD_WIDTH;
-}
-
-bool SkCoverageDeltaMask::CanHandle(const SkIRect& bounds) {
-    // Return early if either width or height is very large because width * height might overflow.
-    if (bounds.width() >= MAX_MASK_SIZE || bounds.height() >= MAX_MASK_SIZE) {
-        return false;
-    }
-    // Expand width so we don't have to worry about the boundary
-    return ExpandWidth(bounds.width()) * bounds.height() + PADDING * 2 < MAX_MASK_SIZE;
-}
-
-bool SkCoverageDeltaMask::Suitable(const SkIRect& bounds) {
-    return bounds.width() <= SUITABLE_WIDTH && CanHandle(bounds);
-}
-
-SkCoverageDeltaMask::SkCoverageDeltaMask(SkArenaAlloc* alloc, const SkIRect& bounds) {
-    SkASSERT(CanHandle(bounds));
-
-    fBounds             = bounds;
-
-    // Init the anti-rect to be empty
-    fAntiRect.fY        = fBounds.fBottom;
-    fAntiRect.fHeight   = 0;
-
-    fExpandedWidth      = ExpandWidth(fBounds.width());
-
-    int size            = fExpandedWidth * bounds.height() + PADDING * 2;
-    fDeltaStorage       = alloc->makeArray<SkFixed>(size);
-    fMask               = alloc->makeArrayDefault<SkAlpha>(size);
-
-    // Add PADDING columns so we may access fDeltas[index(-PADDING, 0)]
-    // Minus index(fBounds.fLeft, fBounds.fTop) so we can directly access fDeltas[index(x, y)]
-    fDeltas             = fDeltaStorage + PADDING - this->index(fBounds.fLeft, fBounds.fTop);
-}
-
-// TODO As this function is so performance-critical (and we're thinking so much about SIMD), use
-// SkOpts framework to compile multiple versions of this function so we can choose the best one
-// available at runtime.
-void SkCoverageDeltaMask::convertCoverageToAlpha(bool isEvenOdd, bool isInverse, bool isConvex) {
-    SkFixed* deltaRow = &this->delta(fBounds.fLeft, fBounds.fTop);
-    SkAlpha* maskRow = fMask;
-    for(int iy = 0; iy < fBounds.height(); ++iy) {
-        // If we're inside fAntiRect, blit it to the mask and advance to its bottom
-        if (fAntiRect.fHeight && iy == fAntiRect.fY - fBounds.fTop) {
-            // Blit the mask
-            int L = fAntiRect.fX - fBounds.fLeft;
-            for(int i = 0; i < fAntiRect.fHeight; ++i) {
-                sk_bzero(maskRow, fBounds.width());
-                SkAlpha* tMask = maskRow + L;
-                if (fAntiRect.fLeftAlpha) {
-                    tMask[0] = fAntiRect.fLeftAlpha;
-                }
-                memset(tMask + 1, 0xff, fAntiRect.fWidth);
-                if (fAntiRect.fRightAlpha) {
-                    tMask[fAntiRect.fWidth + 1] = fAntiRect.fRightAlpha;
-                }
-                maskRow += fBounds.width();
-            }
-
-            // Advance to the bottom (maskRow is already advanced to the bottom).
-            deltaRow    += fExpandedWidth * fAntiRect.fHeight;
-            iy          += fAntiRect.fHeight - 1; // -1 because we'll ++iy after continue
-            continue;
-        }
-
-        // Otherwise, cumulate deltas into coverages, and convert them into alphas
-        SkFixed c[SIMD_WIDTH] = {0}; // prepare SIMD_WIDTH coverages at a time
-        for(int ix = 0; ix < fExpandedWidth; ix += SIMD_WIDTH) {
-            // Future todo: is it faster to process SIMD_WIDTH rows at a time so we can use SIMD
-            // for coverage accumulation?
-
-            // Cumulate deltas to get SIMD_WIDTH new coverages
-            c[0] = c[SIMD_WIDTH - 1] + deltaRow[ix];
-            for(int j = 1; j < SIMD_WIDTH; ++j) {
-                c[j] = c[j - 1] + deltaRow[ix + j];
-            }
-
-            using SkNi = SkNx<SIMD_WIDTH, int>;
-            SkNi cn = SkNi::Load(c);
-            SkNi an = isConvex ? ConvexCoverageToAlpha(cn, isInverse)
-                               : CoverageToAlpha(cn, isEvenOdd, isInverse);
-            SkNx_cast<SkAlpha>(an).store(maskRow + ix);
-        }
-
-        // Finally, advance to the next row
-        deltaRow    += fExpandedWidth;
-        maskRow     += fBounds.width();
-    }
-}
diff --git a/src/core/SkCoverageDelta.h b/src/core/SkCoverageDelta.h
deleted file mode 100644
index e2dc06c..0000000
--- a/src/core/SkCoverageDelta.h
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkCoverageDelta_DEFINED
-#define SkCoverageDelta_DEFINED
-
-#include "SkArenaAlloc.h"
-#include "SkFixed.h"
-#include "SkMask.h"
-#include "SkTSort.h"
-#include "SkUtils.h"
-
-// Future todo: maybe we can make fX and fDelta 16-bit long to speed it up a little bit.
-struct SkCoverageDelta {
-    int     fX;     // the y coordinate will be implied in SkCoverageDeltaList
-    SkFixed fDelta; // the amount that the alpha changed
-
-    // Sort according to fX
-    bool operator<(const SkCoverageDelta& other) const {
-        return fX < other.fX;
-    }
-};
-
-// All the arguments needed for SkBlitter::blitAntiRect
-struct SkAntiRect {
-    int     fX;
-    int     fY;
-    int     fWidth;
-    int     fHeight;
-    SkAlpha fLeftAlpha;
-    SkAlpha fRightAlpha;
-};
-
-// A list of SkCoverageDelta with y from top() to bottom().
-// For each row y, there are count(y) number of deltas.
-// You can ask whether they are sorted or not by sorted(y), and you can sort them by sort(y).
-// Once sorted, getDelta(y, i) should return the i-th leftmost delta on row y.
-class SkCoverageDeltaList {
-public:
-    // We can store INIT_ROW_SIZE deltas per row (i.e., per y-scanline) initially.
-#ifdef SK_BUILD_FOR_GOOGLE3
-    static constexpr int INIT_ROW_SIZE = 8; // google3 has 16k stack limit; so we make it small
-#else
-    static constexpr int INIT_ROW_SIZE = 32;
-#endif
-
-    SkCoverageDeltaList(SkArenaAlloc* alloc, const SkIRect& bounds, bool forceRLE);
-
-    int  top() const { return fBounds.fTop; }
-    int  bottom() const { return fBounds.fBottom; }
-    int  left() const { return fBounds.fLeft; }
-    int  right() const { return fBounds.fRight; }
-    bool forceRLE() const { return fForceRLE; }
-    int  count(int y) const { this->checkY(y); return fCounts[y]; }
-    bool sorted(int y) const { this->checkY(y); return fSorted[y]; }
-
-    SK_ALWAYS_INLINE void addDelta(int x, int y, SkFixed delta) { this->push_back(y, {x, delta}); }
-    SK_ALWAYS_INLINE const SkCoverageDelta& getDelta(int y, int i) const {
-        this->checkY(y);
-        SkASSERT(i < fCounts[y]);
-        return fRows[y][i];
-    }
-
-    // It might be better to sort right before blitting to make the memory hot
-    void sort(int y) {
-        this->checkY(y);
-        if (!fSorted[y]) {
-            SkTQSort(fRows[y], fRows[y] + fCounts[y] - 1);
-            fSorted[y] = true;
-        }
-    }
-
-    const SkAntiRect& getAntiRect() const { return fAntiRect; }
-    void setAntiRect(int x, int y, int width, int height,
-            SkAlpha leftAlpha, SkAlpha rightAlpha) {
-        fAntiRect = {x, y, width, height, leftAlpha, rightAlpha};
-    }
-
-private:
-    SkArenaAlloc*               fAlloc;
-    SkCoverageDelta**           fRows;
-    bool*                       fSorted;
-    int*                        fCounts;
-    int*                        fMaxCounts;
-    SkIRect                     fBounds;
-    SkAntiRect                  fAntiRect;
-    bool                        fForceRLE;
-
-    void checkY(int y) const { SkASSERT(y >= fBounds.fTop && y < fBounds.fBottom); }
-
-    SK_ALWAYS_INLINE void push_back(int y, const SkCoverageDelta& delta) {
-        this->checkY(y);
-        if (fCounts[y] == fMaxCounts[y]) {
-            fMaxCounts[y] *= 4;
-            SkCoverageDelta* newRow = fAlloc->makeArrayDefault<SkCoverageDelta>(fMaxCounts[y]);
-            memcpy(newRow, fRows[y], sizeof(SkCoverageDelta) * fCounts[y]);
-            fRows[y] = newRow;
-        }
-        SkASSERT(fCounts[y] < fMaxCounts[y]);
-        fRows[y][fCounts[y]++] = delta;
-        fSorted[y] = fSorted[y] && (fCounts[y] == 1 || delta.fX >= fRows[y][fCounts[y] - 2].fX);
-    }
-};
-
-class SkCoverageDeltaMask {
-public:
-    // 3 for precision error, 1 for boundary delta (e.g., -SK_Fixed1 at fBounds.fRight + 1)
-    static constexpr int PADDING        = 4;
-
-    static constexpr int SIMD_WIDTH     = 8;
-    static constexpr int SUITABLE_WIDTH = 32;
-#ifdef SK_BUILD_FOR_GOOGLE3
-    static constexpr int MAX_MASK_SIZE  = 1024; // G3 has 16k stack limit based on -fstack-usage
-#else
-    static constexpr int MAX_MASK_SIZE  = 2048;
-#endif
-    static constexpr int MAX_SIZE       = MAX_MASK_SIZE * (sizeof(SkFixed) + sizeof(SkAlpha));
-
-    // Expand PADDING on both sides, and make it a multiple of SIMD_WIDTH
-    static int  ExpandWidth(int width);
-    static bool CanHandle(const SkIRect& bounds);   // whether bounds fits into MAX_MASK_SIZE
-    static bool Suitable(const SkIRect& bounds);    // CanHandle(bounds) && width <= SUITABLE_WIDTH
-
-    SkCoverageDeltaMask(SkArenaAlloc* alloc, const SkIRect& bounds);
-
-    int              top()       const { return fBounds.fTop; }
-    int              bottom()    const { return fBounds.fBottom; }
-    SkAlpha*         getMask()         { return fMask; }
-    const SkIRect&   getBounds() const { return fBounds; }
-
-    SK_ALWAYS_INLINE void addDelta (int x, int y, SkFixed delta) { this->delta(x, y) += delta; }
-    SK_ALWAYS_INLINE SkFixed& delta (int x, int y) {
-        this->checkX(x);
-        this->checkY(y);
-        return fDeltas[this->index(x, y)];
-    }
-
-    void setAntiRect(int x, int y, int width, int height,
-                            SkAlpha leftAlpha, SkAlpha rightAlpha) {
-        fAntiRect = {x, y, width, height, leftAlpha, rightAlpha};
-    }
-
-    SkMask prepareSkMask() {
-        SkMask mask;
-        mask.fImage     = fMask;
-        mask.fBounds    = fBounds;
-        mask.fRowBytes  = fBounds.width();
-        mask.fFormat    = SkMask::kA8_Format;
-        return mask;
-    }
-
-    void convertCoverageToAlpha(bool isEvenOdd, bool isInverse, bool isConvex);
-
-private:
-    SkIRect     fBounds;
-    SkFixed*    fDeltaStorage;
-    SkFixed*    fDeltas;
-    SkAlpha*    fMask;
-    int         fExpandedWidth;
-    SkAntiRect  fAntiRect;
-
-    SK_ALWAYS_INLINE int index(int x, int y) const { return y * fExpandedWidth + x; }
-
-    void checkY(int y) const { SkASSERT(y >= fBounds.fTop && y < fBounds.fBottom); }
-    void checkX(int x) const {
-        SkASSERT(x >= fBounds.fLeft - PADDING && x < fBounds.fRight + PADDING);
-    }
-};
-
-static SK_ALWAYS_INLINE SkAlpha CoverageToAlpha(SkFixed coverage, bool isEvenOdd, bool isInverse) {
-    SkAlpha result;
-    if (isEvenOdd) {
-        SkFixed mod17 = coverage & 0x1ffff;
-        SkFixed mod16 = coverage & 0xffff;
-        result = SkTPin(SkAbs32((mod16 << 1) - mod17) >> 8, 0, 255);
-    } else {
-        result = SkTPin(SkAbs32(coverage) >> 8, 0, 255);
-    }
-    return isInverse ? 255 - result : result;
-}
-
-struct SkDAARecord {
-    enum class Type {
-        kToBeComputed,
-        kMask,
-        kList,
-        kEmpty
-    } fType;
-
-    SkMask               fMask;
-    SkCoverageDeltaList* fList;
-    SkArenaAlloc*        fAlloc;
-
-    SkDAARecord(SkArenaAlloc* alloc) : fType(Type::kToBeComputed), fAlloc(alloc) {}
-
-    // When the scan converter returns early (e.g., the path is completely out of the clip), we set
-    // the type to empty to signal that the record has been computed and it's empty. This is
-    // required only for DEBUG where we check that the type must not be kToBeComputed after
-    // init-once.
-    void setEmpty() { fType = Type::kEmpty; }
-    static inline void SetEmpty(SkDAARecord* record) { // record may be nullptr
-#ifdef SK_DEBUG
-        // If type != kToBeComputed, then we're in the draw phase and we shouldn't set it to empty
-        // because being empty in one tile does not imply emptiness in other tiles.
-        if (record && record->fType == Type::kToBeComputed) {
-            record->setEmpty();
-        }
-#endif
-    }
-};
-
-template<typename T>
-static SK_ALWAYS_INLINE T CoverageToAlpha(const T&  coverage, bool isEvenOdd, bool isInverse) {
-    T t0(0), t255(255);
-    T result;
-    if (isEvenOdd) {
-        T mod17 = coverage & 0x1ffff;
-        T mod16 = coverage & 0xffff;
-        result = ((mod16 << 1) - mod17).abs() >> 8;
-    } else {
-        result = coverage.abs() >> 8;
-    }
-    result = T::Min(result, t255);
-    result = T::Max(result, t0);
-    return isInverse ? 255 - result : result;
-}
-
-// For convex paths (including inverse mode), the coverage is guaranteed to be
-// between [-SK_Fixed1, SK_Fixed1] so we can skip isEvenOdd and SkTPin.
-static SK_ALWAYS_INLINE SkAlpha ConvexCoverageToAlpha(SkFixed coverage, bool isInverse) {
-    SkASSERT(coverage >= -SK_Fixed1 && coverage <= SK_Fixed1);
-    int result = SkAbs32(coverage) >> 8;
-    result -= (result >> 8); // 256 to 255
-    return isInverse ? 255 - result : result;
-}
-
-template<typename T>
-static SK_ALWAYS_INLINE T ConvexCoverageToAlpha(const T& coverage, bool isInverse) {
-    // allTrue is not implemented
-    // SkASSERT((coverage >= 0).allTrue() && (coverage <= SK_Fixed1).allTrue());
-    T result = coverage.abs() >> 8;
-    result -= (result >> 8); // 256 to 255
-    return isInverse ? 255 - result : result;
-}
-
-#endif
diff --git a/src/core/SkCubicMap.cpp b/src/core/SkCubicMap.cpp
index a390d44..7656009 100644
--- a/src/core/SkCubicMap.cpp
+++ b/src/core/SkCubicMap.cpp
@@ -181,21 +181,22 @@
     float b = fCoeff[1].fY;
     float c = fCoeff[2].fY;
     float y = ((a * t + b) * t + c) * t;
-    SkASSERT(y >= 0);
-    return std::min(y, 1.0f);
+
+    return y;
 }
 
 static inline bool coeff_nearly_zero(float delta) {
     return sk_float_abs(delta) <= 0.0000001f;
 }
 
-void SkCubicMap::setPts(SkPoint p1, SkPoint p2) {
+SkCubicMap::SkCubicMap(SkPoint p1, SkPoint p2) {
+    // Clamp X values only (we allow Ys outside [0..1]).
+    p1.fX = SkTMin(SkTMax(p1.fX, 0.0f), 1.0f);
+    p2.fX = SkTMin(SkTMax(p2.fX, 0.0f), 1.0f);
+
     Sk2s s1 = Sk2s::Load(&p1) * 3;
     Sk2s s2 = Sk2s::Load(&p2) * 3;
 
-    s1 = Sk2s::Min(Sk2s::Max(s1, 0), 3);
-    s2 = Sk2s::Min(Sk2s::Max(s2, 0), 3);
-
     (Sk2s(1) + s1 - s2).store(&fCoeff[0]);
     (s2 - s1 - s1).store(&fCoeff[1]);
     s1.store(&fCoeff[2]);
diff --git a/src/core/SkCubicMap.h b/src/core/SkCubicMap.h
deleted file mode 100644
index 86f5762..0000000
--- a/src/core/SkCubicMap.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkCubicMap_DEFINED
-#define SkCubicMap_DEFINED
-
-#include "SkPoint.h"
-
-/**
- *  Fast evaluation of a cubic ease-in / ease-out curve. This is defined as a parametric cubic
- *  curve inside the unit square.
- *
- *  pt[0] is implicitly { 0, 0 }
- *  pt[3] is implicitly { 1, 1 }
- *  pts[1,2] are inside the unit square
- */
-class SkCubicMap {
-public:
-    SkCubicMap() {} // must call setPts() before using
-
-    SkCubicMap(SkPoint p1, SkPoint p2) {
-        this->setPts(p1, p2);
-    }
-
-    void setPts(SkPoint p1, SkPoint p2);
-
-    float computeYFromX(float x) const;
-
-    SkPoint computeFromT(float t) const;
-
-private:
-    enum Type {
-        kLine_Type,     // x == y
-        kCubeRoot_Type, // At^3 == x
-        kSolver_Type,   // general monotonic cubic solver
-    };
-    SkPoint fCoeff[3];
-    Type    fType;
-};
-
-#endif
-
diff --git a/src/core/SkDeferredDisplayListRecorder.cpp b/src/core/SkDeferredDisplayListRecorder.cpp
index 1d7c970..6bf3390 100644
--- a/src/core/SkDeferredDisplayListRecorder.cpp
+++ b/src/core/SkDeferredDisplayListRecorder.cpp
@@ -35,7 +35,7 @@
         PromiseImageTextureReleaseProc textureReleaseProc,
         PromiseImageTextureDoneProc textureDoneProc,
         PromiseImageTextureContext textureContext,
-        DelayReleaseCallback delayReleaseCallback) {
+        PromiseImageApiVersion) {
     return nullptr;
 }
 
@@ -52,7 +52,7 @@
         PromiseImageTextureReleaseProc textureReleaseProc,
         PromiseImageTextureDoneProc textureDoneProc,
         PromiseImageTextureContext textureContexts[],
-        DelayReleaseCallback delayReleaseCallback) {
+        PromiseImageApiVersion) {
     return nullptr;
 }
 
@@ -117,6 +117,18 @@
         }
     }
 
+    if (fCharacterization.vulkanSecondaryCBCompatible()) {
+        // Because of the restrictive API allowed for a GrVkSecondaryCBDrawContext, we know ahead
+        // of time that we don't be able to support certain parameter combinations. Specifially we
+        // fail on usesGLFBO0 since we can't mix GL and Vulkan. We can't have a texturable object.
+        // And finally the GrVkSecondaryCBDrawContext always assumes a top left origin.
+        if (usesGLFBO0 ||
+            fCharacterization.isTextureable() ||
+            fCharacterization.origin() == kBottomLeft_GrSurfaceOrigin) {
+            return false;
+        }
+    }
+
     GrSurfaceDesc desc;
     desc.fFlags = kRenderTarget_GrSurfaceFlag;
     desc.fWidth = fCharacterization.width();
@@ -150,14 +162,11 @@
 
     sk_sp<GrRenderTargetProxy> proxy = proxyProvider->createLazyRenderTargetProxy(
             [lazyProxyData](GrResourceProvider* resourceProvider) {
-                if (!resourceProvider) {
-                    return sk_sp<GrSurface>();
-                }
-
                 // The proxy backing the destination surface had better have been instantiated
                 // prior to the proxy backing the DLL's surface. Steal its GrRenderTarget.
                 SkASSERT(lazyProxyData->fReplayDest->peekSurface());
-                return sk_ref_sp<GrSurface>(lazyProxyData->fReplayDest->peekSurface());
+                auto surface = sk_ref_sp<GrSurface>(lazyProxyData->fReplayDest->peekSurface());
+                return GrSurfaceProxy::LazyInstantiationResult(std::move(surface));
             },
             format,
             desc,
@@ -165,7 +174,8 @@
             surfaceFlags,
             optionalTextureInfo,
             SkBackingFit::kExact,
-            SkBudgeted::kYes);
+            SkBudgeted::kYes,
+            fCharacterization.vulkanSecondaryCBCompatible());
 
     sk_sp<GrSurfaceContext> c = fContext->priv().makeWrappedSurfaceContext(
                                                                  std::move(proxy),
@@ -223,7 +233,7 @@
         PromiseImageTextureReleaseProc textureReleaseProc,
         PromiseImageTextureDoneProc textureDoneProc,
         PromiseImageTextureContext textureContext,
-        DelayReleaseCallback delayReleaseCallback) {
+        PromiseImageApiVersion version) {
     if (!fContext) {
         return nullptr;
     }
@@ -241,7 +251,7 @@
                                            textureReleaseProc,
                                            textureDoneProc,
                                            textureContext,
-                                           delayReleaseCallback);
+                                           version);
 }
 
 sk_sp<SkImage> SkDeferredDisplayListRecorder::makeYUVAPromiseTexture(
@@ -257,7 +267,7 @@
         PromiseImageTextureReleaseProc textureReleaseProc,
         PromiseImageTextureDoneProc textureDoneProc,
         PromiseImageTextureContext textureContexts[],
-        DelayReleaseCallback delayReleaseCallback) {
+        PromiseImageApiVersion version) {
     if (!fContext) {
         return nullptr;
     }
@@ -275,7 +285,7 @@
                                                    textureReleaseProc,
                                                    textureDoneProc,
                                                    textureContexts,
-                                                   delayReleaseCallback);
+                                                   version);
 }
 
 #endif
diff --git a/src/core/SkDeque.cpp b/src/core/SkDeque.cpp
index 167ce46..d772a4f 100644
--- a/src/core/SkDeque.cpp
+++ b/src/core/SkDeque.cpp
@@ -157,10 +157,10 @@
 
     if (first->fBegin == nullptr) {  // we were marked empty from before
         first = first->fNext;
+        SkASSERT(first != nullptr);    // else we popped too far
         first->fPrev = nullptr;
         this->freeBlock(fFrontBlock);
         fFrontBlock = first;
-        SkASSERT(first != nullptr);    // else we popped too far
     }
 
     char* begin = first->fBegin + fElemSize;
@@ -191,10 +191,10 @@
 
     if (last->fEnd == nullptr) {  // we were marked empty from before
         last = last->fPrev;
+        SkASSERT(last != nullptr);  // else we popped too far
         last->fNext = nullptr;
         this->freeBlock(fBackBlock);
         fBackBlock = last;
-        SkASSERT(last != nullptr);  // else we popped too far
     }
 
     char* end = last->fEnd - fElemSize;
diff --git a/src/core/SkDescriptor.cpp b/src/core/SkDescriptor.cpp
index 572b9be..e71471b 100644
--- a/src/core/SkDescriptor.cpp
+++ b/src/core/SkDescriptor.cpp
@@ -85,6 +85,22 @@
     return SkOpts::hash(ptr, len);
 }
 
+bool SkDescriptor::isValid() const {
+    uint32_t count = 0;
+    size_t offset = sizeof(SkDescriptor);
+
+    while (offset < fLength) {
+        const Entry* entry = (const Entry*)(reinterpret_cast<const char*>(this) + offset);
+        // rec tags are always a known size.
+        if (entry->fTag == kRec_SkDescriptorTag && entry->fLen != sizeof(SkScalerContextRec)) {
+            return false;
+        }
+        offset += sizeof(Entry) + entry->fLen;
+        count++;
+    }
+    return offset <= fLength && count == fCount;
+}
+
 SkAutoDescriptor::SkAutoDescriptor() = default;
 SkAutoDescriptor::SkAutoDescriptor(size_t size) { this->reset(size); }
 SkAutoDescriptor::SkAutoDescriptor(const SkDescriptor& desc) { this->reset(desc); }
diff --git a/src/core/SkDescriptor.h b/src/core/SkDescriptor.h
index 58b82e5..88ec062 100644
--- a/src/core/SkDescriptor.h
+++ b/src/core/SkDescriptor.h
@@ -33,6 +33,9 @@
     void* addEntry(uint32_t tag, size_t length, const void* data = nullptr);
     void computeChecksum();
 
+    // Assumes that getLength <= capacity of this SkDescriptor.
+    bool isValid() const;
+
 #ifdef SK_DEBUG
     void assertChecksum() const {
         SkASSERT(SkDescriptor::ComputeChecksum(this) == fChecksum);
@@ -62,6 +65,7 @@
 private:
     // private so no one can create one except our factories
     SkDescriptor() = default;
+    friend class SkDescriptorTestHelper;
 
     static uint32_t ComputeChecksum(const SkDescriptor* desc);
 
diff --git a/src/core/SkDevice.cpp b/src/core/SkDevice.cpp
index e6cb9f0..a52bd84 100644
--- a/src/core/SkDevice.cpp
+++ b/src/core/SkDevice.cpp
@@ -128,16 +128,6 @@
     this->drawPath(path, paint, true);
 }
 
-void SkBaseDevice::drawEdgeAARect(const SkRect& r, SkCanvas::QuadAAFlags aa, SkColor color,
-                                  SkBlendMode mode) {
-    SkPaint paint;
-    paint.setColor(color);
-    paint.setBlendMode(mode);
-    paint.setAntiAlias(aa == SkCanvas::kAll_QuadAAFlags);
-
-    this->drawRect(r, paint);
-}
-
 void SkBaseDevice::drawPatch(const SkPoint cubics[12], const SkColor colors[4],
                              const SkPoint texCoords[4], SkBlendMode bmode, const SkPaint& paint) {
     SkISize lod = SkPatchUtils::GetLevelOfDetail(cubics, &this->ctm());
@@ -148,14 +138,6 @@
     }
 }
 
-void SkBaseDevice::drawImage(const SkImage* image, SkScalar x, SkScalar y,
-                             const SkPaint& paint) {
-    SkBitmap bm;
-    if (as_IB(image)->getROPixels(&bm)) {
-        this->drawBitmap(bm, x, y, paint);
-    }
-}
-
 void SkBaseDevice::drawImageRect(const SkImage* image, const SkRect* src,
                                  const SkRect& dst, const SkPaint& paint,
                                  SkCanvas::SrcRectConstraint constraint) {
@@ -212,22 +194,6 @@
     }
 }
 
-void SkBaseDevice::drawImageSet(const SkCanvas::ImageSetEntry images[], int count,
-                                SkFilterQuality filterQuality, SkBlendMode mode) {
-    SkPaint paint;
-    paint.setFilterQuality(SkTPin(filterQuality, kNone_SkFilterQuality, kLow_SkFilterQuality));
-    paint.setBlendMode(mode);
-    for (int i = 0; i < count; ++i) {
-        // TODO: Handle per-edge AA. Right now this mirrors the SkiaRenderer component of Chrome
-        // which turns off antialiasing unless all four edges should be antialiased. This avoids
-        // seaming in tiled composited layers.
-        paint.setAntiAlias(images[i].fAAFlags == SkCanvas::kAll_QuadAAFlags);
-        paint.setAlpha(SkToUInt(SkTClamp(SkScalarRoundToInt(images[i].fAlpha * 255), 0, 255)));
-        this->drawImageRect(images[i].fImage.get(), &images[i].fSrcRect, images[i].fDstRect, paint,
-                            SkCanvas::kFast_SrcRectConstraint);
-    }
-}
-
 void SkBaseDevice::drawBitmapLattice(const SkBitmap& bitmap,
                                      const SkCanvas::Lattice& lattice, const SkRect& dst,
                                      const SkPaint& paint) {
@@ -283,6 +249,70 @@
     this->drawVertices(builder.detach().get(), nullptr, 0, mode, p);
 }
 
+
+void SkBaseDevice::drawEdgeAAQuad(const SkRect& r, const SkPoint clip[4],
+                                  SkCanvas::QuadAAFlags aa, SkColor color, SkBlendMode mode) {
+    SkPaint paint;
+    paint.setColor(color);
+    paint.setBlendMode(mode);
+    paint.setAntiAlias(aa == SkCanvas::kAll_QuadAAFlags);
+
+    if (clip) {
+        // Draw the clip directly as a quad since it's a filled color with no local coords
+        SkPath clipPath;
+        clipPath.addPoly(clip, 4, true);
+        this->drawPath(clipPath, paint);
+    } else {
+        this->drawRect(r, paint);
+    }
+}
+
+void SkBaseDevice::drawEdgeAAImageSet(const SkCanvas::ImageSetEntry images[], int count,
+                                      const SkPoint dstClips[], const SkMatrix preViewMatrices[],
+                                      const SkPaint& paint,
+                                      SkCanvas::SrcRectConstraint constraint) {
+    SkASSERT(paint.getStyle() == SkPaint::kFill_Style);
+    SkASSERT(!paint.getPathEffect());
+
+    SkPaint entryPaint = paint;
+    const SkMatrix baseCTM = this->ctm();
+    int clipIndex = 0;
+    for (int i = 0; i < count; ++i) {
+        // TODO: Handle per-edge AA. Right now this mirrors the SkiaRenderer component of Chrome
+        // which turns off antialiasing unless all four edges should be antialiased. This avoids
+        // seaming in tiled composited layers.
+        entryPaint.setAntiAlias(images[i].fAAFlags == SkCanvas::kAll_QuadAAFlags);
+        entryPaint.setAlphaf(paint.getAlphaf() * images[i].fAlpha);
+
+        bool needsRestore = false;
+        SkASSERT(images[i].fMatrixIndex < 0 || preViewMatrices);
+        if (images[i].fMatrixIndex >= 0) {
+            this->save();
+            this->setGlobalCTM(SkMatrix::Concat(
+                    baseCTM, preViewMatrices[images[i].fMatrixIndex]));
+            needsRestore = true;
+        }
+
+        SkASSERT(!images[i].fHasClip || dstClips);
+        if (images[i].fHasClip) {
+            // Since drawImageRect requires a srcRect, the dst clip is implemented as a true clip
+            if (!needsRestore) {
+                this->save();
+                needsRestore = true;
+            }
+            SkPath clipPath;
+            clipPath.addPoly(dstClips + clipIndex, 4, true);
+            this->clipPath(clipPath, SkClipOp::kIntersect, entryPaint.isAntiAlias());
+            clipIndex += 4;
+        }
+        this->drawImageRect(images[i].fImage.get(), &images[i].fSrcRect, images[i].fDstRect,
+                            entryPaint, constraint);
+        if (needsRestore) {
+            this->restore(baseCTM);
+        }
+    }
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 void SkBaseDevice::drawDrawable(SkDrawable* drawable, const SkMatrix* matrix, SkCanvas* canvas) {
@@ -396,8 +426,10 @@
 
 //////////////////////////////////////////////////////////////////////////////////////////
 
-void SkBaseDevice::LogDrawScaleFactor(const SkMatrix& matrix, SkFilterQuality filterQuality) {
+void SkBaseDevice::LogDrawScaleFactor(const SkMatrix& view, const SkMatrix& srcToDst,
+                                      SkFilterQuality filterQuality) {
 #if SK_HISTOGRAMS_ENABLED
+    SkMatrix matrix = SkMatrix::Concat(view, srcToDst);
     enum ScaleFactor {
         kUpscale_ScaleFactor,
         kNoScale_ScaleFactor,
diff --git a/src/core/SkDevice.h b/src/core/SkDevice.h
index 9f2d7fd..5a0f246 100644
--- a/src/core/SkDevice.h
+++ b/src/core/SkDevice.h
@@ -175,11 +175,6 @@
     virtual void drawDRRect(const SkRRect& outer,
                             const SkRRect& inner, const SkPaint&);
 
-    // Default impl always calls drawRect() with a solid-color paint, setting it to anti-aliased
-    // only when all edge flags are set.
-    virtual void drawEdgeAARect(const SkRect& r, SkCanvas::QuadAAFlags aa, SkColor color,
-                                SkBlendMode mode);
-
     /**
      *  If pathIsMutable, then the implementation is allowed to cast path to a
      *  non-const pointer and modify it in place (as an optimization). Canvas
@@ -189,10 +184,6 @@
     virtual void drawPath(const SkPath& path,
                           const SkPaint& paint,
                           bool pathIsMutable = false) = 0;
-    virtual void drawBitmap(const SkBitmap& bitmap,
-                            SkScalar x,
-                            SkScalar y,
-                            const SkPaint& paint) = 0;
     virtual void drawSprite(const SkBitmap& bitmap,
                             int x, int y, const SkPaint& paint) = 0;
 
@@ -209,7 +200,6 @@
     virtual void drawBitmapLattice(const SkBitmap&, const SkCanvas::Lattice&,
                                    const SkRect& dst, const SkPaint&);
 
-    virtual void drawImage(const SkImage*, SkScalar x, SkScalar y, const SkPaint&);
     virtual void drawImageRect(const SkImage*, const SkRect* src, const SkRect& dst,
                                const SkPaint&, SkCanvas::SrcRectConstraint);
     virtual void drawImageNine(const SkImage*, const SkIRect& center,
@@ -217,9 +207,6 @@
     virtual void drawImageLattice(const SkImage*, const SkCanvas::Lattice&,
                                   const SkRect& dst, const SkPaint&);
 
-    virtual void drawImageSet(const SkCanvas::ImageSetEntry[], int count, SkFilterQuality,
-                              SkBlendMode);
-
     virtual void drawVertices(const SkVertices*, const SkVertices::Bone bones[], int boneCount,
                               SkBlendMode, const SkPaint&) = 0;
     virtual void drawShadow(const SkPath&, const SkDrawShadowRec&);
@@ -235,6 +222,17 @@
 
     virtual void drawAnnotation(const SkRect&, const char[], SkData*) {}
 
+    // Default impl always calls drawRect() with a solid-color paint, setting it to anti-aliased
+    // only when all edge flags are set. If there's a clip region, it draws that using drawPath,
+    // or uses clipPath().
+    virtual void drawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
+                                SkCanvas::QuadAAFlags aaFlags, SkColor color, SkBlendMode mode);
+    // Default impl uses drawImageRect per entry, being anti-aliased only when an entry's edge flags
+    // are all set. If there's a clip region, it will be applied using clipPath().
+    virtual void drawEdgeAAImageSet(const SkCanvas::ImageSetEntry[], int count,
+                                    const SkPoint dstClips[], const SkMatrix preViewMatrices[],
+                                    const SkPaint& paint, SkCanvas::SrcRectConstraint);
+
     /** The SkDevice passed will be an SkDevice which was returned by a call to
         onCreateDevice on this device with kNeverTile_TileExpectation.
      */
@@ -330,7 +328,7 @@
     }
 
     // A helper function used by derived classes to log the scale factor of a bitmap or image draw.
-    static void LogDrawScaleFactor(const SkMatrix&, SkFilterQuality);
+    static void LogDrawScaleFactor(const SkMatrix& view, const SkMatrix& srcToDst, SkFilterQuality);
 
 private:
     friend class SkAndroidFrameworkUtils;
@@ -384,8 +382,10 @@
 
 class SkNoPixelsDevice : public SkBaseDevice {
 public:
-    SkNoPixelsDevice(const SkIRect& bounds, const SkSurfaceProps& props)
-            : SkBaseDevice(SkImageInfo::MakeUnknown(bounds.width(), bounds.height()), props)
+    SkNoPixelsDevice(const SkIRect& bounds, const SkSurfaceProps& props,
+                     sk_sp<SkColorSpace> colorSpace = nullptr)
+    : SkBaseDevice(SkImageInfo::Make(bounds.width(), bounds.height(), kUnknown_SkColorType,
+                                     kUnknown_SkAlphaType, std::move(colorSpace)), props)
     {
         // this fails if we enable this assert: DiscardableImageMapTest.GetDiscardableImagesInRectMaxImage
         //SkASSERT(bounds.width() >= 0 && bounds.height() >= 0);
@@ -420,7 +420,6 @@
     void drawOval(const SkRect&, const SkPaint&) override {}
     void drawRRect(const SkRRect&, const SkPaint&) override {}
     void drawPath(const SkPath&, const SkPaint&, bool) override {}
-    void drawBitmap(const SkBitmap&, SkScalar x, SkScalar y, const SkPaint&) override {}
     void drawSprite(const SkBitmap&, int, int, const SkPaint&) override {}
     void drawBitmapRect(const SkBitmap&, const SkRect*, const SkRect&, const SkPaint&,
                         SkCanvas::SrcRectConstraint) override {}
diff --git a/src/core/SkDraw.cpp b/src/core/SkDraw.cpp
index cb7d5be..b30f654 100644
--- a/src/core/SkDraw.cpp
+++ b/src/core/SkDraw.cpp
@@ -40,9 +40,9 @@
 static SkPaint make_paint_with_image(
     const SkPaint& origPaint, const SkBitmap& bitmap, SkMatrix* matrix = nullptr) {
     SkPaint paint(origPaint);
-    paint.setShader(SkMakeBitmapShader(bitmap, SkShader::kClamp_TileMode,
-                                       SkShader::kClamp_TileMode, matrix,
-                                       kNever_SkCopyPixelsMode));
+    paint.setShader(SkMakeBitmapShaderForPaint(origPaint, bitmap, SkTileMode::kClamp,
+                                               SkTileMode::kClamp, matrix,
+                                               kNever_SkCopyPixelsMode));
     return paint;
 }
 
@@ -897,7 +897,6 @@
     SkMatrix        tmpMatrix;
     const SkMatrix* matrix = fMatrix;
     tmpPath->setIsVolatile(true);
-    SkPathPriv::SetIsBadForDAA(*tmpPath, SkPathPriv::IsBadForDAA(origSrcPath));
 
     if (prePathMatrix) {
         if (origPaint.getPathEffect() || origPaint.getStyle() != SkPaint::kFill_Style) {
diff --git a/src/core/SkDraw.h b/src/core/SkDraw.h
index 9816c6b..a100152 100644
--- a/src/core/SkDraw.h
+++ b/src/core/SkDraw.h
@@ -82,7 +82,7 @@
         this->drawPath(src, paint, nullptr, false, !isHairline, customBlitter);
     }
 
-    void paintPaths(SkSpan<const SkGlyphRunListPainter::PathAndPos> pathsAndPositions,
+    void paintPaths(SkSpan<const SkPathPos> pathsAndPositions,
                     SkScalar scale,
                     const SkPaint& paint) const override;
 
diff --git a/src/core/SkDraw_text.cpp b/src/core/SkDraw_text.cpp
index 533936e..5f2ed3b 100644
--- a/src/core/SkDraw_text.cpp
+++ b/src/core/SkDraw_text.cpp
@@ -101,7 +101,7 @@
     }
 }
 
-void SkDraw::paintPaths(SkSpan<const SkGlyphRunListPainter::PathAndPos> pathsAndPositions,
+void SkDraw::paintPaths(SkSpan<const SkPathPos> pathsAndPositions,
                         SkScalar scale,
                         const SkPaint& paint) const {
     for (const auto& pathAndPos : pathsAndPositions) {
diff --git a/src/core/SkDraw_vertices.cpp b/src/core/SkDraw_vertices.cpp
index 4c0c783..1190cc1 100644
--- a/src/core/SkDraw_vertices.cpp
+++ b/src/core/SkDraw_vertices.cpp
@@ -9,17 +9,16 @@
 #include "SkAutoBlitterChoose.h"
 #include "SkComposeShader.h"
 #include "SkConvertPixels.h"
+#include "SkCoreBlitters.h"
 #include "SkDraw.h"
 #include "SkNx.h"
 #include "SkRasterClip.h"
+#include "SkRasterPipeline.h"
 #include "SkScan.h"
 #include "SkShaderBase.h"
 #include "SkString.h"
 #include "SkVertState.h"
 
-#include "SkArenaAlloc.h"
-#include "SkCoreBlitters.h"
-
 struct Matrix43 {
     float fMat[12];    // column major
 
@@ -83,7 +82,7 @@
         return nullptr;
     }
 #endif
-    bool onAppendStages(const StageRec& rec) const override {
+    bool onAppendStages(const SkStageRec& rec) const override {
         rec.fPipeline->append(SkRasterPipeline::seed_shader);
         rec.fPipeline->append(SkRasterPipeline::matrix_4x3, &fM43);
         return true;
@@ -205,7 +204,7 @@
 
     constexpr size_t kDefVertexCount = 16;
     constexpr size_t kOuterSize = sizeof(SkTriColorShader) +
-                                 sizeof(SkComposeShader) +
+                                 sizeof(SkShader_Blend) +
                                  (2 * sizeof(SkPoint) + sizeof(SkColor4f)) * kDefVertexCount;
     SkSTArenaAlloc<kOuterSize> outerAlloc;
 
@@ -278,8 +277,8 @@
                                                                                   vertexCount));
             matrix43 = triShader->getMatrix43();
             if (shader) {
-                shader = outerAlloc.make<SkComposeShader>(sk_ref_sp(triShader), sk_ref_sp(shader),
-                                                          bmode, 1);
+                shader = outerAlloc.make<SkShader_Blend>(bmode,
+                                                         sk_ref_sp(triShader), sk_ref_sp(shader));
             } else {
                 shader = triShader;
             }
diff --git a/src/core/SkEdgeBuilder.cpp b/src/core/SkEdgeBuilder.cpp
index c1a0235..a643a6c 100644
--- a/src/core/SkEdgeBuilder.cpp
+++ b/src/core/SkEdgeBuilder.cpp
@@ -145,13 +145,6 @@
         }
     }
 }
-void SkBezierEdgeBuilder::addLine(const SkPoint pts[]) {
-    SkLine* line = fAlloc.make<SkLine>();
-    if (line->set(pts)) {
-        fList.push_back(line);
-    }
-}
-
 void SkBasicEdgeBuilder::addQuad(const SkPoint pts[]) {
     SkQuadraticEdge* edge = fAlloc.make<SkQuadraticEdge>();
     if (edge->setQuadratic(pts, fClipShift)) {
@@ -164,12 +157,6 @@
         fList.push_back(edge);
     }
 }
-void SkBezierEdgeBuilder::addQuad(const SkPoint pts[]) {
-    SkQuad* quad = fAlloc.make<SkQuad>();
-    if (quad->set(pts)) {
-        fList.push_back(quad);
-    }
-}
 
 void SkBasicEdgeBuilder::addCubic(const SkPoint pts[]) {
     SkCubicEdge* edge = fAlloc.make<SkCubicEdge>();
@@ -183,12 +170,6 @@
         fList.push_back(edge);
     }
 }
-void SkBezierEdgeBuilder::addCubic(const SkPoint pts[]) {
-    SkCubic* cubic = fAlloc.make<SkCubic>();
-    if (cubic->set(pts)) {
-        fList.push_back(cubic);
-    }
-}
 
 // TODO: merge addLine() and addPolyLine()?
 
@@ -216,15 +197,6 @@
     }
     return SkEdgeBuilder::kPartial_Combine;  // As above.
 }
-SkEdgeBuilder::Combine SkBezierEdgeBuilder::addPolyLine(SkPoint pts[],
-                                                        char* arg_edge, char** arg_edgePtr) {
-    auto edge = (SkLine*)arg_edge;
-
-    if (edge->set(pts)) {
-        return kNo_Combine;
-    }
-    return SkEdgeBuilder::kPartial_Combine;  // As above.
-}
 
 SkRect SkBasicEdgeBuilder::recoverClip(const SkIRect& src) const {
     return { SkIntToScalar(src.fLeft   >> fClipShift),
@@ -235,9 +207,6 @@
 SkRect SkAnalyticEdgeBuilder::recoverClip(const SkIRect& src) const {
     return SkRect::Make(src);
 }
-SkRect SkBezierEdgeBuilder::recoverClip(const SkIRect& src) const {
-    return SkRect::Make(src);
-}
 
 char* SkBasicEdgeBuilder::allocEdges(size_t n, size_t* size) {
     *size = sizeof(SkEdge);
@@ -247,10 +216,6 @@
     *size = sizeof(SkAnalyticEdge);
     return (char*)fAlloc.makeArrayDefault<SkAnalyticEdge>(n);
 }
-char* SkBezierEdgeBuilder::allocEdges(size_t n, size_t* size) {
-    *size = sizeof(SkLine);
-    return (char*)fAlloc.makeArrayDefault<SkLine>(n);
-}
 
 // TODO: maybe get rid of buildPoly() entirely?
 int SkEdgeBuilder::buildPoly(const SkPath& path, const SkIRect* iclip, bool canCullToTheRight) {
@@ -436,10 +401,6 @@
                     }
                 } break;
                 case SkPath::kCubic_Verb: {
-                    if (!this->chopCubics()) {
-                        this->addCubic(pts);
-                        break;
-                    }
                     SkPoint monoY[10];
                     int n = SkChopCubicAtYExtrema(pts, monoY);
                     for (int i = 0; i <= n; i++) {
@@ -470,12 +431,9 @@
 
     SkASSERT(count >= 0);
 
-    // If we can't cull to the right, we should have count > 1 (or 0),
-    // unless we're in DAA which doesn't need to chop edges at y extrema.
-    // For example, a single cubic edge with a valley shape \_/ is fine for DAA.
-    if (!canCullToTheRight && count == 1) {
-        SkASSERT(!this->chopCubics());
+    // If we can't cull to the right, we should have count > 1 (or 0).
+    if (!canCullToTheRight) {
+        SkASSERT(count != 1);
     }
-
     return count;
 }
diff --git a/src/core/SkEdgeBuilder.h b/src/core/SkEdgeBuilder.h
index cf662d5..5ab6942 100644
--- a/src/core/SkEdgeBuilder.h
+++ b/src/core/SkEdgeBuilder.h
@@ -42,7 +42,6 @@
 
     virtual char* allocEdges(size_t n, size_t* sizeof_edge) = 0;
     virtual SkRect recoverClip(const SkIRect&) const = 0;
-    virtual bool chopCubics() const = 0;
 
     virtual void addLine (const SkPoint pts[]) = 0;
     virtual void addQuad (const SkPoint pts[]) = 0;
@@ -61,7 +60,6 @@
 
     char* allocEdges(size_t, size_t*) override;
     SkRect recoverClip(const SkIRect&) const override;
-    bool chopCubics() const override { return true; }
 
     void addLine (const SkPoint pts[]) override;
     void addQuad (const SkPoint pts[]) override;
@@ -82,29 +80,10 @@
 
     char* allocEdges(size_t, size_t*) override;
     SkRect recoverClip(const SkIRect&) const override;
-    bool chopCubics() const override { return true; }
 
     void addLine (const SkPoint pts[]) override;
     void addQuad (const SkPoint pts[]) override;
     void addCubic(const SkPoint pts[]) override;
     Combine addPolyLine(SkPoint pts[], char* edge, char** edgePtr) override;
 };
-
-class SkBezierEdgeBuilder final : public SkEdgeBuilder {
-public:
-    SkBezierEdgeBuilder() {}
-
-    SkBezier** bezierList() { return (SkBezier**)fEdgeList; }
-
-private:
-    char* allocEdges(size_t, size_t*) override;
-    SkRect recoverClip(const SkIRect&) const override;
-    bool chopCubics() const override { return false; }
-
-    void addLine (const SkPoint pts[]) override;
-    void addQuad (const SkPoint pts[]) override;
-    void addCubic(const SkPoint pts[]) override;
-    Combine addPolyLine(SkPoint pts[], char* edge, char** edgePtr) override;
-};
-
 #endif
diff --git a/src/core/SkEffectPriv.h b/src/core/SkEffectPriv.h
new file mode 100644
index 0000000..7dbd9c5
--- /dev/null
+++ b/src/core/SkEffectPriv.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkEffectPriv_DEFINED
+#define SkEffectPriv_DEFINED
+
+#include "SkImageInfo.h"
+
+class SkArenaAlloc;
+class SkColorSpace;
+class SkMatrix;
+class SkPaint;
+class SkRasterPipeline;
+
+// Passed to effects that will add stages to rasterpipeline
+struct SkStageRec {
+    SkRasterPipeline*   fPipeline;
+    SkArenaAlloc*       fAlloc;
+    SkColorType         fDstColorType;
+    SkColorSpace*       fDstCS;         // may be nullptr
+    const SkPaint&      fPaint;
+    const SkMatrix*     fLocalM;        // may be nullptr
+    const SkMatrix      fCTM;
+};
+
+#endif // SkEffectPriv_DEFINED
diff --git a/src/core/SkFindAndPlaceGlyph.h b/src/core/SkFindAndPlaceGlyph.h
deleted file mode 100644
index 280074d..0000000
--- a/src/core/SkFindAndPlaceGlyph.h
+++ /dev/null
@@ -1,352 +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 SkFindAndPositionGlyph_DEFINED
-#define SkFindAndPositionGlyph_DEFINED
-
-#include "SkArenaAlloc.h"
-#include "SkGlyph.h"
-#include "SkMatrixPriv.h"
-#include "SkPaint.h"
-#include "SkStrike.h"
-#include "SkTemplates.h"
-#include "SkUTF.h"
-#include <utility>
-
-class SkFindAndPlaceGlyph {
-public:
-    // ProcessPosText handles all cases for finding and positioning glyphs. It has a very large
-    // multiplicity. It figures out the glyph, position and rounding and pass those parameters to
-    // processOneGlyph.
-    //
-    // The routine processOneGlyph passed in by the client has the following signature:
-    // void f(const SkGlyph& glyph, SkPoint position, SkPoint rounding);
-    //
-    // * Sub-pixel positioning (2) - use sub-pixel positioning.
-    // * Text alignment (3) - text alignment with respect to the glyph's width.
-    // * Matrix type (3) - special cases for translation and X-coordinate scaling.
-    // * Components per position (2) - the positions vector can have a common Y with different
-    //   Xs, or XY-pairs.
-    // * Axis Alignment (for sub-pixel positioning) (3) - when using sub-pixel positioning, round
-    //   to a whole coordinate instead of using sub-pixel positioning.
-    // The number of variations is 108 for sub-pixel and 36 for full-pixel.
-    // This routine handles all of them using inline polymorphic variable (no heap allocation).
-    template<typename ProcessOneGlyph>
-    static void ProcessPosText(
-        const SkGlyphID[], int count,
-        SkPoint offset, const SkMatrix& matrix, const SkScalar pos[], int scalarsPerPosition,
-        SkStrike* cache, ProcessOneGlyph&& processOneGlyph);
-
-    // The SubpixelAlignment function produces a suitable position for the glyph cache to
-    // produce the correct sub-pixel alignment. If a position is aligned with an axis a shortcut
-    // of 0 is used for the sub-pixel position.
-    static SkIPoint SubpixelAlignment(SkAxisAlignment axisAlignment, SkPoint position) {
-
-        if (!SkScalarsAreFinite(position.fX, position.fY)) {
-            return {0, 0};
-        }
-
-        // Only the fractional part of position.fX and position.fY matter, because the result of
-        // this function will just be passed to FixedToSub.
-        switch (axisAlignment) {
-            case kX_SkAxisAlignment:
-                return {SkScalarToFixed(SkScalarFraction(position.fX) + kSubpixelRounding), 0};
-            case kY_SkAxisAlignment:
-                return {0, SkScalarToFixed(SkScalarFraction(position.fY) + kSubpixelRounding)};
-            case kNone_SkAxisAlignment:
-                return {SkScalarToFixed(SkScalarFraction(position.fX) + kSubpixelRounding),
-                        SkScalarToFixed(SkScalarFraction(position.fY) + kSubpixelRounding)};
-        }
-        SK_ABORT("Should not get here.");
-        return {0, 0};
-    }
-
-    // The SubpixelPositionRounding function returns a point suitable for rounding a sub-pixel
-    // positioned glyph.
-    static SkPoint SubpixelPositionRounding(SkAxisAlignment axisAlignment) {
-        switch (axisAlignment) {
-            case kX_SkAxisAlignment:
-                return {kSubpixelRounding, SK_ScalarHalf};
-            case kY_SkAxisAlignment:
-                return {SK_ScalarHalf, kSubpixelRounding};
-            case kNone_SkAxisAlignment:
-                return {kSubpixelRounding, kSubpixelRounding};
-        }
-        SK_ABORT("Should not get here.");
-        return {0.0f, 0.0f};
-    }
-
-    // MapperInterface given a point map it through the matrix. There are several shortcut
-    // variants.
-    // * TranslationMapper - assumes a translation only matrix.
-    // * XScaleMapper - assumes an X scaling and a translation.
-    // * GeneralMapper - Does all other matricies.
-    class MapperInterface {
-    public:
-        virtual ~MapperInterface() {}
-
-        virtual SkPoint map(SkPoint position) const = 0;
-    };
-
-    static MapperInterface* CreateMapper(const SkMatrix& matrix, const SkPoint& offset,
-                                         int scalarsPerPosition, SkArenaAlloc* arena) {
-        auto mtype = matrix.getType();
-        if (mtype & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask) ||
-            scalarsPerPosition == 2) {
-            return arena->make<GeneralMapper>(matrix, offset);
-        }
-
-        if (mtype & SkMatrix::kScale_Mask) {
-            return arena->make<XScaleMapper>(matrix, offset);
-        }
-
-        return arena->make<TranslationMapper>(matrix, offset);
-    }
-
-private:
-    // PositionReaderInterface reads a point from the pos vector.
-    // * HorizontalPositions - assumes a common Y for many X values.
-    // * ArbitraryPositions - a list of (X,Y) pairs.
-    class PositionReaderInterface {
-    public:
-        virtual ~PositionReaderInterface() { }
-        virtual SkPoint nextPoint() = 0;
-    };
-
-    class HorizontalPositions final : public PositionReaderInterface {
-    public:
-        explicit HorizontalPositions(const SkScalar* positions)
-            : fPositions(positions) { }
-
-        SkPoint nextPoint() override {
-            SkScalar x = *fPositions++;
-            return {x, 0};
-        }
-
-    private:
-        const SkScalar* fPositions;
-    };
-
-    class ArbitraryPositions final : public PositionReaderInterface {
-    public:
-        explicit ArbitraryPositions(const SkScalar* positions)
-            : fPositions(positions) { }
-
-        SkPoint nextPoint() override {
-            SkPoint to_return{fPositions[0], fPositions[1]};
-            fPositions += 2;
-            return to_return;
-        }
-
-    private:
-        const SkScalar* fPositions;
-    };
-
-    class TranslationMapper final : public MapperInterface {
-    public:
-        TranslationMapper(const SkMatrix& matrix, const SkPoint origin)
-            : fTranslate(matrix.mapXY(origin.fX, origin.fY)) { }
-
-        SkPoint map(SkPoint position) const override {
-            return position + fTranslate;
-        }
-
-    private:
-        const SkPoint fTranslate;
-    };
-
-    class XScaleMapper final : public MapperInterface {
-    public:
-        XScaleMapper(const SkMatrix& matrix, const SkPoint origin)
-            : fTranslate(matrix.mapXY(origin.fX, origin.fY)), fXScale(matrix.getScaleX()) { }
-
-        SkPoint map(SkPoint position) const override {
-            return {fXScale * position.fX + fTranslate.fX, fTranslate.fY};
-        }
-
-    private:
-        const SkPoint fTranslate;
-        const SkScalar fXScale;
-    };
-
-    // The caller must keep matrix alive while this class is used.
-    class GeneralMapper final : public MapperInterface {
-    public:
-        GeneralMapper(const SkMatrix& matrix, const SkPoint origin)
-            : fOrigin(origin), fMatrix(matrix), fMapProc(SkMatrixPriv::GetMapXYProc(matrix)) { }
-
-        SkPoint map(SkPoint position) const override {
-            SkPoint result;
-            fMapProc(fMatrix, position.fX + fOrigin.fX, position.fY + fOrigin.fY, &result);
-            return result;
-        }
-
-    private:
-        const SkPoint fOrigin;
-        const SkMatrix& fMatrix;
-        const SkMatrixPriv::MapXYProc fMapProc;
-    };
-
-    // The "call" to SkFixedToScalar is actually a macro. It's macros all the way down.
-    // Needs to be a macro because you can't have a const float unless you make it constexpr.
-    static constexpr SkScalar kSubpixelRounding = SkFixedToScalar(SkGlyph::kSubpixelRound);
-
-    // GlyphFindAndPlaceInterface given the text and position finds the correct glyph and does
-    // glyph specific position adjustment. The findAndPositionGlyph method takes text and
-    // position and calls processOneGlyph with the correct glyph, final position and rounding
-    // terms. The final position is not rounded yet and is the responsibility of processOneGlyph.
-    template<typename ProcessOneGlyph>
-    class GlyphFindAndPlaceInterface : SkNoncopyable {
-    public:
-        virtual ~GlyphFindAndPlaceInterface() { }
-
-        // findAndPositionGlyph calculates the position of the glyph, finds the glyph, and
-        // returns the position of where the next glyph will be using the glyph's advance. The
-        // returned position is used by drawText, but ignored by drawPosText.
-        // The compiler should prune all this calculation if the return value is not used.
-        //
-        // This should be a pure virtual, but some versions of GCC <= 4.8 have a bug that causes a
-        // compile error.
-        // See GCC bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60277
-        virtual SkPoint findAndPositionGlyph(
-            SkGlyphID, SkPoint position,
-            ProcessOneGlyph&& processOneGlyph) {
-            SK_ABORT("Should never get here.");
-            return {0.0f, 0.0f};
-        }
-    };
-
-    // GlyphFindAndPlaceSubpixel handles finding and placing glyphs when sub-pixel positioning is
-    // requested. After it has found and placed the glyph it calls the templated function
-    // ProcessOneGlyph in order to actually perform an action.
-    template<typename ProcessOneGlyph, SkAxisAlignment kAxisAlignment>
-    class GlyphFindAndPlaceSubpixel final : public GlyphFindAndPlaceInterface<ProcessOneGlyph> {
-    public:
-        explicit GlyphFindAndPlaceSubpixel(SkStrike* cache) : fCache(cache) {}
-
-        SkPoint findAndPositionGlyph(SkGlyphID glyphID, SkPoint position, ProcessOneGlyph&& processOneGlyph) override {
-            // Find the glyph.
-            SkIPoint lookupPosition = SubpixelAlignment(kAxisAlignment, position);
-            const SkGlyph& renderGlyph = fCache->getGlyphIDMetrics(glyphID, lookupPosition.fX, lookupPosition.fY);
-
-            // If the glyph has no width (no pixels) then don't bother processing it.
-            if (renderGlyph.fWidth > 0) {
-                processOneGlyph(renderGlyph, position,
-                                SubpixelPositionRounding(kAxisAlignment));
-            }
-            return position + SkPoint{SkFloatToScalar(renderGlyph.fAdvanceX),
-                                      SkFloatToScalar(renderGlyph.fAdvanceY)};
-        }
-
-    private:
-        SkStrike* fCache;
-    };
-
-    // GlyphFindAndPlaceFullPixel handles finding and placing glyphs when no sub-pixel
-    // positioning is requested.
-    template<typename ProcessOneGlyph>
-    class GlyphFindAndPlaceFullPixel final : public GlyphFindAndPlaceInterface<ProcessOneGlyph> {
-    public:
-        explicit GlyphFindAndPlaceFullPixel(SkStrike* cache) : fCache(cache) {}
-
-        SkPoint findAndPositionGlyph(
-            SkGlyphID glyphID, SkPoint position,
-            ProcessOneGlyph&& processOneGlyph) override {
-            SkPoint finalPosition = position;
-            const SkGlyph& glyph = fCache->getGlyphIDMetrics(glyphID);
-
-            if (glyph.fWidth > 0) {
-                processOneGlyph(glyph, finalPosition, {SK_ScalarHalf, SK_ScalarHalf});
-            }
-            return finalPosition + SkPoint{SkFloatToScalar(glyph.fAdvanceX),
-                                           SkFloatToScalar(glyph.fAdvanceY)};
-        }
-
-    private:
-        SkStrike* fCache;
-    };
-
-    template <typename ProcessOneGlyph>
-    static GlyphFindAndPlaceInterface<ProcessOneGlyph>* getSubpixel(
-        SkArenaAlloc* arena, SkAxisAlignment axisAlignment, SkStrike* cache)
-    {
-        switch (axisAlignment) {
-            case kX_SkAxisAlignment:
-                return arena->make<GlyphFindAndPlaceSubpixel<
-                    ProcessOneGlyph, kX_SkAxisAlignment>>(cache);
-            case kNone_SkAxisAlignment:
-                return arena->make<GlyphFindAndPlaceSubpixel<
-                    ProcessOneGlyph, kNone_SkAxisAlignment>>(cache);
-            case kY_SkAxisAlignment:
-                return arena->make<GlyphFindAndPlaceSubpixel<
-                    ProcessOneGlyph, kY_SkAxisAlignment>>(cache);
-        }
-        SK_ABORT("Should never get here.");
-        return nullptr;
-    }
-};
-
-template<typename ProcessOneGlyph>
-inline void SkFindAndPlaceGlyph::ProcessPosText(
-    const SkGlyphID glyphs[], int count,
-    SkPoint offset, const SkMatrix& matrix, const SkScalar pos[], int scalarsPerPosition,
-    SkStrike* cache, ProcessOneGlyph&& processOneGlyph) {
-
-    SkAxisAlignment axisAlignment = cache->getScalerContext()->computeAxisAlignmentForHText();
-    uint32_t mtype = matrix.getType();
-
-    // Specialized code for handling the most common case for blink.
-    if (axisAlignment == kX_SkAxisAlignment
-        && cache->isSubpixel()
-        && mtype <= SkMatrix::kTranslate_Mask)
-    {
-        using Positioner =
-            GlyphFindAndPlaceSubpixel <
-                ProcessOneGlyph, kX_SkAxisAlignment>;
-        HorizontalPositions hPositions{pos};
-        ArbitraryPositions  aPositions{pos};
-        PositionReaderInterface* positions = nullptr;
-        if (scalarsPerPosition == 2) {
-            positions = &aPositions;
-        } else {
-            positions = &hPositions;
-        }
-        TranslationMapper mapper{matrix, offset};
-        Positioner positioner(cache);
-        for (int i = 0; i < count; ++i) {
-            SkPoint mappedPoint = mapper.TranslationMapper::map(positions->nextPoint());
-            positioner.Positioner::findAndPositionGlyph(
-                glyphs[i], mappedPoint, std::forward<ProcessOneGlyph>(processOneGlyph));
-        }
-        return;
-    }
-
-    SkSTArenaAlloc<120> arena;
-
-    PositionReaderInterface* positionReader = nullptr;
-    if (2 == scalarsPerPosition) {
-        positionReader = arena.make<ArbitraryPositions>(pos);
-    } else {
-        positionReader = arena.make<HorizontalPositions>(pos);
-    }
-
-    MapperInterface* mapper = CreateMapper(matrix, offset, scalarsPerPosition, &arena);
-    GlyphFindAndPlaceInterface<ProcessOneGlyph>* findAndPosition = nullptr;
-    if (cache->isSubpixel()) {
-        findAndPosition = getSubpixel<ProcessOneGlyph>(&arena, axisAlignment, cache);
-    } else {
-        findAndPosition = arena.make<GlyphFindAndPlaceFullPixel<ProcessOneGlyph>>(cache);
-    }
-
-    for (int i = 0; i < count; ++i) {
-        SkPoint mappedPoint = mapper->map(positionReader->nextPoint());
-        findAndPosition->findAndPositionGlyph(
-            glyphs[i], mappedPoint, std::forward<ProcessOneGlyph>(processOneGlyph));
-    }
-}
-
-#endif  // SkFindAndPositionGlyph_DEFINED
diff --git a/src/core/SkFont.cpp b/src/core/SkFont.cpp
index 3045e04..5ab8447 100644
--- a/src/core/SkFont.cpp
+++ b/src/core/SkFont.cpp
@@ -171,7 +171,7 @@
 }
 
 int SkFont::textToGlyphs(const void* text, size_t byteLength, SkTextEncoding encoding,
-                         uint16_t glyphs[], int maxGlyphCount) const {
+                         SkGlyphID glyphs[], int maxGlyphCount) const {
     if (0 == byteLength) {
         return 0;
     }
@@ -250,7 +250,7 @@
         }
         return 0;
     }
-    const uint16_t* glyphs = atg.glyphs();
+    const SkGlyphID* glyphs = atg.glyphs();
 
     auto cache = SkStrikeCache::FindOrCreateStrikeWithNoDeviceExclusive(font, canon.getPaint());
 
@@ -293,7 +293,7 @@
 }
 
 template <typename HANDLER>
-void VisitGlyphs(const SkFont& origFont, const SkPaint* paint, const uint16_t glyphs[], int count,
+void VisitGlyphs(const SkFont& origFont, const SkPaint* paint, const SkGlyphID glyphs[], int count,
                  HANDLER handler) {
     if (count <= 0) {
         return;
@@ -310,10 +310,10 @@
     handler(cache.get(), glyphs, count, scale);
 }
 
-void SkFont::getWidthsBounds(const uint16_t glyphs[], int count, SkScalar widths[], SkRect bounds[],
+void SkFont::getWidthsBounds(const SkGlyphID glyphs[], int count, SkScalar widths[], SkRect bounds[],
                              const SkPaint* paint) const {
     VisitGlyphs(*this, paint, glyphs, count, [widths, bounds]
-                (SkStrike* cache, const uint16_t glyphs[], int count, SkScalar scale) {
+                (SkStrike* cache, const SkGlyphID glyphs[], int count, SkScalar scale) {
         for (int i = 0; i < count; ++i) {
             const SkGlyph* g;
             if (bounds) {
@@ -329,9 +329,9 @@
     });
 }
 
-void SkFont::getPos(const uint16_t glyphs[], int count, SkPoint pos[], SkPoint origin) const {
+void SkFont::getPos(const SkGlyphID glyphs[], int count, SkPoint pos[], SkPoint origin) const {
     VisitGlyphs(*this, nullptr, glyphs, count, [pos, origin]
-                      (SkStrike* cache, const uint16_t glyphs[], int count, SkScalar scale) {
+                      (SkStrike* cache, const SkGlyphID glyphs[], int count, SkScalar scale) {
         SkPoint loc = origin;
         for (int i = 0; i < count; ++i) {
             pos[i] = loc;
@@ -340,9 +340,9 @@
     });
 }
 
-void SkFont::getXPos(const uint16_t glyphs[], int count, SkScalar xpos[], SkScalar origin) const {
+void SkFont::getXPos(const SkGlyphID glyphs[], int count, SkScalar xpos[], SkScalar origin) const {
     VisitGlyphs(*this, nullptr, glyphs, count, [xpos, origin]
-                      (SkStrike* cache, const uint16_t glyphs[], int count, SkScalar scale) {
+                      (SkStrike* cache, const SkGlyphID glyphs[], int count, SkScalar scale) {
         SkScalar x = origin;
         for (int i = 0; i < count; ++i) {
             xpos[i] = x;
@@ -351,7 +351,7 @@
     });
 }
 
-void SkFont::getPaths(const uint16_t glyphs[], int count,
+void SkFont::getPaths(const SkGlyphID glyphs[], int count,
                       void (*proc)(const SkPath*, const SkMatrix&, void*), void* ctx) const {
     SkFont font(*this);
     SkScalar scale = font.setupForAsPaths(nullptr);
@@ -368,7 +368,7 @@
     }
 }
 
-bool SkFont::getPath(uint16_t glyphID, SkPath* path) const {
+bool SkFont::getPath(SkGlyphID glyphID, SkPath* path) const {
     struct Pair {
         SkPath* fPath;
         bool    fWasSet;
@@ -413,20 +413,6 @@
 
 //////////////////////////////////////////////////////////////////////////////////////////////////
 
-int SkFontPriv::ValidCountText(const void* text, size_t length, SkTextEncoding encoding) {
-    switch (encoding) {
-        case kUTF8_SkTextEncoding: return SkUTF::CountUTF8((const char*)text, length);
-        case kUTF16_SkTextEncoding: return SkUTF::CountUTF16((const uint16_t*)text, length);
-        case kUTF32_SkTextEncoding: return SkUTF::CountUTF32((const int32_t*)text, length);
-        case kGlyphID_SkTextEncoding:
-            if (!SkIsAlign2(intptr_t(text)) || !SkIsAlign2(length)) {
-                return -1;
-            }
-            return length >> 1;
-    }
-    return -1;
-}
-
 void SkFontPriv::ScaleFontMetrics(SkFontMetrics* metrics, SkScalar scale) {
     metrics->fTop *= scale;
     metrics->fAscent *= scale;
@@ -472,7 +458,7 @@
     return 0;
 }
 
-void SkFontPriv::GlyphsToUnichars(const SkFont& font, const uint16_t glyphs[], int count,
+void SkFontPriv::GlyphsToUnichars(const SkFont& font, const SkGlyphID glyphs[], int count,
                                   SkUnichar uni[]) {
     font.glyphsToUnichars(glyphs, count, uni);
 }
diff --git a/src/core/SkFontPriv.h b/src/core/SkFontPriv.h
index 36e025b..db0f48a 100644
--- a/src/core/SkFontPriv.h
+++ b/src/core/SkFontPriv.h
@@ -54,9 +54,6 @@
 
     static void ScaleFontMetrics(SkFontMetrics*, SkScalar);
 
-    // returns -1 if buffer is invalid for specified encoding
-    static int ValidCountText(const void* text, size_t length, SkTextEncoding);
-
     /**
         Returns the union of bounds of all glyphs.
         Returned dimensions are computed by font manager from font data,
@@ -71,9 +68,9 @@
     static SkRect GetFontBounds(const SkFont&);
 
     static bool IsFinite(const SkFont& font) {
-        return SkScalarIsFinite(font.fSize) &&
-               SkScalarIsFinite(font.fScaleX) &&
-               SkScalarIsFinite(font.fSkewX);
+        return SkScalarIsFinite(font.getSize()) &&
+               SkScalarIsFinite(font.getScaleX()) &&
+               SkScalarIsFinite(font.getSkewX());
     }
 
     // Returns the number of elements (characters or glyphs) in the array.
diff --git a/src/core/SkGlyph.h b/src/core/SkGlyph.h
index 03493bb..41368d2 100644
--- a/src/core/SkGlyph.h
+++ b/src/core/SkGlyph.h
@@ -124,15 +124,6 @@
     }
 };
 
-struct SkPackedUnicharID : public SkPackedID {
-    SkPackedUnicharID(SkUnichar code) : SkPackedID(code) { }
-    SkPackedUnicharID(SkUnichar code, SkFixed x, SkFixed y) : SkPackedID(code, x, y) { }
-    constexpr SkPackedUnicharID() = default;
-    SkUnichar code() const {
-        return SkTo<SkUnichar>(SkPackedID::code());
-    }
-};
-
 class SkGlyph {
     struct PathData;
 
@@ -167,6 +158,13 @@
         return fPathData != nullptr && fPathData->fHasPath ? &fPathData->fPath : nullptr;
     }
 
+    int maxDimension() const {
+        // width and height are only defined if a metrics call was made.
+        SkASSERT(fMaskFormat != MASK_FORMAT_UNKNOWN);
+
+        return std::max(fWidth, fHeight);
+    }
+
     // Returns the size allocated on the arena.
     size_t copyImageData(const SkGlyph& from, SkArenaAlloc* alloc);
 
diff --git a/src/core/SkGlyphRunPainter.cpp b/src/core/SkGlyphRunPainter.cpp
index a0af48c..5a6da46 100644
--- a/src/core/SkGlyphRunPainter.cpp
+++ b/src/core/SkGlyphRunPainter.cpp
@@ -8,7 +8,11 @@
 #include "SkGlyphRunPainter.h"
 
 #if SK_SUPPORT_GPU
+#include "GrCaps.h"
 #include "GrColorSpaceInfo.h"
+#include "GrContextPriv.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "SkGr.h"
 #include "text/GrTextBlobCache.h"
@@ -25,6 +29,7 @@
 #include "SkPathEffect.h"
 #include "SkRasterClip.h"
 #include "SkRemoteGlyphCacheImpl.h"
+#include "SkStrikeInterface.h"
 #include "SkStrike.h"
 #include "SkStrikeCache.h"
 #include "SkTDArray.h"
@@ -66,36 +71,41 @@
     return {lookupX, lookupY};
 }
 
-bool SkStrikeCommon::GlyphTooBigForAtlas(const SkGlyph& glyph) {
-    return glyph.fWidth > kSkSideTooBigForAtlas || glyph.fHeight > kSkSideTooBigForAtlas;
-}
-
 // -- SkGlyphRunListPainter ------------------------------------------------------------------------
-SkGlyphRunListPainter::SkGlyphRunListPainter(
-        const SkSurfaceProps& props, SkColorType colorType, SkScalerContextFlags flags)
+SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props,
+                                             SkColorType colorType,
+                                             SkScalerContextFlags flags,
+                                             SkStrikeCacheInterface* strikeCache)
         : fDeviceProps{props}
-        , fBitmapFallbackProps{SkSurfaceProps{props.flags(), kUnknown_SkPixelGeometry}}
-        , fColorType{colorType}
-        , fScalerContextFlags{flags} {}
-
-#if SK_SUPPORT_GPU
+        ,  fBitmapFallbackProps{SkSurfaceProps{props.flags(), kUnknown_SkPixelGeometry}}
+        ,  fColorType{colorType}, fScalerContextFlags{flags}
+        ,  fStrikeCache{strikeCache} {}
 
 // TODO: unify with code in GrTextContext.cpp
-static SkScalerContextFlags compute_scaler_context_flags(
-        const GrColorSpaceInfo& colorSpaceInfo) {
+static SkScalerContextFlags compute_scaler_context_flags(const SkColorSpace* cs) {
     // If we're doing linear blending, then we can disable the gamma hacks.
     // Otherwise, leave them on. In either case, we still want the contrast boost:
     // TODO: Can we be even smarter about mask gamma based on the dest transfer function?
-    if (colorSpaceInfo.isLinearlyBlended()) {
+    if (cs && cs->gammaIsLinear()) {
         return SkScalerContextFlags::kBoostContrast;
     } else {
         return SkScalerContextFlags::kFakeGammaAndBoostContrast;
     }
 }
 
-SkGlyphRunListPainter::SkGlyphRunListPainter(
-        const SkSurfaceProps& props, const GrColorSpaceInfo& csi)
-        : SkGlyphRunListPainter(props, kUnknown_SkColorType, compute_scaler_context_flags(csi)) {}
+SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props,
+                                             SkColorType colorType,
+                                             SkColorSpace* cs,
+                                             SkStrikeCacheInterface* strikeCache)
+        : SkGlyphRunListPainter(props, colorType, compute_scaler_context_flags(cs), strikeCache) {}
+
+#if SK_SUPPORT_GPU
+SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props,
+                                             const GrColorSpaceInfo& csi)
+        : SkGlyphRunListPainter(props,
+                                kUnknown_SkColorType,
+                                compute_scaler_context_flags(csi.colorSpace()),
+                                SkStrikeCache::GlobalStrikeCache()) {}
 
 SkGlyphRunListPainter::SkGlyphRunListPainter(const GrRenderTargetContext& rtc)
         : SkGlyphRunListPainter{rtc.surfaceProps(), rtc.colorSpaceInfo()} {}
@@ -174,11 +184,14 @@
             SkFont  pathFont{runFont};
             SkScalar textScale = pathFont.setupForAsPaths(&pathPaint);
 
+            // The sub-pixel position will always happen when transforming to the screen.
+            pathFont.setSubpixel(false);
+
             auto pathCache = SkStrikeCache::FindOrCreateStrikeExclusive(
                                 pathFont, pathPaint, props,
                                 fScalerContextFlags, SkMatrix::I());
 
-            SkTDArray<PathAndPos> pathsAndPositions;
+            SkTDArray<SkPathPos> pathsAndPositions;
             pathsAndPositions.setReserve(runSize);
             SkPoint* positionCursor = fPositions;
             for (auto glyphID : glyphRun.glyphsIDs()) {
@@ -188,7 +201,7 @@
                     if (!glyph.isEmpty()) {
                         const SkPath* path = pathCache->findPath(glyph);
                         if (path != nullptr) {
-                            pathsAndPositions.push_back(PathAndPos{path, position});
+                            pathsAndPositions.push_back(SkPathPos{path, position});
                         }
                     }
                 }
@@ -200,7 +213,7 @@
             pathPaint.setAntiAlias(runFont.hasSomeAntiAliasing());
 
             bitmapDevice->paintPaths(
-                    SkSpan<const PathAndPos>{pathsAndPositions.begin(), pathsAndPositions.size()},
+                    SkSpan<const SkPathPos>{pathsAndPositions.begin(), pathsAndPositions.size()},
                     textScale, pathPaint);
         } else {
             auto cache = SkStrikeCache::FindOrCreateStrikeExclusive(
@@ -253,28 +266,30 @@
 //   transformations from the view matrix. Calculate a text scale based on that reduction. This
 //   scale factor is used to increase the size of the destination rectangles. The destination
 //   rectangles are then scaled, rotated, etc. by the GPU using the view matrix.
-void SkGlyphRunListPainter::processARGBFallback(
-        SkScalar maxGlyphDimension, const SkPaint& runPaint, const SkFont& runFont,
-        const SkMatrix& viewMatrix, SkScalar textScale, ARGBFallback argbFallback) {
+void SkGlyphRunListPainter::processARGBFallback(SkScalar maxSourceGlyphDimension,
+                                                const SkPaint& runPaint,
+                                                const SkFont& runFont,
+                                                const SkMatrix& viewMatrix,
+                                                SkGlyphRunPainterInterface* process) {
     SkASSERT(!fARGBGlyphsIDs.empty());
 
     SkScalar maxScale = viewMatrix.getMaxScale();
 
-    // This is a conservative estimate of the longest dimension among all the glyph widths and
-    // heights.
-    SkScalar conservativeMaxGlyphDimension = maxGlyphDimension * textScale * maxScale;
+    // This is a linear estimate of the longest dimension among all the glyph widths and heights.
+    SkScalar conservativeMaxGlyphDimension = maxSourceGlyphDimension * maxScale;
 
     // If the situation that the matrix is simple, and all the glyphs are small enough. Go fast!
-    bool useFastPath =
-            viewMatrix.isScaleTranslate() && conservativeMaxGlyphDimension <= maxGlyphDimension;
-
-    auto glyphIDs = SkSpan<const SkGlyphID>{fARGBGlyphsIDs};
+    // N.B. If the matrix has scale, that will be reflected in the strike through the viewMatrix
+    // in the useFastPath case.
+    bool useDeviceCache =
+            viewMatrix.isScaleTranslate()
+            && conservativeMaxGlyphDimension <= SkStrikeCommon::kSkSideTooBigForAtlas;
 
     // A scaled and translated transform is the common case, and is handled directly in fallback.
     // Even if the transform is scale and translate, fallback must be careful to use glyphs that
     // fit in the atlas. If a glyph will not fit in the atlas, then the general transform case is
     // used to render the glyphs.
-    if (useFastPath) {
+    if (useDeviceCache) {
         // Translate the positions to device space.
         viewMatrix.mapPoints(fARGBPositions.data(), fARGBPositions.size());
         for (SkPoint& point : fARGBPositions) {
@@ -282,14 +297,22 @@
             point.fY =  SkScalarFloorToScalar(point.fY);
         }
 
-        auto positions = SkSpan<const SkPoint>{fARGBPositions};
-        argbFallback(runPaint,
-                     runFont,
-                     glyphIDs,
-                     positions,
-                     SK_Scalar1,
-                     viewMatrix,
-                     kTransformDone);
+        SkAutoDescriptor ad;
+        SkScalerContextEffects effects;
+        SkScalerContext::CreateDescriptorAndEffectsUsingPaint(
+                runFont, runPaint, fDeviceProps, fScalerContextFlags, viewMatrix, &ad, &effects);
+
+        SkScopedStrike strike =
+                fStrikeCache->findOrCreateScopedStrike(
+                        *ad.getDesc(), effects, *runFont.getTypefaceOrDefault());
+
+        SkSpan<const SkGlyphPos> glyphPosSpan = strike->glyphMetrics(fARGBGlyphsIDs.data(),
+                                                                     fARGBPositions.data(),
+                                                                     fARGBGlyphsIDs.size(),
+                                                                     fGlyphPos);
+        if (process) {
+            process->processDeviceFallback(glyphPosSpan, strike.get());
+        }
 
     } else {
         // If the matrix is complicated or if scaling is used to fit the glyphs in the cache,
@@ -301,182 +324,276 @@
         SkScalar runFontTextSize = runFont.getSize();
 
         // Scale the text size down so the long side of all the glyphs will fit in the atlas.
-        SkScalar reducedTextSize =
-                (maxAtlasDimension / conservativeMaxGlyphDimension) * runFontTextSize;
-
-        // If there's a glyph in the font that's particularly large, it's possible
-        // that fScaledFallbackTextSize may end up minimizing too much. We'd rather skip
-        // that glyph than make the others blurry, so we set a minimum size of half the
-        // maximum text size to avoid this case.
-        SkScalar fallbackTextSize =
-                SkScalarFloorToScalar(std::max(reducedTextSize, 0.5f * runFontTextSize));
-
-        // Don't allow the text size to get too big. This will also improve glyph cache hit rate
-        // for larger text sizes.
-        fallbackTextSize = std::min(fallbackTextSize, 256.0f);
+        SkScalar fallbackTextSize = SkScalarFloorToScalar(
+                (maxAtlasDimension / maxSourceGlyphDimension) * runFontTextSize);
 
         SkFont fallbackFont{runFont};
         fallbackFont.setSize(fallbackTextSize);
+
+        // No sub-pixel needed. The transform to the screen will take care of sub-pixel positioning.
+        fallbackFont.setSubpixel(false);
+
+        // The scale factor to go from strike size to the source size for glyphs.
         SkScalar fallbackTextScale = runFontTextSize / fallbackTextSize;
-        auto positions = SkSpan<const SkPoint>{fARGBPositions};
-        argbFallback(runPaint,
-                     fallbackFont,
-                     glyphIDs,
-                     positions,
-                     fallbackTextScale,
-                     SkMatrix::I(),
-                     kDoTransform);
+
+        SkAutoDescriptor ad;
+        SkScalerContextEffects effects;
+        SkScalerContext::CreateDescriptorAndEffectsUsingPaint(fallbackFont,
+                                                              runPaint,
+                                                              fDeviceProps,
+                                                              fScalerContextFlags,
+                                                              SkMatrix::I(),
+                                                              &ad,
+                                                              &effects);
+
+        SkScopedStrike strike =
+                fStrikeCache->findOrCreateScopedStrike(
+                        *ad.getDesc(), effects, *fallbackFont.getTypefaceOrDefault());
+
+        int glyphCount = 0;
+        for (size_t i = 0; i < fARGBGlyphsIDs.size(); i++) {
+            SkGlyphID glyphID = fARGBGlyphsIDs[i];
+            SkPoint pos = fARGBPositions[i];
+            const SkGlyph& glyph = strike->getGlyphMetrics(glyphID, {0, 0});
+            fGlyphPos[glyphCount++] = {i, &glyph, pos};
+        }
+
+        if (process) {
+            process->processSourceFallback(
+                    SkSpan<const SkGlyphPos>{fGlyphPos, SkTo<size_t>(glyphCount)},
+                    strike.get(),
+                    fallbackTextScale,
+                    viewMatrix.hasPerspective());
+        }
     }
 }
 
-// Beware! The following code will end up holding two glyph caches at the same time, but they
-// will not be the same cache (which would cause two separate caches to be created).
-template <typename PerEmptyT, typename PerPathT>
-void SkGlyphRunListPainter::drawGlyphRunAsPathWithARGBFallback(
-        SkStrikeInterface* pathCache, const SkGlyphRun& glyphRun,
-        SkPoint origin, const SkPaint& runPaint, const SkMatrix& viewMatrix, SkScalar textScale,
-        PerEmptyT&& perEmpty, PerPathT&& perPath, ARGBFallback&& argbFallback) {
-    fARGBGlyphsIDs.clear();
-    fARGBPositions.clear();
-    SkScalar maxFallbackDimension{-SK_ScalarInfinity};
+#if SK_SUPPORT_GPU
+void SkGlyphRunListPainter::processGlyphRunList(const SkGlyphRunList& glyphRunList,
+                                                const SkMatrix& viewMatrix,
+                                                const SkSurfaceProps& props,
+                                                bool contextSupportsDistanceFieldText,
+                                                const GrTextContext::Options& options,
+                                                SkGlyphRunPainterInterface* process) {
 
-    const SkPoint* positionCursor = glyphRun.positions().data();
-    for (auto glyphID : glyphRun.glyphsIDs()) {
-        SkPoint glyphPos = origin + *positionCursor++;
-        const SkGlyph& glyph = pathCache->getGlyphMetrics(glyphID, {0, 0});
-        if (glyph.isEmpty()) {
-            perEmpty(glyph, glyphPos);
-        } else if (glyph.fMaskFormat != SkMask::kARGB32_Format) {
-            if (pathCache->hasPath(glyph)) {
-                perPath(glyph, glyphPos);
-            } else {
-                perEmpty(glyph, glyphPos);
+    SkPoint origin = glyphRunList.origin();
+    const SkPaint& runPaint = glyphRunList.paint();
+
+    for (const auto& glyphRun : glyphRunList) {
+        SkScalar maxFallbackDimension{-SK_ScalarInfinity};
+        ScopedBuffers _ = this->ensureBuffers(glyphRun);
+
+        auto addFallback = [this, &maxFallbackDimension]
+                (const SkGlyph& glyph, SkPoint sourcePosition) {
+            maxFallbackDimension = std::max(maxFallbackDimension,
+                                            SkIntToScalar(glyph.maxDimension()));
+            fARGBGlyphsIDs.push_back(glyph.getGlyphID());
+            fARGBPositions.push_back(sourcePosition);
+        };
+
+        const SkFont& runFont = glyphRun.font();
+
+        bool useSDFT = GrTextContext::CanDrawAsDistanceFields(
+                runPaint, runFont, viewMatrix, props, contextSupportsDistanceFieldText, options);
+        if (process) {
+            process->startRun(glyphRun, useSDFT);
+        }
+
+        if (useSDFT) {
+
+            // Translate all glyphs to the origin.
+            SkMatrix translate = SkMatrix::MakeTrans(origin.x(), origin.y());
+            translate.mapPoints(fPositions, glyphRun.positions().data(), glyphRun.runSize());
+
+            // Setup distance field runPaint and text ratio
+            SkPaint dfPaint = GrTextContext::InitDistanceFieldPaint(runPaint);
+            SkScalar cacheToSourceScale;
+            SkFont dfFont = GrTextContext::InitDistanceFieldFont(
+                    runFont, viewMatrix, options, &cacheToSourceScale);
+
+            // Fake-gamma and subpixel antialiasing are applied in the shader, so we ignore the
+            // passed-in scaler context flags. (It's only used when we fall-back to bitmap text).
+            SkScalerContextFlags flags = SkScalerContextFlags::kNone;
+
+            SkScalar minScale, maxScale;
+            std::tie(minScale, maxScale) = GrTextContext::InitDistanceFieldMinMaxScale(
+                    runFont.getSize(), viewMatrix, options);
+
+            SkAutoDescriptor ad;
+            SkScalerContextEffects effects;
+            SkScalerContext::CreateDescriptorAndEffectsUsingPaint(
+                    dfFont, dfPaint, fDeviceProps, flags, SkMatrix::I(), &ad, &effects);
+            SkScopedStrike strike =
+                    fStrikeCache->findOrCreateScopedStrike(
+                            *ad.getDesc(), effects, *dfFont.getTypefaceOrDefault());
+
+            int glyphCount = 0;
+            for (size_t i = 0; i < glyphRun.runSize(); i++) {
+                SkGlyphID glyphID = glyphRun.glyphsIDs()[i];
+                SkPoint glyphSourcePosition = fPositions[i];
+                const SkGlyph& glyph = strike->getGlyphMetrics(glyphID, {0, 0});
+
+                if (glyph.isEmpty()) {
+                    // do nothing
+                } else if (glyph.fMaskFormat == SkMask::kSDF_Format
+                           && glyph.maxDimension() <= SkStrikeCommon::kSkSideTooBigForAtlas) {
+                    fGlyphPos[glyphCount++] = {i, &glyph, glyphSourcePosition};
+                } else if (glyph.fMaskFormat != SkMask::kARGB32_Format
+                           && strike->decideCouldDrawFromPath(glyph)) {
+                    fPaths.push_back({i, &glyph, glyphSourcePosition});
+                } else {
+                    addFallback(glyph, glyphSourcePosition);
+                }
+            }
+
+            if (process) {
+                if (glyphCount > 0) {
+                    bool hasWCoord = viewMatrix.hasPerspective()
+                                     || options.fDistanceFieldVerticesAlwaysHaveW;
+                    process->processSourceSDFT(
+                            SkSpan<const SkGlyphPos>{fGlyphPos, SkTo<size_t>(glyphCount)},
+                            strike.get(),
+                            runFont,
+                            cacheToSourceScale,
+                            minScale,
+                            maxScale,
+                            hasWCoord);
+                }
+
+                if (!fPaths.empty()) {
+                    process->processSourcePaths(
+                            SkSpan<const SkGlyphPos>{fPaths}, strike.get(), cacheToSourceScale);
+                }
+            }
+
+            // fGlyphPos will be reused here.
+            if (!fARGBGlyphsIDs.empty()) {
+                this->processARGBFallback(maxFallbackDimension * cacheToSourceScale,
+                                          runPaint, runFont, viewMatrix, process);
+            }
+        } else if (SkGlyphRunListPainter::ShouldDrawAsPath(runPaint, runFont, viewMatrix)) {
+
+            // Translate all glyphs to the origin.
+            SkMatrix translate = SkMatrix::MakeTrans(origin.x(), origin.y());
+            translate.mapPoints(fPositions, glyphRun.positions().data(), glyphRun.runSize());
+
+            // setup our std runPaint, in hopes of getting hits in the cache
+            SkPaint pathPaint{runPaint};
+            SkFont pathFont{runFont};
+
+            // The factor to get from the size stored in the strike to the size needed for
+            // the source.
+            SkScalar strikeToSourceRatio = pathFont.setupForAsPaths(&pathPaint);
+
+            // The sub-pixel position will always happen when transforming to the screen.
+            pathFont.setSubpixel(false);
+
+            SkAutoDescriptor ad;
+            SkScalerContextEffects effects;
+            SkScalerContext::CreateDescriptorAndEffectsUsingPaint(pathFont,
+                                                                  pathPaint,
+                                                                  fDeviceProps,
+                                                                  fScalerContextFlags,
+                                                                  SkMatrix::I(),
+                                                                  &ad,
+                                                                  &effects);
+
+            SkScopedStrike strike =
+                    fStrikeCache->findOrCreateScopedStrike(
+                            *ad.getDesc(), effects,*pathFont.getTypefaceOrDefault());
+
+            int glyphCount = 0;
+            for (size_t i = 0; i < glyphRun.runSize(); i++) {
+                SkGlyphID glyphID = glyphRun.glyphsIDs()[i];
+                SkPoint glyphSourcePosition = fPositions[i];
+
+                // Use outline from {0, 0} because all transforms including subpixel translation
+                // happen during drawing.
+                const SkGlyph& glyph = strike->getGlyphMetrics(glyphID, {0, 0});
+                if (glyph.isEmpty()) {
+                    // do nothing
+                } else if (glyph.fMaskFormat != SkMask::kARGB32_Format
+                           && strike->decideCouldDrawFromPath(glyph)) {
+                    fGlyphPos[glyphCount++] = {i, &glyph, glyphSourcePosition};
+                } else {
+                    addFallback(glyph, glyphSourcePosition);
+                }
+            }
+
+            if (process) {
+                if (glyphCount > 0) {
+                    process->processSourcePaths(
+                            SkSpan<const SkGlyphPos>{fGlyphPos, SkTo<size_t>(glyphCount)},
+                            strike.get(),
+                            strikeToSourceRatio);
+                }
+            }
+            // fGlyphPos will be reused here.
+            if (!fARGBGlyphsIDs.empty()) {
+                this->processARGBFallback(maxFallbackDimension * strikeToSourceRatio,
+                                          runPaint, runFont, viewMatrix, process);
             }
         } else {
-            SkScalar largestDimension = std::max(glyph.fWidth, glyph.fHeight);
-            maxFallbackDimension = std::max(maxFallbackDimension, largestDimension);
-            fARGBGlyphsIDs.push_back(glyphID);
-            fARGBPositions.push_back(glyphPos);
-        }
-    }
 
-    if (!fARGBGlyphsIDs.empty()) {
-        this->processARGBFallback(
-                maxFallbackDimension, runPaint, glyphRun.font(), viewMatrix, textScale,
-                std::move(argbFallback));
+            SkAutoDescriptor ad;
+            SkScalerContextEffects effects;
 
-    }
-}
+            SkScalerContext::CreateDescriptorAndEffectsUsingPaint(
+                    runFont, runPaint, fDeviceProps, fScalerContextFlags, viewMatrix, &ad,
+                    &effects);
 
-template <typename EmptiesT, typename MasksT, typename PathsT>
-void SkGlyphRunListPainter::drawGlyphRunAsBMPWithPathFallback(
-        SkStrikeInterface* cache, const SkGlyphRun& glyphRun,
-        SkPoint origin, const SkMatrix& deviceMatrix,
-        EmptiesT&& processEmpties, MasksT&& processMasks, PathsT&& processPaths) {
-    ScopedBuffers _ = this->ensureBuffers(glyphRun);
+            SkTypeface* typeface = runFont.getTypefaceOrDefault();
+            SkScopedStrike strike =
+                    fStrikeCache->findOrCreateScopedStrike(*ad.getDesc(), effects, *typeface);
 
-    int glyphCount = 0;
-    // Four empty glyphs are expected; one for each horizontal subpixel position.
-    SkSTArray<4, const SkGlyph*> emptyGlyphs;
+            SkMatrix mapping = viewMatrix;
+            mapping.preTranslate(origin.x(), origin.y());
+            SkVector rounding = strike->rounding();
+            mapping.postTranslate(rounding.x(), rounding.y());
+            mapping.mapPoints(fPositions, glyphRun.positions().data(), glyphRun.runSize());
 
-    SkMatrix mapping = deviceMatrix;
-    mapping.preTranslate(origin.x(), origin.y());
-    SkVector rounding = cache->rounding();
-    mapping.postTranslate(rounding.x(), rounding.y());
-    mapping.mapPoints(fPositions,  glyphRun.positions().data(), glyphRun.runSize());
+            SkSpan<const SkGlyphPos> glyphPosSpan = strike->glyphMetrics(
+                    glyphRun.glyphsIDs().data(), fPositions, glyphRun.runSize(), fGlyphPos);
 
-    const SkPoint* posCursor = fPositions;
-    for (auto glyphID : glyphRun.glyphsIDs()) {
-        SkPoint mappedPt = *posCursor++;
-
-        if (std::any_of(emptyGlyphs.begin(), emptyGlyphs.end(),
-                        [glyphID](const SkGlyph* g) { return g->getGlyphID() == glyphID; })) {
-            continue;
-        }
-
-        if (SkScalarsAreFinite(mappedPt.x(), mappedPt.y())) {
-            const SkGlyph& glyph = cache->getGlyphMetrics(glyphID, mappedPt);
-            if (glyph.isEmpty()) {
-                emptyGlyphs.push_back(&glyph);
-            } else if (SkStrikeCommon::GlyphTooBigForAtlas(glyph)) {
-                if (cache->hasPath(glyph)) {
-                    fPaths.push_back({&glyph, mappedPt});
-                } else {
-                    // This happens when a bitmap-only font is forced to scale very large. This
-                    // doesn't happen in practice.
-                    emptyGlyphs.push_back(&glyph);
+            size_t glyphsWithMaskCount = 0;
+            for (const SkGlyphPos& glyphPos : glyphPosSpan) {
+                const SkGlyph& glyph = *glyphPos.glyph;
+                const SkPoint position = glyphPos.position;
+                if (!SkScalarsAreFinite(position.x(), position.y())) {
+                    continue;
                 }
-            } else {
-                if (cache->hasImage(glyph)) {
-                    fMasks[glyphCount++] = {&glyph, mappedPt};
+
+                if (glyph.maxDimension() <= SkStrikeCommon::kSkSideTooBigForAtlas) {
+                    fGlyphPos[glyphsWithMaskCount++] = glyphPos;
+                } else if (glyph.fMaskFormat != SkMask::kARGB32_Format
+                           && strike->decideCouldDrawFromPath(glyph)) {
+                    fPaths.push_back(glyphPos);
                 } else {
-                    // In practice, this never happens.
-                    emptyGlyphs.push_back(&glyph);
+                    addFallback(glyph, origin + glyphRun.positions()[glyphPos.index]);
                 }
             }
-        }
-    }
 
-    if (!emptyGlyphs.empty()) {
-        processEmpties(SkSpan<const SkGlyph*>{emptyGlyphs.data(), emptyGlyphs.size()});
-    }
-    if (glyphCount > 0) {
-        mapping.mapPoints(fPositions, glyphCount);
-        processMasks(SkSpan<const GlyphAndPos>{fMasks, SkTo<size_t>(glyphCount)});
-    }
-    if (!fPaths.empty()) {
-        processPaths(SkSpan<const GlyphAndPos>{fPaths});
-    }
-}
-
-template <typename PerEmptyT, typename PerSDFT, typename PerPathT>
-void SkGlyphRunListPainter::drawGlyphRunAsSDFWithARGBFallback(
-        SkStrikeInterface* cache, const SkGlyphRun& glyphRun,
-        SkPoint origin, const SkPaint& runPaint, const SkMatrix& viewMatrix, SkScalar textScale,
-        PerEmptyT&& perEmpty, PerSDFT&& perSDF, PerPathT&& perPath, ARGBFallback&& argbFallback) {
-    fARGBGlyphsIDs.clear();
-    fARGBPositions.clear();
-    SkScalar maxFallbackDimension{-SK_ScalarInfinity};
-
-    const SkPoint* positionCursor = glyphRun.positions().data();
-    for (auto glyphID : glyphRun.glyphsIDs()) {
-        const SkGlyph& glyph = cache->getGlyphMetrics(glyphID, {0, 0});
-        SkPoint glyphPos = origin + *positionCursor++;
-        if (glyph.isEmpty()) {
-            perEmpty(glyph, glyphPos);
-        } else if (glyph.fMaskFormat == SkMask::kSDF_Format) {
-            if (!SkStrikeCommon::GlyphTooBigForAtlas(glyph)) {
-                // TODO: this check is probably not needed. Remove when proven.
-                if (cache->hasImage(glyph)) {
-                    perSDF(glyph, glyphPos);
-                } else {
-                    perEmpty(glyph, glyphPos);
-                }
-            } else {
-                if (cache->hasPath(glyph)) {
-                    perPath(glyph, glyphPos);
-                } else {
-                    perEmpty(glyph, glyphPos);
+            if (process) {
+                // processDeviceMasks must be called even if there are no glyphs to make sure runs
+                // are set correctly.
+                process->processDeviceMasks(
+                        SkSpan<const SkGlyphPos>{fGlyphPos, SkTo<size_t>(glyphsWithMaskCount)},
+                        strike.get());
+                if (!fPaths.empty()) {
+                    process->processDevicePaths(SkSpan<const SkGlyphPos>{fPaths});
                 }
             }
-        } else {
-            SkASSERT(glyph.fMaskFormat == SkMask::kARGB32_Format);
-            SkScalar largestDimension = std::max(glyph.fWidth, glyph.fHeight);
-            maxFallbackDimension = std::max(maxFallbackDimension, largestDimension);
-            fARGBGlyphsIDs.push_back(glyphID);
-            fARGBPositions.push_back(glyphPos);
-        }
-    }
-
-    if (!fARGBGlyphsIDs.empty()) {
-        this->processARGBFallback(
-                maxFallbackDimension, runPaint, glyphRun.font(), viewMatrix, textScale,
-                std::move(argbFallback));
-    }
+            // fGlyphPos will be reused here.
+            if (!fARGBGlyphsIDs.empty()) {
+                this->processARGBFallback(maxFallbackDimension / viewMatrix.getMaxScale(),
+                                          runPaint, runFont, viewMatrix, process);
+            }
+        }  // Mask case
+    }  // For all glyph runs
 }
+#endif  // SK_SUPPORT_GPU
 
-SkGlyphRunListPainter::ScopedBuffers
-SkGlyphRunListPainter::ensureBuffers(const SkGlyphRunList& glyphRunList) {
+auto SkGlyphRunListPainter::ensureBuffers(const SkGlyphRunList& glyphRunList) -> ScopedBuffers {
     size_t size = 0;
     for (const SkGlyphRun& run : glyphRunList) {
         size = std::max(run.runSize(), size);
@@ -504,7 +621,7 @@
 }
 
 void GrTextContext::drawGlyphRunList(
-        GrContext* context, GrTextTarget* target, const GrClip& clip,
+        GrRecordingContext* context, GrTextTarget* target, const GrClip& clip,
         const SkMatrix& viewMatrix, const SkSurfaceProps& props,
         const SkGlyphRunList& glyphRunList) {
     SkPoint origin = glyphRunList.origin();
@@ -516,7 +633,7 @@
     GrColor color = generate_filtered_color(listPaint, target->colorSpaceInfo()).toBytes_RGBA();
 
     // If we have been abandoned, then don't draw
-    if (context->abandoned()) {
+    if (context->priv().abandoned()) {
         return;
     }
 
@@ -528,7 +645,7 @@
                                                  (mf && !as_MFB(mf)->asABlur(&blurRec)));
     SkScalerContextFlags scalerContextFlags = ComputeScalerContextFlags(target->colorSpaceInfo());
 
-    auto glyphCache = context->priv().getGlyphCache();
+    auto grStrikeCache = context->priv().getGrStrikeCache();
     GrTextBlobCache* textBlobCache = context->priv().getTextBlobCache();
 
     sk_sp<GrTextBlob> cacheBlob;
@@ -562,19 +679,21 @@
             // TODO we could probably get away reuse most of the time if the pointer is unique,
             // but we'd have to clear the subrun information
             textBlobCache->remove(cacheBlob.get());
-            cacheBlob = textBlobCache->makeCachedBlob(glyphRunList, key, blurRec, listPaint, color);
+            cacheBlob = textBlobCache->makeCachedBlob(
+                    glyphRunList, key, blurRec, listPaint, color, grStrikeCache);
             cacheBlob->generateFromGlyphRunList(
-                    glyphCache, *context->priv().caps()->shaderCaps(), fOptions,
+                    *context->priv().caps()->shaderCaps(), fOptions,
                     listPaint, scalerContextFlags, viewMatrix, props,
                     glyphRunList, target->glyphPainter());
         } else {
             textBlobCache->makeMRU(cacheBlob.get());
 
             if (CACHE_SANITY_CHECK) {
-                sk_sp<GrTextBlob> sanityBlob(textBlobCache->makeBlob(glyphRunList, color));
+                sk_sp<GrTextBlob> sanityBlob(textBlobCache->makeBlob(
+                        glyphRunList, color, grStrikeCache));
                 sanityBlob->setupKey(key, blurRec, listPaint);
                 cacheBlob->generateFromGlyphRunList(
-                        glyphCache, *context->priv().caps()->shaderCaps(), fOptions,
+                        *context->priv().caps()->shaderCaps(), fOptions,
                         listPaint, scalerContextFlags, viewMatrix, props, glyphRunList,
                         target->glyphPainter());
                 GrTextBlob::AssertEqual(*sanityBlob, *cacheBlob);
@@ -582,12 +701,13 @@
         }
     } else {
         if (canCache) {
-            cacheBlob = textBlobCache->makeCachedBlob(glyphRunList, key, blurRec, listPaint, color);
+            cacheBlob = textBlobCache->makeCachedBlob(
+                    glyphRunList, key, blurRec, listPaint, color, grStrikeCache);
         } else {
-            cacheBlob = textBlobCache->makeBlob(glyphRunList, color);
+            cacheBlob = textBlobCache->makeBlob(glyphRunList, color, grStrikeCache);
         }
         cacheBlob->generateFromGlyphRunList(
-                glyphCache, *context->priv().caps()->shaderCaps(), fOptions, listPaint,
+                *context->priv().caps()->shaderCaps(), fOptions, listPaint,
                 scalerContextFlags, viewMatrix, props, glyphRunList,
                 target->glyphPainter());
     }
@@ -683,8 +803,7 @@
     }
 }
 
-void GrTextBlob::generateFromGlyphRunList(GrStrikeCache* glyphCache,
-                                          const GrShaderCaps& shaderCaps,
+void GrTextBlob::generateFromGlyphRunList(const GrShaderCaps& shaderCaps,
                                           const GrTextContext::Options& options,
                                           const SkPaint& paint,
                                           SkScalerContextFlags scalerContextFlags,
@@ -692,188 +811,130 @@
                                           const SkSurfaceProps& props,
                                           const SkGlyphRunList& glyphRunList,
                                           SkGlyphRunListPainter* glyphPainter) {
-    struct ARGBFallbackHelper {
-        void operator()(const SkPaint& fallbackPaint, const SkFont& fallbackFont,
-                        SkSpan<const SkGlyphID> glyphIDs,
-                        SkSpan<const SkPoint> positions, SkScalar textScale,
-                        const SkMatrix& glyphCacheMatrix,
-                        SkGlyphRunListPainter::NeedsTransform needsTransform) const {
-            fBlob->setHasBitmap();
-            fRun->setSubRunHasW(glyphCacheMatrix.hasPerspective());
-            auto subRun = fRun->initARGBFallback();
-            SkExclusiveStrikePtr fallbackCache = SkStrikeCache::FindOrCreateStrikeExclusive(
-                    fallbackFont, fallbackPaint, fProps, fScalerContextFlags, glyphCacheMatrix);
-            sk_sp<GrTextStrike> strike = fGrStrikeCache->getStrike(fallbackCache.get());
-            fRun->setupFont(fallbackPaint, fallbackFont, fallbackCache->getDescriptor());
-
-            SkASSERT(strike != nullptr);
-            subRun->setStrike(strike);
-            const SkPoint* glyphPos = positions.data();
-            if (needsTransform == SkGlyphRunListPainter::kTransformDone) {
-                for (auto glyphID : glyphIDs) {
-                    const SkGlyph& glyph = fallbackCache->getGlyphIDMetrics(glyphID);
-                    fRun->appendDeviceSpaceGlyph(strike, glyph, *glyphPos++);
-                }
-            } else {
-                for (auto glyphID : glyphIDs) {
-                    const SkGlyph& glyph = fallbackCache->getGlyphIDMetrics(glyphID);
-                    fRun->appendSourceSpaceGlyph(strike, glyph, *glyphPos++, textScale);
-                }
-            }
-        }
-
-        GrTextBlob* const fBlob;
-        GrTextBlob::Run* fRun;
-        const SkSurfaceProps& fProps;
-        const SkScalerContextFlags fScalerContextFlags;
-        GrStrikeCache* const fGrStrikeCache;
-    };
-
     SkPoint origin = glyphRunList.origin();
     const SkPaint& runPaint = glyphRunList.paint();
     this->initReusableBlob(SkPaintPriv::ComputeLuminanceColor(runPaint), viewMatrix,
                            origin.x(), origin.y());
 
-    for (const auto& glyphRun : glyphRunList) {
-        const SkFont& runFont = glyphRun.font();
+    glyphPainter->processGlyphRunList(glyphRunList,
+                                      viewMatrix,
+                                      props,
+                                      shaderCaps.supportsDistanceFieldText(),
+                                      options,
+                                      this);
+}
 
-        Run* run = this->pushBackRun();
+GrTextBlob::Run* GrTextBlob::currentRun() {
+    return &fRuns[fRunCount - 1];
+}
 
-        run->setRunFontAntiAlias(runFont.hasSomeAntiAliasing());
+void GrTextBlob::startRun(const SkGlyphRun& glyphRun, bool useSDFT) {
+    if (useSDFT) {
+        this->setHasDistanceField();
+    }
+    Run* run = this->pushBackRun();
+    run->setRunFontAntiAlias(glyphRun.font().hasSomeAntiAliasing());
+}
 
-        if (GrTextContext::CanDrawAsDistanceFields(runPaint, runFont, viewMatrix, props,
-                                    shaderCaps.supportsDistanceFieldText(), options)) {
-            bool hasWCoord = viewMatrix.hasPerspective()
-                             || options.fDistanceFieldVerticesAlwaysHaveW;
+void GrTextBlob::processDeviceMasks(SkSpan<const SkGlyphPos> masks,
+                                    SkStrikeInterface* strike) {
+    Run* run = this->currentRun();
+    this->setHasBitmap();
+    run->setupFont(strike->strikeSpec());
+    sk_sp<GrTextStrike> currStrike = fStrikeCache->getStrike(strike->getDescriptor());
+    for (const auto& mask : masks) {
+        SkPoint pt{SkScalarFloorToScalar(mask.position.fX),
+                   SkScalarFloorToScalar(mask.position.fY)};
+        run->appendDeviceSpaceGlyph(currStrike, *mask.glyph, pt);
+    }
+}
 
-            // Setup distance field runPaint and text ratio
-            SkScalar textScale;
-            SkPaint distanceFieldPaint{runPaint};
-            SkFont distanceFieldFont{runFont};
-            SkScalerContextFlags flags;
-            GrTextContext::InitDistanceFieldPaint(runFont.getSize(),
-                                                  viewMatrix,
-                                                  options,
-                                                  this,
-                                                  &distanceFieldPaint,
-                                                  &distanceFieldFont,
-                                                  &textScale,
-                                                  &flags);
-            this->setHasDistanceField();
-            run->setSubRunHasDistanceFields(
-                    runFont.getEdging() == SkFont::Edging::kSubpixelAntiAlias,
-                    runFont.hasSomeAntiAliasing(),
-                    hasWCoord);
-
-            {
-                SkExclusiveStrikePtr cache =SkStrikeCache::FindOrCreateStrikeExclusive(
-                        distanceFieldFont, distanceFieldPaint, props, flags, SkMatrix::I());
-                sk_sp<GrTextStrike> currStrike = glyphCache->getStrike(cache.get());
-                run->setupFont(distanceFieldPaint, distanceFieldFont, cache->getDescriptor());
-
-                auto perEmpty = [](const SkGlyph&, SkPoint) {};
-
-                auto perSDF =
-                    [run, &currStrike, textScale]
-                    (const SkGlyph& glyph, SkPoint position) {
-                        run->appendSourceSpaceGlyph(currStrike, glyph, position, textScale);
-                    };
-
-                auto perPath =
-                    [run, textScale]
-                    (const SkGlyph& glyph, SkPoint position) {
-                        // TODO: path should always be set. Remove when proven.
-                        if (const SkPath* glyphPath = glyph.path()) {
-                            run->appendPathGlyph(*glyphPath, position, textScale, false);
-                        }
-                    };
-
-                ARGBFallbackHelper argbFallback{this, run, props, scalerContextFlags,
-                                                glyphCache};
-
-                glyphPainter->drawGlyphRunAsSDFWithARGBFallback(
-                    cache.get(), glyphRun, origin, runPaint, viewMatrix, textScale,
-                    std::move(perEmpty), std::move(perSDF), std::move(perPath),
-                    std::move(argbFallback));
-            }
-
-        } else if (SkGlyphRunListPainter::ShouldDrawAsPath(runPaint, runFont, viewMatrix)) {
-            // The glyphs are big, so use paths to draw them.
-
-            // Ensure the blob is set for bitmaptext
-            this->setHasBitmap();
-
-            // setup our std runPaint, in hopes of getting hits in the cache
-            SkPaint pathPaint{runPaint};
-            SkFont pathFont{runFont};
-            SkScalar textScale = pathFont.setupForAsPaths(&pathPaint);
-
-            auto pathCache = SkStrikeCache::FindOrCreateStrikeExclusive(
-                                pathFont, pathPaint, props,
-                                scalerContextFlags, SkMatrix::I());
-
-            auto perEmpty = [](const SkGlyph&, SkPoint) {};
-
-            // Given a glyph that is not ARGB, draw it.
-            auto perPath = [textScale, run]
-                           (const SkGlyph& glyph, SkPoint position) {
-                // TODO: path should always be set. Remove when proven.
-                if (const SkPath* glyphPath = glyph.path()) {
-                    run->appendPathGlyph(*glyphPath, position, textScale, false);
-                }
-            };
-
-            ARGBFallbackHelper argbFallback{this, run, props, scalerContextFlags, glyphCache};
-
-            glyphPainter->drawGlyphRunAsPathWithARGBFallback(
-                pathCache.get(), glyphRun, origin, runPaint, viewMatrix, textScale,
-                std::move(perEmpty), std::move(perPath), std::move(argbFallback));
-        } else {
-            // Ensure the blob is set for bitmaptext
-            this->setHasBitmap();
-
-            auto cache = SkStrikeCache::FindOrCreateStrikeExclusive(
-                    runFont, runPaint, props, scalerContextFlags, viewMatrix);
-            run->setupFont(runPaint, runFont, cache->getDescriptor());
-
-            auto processEmpties = [](SkSpan<const SkGlyph*>glyphs) {};
-
-            auto processMasks =
-                [run, cache{cache.get()}, glyphCache]
-                (SkSpan<const SkGlyphRunListPainter::GlyphAndPos> masks) {
-                    sk_sp<GrTextStrike> currStrike = glyphCache->getStrike(cache);
-                    for (const auto& mask : masks) {
-                        SkPoint pt{SkScalarFloorToScalar(mask.position.fX),
-                                   SkScalarFloorToScalar(mask.position.fY)};
-                        run->appendDeviceSpaceGlyph(currStrike, *mask.glyph, pt);
-                    }
-                };
-
-            auto processPaths =
-                [run]
-                (SkSpan<const SkGlyphRunListPainter::GlyphAndPos> paths) {
-                    for (const auto& path : paths) {
-                        SkPoint pt{SkScalarFloorToScalar(path.position.fX),
-                                   SkScalarFloorToScalar(path.position.fY)};
-                        // TODO: path should always be set. Remove when proven.
-                        if (const SkPath* glyphPath = path.glyph->path()) {
-                            run->appendPathGlyph(*glyphPath, pt, SK_Scalar1, true);
-                        }
-                    }
-                };
-
-            glyphPainter->drawGlyphRunAsBMPWithPathFallback(
-                    cache.get(), glyphRun, origin, viewMatrix,
-                    std::move(processEmpties), std::move(processMasks), std::move(processPaths));
+void GrTextBlob::processSourcePaths(SkSpan<const SkGlyphPos> paths,
+                                    SkStrikeInterface* strike, SkScalar cacheToSourceScale) {
+    Run* run = this->currentRun();
+    this->setHasBitmap();
+    run->setupFont(strike->strikeSpec());
+    for (const auto& path : paths) {
+        if (const SkPath* glyphPath = path.glyph->path()) {
+            run->appendPathGlyph(*glyphPath, path.position, cacheToSourceScale,
+                                 false);
         }
     }
 }
 
+void GrTextBlob::processDevicePaths(SkSpan<const SkGlyphPos> paths) {
+    Run* run = this->currentRun();
+    this->setHasBitmap();
+    for (const auto& path : paths) {
+        SkPoint pt{SkScalarFloorToScalar(path.position.fX),
+                   SkScalarFloorToScalar(path.position.fY)};
+        // TODO: path should always be set. Remove when proven.
+        if (const SkPath* glyphPath = path.glyph->path()) {
+            run->appendPathGlyph(*glyphPath, pt, SK_Scalar1, true);
+        }
+    }
+}
+
+void GrTextBlob::processSourceSDFT(SkSpan<const SkGlyphPos> masks,
+                                   SkStrikeInterface* strike,
+                                   const SkFont& runFont,
+                                   SkScalar cacheToSourceScale,
+                                   SkScalar minScale,
+                                   SkScalar maxScale,
+                                   bool hasWCoord) {
+
+    Run* run = this->currentRun();
+    run->setSubRunHasDistanceFields(
+            runFont.getEdging() == SkFont::Edging::kSubpixelAntiAlias,
+            runFont.hasSomeAntiAliasing(),
+            hasWCoord);
+    this->setMinAndMaxScale(minScale, maxScale);
+    run->setupFont(strike->strikeSpec());
+    sk_sp<GrTextStrike> currStrike = fStrikeCache->getStrike(strike->getDescriptor());
+    for (const auto& mask : masks) {
+        run->appendSourceSpaceGlyph(
+                currStrike, *mask.glyph, mask.position, cacheToSourceScale);
+    }
+}
+
+void GrTextBlob::processSourceFallback(SkSpan<const SkGlyphPos> masks,
+                                       SkStrikeInterface* strike, SkScalar cacheToSourceScale,
+                                       bool hasW) {
+    Run* run = this->currentRun();
+
+    auto subRun = run->initARGBFallback();
+    sk_sp<GrTextStrike> grStrike =
+            fStrikeCache->getStrike(strike->getDescriptor());
+    subRun->setStrike(grStrike);
+    subRun->setHasWCoord(hasW);
+
+    this->setHasBitmap();
+    run->setupFont(strike->strikeSpec());
+    for (const auto& mask : masks) {
+        run->appendSourceSpaceGlyph
+                (grStrike, *mask.glyph, mask.position, cacheToSourceScale);
+    }
+}
+
+void GrTextBlob::processDeviceFallback(SkSpan<const SkGlyphPos> masks,
+                                       SkStrikeInterface* strike) {
+    Run* run = this->currentRun();
+    this->setHasBitmap();
+    sk_sp<GrTextStrike> grStrike = fStrikeCache->getStrike(strike->getDescriptor());
+    auto subRun = run->initARGBFallback();
+    run->setupFont(strike->strikeSpec());
+    subRun->setStrike(grStrike);
+    for (const auto& mask : masks) {
+        run->appendDeviceSpaceGlyph(grStrike, *mask.glyph, mask.position);
+    }
+}
+
 #if GR_TEST_UTILS
 
 #include "GrRenderTargetContext.h"
+#include "GrRecordingContextPriv.h"
 
-std::unique_ptr<GrDrawOp> GrTextContext::createOp_TestingOnly(GrContext* context,
+std::unique_ptr<GrDrawOp> GrTextContext::createOp_TestingOnly(GrRecordingContext* context,
                                                               GrTextContext* textContext,
                                                               GrRenderTargetContext* rtc,
                                                               const SkPaint& skPaint,
@@ -882,7 +943,12 @@
                                                               const char* text,
                                                               int x,
                                                               int y) {
-    auto glyphCache = context->priv().getGlyphCache();
+    auto direct = context->priv().asDirectContext();
+    if (!direct) {
+        return nullptr;
+    }
+
+    auto strikeCache = direct->priv().getGrStrikeCache();
 
     static SkSurfaceProps surfaceProps(SkSurfaceProps::kLegacyFontHost_InitType);
 
@@ -898,12 +964,12 @@
     auto glyphRunList = builder.useGlyphRunList();
     sk_sp<GrTextBlob> blob;
     if (!glyphRunList.empty()) {
-        blob = context->priv().getTextBlobCache()->makeBlob(glyphRunList, color);
+        blob = direct->priv().getTextBlobCache()->makeBlob(glyphRunList, color, strikeCache);
         // Use the text and textLen below, because we don't want to mess with the paint.
         SkScalerContextFlags scalerContextFlags =
                 ComputeScalerContextFlags(rtc->colorSpaceInfo());
         blob->generateFromGlyphRunList(
-                glyphCache, *context->priv().caps()->shaderCaps(), textContext->fOptions,
+                *context->priv().caps()->shaderCaps(), textContext->fOptions,
                 skPaint, scalerContextFlags, viewMatrix, surfaceProps,
                 glyphRunList, rtc->textTarget()->glyphPainter());
     }
@@ -915,188 +981,6 @@
 #endif  // GR_TEST_UTILS
 #endif  // SK_SUPPORT_GPU
 
-// -- SkTextBlobCacheDiffCanvas::TrackLayerDevice --------------------------------------------------
-
-void SkTextBlobCacheDiffCanvas::TrackLayerDevice::processGlyphRun(
-        const SkPoint& origin, const SkGlyphRun& glyphRun, const SkPaint& runPaint) {
-    TRACE_EVENT0("skia", "SkTextBlobCacheDiffCanvas::processGlyphRun");
-
-    const SkMatrix& runMatrix = this->ctm();
-
-    // If the matrix has perspective, we fall back to using distance field text or paths.
-#if SK_SUPPORT_GPU
-    if (this->maybeProcessGlyphRunForDFT(glyphRun, runMatrix, origin, runPaint)) {
-        return;
-    } else
-#endif
-    if (SkGlyphRunListPainter::ShouldDrawAsPath(runPaint, glyphRun.font(), runMatrix)) {
-        this->processGlyphRunForPaths(glyphRun, runMatrix, origin, runPaint);
-    } else {
-        this->processGlyphRunForMask(glyphRun, runMatrix, origin, runPaint);
-    }
-}
-
-void SkTextBlobCacheDiffCanvas::TrackLayerDevice::processGlyphRunForMask(
-        const SkGlyphRun& glyphRun, const SkMatrix& runMatrix,
-        SkPoint origin, const SkPaint& runPaint) {
-    TRACE_EVENT0("skia", "SkTextBlobCacheDiffCanvas::processGlyphRunForMask");
-
-    SkScalerContextEffects effects;
-    auto* glyphCacheState = fStrikeServer->getOrCreateCache(
-            runPaint, glyphRun.font(), this->surfaceProps(), runMatrix,
-            SkScalerContextFlags::kFakeGammaAndBoostContrast, &effects);
-    SkASSERT(glyphCacheState);
-
-    auto processEmpties = [glyphCacheState] (SkSpan<const SkGlyph*>glyphs) {
-        for (const SkGlyph* glyph : glyphs) {
-            glyphCacheState->addGlyph(glyph->getPackedID(), false);
-        }
-    };
-
-    auto processMasks = [glyphCacheState]
-                    (SkSpan<const SkGlyphRunListPainter::GlyphAndPos> masks) {
-        for (const auto& mask : masks) {
-            glyphCacheState->addGlyph(mask.glyph->getPackedID(), false);
-        }
-    };
-
-    // Glyphs which are too large for the atlas still request images when computing the bounds
-    // for the glyph, which is why its necessary to send both. See related code in
-    // get_packed_glyph_bounds in GrStrikeCache.cpp and crbug.com/510931.
-    auto processPaths = [glyphCacheState]
-                   (SkSpan<const SkGlyphRunListPainter::GlyphAndPos> paths) {
-        for (const auto& path : paths) {
-            SkPackedGlyphID glyphID = path.glyph->getPackedID();
-            glyphCacheState->addGlyph(glyphID, true);
-            glyphCacheState->addGlyph(glyphID, false);
-        }
-    };
-
-    fPainter.drawGlyphRunAsBMPWithPathFallback(
-            glyphCacheState, glyphRun, origin, runMatrix,
-            std::move(processEmpties), std::move(processMasks), std::move(processPaths));
-}
-
-struct ARGBHelper {
-    void operator()(const SkPaint& fallbackPaint,
-                    const SkFont& fallbackFont,
-                    SkSpan<const SkGlyphID> glyphIDs,
-                    SkSpan<const SkPoint> positions,
-                    SkScalar textScale,
-                    const SkMatrix& glyphCacheMatrix,
-                    SkGlyphRunListPainter::NeedsTransform needsTransform) {
-        TRACE_EVENT0("skia", "argbFallback");
-
-        SkScalerContextEffects effects;
-        auto* fallbackCache =
-                fStrikeServer->getOrCreateCache(
-                        fallbackPaint, fallbackFont, fSurfaceProps, fFallbackMatrix,
-                        SkScalerContextFlags::kFakeGammaAndBoostContrast, &effects);
-
-        for (auto glyphID : glyphIDs) {
-            fallbackCache->addGlyph(SkPackedGlyphID(glyphID, 0, 0), false);
-        }
-    }
-
-    const SkMatrix& fFallbackMatrix;
-    const SkSurfaceProps& fSurfaceProps;
-    SkStrikeServer* const fStrikeServer;
-};
-
-SkScalar SkTextBlobCacheDiffCanvas::SetupForPath(SkPaint* paint, SkFont* font) {
-    return font->setupForAsPaths(paint);
-}
-
-void SkTextBlobCacheDiffCanvas::TrackLayerDevice::processGlyphRunForPaths(
-        const SkGlyphRun& glyphRun, const SkMatrix& runMatrix,
-        SkPoint origin, const SkPaint& runPaint) {
-    TRACE_EVENT0("skia", "SkTextBlobCacheDiffCanvas::processGlyphRunForPaths");
-
-    SkPaint pathPaint{runPaint};
-    SkFont pathFont{glyphRun.font()};
-    SkScalar textScale = SetupForPath(&pathPaint, &pathFont);
-
-    SkScalerContextEffects effects;
-    auto* glyphCacheState = fStrikeServer->getOrCreateCache(
-            pathPaint, pathFont, this->surfaceProps(), SkMatrix::I(),
-            SkScalerContextFlags::kFakeGammaAndBoostContrast, &effects);
-
-    auto perEmpty = [glyphCacheState] (const SkGlyph& glyph, SkPoint mappedPt) {
-        glyphCacheState->addGlyph(glyph.getPackedID(), false);
-    };
-
-    auto perPath = [glyphCacheState](const SkGlyph& glyph, SkPoint position) {
-        const bool asPath = true;
-        glyphCacheState->addGlyph(glyph.getGlyphID(), asPath);
-    };
-
-    ARGBHelper argbFallback{runMatrix, surfaceProps(), fStrikeServer};
-
-    fPainter.drawGlyphRunAsPathWithARGBFallback(
-            glyphCacheState, glyphRun, origin, runPaint, runMatrix, textScale,
-            std::move(perEmpty), std::move(perPath), std::move(argbFallback));
-}
-
-#if SK_SUPPORT_GPU
-bool SkTextBlobCacheDiffCanvas::TrackLayerDevice::maybeProcessGlyphRunForDFT(
-        const SkGlyphRun& glyphRun, const SkMatrix& runMatrix,
-        SkPoint origin, const SkPaint& runPaint) {
-    TRACE_EVENT0("skia", "SkTextBlobCacheDiffCanvas::maybeProcessGlyphRunForDFT");
-
-    const SkFont& runFont = glyphRun.font();
-
-    GrTextContext::Options options;
-    options.fMinDistanceFieldFontSize = fSettings.fMinDistanceFieldFontSize;
-    options.fMaxDistanceFieldFontSize = fSettings.fMaxDistanceFieldFontSize;
-    GrTextContext::SanitizeOptions(&options);
-    if (!GrTextContext::CanDrawAsDistanceFields(runPaint, runFont,
-                                                runMatrix, this->surfaceProps(),
-                                                fSettings.fContextSupportsDistanceFieldText,
-                                                options)) {
-        return false;
-    }
-
-    SkScalar textRatio;
-    SkPaint dfPaint{runPaint};
-    SkFont dfFont{runFont};
-    SkScalerContextFlags flags;
-    GrTextContext::InitDistanceFieldPaint(runFont.getSize(),
-                                          runMatrix,
-                                          options,
-                                          nullptr,
-                                          &dfPaint,
-                                          &dfFont,
-                                          &textRatio,
-                                          &flags);
-    SkScalerContextEffects effects;
-    auto* sdfCache = fStrikeServer->getOrCreateCache(dfPaint, dfFont, this->surfaceProps(),
-                                                     SkMatrix::I(), flags, &effects);
-
-    ARGBHelper argbFallback{runMatrix, surfaceProps(), fStrikeServer};
-
-    auto perEmpty = [sdfCache] (const SkGlyph& glyph, SkPoint mappedPt) {
-        sdfCache->addGlyph(glyph.getPackedID(), false);
-    };
-
-    auto perSDF = [sdfCache] (const SkGlyph& glyph, SkPoint position) {
-        const bool asPath = false;
-        sdfCache->addGlyph(glyph.getGlyphID(), asPath);
-    };
-
-    auto perPath = [sdfCache] (const SkGlyph& glyph, SkPoint position) {
-        const bool asPath = true;
-        sdfCache->addGlyph(glyph.getGlyphID(), asPath);
-    };
-
-    fPainter.drawGlyphRunAsSDFWithARGBFallback(
-            sdfCache, glyphRun, origin, runPaint, runMatrix, textRatio,
-            std::move(perEmpty), std::move(perSDF), std::move(perPath),
-            std::move(argbFallback));
-
-    return true;
-}
-#endif
-
 SkGlyphRunListPainter::ScopedBuffers::ScopedBuffers(SkGlyphRunListPainter* painter, int size)
         : fPainter{painter} {
     SkASSERT(size >= 0);
@@ -1104,7 +988,7 @@
         fPainter->fMaxRunSize = size;
 
         fPainter->fPositions.reset(size);
-        fPainter->fMasks.reset(size);
+        fPainter->fGlyphPos.reset(size);
     }
 }
 
@@ -1116,7 +1000,7 @@
     if (fPainter->fMaxRunSize > 200) {
         fPainter->fMaxRunSize = 0;
         fPainter->fPositions.reset();
-        fPainter->fMasks.reset();
+        fPainter->fGlyphPos.reset();
         fPainter->fPaths.shrink_to_fit();
         fPainter->fARGBGlyphsIDs.shrink_to_fit();
         fPainter->fARGBPositions.shrink_to_fit();
diff --git a/src/core/SkGlyphRunPainter.h b/src/core/SkGlyphRunPainter.h
index 24e4a6a..409f40a 100644
--- a/src/core/SkGlyphRunPainter.h
+++ b/src/core/SkGlyphRunPainter.h
@@ -15,18 +15,12 @@
 #include "SkTextBlobPriv.h"
 
 #if SK_SUPPORT_GPU
+#include "text/GrTextContext.h"
 class GrColorSpaceInfo;
 class GrRenderTargetContext;
 #endif
 
-class SkStrikeInterface {
-public:
-    virtual ~SkStrikeInterface() = default;
-    virtual SkVector rounding() const = 0;
-    virtual const SkGlyph& getGlyphMetrics(SkGlyphID glyphID, SkPoint position) = 0;
-    virtual bool hasImage(const SkGlyph& glyph) = 0;
-    virtual bool hasPath(const SkGlyph& glyph) = 0;
-};
+class SkGlyphRunPainterInterface;
 
 class SkStrikeCommon {
 public:
@@ -38,36 +32,28 @@
     // An atlas consists of plots, and plots hold glyphs. The minimum a plot can be is 256x256.
     // This means that the maximum size a glyph can be is 256x256.
     static constexpr uint16_t kSkSideTooBigForAtlas = 256;
-
-    static bool GlyphTooBigForAtlas(const SkGlyph& glyph);
 };
 
 class SkGlyphRunListPainter {
 public:
     // Constructor for SkBitmpapDevice.
-    SkGlyphRunListPainter(
-            const SkSurfaceProps& props, SkColorType colorType, SkScalerContextFlags flags);
+    SkGlyphRunListPainter(const SkSurfaceProps& props,
+                          SkColorType colorType,
+                          SkColorSpace* cs,
+                          SkStrikeCacheInterface* strikeCache);
 
 #if SK_SUPPORT_GPU
+    // The following two ctors are used exclusively by the GPU, and will always use the global
+    // strike cache.
     SkGlyphRunListPainter(const SkSurfaceProps&, const GrColorSpaceInfo&);
     explicit SkGlyphRunListPainter(const GrRenderTargetContext& renderTargetContext);
-#endif
-
-    struct PathAndPos {
-        const SkPath* path;
-        SkPoint position;
-    };
-
-    struct GlyphAndPos {
-        const SkGlyph* glyph;
-        SkPoint position;
-    };
+#endif  // SK_SUPPORT_GPU
 
     class BitmapDevicePainter {
     public:
         virtual ~BitmapDevicePainter() = default;
 
-        virtual void paintPaths(SkSpan<const PathAndPos> pathsAndPositions,
+        virtual void paintPaths(SkSpan<const SkPathPos> pathsAndPositions,
                                 SkScalar scale,
                                 const SkPaint& paint) const = 0;
 
@@ -78,45 +64,24 @@
             const SkGlyphRunList& glyphRunList, const SkMatrix& deviceMatrix,
             const BitmapDevicePainter* bitmapDevice);
 
-    template <typename EmptiesT, typename MasksT, typename PathsT>
-    void drawGlyphRunAsBMPWithPathFallback(
-            SkStrikeInterface* cache, const SkGlyphRun& glyphRun,
-            SkPoint origin, const SkMatrix& deviceMatrix,
-            EmptiesT&& processEmpties, MasksT&& processMasks, PathsT&& processPaths);
-
-    enum NeedsTransform : bool { kTransformDone = false, kDoTransform = true };
-
-    using ARGBFallback =
-    std::function<void(const SkPaint& fallbackPaint, // The run paint maybe with a new text size
-                       const SkFont& fallbackFont,
-                       SkSpan<const SkGlyphID> fallbackGlyphIDs, // Colored glyphs
-                       SkSpan<const SkPoint> fallbackPositions,  // Positions of above glyphs
-                       SkScalar fallbackTextScale,               // Scale factor for glyph
-                       const SkMatrix& glyphCacheMatrix,         // Matrix of glyph cache
-                       NeedsTransform handleTransformLater)>;    // Positions / glyph transformed
-
-    // Draw glyphs as paths with fallback to scaled ARGB glyphs if color is needed.
-    // PerPath - perPath(const SkGlyph&, SkPoint position)
-    // FallbackARGB - fallbackARGB(SkSpan<const SkGlyphID>, SkSpan<const SkPoint>)
-    // For each glyph that is not ARGB call perPath. If the glyph is ARGB then store the glyphID
-    // and the position in fallback vectors. After all the glyphs are processed, pass the
-    // fallback glyphIDs and positions to fallbackARGB.
-    template <typename PerEmptyT, typename PerPath>
-    void drawGlyphRunAsPathWithARGBFallback(
-            SkStrikeInterface* cache, const SkGlyphRun& glyphRun,
-            SkPoint origin, const SkPaint& paint, const SkMatrix& viewMatrix, SkScalar textScale,
-            PerEmptyT&& perEmpty, PerPath&& perPath, ARGBFallback&& fallbackARGB);
-
-    template <typename PerEmptyT, typename PerSDFT, typename PerPathT>
-    void drawGlyphRunAsSDFWithARGBFallback(
-            SkStrikeInterface* cache, const SkGlyphRun& glyphRun,
-            SkPoint origin, const SkPaint& runPaint, const SkMatrix& viewMatrix, SkScalar textRatio,
-            PerEmptyT&& perEmpty, PerSDFT&& perSDF, PerPathT&& perPath, ARGBFallback&& perFallback);
+#if SK_SUPPORT_GPU
+    // A nullptr for process means that the calls to the cache will be performed, but none of the
+    // callbacks will be called.
+    void processGlyphRunList(const SkGlyphRunList& glyphRunList,
+                             const SkMatrix& viewMatrix,
+                             const SkSurfaceProps& props,
+                             bool contextSupportsDistanceFieldText,
+                             const GrTextContext::Options& options,
+                             SkGlyphRunPainterInterface* process);
+#endif  // SK_SUPPORT_GPU
 
     // TODO: Make this the canonical check for Skia.
     static bool ShouldDrawAsPath(const SkPaint& paint, const SkFont& font, const SkMatrix& matrix);
 
 private:
+    SkGlyphRunListPainter(const SkSurfaceProps& props, SkColorType colorType,
+                          SkScalerContextFlags flags, SkStrikeCacheInterface* strikeCache);
+
     struct ScopedBuffers {
         ScopedBuffers(SkGlyphRunListPainter* painter, int size);
         ~ScopedBuffers();
@@ -128,9 +93,18 @@
     // TODO: Remove once I can hoist ensureBuffers above the list for loop in all cases.
     ScopedBuffers SK_WARN_UNUSED_RESULT ensureBuffers(const SkGlyphRun& glyphRun);
 
-    void processARGBFallback(
-            SkScalar maxGlyphDimension, const SkPaint& fallbackPaint, const SkFont& fallbackFont,
-            const SkMatrix& viewMatrix, SkScalar textScale, ARGBFallback argbFallback);
+    /**
+     *  @param fARGBPositions in source space
+     *  @param fARGBGlyphsIDs the glyphs to process
+     *  @param fGlyphPos used as scratch space
+     *  @param maxSourceGlyphDimension the longest dimension of any glyph as if all fARGBGlyphsIDs
+     *                                 were drawn in source space (as if viewMatrix were identity)
+     */
+    void processARGBFallback(SkScalar maxSourceGlyphDimension,
+                             const SkPaint& runPaint,
+                             const SkFont& runFont,
+                             const SkMatrix& viewMatrix,
+                             SkGlyphRunPainterInterface* process);
 
     // The props as on the actual device.
     const SkSurfaceProps fDeviceProps;
@@ -139,15 +113,61 @@
     const SkColorType fColorType;
     const SkScalerContextFlags fScalerContextFlags;
 
+    SkStrikeCacheInterface* const fStrikeCache;
+
     int fMaxRunSize{0};
     SkAutoTMalloc<SkPoint> fPositions;
-    SkAutoTMalloc<GlyphAndPos> fMasks;
+    SkAutoTMalloc<SkGlyphPos> fGlyphPos;
 
-    std::vector<GlyphAndPos> fPaths;
+    std::vector<SkGlyphPos> fPaths;
 
     // Vectors for tracking ARGB fallback information.
     std::vector<SkGlyphID> fARGBGlyphsIDs;
     std::vector<SkPoint>   fARGBPositions;
 };
 
+// SkGlyphRunPainterInterface are all the ways that Ganesh generates glyphs. The first
+// distinction is between Device and Source.
+// * Device - the data in the cache is scaled to the device. There is no transformation from the
+//   cache to the screen.
+// * Source - the data in the cache needs to be scaled from the cache to source space using the
+//   factor cacheToSourceScale. When drawn the system must combine cacheToSourceScale and the
+//   deviceView matrix to transform the cache data onto the screen. This allows zooming and
+//   simple animation to reuse the same glyph data by just changing the transform.
+//
+// In addition to transformation type above, Masks, Paths, SDFT, and Fallback (or really the
+// rendering method of last resort) are the different
+// formats of data used from the cache.
+class SkGlyphRunPainterInterface {
+public:
+    virtual ~SkGlyphRunPainterInterface() = default;
+
+    virtual void startRun(const SkGlyphRun& glyphRun, bool useSDFT) = 0;
+
+    virtual void processDeviceMasks(SkSpan<const SkGlyphPos> masks,
+                                    SkStrikeInterface* strike) = 0;
+
+    virtual void processSourcePaths(SkSpan<const SkGlyphPos> paths,
+                                    SkStrikeInterface* strike, SkScalar cacheToSourceScale) = 0;
+
+    virtual void processDevicePaths(SkSpan<const SkGlyphPos> paths) = 0;
+
+    virtual void processSourceSDFT(SkSpan<const SkGlyphPos> masks,
+                                   SkStrikeInterface* strike,
+                                   const SkFont& runFont,
+                                   SkScalar cacheToSourceScale,
+                                   SkScalar minScale,
+                                   SkScalar maxScale,
+                                   bool hasWCoord) = 0;
+
+    virtual void processSourceFallback(SkSpan<const SkGlyphPos> masks,
+                                       SkStrikeInterface* strike,
+                                       SkScalar cacheToSourceScale,
+                                       bool hasW) = 0;
+
+    virtual void processDeviceFallback(SkSpan<const SkGlyphPos> masks,
+                                       SkStrikeInterface* strike) = 0;
+
+};
+
 #endif  // SkGlyphRunPainter_DEFINED
diff --git a/src/core/SkGpuBlurUtils.cpp b/src/core/SkGpuBlurUtils.cpp
index 7c79d72..a1b5b20 100644
--- a/src/core/SkGpuBlurUtils.cpp
+++ b/src/core/SkGpuBlurUtils.cpp
@@ -11,8 +11,9 @@
 
 #if SK_SUPPORT_GPU
 #include "GrCaps.h"
-#include "GrContext.h"
 #include "GrFixedClip.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrRenderTargetContextPriv.h"
 #include "effects/GrGaussianConvolutionFragmentProcessor.h"
@@ -98,12 +99,13 @@
              kRGB_888_GrPixelConfig == config || kRGBA_4444_GrPixelConfig == config ||
              kRGB_565_GrPixelConfig == config || kSRGBA_8888_GrPixelConfig == config ||
              kSBGRA_8888_GrPixelConfig == config || kRGBA_half_GrPixelConfig == config ||
-             kAlpha_8_GrPixelConfig == config || kRGBA_1010102_GrPixelConfig == config);
+             kAlpha_8_GrPixelConfig == config || kRGBA_1010102_GrPixelConfig == config ||
+             kRGBA_half_Clamped_GrPixelConfig == config);
 
     return config;
 }
 
-static sk_sp<GrRenderTargetContext> convolve_gaussian_2d(GrContext* context,
+static sk_sp<GrRenderTargetContext> convolve_gaussian_2d(GrRecordingContext* context,
                                                          sk_sp<GrTextureProxy> proxy,
                                                          const SkIRect& srcBounds,
                                                          const SkIPoint& srcOffset,
@@ -150,7 +152,7 @@
     return renderTargetContext;
 }
 
-static sk_sp<GrRenderTargetContext> convolve_gaussian(GrContext* context,
+static sk_sp<GrRenderTargetContext> convolve_gaussian(GrRecordingContext* context,
                                                       sk_sp<GrTextureProxy> proxy,
                                                       const SkIRect& srcRect,
                                                       const SkIPoint& srcOffset,
@@ -259,7 +261,7 @@
     return dstRenderTargetContext;
 }
 
-static sk_sp<GrTextureProxy> decimate(GrContext* context,
+static sk_sp<GrTextureProxy> decimate(GrRecordingContext* context,
                                       sk_sp<GrTextureProxy> src,
                                       SkIPoint* srcOffset,
                                       SkIRect* contentRect,
@@ -317,8 +319,8 @@
                                                                 : mode;
 
             SkRect domain = SkRect::Make(*contentRect);
-            domain.inset((i < scaleFactorX) ? SK_ScalarHalf : 0.0f,
-                         (i < scaleFactorY) ? SK_ScalarHalf : 0.0f);
+            domain.inset((i < scaleFactorX) ? SK_ScalarHalf + SK_ScalarNearlyZero : 0.0f,
+                         (i < scaleFactorY) ? SK_ScalarHalf + SK_ScalarNearlyZero : 0.0f);
             auto fp = GrTextureDomainEffect::Make(std::move(src),
                                                   SkMatrix::I(),
                                                   domain,
@@ -373,7 +375,7 @@
 }
 
 // Expand the contents of 'srcRenderTargetContext' to fit in 'dstII'.
-static sk_sp<GrRenderTargetContext> reexpand(GrContext* context,
+static sk_sp<GrRenderTargetContext> reexpand(GrRecordingContext* context,
                                              sk_sp<GrRenderTargetContext> srcRenderTargetContext,
                                              const SkIRect& localSrcBounds,
                                              int scaleFactorX, int scaleFactorY,
@@ -449,7 +451,7 @@
 
 namespace SkGpuBlurUtils {
 
-sk_sp<GrRenderTargetContext> GaussianBlur(GrContext* context,
+sk_sp<GrRenderTargetContext> GaussianBlur(GrRecordingContext* context,
                                           sk_sp<GrTextureProxy> srcProxy,
                                           sk_sp<SkColorSpace> colorSpace,
                                           const SkIRect& dstBounds,
diff --git a/src/core/SkGpuBlurUtils.h b/src/core/SkGpuBlurUtils.h
index e06dc8b..cfee28e 100644
--- a/src/core/SkGpuBlurUtils.h
+++ b/src/core/SkGpuBlurUtils.h
@@ -36,7 +36,7 @@
     * @return                The renderTargetContext containing the blurred result.
     */
     sk_sp<GrRenderTargetContext> GaussianBlur(
-            GrContext* context,
+            GrRecordingContext* context,
             sk_sp<GrTextureProxy> src,
             sk_sp<SkColorSpace> colorSpace,
             const SkIRect& dstBounds,
diff --git a/src/core/SkGraphics.cpp b/src/core/SkGraphics.cpp
index d705f27..b85e236 100644
--- a/src/core/SkGraphics.cpp
+++ b/src/core/SkGraphics.cpp
@@ -30,18 +30,6 @@
 
 #include <stdlib.h>
 
-void SkGraphics::GetVersion(int32_t* major, int32_t* minor, int32_t* patch) {
-    if (major) {
-        *major = SKIA_VERSION_MAJOR;
-    }
-    if (minor) {
-        *minor = SKIA_VERSION_MINOR;
-    }
-    if (patch) {
-        *patch = SKIA_VERSION_PATCH;
-    }
-}
-
 void SkGraphics::Init() {
     // SkGraphics::Init() must be thread-safe and idempotent.
     SkCpu::CacheRuntimeFeatures();
diff --git a/src/core/SkICC.cpp b/src/core/SkICC.cpp
index 31d96bd..4ec70f2 100644
--- a/src/core/SkICC.cpp
+++ b/src/core/SkICC.cpp
@@ -280,8 +280,7 @@
         md5.write(&toXYZD50, sizeof(toXYZD50));
         static_assert(sizeof(fn) == sizeof(float) * 7, "packed");
         md5.write(&fn, sizeof(fn));
-        SkMD5::Digest digest;
-        md5.finish(digest);
+        SkMD5::Digest digest = md5.finish();
         char* ptr = dst + sizeof(kDescriptionTagBodyPrefix);
         for (unsigned i = 0; i < sizeof(SkMD5::Digest); ++i) {
             uint8_t byte = digest.data[i];
diff --git a/src/core/SkImageFilter.cpp b/src/core/SkImageFilter.cpp
index d6418f9..01767c8 100644
--- a/src/core/SkImageFilter.cpp
+++ b/src/core/SkImageFilter.cpp
@@ -22,7 +22,10 @@
 #if SK_SUPPORT_GPU
 #include "GrColorSpaceXform.h"
 #include "GrContext.h"
+#include "GrContextPriv.h"
 #include "GrFixedClip.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrTextureProxy.h"
 #include "SkGr.h"
@@ -181,7 +184,7 @@
     if (src->isTextureBacked() && result && !result->isTextureBacked()) {
         // Keep the result on the GPU - this is still required for some
         // image filters that don't support GPU in all cases
-        GrContext* context = src->getContext();
+        auto context = src->getContext();
         result = result->makeTextureImage(context);
     }
 #endif
@@ -238,7 +241,7 @@
 }
 
 #if SK_SUPPORT_GPU
-sk_sp<SkSpecialImage> SkImageFilter::DrawWithFP(GrContext* context,
+sk_sp<SkSpecialImage> SkImageFilter::DrawWithFP(GrRecordingContext* context,
                                                 std::unique_ptr<GrFragmentProcessor> fp,
                                                 const SkIRect& bounds,
                                                 const OutputProperties& outputProperties) {
diff --git a/src/core/SkImageGenerator.cpp b/src/core/SkImageGenerator.cpp
index 8b17911..611f7a4 100644
--- a/src/core/SkImageGenerator.cpp
+++ b/src/core/SkImageGenerator.cpp
@@ -61,7 +61,8 @@
 #if SK_SUPPORT_GPU
 #include "GrTextureProxy.h"
 
-sk_sp<GrTextureProxy> SkImageGenerator::generateTexture(GrContext* ctx, const SkImageInfo& info,
+sk_sp<GrTextureProxy> SkImageGenerator::generateTexture(GrRecordingContext* ctx,
+                                                        const SkImageInfo& info,
                                                         const SkIPoint& origin,
                                                         bool willNeedMipMaps) {
     SkIRect srcRect = SkIRect::MakeXYWH(origin.x(), origin.y(), info.width(), info.height());
@@ -71,7 +72,8 @@
     return this->onGenerateTexture(ctx, info, origin, willNeedMipMaps);
 }
 
-sk_sp<GrTextureProxy> SkImageGenerator::onGenerateTexture(GrContext*, const SkImageInfo&,
+sk_sp<GrTextureProxy> SkImageGenerator::onGenerateTexture(GrRecordingContext*,
+                                                          const SkImageInfo&,
                                                           const SkIPoint&,
                                                           bool willNeedMipMaps) {
     return nullptr;
diff --git a/src/core/SkImageInfo.cpp b/src/core/SkImageInfo.cpp
index 0a98c28..921c78a 100644
--- a/src/core/SkImageInfo.cpp
+++ b/src/core/SkImageInfo.cpp
@@ -22,6 +22,7 @@
         case kRGBA_1010102_SkColorType: return 4;
         case kRGB_101010x_SkColorType:  return 4;
         case kGray_8_SkColorType:       return 1;
+        case kRGBA_F16Norm_SkColorType: return 8;
         case kRGBA_F16_SkColorType:     return 8;
         case kRGBA_F32_SkColorType:     return 16;
     }
@@ -74,6 +75,7 @@
         case kRGBA_8888_SkColorType:
         case kBGRA_8888_SkColorType:
         case kRGBA_1010102_SkColorType:
+        case kRGBA_F16Norm_SkColorType:
         case kRGBA_F16_SkColorType:
         case kRGBA_F32_SkColorType:
             if (kUnknown_SkAlphaType == alphaType) {
diff --git a/src/core/SkImagePriv.h b/src/core/SkImagePriv.h
index 1fd7675..6e82b73 100644
--- a/src/core/SkImagePriv.h
+++ b/src/core/SkImagePriv.h
@@ -10,6 +10,7 @@
 
 #include "SkImage.h"
 #include "SkSurface.h"
+#include "SkTileMode.h"
 
 enum SkCopyPixelsMode {
     kIfMutable_SkCopyPixelsMode,  //!< only copy src pixels if they are marked mutable
@@ -22,9 +23,17 @@
 
 // If alloc is non-nullptr, it will be used to allocate the returned SkShader, and MUST outlive
 // the SkShader.
-sk_sp<SkShader> SkMakeBitmapShader(const SkBitmap& src, SkShader::TileMode, SkShader::TileMode,
+sk_sp<SkShader> SkMakeBitmapShader(const SkBitmap& src, SkTileMode, SkTileMode,
                                    const SkMatrix* localMatrix, SkCopyPixelsMode);
 
+// Convenience function to return a shader that implements the shader+image behavior defined for
+// drawImage/Bitmap where the paint's shader is ignored when the bitmap is a color image, but
+// properly compose them together when it is an alpha image. This allows the returned paint to
+// be assigned to a paint clone without discarding the original behavior.
+sk_sp<SkShader> SkMakeBitmapShaderForPaint(const SkPaint& paint, const SkBitmap& src,
+                                           SkTileMode, SkTileMode,
+                                           const SkMatrix* localMatrix, SkCopyPixelsMode);
+
 /**
  *  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/SkLiteDL.cpp b/src/core/SkLiteDL.cpp
index d61f52c..7083a7a 100644
--- a/src/core/SkLiteDL.cpp
+++ b/src/core/SkLiteDL.cpp
@@ -52,12 +52,13 @@
     M(Flush) M(Save) M(Restore) M(SaveLayer) M(SaveBehind)                             \
     M(Concat) M(SetMatrix) M(Translate)                                                \
     M(ClipPath) M(ClipRect) M(ClipRRect) M(ClipRegion)                                 \
-    M(DrawPaint) M(DrawPath) M(DrawRect) M(DrawEdgeAARect)                             \
+    M(DrawPaint) M(DrawPath) M(DrawRect)                                               \
     M(DrawRegion) M(DrawOval) M(DrawArc)                                               \
     M(DrawRRect) M(DrawDRRect) M(DrawAnnotation) M(DrawDrawable) M(DrawPicture)        \
-    M(DrawImage) M(DrawImageNine) M(DrawImageRect) M(DrawImageLattice) M(DrawImageSet) \
+    M(DrawImage) M(DrawImageNine) M(DrawImageRect) M(DrawImageLattice)                 \
     M(DrawTextBlob)                                                                    \
-    M(DrawPatch) M(DrawPoints) M(DrawVertices) M(DrawAtlas) M(DrawShadowRec)
+    M(DrawPatch) M(DrawPoints) M(DrawVertices) M(DrawAtlas) M(DrawShadowRec)           \
+    M(DrawEdgeAAQuad) M(DrawEdgeAAImageSet)
 
 #define M(T) T,
     enum class Type : uint8_t { TYPES(M) };
@@ -190,19 +191,6 @@
         SkPaint paint;
         void draw(SkCanvas* c, const SkMatrix&) const { c->drawRect(rect, paint); }
     };
-    struct DrawEdgeAARect final : Op {
-        static const auto kType = Type::DrawEdgeAARect;
-        DrawEdgeAARect(const SkRect& rect, SkCanvas::QuadAAFlags aa, SkColor color,
-                       SkBlendMode mode)
-            : rect(rect), aa(aa), color(color), mode(mode) {}
-        SkRect rect;
-        SkCanvas::QuadAAFlags aa;
-        SkColor color;
-        SkBlendMode mode;
-        void draw(SkCanvas* c, const SkMatrix&) const {
-            c->experimental_DrawEdgeAARectV1(rect, aa, color, mode);
-        }
-    };
     struct DrawRegion final : Op {
         static const auto kType = Type::DrawRegion;
         DrawRegion(const SkRegion& region, const SkPaint& paint) : region(region), paint(paint) {}
@@ -314,7 +302,7 @@
         DrawImageRect(sk_sp<const SkImage>&& image, const SkRect* src, const SkRect& dst,
                       const SkPaint* paint, SkCanvas::SrcRectConstraint constraint)
             : image(std::move(image)), dst(dst), constraint(constraint) {
-            this->src = src ? *src : SkRect::MakeIWH(image->width(), image->height());
+            this->src = src ? *src : SkRect::MakeIWH(this->image->width(), this->image->height());
             if (paint) { this->paint = *paint; }
         }
         sk_sp<const SkImage> image;
@@ -349,21 +337,6 @@
                                 &paint);
         }
     };
-    struct DrawImageSet final : Op {
-        static const auto kType = Type::DrawImageSet;
-        DrawImageSet(const SkCanvas::ImageSetEntry set[], int count, SkFilterQuality quality,
-                     SkBlendMode xfermode)
-                : count(count), quality(quality), xfermode(xfermode), set(count) {
-            std::copy_n(set, count, this->set.get());
-        }
-        int                                   count;
-        SkFilterQuality                       quality;
-        SkBlendMode                           xfermode;
-        SkAutoTArray<SkCanvas::ImageSetEntry> set;
-        void draw(SkCanvas* c, const SkMatrix&) const {
-            c->experimental_DrawImageSetV1(set.get(), count, quality, xfermode);
-        }
-    };
     struct DrawTextBlob final : Op {
         static const auto kType = Type::DrawTextBlob;
         DrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint)
@@ -459,6 +432,48 @@
             c->private_draw_shadow_rec(fPath, fRec);
         }
     };
+
+    struct DrawEdgeAAQuad final : Op {
+        static const auto kType = Type::DrawEdgeAAQuad;
+        DrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
+                       SkCanvas::QuadAAFlags aa, SkColor color, SkBlendMode mode)
+            : rect(rect), hasClip(clip != nullptr), aa(aa), color(color), mode(mode) {
+                if (clip) {
+                    for (int i = 0; i < 4; ++i) {
+                        this->clip[i] = clip[i];
+                    }
+                }
+            }
+        SkRect rect;
+        SkPoint clip[4];
+        bool hasClip;
+        SkCanvas::QuadAAFlags aa;
+        SkColor color;
+        SkBlendMode mode;
+        void draw(SkCanvas* c, const SkMatrix&) const {
+            c->experimental_DrawEdgeAAQuad(rect, hasClip ? clip : nullptr, aa, color, mode);
+        }
+    };
+    struct DrawEdgeAAImageSet final : Op {
+        static const auto kType = Type::DrawEdgeAAImageSet;
+        DrawEdgeAAImageSet(const SkCanvas::ImageSetEntry set[], int count, int dstClipCount,
+                     const SkPaint* paint, SkCanvas::SrcRectConstraint constraint)
+                : count(count), set(count), dstClipCount(dstClipCount), constraint(constraint) {
+            std::copy_n(set, count, this->set.get());
+            if (paint) { this->paint = *paint; }
+        }
+        int count;
+        SkAutoTArray<SkCanvas::ImageSetEntry> set;
+        int dstClipCount;
+        SkPaint paint;
+        SkCanvas::SrcRectConstraint constraint;
+        void draw(SkCanvas* c, const SkMatrix&) const {
+            auto dstClips = pod<SkPoint>(this);
+            auto preViewMatrices = pod<SkMatrix>(this, dstClipCount * sizeof(SkPoint));
+            c->experimental_DrawEdgeAAImageSet(
+                    set.get(), count, dstClips, preViewMatrices, &paint, constraint);
+        }
+    };
 }
 
 template <typename T, typename... Args>
@@ -533,10 +548,6 @@
 void SkLiteDL::drawRect(const SkRect& rect, const SkPaint& paint) {
     this->push<DrawRect>(0, rect, paint);
 }
-void SkLiteDL::drawEdgeAARect(const SkRect& rect, SkCanvas::QuadAAFlags aa, SkColor color,
-                              SkBlendMode mode) {
-    this->push<DrawEdgeAARect>(0, rect, aa, color, mode);
-}
 void SkLiteDL::drawRegion(const SkRegion& region, const SkPaint& paint) {
     this->push<DrawRegion>(0, region, paint);
 }
@@ -592,11 +603,6 @@
                 lattice.fRectTypes, fs);
 }
 
-void SkLiteDL::drawImageSet(const SkCanvas::ImageSetEntry set[], int count,
-                            SkFilterQuality filterQuality, SkBlendMode mode) {
-    this->push<DrawImageSet>(0, set, count, filterQuality, mode);
-}
-
 void SkLiteDL::drawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint) {
     this->push<DrawTextBlob>(0, blob, x,y, paint);
 }
@@ -636,6 +642,24 @@
     this->push<DrawShadowRec>(0, path, rec);
 }
 
+void SkLiteDL::drawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
+                              SkCanvas::QuadAAFlags aa, SkColor color, SkBlendMode mode) {
+    this->push<DrawEdgeAAQuad>(0, rect, clip, aa, color, mode);
+}
+
+void SkLiteDL::drawEdgeAAImageSet(const SkCanvas::ImageSetEntry set[], int count,
+                            const SkPoint dstClips[], const SkMatrix preViewMatrices[],
+                            const SkPaint* paint, SkCanvas::SrcRectConstraint constraint) {
+    int totalDstClipCount, totalMatrixCount;
+    SkCanvasPriv::GetDstClipAndMatrixCounts(set, count, &totalDstClipCount, &totalMatrixCount);
+
+    size_t bytes = totalDstClipCount * sizeof(SkPoint) + totalMatrixCount * sizeof(SkMatrix);
+    void* pod = this->push<DrawEdgeAAImageSet>(
+            bytes, set, count, totalDstClipCount, paint, constraint);
+    copy_v(pod, dstClips, totalDstClipCount,
+           preViewMatrices, totalMatrixCount);
+}
+
 typedef void(*draw_fn)(const void*,  SkCanvas*, const SkMatrix&);
 typedef void(*void_fn)(const void*);
 
diff --git a/src/core/SkLiteDL.h b/src/core/SkLiteDL.h
index a6041bc..ea9a5fa 100644
--- a/src/core/SkLiteDL.h
+++ b/src/core/SkLiteDL.h
@@ -46,7 +46,6 @@
     void drawPaint (const SkPaint&);
     void drawPath  (const SkPath&, const SkPaint&);
     void drawRect  (const SkRect&, const SkPaint&);
-    void drawEdgeAARect(const SkRect&, SkCanvas::QuadAAFlags, SkColor, SkBlendMode);
     void drawRegion(const SkRegion&, const SkPaint&);
     void drawOval  (const SkRect&, const SkPaint&);
     void drawArc   (const SkRect&, SkScalar, SkScalar, bool, const SkPaint&);
@@ -65,7 +64,6 @@
                        SkCanvas::SrcRectConstraint);
     void drawImageLattice(sk_sp<const SkImage>, const SkCanvas::Lattice&,
                           const SkRect&, const SkPaint*);
-    void drawImageSet(const SkCanvas::ImageSetEntry[], int count, SkFilterQuality, SkBlendMode);
 
     void drawPatch(const SkPoint[12], const SkColor[4], const SkPoint[4],
                    SkBlendMode, const SkPaint&);
@@ -76,6 +74,12 @@
                    SkBlendMode, const SkRect*, const SkPaint*);
     void drawShadowRec(const SkPath&, const SkDrawShadowRec&);
 
+    void drawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], SkCanvas::QuadAAFlags aaFlags,
+                        SkColor color, SkBlendMode mode);
+    void drawEdgeAAImageSet(const SkCanvas::ImageSetEntry[], int count, const SkPoint dstClips[],
+                            const SkMatrix preViewMatrices[], const SkPaint* paint,
+                            SkCanvas::SrcRectConstraint constraint);
+
 private:
     template <typename T, typename... Args>
     void* push(size_t, Args&&...);
diff --git a/src/core/SkLiteRecorder.cpp b/src/core/SkLiteRecorder.cpp
index 6dda8d6..f95dc34 100644
--- a/src/core/SkLiteRecorder.cpp
+++ b/src/core/SkLiteRecorder.cpp
@@ -66,10 +66,6 @@
 void SkLiteRecorder::onDrawRect(const SkRect& rect, const SkPaint& paint) {
     fDL->drawRect(rect, paint);
 }
-void SkLiteRecorder::onDrawEdgeAARect(const SkRect& rect, SkCanvas::QuadAAFlags aa, SkColor color,
-                                      SkBlendMode mode) {
-    fDL->drawEdgeAARect(rect, aa, color, mode);
-}
 void SkLiteRecorder::onDrawRegion(const SkRegion& region, const SkPaint& paint) {
     fDL->drawRegion(region, paint);
 }
@@ -147,11 +143,6 @@
     fDL->drawImageLattice(sk_ref_sp(img), lattice, dst, paint);
 }
 
-void SkLiteRecorder::onDrawImageSet(const ImageSetEntry set[], int count,
-                                    SkFilterQuality filterQuality, SkBlendMode mode) {
-    fDL->drawImageSet(set, count, filterQuality, mode);
-}
-
 void SkLiteRecorder::onDrawPatch(const SkPoint cubics[12],
                                  const SkColor colors[4], const SkPoint texCoords[4],
                                  SkBlendMode bmode, const SkPaint& paint) {
@@ -180,3 +171,14 @@
 void SkLiteRecorder::onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) {
     fDL->drawShadowRec(path, rec);
 }
+
+void SkLiteRecorder::onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
+                                      QuadAAFlags aaFlags, SkColor color, SkBlendMode mode) {
+  fDL->drawEdgeAAQuad(rect, clip, aaFlags, color, mode);
+}
+void SkLiteRecorder::onDrawEdgeAAImageSet(const ImageSetEntry set[], int count,
+                                          const SkPoint dstClips[],
+                                          const SkMatrix preViewMatrices[], const SkPaint* paint,
+                                          SkCanvas::SrcRectConstraint constraint) {
+  fDL->drawEdgeAAImageSet(set, count, dstClips, preViewMatrices, paint, constraint);
+}
diff --git a/src/core/SkLiteRecorder.h b/src/core/SkLiteRecorder.h
index 81eedb3..7b54f89 100644
--- a/src/core/SkLiteRecorder.h
+++ b/src/core/SkLiteRecorder.h
@@ -39,7 +39,6 @@
     void onDrawPaint (const SkPaint&) override;
     void onDrawPath  (const SkPath&, const SkPaint&) override;
     void onDrawRect  (const SkRect&, const SkPaint&) override;
-    void onDrawEdgeAARect(const SkRect&, SkCanvas::QuadAAFlags, SkColor, SkBlendMode) override;
     void onDrawRegion(const SkRegion&, const SkPaint&) override;
     void onDrawOval  (const SkRect&, const SkPaint&) override;
     void onDrawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&) override;
@@ -64,7 +63,6 @@
     void onDrawImageNine(const SkImage*, const SkIRect&, const SkRect&, const SkPaint*) override;
     void onDrawImageRect(const SkImage*, const SkRect*, const SkRect&, const SkPaint*,
                          SrcRectConstraint) override;
-    void onDrawImageSet(const ImageSetEntry[], int count, SkFilterQuality, SkBlendMode) override;
 
     void onDrawPatch(const SkPoint[12], const SkColor[4],
                      const SkPoint[4], SkBlendMode, const SkPaint&) override;
@@ -75,6 +73,11 @@
                      int, SkBlendMode, const SkRect*, const SkPaint*) override;
     void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override;
 
+    void onDrawEdgeAAQuad(const SkRect&, const SkPoint[4], QuadAAFlags, SkColor,
+                          SkBlendMode) override;
+    void onDrawEdgeAAImageSet(const ImageSetEntry[], int count, const SkPoint[], const SkMatrix[],
+                              const SkPaint*, SrcRectConstraint) override;
+
 private:
     typedef SkCanvasVirtualEnforcer<SkNoDrawCanvas> INHERITED;
 
diff --git a/src/core/SkLocalMatrixImageFilter.cpp b/src/core/SkLocalMatrixImageFilter.cpp
index fb484c9..4271553 100644
--- a/src/core/SkLocalMatrixImageFilter.cpp
+++ b/src/core/SkLocalMatrixImageFilter.cpp
@@ -5,7 +5,6 @@
  * found in the LICENSE file.
  */
 
-#include "SkColorSpaceXformer.h"
 #include "SkImageFilterPriv.h"
 #include "SkLocalMatrixImageFilter.h"
 #include "SkReadBuffer.h"
@@ -56,14 +55,3 @@
                                                  MapDirection dir, const SkIRect* inputRect) const {
     return this->getInput(0)->filterBounds(src, SkMatrix::Concat(ctm, fLocalM), dir, inputRect);
 }
-
-sk_sp<SkImageFilter> SkLocalMatrixImageFilter::onMakeColorSpace(SkColorSpaceXformer* xformer)
-const {
-    SkASSERT(1 == this->countInputs() && this->getInput(0));
-
-    auto input = xformer->apply(this->getInput(0));
-    if (input.get() != this->getInput(0)) {
-        return SkLocalMatrixImageFilter::Make(fLocalM, std::move(input));
-    }
-    return this->refMe();
-}
diff --git a/src/core/SkLocalMatrixImageFilter.h b/src/core/SkLocalMatrixImageFilter.h
index abdadbf..3e3688b 100644
--- a/src/core/SkLocalMatrixImageFilter.h
+++ b/src/core/SkLocalMatrixImageFilter.h
@@ -23,7 +23,6 @@
     void flatten(SkWriteBuffer&) const override;
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
                                         SkIPoint* offset) const override;
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
     SkIRect onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
                            MapDirection, const SkIRect* inputRect) const override;
 
diff --git a/src/core/SkMD5.cpp b/src/core/SkMD5.cpp
index c0c3c31..0a9cb4f 100644
--- a/src/core/SkMD5.cpp
+++ b/src/core/SkMD5.cpp
@@ -66,7 +66,8 @@
     return true;
 }
 
-void SkMD5::finish(Digest& digest) {
+SkMD5::Digest SkMD5::finish() {
+    SkMD5::Digest digest;
     // Get the number of bits before padding.
     uint8_t bits[8];
     encode(bits, this->byteCount << 3);
@@ -92,6 +93,7 @@
     // Clear state.
     memset(this, 0, sizeof(*this));
 #endif
+    return digest;
 }
 
 struct F { uint32_t operator()(uint32_t x, uint32_t y, uint32_t z) {
diff --git a/src/core/SkMD5.h b/src/core/SkMD5.h
index 5ae14e1..ce258be 100644
--- a/src/core/SkMD5.h
+++ b/src/core/SkMD5.h
@@ -31,7 +31,7 @@
     };
 
     /** Computes and returns the digest. */
-    void finish(Digest& digest);
+    Digest finish();
 
 private:
     uint64_t byteCount;  // number of bytes, modulo 2^64
diff --git a/src/core/SkMask.h b/src/core/SkMask.h
index dc644da..258186c 100644
--- a/src/core/SkMask.h
+++ b/src/core/SkMask.h
@@ -40,6 +40,8 @@
     uint32_t    fRowBytes;
     Format      fFormat;
 
+    static bool IsValidFormat(uint8_t format) { return format < kCountMaskFormats; }
+
     /** Returns true if the mask is empty: i.e. it has an empty bounds.
      */
     bool isEmpty() const { return fBounds.isEmpty(); }
diff --git a/src/core/SkMaskFilter.cpp b/src/core/SkMaskFilter.cpp
index 07e00a8..766be2b 100644
--- a/src/core/SkMaskFilter.cpp
+++ b/src/core/SkMaskFilter.cpp
@@ -331,7 +331,7 @@
     return false;
 }
 
-bool SkMaskFilterBase::directFilterMaskGPU(GrContext*,
+bool SkMaskFilterBase::directFilterMaskGPU(GrRecordingContext*,
                                            GrRenderTargetContext*,
                                            GrPaint&&,
                                            const GrClip&,
@@ -340,7 +340,7 @@
     return false;
 }
 
-sk_sp<GrTextureProxy> SkMaskFilterBase::filterMaskGPU(GrContext*,
+sk_sp<GrTextureProxy> SkMaskFilterBase::filterMaskGPU(GrRecordingContext*,
                                                       sk_sp<GrTextureProxy> srcProxy,
                                                       const SkMatrix& ctm,
                                                       const SkIRect& maskRect) const {
diff --git a/src/core/SkMaskFilterBase.h b/src/core/SkMaskFilterBase.h
index d3a257c..b692e56 100644
--- a/src/core/SkMaskFilterBase.h
+++ b/src/core/SkMaskFilterBase.h
@@ -17,10 +17,10 @@
 #include "SkStrokeRec.h"
 
 class GrClip;
-class GrContext;
 struct GrFPArgs;
 class GrFragmentProcessor;
 class GrPaint;
+class GrRecordingContext;
 class GrRenderTarget;
 class GrRenderTargetContext;
 class GrResourceProvider;
@@ -108,7 +108,7 @@
      *  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(GrContext*,
+    virtual bool directFilterMaskGPU(GrRecordingContext*,
                                      GrRenderTargetContext*,
                                      GrPaint&& paint,
                                      const GrClip&,
@@ -122,7 +122,7 @@
      * Implementations are free to get the GrContext from the src texture in order to create
      * additional textures and perform multiple passes.
      */
-    virtual sk_sp<GrTextureProxy> filterMaskGPU(GrContext*,
+    virtual sk_sp<GrTextureProxy> filterMaskGPU(GrRecordingContext*,
                                                 sk_sp<GrTextureProxy> srcProxy,
                                                 const SkMatrix& ctm,
                                                 const SkIRect& maskRect) const;
diff --git a/src/core/SkMath.cpp b/src/core/SkMath.cpp
index 2d2ddac..b904609 100644
--- a/src/core/SkMath.cpp
+++ b/src/core/SkMath.cpp
@@ -68,22 +68,6 @@
     return root;
 }
 
-float SkScalarSinCos(float radians, float* cosValue) {
-    float sinValue = sk_float_sin(radians);
-
-    if (cosValue) {
-        *cosValue = sk_float_cos(radians);
-        if (SkScalarNearlyZero(*cosValue)) {
-            *cosValue = 0;
-        }
-    }
-
-    if (SkScalarNearlyZero(sinValue)) {
-        sinValue = 0;
-    }
-    return sinValue;
-}
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 size_t SkSafeMath::Add(size_t x, size_t y) {
@@ -97,3 +81,13 @@
     size_t prod = tmp.mul(x, y);
     return tmp.ok() ? prod : SIZE_MAX;
 }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool sk_floats_are_unit(const float array[], size_t count) {
+    bool is_unit = true;
+    for (size_t i = 0; i < count; ++i) {
+        is_unit &= (array[i] >= 0) & (array[i] <= 1);
+    }
+    return is_unit;
+}
diff --git a/src/core/SkMatrix.cpp b/src/core/SkMatrix.cpp
index 78d40bb..bb68e2b 100644
--- a/src/core/SkMatrix.cpp
+++ b/src/core/SkMatrix.cpp
@@ -475,15 +475,13 @@
 }
 
 void SkMatrix::setRotate(SkScalar degrees, SkScalar px, SkScalar py) {
-    SkScalar sinV, cosV;
-    sinV = SkScalarSinCos(SkDegreesToRadians(degrees), &cosV);
-    this->setSinCos(sinV, cosV, px, py);
+    SkScalar rad = SkDegreesToRadians(degrees);
+    this->setSinCos(SkScalarSinSnapToZero(rad), SkScalarCosSnapToZero(rad), px, py);
 }
 
 void SkMatrix::setRotate(SkScalar degrees) {
-    SkScalar sinV, cosV;
-    sinV = SkScalarSinCos(SkDegreesToRadians(degrees), &cosV);
-    this->setSinCos(sinV, cosV);
+    SkScalar rad = SkDegreesToRadians(degrees);
+    this->setSinCos(SkScalarSinSnapToZero(rad), SkScalarCosSnapToZero(rad));
 }
 
 void SkMatrix::preRotate(SkScalar degrees, SkScalar px, SkScalar py) {
@@ -796,6 +794,18 @@
     return true;
 }
 
+void SkMatrix::mapPoints(SkPoint dst[], const SkPoint src[], int count) const {
+    SkASSERT((dst && src && count > 0) || 0 == count);
+    // no partial overlap
+    SkASSERT(src == dst || &dst[count] <= &src[0] || &src[count] <= &dst[0]);
+    this->getMapPtsProc()(*this, dst, src, count);
+}
+
+void SkMatrix::mapXY(SkScalar x, SkScalar y, SkPoint* result) const {
+    SkASSERT(result);
+    this->getMapXYProc()(*this, x, y, result);
+}
+
 void SkMatrix::ComputeInv(SkScalar dst[9], const SkScalar src[9], double invDet, bool isPersp) {
     SkASSERT(src != dst);
     SkASSERT(src && dst);
diff --git a/src/core/SkMatrix44.cpp b/src/core/SkMatrix44.cpp
index 07aba07..563ce49 100644
--- a/src/core/SkMatrix44.cpp
+++ b/src/core/SkMatrix44.cpp
@@ -6,8 +6,19 @@
  */
 
 #include "SkMatrix44.h"
+#include <type_traits>
 #include <utility>
 
+// Copying SkMatrix44 byte-wise is performance-critical to Blink. This class is
+// contained in several Transform classes, which are copied multiple times
+// during the rendering life cycle. See crbug.com/938563 for reference.
+#if defined(SK_BUILD_FOR_WIN) || defined(SK_BUILD_FOR_MAC)
+// std::is_trivially_copyable is not supported for some older clang versions,
+// which (at least as of this patch) are in use for Chromecast.
+static_assert(std::is_trivially_copyable<SkMatrix44>::value,
+              "SkMatrix44 must be trivially copyable");
+#endif
+
 static inline bool eq4(const SkMScalar* SK_RESTRICT a,
                       const SkMScalar* SK_RESTRICT b) {
     return (a[0] == b[0]) & (a[1] == b[1]) & (a[2] == b[2]) & (a[3] == b[3]);
@@ -18,7 +29,7 @@
         return true;
     }
 
-    if (this->isTriviallyIdentity() && other.isTriviallyIdentity()) {
+    if (this->isIdentity() && other.isIdentity()) {
         return true;
     }
 
@@ -49,14 +60,13 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-
-int SkMatrix44::computeTypeMask() const {
-    unsigned mask = 0;
-
+void SkMatrix44::recomputeTypeMask() {
     if (0 != perspX() || 0 != perspY() || 0 != perspZ() || 1 != fMat[3][3]) {
-        return kTranslate_Mask | kScale_Mask | kAffine_Mask | kPerspective_Mask;
+        fTypeMask = kTranslate_Mask | kScale_Mask | kAffine_Mask | kPerspective_Mask;
+        return;
     }
 
+    TypeMask mask = kIdentity_Mask;
     if (0 != transX() || 0 != transY() || 0 != transZ()) {
         mask |= kTranslate_Mask;
     }
@@ -69,8 +79,7 @@
         0 != fMat[2][0] || 0 != fMat[1][2] || 0 != fMat[2][1]) {
             mask |= kAffine_Mask;
     }
-
-    return mask;
+    fTypeMask = mask;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -137,7 +146,7 @@
     memcpy(dst, src, 16 * sizeof(float));
 #endif
 
-    this->dirtyTypeMask();
+    this->recomputeTypeMask();
 }
 
 void SkMatrix44::setColMajord(const double src[]) {
@@ -150,7 +159,7 @@
     }
 #endif
 
-    this->dirtyTypeMask();
+    this->recomputeTypeMask();
 }
 
 void SkMatrix44::setRowMajorf(const float src[]) {
@@ -163,7 +172,7 @@
         src += 4;
         dst += 1;
     }
-    this->dirtyTypeMask();
+    this->recomputeTypeMask();
 }
 
 void SkMatrix44::setRowMajord(const double src[]) {
@@ -176,7 +185,7 @@
         src += 4;
         dst += 1;
     }
-    this->dirtyTypeMask();
+    this->recomputeTypeMask();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -213,7 +222,7 @@
     fMat[1][0] = m_01; fMat[1][1] = m_11; fMat[1][2] = m_21; fMat[1][3] = 0;
     fMat[2][0] = m_02; fMat[2][1] = m_12; fMat[2][2] = m_22; fMat[2][3] = 0;
     fMat[3][0] = 0;    fMat[3][1] = 0;    fMat[3][2] = 0;    fMat[3][3] = 1;
-    this->dirtyTypeMask();
+    this->recomputeTypeMask();
 }
 
 void SkMatrix44::set3x3RowMajorf(const float src[]) {
@@ -221,7 +230,7 @@
     fMat[1][0] = src[1]; fMat[1][1] = src[4]; fMat[1][2] = src[7]; fMat[1][3] = 0;
     fMat[2][0] = src[2]; fMat[2][1] = src[5]; fMat[2][2] = src[8]; fMat[2][3] = 0;
     fMat[3][0] = 0;      fMat[3][1] = 0;      fMat[3][2] = 0;      fMat[3][3] = 1;
-    this->dirtyTypeMask();
+    this->recomputeTypeMask();
 }
 
 void SkMatrix44::set3x4RowMajorf(const float src[]) {
@@ -229,9 +238,21 @@
     fMat[0][1] = src[4]; fMat[1][1] = src[5]; fMat[2][1] = src[6];  fMat[3][1] = src[7];
     fMat[0][2] = src[8]; fMat[1][2] = src[9]; fMat[2][2] = src[10]; fMat[3][2] = src[11];
     fMat[0][3] = 0;      fMat[1][3] = 0;      fMat[2][3] = 0;       fMat[3][3] = 1;
-    this->dirtyTypeMask();
+    this->recomputeTypeMask();
 }
 
+void SkMatrix44::set4x4(SkMScalar m_00, SkMScalar m_10, SkMScalar m_20, SkMScalar m_30,
+                SkMScalar m_01, SkMScalar m_11, SkMScalar m_21, SkMScalar m_31,
+                SkMScalar m_02, SkMScalar m_12, SkMScalar m_22, SkMScalar m_32,
+                SkMScalar m_03, SkMScalar m_13, SkMScalar m_23, SkMScalar m_33) {
+    fMat[0][0] = m_00; fMat[0][1] = m_10; fMat[0][2] = m_20; fMat[0][3] = m_30;
+    fMat[1][0] = m_01; fMat[1][1] = m_11; fMat[1][2] = m_21; fMat[1][3] = m_31;
+    fMat[2][0] = m_02; fMat[2][1] = m_12; fMat[2][2] = m_22; fMat[2][3] = m_32;
+    fMat[3][0] = m_03; fMat[3][1] = m_13; fMat[3][2] = m_23; fMat[3][3] = m_33;
+    this->recomputeTypeMask();
+}
+
+
 ///////////////////////////////////////////////////////////////////////////////
 
 void SkMatrix44::setTranslate(SkMScalar dx, SkMScalar dy, SkMScalar dz) {
@@ -255,7 +276,7 @@
     for (int i = 0; i < 4; ++i) {
         fMat[3][i] = fMat[0][i] * dx + fMat[1][i] * dy + fMat[2][i] * dz + fMat[3][i];
     }
-    this->dirtyTypeMask();
+    this->recomputeTypeMask();
 }
 
 void SkMatrix44::postTranslate(SkMScalar dx, SkMScalar dy, SkMScalar dz) {
@@ -273,7 +294,7 @@
         fMat[3][0] += dx;
         fMat[3][1] += dy;
         fMat[3][2] += dz;
-        this->dirtyTypeMask();
+        this->recomputeTypeMask();
     }
 }
 
@@ -305,7 +326,7 @@
         fMat[1][i] *= sy;
         fMat[2][i] *= sz;
     }
-    this->dirtyTypeMask();
+    this->recomputeTypeMask();
 }
 
 void SkMatrix44::postScale(SkMScalar sx, SkMScalar sy, SkMScalar sz) {
@@ -318,7 +339,7 @@
         fMat[i][1] *= sy;
         fMat[i][2] *= sz;
     }
-    this->dirtyTypeMask();
+    this->recomputeTypeMask();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -418,7 +439,7 @@
     if (useStorage) {
         memcpy(fMat, storage, sizeof(storage));
     }
-    this->dirtyTypeMask();
+    this->recomputeTypeMask();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -678,8 +699,6 @@
     inverse->fMat[3][1] = SkDoubleToMScalar(a00 * b09 - a01 * b07 + a02 * b06);
     inverse->fMat[3][2] = SkDoubleToMScalar(a31 * b01 - a30 * b03 - a32 * b00);
     inverse->fMat[3][3] = SkDoubleToMScalar(a20 * b03 - a21 * b01 + a22 * b00);
-    inverse->dirtyTypeMask();
-
     inverse->setTypeMask(this->getType());
     if (!is_matrix_finite(*inverse)) {
         return false;
@@ -693,16 +712,15 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void SkMatrix44::transpose() {
-    using std::swap;
-    swap(fMat[0][1], fMat[1][0]);
-    swap(fMat[0][2], fMat[2][0]);
-    swap(fMat[0][3], fMat[3][0]);
-    swap(fMat[1][2], fMat[2][1]);
-    swap(fMat[1][3], fMat[3][1]);
-    swap(fMat[2][3], fMat[3][2]);
-
-    if (!this->isTriviallyIdentity()) {
-        this->dirtyTypeMask();
+    if (!this->isIdentity()) {
+        using std::swap;
+        swap(fMat[0][1], fMat[1][0]);
+        swap(fMat[0][2], fMat[2][0]);
+        swap(fMat[0][3], fMat[3][0]);
+        swap(fMat[1][2], fMat[2][1]);
+        swap(fMat[1][3], fMat[3][1]);
+        swap(fMat[2][3], fMat[3][2]);
+        this->recomputeTypeMask();
     }
 }
 
@@ -990,7 +1008,7 @@
     if (src.isIdentity()) {
         this->setTypeMask(kIdentity_Mask);
     } else {
-        this->dirtyTypeMask();
+        this->recomputeTypeMask();
     }
     return *this;
 }
diff --git a/src/core/SkMatrixImageFilter.cpp b/src/core/SkMatrixImageFilter.cpp
index 6da4dd3..f565419 100644
--- a/src/core/SkMatrixImageFilter.cpp
+++ b/src/core/SkMatrixImageFilter.cpp
@@ -8,7 +8,6 @@
 #include "SkMatrixImageFilter.h"
 
 #include "SkCanvas.h"
-#include "SkColorSpaceXformer.h"
 #include "SkImageFilterPriv.h"
 #include "SkReadBuffer.h"
 #include "SkSpecialImage.h"
@@ -97,15 +96,6 @@
     return surf->makeImageSnapshot();
 }
 
-sk_sp<SkImageFilter> SkMatrixImageFilter::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    SkASSERT(1 == this->countInputs());
-    auto input = xformer->apply(this->getInput(0));
-    if (input.get() != this->getInput(0)) {
-        return SkMatrixImageFilter::Make(fTransform, fFilterQuality, std::move(input));
-    }
-    return this->refMe();
-}
-
 SkRect SkMatrixImageFilter::computeFastBounds(const SkRect& src) const {
     SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src;
     SkRect dst;
diff --git a/src/core/SkMatrixImageFilter.h b/src/core/SkMatrixImageFilter.h
index 6c336c8..6dabab0 100644
--- a/src/core/SkMatrixImageFilter.h
+++ b/src/core/SkMatrixImageFilter.h
@@ -40,7 +40,6 @@
 
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
                                         SkIPoint* offset) const override;
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
     SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
                                MapDirection, const SkIRect* inputRect) const override;
 
diff --git a/src/core/SkMipMap.cpp b/src/core/SkMipMap.cpp
index 6d05666..84db6bb 100644
--- a/src/core/SkMipMap.cpp
+++ b/src/core/SkMipMap.cpp
@@ -369,6 +369,7 @@
             proc_3_2 = downsample_3_2<ColorTypeFilter_8>;
             proc_3_3 = downsample_3_3<ColorTypeFilter_8>;
             break;
+        case kRGBA_F16Norm_SkColorType:
         case kRGBA_F16_SkColorType:
             proc_1_2 = downsample_1_2<ColorTypeFilter_F16>;
             proc_1_3 = downsample_1_3<ColorTypeFilter_F16>;
diff --git a/src/core/SkModeColorFilter.cpp b/src/core/SkModeColorFilter.cpp
index 8cf1929..9e4855e 100644
--- a/src/core/SkModeColorFilter.cpp
+++ b/src/core/SkModeColorFilter.cpp
@@ -11,7 +11,6 @@
 #include "SkColorFilter.h"
 #include "SkColorData.h"
 #include "SkColorSpacePriv.h"
-#include "SkColorSpaceXformer.h"
 #include "SkColorSpaceXformSteps.h"
 #include "SkModeColorFilter.h"
 #include "SkRandom.h"
@@ -59,38 +58,28 @@
 sk_sp<SkFlattenable> SkModeColorFilter::CreateProc(SkReadBuffer& buffer) {
     SkColor color = buffer.readColor();
     SkBlendMode mode = (SkBlendMode)buffer.readUInt();
-    return SkColorFilter::MakeModeFilter(color, mode);
+    return SkColorFilters::Blend(color, mode);
 }
 
-void SkModeColorFilter::onAppendStages(SkRasterPipeline* p,
-                                       SkColorSpace* dst,
-                                       SkArenaAlloc* scratch,
-                                       bool shaderIsOpaque) const {
-    p->append(SkRasterPipeline::move_src_dst);
+bool SkModeColorFilter::onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const {
+    rec.fPipeline->append(SkRasterPipeline::move_src_dst);
     SkColor4f color = SkColor4f::FromColor(fColor);
     SkColorSpaceXformSteps(sk_srgb_singleton(), kUnpremul_SkAlphaType,
-                           dst,                 kUnpremul_SkAlphaType).apply(color.vec());
-    p->append_constant_color(scratch, color.premul().vec());
-    SkBlendMode_AppendStages(fMode, p);
-}
-
-sk_sp<SkColorFilter> SkModeColorFilter::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    SkColor color = xformer->apply(fColor);
-    if (color != fColor) {
-        return SkColorFilter::MakeModeFilter(color, fMode);
-    }
-    return this->INHERITED::onMakeColorSpace(xformer);
+                           rec.fDstCS,          kUnpremul_SkAlphaType).apply(color.vec());
+    rec.fPipeline->append_constant_color(rec.fAlloc, color.premul().vec());
+    SkBlendMode_AppendStages(fMode, rec.fPipeline);
+    return true;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 #if SK_SUPPORT_GPU
 #include "GrBlend.h"
 #include "effects/GrXfermodeFragmentProcessor.h"
-#include "effects/GrConstColorProcessor.h"
+#include "effects/generated/GrConstColorProcessor.h"
 #include "SkGr.h"
 
 std::unique_ptr<GrFragmentProcessor> SkModeColorFilter::asFragmentProcessor(
-        GrContext*, const GrColorSpaceInfo& dstColorSpaceInfo) const {
+        GrRecordingContext*, const GrColorSpaceInfo& dstColorSpaceInfo) const {
     if (SkBlendMode::kDst == fMode) {
         return nullptr;
     }
@@ -115,7 +104,13 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
+#ifdef SK_SUPPORT_LEGACY_COLORFILTER_FACTORIES
 sk_sp<SkColorFilter> SkColorFilter::MakeModeFilter(SkColor color, SkBlendMode mode) {
+    return SkColorFilters::Blend(color, mode);
+}
+#endif
+
+sk_sp<SkColorFilter> SkColorFilters::Blend(SkColor color, SkBlendMode mode) {
     if (!SkIsValidMode(mode)) {
         return nullptr;
     }
diff --git a/src/core/SkModeColorFilter.h b/src/core/SkModeColorFilter.h
index ce0b323..aac6324 100644
--- a/src/core/SkModeColorFilter.h
+++ b/src/core/SkModeColorFilter.h
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "SkColorFilter.h"
-#include "SkFlattenable.h"
-
 #ifndef SkModeColorFilter_DEFINED
 #define SkModeColorFilter_DEFINED
 
+#include "SkColorFilter.h"
+#include "SkFlattenable.h"
+
 class SkModeColorFilter : public SkColorFilter {
 public:
     static sk_sp<SkColorFilter> Make(SkColor color, SkBlendMode mode) {
@@ -22,7 +22,7 @@
 
 #if SK_SUPPORT_GPU
     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
-            GrContext*, const GrColorSpaceInfo&) const override;
+            GrRecordingContext*, const GrColorSpaceInfo&) const override;
 #endif
 
 protected:
@@ -30,10 +30,7 @@
 
     void flatten(SkWriteBuffer&) const override;
 
-    void onAppendStages(SkRasterPipeline*, SkColorSpace*, SkArenaAlloc*,
-                        bool shaderIsOpaque) const override;
-
-    sk_sp<SkColorFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
+    bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override;
 
 private:
     SK_FLATTENABLE_HOOKS(SkModeColorFilter)
diff --git a/src/core/SkNormalMapSource.cpp b/src/core/SkNormalMapSource.cpp
index bcd4bb6..f75c03d 100644
--- a/src/core/SkNormalMapSource.cpp
+++ b/src/core/SkNormalMapSource.cpp
@@ -47,7 +47,7 @@
             // add uniform
             const char* xformUniName = nullptr;
             fXformUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kFloat2x2_GrSLType,
-                                                   kDefault_GrSLPrecision, "Xform", &xformUniName);
+                                                   "Xform", &xformUniName);
 
             SkString dstNormalColorName("dstNormalColor");
             this->emitChild(0, &dstNormalColorName, args);
@@ -147,11 +147,15 @@
         return nullptr;
     }
 
+    // Normals really aren't colors, so to ensure we can always make the context, we ignore
+    // the rec's colorspace
+    SkColorSpace* dstColorSpace = nullptr;
+
     // Overriding paint's alpha because we need the normal map's RGB channels to be unpremul'd
     SkPaint overridePaint {*(rec.fPaint)};
     overridePaint.setAlpha(0xFF);
     SkShaderBase::ContextRec overrideRec(overridePaint, *(rec.fMatrix), rec.fLocalMatrix,
-                                         rec.fDstColorType, rec.fDstColorSpace);
+                                         rec.fDstColorType, dstColorSpace);
 
     auto* context = as_SB(fMapShader)->makeContext(overrideRec, alloc);
     if (!context) {
@@ -163,7 +167,7 @@
 
 bool SkNormalMapSourceImpl::computeNormTotalInverse(const SkShaderBase::ContextRec& rec,
                                                     SkMatrix* normTotalInverse) const {
-    SkMatrix total = SkMatrix::Concat(*rec.fMatrix, fMapShader->getLocalMatrix());
+    SkMatrix total = SkMatrix::Concat(*rec.fMatrix, as_SB(fMapShader)->getLocalMatrix());
     if (rec.fLocalMatrix) {
         total.preConcat(*rec.fLocalMatrix);
     }
diff --git a/src/core/SkOverdrawCanvas.cpp b/src/core/SkOverdrawCanvas.cpp
index a7b39bb..b81b5f6 100644
--- a/src/core/SkOverdrawCanvas.cpp
+++ b/src/core/SkOverdrawCanvas.cpp
@@ -8,9 +8,10 @@
 #include "SkOverdrawCanvas.h"
 
 #include "SkColorFilter.h"
+#include "SkDevice.h"
 #include "SkDrawShadowInfo.h"
 #include "SkDrawable.h"
-#include "SkFindAndPlaceGlyph.h"
+#include "SkGlyphRunPainter.h"
 #include "SkImagePriv.h"
 #include "SkLatticeIter.h"
 #include "SkPatchUtils.h"
@@ -22,26 +23,6 @@
 #include "SkTextBlobPriv.h"
 #include "SkTo.h"
 
-namespace {
-class ProcessOneGlyphBounds {
-public:
-    ProcessOneGlyphBounds(SkOverdrawCanvas* canvas)
-        : fCanvas(canvas)
-    {}
-
-    void operator()(const SkGlyph& glyph, SkPoint position, SkPoint rounding) {
-        int left = SkScalarFloorToInt(position.fX) + glyph.fLeft;
-        int top = SkScalarFloorToInt(position.fY) + glyph.fTop;
-        int right = left + glyph.fWidth;
-        int bottom = top + glyph.fHeight;
-        fCanvas->onDrawRect(SkRect::MakeLTRB(left, top, right, bottom), SkPaint());
-    }
-
-private:
-    SkOverdrawCanvas* fCanvas;
-};
-};
-
 SkOverdrawCanvas::SkOverdrawCanvas(SkCanvas* canvas)
     : INHERITED(canvas->onImageInfo().width(), canvas->onImageInfo().height())
 {
@@ -58,45 +39,45 @@
 
     fPaint.setAntiAlias(false);
     fPaint.setBlendMode(SkBlendMode::kPlus);
-    fPaint.setColorFilter(SkColorFilter::MakeMatrixFilterRowMajor255(kIncrementAlpha));
+    fPaint.setColorFilter(SkColorFilters::MatrixRowMajor255(kIncrementAlpha));
 }
 
-void SkOverdrawCanvas::drawPosTextCommon(const SkGlyphID glyphs[], int count, const SkScalar pos[],
-                                         int scalarsPerPos, const SkPoint& offset,
-                                         const SkFont& font, const SkPaint& paint) {
-    ProcessOneGlyphBounds processBounds(this);
-    SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
-    this->getProps(&props);
-    auto cache = SkStrikeCache::FindOrCreateStrikeExclusive(
-                                font, paint, props,
-                                SkScalerContextFlags::kNone, this->getTotalMatrix());
-    SkFindAndPlaceGlyph::ProcessPosText(glyphs, count,
-                                        SkPoint::Make(0, 0), SkMatrix(), (const SkScalar*) pos, 2,
-                                        cache.get(), processBounds);
-}
+namespace {
+class TextDevice : public SkNoPixelsDevice, public SkGlyphRunListPainter::BitmapDevicePainter {
+public:
+    TextDevice(SkCanvas* overdrawCanvas, const SkSurfaceProps& props)
+            : SkNoPixelsDevice{SkIRect::MakeWH(32767, 32767), props},
+              fOverdrawCanvas{overdrawCanvas},
+              fPainter{props, kN32_SkColorType, nullptr, SkStrikeCache::GlobalStrikeCache()} {}
+
+    void paintPaths(SkSpan<const SkPathPos> pathsAndPositions, SkScalar scale,
+                    const SkPaint& paint) const override {}
+
+    void paintMasks(SkSpan<const SkMask> masks, const SkPaint& paint) const override {
+        for (auto& mask : masks) {
+            fOverdrawCanvas->drawRect(SkRect::Make(mask.fBounds), SkPaint());
+        }
+    }
+
+protected:
+    void drawGlyphRunList(const SkGlyphRunList& glyphRunList) override {
+        fPainter.drawForBitmapDevice(glyphRunList, fOverdrawCanvas->getTotalMatrix(), this);
+    }
+
+private:
+    SkCanvas* const fOverdrawCanvas;
+    SkGlyphRunListPainter fPainter;
+};
+}  // namespace
 
 void SkOverdrawCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                                       const SkPaint& paint) {
-    SkTextBlobRunIterator it(blob);
-    for (;!it.done(); it.next()) {
-        const SkPoint& offset = it.offset();
-        switch (it.positioning()) {
-            case SkTextBlobRunIterator::kDefault_Positioning:
-                SK_ABORT("This canvas does not support draw text.");
-                break;
-            case SkTextBlobRunIterator::kHorizontal_Positioning:
-                this->drawPosTextCommon(it.glyphs(), it.glyphCount(), it.pos(), 1,
-                                        SkPoint::Make(x, y + offset.y()), it.font(), paint);
-                break;
-            case SkTextBlobRunIterator::kFull_Positioning:
-                this->drawPosTextCommon(it.glyphs(), it.glyphCount(), it.pos(), 2, {x, y},
-                                        it.font(), paint);
-                break;
-            case SkTextBlobRunIterator::kRSXform_Positioning:
-                // unimplemented ...
-                break;
-        }
-    }
+    SkGlyphRunBuilder b;
+    SkSurfaceProps props{0, kUnknown_SkPixelGeometry};
+    this->getProps(&props);
+    TextDevice device{this, props};
+
+    b.drawTextBlob(paint, *blob, {x, y}, &device);
 }
 
 void SkOverdrawCanvas::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
@@ -117,11 +98,6 @@
     fList[0]->onDrawRect(rect, this->overdrawPaint(paint));
 }
 
-void SkOverdrawCanvas::onDrawEdgeAARect(const SkRect& rect, SkCanvas::QuadAAFlags aa, SkColor color,
-                                        SkBlendMode mode) {
-    fList[0]->onDrawRect(rect, fPaint);
-}
-
 void SkOverdrawCanvas::onDrawRegion(const SkRegion& region, const SkPaint& paint) {
     fList[0]->onDrawRegion(region, this->overdrawPaint(paint));
 }
@@ -211,13 +187,6 @@
     }
 }
 
-void SkOverdrawCanvas::onDrawImageSet(const ImageSetEntry set[], int count, SkFilterQuality,
-                                      SkBlendMode) {
-    for (int i = 0; i < count; ++i) {
-        fList[0]->onDrawRect(set[i].fDstRect, fPaint);
-    }
-}
-
 void SkOverdrawCanvas::onDrawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y,
                                     const SkPaint*) {
     fList[0]->onDrawRect(SkRect::MakeXYWH(x, y, bitmap.width(), bitmap.height()), fPaint);
@@ -256,6 +225,41 @@
     fList[0]->onDrawRect(bounds, fPaint);
 }
 
+void SkOverdrawCanvas::onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
+                                        QuadAAFlags aa, SkColor color, SkBlendMode mode) {
+    if (clip) {
+        SkPath path;
+        path.addPoly(clip, 4, true);
+        fList[0]->onDrawPath(path, fPaint);
+    } else {
+        fList[0]->onDrawRect(rect, fPaint);
+    }
+}
+
+void SkOverdrawCanvas::onDrawEdgeAAImageSet(const ImageSetEntry set[], int count,
+                                            const SkPoint dstClips[],
+                                            const SkMatrix preViewMatrices[], const SkPaint* paint,
+                                            SrcRectConstraint constraint) {
+    int clipIndex = 0;
+    for (int i = 0; i < count; ++i) {
+        if (set[i].fMatrixIndex >= 0) {
+            fList[0]->save();
+            fList[0]->concat(preViewMatrices[set[i].fMatrixIndex]);
+        }
+        if (set[i].fHasClip) {
+            SkPath path;
+            path.addPoly(dstClips + clipIndex, 4, true);
+            clipIndex += 4;
+            fList[0]->onDrawPath(path, fPaint);
+        } else {
+            fList[0]->onDrawRect(set[i].fDstRect, fPaint);
+        }
+        if (set[i].fMatrixIndex >= 0) {
+            fList[0]->restore();
+        }
+    }
+}
+
 inline SkPaint SkOverdrawCanvas::overdrawPaint(const SkPaint& paint) {
     SkPaint newPaint = fPaint;
     newPaint.setStyle(paint.getStyle());
diff --git a/src/core/SkPaint.cpp b/src/core/SkPaint.cpp
index 150fcce..919e49f 100644
--- a/src/core/SkPaint.cpp
+++ b/src/core/SkPaint.cpp
@@ -39,97 +39,32 @@
 // e.g. setTextSize(-1)
 //#define SK_REPORT_API_RANGE_CHECK
 
-SkPaint::SkPaint() {
-    fColor4f    = { 0, 0, 0, 1 }; // opaque black
-    fWidth      = 0;
-    fMiterLimit = SkPaintDefaults_MiterLimit;
 
-    // we init (to 0) and copy using fBitfieldsUInt rather than fBitfields, so we need it
-    // to be large enough to cover all of the bits.
-    static_assert(sizeof(fBitfields) <= sizeof(fBitfieldsUInt),
-                  "need union uint to be large enough");
-
-    // Zero all bitfields, then set some non-zero defaults.
-    fBitfieldsUInt           = 0;
-    fBitfields.fCapType      = kDefault_Cap;
-    fBitfields.fJoinType     = kDefault_Join;
-    fBitfields.fStyle        = kFill_Style;
-    fBitfields.fBlendMode  = (unsigned)SkBlendMode::kSrcOver;
+SkPaint::SkPaint()
+    : fColor4f{0, 0, 0, 1}  // opaque black
+    , fWidth{0}
+    , fMiterLimit{SkPaintDefaults_MiterLimit}
+    , fBitfields{(unsigned)false,                   // fAntiAlias
+                 (unsigned)false,                   // fDither
+                 (unsigned)SkPaint::kDefault_Cap,   // fCapType
+                 (unsigned)SkPaint::kDefault_Join,  // fJoinType
+                 (unsigned)SkPaint::kFill_Style,    // fStyle
+                 (unsigned)kNone_SkFilterQuality,   // fFilterQuality
+                 (unsigned)SkBlendMode::kSrcOver,   // fBlendMode
+                 0}                                 // fPadding
+{
+    static_assert(sizeof(fBitfields) == sizeof(fBitfieldsUInt), "");
 }
 
-SkPaint::SkPaint(const SkPaint& src)
-#define COPY(field) field(src.field)
-    : COPY(fPathEffect)
-    , COPY(fShader)
-    , COPY(fMaskFilter)
-    , COPY(fColorFilter)
-    , COPY(fDrawLooper)
-    , COPY(fImageFilter)
-    , COPY(fColor4f)
-    , COPY(fWidth)
-    , COPY(fMiterLimit)
-    , COPY(fBitfieldsUInt)
-#undef COPY
-{}
+SkPaint::SkPaint(const SkPaint& src) = default;
 
-SkPaint::SkPaint(SkPaint&& src) {
-#define MOVE(field) field = std::move(src.field)
-    MOVE(fPathEffect);
-    MOVE(fShader);
-    MOVE(fMaskFilter);
-    MOVE(fColorFilter);
-    MOVE(fDrawLooper);
-    MOVE(fImageFilter);
-    MOVE(fColor4f);
-    MOVE(fWidth);
-    MOVE(fMiterLimit);
-    MOVE(fBitfieldsUInt);
-#undef MOVE
-}
+SkPaint::SkPaint(SkPaint&& src) = default;
 
-SkPaint::~SkPaint() {}
+SkPaint::~SkPaint() = default;
 
-SkPaint& SkPaint::operator=(const SkPaint& src) {
-    if (this == &src) {
-        return *this;
-    }
+SkPaint& SkPaint::operator=(const SkPaint& src) = default;
 
-#define ASSIGN(field) field = src.field
-    ASSIGN(fPathEffect);
-    ASSIGN(fShader);
-    ASSIGN(fMaskFilter);
-    ASSIGN(fColorFilter);
-    ASSIGN(fDrawLooper);
-    ASSIGN(fImageFilter);
-    ASSIGN(fColor4f);
-    ASSIGN(fWidth);
-    ASSIGN(fMiterLimit);
-    ASSIGN(fBitfieldsUInt);
-#undef ASSIGN
-
-    return *this;
-}
-
-SkPaint& SkPaint::operator=(SkPaint&& src) {
-    if (this == &src) {
-        return *this;
-    }
-
-#define MOVE(field) field = std::move(src.field)
-    MOVE(fPathEffect);
-    MOVE(fShader);
-    MOVE(fMaskFilter);
-    MOVE(fColorFilter);
-    MOVE(fDrawLooper);
-    MOVE(fImageFilter);
-    MOVE(fColor4f);
-    MOVE(fWidth);
-    MOVE(fMiterLimit);
-    MOVE(fBitfieldsUInt);
-#undef MOVE
-
-    return *this;
-}
+SkPaint& SkPaint::operator=(SkPaint&& src) = default;
 
 bool operator==(const SkPaint& a, const SkPaint& b) {
 #define EQUAL(field) (a.field == b.field)
@@ -156,10 +91,7 @@
 DEFINE_REF_FOO(Shader)
 #undef DEFINE_REF_FOO
 
-void SkPaint::reset() {
-    SkPaint init;
-    *this = init;
-}
+void SkPaint::reset() { *this = SkPaint(); }
 
 void SkPaint::setFilterQuality(SkFilterQuality quality) {
     fBitfields.fFilterQuality = quality;
@@ -180,15 +112,17 @@
 }
 
 void SkPaint::setColor4f(const SkColor4f& color, SkColorSpace* colorSpace) {
+    SkASSERT(fColor4f.fA >= 0 && fColor4f.fA <= 1.0f);
+
     SkColorSpaceXformSteps steps{colorSpace,          kUnpremul_SkAlphaType,
                                  sk_srgb_singleton(), kUnpremul_SkAlphaType};
     fColor4f = color;
     steps.apply(fColor4f.vec());
 }
 
-void SkPaint::setAlpha(U8CPU a) {
-    SkASSERT(a <= 255);
-    fColor4f.fA = a * (1.0f / 255);
+void SkPaint::setAlphaf(float a) {
+    SkASSERT(a >= 0 && a <= 1.0f);
+    fColor4f.fA = a;
 }
 
 void SkPaint::setARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b) {
diff --git a/src/core/SkPaintDefaults.h b/src/core/SkPaintDefaults.h
index 410f93d..a1b9630 100644
--- a/src/core/SkPaintDefaults.h
+++ b/src/core/SkPaintDefaults.h
@@ -16,10 +16,6 @@
  *  edited directly.
  */
 
-#ifndef SkPaintDefaults_Flags
-    #define SkPaintDefaults_Flags           0
-#endif
-
 #ifndef SkPaintDefaults_TextSize
     #define SkPaintDefaults_TextSize        SkIntToScalar(12)
 #endif
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index 9650849..3593967 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -156,7 +156,6 @@
     : fPathRef(SkPathRef::CreateEmpty()) {
     this->resetFields();
     fIsVolatile = false;
-    fIsBadForDAA = false;
 }
 
 void SkPath::resetFields() {
@@ -196,7 +195,6 @@
     fLastMoveToIndex = that.fLastMoveToIndex;
     fFillType        = that.fFillType;
     fIsVolatile      = that.fIsVolatile;
-    fIsBadForDAA     = that.fIsBadForDAA;
 
     // Non-atomic assignment of atomic values.
     this->setConvexity(that.getConvexityOrUnknown());
@@ -1113,8 +1111,13 @@
 //
 static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
                                    SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
-    startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
-    stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
+    SkScalar startRad = SkDegreesToRadians(startAngle),
+             stopRad  = SkDegreesToRadians(startAngle + sweepAngle);
+
+    startV->fY = SkScalarSinSnapToZero(startRad);
+    startV->fX = SkScalarCosSnapToZero(startRad);
+    stopV->fY = SkScalarSinSnapToZero(stopRad);
+    stopV->fX = SkScalarCosSnapToZero(stopRad);
 
     /*  If the sweep angle is nearly (but less than) 360, then due to precision
      loss in radians-conversion and/or sin/cos, we may end up with coincident
@@ -1127,13 +1130,13 @@
     if (*startV == *stopV) {
         SkScalar sw = SkScalarAbs(sweepAngle);
         if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
-            SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
             // make a guess at a tiny angle (in radians) to tweak by
             SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
             // not sure how much will be enough, so we use a loop
             do {
                 stopRad -= deltaRad;
-                stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
+                stopV->fY = SkScalarSinSnapToZero(stopRad);
+                stopV->fX = SkScalarCosSnapToZero(stopRad);
             } while (*startV == *stopV);
         }
     }
@@ -1371,13 +1374,11 @@
         SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
         SkScalar radiusX = oval.width() / 2;
         SkScalar radiusY = oval.height() / 2;
-        // We cannot use SkScalarSinCos function in the next line because
-        // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
-        // is 0 and sweepAngle is very small and radius is huge, the expected
-        // behavior here is to draw a line. But calling SkScalarSinCos will
-        // make sin(endAngle) to be 0 which will then draw a dot.
-        singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
-            oval.centerY() + radiusY * sk_float_sin(endAngle));
+        // We do not use SkScalar[Sin|Cos]SnapToZero here. When sin(startAngle) is 0 and sweepAngle
+        // is very small and radius is huge, the expected behavior here is to draw a line. But
+        // calling SkScalarSinSnapToZero will make sin(endAngle) be 0 which will then draw a dot.
+        singlePt.set(oval.centerX() + radiusX * SkScalarCos(endAngle),
+                     oval.centerY() + radiusY * SkScalarSin(endAngle));
         addPt(singlePt);
         return *this;
     }
@@ -1492,8 +1493,9 @@
         scalar_is_integer(x) && scalar_is_integer(y);
 
     for (int i = 0; i < segments; ++i) {
-        SkScalar endTheta = startTheta + thetaWidth;
-        SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
+        SkScalar endTheta    = startTheta + thetaWidth,
+                 sinEndTheta = SkScalarSinSnapToZero(endTheta),
+                 cosEndTheta = SkScalarCosSnapToZero(endTheta);
 
         unitPts[1].set(cosEndTheta, sinEndTheta);
         unitPts[1] += centerPoint;
@@ -1959,13 +1961,6 @@
     return false;
 }
 
-SkPath::Verb SkPath::Iter::peekVerb() const {
-    if (fVerbs == nullptr || fVerbs == fVerbStop) {
-        return kDone_Verb;
-    }
-    return (Verb)fVerbs[-1];
-}
-
 SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
     SkASSERT(pts);
     if (fLastPt != fMoveTo) {
@@ -3865,7 +3860,7 @@
     return n + 1;
 }
 
-static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
+static int compute_cubic_extremas(const SkPoint src[4], SkPoint extremas[5]) {
     SkScalar ts[4];
     int n  = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
         n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
diff --git a/src/core/SkPathPriv.h b/src/core/SkPathPriv.h
index 14de724..ff2fdc1 100644
--- a/src/core/SkPathPriv.h
+++ b/src/core/SkPathPriv.h
@@ -238,10 +238,6 @@
         return result;
     }
 
-    // For crbug.com/821353 and skbug.com/6886
-    static bool IsBadForDAA(const SkPath& path) { return path.fIsBadForDAA; }
-    static void SetIsBadForDAA(SkPath& path, bool isBadForDAA) { path.fIsBadForDAA = isBadForDAA; }
-
     /**
      *  Sometimes in the drawing pipeline, we have to perform math on path coordinates, even after
      *  the path is in device-coordinates. Tessellation and clipping are two examples. Usually this
diff --git a/src/core/SkPathRef.cpp b/src/core/SkPathRef.cpp
index fb2a70b..c435121 100644
--- a/src/core/SkPathRef.cpp
+++ b/src/core/SkPathRef.cpp
@@ -243,6 +243,11 @@
         (*dst)->fRRectOrOvalStartIdx = start;
     }
 
+    if (dst->get() == &src) {
+        (*dst)->callGenIDChangeListeners();
+        (*dst)->fGenerationID = 0;
+    }
+
     SkDEBUGCODE((*dst)->validate();)
 }
 
@@ -810,7 +815,11 @@
 
 uint8_t SkPathRef::Iter::next(SkPoint pts[4]) {
     SkASSERT(pts);
+
+    SkDEBUGCODE(unsigned peekResult = this->peek();)
+
     if (fVerbs == fVerbStop) {
+        SkASSERT(peekResult == SkPath::kDone_Verb);
         return (uint8_t) SkPath::kDone_Verb;
     }
 
@@ -851,12 +860,13 @@
             break;
     }
     fPts = srcPts;
+    SkASSERT(peekResult == verb);
     return (uint8_t) verb;
 }
 
 uint8_t SkPathRef::Iter::peek() const {
-    const uint8_t* next = fVerbs - 1;
-    return next <= fVerbStop ? (uint8_t) SkPath::kDone_Verb : *next;
+    const uint8_t* next = fVerbs;
+    return next <= fVerbStop ? (uint8_t) SkPath::kDone_Verb : next[-1];
 }
 
 
diff --git a/src/core/SkPictureFlat.h b/src/core/SkPictureFlat.h
index de1c269..34e4dfb 100644
--- a/src/core/SkPictureFlat.h
+++ b/src/core/SkPictureFlat.h
@@ -97,13 +97,13 @@
 
     FLUSH,
 
-    DRAW_IMAGE_SET,
+    DRAW_EDGEAA_IMAGE_SET,
 
     SAVE_BEHIND,
 
-    DRAW_EDGEAA_RECT,
+    DRAW_EDGEAA_QUAD,
 
-    LAST_DRAWTYPE_ENUM = DRAW_EDGEAA_RECT,
+    LAST_DRAWTYPE_ENUM = DRAW_EDGEAA_QUAD,
 };
 
 enum DrawVertexFlags {
diff --git a/src/core/SkPictureImageGenerator.cpp b/src/core/SkPictureImageGenerator.cpp
index 1b10352..ac34a3e 100644
--- a/src/core/SkPictureImageGenerator.cpp
+++ b/src/core/SkPictureImageGenerator.cpp
@@ -26,8 +26,8 @@
 
 #if SK_SUPPORT_GPU
     TexGenType onCanGenerateTexture() const override { return TexGenType::kExpensive; }
-    sk_sp<GrTextureProxy> onGenerateTexture(GrContext*, const SkImageInfo&, const SkIPoint&,
-                                            bool willNeedMipMaps) override;
+    sk_sp<GrTextureProxy> onGenerateTexture(GrRecordingContext*, const SkImageInfo&,
+                                            const SkIPoint&, bool willNeedMipMaps) override;
 #endif
 
 private:
@@ -92,12 +92,19 @@
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 #if SK_SUPPORT_GPU
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
+
 sk_sp<GrTextureProxy> SkPictureImageGenerator::onGenerateTexture(
-        GrContext* ctx, const SkImageInfo& info, const SkIPoint& origin, bool willNeedMipMaps) {
+        GrRecordingContext* ctx, const SkImageInfo& info,
+        const SkIPoint& origin, bool willNeedMipMaps) {
     SkASSERT(ctx);
 
     SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
-    sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(ctx, SkBudgeted::kYes, info, 0,
+
+    // CONTEXT TODO: remove this use of 'backdoor' to create an SkSkSurface
+    sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(ctx->priv().backdoor(),
+                                                         SkBudgeted::kYes, info, 0,
                                                          kTopLeft_GrSurfaceOrigin, &props,
                                                          willNeedMipMaps));
     if (!surface) {
@@ -112,7 +119,7 @@
     if (!image) {
         return nullptr;
     }
-    sk_sp<GrTextureProxy> proxy = as_IB(image)->asTextureProxyRef();
+    sk_sp<GrTextureProxy> proxy = as_IB(image)->asTextureProxyRef(ctx);
     SkASSERT(!willNeedMipMaps || GrMipMapped::kYes == proxy->mipMapped());
     return proxy;
 }
diff --git a/src/core/SkPicturePlayback.cpp b/src/core/SkPicturePlayback.cpp
index e0120dd..3646006 100644
--- a/src/core/SkPicturePlayback.cpp
+++ b/src/core/SkPicturePlayback.cpp
@@ -274,15 +274,85 @@
                 canvas->drawDRRect(outer, inner, *paint);
             }
         } break;
-        case DRAW_EDGEAA_RECT: {
+        case DRAW_EDGEAA_QUAD: {
             SkRect rect;
             reader->readRect(&rect);
             SkCanvas::QuadAAFlags aaFlags = static_cast<SkCanvas::QuadAAFlags>(reader->read32());
             SkColor color = reader->read32();
             SkBlendMode blend = static_cast<SkBlendMode>(reader->read32());
+            bool hasClip = reader->readInt();
+            SkPoint* clip = nullptr;
+            if (hasClip) {
+                clip = (SkPoint*) reader->skip(4, sizeof(SkPoint));
+            }
+            BREAK_ON_READ_ERROR(reader);
+            canvas->experimental_DrawEdgeAAQuad(rect, clip, aaFlags, color, blend);
+        } break;
+        case DRAW_EDGEAA_IMAGE_SET: {
+            static const size_t kEntryReadSize =
+                    4 * sizeof(uint32_t) + 2 * sizeof(SkRect) + sizeof(SkScalar);
+            static const size_t kMatrixSize = 9 * sizeof(SkScalar); // != sizeof(SkMatrix)
+
+            int cnt = reader->readInt();
+            if (!reader->validate(cnt >= 0)) {
+                break;
+            }
+            const SkPaint* paint = fPictureData->getPaint(reader);
+            SkCanvas::SrcRectConstraint constraint =
+                    static_cast<SkCanvas::SrcRectConstraint>(reader->readInt());
+
+            if (!reader->validate(SkSafeMath::Mul(cnt, kEntryReadSize) <= reader->available())) {
+                break;
+            }
+
+            // Track minimum necessary clip points and matrices that must be provided to satisfy
+            // the entries.
+            int expectedClips = 0;
+            int maxMatrixIndex = -1;
+            SkAutoTArray<SkCanvas::ImageSetEntry> set(cnt);
+            for (int i = 0; i < cnt && reader->isValid(); ++i) {
+                set[i].fImage = sk_ref_sp(fPictureData->getImage(reader));
+                reader->readRect(&set[i].fSrcRect);
+                reader->readRect(&set[i].fDstRect);
+                set[i].fMatrixIndex = reader->readInt();
+                set[i].fAlpha = reader->readScalar();
+                set[i].fAAFlags = reader->readUInt();
+                set[i].fHasClip = reader->readInt();
+
+                expectedClips += set[i].fHasClip ? 1 : 0;
+                if (set[i].fMatrixIndex > maxMatrixIndex) {
+                    maxMatrixIndex = set[i].fMatrixIndex;
+                }
+            }
+
+            int dstClipCount = reader->readInt();
+            SkPoint* dstClips = nullptr;
+            if (!reader->validate(expectedClips <= dstClipCount)) {
+                // Entries request more dstClip points than are provided in the buffer
+                break;
+            } else if (dstClipCount > 0) {
+                dstClips = (SkPoint*) reader->skip(dstClipCount, sizeof(SkPoint));
+                if (dstClips == nullptr) {
+                    // Not enough bytes remaining so the reader has been invalidated
+                    break;
+                }
+            }
+            int matrixCount = reader->readInt();
+            if (!reader->validate((maxMatrixIndex + 1) <= matrixCount) ||
+                !reader->validate(
+                    SkSafeMath::Mul(matrixCount, kMatrixSize) <= reader->available())) {
+                // Entries access out-of-bound matrix indices, given provided matrices or
+                // there aren't enough bytes to provide that many matrices
+                break;
+            }
+            SkTArray<SkMatrix> matrices(matrixCount);
+            for (int i = 0; i < matrixCount && reader->isValid(); ++i) {
+                reader->readMatrix(&matrices.push_back());
+            }
             BREAK_ON_READ_ERROR(reader);
 
-            canvas->experimental_DrawEdgeAARectV1(rect, aaFlags, color, blend);
+            canvas->experimental_DrawEdgeAAImageSet(set.get(), cnt, dstClips, matrices.begin(),
+                                                    paint, constraint);
         } break;
         case DRAW_IMAGE: {
             const SkPaint* paint = fPictureData->getPaint(reader);
@@ -331,25 +401,6 @@
 
             canvas->legacy_drawImageRect(image, src, dst, paint, constraint);
         } break;
-        case DRAW_IMAGE_SET: {
-            int cnt = reader->readInt();
-            if (!reader->validate(cnt >= 0)) {
-                break;
-            }
-            SkFilterQuality filterQuality = (SkFilterQuality)reader->readUInt();
-            SkBlendMode mode = (SkBlendMode)reader->readUInt();
-            SkAutoTArray<SkCanvas::ImageSetEntry> set(cnt);
-            for (int i = 0; i < cnt; ++i) {
-                set[i].fImage = sk_ref_sp(fPictureData->getImage(reader));
-                reader->readRect(&set[i].fSrcRect);
-                reader->readRect(&set[i].fDstRect);
-                set[i].fAlpha = reader->readScalar();
-                set[i].fAAFlags = reader->readUInt();
-            }
-            BREAK_ON_READ_ERROR(reader);
-
-            canvas->experimental_DrawImageSetV1(set.get(), cnt, filterQuality, mode);
-        } break;
         case DRAW_OVAL: {
             const SkPaint* paint = fPictureData->getPaint(reader);
             SkRect rect;
diff --git a/src/core/SkPictureRecord.cpp b/src/core/SkPictureRecord.cpp
index 01115bf..d8bc36b 100644
--- a/src/core/SkPictureRecord.cpp
+++ b/src/core/SkPictureRecord.cpp
@@ -467,18 +467,6 @@
     this->validate(initialOffset, size);
 }
 
-void SkPictureRecord::onDrawEdgeAARect(const SkRect& rect, SkCanvas::QuadAAFlags aa,
-                                       SkColor color, SkBlendMode mode) {
-    // op + rect + aa flags + color + mode
-    size_t size = 4 * kUInt32Size + sizeof(rect);
-    size_t initialOffset = this->addDraw(DRAW_EDGEAA_RECT, &size);
-    this->addRect(rect);
-    this->addInt((int) aa);
-    this->addInt((int) color);
-    this->addInt((int) mode);
-    this->validate(initialOffset, size);
-}
-
 void SkPictureRecord::onDrawRegion(const SkRegion& region, const SkPaint& paint) {
     // op + paint index + region
     size_t regionBytes = region.writeToMemory(nullptr);
@@ -574,25 +562,6 @@
     this->validate(initialOffset, size);
 }
 
-void SkPictureRecord::onDrawImageSet(const SkCanvas::ImageSetEntry set[], int count,
-                                     SkFilterQuality filterQuality, SkBlendMode mode) {
-    // op + count + alpha + fq + mode + (image index, src rect, dst rect, alpha, aa flags) * cnt
-    size_t size =
-            4 * kUInt32Size + (2 * kUInt32Size + 2 * sizeof(SkRect) + sizeof(SkScalar)) * count;
-    size_t initialOffset = this->addDraw(DRAW_IMAGE_SET, &size);
-    this->addInt(count);
-    this->addInt((int)filterQuality);
-    this->addInt((int)mode);
-    for (int i = 0; i < count; ++i) {
-        this->addImage(set[i].fImage.get());
-        this->addRect(set[i].fSrcRect);
-        this->addRect(set[i].fDstRect);
-        this->addScalar(set[i].fAlpha);
-        this->addInt((int)set[i].fAAFlags);
-    }
-    this->validate(initialOffset, size);
-}
-
 void SkPictureRecord::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                                      const SkPaint& paint) {
 
@@ -762,6 +731,59 @@
     this->validate(initialOffset, size);
 }
 
+void SkPictureRecord::onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
+                                       SkCanvas::QuadAAFlags aa, SkColor color, SkBlendMode mode) {
+
+    // op + rect + aa flags + color + mode + hasClip(as int) + clipCount*points
+    size_t size = 5 * kUInt32Size + sizeof(rect) + (clip ? 4 : 0) * sizeof(SkPoint);
+    size_t initialOffset = this->addDraw(DRAW_EDGEAA_QUAD, &size);
+    this->addRect(rect);
+    this->addInt((int) aa);
+    this->addInt((int) color);
+    this->addInt((int) mode);
+    this->addInt(clip != nullptr);
+    if (clip) {
+        this->addPoints(clip, 4);
+    }
+    this->validate(initialOffset, size);
+}
+
+void SkPictureRecord::onDrawEdgeAAImageSet(const SkCanvas::ImageSetEntry set[], int count,
+                                           const SkPoint dstClips[],
+                                           const SkMatrix preViewMatrices[],
+                                           const SkPaint* paint,
+                                           SkCanvas::SrcRectConstraint constraint) {
+    static constexpr size_t kMatrixSize = 9 * sizeof(SkScalar); // *not* sizeof(SkMatrix)
+    // op + count + paint + constraint + (image index, src rect, dst rect, alpha, aa flags,
+    // hasClip(int), matrixIndex) * cnt + totalClipCount + dstClips + totalMatrixCount + matrices
+    int totalDstClipCount, totalMatrixCount;
+    SkCanvasPriv::GetDstClipAndMatrixCounts(set, count, &totalDstClipCount, &totalMatrixCount);
+
+    size_t size = 6 * kUInt32Size + sizeof(SkPoint) * totalDstClipCount +
+                  kMatrixSize * totalMatrixCount +
+                  (4 * kUInt32Size + 2 * sizeof(SkRect) + sizeof(SkScalar)) * count;
+    size_t initialOffset = this->addDraw(DRAW_EDGEAA_IMAGE_SET, &size);
+    this->addInt(count);
+    this->addPaintPtr(paint);
+    this->addInt((int) constraint);
+    for (int i = 0; i < count; ++i) {
+        this->addImage(set[i].fImage.get());
+        this->addRect(set[i].fSrcRect);
+        this->addRect(set[i].fDstRect);
+        this->addInt(set[i].fMatrixIndex);
+        this->addScalar(set[i].fAlpha);
+        this->addInt((int)set[i].fAAFlags);
+        this->addInt(set[i].fHasClip);
+    }
+    this->addInt(totalDstClipCount);
+    this->addPoints(dstClips, totalDstClipCount);
+    this->addInt(totalMatrixCount);
+    for (int i = 0; i < totalMatrixCount; ++i) {
+        this->addMatrix(preViewMatrices[i]);
+    }
+    this->validate(initialOffset, size);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 // De-duping helper.
diff --git a/src/core/SkPictureRecord.h b/src/core/SkPictureRecord.h
index 77137c4..f355e41 100644
--- a/src/core/SkPictureRecord.h
+++ b/src/core/SkPictureRecord.h
@@ -176,7 +176,6 @@
     void onDrawPaint(const SkPaint&) override;
     void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) override;
     void onDrawRect(const SkRect&, const SkPaint&) override;
-    void onDrawEdgeAARect(const SkRect&, SkCanvas::QuadAAFlags, SkColor, SkBlendMode) override;
     void onDrawRegion(const SkRegion&, const SkPaint&) override;
     void onDrawOval(const SkRect&, const SkPaint&) override;
     void onDrawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&) override;
@@ -189,8 +188,7 @@
                          const SkPaint*) override;
     void onDrawImageLattice(const SkImage*, const SkCanvas::Lattice& lattice, const SkRect& dst,
                             const SkPaint*) override;
-    void onDrawImageSet(const SkCanvas::ImageSetEntry[], int count, SkFilterQuality,
-                        SkBlendMode) override;
+
     void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override;
     void onDrawVerticesObject(const SkVertices*, const SkVertices::Bone bones[], int boneCount,
                               SkBlendMode, const SkPaint&) override;
@@ -205,6 +203,11 @@
     void onDrawDrawable(SkDrawable*, const SkMatrix*) override;
     void onDrawAnnotation(const SkRect&, const char[], SkData*) override;
 
+    void onDrawEdgeAAQuad(const SkRect&, const SkPoint[4], QuadAAFlags, SkColor,
+                          SkBlendMode) override;
+    void onDrawEdgeAAImageSet(const ImageSetEntry[], int count, const SkPoint[], const SkMatrix[],
+                              const SkPaint*, SrcRectConstraint) override;
+
     int addPathToHeap(const SkPath& path);  // does not write to ops stream
 
     // These entry points allow the writing of matrices, clips, saves &
diff --git a/src/core/SkPixmap.cpp b/src/core/SkPixmap.cpp
index 316614e..2c3faa3 100644
--- a/src/core/SkPixmap.cpp
+++ b/src/core/SkPixmap.cpp
@@ -119,6 +119,7 @@
             uint32_t u32 = static_cast<const uint32_t*>(srcPtr)[0];
             value = (u32 >> 30) * (1.0f/3);
         } break;
+        case kRGBA_F16Norm_SkColorType:
         case kRGBA_F16_SkColorType: {
             uint64_t px;
             memcpy(&px, srcPtr, sizeof(px));
@@ -216,8 +217,8 @@
 
     // We'll create a shader to do this draw so we have control over the bicubic clamp.
     sk_sp<SkShader> shader = SkImageShader::Make(SkImage::MakeFromBitmap(bitmap),
-                                                 SkShader::kClamp_TileMode,
-                                                 SkShader::kClamp_TileMode,
+                                                 SkTileMode::kClamp,
+                                                 SkTileMode::kClamp,
                                                  &scale,
                                                  clampAsIfUnpremul);
 
@@ -304,6 +305,7 @@
                  | (uint32_t)( b * 255.0f ) <<  0
                  | (uint32_t)( a * 255.0f ) << 24;
         }
+        case kRGBA_F16Norm_SkColorType:
         case kRGBA_F16_SkColorType: {
             const uint64_t* addr =
                 (const uint64_t*)fPixels + y * (fRowBytes >> 3) + x;
@@ -388,6 +390,7 @@
             }
             return true;
         }
+        case kRGBA_F16Norm_SkColorType:
         case kRGBA_F16_SkColorType: {
             const SkHalf* row = (const SkHalf*)this->addr();
             for (int y = 0; y < height; ++y) {
diff --git a/src/core/SkPoint.cpp b/src/core/SkPoint.cpp
index 358199a..dad8864 100644
--- a/src/core/SkPoint.cpp
+++ b/src/core/SkPoint.cpp
@@ -5,30 +5,9 @@
  * found in the LICENSE file.
  */
 
-
 #include "SkMathPriv.h"
 #include "SkPointPriv.h"
 
-#if 0
-void SkIPoint::rotateCW(SkIPoint* dst) const {
-    SkASSERT(dst);
-
-    // use a tmp in case this == dst
-    int32_t tmp = fX;
-    dst->fX = -fY;
-    dst->fY = tmp;
-}
-
-void SkIPoint::rotateCCW(SkIPoint* dst) const {
-    SkASSERT(dst);
-
-    // use a tmp in case this == dst
-    int32_t tmp = fX;
-    dst->fX = fY;
-    dst->fY = -tmp;
-}
-#endif
-
 ///////////////////////////////////////////////////////////////////////////////
 
 void SkPoint::scale(SkScalar scale, SkPoint* dst) const {
@@ -48,23 +27,6 @@
     return this->setLength(fX, fY, length);
 }
 
-// Returns the square of the Euclidian distance to (dx,dy).
-static inline float getLengthSquared(float dx, float dy) {
-    return dx * dx + dy * dy;
-}
-
-// Calculates the square of the Euclidian distance to (dx,dy) and stores it in
-// *lengthSquared.  Returns true if the distance is judged to be "nearly zero".
-//
-// This logic is encapsulated in a helper method to make it explicit that we
-// always perform this check in the same manner, to avoid inconsistencies
-// (see http://code.google.com/p/skia/issues/detail?id=560 ).
-static inline bool is_length_nearly_zero(float dx, float dy,
-                                         float *lengthSquared) {
-    *lengthSquared = getLengthSquared(dx, dy);
-    return *lengthSquared <= (SK_ScalarNearlyZero * SK_ScalarNearlyZero);
-}
-
 /*
  *  We have to worry about 2 tricky conditions:
  *  1. underflow of mag2 (compared against nearlyzero^2)
@@ -77,41 +39,23 @@
                                                 float* orig_length = nullptr) {
     SkASSERT(!use_rsqrt || (orig_length == nullptr));
 
-    float mag = 0;
-    float mag2;
-    if (is_length_nearly_zero(x, y, &mag2)) {
+    // our mag2 step overflowed to infinity, so use doubles instead.
+    // much slower, but needed when x or y are very large, other wise we
+    // divide by inf. and return (0,0) vector.
+    double xx = x;
+    double yy = y;
+    double dmag = sqrt(xx * xx + yy * yy);
+    double dscale = sk_ieee_double_divide(length, dmag);
+    x *= dscale;
+    y *= dscale;
+    // check if we're not finite, or we're zero-length
+    if (!sk_float_isfinite(x) || !sk_float_isfinite(y) || (x == 0 && y == 0)) {
         pt->set(0, 0);
         return false;
     }
-
-    if (sk_float_isfinite(mag2)) {
-        float scale;
-        if (use_rsqrt) {
-            scale = length * sk_float_rsqrt(mag2);
-        } else {
-            mag = sk_float_sqrt(mag2);
-            scale = length / mag;
-        }
-        x *= scale;
-        y *= scale;
-    } else {
-        // our mag2 step overflowed to infinity, so use doubles instead.
-        // much slower, but needed when x or y are very large, other wise we
-        // divide by inf. and return (0,0) vector.
-        double xx = x;
-        double yy = y;
-        double dmag = sqrt(xx * xx + yy * yy);
-        double dscale = length / dmag;
-        x *= dscale;
-        y *= dscale;
-        // check if we're not finite, or we're zero-length
-        if (!sk_float_isfinite(x) || !sk_float_isfinite(y) || (x == 0 && y == 0)) {
-            pt->set(0, 0);
-            return false;
-        }
-        if (orig_length) {
-            mag = sk_double_to_float(dmag);
-        }
+    float mag = 0;
+    if (orig_length) {
+        mag = sk_double_to_float(dmag);
     }
     pt->set(x, y);
     if (orig_length) {
diff --git a/src/core/SkPointPriv.h b/src/core/SkPointPriv.h
index 338905d..7089fcf 100644
--- a/src/core/SkPointPriv.h
+++ b/src/core/SkPointPriv.h
@@ -26,11 +26,7 @@
     static const SkScalar* AsScalars(const SkPoint& pt) { return &pt.fX; }
 
     static bool CanNormalize(SkScalar dx, SkScalar dy) {
-        if (!SkScalarsAreFinite(dx, dy)) {
-            return false;
-        }
-        // Simple enough (and performance critical sometimes) so we inline it.
-        return (dx*dx + dy*dy) > (SK_ScalarNearlyZero * SK_ScalarNearlyZero);
+        return SkScalarsAreFinite(dx, dy) && (dx || dy);
     }
 
     static SkScalar DistanceToLineBetweenSqd(const SkPoint& pt, const SkPoint& a,
diff --git a/src/core/SkRRect.cpp b/src/core/SkRRect.cpp
index 0ddf3ac..4fa097d 100644
--- a/src/core/SkRRect.cpp
+++ b/src/core/SkRRect.cpp
@@ -389,9 +389,7 @@
         return true;
     }
 
-    // If transform supported 90 degree rotations (which it could), we could
-    // use SkMatrix::rectStaysRect() to check for a valid transformation.
-    if (!matrix.isScaleTranslate()) {
+    if (!matrix.preservesAxisAlignment()) {
         return false;
     }
 
@@ -411,7 +409,7 @@
     // At this point, this is guaranteed to succeed, so we can modify dst.
     dst->fRect = newRect;
 
-    // Since the only transforms that were allowed are scale and translate, the type
+    // Since the only transforms that were allowed are axis aligned, the type
     // remains unchanged.
     dst->fType = fType;
 
@@ -430,11 +428,37 @@
 
     // Now scale each corner
     SkScalar xScale = matrix.getScaleX();
+    SkScalar yScale = matrix.getScaleY();
+
+    // There is a rotation of 90 (Clockwise 90) or 270 (Counter clockwise 90).
+    // 180 degrees rotations are simply flipX with a flipY and would come under
+    // a scale transform.
+    if (!matrix.isScaleTranslate()) {
+        const bool isClockwise = matrix.getSkewX() < 0;
+
+        // The matrix location for scale changes if there is a rotation.
+        xScale = matrix.getSkewY() * (isClockwise ? 1 : -1);
+        yScale = matrix.getSkewX() * (isClockwise ? -1 : 1);
+
+        const int dir = isClockwise ? 3 : 1;
+        for (int i = 0; i < 4; ++i) {
+            const int src = (i + dir) >= 4 ? (i + dir) % 4 : (i + dir);
+            // Swap X and Y axis for the radii.
+            dst->fRadii[i].fX = fRadii[src].fY;
+            dst->fRadii[i].fY = fRadii[src].fX;
+        }
+    } else {
+        for (int i = 0; i < 4; ++i) {
+            dst->fRadii[i].fX = fRadii[i].fX;
+            dst->fRadii[i].fY = fRadii[i].fY;
+        }
+    }
+
     const bool flipX = xScale < 0;
     if (flipX) {
         xScale = -xScale;
     }
-    SkScalar yScale = matrix.getScaleY();
+
     const bool flipY = yScale < 0;
     if (flipY) {
         yScale = -yScale;
@@ -442,8 +466,8 @@
 
     // Scale the radii without respecting the flip.
     for (int i = 0; i < 4; ++i) {
-        dst->fRadii[i].fX = fRadii[i].fX * xScale;
-        dst->fRadii[i].fY = fRadii[i].fY * yScale;
+        dst->fRadii[i].fX *= xScale;
+        dst->fRadii[i].fY *= yScale;
     }
 
     // Now swap as necessary.
diff --git a/src/core/SkRasterPipeline.cpp b/src/core/SkRasterPipeline.cpp
index 50df7ac..eeb6c28 100644
--- a/src/core/SkRasterPipeline.cpp
+++ b/src/core/SkRasterPipeline.cpp
@@ -165,6 +165,7 @@
         case kARGB_4444_SkColorType:    this->append(load_4444,    ctx); break;
         case kRGBA_8888_SkColorType:    this->append(load_8888,    ctx); break;
         case kRGBA_1010102_SkColorType: this->append(load_1010102, ctx); break;
+        case kRGBA_F16Norm_SkColorType:
         case kRGBA_F16_SkColorType:     this->append(load_f16,     ctx); break;
         case kRGBA_F32_SkColorType:     this->append(load_f32,     ctx); break;
 
@@ -195,6 +196,7 @@
         case kARGB_4444_SkColorType:    this->append(load_4444_dst,    ctx); break;
         case kRGBA_8888_SkColorType:    this->append(load_8888_dst,    ctx); break;
         case kRGBA_1010102_SkColorType: this->append(load_1010102_dst, ctx); break;
+        case kRGBA_F16Norm_SkColorType:
         case kRGBA_F16_SkColorType:     this->append(load_f16_dst,     ctx); break;
         case kRGBA_F32_SkColorType:     this->append(load_f32_dst,     ctx); break;
 
@@ -225,6 +227,7 @@
         case kARGB_4444_SkColorType:    this->append(store_4444,    ctx); break;
         case kRGBA_8888_SkColorType:    this->append(store_8888,    ctx); break;
         case kRGBA_1010102_SkColorType: this->append(store_1010102, ctx); break;
+        case kRGBA_F16Norm_SkColorType:
         case kRGBA_F16_SkColorType:     this->append(store_f16,     ctx); break;
         case kRGBA_F32_SkColorType:     this->append(store_f32,     ctx); break;
 
@@ -247,6 +250,7 @@
 }
 
 void SkRasterPipeline::append_gamut_clamp_if_normalized(const SkImageInfo& dstInfo) {
+    // N.B. we _do_ clamp for kRGBA_F16Norm_SkColorType... because it's normalized.
     if (dstInfo.colorType() != kRGBA_F16_SkColorType &&
         dstInfo.colorType() != kRGBA_F32_SkColorType &&
         dstInfo.alphaType() == kPremul_SkAlphaType)
diff --git a/src/core/SkRasterPipeline.h b/src/core/SkRasterPipeline.h
index 74e915f..4d6cdbb 100644
--- a/src/core/SkRasterPipeline.h
+++ b/src/core/SkRasterPipeline.h
@@ -35,7 +35,7 @@
 #define SK_RASTER_PIPELINE_STAGES(M)                               \
     M(callback)                                                    \
     M(move_src_dst) M(move_dst_src)                                \
-    M(clamp_0) M(clamp_1) M(clamp_a) M(clamp_a_dst) M(clamp_gamut) \
+    M(clamp_0) M(clamp_1) M(clamp_a) M(clamp_gamut)                \
     M(unpremul) M(premul) M(premul_dst)                            \
     M(force_opaque) M(force_opaque_dst)                            \
     M(set_rgb) M(unbounded_set_rgb) M(swap_rb) M(swap_rb_dst)      \
@@ -52,9 +52,9 @@
     M(alpha_to_gray) M(alpha_to_gray_dst) M(luminance_to_alpha)    \
     M(bilerp_clamp_8888)                                           \
     M(store_u16_be)                                                \
-    M(load_rgba) M(store_rgba)                                     \
+    M(load_src) M(store_src) M(load_dst) M(store_dst)              \
     M(scale_u8) M(scale_565) M(scale_1_float)                      \
-    M( lerp_u8) M( lerp_565) M( lerp_1_float)                      \
+    M( lerp_u8) M( lerp_565) M( lerp_1_float) M(lerp_native)       \
     M(dstatop) M(dstin) M(dstout) M(dstover)                       \
     M(srcatop) M(srcin) M(srcout) M(srcover)                       \
     M(clear) M(modulate) M(multiply) M(plus_) M(screen) M(xor_)    \
diff --git a/src/core/SkRasterPipelineBlitter.cpp b/src/core/SkRasterPipelineBlitter.cpp
index 4effba1..f9cde3e 100644
--- a/src/core/SkRasterPipelineBlitter.cpp
+++ b/src/core/SkRasterPipelineBlitter.cpp
@@ -11,9 +11,9 @@
 #include "SkColor.h"
 #include "SkColorFilter.h"
 #include "SkColorSpacePriv.h"
-#include "SkColorSpaceXformer.h"
 #include "SkColorSpaceXformSteps.h"
 #include "SkOpts.h"
+#include "SkPaint.h"
 #include "SkRasterPipeline.h"
 #include "SkShader.h"
 #include "SkShaderBase.h"
@@ -81,8 +81,7 @@
                                          const SkPaint& paint,
                                          const SkMatrix& ctm,
                                          SkArenaAlloc* alloc) {
-    // For legacy/SkColorSpaceXformCanvas to keep working,
-    // we need to sometimes still need to distinguish null dstCS from sRGB.
+    // For legacy to keep working, we need to sometimes still distinguish null dstCS from sRGB.
 #if 0
     SkColorSpace* dstCS = dst.colorSpace() ? dst.colorSpace()
                                            : sk_srgb_singleton();
@@ -152,7 +151,10 @@
 
     // If there's a color filter it comes next.
     if (auto colorFilter = paint.getColorFilter()) {
-        colorFilter->appendStages(colorPipeline, dst.colorSpace(), alloc, is_opaque);
+        SkStageRec rec = {
+            colorPipeline, alloc, dst.colorType(), dst.colorSpace(), paint, nullptr, SkMatrix::I()
+        };
+        colorFilter->appendStages(rec, is_opaque);
         is_opaque = is_opaque && (colorFilter->getFlags() & SkColorFilter::kAlphaUnchanged_Flag);
     }
 
diff --git a/src/core/SkReadBuffer.cpp b/src/core/SkReadBuffer.cpp
index 9380cb1..9febf01 100644
--- a/src/core/SkReadBuffer.cpp
+++ b/src/core/SkReadBuffer.cpp
@@ -142,14 +142,23 @@
     return false;
 }
 
+const char* SkReadBuffer::readString(size_t* len) {
+    *len = this->readUInt();
+
+    // The string is len characters and a terminating \0.
+    const char* c_str = this->skipT<char>(*len+1);
+
+    if (this->validate(c_str && c_str[*len] == '\0')) {
+        return c_str;
+    }
+    return nullptr;
+}
+
 void SkReadBuffer::readString(SkString* string) {
-    const size_t len = this->readUInt();
-    // skip over the string + '\0'
-    if (const char* src = this->skipT<char>(len + 1)) {
-        if (this->validate(src[len] == 0)) {
-            string->set(src, len);
-            return;
-        }
+    size_t len;
+    if (const char* c_str = this->readString(&len)) {
+        string->set(c_str, len);
+        return;
     }
     string->reset();
 }
@@ -385,13 +394,13 @@
         }
         factory = fFactoryArray[index];
     } else {
-        if (this->peekByte()) {
+        if (this->peekByte() != 0) {
             // If the first byte is non-zero, the flattenable is specified by a string.
-            SkString name;
-            this->readString(&name);
-
-            factory = SkFlattenable::NameToFactory(name.c_str());
-            fFlattenableDict.set(fFlattenableDict.count() + 1, factory);
+            size_t ignored_length;
+            if (const char* name = this->readString(&ignored_length)) {
+                factory = SkFlattenable::NameToFactory(name);
+                fFlattenableDict.set(fFlattenableDict.count() + 1, factory);
+            }
         } else {
             // Read the index.  We are guaranteed that the first byte
             // is zeroed, so we must shift down a byte.
diff --git a/src/core/SkReadBuffer.h b/src/core/SkReadBuffer.h
index 61d065b..5c7aeef 100644
--- a/src/core/SkReadBuffer.h
+++ b/src/core/SkReadBuffer.h
@@ -98,7 +98,6 @@
     // peek
     uint8_t peekByte();
 
-    // strings -- the caller is responsible for freeing the string contents
     void readString(SkString* string);
 
     // common data structures
@@ -208,6 +207,8 @@
     SkFilterQuality checkFilterQuality();
 
 private:
+    const char* readString(size_t* length);
+
     void setInvalid();
     bool readArray(void* value, size_t size, size_t elementSize);
     void setMemory(const void*, size_t);
diff --git a/src/core/SkRecord.h b/src/core/SkRecord.h
index 088975b..feabec8 100644
--- a/src/core/SkRecord.h
+++ b/src/core/SkRecord.h
@@ -141,22 +141,20 @@
 
     // A typed pointer to some bytes in fAlloc.  visit() and mutate() allow polymorphic dispatch.
     struct Record {
-        // On 32-bit machines we store type in 4 bytes, followed by a pointer.  Simple.
-        // On 64-bit machines we store a pointer with the type slotted into two top (unused) bytes.
-        // FWIW, SkRecords::Type is tiny.  It can easily fit in one byte.
-        uint64_t fTypeAndPtr;
-        static const int kTypeShift = sizeof(void*) == 4 ? 32 : 48;
+        SkRecords::Type fType;
+        void*           fPtr;
 
         // Point this record to its data in fAlloc.  Returns ptr for convenience.
         template <typename T>
         T* set(T* ptr) {
-            fTypeAndPtr = ((uint64_t)T::kType) << kTypeShift | (uintptr_t)ptr;
+            fType = T::kType;
+            fPtr  = ptr;
             SkASSERT(this->ptr() == ptr && this->type() == T::kType);
             return ptr;
         }
 
-        SkRecords::Type type() const { return (SkRecords::Type)(fTypeAndPtr >> kTypeShift); }
-        void* ptr() const { return (void*)(fTypeAndPtr & ((1ull<<kTypeShift)-1)); }
+        SkRecords::Type type() const { return fType; }
+        void* ptr() const { return fPtr; }
 
         // Visit this record with functor F (see public API above).
         template <typename F>
diff --git a/src/core/SkRecordDraw.cpp b/src/core/SkRecordDraw.cpp
index 4dcc455..b6a3033 100644
--- a/src/core/SkRecordDraw.cpp
+++ b/src/core/SkRecordDraw.cpp
@@ -115,7 +115,6 @@
 
 DRAW(DrawImageRect, legacy_drawImageRect(r.image.get(), r.src, r.dst, r.paint, r.constraint));
 DRAW(DrawImageNine, drawImageNine(r.image.get(), r.center, r.dst, r.paint));
-DRAW(DrawImageSet, experimental_DrawImageSetV1(r.set.get(), r.count, r.quality, r.mode));
 DRAW(DrawOval, drawOval(r.oval, r.paint));
 DRAW(DrawPaint, drawPaint(r.paint));
 DRAW(DrawPath, drawPath(r.path, r.paint));
@@ -124,7 +123,6 @@
 DRAW(DrawPoints, drawPoints(r.mode, r.count, r.pts, r.paint));
 DRAW(DrawRRect, drawRRect(r.rrect, r.paint));
 DRAW(DrawRect, drawRect(r.rect, r.paint));
-DRAW(DrawEdgeAARect, experimental_DrawEdgeAARectV1(r.rect, r.aa, r.color, r.mode));
 DRAW(DrawRegion, drawRegion(r.region, r.paint));
 DRAW(DrawTextBlob, drawTextBlob(r.blob.get(), r.x, r.y, r.paint));
 DRAW(DrawAtlas, drawAtlas(r.atlas.get(),
@@ -132,6 +130,12 @@
 DRAW(DrawVertices, drawVertices(r.vertices, r.bones, r.boneCount, r.bmode, r.paint));
 DRAW(DrawShadowRec, private_draw_shadow_rec(r.path, r.rec));
 DRAW(DrawAnnotation, drawAnnotation(r.rect, r.key.c_str(), r.value.get()));
+
+DRAW(DrawEdgeAAQuad, experimental_DrawEdgeAAQuad(
+        r.rect, r.clip, r.aa, r.color, r.mode));
+DRAW(DrawEdgeAAImageSet, experimental_DrawEdgeAAImageSet(
+        r.set.get(), r.count, r.dstClips, r.preViewMatrices, r.paint, r.constraint));
+
 #undef DRAW
 
 template <> void Draw::draw(const DrawDrawable& r) {
@@ -357,8 +361,6 @@
     Bounds bounds(const NoOp&)  const { return Bounds::MakeEmpty(); }    // NoOps don't draw.
 
     Bounds bounds(const DrawRect& op) const { return this->adjustAndMap(op.rect, &op.paint); }
-    Bounds bounds(const DrawEdgeAARect& op) const { return this->adjustAndMap(op.rect, nullptr); }
-
     Bounds bounds(const DrawRegion& op) const {
         SkRect rect = SkRect::Make(op.region.getBounds());
         return this->adjustAndMap(rect, &op.paint);
@@ -387,13 +389,6 @@
     Bounds bounds(const DrawImageNine& op) const {
         return this->adjustAndMap(op.dst, op.paint);
     }
-    Bounds bounds(const DrawImageSet& op) const {
-        SkRect rect = SkRect::MakeEmpty();
-        for (int i = 0; i < op.count; ++i) {
-            rect.join(this->adjustAndMap(op.set[i].fDstRect, nullptr));
-        }
-        return rect;
-    }
     Bounds bounds(const DrawPath& op) const {
         return op.path.isInverseFillType() ? fCullRect
                                            : this->adjustAndMap(op.path.getBounds(), &op.paint);
@@ -452,6 +447,29 @@
     Bounds bounds(const DrawAnnotation& op) const {
         return this->adjustAndMap(op.rect, nullptr);
     }
+    Bounds bounds(const DrawEdgeAAQuad& op) const {
+        SkRect bounds = op.rect;
+        if (op.clip) {
+            bounds.setBounds(op.clip, 4);
+        }
+        return this->adjustAndMap(bounds, nullptr);
+    }
+    Bounds bounds(const DrawEdgeAAImageSet& op) const {
+        SkRect rect = SkRect::MakeEmpty();
+        int clipIndex = 0;
+        for (int i = 0; i < op.count; ++i) {
+            SkRect entryBounds = op.set[i].fDstRect;
+            if (op.set[i].fHasClip) {
+                entryBounds.setBounds(op.dstClips + clipIndex, 4);
+                clipIndex += 4;
+            }
+            if (op.set[i].fMatrixIndex >= 0) {
+                op.preViewMatrices[op.set[i].fMatrixIndex].mapRect(&entryBounds);
+            }
+            rect.join(this->adjustAndMap(entryBounds, nullptr));
+        }
+        return rect;
+    }
 
     // Returns true if rect was meaningfully adjusted for the effects of paint,
     // false if the paint could affect the rect in unknown ways.
diff --git a/src/core/SkRecorder.cpp b/src/core/SkRecorder.cpp
index bfe1067..ef43f81 100644
--- a/src/core/SkRecorder.cpp
+++ b/src/core/SkRecorder.cpp
@@ -151,11 +151,6 @@
     this->append<SkRecords::DrawRect>(paint, rect);
 }
 
-void SkRecorder::onDrawEdgeAARect(const SkRect& rect, SkCanvas::QuadAAFlags aa, SkColor color,
-                                  SkBlendMode mode) {
-    this->append<SkRecords::DrawEdgeAARect>(rect, aa, color, mode);
-}
-
 void SkRecorder::onDrawRegion(const SkRegion& region, const SkPaint& paint) {
     this->append<SkRecords::DrawRegion>(paint, region);
 }
@@ -258,15 +253,6 @@
            this->copy(lattice.fColors, flagCount), *lattice.fBounds, dst);
 }
 
-void SkRecorder::onDrawImageSet(const ImageSetEntry set[], int count, SkFilterQuality filterQuality,
-                                SkBlendMode mode) {
-    SkAutoTArray<ImageSetEntry> setCopy(count);
-    for (int i = 0; i < count; ++i) {
-        setCopy[i] = set[i];
-    }
-    this->append<SkRecords::DrawImageSet>(std::move(setCopy), count, filterQuality, mode);
-}
-
 void SkRecorder::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                                 const SkPaint& paint) {
     TRY_MINIRECORDER(drawTextBlob, blob, x, y, paint);
@@ -324,6 +310,28 @@
     this->append<SkRecords::DrawAnnotation>(rect, SkString(key), sk_ref_sp(value));
 }
 
+void SkRecorder::onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
+                                  QuadAAFlags aa, SkColor color, SkBlendMode mode) {
+    this->append<SkRecords::DrawEdgeAAQuad>(
+            rect, this->copy(clip, 4), aa, color, mode);
+}
+
+void SkRecorder::onDrawEdgeAAImageSet(const ImageSetEntry set[], int count,
+                                      const SkPoint dstClips[], const SkMatrix preViewMatrices[],
+                                      const SkPaint* paint, SrcRectConstraint constraint) {
+    int totalDstClipCount, totalMatrixCount;
+    SkCanvasPriv::GetDstClipAndMatrixCounts(set, count, &totalDstClipCount, &totalMatrixCount);
+
+    SkAutoTArray<ImageSetEntry> setCopy(count);
+    for (int i = 0; i < count; ++i) {
+        setCopy[i] = set[i];
+    }
+
+    this->append<SkRecords::DrawEdgeAAImageSet>(this->copy(paint), std::move(setCopy), count,
+            this->copy(dstClips, totalDstClipCount),
+            this->copy(preViewMatrices, totalMatrixCount), constraint);
+}
+
 void SkRecorder::onFlush() {
     this->append<SkRecords::Flush>();
 }
diff --git a/src/core/SkRecorder.h b/src/core/SkRecorder.h
index 6b4c7fe..a1d3a69 100644
--- a/src/core/SkRecorder.h
+++ b/src/core/SkRecorder.h
@@ -79,7 +79,6 @@
     void onDrawPaint(const SkPaint&) override;
     void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) override;
     void onDrawRect(const SkRect&, const SkPaint&) override;
-    void onDrawEdgeAARect(const SkRect&, SkCanvas::QuadAAFlags, SkColor, SkBlendMode) override;
     void onDrawRegion(const SkRegion&, const SkPaint&) override;
     void onDrawOval(const SkRect&, const SkPaint&) override;
     void onDrawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&) override;
@@ -99,8 +98,6 @@
                             const SkPaint*) override;
     void onDrawBitmapLattice(const SkBitmap&, const Lattice& lattice, const SkRect& dst,
                              const SkPaint*) override;
-    void onDrawImageSet(const SkCanvas::ImageSetEntry[], int count, SkFilterQuality,
-                        SkBlendMode) override;
     void onDrawVerticesObject(const SkVertices*, const SkVertices::Bone bones[], int boneCount,
                               SkBlendMode, const SkPaint&) override;
     void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[],
@@ -116,6 +113,11 @@
 
     void onDrawAnnotation(const SkRect&, const char[], SkData*) override;
 
+    void onDrawEdgeAAQuad(const SkRect&, const SkPoint[4], QuadAAFlags, SkColor,
+                          SkBlendMode) override;
+    void onDrawEdgeAAImageSet(const ImageSetEntry[], int count, const SkPoint[], const SkMatrix[],
+                              const SkPaint*, SrcRectConstraint) override;
+
     sk_sp<SkSurface> onNewSurface(const SkImageInfo&, const SkSurfaceProps&) override;
 
     void flushMiniRecorder();
diff --git a/src/core/SkRecords.h b/src/core/SkRecords.h
index 8f80924..6612a06 100644
--- a/src/core/SkRecords.h
+++ b/src/core/SkRecords.h
@@ -57,7 +57,6 @@
     M(DrawImageLattice)                                             \
     M(DrawImageRect)                                                \
     M(DrawImageNine)                                                \
-    M(DrawImageSet)                                                 \
     M(DrawDRRect)                                                   \
     M(DrawOval)                                                     \
     M(DrawPaint)                                                    \
@@ -67,13 +66,15 @@
     M(DrawPoints)                                                   \
     M(DrawRRect)                                                    \
     M(DrawRect)                                                     \
-    M(DrawEdgeAARect)                                               \
     M(DrawRegion)                                                   \
     M(DrawTextBlob)                                                 \
     M(DrawAtlas)                                                    \
     M(DrawVertices)                                                 \
     M(DrawShadowRec)                                                \
-    M(DrawAnnotation)
+    M(DrawAnnotation)                                               \
+    M(DrawEdgeAAQuad)                                               \
+    M(DrawEdgeAAImageSet)
+
 
 // Defines SkRecords::Type, an enum of all record types.
 #define ENUM(T) T##_Type,
@@ -259,11 +260,6 @@
         sk_sp<const SkImage> image;
         SkIRect center;
         SkRect dst);
-RECORD(DrawImageSet, kDraw_Tag|kHasImage_Tag,
-       SkAutoTArray<SkCanvas::ImageSetEntry> set;
-       int count;
-       SkFilterQuality quality;
-       SkBlendMode mode);
 RECORD(DrawOval, kDraw_Tag|kHasPaint_Tag,
         SkPaint paint;
         SkRect oval);
@@ -287,11 +283,6 @@
 RECORD(DrawRect, kDraw_Tag|kHasPaint_Tag,
         SkPaint paint;
         SkRect rect);
-RECORD(DrawEdgeAARect, kDraw_Tag,
-       SkRect rect;
-       SkCanvas::QuadAAFlags aa;
-       SkColor color;
-       SkBlendMode mode);
 RECORD(DrawRegion, kDraw_Tag|kHasPaint_Tag,
         SkPaint paint;
         SkRegion region);
@@ -328,6 +319,19 @@
        SkRect rect;
        SkString key;
        sk_sp<SkData> value);
+RECORD(DrawEdgeAAQuad, kDraw_Tag,
+       SkRect rect;
+       PODArray<SkPoint> clip;
+       SkCanvas::QuadAAFlags aa;
+       SkColor color;
+       SkBlendMode mode);
+RECORD(DrawEdgeAAImageSet, kDraw_Tag|kHasImage_Tag|kHasPaint_Tag,
+       Optional<SkPaint> paint;
+       SkAutoTArray<SkCanvas::ImageSetEntry> set;
+       int count;
+       PODArray<SkPoint> dstClips;
+       PODArray<SkMatrix> preViewMatrices;
+       SkCanvas::SrcRectConstraint constraint);
 #undef RECORD
 
 }  // namespace SkRecords
diff --git a/src/core/SkRemoteGlyphCache.cpp b/src/core/SkRemoteGlyphCache.cpp
index 6ece2c4..5abb37c 100644
--- a/src/core/SkRemoteGlyphCache.cpp
+++ b/src/core/SkRemoteGlyphCache.cpp
@@ -124,15 +124,19 @@
     }
 
     bool readDescriptor(SkAutoDescriptor* ad) {
-        uint32_t desc_length = 0u;
-        if (!read<uint32_t>(&desc_length)) return false;
+        uint32_t descLength = 0u;
+        if (!read<uint32_t>(&descLength)) return false;
+        if (descLength < sizeof(SkDescriptor)) return false;
+        if (descLength != SkAlign4(descLength)) return false;
 
-        auto* result = this->ensureAtLeast(desc_length, alignof(SkDescriptor));
+        auto* result = this->ensureAtLeast(descLength, alignof(SkDescriptor));
         if (!result) return false;
 
-        ad->reset(desc_length);
-        memcpy(ad->getDesc(), const_cast<const char*>(result), desc_length);
-        return true;
+        ad->reset(descLength);
+        memcpy(ad->getDesc(), const_cast<const char*>(result), descLength);
+
+        if (ad->getDesc()->getLength() > descLength) return false;
+        return ad->getDesc()->isValid();
     }
 
     const volatile void* read(size_t size, size_t alignment) {
@@ -143,8 +147,9 @@
     const volatile char* ensureAtLeast(size_t size, size_t alignment) {
         size_t padded = pad(fBytesRead, alignment);
 
-        // Not enough data
-        if (padded + size > fMemorySize) return nullptr;
+        // Not enough data.
+        if (padded > fMemorySize) return nullptr;
+        if (size > fMemorySize - padded) return nullptr;
 
         auto* result = fMemory + padded;
         fBytesRead = padded + size;
@@ -169,6 +174,10 @@
     auto* path = deserializer->read(pathSize, kPathAlignment);
     if (!path) return false;
 
+    // Don't overwrite the path if we already have one. We could have used a fallback if the
+    // glyph was missing earlier.
+    if (glyph->fPathData != nullptr) return true;
+
     return cache->initializePath(glyph, path, pathSize);
 }
 
@@ -193,43 +202,56 @@
 // -- TrackLayerDevice -----------------------------------------------------------------------------
 SkTextBlobCacheDiffCanvas::TrackLayerDevice::TrackLayerDevice(
         const SkIRect& bounds, const SkSurfaceProps& props, SkStrikeServer* server,
-        const SkTextBlobCacheDiffCanvas::Settings& settings)
-    : SkNoPixelsDevice(bounds, props)
-    , fStrikeServer(server)
-    , fSettings(settings)
-    , fPainter{props, kUnknown_SkColorType, SkScalerContextFlags::kFakeGammaAndBoostContrast} {
+        sk_sp<SkColorSpace> colorSpace, const SkTextBlobCacheDiffCanvas::Settings& settings)
+        : SkNoPixelsDevice(bounds, props, std::move(colorSpace))
+        , fStrikeServer(server)
+        , fSettings(settings)
+        , fPainter{props, kUnknown_SkColorType, imageInfo().colorSpace(), fStrikeServer} {
     SkASSERT(fStrikeServer);
 }
 
 SkBaseDevice* SkTextBlobCacheDiffCanvas::TrackLayerDevice::onCreateDevice(
         const CreateInfo& cinfo, const SkPaint*) {
     const SkSurfaceProps surfaceProps(this->surfaceProps().flags(), cinfo.fPixelGeometry);
-    return new TrackLayerDevice(this->getGlobalBounds(), surfaceProps, fStrikeServer, fSettings);
+    return new TrackLayerDevice(this->getGlobalBounds(), surfaceProps, fStrikeServer,
+                                cinfo.fInfo.refColorSpace(), fSettings);
 }
 
 void SkTextBlobCacheDiffCanvas::TrackLayerDevice::drawGlyphRunList(
         const SkGlyphRunList& glyphRunList) {
-    for (auto& glyphRun : glyphRunList) {
-        this->processGlyphRun(glyphRunList.origin(), glyphRun, glyphRunList.paint());
-    }
+
+    #if SK_SUPPORT_GPU
+    GrTextContext::Options options;
+    options.fMinDistanceFieldFontSize = fSettings.fMinDistanceFieldFontSize;
+    options.fMaxDistanceFieldFontSize = fSettings.fMaxDistanceFieldFontSize;
+    GrTextContext::SanitizeOptions(&options);
+
+    fPainter.processGlyphRunList(glyphRunList,
+                                 this->ctm(),
+                                 this->surfaceProps(),
+                                 fSettings.fContextSupportsDistanceFieldText,
+                                 options,
+                                 nullptr);
+    #endif  // SK_SUPPORT_GPU
+
 }
 
 // -- SkTextBlobCacheDiffCanvas -------------------------------------------------------------------
 SkTextBlobCacheDiffCanvas::Settings::Settings() = default;
 
-SkTextBlobCacheDiffCanvas::SkTextBlobCacheDiffCanvas(int width, int height,
-                                                     const SkMatrix& deviceMatrix,
-                                                     const SkSurfaceProps& props,
-                                                     SkStrikeServer* strikeServer,
-                                                     Settings settings)
-        : SkNoDrawCanvas{sk_make_sp<TrackLayerDevice>(SkIRect::MakeWH(width, height), props,
-                                                      strikeServer, settings)} {}
-
 SkTextBlobCacheDiffCanvas::SkTextBlobCacheDiffCanvas(
         int width, int height, const SkSurfaceProps& props,
         SkStrikeServer* strikeServer, Settings settings)
     : SkNoDrawCanvas{sk_make_sp<TrackLayerDevice>(
-            SkIRect::MakeWH(width, height), props, strikeServer, settings)} {}
+            SkIRect::MakeWH(width, height), props, strikeServer, nullptr, settings)} {}
+
+SkTextBlobCacheDiffCanvas::SkTextBlobCacheDiffCanvas(int width, int height,
+                                                     const SkSurfaceProps& props,
+                                                     SkStrikeServer* strikeServer,
+                                                     sk_sp<SkColorSpace> colorSpace,
+                                                     Settings settings)
+    : SkNoDrawCanvas{sk_make_sp<TrackLayerDevice>(SkIRect::MakeWH(width, height), props,
+                                                  strikeServer, std::move(colorSpace), settings)} {}
 
 SkTextBlobCacheDiffCanvas::~SkTextBlobCacheDiffCanvas() = default;
 
@@ -244,6 +266,7 @@
 
 void SkTextBlobCacheDiffCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                                                const SkPaint& paint) {
+    ///
     SkCanvas::onDrawTextBlob(blob, x, y, paint);
 }
 
@@ -309,68 +332,14 @@
     SkAutoDescriptor descStorage;
     auto desc = create_descriptor(paint, font, matrix, props, flags, &descStorage, effects);
 
-    // In cases where tracing is turned off, make sure not to get an unused function warning.
-    // Lambdaize the function.
-    TRACE_EVENT1("skia", "RecForDesc", "rec",
-            TRACE_STR_COPY(
-                    [desc](){
-                        auto ptr = desc->findEntry(kRec_SkDescriptorTag, nullptr);
-                        SkScalerContextRec rec;
-                        std::memcpy(&rec, ptr, sizeof(rec));
-                        return rec.dump();
-                    }().c_str()
-            )
-    );
+    return this->getOrCreateCache(*desc, *font.getTypefaceOrDefault(), *effects);
 
-    // Already locked.
-    if (fLockedDescs.find(desc) != fLockedDescs.end()) {
-        auto it = fRemoteGlyphStateMap.find(desc);
-        SkASSERT(it != fRemoteGlyphStateMap.end());
-        SkGlyphCacheState* cache = it->second.get();
-        cache->setFontAndEffects(font, SkScalerContextEffects{paint});
-        return cache;
-    }
+}
 
-    // Try to lock.
-    auto it = fRemoteGlyphStateMap.find(desc);
-    if (it != fRemoteGlyphStateMap.end()) {
-        SkGlyphCacheState* cache = it->second.get();
-        bool locked = fDiscardableHandleManager->lockHandle(it->second->discardableHandleId());
-        if (locked) {
-            fLockedDescs.insert(it->first);
-            cache->setFontAndEffects(font, SkScalerContextEffects{paint});
-            return cache;
-        }
-
-        // If the lock failed, the entry was deleted on the client. Remove our
-        // tracking.
-        fRemoteGlyphStateMap.erase(it);
-    }
-
-    auto* tf = font.getTypefaceOrDefault();
-    const SkFontID typefaceId = tf->uniqueID();
-    if (!fCachedTypefaces.contains(typefaceId)) {
-        fCachedTypefaces.add(typefaceId);
-        fTypefacesToSend.emplace_back(typefaceId, tf->countGlyphs(), tf->fontStyle(),
-                                      tf->isFixedPitch());
-    }
-
-    auto context = tf->createScalerContext(*effects, desc);
-
-    // Create a new cache state and insert it into the map.
-    auto newHandle = fDiscardableHandleManager->createHandle();
-    auto cacheState = skstd::make_unique<SkGlyphCacheState>(
-            *desc, std::move(context), newHandle);
-
-    auto* cacheStatePtr = cacheState.get();
-
-    fLockedDescs.insert(&cacheStatePtr->getDescriptor());
-    fRemoteGlyphStateMap[&cacheStatePtr->getDescriptor()] = std::move(cacheState);
-
-    checkForDeletedEntries();
-
-    cacheStatePtr->setFontAndEffects(font, SkScalerContextEffects{paint});
-    return cacheStatePtr;
+SkScopedStrike SkStrikeServer::findOrCreateScopedStrike(const SkDescriptor& desc,
+                                                        const SkScalerContextEffects& effects,
+                                                        const SkTypeface& typeface) {
+    return SkScopedStrike{this->getOrCreateCache(desc, typeface, effects)};
 }
 
 void SkStrikeServer::checkForDeletedEntries() {
@@ -385,6 +354,72 @@
     }
 }
 
+SkStrikeServer::SkGlyphCacheState* SkStrikeServer::getOrCreateCache(
+        const SkDescriptor& desc, const SkTypeface& typeface, SkScalerContextEffects effects) {
+
+    // In cases where tracing is turned off, make sure not to get an unused function warning.
+    // Lambdaize the function.
+    TRACE_EVENT1("skia", "RecForDesc", "rec",
+            TRACE_STR_COPY(
+                    [&desc](){
+                        auto ptr = desc.findEntry(kRec_SkDescriptorTag, nullptr);
+                        SkScalerContextRec rec;
+                        std::memcpy(&rec, ptr, sizeof(rec));
+                        return rec.dump();
+                    }().c_str()
+            )
+    );
+
+    // Already locked.
+    if (fLockedDescs.find(&desc) != fLockedDescs.end()) {
+        auto it = fRemoteGlyphStateMap.find(&desc);
+        SkASSERT(it != fRemoteGlyphStateMap.end());
+        SkGlyphCacheState* cache = it->second.get();
+        cache->setTypefaceAndEffects(&typeface, effects);
+        return cache;
+    }
+
+    // Try to lock.
+    auto it = fRemoteGlyphStateMap.find(&desc);
+    if (it != fRemoteGlyphStateMap.end()) {
+        SkGlyphCacheState* cache = it->second.get();
+        bool locked = fDiscardableHandleManager->lockHandle(it->second->discardableHandleId());
+        if (locked) {
+            fLockedDescs.insert(it->first);
+            cache->setTypefaceAndEffects(&typeface, effects);
+            return cache;
+        }
+
+        // If the lock failed, the entry was deleted on the client. Remove our
+        // tracking.
+        fRemoteGlyphStateMap.erase(it);
+    }
+
+    const SkFontID typefaceId = typeface.uniqueID();
+    if (!fCachedTypefaces.contains(typefaceId)) {
+        fCachedTypefaces.add(typefaceId);
+        fTypefacesToSend.emplace_back(typefaceId, typeface.countGlyphs(),
+                                      typeface.fontStyle(),
+                                      typeface.isFixedPitch());
+    }
+
+    auto context = typeface.createScalerContext(effects, &desc);
+
+    // Create a new cache state and insert it into the map.
+    auto newHandle = fDiscardableHandleManager->createHandle();
+    auto cacheState = skstd::make_unique<SkGlyphCacheState>(desc, std::move(context), newHandle);
+
+    auto* cacheStatePtr = cacheState.get();
+
+    fLockedDescs.insert(&cacheStatePtr->getDescriptor());
+    fRemoteGlyphStateMap[&cacheStatePtr->getDescriptor()] = std::move(cacheState);
+
+    checkForDeletedEntries();
+
+    cacheStatePtr->setTypefaceAndEffects(&typeface, effects);
+    return cacheStatePtr;
+}
+
 // -- SkGlyphCacheState ----------------------------------------------------------------------------
 SkStrikeServer::SkGlyphCacheState::SkGlyphCacheState(
         const SkDescriptor& descriptor,
@@ -455,8 +490,9 @@
     for (const auto& glyphID : fPendingGlyphImages) {
         SkGlyph glyph{glyphID};
         fContext->getMetrics(&glyph);
-        writeGlyph(&glyph, serializer);
+        SkASSERT(SkMask::IsValidFormat(glyph.fMaskFormat));
 
+        writeGlyph(&glyph, serializer);
         auto imageSize = glyph.computeImageSize();
         if (imageSize == 0u) continue;
 
@@ -472,6 +508,8 @@
     for (const auto& glyphID : fPendingGlyphPaths) {
         SkGlyph glyph{glyphID};
         fContext->getMetrics(&glyph);
+        SkASSERT(SkMask::IsValidFormat(glyph.fMaskFormat));
+
         writeGlyph(&glyph, serializer);
         writeGlyphPath(glyphID, serializer);
     }
@@ -479,33 +517,20 @@
     this->resetScalerContext();
 }
 
-const SkGlyph& SkStrikeServer::SkGlyphCacheState::findGlyph(SkPackedGlyphID glyphID) {
-    SkGlyph* glyphPtr = fGlyphMap.findOrNull(glyphID);
-    if (glyphPtr == nullptr) {
-        glyphPtr = fAlloc.make<SkGlyph>(glyphID);
-        fGlyphMap.set(glyphPtr);
-        this->ensureScalerContext();
-        fContext->getMetrics(glyphPtr);
-    }
-
-    return *glyphPtr;
-}
-
 void SkStrikeServer::SkGlyphCacheState::ensureScalerContext() {
     if (fContext == nullptr) {
-        auto tf = fFont->getTypefaceOrDefault();
-        fContext = tf->createScalerContext(fEffects, fDescriptor.getDesc());
+        fContext = fTypeface->createScalerContext(fEffects, fDescriptor.getDesc());
     }
 }
 
 void SkStrikeServer::SkGlyphCacheState::resetScalerContext() {
     fContext.reset();
-    fFont = nullptr;
+    fTypeface = nullptr;
 }
 
-void SkStrikeServer::SkGlyphCacheState::setFontAndEffects(
-        const SkFont& font, SkScalerContextEffects effects) {
-    fFont = &font;
+void SkStrikeServer::SkGlyphCacheState::setTypefaceAndEffects(
+        const SkTypeface* typeface, SkScalerContextEffects effects) {
+    fTypeface = typeface;
     fEffects = effects;
 }
 
@@ -513,22 +538,60 @@
     return SkStrikeCommon::PixelRounding(fIsSubpixel, fAxisAlignmentForHText);
 }
 
+// Note: In the split Renderer/GPU architecture, if getGlyphMetrics is called in the Renderer
+// process, then it will be called on the GPU process because they share the rendering code. Any
+// data that is created in the Renderer process needs to be found in the GPU process. By
+// implication, any cache-miss/glyph-creation data needs to be sent to the GPU.
 const SkGlyph& SkStrikeServer::SkGlyphCacheState::getGlyphMetrics(
         SkGlyphID glyphID, SkPoint position) {
     SkIPoint lookupPoint = SkStrikeCommon::SubpixelLookup(fAxisAlignmentForHText, position);
     SkPackedGlyphID packedGlyphID = fIsSubpixel ? SkPackedGlyphID{glyphID, lookupPoint}
                                                 : SkPackedGlyphID{glyphID};
 
-    return this->findGlyph(packedGlyphID);
+    // Check the cache for the glyph.
+    SkGlyph* glyphPtr = fGlyphMap.findOrNull(packedGlyphID);
+
+    // Has this glyph ever been seen before?
+    if (glyphPtr == nullptr) {
+
+        // Never seen before. Make a new glyph.
+        glyphPtr = fAlloc.make<SkGlyph>(packedGlyphID);
+        fGlyphMap.set(glyphPtr);
+        this->ensureScalerContext();
+        fContext->getMetrics(glyphPtr);
+
+        // Make sure to send the glyph to the GPU because we always send the image for a glyph.
+        fCachedGlyphImages.add(packedGlyphID);
+        fPendingGlyphImages.push_back(packedGlyphID);
+    }
+
+    return *glyphPtr;
 }
 
-bool SkStrikeServer::SkGlyphCacheState::hasImage(const SkGlyph& glyph) {
-    // If a glyph has width and height, it must have an image available to it.
-    return !glyph.isEmpty();
-}
+// Because the strike calls between the Renderer and the GPU are mirror images of each other, the
+// information needed to make the call in the Renderer needs to be sent to the GPU so it can also
+// make the call. If there is a path then it should be sent, and the path is queued to be sent and
+// true returned. Otherwise, false is returned signaling an empty glyph.
+//
+// A key reason for no path is the fact that the glyph is a color image or is a bitmap only
+// font.
+bool SkStrikeServer::SkGlyphCacheState::decideCouldDrawFromPath(const SkGlyph& glyph) {
 
-bool SkStrikeServer::SkGlyphCacheState::hasPath(const SkGlyph& glyph) {
-    return const_cast<SkGlyph&>(glyph).addPath(fContext.get(), &fAlloc) != nullptr;
+    // Check to see if we have processed this glyph for a path before.
+    if (glyph.fPathData == nullptr) {
+
+        // Never checked for a path before. Add the path now.
+        auto path = const_cast<SkGlyph&>(glyph).addPath(fContext.get(), &fAlloc);
+        if (path != nullptr) {
+
+            // A path was added make sure to send it to the GPU.
+            fCachedGlyphPaths.add(glyph.getPackedID());
+            fPendingGlyphPaths.push_back(glyph.getPackedID());
+            return true;
+        }
+    }
+
+    return glyph.path() != nullptr;
 }
 
 void SkStrikeServer::SkGlyphCacheState::writeGlyphPath(const SkPackedGlyphID& glyphID,
@@ -544,6 +607,43 @@
     path.writeToMemory(serializer->allocate(pathSize, kPathAlignment));
 }
 
+
+// This version of glyphMetrics only adds entries to result if their data need to be sent to the
+// GPU process.
+SkSpan<const SkGlyphPos> SkStrikeServer::SkGlyphCacheState::glyphMetrics(
+        const SkGlyphID glyphIDs[], const SkPoint positions[], size_t n, SkGlyphPos result[]) {
+
+    size_t glyphsToSendCount = 0;
+    for (size_t i = 0; i < n; i++) {
+        SkPoint glyphPos = positions[i];
+        SkGlyphID glyphID = glyphIDs[i];
+        SkIPoint lookupPoint = SkStrikeCommon::SubpixelLookup(fAxisAlignmentForHText, glyphPos);
+        SkPackedGlyphID packedGlyphID = fIsSubpixel ? SkPackedGlyphID{glyphID, lookupPoint}
+                                                    : SkPackedGlyphID{glyphID};
+
+        // Check the cache for the glyph.
+        SkGlyph* glyphPtr = fGlyphMap.findOrNull(packedGlyphID);
+
+        // Has this glyph ever been seen before?
+        if (glyphPtr == nullptr) {
+
+            // Never seen before. Make a new glyph.
+            glyphPtr = fAlloc.make<SkGlyph>(packedGlyphID);
+            fGlyphMap.set(glyphPtr);
+            this->ensureScalerContext();
+            fContext->getMetrics(glyphPtr);
+
+            result[glyphsToSendCount++] = {i, glyphPtr, glyphPos};
+
+            // Make sure to send the glyph to the GPU because we always send the image for a glyph.
+            fCachedGlyphImages.add(packedGlyphID);
+            fPendingGlyphImages.push_back(packedGlyphID);
+        }
+    }
+
+    return SkSpan<const SkGlyphPos>{result, glyphsToSendCount};
+}
+
 // SkStrikeClient -----------------------------------------
 class SkStrikeClient::DiscardableStrikePinner : public SkStrikePinner {
 public:
@@ -568,10 +668,10 @@
 
 SkStrikeClient::~SkStrikeClient() = default;
 
-#define READ_FAILURE                      \
-    {                                     \
-        SkDEBUGFAIL("Bad serialization"); \
-        return false;                     \
+#define READ_FAILURE                             \
+    {                                            \
+        SkDebugf("Bad font data serialization"); \
+        return false;                            \
     }
 
 static bool readGlyph(SkTLazy<SkGlyph>& glyph, Deserializer* deserializer) {
@@ -586,6 +686,8 @@
     if (!deserializer->read<int16_t>(&glyph->fLeft)) return false;
     if (!deserializer->read<int8_t>(&glyph->fForceBW)) return false;
     if (!deserializer->read<uint8_t>(&glyph->fMaskFormat)) return false;
+    if (!SkMask::IsValidFormat(glyph->fMaskFormat)) return false;
+
     return true;
 }
 
@@ -625,9 +727,10 @@
         if (!deserializer.read<SkFontMetrics>(&fontMetrics)) READ_FAILURE
 
         // Get the local typeface from remote fontID.
-        auto* tf = fRemoteFontIdToTypeface.find(spec.typefaceID)->get();
+        auto* tfPtr = fRemoteFontIdToTypeface.find(spec.typefaceID);
         // Received strikes for a typeface which doesn't exist.
-        if (!tf) READ_FAILURE
+        if (!tfPtr) READ_FAILURE
+        auto* tf = tfPtr->get();
 
         // Replace the ContextRec in the desc from the server to create the client
         // side descriptor.
@@ -669,9 +772,14 @@
             auto imageSize = glyph->computeImageSize();
             if (imageSize == 0u) continue;
 
-            auto* image = deserializer.read(imageSize, allocatedGlyph->formatAlignment());
+            auto* image = deserializer.read(imageSize, glyph->formatAlignment());
             if (!image) READ_FAILURE
-            strike->initializeImage(image, imageSize, allocatedGlyph);
+
+            // Don't overwrite the image if we already have one. We could have used a fallback if
+            // the glyph was missing earlier.
+            if (allocatedGlyph->fImage == nullptr) {
+                strike->initializeImage(image, imageSize, allocatedGlyph);
+            }
         }
 
         uint64_t glyphPathsCount = 0u;
diff --git a/src/core/SkRemoteGlyphCache.h b/src/core/SkRemoteGlyphCache.h
index 701375a..7b0dd18 100644
--- a/src/core/SkRemoteGlyphCache.h
+++ b/src/core/SkRemoteGlyphCache.h
@@ -22,6 +22,7 @@
 #include "SkNoDrawCanvas.h"
 #include "SkRefCnt.h"
 #include "SkSerialProcs.h"
+#include "SkStrikeInterface.h"
 #include "SkTypeface.h"
 
 class Serializer;
@@ -61,13 +62,14 @@
         int fMaxTextureSize = 0;
         size_t fMaxTextureBytes = 0u;
     };
+
     SkTextBlobCacheDiffCanvas(int width, int height, const SkSurfaceProps& props,
                               SkStrikeServer* strikeServer, Settings settings = Settings());
 
-    // TODO(khushalsagar): Remove once removed from chromium.
-    SkTextBlobCacheDiffCanvas(int width, int height, const SkMatrix& deviceMatrix,
-                              const SkSurfaceProps& props, SkStrikeServer* strikeserver,
+    SkTextBlobCacheDiffCanvas(int width, int height, const SkSurfaceProps& props,
+                              SkStrikeServer* strikeServer, sk_sp<SkColorSpace> colorSpace,
                               Settings settings = Settings());
+
     ~SkTextBlobCacheDiffCanvas() override;
 
 protected:
@@ -85,7 +87,7 @@
 using SkDiscardableHandleId = uint32_t;
 
 // This class is not thread-safe.
-class SK_API SkStrikeServer {
+class SK_API SkStrikeServer final : public SkStrikeCacheInterface {
 public:
     // An interface used by the server to create handles for pinning SkStrike
     // entries on the remote client.
@@ -112,7 +114,7 @@
     };
 
     explicit SkStrikeServer(DiscardableHandleManager* discardableHandleManager);
-    ~SkStrikeServer();
+    ~SkStrikeServer() override;
 
     // Serializes the typeface to be remoted using this server.
     sk_sp<SkData> serializeTypeface(SkTypeface*);
@@ -132,6 +134,10 @@
                                         SkScalerContextFlags flags,
                                         SkScalerContextEffects* effects);
 
+    SkScopedStrike findOrCreateScopedStrike(const SkDescriptor& desc,
+                                            const SkScalerContextEffects& effects,
+                                            const SkTypeface& typeface) override;
+
     void setMaxEntriesInDescriptorMapForTesting(size_t count) {
         fMaxEntriesInDescriptorMap = count;
     }
@@ -142,6 +148,10 @@
 
     void checkForDeletedEntries();
 
+    SkGlyphCacheState* getOrCreateCache(const SkDescriptor& desc,
+                                        const SkTypeface& typeface,
+                                        SkScalerContextEffects effects);
+
     SkDescriptorMap<std::unique_ptr<SkGlyphCacheState>> fRemoteGlyphStateMap;
     DiscardableHandleManager* const fDiscardableHandleManager;
     SkTHashSet<SkFontID> fCachedTypefaces;
diff --git a/src/core/SkRemoteGlyphCacheImpl.h b/src/core/SkRemoteGlyphCacheImpl.h
index 6b0fe70..c7f5acf 100644
--- a/src/core/SkRemoteGlyphCacheImpl.h
+++ b/src/core/SkRemoteGlyphCacheImpl.h
@@ -29,21 +29,26 @@
     bool isSubpixel() const { return fIsSubpixel; }
     SkAxisAlignment axisAlignmentForHText() const { return fAxisAlignmentForHText; }
 
-    const SkDescriptor& getDescriptor() {
+    const SkDescriptor& getDescriptor() const override {
         return *fDescriptor.getDesc();
     }
 
-    const SkGlyph& findGlyph(SkPackedGlyphID);
+    SkStrikeSpec strikeSpec() const override {
+        return SkStrikeSpec(this->getDescriptor(), *fTypeface, fEffects);
+    }
 
-    void setFontAndEffects(const SkFont& font, SkScalerContextEffects effects);
+    void setTypefaceAndEffects(const SkTypeface* typeface, SkScalerContextEffects effects);
 
     SkVector rounding() const override;
 
     const SkGlyph& getGlyphMetrics(SkGlyphID glyphID, SkPoint position) override;
 
-    bool hasImage(const SkGlyph& glyph) override;
+    SkSpan<const SkGlyphPos> glyphMetrics(
+            const SkGlyphID[], const SkPoint[], size_t n, SkGlyphPos result[]) override;
 
-    bool hasPath(const SkGlyph& glyph) override;
+    bool decideCouldDrawFromPath(const SkGlyph& glyph) override;
+
+    void onAboutToExitScope() override {}
 
 private:
     bool hasPendingGlyphs() const {
@@ -76,7 +81,7 @@
 
     // These fields are set everytime getOrCreateCache. This allows the code to maintain the
     // fContext as lazy as possible.
-    const SkFont* fFont{nullptr};
+    const SkTypeface* fTypeface{nullptr};
     SkScalerContextEffects fEffects;
 
     class GlyphMapHashTraits {
@@ -99,6 +104,7 @@
 class SkTextBlobCacheDiffCanvas::TrackLayerDevice : public SkNoPixelsDevice {
 public:
     TrackLayerDevice(const SkIRect& bounds, const SkSurfaceProps& props, SkStrikeServer* server,
+                     sk_sp<SkColorSpace> colorSpace,
                      const SkTextBlobCacheDiffCanvas::Settings& settings);
 
     SkBaseDevice* onCreateDevice(const CreateInfo& cinfo, const SkPaint*) override;
@@ -107,23 +113,6 @@
     void drawGlyphRunList(const SkGlyphRunList& glyphRunList) override;
 
 private:
-    void processGlyphRun(
-            const SkPoint& origin, const SkGlyphRun& glyphRun, const SkPaint& runPaint);
-
-    void processGlyphRunForMask(
-            const SkGlyphRun& glyphRun, const SkMatrix& runMatrix,
-            SkPoint origin, const SkPaint& paint);
-
-    void processGlyphRunForPaths(
-            const SkGlyphRun& glyphRun, const SkMatrix& runMatrix,
-            SkPoint origin, const SkPaint& paint);
-
-#if SK_SUPPORT_GPU
-    bool maybeProcessGlyphRunForDFT(
-            const SkGlyphRun& glyphRun, const SkMatrix& runMatrix,
-            SkPoint origin, const SkPaint& paint);
-#endif
-
     SkStrikeServer* const fStrikeServer;
     const SkTextBlobCacheDiffCanvas::Settings fSettings;
     SkGlyphRunListPainter fPainter;
diff --git a/src/core/SkScalerContext.cpp b/src/core/SkScalerContext.cpp
index 14aab10..28f4ca7 100644
--- a/src/core/SkScalerContext.cpp
+++ b/src/core/SkScalerContext.cpp
@@ -67,7 +67,7 @@
     }
 
     // TODO: remove CanonicalColor when we to fix up Chrome layout tests.
-    rec.setLuminanceColor(SkMaskGamma::CanonicalColor(lumColor));
+    rec.setLuminanceColor(lumColor);
 
     return rec;
 }
@@ -529,8 +529,6 @@
         } else {
             SkASSERT(SkMask::kARGB32_Format != origGlyph.fMaskFormat);
             SkASSERT(SkMask::kARGB32_Format != mask.fFormat);
-            // DAA would have over coverage issues with small stroke_and_fill (crbug.com/821353)
-            SkPathPriv::SetIsBadForDAA(devPath, fRec.fFrameWidth > 0 && fRec.fFrameWidth <= 2);
             generateMask(mask, devPath, fPreBlend);
         }
     }
@@ -815,6 +813,19 @@
     return kNone_SkAxisAlignment;
 }
 
+#ifdef SK_ENABLE_LEGACY_TEXT_COLOR
+void SkScalerContextRec::setLuminanceColor(SkColor c) {
+    fLumBits = SkColorSetRGB(SkColorGetR(c), SkColorGetG(c), SkColorGetB(c));
+}
+#else
+void SkScalerContextRec::setLuminanceColor(SkColor c) {
+    fLumBits = SkMaskGamma::CanonicalColor(
+            SkColorSetRGB(SkColorGetR(c), SkColorGetG(c), SkColorGetB(c)));
+}
+#endif
+
+
+
 ///////////////////////////////////////////////////////////////////////////////
 
 class SkScalerContext_Empty : public SkScalerContext {
diff --git a/src/core/SkScalerContext.h b/src/core/SkScalerContext.h
index 43e9dab..4ee91b5 100644
--- a/src/core/SkScalerContext.h
+++ b/src/core/SkScalerContext.h
@@ -19,6 +19,7 @@
 #include "SkMaskGamma.h"
 #include "SkMatrix.h"
 #include "SkPaint.h"
+#include "SkStrikeInterface.h"
 #include "SkSurfacePriv.h"
 #include "SkTypeface.h"
 #include "SkWriteBuffer.h"
@@ -37,18 +38,6 @@
     kFakeGammaAndBoostContrast = kFakeGamma | kBoostContrast,
 };
 
-struct SkScalerContextEffects {
-    SkScalerContextEffects() : fPathEffect(nullptr), fMaskFilter(nullptr) {}
-    SkScalerContextEffects(SkPathEffect* pe, SkMaskFilter* mf)
-        : fPathEffect(pe), fMaskFilter(mf) {}
-    explicit SkScalerContextEffects(const SkPaint& paint)
-        : fPathEffect(paint.getPathEffect())
-        , fMaskFilter(paint.getMaskFilter()) {}
-
-    SkPathEffect*   fPathEffect;
-    SkMaskFilter*   fMaskFilter;
-};
-
 enum SkAxisAlignment : uint32_t {
     kNone_SkAxisAlignment,
     kX_SkAxisAlignment,
@@ -96,6 +85,7 @@
     }
 
     SkScalar getContrast() const {
+        sk_ignore_unused_variable(fReservedAlign);
         return SkIntToScalar(fContrast) / ((1 << 8) - 1);
     }
     void setContrast(SkScalar c) {
@@ -206,20 +196,17 @@
         return static_cast<SkMask::Format>(fMaskFormat);
     }
 
-private:
-    // TODO: get rid of these bad friends.
-    friend class SkScalerContext;
-    friend class SkScalerContext_DW;
-
     SkColor getLuminanceColor() const {
         return fLumBits;
     }
 
     // setLuminanceColor forces the alpha to be 0xFF because the blitter that draws the glyph
     // will apply the alpha from the paint. Don't apply the alpha twice.
-    void setLuminanceColor(SkColor c) {
-        fLumBits = SkColorSetRGB(SkColorGetR(c), SkColorGetG(c), SkColorGetB(c));
-    }
+    void setLuminanceColor(SkColor c);
+
+private:
+    // TODO: remove
+    friend class SkScalerContext;
 };
 SK_END_REQUIRE_DENSE
 
@@ -407,7 +394,7 @@
     void forceOffGenerateImageFromPath() { fGenerateImageFromPath = false; }
 
 private:
-    friend class SkRandomScalerContext; // For debug purposes
+    friend class RandomScalerContext;  // For debug purposes
 
     static SkScalerContextRec PreprocessRec(const SkTypeface& typeface,
                                             const SkScalerContextEffects& effects,
diff --git a/src/core/SkScan.cpp b/src/core/SkScan.cpp
index c2c894a..c7a0614 100644
--- a/src/core/SkScan.cpp
+++ b/src/core/SkScan.cpp
@@ -13,9 +13,6 @@
 std::atomic<bool> gSkUseAnalyticAA{true};
 std::atomic<bool> gSkForceAnalyticAA{false};
 
-std::atomic<bool> gSkUseDeltaAA{false};
-std::atomic<bool> gSkForceDeltaAA{false};
-
 static inline void blitrect(SkBlitter* blitter, const SkIRect& r) {
     blitter->blitRect(r.fLeft, r.fTop, r.width(), r.height());
 }
diff --git a/src/core/SkScan.h b/src/core/SkScan.h
index 75fd2ac..92f6d1c 100644
--- a/src/core/SkScan.h
+++ b/src/core/SkScan.h
@@ -9,7 +9,6 @@
 #ifndef SkScan_DEFINED
 #define SkScan_DEFINED
 
-#include "SkCoverageDelta.h"
 #include "SkFixed.h"
 #include "SkRect.h"
 #include <atomic>
@@ -24,8 +23,6 @@
 */
 typedef SkIRect SkXRect;
 
-extern std::atomic<bool> gSkUseDeltaAA;
-extern std::atomic<bool> gSkForceDeltaAA;
 extern std::atomic<bool> gSkUseAnalyticAA;
 extern std::atomic<bool> gSkForceAnalyticAA;
 
@@ -53,7 +50,7 @@
     static void AntiFillRect(const SkRect&, const SkRasterClip&, SkBlitter*);
     static void AntiFillXRect(const SkXRect&, const SkRasterClip&, SkBlitter*);
     static void FillPath(const SkPath&, const SkRasterClip&, SkBlitter*);
-    static void AntiFillPath(const SkPath&, const SkRasterClip&, SkBlitter*, SkDAARecord*);
+    static void AntiFillPath(const SkPath&, const SkRasterClip&, SkBlitter*);
     static void FrameRect(const SkRect&, const SkPoint& strokeSize,
                           const SkRasterClip&, SkBlitter*);
     static void AntiFrameRect(const SkRect&, const SkPoint& strokeSize,
@@ -73,10 +70,6 @@
     // Needed by do_fill_path in SkScanPriv.h
     static void FillPath(const SkPath&, const SkRegion& clip, SkBlitter*);
 
-    // We have this instead of a default nullptr parameter because of function pointer match.
-    static void AntiFillPath(const SkPath& path, const SkRasterClip& rc, SkBlitter* blitter) {
-        AntiFillPath(path, rc, blitter, nullptr);
-    }
 private:
     friend class SkAAClip;
     friend class SkRegion;
@@ -86,8 +79,7 @@
     static void FillRect(const SkRect&, const SkRegion* clip, SkBlitter*);
     static void AntiFillRect(const SkRect&, const SkRegion* clip, SkBlitter*);
     static void AntiFillXRect(const SkXRect&, const SkRegion*, SkBlitter*);
-    static void AntiFillPath(const SkPath&, const SkRegion& clip, SkBlitter*,
-                             bool forceRLE = false, SkDAARecord* daaRecord = nullptr);
+    static void AntiFillPath(const SkPath&, const SkRegion& clip, SkBlitter*, bool forceRLE);
     static void FillTriangle(const SkPoint pts[], const SkRegion*, SkBlitter*);
 
     static void AntiFrameRect(const SkRect&, const SkPoint& strokeSize,
@@ -96,8 +88,6 @@
     static void AntiHairLineRgn(const SkPoint[], int count, const SkRegion*, SkBlitter*);
     static void AAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& pathIR,
                             const SkIRect& clipBounds, bool forceRLE);
-    static void DAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& pathIR,
-                            const SkIRect& clipBounds, bool forceRLE, SkDAARecord* daaRecord);
     static void SAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& pathIR,
                             const SkIRect& clipBounds, bool forceRLE);
 };
diff --git a/src/core/SkScan_AAAPath.cpp b/src/core/SkScan_AAAPath.cpp
index e5209a5..fd4adb9 100644
--- a/src/core/SkScan_AAAPath.cpp
+++ b/src/core/SkScan_AAAPath.cpp
@@ -26,13 +26,11 @@
 #include <utility>
 
 #if defined(SK_DISABLE_AAA)
-void SkScan::AAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& ir,
-                         const SkIRect& clipBounds, bool forceRLE) {
+void SkScan::AAAFillPath(const SkPath&, SkBlitter*, const SkIRect&, const SkIRect&, bool) {
     SkDEBUGFAIL("AAA Disabled");
     return;
 }
 #else
-///////////////////////////////////////////////////////////////////////////////
 
 /*
 
@@ -92,15 +90,13 @@
 
 */
 
-///////////////////////////////////////////////////////////////////////////////
-
-static inline void addAlpha(SkAlpha* alpha, SkAlpha delta) {
-    SkASSERT(*alpha + (int)delta <= 256);
-    *alpha = SkAlphaRuns::CatchOverflow(*alpha + (int)delta);
+static void add_alpha(SkAlpha* alpha, SkAlpha delta) {
+    SkASSERT(*alpha + delta <= 256);
+    *alpha = SkAlphaRuns::CatchOverflow(*alpha + delta);
 }
 
-static inline void safelyAddAlpha(SkAlpha* alpha, SkAlpha delta) {
-    *alpha = SkTMin(0xFF, *alpha + (int)delta);
+static void safely_add_alpha(SkAlpha* alpha, SkAlpha delta) {
+    *alpha = SkTMin(0xFF, *alpha + delta);
 }
 
 class AdditiveBlitter : public SkBlitter {
@@ -110,8 +106,8 @@
     virtual SkBlitter* getRealBlitter(bool forceRealBlitter = false) = 0;
 
     virtual void blitAntiH(int x, int y, const SkAlpha antialias[], int len) = 0;
-    virtual void blitAntiH(int x, int y, const SkAlpha alpha) = 0;
-    virtual void blitAntiH(int x, int y, int width, const SkAlpha alpha) = 0;
+    virtual void blitAntiH(int x, int y, const SkAlpha alpha)                = 0;
+    virtual void blitAntiH(int x, int y, int width, const SkAlpha alpha)     = 0;
 
     void blitAntiH(int x, int y, const SkAlpha antialias[], const int16_t runs[]) override {
         SkDEBUGFAIL("Please call real blitter's blitAntiH instead.");
@@ -129,8 +125,8 @@
         SkDEBUGFAIL("Please call real blitter's blitRect instead.");
     }
 
-    void blitAntiRect(int x, int y, int width, int height,
-                      SkAlpha leftAlpha, SkAlpha rightAlpha) override {
+    void blitAntiRect(int x, int y, int width, int height, SkAlpha leftAlpha, SkAlpha rightAlpha)
+            override {
         SkDEBUGFAIL("Please call real blitter's blitAntiRect instead.");
     }
 
@@ -144,11 +140,11 @@
 // We need this mask blitter because it significantly accelerates small path filling.
 class MaskAdditiveBlitter : public AdditiveBlitter {
 public:
-    MaskAdditiveBlitter(SkBlitter* realBlitter, const SkIRect& ir, const SkIRect& clipBounds,
-            bool isInverse);
-    ~MaskAdditiveBlitter() override {
-        fRealBlitter->blitMask(fMask, fClipRect);
-    }
+    MaskAdditiveBlitter(SkBlitter*     realBlitter,
+                        const SkIRect& ir,
+                        const SkIRect& clipBounds,
+                        bool           isInverse);
+    ~MaskAdditiveBlitter() override { fRealBlitter->blitMask(fMask, fClipRect); }
 
     // Most of the time, we still consider this mask blitter as the real blitter
     // so we can accelerate blitRect and others. But sometimes we want to return
@@ -166,15 +162,15 @@
     void blitAntiH(int x, int y, int width, const SkAlpha alpha) override;
     void blitV(int x, int y, int height, SkAlpha alpha) override;
     void blitRect(int x, int y, int width, int height) override;
-    void blitAntiRect(int x, int y, int width, int height,
-                      SkAlpha leftAlpha, SkAlpha rightAlpha) override;
+    void blitAntiRect(int x, int y, int width, int height, SkAlpha leftAlpha, SkAlpha rightAlpha)
+            override;
 
     // The flush is only needed for RLE (RunBasedAdditiveBlitter)
     void flush_if_y_changed(SkFixed y, SkFixed nextY) override {}
 
     int getWidth() override { return fClipRect.width(); }
 
-    static bool canHandleRect(const SkIRect& bounds) {
+    static bool CanHandleRect(const SkIRect& bounds) {
         int width = bounds.width();
         if (width > MaskAdditiveBlitter::kMAX_WIDTH) {
             return false;
@@ -188,9 +184,9 @@
     }
 
     // Return a pointer where pointer[x] corresonds to the alpha of (x, y)
-    inline uint8_t* getRow(int y) {
+    uint8_t* getRow(int y) {
         if (y != fY) {
-            fY = y;
+            fY   = y;
             fRow = fMask.fImage + (y - fMask.fBounds.fTop) * fMask.fRowBytes - fMask.fBounds.fLeft;
         }
         return fRow;
@@ -198,32 +194,34 @@
 
 private:
     // so we don't try to do very wide things, where the RLE blitter would be faster
-    static const int kMAX_WIDTH = 32;
+    static const int kMAX_WIDTH   = 32;
     static const int kMAX_STORAGE = 1024;
 
-    SkBlitter*  fRealBlitter;
-    SkMask      fMask;
-    SkIRect     fClipRect;
+    SkBlitter* fRealBlitter;
+    SkMask     fMask;
+    SkIRect    fClipRect;
     // we add 2 because we can write 1 extra byte at either end due to precision error
-    uint32_t    fStorage[(kMAX_STORAGE >> 2) + 2];
+    uint32_t fStorage[(kMAX_STORAGE >> 2) + 2];
 
-    uint8_t*    fRow;
-    int         fY;
+    uint8_t* fRow;
+    int      fY;
 };
 
-MaskAdditiveBlitter::MaskAdditiveBlitter(
-        SkBlitter* realBlitter, const SkIRect& ir, const SkIRect& clipBounds, bool isInverse) {
-    SkASSERT(canHandleRect(ir));
+MaskAdditiveBlitter::MaskAdditiveBlitter(SkBlitter*     realBlitter,
+                                         const SkIRect& ir,
+                                         const SkIRect& clipBounds,
+                                         bool           isInverse) {
+    SkASSERT(CanHandleRect(ir));
     SkASSERT(!isInverse);
 
     fRealBlitter = realBlitter;
 
-    fMask.fImage    = (uint8_t*)fStorage + 1; // There's 1 extra byte at either end of fStorage
+    fMask.fImage    = (uint8_t*)fStorage + 1;  // There's 1 extra byte at either end of fStorage
     fMask.fBounds   = ir;
     fMask.fRowBytes = ir.width();
     fMask.fFormat   = SkMask::kA8_Format;
 
-    fY = ir.fTop - 1;
+    fY   = ir.fTop - 1;
     fRow = nullptr;
 
     fClipRect = ir;
@@ -240,15 +238,15 @@
 }
 
 void MaskAdditiveBlitter::blitAntiH(int x, int y, const SkAlpha alpha) {
-    SkASSERT(x >= fMask.fBounds.fLeft -1);
-    addAlpha(&this->getRow(y)[x], alpha);
+    SkASSERT(x >= fMask.fBounds.fLeft - 1);
+    add_alpha(&this->getRow(y)[x], alpha);
 }
 
 void MaskAdditiveBlitter::blitAntiH(int x, int y, int width, const SkAlpha alpha) {
-    SkASSERT(x >= fMask.fBounds.fLeft -1);
+    SkASSERT(x >= fMask.fBounds.fLeft - 1);
     uint8_t* row = this->getRow(y);
     for (int i = 0; i < width; ++i) {
-        addAlpha(&row[x + i], alpha);
+        add_alpha(&row[x + i], alpha);
     }
 }
 
@@ -256,7 +254,7 @@
     if (alpha == 0) {
         return;
     }
-    SkASSERT(x >= fMask.fBounds.fLeft -1);
+    SkASSERT(x >= fMask.fBounds.fLeft - 1);
     // This must be called as if this is a real blitter.
     // So we directly set alpha rather than adding it.
     uint8_t* row = this->getRow(y);
@@ -267,7 +265,7 @@
 }
 
 void MaskAdditiveBlitter::blitRect(int x, int y, int width, int height) {
-    SkASSERT(x >= fMask.fBounds.fLeft -1);
+    SkASSERT(x >= fMask.fBounds.fLeft - 1);
     // This must be called as if this is a real blitter.
     // So we directly set alpha rather than adding it.
     uint8_t* row = this->getRow(y);
@@ -277,8 +275,12 @@
     }
 }
 
-void MaskAdditiveBlitter::blitAntiRect(int x, int y, int width, int height,
-        SkAlpha leftAlpha, SkAlpha rightAlpha) {
+void MaskAdditiveBlitter::blitAntiRect(int     x,
+                                       int     y,
+                                       int     width,
+                                       int     height,
+                                       SkAlpha leftAlpha,
+                                       SkAlpha rightAlpha) {
     blitV(x, y, height, leftAlpha);
     blitV(x + 1 + width, y, height, rightAlpha);
     blitRect(x + 1, y, width, height);
@@ -286,17 +288,20 @@
 
 class RunBasedAdditiveBlitter : public AdditiveBlitter {
 public:
-    RunBasedAdditiveBlitter(SkBlitter* realBlitter, const SkIRect& ir, const SkIRect& clipBounds,
-            bool isInverse);
-    ~RunBasedAdditiveBlitter() override;
+    RunBasedAdditiveBlitter(SkBlitter*     realBlitter,
+                            const SkIRect& ir,
+                            const SkIRect& clipBounds,
+                            bool           isInverse);
 
-    SkBlitter* getRealBlitter(bool forceRealBlitter) override;
+    ~RunBasedAdditiveBlitter() override { this->flush(); }
+
+    SkBlitter* getRealBlitter(bool forceRealBlitter) override { return fRealBlitter; }
 
     void blitAntiH(int x, int y, const SkAlpha antialias[], int len) override;
     void blitAntiH(int x, int y, const SkAlpha alpha) override;
     void blitAntiH(int x, int y, int width, const SkAlpha alpha) override;
 
-    int getWidth() override;
+    int getWidth() override { return fWidth; }
 
     void flush_if_y_changed(SkFixed y, SkFixed nextY) override {
         if (SkFixedFloorToInt(y) != SkFixedFloorToInt(nextY)) {
@@ -307,14 +312,10 @@
 protected:
     SkBlitter* fRealBlitter;
 
-    /// Current y coordinate
-    int         fCurrY;
-    /// Widest row of region to be blitted
-    int         fWidth;
-    /// Leftmost x coordinate in any row
-    int         fLeft;
-    /// Initial y coordinate (top of bounds).
-    int         fTop;
+    int fCurrY;  // Current y coordinate.
+    int fWidth;  // Widest row of region to be blitted
+    int fLeft;   // Leftmost x coordinate in any row
+    int fTop;    // Initial y coordinate (top of bounds)
 
     // The next three variables are used to track a circular buffer that
     // contains the values used in SkAlphaRuns. These variables should only
@@ -325,38 +326,29 @@
     int         fCurrentRun;
     SkAlphaRuns fRuns;
 
-    int         fOffsetX;
+    int fOffsetX;
 
-    inline bool check(int x, int width) const {
-        #ifdef SK_DEBUG
-        if (x < 0 || x + width > fWidth) {
-            // SkDebugf("Ignore x = %d, width = %d\n", x, width);
-        }
-        #endif
-        return (x >= 0 && x + width <= fWidth);
-    }
+    bool check(int x, int width) const { return x >= 0 && x + width <= fWidth; }
 
     // extra one to store the zero at the end
-    inline int getRunsSz() const { return (fWidth + 1 + (fWidth + 2)/2) * sizeof(int16_t); }
+    int getRunsSz() const { return (fWidth + 1 + (fWidth + 2) / 2) * sizeof(int16_t); }
 
     // This function updates the fRuns variable to point to the next buffer space
     // with adequate storage for a SkAlphaRuns. It mostly just advances fCurrentRun
     // and resets fRuns to point to an empty scanline.
-    inline void advanceRuns() {
+    void advanceRuns() {
         const size_t kRunsSz = this->getRunsSz();
-        fCurrentRun = (fCurrentRun + 1) % fRunsToBuffer;
-        fRuns.fRuns = reinterpret_cast<int16_t*>(
-            reinterpret_cast<uint8_t*>(fRunsBuffer) + fCurrentRun * kRunsSz);
-        fRuns.fAlpha = reinterpret_cast<SkAlpha*>(fRuns.fRuns + fWidth + 1);
+        fCurrentRun          = (fCurrentRun + 1) % fRunsToBuffer;
+        fRuns.fRuns          = reinterpret_cast<int16_t*>(reinterpret_cast<uint8_t*>(fRunsBuffer) +
+                                                 fCurrentRun * kRunsSz);
+        fRuns.fAlpha         = reinterpret_cast<SkAlpha*>(fRuns.fRuns + fWidth + 1);
         fRuns.reset(fWidth);
     }
 
     // Blitting 0xFF and 0 is much faster so we snap alphas close to them
-    inline SkAlpha snapAlpha(SkAlpha alpha) {
-        return alpha > 247 ? 0xFF : alpha < 8 ? 0 : alpha;
-    }
+    SkAlpha snapAlpha(SkAlpha alpha) { return alpha > 247 ? 0xFF : alpha < 8 ? 0x00 : alpha; }
 
-    inline void flush() {
+    void flush() {
         if (fCurrY >= fTop) {
             SkASSERT(fCurrentRun < fRunsToBuffer);
             for (int x = 0; fRuns.fRuns[x]; x += fRuns.fRuns[x]) {
@@ -373,7 +365,7 @@
         }
     }
 
-    inline void checkY(int y) {
+    void checkY(int y) {
         if (y != fCurrY) {
             this->flush();
             fCurrY = y;
@@ -381,14 +373,16 @@
     }
 };
 
-RunBasedAdditiveBlitter::RunBasedAdditiveBlitter(
-        SkBlitter* realBlitter, const SkIRect& ir, const SkIRect& clipBounds, bool isInverse) {
+RunBasedAdditiveBlitter::RunBasedAdditiveBlitter(SkBlitter*     realBlitter,
+                                                 const SkIRect& ir,
+                                                 const SkIRect& clipBounds,
+                                                 bool           isInverse) {
     fRealBlitter = realBlitter;
 
     SkIRect sectBounds;
     if (isInverse) {
         // We use the clip bounds instead of the ir, since we may be asked to
-        //draw outside of the rect when we're a inverse filltype
+        // draw outside of the rect when we're a inverse filltype
         sectBounds = clipBounds;
     } else {
         if (!sectBounds.intersect(ir, clipBounds)) {
@@ -396,31 +390,23 @@
         }
     }
 
-    const int left = sectBounds.left();
+    const int left  = sectBounds.left();
     const int right = sectBounds.right();
 
-    fLeft = left;
+    fLeft  = left;
     fWidth = right - left;
-    fTop = sectBounds.top();
+    fTop   = sectBounds.top();
     fCurrY = fTop - 1;
 
     fRunsToBuffer = realBlitter->requestRowsPreserved();
-    fRunsBuffer = realBlitter->allocBlitMemory(fRunsToBuffer * this->getRunsSz());
-    fCurrentRun = -1;
+    fRunsBuffer   = realBlitter->allocBlitMemory(fRunsToBuffer * this->getRunsSz());
+    fCurrentRun   = -1;
 
     this->advanceRuns();
 
     fOffsetX = 0;
 }
 
-RunBasedAdditiveBlitter::~RunBasedAdditiveBlitter() {
-    this->flush();
-}
-
-SkBlitter* RunBasedAdditiveBlitter::getRealBlitter(bool forceRealBlitter) {
-    return fRealBlitter;
-}
-
 void RunBasedAdditiveBlitter::blitAntiH(int x, int y, const SkAlpha antialias[], int len) {
     checkY(y);
     x -= fLeft;
@@ -437,18 +423,19 @@
         fOffsetX = 0;
     }
 
-    fOffsetX = fRuns.add(x, 0, len, 0, 0, fOffsetX); // Break the run
+    fOffsetX = fRuns.add(x, 0, len, 0, 0, fOffsetX);  // Break the run
     for (int i = 0; i < len; i += fRuns.fRuns[x + i]) {
         for (int j = 1; j < fRuns.fRuns[x + i]; j++) {
-            fRuns.fRuns[x + i + j] = 1;
+            fRuns.fRuns[x + i + j]  = 1;
             fRuns.fAlpha[x + i + j] = fRuns.fAlpha[x + i];
         }
         fRuns.fRuns[x + i] = 1;
     }
     for (int i = 0; i < len; ++i) {
-        addAlpha(&fRuns.fAlpha[x + i], antialias[i]);
+        add_alpha(&fRuns.fAlpha[x + i], antialias[i]);
     }
 }
+
 void RunBasedAdditiveBlitter::blitAntiH(int x, int y, const SkAlpha alpha) {
     checkY(y);
     x -= fLeft;
@@ -475,14 +462,15 @@
     }
 }
 
-int RunBasedAdditiveBlitter::getWidth() { return fWidth; }
-
 // This exists specifically for concave path filling.
 // In those cases, we can easily accumulate alpha greater than 0xFF.
 class SafeRLEAdditiveBlitter : public RunBasedAdditiveBlitter {
 public:
-    SafeRLEAdditiveBlitter(SkBlitter* realBlitter, const SkIRect& ir, const SkIRect& clipBounds,
-            bool isInverse) : RunBasedAdditiveBlitter(realBlitter, ir, clipBounds, isInverse) {}
+    SafeRLEAdditiveBlitter(SkBlitter*     realBlitter,
+                           const SkIRect& ir,
+                           const SkIRect& clipBounds,
+                           bool           isInverse)
+            : RunBasedAdditiveBlitter(realBlitter, ir, clipBounds, isInverse) {}
 
     void blitAntiH(int x, int y, const SkAlpha antialias[], int len) override;
     void blitAntiH(int x, int y, const SkAlpha alpha) override;
@@ -505,16 +493,16 @@
         fOffsetX = 0;
     }
 
-    fOffsetX = fRuns.add(x, 0, len, 0, 0, fOffsetX); // Break the run
+    fOffsetX = fRuns.add(x, 0, len, 0, 0, fOffsetX);  // Break the run
     for (int i = 0; i < len; i += fRuns.fRuns[x + i]) {
         for (int j = 1; j < fRuns.fRuns[x + i]; j++) {
-            fRuns.fRuns[x + i + j] = 1;
+            fRuns.fRuns[x + i + j]  = 1;
             fRuns.fAlpha[x + i + j] = fRuns.fAlpha[x + i];
         }
         fRuns.fRuns[x + i] = 1;
     }
     for (int i = 0; i < len; ++i) {
-        safelyAddAlpha(&fRuns.fAlpha[x + i], antialias[i]);
+        safely_add_alpha(&fRuns.fAlpha[x + i], antialias[i]);
     }
 }
 
@@ -529,7 +517,7 @@
     if (check(x, 1)) {
         // Break the run
         fOffsetX = fRuns.add(x, 0, 1, 0, 0, fOffsetX);
-        safelyAddAlpha(&fRuns.fAlpha[x], alpha);
+        safely_add_alpha(&fRuns.fAlpha[x], alpha);
     }
 }
 
@@ -544,136 +532,165 @@
     if (check(x, width)) {
         // Break the run
         fOffsetX = fRuns.add(x, 0, width, 0, 0, fOffsetX);
-        for(int i = x; i < x + width; i += fRuns.fRuns[i]) {
-            safelyAddAlpha(&fRuns.fAlpha[i], alpha);
+        for (int i = x; i < x + width; i += fRuns.fRuns[i]) {
+            safely_add_alpha(&fRuns.fAlpha[i], alpha);
         }
     }
 }
 
-///////////////////////////////////////////////////////////////////////////////
-
 // Return the alpha of a trapezoid whose height is 1
-static inline SkAlpha trapezoidToAlpha(SkFixed l1, SkFixed l2) {
+static SkAlpha trapezoid_to_alpha(SkFixed l1, SkFixed l2) {
     SkASSERT(l1 >= 0 && l2 >= 0);
-    return (l1 + l2) >> 9;
-}
-
-// The alpha of right-triangle (a, a*b), in 16 bits
-static inline SkFixed partialTriangleToAlpha16(SkFixed a, SkFixed b) {
-    SkASSERT(a <= SK_Fixed1);
-    // SkFixedMul(SkFixedMul(a, a), b) >> 1
-    // return ((((a >> 8) * (a >> 8)) >> 8) * (b >> 8)) >> 1;
-    return (a >> 11) * (a >> 11) * (b >> 11);
+    SkFixed area = (l1 + l2) / 2;
+    return SkTo<SkAlpha>(area >> 8);
 }
 
 // The alpha of right-triangle (a, a*b)
-static inline SkAlpha partialTriangleToAlpha(SkFixed a, SkFixed b) {
-    return partialTriangleToAlpha16(a, b) >> 8;
+static SkAlpha partial_triangle_to_alpha(SkFixed a, SkFixed b) {
+    SkASSERT(a <= SK_Fixed1);
+#if 0
+    // TODO(mtklein): skia:8877
+    SkASSERT(b <= SK_Fixed1);
+#endif
+
+    // Approximating...
+    // SkFixed area = SkFixedMul(a, SkFixedMul(a,b)) / 2;
+    SkFixed area = (a >> 11) * (a >> 11) * (b >> 11);
+
+#if 0
+    // TODO(mtklein): skia:8877
+    return SkTo<SkAlpha>(area >> 8);
+#else
+    return SkTo<SkAlpha>((area >> 8) & 0xFF);
+#endif
 }
 
-static inline SkAlpha getPartialAlpha(SkAlpha alpha, SkFixed partialHeight) {
+static SkAlpha get_partial_alpha(SkAlpha alpha, SkFixed partialHeight) {
     return SkToU8(SkFixedRoundToInt(alpha * partialHeight));
 }
 
-static inline SkAlpha getPartialAlpha(SkAlpha alpha, SkAlpha fullAlpha) {
-    return ((uint16_t)alpha * fullAlpha) >> 8;
+static SkAlpha get_partial_alpha(SkAlpha alpha, SkAlpha fullAlpha) {
+    return (alpha * fullAlpha) >> 8;
 }
 
 // For SkFixed that's close to SK_Fixed1, we can't convert it to alpha by just shifting right.
 // For example, when f = SK_Fixed1, right shifting 8 will get 256, but we need 255.
 // This is rarely the problem so we'll only use this for blitting rectangles.
-static inline SkAlpha f2a(SkFixed f) {
+static SkAlpha fixed_to_alpha(SkFixed f) {
     SkASSERT(f <= SK_Fixed1);
-    return getPartialAlpha(0xFF, f);
+    return get_partial_alpha(0xFF, f);
 }
 
 // Suppose that line (l1, y)-(r1, y+1) intersects with (l2, y)-(r2, y+1),
 // approximate (very coarsely) the x coordinate of the intersection.
-static inline SkFixed approximateIntersection(SkFixed l1, SkFixed r1, SkFixed l2, SkFixed r2) {
-    if (l1 > r1) { using std::swap; swap(l1, r1); }
-    if (l2 > r2) { using std::swap; swap(l2, r2); }
-    return (SkTMax(l1, l2) + SkTMin(r1, r2)) >> 1;
+static SkFixed approximate_intersection(SkFixed l1, SkFixed r1, SkFixed l2, SkFixed r2) {
+    if (l1 > r1) {
+        std::swap(l1, r1);
+    }
+    if (l2 > r2) {
+        std::swap(l2, r2);
+    }
+    return (SkTMax(l1, l2) + SkTMin(r1, r2)) / 2;
 }
 
 // Here we always send in l < SK_Fixed1, and the first alpha we want to compute is alphas[0]
-static inline void computeAlphaAboveLine(SkAlpha* alphas, SkFixed l, SkFixed r,
-                                         SkFixed dY, SkAlpha fullAlpha) {
+static void compute_alpha_above_line(SkAlpha* alphas,
+                                     SkFixed  l,
+                                     SkFixed  r,
+                                     SkFixed  dY,
+                                     SkAlpha  fullAlpha) {
     SkASSERT(l <= r);
     SkASSERT(l >> 16 == 0);
     int R = SkFixedCeilToInt(r);
     if (R == 0) {
         return;
     } else if (R == 1) {
-        alphas[0] = getPartialAlpha(((R << 17) - l - r) >> 9, fullAlpha);
+        alphas[0] = get_partial_alpha(((R << 17) - l - r) >> 9, fullAlpha);
     } else {
-        SkFixed first = SK_Fixed1 - l; // horizontal edge length of the left-most triangle
-        SkFixed last = r - ((R - 1) << 16); // horizontal edge length of the right-most triangle
-        SkFixed firstH = SkFixedMul(first, dY); // vertical edge of the left-most triangle
-        alphas[0] = SkFixedMul(first, firstH) >> 9; // triangle alpha
-        SkFixed alpha16 = firstH + (dY >> 1); // rectangle plus triangle
+        SkFixed first   = SK_Fixed1 - l;        // horizontal edge length of the left-most triangle
+        SkFixed last    = r - ((R - 1) << 16);  // horizontal edge length of the right-most triangle
+        SkFixed firstH  = SkFixedMul(first, dY);  // vertical edge of the left-most triangle
+        alphas[0]       = SkFixedMul(first, firstH) >> 9;  // triangle alpha
+        SkFixed alpha16 = firstH + (dY >> 1);              // rectangle plus triangle
         for (int i = 1; i < R - 1; ++i) {
             alphas[i] = alpha16 >> 8;
             alpha16 += dY;
         }
-        alphas[R - 1] = fullAlpha - partialTriangleToAlpha(last, dY);
+        alphas[R - 1] = fullAlpha - partial_triangle_to_alpha(last, dY);
     }
 }
 
 // Here we always send in l < SK_Fixed1, and the first alpha we want to compute is alphas[0]
-static inline void computeAlphaBelowLine(
-        SkAlpha* alphas, SkFixed l, SkFixed r, SkFixed dY, SkAlpha fullAlpha) {
+static void compute_alpha_below_line(SkAlpha* alphas,
+                                     SkFixed  l,
+                                     SkFixed  r,
+                                     SkFixed  dY,
+                                     SkAlpha  fullAlpha) {
     SkASSERT(l <= r);
     SkASSERT(l >> 16 == 0);
     int R = SkFixedCeilToInt(r);
     if (R == 0) {
         return;
     } else if (R == 1) {
-        alphas[0] = getPartialAlpha(trapezoidToAlpha(l, r), fullAlpha);
+        alphas[0] = get_partial_alpha(trapezoid_to_alpha(l, r), fullAlpha);
     } else {
-        SkFixed first = SK_Fixed1 - l; // horizontal edge length of the left-most triangle
-        SkFixed last = r - ((R - 1) << 16); // horizontal edge length of the right-most triangle
-        SkFixed lastH = SkFixedMul(last, dY); // vertical edge of the right-most triangle
-        alphas[R-1] = SkFixedMul(last, lastH) >> 9; // triangle alpha
-        SkFixed alpha16 = lastH + (dY >> 1); // rectangle plus triangle
+        SkFixed first   = SK_Fixed1 - l;        // horizontal edge length of the left-most triangle
+        SkFixed last    = r - ((R - 1) << 16);  // horizontal edge length of the right-most triangle
+        SkFixed lastH   = SkFixedMul(last, dY);          // vertical edge of the right-most triangle
+        alphas[R - 1]   = SkFixedMul(last, lastH) >> 9;  // triangle alpha
+        SkFixed alpha16 = lastH + (dY >> 1);             // rectangle plus triangle
         for (int i = R - 2; i > 0; i--) {
             alphas[i] = (alpha16 >> 8) & 0xFF;
             alpha16 += dY;
         }
-        alphas[0] = fullAlpha - partialTriangleToAlpha(first, dY);
+        alphas[0] = fullAlpha - partial_triangle_to_alpha(first, dY);
     }
 }
 
 // Note that if fullAlpha != 0xFF, we'll multiply alpha by fullAlpha
-static SK_ALWAYS_INLINE void blit_single_alpha(AdditiveBlitter* blitter, int y, int x,
-                              SkAlpha alpha, SkAlpha fullAlpha, SkAlpha* maskRow,
-                              bool isUsingMask, bool noRealBlitter, bool needSafeCheck) {
+static SK_ALWAYS_INLINE void blit_single_alpha(AdditiveBlitter* blitter,
+                                               int              y,
+                                               int              x,
+                                               SkAlpha          alpha,
+                                               SkAlpha          fullAlpha,
+                                               SkAlpha*         maskRow,
+                                               bool             isUsingMask,
+                                               bool             noRealBlitter,
+                                               bool             needSafeCheck) {
     if (isUsingMask) {
-        if (fullAlpha == 0xFF && !noRealBlitter) { // noRealBlitter is needed for concave paths
+        if (fullAlpha == 0xFF && !noRealBlitter) {  // noRealBlitter is needed for concave paths
             maskRow[x] = alpha;
         } else if (needSafeCheck) {
-            safelyAddAlpha(&maskRow[x], getPartialAlpha(alpha, fullAlpha));
+            safely_add_alpha(&maskRow[x], get_partial_alpha(alpha, fullAlpha));
         } else {
-            addAlpha(&maskRow[x], getPartialAlpha(alpha, fullAlpha));
+            add_alpha(&maskRow[x], get_partial_alpha(alpha, fullAlpha));
         }
     } else {
         if (fullAlpha == 0xFF && !noRealBlitter) {
             blitter->getRealBlitter()->blitV(x, y, 1, alpha);
         } else {
-            blitter->blitAntiH(x, y, getPartialAlpha(alpha, fullAlpha));
+            blitter->blitAntiH(x, y, get_partial_alpha(alpha, fullAlpha));
         }
     }
 }
 
-static SK_ALWAYS_INLINE void blit_two_alphas(AdditiveBlitter* blitter, int y, int x,
-                            SkAlpha a1, SkAlpha a2, SkAlpha fullAlpha, SkAlpha* maskRow,
-                            bool isUsingMask, bool noRealBlitter, bool needSafeCheck) {
+static SK_ALWAYS_INLINE void blit_two_alphas(AdditiveBlitter* blitter,
+                                             int              y,
+                                             int              x,
+                                             SkAlpha          a1,
+                                             SkAlpha          a2,
+                                             SkAlpha          fullAlpha,
+                                             SkAlpha*         maskRow,
+                                             bool             isUsingMask,
+                                             bool             noRealBlitter,
+                                             bool             needSafeCheck) {
     if (isUsingMask) {
         if (needSafeCheck) {
-            safelyAddAlpha(&maskRow[x], a1);
-            safelyAddAlpha(&maskRow[x + 1], a2);
+            safely_add_alpha(&maskRow[x], a1);
+            safely_add_alpha(&maskRow[x + 1], a2);
         } else {
-            addAlpha(&maskRow[x], a1);
-            addAlpha(&maskRow[x + 1], a2);
+            add_alpha(&maskRow[x], a1);
+            add_alpha(&maskRow[x + 1], a2);
         }
     } else {
         if (fullAlpha == 0xFF && !noRealBlitter) {
@@ -685,16 +702,21 @@
     }
 }
 
-// It's important that this is inline. Otherwise it'll be much slower.
-static SK_ALWAYS_INLINE void blit_full_alpha(AdditiveBlitter* blitter, int y, int x, int len,
-                            SkAlpha fullAlpha, SkAlpha* maskRow, bool isUsingMask,
-                            bool noRealBlitter, bool needSafeCheck) {
+static SK_ALWAYS_INLINE void blit_full_alpha(AdditiveBlitter* blitter,
+                                             int              y,
+                                             int              x,
+                                             int              len,
+                                             SkAlpha          fullAlpha,
+                                             SkAlpha*         maskRow,
+                                             bool             isUsingMask,
+                                             bool             noRealBlitter,
+                                             bool             needSafeCheck) {
     if (isUsingMask) {
         for (int i = 0; i < len; ++i) {
             if (needSafeCheck) {
-                safelyAddAlpha(&maskRow[x + i], fullAlpha);
+                safely_add_alpha(&maskRow[x + i], fullAlpha);
             } else {
-                addAlpha(&maskRow[x + i], fullAlpha);
+                add_alpha(&maskRow[x + i], fullAlpha);
             }
         }
     } else {
@@ -706,27 +728,39 @@
     }
 }
 
-static void blit_aaa_trapezoid_row(AdditiveBlitter* blitter, int y,
-                                   SkFixed ul, SkFixed ur, SkFixed ll, SkFixed lr,
-                                   SkFixed lDY, SkFixed rDY, SkAlpha fullAlpha, SkAlpha* maskRow,
-                                   bool isUsingMask, bool noRealBlitter, bool needSafeCheck) {
+static void blit_aaa_trapezoid_row(AdditiveBlitter* blitter,
+                                   int              y,
+                                   SkFixed          ul,
+                                   SkFixed          ur,
+                                   SkFixed          ll,
+                                   SkFixed          lr,
+                                   SkFixed          lDY,
+                                   SkFixed          rDY,
+                                   SkAlpha          fullAlpha,
+                                   SkAlpha*         maskRow,
+                                   bool             isUsingMask,
+                                   bool             noRealBlitter,
+                                   bool             needSafeCheck) {
     int L = SkFixedFloorToInt(ul), R = SkFixedCeilToInt(lr);
     int len = R - L;
 
     if (len == 1) {
-        SkAlpha alpha = trapezoidToAlpha(ur - ul, lr - ll);
-        blit_single_alpha(blitter, y, L, alpha, fullAlpha, maskRow, isUsingMask, noRealBlitter,
-                needSafeCheck);
+        SkAlpha alpha = trapezoid_to_alpha(ur - ul, lr - ll);
+        blit_single_alpha(blitter,
+                          y,
+                          L,
+                          alpha,
+                          fullAlpha,
+                          maskRow,
+                          isUsingMask,
+                          noRealBlitter,
+                          needSafeCheck);
         return;
     }
 
-    // SkDebugf("y = %d, len = %d, ul = %f, ur = %f, ll = %f, lr = %f\n", y, len,
-    //         SkFixedToFloat(ul), SkFixedToFloat(ur), SkFixedToFloat(ll), SkFixedToFloat(lr));
-
     const int kQuickLen = 31;
-    // This is faster than SkAutoSMalloc<1024>
-    char quickMemory[(sizeof(SkAlpha) * 2 + sizeof(int16_t)) * (kQuickLen + 1)];
-    SkAlpha* alphas;
+    char      quickMemory[(sizeof(SkAlpha) * 2 + sizeof(int16_t)) * (kQuickLen + 1)];
+    SkAlpha*  alphas;
 
     if (len <= kQuickLen) {
         alphas = (SkAlpha*)quickMemory;
@@ -735,26 +769,26 @@
     }
 
     SkAlpha* tempAlphas = alphas + len + 1;
-    int16_t* runs = (int16_t*)(alphas + (len + 1) * 2);
+    int16_t* runs       = (int16_t*)(alphas + (len + 1) * 2);
 
     for (int i = 0; i < len; ++i) {
-        runs[i] = 1;
+        runs[i]   = 1;
         alphas[i] = fullAlpha;
     }
     runs[len] = 0;
 
     int uL = SkFixedFloorToInt(ul);
     int lL = SkFixedCeilToInt(ll);
-    if (uL + 2 == lL) { // We only need to compute two triangles, accelerate this special case
-        SkFixed first = SkIntToFixed(uL) + SK_Fixed1 - ul;
+    if (uL + 2 == lL) {  // We only need to compute two triangles, accelerate this special case
+        SkFixed first  = SkIntToFixed(uL) + SK_Fixed1 - ul;
         SkFixed second = ll - ul - first;
-        SkAlpha a1 = fullAlpha - partialTriangleToAlpha(first, lDY);
-        SkAlpha a2 = partialTriangleToAlpha(second, lDY);
-        alphas[0] = alphas[0] > a1 ? alphas[0] - a1 : 0;
-        alphas[1] = alphas[1] > a2 ? alphas[1] - a2 : 0;
+        SkAlpha a1     = fullAlpha - partial_triangle_to_alpha(first, lDY);
+        SkAlpha a2     = partial_triangle_to_alpha(second, lDY);
+        alphas[0]      = alphas[0] > a1 ? alphas[0] - a1 : 0;
+        alphas[1]      = alphas[1] > a2 ? alphas[1] - a2 : 0;
     } else {
-        computeAlphaBelowLine(tempAlphas + uL - L, ul - SkIntToFixed(uL), ll - SkIntToFixed(uL),
-                lDY, fullAlpha);
+        compute_alpha_below_line(
+                tempAlphas + uL - L, ul - SkIntToFixed(uL), ll - SkIntToFixed(uL), lDY, fullAlpha);
         for (int i = uL; i < lL; ++i) {
             if (alphas[i - L] > tempAlphas[i - L]) {
                 alphas[i - L] -= tempAlphas[i - L];
@@ -766,16 +800,16 @@
 
     int uR = SkFixedFloorToInt(ur);
     int lR = SkFixedCeilToInt(lr);
-    if (uR + 2 == lR) { // We only need to compute two triangles, accelerate this special case
-        SkFixed first = SkIntToFixed(uR) + SK_Fixed1 - ur;
-        SkFixed second = lr - ur - first;
-        SkAlpha a1 = partialTriangleToAlpha(first, rDY);
-        SkAlpha a2 = fullAlpha - partialTriangleToAlpha(second, rDY);
-        alphas[len-2] = alphas[len-2] > a1 ? alphas[len-2] - a1 : 0;
-        alphas[len-1] = alphas[len-1] > a2 ? alphas[len-1] - a2 : 0;
+    if (uR + 2 == lR) {  // We only need to compute two triangles, accelerate this special case
+        SkFixed first   = SkIntToFixed(uR) + SK_Fixed1 - ur;
+        SkFixed second  = lr - ur - first;
+        SkAlpha a1      = partial_triangle_to_alpha(first, rDY);
+        SkAlpha a2      = fullAlpha - partial_triangle_to_alpha(second, rDY);
+        alphas[len - 2] = alphas[len - 2] > a1 ? alphas[len - 2] - a1 : 0;
+        alphas[len - 1] = alphas[len - 1] > a2 ? alphas[len - 1] - a2 : 0;
     } else {
-        computeAlphaAboveLine(tempAlphas + uR - L, ur - SkIntToFixed(uR), lr - SkIntToFixed(uR),
-                rDY, fullAlpha);
+        compute_alpha_above_line(
+                tempAlphas + uR - L, ur - SkIntToFixed(uR), lr - SkIntToFixed(uR), rDY, fullAlpha);
         for (int i = uR; i < lR; ++i) {
             if (alphas[i - L] > tempAlphas[i - L]) {
                 alphas[i - L] -= tempAlphas[i - L];
@@ -788,9 +822,9 @@
     if (isUsingMask) {
         for (int i = 0; i < len; ++i) {
             if (needSafeCheck) {
-                safelyAddAlpha(&maskRow[L + i], alphas[i]);
+                safely_add_alpha(&maskRow[L + i], alphas[i]);
             } else {
-                addAlpha(&maskRow[L + i], alphas[i]);
+                add_alpha(&maskRow[L + i], alphas[i]);
             }
         }
     } else {
@@ -803,101 +837,171 @@
     }
 
     if (len > kQuickLen) {
-        delete [] alphas;
+        delete[] alphas;
     }
 }
 
-static SK_ALWAYS_INLINE void blit_trapezoid_row(AdditiveBlitter* blitter, int y,
-                               SkFixed ul, SkFixed ur, SkFixed ll, SkFixed lr,
-                               SkFixed lDY, SkFixed rDY, SkAlpha fullAlpha,
-                               SkAlpha* maskRow, bool isUsingMask, bool noRealBlitter = false,
-                               bool needSafeCheck = false) {
-    SkASSERT(lDY >= 0 && rDY >= 0); // We should only send in the absolte value
+static SK_ALWAYS_INLINE void blit_trapezoid_row(AdditiveBlitter* blitter,
+                                                int              y,
+                                                SkFixed          ul,
+                                                SkFixed          ur,
+                                                SkFixed          ll,
+                                                SkFixed          lr,
+                                                SkFixed          lDY,
+                                                SkFixed          rDY,
+                                                SkAlpha          fullAlpha,
+                                                SkAlpha*         maskRow,
+                                                bool             isUsingMask,
+                                                bool             noRealBlitter = false,
+                                                bool             needSafeCheck = false) {
+    SkASSERT(lDY >= 0 && rDY >= 0);  // We should only send in the absolte value
 
     if (ul > ur) {
-#ifdef SK_DEBUG
-        // SkDebugf("ul = %f > ur = %f!\n", SkFixedToFloat(ul), SkFixedToFloat(ur));
-#endif
         return;
     }
 
     // Edge crosses. Approximate it. This should only happend due to precision limit,
     // so the approximation could be very coarse.
     if (ll > lr) {
-#ifdef SK_DEBUG
-        // SkDebugf("approximate intersection: %d %f %f\n", y,
-        //          SkFixedToFloat(ll), SkFixedToFloat(lr));
-#endif
-        ll = lr = approximateIntersection(ul, ll, ur, lr);
+        ll = lr = approximate_intersection(ul, ll, ur, lr);
     }
 
     if (ul == ur && ll == lr) {
-        return; // empty trapzoid
+        return;  // empty trapzoid
     }
 
     // We're going to use the left line ul-ll and the rite line ur-lr
     // to exclude the area that's not covered by the path.
     // Swapping (ul, ll) or (ur, lr) won't affect that exclusion
     // so we'll do that for simplicity.
-    if (ul > ll) { using std::swap; swap(ul, ll); }
-    if (ur > lr) { using std::swap; swap(ur, lr); }
+    if (ul > ll) {
+        std::swap(ul, ll);
+    }
+    if (ur > lr) {
+        std::swap(ur, lr);
+    }
 
     SkFixed joinLeft = SkFixedCeilToFixed(ll);
     SkFixed joinRite = SkFixedFloorToFixed(ur);
-    if (joinLeft <= joinRite) { // There's a rect from joinLeft to joinRite that we can blit
+    if (joinLeft <= joinRite) {  // There's a rect from joinLeft to joinRite that we can blit
         if (ul < joinLeft) {
             int len = SkFixedCeilToInt(joinLeft - ul);
             if (len == 1) {
-                SkAlpha alpha = trapezoidToAlpha(joinLeft - ul, joinLeft - ll);
-                blit_single_alpha(blitter, y, ul >> 16, alpha, fullAlpha, maskRow, isUsingMask,
-                        noRealBlitter, needSafeCheck);
+                SkAlpha alpha = trapezoid_to_alpha(joinLeft - ul, joinLeft - ll);
+                blit_single_alpha(blitter,
+                                  y,
+                                  ul >> 16,
+                                  alpha,
+                                  fullAlpha,
+                                  maskRow,
+                                  isUsingMask,
+                                  noRealBlitter,
+                                  needSafeCheck);
             } else if (len == 2) {
-                SkFixed first = joinLeft - SK_Fixed1 - ul;
+                SkFixed first  = joinLeft - SK_Fixed1 - ul;
                 SkFixed second = ll - ul - first;
-                SkAlpha a1 = partialTriangleToAlpha(first, lDY);
-                SkAlpha a2 = fullAlpha - partialTriangleToAlpha(second, lDY);
-                blit_two_alphas(blitter, y, ul >> 16, a1, a2, fullAlpha, maskRow, isUsingMask,
-                        noRealBlitter, needSafeCheck);
+                SkAlpha a1     = partial_triangle_to_alpha(first, lDY);
+                SkAlpha a2     = fullAlpha - partial_triangle_to_alpha(second, lDY);
+                blit_two_alphas(blitter,
+                                y,
+                                ul >> 16,
+                                a1,
+                                a2,
+                                fullAlpha,
+                                maskRow,
+                                isUsingMask,
+                                noRealBlitter,
+                                needSafeCheck);
             } else {
-                blit_aaa_trapezoid_row(blitter, y, ul, joinLeft, ll, joinLeft, lDY, SK_MaxS32,
-                                       fullAlpha, maskRow, isUsingMask, noRealBlitter,
+                blit_aaa_trapezoid_row(blitter,
+                                       y,
+                                       ul,
+                                       joinLeft,
+                                       ll,
+                                       joinLeft,
+                                       lDY,
+                                       SK_MaxS32,
+                                       fullAlpha,
+                                       maskRow,
+                                       isUsingMask,
+                                       noRealBlitter,
                                        needSafeCheck);
             }
         }
         // SkAAClip requires that we blit from left to right.
         // Hence we must blit [ul, joinLeft] before blitting [joinLeft, joinRite]
         if (joinLeft < joinRite) {
-            blit_full_alpha(blitter, y, SkFixedFloorToInt(joinLeft),
+            blit_full_alpha(blitter,
+                            y,
+                            SkFixedFloorToInt(joinLeft),
                             SkFixedFloorToInt(joinRite - joinLeft),
-                            fullAlpha, maskRow, isUsingMask, noRealBlitter, needSafeCheck);
+                            fullAlpha,
+                            maskRow,
+                            isUsingMask,
+                            noRealBlitter,
+                            needSafeCheck);
         }
         if (lr > joinRite) {
             int len = SkFixedCeilToInt(lr - joinRite);
             if (len == 1) {
-                SkAlpha alpha = trapezoidToAlpha(ur - joinRite, lr - joinRite);
-                blit_single_alpha(blitter, y, joinRite >> 16, alpha, fullAlpha, maskRow,
-                                  isUsingMask, noRealBlitter, needSafeCheck);
+                SkAlpha alpha = trapezoid_to_alpha(ur - joinRite, lr - joinRite);
+                blit_single_alpha(blitter,
+                                  y,
+                                  joinRite >> 16,
+                                  alpha,
+                                  fullAlpha,
+                                  maskRow,
+                                  isUsingMask,
+                                  noRealBlitter,
+                                  needSafeCheck);
             } else if (len == 2) {
-                SkFixed first = joinRite + SK_Fixed1 - ur;
+                SkFixed first  = joinRite + SK_Fixed1 - ur;
                 SkFixed second = lr - ur - first;
-                SkAlpha a1 = fullAlpha - partialTriangleToAlpha(first, rDY);
-                SkAlpha a2 = partialTriangleToAlpha(second, rDY);
-                blit_two_alphas(blitter, y, joinRite >> 16, a1, a2, fullAlpha, maskRow,
-                                isUsingMask, noRealBlitter, needSafeCheck);
+                SkAlpha a1     = fullAlpha - partial_triangle_to_alpha(first, rDY);
+                SkAlpha a2     = partial_triangle_to_alpha(second, rDY);
+                blit_two_alphas(blitter,
+                                y,
+                                joinRite >> 16,
+                                a1,
+                                a2,
+                                fullAlpha,
+                                maskRow,
+                                isUsingMask,
+                                noRealBlitter,
+                                needSafeCheck);
             } else {
-                blit_aaa_trapezoid_row(blitter, y, joinRite, ur, joinRite, lr, SK_MaxS32, rDY,
-                                       fullAlpha, maskRow, isUsingMask, noRealBlitter,
+                blit_aaa_trapezoid_row(blitter,
+                                       y,
+                                       joinRite,
+                                       ur,
+                                       joinRite,
+                                       lr,
+                                       SK_MaxS32,
+                                       rDY,
+                                       fullAlpha,
+                                       maskRow,
+                                       isUsingMask,
+                                       noRealBlitter,
                                        needSafeCheck);
             }
         }
     } else {
-        blit_aaa_trapezoid_row(blitter, y, ul, ur, ll, lr, lDY, rDY, fullAlpha, maskRow,
-                               isUsingMask, noRealBlitter, needSafeCheck);
+        blit_aaa_trapezoid_row(blitter,
+                               y,
+                               ul,
+                               ur,
+                               ll,
+                               lr,
+                               lDY,
+                               rDY,
+                               fullAlpha,
+                               maskRow,
+                               isUsingMask,
+                               noRealBlitter,
+                               needSafeCheck);
     }
 }
 
-///////////////////////////////////////////////////////////////////////////////
-
 static bool operator<(const SkAnalyticEdge& a, const SkAnalyticEdge& b) {
     int valuea = a.fUpperY;
     int valueb = b.fUpperY;
@@ -921,98 +1025,95 @@
     // now make the edges linked in sorted order
     for (int i = 1; i < count; ++i) {
         list[i - 1]->fNext = list[i];
-        list[i]->fPrev = list[i - 1];
+        list[i]->fPrev     = list[i - 1];
     }
 
     *last = list[count - 1];
     return list[0];
 }
 
+static void validate_sort(const SkAnalyticEdge* edge) {
 #ifdef SK_DEBUG
-    static void validate_sort(const SkAnalyticEdge* edge) {
-        SkFixed y = SkIntToFixed(-32768);
+    SkFixed y = SkIntToFixed(-32768);
 
-        while (edge->fUpperY != SK_MaxS32) {
-            edge->validate();
-            SkASSERT(y <= edge->fUpperY);
+    while (edge->fUpperY != SK_MaxS32) {
+        edge->validate();
+        SkASSERT(y <= edge->fUpperY);
 
-            y = edge->fUpperY;
-            edge = (SkAnalyticEdge*)edge->fNext;
-        }
+        y    = edge->fUpperY;
+        edge = (SkAnalyticEdge*)edge->fNext;
     }
-#else
-    #define validate_sort(edge)
 #endif
+}
 
 // For an edge, we consider it smooth if the Dx doesn't change much, and Dy is large enough
 // For curves that are updating, the Dx is not changing much if fQDx/fCDx and fQDy/fCDy are
 // relatively large compared to fQDDx/QCDDx and fQDDy/fCDDy
-static inline bool isSmoothEnough(SkAnalyticEdge* thisEdge, SkAnalyticEdge* nextEdge, int stop_y) {
+static bool is_smooth_enough(SkAnalyticEdge* thisEdge, SkAnalyticEdge* nextEdge, int stop_y) {
     if (thisEdge->fCurveCount < 0) {
-        const SkCubicEdge& cEdge = static_cast<SkAnalyticCubicEdge*>(thisEdge)->fCEdge;
-        int ddshift = cEdge.fCurveShift;
+        const SkCubicEdge& cEdge   = static_cast<SkAnalyticCubicEdge*>(thisEdge)->fCEdge;
+        int                ddshift = cEdge.fCurveShift;
         return SkAbs32(cEdge.fCDx) >> 1 >= SkAbs32(cEdge.fCDDx) >> ddshift &&
-                SkAbs32(cEdge.fCDy) >> 1 >= SkAbs32(cEdge.fCDDy) >> ddshift &&
-                // current Dy is (fCDy - (fCDDy >> ddshift)) >> dshift
-                (cEdge.fCDy - (cEdge.fCDDy >> ddshift)) >> cEdge.fCubicDShift >= SK_Fixed1;
+               SkAbs32(cEdge.fCDy) >> 1 >= SkAbs32(cEdge.fCDDy) >> ddshift &&
+               // current Dy is (fCDy - (fCDDy >> ddshift)) >> dshift
+               (cEdge.fCDy - (cEdge.fCDDy >> ddshift)) >> cEdge.fCubicDShift >= SK_Fixed1;
     } else if (thisEdge->fCurveCount > 0) {
         const SkQuadraticEdge& qEdge = static_cast<SkAnalyticQuadraticEdge*>(thisEdge)->fQEdge;
         return SkAbs32(qEdge.fQDx) >> 1 >= SkAbs32(qEdge.fQDDx) &&
-                SkAbs32(qEdge.fQDy) >> 1 >= SkAbs32(qEdge.fQDDy) &&
-                // current Dy is (fQDy - fQDDy) >> shift
-                (qEdge.fQDy - qEdge.fQDDy) >> qEdge.fCurveShift
-                >= SK_Fixed1;
+               SkAbs32(qEdge.fQDy) >> 1 >= SkAbs32(qEdge.fQDDy) &&
+               // current Dy is (fQDy - fQDDy) >> shift
+               (qEdge.fQDy - qEdge.fQDDy) >> qEdge.fCurveShift >= SK_Fixed1;
     }
-    return SkAbs32(nextEdge->fDX - thisEdge->fDX) <= SK_Fixed1 && // DDx should be small
-            nextEdge->fLowerY - nextEdge->fUpperY >= SK_Fixed1; // Dy should be large
+    return SkAbs32(nextEdge->fDX - thisEdge->fDX) <= SK_Fixed1 &&  // DDx should be small
+           nextEdge->fLowerY - nextEdge->fUpperY >= SK_Fixed1;     // Dy should be large
 }
 
 // Check if the leftE and riteE are changing smoothly in terms of fDX.
 // If yes, we can later skip the fractional y and directly jump to integer y.
-static inline bool isSmoothEnough(SkAnalyticEdge* leftE, SkAnalyticEdge* riteE,
-                           SkAnalyticEdge* currE, int stop_y) {
+static bool is_smooth_enough(SkAnalyticEdge* leftE,
+                             SkAnalyticEdge* riteE,
+                             SkAnalyticEdge* currE,
+                             int             stop_y) {
     if (currE->fUpperY >= SkLeftShift(stop_y, 16)) {
-        return false; // We're at the end so we won't skip anything
+        return false;  // We're at the end so we won't skip anything
     }
     if (leftE->fLowerY + SK_Fixed1 < riteE->fLowerY) {
-        return isSmoothEnough(leftE, currE, stop_y); // Only leftE is changing
+        return is_smooth_enough(leftE, currE, stop_y);  // Only leftE is changing
     } else if (leftE->fLowerY > riteE->fLowerY + SK_Fixed1) {
-        return isSmoothEnough(riteE, currE, stop_y); // Only riteE is changing
+        return is_smooth_enough(riteE, currE, stop_y);  // Only riteE is changing
     }
 
     // Now both edges are changing, find the second next edge
     SkAnalyticEdge* nextCurrE = currE->fNext;
-    if (nextCurrE->fUpperY >= stop_y << 16) { // Check if we're at the end
+    if (nextCurrE->fUpperY >= stop_y << 16) {  // Check if we're at the end
         return false;
     }
     // Ensure that currE is the next left edge and nextCurrE is the next right edge. Swap if not.
     if (nextCurrE->fUpperX < currE->fUpperX) {
-        using std::swap;
-        swap(currE, nextCurrE);
+        std::swap(currE, nextCurrE);
     }
-    return isSmoothEnough(leftE, currE, stop_y) && isSmoothEnough(riteE, nextCurrE, stop_y);
+    return is_smooth_enough(leftE, currE, stop_y) && is_smooth_enough(riteE, nextCurrE, stop_y);
 }
 
-static inline void aaa_walk_convex_edges(SkAnalyticEdge* prevHead,
-        AdditiveBlitter* blitter, int start_y, int stop_y, SkFixed leftBound, SkFixed riteBound,
-        bool isUsingMask) {
+static void aaa_walk_convex_edges(SkAnalyticEdge*  prevHead,
+                                  AdditiveBlitter* blitter,
+                                  int              start_y,
+                                  int              stop_y,
+                                  SkFixed          leftBound,
+                                  SkFixed          riteBound,
+                                  bool             isUsingMask) {
     validate_sort((SkAnalyticEdge*)prevHead->fNext);
 
-    SkAnalyticEdge* leftE = (SkAnalyticEdge*) prevHead->fNext;
-    SkAnalyticEdge* riteE = (SkAnalyticEdge*) leftE->fNext;
-    SkAnalyticEdge* currE = (SkAnalyticEdge*) riteE->fNext;
+    SkAnalyticEdge* leftE = (SkAnalyticEdge*)prevHead->fNext;
+    SkAnalyticEdge* riteE = (SkAnalyticEdge*)leftE->fNext;
+    SkAnalyticEdge* currE = (SkAnalyticEdge*)riteE->fNext;
 
     SkFixed y = SkTMax(leftE->fUpperY, riteE->fUpperY);
 
-    #ifdef SK_DEBUG
-    int frac_y_cnt = 0;
-    int total_y_cnt = 0;
-    #endif
-
     for (;;) {
         // We have to check fLowerY first because some edges might be alone (e.g., there's only
         // a left edge but no right edge in a given y scan line) due to precision limit.
-        while (leftE->fLowerY <= y) { // Due to smooth jump, we may pass multiple short edges
+        while (leftE->fLowerY <= y) {  // Due to smooth jump, we may pass multiple short edges
             if (!leftE->update(y)) {
                 if (SkFixedFloorToInt(currE->fUpperY) >= stop_y) {
                     goto END_WALK;
@@ -1021,7 +1122,7 @@
                 currE = (SkAnalyticEdge*)currE->fNext;
             }
         }
-        while (riteE->fLowerY <= y) { // Due to smooth jump, we may pass multiple short edges
+        while (riteE->fLowerY <= y) {  // Due to smooth jump, we may pass multiple short edges
             if (!riteE->update(y)) {
                 if (SkFixedFloorToInt(currE->fUpperY) >= stop_y) {
                     goto END_WALK;
@@ -1045,21 +1146,19 @@
         leftE->goY(y);
         riteE->goY(y);
 
-        if (leftE->fX > riteE->fX || (leftE->fX == riteE->fX &&
-                                      leftE->fDX > riteE->fDX)) {
-            using std::swap;
-            swap(leftE, riteE);
+        if (leftE->fX > riteE->fX || (leftE->fX == riteE->fX && leftE->fDX > riteE->fDX)) {
+            std::swap(leftE, riteE);
         }
 
         SkFixed local_bot_fixed = SkMin32(leftE->fLowerY, riteE->fLowerY);
-        if (isSmoothEnough(leftE, riteE, currE, stop_y)) {
+        if (is_smooth_enough(leftE, riteE, currE, stop_y)) {
             local_bot_fixed = SkFixedCeilToFixed(local_bot_fixed);
         }
         local_bot_fixed = SkMin32(local_bot_fixed, SkIntToFixed(stop_y));
 
-        SkFixed left = SkTMax(leftBound, leftE->fX);
+        SkFixed left  = SkTMax(leftBound, leftE->fX);
         SkFixed dLeft = leftE->fDX;
-        SkFixed rite = SkTMin(riteBound, riteE->fX);
+        SkFixed rite  = SkTMin(riteBound, riteE->fX);
         SkFixed dRite = riteE->fDX;
         if (0 == (dLeft | dRite)) {
             int     fullLeft    = SkFixedCeilToInt(left);
@@ -1070,60 +1169,73 @@
             int     fullBot     = SkFixedFloorToInt(local_bot_fixed);
             SkFixed partialTop  = SkIntToFixed(fullTop) - y;
             SkFixed partialBot  = local_bot_fixed - SkIntToFixed(fullBot);
-            if (fullTop > fullBot) { // The rectangle is within one pixel height...
+            if (fullTop > fullBot) {  // The rectangle is within one pixel height...
                 partialTop -= (SK_Fixed1 - partialBot);
                 partialBot = 0;
             }
 
             if (fullRite >= fullLeft) {
-                if (partialTop > 0) { // blit first partial row
+                if (partialTop > 0) {  // blit first partial row
                     if (partialLeft > 0) {
-                        blitter->blitAntiH(fullLeft - 1, fullTop - 1,
-                                f2a(SkFixedMul(partialTop, partialLeft)));
+                        blitter->blitAntiH(fullLeft - 1,
+                                           fullTop - 1,
+                                           fixed_to_alpha(SkFixedMul(partialTop, partialLeft)));
                     }
-                    blitter->blitAntiH(fullLeft, fullTop - 1, fullRite - fullLeft,
-                                       f2a(partialTop));
+                    blitter->blitAntiH(
+                            fullLeft, fullTop - 1, fullRite - fullLeft, fixed_to_alpha(partialTop));
                     if (partialRite > 0) {
-                        blitter->blitAntiH(fullRite, fullTop - 1,
-                                f2a(SkFixedMul(partialTop, partialRite)));
+                        blitter->blitAntiH(fullRite,
+                                           fullTop - 1,
+                                           fixed_to_alpha(SkFixedMul(partialTop, partialRite)));
                     }
                     blitter->flush_if_y_changed(y, y + partialTop);
                 }
 
                 // Blit all full-height rows from fullTop to fullBot
                 if (fullBot > fullTop &&
-                        // SkAAClip cannot handle the empty rect so check the non-emptiness here
-                        // (bug chromium:662800)
-                        (fullRite > fullLeft || f2a(partialLeft) > 0 || f2a(partialRite) > 0)) {
-                    blitter->getRealBlitter()->blitAntiRect(fullLeft - 1, fullTop,
-                                                            fullRite - fullLeft, fullBot - fullTop,
-                                                            f2a(partialLeft), f2a(partialRite));
+                    // SkAAClip cannot handle the empty rect so check the non-emptiness here
+                    // (bug chromium:662800)
+                    (fullRite > fullLeft || fixed_to_alpha(partialLeft) > 0 ||
+                     fixed_to_alpha(partialRite) > 0)) {
+                    blitter->getRealBlitter()->blitAntiRect(fullLeft - 1,
+                                                            fullTop,
+                                                            fullRite - fullLeft,
+                                                            fullBot - fullTop,
+                                                            fixed_to_alpha(partialLeft),
+                                                            fixed_to_alpha(partialRite));
                 }
 
-                if (partialBot > 0) { // blit last partial row
+                if (partialBot > 0) {  // blit last partial row
                     if (partialLeft > 0) {
-                        blitter->blitAntiH(fullLeft - 1, fullBot,
-                                           f2a(SkFixedMul(partialBot, partialLeft)));
+                        blitter->blitAntiH(fullLeft - 1,
+                                           fullBot,
+                                           fixed_to_alpha(SkFixedMul(partialBot, partialLeft)));
                     }
-                    blitter->blitAntiH(fullLeft, fullBot, fullRite - fullLeft, f2a(partialBot));
+                    blitter->blitAntiH(
+                            fullLeft, fullBot, fullRite - fullLeft, fixed_to_alpha(partialBot));
                     if (partialRite > 0) {
-                        blitter->blitAntiH(fullRite, fullBot,
-                                           f2a(SkFixedMul(partialBot, partialRite)));
+                        blitter->blitAntiH(fullRite,
+                                           fullBot,
+                                           fixed_to_alpha(SkFixedMul(partialBot, partialRite)));
                     }
                 }
-            } else { // left and rite are within the same pixel
+            } else {  // left and rite are within the same pixel
                 if (partialTop > 0) {
-                    blitter->blitAntiH(fullLeft - 1, fullTop - 1, 1,
-                            f2a(SkFixedMul(partialTop, rite - left)));
+                    blitter->blitAntiH(fullLeft - 1,
+                                       fullTop - 1,
+                                       1,
+                                       fixed_to_alpha(SkFixedMul(partialTop, rite - left)));
                     blitter->flush_if_y_changed(y, y + partialTop);
                 }
                 if (fullBot > fullTop) {
-                    blitter->getRealBlitter()->blitV(fullLeft - 1, fullTop, fullBot - fullTop,
-                            f2a(rite - left));
+                    blitter->getRealBlitter()->blitV(
+                            fullLeft - 1, fullTop, fullBot - fullTop, fixed_to_alpha(rite - left));
                 }
                 if (partialBot > 0) {
-                    blitter->blitAntiH(fullLeft - 1, fullBot, 1,
-                            f2a(SkFixedMul(partialBot, rite - left)));
+                    blitter->blitAntiH(fullLeft - 1,
+                                       fullBot,
+                                       1,
+                                       fixed_to_alpha(SkFixedMul(partialBot, rite - left)));
                 }
             }
 
@@ -1133,19 +1245,13 @@
             // We snap X mainly for speedup (no tiny triangle) and
             // avoiding edge cases caused by precision errors
             const SkFixed kSnapDigit = SK_Fixed1 >> 4;
-            const SkFixed kSnapHalf = kSnapDigit >> 1;
-            const SkFixed kSnapMask = (-1 ^ (kSnapDigit - 1));
-            left += kSnapHalf; rite += kSnapHalf; // For fast rounding
+            const SkFixed kSnapHalf  = kSnapDigit >> 1;
+            const SkFixed kSnapMask  = (-1 ^ (kSnapDigit - 1));
+            left += kSnapHalf;
+            rite += kSnapHalf;  // For fast rounding
 
             // Number of blit_trapezoid_row calls we'll have
             int count = SkFixedCeilToInt(local_bot_fixed) - SkFixedFloorToInt(y);
-            #ifdef SK_DEBUG
-            total_y_cnt += count;
-            frac_y_cnt += ((int)(y & 0xFFFF0000) != y);
-            if ((int)(y & 0xFFFF0000) != y) {
-                // SkDebugf("frac_y = %f\n", SkFixedToFloat(y));
-            }
-            #endif
 
             // If we're using mask blitter, we advance the mask row in this function
             // to save some "if" condition checks.
@@ -1159,36 +1265,56 @@
             // handle partial-row blit and full-row blit separately. It will save us much time
             // on changing y, left, and rite.
             if (count > 1) {
-                if ((int)(y & 0xFFFF0000) != y) { // There's a partial-row on the top
+                if ((int)(y & 0xFFFF0000) != y) {  // There's a partial-row on the top
                     count--;
-                    SkFixed nextY = SkFixedCeilToFixed(y + 1);
-                    SkFixed dY = nextY - y;
+                    SkFixed nextY    = SkFixedCeilToFixed(y + 1);
+                    SkFixed dY       = nextY - y;
                     SkFixed nextLeft = left + SkFixedMul(dLeft, dY);
                     SkFixed nextRite = rite + SkFixedMul(dRite, dY);
                     SkASSERT((left & kSnapMask) >= leftBound && (rite & kSnapMask) <= riteBound &&
-                            (nextLeft & kSnapMask) >= leftBound &&
-                            (nextRite & kSnapMask) <= riteBound);
-                    blit_trapezoid_row(blitter, y >> 16, left & kSnapMask, rite & kSnapMask,
-                            nextLeft & kSnapMask, nextRite & kSnapMask, leftE->fDY, riteE->fDY,
-                            getPartialAlpha(0xFF, dY), maskRow, isUsingMask);
+                             (nextLeft & kSnapMask) >= leftBound &&
+                             (nextRite & kSnapMask) <= riteBound);
+                    blit_trapezoid_row(blitter,
+                                       y >> 16,
+                                       left & kSnapMask,
+                                       rite & kSnapMask,
+                                       nextLeft & kSnapMask,
+                                       nextRite & kSnapMask,
+                                       leftE->fDY,
+                                       riteE->fDY,
+                                       get_partial_alpha(0xFF, dY),
+                                       maskRow,
+                                       isUsingMask);
                     blitter->flush_if_y_changed(y, nextY);
-                    left = nextLeft; rite = nextRite; y = nextY;
+                    left = nextLeft;
+                    rite = nextRite;
+                    y    = nextY;
                 }
 
-                while (count > 1) { // Full rows in the middle
+                while (count > 1) {  // Full rows in the middle
                     count--;
                     if (isUsingMask) {
                         maskRow = static_cast<MaskAdditiveBlitter*>(blitter)->getRow(y >> 16);
                     }
                     SkFixed nextY = y + SK_Fixed1, nextLeft = left + dLeft, nextRite = rite + dRite;
                     SkASSERT((left & kSnapMask) >= leftBound && (rite & kSnapMask) <= riteBound &&
-                            (nextLeft & kSnapMask) >= leftBound &&
-                            (nextRite & kSnapMask) <= riteBound);
-                    blit_trapezoid_row(blitter, y >> 16, left & kSnapMask, rite & kSnapMask,
-                            nextLeft & kSnapMask, nextRite & kSnapMask,
-                            leftE->fDY, riteE->fDY, 0xFF, maskRow, isUsingMask);
+                             (nextLeft & kSnapMask) >= leftBound &&
+                             (nextRite & kSnapMask) <= riteBound);
+                    blit_trapezoid_row(blitter,
+                                       y >> 16,
+                                       left & kSnapMask,
+                                       rite & kSnapMask,
+                                       nextLeft & kSnapMask,
+                                       nextRite & kSnapMask,
+                                       leftE->fDY,
+                                       riteE->fDY,
+                                       0xFF,
+                                       maskRow,
+                                       isUsingMask);
                     blitter->flush_if_y_changed(y, nextY);
-                    left = nextLeft; rite = nextRite; y = nextY;
+                    left = nextLeft;
+                    rite = nextRite;
+                    y    = nextY;
                 }
             }
 
@@ -1196,7 +1322,7 @@
                 maskRow = static_cast<MaskAdditiveBlitter*>(blitter)->getRow(y >> 16);
             }
 
-            SkFixed dY = local_bot_fixed - y; // partial-row on the bottom
+            SkFixed dY = local_bot_fixed - y;  // partial-row on the bottom
             SkASSERT(dY <= SK_Fixed1);
             // Smooth jumping to integer y may make the last nextLeft/nextRite out of bound.
             // Take them back into the bound here.
@@ -1204,13 +1330,24 @@
             SkFixed nextLeft = SkTMax(left + SkFixedMul(dLeft, dY), leftBound + kSnapHalf);
             SkFixed nextRite = SkTMin(rite + SkFixedMul(dRite, dY), riteBound + kSnapHalf);
             SkASSERT((left & kSnapMask) >= leftBound && (rite & kSnapMask) <= riteBound &&
-                    (nextLeft & kSnapMask) >= leftBound && (nextRite & kSnapMask) <= riteBound);
-            blit_trapezoid_row(blitter, y >> 16, left & kSnapMask, rite & kSnapMask,
-                    nextLeft & kSnapMask, nextRite & kSnapMask, leftE->fDY, riteE->fDY,
-                    getPartialAlpha(0xFF, dY), maskRow, isUsingMask);
+                     (nextLeft & kSnapMask) >= leftBound && (nextRite & kSnapMask) <= riteBound);
+            blit_trapezoid_row(blitter,
+                               y >> 16,
+                               left & kSnapMask,
+                               rite & kSnapMask,
+                               nextLeft & kSnapMask,
+                               nextRite & kSnapMask,
+                               leftE->fDY,
+                               riteE->fDY,
+                               get_partial_alpha(0xFF, dY),
+                               maskRow,
+                               isUsingMask);
             blitter->flush_if_y_changed(y, local_bot_fixed);
-            left = nextLeft; rite = nextRite; y = local_bot_fixed;
-            left -= kSnapHalf; rite -= kSnapHalf;
+            left = nextLeft;
+            rite = nextRite;
+            y    = local_bot_fixed;
+            left -= kSnapHalf;
+            rite -= kSnapHalf;
         }
 
         leftE->fX = left;
@@ -1218,21 +1355,14 @@
         leftE->fY = riteE->fY = y;
     }
 
-END_WALK:
-    ;
-    #ifdef SK_DEBUG
-    // SkDebugf("frac_y_cnt = %d, total_y_cnt = %d\n", frac_y_cnt, total_y_cnt);
-    #endif
+END_WALK:;
 }
 
-///////////////////////////////////////////////////////////////////////////////
-
-static inline void updateNextNextY(SkFixed y, SkFixed nextY, SkFixed* nextNextY) {
+static void update_next_next_y(SkFixed y, SkFixed nextY, SkFixed* nextNextY) {
     *nextNextY = y > nextY && y < *nextNextY ? y : *nextNextY;
 }
 
-static inline void checkIntersection(const SkAnalyticEdge* edge, SkFixed nextY, SkFixed* nextNextY)
-{
+static void check_intersection(const SkAnalyticEdge* edge, SkFixed nextY, SkFixed* nextNextY) {
     if (edge->fPrev->fPrev && edge->fPrev->fX + edge->fPrev->fDX > edge->fX + edge->fDX) {
         *nextNextY = nextY + (SK_Fixed1 >> SkAnalyticEdge::kDefaultAccuracy);
     }
@@ -1240,22 +1370,22 @@
 
 static void insert_new_edges(SkAnalyticEdge* newEdge, SkFixed y, SkFixed* nextNextY) {
     if (newEdge->fUpperY > y) {
-        updateNextNextY(newEdge->fUpperY, y, nextNextY);
+        update_next_next_y(newEdge->fUpperY, y, nextNextY);
         return;
     }
     SkAnalyticEdge* prev = newEdge->fPrev;
     if (prev->fX <= newEdge->fX) {
         while (newEdge->fUpperY <= y) {
-            checkIntersection(newEdge, y, nextNextY);
-            updateNextNextY(newEdge->fLowerY, y, nextNextY);
+            check_intersection(newEdge, y, nextNextY);
+            update_next_next_y(newEdge->fLowerY, y, nextNextY);
             newEdge = newEdge->fNext;
         }
-        updateNextNextY(newEdge->fUpperY, y, nextNextY);
+        update_next_next_y(newEdge->fUpperY, y, nextNextY);
         return;
     }
     // find first x pos to insert
     SkAnalyticEdge* start = backward_insert_start(prev, newEdge->fX);
-    //insert the lot, fixing up the links as we go
+    // insert the lot, fixing up the links as we go
     do {
         SkAnalyticEdge* next = newEdge->fNext;
         do {
@@ -1271,13 +1401,13 @@
         } while (true);
         remove_edge(newEdge);
         insert_edge_after(newEdge, start);
-nextEdge:
-        checkIntersection(newEdge, y, nextNextY);
-        updateNextNextY(newEdge->fLowerY, y, nextNextY);
-        start = newEdge;
+    nextEdge:
+        check_intersection(newEdge, y, nextNextY);
+        update_next_next_y(newEdge->fLowerY, y, nextNextY);
+        start   = newEdge;
         newEdge = next;
     } while (newEdge->fUpperY <= y);
-    updateNextNextY(newEdge->fUpperY, y, nextNextY);
+    update_next_next_y(newEdge->fUpperY, y, nextNextY);
 }
 
 static void validate_edges_for_y(const SkAnalyticEdge* edge, SkFixed y) {
@@ -1294,7 +1424,7 @@
 }
 
 // Return true if prev->fX, next->fX are too close in the current pixel row.
-static inline bool edges_too_close(SkAnalyticEdge* prev, SkAnalyticEdge* next, SkFixed lowerY) {
+static bool edges_too_close(SkAnalyticEdge* prev, SkAnalyticEdge* next, SkFixed lowerY) {
     // When next->fDX == 0, prev->fX >= next->fX - SkAbs32(next->fDX) would be false
     // even if prev->fX and next->fX are close and within one pixel (e.g., prev->fX == 0.1,
     // next->fX == 0.9). Adding SLACK = 1 to the formula would guarantee it to be true if two
@@ -1306,8 +1436,8 @@
     // However, to handle that case, we have to sacrafice more performance.
     // I think the current quality is good enough (mainly by looking at Nebraska-StateSeal.svg)
     // so I'll ignore fDX for performance tradeoff.
-    return next && prev && next->fUpperY < lowerY && prev->fX + SLACK >=
-                                                     next->fX - SkAbs32(next->fDX);
+    return next && prev && next->fUpperY < lowerY &&
+           prev->fX + SLACK >= next->fX - SkAbs32(next->fDX);
     // The following is more accurate but also slower.
     // return (prev && prev->fPrev && next && next->fNext != nullptr && next->fUpperY < lowerY &&
     //     prev->fX + SkAbs32(prev->fDX) + SLACK >= next->fX - SkAbs32(next->fDX));
@@ -1315,45 +1445,78 @@
 
 // This function exists for the case where the previous rite edge is removed because
 // its fLowerY <= nextY
-static inline bool edges_too_close(int prevRite, SkFixed ul, SkFixed ll) {
+static bool edges_too_close(int prevRite, SkFixed ul, SkFixed ll) {
     return prevRite > SkFixedFloorToInt(ul) || prevRite > SkFixedFloorToInt(ll);
 }
 
-static inline void blit_saved_trapezoid(SkAnalyticEdge* leftE, SkFixed lowerY,
-        SkFixed lowerLeft, SkFixed lowerRite,
-        AdditiveBlitter* blitter, SkAlpha* maskRow, bool isUsingMask, bool noRealBlitter,
-        SkFixed leftClip, SkFixed rightClip) {
+static void blit_saved_trapezoid(SkAnalyticEdge*  leftE,
+                                 SkFixed          lowerY,
+                                 SkFixed          lowerLeft,
+                                 SkFixed          lowerRite,
+                                 AdditiveBlitter* blitter,
+                                 SkAlpha*         maskRow,
+                                 bool             isUsingMask,
+                                 bool             noRealBlitter,
+                                 SkFixed          leftClip,
+                                 SkFixed          rightClip) {
     SkAnalyticEdge* riteE = leftE->fRiteE;
     SkASSERT(riteE);
     SkASSERT(riteE->fNext == nullptr || leftE->fSavedY == riteE->fSavedY);
     SkASSERT(SkFixedFloorToInt(lowerY - 1) == SkFixedFloorToInt(leftE->fSavedY));
     int y = SkFixedFloorToInt(leftE->fSavedY);
-    // Instead of using f2a(lowerY - leftE->fSavedY), we use the following fullAlpha
+    // Instead of using fixed_to_alpha(lowerY - leftE->fSavedY), we use the following fullAlpha
     // to elimiate cumulative error: if there are many fractional y scan lines within the
     // same row, the former may accumulate the rounding error while the later won't.
-    SkAlpha fullAlpha = f2a(lowerY - SkIntToFixed(y)) - f2a(leftE->fSavedY - SkIntToFixed(y));
+    SkAlpha fullAlpha = fixed_to_alpha(lowerY - SkIntToFixed(y)) -
+                        fixed_to_alpha(leftE->fSavedY - SkIntToFixed(y));
     // We need fSavedDY because the (quad or cubic) edge might be updated
-    blit_trapezoid_row(blitter, y,
-            SkTMax(leftE->fSavedX, leftClip), SkTMin(riteE->fSavedX, rightClip),
-            SkTMax(lowerLeft, leftClip), SkTMin(lowerRite, rightClip),
-            leftE->fSavedDY, riteE->fSavedDY, fullAlpha, maskRow, isUsingMask,
-            noRealBlitter ||
-                    (fullAlpha == 0xFF && (edges_too_close(leftE->fPrev, leftE, lowerY)
-                            || edges_too_close(riteE, riteE->fNext, lowerY))),
+    blit_trapezoid_row(
+            blitter,
+            y,
+            SkTMax(leftE->fSavedX, leftClip),
+            SkTMin(riteE->fSavedX, rightClip),
+            SkTMax(lowerLeft, leftClip),
+            SkTMin(lowerRite, rightClip),
+            leftE->fSavedDY,
+            riteE->fSavedDY,
+            fullAlpha,
+            maskRow,
+            isUsingMask,
+            noRealBlitter || (fullAlpha == 0xFF && (edges_too_close(leftE->fPrev, leftE, lowerY) ||
+                                                    edges_too_close(riteE, riteE->fNext, lowerY))),
             true);
     leftE->fRiteE = nullptr;
 }
 
-static inline void deferred_blit(SkAnalyticEdge* leftE, SkAnalyticEdge* riteE,
-        SkFixed left, SkFixed leftDY, // don't save leftE->fX/fDY as they may have been updated
-        SkFixed y, SkFixed nextY, bool isIntegralNextY, bool leftEnds, bool riteEnds,
-        AdditiveBlitter* blitter, SkAlpha* maskRow, bool isUsingMask, bool noRealBlitter,
-        SkFixed leftClip, SkFixed rightClip, int yShift) {
+static void deferred_blit(SkAnalyticEdge* leftE,
+                          SkAnalyticEdge* riteE,
+                          SkFixed         left,
+                          SkFixed leftDY,  // don't save leftE->fX/fDY as they may have been updated
+                          SkFixed y,
+                          SkFixed nextY,
+                          bool    isIntegralNextY,
+                          bool    leftEnds,
+                          bool    riteEnds,
+                          AdditiveBlitter* blitter,
+                          SkAlpha*         maskRow,
+                          bool             isUsingMask,
+                          bool             noRealBlitter,
+                          SkFixed          leftClip,
+                          SkFixed          rightClip,
+                          int              yShift) {
     if (leftE->fRiteE && leftE->fRiteE != riteE) {
         // leftE's right edge changed. Blit the saved trapezoid.
         SkASSERT(leftE->fRiteE->fNext == nullptr || leftE->fRiteE->fY == y);
-        blit_saved_trapezoid(leftE, y, left, leftE->fRiteE->fX,
-                blitter, maskRow, isUsingMask, noRealBlitter, leftClip, rightClip);
+        blit_saved_trapezoid(leftE,
+                             y,
+                             left,
+                             leftE->fRiteE->fX,
+                             blitter,
+                             maskRow,
+                             isUsingMask,
+                             noRealBlitter,
+                             leftClip,
+                             rightClip);
     }
     if (!leftE->fRiteE) {
         // Save and defer blitting the trapezoid
@@ -1368,27 +1531,43 @@
     riteE->goY(nextY, yShift);
     // Always blit when edges end or nextY is integral
     if (isIntegralNextY || leftEnds || riteEnds) {
-        blit_saved_trapezoid(leftE, nextY, leftE->fX, riteE->fX,
-                blitter, maskRow, isUsingMask, noRealBlitter, leftClip, rightClip);
+        blit_saved_trapezoid(leftE,
+                             nextY,
+                             leftE->fX,
+                             riteE->fX,
+                             blitter,
+                             maskRow,
+                             isUsingMask,
+                             noRealBlitter,
+                             leftClip,
+                             rightClip);
     }
 }
 
-static void aaa_walk_edges(SkAnalyticEdge* prevHead, SkAnalyticEdge* nextTail,
-        SkPath::FillType fillType, AdditiveBlitter* blitter, int start_y, int stop_y,
-        SkFixed leftClip, SkFixed rightClip, bool isUsingMask, bool forceRLE, bool useDeferred,
-        bool skipIntersect) {
+static void aaa_walk_edges(SkAnalyticEdge*  prevHead,
+                           SkAnalyticEdge*  nextTail,
+                           SkPath::FillType fillType,
+                           AdditiveBlitter* blitter,
+                           int              start_y,
+                           int              stop_y,
+                           SkFixed          leftClip,
+                           SkFixed          rightClip,
+                           bool             isUsingMask,
+                           bool             forceRLE,
+                           bool             useDeferred,
+                           bool             skipIntersect) {
     prevHead->fX = prevHead->fUpperX = leftClip;
     nextTail->fX = nextTail->fUpperX = rightClip;
-    SkFixed y = SkTMax(prevHead->fNext->fUpperY, SkIntToFixed(start_y));
-    SkFixed nextNextY = SK_MaxS32;
+    SkFixed y                        = SkTMax(prevHead->fNext->fUpperY, SkIntToFixed(start_y));
+    SkFixed nextNextY                = SK_MaxS32;
 
     {
         SkAnalyticEdge* edge;
-        for(edge = prevHead->fNext; edge->fUpperY <= y; edge = edge->fNext) {
+        for (edge = prevHead->fNext; edge->fUpperY <= y; edge = edge->fNext) {
             edge->goY(y);
-            updateNextNextY(edge->fLowerY, y, &nextNextY);
+            update_next_next_y(edge->fLowerY, y, &nextNextY);
         }
-        updateNextNextY(edge->fUpperY, y, &nextNextY);
+        update_next_next_y(edge->fUpperY, y, &nextNextY);
     }
 
     // returns 1 for evenodd, -1 for winding, regardless of inverse-ness
@@ -1399,42 +1578,49 @@
     if (isInverse && SkIntToFixed(start_y) != y) {
         int width = SkFixedFloorToInt(rightClip - leftClip);
         if (SkFixedFloorToInt(y) != start_y) {
-            blitter->getRealBlitter()->blitRect(SkFixedFloorToInt(leftClip), start_y,
-                    width, SkFixedFloorToInt(y) - start_y);
+            blitter->getRealBlitter()->blitRect(
+                    SkFixedFloorToInt(leftClip), start_y, width, SkFixedFloorToInt(y) - start_y);
             start_y = SkFixedFloorToInt(y);
         }
-        SkAlpha* maskRow = isUsingMask ? static_cast<MaskAdditiveBlitter*>(blitter)->getRow(start_y)
-                                       : nullptr;
-        blit_full_alpha(blitter, start_y, SkFixedFloorToInt(leftClip), width,
-                f2a(y - SkIntToFixed(start_y)), maskRow, isUsingMask, false, false);
+        SkAlpha* maskRow =
+                isUsingMask ? static_cast<MaskAdditiveBlitter*>(blitter)->getRow(start_y) : nullptr;
+        blit_full_alpha(blitter,
+                        start_y,
+                        SkFixedFloorToInt(leftClip),
+                        width,
+                        fixed_to_alpha(y - SkIntToFixed(start_y)),
+                        maskRow,
+                        isUsingMask,
+                        false,
+                        false);
     }
 
     while (true) {
-        int     w = 0;
-        bool    in_interval     = isInverse;
-        SkFixed prevX           = prevHead->fX;
-        SkFixed nextY           = SkTMin(nextNextY, SkFixedCeilToFixed(y + 1));
-        bool isIntegralNextY    = (nextY & (SK_Fixed1 - 1)) == 0;
-        SkAnalyticEdge* currE   = prevHead->fNext;
-        SkAnalyticEdge* leftE   = prevHead;
-        SkFixed left            = leftClip;
-        SkFixed leftDY          = 0;
-        bool leftEnds           = false;
-        int prevRite            = SkFixedFloorToInt(leftClip);
+        int             w               = 0;
+        bool            in_interval     = isInverse;
+        SkFixed         prevX           = prevHead->fX;
+        SkFixed         nextY           = SkTMin(nextNextY, SkFixedCeilToFixed(y + 1));
+        bool            isIntegralNextY = (nextY & (SK_Fixed1 - 1)) == 0;
+        SkAnalyticEdge* currE           = prevHead->fNext;
+        SkAnalyticEdge* leftE           = prevHead;
+        SkFixed         left            = leftClip;
+        SkFixed         leftDY          = 0;
+        bool            leftEnds        = false;
+        int             prevRite        = SkFixedFloorToInt(leftClip);
 
-        nextNextY               = SK_MaxS32;
+        nextNextY = SK_MaxS32;
 
         SkASSERT((nextY & ((SK_Fixed1 >> 2) - 1)) == 0);
         int yShift = 0;
         if ((nextY - y) & (SK_Fixed1 >> 2)) {
             yShift = 2;
-            nextY = y + (SK_Fixed1 >> 2);
+            nextY  = y + (SK_Fixed1 >> 2);
         } else if ((nextY - y) & (SK_Fixed1 >> 1)) {
             yShift = 1;
             SkASSERT(nextY == y + (SK_Fixed1 >> 1));
         }
 
-        SkAlpha fullAlpha = f2a(nextY - y);
+        SkAlpha fullAlpha = fixed_to_alpha(nextY - y);
 
         // If we're using mask blitter, we advance the mask row in this function
         // to save some "if" condition checks.
@@ -1448,7 +1634,7 @@
 
         // Even if next - y == SK_Fixed1, we can still break the left-to-right order requirement
         // of the SKAAClip: |\| (two trapezoids with overlapping middle wedges)
-        bool noRealBlitter = forceRLE; // forceRLE && (nextY - y != SK_Fixed1);
+        bool noRealBlitter = forceRLE;  // forceRLE && (nextY - y != SK_Fixed1);
 
         while (currE->fUpperY <= y) {
             SkASSERT(currE->fLowerY >= nextY);
@@ -1456,10 +1642,10 @@
 
             w += currE->fWinding;
             bool prev_in_interval = in_interval;
-            in_interval = !(w & windingMask) == isInverse;
+            in_interval           = !(w & windingMask) == isInverse;
 
-            bool isLeft = in_interval && !prev_in_interval;
-            bool isRite = !in_interval && prev_in_interval;
+            bool isLeft   = in_interval && !prev_in_interval;
+            bool isRite   = !in_interval && prev_in_interval;
             bool currEnds = currE->fLowerY == nextY;
 
             if (useDeferred) {
@@ -1467,51 +1653,88 @@
                     // currE is a left edge previously, but now it's not.
                     // Blit the trapezoid between fSavedY and y.
                     SkASSERT(currE->fRiteE->fY == y);
-                    blit_saved_trapezoid(currE, y, currE->fX, currE->fRiteE->fX,
-                            blitter, maskRow, isUsingMask, noRealBlitter, leftClip, rightClip);
+                    blit_saved_trapezoid(currE,
+                                         y,
+                                         currE->fX,
+                                         currE->fRiteE->fX,
+                                         blitter,
+                                         maskRow,
+                                         isUsingMask,
+                                         noRealBlitter,
+                                         leftClip,
+                                         rightClip);
                 }
                 if (leftE->fRiteE == currE && !isRite) {
                     // currE is a right edge previously, but now it's not.
                     // Moreover, its corresponding leftE doesn't change (otherwise we'll handle it
                     // in the previous if clause). Hence we blit the trapezoid.
-                    blit_saved_trapezoid(leftE, y, left, currE->fX,
-                            blitter, maskRow, isUsingMask, noRealBlitter, leftClip, rightClip);
+                    blit_saved_trapezoid(leftE,
+                                         y,
+                                         left,
+                                         currE->fX,
+                                         blitter,
+                                         maskRow,
+                                         isUsingMask,
+                                         noRealBlitter,
+                                         leftClip,
+                                         rightClip);
                 }
             }
 
             if (isRite) {
                 if (useDeferred) {
-                    deferred_blit(leftE, currE, left, leftDY, y, nextY, isIntegralNextY,
-                            leftEnds, currEnds, blitter, maskRow, isUsingMask, noRealBlitter,
-                            leftClip, rightClip, yShift);
+                    deferred_blit(leftE,
+                                  currE,
+                                  left,
+                                  leftDY,
+                                  y,
+                                  nextY,
+                                  isIntegralNextY,
+                                  leftEnds,
+                                  currEnds,
+                                  blitter,
+                                  maskRow,
+                                  isUsingMask,
+                                  noRealBlitter,
+                                  leftClip,
+                                  rightClip,
+                                  yShift);
                 } else {
                     SkFixed rite = currE->fX;
                     currE->goY(nextY, yShift);
                     SkFixed nextLeft = SkTMax(leftClip, leftE->fX);
-                    rite = SkTMin(rightClip, rite);
+                    rite             = SkTMin(rightClip, rite);
                     SkFixed nextRite = SkTMin(rightClip, currE->fX);
-                    blit_trapezoid_row(blitter, y >> 16, left, rite, nextLeft, nextRite,
-                            leftDY, currE->fDY, fullAlpha, maskRow, isUsingMask,
-                            noRealBlitter || (fullAlpha == 0xFF && (
-                                    edges_too_close(prevRite, left, leftE->fX) ||
-                                    edges_too_close(currE, currE->fNext, nextY)
-                            )),
+                    blit_trapezoid_row(
+                            blitter,
+                            y >> 16,
+                            left,
+                            rite,
+                            nextLeft,
+                            nextRite,
+                            leftDY,
+                            currE->fDY,
+                            fullAlpha,
+                            maskRow,
+                            isUsingMask,
+                            noRealBlitter || (fullAlpha == 0xFF &&
+                                              (edges_too_close(prevRite, left, leftE->fX) ||
+                                               edges_too_close(currE, currE->fNext, nextY))),
                             true);
                     prevRite = SkFixedCeilToInt(SkTMax(rite, currE->fX));
                 }
             } else {
                 if (isLeft) {
-                    left = SkTMax(currE->fX, leftClip);
-                    leftDY = currE->fDY;
-                    leftE = currE;
+                    left     = SkTMax(currE->fX, leftClip);
+                    leftDY   = currE->fDY;
+                    leftE    = currE;
                     leftEnds = leftE->fLowerY == nextY;
                 }
                 currE->goY(nextY, yShift);
             }
 
-
             SkAnalyticEdge* next = currE->fNext;
-            SkFixed newX;
+            SkFixed         newX;
 
             while (currE->fLowerY <= nextY) {
                 if (currE->fCurveCount < 0) {
@@ -1535,22 +1758,30 @@
             if (currE->fLowerY <= nextY) {
                 remove_edge(currE);
             } else {
-                updateNextNextY(currE->fLowerY, nextY, &nextNextY);
+                update_next_next_y(currE->fLowerY, nextY, &nextNextY);
                 newX = currE->fX;
                 SkASSERT(currE->fLowerY > nextY);
-                if (newX < prevX) { // ripple currE backwards until it is x-sorted
+                if (newX < prevX) {  // ripple currE backwards until it is x-sorted
                     // If the crossing edge is a right edge, blit the saved trapezoid.
                     if (leftE->fRiteE == currE && useDeferred) {
                         SkASSERT(leftE->fY == nextY && currE->fY == nextY);
-                        blit_saved_trapezoid(leftE, nextY, leftE->fX, currE->fX,
-                                blitter, maskRow, isUsingMask, noRealBlitter, leftClip, rightClip);
+                        blit_saved_trapezoid(leftE,
+                                             nextY,
+                                             leftE->fX,
+                                             currE->fX,
+                                             blitter,
+                                             maskRow,
+                                             isUsingMask,
+                                             noRealBlitter,
+                                             leftClip,
+                                             rightClip);
                     }
                     backward_insert_edge_based_on_x(currE);
                 } else {
                     prevX = newX;
                 }
                 if (!skipIntersect) {
-                   checkIntersection(currE, nextY, &nextNextY);
+                    check_intersection(currE, nextY, &nextNextY);
                 }
             }
 
@@ -1561,17 +1792,37 @@
         // was our right-edge culled away?
         if (in_interval) {
             if (useDeferred) {
-                deferred_blit(leftE, nextTail, left, leftDY, y, nextY, isIntegralNextY,
-                        leftEnds, false, blitter, maskRow, isUsingMask, noRealBlitter,
-                        leftClip, rightClip, yShift);
+                deferred_blit(leftE,
+                              nextTail,
+                              left,
+                              leftDY,
+                              y,
+                              nextY,
+                              isIntegralNextY,
+                              leftEnds,
+                              false,
+                              blitter,
+                              maskRow,
+                              isUsingMask,
+                              noRealBlitter,
+                              leftClip,
+                              rightClip,
+                              yShift);
             } else {
-                blit_trapezoid_row(blitter, y >> 16,
-                        left, rightClip,
-                        SkTMax(leftClip, leftE->fX), rightClip,
-                        leftDY, 0, fullAlpha, maskRow, isUsingMask,
-                        noRealBlitter ||
-                                (fullAlpha == 0xFF && edges_too_close(leftE->fPrev, leftE, nextY)),
-                        true);
+                blit_trapezoid_row(blitter,
+                                   y >> 16,
+                                   left,
+                                   rightClip,
+                                   SkTMax(leftClip, leftE->fX),
+                                   rightClip,
+                                   leftDY,
+                                   0,
+                                   fullAlpha,
+                                   maskRow,
+                                   isUsingMask,
+                                   noRealBlitter || (fullAlpha == 0xFF &&
+                                                     edges_too_close(leftE->fPrev, leftE, nextY)),
+                                   true);
             }
         }
 
@@ -1589,14 +1840,20 @@
     }
 }
 
-static SK_ALWAYS_INLINE void aaa_fill_path(const SkPath& path, const SkIRect& clipRect,
-        AdditiveBlitter* blitter, int start_y, int stop_y, bool pathContainedInClip,
-        bool isUsingMask, bool forceRLE) { // forceRLE implies that SkAAClip is calling us
+static SK_ALWAYS_INLINE void aaa_fill_path(
+        const SkPath&    path,
+        const SkIRect&   clipRect,
+        AdditiveBlitter* blitter,
+        int              start_y,
+        int              stop_y,
+        bool             pathContainedInClip,
+        bool             isUsingMask,
+        bool             forceRLE) {  // forceRLE implies that SkAAClip is calling us
     SkASSERT(blitter);
 
     SkAnalyticEdgeBuilder builder;
-    int count = builder.buildEdges(path, pathContainedInClip ? nullptr : &clipRect);
-    SkAnalyticEdge** list = builder.analyticEdgeList();
+    int              count = builder.buildEdges(path, pathContainedInClip ? nullptr : &clipRect);
+    SkAnalyticEdge** list  = builder.analyticEdgeList();
 
     SkIRect rect = clipRect;
     if (0 == count) {
@@ -1614,8 +1871,8 @@
                 rect.fBottom = stop_y;
             }
             if (!rect.isEmpty()) {
-                blitter->getRealBlitter()->blitRect(rect.fLeft, rect.fTop,
-                        rect.width(), rect.height());
+                blitter->getRealBlitter()->blitRect(
+                        rect.fLeft, rect.fTop, rect.width(), rect.height());
             }
         }
         return;
@@ -1625,25 +1882,25 @@
     // this returns the first and last edge after they're sorted into a dlink list
     SkAnalyticEdge* edge = sort_edges(list, count, &last);
 
-    headEdge.fRiteE = nullptr;
-    headEdge.fPrev = nullptr;
-    headEdge.fNext = edge;
+    headEdge.fRiteE  = nullptr;
+    headEdge.fPrev   = nullptr;
+    headEdge.fNext   = edge;
     headEdge.fUpperY = headEdge.fLowerY = SK_MinS32;
-    headEdge.fX = SK_MinS32;
-    headEdge.fDX = 0;
-    headEdge.fDY = SK_MaxS32;
-    headEdge.fUpperX = SK_MinS32;
-    edge->fPrev = &headEdge;
+    headEdge.fX                         = SK_MinS32;
+    headEdge.fDX                        = 0;
+    headEdge.fDY                        = SK_MaxS32;
+    headEdge.fUpperX                    = SK_MinS32;
+    edge->fPrev                         = &headEdge;
 
-    tailEdge.fRiteE = nullptr;
-    tailEdge.fPrev = last;
-    tailEdge.fNext = nullptr;
+    tailEdge.fRiteE  = nullptr;
+    tailEdge.fPrev   = last;
+    tailEdge.fNext   = nullptr;
     tailEdge.fUpperY = tailEdge.fLowerY = SK_MaxS32;
-    tailEdge.fX = SK_MaxS32;
-    tailEdge.fDX = 0;
-    tailEdge.fDY = SK_MaxS32;
-    tailEdge.fUpperX = SK_MaxS32;
-    last->fNext = &tailEdge;
+    tailEdge.fX                         = SK_MaxS32;
+    tailEdge.fDX                        = 0;
+    tailEdge.fDY                        = SK_MaxS32;
+    tailEdge.fUpperX                    = SK_MaxS32;
+    last->fNext                         = &tailEdge;
 
     // now edge is the head of the sorted linklist
 
@@ -1654,55 +1911,73 @@
         stop_y = clipRect.fBottom;
     }
 
-    SkFixed leftBound = SkIntToFixed(rect.fLeft);
+    SkFixed leftBound  = SkIntToFixed(rect.fLeft);
     SkFixed rightBound = SkIntToFixed(rect.fRight);
     if (isUsingMask) {
         // If we're using mask, then we have to limit the bound within the path bounds.
         // Otherwise, the edge drift may access an invalid address inside the mask.
         SkIRect ir;
         path.getBounds().roundOut(&ir);
-        leftBound = SkTMax(leftBound, SkIntToFixed(ir.fLeft));
+        leftBound  = SkTMax(leftBound, SkIntToFixed(ir.fLeft));
         rightBound = SkTMin(rightBound, SkIntToFixed(ir.fRight));
     }
 
     if (!path.isInverseFillType() && path.isConvex() && count >= 2) {
-        aaa_walk_convex_edges(&headEdge, blitter, start_y, stop_y,
-                              leftBound, rightBound, isUsingMask);
+        aaa_walk_convex_edges(
+                &headEdge, blitter, start_y, stop_y, leftBound, rightBound, isUsingMask);
     } else {
         // Only use deferred blitting if there are many edges.
-        bool useDeferred = count >
+        bool useDeferred =
+                count >
                 (SkFixedFloorToInt(tailEdge.fPrev->fLowerY - headEdge.fNext->fUpperY) + 1) * 4;
 
         // We skip intersection computation if there are many points which probably already
         // give us enough fractional scan lines.
         bool skipIntersect = path.countPoints() > (stop_y - start_y) * 2;
 
-        aaa_walk_edges(&headEdge, &tailEdge, path.getFillType(), blitter, start_y, stop_y,
-               leftBound, rightBound, isUsingMask, forceRLE, useDeferred, skipIntersect);
+        aaa_walk_edges(&headEdge,
+                       &tailEdge,
+                       path.getFillType(),
+                       blitter,
+                       start_y,
+                       stop_y,
+                       leftBound,
+                       rightBound,
+                       isUsingMask,
+                       forceRLE,
+                       useDeferred,
+                       skipIntersect);
     }
 }
 
-///////////////////////////////////////////////////////////////////////////////
-
-void SkScan::AAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& ir,
-                         const SkIRect& clipBounds, bool forceRLE) {
+void SkScan::AAAFillPath(const SkPath&  path,
+                         SkBlitter*     blitter,
+                         const SkIRect& ir,
+                         const SkIRect& clipBounds,
+                         bool           forceRLE) {
     bool containedInClip = clipBounds.contains(ir);
-    bool isInverse = path.isInverseFillType();
+    bool isInverse       = path.isInverseFillType();
 
     // The mask blitter (where we store intermediate alpha values directly in a mask, and then call
     // the real blitter once in the end to blit the whole mask) is faster than the RLE blitter when
-    // the blit region is small enough (i.e., canHandleRect(ir)). When isInverse is true, the blit
+    // the blit region is small enough (i.e., CanHandleRect(ir)). When isInverse is true, the blit
     // region is no longer the rectangle ir so we won't use the mask blitter. The caller may also
     // use the forceRLE flag to force not using the mask blitter. Also, when the path is a simple
     // rect, preparing a mask and blitting it might have too much overhead. Hence we'll use
     // blitFatAntiRect to avoid the mask and its overhead.
-    if (MaskAdditiveBlitter::canHandleRect(ir) && !isInverse && !forceRLE) {
+    if (MaskAdditiveBlitter::CanHandleRect(ir) && !isInverse && !forceRLE) {
         // blitFatAntiRect is slower than the normal AAA flow without MaskAdditiveBlitter.
         // Hence only tryBlitFatAntiRect when MaskAdditiveBlitter would have been used.
         if (!TryBlitFatAntiRect(blitter, path, clipBounds)) {
             MaskAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse);
-            aaa_fill_path(path, clipBounds, &additiveBlitter, ir.fTop, ir.fBottom,
-                    containedInClip, true, forceRLE);
+            aaa_fill_path(path,
+                          clipBounds,
+                          &additiveBlitter,
+                          ir.fTop,
+                          ir.fBottom,
+                          containedInClip,
+                          true,
+                          forceRLE);
         }
     } else if (!isInverse && path.isConvex()) {
         // If the filling area is convex (i.e., path.isConvex && !isInverse), our simpler
@@ -1710,15 +1985,27 @@
         // SafeRLEAdditiveBlitter (which is slow due to clamping). The basic RLE blitter
         // RunBasedAdditiveBlitter would suffice.
         RunBasedAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse);
-        aaa_fill_path(path, clipBounds, &additiveBlitter, ir.fTop, ir.fBottom,
-                containedInClip, false, forceRLE);
+        aaa_fill_path(path,
+                      clipBounds,
+                      &additiveBlitter,
+                      ir.fTop,
+                      ir.fBottom,
+                      containedInClip,
+                      false,
+                      forceRLE);
     } else {
         // If the filling area might not be convex, the more involved aaa_walk_edges would
         // be called and we have to clamp the alpha downto 255. The SafeRLEAdditiveBlitter
         // does that at a cost of performance.
         SafeRLEAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse);
-        aaa_fill_path(path, clipBounds, &additiveBlitter, ir.fTop, ir.fBottom,
-                containedInClip, false, forceRLE);
+        aaa_fill_path(path,
+                      clipBounds,
+                      &additiveBlitter,
+                      ir.fTop,
+                      ir.fBottom,
+                      containedInClip,
+                      false,
+                      forceRLE);
     }
 }
-#endif //defined(SK_DISABLE_AAA)
+#endif  // defined(SK_DISABLE_AAA)
diff --git a/src/core/SkScan_AntiPath.cpp b/src/core/SkScan_AntiPath.cpp
index b3b3c77..d21cc2b 100644
--- a/src/core/SkScan_AntiPath.cpp
+++ b/src/core/SkScan_AntiPath.cpp
@@ -9,7 +9,6 @@
 
 #include "SkAntiRun.h"
 #include "SkBlitter.h"
-#include "SkCoverageDelta.h"
 #include "SkMatrix.h"
 #include "SkPath.h"
 #include "SkPathPriv.h"
@@ -600,7 +599,7 @@
 }
 
 constexpr int kSampleSize = 8;
-#if !defined(SK_DISABLE_DAA) || !defined(SK_DISABLE_AAA)
+#if !defined(SK_DISABLE_AAA)
     constexpr SkScalar kComplexityThreshold = 0.25;
 #endif
 
@@ -637,51 +636,6 @@
     }
 }
 
-static bool ShouldUseDAA(const SkPath& path, SkScalar avgLength, SkScalar complexity) {
-#if defined(SK_DISABLE_DAA)
-    return false;
-#else
-    if (gSkForceDeltaAA) {
-        return true;
-    }
-    if (!gSkUseDeltaAA || SkPathPriv::IsBadForDAA(path)) {
-        return false;
-    }
-
-    #ifdef SK_SUPPORT_LEGACY_AA_CHOICE
-        const SkRect& bounds = path.getBounds();
-        return !path.isConvex()
-            && path.countPoints() >= SkTMax(bounds.width(), bounds.height()) / 8;
-    #else
-        if (avgLength < 0 || complexity < 0 || path.getBounds().isEmpty() || path.isConvex()) {
-            return false;
-        }
-
-        // DAA is fast with mask
-        if (SkCoverageDeltaMask::CanHandle(safeRoundOut(path.getBounds()))) {
-            return true;
-        }
-
-        // DAA is much faster in small cubics (since we don't have to chop them).
-        // If there are many cubics, and the average length if small, use DAA.
-        constexpr SkScalar kSmallCubicThreshold = 16;
-        if (avgLength < kSmallCubicThreshold) {
-            uint8_t sampleVerbs[kSampleSize];
-            int verbCount = SkTMin(kSampleSize, path.getVerbs(sampleVerbs, kSampleSize));
-            int cubicCount = 0;
-            for(int i = 0; i < verbCount; ++i) {
-                cubicCount += (sampleVerbs[i] == SkPath::kCubic_Verb);
-            }
-            if (cubicCount * 2 >= verbCount) {
-                return true;
-            }
-        }
-
-        return complexity >= kComplexityThreshold;
-    #endif
-#endif
-}
-
 static bool ShouldUseAAA(const SkPath& path, SkScalar avgLength, SkScalar complexity) {
 #if defined(SK_DISABLE_AAA)
     return false;
@@ -759,9 +713,8 @@
 }
 
 void SkScan::AntiFillPath(const SkPath& path, const SkRegion& origClip,
-                          SkBlitter* blitter, bool forceRLE, SkDAARecord* daaRecord) {
+                          SkBlitter* blitter, bool forceRLE) {
     if (origClip.isEmpty()) {
-        SkDAARecord::SetEmpty(daaRecord);
         return;
     }
 
@@ -771,7 +724,6 @@
         if (isInverse) {
             blitter->blitRegion(origClip);
         }
-        SkDAARecord::SetEmpty(daaRecord);
         return;
     }
 
@@ -785,11 +737,10 @@
        clippedIR = origClip.getBounds();
     } else {
        if (!clippedIR.intersect(ir, origClip.getBounds())) {
-            SkDAARecord::SetEmpty(daaRecord);
            return;
        }
     }
-    if (!daaRecord && rect_overflows_short_shift(clippedIR, SHIFT)) {
+    if (rect_overflows_short_shift(clippedIR, SHIFT)) {
         SkScan::FillPath(path, origClip, blitter);
         return;
     }
@@ -819,7 +770,6 @@
         if (isInverse) {
             blitter->blitRegion(*clipRgn);
         }
-        SkDAARecord::SetEmpty(daaRecord);
         return;
     }
 
@@ -836,9 +786,7 @@
     SkScalar avgLength, complexity;
     compute_complexity(path, avgLength, complexity);
 
-    if (daaRecord || ShouldUseDAA(path, avgLength, complexity)) {
-        SkScan::DAAFillPath(path, blitter, ir, clipRgn->getBounds(), forceRLE, daaRecord);
-    } else if (ShouldUseAAA(path, avgLength, complexity)) {
+    if (ShouldUseAAA(path, avgLength, complexity)) {
         // Do not use AAA if path is too complicated:
         // there won't be any speedup or significant visual improvement.
         SkScan::AAAFillPath(path, blitter, ir, clipRgn->getBounds(), forceRLE);
@@ -872,21 +820,19 @@
     }
 }
 
-void SkScan::AntiFillPath(const SkPath& path, const SkRasterClip& clip,
-                          SkBlitter* blitter, SkDAARecord* daaRecord) {
+void SkScan::AntiFillPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) {
     if (clip.isEmpty() || !path.isFinite()) {
-        SkDAARecord::SetEmpty(daaRecord);
         return;
     }
 
     if (clip.isBW()) {
-        AntiFillPath(path, clip.bwRgn(), blitter, false, daaRecord);
+        AntiFillPath(path, clip.bwRgn(), blitter, false);
     } else {
         SkRegion        tmp;
         SkAAClipBlitter aaBlitter;
 
         tmp.setRect(clip.getBounds());
         aaBlitter.init(blitter, &clip.aaRgn());
-        AntiFillPath(path, tmp, &aaBlitter, true, daaRecord); // SkAAClipBlitter can blitMask, why forceRLE?
+        AntiFillPath(path, tmp, &aaBlitter, true); // SkAAClipBlitter can blitMask, why forceRLE?
     }
 }
diff --git a/src/core/SkScan_DAAPath.cpp b/src/core/SkScan_DAAPath.cpp
deleted file mode 100644
index 53da58a..0000000
--- a/src/core/SkScan_DAAPath.cpp
+++ /dev/null
@@ -1,393 +0,0 @@
-/*
- * Copyright 2016 The Android Open Source Project
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkAnalyticEdge.h"
-#include "SkAntiRun.h"
-#include "SkAutoMalloc.h"
-#include "SkBlitter.h"
-#include "SkCoverageDelta.h"
-#include "SkEdge.h"
-#include "SkEdgeBuilder.h"
-#include "SkGeometry.h"
-#include "SkMask.h"
-#include "SkPath.h"
-#include "SkQuadClipper.h"
-#include "SkRasterClip.h"
-#include "SkRegion.h"
-#include "SkScan.h"
-#include "SkScanPriv.h"
-#include "SkTSort.h"
-#include "SkTemplates.h"
-#include "SkUTF.h"
-
-#if defined(SK_DISABLE_DAA)
-void SkScan::DAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& ir,
-                         const SkIRect& clipBounds, bool forceRLE, SkDAARecord* record) {
-    SkDEBUGFAIL("DAA Disabled");
-    return;
-}
-#else
-///////////////////////////////////////////////////////////////////////////////
-
-/*
-
-DAA stands for Delta-based Anti-Aliasing.
-
-This is an improved version of our analytic AA algorithm (in SkScan_AAAPath.cpp)
-which first scan convert paths into coverage deltas (this step can happen out of order,
-and we don't seem to be needed to worry about the intersection, clamping, etc.),
-and then use a single blitter run to convert all those deltas into the final alphas.
-
-Before we go to the final blitter run, we'll use SkFixed for all delta values so we
-don't ever have to worry about under/overflow.
-
-*/
-
-///////////////////////////////////////////////////////////////////////////////
-
-// The following helper functions are the same as those from SkScan_AAAPath.cpp
-// except that we use SkFixed everywhere instead of SkAlpha.
-
-static inline SkFixed SkFixedMul_lowprec(SkFixed a, SkFixed b) {
-    return (a >> 8) * (b >> 8);
-}
-
-// Return the alpha of a trapezoid whose height is 1
-static inline SkFixed trapezoidToAlpha(SkFixed l1, SkFixed l2) {
-    SkASSERT(l1 >= 0 && l2 >= 0);
-    return (l1 + l2) >> 1;
-}
-
-// The alpha of right-triangle (a, a*b)
-static inline SkFixed partialTriangleToAlpha(SkFixed a, SkFixed b) {
-    SkASSERT(a <= SK_Fixed1);
-    // SkFixedMul(SkFixedMul(a, a), b) >> 1
-    // return ((((a >> 8) * (a >> 8)) >> 8) * (b >> 8)) >> 1;
-    return (a >> 11) * (a >> 11) * (b >> 11);
-}
-
-static inline SkFixed getPartialAlpha(SkFixed alpha, SkFixed partialMultiplier) {
-    // DAA should not be so sensitive to the precision (compared to AAA).
-    return SkFixedMul_lowprec(alpha, partialMultiplier);
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-template<bool isPartial, class Deltas>
-static inline void add_coverage_delta_segment(int y, SkFixed rowHeight, const SkAnalyticEdge* edge,
-        SkFixed nextX, Deltas* deltas) { // rowHeight=fullAlpha
-    SkASSERT(rowHeight <= SK_Fixed1 && rowHeight >= 0);
-
-    // Let's see if multiplying sign is faster than multiplying edge->fWinding.
-    // (Compiler should be able to optimize multiplication with 1/-1?)
-    int sign = edge->fWinding == 1 ? 1 : -1;
-
-    SkFixed l = SkTMin(edge->fX, nextX);
-    SkFixed r = edge->fX + nextX - l;
-    int     L = SkFixedFloorToInt(l);
-    int     R = SkFixedCeilToInt(r);
-    int     len = R - L;
-
-    switch (len) {
-        case 0: {
-            deltas->addDelta(L, y, rowHeight * sign);
-            return;
-        }
-        case 1: {
-            SkFixed fixedR  = SkIntToFixed(R);
-            SkFixed alpha   = trapezoidToAlpha(fixedR - l, fixedR - r);
-            if (isPartial) {
-                alpha = getPartialAlpha(alpha, rowHeight);
-            }
-            deltas->addDelta(L,     y,  alpha * sign);
-            deltas->addDelta(L + 1, y,  (rowHeight - alpha) * sign);
-            return;
-        }
-        case 2: {
-            SkFixed middle  = SkIntToFixed(L + 1);
-            SkFixed x1      = middle - l;
-            SkFixed x2      = r - middle;
-            SkFixed alpha1  = partialTriangleToAlpha(x1, edge->fDY);
-            SkFixed alpha2  = rowHeight - partialTriangleToAlpha(x2, edge->fDY);
-            deltas->addDelta(L,     y,  alpha1 * sign);
-            deltas->addDelta(L + 1, y,  (alpha2 - alpha1) * sign);
-            deltas->addDelta(L + 2, y,  (rowHeight - alpha2) * sign);
-            return;
-        }
-    }
-
-    // When len > 2, computations are similar to computeAlphaAboveLine in SkScan_AAAPath.cpp
-    SkFixed dY      = edge->fDY;
-    SkFixed fixedL  = SkIntToFixed(L);
-    SkFixed fixedR  = SkIntToFixed(R);
-    SkFixed first   = SK_Fixed1 + fixedL - l; // horizontal edge of the left-most triangle
-    SkFixed last    = r - (fixedR - SK_Fixed1); // horizontal edge of the right-most triangle
-    SkFixed firstH  = SkFixedMul_lowprec(first, dY); // vertical edge of the left-most triangle
-
-    SkFixed alpha0  = SkFixedMul_lowprec(first, firstH) >> 1;   // triangle alpha
-    SkFixed alpha1  = firstH + (dY >> 1);                       // rectangle plus triangle
-    deltas->addDelta(L, y, alpha0 * sign);
-    deltas->addDelta(L + 1, y, (alpha1 - alpha0) * sign);
-    for(int i = 2; i < len - 1; ++i) {
-        deltas->addDelta(L + i, y, dY * sign); // the increment is always a rect of height = dY
-    }
-
-    SkFixed alphaR2     = alpha1 + dY * (len - 3);                      // the alpha at R - 2
-    SkFixed lastAlpha   = rowHeight - partialTriangleToAlpha(last, dY); // the alpha at R - 1
-    deltas->addDelta(R - 1, y, (lastAlpha - alphaR2) * sign);
-    deltas->addDelta(R,     y, (rowHeight - lastAlpha) * sign);
-}
-
-class XLessThan {
-public:
-    bool operator()(const SkBezier* a, const SkBezier* b) {
-        return a->fP0.fX + a->fP1.fX < b->fP0.fX + b->fP1.fX;
-    }
-};
-
-class YLessThan {
-public:
-    bool operator()(const SkBezier* a, const SkBezier* b) {
-        return a->fP0.fY + a->fP1.fY < b->fP0.fY + b->fP1.fY;
-    }
-};
-
-template<class Deltas> static SK_ALWAYS_INLINE
-void gen_alpha_deltas(const SkPath& path, const SkIRect& clippedIR, const SkIRect& clipBounds,
-        Deltas& result, SkBlitter* blitter, bool skipRect, bool pathContainedInClip) {
-    // 1. Build edges
-    SkBezierEdgeBuilder builder;
-    // We have to use clipBounds instead of clippedIR to build edges because of "canCullToTheRight":
-    // if the builder finds a right edge past the right clip, it won't build that right edge.
-    int  count = builder.buildEdges(path, pathContainedInClip ? nullptr : &clipBounds);
-
-    if (count == 0) {
-        return;
-    }
-    SkBezier** list = builder.bezierList();
-
-    // 2. Try to find the rect part because blitAntiRect is so much faster than blitCoverageDeltas
-    int rectTop = clippedIR.fBottom;   // the rect is initialized to be empty as top = bot
-    int rectBot = clippedIR.fBottom;
-    if (skipRect) {             // only find that rect is skipRect == true
-        YLessThan lessThan;     // sort edges in YX order
-        SkTQSort(list, list + count - 1, lessThan);
-        for(int i = 0; i < count - 1; ++i) {
-            SkBezier* lb = list[i];
-            SkBezier* rb = list[i + 1];
-
-            // fCount == 2 ensures that lb and rb are lines instead of quads or cubics.
-            bool lDX0 = lb->fP0.fX == lb->fP1.fX && lb->fCount == 2;
-            bool rDX0 = rb->fP0.fX == rb->fP1.fX && rb->fCount == 2;
-            if (!lDX0 || !rDX0) { // make sure that the edges are vertical
-                continue;
-            }
-
-            SkAnalyticEdge l, r;
-            if (!l.setLine(lb->fP0, lb->fP1) || !r.setLine(rb->fP0, rb->fP1)) {
-                continue;
-            }
-
-            SkFixed xorUpperY = l.fUpperY ^ r.fUpperY;
-            SkFixed xorLowerY = l.fLowerY ^ r.fLowerY;
-            if ((xorUpperY | xorLowerY) == 0) { // equal upperY and lowerY
-                rectTop = SkFixedCeilToInt(l.fUpperY);
-                rectBot = SkFixedFloorToInt(l.fLowerY);
-                if (rectBot > rectTop) { // if bot == top, the rect is too short for blitAntiRect
-                    int L = SkFixedCeilToInt(l.fUpperX);
-                    int R = SkFixedFloorToInt(r.fUpperX);
-                    if (L <= R) {
-                        SkAlpha la = (SkIntToFixed(L) - l.fUpperX) >> 8;
-                        SkAlpha ra = (r.fUpperX - SkIntToFixed(R)) >> 8;
-                        result.setAntiRect(L - 1, rectTop, R - L, rectBot - rectTop, la, ra);
-                    } else { // too thin to use blitAntiRect; reset the rect region to be emtpy
-                        rectTop = rectBot = clippedIR.fBottom;
-                    }
-                }
-                break;
-            }
-
-        }
-    }
-
-    // 3. Sort edges in x so we may need less sorting for delta based on x. This only helps
-    //    SkCoverageDeltaList. And we don't want to sort more than SORT_THRESHOLD edges where
-    //    the log(count) factor of the quick sort may become a bottleneck; when there are so
-    //    many edges, we're unlikely to make deltas sorted anyway.
-    constexpr int SORT_THRESHOLD = 256;
-    if (std::is_same<Deltas, SkCoverageDeltaList>::value && count < SORT_THRESHOLD) {
-        XLessThan lessThan;
-        SkTQSort(list, list + count - 1, lessThan);
-    }
-
-    // Future todo: parallize and SIMD the following code.
-    // 4. iterate through edges and generate deltas
-    for(int index = 0; index < count; ++index) {
-        SkAnalyticCubicEdge storage;
-        SkASSERT(sizeof(SkAnalyticQuadraticEdge) >= sizeof(SkAnalyticEdge));
-        SkASSERT(sizeof(SkAnalyticCubicEdge) >= sizeof(SkAnalyticQuadraticEdge));
-
-        SkBezier* bezier        = list[index];
-        SkAnalyticEdge* currE   = &storage;
-        bool edgeSet            = false;
-
-        int originalWinding = 1;
-        bool sortY = true;
-        switch (bezier->fCount) {
-            case 2: {
-                edgeSet = currE->setLine(bezier->fP0, bezier->fP1);
-                originalWinding = currE->fWinding;
-                break;
-            }
-            case 3: {
-                SkQuad* quad = static_cast<SkQuad*>(bezier);
-                SkPoint pts[3] = {quad->fP0, quad->fP1, quad->fP2};
-                edgeSet = static_cast<SkAnalyticQuadraticEdge*>(currE)->setQuadratic(pts);
-                originalWinding = static_cast<SkAnalyticQuadraticEdge*>(currE)->fQEdge.fWinding;
-                break;
-            }
-            case 4: {
-                sortY = false;
-                SkCubic* cubic = static_cast<SkCubic*>(bezier);
-                SkPoint pts[4] = {cubic->fP0, cubic->fP1, cubic->fP2, cubic->fP3};
-                edgeSet = static_cast<SkAnalyticCubicEdge*>(currE)->setCubic(pts, sortY);
-                originalWinding = static_cast<SkAnalyticCubicEdge*>(currE)->fCEdge.fWinding;
-                break;
-            }
-        }
-
-        if (!edgeSet) {
-            continue;
-        }
-
-        do {
-            currE->fX =  currE->fUpperX;
-
-            SkFixed upperFloor  = SkFixedFloorToFixed(currE->fUpperY);
-            SkFixed lowerCeil   = SkFixedCeilToFixed(currE->fLowerY);
-            int     iy          = SkFixedFloorToInt(upperFloor);
-
-            if (lowerCeil <= upperFloor + SK_Fixed1) { // only one row is affected by the currE
-                SkFixed rowHeight = currE->fLowerY - currE->fUpperY;
-                SkFixed nextX = currE->fX + SkFixedMul(currE->fDX, rowHeight);
-                if (iy >= clippedIR.fTop && iy < clippedIR.fBottom) {
-                    add_coverage_delta_segment<true>(iy, rowHeight, currE, nextX, &result);
-                }
-                continue;
-            }
-
-            // check first row
-            SkFixed rowHeight = upperFloor + SK_Fixed1 - currE->fUpperY;
-            SkFixed nextX;
-            if (rowHeight != SK_Fixed1) {   // it's a partial row
-                nextX = currE->fX + SkFixedMul(currE->fDX, rowHeight);
-                add_coverage_delta_segment<true>(iy, rowHeight, currE, nextX, &result);
-            } else {                        // it's a full row so we can leave it to the while loop
-                iy--;                       // compensate the iy++ in the while loop
-                nextX = currE->fX;
-            }
-
-            while (true) { // process the full rows in the middle
-                iy++;
-                SkFixed y = SkIntToFixed(iy);
-                currE->fX = nextX;
-                nextX += currE->fDX;
-
-                if (y + SK_Fixed1 > currE->fLowerY) {
-                    break; // no full rows left, break
-                }
-
-                // Check whether we're in the rect part that will be covered by blitAntiRect
-                if (iy >= rectTop && iy < rectBot) {
-                    SkASSERT(currE->fDX == 0);  // If yes, we must be on an edge with fDX = 0.
-                    iy = rectBot - 1;           // Skip the rect part by advancing iy to the bottom.
-                    continue;
-                }
-
-                // Add current edge's coverage deltas on this full row
-                add_coverage_delta_segment<false>(iy, SK_Fixed1, currE, nextX, &result);
-            }
-
-            // last partial row
-            if (SkIntToFixed(iy) < currE->fLowerY &&
-                    iy >= clippedIR.fTop && iy < clippedIR.fBottom) {
-                rowHeight = currE->fLowerY - SkIntToFixed(iy);
-                nextX = currE->fX + SkFixedMul(currE->fDX, rowHeight);
-                add_coverage_delta_segment<true>(iy, rowHeight, currE, nextX, &result);
-            }
-        // Intended assignment to fWinding to restore the maybe-negated winding (during updateLine)
-        } while ((currE->fWinding = originalWinding) && currE->update(currE->fLowerY, sortY));
-    }
-}
-
-void SkScan::DAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& ir,
-                         const SkIRect& clipBounds, bool forceRLE, SkDAARecord* record) {
-    bool containedInClip = clipBounds.contains(ir);
-    bool isEvenOdd  = path.getFillType() & 1;
-    bool isConvex   = path.isConvex();
-    bool isInverse  = path.isInverseFillType();
-    bool skipRect   = isConvex && !isInverse;
-    bool isInitOnce = record && record->fType == SkDAARecord::Type::kToBeComputed;
-
-    SkIRect clippedIR = ir;
-    clippedIR.intersect(clipBounds);
-
-    // The overhead of even constructing SkCoverageDeltaList/Mask is too big.
-    // So TryBlitFatAntiRect and return if it's successful.
-    if (!isInverse && TryBlitFatAntiRect(blitter, path, clipBounds)) {
-        SkDAARecord::SetEmpty(record);
-        return;
-    }
-
-#ifdef SK_BUILD_FOR_GOOGLE3
-    constexpr int STACK_SIZE = 12 << 10; // 12K stack size alloc; Google3 has 16K limit.
-#else
-    constexpr int STACK_SIZE = 64 << 10; // 64k stack size to avoid heap allocation
-#endif
-    SkSTArenaAlloc<STACK_SIZE> stackAlloc; // avoid heap allocation with SkSTArenaAlloc
-
-    // Set alloc to record's alloc if and only if we're in the init-once phase. We have to do that
-    // during init phase because the mask or list needs to live longer. We can't do that during blit
-    // phase because the same record could be accessed by multiple threads simultaneously.
-    SkArenaAlloc* alloc = isInitOnce ? record->fAlloc : &stackAlloc;
-
-    if (record == nullptr) {
-        record = alloc->make<SkDAARecord>(alloc);
-    }
-
-    // Only blitter->blitXXX needs to be done in order in the threaded backend. Everything else can
-    // be done out of order in the init-once phase. We do that by calling DAAFillPath twice: first
-    // with a null blitter, and then second with the real blitter and the SkMask/SkCoverageDeltaList
-    // generated in the first step.
-    if (record->fType == SkDAARecord::Type::kToBeComputed) {
-        if (!forceRLE && !isInverse && SkCoverageDeltaMask::Suitable(clippedIR)) {
-            record->fType = SkDAARecord::Type::kMask;
-            SkCoverageDeltaMask deltaMask(alloc, clippedIR);
-            gen_alpha_deltas(path, clippedIR, clipBounds, deltaMask, blitter, skipRect,
-                             containedInClip);
-            deltaMask.convertCoverageToAlpha(isEvenOdd, isInverse, isConvex);
-            record->fMask = deltaMask.prepareSkMask();
-        } else {
-            record->fType = SkDAARecord::Type::kList;
-            SkCoverageDeltaList* deltaList = alloc->make<SkCoverageDeltaList>(
-                    alloc, clippedIR, forceRLE);
-            gen_alpha_deltas(path, clippedIR, clipBounds, *deltaList, blitter, skipRect,
-                             containedInClip);
-            record->fList = deltaList;
-        }
-    }
-
-    if (!isInitOnce) {
-        SkASSERT(record->fType != SkDAARecord::Type::kToBeComputed);
-        if (record->fType == SkDAARecord::Type::kMask) {
-            blitter->blitMask(record->fMask, clippedIR);
-        } else {
-            blitter->blitCoverageDeltas(record->fList, clipBounds, isEvenOdd, isInverse, isConvex);
-        }
-    }
-}
-#endif //defined(SK_DISABLE_DAA)
diff --git a/src/core/SkSpan.h b/src/core/SkSpan.h
index 8f8e96b..42d536e 100644
--- a/src/core/SkSpan.h
+++ b/src/core/SkSpan.h
@@ -21,7 +21,11 @@
     template <typename U>
     constexpr explicit SkSpan(std::vector<U>& v) : fPtr{v.data()}, fSize{v.size()} {}
     constexpr SkSpan(const SkSpan& o) = default;
-    constexpr SkSpan& operator=( const SkSpan& other ) = default;
+    constexpr SkSpan& operator=(const SkSpan& that) {
+        fPtr = that.fPtr;
+        fSize = that.fSize;
+        return *this;
+    }
     constexpr T& operator [] (size_t i) const { return fPtr[i]; }
     constexpr T* begin() const { return fPtr; }
     constexpr T* end() const { return fPtr + fSize; }
@@ -32,6 +36,7 @@
     constexpr bool empty() const { return fSize == 0; }
     constexpr size_t size_bytes() const { return fSize * sizeof(T); }
     constexpr SkSpan<const T> toConst() const { return SkSpan<const T>{fPtr, fSize}; }
+    constexpr SkSpan<T> first(size_t prefixLen) { return SkSpan<T>{fPtr, prefixLen}; }
 
 private:
     T* fPtr;
diff --git a/src/core/SkSpecialImage.cpp b/src/core/SkSpecialImage.cpp
index e7a4143..c3a55e3 100644
--- a/src/core/SkSpecialImage.cpp
+++ b/src/core/SkSpecialImage.cpp
@@ -19,6 +19,8 @@
 #include "GrContext.h"
 #include "GrContextPriv.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrSurfaceContext.h"
 #include "GrTextureProxy.h"
 #include "SkImage_Gpu.h"
@@ -43,12 +45,12 @@
 
     virtual bool onGetROPixels(SkBitmap*) const = 0;
 
-    virtual GrContext* onGetContext() const { return nullptr; }
+    virtual GrRecordingContext* onGetContext() const { return nullptr; }
 
     virtual SkColorSpace* onGetColorSpace() const = 0;
 
 #if SK_SUPPORT_GPU
-    virtual sk_sp<GrTextureProxy> onAsTextureProxyRef(GrContext* context) const = 0;
+    virtual sk_sp<GrTextureProxy> onAsTextureProxyRef(GrRecordingContext* context) const = 0;
 #endif
 
     virtual sk_sp<SkSpecialImage> onMakeSubset(const SkIRect& subset) const = 0;
@@ -79,13 +81,13 @@
     , fUniqueID(kNeedNewImageUniqueID_SpecialImage == uniqueID ? SkNextID::ImageID() : uniqueID) {
 }
 
-sk_sp<SkSpecialImage> SkSpecialImage::makeTextureImage(GrContext* context) {
+sk_sp<SkSpecialImage> SkSpecialImage::makeTextureImage(GrRecordingContext* context) {
 #if SK_SUPPORT_GPU
     if (!context) {
         return nullptr;
     }
-    if (GrContext* curContext = as_SIB(this)->onGetContext()) {
-        return curContext == context ? sk_sp<SkSpecialImage>(SkRef(this)) : nullptr;
+    if (GrRecordingContext* curContext = as_SIB(this)->onGetContext()) {
+        return curContext->priv().matches(context) ? sk_sp<SkSpecialImage>(SkRef(this)) : nullptr;
     }
 
     auto proxyProvider = context->priv().proxyProvider();
@@ -137,7 +139,7 @@
     return SkToBool(as_SIB(this)->onGetContext());
 }
 
-GrContext* SkSpecialImage::getContext() const {
+GrRecordingContext* SkSpecialImage::getContext() const {
     return as_SIB(this)->onGetContext();
 }
 
@@ -146,7 +148,7 @@
 }
 
 #if SK_SUPPORT_GPU
-sk_sp<GrTextureProxy> SkSpecialImage::asTextureProxyRef(GrContext* context) const {
+sk_sp<GrTextureProxy> SkSpecialImage::asTextureProxyRef(GrRecordingContext* context) const {
     return as_SIB(this)->onAsTextureProxyRef(context);
 }
 #endif
@@ -184,20 +186,20 @@
 }
 #endif
 
-sk_sp<SkSpecialImage> SkSpecialImage::MakeFromImage(GrContext* context,
+sk_sp<SkSpecialImage> SkSpecialImage::MakeFromImage(GrRecordingContext* context,
                                                     const SkIRect& subset,
                                                     sk_sp<SkImage> image,
                                                     const SkSurfaceProps* props) {
     SkASSERT(rect_fits(subset, image->width(), image->height()));
 
 #if SK_SUPPORT_GPU
-    if (sk_sp<GrTextureProxy> proxy = as_IB(image)->asTextureProxyRef()) {
-        if (as_IB(image)->contextID() != context->priv().contextID()) {
+    if (sk_sp<GrTextureProxy> proxy = as_IB(image)->asTextureProxyRef(context)) {
+        if (!as_IB(image)->context()->priv().matches(context)) {
             return nullptr;
         }
 
         return MakeDeferredFromGpu(context, subset, image->uniqueID(), std::move(proxy),
-                                   as_IB(image)->onImageInfo().refColorSpace(), props);
+                                   image->refColorSpace(), props);
     } else
 #endif
     {
@@ -243,7 +245,7 @@
     }
 
 #if SK_SUPPORT_GPU
-    sk_sp<GrTextureProxy> onAsTextureProxyRef(GrContext* context) const override {
+    sk_sp<GrTextureProxy> onAsTextureProxyRef(GrRecordingContext* context) const override {
         if (context) {
             return GrMakeCachedBitmapProxy(context->priv().proxyProvider(), fBitmap);
         }
@@ -252,21 +254,11 @@
     }
 #endif
 
-// TODO: The raster implementations of image filters all currently assume that the pixels are
-// legacy N32. Until they actually check the format and operate on sRGB or F16 data appropriately,
-// we can't enable this. (They will continue to produce incorrect results, but less-so).
-#define RASTER_IMAGE_FILTERS_SUPPORT_SRGB_AND_F16 0
-
     sk_sp<SkSpecialSurface> onMakeSurface(const SkImageFilter::OutputProperties& outProps,
                                           const SkISize& size, SkAlphaType at,
                                           const SkSurfaceProps* props) const override {
-#if RASTER_IMAGE_FILTERS_SUPPORT_SRGB_AND_F16
         SkColorSpace* colorSpace = outProps.colorSpace();
-#else
-        SkColorSpace* colorSpace = nullptr;
-#endif
-        SkColorType colorType = colorSpace && colorSpace->gammaIsLinear()
-            ? kRGBA_F16_SkColorType : kN32_SkColorType;
+        SkColorType colorType = kN32_SkColorType;   // TODO: find ways to allow f16
         SkImageInfo info = SkImageInfo::Make(size.width(), size.height(), colorType, at,
                                              sk_ref_sp(colorSpace));
         return SkSpecialSurface::MakeRaster(info, props);
@@ -300,13 +292,8 @@
 
     sk_sp<SkSurface> onMakeTightSurface(const SkImageFilter::OutputProperties& outProps,
                                         const SkISize& size, SkAlphaType at) const override {
-#if RASTER_IMAGE_FILTERS_SUPPORT_SRGB_AND_F16
         SkColorSpace* colorSpace = outProps.colorSpace();
-#else
-        SkColorSpace* colorSpace = nullptr;
-#endif
-        SkColorType colorType = colorSpace && colorSpace->gammaIsLinear()
-            ? kRGBA_F16_SkColorType : kN32_SkColorType;
+        SkColorType colorType = kN32_SkColorType;   // TODO: find ways to allow f16
         SkImageInfo info = SkImageInfo::Make(size.width(), size.height(), colorType, at,
                                              sk_ref_sp(colorSpace));
         return SkSurface::MakeRaster(info);
@@ -362,15 +349,17 @@
 
 #if SK_SUPPORT_GPU
 ///////////////////////////////////////////////////////////////////////////////
-static sk_sp<SkImage> wrap_proxy_in_image(GrContext* context, sk_sp<GrTextureProxy> proxy,
+static sk_sp<SkImage> wrap_proxy_in_image(GrRecordingContext* context, sk_sp<GrTextureProxy> proxy,
                                           SkAlphaType alphaType, sk_sp<SkColorSpace> colorSpace) {
-    return sk_make_sp<SkImage_Gpu>(sk_ref_sp(context), kNeedNewImageUniqueID, alphaType,
+    // CONTEXT TODO: remove this use of 'backdoor' to create an SkImage
+    return sk_make_sp<SkImage_Gpu>(sk_ref_sp(context->priv().backdoor()),
+                                   kNeedNewImageUniqueID, alphaType,
                                    std::move(proxy), std::move(colorSpace));
 }
 
 class SkSpecialImage_Gpu : public SkSpecialImage_Base {
 public:
-    SkSpecialImage_Gpu(GrContext* context, const SkIRect& subset,
+    SkSpecialImage_Gpu(GrRecordingContext* context, const SkIRect& subset,
                        uint32_t uniqueID, sk_sp<GrTextureProxy> proxy, SkAlphaType at,
                        sk_sp<SkColorSpace> colorSpace, const SkSurfaceProps* props)
         : INHERITED(subset, uniqueID, props)
@@ -409,9 +398,9 @@
                               dst, paint, SkCanvas::kStrict_SrcRectConstraint);
     }
 
-    GrContext* onGetContext() const override { return fContext; }
+    GrRecordingContext* onGetContext() const override { return fContext; }
 
-    sk_sp<GrTextureProxy> onAsTextureProxyRef(GrContext*) const override {
+    sk_sp<GrTextureProxy> onAsTextureProxyRef(GrRecordingContext*) const override {
         return fTextureProxy;
     }
 
@@ -516,11 +505,12 @@
             ? kRGBA_F16_SkColorType : kRGBA_8888_SkColorType;
         SkImageInfo info = SkImageInfo::Make(size.width(), size.height(), colorType, at,
                                              sk_ref_sp(colorSpace));
-        return SkSurface::MakeRenderTarget(fContext, SkBudgeted::kYes, info);
+        // CONTEXT TODO: remove this use of 'backdoor' to create an SkSurface
+        return SkSurface::MakeRenderTarget(fContext->priv().backdoor(), SkBudgeted::kYes, info);
     }
 
 private:
-    GrContext*                fContext;
+    GrRecordingContext*       fContext;
     sk_sp<GrTextureProxy>     fTextureProxy;
     const SkAlphaType         fAlphaType;
     sk_sp<SkColorSpace>       fColorSpace;
@@ -529,14 +519,14 @@
     typedef SkSpecialImage_Base INHERITED;
 };
 
-sk_sp<SkSpecialImage> SkSpecialImage::MakeDeferredFromGpu(GrContext* context,
+sk_sp<SkSpecialImage> SkSpecialImage::MakeDeferredFromGpu(GrRecordingContext* context,
                                                           const SkIRect& subset,
                                                           uint32_t uniqueID,
                                                           sk_sp<GrTextureProxy> proxy,
                                                           sk_sp<SkColorSpace> colorSpace,
                                                           const SkSurfaceProps* props,
                                                           SkAlphaType at) {
-    if (!context || context->abandoned() || !proxy) {
+    if (!context || context->priv().abandoned() || !proxy) {
         return nullptr;
     }
     SkASSERT_RELEASE(rect_fits(subset, proxy->width(), proxy->height()));
diff --git a/src/core/SkSpecialImage.h b/src/core/SkSpecialImage.h
index bf5d98f..d881902 100644
--- a/src/core/SkSpecialImage.h
+++ b/src/core/SkSpecialImage.h
@@ -15,7 +15,7 @@
 #include "SkImageFilter.h" // for OutputProperties
 #include "SkImageInfo.h"   // for SkAlphaType
 
-class GrContext;
+class GrRecordingContext;
 class GrTextureProxy;
 class SkBitmap;
 class SkCanvas;
@@ -58,18 +58,18 @@
     virtual size_t getSize() const = 0;
 
     /**
-     *  Ensures that a special image is backed by a texture (when GrContext is non-null). If no
-     *  transformation is required, the returned image may be the same as this special image.
-     *  If this special image is from a different GrContext, this will fail.
+     *  Ensures that a special image is backed by a texture (when GrRecordingContext is non-null).
+     *  If no transformation is required, the returned image may be the same as this special image.
+     *  If this special image is from a different GrRecordingContext, this will fail.
      */
-    sk_sp<SkSpecialImage> makeTextureImage(GrContext*);
+    sk_sp<SkSpecialImage> makeTextureImage(GrRecordingContext*);
 
     /**
      *  Draw this SpecialImage into the canvas.
      */
     void draw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) const;
 
-    static sk_sp<SkSpecialImage> MakeFromImage(GrContext*,
+    static sk_sp<SkSpecialImage> MakeFromImage(GrRecordingContext*,
                                                const SkIRect& subset,
                                                sk_sp<SkImage>,
                                                const SkSurfaceProps* = nullptr);
@@ -80,7 +80,7 @@
                                                 const SkBitmap&,
                                                 const SkSurfaceProps* = nullptr);
 #if SK_SUPPORT_GPU
-    static sk_sp<SkSpecialImage> MakeDeferredFromGpu(GrContext*,
+    static sk_sp<SkSpecialImage> MakeDeferredFromGpu(GrRecordingContext*,
                                                      const SkIRect& subset,
                                                      uint32_t uniqueID,
                                                      sk_sp<GrTextureProxy>,
@@ -127,16 +127,16 @@
     bool isTextureBacked() const;
 
     /**
-     * Return the GrContext if the SkSpecialImage is GrTexture-backed
+     * Return the GrRecordingContext if the SkSpecialImage is GrTexture-backed
      */
-    GrContext* getContext() const;
+    GrRecordingContext* getContext() const;
 
 #if SK_SUPPORT_GPU
     /**
      *  Regardless of the underlying backing store, return the contents as a GrTextureProxy.
      *  The active portion of the texture can be retrieved via 'subset'.
      */
-    sk_sp<GrTextureProxy> asTextureProxyRef(GrContext*) const;
+    sk_sp<GrTextureProxy> asTextureProxyRef(GrRecordingContext*) const;
 #endif
 
     /**
diff --git a/src/core/SkSpecialSurface.cpp b/src/core/SkSpecialSurface.cpp
index 7f6a04a..a644003 100644
--- a/src/core/SkSpecialSurface.cpp
+++ b/src/core/SkSpecialSurface.cpp
@@ -118,17 +118,21 @@
 #if SK_SUPPORT_GPU
 ///////////////////////////////////////////////////////////////////////////////
 #include "GrBackendSurface.h"
-#include "GrContext.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "SkGpuDevice.h"
 
 class SkSpecialSurface_Gpu : public SkSpecialSurface_Base {
 public:
-    SkSpecialSurface_Gpu(GrContext* context, sk_sp<GrRenderTargetContext> renderTargetContext,
+    SkSpecialSurface_Gpu(GrRecordingContext* context,
+                         sk_sp<GrRenderTargetContext> renderTargetContext,
                          int width, int height, const SkIRect& subset)
         : INHERITED(subset, &renderTargetContext->surfaceProps())
         , fRenderTargetContext(std::move(renderTargetContext)) {
 
-        sk_sp<SkBaseDevice> device(SkGpuDevice::Make(context, fRenderTargetContext, width, height,
+        // CONTEXT TODO: remove this use of 'backdoor' to create an SkGpuDevice
+        sk_sp<SkBaseDevice> device(SkGpuDevice::Make(context->priv().backdoor(),
+                                                     fRenderTargetContext, width, height,
                                                      SkGpuDevice::kUninit_InitContents));
         if (!device) {
             return;
@@ -164,7 +168,7 @@
     typedef SkSpecialSurface_Base INHERITED;
 };
 
-sk_sp<SkSpecialSurface> SkSpecialSurface::MakeRenderTarget(GrContext* context,
+sk_sp<SkSpecialSurface> SkSpecialSurface::MakeRenderTarget(GrRecordingContext* context,
                                                            const GrBackendFormat& format,
                                                            int width, int height,
                                                            GrPixelConfig config,
diff --git a/src/core/SkSpecialSurface.h b/src/core/SkSpecialSurface.h
index 1b7aada..abbfd87 100644
--- a/src/core/SkSpecialSurface.h
+++ b/src/core/SkSpecialSurface.h
@@ -60,7 +60,7 @@
      *  Allocate a new GPU-backed SkSpecialSurface. If the requested surface cannot
      *  be created, nullptr will be returned.
      */
-    static sk_sp<SkSpecialSurface> MakeRenderTarget(GrContext*,
+    static sk_sp<SkSpecialSurface> MakeRenderTarget(GrRecordingContext*,
                                                     const GrBackendFormat& format,
                                                     int width, int height,
                                                     GrPixelConfig config,
diff --git a/src/core/SkSpinlock.cpp b/src/core/SkSpinlock.cpp
index eb9d633..2e4a9ed 100644
--- a/src/core/SkSpinlock.cpp
+++ b/src/core/SkSpinlock.cpp
@@ -7,9 +7,16 @@
 
 #include "SkSpinlock.h"
 
+#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2
+    #include <emmintrin.h>
+    static void pause() { _mm_pause(); }
+#else
+    static void pause() { /*spin*/ }
+#endif
+
 void SkSpinlock::contendedAcquire() {
     // To act as a mutex, we need an acquire barrier when we acquire the lock.
     while (fLocked.exchange(true, std::memory_order_acquire)) {
-        /*spin*/
+        pause();
     }
 }
diff --git a/src/core/SkStrike.cpp b/src/core/SkStrike.cpp
index f11c1f7..236d721 100644
--- a/src/core/SkStrike.cpp
+++ b/src/core/SkStrike.cpp
@@ -91,33 +91,33 @@
 SkGlyph* SkStrike::lookupByPackedGlyphID(SkPackedGlyphID packedGlyphID, MetricsType type) {
     SkGlyph* glyphPtr = fGlyphMap.findOrNull(packedGlyphID);
 
-    if (nullptr == glyphPtr) {
-        glyphPtr = this->allocateNewGlyph(packedGlyphID, type);
+    if (glyphPtr == nullptr) {
+        // Glyph is not present in the stirke. Make a new glyph and fill it in.
+
+        fMemoryUsed += sizeof(SkGlyph);
+        glyphPtr = fAlloc.make<SkGlyph>(packedGlyphID);
         fGlyphMap.set(glyphPtr);
+
+        switch (type) {
+            // * Nothing - is only used for raw glyphs. It is assumed that the advances, etc. are
+            // filled in by external code. This is used by the remote glyph cache to fill in glyphs.
+            case kNothing_MetricsType:
+                break;
+            case kJustAdvance_MetricsType:
+                fScalerContext->getAdvance(glyphPtr);
+                break;
+            case kFull_MetricsType:
+                fScalerContext->getMetrics(glyphPtr);
+                break;
+        }
     } else {
+        // Glyph is present in strike. Make sure the glyph has the right data.
+
         if (type == kFull_MetricsType && glyphPtr->isJustAdvance()) {
             fScalerContext->getMetrics(glyphPtr);
         }
     }
-    return glyphPtr;
-}
 
-SkGlyph* SkStrike::allocateNewGlyph(SkPackedGlyphID packedGlyphID, MetricsType mtype) {
-    fMemoryUsed += sizeof(SkGlyph);
-
-    SkGlyph* glyphPtr = fAlloc.make<SkGlyph>(packedGlyphID);
-    fGlyphMap.set(glyphPtr);
-
-    if (kNothing_MetricsType == mtype) {
-        return glyphPtr;
-    } else if (kJustAdvance_MetricsType == mtype) {
-        fScalerContext->getAdvance(glyphPtr);
-    } else {
-        SkASSERT(kFull_MetricsType == mtype);
-        fScalerContext->getMetrics(glyphPtr);
-    }
-
-    SkASSERT(glyphPtr->fID != SkPackedGlyphID());
     return glyphPtr;
 }
 
@@ -142,9 +142,7 @@
 }
 
 void SkStrike::initializeImage(const volatile void* data, size_t size, SkGlyph* glyph) {
-    // Don't overwrite the image if we already have one. We could have used a fallback if the
-    // glyph was missing earlier.
-    if (glyph->fImage) return;
+    SkASSERT(!glyph->fImage);
 
     if (glyph->fWidth > 0 && glyph->fWidth < kMaxGlyphWidth) {
         size_t allocSize = glyph->allocImage(&fAlloc);
@@ -180,9 +178,7 @@
 }
 
 bool SkStrike::initializePath(SkGlyph* glyph, const volatile void* data, size_t size) {
-    // Don't overwrite the path if we already have one. We could have used a fallback if the
-    // glyph was missing earlier.
-    if (glyph->fPathData) return true;
+    SkASSERT(!glyph->fPathData);
 
     if (glyph->fWidth) {
         SkGlyph::PathData* pathData = fAlloc.make<SkGlyph::PathData>();
@@ -235,6 +231,26 @@
     }
 }
 
+// N.B. This glyphMetrics call culls all the glyphs which will not display based on a non-finite
+// position or that there are no mask pixels.
+SkSpan<const SkGlyphPos> SkStrike::glyphMetrics(const SkGlyphID glyphIDs[],
+                                                const SkPoint positions[],
+                                                size_t n,
+                                                SkGlyphPos result[]) {
+    size_t drawableGlyphCount = 0;
+    for (size_t i = 0; i < n; i++) {
+        SkPoint glyphPos = positions[i];
+        if (SkScalarsAreFinite(glyphPos.x(), glyphPos.y())) {
+            const SkGlyph& glyph = this->getGlyphMetrics(glyphIDs[i], glyphPos);
+            if (!glyph.isEmpty()) {
+                result[drawableGlyphCount++] = {i, &glyph, glyphPos};
+            }
+        }
+    }
+
+    return SkSpan<const SkGlyphPos>{result, drawableGlyphCount};
+}
+
 #include "../pathops/SkPathOpsCubic.h"
 #include "../pathops/SkPathOpsQuad.h"
 
@@ -419,14 +435,12 @@
     SkDebugf("%s\n", msg.c_str());
 }
 
-bool SkStrike::hasImage(const SkGlyph& glyph) {
-    return !glyph.isEmpty() && this->findImage(glyph) != nullptr;
-}
-
-bool SkStrike::hasPath(const SkGlyph& glyph) {
+bool SkStrike::decideCouldDrawFromPath(const SkGlyph& glyph) {
     return !glyph.isEmpty() && this->findPath(glyph) != nullptr;
 }
 
+void SkStrike::onAboutToExitScope() { }
+
 #ifdef SK_DEBUG
 void SkStrike::forceValidate() const {
     size_t memoryUsed = sizeof(*this);
@@ -447,6 +461,6 @@
     forceValidate();
 #endif
 }
-#endif
+#endif  // SK_DEBUG
 
 
diff --git a/src/core/SkStrike.h b/src/core/SkStrike.h
index f410a7e..1f17b14 100644
--- a/src/core/SkStrike.h
+++ b/src/core/SkStrike.h
@@ -16,6 +16,7 @@
 #include "SkPaint.h"
 #include "SkTHash.h"
 #include "SkScalerContext.h"
+#include "SkStrikeInterface.h"
 #include "SkTemplates.h"
 #include <memory>
 
@@ -32,14 +33,12 @@
     The Find*Exclusive() method returns SkExclusiveStrikePtr, which releases exclusive ownership
     when they go out of scope.
 */
-class SkStrike : public SkStrikeInterface {
+class SkStrike final : public SkStrikeInterface {
 public:
     SkStrike(const SkDescriptor& desc,
              std::unique_ptr<SkScalerContext> scaler,
              const SkFontMetrics&);
 
-    const SkDescriptor& getDescriptor() const;
-
     /** Return true if glyph is cached. */
     bool isGlyphCached(SkGlyphID glyphID, SkFixed x, SkFixed y) const;
 
@@ -127,9 +126,20 @@
 
     const SkGlyph& getGlyphMetrics(SkGlyphID glyphID, SkPoint position) override;
 
-    bool hasImage(const SkGlyph& glyph) override;
+    bool decideCouldDrawFromPath(const SkGlyph& glyph) override;
 
-    bool hasPath(const SkGlyph& glyph) override;
+    const SkDescriptor& getDescriptor() const override;
+
+    SkStrikeSpec strikeSpec() const override {
+        return SkStrikeSpec{this->getDescriptor(),
+                            *this->getScalerContext()->getTypeface(),
+                            this->getScalerContext()->getEffects()};
+    }
+
+    SkSpan<const SkGlyphPos> glyphMetrics(
+            const SkGlyphID[], const SkPoint[], size_t n, SkGlyphPos result[]) override;
+
+    void onAboutToExitScope() override;
 
     /** Return the approx RAM usage for this cache. */
     size_t getMemoryUsed() const { return fMemoryUsed; }
@@ -179,13 +189,9 @@
 
     // Return the SkGlyph* associated with MakeID. The id parameter is the
     // combined glyph/x/y id generated by MakeID. If it is just a glyph id
-    // then x and y are assumed to be zero.
+    // then x and y are assumed to be zero. Limit the amount of work using type.
     SkGlyph* lookupByPackedGlyphID(SkPackedGlyphID packedGlyphID, MetricsType type);
 
-    // Return a new SkGlyph for the glyph ID and subpixel position id. Limit the amount
-    // of work using type.
-    SkGlyph* allocateNewGlyph(SkPackedGlyphID packedGlyphID, MetricsType type);
-
     static void OffsetResults(const SkGlyph::Intercept* intercept, SkScalar scale,
                               SkScalar xPos, SkScalar* array, int* count);
     static void AddInterval(SkScalar val, SkGlyph::Intercept* intercept);
diff --git a/src/core/SkStrikeCache.cpp b/src/core/SkStrikeCache.cpp
index f667322..f5da322 100644
--- a/src/core/SkStrikeCache.cpp
+++ b/src/core/SkStrikeCache.cpp
@@ -25,29 +25,43 @@
          const SkFontMetrics& metrics,
          std::unique_ptr<SkStrikePinner> pinner)
             : fStrikeCache{strikeCache}
-            , fCache{desc, std::move(scaler), metrics}
+            , fStrike{desc, std::move(scaler), metrics}
             , fPinner{std::move(pinner)} {}
 
     SkVector rounding() const override {
-        return fCache.rounding();
+        return fStrike.rounding();
     }
 
     const SkGlyph& getGlyphMetrics(SkGlyphID glyphID, SkPoint position) override {
-        return fCache.getGlyphMetrics(glyphID, position);
+        return fStrike.getGlyphMetrics(glyphID, position);
     }
 
-    bool hasImage(const SkGlyph& glyph) override {
-        return fCache.hasImage(glyph);
+    SkSpan<const SkGlyphPos> glyphMetrics(
+            const SkGlyphID id[], const SkPoint point[], size_t n, SkGlyphPos result[]) override {
+        return fStrike.glyphMetrics(id, point, n, result);
     }
 
-    bool hasPath(const SkGlyph& glyph) override {
-        return fCache.hasPath(glyph);
+
+    bool decideCouldDrawFromPath(const SkGlyph& glyph) override {
+        return fStrike.decideCouldDrawFromPath(glyph);
+    }
+
+    const SkDescriptor& getDescriptor() const override {
+        return fStrike.getDescriptor();
+    }
+
+    SkStrikeSpec strikeSpec() const override {
+        return fStrike.strikeSpec();
+    }
+
+    void onAboutToExitScope() override {
+        fStrikeCache->attachNode(this);
     }
 
     SkStrikeCache* const            fStrikeCache;
     Node*                           fNext{nullptr};
     Node*                           fPrev{nullptr};
-    SkStrike                        fCache;
+    SkStrike                        fStrike;
     std::unique_ptr<SkStrikePinner> fPinner;
 };
 
@@ -84,7 +98,7 @@
 }
 
 SkStrike* SkStrikeCache::ExclusiveStrikePtr::get() const {
-    return &fNode->fCache;
+    return &fNode->fStrike;
 }
 
 SkStrike* SkStrikeCache::ExclusiveStrikePtr::operator -> () const {
@@ -165,6 +179,17 @@
     return node;
 }
 
+SkScopedStrike SkStrikeCache::findOrCreateScopedStrike(const SkDescriptor& desc,
+                                                       const SkScalerContextEffects& effects,
+                                                       const SkTypeface& typeface) {
+    Node* node = this->findAndDetachStrike(desc);
+    if (node == nullptr) {
+        auto scaler = CreateScalerContext(desc, effects, typeface);
+        node = this->createStrike(desc, std::move(scaler));
+    }
+    return SkScopedStrike{node};
+}
+
 SkExclusiveStrikePtr SkStrikeCache::FindOrCreateStrikeExclusive(
         const SkFont& font,
         const SkPaint& paint,
@@ -286,7 +311,7 @@
     SkAutoExclusive ac(fLock);
 
     this->validate();
-    node->fCache.validate();
+    node->fStrike.validate();
 
     this->internalAttachToHead(node);
     this->internalPurge();
@@ -300,7 +325,7 @@
     SkAutoExclusive ac(fLock);
 
     for (Node* node = internalGetHead(); node != nullptr; node = node->fNext) {
-        if (node->fCache.getDescriptor() == desc) {
+        if (node->fStrike.getDescriptor() == desc) {
             this->internalDetachCache(node);
             return node;
         }
@@ -345,10 +370,10 @@
             targetSubY = glyph->getSubYFixed();
 
     for (Node* node = internalGetHead(); node != nullptr; node = node->fNext) {
-        if (loose_compare(node->fCache.getDescriptor(), desc)) {
+        if (loose_compare(node->fStrike.getDescriptor(), desc)) {
             auto targetGlyphID = SkPackedGlyphID(glyphID, targetSubX, targetSubY);
-            if (node->fCache.isGlyphCached(glyphID, targetSubX, targetSubY)) {
-                SkGlyph* fallback = node->fCache.getRawGlyphByID(targetGlyphID);
+            if (node->fStrike.isGlyphCached(glyphID, targetSubX, targetSubY)) {
+                SkGlyph* fallback = node->fStrike.getRawGlyphByID(targetGlyphID);
                 // This desperate-match node may disappear as soon as we drop fLock, so we
                 // need to copy the glyph from node into this strike, including a
                 // deep copy of the mask.
@@ -357,7 +382,7 @@
             }
 
             // Look for any sub-pixel pos for this glyph, in case there is a pos mismatch.
-            if (const auto* fallback = node->fCache.getCachedGlyphAnySubPix(glyphID)) {
+            if (const auto* fallback = node->fStrike.getCachedGlyphAnySubPix(glyphID)) {
                 targetCache->initializeGlyphFromFallback(glyph, *fallback);
                 return true;
             }
@@ -378,9 +403,9 @@
     // This will have to search the sub-pixel positions too.
     // There is also a problem with accounting for cache size with shared path data.
     for (Node* node = internalGetHead(); node != nullptr; node = node->fNext) {
-        if (loose_compare(node->fCache.getDescriptor(), desc)) {
-            if (node->fCache.isGlyphCached(glyphID, 0, 0)) {
-                SkGlyph* from = node->fCache.getRawGlyphByID(SkPackedGlyphID(glyphID));
+        if (loose_compare(node->fStrike.getDescriptor(), desc)) {
+            if (node->fStrike.isGlyphCached(glyphID, 0, 0)) {
+                SkGlyph* from = node->fStrike.getRawGlyphByID(SkPackedGlyphID(glyphID));
                 if (from->fPathData != nullptr) {
                     // We can just copy the path out by value here, so no need to worry
                     // about the lifetime of this desperate-match node.
@@ -503,7 +528,7 @@
     this->validate();
 
     for (Node* node = this->internalGetHead(); node != nullptr; node = node->fNext) {
-        visitor(node->fCache);
+        visitor(node->fStrike);
     }
 }
 
@@ -543,7 +568,7 @@
 
         // Only delete if the strike is not pinned.
         if (node->fPinner == nullptr || node->fPinner->canDelete()) {
-            bytesFreed += node->fCache.getMemoryUsed();
+            bytesFreed += node->fStrike.getMemoryUsed();
             countFreed += 1;
             this->internalDetachCache(node);
             delete node;
@@ -576,13 +601,13 @@
     }
 
     fCacheCount += 1;
-    fTotalMemoryUsed += node->fCache.getMemoryUsed();
+    fTotalMemoryUsed += node->fStrike.getMemoryUsed();
 }
 
 void SkStrikeCache::internalDetachCache(Node* node) {
     SkASSERT(fCacheCount > 0);
     fCacheCount -= 1;
-    fTotalMemoryUsed -= node->fCache.getMemoryUsed();
+    fTotalMemoryUsed -= node->fStrike.getMemoryUsed();
 
     if (node->fPrev) {
         node->fPrev->fNext = node->fNext;
@@ -618,7 +643,7 @@
 
     const Node* node = fHead;
     while (node != nullptr) {
-        computedBytes += node->fCache.getMemoryUsed();
+        computedBytes += node->fStrike.getMemoryUsed();
         computedCount += 1;
         node = node->fNext;
     }
diff --git a/src/core/SkStrikeCache.h b/src/core/SkStrikeCache.h
index bcef370..8aeb63d 100644
--- a/src/core/SkStrikeCache.h
+++ b/src/core/SkStrikeCache.h
@@ -39,12 +39,12 @@
     virtual bool canDelete() = 0;
 };
 
-class SkStrikeCache {
+class SkStrikeCache final : public SkStrikeCacheInterface {
     class Node;
 
 public:
     SkStrikeCache() = default;
-    ~SkStrikeCache();
+    ~SkStrikeCache() override;
 
     class ExclusiveStrikePtr {
     public:
@@ -114,6 +114,10 @@
                                    SkStrike* targetCache);
     bool desperationSearchForPath(const SkDescriptor& desc, SkGlyphID glyphID, SkPath* path);
 
+    SkScopedStrike findOrCreateScopedStrike(const SkDescriptor& desc,
+                                            const SkScalerContextEffects& effects,
+                                            const SkTypeface& typeface) override;
+
     static ExclusiveStrikePtr FindOrCreateStrikeExclusive(
             const SkFont& font,
             const SkPaint& paint,
diff --git a/src/core/SkStrikeInterface.h b/src/core/SkStrikeInterface.h
new file mode 100644
index 0000000..0353b0a
--- /dev/null
+++ b/src/core/SkStrikeInterface.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkStrikeInterface_DEFINED
+#define SkStrikeInterface_DEFINED
+
+#include <memory>
+
+#include "SkPoint.h"
+#include "SkSpan.h"
+#include "SkTypes.h"
+
+class SkDescriptor;
+class SkGlyph;
+class SkMaskFilter;
+class SkPathEffect;
+class SkTypeface;
+
+// TODO: rename SkScalerContextEffects -> SkStrikeEffects
+struct SkScalerContextEffects {
+    SkScalerContextEffects() : fPathEffect(nullptr), fMaskFilter(nullptr) {}
+    SkScalerContextEffects(SkPathEffect* pe, SkMaskFilter* mf)
+            : fPathEffect(pe), fMaskFilter(mf) {}
+    explicit SkScalerContextEffects(const SkPaint& paint)
+            : fPathEffect(paint.getPathEffect())
+            , fMaskFilter(paint.getMaskFilter()) {}
+
+    SkPathEffect*   fPathEffect;
+    SkMaskFilter*   fMaskFilter;
+};
+
+class SkStrikeSpec {
+public:
+    SkStrikeSpec(const SkDescriptor& desc,
+                 const SkTypeface& typeface,
+                 const SkScalerContextEffects& effects)
+            : fDesc{desc}
+            , fTypeface{typeface}
+            , fEffects{effects} {}
+
+    const SkDescriptor& desc() const { return fDesc; }
+    const SkTypeface& typeface() const { return fTypeface; }
+    SkScalerContextEffects effects() const {return fEffects; }
+
+private:
+    const SkDescriptor& fDesc;
+    const SkTypeface& fTypeface;
+    const SkScalerContextEffects fEffects;
+};
+
+struct SkGlyphPos {
+    size_t index;
+    const SkGlyph* glyph;
+    SkPoint position;
+};
+
+struct SkPathPos {
+    const SkPath* path;
+    SkPoint position;
+};
+
+class SkStrikeInterface {
+public:
+    virtual ~SkStrikeInterface() = default;
+    virtual SkVector rounding() const = 0;
+    virtual const SkDescriptor& getDescriptor() const = 0;
+    virtual SkStrikeSpec strikeSpec() const = 0;
+
+    // glyphMetrics writes its results to result, but only returns a subspan of result.
+    virtual SkSpan<const SkGlyphPos> glyphMetrics(
+            const SkGlyphID[], const SkPoint[], size_t n, SkGlyphPos result[]) = 0;
+    virtual const SkGlyph& getGlyphMetrics(SkGlyphID glyphID, SkPoint position) = 0;
+    virtual bool decideCouldDrawFromPath(const SkGlyph& glyph) = 0;
+    virtual void onAboutToExitScope() = 0;
+
+    struct Deleter {
+        void operator()(SkStrikeInterface* ptr) const {
+            ptr->onAboutToExitScope();
+        }
+    };
+};
+
+using SkScopedStrike = std::unique_ptr<SkStrikeInterface, SkStrikeInterface::Deleter>;
+
+class SkStrikeCacheInterface {
+public:
+    virtual ~SkStrikeCacheInterface() = default;
+    virtual SkScopedStrike findOrCreateScopedStrike(const SkDescriptor& desc,
+                                                    const SkScalerContextEffects& effects,
+                                                    const SkTypeface& typeface) = 0;
+};
+
+#endif  //SkStrikeInterface_DEFINED
diff --git a/src/core/SkString.cpp b/src/core/SkString.cpp
index b6ac84e..bde5eb2 100644
--- a/src/core/SkString.cpp
+++ b/src/core/SkString.cpp
@@ -246,7 +246,7 @@
 }
 
 #ifdef SK_DEBUG
-void SkString::validate() const {
+const SkString& SkString::validate() const {
     // make sure know one has written over our global
     SkASSERT(0 == gEmptyRec.fLength);
     SkASSERT(0 == gEmptyRec.fRefCnt.load(std::memory_order_relaxed));
@@ -257,6 +257,7 @@
         SkASSERT(fRec->fRefCnt.load(std::memory_order_relaxed) > 0);
         SkASSERT(0 == fRec->data()[fRec->fLength]);
     }
+    return *this;
 }
 #endif
 
@@ -279,16 +280,9 @@
     fRec = Rec::Make(text, len);
 }
 
-SkString::SkString(const SkString& src) {
-    src.validate();
+SkString::SkString(const SkString& src) : fRec(src.validate().fRec) {}
 
-    fRec = src.fRec;
-}
-
-SkString::SkString(SkString&& src) {
-    src.validate();
-
-    fRec = std::move(src.fRec);
+SkString::SkString(SkString&& src) : fRec(std::move(src.validate().fRec)) {
     src.fRec.reset(const_cast<Rec*>(&gEmptyRec));
 }
 
@@ -312,11 +306,7 @@
 
 SkString& SkString::operator=(const SkString& src) {
     this->validate();
-
-    if (fRec != src.fRec) {
-        SkString    tmp(src);
-        this->swap(tmp);
-    }
+    fRec = src.fRec;  // sk_sp<Rec>::operator=(const sk_sp<Ref>&) checks for self-assignment.
     return *this;
 }
 
@@ -331,11 +321,7 @@
 
 SkString& SkString::operator=(const char text[]) {
     this->validate();
-
-    SkString tmp(text);
-    this->swap(tmp);
-
-    return *this;
+    return *this = SkString(text);
 }
 
 void SkString::reset() {
diff --git a/src/core/SkStroke.cpp b/src/core/SkStroke.cpp
index 176871c..c898fda 100644
--- a/src/core/SkStroke.cpp
+++ b/src/core/SkStroke.cpp
@@ -499,17 +499,18 @@
 // returns the distance squared from the point to the line
 static SkScalar pt_to_line(const SkPoint& pt, const SkPoint& lineStart, const SkPoint& lineEnd) {
     SkVector dxy = lineEnd - lineStart;
-    if (degenerate_vector(dxy)) {
-        return SkPointPriv::DistanceToSqd(pt, lineStart);
-    }
     SkVector ab0 = pt - lineStart;
     SkScalar numer = dxy.dot(ab0);
     SkScalar denom = dxy.dot(dxy);
-    SkScalar t = numer / denom;
-    SkPoint hit;
-    hit.fX = lineStart.fX * (1 - t) + lineEnd.fX * t;
-    hit.fY = lineStart.fY * (1 - t) + lineEnd.fY * t;
-    return SkPointPriv::DistanceToSqd(hit, pt);
+    SkScalar t = sk_ieee_float_divide(numer, denom);
+    if (t >= 0 && t <= 1) {
+        SkPoint hit;
+        hit.fX = lineStart.fX * (1 - t) + lineEnd.fX * t;
+        hit.fY = lineStart.fY * (1 - t) + lineEnd.fY * t;
+        return SkPointPriv::DistanceToSqd(hit, pt);
+    } else {
+        return SkPointPriv::DistanceToSqd(pt, lineStart);
+    }
 }
 
 /*  Given a cubic, determine if all four points are in a line.
@@ -764,13 +765,8 @@
 // compute the perpendicular point and its tangent.
 void SkPathStroker::setRayPts(const SkPoint& tPt, SkVector* dxy, SkPoint* onPt,
         SkPoint* tangent) const {
-    SkPoint oldDxy = *dxy;
-    if (!dxy->setLength(fRadius)) {  // consider moving double logic into SkPoint::setLength
-        double xx = oldDxy.fX;
-        double yy = oldDxy.fY;
-        double dscale = fRadius / sqrt(xx * xx + yy * yy);
-        dxy->fX = SkDoubleToScalar(xx * dscale);
-        dxy->fY = SkDoubleToScalar(yy * dscale);
+    if (!dxy->setLength(fRadius)) {
+        dxy->set(fRadius, 0);
     }
     SkScalar axisFlip = SkIntToScalar(fStrokeType);  // go opposite ways for outer, inner
     onPt->fX = tPt.fX + axisFlip * dxy->fY;
diff --git a/src/core/SkTypeface.cpp b/src/core/SkTypeface.cpp
index 82ca3f6..3950e5d 100644
--- a/src/core/SkTypeface.cpp
+++ b/src/core/SkTypeface.cpp
@@ -44,7 +44,7 @@
 protected:
     SkEmptyTypeface() : SkTypeface(SkFontStyle(), true) { }
 
-    SkStreamAsset* onOpenStream(int* ttcIndex) const override { return nullptr; }
+    std::unique_ptr<SkStreamAsset> onOpenStream(int* ttcIndex) const override { return nullptr; }
     sk_sp<SkTypeface> onMakeClone(const SkFontArguments& args) const override {
         return sk_ref_sp(this);
     }
@@ -262,7 +262,7 @@
     return this->onGetTableData(tag, offset, length, data);
 }
 
-SkStreamAsset* SkTypeface::openStream(int* ttcIndex) const {
+std::unique_ptr<SkStreamAsset> SkTypeface::openStream(int* ttcIndex) const {
     int ttcIndexStorage;
     if (nullptr == ttcIndex) {
         // So our subclasses don't need to check for null param
diff --git a/src/core/SkTypefaceCache.cpp b/src/core/SkTypefaceCache.cpp
index 0cf0d84..6238c5d 100644
--- a/src/core/SkTypefaceCache.cpp
+++ b/src/core/SkTypefaceCache.cpp
@@ -13,18 +13,18 @@
 
 SkTypefaceCache::SkTypefaceCache() {}
 
-void SkTypefaceCache::add(SkTypeface* face) {
+void SkTypefaceCache::add(sk_sp<SkTypeface> face) {
     if (fTypefaces.count() >= TYPEFACE_CACHE_LIMIT) {
         this->purge(TYPEFACE_CACHE_LIMIT >> 2);
     }
 
-    fTypefaces.emplace_back(SkRef(face));
+    fTypefaces.emplace_back(std::move(face));
 }
 
-SkTypeface* SkTypefaceCache::findByProcAndRef(FindProc proc, void* ctx) const {
+sk_sp<SkTypeface> SkTypefaceCache::findByProcAndRef(FindProc proc, void* ctx) const {
     for (const sk_sp<SkTypeface>& typeface : fTypefaces) {
         if (proc(typeface.get(), ctx)) {
-            return SkRef(typeface.get());
+            return typeface;
         }
     }
     return nullptr;
@@ -64,12 +64,12 @@
 
 SK_DECLARE_STATIC_MUTEX(gMutex);
 
-void SkTypefaceCache::Add(SkTypeface* face) {
+void SkTypefaceCache::Add(sk_sp<SkTypeface> face) {
     SkAutoMutexAcquire ama(gMutex);
-    Get().add(face);
+    Get().add(std::move(face));
 }
 
-SkTypeface* SkTypefaceCache::FindByProcAndRef(FindProc proc, void* ctx) {
+sk_sp<SkTypeface> SkTypefaceCache::FindByProcAndRef(FindProc proc, void* ctx) {
     SkAutoMutexAcquire ama(gMutex);
     return Get().findByProcAndRef(proc, ctx);
 }
diff --git a/src/core/SkTypefaceCache.h b/src/core/SkTypefaceCache.h
index bf90dae..99ccea9 100644
--- a/src/core/SkTypefaceCache.h
+++ b/src/core/SkTypefaceCache.h
@@ -26,19 +26,17 @@
     typedef bool(*FindProc)(SkTypeface*, void* context);
 
     /**
-     *  Add a typeface to the cache. This ref()s the typeface, so that the
-     *  cache is also an owner. Later, if we need to purge the cache, typefaces
-     *  whose refcnt is 1 (meaning only the cache is an owner) will be
-     *  unref()ed.
+     *  Add a typeface to the cache. Later, if we need to purge the cache,
+     *  typefaces uniquely owned by the cache will be unref()ed.
      */
-    void add(SkTypeface*);
+    void add(sk_sp<SkTypeface>);
 
     /**
-     *  Iterate through the cache, calling proc(typeface, ctx) with each
-     *  typeface. If proc returns true, then we return that typeface (this
-     *  ref()s the typeface). If it never returns true, we return nullptr.
+     *  Iterate through the cache, calling proc(typeface, ctx) for each typeface.
+     *  If proc returns true, then return that typeface.
+     *  If it never returns true, return nullptr.
      */
-    SkTypeface* findByProcAndRef(FindProc proc, void* ctx) const;
+    sk_sp<SkTypeface> findByProcAndRef(FindProc proc, void* ctx) const;
 
     /**
      *  This will unref all of the typefaces in the cache for which the cache
@@ -56,8 +54,8 @@
 
     // These are static wrappers around a global instance of a cache.
 
-    static void Add(SkTypeface*);
-    static SkTypeface* FindByProcAndRef(FindProc proc, void* ctx);
+    static void Add(sk_sp<SkTypeface>);
+    static sk_sp<SkTypeface> FindByProcAndRef(FindProc proc, void* ctx);
     static void PurgeAll();
 
     /**
diff --git a/src/core/SkTypeface_remote.h b/src/core/SkTypeface_remote.h
index be6fbf6..183ec9c 100644
--- a/src/core/SkTypeface_remote.h
+++ b/src/core/SkTypeface_remote.h
@@ -65,7 +65,7 @@
 
 protected:
     int onGetUPEM() const override { SK_ABORT("Should never be called."); return 0; }
-    SkStreamAsset* onOpenStream(int* ttcIndex) const override {
+    std::unique_ptr<SkStreamAsset> onOpenStream(int* ttcIndex) const override {
         SK_ABORT("Should never be called.");
         return nullptr;
     }
diff --git a/src/effects/Sk1DPathEffect.cpp b/src/effects/Sk1DPathEffect.cpp
index 72a128d..fbb73f5 100644
--- a/src/effects/Sk1DPathEffect.cpp
+++ b/src/effects/Sk1DPathEffect.cpp
@@ -41,6 +41,10 @@
     SkASSERT(advance > 0 && !path.isEmpty());
     SkASSERT((unsigned)style <= kMorph_Style);
 
+    // Make the path thread-safe.
+    fPath.updateBoundsCache();
+    (void)fPath.getGenerationID();
+
     // cleanup their phase parameter, inverting it so that it becomes an
     // offset along the path (to match the interpretation in PostScript)
     if (phase < 0) {
diff --git a/src/effects/SkColorMatrix.cpp b/src/effects/SkColorMatrix.cpp
index 45cd502..6726a54 100644
--- a/src/effects/SkColorMatrix.cpp
+++ b/src/effects/SkColorMatrix.cpp
@@ -93,11 +93,8 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void SkColorMatrix::setRotate(Axis axis, SkScalar degrees) {
-    SkScalar S, C;
-
-    S = SkScalarSinCos(SkDegreesToRadians(degrees), &C);
-
-    this->setSinCos(axis, S, C);
+    SkScalar r = SkDegreesToRadians(degrees);
+    this->setSinCos(axis, SkScalarSin(r), SkScalarCos(r));
 }
 
 void SkColorMatrix::setSinCos(Axis axis, SkScalar sine, SkScalar cosine) {
diff --git a/src/effects/SkColorMatrixFilter.cpp b/src/effects/SkColorMatrixFilter.cpp
index 7e04f76..088a60f 100644
--- a/src/effects/SkColorMatrixFilter.cpp
+++ b/src/effects/SkColorMatrixFilter.cpp
@@ -6,11 +6,6 @@
  */
 
 #include "SkColorMatrixFilter.h"
-#include "SkColorSpace.h"
-#include "SkColorSpaceXformer.h"
-#if SK_SUPPORT_GPU
-    #include "GrFragmentProcessor.h"
-#endif
 
 static SkScalar byte_to_scale(U8CPU byte) {
     if (0xFF == byte) {
@@ -21,72 +16,21 @@
     }
 }
 
-// If we can't reduce to a mode filter in MakeLightingFilter(), this is the general case.
-// We operate as a matrix color filter, but remember our input colors in case we're asked
-// to onMakeColorSpace() a new filter.
-class SkLightingColorFilter : public SkColorFilter {
-public:
-    SkLightingColorFilter(SkColor mul, SkColor add) : fMul(mul), fAdd(add) {
-        SkColorMatrix matrix;
-        matrix.setScale(byte_to_scale(SkColorGetR(mul)),
-                        byte_to_scale(SkColorGetG(mul)),
-                        byte_to_scale(SkColorGetB(mul)),
-                        1);
-        matrix.postTranslate(SkIntToScalar(SkColorGetR(add)),
-                             SkIntToScalar(SkColorGetG(add)),
-                             SkIntToScalar(SkColorGetB(add)),
-                             0);
-        fMatrixFilter = SkColorFilter::MakeMatrixFilterRowMajor255(matrix.fMat);
-    }
-
-    // Overriding this method is the class' raison d'etre.
-    sk_sp<SkColorFilter> onMakeColorSpace(SkColorSpaceXformer* xformer) const override {
-        SkColor add = xformer->apply(fAdd);
-        if (add != fAdd) {
-            return sk_make_sp<SkLightingColorFilter>(fMul, add);
-        }
-        return this->INHERITED::onMakeColorSpace(xformer);
-    }
-
-    // Let fMatrixFilter handle all the other calls directly.
-
-    uint32_t getFlags() const override {
-        return fMatrixFilter->getFlags();
-    }
-    bool asColorMatrix(SkScalar matrix[20]) const override {
-        return fMatrixFilter->asColorMatrix(matrix);
-    }
-    void onAppendStages(SkRasterPipeline* p, SkColorSpace* cs, SkArenaAlloc* alloc,
-                        bool shaderIsOpaque) const override {
-        fMatrixFilter->appendStages(p, cs, alloc, shaderIsOpaque);
-    }
-
-    // TODO: might want to remember we're a lighting color filter through serialization?
-    void flatten(SkWriteBuffer& buf) const override { return fMatrixFilter->flatten(buf); }
-
-
-#if SK_SUPPORT_GPU
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
-            GrContext* ctx, const GrColorSpaceInfo& csi) const override {
-        return fMatrixFilter->asFragmentProcessor(ctx, csi);
-    }
-#endif
-
-private:
-    Factory      getFactory() const override { return fMatrixFilter->getFactory(); }
-    const char* getTypeName() const override { return fMatrixFilter->getTypeName(); }
-
-    SkColor              fMul, fAdd;
-    sk_sp<SkColorFilter> fMatrixFilter;
-
-    typedef SkColorFilter INHERITED;
-};
-
 sk_sp<SkColorFilter> SkColorMatrixFilter::MakeLightingFilter(SkColor mul, SkColor add) {
     const SkColor opaqueAlphaMask = SK_ColorBLACK;
     // omit the alpha and compare only the RGB values
     if (0 == (add & ~opaqueAlphaMask)) {
-        return SkColorFilter::MakeModeFilter(mul | opaqueAlphaMask, SkBlendMode::kModulate);
+        return SkColorFilters::Blend(mul | opaqueAlphaMask, SkBlendMode::kModulate);
     }
-    return sk_make_sp<SkLightingColorFilter>(mul, add);
+
+    SkColorMatrix matrix;
+    matrix.setScale(byte_to_scale(SkColorGetR(mul)),
+                    byte_to_scale(SkColorGetG(mul)),
+                    byte_to_scale(SkColorGetB(mul)),
+                    1);
+    matrix.postTranslate(SkIntToScalar(SkColorGetR(add)),
+                         SkIntToScalar(SkColorGetG(add)),
+                         SkIntToScalar(SkColorGetB(add)),
+                         0);
+    return SkColorFilters::MatrixRowMajor255(matrix.fMat);
 }
diff --git a/src/effects/SkHighContrastFilter.cpp b/src/effects/SkHighContrastFilter.cpp
index a922545..0fc7b00 100644
--- a/src/effects/SkHighContrastFilter.cpp
+++ b/src/effects/SkHighContrastFilter.cpp
@@ -8,6 +8,7 @@
 #include "SkHighContrastFilter.h"
 #include "SkArenaAlloc.h"
 #include "SkColorData.h"
+#include "SkEffectPriv.h"
 #include "SkRasterPipeline.h"
 #include "SkReadBuffer.h"
 #include "SkString.h"
@@ -36,13 +37,10 @@
 
 #if SK_SUPPORT_GPU
     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
-            GrContext*, const GrColorSpaceInfo&) const override;
+            GrRecordingContext*, const GrColorSpaceInfo&) const override;
  #endif
 
-    void onAppendStages(SkRasterPipeline* p,
-                        SkColorSpace* dst,
-                        SkArenaAlloc* scratch,
-                        bool shaderIsOpaque) const override;
+    bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override;
 
 protected:
     void flatten(SkWriteBuffer&) const override;
@@ -57,23 +55,24 @@
     typedef SkColorFilter INHERITED;
 };
 
-void SkHighContrast_Filter::onAppendStages(SkRasterPipeline* p,
-                                           SkColorSpace* dstCS,
-                                           SkArenaAlloc* alloc,
-                                           bool shaderIsOpaque) const {
+bool SkHighContrast_Filter::onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const {
+    SkRasterPipeline* p = rec.fPipeline;
+    SkArenaAlloc* alloc = rec.fAlloc;
+
     if (!shaderIsOpaque) {
         p->append(SkRasterPipeline::unpremul);
     }
 
-    if (!dstCS) {
-        // In legacy draws this effect approximately linearizes by squaring.
-        // When non-legacy, we're already (better) linearized.
-        auto square = alloc->make<skcms_TransferFunction>();
-        square->g = 2.0f; square->a = 1.0f;
-        square->b = square->c = square->d = square->e = square->f = 0;
-
-        p->append(SkRasterPipeline::parametric, square);
+    // Linearize before applying high-contrast filter.
+    auto tf = alloc->make<skcms_TransferFunction>();
+    if (rec.fDstCS) {
+        rec.fDstCS->transferFn(&tf->g);
+    } else {
+        // Historically we approximate untagged destinations as gamma 2.
+        // TODO: sRGB?
+        *tf = {2,1, 0,0,0,0,0};
     }
+    p->append(SkRasterPipeline::parametric, tf);
 
     if (fConfig.fGrayscale) {
         float r = SK_LUM_COEFF_R;
@@ -113,18 +112,20 @@
     p->append(SkRasterPipeline::clamp_0);
     p->append(SkRasterPipeline::clamp_1);
 
-    if (!dstCS) {
-        // See the previous if(!dstCS) { ... }
-        auto sqrt = alloc->make<skcms_TransferFunction>();
-        sqrt->g = 0.5f; sqrt->a = 1.0f;
-        sqrt->b = sqrt->c = sqrt->d = sqrt->e = sqrt->f = 0;
-
-        p->append(SkRasterPipeline::parametric, sqrt);
+    // Re-encode back from linear.
+    auto invTF = alloc->make<skcms_TransferFunction>();
+    if (rec.fDstCS) {
+        rec.fDstCS->invTransferFn(&invTF->g);
+    } else {
+        // See above... historically untagged == gamma 2 in this filter.
+        *invTF ={0.5f,1, 0,0,0,0,0};
     }
+    p->append(SkRasterPipeline::parametric, invTF);
 
     if (!shaderIsOpaque) {
         p->append(SkRasterPipeline::premul);
     }
+    return true;
 }
 
 void SkHighContrast_Filter::flatten(SkWriteBuffer& buffer) const {
@@ -353,7 +354,7 @@
 }
 
 std::unique_ptr<GrFragmentProcessor> SkHighContrast_Filter::asFragmentProcessor(
-        GrContext*, const GrColorSpaceInfo& csi) const {
+        GrRecordingContext*, const GrColorSpaceInfo& csi) const {
     bool linearize = !csi.isLinearlyBlended();
     return HighContrastFilterEffect::Make(fConfig, linearize);
 }
diff --git a/src/effects/SkLayerDrawLooper.cpp b/src/effects/SkLayerDrawLooper.cpp
index c037133..cd3f51b 100644
--- a/src/effects/SkLayerDrawLooper.cpp
+++ b/src/effects/SkLayerDrawLooper.cpp
@@ -10,7 +10,6 @@
 #include "SkColorSpacePriv.h"
 #include "SkMaskFilter.h"
 #include "SkCanvas.h"
-#include "SkColorSpaceXformer.h"
 #include "SkColor.h"
 #include "SkMaskFilterBase.h"
 #include "SkReadBuffer.h"
@@ -207,37 +206,6 @@
     return true;
 }
 
-sk_sp<SkDrawLooper> SkLayerDrawLooper::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    if (!fCount) {
-        return sk_ref_sp(const_cast<SkLayerDrawLooper*>(this));
-    }
-
-    auto looper = sk_sp<SkLayerDrawLooper>(new SkLayerDrawLooper());
-    looper->fCount = fCount;
-
-    Rec* oldRec = fRecs;
-    Rec* newTopRec = new Rec();
-    newTopRec->fInfo = oldRec->fInfo;
-    newTopRec->fPaint = xformer->apply(oldRec->fPaint);
-    newTopRec->fNext = nullptr;
-
-    Rec* prevNewRec = newTopRec;
-    oldRec = oldRec->fNext;
-    while (oldRec) {
-        Rec* newRec = new Rec();
-        newRec->fInfo = oldRec->fInfo;
-        newRec->fPaint = xformer->apply(oldRec->fPaint);
-        newRec->fNext = nullptr;
-        prevNewRec->fNext = newRec;
-
-        prevNewRec = newRec;
-        oldRec = oldRec->fNext;
-    }
-
-    looper->fRecs = newTopRec;
-    return std::move(looper);
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 
 void SkLayerDrawLooper::flatten(SkWriteBuffer& buffer) const {
diff --git a/src/effects/SkLumaColorFilter.cpp b/src/effects/SkLumaColorFilter.cpp
index a53d69b..a7b7ff2 100644
--- a/src/effects/SkLumaColorFilter.cpp
+++ b/src/effects/SkLumaColorFilter.cpp
@@ -7,21 +7,22 @@
 
 #include "SkLumaColorFilter.h"
 #include "SkColorData.h"
+#include "SkEffectPriv.h"
 #include "SkRasterPipeline.h"
 #include "SkString.h"
 
 #if SK_SUPPORT_GPU
 #include "GrContext.h"
-#include "effects/GrLumaColorFilterEffect.h"
+#include "effects/generated/GrLumaColorFilterEffect.h"
 #include "glsl/GrGLSLFragmentProcessor.h"
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
 #endif
 
-void SkLumaColorFilter::onAppendStages(SkRasterPipeline* p,
-                                       SkColorSpace* dst,
-                                       SkArenaAlloc* scratch,
-                                       bool shaderIsOpaque) const {
-    p->append(SkRasterPipeline::luminance_to_alpha);
+bool SkLumaColorFilter::onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const {
+    rec.fPipeline->append(SkRasterPipeline::luminance_to_alpha);
+    rec.fPipeline->append(SkRasterPipeline::clamp_0);
+    rec.fPipeline->append(SkRasterPipeline::clamp_1);
+    return true;
 }
 
 sk_sp<SkColorFilter> SkLumaColorFilter::Make() {
@@ -38,7 +39,7 @@
 
 #if SK_SUPPORT_GPU
 std::unique_ptr<GrFragmentProcessor> SkLumaColorFilter::asFragmentProcessor(
-        GrContext*, const GrColorSpaceInfo&) const {
+        GrRecordingContext*, const GrColorSpaceInfo&) const {
     return GrLumaColorFilterEffect::Make();
 }
 #endif
diff --git a/src/effects/SkOverdrawColorFilter.cpp b/src/effects/SkOverdrawColorFilter.cpp
index 6eead48..044bdf3 100644
--- a/src/effects/SkOverdrawColorFilter.cpp
+++ b/src/effects/SkOverdrawColorFilter.cpp
@@ -6,6 +6,7 @@
  */
 
 #include "SkArenaAlloc.h"
+#include "SkEffectPriv.h"
 #include "SkOverdrawColorFilter.h"
 #include "SkRasterPipeline.h"
 #include "SkReadBuffer.h"
@@ -21,7 +22,7 @@
 layout(ctype=SkPMColor) in uniform half4 color4;
 layout(ctype=SkPMColor) in uniform half4 color5;
 
-void main(int x, int y, inout half4 color) {
+void main(inout half4 color) {
     half alpha = 255.0 * color.a;
     if (alpha < 0.5) {
         color = color0;
@@ -40,15 +41,12 @@
 )";
 #endif
 
-void SkOverdrawColorFilter::onAppendStages(SkRasterPipeline* p,
-                                           SkColorSpace* dstCS,
-                                           SkArenaAlloc* alloc,
-                                           bool shader_is_opaque) const {
+bool SkOverdrawColorFilter::onAppendStages(const SkStageRec& rec, bool shader_is_opaque) const {
     struct Ctx : public SkRasterPipeline_CallbackCtx {
         const SkPMColor* colors;
     };
     // TODO: do we care about transforming to dstCS?
-    auto ctx = alloc->make<Ctx>();
+    auto ctx = rec.fAlloc->make<Ctx>();
     ctx->colors = fColors;
     ctx->fn = [](SkRasterPipeline_CallbackCtx* arg, int active_pixels) {
         auto ctx = (Ctx*)arg;
@@ -61,7 +59,8 @@
             pixels[i] = SkPMColor4f::FromPMColor(ctx->colors[alpha]);
         }
     };
-    p->append(SkRasterPipeline::callback, ctx);
+    rec.fPipeline->append(SkRasterPipeline::callback, ctx);
+    return true;
 }
 
 void SkOverdrawColorFilter::flatten(SkWriteBuffer& buffer) const {
@@ -86,8 +85,10 @@
 }
 #if SK_SUPPORT_GPU
 
+#include "GrRecordingContext.h"
+
 std::unique_ptr<GrFragmentProcessor> SkOverdrawColorFilter::asFragmentProcessor(
-        GrContext* context, const GrColorSpaceInfo&) const {
+        GrRecordingContext* context, const GrColorSpaceInfo&) const {
     static int overdrawIndex = GrSkSLFP::NewIndex();
     return GrSkSLFP::Make(context, overdrawIndex, "Overdraw", SKSL_OVERDRAW_SRC, fColors,
                           sizeof(fColors));
diff --git a/src/effects/SkTableColorFilter.cpp b/src/effects/SkTableColorFilter.cpp
index c8ebb1e..ca83f05 100644
--- a/src/effects/SkTableColorFilter.cpp
+++ b/src/effects/SkTableColorFilter.cpp
@@ -10,6 +10,7 @@
 #include "SkArenaAlloc.h"
 #include "SkBitmap.h"
 #include "SkColorData.h"
+#include "SkEffectPriv.h"
 #include "SkRasterPipeline.h"
 #include "SkReadBuffer.h"
 #include "SkString.h"
@@ -83,12 +84,9 @@
 
     ~SkTable_ColorFilter() override { delete fBitmap; }
 
-    bool asComponentTable(SkBitmap* table) const override;
-    sk_sp<SkColorFilter> onMakeComposed(sk_sp<SkColorFilter> inner) const override;
-
 #if SK_SUPPORT_GPU
     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
-            GrContext*, const GrColorSpaceInfo&) const override;
+            GrRecordingContext*, const GrColorSpaceInfo&) const override;
 #endif
 
     enum {
@@ -98,8 +96,7 @@
         kB_Flag = 1 << 3,
     };
 
-    void onAppendStages(SkRasterPipeline* p, SkColorSpace*, SkArenaAlloc* alloc,
-                        bool shaderIsOpaque) const override {
+    bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override {
         const uint8_t *r = gIdentityTable,
                       *g = gIdentityTable,
                       *b = gIdentityTable,
@@ -110,17 +107,19 @@
         if (fFlags & kG_Flag) { g = ptr; ptr += 256; }
         if (fFlags & kB_Flag) { b = ptr;             }
 
+        SkRasterPipeline* p = rec.fPipeline;
         if (!shaderIsOpaque) {
             p->append(SkRasterPipeline::unpremul);
         }
 
         struct Tables { const uint8_t *r, *g, *b, *a; };
-        p->append(SkRasterPipeline::byte_tables, alloc->make<Tables>(Tables{r,g,b,a}));
+        p->append(SkRasterPipeline::byte_tables, rec.fAlloc->make<Tables>(Tables{r,g,b,a}));
 
         bool definitelyOpaque = shaderIsOpaque && a[0xff] == 0xff;
         if (!definitelyOpaque) {
             p->append(SkRasterPipeline::premul);
         }
+        return true;
     }
 
 protected:
@@ -129,6 +128,8 @@
 private:
     SK_FLATTENABLE_HOOKS(SkTable_ColorFilter)
 
+    void getTableAsBitmap(SkBitmap* table) const;
+
     mutable const SkBitmap* fBitmap; // lazily allocated
 
     uint8_t fStorage[256 * 4];
@@ -205,7 +206,7 @@
     return SkTableColorFilter::MakeARGB(a, r, g, b);
 }
 
-bool SkTable_ColorFilter::asComponentTable(SkBitmap* table) const {
+void SkTable_ColorFilter::getTableAsBitmap(SkBitmap* table) const {
     if (table) {
         if (nullptr == fBitmap) {
             SkBitmap* bmp = new SkBitmap;
@@ -228,64 +229,14 @@
         }
         *table = *fBitmap;
     }
-    return true;
-}
-
-// Combines the two lookup tables so that making a lookup using res[] has
-// the same effect as making a lookup through inner[] then outer[].
-static void combine_tables(uint8_t res[256], const uint8_t outer[256], const uint8_t inner[256]) {
-    for (int i = 0; i < 256; i++) {
-        res[i] = outer[inner[i]];
-    }
-}
-
-sk_sp<SkColorFilter> SkTable_ColorFilter::onMakeComposed(sk_sp<SkColorFilter> innerFilter) const {
-    SkBitmap innerBM;
-    if (!innerFilter->asComponentTable(&innerBM)) {
-        return nullptr;
-    }
-
-    if (nullptr == innerBM.getPixels()) {
-        return nullptr;
-    }
-
-    const uint8_t* table = fStorage;
-    const uint8_t* tableA = gIdentityTable;
-    const uint8_t* tableR = gIdentityTable;
-    const uint8_t* tableG = gIdentityTable;
-    const uint8_t* tableB = gIdentityTable;
-    if (fFlags & kA_Flag) {
-        tableA = table; table += 256;
-    }
-    if (fFlags & kR_Flag) {
-        tableR = table; table += 256;
-    }
-    if (fFlags & kG_Flag) {
-        tableG = table; table += 256;
-    }
-    if (fFlags & kB_Flag) {
-        tableB = table;
-    }
-
-    uint8_t concatA[256];
-    uint8_t concatR[256];
-    uint8_t concatG[256];
-    uint8_t concatB[256];
-
-    combine_tables(concatA, tableA, innerBM.getAddr8(0, 0));
-    combine_tables(concatR, tableR, innerBM.getAddr8(0, 1));
-    combine_tables(concatG, tableG, innerBM.getAddr8(0, 2));
-    combine_tables(concatB, tableB, innerBM.getAddr8(0, 3));
-
-    return SkTableColorFilter::MakeARGB(concatA, concatR, concatG, concatB);
 }
 
 #if SK_SUPPORT_GPU
 
 #include "GrColorSpaceInfo.h"
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrFragmentProcessor.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "SkGr.h"
 #include "glsl/GrGLSLFragmentProcessor.h"
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
@@ -294,7 +245,8 @@
 
 class ColorTableEffect : public GrFragmentProcessor {
 public:
-    static std::unique_ptr<GrFragmentProcessor> Make(GrContext* context, const SkBitmap& bitmap);
+    static std::unique_ptr<GrFragmentProcessor> Make(GrRecordingContext* context,
+                                                     const SkBitmap& bitmap);
 
     ~ColorTableEffect() override {}
 
@@ -384,7 +336,7 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-std::unique_ptr<GrFragmentProcessor> ColorTableEffect::Make(GrContext* context,
+std::unique_ptr<GrFragmentProcessor> ColorTableEffect::Make(GrRecordingContext* context,
                                                             const SkBitmap& bitmap) {
     SkASSERT(kPremul_SkAlphaType == bitmap.alphaType());
     SkASSERT(bitmap.isImmutable());
@@ -421,6 +373,9 @@
 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(ColorTableEffect);
 
 #if GR_TEST_UTILS
+
+#include "GrContext.h"
+
 std::unique_ptr<GrFragmentProcessor> ColorTableEffect::TestCreate(GrProcessorTestData* d) {
     int flags = 0;
     uint8_t luts[256][4];
@@ -451,9 +406,9 @@
 #endif
 
 std::unique_ptr<GrFragmentProcessor> SkTable_ColorFilter::asFragmentProcessor(
-        GrContext* context, const GrColorSpaceInfo&) const {
+        GrRecordingContext* context, const GrColorSpaceInfo&) const {
     SkBitmap bitmap;
-    this->asComponentTable(&bitmap);
+    this->getTableAsBitmap(&bitmap);
 
     return ColorTableEffect::Make(context, bitmap);
 }
diff --git a/src/effects/SkToSRGBColorFilter.cpp b/src/effects/SkToSRGBColorFilter.cpp
deleted file mode 100644
index 913bdbc..0000000
--- a/src/effects/SkToSRGBColorFilter.cpp
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkColorSpacePriv.h"
-#include "SkColorSpaceXformSteps.h"
-#include "SkRasterPipeline.h"
-#include "SkReadBuffer.h"
-#include "SkString.h"
-#include "SkToSRGBColorFilter.h"
-#include "SkWriteBuffer.h"
-
-#if SK_SUPPORT_GPU
-    #include "GrColorSpaceXform.h"
-#endif
-
-void SkToSRGBColorFilter::onAppendStages(SkRasterPipeline* p,
-                                         SkColorSpace* /*dst color space*/,
-                                         SkArenaAlloc* alloc,
-                                         bool shaderIsOpaque) const {
-    bool shaderIsNormalized = false;
-    alloc->make<SkColorSpaceXformSteps>(fSrcColorSpace.get(), kPremul_SkAlphaType,
-                                        sk_srgb_singleton() , kPremul_SkAlphaType)
-        ->apply(p, shaderIsNormalized);
-}
-
-sk_sp<SkColorFilter> SkToSRGBColorFilter::Make(sk_sp<SkColorSpace> srcColorSpace) {
-    if (!srcColorSpace || srcColorSpace->isSRGB()) {
-        return nullptr;
-    } else {
-        return sk_sp<SkColorFilter>(new SkToSRGBColorFilter(std::move(srcColorSpace)));
-    }
-}
-
-SkToSRGBColorFilter::SkToSRGBColorFilter(sk_sp<SkColorSpace> srcColorSpace)
-        : fSrcColorSpace(std::move(srcColorSpace)) {
-    SkASSERT(fSrcColorSpace);
-}
-
-sk_sp<SkFlattenable> SkToSRGBColorFilter::CreateProc(SkReadBuffer& buffer) {
-    auto data = buffer.readByteArrayAsData();
-    return data ? Make(SkColorSpace::Deserialize(data->data(), data->size())) : nullptr;
-}
-
-void SkToSRGBColorFilter::flatten(SkWriteBuffer& buffer) const {
-    buffer.writeDataAsByteArray(fSrcColorSpace->serialize().get());
-}
-
-#if SK_SUPPORT_GPU
-std::unique_ptr<GrFragmentProcessor> SkToSRGBColorFilter::asFragmentProcessor(
-        GrContext*, const GrColorSpaceInfo&) const {
-    return GrColorSpaceXformEffect::Make(fSrcColorSpace.get(), kPremul_SkAlphaType,
-                                         sk_srgb_singleton(),  kPremul_SkAlphaType);
-}
-#endif
diff --git a/src/effects/imagefilters/SkAlphaThresholdFilter.cpp b/src/effects/imagefilters/SkAlphaThresholdFilter.cpp
index 39b92f5..01a738e 100644
--- a/src/effects/imagefilters/SkAlphaThresholdFilter.cpp
+++ b/src/effects/imagefilters/SkAlphaThresholdFilter.cpp
@@ -8,7 +8,6 @@
 #include "SkAlphaThresholdFilter.h"
 
 #include "SkBitmap.h"
-#include "SkColorSpaceXformer.h"
 #include "SkImageFilterPriv.h"
 #include "SkReadBuffer.h"
 #include "SkSpecialImage.h"
@@ -16,13 +15,16 @@
 #include "SkRegion.h"
 
 #if SK_SUPPORT_GPU
+#include "GrCaps.h"
 #include "GrColorSpaceXform.h"
 #include "GrContext.h"
 #include "GrFixedClip.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrTextureProxy.h"
-#include "effects/GrSimpleTextureEffect.h"
-#include "effects/GrAlphaThresholdFragmentProcessor.h"
+#include "effects/generated/GrAlphaThresholdFragmentProcessor.h"
+#include "effects/generated/GrSimpleTextureEffect.h"
 #endif
 
 class SkAlphaThresholdFilterImpl : public SkImageFilter {
@@ -39,10 +41,8 @@
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
                                         SkIPoint* offset) const override;
 
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
-
 #if SK_SUPPORT_GPU
-    sk_sp<GrTextureProxy> createMaskTexture(GrContext*,
+    sk_sp<GrTextureProxy> createMaskTexture(GrRecordingContext*,
                                             const SkMatrix&,
                                             const SkIRect& bounds) const;
 #endif
@@ -102,7 +102,7 @@
 }
 
 #if SK_SUPPORT_GPU
-sk_sp<GrTextureProxy> SkAlphaThresholdFilterImpl::createMaskTexture(GrContext* context,
+sk_sp<GrTextureProxy> SkAlphaThresholdFilterImpl::createMaskTexture(GrRecordingContext* context,
                                                                     const SkMatrix& inMatrix,
                                                                     const SkIRect& bounds) const {
     GrBackendFormat format =
@@ -161,7 +161,7 @@
 
 #if SK_SUPPORT_GPU
     if (source->isTextureBacked()) {
-        GrContext* context = source->getContext();
+        auto context = source->getContext();
 
         sk_sp<GrTextureProxy> inputProxy(input->asTextureProxyRef(context));
         SkASSERT(inputProxy);
@@ -274,14 +274,3 @@
     return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(bounds.width(), bounds.height()),
                                           dst);
 }
-
-sk_sp<SkImageFilter> SkAlphaThresholdFilterImpl::onMakeColorSpace(SkColorSpaceXformer* xformer)
-const {
-    SkASSERT(1 == this->countInputs());
-    sk_sp<SkImageFilter> input = xformer->apply(this->getInput(0));
-    if (input.get() != this->getInput(0)) {
-        return SkAlphaThresholdFilter::Make(fRegion, fInnerThreshold, fOuterThreshold,
-                                            std::move(input), this->getCropRectIfSet());
-    }
-    return this->refMe();
-}
diff --git a/src/effects/imagefilters/SkArithmeticImageFilter.cpp b/src/effects/imagefilters/SkArithmeticImageFilter.cpp
index 9cb1a30..0a174d5 100644
--- a/src/effects/imagefilters/SkArithmeticImageFilter.cpp
+++ b/src/effects/imagefilters/SkArithmeticImageFilter.cpp
@@ -7,7 +7,6 @@
 
 #include "SkArithmeticImageFilter.h"
 #include "SkCanvas.h"
-#include "SkColorSpaceXformer.h"
 #include "SkImageFilterPriv.h"
 #include "SkNx.h"
 #include "SkReadBuffer.h"
@@ -18,11 +17,12 @@
 #if SK_SUPPORT_GPU
 #include "GrClip.h"
 #include "GrColorSpaceXform.h"
-#include "GrContext.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrTextureProxy.h"
 #include "SkGr.h"
-#include "effects/GrConstColorProcessor.h"
+#include "effects/generated/GrConstColorProcessor.h"
 #include "effects/GrSkSLFP.h"
 #include "effects/GrTextureDomain.h"
 #include "glsl/GrGLSLFragmentProcessor.h"
@@ -31,13 +31,13 @@
 #include "glsl/GrGLSLUniformHandler.h"
 
 GR_FP_SRC_STRING SKSL_ARITHMETIC_SRC = R"(
-in uniform half4 k;
+in uniform float4 k;
 layout(key) const in bool enforcePMColor;
 in fragmentProcessor child;
 
-void main(int x, int y, inout half4 color) {
+void main(inout half4 color) {
     half4 dst = process(child);
-    color = saturate(k.x * color * dst + k.y * color + k.z * dst + k.w);
+    color = saturate(half(k.x) * color * dst + half(k.y) * color + half(k.z) * dst + half(k.w));
     if (enforcePMColor) {
         color.rgb = min(color.rgb, color.a);
     }
@@ -78,8 +78,6 @@
 
     void drawForeground(SkCanvas* canvas, SkSpecialImage*, const SkIRect&) const;
 
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
-
 private:
     SK_FLATTENABLE_HOOKS(ArithmeticImageFilterImpl)
 
@@ -292,7 +290,7 @@
         const OutputProperties& outputProperties) const {
     SkASSERT(source->isTextureBacked());
 
-    GrContext* context = source->getContext();
+    auto context = source->getContext();
 
     sk_sp<GrTextureProxy> backgroundProxy, foregroundProxy;
 
@@ -432,19 +430,6 @@
     }
 }
 
-sk_sp<SkImageFilter> ArithmeticImageFilterImpl::onMakeColorSpace(SkColorSpaceXformer* xformer)
-const {
-    SkASSERT(2 == this->countInputs());
-    auto background = xformer->apply(this->getInput(0));
-    auto foreground = xformer->apply(this->getInput(1));
-    if (background.get() != this->getInput(0) || foreground.get() != this->getInput(1)) {
-        return SkArithmeticImageFilter::Make(fK[0], fK[1], fK[2], fK[3], fEnforcePMColor,
-                                             std::move(background), std::move(foreground),
-                                             getCropRectIfSet());
-    }
-    return this->refMe();
-}
-
 sk_sp<SkImageFilter> SkArithmeticImageFilter::Make(float k1, float k2, float k3, float k4,
                                                    bool enforcePMColor,
                                                    sk_sp<SkImageFilter> background,
diff --git a/src/effects/imagefilters/SkBlurImageFilter.cpp b/src/effects/imagefilters/SkBlurImageFilter.cpp
index 019bbe5..62d53c7 100644
--- a/src/effects/imagefilters/SkBlurImageFilter.cpp
+++ b/src/effects/imagefilters/SkBlurImageFilter.cpp
@@ -13,7 +13,6 @@
 #include "SkAutoPixmapStorage.h"
 #include "SkBitmap.h"
 #include "SkColorData.h"
-#include "SkColorSpaceXformer.h"
 #include "SkImageFilterPriv.h"
 #include "SkTFitsIn.h"
 #include "SkGpuBlurUtils.h"
@@ -45,7 +44,6 @@
     void flatten(SkWriteBuffer&) const override;
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
                                         SkIPoint* offset) const override;
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
     SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
                                MapDirection, const SkIRect* inputRect) const override;
 
@@ -486,7 +484,7 @@
          dstW = dstBounds.width(),
          dstH = dstBounds.height();
 
-    SkImageInfo dstInfo = SkImageInfo::Make(dstW, dstH, inputBM.colorType(), inputBM.alphaType());
+    SkImageInfo dstInfo = inputBM.info().makeWH(dstW, dstH);
 
     SkBitmap dst;
     if (!dst.tryAllocPixels(dstInfo)) {
@@ -629,24 +627,13 @@
         return input->makeSubset(inputBounds);
     }
 
-    GrContext* context = source->getContext();
+    auto context = source->getContext();
 
     sk_sp<GrTextureProxy> inputTexture(input->asTextureProxyRef(context));
     if (!inputTexture) {
         return nullptr;
     }
 
-    // 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.
-    // ...
-    // Unless the output is legacy. In that case, the input could be almost anything (if we're
-    // using SkColorSpaceXformCanvas), but we can't make a corresponding RTC. We don't care to,
-    // either, we want to do our blending (and blurring) without any color correction, so pass
-    // nullptr here, causing us to operate entirely in the input's color space, with no decoding.
-    // Then, when we create the output image later, we tag it with the input's color space, so
-    // it will be tagged correctly, regardless of how we created the intermediate RTCs.
     sk_sp<GrRenderTargetContext> renderTargetContext(SkGpuBlurUtils::GaussianBlur(
                             context,
                             std::move(inputTexture),
@@ -671,18 +658,6 @@
 }
 #endif
 
-sk_sp<SkImageFilter> SkBlurImageFilterImpl::onMakeColorSpace(SkColorSpaceXformer* xformer)
-const {
-    SkASSERT(1 == this->countInputs());
-
-    auto input = xformer->apply(this->getInput(0));
-    if (this->getInput(0) != input.get()) {
-        return SkBlurImageFilter::Make(fSigma.width(), fSigma.height(), std::move(input),
-                                       this->getCropRectIfSet(), fTileMode);
-    }
-    return this->refMe();
-}
-
 SkRect SkBlurImageFilterImpl::computeFastBounds(const SkRect& src) const {
     SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src;
     bounds.outset(fSigma.width() * 3, fSigma.height() * 3);
diff --git a/src/effects/imagefilters/SkColorFilterImageFilter.cpp b/src/effects/imagefilters/SkColorFilterImageFilter.cpp
index 9bea794..af0ff36 100644
--- a/src/effects/imagefilters/SkColorFilterImageFilter.cpp
+++ b/src/effects/imagefilters/SkColorFilterImageFilter.cpp
@@ -9,7 +9,6 @@
 
 #include "SkCanvas.h"
 #include "SkColorFilter.h"
-#include "SkColorSpaceXformer.h"
 #include "SkImageFilterPriv.h"
 #include "SkReadBuffer.h"
 #include "SkSpecialImage.h"
@@ -117,19 +116,6 @@
     return surf->makeImageSnapshot();
 }
 
-sk_sp<SkImageFilter> SkColorFilterImageFilter::onMakeColorSpace(SkColorSpaceXformer* xformer)
-const {
-    SkASSERT(1 == this->countInputs());
-
-    sk_sp<SkImageFilter> input = xformer->apply(this->getInput(0));
-    auto colorFilter = xformer->apply(fColorFilter.get());
-    if (this->getInput(0) != input.get() || fColorFilter != colorFilter) {
-        return SkColorFilterImageFilter::Make(std::move(colorFilter), std::move(input),
-                                              this->getCropRectIfSet());
-    }
-    return this->refMe();
-}
-
 bool SkColorFilterImageFilter::onIsColorFilterNode(SkColorFilter** filter) const {
     SkASSERT(1 == this->countInputs());
     if (!this->cropRectIsSet()) {
diff --git a/src/effects/imagefilters/SkComposeImageFilter.cpp b/src/effects/imagefilters/SkComposeImageFilter.cpp
index 842588d..0f1672d 100644
--- a/src/effects/imagefilters/SkComposeImageFilter.cpp
+++ b/src/effects/imagefilters/SkComposeImageFilter.cpp
@@ -6,7 +6,6 @@
  */
 
 #include "SkComposeImageFilter.h"
-#include "SkColorSpaceXformer.h"
 #include "SkImageFilterPriv.h"
 #include "SkReadBuffer.h"
 #include "SkSpecialImage.h"
@@ -63,17 +62,6 @@
     return outer;
 }
 
-sk_sp<SkImageFilter> SkComposeImageFilter::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    SkASSERT(2 == this->countInputs() && this->getInput(0) && this->getInput(1));
-
-    auto input0 = xformer->apply(this->getInput(0));
-    auto input1 = xformer->apply(this->getInput(1));
-    if (input0.get() != this->getInput(0) || input1.get() != this->getInput(1)) {
-        return SkComposeImageFilter::Make(std::move(input0), std::move(input1));
-    }
-    return this->refMe();
-}
-
 SkIRect SkComposeImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
                                              MapDirection dir, const SkIRect* inputRect) const {
     SkImageFilter* outer = this->getInput(0);
diff --git a/src/effects/imagefilters/SkDisplacementMapEffect.cpp b/src/effects/imagefilters/SkDisplacementMapEffect.cpp
index 3c48d75..b665fd6 100644
--- a/src/effects/imagefilters/SkDisplacementMapEffect.cpp
+++ b/src/effects/imagefilters/SkDisplacementMapEffect.cpp
@@ -8,7 +8,6 @@
 #include "SkDisplacementMapEffect.h"
 
 #include "SkBitmap.h"
-#include "SkColorSpaceXformer.h"
 #include "SkImageFilterPriv.h"
 #include "SkReadBuffer.h"
 #include "SkSpecialImage.h"
@@ -16,10 +15,12 @@
 #include "SkUnPreMultiply.h"
 #include "SkColorData.h"
 #if SK_SUPPORT_GPU
+#include "GrCaps.h"
 #include "GrClip.h"
 #include "GrColorSpaceXform.h"
-#include "GrContext.h"
 #include "GrCoordTransform.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrTexture.h"
 #include "GrTextureProxy.h"
@@ -270,7 +271,7 @@
 
 #if SK_SUPPORT_GPU
     if (source->isTextureBacked()) {
-        GrContext* context = source->getContext();
+        auto context = source->getContext();
 
         sk_sp<GrTextureProxy> colorProxy(color->asTextureProxyRef(context));
         sk_sp<GrTextureProxy> displProxy(displ->asTextureProxyRef(context));
@@ -357,21 +358,6 @@
                                           dst);
 }
 
-sk_sp<SkImageFilter> SkDisplacementMapEffect::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    SkASSERT(2 == this->countInputs());
-    // Intentionally avoid xforming the displacement filter.  The values will be used as
-    // offsets, not as colors.
-    sk_sp<SkImageFilter> displacement = sk_ref_sp(const_cast<SkImageFilter*>(this->getInput(0)));
-    sk_sp<SkImageFilter> color = xformer->apply(this->getInput(1));
-
-    if (color.get() != this->getInput(1)) {
-        return SkDisplacementMapEffect::Make(fXChannelSelector, fYChannelSelector, fScale,
-                                             std::move(displacement), std::move(color),
-                                             this->getCropRectIfSet());
-    }
-    return this->refMe();
-}
-
 SkRect SkDisplacementMapEffect::computeFastBounds(const SkRect& src) const {
     SkRect bounds = this->getColorInput() ? this->getColorInput()->computeFastBounds(src) : src;
     bounds.outset(SkScalarAbs(fScale) * SK_ScalarHalf, SkScalarAbs(fScale) * SK_ScalarHalf);
diff --git a/src/effects/imagefilters/SkDropShadowImageFilter.cpp b/src/effects/imagefilters/SkDropShadowImageFilter.cpp
index d19c05f..c27ff22 100644
--- a/src/effects/imagefilters/SkDropShadowImageFilter.cpp
+++ b/src/effects/imagefilters/SkDropShadowImageFilter.cpp
@@ -9,7 +9,6 @@
 
 #include "SkBlurImageFilter.h"
 #include "SkCanvas.h"
-#include "SkColorSpaceXformer.h"
 #include "SkImageFilterPriv.h"
 #include "SkReadBuffer.h"
 #include "SkSpecialImage.h"
@@ -97,7 +96,7 @@
     SkPaint paint;
     paint.setAntiAlias(true);
     paint.setImageFilter(SkBlurImageFilter::Make(sigma.fX, sigma.fY, nullptr));
-    paint.setColorFilter(SkColorFilter::MakeModeFilter(fColor, SkBlendMode::kSrcIn));
+    paint.setColorFilter(SkColorFilters::Blend(fColor, SkBlendMode::kSrcIn));
 
     SkVector offsetVec = SkVector::Make(fDx, fDy);
     ctx.ctm().mapVectors(&offsetVec, 1);
@@ -114,18 +113,6 @@
     return surf->makeImageSnapshot();
 }
 
-sk_sp<SkImageFilter> SkDropShadowImageFilter::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    SkASSERT(1 == this->countInputs());
-
-    sk_sp<SkImageFilter> input = xformer->apply(this->getInput(0));
-    SkColor color = xformer->apply(fColor);
-    if (input.get() != this->getInput(0) || color != fColor) {
-        return SkDropShadowImageFilter::Make(fDx, fDy, fSigmaX, fSigmaY, color,
-                                             fShadowMode, input, this->getCropRectIfSet());
-    }
-    return this->refMe();
-}
-
 SkRect SkDropShadowImageFilter::computeFastBounds(const SkRect& src) const {
     SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src;
     SkRect shadowBounds = bounds;
diff --git a/src/effects/imagefilters/SkImageSource.cpp b/src/effects/imagefilters/SkImageSource.cpp
index 2934de5..5a7d26d 100644
--- a/src/effects/imagefilters/SkImageSource.cpp
+++ b/src/effects/imagefilters/SkImageSource.cpp
@@ -8,7 +8,6 @@
 #include "SkImageSource.h"
 
 #include "SkCanvas.h"
-#include "SkColorSpaceXformer.h"
 #include "SkImage.h"
 #include "SkReadBuffer.h"
 #include "SkSpecialImage.h"
@@ -132,16 +131,6 @@
     return surf->makeImageSnapshot();
 }
 
-sk_sp<SkImageFilter> SkImageSource::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    SkASSERT(0 == this->countInputs());
-
-    auto image = xformer->apply(fImage.get());
-    if (image != fImage) {
-        return SkImageSource::Make(image, fSrcRect, fDstRect, fFilterQuality);
-    }
-    return this->refMe();
-}
-
 SkRect SkImageSource::computeFastBounds(const SkRect& src) const {
     return fDstRect;
 }
diff --git a/src/effects/imagefilters/SkLightingImageFilter.cpp b/src/effects/imagefilters/SkLightingImageFilter.cpp
index 40bb72f..55e9801 100644
--- a/src/effects/imagefilters/SkLightingImageFilter.cpp
+++ b/src/effects/imagefilters/SkLightingImageFilter.cpp
@@ -8,7 +8,6 @@
 #include "SkLightingImageFilter.h"
 #include "SkBitmap.h"
 #include "SkColorData.h"
-#include "SkColorSpaceXformer.h"
 #include "SkImageFilterPriv.h"
 #include "SkPoint3.h"
 #include "SkReadBuffer.h"
@@ -17,10 +16,12 @@
 #include "SkWriteBuffer.h"
 
 #if SK_SUPPORT_GPU
-#include "GrContext.h"
+#include "GrCaps.h"
 #include "GrFixedClip.h"
 #include "GrFragmentProcessor.h"
 #include "GrPaint.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrTexture.h"
 #include "GrTextureProxy.h"
@@ -111,8 +112,6 @@
     }
     virtual SkImageFilterLight* transform(const SkMatrix& matrix) const = 0;
 
-    virtual sk_sp<SkImageFilterLight> makeColorSpace(SkColorSpaceXformer*) const = 0;
-
     // Defined below SkLight's subclasses.
     void flattenLight(SkWriteBuffer& buffer) const;
     static SkImageFilterLight* UnflattenLight(SkReadBuffer& buffer);
@@ -459,7 +458,7 @@
                                                    const OutputProperties& outputProperties) const {
     SkASSERT(source->isTextureBacked());
 
-    GrContext* context = source->getContext();
+    auto context = source->getContext();
 
     sk_sp<GrTextureProxy> inputProxy(input->asTextureProxyRef(context));
     SkASSERT(inputProxy);
@@ -541,7 +540,6 @@
 
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
                                         SkIPoint* offset) const override;
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
 
 #if SK_SUPPORT_GPU
     std::unique_ptr<GrFragmentProcessor> makeFragmentProcessor(sk_sp<GrTextureProxy>,
@@ -577,7 +575,6 @@
 
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
                                         SkIPoint* offset) const override;
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
 
 #if SK_SUPPORT_GPU
     std::unique_ptr<GrFragmentProcessor> makeFragmentProcessor(sk_sp<GrTextureProxy>,
@@ -824,13 +821,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-static SkColor xform_color(const SkPoint3& color, SkColorSpaceXformer* xformer) {
-    SkColor origColor = SkColorSetRGB(SkScalarRoundToInt(color.fX),
-                                      SkScalarRoundToInt(color.fY),
-                                      SkScalarRoundToInt(color.fZ));
-    return xformer->apply(origColor);
-}
-
 class SkDistantLight : public SkImageFilterLight {
 public:
     SkDistantLight(const SkPoint3& direction, SkColor color)
@@ -852,10 +842,6 @@
 #endif
     }
 
-    sk_sp<SkImageFilterLight> makeColorSpace(SkColorSpaceXformer* xformer) const override {
-        return sk_make_sp<SkDistantLight>(fDirection, xform_color(this->color(), xformer));
-    }
-
     bool isEqual(const SkImageFilterLight& other) const override {
         if (other.type() != kDistant_LightType) {
             return false;
@@ -913,10 +899,6 @@
 #endif
     }
 
-    sk_sp<SkImageFilterLight> makeColorSpace(SkColorSpaceXformer* xformer) const override {
-        return sk_make_sp<SkPointLight>(fLocation, xform_color(this->color(), xformer));
-    }
-
     bool isEqual(const SkImageFilterLight& other) const override {
         if (other.type() != kPoint_LightType) {
             return false;
@@ -966,8 +948,7 @@
      : INHERITED(color),
        fLocation(location),
        fTarget(target),
-       fSpecularExponent(SkScalarPin(specularExponent, kSpecularExponentMin, kSpecularExponentMax)),
-       fCutoffAngle(cutoffAngle)
+       fSpecularExponent(SkScalarPin(specularExponent, kSpecularExponentMin, kSpecularExponentMax))
     {
        fS = target - location;
        fast_normalize(&fS);
@@ -977,11 +958,6 @@
        fConeScale = SkScalarInvert(antiAliasThreshold);
     }
 
-    sk_sp<SkImageFilterLight> makeColorSpace(SkColorSpaceXformer* xformer) const override {
-        return sk_make_sp<SkSpotLight>(fLocation, fTarget, fSpecularExponent, fCutoffAngle,
-                                       xform_color(this->color(), xformer));
-    }
-
     SkImageFilterLight* transform(const SkMatrix& matrix) const override {
         SkPoint location2 = SkPoint::Make(fLocation.fX, fLocation.fY);
         matrix.mapPoints(&location2, 1);
@@ -1105,7 +1081,6 @@
     SkPoint3 fLocation;
     SkPoint3 fTarget;
     SkScalar fSpecularExponent;
-    SkScalar fCutoffAngle;
     SkScalar fCosOuterConeAngle;
     SkScalar fCosInnerConeAngle;
     SkScalar fConeScale;
@@ -1360,18 +1335,6 @@
                                           dst);
 }
 
-sk_sp<SkImageFilter> SkDiffuseLightingImageFilter::onMakeColorSpace(SkColorSpaceXformer* xformer)
-const {
-    SkASSERT(1 == this->countInputs());
-    auto input = xformer->apply(this->getInput(0));
-    auto light = this->light()->makeColorSpace(xformer);
-    if (input.get() != this->getInput(0) || light.get() != this->light()) {
-        return SkDiffuseLightingImageFilter::Make(std::move(light), 255.0f * this->surfaceScale(),
-                                                  fKD, std::move(input), this->getCropRectIfSet());
-    }
-    return this->refMe();
-}
-
 #if SK_SUPPORT_GPU
 std::unique_ptr<GrFragmentProcessor> SkDiffuseLightingImageFilter::makeFragmentProcessor(
         sk_sp<GrTextureProxy> proxy,
@@ -1507,20 +1470,6 @@
     return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(bounds.width(), bounds.height()), dst);
 }
 
-sk_sp<SkImageFilter> SkSpecularLightingImageFilter::onMakeColorSpace(SkColorSpaceXformer* xformer)
-const {
-    SkASSERT(1 == this->countInputs());
-
-    auto input = xformer->apply(this->getInput(0));
-    auto light = this->light()->makeColorSpace(xformer);
-    if (input.get() != this->getInput(0) || light.get() != this->light()) {
-        return SkSpecularLightingImageFilter::Make(std::move(light),
-                                                   255.0f * this->surfaceScale(), fKS, fShininess,
-                                                   std::move(input), this->getCropRectIfSet());
-    }
-    return this->refMe();
-}
-
 #if SK_SUPPORT_GPU
 std::unique_ptr<GrFragmentProcessor> SkSpecularLightingImageFilter::makeFragmentProcessor(
         sk_sp<GrTextureProxy> proxy,
diff --git a/src/effects/imagefilters/SkMagnifierImageFilter.cpp b/src/effects/imagefilters/SkMagnifierImageFilter.cpp
index 6c46c4c..ed9f3c7 100644
--- a/src/effects/imagefilters/SkMagnifierImageFilter.cpp
+++ b/src/effects/imagefilters/SkMagnifierImageFilter.cpp
@@ -9,7 +9,6 @@
 
 #include "SkBitmap.h"
 #include "SkColorData.h"
-#include "SkColorSpaceXformer.h"
 #include "SkImageFilterPriv.h"
 #include "SkReadBuffer.h"
 #include "SkSpecialImage.h"
@@ -22,7 +21,7 @@
 #include "GrContext.h"
 #include "GrCoordTransform.h"
 #include "GrTexture.h"
-#include "effects/GrMagnifierEffect.h"
+#include "effects/generated/GrMagnifierEffect.h"
 #include "glsl/GrGLSLFragmentProcessor.h"
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
 #include "glsl/GrGLSLProgramDataManager.h"
@@ -97,7 +96,7 @@
 
 #if SK_SUPPORT_GPU
     if (source->isTextureBacked()) {
-        GrContext* context = source->getContext();
+        auto context = source->getContext();
 
         sk_sp<GrTextureProxy> inputProxy(input->asTextureProxyRef(context));
         SkASSERT(inputProxy);
@@ -188,13 +187,3 @@
     return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(bounds.width(), bounds.height()),
                                           dst);
 }
-
-sk_sp<SkImageFilter> SkMagnifierImageFilter::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    SkASSERT(1 == this->countInputs());
-    auto input = xformer->apply(this->getInput(0));
-    if (input.get() != this->getInput(0)) {
-        return SkMagnifierImageFilter::Make(fSrcRect, fInset, std::move(input),
-                                            this->getCropRectIfSet());
-    }
-    return this->refMe();
-}
diff --git a/src/effects/imagefilters/SkMatrixConvolutionImageFilter.cpp b/src/effects/imagefilters/SkMatrixConvolutionImageFilter.cpp
index 7668a02..7d2f9d3 100644
--- a/src/effects/imagefilters/SkMatrixConvolutionImageFilter.cpp
+++ b/src/effects/imagefilters/SkMatrixConvolutionImageFilter.cpp
@@ -8,7 +8,6 @@
 #include "SkMatrixConvolutionImageFilter.h"
 #include "SkBitmap.h"
 #include "SkColorData.h"
-#include "SkColorSpaceXformer.h"
 #include "SkImageFilterPriv.h"
 #include "SkReadBuffer.h"
 #include "SkSpecialImage.h"
@@ -339,7 +338,7 @@
     // Note: if the kernel is too big, the GPU path falls back to SW
     if (source->isTextureBacked() &&
         fKernelSize.width() * fKernelSize.height() <= MAX_KERNEL_SIZE) {
-        GrContext* context = source->getContext();
+        auto 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
@@ -436,19 +435,6 @@
                                           dst);
 }
 
-sk_sp<SkImageFilter> SkMatrixConvolutionImageFilter::onMakeColorSpace(SkColorSpaceXformer* xformer)
-const {
-    SkASSERT(1 == this->countInputs());
-
-    sk_sp<SkImageFilter> input = xformer->apply(this->getInput(0));
-    if (input.get() != this->getInput(0)) {
-        return SkMatrixConvolutionImageFilter::Make(fKernelSize, fKernel, fGain, fBias,
-                                                    fKernelOffset, fTileMode, fConvolveAlpha,
-                                                    std::move(input), this->getCropRectIfSet());
-    }
-    return this->refMe();
-}
-
 SkIRect SkMatrixConvolutionImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
                                                            MapDirection dir,
                                                            const SkIRect* inputRect) const {
diff --git a/src/effects/imagefilters/SkMergeImageFilter.cpp b/src/effects/imagefilters/SkMergeImageFilter.cpp
index 633ddd4..9730506 100644
--- a/src/effects/imagefilters/SkMergeImageFilter.cpp
+++ b/src/effects/imagefilters/SkMergeImageFilter.cpp
@@ -8,7 +8,6 @@
 #include "SkMergeImageFilter.h"
 
 #include "SkCanvas.h"
-#include "SkColorSpaceXformer.h"
 #include "SkReadBuffer.h"
 #include "SkSpecialImage.h"
 #include "SkSpecialSurface.h"
@@ -94,21 +93,6 @@
     return surf->makeImageSnapshot();
 }
 
-sk_sp<SkImageFilter> SkMergeImageFilter::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    SkSTArray<5, sk_sp<SkImageFilter>> inputs(this->countInputs());
-    bool changed = false;
-    for (int i = 0; i < this->countInputs(); i++) {
-        inputs.push_back(xformer->apply(this->getInput(i)));
-        changed |= (inputs[i].get() != this->getInput(i));
-    }
-
-    if (changed) {
-        return SkMergeImageFilter::Make(inputs.begin(), this->countInputs(),
-                                        this->getCropRectIfSet());
-    }
-    return this->refMe();
-}
-
 sk_sp<SkFlattenable> SkMergeImageFilter::CreateProc(SkReadBuffer& buffer) {
     Common common;
     if (!common.unflatten(buffer, -1) || !buffer.isValid()) {
diff --git a/src/effects/imagefilters/SkMorphologyImageFilter.cpp b/src/effects/imagefilters/SkMorphologyImageFilter.cpp
index 46795fc..e156764 100644
--- a/src/effects/imagefilters/SkMorphologyImageFilter.cpp
+++ b/src/effects/imagefilters/SkMorphologyImageFilter.cpp
@@ -9,7 +9,6 @@
 
 #include "SkBitmap.h"
 #include "SkColorData.h"
-#include "SkColorSpaceXformer.h"
 #include "SkImageFilterPriv.h"
 #include "SkReadBuffer.h"
 #include "SkRect.h"
@@ -18,8 +17,11 @@
 
 #if SK_SUPPORT_GPU
 #include "GrContext.h"
+#include "GrContextPriv.h"
 #include "GrCoordTransform.h"
 #include "GrFixedClip.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrTexture.h"
 #include "GrTextureProxy.h"
@@ -461,7 +463,7 @@
 }
 
 static sk_sp<SkSpecialImage> apply_morphology(
-                                          GrContext* context,
+                                          GrRecordingContext* context,
                                           SkSpecialImage* input,
                                           const SkIRect& rect,
                                           GrMorphologyEffect::Type morphType,
@@ -677,7 +679,7 @@
 
 #if SK_SUPPORT_GPU
     if (source->isTextureBacked()) {
-        GrContext* context = source->getContext();
+        auto 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
@@ -751,16 +753,3 @@
     return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(bounds.width(), bounds.height()),
                                           dst, &source->props());
 }
-
-sk_sp<SkImageFilter> SkMorphologyImageFilter::onMakeColorSpace(SkColorSpaceXformer* xformer) const{
-    SkASSERT(1 == this->countInputs());
-    auto input = xformer->apply(this->getInput(0));
-    if (input.get() != this->getInput(0)) {
-        return (SkMorphologyImageFilter::kDilate_Op == this->op())
-                ? SkDilateImageFilter::Make(fRadius.width(), fRadius.height(), std::move(input),
-                                            this->getCropRectIfSet())
-                : SkErodeImageFilter::Make(fRadius.width(), fRadius.height(), std::move(input),
-                                           this->getCropRectIfSet());
-    }
-    return this->refMe();
-}
diff --git a/src/effects/imagefilters/SkOffsetImageFilter.cpp b/src/effects/imagefilters/SkOffsetImageFilter.cpp
index 25b7289..f7d3bc1 100644
--- a/src/effects/imagefilters/SkOffsetImageFilter.cpp
+++ b/src/effects/imagefilters/SkOffsetImageFilter.cpp
@@ -6,7 +6,6 @@
  */
 
 #include "SkOffsetImageFilter.h"
-#include "SkColorSpaceXformer.h"
 #include "SkCanvas.h"
 #include "SkImageFilterPriv.h"
 #include "SkMatrix.h"
@@ -79,17 +78,6 @@
     }
 }
 
-sk_sp<SkImageFilter> SkOffsetImageFilter::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    SkASSERT(1 == this->countInputs());
-
-    auto input = xformer->apply(this->getInput(0));
-    if (input.get() != this->getInput(0)) {
-        return SkOffsetImageFilter::Make(fOffset.fX, fOffset.fY, std::move(input),
-                                         this->getCropRectIfSet());
-    }
-    return this->refMe();
-}
-
 SkRect SkOffsetImageFilter::computeFastBounds(const SkRect& src) const {
     SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src;
     bounds.offset(fOffset.fX, fOffset.fY);
diff --git a/src/effects/imagefilters/SkPaintImageFilter.cpp b/src/effects/imagefilters/SkPaintImageFilter.cpp
index 39f79bb..10d924d 100644
--- a/src/effects/imagefilters/SkPaintImageFilter.cpp
+++ b/src/effects/imagefilters/SkPaintImageFilter.cpp
@@ -7,7 +7,6 @@
 
 #include "SkPaintImageFilter.h"
 #include "SkCanvas.h"
-#include "SkColorSpaceXformer.h"
 #include "SkImageFilterPriv.h"
 #include "SkReadBuffer.h"
 #include "SkSpecialImage.h"
@@ -72,14 +71,6 @@
     return surf->makeImageSnapshot();
 }
 
-sk_sp<SkImageFilter> SkPaintImageFilter::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    SkPaint paint = xformer->apply(fPaint);
-    if (paint != fPaint) {
-        return SkPaintImageFilter::Make(paint, this->getCropRectIfSet());
-    }
-    return this->refMe();
-}
-
 bool SkPaintImageFilter::affectsTransparentBlack() const {
     return true;
 }
diff --git a/src/effects/imagefilters/SkPictureImageFilter.cpp b/src/effects/imagefilters/SkPictureImageFilter.cpp
index 898f970..da8a98a 100644
--- a/src/effects/imagefilters/SkPictureImageFilter.cpp
+++ b/src/effects/imagefilters/SkPictureImageFilter.cpp
@@ -8,8 +8,6 @@
 #include "SkPictureImageFilter.h"
 
 #include "SkCanvas.h"
-#include "SkColorSpaceXformCanvas.h"
-#include "SkColorSpaceXformer.h"
 #include "SkImageSource.h"
 #include "SkPicturePriv.h"
 #include "SkReadBuffer.h"
@@ -24,7 +22,7 @@
 
 sk_sp<SkImageFilter> SkPictureImageFilter::Make(sk_sp<SkPicture> picture,
                                                 const SkRect& cropRect) {
-    return sk_sp<SkImageFilter>(new SkPictureImageFilter(std::move(picture), cropRect, nullptr));
+    return sk_sp<SkImageFilter>(new SkPictureImageFilter(std::move(picture), cropRect));
 }
 
 SkPictureImageFilter::SkPictureImageFilter(sk_sp<SkPicture> picture)
@@ -33,12 +31,10 @@
     , fCropRect(fPicture ? fPicture->cullRect() : SkRect::MakeEmpty()) {
 }
 
-SkPictureImageFilter::SkPictureImageFilter(sk_sp<SkPicture> picture, const SkRect& cropRect,
-                                           sk_sp<SkColorSpace> colorSpace)
+SkPictureImageFilter::SkPictureImageFilter(sk_sp<SkPicture> picture, const SkRect& cropRect)
     : INHERITED(nullptr, 0, nullptr)
     , fPicture(std::move(picture))
-    , fCropRect(cropRect)
-    , fColorSpace(std::move(colorSpace)) {
+    , fCropRect(cropRect) {
 }
 
 enum PictureResolution {
@@ -70,7 +66,7 @@
                                           buffer.checkFilterQuality());
         }
     }
-    return sk_sp<SkImageFilter>(new SkPictureImageFilter(picture, cropRect, nullptr));
+    return sk_sp<SkImageFilter>(new SkPictureImageFilter(picture, cropRect));
 }
 
 void SkPictureImageFilter::flatten(SkWriteBuffer& buffer) const {
@@ -113,13 +109,6 @@
     SkASSERT(canvas);
     canvas->clear(0x0);
 
-    std::unique_ptr<SkCanvas> xformCanvas;
-    if (fColorSpace) {
-        // Only non-null in the case where onMakeColorSpace() was called.  This instructs
-        // us to do the color space xform on playback.
-        xformCanvas = SkCreateColorSpaceXformCanvas(canvas, fColorSpace);
-        canvas = xformCanvas.get();
-    }
     canvas->translate(-SkIntToScalar(bounds.fLeft), -SkIntToScalar(bounds.fTop));
     canvas->concat(ctx.ctm());
     canvas->drawPicture(fPicture);
@@ -128,12 +117,3 @@
     offset->fY = bounds.fTop;
     return surf->makeImageSnapshot();
 }
-
-sk_sp<SkImageFilter> SkPictureImageFilter::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    sk_sp<SkColorSpace> dstCS = xformer->dst();
-    if (SkColorSpace::Equals(dstCS.get(), fColorSpace.get())) {
-        return this->refMe();
-    }
-
-    return sk_sp<SkImageFilter>(new SkPictureImageFilter(fPicture, fCropRect, std::move(dstCS)));
-}
diff --git a/src/effects/imagefilters/SkTileImageFilter.cpp b/src/effects/imagefilters/SkTileImageFilter.cpp
index eddad27..edd9f67 100644
--- a/src/effects/imagefilters/SkTileImageFilter.cpp
+++ b/src/effects/imagefilters/SkTileImageFilter.cpp
@@ -6,7 +6,6 @@
  */
 
 #include "SkTileImageFilter.h"
-#include "SkColorSpaceXformer.h"
 #include "SkCanvas.h"
 #include "SkImage.h"
 #include "SkImageFilterPriv.h"
@@ -109,7 +108,7 @@
 
     SkPaint paint;
     paint.setBlendMode(SkBlendMode::kSrc);
-    paint.setShader(subset->makeShader(SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode));
+    paint.setShader(subset->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat));
     canvas->translate(-dstRect.fLeft, -dstRect.fTop);
     canvas->drawRect(dstRect, paint);
     offset->fX = dstIRect.fLeft;
@@ -117,16 +116,6 @@
     return surf->makeImageSnapshot();
 }
 
-sk_sp<SkImageFilter> SkTileImageFilter::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    SkASSERT(1 == this->countInputs());
-
-    auto input = xformer->apply(this->getInput(0));
-    if (input.get() != this->getInput(0)) {
-        return SkTileImageFilter::Make(fSrcRect, fDstRect, std::move(input));
-    }
-    return this->refMe();
-}
-
 SkIRect SkTileImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
                                               MapDirection dir, const SkIRect* inputRect) const {
     SkRect rect = kReverse_MapDirection == dir ? fSrcRect : fDstRect;
diff --git a/src/effects/imagefilters/SkXfermodeImageFilter.cpp b/src/effects/imagefilters/SkXfermodeImageFilter.cpp
index a9d4ed8..8cd21c8 100644
--- a/src/effects/imagefilters/SkXfermodeImageFilter.cpp
+++ b/src/effects/imagefilters/SkXfermodeImageFilter.cpp
@@ -9,22 +9,23 @@
 #include "SkArithmeticImageFilter.h"
 #include "SkCanvas.h"
 #include "SkColorData.h"
-#include "SkColorSpaceXformer.h"
 #include "SkImageFilterPriv.h"
 #include "SkReadBuffer.h"
 #include "SkSpecialImage.h"
 #include "SkSpecialSurface.h"
 #include "SkWriteBuffer.h"
 #if SK_SUPPORT_GPU
+#include "GrCaps.h"
 #include "GrClip.h"
 #include "GrColorSpaceXform.h"
-#include "GrContext.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrTextureProxy.h"
 
-#include "effects/GrConstColorProcessor.h"
+#include "effects/generated/GrConstColorProcessor.h"
 #include "effects/GrTextureDomain.h"
-#include "effects/GrSimpleTextureEffect.h"
+#include "effects/generated/GrSimpleTextureEffect.h"
 #include "SkGr.h"
 #endif
 #include "SkClipOpPriv.h"
@@ -37,7 +38,6 @@
 protected:
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
                                         SkIPoint* offset) const override;
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
 
     SkIRect onFilterBounds(const SkIRect&, const SkMatrix& ctm,
                            MapDirection, const SkIRect* inputRect) const override;
@@ -220,18 +220,6 @@
     }
 }
 
-sk_sp<SkImageFilter> SkXfermodeImageFilter_Base::onMakeColorSpace(SkColorSpaceXformer* xformer)
-const {
-    SkASSERT(2 == this->countInputs());
-    auto background = xformer->apply(this->getInput(0));
-    auto foreground = xformer->apply(this->getInput(1));
-    if (background.get() != this->getInput(0) || foreground.get() != this->getInput(1)) {
-        return SkXfermodeImageFilter::Make(fMode, std::move(background), std::move(foreground),
-                                           this->getCropRectIfSet());
-    }
-    return this->refMe();
-}
-
 void SkXfermodeImageFilter_Base::drawForeground(SkCanvas* canvas, SkSpecialImage* img,
                                                 const SkIRect& fgBounds) const {
     SkPaint paint;
@@ -260,7 +248,7 @@
                                                    const OutputProperties& outputProperties) const {
     SkASSERT(source->isTextureBacked());
 
-    GrContext* context = source->getContext();
+    auto context = source->getContext();
 
     sk_sp<GrTextureProxy> backgroundProxy, foregroundProxy;
 
diff --git a/src/gpu/GrAHardwareBufferImageGenerator.cpp b/src/gpu/GrAHardwareBufferImageGenerator.cpp
index 3c52c40..51908e1 100644
--- a/src/gpu/GrAHardwareBufferImageGenerator.cpp
+++ b/src/gpu/GrAHardwareBufferImageGenerator.cpp
@@ -16,15 +16,19 @@
 
 #include <android/hardware_buffer.h>
 
+#include "GrAHardwareBufferUtils.h"
 #include "GrBackendSurface.h"
 #include "GrContext.h"
 #include "GrContextPriv.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrResourceCache.h"
 #include "GrResourceProvider.h"
 #include "GrResourceProviderPriv.h"
 #include "GrTexture.h"
 #include "GrTextureProxy.h"
+#include "SkExchange.h"
 #include "SkMessageBus.h"
 #include "gl/GrGLDefines.h"
 #include "gl/GrGLTypes.h"
@@ -42,63 +46,17 @@
 #define PROT_CONTENT_EXT_STR "EGL_EXT_protected_content"
 #define EGL_PROTECTED_CONTENT_EXT 0x32C0
 
-static bool can_import_protected_content_eglimpl() {
-    EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
-    const char* exts = eglQueryString(dpy, EGL_EXTENSIONS);
-    size_t cropExtLen = strlen(PROT_CONTENT_EXT_STR);
-    size_t extsLen = strlen(exts);
-    bool equal = !strcmp(PROT_CONTENT_EXT_STR, exts);
-    bool atStart = !strncmp(PROT_CONTENT_EXT_STR " ", exts, cropExtLen+1);
-    bool atEnd = (cropExtLen+1) < extsLen
-                  && !strcmp(" " PROT_CONTENT_EXT_STR,
-                  exts + extsLen - (cropExtLen+1));
-    bool inMiddle = strstr(exts, " " PROT_CONTENT_EXT_STR " ");
-    return equal || atStart || atEnd || inMiddle;
-}
-
-static bool can_import_protected_content(GrContext* context) {
-    if (GrBackendApi::kOpenGL == context->backend()) {
-        // Only compute whether the extension is present once the first time this
-        // function is called.
-        static bool hasIt = can_import_protected_content_eglimpl();
-        return hasIt;
-    }
-    return false;
-}
-
 std::unique_ptr<SkImageGenerator> GrAHardwareBufferImageGenerator::Make(
         AHardwareBuffer* graphicBuffer, SkAlphaType alphaType, sk_sp<SkColorSpace> colorSpace,
         GrSurfaceOrigin surfaceOrigin) {
     AHardwareBuffer_Desc bufferDesc;
     AHardwareBuffer_describe(graphicBuffer, &bufferDesc);
-    SkColorType colorType;
-    switch (bufferDesc.format) {
-    case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
-    case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
-        colorType = kRGBA_8888_SkColorType;
-        break;
-    case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
-        colorType = kRGBA_F16_SkColorType;
-        break;
-    case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
-        colorType = kRGB_565_SkColorType;
-        break;
-    case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
-        colorType = kRGB_888x_SkColorType;
-        break;
-    case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
-        colorType = kRGBA_1010102_SkColorType;
-        break;
-    default:
-        // Given that we only use this texture as a source, colorType will not impact how Skia uses
-        // the texture.  The only potential affect this is anticipated to have is that for some
-        // format types if we are not bound as an OES texture we may get invalid results for SKP
-        // capture if we read back the texture.
-        colorType = kRGBA_8888_SkColorType;
-        break;
-    }
+
+    SkColorType colorType =
+            GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(bufferDesc.format);
     SkImageInfo info = SkImageInfo::Make(bufferDesc.width, bufferDesc.height, colorType,
                                          alphaType, std::move(colorSpace));
+
     bool createProtectedImage = 0 != (bufferDesc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
     return std::unique_ptr<SkImageGenerator>(new GrAHardwareBufferImageGenerator(
             info, graphicBuffer, alphaType, createProtectedImage,
@@ -122,428 +80,21 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-#ifdef SK_VULKAN
-
-class VulkanCleanupHelper {
-public:
-    VulkanCleanupHelper(GrVkGpu* gpu, VkImage image, VkDeviceMemory memory)
-        : fDevice(gpu->device())
-        , fImage(image)
-        , fMemory(memory)
-        , fDestroyImage(gpu->vkInterface()->fFunctions.fDestroyImage)
-        , fFreeMemory(gpu->vkInterface()->fFunctions.fFreeMemory) {}
-    ~VulkanCleanupHelper() {
-        fDestroyImage(fDevice, fImage, nullptr);
-        fFreeMemory(fDevice, fMemory, nullptr);
-    }
-private:
-    VkDevice           fDevice;
-    VkImage            fImage;
-    VkDeviceMemory     fMemory;
-    PFN_vkDestroyImage fDestroyImage;
-    PFN_vkFreeMemory   fFreeMemory;
-};
-
-void GrAHardwareBufferImageGenerator::DeleteVkImage(void* context) {
-    VulkanCleanupHelper* cleanupHelper = static_cast<VulkanCleanupHelper*>(context);
-    delete cleanupHelper;
-}
-
-#define VK_CALL(X) gpu->vkInterface()->fFunctions.f##X;
-
-static GrBackendTexture make_vk_backend_texture(
-        GrContext* context, AHardwareBuffer* hardwareBuffer,
-        int width, int height, GrPixelConfig config,
-        GrAHardwareBufferImageGenerator::DeleteImageProc* deleteProc,
-        GrAHardwareBufferImageGenerator::DeleteImageCtx* deleteCtx,
-        bool isProtectedContent,
-        const GrBackendFormat& backendFormat) {
-    SkASSERT(context->backend() == GrBackendApi::kVulkan);
-    GrVkGpu* gpu = static_cast<GrVkGpu*>(context->priv().getGpu());
-
-    VkPhysicalDevice physicalDevice = gpu->physicalDevice();
-    VkDevice device = gpu->device();
-
-    SkASSERT(gpu);
-
-    if (!gpu->vkCaps().supportsAndroidHWBExternalMemory()) {
-        return GrBackendTexture();
-    }
-
-    SkASSERT(backendFormat.getVkFormat());
-    VkFormat format = *backendFormat.getVkFormat();
-
-    VkResult err;
-
-    VkAndroidHardwareBufferFormatPropertiesANDROID hwbFormatProps;
-    hwbFormatProps.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID;
-    hwbFormatProps.pNext = nullptr;
-
-    VkAndroidHardwareBufferPropertiesANDROID hwbProps;
-    hwbProps.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID;
-    hwbProps.pNext = &hwbFormatProps;
-
-    err = VK_CALL(GetAndroidHardwareBufferProperties(device, hardwareBuffer, &hwbProps));
-    if (VK_SUCCESS != err) {
-        return GrBackendTexture();
-    }
-
-    VkExternalFormatANDROID externalFormat;
-    externalFormat.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID;
-    externalFormat.pNext = nullptr;
-    externalFormat.externalFormat = 0;  // If this is zero it is as if we aren't using this struct.
-
-    const GrVkYcbcrConversionInfo* ycbcrConversion = backendFormat.getVkYcbcrConversionInfo();
-    if (!ycbcrConversion) {
-        return GrBackendTexture();
-    }
-
-    if (hwbFormatProps.format != VK_FORMAT_UNDEFINED) {
-        // TODO: We should not assume the transfer features here and instead should have a way for
-        // Ganesh's tracking of intenral images to report whether or not they support transfers.
-        SkASSERT(SkToBool(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT & hwbFormatProps.formatFeatures) &&
-                 SkToBool(VK_FORMAT_FEATURE_TRANSFER_SRC_BIT & hwbFormatProps.formatFeatures) &&
-                 SkToBool(VK_FORMAT_FEATURE_TRANSFER_DST_BIT & hwbFormatProps.formatFeatures));
-        SkASSERT(!ycbcrConversion->isValid());
-    } else {
-        SkASSERT(ycbcrConversion->isValid());
-        // We have an external only format
-        SkASSERT(SkToBool(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT & hwbFormatProps.formatFeatures));
-        SkASSERT(format == VK_FORMAT_UNDEFINED);
-        SkASSERT(hwbFormatProps.externalFormat == ycbcrConversion->fExternalFormat);
-        externalFormat.externalFormat = hwbFormatProps.externalFormat;
-    }
-    SkASSERT(format == hwbFormatProps.format);
-
-    const VkExternalMemoryImageCreateInfo externalMemoryImageInfo{
-            VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,                 // sType
-            &externalFormat,                                                     // pNext
-            VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,  // handleTypes
-    };
-    VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_SAMPLED_BIT;
-    if (format != VK_FORMAT_UNDEFINED) {
-        usageFlags = usageFlags |
-                VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
-                VK_IMAGE_USAGE_TRANSFER_DST_BIT;
-    }
-
-    // TODO: Check the supported tilings vkGetPhysicalDeviceImageFormatProperties2 to see if we have
-    // to use linear. Add better linear support throughout Ganesh.
-    VkImageTiling tiling = VK_IMAGE_TILING_OPTIMAL;
-
-    const VkImageCreateInfo imageCreateInfo = {
-        VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,         // sType
-        &externalMemoryImageInfo,                    // pNext
-        0,                                           // VkImageCreateFlags
-        VK_IMAGE_TYPE_2D,                            // VkImageType
-        format,                                      // VkFormat
-        { (uint32_t)width, (uint32_t)height, 1 },    // VkExtent3D
-        1,                                           // mipLevels
-        1,                                           // arrayLayers
-        VK_SAMPLE_COUNT_1_BIT,                       // samples
-        tiling,                                      // VkImageTiling
-        usageFlags,                                  // VkImageUsageFlags
-        VK_SHARING_MODE_EXCLUSIVE,                   // VkSharingMode
-        0,                                           // queueFamilyCount
-        0,                                           // pQueueFamilyIndices
-        VK_IMAGE_LAYOUT_UNDEFINED,                   // initialLayout
-    };
-
-    VkImage image;
-    err = VK_CALL(CreateImage(device, &imageCreateInfo, nullptr, &image));
-    if (VK_SUCCESS != err) {
-        return GrBackendTexture();
-    }
-
-    VkPhysicalDeviceMemoryProperties2 phyDevMemProps;
-    phyDevMemProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2;
-    phyDevMemProps.pNext = nullptr;
-
-    uint32_t typeIndex = 0;
-    uint32_t heapIndex = 0;
-    bool foundHeap = false;
-    VK_CALL(GetPhysicalDeviceMemoryProperties2(physicalDevice, &phyDevMemProps));
-    uint32_t memTypeCnt = phyDevMemProps.memoryProperties.memoryTypeCount;
-    for (uint32_t i = 0; i < memTypeCnt && !foundHeap; ++i) {
-        if (hwbProps.memoryTypeBits & (1 << i)) {
-            const VkPhysicalDeviceMemoryProperties& pdmp = phyDevMemProps.memoryProperties;
-            uint32_t supportedFlags = pdmp.memoryTypes[i].propertyFlags &
-                    VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
-            if (supportedFlags == VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
-                typeIndex = i;
-                heapIndex = pdmp.memoryTypes[i].heapIndex;
-                foundHeap = true;
-            }
-        }
-    }
-    if (!foundHeap) {
-        VK_CALL(DestroyImage(device, image, nullptr));
-        return GrBackendTexture();
-    }
-
-    VkImportAndroidHardwareBufferInfoANDROID hwbImportInfo;
-    hwbImportInfo.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID;
-    hwbImportInfo.pNext = nullptr;
-    hwbImportInfo.buffer = hardwareBuffer;
-
-    VkMemoryDedicatedAllocateInfo dedicatedAllocInfo;
-    dedicatedAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
-    dedicatedAllocInfo.pNext = &hwbImportInfo;
-    dedicatedAllocInfo.image = image;
-    dedicatedAllocInfo.buffer = VK_NULL_HANDLE;
-
-    VkMemoryAllocateInfo allocInfo = {
-        VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,      // sType
-        &dedicatedAllocInfo,                         // pNext
-        hwbProps.allocationSize,                     // allocationSize
-        typeIndex,                                   // memoryTypeIndex
-    };
-
-    VkDeviceMemory memory;
-
-    err = VK_CALL(AllocateMemory(device, &allocInfo, nullptr, &memory));
-    if (VK_SUCCESS != err) {
-        VK_CALL(DestroyImage(device, image, nullptr));
-        return GrBackendTexture();
-    }
-
-    VkBindImageMemoryInfo bindImageInfo;
-    bindImageInfo.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO;
-    bindImageInfo.pNext = nullptr;
-    bindImageInfo.image = image;
-    bindImageInfo.memory = memory;
-    bindImageInfo.memoryOffset = 0;
-
-    err = VK_CALL(BindImageMemory2(device, 1, &bindImageInfo));
-    if (VK_SUCCESS != err) {
-        VK_CALL(DestroyImage(device, image, nullptr));
-        VK_CALL(FreeMemory(device, memory, nullptr));
-        return GrBackendTexture();
-    }
-
-    GrVkImageInfo imageInfo;
-
-    imageInfo.fImage = image;
-    imageInfo.fAlloc = GrVkAlloc(memory, 0, hwbProps.allocationSize, 0);
-    imageInfo.fImageTiling = tiling;
-    imageInfo.fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
-    imageInfo.fFormat = format;
-    imageInfo.fLevelCount = 1;
-    // TODO: This should possibly be VK_QUEUE_FAMILY_FOREIGN_EXT but current Adreno devices do not
-    // support that extension. Or if we know the source of the AHardwareBuffer is not from a
-    // "foreign" device we can leave them as external.
-    imageInfo.fCurrentQueueFamily = VK_QUEUE_FAMILY_EXTERNAL;
-    imageInfo.fYcbcrConversionInfo = *ycbcrConversion;
-
-    *deleteProc = GrAHardwareBufferImageGenerator::DeleteVkImage;
-    *deleteCtx = new VulkanCleanupHelper(gpu, image, memory);
-
-    return GrBackendTexture(width, height, imageInfo);
-}
-#endif
-
-class GLCleanupHelper {
-public:
-    GLCleanupHelper(GrGLuint texID, EGLImageKHR image, EGLDisplay display)
-        : fTexID(texID)
-        , fImage(image)
-        , fDisplay(display) { }
-    ~GLCleanupHelper() {
-        glDeleteTextures(1, &fTexID);
-        // eglDestroyImageKHR will remove a ref from the AHardwareBuffer
-        eglDestroyImageKHR(fDisplay, fImage);
-    }
-private:
-    GrGLuint    fTexID;
-    EGLImageKHR fImage;
-    EGLDisplay  fDisplay;
-};
-
-void GrAHardwareBufferImageGenerator::DeleteGLTexture(void* context) {
-    GLCleanupHelper* cleanupHelper = static_cast<GLCleanupHelper*>(context);
-    delete cleanupHelper;
-}
-
-static GrBackendTexture make_gl_backend_texture(
-        GrContext* context, AHardwareBuffer* hardwareBuffer,
-        int width, int height, GrPixelConfig config,
-        GrAHardwareBufferImageGenerator::DeleteImageProc* deleteProc,
-        GrAHardwareBufferImageGenerator::DeleteImageCtx* deleteCtx,
-        bool isProtectedContent,
-        const GrBackendFormat& backendFormat) {
-    while (GL_NO_ERROR != glGetError()) {} //clear GL errors
-
-    EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(hardwareBuffer);
-    EGLint attribs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
-                         isProtectedContent ? EGL_PROTECTED_CONTENT_EXT : EGL_NONE,
-                         isProtectedContent ? EGL_TRUE : EGL_NONE,
-                         EGL_NONE };
-    EGLDisplay display = eglGetCurrentDisplay();
-    // eglCreateImageKHR will add a ref to the AHardwareBuffer
-    EGLImageKHR image = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
-                                          clientBuffer, attribs);
-    if (EGL_NO_IMAGE_KHR == image) {
-        SkDebugf("Could not create EGL image, err = (%#x)", (int) eglGetError() );
-        return GrBackendTexture();
-    }
-
-    GrGLuint texID;
-    glGenTextures(1, &texID);
-    if (!texID) {
-        eglDestroyImageKHR(display, image);
-        return GrBackendTexture();
-    }
-    glBindTexture(GL_TEXTURE_EXTERNAL_OES, texID);
-    GLenum status = GL_NO_ERROR;
-    if ((status = glGetError()) != GL_NO_ERROR) {
-        SkDebugf("glBindTexture failed (%#x)", (int) status);
-        glDeleteTextures(1, &texID);
-        eglDestroyImageKHR(display, image);
-        return GrBackendTexture();
-    }
-    glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image);
-    if ((status = glGetError()) != GL_NO_ERROR) {
-        SkDebugf("glEGLImageTargetTexture2DOES failed (%#x)", (int) status);
-        glDeleteTextures(1, &texID);
-        eglDestroyImageKHR(display, image);
-        return GrBackendTexture();
-    }
-    context->resetContext(kTextureBinding_GrGLBackendState);
-
-    GrGLTextureInfo textureInfo;
-    textureInfo.fID = texID;
-    SkASSERT(backendFormat.isValid());
-    textureInfo.fTarget = *backendFormat.getGLTarget();
-    textureInfo.fFormat = *backendFormat.getGLFormat();
-
-    *deleteProc = GrAHardwareBufferImageGenerator::DeleteGLTexture;
-    *deleteCtx = new GLCleanupHelper(texID, image, display);
-
-    return GrBackendTexture(width, height, GrMipMapped::kNo, textureInfo);
-}
-
-static GrBackendTexture make_backend_texture(
-        GrContext* context, AHardwareBuffer* hardwareBuffer,
-        int width, int height, GrPixelConfig config,
-        GrAHardwareBufferImageGenerator::DeleteImageProc* deleteProc,
-        GrAHardwareBufferImageGenerator::DeleteImageCtx* deleteCtx,
-        bool isProtectedContent,
-        const GrBackendFormat& backendFormat) {
-    if (context->abandoned()) {
-        return GrBackendTexture();
-    }
-    bool createProtectedImage = isProtectedContent && can_import_protected_content(context);
-
-    if (GrBackendApi::kOpenGL == context->backend()) {
-        return make_gl_backend_texture(context, hardwareBuffer, width, height, config, deleteProc,
-                                       deleteCtx, createProtectedImage, backendFormat);
-    } else {
-        SkASSERT(GrBackendApi::kVulkan == context->backend());
-#ifdef SK_VULKAN
-        // Currently we don't support protected images on vulkan
-        SkASSERT(!createProtectedImage);
-        return make_vk_backend_texture(context, hardwareBuffer, width, height, config, deleteProc,
-                                       deleteCtx, createProtectedImage, backendFormat);
-#else
-        return GrBackendTexture();
-#endif
-    }
-}
-
-GrBackendFormat get_backend_format(GrContext* context, AHardwareBuffer* hardwareBuffer,
-                                   GrBackendApi backend, uint32_t bufferFormat) {
-    if (backend == GrBackendApi::kOpenGL) {
-        switch (bufferFormat) {
-            //TODO: find out if we can detect, which graphic buffers support GR_GL_TEXTURE_2D
-            case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
-            case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
-                return GrBackendFormat::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_EXTERNAL);
-            case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
-                return GrBackendFormat::MakeGL(GR_GL_RGBA16F, GR_GL_TEXTURE_EXTERNAL);
-            case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
-                return GrBackendFormat::MakeGL(GR_GL_RGB565, GR_GL_TEXTURE_EXTERNAL);
-            case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
-                return GrBackendFormat::MakeGL(GR_GL_RGB10_A2, GR_GL_TEXTURE_EXTERNAL);
-            case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
-                return GrBackendFormat::MakeGL(GR_GL_RGB8, GR_GL_TEXTURE_EXTERNAL);
-            default:
-                return GrBackendFormat::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_EXTERNAL);
-        }
-    } else if (backend == GrBackendApi::kVulkan) {
-#ifdef SK_VULKAN
-        switch (bufferFormat) {
-            case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
-                return GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8A8_UNORM);
-            case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
-                return GrBackendFormat::MakeVk(VK_FORMAT_R16G16B16A16_SFLOAT);
-            case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
-                return GrBackendFormat::MakeVk(VK_FORMAT_R5G6B5_UNORM_PACK16);
-            case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
-                return GrBackendFormat::MakeVk(VK_FORMAT_A2B10G10R10_UNORM_PACK32);
-            case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
-                return GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8A8_UNORM);
-            case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
-                return GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8_UNORM);
-            default: {
-                GrVkGpu* gpu = static_cast<GrVkGpu*>(context->priv().getGpu());
-                SkASSERT(gpu);
-                VkDevice device = gpu->device();
-
-                if (!gpu->vkCaps().supportsAndroidHWBExternalMemory()) {
-                    return GrBackendFormat();
-                }
-                VkAndroidHardwareBufferFormatPropertiesANDROID hwbFormatProps;
-                hwbFormatProps.sType =
-                        VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID;
-                hwbFormatProps.pNext = nullptr;
-
-                VkAndroidHardwareBufferPropertiesANDROID hwbProps;
-                hwbProps.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID;
-                hwbProps.pNext = &hwbFormatProps;
-
-                VkResult err = VK_CALL(GetAndroidHardwareBufferProperties(device, hardwareBuffer,
-                                                                          &hwbProps));
-                if (VK_SUCCESS != err) {
-                    return GrBackendFormat();
-                }
-
-                if (hwbFormatProps.format != VK_FORMAT_UNDEFINED) {
-                    return GrBackendFormat();
-                }
-
-                GrVkYcbcrConversionInfo ycbcrConversion;
-                ycbcrConversion.fYcbcrModel = hwbFormatProps.suggestedYcbcrModel;
-                ycbcrConversion.fYcbcrRange = hwbFormatProps.suggestedYcbcrRange;
-                ycbcrConversion.fXChromaOffset = hwbFormatProps.suggestedXChromaOffset;
-                ycbcrConversion.fYChromaOffset = hwbFormatProps.suggestedYChromaOffset;
-                ycbcrConversion.fForceExplicitReconstruction = VK_FALSE;
-                ycbcrConversion.fExternalFormat = hwbFormatProps.externalFormat;
-                ycbcrConversion.fExternalFormatFeatures = hwbFormatProps.formatFeatures;
-                if (VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT &
-                    hwbFormatProps.formatFeatures) {
-                    ycbcrConversion.fChromaFilter = VK_FILTER_LINEAR;
-                } else {
-                    ycbcrConversion.fChromaFilter = VK_FILTER_NEAREST;
-                }
-
-                return GrBackendFormat::MakeVk(ycbcrConversion);
-            }
-        }
-#else
-        return GrBackendFormat();
-#endif
-    }
-    return GrBackendFormat();
-}
-
-sk_sp<GrTextureProxy> GrAHardwareBufferImageGenerator::makeProxy(GrContext* context) {
-    if (context->abandoned()) {
+sk_sp<GrTextureProxy> GrAHardwareBufferImageGenerator::makeProxy(GrRecordingContext* context) {
+    if (context->priv().abandoned()) {
         return nullptr;
     }
 
-    GrBackendFormat backendFormat = get_backend_format(context, fHardwareBuffer,
-                                                       context->backend(),
-                                                       fBufferFormat);
+    auto direct = context->priv().asDirectContext();
+    if (!direct) {
+        return nullptr;
+    }
+
+    GrBackendFormat backendFormat = GrAHardwareBufferUtils::GetBackendFormat(direct,
+                                                                             fHardwareBuffer,
+                                                                             fBufferFormat,
+                                                                             false);
+
     GrPixelConfig pixelConfig = context->priv().caps()->getConfigFromBackendFormat(
             backendFormat, this->getInfo().colorType());
 
@@ -577,25 +128,43 @@
 
     const bool isProtectedContent = fIsProtectedContent;
 
+    class AutoAHBRelease {
+    public:
+        AutoAHBRelease(AHardwareBuffer* ahb) : fAhb(ahb) {}
+        // std::function() must be CopyConstructible, but ours should never actually be copied.
+        AutoAHBRelease(const AutoAHBRelease&) { SkASSERT(0); }
+        AutoAHBRelease(AutoAHBRelease&& that) : fAhb(that.fAhb) { that.fAhb = nullptr; }
+        ~AutoAHBRelease() { fAhb ? AHardwareBuffer_release(fAhb) : void(); }
+
+        AutoAHBRelease& operator=(AutoAHBRelease&& that) {
+            fAhb = skstd::exchange(that.fAhb, nullptr);
+            return *this;
+        }
+        AutoAHBRelease& operator=(const AutoAHBRelease&) = delete;
+
+        AHardwareBuffer* get() const { return fAhb; }
+
+    private:
+        AHardwareBuffer* fAhb;
+    };
+
     sk_sp<GrTextureProxy> texProxy = proxyProvider->createLazyProxy(
-            [context, hardwareBuffer, width, height, pixelConfig, isProtectedContent,
-             backendFormat](GrResourceProvider* resourceProvider) {
-                if (!resourceProvider) {
-                    AHardwareBuffer_release(hardwareBuffer);
-                    return sk_sp<GrTexture>();
-                }
+            [direct, buffer = AutoAHBRelease(hardwareBuffer), width, height, pixelConfig,
+             isProtectedContent, backendFormat](GrResourceProvider* resourceProvider)
+                    -> GrSurfaceProxy::LazyInstantiationResult {
+                GrAHardwareBufferUtils::DeleteImageProc deleteImageProc = nullptr;
+                GrAHardwareBufferUtils::DeleteImageCtx deleteImageCtx = nullptr;
 
-                DeleteImageProc deleteImageProc = nullptr;
-                DeleteImageCtx deleteImageCtx = nullptr;
-
-                GrBackendTexture backendTex = make_backend_texture(context, hardwareBuffer,
-                                                                   width, height, pixelConfig,
+                GrBackendTexture backendTex =
+                        GrAHardwareBufferUtils::MakeBackendTexture(direct, buffer.get(),
+                                                                   width, height,
                                                                    &deleteImageProc,
                                                                    &deleteImageCtx,
                                                                    isProtectedContent,
-                                                                   backendFormat);
+                                                                   backendFormat,
+                                                                   false);
                 if (!backendTex.isValid()) {
-                    return sk_sp<GrTexture>();
+                    return {};
                 }
                 SkASSERT(deleteImageProc && deleteImageCtx);
 
@@ -607,28 +176,24 @@
                         backendTex, kBorrow_GrWrapOwnership, GrWrapCacheable::kYes, kRead_GrIOType);
                 if (!tex) {
                     deleteImageProc(deleteImageCtx);
-                    return sk_sp<GrTexture>();
+                    return {};
                 }
 
                 if (deleteImageProc) {
-                    sk_sp<GrReleaseProcHelper> releaseProcHelper(
-                            new GrReleaseProcHelper(deleteImageProc, deleteImageCtx));
-                    tex->setRelease(releaseProcHelper);
+                    tex->setRelease(deleteImageProc, deleteImageCtx);
                 }
 
-                return tex;
+                return std::move(tex);
             },
             backendFormat, desc, fSurfaceOrigin, GrMipMapped::kNo,
             GrInternalSurfaceFlags::kReadOnly, SkBackingFit::kExact, SkBudgeted::kNo);
 
-    if (!texProxy) {
-        AHardwareBuffer_release(hardwareBuffer);
-    }
     return texProxy;
 }
 
 sk_sp<GrTextureProxy> GrAHardwareBufferImageGenerator::onGenerateTexture(
-        GrContext* context, const SkImageInfo& info, const SkIPoint& origin, bool willNeedMipMaps) {
+        GrRecordingContext* context, const SkImageInfo& info,
+        const SkIPoint& origin, bool willNeedMipMaps) {
     sk_sp<GrTextureProxy> texProxy = this->makeProxy(context);
     if (!texProxy) {
         return nullptr;
diff --git a/src/gpu/GrAHardwareBufferImageGenerator.h b/src/gpu/GrAHardwareBufferImageGenerator.h
index 2526e0a..95b2fc2 100644
--- a/src/gpu/GrAHardwareBufferImageGenerator.h
+++ b/src/gpu/GrAHardwareBufferImageGenerator.h
@@ -35,28 +35,21 @@
 
     ~GrAHardwareBufferImageGenerator() override;
 
-    typedef void* DeleteImageCtx;
-    typedef void (*DeleteImageProc)(DeleteImageCtx);
-
     static void DeleteGLTexture(void* ctx);
 
-#ifdef SK_VULKAN
-    static void DeleteVkImage(void* ctx);
-#endif
-
 protected:
 
     bool onIsValid(GrContext*) const override;
 
     TexGenType onCanGenerateTexture() const override { return TexGenType::kCheap; }
-    sk_sp<GrTextureProxy> onGenerateTexture(GrContext*, const SkImageInfo&, const SkIPoint&,
-                                            bool willNeedMipMaps) override;
+    sk_sp<GrTextureProxy> onGenerateTexture(GrRecordingContext*, const SkImageInfo&,
+                                            const SkIPoint&, bool willNeedMipMaps) override;
 
 private:
     GrAHardwareBufferImageGenerator(const SkImageInfo&, AHardwareBuffer*, SkAlphaType,
                                     bool isProtectedContent, uint32_t bufferFormat,
                                     GrSurfaceOrigin surfaceOrigin);
-    sk_sp<GrTextureProxy> makeProxy(GrContext* context);
+    sk_sp<GrTextureProxy> makeProxy(GrRecordingContext* context);
 
     void releaseTextureRef();
 
diff --git a/src/gpu/GrAHardwareBufferUtils.cpp b/src/gpu/GrAHardwareBufferUtils.cpp
new file mode 100644
index 0000000..43272ce
--- /dev/null
+++ b/src/gpu/GrAHardwareBufferUtils.cpp
@@ -0,0 +1,522 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTypes.h"
+
+#if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26
+#define GL_GLEXT_PROTOTYPES
+#define EGL_EGLEXT_PROTOTYPES
+
+#include "GrAHardwareBufferUtils.h"
+
+#include <android/hardware_buffer.h>
+
+#include "GrContext.h"
+#include "GrContextPriv.h"
+#include "gl/GrGLDefines.h"
+#include "gl/GrGLTypes.h"
+
+#ifdef SK_VULKAN
+#include "vk/GrVkCaps.h"
+#include "vk/GrVkGpu.h"
+#endif
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES/gl.h>
+#include <GLES/glext.h>
+
+#define PROT_CONTENT_EXT_STR "EGL_EXT_protected_content"
+#define EGL_PROTECTED_CONTENT_EXT 0x32C0
+
+#define VK_CALL(X) gpu->vkInterface()->fFunctions.f##X;
+
+namespace GrAHardwareBufferUtils {
+
+SkColorType GetSkColorTypeFromBufferFormat(uint32_t bufferFormat) {
+    switch (bufferFormat) {
+        case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
+            return kRGBA_8888_SkColorType;
+        case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
+            return kRGB_888x_SkColorType;
+        case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
+            return kRGBA_F16_SkColorType;
+        case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
+            return kRGB_565_SkColorType;
+        case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
+            return kRGB_888x_SkColorType;
+        case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
+            return kRGBA_1010102_SkColorType;
+        default:
+            // Given that we only use this texture as a source, colorType will not impact how Skia
+            // uses the texture.  The only potential affect this is anticipated to have is that for
+            // some format types if we are not bound as an OES texture we may get invalid results
+            // for SKP capture if we read back the texture.
+            return kRGBA_8888_SkColorType;
+    }
+}
+
+GrBackendFormat GetBackendFormat(GrContext* context, AHardwareBuffer* hardwareBuffer,
+                                 uint32_t bufferFormat, bool requireKnownFormat) {
+    GrBackendApi backend = context->backend();
+
+    if (backend == GrBackendApi::kOpenGL) {
+        switch (bufferFormat) {
+            //TODO: find out if we can detect, which graphic buffers support GR_GL_TEXTURE_2D
+            case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
+            case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
+                return GrBackendFormat::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_EXTERNAL);
+            case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
+                return GrBackendFormat::MakeGL(GR_GL_RGBA16F, GR_GL_TEXTURE_EXTERNAL);
+            case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
+                return GrBackendFormat::MakeGL(GR_GL_RGB565, GR_GL_TEXTURE_EXTERNAL);
+            case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
+                return GrBackendFormat::MakeGL(GR_GL_RGB10_A2, GR_GL_TEXTURE_EXTERNAL);
+            case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
+                return GrBackendFormat::MakeGL(GR_GL_RGB8, GR_GL_TEXTURE_EXTERNAL);
+            default:
+                if (requireKnownFormat) {
+                    return GrBackendFormat();
+                } else {
+                    return GrBackendFormat::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_EXTERNAL);
+                }
+        }
+    } else if (backend == GrBackendApi::kVulkan) {
+#ifdef SK_VULKAN
+        switch (bufferFormat) {
+            case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
+                return GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8A8_UNORM);
+            case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
+                return GrBackendFormat::MakeVk(VK_FORMAT_R16G16B16A16_SFLOAT);
+            case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
+                return GrBackendFormat::MakeVk(VK_FORMAT_R5G6B5_UNORM_PACK16);
+            case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
+                return GrBackendFormat::MakeVk(VK_FORMAT_A2B10G10R10_UNORM_PACK32);
+            case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
+                return GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8A8_UNORM);
+            case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
+                return GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8_UNORM);
+            default: {
+                if (requireKnownFormat) {
+                    return GrBackendFormat();
+                } else {
+                    GrVkGpu* gpu = static_cast<GrVkGpu*>(context->priv().getGpu());
+                    SkASSERT(gpu);
+                    VkDevice device = gpu->device();
+
+                    if (!gpu->vkCaps().supportsAndroidHWBExternalMemory()) {
+                        return GrBackendFormat();
+                    }
+                    VkAndroidHardwareBufferFormatPropertiesANDROID hwbFormatProps;
+                    hwbFormatProps.sType =
+                            VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID;
+                    hwbFormatProps.pNext = nullptr;
+
+                    VkAndroidHardwareBufferPropertiesANDROID hwbProps;
+                    hwbProps.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID;
+                    hwbProps.pNext = &hwbFormatProps;
+
+                    VkResult err = VK_CALL(GetAndroidHardwareBufferProperties(device,
+                                                                              hardwareBuffer,
+                                                                              &hwbProps));
+                    if (VK_SUCCESS != err) {
+                        return GrBackendFormat();
+                    }
+
+                    if (hwbFormatProps.format != VK_FORMAT_UNDEFINED) {
+                        return GrBackendFormat();
+                    }
+
+                    GrVkYcbcrConversionInfo ycbcrConversion;
+                    ycbcrConversion.fYcbcrModel = hwbFormatProps.suggestedYcbcrModel;
+                    ycbcrConversion.fYcbcrRange = hwbFormatProps.suggestedYcbcrRange;
+                    ycbcrConversion.fXChromaOffset = hwbFormatProps.suggestedXChromaOffset;
+                    ycbcrConversion.fYChromaOffset = hwbFormatProps.suggestedYChromaOffset;
+                    ycbcrConversion.fForceExplicitReconstruction = VK_FALSE;
+                    ycbcrConversion.fExternalFormat = hwbFormatProps.externalFormat;
+                    ycbcrConversion.fExternalFormatFeatures = hwbFormatProps.formatFeatures;
+                    if (VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT &
+                        hwbFormatProps.formatFeatures) {
+                        ycbcrConversion.fChromaFilter = VK_FILTER_LINEAR;
+                    } else {
+                        ycbcrConversion.fChromaFilter = VK_FILTER_NEAREST;
+                    }
+
+                    return GrBackendFormat::MakeVk(ycbcrConversion);
+                }
+            }
+        }
+#else
+        return GrBackendFormat();
+#endif
+    }
+    return GrBackendFormat();
+}
+
+class GLCleanupHelper {
+public:
+    GLCleanupHelper(GrGLuint texID, EGLImageKHR image, EGLDisplay display)
+        : fTexID(texID)
+        , fImage(image)
+        , fDisplay(display) { }
+    ~GLCleanupHelper() {
+        glDeleteTextures(1, &fTexID);
+        // eglDestroyImageKHR will remove a ref from the AHardwareBuffer
+        eglDestroyImageKHR(fDisplay, fImage);
+    }
+private:
+    GrGLuint    fTexID;
+    EGLImageKHR fImage;
+    EGLDisplay  fDisplay;
+};
+
+void delete_gl_texture(void* context) {
+    GLCleanupHelper* cleanupHelper = static_cast<GLCleanupHelper*>(context);
+    delete cleanupHelper;
+}
+
+static GrBackendTexture make_gl_backend_texture(
+        GrContext* context, AHardwareBuffer* hardwareBuffer,
+        int width, int height,
+        DeleteImageProc* deleteProc,
+        DeleteImageCtx* deleteCtx,
+        bool isProtectedContent,
+        const GrBackendFormat& backendFormat,
+        bool isRenderable) {
+    while (GL_NO_ERROR != glGetError()) {} //clear GL errors
+
+    EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(hardwareBuffer);
+    EGLint attribs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
+                         isProtectedContent ? EGL_PROTECTED_CONTENT_EXT : EGL_NONE,
+                         isProtectedContent ? EGL_TRUE : EGL_NONE,
+                         EGL_NONE };
+    EGLDisplay display = eglGetCurrentDisplay();
+    // eglCreateImageKHR will add a ref to the AHardwareBuffer
+    EGLImageKHR image = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
+                                          clientBuffer, attribs);
+    if (EGL_NO_IMAGE_KHR == image) {
+        SkDebugf("Could not create EGL image, err = (%#x)", (int) eglGetError() );
+        return GrBackendTexture();
+    }
+
+    GrGLuint texID;
+    glGenTextures(1, &texID);
+    if (!texID) {
+        eglDestroyImageKHR(display, image);
+        return GrBackendTexture();
+    }
+
+    GrGLuint target = isRenderable ? GR_GL_TEXTURE_2D : GR_GL_TEXTURE_EXTERNAL;
+
+    glBindTexture(target, texID);
+    GLenum status = GL_NO_ERROR;
+    if ((status = glGetError()) != GL_NO_ERROR) {
+        SkDebugf("glBindTexture failed (%#x)", (int) status);
+        glDeleteTextures(1, &texID);
+        eglDestroyImageKHR(display, image);
+        return GrBackendTexture();
+    }
+    glEGLImageTargetTexture2DOES(target, image);
+    if ((status = glGetError()) != GL_NO_ERROR) {
+        SkDebugf("glEGLImageTargetTexture2DOES failed (%#x)", (int) status);
+        glDeleteTextures(1, &texID);
+        eglDestroyImageKHR(display, image);
+        return GrBackendTexture();
+    }
+    context->resetContext(kTextureBinding_GrGLBackendState);
+
+    GrGLTextureInfo textureInfo;
+    textureInfo.fID = texID;
+    SkASSERT(backendFormat.isValid());
+    textureInfo.fTarget = target;
+    textureInfo.fFormat = *backendFormat.getGLFormat();
+
+    *deleteProc = delete_gl_texture;
+    *deleteCtx = new GLCleanupHelper(texID, image, display);
+
+    return GrBackendTexture(width, height, GrMipMapped::kNo, textureInfo);
+}
+
+#ifdef SK_VULKAN
+class VulkanCleanupHelper {
+public:
+    VulkanCleanupHelper(GrVkGpu* gpu, VkImage image, VkDeviceMemory memory)
+        : fDevice(gpu->device())
+        , fImage(image)
+        , fMemory(memory)
+        , fDestroyImage(gpu->vkInterface()->fFunctions.fDestroyImage)
+        , fFreeMemory(gpu->vkInterface()->fFunctions.fFreeMemory) {}
+    ~VulkanCleanupHelper() {
+        fDestroyImage(fDevice, fImage, nullptr);
+        fFreeMemory(fDevice, fMemory, nullptr);
+    }
+private:
+    VkDevice           fDevice;
+    VkImage            fImage;
+    VkDeviceMemory     fMemory;
+    PFN_vkDestroyImage fDestroyImage;
+    PFN_vkFreeMemory   fFreeMemory;
+};
+
+void delete_vk_image(void* context) {
+    VulkanCleanupHelper* cleanupHelper = static_cast<VulkanCleanupHelper*>(context);
+    delete cleanupHelper;
+}
+
+static GrBackendTexture make_vk_backend_texture(
+        GrContext* context, AHardwareBuffer* hardwareBuffer,
+        int width, int height,
+        DeleteImageProc* deleteProc,
+        DeleteImageCtx* deleteCtx,
+        bool isProtectedContent,
+        const GrBackendFormat& backendFormat,
+        bool isRenderable) {
+    SkASSERT(context->backend() == GrBackendApi::kVulkan);
+    GrVkGpu* gpu = static_cast<GrVkGpu*>(context->priv().getGpu());
+
+    VkPhysicalDevice physicalDevice = gpu->physicalDevice();
+    VkDevice device = gpu->device();
+
+    SkASSERT(gpu);
+
+    if (!gpu->vkCaps().supportsAndroidHWBExternalMemory()) {
+        return GrBackendTexture();
+    }
+
+    SkASSERT(backendFormat.getVkFormat());
+    VkFormat format = *backendFormat.getVkFormat();
+
+    VkResult err;
+
+    VkAndroidHardwareBufferFormatPropertiesANDROID hwbFormatProps;
+    hwbFormatProps.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID;
+    hwbFormatProps.pNext = nullptr;
+
+    VkAndroidHardwareBufferPropertiesANDROID hwbProps;
+    hwbProps.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID;
+    hwbProps.pNext = &hwbFormatProps;
+
+    err = VK_CALL(GetAndroidHardwareBufferProperties(device, hardwareBuffer, &hwbProps));
+    if (VK_SUCCESS != err) {
+        return GrBackendTexture();
+    }
+
+    VkExternalFormatANDROID externalFormat;
+    externalFormat.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID;
+    externalFormat.pNext = nullptr;
+    externalFormat.externalFormat = 0;  // If this is zero it is as if we aren't using this struct.
+
+    const GrVkYcbcrConversionInfo* ycbcrConversion = backendFormat.getVkYcbcrConversionInfo();
+    if (!ycbcrConversion) {
+        return GrBackendTexture();
+    }
+
+    if (hwbFormatProps.format != VK_FORMAT_UNDEFINED) {
+        // TODO: We should not assume the transfer features here and instead should have a way for
+        // Ganesh's tracking of intenral images to report whether or not they support transfers.
+        SkASSERT(SkToBool(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT & hwbFormatProps.formatFeatures) &&
+                 SkToBool(VK_FORMAT_FEATURE_TRANSFER_SRC_BIT & hwbFormatProps.formatFeatures) &&
+                 SkToBool(VK_FORMAT_FEATURE_TRANSFER_DST_BIT & hwbFormatProps.formatFeatures));
+        SkASSERT(!ycbcrConversion->isValid());
+    } else {
+        SkASSERT(ycbcrConversion->isValid());
+        // We have an external only format
+        SkASSERT(SkToBool(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT & hwbFormatProps.formatFeatures));
+        SkASSERT(format == VK_FORMAT_UNDEFINED);
+        SkASSERT(hwbFormatProps.externalFormat == ycbcrConversion->fExternalFormat);
+        externalFormat.externalFormat = hwbFormatProps.externalFormat;
+    }
+    SkASSERT(format == hwbFormatProps.format);
+
+    const VkExternalMemoryImageCreateInfo externalMemoryImageInfo{
+            VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,                 // sType
+            &externalFormat,                                                     // pNext
+            VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,  // handleTypes
+    };
+    VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_SAMPLED_BIT;
+    if (format != VK_FORMAT_UNDEFINED) {
+        usageFlags = usageFlags |
+                VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
+                VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+        if (isRenderable) {
+            usageFlags = usageFlags | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+        }
+    }
+
+    // TODO: Check the supported tilings vkGetPhysicalDeviceImageFormatProperties2 to see if we have
+    // to use linear. Add better linear support throughout Ganesh.
+    VkImageTiling tiling = VK_IMAGE_TILING_OPTIMAL;
+
+    const VkImageCreateInfo imageCreateInfo = {
+        VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,         // sType
+        &externalMemoryImageInfo,                    // pNext
+        0,                                           // VkImageCreateFlags
+        VK_IMAGE_TYPE_2D,                            // VkImageType
+        format,                                      // VkFormat
+        { (uint32_t)width, (uint32_t)height, 1 },    // VkExtent3D
+        1,                                           // mipLevels
+        1,                                           // arrayLayers
+        VK_SAMPLE_COUNT_1_BIT,                       // samples
+        tiling,                                      // VkImageTiling
+        usageFlags,                                  // VkImageUsageFlags
+        VK_SHARING_MODE_EXCLUSIVE,                   // VkSharingMode
+        0,                                           // queueFamilyCount
+        0,                                           // pQueueFamilyIndices
+        VK_IMAGE_LAYOUT_UNDEFINED,                   // initialLayout
+    };
+
+    VkImage image;
+    err = VK_CALL(CreateImage(device, &imageCreateInfo, nullptr, &image));
+    if (VK_SUCCESS != err) {
+        return GrBackendTexture();
+    }
+
+    VkPhysicalDeviceMemoryProperties2 phyDevMemProps;
+    phyDevMemProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2;
+    phyDevMemProps.pNext = nullptr;
+
+    uint32_t typeIndex = 0;
+    uint32_t heapIndex = 0;
+    bool foundHeap = false;
+    VK_CALL(GetPhysicalDeviceMemoryProperties2(physicalDevice, &phyDevMemProps));
+    uint32_t memTypeCnt = phyDevMemProps.memoryProperties.memoryTypeCount;
+    for (uint32_t i = 0; i < memTypeCnt && !foundHeap; ++i) {
+        if (hwbProps.memoryTypeBits & (1 << i)) {
+            const VkPhysicalDeviceMemoryProperties& pdmp = phyDevMemProps.memoryProperties;
+            uint32_t supportedFlags = pdmp.memoryTypes[i].propertyFlags &
+                    VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+            if (supportedFlags == VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
+                typeIndex = i;
+                heapIndex = pdmp.memoryTypes[i].heapIndex;
+                foundHeap = true;
+            }
+        }
+    }
+    if (!foundHeap) {
+        VK_CALL(DestroyImage(device, image, nullptr));
+        return GrBackendTexture();
+    }
+
+    VkImportAndroidHardwareBufferInfoANDROID hwbImportInfo;
+    hwbImportInfo.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID;
+    hwbImportInfo.pNext = nullptr;
+    hwbImportInfo.buffer = hardwareBuffer;
+
+    VkMemoryDedicatedAllocateInfo dedicatedAllocInfo;
+    dedicatedAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
+    dedicatedAllocInfo.pNext = &hwbImportInfo;
+    dedicatedAllocInfo.image = image;
+    dedicatedAllocInfo.buffer = VK_NULL_HANDLE;
+
+    VkMemoryAllocateInfo allocInfo = {
+        VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,      // sType
+        &dedicatedAllocInfo,                         // pNext
+        hwbProps.allocationSize,                     // allocationSize
+        typeIndex,                                   // memoryTypeIndex
+    };
+
+    VkDeviceMemory memory;
+
+    err = VK_CALL(AllocateMemory(device, &allocInfo, nullptr, &memory));
+    if (VK_SUCCESS != err) {
+        VK_CALL(DestroyImage(device, image, nullptr));
+        return GrBackendTexture();
+    }
+
+    VkBindImageMemoryInfo bindImageInfo;
+    bindImageInfo.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO;
+    bindImageInfo.pNext = nullptr;
+    bindImageInfo.image = image;
+    bindImageInfo.memory = memory;
+    bindImageInfo.memoryOffset = 0;
+
+    err = VK_CALL(BindImageMemory2(device, 1, &bindImageInfo));
+    if (VK_SUCCESS != err) {
+        VK_CALL(DestroyImage(device, image, nullptr));
+        VK_CALL(FreeMemory(device, memory, nullptr));
+        return GrBackendTexture();
+    }
+
+    GrVkImageInfo imageInfo;
+
+    imageInfo.fImage = image;
+    imageInfo.fAlloc = GrVkAlloc(memory, 0, hwbProps.allocationSize, 0);
+    imageInfo.fImageTiling = tiling;
+    imageInfo.fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    imageInfo.fFormat = format;
+    imageInfo.fLevelCount = 1;
+    // TODO: This should possibly be VK_QUEUE_FAMILY_FOREIGN_EXT but current Adreno devices do not
+    // support that extension. Or if we know the source of the AHardwareBuffer is not from a
+    // "foreign" device we can leave them as external.
+    imageInfo.fCurrentQueueFamily = VK_QUEUE_FAMILY_EXTERNAL;
+    imageInfo.fYcbcrConversionInfo = *ycbcrConversion;
+
+    *deleteProc = delete_vk_image;
+    *deleteCtx = new VulkanCleanupHelper(gpu, image, memory);
+
+    return GrBackendTexture(width, height, imageInfo);
+}
+#endif
+
+static bool can_import_protected_content_eglimpl() {
+    EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+    const char* exts = eglQueryString(dpy, EGL_EXTENSIONS);
+    size_t cropExtLen = strlen(PROT_CONTENT_EXT_STR);
+    size_t extsLen = strlen(exts);
+    bool equal = !strcmp(PROT_CONTENT_EXT_STR, exts);
+    bool atStart = !strncmp(PROT_CONTENT_EXT_STR " ", exts, cropExtLen+1);
+    bool atEnd = (cropExtLen+1) < extsLen
+                  && !strcmp(" " PROT_CONTENT_EXT_STR,
+                  exts + extsLen - (cropExtLen+1));
+    bool inMiddle = strstr(exts, " " PROT_CONTENT_EXT_STR " ");
+    return equal || atStart || atEnd || inMiddle;
+}
+
+static bool can_import_protected_content(GrContext* context) {
+    if (GrBackendApi::kOpenGL == context->backend()) {
+        // Only compute whether the extension is present once the first time this
+        // function is called.
+        static bool hasIt = can_import_protected_content_eglimpl();
+        return hasIt;
+    }
+    return false;
+}
+
+GrBackendTexture MakeBackendTexture(GrContext* context, AHardwareBuffer* hardwareBuffer,
+                                    int width, int height,
+                                    DeleteImageProc* deleteProc,
+                                    DeleteImageCtx* deleteCtx,
+                                    bool isProtectedContent,
+                                    const GrBackendFormat& backendFormat,
+                                    bool isRenderable) {
+    if (context->abandoned()) {
+        return GrBackendTexture();
+    }
+    bool createProtectedImage = isProtectedContent && can_import_protected_content(context);
+
+    if (GrBackendApi::kOpenGL == context->backend()) {
+        return make_gl_backend_texture(context, hardwareBuffer, width, height, deleteProc,
+                                       deleteCtx, createProtectedImage, backendFormat,
+                                       isRenderable);
+    } else {
+        SkASSERT(GrBackendApi::kVulkan == context->backend());
+#ifdef SK_VULKAN
+        // Currently we don't support protected images on vulkan
+        SkASSERT(!createProtectedImage);
+        return make_vk_backend_texture(context, hardwareBuffer, width, height, deleteProc,
+                                       deleteCtx, createProtectedImage, backendFormat,
+                                       isRenderable);
+#else
+        return GrBackendTexture();
+#endif
+    }
+}
+
+} // GrAHardwareBufferUtils
+
+#endif
+
diff --git a/src/gpu/GrAHardwareBufferUtils.h b/src/gpu/GrAHardwareBufferUtils.h
new file mode 100644
index 0000000..31cb140
--- /dev/null
+++ b/src/gpu/GrAHardwareBufferUtils.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef GrAHardwareBufferUtils_DEFINED
+#define GrAHardwareBufferUtils_DEFINED
+
+#include "SkTypes.h"
+
+#if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26
+
+#include "GrBackendSurface.h"
+#include "GrTypes.h"
+
+class GrContext;
+
+extern "C" {
+    typedef struct AHardwareBuffer AHardwareBuffer;
+}
+
+namespace GrAHardwareBufferUtils {
+
+SkColorType GetSkColorTypeFromBufferFormat(uint32_t bufferFormat);
+
+GrBackendFormat GetBackendFormat(GrContext* context, AHardwareBuffer* hardwareBuffer,
+                                 uint32_t bufferFormat, bool requireKnownFormat);
+
+typedef void* DeleteImageCtx;
+typedef void (*DeleteImageProc)(DeleteImageCtx);
+
+GrBackendTexture MakeBackendTexture(GrContext* context, AHardwareBuffer* hardwareBuffer,
+                                    int width, int height,
+                                    DeleteImageProc* deleteProc,
+                                    DeleteImageCtx* deleteCtx,
+                                    bool isProtectedContent,
+                                    const GrBackendFormat& backendFormat,
+                                    bool isRenderable);
+
+} // GrAHardwareBufferUtils
+
+
+#endif
+#endif
diff --git a/src/gpu/GrBackendSurface.cpp b/src/gpu/GrBackendSurface.cpp
index 4e6eb27..187f7d8 100644
--- a/src/gpu/GrBackendSurface.cpp
+++ b/src/gpu/GrBackendSurface.cpp
@@ -81,6 +81,9 @@
         , fTextureType(GrTextureType::k2D) {
     fVk.fFormat = vkFormat;
     fVk.fYcbcrConversionInfo = ycbcrInfo;
+    if (fVk.fYcbcrConversionInfo.isValid()) {
+        fTextureType = GrTextureType::kExternal;
+    }
 }
 
 const VkFormat* GrBackendFormat::getVkFormat() const {
@@ -128,9 +131,16 @@
 }
 
 GrBackendFormat GrBackendFormat::makeTexture2D() const {
-    // TODO: once we support ycbcr conversions in Vulkan we need to check if we are using an
-    // external format since they will not be able to be made into a Texture2D.
     GrBackendFormat copy = *this;
+    if (const GrVkYcbcrConversionInfo* ycbcrInfo = this->getVkYcbcrConversionInfo()) {
+        if (ycbcrInfo->isValid()) {
+            // If we have a ycbcr we remove it from the backend format and set the VkFormat to
+            // R8G8B8A8_UNORM
+            SkASSERT(copy.fBackend == GrBackendApi::kVulkan);
+            copy.fVk.fYcbcrConversionInfo = GrVkYcbcrConversionInfo();
+            copy.fVk.fFormat = VK_FORMAT_R8G8B8A8_UNORM;
+        }
+    }
     copy.fTextureType = GrTextureType::k2D;
     return copy;
 }
@@ -330,6 +340,14 @@
     if (this->isValid() && GrBackendApi::kOpenGL == fBackend) {
         *outInfo = fGLInfo;
         return true;
+    } else if (this->isValid() && GrBackendApi::kMock == fBackend) {
+        // Hack! This allows some blink unit tests to work when using the Mock GrContext.
+        // Specifically, tests that rely on CanvasResourceProviderTextureGpuMemoryBuffer.
+        // If that code ever goes away (or ideally becomes backend-agnostic), this can go away.
+        *outInfo = GrGLTextureInfo{ GR_GL_TEXTURE_2D,
+                                    static_cast<GrGLuint>(fMockInfo.fID),
+                                    GR_GL_RGBA8 };
+        return true;
     }
     return false;
 }
diff --git a/src/gpu/GrBackendTextureImageGenerator.cpp b/src/gpu/GrBackendTextureImageGenerator.cpp
index b968e141..8ed29ec 100644
--- a/src/gpu/GrBackendTextureImageGenerator.cpp
+++ b/src/gpu/GrBackendTextureImageGenerator.cpp
@@ -10,6 +10,8 @@
 #include "GrContextPriv.h"
 #include "GrGpu.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrResourceCache.h"
 #include "GrResourceProvider.h"
@@ -22,8 +24,14 @@
 #include "SkMessageBus.h"
 #include "gl/GrGLTexture.h"
 
+GrBackendTextureImageGenerator::RefHelper::RefHelper(GrTexture* texture, uint32_t owningContextID)
+        : fOriginalTexture(texture)
+        , fOwningContextID(owningContextID)
+        , fBorrowingContextReleaseProc(nullptr)
+        , fBorrowingContextID(SK_InvalidGenID) {}
+
 GrBackendTextureImageGenerator::RefHelper::~RefHelper() {
-    SkASSERT(nullptr == fBorrowedTexture);
+    SkASSERT(fBorrowingContextID == SK_InvalidUniqueID);
 
     // Generator has been freed, and no one is borrowing the texture. Notify the original cache
     // that it can free the last ref, so it happens on the correct thread.
@@ -40,7 +48,7 @@
     // Attach our texture to this context's resource cache. This ensures that deletion will happen
     // in the correct thread/context. This adds the only ref to the texture that will persist from
     // this point. That ref will be released when the generator's RefHelper is freed.
-    context->priv().getResourceCache()->insertCrossContextGpuResource(texture.get());
+    context->priv().getResourceCache()->insertDelayedResourceUnref(texture.get());
 
     GrBackendTexture backendTexture = texture->getBackendTexture();
     GrBackendFormat backendFormat = backendTexture.getBackendFormat();
@@ -66,12 +74,12 @@
                                                                uint32_t owningContextID,
                                                                sk_sp<GrSemaphore> semaphore,
                                                                const GrBackendTexture& backendTex)
-    : INHERITED(info)
-    , fRefHelper(new RefHelper(texture, owningContextID))
-    , fSemaphore(std::move(semaphore))
-    , fBackendTexture(backendTex)
-    , fConfig(backendTex.config())
-    , fSurfaceOrigin(origin) { }
+        : INHERITED(info)
+        , fRefHelper(new RefHelper(texture, owningContextID))
+        , fSemaphore(std::move(semaphore))
+        , fBackendTexture(backendTex)
+        , fConfig(backendTex.config())
+        , fSurfaceOrigin(origin) {}
 
 GrBackendTextureImageGenerator::~GrBackendTextureImageGenerator() {
     fRefHelper->unref();
@@ -83,14 +91,14 @@
     RefHelper* refHelper = static_cast<RefHelper*>(ctx);
     SkASSERT(refHelper);
 
-    refHelper->fBorrowedTexture = nullptr;
     refHelper->fBorrowingContextReleaseProc = nullptr;
     refHelper->fBorrowingContextID = SK_InvalidGenID;
     refHelper->unref();
 }
 
 sk_sp<GrTextureProxy> GrBackendTextureImageGenerator::onGenerateTexture(
-        GrContext* context, const SkImageInfo& info, const SkIPoint& origin, bool willNeedMipMaps) {
+        GrRecordingContext* context, const SkImageInfo& info,
+        const SkIPoint& origin, bool willNeedMipMaps) {
     SkASSERT(context);
 
     if (context->backend() != fBackendTexture.backend()) {
@@ -103,7 +111,7 @@
     auto proxyProvider = context->priv().proxyProvider();
 
     fBorrowingMutex.acquire();
-    sk_sp<GrReleaseProcHelper> releaseProcHelper;
+    sk_sp<GrRefCntedCallback> releaseProcHelper;
     if (SK_InvalidGenID != fRefHelper->fBorrowingContextID) {
         if (fRefHelper->fBorrowingContextID != context->priv().contextID()) {
             fBorrowingMutex.release();
@@ -116,13 +124,18 @@
     } else {
         SkASSERT(!fRefHelper->fBorrowingContextReleaseProc);
         // The ref we add to fRefHelper here will be passed into and owned by the
-        // GrReleaseProcHelper.
+        // GrRefCntedCallback.
         fRefHelper->ref();
-        releaseProcHelper.reset(new GrReleaseProcHelper(ReleaseRefHelper_TextureReleaseProc,
-                                                        fRefHelper));
+        releaseProcHelper.reset(
+                new GrRefCntedCallback(ReleaseRefHelper_TextureReleaseProc, fRefHelper));
         fRefHelper->fBorrowingContextReleaseProc = releaseProcHelper.get();
     }
     fRefHelper->fBorrowingContextID = context->priv().contextID();
+    if (!fRefHelper->fBorrowedTextureKey.isValid()) {
+        static const auto kDomain = GrUniqueKey::GenerateDomain();
+        GrUniqueKey::Builder builder(&fRefHelper->fBorrowedTextureKey, kDomain, 1);
+        builder[0] = this->uniqueID();
+    }
     fBorrowingMutex.release();
 
     SkASSERT(fRefHelper->fBorrowingContextID == context->priv().contextID());
@@ -133,34 +146,30 @@
     desc.fConfig = fConfig;
     GrMipMapped mipMapped = fBackendTexture.hasMipMaps() ? GrMipMapped::kYes : GrMipMapped::kNo;
 
-    // Must make copies of member variables to capture in the lambda since this image generator may
-    // be deleted before we actuallly execute the lambda.
-    sk_sp<GrSemaphore> semaphore = fSemaphore;
-    GrBackendTexture backendTexture = fBackendTexture;
-    RefHelper* refHelper = fRefHelper;
-
-    GrBackendFormat format = backendTexture.getBackendFormat();
+    GrBackendFormat format = fBackendTexture.getBackendFormat();
     SkASSERT(format.isValid());
 
+    // Must make copies of member variables to capture in the lambda since this image generator may
+    // be deleted before we actually execute the lambda.
     sk_sp<GrTextureProxy> proxy = proxyProvider->createLazyProxy(
-            [refHelper, releaseProcHelper, semaphore,
-             backendTexture](GrResourceProvider* resourceProvider) {
-                if (!resourceProvider) {
-                    return sk_sp<GrTexture>();
-                }
-
+            [refHelper = fRefHelper, releaseProcHelper, semaphore = fSemaphore,
+             backendTexture = fBackendTexture](GrResourceProvider* resourceProvider)
+                    -> GrSurfaceProxy::LazyInstantiationResult {
                 if (semaphore) {
                     resourceProvider->priv().gpu()->waitSemaphore(semaphore);
                 }
 
+                // If a client re-draws the same image multiple times, the texture we return
+                // will be cached and re-used. If they draw a subset, though, we may be
+                // re-called. In that case, we want to re-use the borrowed texture we've
+                // previously created.
                 sk_sp<GrTexture> tex;
-                if (refHelper->fBorrowedTexture) {
-                    // If a client re-draws the same image multiple times, the texture we return
-                    // will be cached and re-used. If they draw a subset, though, we may be
-                    // re-called. In that case, we want to re-use the borrowed texture we've
-                    // previously created.
-                    tex = sk_ref_sp(refHelper->fBorrowedTexture);
-                    SkASSERT(tex);
+                SkASSERT(refHelper->fBorrowedTextureKey.isValid());
+                auto surf = resourceProvider->findByUniqueKey<GrSurface>(
+                        refHelper->fBorrowedTextureKey);
+                if (surf) {
+                    SkASSERT(surf->asTexture());
+                    tex = sk_ref_sp(surf->asTexture());
                 } else {
                     // We just gained access to the texture. If we're on the original context, we
                     // could use the original texture, but we'd have no way of detecting that it's
@@ -173,14 +182,14 @@
                             backendTexture, kBorrow_GrWrapOwnership, GrWrapCacheable::kNo,
                             kRead_GrIOType);
                     if (!tex) {
-                        return sk_sp<GrTexture>();
+                        return {};
                     }
-                    refHelper->fBorrowedTexture = tex.get();
-
                     tex->setRelease(releaseProcHelper);
+                    tex->resourcePriv().setUniqueKey(refHelper->fBorrowedTextureKey);
                 }
-
-                return tex;
+                // We use keys to avoid re-wrapping the GrBackendTexture in a GrTexture. This is
+                // unrelated to the whatever SkImage key may be assigned to the proxy.
+                return {std::move(tex), GrSurfaceProxy::LazyInstantiationKeyMode::kUnsynced};
             },
             format, desc, fSurfaceOrigin, mipMapped, GrInternalSurfaceFlags::kReadOnly,
             SkBackingFit::kExact, SkBudgeted::kNo);
diff --git a/src/gpu/GrBackendTextureImageGenerator.h b/src/gpu/GrBackendTextureImageGenerator.h
index 9d76e02..5999f8f 100644
--- a/src/gpu/GrBackendTextureImageGenerator.h
+++ b/src/gpu/GrBackendTextureImageGenerator.h
@@ -7,9 +7,9 @@
 #ifndef GrBackendTextureImageGenerator_DEFINED
 #define GrBackendTextureImageGenerator_DEFINED
 
-#include "SkImageGenerator.h"
-
 #include "GrBackendSurface.h"
+#include "GrResourceKey.h"
+#include "SkImageGenerator.h"
 #include "SkMutex.h"
 
 class GrSemaphore;
@@ -40,8 +40,8 @@
     bool onIsValid(GrContext*) const override { return true; }
 
     TexGenType onCanGenerateTexture() const override { return TexGenType::kCheap; }
-    sk_sp<GrTextureProxy> onGenerateTexture(GrContext*, const SkImageInfo&, const SkIPoint&,
-                                            bool willNeedMipMaps) override;
+    sk_sp<GrTextureProxy> onGenerateTexture(GrRecordingContext*, const SkImageInfo&,
+                                            const SkIPoint&, bool willNeedMipMaps) override;
 
 private:
     GrBackendTextureImageGenerator(const SkImageInfo& info, GrTexture*, GrSurfaceOrigin,
@@ -52,28 +52,22 @@
 
     class RefHelper : public SkNVRefCnt<RefHelper> {
     public:
-        RefHelper(GrTexture* texture, uint32_t owningContextID)
-            : fOriginalTexture(texture)
-            , fOwningContextID(owningContextID)
-            , fBorrowedTexture(nullptr)
-            , fBorrowingContextReleaseProc(nullptr)
-            , fBorrowingContextID(SK_InvalidGenID) {}
+        RefHelper(GrTexture*, uint32_t owningContextID);
 
         ~RefHelper();
 
         GrTexture*          fOriginalTexture;
         uint32_t            fOwningContextID;
 
-        // There is never a ref associated with this pointer. We rely on our atomic bookkeeping
-        // with the context ID to know when this pointer is valid and safe to use. This lets us
-        // avoid releasing a ref from another thread, or get into races during context shutdown.
-        GrTexture*           fBorrowedTexture;
-        // For the same reason as the fBorrowedTexture, there is no ref associated with this
-        // pointer. The fBorrowingContextReleaseProc is used to make sure all uses of the wrapped
-        // texture are finished on the borrowing context before we open this back up to other
-        // contexts. In general a ref to this release proc is owned by all proxies and gpu uses of
-        // the backend texture.
-        GrReleaseProcHelper* fBorrowingContextReleaseProc;
+        // We use this key so that we don't rewrap the GrBackendTexture in a GrTexture for each
+        // proxy created from this generator for a particular borrowing context.
+        GrUniqueKey         fBorrowedTextureKey;
+        // There is no ref associated with this pointer. We rely on our atomic bookkeeping with the
+        // context ID to know when this pointer is valid and safe to use. This is used to make sure
+        // all uses of the wrapped texture are finished on the borrowing context before we open
+        // this back up to other contexts. In general a ref to this release proc is owned by all
+        // proxies and gpu uses of the backend texture.
+        GrRefCntedCallback*  fBorrowingContextReleaseProc;
         uint32_t             fBorrowingContextID;
     };
 
diff --git a/src/gpu/GrBaseContextPriv.h b/src/gpu/GrBaseContextPriv.h
index ab99684..31958b3 100644
--- a/src/gpu/GrBaseContextPriv.h
+++ b/src/gpu/GrBaseContextPriv.h
@@ -18,12 +18,22 @@
     // from GrContext_Base
     uint32_t contextID() const { return fContext->contextID(); }
 
+    bool matches(GrContext_Base* candidate) const { return fContext->matches(candidate); }
+
     const GrContextOptions& options() const { return fContext->options(); }
 
-    const GrCaps* caps() const { return fContext->caps(); }
-    sk_sp<const GrCaps> refCaps() const { return fContext->refCaps(); }
+    bool explicitlyAllocateGPUResources() const {
+        return fContext->explicitlyAllocateGPUResources();
+    }
 
-    sk_sp<GrSkSLFPFactoryCache> fpFactoryCache() { return fContext->fpFactoryCache(); }
+    const GrCaps* caps() const { return fContext->caps(); }
+    sk_sp<const GrCaps> refCaps() const;
+
+    sk_sp<GrSkSLFPFactoryCache> fpFactoryCache();
+
+    GrImageContext* asImageContext() { return fContext->asImageContext(); }
+    GrRecordingContext* asRecordingContext() { return fContext->asRecordingContext(); }
+    GrContext* asDirectContext() { return fContext->asDirectContext(); }
 
 private:
     explicit GrBaseContextPriv(GrContext_Base* context) : fContext(context) {}
diff --git a/src/gpu/GrBitmapTextureMaker.cpp b/src/gpu/GrBitmapTextureMaker.cpp
index 95f1c84..c6d7504 100644
--- a/src/gpu/GrBitmapTextureMaker.cpp
+++ b/src/gpu/GrBitmapTextureMaker.cpp
@@ -7,10 +7,10 @@
 
 #include "GrBitmapTextureMaker.h"
 
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrGpuResourcePriv.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrSurfaceContext.h"
 #include "SkBitmap.h"
 #include "SkGr.h"
@@ -19,8 +19,9 @@
 
 static bool bmp_is_alpha_only(const SkBitmap& bm) { return kAlpha_8_SkColorType == bm.colorType(); }
 
-GrBitmapTextureMaker::GrBitmapTextureMaker(GrContext* context, const SkBitmap& bitmap)
-    : INHERITED(context, bitmap.width(), bitmap.height(), bmp_is_alpha_only(bitmap))
+GrBitmapTextureMaker::GrBitmapTextureMaker(GrRecordingContext* context, const SkBitmap& bitmap,
+                                           bool useDecal)
+    : INHERITED(context, bitmap.width(), bitmap.height(), bmp_is_alpha_only(bitmap), useDecal)
     , fBitmap(bitmap) {
     if (!bitmap.isVolatile()) {
         SkIPoint origin = bitmap.pixelRefOrigin();
@@ -47,12 +48,8 @@
     }
 
     if (!proxy) {
-        if (willBeMipped) {
-            proxy = proxyProvider->createMipMapProxyFromBitmap(fBitmap);
-        }
-        if (!proxy) {
-            proxy = GrUploadBitmapToTextureProxy(proxyProvider, fBitmap);
-        }
+        proxy = proxyProvider->createProxyFromBitmap(fBitmap, willBeMipped ? GrMipMapped::kYes
+                                                                           : GrMipMapped::kNo);
         if (proxy) {
             if (fOriginalKey.isValid()) {
                 proxyProvider->assignUniqueKeyToProxy(fOriginalKey, proxy.get());
@@ -61,7 +58,7 @@
                 SkASSERT(proxy->origin() == kTopLeft_GrSurfaceOrigin);
                 if (fOriginalKey.isValid()) {
                     GrInstallBitmapUniqueKeyInvalidator(
-                            fOriginalKey, proxyProvider->contextUniqueID(), fBitmap.pixelRef());
+                            fOriginalKey, proxyProvider->contextID(), fBitmap.pixelRef());
                 }
                 return proxy;
             }
@@ -86,7 +83,7 @@
                 SkASSERT(proxy->getUniqueKey() == fOriginalKey);
                 proxyProvider->removeUniqueKeyFromProxy(proxy.get());
                 proxyProvider->assignUniqueKeyToProxy(fOriginalKey, mippedProxy.get());
-                GrInstallBitmapUniqueKeyInvalidator(fOriginalKey, proxyProvider->contextUniqueID(),
+                GrInstallBitmapUniqueKeyInvalidator(fOriginalKey, proxyProvider->contextID(),
                                                     fBitmap.pixelRef());
             }
             return mippedProxy;
diff --git a/src/gpu/GrBitmapTextureMaker.h b/src/gpu/GrBitmapTextureMaker.h
index 0602e5a..da9869c 100644
--- a/src/gpu/GrBitmapTextureMaker.h
+++ b/src/gpu/GrBitmapTextureMaker.h
@@ -16,7 +16,8 @@
     subset of the pixelref specified by the bitmap. */
 class GrBitmapTextureMaker : public GrTextureMaker {
 public:
-    GrBitmapTextureMaker(GrContext* context, const SkBitmap& bitmap);
+    GrBitmapTextureMaker(GrRecordingContext* context, const SkBitmap& bitmap,
+                         bool useDecal = false);
 
 protected:
     sk_sp<GrTextureProxy> refOriginalTextureProxy(bool willBeMipped,
diff --git a/src/gpu/GrBlurUtils.cpp b/src/gpu/GrBlurUtils.cpp
index 816801a..2b61aef 100644
--- a/src/gpu/GrBlurUtils.cpp
+++ b/src/gpu/GrBlurUtils.cpp
@@ -8,17 +8,17 @@
 #include "GrBlurUtils.h"
 
 #include "GrCaps.h"
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrFixedClip.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrRenderTargetContextPriv.h"
 #include "GrShape.h"
 #include "GrSoftwarePathRenderer.h"
 #include "GrStyle.h"
 #include "GrTextureProxy.h"
-#include "effects/GrSimpleTextureEffect.h"
+#include "effects/generated/GrSimpleTextureEffect.h"
 
 #include "SkDraw.h"
 #include "SkGr.h"
@@ -58,7 +58,7 @@
     SkMask::FreeImage(addr);
 }
 
-static bool sw_draw_with_mask_filter(GrContext* context,
+static bool sw_draw_with_mask_filter(GrRecordingContext* context,
                                      GrRenderTargetContext* renderTargetContext,
                                      const GrClip& clipData,
                                      const SkMatrix& viewMatrix,
@@ -170,7 +170,7 @@
 }
 
 // Create a mask of 'shape' and place the result in 'mask'.
-static sk_sp<GrTextureProxy> create_mask_GPU(GrContext* context,
+static sk_sp<GrTextureProxy> create_mask_GPU(GrRecordingContext* context,
                                              const SkIRect& maskRect,
                                              const SkMatrix& origViewMatrix,
                                              const GrShape& shape,
@@ -249,7 +249,7 @@
     return true;
 }
 
-static void draw_shape_with_mask_filter(GrContext* context,
+static void draw_shape_with_mask_filter(GrRecordingContext* context,
                                         GrRenderTargetContext* renderTargetContext,
                                         const GrClip& clip,
                                         GrPaint&& paint,
@@ -430,7 +430,7 @@
                              maskFilter, *boundsForClip, std::move(paint), maskKey);
 }
 
-void GrBlurUtils::drawShapeWithMaskFilter(GrContext* context,
+void GrBlurUtils::drawShapeWithMaskFilter(GrRecordingContext* context,
                                           GrRenderTargetContext* renderTargetContext,
                                           const GrClip& clip,
                                           const GrShape& shape,
@@ -441,13 +441,13 @@
                                 viewMatrix, as_MFB(mf), shape);
 }
 
-void GrBlurUtils::drawShapeWithMaskFilter(GrContext* context,
+void GrBlurUtils::drawShapeWithMaskFilter(GrRecordingContext* context,
                                           GrRenderTargetContext* renderTargetContext,
                                           const GrClip& clip,
                                           const SkPaint& paint,
                                           const SkMatrix& viewMatrix,
                                           const GrShape& shape) {
-    if (context->abandoned()) {
+    if (context->priv().abandoned()) {
         return;
     }
 
diff --git a/src/gpu/GrBlurUtils.h b/src/gpu/GrBlurUtils.h
index 6db3cd2..7e10edc 100644
--- a/src/gpu/GrBlurUtils.h
+++ b/src/gpu/GrBlurUtils.h
@@ -13,6 +13,7 @@
 class GrClip;
 class GrContext;
 class GrPaint;
+class GrRecordingContext;
 class GrRenderTarget;
 class GrRenderTargetContext;
 class GrShape;
@@ -32,7 +33,7 @@
     /**
      * Draw a shape handling the mask filter if present.
      */
-    void drawShapeWithMaskFilter(GrContext*,
+    void drawShapeWithMaskFilter(GrRecordingContext*,
                                  GrRenderTargetContext*,
                                  const GrClip&,
                                  const SkPaint&,
@@ -43,7 +44,7 @@
      * Draw a shape handling the mask filter. The mask filter is not optional.
      * The GrPaint will be modified after return.
      */
-    void drawShapeWithMaskFilter(GrContext*,
+    void drawShapeWithMaskFilter(GrRecordingContext*,
                                  GrRenderTargetContext*,
                                  const GrClip&,
                                  const GrShape&,
diff --git a/src/gpu/GrBuffer.cpp b/src/gpu/GrBuffer.cpp
deleted file mode 100644
index cec2556..0000000
--- a/src/gpu/GrBuffer.cpp
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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 "GrBuffer.h"
-#include "GrGpu.h"
-#include "GrCaps.h"
-
-sk_sp<GrBuffer> GrBuffer::MakeCPUBacked(GrGpu* gpu, size_t sizeInBytes,
-                                        GrGpuBufferType intendedType, const void* data) {
-    SkASSERT(GrBufferTypeIsVertexOrIndex(intendedType));
-    void* cpuData;
-    if (gpu->caps()->mustClearUploadedBufferData()) {
-        cpuData = sk_calloc_throw(sizeInBytes);
-    } else {
-        cpuData = sk_malloc_throw(sizeInBytes);
-    }
-    if (data) {
-        memcpy(cpuData, data, sizeInBytes);
-    }
-    return sk_sp<GrBuffer>(new GrBuffer(gpu, sizeInBytes, intendedType, cpuData));
-}
-
-GrBuffer::GrBuffer(GrGpu* gpu, size_t sizeInBytes, GrGpuBufferType type, void* cpuData)
-    : INHERITED(gpu)
-    , fMapPtr(nullptr)
-    , fSizeInBytes(sizeInBytes)
-    , fAccessPattern(kDynamic_GrAccessPattern)
-    , fCPUData(cpuData)
-    , fIntendedType(type) {
-    this->registerWithCache(SkBudgeted::kNo);
-}
-
-GrBuffer::GrBuffer(GrGpu* gpu, size_t sizeInBytes, GrGpuBufferType type, GrAccessPattern pattern)
-    : INHERITED(gpu)
-    , fMapPtr(nullptr)
-    , fSizeInBytes(sizeInBytes)
-    , fAccessPattern(pattern)
-    , fCPUData(nullptr)
-    , fIntendedType(type) {
-    // Subclass registers with cache.
-}
-
-void GrBuffer::ComputeScratchKeyForDynamicVBO(size_t size, GrGpuBufferType intendedType,
-                                              GrScratchKey* key) {
-    static const GrScratchKey::ResourceType kType = GrScratchKey::GenerateResourceType();
-    GrScratchKey::Builder builder(key, kType, 1 + (sizeof(size_t) + 3) / 4);
-    // TODO: There's not always reason to cache a buffer by type. In some (all?) APIs it's just
-    // a chunk of memory we can use/reuse for any type of data. We really only need to
-    // differentiate between the "read" types (e.g. kGpuToCpu_BufferType) and "draw" types.
-    builder[0] = SkToU32(intendedType);
-    builder[1] = (uint32_t)size;
-    if (sizeof(size_t) > 4) {
-        builder[2] = (uint32_t)((uint64_t)size >> 32);
-    }
-}
-
-bool GrBuffer::onUpdateData(const void* src, size_t srcSizeInBytes) {
-    SkASSERT(this->isCPUBacked());
-    memcpy(fCPUData, src, srcSizeInBytes);
-    return true;
-}
-
-void GrBuffer::computeScratchKey(GrScratchKey* key) const {
-    if (!this->isCPUBacked() && SkIsPow2(fSizeInBytes) &&
-        kDynamic_GrAccessPattern == fAccessPattern) {
-        ComputeScratchKeyForDynamicVBO(fSizeInBytes, fIntendedType, key);
-    }
-}
diff --git a/src/gpu/GrBuffer.h b/src/gpu/GrBuffer.h
index d90b587..cc710c7 100644
--- a/src/gpu/GrBuffer.h
+++ b/src/gpu/GrBuffer.h
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 Google Inc.
+ * Copyright 2019 Google Inc.
  *
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
@@ -8,124 +8,29 @@
 #ifndef GrBuffer_DEFINED
 #define GrBuffer_DEFINED
 
-#include "GrGpuResource.h"
+#include "GrTypes.h"
 
-class GrGpu;
-
-class GrBuffer : public GrGpuResource {
+/** Base class for a GPU buffer object or a client side arrays. */
+class GrBuffer {
 public:
-    /**
-     * Creates a client-side buffer.
-     */
-    static SK_WARN_UNUSED_RESULT sk_sp<GrBuffer> MakeCPUBacked(GrGpu*, size_t sizeInBytes,
-                                                               GrGpuBufferType,
-                                                               const void* data = nullptr);
+    GrBuffer(const GrBuffer&) = delete;
+    GrBuffer& operator=(const GrBuffer&) = delete;
 
-    /**
-     * Computes a scratch key for a GPU-side buffer with a "dynamic" access pattern. (Buffers with
-     * "static" and "stream" patterns are disqualified by nature from being cached and reused.)
-     */
-    static void ComputeScratchKeyForDynamicVBO(size_t size, GrGpuBufferType, GrScratchKey*);
+    virtual ~GrBuffer() = default;
 
-    GrAccessPattern accessPattern() const { return fAccessPattern; }
-    size_t sizeInBytes() const { return fSizeInBytes; }
+    // Our subclasses derive from different ref counting base classes. In order to use base
+    // class pointers with sk_sp we virtualize ref() and unref().
+    virtual void ref() const = 0;
+    virtual void unref() const = 0;
 
-    /**
-     * Returns true if the buffer is a wrapper around a CPU array. If true it
-     * indicates that map will always succeed and will be free.
-     */
-    bool isCPUBacked() const { return SkToBool(fCPUData); }
-    size_t baseOffset() const { return reinterpret_cast<size_t>(fCPUData); }
+    /** Size of the buffer in bytes. */
+    virtual size_t size() const = 0;
 
-    /**
-     * Maps the buffer to be written by the CPU.
-     *
-     * The previous content of the buffer is invalidated. It is an error
-     * to draw from the buffer while it is mapped. It may fail if the backend
-     * doesn't support mapping the buffer. If the buffer is CPU backed then
-     * it will always succeed and is a free operation. Once a buffer is mapped,
-     * subsequent calls to map() are ignored.
-     *
-     * Note that buffer mapping does not go through GrContext and therefore is
-     * not serialized with other operations.
-     *
-     * @return a pointer to the data or nullptr if the map fails.
-     */
-     void* map() {
-         if (!fMapPtr) {
-             this->onMap();
-         }
-         return fMapPtr;
-     }
-
-    /**
-     * Unmaps the buffer.
-     *
-     * The pointer returned by the previous map call will no longer be valid.
-     */
-     void unmap() {
-         SkASSERT(fMapPtr);
-         this->onUnmap();
-         fMapPtr = nullptr;
-     }
-
-    /**
-     Queries whether the buffer has been mapped.
-
-     @return true if the buffer is mapped, false otherwise.
-     */
-     bool isMapped() const { return SkToBool(fMapPtr); }
-
-    /**
-     * Updates the buffer data.
-     *
-     * The size of the buffer will be preserved. The src data will be
-     * placed at the beginning of the buffer and any remaining contents will
-     * be undefined. srcSizeInBytes must be <= to the buffer size.
-     *
-     * The buffer must not be mapped.
-     *
-     * Note that buffer updates do not go through GrContext and therefore are
-     * not serialized with other operations.
-     *
-     * @return returns true if the update succeeds, false otherwise.
-     */
-    bool updateData(const void* src, size_t srcSizeInBytes) {
-        SkASSERT(!this->isMapped());
-        SkASSERT(srcSizeInBytes <= fSizeInBytes);
-        return this->onUpdateData(src, srcSizeInBytes);
-    }
-
-    ~GrBuffer() override {
-        sk_free(fCPUData);
-    }
+    /** Is this an instance of GrCpuBuffer? Otherwise, an instance of GrGpuBuffer. */
+    virtual bool isCpuBuffer() const = 0;
 
 protected:
-    GrBuffer(GrGpu*, size_t sizeInBytes, GrGpuBufferType, GrAccessPattern);
-    GrGpuBufferType intendedType() const { return fIntendedType; }
-
-    void* fMapPtr;
-
-private:
-    /**
-     * Internal constructor to make a CPU-backed buffer.
-     */
-    GrBuffer(GrGpu*, size_t sizeInBytes, GrGpuBufferType, void* cpuData);
-
-    virtual void onMap() { SkASSERT(this->isCPUBacked()); fMapPtr = fCPUData; }
-    virtual void onUnmap() { SkASSERT(this->isCPUBacked()); }
-    virtual bool onUpdateData(const void* src, size_t srcSizeInBytes);
-
-    size_t onGpuMemorySize() const override { return fSizeInBytes; } // TODO: zero for cpu backed?
-    const char* getResourceType() const override { return "Buffer Object"; }
-    void computeScratchKey(GrScratchKey* key) const override;
-
-    size_t            fSizeInBytes;
-    GrAccessPattern   fAccessPattern;
-    void*             fCPUData;
-    GrGpuBufferType   fIntendedType;
-
-    typedef GrGpuResource INHERITED;
+    GrBuffer() = default;
 };
 
 #endif
diff --git a/src/gpu/GrBufferAllocPool.cpp b/src/gpu/GrBufferAllocPool.cpp
index 0d0e151..36c0431 100644
--- a/src/gpu/GrBufferAllocPool.cpp
+++ b/src/gpu/GrBufferAllocPool.cpp
@@ -6,48 +6,96 @@
  */
 
 #include "GrBufferAllocPool.h"
-
-#include "GrBuffer.h"
 #include "GrCaps.h"
 #include "GrContext.h"
 #include "GrContextPriv.h"
+#include "GrCpuBuffer.h"
 #include "GrGpu.h"
+#include "GrGpuBuffer.h"
 #include "GrResourceProvider.h"
 #include "GrTypes.h"
 #include "SkMacros.h"
 #include "SkSafeMath.h"
 #include "SkTraceEvent.h"
 
+sk_sp<GrBufferAllocPool::CpuBufferCache> GrBufferAllocPool::CpuBufferCache::Make(
+        int maxBuffersToCache) {
+    return sk_sp<CpuBufferCache>(new CpuBufferCache(maxBuffersToCache));
+}
+
+GrBufferAllocPool::CpuBufferCache::CpuBufferCache(int maxBuffersToCache)
+        : fMaxBuffersToCache(maxBuffersToCache) {
+    if (fMaxBuffersToCache) {
+        fBuffers.reset(new Buffer[fMaxBuffersToCache]);
+    }
+}
+
+sk_sp<GrCpuBuffer> GrBufferAllocPool::CpuBufferCache::makeBuffer(size_t size,
+                                                                 bool mustBeInitialized) {
+    SkASSERT(size > 0);
+    Buffer* result = nullptr;
+    if (size == kDefaultBufferSize) {
+        int i = 0;
+        for (; i < fMaxBuffersToCache && fBuffers[i].fBuffer; ++i) {
+            SkASSERT(fBuffers[i].fBuffer->size() == kDefaultBufferSize);
+            if (fBuffers[i].fBuffer->unique()) {
+                result = &fBuffers[i];
+            }
+        }
+        if (!result && i < fMaxBuffersToCache) {
+            fBuffers[i].fBuffer = GrCpuBuffer::Make(size);
+            result = &fBuffers[i];
+        }
+    }
+    Buffer tempResult;
+    if (!result) {
+        tempResult.fBuffer = GrCpuBuffer::Make(size);
+        result = &tempResult;
+    }
+    if (mustBeInitialized && !result->fCleared) {
+        result->fCleared = true;
+        memset(result->fBuffer->data(), 0, result->fBuffer->size());
+    }
+    return result->fBuffer;
+}
+
+void GrBufferAllocPool::CpuBufferCache::releaseAll() {
+    for (int i = 0; i < fMaxBuffersToCache && fBuffers[i].fBuffer; ++i) {
+        fBuffers[i].fBuffer.reset();
+        fBuffers[i].fCleared = false;
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
 #ifdef SK_DEBUG
     #define VALIDATE validate
 #else
     static void VALIDATE(bool = false) {}
 #endif
 
-#define UNMAP_BUFFER(block)                                                               \
-do {                                                                                      \
-    TRACE_EVENT_INSTANT1("skia.gpu",                                                      \
-                         "GrBufferAllocPool Unmapping Buffer",                            \
-                         TRACE_EVENT_SCOPE_THREAD,                                        \
-                         "percent_unwritten",                                             \
-                         (float)((block).fBytesFree) / (block).fBuffer->gpuMemorySize()); \
-    (block).fBuffer->unmap();                                                             \
-} while (false)
+#define UNMAP_BUFFER(block)                                                          \
+    do {                                                                             \
+        TRACE_EVENT_INSTANT1("skia.gpu", "GrBufferAllocPool Unmapping Buffer",       \
+                             TRACE_EVENT_SCOPE_THREAD, "percent_unwritten",          \
+                             (float)((block).fBytesFree) / (block).fBuffer->size()); \
+        SkASSERT(!block.fBuffer->isCpuBuffer());                                     \
+        static_cast<GrGpuBuffer*>(block.fBuffer.get())->unmap();                     \
+    } while (false)
 
 constexpr size_t GrBufferAllocPool::kDefaultBufferSize;
 
-GrBufferAllocPool::GrBufferAllocPool(GrGpu* gpu, GrGpuBufferType bufferType, void* initialBuffer)
-        : fBlocks(8), fGpu(gpu), fBufferType(bufferType), fInitialCpuData(initialBuffer) {
-    if (fInitialCpuData) {
-        fCpuDataSize = kDefaultBufferSize;
-        fCpuData = fInitialCpuData;
-    }
-}
+GrBufferAllocPool::GrBufferAllocPool(GrGpu* gpu, GrGpuBufferType bufferType,
+                                     sk_sp<CpuBufferCache> cpuBufferCache)
+        : fBlocks(8)
+        , fCpuBufferCache(std::move(cpuBufferCache))
+        , fGpu(gpu)
+        , fBufferType(bufferType) {}
 
 void GrBufferAllocPool::deleteBlocks() {
     if (fBlocks.count()) {
         GrBuffer* buffer = fBlocks.back().fBuffer.get();
-        if (buffer->isMapped()) {
+        if (!buffer->isCpuBuffer() && static_cast<GrGpuBuffer*>(buffer)->isMapped()) {
             UNMAP_BUFFER(fBlocks.back());
         }
     }
@@ -60,9 +108,6 @@
 GrBufferAllocPool::~GrBufferAllocPool() {
     VALIDATE();
     this->deleteBlocks();
-    if (fCpuData != fInitialCpuData) {
-        sk_free(fCpuData);
-    }
 }
 
 void GrBufferAllocPool::reset() {
@@ -78,11 +123,14 @@
 
     if (fBufferPtr) {
         BufferBlock& block = fBlocks.back();
-        if (block.fBuffer->isMapped()) {
-            UNMAP_BUFFER(block);
-        } else {
-            size_t flushSize = block.fBuffer->gpuMemorySize() - block.fBytesFree;
-            this->flushCpuData(fBlocks.back(), flushSize);
+        GrBuffer* buffer = block.fBuffer.get();
+        if (!buffer->isCpuBuffer()) {
+            if (static_cast<GrGpuBuffer*>(buffer)->isMapped()) {
+                UNMAP_BUFFER(block);
+            } else {
+                size_t flushSize = block.fBuffer->size() - block.fBytesFree;
+                this->flushCpuData(fBlocks.back(), flushSize);
+            }
         }
         fBufferPtr = nullptr;
     }
@@ -94,21 +142,25 @@
     bool wasDestroyed = false;
     if (fBufferPtr) {
         SkASSERT(!fBlocks.empty());
-        if (!fBlocks.back().fBuffer->isMapped()) {
-            SkASSERT(fCpuData == fBufferPtr);
+        const GrBuffer* buffer = fBlocks.back().fBuffer.get();
+        if (!buffer->isCpuBuffer() && !static_cast<const GrGpuBuffer*>(buffer)->isMapped()) {
+            SkASSERT(fCpuStagingBuffer && fCpuStagingBuffer->data() == fBufferPtr);
         }
-    } else {
-        SkASSERT(fBlocks.empty() || !fBlocks.back().fBuffer->isMapped());
+    } else if (!fBlocks.empty()) {
+        const GrBuffer* buffer = fBlocks.back().fBuffer.get();
+        SkASSERT(buffer->isCpuBuffer() || !static_cast<const GrGpuBuffer*>(buffer)->isMapped());
     }
     size_t bytesInUse = 0;
     for (int i = 0; i < fBlocks.count() - 1; ++i) {
-        SkASSERT(!fBlocks[i].fBuffer->isMapped());
+        const GrBuffer* buffer = fBlocks[i].fBuffer.get();
+        SkASSERT(buffer->isCpuBuffer() || !static_cast<const GrGpuBuffer*>(buffer)->isMapped());
     }
     for (int i = 0; !wasDestroyed && i < fBlocks.count(); ++i) {
-        if (fBlocks[i].fBuffer->wasDestroyed()) {
+        GrBuffer* buffer = fBlocks[i].fBuffer.get();
+        if (!buffer->isCpuBuffer() && static_cast<GrGpuBuffer*>(buffer)->wasDestroyed()) {
             wasDestroyed = true;
         } else {
-            size_t bytes = fBlocks[i].fBuffer->gpuMemorySize() - fBlocks[i].fBytesFree;
+            size_t bytes = fBlocks[i].fBuffer->size() - fBlocks[i].fBytesFree;
             bytesInUse += bytes;
             SkASSERT(bytes || unusedBlockAllowed);
         }
@@ -137,7 +189,7 @@
 
     if (fBufferPtr) {
         BufferBlock& back = fBlocks.back();
-        size_t usedBytes = back.fBuffer->gpuMemorySize() - back.fBytesFree;
+        size_t usedBytes = back.fBuffer->size() - back.fBytesFree;
         size_t pad = GrSizeAlignUpPad(usedBytes, alignment);
         SkSafeMath safeMath;
         size_t alignedSize = safeMath.add(pad, size);
@@ -192,7 +244,7 @@
 
     if (fBufferPtr) {
         BufferBlock& back = fBlocks.back();
-        size_t usedBytes = back.fBuffer->gpuMemorySize() - back.fBytesFree;
+        size_t usedBytes = back.fBuffer->size() - back.fBytesFree;
         size_t pad = GrSizeAlignUpPad(usedBytes, alignment);
         if ((minSize + pad) <= back.fBytesFree) {
             // Consume padding first, to make subsequent alignment math easier
@@ -250,13 +302,14 @@
         // caller shouldn't try to put back more than they've taken
         SkASSERT(!fBlocks.empty());
         BufferBlock& block = fBlocks.back();
-        size_t bytesUsed = block.fBuffer->gpuMemorySize() - block.fBytesFree;
+        size_t bytesUsed = block.fBuffer->size() - block.fBytesFree;
         if (bytes >= bytesUsed) {
             bytes -= bytesUsed;
             fBytesInUse -= bytesUsed;
             // if we locked a vb to satisfy the make space and we're releasing
             // beyond it, then unmap it.
-            if (block.fBuffer->isMapped()) {
+            GrBuffer* buffer = block.fBuffer.get();
+            if (!buffer->isCpuBuffer() && static_cast<GrGpuBuffer*>(buffer)->isMapped()) {
                 UNMAP_BUFFER(block);
             }
             this->destroyBlock();
@@ -284,34 +337,38 @@
         return false;
     }
 
-    block.fBytesFree = block.fBuffer->gpuMemorySize();
+    block.fBytesFree = block.fBuffer->size();
     if (fBufferPtr) {
         SkASSERT(fBlocks.count() > 1);
         BufferBlock& prev = fBlocks.fromBack(1);
-        if (prev.fBuffer->isMapped()) {
-            UNMAP_BUFFER(prev);
-        } else {
-            this->flushCpuData(prev, prev.fBuffer->gpuMemorySize() - prev.fBytesFree);
+        GrBuffer* buffer = prev.fBuffer.get();
+        if (!buffer->isCpuBuffer()) {
+            if (static_cast<GrGpuBuffer*>(buffer)->isMapped()) {
+                UNMAP_BUFFER(prev);
+            } else {
+                this->flushCpuData(prev, prev.fBuffer->size() - prev.fBytesFree);
+            }
         }
         fBufferPtr = nullptr;
     }
 
     SkASSERT(!fBufferPtr);
 
-    // If the buffer is CPU-backed we map it because it is free to do so and saves a copy.
+    // If the buffer is CPU-backed we "map" it because it is free to do so and saves a copy.
     // Otherwise when buffer mapping is supported we map if the buffer size is greater than the
     // threshold.
-    bool attemptMap = block.fBuffer->isCPUBacked();
-    if (!attemptMap && GrCaps::kNone_MapFlags != fGpu->caps()->mapBufferFlags()) {
-        attemptMap = size > fGpu->caps()->bufferMapThreshold();
+    if (block.fBuffer->isCpuBuffer()) {
+        fBufferPtr = static_cast<GrCpuBuffer*>(block.fBuffer.get())->data();
+        SkASSERT(fBufferPtr);
+    } else {
+        if (GrCaps::kNone_MapFlags != fGpu->caps()->mapBufferFlags() &&
+            size > fGpu->caps()->bufferMapThreshold()) {
+            fBufferPtr = static_cast<GrGpuBuffer*>(block.fBuffer.get())->map();
+        }
     }
-
-    if (attemptMap) {
-        fBufferPtr = block.fBuffer->map();
-    }
-
     if (!fBufferPtr) {
-        fBufferPtr = this->resetCpuData(block.fBytesFree);
+        this->resetCpuData(block.fBytesFree);
+        fBufferPtr = fCpuStagingBuffer->data();
     }
 
     VALIDATE(true);
@@ -321,35 +378,33 @@
 
 void GrBufferAllocPool::destroyBlock() {
     SkASSERT(!fBlocks.empty());
-    SkASSERT(!fBlocks.back().fBuffer->isMapped());
+    SkASSERT(fBlocks.back().fBuffer->isCpuBuffer() ||
+             !static_cast<GrGpuBuffer*>(fBlocks.back().fBuffer.get())->isMapped());
     fBlocks.pop_back();
     fBufferPtr = nullptr;
 }
 
-void* GrBufferAllocPool::resetCpuData(size_t newSize) {
-    if (newSize <= fCpuDataSize) {
-        SkASSERT(!newSize || fCpuData);
-        return fCpuData;
+void GrBufferAllocPool::resetCpuData(size_t newSize) {
+    SkASSERT(newSize >= kDefaultBufferSize || !newSize);
+    if (!newSize) {
+        fCpuStagingBuffer.reset();
+        return;
     }
-    if (fCpuData != fInitialCpuData) {
-        sk_free(fCpuData);
+    if (fCpuStagingBuffer && newSize <= fCpuStagingBuffer->size()) {
+        return;
     }
-    if (fGpu->caps()->mustClearUploadedBufferData()) {
-        fCpuData = sk_calloc_throw(newSize);
-    } else {
-        fCpuData = sk_malloc_throw(newSize);
-    }
-    fCpuDataSize = newSize;
-    return fCpuData;
+    bool mustInitialize = fGpu->caps()->mustClearUploadedBufferData();
+    fCpuStagingBuffer = fCpuBufferCache ? fCpuBufferCache->makeBuffer(newSize, mustInitialize)
+                                        : GrCpuBuffer::Make(newSize);
 }
 
-
 void GrBufferAllocPool::flushCpuData(const BufferBlock& block, size_t flushSize) {
-    GrBuffer* buffer = block.fBuffer.get();
-    SkASSERT(buffer);
+    SkASSERT(block.fBuffer.get());
+    SkASSERT(!block.fBuffer.get()->isCpuBuffer());
+    GrGpuBuffer* buffer = static_cast<GrGpuBuffer*>(block.fBuffer.get());
     SkASSERT(!buffer->isMapped());
-    SkASSERT(fCpuData == fBufferPtr);
-    SkASSERT(flushSize <= buffer->gpuMemorySize());
+    SkASSERT(fCpuStagingBuffer && fCpuStagingBuffer->data() == fBufferPtr);
+    SkASSERT(flushSize <= buffer->size());
     VALIDATE(true);
 
     if (GrCaps::kNone_MapFlags != fGpu->caps()->mapBufferFlags() &&
@@ -368,14 +423,18 @@
 sk_sp<GrBuffer> GrBufferAllocPool::getBuffer(size_t size) {
     auto resourceProvider = fGpu->getContext()->priv().resourceProvider();
 
-    return resourceProvider->createBuffer(size, fBufferType, kDynamic_GrAccessPattern,
-            GrResourceProvider::Flags::kNone);
+    if (fGpu->caps()->preferClientSideDynamicBuffers()) {
+        bool mustInitialize = fGpu->caps()->mustClearUploadedBufferData();
+        return fCpuBufferCache ? fCpuBufferCache->makeBuffer(size, mustInitialize)
+                               : GrCpuBuffer::Make(size);
+    }
+    return resourceProvider->createBuffer(size, fBufferType, kDynamic_GrAccessPattern);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
-GrVertexBufferAllocPool::GrVertexBufferAllocPool(GrGpu* gpu, void* initialCpuBuffer)
-        : GrBufferAllocPool(gpu, GrGpuBufferType::kVertex, initialCpuBuffer) {}
+GrVertexBufferAllocPool::GrVertexBufferAllocPool(GrGpu* gpu, sk_sp<CpuBufferCache> cpuBufferCache)
+        : GrBufferAllocPool(gpu, GrGpuBufferType::kVertex, std::move(cpuBufferCache)) {}
 
 void* GrVertexBufferAllocPool::makeSpace(size_t vertexSize,
                                          int vertexCount,
@@ -427,8 +486,8 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-GrIndexBufferAllocPool::GrIndexBufferAllocPool(GrGpu* gpu, void* initialCpuBuffer)
-        : GrBufferAllocPool(gpu, GrGpuBufferType::kIndex, initialCpuBuffer) {}
+GrIndexBufferAllocPool::GrIndexBufferAllocPool(GrGpu* gpu, sk_sp<CpuBufferCache> cpuBufferCache)
+        : GrBufferAllocPool(gpu, GrGpuBufferType::kIndex, std::move(cpuBufferCache)) {}
 
 void* GrIndexBufferAllocPool::makeSpace(int indexCount, sk_sp<const GrBuffer>* buffer,
                                         int* startIndex) {
diff --git a/src/gpu/GrBufferAllocPool.h b/src/gpu/GrBufferAllocPool.h
index 9453660..b499e80 100644
--- a/src/gpu/GrBufferAllocPool.h
+++ b/src/gpu/GrBufferAllocPool.h
@@ -8,13 +8,14 @@
 #ifndef GrBufferAllocPool_DEFINED
 #define GrBufferAllocPool_DEFINED
 
+#include "GrCpuBuffer.h"
+#include "GrNonAtomicRef.h"
 #include "GrTypesPriv.h"
 #include "SkNoncopyable.h"
 #include "SkTArray.h"
 #include "SkTDArray.h"
 #include "SkTypes.h"
 
-class GrBuffer;
 class GrGpu;
 
 /**
@@ -34,6 +35,28 @@
     static constexpr size_t kDefaultBufferSize = 1 << 15;
 
     /**
+     * A cache object that can be shared by multiple GrBufferAllocPool instances. It caches
+     * cpu buffer allocations to avoid reallocating them.
+     */
+    class CpuBufferCache : public GrNonAtomicRef<CpuBufferCache> {
+    public:
+        static sk_sp<CpuBufferCache> Make(int maxBuffersToCache);
+
+        sk_sp<GrCpuBuffer> makeBuffer(size_t size, bool mustBeInitialized);
+        void releaseAll();
+
+    private:
+        CpuBufferCache(int maxBuffersToCache);
+
+        struct Buffer {
+            sk_sp<GrCpuBuffer> fBuffer;
+            bool fCleared = false;
+        };
+        std::unique_ptr<Buffer[]> fBuffers;
+        int fMaxBuffersToCache = 0;
+    };
+
+    /**
      * Ensures all buffers are unmapped and have all data written to them.
      * Call before drawing using buffers from the pool.
      */
@@ -55,11 +78,11 @@
      *
      * @param gpu                   The GrGpu used to create the buffers.
      * @param bufferType            The type of buffers to create.
-     * @param initialBuffer         If non-null this should be a kDefaultBufferSize byte allocation.
-     *                              This parameter can be used to avoid malloc/free when all
-     *                              usages can be satisfied with default-sized buffers.
+     * @param cpuBufferCache        If non-null a cache for client side array buffers
+     *                              or staging buffers used before data is uploaded to
+     *                              GPU buffer objects.
      */
-    GrBufferAllocPool(GrGpu* gpu, GrGpuBufferType bufferType, void* initialBuffer);
+    GrBufferAllocPool(GrGpu* gpu, GrGpuBufferType bufferType, sk_sp<CpuBufferCache> cpuBufferCache);
 
     virtual ~GrBufferAllocPool();
 
@@ -128,18 +151,17 @@
     void destroyBlock();
     void deleteBlocks();
     void flushCpuData(const BufferBlock& block, size_t flushSize);
-    void* resetCpuData(size_t newSize);
+    void resetCpuData(size_t newSize);
 #ifdef SK_DEBUG
     void validate(bool unusedBlockAllowed = false) const;
 #endif
     size_t fBytesInUse = 0;
 
     SkTArray<BufferBlock> fBlocks;
+    sk_sp<CpuBufferCache> fCpuBufferCache;
+    sk_sp<GrCpuBuffer> fCpuStagingBuffer;
     GrGpu* fGpu;
     GrGpuBufferType fBufferType;
-    void* fInitialCpuData = nullptr;
-    void* fCpuData = nullptr;
-    size_t fCpuDataSize = 0;
     void* fBufferPtr = nullptr;
 };
 
@@ -152,11 +174,11 @@
      * Constructor
      *
      * @param gpu                   The GrGpu used to create the vertex buffers.
-     * @param initialBuffer         If non-null this should be a kDefaultBufferSize byte allocation.
-     *                              This parameter can be used to avoid malloc/free when all
-     *                              usages can be satisfied with default-sized buffers.
+     * @param cpuBufferCache        If non-null a cache for client side array buffers
+     *                              or staging buffers used before data is uploaded to
+     *                              GPU buffer objects.
      */
-    GrVertexBufferAllocPool(GrGpu* gpu, void* initialBuffer);
+    GrVertexBufferAllocPool(GrGpu* gpu, sk_sp<CpuBufferCache> cpuBufferCache);
 
     /**
      * Returns a block of memory to hold vertices. A buffer designated to hold
@@ -231,11 +253,11 @@
      * Constructor
      *
      * @param gpu                   The GrGpu used to create the index buffers.
-     * @param initialBuffer         If non-null this should be a kDefaultBufferSize byte allocation.
-     *                              This parameter can be used to avoid malloc/free when all
-     *                              usages can be satisfied with default-sized buffers.
+     * @param cpuBufferCache        If non-null a cache for client side array buffers
+     *                              or staging buffers used before data is uploaded to
+     *                              GPU buffer objects.
      */
-    GrIndexBufferAllocPool(GrGpu* gpu, void* initialBuffer);
+    GrIndexBufferAllocPool(GrGpu* gpu, sk_sp<CpuBufferCache> cpuBufferCache);
 
     /**
      * Returns a block of memory to hold indices. A buffer designated to hold
diff --git a/src/gpu/GrCaps.cpp b/src/gpu/GrCaps.cpp
index 638a70f..4624171 100644
--- a/src/gpu/GrCaps.cpp
+++ b/src/gpu/GrCaps.cpp
@@ -33,6 +33,7 @@
     fPreferClientSideDynamicBuffers = false;
     fPreferFullscreenClears = false;
     fMustClearUploadedBufferData = false;
+    fShouldInitializeTextures = false;
     fSupportsAHardwareBufferImages = false;
     fFenceSyncSupport = false;
     fCrossContextTextureSupport = false;
@@ -41,6 +42,9 @@
     fPerformPartialClearsAsDraws = false;
     fPerformColorClearsAsDraws = false;
     fPerformStencilClearsAsDraws = false;
+    fAllowCoverageCounting = false;
+    fTransferBufferSupport = false;
+    fDriverBlacklistCCPR = false;
 
     fBlendEquationSupport = kBasic_BlendEquationSupport;
     fAdvBlendEqBlacklist = 0;
@@ -51,7 +55,6 @@
     fMaxRenderTargetSize = 1;
     fMaxPreferredRenderTargetSize = 1;
     fMaxTextureSize = 1;
-    fMaxRasterSamples = 0;
     fMaxWindowRectangles = 0;
 
     // An default count of 4 was chosen because of the common pattern in Blink of:
@@ -69,12 +72,13 @@
     fWireframeMode = false;
 #endif
     fBufferMapThreshold = options.fBufferMapThreshold;
-    fBlacklistCoverageCounting = false;
     fAvoidStencilBuffers = false;
     fAvoidWritePixelsFastPath = false;
 
     fPreferVRAMUseOverFlushes = true;
 
+    fPreferTrianglesOverSampleMask = false;
+
     // Default to true, allow older versions of OpenGL to disable explicitly
     fClampToBorderSupport = true;
 
@@ -84,9 +88,7 @@
 void GrCaps::applyOptionsOverrides(const GrContextOptions& options) {
     this->onApplyOptionsOverrides(options);
     if (options.fDisableDriverCorrectnessWorkarounds) {
-        // We always blacklist coverage counting on Vulkan currently. TODO: Either stop doing that
-        // or disambiguate blacklisting from incomplete implementation.
-        // SkASSERT(!fBlacklistCoverageCounting);
+        SkASSERT(!fDriverBlacklistCCPR);
         SkASSERT(!fAvoidStencilBuffers);
         SkASSERT(!fAdvBlendEqBlacklist);
         SkASSERT(!fPerformColorClearsAsDraws);
@@ -102,6 +104,8 @@
         fPerformStencilClearsAsDraws = true;
     }
 
+    fAllowCoverageCounting = !options.fDisableCoverageCountingPaths;
+
     fMaxTextureSize = SkTMin(fMaxTextureSize, options.fMaxTextureSizeOverride);
     fMaxTileSize = fMaxTextureSize;
 #if GR_TEST_UTILS
@@ -138,6 +142,7 @@
         case kRGBA_4444_GrPixelConfig: return "RGBA444";
         case kRGBA_8888_GrPixelConfig: return "RGBA8888";
         case kRGB_888_GrPixelConfig: return "RGB888";
+        case kRGB_888X_GrPixelConfig: return "RGB888X";
         case kRG_88_GrPixelConfig: return "RG88";
         case kBGRA_8888_GrPixelConfig: return "BGRA8888";
         case kSRGBA_8888_GrPixelConfig: return "SRGBA8888";
@@ -148,6 +153,7 @@
         case kAlpha_half_GrPixelConfig: return "AlphaHalf";
         case kAlpha_half_as_Red_GrPixelConfig: return "AlphaHalf_asRed";
         case kRGBA_half_GrPixelConfig: return "RGBAHalf";
+        case kRGBA_half_Clamped_GrPixelConfig: return "RGBAHalfClamped";
         case kRGB_ETC1_GrPixelConfig: return "RGBETC1";
     }
     SK_ABORT("Invalid pixel config");
@@ -195,6 +201,7 @@
     writer->appendBool("Prefer client-side dynamic buffers", fPreferClientSideDynamicBuffers);
     writer->appendBool("Prefer fullscreen clears", fPreferFullscreenClears);
     writer->appendBool("Must clear buffer memory", fMustClearUploadedBufferData);
+    writer->appendBool("Should initialize textures", fShouldInitializeTextures);
     writer->appendBool("Supports importing AHardwareBuffers", fSupportsAHardwareBufferImages);
     writer->appendBool("Fence sync support", fFenceSyncSupport);
     writer->appendBool("Cross context texture support", fCrossContextTextureSupport);
@@ -204,11 +211,14 @@
     writer->appendBool("Use draws for partial clears", fPerformPartialClearsAsDraws);
     writer->appendBool("Use draws for color clears", fPerformColorClearsAsDraws);
     writer->appendBool("Use draws for stencil clip clears", fPerformStencilClearsAsDraws);
+    writer->appendBool("Allow coverage counting shortcuts", fAllowCoverageCounting);
+    writer->appendBool("Supports transfer buffers", fTransferBufferSupport);
+    writer->appendBool("Blacklist CCPR on current driver [workaround]", fDriverBlacklistCCPR);
     writer->appendBool("Clamp-to-border", fClampToBorderSupport);
 
-    writer->appendBool("Blacklist Coverage Counting Path Renderer [workaround]",
-                       fBlacklistCoverageCounting);
     writer->appendBool("Prefer VRAM Use over flushes [workaround]", fPreferVRAMUseOverFlushes);
+    writer->appendBool("Prefer more triangles over sample mask [MSAA only]",
+                       fPreferTrianglesOverSampleMask);
     writer->appendBool("Avoid stencil buffers [workaround]", fAvoidStencilBuffers);
 
     if (this->advancedBlendEquationSupport()) {
@@ -219,7 +229,6 @@
     writer->appendS32("Max Texture Size", fMaxTextureSize);
     writer->appendS32("Max Render Target Size", fMaxRenderTargetSize);
     writer->appendS32("Max Preferred Render Target Size", fMaxPreferredRenderTargetSize);
-    writer->appendS32("Max Raster Samples", fMaxRasterSamples);
     writer->appendS32("Max Window Rectangles", fMaxWindowRectangles);
     writer->appendS32("Max Clip Analytic Fragment Processors", fMaxClipAnalyticFPs);
 
@@ -268,6 +277,15 @@
     return surface->readOnly() ? false : this->onSurfaceSupportsWritePixels(surface);
 }
 
+bool GrCaps::transferFromBufferRequirements(GrColorType bufferColorType, int width,
+                                            size_t* rowBytes, size_t* offsetAlignment) const {
+    if (!this->transferBufferSupport()) {
+        return false;
+    }
+    return this->onTransferFromBufferRequirements(bufferColorType, width, rowBytes,
+                                                  offsetAlignment);
+}
+
 bool GrCaps::canCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
                             const SkIRect& srcRect, const SkIPoint& dstPoint) const {
     return dst->readOnly() ? false : this->onCanCopySurface(dst, src, srcRect, dstPoint);
@@ -311,3 +329,12 @@
 GrBackendFormat GrCaps::getBackendFormatFromColorType(SkColorType ct) const {
     return this->getBackendFormatFromGrColorType(SkColorTypeToGrColorType(ct), GrSRGBEncoded::kNo);
 }
+
+bool GrCaps::onTransferFromBufferRequirements(GrColorType bufferColorType, int width,
+                                              size_t* rowBytes, size_t* offsetAlignment) const {
+    // TODO(bsalomon): Provide backend-specific overrides of this the return the true requirements.
+    // Currently assuming tight row bytes rounded up to a multiple of 4 and 4 byte offset alignment.
+    *rowBytes = GrSizeAlignUp(GrColorTypeBytesPerPixel(bufferColorType) * width, 4);
+    *offsetAlignment = 4;
+    return true;
+}
diff --git a/src/gpu/GrCaps.h b/src/gpu/GrCaps.h
index 76eae0f..4045e5f 100644
--- a/src/gpu/GrCaps.h
+++ b/src/gpu/GrCaps.h
@@ -73,7 +73,7 @@
 
     bool preferVRAMUseOverFlushes() const { return fPreferVRAMUseOverFlushes; }
 
-    bool blacklistCoverageCounting() const { return fBlacklistCoverageCounting; }
+    bool preferTrianglesOverSampleMask() const { return fPreferTrianglesOverSampleMask; }
 
     bool avoidStencilBuffers() const { return fAvoidStencilBuffers; }
 
@@ -148,8 +148,6 @@
         return fMaxTileSize;
     }
 
-    int maxRasterSamples() const { return fMaxRasterSamples; }
-
     int maxWindowRectangles() const { return fMaxWindowRectangles; }
 
     // Returns whether mixed samples is supported for the given backend render target.
@@ -222,6 +220,26 @@
         return GrPixelConfigToColorType(config);
     }
 
+    /** Are transfer buffers (to textures and from surfaces) supported? */
+    bool transferBufferSupport() const { return fTransferBufferSupport; }
+
+    /**
+     * Gets the requirements to use GrGpu::transferPixelsFrom for a given GrColorType. To check
+     * whether a pixels as GrColorType can be read for a given surface see
+     * supportedReadPixelsColorType() and surfaceSupportsReadPixels().
+     *
+     * @param bufferColorType The color type of the pixel data that will be stored in the transfer
+     *                        buffer.
+     * @param width  The number of color values per row that will be stored in the buffer.
+     * @param rowBytes The number of bytes per row that will be used in the transfer buffer.
+     * @param alignment The required alignment of the offset into the transfer buffer passed
+     *                         to GrGpu::transferPixelsFrom.
+     * @return true if transferPixelFrom is supported, false otherwise. If false then rowBytes and
+     * alignment are not updated.
+     */
+    bool transferFromBufferRequirements(GrColorType bufferColorType, int width, size_t* rowBytes,
+                                        size_t* offsetAlignment) const;
+
     bool suppressPrints() const { return fSuppressPrints; }
 
     size_t bufferMapThreshold() const {
@@ -233,6 +251,13 @@
         is not initialized (even if not read by draw calls). */
     bool mustClearUploadedBufferData() const { return fMustClearUploadedBufferData; }
 
+    /** For some environments, there is a performance or safety concern to not
+        initializing textures. For example, with WebGL and Firefox, there is a large
+        performance hit to not doing it.
+     */
+    bool shouldInitializeTextures() const { return fShouldInitializeTextures; }
+
+
     /** Returns true if the given backend supports importing AHardwareBuffers via the
      * GrAHardwarebufferImageGenerator. This will only ever be supported on Android devices with API
      * level >= 26.
@@ -260,16 +285,19 @@
     }
 
     // Many drivers have issues with color clears.
-    bool performColorClearsAsDraws() const {
-        return fPerformColorClearsAsDraws;
-    }
+    bool performColorClearsAsDraws() const { return fPerformColorClearsAsDraws; }
 
     /// Adreno 4xx devices experience an issue when there are a large number of stencil clip bit
     /// clears. The minimal repro steps are not precisely known but drawing a rect with a stencil
     /// op instead of using glClear seems to resolve the issue.
-    bool performStencilClearsAsDraws() const {
-        return fPerformStencilClearsAsDraws;
-    }
+    bool performStencilClearsAsDraws() const { return fPerformStencilClearsAsDraws; }
+
+    // Can we use coverage counting shortcuts to render paths? Coverage counting can cause artifacts
+    // along shared edges if care isn't taken to ensure both contours wind in the same direction.
+    bool allowCoverageCounting() const { return fAllowCoverageCounting; }
+
+    // Should we disable the CCPR code due to a faulty driver?
+    bool driverBlacklistCCPR() const { return fDriverBlacklistCCPR; }
 
     /**
      * This is can be called before allocating a texture to be a dst for copySurface. This is only
@@ -345,21 +373,27 @@
     bool fPreferClientSideDynamicBuffers             : 1;
     bool fPreferFullscreenClears                     : 1;
     bool fMustClearUploadedBufferData                : 1;
+    bool fShouldInitializeTextures                   : 1;
     bool fSupportsAHardwareBufferImages              : 1;
     bool fHalfFloatVertexAttributeSupport            : 1;
     bool fClampToBorderSupport                       : 1;
     bool fPerformPartialClearsAsDraws                : 1;
     bool fPerformColorClearsAsDraws                  : 1;
     bool fPerformStencilClearsAsDraws                : 1;
+    bool fAllowCoverageCounting                      : 1;
+    bool fTransferBufferSupport                      : 1;
 
     // Driver workaround
-    bool fBlacklistCoverageCounting                  : 1;
+    bool fDriverBlacklistCCPR                        : 1;
     bool fAvoidStencilBuffers                        : 1;
     bool fAvoidWritePixelsFastPath                   : 1;
 
     // ANGLE performance workaround
     bool fPreferVRAMUseOverFlushes                   : 1;
 
+    // On some platforms it's better to make more triangles than to use the sample mask (MSAA only).
+    bool fPreferTrianglesOverSampleMask              : 1;
+
     // TODO: this may need to be an enum to support different fence types
     bool fFenceSyncSupport                           : 1;
 
@@ -381,7 +415,6 @@
     int fMaxVertexAttributes;
     int fMaxTextureSize;
     int fMaxTileSize;
-    int fMaxRasterSamples;
     int fMaxWindowRectangles;
     int fMaxClipAnalyticFPs;
 
@@ -393,6 +426,8 @@
     virtual bool onSurfaceSupportsWritePixels(const GrSurface*) const = 0;
     virtual bool onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
                                   const SkIRect& srcRect, const SkIPoint& dstPoint) const = 0;
+    virtual bool onTransferFromBufferRequirements(GrColorType bufferColorType, int width,
+                                                  size_t* rowBytes, size_t* offsetAlignment) const;
 
     // Backends should implement this if they have any extra requirements for use of window
     // rectangles for a specific GrBackendRenderTarget outside of basic support.
diff --git a/src/gpu/GrClip.h b/src/gpu/GrClip.h
index 7358e67..eb7d95b 100644
--- a/src/gpu/GrClip.h
+++ b/src/gpu/GrClip.h
@@ -35,7 +35,7 @@
      * conservative bounds of the clip. A return value of false indicates that the draw can be
      * skipped as it is fully clipped out.
      */
-    virtual bool apply(GrContext*, GrRenderTargetContext*, bool useHWAA,
+    virtual bool apply(GrRecordingContext*, GrRenderTargetContext*, bool useHWAA,
                        bool hasUserStencilSettings, GrAppliedClip*, SkRect* bounds) const = 0;
 
     virtual ~GrClip() {}
@@ -147,8 +147,8 @@
     virtual bool apply(int rtWidth, int rtHeight, GrAppliedHardClip* out, SkRect* bounds) const = 0;
 
 private:
-    bool apply(GrContext*, GrRenderTargetContext* rtc, bool useHWAA, bool hasUserStencilSettings,
-               GrAppliedClip* out, SkRect* bounds) const final {
+    bool apply(GrRecordingContext*, GrRenderTargetContext* rtc, bool useHWAA,
+               bool hasUserStencilSettings, GrAppliedClip* out, SkRect* bounds) const final {
         return this->apply(rtc->width(), rtc->height(), &out->hardClip(), bounds);
     }
 };
diff --git a/src/gpu/GrClipStackClip.cpp b/src/gpu/GrClipStackClip.cpp
index c2209d9..dd31406 100644
--- a/src/gpu/GrClipStackClip.cpp
+++ b/src/gpu/GrClipStackClip.cpp
@@ -13,6 +13,7 @@
 #include "GrFixedClip.h"
 #include "GrGpuResourcePriv.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContextPriv.h"
 #include "GrSWMaskHelper.h"
 #include "GrShape.h"
@@ -87,7 +88,7 @@
 // Does the path in 'element' require SW rendering? If so, return true (and,
 // optionally, set 'prOut' to NULL. If not, return false (and, optionally, set
 // 'prOut' to the non-SW path renderer that will do the job).
-bool GrClipStackClip::PathNeedsSWRenderer(GrContext* context,
+bool GrClipStackClip::PathNeedsSWRenderer(GrRecordingContext* context,
                                           const SkIRect& scissorRect,
                                           bool hasUserStencilSettings,
                                           const GrRenderTargetContext* renderTargetContext,
@@ -113,6 +114,12 @@
             path.toggleInverseFillType();
         }
 
+        // We only use this method when rendering coverage clip masks.
+        SkASSERT(GrFSAAType::kNone == renderTargetContext->fsaaType());
+        auto aaTypeFlags = (element->isAA())
+                ? GrPathRenderer::AATypeFlags::kCoverage
+                : GrPathRenderer::AATypeFlags::kNone;
+
         GrPathRendererChain::DrawType type =
                 needsStencil ? GrPathRendererChain::DrawType::kStencilAndColor
                              : GrPathRendererChain::DrawType::kColor;
@@ -123,10 +130,7 @@
         canDrawArgs.fClipConservativeBounds = &scissorRect;
         canDrawArgs.fViewMatrix = &viewMatrix;
         canDrawArgs.fShape = &shape;
-        canDrawArgs.fAAType = GrChooseAAType(GrAA(element->isAA()),
-                                             renderTargetContext->fsaaType(),
-                                             GrAllowMixedSamples::kYes,
-                                             *context->priv().caps());
+        canDrawArgs.fAATypeFlags = aaTypeFlags;
         SkASSERT(!renderTargetContext->wrapsVkSecondaryCB());
         canDrawArgs.fTargetIsWrappedVkSecondaryCB = false;
         canDrawArgs.fHasUserStencilSettings = hasUserStencilSettings;
@@ -146,7 +150,7 @@
  * will be used on any element. If so, it returns true to indicate that the
  * entire clip should be rendered in SW and then uploaded en masse to the gpu.
  */
-bool GrClipStackClip::UseSWOnlyPath(GrContext* context,
+bool GrClipStackClip::UseSWOnlyPath(GrRecordingContext* context,
                                     bool hasUserStencilSettings,
                                     const GrRenderTargetContext* renderTargetContext,
                                     const GrReducedClip& reducedClip) {
@@ -189,7 +193,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 // sort out what kind of clip mask needs to be created: alpha, stencil,
 // scissor, or entirely software
-bool GrClipStackClip::apply(GrContext* context, GrRenderTargetContext* renderTargetContext,
+bool GrClipStackClip::apply(GrRecordingContext* context, GrRenderTargetContext* renderTargetContext,
                             bool useHWAA, bool hasUserStencilSettings, GrAppliedClip* out,
                             SkRect* bounds) const {
     SkRect devBounds = SkRect::MakeIWH(renderTargetContext->width(), renderTargetContext->height());
@@ -249,7 +253,8 @@
     return true;
 }
 
-bool GrClipStackClip::applyClipMask(GrContext* context, GrRenderTargetContext* renderTargetContext,
+bool GrClipStackClip::applyClipMask(GrRecordingContext* context,
+                                    GrRenderTargetContext* renderTargetContext,
                                     const GrReducedClip& reducedClip, bool hasUserStencilSettings,
                                     GrAppliedClip* out) const {
 #ifdef SK_DEBUG
@@ -322,7 +327,7 @@
     builder[3] = numAnalyticFPs;
 }
 
-static void add_invalidate_on_pop_message(GrContext* context,
+static void add_invalidate_on_pop_message(GrRecordingContext* context,
                                           const SkClipStack& stack, uint32_t clipGenID,
                                           const GrUniqueKey& clipMaskKey) {
     GrProxyProvider* proxyProvider = context->priv().proxyProvider();
@@ -337,7 +342,7 @@
     SkDEBUGFAIL("Gen ID was not found in stack.");
 }
 
-sk_sp<GrTextureProxy> GrClipStackClip::createAlphaClipMask(GrContext* context,
+sk_sp<GrTextureProxy> GrClipStackClip::createAlphaClipMask(GrRecordingContext* context,
                                                            const GrReducedClip& reducedClip) const {
     GrProxyProvider* proxyProvider = context->priv().proxyProvider();
     GrUniqueKey key;
@@ -457,7 +462,7 @@
 }
 
 sk_sp<GrTextureProxy> GrClipStackClip::createSoftwareClipMask(
-        GrContext* context, const GrReducedClip& reducedClip,
+        GrRecordingContext* context, const GrReducedClip& reducedClip,
         GrRenderTargetContext* renderTargetContext) const {
     GrUniqueKey key;
     create_clip_mask_key(reducedClip.maskGenID(), reducedClip.scissor(),
@@ -475,7 +480,11 @@
     // left corner of the resulting rect to the top left of the texture.
     SkIRect maskSpaceIBounds = SkIRect::MakeWH(reducedClip.width(), reducedClip.height());
 
-    SkTaskGroup* taskGroup = context->priv().getTaskGroup();
+    SkTaskGroup* taskGroup = nullptr;
+    if (auto direct = context->priv().asDirectContext()) {
+        taskGroup = direct->priv().getTaskGroup();
+    }
+
     if (taskGroup && renderTargetContext) {
         // Create our texture proxy
         GrSurfaceDesc desc;
diff --git a/src/gpu/GrClipStackClip.h b/src/gpu/GrClipStackClip.h
index aeb9834..6f4b2b1 100644
--- a/src/gpu/GrClipStackClip.h
+++ b/src/gpu/GrClipStackClip.h
@@ -28,8 +28,8 @@
     bool quickContains(const SkRRect&) const final;
     void getConservativeBounds(int width, int height, SkIRect* devResult,
                                bool* isIntersectionOfRects) const final;
-    bool apply(GrContext*, GrRenderTargetContext*, bool useHWAA, bool hasUserStencilSettings,
-               GrAppliedClip* out, SkRect* bounds) const final;
+    bool apply(GrRecordingContext*, GrRenderTargetContext*, bool useHWAA,
+               bool hasUserStencilSettings, GrAppliedClip* out, SkRect* bounds) const final;
 
     bool isRRect(const SkRect& rtBounds, SkRRect* rr, GrAA* aa) const override;
 
@@ -37,7 +37,7 @@
     static const char kMaskTestTag[];
 
 private:
-    static bool PathNeedsSWRenderer(GrContext* context,
+    static bool PathNeedsSWRenderer(GrRecordingContext* context,
                                     const SkIRect& scissorRect,
                                     bool hasUserStencilSettings,
                                     const GrRenderTargetContext*,
@@ -46,18 +46,18 @@
                                     GrPathRenderer** prOut,
                                     bool needsStencil);
 
-    bool applyClipMask(GrContext*, GrRenderTargetContext*, const GrReducedClip&,
+    bool applyClipMask(GrRecordingContext*, GrRenderTargetContext*, const GrReducedClip&,
                        bool hasUserStencilSettings, GrAppliedClip*) const;
 
     // Creates an alpha mask of the clip. The mask is a rasterization of elements through the
     // rect specified by clipSpaceIBounds.
-    sk_sp<GrTextureProxy> createAlphaClipMask(GrContext*, const GrReducedClip&) const;
+    sk_sp<GrTextureProxy> createAlphaClipMask(GrRecordingContext*, const GrReducedClip&) const;
 
     // Similar to createAlphaClipMask but it rasterizes in SW and uploads to the result texture.
-    sk_sp<GrTextureProxy> createSoftwareClipMask(GrContext*, const GrReducedClip&,
+    sk_sp<GrTextureProxy> createSoftwareClipMask(GrRecordingContext*, const GrReducedClip&,
                                                  GrRenderTargetContext*) const;
 
-    static bool UseSWOnlyPath(GrContext*,
+    static bool UseSWOnlyPath(GrRecordingContext*,
                               bool hasUserStencilSettings,
                               const GrRenderTargetContext*,
                               const GrReducedClip&);
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 9bf044d..075ac00 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -25,7 +25,7 @@
 #include "SkSurface_Gpu.h"
 #include "SkTaskGroup.h"
 #include "SkTraceMemoryDump.h"
-#include "effects/GrConfigConversionEffect.h"
+#include "effects/generated/GrConfigConversionEffect.h"
 #include "effects/GrSkSLFP.h"
 #include "ccpr/GrCoverageCountingPathRenderer.h"
 #include "text/GrTextBlobCache.h"
@@ -38,89 +38,54 @@
 
 #define ASSERT_OWNED_RESOURCE(R) SkASSERT(!(R) || (R)->getContext() == this)
 #define ASSERT_SINGLE_OWNER \
-    SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(&fSingleOwner);)
-#define RETURN_IF_ABANDONED if (fDrawingManager->wasAbandoned()) { return; }
-#define RETURN_FALSE_IF_ABANDONED if (fDrawingManager->wasAbandoned()) { return false; }
-#define RETURN_NULL_IF_ABANDONED if (fDrawingManager->wasAbandoned()) { return nullptr; }
+    SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(this->singleOwner());)
+#define RETURN_IF_ABANDONED if (this->abandoned()) { return; }
+#define RETURN_FALSE_IF_ABANDONED if (this->abandoned()) { return false; }
+#define RETURN_NULL_IF_ABANDONED if (this->abandoned()) { return nullptr; }
 
 ////////////////////////////////////////////////////////////////////////////////
 
-GrContext::GrContext(GrBackendApi backend, const GrContextOptions& options, int32_t id)
-        : INHERITED(backend, options, id) {
+GrContext::GrContext(GrBackendApi backend, const GrContextOptions& options, int32_t contextID)
+        : INHERITED(backend, options, contextID) {
     fResourceCache = nullptr;
     fResourceProvider = nullptr;
-    fProxyProvider = nullptr;
-    fGlyphCache = nullptr;
+}
+
+GrContext::~GrContext() {
+    ASSERT_SINGLE_OWNER
+
+    if (this->drawingManager()) {
+        this->drawingManager()->cleanup();
+    }
+    delete fResourceProvider;
+    delete fResourceCache;
 }
 
 bool GrContext::init(sk_sp<const GrCaps> caps, sk_sp<GrSkSLFPFactoryCache> FPFactoryCache) {
     ASSERT_SINGLE_OWNER
     SkASSERT(fThreadSafeProxy); // needs to have been initialized by derived classes
+    SkASSERT(this->proxyProvider());
 
     if (!INHERITED::init(std::move(caps), std::move(FPFactoryCache))) {
         return false;
     }
 
     SkASSERT(this->caps());
+    SkASSERT(this->getGrStrikeCache());
+    SkASSERT(this->getTextBlobCache());
 
     if (fGpu) {
-        fResourceCache = new GrResourceCache(this->caps(), &fSingleOwner, this->contextID());
-        fResourceProvider = new GrResourceProvider(fGpu.get(), fResourceCache, &fSingleOwner,
-                                                   this->options().fExplicitlyAllocateGPUResources);
-        fProxyProvider = new GrProxyProvider(fResourceProvider, fResourceCache,
-                                             this->refCaps(), &fSingleOwner);
-    } else {
-        fProxyProvider = new GrProxyProvider(this->contextID(), this->refCaps(), &fSingleOwner);
+        fResourceCache = new GrResourceCache(this->caps(), this->singleOwner(), this->contextID());
+        fResourceProvider = new GrResourceProvider(fGpu.get(), fResourceCache, this->singleOwner(),
+                                                   this->explicitlyAllocateGPUResources());
     }
 
     if (fResourceCache) {
-        fResourceCache->setProxyProvider(fProxyProvider);
+        fResourceCache->setProxyProvider(this->proxyProvider());
     }
 
     fDidTestPMConversions = false;
 
-    GrPathRendererChain::Options prcOptions;
-    prcOptions.fAllowPathMaskCaching = this->options().fAllowPathMaskCaching;
-#if GR_TEST_UTILS
-    prcOptions.fGpuPathRenderers = this->options().fGpuPathRenderers;
-#endif
-    if (this->options().fDisableCoverageCountingPaths) {
-        prcOptions.fGpuPathRenderers &= ~GpuPathRenderers::kCoverageCounting;
-    }
-    if (this->options().fDisableDistanceFieldPaths) {
-        prcOptions.fGpuPathRenderers &= ~GpuPathRenderers::kSmall;
-    }
-
-    if (!fResourceCache) {
-        // DDL TODO: remove this crippling of the path renderer chain
-        // Disable the small path renderer bc of the proxies in the atlas. They need to be
-        // unified when the opLists are added back to the destination drawing manager.
-        prcOptions.fGpuPathRenderers &= ~GpuPathRenderers::kSmall;
-        prcOptions.fGpuPathRenderers &= ~GpuPathRenderers::kStencilAndCover;
-    }
-
-    GrTextContext::Options textContextOptions;
-    textContextOptions.fMaxDistanceFieldFontSize = this->options().fGlyphsAsPathsFontSize;
-    textContextOptions.fMinDistanceFieldFontSize = this->options().fMinDistanceFieldFontSize;
-    textContextOptions.fDistanceFieldVerticesAlwaysHaveW = false;
-#if SK_SUPPORT_ATLAS_TEXT
-    if (GrContextOptions::Enable::kYes == this->options().fDistanceFieldGlyphVerticesAlwaysHaveW) {
-        textContextOptions.fDistanceFieldVerticesAlwaysHaveW = true;
-    }
-#endif
-
-    bool explicitlyAllocatingResources = fResourceProvider
-                                            ? fResourceProvider->explicitlyAllocateGPUResources()
-                                            : false;
-    fDrawingManager.reset(new GrDrawingManager(this, prcOptions, textContextOptions,
-                                               &fSingleOwner, explicitlyAllocatingResources,
-                                               this->options().fSortRenderTargets,
-                                               this->options().fReduceOpListSplitting));
-
-    fGlyphCache = new GrStrikeCache(this->caps(), this->options().fGlyphCacheTextureMaximumBytes);
-
-    fTextBlobCache.reset(new GrTextBlobCache(TextBlobCacheOverBudgetCB, this, this->contextID()));
-
     // DDL TODO: we need to think through how the task group & persistent cache
     // get passed on to/shared between all the DDLRecorders created with this context.
     if (this->options().fExecutor) {
@@ -132,18 +97,6 @@
     return true;
 }
 
-GrContext::~GrContext() {
-    ASSERT_SINGLE_OWNER
-
-    if (fDrawingManager) {
-        fDrawingManager->cleanup();
-    }
-    delete fResourceProvider;
-    delete fResourceCache;
-    delete fProxyProvider;
-    delete fGlyphCache;
-}
-
 sk_sp<GrContextThreadSafeProxy> GrContext::threadSafeProxy() {
     return fThreadSafeProxy;
 }
@@ -151,51 +104,49 @@
 //////////////////////////////////////////////////////////////////////////////
 
 void GrContext::abandonContext() {
-    ASSERT_SINGLE_OWNER
+    if (this->abandoned()) {
+        return;
+    }
 
-    fProxyProvider->abandon();
+    INHERITED::abandonContext();
+
     fResourceProvider->abandon();
 
-    // Need to abandon the drawing manager first so all the render targets
+    // Need to cleanup the drawing manager first so all the render targets
     // will be released/forgotten before they too are abandoned.
-    fDrawingManager->abandon();
+    this->drawingManager()->cleanup();
 
     // abandon first to so destructors
     // don't try to free the resources in the API.
     fResourceCache->abandonAll();
 
     fGpu->disconnect(GrGpu::DisconnectType::kAbandon);
-
-    fGlyphCache->freeAll();
-    fTextBlobCache->freeAll();
-}
-
-bool GrContext::abandoned() const {
-    ASSERT_SINGLE_OWNER
-    // If called from ~GrContext(), the drawing manager may already be gone.
-    return !fDrawingManager || fDrawingManager->wasAbandoned();
 }
 
 void GrContext::releaseResourcesAndAbandonContext() {
-    ASSERT_SINGLE_OWNER
-
     if (this->abandoned()) {
         return;
     }
-    fProxyProvider->abandon();
+
+    INHERITED::abandonContext();
+
     fResourceProvider->abandon();
 
-    // Need to abandon the drawing manager first so all the render targets
+    // Need to cleanup the drawing manager first so all the render targets
     // will be released/forgotten before they too are abandoned.
-    fDrawingManager->abandon();
+    this->drawingManager()->cleanup();
 
     // Release all resources in the backend 3D API.
     fResourceCache->releaseAll();
 
     fGpu->disconnect(GrGpu::DisconnectType::kCleanup);
+}
 
-    fGlyphCache->freeAll();
-    fTextBlobCache->freeAll();
+void GrContext::resetGLTextureBindings() {
+    if (this->abandoned() || this->backend() != GrBackendApi::kOpenGL) {
+        return;
+    }
+    fGpu->resetTextureBindings();
 }
 
 void GrContext::resetContext(uint32_t state) {
@@ -206,9 +157,11 @@
 void GrContext::freeGpuResources() {
     ASSERT_SINGLE_OWNER
 
-    fGlyphCache->freeAll();
+    // TODO: the glyph cache doesn't hold any GpuResources so this call should not be needed here.
+    // Some slack in the GrTextBlob's implementation requires it though. That could be fixed.
+    this->getGrStrikeCache()->freeAll();
 
-    fDrawingManager->freeGpuResources();
+    this->drawingManager()->freeGpuResources();
 
     fResourceCache->purgeAllUnlocked();
 }
@@ -217,7 +170,10 @@
     ASSERT_SINGLE_OWNER
     fResourceCache->purgeUnlockedResources(scratchResourcesOnly);
     fResourceCache->purgeAsNeeded();
-    fTextBlobCache->purgeStaleBlobs();
+
+    // The textBlob Cache doesn't actually hold any GPU resource but this is a convenient
+    // place to purge stale blobs
+    this->getTextBlobCache()->purgeStaleBlobs();
 }
 
 void GrContext::performDeferredCleanup(std::chrono::milliseconds msNotUsed) {
@@ -228,11 +184,13 @@
     fResourceCache->purgeAsNeeded();
     fResourceCache->purgeResourcesNotUsedSince(purgeTime);
 
-    if (auto ccpr = fDrawingManager->getCoverageCountingPathRenderer()) {
-        ccpr->purgeCacheEntriesOlderThan(fProxyProvider, purgeTime);
+    if (auto ccpr = this->drawingManager()->getCoverageCountingPathRenderer()) {
+        ccpr->purgeCacheEntriesOlderThan(this->proxyProvider(), purgeTime);
     }
 
-    fTextBlobCache->purgeStaleBlobs();
+    // The textBlob Cache doesn't actually hold any GPU resource but this is a convenient
+    // place to purge stale blobs
+    this->getTextBlobCache()->purgeStaleBlobs();
 }
 
 void GrContext::purgeUnlockedResources(size_t bytesToPurge, bool preferScratchResources) {
@@ -256,6 +214,13 @@
     return fResourceCache->getPurgeableBytes();
 }
 
+size_t GrContext::ComputeTextureSize(SkColorType type, int width, int height, GrMipMapped mipMapped,
+                                     bool useNextPow2) {
+    int colorSamplesPerPixel = 1;
+    return GrSurface::ComputeSize(SkColorType2GrPixelConfig(type), width, height,
+                                  colorSamplesPerPixel, mipMapped, useNextPow2);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 int GrContext::maxTextureSize() const { return this->caps()->maxTextureSize(); }
@@ -274,31 +239,23 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-void GrContext::TextBlobCacheOverBudgetCB(void* data) {
-    SkASSERT(data);
-    // TextBlobs are drawn at the SkGpuDevice level, therefore they cannot rely on
-    // GrRenderTargetContext to perform a necessary flush.  The solution is to move drawText calls
-    // to below the GrContext level, but this is not trivial because they call drawPath on
-    // SkGpuDevice.
-    GrContext* context = reinterpret_cast<GrContext*>(data);
-    context->flush();
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
 void GrContext::flush() {
     ASSERT_SINGLE_OWNER
     RETURN_IF_ABANDONED
 
-    fDrawingManager->flush(nullptr);
+    this->drawingManager()->flush(nullptr, SkSurface::BackendSurfaceAccess::kNoAccess,
+                                  kNone_GrFlushFlags, 0, nullptr);
 }
 
-GrSemaphoresSubmitted GrContext::flushAndSignalSemaphores(int numSemaphores,
-                                                          GrBackendSemaphore signalSemaphores[]) {
+GrSemaphoresSubmitted GrContext::flush(GrFlushFlags flags, int numSemaphores,
+                                       GrBackendSemaphore signalSemaphores[]) {
     ASSERT_SINGLE_OWNER
-    if (fDrawingManager->wasAbandoned()) { return GrSemaphoresSubmitted::kNo; }
+    if (this->abandoned()) {
+        return GrSemaphoresSubmitted::kNo;
+    }
 
-    return fDrawingManager->flush(nullptr, numSemaphores, signalSemaphores);
+    return this->drawingManager()->flush(nullptr, SkSurface::BackendSurfaceAccess::kNoAccess,
+                                         flags, numSemaphores, signalSemaphores);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -371,6 +328,6 @@
     ASSERT_SINGLE_OWNER
     fResourceCache->dumpMemoryStatistics(traceMemoryDump);
     traceMemoryDump->dumpNumericValue("skia/gr_text_blob_cache", "size", "bytes",
-                                      fTextBlobCache->usedBytes());
+                                      this->getTextBlobCache()->usedBytes());
 }
 
diff --git a/src/gpu/GrContextPriv.cpp b/src/gpu/GrContextPriv.cpp
index c12ba76..f545e90 100644
--- a/src/gpu/GrContextPriv.cpp
+++ b/src/gpu/GrContextPriv.cpp
@@ -27,72 +27,77 @@
 #define ASSERT_OWNED_PROXY_PRIV(P) \
     SkASSERT(!(P) || !((P)->peekTexture()) || (P)->peekTexture()->getContext() == fContext)
 #define ASSERT_SINGLE_OWNER_PRIV \
-    SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(&fContext->fSingleOwner);)
-#define RETURN_IF_ABANDONED_PRIV if (fContext->fDrawingManager->wasAbandoned()) { return; }
-#define RETURN_FALSE_IF_ABANDONED_PRIV if (fContext->fDrawingManager->wasAbandoned()) { return false; }
+    SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(fContext->singleOwner());)
+#define RETURN_IF_ABANDONED_PRIV if (fContext->abandoned()) { return; }
+#define RETURN_FALSE_IF_ABANDONED_PRIV if (fContext->abandoned()) { return false; }
+
+sk_sp<const GrCaps> GrContextPriv::refCaps() const {
+    return fContext->refCaps();
+}
 
 sk_sp<GrSkSLFPFactoryCache> GrContextPriv::fpFactoryCache() {
     return fContext->fpFactoryCache();
 }
 
 sk_sp<GrOpMemoryPool> GrContextPriv::refOpMemoryPool() {
-    if (!fContext->fOpMemoryPool) {
-        // DDL TODO: should the size of the memory pool be decreased in DDL mode? CPU-side memory
-        // consumed in DDL mode vs. normal mode for a single skp might be a good metric of wasted
-        // memory.
-        fContext->fOpMemoryPool = sk_sp<GrOpMemoryPool>(new GrOpMemoryPool(16384, 16384));
-    }
-
-    SkASSERT(fContext->fOpMemoryPool);
-    return fContext->fOpMemoryPool;
+    return fContext->refOpMemoryPool();
 }
 
-GrOpMemoryPool* GrContextPriv::opMemoryPool() {
-    return this->refOpMemoryPool().get();
+void GrContextPriv::addOnFlushCallbackObject(GrOnFlushCallbackObject* onFlushCBObject) {
+    fContext->addOnFlushCallbackObject(onFlushCBObject);
 }
 
-sk_sp<GrSurfaceContext> GrContextPriv::makeWrappedSurfaceContext(sk_sp<GrSurfaceProxy> proxy,
-                                                                 sk_sp<SkColorSpace> colorSpace,
-                                                                 const SkSurfaceProps* props) {
-    ASSERT_SINGLE_OWNER_PRIV
-
-    if (proxy->asRenderTargetProxy()) {
-        return this->drawingManager()->makeRenderTargetContext(std::move(proxy),
-                                                               std::move(colorSpace), props);
-    } else {
-        SkASSERT(proxy->asTextureProxy());
-        SkASSERT(!props);
-        return this->drawingManager()->makeTextureContext(std::move(proxy), std::move(colorSpace));
-    }
+sk_sp<GrSurfaceContext> GrContextPriv::makeWrappedSurfaceContext(
+                                                    sk_sp<GrSurfaceProxy> proxy,
+                                                    sk_sp<SkColorSpace> colorSpace,
+                                                    const SkSurfaceProps* props) {
+    return fContext->makeWrappedSurfaceContext(std::move(proxy), std::move(colorSpace), props);
 }
 
-sk_sp<GrSurfaceContext> GrContextPriv::makeDeferredSurfaceContext(const GrBackendFormat& format,
-                                                                  const GrSurfaceDesc& dstDesc,
-                                                                  GrSurfaceOrigin origin,
-                                                                  GrMipMapped mipMapped,
-                                                                  SkBackingFit fit,
-                                                                  SkBudgeted isDstBudgeted,
-                                                                  sk_sp<SkColorSpace> colorSpace,
-                                                                  const SkSurfaceProps* props) {
-    sk_sp<GrTextureProxy> proxy;
-    if (GrMipMapped::kNo == mipMapped) {
-        proxy = this->proxyProvider()->createProxy(format, dstDesc, origin, fit, isDstBudgeted);
-    } else {
-        SkASSERT(SkBackingFit::kExact == fit);
-        proxy = this->proxyProvider()->createMipMapProxy(format, dstDesc, origin, isDstBudgeted);
-    }
-    if (!proxy) {
-        return nullptr;
-    }
+sk_sp<GrSurfaceContext> GrContextPriv::makeDeferredSurfaceContext(
+                                                    const GrBackendFormat& format,
+                                                    const GrSurfaceDesc& dstDesc,
+                                                    GrSurfaceOrigin origin,
+                                                    GrMipMapped mipMapped,
+                                                    SkBackingFit fit,
+                                                    SkBudgeted isDstBudgeted,
+                                                    sk_sp<SkColorSpace> colorSpace,
+                                                    const SkSurfaceProps* props) {
+    return fContext->makeDeferredSurfaceContext(format, dstDesc, origin, mipMapped, fit,
+                                                isDstBudgeted, std::move(colorSpace), props);
+}
 
-    sk_sp<GrSurfaceContext> sContext = this->makeWrappedSurfaceContext(std::move(proxy),
-                                                                       std::move(colorSpace),
-                                                                       props);
-    if (sContext && sContext->asRenderTargetContext()) {
-        sContext->asRenderTargetContext()->discard();
-    }
+sk_sp<GrRenderTargetContext> GrContextPriv::makeDeferredRenderTargetContext(
+                                        const GrBackendFormat& format,
+                                        SkBackingFit fit,
+                                        int width, int height,
+                                        GrPixelConfig config,
+                                        sk_sp<SkColorSpace> colorSpace,
+                                        int sampleCnt,
+                                        GrMipMapped mipMapped,
+                                        GrSurfaceOrigin origin,
+                                        const SkSurfaceProps* surfaceProps,
+                                        SkBudgeted budgeted) {
+    return fContext->makeDeferredRenderTargetContext(format, fit, width, height, config,
+                                                     std::move(colorSpace), sampleCnt, mipMapped,
+                                                     origin, surfaceProps, budgeted);
+}
 
-    return sContext;
+sk_sp<GrRenderTargetContext> GrContextPriv::makeDeferredRenderTargetContextWithFallback(
+                                        const GrBackendFormat& format,
+                                        SkBackingFit fit,
+                                        int width, int height,
+                                        GrPixelConfig config,
+                                        sk_sp<SkColorSpace> colorSpace,
+                                        int sampleCnt,
+                                        GrMipMapped mipMapped,
+                                        GrSurfaceOrigin origin,
+                                        const SkSurfaceProps* surfaceProps,
+                                        SkBudgeted budgeted) {
+    return fContext->makeDeferredRenderTargetContextWithFallback(format, fit, width, height, config,
+                                                                 std::move(colorSpace), sampleCnt,
+                                                                 mipMapped, origin, surfaceProps,
+                                                                 budgeted);
 }
 
 sk_sp<GrTextureContext> GrContextPriv::makeBackendTextureContext(const GrBackendTexture& tex,
@@ -189,31 +194,8 @@
     RETURN_IF_ABANDONED_PRIV
     ASSERT_OWNED_PROXY_PRIV(proxy);
 
-    fContext->fDrawingManager->flush(proxy);
-}
-
-void GrContextPriv::addOnFlushCallbackObject(GrOnFlushCallbackObject* onFlushCBObject) {
-    fContext->fDrawingManager->addOnFlushCallbackObject(onFlushCBObject);
-}
-
-void GrContextPriv::flushSurfaceWrites(GrSurfaceProxy* proxy) {
-    ASSERT_SINGLE_OWNER_PRIV
-    RETURN_IF_ABANDONED_PRIV
-    SkASSERT(proxy);
-    ASSERT_OWNED_PROXY_PRIV(proxy);
-    if (proxy->priv().hasPendingWrite()) {
-        this->flush(proxy);
-    }
-}
-
-void GrContextPriv::flushSurfaceIO(GrSurfaceProxy* proxy) {
-    ASSERT_SINGLE_OWNER_PRIV
-    RETURN_IF_ABANDONED_PRIV
-    SkASSERT(proxy);
-    ASSERT_OWNED_PROXY_PRIV(proxy);
-    if (proxy->priv().hasPendingIO()) {
-        this->flush(proxy);
-    }
+    fContext->drawingManager()->flush(proxy, SkSurface::BackendSurfaceAccess::kNoAccess,
+                                      kNone_GrFlushFlags, 0, nullptr);
 }
 
 void GrContextPriv::prepareSurfaceForExternalIO(GrSurfaceProxy* proxy) {
@@ -221,26 +203,28 @@
     RETURN_IF_ABANDONED_PRIV
     SkASSERT(proxy);
     ASSERT_OWNED_PROXY_PRIV(proxy);
-    fContext->fDrawingManager->prepareSurfaceForExternalIO(proxy, 0, nullptr);
+    fContext->drawingManager()->prepareSurfaceForExternalIO(proxy,
+            SkSurface::BackendSurfaceAccess::kNoAccess, kNone_GrFlushFlags, 0, nullptr);
 }
 
 static bool valid_premul_color_type(GrColorType ct) {
     switch (ct) {
-        case GrColorType::kUnknown:      return false;
-        case GrColorType::kAlpha_8:      return false;
-        case GrColorType::kRGB_565:      return false;
-        case GrColorType::kABGR_4444:    return true;
-        case GrColorType::kRGBA_8888:    return true;
-        case GrColorType::kRGB_888x:     return false;
-        case GrColorType::kRG_88:        return false;
-        case GrColorType::kBGRA_8888:    return true;
-        case GrColorType::kRGBA_1010102: return true;
-        case GrColorType::kGray_8:       return false;
-        case GrColorType::kAlpha_F16:    return false;
-        case GrColorType::kRGBA_F16:     return true;
-        case GrColorType::kRG_F32:       return false;
-        case GrColorType::kRGBA_F32:     return true;
-        case GrColorType::kRGB_ETC1:     return false;
+        case GrColorType::kUnknown:          return false;
+        case GrColorType::kAlpha_8:          return false;
+        case GrColorType::kRGB_565:          return false;
+        case GrColorType::kABGR_4444:        return true;
+        case GrColorType::kRGBA_8888:        return true;
+        case GrColorType::kRGB_888x:         return false;
+        case GrColorType::kRG_88:            return false;
+        case GrColorType::kBGRA_8888:        return true;
+        case GrColorType::kRGBA_1010102:     return true;
+        case GrColorType::kGray_8:           return false;
+        case GrColorType::kAlpha_F16:        return false;
+        case GrColorType::kRGBA_F16:         return true;
+        case GrColorType::kRGBA_F16_Clamped: return true;
+        case GrColorType::kRG_F32:           return false;
+        case GrColorType::kRGBA_F32:         return true;
+        case GrColorType::kRGB_ETC1:         return false;
     }
     SK_ABORT("Invalid GrColorType");
     return false;
@@ -257,6 +241,7 @@
         case kRGBA_4444_GrPixelConfig:          return true;
         case kRGBA_8888_GrPixelConfig:          return true;
         case kRGB_888_GrPixelConfig:            return false;
+        case kRGB_888X_GrPixelConfig:           return false;
         case kRG_88_GrPixelConfig:              return false;
         case kBGRA_8888_GrPixelConfig:          return true;
         case kSRGBA_8888_GrPixelConfig:         return true;
@@ -266,6 +251,7 @@
         case kRG_float_GrPixelConfig:           return false;
         case kAlpha_half_GrPixelConfig:         return false;
         case kRGBA_half_GrPixelConfig:          return true;
+        case kRGBA_half_Clamped_GrPixelConfig:  return true;
         case kRGB_ETC1_GrPixelConfig:           return false;
         case kAlpha_8_as_Alpha_GrPixelConfig:   return false;
         case kAlpha_8_as_Red_GrPixelConfig:     return false;
@@ -298,17 +284,13 @@
     ASSERT_OWNED_PROXY_PRIV(src->asSurfaceProxy());
     GR_CREATE_TRACE_MARKER_CONTEXT("GrContextPriv", "readSurfacePixels", fContext);
 
-    SkASSERT(!(pixelOpsFlags & kDontFlush_PixelOpsFlag));
-    if (pixelOpsFlags & kDontFlush_PixelOpsFlag) {
-        return false;
-    }
+    GrSurfaceProxy* srcProxy = src->asSurfaceProxy();
 
     // MDB TODO: delay this instantiation until later in the method
-    if (!src->asSurfaceProxy()->instantiate(this->resourceProvider())) {
+    if (!srcProxy->instantiate(this->resourceProvider())) {
         return false;
     }
 
-    GrSurfaceProxy* srcProxy = src->asSurfaceProxy();
     GrSurface* srcSurface = srcProxy->peekSurface();
 
     if (!GrSurfacePriv::AdjustReadPixelParams(srcSurface->width(), srcSurface->height(),
@@ -324,6 +306,9 @@
         return false;
     }
 
+    bool needColorConversion =
+            SkColorSpaceXformSteps::Required(src->colorSpaceInfo().colorSpace(), dstColorSpace);
+
     // This is the getImageData equivalent to the canvas2D putImageData fast path. We probably don't
     // care so much about getImageData performance. However, in order to ensure putImageData/
     // getImageData in "legacy" mode are round-trippable we use the GPU to do the complementary
@@ -331,7 +316,7 @@
     // fContext->vaildaPMUPMConversionExists()).
     bool canvas2DFastPath =
             unpremul &&
-            !src->colorSpaceInfo().colorSpace() &&
+            !needColorConversion &&
             (GrColorType::kRGBA_8888 == dstColorType || GrColorType::kBGRA_8888 == dstColorType) &&
             SkToBool(srcProxy->asTextureProxy()) &&
             (srcProxy->config() == kRGBA_8888_GrPixelConfig ||
@@ -342,8 +327,6 @@
     if (!fContext->priv().caps()->surfaceSupportsReadPixels(srcSurface) ||
         canvas2DFastPath) {
         GrSurfaceDesc desc;
-        desc.fFlags = canvas2DFastPath ? kRenderTarget_GrSurfaceFlag : kNone_GrSurfaceFlags;
-        desc.fConfig = canvas2DFastPath ? kRGBA_8888_GrPixelConfig : srcProxy->config();
         desc.fWidth = width;
         desc.fHeight = height;
         desc.fSampleCnt = 1;
@@ -405,7 +388,7 @@
                                        dstColorSpace, buffer, rowBytes, flags);
     }
 
-    bool convert = unpremul;
+    bool convert = unpremul || needColorConversion;
 
     bool flip = srcProxy->origin() == kBottomLeft_GrSurfaceOrigin;
     if (flip) {
@@ -416,12 +399,6 @@
             srcProxy->config(), dstColorType);
     convert = convert || (dstColorType != allowedColorType);
 
-    if (!src->colorSpaceInfo().colorSpace()) {
-        // "Legacy" mode - no color space conversions.
-        dstColorSpace = nullptr;
-    }
-    convert = convert || !SkColorSpace::Equals(dstColorSpace, src->colorSpaceInfo().colorSpace());
-
     SkAutoPixmapStorage tempPixmap;
     SkPixmap finalPixmap;
     if (convert) {
@@ -454,9 +431,7 @@
         sk_bzero(buffer, tempPixmap.computeByteSize());
     }
 
-    if (srcSurface->surfacePriv().hasPendingWrite()) {
-        this->flush(nullptr);  // MDB TODO: tighten this
-    }
+    this->flush(srcProxy);
 
     if (!fContext->fGpu->readPixels(srcSurface, left, top, width, height, allowedColorType, buffer,
                                     rowBytes)) {
@@ -497,11 +472,11 @@
         return false;
     }
 
-    if (!dst->asSurfaceProxy()->instantiate(this->resourceProvider())) {
+    GrSurfaceProxy* dstProxy = dst->asSurfaceProxy();
+    if (!dstProxy->instantiate(this->resourceProvider())) {
         return false;
     }
 
-    GrSurfaceProxy* dstProxy = dst->asSurfaceProxy();
     GrSurface* dstSurface = dstProxy->peekSurface();
 
     if (!GrSurfacePriv::AdjustWritePixelParams(dstSurface->width(), dstSurface->height(),
@@ -513,29 +488,24 @@
     // TODO: Make GrSurfaceContext know its alpha type and pass src buffer's alpha type.
     bool premul = SkToBool(kUnpremul_PixelOpsFlag & pixelOpsFlags);
 
+    bool needColorConversion =
+            SkColorSpaceXformSteps::Required(srcColorSpace, dst->colorSpaceInfo().colorSpace());
+
     // For canvas2D putImageData performance we have a special code path for unpremul RGBA_8888 srcs
     // that are premultiplied on the GPU. This is kept as narrow as possible for now.
     bool canvas2DFastPath =
             !fContext->priv().caps()->avoidWritePixelsFastPath() &&
             premul &&
-            !dst->colorSpaceInfo().colorSpace() &&
+            !needColorConversion &&
             (srcColorType == GrColorType::kRGBA_8888 || srcColorType == GrColorType::kBGRA_8888) &&
             SkToBool(dst->asRenderTargetContext()) &&
             (dstProxy->config() == kRGBA_8888_GrPixelConfig ||
              dstProxy->config() == kBGRA_8888_GrPixelConfig) &&
-            !(pixelOpsFlags & kDontFlush_PixelOpsFlag) &&
             fContext->priv().caps()->isConfigTexturable(kRGBA_8888_GrPixelConfig) &&
             fContext->validPMUPMConversionExists();
 
     const GrCaps* caps = this->caps();
-    if (!caps->surfaceSupportsWritePixels(dstSurface) ||
-        canvas2DFastPath) {
-        // We don't expect callers that are skipping flushes to require an intermediate draw.
-        SkASSERT(!(pixelOpsFlags & kDontFlush_PixelOpsFlag));
-        if (pixelOpsFlags & kDontFlush_PixelOpsFlag) {
-            return false;
-        }
-
+    if (!caps->surfaceSupportsWritePixels(dstSurface) || canvas2DFastPath) {
         GrSurfaceDesc desc;
         desc.fWidth = width;
         desc.fHeight = height;
@@ -589,13 +559,16 @@
             dst->asRenderTargetContext()->fillRectToRect(
                     GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(),
                     SkRect::MakeXYWH(left, top, width, height), SkRect::MakeWH(width, height));
-            return true;
         } else {
-            return dst->copy(tempProxy.get(), SkIRect::MakeWH(width, height), {left, top});
+            if (!dst->copy(tempProxy.get(), SkIRect::MakeWH(width, height), {left, top})) {
+                return false;
+            }
         }
+
+        return true;
     }
 
-    bool convert = premul;
+    bool convert = premul || needColorConversion;
 
     if (!valid_pixel_conversion(srcColorType, dstProxy->config(), premul)) {
         return false;
@@ -605,12 +578,6 @@
             dstProxy->config(), srcColorType);
     convert = convert || (srcColorType != allowedColorType);
 
-    if (!dst->colorSpaceInfo().colorSpace()) {
-        // "Legacy" mode - no color space conversions.
-        srcColorSpace = nullptr;
-    }
-    convert = convert || !SkColorSpace::Equals(srcColorSpace, dst->colorSpaceInfo().colorSpace());
-
     std::unique_ptr<char[]> tempBuffer;
     if (convert) {
         auto srcSkColorType = GrColorTypeToSkColorType(srcColorType);
@@ -660,123 +627,19 @@
         top = dstSurface->height() - top - height;
     }
 
-    if (!(kDontFlush_PixelOpsFlag & pixelOpsFlags) && dstSurface->surfacePriv().hasPendingIO()) {
-        this->flush(nullptr);  // MDB TODO: tighten this
-    }
+    this->flush(dstProxy);
 
     return this->getGpu()->writePixels(dstSurface, left, top, width, height, srcColorType, buffer,
                                        rowBytes);
 }
 
 void GrContextPriv::moveOpListsToDDL(SkDeferredDisplayList* ddl) {
-    fContext->fDrawingManager->moveOpListsToDDL(ddl);
+    fContext->drawingManager()->moveOpListsToDDL(ddl);
 }
 
 void GrContextPriv::copyOpListsFromDDL(const SkDeferredDisplayList* ddl,
                                        GrRenderTargetProxy* newDest) {
-    fContext->fDrawingManager->copyOpListsFromDDL(ddl, newDest);
-}
-
-sk_sp<GrRenderTargetContext> GrContextPriv::makeDeferredRenderTargetContext(
-                                                        const GrBackendFormat& format,
-                                                        SkBackingFit fit,
-                                                        int width, int height,
-                                                        GrPixelConfig config,
-                                                        sk_sp<SkColorSpace> colorSpace,
-                                                        int sampleCnt,
-                                                        GrMipMapped mipMapped,
-                                                        GrSurfaceOrigin origin,
-                                                        const SkSurfaceProps* surfaceProps,
-                                                        SkBudgeted budgeted) {
-    SkASSERT(sampleCnt > 0);
-    if (fContext->abandoned()) {
-        return nullptr;
-    }
-
-    GrSurfaceDesc desc;
-    desc.fFlags = kRenderTarget_GrSurfaceFlag;
-    desc.fWidth = width;
-    desc.fHeight = height;
-    desc.fConfig = config;
-    desc.fSampleCnt = sampleCnt;
-
-    sk_sp<GrTextureProxy> rtp;
-    if (GrMipMapped::kNo == mipMapped) {
-        rtp = fContext->fProxyProvider->createProxy(format, desc, origin, fit, budgeted);
-    } else {
-        rtp = fContext->fProxyProvider->createMipMapProxy(format, desc, origin, budgeted);
-    }
-    if (!rtp) {
-        return nullptr;
-    }
-
-    sk_sp<GrRenderTargetContext> renderTargetContext(
-        fContext->fDrawingManager->makeRenderTargetContext(std::move(rtp),
-                                                           std::move(colorSpace),
-                                                           surfaceProps));
-    if (!renderTargetContext) {
-        return nullptr;
-    }
-
-    renderTargetContext->discard();
-
-    return renderTargetContext;
-}
-
-static inline GrPixelConfig GrPixelConfigFallback(GrPixelConfig config) {
-    switch (config) {
-        case kAlpha_8_GrPixelConfig:
-        case kAlpha_8_as_Alpha_GrPixelConfig:
-        case kAlpha_8_as_Red_GrPixelConfig:
-        case kRGB_565_GrPixelConfig:
-        case kRGBA_4444_GrPixelConfig:
-        case kBGRA_8888_GrPixelConfig:
-        case kRGBA_1010102_GrPixelConfig:
-        case kRGBA_half_GrPixelConfig:
-            return kRGBA_8888_GrPixelConfig;
-        case kSBGRA_8888_GrPixelConfig:
-            return kSRGBA_8888_GrPixelConfig;
-        case kAlpha_half_GrPixelConfig:
-        case kAlpha_half_as_Red_GrPixelConfig:
-            return kRGBA_half_GrPixelConfig;
-        case kGray_8_GrPixelConfig:
-        case kGray_8_as_Lum_GrPixelConfig:
-        case kGray_8_as_Red_GrPixelConfig:
-            return kRGB_888_GrPixelConfig;
-        default:
-            return kUnknown_GrPixelConfig;
-    }
-}
-
-sk_sp<GrRenderTargetContext> GrContextPriv::makeDeferredRenderTargetContextWithFallback(
-                                                                 const GrBackendFormat& format,
-                                                                 SkBackingFit fit,
-                                                                 int width, int height,
-                                                                 GrPixelConfig config,
-                                                                 sk_sp<SkColorSpace> colorSpace,
-                                                                 int sampleCnt,
-                                                                 GrMipMapped mipMapped,
-                                                                 GrSurfaceOrigin origin,
-                                                                 const SkSurfaceProps* surfaceProps,
-                                                                 SkBudgeted budgeted) {
-    GrBackendFormat localFormat = format;
-    SkASSERT(sampleCnt > 0);
-    if (0 == fContext->priv().caps()->getRenderTargetSampleCount(sampleCnt, config)) {
-        config = GrPixelConfigFallback(config);
-        // TODO: First we should be checking the getRenderTargetSampleCount from the GrBackendFormat
-        // and not GrPixelConfig. Besides that, we should implement the fallback in the caps, but
-        // for now we just convert the fallback pixel config to an SkColorType and then get the
-        // GrBackendFormat from that.
-        SkColorType colorType;
-        if (!GrPixelConfigToColorType(config, &colorType)) {
-            return nullptr;
-        }
-        localFormat = fContext->caps()->getBackendFormatFromColorType(colorType);
-    }
-
-    return this->makeDeferredRenderTargetContext(localFormat, fit, width, height, config,
-                                                 std::move(colorSpace), sampleCnt, mipMapped,
-                                                 origin, surfaceProps, budgeted);
+    fContext->drawingManager()->copyOpListsFromDDL(ddl, newDest);
 }
 
 //////////////////////////////////////////////////////////////////////////////
@@ -865,7 +728,7 @@
 }
 
 void GrContextPriv::testingOnly_setTextBlobCacheLimit(size_t bytes) {
-    fContext->fTextBlobCache->setBudget(bytes);
+    fContext->priv().getTextBlobCache()->setBudget(bytes);
 }
 
 sk_sp<SkImage> GrContextPriv::testingOnly_getFontAtlasImage(GrMaskFormat format, unsigned int index) {
@@ -892,7 +755,7 @@
 
 void GrContextPriv::testingOnly_flushAndRemoveOnFlushCallbackObject(GrOnFlushCallbackObject* cb) {
     fContext->flush();
-    fContext->fDrawingManager->testingOnly_removeOnFlushCallbackObject(cb);
+    fContext->drawingManager()->testingOnly_removeOnFlushCallbackObject(cb);
 }
 #endif
 
diff --git a/src/gpu/GrContextPriv.h b/src/gpu/GrContextPriv.h
index 5b3c05e..070cccb 100644
--- a/src/gpu/GrContextPriv.h
+++ b/src/gpu/GrContextPriv.h
@@ -18,10 +18,12 @@
 class GrOnFlushCallbackObject;
 class GrSemaphore;
 class GrSkSLFPFactory;
+class GrSkSLFPFactoryCache;
 class GrSurfaceProxy;
 class GrTextureContext;
 
 class SkDeferredDisplayList;
+class SkTaskGroup;
 
 /** Class that adds methods to GrContext that are only intended for use internal to Skia.
     This class is purely a privileged window into GrContext. It should never have additional
@@ -32,26 +34,48 @@
     // from GrContext_Base
     uint32_t contextID() const { return fContext->contextID(); }
 
+    bool matches(GrContext_Base* candidate) const { return fContext->matches(candidate); }
+
     const GrContextOptions& options() const { return fContext->options(); }
 
+    bool explicitlyAllocateGPUResources() const {
+        return fContext->explicitlyAllocateGPUResources();
+    }
+
     const GrCaps* caps() const { return fContext->caps(); }
-    sk_sp<const GrCaps> refCaps() const { return fContext->refCaps(); }
+    sk_sp<const GrCaps> refCaps() const;
 
     sk_sp<GrSkSLFPFactoryCache> fpFactoryCache();
 
+    GrImageContext* asImageContext() { return fContext->asImageContext(); }
+    GrRecordingContext* asRecordingContext() { return fContext->asRecordingContext(); }
+    GrContext* asDirectContext() { return fContext->asDirectContext(); }
+
     // from GrImageContext
+    GrProxyProvider* proxyProvider() { return fContext->proxyProvider(); }
+    const GrProxyProvider* proxyProvider() const { return fContext->proxyProvider(); }
+
+    bool abandoned() const { return fContext->abandoned(); }
+
+    /** This is only useful for debug purposes */
+    SkDEBUGCODE(GrSingleOwner* singleOwner() const { return fContext->singleOwner(); } )
 
     // from GrRecordingContext
-
-    /**
-     * Create a GrContext without a resource cache
-     */
-    static sk_sp<GrContext> MakeDDL(const sk_sp<GrContextThreadSafeProxy>&);
+    GrDrawingManager* drawingManager() { return fContext->drawingManager(); }
 
     sk_sp<GrOpMemoryPool> refOpMemoryPool();
-    GrOpMemoryPool* opMemoryPool();
+    GrOpMemoryPool* opMemoryPool() { return fContext->opMemoryPool(); }
 
-    GrDrawingManager* drawingManager() { return fContext->fDrawingManager.get(); }
+    GrStrikeCache* getGrStrikeCache() { return fContext->getGrStrikeCache(); }
+    GrTextBlobCache* getTextBlobCache() { return fContext->getTextBlobCache(); }
+
+    /**
+     * Registers an object for flush-related callbacks. (See GrOnFlushCallbackObject.)
+     *
+     * NOTE: the drawing manager tracks this object as a raw pointer; it is up to the caller to
+     * ensure its lifetime is tied to that of the context.
+     */
+    void addOnFlushCallbackObject(GrOnFlushCallbackObject*);
 
     sk_sp<GrSurfaceContext> makeWrappedSurfaceContext(sk_sp<GrSurfaceProxy>,
                                                       sk_sp<SkColorSpace> = nullptr,
@@ -66,6 +90,48 @@
                                                        sk_sp<SkColorSpace> colorSpace = nullptr,
                                                        const SkSurfaceProps* = nullptr);
 
+    /*
+     * Create a new render target context backed by a deferred-style
+     * GrRenderTargetProxy. We guarantee that "asTextureProxy" will succeed for
+     * renderTargetContexts created via this entry point.
+     */
+    sk_sp<GrRenderTargetContext> makeDeferredRenderTargetContext(
+                                            const GrBackendFormat& format,
+                                            SkBackingFit fit,
+                                            int width, int height,
+                                            GrPixelConfig config,
+                                            sk_sp<SkColorSpace> colorSpace,
+                                            int sampleCnt = 1,
+                                            GrMipMapped = GrMipMapped::kNo,
+                                            GrSurfaceOrigin origin = kBottomLeft_GrSurfaceOrigin,
+                                            const SkSurfaceProps* surfaceProps = nullptr,
+                                            SkBudgeted = SkBudgeted::kYes);
+
+    /*
+     * This method will attempt to create a renderTargetContext that has, at least, the number of
+     * channels and precision per channel as requested in 'config' (e.g., A8 and 888 can be
+     * converted to 8888). It may also swizzle the channels (e.g., BGRA -> RGBA).
+     * SRGB-ness will be preserved.
+     */
+    sk_sp<GrRenderTargetContext> makeDeferredRenderTargetContextWithFallback(
+                                            const GrBackendFormat& format,
+                                            SkBackingFit fit,
+                                            int width, int height,
+                                            GrPixelConfig config,
+                                            sk_sp<SkColorSpace> colorSpace,
+                                            int sampleCnt = 1,
+                                            GrMipMapped = GrMipMapped::kNo,
+                                            GrSurfaceOrigin origin = kBottomLeft_GrSurfaceOrigin,
+                                            const SkSurfaceProps* surfaceProps = nullptr,
+                                            SkBudgeted budgeted = SkBudgeted::kYes);
+
+    GrAuditTrail* auditTrail() { return fContext->auditTrail(); }
+
+    /**
+     * Create a GrContext without a resource cache
+     */
+    static sk_sp<GrContext> MakeDDL(const sk_sp<GrContextThreadSafeProxy>&);
+
     sk_sp<GrTextureContext> makeBackendTextureContext(const GrBackendTexture& tex,
                                                       GrSurfaceOrigin origin,
                                                       sk_sp<SkColorSpace> colorSpace);
@@ -111,26 +177,6 @@
     void flush(GrSurfaceProxy*);
 
     /**
-     * Registers an object for flush-related callbacks. (See GrOnFlushCallbackObject.)
-     *
-     * NOTE: the drawing manager tracks this object as a raw pointer; it is up to the caller to
-     * ensure its lifetime is tied to that of the context.
-     */
-    void addOnFlushCallbackObject(GrOnFlushCallbackObject*);
-
-    /**
-     * After this returns any pending writes to the surface will have been issued to the
-     * backend 3D API.
-     */
-    void flushSurfaceWrites(GrSurfaceProxy*);
-
-    /**
-     * After this returns any pending reads or writes to the surface will have been issued to the
-     * backend 3D API.
-     */
-    void flushSurfaceIO(GrSurfaceProxy*);
-
-    /**
      * Finalizes all pending reads and writes to the surface and also performs an MSAA resolve
      * if necessary.
      *
@@ -144,12 +190,6 @@
     * These flags can be used with the read/write pixels functions below.
     */
     enum PixelOpsFlags {
-        /** The GrContext will not be flushed before the surface read or write. This means that
-            the read or write may occur before previous draws have executed. */
-        kDontFlush_PixelOpsFlag = 0x1,
-        /** Any surface writes should be flushed to the backend 3D API after the surface operation
-            is complete */
-        kFlushWrites_PixelOp = 0x2,
         /** The src for write or dst read is unpremultiplied. This is only respected if both the
             config src and dst configs are an RGBA/BGRA 8888 format. */
         kUnpremul_PixelOpsFlag  = 0x4,
@@ -200,9 +240,6 @@
 
     SkTaskGroup* getTaskGroup() { return fContext->fTaskGroup.get(); }
 
-    GrProxyProvider* proxyProvider() { return fContext->fProxyProvider; }
-    const GrProxyProvider* proxyProvider() const { return fContext->fProxyProvider; }
-
     GrResourceProvider* resourceProvider() { return fContext->fResourceProvider; }
     const GrResourceProvider* resourceProvider() const { return fContext->fResourceProvider; }
 
@@ -211,9 +248,6 @@
     GrGpu* getGpu() { return fContext->fGpu.get(); }
     const GrGpu* getGpu() const { return fContext->fGpu.get(); }
 
-    GrStrikeCache* getGlyphCache() { return fContext->fGlyphCache; }
-    GrTextBlobCache* getTextBlobCache() { return fContext->fTextBlobCache.get(); }
-
     // This accessor should only ever be called by the GrOpFlushState.
     GrAtlasManager* getAtlasManager() {
         return fContext->onGetAtlasManager();
@@ -222,47 +256,8 @@
     void moveOpListsToDDL(SkDeferredDisplayList*);
     void copyOpListsFromDDL(const SkDeferredDisplayList*, GrRenderTargetProxy* newDest);
 
-    /*
-     * Create a new render target context backed by a deferred-style
-     * GrRenderTargetProxy. We guarantee that "asTextureProxy" will succeed for
-     * renderTargetContexts created via this entry point.
-     */
-    sk_sp<GrRenderTargetContext> makeDeferredRenderTargetContext(
-                                                 const GrBackendFormat& format,
-                                                 SkBackingFit fit,
-                                                 int width, int height,
-                                                 GrPixelConfig config,
-                                                 sk_sp<SkColorSpace> colorSpace,
-                                                 int sampleCnt = 1,
-                                                 GrMipMapped = GrMipMapped::kNo,
-                                                 GrSurfaceOrigin origin = kBottomLeft_GrSurfaceOrigin,
-                                                 const SkSurfaceProps* surfaceProps = nullptr,
-                                                 SkBudgeted = SkBudgeted::kYes);
-    /*
-     * This method will attempt to create a renderTargetContext that has, at least, the number of
-     * channels and precision per channel as requested in 'config' (e.g., A8 and 888 can be
-     * converted to 8888). It may also swizzle the channels (e.g., BGRA -> RGBA).
-     * SRGB-ness will be preserved.
-     */
-    sk_sp<GrRenderTargetContext> makeDeferredRenderTargetContextWithFallback(
-                                                 const GrBackendFormat& format,
-                                                 SkBackingFit fit,
-                                                 int width, int height,
-                                                 GrPixelConfig config,
-                                                 sk_sp<SkColorSpace> colorSpace,
-                                                 int sampleCnt = 1,
-                                                 GrMipMapped = GrMipMapped::kNo,
-                                                 GrSurfaceOrigin origin = kBottomLeft_GrSurfaceOrigin,
-                                                 const SkSurfaceProps* surfaceProps = nullptr,
-                                                 SkBudgeted budgeted = SkBudgeted::kYes);
-
-    GrAuditTrail* getAuditTrail() { return &fContext->fAuditTrail; }
-
     GrContextOptions::PersistentCache* getPersistentCache() { return fContext->fPersistentCache; }
 
-    /** This is only useful for debug purposes */
-    SkDEBUGCODE(GrSingleOwner* debugSingleOwner() const { return &fContext->fSingleOwner; } )
-
 #ifdef SK_ENABLE_DUMP_GPU
     /** Returns a string with detailed information about the context & GPU, in JSON format. */
     SkString dump() const;
diff --git a/src/gpu/GrContextThreadSafeProxy.cpp b/src/gpu/GrContextThreadSafeProxy.cpp
index 6540879..826867f 100644
--- a/src/gpu/GrContextThreadSafeProxy.cpp
+++ b/src/gpu/GrContextThreadSafeProxy.cpp
@@ -28,10 +28,6 @@
     return INHERITED::init(std::move(caps), std::move(FPFactoryCache));
 }
 
-bool GrContextThreadSafeProxy::matches(GrContext_Base* context) const {
-    return context->priv().contextID() == this->contextID();
-}
-
 SkSurfaceCharacterization GrContextThreadSafeProxy::createCharacterization(
                                      size_t cacheMaxResourceBytes,
                                      const SkImageInfo& ii, const GrBackendFormat& backendFormat,
diff --git a/src/gpu/GrContextThreadSafeProxyPriv.h b/src/gpu/GrContextThreadSafeProxyPriv.h
index d23e55a..4da021d 100644
--- a/src/gpu/GrContextThreadSafeProxyPriv.h
+++ b/src/gpu/GrContextThreadSafeProxyPriv.h
@@ -20,6 +20,8 @@
     // from GrContext_Base
     uint32_t contextID() const { return fProxy->contextID(); }
 
+    bool matches(GrContext_Base* candidate) const { return fProxy->matches(candidate); }
+
     const GrContextOptions& options() const { return fProxy->options(); }
 
     const GrCaps* caps() const { return fProxy->caps(); }
diff --git a/src/gpu/GrContext_Base.cpp b/src/gpu/GrContext_Base.cpp
index 7f71df5..e3f8988 100644
--- a/src/gpu/GrContext_Base.cpp
+++ b/src/gpu/GrContext_Base.cpp
@@ -7,6 +7,7 @@
 
 #include "GrContext_Base.h"
 
+#include "GrBaseContextPriv.h"
 #include "GrCaps.h"
 #include "GrSkSLFPFactoryCache.h"
 
@@ -29,11 +30,6 @@
 
 GrContext_Base::~GrContext_Base() { }
 
-const GrCaps* GrContext_Base::caps() const { return fCaps.get(); }
-sk_sp<const GrCaps> GrContext_Base::refCaps() const { return fCaps; }
-
-sk_sp<GrSkSLFPFactoryCache> GrContext_Base::fpFactoryCache() { return fFPFactoryCache; }
-
 bool GrContext_Base::init(sk_sp<const GrCaps> caps, sk_sp<GrSkSLFPFactoryCache> FPFactoryCache) {
     SkASSERT(caps && FPFactoryCache);
 
@@ -42,3 +38,16 @@
     return true;
 }
 
+const GrCaps* GrContext_Base::caps() const { return fCaps.get(); }
+sk_sp<const GrCaps> GrContext_Base::refCaps() const { return fCaps; }
+
+sk_sp<GrSkSLFPFactoryCache> GrContext_Base::fpFactoryCache() { return fFPFactoryCache; }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+sk_sp<const GrCaps> GrBaseContextPriv::refCaps() const {
+    return fContext->refCaps();
+}
+
+sk_sp<GrSkSLFPFactoryCache> GrBaseContextPriv::fpFactoryCache() {
+    return fContext->fpFactoryCache();
+}
diff --git a/src/gpu/GrCpuBuffer.h b/src/gpu/GrCpuBuffer.h
new file mode 100644
index 0000000..3fab08b
--- /dev/null
+++ b/src/gpu/GrCpuBuffer.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrCpuBuffer_DEFINED
+#define GrCpuBuffer_DEFINED
+
+#include "GrBuffer.h"
+#include "GrNonAtomicRef.h"
+
+class GrCpuBuffer final : public GrNonAtomicRef<GrCpuBuffer>, public GrBuffer {
+public:
+    static sk_sp<GrCpuBuffer> Make(size_t size) {
+        SkASSERT(size > 0);
+        auto mem = ::operator new(sizeof(GrCpuBuffer) + size);
+        return sk_sp<GrCpuBuffer>(new (mem) GrCpuBuffer((char*)mem + sizeof(GrCpuBuffer), size));
+    }
+
+    void ref() const override { GrNonAtomicRef<GrCpuBuffer>::ref(); }
+    void unref() const override { GrNonAtomicRef<GrCpuBuffer>::unref(); }
+    size_t size() const override { return fSize; }
+    bool isCpuBuffer() const override { return true; }
+
+    char* data() { return reinterpret_cast<char*>(fData); }
+    const char* data() const { return reinterpret_cast<const char*>(fData); }
+
+private:
+    GrCpuBuffer(void* data, size_t size) : fData(data), fSize(size) {}
+    void* fData;
+    size_t fSize;
+};
+
+#endif
diff --git a/src/gpu/GrDDLContext.cpp b/src/gpu/GrDDLContext.cpp
index 6b72e4e..6836e46 100644
--- a/src/gpu/GrDDLContext.cpp
+++ b/src/gpu/GrDDLContext.cpp
@@ -40,6 +40,10 @@
     }
 
 protected:
+    // TODO: Here we're pretending this isn't derived from GrContext. Switch this to be derived from
+    // GrRecordingContext!
+    GrContext* asDirectContext() override { return nullptr; }
+
     bool init(sk_sp<const GrCaps> caps, sk_sp<GrSkSLFPFactoryCache> FPFactoryCache) override {
         SkASSERT(caps && FPFactoryCache);
         SkASSERT(fThreadSafeProxy); // should've been set in the ctor
@@ -48,6 +52,10 @@
             return false;
         }
 
+        // DDL contexts/drawing managers always sort the oplists. This, in turn, implies that
+        // explicit resource allocation is always on (regardless of how Skia is compiled).
+        this->setupDrawingManager(true, true);
+
         SkASSERT(this->caps());
 
         return true;
diff --git a/src/gpu/GrDefaultGeoProcFactory.cpp b/src/gpu/GrDefaultGeoProcFactory.cpp
index 3a81a25..fdc1214 100644
--- a/src/gpu/GrDefaultGeoProcFactory.cpp
+++ b/src/gpu/GrDefaultGeoProcFactory.cpp
@@ -30,13 +30,8 @@
     kLocalCoordAttribute_GPFlag     = 0x8,
     kCoverageAttribute_GPFlag       = 0x10,
     kCoverageAttributeTweak_GPFlag  = 0x20,
-    kBonesAttribute_GPFlag          = 0x40,
 };
 
-static constexpr int kNumVec2sPerBone = 3; // Our bone matrices are 3x2 matrices passed in as
-                                           // vec2s in column major order, and thus there are 3
-                                           // vec2s per bone.
-
 class DefaultGeoProc : public GrGeometryProcessor {
 public:
     static sk_sp<GrGeometryProcessor> Make(const GrShaderCaps* shaderCaps,
@@ -46,12 +41,10 @@
                                            const SkMatrix& viewMatrix,
                                            const SkMatrix& localMatrix,
                                            bool localCoordsWillBeRead,
-                                           uint8_t coverage,
-                                           const float* bones,
-                                           int boneCount) {
+                                           uint8_t coverage) {
         return sk_sp<GrGeometryProcessor>(new DefaultGeoProc(
                 shaderCaps, gpTypeFlags, color, std::move(colorSpaceXform), viewMatrix, localMatrix,
-                coverage, localCoordsWillBeRead, bones, boneCount));
+                coverage, localCoordsWillBeRead));
     }
 
     const char* name() const override { return "DefaultGeometryProcessor"; }
@@ -63,9 +56,6 @@
     bool localCoordsWillBeRead() const { return fLocalCoordsWillBeRead; }
     uint8_t coverage() const { return fCoverage; }
     bool hasVertexCoverage() const { return fInCoverage.isInitialized(); }
-    const float* bones() const { return fBones; }
-    int boneCount() const { return fBoneCount; }
-    bool hasBones() const { return SkToBool(fBones); }
 
     class GLSLProcessor : public GrGLSLGeometryProcessor {
     public:
@@ -132,62 +122,11 @@
                                         &fColorUniform);
             }
 
-            // Setup bone transforms
-            // NOTE: This code path is currently unused. Benchmarks have found that for all
-            // reasonable cases of skinned vertices, the overhead involved in copying and uploading
-            // bone data makes performing the transformations on the CPU faster than doing so on
-            // the GPU. This is being kept here in case that changes.
-            const char* transformedPositionName = gp.fInPosition.name();
-            if (gp.hasBones()) {
-                // Set up the uniform for the bones.
-                const char* vertBonesUniformName;
-                fBonesUniform = uniformHandler->addUniformArray(kVertex_GrShaderFlag,
-                                                                kFloat2_GrSLType,
-                                                                "Bones",
-                                                                kMaxBones * kNumVec2sPerBone,
-                                                                &vertBonesUniformName);
-
-                // Set up the bone application function.
-                SkString applyBoneFunctionName;
-                this->emitApplyBoneFunction(vertBuilder,
-                                            vertBonesUniformName,
-                                            &applyBoneFunctionName);
-
-                // Apply the world transform to the position first.
-                vertBuilder->codeAppendf(
-                        "float2 worldPosition = %s(0, %s);"
-                        "float2 transformedPosition = float2(0, 0);"
-                        "for (int i = 0; i < 4; i++) {",
-                        applyBoneFunctionName.c_str(),
-                        gp.fInPosition.name());
-
-                // If the GPU supports unsigned integers, then we can read the index. Otherwise,
-                // we have to estimate it given the float representation.
-                if (args.fShaderCaps->unsignedSupport()) {
-                    vertBuilder->codeAppendf(
-                        "    byte index = %s[i];",
-                        gp.fInBoneIndices.name());
-                } else {
-                    vertBuilder->codeAppendf(
-                        "    byte index = byte(floor(%s[i] * 255 + 0.5));",
-                        gp.fInBoneIndices.name());
-                }
-
-                // Get the weight and apply the transformation.
-                vertBuilder->codeAppendf(
-                        "    float weight = %s[i];"
-                        "    transformedPosition += %s(index, worldPosition) * weight;"
-                        "}",
-                        gp.fInBoneWeights.name(),
-                        applyBoneFunctionName.c_str());
-                transformedPositionName = "transformedPosition";
-            }
-
             // Setup position
             this->writeOutputPosition(vertBuilder,
                                       uniformHandler,
                                       gpArgs,
-                                      transformedPositionName,
+                                      gp.fInPosition.name(),
                                       gp.viewMatrix(),
                                       &fViewMatrixUniform);
 
@@ -262,40 +201,6 @@
             this->setTransformDataHelper(dgp.fLocalMatrix, pdman, &transformIter);
 
             fColorSpaceHelper.setData(pdman, dgp.fColorSpaceXform.get());
-
-            if (dgp.hasBones()) {
-                pdman.set2fv(fBonesUniform, dgp.boneCount() * kNumVec2sPerBone, dgp.bones());
-            }
-        }
-
-    private:
-        void emitApplyBoneFunction(GrGLSLVertexBuilder* vertBuilder,
-                                   const char* vertBonesUniformName,
-                                   SkString* funcName) {
-                // The bone matrices are passed in as 3x2 matrices in column-major order as groups
-                // of 3 float2s. This code takes those float2s and performs the matrix operation on
-                // a given matrix and float2.
-                const GrShaderVar gApplyBoneArgs[] = {
-                    GrShaderVar("index", kByte_GrSLType),
-                    GrShaderVar("vec", kFloat2_GrSLType),
-                };
-                SkString body;
-                body.appendf(
-                    "    float2 c0 = %s[index * 3];"
-                    "    float2 c1 = %s[index * 3 + 1];"
-                    "    float2 c2 = %s[index * 3 + 2];"
-                    "    float x = c0.x * vec.x + c1.x * vec.y + c2.x;"
-                    "    float y = c0.y * vec.x + c1.y * vec.y + c2.y;"
-                    "    return float2(x, y);",
-                    vertBonesUniformName,
-                    vertBonesUniformName,
-                    vertBonesUniformName);
-                vertBuilder->emitFunction(kFloat2_GrSLType,
-                                          "applyBone",
-                                          SK_ARRAY_COUNT(gApplyBoneArgs),
-                                          gApplyBoneArgs,
-                                          body.c_str(),
-                                          funcName);
         }
 
     private:
@@ -305,7 +210,6 @@
         UniformHandle fViewMatrixUniform;
         UniformHandle fColorUniform;
         UniformHandle fCoverageUniform;
-        UniformHandle fBonesUniform;
         GrGLSLColorSpaceXformHelper fColorSpaceHelper;
 
         typedef GrGLSLGeometryProcessor INHERITED;
@@ -327,9 +231,7 @@
                    const SkMatrix& viewMatrix,
                    const SkMatrix& localMatrix,
                    uint8_t coverage,
-                   bool localCoordsWillBeRead,
-                   const float* bones,
-                   int boneCount)
+                   bool localCoordsWillBeRead)
             : INHERITED(kDefaultGeoProc_ClassID)
             , fColor(color)
             , fViewMatrix(viewMatrix)
@@ -337,9 +239,7 @@
             , fCoverage(coverage)
             , fFlags(gpTypeFlags)
             , fLocalCoordsWillBeRead(localCoordsWillBeRead)
-            , fColorSpaceXform(std::move(colorSpaceXform))
-            , fBones(bones)
-            , fBoneCount(boneCount) {
+            , fColorSpaceXform(std::move(colorSpaceXform)) {
         fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
         if (fFlags & kColorAttribute_GPFlag) {
             fInColor = MakeColorAttribute("inColor",
@@ -352,28 +252,13 @@
         if (fFlags & kCoverageAttribute_GPFlag) {
             fInCoverage = {"inCoverage", kFloat_GrVertexAttribType, kHalf_GrSLType};
         }
-        if (fFlags & kBonesAttribute_GPFlag) {
-            SkASSERT(bones && (boneCount > 0));
-            // GLSL 1.10 and 1.20 don't support integer attributes.
-            GrVertexAttribType indicesCPUType = kByte4_GrVertexAttribType;
-            GrSLType indicesGPUType = kByte4_GrSLType;
-            if (!shaderCaps->unsignedSupport()) {
-                indicesCPUType = kUByte4_norm_GrVertexAttribType;
-                indicesGPUType = kHalf4_GrSLType;
-            }
-            fInBoneIndices = {"inBoneIndices", indicesCPUType, indicesGPUType};
-            fInBoneWeights = {"inBoneWeights", kUByte4_norm_GrVertexAttribType,
-                                               kHalf4_GrSLType};
-        }
-        this->setVertexAttributes(&fInPosition, 6);
+        this->setVertexAttributes(&fInPosition, 4);
     }
 
     Attribute fInPosition;
     Attribute fInColor;
     Attribute fInLocalCoords;
     Attribute fInCoverage;
-    Attribute fInBoneIndices;
-    Attribute fInBoneWeights;
     SkPMColor4f fColor;
     SkMatrix fViewMatrix;
     SkMatrix fLocalMatrix;
@@ -381,8 +266,6 @@
     uint32_t fFlags;
     bool fLocalCoordsWillBeRead;
     sk_sp<GrColorSpaceXform> fColorSpaceXform;
-    const float* fBones;
-    int fBoneCount;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
 
@@ -392,15 +275,6 @@
 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DefaultGeoProc);
 
 #if GR_TEST_UTILS
-static constexpr int kNumFloatsPerBone = 6;
-static constexpr int kTestBoneCount = 4;
-static constexpr float kTestBones[kTestBoneCount * kNumFloatsPerBone] = {
-    1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-    1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-    1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-    1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-};
-
 sk_sp<GrGeometryProcessor> DefaultGeoProc::TestCreate(GrProcessorTestData* d) {
     uint32_t flags = 0;
     if (d->fRandom->nextBool()) {
@@ -421,9 +295,6 @@
     if (d->fRandom->nextBool()) {
         flags |= kLocalCoordAttribute_GPFlag;
     }
-    if (d->fRandom->nextBool()) {
-        flags |= kBonesAttribute_GPFlag;
-    }
 
     return DefaultGeoProc::Make(d->caps()->shaderCaps(),
                                 flags,
@@ -432,9 +303,7 @@
                                 GrTest::TestMatrix(d->fRandom),
                                 GrTest::TestMatrix(d->fRandom),
                                 d->fRandom->nextBool(),
-                                GrRandomCoverage(d->fRandom),
-                                kTestBones,
-                                kTestBoneCount);
+                                GrRandomCoverage(d->fRandom));
 }
 #endif
 
@@ -468,9 +337,7 @@
                                 viewMatrix,
                                 localCoords.fMatrix ? *localCoords.fMatrix : SkMatrix::I(),
                                 localCoordsWillBeRead,
-                                inCoverage,
-                                nullptr,
-                                0);
+                                inCoverage);
 }
 
 sk_sp<GrGeometryProcessor> GrDefaultGeoProcFactory::MakeForDeviceSpace(
@@ -494,40 +361,3 @@
     LocalCoords inverted(LocalCoords::kUsePosition_Type, &invert);
     return Make(shaderCaps, color, coverage, inverted, SkMatrix::I());
 }
-
-sk_sp<GrGeometryProcessor> GrDefaultGeoProcFactory::MakeWithBones(const GrShaderCaps* shaderCaps,
-                                                                  const Color& color,
-                                                                  const Coverage& coverage,
-                                                                  const LocalCoords& localCoords,
-                                                                  const Bones& bones,
-                                                                  const SkMatrix& viewMatrix) {
-    uint32_t flags = 0;
-    if (Color::kPremulGrColorAttribute_Type == color.fType) {
-        flags |= kColorAttribute_GPFlag;
-    } else if (Color::kUnpremulSkColorAttribute_Type == color.fType) {
-        flags |= kColorAttribute_GPFlag | kColorAttributeIsSkColor_GPFlag;
-    } else if (Color::kPremulWideColorAttribute_Type == color.fType) {
-        flags |= kColorAttribute_GPFlag | kColorAttributeIsWide_GPFlag;
-    }
-    if (Coverage::kAttribute_Type == coverage.fType) {
-        flags |= kCoverageAttribute_GPFlag;
-    } else if (Coverage::kAttributeTweakAlpha_Type == coverage.fType) {
-        flags |= kCoverageAttribute_GPFlag | kCoverageAttributeTweak_GPFlag;
-    }
-    flags |= localCoords.fType == LocalCoords::kHasExplicit_Type ? kLocalCoordAttribute_GPFlag : 0;
-    flags |= kBonesAttribute_GPFlag;
-
-    uint8_t inCoverage = coverage.fCoverage;
-    bool localCoordsWillBeRead = localCoords.fType != LocalCoords::kUnused_Type;
-
-    return DefaultGeoProc::Make(shaderCaps,
-                                flags,
-                                color.fColor,
-                                color.fColorSpaceXform,
-                                viewMatrix,
-                                localCoords.fMatrix ? *localCoords.fMatrix : SkMatrix::I(),
-                                localCoordsWillBeRead,
-                                inCoverage,
-                                bones.fBones,
-                                bones.fBoneCount);
-}
diff --git a/src/gpu/GrDefaultGeoProcFactory.h b/src/gpu/GrDefaultGeoProcFactory.h
index 96363f0..b02d556 100644
--- a/src/gpu/GrDefaultGeoProcFactory.h
+++ b/src/gpu/GrDefaultGeoProcFactory.h
@@ -12,8 +12,6 @@
 #include "GrGeometryProcessor.h"
 #include "GrShaderCaps.h"
 
-constexpr int kMaxBones = 80; // Supports up to 80 bones per mesh.
-
 /*
  * A factory for creating default Geometry Processors which simply multiply position by the uniform
  * view matrix and wire through color, coverage, UV coords if requested.
@@ -78,15 +76,6 @@
         const SkMatrix* fMatrix;
     };
 
-    struct Bones {
-        Bones(const float bones[], int boneCount)
-            : fBones(bones)
-            , fBoneCount(boneCount) {}
-
-        const float* fBones;
-        int fBoneCount;
-    };
-
     sk_sp<GrGeometryProcessor> Make(const GrShaderCaps*,
                                     const Color&,
                                     const Coverage&,
@@ -103,18 +92,6 @@
                                                   const Coverage&,
                                                   const LocalCoords&,
                                                   const SkMatrix& viewMatrix);
-
-    /*
-     * Use this factory to create a GrGeometryProcessor that supports skeletal animation through
-     * deformation of vertices using matrices that are passed in. This should only be called from
-     * GrDrawVerticesOp.
-     */
-    sk_sp<GrGeometryProcessor> MakeWithBones(const GrShaderCaps*,
-                                             const Color&,
-                                             const Coverage&,
-                                             const LocalCoords&,
-                                             const Bones&,
-                                             const SkMatrix& viewMatrix);
 };
 
 #endif
diff --git a/src/gpu/GrDeinstantiateProxyTracker.cpp b/src/gpu/GrDeinstantiateProxyTracker.cpp
index 9870617..06d1e93 100644
--- a/src/gpu/GrDeinstantiateProxyTracker.cpp
+++ b/src/gpu/GrDeinstantiateProxyTracker.cpp
@@ -18,7 +18,8 @@
         SkASSERT(proxy != fProxies[i].get());
     }
 #endif
-    fProxies.push_back(sk_ref_sp(proxy));
+    proxy->firstRefAccess().ref(fCache);
+    fProxies.push_back(sk_sp<GrSurfaceProxy>(proxy));
 }
 
 void GrDeinstantiateProxyTracker::deinstantiateAllProxies() {
diff --git a/src/gpu/GrDeinstantiateProxyTracker.h b/src/gpu/GrDeinstantiateProxyTracker.h
index 2555ab1..f144fc2 100644
--- a/src/gpu/GrDeinstantiateProxyTracker.h
+++ b/src/gpu/GrDeinstantiateProxyTracker.h
@@ -11,9 +11,11 @@
 #include "GrSurfaceProxy.h"
 #include "SkTArray.h"
 
+class GrResourceCache;
+
 class GrDeinstantiateProxyTracker {
 public:
-    GrDeinstantiateProxyTracker() {}
+    GrDeinstantiateProxyTracker(GrResourceCache* cache) : fCache(cache) {}
 
     // Adds a proxy which will be deinstantiated at the end of flush. The same proxy may not be
     // added multiple times.
@@ -23,6 +25,7 @@
     void deinstantiateAllProxies();
 
 private:
+    GrResourceCache* fCache;
     SkTArray<sk_sp<GrSurfaceProxy>> fProxies;
 };
 
diff --git a/src/gpu/GrDirectContext.cpp b/src/gpu/GrDirectContext.cpp
deleted file mode 100644
index fb2adf6..0000000
--- a/src/gpu/GrDirectContext.cpp
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-
-#include "GrContext.h"
-
-#include "GrContextPriv.h"
-#include "GrContextThreadSafeProxy.h"
-#include "GrContextThreadSafeProxyPriv.h"
-#include "GrGpu.h"
-
-#include "effects/GrSkSLFP.h"
-#include "gl/GrGLGpu.h"
-#include "mock/GrMockGpu.h"
-#include "text/GrStrikeCache.h"
-#ifdef SK_METAL
-#include "mtl/GrMtlTrampoline.h"
-#endif
-#ifdef SK_VULKAN
-#include "vk/GrVkGpu.h"
-#endif
-
-class SK_API GrDirectContext : public GrContext {
-public:
-    GrDirectContext(GrBackendApi backend, const GrContextOptions& options)
-            : INHERITED(backend, options)
-            , fAtlasManager(nullptr) {
-    }
-
-    ~GrDirectContext() override {
-        // this if-test protects against the case where the context is being destroyed
-        // before having been fully created
-        if (this->priv().getGpu()) {
-            this->flush();
-        }
-
-        delete fAtlasManager;
-    }
-
-    void abandonContext() override {
-        INHERITED::abandonContext();
-        fAtlasManager->freeAll();
-    }
-
-    void releaseResourcesAndAbandonContext() override {
-        INHERITED::releaseResourcesAndAbandonContext();
-        fAtlasManager->freeAll();
-    }
-
-    void freeGpuResources() override {
-        this->flush();
-        fAtlasManager->freeAll();
-
-        INHERITED::freeGpuResources();
-    }
-
-protected:
-    bool init(sk_sp<const GrCaps> caps, sk_sp<GrSkSLFPFactoryCache> FPFactoryCache) override {
-        SkASSERT(caps && !FPFactoryCache);
-        SkASSERT(!fThreadSafeProxy);
-
-        FPFactoryCache.reset(new GrSkSLFPFactoryCache());
-        fThreadSafeProxy = GrContextThreadSafeProxyPriv::Make(this->backend(),
-                                                              this->options(),
-                                                              this->contextID(),
-                                                              caps, FPFactoryCache);
-
-        if (!INHERITED::init(std::move(caps), std::move(FPFactoryCache))) {
-            return false;
-        }
-
-        SkASSERT(this->caps());
-
-        GrDrawOpAtlas::AllowMultitexturing allowMultitexturing;
-        if (GrContextOptions::Enable::kNo == this->options().fAllowMultipleGlyphCacheTextures ||
-            // multitexturing supported only if range can represent the index + texcoords fully
-            !(this->caps()->shaderCaps()->floatIs32Bits() ||
-              this->caps()->shaderCaps()->integerSupport())) {
-            allowMultitexturing = GrDrawOpAtlas::AllowMultitexturing::kNo;
-        } else {
-            allowMultitexturing = GrDrawOpAtlas::AllowMultitexturing::kYes;
-        }
-
-        GrStrikeCache* glyphCache = this->priv().getGlyphCache();
-        GrProxyProvider* proxyProvider = this->priv().proxyProvider();
-
-        fAtlasManager = new GrAtlasManager(proxyProvider, glyphCache,
-                                           this->options().fGlyphCacheTextureMaximumBytes,
-                                           allowMultitexturing);
-        this->priv().addOnFlushCallbackObject(fAtlasManager);
-
-        return true;
-    }
-
-    GrAtlasManager* onGetAtlasManager() override { return fAtlasManager; }
-
-private:
-    GrAtlasManager* fAtlasManager;
-
-    typedef GrContext INHERITED;
-};
-
-sk_sp<GrContext> GrContext::MakeGL(sk_sp<const GrGLInterface> interface) {
-    GrContextOptions defaultOptions;
-    return MakeGL(std::move(interface), defaultOptions);
-}
-
-sk_sp<GrContext> GrContext::MakeGL(const GrContextOptions& options) {
-    return MakeGL(nullptr, options);
-}
-
-sk_sp<GrContext> GrContext::MakeGL() {
-    GrContextOptions defaultOptions;
-    return MakeGL(nullptr, defaultOptions);
-}
-
-sk_sp<GrContext> GrContext::MakeGL(sk_sp<const GrGLInterface> interface,
-                                   const GrContextOptions& options) {
-    sk_sp<GrContext> context(new GrDirectContext(GrBackendApi::kOpenGL, options));
-
-    context->fGpu = GrGLGpu::Make(std::move(interface), options, context.get());
-    if (!context->fGpu) {
-        return nullptr;
-    }
-
-    if (!context->init(context->fGpu->refCaps(), nullptr)) {
-        return nullptr;
-    }
-    return context;
-}
-
-sk_sp<GrContext> GrContext::MakeMock(const GrMockOptions* mockOptions) {
-    GrContextOptions defaultOptions;
-    return MakeMock(mockOptions, defaultOptions);
-}
-
-sk_sp<GrContext> GrContext::MakeMock(const GrMockOptions* mockOptions,
-                                     const GrContextOptions& options) {
-    sk_sp<GrContext> context(new GrDirectContext(GrBackendApi::kMock, options));
-
-    context->fGpu = GrMockGpu::Make(mockOptions, options, context.get());
-    if (!context->fGpu) {
-        return nullptr;
-    }
-
-    if (!context->init(context->fGpu->refCaps(), nullptr)) {
-        return nullptr;
-    }
-    return context;
-}
-
-sk_sp<GrContext> GrContext::MakeVulkan(const GrVkBackendContext& backendContext) {
-#ifdef SK_VULKAN
-    GrContextOptions defaultOptions;
-    return MakeVulkan(backendContext, defaultOptions);
-#else
-    return nullptr;
-#endif
-}
-
-sk_sp<GrContext> GrContext::MakeVulkan(const GrVkBackendContext& backendContext,
-                                       const GrContextOptions& options) {
-#ifdef SK_VULKAN
-    GrContextOptions defaultOptions;
-    sk_sp<GrContext> context(new GrDirectContext(GrBackendApi::kVulkan, options));
-
-    context->fGpu = GrVkGpu::Make(backendContext, options, context.get());
-    if (!context->fGpu) {
-        return nullptr;
-    }
-
-    if (!context->init(context->fGpu->refCaps(), nullptr)) {
-        return nullptr;
-    }
-    return context;
-#else
-    return nullptr;
-#endif
-}
-
-#ifdef SK_METAL
-sk_sp<GrContext> GrContext::MakeMetal(void* device, void* queue) {
-    GrContextOptions defaultOptions;
-    return MakeMetal(device, queue, defaultOptions);
-}
-
-sk_sp<GrContext> GrContext::MakeMetal(void* device, void* queue, const GrContextOptions& options) {
-    sk_sp<GrContext> context(new GrDirectContext(GrBackendApi::kMetal, options));
-
-    context->fGpu = GrMtlTrampoline::MakeGpu(context.get(), options, device, queue);
-    if (!context->fGpu) {
-        return nullptr;
-    }
-
-    if (!context->init(context->fGpu->refCaps(), nullptr)) {
-        return nullptr;
-    }
-    return context;
-}
-#endif
-
diff --git a/src/gpu/GrDrawOpAtlas.cpp b/src/gpu/GrDrawOpAtlas.cpp
index 94afadc..0d9cd80 100644
--- a/src/gpu/GrDrawOpAtlas.cpp
+++ b/src/gpu/GrDrawOpAtlas.cpp
@@ -515,7 +515,13 @@
     SkASSERT(SkIsPow2(fTextureWidth) && SkIsPow2(fTextureHeight));
 
     GrSurfaceDesc desc;
-    desc.fFlags = kNone_GrSurfaceFlags;
+    if (proxyProvider->caps()->shouldInitializeTextures()) {
+        // The atlas isn't guaranteed to touch all its pixels so, for platforms that benefit
+        // from complete initialization, clear everything.
+        desc.fFlags = kPerformInitialClear_GrSurfaceFlag;
+    } else {
+        desc.fFlags = kNone_GrSurfaceFlags;
+    }
     desc.fWidth = fTextureWidth;
     desc.fHeight = fTextureHeight;
     desc.fConfig = fPixelConfig;
diff --git a/src/gpu/GrDrawOpTest.cpp b/src/gpu/GrDrawOpTest.cpp
index 90c6e59..cebe297 100644
--- a/src/gpu/GrDrawOpTest.cpp
+++ b/src/gpu/GrDrawOpTest.cpp
@@ -7,15 +7,15 @@
 
 #include "GrDrawOpTest.h"
 #include "GrCaps.h"
-#include "GrContext.h"
-#include "GrContextPriv.h"
+#include "GrContext_Base.h"
+#include "GrBaseContextPriv.h"
 #include "GrUserStencilSettings.h"
 #include "SkRandom.h"
 #include "SkTypes.h"
 
 #if GR_TEST_UTILS
 
-const GrUserStencilSettings* GrGetRandomStencil(SkRandom* random, GrContext* context) {
+const GrUserStencilSettings* GrGetRandomStencil(SkRandom* random, GrContext_Base* context) {
     if (context->priv().caps()->avoidStencilBuffers()) {
         return &GrUserStencilSettings::kUnused;
     }
diff --git a/src/gpu/GrDrawOpTest.h b/src/gpu/GrDrawOpTest.h
index 6dc9541..ba97ae7 100644
--- a/src/gpu/GrDrawOpTest.h
+++ b/src/gpu/GrDrawOpTest.h
@@ -13,8 +13,10 @@
 
 #if GR_TEST_UTILS
 
+class GrContext_Base;
 class GrDrawOp;
 class GrPaint;
+class GrRecordingContext;
 class GrRenderTargetContext;
 struct GrUserStencilSettings;
 class SkRandom;
@@ -24,14 +26,14 @@
 
 /** GrDrawOp subclasses should define test factory functions using this macro. */
 #define GR_DRAW_OP_TEST_DEFINE(Op)                                                              \
-    std::unique_ptr<GrDrawOp> Op##__Test(GrPaint&& paint, SkRandom* random, GrContext* context, \
-                                         GrFSAAType fsaaType)
-#define GR_DRAW_OP_TEST_FRIEND(Op)                                                 \
-    friend std::unique_ptr<GrDrawOp> Op##__Test(GrPaint&& paint, SkRandom* random, \
-                                                GrContext* context, GrFSAAType fsaaType)
+    std::unique_ptr<GrDrawOp> Op##__Test(GrPaint&& paint, SkRandom* random,                     \
+                                         GrRecordingContext* context, GrFSAAType fsaaType)
+#define GR_DRAW_OP_TEST_FRIEND(Op)                                                              \
+    friend std::unique_ptr<GrDrawOp> Op##__Test(GrPaint&& paint, SkRandom* random,              \
+                                                GrRecordingContext* context, GrFSAAType fsaaType)
 
 /** Helper for op test factories to pick a random stencil state. */
-const GrUserStencilSettings* GrGetRandomStencil(SkRandom* random, GrContext*);
+const GrUserStencilSettings* GrGetRandomStencil(SkRandom* random, GrContext_Base*);
 
 #endif
 #endif
diff --git a/src/gpu/GrDrawingManager.cpp b/src/gpu/GrDrawingManager.cpp
index 3b4c73f..7904d75 100644
--- a/src/gpu/GrDrawingManager.cpp
+++ b/src/gpu/GrDrawingManager.cpp
@@ -6,13 +6,15 @@
  */
 
 #include "GrDrawingManager.h"
+
 #include "GrBackendSemaphore.h"
-#include "GrContext.h"
 #include "GrContextPriv.h"
 #include "GrGpu.h"
 #include "GrMemoryPool.h"
 #include "GrOnFlushResourceProvider.h"
 #include "GrOpList.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrRenderTargetProxy.h"
 #include "GrResourceAllocator.h"
@@ -32,16 +34,9 @@
 #include "ccpr/GrCoverageCountingPathRenderer.h"
 #include "text/GrTextContext.h"
 
-GrDrawingManager::OpListDAG::OpListDAG(bool explicitlyAllocating,
-                                       GrContextOptions::Enable sortOpLists) {
-    if (GrContextOptions::Enable::kNo == sortOpLists) {
-        fSortOpLists = false;
-    } else if (GrContextOptions::Enable::kYes == sortOpLists) {
-        fSortOpLists = true;
-    } else {
-        // By default we always enable sorting when we're explicitly allocating GPU resources
-        fSortOpLists = explicitlyAllocating;
-    }
+GrDrawingManager::OpListDAG::OpListDAG(bool explicitlyAllocating, bool sortOpLists)
+        : fSortOpLists(sortOpLists) {
+    SkASSERT(!sortOpLists || explicitlyAllocating);
 }
 
 GrDrawingManager::OpListDAG::~OpListDAG() {}
@@ -77,6 +72,16 @@
     }
 }
 
+bool GrDrawingManager::OpListDAG::isUsed(GrSurfaceProxy* proxy) const {
+    for (int i = 0; i < fOpLists.count(); ++i) {
+        if (fOpLists[i] && fOpLists[i]->isUsed(proxy)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 void GrDrawingManager::OpListDAG::add(sk_sp<GrOpList> opList) {
     fOpLists.emplace_back(std::move(opList));
 }
@@ -145,18 +150,15 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-GrDrawingManager::GrDrawingManager(GrContext* context,
+GrDrawingManager::GrDrawingManager(GrRecordingContext* context,
                                    const GrPathRendererChain::Options& optionsForPathRendererChain,
                                    const GrTextContext::Options& optionsForTextContext,
-                                   GrSingleOwner* singleOwner,
                                    bool explicitlyAllocating,
-                                   GrContextOptions::Enable sortOpLists,
+                                   bool sortOpLists,
                                    GrContextOptions::Enable reduceOpListSplitting)
         : fContext(context)
         , fOptionsForPathRendererChain(optionsForPathRendererChain)
         , fOptionsForTextContext(optionsForTextContext)
-        , fSingleOwner(singleOwner)
-        , fAbandoned(false)
         , fDAG(explicitlyAllocating, sortOpLists)
         , fTextContext(nullptr)
         , fPathRendererChain(nullptr)
@@ -186,9 +188,8 @@
     this->cleanup();
 }
 
-void GrDrawingManager::abandon() {
-    fAbandoned = true;
-    this->cleanup();
+bool GrDrawingManager::wasAbandoned() const {
+    return fContext->priv().abandoned();
 }
 
 void GrDrawingManager::freeGpuResources() {
@@ -205,7 +206,9 @@
 }
 
 // MDB TODO: make use of the 'proxy' parameter.
-GrSemaphoresSubmitted GrDrawingManager::flush(GrSurfaceProxy*,
+GrSemaphoresSubmitted GrDrawingManager::flush(GrSurfaceProxy* proxy,
+                                              SkSurface::BackendSurfaceAccess access,
+                                              GrFlushFlags flags,
                                               int numSemaphores,
                                               GrBackendSemaphore backendSemaphores[]) {
     GR_CREATE_TRACE_MARKER_CONTEXT("GrDrawingManager", "flush", fContext);
@@ -213,14 +216,27 @@
     if (fFlushing || this->wasAbandoned()) {
         return GrSemaphoresSubmitted::kNo;
     }
+
     SkDEBUGCODE(this->validate());
 
-    GrGpu* gpu = fContext->priv().getGpu();
+    if (kNone_GrFlushFlags == flags && !numSemaphores && proxy && !fDAG.isUsed(proxy)) {
+        return GrSemaphoresSubmitted::kNo;
+    }
+
+    auto direct = fContext->priv().asDirectContext();
+    if (!direct) {
+        return GrSemaphoresSubmitted::kNo; // Can't flush while DDL recording
+    }
+
+    GrGpu* gpu = direct->priv().getGpu();
     if (!gpu) {
         return GrSemaphoresSubmitted::kNo; // Can't flush while DDL recording
     }
     fFlushing = true;
 
+    auto resourceProvider = direct->priv().resourceProvider();
+    auto resourceCache = direct->priv().getResourceCache();
+
     // Semi-usually the GrOpLists are already closed at this point, but sometimes Ganesh
     // needs to flush mid-draw. In that case, the SkGpuDevice's GrOpLists won't be closed
     // but need to be flushed anyway. Closing such GrOpLists here will mean new
@@ -229,14 +245,16 @@
     fActiveOpList = nullptr;
 
     fDAG.prepForFlush();
-    SkASSERT(SkToBool(fVertexBufferSpace) == SkToBool(fIndexBufferSpace));
-    if (!fVertexBufferSpace) {
-        fVertexBufferSpace.reset(new char[GrBufferAllocPool::kDefaultBufferSize]());
-        fIndexBufferSpace.reset(new char[GrBufferAllocPool::kDefaultBufferSize]());
+    if (!fCpuBufferCache) {
+        // We cache more buffers when the backend is using client side arrays. Otherwise, we
+        // expect each pool will use a CPU buffer as a staging buffer before uploading to a GPU
+        // buffer object. Each pool only requires one staging buffer at a time.
+        int maxCachedBuffers = fContext->priv().caps()->preferClientSideDynamicBuffers() ? 2 : 6;
+        fCpuBufferCache = GrBufferAllocPool::CpuBufferCache::Make(maxCachedBuffers);
     }
 
-    GrOpFlushState flushState(gpu, fContext->priv().resourceProvider(), &fTokenTracker,
-                              fVertexBufferSpace.get(), fIndexBufferSpace.get());
+    GrOpFlushState flushState(gpu, resourceProvider, resourceCache, &fTokenTracker,
+                              fCpuBufferCache);
 
     GrOnFlushResourceProvider onFlushProvider(this);
     // TODO: AFAICT the only reason fFlushState is on GrDrawingManager rather than on the
@@ -283,8 +301,8 @@
     bool flushed = false;
 
     {
-        GrResourceAllocator alloc(fContext->priv().resourceProvider(),
-                                  flushState.deinstantiateProxyTracker());
+        GrResourceAllocator alloc(resourceProvider, flushState.deinstantiateProxyTracker()
+                                  SkDEBUGCODE(, fDAG.numOpLists()));
         for (int i = 0; i < fDAG.numOpLists(); ++i) {
             if (fDAG.opList(i)) {
                 fDAG.opList(i)->gatherProxyIntervals(&alloc);
@@ -332,17 +350,23 @@
     opMemoryPool->isEmpty();
 #endif
 
-    GrSemaphoresSubmitted result = gpu->finishFlush(numSemaphores, backendSemaphores);
+    GrSemaphoresSubmitted result = gpu->finishFlush(proxy, access, flags, numSemaphores,
+                                                    backendSemaphores);
 
     flushState.deinstantiateProxyTracker()->deinstantiateAllProxies();
 
     // Give the cache a chance to purge resources that become purgeable due to flushing.
     if (flushed) {
-        fContext->priv().getResourceCache()->purgeAsNeeded();
+        resourceCache->purgeAsNeeded();
+        flushed = false;
     }
     for (GrOnFlushCallbackObject* onFlushCBObject : fOnFlushCBObjects) {
         onFlushCBObject->postFlush(fTokenTracker.nextTokenToFlush(), fFlushingOpListIDs.begin(),
                                    fFlushingOpListIDs.count());
+        flushed = true;
+    }
+    if (flushed) {
+        resourceCache->purgeAsNeeded();
     }
     fFlushingOpListIDs.reset();
     fFlushing = false;
@@ -364,7 +388,12 @@
     }
 #endif
 
-    GrResourceProvider* resourceProvider = fContext->priv().resourceProvider();
+    auto direct = fContext->priv().asDirectContext();
+    if (!direct) {
+        return false;
+    }
+
+    auto resourceProvider = direct->priv().resourceProvider();
     bool anyOpListsExecuted = false;
 
     for (int i = startIndex; i < stopIndex; ++i) {
@@ -389,7 +418,7 @@
 
         // TODO: handle this instantiation via lazy surface proxies?
         // Instantiate all deferred proxies (being built on worker threads) so we can upload them
-        opList->instantiateDeferredProxies(fContext->priv().resourceProvider());
+        opList->instantiateDeferredProxies(resourceProvider);
         opList->prepare(flushState);
     }
 
@@ -412,7 +441,8 @@
         onFlushOpList = nullptr;
         (*numOpListsExecuted)++;
         if (*numOpListsExecuted >= kMaxOpListsBeforeFlush) {
-            flushState->gpu()->finishFlush(0, nullptr);
+            flushState->gpu()->finishFlush(nullptr, SkSurface::BackendSurfaceAccess::kNoAccess,
+                                           kNone_GrFlushFlags, 0, nullptr);
             *numOpListsExecuted = 0;
         }
     }
@@ -429,7 +459,8 @@
         }
         (*numOpListsExecuted)++;
         if (*numOpListsExecuted >= kMaxOpListsBeforeFlush) {
-            flushState->gpu()->finishFlush(0, nullptr);
+            flushState->gpu()->finishFlush(nullptr, SkSurface::BackendSurfaceAccess::kNoAccess,
+                                           kNone_GrFlushFlags, 0, nullptr);
             *numOpListsExecuted = 0;
         }
     }
@@ -448,24 +479,30 @@
 }
 
 GrSemaphoresSubmitted GrDrawingManager::prepareSurfaceForExternalIO(
-        GrSurfaceProxy* proxy, int numSemaphores, GrBackendSemaphore backendSemaphores[]) {
+        GrSurfaceProxy* proxy, SkSurface::BackendSurfaceAccess access, GrFlushFlags flags,
+        int numSemaphores, GrBackendSemaphore backendSemaphores[]) {
     if (this->wasAbandoned()) {
         return GrSemaphoresSubmitted::kNo;
     }
     SkDEBUGCODE(this->validate());
     SkASSERT(proxy);
 
-    GrGpu* gpu = fContext->priv().getGpu();
+    auto direct = fContext->priv().asDirectContext();
+    if (!direct) {
+        return GrSemaphoresSubmitted::kNo; // Can't flush while DDL recording
+    }
+
+    GrGpu* gpu = direct->priv().getGpu();
     if (!gpu) {
         return GrSemaphoresSubmitted::kNo; // Can't flush while DDL recording
     }
 
-    GrSemaphoresSubmitted result = GrSemaphoresSubmitted::kNo;
-    if (proxy->priv().hasPendingIO() || numSemaphores) {
-        result = this->flush(proxy, numSemaphores, backendSemaphores);
-    }
-
-    if (!proxy->instantiate(fContext->priv().resourceProvider())) {
+    // TODO: It is important to upgrade the drawingmanager to just flushing the
+    // portion of the DAG required by 'proxy' in order to restore some of the
+    // semantics of this method.
+    GrSemaphoresSubmitted result = this->flush(proxy, access, flags, numSemaphores,
+                                               backendSemaphores);
+    if (!proxy->isInstantiated()) {
         return result;
     }
 
@@ -590,13 +627,19 @@
         fActiveOpList = nullptr;
     }
 
-    auto resourceProvider = fContext->priv().resourceProvider();
+    // MDB TODO: this is unfortunate. GrOpList only needs the resourceProvider here so that, when
+    // not explicitly allocating resources, it can immediately instantiate 'rtp' so that the use
+    // order matches the allocation order (see the comment in GrOpList's ctor).
+    GrResourceProvider* resourceProvider = nullptr;
+    if (fContext->priv().asDirectContext()) {
+        resourceProvider = fContext->priv().asDirectContext()->priv().resourceProvider();
+    }
 
     sk_sp<GrRenderTargetOpList> opList(new GrRenderTargetOpList(
                                                         resourceProvider,
                                                         fContext->priv().refOpMemoryPool(),
                                                         rtp,
-                                                        fContext->priv().getAuditTrail()));
+                                                        fContext->priv().auditTrail()));
     SkASSERT(rtp->getLastOpList() == opList.get());
 
     if (managedOpList) {
@@ -633,10 +676,18 @@
         fActiveOpList = nullptr;
     }
 
-    sk_sp<GrTextureOpList> opList(new GrTextureOpList(fContext->priv().resourceProvider(),
+    // MDB TODO: this is unfortunate. GrOpList only needs the resourceProvider here so that, when
+    // not explicitly allocating resources, it can immediately instantiate 'texureProxy' so that
+    // the use order matches the allocation order (see the comment in GrOpList's ctor).
+    GrResourceProvider* resourceProvider = nullptr;
+    if (fContext->priv().asDirectContext()) {
+        resourceProvider = fContext->priv().asDirectContext()->priv().resourceProvider();
+    }
+
+    sk_sp<GrTextureOpList> opList(new GrTextureOpList(resourceProvider,
                                                       fContext->priv().refOpMemoryPool(),
                                                       textureProxy,
-                                                      fContext->priv().getAuditTrail()));
+                                                      fContext->priv().auditTrail()));
 
     SkASSERT(textureProxy->getLastOpList() == opList.get());
 
@@ -700,9 +751,15 @@
 }
 
 void GrDrawingManager::flushIfNecessary() {
-    GrResourceCache* resourceCache = fContext->priv().getResourceCache();
+    auto direct = fContext->priv().asDirectContext();
+    if (!direct) {
+        return;
+    }
+
+    auto resourceCache = direct->priv().getResourceCache();
     if (resourceCache && resourceCache->requestsFlush()) {
-        this->flush(nullptr, 0, nullptr);
+        this->flush(nullptr, SkSurface::BackendSurfaceAccess::kNoAccess,
+                    kNone_GrFlushFlags, 0, nullptr);
         resourceCache->purgeAsNeeded();
     }
 }
@@ -723,14 +780,13 @@
         return nullptr;
     }
 
-    sk_sp<GrRenderTargetProxy> rtp(sk_ref_sp(sProxy->asRenderTargetProxy()));
+    sk_sp<GrRenderTargetProxy> renderTargetProxy(sk_ref_sp(sProxy->asRenderTargetProxy()));
 
-    return sk_sp<GrRenderTargetContext>(new GrRenderTargetContext(
-                                                        fContext, this, std::move(rtp),
-                                                        std::move(colorSpace),
-                                                        surfaceProps,
-                                                        fContext->priv().getAuditTrail(),
-                                                        fSingleOwner, managedOpList));
+    return sk_sp<GrRenderTargetContext>(new GrRenderTargetContext(fContext,
+                                                                  std::move(renderTargetProxy),
+                                                                  std::move(colorSpace),
+                                                                  surfaceProps,
+                                                                  managedOpList));
 }
 
 sk_sp<GrTextureContext> GrDrawingManager::makeTextureContext(sk_sp<GrSurfaceProxy> sProxy,
@@ -751,8 +807,7 @@
 
     sk_sp<GrTextureProxy> textureProxy(sk_ref_sp(sProxy->asTextureProxy()));
 
-    return sk_sp<GrTextureContext>(new GrTextureContext(fContext, this, std::move(textureProxy),
-                                                        std::move(colorSpace),
-                                                        fContext->priv().getAuditTrail(),
-                                                        fSingleOwner));
+    return sk_sp<GrTextureContext>(new GrTextureContext(fContext,
+                                                        std::move(textureProxy),
+                                                        std::move(colorSpace)));
 }
diff --git a/src/gpu/GrDrawingManager.h b/src/gpu/GrDrawingManager.h
index 7b17917..d3b8ac1 100644
--- a/src/gpu/GrDrawingManager.h
+++ b/src/gpu/GrDrawingManager.h
@@ -8,20 +8,21 @@
 #ifndef GrDrawingManager_DEFINED
 #define GrDrawingManager_DEFINED
 
+#include "GrBufferAllocPool.h"
 #include "GrDeferredUpload.h"
 #include "GrPathRenderer.h"
 #include "GrPathRendererChain.h"
 #include "GrResourceCache.h"
+#include "SkSurface.h"
 #include "SkTArray.h"
 #include "text/GrTextContext.h"
 
-class GrContext;
 class GrCoverageCountingPathRenderer;
 class GrOnFlushCallbackObject;
 class GrOpFlushState;
+class GrRecordingContext;
 class GrRenderTargetContext;
 class GrRenderTargetProxy;
-class GrSingleOWner;
 class GrRenderTargetOpList;
 class GrSoftwarePathRenderer;
 class GrTextureContext;
@@ -37,7 +38,6 @@
 public:
     ~GrDrawingManager();
 
-    bool wasAbandoned() const { return fAbandoned; }
     void freeGpuResources();
 
     sk_sp<GrRenderTargetContext> makeRenderTargetContext(sk_sp<GrSurfaceProxy>,
@@ -53,7 +53,7 @@
     sk_sp<GrRenderTargetOpList> newRTOpList(GrRenderTargetProxy* rtp, bool managedOpList);
     sk_sp<GrTextureOpList> newTextureOpList(GrTextureProxy* textureProxy);
 
-    GrContext* getContext() { return fContext; }
+    GrRecordingContext* getContext() { return fContext; }
 
     GrTextContext* getTextContext();
 
@@ -73,6 +73,8 @@
     static bool ProgramUnitTest(GrContext* context, int maxStages, int maxLevels);
 
     GrSemaphoresSubmitted prepareSurfaceForExternalIO(GrSurfaceProxy*,
+                                                      SkSurface::BackendSurfaceAccess access,
+                                                      GrFlushFlags flags,
                                                       int numSemaphores,
                                                       GrBackendSemaphore backendSemaphores[]);
 
@@ -89,7 +91,7 @@
     // This class encapsulates maintenance and manipulation of the drawing manager's DAG of opLists.
     class OpListDAG {
     public:
-        OpListDAG(bool explicitlyAllocating, GrContextOptions::Enable sortOpLists);
+        OpListDAG(bool explicitlyAllocating, bool sortOpLists);
         ~OpListDAG();
 
         // Currently, when explicitly allocating resources, this call will topologically sort the
@@ -116,6 +118,8 @@
         bool empty() const { return fOpLists.empty(); }
         int numOpLists() const { return fOpLists.count(); }
 
+        bool isUsed(GrSurfaceProxy*) const;
+
         GrOpList* opList(int index) { return fOpLists[index].get(); }
         const GrOpList* opList(int index) const { return fOpLists[index].get(); }
 
@@ -134,40 +138,43 @@
         bool                      fSortOpLists;
     };
 
-    GrDrawingManager(GrContext*, const GrPathRendererChain::Options&,
-                     const GrTextContext::Options&, GrSingleOwner*,
-                     bool explicitlyAllocating, GrContextOptions::Enable sortRenderTargets,
+    GrDrawingManager(GrRecordingContext*, const GrPathRendererChain::Options&,
+                     const GrTextContext::Options&,
+                     bool explicitlyAllocating,
+                     bool sortOpLists,
                      GrContextOptions::Enable reduceOpListSplitting);
 
-    void abandon();
+    bool wasAbandoned() const;
+
     void cleanup();
 
     // return true if any opLists were actually executed; false otherwise
     bool executeOpLists(int startIndex, int stopIndex, GrOpFlushState*, int* numOpListsExecuted);
 
     GrSemaphoresSubmitted flush(GrSurfaceProxy* proxy,
-                                int numSemaphores = 0,
-                                GrBackendSemaphore backendSemaphores[] = nullptr);
+                                SkSurface::BackendSurfaceAccess access,
+                                GrFlushFlags flags,
+                                int numSemaphores,
+                                GrBackendSemaphore backendSemaphores[]);
 
     SkDEBUGCODE(void validate() const);
 
-    friend class GrContext;  // for access to: ctor, abandon, reset & flush
+    friend class GrContext; // access to: flush & cleanup
     friend class GrContextPriv; // access to: flush
     friend class GrOnFlushResourceProvider; // this is just a shallow wrapper around this class
+    friend class GrRecordingContext;  // access to: ctor
+    friend class SkImage; // for access to: flush
 
     static const int kNumPixelGeometries = 5; // The different pixel geometries
     static const int kNumDFTOptions = 2;      // DFT or no DFT
 
-    GrContext*                        fContext;
+    GrRecordingContext*               fContext;
     GrPathRendererChain::Options      fOptionsForPathRendererChain;
     GrTextContext::Options            fOptionsForTextContext;
+    // This cache is used by both the vertex and index pools. It reuses memory across multiple
+    // flushes.
+    sk_sp<GrBufferAllocPool::CpuBufferCache> fCpuBufferCache;
 
-    std::unique_ptr<char[]>           fVertexBufferSpace;
-    std::unique_ptr<char[]>           fIndexBufferSpace;
-    // In debug builds we guard against improper thread handling
-    GrSingleOwner*                    fSingleOwner;
-
-    bool                              fAbandoned;
     OpListDAG                         fDAG;
     GrOpList*                         fActiveOpList = nullptr;
     // These are the IDs of the opLists currently being flushed (in internalFlush)
diff --git a/src/gpu/GrFPArgs.h b/src/gpu/GrFPArgs.h
index e67c638..94b26e6 100644
--- a/src/gpu/GrFPArgs.h
+++ b/src/gpu/GrFPArgs.h
@@ -11,11 +11,11 @@
 #include "SkFilterQuality.h"
 #include "SkMatrix.h"
 
-class GrContext;
 class GrColorSpaceInfo;
+class GrRecordingContext;
 
 struct GrFPArgs {
-    GrFPArgs(GrContext* context,
+    GrFPArgs(GrRecordingContext* context,
              const SkMatrix* viewMatrix,
              SkFilterQuality filterQuality,
              const GrColorSpaceInfo* dstColorSpaceInfo)
@@ -30,7 +30,7 @@
     class WithPreLocalMatrix;
     class WithPostLocalMatrix;
 
-    GrContext* fContext;
+    GrRecordingContext* fContext;
     const SkMatrix* fViewMatrix;
 
     // We track both pre and post local matrix adjustments.  For a given FP:
diff --git a/src/gpu/GrFragmentProcessor.cpp b/src/gpu/GrFragmentProcessor.cpp
index eff7419..3d6ec03 100644
--- a/src/gpu/GrFragmentProcessor.cpp
+++ b/src/gpu/GrFragmentProcessor.cpp
@@ -9,8 +9,8 @@
 #include "GrCoordTransform.h"
 #include "GrPipeline.h"
 #include "GrProcessorAnalysis.h"
-#include "effects/GrConstColorProcessor.h"
-#include "effects/GrPremulInputFragmentProcessor.h"
+#include "effects/generated/GrConstColorProcessor.h"
+#include "effects/generated/GrPremulInputFragmentProcessor.h"
 #include "effects/GrXfermodeFragmentProcessor.h"
 #include "glsl/GrGLSLFragmentProcessor.h"
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
@@ -104,6 +104,7 @@
     if (child->usesLocalCoords()) {
         fFlags |= kUsesLocalCoords_Flag;
     }
+    fRequestedFeatures |= child->fRequestedFeatures;
 
     int index = fChildProcessors.count();
     fChildProcessors.push_back(std::move(child));
diff --git a/src/gpu/GrFragmentProcessor.h b/src/gpu/GrFragmentProcessor.h
index 82a3d18..5136243 100644
--- a/src/gpu/GrFragmentProcessor.h
+++ b/src/gpu/GrFragmentProcessor.h
@@ -431,8 +431,13 @@
     bool operator!=(const TextureSampler& other) const { return !(*this == other); }
 
     // 'instantiate' should only ever be called at flush time.
+    // TODO: this can go away once explicit allocation has stuck
     bool instantiate(GrResourceProvider* resourceProvider) const {
-        return SkToBool(fProxyRef.get()->instantiate(resourceProvider));
+        if (resourceProvider->explicitlyAllocateGPUResources()) {
+            return fProxyRef.get()->isInstantiated();
+        } else {
+            return SkToBool(fProxyRef.get()->instantiate(resourceProvider));
+        }
     }
 
     // 'peekTexture' should only ever be called after a successful 'instantiate' call
diff --git a/src/gpu/GrGpu.cpp b/src/gpu/GrGpu.cpp
index cfb22e8..c20b255 100644
--- a/src/gpu/GrGpu.cpp
+++ b/src/gpu/GrGpu.cpp
@@ -10,7 +10,6 @@
 
 #include "GrBackendSemaphore.h"
 #include "GrBackendSurface.h"
-#include "GrBuffer.h"
 #include "GrCaps.h"
 #include "GrContext.h"
 #include "GrContextPriv.h"
@@ -217,10 +216,10 @@
     return nullptr;
 }
 
-sk_sp<GrBuffer> GrGpu::createBuffer(size_t size, GrGpuBufferType intendedType,
-                                    GrAccessPattern accessPattern, const void* data) {
+sk_sp<GrGpuBuffer> GrGpu::createBuffer(size_t size, GrGpuBufferType intendedType,
+                                       GrAccessPattern accessPattern, const void* data) {
     this->handleDirtyContext();
-    sk_sp<GrBuffer> buffer = this->onCreateBuffer(size, intendedType, accessPattern, data);
+    sk_sp<GrGpuBuffer> buffer = this->onCreateBuffer(size, intendedType, accessPattern, data);
     if (!this->caps()->reuseScratchBuffers()) {
         buffer->resourcePriv().removeScratchKey();
     }
@@ -302,9 +301,9 @@
     return false;
 }
 
-bool GrGpu::transferPixels(GrTexture* texture, int left, int top, int width, int height,
-                           GrColorType bufferColorType, GrBuffer* transferBuffer, size_t offset,
-                           size_t rowBytes) {
+bool GrGpu::transferPixelsTo(GrTexture* texture, int left, int top, int width, int height,
+                             GrColorType bufferColorType, GrGpuBuffer* transferBuffer,
+                             size_t offset, size_t rowBytes) {
     SkASSERT(texture);
     SkASSERT(transferBuffer);
 
@@ -320,8 +319,8 @@
     }
 
     this->handleDirtyContext();
-    if (this->onTransferPixels(texture, left, top, width, height, bufferColorType, transferBuffer,
-                               offset, rowBytes)) {
+    if (this->onTransferPixelsTo(texture, left, top, width, height, bufferColorType, transferBuffer,
+                                 offset, rowBytes)) {
         SkIRect rect = SkIRect::MakeXYWH(left, top, width, height);
         this->didWriteToSurface(texture, kTopLeft_GrSurfaceOrigin, &rect);
         fStats.incTransfersToTexture();
@@ -331,6 +330,28 @@
     return false;
 }
 
+size_t GrGpu::transferPixelsFrom(GrSurface* surface, int left, int top, int width, int height,
+                                 GrColorType bufferColorType, GrGpuBuffer* transferBuffer,
+                                 size_t offset) {
+    SkASSERT(surface);
+    SkASSERT(transferBuffer);
+
+    // We require that the write region is contained in the texture
+    SkIRect subRect = SkIRect::MakeXYWH(left, top, width, height);
+    SkIRect bounds = SkIRect::MakeWH(surface->width(), surface->height());
+    if (!bounds.contains(subRect)) {
+        return false;
+    }
+
+    this->handleDirtyContext();
+    if (auto result = this->onTransferPixelsFrom(surface, left, top, width, height, bufferColorType,
+                                                 transferBuffer, offset)) {
+        fStats.incTransfersFromSurface();
+        return result;
+    }
+    return 0;
+}
+
 bool GrGpu::regenerateMipMapLevels(GrTexture* texture) {
     SkASSERT(texture);
     SkASSERT(this->caps()->mipMapSupport());
@@ -347,6 +368,11 @@
     return false;
 }
 
+void GrGpu::resetTextureBindings() {
+    this->handleDirtyContext();
+    this->onResetTextureBindings();
+}
+
 void GrGpu::resolveRenderTarget(GrRenderTarget* target) {
     SkASSERT(target);
     this->handleDirtyContext();
@@ -375,7 +401,26 @@
     }
 }
 
-GrSemaphoresSubmitted GrGpu::finishFlush(int numSemaphores,
+int GrGpu::findOrAssignSamplePatternKey(GrRenderTarget* renderTarget, const GrPipeline& pipeline) {
+    SkASSERT(this->caps()->sampleLocationsSupport());
+    SkASSERT(renderTarget->numStencilSamples() > 1);
+    SkASSERT(pipeline.isHWAntialiasState());
+
+    GrStencilSettings stencil;
+    if (pipeline.isStencilEnabled()) {
+        SkASSERT(renderTarget->renderTargetPriv().getStencilAttachment());
+        stencil.reset(*pipeline.getUserStencil(), pipeline.hasStencilClip(),
+                      renderTarget->renderTargetPriv().numStencilBits());
+    }
+
+    SkSTArray<16, SkPoint> sampleLocations;
+    this->querySampleLocations(renderTarget, stencil, &sampleLocations);
+    return fSamplePatternDictionary.findOrAssignSamplePatternKey(sampleLocations);
+}
+
+GrSemaphoresSubmitted GrGpu::finishFlush(GrSurfaceProxy* proxy,
+                                         SkSurface::BackendSurfaceAccess access,
+                                         GrFlushFlags flags, int numSemaphores,
                                          GrBackendSemaphore backendSemaphores[]) {
     this->stats()->incNumFinishFlushes();
     GrResourceProvider* resourceProvider = fContext->priv().resourceProvider();
@@ -397,7 +442,8 @@
             }
         }
     }
-    this->onFinishFlush((numSemaphores > 0 && this->caps()->fenceSyncSupport()));
+    this->onFinishFlush(proxy, access, flags,
+                        (numSemaphores > 0 && this->caps()->fenceSyncSupport()));
     return this->caps()->fenceSyncSupport() ? GrSemaphoresSubmitted::kYes
                                             : GrSemaphoresSubmitted::kNo;
 }
@@ -433,6 +479,7 @@
     out->appendf("Textures Created: %d\n", fTextureCreates);
     out->appendf("Texture Uploads: %d\n", fTextureUploads);
     out->appendf("Transfers to Texture: %d\n", fTransfersToTexture);
+    out->appendf("Transfers from Surface: %d\n", fTransfersFromSurface);
     out->appendf("Stencil Buffer Creates: %d\n", fStencilAttachmentCreates);
     out->appendf("Number of draws: %d\n", fNumDraws);
 }
@@ -440,9 +487,6 @@
 void GrGpu::Stats::dumpKeyValuePairs(SkTArray<SkString>* keys, SkTArray<double>* values) {
     keys->push_back(SkString("render_target_binds")); values->push_back(fRenderTargetBinds);
     keys->push_back(SkString("shader_compilations")); values->push_back(fShaderCompilations);
-    keys->push_back(SkString("texture_uploads")); values->push_back(fTextureUploads);
-    keys->push_back(SkString("number_of_draws")); values->push_back(fNumDraws);
-    keys->push_back(SkString("number_of_failed_draws")); values->push_back(fNumFailedDraws);
 }
 
 #endif
diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h
index 917e887..e44e74c 100644
--- a/src/gpu/GrGpu.h
+++ b/src/gpu/GrGpu.h
@@ -11,18 +11,20 @@
 #include "GrCaps.h"
 #include "GrGpuCommandBuffer.h"
 #include "GrProgramDesc.h"
+#include "GrSamplePatternDictionary.h"
 #include "GrSwizzle.h"
 #include "GrAllocator.h"
 #include "GrTextureProducer.h"
 #include "GrTypes.h"
 #include "GrXferProcessor.h"
 #include "SkPath.h"
+#include "SkSurface.h"
 #include "SkTArray.h"
 #include <map>
 
 class GrBackendRenderTarget;
 class GrBackendSemaphore;
-class GrBuffer;
+class GrGpuBuffer;
 class GrContext;
 struct GrContextOptions;
 class GrGLContext;
@@ -140,8 +142,8 @@
      *
      * @return the buffer if successful, otherwise nullptr.
      */
-    sk_sp<GrBuffer> createBuffer(size_t size, GrGpuBufferType intendedType,
-                                 GrAccessPattern accessPattern, const void* data = nullptr);
+    sk_sp<GrGpuBuffer> createBuffer(size_t size, GrGpuBufferType intendedType,
+                                    GrAccessPattern accessPattern, const void* data = nullptr);
 
     /**
      * Resolves MSAA.
@@ -154,6 +156,11 @@
     bool regenerateMipMapLevels(GrTexture*);
 
     /**
+     * If the backend API has stateful texture bindings, this resets them back to defaults.
+     */
+    void resetTextureBindings();
+
+    /**
      * Reads a rectangle of pixels from a render target. No sRGB/linear conversions are performed.
      *
      * @param surface       The surface to read from
@@ -200,10 +207,8 @@
     }
 
     /**
-     * Updates the pixels in a rectangle of a texture using a buffer
-     *
-     * There are a couple of assumptions here. First, we only update the top miplevel.
-     * And second, that any y flip needed has already been done in the buffer.
+     * Updates the pixels in a rectangle of a texture using a buffer. If the texture is MIP mapped,
+     * the base level is written to.
      *
      * @param texture          The texture to write to.
      * @param left             left edge of the rectangle to write (inclusive)
@@ -216,9 +221,29 @@
      * @param rowBytes         number of bytes between consecutive rows in the buffer. Zero
      *                         means rows are tightly packed.
      */
-    bool transferPixels(GrTexture* texture, int left, int top, int width, int height,
-                        GrColorType bufferColorType, GrBuffer* transferBuffer, size_t offset,
-                        size_t rowBytes);
+    bool transferPixelsTo(GrTexture* texture, int left, int top, int width, int height,
+                          GrColorType bufferColorType, GrGpuBuffer* transferBuffer, size_t offset,
+                          size_t rowBytes);
+
+    /**
+     * Reads the pixels from a rectangle of a surface into a buffer. Use
+     * GrCaps::transferFromBufferRequirements to determine the requirements for the buffer size
+     * and offset alignment. If the surface is a MIP mapped texture, the base level is read.
+     *
+     * Returns the number of bytes per row in the buffer or zero on failure.
+     *
+     * @param surface          The surface to read from.
+     * @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 bufferColorType  the color type of the transfer buffer's pixel data
+     * @param transferBuffer   GrBuffer to write pixels to (type must be "kXferGpuToCpu")
+     * @param offset           offset from the start of the buffer
+     */
+    size_t transferPixelsFrom(GrSurface* surface, int left, int top, int width, int height,
+                              GrColorType bufferColorType, GrGpuBuffer* transferBuffer,
+                              size_t offset);
 
     // After the client interacts directly with the 3D context state the GrGpu
     // must resync its internal state and assumptions about 3D context state.
@@ -246,6 +271,22 @@
                      const SkIPoint& dstPoint,
                      bool canDiscardOutsideDstRect = false);
 
+    // Queries the per-pixel HW sample locations for the given render target, and then finds or
+    // assigns a key that uniquely identifies the sample pattern. The actual sample locations can be
+    // retrieved with retrieveSampleLocations().
+    //
+    // NOTE: The pipeline argument is required in order for us to flush draw state prior to querying
+    // multisample info. The pipeline itself is not expected to affect sample locations.
+    int findOrAssignSamplePatternKey(GrRenderTarget*, const GrPipeline&);
+
+    // Retrieves the per-pixel HW sample locations for the given sample pattern key, and, as a
+    // by-product, the actual number of samples in use. (This may differ from the number of samples
+    // requested by the render target.) Sample locations are returned as 0..1 offsets relative to
+    // the top-left corner of the pixel.
+    const SkTArray<SkPoint>& retrieveSampleLocations(int samplePatternKey) const {
+        return fSamplePatternDictionary.retrieveSampleLocations(samplePatternKey);
+    }
+
     // Returns a GrGpuRTCommandBuffer which GrOpLists send draw commands to instead of directly
     // to the Gpu object. The 'bounds' rect is the content rect of the destination.
     virtual GrGpuRTCommandBuffer* getCommandBuffer(
@@ -261,7 +302,9 @@
     // Provides a hook for post-flush actions (e.g. Vulkan command buffer submits). This will also
     // insert any numSemaphore semaphores on the gpu and set the backendSemaphores to match the
     // inserted semaphores.
-    GrSemaphoresSubmitted finishFlush(int numSemaphores, GrBackendSemaphore backendSemaphores[]);
+    GrSemaphoresSubmitted finishFlush(GrSurfaceProxy*, SkSurface::BackendSurfaceAccess access,
+                                      GrFlushFlags flags, int numSemaphores,
+                                      GrBackendSemaphore backendSemaphores[]);
 
     virtual void submit(GrGpuCommandBuffer*) = 0;
 
@@ -289,19 +332,9 @@
     class Stats {
     public:
 #if GR_GPU_STATS
-        Stats() { this->reset(); }
+        Stats() = default;
 
-        void reset() {
-            fRenderTargetBinds = 0;
-            fShaderCompilations = 0;
-            fTextureCreates = 0;
-            fTextureUploads = 0;
-            fTransfersToTexture = 0;
-            fStencilAttachmentCreates = 0;
-            fNumDraws = 0;
-            fNumFailedDraws = 0;
-            fNumFinishFlushes = 0;
-        }
+        void reset() { *this = {}; }
 
         int renderTargetBinds() const { return fRenderTargetBinds; }
         void incRenderTargetBinds() { fRenderTargetBinds++; }
@@ -312,7 +345,9 @@
         int textureUploads() const { return fTextureUploads; }
         void incTextureUploads() { fTextureUploads++; }
         int transfersToTexture() const { return fTransfersToTexture; }
+        int transfersFromSurface() const { return fTransfersFromSurface; }
         void incTransfersToTexture() { fTransfersToTexture++; }
+        void incTransfersFromSurface() { fTransfersFromSurface++; }
         void incStencilAttachmentCreates() { fStencilAttachmentCreates++; }
         void incNumDraws() { fNumDraws++; }
         void incNumFailedDraws() { ++fNumFailedDraws; }
@@ -325,15 +360,16 @@
         int numFailedDraws() const { return fNumFailedDraws; }
         int numFinishFlushes() const { return fNumFinishFlushes; }
     private:
-        int fRenderTargetBinds;
-        int fShaderCompilations;
-        int fTextureCreates;
-        int fTextureUploads;
-        int fTransfersToTexture;
-        int fStencilAttachmentCreates;
-        int fNumDraws;
-        int fNumFailedDraws;
-        int fNumFinishFlushes;
+        int fRenderTargetBinds = 0;
+        int fShaderCompilations = 0;
+        int fTextureCreates = 0;
+        int fTextureUploads = 0;
+        int fTransfersToTexture = 0;
+        int fTransfersFromSurface = 0;
+        int fStencilAttachmentCreates = 0;
+        int fNumDraws = 0;
+        int fNumFailedDraws = 0;
+        int fNumFinishFlushes = 0;
 #else
 
 #if GR_TEST_UTILS
@@ -446,13 +482,19 @@
     // Subclass must initialize this in its constructor.
     sk_sp<const GrCaps>              fCaps;
 
-    typedef SkTArray<SkPoint, true> SamplePattern;
-
 private:
     // called when the 3D context state is unknown. Subclass should emit any
     // assumed 3D context state and dirty any state cache.
     virtual void onResetContext(uint32_t resetBits) = 0;
 
+    // Implementation of resetTextureBindings.
+    virtual void onResetTextureBindings() {}
+
+    // Queries the effective number of samples in use by the hardware for the given render target,
+    // and queries the individual sample locations.
+    virtual void querySampleLocations(
+            GrRenderTarget*, const GrStencilSettings&, SkTArray<SkPoint>*) = 0;
+
     // Called before certain draws in order to guarantee coherent results from dst reads.
     virtual void xferBarrier(GrRenderTarget*, GrXferBarrierType) = 0;
 
@@ -472,8 +514,8 @@
     virtual sk_sp<GrRenderTarget> onWrapVulkanSecondaryCBAsRenderTarget(const SkImageInfo&,
                                                                         const GrVkDrawableInfo&);
 
-    virtual sk_sp<GrBuffer> onCreateBuffer(size_t size, GrGpuBufferType intendedType,
-                                           GrAccessPattern, const void* data) = 0;
+    virtual sk_sp<GrGpuBuffer> onCreateBuffer(size_t size, GrGpuBufferType intendedType,
+                                              GrAccessPattern, const void* data) = 0;
 
     // overridden by backend-specific derived class to perform the surface read
     virtual bool onReadPixels(GrSurface*, int left, int top, int width, int height, GrColorType,
@@ -484,9 +526,13 @@
                                const GrMipLevel texels[], int mipLevelCount) = 0;
 
     // overridden by backend-specific derived class to perform the texture transfer
-    virtual bool onTransferPixels(GrTexture*, int left, int top, int width, int height,
-                                  GrColorType colorType, GrBuffer* transferBuffer, size_t offset,
-                                  size_t rowBytes) = 0;
+    virtual bool onTransferPixelsTo(GrTexture*, int left, int top, int width, int height,
+                                    GrColorType colorType, GrGpuBuffer* transferBuffer,
+                                    size_t offset, size_t rowBytes) = 0;
+    // overridden by backend-specific derived class to perform the surface transfer
+    virtual size_t onTransferPixelsFrom(GrSurface*, int left, int top, int width, int height,
+                                        GrColorType colorType, GrGpuBuffer* transferBuffer,
+                                        size_t offset) = 0;
 
     // overridden by backend-specific derived class to perform the resolve
     virtual void onResolveRenderTarget(GrRenderTarget* target) = 0;
@@ -500,7 +546,8 @@
                                const SkIRect& srcRect, const SkIPoint& dstPoint,
                                bool canDiscardOutsideDstRect) = 0;
 
-    virtual void onFinishFlush(bool insertedSemaphores) = 0;
+    virtual void onFinishFlush(GrSurfaceProxy*, SkSurface::BackendSurfaceAccess access,
+                               GrFlushFlags flags, bool insertedSemaphores) = 0;
 
 #ifdef SK_ENABLE_DUMP_GPU
     virtual void onDumpJSON(SkJSONWriter*) const {}
@@ -516,6 +563,7 @@
     uint32_t fResetBits;
     // The context owns us, not vice-versa, so this ptr is not ref'ed by Gpu.
     GrContext* fContext;
+    GrSamplePatternDictionary fSamplePatternDictionary;
 
     friend class GrPathRendering;
     typedef SkRefCnt INHERITED;
diff --git a/src/gpu/GrGpuBuffer.cpp b/src/gpu/GrGpuBuffer.cpp
new file mode 100644
index 0000000..b679b47
--- /dev/null
+++ b/src/gpu/GrGpuBuffer.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrGpuBuffer.h"
+#include "GrCaps.h"
+#include "GrGpu.h"
+
+GrGpuBuffer::GrGpuBuffer(GrGpu* gpu, size_t sizeInBytes, GrGpuBufferType type,
+                         GrAccessPattern pattern)
+        : GrGpuResource(gpu)
+        , fMapPtr(nullptr)
+        , fSizeInBytes(sizeInBytes)
+        , fAccessPattern(pattern)
+        , fIntendedType(type) {}
+
+void GrGpuBuffer::ComputeScratchKeyForDynamicVBO(size_t size, GrGpuBufferType intendedType,
+                                                 GrScratchKey* key) {
+    static const GrScratchKey::ResourceType kType = GrScratchKey::GenerateResourceType();
+    GrScratchKey::Builder builder(key, kType, 1 + (sizeof(size_t) + 3) / 4);
+    // TODO: There's not always reason to cache a buffer by type. In some (all?) APIs it's just
+    // a chunk of memory we can use/reuse for any type of data. We really only need to
+    // differentiate between the "read" types (e.g. kGpuToCpu_BufferType) and "draw" types.
+    builder[0] = SkToU32(intendedType);
+    builder[1] = (uint32_t)size;
+    if (sizeof(size_t) > 4) {
+        builder[2] = (uint32_t)((uint64_t)size >> 32);
+    }
+}
+
+void GrGpuBuffer::computeScratchKey(GrScratchKey* key) const {
+    if (SkIsPow2(fSizeInBytes) && kDynamic_GrAccessPattern == fAccessPattern) {
+        ComputeScratchKeyForDynamicVBO(fSizeInBytes, fIntendedType, key);
+    }
+}
diff --git a/src/gpu/GrGpuBuffer.h b/src/gpu/GrGpuBuffer.h
new file mode 100644
index 0000000..5bc6ed1
--- /dev/null
+++ b/src/gpu/GrGpuBuffer.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrGpuBuffer_DEFINED
+#define GrGpuBuffer_DEFINED
+
+#include "GrBuffer.h"
+#include "GrGpuResource.h"
+
+class GrGpu;
+
+class GrGpuBuffer : public GrGpuResource, public GrBuffer {
+public:
+    /**
+     * Computes a scratch key for a GPU-side buffer with a "dynamic" access pattern. (Buffers with
+     * "static" and "stream" patterns are disqualified by nature from being cached and reused.)
+     */
+    static void ComputeScratchKeyForDynamicVBO(size_t size, GrGpuBufferType, GrScratchKey*);
+
+    GrAccessPattern accessPattern() const { return fAccessPattern; }
+
+    size_t size() const final { return fSizeInBytes; }
+
+    void ref() const final { GrGpuResource::ref(); }
+
+    void unref() const final { GrGpuResource::unref(); }
+
+    /**
+     * Maps the buffer to be written by the CPU.
+     *
+     * The previous content of the buffer is invalidated. It is an error
+     * to draw from the buffer while it is mapped. It may fail if the backend
+     * doesn't support mapping the buffer. If the buffer is CPU backed then
+     * it will always succeed and is a free operation. Once a buffer is mapped,
+     * subsequent calls to map() are ignored.
+     *
+     * Note that buffer mapping does not go through GrContext and therefore is
+     * not serialized with other operations.
+     *
+     * @return a pointer to the data or nullptr if the map fails.
+     */
+    void* map() {
+        if (!fMapPtr) {
+            this->onMap();
+        }
+        return fMapPtr;
+    }
+
+    /**
+     * Unmaps the buffer.
+     *
+     * The pointer returned by the previous map call will no longer be valid.
+     */
+    void unmap() {
+        SkASSERT(fMapPtr);
+        this->onUnmap();
+        fMapPtr = nullptr;
+    }
+
+    /**
+     Queries whether the buffer has been mapped.
+
+     @return true if the buffer is mapped, false otherwise.
+     */
+    bool isMapped() const { return SkToBool(fMapPtr); }
+
+    bool isCpuBuffer() const final { return false; }
+
+    /**
+     * Updates the buffer data.
+     *
+     * The size of the buffer will be preserved. The src data will be
+     * placed at the beginning of the buffer and any remaining contents will
+     * be undefined. srcSizeInBytes must be <= to the buffer size.
+     *
+     * The buffer must not be mapped.
+     *
+     * Note that buffer updates do not go through GrContext and therefore are
+     * not serialized with other operations.
+     *
+     * @return returns true if the update succeeds, false otherwise.
+     */
+    bool updateData(const void* src, size_t srcSizeInBytes) {
+        SkASSERT(!this->isMapped());
+        SkASSERT(srcSizeInBytes <= fSizeInBytes);
+        return this->onUpdateData(src, srcSizeInBytes);
+    }
+
+protected:
+    GrGpuBuffer(GrGpu*, size_t sizeInBytes, GrGpuBufferType, GrAccessPattern);
+    GrGpuBufferType intendedType() const { return fIntendedType; }
+
+    void* fMapPtr;
+
+private:
+    virtual void onMap() = 0;
+    virtual void onUnmap() = 0;
+    virtual bool onUpdateData(const void* src, size_t srcSizeInBytes) = 0;
+
+    size_t onGpuMemorySize() const override { return fSizeInBytes; }
+    const char* getResourceType() const override { return "Buffer Object"; }
+    void computeScratchKey(GrScratchKey* key) const override;
+
+    size_t            fSizeInBytes;
+    GrAccessPattern   fAccessPattern;
+    GrGpuBufferType   fIntendedType;
+};
+
+#endif
diff --git a/src/gpu/GrGpuCommandBuffer.cpp b/src/gpu/GrGpuCommandBuffer.cpp
index 2684219..e663fd1 100644
--- a/src/gpu/GrGpuCommandBuffer.cpp
+++ b/src/gpu/GrGpuCommandBuffer.cpp
@@ -7,8 +7,9 @@
 
 #include "GrGpuCommandBuffer.h"
 
-#include "GrContext.h"
 #include "GrCaps.h"
+#include "GrContext.h"
+#include "GrContextPriv.h"
 #include "GrFixedClip.h"
 #include "GrGpu.h"
 #include "GrMesh.h"
@@ -41,7 +42,7 @@
         SkASSERT(!GrPrimTypeRequiresGeometryShaderSupport(meshes[i].primitiveType()) ||
                  this->gpu()->caps()->shaderCaps()->geometryShaderSupport());
         SkASSERT(primProc.hasVertexAttributes() == meshes[i].hasVertexData());
-        SkASSERT(primProc.hasInstanceAttributes() == meshes[i].isInstanced());
+        SkASSERT(primProc.hasInstanceAttributes() == meshes[i].hasInstanceData());
     }
 #endif
     SkASSERT(!pipeline.isScissorEnabled() || fixedDynamicState ||
@@ -53,8 +54,11 @@
         return false;
     }
     if (fixedDynamicState && fixedDynamicState->fPrimitiveProcessorTextures) {
+        GrTextureProxy** processorProxies = fixedDynamicState->fPrimitiveProcessorTextures;
         for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
-            if (!fixedDynamicState->fPrimitiveProcessorTextures[i]->instantiate(resourceProvider)) {
+            if (resourceProvider->explicitlyAllocateGPUResources()) {
+                SkASSERT(processorProxies[i]->isInstantiated());
+            } else if (!processorProxies[i]->instantiate(resourceProvider)) {
                 return false;
             }
         }
@@ -63,7 +67,9 @@
         int n = primProc.numTextureSamplers() * meshCount;
         const auto* textures = dynamicStateArrays->fPrimitiveProcessorTextures;
         for (int i = 0; i < n; ++i) {
-            if (!textures[i]->instantiate(resourceProvider)) {
+            if (resourceProvider->explicitlyAllocateGPUResources()) {
+                SkASSERT(textures[i]->isInstantiated());
+            } else if (!textures[i]->instantiate(resourceProvider)) {
                 return false;
             }
         }
diff --git a/src/gpu/GrGpuResource.cpp b/src/gpu/GrGpuResource.cpp
index 0c7c3a1..3d2c197 100644
--- a/src/gpu/GrGpuResource.cpp
+++ b/src/gpu/GrGpuResource.cpp
@@ -104,6 +104,8 @@
     return this->internalHasRef() || this->internalHasPendingIO();
 }
 
+bool GrGpuResource::hasRef() const { return this->internalHasRef(); }
+
 SkString GrGpuResource::getResourceName() const {
     // Dump resource as "skia/gpu_resources/resource_#".
     SkString resourceName("skia/gpu_resources/resource_");
@@ -155,9 +157,12 @@
     get_resource_cache(fGpu)->resourceAccess().changeUniqueKey(this, key);
 }
 
-void GrGpuResource::notifyAllCntsAreZero(CntType lastCntTypeToReachZero) const {
+void GrGpuResource::notifyAllCntsWillBeZero() const {
     GrGpuResource* mutableThis = const_cast<GrGpuResource*>(this);
-    mutableThis->removedLastRefOrPendingIO();
+    mutableThis->willRemoveLastRefOrPendingIO();
+}
+
+void GrGpuResource::notifyAllCntsAreZero(CntType lastCntTypeToReachZero) const {
     if (this->wasDestroyed()) {
         // We've already been removed from the cache. Goodbye cruel world!
         delete this;
@@ -169,6 +174,7 @@
 
     static const uint32_t kFlag =
         GrResourceCache::ResourceAccess::kAllCntsReachedZero_RefNotificationFlag;
+    GrGpuResource* mutableThis = const_cast<GrGpuResource*>(this);
     get_resource_cache(fGpu)->resourceAccess().notifyCntReachedZero(mutableThis, kFlag);
 }
 
@@ -182,7 +188,6 @@
     uint32_t flags = GrResourceCache::ResourceAccess::kRefCntReachedZero_RefNotificationFlag;
     if (!this->internalHasPendingIO()) {
         flags |= GrResourceCache::ResourceAccess::kAllCntsReachedZero_RefNotificationFlag;
-        mutableThis->removedLastRefOrPendingIO();
     }
     get_resource_cache(fGpu)->resourceAccess().notifyCntReachedZero(mutableThis, flags);
 
@@ -226,3 +231,10 @@
     } while (id == SK_InvalidUniqueID);
     return id;
 }
+
+//////////////////////////////////////////////////////////////////////////////
+
+void GrGpuResource::ProxyAccess::ref(GrResourceCache* cache) {
+    SkASSERT(cache == fResource->getContext()->priv().getResourceCache());
+    cache->resourceAccess().refResource(fResource);
+}
diff --git a/src/gpu/GrGpuResourceCacheAccess.h b/src/gpu/GrGpuResourceCacheAccess.h
index e80b9b5..0e1ace03 100644
--- a/src/gpu/GrGpuResourceCacheAccess.h
+++ b/src/gpu/GrGpuResourceCacheAccess.h
@@ -20,6 +20,9 @@
  */
 class GrGpuResource::CacheAccess {
 private:
+    /** The cache is allowed to go from no refs to 1 ref. */
+    void ref() { fResource->addInitialRef(); }
+
     /**
      * Is the resource currently cached as scratch? This means it is cached, has a valid scratch
      * key, and does not have a unique key.
@@ -52,6 +55,9 @@
     /** Called by the cache to assign a new unique key. */
     void setUniqueKey(const GrUniqueKey& key) { fResource->fUniqueKey = key; }
 
+    /** Is the resource ref'ed (not counting pending IOs). */
+    bool hasRef() const { return fResource->hasRef(); }
+
     /** Called by the cache to make the unique key invalid. */
     void removeUniqueKey() { fResource->fUniqueKey.reset(); }
 
@@ -78,8 +84,8 @@
     CacheAccess& operator=(const CacheAccess&); // unimpl
 
     // No taking addresses of this type.
-    const CacheAccess* operator&() const;
-    CacheAccess* operator&();
+    const CacheAccess* operator&() const = delete;
+    CacheAccess* operator&() = delete;
 
     GrGpuResource* fResource;
 
diff --git a/src/gpu/GrImageContext.cpp b/src/gpu/GrImageContext.cpp
index 6adf700..91e81bc 100644
--- a/src/gpu/GrImageContext.cpp
+++ b/src/gpu/GrImageContext.cpp
@@ -7,10 +7,41 @@
 
 #include "GrImageContext.h"
 
+#include "GrCaps.h"
+#include "GrImageContextPriv.h"
+#include "GrProxyProvider.h"
+#include "GrSkSLFPFactoryCache.h"
+
+#define ASSERT_SINGLE_OWNER \
+    SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(this->singleOwner());)
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
 GrImageContext::GrImageContext(GrBackendApi backend,
                                const GrContextOptions& options,
-                               uint32_t uniqueID)
-            : INHERITED(backend, options, uniqueID) {
+                               uint32_t contextID)
+            : INHERITED(backend, options, contextID) {
+    fProxyProvider.reset(new GrProxyProvider(this));
 }
 
 GrImageContext::~GrImageContext() {}
+
+void GrImageContext::abandonContext() {
+    ASSERT_SINGLE_OWNER
+
+    fAbandoned = true;
+}
+
+bool GrImageContext::abandoned() const {
+    ASSERT_SINGLE_OWNER
+
+    return fAbandoned;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+sk_sp<const GrCaps> GrImageContextPriv::refCaps() const {
+    return fContext->refCaps();
+}
+
+sk_sp<GrSkSLFPFactoryCache> GrImageContextPriv::fpFactoryCache() {
+    return fContext->fpFactoryCache();
+}
diff --git a/src/gpu/GrImageContextPriv.h b/src/gpu/GrImageContextPriv.h
index 7bbfc28..1139085 100644
--- a/src/gpu/GrImageContextPriv.h
+++ b/src/gpu/GrImageContextPriv.h
@@ -18,14 +18,31 @@
     // from GrContext_Base
     uint32_t contextID() const { return fContext->contextID(); }
 
+    bool matches(GrContext_Base* candidate) const { return fContext->matches(candidate); }
+
     const GrContextOptions& options() const { return fContext->options(); }
 
-    const GrCaps* caps() const { return fContext->caps(); }
-    sk_sp<const GrCaps> refCaps() const { return fContext->refCaps(); }
+    bool explicitlyAllocateGPUResources() const {
+        return fContext->explicitlyAllocateGPUResources();
+    }
 
-    sk_sp<GrSkSLFPFactoryCache> fpFactoryCache() { return fContext->fpFactoryCache(); }
+    const GrCaps* caps() const { return fContext->caps(); }
+    sk_sp<const GrCaps> refCaps() const;
+
+    sk_sp<GrSkSLFPFactoryCache> fpFactoryCache();
+
+    GrImageContext* asImageContext() { return fContext->asImageContext(); }
+    GrRecordingContext* asRecordingContext() { return fContext->asRecordingContext(); }
+    GrContext* asDirectContext() { return fContext->asDirectContext(); }
 
     // from GrImageContext
+    GrProxyProvider* proxyProvider() { return fContext->proxyProvider(); }
+    const GrProxyProvider* proxyProvider() const { return fContext->proxyProvider(); }
+
+    bool abandoned() const { return fContext->abandoned(); }
+
+    /** This is only useful for debug purposes */
+    SkDEBUGCODE(GrSingleOwner* singleOwner() const { return fContext->singleOwner(); } )
 
 private:
     explicit GrImageContextPriv(GrImageContext* context) : fContext(context) {}
diff --git a/src/gpu/GrImageTextureMaker.cpp b/src/gpu/GrImageTextureMaker.cpp
index 5e821f5..5e5327f 100644
--- a/src/gpu/GrImageTextureMaker.cpp
+++ b/src/gpu/GrImageTextureMaker.cpp
@@ -12,9 +12,9 @@
 #include "SkImage_Lazy.h"
 #include "effects/GrYUVtoRGBEffect.h"
 
-GrImageTextureMaker::GrImageTextureMaker(GrContext* context, const SkImage* client,
-                                         SkImage::CachingHint chint)
-        : INHERITED(context, client->width(), client->height(), client->isAlphaOnly())
+GrImageTextureMaker::GrImageTextureMaker(GrRecordingContext* context, const SkImage* client,
+                                         SkImage::CachingHint chint, bool useDecal)
+        : INHERITED(context, client->width(), client->height(), client->isAlphaOnly(), useDecal)
         , fImage(static_cast<const SkImage_Lazy*>(client))
         , fCachingHint(chint) {
     SkASSERT(client->isLazyGenerated());
@@ -45,8 +45,9 @@
 
 /////////////////////////////////////////////////////////////////////////////////////////////////
 
-GrYUVAImageTextureMaker::GrYUVAImageTextureMaker(GrContext* context, const SkImage* client )
-    : INHERITED(context, client->width(), client->height(), client->isAlphaOnly())
+GrYUVAImageTextureMaker::GrYUVAImageTextureMaker(GrContext* context, const SkImage* client,
+                                                 bool useDecal)
+    : INHERITED(context, client->width(), client->height(), client->isAlphaOnly(), useDecal)
     , fImage(static_cast<const SkImage_GpuYUVA*>(client)) {
     SkASSERT(as_IB(client)->isYUVA());
     GrMakeKeyFromImageID(&fOriginalKey, client->uniqueID(),
@@ -60,9 +61,9 @@
     }
 
     if (willBeMipped) {
-        return fImage->asMippedTextureProxyRef();
+        return fImage->asMippedTextureProxyRef(this->context());
     } else {
-        return fImage->asTextureProxyRef();
+        return fImage->asTextureProxyRef(this->context());
     }
 }
 
@@ -93,9 +94,11 @@
     // Check simple cases to see if we need to fall back to flattening the image
     // TODO: See if we can relax this -- for example, if filterConstraint
     //       is kYes_FilterConstraint we still may not need a TextureDomain
-    //       in some cases.
+    //       in some cases. Or allow YUVtoRGBEffect to take a wrap mode to
+    //       handle ClampToBorder when a decal is needed.
     if (!textureMatrix.isIdentity() || kNo_FilterConstraint != filterConstraint ||
-        !coordsLimitedToConstraintRect || !filterOrNullForBicubic) {
+        !coordsLimitedToConstraintRect || !filterOrNullForBicubic ||
+        this->domainNeedsDecal()) {
         return this->INHERITED::createFragmentProcessor(textureMatrix, constraintRect,
                                                         filterConstraint,
                                                         coordsLimitedToConstraintRect,
@@ -105,15 +108,16 @@
     // Check to see if the client has given us pre-mipped textures or we can generate them
     // If not, fall back to bilerp
     GrSamplerState::Filter filter = *filterOrNullForBicubic;
-    if (GrSamplerState::Filter::kMipMap == filter && !fImage->setupMipmapsForPlanes()) {
+    if (GrSamplerState::Filter::kMipMap == filter &&
+        !fImage->setupMipmapsForPlanes(this->context())) {
         filter = GrSamplerState::Filter::kBilerp;
     }
 
     auto fp = GrYUVtoRGBEffect::Make(fImage->fProxies, fImage->fYUVAIndices,
                                      fImage->fYUVColorSpace, filter);
-    if (fImage->fTargetColorSpace) {
-        fp = GrColorSpaceXformEffect::Make(std::move(fp), fImage->fColorSpace.get(),
-                                           fImage->alphaType(), fImage->fTargetColorSpace.get());
+    if (fImage->fFromColorSpace) {
+        fp = GrColorSpaceXformEffect::Make(std::move(fp), fImage->fFromColorSpace.get(),
+                                           fImage->alphaType(), fImage->colorSpace());
     }
     return fp;
 }
diff --git a/src/gpu/GrImageTextureMaker.h b/src/gpu/GrImageTextureMaker.h
index 544705f..ba7772b 100644
--- a/src/gpu/GrImageTextureMaker.h
+++ b/src/gpu/GrImageTextureMaker.h
@@ -18,7 +18,8 @@
     is kAllow the image's ID is used for the cache key. */
 class GrImageTextureMaker : public GrTextureMaker {
 public:
-    GrImageTextureMaker(GrContext* context, const SkImage* client, SkImage::CachingHint chint);
+    GrImageTextureMaker(GrRecordingContext* context, const SkImage* client,
+                        SkImage::CachingHint chint, bool useDecal = false);
 
 protected:
     // TODO: consider overriding this, for the case where the underlying generator might be
@@ -44,7 +45,7 @@
 /** This class manages the conversion of generator-backed YUVA images to GrTextures. */
 class GrYUVAImageTextureMaker : public GrTextureMaker {
 public:
-    GrYUVAImageTextureMaker(GrContext* context, const SkImage* client);
+    GrYUVAImageTextureMaker(GrContext* context, const SkImage* client, bool useDecal = false);
 
 protected:
     // TODO: consider overriding this, for the case where the underlying generator might be
diff --git a/src/gpu/GrLegacyDirectContext.cpp b/src/gpu/GrLegacyDirectContext.cpp
new file mode 100644
index 0000000..d5bce1b
--- /dev/null
+++ b/src/gpu/GrLegacyDirectContext.cpp
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "GrContext.h"
+
+#include "GrContextPriv.h"
+#include "GrContextThreadSafeProxy.h"
+#include "GrContextThreadSafeProxyPriv.h"
+#include "GrGpu.h"
+
+#include "effects/GrSkSLFP.h"
+#include "gl/GrGLGpu.h"
+#include "mock/GrMockGpu.h"
+#include "text/GrStrikeCache.h"
+#ifdef SK_METAL
+#include "mtl/GrMtlTrampoline.h"
+#endif
+#ifdef SK_VULKAN
+#include "vk/GrVkGpu.h"
+#endif
+
+#ifdef SK_DISABLE_OPLIST_SORTING
+static const bool kDefaultSortOpLists = false;
+#else
+static const bool kDefaultSortOpLists = true;
+#endif
+
+class SK_API GrLegacyDirectContext : public GrContext {
+public:
+    GrLegacyDirectContext(GrBackendApi backend, const GrContextOptions& options)
+            : INHERITED(backend, options)
+            , fAtlasManager(nullptr) {
+    }
+
+    ~GrLegacyDirectContext() override {
+        // this if-test protects against the case where the context is being destroyed
+        // before having been fully created
+        if (this->priv().getGpu()) {
+            this->flush();
+        }
+
+        delete fAtlasManager;
+    }
+
+    void abandonContext() override {
+        INHERITED::abandonContext();
+        fAtlasManager->freeAll();
+    }
+
+    void releaseResourcesAndAbandonContext() override {
+        INHERITED::releaseResourcesAndAbandonContext();
+        fAtlasManager->freeAll();
+    }
+
+    void freeGpuResources() override {
+        this->flush();
+        fAtlasManager->freeAll();
+
+        INHERITED::freeGpuResources();
+    }
+
+protected:
+    bool init(sk_sp<const GrCaps> caps, sk_sp<GrSkSLFPFactoryCache> FPFactoryCache) override {
+        SkASSERT(caps && !FPFactoryCache);
+        SkASSERT(!fThreadSafeProxy);
+
+        FPFactoryCache.reset(new GrSkSLFPFactoryCache());
+        fThreadSafeProxy = GrContextThreadSafeProxyPriv::Make(this->backend(),
+                                                              this->options(),
+                                                              this->contextID(),
+                                                              caps, FPFactoryCache);
+
+        if (!INHERITED::init(std::move(caps), std::move(FPFactoryCache))) {
+            return false;
+        }
+
+        bool sortOpLists = kDefaultSortOpLists;
+        if (GrContextOptions::Enable::kNo == this->options().fSortRenderTargets) {
+            sortOpLists = false;
+        } else if (GrContextOptions::Enable::kYes == this->options().fSortRenderTargets) {
+            sortOpLists = true;
+        }
+
+        this->setupDrawingManager(this->explicitlyAllocateGPUResources(), sortOpLists);
+
+        SkASSERT(this->caps());
+
+        GrDrawOpAtlas::AllowMultitexturing allowMultitexturing;
+        if (GrContextOptions::Enable::kNo == this->options().fAllowMultipleGlyphCacheTextures ||
+            // multitexturing supported only if range can represent the index + texcoords fully
+            !(this->caps()->shaderCaps()->floatIs32Bits() ||
+              this->caps()->shaderCaps()->integerSupport())) {
+            allowMultitexturing = GrDrawOpAtlas::AllowMultitexturing::kNo;
+        } else {
+            allowMultitexturing = GrDrawOpAtlas::AllowMultitexturing::kYes;
+        }
+
+        GrStrikeCache* glyphCache = this->priv().getGrStrikeCache();
+        GrProxyProvider* proxyProvider = this->priv().proxyProvider();
+
+        fAtlasManager = new GrAtlasManager(proxyProvider, glyphCache,
+                                           this->options().fGlyphCacheTextureMaximumBytes,
+                                           allowMultitexturing);
+        this->priv().addOnFlushCallbackObject(fAtlasManager);
+
+        return true;
+    }
+
+    GrAtlasManager* onGetAtlasManager() override { return fAtlasManager; }
+
+private:
+    GrAtlasManager* fAtlasManager;
+
+    typedef GrContext INHERITED;
+};
+
+sk_sp<GrContext> GrContext::MakeGL(sk_sp<const GrGLInterface> interface) {
+    GrContextOptions defaultOptions;
+    return MakeGL(std::move(interface), defaultOptions);
+}
+
+sk_sp<GrContext> GrContext::MakeGL(const GrContextOptions& options) {
+    return MakeGL(nullptr, options);
+}
+
+sk_sp<GrContext> GrContext::MakeGL() {
+    GrContextOptions defaultOptions;
+    return MakeGL(nullptr, defaultOptions);
+}
+
+sk_sp<GrContext> GrContext::MakeGL(sk_sp<const GrGLInterface> interface,
+                                   const GrContextOptions& options) {
+    sk_sp<GrContext> context(new GrLegacyDirectContext(GrBackendApi::kOpenGL, options));
+
+    context->fGpu = GrGLGpu::Make(std::move(interface), options, context.get());
+    if (!context->fGpu) {
+        return nullptr;
+    }
+
+    if (!context->init(context->fGpu->refCaps(), nullptr)) {
+        return nullptr;
+    }
+    return context;
+}
+
+sk_sp<GrContext> GrContext::MakeMock(const GrMockOptions* mockOptions) {
+    GrContextOptions defaultOptions;
+    return MakeMock(mockOptions, defaultOptions);
+}
+
+sk_sp<GrContext> GrContext::MakeMock(const GrMockOptions* mockOptions,
+                                     const GrContextOptions& options) {
+    sk_sp<GrContext> context(new GrLegacyDirectContext(GrBackendApi::kMock, options));
+
+    context->fGpu = GrMockGpu::Make(mockOptions, options, context.get());
+    if (!context->fGpu) {
+        return nullptr;
+    }
+
+    if (!context->init(context->fGpu->refCaps(), nullptr)) {
+        return nullptr;
+    }
+    return context;
+}
+
+sk_sp<GrContext> GrContext::MakeVulkan(const GrVkBackendContext& backendContext) {
+#ifdef SK_VULKAN
+    GrContextOptions defaultOptions;
+    return MakeVulkan(backendContext, defaultOptions);
+#else
+    return nullptr;
+#endif
+}
+
+sk_sp<GrContext> GrContext::MakeVulkan(const GrVkBackendContext& backendContext,
+                                       const GrContextOptions& options) {
+#ifdef SK_VULKAN
+    GrContextOptions defaultOptions;
+    sk_sp<GrContext> context(new GrLegacyDirectContext(GrBackendApi::kVulkan, options));
+
+    context->fGpu = GrVkGpu::Make(backendContext, options, context.get());
+    if (!context->fGpu) {
+        return nullptr;
+    }
+
+    if (!context->init(context->fGpu->refCaps(), nullptr)) {
+        return nullptr;
+    }
+    return context;
+#else
+    return nullptr;
+#endif
+}
+
+#ifdef SK_METAL
+sk_sp<GrContext> GrContext::MakeMetal(void* device, void* queue) {
+    GrContextOptions defaultOptions;
+    return MakeMetal(device, queue, defaultOptions);
+}
+
+sk_sp<GrContext> GrContext::MakeMetal(void* device, void* queue, const GrContextOptions& options) {
+    sk_sp<GrContext> context(new GrLegacyDirectContext(GrBackendApi::kMetal, options));
+
+    context->fGpu = GrMtlTrampoline::MakeGpu(context.get(), options, device, queue);
+    if (!context->fGpu) {
+        return nullptr;
+    }
+
+    if (!context->init(context->fGpu->refCaps(), nullptr)) {
+        return nullptr;
+    }
+    return context;
+}
+#endif
+
diff --git a/src/gpu/GrMesh.h b/src/gpu/GrMesh.h
index a76ed87..5cfc5fb 100644
--- a/src/gpu/GrMesh.h
+++ b/src/gpu/GrMesh.h
@@ -10,6 +10,7 @@
 
 #include "GrBuffer.h"
 #include "GrPendingIOResource.h"
+#include "GrGpuBuffer.h"
 
 class GrPrimitiveProcessor;
 
@@ -30,7 +31,11 @@
     GrPrimitiveType primitiveType() const { return fPrimitiveType; }
 
     bool isIndexed() const { return SkToBool(fIndexBuffer.get()); }
-    bool isInstanced() const { return SkToBool(fInstanceBuffer.get()); }
+    GrPrimitiveRestart primitiveRestart() const {
+        return GrPrimitiveRestart(fFlags & Flags::kUsePrimitiveRestart);
+    }
+    bool isInstanced() const { return fFlags & Flags::kIsInstanced; }
+    bool hasInstanceData() const { return SkToBool(fInstanceBuffer.get()); }
     bool hasVertexData() const { return SkToBool(fVertexBuffer.get()); }
 
     void setNonIndexedNonInstanced(int vertexCount);
@@ -42,8 +47,9 @@
 
     void setInstanced(sk_sp<const GrBuffer> instanceBuffer, int instanceCount, int baseInstance,
                       int vertexCount);
-    void setIndexedInstanced(sk_sp<const GrBuffer>, int indexCount, sk_sp<const GrBuffer>,
-                             int instanceCount, int baseInstance, GrPrimitiveRestart);
+    void setIndexedInstanced(sk_sp<const GrBuffer> indexBuffer, int indexCount,
+                             sk_sp<const GrBuffer> instanceBuffer, int instanceCount,
+                             int baseInstance, GrPrimitiveRestart);
 
     void setVertexData(sk_sp<const GrBuffer> vertexBuffer, int baseVertex = 0);
 
@@ -75,19 +81,29 @@
     void sendToGpu(SendToGpuImpl*) const;
 
 private:
+    enum class Flags {
+        kNone = 0,
+        kUsePrimitiveRestart = 1 << 0,
+        kIsInstanced = 1 << 1,
+    };
+
+    GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(Flags);
+    GR_STATIC_ASSERT(Flags(GrPrimitiveRestart::kNo) == Flags::kNone);
+    GR_STATIC_ASSERT(Flags(GrPrimitiveRestart::kYes) == Flags::kUsePrimitiveRestart);
+
     GrPrimitiveType fPrimitiveType;
     sk_sp<const GrBuffer> fIndexBuffer;
     sk_sp<const GrBuffer> fInstanceBuffer;
     sk_sp<const GrBuffer> fVertexBuffer;
     int fBaseVertex;
-    GrPrimitiveRestart fPrimitiveRestart;
+    Flags fFlags;
 
     union {
-        struct { // When fIndexBuffer == nullptr and fInstanceBuffer == nullptr.
+        struct { // When fIndexBuffer == nullptr and isInstanced() == false.
             int   fVertexCount;
         } fNonIndexNonInstanceData;
 
-        struct { // When fIndexBuffer != nullptr and fInstanceBuffer == nullptr.
+        struct { // When fIndexBuffer != nullptr and isInstanced() == false.
             struct {
                 int   fIndexCount;
                 int   fPatternRepeatCount;
@@ -107,7 +123,7 @@
             };
         };
 
-        struct { // When fInstanceBuffer != nullptr.
+        struct { // When isInstanced() != false.
             struct {
                 int   fInstanceCount;
                 int   fBaseInstance;
@@ -126,11 +142,13 @@
     };
 };
 
+GR_MAKE_BITFIELD_CLASS_OPS(GrMesh::Flags);
+
 inline void GrMesh::setNonIndexedNonInstanced(int vertexCount) {
-    fIndexBuffer.reset(nullptr);
-    fInstanceBuffer.reset(nullptr);
+    fIndexBuffer.reset();
+    fInstanceBuffer.reset();
     fNonIndexNonInstanceData.fVertexCount = vertexCount;
-    fPrimitiveRestart = GrPrimitiveRestart::kNo;
+    fFlags = Flags::kNone;
 }
 
 inline void GrMesh::setIndexed(sk_sp<const GrBuffer> indexBuffer, int indexCount, int baseIndex,
@@ -147,7 +165,7 @@
     fNonPatternIndexData.fBaseIndex = baseIndex;
     fNonPatternIndexData.fMinIndexValue = minIndexValue;
     fNonPatternIndexData.fMaxIndexValue = maxIndexValue;
-    fPrimitiveRestart = primitiveRestart;
+    fFlags = Flags(primitiveRestart);
 }
 
 inline void GrMesh::setIndexedPatterned(sk_sp<const GrBuffer> indexBuffer, int indexCount,
@@ -164,12 +182,11 @@
     fIndexData.fPatternRepeatCount = patternRepeatCount;
     fPatternData.fVertexCount = vertexCount;
     fPatternData.fMaxPatternRepetitionsInIndexBuffer = maxPatternRepetitionsInIndexBuffer;
-    fPrimitiveRestart = GrPrimitiveRestart::kNo;
+    fFlags = Flags::kNone;
 }
 
 inline void GrMesh::setInstanced(sk_sp<const GrBuffer> instanceBuffer, int instanceCount,
                                  int baseInstance, int vertexCount) {
-    SkASSERT(instanceBuffer);
     SkASSERT(instanceCount >= 1);
     SkASSERT(baseInstance >= 0);
     fIndexBuffer.reset();
@@ -177,7 +194,7 @@
     fInstanceData.fInstanceCount = instanceCount;
     fInstanceData.fBaseInstance = baseInstance;
     fInstanceNonIndexData.fVertexCount = vertexCount;
-    fPrimitiveRestart = GrPrimitiveRestart::kNo;
+    fFlags = Flags::kIsInstanced;
 }
 
 inline void GrMesh::setIndexedInstanced(sk_sp<const GrBuffer> indexBuffer, int indexCount,
@@ -185,7 +202,6 @@
                                         int baseInstance, GrPrimitiveRestart primitiveRestart) {
     SkASSERT(indexBuffer);
     SkASSERT(indexCount >= 1);
-    SkASSERT(instanceBuffer);
     SkASSERT(instanceCount >= 1);
     SkASSERT(baseInstance >= 0);
     fIndexBuffer = std::move(indexBuffer);
@@ -193,7 +209,7 @@
     fInstanceData.fInstanceCount = instanceCount;
     fInstanceData.fBaseInstance = baseInstance;
     fInstanceIndexData.fIndexCount = indexCount;
-    fPrimitiveRestart = primitiveRestart;
+    fFlags = Flags::kIsInstanced | Flags(primitiveRestart);
 }
 
 inline void GrMesh::setVertexData(sk_sp<const GrBuffer> vertexBuffer, int baseVertex) {
@@ -213,7 +229,8 @@
             impl->sendIndexedInstancedMeshToGpu(
                     fPrimitiveType, fIndexBuffer.get(), fInstanceIndexData.fIndexCount, 0,
                     fVertexBuffer.get(), fBaseVertex, fInstanceBuffer.get(),
-                    fInstanceData.fInstanceCount, fInstanceData.fBaseInstance, fPrimitiveRestart);
+                    fInstanceData.fInstanceCount, fInstanceData.fBaseInstance,
+                    this->primitiveRestart());
         }
         return;
     }
@@ -226,11 +243,11 @@
     }
 
     if (0 == fIndexData.fPatternRepeatCount) {
-        impl->sendIndexedMeshToGpu(fPrimitiveType, fIndexBuffer.get(), fIndexData.fIndexCount,
-                                   fNonPatternIndexData.fBaseIndex,
-                                   fNonPatternIndexData.fMinIndexValue,
-                                   fNonPatternIndexData.fMaxIndexValue, fVertexBuffer.get(),
-                                   fBaseVertex, fPrimitiveRestart);
+        impl->sendIndexedMeshToGpu(
+                fPrimitiveType, fIndexBuffer.get(), fIndexData.fIndexCount,
+                fNonPatternIndexData.fBaseIndex, fNonPatternIndexData.fMinIndexValue,
+                fNonPatternIndexData.fMaxIndexValue, fVertexBuffer.get(), fBaseVertex,
+                this->primitiveRestart());
         return;
     }
 
@@ -242,7 +259,7 @@
         // A patterned index buffer must contain indices in the range [0..vertexCount].
         int minIndexValue = 0;
         int maxIndexValue = fPatternData.fVertexCount * repeatCount - 1;
-        SkASSERT(fPrimitiveRestart == GrPrimitiveRestart::kNo);
+        SkASSERT(!(fFlags & Flags::kUsePrimitiveRestart));
         impl->sendIndexedMeshToGpu(
                 fPrimitiveType, fIndexBuffer.get(), fIndexData.fIndexCount * repeatCount, 0,
                 minIndexValue, maxIndexValue, fVertexBuffer.get(),
diff --git a/src/gpu/GrOnFlushResourceProvider.cpp b/src/gpu/GrOnFlushResourceProvider.cpp
index a2c56e3..7349901 100644
--- a/src/gpu/GrOnFlushResourceProvider.cpp
+++ b/src/gpu/GrOnFlushResourceProvider.cpp
@@ -6,10 +6,12 @@
  */
 
 #include "GrOnFlushResourceProvider.h"
-#include "GrContext.h"
+
 #include "GrContextPriv.h"
 #include "GrDrawingManager.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrSurfaceProxy.h"
 
@@ -62,7 +64,15 @@
 }
 
 bool GrOnFlushResourceProvider::instatiateProxy(GrSurfaceProxy* proxy) {
-    auto resourceProvider = fDrawingMgr->getContext()->priv().resourceProvider();
+    SkASSERT(proxy->priv().requiresNoPendingIO());
+
+    // TODO: this class should probably just get a GrDirectContext
+    auto direct = fDrawingMgr->getContext()->priv().asDirectContext();
+    if (!direct) {
+        return false;
+    }
+
+    auto resourceProvider = direct->priv().resourceProvider();
 
     if (GrSurfaceProxy::LazyState::kNot != proxy->lazyInstantiationState()) {
         // DDL TODO: Decide if we ever plan to have these proxies use the GrDeinstantiateTracker
@@ -73,20 +83,32 @@
     return proxy->instantiate(resourceProvider);
 }
 
-sk_sp<GrBuffer> GrOnFlushResourceProvider::makeBuffer(GrGpuBufferType intendedType, size_t size,
-                                                      const void* data) {
-    auto resourceProvider = fDrawingMgr->getContext()->priv().resourceProvider();
-    return sk_sp<GrBuffer>(resourceProvider->createBuffer(size, intendedType,
-                                                          kDynamic_GrAccessPattern,
-                                                          GrResourceProvider::Flags::kNone,
-                                                          data));
+sk_sp<GrGpuBuffer> GrOnFlushResourceProvider::makeBuffer(GrGpuBufferType intendedType, size_t size,
+                                                         const void* data) {
+    // TODO: this class should probably just get a GrDirectContext
+    auto direct = fDrawingMgr->getContext()->priv().asDirectContext();
+    if (!direct) {
+        return nullptr;
+    }
+
+    auto resourceProvider = direct->priv().resourceProvider();
+
+    return sk_sp<GrGpuBuffer>(
+            resourceProvider->createBuffer(size, intendedType, kDynamic_GrAccessPattern, data));
 }
 
-sk_sp<const GrBuffer> GrOnFlushResourceProvider::findOrMakeStaticBuffer(
+sk_sp<const GrGpuBuffer> GrOnFlushResourceProvider::findOrMakeStaticBuffer(
         GrGpuBufferType intendedType, size_t size, const void* data, const GrUniqueKey& key) {
-    auto resourceProvider = fDrawingMgr->getContext()->priv().resourceProvider();
-    sk_sp<const GrBuffer> buffer = resourceProvider->findOrMakeStaticBuffer(intendedType, size,
-                                                                            data, key);
+    // TODO: class should probably just get a GrDirectContext
+    auto direct = fDrawingMgr->getContext()->priv().asDirectContext();
+    if (!direct) {
+        return nullptr;
+    }
+
+    auto resourceProvider = direct->priv().resourceProvider();
+
+    sk_sp<const GrGpuBuffer> buffer =
+            resourceProvider->findOrMakeStaticBuffer(intendedType, size, data, key);
     // Static buffers should never have pending IO.
     SkASSERT(!buffer || !buffer->resourcePriv().hasPendingIO_debugOnly());
     return buffer;
diff --git a/src/gpu/GrOnFlushResourceProvider.h b/src/gpu/GrOnFlushResourceProvider.h
index 11caf39..5851942 100644
--- a/src/gpu/GrOnFlushResourceProvider.h
+++ b/src/gpu/GrOnFlushResourceProvider.h
@@ -66,13 +66,6 @@
 public:
     explicit GrOnFlushResourceProvider(GrDrawingManager* drawingMgr) : fDrawingMgr(drawingMgr) {}
 
-#if 0
-    sk_sp<GrRenderTargetContext> makeRenderTargetContext(const GrSurfaceDesc&,
-                                                         GrSurfaceOrigin,
-                                                         sk_sp<SkColorSpace>,
-                                                         const SkSurfaceProps*);
-#endif
-
     sk_sp<GrRenderTargetContext> makeRenderTargetContext(sk_sp<GrSurfaceProxy>,
                                                          sk_sp<SkColorSpace>,
                                                          const SkSurfaceProps*);
@@ -86,11 +79,11 @@
     bool instatiateProxy(GrSurfaceProxy*);
 
     // Creates a GPU buffer with a "dynamic" access pattern.
-    sk_sp<GrBuffer> makeBuffer(GrGpuBufferType, size_t, const void* data = nullptr);
+    sk_sp<GrGpuBuffer> makeBuffer(GrGpuBufferType, size_t, const void* data = nullptr);
 
     // Either finds and refs, or creates a static GPU buffer with the given data.
-    sk_sp<const GrBuffer> findOrMakeStaticBuffer(GrGpuBufferType, size_t, const void* data,
-                                                 const GrUniqueKey&);
+    sk_sp<const GrGpuBuffer> findOrMakeStaticBuffer(GrGpuBufferType, size_t, const void* data,
+                                                    const GrUniqueKey&);
 
     uint32_t contextID() const;
     const GrCaps* caps() const;
diff --git a/src/gpu/GrOpFlushState.cpp b/src/gpu/GrOpFlushState.cpp
index 37ee176..0b0d9ba 100644
--- a/src/gpu/GrOpFlushState.cpp
+++ b/src/gpu/GrOpFlushState.cpp
@@ -16,12 +16,14 @@
 //////////////////////////////////////////////////////////////////////////////
 
 GrOpFlushState::GrOpFlushState(GrGpu* gpu, GrResourceProvider* resourceProvider,
-                               GrTokenTracker* tokenTracker, void* vertexSpace, void* indexSpace)
-        : fVertexPool(gpu, vertexSpace)
-        , fIndexPool(gpu, indexSpace)
+                               GrResourceCache* cache, GrTokenTracker* tokenTracker,
+                               sk_sp<GrBufferAllocPool::CpuBufferCache> cpuBufferCache)
+        : fVertexPool(gpu, cpuBufferCache)
+        , fIndexPool(gpu, std::move(cpuBufferCache))
         , fGpu(gpu)
         , fResourceProvider(resourceProvider)
-        , fTokenTracker(tokenTracker) {}
+        , fTokenTracker(tokenTracker)
+        , fDeinstantiateProxyTracker(cache) {}
 
 const GrCaps& GrOpFlushState::caps() const {
     return *fGpu->caps();
@@ -31,8 +33,19 @@
     return fCommandBuffer->asRTCommandBuffer();
 }
 
-void GrOpFlushState::executeDrawsAndUploadsForMeshDrawOp(const GrOp* op, const SkRect& opBounds) {
+void GrOpFlushState::executeDrawsAndUploadsForMeshDrawOp(
+        const GrOp* op, const SkRect& chainBounds, GrProcessorSet&& processorSet,
+        GrPipeline::InputFlags pipelineFlags, const GrUserStencilSettings* stencilSettings) {
     SkASSERT(this->rtCommandBuffer());
+
+    GrPipeline::InitArgs pipelineArgs;
+    pipelineArgs.fInputFlags = pipelineFlags;
+    pipelineArgs.fDstProxy = this->dstProxy();
+    pipelineArgs.fCaps = &this->caps();
+    pipelineArgs.fResourceProvider = this->resourceProvider();
+    pipelineArgs.fUserStencil = stencilSettings;
+    GrPipeline pipeline(pipelineArgs, std::move(processorSet), this->detachAppliedClip());
+
     while (fCurrDraw != fDraws.end() && fCurrDraw->fOp == op) {
         GrDeferredUploadToken drawToken = fTokenTracker->nextTokenToFlush();
         while (fCurrUpload != fInlineUploads.end() &&
@@ -40,9 +53,10 @@
             this->rtCommandBuffer()->inlineUpload(this, fCurrUpload->fUpload);
             ++fCurrUpload;
         }
-        this->rtCommandBuffer()->draw(*fCurrDraw->fGeometryProcessor, *fCurrDraw->fPipeline,
-                                      fCurrDraw->fFixedDynamicState, fCurrDraw->fDynamicStateArrays,
-                                      fCurrDraw->fMeshes, fCurrDraw->fMeshCnt, opBounds);
+        this->rtCommandBuffer()->draw(
+                *fCurrDraw->fGeometryProcessor, pipeline, fCurrDraw->fFixedDynamicState,
+                fCurrDraw->fDynamicStateArrays, fCurrDraw->fMeshes, fCurrDraw->fMeshCnt,
+                chainBounds);
         fTokenTracker->flushToken();
         ++fCurrDraw;
     }
@@ -97,10 +111,10 @@
     return fTokenTracker->nextTokenToFlush();
 }
 
-void GrOpFlushState::draw(sk_sp<const GrGeometryProcessor> gp, const GrPipeline* pipeline,
-                          const GrPipeline::FixedDynamicState* fixedDynamicState,
-                          const GrPipeline::DynamicStateArrays* dynamicStateArrays,
-                          const GrMesh meshes[], int meshCnt) {
+void GrOpFlushState::recordDraw(
+        sk_sp<const GrGeometryProcessor> gp, const GrMesh meshes[], int meshCnt,
+        const GrPipeline::FixedDynamicState* fixedDynamicState,
+        const GrPipeline::DynamicStateArrays* dynamicStateArrays) {
     SkASSERT(fOpArgs);
     SkASSERT(fOpArgs->fOp);
     bool firstDraw = fDraws.begin() == fDraws.end();
@@ -118,7 +132,6 @@
         }
     }
     draw.fGeometryProcessor = std::move(gp);
-    draw.fPipeline = pipeline;
     draw.fFixedDynamicState = fixedDynamicState;
     draw.fDynamicStateArrays = dynamicStateArrays;
     draw.fMeshes = meshes;
@@ -166,7 +179,7 @@
 }
 
 GrStrikeCache* GrOpFlushState::glyphCache() const {
-    return fGpu->getContext()->priv().getGlyphCache();
+    return fGpu->getContext()->priv().getGrStrikeCache();
 }
 
 GrAtlasManager* GrOpFlushState::atlasManager() const {
diff --git a/src/gpu/GrOpFlushState.h b/src/gpu/GrOpFlushState.h
index 9159d49..66aa470 100644
--- a/src/gpu/GrOpFlushState.h
+++ b/src/gpu/GrOpFlushState.h
@@ -29,8 +29,8 @@
     // vertexSpace and indexSpace may either be null or an alloation of size
     // GrBufferAllocPool::kDefaultBufferSize. If the latter, then CPU memory is only allocated for
     // vertices/indices when a buffer larger than kDefaultBufferSize is required.
-    GrOpFlushState(GrGpu*, GrResourceProvider*, GrTokenTracker*, void* vertexSpace,
-                   void* indexSpace);
+    GrOpFlushState(GrGpu*, GrResourceProvider*, GrResourceCache*, GrTokenTracker*,
+                   sk_sp<GrBufferAllocPool::CpuBufferCache> = nullptr);
 
     ~GrOpFlushState() final { this->reset(); }
 
@@ -41,7 +41,10 @@
     void doUpload(GrDeferredTextureUploadFn&);
 
     /** Called as ops are executed. Must be called in the same order as the ops were prepared. */
-    void executeDrawsAndUploadsForMeshDrawOp(const GrOp* op, const SkRect& opBounds);
+    void executeDrawsAndUploadsForMeshDrawOp(
+            const GrOp* op, const SkRect& chainBounds, GrProcessorSet&&,
+            GrPipeline::InputFlags = GrPipeline::InputFlags::kNone,
+            const GrUserStencilSettings* = &GrUserStencilSettings::kUnused);
 
     GrGpuCommandBuffer* commandBuffer() { return fCommandBuffer; }
     // Helper function used by Ops that are only called via RenderTargetOpLists
@@ -79,12 +82,9 @@
     GrDeferredUploadToken addASAPUpload(GrDeferredTextureUploadFn&&) final;
 
     /** Overrides of GrMeshDrawOp::Target. */
-    void draw(sk_sp<const GrGeometryProcessor>,
-              const GrPipeline*,
-              const GrPipeline::FixedDynamicState*,
-              const GrPipeline::DynamicStateArrays*,
-              const GrMesh[],
-              int meshCnt) final;
+    void recordDraw(
+            sk_sp<const GrGeometryProcessor>, const GrMesh[], int meshCnt,
+            const GrPipeline::FixedDynamicState*, const GrPipeline::DynamicStateArrays*) final;
     void* makeVertexSpace(size_t vertexSize, int vertexCount, sk_sp<const GrBuffer>*,
                           int* startVertex) final;
     uint16_t* makeIndexSpace(int indexCount, sk_sp<const GrBuffer>*, int* startIndex) final;
@@ -97,6 +97,7 @@
     void putBackIndices(int indexCount) final;
     void putBackVertices(int vertices, size_t vertexStride) final;
     GrRenderTargetProxy* proxy() const final { return fOpArgs->fProxy; }
+    const GrAppliedClip* appliedClip() final { return fOpArgs->fAppliedClip; }
     GrAppliedClip detachAppliedClip() final;
     const GrXferProcessor::DstProxy& dstProxy() const final { return fOpArgs->fDstProxy; }
     GrDeferredUploadTarget* deferredUploadTarget() final { return this; }
@@ -113,7 +114,7 @@
 
 private:
     /** GrMeshDrawOp::Target override. */
-    SkArenaAlloc* pipelineArena() override { return &fArena; }
+    SkArenaAlloc* allocator() override { return &fArena; }
 
     struct InlineUpload {
         InlineUpload(GrDeferredTextureUploadFn&& upload, GrDeferredUploadToken token)
@@ -129,7 +130,6 @@
     struct Draw {
         ~Draw();
         sk_sp<const GrGeometryProcessor> fGeometryProcessor;
-        const GrPipeline* fPipeline = nullptr;
         const GrPipeline::FixedDynamicState* fFixedDynamicState;
         const GrPipeline::DynamicStateArrays* fDynamicStateArrays;
         const GrMesh* fMeshes = nullptr;
diff --git a/src/gpu/GrOpList.cpp b/src/gpu/GrOpList.cpp
index b1ccc82..4c20a24 100644
--- a/src/gpu/GrOpList.cpp
+++ b/src/gpu/GrOpList.cpp
@@ -54,8 +54,14 @@
     }
 }
 
+// TODO: this can go away when explicit allocation has stuck
 bool GrOpList::instantiate(GrResourceProvider* resourceProvider) {
-    return SkToBool(fTarget.get()->instantiate(resourceProvider));
+    if (resourceProvider->explicitlyAllocateGPUResources()) {
+        SkASSERT(fTarget.get()->isInstantiated());
+        return true;
+    } else {
+        return SkToBool(fTarget.get()->instantiate(resourceProvider));
+    }
 }
 
 void GrOpList::endFlush() {
diff --git a/src/gpu/GrPaint.cpp b/src/gpu/GrPaint.cpp
index ee09a26..cb1ab0c 100644
--- a/src/gpu/GrPaint.cpp
+++ b/src/gpu/GrPaint.cpp
@@ -9,7 +9,7 @@
 #include "GrXferProcessor.h"
 #include "effects/GrCoverageSetOpXP.h"
 #include "effects/GrPorterDuffXferProcessor.h"
-#include "effects/GrSimpleTextureEffect.h"
+#include "effects/generated/GrSimpleTextureEffect.h"
 
 GrPaint::GrPaint(const GrPaint& that)
         : fXPFactory(that.fXPFactory)
diff --git a/src/gpu/GrPathRenderer.cpp b/src/gpu/GrPathRenderer.cpp
index d16ff49..1595207 100644
--- a/src/gpu/GrPathRenderer.cpp
+++ b/src/gpu/GrPathRenderer.cpp
@@ -7,8 +7,8 @@
 
 #include "GrPathRenderer.h"
 #include "GrCaps.h"
-#include "GrContextPriv.h"
 #include "GrPaint.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrShape.h"
 #include "GrUserStencilSettings.h"
@@ -22,7 +22,6 @@
     SkASSERT(fViewMatrix);
     SkASSERT(fShape);
     SkASSERT(fShape->style().isSimpleFill());
-    SkASSERT(GrAAType::kCoverage != fAAType);
     SkPath path;
     fShape->asPath(&path);
     SkASSERT(!path.isInverseFillType());
@@ -49,15 +48,14 @@
     canArgs.fClipConservativeBounds = args.fClipConservativeBounds;
     canArgs.fViewMatrix = args.fViewMatrix;
     canArgs.fShape = args.fShape;
-    canArgs.fAAType = args.fAAType;
+    canArgs.fAATypeFlags = args.fAATypeFlags;
     canArgs.fTargetIsWrappedVkSecondaryCB = args.fRenderTargetContext->wrapsVkSecondaryCB();
     canArgs.validate();
 
     canArgs.fHasUserStencilSettings = !args.fUserStencilSettings->isUnused();
-    SkASSERT(!(canArgs.fAAType == GrAAType::kMSAA &&
-               GrFSAAType::kUnifiedMSAA != args.fRenderTargetContext->fsaaType()));
-    SkASSERT(!(canArgs.fAAType == GrAAType::kMixedSamples &&
-               GrFSAAType::kMixedSamples != args.fRenderTargetContext->fsaaType()));
+    if (AATypeFlags::kMixedSampledStencilThenCover & canArgs.fAATypeFlags) {
+        SkASSERT(GrFSAAType::kMixedSamples == args.fRenderTargetContext->fsaaType());
+    }
     SkASSERT(CanDrawPath::kNo != this->canDrawPath(canArgs));
     if (!args.fUserStencilSettings->isUnused()) {
         SkPath path;
@@ -118,7 +116,9 @@
                           args.fClipConservativeBounds,
                           args.fViewMatrix,
                           args.fShape,
-                          args.fAAType,
+                          (GrAA::kYes == args.fDoStencilMSAA)
+                                  ? AATypeFlags::kMSAA
+                                  : AATypeFlags::kNone,
                           false};
     this->drawPath(drawArgs);
 }
diff --git a/src/gpu/GrPathRenderer.h b/src/gpu/GrPathRenderer.h
index a6c3ed5..aac401e 100644
--- a/src/gpu/GrPathRenderer.h
+++ b/src/gpu/GrPathRenderer.h
@@ -14,10 +14,10 @@
 
 class GrCaps;
 class GrClip;
-class GrContext;
 class GrFixedClip;
 class GrHardClip;
 class GrPaint;
+class GrRecordingContext;
 class GrRenderTargetContext;
 class GrShape;
 class GrStyle;
@@ -72,6 +72,17 @@
         kYes
     };
 
+    /**
+     * This enum defines a set of flags indicating which AA methods would be acceptable for a path
+     * renderer to employ (if any) while drawing a given path.
+     */
+    enum class AATypeFlags {
+        kNone = 0,
+        kCoverage = (1 << 0),
+        kMSAA = (1 << 1),
+        kMixedSampledStencilThenCover = (1 << 2),
+    };
+
     struct CanDrawPathArgs {
         SkDEBUGCODE(CanDrawPathArgs() { memset(this, 0, sizeof(*this)); }) // For validation.
 
@@ -79,7 +90,7 @@
         const SkIRect*              fClipConservativeBounds;
         const SkMatrix*             fViewMatrix;
         const GrShape*              fShape;
-        GrAAType                    fAAType;
+        AATypeFlags                 fAATypeFlags;
         bool                        fTargetIsWrappedVkSecondaryCB;
 
         // This is only used by GrStencilAndCoverPathRenderer
@@ -106,7 +117,7 @@
     }
 
     struct DrawPathArgs {
-        GrContext*                   fContext;
+        GrRecordingContext*          fContext;
         GrPaint&&                    fPaint;
         const GrUserStencilSettings* fUserStencilSettings;
         GrRenderTargetContext*       fRenderTargetContext;
@@ -114,7 +125,7 @@
         const SkIRect*               fClipConservativeBounds;
         const SkMatrix*              fViewMatrix;
         const GrShape*               fShape;
-        GrAAType                     fAAType;
+        AATypeFlags                  fAATypeFlags;
         bool                         fGammaCorrect;
 #ifdef SK_DEBUG
         void validate() const {
@@ -140,13 +151,13 @@
     struct StencilPathArgs {
         SkDEBUGCODE(StencilPathArgs() { memset(this, 0, sizeof(*this)); }) // For validation.
 
-        GrContext*             fContext;
+        GrRecordingContext*    fContext;
         GrRenderTargetContext* fRenderTargetContext;
         const GrHardClip*      fClip;
         const SkIRect*         fClipConservativeBounds;
         const SkMatrix*        fViewMatrix;
-        GrAAType               fAAType;
         const GrShape*         fShape;
+        GrAA                   fDoStencilMSAA;
 
         SkDEBUGCODE(void validate() const);
     };
@@ -202,4 +213,6 @@
     typedef SkRefCnt INHERITED;
 };
 
+GR_MAKE_BITFIELD_CLASS_OPS(GrPathRenderer::AATypeFlags);
+
 #endif
diff --git a/src/gpu/GrPathRendererChain.cpp b/src/gpu/GrPathRendererChain.cpp
index 64eb986..93f3f11 100644
--- a/src/gpu/GrPathRendererChain.cpp
+++ b/src/gpu/GrPathRendererChain.cpp
@@ -7,11 +7,14 @@
 
 
 #include "GrPathRendererChain.h"
+
 #include "GrCaps.h"
-#include "GrShaderCaps.h"
 #include "GrContext.h"
 #include "GrContextPriv.h"
 #include "GrGpu.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
+#include "GrShaderCaps.h"
 #include "ccpr/GrCoverageCountingPathRenderer.h"
 #include "ops/GrAAConvexPathRenderer.h"
 #include "ops/GrAAHairLinePathRenderer.h"
@@ -22,16 +25,21 @@
 #include "ops/GrStencilAndCoverPathRenderer.h"
 #include "ops/GrTessellatingPathRenderer.h"
 
-GrPathRendererChain::GrPathRendererChain(GrContext* context, const Options& options) {
+GrPathRendererChain::GrPathRendererChain(GrRecordingContext* context, const Options& options) {
     const GrCaps& caps = *context->priv().caps();
     if (options.fGpuPathRenderers & GpuPathRenderers::kDashLine) {
         fChain.push_back(sk_make_sp<GrDashLinePathRenderer>());
     }
     if (options.fGpuPathRenderers & GpuPathRenderers::kStencilAndCover) {
-        sk_sp<GrPathRenderer> pr(
-           GrStencilAndCoverPathRenderer::Create(context->priv().resourceProvider(), caps));
-        if (pr) {
-            fChain.push_back(std::move(pr));
+        auto direct = context->priv().asDirectContext();
+        if (direct) {
+            auto resourceProvider = direct->priv().resourceProvider();
+
+            sk_sp<GrPathRenderer> pr(
+               GrStencilAndCoverPathRenderer::Create(resourceProvider, caps));
+            if (pr) {
+                fChain.push_back(std::move(pr));
+            }
         }
     }
     if (options.fGpuPathRenderers & GpuPathRenderers::kAAConvex) {
diff --git a/src/gpu/GrPathRendererChain.h b/src/gpu/GrPathRendererChain.h
index e368450..3542ec4 100644
--- a/src/gpu/GrPathRendererChain.h
+++ b/src/gpu/GrPathRendererChain.h
@@ -30,7 +30,7 @@
         bool fAllowPathMaskCaching = false;
         GpuPathRenderers fGpuPathRenderers = GpuPathRenderers::kAll;
     };
-    GrPathRendererChain(GrContext* context, const Options&);
+    GrPathRendererChain(GrRecordingContext* context, const Options&);
 
     /** Documents how the caller plans to use a GrPathRenderer to draw a path. It affects the PR
         returned by getPathRenderer */
diff --git a/src/gpu/GrPathRendering_none.cpp b/src/gpu/GrPathRendering_none.cpp
index ce46ad4..542ebdc 100644
--- a/src/gpu/GrPathRendering_none.cpp
+++ b/src/gpu/GrPathRendering_none.cpp
@@ -17,6 +17,8 @@
 #include "ops/GrStencilAndCoverPathRenderer.h"
 #include "ops/GrStencilPathOp.h"
 
+class GrRecordingContext;
+
 GrPathRenderer* GrStencilAndCoverPathRenderer::Create(GrResourceProvider* resourceProvider,
                                                       const GrCaps& caps) {
     return nullptr;
@@ -49,7 +51,7 @@
 
 void GrGLPathRendering::onStencilPath(const StencilPathArgs&, const GrPath*) {}
 
-std::unique_ptr<GrOp> GrStencilPathOp::Make(GrContext*,
+std::unique_ptr<GrOp> GrStencilPathOp::Make(GrRecordingContext*,
                                             const SkMatrix&,
                                             bool,
                                             GrPathRendering::FillType,
diff --git a/src/gpu/GrPendingIOResource.h b/src/gpu/GrPendingIOResource.h
index e56fa09..e80429a 100644
--- a/src/gpu/GrPendingIOResource.h
+++ b/src/gpu/GrPendingIOResource.h
@@ -20,7 +20,7 @@
 class GrPendingIOResource : SkNoncopyable {
 public:
     GrPendingIOResource() = default;
-    explicit GrPendingIOResource(T* resource) { this->reset(resource); }
+    GrPendingIOResource(T* resource) { this->reset(resource); }
     GrPendingIOResource(sk_sp<T> resource) { *this = std::move(resource); }
     GrPendingIOResource(const GrPendingIOResource& that) : GrPendingIOResource(that.get()) {}
     ~GrPendingIOResource() { this->release(); }
@@ -54,6 +54,8 @@
     bool operator==(const GrPendingIOResource& other) const { return fResource == other.fResource; }
 
     T* get() const { return fResource; }
+    T* operator*() const { return *fResource; }
+    T* operator->() const { return fResource; }
 
 private:
     void release() {
diff --git a/src/gpu/GrPersistentCacheUtils.h b/src/gpu/GrPersistentCacheUtils.h
new file mode 100644
index 0000000..a3c34ef
--- /dev/null
+++ b/src/gpu/GrPersistentCacheUtils.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrPersistentCacheEntry_DEFINED
+#define GrPersistentCacheEntry_DEFINED
+
+#include "GrTypesPriv.h"
+#include "SkReader32.h"
+#include "ir/SkSLProgram.h"
+#include "SkSLString.h"
+#include "SkWriter32.h"
+
+// The GrPersistentCache stores opaque blobs, as far as clients are concerned. It's helpful to
+// inspect certain kinds of cached data within our tools, so for those cases (GLSL, SPIR-V), we
+// put the serialization logic here, to be shared by the backend code and the tool code.
+namespace GrPersistentCacheUtils {
+
+static inline void PackCachedGLSL(SkWriter32& writer, const SkSL::Program::Inputs& inputs,
+                                  const SkSL::String glsl[]) {
+    writer.writePad(&inputs, sizeof(inputs));
+    for (int i = 0; i < kGrShaderTypeCount; ++i) {
+        writer.writeString(glsl[i].c_str(), glsl[i].size());
+    }
+}
+
+static inline void UnpackCachedGLSL(SkReader32& reader, SkSL::Program::Inputs* inputs,
+                                    SkSL::String glsl[]) {
+    reader.read(inputs, sizeof(*inputs));
+    for (int i = 0; i < kGrShaderTypeCount; ++i) {
+        size_t stringLen = 0;
+        const char* string = reader.readString(&stringLen);
+        glsl[i] = SkSL::String(string, stringLen);
+    }
+}
+
+static inline void PackCachedSPIRV(SkWriter32& writer, const SkSL::String shaders[],
+                                   const SkSL::Program::Inputs inputs[]) {
+    for (int i = 0; i < kGrShaderTypeCount; ++i) {
+        writer.writeString(shaders[i].c_str(), shaders[i].size());
+        writer.writePad(&inputs[i], sizeof(inputs[i]));
+    }
+}
+
+static inline void UnpackCachedSPIRV(SkReader32& reader, SkSL::String shaders[],
+                                     SkSL::Program::Inputs inputs[]) {
+    for (int i = 0; i < kGrShaderTypeCount; ++i) {
+        size_t stringLen = 0;
+        const char* string = reader.readString(&stringLen);
+        shaders[i] = SkSL::String(string, stringLen);
+        reader.read(&inputs[i], sizeof(inputs[i]));
+    }
+}
+
+}
+
+#endif
diff --git a/src/gpu/GrPipeline.cpp b/src/gpu/GrPipeline.cpp
index 537c543..2c15f32 100644
--- a/src/gpu/GrPipeline.cpp
+++ b/src/gpu/GrPipeline.cpp
@@ -21,17 +21,17 @@
                        GrAppliedClip&& appliedClip) {
     SkASSERT(processors.isFinalized());
 
-    fFlags = args.fFlags;
+    fFlags = (Flags)args.fInputFlags;
     if (appliedClip.hasStencilClip()) {
-        fFlags |= kHasStencilClip_Flag;
+        fFlags |= Flags::kHasStencilClip;
     }
     if (appliedClip.scissorState().enabled()) {
-        fFlags |= kScissorEnabled_Flag;
+        fFlags |= Flags::kScissorEnabled;
     }
 
     fWindowRectsState = appliedClip.windowRectsState();
-    if (!args.fUserStencil->isDisabled(fFlags & kHasStencilClip_Flag)) {
-        fFlags |= kStencilEnabled_Flag;
+    if (!args.fUserStencil->isDisabled(fFlags & Flags::kHasStencilClip)) {
+        fFlags |= Flags::kStencilEnabled;
     }
 
     fUserStencilSettings = args.fUserStencil;
@@ -39,7 +39,9 @@
     fXferProcessor = processors.refXferProcessor();
 
     if (args.fDstProxy.proxy()) {
-        if (!args.fDstProxy.proxy()->instantiate(args.fResourceProvider)) {
+        if (args.fResourceProvider->explicitlyAllocateGPUResources()) {
+            SkASSERT(args.fDstProxy.proxy()->isInstantiated());
+        } else if (!args.fDstProxy.proxy()->instantiate(args.fResourceProvider)) {
             this->markAsBad();
         }
 
@@ -95,14 +97,35 @@
     return this->getXferProcessor().xferBarrierType(caps);
 }
 
-GrPipeline::GrPipeline(GrScissorTest scissorTest, SkBlendMode blendmode)
+GrPipeline::GrPipeline(GrScissorTest scissorTest, SkBlendMode blendmode, InputFlags inputFlags,
+                       const GrUserStencilSettings* userStencil)
         : fWindowRectsState()
-        , fUserStencilSettings(&GrUserStencilSettings::kUnused)
-        , fFlags()
+        , fUserStencilSettings(userStencil)
+        , fFlags((Flags)inputFlags)
         , fXferProcessor(GrPorterDuffXPFactory::MakeNoCoverageXP(blendmode))
         , fFragmentProcessors()
         , fNumColorProcessors(0) {
     if (GrScissorTest::kEnabled == scissorTest) {
-        fFlags |= kScissorEnabled_Flag;
+        fFlags |= Flags::kScissorEnabled;
     }
+    if (!userStencil->isDisabled(false)) {
+        fFlags |= Flags::kStencilEnabled;
+    }
+}
+
+uint32_t GrPipeline::getBlendInfoKey() const {
+    GrXferProcessor::BlendInfo blendInfo;
+    this->getXferProcessor().getBlendInfo(&blendInfo);
+
+    static const uint32_t kBlendWriteShift = 1;
+    static const uint32_t kBlendCoeffShift = 5;
+    GR_STATIC_ASSERT(kLast_GrBlendCoeff < (1 << kBlendCoeffShift));
+    GR_STATIC_ASSERT(kFirstAdvancedGrBlendEquation - 1 < 4);
+
+    uint32_t key = blendInfo.fWriteColor;
+    key |= (blendInfo.fSrcBlend << kBlendWriteShift);
+    key |= (blendInfo.fDstBlend << (kBlendWriteShift + kBlendCoeffShift));
+    key |= (blendInfo.fEquation << (kBlendWriteShift + 2 * kBlendCoeffShift));
+
+    return key;
 }
diff --git a/src/gpu/GrPipeline.h b/src/gpu/GrPipeline.h
index 91f619e..39dc0c3 100644
--- a/src/gpu/GrPipeline.h
+++ b/src/gpu/GrPipeline.h
@@ -23,7 +23,7 @@
 #include "effects/GrCoverageSetOpXP.h"
 #include "effects/GrDisableColorXP.h"
 #include "effects/GrPorterDuffXferProcessor.h"
-#include "effects/GrSimpleTextureEffect.h"
+#include "effects/generated/GrSimpleTextureEffect.h"
 
 class GrAppliedClip;
 class GrOp;
@@ -39,21 +39,24 @@
     ///////////////////////////////////////////////////////////////////////////
     /// @name Creation
 
-    enum Flags {
+    // Pipeline options that the caller may enable.
+    // NOTE: This enum is extended later by GrPipeline::Flags.
+    enum class InputFlags : uint8_t {
+        kNone = 0,
         /**
          * Perform HW anti-aliasing. This means either HW FSAA, if supported by the render target,
          * or smooth-line rendering if a line primitive is drawn and line smoothing is supported by
          * the 3D API.
          */
-        kHWAntialias_Flag = 0x1,
+        kHWAntialias = (1 << 0),
         /**
          * Modifies the vertex shader so that vertices will be positioned at pixel centers.
          */
-        kSnapVerticesToPixelCenters_Flag = 0x2,
+        kSnapVerticesToPixelCenters = (1 << 1),  // This value must be last. (See kLastInputFlag.)
     };
 
     struct InitArgs {
-        uint32_t fFlags = 0;
+        InputFlags fInputFlags = InputFlags::kNone;
         const GrUserStencilSettings* fUserStencil = &GrUserStencilSettings::kUnused;
         const GrCaps* fCaps = nullptr;
         GrResourceProvider* fResourceProvider = nullptr;
@@ -93,7 +96,8 @@
      * must be "Porter Duff" (<= kLastCoeffMode). If using GrScissorTest::kEnabled, the caller must
      * specify a scissor rectangle through the DynamicState struct.
      **/
-    GrPipeline(GrScissorTest, SkBlendMode);
+    GrPipeline(GrScissorTest, SkBlendMode, InputFlags = InputFlags::kNone,
+               const GrUserStencilSettings* = &GrUserStencilSettings::kUnused);
 
     GrPipeline(const InitArgs&, GrProcessorSet&&, GrAppliedClip&&);
 
@@ -162,50 +166,45 @@
     const GrUserStencilSettings* getUserStencil() const { return fUserStencilSettings; }
 
     bool isScissorEnabled() const {
-        return SkToBool(fFlags & kScissorEnabled_Flag);
+        return SkToBool(fFlags & Flags::kScissorEnabled);
     }
 
     const GrWindowRectsState& getWindowRectsState() const { return fWindowRectsState; }
 
-    bool isHWAntialiasState() const { return SkToBool(fFlags & kHWAntialias_Flag); }
+    bool isHWAntialiasState() const { return SkToBool(fFlags & InputFlags::kHWAntialias); }
     bool snapVerticesToPixelCenters() const {
-        return SkToBool(fFlags & kSnapVerticesToPixelCenters_Flag);
+        return SkToBool(fFlags & InputFlags::kSnapVerticesToPixelCenters);
     }
     bool hasStencilClip() const {
-        return SkToBool(fFlags & kHasStencilClip_Flag);
+        return SkToBool(fFlags & Flags::kHasStencilClip);
     }
     bool isStencilEnabled() const {
-        return SkToBool(fFlags & kStencilEnabled_Flag);
+        return SkToBool(fFlags & Flags::kStencilEnabled);
     }
-    bool isBad() const { return SkToBool(fFlags & kIsBad_Flag); }
+    bool isBad() const { return SkToBool(fFlags & Flags::kIsBad); }
 
     GrXferBarrierType xferBarrierType(GrTexture*, const GrCaps&) const;
 
-    static SkString DumpFlags(uint32_t flags) {
-        if (flags) {
-            SkString result;
-            if (flags & GrPipeline::kSnapVerticesToPixelCenters_Flag) {
-                result.append("Snap vertices to pixel center.\n");
-            }
-            if (flags & GrPipeline::kHWAntialias_Flag) {
-                result.append("HW Antialiasing enabled.\n");
-            }
-            return result;
-        }
-        return SkString("No pipeline flags\n");
-    }
+    // Used by Vulkan and Metal to cache their respective pipeline objects
+    uint32_t getBlendInfoKey() const;
 
 private:
-    void markAsBad() { fFlags |= kIsBad_Flag; }
+    void markAsBad() { fFlags |= Flags::kIsBad; }
 
-    /** This is a continuation of the public "Flags" enum. */
-    enum PrivateFlags {
-        kHasStencilClip_Flag = 0x10,
-        kStencilEnabled_Flag = 0x20,
-        kScissorEnabled_Flag = 0x40,
-        kIsBad_Flag = 0x80,
+    static constexpr uint8_t kLastInputFlag = (uint8_t)InputFlags::kSnapVerticesToPixelCenters;
+
+    /** This is a continuation of the public "InputFlags" enum. */
+    enum class Flags : uint8_t {
+        kHasStencilClip = (kLastInputFlag << 1),
+        kStencilEnabled = (kLastInputFlag << 2),
+        kScissorEnabled = (kLastInputFlag << 3),
+        kIsBad = (kLastInputFlag << 4),
     };
 
+    GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(Flags);
+
+    friend bool operator&(Flags, InputFlags);
+
     using DstTextureProxy = GrPendingIOResource<GrTextureProxy, kRead_GrIOType>;
     using FragmentProcessorArray = SkAutoSTArray<8, std::unique_ptr<const GrFragmentProcessor>>;
 
@@ -213,7 +212,7 @@
     SkIPoint fDstTextureOffset;
     GrWindowRectsState fWindowRectsState;
     const GrUserStencilSettings* fUserStencilSettings;
-    uint16_t fFlags;
+    Flags fFlags;
     sk_sp<const GrXferProcessor> fXferProcessor;
     FragmentProcessorArray fFragmentProcessors;
 
@@ -221,4 +220,11 @@
     int fNumColorProcessors;
 };
 
+GR_MAKE_BITFIELD_CLASS_OPS(GrPipeline::InputFlags);
+GR_MAKE_BITFIELD_CLASS_OPS(GrPipeline::Flags);
+
+inline bool operator&(GrPipeline::Flags flags, GrPipeline::InputFlags inputFlag) {
+    return (flags & (GrPipeline::Flags)inputFlag);
+}
+
 #endif
diff --git a/src/gpu/GrProcessor.h b/src/gpu/GrProcessor.h
index 8ae9493..0e6d7bc 100644
--- a/src/gpu/GrProcessor.h
+++ b/src/gpu/GrProcessor.h
@@ -8,8 +8,8 @@
 #ifndef GrProcessor_DEFINED
 #define GrProcessor_DEFINED
 
-#include "GrBuffer.h"
 #include "GrColor.h"
+#include "GrGpuBuffer.h"
 #include "GrProcessorUnitTest.h"
 #include "GrSamplerState.h"
 #include "GrShaderVar.h"
@@ -83,7 +83,6 @@
         kEllipticalRRectEffect_ClassID,
         kGP_ClassID,
         kVertexColorSpaceBenchGP_ClassID,
-        kGrAAFillRRectOp_Processor_ClassID,
         kGrAARectEffect_ClassID,
         kGrAlphaThresholdFragmentProcessor_ClassID,
         kGrArithmeticFP_ClassID,
@@ -91,12 +90,13 @@
         kGrBitmapTextGeoProc_ClassID,
         kGrBlurredEdgeFragmentProcessor_ClassID,
         kGrCCClipProcessor_ClassID,
-        kGrCCCoverageProcessor_ClassID,
         kGrCCPathProcessor_ClassID,
         kGrCircleBlurFragmentProcessor_ClassID,
         kGrCircleEffect_ClassID,
         kGrClampedGradientEffect_ClassID,
         kGrColorSpaceXformEffect_ClassID,
+        kGrComposeLerpEffect_ClassID,
+        kGrComposeLerpRedEffect_ClassID,
         kGrConfigConversionEffect_ClassID,
         kGrConicEffect_ClassID,
         kGrConstColorProcessor_ClassID,
@@ -110,7 +110,9 @@
         kGrDitherEffect_ClassID,
         kGrDualIntervalGradientColorizer_ClassID,
         kGrEllipseEffect_ClassID,
+        kGrFillRRectOp_Processor_ClassID,
         kGrGaussianConvolutionFragmentProcessor_ClassID,
+        kGrGSCoverageProcessor_ClassID,
         kGrImprovedPerlinNoiseEffect_ClassID,
         kGrLightingEffect_ClassID,
         kGrLinearGradient_ClassID,
@@ -120,6 +122,7 @@
         kGrMatrixConvolutionEffect_ClassID,
         kGrMeshTestProcessor_ClassID,
         kGrMorphologyEffect_ClassID,
+        kGrMixerEffect_ClassID,
         kGrOverdrawFragmentProcessor_ClassID,
         kGrPathProcessor_ClassID,
         kGrPerlinNoise2Effect_ClassID,
@@ -144,6 +147,7 @@
         kGrTwoPointConicalGradientLayout_ClassID,
         kGrUnpremulInputFragmentProcessor_ClassID,
         kGrUnrolledBinaryGradientColorizer_ClassID,
+        kGrVSCoverageProcessor_ClassID,
         kGrYUVtoRGBEffect_ClassID,
         kHighContrastFilterEffect_ClassID,
         kInstanceProcessor_ClassID,
@@ -157,6 +161,7 @@
         kQuadPerEdgeAAGeometryProcessor_ClassID,
         kReplaceInputFragmentProcessor_ClassID,
         kRRectsGaussianEdgeFP_ClassID,
+        kSampleLocationsTestProcessor_ClassID,
         kSeriesFragmentProcessor_ClassID,
         kShaderPDXferProcessor_ClassID,
         kFwidthSquircleTestProcessor_ClassID,
@@ -186,6 +191,20 @@
     SkString dumpInfo() const { return SkString("<Processor information unavailable>"); }
 #endif
 
+    /**
+     * Custom shader features provided by the framework. These require special handling when
+     * preparing shaders, so a processor must call setWillUseCustomFeature() from its constructor if
+     * it intends to use one.
+     */
+    enum class CustomFeatures {
+        kNone = 0,
+        kSampleLocations = 1 << 0,
+    };
+
+    GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(CustomFeatures);
+
+    CustomFeatures requestedFeatures() const { return fRequestedFeatures; }
+
     void* operator new(size_t size);
     void operator delete(void* target);
 
@@ -203,12 +222,15 @@
 
 protected:
     GrProcessor(ClassID classID) : fClassID(classID) {}
-
-private:
     GrProcessor(const GrProcessor&) = delete;
     GrProcessor& operator=(const GrProcessor&) = delete;
 
-    ClassID fClassID;
+    void setWillUseCustomFeature(CustomFeatures feature) { fRequestedFeatures |= feature; }
+
+    const ClassID fClassID;
+    CustomFeatures fRequestedFeatures = CustomFeatures::kNone;
 };
 
+GR_MAKE_BITFIELD_CLASS_OPS(GrProcessor::CustomFeatures);
+
 #endif
diff --git a/src/gpu/GrProcessorSet.cpp b/src/gpu/GrProcessorSet.cpp
index 478d250..94dd240 100644
--- a/src/gpu/GrProcessorSet.cpp
+++ b/src/gpu/GrProcessorSet.cpp
@@ -8,6 +8,7 @@
 #include "GrProcessorSet.h"
 #include "GrAppliedClip.h"
 #include "GrCaps.h"
+#include "GrUserStencilSettings.h"
 #include "GrXferProcessor.h"
 #include "SkBlendModePriv.h"
 #include "effects/GrPorterDuffXferProcessor.h"
@@ -159,11 +160,10 @@
     return thisXP.isEqual(thatXP);
 }
 
-GrProcessorSet::Analysis GrProcessorSet::finalize(const GrProcessorAnalysisColor& colorInput,
-                                                  const GrProcessorAnalysisCoverage coverageInput,
-                                                  const GrAppliedClip* clip, bool isMixedSamples,
-                                                  const GrCaps& caps,
-                                                  SkPMColor4f* overrideInputColor) {
+GrProcessorSet::Analysis GrProcessorSet::finalize(
+        const GrProcessorAnalysisColor& colorInput, const GrProcessorAnalysisCoverage coverageInput,
+        const GrAppliedClip* clip, const GrUserStencilSettings* userStencil, GrFSAAType fsaaType,
+        const GrCaps& caps, GrClampType clampType, SkPMColor4f* overrideInputColor) {
     SkASSERT(!this->isFinalized());
     SkASSERT(!fFragmentProcessorOffset);
 
@@ -209,14 +209,14 @@
     }
 
     GrXPFactory::AnalysisProperties props = GrXPFactory::GetAnalysisProperties(
-            this->xpFactory(), colorAnalysis.outputColor(), outputCoverage, caps);
+            this->xpFactory(), colorAnalysis.outputColor(), outputCoverage, caps, clampType);
     if (!this->numCoverageFragmentProcessors() &&
         GrProcessorAnalysisCoverage::kNone == coverageInput) {
     }
     analysis.fRequiresDstTexture =
             SkToBool(props & GrXPFactory::AnalysisProperties::kRequiresDstTexture);
     analysis.fCompatibleWithCoverageAsAlpha &=
-            SkToBool(props & GrXPFactory::AnalysisProperties::kCompatibleWithAlphaAsCoverage);
+            SkToBool(props & GrXPFactory::AnalysisProperties::kCompatibleWithCoverageAsAlpha);
     analysis.fRequiresNonOverlappingDraws = SkToBool(
             props & GrXPFactory::AnalysisProperties::kRequiresNonOverlappingDraws);
     if (props & GrXPFactory::AnalysisProperties::kIgnoresInputColor) {
@@ -235,9 +235,13 @@
     }
     fFragmentProcessorOffset = colorFPsToEliminate;
     fColorFragmentProcessorCnt -= colorFPsToEliminate;
+    analysis.fHasColorFragmentProcessor = (fColorFragmentProcessorCnt != 0);
 
+    bool hasMixedSampledCoverage = (GrFSAAType::kMixedSamples == fsaaType)
+            && !userStencil->testAlwaysPasses((clip) ? clip->hasStencilClip() : false);
     auto xp = GrXPFactory::MakeXferProcessor(this->xpFactory(), colorAnalysis.outputColor(),
-                                             outputCoverage, isMixedSamples, caps);
+                                             outputCoverage, hasMixedSampledCoverage, caps,
+                                             clampType);
     fXP.fProcessor = xp.release();
 
     fFlags |= kFinalized_Flag;
diff --git a/src/gpu/GrProcessorSet.h b/src/gpu/GrProcessorSet.h
index f60392c..ec550ee 100644
--- a/src/gpu/GrProcessorSet.h
+++ b/src/gpu/GrProcessorSet.h
@@ -14,6 +14,7 @@
 #include "SkTemplates.h"
 #include "GrXferProcessor.h"
 
+struct GrUserStencilSettings;
 class GrAppliedClip;
 class GrXPFactory;
 
@@ -83,6 +84,8 @@
         bool requiresDstTexture() const { return fRequiresDstTexture; }
         bool requiresNonOverlappingDraws() const { return fRequiresNonOverlappingDraws; }
         bool isCompatibleWithCoverageAsAlpha() const { return fCompatibleWithCoverageAsAlpha; }
+        // Indicates whether all color fragment processors were eliminated in the analysis.
+        bool hasColorFragmentProcessor() const { return fHasColorFragmentProcessor; }
 
         bool inputColorIsIgnored() const { return fInputColorType == kIgnored_InputColorType; }
         bool inputColorIsOverridden() const {
@@ -95,6 +98,7 @@
                 , fCompatibleWithCoverageAsAlpha(true)
                 , fRequiresDstTexture(false)
                 , fRequiresNonOverlappingDraws(false)
+                , fHasColorFragmentProcessor(false)
                 , fIsInitialized(true)
                 , fInputColorType(kOriginal_InputColorType) {}
         enum InputColorType : uint32_t {
@@ -111,6 +115,7 @@
         PackedBool fCompatibleWithCoverageAsAlpha : 1;
         PackedBool fRequiresDstTexture : 1;
         PackedBool fRequiresNonOverlappingDraws : 1;
+        PackedBool fHasColorFragmentProcessor : 1;
         PackedBool fIsInitialized : 1;
         PackedInputColorType fInputColorType : 2;
 
@@ -132,9 +137,9 @@
      * that owns a processor set is recorded to ensure pending and writes are propagated to
      * resources referred to by the processors. Otherwise, data hazards may occur.
      */
-    Analysis finalize(const GrProcessorAnalysisColor& colorInput,
-                      const GrProcessorAnalysisCoverage coverageInput, const GrAppliedClip*,
-                      bool isMixedSamples, const GrCaps&, SkPMColor4f* inputColorOverride);
+    Analysis finalize(const GrProcessorAnalysisColor&, const GrProcessorAnalysisCoverage,
+                      const GrAppliedClip*, const GrUserStencilSettings*, GrFSAAType, const GrCaps&,
+                      GrClampType, SkPMColor4f* inputColorOverride);
 
     bool isFinalized() const { return SkToBool(kFinalized_Flag & fFlags); }
 
diff --git a/src/gpu/GrProgramDesc.cpp b/src/gpu/GrProgramDesc.cpp
index 952dc00..a066145 100644
--- a/src/gpu/GrProgramDesc.cpp
+++ b/src/gpu/GrProgramDesc.cpp
@@ -39,72 +39,60 @@
     return SkToU16(value);
 }
 
-static uint16_t sampler_key(GrTextureType textureType, GrPixelConfig config,
+static uint32_t sampler_key(GrTextureType textureType, GrPixelConfig config,
                             const GrShaderCaps& caps) {
     int samplerTypeKey = texture_type_key(textureType);
 
-    GR_STATIC_ASSERT(1 == sizeof(caps.configTextureSwizzle(config).asKey()));
-    return SkToU16(samplerTypeKey |
+    GR_STATIC_ASSERT(2 == sizeof(caps.configTextureSwizzle(config).asKey()));
+    return SkToU32(samplerTypeKey |
                    caps.configTextureSwizzle(config).asKey() << kSamplerOrImageTypeKeyBits |
-                   (GrSLSamplerPrecision(config) << (8 + kSamplerOrImageTypeKeyBits)));
+                   (GrSLSamplerPrecision(config) << (16 + kSamplerOrImageTypeKeyBits)));
 }
 
 static void add_sampler_keys(GrProcessorKeyBuilder* b, const GrFragmentProcessor& fp,
                              GrGpu* gpu, const GrShaderCaps& caps) {
     int numTextureSamplers = fp.numTextureSamplers();
-    // Need two bytes per key.
-    int word32Count = (numTextureSamplers + 1) / 2;
-    if (0 == word32Count) {
+    if (!numTextureSamplers) {
         return;
     }
-    uint16_t* k16 = reinterpret_cast<uint16_t*>(b->add32n(word32Count));
+    uint32_t* k32 = b->add32n(numTextureSamplers);
     for (int i = 0; i < numTextureSamplers; ++i) {
         const GrFragmentProcessor::TextureSampler& sampler = fp.textureSampler(i);
         const GrTexture* tex = sampler.peekTexture();
-        k16[i] = sampler_key(tex->texturePriv().textureType(), tex->config(), caps);
+        k32[i] = sampler_key(tex->texturePriv().textureType(), tex->config(), caps);
         uint32_t extraSamplerKey = gpu->getExtraSamplerKeyForProgram(
                 sampler.samplerState(), sampler.proxy()->backendFormat());
         if (extraSamplerKey) {
             SkASSERT(sampler.proxy()->textureType() == GrTextureType::kExternal);
             // We first mark the normal sampler key with last bit to flag that it has an extra
             // sampler key. We then add all the extraSamplerKeys to the end of the normal ones.
-            SkASSERT((k16[i] & (1 << 15)) == 0);
-            k16[i] = k16[i] | (1 << 15);
+            SkASSERT((k32[i] & (1 << 31)) == 0);
+            k32[i] = k32[i] | (1 << 31);
             b->add32(extraSamplerKey);
         }
     }
-    // zero the last 16 bits if the number of uniforms for samplers is odd.
-    if (numTextureSamplers & 0x1) {
-        k16[numTextureSamplers] = 0;
-    }
 }
 
 static void add_sampler_keys(GrProcessorKeyBuilder* b, const GrPrimitiveProcessor& pp,
                              const GrShaderCaps& caps) {
     int numTextureSamplers = pp.numTextureSamplers();
-    // Need two bytes per key.
-    int word32Count = (numTextureSamplers + 1) / 2;
-    if (0 == word32Count) {
+    if (!numTextureSamplers) {
         return;
     }
-    uint16_t* k16 = reinterpret_cast<uint16_t*>(b->add32n(word32Count));
+    uint32_t* k32 = b->add32n(numTextureSamplers);
     for (int i = 0; i < numTextureSamplers; ++i) {
         const GrPrimitiveProcessor::TextureSampler& sampler = pp.textureSampler(i);
-        k16[i] = sampler_key(sampler.textureType(), sampler.config(), caps);
+        k32[i] = sampler_key(sampler.textureType(), sampler.config(), caps);
         uint32_t extraSamplerKey = sampler.extraSamplerKey();
         if (extraSamplerKey) {
             SkASSERT(sampler.textureType() == GrTextureType::kExternal);
             // We first mark the normal sampler key with last bit to flag that it has an extra
             // sampler key. We then add all the extraSamplerKeys to the end of the normal ones.
-            SkASSERT((k16[i] & (1 << 15)) == 0);
-            k16[i] = k16[i] | (1 << 15);
+            SkASSERT((k32[i] & (1 << 15)) == 0);
+            k32[i] = k32[i] | (1 << 15);
             b->add32(extraSamplerKey);
         }
     }
-    // zero the last 16 bits if the number of uniforms for samplers is odd.
-    if (numTextureSamplers & 0x1) {
-        k16[numTextureSamplers] = 0;
-    }
 }
 
 /**
@@ -192,12 +180,10 @@
                                                                       fp.numCoordTransforms()), b);
 }
 
-bool GrProgramDesc::Build(GrProgramDesc* desc,
-                          GrPixelConfig config,
-                          const GrPrimitiveProcessor& primProc,
-                          bool hasPointSize,
-                          const GrPipeline& pipeline,
-                          GrGpu* gpu) {
+bool GrProgramDesc::Build(
+        GrProgramDesc* desc, const GrRenderTarget* renderTarget,
+        const GrPrimitiveProcessor& primProc, bool hasPointSize, const GrPipeline& pipeline,
+        GrGpu* gpu) {
     // The descriptor is used as a cache key. Thus when a field of the
     // descriptor will not affect program generation (because of the attribute
     // bindings in use or other descriptor field settings) it should be set
@@ -218,6 +204,7 @@
         desc->key().reset();
         return false;
     }
+    GrProcessor::CustomFeatures processorFeatures = primProc.requestedFeatures();
 
     for (int i = 0; i < pipeline.numFragmentProcessors(); ++i) {
         const GrFragmentProcessor& fp = pipeline.getFragmentProcessor(i);
@@ -225,6 +212,7 @@
             desc->key().reset();
             return false;
         }
+        processorFeatures |= fp.requestedFeatures();
     }
 
     const GrXferProcessor& xp = pipeline.getXferProcessor();
@@ -239,6 +227,12 @@
         desc->key().reset();
         return false;
     }
+    processorFeatures |= xp.requestedFeatures();
+
+    if (processorFeatures & GrProcessor::CustomFeatures::kSampleLocations) {
+        SkASSERT(pipeline.isHWAntialiasState());
+        b.add32(renderTarget->renderTargetPriv().getSamplePatternKey(pipeline));
+    }
 
     // --------DO NOT MOVE HEADER ABOVE THIS LINE--------------------------------------------------
     // Because header is a pointer into the dynamic array, we can't push any new data into the key
@@ -247,8 +241,7 @@
 
     // make sure any padding in the header is zeroed.
     memset(header, 0, kHeaderSize);
-    header->fOutputSwizzle = shaderCaps.configOutputSwizzle(config).asKey();
-    header->fSnapVerticesToPixelCenters = pipeline.snapVerticesToPixelCenters();
+    header->fOutputSwizzle = shaderCaps.configOutputSwizzle(renderTarget->config()).asKey();
     header->fColorFragmentProcessorCnt = pipeline.numColorFragmentProcessors();
     header->fCoverageFragmentProcessorCnt = pipeline.numCoverageFragmentProcessors();
     // Fail if the client requested more processors than the key can fit.
@@ -256,6 +249,11 @@
         header->fCoverageFragmentProcessorCnt != pipeline.numCoverageFragmentProcessors()) {
         return false;
     }
+    header->fProcessorFeatures = (uint8_t)processorFeatures;
+    SkASSERT(header->processorFeatures() == processorFeatures);  // Ensure enough bits.
+    header->fSnapVerticesToPixelCenters = pipeline.snapVerticesToPixelCenters();
     header->fHasPointSize = hasPointSize ? 1 : 0;
+    header->fClampBlendInput =
+            GrClampType::kManual == GrPixelConfigClampType(renderTarget->config()) ? 1 : 0;
     return true;
 }
diff --git a/src/gpu/GrProgramDesc.h b/src/gpu/GrProgramDesc.h
index 08e03c9..af5ce80 100644
--- a/src/gpu/GrProgramDesc.h
+++ b/src/gpu/GrProgramDesc.h
@@ -39,12 +39,8 @@
     * @param GrGpu          Ptr to the GrGpu object the program will be used with.
     * @param GrProgramDesc  The built and finalized descriptor
     **/
-    static bool Build(GrProgramDesc*,
-                      GrPixelConfig,
-                      const GrPrimitiveProcessor&,
-                      bool hasPointSize,
-                      const GrPipeline&,
-                      GrGpu*);
+    static bool Build(GrProgramDesc*, const GrRenderTarget*, const GrPrimitiveProcessor&,
+                      bool hasPointSize, const GrPipeline&, GrGpu*);
 
     // Returns this as a uint32_t array to be used as a key in the program cache.
     const uint32_t* asKey() const {
@@ -91,17 +87,26 @@
     }
 
     struct KeyHeader {
+        bool hasSurfaceOriginKey() const {
+            return SkToBool(fSurfaceOriginKey);
+        }
+        GrProcessor::CustomFeatures processorFeatures() const {
+            return (GrProcessor::CustomFeatures)fProcessorFeatures;
+        }
+
         // Set to uniquely idenitify any swizzling of the shader's output color(s).
-        uint8_t fOutputSwizzle;
+        uint16_t fOutputSwizzle;
         uint8_t fColorFragmentProcessorCnt; // Can be packed into 4 bits if required.
         uint8_t fCoverageFragmentProcessorCnt;
         // Set to uniquely identify the rt's origin, or 0 if the shader does not require this info.
         uint8_t fSurfaceOriginKey : 2;
+        uint8_t fProcessorFeatures : 1;
         bool fSnapVerticesToPixelCenters : 1;
         bool fHasPointSize : 1;
-        uint8_t fPad : 4;
+        bool fClampBlendInput : 1;
+        uint8_t fPad : 2;
     };
-    GR_STATIC_ASSERT(sizeof(KeyHeader) == 4);
+    GR_STATIC_ASSERT(sizeof(KeyHeader) == 6);
 
     // This should really only be used internally, base classes should return their own headers
     const KeyHeader& header() const { return *this->atOffset<KeyHeader, kHeaderOffset>(); }
diff --git a/src/gpu/GrProxyProvider.cpp b/src/gpu/GrProxyProvider.cpp
index 46f2709..d2c47ac 100644
--- a/src/gpu/GrProxyProvider.cpp
+++ b/src/gpu/GrProxyProvider.cpp
@@ -8,6 +8,10 @@
 #include "GrProxyProvider.h"
 
 #include "GrCaps.h"
+#include "GrContext.h"
+#include "GrContextPriv.h"
+#include "GrImageContext.h"
+#include "GrImageContextPriv.h"
 #include "GrRenderTarget.h"
 #include "GrResourceKey.h"
 #include "GrResourceProvider.h"
@@ -28,46 +32,12 @@
 #include "SkTraceEvent.h"
 
 #define ASSERT_SINGLE_OWNER \
-    SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(fSingleOwner);)
+    SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(fImageContext->priv().singleOwner());)
 
-GrProxyProvider::GrProxyProvider(GrResourceProvider* resourceProvider,
-                                 GrResourceCache* resourceCache,
-                                 sk_sp<const GrCaps> caps,
-                                 GrSingleOwner* owner)
-        : fResourceProvider(resourceProvider)
-        , fResourceCache(resourceCache)
-        , fAbandoned(false)
-        , fCaps(caps)
-        , fContextUniqueID(resourceCache->contextUniqueID())
-#ifdef SK_DEBUG
-        , fSingleOwner(owner)
-#endif
-{
-    SkASSERT(fResourceProvider);
-    SkASSERT(fResourceCache);
-    SkASSERT(fCaps);
-    SkASSERT(fSingleOwner);
-}
-
-GrProxyProvider::GrProxyProvider(uint32_t contextUniqueID,
-                                 sk_sp<const GrCaps> caps,
-                                 GrSingleOwner* owner)
-        : fResourceProvider(nullptr)
-        , fResourceCache(nullptr)
-        , fAbandoned(false)
-        , fCaps(caps)
-        , fContextUniqueID(contextUniqueID)
-#ifdef SK_DEBUG
-        , fSingleOwner(owner)
-#endif
-{
-    SkASSERT(fContextUniqueID != SK_InvalidUniqueID);
-    SkASSERT(fCaps);
-    SkASSERT(fSingleOwner);
-}
+GrProxyProvider::GrProxyProvider(GrImageContext* imageContext) : fImageContext(imageContext) {}
 
 GrProxyProvider::~GrProxyProvider() {
-    if (fResourceCache) {
+    if (this->renderingDirectly()) {
         // In DDL-mode a proxy provider can still have extant uniquely keyed proxies (since
         // they need their unique keys to, potentially, find a cached resource when the
         // DDL is played) but, in non-DDL-mode they should all have been cleaned up by this point.
@@ -82,10 +52,18 @@
         return false;
     }
 
-    // If there is already a GrResource with this key then the caller has violated the normal
-    // usage pattern of uniquely keyed resources (e.g., they have created one w/o first seeing
-    // if it already existed in the cache).
-    SkASSERT(!fResourceCache || !fResourceCache->findAndRefUniqueResource(key));
+#ifdef SK_DEBUG
+    {
+        GrContext* direct = fImageContext->priv().asDirectContext();
+        if (direct) {
+            GrResourceCache* resourceCache = direct->priv().getResourceCache();
+            // If there is already a GrResource with this key then the caller has violated the
+            // normal usage pattern of uniquely keyed resources (e.g., they have created one w/o
+            // first seeing if it already existed in the cache).
+            SkASSERT(!resourceCache->findAndRefUniqueResource(key));
+        }
+    }
+#endif
 
     SkASSERT(!fUniquelyKeyedProxies.find(key));     // multiple proxies can't get the same key
 
@@ -124,13 +102,52 @@
         return nullptr;
     }
 
-    sk_sp<GrTextureProxy> result = sk_ref_sp(fUniquelyKeyedProxies.find(key));
-    if (result) {
+    GrTextureProxy* proxy = fUniquelyKeyedProxies.find(key);
+    sk_sp<GrTextureProxy> result;
+    if (proxy) {
+        GrResourceCache* cache = nullptr;
+        if (auto directContext = fImageContext->priv().asDirectContext()) {
+            cache = directContext->priv().getResourceCache();
+        }
+        proxy->firstRefAccess().ref(cache);
+        result.reset(proxy);
         SkASSERT(result->origin() == origin);
     }
     return result;
 }
 
+///////////////////////////////////////////////////////////////////////////////
+
+#if GR_TEST_UTILS
+sk_sp<GrTextureProxy> GrProxyProvider::testingOnly_createInstantiatedProxy(
+        const GrSurfaceDesc& desc, GrSurfaceOrigin origin, SkBackingFit fit, SkBudgeted budgeted) {
+    GrContext* direct = fImageContext->priv().asDirectContext();
+    if (!direct) {
+        return nullptr;
+    }
+
+    GrResourceProvider* resourceProvider = direct->priv().resourceProvider();
+    sk_sp<GrTexture> tex;
+
+    if (SkBackingFit::kApprox == fit) {
+        tex = resourceProvider->createApproxTexture(desc, GrResourceProvider::Flags::kNoPendingIO);
+    } else {
+        tex = resourceProvider->createTexture(desc, budgeted,
+                                              GrResourceProvider::Flags::kNoPendingIO);
+    }
+    if (!tex) {
+        return nullptr;
+    }
+
+    return this->createWrapped(std::move(tex), origin);
+}
+
+sk_sp<GrTextureProxy> GrProxyProvider::testingOnly_createWrapped(sk_sp<GrTexture> tex,
+                                                                 GrSurfaceOrigin origin) {
+    return this->createWrapped(std::move(tex), origin);
+}
+#endif
+
 sk_sp<GrTextureProxy> GrProxyProvider::createWrapped(sk_sp<GrTexture> tex, GrSurfaceOrigin origin) {
 #ifdef SK_DEBUG
     if (tex->getUniqueKey().isValid()) {
@@ -158,11 +175,14 @@
         return result;
     }
 
-    if (!fResourceCache) {
+    GrContext* direct = fImageContext->priv().asDirectContext();
+    if (!direct) {
         return nullptr;
     }
 
-    GrGpuResource* resource = fResourceCache->findAndRefUniqueResource(key);
+    GrResourceCache* resourceCache = direct->priv().getResourceCache();
+
+    GrGpuResource* resource = resourceCache->findAndRefUniqueResource(key);
     if (!resource) {
         return nullptr;
     }
@@ -190,14 +210,14 @@
         return nullptr;
     }
 
-    SkImageInfo info = as_IB(srcImage)->onImageInfo();
+    const SkImageInfo& info = srcImage->imageInfo();
     GrPixelConfig config = SkImageInfo2GrPixelConfig(info);
 
     if (kUnknown_GrPixelConfig == config) {
         return nullptr;
     }
 
-    GrBackendFormat format = fCaps->getBackendFormatFromColorType(info.colorType());
+    GrBackendFormat format = this->caps()->getBackendFormatFromColorType(info.colorType());
     if (!format.isValid()) {
         return nullptr;
     }
@@ -221,10 +241,13 @@
     }
 
     if (SkToBool(descFlags & kRenderTarget_GrSurfaceFlag)) {
-        if (fCaps->usesMixedSamples() && sampleCnt > 1) {
+        if (this->caps()->usesMixedSamples() && sampleCnt > 1) {
             surfaceFlags |= GrInternalSurfaceFlags::kMixedSampled;
         }
     }
+    if (fImageContext->priv().explicitlyAllocateGPUResources()) {
+        surfaceFlags |= GrInternalSurfaceFlags::kNoPendingIO;
+    }
 
     GrSurfaceDesc desc;
     desc.fWidth = srcImage->width();
@@ -235,11 +258,6 @@
 
     sk_sp<GrTextureProxy> proxy = this->createLazyProxy(
             [desc, budgeted, srcImage, fit, surfaceFlags](GrResourceProvider* resourceProvider) {
-                if (!resourceProvider) {
-                    // Nothing to clean up here. Once the proxy (and thus lambda) is deleted the ref
-                    // on srcImage will be released.
-                    return sk_sp<GrTexture>();
-                }
                 SkPixmap pixMap;
                 SkAssertResult(srcImage->peekPixels(&pixMap));
                 GrMipLevel mipLevel = { pixMap.addr(), pixMap.rowBytes() };
@@ -248,8 +266,8 @@
                 if (surfaceFlags & GrInternalSurfaceFlags::kNoPendingIO) {
                     resourceProviderFlags |= GrResourceProvider::Flags::kNoPendingIO;
                 }
-                return resourceProvider->createTexture(desc, budgeted, fit, mipLevel,
-                                                       resourceProviderFlags);
+                return LazyInstantiationResult(resourceProvider->createTexture(
+                        desc, budgeted, fit, mipLevel, resourceProviderFlags));
             },
             format, desc, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo, surfaceFlags, fit, budgeted);
 
@@ -257,10 +275,13 @@
         return nullptr;
     }
 
-    if (fResourceProvider) {
+    GrContext* direct = fImageContext->priv().asDirectContext();
+    if (direct) {
+        GrResourceProvider* resourceProvider = direct->priv().resourceProvider();
+
         // In order to reuse code we always create a lazy proxy. When we aren't in DDL mode however
         // we're better off instantiating the proxy immediately here.
-        if (!proxy->priv().doLazyInstantiation(fResourceProvider)) {
+        if (!proxy->priv().doLazyInstantiation(resourceProvider)) {
             return nullptr;
         }
     }
@@ -284,30 +305,42 @@
                              budgeted, GrInternalSurfaceFlags::kNone);
 }
 
-sk_sp<GrTextureProxy> GrProxyProvider::createMipMapProxyFromBitmap(const SkBitmap& bitmap) {
+sk_sp<GrTextureProxy> GrProxyProvider::createProxyFromBitmap(const SkBitmap& bitmap,
+                                                             GrMipMapped mipMapped) {
+    ASSERT_SINGLE_OWNER
+    SkASSERT(GrMipMapped::kNo == mipMapped || this->caps()->mipMapSupport());
+
+    if (this->isAbandoned()) {
+        return nullptr;
+    }
+
     if (!SkImageInfoIsValid(bitmap.info())) {
         return nullptr;
     }
 
-    ATRACE_ANDROID_FRAMEWORK("Upload MipMap Texture [%ux%u]", bitmap.width(), bitmap.height());
+    ATRACE_ANDROID_FRAMEWORK("Upload %sTexture [%ux%u]",
+                             GrMipMapped::kYes == mipMapped ? "MipMap " : "",
+                             bitmap.width(), bitmap.height());
 
     // In non-ddl we will always instantiate right away. Thus we never want to copy the SkBitmap
     // even if its mutable. In ddl, if the bitmap is mutable then we must make a copy since the
     // upload of the data to the gpu can happen at anytime and the bitmap may change by then.
-    SkCopyPixelsMode copyMode = this->recordingDDL() ? kIfMutable_SkCopyPixelsMode
-                                                     : kNever_SkCopyPixelsMode;
+    SkCopyPixelsMode copyMode = this->renderingDirectly() ? kNever_SkCopyPixelsMode
+                                                          : kIfMutable_SkCopyPixelsMode;
     sk_sp<SkImage> baseLevel = SkMakeImageFromRasterBitmap(bitmap, copyMode);
     if (!baseLevel) {
         return nullptr;
     }
 
-    // This was never going to have mips anyway
-    if (0 == SkMipMap::ComputeLevelCount(baseLevel->width(), baseLevel->height())) {
-        return this->createTextureProxy(baseLevel, kNone_GrSurfaceFlags, 1, SkBudgeted::kYes,
-                                        SkBackingFit::kExact);
+    // If mips weren't requested (or this was too small to have any), then take the fast path
+    if (GrMipMapped::kNo == mipMapped ||
+        0 == SkMipMap::ComputeLevelCount(baseLevel->width(), baseLevel->height())) {
+        return this->createTextureProxy(std::move(baseLevel), kNone_GrSurfaceFlags, 1,
+                                        SkBudgeted::kYes, SkBackingFit::kExact);
     }
 
-    const GrBackendFormat format = fCaps->getBackendFormatFromColorType(bitmap.info().colorType());
+    const GrBackendFormat format =
+                            this->caps()->getBackendFormatFromColorType(bitmap.info().colorType());
     if (!format.isValid()) {
         return nullptr;
     }
@@ -333,10 +366,6 @@
 
     sk_sp<GrTextureProxy> proxy = this->createLazyProxy(
             [desc, baseLevel, mipmaps](GrResourceProvider* resourceProvider) {
-                if (!resourceProvider) {
-                    return sk_sp<GrTexture>();
-                }
-
                 const int mipLevelCount = mipmaps->countLevels() + 1;
                 std::unique_ptr<GrMipLevel[]> texels(new GrMipLevel[mipLevelCount]);
 
@@ -356,8 +385,8 @@
                     SkASSERT(texels[i].fPixels);
                 }
 
-                return resourceProvider->createTexture(desc, SkBudgeted::kYes, texels.get(),
-                                                       mipLevelCount);
+                return LazyInstantiationResult(resourceProvider->createTexture(
+                        desc, SkBudgeted::kYes, texels.get(), mipLevelCount));
             },
             format, desc, kTopLeft_GrSurfaceOrigin, GrMipMapped::kYes, SkBackingFit::kExact,
             SkBudgeted::kYes);
@@ -366,10 +395,12 @@
         return nullptr;
     }
 
-    if (fResourceProvider) {
+    GrContext* direct = fImageContext->priv().asDirectContext();
+    if (direct) {
+        GrResourceProvider* resourceProvider = direct->priv().resourceProvider();
         // In order to reuse code we always create a lazy proxy. When we aren't in DDL mode however
         // we're better off instantiating the proxy immediately here.
-        if (!proxy->priv().doLazyInstantiation(fResourceProvider)) {
+        if (!proxy->priv().doLazyInstantiation(resourceProvider)) {
             return nullptr;
         }
     }
@@ -418,18 +449,16 @@
     }
 
     const GrColorType ct = GrPixelConfigToColorType(desc.fConfig);
-    const GrBackendFormat format = fCaps->getBackendFormatFromGrColorType(ct, GrSRGBEncoded::kNo);
+    const GrBackendFormat format =
+                            this->caps()->getBackendFormatFromGrColorType(ct, GrSRGBEncoded::kNo);
 
     sk_sp<GrTextureProxy> proxy = this->createLazyProxy(
         [desc, data](GrResourceProvider* resourceProvider) {
-            if (!resourceProvider) {
-                return sk_sp<GrTexture>();
-            }
-
             GrMipLevel texels;
             texels.fPixels = data->data();
             texels.fRowBytes = GrBytesPerPixel(desc.fConfig)*desc.fWidth;
-            return resourceProvider->createTexture(desc, SkBudgeted::kYes, &texels, 1);
+            return LazyInstantiationResult(
+                    resourceProvider->createTexture(desc, SkBudgeted::kYes, &texels, 1));
         },
         format, desc, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo, SkBackingFit::kExact,
         SkBudgeted::kYes);
@@ -438,10 +467,12 @@
         return nullptr;
     }
 
-    if (fResourceProvider) {
+    GrContext* direct = fImageContext->priv().asDirectContext();
+    if (direct) {
+        GrResourceProvider* resourceProvider = direct->priv().resourceProvider();
         // In order to reuse code we always create a lazy proxy. When we aren't in DDL mode however
         // we're better off instantiating the proxy immediately here.
-        if (!proxy->priv().doLazyInstantiation(fResourceProvider)) {
+        if (!proxy->priv().doLazyInstantiation(resourceProvider)) {
             return nullptr;
         }
     }
@@ -461,21 +492,21 @@
     }
 
     // This is only supported on a direct GrContext.
-    if (!fResourceProvider) {
+    GrContext* direct = fImageContext->priv().asDirectContext();
+    if (!direct) {
         return nullptr;
     }
 
+    GrResourceProvider* resourceProvider = direct->priv().resourceProvider();
+
     sk_sp<GrTexture> tex =
-            fResourceProvider->wrapBackendTexture(backendTex, ownership, cacheable, ioType);
+            resourceProvider->wrapBackendTexture(backendTex, ownership, cacheable, ioType);
     if (!tex) {
         return nullptr;
     }
 
-    sk_sp<GrReleaseProcHelper> releaseHelper;
     if (releaseProc) {
-        releaseHelper.reset(new GrReleaseProcHelper(releaseProc, releaseCtx));
-        // This gives the texture a ref on the releaseHelper
-        tex->setRelease(std::move(releaseHelper));
+        tex->setRelease(releaseProc, releaseCtx);
     }
 
     SkASSERT(!tex->asRenderTarget());  // Strictly a GrTexture
@@ -494,26 +525,26 @@
     }
 
     // This is only supported on a direct GrContext.
-    if (!fResourceProvider) {
+    GrContext* direct = fImageContext->priv().asDirectContext();
+    if (!direct) {
         return nullptr;
     }
 
+    GrResourceProvider* resourceProvider = direct->priv().resourceProvider();
+
     sampleCnt = this->caps()->getRenderTargetSampleCount(sampleCnt, backendTex.config());
     if (!sampleCnt) {
         return nullptr;
     }
 
-    sk_sp<GrTexture> tex = fResourceProvider->wrapRenderableBackendTexture(backendTex, sampleCnt,
-                                                                           ownership, cacheable);
+    sk_sp<GrTexture> tex = resourceProvider->wrapRenderableBackendTexture(backendTex, sampleCnt,
+                                                                          ownership, cacheable);
     if (!tex) {
         return nullptr;
     }
 
-    sk_sp<GrReleaseProcHelper> releaseHelper;
     if (releaseProc) {
-        releaseHelper.reset(new GrReleaseProcHelper(releaseProc, releaseCtx));
-        // This gives the texture a ref on the releaseHelper
-        tex->setRelease(std::move(releaseHelper));
+        tex->setRelease(releaseProc, releaseCtx);
     }
 
     SkASSERT(tex->asRenderTarget());  // A GrTextureRenderTarget
@@ -531,20 +562,20 @@
     }
 
     // This is only supported on a direct GrContext.
-    if (!fResourceProvider) {
+    GrContext* direct = fImageContext->priv().asDirectContext();
+    if (!direct) {
         return nullptr;
     }
 
-    sk_sp<GrRenderTarget> rt = fResourceProvider->wrapBackendRenderTarget(backendRT);
+    GrResourceProvider* resourceProvider = direct->priv().resourceProvider();
+
+    sk_sp<GrRenderTarget> rt = resourceProvider->wrapBackendRenderTarget(backendRT);
     if (!rt) {
         return nullptr;
     }
 
-    sk_sp<GrReleaseProcHelper> releaseHelper;
     if (releaseProc) {
-        releaseHelper.reset(new GrReleaseProcHelper(releaseProc, releaseCtx));
-        // This gives the render target a ref on the releaseHelper
-        rt->setRelease(std::move(releaseHelper));
+        rt->setRelease(releaseProc, releaseCtx);
     }
 
     SkASSERT(!rt->asTexture());  // A GrRenderTarget that's not textureable
@@ -562,12 +593,15 @@
     }
 
     // This is only supported on a direct GrContext.
-    if (!fResourceProvider) {
+    GrContext* direct = fImageContext->priv().asDirectContext();
+    if (!direct) {
         return nullptr;
     }
 
+    GrResourceProvider* resourceProvider = direct->priv().resourceProvider();
+
     sk_sp<GrRenderTarget> rt =
-            fResourceProvider->wrapBackendTextureAsRenderTarget(backendTex, sampleCnt);
+            resourceProvider->wrapBackendTextureAsRenderTarget(backendTex, sampleCnt);
     if (!rt) {
         return nullptr;
     }
@@ -586,16 +620,19 @@
     }
 
     // This is only supported on a direct GrContext.
-    if (!fResourceProvider) {
+    GrContext* direct = fImageContext->priv().asDirectContext();
+    if (!direct) {
         return nullptr;
     }
 
-    sk_sp<GrRenderTarget> rt = fResourceProvider->wrapVulkanSecondaryCBAsRenderTarget(imageInfo,
-                                                                                      vkInfo);
+    GrResourceProvider* resourceProvider = direct->priv().resourceProvider();
 
+    sk_sp<GrRenderTarget> rt = resourceProvider->wrapVulkanSecondaryCBAsRenderTarget(imageInfo,
+                                                                                     vkInfo);
     if (!rt) {
         return nullptr;
     }
+
     SkASSERT(!rt->asTexture());  // A GrRenderTarget that's not textureable
     SkASSERT(!rt->getUniqueKey().isValid());
     // This proxy should be unbudgeted because we're just wrapping an external resource
@@ -628,8 +665,8 @@
                                                        SkBackingFit fit,
                                                        SkBudgeted budgeted) {
     // For non-ddl draws always make lazy proxy's single use.
-    LazyInstantiationType lazyType = fResourceProvider ? LazyInstantiationType::kSingleUse
-                                                       : LazyInstantiationType::kMultipleUse;
+    LazyInstantiationType lazyType = this->renderingDirectly() ? LazyInstantiationType::kSingleUse
+                                                               : LazyInstantiationType::kMultipleUse;
     return this->createLazyProxy(std::move(callback), format, desc, origin, mipMapped, surfaceFlags,
                                  fit, budgeted, lazyType);
 }
@@ -646,7 +683,8 @@
     SkASSERT((desc.fWidth <= 0 && desc.fHeight <= 0) ||
              (desc.fWidth > 0 && desc.fHeight > 0));
 
-    if (desc.fWidth > fCaps->maxTextureSize() || desc.fHeight > fCaps->maxTextureSize()) {
+    if (desc.fWidth > this->caps()->maxTextureSize() ||
+        desc.fHeight > this->caps()->maxTextureSize()) {
         return nullptr;
     }
 
@@ -654,7 +692,7 @@
 #ifdef SK_DEBUG
     if (SkToBool(kRenderTarget_GrSurfaceFlag & desc.fFlags)) {
         if (SkToBool(surfaceFlags & GrInternalSurfaceFlags::kMixedSampled)) {
-            SkASSERT(fCaps->usesMixedSamples() && desc.fSampleCnt > 1);
+            SkASSERT(this->caps()->usesMixedSamples() && desc.fSampleCnt > 1);
         }
     }
 #endif
@@ -670,11 +708,13 @@
 sk_sp<GrRenderTargetProxy> GrProxyProvider::createLazyRenderTargetProxy(
         LazyInstantiateCallback&& callback, const GrBackendFormat& format,
         const GrSurfaceDesc& desc, GrSurfaceOrigin origin, GrInternalSurfaceFlags surfaceFlags,
-        const TextureInfo* textureInfo, SkBackingFit fit, SkBudgeted budgeted) {
+        const TextureInfo* textureInfo, SkBackingFit fit, SkBudgeted budgeted,
+        bool wrapsVkSecondaryCB) {
     SkASSERT((desc.fWidth <= 0 && desc.fHeight <= 0) ||
              (desc.fWidth > 0 && desc.fHeight > 0));
 
-    if (desc.fWidth > fCaps->maxRenderTargetSize() || desc.fHeight > fCaps->maxRenderTargetSize()) {
+    if (desc.fWidth > this->caps()->maxRenderTargetSize() ||
+        desc.fHeight > this->caps()->maxRenderTargetSize()) {
         return nullptr;
     }
 
@@ -682,23 +722,31 @@
 
 #ifdef SK_DEBUG
     if (SkToBool(surfaceFlags & GrInternalSurfaceFlags::kMixedSampled)) {
-        SkASSERT(fCaps->usesMixedSamples() && desc.fSampleCnt > 1);
+        SkASSERT(this->caps()->usesMixedSamples() && desc.fSampleCnt > 1);
     }
 #endif
 
     using LazyInstantiationType = GrSurfaceProxy::LazyInstantiationType;
     // For non-ddl draws always make lazy proxy's single use.
-    LazyInstantiationType lazyType = fResourceProvider ? LazyInstantiationType::kSingleUse
-                                                       : LazyInstantiationType::kMultipleUse;
+    LazyInstantiationType lazyType = this->renderingDirectly() ? LazyInstantiationType::kSingleUse
+                                                               : LazyInstantiationType::kMultipleUse;
 
     if (textureInfo) {
+        // Wrapped vulkan secondary command buffers don't support texturing since we won't have an
+        // actual VkImage to texture from.
+        SkASSERT(!wrapsVkSecondaryCB);
         return sk_sp<GrRenderTargetProxy>(new GrTextureRenderTargetProxy(
                 std::move(callback), lazyType, format, desc, origin, textureInfo->fMipMapped,
                 fit, budgeted, surfaceFlags));
     }
 
+    GrRenderTargetProxy::WrapsVkSecondaryCB vkSCB =
+            wrapsVkSecondaryCB ? GrRenderTargetProxy::WrapsVkSecondaryCB::kYes
+                               : GrRenderTargetProxy::WrapsVkSecondaryCB::kNo;
+
     return sk_sp<GrRenderTargetProxy>(new GrRenderTargetProxy(
-            std::move(callback), lazyType, format, desc, origin, fit, budgeted, surfaceFlags));
+            std::move(callback), lazyType, format, desc, origin, fit, budgeted, surfaceFlags,
+            vkSCB));
 }
 
 sk_sp<GrTextureProxy> GrProxyProvider::MakeFullyLazyProxy(LazyInstantiateCallback&& callback,
@@ -753,11 +801,10 @@
     // proxy's unique key. We must do it in this order because 'key' may alias the proxy's key.
     sk_sp<GrGpuResource> invalidGpuResource;
     if (InvalidateGPUResource::kYes == invalidateGPUResource) {
-        if (proxy && proxy->isInstantiated()) {
-            invalidGpuResource = sk_ref_sp(proxy->peekSurface());
-        }
-        if (!invalidGpuResource && fResourceProvider) {
-            invalidGpuResource = fResourceProvider->findByUniqueKey<GrGpuResource>(key);
+        GrContext* direct = fImageContext->priv().asDirectContext();
+        if (direct) {
+            GrResourceProvider* resourceProvider = direct->priv().resourceProvider();
+            invalidGpuResource = resourceProvider->findByUniqueKey<GrGpuResource>(key);
         }
         SkASSERT(!invalidGpuResource || invalidGpuResource->getUniqueKey() == key);
     }
@@ -774,6 +821,22 @@
     }
 }
 
+uint32_t GrProxyProvider::contextID() const {
+    return fImageContext->priv().contextID();
+}
+
+const GrCaps* GrProxyProvider::caps() const {
+    return fImageContext->priv().caps();
+}
+
+sk_sp<const GrCaps> GrProxyProvider::refCaps() const {
+    return fImageContext->priv().refCaps();
+}
+
+bool GrProxyProvider::isAbandoned() const {
+    return fImageContext->priv().abandoned();
+}
+
 void GrProxyProvider::orphanAllUniqueKeys() {
     UniquelyKeyedProxyHash::Iter iter(&fUniquelyKeyedProxies);
     for (UniquelyKeyedProxyHash::Iter iter(&fUniquelyKeyedProxies); !iter.done(); ++iter) {
@@ -792,3 +855,7 @@
     }
     SkASSERT(!fUniquelyKeyedProxies.count());
 }
+
+bool GrProxyProvider::renderingDirectly() const {
+    return fImageContext->priv().asDirectContext();
+}
diff --git a/src/gpu/GrProxyProvider.h b/src/gpu/GrProxyProvider.h
index f7481e5..089093c 100644
--- a/src/gpu/GrProxyProvider.h
+++ b/src/gpu/GrProxyProvider.h
@@ -8,15 +8,12 @@
 #ifndef GrProxyProvider_DEFINED
 #define GrProxyProvider_DEFINED
 
-#include "GrCaps.h"
 #include "GrResourceKey.h"
 #include "GrTextureProxy.h"
 #include "GrTypes.h"
-#include "SkRefCnt.h"
 #include "SkTDynamicHash.h"
 
-class GrResourceProvider;
-class GrSingleOwner;
+class GrImageContext;
 class GrBackendRenderTarget;
 class SkBitmap;
 class SkImage;
@@ -26,8 +23,7 @@
  */
 class GrProxyProvider {
 public:
-    GrProxyProvider(GrResourceProvider*, GrResourceCache*, sk_sp<const GrCaps>, GrSingleOwner*);
-    GrProxyProvider(uint32_t contextUniqueID, sk_sp<const GrCaps>, GrSingleOwner*);
+    GrProxyProvider(GrImageContext*);
 
     ~GrProxyProvider();
 
@@ -80,9 +76,9 @@
                                             GrSurfaceOrigin, SkBudgeted);
 
     /*
-     * Creates a new mipmapped texture proxy for the bitmap with mip levels generated by the cpu.
+     * Creates a new texture proxy for the bitmap, optionally with mip levels generated by the cpu.
      */
-    sk_sp<GrTextureProxy> createMipMapProxyFromBitmap(const SkBitmap& bitmap);
+    sk_sp<GrTextureProxy> createProxyFromBitmap(const SkBitmap& bitmap, GrMipMapped);
 
     /*
      * Create a GrSurfaceProxy without any data.
@@ -140,7 +136,9 @@
     sk_sp<GrRenderTargetProxy> wrapVulkanSecondaryCBAsRenderTarget(const SkImageInfo&,
                                                                    const GrVkDrawableInfo&);
 
-    using LazyInstantiateCallback = std::function<sk_sp<GrSurface>(GrResourceProvider*)>;
+    using LazyInstantiationKeyMode = GrSurfaceProxy::LazyInstantiationKeyMode;
+    using LazyInstantiationResult = GrSurfaceProxy::LazyInstantiationResult;
+    using LazyInstantiateCallback = GrSurfaceProxy::LazyInstantiateCallback;
 
     enum class Renderable : bool {
         kNo = false,
@@ -184,7 +182,8 @@
                                                            GrInternalSurfaceFlags,
                                                            const TextureInfo*,
                                                            SkBackingFit,
-                                                           SkBudgeted);
+                                                           SkBudgeted,
+                                                           bool wrapsVkSecondaryCB);
 
     /**
      * Fully lazy proxies have unspecified width and height. Methods that rely on those values
@@ -215,24 +214,10 @@
      */
     void processInvalidUniqueKey(const GrUniqueKey&, GrTextureProxy*, InvalidateGPUResource);
 
-    uint32_t contextUniqueID() const { return fContextUniqueID; }
-    const GrCaps* caps() const { return fCaps.get(); }
-    sk_sp<const GrCaps> refCaps() const { return fCaps; }
-
-    void abandon() {
-        fResourceCache = nullptr;
-        fResourceProvider = nullptr;
-        fAbandoned = true;
-    }
-
-    bool isAbandoned() const {
-#ifdef SK_DEBUG
-        if (fAbandoned) {
-            SkASSERT(!fResourceCache && !fResourceProvider);
-        }
-#endif
-        return fAbandoned;
-    }
+    // TODO: remove these entry points - it is a bit sloppy to be getting context info from here
+    uint32_t contextID() const;
+    const GrCaps* caps() const;
+    sk_sp<const GrCaps> refCaps() const;
 
     int numUniqueKeyProxies_TestOnly() const;
 
@@ -244,21 +229,26 @@
     void removeAllUniqueKeys();
 
     /**
-     * Are we currently recording a DDL?
+     * Does the proxy provider have access to a GrDirectContext? If so, proxies will be
+     * instantiated immediately.
      */
-    bool recordingDDL() const { return !SkToBool(fResourceProvider); }
+    bool renderingDirectly() const;
 
+#if GR_TEST_UTILS
     /*
      * Create a texture proxy that is backed by an instantiated GrSurface.
      */
     sk_sp<GrTextureProxy> testingOnly_createInstantiatedProxy(const GrSurfaceDesc&, GrSurfaceOrigin,
                                                               SkBackingFit, SkBudgeted);
     sk_sp<GrTextureProxy> testingOnly_createWrapped(sk_sp<GrTexture>, GrSurfaceOrigin);
+#endif
 
 private:
     friend class GrAHardwareBufferImageGenerator; // for createWrapped
     friend class GrResourceProvider; // for createWrapped
 
+    bool isAbandoned() const;
+
     sk_sp<GrTextureProxy> createWrapped(sk_sp<GrTexture> tex, GrSurfaceOrigin origin);
 
     struct UniquelyKeyedProxyHashTraits {
@@ -272,15 +262,7 @@
     // on these proxies but they must send a message to the resourceCache when they are deleted.
     UniquelyKeyedProxyHash fUniquelyKeyedProxies;
 
-    GrResourceProvider*    fResourceProvider;
-    GrResourceCache*       fResourceCache;
-    bool                   fAbandoned;
-    sk_sp<const GrCaps>    fCaps;
-    // If this provider is owned by a DDLContext then this is the DirectContext's ID.
-    uint32_t               fContextUniqueID;
-
-    // In debug builds we guard against improper thread handling
-    SkDEBUGCODE(mutable GrSingleOwner* fSingleOwner;)
+    GrImageContext*        fImageContext;
 };
 
 #endif
diff --git a/src/gpu/GrQuad.cpp b/src/gpu/GrQuad.cpp
index 0996ec7..c741827 100644
--- a/src/gpu/GrQuad.cpp
+++ b/src/gpu/GrQuad.cpp
@@ -91,6 +91,60 @@
     return !SkScalarIsInt(ql) || !SkScalarIsInt(qr) || !SkScalarIsInt(qt) || !SkScalarIsInt(qb);
 }
 
+static void map_rect_translate_scale(const SkRect& rect, const SkMatrix& m,
+                                     Sk4f* xs, Sk4f* ys) {
+    SkMatrix::TypeMask tm = m.getType();
+    SkASSERT(tm <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask));
+
+    Sk4f r = Sk4f::Load(&rect);
+    if (tm > SkMatrix::kIdentity_Mask) {
+        const Sk4f t(m.getTranslateX(), m.getTranslateY(), m.getTranslateX(), m.getTranslateY());
+        if (tm <= SkMatrix::kTranslate_Mask) {
+            r += t;
+        } else {
+            const Sk4f s(m.getScaleX(), m.getScaleY(), m.getScaleX(), m.getScaleY());
+            r = r * s + t;
+        }
+    }
+    *xs = SkNx_shuffle<0, 0, 2, 2>(r);
+    *ys = SkNx_shuffle<1, 3, 1, 3>(r);
+}
+
+static void map_quad_general(const Sk4f& qx, const Sk4f& qy, const SkMatrix& m,
+                             Sk4f* xs, Sk4f* ys, Sk4f* ws) {
+    static constexpr auto fma = SkNx_fma<4, float>;
+    *xs = fma(m.getScaleX(), qx, fma(m.getSkewX(), qy, m.getTranslateX()));
+    *ys = fma(m.getSkewY(), qx, fma(m.getScaleY(), qy, m.getTranslateY()));
+    if (m.hasPerspective()) {
+        Sk4f w = fma(m.getPerspX(), qx, fma(m.getPerspY(), qy, m.get(SkMatrix::kMPersp2)));
+        if (ws) {
+            // Output the calculated w coordinates
+            *ws = w;
+        } else {
+            // Apply perspective division immediately
+            Sk4f iw = w.invert();
+            *xs *= iw;
+            *ys *= iw;
+        }
+    } else if (ws) {
+        *ws = 1.f;
+    }
+}
+
+static void map_rect_general(const SkRect& rect, const SkMatrix& matrix,
+                             Sk4f* xs, Sk4f* ys, Sk4f* ws) {
+    Sk4f rx(rect.fLeft, rect.fLeft, rect.fRight, rect.fRight);
+    Sk4f ry(rect.fTop, rect.fBottom, rect.fTop, rect.fBottom);
+    map_quad_general(rx, ry, matrix, xs, ys, ws);
+}
+
+// Rearranges (top-left, top-right, bottom-right, bottom-left) ordered skQuadPts into xs and ys
+// ordered (top-left, bottom-left, top-right, bottom-right)
+static void rearrange_sk_to_gr_points(const SkPoint skQuadPts[4], Sk4f* xs, Sk4f* ys) {
+    *xs = Sk4f(skQuadPts[0].fX, skQuadPts[3].fX, skQuadPts[1].fX, skQuadPts[2].fX);
+    *ys = Sk4f(skQuadPts[0].fY, skQuadPts[3].fY, skQuadPts[1].fY, skQuadPts[2].fY);
+}
+
 template <typename Q>
 void GrResolveAATypeForQuad(GrAAType requestedAAType, GrQuadAAFlags requestedEdgeFlags,
                             const Q& quad, GrQuadType knownType,
@@ -146,40 +200,41 @@
     }
 }
 
-GrQuad::GrQuad(const SkRect& rect, const SkMatrix& m) {
+GrQuadType GrQuadTypeForPoints(const SkPoint pts[4], const SkMatrix& matrix) {
+    if (matrix.hasPerspective()) {
+        return GrQuadType::kPerspective;
+    }
+    // If 'pts' was formed by SkRect::toQuad() and not transformed further, it is safe to use the
+    // quad type derived from 'matrix'. Otherwise don't waste any more time and assume kStandard
+    // (most general 2D quad).
+    if ((pts[0].fY == pts[3].fY && pts[1].fY == pts[2].fY) &&
+        (pts[0].fX == pts[1].fX && pts[2].fX == pts[3].fX)) {
+        return GrQuadTypeForTransformedRect(matrix);
+    } else {
+        return GrQuadType::kStandard;
+    }
+}
+
+GrQuad GrQuad::MakeFromRect(const SkRect& rect, const SkMatrix& m) {
+    Sk4f x, y;
     SkMatrix::TypeMask tm = m.getType();
     if (tm <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)) {
-        auto r = Sk4f::Load(&rect);
-        const Sk4f t(m.getTranslateX(), m.getTranslateY(), m.getTranslateX(), m.getTranslateY());
-        if (tm <= SkMatrix::kTranslate_Mask) {
-            r += t;
-        } else {
-            const Sk4f s(m.getScaleX(), m.getScaleY(), m.getScaleX(), m.getScaleY());
-            r = r * s + t;
-        }
-        SkNx_shuffle<0, 0, 2, 2>(r).store(fX);
-        SkNx_shuffle<1, 3, 1, 3>(r).store(fY);
+        map_rect_translate_scale(rect, m, &x, &y);
     } else {
-        Sk4f rx(rect.fLeft, rect.fLeft, rect.fRight, rect.fRight);
-        Sk4f ry(rect.fTop, rect.fBottom, rect.fTop, rect.fBottom);
-        Sk4f sx(m.getScaleX());
-        Sk4f kx(m.getSkewX());
-        Sk4f tx(m.getTranslateX());
-        Sk4f ky(m.getSkewY());
-        Sk4f sy(m.getScaleY());
-        Sk4f ty(m.getTranslateY());
-        auto x = SkNx_fma(sx, rx, SkNx_fma(kx, ry, tx));
-        auto y = SkNx_fma(ky, rx, SkNx_fma(sy, ry, ty));
-        if (m.hasPerspective()) {
-            Sk4f w0(m.getPerspX());
-            Sk4f w1(m.getPerspY());
-            Sk4f w2(m.get(SkMatrix::kMPersp2));
-            auto iw = SkNx_fma(w0, rx, SkNx_fma(w1, ry, w2)).invert();
-            x *= iw;
-            y *= iw;
-        }
-        x.store(fX);
-        y.store(fY);
+        map_rect_general(rect, m, &x, &y, nullptr);
+    }
+    return GrQuad(x, y);
+}
+
+GrQuad GrQuad::MakeFromSkQuad(const SkPoint pts[4], const SkMatrix& matrix) {
+    Sk4f xs, ys;
+    rearrange_sk_to_gr_points(pts, &xs, &ys);
+    if (matrix.isIdentity()) {
+        return GrQuad(xs, ys);
+    } else {
+        Sk4f mx, my;
+        map_quad_general(xs, ys, matrix, &mx, &my, nullptr);
+        return GrQuad(mx, my);
     }
 }
 
@@ -188,43 +243,6 @@
     return aa_affects_rect(fX[0], fY[0], fX[3], fY[3]);
 }
 
-GrPerspQuad::GrPerspQuad(const SkRect& rect, const SkMatrix& m) {
-    SkMatrix::TypeMask tm = m.getType();
-    if (tm <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)) {
-        auto r = Sk4f::Load(&rect);
-        const Sk4f t(m.getTranslateX(), m.getTranslateY(), m.getTranslateX(), m.getTranslateY());
-        if (tm <= SkMatrix::kTranslate_Mask) {
-            r += t;
-        } else {
-            const Sk4f s(m.getScaleX(), m.getScaleY(), m.getScaleX(), m.getScaleY());
-            r = r * s + t;
-        }
-        SkNx_shuffle<0, 0, 2, 2>(r).store(fX);
-        SkNx_shuffle<1, 3, 1, 3>(r).store(fY);
-        fW[0] = fW[1] = fW[2] = fW[3] = 1.f;
-    } else {
-        Sk4f rx(rect.fLeft, rect.fLeft, rect.fRight, rect.fRight);
-        Sk4f ry(rect.fTop, rect.fBottom, rect.fTop, rect.fBottom);
-        Sk4f sx(m.getScaleX());
-        Sk4f kx(m.getSkewX());
-        Sk4f tx(m.getTranslateX());
-        Sk4f ky(m.getSkewY());
-        Sk4f sy(m.getScaleY());
-        Sk4f ty(m.getTranslateY());
-        SkNx_fma(sx, rx, SkNx_fma(kx, ry, tx)).store(fX);
-        SkNx_fma(ky, rx, SkNx_fma(sy, ry, ty)).store(fY);
-        if (m.hasPerspective()) {
-            Sk4f w0(m.getPerspX());
-            Sk4f w1(m.getPerspY());
-            Sk4f w2(m.get(SkMatrix::kMPersp2));
-            auto w = SkNx_fma(w0, rx, SkNx_fma(w1, ry, w2));
-            w.store(fW);
-        } else {
-            fW[0] = fW[1] = fW[2] = fW[3] = 1.f;
-        }
-    }
-}
-
 // Private constructor used by GrQuadList to quickly fill in a quad's values from the channel arrays
 GrPerspQuad::GrPerspQuad(const float* xs, const float* ys, const float* ws) {
     memcpy(fX, xs, 4 * sizeof(float));
@@ -232,6 +250,30 @@
     memcpy(fW, ws, 4 * sizeof(float));
 }
 
+GrPerspQuad GrPerspQuad::MakeFromRect(const SkRect& rect, const SkMatrix& m) {
+    Sk4f x, y, w;
+    SkMatrix::TypeMask tm = m.getType();
+    if (tm <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)) {
+        map_rect_translate_scale(rect, m, &x, &y);
+        w = 1.f;
+    } else {
+        map_rect_general(rect, m, &x, &y, &w);
+    }
+    return GrPerspQuad(x, y, w);
+}
+
+GrPerspQuad GrPerspQuad::MakeFromSkQuad(const SkPoint pts[4], const SkMatrix& matrix) {
+    Sk4f xs, ys;
+    rearrange_sk_to_gr_points(pts, &xs, &ys);
+    if (matrix.isIdentity()) {
+        return GrPerspQuad(xs, ys, 1.f);
+    } else {
+        Sk4f mx, my, mw;
+        map_quad_general(xs, ys, matrix, &mx, &my, &mw);
+        return GrPerspQuad(mx, my, mw);
+    }
+}
+
 bool GrPerspQuad::aaHasEffectOnRect() const {
     SkASSERT(this->quadType() == GrQuadType::kRect);
     // If rect, ws must all be 1s so no need to divide
diff --git a/src/gpu/GrQuad.h b/src/gpu/GrQuad.h
index 33fd326..675a088 100644
--- a/src/gpu/GrQuad.h
+++ b/src/gpu/GrQuad.h
@@ -38,6 +38,9 @@
 // quadType() is only provided on Gr[Persp]Quad in debug builds, production code should use this
 // to efficiently determine quad types.
 GrQuadType GrQuadTypeForTransformedRect(const SkMatrix& matrix);
+// Perform minimal analysis of 'pts' (which are suitable for MakeFromSkQuad), and determine a
+// quad type that will be as minimally general as possible.
+GrQuadType GrQuadTypeForPoints(const SkPoint pts[4], const SkMatrix& matrix);
 
 // Resolve disagreements between the overall requested AA type and the per-edge quad AA flags.
 // knownQuadType must have come from GrQuadTypeForTransformedRect with the matrix that created the
@@ -61,13 +64,24 @@
             : fX{rect.fLeft, rect.fLeft, rect.fRight, rect.fRight}
             , fY{rect.fTop, rect.fBottom, rect.fTop, rect.fBottom} {}
 
-    /** Sets the quad to the rect as transformed by the matrix. */
-    GrQuad(const SkRect&, const SkMatrix&);
+    GrQuad(const Sk4f& xs, const Sk4f& ys) {
+        xs.store(fX);
+        ys.store(fY);
+    }
 
     explicit GrQuad(const SkPoint pts[4])
             : fX{pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX}
             , fY{pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY} {}
 
+    /** Sets the quad to the rect as transformed by the matrix. */
+    static GrQuad MakeFromRect(const SkRect&, const SkMatrix&);
+
+    // Creates a GrQuad from the quadrilateral 'pts', transformed by the matrix. Unlike the explicit
+    // constructor, the input points array is arranged as per SkRect::toQuad (top-left, top-right,
+    // bottom-right, bottom-left). The returned instance's point order will still be CCW tri-strip
+    // order.
+    static GrQuad MakeFromSkQuad(const SkPoint pts[4], const SkMatrix&);
+
     GrQuad& operator=(const GrQuad& that) = default;
 
     SkPoint point(int i) const { return {fX[i], fY[i]}; }
@@ -102,7 +116,29 @@
 public:
     GrPerspQuad() = default;
 
-    GrPerspQuad(const SkRect&, const SkMatrix&);
+    explicit GrPerspQuad(const SkRect& rect)
+            : fX{rect.fLeft, rect.fLeft, rect.fRight, rect.fRight}
+            , fY{rect.fTop, rect.fBottom, rect.fTop, rect.fBottom}
+            , fW{1.f, 1.f, 1.f, 1.f} {}
+
+    GrPerspQuad(const Sk4f& xs, const Sk4f& ys) {
+        xs.store(fX);
+        ys.store(fY);
+        fW[0] = fW[1] = fW[2] = fW[3] = 1.f;
+    }
+
+    GrPerspQuad(const Sk4f& xs, const Sk4f& ys, const Sk4f& ws) {
+        xs.store(fX);
+        ys.store(fY);
+        ws.store(fW);
+    }
+
+    static GrPerspQuad MakeFromRect(const SkRect&, const SkMatrix&);
+
+    // Creates a GrPerspQuad from the quadrilateral 'pts', transformed by the matrix. The input
+    // points array is arranged as per SkRect::toQuad (top-left, top-right, bottom-right,
+    // bottom-left). The returned instance's point order will still be CCW tri-strip order.
+    static GrPerspQuad MakeFromSkQuad(const SkPoint pts[4], const SkMatrix&);
 
     GrPerspQuad& operator=(const GrPerspQuad&) = default;
 
diff --git a/src/gpu/GrRecordingContext.cpp b/src/gpu/GrRecordingContext.cpp
index ca19782..7adb948 100644
--- a/src/gpu/GrRecordingContext.cpp
+++ b/src/gpu/GrRecordingContext.cpp
@@ -7,11 +7,365 @@
 
 #include "GrRecordingContext.h"
 
+#include "GrCaps.h"
+#include "GrContext.h"
+#include "GrDrawingManager.h"
+#include "GrMemoryPool.h"
+#include "GrProxyProvider.h"
+#include "GrRecordingContextPriv.h"
+#include "GrRenderTargetContext.h"
+#include "GrSkSLFPFactoryCache.h"
+#include "GrTextureContext.h"
+#include "SkGr.h"
+#include "text/GrTextBlobCache.h"
+
+#define ASSERT_SINGLE_OWNER_PRIV \
+    SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(this->singleOwner());)
+
 GrRecordingContext::GrRecordingContext(GrBackendApi backend,
                                        const GrContextOptions& options,
-                                       uint32_t uniqueID)
-        : INHERITED(backend, options, uniqueID) {
+                                       uint32_t contextID)
+        : INHERITED(backend, options, contextID) {
 }
 
 GrRecordingContext::~GrRecordingContext() { }
 
+/**
+ * TODO: move textblob draw calls below context (see comment below)
+ */
+static void textblobcache_overbudget_CB(void* data) {
+    SkASSERT(data);
+    GrRecordingContext* context = reinterpret_cast<GrRecordingContext*>(data);
+
+    GrContext* direct = context->priv().asDirectContext();
+    if (!direct) {
+        return;
+    }
+
+    // TextBlobs are drawn at the SkGpuDevice level, therefore they cannot rely on
+    // GrRenderTargetContext to perform a necessary flush.  The solution is to move drawText calls
+    // to below the GrContext level, but this is not trivial because they call drawPath on
+    // SkGpuDevice.
+    direct->flush();
+}
+
+bool GrRecordingContext::init(sk_sp<const GrCaps> caps, sk_sp<GrSkSLFPFactoryCache> cache) {
+
+    if (!INHERITED::init(std::move(caps), std::move(cache))) {
+        return false;
+    }
+
+    fStrikeCache.reset(new GrStrikeCache(this->caps(),
+                                        this->options().fGlyphCacheTextureMaximumBytes));
+
+    fTextBlobCache.reset(new GrTextBlobCache(textblobcache_overbudget_CB, this,
+                                             this->contextID()));
+
+    return true;
+}
+
+void GrRecordingContext::setupDrawingManager(bool explicitlyAllocate, bool sortOpLists) {
+    GrPathRendererChain::Options prcOptions;
+    prcOptions.fAllowPathMaskCaching = this->options().fAllowPathMaskCaching;
+#if GR_TEST_UTILS
+    prcOptions.fGpuPathRenderers = this->options().fGpuPathRenderers;
+#endif
+    // FIXME: Once this is removed from Chrome and Android, rename to fEnable"".
+    if (!this->options().fDisableCoverageCountingPaths) {
+        prcOptions.fGpuPathRenderers |= GpuPathRenderers::kCoverageCounting;
+    }
+    if (this->options().fDisableDistanceFieldPaths) {
+        prcOptions.fGpuPathRenderers &= ~GpuPathRenderers::kSmall;
+    }
+
+    if (!this->proxyProvider()->renderingDirectly()) {
+        // DDL TODO: remove this crippling of the path renderer chain
+        // Disable the small path renderer bc of the proxies in the atlas. They need to be
+        // unified when the opLists are added back to the destination drawing manager.
+        prcOptions.fGpuPathRenderers &= ~GpuPathRenderers::kSmall;
+    }
+
+    GrTextContext::Options textContextOptions;
+    textContextOptions.fMaxDistanceFieldFontSize = this->options().fGlyphsAsPathsFontSize;
+    textContextOptions.fMinDistanceFieldFontSize = this->options().fMinDistanceFieldFontSize;
+    textContextOptions.fDistanceFieldVerticesAlwaysHaveW = false;
+#if SK_SUPPORT_ATLAS_TEXT
+    if (GrContextOptions::Enable::kYes == this->options().fDistanceFieldGlyphVerticesAlwaysHaveW) {
+        textContextOptions.fDistanceFieldVerticesAlwaysHaveW = true;
+    }
+#endif
+
+    // SHORT TERM TODO: until intermediate flushes at allocation time are added we need to obey the
+    // reduceOpListSplitting flag. Once that lands we should always reduce opList splitting in
+    // DDL contexts/drawing managers. We should still obey the options for non-DDL drawing managers
+    // until predictive intermediate flushes are added (i.e., we can't reorder forever).
+    fDrawingManager.reset(new GrDrawingManager(this,
+                                                prcOptions,
+                                                textContextOptions,
+                                                explicitlyAllocate,
+                                                sortOpLists,
+                                                this->options().fReduceOpListSplitting));
+}
+
+void GrRecordingContext::abandonContext() {
+    INHERITED::abandonContext();
+
+    fStrikeCache->freeAll();
+    fTextBlobCache->freeAll();
+}
+
+GrDrawingManager* GrRecordingContext::drawingManager() {
+    return fDrawingManager.get();
+}
+
+sk_sp<GrOpMemoryPool> GrRecordingContext::refOpMemoryPool() {
+    if (!fOpMemoryPool) {
+        // DDL TODO: should the size of the memory pool be decreased in DDL mode? CPU-side memory
+        // consumed in DDL mode vs. normal mode for a single skp might be a good metric of wasted
+        // memory.
+        fOpMemoryPool = sk_sp<GrOpMemoryPool>(new GrOpMemoryPool(16384, 16384));
+    }
+
+    SkASSERT(fOpMemoryPool);
+    return fOpMemoryPool;
+}
+
+GrOpMemoryPool* GrRecordingContext::opMemoryPool() {
+    return this->refOpMemoryPool().get();
+}
+
+GrTextBlobCache* GrRecordingContext::getTextBlobCache() {
+    return fTextBlobCache.get();
+}
+
+const GrTextBlobCache* GrRecordingContext::getTextBlobCache() const {
+    return fTextBlobCache.get();
+}
+
+void GrRecordingContext::addOnFlushCallbackObject(GrOnFlushCallbackObject* onFlushCBObject) {
+    this->drawingManager()->addOnFlushCallbackObject(onFlushCBObject);
+}
+
+sk_sp<GrSurfaceContext> GrRecordingContext::makeWrappedSurfaceContext(
+                                                                 sk_sp<GrSurfaceProxy> proxy,
+                                                                 sk_sp<SkColorSpace> colorSpace,
+                                                                 const SkSurfaceProps* props) {
+    ASSERT_SINGLE_OWNER_PRIV
+
+    if (proxy->asRenderTargetProxy()) {
+        return this->drawingManager()->makeRenderTargetContext(std::move(proxy),
+                                                               std::move(colorSpace), props);
+    } else {
+        SkASSERT(proxy->asTextureProxy());
+        SkASSERT(!props);
+        return this->drawingManager()->makeTextureContext(std::move(proxy), std::move(colorSpace));
+    }
+}
+
+sk_sp<GrSurfaceContext> GrRecordingContext::makeDeferredSurfaceContext(
+                                                                  const GrBackendFormat& format,
+                                                                  const GrSurfaceDesc& dstDesc,
+                                                                  GrSurfaceOrigin origin,
+                                                                  GrMipMapped mipMapped,
+                                                                  SkBackingFit fit,
+                                                                  SkBudgeted isDstBudgeted,
+                                                                  sk_sp<SkColorSpace> colorSpace,
+                                                                  const SkSurfaceProps* props) {
+    sk_sp<GrTextureProxy> proxy;
+    if (GrMipMapped::kNo == mipMapped) {
+        proxy = this->proxyProvider()->createProxy(format, dstDesc, origin, fit, isDstBudgeted);
+    } else {
+        SkASSERT(SkBackingFit::kExact == fit);
+        proxy = this->proxyProvider()->createMipMapProxy(format, dstDesc, origin, isDstBudgeted);
+    }
+    if (!proxy) {
+        return nullptr;
+    }
+
+    sk_sp<GrSurfaceContext> sContext = this->makeWrappedSurfaceContext(std::move(proxy),
+                                                                       std::move(colorSpace),
+                                                                       props);
+    if (sContext && sContext->asRenderTargetContext()) {
+        sContext->asRenderTargetContext()->discard();
+    }
+
+    return sContext;
+}
+
+sk_sp<GrRenderTargetContext> GrRecordingContext::makeDeferredRenderTargetContext(
+                                                        const GrBackendFormat& format,
+                                                        SkBackingFit fit,
+                                                        int width, int height,
+                                                        GrPixelConfig config,
+                                                        sk_sp<SkColorSpace> colorSpace,
+                                                        int sampleCnt,
+                                                        GrMipMapped mipMapped,
+                                                        GrSurfaceOrigin origin,
+                                                        const SkSurfaceProps* surfaceProps,
+                                                        SkBudgeted budgeted) {
+    SkASSERT(sampleCnt > 0);
+    if (this->abandoned()) {
+        return nullptr;
+    }
+
+    GrSurfaceDesc desc;
+    desc.fFlags = kRenderTarget_GrSurfaceFlag;
+    desc.fWidth = width;
+    desc.fHeight = height;
+    desc.fConfig = config;
+    desc.fSampleCnt = sampleCnt;
+
+    sk_sp<GrTextureProxy> rtp;
+    if (GrMipMapped::kNo == mipMapped) {
+        rtp = this->proxyProvider()->createProxy(format, desc, origin, fit, budgeted);
+    } else {
+        rtp = this->proxyProvider()->createMipMapProxy(format, desc, origin, budgeted);
+    }
+    if (!rtp) {
+        return nullptr;
+    }
+
+    auto drawingManager = this->drawingManager();
+
+    sk_sp<GrRenderTargetContext> renderTargetContext =
+        drawingManager->makeRenderTargetContext(std::move(rtp), std::move(colorSpace),
+                                                surfaceProps);
+    if (!renderTargetContext) {
+        return nullptr;
+    }
+
+    renderTargetContext->discard();
+
+    return renderTargetContext;
+}
+
+static inline GrPixelConfig GrPixelConfigFallback(GrPixelConfig config) {
+    switch (config) {
+        case kAlpha_8_GrPixelConfig:
+        case kAlpha_8_as_Alpha_GrPixelConfig:
+        case kAlpha_8_as_Red_GrPixelConfig:
+        case kRGB_565_GrPixelConfig:
+        case kRGBA_4444_GrPixelConfig:
+        case kBGRA_8888_GrPixelConfig:
+        case kRGBA_1010102_GrPixelConfig:
+        case kRGBA_half_GrPixelConfig:
+        case kRGBA_half_Clamped_GrPixelConfig:
+            return kRGBA_8888_GrPixelConfig;
+        case kSBGRA_8888_GrPixelConfig:
+            return kSRGBA_8888_GrPixelConfig;
+        case kAlpha_half_GrPixelConfig:
+        case kAlpha_half_as_Red_GrPixelConfig:
+            return kRGBA_half_GrPixelConfig;
+        case kGray_8_GrPixelConfig:
+        case kGray_8_as_Lum_GrPixelConfig:
+        case kGray_8_as_Red_GrPixelConfig:
+            return kRGB_888_GrPixelConfig;
+        default:
+            return kUnknown_GrPixelConfig;
+    }
+}
+
+sk_sp<GrRenderTargetContext> GrRecordingContext::makeDeferredRenderTargetContextWithFallback(
+                                                                 const GrBackendFormat& format,
+                                                                 SkBackingFit fit,
+                                                                 int width, int height,
+                                                                 GrPixelConfig config,
+                                                                 sk_sp<SkColorSpace> colorSpace,
+                                                                 int sampleCnt,
+                                                                 GrMipMapped mipMapped,
+                                                                 GrSurfaceOrigin origin,
+                                                                 const SkSurfaceProps* surfaceProps,
+                                                                 SkBudgeted budgeted) {
+    GrBackendFormat localFormat = format;
+    SkASSERT(sampleCnt > 0);
+    if (0 == this->caps()->getRenderTargetSampleCount(sampleCnt, config)) {
+        config = GrPixelConfigFallback(config);
+        // TODO: First we should be checking the getRenderTargetSampleCount from the GrBackendFormat
+        // and not GrPixelConfig. Besides that, we should implement the fallback in the caps, but
+        // for now we just convert the fallback pixel config to an SkColorType and then get the
+        // GrBackendFormat from that.
+        SkColorType colorType;
+        if (!GrPixelConfigToColorType(config, &colorType)) {
+            return nullptr;
+        }
+        localFormat = this->caps()->getBackendFormatFromColorType(colorType);
+    }
+
+    return this->makeDeferredRenderTargetContext(localFormat, fit, width, height, config,
+                                                 std::move(colorSpace), sampleCnt, mipMapped,
+                                                 origin, surfaceProps, budgeted);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+sk_sp<const GrCaps> GrRecordingContextPriv::refCaps() const {
+    return fContext->refCaps();
+}
+
+sk_sp<GrSkSLFPFactoryCache> GrRecordingContextPriv::fpFactoryCache() {
+    return fContext->fpFactoryCache();
+}
+
+sk_sp<GrOpMemoryPool> GrRecordingContextPriv::refOpMemoryPool() {
+    return fContext->refOpMemoryPool();
+}
+
+void GrRecordingContextPriv::addOnFlushCallbackObject(GrOnFlushCallbackObject* onFlushCBObject) {
+    fContext->addOnFlushCallbackObject(onFlushCBObject);
+}
+
+sk_sp<GrSurfaceContext> GrRecordingContextPriv::makeWrappedSurfaceContext(
+                                                    sk_sp<GrSurfaceProxy> proxy,
+                                                    sk_sp<SkColorSpace> colorSpace,
+                                                    const SkSurfaceProps* props) {
+    return fContext->makeWrappedSurfaceContext(std::move(proxy), std::move(colorSpace), props);
+}
+
+sk_sp<GrSurfaceContext> GrRecordingContextPriv::makeDeferredSurfaceContext(
+                                                    const GrBackendFormat& format,
+                                                    const GrSurfaceDesc& dstDesc,
+                                                    GrSurfaceOrigin origin,
+                                                    GrMipMapped mipMapped,
+                                                    SkBackingFit fit,
+                                                    SkBudgeted isDstBudgeted,
+                                                    sk_sp<SkColorSpace> colorSpace,
+                                                    const SkSurfaceProps* props) {
+    return fContext->makeDeferredSurfaceContext(format, dstDesc, origin, mipMapped, fit,
+                                                isDstBudgeted, std::move(colorSpace), props);
+}
+
+sk_sp<GrRenderTargetContext> GrRecordingContextPriv::makeDeferredRenderTargetContext(
+                                                    const GrBackendFormat& format,
+                                                    SkBackingFit fit,
+                                                    int width, int height,
+                                                    GrPixelConfig config,
+                                                    sk_sp<SkColorSpace> colorSpace,
+                                                    int sampleCnt,
+                                                    GrMipMapped mipMapped,
+                                                    GrSurfaceOrigin origin,
+                                                    const SkSurfaceProps* surfaceProps,
+                                                    SkBudgeted budgeted) {
+    return fContext->makeDeferredRenderTargetContext(format, fit, width, height, config,
+                                                     std::move(colorSpace), sampleCnt, mipMapped,
+                                                     origin, surfaceProps, budgeted);
+}
+
+sk_sp<GrRenderTargetContext> GrRecordingContextPriv::makeDeferredRenderTargetContextWithFallback(
+                                        const GrBackendFormat& format,
+                                        SkBackingFit fit,
+                                        int width, int height,
+                                        GrPixelConfig config,
+                                        sk_sp<SkColorSpace> colorSpace,
+                                        int sampleCnt,
+                                        GrMipMapped mipMapped,
+                                        GrSurfaceOrigin origin,
+                                        const SkSurfaceProps* surfaceProps,
+                                        SkBudgeted budgeted) {
+    return fContext->makeDeferredRenderTargetContextWithFallback(format, fit, width, height, config,
+                                                                 std::move(colorSpace), sampleCnt,
+                                                                 mipMapped, origin, surfaceProps,
+                                                                 budgeted);
+}
+
+GrContext* GrRecordingContextPriv::backdoor() {
+    return (GrContext*) fContext;
+}
diff --git a/src/gpu/GrRecordingContextPriv.h b/src/gpu/GrRecordingContextPriv.h
index e4cd3c4..46f13f2 100644
--- a/src/gpu/GrRecordingContextPriv.h
+++ b/src/gpu/GrRecordingContextPriv.h
@@ -18,16 +18,102 @@
     // from GrContext_Base
     uint32_t contextID() const { return fContext->contextID(); }
 
+    bool matches(GrContext_Base* candidate) const { return fContext->matches(candidate); }
+
     const GrContextOptions& options() const { return fContext->options(); }
 
-    const GrCaps* caps() const { return fContext->caps(); }
-    sk_sp<const GrCaps> refCaps() const { return fContext->refCaps(); }
+    bool explicitlyAllocateGPUResources() const {
+        return fContext->explicitlyAllocateGPUResources();
+    }
 
-    sk_sp<GrSkSLFPFactoryCache> fpFactoryCache(); // { return fContext->getFPFactoryCache(); }
+    const GrCaps* caps() const { return fContext->caps(); }
+    sk_sp<const GrCaps> refCaps() const;
+
+    sk_sp<GrSkSLFPFactoryCache> fpFactoryCache();
+
+    GrImageContext* asImageContext() { return fContext->asImageContext(); }
+    GrRecordingContext* asRecordingContext() { return fContext->asRecordingContext(); }
+    GrContext* asDirectContext() { return fContext->asDirectContext(); }
 
     // from GrImageContext
+    GrProxyProvider* proxyProvider() { return fContext->proxyProvider(); }
+    const GrProxyProvider* proxyProvider() const { return fContext->proxyProvider(); }
+
+    bool abandoned() const { return fContext->abandoned(); }
+
+    /** This is only useful for debug purposes */
+    SkDEBUGCODE(GrSingleOwner* singleOwner() const { return fContext->singleOwner(); } )
 
     // from GrRecordingContext
+    GrDrawingManager* drawingManager() { return fContext->drawingManager(); }
+
+    sk_sp<GrOpMemoryPool> refOpMemoryPool();
+    GrOpMemoryPool* opMemoryPool() { return fContext->opMemoryPool(); }
+
+    GrStrikeCache* getGrStrikeCache() { return fContext->getGrStrikeCache(); }
+    GrTextBlobCache* getTextBlobCache() { return fContext->getTextBlobCache(); }
+
+    /**
+     * Registers an object for flush-related callbacks. (See GrOnFlushCallbackObject.)
+     *
+     * NOTE: the drawing manager tracks this object as a raw pointer; it is up to the caller to
+     * ensure its lifetime is tied to that of the context.
+     */
+    void addOnFlushCallbackObject(GrOnFlushCallbackObject*);
+
+    sk_sp<GrSurfaceContext> makeWrappedSurfaceContext(sk_sp<GrSurfaceProxy>,
+                                                      sk_sp<SkColorSpace> = nullptr,
+                                                      const SkSurfaceProps* = nullptr);
+
+    sk_sp<GrSurfaceContext> makeDeferredSurfaceContext(const GrBackendFormat&,
+                                                       const GrSurfaceDesc&,
+                                                       GrSurfaceOrigin,
+                                                       GrMipMapped,
+                                                       SkBackingFit,
+                                                       SkBudgeted,
+                                                       sk_sp<SkColorSpace> colorSpace = nullptr,
+                                                       const SkSurfaceProps* = nullptr);
+
+    /*
+     * Create a new render target context backed by a deferred-style
+     * GrRenderTargetProxy. We guarantee that "asTextureProxy" will succeed for
+     * renderTargetContexts created via this entry point.
+     */
+    sk_sp<GrRenderTargetContext> makeDeferredRenderTargetContext(
+                                            const GrBackendFormat& format,
+                                            SkBackingFit fit,
+                                            int width, int height,
+                                            GrPixelConfig config,
+                                            sk_sp<SkColorSpace> colorSpace,
+                                            int sampleCnt = 1,
+                                            GrMipMapped = GrMipMapped::kNo,
+                                            GrSurfaceOrigin origin = kBottomLeft_GrSurfaceOrigin,
+                                            const SkSurfaceProps* surfaceProps = nullptr,
+                                            SkBudgeted = SkBudgeted::kYes);
+
+    /*
+     * This method will attempt to create a renderTargetContext that has, at least, the number of
+     * channels and precision per channel as requested in 'config' (e.g., A8 and 888 can be
+     * converted to 8888). It may also swizzle the channels (e.g., BGRA -> RGBA).
+     * SRGB-ness will be preserved.
+     */
+    sk_sp<GrRenderTargetContext> makeDeferredRenderTargetContextWithFallback(
+                                            const GrBackendFormat& format,
+                                            SkBackingFit fit,
+                                            int width, int height,
+                                            GrPixelConfig config,
+                                            sk_sp<SkColorSpace> colorSpace,
+                                            int sampleCnt = 1,
+                                            GrMipMapped = GrMipMapped::kNo,
+                                            GrSurfaceOrigin origin = kBottomLeft_GrSurfaceOrigin,
+                                            const SkSurfaceProps* surfaceProps = nullptr,
+                                            SkBudgeted budgeted = SkBudgeted::kYes);
+
+    GrAuditTrail* auditTrail() { return fContext->auditTrail(); }
+
+    // CONTEXT TODO: remove this backdoor
+    // In order to make progress we temporarily need a way to break CL impasses.
+    GrContext* backdoor();
 
 private:
     explicit GrRecordingContextPriv(GrRecordingContext* context) : fContext(context) {}
diff --git a/src/gpu/GrRect.h b/src/gpu/GrRect.h
index 8c44ed7..aa00534 100644
--- a/src/gpu/GrRect.h
+++ b/src/gpu/GrRect.h
@@ -72,4 +72,14 @@
     SkASSERT(!b.isFinite() || (b.fLeft <= b.fRight && b.fTop <= b.fBottom));
     return a.fRight >= b.fLeft && a.fBottom >= b.fTop && b.fRight >= a.fLeft && b.fBottom >= a.fTop;
 }
+
+/**
+ * Apply the transform from 'inRect' to 'outRect' to each point in 'inPts', storing the mapped point
+ * into the parallel index of 'outPts'.
+ */
+static inline void GrMapRectPoints(const SkRect& inRect, const SkRect& outRect,
+                                   const SkPoint inPts[], SkPoint outPts[], int ptCount) {
+    SkMatrix rectTransform = SkMatrix::MakeRectToRect(inRect, outRect, SkMatrix::kFill_ScaleToFit);
+    rectTransform.mapPoints(outPts, inPts, ptCount);
+}
 #endif
diff --git a/src/gpu/GrReducedClip.cpp b/src/gpu/GrReducedClip.cpp
index 967386f..1123876 100644
--- a/src/gpu/GrReducedClip.cpp
+++ b/src/gpu/GrReducedClip.cpp
@@ -9,10 +9,10 @@
 #include "GrAppliedClip.h"
 #include "GrClip.h"
 #include "GrColor.h"
-#include "GrContextPriv.h"
 #include "GrDrawingManager.h"
 #include "GrFixedClip.h"
 #include "GrPathRenderer.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrRenderTargetContextPriv.h"
 #include "GrShape.h"
@@ -22,7 +22,7 @@
 #include "GrUserStencilSettings.h"
 #include "SkClipOpPriv.h"
 #include "ccpr/GrCoverageCountingPathRenderer.h"
-#include "effects/GrAARectEffect.h"
+#include "effects/generated/GrAARectEffect.h"
 #include "effects/GrConvexPolyEffect.h"
 #include "effects/GrRRectEffect.h"
 
@@ -809,7 +809,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 // Create a 1-bit clip mask in the stencil buffer.
 
-bool GrReducedClip::drawStencilClipMask(GrContext* context,
+bool GrReducedClip::drawStencilClipMask(GrRecordingContext* context,
                                         GrRenderTargetContext* renderTargetContext) const {
     // We set the current clip to the bounds so that our recursive draws are scissored to them.
     GrStencilClip stencilClip(fScissor, this->maskGenID());
@@ -824,12 +824,13 @@
 
     // walk through each clip element and perform its set op with the existing clip.
     for (ElementList::Iter iter(fMaskElements); iter.get(); iter.next()) {
+        using AATypeFlags = GrPathRenderer::AATypeFlags;
         const Element* element = iter.get();
-        GrAAType aaType = GrAAType::kNone;
-        if (element->isAA() && GrFSAAType::kNone != renderTargetContext->fsaaType()) {
-            aaType = GrAAType::kMSAA;
-        }
-
+        bool doStencilMSAA =
+                element->isAA() && GrFSAAType::kNone != renderTargetContext->fsaaType();
+        // Since we are only drawing to the stencil buffer, we can use kMSAA even if the render
+        // target is mixed sampled.
+        auto pathAATypeFlags = (doStencilMSAA) ? AATypeFlags::kMSAA : AATypeFlags::kNone;
         bool fillInverted = false;
 
         // This will be used to determine whether the clip shape can be rendered into the
@@ -856,7 +857,7 @@
             canDrawArgs.fClipConservativeBounds = &stencilClip.fixedClip().scissorRect();
             canDrawArgs.fViewMatrix = &SkMatrix::I();
             canDrawArgs.fShape = &shape;
-            canDrawArgs.fAAType = aaType;
+            canDrawArgs.fAATypeFlags = pathAATypeFlags;
             canDrawArgs.fHasUserStencilSettings = false;
             canDrawArgs.fTargetIsWrappedVkSecondaryCB = renderTargetContext->wrapsVkSecondaryCB();
 
@@ -890,9 +891,9 @@
                      0xffff>()
             );
             if (Element::DeviceSpaceType::kRect == element->getDeviceSpaceType()) {
-                renderTargetContext->priv().stencilRect(stencilClip.fixedClip(), &kDrawToStencil,
-                                                        aaType, SkMatrix::I(),
-                                                        element->getDeviceSpaceRect());
+                renderTargetContext->priv().stencilRect(
+                        stencilClip.fixedClip(), &kDrawToStencil, GrAA(doStencilMSAA),
+                        SkMatrix::I(), element->getDeviceSpaceRect());
             } else {
                 if (!clipPath.isEmpty()) {
                     GrShape shape(clipPath, GrStyle::SimpleFill());
@@ -908,7 +909,7 @@
                                                           &stencilClip.fixedClip().scissorRect(),
                                                           &SkMatrix::I(),
                                                           &shape,
-                                                          aaType,
+                                                          pathAATypeFlags,
                                                           false};
                         pr->drawPath(args);
                     } else {
@@ -918,7 +919,7 @@
                         args.fClip = &stencilClip.fixedClip();
                         args.fClipConservativeBounds = &stencilClip.fixedClip().scissorRect();
                         args.fViewMatrix = &SkMatrix::I();
-                        args.fAAType = aaType;
+                        args.fDoStencilMSAA = GrAA(doStencilMSAA);
                         args.fShape = &shape;
                         pr->stencilPath(args);
                     }
@@ -931,9 +932,9 @@
         for (GrUserStencilSettings const* const* pass = stencilPasses; *pass; ++pass) {
             if (drawDirectToClip) {
                 if (Element::DeviceSpaceType::kRect == element->getDeviceSpaceType()) {
-                    renderTargetContext->priv().stencilRect(stencilClip, *pass, aaType,
-                                                            SkMatrix::I(),
-                                                            element->getDeviceSpaceRect());
+                    renderTargetContext->priv().stencilRect(
+                            stencilClip, *pass, GrAA(doStencilMSAA), SkMatrix::I(),
+                            element->getDeviceSpaceRect());
                 } else {
                     GrShape shape(clipPath, GrStyle::SimpleFill());
                     GrPaint paint;
@@ -946,15 +947,16 @@
                                                       &stencilClip.fixedClip().scissorRect(),
                                                       &SkMatrix::I(),
                                                       &shape,
-                                                      aaType,
+                                                      pathAATypeFlags,
                                                       false};
                     pr->drawPath(args);
                 }
             } else {
                 // The view matrix is setup to do clip space -> stencil space translation, so
                 // draw rect in clip space.
-                renderTargetContext->priv().stencilRect(stencilClip, *pass, aaType, SkMatrix::I(),
-                                                        SkRect::Make(fScissor));
+                renderTargetContext->priv().stencilRect(
+                        stencilClip, *pass, GrAA(doStencilMSAA), SkMatrix::I(),
+                        SkRect::Make(fScissor));
             }
         }
     }
diff --git a/src/gpu/GrReducedClip.h b/src/gpu/GrReducedClip.h
index fcdc66c8..bac08a3 100644
--- a/src/gpu/GrReducedClip.h
+++ b/src/gpu/GrReducedClip.h
@@ -13,8 +13,8 @@
 #include "SkClipStack.h"
 #include "SkTLList.h"
 
-class GrContext;
 class GrCoverageCountingPathRenderer;
+class GrRecordingContext;
 class GrRenderTargetContext;
 
 /**
@@ -83,7 +83,7 @@
     bool maskRequiresAA() const { SkASSERT(!fMaskElements.isEmpty()); return fMaskRequiresAA; }
 
     bool drawAlphaClipMask(GrRenderTargetContext*) const;
-    bool drawStencilClipMask(GrContext*, GrRenderTargetContext*) const;
+    bool drawStencilClipMask(GrRecordingContext*, GrRenderTargetContext*) const;
 
     int numAnalyticFPs() const { return fAnalyticFPs.count() + fCCPRClipPaths.count(); }
 
diff --git a/src/gpu/GrRenderTarget.cpp b/src/gpu/GrRenderTarget.cpp
index e654648..f265fba 100644
--- a/src/gpu/GrRenderTarget.cpp
+++ b/src/gpu/GrRenderTarget.cpp
@@ -14,6 +14,7 @@
 #include "GrGpu.h"
 #include "GrRenderTargetOpList.h"
 #include "GrRenderTargetPriv.h"
+#include "GrSamplePatternDictionary.h"
 #include "GrStencilAttachment.h"
 #include "GrStencilSettings.h"
 #include "SkRectPriv.h"
@@ -22,6 +23,7 @@
                                GrStencilAttachment* stencil)
         : INHERITED(gpu, desc)
         , fSampleCnt(desc.fSampleCnt)
+        , fSamplePatternKey(GrSamplePatternDictionary::kInvalidSamplePatternKey)
         , fStencilAttachment(stencil) {
     SkASSERT(desc.fFlags & kRenderTarget_GrSurfaceFlag);
     SkASSERT(!this->hasMixedSamples() || fSampleCnt > 1);
@@ -88,3 +90,18 @@
     SkASSERT(this->getStencilAttachment());
     return this->getStencilAttachment()->bits();
 }
+
+int GrRenderTargetPriv::getSamplePatternKey(const GrPipeline& pipeline) const {
+    SkASSERT(fRenderTarget->fSampleCnt > 1);
+    if (GrSamplePatternDictionary::kInvalidSamplePatternKey == fRenderTarget->fSamplePatternKey) {
+        fRenderTarget->fSamplePatternKey =
+                fRenderTarget->getGpu()->findOrAssignSamplePatternKey(fRenderTarget, pipeline);
+    }
+    SkASSERT(GrSamplePatternDictionary::kInvalidSamplePatternKey
+                     != fRenderTarget->fSamplePatternKey);
+    // Verify we always have the same sample pattern key every time this is called, regardless of
+    // pipeline state.
+    SkASSERT(fRenderTarget->getGpu()->findOrAssignSamplePatternKey(fRenderTarget, pipeline)
+                     == fRenderTarget->fSamplePatternKey);
+    return fRenderTarget->fSamplePatternKey;
+}
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index 2e20901..05241d6 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -11,14 +11,18 @@
 #include "GrAppliedClip.h"
 #include "GrBackendSemaphore.h"
 #include "GrBlurUtils.h"
+#include "GrCaps.h"
 #include "GrColor.h"
 #include "GrContextPriv.h"
 #include "GrDrawingManager.h"
 #include "GrFixedClip.h"
 #include "GrGpuResourcePriv.h"
+#include "GrMemoryPool.h"
 #include "GrOpList.h"
 #include "GrPathRenderer.h"
 #include "GrQuad.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTarget.h"
 #include "GrRenderTargetContextPriv.h"
 #include "GrResourceProvider.h"
@@ -36,6 +40,7 @@
 #include "SkShadowUtils.h"
 #include "SkSurfacePriv.h"
 #include "effects/GrRRectEffect.h"
+#include "effects/GrTextureDomain.h"
 #include "ops/GrAtlasTextOp.h"
 #include "ops/GrClearOp.h"
 #include "ops/GrClearStencilClipOp.h"
@@ -45,7 +50,7 @@
 #include "ops/GrDrawOp.h"
 #include "ops/GrDrawVerticesOp.h"
 #include "ops/GrFillRectOp.h"
-#include "ops/GrAAFillRRectOp.h"
+#include "ops/GrFillRRectOp.h"
 #include "ops/GrLatticeOp.h"
 #include "ops/GrOp.h"
 #include "ops/GrOvalOpFactory.h"
@@ -62,7 +67,7 @@
 public:
     TextTarget(GrRenderTargetContext* renderTargetContext)
             : GrTextTarget(renderTargetContext->width(), renderTargetContext->height(),
-                     renderTargetContext->colorSpaceInfo())
+                           renderTargetContext->colorSpaceInfo())
             , fRenderTargetContext(renderTargetContext)
             , fGlyphPainter{*renderTargetContext}{}
 
@@ -78,7 +83,7 @@
 
     void makeGrPaint(GrMaskFormat maskFormat, const SkPaint& skPaint, const SkMatrix& viewMatrix,
                      GrPaint* grPaint) override {
-        GrContext* context = fRenderTargetContext->fContext;
+        auto context = fRenderTargetContext->fContext;
         const GrColorSpaceInfo& colorSpaceInfo = fRenderTargetContext->colorSpaceInfo();
         if (kARGB_GrMaskFormat == maskFormat) {
             SkPaintToGrPaintWithPrimitiveColor(context, colorSpaceInfo, skPaint, grPaint);
@@ -87,7 +92,7 @@
         }
     }
 
-    GrContext* getContext() override {
+    GrRecordingContext* getContext() override {
         return fRenderTargetContext->fContext;
     }
 
@@ -106,36 +111,11 @@
     SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(this->singleOwner());)
 #define ASSERT_SINGLE_OWNER_PRIV \
     SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(fRenderTargetContext->singleOwner());)
-#define RETURN_IF_ABANDONED        if (this->drawingManager()->wasAbandoned()) { return; }
-#define RETURN_IF_ABANDONED_PRIV   if (fRenderTargetContext->drawingManager()->wasAbandoned()) { return; }
-#define RETURN_FALSE_IF_ABANDONED  if (this->drawingManager()->wasAbandoned()) { return false; }
-#define RETURN_FALSE_IF_ABANDONED_PRIV  if (fRenderTargetContext->drawingManager()->wasAbandoned()) { return false; }
-#define RETURN_NULL_IF_ABANDONED   if (this->drawingManager()->wasAbandoned()) { return nullptr; }
-
-//////////////////////////////////////////////////////////////////////////////
-
-GrAAType GrChooseAAType(GrAA aa, GrFSAAType fsaaType, GrAllowMixedSamples allowMixedSamples,
-                        const GrCaps& caps) {
-    if (GrAA::kNo == aa) {
-        // On some devices we cannot disable MSAA if it is enabled so we make the AA type reflect
-        // that.
-        if (fsaaType == GrFSAAType::kUnifiedMSAA && !caps.multisampleDisableSupport()) {
-            return GrAAType::kMSAA;
-        }
-        return GrAAType::kNone;
-    }
-    switch (fsaaType) {
-        case GrFSAAType::kNone:
-            return GrAAType::kCoverage;
-        case GrFSAAType::kUnifiedMSAA:
-            return GrAAType::kMSAA;
-        case GrFSAAType::kMixedSamples:
-            return GrAllowMixedSamples::kYes == allowMixedSamples ? GrAAType::kMixedSamples
-                                                                  : GrAAType::kCoverage;
-    }
-    SK_ABORT("Unexpected fsaa type");
-    return GrAAType::kNone;
-}
+#define RETURN_IF_ABANDONED        if (fContext->priv().abandoned()) { return; }
+#define RETURN_IF_ABANDONED_PRIV   if (fRenderTargetContext->fContext->priv().abandoned()) { return; }
+#define RETURN_FALSE_IF_ABANDONED  if (fContext->priv().abandoned()) { return false; }
+#define RETURN_FALSE_IF_ABANDONED_PRIV  if (fRenderTargetContext->fContext->priv().abandoned()) { return false; }
+#define RETURN_NULL_IF_ABANDONED   if (fContext->priv().abandoned()) { return nullptr; }
 
 //////////////////////////////////////////////////////////////////////////////
 
@@ -150,30 +130,21 @@
     GrDrawingManager* fDrawingManager;
 };
 
-bool GrRenderTargetContext::wasAbandoned() const {
-    return this->drawingManager()->wasAbandoned();
-}
-
 // In MDB mode the reffing of the 'getLastOpList' call's result allows in-progress
 // GrOpLists to be picked up and added to by renderTargetContexts lower in the call
 // stack. When this occurs with a closed GrOpList, a new one will be allocated
 // when the renderTargetContext attempts to use it (via getOpList).
-GrRenderTargetContext::GrRenderTargetContext(GrContext* context,
-                                             GrDrawingManager* drawingMgr,
+GrRenderTargetContext::GrRenderTargetContext(GrRecordingContext* context,
                                              sk_sp<GrRenderTargetProxy> rtp,
                                              sk_sp<SkColorSpace> colorSpace,
                                              const SkSurfaceProps* surfaceProps,
-                                             GrAuditTrail* auditTrail,
-                                             GrSingleOwner* singleOwner,
                                              bool managedOpList)
-        : GrSurfaceContext(context, drawingMgr, rtp->config(), std::move(colorSpace), auditTrail,
-                           singleOwner)
+        : GrSurfaceContext(context, rtp->config(), std::move(colorSpace))
         , fRenderTargetProxy(std::move(rtp))
         , fOpList(sk_ref_sp(fRenderTargetProxy->getLastRenderTargetOpList()))
         , fSurfaceProps(SkSurfacePropsCopyOrDefault(surfaceProps))
         , fManagedOpList(managedOpList) {
-    GrResourceProvider* resourceProvider = context->priv().resourceProvider();
-    if (resourceProvider && !resourceProvider->explicitlyAllocateGPUResources()) {
+    if (!context->priv().explicitlyAllocateGPUResources()) {
         // MDB TODO: to ensure all resources still get allocated in the correct order in the hybrid
         // world we need to get the correct opList here so that it, in turn, can grab and hold
         // its rendertarget.
@@ -199,6 +170,50 @@
     ASSERT_SINGLE_OWNER
 }
 
+inline GrAAType GrRenderTargetContext::chooseAAType(GrAA aa) {
+    auto fsaaType = this->fsaaType();
+    if (GrAA::kNo == aa) {
+        // On some devices we cannot disable MSAA if it is enabled so we make the AA type reflect
+        // that.
+        if (fsaaType == GrFSAAType::kUnifiedMSAA && !this->caps()->multisampleDisableSupport()) {
+            return GrAAType::kMSAA;
+        }
+        return GrAAType::kNone;
+    }
+    switch (fsaaType) {
+        case GrFSAAType::kNone:
+        case GrFSAAType::kMixedSamples:
+            return GrAAType::kCoverage;
+        case GrFSAAType::kUnifiedMSAA:
+            return GrAAType::kMSAA;
+    }
+    SK_ABORT("Unexpected fsaa type");
+    return GrAAType::kNone;
+}
+
+static inline GrPathRenderer::AATypeFlags choose_path_aa_type_flags(
+        GrAA aa, GrFSAAType fsaaType, const GrCaps& caps) {
+    using AATypeFlags = GrPathRenderer::AATypeFlags;
+    if (GrAA::kNo == aa) {
+        // On some devices we cannot disable MSAA if it is enabled so we make the AA type flags
+        // reflect that.
+        if (fsaaType == GrFSAAType::kUnifiedMSAA && !caps.multisampleDisableSupport()) {
+            return AATypeFlags::kMSAA;
+        }
+        return AATypeFlags::kNone;
+    }
+    switch (fsaaType) {
+        case GrFSAAType::kNone:
+            return AATypeFlags::kCoverage;
+        case GrFSAAType::kMixedSamples:
+            return AATypeFlags::kCoverage | AATypeFlags::kMixedSampledStencilThenCover;
+        case GrFSAAType::kUnifiedMSAA:
+            return AATypeFlags::kMSAA;
+    }
+    SK_ABORT("Invalid GrFSAAType.");
+    return AATypeFlags::kNone;
+}
+
 GrTextureProxy* GrRenderTargetContext::asTextureProxy() {
     return fRenderTargetProxy->asTextureProxy();
 }
@@ -307,9 +322,12 @@
                                           CanClearFullscreen canClearFullscreen) {
     bool isFull = false;
     if (!clip.hasWindowRectangles()) {
+        // TODO: wrt the shouldInitializeTextures path, it would be more performant to
+        // only clear the entire target if we knew it had not been cleared before. As
+        // is this could end up doing a lot of redundant clears.
         isFull = !clip.scissorEnabled() ||
                  (CanClearFullscreen::kYes == canClearFullscreen &&
-                  this->caps()->preferFullscreenClears()) ||
+                  (this->caps()->preferFullscreenClears() || this->caps()->shouldInitializeTextures())) ||
                  clip.scissorRect().contains(SkIRect::MakeWH(this->width(), this->height()));
     }
 
@@ -600,7 +618,7 @@
         }
         // The clip region in the rect's local space, so the test becomes the local rect containing
         // the quad's points.
-        GrQuad quad(rtRect, invM);
+        GrQuad quad = GrQuad::MakeFromRect(rtRect, invM);
         if (!rect_contains_inclusive(rect, quad.point(0)) ||
             !rect_contains_inclusive(rect, quad.point(1)) ||
             !rect_contains_inclusive(rect, quad.point(2)) ||
@@ -640,7 +658,7 @@
     if (clipAA == GrAA::kYes) {
         aa = GrAA::kYes;
     }
-    GrAAType aaType = this->chooseAAType(aa, GrAllowMixedSamples::kNo);
+    GrAAType aaType = this->chooseAAType(aa);
     this->addDrawOp(GrFixedClip::Disabled(),
                     GrFillRectOp::Make(fContext, std::move(paint), aaType, SkMatrix::I(),
                                        combinedRect));
@@ -668,7 +686,7 @@
         return;
     }
 
-    GrAAType aaType = this->chooseAAType(aa, GrAllowMixedSamples::kNo);
+    GrAAType aaType = this->chooseAAType(aa);
     this->addDrawOp(clip, GrFillRectOp::Make(fContext, std::move(paint), aaType, viewMatrix,
                                              croppedRect, ss));
 }
@@ -733,7 +751,7 @@
 
         std::unique_ptr<GrDrawOp> op;
 
-        GrAAType aaType = this->chooseAAType(aa, GrAllowMixedSamples::kNo);
+        GrAAType aaType = this->chooseAAType(aa);
         op = GrStrokeRectOp::Make(fContext, std::move(paint), aaType, viewMatrix, rect, stroke);
         // op may be null if the stroke is not supported or if using coverage aa and the view matrix
         // does not preserve rectangles.
@@ -749,7 +767,7 @@
 void GrRenderTargetContext::drawQuadSet(const GrClip& clip, GrPaint&& paint, GrAA aa,
                                         const SkMatrix& viewMatrix, const QuadSetEntry quads[],
                                         int cnt) {
-    GrAAType aaType = this->chooseAAType(aa, GrAllowMixedSamples::kNo);
+    GrAAType aaType = this->chooseAAType(aa);
     this->addDrawOp(clip, GrFillRectOp::MakeSet(fContext, std::move(paint), aaType, viewMatrix,
                                                 quads, cnt));
 }
@@ -797,7 +815,7 @@
 }
 
 void GrRenderTargetContextPriv::stencilPath(const GrHardClip& clip,
-                                            GrAAType aaType,
+                                            GrAA doStencilMSAA,
                                             const SkMatrix& viewMatrix,
                                             const GrPath* path) {
     ASSERT_SINGLE_OWNER_PRIV
@@ -806,9 +824,6 @@
     GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContextPriv", "stencilPath",
                                    fRenderTargetContext->fContext);
 
-    SkASSERT(aaType != GrAAType::kCoverage);
-
-    bool useHWAA = GrAATypeIsHW(aaType);
     // TODO: extract portions of checkDraw that are relevant to path stenciling.
     SkASSERT(path);
     SkASSERT(fRenderTargetContext->caps()->shaderCaps()->pathRenderingSupport());
@@ -828,7 +843,7 @@
 
     std::unique_ptr<GrOp> op = GrStencilPathOp::Make(fRenderTargetContext->fContext,
                                                      viewMatrix,
-                                                     useHWAA,
+                                                     GrAA::kYes == doStencilMSAA,
                                                      path->getFillType(),
                                                      appliedClip.hasStencilClip(),
                                                      appliedClip.scissorState(),
@@ -842,7 +857,7 @@
 
 void GrRenderTargetContextPriv::stencilRect(const GrHardClip& clip,
                                             const GrUserStencilSettings* ss,
-                                            GrAAType aaType,
+                                            GrAA doStencilMSAA,
                                             const SkMatrix& viewMatrix,
                                             const SkRect& rect) {
     ASSERT_SINGLE_OWNER_PRIV
@@ -851,11 +866,11 @@
     GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContextPriv", "stencilRect",
                                    fRenderTargetContext->fContext);
 
-    SkASSERT(GrAAType::kCoverage != aaType);
     AutoCheckFlush acf(fRenderTargetContext->drawingManager());
 
     GrPaint paint;
     paint.setXPFactory(GrDisableColorXPFactory::Get());
+    auto aaType = (GrAA::kYes == doStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone;
     std::unique_ptr<GrDrawOp> op = GrFillRectOp::Make(
             fRenderTargetContext->fContext, std::move(paint), aaType, viewMatrix,  rect, ss);
     fRenderTargetContext->addDrawOp(clip, std::move(op));
@@ -884,66 +899,98 @@
     return true;
 }
 
-void GrRenderTargetContext::fillRectToRect(const GrClip& clip,
-                                           GrPaint&& paint,
-                                           GrAA aa,
-                                           const SkMatrix& viewMatrix,
-                                           const SkRect& rectToDraw,
-                                           const SkRect& localRect) {
-    ASSERT_SINGLE_OWNER
-    RETURN_IF_ABANDONED
-    SkDEBUGCODE(this->validate();)
-            GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "fillRectToRect", fContext);
-
-    SkRect croppedRect = rectToDraw;
-    SkRect croppedLocalRect = localRect;
-    if (!crop_filled_rect(this->width(), this->height(), clip, viewMatrix,
-                          &croppedRect, &croppedLocalRect)) {
-        return;
-    }
-
-    AutoCheckFlush acf(this->drawingManager());
-
-    GrAAType aaType = this->chooseAAType(aa, GrAllowMixedSamples::kNo);
-    this->addDrawOp(clip, GrFillRectOp::MakeWithLocalRect(fContext, std::move(paint), aaType,
-            viewMatrix, croppedRect, croppedLocalRect));
-}
-
-void GrRenderTargetContext::fillRectWithEdgeAA(const GrClip& clip, GrPaint&& paint,
+void GrRenderTargetContext::fillRectWithEdgeAA(const GrClip& clip, GrPaint&& paint, GrAA aa,
                                                GrQuadAAFlags edgeAA, const SkMatrix& viewMatrix,
-                                               const SkRect& rect) {
+                                               const SkRect& rect, const SkRect* localRect) {
     ASSERT_SINGLE_OWNER
     RETURN_IF_ABANDONED
     SkDEBUGCODE(this->validate();)
     GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "fillRectWithEdgeAA", fContext);
 
-    // If aaType turns into MSAA, make sure to keep quads with no AA edges as MSAA. Sending those
-    // to drawFilledRect() would have it turn off MSAA in that case, which breaks seaming with
-    // any partial AA edges that kept MSAA.
-    GrAAType aaType = this->chooseAAType(GrAA::kYes, GrAllowMixedSamples::kNo);
-    if (aaType != GrAAType::kMSAA &&
-        (edgeAA == GrQuadAAFlags::kNone || edgeAA == GrQuadAAFlags::kAll)) {
-        // This is equivalent to a regular filled rect draw, so route through there to take
-        // advantage of draw->clear optimizations
-        this->drawFilledRect(clip, std::move(paint), GrAA(edgeAA == GrQuadAAFlags::kAll),
-                             viewMatrix, rect);
-        return;
-    }
+    GrAAType aaType = this->chooseAAType(aa);
+    std::unique_ptr<GrDrawOp> op;
 
-    SkRect croppedRect = rect;
-    if (!crop_filled_rect(this->width(), this->height(), clip, viewMatrix, &croppedRect)) {
-        return;
+    if (localRect) {
+        // If local coordinates are provided, skip the optimization check to go through
+        // drawFilledRect, and also calculate clipped local coordinates
+        SkRect croppedRect = rect;
+        SkRect croppedLocalRect = *localRect;
+        if (!crop_filled_rect(this->width(), this->height(), clip, viewMatrix, &croppedRect,
+                              &croppedLocalRect)) {
+            return;
+        }
+        op = GrFillRectOp::MakePerEdgeWithLocalRect(fContext, std::move(paint), aaType, edgeAA,
+                                                    viewMatrix, croppedRect, croppedLocalRect);
+    } else {
+        // If aaType turns into MSAA, make sure to keep quads with no AA edges as MSAA. Sending
+        // those to drawFilledRect() would have it turn off MSAA in that case, which breaks seaming
+        // with any partial AA edges that kept MSAA.
+        if (aaType != GrAAType::kMSAA &&
+            (edgeAA == GrQuadAAFlags::kNone || edgeAA == GrQuadAAFlags::kAll)) {
+            // This is equivalent to a regular filled rect draw, so route through there to take
+            // advantage of draw->clear optimizations
+            this->drawFilledRect(clip, std::move(paint), GrAA(edgeAA == GrQuadAAFlags::kAll),
+                                 viewMatrix, rect);
+            return;
+        }
+
+        SkRect croppedRect = rect;
+        if (!crop_filled_rect(this->width(), this->height(), clip, viewMatrix, &croppedRect)) {
+            return;
+        }
+        op = GrFillRectOp::MakePerEdge(fContext, std::move(paint), aaType, edgeAA, viewMatrix,
+                                       croppedRect);
     }
 
     AutoCheckFlush acf(this->drawingManager());
-    this->addDrawOp(clip, GrFillRectOp::MakePerEdge(fContext, std::move(paint), aaType, edgeAA,
-                                                    viewMatrix, croppedRect));
+    this->addDrawOp(clip, std::move(op));
+}
+
+void GrRenderTargetContext::fillQuadWithEdgeAA(const GrClip& clip, GrPaint&& paint, GrAA aa,
+                                               GrQuadAAFlags edgeAA, const SkMatrix& viewMatrix,
+                                               const SkPoint quad[4], const SkPoint localQuad[4]) {
+    ASSERT_SINGLE_OWNER
+    RETURN_IF_ABANDONED
+    SkDEBUGCODE(this->validate();)
+    GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "fillQuadWithEdgeAA", fContext);
+
+    GrAAType aaType = this->chooseAAType(aa);
+
+    AutoCheckFlush acf(this->drawingManager());
+    // MakePerEdgeQuad automatically does the right thing if localQuad is null or not
+    this->addDrawOp(clip, GrFillRectOp::MakePerEdgeQuad(fContext, std::move(paint), aaType, edgeAA,
+                                                        viewMatrix, quad, localQuad));
+}
+
+// Creates a paint for GrFillRectOp that matches behavior of GrTextureOp
+static void draw_texture_to_grpaint(sk_sp<GrTextureProxy> proxy, const SkRect* domain,
+                                    GrSamplerState::Filter filter, SkBlendMode mode,
+                                    const SkPMColor4f& color, sk_sp<GrColorSpaceXform> csXform,
+                                    GrPaint* paint) {
+    paint->setColor4f(color);
+    paint->setXPFactory(SkBlendMode_AsXPFactory(mode));
+
+    std::unique_ptr<GrFragmentProcessor> fp;
+    if (domain) {
+        SkRect correctedDomain = *domain;
+        if (filter == GrSamplerState::Filter::kBilerp) {
+            // Inset by 1/2 pixel, which GrTextureOp and GrTextureAdjuster handle automatically
+            correctedDomain.inset(0.5f, 0.5f);
+        }
+        fp = GrTextureDomainEffect::Make(std::move(proxy), SkMatrix::I(), correctedDomain,
+                                         GrTextureDomain::kClamp_Mode, filter);
+    } else {
+        fp = GrSimpleTextureEffect::Make(std::move(proxy), SkMatrix::I(), filter);
+    }
+
+    fp = GrColorSpaceXformEffect::Make(std::move(fp), csXform);
+    paint->addColorFragmentProcessor(std::move(fp));
 }
 
 void GrRenderTargetContext::drawTexture(const GrClip& clip, sk_sp<GrTextureProxy> proxy,
-                                        GrSamplerState::Filter filter, const SkPMColor4f& color,
-                                        const SkRect& srcRect, const SkRect& dstRect,
-                                        GrQuadAAFlags aaFlags,
+                                        GrSamplerState::Filter filter, SkBlendMode mode,
+                                        const SkPMColor4f& color, const SkRect& srcRect,
+                                        const SkRect& dstRect, GrAA aa, GrQuadAAFlags aaFlags,
                                         SkCanvas::SrcRectConstraint constraint,
                                         const SkMatrix& viewMatrix,
                                         sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
@@ -955,22 +1002,82 @@
         srcRect.contains(proxy->getWorstCaseBoundsRect())) {
         constraint = SkCanvas::kFast_SrcRectConstraint;
     }
-    GrAAType aaType =
-            this->chooseAAType(GrAA(aaFlags != GrQuadAAFlags::kNone), GrAllowMixedSamples::kNo);
+
+    GrAAType aaType = this->chooseAAType(aa);
     SkRect clippedDstRect = dstRect;
     SkRect clippedSrcRect = srcRect;
     if (!crop_filled_rect(this->width(), this->height(), clip, viewMatrix, &clippedDstRect,
                           &clippedSrcRect)) {
         return;
     }
-    auto op = GrTextureOp::Make(fContext, std::move(proxy), filter, color, clippedSrcRect,
-                                clippedDstRect, aaType, aaFlags, constraint, viewMatrix,
-                                std::move(textureColorSpaceXform));
+
+    AutoCheckFlush acf(this->drawingManager());
+
+    std::unique_ptr<GrDrawOp> op;
+    if (mode != SkBlendMode::kSrcOver) {
+        // Emulation mode with GrPaint and GrFillRectOp
+        if (filter != GrSamplerState::Filter::kNearest &&
+            !GrTextureOp::GetFilterHasEffect(viewMatrix, clippedSrcRect, clippedDstRect)) {
+            filter = GrSamplerState::Filter::kNearest;
+        }
+
+        GrPaint paint;
+        draw_texture_to_grpaint(std::move(proxy),
+                constraint == SkCanvas::kStrict_SrcRectConstraint ? &srcRect : nullptr,
+                filter, mode, color, std::move(textureColorSpaceXform), &paint);
+        op = GrFillRectOp::MakePerEdgeWithLocalRect(fContext, std::move(paint), aaType, aaFlags,
+                                                    viewMatrix, clippedDstRect, clippedSrcRect);
+    } else {
+        // Can use a lighter weight op that can chain across proxies
+        op = GrTextureOp::Make(fContext, std::move(proxy), filter, color, clippedSrcRect,
+                               clippedDstRect, aaType, aaFlags, constraint, viewMatrix,
+                               std::move(textureColorSpaceXform));
+    }
+
+    this->addDrawOp(clip, std::move(op));
+}
+
+void GrRenderTargetContext::drawTextureQuad(const GrClip& clip, sk_sp<GrTextureProxy> proxy,
+                                            GrSamplerState::Filter filter, SkBlendMode mode,
+                                            const SkPMColor4f& color, const SkPoint srcQuad[4],
+                                            const SkPoint dstQuad[4], GrAA aa,
+                                            GrQuadAAFlags aaFlags, const SkRect* domain,
+                                            const SkMatrix& viewMatrix,
+                                            sk_sp<GrColorSpaceXform> texXform) {
+    ASSERT_SINGLE_OWNER
+    RETURN_IF_ABANDONED
+    SkDEBUGCODE(this->validate();)
+    GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawTextureQuad", fContext);
+    if (domain && domain->contains(proxy->getWorstCaseBoundsRect())) {
+        domain = nullptr;
+    }
+
+    GrAAType aaType = this->chooseAAType(aa);
+
+    // Unlike drawTexture(), don't bother cropping or optimizing the filter type since we're
+    // sampling an arbitrary quad of the texture.
+    AutoCheckFlush acf(this->drawingManager());
+    std::unique_ptr<GrDrawOp> op;
+    if (mode != SkBlendMode::kSrcOver) {
+        // Emulation mode, but don't bother converting to kNearest filter since it's an arbitrary
+        // quad that is being drawn, which makes the tests too expensive here
+        GrPaint paint;
+        draw_texture_to_grpaint(
+                std::move(proxy), domain, filter, mode, color, std::move(texXform), &paint);
+        op = GrFillRectOp::MakePerEdgeQuad(fContext, std::move(paint), aaType, aaFlags, viewMatrix,
+                                           dstQuad, srcQuad);
+    } else {
+        // Use lighter weight GrTextureOp
+        op = GrTextureOp::MakeQuad(fContext, std::move(proxy), filter, color, srcQuad, dstQuad,
+                                   aaType, aaFlags, domain, viewMatrix, std::move(texXform));
+    }
+
     this->addDrawOp(clip, std::move(op));
 }
 
 void GrRenderTargetContext::drawTextureSet(const GrClip& clip, const TextureSetEntry set[], int cnt,
                                            GrSamplerState::Filter filter, SkBlendMode mode,
+                                           GrAA aa, SkCanvas::SrcRectConstraint constraint,
                                            const SkMatrix& viewMatrix,
                                            sk_sp<GrColorSpaceXform> texXform) {
     ASSERT_SINGLE_OWNER
@@ -978,37 +1085,40 @@
     SkDEBUGCODE(this->validate();)
     GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawTextureSet", fContext);
 
-    AutoCheckFlush acf(this->drawingManager());
-
-    GrAAType aaType = this->chooseAAType(GrAA::kYes, GrAllowMixedSamples::kNo);
     if (mode != SkBlendMode::kSrcOver ||
         !fContext->priv().caps()->dynamicStateArrayGeometryProcessorTextureSupport()) {
         // Draw one at a time with GrFillRectOp and a GrPaint that emulates what GrTextureOp does
-        const GrXPFactory* xpFactory = SkBlendMode_AsXPFactory(mode);
+        SkMatrix ctm;
         for (int i = 0; i < cnt; ++i) {
-            GrPaint paint;
-            paint.setColor4f({set[i].fAlpha, set[i].fAlpha, set[i].fAlpha, set[i].fAlpha});
-            paint.setXPFactory(xpFactory);
-
-            // See if we can disable bilerp filtering when the src and dst rects line up
-            if (filter != GrSamplerState::Filter::kNearest &&
-                !GrTextureOp::GetFilterHasEffect(viewMatrix, set[i].fSrcRect, set[i].fDstRect)) {
-                filter = GrSamplerState::Filter::kNearest;
+            float alpha = set[i].fAlpha;
+            ctm = viewMatrix;
+            if (set[i].fPreViewMatrix) {
+                ctm.preConcat(*set[i].fPreViewMatrix);
             }
 
-            auto fp = GrSimpleTextureEffect::Make(set[i].fProxy, SkMatrix::I(), filter);
-            fp = GrColorSpaceXformEffect::Make(std::move(fp), texXform);
-            paint.addColorFragmentProcessor(std::move(fp));
-
-            auto op = GrFillRectOp::MakePerEdgeWithLocalRect(fContext, std::move(paint), aaType,
-                                                             set[i].fAAFlags, viewMatrix,
-                                                             set[i].fDstRect, set[i].fSrcRect);
-            this->addDrawOp(clip, std::move(op));
+            if (set[i].fDstClipQuad == nullptr) {
+                // Stick with original rectangles, which allows the ops to know more about what's
+                // being drawn.
+                this->drawTexture(clip, set[i].fProxy, filter, mode, {alpha, alpha, alpha, alpha},
+                                  set[i].fSrcRect, set[i].fDstRect, aa, set[i].fAAFlags,
+                                  constraint, ctm, texXform);
+            } else {
+                // Generate interpolated texture coordinates to match the dst clip
+                SkPoint srcQuad[4];
+                GrMapRectPoints(set[i].fDstRect, set[i].fSrcRect, set[i].fDstClipQuad, srcQuad, 4);
+                const SkRect* domain = constraint == SkCanvas::kStrict_SrcRectConstraint
+                        ? &set[i].fSrcRect : nullptr;
+                this->drawTextureQuad(clip, set[i].fProxy, filter, mode,
+                                      {alpha, alpha, alpha, alpha}, srcQuad, set[i].fDstClipQuad,
+                                      aa, set[i].fAAFlags, domain, ctm, texXform);
+            }
         }
     } else {
         // Can use a single op, avoiding GrPaint creation, and can batch across proxies
-        auto op = GrTextureOp::Make(fContext, set, cnt, filter, aaType, viewMatrix,
-                                    std::move(texXform));
+        AutoCheckFlush acf(this->drawingManager());
+        GrAAType aaType = this->chooseAAType(aa);
+        auto op = GrTextureOp::MakeSet(fContext, set, cnt, filter, aaType, constraint, viewMatrix,
+                                       std::move(texXform));
         this->addDrawOp(clip, std::move(op));
     }
 }
@@ -1031,7 +1141,7 @@
 
     AutoCheckFlush acf(this->drawingManager());
 
-    GrAAType aaType = this->chooseAAType(aa, GrAllowMixedSamples::kNo);
+    GrAAType aaType = this->chooseAAType(aa);
     this->addDrawOp(clip, GrFillRectOp::MakeWithLocalMatrix(fContext, std::move(paint), aaType,
                                                             viewMatrix, localMatrix, croppedRect));
 }
@@ -1051,7 +1161,7 @@
     AutoCheckFlush acf(this->drawingManager());
 
     SkASSERT(vertices);
-    GrAAType aaType = this->chooseAAType(GrAA::kNo, GrAllowMixedSamples::kNo);
+    GrAAType aaType = this->chooseAAType(GrAA::kNo);
     std::unique_ptr<GrDrawOp> op = GrDrawVerticesOp::Make(
             fContext, std::move(paint), std::move(vertices), bones, boneCount, viewMatrix, aaType,
             this->colorSpaceInfo().refColorSpaceXformFromSRGB(), overridePrimType);
@@ -1074,7 +1184,7 @@
 
     AutoCheckFlush acf(this->drawingManager());
 
-    GrAAType aaType = this->chooseAAType(GrAA::kNo, GrAllowMixedSamples::kNo);
+    GrAAType aaType = this->chooseAAType(GrAA::kNo);
     std::unique_ptr<GrDrawOp> op = GrDrawAtlasOp::Make(fContext, std::move(paint), viewMatrix,
                                                        aaType, spriteCount, xform, texRect, colors);
     this->addDrawOp(clip, std::move(op));
@@ -1105,9 +1215,11 @@
     // draw is aa. Since our lower level clip code works from op bounds, which are SkRects, it
     // doesn't detect that the clip can be ignored (modulo antialiasing). The following test
     // attempts to mitigate the stencil clip cost but will only help when the entire clip stack
-    // can be ignored. We'd prefer to fix this in the framework by removing the clips calls.
+    // can be ignored. We'd prefer to fix this in the framework by removing the clips calls. This
+    // only works for filled rrects since the stroke width outsets beyond the rrect itself.
     SkRRect devRRect;
-    if (rrect.transform(viewMatrix, &devRRect) && clip->quickContains(devRRect)) {
+    if (stroke.getStyle() == SkStrokeRec::kFill_Style && rrect.transform(viewMatrix, &devRRect) &&
+        clip->quickContains(devRRect)) {
         clip = &noclip;
     }
 #endif
@@ -1115,23 +1227,23 @@
 
     AutoCheckFlush acf(this->drawingManager());
 
-    GrAAType aaType = this->chooseAAType(aa, GrAllowMixedSamples::kNo);
-    if (GrAAType::kCoverage == aaType) {
-        std::unique_ptr<GrDrawOp> op;
-        if (style.isSimpleFill()) {
-            op = GrAAFillRRectOp::Make(fContext, viewMatrix, rrect, *this->caps(),
-                                       std::move(paint));
-        }
-        if (!op) {
-            assert_alive(paint);
-            op = GrOvalOpFactory::MakeRRectOp(fContext, std::move(paint), viewMatrix, rrect, stroke,
-                                              this->caps()->shaderCaps());
-        }
+    GrAAType aaType = this->chooseAAType(aa);
 
-        if (op) {
-            this->addDrawOp(*clip, std::move(op));
-            return;
-        }
+    std::unique_ptr<GrDrawOp> op;
+    if (style.isSimpleFill()) {
+        assert_alive(paint);
+        op = GrFillRRectOp::Make(
+                fContext, aaType, viewMatrix, rrect, *this->caps(), std::move(paint));
+    }
+    if (!op && GrAAType::kCoverage == aaType) {
+        assert_alive(paint);
+        op = GrOvalOpFactory::MakeRRectOp(
+                fContext, std::move(paint), viewMatrix, rrect, stroke, this->caps()->shaderCaps());
+
+    }
+    if (op) {
+        this->addDrawOp(*clip, std::move(op));
+        return;
     }
 
     assert_alive(paint);
@@ -1153,7 +1265,7 @@
                                            const SkPath& path,
                                            const SkDrawShadowRec& rec) {
     ASSERT_SINGLE_OWNER
-    if (this->drawingManager()->wasAbandoned()) {
+    if (fContext->priv().abandoned()) {
         return true;
     }
     SkDEBUGCODE(this->validate();)
@@ -1352,7 +1464,7 @@
 
     SkTCopyOnFirstWrite<SkRRect> inner(origInner), outer(origOuter);
 
-    GrAAType aaType = this->chooseAAType(aa, GrAllowMixedSamples::kNo);
+    GrAAType aaType = this->chooseAAType(aa);
 
     if (GrAAType::kMSAA == aaType) {
         return false;
@@ -1491,7 +1603,7 @@
         return this->drawPath(clip, std::move(paint), aa, viewMatrix, path, style);
     }
 
-    GrAAType aaType = this->chooseAAType(GrAA::kNo, GrAllowMixedSamples::kNo);
+    GrAAType aaType = this->chooseAAType(GrAA::kNo);
     std::unique_ptr<GrDrawOp> op = GrRegionOp::Make(fContext, std::move(paint), viewMatrix, region,
                                                     aaType, ss);
     this->addDrawOp(clip, std::move(op));
@@ -1521,30 +1633,32 @@
 
     AutoCheckFlush acf(this->drawingManager());
 
-    GrAAType aaType = this->chooseAAType(aa, GrAllowMixedSamples::kNo);
-    if (GrAAType::kCoverage == aaType) {
-        std::unique_ptr<GrDrawOp> op;
-        // GrAAFillRRectOp has special geometry and a fragment-shader branch to conditionally
-        // evaluate the arc equation. This same special geometry and fragment branch also turn out
-        // to be a substantial optimization for drawing ovals (namely, by not evaluating the arc
-        // equation inside the oval's inner diamond). Given these optimizations, it's a clear win to
-        // draw ovals the exact same way we do round rects.
+    GrAAType aaType = this->chooseAAType(aa);
+
+    std::unique_ptr<GrDrawOp> op;
+    if (style.isSimpleFill()) {
+        // GrFillRRectOp has special geometry and a fragment-shader branch to conditionally evaluate
+        // the arc equation. This same special geometry and fragment branch also turn out to be a
+        // substantial optimization for drawing ovals (namely, by not evaluating the arc equation
+        // inside the oval's inner diamond). Given these optimizations, it's a clear win to draw
+        // ovals the exact same way we do round rects.
         //
-        // However, we still don't draw true circles as round rects, because it can cause perf
-        // regressions on some platforms as compared to the dedicated circle Op.
-        if (style.isSimpleFill() && oval.height() != oval.width()) {
-            op = GrAAFillRRectOp::Make(fContext, viewMatrix, SkRRect::MakeOval(oval), *this->caps(),
-                                       std::move(paint));
-        }
-        if (!op) {
+        // However, we still don't draw true circles as round rects in coverage mode, because it can
+        // cause perf regressions on some platforms as compared to the dedicated circle Op.
+        if (GrAAType::kCoverage != aaType || oval.height() != oval.width()) {
             assert_alive(paint);
-            op = GrOvalOpFactory::MakeOvalOp(fContext, std::move(paint), viewMatrix, oval, style,
-                                             this->caps()->shaderCaps());
+            op = GrFillRRectOp::Make(fContext, aaType, viewMatrix, SkRRect::MakeOval(oval),
+                                     *this->caps(), std::move(paint));
         }
-        if (op) {
-            this->addDrawOp(clip, std::move(op));
-            return;
-        }
+    }
+    if (!op && GrAAType::kCoverage == aaType) {
+        assert_alive(paint);
+        op = GrOvalOpFactory::MakeOvalOp(fContext, std::move(paint), viewMatrix, oval, style,
+                                         this->caps()->shaderCaps());
+    }
+    if (op) {
+        this->addDrawOp(clip, std::move(op));
+        return;
     }
 
     assert_alive(paint);
@@ -1569,7 +1683,7 @@
 
     AutoCheckFlush acf(this->drawingManager());
 
-    GrAAType aaType = this->chooseAAType(aa, GrAllowMixedSamples::kNo);
+    GrAAType aaType = this->chooseAAType(aa);
     if (GrAAType::kCoverage == aaType) {
         const GrShaderCaps* shaderCaps = this->caps()->shaderCaps();
         std::unique_ptr<GrDrawOp> op = GrOvalOpFactory::MakeArcOp(fContext,
@@ -1621,19 +1735,23 @@
 }
 
 GrSemaphoresSubmitted GrRenderTargetContext::prepareForExternalIO(
-        int numSemaphores, GrBackendSemaphore backendSemaphores[]) {
+        SkSurface::BackendSurfaceAccess access, GrFlushFlags flags, int numSemaphores,
+        GrBackendSemaphore backendSemaphores[]) {
     ASSERT_SINGLE_OWNER
-    if (this->drawingManager()->wasAbandoned()) { return GrSemaphoresSubmitted::kNo; }
+    if (fContext->priv().abandoned()) {
+        return GrSemaphoresSubmitted::kNo;
+    }
     SkDEBUGCODE(this->validate();)
     GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "prepareForExternalIO", fContext);
 
     return this->drawingManager()->prepareSurfaceForExternalIO(fRenderTargetProxy.get(),
+                                                               access, flags,
                                                                numSemaphores,
                                                                backendSemaphores);
 }
 
 bool GrRenderTargetContext::waitOnSemaphores(int numSemaphores,
-                                             const GrBackendSemaphore* waitSemaphores) {
+                                             const GrBackendSemaphore waitSemaphores[]) {
     ASSERT_SINGLE_OWNER
     RETURN_FALSE_IF_ABANDONED
     SkDEBUGCODE(this->validate();)
@@ -1645,16 +1763,21 @@
         return false;
     }
 
-    auto resourceProvider = fContext->priv().resourceProvider();
+    auto direct = fContext->priv().asDirectContext();
+    if (!direct) {
+        return false;
+    }
+
+    auto resourceProvider = direct->priv().resourceProvider();
 
     SkTArray<sk_sp<GrSemaphore>> semaphores(numSemaphores);
     for (int i = 0; i < numSemaphores; ++i) {
         sk_sp<GrSemaphore> sema = resourceProvider->wrapBackendSemaphore(
                 waitSemaphores[i], GrResourceProvider::SemaphoreWrapType::kWillWait,
                 kAdopt_GrWrapOwnership);
-        std::unique_ptr<GrOp> waitOp(GrSemaphoreOp::MakeWait(fContext, sema,
+        std::unique_ptr<GrOp> waitOp(GrSemaphoreOp::MakeWait(fContext, std::move(sema),
                                                              fRenderTargetProxy.get()));
-        this->getRTOpList()->addOp(std::move(waitOp), *this->caps());
+        this->getRTOpList()->addWaitOp(std::move(waitOp), *this->caps());
     }
     return true;
 }
@@ -1664,6 +1787,10 @@
     this->getRTOpList()->addOp(std::move(op), *this->caps());
 }
 
+const GrCaps* GrRenderTargetContext::caps() const {
+    return fContext->priv().caps();
+}
+
 void GrRenderTargetContext::drawPath(const GrClip& clip,
                                      GrPaint&& paint,
                                      GrAA aa,
@@ -1700,7 +1827,7 @@
     AutoCheckFlush acf(this->drawingManager());
 
     if (!shape.style().hasPathEffect()) {
-        GrAAType aaType = this->chooseAAType(aa, GrAllowMixedSamples::kNo);
+        GrAAType aaType = this->chooseAAType(aa);
         SkRRect rrect;
         // We can ignore the starting point and direction since there is no path effect.
         bool inverted;
@@ -1762,7 +1889,8 @@
     // the src color (either the input alpha or in the frag shader) to implement
     // aa. If we have some future driver-mojo path AA that can do the right
     // thing WRT to the blend then we'll need some query on the PR.
-    GrAAType aaType = fRenderTargetContext->chooseAAType(aa, GrAllowMixedSamples::kNo);
+    auto aaTypeFlags = choose_path_aa_type_flags(
+            aa, fRenderTargetContext->fsaaType(), *fRenderTargetContext->caps());
     bool hasUserStencilSettings = !ss->isUnused();
 
     SkIRect clipConservativeBounds;
@@ -1775,7 +1903,7 @@
     canDrawArgs.fViewMatrix = &viewMatrix;
     canDrawArgs.fShape = &shape;
     canDrawArgs.fClipConservativeBounds = &clipConservativeBounds;
-    canDrawArgs.fAAType = aaType;
+    canDrawArgs.fAATypeFlags = aaTypeFlags;
     SkASSERT(!fRenderTargetContext->wrapsVkSecondaryCB());
     canDrawArgs.fTargetIsWrappedVkSecondaryCB = false;
     canDrawArgs.fHasUserStencilSettings = hasUserStencilSettings;
@@ -1798,7 +1926,7 @@
                                       &clipConservativeBounds,
                                       &viewMatrix,
                                       &shape,
-                                      aaType,
+                                      aaTypeFlags,
                                       fRenderTargetContext->colorSpaceInfo().isLinearlyBlended()};
     pr->drawPath(args);
     return true;
@@ -1807,7 +1935,7 @@
 SkBudgeted GrRenderTargetContextPriv::isBudgeted() const {
     ASSERT_SINGLE_OWNER_PRIV
 
-    if (fRenderTargetContext->wasAbandoned()) {
+    if (fRenderTargetContext->fContext->priv().abandoned()) {
         return SkBudgeted::kNo;
     }
 
@@ -1833,13 +1961,8 @@
     clip.getConservativeBounds(this->width(), this->height(), &clipConservativeBounds, nullptr);
 
     GrShape tempShape;
-    // NVPR cannot handle hairlines, so this would get picked up by a different stencil and
-    // cover path renderer (i.e. default path renderer). The hairline renderer produces much
-    // smoother hairlines than MSAA.
-    GrAllowMixedSamples allowMixedSamples = originalShape.style().isSimpleHairline()
-                                                    ? GrAllowMixedSamples::kNo
-                                                    : GrAllowMixedSamples::kYes;
-    GrAAType aaType = this->chooseAAType(aa, allowMixedSamples);
+    auto aaTypeFlags = choose_path_aa_type_flags(aa, this->fsaaType(), *this->caps());
+
     GrPathRenderer::CanDrawPathArgs canDrawArgs;
     canDrawArgs.fCaps = this->caps();
     canDrawArgs.fViewMatrix = &viewMatrix;
@@ -1854,7 +1977,7 @@
         return;
     }
 
-    canDrawArgs.fAAType = aaType;
+    canDrawArgs.fAATypeFlags = aaTypeFlags;
 
     // Try a 1st time without applying any of the style to the geometry (and barring sw)
     pr = this->drawingManager()->getPathRenderer(canDrawArgs, false, kType);
@@ -1899,7 +2022,7 @@
                                       &clipConservativeBounds,
                                       &viewMatrix,
                                       canDrawArgs.fShape,
-                                      aaType,
+                                      aaTypeFlags,
                                       this->colorSpaceInfo().isLinearlyBlended()};
     pr->drawPath(args);
 }
@@ -1933,7 +2056,7 @@
 void GrRenderTargetContext::addDrawOp(const GrClip& clip, std::unique_ptr<GrDrawOp> op,
                                       const std::function<WillAddOpFn>& willAddFn) {
     ASSERT_SINGLE_OWNER
-    if (this->drawingManager()->wasAbandoned()) {
+    if (fContext->priv().abandoned()) {
         fContext->priv().opMemoryPool()->release(std::move(op));
         return;
     }
@@ -1972,8 +2095,10 @@
         this->setNeedsStencil();
     }
 
+    GrClampType clampType = GrPixelConfigClampType(this->colorSpaceInfo().config());
     GrXferProcessor::DstProxy dstProxy;
-    GrProcessorSet::Analysis analysis = op->finalize(*this->caps(), &appliedClip);
+    GrProcessorSet::Analysis analysis = op->finalize(
+            *this->caps(), &appliedClip, this->fsaaType(), clampType);
     if (analysis.requiresDstTexture()) {
         if (!this->setupDstProxy(this->asRenderTargetProxy(), clip, *op, &dstProxy)) {
             fContext->priv().opMemoryPool()->release(std::move(op));
diff --git a/src/gpu/GrRenderTargetContext.h b/src/gpu/GrRenderTargetContext.h
index f1e45bd..c4ef529 100644
--- a/src/gpu/GrRenderTargetContext.h
+++ b/src/gpu/GrRenderTargetContext.h
@@ -9,8 +9,6 @@
 #define GrRenderTargetContext_DEFINED
 
 #include "../private/GrRenderTargetProxy.h"
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrPaint.h"
 #include "GrSurfaceContext.h"
 #include "GrTypesPriv.h"
@@ -18,6 +16,7 @@
 #include "SkCanvas.h"
 #include "SkDrawable.h"
 #include "SkRefCnt.h"
+#include "SkSurface.h"
 #include "SkSurfaceProps.h"
 #include "text/GrTextTarget.h"
 
@@ -28,6 +27,7 @@
 class GrDrawingManager;
 class GrDrawOp;
 class GrFixedClip;
+class GrOp;
 class GrRenderTarget;
 class GrRenderTargetContextPriv;
 class GrRenderTargetOpList;
@@ -110,12 +110,16 @@
      * @param rectToDraw   the rectangle to draw
      * @param localRect    the rectangle of shader coordinates applied to rectToDraw
      */
-    void fillRectToRect(const GrClip&,
+    void fillRectToRect(const GrClip& clip,
                         GrPaint&& paint,
-                        GrAA,
+                        GrAA aa,
                         const SkMatrix& viewMatrix,
                         const SkRect& rectToDraw,
-                        const SkRect& localRect);
+                        const SkRect& localRect) {
+        this->fillRectWithEdgeAA(clip, std::move(paint), aa,
+                                 aa == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone,
+                                 viewMatrix, rectToDraw, &localRect);
+    }
 
     /**
      * Fills a rect with a paint and a localMatrix.
@@ -129,9 +133,29 @@
 
     /**
      * Creates an op that draws a fill rect with per-edge control over anti-aliasing.
+     *
+     * This is a specialized version of fillQuadWithEdgeAA, but is kept separate since knowing
+     * the geometry is a rectangle affords more optimizations.
      */
-    void fillRectWithEdgeAA(const GrClip& clip, GrPaint&& paint, GrQuadAAFlags edgeAA,
-                            const SkMatrix& viewMatrix, const SkRect& rect);
+    void fillRectWithEdgeAA(const GrClip& clip, GrPaint&& paint, GrAA aa, GrQuadAAFlags edgeAA,
+                            const SkMatrix& viewMatrix, const SkRect& rect,
+                            const SkRect* optionalLocalRect = nullptr);
+
+    /**
+     * Similar to fillRectWithEdgeAA but draws an arbitrary 2D convex quadrilateral transformed
+     * by 'viewMatrix', with per-edge control over anti-aliasing. The quad should follow the
+     * ordering used by SkRect::toQuad(), which determines how the edge AA is applied:
+     *  - "top" = points [0] and [1]
+     *  - "right" = points[1] and [2]
+     *  - "bottom" = points[2] and [3]
+     *  - "left" = points[3] and [0]
+     *
+     * The last argument, 'optionalLocalQuad', can be null if no separate local coordinates are
+     * necessary.
+     */
+    void fillQuadWithEdgeAA(const GrClip& clip, GrPaint&& paint, GrAA aa, GrQuadAAFlags edgeAA,
+                            const SkMatrix& viewMatrix, const SkPoint quad[4],
+                            const SkPoint optionalLocalQuad[4]);
 
     /** Used with drawQuadSet */
     struct QuadSetEntry {
@@ -152,25 +176,41 @@
      * device space.
      */
     void drawTexture(const GrClip& clip, sk_sp<GrTextureProxy>, GrSamplerState::Filter,
-                     const SkPMColor4f&, const SkRect& srcRect, const SkRect& dstRect,
-                     GrQuadAAFlags, SkCanvas::SrcRectConstraint, const SkMatrix& viewMatrix,
-                     sk_sp<GrColorSpaceXform> texXform);
+                     SkBlendMode mode, const SkPMColor4f&, const SkRect& srcRect,
+                     const SkRect& dstRect, GrAA, GrQuadAAFlags, SkCanvas::SrcRectConstraint,
+                     const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform> texXform);
+
+    /**
+     * Variant of drawTexture that instead draws the texture applied to 'dstQuad' transformed by
+     * 'viewMatrix', using the 'srcQuad' texture coordinates clamped to the optional 'domain'. If
+     * 'domain' is null, it's equivalent to using the fast src rect constraint. If 'domain' is
+     * provided, the strict src rect constraint is applied using 'domain'.
+     */
+    void drawTextureQuad(const GrClip& clip, sk_sp<GrTextureProxy>, GrSamplerState::Filter,
+                         SkBlendMode mode, const SkPMColor4f&, const SkPoint srcQuad[4],
+                         const SkPoint dstQuad[4], GrAA, GrQuadAAFlags, const SkRect* domain,
+                         const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform> texXform);
 
     /** Used with drawTextureSet */
     struct TextureSetEntry {
         sk_sp<GrTextureProxy> fProxy;
         SkRect fSrcRect;
         SkRect fDstRect;
+        const SkPoint* fDstClipQuad; // Must be null, or point to an array of 4 points
+        const SkMatrix* fPreViewMatrix; // If not null, entry's CTM is 'viewMatrix' * fPreViewMatrix
         float fAlpha;
         GrQuadAAFlags fAAFlags;
     };
     /**
      * Draws a set of textures with a shared filter, color, view matrix, color xform, and
      * texture color xform. The textures must all have the same GrTextureType and GrConfig.
+     *
+     * If any entries provide a non-null fDstClip array, it will be read from immediately based on
+     * fDstClipCount, so the pointer can become invalid after this returns.
      */
     void drawTextureSet(const GrClip&, const TextureSetEntry[], int cnt, GrSamplerState::Filter,
-                        SkBlendMode mode, const SkMatrix& viewMatrix,
-                        sk_sp<GrColorSpaceXform> texXform);
+                        SkBlendMode mode, GrAA aa, SkCanvas::SrcRectConstraint,
+                        const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform> texXform);
 
     /**
      * Draw a roundrect using a paint.
@@ -366,19 +406,20 @@
      * After this returns any pending surface IO will be issued to the backend 3D API and
      * if the surface has MSAA it will be resolved.
      */
-    GrSemaphoresSubmitted prepareForExternalIO(int numSemaphores,
+    GrSemaphoresSubmitted prepareForExternalIO(SkSurface::BackendSurfaceAccess access,
+                                               GrFlushFlags flags, int numSemaphores,
                                                GrBackendSemaphore backendSemaphores[]);
 
     /**
      *  The next time this GrRenderTargetContext is flushed, the gpu will wait on the passed in
      *  semaphores before executing any commands.
      */
-    bool waitOnSemaphores(int numSemaphores, const GrBackendSemaphore* waitSemaphores);
+    bool waitOnSemaphores(int numSemaphores, const GrBackendSemaphore waitSemaphores[]);
 
     void insertEventMarker(const SkString&);
 
     GrFSAAType fsaaType() const { return fRenderTargetProxy->fsaaType(); }
-    const GrCaps* caps() const { return fContext->priv().caps(); }
+    const GrCaps* caps() const;
     int width() const { return fRenderTargetProxy->width(); }
     int height() const { return fRenderTargetProxy->height(); }
     int numColorSamples() const { return fRenderTargetProxy->numColorSamples(); }
@@ -388,18 +429,11 @@
     bool wrapsVkSecondaryCB() const { return fRenderTargetProxy->wrapsVkSecondaryCB(); }
     GrMipMapped mipMapped() const;
 
-    bool wasAbandoned() const;
-
     void setNeedsStencil() { fRenderTargetProxy->setNeedsStencil(); }
 
-    GrRenderTarget* accessRenderTarget() {
-        // TODO: usage of this entry point needs to be reduced and potentially eliminated
-        // since it ends the deferral of the GrRenderTarget's allocation
-        if (!fRenderTargetProxy->instantiate(fContext->priv().resourceProvider())) {
-            return nullptr;
-        }
-        return fRenderTargetProxy->peekRenderTarget();
-    }
+    // This entry point should only be called if the backing GPU object is known to be
+    // instantiated.
+    GrRenderTarget* accessRenderTarget() { return fRenderTargetProxy->peekRenderTarget(); }
 
     GrSurfaceProxy* asSurfaceProxy() override { return fRenderTargetProxy.get(); }
     const GrSurfaceProxy* asSurfaceProxy() const override { return fRenderTargetProxy.get(); }
@@ -423,18 +457,16 @@
     bool isWrapped_ForTesting() const;
 
 protected:
-    GrRenderTargetContext(GrContext*, GrDrawingManager*, sk_sp<GrRenderTargetProxy>,
-                          sk_sp<SkColorSpace>, const SkSurfaceProps*, GrAuditTrail*,
-                          GrSingleOwner*, bool managedOpList = true);
+    GrRenderTargetContext(GrRecordingContext*, sk_sp<GrRenderTargetProxy>,
+                          sk_sp<SkColorSpace>, const SkSurfaceProps*,
+                          bool managedOpList = true);
 
     SkDEBUGCODE(void validate() const override;)
 
 private:
     class TextTarget;
 
-    inline GrAAType chooseAAType(GrAA aa, GrAllowMixedSamples allowMixedSamples) {
-        return GrChooseAAType(aa, this->fsaaType(), allowMixedSamples, *this->caps());
-    }
+    GrAAType chooseAAType(GrAA);
 
     friend class GrAtlasTextBlob;               // for access to add[Mesh]DrawOp
     friend class GrClipStackClip;               // for access to getOpList
diff --git a/src/gpu/GrRenderTargetContextPriv.h b/src/gpu/GrRenderTargetContextPriv.h
index c06c8ca..0858749 100644
--- a/src/gpu/GrRenderTargetContextPriv.h
+++ b/src/gpu/GrRenderTargetContextPriv.h
@@ -62,13 +62,12 @@
      */
     void absClear(const SkIRect* rect, const SkPMColor4f& color);
 
-    void stencilRect(const GrHardClip&,
-                     const GrUserStencilSettings* ss,
-                     GrAAType,
-                     const SkMatrix& viewMatrix,
-                     const SkRect& rect);
+    void stencilRect(
+            const GrHardClip&, const GrUserStencilSettings* ss, GrAA doStencilMSAA,
+            const SkMatrix& viewMatrix, const SkRect& rect);
 
-    void stencilPath(const GrHardClip&, GrAAType, const SkMatrix& viewMatrix, const GrPath*);
+    void stencilPath(
+            const GrHardClip&, GrAA doStencilMSAA, const SkMatrix& viewMatrix, const GrPath*);
 
     /**
      * Draws a rect, either AA or not, and touches the stencil buffer with the user stencil settings
@@ -78,7 +77,7 @@
                             const GrUserStencilSettings*,
                             SkRegion::Op op,
                             bool invert,
-                            GrAA,
+                            GrAA doStencilMSAA,
                             const SkMatrix& viewMatrix,
                             const SkRect&);
 
@@ -90,10 +89,16 @@
                             const GrUserStencilSettings*,
                             SkRegion::Op op,
                             bool invert,
-                            GrAA,
+                            GrAA doStencilMSAA,
                             const SkMatrix& viewMatrix,
                             const SkPath&);
 
+    void drawFilledRect(
+            const GrClip& clip, GrPaint&& paint, GrAA aa, const SkMatrix& m, const SkRect& rect,
+            const GrUserStencilSettings* ss = nullptr) {
+        fRenderTargetContext->drawFilledRect(clip, std::move(paint), aa, m, rect, ss);
+    }
+
     SkBudgeted isBudgeted() const;
 
     int maxWindowRectangles() const;
diff --git a/src/gpu/GrRenderTargetOpList.cpp b/src/gpu/GrRenderTargetOpList.cpp
index f5469d1..f66fec9 100644
--- a/src/gpu/GrRenderTargetOpList.cpp
+++ b/src/gpu/GrRenderTargetOpList.cpp
@@ -11,6 +11,8 @@
 #include "GrGpu.h"
 #include "GrGpuCommandBuffer.h"
 #include "GrMemoryPool.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRect.h"
 #include "GrRenderTargetContext.h"
 #include "GrResourceAllocator.h"
@@ -556,7 +558,9 @@
     // Although the clear will ignore the stencil buffer, following draw ops may not so we can't get
     // rid of all the preceding ops. Beware! If we ever add any ops that have a side effect beyond
     // modifying the stencil buffer we will need a more elaborate tracking system (skbug.com/7002).
-    if (this->isEmpty() || !fTarget.get()->asRenderTargetProxy()->needsStencil()) {
+    // Additionally, if we previously recorded a wait op, we cannot delete the wait op. Until we
+    // track the wait ops separately from normal ops, we have to avoid clearing out any ops.
+    if (this->isEmpty() || (!fTarget.get()->asRenderTargetProxy()->needsStencil() && !fHasWaitOp)) {
         this->deleteOps();
         fDeferredProxies.reset();
 
@@ -574,7 +578,7 @@
 
 // This closely parallels GrTextureOpList::copySurface but renderTargetOpLists
 // also store the applied clip and dest proxy with the op
-bool GrRenderTargetOpList::copySurface(GrContext* context,
+bool GrRenderTargetOpList::copySurface(GrRecordingContext* context,
                                        GrSurfaceProxy* dst,
                                        GrSurfaceProxy* src,
                                        const SkIRect& srcRect,
@@ -606,8 +610,22 @@
     }
 }
 
+bool GrRenderTargetOpList::onIsUsed(GrSurfaceProxy* proxyToCheck) const {
+    bool used = false;
+
+    auto visit = [ proxyToCheck, &used ] (GrSurfaceProxy* p) {
+        if (p == proxyToCheck) {
+            used = true;
+        }
+    };
+    for (const OpChain& recordedOp : fOpChains) {
+        recordedOp.visitProxies(visit, GrOp::VisitorType::kOther);
+    }
+
+    return used;
+}
+
 void GrRenderTargetOpList::gatherProxyIntervals(GrResourceAllocator* alloc) const {
-    unsigned int cur = alloc->numOps();
 
     for (int i = 0; i < fDeferredProxies.count(); ++i) {
         SkASSERT(!fDeferredProxies[i]->isInstantiated());
@@ -621,23 +639,25 @@
 
     // Add the interval for all the writes to this opList's target
     if (fOpChains.count()) {
+        unsigned int cur = alloc->curOp();
+
         alloc->addInterval(fTarget.get(), cur, cur + fOpChains.count() - 1);
     } else {
         // This can happen if there is a loadOp (e.g., a clear) but no other draws. In this case we
         // still need to add an interval for the destination so we create a fake op# for
         // the missing clear op.
-        alloc->addInterval(fTarget.get());
+        alloc->addInterval(fTarget.get(), alloc->curOp(), alloc->curOp());
         alloc->incOps();
     }
 
     auto gather = [ alloc SkDEBUGCODE(, this) ] (GrSurfaceProxy* p) {
-        alloc->addInterval(p SkDEBUGCODE(, fTarget.get() == p));
+        alloc->addInterval(p, alloc->curOp(), alloc->curOp() SkDEBUGCODE(, fTarget.get() == p));
     };
     for (const OpChain& recordedOp : fOpChains) {
         // only diff from the GrTextureOpList version
         recordedOp.visitProxies(gather, GrOp::VisitorType::kAllocatorGather);
 
-        // Even though the op may have been moved we still need to increment the op count to
+        // Even though the op may have been (re)moved we still need to increment the op count to
         // keep all the math consistent.
         alloc->incOps();
     }
diff --git a/src/gpu/GrRenderTargetOpList.h b/src/gpu/GrRenderTargetOpList.h
index 7e44467..44cc39f 100644
--- a/src/gpu/GrRenderTargetOpList.h
+++ b/src/gpu/GrRenderTargetOpList.h
@@ -72,6 +72,11 @@
         this->recordOp(std::move(op), GrProcessorSet::EmptySetAnalysis(), nullptr, nullptr, caps);
     }
 
+    void addWaitOp(std::unique_ptr<GrOp> op, const GrCaps& caps) {
+        fHasWaitOp= true;
+        this->addOp(std::move(op), caps);
+    }
+
     void addDrawOp(std::unique_ptr<GrDrawOp> op, const GrProcessorSet::Analysis& processorAnalysis,
                    GrAppliedClip&& clip, const DstProxy& dstProxy, const GrCaps& caps) {
         auto addDependency = [ &caps, this ] (GrSurfaceProxy* p) {
@@ -100,7 +105,7 @@
      * depending on the type of surface, configs, etc, and the backend-specific
      * limitations.
      */
-    bool copySurface(GrContext*,
+    bool copySurface(GrRecordingContext*,
                      GrSurfaceProxy* dst,
                      GrSurfaceProxy* src,
                      const SkIRect& srcRect,
@@ -121,6 +126,8 @@
     // however, requires that the RTC be able to coordinate with the op list to achieve similar ends
     friend class GrRenderTargetContext;
 
+    bool onIsUsed(GrSurfaceProxy*) const override;
+
     // Must only be called if native stencil buffer clearing is enabled
     void setStencilLoadOp(GrLoadOp op);
     // Must only be called if native color buffer clearing is enabled.
@@ -222,6 +229,9 @@
     SkIRect                        fLastDevClipBounds;
     int                            fLastClipNumAnalyticFPs;
 
+    // We must track if we have a wait op so that we don't delete the op when we have a full clear.
+    bool fHasWaitOp = false;;
+
     // For ops/opList we have mean: 5 stdDev: 28
     SkSTArray<25, OpChain, true> fOpChains;
 
diff --git a/src/gpu/GrRenderTargetPriv.h b/src/gpu/GrRenderTargetPriv.h
index ff3607b..d781a9c 100644
--- a/src/gpu/GrRenderTargetPriv.h
+++ b/src/gpu/GrRenderTargetPriv.h
@@ -34,6 +34,25 @@
 
     int numStencilBits() const;
 
+    /**
+     * Returns a unique key that identifies this render target's sample pattern. (Must be
+     * multisampled.)
+     *
+     * NOTE: The pipeline argument is only required in case we need to flush draw state and actually
+     * query multisample info. The pipeline itself is not expected to affect sample locations.
+     */
+    int getSamplePatternKey(const GrPipeline&) const;
+
+    /**
+     * Retrieves the per-pixel HW sample locations for this render target, and, as a by-product, the actual
+     * number of samples in use. (This may differ from fSampleCnt.) Sample locations are returned as
+     * 0..1 offsets relative to the top-left corner of the pixel.
+     */
+    const SkTArray<SkPoint>& getSampleLocations(const GrPipeline& pipeline) const {
+        int samplePatternKey = this->getSamplePatternKey(pipeline);
+        return fRenderTarget->getGpu()->retrieveSampleLocations(samplePatternKey);
+    }
+
 private:
     explicit GrRenderTargetPriv(GrRenderTarget* renderTarget) : fRenderTarget(renderTarget) {}
     GrRenderTargetPriv(const GrRenderTargetPriv&) {} // unimpl
diff --git a/src/gpu/GrRenderTargetProxy.cpp b/src/gpu/GrRenderTargetProxy.cpp
index 3f7b7e0..3a84bfe 100644
--- a/src/gpu/GrRenderTargetProxy.cpp
+++ b/src/gpu/GrRenderTargetProxy.cpp
@@ -39,12 +39,13 @@
                                          LazyInstantiationType lazyType,
                                          const GrBackendFormat& format, const GrSurfaceDesc& desc,
                                          GrSurfaceOrigin origin,  SkBackingFit fit,
-                                         SkBudgeted budgeted, GrInternalSurfaceFlags surfaceFlags)
+                                         SkBudgeted budgeted, GrInternalSurfaceFlags surfaceFlags,
+                                         WrapsVkSecondaryCB wrapsVkSecondaryCB)
         : INHERITED(std::move(callback), lazyType, format, desc, origin, fit, budgeted,
                     surfaceFlags)
         , fSampleCnt(desc.fSampleCnt)
         , fNeedsStencil(false)
-        , fWrapsVkSecondaryCB(WrapsVkSecondaryCB::kNo) {
+        , fWrapsVkSecondaryCB(wrapsVkSecondaryCB) {
     SkASSERT(SkToBool(kRenderTarget_GrSurfaceFlag & desc.fFlags));
 }
 
@@ -61,14 +62,15 @@
     return this->glRTFBOIDIs0() ? 0 : caps.maxWindowRectangles();
 }
 
-bool GrRenderTargetProxy::instantiate(GrResourceProvider* resourceProvider) {
+bool GrRenderTargetProxy::instantiate(GrResourceProvider* resourceProvider,
+                                      bool dontForceNoPendingIO) {
     if (LazyState::kNot != this->lazyInstantiationState()) {
         return false;
     }
     static constexpr GrSurfaceDescFlags kDescFlags = kRenderTarget_GrSurfaceFlag;
 
     if (!this->instantiateImpl(resourceProvider, fSampleCnt, fNeedsStencil, kDescFlags,
-                               GrMipMapped::kNo, nullptr)) {
+                               GrMipMapped::kNo, nullptr, dontForceNoPendingIO)) {
         return false;
     }
     SkASSERT(fTarget->asRenderTarget());
@@ -77,10 +79,12 @@
 }
 
 sk_sp<GrSurface> GrRenderTargetProxy::createSurface(GrResourceProvider* resourceProvider) const {
+    SkASSERT(resourceProvider->explicitlyAllocateGPUResources());
+
     static constexpr GrSurfaceDescFlags kDescFlags = kRenderTarget_GrSurfaceFlag;
 
     sk_sp<GrSurface> surface = this->createSurfaceImpl(resourceProvider, fSampleCnt, fNeedsStencil,
-                                                       kDescFlags, GrMipMapped::kNo);
+                                                       kDescFlags, GrMipMapped::kNo, true);
     if (!surface) {
         return nullptr;
     }
diff --git a/src/gpu/GrResourceAllocator.cpp b/src/gpu/GrResourceAllocator.cpp
index 0d3c34e..2142b06 100644
--- a/src/gpu/GrResourceAllocator.cpp
+++ b/src/gpu/GrResourceAllocator.cpp
@@ -47,6 +47,7 @@
     }
 
     fEndOfOpListOpIndices.push_back(this->curOp()); // This is the first op index of the next opList
+    SkASSERT(fEndOfOpListOpIndices.count() <= fNumOpLists);
 }
 
 GrResourceAllocator::~GrResourceAllocator() {
@@ -309,6 +310,34 @@
     }
 }
 
+bool GrResourceAllocator::onOpListBoundary() const {
+    if (fIntvlList.empty()) {
+        SkASSERT(fCurOpListIndex+1 <= fNumOpLists);
+        // Although technically on an opList boundary there is no need to force an
+        // intermediate flush here
+        return false;
+    }
+
+    const Interval* tmp = fIntvlList.peekHead();
+    return fEndOfOpListOpIndices[fCurOpListIndex] <= tmp->start();
+}
+
+void GrResourceAllocator::forceIntermediateFlush(int* stopIndex) {
+    *stopIndex = fCurOpListIndex+1;
+
+    // This is interrupting the allocation of resources for this flush. We need to
+    // proactively clear the active interval list of any intervals that aren't
+    // guaranteed to survive the partial flush lest they become zombies (i.e.,
+    // holding a deleted surface proxy).
+    const Interval* tmp = fIntvlList.peekHead();
+    SkASSERT(fEndOfOpListOpIndices[fCurOpListIndex] <= tmp->start());
+
+    fCurOpListIndex++;
+    SkASSERT(fCurOpListIndex < fNumOpLists);
+
+    this->expire(tmp->start());
+}
+
 bool GrResourceAllocator::assign(int* startIndex, int* stopIndex, AssignError* outError) {
     SkASSERT(outError);
     *outError = AssignError::kNoError;
@@ -318,9 +347,22 @@
         return false;          // nothing to render
     }
 
+    SkASSERT(fCurOpListIndex < fNumOpLists);
+    SkASSERT(fNumOpLists == fEndOfOpListOpIndices.count());
+
     *startIndex = fCurOpListIndex;
     *stopIndex = fEndOfOpListOpIndices.count();
 
+#if GR_ALLOCATION_SPEW
+    SkDebugf("assigning opLists %d through %d out of %d numOpLists\n",
+             *startIndex, *stopIndex, fNumOpLists);
+    SkDebugf("EndOfOpListIndices: ");
+    for (int i = 0; i < fEndOfOpListOpIndices.count(); ++i) {
+        SkDebugf("%d ", fEndOfOpListOpIndices[i]);
+    }
+    SkDebugf("\n");
+#endif
+
     if (!fResourceProvider->explicitlyAllocateGPUResources()) {
         fIntvlList.detachAll(); // arena allocator will clean these up for us
         return true;
@@ -332,8 +374,9 @@
     this->dumpIntervals();
 #endif
     while (Interval* cur = fIntvlList.popHead()) {
-        if (fEndOfOpListOpIndices[fCurOpListIndex] < cur->start()) {
+        if (fEndOfOpListOpIndices[fCurOpListIndex] <= cur->start()) {
             fCurOpListIndex++;
+            SkASSERT(fCurOpListIndex < fNumOpLists);
         }
 
         this->expire(cur->start());
@@ -352,19 +395,8 @@
 
             if (fResourceProvider->overBudget()) {
                 // Only force intermediate draws on opList boundaries
-                if (!fIntvlList.empty() &&
-                    fEndOfOpListOpIndices[fCurOpListIndex] < fIntvlList.peekHead()->start()) {
-                    *stopIndex = fCurOpListIndex+1;
-
-                    // This is interrupting the allocation of resources for this flush. We need to
-                    // proactively clear the active interval list of any intervals that aren't
-                    // guaranteed to survive the partial flush lest they become zombies (i.e.,
-                    // holding a deleted surface proxy).
-                    if (const Interval* tmp = fIntvlList.peekHead()) {
-                        this->expire(tmp->start());
-                    } else {
-                        this->expire(std::numeric_limits<unsigned int>::max());
-                    }
+                if (this->onOpListBoundary()) {
+                    this->forceIntermediateFlush(stopIndex);
                     return true;
                 }
             }
@@ -409,19 +441,8 @@
 
         if (fResourceProvider->overBudget()) {
             // Only force intermediate draws on opList boundaries
-            if (!fIntvlList.empty() &&
-                fEndOfOpListOpIndices[fCurOpListIndex] < fIntvlList.peekHead()->start()) {
-                *stopIndex = fCurOpListIndex+1;
-
-                // This is interrupting the allocation of resources for this flush. We need to
-                // proactively clear the active interval list of any intervals that aren't
-                // guaranteed to survive the partial flush lest they become zombies (i.e.,
-                // holding a deleted surface proxy).
-                if (const Interval* tmp = fIntvlList.peekHead()) {
-                    this->expire(tmp->start());
-                } else {
-                    this->expire(std::numeric_limits<unsigned int>::max());
-                }
+            if (this->onOpListBoundary()) {
+                this->forceIntermediateFlush(stopIndex);
                 return true;
             }
         }
@@ -434,9 +455,9 @@
 
 #if GR_ALLOCATION_SPEW
 void GrResourceAllocator::dumpIntervals() {
-
     // Print all the intervals while computing their range
-    unsigned int min = fNumOps+1;
+    SkDebugf("------------------------------------------------------------\n");
+    unsigned int min = std::numeric_limits<unsigned int>::max();
     unsigned int max = 0;
     for(const Interval* cur = fIntvlList.peekHead(); cur; cur = cur->next()) {
         SkDebugf("{ %3d,%3d }: [%2d, %2d] - proxyRefs:%d surfaceRefs:%d R:%d W:%d\n",
diff --git a/src/gpu/GrResourceAllocator.h b/src/gpu/GrResourceAllocator.h
index ea1250f..fabaf00 100644
--- a/src/gpu/GrResourceAllocator.h
+++ b/src/gpu/GrResourceAllocator.h
@@ -42,27 +42,24 @@
  */
 class GrResourceAllocator {
 public:
-    GrResourceAllocator(GrResourceProvider* resourceProvider, GrDeinstantiateProxyTracker* tracker)
-            : fResourceProvider(resourceProvider), fDeinstantiateTracker(tracker) {}
+    GrResourceAllocator(GrResourceProvider* resourceProvider,
+                        GrDeinstantiateProxyTracker* tracker
+                        SkDEBUGCODE(, int numOpLists))
+            : fResourceProvider(resourceProvider)
+            , fDeinstantiateTracker(tracker)
+            SkDEBUGCODE(, fNumOpLists(numOpLists)) {
+    }
 
     ~GrResourceAllocator();
 
     unsigned int curOp() const { return fNumOps; }
     void incOps() { fNumOps++; }
-    unsigned int numOps() const { return fNumOps; }
 
     // Add a usage interval from 'start' to 'end' inclusive. This is usually used for renderTargets.
     // If an existing interval already exists it will be expanded to include the new range.
     void addInterval(GrSurfaceProxy*, unsigned int start, unsigned int end
                      SkDEBUGCODE(, bool isDirectDstRead = false));
 
-    // Add an interval that spans just the current op. Usually this is for texture uses.
-    // If an existing interval already exists it will be expanded to include the new operation.
-    void addInterval(GrSurfaceProxy* proxy
-                     SkDEBUGCODE(, bool isDirectDstRead = false)) {
-        this->addInterval(proxy, fNumOps, fNumOps SkDEBUGCODE(, isDirectDstRead));
-    }
-
     enum class AssignError {
         kNoError,
         kFailedProxyInstantiation
@@ -88,6 +85,9 @@
     // Remove dead intervals from the active list
     void expire(unsigned int curIndex);
 
+    bool onOpListBoundary() const;
+    void forceIntermediateFlush(int* stopIndex);
+
     // These two methods wrap the interactions with the free pool
     void recycleSurface(sk_sp<GrSurface> surface);
     sk_sp<GrSurface> findSurfaceFor(const GrSurfaceProxy* proxy, bool needsStencil);
@@ -218,11 +218,11 @@
 
     IntervalList                 fIntvlList;         // All the intervals sorted by increasing start
     IntervalList                 fActiveIntvls;      // List of live intervals during assignment
-                                               // (sorted by increasing end)
-    unsigned int                 fNumOps = 1;        // op # 0 is reserved for uploads at the start
-                                               // of a flush
+                                                     // (sorted by increasing end)
+    unsigned int                 fNumOps = 0;
     SkTArray<unsigned int>       fEndOfOpListOpIndices;
     int                          fCurOpListIndex = 0;
+    SkDEBUGCODE(const int        fNumOpLists = -1;)
 
     SkDEBUGCODE(bool             fAssigned = false;)
 
diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp
index 9b17d92..4374cd8 100644
--- a/src/gpu/GrResourceCache.cpp
+++ b/src/gpu/GrResourceCache.cpp
@@ -8,12 +8,15 @@
 #include "GrResourceCache.h"
 #include <atomic>
 #include "GrCaps.h"
+#include "GrContext.h"
+#include "GrContextPriv.h"
 #include "GrGpuResourceCacheAccess.h"
 #include "GrProxyProvider.h"
 #include "GrSingleOwner.h"
 #include "GrTexture.h"
 #include "GrTextureProxyCacheAccess.h"
 #include "GrTracing.h"
+#include "SkExchange.h"
 #include "SkGr.h"
 #include "SkMessageBus.h"
 #include "SkOpts.h"
@@ -67,32 +70,53 @@
     GrResourceCache* fCache;
 };
 
- //////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////
+
+inline GrResourceCache::ResourceAwaitingUnref::ResourceAwaitingUnref() = default;
+
+inline GrResourceCache::ResourceAwaitingUnref::ResourceAwaitingUnref(GrGpuResource* resource)
+        : fResource(resource), fNumUnrefs(1) {}
+
+inline GrResourceCache::ResourceAwaitingUnref::ResourceAwaitingUnref(ResourceAwaitingUnref&& that) {
+    fResource = skstd::exchange(that.fResource, nullptr);
+    fNumUnrefs = skstd::exchange(that.fNumUnrefs, 0);
+}
+
+inline GrResourceCache::ResourceAwaitingUnref& GrResourceCache::ResourceAwaitingUnref::operator=(
+        ResourceAwaitingUnref&& that) {
+    fResource = skstd::exchange(that.fResource, nullptr);
+    fNumUnrefs = skstd::exchange(that.fNumUnrefs, 0);
+    return *this;
+}
+
+inline GrResourceCache::ResourceAwaitingUnref::~ResourceAwaitingUnref() {
+    if (fResource) {
+        for (int i = 0; i < fNumUnrefs; ++i) {
+            fResource->unref();
+        }
+    }
+}
+
+inline void GrResourceCache::ResourceAwaitingUnref::addRef() { ++fNumUnrefs; }
+
+inline void GrResourceCache::ResourceAwaitingUnref::unref() {
+    SkASSERT(fNumUnrefs > 0);
+    fResource->unref();
+    --fNumUnrefs;
+}
+
+inline bool GrResourceCache::ResourceAwaitingUnref::finished() { return !fNumUnrefs; }
+
+//////////////////////////////////////////////////////////////////////////////
 
 GrResourceCache::GrResourceCache(const GrCaps* caps, GrSingleOwner* singleOwner,
                                  uint32_t contextUniqueID)
-        : fProxyProvider(nullptr)
-        , fTimestamp(0)
-        , fMaxCount(kDefaultMaxCount)
-        , fMaxBytes(kDefaultMaxSize)
-#if GR_CACHE_STATS
-        , fHighWaterCount(0)
-        , fHighWaterBytes(0)
-        , fBudgetedHighWaterCount(0)
-        , fBudgetedHighWaterBytes(0)
-#endif
-        , fBytes(0)
-        , fBudgetedCount(0)
-        , fBudgetedBytes(0)
-        , fPurgeableBytes(0)
-        , fInvalidUniqueKeyInbox(contextUniqueID)
+        : fInvalidUniqueKeyInbox(contextUniqueID)
         , fFreedGpuResourceInbox(contextUniqueID)
         , fContextUniqueID(contextUniqueID)
         , fSingleOwner(singleOwner)
         , fPreferVRAMUseOverFlushes(caps->preferVRAMUseOverFlushes()) {
     SkASSERT(contextUniqueID != SK_InvalidUniqueID);
-    SkDEBUGCODE(fCount = 0;)
-    SkDEBUGCODE(fNewlyPurgeableResourceForValidation = nullptr;)
 }
 
 GrResourceCache::~GrResourceCache() {
@@ -179,10 +203,9 @@
 void GrResourceCache::abandonAll() {
     AutoValidate av(this);
 
-    for (int i = 0; i < fResourcesWaitingForFreeMsg.count(); ++i) {
-        fResourcesWaitingForFreeMsg[i]->cacheAccess().abandon();
-    }
-    fResourcesWaitingForFreeMsg.reset();
+    // We need to make sure to free any resources that were waiting on a free message but never
+    // received one.
+    fResourcesAwaitingUnref.reset();
 
     while (fNonpurgeableResources.count()) {
         GrGpuResource* back = *(fNonpurgeableResources.end() - 1);
@@ -204,7 +227,7 @@
     SkASSERT(!fBudgetedCount);
     SkASSERT(!fBudgetedBytes);
     SkASSERT(!fPurgeableBytes);
-    SkASSERT(!fResourcesWaitingForFreeMsg.count());
+    SkASSERT(!fResourcesAwaitingUnref.count());
 }
 
 void GrResourceCache::releaseAll() {
@@ -214,10 +237,7 @@
 
     // We need to make sure to free any resources that were waiting on a free message but never
     // received one.
-    for (int i = 0; i < fResourcesWaitingForFreeMsg.count(); ++i) {
-        fResourcesWaitingForFreeMsg[i]->unref();
-    }
-    fResourcesWaitingForFreeMsg.reset();
+    fResourcesAwaitingUnref.reset();
 
     SkASSERT(fProxyProvider); // better have called setProxyProvider
     // We must remove the uniqueKeys from the proxies here. While they possess a uniqueKey
@@ -244,7 +264,18 @@
     SkASSERT(!fBudgetedCount);
     SkASSERT(!fBudgetedBytes);
     SkASSERT(!fPurgeableBytes);
-    SkASSERT(!fResourcesWaitingForFreeMsg.count());
+    SkASSERT(!fResourcesAwaitingUnref.count());
+}
+
+void GrResourceCache::refResource(GrGpuResource* resource) {
+    SkASSERT(resource);
+    SkASSERT(resource->getContext()->priv().getResourceCache() == this);
+    if (resource->cacheAccess().hasRef()) {
+        resource->ref();
+    } else {
+        this->refAndMakeResourceMRU(resource);
+    }
+    this->validate();
 }
 
 class GrResourceCache::AvailableForScratchUse {
@@ -375,8 +406,12 @@
         fPurgeableBytes -= resource->gpuMemorySize();
         fPurgeableQueue.remove(resource);
         this->addToNonpurgeableArray(resource);
+    } else if (!resource->cacheAccess().hasRef() &&
+               resource->resourcePriv().budgetedType() == GrBudgetedType::kBudgeted) {
+        SkASSERT(fNumBudgetedResourcesFlushWillMakePurgeable > 0);
+        fNumBudgetedResourcesFlushWillMakePurgeable--;
     }
-    resource->ref();
+    resource->cacheAccess().ref();
 
     resource->cacheAccess().setTimestamp(this->getNextTimestamp());
     this->validate();
@@ -403,6 +438,19 @@
 #endif
         resource->cacheAccess().setTimestamp(this->getNextTimestamp());
         SkDEBUGCODE(fNewlyPurgeableResourceForValidation = nullptr);
+        if (!resource->resourcePriv().isPurgeable() &&
+            resource->resourcePriv().budgetedType() == GrBudgetedType::kBudgeted) {
+            SkASSERT(resource->resourcePriv().hasPendingIO_debugOnly());
+            ++fNumBudgetedResourcesFlushWillMakePurgeable;
+        }
+    } else {
+        // If this is budgeted and just became purgeable by dropping the last pending IO
+        // then it clearly no longer needs a flush to become purgeable.
+        if (resource->resourcePriv().budgetedType() == GrBudgetedType::kBudgeted &&
+            resource->resourcePriv().isPurgeable()) {
+            SkASSERT(fNumBudgetedResourcesFlushWillMakePurgeable > 0);
+            fNumBudgetedResourcesFlushWillMakePurgeable--;
+        }
     }
 
     if (!SkToBool(ResourceAccess::kAllCntsReachedZero_RefNotificationFlag & flags)) {
@@ -474,11 +522,17 @@
         fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes);
         fBudgetedHighWaterCount = SkTMax(fBudgetedCount, fBudgetedHighWaterCount);
 #endif
+        if (!resource->resourcePriv().isPurgeable() && !resource->cacheAccess().hasRef()) {
+            ++fNumBudgetedResourcesFlushWillMakePurgeable;
+        }
         this->purgeAsNeeded();
     } else {
         SkASSERT(resource->resourcePriv().budgetedType() != GrBudgetedType::kUnbudgetedCacheable);
         --fBudgetedCount;
         fBudgetedBytes -= size;
+        if (!resource->resourcePriv().isPurgeable() && !resource->cacheAccess().hasRef()) {
+            --fNumBudgetedResourcesFlushWillMakePurgeable;
+        }
     }
     SkASSERT(wasPurgeable == resource->resourcePriv().isPurgeable());
     TRACE_COUNTER2("skia.gpu.cache", "skia budget", "used",
@@ -604,11 +658,20 @@
         fMaxBytes = cachedByteCount;
     }
 }
+bool GrResourceCache::requestsFlush() const {
+    return this->overBudget() && !fPurgeableQueue.count() &&
+           fNumBudgetedResourcesFlushWillMakePurgeable > 0;
+}
 
-void GrResourceCache::insertCrossContextGpuResource(GrGpuResource* resource) {
+
+void GrResourceCache::insertDelayedResourceUnref(GrGpuResource* resource) {
     resource->ref();
-    SkASSERT(!fResourcesWaitingForFreeMsg.contains(resource));
-    fResourcesWaitingForFreeMsg.push_back(resource);
+    uint32_t id = resource->uniqueID().asUInt();
+    if (auto* data = fResourcesAwaitingUnref.find(id)) {
+        data->addRef();
+    } else {
+        fResourcesAwaitingUnref.set(id, {resource});
+    }
 }
 
 void GrResourceCache::processFreedGpuResources() {
@@ -616,14 +679,17 @@
     fFreedGpuResourceInbox.poll(&msgs);
     for (int i = 0; i < msgs.count(); ++i) {
         SkASSERT(msgs[i].fOwningUniqueID == fContextUniqueID);
-        int index = fResourcesWaitingForFreeMsg.find(msgs[i].fResource);
+        uint32_t id = msgs[i].fResource->uniqueID().asUInt();
+        ResourceAwaitingUnref* info = fResourcesAwaitingUnref.find(id);
         // If we called release or abandon on the GrContext we will have already released our ref on
         // the GrGpuResource. If then the message arrives before the actual GrContext gets destroyed
         // we will try to process the message when we destroy the GrContext. This protects us from
         // trying to unref the resource twice.
-        if (index != -1) {
-            fResourcesWaitingForFreeMsg.removeShuffle(index);
-            msgs[i].fResource->unref();
+        if (info) {
+            info->unref();
+            if (info->finished()) {
+                fResourcesAwaitingUnref.remove(id);
+            }
         }
     }
 }
@@ -850,12 +916,19 @@
 
     Stats stats(this);
     size_t purgeableBytes = 0;
+    int numBudgetedResourcesFlushWillMakePurgeable = 0;
 
     for (int i = 0; i < fNonpurgeableResources.count(); ++i) {
         SkASSERT(!fNonpurgeableResources[i]->resourcePriv().isPurgeable() ||
                  fNewlyPurgeableResourceForValidation == fNonpurgeableResources[i]);
         SkASSERT(*fNonpurgeableResources[i]->cacheAccess().accessCacheIndex() == i);
         SkASSERT(!fNonpurgeableResources[i]->wasDestroyed());
+        if (fNonpurgeableResources[i]->resourcePriv().budgetedType() == GrBudgetedType::kBudgeted &&
+            !fNonpurgeableResources[i]->cacheAccess().hasRef() &&
+            fNewlyPurgeableResourceForValidation != fNonpurgeableResources[i]) {
+            SkASSERT(fNonpurgeableResources[i]->resourcePriv().hasPendingIO_debugOnly());
+            ++numBudgetedResourcesFlushWillMakePurgeable;
+        }
         stats.update(fNonpurgeableResources[i]);
     }
     for (int i = 0; i < fPurgeableQueue.count(); ++i) {
@@ -870,6 +943,8 @@
     SkASSERT(fBudgetedCount <= fCount);
     SkASSERT(fBudgetedBytes <= fBytes);
     SkASSERT(stats.fBytes == fBytes);
+    SkASSERT(fNumBudgetedResourcesFlushWillMakePurgeable ==
+             numBudgetedResourcesFlushWillMakePurgeable);
     SkASSERT(stats.fBudgetedBytes == fBudgetedBytes);
     SkASSERT(stats.fBudgetedCount == fBudgetedCount);
     SkASSERT(purgeableBytes == fPurgeableBytes);
diff --git a/src/gpu/GrResourceCache.h b/src/gpu/GrResourceCache.h
index c948864..a582648 100644
--- a/src/gpu/GrResourceCache.h
+++ b/src/gpu/GrResourceCache.h
@@ -16,6 +16,7 @@
 #include "SkRefCnt.h"
 #include "SkTArray.h"
 #include "SkTDPQueue.h"
+#include "SkTHash.h"
 #include "SkTInternalLList.h"
 #include "SkTMultiMap.h"
 
@@ -192,10 +193,10 @@
 
     /** Returns true if the cache would like a flush to occur in order to make more resources
         purgeable. */
-    bool requestsFlush() const { return this->overBudget() && !fPurgeableQueue.count(); }
+    bool requestsFlush() const;
 
     /** Maintain a ref to this resource until we receive a GrGpuResourceFreedMessage. */
-    void insertCrossContextGpuResource(GrGpuResource* resource);
+    void insertDelayedResourceUnref(GrGpuResource* resource);
 
 #if GR_CACHE_STATS
     struct Stats {
@@ -264,9 +265,10 @@
     void removeUniqueKey(GrGpuResource*);
     void willRemoveScratchKey(const GrGpuResource*);
     void didChangeBudgetStatus(GrGpuResource*);
-    void refAndMakeResourceMRU(GrGpuResource*);
+    void refResource(GrGpuResource* resource);
     /// @}
 
+    void refAndMakeResourceMRU(GrGpuResource*);
     void processFreedGpuResources();
     void addToNonpurgeableArray(GrGpuResource*);
     void removeFromNonpurgeableArray(GrGpuResource*);
@@ -305,6 +307,25 @@
     };
     typedef SkTDynamicHash<GrGpuResource, GrUniqueKey, UniqueHashTraits> UniqueHash;
 
+    class ResourceAwaitingUnref {
+    public:
+        ResourceAwaitingUnref();
+        ResourceAwaitingUnref(GrGpuResource* resource);
+        ResourceAwaitingUnref(const ResourceAwaitingUnref&) = delete;
+        ResourceAwaitingUnref& operator=(const ResourceAwaitingUnref&) = delete;
+        ResourceAwaitingUnref(ResourceAwaitingUnref&&);
+        ResourceAwaitingUnref& operator=(ResourceAwaitingUnref&&);
+        ~ResourceAwaitingUnref();
+        void addRef();
+        void unref();
+        bool finished();
+
+    private:
+        GrGpuResource* fResource = nullptr;
+        int fNumUnrefs = 0;
+    };
+    using ReourcesAwaitingUnref = SkTHashMap<uint32_t, ResourceAwaitingUnref>;
+
     static bool CompareTimestamp(GrGpuResource* const& a, GrGpuResource* const& b) {
         return a->cacheAccess().timestamp() < b->cacheAccess().timestamp();
     }
@@ -318,11 +339,11 @@
     typedef SkTDPQueue<GrGpuResource*, CompareTimestamp, AccessResourceIndex> PurgeableQueue;
     typedef SkTDArray<GrGpuResource*> ResourceArray;
 
-    GrProxyProvider*                    fProxyProvider;
+    GrProxyProvider*                    fProxyProvider = nullptr;
     // Whenever a resource is added to the cache or the result of a cache lookup, fTimestamp is
     // assigned as the resource's timestamp and then incremented. fPurgeableQueue orders the
     // purgeable resources by this value, and thus is used to purge resources in LRU order.
-    uint32_t                            fTimestamp;
+    uint32_t                            fTimestamp = 0;
     PurgeableQueue                      fPurgeableQueue;
     ResourceArray                       fNonpurgeableResources;
 
@@ -332,38 +353,38 @@
     UniqueHash                          fUniqueHash;
 
     // our budget, used in purgeAsNeeded()
-    int                                 fMaxCount;
-    size_t                              fMaxBytes;
+    int                                 fMaxCount = kDefaultMaxCount;
+    size_t                              fMaxBytes = kDefaultMaxSize;
 
 #if GR_CACHE_STATS
-    int                                 fHighWaterCount;
-    size_t                              fHighWaterBytes;
-    int                                 fBudgetedHighWaterCount;
-    size_t                              fBudgetedHighWaterBytes;
+    int                                 fHighWaterCount = 0;
+    size_t                              fHighWaterBytes = 0;
+    int                                 fBudgetedHighWaterCount = 0;
+    size_t                              fBudgetedHighWaterBytes = 0;
 #endif
 
     // our current stats for all resources
-    SkDEBUGCODE(int                     fCount;)
-    size_t                              fBytes;
+    SkDEBUGCODE(int                     fCount = 0;)
+    size_t                              fBytes = 0;
 
     // our current stats for resources that count against the budget
-    int                                 fBudgetedCount;
-    size_t                              fBudgetedBytes;
-    size_t                              fPurgeableBytes;
+    int                                 fBudgetedCount = 0;
+    size_t                              fBudgetedBytes = 0;
+    size_t                              fPurgeableBytes = 0;
+    int                                 fNumBudgetedResourcesFlushWillMakePurgeable = 0;
 
     InvalidUniqueKeyInbox               fInvalidUniqueKeyInbox;
     FreedGpuResourceInbox               fFreedGpuResourceInbox;
+    ReourcesAwaitingUnref               fResourcesAwaitingUnref;
 
-    SkTDArray<GrGpuResource*>           fResourcesWaitingForFreeMsg;
-
-    uint32_t                            fContextUniqueID;
-    GrSingleOwner*                      fSingleOwner;
+    uint32_t                            fContextUniqueID = SK_InvalidUniqueID;
+    GrSingleOwner*                      fSingleOwner = nullptr;
 
     // This resource is allowed to be in the nonpurgeable array for the sake of validate() because
     // we're in the midst of converting it to purgeable status.
-    SkDEBUGCODE(GrGpuResource*          fNewlyPurgeableResourceForValidation;)
+    SkDEBUGCODE(GrGpuResource*          fNewlyPurgeableResourceForValidation = nullptr;)
 
-    bool                                fPreferVRAMUseOverFlushes;
+    bool                                fPreferVRAMUseOverFlushes = false;
 };
 
 GR_MAKE_BITFIELD_CLASS_OPS(GrResourceCache::ScratchFlags);
@@ -385,6 +406,12 @@
     void removeResource(GrGpuResource* resource) { fCache->removeResource(resource); }
 
     /**
+     * Adds a ref to a resource with proper tracking if the resource has 0 refs prior to
+     * adding the ref.
+     */
+    void refResource(GrGpuResource* resource) { fCache->refResource(resource); }
+
+    /**
      * Notifications that should be sent to the cache when the ref/io cnt status of resources
      * changes.
      */
diff --git a/src/gpu/GrResourceProvider.cpp b/src/gpu/GrResourceProvider.cpp
index 4ba7de4..622cea2 100644
--- a/src/gpu/GrResourceProvider.cpp
+++ b/src/gpu/GrResourceProvider.cpp
@@ -6,13 +6,13 @@
  */
 
 #include "GrResourceProvider.h"
-
+#include "../private/GrSingleOwner.h"
 #include "GrBackendSemaphore.h"
-#include "GrBuffer.h"
 #include "GrCaps.h"
 #include "GrContext.h"
 #include "GrContextPriv.h"
 #include "GrGpu.h"
+#include "GrGpuBuffer.h"
 #include "GrPath.h"
 #include "GrPathRendering.h"
 #include "GrProxyProvider.h"
@@ -22,43 +22,24 @@
 #include "GrSemaphore.h"
 #include "GrStencilAttachment.h"
 #include "GrTexturePriv.h"
-#include "../private/GrSingleOwner.h"
 #include "SkGr.h"
 #include "SkMathPriv.h"
 
-GR_DECLARE_STATIC_UNIQUE_KEY(gQuadIndexBufferKey);
-
 const uint32_t GrResourceProvider::kMinScratchTextureSize = 16;
 
-#ifdef SK_DISABLE_EXPLICIT_GPU_RESOURCE_ALLOCATION
-static const bool kDefaultExplicitlyAllocateGPUResources = false;
-#else
-static const bool kDefaultExplicitlyAllocateGPUResources = true;
-#endif
-
 #define ASSERT_SINGLE_OWNER \
     SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(fSingleOwner);)
 
 GrResourceProvider::GrResourceProvider(GrGpu* gpu, GrResourceCache* cache, GrSingleOwner* owner,
-                                       GrContextOptions::Enable explicitlyAllocateGPUResources)
+                                       bool explicitlyAllocateGPUResources)
         : fCache(cache)
         , fGpu(gpu)
+        , fExplicitlyAllocateGPUResources(explicitlyAllocateGPUResources)
 #ifdef SK_DEBUG
         , fSingleOwner(owner)
 #endif
 {
-    if (GrContextOptions::Enable::kNo == explicitlyAllocateGPUResources) {
-        fExplicitlyAllocateGPUResources = false;
-    } else if (GrContextOptions::Enable::kYes == explicitlyAllocateGPUResources) {
-        fExplicitlyAllocateGPUResources = true;
-    } else {
-        fExplicitlyAllocateGPUResources = kDefaultExplicitlyAllocateGPUResources;
-    }
-
     fCaps = sk_ref_sp(fGpu->caps());
-
-    GR_DEFINE_STATIC_UNIQUE_KEY(gQuadIndexBufferKey);
-    fQuadIndexBufferKey = gQuadIndexBufferKey;
 }
 
 sk_sp<GrTexture> GrResourceProvider::createTexture(const GrSurfaceDesc& desc, SkBudgeted budgeted,
@@ -285,35 +266,34 @@
                                : sk_sp<GrGpuResource>(fCache->findAndRefUniqueResource(key));
 }
 
-sk_sp<const GrBuffer> GrResourceProvider::findOrMakeStaticBuffer(GrGpuBufferType intendedType,
-                                                                 size_t size,
-                                                                 const void* data,
-                                                                 const GrUniqueKey& key) {
-    if (auto buffer = this->findByUniqueKey<GrBuffer>(key)) {
+sk_sp<const GrGpuBuffer> GrResourceProvider::findOrMakeStaticBuffer(GrGpuBufferType intendedType,
+                                                                    size_t size,
+                                                                    const void* data,
+                                                                    const GrUniqueKey& key) {
+    if (auto buffer = this->findByUniqueKey<GrGpuBuffer>(key)) {
         return std::move(buffer);
     }
-    if (auto buffer = this->createBuffer(size, intendedType, kStatic_GrAccessPattern, Flags::kNone,
-                                         data)) {
+    if (auto buffer = this->createBuffer(size, intendedType, kStatic_GrAccessPattern, data)) {
         // We shouldn't bin and/or cache static buffers.
-        SkASSERT(buffer->sizeInBytes() == size);
+        SkASSERT(buffer->size() == size);
         SkASSERT(!buffer->resourcePriv().getScratchKey().isValid());
         SkASSERT(!buffer->resourcePriv().hasPendingIO_debugOnly());
         buffer->resourcePriv().setUniqueKey(key);
-        return sk_sp<const GrBuffer>(buffer);
+        return sk_sp<const GrGpuBuffer>(buffer);
     }
     return nullptr;
 }
 
-sk_sp<const GrBuffer> GrResourceProvider::createPatternedIndexBuffer(const uint16_t* pattern,
-                                                                     int patternSize,
-                                                                     int reps,
-                                                                     int vertCount,
-                                                                     const GrUniqueKey& key) {
+sk_sp<const GrGpuBuffer> GrResourceProvider::createPatternedIndexBuffer(const uint16_t* pattern,
+                                                                        int patternSize,
+                                                                        int reps,
+                                                                        int vertCount,
+                                                                        const GrUniqueKey* key) {
     size_t bufferSize = patternSize * reps * sizeof(uint16_t);
 
     // This is typically used in GrMeshDrawOps, so we assume kNoPendingIO.
-    sk_sp<GrBuffer> buffer(this->createBuffer(bufferSize, GrGpuBufferType::kIndex,
-                                              kStatic_GrAccessPattern, Flags::kNone));
+    sk_sp<GrGpuBuffer> buffer(
+            this->createBuffer(bufferSize, GrGpuBufferType::kIndex, kStatic_GrAccessPattern));
     if (!buffer) {
         return nullptr;
     }
@@ -337,16 +317,19 @@
     } else {
         buffer->unmap();
     }
-    this->assignUniqueKeyToResource(key, buffer.get());
+    if (key) {
+        SkASSERT(key->isValid());
+        this->assignUniqueKeyToResource(*key, buffer.get());
+    }
     return std::move(buffer);
 }
 
 static constexpr int kMaxQuads = 1 << 12;  // max possible: (1 << 14) - 1;
 
-sk_sp<const GrBuffer> GrResourceProvider::createQuadIndexBuffer() {
+sk_sp<const GrGpuBuffer> GrResourceProvider::createQuadIndexBuffer() {
     GR_STATIC_ASSERT(4 * kMaxQuads <= 65535);
     static const uint16_t kPattern[] = { 0, 1, 2, 2, 1, 3 };
-    return this->createPatternedIndexBuffer(kPattern, 6, kMaxQuads, 4, fQuadIndexBufferKey);
+    return this->createPatternedIndexBuffer(kPattern, 6, kMaxQuads, 4, nullptr);
 }
 
 int GrResourceProvider::QuadCountOfQuadBuffer() { return kMaxQuads; }
@@ -360,36 +343,24 @@
     return this->gpu()->pathRendering()->createPath(path, style);
 }
 
-sk_sp<GrBuffer> GrResourceProvider::createBuffer(size_t size, GrGpuBufferType intendedType,
-                                                 GrAccessPattern accessPattern, Flags flags,
-                                                 const void* data) {
+sk_sp<GrGpuBuffer> GrResourceProvider::createBuffer(size_t size, GrGpuBufferType intendedType,
+                                                    GrAccessPattern accessPattern,
+                                                    const void* data) {
     if (this->isAbandoned()) {
         return nullptr;
     }
     if (kDynamic_GrAccessPattern != accessPattern) {
         return this->gpu()->createBuffer(size, intendedType, accessPattern, data);
     }
-    if (!(flags & Flags::kRequireGpuMemory) &&
-        this->gpu()->caps()->preferClientSideDynamicBuffers() &&
-        GrBufferTypeIsVertexOrIndex(intendedType) &&
-        kDynamic_GrAccessPattern == accessPattern) {
-        return GrBuffer::MakeCPUBacked(this->gpu(), size, intendedType, data);
-    }
-
     // bin by pow2 with a reasonable min
     static const size_t MIN_SIZE = 1 << 12;
     size_t allocSize = SkTMax(MIN_SIZE, GrNextSizePow2(size));
 
     GrScratchKey key;
-    GrBuffer::ComputeScratchKeyForDynamicVBO(allocSize, intendedType, &key);
-    auto scratchFlags = GrResourceCache::ScratchFlags::kNone;
-    if (flags & Flags::kNoPendingIO) {
-        scratchFlags = GrResourceCache::ScratchFlags::kRequireNoPendingIO;
-    } else {
-        scratchFlags = GrResourceCache::ScratchFlags::kPreferNoPendingIO;
-    }
-    auto buffer = sk_sp<GrBuffer>(static_cast<GrBuffer*>(
-            this->cache()->findAndRefScratchResource(key, allocSize, scratchFlags)));
+    GrGpuBuffer::ComputeScratchKeyForDynamicVBO(allocSize, intendedType, &key);
+    auto buffer =
+            sk_sp<GrGpuBuffer>(static_cast<GrGpuBuffer*>(this->cache()->findAndRefScratchResource(
+                    key, allocSize, GrResourceCache::ScratchFlags::kNone)));
     if (!buffer) {
         buffer = this->gpu()->createBuffer(allocSize, intendedType, kDynamic_GrAccessPattern);
         if (!buffer) {
@@ -399,7 +370,6 @@
     if (data) {
         buffer->updateData(data, size);
     }
-    SkASSERT(!buffer->isCPUBacked()); // We should only cache real VBOs.
     return buffer;
 }
 
diff --git a/src/gpu/GrResourceProvider.h b/src/gpu/GrResourceProvider.h
index 9005a96..1a9924b 100644
--- a/src/gpu/GrResourceProvider.h
+++ b/src/gpu/GrResourceProvider.h
@@ -8,8 +8,8 @@
 #ifndef GrResourceProvider_DEFINED
 #define GrResourceProvider_DEFINED
 
-#include "GrBuffer.h"
 #include "GrContextOptions.h"
+#include "GrGpuBuffer.h"
 #include "GrResourceCache.h"
 #include "SkImageInfoPriv.h"
 #include "SkScalerContext.h"
@@ -51,22 +51,19 @@
          *  Make this automatic: https://bug.skia.org/4156
          */
         kNoPendingIO     = 0x1,
-
-        /** Normally the caps may indicate a preference for client-side buffers. Set this flag when
-         *  creating a buffer to guarantee it resides in GPU memory.
-         */
-        kRequireGpuMemory = 0x2,
     };
 
     GrResourceProvider(GrGpu*, GrResourceCache*, GrSingleOwner*,
-                       GrContextOptions::Enable explicitlyAllocateGPUResources);
+                       bool explicitlyAllocateGPUResources);
 
     /**
      * Finds a resource in the cache, based on the specified key. Prior to calling this, the caller
      * must be sure that if a resource of exists in the cache with the given unique key then it is
      * of type T.
      */
-    template <typename T = GrGpuResource> sk_sp<T> findByUniqueKey(const GrUniqueKey& key) {
+    template <typename T = GrGpuResource>
+    typename std::enable_if<std::is_base_of<GrGpuResource, T>::value, sk_sp<T>>::type
+    findByUniqueKey(const GrUniqueKey& key) {
         return sk_sp<T>(static_cast<T*>(this->findResourceByUniqueKey(key).release()));
     }
 
@@ -145,8 +142,8 @@
      *
      * @return The buffer if successful, otherwise nullptr.
      */
-    sk_sp<const GrBuffer> findOrMakeStaticBuffer(GrGpuBufferType intendedType, size_t size,
-                                                 const void* data, const GrUniqueKey& key);
+    sk_sp<const GrGpuBuffer> findOrMakeStaticBuffer(GrGpuBufferType intendedType, size_t size,
+                                                    const void* data, const GrUniqueKey& key);
 
     /**
      * Either finds and refs, or creates an index buffer with a repeating pattern for drawing
@@ -161,15 +158,15 @@
      *
      * @return The index buffer if successful, otherwise nullptr.
      */
-    sk_sp<const GrBuffer> findOrCreatePatternedIndexBuffer(const uint16_t* pattern,
-                                                           int patternSize,
-                                                           int reps,
-                                                           int vertCount,
-                                                           const GrUniqueKey& key) {
-        if (auto buffer = this->findByUniqueKey<GrBuffer>(key)) {
-            return std::move(buffer);
+    sk_sp<const GrGpuBuffer> findOrCreatePatternedIndexBuffer(const uint16_t* pattern,
+                                                              int patternSize,
+                                                              int reps,
+                                                              int vertCount,
+                                                              const GrUniqueKey& key) {
+        if (auto buffer = this->findByUniqueKey<const GrGpuBuffer>(key)) {
+            return buffer;
         }
-        return this->createPatternedIndexBuffer(pattern, patternSize, reps, vertCount, key);
+        return this->createPatternedIndexBuffer(pattern, patternSize, reps, vertCount, &key);
     }
 
     /**
@@ -179,11 +176,11 @@
      * Draw with GrPrimitiveType::kTriangles
      * @ return the quad index buffer
      */
-    sk_sp<const GrBuffer> refQuadIndexBuffer() {
-        if (auto buffer = this->findByUniqueKey<const GrBuffer>(fQuadIndexBufferKey)) {
-            return buffer;
+    sk_sp<const GrGpuBuffer> refQuadIndexBuffer() {
+        if (!fQuadIndexBuffer) {
+            fQuadIndexBuffer = this->createQuadIndexBuffer();
         }
-        return this->createQuadIndexBuffer();
+        return fQuadIndexBuffer;
     }
 
     static int QuadCountOfQuadBuffer();
@@ -205,8 +202,8 @@
      *
      * @return the buffer if successful, otherwise nullptr.
      */
-    sk_sp<GrBuffer> createBuffer(size_t size, GrGpuBufferType intendedType, GrAccessPattern, Flags,
-                                 const void* data = nullptr);
+    sk_sp<GrGpuBuffer> createBuffer(size_t size, GrGpuBufferType intendedType, GrAccessPattern,
+                                    const void* data = nullptr);
 
     /**
      * If passed in render target already has a stencil buffer, return true. Otherwise attempt to
@@ -286,19 +283,19 @@
         return !SkToBool(fCache);
     }
 
-    sk_sp<const GrBuffer> createPatternedIndexBuffer(const uint16_t* pattern,
-                                                     int patternSize,
-                                                     int reps,
-                                                     int vertCount,
-                                                     const GrUniqueKey& key);
+    sk_sp<const GrGpuBuffer> createPatternedIndexBuffer(const uint16_t* pattern,
+                                                        int patternSize,
+                                                        int reps,
+                                                        int vertCount,
+                                                        const GrUniqueKey* key);
 
-    sk_sp<const GrBuffer> createQuadIndexBuffer();
+    sk_sp<const GrGpuBuffer> createQuadIndexBuffer();
 
-    GrResourceCache*    fCache;
-    GrGpu*              fGpu;
+    GrResourceCache* fCache;
+    GrGpu* fGpu;
     sk_sp<const GrCaps> fCaps;
-    GrUniqueKey         fQuadIndexBufferKey;
-    bool                fExplicitlyAllocateGPUResources;
+    sk_sp<const GrGpuBuffer> fQuadIndexBuffer;
+    bool fExplicitlyAllocateGPUResources;
 
     // In debug builds we guard against improper thread handling
     SkDEBUGCODE(mutable GrSingleOwner* fSingleOwner;)
diff --git a/src/gpu/GrSWMaskHelper.cpp b/src/gpu/GrSWMaskHelper.cpp
index 2f21a67..c02818a 100644
--- a/src/gpu/GrSWMaskHelper.cpp
+++ b/src/gpu/GrSWMaskHelper.cpp
@@ -7,9 +7,10 @@
 
 #include "GrSWMaskHelper.h"
 
-#include "GrContext.h"
-#include "GrContextPriv.h"
+#include "GrCaps.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrShape.h"
 #include "GrSurfaceContext.h"
 #include "GrTextureProxy.h"
@@ -91,7 +92,8 @@
     return true;
 }
 
-sk_sp<GrTextureProxy> GrSWMaskHelper::toTextureProxy(GrContext* context, SkBackingFit fit) {
+sk_sp<GrTextureProxy> GrSWMaskHelper::toTextureProxy(GrRecordingContext* context,
+                                                     SkBackingFit fit) {
     SkImageInfo ii = SkImageInfo::MakeA8(fPixels->width(), fPixels->height());
     size_t rowBytes = fPixels->rowBytes();
 
@@ -108,21 +110,15 @@
     // TODO: http://skbug.com/8422: Although this fixes http://skbug.com/8351, it seems like these
     // should just participate in the normal allocation process and not need the pending IO flag.
     auto surfaceFlags = GrInternalSurfaceFlags::kNone;
-    if (!context->priv().resourceProvider()) {
+    if (!context->priv().proxyProvider()->renderingDirectly()) {
         // In DDL mode, this texture proxy will be instantiated at flush time, therfore it cannot
         // have pending IO.
         surfaceFlags |= GrInternalSurfaceFlags::kNoPendingIO;
     }
     auto clearFlag = kNone_GrSurfaceFlags;
-    // In a WASM build on Firefox, we see warnings like
-    // WebGL warning: texSubImage2D: This operation requires zeroing texture data. This is slow.
-    // WebGL warning: texSubImage2D: Texture has not been initialized prior to a partial upload,
-    //                forcing the browser to clear it. This may be slow.
-    // Setting the initial clear seems to make those warnings go away and offers a substantial
-    // boost in performance in Firefox. Chrome sees a more modest increase.
-#if IS_WEBGL==1
-    clearFlag = kPerformInitialClear_GrSurfaceFlag;
-#endif
+    if (context->priv().caps()->shouldInitializeTextures() && fit == SkBackingFit::kApprox) {
+        clearFlag = kPerformInitialClear_GrSurfaceFlag;
+    }
     return context->priv().proxyProvider()->createTextureProxy(
             std::move(img), clearFlag, 1, SkBudgeted::kYes, fit, surfaceFlags);
 }
diff --git a/src/gpu/GrSWMaskHelper.h b/src/gpu/GrSWMaskHelper.h
index 1456d40..c2df5db 100644
--- a/src/gpu/GrSWMaskHelper.h
+++ b/src/gpu/GrSWMaskHelper.h
@@ -17,6 +17,7 @@
 #include "SkTypes.h"
 
 class GrShape;
+class GrRecordingContext;
 class GrTextureProxy;
 
 /**
@@ -50,7 +51,7 @@
     // Draw a single path into the accumuation bitmap using the specified op
     void drawShape(const GrShape&, const SkMatrix& matrix, SkRegion::Op op, GrAA, uint8_t alpha);
 
-    sk_sp<GrTextureProxy> toTextureProxy(GrContext*, SkBackingFit fit);
+    sk_sp<GrTextureProxy> toTextureProxy(GrRecordingContext*, SkBackingFit fit);
 
     // Reset the internal bitmap
     void clear(uint8_t alpha) {
diff --git a/src/gpu/GrSamplePatternDictionary.cpp b/src/gpu/GrSamplePatternDictionary.cpp
new file mode 100644
index 0000000..453ed45
--- /dev/null
+++ b/src/gpu/GrSamplePatternDictionary.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrSamplePatternDictionary.h"
+
+bool GrSamplePatternDictionary::LessThan::operator()(
+        const SkTArray<SkPoint>& a, const SkTArray<SkPoint>& b) const {
+    if (a.count() != b.count()) {
+        return a.count() < b.count();
+    }
+    for (int i = 0; i < a.count(); ++i) {
+        // This doesn't have geometric meaning. We just need to define an ordering for std::map.
+        if (a[i].x() != b[i].x()) {
+            return a[i].x() < b[i].x();
+        }
+        if (a[i].y() != b[i].y()) {
+            return a[i].y() < b[i].y();
+        }
+    }
+    return false;  // Both sample patterns are equal, therefore, "a < b" is false.
+}
+
+int GrSamplePatternDictionary::findOrAssignSamplePatternKey(
+        const SkTArray<SkPoint>& sampleLocations) {
+    if (std::numeric_limits<int>::max() == fSampleLocationsArray.count()) {
+        return 0;
+    }
+    const auto& insertResult = fSamplePatternKeyMap.insert(
+            {sampleLocations, fSampleLocationsArray.count()});
+    if (insertResult.second) {
+        // This means the "insert" call did not find the pattern in the key map already, and
+        // therefore an actual insertion took place. (We don't expect to see many unique sample
+        // patterns.)
+        const SkTArray<SkPoint>& sampleLocations = insertResult.first->first;
+        fSampleLocationsArray.push_back(&sampleLocations);
+    }
+    return insertResult.first->second;  // Return the new sample pattern key.
+}
diff --git a/src/gpu/GrSamplePatternDictionary.h b/src/gpu/GrSamplePatternDictionary.h
new file mode 100644
index 0000000..5d6fb28
--- /dev/null
+++ b/src/gpu/GrSamplePatternDictionary.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrSamplePatternDictionary_DEFINED
+#define GrSamplePatternDictionary_DEFINED
+
+#include "SkPoint.h"
+#include "SkTArray.h"
+#include <map>
+
+/**
+ * A bidirectional dictionary mapping between sample patterns (i.e., a list of sample locations) and
+ * unique keys. Since we expect that most render targets will draw from the same small pool of
+ * sample patterns, we favor sample pattern keys over actual arrays of points.
+ */
+class GrSamplePatternDictionary {
+public:
+    static constexpr int kInvalidSamplePatternKey = -1;
+
+    int findOrAssignSamplePatternKey(const SkTArray<SkPoint>& sampleLocations);
+
+    const SkTArray<SkPoint>& retrieveSampleLocations(int samplePatternKey) const {
+        return *fSampleLocationsArray[samplePatternKey];
+    }
+
+private:
+    struct LessThan {
+        bool operator()(const SkTArray<SkPoint>&, const SkTArray<SkPoint>&) const;
+    };
+
+    std::map<SkTArray<SkPoint>, int, LessThan> fSamplePatternKeyMap;
+    SkTArray<const SkTArray<SkPoint>*> fSampleLocationsArray;
+};
+
+#endif
diff --git a/src/gpu/GrShaderCaps.cpp b/src/gpu/GrShaderCaps.cpp
index 43a194d..56dbc23 100644
--- a/src/gpu/GrShaderCaps.cpp
+++ b/src/gpu/GrShaderCaps.cpp
@@ -43,14 +43,17 @@
     fEmulateAbsIntFunction = false;
     fRewriteDoWhileLoops = false;
     fRemovePowWithConstantExponent = false;
+    fMustWriteToFragColor = false;
     fFlatInterpolationSupport = false;
     fPreferFlatInterpolation = false;
     fNoPerspectiveInterpolationSupport = false;
+    fSampleVariablesSupport = false;
     fExternalTextureSupport = false;
     fVertexIDSupport = false;
     fFPManipulationSupport = false;
     fFloatIs32Bits = true;
     fHalfIs32Bits = false;
+    fHasLowFragmentPrecision = false;
     fUnsignedSupport = false;
     fBuiltinFMASupport = false;
 
@@ -63,6 +66,7 @@
     fExternalTextureExtensionString = nullptr;
     fSecondExternalTextureExtensionString = nullptr;
     fNoPerspectiveInterpolationExtensionString = nullptr;
+    fSampleVariablesExtensionString = nullptr;
     fFBFetchColorName = nullptr;
     fFBFetchExtensionString = nullptr;
     fImageLoadStoreExtensionString = nullptr;
@@ -114,14 +118,17 @@
     writer->appendBool("Emulate abs(int) function", fEmulateAbsIntFunction);
     writer->appendBool("Rewrite do while loops", fRewriteDoWhileLoops);
     writer->appendBool("Rewrite pow with constant exponent", fRemovePowWithConstantExponent);
+    writer->appendBool("Must write to sk_FragColor [workaround]", fMustWriteToFragColor);
     writer->appendBool("Flat interpolation support", fFlatInterpolationSupport);
     writer->appendBool("Prefer flat interpolation", fPreferFlatInterpolation);
     writer->appendBool("No perspective interpolation support", fNoPerspectiveInterpolationSupport);
+    writer->appendBool("Sample variables support", fSampleVariablesSupport);
     writer->appendBool("External texture support", fExternalTextureSupport);
     writer->appendBool("sk_VertexID support", fVertexIDSupport);
     writer->appendBool("Floating point manipulation support", fFPManipulationSupport);
     writer->appendBool("float == fp32", fFloatIs32Bits);
     writer->appendBool("half == fp32", fHalfIs32Bits);
+    writer->appendBool("Has poor fragment precision", fHasLowFragmentPrecision);
     writer->appendBool("Builtin fma() support", fBuiltinFMASupport);
 
     writer->appendS32("Max FS Samplers", fMaxFragmentSamplers);
@@ -152,6 +159,7 @@
         SkASSERT(!fEmulateAbsIntFunction);
         SkASSERT(!fRewriteDoWhileLoops);
         SkASSERT(!fRemovePowWithConstantExponent);
+        SkASSERT(!fMustWriteToFragColor);
     }
 #if GR_TEST_UTILS
     fDualSourceBlendingSupport = fDualSourceBlendingSupport && !options.fSuppressDualSourceBlending;
diff --git a/src/gpu/GrShaderCaps.h b/src/gpu/GrShaderCaps.h
index fb1b17f..bbac42d 100644
--- a/src/gpu/GrShaderCaps.h
+++ b/src/gpu/GrShaderCaps.h
@@ -73,6 +73,8 @@
 
     bool noperspectiveInterpolationSupport() const { return fNoPerspectiveInterpolationSupport; }
 
+    bool sampleVariablesSupport() const { return fSampleVariablesSupport; }
+
     bool externalTextureSupport() const { return fExternalTextureSupport; }
 
     bool vertexIDSupport() const { return fVertexIDSupport; }
@@ -84,6 +86,8 @@
 
     bool halfIs32Bits() const { return fHalfIs32Bits; }
 
+    bool hasLowFragmentPrecision() const { return fHasLowFragmentPrecision; }
+
     bool unsignedSupport() const { return fUnsignedSupport; }
 
     // SkSL only.
@@ -150,6 +154,10 @@
         return fMustGuardDivisionEvenAfterExplicitZeroCheck;
     }
 
+    // On Nexus 6, the GL context can get lost if a shader does not write a value to gl_FragColor.
+    // https://bugs.chromium.org/p/chromium/issues/detail?id=445377
+    bool mustWriteToFragColor() const { return fMustWriteToFragColor; }
+
     // Returns the string of an extension that must be enabled in the shader to support
     // derivatives. If nullptr is returned then no extension needs to be enabled. Before calling
     // this function, the caller should check that shaderDerivativeSupport exists.
@@ -207,6 +215,11 @@
         return fNoPerspectiveInterpolationExtensionString;
     }
 
+    const char* sampleVariablesExtensionString() const {
+        SkASSERT(this->sampleVariablesSupport());
+        return fSampleVariablesExtensionString;
+    }
+
     const char* imageLoadStoreExtensionString() const {
         SkASSERT(this->imageLoadStoreSupport());
         return fImageLoadStoreExtensionString;
@@ -250,11 +263,13 @@
     bool fFlatInterpolationSupport          : 1;
     bool fPreferFlatInterpolation           : 1;
     bool fNoPerspectiveInterpolationSupport : 1;
+    bool fSampleVariablesSupport            : 1;
     bool fExternalTextureSupport            : 1;
     bool fVertexIDSupport                   : 1;
     bool fFPManipulationSupport             : 1;
     bool fFloatIs32Bits                     : 1;
     bool fHalfIs32Bits                      : 1;
+    bool fHasLowFragmentPrecision           : 1;
     bool fUnsignedSupport                   : 1;
 
     // Used by SkSL to know when to generate polyfills.
@@ -277,6 +292,7 @@
     bool fEmulateAbsIntFunction                       : 1;
     bool fRewriteDoWhileLoops                         : 1;
     bool fRemovePowWithConstantExponent               : 1;
+    bool fMustWriteToFragColor                        : 1;
 
     const char* fVersionDeclString;
 
@@ -288,6 +304,7 @@
     const char* fExternalTextureExtensionString;
     const char* fSecondExternalTextureExtensionString;
     const char* fNoPerspectiveInterpolationExtensionString;
+    const char* fSampleVariablesExtensionString;
     const char* fImageLoadStoreExtensionString;
 
     const char* fFBFetchColorName;
@@ -295,8 +312,6 @@
 
     int fMaxFragmentSamplers;
 
-    size_t fDisableImageMultitexturingDstRectAreaThreshold;
-
     AdvBlendEqInteraction fAdvBlendEqInteraction;
 
     GrSwizzle fConfigTextureSwizzle[kGrPixelConfigCnt];
diff --git a/src/gpu/GrShaderVar.cpp b/src/gpu/GrShaderVar.cpp
index 61cd3c9..269ba83 100644
--- a/src/gpu/GrShaderVar.cpp
+++ b/src/gpu/GrShaderVar.cpp
@@ -35,25 +35,7 @@
     SK_ABORT("Unknown io type.");
 }
 
-// Converts a GrSLPrecision to its corresponding GLSL precision qualifier. TODO: Remove this as we
-// shouldn't need it with SkSL.
-static inline const char* glsl_precision_string(GrSLPrecision p) {
-    switch (p) {
-        case kLow_GrSLPrecision:
-            return "lowp";
-        case kMedium_GrSLPrecision:
-            return "mediump";
-        case kHigh_GrSLPrecision:
-            return "highp";
-        case kDefault_GrSLPrecision:
-            return "";
-    }
-    SK_ABORT("Unexpected precision type.");
-    return "";
-}
-
 void GrShaderVar::appendDecl(const GrShaderCaps* shaderCaps, SkString* out) const {
-    SkASSERT(kDefault_GrSLPrecision == fPrecision || GrSLTypeTemporarilyAcceptsPrecision(fType));
     SkString layout = fLayoutQualifier;
     if (!fLayoutQualifier.isEmpty()) {
         out->appendf("layout(%s) ", fLayoutQualifier.c_str());
@@ -64,25 +46,17 @@
         out->append(" ");
     }
     GrSLType effectiveType = this->getType();
-    if (shaderCaps->usesPrecisionModifiers() && GrSLTypeAcceptsPrecision(effectiveType)) {
-        // Desktop GLSL has added precision qualifiers but they don't do anything.
-        out->appendf("%s ", glsl_precision_string(fPrecision));
-    }
     if (this->isArray()) {
         if (this->isUnsizedArray()) {
-            out->appendf("%s %s[]",
-                         GrGLSLTypeString(shaderCaps, effectiveType),
-                         this->getName().c_str());
+            out->appendf("%s %s[]", GrGLSLTypeString(effectiveType), this->getName().c_str());
         } else {
             SkASSERT(this->getArrayCount() > 0);
             out->appendf("%s %s[%d]",
-                         GrGLSLTypeString(shaderCaps, effectiveType),
+                         GrGLSLTypeString(effectiveType),
                          this->getName().c_str(),
                          this->getArrayCount());
         }
     } else {
-        out->appendf("%s %s",
-                     GrGLSLTypeString(shaderCaps, effectiveType),
-                     this->getName().c_str());
+        out->appendf("%s %s", GrGLSLTypeString(effectiveType), this->getName().c_str());
     }
 }
diff --git a/src/gpu/GrShaderVar.h b/src/gpu/GrShaderVar.h
index 0f22085..ef0d830 100644
--- a/src/gpu/GrShaderVar.h
+++ b/src/gpu/GrShaderVar.h
@@ -46,47 +46,39 @@
         , fUseUniformFloatArrays(USE_UNIFORM_FLOAT_ARRAYS) {
     }
 
-    GrShaderVar(const SkString& name, GrSLType type, int arrayCount = kNonArray,
-                GrSLPrecision precision = kDefault_GrSLPrecision)
+    GrShaderVar(const SkString& name, GrSLType type, int arrayCount = kNonArray)
         : fType(type)
         , fTypeModifier(kNone_TypeModifier)
         , fCount(arrayCount)
-        , fPrecision(precision)
         , fUseUniformFloatArrays(USE_UNIFORM_FLOAT_ARRAYS)
         , fName(name) {
         SkASSERT(kVoid_GrSLType != type);
         fUseUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS;
     }
 
-    GrShaderVar(const char* name, GrSLType type, int arrayCount = kNonArray,
-                GrSLPrecision precision = kDefault_GrSLPrecision)
+    GrShaderVar(const char* name, GrSLType type, int arrayCount = kNonArray)
         : fType(type)
         , fTypeModifier(kNone_TypeModifier)
         , fCount(arrayCount)
-        , fPrecision(precision)
         , fUseUniformFloatArrays(USE_UNIFORM_FLOAT_ARRAYS)
         , fName(name) {
         SkASSERT(kVoid_GrSLType != type);
         fUseUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS;
     }
 
-    GrShaderVar(const char* name, GrSLType type, TypeModifier typeModifier,
-                GrSLPrecision precision = kDefault_GrSLPrecision)
+    GrShaderVar(const char* name, GrSLType type, TypeModifier typeModifier)
         : fType(type)
         , fTypeModifier(typeModifier)
         , fCount(kNonArray)
-        , fPrecision(precision)
         , fUseUniformFloatArrays(USE_UNIFORM_FLOAT_ARRAYS)
         , fName(name) {
         SkASSERT(kVoid_GrSLType != type);
     }
 
-    GrShaderVar(const char* name, GrSLType type, TypeModifier typeModifier,
-                int arrayCount, GrSLPrecision precision = kDefault_GrSLPrecision)
+    GrShaderVar(const char* name, GrSLType type, TypeModifier typeModifier, int arrayCount)
         : fType(type)
         , fTypeModifier(typeModifier)
         , fCount(arrayCount)
-        , fPrecision(precision)
         , fUseUniformFloatArrays(USE_UNIFORM_FLOAT_ARRAYS)
         , fName(name) {
         SkASSERT(kVoid_GrSLType != type);
@@ -96,7 +88,6 @@
         : fType(that.fType)
         , fTypeModifier(that.fTypeModifier)
         , fCount(that.fCount)
-        , fPrecision(that.fPrecision)
         , fUseUniformFloatArrays(USE_UNIFORM_FLOAT_ARRAYS)
         , fName(that.fName)
         , fLayoutQualifier(that.fLayoutQualifier)
@@ -110,17 +101,14 @@
     void set(GrSLType type,
              const SkString& name,
              TypeModifier typeModifier = kNone_TypeModifier,
-             GrSLPrecision precision = kDefault_GrSLPrecision,
              const char* layoutQualifier = nullptr,
              const char* extraModifiers = nullptr,
              bool useUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS) {
         SkASSERT(kVoid_GrSLType != type);
-        SkASSERT(kDefault_GrSLPrecision == precision || GrSLTypeTemporarilyAcceptsPrecision(type));
         fType = type;
         fTypeModifier = typeModifier;
         fName = name;
         fCount = kNonArray;
-        fPrecision = precision;
         fLayoutQualifier = layoutQualifier;
         if (extraModifiers) {
             fExtraModifiers.printf("%s ", extraModifiers);
@@ -134,17 +122,14 @@
     void set(GrSLType type,
              const char* name,
              TypeModifier typeModifier = kNone_TypeModifier,
-             GrSLPrecision precision = kDefault_GrSLPrecision,
              const char* layoutQualifier = nullptr,
              const char* extraModifiers = nullptr,
              bool useUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS) {
         SkASSERT(kVoid_GrSLType != type);
-        SkASSERT(kDefault_GrSLPrecision == precision || GrSLTypeTemporarilyAcceptsPrecision(type));
         fType = type;
         fTypeModifier = typeModifier;
         fName = name;
         fCount = kNonArray;
-        fPrecision = precision;
         fLayoutQualifier = layoutQualifier;
         if (extraModifiers) {
             fExtraModifiers.printf("%s ", extraModifiers);
@@ -159,17 +144,14 @@
              const SkString& name,
              int count,
              TypeModifier typeModifier,
-             GrSLPrecision precision = kDefault_GrSLPrecision,
              const char* layoutQualifier = nullptr,
              const char* extraModifiers = nullptr,
              bool useUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS) {
         SkASSERT(kVoid_GrSLType != type);
-        SkASSERT(kDefault_GrSLPrecision == precision || GrSLTypeTemporarilyAcceptsPrecision(type));
         fType = type;
         fTypeModifier = typeModifier;
         fName = name;
         fCount = count;
-        fPrecision = precision;
         fLayoutQualifier = layoutQualifier;
         if (extraModifiers) {
             fExtraModifiers.printf("%s ", extraModifiers);
@@ -184,17 +166,14 @@
              const char* name,
              int count,
              TypeModifier typeModifier,
-             GrSLPrecision precision = kDefault_GrSLPrecision,
              const char* layoutQualifier = nullptr,
              const char* extraModifiers = nullptr,
              bool useUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS) {
         SkASSERT(kVoid_GrSLType != type);
-        SkASSERT(kDefault_GrSLPrecision == precision || GrSLTypeTemporarilyAcceptsPrecision(type));
         fType = type;
         fTypeModifier = typeModifier;
         fName = name;
         fCount = count;
-        fPrecision = precision;
         fLayoutQualifier = layoutQualifier;
         if (extraModifiers) {
             fExtraModifiers.printf("%s ", extraModifiers);
@@ -260,16 +239,6 @@
     void setTypeModifier(TypeModifier type) { fTypeModifier = type; }
 
     /**
-     * Get the precision of the var
-     */
-    GrSLPrecision getPrecision() const { return fPrecision; }
-
-    /**
-     * Set the precision of the var
-     */
-    void setPrecision(GrSLPrecision p) { fPrecision = p; }
-
-    /**
      * Appends to the layout qualifier
      */
     void addLayoutQualifier(const char* layoutQualifier) {
@@ -314,7 +283,6 @@
     GrSLType        fType;
     TypeModifier    fTypeModifier;
     int             fCount;
-    GrSLPrecision   fPrecision;
     /// Work around driver bugs on some hardware that don't correctly
     /// support uniform float []
     bool            fUseUniformFloatArrays;
diff --git a/src/gpu/GrSoftwarePathRenderer.cpp b/src/gpu/GrSoftwarePathRenderer.cpp
index 9fd8ecd..2fee0fb 100644
--- a/src/gpu/GrSoftwarePathRenderer.cpp
+++ b/src/gpu/GrSoftwarePathRenderer.cpp
@@ -7,6 +7,7 @@
 
 #include "GrSoftwarePathRenderer.h"
 #include "GrAuditTrail.h"
+#include "GrCaps.h"
 #include "GrClip.h"
 #include "GrContextPriv.h"
 #include "GrDeferredProxyUploader.h"
@@ -14,6 +15,7 @@
 #include "GrOpFlushState.h"
 #include "GrOpList.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContextPriv.h"
 #include "GrSWMaskHelper.h"
 #include "GrShape.h"
 #include "GrSurfaceContextPriv.h"
@@ -30,7 +32,7 @@
     // Pass on any style that applies. The caller will apply the style if a suitable renderer is
     // not found and try again with the new GrShape.
     if (!args.fShape->style().applies() && SkToBool(fProxyProvider) &&
-        (args.fAAType == GrAAType::kCoverage || args.fAAType == GrAAType::kNone)) {
+        ((args.fAATypeFlags & AATypeFlags::kCoverage) || args.fAATypeFlags == AATypeFlags::kNone)) {
         // This is the fallback renderer for when a path is too complicated for the GPU ones.
         return CanDrawPath::kAsBackup;
     }
@@ -98,7 +100,7 @@
                                            const SkMatrix& viewMatrix,
                                            const SkRect& rect,
                                            const SkMatrix& localMatrix) {
-    GrContext* context = renderTargetContext->surfPriv().getContext();
+    auto context = renderTargetContext->surfPriv().getContext();
     renderTargetContext->addDrawOp(clip,
                                    GrFillRectOp::MakeWithLocalMatrix(
                                            context, std::move(paint), GrAAType::kNone, viewMatrix,
@@ -172,7 +174,8 @@
                   dstRect, invert);
 }
 
-static sk_sp<GrTextureProxy> make_deferred_mask_texture_proxy(GrContext* context, SkBackingFit fit,
+static sk_sp<GrTextureProxy> make_deferred_mask_texture_proxy(GrRecordingContext* context,
+                                                              SkBackingFit fit,
                                                               int width, int height) {
     GrProxyProvider* proxyProvider = context->priv().proxyProvider();
 
@@ -254,7 +257,7 @@
     // To prevent overloading the cache with entries during animations we limit the cache of masks
     // to cases where the matrix preserves axis alignment.
     bool useCache = fAllowCaching && !inverseFilled && args.fViewMatrix->preservesAxisAlignment() &&
-                    args.fShape->hasUnstyledKey() && GrAAType::kCoverage == args.fAAType;
+                    args.fShape->hasUnstyledKey() && (AATypeFlags::kCoverage & args.fAATypeFlags);
 
     if (!GetShapeAndClipBounds(args.fRenderTargetContext,
                                *args.fClip, *args.fShape,
@@ -329,9 +332,13 @@
     }
     if (!proxy) {
         SkBackingFit fit = useCache ? SkBackingFit::kExact : SkBackingFit::kApprox;
-        GrAA aa = GrAAType::kCoverage == args.fAAType ? GrAA::kYes : GrAA::kNo;
+        GrAA aa = GrAA(SkToBool(AATypeFlags::kCoverage & args.fAATypeFlags));
 
-        SkTaskGroup* taskGroup = args.fContext->priv().getTaskGroup();
+        SkTaskGroup* taskGroup = nullptr;
+        if (auto direct = args.fContext->priv().asDirectContext()) {
+            taskGroup = direct->priv().getTaskGroup();
+        }
+
         if (taskGroup) {
             proxy = make_deferred_mask_texture_proxy(args.fContext, fit,
                                                      boundsForMask->width(),
diff --git a/src/gpu/GrSurfaceContext.cpp b/src/gpu/GrSurfaceContext.cpp
index 75059d4..ae44eb9 100644
--- a/src/gpu/GrSurfaceContext.cpp
+++ b/src/gpu/GrSurfaceContext.cpp
@@ -9,39 +9,50 @@
 #include "GrContextPriv.h"
 #include "GrDrawingManager.h"
 #include "GrOpList.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "SkGr.h"
 #include "../private/GrAuditTrail.h"
 
 #define ASSERT_SINGLE_OWNER \
     SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(this->singleOwner());)
-#define RETURN_FALSE_IF_ABANDONED  if (this->drawingManager()->wasAbandoned()) { return false; }
+#define RETURN_FALSE_IF_ABANDONED  if (this->fContext->priv().abandoned()) { return false; }
 
 // In MDB mode the reffing of the 'getLastOpList' call's result allows in-progress
 // GrOpLists to be picked up and added to by renderTargetContexts lower in the call
 // stack. When this occurs with a closed GrOpList, a new one will be allocated
 // when the renderTargetContext attempts to use it (via getOpList).
-GrSurfaceContext::GrSurfaceContext(GrContext* context,
-                                   GrDrawingManager* drawingMgr,
+GrSurfaceContext::GrSurfaceContext(GrRecordingContext* context,
                                    GrPixelConfig config,
-                                   sk_sp<SkColorSpace> colorSpace,
-                                   GrAuditTrail* auditTrail,
-                                   GrSingleOwner* singleOwner)
+                                   sk_sp<SkColorSpace> colorSpace)
         : fContext(context)
-        , fAuditTrail(auditTrail)
-        , fColorSpaceInfo(std::move(colorSpace), config)
-        , fDrawingManager(drawingMgr)
-#ifdef SK_DEBUG
-        , fSingleOwner(singleOwner)
-#endif
-{
+        , fColorSpaceInfo(std::move(colorSpace), config) {
 }
 
+GrAuditTrail* GrSurfaceContext::auditTrail() {
+    return fContext->priv().auditTrail();
+}
+
+GrDrawingManager* GrSurfaceContext::drawingManager() {
+    return fContext->priv().drawingManager();
+}
+
+const GrDrawingManager* GrSurfaceContext::drawingManager() const {
+    return fContext->priv().drawingManager();
+}
+
+#ifdef SK_DEBUG
+GrSingleOwner* GrSurfaceContext::singleOwner() {
+    return fContext->priv().singleOwner();
+}
+#endif
+
 bool GrSurfaceContext::readPixels(const SkImageInfo& dstInfo, void* dstBuffer,
                                   size_t dstRowBytes, int x, int y, uint32_t flags) {
     ASSERT_SINGLE_OWNER
     RETURN_FALSE_IF_ABANDONED
     SkDEBUGCODE(this->validate();)
-    GR_AUDIT_TRAIL_AUTO_FRAME(fAuditTrail, "GrSurfaceContext::readPixels");
+    GR_AUDIT_TRAIL_AUTO_FRAME(this->auditTrail(), "GrSurfaceContext::readPixels");
 
     // TODO: this seems to duplicate code in SkImage_Gpu::onReadPixels
     if (kUnpremul_SkAlphaType == dstInfo.alphaType() &&
@@ -52,9 +63,15 @@
     if (GrColorType::kUnknown == colorType) {
         return false;
     }
-    return fContext->priv().readSurfacePixels(this, x, y, dstInfo.width(), dstInfo.height(),
-                                                     colorType, dstInfo.colorSpace(), dstBuffer,
-                                                     dstRowBytes, flags);
+
+    auto direct = fContext->priv().asDirectContext();
+    if (!direct) {
+        return false;
+    }
+
+    return direct->priv().readSurfacePixels(this, x, y, dstInfo.width(), dstInfo.height(),
+                                            colorType, dstInfo.colorSpace(), dstBuffer,
+                                            dstRowBytes, flags);
 }
 
 bool GrSurfaceContext::writePixels(const SkImageInfo& srcInfo, const void* srcBuffer,
@@ -62,7 +79,7 @@
     ASSERT_SINGLE_OWNER
     RETURN_FALSE_IF_ABANDONED
     SkDEBUGCODE(this->validate();)
-    GR_AUDIT_TRAIL_AUTO_FRAME(fAuditTrail, "GrSurfaceContext::writePixels");
+    GR_AUDIT_TRAIL_AUTO_FRAME(this->auditTrail(), "GrSurfaceContext::writePixels");
 
     if (kUnpremul_SkAlphaType == srcInfo.alphaType()) {
         flags |= GrContextPriv::kUnpremul_PixelOpsFlag;
@@ -71,16 +88,22 @@
     if (GrColorType::kUnknown == colorType) {
         return false;
     }
-    return fContext->priv().writeSurfacePixels(this, x, y, srcInfo.width(), srcInfo.height(),
-                                                      colorType, srcInfo.colorSpace(), srcBuffer,
-                                                      srcRowBytes, flags);
+
+    auto direct = fContext->priv().asDirectContext();
+    if (!direct) {
+        return false;
+    }
+
+    return direct->priv().writeSurfacePixels(this, x, y, srcInfo.width(), srcInfo.height(),
+                                             colorType, srcInfo.colorSpace(), srcBuffer,
+                                             srcRowBytes, flags);
 }
 
 bool GrSurfaceContext::copy(GrSurfaceProxy* src, const SkIRect& srcRect, const SkIPoint& dstPoint) {
     ASSERT_SINGLE_OWNER
     RETURN_FALSE_IF_ABANDONED
     SkDEBUGCODE(this->validate();)
-    GR_AUDIT_TRAIL_AUTO_FRAME(fAuditTrail, "GrSurfaceContext::copy");
+    GR_AUDIT_TRAIL_AUTO_FRAME(this->auditTrail(), "GrSurfaceContext::copy");
 
     if (!fContext->priv().caps()->canCopySurface(this->asSurfaceProxy(), src, srcRect,
                                                         dstPoint)) {
diff --git a/src/gpu/GrSurfaceContext.h b/src/gpu/GrSurfaceContext.h
index c499d09..368fbfb 100644
--- a/src/gpu/GrSurfaceContext.h
+++ b/src/gpu/GrSurfaceContext.h
@@ -13,9 +13,9 @@
 #include "SkRefCnt.h"
 
 class GrAuditTrail;
-class GrContext;
 class GrDrawingManager;
 class GrOpList;
+class GrRecordingContext;
 class GrRenderTargetContext;
 class GrRenderTargetProxy;
 class GrSingleOwner;
@@ -102,7 +102,7 @@
 
     virtual GrRenderTargetContext* asRenderTargetContext() { return nullptr; }
 
-    GrAuditTrail* auditTrail() { return fAuditTrail; }
+    GrAuditTrail* auditTrail();
 
     // Provides access to functions that aren't part of the public API.
     GrSurfaceContextPriv surfPriv();
@@ -111,27 +111,20 @@
 protected:
     friend class GrSurfaceContextPriv;
 
-    GrSurfaceContext(GrContext*, GrDrawingManager*, GrPixelConfig, sk_sp<SkColorSpace>,
-                     GrAuditTrail*, GrSingleOwner*);
+    GrSurfaceContext(GrRecordingContext*, GrPixelConfig, sk_sp<SkColorSpace>);
 
-    GrDrawingManager* drawingManager() { return fDrawingManager; }
-    const GrDrawingManager* drawingManager() const { return fDrawingManager; }
+    GrDrawingManager* drawingManager();
+    const GrDrawingManager* drawingManager() const;
 
     virtual GrOpList* getOpList() = 0;
     SkDEBUGCODE(virtual void validate() const = 0;)
 
-    SkDEBUGCODE(GrSingleOwner* singleOwner() { return fSingleOwner; })
+    SkDEBUGCODE(GrSingleOwner* singleOwner();)
 
-    GrContext* fContext;
-    GrAuditTrail* fAuditTrail;
+    GrRecordingContext* fContext;
 
 private:
-    GrColorSpaceInfo fColorSpaceInfo;
-
-    GrDrawingManager* fDrawingManager;
-
-    // In debug builds we guard against improper thread handling
-    SkDEBUGCODE(mutable GrSingleOwner* fSingleOwner;)
+    GrColorSpaceInfo    fColorSpaceInfo;
 
     typedef SkRefCnt INHERITED;
 };
diff --git a/src/gpu/GrSurfaceContextPriv.h b/src/gpu/GrSurfaceContextPriv.h
index 4a45f8f..0749d3d 100644
--- a/src/gpu/GrSurfaceContextPriv.h
+++ b/src/gpu/GrSurfaceContextPriv.h
@@ -15,7 +15,7 @@
     additional data members or virtual methods. */
 class GrSurfaceContextPriv {
 public:
-    GrContext* getContext() { return fSurfaceContext->fContext; }
+    GrRecordingContext* getContext() { return fSurfaceContext->fContext; }
 
 private:
     explicit GrSurfaceContextPriv(GrSurfaceContext* surfaceContext)
diff --git a/src/gpu/GrSurfaceProxy.cpp b/src/gpu/GrSurfaceProxy.cpp
index 6051f51..e9f14cb 100644
--- a/src/gpu/GrSurfaceProxy.cpp
+++ b/src/gpu/GrSurfaceProxy.cpp
@@ -14,6 +14,8 @@
 #include "GrGpuResourcePriv.h"
 #include "GrOpList.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrSurfaceContext.h"
 #include "GrSurfacePriv.h"
 #include "GrTexturePriv.h"
@@ -100,11 +102,6 @@
 }
 
 GrSurfaceProxy::~GrSurfaceProxy() {
-    if (fLazyInstantiateCallback) {
-        // We call the callback with a null GrResourceProvider to signal that the lambda should
-        // clean itself up if it is holding onto any captured objects.
-        this->fLazyInstantiateCallback(nullptr);
-    }
     // For this to be deleted the opList that held a ref on it (if there was one) must have been
     // deleted. Which would have cleared out this back pointer.
     SkASSERT(!fLastOpList);
@@ -130,7 +127,8 @@
 sk_sp<GrSurface> GrSurfaceProxy::createSurfaceImpl(GrResourceProvider* resourceProvider,
                                                    int sampleCnt, bool needsStencil,
                                                    GrSurfaceDescFlags descFlags,
-                                                   GrMipMapped mipMapped) const {
+                                                   GrMipMapped mipMapped,
+                                                   bool forceNoPendingIO) const {
     SkASSERT(GrSurfaceProxy::LazyState::kNot == this->lazyInstantiationState());
     SkASSERT(!fTarget);
     GrSurfaceDesc desc;
@@ -144,8 +142,7 @@
     desc.fSampleCnt = sampleCnt;
 
     GrResourceProvider::Flags resourceProviderFlags = GrResourceProvider::Flags::kNone;
-    if ((fSurfaceFlags & GrInternalSurfaceFlags::kNoPendingIO) ||
-        resourceProvider->explicitlyAllocateGPUResources()) {
+    if ((fSurfaceFlags & GrInternalSurfaceFlags::kNoPendingIO) || forceNoPendingIO) {
         // The explicit resource allocator requires that any resources it pulls out of the
         // cache have no pending IO.
         resourceProviderFlags = GrResourceProvider::Flags::kNoPendingIO;
@@ -226,7 +223,8 @@
 
 bool GrSurfaceProxy::instantiateImpl(GrResourceProvider* resourceProvider, int sampleCnt,
                                      bool needsStencil, GrSurfaceDescFlags descFlags,
-                                     GrMipMapped mipMapped, const GrUniqueKey* uniqueKey) {
+                                     GrMipMapped mipMapped, const GrUniqueKey* uniqueKey,
+                                     bool dontForceNoPendingIO) {
     SkASSERT(LazyState::kNot == this->lazyInstantiationState());
     if (fTarget) {
         if (uniqueKey && uniqueKey->isValid()) {
@@ -236,7 +234,7 @@
     }
 
     sk_sp<GrSurface> surface = this->createSurfaceImpl(resourceProvider, sampleCnt, needsStencil,
-                                                       descFlags, mipMapped);
+                                                       descFlags, mipMapped, !dontForceNoPendingIO);
     if (!surface) {
         return false;
     }
@@ -323,7 +321,7 @@
 }
 
 #ifdef SK_DEBUG
-void GrSurfaceProxy::validate(GrContext* context) const {
+void GrSurfaceProxy::validate(GrContext_Base* context) const {
     if (fTarget) {
         SkASSERT(fTarget->getContext() == context);
     }
@@ -332,7 +330,7 @@
 }
 #endif
 
-sk_sp<GrTextureProxy> GrSurfaceProxy::Copy(GrContext* context,
+sk_sp<GrTextureProxy> GrSurfaceProxy::Copy(GrRecordingContext* context,
                                            GrSurfaceProxy* src,
                                            GrMipMapped mipMapped,
                                            SkIRect srcRect,
@@ -366,7 +364,7 @@
     return dstContext->asTextureProxyRef();
 }
 
-sk_sp<GrTextureProxy> GrSurfaceProxy::Copy(GrContext* context, GrSurfaceProxy* src,
+sk_sp<GrTextureProxy> GrSurfaceProxy::Copy(GrRecordingContext* context, GrSurfaceProxy* src,
                                            GrMipMapped mipMapped, SkBackingFit fit,
                                            SkBudgeted budgeted) {
     SkASSERT(LazyState::kFully != src->lazyInstantiationState());
@@ -374,7 +372,8 @@
                 budgeted);
 }
 
-sk_sp<GrSurfaceContext> GrSurfaceProxy::TestCopy(GrContext* context, const GrSurfaceDesc& dstDesc,
+sk_sp<GrSurfaceContext> GrSurfaceProxy::TestCopy(GrRecordingContext* context,
+                                                 const GrSurfaceDesc& dstDesc,
                                                  GrSurfaceOrigin origin, GrSurfaceProxy* srcProxy) {
     SkASSERT(LazyState::kFully != srcProxy->lazyInstantiationState());
 
@@ -434,11 +433,13 @@
                                                         fProxy->asTextureProxy()->getUniqueKey());
     }
 
+    bool syncKey = true;
     if (!surface) {
-        surface = fProxy->fLazyInstantiateCallback(resourceProvider);
+        auto result = fProxy->fLazyInstantiateCallback(resourceProvider);
+        surface = std::move(result.fSurface);
+        syncKey = result.fKeyMode == GrSurfaceProxy::LazyInstantiationKeyMode::kSynced;
     }
     if (GrSurfaceProxy::LazyInstantiationType::kSingleUse == fProxy->fLazyInstantiationType) {
-        fProxy->fLazyInstantiateCallback(nullptr);
         fProxy->fLazyInstantiateCallback = nullptr;
     }
     if (!surface) {
@@ -465,14 +466,19 @@
     }
 
     if (GrTextureProxy* texProxy = fProxy->asTextureProxy()) {
-        const GrUniqueKey& key = texProxy->getUniqueKey();
-        if (key.isValid()) {
-            if (!surface->asTexture()->getUniqueKey().isValid()) {
-                // If 'surface' is newly created, attach the unique key
-                resourceProvider->assignUniqueKeyToResource(key, surface.get());
+        texProxy->setTargetKeySync(syncKey);
+        if (syncKey) {
+            const GrUniqueKey& key = texProxy->getUniqueKey();
+            if (key.isValid()) {
+                if (!surface->asTexture()->getUniqueKey().isValid()) {
+                    // If 'surface' is newly created, attach the unique key
+                    resourceProvider->assignUniqueKeyToResource(key, surface.get());
+                } else {
+                    // otherwise we had better have reattached to a cached version
+                    SkASSERT(surface->asTexture()->getUniqueKey() == key);
+                }
             } else {
-                // otherwise we had better have reattached to a cached version
-                SkASSERT(surface->asTexture()->getUniqueKey() == key);
+                SkASSERT(!surface->getUniqueKey().isValid());
             }
         }
     }
diff --git a/src/gpu/GrSurfaceProxyPriv.h b/src/gpu/GrSurfaceProxyPriv.h
index a9076b8..3b383a5 100644
--- a/src/gpu/GrSurfaceProxyPriv.h
+++ b/src/gpu/GrSurfaceProxyPriv.h
@@ -61,8 +61,7 @@
     }
 
     bool isSafeToDeinstantiate() const {
-        return SkToBool(fProxy->fTarget) &&
-               SkToBool(fProxy->fLazyInstantiateCallback) &&
+        return SkToBool(fProxy->fTarget) && SkToBool(fProxy->fLazyInstantiateCallback) &&
                GrSurfaceProxy::LazyInstantiationType::kDeinstantiate == lazyInstantiationType();
     }
 
diff --git a/src/gpu/GrSwizzle.h b/src/gpu/GrSwizzle.h
index 109c3a6..469740b 100644
--- a/src/gpu/GrSwizzle.h
+++ b/src/gpu/GrSwizzle.h
@@ -33,11 +33,11 @@
     }
 
     /** Recreates a GrSwizzle from the output of asKey() */
-    constexpr void setFromKey(uint8_t key) {
+    constexpr void setFromKey(uint16_t key) {
         fKey = key;
         for (int i = 0; i < 4; ++i) {
-            fSwiz[i] = IToC(key & 3);
-            key >>= 2;
+            fSwiz[i] = IToC(key & 15);
+            key >>= 4;
         }
         SkASSERT(fSwiz[4] == 0);
     }
@@ -46,7 +46,7 @@
     constexpr bool operator!=(const GrSwizzle& that) const { return !(*this == that); }
 
     /** Compact representation of the swizzle suitable for a key. */
-    constexpr uint8_t asKey() const { return fKey; }
+    constexpr uint16_t asKey() const { return fKey; }
 
     /** 4 char null terminated string consisting only of chars 'r', 'g', 'b', 'a'. */
     const char* c_str() const { return fSwiz; }
@@ -56,22 +56,38 @@
         return fSwiz[i];
     }
 
+
+    // The normal component swizzles map to key values 0-3. We set the key for constant 1 to the
+    // next int.
+    static const int k1KeyValue = 4;
+
+    static float component_idx_to_float(const SkPMColor4f& color, int idx) {
+        if (idx <= 3) {
+            return color[idx];
+        }
+        if (idx == k1KeyValue) {
+            return 1.0f;
+        }
+        SK_ABORT("Unexpected swizzle component indx");
+        return -1.0f;
+    }
+
     /** Applies this swizzle to the input color and returns the swizzled color. */
     SkPMColor4f applyTo(const SkPMColor4f& color) const {
         int idx;
         uint32_t key = fKey;
         // Index of the input color that should be mapped to output r.
-        idx = (key & 3);
-        float outR = color[idx];
-        key >>= 2;
-        idx = (key & 3);
-        float outG = color[idx];
-        key >>= 2;
-        idx = (key & 3);
-        float outB = color[idx];
-        key >>= 2;
-        idx = (key & 3);
-        float outA = color[idx];
+        idx = (key & 15);
+        float outR = component_idx_to_float(color, idx);
+        key >>= 4;
+        idx = (key & 15);
+        float outG = component_idx_to_float(color, idx);
+        key >>= 4;
+        idx = (key & 15);
+        float outB = component_idx_to_float(color, idx);
+        key >>= 4;
+        idx = (key & 15);
+        float outA = component_idx_to_float(color, idx);
         return { outR, outG, outB, outA };
     }
 
@@ -81,10 +97,11 @@
     static constexpr GrSwizzle RRRA() { return GrSwizzle("rrra"); }
     static constexpr GrSwizzle BGRA() { return GrSwizzle("bgra"); }
     static constexpr GrSwizzle RGRG() { return GrSwizzle("rgrg"); }
+    static constexpr GrSwizzle RGB1() { return GrSwizzle("rgb1"); }
 
 private:
     char fSwiz[5];
-    uint8_t fKey;
+    uint16_t fKey;
 
     static constexpr int CToI(char c) {
         switch (c) {
@@ -92,23 +109,25 @@
             case 'g': return (GrColor_SHIFT_G / 8);
             case 'b': return (GrColor_SHIFT_B / 8);
             case 'a': return (GrColor_SHIFT_A / 8);
+            case '1': return k1KeyValue;
             default:  return -1;
         }
     }
 
     static constexpr char IToC(int idx) {
         switch (8 * idx) {
-            case GrColor_SHIFT_R : return 'r';
-            case GrColor_SHIFT_G : return 'g';
-            case GrColor_SHIFT_B : return 'b';
-            case GrColor_SHIFT_A : return 'a';
-            default:               return -1;
+            case GrColor_SHIFT_R  : return 'r';
+            case GrColor_SHIFT_G  : return 'g';
+            case GrColor_SHIFT_B  : return 'b';
+            case GrColor_SHIFT_A  : return 'a';
+            case (k1KeyValue * 8) : return '1';
+            default:                return -1;
         }
     }
 
     constexpr GrSwizzle(const char c[4])
             : fSwiz{c[0], c[1], c[2], c[3], '\0'}
-            , fKey((CToI(c[0]) << 0) | (CToI(c[1]) << 2) | (CToI(c[2]) << 4) | (CToI(c[3]) << 6)) {}
+            , fKey((CToI(c[0]) << 0) | (CToI(c[1]) << 4) | (CToI(c[2]) << 8) | (CToI(c[3]) << 12)) {}
 };
 
 #endif
diff --git a/src/gpu/GrTestUtils.cpp b/src/gpu/GrTestUtils.cpp
index ded7383..845ee77 100644
--- a/src/gpu/GrTestUtils.cpp
+++ b/src/gpu/GrTestUtils.cpp
@@ -6,7 +6,9 @@
  */
 
 #include "GrTestUtils.h"
+
 #include "GrColorSpaceInfo.h"
+#include "GrContext.h"
 #include "GrProcessorUnitTest.h"
 #include "GrStyle.h"
 #include "SkDashPathPriv.h"
diff --git a/src/gpu/GrTextureAdjuster.cpp b/src/gpu/GrTextureAdjuster.cpp
index 1544398..a840c52 100644
--- a/src/gpu/GrTextureAdjuster.cpp
+++ b/src/gpu/GrTextureAdjuster.cpp
@@ -7,18 +7,19 @@
 
 #include "GrTextureAdjuster.h"
 #include "GrColorSpaceXform.h"
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrGpu.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "SkGr.h"
 
-GrTextureAdjuster::GrTextureAdjuster(GrContext* context, sk_sp<GrTextureProxy> original,
+GrTextureAdjuster::GrTextureAdjuster(GrRecordingContext* context, sk_sp<GrTextureProxy> original,
                                      SkAlphaType alphaType,
                                      uint32_t uniqueID,
-                                     SkColorSpace* cs)
+                                     SkColorSpace* cs,
+                                     bool useDecal)
     : INHERITED(context, original->width(), original->height(),
-                GrPixelConfigIsAlphaOnly(original->config()))
+                GrPixelConfigIsAlphaOnly(original->config()), useDecal)
     , fOriginal(std::move(original))
     , fAlphaType(alphaType)
     , fColorSpace(cs)
@@ -37,7 +38,7 @@
 
 sk_sp<GrTextureProxy> GrTextureAdjuster::refTextureProxyCopy(const CopyParams& copyParams,
                                                              bool willBeMipped) {
-    GrProxyProvider* proxyProvider = fContext->priv().proxyProvider();
+    GrProxyProvider* proxyProvider = this->context()->priv().proxyProvider();
 
     GrUniqueKey key;
     this->makeCopyKey(copyParams, &key);
@@ -52,7 +53,8 @@
 
     sk_sp<GrTextureProxy> proxy = this->originalProxyRef();
 
-    sk_sp<GrTextureProxy> copy = CopyOnGpu(fContext, std::move(proxy), copyParams, willBeMipped);
+    sk_sp<GrTextureProxy> copy = CopyOnGpu(this->context(), std::move(proxy),
+                                           copyParams, willBeMipped);
     if (copy) {
         if (key.isValid()) {
             SkASSERT(copy->origin() == this->originalProxy()->origin());
@@ -66,7 +68,7 @@
                 proxyProvider->removeUniqueKeyFromProxy(cachedCopy.get());
             }
             proxyProvider->assignUniqueKeyToProxy(key, copy.get());
-            this->didCacheCopy(key, proxyProvider->contextUniqueID());
+            this->didCacheCopy(key, proxyProvider->contextID());
         }
     }
     return copy;
@@ -79,20 +81,20 @@
     sk_sp<GrTextureProxy> proxy = this->originalProxyRef();
     CopyParams copyParams;
 
-    if (!fContext) {
+    if (this->context()->priv().abandoned()) {
         // The texture was abandoned.
         return nullptr;
     }
 
-    SkASSERT(this->width() <= fContext->priv().caps()->maxTextureSize() &&
-             this->height() <= fContext->priv().caps()->maxTextureSize());
+    SkASSERT(this->width() <= this->context()->priv().caps()->maxTextureSize() &&
+             this->height() <= this->context()->priv().caps()->maxTextureSize());
 
     bool needsCopyForMipsOnly = false;
     if (!params.isRepeated() ||
-        !GrGpu::IsACopyNeededForRepeatWrapMode(fContext->priv().caps(), proxy.get(),
+        !GrGpu::IsACopyNeededForRepeatWrapMode(this->context()->priv().caps(), proxy.get(),
                                                proxy->width(), proxy->height(), params.filter(),
                                                &copyParams, scaleAdjust)) {
-        needsCopyForMipsOnly = GrGpu::IsACopyNeededForMips(fContext->priv().caps(),
+        needsCopyForMipsOnly = GrGpu::IsACopyNeededForMips(this->context()->priv().caps(),
                                                            proxy.get(), params.filter(),
                                                            &copyParams);
         if (!needsCopyForMipsOnly) {
@@ -118,14 +120,9 @@
         const GrSamplerState::Filter* filterOrNullForBicubic) {
     SkMatrix textureMatrix = origTextureMatrix;
 
-    SkRect domain;
-    GrSamplerState samplerState;
-    if (filterOrNullForBicubic) {
-        samplerState.setFilterMode(*filterOrNullForBicubic);
-    }
     SkScalar scaleAdjust[2] = { 1.0f, 1.0f };
     sk_sp<GrTextureProxy> proxy(
-            this->refTextureProxyForParams(samplerState, scaleAdjust));
+            this->refTextureProxyForParams(filterOrNullForBicubic, scaleAdjust));
     if (!proxy) {
         return nullptr;
     }
@@ -135,6 +132,7 @@
         textureMatrix.postScale(scaleAdjust[0], scaleAdjust[1]);
     }
 
+    SkRect domain;
     DomainMode domainMode =
         DetermineDomainMode(constraintRect, filterConstraint, coordsLimitedToConstraintRect,
                             proxy.get(), filterOrNullForBicubic, &domain);
@@ -154,6 +152,6 @@
     }
     SkASSERT(kNoDomain_DomainMode == domainMode ||
              (domain.fLeft <= domain.fRight && domain.fTop <= domain.fBottom));
-    return CreateFragmentProcessorForDomainAndFilter(std::move(proxy), textureMatrix, domainMode,
-                                                     domain, filterOrNullForBicubic);
+    return this->createFragmentProcessorForDomainAndFilter(
+            std::move(proxy), textureMatrix, domainMode, domain, filterOrNullForBicubic);
 }
diff --git a/src/gpu/GrTextureAdjuster.h b/src/gpu/GrTextureAdjuster.h
index ec008e7..58195e9 100644
--- a/src/gpu/GrTextureAdjuster.h
+++ b/src/gpu/GrTextureAdjuster.h
@@ -12,6 +12,8 @@
 #include "GrTextureProxy.h"
 #include "SkTLazy.h"
 
+class GrRecordingContext;
+
 /**
  * Base class for sources that start out as textures. Optionally allows for a content area subrect.
  * The intent is not to use content area for subrect rendering. Rather, the pixels outside the
@@ -29,8 +31,8 @@
 
     // We do not ref the texture nor the colorspace, so the caller must keep them in scope while
     // this Adjuster is alive.
-    GrTextureAdjuster(GrContext*, sk_sp<GrTextureProxy>, SkAlphaType, uint32_t uniqueID,
-                      SkColorSpace*);
+    GrTextureAdjuster(GrRecordingContext*, sk_sp<GrTextureProxy>, SkAlphaType,
+                      uint32_t uniqueID, SkColorSpace*, bool useDecal = false);
 
 protected:
     SkAlphaType alphaType() const override { return fAlphaType; }
diff --git a/src/gpu/GrTextureContext.cpp b/src/gpu/GrTextureContext.cpp
index c5b3203..0b396e1 100644
--- a/src/gpu/GrTextureContext.cpp
+++ b/src/gpu/GrTextureContext.cpp
@@ -17,14 +17,10 @@
     SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(this->singleOwner());)
 #define RETURN_FALSE_IF_ABANDONED  if (this->drawingManager()->wasAbandoned()) { return false; }
 
-GrTextureContext::GrTextureContext(GrContext* context,
-                                   GrDrawingManager* drawingMgr,
+GrTextureContext::GrTextureContext(GrRecordingContext* context,
                                    sk_sp<GrTextureProxy> textureProxy,
-                                   sk_sp<SkColorSpace> colorSpace,
-                                   GrAuditTrail* auditTrail,
-                                   GrSingleOwner* singleOwner)
-        : GrSurfaceContext(context, drawingMgr, textureProxy->config(), std::move(colorSpace),
-                           auditTrail, singleOwner)
+                                   sk_sp<SkColorSpace> colorSpace)
+        : GrSurfaceContext(context, textureProxy->config(), std::move(colorSpace))
         , fTextureProxy(std::move(textureProxy))
         , fOpList(sk_ref_sp(fTextureProxy->getLastTextureOpList())) {
     SkDEBUGCODE(this->validate();)
diff --git a/src/gpu/GrTextureContext.h b/src/gpu/GrTextureContext.h
index f083e71..9544f4d 100644
--- a/src/gpu/GrTextureContext.h
+++ b/src/gpu/GrTextureContext.h
@@ -39,8 +39,7 @@
     sk_sp<GrRenderTargetProxy> asRenderTargetProxyRef() override;
 
 protected:
-    GrTextureContext(GrContext*, GrDrawingManager*, sk_sp<GrTextureProxy>,
-                     sk_sp<SkColorSpace>, GrAuditTrail*, GrSingleOwner*);
+    GrTextureContext(GrRecordingContext*, sk_sp<GrTextureProxy>, sk_sp<SkColorSpace>);
 
     SkDEBUGCODE(void validate() const override;)
 
@@ -49,11 +48,11 @@
 
     GrOpList* getOpList() override;
 
-    sk_sp<GrTextureProxy>        fTextureProxy;
+    sk_sp<GrTextureProxy>  fTextureProxy;
 
     // In MDB-mode the GrOpList can be closed by some other renderTargetContext that has picked
     // it up. For this reason, the GrOpList should only ever be accessed via 'getOpList'.
-    sk_sp<GrTextureOpList>       fOpList;
+    sk_sp<GrTextureOpList> fOpList;
 
     typedef GrSurfaceContext INHERITED;
 };
diff --git a/src/gpu/GrTextureMaker.cpp b/src/gpu/GrTextureMaker.cpp
index afbfbf0..fbaa300 100644
--- a/src/gpu/GrTextureMaker.cpp
+++ b/src/gpu/GrTextureMaker.cpp
@@ -8,16 +8,16 @@
 #include "GrTextureMaker.h"
 
 #include "GrColorSpaceXform.h"
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrGpu.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 
 sk_sp<GrTextureProxy> GrTextureMaker::onRefTextureProxyForParams(const GrSamplerState& params,
                                                                  bool willBeMipped,
                                                                  SkScalar scaleAdjust[2]) {
-    if (this->width() > fContext->priv().caps()->maxTextureSize() ||
-        this->height() > fContext->priv().caps()->maxTextureSize()) {
+    if (this->width() > this->context()->priv().caps()->maxTextureSize() ||
+        this->height() > this->context()->priv().caps()->maxTextureSize()) {
         return nullptr;
     }
 
@@ -28,10 +28,10 @@
     bool needsCopyForMipsOnly = false;
     if (original) {
         if (!params.isRepeated() ||
-            !GrGpu::IsACopyNeededForRepeatWrapMode(fContext->priv().caps(), original.get(),
+            !GrGpu::IsACopyNeededForRepeatWrapMode(this->context()->priv().caps(), original.get(),
                                                    original->width(), original->height(),
                                                    params.filter(), &copyParams, scaleAdjust)) {
-            needsCopyForMipsOnly = GrGpu::IsACopyNeededForMips(fContext->priv().caps(),
+            needsCopyForMipsOnly = GrGpu::IsACopyNeededForMips(this->context()->priv().caps(),
                                                                original.get(), params.filter(),
                                                                &copyParams);
             if (!needsCopyForMipsOnly) {
@@ -40,14 +40,14 @@
         }
     } else {
         if (!params.isRepeated() ||
-            !GrGpu::IsACopyNeededForRepeatWrapMode(fContext->priv().caps(), nullptr,
+            !GrGpu::IsACopyNeededForRepeatWrapMode(this->context()->priv().caps(), nullptr,
                                                    this->width(), this->height(),
                                                    params.filter(), &copyParams, scaleAdjust)) {
             return this->refOriginalTextureProxy(willBeMipped, AllowedTexGenType::kAny);
         }
     }
 
-    GrProxyProvider* proxyProvider = fContext->priv().proxyProvider();
+    GrProxyProvider* proxyProvider = this->context()->priv().proxyProvider();
 
     GrSurfaceOrigin origOrigin = original ? original->origin() : kTopLeft_GrSurfaceOrigin;
     GrUniqueKey copyKey;
@@ -74,7 +74,7 @@
         return nullptr;
     }
 
-    sk_sp<GrTextureProxy> result = CopyOnGpu(fContext, source, copyParams, willBeMipped);
+    sk_sp<GrTextureProxy> result = CopyOnGpu(this->context(), source, copyParams, willBeMipped);
 
     if (!result) {
         // If we were unable to make a copy and we only needed a copy for mips, then we will return
@@ -98,7 +98,7 @@
             proxyProvider->removeUniqueKeyFromProxy(cachedProxy.get());
         }
         proxyProvider->assignUniqueKeyToProxy(copyKey, result.get());
-        this->didCacheCopy(copyKey, proxyProvider->contextUniqueID());
+        this->didCacheCopy(copyKey, proxyProvider->contextID());
     }
     return result;
 }
@@ -112,7 +112,7 @@
     const GrSamplerState::Filter* fmForDetermineDomain = filterOrNullForBicubic;
     if (filterOrNullForBicubic && GrSamplerState::Filter::kMipMap == *filterOrNullForBicubic &&
         kYes_FilterConstraint == filterConstraint) {
-        // TODo: Here we should force a copy restricted to the constraintRect since MIP maps will
+        // TODO: Here we should force a copy restricted to the constraintRect since MIP maps will
         // read outside the constraint rect. However, as in the adjuster case, we aren't currently
         // doing that.
         // We instead we compute the domain as though were bilerping which is only correct if we
@@ -121,25 +121,20 @@
         fmForDetermineDomain = &kBilerp;
     }
 
-    GrSamplerState samplerState;
-    if (filterOrNullForBicubic) {
-        samplerState = GrSamplerState(GrSamplerState::WrapMode::kClamp, *filterOrNullForBicubic);
-    } else {
-        // Bicubic doesn't use filtering for it's texture accesses.
-        samplerState = GrSamplerState::ClampNearest();
-    }
     SkScalar scaleAdjust[2] = { 1.0f, 1.0f };
-    sk_sp<GrTextureProxy> proxy(this->refTextureProxyForParams(samplerState, scaleAdjust));
+    sk_sp<GrTextureProxy> proxy(this->refTextureProxyForParams(filterOrNullForBicubic,
+                                                               scaleAdjust));
     if (!proxy) {
         return nullptr;
     }
     SkMatrix adjustedMatrix = textureMatrix;
     adjustedMatrix.postScale(scaleAdjust[0], scaleAdjust[1]);
+
     SkRect domain;
     DomainMode domainMode =
         DetermineDomainMode(constraintRect, filterConstraint, coordsLimitedToConstraintRect,
                             proxy.get(), fmForDetermineDomain, &domain);
     SkASSERT(kTightCopy_DomainMode != domainMode);
-    return CreateFragmentProcessorForDomainAndFilter(std::move(proxy), adjustedMatrix, domainMode,
-                                                     domain, filterOrNullForBicubic);
+    return this->createFragmentProcessorForDomainAndFilter(
+            std::move(proxy), adjustedMatrix, domainMode, domain, filterOrNullForBicubic);
 }
diff --git a/src/gpu/GrTextureMaker.h b/src/gpu/GrTextureMaker.h
index e79b976..543bbda 100644
--- a/src/gpu/GrTextureMaker.h
+++ b/src/gpu/GrTextureMaker.h
@@ -26,8 +26,9 @@
             const GrSamplerState::Filter* filterOrNullForBicubic) override;
 
 protected:
-    GrTextureMaker(GrContext* context, int width, int height, bool isAlphaOnly)
-        : INHERITED(context, width, height, isAlphaOnly) {}
+    GrTextureMaker(GrRecordingContext* context, int width, int height, bool isAlphaOnly,
+                   bool domainNeedsLocal)
+        : INHERITED(context, width, height, isAlphaOnly, domainNeedsLocal) {}
 
     /**
      *  Return the maker's "original" texture. It is the responsibility of the maker to handle any
@@ -39,8 +40,6 @@
     virtual sk_sp<GrTextureProxy> refOriginalTextureProxy(bool willBeMipped,
                                                           AllowedTexGenType genType) = 0;
 
-    GrContext* context() const { return fContext; }
-
 private:
     sk_sp<GrTextureProxy> onRefTextureProxyForParams(const GrSamplerState&,
                                                      bool willBeMipped,
diff --git a/src/gpu/GrTextureOpList.cpp b/src/gpu/GrTextureOpList.cpp
index f63c5a7..1b18c09 100644
--- a/src/gpu/GrTextureOpList.cpp
+++ b/src/gpu/GrTextureOpList.cpp
@@ -12,6 +12,8 @@
 #include "GrContextPriv.h"
 #include "GrGpu.h"
 #include "GrMemoryPool.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrResourceAllocator.h"
 #include "GrTextureProxy.h"
 #include "SkStringUtils.h"
@@ -136,7 +138,7 @@
 
 // This closely parallels GrRenderTargetOpList::copySurface but renderTargetOpList
 // stores extra data with the op
-bool GrTextureOpList::copySurface(GrContext* context,
+bool GrTextureOpList::copySurface(GrRecordingContext* context,
                                   GrSurfaceProxy* dst,
                                   GrSurfaceProxy* src,
                                   const SkIRect& srcRect,
@@ -178,22 +180,41 @@
     }
 }
 
+bool GrTextureOpList::onIsUsed(GrSurfaceProxy* proxyToCheck) const {
+    bool used = false;
+
+    auto visit = [ proxyToCheck, &used ] (GrSurfaceProxy* p) {
+        if (p == proxyToCheck) {
+            used = true;
+        }
+    };
+    for (int i = 0; i < fRecordedOps.count(); ++i) {
+        const GrOp* op = fRecordedOps[i].get();
+        if (op) {
+            op->visitProxies(visit, GrOp::VisitorType::kOther);
+        }
+    }
+
+    return used;
+}
+
 void GrTextureOpList::gatherProxyIntervals(GrResourceAllocator* alloc) const {
-    unsigned int cur = alloc->numOps();
 
     // Add the interval for all the writes to this opList's target
     if (fRecordedOps.count()) {
+        unsigned int cur = alloc->curOp();
+
         alloc->addInterval(fTarget.get(), cur, cur+fRecordedOps.count()-1);
     } else {
         // This can happen if there is a loadOp (e.g., a clear) but no other draws. In this case we
         // still need to add an interval for the destination so we create a fake op# for
         // the missing clear op.
-        alloc->addInterval(fTarget.get());
+        alloc->addInterval(fTarget.get(), alloc->curOp(), alloc->curOp());
         alloc->incOps();
     }
 
     auto gather = [ alloc SkDEBUGCODE(, this) ] (GrSurfaceProxy* p) {
-        alloc->addInterval(p SkDEBUGCODE(, p == fTarget.get()));
+        alloc->addInterval(p, alloc->curOp(), alloc->curOp() SkDEBUGCODE(, p == fTarget.get()));
     };
     for (int i = 0; i < fRecordedOps.count(); ++i) {
         const GrOp* op = fRecordedOps[i].get(); // only diff from the GrRenderTargetOpList version
@@ -201,7 +222,7 @@
             op->visitProxies(gather, GrOp::VisitorType::kAllocatorGather);
         }
 
-        // Even though the op may have been moved we still need to increment the op count to
+        // Even though the op may have been (re)moved we still need to increment the op count to
         // keep all the math consistent.
         alloc->incOps();
     }
diff --git a/src/gpu/GrTextureOpList.h b/src/gpu/GrTextureOpList.h
index dd1dc69..839b863 100644
--- a/src/gpu/GrTextureOpList.h
+++ b/src/gpu/GrTextureOpList.h
@@ -48,7 +48,7 @@
      * depending on the type of surface, configs, etc, and the backend-specific
      * limitations.
      */
-    bool copySurface(GrContext*,
+    bool copySurface(GrRecordingContext*,
                      GrSurfaceProxy* dst,
                      GrSurfaceProxy* src,
                      const SkIRect& srcRect,
@@ -59,6 +59,8 @@
     SkDEBUGCODE(void dump(bool printDependencies) const override;)
 
 private:
+    bool onIsUsed(GrSurfaceProxy*) const override;
+
     void deleteOp(int index);
     void deleteOps();
 
diff --git a/src/gpu/GrTextureProducer.cpp b/src/gpu/GrTextureProducer.cpp
index f11d2a3..b9a952f 100644
--- a/src/gpu/GrTextureProducer.cpp
+++ b/src/gpu/GrTextureProducer.cpp
@@ -7,17 +7,20 @@
 
 #include "GrTextureProducer.h"
 #include "GrClip.h"
+#include "GrContextPriv.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrTextureProxy.h"
 #include "SkGr.h"
 #include "SkMipMap.h"
 #include "SkRectPriv.h"
 #include "effects/GrBicubicEffect.h"
-#include "effects/GrSimpleTextureEffect.h"
+#include "effects/generated/GrSimpleTextureEffect.h"
 #include "effects/GrTextureDomain.h"
 
-sk_sp<GrTextureProxy> GrTextureProducer::CopyOnGpu(GrContext* context,
+sk_sp<GrTextureProxy> GrTextureProducer::CopyOnGpu(GrRecordingContext* context,
                                                    sk_sp<GrTextureProxy> inputProxy,
                                                    const CopyParams& copyParams,
                                                    bool dstWillRequireMipMaps) {
@@ -195,34 +198,65 @@
     return kDomain_DomainMode;
 }
 
-std::unique_ptr<GrFragmentProcessor> GrTextureProducer::CreateFragmentProcessorForDomainAndFilter(
+std::unique_ptr<GrFragmentProcessor> GrTextureProducer::createFragmentProcessorForDomainAndFilter(
         sk_sp<GrTextureProxy> proxy,
         const SkMatrix& textureMatrix,
         DomainMode domainMode,
         const SkRect& domain,
         const GrSamplerState::Filter* filterOrNullForBicubic) {
     SkASSERT(kTightCopy_DomainMode != domainMode);
+    bool clampToBorderSupport = fContext->priv().caps()->clampToBorderSupport();
     if (filterOrNullForBicubic) {
-        if (kDomain_DomainMode == domainMode) {
+        if (kDomain_DomainMode == domainMode || (fDomainNeedsDecal && !clampToBorderSupport)) {
+            GrTextureDomain::Mode wrapMode = fDomainNeedsDecal ? GrTextureDomain::kDecal_Mode
+                                                               : GrTextureDomain::kClamp_Mode;
             return GrTextureDomainEffect::Make(std::move(proxy), textureMatrix, domain,
-                                               GrTextureDomain::kClamp_Mode,
-                                               *filterOrNullForBicubic);
+                                               wrapMode, *filterOrNullForBicubic);
         } else {
-            GrSamplerState samplerState(GrSamplerState::WrapMode::kClamp, *filterOrNullForBicubic);
+            GrSamplerState::WrapMode wrapMode =
+                    fDomainNeedsDecal ? GrSamplerState::WrapMode::kClampToBorder
+                                      : GrSamplerState::WrapMode::kClamp;
+            GrSamplerState samplerState(wrapMode, *filterOrNullForBicubic);
             return GrSimpleTextureEffect::Make(std::move(proxy), textureMatrix, samplerState);
         }
     } else {
-        if (kDomain_DomainMode == domainMode) {
-            return GrBicubicEffect::Make(std::move(proxy), textureMatrix, domain);
+        static const GrSamplerState::WrapMode kClampClamp[] = {
+                GrSamplerState::WrapMode::kClamp, GrSamplerState::WrapMode::kClamp};
+        static const GrSamplerState::WrapMode kDecalDecal[] = {
+                GrSamplerState::WrapMode::kClampToBorder, GrSamplerState::WrapMode::kClampToBorder};
+
+        if (kDomain_DomainMode == domainMode || (fDomainNeedsDecal && !clampToBorderSupport)) {
+            GrTextureDomain::Mode wrapMode = fDomainNeedsDecal ? GrTextureDomain::kDecal_Mode
+                                         : GrTextureDomain::kClamp_Mode;
+            return GrBicubicEffect::Make(std::move(proxy), textureMatrix, kClampClamp,
+                                         wrapMode, wrapMode, this->alphaType(),
+                                         kDomain_DomainMode == domainMode ? &domain : nullptr);
         } else {
-            static const GrSamplerState::WrapMode kClampClamp[] = {
-                    GrSamplerState::WrapMode::kClamp, GrSamplerState::WrapMode::kClamp};
-            return GrBicubicEffect::Make(std::move(proxy), textureMatrix, kClampClamp);
+            return GrBicubicEffect::Make(std::move(proxy), textureMatrix,
+                                         fDomainNeedsDecal ? kDecalDecal : kClampClamp,
+                                         this->alphaType());
         }
     }
 }
 
 sk_sp<GrTextureProxy> GrTextureProducer::refTextureProxyForParams(
+        const GrSamplerState::Filter* filterOrNullForBicubic,
+        SkScalar scaleAdjust[2]) {
+    GrSamplerState sampler; // Default is nearest + clamp
+    if (filterOrNullForBicubic) {
+        sampler.setFilterMode(*filterOrNullForBicubic);
+    }
+    if (fDomainNeedsDecal) {
+        // Assuming hardware support, switch to clamp-to-border instead of clamp
+        if (fContext->priv().caps()->clampToBorderSupport()) {
+            sampler.setWrapModeX(GrSamplerState::WrapMode::kClampToBorder);
+            sampler.setWrapModeY(GrSamplerState::WrapMode::kClampToBorder);
+        }
+    }
+    return this->refTextureProxyForParams(sampler, scaleAdjust);
+}
+
+sk_sp<GrTextureProxy> GrTextureProducer::refTextureProxyForParams(
         const GrSamplerState& sampler,
         SkScalar scaleAdjust[2]) {
     // Check that the caller pre-initialized scaleAdjust
@@ -235,14 +269,14 @@
 
     int mipCount = SkMipMap::ComputeLevelCount(this->width(), this->height());
     bool willBeMipped = GrSamplerState::Filter::kMipMap == sampler.filter() && mipCount &&
-                        fContext->priv().caps()->mipMapSupport();
+                        this->context()->priv().caps()->mipMapSupport();
 
     auto result = this->onRefTextureProxyForParams(sampler, willBeMipped, scaleAdjust);
 
     // Check to make sure that if we say the texture willBeMipped that the returned texture has mip
     // maps, unless the config is not copyable.
     SkASSERT(!result || !willBeMipped || result->mipMapped() == GrMipMapped::kYes ||
-             !fContext->priv().caps()->isConfigCopyable(result->config()));
+             !this->context()->priv().caps()->isConfigCopyable(result->config()));
 
     // Check that the "no scaling expected" case always returns a proxy of the same size as the
     // producer.
@@ -259,14 +293,14 @@
 
     int mipCount = SkMipMap::ComputeLevelCount(this->width(), this->height());
     bool willBeMipped = GrSamplerState::Filter::kMipMap == sampler.filter() && mipCount &&
-                        fContext->priv().caps()->mipMapSupport();
+                        this->context()->priv().caps()->mipMapSupport();
 
     auto result = this->onRefTextureProxyForParams(sampler, willBeMipped, nullptr);
 
     // Check to make sure that if we say the texture willBeMipped that the returned texture has mip
     // maps, unless the config is not copyable.
     SkASSERT(!result || !willBeMipped || result->mipMapped() == GrMipMapped::kYes ||
-             !fContext->priv().caps()->isConfigCopyable(result->config()));
+             !this->context()->priv().caps()->isConfigCopyable(result->config()));
 
     // Check that no scaling occured and we returned a proxy of the same size as the producer.
     SkASSERT(!result || (result->width() == this->width() && result->height() == this->height()));
diff --git a/src/gpu/GrTextureProducer.h b/src/gpu/GrTextureProducer.h
index 1994f8b..b1d94df 100644
--- a/src/gpu/GrTextureProducer.h
+++ b/src/gpu/GrTextureProducer.h
@@ -13,8 +13,8 @@
 #include "SkImageInfo.h"
 #include "SkNoncopyable.h"
 
-class GrContext;
 class GrFragmentProcessor;
+class GrRecordingContext;
 class GrTexture;
 class GrTextureProxy;
 class SkColorSpace;
@@ -85,11 +85,8 @@
     sk_sp<GrTextureProxy> refTextureProxyForParams(const GrSamplerState&,
                                                    SkScalar scaleAdjust[2]);
 
-    sk_sp<GrTextureProxy> refTextureProxyForParams(GrSamplerState::Filter filter,
-                                                   SkScalar scaleAdjust[2]) {
-        return this->refTextureProxyForParams(
-                GrSamplerState(GrSamplerState::WrapMode::kClamp, filter), scaleAdjust);
-    }
+    sk_sp<GrTextureProxy> refTextureProxyForParams(
+            const GrSamplerState::Filter* filterOrNullForBicubic, SkScalar scaleAdjust[2]);
 
     /**
      * Returns a texture. If willNeedMips is true then the returned texture is guaranteed to have
@@ -107,17 +104,20 @@
     int width() const { return fWidth; }
     int height() const { return fHeight; }
     bool isAlphaOnly() const { return fIsAlphaOnly; }
+    bool domainNeedsDecal() const { return fDomainNeedsDecal; }
     virtual SkAlphaType alphaType() const = 0;
     virtual SkColorSpace* colorSpace() const = 0;
 
 protected:
     friend class GrTextureProducer_TestAccess;
 
-    GrTextureProducer(GrContext* context, int width, int height, bool isAlphaOnly)
+    GrTextureProducer(GrRecordingContext* context, int width, int height, bool isAlphaOnly,
+                      bool domainNeedsDecal)
         : fContext(context)
         , fWidth(width)
         , fHeight(height)
-        , fIsAlphaOnly(isAlphaOnly) {}
+        , fIsAlphaOnly(isAlphaOnly)
+        , fDomainNeedsDecal(domainNeedsDecal) {}
 
     /** Helper for creating a key for a copy from an original key. */
     static void MakeCopyKeyFromOrigKey(const GrUniqueKey& origKey,
@@ -156,7 +156,8 @@
         kTightCopy_DomainMode
     };
 
-    static sk_sp<GrTextureProxy> CopyOnGpu(GrContext*, sk_sp<GrTextureProxy> inputProxy,
+    // This can draw to accomplish the copy, thus the recording context is needed
+    static sk_sp<GrTextureProxy> CopyOnGpu(GrRecordingContext*, sk_sp<GrTextureProxy> inputProxy,
                                            const CopyParams& copyParams,
                                            bool dstWillRequireMipMaps);
 
@@ -167,23 +168,27 @@
                                           const GrSamplerState::Filter* filterModeOrNullForBicubic,
                                           SkRect* domainRect);
 
-    static std::unique_ptr<GrFragmentProcessor> CreateFragmentProcessorForDomainAndFilter(
+    std::unique_ptr<GrFragmentProcessor> createFragmentProcessorForDomainAndFilter(
             sk_sp<GrTextureProxy> proxy,
             const SkMatrix& textureMatrix,
             DomainMode,
             const SkRect& domain,
             const GrSamplerState::Filter* filterOrNullForBicubic);
 
-    GrContext* fContext;
+    GrRecordingContext* context() const { return fContext; }
 
 private:
     virtual sk_sp<GrTextureProxy> onRefTextureProxyForParams(const GrSamplerState&,
                                                              bool willBeMipped,
                                                              SkScalar scaleAdjust[2]) = 0;
 
-    const int   fWidth;
-    const int   fHeight;
-    const bool  fIsAlphaOnly;
+    GrRecordingContext* fContext;
+    const int           fWidth;
+    const int           fHeight;
+    const bool          fIsAlphaOnly;
+    // If true, any domain effect uses kDecal instead of kClamp, and sampler filter uses
+    // kClampToBorder instead of kClamp.
+    const bool  fDomainNeedsDecal;
 
     typedef SkNoncopyable INHERITED;
 };
diff --git a/src/gpu/GrTextureProxy.cpp b/src/gpu/GrTextureProxy.cpp
index 71af55b..b319dba 100644
--- a/src/gpu/GrTextureProxy.cpp
+++ b/src/gpu/GrTextureProxy.cpp
@@ -76,13 +76,15 @@
     }
 }
 
-bool GrTextureProxy::instantiate(GrResourceProvider* resourceProvider) {
+bool GrTextureProxy::instantiate(GrResourceProvider* resourceProvider,
+                                 bool dontForceNoPendingIO) {
     if (LazyState::kNot != this->lazyInstantiationState()) {
         return false;
     }
     if (!this->instantiateImpl(resourceProvider, 1, /* needsStencil = */ false,
                                kNone_GrSurfaceFlags, fMipMapped,
-                               fUniqueKey.isValid() ? &fUniqueKey : nullptr)) {
+                               fUniqueKey.isValid() ? &fUniqueKey : nullptr,
+                               dontForceNoPendingIO)) {
         return false;
     }
 
@@ -92,10 +94,12 @@
 }
 
 sk_sp<GrSurface> GrTextureProxy::createSurface(GrResourceProvider* resourceProvider) const {
-    sk_sp<GrSurface> surface= this->createSurfaceImpl(resourceProvider, 1,
-                                                      /* needsStencil = */ false,
-                                                      kNone_GrSurfaceFlags,
-                                                      fMipMapped);
+    SkASSERT(resourceProvider->explicitlyAllocateGPUResources());
+
+    sk_sp<GrSurface> surface = this->createSurfaceImpl(resourceProvider, 1,
+                                                       /* needsStencil = */ false,
+                                                       kNone_GrSurfaceFlags,
+                                                       fMipMapped, true);
     if (!surface) {
         return nullptr;
     }
@@ -150,7 +154,7 @@
     SkASSERT(key.isValid());
     SkASSERT(!fUniqueKey.isValid()); // proxies can only ever get one uniqueKey
 
-    if (fTarget) {
+    if (fTarget && fSyncTargetKey) {
         if (!fTarget->getUniqueKey().isValid()) {
             fTarget->resourcePriv().setUniqueKey(key);
         }
diff --git a/src/gpu/GrTextureRenderTargetProxy.cpp b/src/gpu/GrTextureRenderTargetProxy.cpp
index 3e52b2c..af53572 100644
--- a/src/gpu/GrTextureRenderTargetProxy.cpp
+++ b/src/gpu/GrTextureRenderTargetProxy.cpp
@@ -46,7 +46,7 @@
         // Since we have virtual inheritance, we initialize GrSurfaceProxy directly. Send null
         // callbacks to the texture and RT proxies simply to route to the appropriate constructors.
         , GrRenderTargetProxy(LazyInstantiateCallback(), lazyType, format, desc, origin, fit,
-                              budgeted, surfaceFlags)
+                              budgeted, surfaceFlags, WrapsVkSecondaryCB::kNo)
         , GrTextureProxy(LazyInstantiateCallback(), lazyType, format, desc, origin, mipMapped,
                          fit, budgeted, surfaceFlags) {}
 
@@ -75,7 +75,8 @@
                                   !this->priv().isExact());
 }
 
-bool GrTextureRenderTargetProxy::instantiate(GrResourceProvider* resourceProvider) {
+bool GrTextureRenderTargetProxy::instantiate(GrResourceProvider* resourceProvider,
+                                             bool dontForceNoPendingIO) {
     if (LazyState::kNot != this->lazyInstantiationState()) {
         return false;
     }
@@ -84,7 +85,8 @@
     const GrUniqueKey& key = this->getUniqueKey();
 
     if (!this->instantiateImpl(resourceProvider, this->numStencilSamples(), this->needsStencil(),
-                               kDescFlags, this->mipMapped(), key.isValid() ? &key : nullptr)) {
+                               kDescFlags, this->mipMapped(), key.isValid() ? &key : nullptr,
+                               dontForceNoPendingIO)) {
         return false;
     }
     if (key.isValid()) {
@@ -99,11 +101,13 @@
 
 sk_sp<GrSurface> GrTextureRenderTargetProxy::createSurface(
                                                     GrResourceProvider* resourceProvider) const {
+    SkASSERT(resourceProvider->explicitlyAllocateGPUResources());
+
     static constexpr GrSurfaceDescFlags kDescFlags = kRenderTarget_GrSurfaceFlag;
 
     sk_sp<GrSurface> surface = this->createSurfaceImpl(resourceProvider, this->numStencilSamples(),
                                                        this->needsStencil(), kDescFlags,
-                                                       this->mipMapped());
+                                                       this->mipMapped(), true);
     if (!surface) {
         return nullptr;
     }
diff --git a/src/gpu/GrTextureRenderTargetProxy.h b/src/gpu/GrTextureRenderTargetProxy.h
index 18c1419..fc69e2d 100644
--- a/src/gpu/GrTextureRenderTargetProxy.h
+++ b/src/gpu/GrTextureRenderTargetProxy.h
@@ -40,7 +40,7 @@
     // Wrapped version
     GrTextureRenderTargetProxy(sk_sp<GrSurface>, GrSurfaceOrigin);
 
-    bool instantiate(GrResourceProvider*) override;
+    bool instantiate(GrResourceProvider*, bool dontForceNoPendingIO = false) override;
     sk_sp<GrSurface> createSurface(GrResourceProvider*) const override;
 
     size_t onUninstantiatedGpuMemorySize() const override;
diff --git a/src/gpu/GrTracing.h b/src/gpu/GrTracing.h
index 82c6f8d..64adf1d 100644
--- a/src/gpu/GrTracing.h
+++ b/src/gpu/GrTracing.h
@@ -16,7 +16,7 @@
  * Context level GrTracing macros, classname and op are const char*, context is GrContext
  */
 #define GR_CREATE_TRACE_MARKER_CONTEXT(classname, op, context)                            \
-    GR_AUDIT_TRAIL_AUTO_FRAME(context->priv().getAuditTrail(), classname "::" op); \
+    GR_AUDIT_TRAIL_AUTO_FRAME(context->priv().auditTrail(), classname "::" op); \
     TRACE_EVENT0("skia.gpu", classname "::" op)
 
 #endif
diff --git a/src/gpu/GrUserStencilSettings.h b/src/gpu/GrUserStencilSettings.h
index 2549c44..add8d16 100644
--- a/src/gpu/GrUserStencilSettings.h
+++ b/src/gpu/GrUserStencilSettings.h
@@ -33,10 +33,11 @@
  */
 
 enum GrStencilFlags {
-    kDisabled_StencilFlag         = 0x1,
-    kNoModifyStencil_StencilFlag  = 0x2,
-    kNoWrapOps_StencilFlag        = 0x4,
-    kSingleSided_StencilFlag      = 0x8,
+    kDisabled_StencilFlag         = (1 << 0),
+    kTestAlwaysPasses_StencilFlag = (1 << 1),
+    kNoModifyStencil_StencilFlag  = (1 << 2),
+    kNoWrapOps_StencilFlag        = (1 << 3),
+    kSingleSided_StencilFlag      = (1 << 4),
 
     kLast_StencilFlag = kSingleSided_StencilFlag,
     kAll_StencilFlags = kLast_StencilFlag | (kLast_StencilFlag - 1)
@@ -188,6 +189,9 @@
     bool isDisabled(bool hasStencilClip) const {
         return this->flags(hasStencilClip) & kDisabled_StencilFlag;
     }
+    bool testAlwaysPasses(bool hasStencilClip) const {
+        return this->flags(hasStencilClip) & kTestAlwaysPasses_StencilFlag;
+    }
     bool isTwoSided(bool hasStencilClip) const {
         return !(this->flags(hasStencilClip) & kSingleSided_StencilFlag);
     }
@@ -235,6 +239,7 @@
     }
     constexpr static uint16_t Flags(bool hasStencilClip) {
         return (IsDisabled(hasStencilClip) ? kDisabled_StencilFlag : 0) |
+               (TestAlwaysPasses(hasStencilClip) ? kTestAlwaysPasses_StencilFlag : 0) |
                (DoesNotModifyStencil(hasStencilClip) ? kNoModifyStencil_StencilFlag : 0) |
                (UsesWrapOps() ? 0 : kNoWrapOps_StencilFlag);
     }
diff --git a/src/gpu/GrXferProcessor.cpp b/src/gpu/GrXferProcessor.cpp
index fb2eeea..608bb82 100644
--- a/src/gpu/GrXferProcessor.cpp
+++ b/src/gpu/GrXferProcessor.cpp
@@ -166,12 +166,14 @@
         const GrXPFactory* factory,
         const GrProcessorAnalysisColor& color,
         const GrProcessorAnalysisCoverage& coverage,
-        const GrCaps& caps) {
+        const GrCaps& caps,
+        GrClampType clampType) {
     AnalysisProperties result;
     if (factory) {
-        result = factory->analysisProperties(color, coverage, caps);
+        result = factory->analysisProperties(color, coverage, caps, clampType);
     } else {
-        result = GrPorterDuffXPFactory::SrcOverAnalysisProperties(color, coverage, caps);
+        result = GrPorterDuffXPFactory::SrcOverAnalysisProperties(color, coverage, caps,
+                                                                  clampType);
     }
     SkASSERT(!(result & AnalysisProperties::kRequiresDstTexture));
     if ((result & AnalysisProperties::kReadsDstInShader) &&
@@ -186,10 +188,11 @@
                                                             const GrProcessorAnalysisColor& color,
                                                             GrProcessorAnalysisCoverage coverage,
                                                             bool hasMixedSamples,
-                                                            const GrCaps& caps) {
+                                                            const GrCaps& caps,
+                                                            GrClampType clampType) {
     SkASSERT(!hasMixedSamples || caps.shaderCaps()->dualSourceBlendingSupport());
     if (factory) {
-        return factory->makeXferProcessor(color, coverage, hasMixedSamples, caps);
+        return factory->makeXferProcessor(color, coverage, hasMixedSamples, caps, clampType);
     } else {
         return GrPorterDuffXPFactory::MakeSrcOverXferProcessor(color, coverage, hasMixedSamples,
                                                                caps);
diff --git a/src/gpu/GrXferProcessor.h b/src/gpu/GrXferProcessor.h
index 295bf00..008394b 100644
--- a/src/gpu/GrXferProcessor.h
+++ b/src/gpu/GrXferProcessor.h
@@ -95,10 +95,6 @@
             }
         }
 
-        bool instantiate(GrResourceProvider* resourceProvider) {
-            return SkToBool(fProxy->instantiate(resourceProvider));
-        }
-
     private:
         sk_sp<GrTextureProxy> fProxy;
         SkIPoint              fOffset;
@@ -264,7 +260,7 @@
         /**
          * The op may apply coverage as alpha and still blend correctly.
          */
-        kCompatibleWithAlphaAsCoverage = 0x2,
+        kCompatibleWithCoverageAsAlpha = 0x2,
         /**
          * The color input to the GrXferProcessor will be ignored.
          */
@@ -286,12 +282,14 @@
                                                           const GrProcessorAnalysisColor&,
                                                           GrProcessorAnalysisCoverage,
                                                           bool hasMixedSamples,
-                                                          const GrCaps& caps);
+                                                          const GrCaps& caps,
+                                                          GrClampType);
 
     static AnalysisProperties GetAnalysisProperties(const GrXPFactory*,
                                                     const GrProcessorAnalysisColor&,
                                                     const GrProcessorAnalysisCoverage&,
-                                                    const GrCaps&);
+                                                    const GrCaps&,
+                                                    GrClampType);
 
 protected:
     constexpr GrXPFactory() {}
@@ -300,7 +298,8 @@
     virtual sk_sp<const GrXferProcessor> makeXferProcessor(const GrProcessorAnalysisColor&,
                                                            GrProcessorAnalysisCoverage,
                                                            bool hasMixedSamples,
-                                                           const GrCaps&) const = 0;
+                                                           const GrCaps&,
+                                                           GrClampType) const = 0;
 
     /**
      * Subclass analysis implementation. This should not return kNeedsDstInTexture as that will be
@@ -308,7 +307,8 @@
      */
     virtual AnalysisProperties analysisProperties(const GrProcessorAnalysisColor&,
                                                   const GrProcessorAnalysisCoverage&,
-                                                  const GrCaps&) const = 0;
+                                                  const GrCaps&,
+                                                  GrClampType) const = 0;
 };
 #if defined(__GNUC__)
 #pragma GCC diagnostic pop
diff --git a/src/gpu/GrYUVProvider.cpp b/src/gpu/GrYUVProvider.cpp
index a4cab0a..1153742 100644
--- a/src/gpu/GrYUVProvider.cpp
+++ b/src/gpu/GrYUVProvider.cpp
@@ -6,19 +6,21 @@
  */
 
 #include "GrYUVProvider.h"
+
+#include "GrCaps.h"
 #include "GrClip.h"
 #include "GrColorSpaceXform.h"
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrTextureProxy.h"
 #include "SkAutoMalloc.h"
 #include "SkCachedData.h"
 #include "SkRefCnt.h"
 #include "SkResourceCache.h"
-#include "SkYUVPlanesCache.h"
 #include "SkYUVAIndex.h"
+#include "SkYUVPlanesCache.h"
 #include "effects/GrYUVtoRGBEffect.h"
 
 sk_sp<SkCachedData> GrYUVProvider::getPlanes(SkYUVASizeInfo* size,
@@ -101,7 +103,7 @@
     cachedData->unref();
 }
 
-sk_sp<GrTextureProxy> GrYUVProvider::refAsTextureProxy(GrContext* ctx,
+sk_sp<GrTextureProxy> GrYUVProvider::refAsTextureProxy(GrRecordingContext* ctx,
                                                        const GrBackendFormat& format,
                                                        const GrSurfaceDesc& desc,
                                                        SkColorSpace* srcColorSpace,
@@ -148,7 +150,11 @@
                                                           dataStoragePtr);
 
         auto proxyProvider = ctx->priv().proxyProvider();
-        yuvTextureProxies[i] = proxyProvider->createTextureProxy(yuvImage, kNone_GrSurfaceFlags,
+        auto clearFlag = kNone_GrSurfaceFlags;
+        if (ctx->priv().caps()->shouldInitializeTextures() && fit == SkBackingFit::kApprox) {
+            clearFlag = kPerformInitialClear_GrSurfaceFlag;
+        }
+        yuvTextureProxies[i] = proxyProvider->createTextureProxy(yuvImage, clearFlag,
                                                                  1, SkBudgeted::kYes, fit);
 
         SkASSERT(yuvTextureProxies[i]->width() == yuvSizeInfo.fSizes[i].fWidth);
diff --git a/src/gpu/GrYUVProvider.h b/src/gpu/GrYUVProvider.h
index 633ccfb..e09c37a 100644
--- a/src/gpu/GrYUVProvider.h
+++ b/src/gpu/GrYUVProvider.h
@@ -13,8 +13,8 @@
 #include "SkYUVAIndex.h"
 #include "SkYUVASizeInfo.h"
 
-class GrContext;
 class GrBackendFormat;
+class GrRecordingContext;
 struct GrSurfaceDesc;
 class GrTexture;
 class GrTextureProxy;
@@ -41,7 +41,7 @@
      *
      *  On failure (e.g. the provider had no data), this returns NULL.
      */
-    sk_sp<GrTextureProxy> refAsTextureProxy(GrContext*,
+    sk_sp<GrTextureProxy> refAsTextureProxy(GrRecordingContext*,
                                             const GrBackendFormat&,
                                             const GrSurfaceDesc&,
                                             SkColorSpace* srcColorSpace,
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index dc7aeea..9f05af7 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -10,7 +10,6 @@
 #include "../private/SkShadowFlags.h"
 #include "GrBitmapTextureMaker.h"
 #include "GrBlurUtils.h"
-#include "GrColorSpaceXform.h"
 #include "GrContext.h"
 #include "GrContextPriv.h"
 #include "GrGpu.h"
@@ -20,9 +19,9 @@
 #include "GrStyle.h"
 #include "GrSurfaceProxyPriv.h"
 #include "GrTextureAdjuster.h"
-#include "GrTextureProxy.h"
 #include "GrTracing.h"
 #include "SkCanvasPriv.h"
+#include "SkClipStack.h"
 #include "SkDraw.h"
 #include "SkGr.h"
 #include "SkImageFilter.h"
@@ -31,7 +30,6 @@
 #include "SkImage_Base.h"
 #include "SkLatticeIter.h"
 #include "SkMakeUnique.h"
-#include "SkMaskFilterBase.h"
 #include "SkPathEffect.h"
 #include "SkPicture.h"
 #include "SkPictureData.h"
@@ -49,14 +47,12 @@
 #include "SkVertState.h"
 #include "SkVertices.h"
 #include "SkWritePixelsRec.h"
-#include "SkYUVAIndex.h"
 #include "effects/GrBicubicEffect.h"
-#include "effects/GrSimpleTextureEffect.h"
 #include "effects/GrTextureDomain.h"
 #include "text/GrTextTarget.h"
 
 #define ASSERT_SINGLE_OWNER \
-SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(fContext->priv().debugSingleOwner());)
+SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(fContext->priv().singleOwner());)
 
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -87,7 +83,7 @@
                                      sk_sp<GrRenderTargetContext> renderTargetContext,
                                      int width, int height,
                                      InitContents init) {
-    if (!renderTargetContext || renderTargetContext->wasAbandoned()) {
+    if (!renderTargetContext || context->priv().abandoned()) {
         return nullptr;
     }
     unsigned flags;
@@ -268,7 +264,7 @@
     SkASSERT(newRTC->asSurfaceProxy()->priv().isExact());
 
     if (shouldRetainContent) {
-        if (fRenderTargetContext->wasAbandoned()) {
+        if (this->context()->abandoned()) {
             return;
         }
         newRTC->copy(fRenderTargetContext->asSurfaceProxy());
@@ -402,14 +398,13 @@
                                    this->ctm(), rect, &style);
 }
 
-void SkGpuDevice::drawEdgeAARect(const SkRect& r, SkCanvas::QuadAAFlags aa, SkColor color,
-                                 SkBlendMode mode) {
+void SkGpuDevice::drawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
+                                 SkCanvas::QuadAAFlags aaFlags, SkColor color, SkBlendMode mode) {
     ASSERT_SINGLE_OWNER
-    GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawEdgeAARect", fContext.get());
+    GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawEdgeAAQuad", fContext.get());
 
     SkPMColor4f dstColor = SkColor4fPrepForDst(SkColor4f::FromColor(color),
-                                              fRenderTargetContext->colorSpaceInfo(),
-                                              *fContext->priv().caps())
+                                              fRenderTargetContext->colorSpaceInfo())
                            .premul();
 
     GrPaint grPaint;
@@ -418,8 +413,17 @@
         grPaint.setXPFactory(SkBlendMode_AsXPFactory(mode));
     }
 
-    fRenderTargetContext->fillRectWithEdgeAA(this->clip(), std::move(grPaint),
-                                             SkToGrQuadAAFlags(aa), this->ctm(), r);
+    // This is exclusively meant for tiling operations, so keep AA enabled to handle MSAA seaming
+    GrQuadAAFlags grAA = SkToGrQuadAAFlags(aaFlags);
+    if (clip) {
+        // Use fillQuadWithEdgeAA
+        fRenderTargetContext->fillQuadWithEdgeAA(this->clip(), std::move(grPaint), GrAA::kYes, grAA,
+                                                 this->ctm(), clip, nullptr);
+    } else {
+        // Use fillRectWithEdgeAA to preserve mathematical properties of dst being rectangular
+        fRenderTargetContext->fillRectWithEdgeAA(this->clip(), std::move(grPaint), GrAA::kYes, grAA,
+                                                 this->ctm(), rect);
+    }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -706,6 +710,10 @@
     }
 }
 
+const GrCaps* SkGpuDevice::caps() const {
+    return fContext->priv().caps();
+}
+
 bool SkGpuDevice::shouldTileImageID(uint32_t imageID,
                                     const SkIRect& imageRect,
                                     const SkMatrix& viewMatrix,
@@ -793,62 +801,6 @@
                                    &outClippedSrcRect);
 }
 
-void SkGpuDevice::drawBitmap(const SkBitmap& bitmap,
-                             SkScalar x,
-                             SkScalar y,
-                             const SkPaint& paint) {
-    SkMatrix m = SkMatrix::MakeTrans(x, y);
-    ASSERT_SINGLE_OWNER
-    SkMatrix viewMatrix;
-    viewMatrix.setConcat(this->ctm(), m);
-
-    int maxTileSize = this->caps()->maxTileSize();
-
-    // The tile code path doesn't currently support AA, so if the paint asked for aa and we could
-    // draw untiled, then we bypass checking for tiling purely for optimization reasons.
-    bool drawAA = GrFSAAType::kUnifiedMSAA != fRenderTargetContext->fsaaType() &&
-                  paint.isAntiAlias() && bitmap.width() <= maxTileSize &&
-                  bitmap.height() <= maxTileSize;
-
-    bool skipTileCheck = drawAA || paint.getMaskFilter();
-
-    if (!skipTileCheck) {
-        SkRect srcRect = SkRect::MakeIWH(bitmap.width(), bitmap.height());
-        int tileSize;
-        SkIRect clippedSrcRect;
-
-        GrSamplerState samplerState;
-        bool doBicubic;
-        GrSamplerState::Filter textureFilterMode = GrSkFilterQualityToGrFilterMode(
-                paint.getFilterQuality(), viewMatrix, SkMatrix::I(),
-                fContext->priv().options().fSharpenMipmappedTextures, &doBicubic);
-
-        int tileFilterPad;
-
-        if (doBicubic) {
-            tileFilterPad = GrBicubicEffect::kFilterTexelPad;
-        } else if (GrSamplerState::Filter::kNearest == textureFilterMode) {
-            tileFilterPad = 0;
-        } else {
-            tileFilterPad = 1;
-        }
-        samplerState.setFilterMode(textureFilterMode);
-
-        int maxTileSizeForFilter = this->caps()->maxTileSize() - 2 * tileFilterPad;
-        if (this->shouldTileImageID(bitmap.getGenerationID(), bitmap.getSubset(), viewMatrix,
-                                    SkMatrix::I(), samplerState, &srcRect, maxTileSizeForFilter,
-                                    &tileSize, &clippedSrcRect)) {
-            this->drawTiledBitmap(bitmap, viewMatrix, SkMatrix::I(), srcRect, clippedSrcRect,
-                                  samplerState, paint, SkCanvas::kStrict_SrcRectConstraint,
-                                  tileSize, doBicubic);
-            return;
-        }
-    }
-    GrBitmapTextureMaker maker(fContext.get(), bitmap);
-    this->drawTextureProducer(&maker, nullptr, nullptr, SkCanvas::kStrict_SrcRectConstraint,
-                              viewMatrix, paint, true);
-}
-
 // This method outsets 'iRect' by 'outset' all around and then clamps its extents to
 // 'clamp'. 'offset' is adjusted to remain positioned over the top-left corner
 // of 'iRect' for all possible outsets/clamps.
@@ -898,7 +850,7 @@
 
     // This is the funnel for all paths that draw tiled bitmaps/images. Log histogram entries.
     SK_HISTOGRAM_BOOLEAN("DrawTiled", true);
-    LogDrawScaleFactor(viewMatrix, origPaint.getFilterQuality());
+    LogDrawScaleFactor(viewMatrix, SkMatrix::I(), origPaint.getFilterQuality());
 
     const SkPaint* paint = &origPaint;
     SkPaint tempPaint;
@@ -1019,7 +971,7 @@
             domain.fTop = domain.fBottom = srcRect.centerY();
         }
         if (bicubic) {
-            fp = GrBicubicEffect::Make(std::move(proxy), texMatrix, domain);
+            fp = GrBicubicEffect::Make(std::move(proxy), texMatrix, domain, bitmap.alphaType());
         } else {
             fp = GrTextureDomainEffect::Make(std::move(proxy), texMatrix, domain,
                                              GrTextureDomain::kClamp_Mode, samplerState.filter());
@@ -1027,7 +979,7 @@
     } else if (bicubic) {
         SkASSERT(GrSamplerState::Filter::kNearest == samplerState.filter());
         GrSamplerState::WrapMode wrapMode[2] = {samplerState.wrapModeX(), samplerState.wrapModeY()};
-        fp = GrBicubicEffect::Make(std::move(proxy), texMatrix, wrapMode);
+        fp = GrBicubicEffect::Make(std::move(proxy), texMatrix, wrapMode, bitmap.alphaType());
     } else {
         fp = GrSimpleTextureEffect::Make(std::move(proxy), texMatrix, samplerState);
     }
@@ -1052,7 +1004,7 @@
     ASSERT_SINGLE_OWNER
     GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawSprite", fContext.get());
 
-    if (fContext->abandoned()) {
+    if (fContext->priv().abandoned()) {
         return;
     }
 
@@ -1070,8 +1022,6 @@
     ASSERT_SINGLE_OWNER
     GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawSpecial", fContext.get());
 
-    // TODO: clipImage support.
-
     sk_sp<SkSpecialImage> result;
     if (paint.getImageFilter()) {
         SkIPoint offset = { 0, 0 };
@@ -1095,10 +1045,11 @@
 
     const GrPixelConfig config = proxy->config();
 
+    SkMatrix ctm = this->ctm();
+    ctm.postTranslate(-SkIntToScalar(left), -SkIntToScalar(top));
+
     SkPaint tmpUnfiltered(paint);
     if (tmpUnfiltered.getMaskFilter()) {
-        SkMatrix ctm = this->ctm();
-        ctm.postTranslate(-SkIntToScalar(left), -SkIntToScalar(top));
         tmpUnfiltered.setMaskFilter(tmpUnfiltered.getMaskFilter()->makeWithMatrix(ctm));
     }
 
@@ -1120,14 +1071,57 @@
     }
 
     const SkIRect& subset = result->subset();
+    SkRect dstRect = SkRect::Make(SkIRect::MakeXYWH(left, top, subset.width(), subset.height()));
+    SkRect srcRect = SkRect::Make(subset);
+    if (clipImage) {
+        // Add the image as a simple texture effect applied to coverage. Accessing content outside
+        // of the clip image should behave as if it were a decal (i.e. zero coverage). However, to
+        // limit pixels touched and hardware checks, we draw the clip image geometry to get the
+        // decal effect.
+        GrSamplerState sampler = paint.getFilterQuality() > kNone_SkFilterQuality ?
+                GrSamplerState::ClampBilerp() : GrSamplerState::ClampNearest();
+        sk_sp<GrTextureProxy> clipProxy = as_IB(clipImage)->asTextureProxyRef(this->context(),
+                                                                              sampler, nullptr);
+        // Fold clip matrix into ctm
+        ctm.preConcat(clipMatrix);
+        SkMatrix inverseClipMatrix;
 
-    fRenderTargetContext->fillRectToRect(
-            this->clip(),
-            std::move(grPaint),
-            GrAA(tmpUnfiltered.isAntiAlias()),
-            SkMatrix::I(),
-            SkRect::Make(SkIRect::MakeXYWH(left, top, subset.width(), subset.height())),
-            SkRect::Make(subset));
+        std::unique_ptr<GrFragmentProcessor> cfp;
+        if (clipProxy && ctm.invert(&inverseClipMatrix)) {
+            cfp = GrSimpleTextureEffect::Make(std::move(clipProxy), inverseClipMatrix, sampler);
+            if (clipImage->colorType() != kAlpha_8_SkColorType) {
+                cfp = GrFragmentProcessor::SwizzleOutput(std::move(cfp), GrSwizzle::AAAA());
+            }
+        }
+
+        if (cfp) {
+            // If the grPaint already has coverage, this adds an additional stage that multiples
+            // the image's alpha channel with the prior coverage.
+            grPaint.addCoverageFragmentProcessor(std::move(cfp));
+
+            // Undo the offset that was needed for shader coord transforms to get the transform for
+            // the actual drawn geometry.
+            ctm.postTranslate(SkIntToScalar(left), SkIntToScalar(top));
+            inverseClipMatrix.preTranslate(-SkIntToScalar(left), -SkIntToScalar(top));
+            SkRect clipGeometry = SkRect::MakeWH(clipImage->width(), clipImage->height());
+            if (!clipGeometry.contains(inverseClipMatrix.mapRect(dstRect))) {
+                // Draw the clip geometry since it is smaller, using dstRect as an extra scissor
+                SkClipStack clip(this->cs());
+                clip.clipDevRect(SkIRect::MakeXYWH(left, top, subset.width(), subset.height()),
+                                 SkClipOp::kIntersect);
+                SkMatrix local = SkMatrix::Concat(SkMatrix::MakeRectToRect(
+                        dstRect, srcRect, SkMatrix::kFill_ScaleToFit), ctm);
+                fRenderTargetContext->fillRectWithLocalMatrix(GrClipStackClip(&clip),
+                        std::move(grPaint), GrAA(paint.isAntiAlias()), ctm, clipGeometry, local);
+                return;
+            }
+            // Else fall through and draw the subset since that is contained in the clip geometry
+        }
+        // Else some issue configuring the coverage FP, so just draw without the clip mask image
+    }
+    // Draw directly in screen space, possibly with an extra coverage processor
+    fRenderTargetContext->fillRectToRect(this->clip(), std::move(grPaint),
+            GrAA(paint.isAntiAlias()), SkMatrix::I(), dstRect, srcRect);
 }
 
 void SkGpuDevice::drawBitmapRect(const SkBitmap& bitmap,
@@ -1230,13 +1224,13 @@
 sk_sp<SkSpecialImage> SkGpuDevice::makeSpecial(const SkImage* image) {
     SkPixmap pm;
     if (image->isTextureBacked()) {
-        sk_sp<GrTextureProxy> proxy = as_IB(image)->asTextureProxyRef();
+        sk_sp<GrTextureProxy> proxy = as_IB(image)->asTextureProxyRef(this->context());
 
         return SkSpecialImage::MakeDeferredFromGpu(fContext.get(),
                                                    SkIRect::MakeWH(image->width(), image->height()),
                                                    image->uniqueID(),
                                                    std::move(proxy),
-                                                   as_IB(image)->onImageInfo().refColorSpace(),
+                                                   image->refColorSpace(),
                                                    &this->surfaceProps());
     } else if (image->peekPixels(&pm)) {
         SkBitmap bm;
@@ -1332,85 +1326,12 @@
     this->drawSpecial(srcImg.get(), left, top, paint, nullptr, SkMatrix::I());
 }
 
-void SkGpuDevice::drawImage(const SkImage* image, SkScalar x, SkScalar y, const SkPaint& paint) {
-    ASSERT_SINGLE_OWNER
-    SkMatrix viewMatrix = this->ctm();
-    viewMatrix.preTranslate(x, y);
-    if (as_IB(image)->isYUVA()) {
-        GrYUVAImageTextureMaker maker(fContext.get(), image);
-        this->drawTextureProducer(&maker, nullptr, nullptr, SkCanvas::kFast_SrcRectConstraint,
-                                  viewMatrix, paint, false);
-        return;
-    }
-    uint32_t pinnedUniqueID;
-    if (sk_sp<GrTextureProxy> proxy = as_IB(image)->refPinnedTextureProxy(&pinnedUniqueID)) {
-        this->drawPinnedTextureProxy(std::move(proxy), pinnedUniqueID, as_IB(image)->colorSpace(),
-                                     image->alphaType(), nullptr, nullptr,
-                                     SkCanvas::kFast_SrcRectConstraint, viewMatrix, paint);
-        return;
-    }
-    SkBitmap bm;
-    if (this->shouldTileImage(image, nullptr, SkCanvas::kFast_SrcRectConstraint,
-                              paint.getFilterQuality(), viewMatrix, SkMatrix::I())) {
-        // only support tiling as bitmap at the moment, so force raster-version
-        if (!as_IB(image)->getROPixels(&bm)) {
-            return;
-        }
-        this->drawBitmap(bm, x, y, paint);
-        return;
-    }
-    if (image->isLazyGenerated()) {
-        GrImageTextureMaker maker(fContext.get(), image, SkImage::kAllow_CachingHint);
-        this->drawTextureProducer(&maker, nullptr, nullptr, SkCanvas::kFast_SrcRectConstraint,
-                                  viewMatrix, paint, true);
-        return;
-    }
-    if (as_IB(image)->getROPixels(&bm)) {
-        GrBitmapTextureMaker maker(fContext.get(), bm);
-        this->drawTextureProducer(&maker, nullptr, nullptr, SkCanvas::kFast_SrcRectConstraint,
-                                  viewMatrix, paint, true);
-    }
-}
-
 void SkGpuDevice::drawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst,
                                 const SkPaint& paint, SkCanvas::SrcRectConstraint constraint) {
     ASSERT_SINGLE_OWNER
-    if (!src || src->contains(image->bounds())) {
-        constraint = SkCanvas::kFast_SrcRectConstraint;
-    }
-    if (as_IB(image)->isYUVA()) {
-        GrYUVAImageTextureMaker maker(fContext.get(), image);
-        this->drawTextureProducer(&maker, src, &dst, constraint, this->ctm(), paint, false);
-        return;
-    }
-    uint32_t pinnedUniqueID;
-    if (sk_sp<GrTextureProxy> proxy = as_IB(image)->refPinnedTextureProxy(&pinnedUniqueID)) {
-        this->drawPinnedTextureProxy(std::move(proxy), pinnedUniqueID, as_IB(image)->colorSpace(),
-                                     image->alphaType(), src, &dst, constraint, this->ctm(), paint);
-        return;
-    }
-    SkBitmap bm;
-    SkMatrix srcToDstRect;
-    srcToDstRect.setRectToRect((src ? *src : SkRect::MakeIWH(image->width(), image->height())),
-                               dst, SkMatrix::kFill_ScaleToFit);
-    if (this->shouldTileImage(image, src, constraint, paint.getFilterQuality(), this->ctm(),
-                              srcToDstRect)) {
-        // only support tiling as bitmap at the moment, so force raster-version
-        if (!as_IB(image)->getROPixels(&bm)) {
-            return;
-        }
-        this->drawBitmapRect(bm, src, dst, paint, constraint);
-        return;
-    }
-    if (image->isLazyGenerated()) {
-        GrImageTextureMaker maker(fContext.get(), image, SkImage::kAllow_CachingHint);
-        this->drawTextureProducer(&maker, src, &dst, constraint, this->ctm(), paint, true);
-        return;
-    }
-    if (as_IB(image)->getROPixels(&bm)) {
-        GrBitmapTextureMaker maker(fContext.get(), bm);
-        this->drawTextureProducer(&maker, src, &dst, constraint, this->ctm(), paint, true);
-    }
+    GrQuadAAFlags aaFlags = paint.isAntiAlias() ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone;
+    this->drawImageQuad(image, src, &dst, nullptr, GrAA(paint.isAntiAlias()), aaFlags, nullptr,
+                        paint, constraint);
 }
 
 // When drawing nine-patches or n-patches, cap the filter quality at kBilerp.
@@ -1427,10 +1348,10 @@
     ASSERT_SINGLE_OWNER
     uint32_t pinnedUniqueID;
     auto iter = skstd::make_unique<SkLatticeIter>(image->width(), image->height(), center, dst);
-    if (sk_sp<GrTextureProxy> proxy = as_IB(image)->refPinnedTextureProxy(&pinnedUniqueID)) {
-        GrTextureAdjuster adjuster(this->context(), std::move(proxy),
-                                   image->alphaType(), pinnedUniqueID,
-                                   as_IB(image)->onImageInfo().colorSpace());
+    if (sk_sp<GrTextureProxy> proxy = as_IB(image)->refPinnedTextureProxy(this->context(),
+                                                                          &pinnedUniqueID)) {
+        GrTextureAdjuster adjuster(this->context(), std::move(proxy), image->alphaType(),
+                                   pinnedUniqueID, image->colorSpace());
         this->drawProducerLattice(&adjuster, std::move(iter), dst, paint);
     } else {
         SkBitmap bm;
@@ -1469,7 +1390,7 @@
 
     auto dstColorSpace = fRenderTargetContext->colorSpaceInfo().colorSpace();
     const GrSamplerState::Filter filter = compute_lattice_filter_mode(*paint);
-    auto proxy = producer->refTextureProxyForParams(filter, nullptr);
+    auto proxy = producer->refTextureProxyForParams(&filter, nullptr);
     if (!proxy) {
         return;
     }
@@ -1487,10 +1408,10 @@
     ASSERT_SINGLE_OWNER
     uint32_t pinnedUniqueID;
     auto iter = skstd::make_unique<SkLatticeIter>(lattice, dst);
-    if (sk_sp<GrTextureProxy> proxy = as_IB(image)->refPinnedTextureProxy(&pinnedUniqueID)) {
-        GrTextureAdjuster adjuster(this->context(), std::move(proxy),
-                                   image->alphaType(), pinnedUniqueID,
-                                   as_IB(image)->onImageInfo().colorSpace());
+    if (sk_sp<GrTextureProxy> proxy = as_IB(image)->refPinnedTextureProxy(this->context(),
+                                                                          &pinnedUniqueID)) {
+        GrTextureAdjuster adjuster(this->context(), std::move(proxy), image->alphaType(),
+                                   pinnedUniqueID, image->colorSpace());
         this->drawProducerLattice(&adjuster, std::move(iter), dst, paint);
     } else {
         SkBitmap bm;
@@ -1513,71 +1434,6 @@
     this->drawProducerLattice(&maker, std::move(iter), dst, paint);
 }
 
-void SkGpuDevice::drawImageSet(const SkCanvas::ImageSetEntry set[], int count,
-                               SkFilterQuality filterQuality, SkBlendMode mode) {
-    SkASSERT(count > 0);
-
-    GrSamplerState sampler;
-    sampler.setFilterMode(kNone_SkFilterQuality == filterQuality ? GrSamplerState::Filter::kNearest
-                                                                 : GrSamplerState::Filter::kBilerp);
-    SkAutoTArray<GrRenderTargetContext::TextureSetEntry> textures(count);
-    // We accumulate compatible proxies until we find an an incompatible one or reach the end and
-    // issue the accumulated 'n' draws starting at 'base'.
-    int base = 0, n = 0;
-    auto draw = [&] {
-        if (n > 0) {
-            auto textureXform = GrColorSpaceXform::Make(
-                    set[base].fImage->colorSpace(), set[base].fImage->alphaType(),
-                    fRenderTargetContext->colorSpaceInfo().colorSpace(), kPremul_SkAlphaType);
-            fRenderTargetContext->drawTextureSet(this->clip(), textures.get() + base, n,
-                                                 sampler.filter(), mode, this->ctm(),
-                                                 std::move(textureXform));
-        }
-    };
-    for (int i = 0; i < count; ++i) {
-        // The default SkBaseDevice implementation is based on drawImageRect which does not allow
-        // non-sorted src rects. TODO: Decide this is OK or make sure we handle it.
-        if (!set[i].fSrcRect.isSorted()) {
-            draw();
-            base = i + 1;
-            n = 0;
-            continue;
-        }
-        uint32_t uniqueID;
-        textures[i].fProxy = as_IB(set[i].fImage.get())->refPinnedTextureProxy(&uniqueID);
-        if (!textures[i].fProxy) {
-            textures[i].fProxy =
-                    as_IB(set[i].fImage.get())
-                            ->asTextureProxyRef(fContext.get(), GrSamplerState::ClampBilerp(),
-                                                nullptr);
-            // If we failed to make a proxy then flush the accumulated set and reset for the next
-            // image.
-            if (!textures[i].fProxy) {
-                draw();
-                base = i + 1;
-                n = 0;
-                continue;
-            }
-        }
-        textures[i].fSrcRect = set[i].fSrcRect;
-        textures[i].fDstRect = set[i].fDstRect;
-        textures[i].fAlpha = set[i].fAlpha;
-        textures[i].fAAFlags = SkToGrQuadAAFlags(set[i].fAAFlags);
-        if (n > 0 &&
-            (!GrTextureProxy::ProxiesAreCompatibleAsDynamicState(textures[i].fProxy.get(),
-                                                                 textures[base].fProxy.get()) ||
-             set[i].fImage->alphaType() != set[base].fImage->alphaType() ||
-             !SkColorSpace::Equals(set[i].fImage->colorSpace(), set[base].fImage->colorSpace()))) {
-            draw();
-            base = i;
-            n = 1;
-        } else {
-            ++n;
-        }
-    }
-    draw();
-}
-
 static bool init_vertices_paint(GrContext* context, const GrColorSpaceInfo& colorSpaceInfo,
                                 const SkPaint& skPaint, const SkMatrix& matrix, SkBlendMode bmode,
                                 bool hasTexs, bool hasColors, GrPaint* grPaint) {
@@ -1712,12 +1568,7 @@
                             const SkRect texRect[], const SkColor colors[], int count,
                             SkBlendMode mode, const SkPaint& paint) {
     ASSERT_SINGLE_OWNER
-    if (paint.isAntiAlias()) {
-        this->INHERITED::drawAtlas(atlas, xform, texRect, colors, count, mode, paint);
-        return;
-    }
-
-    GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawText", fContext.get());
+    GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawAtlas", fContext.get());
 
     SkPaint p(paint);
     p.setShader(atlas->makeShader());
@@ -1776,14 +1627,18 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void SkGpuDevice::flush() {
-    this->flushAndSignalSemaphores(0, nullptr);
+    this->flushAndSignalSemaphores(SkSurface::BackendSurfaceAccess::kNoAccess,
+                                   kNone_GrFlushFlags, 0, nullptr);
 }
 
-GrSemaphoresSubmitted SkGpuDevice::flushAndSignalSemaphores(int numSemaphores,
+GrSemaphoresSubmitted SkGpuDevice::flushAndSignalSemaphores(SkSurface::BackendSurfaceAccess access,
+                                                            GrFlushFlags flags,
+                                                            int numSemaphores,
                                                             GrBackendSemaphore signalSemaphores[]) {
     ASSERT_SINGLE_OWNER
 
-    return fRenderTargetContext->prepareForExternalIO(numSemaphores, signalSemaphores);
+    return fRenderTargetContext->prepareForExternalIO(access, flags, numSemaphores,
+                                                      signalSemaphores);
 }
 
 bool SkGpuDevice::wait(int numSemaphores, const GrBackendSemaphore* waitSemaphores) {
diff --git a/src/gpu/SkGpuDevice.h b/src/gpu/SkGpuDevice.h
index 3710707..e36286b 100644
--- a/src/gpu/SkGpuDevice.h
+++ b/src/gpu/SkGpuDevice.h
@@ -72,8 +72,6 @@
     void drawPoints(SkCanvas::PointMode mode, size_t count, const SkPoint[],
                     const SkPaint& paint) override;
     void drawRect(const SkRect& r, const SkPaint& paint) override;
-    void drawEdgeAARect(const SkRect& r, SkCanvas::QuadAAFlags edgeAA, SkColor color,
-                        SkBlendMode mode) override;
     void drawRRect(const SkRRect& r, const SkPaint& paint) override;
     void drawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) override;
     void drawRegion(const SkRegion& r, const SkPaint& paint) override;
@@ -81,7 +79,6 @@
     void drawArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
                  bool useCenter, const SkPaint& paint) override;
     void drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable) override;
-    void drawBitmap(const SkBitmap&, SkScalar x, SkScalar y, const SkPaint&) override;
     void drawBitmapRect(const SkBitmap&, const SkRect* srcOrNull, const SkRect& dst,
                         const SkPaint& paint, SkCanvas::SrcRectConstraint) override;
     void drawSprite(const SkBitmap& bitmap, int x, int y,
@@ -94,7 +91,6 @@
                    const SkColor[], int count, SkBlendMode, const SkPaint&) override;
     void drawDevice(SkBaseDevice*, int x, int y, const SkPaint&) override;
 
-    void drawImage(const SkImage*, SkScalar x, SkScalar y, const SkPaint&) override;
     void drawImageRect(const SkImage*, const SkRect* src, const SkRect& dst,
                        const SkPaint&, SkCanvas::SrcRectConstraint) override;
 
@@ -107,20 +103,26 @@
                           const SkRect& dst, const SkPaint&) override;
     void drawBitmapLattice(const SkBitmap&, const SkCanvas::Lattice&,
                            const SkRect& dst, const SkPaint&) override;
-    void drawImageSet(const SkCanvas::ImageSetEntry[], int count, SkFilterQuality,
-                      SkBlendMode) override;
 
     void drawDrawable(SkDrawable*, const SkMatrix*, SkCanvas* canvas) override;
 
     void drawSpecial(SkSpecialImage*, int left, int top, const SkPaint& paint,
                      SkImage*, const SkMatrix&) override;
+
+    void drawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
+                        SkCanvas::QuadAAFlags aaFlags, SkColor color, SkBlendMode mode) override;
+    void drawEdgeAAImageSet(const SkCanvas::ImageSetEntry[], int count, const SkPoint dstClips[],
+                            const SkMatrix[], const SkPaint&, SkCanvas::SrcRectConstraint) override;
+
     sk_sp<SkSpecialImage> makeSpecial(const SkBitmap&) override;
     sk_sp<SkSpecialImage> makeSpecial(const SkImage*) override;
     sk_sp<SkSpecialImage> snapSpecial() override;
     sk_sp<SkSpecialImage> snapBackImage(const SkIRect&) override;
 
     void flush() override;
-    GrSemaphoresSubmitted flushAndSignalSemaphores(int numSemaphores,
+    GrSemaphoresSubmitted flushAndSignalSemaphores(SkSurface::BackendSurfaceAccess access,
+                                                   GrFlushFlags flags,
+                                                   int numSemaphores,
                                                    GrBackendSemaphore signalSemaphores[]);
     bool wait(int numSemaphores, const GrBackendSemaphore* waitSemaphores);
 
@@ -157,7 +159,7 @@
 
     GrClipStackClip clip() const { return GrClipStackClip(&this->cs()); }
 
-    const GrCaps* caps() const { return fContext->priv().caps(); }
+    const GrCaps* caps() const;
 
     /**
      * Helper functions called by drawBitmapCommon. By the time these are called the SkDraw's
@@ -208,16 +210,14 @@
                         bool bicubic,
                         bool needsTextureDomain);
 
-    void drawPinnedTextureProxy(sk_sp<GrTextureProxy>,
-                                uint32_t pinnedUniqueID,
-                                SkColorSpace*,
-                                SkAlphaType alphaType,
-                                const SkRect* srcRect,
-                                const SkRect* dstRect,
-                                SkCanvas::SrcRectConstraint,
-                                const SkMatrix& viewMatrix,
-                                const SkPaint&);
+    // If not null, dstClip must be contained inside dst and will also respect the edge AA flags.
+    // If 'preViewMatrix' is not null, final CTM will be this->ctm() * preViewMatrix.
+    void drawImageQuad(const SkImage*, const SkRect* src, const SkRect* dst,
+                       const SkPoint dstClip[4], GrAA aa, GrQuadAAFlags aaFlags,
+                       const SkMatrix* preViewMatrix, const SkPaint&, SkCanvas::SrcRectConstraint);
 
+    // TODO(michaelludwig): This can be removed once drawBitmapRect is removed from SkDevice
+    // so that drawImageQuad is the sole entry point into the draw-single-image op
     void drawTextureProducer(GrTextureProducer*,
                              const SkRect* srcRect,
                              const SkRect* dstRect,
@@ -226,14 +226,6 @@
                              const SkPaint&,
                              bool attemptDrawTexture);
 
-    void drawTextureProducerImpl(GrTextureProducer*,
-                                 const SkRect& clippedSrcRect,
-                                 const SkRect& clippedDstRect,
-                                 SkCanvas::SrcRectConstraint,
-                                 const SkMatrix& viewMatrix,
-                                 const SkMatrix& srcToDstMatrix,
-                                 const SkPaint&);
-
     void drawProducerLattice(GrTextureProducer*, std::unique_ptr<SkLatticeIter>, const SkRect& dst,
                              const SkPaint&);
 
diff --git a/src/gpu/SkGpuDevice_drawTexture.cpp b/src/gpu/SkGpuDevice_drawTexture.cpp
index 39385e6..8f4d772 100644
--- a/src/gpu/SkGpuDevice_drawTexture.cpp
+++ b/src/gpu/SkGpuDevice_drawTexture.cpp
@@ -6,9 +6,12 @@
  */
 
 #include "SkGpuDevice.h"
+
+#include "GrBitmapTextureMaker.h"
 #include "GrBlurUtils.h"
 #include "GrCaps.h"
 #include "GrColorSpaceXform.h"
+#include "GrImageTextureMaker.h"
 #include "GrRenderTargetContext.h"
 #include "GrShape.h"
 #include "GrStyle.h"
@@ -16,11 +19,15 @@
 #include "GrTextureMaker.h"
 #include "SkDraw.h"
 #include "SkGr.h"
+#include "SkImage_Base.h"
 #include "SkMaskFilterBase.h"
+#include "SkYUVAIndex.h"
 #include "effects/GrBicubicEffect.h"
-#include "effects/GrSimpleTextureEffect.h"
+#include "effects/generated/GrSimpleTextureEffect.h"
 #include "effects/GrTextureDomain.h"
 
+namespace {
+
 static inline bool use_shader(bool textureIsAlphaOnly, const SkPaint& paint) {
     return textureIsAlphaOnly && paint.getShader();
 }
@@ -87,30 +94,88 @@
     return false;
 }
 
+enum class ImageDrawMode {
+    // Src and dst have been restricted to the image content. May need to clamp, no need to decal.
+    kOptimized,
+    // Src and dst are their original sizes, requires use of a decal instead of plain clamping.
+    // This is used when a dst clip is provided and extends outside of the optimized dst rect.
+    kDecal,
+    // Src or dst are empty, or do not intersect the image content so don't draw anything.
+    kSkip
+};
+
+/**
+ * Optimize the src rect sampling area within an image (sized 'width' x 'height') such that
+ * 'outSrcRect' will be completely contained in the image's bounds. The corresponding rect
+ * to draw will be output to 'outDstRect'. The mapping between src and dst will be cached in
+ * 'srcToDst'. Outputs are not always updated when kSkip is returned.
+ *
+ * If 'origSrcRect' is null, implicitly use the image bounds. If 'origDstRect' is null, use the
+ * original src rect. 'dstClip' should be null when there is no additional clipping.
+ */
+static ImageDrawMode optimize_sample_area(const SkISize& image, const SkRect* origSrcRect,
+                                          const SkRect* origDstRect, const SkPoint dstClip[4],
+                                          SkRect* outSrcRect, SkRect* outDstRect,
+                                          SkMatrix* srcToDst) {
+    SkRect srcBounds = SkRect::MakeIWH(image.fWidth, image.fHeight);
+
+    SkRect src = origSrcRect ? *origSrcRect : srcBounds;
+    SkRect dst = origDstRect ? *origDstRect : src;
+
+    if (src.isEmpty() || dst.isEmpty()) {
+        return ImageDrawMode::kSkip;
+    }
+
+    if (outDstRect) {
+        srcToDst->setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
+    } else {
+        srcToDst->setIdentity();
+    }
+
+    if (origSrcRect && !srcBounds.contains(src)) {
+        if (!src.intersect(srcBounds)) {
+            return ImageDrawMode::kSkip;
+        }
+        srcToDst->mapRect(&dst, src);
+
+        // Both src and dst have gotten smaller. If dstClip is provided, confirm it is still
+        // contained in dst, otherwise cannot optimize the sample area and must use a decal instead
+        if (dstClip) {
+            for (int i = 0; i < 4; ++i) {
+                if (!dst.contains(dstClip[i].fX, dstClip[i].fY)) {
+                    // Must resort to using a decal mode restricted to the clipped 'src', and
+                    // use the original dst rect (filling in src bounds as needed)
+                    *outSrcRect = src;
+                    *outDstRect = (origDstRect ? *origDstRect
+                                               : (origSrcRect ? *origSrcRect : srcBounds));
+                    return ImageDrawMode::kDecal;
+                }
+            }
+        }
+    }
+
+    // The original src and dst were fully contained in the image, or there was no dst clip to
+    // worry about, or the clip was still contained in the restricted dst rect.
+    *outSrcRect = src;
+    *outDstRect = dst;
+    return ImageDrawMode::kOptimized;
+}
+
 /**
  * Checks whether the paint is compatible with using GrRenderTargetContext::drawTexture. It is more
  * efficient than the GrTextureProducer general case.
  */
 static bool can_use_draw_texture(const SkPaint& paint) {
     return (!paint.getColorFilter() && !paint.getShader() && !paint.getMaskFilter() &&
-            !paint.getImageFilter() && paint.getFilterQuality() < kMedium_SkFilterQuality &&
-            paint.getBlendMode() == SkBlendMode::kSrcOver);
+            !paint.getImageFilter() && paint.getFilterQuality() < kMedium_SkFilterQuality);
 }
 
-static void draw_texture(const SkPaint& paint, const SkMatrix& ctm, const SkRect* src,
-                         const SkRect* dst, GrAA aa, SkCanvas::SrcRectConstraint constraint,
-                         sk_sp<GrTextureProxy> proxy, SkAlphaType alphaType,
-                         SkColorSpace* colorSpace, const GrClip& clip, GrRenderTargetContext* rtc) {
-    SkASSERT(!(SkToBool(src) && !SkToBool(dst)));
-    SkRect srcRect = src ? *src : SkRect::MakeWH(proxy->width(), proxy->height());
-    SkRect dstRect = dst ? *dst : srcRect;
-    if (src && !SkRect::MakeIWH(proxy->width(), proxy->height()).contains(srcRect)) {
-        // Shrink the src rect to be within bounds and proportionately shrink the dst rect.
-        SkMatrix srcToDst;
-        srcToDst.setRectToRect(srcRect, dstRect, SkMatrix::kFill_ScaleToFit);
-        SkAssertResult(srcRect.intersect(SkRect::MakeIWH(proxy->width(), proxy->height())));
-        srcToDst.mapRect(&dstRect, srcRect);
-    }
+// Assumes srcRect and dstRect have already been optimized to fit the proxy
+static void draw_texture(GrRenderTargetContext* rtc, const GrClip& clip, const SkMatrix& ctm,
+                         const SkPaint& paint, const SkRect& srcRect, const SkRect& dstRect,
+                         const SkPoint dstClip[4], GrAA aa, GrQuadAAFlags aaFlags,
+                         SkCanvas::SrcRectConstraint constraint, sk_sp<GrTextureProxy> proxy,
+                         SkAlphaType alphaType, SkColorSpace* colorSpace) {
     const GrColorSpaceInfo& dstInfo(rtc->colorSpaceInfo());
     auto textureXform =
         GrColorSpaceXform::Make(colorSpace          , alphaType,
@@ -127,131 +192,82 @@
         case kHigh_SkFilterQuality:
             SK_ABORT("Quality level not allowed.");
     }
+
+    // Must specify the strict constraint when the proxy is not functionally exact and the src
+    // rect would access pixels outside the proxy's content area without the constraint.
+    if (constraint != SkCanvas::kStrict_SrcRectConstraint &&
+        !GrProxyProvider::IsFunctionallyExact(proxy.get())) {
+        // Conservative estimate of how much a coord could be outset from src rect:
+        // 1/2 pixel for AA and 1/2 pixel for bilerp
+        float buffer = 0.5f * (aa == GrAA::kYes) +
+                       0.5f * (filter == GrSamplerState::Filter::kBilerp);
+        SkRect safeBounds = SkRect::MakeWH(proxy->width(), proxy->height());
+        safeBounds.inset(buffer, buffer);
+        if (!safeBounds.contains(srcRect)) {
+            constraint = SkCanvas::kStrict_SrcRectConstraint;
+        }
+    }
     SkPMColor4f color;
     if (GrPixelConfigIsAlphaOnly(proxy->config())) {
-        color = SkColor4fPrepForDst(paint.getColor4f(), dstInfo, *rtc->caps()).premul();
+        color = SkColor4fPrepForDst(paint.getColor4f(), dstInfo).premul();
     } else {
         float paintAlpha = paint.getColor4f().fA;
         color = { paintAlpha, paintAlpha, paintAlpha, paintAlpha };
     }
-    GrQuadAAFlags aaFlags = aa == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone;
-    rtc->drawTexture(clip, std::move(proxy), filter, color, srcRect, dstRect, aaFlags, constraint,
-                     ctm, std::move(textureXform));
-}
 
-//////////////////////////////////////////////////////////////////////////////
+    if (dstClip) {
+        // Get source coords corresponding to dstClip
+        SkPoint srcQuad[4];
+        GrMapRectPoints(dstRect, srcRect, dstClip, srcQuad, 4);
 
-void SkGpuDevice::drawPinnedTextureProxy(sk_sp<GrTextureProxy> proxy, uint32_t pinnedUniqueID,
-                                         SkColorSpace* colorSpace, SkAlphaType alphaType,
-                                         const SkRect* srcRect, const SkRect* dstRect,
-                                         SkCanvas::SrcRectConstraint constraint,
-                                         const SkMatrix& viewMatrix, const SkPaint& paint) {
-    GrAA aa = GrAA(paint.isAntiAlias());
-    if (can_use_draw_texture(paint)) {
-        draw_texture(paint, viewMatrix, srcRect, dstRect, aa, constraint, std::move(proxy),
-                     alphaType, colorSpace, this->clip(), fRenderTargetContext.get());
-        return;
+        rtc->drawTextureQuad(clip, std::move(proxy), filter, paint.getBlendMode(), color,
+                             srcQuad, dstClip, aa, aaFlags,
+                             constraint == SkCanvas::kStrict_SrcRectConstraint ? &srcRect : nullptr,
+                             ctm, std::move(textureXform));
+    } else {
+        rtc->drawTexture(clip, std::move(proxy), filter, paint.getBlendMode(), color, srcRect,
+                         dstRect, aa, aaFlags, constraint, ctm, std::move(textureXform));
     }
-    GrTextureAdjuster adjuster(this->context(), std::move(proxy), alphaType, pinnedUniqueID,
-                               colorSpace);
-    this->drawTextureProducer(&adjuster, srcRect, dstRect, constraint, viewMatrix, paint, false);
 }
 
-void SkGpuDevice::drawTextureProducer(GrTextureProducer* producer,
-                                      const SkRect* srcRect,
-                                      const SkRect* dstRect,
-                                      SkCanvas::SrcRectConstraint constraint,
-                                      const SkMatrix& viewMatrix,
-                                      const SkPaint& paint,
-                                      bool attemptDrawTexture) {
+// Assumes srcRect and dstRect have already been optimized to fit the proxy.
+static void draw_texture_producer(GrContext* context, GrRenderTargetContext* rtc,
+                                  const GrClip& clip, const SkMatrix& ctm,
+                                  const SkPaint& paint, GrTextureProducer* producer,
+                                  const SkRect& src, const SkRect& dst, const SkPoint dstClip[4],
+                                  const SkMatrix& srcToDst, GrAA aa, GrQuadAAFlags aaFlags,
+                                  SkCanvas::SrcRectConstraint constraint, bool attemptDrawTexture) {
     if (attemptDrawTexture && can_use_draw_texture(paint)) {
-        GrAA aa = GrAA(paint.isAntiAlias());
         // We've done enough checks above to allow us to pass ClampNearest() and not check for
         // scaling adjustments.
         auto proxy = producer->refTextureProxyForParams(GrSamplerState::ClampNearest(), nullptr);
         if (!proxy) {
             return;
         }
-        draw_texture(paint, viewMatrix, srcRect, dstRect, aa, constraint, std::move(proxy),
-                     producer->alphaType(), producer->colorSpace(), this->clip(),
-                     fRenderTargetContext.get());
+
+        draw_texture(rtc, clip, ctm, paint, src, dst, dstClip, aa, aaFlags, constraint,
+                     std::move(proxy), producer->alphaType(),  producer->colorSpace());
         return;
     }
 
-    // This is the funnel for all non-tiled bitmap/image draw calls. Log a histogram entry.
-    SK_HISTOGRAM_BOOLEAN("DrawTiled", false);
-
-    // Figure out the actual dst and src rect by clipping the src rect to the bounds of the
-    // adjuster. If the src rect is clipped then the dst rect must be recomputed. Also determine
-    // the matrix that maps the src rect to the dst rect.
-    SkRect clippedSrcRect;
-    SkRect clippedDstRect;
-    const SkRect srcBounds = SkRect::MakeIWH(producer->width(), producer->height());
-    SkMatrix srcToDstMatrix;
-    if (srcRect) {
-        if (!dstRect) {
-            dstRect = &srcBounds;
-        }
-        if (!srcBounds.contains(*srcRect)) {
-            clippedSrcRect = *srcRect;
-            if (!clippedSrcRect.intersect(srcBounds)) {
-                return;
-            }
-            if (!srcToDstMatrix.setRectToRect(*srcRect, *dstRect, SkMatrix::kFill_ScaleToFit)) {
-                return;
-            }
-            srcToDstMatrix.mapRect(&clippedDstRect, clippedSrcRect);
-        } else {
-            clippedSrcRect = *srcRect;
-            clippedDstRect = *dstRect;
-            if (!srcToDstMatrix.setRectToRect(*srcRect, *dstRect, SkMatrix::kFill_ScaleToFit)) {
-                return;
-            }
-        }
-    } else {
-        clippedSrcRect = srcBounds;
-        if (dstRect) {
-            clippedDstRect = *dstRect;
-            if (!srcToDstMatrix.setRectToRect(srcBounds, *dstRect, SkMatrix::kFill_ScaleToFit)) {
-                return;
-            }
-        } else {
-            clippedDstRect = srcBounds;
-            srcToDstMatrix.reset();
-        }
-    }
-
-    // Now that we have both the view and srcToDst matrices, log our scale factor.
-    LogDrawScaleFactor(SkMatrix::Concat(viewMatrix, srcToDstMatrix), paint.getFilterQuality());
-
-    this->drawTextureProducerImpl(producer, clippedSrcRect, clippedDstRect, constraint, viewMatrix,
-                                  srcToDstMatrix, paint);
-}
-
-void SkGpuDevice::drawTextureProducerImpl(GrTextureProducer* producer,
-                                          const SkRect& clippedSrcRect,
-                                          const SkRect& clippedDstRect,
-                                          SkCanvas::SrcRectConstraint constraint,
-                                          const SkMatrix& viewMatrix,
-                                          const SkMatrix& srcToDstMatrix,
-                                          const SkPaint& paint) {
-    // Specifying the texture coords as local coordinates is an attempt to enable more GrDrawOp
-    // combining by not baking anything about the srcRect, dstRect, or viewMatrix, into the texture
-    // FP. In the future this should be an opaque optimization enabled by the combination of
-    // GrDrawOp/GP and FP.
     const SkMaskFilter* mf = paint.getMaskFilter();
-    if (mf && as_MFB(mf)->hasFragmentProcessor()) {
-        mf = nullptr;
-    }
+
     // The shader expects proper local coords, so we can't replace local coords with texture coords
     // if the shader will be used. If we have a mask filter we will change the underlying geometry
     // that is rendered.
     bool canUseTextureCoordsAsLocalCoords = !use_shader(producer->isAlphaOnly(), paint) && !mf;
 
+    // Specifying the texture coords as local coordinates is an attempt to enable more GrDrawOp
+    // combining by not baking anything about the srcRect, dstRect, or ctm, into the texture
+    // FP. In the future this should be an opaque optimization enabled by the combination of
+    // GrDrawOp/GP and FP.
+    if (mf && as_MFB(mf)->hasFragmentProcessor()) {
+        mf = nullptr;
+    }
     bool doBicubic;
     GrSamplerState::Filter fm = GrSkFilterQualityToGrFilterMode(
-            paint.getFilterQuality(), viewMatrix, srcToDstMatrix,
-            fContext->priv().options().fSharpenMipmappedTextures, &doBicubic);
+            paint.getFilterQuality(), ctm, srcToDst,
+            context->priv().options().fSharpenMipmappedTextures, &doBicubic);
     const GrSamplerState::Filter* filterMode = doBicubic ? nullptr : &fm;
 
     GrTextureProducer::FilterConstraint constraintMode;
@@ -264,58 +280,316 @@
     // If we have to outset for AA then we will generate texture coords outside the src rect. The
     // same happens for any mask filter that extends the bounds rendered in the dst.
     // This is conservative as a mask filter does not have to expand the bounds rendered.
-    bool coordsAllInsideSrcRect = !paint.isAntiAlias() && !mf;
+    bool coordsAllInsideSrcRect = aaFlags == GrQuadAAFlags::kNone && !mf;
 
     // Check for optimization to drop the src rect constraint when on bilerp.
     if (filterMode && GrSamplerState::Filter::kBilerp == *filterMode &&
         GrTextureAdjuster::kYes_FilterConstraint == constraintMode && coordsAllInsideSrcRect) {
         SkMatrix combinedMatrix;
-        combinedMatrix.setConcat(viewMatrix, srcToDstMatrix);
-        if (can_ignore_bilerp_constraint(*producer, clippedSrcRect, combinedMatrix,
-                                         fRenderTargetContext->fsaaType())) {
+        combinedMatrix.setConcat(ctm, srcToDst);
+        if (can_ignore_bilerp_constraint(*producer, src, combinedMatrix, rtc->fsaaType())) {
             constraintMode = GrTextureAdjuster::kNo_FilterConstraint;
         }
     }
 
-    const SkMatrix* textureMatrix;
-    SkMatrix tempMatrix;
+    SkMatrix textureMatrix;
     if (canUseTextureCoordsAsLocalCoords) {
-        textureMatrix = &SkMatrix::I();
+        textureMatrix = SkMatrix::I();
     } else {
-        if (!srcToDstMatrix.invert(&tempMatrix)) {
+        if (!srcToDst.invert(&textureMatrix)) {
             return;
         }
-        textureMatrix = &tempMatrix;
     }
-    auto fp = producer->createFragmentProcessor(*textureMatrix, clippedSrcRect, constraintMode,
+    auto fp = producer->createFragmentProcessor(textureMatrix, src, constraintMode,
                                                 coordsAllInsideSrcRect, filterMode);
     fp = GrColorSpaceXformEffect::Make(std::move(fp), producer->colorSpace(), producer->alphaType(),
-                                       fRenderTargetContext->colorSpaceInfo().colorSpace());
+                                       rtc->colorSpaceInfo().colorSpace());
     if (!fp) {
         return;
     }
 
     GrPaint grPaint;
-    if (!SkPaintToGrPaintWithTexture(fContext.get(), fRenderTargetContext->colorSpaceInfo(), paint,
-                                     viewMatrix, std::move(fp), producer->isAlphaOnly(),
-                                     &grPaint)) {
-        return;
-    }
-    GrAA aa = GrAA(paint.isAntiAlias());
-    if (canUseTextureCoordsAsLocalCoords) {
-        fRenderTargetContext->fillRectToRect(this->clip(), std::move(grPaint), aa, viewMatrix,
-                                             clippedDstRect, clippedSrcRect);
+    if (!SkPaintToGrPaintWithTexture(context, rtc->colorSpaceInfo(), paint, ctm,
+                                     std::move(fp), producer->isAlphaOnly(), &grPaint)) {
         return;
     }
 
     if (!mf) {
-        fRenderTargetContext->drawRect(this->clip(), std::move(grPaint), aa, viewMatrix,
-                                       clippedDstRect);
+        // Can draw the image directly (any mask filter on the paint was converted to an FP already)
+        if (dstClip) {
+            SkPoint srcClipPoints[4];
+            SkPoint* srcClip = nullptr;
+            if (canUseTextureCoordsAsLocalCoords) {
+                // Calculate texture coordinates that match the dst clip
+                GrMapRectPoints(dst, src, dstClip, srcClipPoints, 4);
+                srcClip = srcClipPoints;
+            }
+            rtc->fillQuadWithEdgeAA(clip, std::move(grPaint), aa, aaFlags, ctm, dstClip, srcClip);
+        } else {
+            // Provide explicit texture coords when possible, otherwise rely on texture matrix
+            rtc->fillRectWithEdgeAA(clip, std::move(grPaint), aa, aaFlags, ctm, dst,
+                                    canUseTextureCoordsAsLocalCoords ? &src : nullptr);
+        }
+    } else {
+        // Must draw the mask filter as a GrShape. For now, this loses the per-edge AA information
+        // since it always draws with AA, but that is should not be noticeable since the mask filter
+        // is probably a blur.
+        GrShape shape;
+        if (dstClip) {
+            // Represent it as an SkPath formed from the dstClip
+            SkPath path;
+            path.addPoly(dstClip, 4, true);
+            shape = GrShape(path);
+        } else {
+            shape = GrShape(dst);
+        }
+
+        GrBlurUtils::drawShapeWithMaskFilter(
+                context, rtc, clip, shape, std::move(grPaint), ctm, mf);
+    }
+}
+
+} // anonymous namespace
+
+//////////////////////////////////////////////////////////////////////////////
+
+void SkGpuDevice::drawImageQuad(const SkImage* image, const SkRect* srcRect, const SkRect* dstRect,
+                                const SkPoint dstClip[4], GrAA aa, GrQuadAAFlags aaFlags,
+                                const SkMatrix* preViewMatrix, const SkPaint& paint,
+                                SkCanvas::SrcRectConstraint constraint) {
+    SkRect src;
+    SkRect dst;
+    SkMatrix srcToDst;
+    ImageDrawMode mode = optimize_sample_area(SkISize::Make(image->width(), image->height()),
+                                              srcRect, dstRect, dstClip, &src, &dst, &srcToDst);
+    if (mode == ImageDrawMode::kSkip) {
         return;
     }
 
-    GrShape shape(clippedDstRect, GrStyle::SimpleFill());
+    if (src.contains(image->bounds())) {
+        constraint = SkCanvas::kFast_SrcRectConstraint;
+    }
+    // Depending on the nature of image, it can flow through more or less optimal pipelines
+    bool useDecal = mode == ImageDrawMode::kDecal;
+    bool attemptDrawTexture = !useDecal; // rtc->drawTexture() only clamps
 
-    GrBlurUtils::drawShapeWithMaskFilter(this->context(), fRenderTargetContext.get(), this->clip(),
-                                         shape, std::move(grPaint), viewMatrix, mf);
+    // Get final CTM matrix
+    SkMatrix ctm = this->ctm();
+    if (preViewMatrix) {
+        ctm.preConcat(*preViewMatrix);
+    }
+
+    // YUVA images can be stored in multiple images with different plane resolutions, so this
+    // uses an effect to combine them dynamically on the GPU. This is done before requesting a
+    // pinned texture proxy because YUV images force-flatten to RGBA in that scenario.
+    if (as_IB(image)->isYUVA()) {
+        SK_HISTOGRAM_BOOLEAN("DrawTiled", false);
+        LogDrawScaleFactor(ctm, srcToDst, paint.getFilterQuality());
+
+        GrYUVAImageTextureMaker maker(fContext.get(), image, useDecal);
+        draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), ctm,
+                              paint, &maker, src, dst, dstClip, srcToDst, aa, aaFlags, constraint,
+                              /* attempt draw texture */ false);
+        return;
+    }
+
+    // Pinned texture proxies can be rendered directly as textures, or with relatively simple
+    // adjustments applied to the image content (scaling, mipmaps, color space, etc.)
+    uint32_t pinnedUniqueID;
+    if (sk_sp<GrTextureProxy> proxy = as_IB(image)->refPinnedTextureProxy(this->context(),
+                                                                          &pinnedUniqueID)) {
+        SK_HISTOGRAM_BOOLEAN("DrawTiled", false);
+        LogDrawScaleFactor(ctm, srcToDst, paint.getFilterQuality());
+
+        SkAlphaType alphaType = image->alphaType();
+        SkColorSpace* colorSpace = as_IB(image)->colorSpace();
+
+        if (attemptDrawTexture && can_use_draw_texture(paint)) {
+            draw_texture(fRenderTargetContext.get(), this->clip(), ctm, paint, src,  dst,
+                         dstClip, aa, aaFlags, constraint, std::move(proxy), alphaType, colorSpace);
+            return;
+        }
+
+        GrTextureAdjuster adjuster(fContext.get(), std::move(proxy), alphaType, pinnedUniqueID,
+                                   colorSpace, useDecal);
+        draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), ctm,
+                              paint, &adjuster, src, dst, dstClip, srcToDst, aa, aaFlags,
+                              constraint, /* attempt draw_texture */ false);
+        return;
+    }
+
+    // Next up, try tiling the image
+    // TODO (michaelludwig): Implement this with per-edge AA flags to handle seaming properly
+    // instead of going through drawBitmapRect (which will be removed from SkDevice in the future)
+    SkBitmap bm;
+    if (this->shouldTileImage(image, &src, constraint, paint.getFilterQuality(), ctm, srcToDst)) {
+        // only support tiling as bitmap at the moment, so force raster-version
+        if (!as_IB(image)->getROPixels(&bm)) {
+            return;
+        }
+        this->drawBitmapRect(bm, &src, dst, paint, constraint);
+        return;
+    }
+
+    // This is the funnel for all non-tiled bitmap/image draw calls. Log a histogram entry.
+    SK_HISTOGRAM_BOOLEAN("DrawTiled", false);
+    LogDrawScaleFactor(ctm, srcToDst, paint.getFilterQuality());
+
+    // Lazily generated images must get drawn as a texture producer that handles the final
+    // texture creation.
+    if (image->isLazyGenerated()) {
+        GrImageTextureMaker maker(fContext.get(), image, SkImage::kAllow_CachingHint, useDecal);
+        draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), ctm,
+                              paint, &maker, src, dst, dstClip, srcToDst, aa, aaFlags, constraint,
+                              attemptDrawTexture);
+        return;
+    }
+    if (as_IB(image)->getROPixels(&bm)) {
+        GrBitmapTextureMaker maker(fContext.get(), bm, useDecal);
+        draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), ctm,
+                              paint, &maker, src, dst, dstClip, srcToDst, aa, aaFlags, constraint,
+                              attemptDrawTexture);
+    }
+
+    // Otherwise don't know how to draw it
+}
+
+void SkGpuDevice::drawEdgeAAImageSet(const SkCanvas::ImageSetEntry set[], int count,
+                                     const SkPoint dstClips[], const SkMatrix preViewMatrices[],
+                                     const SkPaint& paint, SkCanvas::SrcRectConstraint constraint) {
+    SkASSERT(count > 0);
+    if (!can_use_draw_texture(paint)) {
+        // Send every entry through drawImageQuad() to handle the more complicated paint
+        int dstClipIndex = 0;
+        for (int i = 0; i < count; ++i) {
+            // Only no clip or quad clip are supported
+            SkASSERT(!set[i].fHasClip || dstClips);
+            SkASSERT(set[i].fMatrixIndex < 0 || preViewMatrices);
+
+            // Always send GrAA::kYes to preserve seaming across tiling in MSAA
+            this->drawImageQuad(set[i].fImage.get(), &set[i].fSrcRect, &set[i].fDstRect,
+                    set[i].fHasClip ? dstClips + dstClipIndex : nullptr,
+                    GrAA::kYes, SkToGrQuadAAFlags(set[i].fAAFlags),
+                    set[i].fMatrixIndex < 0 ? nullptr : preViewMatrices + set[i].fMatrixIndex,
+                    paint, constraint);
+            dstClipIndex += 4 * set[i].fHasClip;
+        }
+        return;
+    }
+
+    GrSamplerState::Filter filter = kNone_SkFilterQuality == paint.getFilterQuality() ?
+            GrSamplerState::Filter::kNearest : GrSamplerState::Filter::kBilerp;
+    SkBlendMode mode = paint.getBlendMode();
+
+    SkAutoTArray<GrRenderTargetContext::TextureSetEntry> textures(count);
+    // We accumulate compatible proxies until we find an an incompatible one or reach the end and
+    // issue the accumulated 'n' draws starting at 'base'.
+    int base = 0, n = 0;
+    auto draw = [&] {
+        if (n > 0) {
+            auto textureXform = GrColorSpaceXform::Make(
+                    set[base].fImage->colorSpace(), set[base].fImage->alphaType(),
+                    fRenderTargetContext->colorSpaceInfo().colorSpace(), kPremul_SkAlphaType);
+            fRenderTargetContext->drawTextureSet(this->clip(), textures.get() + base, n,
+                                                 filter, mode, GrAA::kYes, constraint, this->ctm(),
+                                                 std::move(textureXform));
+        }
+    };
+    int dstClipIndex = 0;
+    for (int i = 0; i < count; ++i) {
+        SkASSERT(!set[i].fHasClip || dstClips);
+        SkASSERT(set[i].fMatrixIndex < 0 || preViewMatrices);
+
+        // Manage the dst clip pointer tracking before any continues are used so we don't lose
+        // our place in the dstClips array.
+        const SkPoint* clip = set[i].fHasClip ? dstClips + dstClipIndex : nullptr;
+        dstClipIndex += 4 * set[i].fHasClip;
+
+        // The default SkBaseDevice implementation is based on drawImageRect which does not allow
+        // non-sorted src rects. TODO: Decide this is OK or make sure we handle it.
+        if (!set[i].fSrcRect.isSorted()) {
+            draw();
+            base = i + 1;
+            n = 0;
+            continue;
+        }
+
+        sk_sp<GrTextureProxy> proxy;
+        const SkImage_Base* image = as_IB(set[i].fImage.get());
+        // Extract proxy from image, but skip YUV images so they get processed through
+        // drawImageQuad and the proper effect to dynamically sample their planes.
+        if (!image->isYUVA()) {
+            uint32_t uniqueID;
+            proxy = image->refPinnedTextureProxy(this->context(), &uniqueID);
+            if (!proxy) {
+                proxy = image->asTextureProxyRef(this->context(), GrSamplerState::ClampBilerp(),
+                                                 nullptr);
+            }
+        }
+
+        if (!proxy) {
+            // This image can't go through the texture op, send through general image pipeline
+            // after flushing current batch.
+            draw();
+            base = i + 1;
+            n = 0;
+            this->drawImageQuad(image, &set[i].fSrcRect, &set[i].fDstRect, clip, GrAA::kYes,
+                    SkToGrQuadAAFlags(set[i].fAAFlags),
+                    set[i].fMatrixIndex < 0 ? nullptr : preViewMatrices + set[i].fMatrixIndex,
+                    paint, constraint);
+            continue;
+        }
+
+        textures[i].fProxy = std::move(proxy);
+        textures[i].fSrcRect = set[i].fSrcRect;
+        textures[i].fDstRect = set[i].fDstRect;
+        textures[i].fDstClipQuad = clip;
+        textures[i].fPreViewMatrix =
+                set[i].fMatrixIndex < 0 ? nullptr : preViewMatrices + set[i].fMatrixIndex;
+        textures[i].fAlpha = set[i].fAlpha * paint.getAlphaf();
+        textures[i].fAAFlags = SkToGrQuadAAFlags(set[i].fAAFlags);
+
+        if (n > 0 &&
+            (!GrTextureProxy::ProxiesAreCompatibleAsDynamicState(textures[i].fProxy.get(),
+                                                                 textures[base].fProxy.get()) ||
+             set[i].fImage->alphaType() != set[base].fImage->alphaType() ||
+             !SkColorSpace::Equals(set[i].fImage->colorSpace(), set[base].fImage->colorSpace()))) {
+            draw();
+            base = i;
+            n = 1;
+        } else {
+            ++n;
+        }
+    }
+    draw();
+}
+
+// TODO (michaelludwig) - to be removed when drawBitmapRect doesn't need it anymore
+void SkGpuDevice::drawTextureProducer(GrTextureProducer* producer,
+                                      const SkRect* srcRect,
+                                      const SkRect* dstRect,
+                                      SkCanvas::SrcRectConstraint constraint,
+                                      const SkMatrix& viewMatrix,
+                                      const SkPaint& paint,
+                                      bool attemptDrawTexture) {
+    // The texture refactor split the old logic of drawTextureProducer into the beginning of
+    // drawImageQuad() and into the static draw_texture_producer. Replicate necessary logic that
+    // drawImageQuad() handles.
+    SkRect src;
+    SkRect dst;
+    SkMatrix srcToDst;
+    ImageDrawMode mode = optimize_sample_area(SkISize::Make(producer->width(), producer->height()),
+                                              srcRect, dstRect, nullptr, &src, &dst, &srcToDst);
+    if (mode == ImageDrawMode::kSkip) {
+        return;
+    }
+    // There's no dstClip to worry about and the producer is already made so we wouldn't be able
+    // to tell it to use decals if we had to
+    SkASSERT(mode != ImageDrawMode::kDecal);
+
+    draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), viewMatrix,
+                          paint, producer, src, dst, /* clip */ nullptr, srcToDst,
+                          GrAA(paint.isAntiAlias()),
+                          paint.isAntiAlias() ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone,
+                          constraint, attemptDrawTexture);
 }
diff --git a/src/gpu/SkGr.cpp b/src/gpu/SkGr.cpp
index fffbe42..5fc363b 100644
--- a/src/gpu/SkGr.cpp
+++ b/src/gpu/SkGr.cpp
@@ -14,6 +14,8 @@
 #include "GrGpuResourcePriv.h"
 #include "GrPaint.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrTextureProxy.h"
 #include "GrTypes.h"
 #include "GrXferProcessor.h"
@@ -35,7 +37,7 @@
 #include "SkTemplates.h"
 #include "SkTraceEvent.h"
 #include "effects/GrBicubicEffect.h"
-#include "effects/GrConstColorProcessor.h"
+#include "effects/generated/GrConstColorProcessor.h"
 #include "effects/GrPorterDuffXferProcessor.h"
 #include "effects/GrXfermodeFragmentProcessor.h"
 #include "effects/GrSkSLFP.h"
@@ -105,28 +107,6 @@
     builder[4] = imageBounds.fBottom;
 }
 
-//////////////////////////////////////////////////////////////////////////////
-sk_sp<GrTextureProxy> GrUploadBitmapToTextureProxy(GrProxyProvider* proxyProvider,
-                                                   const SkBitmap& bitmap) {
-    if (!bitmap.peekPixels(nullptr)) {
-        return nullptr;
-    }
-
-    if (!SkImageInfoIsValid(bitmap.info())) {
-        return nullptr;
-    }
-
-    // In non-ddl we will always instantiate right away. Thus we never want to copy the SkBitmap
-    // even if it's mutable. In ddl, if the bitmap is mutable then we must make a copy since the
-    // upload of the data to the gpu can happen at anytime and the bitmap may change by then.
-    SkCopyPixelsMode cpyMode = proxyProvider->recordingDDL() ? kIfMutable_SkCopyPixelsMode
-                                                             : kNever_SkCopyPixelsMode;
-    sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, cpyMode);
-
-    return proxyProvider->createTextureProxy(std::move(image), kNone_GrSurfaceFlags, 1,
-                                             SkBudgeted::kYes, SkBackingFit::kExact);
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 
 void GrInstallBitmapUniqueKeyInvalidator(const GrUniqueKey& key, uint32_t contextUniqueID,
@@ -145,7 +125,8 @@
     pixelRef->addGenIDChangeListener(new Invalidator(key, contextUniqueID));
 }
 
-sk_sp<GrTextureProxy> GrCopyBaseMipMapToTextureProxy(GrContext* ctx, GrTextureProxy* baseProxy) {
+sk_sp<GrTextureProxy> GrCopyBaseMipMapToTextureProxy(GrRecordingContext* ctx,
+                                                     GrTextureProxy* baseProxy) {
     SkASSERT(baseProxy);
 
     if (!ctx->priv().caps()->isConfigCopyable(baseProxy->config())) {
@@ -179,7 +160,7 @@
     return proxy;
 }
 
-sk_sp<GrTextureProxy> GrRefCachedBitmapTextureProxy(GrContext* ctx,
+sk_sp<GrTextureProxy> GrRefCachedBitmapTextureProxy(GrRecordingContext* ctx,
                                                     const SkBitmap& bitmap,
                                                     const GrSamplerState& params,
                                                     SkScalar scaleAdjust[2]) {
@@ -196,8 +177,8 @@
     // In non-ddl we will always instantiate right away. Thus we never want to copy the SkBitmap
     // even if its mutable. In ddl, if the bitmap is mutable then we must make a copy since the
     // upload of the data to the gpu can happen at anytime and the bitmap may change by then.
-    SkCopyPixelsMode cpyMode = proxyProvider->recordingDDL() ? kIfMutable_SkCopyPixelsMode
-                                                             : kNever_SkCopyPixelsMode;
+    SkCopyPixelsMode cpyMode = proxyProvider->renderingDirectly() ? kNever_SkCopyPixelsMode
+                                                                  : kIfMutable_SkCopyPixelsMode;
     sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, cpyMode);
 
     if (!image) {
@@ -244,8 +225,8 @@
             const SkBitmap* bm = as_IB(srcImage.get())->onPeekBitmap();
             // When recording DDLs we do not want to install change listeners because doing
             // so isn't threadsafe.
-            if (bm && !proxyProvider->recordingDDL()) {
-                GrInstallBitmapUniqueKeyInvalidator(originalKey, proxyProvider->contextUniqueID(),
+            if (bm && proxyProvider->renderingDirectly()) {
+                GrInstallBitmapUniqueKeyInvalidator(originalKey, proxyProvider->contextID(),
                                                     bm->pixelRef());
             }
         }
@@ -264,18 +245,10 @@
     return color.premul();
 }
 
-SkColor4f SkColor4fPrepForDst(SkColor4f color, const GrColorSpaceInfo& colorSpaceInfo,
-                              const GrCaps& caps) {
+SkColor4f SkColor4fPrepForDst(SkColor4f color, const GrColorSpaceInfo& colorSpaceInfo) {
     if (auto* xform = colorSpaceInfo.colorSpaceXformFromSRGB()) {
         color = xform->apply(color);
     }
-    if (!GrPixelConfigIsFloatingPoint(colorSpaceInfo.config()) ||
-        !caps.halfFloatVertexAttributeSupport()) {
-        color = { SkTPin(color.fR, 0.0f, 1.0f),
-                  SkTPin(color.fG, 0.0f, 1.0f),
-                  SkTPin(color.fB, 0.0f, 1.0f),
-                         color.fA };
-    }
     return color;
 }
 
@@ -303,6 +276,8 @@
             return kUnknown_GrPixelConfig;
         case kGray_8_SkColorType:
             return kGray_8_GrPixelConfig;
+        case kRGBA_F16Norm_SkColorType:
+            return kRGBA_half_Clamped_GrPixelConfig;
         case kRGBA_F16_SkColorType:
             return kRGBA_half_GrPixelConfig;
         case kRGBA_F32_SkColorType:
@@ -341,6 +316,7 @@
         case kGray_8_as_Red_GrPixelConfig:
         case kRGBA_8888_GrPixelConfig:
         case kRGB_888_GrPixelConfig:
+        case kRGB_888X_GrPixelConfig:
         case kRG_88_GrPixelConfig:
         case kBGRA_8888_GrPixelConfig:
             return 0;
@@ -357,6 +333,7 @@
         case kRGBA_float_GrPixelConfig:
         case kRG_float_GrPixelConfig:
         case kRGBA_half_GrPixelConfig:
+        case kRGBA_half_Clamped_GrPixelConfig:
         case kRGB_ETC1_GrPixelConfig:
         case kAlpha_8_GrPixelConfig:
         case kAlpha_8_as_Alpha_GrPixelConfig:
@@ -368,7 +345,7 @@
 }
 #endif
 
-static inline bool skpaint_to_grpaint_impl(GrContext* context,
+static inline bool skpaint_to_grpaint_impl(GrRecordingContext* context,
                                            const GrColorSpaceInfo& colorSpaceInfo,
                                            const SkPaint& skPaint,
                                            const SkMatrix& viewM,
@@ -376,8 +353,7 @@
                                            SkBlendMode* primColorMode,
                                            GrPaint* grPaint) {
     // Convert SkPaint color to 4f format in the destination color space
-    SkColor4f origColor = SkColor4fPrepForDst(skPaint.getColor4f(), colorSpaceInfo,
-                                              *context->priv().caps());
+    SkColor4f origColor = SkColor4fPrepForDst(skPaint.getColor4f(), colorSpaceInfo);
 
     const GrFPArgs fpArgs(context, &viewM, skPaint.getFilterQuality(), &colorSpaceInfo);
 
@@ -512,14 +488,14 @@
     return true;
 }
 
-bool SkPaintToGrPaint(GrContext* context, const GrColorSpaceInfo& colorSpaceInfo,
+bool SkPaintToGrPaint(GrRecordingContext* context, const GrColorSpaceInfo& colorSpaceInfo,
                       const SkPaint& skPaint, const SkMatrix& viewM, GrPaint* grPaint) {
     return skpaint_to_grpaint_impl(context, colorSpaceInfo, skPaint, viewM, nullptr, nullptr,
                                    grPaint);
 }
 
 /** Replaces the SkShader (if any) on skPaint with the passed in GrFragmentProcessor. */
-bool SkPaintToGrPaintReplaceShader(GrContext* context,
+bool SkPaintToGrPaintReplaceShader(GrRecordingContext* context,
                                    const GrColorSpaceInfo& colorSpaceInfo,
                                    const SkPaint& skPaint,
                                    std::unique_ptr<GrFragmentProcessor> shaderFP,
@@ -532,7 +508,7 @@
 }
 
 /** Ignores the SkShader (if any) on skPaint. */
-bool SkPaintToGrPaintNoShader(GrContext* context,
+bool SkPaintToGrPaintNoShader(GrRecordingContext* context,
                               const GrColorSpaceInfo& colorSpaceInfo,
                               const SkPaint& skPaint,
                               GrPaint* grPaint) {
@@ -544,7 +520,7 @@
 
 /** Blends the SkPaint's shader (or color if no shader) with a per-primitive color which must
 be setup as a vertex attribute using the specified SkBlendMode. */
-bool SkPaintToGrPaintWithXfermode(GrContext* context,
+bool SkPaintToGrPaintWithXfermode(GrRecordingContext* context,
                                   const GrColorSpaceInfo& colorSpaceInfo,
                                   const SkPaint& skPaint,
                                   const SkMatrix& viewM,
@@ -554,7 +530,7 @@
                                    grPaint);
 }
 
-bool SkPaintToGrPaintWithTexture(GrContext* context,
+bool SkPaintToGrPaintWithTexture(GrRecordingContext* context,
                                  const GrColorSpaceInfo& colorSpaceInfo,
                                  const SkPaint& paint,
                                  const SkMatrix& viewM,
diff --git a/src/gpu/SkGr.h b/src/gpu/SkGr.h
index aadf734..824c06b 100644
--- a/src/gpu/SkGr.h
+++ b/src/gpu/SkGr.h
@@ -9,6 +9,7 @@
 #define SkGr_DEFINED
 
 #include "GrBlend.h"
+#include "GrCaps.h"
 #include "GrColor.h"
 #include "GrSamplerState.h"
 #include "GrTypes.h"
@@ -27,6 +28,7 @@
 class GrContext;
 class GrFragmentProcessor;
 class GrPaint;
+class GrRecordingContext;
 class GrResourceProvider;
 class GrTextureProxy;
 class GrUniqueKey;
@@ -60,23 +62,31 @@
 /** Similar, but using SkPMColor4f. */
 SkPMColor4f SkColorToPMColor4f(SkColor, const GrColorSpaceInfo&);
 
-/** Converts an SkColor4f to the destination color space. Pins the color if the destination is
-    normalized, or the device does not support half-float vertex attributes. */
-SkColor4f SkColor4fPrepForDst(SkColor4f, const GrColorSpaceInfo&, const GrCaps&);
+/** Converts an SkColor4f to the destination color space. */
+SkColor4f SkColor4fPrepForDst(SkColor4f, const GrColorSpaceInfo&);
+
+/** Returns true if half-floats are required to store the color in a vertex (and half-floats
+    are supported). */
+static inline bool SkPMColor4fNeedsWideColor(SkPMColor4f color, GrClampType clampType,
+                                             const GrCaps& caps) {
+    return GrClampType::kNone == clampType &&
+        caps.halfFloatVertexAttributeSupport() &&
+        !color.fitsInBytes();
+}
 
 ////////////////////////////////////////////////////////////////////////////////
 // Paint conversion
 
-/** Converts an SkPaint to a GrPaint for a given GrContext. The matrix is required in order
+/** Converts an SkPaint to a GrPaint for a given GrRecordingContext. The matrix is required in order
     to convert the SkShader (if any) on the SkPaint. The primitive itself has no color. */
-bool SkPaintToGrPaint(GrContext*,
+bool SkPaintToGrPaint(GrRecordingContext*,
                       const GrColorSpaceInfo& dstColorSpaceInfo,
                       const SkPaint& skPaint,
                       const SkMatrix& viewM,
                       GrPaint* grPaint);
 
 /** Same as above but ignores the SkShader (if any) on skPaint. */
-bool SkPaintToGrPaintNoShader(GrContext* context,
+bool SkPaintToGrPaintNoShader(GrRecordingContext*,
                               const GrColorSpaceInfo& dstColorSpaceInfo,
                               const SkPaint& skPaint,
                               GrPaint* grPaint);
@@ -84,7 +94,7 @@
 /** Replaces the SkShader (if any) on skPaint with the passed in GrFragmentProcessor. The processor
     should expect an unpremul input color and produce a premultiplied output color. There is
     no primitive color. */
-bool SkPaintToGrPaintReplaceShader(GrContext*,
+bool SkPaintToGrPaintReplaceShader(GrRecordingContext*,
                                    const GrColorSpaceInfo& dstColorSpaceInfo,
                                    const SkPaint& skPaint,
                                    std::unique_ptr<GrFragmentProcessor> shaderFP,
@@ -92,7 +102,7 @@
 
 /** Blends the SkPaint's shader (or color if no shader) with the color which specified via a
     GrOp's GrPrimitiveProcesssor. */
-bool SkPaintToGrPaintWithXfermode(GrContext* context,
+bool SkPaintToGrPaintWithXfermode(GrRecordingContext*,
                                   const GrColorSpaceInfo& dstColorSpaceInfo,
                                   const SkPaint& skPaint,
                                   const SkMatrix& viewM,
@@ -103,18 +113,19 @@
     the expectation is that the primitive color will be premultiplied, though it really should be
     unpremultiplied so that interpolation is done in unpremul space. The paint's alpha will be
     applied to the primitive color after interpolation. */
-inline bool SkPaintToGrPaintWithPrimitiveColor(GrContext* context,
+inline bool SkPaintToGrPaintWithPrimitiveColor(GrRecordingContext* context,
                                                const GrColorSpaceInfo& dstColorSpaceInfo,
-                                               const SkPaint& skPaint, GrPaint* grPaint) {
+                                               const SkPaint& skPaint,
+                                               GrPaint* grPaint) {
     return SkPaintToGrPaintWithXfermode(context, dstColorSpaceInfo, skPaint, SkMatrix::I(),
                                         SkBlendMode::kDst, grPaint);
 }
 
 /** This is used when there may or may not be a shader, and the caller wants to plugin a texture
     lookup.  If there is a shader, then its output will only be used if the texture is alpha8. */
-bool SkPaintToGrPaintWithTexture(GrContext* context,
+bool SkPaintToGrPaintWithTexture(GrRecordingContext*,
                                  const GrColorSpaceInfo& dstColorSpaceInfo,
-                                 const SkPaint& paint,
+                                 const SkPaint& skPaint,
                                  const SkMatrix& viewM,
                                  std::unique_ptr<GrFragmentProcessor> fp,
                                  bool textureIsAlphaOnly,
@@ -174,22 +185,15 @@
  *  performed on the absolute texture coordinates (e.g., if the texture is resized out to
  *  the next power of two). It can be null if the caller is sure the bitmap won't be resized.
  */
-sk_sp<GrTextureProxy> GrRefCachedBitmapTextureProxy(GrContext*,
+sk_sp<GrTextureProxy> GrRefCachedBitmapTextureProxy(GrRecordingContext*,
                                                     const SkBitmap&,
                                                     const GrSamplerState&,
                                                     SkScalar scaleAdjust[2]);
 
 /**
- * Creates a new texture for the bitmap. Does not concern itself with cache keys or texture params.
- * The bitmap must have CPU-accessible pixels. Attempts to take advantage of faster paths for
- * yuv planes.
- */
-sk_sp<GrTextureProxy> GrUploadBitmapToTextureProxy(GrProxyProvider*, const SkBitmap&);
-
-/**
  * Creates a new texture with mipmap levels and copies the baseProxy into the base layer.
  */
-sk_sp<GrTextureProxy> GrCopyBaseMipMapToTextureProxy(GrContext*,
+sk_sp<GrTextureProxy> GrCopyBaseMipMapToTextureProxy(GrRecordingContext*,
                                                      GrTextureProxy* baseProxy);
 
 /*
@@ -221,7 +225,7 @@
 
 /** Call this after installing a GrUniqueKey on texture. It will cause the texture's key to be
     removed should the bitmap's contents change or be destroyed. */
-void GrInstallBitmapUniqueKeyInvalidator(const GrUniqueKey& key, uint32_t contextUniqueID,
+void GrInstallBitmapUniqueKeyInvalidator(const GrUniqueKey& key, uint32_t contextID,
                                          SkPixelRef* pixelRef);
 
 #endif
diff --git a/src/gpu/ccpr/GrCCAtlas.cpp b/src/gpu/ccpr/GrCCAtlas.cpp
index 4d147fd..417bf96 100644
--- a/src/gpu/ccpr/GrCCAtlas.cpp
+++ b/src/gpu/ccpr/GrCCAtlas.cpp
@@ -14,6 +14,7 @@
 #include "GrRenderTargetContext.h"
 #include "GrTexture.h"
 #include "GrTextureProxy.h"
+#include "SkIPoint16.h"
 #include "SkMakeUnique.h"
 #include "SkMathPriv.h"
 #include "ccpr/GrCCPathCache.h"
@@ -84,18 +85,16 @@
 
     fTextureProxy = GrProxyProvider::MakeFullyLazyProxy(
             [this, pixelConfig](GrResourceProvider* resourceProvider) {
-                    if (!resourceProvider) {
-                        return sk_sp<GrTexture>();
-                    }
                     if (!fBackingTexture) {
                         GrSurfaceDesc desc;
                         desc.fFlags = kRenderTarget_GrSurfaceFlag;
                         desc.fWidth = fWidth;
                         desc.fHeight = fHeight;
                         desc.fConfig = pixelConfig;
-                        fBackingTexture = resourceProvider->createTexture(desc, SkBudgeted::kYes);
+                        fBackingTexture = resourceProvider->createTexture(
+                            desc, SkBudgeted::kYes, GrResourceProvider::Flags::kNoPendingIO);
                     }
-                    return fBackingTexture;
+                    return GrSurfaceProxy::LazyInstantiationResult(fBackingTexture);
             },
             format, GrProxyProvider::Renderable::kYes, kTextureOrigin, pixelConfig, caps);
 }
diff --git a/src/gpu/ccpr/GrCCClipPath.cpp b/src/gpu/ccpr/GrCCClipPath.cpp
index 8b8d8a6..4c82fb1 100644
--- a/src/gpu/ccpr/GrCCClipPath.cpp
+++ b/src/gpu/ccpr/GrCCClipPath.cpp
@@ -20,10 +20,8 @@
                                                                         GrSRGBEncoded::kNo);
 
     fAtlasLazyProxy = GrProxyProvider::MakeFullyLazyProxy(
-            [this](GrResourceProvider* resourceProvider) {
-                if (!resourceProvider) {
-                    return sk_sp<GrTexture>();
-                }
+            [this](GrResourceProvider* resourceProvider)
+                    -> GrSurfaceProxy::LazyInstantiationResult {
                 SkASSERT(fHasAtlas);
                 SkASSERT(!fHasAtlasTransform);
 
@@ -31,7 +29,7 @@
                 if (!textureProxy || !textureProxy->instantiate(resourceProvider)) {
                     fAtlasScale = fAtlasTranslate = {0, 0};
                     SkDEBUGCODE(fHasAtlasTransform = true);
-                    return sk_sp<GrTexture>();
+                    return {};
                 }
 
                 SkASSERT(kTopLeft_GrSurfaceOrigin == textureProxy->origin());
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor.cpp b/src/gpu/ccpr/GrCCCoverageProcessor.cpp
index f72ccf9..0be4c08 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor.cpp
+++ b/src/gpu/ccpr/GrCCCoverageProcessor.cpp
@@ -184,23 +184,6 @@
                    outputAttenuation);
 }
 
-void GrCCCoverageProcessor::getGLSLProcessorKey(const GrShaderCaps&,
-                                                GrProcessorKeyBuilder* b) const {
-    int key = (int)fPrimitiveType << 2;
-    if (GSSubpass::kCorners == fGSSubpass) {
-        key |= 2;
-    }
-    if (Impl::kVertexShader == fImpl) {
-        key |= 1;
-    }
-#ifdef SK_DEBUG
-    uint32_t bloatBits;
-    memcpy(&bloatBits, &fDebugBloat, 4);
-    b->add32(bloatBits);
-#endif
-    b->add32(key);
-}
-
 GrGLSLPrimitiveProcessor* GrCCCoverageProcessor::createGLSLInstance(const GrShaderCaps&) const {
     std::unique_ptr<Shader> shader;
     switch (fPrimitiveType) {
@@ -218,33 +201,23 @@
             shader = skstd::make_unique<GrCCConicShader>();
             break;
     }
-    return Impl::kGeometryShader == fImpl ? this->createGSImpl(std::move(shader))
-                                          : this->createVSImpl(std::move(shader));
+    return this->onCreateGLSLInstance(std::move(shader));
 }
 
-void GrCCCoverageProcessor::Shader::emitFragmentCode(const GrCCCoverageProcessor& proc,
-                                                     GrGLSLFPFragmentBuilder* f,
-                                                     const char* skOutputColor,
-                                                     const char* skOutputCoverage) const {
+void GrCCCoverageProcessor::Shader::emitFragmentCode(
+        const GrCCCoverageProcessor& proc, GrGLSLFPFragmentBuilder* f, const char* skOutputColor,
+        const char* skOutputCoverage) const {
     f->codeAppendf("half coverage = 0;");
     this->onEmitFragmentCode(f, "coverage");
     f->codeAppendf("%s.a = coverage;", skOutputColor);
     f->codeAppendf("%s = half4(1);", skOutputCoverage);
 }
 
-void GrCCCoverageProcessor::draw(GrOpFlushState* flushState, const GrPipeline& pipeline,
-                                 const SkIRect scissorRects[], const GrMesh meshes[], int meshCount,
-                                 const SkRect& drawBounds) const {
+void GrCCCoverageProcessor::draw(
+        GrOpFlushState* flushState, const GrPipeline& pipeline, const SkIRect scissorRects[],
+        const GrMesh meshes[], int meshCount, const SkRect& drawBounds) const {
     GrPipeline::DynamicStateArrays dynamicStateArrays;
     dynamicStateArrays.fScissorRects = scissorRects;
     GrGpuRTCommandBuffer* cmdBuff = flushState->rtCommandBuffer();
     cmdBuff->draw(*this, pipeline, nullptr, &dynamicStateArrays, meshes, meshCount, drawBounds);
-
-    // Geometry shader backend draws primitives in two subpasses.
-    if (Impl::kGeometryShader == fImpl) {
-        SkASSERT(GSSubpass::kHulls == fGSSubpass);
-        GrCCCoverageProcessor cornerProc(*this, GSSubpass::kCorners);
-        cmdBuff->draw(cornerProc, pipeline, nullptr, &dynamicStateArrays, meshes, meshCount,
-                      drawBounds);
-    }
 }
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor.h b/src/gpu/ccpr/GrCCCoverageProcessor.h
index 5871fa1..9687bba 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor.h
+++ b/src/gpu/ccpr/GrCCCoverageProcessor.h
@@ -37,7 +37,7 @@
 public:
     enum class PrimitiveType {
         kTriangles,
-        kWeightedTriangles, // Triangles (from the tessellator) whose winding magnitude > 1.
+        kWeightedTriangles,  // Triangles (from the tessellator) whose winding magnitude > 1.
         kQuadratics,
         kCubics,
         kConics
@@ -68,16 +68,21 @@
         void setW(const Sk2f& P0, const Sk2f& P1, const Sk2f& P2, const Sk2f& trans, float w);
     };
 
-    GrCCCoverageProcessor(GrResourceProvider* rp, PrimitiveType type)
-            : INHERITED(kGrCCCoverageProcessor_ClassID)
-            , fPrimitiveType(type)
-            , fImpl(rp->caps()->shaderCaps()->geometryShaderSupport() ? Impl::kGeometryShader
-                                                                      : Impl::kVertexShader) {
-        if (Impl::kGeometryShader == fImpl) {
-            this->initGS();
-        } else {
-            this->initVS(rp);
-        }
+    virtual void reset(PrimitiveType, GrResourceProvider*) = 0;
+
+    PrimitiveType primitiveType() const { return fPrimitiveType; }
+
+    // Number of bezier points for curves, or 3 for triangles.
+    int numInputPoints() const { return PrimitiveType::kCubics == fPrimitiveType ? 4 : 3; }
+
+    bool isTriangles() const {
+        return PrimitiveType::kTriangles == fPrimitiveType ||
+               PrimitiveType::kWeightedTriangles == fPrimitiveType;
+    }
+
+    int hasInputWeight() const {
+        return PrimitiveType::kWeightedTriangles == fPrimitiveType ||
+               PrimitiveType::kConics == fPrimitiveType;
     }
 
     // GrPrimitiveProcessor overrides.
@@ -87,30 +92,32 @@
         return SkStringPrintf("%s\n%s", this->name(), this->INHERITED::dumpInfo().c_str());
     }
 #endif
-    void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override;
+    void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
+        SkDEBUGCODE(this->getDebugBloatKey(b));
+        b->add32((int)fPrimitiveType);
+    }
+    GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final;
 
 #ifdef SK_DEBUG
     // Increases the 1/2 pixel AA bloat by a factor of debugBloat.
     void enableDebugBloat(float debugBloat) { fDebugBloat = debugBloat; }
     bool debugBloatEnabled() const { return fDebugBloat > 0; }
     float debugBloat() const { SkASSERT(this->debugBloatEnabled()); return fDebugBloat; }
+    void getDebugBloatKey(GrProcessorKeyBuilder* b) const {
+        uint32_t bloatBits;
+        memcpy(&bloatBits, &fDebugBloat, 4);
+        b->add32(bloatBits);
+    }
 #endif
 
     // Appends a GrMesh that will draw the provided instances. The instanceBuffer must be an array
     // of either TriPointInstance or QuadPointInstance, depending on this processor's RendererPass,
     // with coordinates in the desired shape's final atlas-space position.
-    void appendMesh(sk_sp<GrBuffer> instanceBuffer, int instanceCount, int baseInstance,
-                    SkTArray<GrMesh>* out) const {
-        if (Impl::kGeometryShader == fImpl) {
-            this->appendGSMesh(std::move(instanceBuffer), instanceCount, baseInstance, out);
-        } else {
-            this->appendVSMesh(std::move(instanceBuffer), instanceCount, baseInstance, out);
-        }
-    }
+    virtual void appendMesh(sk_sp<const GrGpuBuffer> instanceBuffer, int instanceCount,
+                            int baseInstance, SkTArray<GrMesh>* out) const = 0;
 
-    void draw(GrOpFlushState*, const GrPipeline&, const SkIRect scissorRects[], const GrMesh[],
-              int meshCount, const SkRect& drawBounds) const;
+    virtual void draw(GrOpFlushState*, const GrPipeline&, const SkIRect scissorRects[],
+                      const GrMesh[], int meshCount, const SkRect& drawBounds) const;
 
     // The Shader provides code to calculate each pixel's coverage in a RenderPass. It also
     // provides details about shape-specific geometry.
@@ -197,82 +204,26 @@
             return Scope::kGeoToFrag == varying.scope() ? varying.gsOut() : varying.vsOut();
         }
 
-        // Our friendship with GrGLSLShaderBuilder does not propogate to subclasses.
+        // Our friendship with GrGLSLShaderBuilder does not propagate to subclasses.
         inline static SkString& AccessCodeString(GrGLSLShaderBuilder* s) { return s->code(); }
     };
 
-private:
-    class GSImpl;
-    class GSTriangleHullImpl;
-    class GSCurveHullImpl;
-    class GSCornerImpl;
-    class VSImpl;
-    class TriangleShader;
-
+protected:
     // Slightly undershoot a bloat radius of 0.5 so vertices that fall on integer boundaries don't
     // accidentally bleed into neighbor pixels.
     static constexpr float kAABloatRadius = 0.491111f;
 
-    // Number of bezier points for curves, or 3 for triangles.
-    int numInputPoints() const { return PrimitiveType::kCubics == fPrimitiveType ? 4 : 3; }
+    GrCCCoverageProcessor(ClassID classID) : INHERITED(classID) {}
 
-    bool isTriangles() const {
-        return PrimitiveType::kTriangles == fPrimitiveType ||
-               PrimitiveType::kWeightedTriangles == fPrimitiveType;
-    }
+    virtual GrGLSLPrimitiveProcessor* onCreateGLSLInstance(std::unique_ptr<Shader>) const = 0;
 
-    int hasInputWeight() const {
-        return PrimitiveType::kWeightedTriangles == fPrimitiveType ||
-               PrimitiveType::kConics == fPrimitiveType;
-    }
+    // Our friendship with GrGLSLShaderBuilder does not propagate to subclasses.
+    inline static SkString& AccessCodeString(GrGLSLShaderBuilder* s) { return s->code(); }
 
-    enum class Impl : bool {
-        kGeometryShader,
-        kVertexShader
-    };
-
-    // Geometry shader backend draws primitives in two subpasses.
-    enum class GSSubpass : bool {
-        kHulls,
-        kCorners
-    };
-
-    GrCCCoverageProcessor(const GrCCCoverageProcessor& proc, GSSubpass subpass)
-            : INHERITED(kGrCCCoverageProcessor_ClassID)
-            , fPrimitiveType(proc.fPrimitiveType)
-            , fImpl(Impl::kGeometryShader)
-            SkDEBUGCODE(, fDebugBloat(proc.fDebugBloat))
-            , fGSSubpass(subpass) {
-        SkASSERT(Impl::kGeometryShader == proc.fImpl);
-        this->initGS();
-    }
-
-    void initGS();
-    void initVS(GrResourceProvider*);
-
-    void appendGSMesh(sk_sp<const GrBuffer> instanceBuffer, int instanceCount, int baseInstance,
-                      SkTArray<GrMesh>* out) const;
-    void appendVSMesh(sk_sp<const GrBuffer> instanceBuffer, int instanceCount, int baseInstance,
-                      SkTArray<GrMesh>* out) const;
-
-    GrGLSLPrimitiveProcessor* createGSImpl(std::unique_ptr<Shader>) const;
-    GrGLSLPrimitiveProcessor* createVSImpl(std::unique_ptr<Shader>) const;
-    // The type and meaning of this attribute depends on whether we're using VSImpl or GSImpl.
-    Attribute fVertexAttribute;
-
-    const PrimitiveType fPrimitiveType;
-    const Impl fImpl;
+    PrimitiveType fPrimitiveType;
     SkDEBUGCODE(float fDebugBloat = 0);
 
-    // Used by GSImpl.
-    const GSSubpass fGSSubpass = GSSubpass::kHulls;
-
-    // Used by VSImpl.
-    Attribute fInstanceAttributes[2];
-    sk_sp<const GrBuffer> fVSVertexBuffer;
-    sk_sp<const GrBuffer> fVSIndexBuffer;
-    int fVSNumIndicesPerInstance;
-    GrPrimitiveType fVSTriangleType;
+    class TriangleShader;
 
     typedef GrGeometryProcessor INHERITED;
 };
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp b/src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp
deleted file mode 100644
index 79ee1af..0000000
--- a/src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp
+++ /dev/null
@@ -1,419 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrCCCoverageProcessor.h"
-
-#include "GrMesh.h"
-#include "glsl/GrGLSLVertexGeoBuilder.h"
-
-using InputType = GrGLSLGeometryBuilder::InputType;
-using OutputType = GrGLSLGeometryBuilder::OutputType;
-
-/**
- * This class and its subclasses implement the coverage processor with geometry shaders.
- */
-class GrCCCoverageProcessor::GSImpl : public GrGLSLGeometryProcessor {
-protected:
-    GSImpl(std::unique_ptr<Shader> shader) : fShader(std::move(shader)) {}
-
-    virtual bool hasCoverage() const { return false; }
-
-    void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
-                 FPCoordTransformIter&& transformIter) final {
-        this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
-    }
-
-    void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) final {
-        const GrCCCoverageProcessor& proc = args.fGP.cast<GrCCCoverageProcessor>();
-
-        // The vertex shader simply forwards transposed x or y values to the geometry shader.
-        SkASSERT(1 == proc.numVertexAttributes());
-        gpArgs->fPositionVar = proc.fVertexAttribute.asShaderVar();
-
-        // Geometry shader.
-        GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
-        this->emitGeometryShader(proc, varyingHandler, args.fGeomBuilder, args.fRTAdjustName);
-        varyingHandler->emitAttributes(proc);
-        varyingHandler->setNoPerspective();
-        SkASSERT(!args.fFPCoordTransformHandler->nextCoordTransform());
-
-        // Fragment shader.
-        fShader->emitFragmentCode(proc, args.fFragBuilder, args.fOutputColor, args.fOutputCoverage);
-    }
-
-    void emitGeometryShader(const GrCCCoverageProcessor& proc,
-                            GrGLSLVaryingHandler* varyingHandler, GrGLSLGeometryBuilder* g,
-                            const char* rtAdjust) const {
-        int numInputPoints = proc.numInputPoints();
-        SkASSERT(3 == numInputPoints || 4 == numInputPoints);
-
-        int inputWidth = (4 == numInputPoints || proc.hasInputWeight()) ? 4 : 3;
-        const char* posValues = (4 == inputWidth) ? "sk_Position" : "sk_Position.xyz";
-        g->codeAppendf("float%ix2 pts = transpose(float2x%i(sk_in[0].%s, sk_in[1].%s));",
-                       inputWidth, inputWidth, posValues, posValues);
-
-        GrShaderVar wind("wind", kHalf_GrSLType);
-        g->declareGlobal(wind);
-        Shader::CalcWind(proc, g, "pts", wind.c_str());
-        if (PrimitiveType::kWeightedTriangles == proc.fPrimitiveType) {
-            SkASSERT(3 == numInputPoints);
-            SkASSERT(kFloat4_GrVertexAttribType == proc.fVertexAttribute.cpuType());
-            g->codeAppendf("%s *= half(sk_in[0].sk_Position.w);", wind.c_str());
-        }
-
-        SkString emitVertexFn;
-        SkSTArray<2, GrShaderVar> emitArgs;
-        const char* corner = emitArgs.emplace_back("corner", kFloat2_GrSLType).c_str();
-        const char* bloatdir = emitArgs.emplace_back("bloatdir", kFloat2_GrSLType).c_str();
-        const char* coverage = nullptr;
-        if (this->hasCoverage()) {
-            coverage = emitArgs.emplace_back("coverage", kHalf_GrSLType).c_str();
-        }
-        const char* cornerCoverage = nullptr;
-        if (GSSubpass::kCorners == proc.fGSSubpass) {
-            cornerCoverage = emitArgs.emplace_back("corner_coverage", kHalf2_GrSLType).c_str();
-        }
-        g->emitFunction(kVoid_GrSLType, "emitVertex", emitArgs.count(), emitArgs.begin(), [&]() {
-            SkString fnBody;
-            if (coverage) {
-                fnBody.appendf("%s *= %s;", coverage, wind.c_str());
-            }
-            if (cornerCoverage) {
-                fnBody.appendf("%s.x *= %s;", cornerCoverage, wind.c_str());
-            }
-            fnBody.appendf("float2 vertexpos = fma(%s, float2(bloat), %s);", bloatdir, corner);
-            fShader->emitVaryings(varyingHandler, GrGLSLVarying::Scope::kGeoToFrag, &fnBody,
-                                  "vertexpos", coverage ? coverage : wind.c_str(), cornerCoverage);
-            g->emitVertex(&fnBody, "vertexpos", rtAdjust);
-            return fnBody;
-        }().c_str(), &emitVertexFn);
-
-        float bloat = kAABloatRadius;
-#ifdef SK_DEBUG
-        if (proc.debugBloatEnabled()) {
-            bloat *= proc.debugBloat();
-        }
-#endif
-        g->defineConstant("bloat", bloat);
-
-        this->onEmitGeometryShader(proc, g, wind, emitVertexFn.c_str());
-    }
-
-    virtual void onEmitGeometryShader(const GrCCCoverageProcessor&, GrGLSLGeometryBuilder*,
-                                      const GrShaderVar& wind, const char* emitVertexFn) const = 0;
-
-    virtual ~GSImpl() {}
-
-    const std::unique_ptr<Shader> fShader;
-
-    typedef GrGLSLGeometryProcessor INHERITED;
-};
-
-/**
- * Generates conservative rasters around a triangle and its edges, and calculates coverage ramps.
- *
- * Triangle rough outlines are drawn in two steps: (1) draw a conservative raster of the entire
- * triangle, with a coverage of +1, and (2) draw conservative rasters around each edge, with a
- * coverage ramp from -1 to 0. These edge coverage values convert jagged conservative raster edges
- * into smooth, antialiased ones.
- *
- * The final corners get touched up in a later step by GSTriangleCornerImpl.
- */
-class GrCCCoverageProcessor::GSTriangleHullImpl : public GrCCCoverageProcessor::GSImpl {
-public:
-    GSTriangleHullImpl(std::unique_ptr<Shader> shader) : GSImpl(std::move(shader)) {}
-
-    bool hasCoverage() const override { return true; }
-
-    void onEmitGeometryShader(const GrCCCoverageProcessor&, GrGLSLGeometryBuilder* g,
-                              const GrShaderVar& wind, const char* emitVertexFn) const override {
-        fShader->emitSetupCode(g, "pts", wind.c_str());
-
-        // Visualize the input triangle as upright and equilateral, with a flat base. Paying special
-        // attention to wind, we can identify the points as top, bottom-left, and bottom-right.
-        //
-        // NOTE: We generate the rasters in 5 independent invocations, so each invocation designates
-        // the corner it will begin with as the top.
-        g->codeAppendf("int i = (%s > 0 ? sk_InvocationID : 4 - sk_InvocationID) %% 3;",
-                       wind.c_str());
-        g->codeAppend ("float2 top = pts[i];");
-        g->codeAppendf("float2 right = pts[(i + (%s > 0 ? 1 : 2)) %% 3];", wind.c_str());
-        g->codeAppendf("float2 left = pts[(i + (%s > 0 ? 2 : 1)) %% 3];", wind.c_str());
-
-        // Determine which direction to outset the conservative raster from each of the three edges.
-        g->codeAppend ("float2 leftbloat = sign(top - left);");
-        g->codeAppend ("leftbloat = float2(0 != leftbloat.y ? leftbloat.y : leftbloat.x, "
-                                          "0 != leftbloat.x ? -leftbloat.x : -leftbloat.y);");
-
-        g->codeAppend ("float2 rightbloat = sign(right - top);");
-        g->codeAppend ("rightbloat = float2(0 != rightbloat.y ? rightbloat.y : rightbloat.x, "
-                                           "0 != rightbloat.x ? -rightbloat.x : -rightbloat.y);");
-
-        g->codeAppend ("float2 downbloat = sign(left - right);");
-        g->codeAppend ("downbloat = float2(0 != downbloat.y ? downbloat.y : downbloat.x, "
-                                           "0 != downbloat.x ? -downbloat.x : -downbloat.y);");
-
-        // The triangle's conservative raster has a coverage of +1 all around.
-        g->codeAppend ("half4 coverages = half4(+1);");
-
-        // Edges have coverage ramps.
-        g->codeAppend ("if (sk_InvocationID >= 2) {"); // Are we an edge?
-        Shader::CalcEdgeCoverageAtBloatVertex(g, "top", "right",
-                                              "float2(+rightbloat.y, -rightbloat.x)",
-                                              "coverages[0]");
-        g->codeAppend (    "coverages.yzw = half3(-1, 0, -1 - coverages[0]);");
-        // Reassign bloats to characterize a conservative raster around a single edge, rather than
-        // the entire triangle.
-        g->codeAppend (    "leftbloat = downbloat = -rightbloat;");
-        g->codeAppend ("}");
-
-        // Here we generate the conservative raster geometry. The triangle's conservative raster is
-        // the convex hull of 3 pixel-size boxes centered on the input points. This translates to a
-        // convex polygon with either one, two, or three vertices at each input point (depending on
-        // how sharp the corner is) that we split between two invocations. Edge conservative rasters
-        // are convex hulls of 2 pixel-size boxes, one at each endpoint. For more details on
-        // conservative raster, see:
-        // https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter42.html
-        g->codeAppendf("bool2 left_right_notequal = notEqual(leftbloat, rightbloat);");
-        g->codeAppend ("if (all(left_right_notequal)) {");
-                           // The top corner will have three conservative raster vertices. Emit the
-                           // middle one first to the triangle strip.
-        g->codeAppendf(    "%s(top, float2(-leftbloat.y, +leftbloat.x), coverages[0]);",
-                           emitVertexFn);
-        g->codeAppend ("}");
-        g->codeAppend ("if (any(left_right_notequal)) {");
-                           // Second conservative raster vertex for the top corner.
-        g->codeAppendf(    "%s(top, rightbloat, coverages[1]);", emitVertexFn);
-        g->codeAppend ("}");
-
-        // Main interior body.
-        g->codeAppendf("%s(top, leftbloat, coverages[2]);", emitVertexFn);
-        g->codeAppendf("%s(right, rightbloat, coverages[1]);", emitVertexFn);
-
-        // Here the invocations diverge slightly. We can't symmetrically divide three triangle
-        // points between two invocations, so each does the following:
-        //
-        // sk_InvocationID=0: Finishes the main interior body of the triangle hull.
-        // sk_InvocationID=1: Remaining two conservative raster vertices for the third hull corner.
-        // sk_InvocationID=2..4: Finish the opposite endpoint of their corresponding edge.
-        g->codeAppendf("bool2 right_down_notequal = notEqual(rightbloat, downbloat);");
-        g->codeAppend ("if (any(right_down_notequal) || 0 == sk_InvocationID) {");
-        g->codeAppendf(    "%s((0 == sk_InvocationID) ? left : right, "
-                              "(0 == sk_InvocationID) ? leftbloat : downbloat, "
-                              "coverages[2]);", emitVertexFn);
-        g->codeAppend ("}");
-        g->codeAppend ("if (all(right_down_notequal) && 0 != sk_InvocationID) {");
-        g->codeAppendf(    "%s(right, float2(-rightbloat.y, +rightbloat.x), coverages[3]);",
-                           emitVertexFn);
-        g->codeAppend ("}");
-
-        // 5 invocations: 2 triangle hull invocations and 3 edges.
-        g->configure(InputType::kLines, OutputType::kTriangleStrip, 6, 5);
-    }
-};
-
-/**
- * Generates a conservative raster around a convex quadrilateral that encloses a cubic or quadratic.
- */
-class GrCCCoverageProcessor::GSCurveHullImpl : public GrCCCoverageProcessor::GSImpl {
-public:
-    GSCurveHullImpl(std::unique_ptr<Shader> shader) : GSImpl(std::move(shader)) {}
-
-    void onEmitGeometryShader(const GrCCCoverageProcessor&, GrGLSLGeometryBuilder* g,
-                              const GrShaderVar& wind, const char* emitVertexFn) const override {
-        const char* hullPts = "pts";
-        fShader->emitSetupCode(g, "pts", wind.c_str(), &hullPts);
-
-        // Visualize the input (convex) quadrilateral as a square. Paying special attention to wind,
-        // we can identify the points by their corresponding corner.
-        //
-        // NOTE: We split the square down the diagonal from top-right to bottom-left, and generate
-        // the hull in two independent invocations. Each invocation designates the corner it will
-        // begin with as top-left.
-        g->codeAppend ("int i = sk_InvocationID * 2;");
-        g->codeAppendf("float2 topleft = %s[i];", hullPts);
-        g->codeAppendf("float2 topright = %s[%s > 0 ? i + 1 : 3 - i];", hullPts, wind.c_str());
-        g->codeAppendf("float2 bottomleft = %s[%s > 0 ? 3 - i : i + 1];", hullPts, wind.c_str());
-        g->codeAppendf("float2 bottomright = %s[2 - i];", hullPts);
-
-        // Determine how much to outset the conservative raster hull from the relevant edges.
-        g->codeAppend ("float2 leftbloat = float2(topleft.y > bottomleft.y ? +1 : -1, "
-                                                 "topleft.x > bottomleft.x ? -1 : +1);");
-        g->codeAppend ("float2 upbloat = float2(topright.y > topleft.y ? +1 : -1, "
-                                               "topright.x > topleft.x ? -1 : +1);");
-        g->codeAppend ("float2 rightbloat = float2(bottomright.y > topright.y ? +1 : -1, "
-                                                  "bottomright.x > topright.x ? -1 : +1);");
-
-        // Here we generate the conservative raster geometry. It is the convex hull of 4 pixel-size
-        // boxes centered on the input points, split evenly between two invocations. This translates
-        // to a polygon with either one, two, or three vertices at each input point, depending on
-        // how sharp the corner is. For more details on conservative raster, see:
-        // https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter42.html
-        g->codeAppendf("bool2 left_up_notequal = notEqual(leftbloat, upbloat);");
-        g->codeAppend ("if (all(left_up_notequal)) {");
-                           // The top-left corner will have three conservative raster vertices.
-                           // Emit the middle one first to the triangle strip.
-        g->codeAppendf(    "%s(topleft, float2(-leftbloat.y, leftbloat.x));", emitVertexFn);
-        g->codeAppend ("}");
-        g->codeAppend ("if (any(left_up_notequal)) {");
-                           // Second conservative raster vertex for the top-left corner.
-        g->codeAppendf(    "%s(topleft, leftbloat);", emitVertexFn);
-        g->codeAppend ("}");
-
-        // Main interior body of this invocation's half of the hull.
-        g->codeAppendf("%s(topleft, upbloat);", emitVertexFn);
-        g->codeAppendf("%s(bottomleft, leftbloat);", emitVertexFn);
-        g->codeAppendf("%s(topright, upbloat);", emitVertexFn);
-
-        // Remaining two conservative raster vertices for the top-right corner.
-        g->codeAppendf("bool2 up_right_notequal = notEqual(upbloat, rightbloat);");
-        g->codeAppend ("if (any(up_right_notequal)) {");
-        g->codeAppendf(    "%s(topright, rightbloat);", emitVertexFn);
-        g->codeAppend ("}");
-        g->codeAppend ("if (all(up_right_notequal)) {");
-        g->codeAppendf(    "%s(topright, float2(-upbloat.y, upbloat.x));", emitVertexFn);
-        g->codeAppend ("}");
-
-        g->configure(InputType::kLines, OutputType::kTriangleStrip, 7, 2);
-    }
-};
-
-/**
- * Generates conservative rasters around corners (aka pixel-size boxes) and calculates
- * coverage and attenuation ramps to fix up the coverage values written by the hulls.
- */
-class GrCCCoverageProcessor::GSCornerImpl : public GrCCCoverageProcessor::GSImpl {
-public:
-    GSCornerImpl(std::unique_ptr<Shader> shader) : GSImpl(std::move(shader)) {}
-
-    bool hasCoverage() const override { return true; }
-
-    void onEmitGeometryShader(const GrCCCoverageProcessor& proc, GrGLSLGeometryBuilder* g,
-                              const GrShaderVar& wind, const char* emitVertexFn) const override {
-        fShader->emitSetupCode(g, "pts", wind.c_str());
-
-        g->codeAppendf("int corneridx = sk_InvocationID;");
-        if (!proc.isTriangles()) {
-            g->codeAppendf("corneridx *= %i;", proc.numInputPoints() - 1);
-        }
-
-        g->codeAppendf("float2 corner = pts[corneridx];");
-        g->codeAppendf("float2 left = pts[(corneridx + (%s > 0 ? %i : 1)) %% %i];",
-                       wind.c_str(), proc.numInputPoints() - 1, proc.numInputPoints());
-        g->codeAppendf("float2 right = pts[(corneridx + (%s > 0 ? 1 : %i)) %% %i];",
-                       wind.c_str(), proc.numInputPoints() - 1, proc.numInputPoints());
-
-        g->codeAppend ("float2 leftdir = corner - left;");
-        g->codeAppend ("leftdir = (float2(0) != leftdir) ? normalize(leftdir) : float2(1, 0);");
-
-        g->codeAppend ("float2 rightdir = right - corner;");
-        g->codeAppend ("rightdir = (float2(0) != rightdir) ? normalize(rightdir) : float2(1, 0);");
-
-        // Find "outbloat" and "crossbloat" at our corner. The outbloat points diagonally out of the
-        // triangle, in the direction that should ramp to zero coverage with attenuation. The
-        // crossbloat runs perpindicular to outbloat.
-        g->codeAppend ("float2 outbloat = float2(leftdir.x > rightdir.x ? +1 : -1, "
-                                                "leftdir.y > rightdir.y ? +1 : -1);");
-        g->codeAppend ("float2 crossbloat = float2(-outbloat.y, +outbloat.x);");
-
-        g->codeAppend ("half attenuation; {");
-        Shader::CalcCornerAttenuation(g, "leftdir", "rightdir", "attenuation");
-        g->codeAppend ("}");
-
-        if (proc.isTriangles()) {
-            g->codeAppend ("half2 left_coverages; {");
-            Shader::CalcEdgeCoveragesAtBloatVertices(g, "left", "corner", "-outbloat",
-                                                     "-crossbloat", "left_coverages");
-            g->codeAppend ("}");
-
-            g->codeAppend ("half2 right_coverages; {");
-            Shader::CalcEdgeCoveragesAtBloatVertices(g, "corner", "right", "-outbloat",
-                                                     "crossbloat", "right_coverages");
-            g->codeAppend ("}");
-
-            // Emit a corner box. The first coverage argument erases the values that were written
-            // previously by the hull and edge geometry. The second pair are multiplied together by
-            // the fragment shader. They ramp to 0 with attenuation in the direction of outbloat,
-            // and linearly from left-edge coverage to right-edge coverage in the direction of
-            // crossbloat.
-            //
-            // NOTE: Since this is not a linear mapping, it is important that the box's diagonal
-            // shared edge points in the direction of outbloat.
-            g->codeAppendf("%s(corner, -crossbloat, right_coverages[1] - left_coverages[1],"
-                              "half2(1 + left_coverages[1], 1));",
-                           emitVertexFn);
-
-            g->codeAppendf("%s(corner, outbloat, 1 + left_coverages[0] + right_coverages[0], "
-                              "half2(0, attenuation));",
-                           emitVertexFn);
-
-            g->codeAppendf("%s(corner, -outbloat, -1 - left_coverages[0] - right_coverages[0], "
-                              "half2(1 + left_coverages[0] + right_coverages[0], 1));",
-                           emitVertexFn);
-
-            g->codeAppendf("%s(corner, crossbloat, left_coverages[1] - right_coverages[1],"
-                              "half2(1 + right_coverages[1], 1));",
-                           emitVertexFn);
-        } else {
-            // Curves are simpler. The first coverage value of -1 means "wind = -wind", and causes
-            // the Shader to erase what it had written previously for the hull. Then, at each vertex
-            // of the corner box, the Shader will calculate the curve's local coverage value,
-            // interpolate it alongside our attenuation parameter, and multiply the two together for
-            // a final coverage value.
-            g->codeAppendf("%s(corner, -crossbloat, -1, half2(1));", emitVertexFn);
-            g->codeAppendf("%s(corner, outbloat, -1, half2(0, attenuation));",
-                           emitVertexFn);
-            g->codeAppendf("%s(corner, -outbloat, -1, half2(1));", emitVertexFn);
-            g->codeAppendf("%s(corner, crossbloat, -1, half2(1));", emitVertexFn);
-        }
-
-        g->configure(InputType::kLines, OutputType::kTriangleStrip, 4, proc.isTriangles() ? 3 : 2);
-    }
-};
-
-void GrCCCoverageProcessor::initGS() {
-    SkASSERT(Impl::kGeometryShader == fImpl);
-    if (4 == this->numInputPoints() || this->hasInputWeight()) {
-        fVertexAttribute =
-                {"x_or_y_values", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
-        GR_STATIC_ASSERT(sizeof(QuadPointInstance) ==
-                         2 * GrVertexAttribTypeSize(kFloat4_GrVertexAttribType));
-        GR_STATIC_ASSERT(offsetof(QuadPointInstance, fY) ==
-                         GrVertexAttribTypeSize(kFloat4_GrVertexAttribType));
-    } else {
-        fVertexAttribute =
-                {"x_or_y_values", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
-        GR_STATIC_ASSERT(sizeof(TriPointInstance) ==
-                         2 * GrVertexAttribTypeSize(kFloat3_GrVertexAttribType));
-        GR_STATIC_ASSERT(offsetof(TriPointInstance, fY) ==
-                         GrVertexAttribTypeSize(kFloat3_GrVertexAttribType));
-    }
-    this->setVertexAttributes(&fVertexAttribute, 1);
-    this->setWillUseGeoShader();
-}
-
-void GrCCCoverageProcessor::appendGSMesh(sk_sp<const GrBuffer> instanceBuffer, int instanceCount,
-                                         int baseInstance, SkTArray<GrMesh>* out) const {
-    // GSImpl doesn't actually make instanced draw calls. Instead, we feed transposed x,y point
-    // values to the GPU in a regular vertex array and draw kLines (see initGS). Then, each vertex
-    // invocation receives either the shape's x or y values as inputs, which it forwards to the
-    // geometry shader.
-    SkASSERT(Impl::kGeometryShader == fImpl);
-    GrMesh& mesh = out->emplace_back(GrPrimitiveType::kLines);
-    mesh.setNonIndexedNonInstanced(instanceCount * 2);
-    mesh.setVertexData(std::move(instanceBuffer), baseInstance * 2);
-}
-
-GrGLSLPrimitiveProcessor* GrCCCoverageProcessor::createGSImpl(std::unique_ptr<Shader> shadr) const {
-    if (GSSubpass::kHulls == fGSSubpass) {
-        return this->isTriangles()
-                   ? (GSImpl*) new GSTriangleHullImpl(std::move(shadr))
-                   : (GSImpl*) new GSCurveHullImpl(std::move(shadr));
-    }
-    SkASSERT(GSSubpass::kCorners == fGSSubpass);
-    return new GSCornerImpl(std::move(shadr));
-}
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor_VSImpl.cpp b/src/gpu/ccpr/GrCCCoverageProcessor_VSImpl.cpp
deleted file mode 100644
index 4162ee0..0000000
--- a/src/gpu/ccpr/GrCCCoverageProcessor_VSImpl.cpp
+++ /dev/null
@@ -1,553 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrCCCoverageProcessor.h"
-
-#include "GrMesh.h"
-#include "glsl/GrGLSLVertexGeoBuilder.h"
-
-// This class implements the coverage processor with vertex shaders.
-class GrCCCoverageProcessor::VSImpl : public GrGLSLGeometryProcessor {
-public:
-    VSImpl(std::unique_ptr<Shader> shader, int numSides)
-            : fShader(std::move(shader)), fNumSides(numSides) {}
-
-private:
-    void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
-                 FPCoordTransformIter&& transformIter) final {
-        this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
-    }
-
-    void onEmitCode(EmitArgs&, GrGPArgs*) override;
-
-    const std::unique_ptr<Shader> fShader;
-    const int fNumSides;
-};
-
-static constexpr int kInstanceAttribIdx_X = 0;  // Transposed X values of all input points.
-static constexpr int kInstanceAttribIdx_Y = 1;  // Transposed Y values of all input points.
-
-// Vertex data tells the shader how to offset vertices for conservative raster, as well as how to
-// calculate coverage values for corners and edges.
-static constexpr int kVertexData_LeftNeighborIdShift = 10;
-static constexpr int kVertexData_RightNeighborIdShift = 8;
-static constexpr int kVertexData_BloatIdxShift = 6;
-static constexpr int kVertexData_InvertNegativeCoverageBit = 1 << 5;
-static constexpr int kVertexData_IsCornerBit = 1 << 4;
-static constexpr int kVertexData_IsEdgeBit = 1 << 3;
-static constexpr int kVertexData_IsHullBit = 1 << 2;
-
-static constexpr int32_t pack_vertex_data(int32_t leftNeighborID, int32_t rightNeighborID,
-                                          int32_t bloatIdx, int32_t cornerID,
-                                          int32_t extraData = 0) {
-    return (leftNeighborID << kVertexData_LeftNeighborIdShift) |
-           (rightNeighborID << kVertexData_RightNeighborIdShift) |
-           (bloatIdx << kVertexData_BloatIdxShift) |
-           cornerID | extraData;
-}
-
-static constexpr int32_t hull_vertex_data(int32_t cornerID, int32_t bloatIdx, int n) {
-    return pack_vertex_data((cornerID + n - 1) % n, (cornerID + 1) % n, bloatIdx, cornerID,
-                            kVertexData_IsHullBit);
-}
-
-static constexpr int32_t edge_vertex_data(int32_t edgeID, int32_t endptIdx, int32_t bloatIdx,
-                                          int n) {
-    return pack_vertex_data(0 == endptIdx ? (edgeID + 1) % n : edgeID,
-                            0 == endptIdx ? (edgeID + 1) % n : edgeID,
-                            bloatIdx, 0 == endptIdx ? edgeID : (edgeID + 1) % n,
-                            kVertexData_IsEdgeBit |
-                            (!endptIdx ? kVertexData_InvertNegativeCoverageBit : 0));
-}
-
-static constexpr int32_t corner_vertex_data(int32_t leftID, int32_t cornerID, int32_t rightID,
-                                            int32_t bloatIdx) {
-    return pack_vertex_data(leftID, rightID, bloatIdx, cornerID, kVertexData_IsCornerBit);
-}
-
-static constexpr int32_t kTriangleVertices[] = {
-    hull_vertex_data(0, 0, 3),
-    hull_vertex_data(0, 1, 3),
-    hull_vertex_data(0, 2, 3),
-    hull_vertex_data(1, 0, 3),
-    hull_vertex_data(1, 1, 3),
-    hull_vertex_data(1, 2, 3),
-    hull_vertex_data(2, 0, 3),
-    hull_vertex_data(2, 1, 3),
-    hull_vertex_data(2, 2, 3),
-
-    edge_vertex_data(0, 0, 0, 3),
-    edge_vertex_data(0, 0, 1, 3),
-    edge_vertex_data(0, 0, 2, 3),
-    edge_vertex_data(0, 1, 0, 3),
-    edge_vertex_data(0, 1, 1, 3),
-    edge_vertex_data(0, 1, 2, 3),
-
-    edge_vertex_data(1, 0, 0, 3),
-    edge_vertex_data(1, 0, 1, 3),
-    edge_vertex_data(1, 0, 2, 3),
-    edge_vertex_data(1, 1, 0, 3),
-    edge_vertex_data(1, 1, 1, 3),
-    edge_vertex_data(1, 1, 2, 3),
-
-    edge_vertex_data(2, 0, 0, 3),
-    edge_vertex_data(2, 0, 1, 3),
-    edge_vertex_data(2, 0, 2, 3),
-    edge_vertex_data(2, 1, 0, 3),
-    edge_vertex_data(2, 1, 1, 3),
-    edge_vertex_data(2, 1, 2, 3),
-
-    corner_vertex_data(2, 0, 1, 0),
-    corner_vertex_data(2, 0, 1, 1),
-    corner_vertex_data(2, 0, 1, 2),
-    corner_vertex_data(2, 0, 1, 3),
-
-    corner_vertex_data(0, 1, 2, 0),
-    corner_vertex_data(0, 1, 2, 1),
-    corner_vertex_data(0, 1, 2, 2),
-    corner_vertex_data(0, 1, 2, 3),
-
-    corner_vertex_data(1, 2, 0, 0),
-    corner_vertex_data(1, 2, 0, 1),
-    corner_vertex_data(1, 2, 0, 2),
-    corner_vertex_data(1, 2, 0, 3),
-};
-
-GR_DECLARE_STATIC_UNIQUE_KEY(gTriangleVertexBufferKey);
-
-static constexpr uint16_t kRestartStrip = 0xffff;
-
-static constexpr uint16_t kTriangleIndicesAsStrips[] =  {
-    1, 2, 0, 3, 8, kRestartStrip, // First corner and main body of the hull.
-    4, 5, 3, 6, 8, 7, kRestartStrip, // Opposite side and corners of the hull.
-    10, 9, 11, 14, 12, 13, kRestartStrip, // First edge.
-    16, 15, 17, 20, 18, 19, kRestartStrip, // Second edge.
-    22, 21, 23, 26, 24, 25, kRestartStrip, // Third edge.
-    28, 27, 29, 30, kRestartStrip, // First corner.
-    32, 31, 33, 34, kRestartStrip, // Second corner.
-    36, 35, 37, 38 // Third corner.
-};
-
-static constexpr uint16_t kTriangleIndicesAsTris[] =  {
-    // First corner and main body of the hull.
-    1, 2, 0,
-    2, 3, 0,
-    0, 3, 8, // Main body.
-
-    // Opposite side and corners of the hull.
-    4, 5, 3,
-    5, 6, 3,
-    3, 6, 8,
-    6, 7, 8,
-
-    // First edge.
-    10,  9, 11,
-     9, 14, 11,
-    11, 14, 12,
-    14, 13, 12,
-
-    // Second edge.
-    16, 15, 17,
-    15, 20, 17,
-    17, 20, 18,
-    20, 19, 18,
-
-    // Third edge.
-    22, 21, 23,
-    21, 26, 23,
-    23, 26, 24,
-    26, 25, 24,
-
-    // First corner.
-    28, 27, 29,
-    27, 30, 29,
-
-    // Second corner.
-    32, 31, 33,
-    31, 34, 33,
-
-    // Third corner.
-    36, 35, 37,
-    35, 38, 37,
-};
-
-GR_DECLARE_STATIC_UNIQUE_KEY(gTriangleIndexBufferKey);
-
-// Curves, including quadratics, are drawn with a four-sided hull.
-static constexpr int32_t kCurveVertices[] = {
-    hull_vertex_data(0, 0, 4),
-    hull_vertex_data(0, 1, 4),
-    hull_vertex_data(0, 2, 4),
-    hull_vertex_data(1, 0, 4),
-    hull_vertex_data(1, 1, 4),
-    hull_vertex_data(1, 2, 4),
-    hull_vertex_data(2, 0, 4),
-    hull_vertex_data(2, 1, 4),
-    hull_vertex_data(2, 2, 4),
-    hull_vertex_data(3, 0, 4),
-    hull_vertex_data(3, 1, 4),
-    hull_vertex_data(3, 2, 4),
-
-    corner_vertex_data(3, 0, 1, 0),
-    corner_vertex_data(3, 0, 1, 1),
-    corner_vertex_data(3, 0, 1, 2),
-    corner_vertex_data(3, 0, 1, 3),
-
-    corner_vertex_data(2, 3, 0, 0),
-    corner_vertex_data(2, 3, 0, 1),
-    corner_vertex_data(2, 3, 0, 2),
-    corner_vertex_data(2, 3, 0, 3),
-};
-
-GR_DECLARE_STATIC_UNIQUE_KEY(gCurveVertexBufferKey);
-
-static constexpr uint16_t kCurveIndicesAsStrips[] =  {
-    1, 0, 2, 11, 3, 5, 4, kRestartStrip, // First half of the hull (split diagonally).
-    7, 6, 8, 5, 9, 11, 10, kRestartStrip, // Second half of the hull.
-    13, 12, 14, 15, kRestartStrip, // First corner.
-    17, 16, 18, 19 // Final corner.
-};
-
-static constexpr uint16_t kCurveIndicesAsTris[] =  {
-    // First half of the hull (split diagonally).
-     1,  0,  2,
-     0, 11,  2,
-     2, 11,  3,
-    11,  5,  3,
-     3,  5,  4,
-
-    // Second half of the hull.
-    7,  6,  8,
-    6,  5,  8,
-    8,  5,  9,
-    5, 11,  9,
-    9, 11, 10,
-
-    // First corner.
-    13, 12, 14,
-    12, 15, 14,
-
-    // Final corner.
-    17, 16, 18,
-    16, 19, 18,
-};
-
-GR_DECLARE_STATIC_UNIQUE_KEY(gCurveIndexBufferKey);
-
-// Generates a conservative raster hull around a triangle or curve. For triangles we generate
-// additional conservative rasters with coverage ramps around the edges and corners.
-//
-// Triangles are drawn in three steps: (1) Draw a conservative raster of the entire triangle, with a
-// coverage of +1. (2) Draw conservative rasters around each edge, with a coverage ramp from -1 to
-// 0. These edge coverage values convert jagged conservative raster edges into smooth, antialiased
-// ones. (3) Draw conservative rasters (aka pixel-size boxes) around each corner, replacing the
-// previous coverage values with ones that ramp to zero in the bloat vertices that fall outside the
-// triangle.
-//
-// Curves are drawn in two separate passes. Here we just draw a conservative raster around the input
-// points. The Shader takes care of everything else for now. The final curve corners get touched up
-// in a later step by VSCornerImpl.
-void GrCCCoverageProcessor::VSImpl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
-    const GrCCCoverageProcessor& proc = args.fGP.cast<GrCCCoverageProcessor>();
-    GrGLSLVertexBuilder* v = args.fVertBuilder;
-    int numInputPoints = proc.numInputPoints();
-
-    int inputWidth = (4 == numInputPoints || proc.hasInputWeight()) ? 4 : 3;
-    const char* swizzle = (4 == inputWidth) ? "xyzw" : "xyz";
-    v->codeAppendf("float%ix2 pts = transpose(float2x%i(%s.%s, %s.%s));", inputWidth, inputWidth,
-                   proc.fInstanceAttributes[kInstanceAttribIdx_X].name(), swizzle,
-                   proc.fInstanceAttributes[kInstanceAttribIdx_Y].name(), swizzle);
-
-    v->codeAppend ("half wind;");
-    Shader::CalcWind(proc, v, "pts", "wind");
-    if (PrimitiveType::kWeightedTriangles == proc.fPrimitiveType) {
-        SkASSERT(3 == numInputPoints);
-        SkASSERT(kFloat4_GrVertexAttribType ==
-                 proc.fInstanceAttributes[kInstanceAttribIdx_X].cpuType());
-        v->codeAppendf("wind *= half(%s.w);",
-                       proc.fInstanceAttributes[kInstanceAttribIdx_X].name());
-    }
-
-    float bloat = kAABloatRadius;
-#ifdef SK_DEBUG
-    if (proc.debugBloatEnabled()) {
-        bloat *= proc.debugBloat();
-    }
-#endif
-    v->defineConstant("bloat", bloat);
-
-    const char* hullPts = "pts";
-    fShader->emitSetupCode(v, "pts", "wind", (4 == fNumSides) ? &hullPts : nullptr);
-
-    // Reverse all indices if the wind is counter-clockwise: [0, 1, 2] -> [2, 1, 0].
-    v->codeAppendf("int clockwise_indices = wind > 0 ? %s : 0x%x - %s;",
-                   proc.fVertexAttribute.name(),
-                   ((fNumSides - 1) << kVertexData_LeftNeighborIdShift) |
-                   ((fNumSides - 1) << kVertexData_RightNeighborIdShift) |
-                   (((1 << kVertexData_RightNeighborIdShift) - 1) ^ 3) |
-                   (fNumSides - 1),
-                   proc.fVertexAttribute.name());
-
-    // Here we generate conservative raster geometry for the input polygon. It is the convex
-    // hull of N pixel-size boxes, one centered on each the input points. Each corner has three
-    // vertices, where one or two may cause degenerate triangles. The vertex data tells us how
-    // to offset each vertex. Triangle edges and corners are also handled here using the same
-    // concept. For more details on conservative raster, see:
-    // https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter42.html
-    v->codeAppendf("float2 corner = %s[clockwise_indices & 3];", hullPts);
-    v->codeAppendf("float2 left = %s[clockwise_indices >> %i];",
-                   hullPts, kVertexData_LeftNeighborIdShift);
-    v->codeAppendf("float2 right = %s[(clockwise_indices >> %i) & 3];",
-                   hullPts, kVertexData_RightNeighborIdShift);
-
-    v->codeAppend ("float2 leftbloat = sign(corner - left);");
-    v->codeAppend ("leftbloat = float2(0 != leftbloat.y ? leftbloat.y : leftbloat.x, "
-                                      "0 != leftbloat.x ? -leftbloat.x : -leftbloat.y);");
-
-    v->codeAppend ("float2 rightbloat = sign(right - corner);");
-    v->codeAppend ("rightbloat = float2(0 != rightbloat.y ? rightbloat.y : rightbloat.x, "
-                                       "0 != rightbloat.x ? -rightbloat.x : -rightbloat.y);");
-
-    v->codeAppend ("bool2 left_right_notequal = notEqual(leftbloat, rightbloat);");
-
-    v->codeAppend ("float2 bloatdir = leftbloat;");
-
-    v->codeAppend ("float2 leftdir = corner - left;");
-    v->codeAppend ("leftdir = (float2(0) != leftdir) ? normalize(leftdir) : float2(1, 0);");
-
-    v->codeAppend ("float2 rightdir = right - corner;");
-    v->codeAppend ("rightdir = (float2(0) != rightdir) ? normalize(rightdir) : float2(1, 0);");
-
-    v->codeAppendf("if (0 != (%s & %i)) {",  // Are we a corner?
-                   proc.fVertexAttribute.name(), kVertexData_IsCornerBit);
-
-                       // In corner boxes, all 4 coverage values will not map linearly.
-                       // Therefore it is important to align the box so its diagonal shared
-                       // edge points out of the triangle, in the direction that ramps to 0.
-    v->codeAppend (    "bloatdir = float2(leftdir.x > rightdir.x ? +1 : -1, "
-                                         "leftdir.y > rightdir.y ? +1 : -1);");
-
-                       // For corner boxes, we hack left_right_notequal to always true. This
-                       // in turn causes the upcoming code to always rotate, generating all
-                       // 4 vertices of the corner box.
-    v->codeAppendf(    "left_right_notequal = bool2(true);");
-    v->codeAppend ("}");
-
-    // At each corner of the polygon, our hull will have either 1, 2, or 3 vertices (or 4 if
-    // it's a corner box). We begin with this corner's first raster vertex (leftbloat), then
-    // continue rotating 90 degrees clockwise until we reach the desired raster vertex for this
-    // invocation. Corners with less than 3 corresponding raster vertices will result in
-    // redundant vertices and degenerate triangles.
-    v->codeAppendf("int bloatidx = (%s >> %i) & 3;", proc.fVertexAttribute.name(),
-                   kVertexData_BloatIdxShift);
-    v->codeAppend ("switch (bloatidx) {");
-    v->codeAppend (    "case 3:");
-                            // Only corners will have bloatidx=3, and corners always rotate.
-    v->codeAppend (        "bloatdir = float2(-bloatdir.y, +bloatdir.x);"); // 90 deg CW.
-                           // fallthru.
-    v->codeAppend (    "case 2:");
-    v->codeAppendf(        "if (all(left_right_notequal)) {");
-    v->codeAppend (            "bloatdir = float2(-bloatdir.y, +bloatdir.x);"); // 90 deg CW.
-    v->codeAppend (        "}");
-                           // fallthru.
-    v->codeAppend (    "case 1:");
-    v->codeAppendf(        "if (any(left_right_notequal)) {");
-    v->codeAppend (            "bloatdir = float2(-bloatdir.y, +bloatdir.x);"); // 90 deg CW.
-    v->codeAppend (        "}");
-                           // fallthru.
-    v->codeAppend ("}");
-
-    v->codeAppend ("float2 vertex = fma(bloatdir, float2(bloat), corner);");
-    gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertex");
-
-    // Hulls have a coverage of +1 all around.
-    v->codeAppend ("half coverage = +1;");
-
-    if (3 == fNumSides) {
-        v->codeAppend ("half left_coverage; {");
-        Shader::CalcEdgeCoverageAtBloatVertex(v, "left", "corner", "bloatdir", "left_coverage");
-        v->codeAppend ("}");
-
-        v->codeAppend ("half right_coverage; {");
-        Shader::CalcEdgeCoverageAtBloatVertex(v, "corner", "right", "bloatdir", "right_coverage");
-        v->codeAppend ("}");
-
-        v->codeAppendf("if (0 != (%s & %i)) {",  // Are we an edge?
-                       proc.fVertexAttribute.name(), kVertexData_IsEdgeBit);
-        v->codeAppend (    "coverage = left_coverage;");
-        v->codeAppend ("}");
-
-        v->codeAppendf("if (0 != (%s & %i)) {",  // Invert coverage?
-                       proc.fVertexAttribute.name(),
-                       kVertexData_InvertNegativeCoverageBit);
-        v->codeAppend (    "coverage = -1 - coverage;");
-        v->codeAppend ("}");
-    }
-
-    // Non-corner geometry should have zero effect from corner coverage.
-    v->codeAppend ("half2 corner_coverage = half2(0);");
-
-    v->codeAppendf("if (0 != (%s & %i)) {",  // Are we a corner?
-                   proc.fVertexAttribute.name(), kVertexData_IsCornerBit);
-                       // We use coverage=-1 to erase what the hull geometry wrote.
-                       //
-                       // In the context of curves, this effectively means "wind = -wind" and
-                       // causes the Shader to erase what it had written previously for the hull.
-                       //
-                       // For triangles it just erases the "+1" value written by the hull geometry.
-    v->codeAppend (    "coverage = -1;");
-    if (3 == fNumSides) {
-                       // Triangle corners also have to erase what the edge geometry wrote.
-        v->codeAppend ("coverage -= left_coverage + right_coverage;");
-    }
-
-                       // Corner boxes require attenuated coverage.
-    v->codeAppend (    "half attenuation; {");
-    Shader::CalcCornerAttenuation(v, "leftdir", "rightdir", "attenuation");
-    v->codeAppend (    "}");
-
-                       // Attenuate corner coverage towards the outermost vertex (where bloatidx=0).
-                       // This is all that curves need: At each vertex of the corner box, the curve
-                       // Shader will calculate the curve's local coverage value, interpolate it
-                       // alongside our attenuation parameter, and multiply the two together for a
-                       // final coverage value.
-    v->codeAppend (    "corner_coverage = (0 == bloatidx) ? half2(0, attenuation) : half2(1);");
-
-    if (3 == fNumSides) {
-                       // For triangles we also provide the actual coverage values at each vertex of
-                       // the corner box.
-        v->codeAppend ("if (1 == bloatidx || 2 == bloatidx) {");
-        v->codeAppend (    "corner_coverage.x += right_coverage;");
-        v->codeAppend ("}");
-        v->codeAppend ("if (bloatidx >= 2) {");
-        v->codeAppend (    "corner_coverage.x += left_coverage;");
-        v->codeAppend ("}");
-    }
-    v->codeAppend ("}");
-
-    GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
-    v->codeAppend ("coverage *= wind;");
-    v->codeAppend ("corner_coverage.x *= wind;");
-    fShader->emitVaryings(varyingHandler, GrGLSLVarying::Scope::kVertToFrag, &v->code(),
-                          gpArgs->fPositionVar.c_str(), "coverage", "corner_coverage");
-
-    varyingHandler->emitAttributes(proc);
-    SkASSERT(!args.fFPCoordTransformHandler->nextCoordTransform());
-
-    // Fragment shader.
-    fShader->emitFragmentCode(proc, args.fFragBuilder, args.fOutputColor, args.fOutputCoverage);
-}
-
-void GrCCCoverageProcessor::initVS(GrResourceProvider* rp) {
-    SkASSERT(Impl::kVertexShader == fImpl);
-    const GrCaps& caps = *rp->caps();
-
-    switch (fPrimitiveType) {
-        case PrimitiveType::kTriangles:
-        case PrimitiveType::kWeightedTriangles: {
-            GR_DEFINE_STATIC_UNIQUE_KEY(gTriangleVertexBufferKey);
-            fVSVertexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kVertex,
-                                                         sizeof(kTriangleVertices),
-                                                         kTriangleVertices,
-                                                         gTriangleVertexBufferKey);
-            GR_DEFINE_STATIC_UNIQUE_KEY(gTriangleIndexBufferKey);
-            if (caps.usePrimitiveRestart()) {
-                fVSIndexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kIndex,
-                                                            sizeof(kTriangleIndicesAsStrips),
-                                                            kTriangleIndicesAsStrips,
-                                                            gTriangleIndexBufferKey);
-                fVSNumIndicesPerInstance = SK_ARRAY_COUNT(kTriangleIndicesAsStrips);
-            } else {
-                fVSIndexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kIndex,
-                                                            sizeof(kTriangleIndicesAsTris),
-                                                            kTriangleIndicesAsTris,
-                                                            gTriangleIndexBufferKey);
-                fVSNumIndicesPerInstance = SK_ARRAY_COUNT(kTriangleIndicesAsTris);
-            }
-            break;
-        }
-
-        case PrimitiveType::kQuadratics:
-        case PrimitiveType::kCubics:
-        case PrimitiveType::kConics: {
-            GR_DEFINE_STATIC_UNIQUE_KEY(gCurveVertexBufferKey);
-            fVSVertexBuffer =
-                    rp->findOrMakeStaticBuffer(GrGpuBufferType::kVertex, sizeof(kCurveVertices),
-                                               kCurveVertices, gCurveVertexBufferKey);
-            GR_DEFINE_STATIC_UNIQUE_KEY(gCurveIndexBufferKey);
-            if (caps.usePrimitiveRestart()) {
-                fVSIndexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kIndex,
-                                                            sizeof(kCurveIndicesAsStrips),
-                                                            kCurveIndicesAsStrips,
-                                                            gCurveIndexBufferKey);
-                fVSNumIndicesPerInstance = SK_ARRAY_COUNT(kCurveIndicesAsStrips);
-            } else {
-                fVSIndexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kIndex,
-                                                            sizeof(kCurveIndicesAsTris),
-                                                            kCurveIndicesAsTris,
-                                                            gCurveIndexBufferKey);
-                fVSNumIndicesPerInstance = SK_ARRAY_COUNT(kCurveIndicesAsTris);
-            }
-            break;
-        }
-    }
-
-    GrVertexAttribType xyAttribType;
-    GrSLType xySLType;
-    if (4 == this->numInputPoints() || this->hasInputWeight()) {
-        GR_STATIC_ASSERT(offsetof(QuadPointInstance, fX) == 0);
-        GR_STATIC_ASSERT(sizeof(QuadPointInstance::fX) ==
-                         GrVertexAttribTypeSize(kFloat4_GrVertexAttribType));
-        GR_STATIC_ASSERT(sizeof(QuadPointInstance::fY) ==
-                         GrVertexAttribTypeSize(kFloat4_GrVertexAttribType));
-        xyAttribType = kFloat4_GrVertexAttribType;
-        xySLType = kFloat4_GrSLType;
-    } else {
-        GR_STATIC_ASSERT(offsetof(TriPointInstance, fX) == 0);
-        GR_STATIC_ASSERT(sizeof(TriPointInstance::fX) ==
-                         GrVertexAttribTypeSize(kFloat3_GrVertexAttribType));
-        GR_STATIC_ASSERT(sizeof(TriPointInstance::fY) ==
-                         GrVertexAttribTypeSize(kFloat3_GrVertexAttribType));
-        xyAttribType = kFloat3_GrVertexAttribType;
-        xySLType = kFloat3_GrSLType;
-    }
-    fInstanceAttributes[kInstanceAttribIdx_X] = {"X", xyAttribType, xySLType};
-    fInstanceAttributes[kInstanceAttribIdx_Y] = {"Y", xyAttribType, xySLType};
-    this->setInstanceAttributes(fInstanceAttributes, 2);
-    fVertexAttribute = {"vertexdata", kInt_GrVertexAttribType, kInt_GrSLType};
-    this->setVertexAttributes(&fVertexAttribute, 1);
-
-    if (caps.usePrimitiveRestart()) {
-        fVSTriangleType = GrPrimitiveType::kTriangleStrip;
-    } else {
-        fVSTriangleType = GrPrimitiveType::kTriangles;
-    }
-}
-
-void GrCCCoverageProcessor::appendVSMesh(sk_sp<const GrBuffer> instanceBuffer, int instanceCount,
-                                         int baseInstance, SkTArray<GrMesh>* out) const {
-    SkASSERT(Impl::kVertexShader == fImpl);
-    GrMesh& mesh = out->emplace_back(fVSTriangleType);
-    auto primitiveRestart = GrPrimitiveRestart(GrPrimitiveType::kTriangleStrip == fVSTriangleType);
-    mesh.setIndexedInstanced(fVSIndexBuffer, fVSNumIndicesPerInstance, std::move(instanceBuffer),
-                             instanceCount, baseInstance, primitiveRestart);
-    mesh.setVertexData(fVSVertexBuffer, 0);
-}
-
-GrGLSLPrimitiveProcessor* GrCCCoverageProcessor::createVSImpl(std::unique_ptr<Shader> shadr) const {
-    switch (fPrimitiveType) {
-        case PrimitiveType::kTriangles:
-        case PrimitiveType::kWeightedTriangles:
-            return new VSImpl(std::move(shadr), 3);
-        case PrimitiveType::kQuadratics:
-        case PrimitiveType::kCubics:
-        case PrimitiveType::kConics:
-            return new VSImpl(std::move(shadr), 4);
-    }
-    SK_ABORT("Invalid RenderPass");
-    return nullptr;
-}
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.cpp b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
index 643118a..d16bff5 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.cpp
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
@@ -7,10 +7,10 @@
 
 #include "GrCCDrawPathsOp.h"
 
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrMemoryPool.h"
 #include "GrOpFlushState.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "ccpr/GrCCPathCache.h"
 #include "ccpr/GrCCPerFlushResources.h"
 #include "ccpr/GrCoverageCountingPathRenderer.h"
@@ -26,8 +26,8 @@
 }
 
 std::unique_ptr<GrCCDrawPathsOp> GrCCDrawPathsOp::Make(
-        GrContext* context, const SkIRect& clipIBounds, const SkMatrix& m, const GrShape& shape,
-        GrPaint&& paint) {
+        GrRecordingContext* context, const SkIRect& clipIBounds, const SkMatrix& m,
+        const GrShape& shape, GrPaint&& paint) {
     SkRect conservativeDevBounds;
     m.mapRect(&conservativeDevBounds, shape.bounds());
 
@@ -75,8 +75,9 @@
 }
 
 std::unique_ptr<GrCCDrawPathsOp> GrCCDrawPathsOp::InternalMake(
-        GrContext* context, const SkIRect& clipIBounds, const SkMatrix& m, const GrShape& shape,
-        float strokeDevWidth, const SkRect& conservativeDevBounds, GrPaint&& paint) {
+        GrRecordingContext* context, const SkIRect& clipIBounds, const SkMatrix& m,
+        const GrShape& shape, float strokeDevWidth, const SkRect& conservativeDevBounds,
+        GrPaint&& paint) {
     // The path itself should have been cropped if larger than kPathCropThreshold. If it had a
     // stroke, that would have further inflated its draw bounds.
     SkASSERT(SkTMax(conservativeDevBounds.height(), conservativeDevBounds.width()) <
@@ -138,16 +139,18 @@
 #endif
 }
 
-GrProcessorSet::Analysis GrCCDrawPathsOp::finalize(const GrCaps& caps, const GrAppliedClip* clip) {
+GrProcessorSet::Analysis GrCCDrawPathsOp::finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                                   GrFSAAType fsaaType, GrClampType clampType) {
     SkASSERT(1 == fNumDraws);  // There should only be one single path draw in this Op right now.
-    return fDraws.head().finalize(caps, clip, &fProcessors);
+    return fDraws.head().finalize(caps, clip, fsaaType, clampType, &fProcessors);
 }
 
 GrProcessorSet::Analysis GrCCDrawPathsOp::SingleDraw::finalize(
-        const GrCaps& caps, const GrAppliedClip* clip, GrProcessorSet* processors) {
+        const GrCaps& caps, const GrAppliedClip* clip, GrFSAAType fsaaType, GrClampType clampType,
+        GrProcessorSet* processors) {
     const GrProcessorSet::Analysis& analysis = processors->finalize(
-            fColor, GrProcessorAnalysisCoverage::kSingleChannel, clip, false, caps,
-            &fColor);
+            fColor, GrProcessorAnalysisCoverage::kSingleChannel, clip,
+            &GrUserStencilSettings::kUnused, fsaaType, caps, clampType, &fColor);
 
     // Lines start looking jagged when they get thinner than 1px. For thin strokes it looks better
     // if we can convert them to hairline (i.e., inflate the stroke width to 1px), and instead
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.h b/src/gpu/ccpr/GrCCDrawPathsOp.h
index fef4ead..6984a5d 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.h
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.h
@@ -14,11 +14,12 @@
 #include "ccpr/GrCCPathCache.h"
 #include "ops/GrDrawOp.h"
 
+class GrCCAtlas;
+class GrCCPerFlushResources;
 struct GrCCPerFlushResourceSpecs;
 struct GrCCPerOpListPaths;
-class GrCCAtlas;
 class GrOnFlushResourceProvider;
-class GrCCPerFlushResources;
+class GrRecordingContext;
 
 /**
  * This is the Op that draws paths to the actual canvas, using atlases generated by CCPR.
@@ -28,13 +29,14 @@
     DEFINE_OP_CLASS_ID
     SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrCCDrawPathsOp);
 
-    static std::unique_ptr<GrCCDrawPathsOp> Make(GrContext*, const SkIRect& clipIBounds,
+    static std::unique_ptr<GrCCDrawPathsOp> Make(GrRecordingContext*, const SkIRect& clipIBounds,
                                                  const SkMatrix&, const GrShape&, GrPaint&&);
     ~GrCCDrawPathsOp() override;
 
     const char* name() const override { return "GrCCDrawPathsOp"; }
     FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
-    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*) override;
+    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrFSAAType,
+                                      GrClampType) override;
     CombineResult onCombineIfPossible(GrOp*, const GrCaps&) override;
     void visitProxies(const VisitProxyFunc& fn, VisitorType) const override {
         fProcessors.visitProxies(fn);
@@ -68,7 +70,8 @@
 private:
     friend class GrOpMemoryPool;
 
-    static std::unique_ptr<GrCCDrawPathsOp> InternalMake(GrContext*, const SkIRect& clipIBounds,
+    static std::unique_ptr<GrCCDrawPathsOp> InternalMake(GrRecordingContext*,
+                                                         const SkIRect& clipIBounds,
                                                          const SkMatrix&, const GrShape&,
                                                          float strokeDevWidth,
                                                          const SkRect& conservativeDevBounds,
@@ -89,7 +92,8 @@
                    const SkPMColor4f&);
 
         // See the corresponding methods in GrCCDrawPathsOp.
-        GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrProcessorSet*);
+        GrProcessorSet::Analysis finalize(
+                const GrCaps&, const GrAppliedClip*, GrFSAAType, GrClampType, GrProcessorSet*);
         void accountForOwnPath(GrCCPathCache*, GrOnFlushResourceProvider*,
                                GrCCPerFlushResourceSpecs*);
         void setupResources(GrCCPathCache*, GrOnFlushResourceProvider*, GrCCPerFlushResources*,
diff --git a/src/gpu/ccpr/GrCCFiller.cpp b/src/gpu/ccpr/GrCCFiller.cpp
index 311eb95..8e26c2f 100644
--- a/src/gpu/ccpr/GrCCFiller.cpp
+++ b/src/gpu/ccpr/GrCCFiller.cpp
@@ -459,54 +459,58 @@
     return true;
 }
 
-void GrCCFiller::drawFills(GrOpFlushState* flushState, BatchID batchID,
-                           const SkIRect& drawBounds) const {
+void GrCCFiller::drawFills(GrOpFlushState* flushState, GrCCCoverageProcessor* proc,
+                           BatchID batchID, const SkIRect& drawBounds) const {
     using PrimitiveType = GrCCCoverageProcessor::PrimitiveType;
 
     SkASSERT(fInstanceBuffer);
 
+    GrResourceProvider* rp = flushState->resourceProvider();
     const PrimitiveTallies& batchTotalCounts = fBatches[batchID].fTotalPrimitiveCounts;
 
     GrPipeline pipeline(GrScissorTest::kEnabled, SkBlendMode::kPlus);
 
     if (batchTotalCounts.fTriangles) {
-        this->drawPrimitives(flushState, pipeline, batchID, PrimitiveType::kTriangles,
-                             &PrimitiveTallies::fTriangles, drawBounds);
+        proc->reset(PrimitiveType::kTriangles, rp);
+        this->drawPrimitives(
+                flushState, *proc, pipeline, batchID, &PrimitiveTallies::fTriangles, drawBounds);
     }
 
     if (batchTotalCounts.fWeightedTriangles) {
-        this->drawPrimitives(flushState, pipeline, batchID, PrimitiveType::kWeightedTriangles,
-                             &PrimitiveTallies::fWeightedTriangles, drawBounds);
+        proc->reset(PrimitiveType::kWeightedTriangles, rp);
+        this->drawPrimitives(
+                flushState, *proc, pipeline, batchID, &PrimitiveTallies::fWeightedTriangles,
+                drawBounds);
     }
 
     if (batchTotalCounts.fQuadratics) {
-        this->drawPrimitives(flushState, pipeline, batchID, PrimitiveType::kQuadratics,
-                             &PrimitiveTallies::fQuadratics, drawBounds);
+        proc->reset(PrimitiveType::kQuadratics, rp);
+        this->drawPrimitives(
+                flushState, *proc, pipeline, batchID, &PrimitiveTallies::fQuadratics, drawBounds);
     }
 
     if (batchTotalCounts.fCubics) {
-        this->drawPrimitives(flushState, pipeline, batchID, PrimitiveType::kCubics,
-                             &PrimitiveTallies::fCubics, drawBounds);
+        proc->reset(PrimitiveType::kCubics, rp);
+        this->drawPrimitives(
+                flushState, *proc, pipeline, batchID, &PrimitiveTallies::fCubics, drawBounds);
     }
 
     if (batchTotalCounts.fConics) {
-        this->drawPrimitives(flushState, pipeline, batchID, PrimitiveType::kConics,
-                             &PrimitiveTallies::fConics, drawBounds);
+        proc->reset(PrimitiveType::kConics, rp);
+        this->drawPrimitives(
+                flushState, *proc, pipeline, batchID, &PrimitiveTallies::fConics, drawBounds);
     }
 }
 
-void GrCCFiller::drawPrimitives(GrOpFlushState* flushState, const GrPipeline& pipeline,
-                                BatchID batchID, GrCCCoverageProcessor::PrimitiveType primitiveType,
-                                int PrimitiveTallies::*instanceType,
-                                const SkIRect& drawBounds) const {
+void GrCCFiller::drawPrimitives(
+        GrOpFlushState* flushState, const GrCCCoverageProcessor& proc, const GrPipeline& pipeline,
+        BatchID batchID, int PrimitiveTallies::*instanceType, const SkIRect& drawBounds) const {
     SkASSERT(pipeline.isScissorEnabled());
 
     // Don't call reset(), as that also resets the reserve count.
     fMeshesScratchBuffer.pop_back_n(fMeshesScratchBuffer.count());
     fScissorRectScratchBuffer.pop_back_n(fScissorRectScratchBuffer.count());
 
-    GrCCCoverageProcessor proc(flushState->resourceProvider(), primitiveType);
-
     SkASSERT(batchID > 0);
     SkASSERT(batchID < fBatches.count());
     const Batch& previousBatch = fBatches[batchID - 1];
diff --git a/src/gpu/ccpr/GrCCFiller.h b/src/gpu/ccpr/GrCCFiller.h
index 45a03a4..4d61a2e 100644
--- a/src/gpu/ccpr/GrCCFiller.h
+++ b/src/gpu/ccpr/GrCCFiller.h
@@ -45,7 +45,8 @@
     bool prepareToDraw(GrOnFlushResourceProvider*);
 
     // Called after prepareToDraw(). Draws the given batch of path fills.
-    void drawFills(GrOpFlushState*, BatchID, const SkIRect& drawBounds) const;
+    void drawFills(
+            GrOpFlushState*, GrCCCoverageProcessor*, BatchID, const SkIRect& drawBounds) const;
 
 private:
     static constexpr int kNumScissorModes = 2;
@@ -95,9 +96,8 @@
         SkIRect fScissor;
     };
 
-    void drawPrimitives(GrOpFlushState*, const GrPipeline&, BatchID,
-                        GrCCCoverageProcessor::PrimitiveType, int PrimitiveTallies::*instanceType,
-                        const SkIRect& drawBounds) const;
+    void drawPrimitives(GrOpFlushState*, const GrCCCoverageProcessor&, const GrPipeline&, BatchID,
+                        int PrimitiveTallies::*instanceType, const SkIRect& drawBounds) const;
 
     GrCCFillGeometry fGeometry;
     SkSTArray<32, PathInfo, true> fPathInfos;
@@ -106,7 +106,7 @@
     PrimitiveTallies fTotalPrimitiveCounts[kNumScissorModes];
     int fMaxMeshesPerDraw = 0;
 
-    sk_sp<GrBuffer> fInstanceBuffer;
+    sk_sp<GrGpuBuffer> fInstanceBuffer;
     PrimitiveTallies fBaseInstances[kNumScissorModes];
     mutable SkSTArray<32, GrMesh> fMeshesScratchBuffer;
     mutable SkSTArray<32, SkIRect> fScissorRectScratchBuffer;
diff --git a/src/gpu/ccpr/GrCCPathProcessor.cpp b/src/gpu/ccpr/GrCCPathProcessor.cpp
index 06e6435..feefd4f 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.cpp
+++ b/src/gpu/ccpr/GrCCPathProcessor.cpp
@@ -34,7 +34,7 @@
 
 GR_DECLARE_STATIC_UNIQUE_KEY(gVertexBufferKey);
 
-sk_sp<const GrBuffer> GrCCPathProcessor::FindVertexBuffer(GrOnFlushResourceProvider* onFlushRP) {
+sk_sp<const GrGpuBuffer> GrCCPathProcessor::FindVertexBuffer(GrOnFlushResourceProvider* onFlushRP) {
     GR_DEFINE_STATIC_UNIQUE_KEY(gVertexBufferKey);
     return onFlushRP->findOrMakeStaticBuffer(GrGpuBufferType::kVertex, sizeof(kOctoEdgeNorms),
                                              kOctoEdgeNorms, gVertexBufferKey);
@@ -64,7 +64,7 @@
 constexpr GrPrimitiveProcessor::Attribute GrCCPathProcessor::kInstanceAttribs[];
 constexpr GrPrimitiveProcessor::Attribute GrCCPathProcessor::kEdgeNormsAttrib;
 
-sk_sp<const GrBuffer> GrCCPathProcessor::FindIndexBuffer(GrOnFlushResourceProvider* onFlushRP) {
+sk_sp<const GrGpuBuffer> GrCCPathProcessor::FindIndexBuffer(GrOnFlushResourceProvider* onFlushRP) {
     GR_DEFINE_STATIC_UNIQUE_KEY(gIndexBufferKey);
     if (onFlushRP->caps()->usePrimitiveRestart()) {
         return onFlushRP->findOrMakeStaticBuffer(GrGpuBufferType::kIndex,
diff --git a/src/gpu/ccpr/GrCCPathProcessor.h b/src/gpu/ccpr/GrCCPathProcessor.h
index 2f54d67..534f08d 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.h
+++ b/src/gpu/ccpr/GrCCPathProcessor.h
@@ -66,8 +66,8 @@
 
     GR_STATIC_ASSERT(4 * 12 == sizeof(Instance));
 
-    static sk_sp<const GrBuffer> FindVertexBuffer(GrOnFlushResourceProvider*);
-    static sk_sp<const GrBuffer> FindIndexBuffer(GrOnFlushResourceProvider*);
+    static sk_sp<const GrGpuBuffer> FindVertexBuffer(GrOnFlushResourceProvider*);
+    static sk_sp<const GrGpuBuffer> FindIndexBuffer(GrOnFlushResourceProvider*);
 
     GrCCPathProcessor(const GrTextureProxy* atlas,
                       const SkMatrix& viewMatrixIfUsingLocalCoords = SkMatrix::I());
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.cpp b/src/gpu/ccpr/GrCCPerFlushResources.cpp
index b1aa3fa..5b612be 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.cpp
+++ b/src/gpu/ccpr/GrCCPerFlushResources.cpp
@@ -10,11 +10,15 @@
 #include "GrClip.h"
 #include "GrMemoryPool.h"
 #include "GrOnFlushResourceProvider.h"
-#include "GrSurfaceContextPriv.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrShape.h"
+#include "GrSurfaceContextPriv.h"
 #include "SkMakeUnique.h"
 #include "ccpr/GrCCPathCache.h"
+#include "ccpr/GrGSCoverageProcessor.h"
+#include "ccpr/GrVSCoverageProcessor.h"
 
 using FillBatchID = GrCCFiller::BatchID;
 using StrokeBatchID = GrCCStroker::BatchID;
@@ -29,7 +33,8 @@
 class AtlasOp : public GrDrawOp {
 public:
     FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
-    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*) override {
+    GrProcessorSet::Analysis finalize(
+            const GrCaps&, const GrAppliedClip*, GrFSAAType, GrClampType) override {
         return GrProcessorSet::EmptySetAnalysis();
     }
     CombineResult onCombineIfPossible(GrOp* other, const GrCaps&) override {
@@ -56,7 +61,7 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           sk_sp<const GrCCPerFlushResources> resources,
                                           sk_sp<GrTextureProxy> copyProxy, int baseInstance,
                                           int endInstance, const SkISize& drawBounds) {
@@ -97,11 +102,11 @@
 };
 
 // Renders coverage counts to a CCPR atlas using the resources' pre-filled GrCCPathParser.
-class RenderAtlasOp : public AtlasOp {
+template<typename ProcessorType> class RenderAtlasOp : public AtlasOp {
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           sk_sp<const GrCCPerFlushResources> resources,
                                           FillBatchID fillBatchID, StrokeBatchID strokeBatchID,
                                           const SkISize& drawBounds) {
@@ -115,8 +120,9 @@
     const char* name() const override { return "RenderAtlasOp (CCPR)"; }
 
     void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
-        fResources->filler().drawFills(flushState, fFillBatchID, fDrawBounds);
-        fResources->stroker().drawStrokes(flushState, fStrokeBatchID, fDrawBounds);
+        ProcessorType proc;
+        fResources->filler().drawFills(flushState, &proc, fFillBatchID, fDrawBounds);
+        fResources->stroker().drawStrokes(flushState, &proc, fStrokeBatchID, fDrawBounds);
     }
 
 private:
@@ -492,9 +498,16 @@
         }
 
         if (auto rtc = atlas->makeRenderTargetContext(onFlushRP, std::move(backingTexture))) {
-            auto op = RenderAtlasOp::Make(rtc->surfPriv().getContext(), sk_ref_sp(this),
-                                          atlas->getFillBatchID(), atlas->getStrokeBatchID(),
-                                          atlas->drawBounds());
+            std::unique_ptr<GrDrawOp> op;
+            if (onFlushRP->caps()->shaderCaps()->geometryShaderSupport()) {
+                op = RenderAtlasOp<GrGSCoverageProcessor>::Make(
+                        rtc->surfPriv().getContext(), sk_ref_sp(this), atlas->getFillBatchID(),
+                        atlas->getStrokeBatchID(), atlas->drawBounds());
+            } else {
+                op = RenderAtlasOp<GrVSCoverageProcessor>::Make(
+                        rtc->surfPriv().getContext(), sk_ref_sp(this), atlas->getFillBatchID(),
+                        atlas->getStrokeBatchID(), atlas->drawBounds());
+            }
             rtc->addDrawOp(GrNoClip(), std::move(op));
             out->push_back(std::move(rtc));
         }
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.h b/src/gpu/ccpr/GrCCPerFlushResources.h
index fc69cee..d507787 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.h
+++ b/src/gpu/ccpr/GrCCPerFlushResources.h
@@ -105,15 +105,15 @@
     // Accessors used by draw calls, once the resources have been finalized.
     const GrCCFiller& filler() const { SkASSERT(!this->isMapped()); return fFiller; }
     const GrCCStroker& stroker() const { SkASSERT(!this->isMapped()); return fStroker; }
-    sk_sp<const GrBuffer> refIndexBuffer() const {
+    sk_sp<const GrGpuBuffer> refIndexBuffer() const {
         SkASSERT(!this->isMapped());
         return fIndexBuffer;
     }
-    sk_sp<const GrBuffer> refVertexBuffer() const {
+    sk_sp<const GrGpuBuffer> refVertexBuffer() const {
         SkASSERT(!this->isMapped());
         return fVertexBuffer;
     }
-    sk_sp<const GrBuffer> refInstanceBuffer() const {
+    sk_sp<const GrGpuBuffer> refInstanceBuffer() const {
         SkASSERT(!this->isMapped());
         return fInstanceBuffer;
     }
@@ -131,9 +131,9 @@
     GrCCAtlasStack fCopyAtlasStack;
     GrCCAtlasStack fRenderedAtlasStack;
 
-    const sk_sp<const GrBuffer> fIndexBuffer;
-    const sk_sp<const GrBuffer> fVertexBuffer;
-    const sk_sp<GrBuffer> fInstanceBuffer;
+    const sk_sp<const GrGpuBuffer> fIndexBuffer;
+    const sk_sp<const GrGpuBuffer> fVertexBuffer;
+    const sk_sp<GrGpuBuffer> fInstanceBuffer;
 
     GrCCPathProcessor::Instance* fPathInstanceData = nullptr;
     int fNextCopyInstanceIdx;
diff --git a/src/gpu/ccpr/GrCCStroker.cpp b/src/gpu/ccpr/GrCCStroker.cpp
index e0e1bc3..65f1c41 100644
--- a/src/gpu/ccpr/GrCCStroker.cpp
+++ b/src/gpu/ccpr/GrCCStroker.cpp
@@ -497,7 +497,7 @@
         }
     }
 
-    sk_sp<GrBuffer> finish() {
+    sk_sp<GrGpuBuffer> finish() {
         SkASSERT(this->isMapped());
         SkASSERT(!memcmp(fNextInstances, fEndInstances, sizeof(fNextInstances)));
         fInstanceBuffer->unmap();
@@ -543,7 +543,7 @@
     InstanceTallies* fCurrNextInstances;
     SkDEBUGCODE(const InstanceTallies* fCurrEndInstances);
 
-    sk_sp<GrBuffer> fInstanceBuffer;
+    sk_sp<GrGpuBuffer> fInstanceBuffer;
     void* fInstanceBufferData = nullptr;
     InstanceTallies fNextInstances[2];
     SkDEBUGCODE(InstanceTallies fEndInstances[2]);
@@ -672,8 +672,8 @@
     return true;
 }
 
-void GrCCStroker::drawStrokes(GrOpFlushState* flushState, BatchID batchID,
-                              const SkIRect& drawBounds) const {
+void GrCCStroker::drawStrokes(GrOpFlushState* flushState, GrCCCoverageProcessor* proc,
+                              BatchID batchID, const SkIRect& drawBounds) const {
     using PrimitiveType = GrCCCoverageProcessor::PrimitiveType;
     SkASSERT(fInstanceBuffer);
 
@@ -708,14 +708,14 @@
     }
 
     // Draw triangles.
-    GrCCCoverageProcessor triProc(flushState->resourceProvider(), PrimitiveType::kTriangles);
+    proc->reset(PrimitiveType::kTriangles, flushState->resourceProvider());
     this->drawConnectingGeometry<&InstanceTallies::fTriangles>(
-            flushState, pipeline, triProc, batch, startIndices, startScissorSubBatch, drawBounds);
+            flushState, pipeline, *proc, batch, startIndices, startScissorSubBatch, drawBounds);
 
     // Draw conics.
-    GrCCCoverageProcessor conicProc(flushState->resourceProvider(), PrimitiveType::kConics);
+    proc->reset(PrimitiveType::kConics, flushState->resourceProvider());
     this->drawConnectingGeometry<&InstanceTallies::fConics>(
-            flushState, pipeline, conicProc, batch, startIndices, startScissorSubBatch, drawBounds);
+            flushState, pipeline, *proc, batch, startIndices, startScissorSubBatch, drawBounds);
 }
 
 void GrCCStroker::appendStrokeMeshesToBuffers(int numSegmentsLog2, const Batch& batch,
diff --git a/src/gpu/ccpr/GrCCStroker.h b/src/gpu/ccpr/GrCCStroker.h
index ac71011..741f265 100644
--- a/src/gpu/ccpr/GrCCStroker.h
+++ b/src/gpu/ccpr/GrCCStroker.h
@@ -13,7 +13,7 @@
 #include "SkNx.h"
 #include "ccpr/GrCCStrokeGeometry.h"
 
-class GrBuffer;
+class GrGpuBuffer;
 class GrCCCoverageProcessor;
 class GrOnFlushResourceProvider;
 class GrOpFlushState;
@@ -55,7 +55,8 @@
     bool prepareToDraw(GrOnFlushResourceProvider*);
 
     // Called after prepareToDraw(). Draws the given batch of path strokes.
-    void drawStrokes(GrOpFlushState*, BatchID, const SkIRect& drawBounds) const;
+    void drawStrokes(
+            GrOpFlushState*, GrCCCoverageProcessor*, BatchID, const SkIRect& drawBounds) const;
 
 private:
     static constexpr int kNumScissorModes = 2;
@@ -116,7 +117,7 @@
     GrSTAllocator<128, InstanceTallies> fTalliesAllocator;
     const InstanceTallies* fInstanceCounts[kNumScissorModes] = {&fZeroTallies, &fZeroTallies};
 
-    sk_sp<GrBuffer> fInstanceBuffer;
+    sk_sp<GrGpuBuffer> fInstanceBuffer;
     // The indices stored in batches are relative to these base instances.
     InstanceTallies fBaseInstances[kNumScissorModes];
 
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
index 077b0ec..c31af4f 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
@@ -20,14 +20,17 @@
 
 bool GrCoverageCountingPathRenderer::IsSupported(const GrCaps& caps) {
     const GrShaderCaps& shaderCaps = *caps.shaderCaps();
-    return caps.instanceAttribSupport() && shaderCaps.integerSupport() &&
-           shaderCaps.floatIs32Bits() && GrCaps::kNone_MapFlags != caps.mapBufferFlags() &&
-           caps.isConfigTexturable(kAlpha_half_GrPixelConfig) &&
-           caps.isConfigRenderable(kAlpha_half_GrPixelConfig) &&
-           caps.isConfigTexturable(kAlpha_8_GrPixelConfig) &&
-           caps.isConfigRenderable(kAlpha_8_GrPixelConfig) &&
-           caps.halfFloatVertexAttributeSupport() &&
-           !caps.blacklistCoverageCounting();
+    if (caps.driverBlacklistCCPR() || !caps.allowCoverageCounting() ||
+        !shaderCaps.integerSupport() || !caps.instanceAttribSupport() ||
+        !shaderCaps.floatIs32Bits() || GrCaps::kNone_MapFlags == caps.mapBufferFlags() ||
+        !caps.isConfigTexturable(kAlpha_half_GrPixelConfig) ||
+        !caps.isConfigRenderable(kAlpha_half_GrPixelConfig) ||
+        !caps.isConfigTexturable(kAlpha_8_GrPixelConfig) ||
+        !caps.isConfigRenderable(kAlpha_8_GrPixelConfig) ||
+        !caps.halfFloatVertexAttributeSupport()) {
+        return false;
+    }
+    return true;
 }
 
 sk_sp<GrCoverageCountingPathRenderer> GrCoverageCountingPathRenderer::CreateIfSupported(
@@ -56,7 +59,7 @@
 GrPathRenderer::CanDrawPath GrCoverageCountingPathRenderer::onCanDrawPath(
         const CanDrawPathArgs& args) const {
     const GrShape& shape = *args.fShape;
-    if (GrAAType::kCoverage != args.fAAType || shape.style().hasPathEffect() ||
+    if (!(AATypeFlags::kCoverage & args.fAATypeFlags) || shape.style().hasPathEffect() ||
         args.fViewMatrix->hasPerspective() || shape.inverseFilled()) {
         return CanDrawPath::kNo;
     }
diff --git a/src/gpu/ccpr/GrGSCoverageProcessor.cpp b/src/gpu/ccpr/GrGSCoverageProcessor.cpp
new file mode 100644
index 0000000..2a3acc3
--- /dev/null
+++ b/src/gpu/ccpr/GrGSCoverageProcessor.cpp
@@ -0,0 +1,430 @@
+/*
+ * 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 "GrGSCoverageProcessor.h"
+
+#include "GrMesh.h"
+#include "glsl/GrGLSLVertexGeoBuilder.h"
+
+using InputType = GrGLSLGeometryBuilder::InputType;
+using OutputType = GrGLSLGeometryBuilder::OutputType;
+
+/**
+ * This class and its subclasses implement the coverage processor with geometry shaders.
+ */
+class GrGSCoverageProcessor::Impl : public GrGLSLGeometryProcessor {
+protected:
+    Impl(std::unique_ptr<Shader> shader) : fShader(std::move(shader)) {}
+
+    virtual bool hasCoverage() const { return false; }
+
+    void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
+                 FPCoordTransformIter&& transformIter) final {
+        this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
+    }
+
+    void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) final {
+        const GrGSCoverageProcessor& proc = args.fGP.cast<GrGSCoverageProcessor>();
+
+        // The vertex shader simply forwards transposed x or y values to the geometry shader.
+        SkASSERT(1 == proc.numVertexAttributes());
+        gpArgs->fPositionVar = proc.fInputXOrYValues.asShaderVar();
+
+        // Geometry shader.
+        GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
+        this->emitGeometryShader(proc, varyingHandler, args.fGeomBuilder, args.fRTAdjustName);
+        varyingHandler->emitAttributes(proc);
+        varyingHandler->setNoPerspective();
+        SkASSERT(!args.fFPCoordTransformHandler->nextCoordTransform());
+
+        // Fragment shader.
+        fShader->emitFragmentCode(proc, args.fFragBuilder, args.fOutputColor, args.fOutputCoverage);
+    }
+
+    void emitGeometryShader(
+            const GrGSCoverageProcessor& proc, GrGLSLVaryingHandler* varyingHandler,
+            GrGLSLGeometryBuilder* g, const char* rtAdjust) const {
+        int numInputPoints = proc.numInputPoints();
+        SkASSERT(3 == numInputPoints || 4 == numInputPoints);
+
+        int inputWidth = (4 == numInputPoints || proc.hasInputWeight()) ? 4 : 3;
+        const char* posValues = (4 == inputWidth) ? "sk_Position" : "sk_Position.xyz";
+        g->codeAppendf("float%ix2 pts = transpose(float2x%i(sk_in[0].%s, sk_in[1].%s));",
+                       inputWidth, inputWidth, posValues, posValues);
+
+        GrShaderVar wind("wind", kHalf_GrSLType);
+        g->declareGlobal(wind);
+        Shader::CalcWind(proc, g, "pts", wind.c_str());
+        if (PrimitiveType::kWeightedTriangles == proc.primitiveType()) {
+            SkASSERT(3 == numInputPoints);
+            SkASSERT(kFloat4_GrVertexAttribType == proc.fInputXOrYValues.cpuType());
+            g->codeAppendf("%s *= half(sk_in[0].sk_Position.w);", wind.c_str());
+        }
+
+        SkString emitVertexFn;
+        SkSTArray<2, GrShaderVar> emitArgs;
+        const char* corner = emitArgs.emplace_back("corner", kFloat2_GrSLType).c_str();
+        const char* bloatdir = emitArgs.emplace_back("bloatdir", kFloat2_GrSLType).c_str();
+        const char* coverage = nullptr;
+        if (this->hasCoverage()) {
+            coverage = emitArgs.emplace_back("coverage", kHalf_GrSLType).c_str();
+        }
+        const char* cornerCoverage = nullptr;
+        if (Subpass::kCorners == proc.fSubpass) {
+            cornerCoverage = emitArgs.emplace_back("corner_coverage", kHalf2_GrSLType).c_str();
+        }
+        g->emitFunction(kVoid_GrSLType, "emitVertex", emitArgs.count(), emitArgs.begin(), [&]() {
+            SkString fnBody;
+            if (coverage) {
+                fnBody.appendf("%s *= %s;", coverage, wind.c_str());
+            }
+            if (cornerCoverage) {
+                fnBody.appendf("%s.x *= %s;", cornerCoverage, wind.c_str());
+            }
+            fnBody.appendf("float2 vertexpos = fma(%s, float2(bloat), %s);", bloatdir, corner);
+            fShader->emitVaryings(varyingHandler, GrGLSLVarying::Scope::kGeoToFrag, &fnBody,
+                                  "vertexpos", coverage ? coverage : wind.c_str(), cornerCoverage);
+            g->emitVertex(&fnBody, "vertexpos", rtAdjust);
+            return fnBody;
+        }().c_str(), &emitVertexFn);
+
+        float bloat = kAABloatRadius;
+#ifdef SK_DEBUG
+        if (proc.debugBloatEnabled()) {
+            bloat *= proc.debugBloat();
+        }
+#endif
+        g->defineConstant("bloat", bloat);
+
+        this->onEmitGeometryShader(proc, g, wind, emitVertexFn.c_str());
+    }
+
+    virtual void onEmitGeometryShader(const GrGSCoverageProcessor&, GrGLSLGeometryBuilder*,
+                                      const GrShaderVar& wind, const char* emitVertexFn) const = 0;
+
+    const std::unique_ptr<Shader> fShader;
+
+    typedef GrGLSLGeometryProcessor INHERITED;
+};
+
+/**
+ * Generates conservative rasters around a triangle and its edges, and calculates coverage ramps.
+ *
+ * Triangle rough outlines are drawn in two steps: (1) draw a conservative raster of the entire
+ * triangle, with a coverage of +1, and (2) draw conservative rasters around each edge, with a
+ * coverage ramp from -1 to 0. These edge coverage values convert jagged conservative raster edges
+ * into smooth, antialiased ones.
+ *
+ * The final corners get touched up in a later step by TriangleCornerImpl.
+ */
+class GrGSCoverageProcessor::TriangleHullImpl : public GrGSCoverageProcessor::Impl {
+public:
+    TriangleHullImpl(std::unique_ptr<Shader> shader) : Impl(std::move(shader)) {}
+
+    bool hasCoverage() const override { return true; }
+
+    void onEmitGeometryShader(const GrGSCoverageProcessor&, GrGLSLGeometryBuilder* g,
+                              const GrShaderVar& wind, const char* emitVertexFn) const override {
+        fShader->emitSetupCode(g, "pts", wind.c_str());
+
+        // Visualize the input triangle as upright and equilateral, with a flat base. Paying special
+        // attention to wind, we can identify the points as top, bottom-left, and bottom-right.
+        //
+        // NOTE: We generate the rasters in 5 independent invocations, so each invocation designates
+        // the corner it will begin with as the top.
+        g->codeAppendf("int i = (%s > 0 ? sk_InvocationID : 4 - sk_InvocationID) %% 3;",
+                       wind.c_str());
+        g->codeAppend ("float2 top = pts[i];");
+        g->codeAppendf("float2 right = pts[(i + (%s > 0 ? 1 : 2)) %% 3];", wind.c_str());
+        g->codeAppendf("float2 left = pts[(i + (%s > 0 ? 2 : 1)) %% 3];", wind.c_str());
+
+        // Determine which direction to outset the conservative raster from each of the three edges.
+        g->codeAppend ("float2 leftbloat = sign(top - left);");
+        g->codeAppend ("leftbloat = float2(0 != leftbloat.y ? leftbloat.y : leftbloat.x, "
+                                          "0 != leftbloat.x ? -leftbloat.x : -leftbloat.y);");
+
+        g->codeAppend ("float2 rightbloat = sign(right - top);");
+        g->codeAppend ("rightbloat = float2(0 != rightbloat.y ? rightbloat.y : rightbloat.x, "
+                                           "0 != rightbloat.x ? -rightbloat.x : -rightbloat.y);");
+
+        g->codeAppend ("float2 downbloat = sign(left - right);");
+        g->codeAppend ("downbloat = float2(0 != downbloat.y ? downbloat.y : downbloat.x, "
+                                           "0 != downbloat.x ? -downbloat.x : -downbloat.y);");
+
+        // The triangle's conservative raster has a coverage of +1 all around.
+        g->codeAppend ("half4 coverages = half4(+1);");
+
+        // Edges have coverage ramps.
+        g->codeAppend ("if (sk_InvocationID >= 2) {"); // Are we an edge?
+        Shader::CalcEdgeCoverageAtBloatVertex(g, "top", "right",
+                                              "float2(+rightbloat.y, -rightbloat.x)",
+                                              "coverages[0]");
+        g->codeAppend (    "coverages.yzw = half3(-1, 0, -1 - coverages[0]);");
+        // Reassign bloats to characterize a conservative raster around a single edge, rather than
+        // the entire triangle.
+        g->codeAppend (    "leftbloat = downbloat = -rightbloat;");
+        g->codeAppend ("}");
+
+        // Here we generate the conservative raster geometry. The triangle's conservative raster is
+        // the convex hull of 3 pixel-size boxes centered on the input points. This translates to a
+        // convex polygon with either one, two, or three vertices at each input point (depending on
+        // how sharp the corner is) that we split between two invocations. Edge conservative rasters
+        // are convex hulls of 2 pixel-size boxes, one at each endpoint. For more details on
+        // conservative raster, see:
+        // https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter42.html
+        g->codeAppendf("bool2 left_right_notequal = notEqual(leftbloat, rightbloat);");
+        g->codeAppend ("if (all(left_right_notequal)) {");
+                           // The top corner will have three conservative raster vertices. Emit the
+                           // middle one first to the triangle strip.
+        g->codeAppendf(    "%s(top, float2(-leftbloat.y, +leftbloat.x), coverages[0]);",
+                           emitVertexFn);
+        g->codeAppend ("}");
+        g->codeAppend ("if (any(left_right_notequal)) {");
+                           // Second conservative raster vertex for the top corner.
+        g->codeAppendf(    "%s(top, rightbloat, coverages[1]);", emitVertexFn);
+        g->codeAppend ("}");
+
+        // Main interior body.
+        g->codeAppendf("%s(top, leftbloat, coverages[2]);", emitVertexFn);
+        g->codeAppendf("%s(right, rightbloat, coverages[1]);", emitVertexFn);
+
+        // Here the invocations diverge slightly. We can't symmetrically divide three triangle
+        // points between two invocations, so each does the following:
+        //
+        // sk_InvocationID=0: Finishes the main interior body of the triangle hull.
+        // sk_InvocationID=1: Remaining two conservative raster vertices for the third hull corner.
+        // sk_InvocationID=2..4: Finish the opposite endpoint of their corresponding edge.
+        g->codeAppendf("bool2 right_down_notequal = notEqual(rightbloat, downbloat);");
+        g->codeAppend ("if (any(right_down_notequal) || 0 == sk_InvocationID) {");
+        g->codeAppendf(    "%s((0 == sk_InvocationID) ? left : right, "
+                              "(0 == sk_InvocationID) ? leftbloat : downbloat, "
+                              "coverages[2]);", emitVertexFn);
+        g->codeAppend ("}");
+        g->codeAppend ("if (all(right_down_notequal) && 0 != sk_InvocationID) {");
+        g->codeAppendf(    "%s(right, float2(-rightbloat.y, +rightbloat.x), coverages[3]);",
+                           emitVertexFn);
+        g->codeAppend ("}");
+
+        // 5 invocations: 2 triangle hull invocations and 3 edges.
+        g->configure(InputType::kLines, OutputType::kTriangleStrip, 6, 5);
+    }
+};
+
+/**
+ * Generates a conservative raster around a convex quadrilateral that encloses a cubic or quadratic.
+ */
+class GrGSCoverageProcessor::CurveHullImpl : public GrGSCoverageProcessor::Impl {
+public:
+    CurveHullImpl(std::unique_ptr<Shader> shader) : Impl(std::move(shader)) {}
+
+    void onEmitGeometryShader(const GrGSCoverageProcessor&, GrGLSLGeometryBuilder* g,
+                              const GrShaderVar& wind, const char* emitVertexFn) const override {
+        const char* hullPts = "pts";
+        fShader->emitSetupCode(g, "pts", wind.c_str(), &hullPts);
+
+        // Visualize the input (convex) quadrilateral as a square. Paying special attention to wind,
+        // we can identify the points by their corresponding corner.
+        //
+        // NOTE: We split the square down the diagonal from top-right to bottom-left, and generate
+        // the hull in two independent invocations. Each invocation designates the corner it will
+        // begin with as top-left.
+        g->codeAppend ("int i = sk_InvocationID * 2;");
+        g->codeAppendf("float2 topleft = %s[i];", hullPts);
+        g->codeAppendf("float2 topright = %s[%s > 0 ? i + 1 : 3 - i];", hullPts, wind.c_str());
+        g->codeAppendf("float2 bottomleft = %s[%s > 0 ? 3 - i : i + 1];", hullPts, wind.c_str());
+        g->codeAppendf("float2 bottomright = %s[2 - i];", hullPts);
+
+        // Determine how much to outset the conservative raster hull from the relevant edges.
+        g->codeAppend ("float2 leftbloat = float2(topleft.y > bottomleft.y ? +1 : -1, "
+                                                 "topleft.x > bottomleft.x ? -1 : +1);");
+        g->codeAppend ("float2 upbloat = float2(topright.y > topleft.y ? +1 : -1, "
+                                               "topright.x > topleft.x ? -1 : +1);");
+        g->codeAppend ("float2 rightbloat = float2(bottomright.y > topright.y ? +1 : -1, "
+                                                  "bottomright.x > topright.x ? -1 : +1);");
+
+        // Here we generate the conservative raster geometry. It is the convex hull of 4 pixel-size
+        // boxes centered on the input points, split evenly between two invocations. This translates
+        // to a polygon with either one, two, or three vertices at each input point, depending on
+        // how sharp the corner is. For more details on conservative raster, see:
+        // https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter42.html
+        g->codeAppendf("bool2 left_up_notequal = notEqual(leftbloat, upbloat);");
+        g->codeAppend ("if (all(left_up_notequal)) {");
+                           // The top-left corner will have three conservative raster vertices.
+                           // Emit the middle one first to the triangle strip.
+        g->codeAppendf(    "%s(topleft, float2(-leftbloat.y, leftbloat.x));", emitVertexFn);
+        g->codeAppend ("}");
+        g->codeAppend ("if (any(left_up_notequal)) {");
+                           // Second conservative raster vertex for the top-left corner.
+        g->codeAppendf(    "%s(topleft, leftbloat);", emitVertexFn);
+        g->codeAppend ("}");
+
+        // Main interior body of this invocation's half of the hull.
+        g->codeAppendf("%s(topleft, upbloat);", emitVertexFn);
+        g->codeAppendf("%s(bottomleft, leftbloat);", emitVertexFn);
+        g->codeAppendf("%s(topright, upbloat);", emitVertexFn);
+
+        // Remaining two conservative raster vertices for the top-right corner.
+        g->codeAppendf("bool2 up_right_notequal = notEqual(upbloat, rightbloat);");
+        g->codeAppend ("if (any(up_right_notequal)) {");
+        g->codeAppendf(    "%s(topright, rightbloat);", emitVertexFn);
+        g->codeAppend ("}");
+        g->codeAppend ("if (all(up_right_notequal)) {");
+        g->codeAppendf(    "%s(topright, float2(-upbloat.y, upbloat.x));", emitVertexFn);
+        g->codeAppend ("}");
+
+        g->configure(InputType::kLines, OutputType::kTriangleStrip, 7, 2);
+    }
+};
+
+/**
+ * Generates conservative rasters around corners (aka pixel-size boxes) and calculates
+ * coverage and attenuation ramps to fix up the coverage values written by the hulls.
+ */
+class GrGSCoverageProcessor::CornerImpl : public GrGSCoverageProcessor::Impl {
+public:
+    CornerImpl(std::unique_ptr<Shader> shader) : Impl(std::move(shader)) {}
+
+    bool hasCoverage() const override { return true; }
+
+    void onEmitGeometryShader(const GrGSCoverageProcessor& proc, GrGLSLGeometryBuilder* g,
+                              const GrShaderVar& wind, const char* emitVertexFn) const override {
+        fShader->emitSetupCode(g, "pts", wind.c_str());
+
+        g->codeAppendf("int corneridx = sk_InvocationID;");
+        if (!proc.isTriangles()) {
+            g->codeAppendf("corneridx *= %i;", proc.numInputPoints() - 1);
+        }
+
+        g->codeAppendf("float2 corner = pts[corneridx];");
+        g->codeAppendf("float2 left = pts[(corneridx + (%s > 0 ? %i : 1)) %% %i];",
+                       wind.c_str(), proc.numInputPoints() - 1, proc.numInputPoints());
+        g->codeAppendf("float2 right = pts[(corneridx + (%s > 0 ? 1 : %i)) %% %i];",
+                       wind.c_str(), proc.numInputPoints() - 1, proc.numInputPoints());
+
+        g->codeAppend ("float2 leftdir = corner - left;");
+        g->codeAppend ("leftdir = (float2(0) != leftdir) ? normalize(leftdir) : float2(1, 0);");
+
+        g->codeAppend ("float2 rightdir = right - corner;");
+        g->codeAppend ("rightdir = (float2(0) != rightdir) ? normalize(rightdir) : float2(1, 0);");
+
+        // Find "outbloat" and "crossbloat" at our corner. The outbloat points diagonally out of the
+        // triangle, in the direction that should ramp to zero coverage with attenuation. The
+        // crossbloat runs perpindicular to outbloat.
+        g->codeAppend ("float2 outbloat = float2(leftdir.x > rightdir.x ? +1 : -1, "
+                                                "leftdir.y > rightdir.y ? +1 : -1);");
+        g->codeAppend ("float2 crossbloat = float2(-outbloat.y, +outbloat.x);");
+
+        g->codeAppend ("half attenuation; {");
+        Shader::CalcCornerAttenuation(g, "leftdir", "rightdir", "attenuation");
+        g->codeAppend ("}");
+
+        if (proc.isTriangles()) {
+            g->codeAppend ("half2 left_coverages; {");
+            Shader::CalcEdgeCoveragesAtBloatVertices(g, "left", "corner", "-outbloat",
+                                                     "-crossbloat", "left_coverages");
+            g->codeAppend ("}");
+
+            g->codeAppend ("half2 right_coverages; {");
+            Shader::CalcEdgeCoveragesAtBloatVertices(g, "corner", "right", "-outbloat",
+                                                     "crossbloat", "right_coverages");
+            g->codeAppend ("}");
+
+            // Emit a corner box. The first coverage argument erases the values that were written
+            // previously by the hull and edge geometry. The second pair are multiplied together by
+            // the fragment shader. They ramp to 0 with attenuation in the direction of outbloat,
+            // and linearly from left-edge coverage to right-edge coverage in the direction of
+            // crossbloat.
+            //
+            // NOTE: Since this is not a linear mapping, it is important that the box's diagonal
+            // shared edge points in the direction of outbloat.
+            g->codeAppendf("%s(corner, -crossbloat, right_coverages[1] - left_coverages[1],"
+                              "half2(1 + left_coverages[1], 1));",
+                           emitVertexFn);
+
+            g->codeAppendf("%s(corner, outbloat, 1 + left_coverages[0] + right_coverages[0], "
+                              "half2(0, attenuation));",
+                           emitVertexFn);
+
+            g->codeAppendf("%s(corner, -outbloat, -1 - left_coverages[0] - right_coverages[0], "
+                              "half2(1 + left_coverages[0] + right_coverages[0], 1));",
+                           emitVertexFn);
+
+            g->codeAppendf("%s(corner, crossbloat, left_coverages[1] - right_coverages[1],"
+                              "half2(1 + right_coverages[1], 1));",
+                           emitVertexFn);
+        } else {
+            // Curves are simpler. The first coverage value of -1 means "wind = -wind", and causes
+            // the Shader to erase what it had written previously for the hull. Then, at each vertex
+            // of the corner box, the Shader will calculate the curve's local coverage value,
+            // interpolate it alongside our attenuation parameter, and multiply the two together for
+            // a final coverage value.
+            g->codeAppendf("%s(corner, -crossbloat, -1, half2(1));", emitVertexFn);
+            g->codeAppendf("%s(corner, outbloat, -1, half2(0, attenuation));",
+                           emitVertexFn);
+            g->codeAppendf("%s(corner, -outbloat, -1, half2(1));", emitVertexFn);
+            g->codeAppendf("%s(corner, crossbloat, -1, half2(1));", emitVertexFn);
+        }
+
+        g->configure(InputType::kLines, OutputType::kTriangleStrip, 4, proc.isTriangles() ? 3 : 2);
+    }
+};
+
+void GrGSCoverageProcessor::reset(PrimitiveType primitiveType, GrResourceProvider*) {
+    fPrimitiveType = primitiveType;  // This will affect the return values for numInputPoints, etc.
+
+    if (4 == this->numInputPoints() || this->hasInputWeight()) {
+        fInputXOrYValues =
+                {"x_or_y_values", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
+        GR_STATIC_ASSERT(sizeof(QuadPointInstance) ==
+                         2 * GrVertexAttribTypeSize(kFloat4_GrVertexAttribType));
+        GR_STATIC_ASSERT(offsetof(QuadPointInstance, fY) ==
+                         GrVertexAttribTypeSize(kFloat4_GrVertexAttribType));
+    } else {
+        fInputXOrYValues =
+                {"x_or_y_values", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
+        GR_STATIC_ASSERT(sizeof(TriPointInstance) ==
+                         2 * GrVertexAttribTypeSize(kFloat3_GrVertexAttribType));
+        GR_STATIC_ASSERT(offsetof(TriPointInstance, fY) ==
+                         GrVertexAttribTypeSize(kFloat3_GrVertexAttribType));
+    }
+
+    this->setVertexAttributes(&fInputXOrYValues, 1);
+}
+
+void GrGSCoverageProcessor::appendMesh(sk_sp<const GrGpuBuffer> instanceBuffer, int instanceCount,
+                                       int baseInstance, SkTArray<GrMesh>* out) const {
+    // We don't actually make instanced draw calls. Instead, we feed transposed x,y point values to
+    // the GPU in a regular vertex array and draw kLines (see initGS). Then, each vertex invocation
+    // receives either the shape's x or y values as inputs, which it forwards to the geometry
+    // shader.
+    GrMesh& mesh = out->emplace_back(GrPrimitiveType::kLines);
+    mesh.setNonIndexedNonInstanced(instanceCount * 2);
+    mesh.setVertexData(std::move(instanceBuffer), baseInstance * 2);
+}
+
+void GrGSCoverageProcessor::draw(
+        GrOpFlushState* flushState, const GrPipeline& pipeline, const SkIRect scissorRects[],
+        const GrMesh meshes[], int meshCount, const SkRect& drawBounds) const {
+    // The geometry shader impl draws primitives in two subpasses: The first pass fills the interior
+    // and does edge AA. The second pass does touch up on corner pixels.
+    for (int i = 0; i < 2; ++i) {
+        fSubpass = (Subpass) i;
+        this->GrCCCoverageProcessor::draw(
+                flushState, pipeline, scissorRects, meshes, meshCount, drawBounds);
+    }
+}
+
+GrGLSLPrimitiveProcessor* GrGSCoverageProcessor::onCreateGLSLInstance(
+        std::unique_ptr<Shader> shader) const {
+    if (Subpass::kHulls == fSubpass) {
+        return this->isTriangles()
+                   ? (Impl*) new TriangleHullImpl(std::move(shader))
+                   : (Impl*) new CurveHullImpl(std::move(shader));
+    }
+    SkASSERT(Subpass::kCorners == fSubpass);
+    return new CornerImpl(std::move(shader));
+}
diff --git a/src/gpu/ccpr/GrGSCoverageProcessor.h b/src/gpu/ccpr/GrGSCoverageProcessor.h
new file mode 100644
index 0000000..f517765
--- /dev/null
+++ b/src/gpu/ccpr/GrGSCoverageProcessor.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrGSCoverageProcessor_DEFINED
+#define GrGSCoverageProcessor_DEFINED
+
+#include "ccpr/GrCCCoverageProcessor.h"
+
+/**
+ * This class implements GrCCCoverageProcessor with analytic coverage using geometry shaders.
+ */
+class GrGSCoverageProcessor : public GrCCCoverageProcessor {
+public:
+    GrGSCoverageProcessor() : GrCCCoverageProcessor(kGrGSCoverageProcessor_ClassID) {
+        this->setWillUseGeoShader();
+    }
+
+private:
+    void reset(PrimitiveType, GrResourceProvider*) override;
+
+    void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
+        SkDEBUGCODE(this->getDebugBloatKey(b));
+        b->add32(((int)fPrimitiveType << 16) | (int)fSubpass);
+    }
+
+    void appendMesh(sk_sp<const GrGpuBuffer> instanceBuffer, int instanceCount, int baseInstance,
+                    SkTArray<GrMesh>* out) const override;
+
+    void draw(GrOpFlushState*, const GrPipeline&, const SkIRect scissorRects[], const GrMesh[],
+              int meshCount, const SkRect& drawBounds) const override;
+
+    GrGLSLPrimitiveProcessor* onCreateGLSLInstance(std::unique_ptr<Shader>) const override;
+
+    // The geometry shader impl draws primitives in two subpasses. The first pass fills the interior
+    // and does edge AA. The second pass does touch up on corner pixels.
+    enum class Subpass : bool {
+        kHulls,
+        kCorners
+    };
+
+    Attribute fInputXOrYValues;
+    mutable Subpass fSubpass = Subpass::kHulls;
+
+    class Impl;
+    class TriangleHullImpl;
+    class CurveHullImpl;
+    class CornerImpl;
+};
+
+#endif
diff --git a/src/gpu/ccpr/GrVSCoverageProcessor.cpp b/src/gpu/ccpr/GrVSCoverageProcessor.cpp
new file mode 100644
index 0000000..dc00b96
--- /dev/null
+++ b/src/gpu/ccpr/GrVSCoverageProcessor.cpp
@@ -0,0 +1,547 @@
+/*
+ * 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 "GrVSCoverageProcessor.h"
+
+#include "GrMesh.h"
+#include "glsl/GrGLSLVertexGeoBuilder.h"
+
+// This class implements the coverage processor with vertex shaders.
+class GrVSCoverageProcessor::Impl : public GrGLSLGeometryProcessor {
+public:
+    Impl(std::unique_ptr<Shader> shader, int numSides)
+            : fShader(std::move(shader)), fNumSides(numSides) {}
+
+private:
+    void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
+                 FPCoordTransformIter&& transformIter) final {
+        this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
+    }
+
+    void onEmitCode(EmitArgs&, GrGPArgs*) override;
+
+    const std::unique_ptr<Shader> fShader;
+    const int fNumSides;
+};
+
+static constexpr int kInstanceAttribIdx_X = 0;  // Transposed X values of all input points.
+static constexpr int kInstanceAttribIdx_Y = 1;  // Transposed Y values of all input points.
+
+// Vertex data tells the shader how to offset vertices for conservative raster, as well as how to
+// calculate coverage values for corners and edges.
+static constexpr int kVertexData_LeftNeighborIdShift = 10;
+static constexpr int kVertexData_RightNeighborIdShift = 8;
+static constexpr int kVertexData_BloatIdxShift = 6;
+static constexpr int kVertexData_InvertNegativeCoverageBit = 1 << 5;
+static constexpr int kVertexData_IsCornerBit = 1 << 4;
+static constexpr int kVertexData_IsEdgeBit = 1 << 3;
+static constexpr int kVertexData_IsHullBit = 1 << 2;
+
+static constexpr int32_t pack_vertex_data(int32_t leftNeighborID, int32_t rightNeighborID,
+                                          int32_t bloatIdx, int32_t cornerID,
+                                          int32_t extraData = 0) {
+    return (leftNeighborID << kVertexData_LeftNeighborIdShift) |
+           (rightNeighborID << kVertexData_RightNeighborIdShift) |
+           (bloatIdx << kVertexData_BloatIdxShift) |
+           cornerID | extraData;
+}
+
+static constexpr int32_t hull_vertex_data(int32_t cornerID, int32_t bloatIdx, int n) {
+    return pack_vertex_data((cornerID + n - 1) % n, (cornerID + 1) % n, bloatIdx, cornerID,
+                            kVertexData_IsHullBit);
+}
+
+static constexpr int32_t edge_vertex_data(int32_t edgeID, int32_t endptIdx, int32_t bloatIdx,
+                                          int n) {
+    return pack_vertex_data(0 == endptIdx ? (edgeID + 1) % n : edgeID,
+                            0 == endptIdx ? (edgeID + 1) % n : edgeID,
+                            bloatIdx, 0 == endptIdx ? edgeID : (edgeID + 1) % n,
+                            kVertexData_IsEdgeBit |
+                            (!endptIdx ? kVertexData_InvertNegativeCoverageBit : 0));
+}
+
+static constexpr int32_t corner_vertex_data(int32_t leftID, int32_t cornerID, int32_t rightID,
+                                            int32_t bloatIdx) {
+    return pack_vertex_data(leftID, rightID, bloatIdx, cornerID, kVertexData_IsCornerBit);
+}
+
+static constexpr int32_t kTriangleVertices[] = {
+    hull_vertex_data(0, 0, 3),
+    hull_vertex_data(0, 1, 3),
+    hull_vertex_data(0, 2, 3),
+    hull_vertex_data(1, 0, 3),
+    hull_vertex_data(1, 1, 3),
+    hull_vertex_data(1, 2, 3),
+    hull_vertex_data(2, 0, 3),
+    hull_vertex_data(2, 1, 3),
+    hull_vertex_data(2, 2, 3),
+
+    edge_vertex_data(0, 0, 0, 3),
+    edge_vertex_data(0, 0, 1, 3),
+    edge_vertex_data(0, 0, 2, 3),
+    edge_vertex_data(0, 1, 0, 3),
+    edge_vertex_data(0, 1, 1, 3),
+    edge_vertex_data(0, 1, 2, 3),
+
+    edge_vertex_data(1, 0, 0, 3),
+    edge_vertex_data(1, 0, 1, 3),
+    edge_vertex_data(1, 0, 2, 3),
+    edge_vertex_data(1, 1, 0, 3),
+    edge_vertex_data(1, 1, 1, 3),
+    edge_vertex_data(1, 1, 2, 3),
+
+    edge_vertex_data(2, 0, 0, 3),
+    edge_vertex_data(2, 0, 1, 3),
+    edge_vertex_data(2, 0, 2, 3),
+    edge_vertex_data(2, 1, 0, 3),
+    edge_vertex_data(2, 1, 1, 3),
+    edge_vertex_data(2, 1, 2, 3),
+
+    corner_vertex_data(2, 0, 1, 0),
+    corner_vertex_data(2, 0, 1, 1),
+    corner_vertex_data(2, 0, 1, 2),
+    corner_vertex_data(2, 0, 1, 3),
+
+    corner_vertex_data(0, 1, 2, 0),
+    corner_vertex_data(0, 1, 2, 1),
+    corner_vertex_data(0, 1, 2, 2),
+    corner_vertex_data(0, 1, 2, 3),
+
+    corner_vertex_data(1, 2, 0, 0),
+    corner_vertex_data(1, 2, 0, 1),
+    corner_vertex_data(1, 2, 0, 2),
+    corner_vertex_data(1, 2, 0, 3),
+};
+
+GR_DECLARE_STATIC_UNIQUE_KEY(gTriangleVertexBufferKey);
+
+static constexpr uint16_t kRestartStrip = 0xffff;
+
+static constexpr uint16_t kTriangleIndicesAsStrips[] =  {
+    1, 2, 0, 3, 8, kRestartStrip, // First corner and main body of the hull.
+    4, 5, 3, 6, 8, 7, kRestartStrip, // Opposite side and corners of the hull.
+    10, 9, 11, 14, 12, 13, kRestartStrip, // First edge.
+    16, 15, 17, 20, 18, 19, kRestartStrip, // Second edge.
+    22, 21, 23, 26, 24, 25, kRestartStrip, // Third edge.
+    28, 27, 29, 30, kRestartStrip, // First corner.
+    32, 31, 33, 34, kRestartStrip, // Second corner.
+    36, 35, 37, 38 // Third corner.
+};
+
+static constexpr uint16_t kTriangleIndicesAsTris[] =  {
+    // First corner and main body of the hull.
+    1, 2, 0,
+    2, 3, 0,
+    0, 3, 8, // Main body.
+
+    // Opposite side and corners of the hull.
+    4, 5, 3,
+    5, 6, 3,
+    3, 6, 8,
+    6, 7, 8,
+
+    // First edge.
+    10,  9, 11,
+     9, 14, 11,
+    11, 14, 12,
+    14, 13, 12,
+
+    // Second edge.
+    16, 15, 17,
+    15, 20, 17,
+    17, 20, 18,
+    20, 19, 18,
+
+    // Third edge.
+    22, 21, 23,
+    21, 26, 23,
+    23, 26, 24,
+    26, 25, 24,
+
+    // First corner.
+    28, 27, 29,
+    27, 30, 29,
+
+    // Second corner.
+    32, 31, 33,
+    31, 34, 33,
+
+    // Third corner.
+    36, 35, 37,
+    35, 38, 37,
+};
+
+GR_DECLARE_STATIC_UNIQUE_KEY(gTriangleIndexBufferKey);
+
+// Curves, including quadratics, are drawn with a four-sided hull.
+static constexpr int32_t kCurveVertices[] = {
+    hull_vertex_data(0, 0, 4),
+    hull_vertex_data(0, 1, 4),
+    hull_vertex_data(0, 2, 4),
+    hull_vertex_data(1, 0, 4),
+    hull_vertex_data(1, 1, 4),
+    hull_vertex_data(1, 2, 4),
+    hull_vertex_data(2, 0, 4),
+    hull_vertex_data(2, 1, 4),
+    hull_vertex_data(2, 2, 4),
+    hull_vertex_data(3, 0, 4),
+    hull_vertex_data(3, 1, 4),
+    hull_vertex_data(3, 2, 4),
+
+    corner_vertex_data(3, 0, 1, 0),
+    corner_vertex_data(3, 0, 1, 1),
+    corner_vertex_data(3, 0, 1, 2),
+    corner_vertex_data(3, 0, 1, 3),
+
+    corner_vertex_data(2, 3, 0, 0),
+    corner_vertex_data(2, 3, 0, 1),
+    corner_vertex_data(2, 3, 0, 2),
+    corner_vertex_data(2, 3, 0, 3),
+};
+
+GR_DECLARE_STATIC_UNIQUE_KEY(gCurveVertexBufferKey);
+
+static constexpr uint16_t kCurveIndicesAsStrips[] =  {
+    1, 0, 2, 11, 3, 5, 4, kRestartStrip, // First half of the hull (split diagonally).
+    7, 6, 8, 5, 9, 11, 10, kRestartStrip, // Second half of the hull.
+    13, 12, 14, 15, kRestartStrip, // First corner.
+    17, 16, 18, 19 // Final corner.
+};
+
+static constexpr uint16_t kCurveIndicesAsTris[] =  {
+    // First half of the hull (split diagonally).
+     1,  0,  2,
+     0, 11,  2,
+     2, 11,  3,
+    11,  5,  3,
+     3,  5,  4,
+
+    // Second half of the hull.
+    7,  6,  8,
+    6,  5,  8,
+    8,  5,  9,
+    5, 11,  9,
+    9, 11, 10,
+
+    // First corner.
+    13, 12, 14,
+    12, 15, 14,
+
+    // Final corner.
+    17, 16, 18,
+    16, 19, 18,
+};
+
+GR_DECLARE_STATIC_UNIQUE_KEY(gCurveIndexBufferKey);
+
+// Generates a conservative raster hull around a triangle or curve. For triangles we generate
+// additional conservative rasters with coverage ramps around the edges and corners.
+//
+// Triangles are drawn in three steps: (1) Draw a conservative raster of the entire triangle, with a
+// coverage of +1. (2) Draw conservative rasters around each edge, with a coverage ramp from -1 to
+// 0. These edge coverage values convert jagged conservative raster edges into smooth, antialiased
+// ones. (3) Draw conservative rasters (aka pixel-size boxes) around each corner, replacing the
+// previous coverage values with ones that ramp to zero in the bloat vertices that fall outside the
+// triangle.
+//
+// Curve shaders handle the opposite edge and corners on their own. For curves we just generate a
+// conservative raster here and the shader does the rest.
+void GrVSCoverageProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
+    const GrVSCoverageProcessor& proc = args.fGP.cast<GrVSCoverageProcessor>();
+    GrGLSLVertexBuilder* v = args.fVertBuilder;
+    int numInputPoints = proc.numInputPoints();
+
+    int inputWidth = (4 == numInputPoints || proc.hasInputWeight()) ? 4 : 3;
+    const char* swizzle = (4 == inputWidth) ? "xyzw" : "xyz";
+    v->codeAppendf("float%ix2 pts = transpose(float2x%i(%s.%s, %s.%s));", inputWidth, inputWidth,
+                   proc.fInputXAndYValues[kInstanceAttribIdx_X].name(), swizzle,
+                   proc.fInputXAndYValues[kInstanceAttribIdx_Y].name(), swizzle);
+
+    v->codeAppend ("half wind;");
+    Shader::CalcWind(proc, v, "pts", "wind");
+    if (PrimitiveType::kWeightedTriangles == proc.fPrimitiveType) {
+        SkASSERT(3 == numInputPoints);
+        SkASSERT(kFloat4_GrVertexAttribType ==
+                 proc.fInputXAndYValues[kInstanceAttribIdx_X].cpuType());
+        v->codeAppendf("wind *= half(%s.w);",
+                       proc.fInputXAndYValues[kInstanceAttribIdx_X].name());
+    }
+
+    float bloat = kAABloatRadius;
+#ifdef SK_DEBUG
+    if (proc.debugBloatEnabled()) {
+        bloat *= proc.debugBloat();
+    }
+#endif
+    v->defineConstant("bloat", bloat);
+
+    const char* hullPts = "pts";
+    fShader->emitSetupCode(v, "pts", "wind", (4 == fNumSides) ? &hullPts : nullptr);
+
+    // Reverse all indices if the wind is counter-clockwise: [0, 1, 2] -> [2, 1, 0].
+    v->codeAppendf("int clockwise_indices = wind > 0 ? %s : 0x%x - %s;",
+                   proc.fPerVertexData.name(),
+                   ((fNumSides - 1) << kVertexData_LeftNeighborIdShift) |
+                   ((fNumSides - 1) << kVertexData_RightNeighborIdShift) |
+                   (((1 << kVertexData_RightNeighborIdShift) - 1) ^ 3) |
+                   (fNumSides - 1),
+                   proc.fPerVertexData.name());
+
+    // Here we generate conservative raster geometry for the input polygon. It is the convex
+    // hull of N pixel-size boxes, one centered on each the input points. Each corner has three
+    // vertices, where one or two may cause degenerate triangles. The vertex data tells us how
+    // to offset each vertex. Triangle edges and corners are also handled here using the same
+    // concept. For more details on conservative raster, see:
+    // https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter42.html
+    v->codeAppendf("float2 corner = %s[clockwise_indices & 3];", hullPts);
+    v->codeAppendf("float2 left = %s[clockwise_indices >> %i];",
+                   hullPts, kVertexData_LeftNeighborIdShift);
+    v->codeAppendf("float2 right = %s[(clockwise_indices >> %i) & 3];",
+                   hullPts, kVertexData_RightNeighborIdShift);
+
+    v->codeAppend ("float2 leftbloat = sign(corner - left);");
+    v->codeAppend ("leftbloat = float2(0 != leftbloat.y ? leftbloat.y : leftbloat.x, "
+                                      "0 != leftbloat.x ? -leftbloat.x : -leftbloat.y);");
+
+    v->codeAppend ("float2 rightbloat = sign(right - corner);");
+    v->codeAppend ("rightbloat = float2(0 != rightbloat.y ? rightbloat.y : rightbloat.x, "
+                                       "0 != rightbloat.x ? -rightbloat.x : -rightbloat.y);");
+
+    v->codeAppend ("bool2 left_right_notequal = notEqual(leftbloat, rightbloat);");
+
+    v->codeAppend ("float2 bloatdir = leftbloat;");
+
+    v->codeAppend ("float2 leftdir = corner - left;");
+    v->codeAppend ("leftdir = (float2(0) != leftdir) ? normalize(leftdir) : float2(1, 0);");
+
+    v->codeAppend ("float2 rightdir = right - corner;");
+    v->codeAppend ("rightdir = (float2(0) != rightdir) ? normalize(rightdir) : float2(1, 0);");
+
+    v->codeAppendf("if (0 != (%s & %i)) {",  // Are we a corner?
+                   proc.fPerVertexData.name(), kVertexData_IsCornerBit);
+
+                       // In corner boxes, all 4 coverage values will not map linearly.
+                       // Therefore it is important to align the box so its diagonal shared
+                       // edge points out of the triangle, in the direction that ramps to 0.
+    v->codeAppend (    "bloatdir = float2(leftdir.x > rightdir.x ? +1 : -1, "
+                                         "leftdir.y > rightdir.y ? +1 : -1);");
+
+                       // For corner boxes, we hack left_right_notequal to always true. This
+                       // in turn causes the upcoming code to always rotate, generating all
+                       // 4 vertices of the corner box.
+    v->codeAppendf(    "left_right_notequal = bool2(true);");
+    v->codeAppend ("}");
+
+    // At each corner of the polygon, our hull will have either 1, 2, or 3 vertices (or 4 if
+    // it's a corner box). We begin with this corner's first raster vertex (leftbloat), then
+    // continue rotating 90 degrees clockwise until we reach the desired raster vertex for this
+    // invocation. Corners with less than 3 corresponding raster vertices will result in
+    // redundant vertices and degenerate triangles.
+    v->codeAppendf("int bloatidx = (%s >> %i) & 3;", proc.fPerVertexData.name(),
+                   kVertexData_BloatIdxShift);
+    v->codeAppend ("switch (bloatidx) {");
+    v->codeAppend (    "case 3:");
+                            // Only corners will have bloatidx=3, and corners always rotate.
+    v->codeAppend (        "bloatdir = float2(-bloatdir.y, +bloatdir.x);"); // 90 deg CW.
+                           // fallthru.
+    v->codeAppend (    "case 2:");
+    v->codeAppendf(        "if (all(left_right_notequal)) {");
+    v->codeAppend (            "bloatdir = float2(-bloatdir.y, +bloatdir.x);"); // 90 deg CW.
+    v->codeAppend (        "}");
+                           // fallthru.
+    v->codeAppend (    "case 1:");
+    v->codeAppendf(        "if (any(left_right_notequal)) {");
+    v->codeAppend (            "bloatdir = float2(-bloatdir.y, +bloatdir.x);"); // 90 deg CW.
+    v->codeAppend (        "}");
+                           // fallthru.
+    v->codeAppend ("}");
+
+    v->codeAppend ("float2 vertex = fma(bloatdir, float2(bloat), corner);");
+    gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertex");
+
+    // Hulls have a coverage of +1 all around.
+    v->codeAppend ("half coverage = +1;");
+
+    if (3 == fNumSides) {
+        v->codeAppend ("half left_coverage; {");
+        Shader::CalcEdgeCoverageAtBloatVertex(v, "left", "corner", "bloatdir", "left_coverage");
+        v->codeAppend ("}");
+
+        v->codeAppend ("half right_coverage; {");
+        Shader::CalcEdgeCoverageAtBloatVertex(v, "corner", "right", "bloatdir", "right_coverage");
+        v->codeAppend ("}");
+
+        v->codeAppendf("if (0 != (%s & %i)) {",  // Are we an edge?
+                       proc.fPerVertexData.name(), kVertexData_IsEdgeBit);
+        v->codeAppend (    "coverage = left_coverage;");
+        v->codeAppend ("}");
+
+        v->codeAppendf("if (0 != (%s & %i)) {",  // Invert coverage?
+                       proc.fPerVertexData.name(),
+                       kVertexData_InvertNegativeCoverageBit);
+        v->codeAppend (    "coverage = -1 - coverage;");
+        v->codeAppend ("}");
+    }
+
+    // Non-corner geometry should have zero effect from corner coverage.
+    v->codeAppend ("half2 corner_coverage = half2(0);");
+
+    v->codeAppendf("if (0 != (%s & %i)) {",  // Are we a corner?
+                   proc.fPerVertexData.name(), kVertexData_IsCornerBit);
+                       // We use coverage=-1 to erase what the hull geometry wrote.
+                       //
+                       // In the context of curves, this effectively means "wind = -wind" and
+                       // causes the Shader to erase what it had written previously for the hull.
+                       //
+                       // For triangles it just erases the "+1" value written by the hull geometry.
+    v->codeAppend (    "coverage = -1;");
+    if (3 == fNumSides) {
+                       // Triangle corners also have to erase what the edge geometry wrote.
+        v->codeAppend ("coverage -= left_coverage + right_coverage;");
+    }
+
+                       // Corner boxes require attenuated coverage.
+    v->codeAppend (    "half attenuation; {");
+    Shader::CalcCornerAttenuation(v, "leftdir", "rightdir", "attenuation");
+    v->codeAppend (    "}");
+
+                       // Attenuate corner coverage towards the outermost vertex (where bloatidx=0).
+                       // This is all that curves need: At each vertex of the corner box, the curve
+                       // Shader will calculate the curve's local coverage value, interpolate it
+                       // alongside our attenuation parameter, and multiply the two together for a
+                       // final coverage value.
+    v->codeAppend (    "corner_coverage = (0 == bloatidx) ? half2(0, attenuation) : half2(1);");
+
+    if (3 == fNumSides) {
+                       // For triangles we also provide the actual coverage values at each vertex of
+                       // the corner box.
+        v->codeAppend ("if (1 == bloatidx || 2 == bloatidx) {");
+        v->codeAppend (    "corner_coverage.x += right_coverage;");
+        v->codeAppend ("}");
+        v->codeAppend ("if (bloatidx >= 2) {");
+        v->codeAppend (    "corner_coverage.x += left_coverage;");
+        v->codeAppend ("}");
+    }
+    v->codeAppend ("}");
+
+    GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
+    v->codeAppend ("coverage *= wind;");
+    v->codeAppend ("corner_coverage.x *= wind;");
+    fShader->emitVaryings(varyingHandler, GrGLSLVarying::Scope::kVertToFrag, &AccessCodeString(v),
+                          gpArgs->fPositionVar.c_str(), "coverage", "corner_coverage");
+
+    varyingHandler->emitAttributes(proc);
+    SkASSERT(!args.fFPCoordTransformHandler->nextCoordTransform());
+
+    // Fragment shader.
+    fShader->emitFragmentCode(proc, args.fFragBuilder, args.fOutputColor, args.fOutputCoverage);
+}
+
+void GrVSCoverageProcessor::reset(PrimitiveType primitiveType, GrResourceProvider* rp) {
+    const GrCaps& caps = *rp->caps();
+
+    fPrimitiveType = primitiveType;
+    switch (fPrimitiveType) {
+        case PrimitiveType::kTriangles:
+        case PrimitiveType::kWeightedTriangles: {
+            GR_DEFINE_STATIC_UNIQUE_KEY(gTriangleVertexBufferKey);
+            fVertexBuffer = rp->findOrMakeStaticBuffer(
+                    GrGpuBufferType::kVertex, sizeof(kTriangleVertices), kTriangleVertices,
+                    gTriangleVertexBufferKey);
+            GR_DEFINE_STATIC_UNIQUE_KEY(gTriangleIndexBufferKey);
+            if (caps.usePrimitiveRestart()) {
+                fIndexBuffer = rp->findOrMakeStaticBuffer(
+                        GrGpuBufferType::kIndex, sizeof(kTriangleIndicesAsStrips),
+                        kTriangleIndicesAsStrips, gTriangleIndexBufferKey);
+                fNumIndicesPerInstance = SK_ARRAY_COUNT(kTriangleIndicesAsStrips);
+            } else {
+                fIndexBuffer = rp->findOrMakeStaticBuffer(
+                        GrGpuBufferType::kIndex, sizeof(kTriangleIndicesAsTris),
+                        kTriangleIndicesAsTris, gTriangleIndexBufferKey);
+                fNumIndicesPerInstance = SK_ARRAY_COUNT(kTriangleIndicesAsTris);
+            }
+            break;
+        }
+
+        case PrimitiveType::kQuadratics:
+        case PrimitiveType::kCubics:
+        case PrimitiveType::kConics: {
+            GR_DEFINE_STATIC_UNIQUE_KEY(gCurveVertexBufferKey);
+            fVertexBuffer = rp->findOrMakeStaticBuffer(
+                    GrGpuBufferType::kVertex, sizeof(kCurveVertices), kCurveVertices,
+                    gCurveVertexBufferKey);
+            GR_DEFINE_STATIC_UNIQUE_KEY(gCurveIndexBufferKey);
+            if (caps.usePrimitiveRestart()) {
+                fIndexBuffer = rp->findOrMakeStaticBuffer(
+                        GrGpuBufferType::kIndex, sizeof(kCurveIndicesAsStrips),
+                        kCurveIndicesAsStrips, gCurveIndexBufferKey);
+                fNumIndicesPerInstance = SK_ARRAY_COUNT(kCurveIndicesAsStrips);
+            } else {
+                fIndexBuffer = rp->findOrMakeStaticBuffer(
+                        GrGpuBufferType::kIndex, sizeof(kCurveIndicesAsTris), kCurveIndicesAsTris,
+                        gCurveIndexBufferKey);
+                fNumIndicesPerInstance = SK_ARRAY_COUNT(kCurveIndicesAsTris);
+            }
+            break;
+        }
+    }
+
+    GrVertexAttribType xyAttribType;
+    GrSLType xySLType;
+    if (4 == this->numInputPoints() || this->hasInputWeight()) {
+        GR_STATIC_ASSERT(offsetof(QuadPointInstance, fX) == 0);
+        GR_STATIC_ASSERT(sizeof(QuadPointInstance::fX) ==
+                         GrVertexAttribTypeSize(kFloat4_GrVertexAttribType));
+        GR_STATIC_ASSERT(sizeof(QuadPointInstance::fY) ==
+                         GrVertexAttribTypeSize(kFloat4_GrVertexAttribType));
+        xyAttribType = kFloat4_GrVertexAttribType;
+        xySLType = kFloat4_GrSLType;
+    } else {
+        GR_STATIC_ASSERT(offsetof(TriPointInstance, fX) == 0);
+        GR_STATIC_ASSERT(sizeof(TriPointInstance::fX) ==
+                         GrVertexAttribTypeSize(kFloat3_GrVertexAttribType));
+        GR_STATIC_ASSERT(sizeof(TriPointInstance::fY) ==
+                         GrVertexAttribTypeSize(kFloat3_GrVertexAttribType));
+        xyAttribType = kFloat3_GrVertexAttribType;
+        xySLType = kFloat3_GrSLType;
+    }
+    fInputXAndYValues[kInstanceAttribIdx_X] = {"X", xyAttribType, xySLType};
+    fInputXAndYValues[kInstanceAttribIdx_Y] = {"Y", xyAttribType, xySLType};
+    this->setInstanceAttributes(fInputXAndYValues, 2);
+    fPerVertexData = {"vertexdata", kInt_GrVertexAttribType, kInt_GrSLType};
+    this->setVertexAttributes(&fPerVertexData, 1);
+
+    if (caps.usePrimitiveRestart()) {
+        fTriangleType = GrPrimitiveType::kTriangleStrip;
+    } else {
+        fTriangleType = GrPrimitiveType::kTriangles;
+    }
+}
+
+void GrVSCoverageProcessor::appendMesh(sk_sp<const GrGpuBuffer> instanceBuffer, int instanceCount,
+                                       int baseInstance, SkTArray<GrMesh>* out) const {
+    GrMesh& mesh = out->emplace_back(fTriangleType);
+    auto primitiveRestart = GrPrimitiveRestart(GrPrimitiveType::kTriangleStrip == fTriangleType);
+    mesh.setIndexedInstanced(fIndexBuffer, fNumIndicesPerInstance, std::move(instanceBuffer),
+                             instanceCount, baseInstance, primitiveRestart);
+    mesh.setVertexData(fVertexBuffer, 0);
+}
+
+GrGLSLPrimitiveProcessor* GrVSCoverageProcessor::onCreateGLSLInstance(
+        std::unique_ptr<Shader> shader) const {
+    switch (fPrimitiveType) {
+        case PrimitiveType::kTriangles:
+        case PrimitiveType::kWeightedTriangles:
+            return new Impl(std::move(shader), 3);
+        case PrimitiveType::kQuadratics:
+        case PrimitiveType::kCubics:
+        case PrimitiveType::kConics:
+            return new Impl(std::move(shader), 4);
+    }
+    SK_ABORT("Invalid PrimitiveType");
+    return nullptr;
+}
diff --git a/src/gpu/ccpr/GrVSCoverageProcessor.h b/src/gpu/ccpr/GrVSCoverageProcessor.h
new file mode 100644
index 0000000..e278548
--- /dev/null
+++ b/src/gpu/ccpr/GrVSCoverageProcessor.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrVSCoverageProcessor_DEFINED
+#define GrVSCoverageProcessor_DEFINED
+
+#include "ccpr/GrCCCoverageProcessor.h"
+
+/**
+ * This class implements GrCCCoverageProcessor with analytic coverage using vertex shaders.
+ */
+class GrVSCoverageProcessor : public GrCCCoverageProcessor {
+public:
+    GrVSCoverageProcessor() : GrCCCoverageProcessor(kGrVSCoverageProcessor_ClassID) {}
+
+private:
+    void reset(PrimitiveType, GrResourceProvider*) override;
+
+    void appendMesh(sk_sp<const GrGpuBuffer> instanceBuffer, int instanceCount, int baseInstance,
+                    SkTArray<GrMesh>* out) const override;
+
+    GrGLSLPrimitiveProcessor* onCreateGLSLInstance(std::unique_ptr<Shader>) const override;
+
+    Attribute fPerVertexData;
+    Attribute fInputXAndYValues[2];
+    sk_sp<const GrGpuBuffer> fVertexBuffer;
+    sk_sp<const GrGpuBuffer> fIndexBuffer;
+    int fNumIndicesPerInstance;
+    GrPrimitiveType fTriangleType;
+
+    class Impl;
+};
+
+#endif
diff --git a/src/gpu/effects/GrAARectEffect.cpp b/src/gpu/effects/GrAARectEffect.cpp
deleted file mode 100644
index 2a4acf1a..0000000
--- a/src/gpu/effects/GrAARectEffect.cpp
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrAARectEffect.fp; do not modify.
- **************************************************************************************************/
-#include "GrAARectEffect.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLAARectEffect : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLAARectEffect() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrAARectEffect& _outer = args.fFp.cast<GrAARectEffect>();
-        (void)_outer;
-        auto edgeType = _outer.edgeType();
-        (void)edgeType;
-        auto rect = _outer.rect();
-        (void)rect;
-        prevRect = float4(-1.0);
-        fRectUniformVar = args.fUniformHandler->addUniform(
-                kFragment_GrShaderFlag, kFloat4_GrSLType, kDefault_GrSLPrecision, "rectUniform");
-        fragBuilder->codeAppendf(
-                "float4 prevRect = float4(%f, %f, %f, %f);\nhalf alpha;\n@switch (%d) {\n    case "
-                "0:\n    case 2:\n        alpha = half(all(greaterThan(float4(sk_FragCoord.xy, "
-                "%s.zw), float4(%s.xy, sk_FragCoord.xy))) ? 1 : 0);\n        break;\n    "
-                "default:\n        half xSub, ySub;\n        xSub = min(half(sk_FragCoord.x - "
-                "%s.x), 0.0);\n        xSub += min(half(%s.z - sk_FragCoord.x), 0.0);\n        "
-                "ySub = min(half(sk_FragCoord.y - %s.y), 0.0);\n        ySub += min(half(%s.w - "
-                "sk_FragCoord.y), 0.0);\n        alpha = (1.0 + ",
-                prevRect.left(),
-                prevRect.top(),
-                prevRect.right(),
-                prevRect.bottom(),
-                (int)_outer.edgeType(),
-                args.fUniformHandler->getUniformCStr(fRectUniformVar),
-                args.fUniformHandler->getUniformCStr(fRectUniformVar),
-                args.fUniformHandler->getUniformCStr(fRectUniformVar),
-                args.fUniformHandler->getUniformCStr(fRectUniformVar),
-                args.fUniformHandler->getUniformCStr(fRectUniformVar),
-                args.fUniformHandler->getUniformCStr(fRectUniformVar));
-        fragBuilder->codeAppendf(
-                "max(xSub, -1.0)) * (1.0 + max(ySub, -1.0));\n}\n@if (%d == 2 || %d == 3) {\n    "
-                "alpha = 1.0 - alpha;\n}\n%s = %s * alpha;\n",
-                (int)_outer.edgeType(),
-                (int)_outer.edgeType(),
-                args.fOutputColor,
-                args.fInputColor);
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {
-        const GrAARectEffect& _outer = _proc.cast<GrAARectEffect>();
-        auto edgeType = _outer.edgeType();
-        (void)edgeType;
-        auto rect = _outer.rect();
-        (void)rect;
-        UniformHandle& rectUniform = fRectUniformVar;
-        (void)rectUniform;
-
-        const SkRect& newRect = GrProcessorEdgeTypeIsAA(edgeType) ? rect.makeInset(.5f, .5f) : rect;
-        if (newRect != prevRect) {
-            pdman.set4f(rectUniform, newRect.fLeft, newRect.fTop, newRect.fRight, newRect.fBottom);
-            prevRect = newRect;
-        }
-    }
-    SkRect prevRect = float4(0);
-    UniformHandle fRectUniformVar;
-};
-GrGLSLFragmentProcessor* GrAARectEffect::onCreateGLSLInstance() const {
-    return new GrGLSLAARectEffect();
-}
-void GrAARectEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                           GrProcessorKeyBuilder* b) const {
-    b->add32((int32_t)fEdgeType);
-}
-bool GrAARectEffect::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrAARectEffect& that = other.cast<GrAARectEffect>();
-    (void)that;
-    if (fEdgeType != that.fEdgeType) return false;
-    if (fRect != that.fRect) return false;
-    return true;
-}
-GrAARectEffect::GrAARectEffect(const GrAARectEffect& src)
-        : INHERITED(kGrAARectEffect_ClassID, src.optimizationFlags())
-        , fEdgeType(src.fEdgeType)
-        , fRect(src.fRect) {}
-std::unique_ptr<GrFragmentProcessor> GrAARectEffect::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrAARectEffect(*this));
-}
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrAARectEffect);
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrAARectEffect::TestCreate(GrProcessorTestData* d) {
-    SkRect rect = SkRect::MakeLTRB(d->fRandom->nextSScalar1(),
-                                   d->fRandom->nextSScalar1(),
-                                   d->fRandom->nextSScalar1(),
-                                   d->fRandom->nextSScalar1());
-    std::unique_ptr<GrFragmentProcessor> fp;
-    do {
-        GrClipEdgeType edgeType =
-                static_cast<GrClipEdgeType>(d->fRandom->nextULessThan(kGrClipEdgeTypeCnt));
-
-        fp = GrAARectEffect::Make(edgeType, rect);
-    } while (nullptr == fp);
-    return fp;
-}
-#endif
diff --git a/src/gpu/effects/GrAARectEffect.h b/src/gpu/effects/GrAARectEffect.h
deleted file mode 100644
index 94974e2c..0000000
--- a/src/gpu/effects/GrAARectEffect.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrAARectEffect.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrAARectEffect_DEFINED
-#define GrAARectEffect_DEFINED
-#include "SkTypes.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrAARectEffect : public GrFragmentProcessor {
-public:
-    const GrClipEdgeType& edgeType() const { return fEdgeType; }
-    const SkRect& rect() const { return fRect; }
-    static std::unique_ptr<GrFragmentProcessor> Make(GrClipEdgeType edgeType, SkRect rect) {
-        return std::unique_ptr<GrFragmentProcessor>(new GrAARectEffect(edgeType, rect));
-    }
-    GrAARectEffect(const GrAARectEffect& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "AARectEffect"; }
-
-private:
-    GrAARectEffect(GrClipEdgeType edgeType, SkRect rect)
-            : INHERITED(kGrAARectEffect_ClassID,
-                        (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag)
-            , fEdgeType(edgeType)
-            , fRect(rect) {}
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    GrClipEdgeType fEdgeType;
-    SkRect fRect;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/effects/GrAlphaThresholdFragmentProcessor.cpp b/src/gpu/effects/GrAlphaThresholdFragmentProcessor.cpp
deleted file mode 100644
index 0920905..0000000
--- a/src/gpu/effects/GrAlphaThresholdFragmentProcessor.cpp
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrAlphaThresholdFragmentProcessor.fp; do not modify.
- **************************************************************************************************/
-#include "GrAlphaThresholdFragmentProcessor.h"
-
-inline GrFragmentProcessor::OptimizationFlags GrAlphaThresholdFragmentProcessor::optFlags(
-        float outerThreshold) {
-    if (outerThreshold >= 1.0) {
-        return kPreservesOpaqueInput_OptimizationFlag |
-               kCompatibleWithCoverageAsAlpha_OptimizationFlag;
-    } else {
-        return kCompatibleWithCoverageAsAlpha_OptimizationFlag;
-    }
-}
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLAlphaThresholdFragmentProcessor : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLAlphaThresholdFragmentProcessor() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrAlphaThresholdFragmentProcessor& _outer =
-                args.fFp.cast<GrAlphaThresholdFragmentProcessor>();
-        (void)_outer;
-        auto innerThreshold = _outer.innerThreshold();
-        (void)innerThreshold;
-        auto outerThreshold = _outer.outerThreshold();
-        (void)outerThreshold;
-        fInnerThresholdVar = args.fUniformHandler->addUniform(
-                kFragment_GrShaderFlag, kHalf_GrSLType, kDefault_GrSLPrecision, "innerThreshold");
-        fOuterThresholdVar = args.fUniformHandler->addUniform(
-                kFragment_GrShaderFlag, kHalf_GrSLType, kDefault_GrSLPrecision, "outerThreshold");
-        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
-        fragBuilder->codeAppendf(
-                "half4 color = %s;\nhalf4 mask_color = texture(%s, %s).%s;\nif (mask_color.w < "
-                "0.5) {\n    if (color.w > %s) {\n        half scale = %s / color.w;\n        "
-                "color.xyz *= scale;\n        color.w = %s;\n    }\n} else if (color.w < %s) {\n   "
-                " half scale = %s / max(0.001, color.w);\n    color.xyz *= scale;\n    color.w = "
-                "%s;\n}\n%s = color;\n",
-                args.fInputColor,
-                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(),
-                sk_TransformedCoords2D_0.c_str(),
-                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str(),
-                args.fUniformHandler->getUniformCStr(fOuterThresholdVar),
-                args.fUniformHandler->getUniformCStr(fOuterThresholdVar),
-                args.fUniformHandler->getUniformCStr(fOuterThresholdVar),
-                args.fUniformHandler->getUniformCStr(fInnerThresholdVar),
-                args.fUniformHandler->getUniformCStr(fInnerThresholdVar),
-                args.fUniformHandler->getUniformCStr(fInnerThresholdVar), args.fOutputColor);
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {
-        const GrAlphaThresholdFragmentProcessor& _outer =
-                _proc.cast<GrAlphaThresholdFragmentProcessor>();
-        {
-            pdman.set1f(fInnerThresholdVar, (_outer.innerThreshold()));
-            pdman.set1f(fOuterThresholdVar, (_outer.outerThreshold()));
-        }
-    }
-    UniformHandle fInnerThresholdVar;
-    UniformHandle fOuterThresholdVar;
-};
-GrGLSLFragmentProcessor* GrAlphaThresholdFragmentProcessor::onCreateGLSLInstance() const {
-    return new GrGLSLAlphaThresholdFragmentProcessor();
-}
-void GrAlphaThresholdFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                                              GrProcessorKeyBuilder* b) const {}
-bool GrAlphaThresholdFragmentProcessor::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrAlphaThresholdFragmentProcessor& that = other.cast<GrAlphaThresholdFragmentProcessor>();
-    (void)that;
-    if (fMask != that.fMask) return false;
-    if (fInnerThreshold != that.fInnerThreshold) return false;
-    if (fOuterThreshold != that.fOuterThreshold) return false;
-    return true;
-}
-GrAlphaThresholdFragmentProcessor::GrAlphaThresholdFragmentProcessor(
-        const GrAlphaThresholdFragmentProcessor& src)
-        : INHERITED(kGrAlphaThresholdFragmentProcessor_ClassID, src.optimizationFlags())
-        , fMask(src.fMask)
-        , fInnerThreshold(src.fInnerThreshold)
-        , fOuterThreshold(src.fOuterThreshold)
-        , fMaskCoordTransform(src.fMaskCoordTransform) {
-    this->setTextureSamplerCnt(1);
-    this->addCoordTransform(&fMaskCoordTransform);
-}
-std::unique_ptr<GrFragmentProcessor> GrAlphaThresholdFragmentProcessor::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrAlphaThresholdFragmentProcessor(*this));
-}
-const GrFragmentProcessor::TextureSampler& GrAlphaThresholdFragmentProcessor::onTextureSampler(
-        int index) const {
-    return IthTextureSampler(index, fMask);
-}
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrAlphaThresholdFragmentProcessor);
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrAlphaThresholdFragmentProcessor::TestCreate(
-        GrProcessorTestData* testData) {
-    sk_sp<GrTextureProxy> maskProxy = testData->textureProxy(GrProcessorUnitTest::kAlphaTextureIdx);
-    // Make the inner and outer thresholds be in (0, 1) exclusive and be sorted correctly.
-    float innerThresh = testData->fRandom->nextUScalar1() * .99f + 0.005f;
-    float outerThresh = testData->fRandom->nextUScalar1() * .99f + 0.005f;
-    const int kMaxWidth = 1000;
-    const int kMaxHeight = 1000;
-    uint32_t width = testData->fRandom->nextULessThan(kMaxWidth);
-    uint32_t height = testData->fRandom->nextULessThan(kMaxHeight);
-    uint32_t x = testData->fRandom->nextULessThan(kMaxWidth - width);
-    uint32_t y = testData->fRandom->nextULessThan(kMaxHeight - height);
-    SkIRect bounds = SkIRect::MakeXYWH(x, y, width, height);
-    return GrAlphaThresholdFragmentProcessor::Make(std::move(maskProxy), innerThresh, outerThresh,
-                                                   bounds);
-}
-#endif
diff --git a/src/gpu/effects/GrAlphaThresholdFragmentProcessor.h b/src/gpu/effects/GrAlphaThresholdFragmentProcessor.h
deleted file mode 100644
index 74811d3..0000000
--- a/src/gpu/effects/GrAlphaThresholdFragmentProcessor.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrAlphaThresholdFragmentProcessor.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrAlphaThresholdFragmentProcessor_DEFINED
-#define GrAlphaThresholdFragmentProcessor_DEFINED
-#include "SkTypes.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrAlphaThresholdFragmentProcessor : public GrFragmentProcessor {
-public:
-    inline OptimizationFlags optFlags(float outerThreshold);
-    float innerThreshold() const { return fInnerThreshold; }
-    float outerThreshold() const { return fOuterThreshold; }
-
-    static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy> mask,
-                                                     float innerThreshold,
-                                                     float outerThreshold,
-                                                     const SkIRect& bounds) {
-        return std::unique_ptr<GrFragmentProcessor>(new GrAlphaThresholdFragmentProcessor(
-                mask, innerThreshold, outerThreshold, bounds));
-    }
-    GrAlphaThresholdFragmentProcessor(const GrAlphaThresholdFragmentProcessor& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "AlphaThresholdFragmentProcessor"; }
-
-private:
-    GrAlphaThresholdFragmentProcessor(sk_sp<GrTextureProxy> mask, float innerThreshold,
-                                      float outerThreshold, const SkIRect& bounds)
-            : INHERITED(kGrAlphaThresholdFragmentProcessor_ClassID, kNone_OptimizationFlags)
-            , fMask(std::move(mask))
-            , fInnerThreshold(innerThreshold)
-            , fOuterThreshold(outerThreshold)
-            , fMaskCoordTransform(
-                      SkMatrix::MakeTrans(SkIntToScalar(-bounds.x()), SkIntToScalar(-bounds.y())),
-                      fMask.proxy()) {
-        this->setTextureSamplerCnt(1);
-        this->addCoordTransform(&fMaskCoordTransform);
-    }
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    const TextureSampler& onTextureSampler(int) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    TextureSampler fMask;
-    float fInnerThreshold;
-    float fOuterThreshold;
-    GrCoordTransform fMaskCoordTransform;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/effects/GrBicubicEffect.cpp b/src/gpu/effects/GrBicubicEffect.cpp
index b3bd5e2..d804021 100644
--- a/src/gpu/effects/GrBicubicEffect.cpp
+++ b/src/gpu/effects/GrBicubicEffect.cpp
@@ -20,6 +20,7 @@
                               GrProcessorKeyBuilder* b) {
         const GrBicubicEffect& bicubicEffect = effect.cast<GrBicubicEffect>();
         b->add32(GrTextureDomain::GLDomain::DomainKey(bicubicEffect.domain()));
+        b->add32(bicubicEffect.alphaType());
     }
 
 protected:
@@ -98,9 +99,17 @@
             "half4 s%d = wx.x * rowColors[0] + wx.y * rowColors[1] + wx.z * rowColors[2] + wx.w * rowColors[3];",
             y);
     }
-    SkString bicubicColor("(wy.x * s0 + wy.y * s1 + wy.z * s2 + wy.w * s3)");
-    fragBuilder->codeAppendf("%s = %s * %s;", args.fOutputColor, bicubicColor.c_str(),
-                             args.fInputColor);
+    fragBuilder->codeAppend("half4 bicubicColor = wy.x * s0 + wy.y * s1 + wy.z * s2 + wy.w * s3;");
+    // Bicubic can send colors out of range, so clamp to get them back in (source) gamut.
+    // The kind of clamp we have to do depends on the alpha type.
+    if (kPremul_SkAlphaType == bicubicEffect.alphaType()) {
+        fragBuilder->codeAppend("bicubicColor.a = saturate(bicubicColor.a);");
+        fragBuilder->codeAppend(
+                "bicubicColor.rgb = max(half3(0.0), min(bicubicColor.rgb, bicubicColor.aaa));");
+    } else {
+        fragBuilder->codeAppend("bicubicColor = saturate(bicubicColor);");
+    }
+    fragBuilder->codeAppendf("%s = bicubicColor * %s;", args.fOutputColor, args.fInputColor);
 }
 
 void GrGLBicubicEffect::onSetData(const GrGLSLProgramDataManager& pdman,
@@ -118,33 +127,18 @@
 }
 
 GrBicubicEffect::GrBicubicEffect(sk_sp<GrTextureProxy> proxy,
-                                 const SkMatrix& matrix,
+                                 const SkMatrix& matrix, const SkRect& domain,
                                  const GrSamplerState::WrapMode wrapModes[2],
-                                 GrTextureDomain::Mode modeX, GrTextureDomain::Mode modeY)
+                                 GrTextureDomain::Mode modeX, GrTextureDomain::Mode modeY,
+                                 SkAlphaType alphaType)
         : INHERITED{kGrBicubicEffect_ClassID,
                     ModulateForSamplerOptFlags(proxy->config(),
                             GrTextureDomain::IsDecalSampled(wrapModes, modeX,modeY))}
         , fCoordTransform(matrix, proxy.get())
-        , fDomain(proxy.get(),
-                  GrTextureDomain::MakeTexelDomain(
-                          SkIRect::MakeWH(proxy->width(), proxy->height()), modeX, modeY),
-                  modeX, modeY)
+        , fDomain(proxy.get(), domain, modeX, modeY)
         , fTextureSampler(std::move(proxy),
-                          GrSamplerState(wrapModes, GrSamplerState::Filter::kNearest)) {
-    this->addCoordTransform(&fCoordTransform);
-    this->setTextureSamplerCnt(1);
-}
-
-GrBicubicEffect::GrBicubicEffect(sk_sp<GrTextureProxy> proxy,
-                                 const SkMatrix& matrix,
-                                 const SkRect& domain)
-        : INHERITED(kGrBicubicEffect_ClassID, ModulateForClampedSamplerOptFlags(proxy->config()))
-        , fCoordTransform(matrix, proxy.get())
-        , fDomain(proxy.get(), domain, GrTextureDomain::kClamp_Mode, GrTextureDomain::kClamp_Mode)
-        , fTextureSampler(std::move(proxy)) {
-    // Make sure the sampler's ctor uses the clamp wrap mode
-    SkASSERT(fTextureSampler.samplerState().wrapModeX() == GrSamplerState::WrapMode::kClamp &&
-             fTextureSampler.samplerState().wrapModeY() == GrSamplerState::WrapMode::kClamp);
+                          GrSamplerState(wrapModes, GrSamplerState::Filter::kNearest))
+        , fAlphaType(alphaType) {
     this->addCoordTransform(&fCoordTransform);
     this->setTextureSamplerCnt(1);
 }
@@ -153,7 +147,8 @@
         : INHERITED(kGrBicubicEffect_ClassID, that.optimizationFlags())
         , fCoordTransform(that.fCoordTransform)
         , fDomain(that.fDomain)
-        , fTextureSampler(that.fTextureSampler) {
+        , fTextureSampler(that.fTextureSampler)
+        , fAlphaType(that.fAlphaType) {
     this->addCoordTransform(&fCoordTransform);
     this->setTextureSamplerCnt(1);
 }
@@ -180,7 +175,8 @@
                                         : GrProcessorUnitTest::kAlphaTextureIdx;
     static const GrSamplerState::WrapMode kClampClamp[] = {GrSamplerState::WrapMode::kClamp,
                                                            GrSamplerState::WrapMode::kClamp};
-    return GrBicubicEffect::Make(d->textureProxy(texIdx), SkMatrix::I(), kClampClamp);
+    SkAlphaType alphaType = d->fRandom->nextBool() ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
+    return GrBicubicEffect::Make(d->textureProxy(texIdx), SkMatrix::I(), kClampClamp, alphaType);
 }
 #endif
 
diff --git a/src/gpu/effects/GrBicubicEffect.h b/src/gpu/effects/GrBicubicEffect.h
index 68a63e1..9086b5a 100644
--- a/src/gpu/effects/GrBicubicEffect.h
+++ b/src/gpu/effects/GrBicubicEffect.h
@@ -28,16 +28,19 @@
 
     const GrTextureDomain& domain() const { return fDomain; }
 
+    SkAlphaType alphaType() const { return fAlphaType; }
+
     /**
      * Create a Mitchell filter effect with specified texture matrix and x/y tile modes.
      */
     static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy> proxy,
                                                      const SkMatrix& matrix,
-                                                     const GrSamplerState::WrapMode wrapModes[2]) {
+                                                     const GrSamplerState::WrapMode wrapModes[2],
+                                                     SkAlphaType alphaType) {
         // Ignore the domain on x and y, since this factory relies solely on the wrap mode of the
         // sampler to constrain texture coordinates
         return Make(std::move(proxy), matrix, wrapModes, GrTextureDomain::kIgnore_Mode,
-                    GrTextureDomain::kIgnore_Mode);
+                    GrTextureDomain::kIgnore_Mode, alphaType);
     }
 
     /**
@@ -49,9 +52,13 @@
                                                      const SkMatrix& matrix,
                                                      const GrSamplerState::WrapMode wrapModes[2],
                                                      GrTextureDomain::Mode modeX,
-                                                     GrTextureDomain::Mode modeY) {
-        return std::unique_ptr<GrFragmentProcessor>(new GrBicubicEffect(std::move(proxy), matrix,
-                                                                        wrapModes, modeX, modeY));
+                                                     GrTextureDomain::Mode modeY,
+                                                     SkAlphaType alphaType,
+                                                     const SkRect* domain = nullptr) {
+        SkRect resolvedDomain = domain ? *domain : GrTextureDomain::MakeTexelDomain(
+                SkIRect::MakeWH(proxy->width(), proxy->height()), modeX, modeY);
+        return std::unique_ptr<GrFragmentProcessor>(new GrBicubicEffect(
+                std::move(proxy), matrix, resolvedDomain, wrapModes, modeX, modeY, alphaType));
     }
 
     /**
@@ -59,9 +66,12 @@
      */
     static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy> proxy,
                                                      const SkMatrix& matrix,
-                                                     const SkRect& domain) {
-        return std::unique_ptr<GrFragmentProcessor>(new GrBicubicEffect(std::move(proxy), matrix,
-                                                                        domain));
+                                                     const SkRect& domain,
+                                                     SkAlphaType alphaType) {
+        static const GrSamplerState::WrapMode kClampClamp[] = {
+                GrSamplerState::WrapMode::kClamp, GrSamplerState::WrapMode::kClamp};
+        return Make(std::move(proxy), matrix, kClampClamp, GrTextureDomain::kClamp_Mode,
+                GrTextureDomain::kClamp_Mode, alphaType, &domain);
     }
 
     /**
@@ -75,10 +85,10 @@
                                  GrSamplerState::Filter* filterMode);
 
 private:
-    GrBicubicEffect(sk_sp<GrTextureProxy>, const SkMatrix& matrix,
+    GrBicubicEffect(sk_sp<GrTextureProxy>, const SkMatrix& matrix, const SkRect& domain,
                     const GrSamplerState::WrapMode wrapModes[2],
-                    GrTextureDomain::Mode modeX, GrTextureDomain::Mode modeY);
-    GrBicubicEffect(sk_sp<GrTextureProxy>, const SkMatrix &matrix, const SkRect& domain);
+                    GrTextureDomain::Mode modeX, GrTextureDomain::Mode modeY,
+                    SkAlphaType alphaType);
     explicit GrBicubicEffect(const GrBicubicEffect&);
 
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
@@ -92,6 +102,7 @@
     GrCoordTransform fCoordTransform;
     GrTextureDomain fDomain;
     TextureSampler fTextureSampler;
+    SkAlphaType fAlphaType;
 
     GR_DECLARE_FRAGMENT_PROCESSOR_TEST
 
diff --git a/src/gpu/effects/GrBitmapTextGeoProc.cpp b/src/gpu/effects/GrBitmapTextGeoProc.cpp
index 370b0e5..1828eca 100644
--- a/src/gpu/effects/GrBitmapTextGeoProc.cpp
+++ b/src/gpu/effects/GrBitmapTextGeoProc.cpp
@@ -35,7 +35,6 @@
         const char* atlasSizeInvName;
         fAtlasSizeInvUniform = uniformHandler->addUniform(kVertex_GrShaderFlag,
                                                           kFloat2_GrSLType,
-                                                          kHigh_GrSLPrecision,
                                                           "AtlasSizeInv",
                                                           &atlasSizeInvName);
 
diff --git a/src/gpu/effects/GrBlurredEdgeFragmentProcessor.cpp b/src/gpu/effects/GrBlurredEdgeFragmentProcessor.cpp
deleted file mode 100644
index 6406e4e..0000000
--- a/src/gpu/effects/GrBlurredEdgeFragmentProcessor.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrBlurredEdgeFragmentProcessor.fp; do not modify.
- **************************************************************************************************/
-#include "GrBlurredEdgeFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLBlurredEdgeFragmentProcessor : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLBlurredEdgeFragmentProcessor() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrBlurredEdgeFragmentProcessor& _outer =
-                args.fFp.cast<GrBlurredEdgeFragmentProcessor>();
-        (void)_outer;
-        auto mode = _outer.mode();
-        (void)mode;
-        fragBuilder->codeAppendf(
-                "half factor = 1.0 - %s.w;\n@switch (%d) {\n    case 0:\n        factor = "
-                "exp((-factor * factor) * 4.0) - 0.017999999999999999;\n        break;\n    case "
-                "1:\n        factor = smoothstep(1.0, 0.0, factor);\n        break;\n}\n%s = "
-                "half4(factor);\n",
-                args.fInputColor, (int)_outer.mode(), args.fOutputColor);
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {}
-};
-GrGLSLFragmentProcessor* GrBlurredEdgeFragmentProcessor::onCreateGLSLInstance() const {
-    return new GrGLSLBlurredEdgeFragmentProcessor();
-}
-void GrBlurredEdgeFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                                           GrProcessorKeyBuilder* b) const {
-    b->add32((int32_t)fMode);
-}
-bool GrBlurredEdgeFragmentProcessor::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrBlurredEdgeFragmentProcessor& that = other.cast<GrBlurredEdgeFragmentProcessor>();
-    (void)that;
-    if (fMode != that.fMode) return false;
-    return true;
-}
-GrBlurredEdgeFragmentProcessor::GrBlurredEdgeFragmentProcessor(
-        const GrBlurredEdgeFragmentProcessor& src)
-        : INHERITED(kGrBlurredEdgeFragmentProcessor_ClassID, src.optimizationFlags())
-        , fMode(src.fMode) {}
-std::unique_ptr<GrFragmentProcessor> GrBlurredEdgeFragmentProcessor::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrBlurredEdgeFragmentProcessor(*this));
-}
diff --git a/src/gpu/effects/GrBlurredEdgeFragmentProcessor.h b/src/gpu/effects/GrBlurredEdgeFragmentProcessor.h
deleted file mode 100644
index 71376d7..0000000
--- a/src/gpu/effects/GrBlurredEdgeFragmentProcessor.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrBlurredEdgeFragmentProcessor.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrBlurredEdgeFragmentProcessor_DEFINED
-#define GrBlurredEdgeFragmentProcessor_DEFINED
-#include "SkTypes.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrBlurredEdgeFragmentProcessor : public GrFragmentProcessor {
-public:
-    enum class Mode { kGaussian = 0, kSmoothStep = 1 };
-    const Mode& mode() const { return fMode; }
-    static std::unique_ptr<GrFragmentProcessor> Make(Mode mode) {
-        return std::unique_ptr<GrFragmentProcessor>(new GrBlurredEdgeFragmentProcessor(mode));
-    }
-    GrBlurredEdgeFragmentProcessor(const GrBlurredEdgeFragmentProcessor& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "BlurredEdgeFragmentProcessor"; }
-
-private:
-    GrBlurredEdgeFragmentProcessor(Mode mode)
-            : INHERITED(kGrBlurredEdgeFragmentProcessor_ClassID, kNone_OptimizationFlags)
-            , fMode(mode) {}
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    Mode fMode;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/effects/GrCircleBlurFragmentProcessor.cpp b/src/gpu/effects/GrCircleBlurFragmentProcessor.cpp
deleted file mode 100644
index bce42f8..0000000
--- a/src/gpu/effects/GrCircleBlurFragmentProcessor.cpp
+++ /dev/null
@@ -1,350 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrCircleBlurFragmentProcessor.fp; do not modify.
- **************************************************************************************************/
-#include "GrCircleBlurFragmentProcessor.h"
-
-#include "GrProxyProvider.h"
-
-// Computes an unnormalized half kernel (right side). Returns the summation of all the half
-// kernel values.
-static float make_unnormalized_half_kernel(float* halfKernel, int halfKernelSize, float sigma) {
-    const float invSigma = 1.f / sigma;
-    const float b = -0.5f * invSigma * invSigma;
-    float tot = 0.0f;
-    // Compute half kernel values at half pixel steps out from the center.
-    float t = 0.5f;
-    for (int i = 0; i < halfKernelSize; ++i) {
-        float value = expf(t * t * b);
-        tot += value;
-        halfKernel[i] = value;
-        t += 1.f;
-    }
-    return tot;
-}
-
-// Create a Gaussian half-kernel (right side) and a summed area table given a sigma and number
-// of discrete steps. The half kernel is normalized to sum to 0.5.
-static void make_half_kernel_and_summed_table(float* halfKernel, float* summedHalfKernel,
-                                              int halfKernelSize, float sigma) {
-    // The half kernel should sum to 0.5 not 1.0.
-    const float tot = 2.f * make_unnormalized_half_kernel(halfKernel, halfKernelSize, sigma);
-    float sum = 0.f;
-    for (int i = 0; i < halfKernelSize; ++i) {
-        halfKernel[i] /= tot;
-        sum += halfKernel[i];
-        summedHalfKernel[i] = sum;
-    }
-}
-
-// Applies the 1D half kernel vertically at points along the x axis to a circle centered at the
-// origin with radius circleR.
-void apply_kernel_in_y(float* results, int numSteps, float firstX, float circleR,
-                       int halfKernelSize, const float* summedHalfKernelTable) {
-    float x = firstX;
-    for (int i = 0; i < numSteps; ++i, x += 1.f) {
-        if (x < -circleR || x > circleR) {
-            results[i] = 0;
-            continue;
-        }
-        float y = sqrtf(circleR * circleR - x * x);
-        // In the column at x we exit the circle at +y and -y
-        // The summed table entry j is actually reflects an offset of j + 0.5.
-        y -= 0.5f;
-        int yInt = SkScalarFloorToInt(y);
-        SkASSERT(yInt >= -1);
-        if (y < 0) {
-            results[i] = (y + 0.5f) * summedHalfKernelTable[0];
-        } else if (yInt >= halfKernelSize - 1) {
-            results[i] = 0.5f;
-        } else {
-            float yFrac = y - yInt;
-            results[i] = (1.f - yFrac) * summedHalfKernelTable[yInt] +
-                         yFrac * summedHalfKernelTable[yInt + 1];
-        }
-    }
-}
-
-// Apply a Gaussian at point (evalX, 0) to a circle centered at the origin with radius circleR.
-// This relies on having a half kernel computed for the Gaussian and a table of applications of
-// the half kernel in y to columns at (evalX - halfKernel, evalX - halfKernel + 1, ..., evalX +
-// halfKernel) passed in as yKernelEvaluations.
-static uint8_t eval_at(float evalX, float circleR, const float* halfKernel, int halfKernelSize,
-                       const float* yKernelEvaluations) {
-    float acc = 0;
-
-    float x = evalX - halfKernelSize;
-    for (int i = 0; i < halfKernelSize; ++i, x += 1.f) {
-        if (x < -circleR || x > circleR) {
-            continue;
-        }
-        float verticalEval = yKernelEvaluations[i];
-        acc += verticalEval * halfKernel[halfKernelSize - i - 1];
-    }
-    for (int i = 0; i < halfKernelSize; ++i, x += 1.f) {
-        if (x < -circleR || x > circleR) {
-            continue;
-        }
-        float verticalEval = yKernelEvaluations[i + halfKernelSize];
-        acc += verticalEval * halfKernel[i];
-    }
-    // Since we applied a half kernel in y we multiply acc by 2 (the circle is symmetric about
-    // the x axis).
-    return SkUnitScalarClampToByte(2.f * acc);
-}
-
-// This function creates a profile of a blurred circle. It does this by computing a kernel for
-// half the Gaussian and a matching summed area table. The summed area table is used to compute
-// an array of vertical applications of the half kernel to the circle along the x axis. The
-// table of y evaluations has 2 * k + n entries where k is the size of the half kernel and n is
-// the size of the profile being computed. Then for each of the n profile entries we walk out k
-// steps in each horizontal direction multiplying the corresponding y evaluation by the half
-// kernel entry and sum these values to compute the profile entry.
-static void create_circle_profile(uint8_t* weights, float sigma, float circleR,
-                                  int profileTextureWidth) {
-    const int numSteps = profileTextureWidth;
-
-    // The full kernel is 6 sigmas wide.
-    int halfKernelSize = SkScalarCeilToInt(6.0f * sigma);
-    // round up to next multiple of 2 and then divide by 2
-    halfKernelSize = ((halfKernelSize + 1) & ~1) >> 1;
-
-    // Number of x steps at which to apply kernel in y to cover all the profile samples in x.
-    int numYSteps = numSteps + 2 * halfKernelSize;
-
-    SkAutoTArray<float> bulkAlloc(halfKernelSize + halfKernelSize + numYSteps);
-    float* halfKernel = bulkAlloc.get();
-    float* summedKernel = bulkAlloc.get() + halfKernelSize;
-    float* yEvals = bulkAlloc.get() + 2 * halfKernelSize;
-    make_half_kernel_and_summed_table(halfKernel, summedKernel, halfKernelSize, sigma);
-
-    float firstX = -halfKernelSize + 0.5f;
-    apply_kernel_in_y(yEvals, numYSteps, firstX, circleR, halfKernelSize, summedKernel);
-
-    for (int i = 0; i < numSteps - 1; ++i) {
-        float evalX = i + 0.5f;
-        weights[i] = eval_at(evalX, circleR, halfKernel, halfKernelSize, yEvals + i);
-    }
-    // Ensure the tail of the Gaussian goes to zero.
-    weights[numSteps - 1] = 0;
-}
-
-static void create_half_plane_profile(uint8_t* profile, int profileWidth) {
-    SkASSERT(!(profileWidth & 0x1));
-    // The full kernel is 6 sigmas wide.
-    float sigma = profileWidth / 6.f;
-    int halfKernelSize = profileWidth / 2;
-
-    SkAutoTArray<float> halfKernel(halfKernelSize);
-
-    // The half kernel should sum to 0.5.
-    const float tot = 2.f * make_unnormalized_half_kernel(halfKernel.get(), halfKernelSize, sigma);
-    float sum = 0.f;
-    // Populate the profile from the right edge to the middle.
-    for (int i = 0; i < halfKernelSize; ++i) {
-        halfKernel[halfKernelSize - i - 1] /= tot;
-        sum += halfKernel[halfKernelSize - i - 1];
-        profile[profileWidth - i - 1] = SkUnitScalarClampToByte(sum);
-    }
-    // Populate the profile from the middle to the left edge (by flipping the half kernel and
-    // continuing the summation).
-    for (int i = 0; i < halfKernelSize; ++i) {
-        sum += halfKernel[i];
-        profile[halfKernelSize - i - 1] = SkUnitScalarClampToByte(sum);
-    }
-    // Ensure tail goes to 0.
-    profile[profileWidth - 1] = 0;
-}
-
-static sk_sp<GrTextureProxy> create_profile_texture(GrProxyProvider* proxyProvider,
-                                                    const SkRect& circle, float sigma,
-                                                    float* solidRadius, float* textureRadius) {
-    float circleR = circle.width() / 2.0f;
-    if (circleR < SK_ScalarNearlyZero) {
-        return nullptr;
-    }
-    // Profile textures are cached by the ratio of sigma to circle radius and by the size of the
-    // profile texture (binned by powers of 2).
-    SkScalar sigmaToCircleRRatio = sigma / circleR;
-    // When sigma is really small this becomes a equivalent to convolving a Gaussian with a
-    // half-plane. Similarly, in the extreme high ratio cases circle becomes a point WRT to the
-    // Guassian and the profile texture is a just a Gaussian evaluation. However, we haven't yet
-    // implemented this latter optimization.
-    sigmaToCircleRRatio = SkTMin(sigmaToCircleRRatio, 8.f);
-    SkFixed sigmaToCircleRRatioFixed;
-    static const SkScalar kHalfPlaneThreshold = 0.1f;
-    bool useHalfPlaneApprox = false;
-    if (sigmaToCircleRRatio <= kHalfPlaneThreshold) {
-        useHalfPlaneApprox = true;
-        sigmaToCircleRRatioFixed = 0;
-        *solidRadius = circleR - 3 * sigma;
-        *textureRadius = 6 * sigma;
-    } else {
-        // Convert to fixed point for the key.
-        sigmaToCircleRRatioFixed = SkScalarToFixed(sigmaToCircleRRatio);
-        // We shave off some bits to reduce the number of unique entries. We could probably
-        // shave off more than we do.
-        sigmaToCircleRRatioFixed &= ~0xff;
-        sigmaToCircleRRatio = SkFixedToScalar(sigmaToCircleRRatioFixed);
-        sigma = circleR * sigmaToCircleRRatio;
-        *solidRadius = 0;
-        *textureRadius = circleR + 3 * sigma;
-    }
-
-    static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
-    GrUniqueKey key;
-    GrUniqueKey::Builder builder(&key, kDomain, 1, "1-D Circular Blur");
-    builder[0] = sigmaToCircleRRatioFixed;
-    builder.finish();
-
-    sk_sp<GrTextureProxy> blurProfile =
-            proxyProvider->findOrCreateProxyByUniqueKey(key, kTopLeft_GrSurfaceOrigin);
-    if (!blurProfile) {
-        static constexpr int kProfileTextureWidth = 512;
-
-        SkBitmap bm;
-        if (!bm.tryAllocPixels(SkImageInfo::MakeA8(kProfileTextureWidth, 1))) {
-            return nullptr;
-        }
-
-        if (useHalfPlaneApprox) {
-            create_half_plane_profile(bm.getAddr8(0, 0), kProfileTextureWidth);
-        } else {
-            // Rescale params to the size of the texture we're creating.
-            SkScalar scale = kProfileTextureWidth / *textureRadius;
-            create_circle_profile(bm.getAddr8(0, 0), sigma * scale, circleR * scale,
-                                  kProfileTextureWidth);
-        }
-
-        bm.setImmutable();
-        sk_sp<SkImage> image = SkImage::MakeFromBitmap(bm);
-
-        blurProfile = proxyProvider->createTextureProxy(std::move(image), kNone_GrSurfaceFlags, 1,
-                                                        SkBudgeted::kYes, SkBackingFit::kExact);
-        if (!blurProfile) {
-            return nullptr;
-        }
-
-        SkASSERT(blurProfile->origin() == kTopLeft_GrSurfaceOrigin);
-        proxyProvider->assignUniqueKeyToProxy(key, blurProfile.get());
-    }
-
-    return blurProfile;
-}
-
-std::unique_ptr<GrFragmentProcessor> GrCircleBlurFragmentProcessor::Make(
-        GrProxyProvider* proxyProvider, const SkRect& circle, float sigma) {
-    float solidRadius;
-    float textureRadius;
-    sk_sp<GrTextureProxy> profile(
-            create_profile_texture(proxyProvider, circle, sigma, &solidRadius, &textureRadius));
-    if (!profile) {
-        return nullptr;
-    }
-    return std::unique_ptr<GrFragmentProcessor>(new GrCircleBlurFragmentProcessor(
-            circle, textureRadius, solidRadius, std::move(profile)));
-}
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLCircleBlurFragmentProcessor : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLCircleBlurFragmentProcessor() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrCircleBlurFragmentProcessor& _outer =
-                args.fFp.cast<GrCircleBlurFragmentProcessor>();
-        (void)_outer;
-        auto circleRect = _outer.circleRect();
-        (void)circleRect;
-        auto textureRadius = _outer.textureRadius();
-        (void)textureRadius;
-        auto solidRadius = _outer.solidRadius();
-        (void)solidRadius;
-        fCircleDataVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType,
-                                                          kDefault_GrSLPrecision, "circleData");
-        fragBuilder->codeAppendf(
-                "half2 vec = half2(half((sk_FragCoord.x - float(%s.x)) * float(%s.w)), "
-                "half((sk_FragCoord.y - float(%s.y)) * float(%s.w)));\nhalf dist = length(vec) + "
-                "(0.5 - %s.z) * %s.w;\n%s = %s * texture(%s, float2(half2(dist, 0.5))).%s.w;\n",
-                args.fUniformHandler->getUniformCStr(fCircleDataVar),
-                args.fUniformHandler->getUniformCStr(fCircleDataVar),
-                args.fUniformHandler->getUniformCStr(fCircleDataVar),
-                args.fUniformHandler->getUniformCStr(fCircleDataVar),
-                args.fUniformHandler->getUniformCStr(fCircleDataVar),
-                args.fUniformHandler->getUniformCStr(fCircleDataVar), args.fOutputColor,
-                args.fInputColor,
-                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(),
-                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str());
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& data,
-                   const GrFragmentProcessor& _proc) override {
-        const GrCircleBlurFragmentProcessor& _outer = _proc.cast<GrCircleBlurFragmentProcessor>();
-        auto circleRect = _outer.circleRect();
-        (void)circleRect;
-        auto textureRadius = _outer.textureRadius();
-        (void)textureRadius;
-        auto solidRadius = _outer.solidRadius();
-        (void)solidRadius;
-        GrSurfaceProxy& blurProfileSamplerProxy = *_outer.textureSampler(0).proxy();
-        GrTexture& blurProfileSampler = *blurProfileSamplerProxy.peekTexture();
-        (void)blurProfileSampler;
-        UniformHandle& circleData = fCircleDataVar;
-        (void)circleData;
-
-        data.set4f(circleData, circleRect.centerX(), circleRect.centerY(), solidRadius,
-                   1.f / textureRadius);
-    }
-    UniformHandle fCircleDataVar;
-};
-GrGLSLFragmentProcessor* GrCircleBlurFragmentProcessor::onCreateGLSLInstance() const {
-    return new GrGLSLCircleBlurFragmentProcessor();
-}
-void GrCircleBlurFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                                          GrProcessorKeyBuilder* b) const {}
-bool GrCircleBlurFragmentProcessor::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrCircleBlurFragmentProcessor& that = other.cast<GrCircleBlurFragmentProcessor>();
-    (void)that;
-    if (fCircleRect != that.fCircleRect) return false;
-    if (fTextureRadius != that.fTextureRadius) return false;
-    if (fSolidRadius != that.fSolidRadius) return false;
-    if (fBlurProfileSampler != that.fBlurProfileSampler) return false;
-    return true;
-}
-GrCircleBlurFragmentProcessor::GrCircleBlurFragmentProcessor(
-        const GrCircleBlurFragmentProcessor& src)
-        : INHERITED(kGrCircleBlurFragmentProcessor_ClassID, src.optimizationFlags())
-        , fCircleRect(src.fCircleRect)
-        , fTextureRadius(src.fTextureRadius)
-        , fSolidRadius(src.fSolidRadius)
-        , fBlurProfileSampler(src.fBlurProfileSampler) {
-    this->setTextureSamplerCnt(1);
-}
-std::unique_ptr<GrFragmentProcessor> GrCircleBlurFragmentProcessor::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrCircleBlurFragmentProcessor(*this));
-}
-const GrFragmentProcessor::TextureSampler& GrCircleBlurFragmentProcessor::onTextureSampler(
-        int index) const {
-    return IthTextureSampler(index, fBlurProfileSampler);
-}
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor);
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrCircleBlurFragmentProcessor::TestCreate(
-        GrProcessorTestData* testData) {
-    SkScalar wh = testData->fRandom->nextRangeScalar(100.f, 1000.f);
-    SkScalar sigma = testData->fRandom->nextRangeF(1.f, 10.f);
-    SkRect circle = SkRect::MakeWH(wh, wh);
-    return GrCircleBlurFragmentProcessor::Make(testData->proxyProvider(), circle, sigma);
-}
-#endif
diff --git a/src/gpu/effects/GrCircleBlurFragmentProcessor.h b/src/gpu/effects/GrCircleBlurFragmentProcessor.h
deleted file mode 100644
index 7a53b01..0000000
--- a/src/gpu/effects/GrCircleBlurFragmentProcessor.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrCircleBlurFragmentProcessor.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrCircleBlurFragmentProcessor_DEFINED
-#define GrCircleBlurFragmentProcessor_DEFINED
-#include "SkTypes.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrCircleBlurFragmentProcessor : public GrFragmentProcessor {
-public:
-    const SkRect& circleRect() const { return fCircleRect; }
-    float textureRadius() const { return fTextureRadius; }
-    float solidRadius() const { return fSolidRadius; }
-
-    static std::unique_ptr<GrFragmentProcessor> Make(GrProxyProvider*, const SkRect& circle,
-                                                     float sigma);
-    GrCircleBlurFragmentProcessor(const GrCircleBlurFragmentProcessor& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "CircleBlurFragmentProcessor"; }
-
-private:
-    GrCircleBlurFragmentProcessor(SkRect circleRect, float textureRadius, float solidRadius,
-                                  sk_sp<GrTextureProxy> blurProfileSampler)
-            : INHERITED(kGrCircleBlurFragmentProcessor_ClassID,
-                        (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag)
-            , fCircleRect(circleRect)
-            , fTextureRadius(textureRadius)
-            , fSolidRadius(solidRadius)
-            , fBlurProfileSampler(std::move(blurProfileSampler)) {
-        this->setTextureSamplerCnt(1);
-    }
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    const TextureSampler& onTextureSampler(int) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    SkRect fCircleRect;
-    float fTextureRadius;
-    float fSolidRadius;
-    TextureSampler fBlurProfileSampler;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/effects/GrCircleEffect.cpp b/src/gpu/effects/GrCircleEffect.cpp
deleted file mode 100644
index ddc81b8..0000000
--- a/src/gpu/effects/GrCircleEffect.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrCircleEffect.fp; do not modify.
- **************************************************************************************************/
-#include "GrCircleEffect.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLCircleEffect : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLCircleEffect() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrCircleEffect& _outer = args.fFp.cast<GrCircleEffect>();
-        (void)_outer;
-        auto edgeType = _outer.edgeType();
-        (void)edgeType;
-        auto center = _outer.center();
-        (void)center;
-        auto radius = _outer.radius();
-        (void)radius;
-        prevRadius = -1.0;
-        fCircleVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType,
-                                                      kDefault_GrSLPrecision, "circle");
-        fragBuilder->codeAppendf(
-                "half2 prevCenter;\nhalf prevRadius = %f;\nhalf d;\n@if (%d == 2 || %d == 3) {\n   "
-                " d = half((length((float2(%s.xy) - sk_FragCoord.xy) * float(%s.w)) - 1.0) * "
-                "float(%s.z));\n} else {\n    d = half((1.0 - length((float2(%s.xy) - "
-                "sk_FragCoord.xy) * float(%s.w))) * float(%s.z));\n}\n@if ((%d == 1 || %d == 3) || "
-                "%d == 4) {\n    d = clamp(d, 0.0, 1.0);\n} else {\n    d = d > 0.5 ? 1.0 : "
-                "0.0;\n}\n%s = %s * d;\n",
-                prevRadius, (int)_outer.edgeType(), (int)_outer.edgeType(),
-                args.fUniformHandler->getUniformCStr(fCircleVar),
-                args.fUniformHandler->getUniformCStr(fCircleVar),
-                args.fUniformHandler->getUniformCStr(fCircleVar),
-                args.fUniformHandler->getUniformCStr(fCircleVar),
-                args.fUniformHandler->getUniformCStr(fCircleVar),
-                args.fUniformHandler->getUniformCStr(fCircleVar), (int)_outer.edgeType(),
-                (int)_outer.edgeType(), (int)_outer.edgeType(), args.fOutputColor,
-                args.fInputColor);
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {
-        const GrCircleEffect& _outer = _proc.cast<GrCircleEffect>();
-        auto edgeType = _outer.edgeType();
-        (void)edgeType;
-        auto center = _outer.center();
-        (void)center;
-        auto radius = _outer.radius();
-        (void)radius;
-        UniformHandle& circle = fCircleVar;
-        (void)circle;
-
-        if (radius != prevRadius || center != prevCenter) {
-            SkScalar effectiveRadius = radius;
-            if (GrProcessorEdgeTypeIsInverseFill((GrClipEdgeType)edgeType)) {
-                effectiveRadius -= 0.5f;
-                // When the radius is 0.5 effectiveRadius is 0 which causes an inf * 0 in the
-                // shader.
-                effectiveRadius = SkTMax(0.001f, effectiveRadius);
-            } else {
-                effectiveRadius += 0.5f;
-            }
-            pdman.set4f(circle, center.fX, center.fY, effectiveRadius,
-                        SkScalarInvert(effectiveRadius));
-            prevCenter = center;
-            prevRadius = radius;
-        }
-    }
-    SkPoint prevCenter = half2(0);
-    float prevRadius = 0;
-    UniformHandle fCircleVar;
-};
-GrGLSLFragmentProcessor* GrCircleEffect::onCreateGLSLInstance() const {
-    return new GrGLSLCircleEffect();
-}
-void GrCircleEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                           GrProcessorKeyBuilder* b) const {
-    b->add32((int32_t)fEdgeType);
-}
-bool GrCircleEffect::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrCircleEffect& that = other.cast<GrCircleEffect>();
-    (void)that;
-    if (fEdgeType != that.fEdgeType) return false;
-    if (fCenter != that.fCenter) return false;
-    if (fRadius != that.fRadius) return false;
-    return true;
-}
-GrCircleEffect::GrCircleEffect(const GrCircleEffect& src)
-        : INHERITED(kGrCircleEffect_ClassID, src.optimizationFlags())
-        , fEdgeType(src.fEdgeType)
-        , fCenter(src.fCenter)
-        , fRadius(src.fRadius) {}
-std::unique_ptr<GrFragmentProcessor> GrCircleEffect::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrCircleEffect(*this));
-}
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleEffect);
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrCircleEffect::TestCreate(GrProcessorTestData* testData) {
-    SkPoint center;
-    center.fX = testData->fRandom->nextRangeScalar(0.f, 1000.f);
-    center.fY = testData->fRandom->nextRangeScalar(0.f, 1000.f);
-    SkScalar radius = testData->fRandom->nextRangeF(1.f, 1000.f);
-    GrClipEdgeType et;
-    do {
-        et = (GrClipEdgeType)testData->fRandom->nextULessThan(kGrClipEdgeTypeCnt);
-    } while (GrClipEdgeType::kHairlineAA == et);
-    return GrCircleEffect::Make(et, center, radius);
-}
-#endif
diff --git a/src/gpu/effects/GrCircleEffect.h b/src/gpu/effects/GrCircleEffect.h
index 686591a..81ab40e 100644
--- a/src/gpu/effects/GrCircleEffect.h
+++ b/src/gpu/effects/GrCircleEffect.h
@@ -15,10 +15,6 @@
 #include "GrCoordTransform.h"
 class GrCircleEffect : public GrFragmentProcessor {
 public:
-    const GrClipEdgeType& edgeType() const { return fEdgeType; }
-    const SkPoint& center() const { return fCenter; }
-    float radius() const { return fRadius; }
-
     static std::unique_ptr<GrFragmentProcessor> Make(GrClipEdgeType edgeType, SkPoint center,
                                                      float radius) {
         // A radius below half causes the implicit insetting done by this processor to become
@@ -31,21 +27,21 @@
     GrCircleEffect(const GrCircleEffect& src);
     std::unique_ptr<GrFragmentProcessor> clone() const override;
     const char* name() const override { return "CircleEffect"; }
+    GrClipEdgeType edgeType;
+    SkPoint center;
+    float radius;
 
 private:
     GrCircleEffect(GrClipEdgeType edgeType, SkPoint center, float radius)
             : INHERITED(kGrCircleEffect_ClassID,
                         (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag)
-            , fEdgeType(edgeType)
-            , fCenter(center)
-            , fRadius(radius) {}
+            , edgeType(edgeType)
+            , center(center)
+            , radius(radius) {}
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
     void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
     bool onIsEqual(const GrFragmentProcessor&) const override;
     GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    GrClipEdgeType fEdgeType;
-    SkPoint fCenter;
-    float fRadius;
     typedef GrFragmentProcessor INHERITED;
 };
 #endif
diff --git a/src/gpu/effects/GrComposeLerpEffect.fp b/src/gpu/effects/GrComposeLerpEffect.fp
new file mode 100644
index 0000000..4f55d21
--- /dev/null
+++ b/src/gpu/effects/GrComposeLerpEffect.fp
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+in fragmentProcessor? child1;
+in fragmentProcessor? child2;
+in uniform float weight;
+
+void main() {
+    sk_OutColor = mix(child1 != null ? process(child1) : sk_InColor,
+                      child2 != null ? process(child2) : sk_InColor,
+                      half(weight));
+}
diff --git a/src/gpu/effects/GrComposeLerpRedEffect.fp b/src/gpu/effects/GrComposeLerpRedEffect.fp
new file mode 100644
index 0000000..6179c93
--- /dev/null
+++ b/src/gpu/effects/GrComposeLerpRedEffect.fp
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+in fragmentProcessor? child1;
+in fragmentProcessor? child2;
+in fragmentProcessor lerp;
+
+void main() {
+    sk_OutColor = mix(child1 != null ? process(child1) : sk_InColor,
+                      child2 != null ? process(child2) : sk_InColor,
+                      process(lerp).r);
+}
diff --git a/src/gpu/effects/GrConfigConversionEffect.cpp b/src/gpu/effects/GrConfigConversionEffect.cpp
deleted file mode 100644
index 158ff90..0000000
--- a/src/gpu/effects/GrConfigConversionEffect.cpp
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrConfigConversionEffect.fp; do not modify.
- **************************************************************************************************/
-#include "GrConfigConversionEffect.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLConfigConversionEffect : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLConfigConversionEffect() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrConfigConversionEffect& _outer = args.fFp.cast<GrConfigConversionEffect>();
-        (void)_outer;
-        auto pmConversion = _outer.pmConversion();
-        (void)pmConversion;
-
-        fragBuilder->forceHighPrecision();
-        fragBuilder->codeAppendf(
-                "%s = floor(%s * 255.0 + 0.5) / 255.0;\n@switch (%d) {\n    case 0:\n        "
-                "%s.xyz = floor((%s.xyz * %s.w) * 255.0 + 0.5) / 255.0;\n        break;\n    case "
-                "1:\n        %s.xyz = %s.w <= 0.0 ? half3(0.0) : floor((%s.xyz / %s.w) * 255.0 + "
-                "0.5) / 255.0;\n        break;\n}\n",
-                args.fOutputColor, args.fInputColor, (int)_outer.pmConversion(), args.fOutputColor,
-                args.fOutputColor, args.fOutputColor, args.fOutputColor, args.fOutputColor,
-                args.fOutputColor, args.fOutputColor);
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {}
-};
-GrGLSLFragmentProcessor* GrConfigConversionEffect::onCreateGLSLInstance() const {
-    return new GrGLSLConfigConversionEffect();
-}
-void GrConfigConversionEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                                     GrProcessorKeyBuilder* b) const {
-    b->add32((int32_t)fPmConversion);
-}
-bool GrConfigConversionEffect::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrConfigConversionEffect& that = other.cast<GrConfigConversionEffect>();
-    (void)that;
-    if (fPmConversion != that.fPmConversion) return false;
-    return true;
-}
-GrConfigConversionEffect::GrConfigConversionEffect(const GrConfigConversionEffect& src)
-        : INHERITED(kGrConfigConversionEffect_ClassID, src.optimizationFlags())
-        , fPmConversion(src.fPmConversion) {}
-std::unique_ptr<GrFragmentProcessor> GrConfigConversionEffect::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrConfigConversionEffect(*this));
-}
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrConfigConversionEffect);
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrConfigConversionEffect::TestCreate(
-        GrProcessorTestData* data) {
-    PMConversion pmConv = static_cast<PMConversion>(
-            data->fRandom->nextULessThan((int)PMConversion::kPMConversionCnt));
-    return std::unique_ptr<GrFragmentProcessor>(new GrConfigConversionEffect(pmConv));
-}
-#endif
diff --git a/src/gpu/effects/GrConfigConversionEffect.h b/src/gpu/effects/GrConfigConversionEffect.h
deleted file mode 100644
index b182765..0000000
--- a/src/gpu/effects/GrConfigConversionEffect.h
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrConfigConversionEffect.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrConfigConversionEffect_DEFINED
-#define GrConfigConversionEffect_DEFINED
-#include "SkTypes.h"
-
-#include "GrClip.h"
-#include "GrContext.h"
-#include "GrContextPriv.h"
-#include "GrProxyProvider.h"
-#include "GrRenderTargetContext.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrConfigConversionEffect : public GrFragmentProcessor {
-public:
-    static bool TestForPreservingPMConversions(GrContext* context) {
-        static constexpr int kSize = 256;
-        static constexpr GrPixelConfig kConfig = kRGBA_8888_GrPixelConfig;
-        static constexpr SkColorType kColorType = kRGBA_8888_SkColorType;
-        const GrBackendFormat format =
-                context->priv().caps()->getBackendFormatFromColorType(kColorType);
-        SkAutoTMalloc<uint32_t> data(kSize * kSize * 3);
-        uint32_t* srcData = data.get();
-        uint32_t* firstRead = data.get() + kSize * kSize;
-        uint32_t* secondRead = data.get() + 2 * kSize * kSize;
-
-        // Fill with every possible premultiplied A, color channel value. There will be 256-y
-        // duplicate values in row y. We set r, g, and b to the same value since they are handled
-        // identically.
-        for (int y = 0; y < kSize; ++y) {
-            for (int x = 0; x < kSize; ++x) {
-                uint8_t* color = reinterpret_cast<uint8_t*>(&srcData[kSize * y + x]);
-                color[3] = y;
-                color[2] = SkTMin(x, y);
-                color[1] = SkTMin(x, y);
-                color[0] = SkTMin(x, y);
-            }
-        }
-        memset(firstRead, 0, kSize * kSize * sizeof(uint32_t));
-        memset(secondRead, 0, kSize * kSize * sizeof(uint32_t));
-
-        const SkImageInfo ii =
-                SkImageInfo::Make(kSize, kSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
-
-        sk_sp<GrRenderTargetContext> readRTC(context->priv().makeDeferredRenderTargetContext(
-                format, SkBackingFit::kExact, kSize, kSize, kConfig, nullptr));
-        sk_sp<GrRenderTargetContext> tempRTC(context->priv().makeDeferredRenderTargetContext(
-                format, SkBackingFit::kExact, kSize, kSize, kConfig, nullptr));
-        if (!readRTC || !readRTC->asTextureProxy() || !tempRTC) {
-            return false;
-        }
-        // Adding discard to appease vulkan validation warning about loading uninitialized data on
-        // draw
-        readRTC->discard();
-
-        GrProxyProvider* proxyProvider = context->priv().proxyProvider();
-
-        SkPixmap pixmap(ii, srcData, 4 * kSize);
-
-        // This function is only ever called if we are in a GrContext that has a GrGpu since we are
-        // calling read pixels here. Thus the pixel data will be uploaded immediately and we don't
-        // need to keep the pixel data alive in the proxy. Therefore the ReleaseProc is nullptr.
-        sk_sp<SkImage> image = SkImage::MakeFromRaster(pixmap, nullptr, nullptr);
-
-        sk_sp<GrTextureProxy> dataProxy = proxyProvider->createTextureProxy(
-                std::move(image), kNone_GrSurfaceFlags, 1, SkBudgeted::kYes, SkBackingFit::kExact);
-        if (!dataProxy) {
-            return false;
-        }
-
-        static const SkRect kRect = SkRect::MakeIWH(kSize, kSize);
-
-        // We do a PM->UPM draw from dataTex to readTex and read the data. Then we do a UPM->PM draw
-        // from readTex to tempTex followed by a PM->UPM draw to readTex and finally read the data.
-        // We then verify that two reads produced the same values.
-
-        GrPaint paint1;
-        GrPaint paint2;
-        GrPaint paint3;
-        std::unique_ptr<GrFragmentProcessor> pmToUPM(
-                new GrConfigConversionEffect(PMConversion::kToUnpremul));
-        std::unique_ptr<GrFragmentProcessor> upmToPM(
-                new GrConfigConversionEffect(PMConversion::kToPremul));
-
-        paint1.addColorTextureProcessor(dataProxy, SkMatrix::I());
-        paint1.addColorFragmentProcessor(pmToUPM->clone());
-        paint1.setPorterDuffXPFactory(SkBlendMode::kSrc);
-
-        readRTC->fillRectToRect(GrNoClip(), std::move(paint1), GrAA::kNo, SkMatrix::I(), kRect,
-                                kRect);
-        if (!readRTC->readPixels(ii, firstRead, 0, 0, 0)) {
-            return false;
-        }
-
-        // Adding discard to appease vulkan validation warning about loading uninitialized data on
-        // draw
-        tempRTC->discard();
-
-        paint2.addColorTextureProcessor(readRTC->asTextureProxyRef(), SkMatrix::I());
-        paint2.addColorFragmentProcessor(std::move(upmToPM));
-        paint2.setPorterDuffXPFactory(SkBlendMode::kSrc);
-
-        tempRTC->fillRectToRect(GrNoClip(), std::move(paint2), GrAA::kNo, SkMatrix::I(), kRect,
-                                kRect);
-
-        paint3.addColorTextureProcessor(tempRTC->asTextureProxyRef(), SkMatrix::I());
-        paint3.addColorFragmentProcessor(std::move(pmToUPM));
-        paint3.setPorterDuffXPFactory(SkBlendMode::kSrc);
-
-        readRTC->fillRectToRect(GrNoClip(), std::move(paint3), GrAA::kNo, SkMatrix::I(), kRect,
-                                kRect);
-
-        if (!readRTC->readPixels(ii, secondRead, 0, 0, 0)) {
-            return false;
-        }
-
-        for (int y = 0; y < kSize; ++y) {
-            for (int x = 0; x <= y; ++x) {
-                if (firstRead[kSize * y + x] != secondRead[kSize * y + x]) {
-                    return false;
-                }
-            }
-        }
-
-        return true;
-    }
-    const PMConversion& pmConversion() const { return fPmConversion; }
-
-    static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> fp,
-                                                     PMConversion pmConversion) {
-        if (!fp) {
-            return nullptr;
-        }
-        std::unique_ptr<GrFragmentProcessor> ccFP(new GrConfigConversionEffect(pmConversion));
-        std::unique_ptr<GrFragmentProcessor> fpPipeline[] = {std::move(fp), std::move(ccFP)};
-        return GrFragmentProcessor::RunInSeries(fpPipeline, 2);
-    }
-    GrConfigConversionEffect(const GrConfigConversionEffect& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "ConfigConversionEffect"; }
-
-private:
-    GrConfigConversionEffect(PMConversion pmConversion)
-            : INHERITED(kGrConfigConversionEffect_ClassID, kNone_OptimizationFlags)
-            , fPmConversion(pmConversion) {}
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    PMConversion fPmConversion;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/effects/GrConstColorProcessor.cpp b/src/gpu/effects/GrConstColorProcessor.cpp
deleted file mode 100644
index 82ef77a..0000000
--- a/src/gpu/effects/GrConstColorProcessor.cpp
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrConstColorProcessor.fp; do not modify.
- **************************************************************************************************/
-#include "GrConstColorProcessor.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLConstColorProcessor : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLConstColorProcessor() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrConstColorProcessor& _outer = args.fFp.cast<GrConstColorProcessor>();
-        (void)_outer;
-        auto color = _outer.color();
-        (void)color;
-        auto mode = _outer.mode();
-        (void)mode;
-        fColorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType,
-                                                     kDefault_GrSLPrecision, "color");
-        fragBuilder->codeAppendf(
-                "@switch (%d) {\n    case 0:\n        %s = %s;\n        break;\n    case 1:\n      "
-                "  %s = %s * %s;\n        break;\n    case 2:\n        %s = %s.w * %s;\n        "
-                "break;\n}\n",
-                (int)_outer.mode(), args.fOutputColor,
-                args.fUniformHandler->getUniformCStr(fColorVar), args.fOutputColor,
-                args.fInputColor, args.fUniformHandler->getUniformCStr(fColorVar),
-                args.fOutputColor, args.fInputColor,
-                args.fUniformHandler->getUniformCStr(fColorVar));
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {
-        const GrConstColorProcessor& _outer = _proc.cast<GrConstColorProcessor>();
-        {
-            const SkPMColor4f& colorValue = _outer.color();
-            if (fColorPrev != colorValue) {
-                fColorPrev = colorValue;
-                pdman.set4fv(fColorVar, 1, colorValue.vec());
-            }
-        }
-    }
-    SkPMColor4f fColorPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN};
-    UniformHandle fColorVar;
-};
-GrGLSLFragmentProcessor* GrConstColorProcessor::onCreateGLSLInstance() const {
-    return new GrGLSLConstColorProcessor();
-}
-void GrConstColorProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                                  GrProcessorKeyBuilder* b) const {
-    b->add32((int32_t)fMode);
-}
-bool GrConstColorProcessor::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrConstColorProcessor& that = other.cast<GrConstColorProcessor>();
-    (void)that;
-    if (fColor != that.fColor) return false;
-    if (fMode != that.fMode) return false;
-    return true;
-}
-GrConstColorProcessor::GrConstColorProcessor(const GrConstColorProcessor& src)
-        : INHERITED(kGrConstColorProcessor_ClassID, src.optimizationFlags())
-        , fColor(src.fColor)
-        , fMode(src.fMode) {}
-std::unique_ptr<GrFragmentProcessor> GrConstColorProcessor::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrConstColorProcessor(*this));
-}
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrConstColorProcessor);
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrConstColorProcessor::TestCreate(GrProcessorTestData* d) {
-    SkPMColor4f color;
-    int colorPicker = d->fRandom->nextULessThan(3);
-    switch (colorPicker) {
-        case 0: {
-            uint32_t a = d->fRandom->nextULessThan(0x100);
-            uint32_t r = d->fRandom->nextULessThan(a + 1);
-            uint32_t g = d->fRandom->nextULessThan(a + 1);
-            uint32_t b = d->fRandom->nextULessThan(a + 1);
-            color = SkPMColor4f::FromBytes_RGBA(GrColorPackRGBA(r, g, b, a));
-            break;
-        }
-        case 1:
-            color = SK_PMColor4fTRANSPARENT;
-            break;
-        case 2:
-            uint32_t c = d->fRandom->nextULessThan(0x100);
-            color = SkPMColor4f::FromBytes_RGBA(c | (c << 8) | (c << 16) | (c << 24));
-            break;
-    }
-    InputMode mode = static_cast<InputMode>(d->fRandom->nextULessThan(kInputModeCnt));
-    return GrConstColorProcessor::Make(color, mode);
-}
-#endif
diff --git a/src/gpu/effects/GrConstColorProcessor.fp b/src/gpu/effects/GrConstColorProcessor.fp
index 1a301ce..7390919 100644
--- a/src/gpu/effects/GrConstColorProcessor.fp
+++ b/src/gpu/effects/GrConstColorProcessor.fp
@@ -49,13 +49,13 @@
     }
 
     SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override {
-        switch (fMode) {
+        switch (mode) {
             case InputMode::kIgnore:
-                return fColor;
+                return color;
             case InputMode::kModulateA:
-                return fColor * input.fA;
+                return color * input.fA;
             case InputMode::kModulateRGBA:
-                return fColor * input;
+                return color * input;
         }
         SK_ABORT("Unexpected mode");
         return SK_PMColor4fTRANSPARENT;
diff --git a/src/gpu/effects/GrConstColorProcessor.h b/src/gpu/effects/GrConstColorProcessor.h
deleted file mode 100644
index ca166f2..0000000
--- a/src/gpu/effects/GrConstColorProcessor.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrConstColorProcessor.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrConstColorProcessor_DEFINED
-#define GrConstColorProcessor_DEFINED
-#include "SkTypes.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrConstColorProcessor : public GrFragmentProcessor {
-public:
-    enum class InputMode { kIgnore = 0, kLast = 2, kModulateA = 2, kModulateRGBA = 1 };
-
-    static const int kInputModeCnt = (int)InputMode::kLast + 1;
-
-    static OptimizationFlags OptFlags(const SkPMColor4f& color, InputMode mode) {
-        OptimizationFlags flags = kConstantOutputForConstantInput_OptimizationFlag;
-        if (mode != InputMode::kIgnore) {
-            flags |= kCompatibleWithCoverageAsAlpha_OptimizationFlag;
-        }
-        if (color.isOpaque()) {
-            flags |= kPreservesOpaqueInput_OptimizationFlag;
-        }
-        return flags;
-    }
-
-    SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override {
-        switch (fMode) {
-            case InputMode::kIgnore:
-                return fColor;
-            case InputMode::kModulateA:
-                return fColor * input.fA;
-            case InputMode::kModulateRGBA:
-                return fColor * input;
-        }
-        SK_ABORT("Unexpected mode");
-        return SK_PMColor4fTRANSPARENT;
-    }
-    const SkPMColor4f& color() const { return fColor; }
-    const InputMode& mode() const { return fMode; }
-    static std::unique_ptr<GrFragmentProcessor> Make(SkPMColor4f color, InputMode mode) {
-        return std::unique_ptr<GrFragmentProcessor>(new GrConstColorProcessor(color, mode));
-    }
-    GrConstColorProcessor(const GrConstColorProcessor& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "ConstColorProcessor"; }
-
-private:
-    GrConstColorProcessor(SkPMColor4f color, InputMode mode)
-            : INHERITED(kGrConstColorProcessor_ClassID, (OptimizationFlags)OptFlags(color, mode))
-            , fColor(color)
-            , fMode(mode) {}
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    SkPMColor4f fColor;
-    InputMode fMode;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/effects/GrConvexPolyEffect.cpp b/src/gpu/effects/GrConvexPolyEffect.cpp
index 625427a..32c03af 100644
--- a/src/gpu/effects/GrConvexPolyEffect.cpp
+++ b/src/gpu/effects/GrConvexPolyEffect.cpp
@@ -7,8 +7,8 @@
 
 #include "GrConvexPolyEffect.h"
 #include "SkPathPriv.h"
-#include "effects/GrAARectEffect.h"
-#include "effects/GrConstColorProcessor.h"
+#include "effects/generated/GrAARectEffect.h"
+#include "effects/generated/GrConstColorProcessor.h"
 #include "glsl/GrGLSLFragmentProcessor.h"
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
 #include "glsl/GrGLSLProgramDataManager.h"
diff --git a/src/gpu/effects/GrCoverageSetOpXP.cpp b/src/gpu/effects/GrCoverageSetOpXP.cpp
index 8e1dbbb..f3f9133 100644
--- a/src/gpu/effects/GrCoverageSetOpXP.cpp
+++ b/src/gpu/effects/GrCoverageSetOpXP.cpp
@@ -211,7 +211,8 @@
         const GrProcessorAnalysisColor&,
         GrProcessorAnalysisCoverage,
         bool hasMixedSamples,
-        const GrCaps& caps) const {
+        const GrCaps& caps,
+        GrClampType) const {
     // We don't support inverting coverage with mixed samples. We don't expect to ever want this in
     // the future, however we could at some point make this work using an inverted coverage
     // modulation table. Note that an inverted table still won't work if there are coverage procs.
diff --git a/src/gpu/effects/GrCoverageSetOpXP.h b/src/gpu/effects/GrCoverageSetOpXP.h
index e8bc5a2..6c761d4 100644
--- a/src/gpu/effects/GrCoverageSetOpXP.h
+++ b/src/gpu/effects/GrCoverageSetOpXP.h
@@ -37,11 +37,13 @@
     sk_sp<const GrXferProcessor> makeXferProcessor(const GrProcessorAnalysisColor&,
                                                    GrProcessorAnalysisCoverage,
                                                    bool hasMixedSamples,
-                                                   const GrCaps&) const override;
+                                                   const GrCaps&,
+                                                   GrClampType) const override;
 
     AnalysisProperties analysisProperties(const GrProcessorAnalysisColor&,
                                           const GrProcessorAnalysisCoverage&,
-                                          const GrCaps&) const override {
+                                          const GrCaps&,
+                                          GrClampType) const override {
         return AnalysisProperties::kIgnoresInputColor;
     }
 
diff --git a/src/gpu/effects/GrCustomXfermode.cpp b/src/gpu/effects/GrCustomXfermode.cpp
index 86e7037..ce0d355 100644
--- a/src/gpu/effects/GrCustomXfermode.cpp
+++ b/src/gpu/effects/GrCustomXfermode.cpp
@@ -220,11 +220,13 @@
     sk_sp<const GrXferProcessor> makeXferProcessor(const GrProcessorAnalysisColor&,
                                                    GrProcessorAnalysisCoverage,
                                                    bool hasMixedSamples,
-                                                   const GrCaps&) const override;
+                                                   const GrCaps&,
+                                                   GrClampType) const override;
 
     AnalysisProperties analysisProperties(const GrProcessorAnalysisColor&,
                                           const GrProcessorAnalysisCoverage&,
-                                          const GrCaps&) const override;
+                                          const GrCaps&,
+                                          GrClampType) const override;
 
     GR_DECLARE_XP_FACTORY_TEST
 
@@ -244,7 +246,8 @@
         const GrProcessorAnalysisColor&,
         GrProcessorAnalysisCoverage coverage,
         bool hasMixedSamples,
-        const GrCaps& caps) const {
+        const GrCaps& caps,
+        GrClampType clampType) const {
     SkASSERT(GrCustomXfermode::IsSupportedMode(fMode));
     if (can_use_hw_blend_equation(fHWBlendEquation, coverage, caps)) {
         return sk_sp<GrXferProcessor>(new CustomXP(fMode, fHWBlendEquation));
@@ -254,7 +257,7 @@
 
 GrXPFactory::AnalysisProperties CustomXPFactory::analysisProperties(
         const GrProcessorAnalysisColor&, const GrProcessorAnalysisCoverage& coverage,
-        const GrCaps& caps) const {
+        const GrCaps& caps, GrClampType clampType) const {
     /*
       The general SVG blend equation is defined in the spec as follows:
 
@@ -352,13 +355,13 @@
     */
     if (can_use_hw_blend_equation(fHWBlendEquation, coverage, caps)) {
         if (caps.blendEquationSupport() == GrCaps::kAdvancedCoherent_BlendEquationSupport) {
-            return AnalysisProperties::kCompatibleWithAlphaAsCoverage;
+            return AnalysisProperties::kCompatibleWithCoverageAsAlpha;
         } else {
-            return AnalysisProperties::kCompatibleWithAlphaAsCoverage |
+            return AnalysisProperties::kCompatibleWithCoverageAsAlpha |
                    AnalysisProperties::kRequiresNonOverlappingDraws;
         }
     }
-    return AnalysisProperties::kCompatibleWithAlphaAsCoverage |
+    return AnalysisProperties::kCompatibleWithCoverageAsAlpha |
            AnalysisProperties::kReadsDstInShader;
 }
 
diff --git a/src/gpu/effects/GrDisableColorXP.cpp b/src/gpu/effects/GrDisableColorXP.cpp
index 3f9f71e..dfbf903 100644
--- a/src/gpu/effects/GrDisableColorXP.cpp
+++ b/src/gpu/effects/GrDisableColorXP.cpp
@@ -6,6 +6,7 @@
  */
 
 #include "effects/GrDisableColorXP.h"
+#include "GrShaderCaps.h"
 #include "GrPipeline.h"
 #include "GrProcessor.h"
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
@@ -50,11 +51,20 @@
 
 private:
     void emitOutputsForBlendState(const EmitArgs& args) override {
-        // This emit code should be empty. However, on the nexus 6 there is a driver bug where if
-        // you do not give gl_FragColor a value, the gl context is lost and we end up drawing
-        // nothing. So this fix just sets the gl_FragColor arbitrarily to 0.
-        GrGLSLXPFragmentBuilder* fragBuilder = args.fXPFragBuilder;
-        fragBuilder->codeAppendf("%s = half4(0);", args.fOutputPrimary);
+        if (args.fShaderCaps->mustWriteToFragColor()) {
+            // This emit code should be empty. However, on the nexus 6 there is a driver bug where
+            // if you do not give gl_FragColor a value, the gl context is lost and we end up drawing
+            // nothing. So this fix just sets the gl_FragColor arbitrarily to 0.
+            // https://bugs.chromium.org/p/chromium/issues/detail?id=445377
+            GrGLSLXPFragmentBuilder* fragBuilder = args.fXPFragBuilder;
+            fragBuilder->codeAppendf("%s = half4(0);", args.fOutputPrimary);
+        }
+    }
+
+    void emitOutputSwizzle(
+            GrGLSLXPFragmentBuilder*, const GrSwizzle&, const char*, const char*) const override {
+        // Don't write any swizzling. This makes sure the final shader does not output a color.
+        return;
     }
 
     void onSetData(const GrGLSLProgramDataManager&, const GrXferProcessor&) override {}
@@ -79,7 +89,8 @@
         const GrProcessorAnalysisColor&,
         GrProcessorAnalysisCoverage,
         bool hasMixedSamples,
-        const GrCaps& caps) const {
+        const GrCaps& caps,
+        GrClampType clampType) const {
     return sk_sp<const GrXferProcessor>(new DisableColorXP);
 }
 
diff --git a/src/gpu/effects/GrDisableColorXP.h b/src/gpu/effects/GrDisableColorXP.h
index d15f952..5e52264 100644
--- a/src/gpu/effects/GrDisableColorXP.h
+++ b/src/gpu/effects/GrDisableColorXP.h
@@ -30,15 +30,17 @@
 
     AnalysisProperties analysisProperties(const GrProcessorAnalysisColor&,
                                           const GrProcessorAnalysisCoverage&,
-                                          const GrCaps&) const override {
-        return AnalysisProperties::kCompatibleWithAlphaAsCoverage |
+                                          const GrCaps&,
+                                          GrClampType) const override {
+        return AnalysisProperties::kCompatibleWithCoverageAsAlpha |
                AnalysisProperties::kIgnoresInputColor;
     }
 
     sk_sp<const GrXferProcessor> makeXferProcessor(const GrProcessorAnalysisColor&,
                                                    GrProcessorAnalysisCoverage,
                                                    bool hasMixedSamples,
-                                                   const GrCaps&) const override;
+                                                   const GrCaps&,
+                                                   GrClampType) const override;
 
     GR_DECLARE_XP_FACTORY_TEST
 
diff --git a/src/gpu/effects/GrDistanceFieldGeoProc.cpp b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
index 883c28c..bb4179a 100644
--- a/src/gpu/effects/GrDistanceFieldGeoProc.cpp
+++ b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
@@ -41,7 +41,6 @@
         const char* atlasSizeInvName;
         fAtlasSizeInvUniform = uniformHandler->addUniform(kVertex_GrShaderFlag,
                                                           kFloat2_GrSLType,
-                                                          kHigh_GrSLPrecision,
                                                           "AtlasSizeInv",
                                                           &atlasSizeInvName);
 #ifdef SK_GAMMA_APPLY_TO_A8
@@ -341,7 +340,6 @@
         const char* atlasSizeInvName;
         fAtlasSizeInvUniform = uniformHandler->addUniform(kVertex_GrShaderFlag,
                                                           kFloat2_GrSLType,
-                                                          kHigh_GrSLPrecision,
                                                           "AtlasSizeInv",
                                                           &atlasSizeInvName);
 
@@ -630,7 +628,6 @@
         const char* atlasSizeInvName;
         fAtlasSizeInvUniform = uniformHandler->addUniform(kVertex_GrShaderFlag,
                                                           kFloat2_GrSLType,
-                                                          kHigh_GrSLPrecision,
                                                           "AtlasSizeInv",
                                                           &atlasSizeInvName);
 
diff --git a/src/gpu/effects/GrEllipseEffect.cpp b/src/gpu/effects/GrEllipseEffect.cpp
deleted file mode 100644
index 7372571..0000000
--- a/src/gpu/effects/GrEllipseEffect.cpp
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrEllipseEffect.fp; do not modify.
- **************************************************************************************************/
-#include "GrEllipseEffect.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLEllipseEffect : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLEllipseEffect() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrEllipseEffect& _outer = args.fFp.cast<GrEllipseEffect>();
-        (void)_outer;
-        auto edgeType = _outer.edgeType();
-        (void)edgeType;
-        auto center = _outer.center();
-        (void)center;
-        auto radii = _outer.radii();
-        (void)radii;
-        prevRadii = float2(-1.0);
-        useScale = !sk_Caps.floatIs32Bits;
-        fEllipseVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
-                                                       kDefault_GrSLPrecision, "ellipse");
-        if (useScale) {
-            fScaleVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat2_GrSLType,
-                                                         kDefault_GrSLPrecision, "scale");
-        }
-        fragBuilder->codeAppendf(
-                "float2 prevCenter;\nfloat2 prevRadii = float2(%f, %f);\nbool useScale = "
-                "%s;\nfloat2 d = sk_FragCoord.xy - %s.xy;\n@if (useScale) {\n    d *= "
-                "%s.y;\n}\nfloat2 Z = d * %s.zw;\nfloat implicit = dot(Z, d) - 1.0;\nfloat "
-                "grad_dot = 4.0 * dot(Z, Z);\ngrad_dot = max(grad_dot, 0.0001);\nfloat approx_dist "
-                "= implicit * inversesqrt(grad_dot);\n@if (useScale) {\n    approx_dist *= "
-                "%s.x;\n}\nhalf alpha;\n@switch (%d) {\n    case 0:\n        alpha = approx_dist > "
-                "0.0 ? 0.0 : 1.0;\n        break;\n    case 1:\n        alph",
-                prevRadii.fX, prevRadii.fY, (useScale ? "true" : "false"),
-                args.fUniformHandler->getUniformCStr(fEllipseVar),
-                fScaleVar.isValid() ? args.fUniformHandler->getUniformCStr(fScaleVar) : "float2(0)",
-                args.fUniformHandler->getUniformCStr(fEllipseVar),
-                fScaleVar.isValid() ? args.fUniformHandler->getUniformCStr(fScaleVar) : "float2(0)",
-                (int)_outer.edgeType());
-        fragBuilder->codeAppendf(
-                "a = clamp(0.5 - half(approx_dist), 0.0, 1.0);\n        break;\n    case 2:\n      "
-                "  alpha = approx_dist > 0.0 ? 1.0 : 0.0;\n        break;\n    case 3:\n        "
-                "alpha = clamp(0.5 + half(approx_dist), 0.0, 1.0);\n        break;\n    default:\n "
-                "       discard;\n}\n%s = %s * alpha;\n",
-                args.fOutputColor, args.fInputColor);
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {
-        const GrEllipseEffect& _outer = _proc.cast<GrEllipseEffect>();
-        auto edgeType = _outer.edgeType();
-        (void)edgeType;
-        auto center = _outer.center();
-        (void)center;
-        auto radii = _outer.radii();
-        (void)radii;
-        UniformHandle& ellipse = fEllipseVar;
-        (void)ellipse;
-        UniformHandle& scale = fScaleVar;
-        (void)scale;
-
-        if (radii != prevRadii || center != prevCenter) {
-            float invRXSqd;
-            float invRYSqd;
-            // If we're using a scale factor to work around precision issues, choose the larger
-            // radius as the scale factor. The inv radii need to be pre-adjusted by the scale
-            // factor.
-            if (scale.isValid()) {
-                if (radii.fX > radii.fY) {
-                    invRXSqd = 1.f;
-                    invRYSqd = (radii.fX * radii.fX) / (radii.fY * radii.fY);
-                    pdman.set2f(scale, radii.fX, 1.f / radii.fX);
-                } else {
-                    invRXSqd = (radii.fY * radii.fY) / (radii.fX * radii.fX);
-                    invRYSqd = 1.f;
-                    pdman.set2f(scale, radii.fY, 1.f / radii.fY);
-                }
-            } else {
-                invRXSqd = 1.f / (radii.fX * radii.fX);
-                invRYSqd = 1.f / (radii.fY * radii.fY);
-            }
-            pdman.set4f(ellipse, center.fX, center.fY, invRXSqd, invRYSqd);
-            prevCenter = center;
-            prevRadii = radii;
-        }
-    }
-    SkPoint prevCenter = float2(0);
-    SkPoint prevRadii = float2(0);
-    bool useScale = false;
-    UniformHandle fEllipseVar;
-    UniformHandle fScaleVar;
-};
-GrGLSLFragmentProcessor* GrEllipseEffect::onCreateGLSLInstance() const {
-    return new GrGLSLEllipseEffect();
-}
-void GrEllipseEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                            GrProcessorKeyBuilder* b) const {
-    b->add32((int32_t)fEdgeType);
-}
-bool GrEllipseEffect::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrEllipseEffect& that = other.cast<GrEllipseEffect>();
-    (void)that;
-    if (fEdgeType != that.fEdgeType) return false;
-    if (fCenter != that.fCenter) return false;
-    if (fRadii != that.fRadii) return false;
-    return true;
-}
-GrEllipseEffect::GrEllipseEffect(const GrEllipseEffect& src)
-        : INHERITED(kGrEllipseEffect_ClassID, src.optimizationFlags())
-        , fEdgeType(src.fEdgeType)
-        , fCenter(src.fCenter)
-        , fRadii(src.fRadii) {}
-std::unique_ptr<GrFragmentProcessor> GrEllipseEffect::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrEllipseEffect(*this));
-}
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrEllipseEffect);
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrEllipseEffect::TestCreate(GrProcessorTestData* testData) {
-    SkPoint center;
-    center.fX = testData->fRandom->nextRangeScalar(0.f, 1000.f);
-    center.fY = testData->fRandom->nextRangeScalar(0.f, 1000.f);
-    SkScalar rx = testData->fRandom->nextRangeF(0.f, 1000.f);
-    SkScalar ry = testData->fRandom->nextRangeF(0.f, 1000.f);
-    GrClipEdgeType et;
-    do {
-        et = (GrClipEdgeType)testData->fRandom->nextULessThan(kGrClipEdgeTypeCnt);
-    } while (GrClipEdgeType::kHairlineAA == et);
-    return GrEllipseEffect::Make(et, center, SkPoint::Make(rx, ry),
-                                 *testData->caps()->shaderCaps());
-}
-#endif
diff --git a/src/gpu/effects/GrEllipseEffect.fp b/src/gpu/effects/GrEllipseEffect.fp
index e50a451..256d0f4 100644
--- a/src/gpu/effects/GrEllipseEffect.fp
+++ b/src/gpu/effects/GrEllipseEffect.fp
@@ -19,8 +19,8 @@
 // The last two terms can underflow when float != fp32, so we also provide a workaround.
 uniform float4 ellipse;
 
-bool useScale = !sk_Caps.floatIs32Bits;
-layout(when=useScale) uniform float2 scale;
+bool medPrecision = !sk_Caps.floatIs32Bits;
+layout(when=medPrecision) uniform float2 scale;
 
 @make {
     static std::unique_ptr<GrFragmentProcessor> Make(GrClipEdgeType edgeType, SkPoint center,
@@ -29,6 +29,14 @@
         if (!caps.floatIs32Bits() && (radii.fX < 0.5f || radii.fY < 0.5f)) {
             return nullptr;
         }
+        // Very narrow ellipses produce bad results on devices without full float
+        if (!caps.floatIs32Bits() && (radii.fX > 255*radii.fY || radii.fY > 255*radii.fX)) {
+            return nullptr;
+        }
+        // Very large ellipses produce bad results on devices without full float
+        if (!caps.floatIs32Bits() && (radii.fX > 16384 || radii.fY > 16384)) {
+            return nullptr;
+        }
         return std::unique_ptr<GrFragmentProcessor>(new GrEllipseEffect(edgeType, center, radii));
     }
 }
@@ -39,17 +47,16 @@
     if (radii != prevRadii || center != prevCenter) {
         float invRXSqd;
         float invRYSqd;
-        // If we're using a scale factor to work around precision issues, choose the larger radius
-        // as the scale factor. The inv radii need to be pre-adjusted by the scale factor.
+        // If we're using a scale factor to work around precision issues, choose the larger
+        // radius as the scale factor. The inv radii need to be pre-adjusted by the scale
+        // factor.
         if (scale.isValid()) {
             if (radii.fX > radii.fY) {
                 invRXSqd = 1.f;
-                invRYSqd = (radii.fX * radii.fX) /
-                           (radii.fY * radii.fY);
+                invRYSqd = (radii.fX * radii.fX) / (radii.fY * radii.fY);
                 pdman.set2f(scale, radii.fX, 1.f / radii.fX);
             } else {
-                invRXSqd = (radii.fY * radii.fY) /
-                           (radii.fX * radii.fX);
+                invRXSqd = (radii.fY * radii.fY) / (radii.fX * radii.fX);
                 invRYSqd = 1.f;
                 pdman.set2f(scale, radii.fY, 1.f / radii.fY);
             }
@@ -67,10 +74,10 @@
     // d is the offset to the ellipse center
     float2 d = sk_FragCoord.xy - ellipse.xy;
     // If we're on a device with a "real" mediump then we'll do the distance computation in a space
-    // that is normalized by the larger radius. The scale uniform will be scale, 1/scale. The
-    // inverse squared radii uniform values are already in this normalized space. The center is
-    // not.
-    @if (useScale) {
+    // that is normalized by the larger radius or 128, whichever is smaller. The scale uniform will
+    // be scale, 1/scale. The inverse squared radii uniform values are already in this normalized space.
+    // The center is not.
+    @if (medPrecision) {
         d *= scale.y;
     }
     float2 Z = d * ellipse.zw;
@@ -79,9 +86,13 @@
     // grad_dot is the squared length of the gradient of the implicit.
     float grad_dot = 4 * dot(Z, Z);
     // Avoid calling inversesqrt on zero.
-    grad_dot = max(grad_dot, 1e-4);
+    @if (medPrecision) {
+        grad_dot = max(grad_dot, 6.1036e-5);
+    } else {
+        grad_dot = max(grad_dot, 1.1755e-38);
+    }
     float approx_dist = implicit * inversesqrt(grad_dot);
-    @if (useScale) {
+    @if (medPrecision) {
         approx_dist *= scale.x;
     }
 
diff --git a/src/gpu/effects/GrEllipseEffect.h b/src/gpu/effects/GrEllipseEffect.h
deleted file mode 100644
index ca1574d..0000000
--- a/src/gpu/effects/GrEllipseEffect.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrEllipseEffect.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrEllipseEffect_DEFINED
-#define GrEllipseEffect_DEFINED
-#include "SkTypes.h"
-
-#include "GrShaderCaps.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrEllipseEffect : public GrFragmentProcessor {
-public:
-    const GrClipEdgeType& edgeType() const { return fEdgeType; }
-    const SkPoint& center() const { return fCenter; }
-    const SkPoint& radii() const { return fRadii; }
-
-    static std::unique_ptr<GrFragmentProcessor> Make(GrClipEdgeType edgeType, SkPoint center,
-                                                     SkPoint radii, const GrShaderCaps& caps) {
-        // Small radii produce bad results on devices without full float.
-        if (!caps.floatIs32Bits() && (radii.fX < 0.5f || radii.fY < 0.5f)) {
-            return nullptr;
-        }
-        return std::unique_ptr<GrFragmentProcessor>(new GrEllipseEffect(edgeType, center, radii));
-    }
-    GrEllipseEffect(const GrEllipseEffect& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "EllipseEffect"; }
-
-private:
-    GrEllipseEffect(GrClipEdgeType edgeType, SkPoint center, SkPoint radii)
-            : INHERITED(kGrEllipseEffect_ClassID,
-                        (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag)
-            , fEdgeType(edgeType)
-            , fCenter(center)
-            , fRadii(radii) {}
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    GrClipEdgeType fEdgeType;
-    SkPoint fCenter;
-    SkPoint fRadii;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/effects/GrLumaColorFilterEffect.cpp b/src/gpu/effects/GrLumaColorFilterEffect.cpp
deleted file mode 100644
index b21dbc6..0000000
--- a/src/gpu/effects/GrLumaColorFilterEffect.cpp
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrLumaColorFilterEffect.fp; do not modify.
- **************************************************************************************************/
-#include "GrLumaColorFilterEffect.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLLumaColorFilterEffect : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLLumaColorFilterEffect() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrLumaColorFilterEffect& _outer = args.fFp.cast<GrLumaColorFilterEffect>();
-        (void)_outer;
-        fragBuilder->codeAppendf(
-                "\nhalf luma = dot(half3(0.21260000000000001, 0.71519999999999995, 0.0722), "
-                "%s.xyz);\n%s = half4(0.0, 0.0, 0.0, luma);\n",
-                args.fInputColor, args.fOutputColor);
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {}
-};
-GrGLSLFragmentProcessor* GrLumaColorFilterEffect::onCreateGLSLInstance() const {
-    return new GrGLSLLumaColorFilterEffect();
-}
-void GrLumaColorFilterEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                                    GrProcessorKeyBuilder* b) const {}
-bool GrLumaColorFilterEffect::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrLumaColorFilterEffect& that = other.cast<GrLumaColorFilterEffect>();
-    (void)that;
-    return true;
-}
-GrLumaColorFilterEffect::GrLumaColorFilterEffect(const GrLumaColorFilterEffect& src)
-        : INHERITED(kGrLumaColorFilterEffect_ClassID, src.optimizationFlags()) {}
-std::unique_ptr<GrFragmentProcessor> GrLumaColorFilterEffect::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrLumaColorFilterEffect(*this));
-}
diff --git a/src/gpu/effects/GrLumaColorFilterEffect.fp b/src/gpu/effects/GrLumaColorFilterEffect.fp
index f53139e..4082d21 100644
--- a/src/gpu/effects/GrLumaColorFilterEffect.fp
+++ b/src/gpu/effects/GrLumaColorFilterEffect.fp
@@ -12,12 +12,12 @@
         float luma = SK_ITU_BT709_LUM_COEFF_R * input.fR +
                      SK_ITU_BT709_LUM_COEFF_G * input.fG +
                      SK_ITU_BT709_LUM_COEFF_B * input.fB;
-        return { 0, 0, 0, luma };
+        return { 0, 0, 0, SkTPin(luma, 0.0f, 1.0f) };
     }
 }
 
 void main() {
     const half3 SK_ITU_BT709_LUM_COEFF = half3(0.2126, 0.7152, 0.0722);
-    half luma = dot(SK_ITU_BT709_LUM_COEFF, sk_InColor.rgb);
+    half luma = saturate(dot(SK_ITU_BT709_LUM_COEFF, sk_InColor.rgb));
     sk_OutColor = half4(0, 0, 0, luma);
 }
\ No newline at end of file
diff --git a/src/gpu/effects/GrLumaColorFilterEffect.h b/src/gpu/effects/GrLumaColorFilterEffect.h
deleted file mode 100644
index bc4b8c4..0000000
--- a/src/gpu/effects/GrLumaColorFilterEffect.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrLumaColorFilterEffect.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrLumaColorFilterEffect_DEFINED
-#define GrLumaColorFilterEffect_DEFINED
-#include "SkTypes.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrLumaColorFilterEffect : public GrFragmentProcessor {
-public:
-#include "SkColorData.h"
-
-    SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override {
-        float luma = SK_ITU_BT709_LUM_COEFF_R * input.fR + SK_ITU_BT709_LUM_COEFF_G * input.fG +
-                     SK_ITU_BT709_LUM_COEFF_B * input.fB;
-        return {0, 0, 0, luma};
-    }
-    static std::unique_ptr<GrFragmentProcessor> Make() {
-        return std::unique_ptr<GrFragmentProcessor>(new GrLumaColorFilterEffect());
-    }
-    GrLumaColorFilterEffect(const GrLumaColorFilterEffect& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "LumaColorFilterEffect"; }
-
-private:
-    GrLumaColorFilterEffect()
-            : INHERITED(kGrLumaColorFilterEffect_ClassID, kNone_OptimizationFlags) {}
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/effects/GrMagnifierEffect.cpp b/src/gpu/effects/GrMagnifierEffect.cpp
deleted file mode 100644
index 8042613..0000000
--- a/src/gpu/effects/GrMagnifierEffect.cpp
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrMagnifierEffect.fp; do not modify.
- **************************************************************************************************/
-#include "GrMagnifierEffect.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLMagnifierEffect : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLMagnifierEffect() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrMagnifierEffect& _outer = args.fFp.cast<GrMagnifierEffect>();
-        (void)_outer;
-        auto bounds = _outer.bounds();
-        (void)bounds;
-        auto srcRect = _outer.srcRect();
-        (void)srcRect;
-        auto xInvZoom = _outer.xInvZoom();
-        (void)xInvZoom;
-        auto yInvZoom = _outer.yInvZoom();
-        (void)yInvZoom;
-        auto xInvInset = _outer.xInvInset();
-        (void)xInvInset;
-        auto yInvInset = _outer.yInvInset();
-        (void)yInvInset;
-        fBoundsUniformVar = args.fUniformHandler->addUniform(
-                kFragment_GrShaderFlag, kFloat4_GrSLType, kDefault_GrSLPrecision, "boundsUniform");
-        fXInvZoomVar = args.fUniformHandler->addUniform(
-                kFragment_GrShaderFlag, kFloat_GrSLType, kDefault_GrSLPrecision, "xInvZoom");
-        fYInvZoomVar = args.fUniformHandler->addUniform(
-                kFragment_GrShaderFlag, kFloat_GrSLType, kDefault_GrSLPrecision, "yInvZoom");
-        fXInvInsetVar = args.fUniformHandler->addUniform(
-                kFragment_GrShaderFlag, kFloat_GrSLType, kDefault_GrSLPrecision, "xInvInset");
-        fYInvInsetVar = args.fUniformHandler->addUniform(
-                kFragment_GrShaderFlag, kFloat_GrSLType, kDefault_GrSLPrecision, "yInvInset");
-        fOffsetVar = args.fUniformHandler->addUniform(
-                kFragment_GrShaderFlag, kHalf2_GrSLType, kDefault_GrSLPrecision, "offset");
-        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
-        fragBuilder->codeAppendf(
-                "float2 coord = %s;\nfloat2 zoom_coord = float2(%s) + coord * float2(%s, "
-                "%s);\nfloat2 delta = (coord - %s.xy) * %s.zw;\ndelta = min(delta, "
-                "float2(half2(1.0, 1.0)) - delta);\ndelta *= float2(%s, %s);\nfloat weight = "
-                "0.0;\nif (delta.x < 2.0 && delta.y < 2.0) {\n    delta = float2(half2(2.0, 2.0)) "
-                "- delta;\n    float dist = length(delta);\n    dist = max(2.0 - dist, 0.0);\n    "
-                "weight = min(dist * dist, 1.0);\n} else {\n    float2 delta_squared = delta * "
-                "delta;\n    weight = min(min(delta_squared.x, delta_square",
-                sk_TransformedCoords2D_0.c_str(),
-                args.fUniformHandler->getUniformCStr(fOffsetVar),
-                args.fUniformHandler->getUniformCStr(fXInvZoomVar),
-                args.fUniformHandler->getUniformCStr(fYInvZoomVar),
-                args.fUniformHandler->getUniformCStr(fBoundsUniformVar),
-                args.fUniformHandler->getUniformCStr(fBoundsUniformVar),
-                args.fUniformHandler->getUniformCStr(fXInvInsetVar),
-                args.fUniformHandler->getUniformCStr(fYInvInsetVar));
-        fragBuilder->codeAppendf(
-                "d.y), 1.0);\n}\n%s = texture(%s, mix(coord, zoom_coord, weight)).%s;\n",
-                args.fOutputColor,
-                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(),
-                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str());
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {
-        const GrMagnifierEffect& _outer = _proc.cast<GrMagnifierEffect>();
-        {
-            pdman.set1f(fXInvZoomVar, (_outer.xInvZoom()));
-            pdman.set1f(fYInvZoomVar, (_outer.yInvZoom()));
-            pdman.set1f(fXInvInsetVar, (_outer.xInvInset()));
-            pdman.set1f(fYInvInsetVar, (_outer.yInvInset()));
-        }
-        GrSurfaceProxy& srcProxy = *_outer.textureSampler(0).proxy();
-        GrTexture& src = *srcProxy.peekTexture();
-        (void)src;
-        auto bounds = _outer.bounds();
-        (void)bounds;
-        UniformHandle& boundsUniform = fBoundsUniformVar;
-        (void)boundsUniform;
-        auto srcRect = _outer.srcRect();
-        (void)srcRect;
-        UniformHandle& xInvZoom = fXInvZoomVar;
-        (void)xInvZoom;
-        UniformHandle& yInvZoom = fYInvZoomVar;
-        (void)yInvZoom;
-        UniformHandle& xInvInset = fXInvInsetVar;
-        (void)xInvInset;
-        UniformHandle& yInvInset = fYInvInsetVar;
-        (void)yInvInset;
-        UniformHandle& offset = fOffsetVar;
-        (void)offset;
-
-        SkScalar invW = 1.0f / src.width();
-        SkScalar invH = 1.0f / src.height();
-
-        {
-            SkScalar y = srcRect.y() * invH;
-            if (srcProxy.origin() != kTopLeft_GrSurfaceOrigin) {
-                y = 1.0f - (srcRect.height() / bounds.height()) - y;
-            }
-
-            pdman.set2f(offset, srcRect.x() * invW, y);
-        }
-
-        {
-            SkScalar y = bounds.y() * invH;
-            if (srcProxy.origin() != kTopLeft_GrSurfaceOrigin) {
-                y = 1.0f - bounds.height() * invH;
-            }
-
-            pdman.set4f(boundsUniform,
-                        bounds.x() * invW,
-                        y,
-                        SkIntToScalar(src.width()) / bounds.width(),
-                        SkIntToScalar(src.height()) / bounds.height());
-        }
-    }
-    UniformHandle fBoundsUniformVar;
-    UniformHandle fOffsetVar;
-    UniformHandle fXInvZoomVar;
-    UniformHandle fYInvZoomVar;
-    UniformHandle fXInvInsetVar;
-    UniformHandle fYInvInsetVar;
-};
-GrGLSLFragmentProcessor* GrMagnifierEffect::onCreateGLSLInstance() const {
-    return new GrGLSLMagnifierEffect();
-}
-void GrMagnifierEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                              GrProcessorKeyBuilder* b) const {}
-bool GrMagnifierEffect::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrMagnifierEffect& that = other.cast<GrMagnifierEffect>();
-    (void)that;
-    if (fSrc != that.fSrc) return false;
-    if (fBounds != that.fBounds) return false;
-    if (fSrcRect != that.fSrcRect) return false;
-    if (fXInvZoom != that.fXInvZoom) return false;
-    if (fYInvZoom != that.fYInvZoom) return false;
-    if (fXInvInset != that.fXInvInset) return false;
-    if (fYInvInset != that.fYInvInset) return false;
-    return true;
-}
-GrMagnifierEffect::GrMagnifierEffect(const GrMagnifierEffect& src)
-        : INHERITED(kGrMagnifierEffect_ClassID, src.optimizationFlags())
-        , fSrc(src.fSrc)
-        , fBounds(src.fBounds)
-        , fSrcRect(src.fSrcRect)
-        , fXInvZoom(src.fXInvZoom)
-        , fYInvZoom(src.fYInvZoom)
-        , fXInvInset(src.fXInvInset)
-        , fYInvInset(src.fYInvInset)
-        , fSrcCoordTransform(src.fSrcCoordTransform) {
-    this->setTextureSamplerCnt(1);
-    this->addCoordTransform(&fSrcCoordTransform);
-}
-std::unique_ptr<GrFragmentProcessor> GrMagnifierEffect::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrMagnifierEffect(*this));
-}
-const GrFragmentProcessor::TextureSampler& GrMagnifierEffect::onTextureSampler(int index) const {
-    return IthTextureSampler(index, fSrc);
-}
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrMagnifierEffect);
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrMagnifierEffect::TestCreate(GrProcessorTestData* d) {
-    sk_sp<GrTextureProxy> proxy = d->textureProxy(0);
-    const int kMaxWidth = 200;
-    const int kMaxHeight = 200;
-    const SkScalar kMaxInset = 20.0f;
-    uint32_t width = d->fRandom->nextULessThan(kMaxWidth);
-    uint32_t height = d->fRandom->nextULessThan(kMaxHeight);
-    SkScalar inset = d->fRandom->nextRangeScalar(1.0f, kMaxInset);
-
-    SkIRect bounds = SkIRect::MakeWH(SkIntToScalar(kMaxWidth), SkIntToScalar(kMaxHeight));
-    SkRect srcRect = SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height));
-
-    auto effect = GrMagnifierEffect::Make(std::move(proxy),
-                                          bounds,
-                                          srcRect,
-                                          srcRect.width() / bounds.width(),
-                                          srcRect.height() / bounds.height(),
-                                          bounds.width() / inset,
-                                          bounds.height() / inset);
-    SkASSERT(effect);
-    return effect;
-}
-#endif
diff --git a/src/gpu/effects/GrMagnifierEffect.h b/src/gpu/effects/GrMagnifierEffect.h
deleted file mode 100644
index c0c44d7..0000000
--- a/src/gpu/effects/GrMagnifierEffect.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrMagnifierEffect.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrMagnifierEffect_DEFINED
-#define GrMagnifierEffect_DEFINED
-#include "SkTypes.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrMagnifierEffect : public GrFragmentProcessor {
-public:
-    const SkIRect& bounds() const { return fBounds; }
-    const SkRect& srcRect() const { return fSrcRect; }
-    float xInvZoom() const { return fXInvZoom; }
-    float yInvZoom() const { return fYInvZoom; }
-    float xInvInset() const { return fXInvInset; }
-    float yInvInset() const { return fYInvInset; }
-    static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy> src, SkIRect bounds,
-                                                     SkRect srcRect, float xInvZoom, float yInvZoom,
-                                                     float xInvInset, float yInvInset) {
-        return std::unique_ptr<GrFragmentProcessor>(new GrMagnifierEffect(
-                src, bounds, srcRect, xInvZoom, yInvZoom, xInvInset, yInvInset));
-    }
-    GrMagnifierEffect(const GrMagnifierEffect& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "MagnifierEffect"; }
-
-private:
-    GrMagnifierEffect(sk_sp<GrTextureProxy> src, SkIRect bounds, SkRect srcRect, float xInvZoom,
-                      float yInvZoom, float xInvInset, float yInvInset)
-            : INHERITED(kGrMagnifierEffect_ClassID, kNone_OptimizationFlags)
-            , fSrc(std::move(src))
-            , fBounds(bounds)
-            , fSrcRect(srcRect)
-            , fXInvZoom(xInvZoom)
-            , fYInvZoom(yInvZoom)
-            , fXInvInset(xInvInset)
-            , fYInvInset(yInvInset)
-            , fSrcCoordTransform(SkMatrix::I(), fSrc.proxy()) {
-        this->setTextureSamplerCnt(1);
-        this->addCoordTransform(&fSrcCoordTransform);
-    }
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    const TextureSampler& onTextureSampler(int) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    TextureSampler fSrc;
-    SkIRect fBounds;
-    SkRect fSrcRect;
-    float fXInvZoom;
-    float fYInvZoom;
-    float fXInvInset;
-    float fYInvInset;
-    GrCoordTransform fSrcCoordTransform;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/effects/GrMixerEffect.fp b/src/gpu/effects/GrMixerEffect.fp
new file mode 100644
index 0000000..cb0c979
--- /dev/null
+++ b/src/gpu/effects/GrMixerEffect.fp
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+// Mixes the output of two FPs.
+
+in fragmentProcessor  fp0;
+in fragmentProcessor? fp1;
+in uniform half      weight;
+
+@class {
+
+    static OptimizationFlags OptFlags(const std::unique_ptr<GrFragmentProcessor>& fp0,
+                                      const std::unique_ptr<GrFragmentProcessor>& fp1) {
+        auto get_flags = [](const std::unique_ptr<GrFragmentProcessor>& fp) {
+            auto flags = kNone_OptimizationFlags;
+
+            if (fp->compatibleWithCoverageAsAlpha()) {
+                flags |= kCompatibleWithCoverageAsAlpha_OptimizationFlag;
+            }
+
+            if (fp->preservesOpaqueInput()) {
+                flags |= kPreservesOpaqueInput_OptimizationFlag;
+            }
+
+            if (fp->hasConstantOutputForConstantInput()) {
+                flags |= kConstantOutputForConstantInput_OptimizationFlag;
+            }
+
+            return flags;
+        };
+
+        const auto fp0_flags = get_flags(fp0);
+
+        return fp1 ? (fp0_flags & get_flags(fp1)) : fp0_flags;
+    }
+
+    SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override {
+        const auto c0 = ConstantOutputForConstantInput(this->childProcessor(0), input),
+                   c1 = (this->numChildProcessors() > 1)
+                      ? ConstantOutputForConstantInput(this->childProcessor(1), input)
+                      : input;
+        return {
+            c0.fR + (c1.fR - c0.fR) * weight,
+            c0.fG + (c1.fG - c0.fG) * weight,
+            c0.fB + (c1.fB - c0.fB) * weight,
+            c0.fA + (c1.fA - c0.fA) * weight
+        };
+    }
+}
+
+@optimizationFlags { OptFlags(fp0, fp1) }
+
+void main() {
+    half4 in0 = process(fp0, sk_InColor);
+    half4 in1 = (fp1 != null) ? process(fp1, sk_InColor) : sk_InColor;
+
+    sk_OutColor = mix(in0, in1, weight);
+}
diff --git a/src/gpu/effects/GrOvalEffect.cpp b/src/gpu/effects/GrOvalEffect.cpp
index a475ec0..cb8e689 100644
--- a/src/gpu/effects/GrOvalEffect.cpp
+++ b/src/gpu/effects/GrOvalEffect.cpp
@@ -7,8 +7,8 @@
 
 #include "GrOvalEffect.h"
 
-#include "GrCircleEffect.h"
-#include "GrEllipseEffect.h"
+#include "generated/GrCircleEffect.h"
+#include "generated/GrEllipseEffect.h"
 #include "SkRect.h"
 
 std::unique_ptr<GrFragmentProcessor> GrOvalEffect::Make(GrClipEdgeType edgeType, const SkRect& oval,
diff --git a/src/gpu/effects/GrPorterDuffXferProcessor.cpp b/src/gpu/effects/GrPorterDuffXferProcessor.cpp
index b7a7e4d..5b72269 100644
--- a/src/gpu/effects/GrPorterDuffXferProcessor.cpp
+++ b/src/gpu/effects/GrPorterDuffXferProcessor.cpp
@@ -759,7 +759,7 @@
 
 sk_sp<const GrXferProcessor> GrPorterDuffXPFactory::makeXferProcessor(
         const GrProcessorAnalysisColor& color, GrProcessorAnalysisCoverage coverage,
-        bool hasMixedSamples, const GrCaps& caps) const {
+        bool hasMixedSamples, const GrCaps& caps, GrClampType clampType) const {
     BlendFormula blendFormula;
     bool isLCD = coverage == GrProcessorAnalysisCoverage::kLCD;
     if (isLCD) {
@@ -778,8 +778,11 @@
                                   hasMixedSamples, fBlendMode);
     }
 
+    // Skia always saturates after the kPlus blend mode, so it requires shader-based blending when
+    // pixels aren't guaranteed to automatically be normalized (i.e. any floating point config).
     if ((blendFormula.hasSecondaryOutput() && !caps.shaderCaps()->dualSourceBlendingSupport()) ||
-        (isLCD && (SkBlendMode::kSrcOver != fBlendMode /*|| !color.isOpaque()*/))) {
+        (isLCD && (SkBlendMode::kSrcOver != fBlendMode /*|| !color.isOpaque()*/)) ||
+        (GrClampType::kAuto != clampType && SkBlendMode::kPlus == fBlendMode)) {
         return sk_sp<const GrXferProcessor>(new ShaderPDXferProcessor(hasMixedSamples, fBlendMode,
                                                                       coverage));
     }
@@ -788,7 +791,7 @@
 
 static inline GrXPFactory::AnalysisProperties analysis_properties(
         const GrProcessorAnalysisColor& color, const GrProcessorAnalysisCoverage& coverage,
-        const GrCaps& caps, SkBlendMode mode) {
+        const GrCaps& caps, GrClampType clampType, SkBlendMode mode) {
     using AnalysisProperties = GrXPFactory::AnalysisProperties;
     AnalysisProperties props = AnalysisProperties::kNone;
     bool hasCoverage = GrProcessorAnalysisCoverage::kNone != coverage;
@@ -801,7 +804,7 @@
     }
 
     if (formula.canTweakAlphaForCoverage() && !isLCD) {
-        props |= AnalysisProperties::kCompatibleWithAlphaAsCoverage;
+        props |= AnalysisProperties::kCompatibleWithCoverageAsAlpha;
     }
 
     if (isLCD) {
@@ -835,6 +838,10 @@
         }
     }
 
+    if (GrClampType::kAuto != clampType && SkBlendMode::kPlus == mode) {
+        props |= AnalysisProperties::kReadsDstInShader;
+    }
+
     if (!formula.modifiesDst() || !formula.usesInputColor()) {
         props |= AnalysisProperties::kIgnoresInputColor;
     }
@@ -844,8 +851,9 @@
 GrXPFactory::AnalysisProperties GrPorterDuffXPFactory::analysisProperties(
         const GrProcessorAnalysisColor& color,
         const GrProcessorAnalysisCoverage& coverage,
-        const GrCaps& caps) const {
-    return analysis_properties(color, coverage, caps, fBlendMode);
+        const GrCaps& caps,
+        GrClampType clampType) const {
+    return analysis_properties(color, coverage, caps, clampType, fBlendMode);
 }
 
 GR_DEFINE_XP_FACTORY_TEST(GrPorterDuffXPFactory);
@@ -932,6 +940,7 @@
 GrXPFactory::AnalysisProperties GrPorterDuffXPFactory::SrcOverAnalysisProperties(
         const GrProcessorAnalysisColor& color,
         const GrProcessorAnalysisCoverage& coverage,
-        const GrCaps& caps) {
-    return analysis_properties(color, coverage, caps, SkBlendMode::kSrcOver);
+        const GrCaps& caps,
+        GrClampType clampType) {
+    return analysis_properties(color, coverage, caps, clampType, SkBlendMode::kSrcOver);
 }
diff --git a/src/gpu/effects/GrPorterDuffXferProcessor.h b/src/gpu/effects/GrPorterDuffXferProcessor.h
index ebe0212..1c39c3a 100644
--- a/src/gpu/effects/GrPorterDuffXferProcessor.h
+++ b/src/gpu/effects/GrPorterDuffXferProcessor.h
@@ -41,7 +41,8 @@
 
     static AnalysisProperties SrcOverAnalysisProperties(const GrProcessorAnalysisColor&,
                                                         const GrProcessorAnalysisCoverage&,
-                                                        const GrCaps&);
+                                                        const GrCaps&,
+                                                        GrClampType);
 
 private:
     constexpr GrPorterDuffXPFactory(SkBlendMode);
@@ -49,11 +50,13 @@
     sk_sp<const GrXferProcessor> makeXferProcessor(const GrProcessorAnalysisColor&,
                                                    GrProcessorAnalysisCoverage,
                                                    bool hasMixedSamples,
-                                                   const GrCaps&) const override;
+                                                   const GrCaps&,
+                                                   GrClampType) const override;
 
     AnalysisProperties analysisProperties(const GrProcessorAnalysisColor&,
                                           const GrProcessorAnalysisCoverage&,
-                                          const GrCaps&) const override;
+                                          const GrCaps&,
+                                          GrClampType) const override;
 
     GR_DECLARE_XP_FACTORY_TEST
     static void TestGetXPOutputTypes(const GrXferProcessor*, int* outPrimary, int* outSecondary);
diff --git a/src/gpu/effects/GrRRectBlurEffect.cpp b/src/gpu/effects/GrRRectBlurEffect.cpp
deleted file mode 100644
index e84fae2..0000000
--- a/src/gpu/effects/GrRRectBlurEffect.cpp
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrRRectBlurEffect.fp; do not modify.
- **************************************************************************************************/
-#include "GrRRectBlurEffect.h"
-
-std::unique_ptr<GrFragmentProcessor> GrRRectBlurEffect::Make(GrContext* context, float sigma,
-                                                             float xformedSigma,
-                                                             const SkRRect& srcRRect,
-                                                             const SkRRect& devRRect) {
-    SkASSERT(!SkRRectPriv::IsCircle(devRRect) &&
-             !devRRect.isRect());  // Should've been caught up-stream
-
-    // TODO: loosen this up
-    if (!SkRRectPriv::IsSimpleCircular(devRRect)) {
-        return nullptr;
-    }
-
-    // Make sure we can successfully ninepatch this rrect -- the blur sigma has to be
-    // sufficiently small relative to both the size of the corner radius and the
-    // width (and height) of the rrect.
-    SkRRect rrectToDraw;
-    SkISize size;
-    SkScalar ignored[kSkBlurRRectMaxDivisions];
-    int ignoredSize;
-    uint32_t ignored32;
-
-    bool ninePatchable = SkComputeBlurredRRectParams(
-            srcRRect, devRRect, SkRect::MakeEmpty(), sigma, xformedSigma, &rrectToDraw, &size,
-            ignored, ignored, ignored, ignored, &ignoredSize, &ignoredSize, &ignored32);
-    if (!ninePatchable) {
-        return nullptr;
-    }
-
-    sk_sp<GrTextureProxy> mask(
-            find_or_create_rrect_blur_mask(context, rrectToDraw, size, xformedSigma));
-    if (!mask) {
-        return nullptr;
-    }
-
-    return std::unique_ptr<GrFragmentProcessor>(
-            new GrRRectBlurEffect(xformedSigma, devRRect.getBounds(),
-                                  SkRRectPriv::GetSimpleRadii(devRRect).fX, std::move(mask)));
-}
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLRRectBlurEffect : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLRRectBlurEffect() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrRRectBlurEffect& _outer = args.fFp.cast<GrRRectBlurEffect>();
-        (void)_outer;
-        auto sigma = _outer.sigma();
-        (void)sigma;
-        auto rect = _outer.rect();
-        (void)rect;
-        auto cornerRadius = _outer.cornerRadius();
-        (void)cornerRadius;
-        fCornerRadiusVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType,
-                                                            kDefault_GrSLPrecision, "cornerRadius");
-        fProxyRectVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
-                                                         kDefault_GrSLPrecision, "proxyRect");
-        fBlurRadiusVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType,
-                                                          kDefault_GrSLPrecision, "blurRadius");
-        fragBuilder->codeAppendf(
-                "\nhalf2 translatedFragPos = half2(sk_FragCoord.xy - %s.xy);\nhalf threshold = %s "
-                "+ 2.0 * %s;\nhalf2 middle = half2((%s.zw - %s.xy) - float(2.0 * threshold));\nif "
-                "(translatedFragPos.x >= threshold && translatedFragPos.x < middle.x + threshold) "
-                "{\n    translatedFragPos.x = threshold;\n} else if (translatedFragPos.x >= "
-                "middle.x + threshold) {\n    translatedFragPos.x -= middle.x - 1.0;\n}\nif "
-                "(translatedFragPos.y > threshold && translatedFragPos.y < middle.y + threshold) "
-                "{\n    translatedFragPos.y = threshold;",
-                args.fUniformHandler->getUniformCStr(fProxyRectVar),
-                args.fUniformHandler->getUniformCStr(fCornerRadiusVar),
-                args.fUniformHandler->getUniformCStr(fBlurRadiusVar),
-                args.fUniformHandler->getUniformCStr(fProxyRectVar),
-                args.fUniformHandler->getUniformCStr(fProxyRectVar));
-        fragBuilder->codeAppendf(
-                "\n} else if (translatedFragPos.y >= middle.y + threshold) {\n    "
-                "translatedFragPos.y -= middle.y - 1.0;\n}\nhalf2 proxyDims = half2(2.0 * "
-                "threshold + 1.0);\nhalf2 texCoord = translatedFragPos / proxyDims;\n%s = %s * "
-                "texture(%s, float2(texCoord)).%s;\n",
-                args.fOutputColor, args.fInputColor,
-                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(),
-                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str());
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {
-        const GrRRectBlurEffect& _outer = _proc.cast<GrRRectBlurEffect>();
-        { pdman.set1f(fCornerRadiusVar, (_outer.cornerRadius())); }
-        auto sigma = _outer.sigma();
-        (void)sigma;
-        auto rect = _outer.rect();
-        (void)rect;
-        UniformHandle& cornerRadius = fCornerRadiusVar;
-        (void)cornerRadius;
-        GrSurfaceProxy& ninePatchSamplerProxy = *_outer.textureSampler(0).proxy();
-        GrTexture& ninePatchSampler = *ninePatchSamplerProxy.peekTexture();
-        (void)ninePatchSampler;
-        UniformHandle& proxyRect = fProxyRectVar;
-        (void)proxyRect;
-        UniformHandle& blurRadius = fBlurRadiusVar;
-        (void)blurRadius;
-
-        float blurRadiusValue = 3.f * SkScalarCeilToScalar(sigma - 1 / 6.0f);
-        pdman.set1f(blurRadius, blurRadiusValue);
-
-        SkRect outset = rect;
-        outset.outset(blurRadiusValue, blurRadiusValue);
-        pdman.set4f(proxyRect, outset.fLeft, outset.fTop, outset.fRight, outset.fBottom);
-    }
-    UniformHandle fProxyRectVar;
-    UniformHandle fBlurRadiusVar;
-    UniformHandle fCornerRadiusVar;
-};
-GrGLSLFragmentProcessor* GrRRectBlurEffect::onCreateGLSLInstance() const {
-    return new GrGLSLRRectBlurEffect();
-}
-void GrRRectBlurEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                              GrProcessorKeyBuilder* b) const {}
-bool GrRRectBlurEffect::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrRRectBlurEffect& that = other.cast<GrRRectBlurEffect>();
-    (void)that;
-    if (fSigma != that.fSigma) return false;
-    if (fRect != that.fRect) return false;
-    if (fCornerRadius != that.fCornerRadius) return false;
-    if (fNinePatchSampler != that.fNinePatchSampler) return false;
-    return true;
-}
-GrRRectBlurEffect::GrRRectBlurEffect(const GrRRectBlurEffect& src)
-        : INHERITED(kGrRRectBlurEffect_ClassID, src.optimizationFlags())
-        , fSigma(src.fSigma)
-        , fRect(src.fRect)
-        , fCornerRadius(src.fCornerRadius)
-        , fNinePatchSampler(src.fNinePatchSampler) {
-    this->setTextureSamplerCnt(1);
-}
-std::unique_ptr<GrFragmentProcessor> GrRRectBlurEffect::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrRRectBlurEffect(*this));
-}
-const GrFragmentProcessor::TextureSampler& GrRRectBlurEffect::onTextureSampler(int index) const {
-    return IthTextureSampler(index, fNinePatchSampler);
-}
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRRectBlurEffect);
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrRRectBlurEffect::TestCreate(GrProcessorTestData* d) {
-    SkScalar w = d->fRandom->nextRangeScalar(100.f, 1000.f);
-    SkScalar h = d->fRandom->nextRangeScalar(100.f, 1000.f);
-    SkScalar r = d->fRandom->nextRangeF(1.f, 9.f);
-    SkScalar sigma = d->fRandom->nextRangeF(1.f, 10.f);
-    SkRRect rrect;
-    rrect.setRectXY(SkRect::MakeWH(w, h), r, r);
-    return GrRRectBlurEffect::Make(d->context(), sigma, sigma, rrect, rrect);
-}
-#endif
diff --git a/src/gpu/effects/GrRRectBlurEffect.fp b/src/gpu/effects/GrRRectBlurEffect.fp
index 698851b5..c5e1b8a 100644
--- a/src/gpu/effects/GrRRectBlurEffect.fp
+++ b/src/gpu/effects/GrRRectBlurEffect.fp
@@ -13,11 +13,13 @@
 uniform half blurRadius;
 
 @header {
+    #include "GrCaps.h"
     #include "GrClip.h"
     #include "GrContext.h"
-    #include "GrContextPriv.h"
     #include "GrPaint.h"
     #include "GrProxyProvider.h"
+    #include "GrRecordingContext.h"
+    #include "GrRecordingContextPriv.h"
     #include "GrRenderTargetContext.h"
     #include "GrStyle.h"
     #include "SkBlurMaskFilter.h"
@@ -27,7 +29,7 @@
 }
 
 @class {
-    static sk_sp<GrTextureProxy> find_or_create_rrect_blur_mask(GrContext* context,
+    static sk_sp<GrTextureProxy> find_or_create_rrect_blur_mask(GrRecordingContext* context,
                                                                 const SkRRect& rrectToDraw,
                                                                 const SkISize& size,
                                                                 float xformedSigma) {
@@ -105,14 +107,16 @@
 }
 
 @make {
-    static std::unique_ptr<GrFragmentProcessor> Make(GrContext* context, float sigma,
+    static std::unique_ptr<GrFragmentProcessor> Make(GrRecordingContext* context,
+                                                     float sigma,
                                                      float xformedSigma,
                                                      const SkRRect& srcRRect,
                                                      const SkRRect& devRRect);
 }
 
 @cpp {
-    std::unique_ptr<GrFragmentProcessor> GrRRectBlurEffect::Make(GrContext* context, float sigma,
+    std::unique_ptr<GrFragmentProcessor> GrRRectBlurEffect::Make(GrRecordingContext* context,
+                                                                 float sigma,
                                                                  float xformedSigma,
                                                                  const SkRRect& srcRRect,
                                                                  const SkRRect& devRRect) {
diff --git a/src/gpu/effects/GrRRectBlurEffect.h b/src/gpu/effects/GrRRectBlurEffect.h
deleted file mode 100644
index ae43a33..0000000
--- a/src/gpu/effects/GrRRectBlurEffect.h
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrRRectBlurEffect.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrRRectBlurEffect_DEFINED
-#define GrRRectBlurEffect_DEFINED
-#include "SkTypes.h"
-
-#include "GrClip.h"
-#include "GrContext.h"
-#include "GrContextPriv.h"
-#include "GrPaint.h"
-#include "GrProxyProvider.h"
-#include "GrRenderTargetContext.h"
-#include "GrStyle.h"
-#include "SkBlurMaskFilter.h"
-#include "SkBlurPriv.h"
-#include "SkGpuBlurUtils.h"
-#include "SkRRectPriv.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrRRectBlurEffect : public GrFragmentProcessor {
-public:
-    static sk_sp<GrTextureProxy> find_or_create_rrect_blur_mask(GrContext* context,
-                                                                const SkRRect& rrectToDraw,
-                                                                const SkISize& size,
-                                                                float xformedSigma) {
-        static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
-        GrUniqueKey key;
-        GrUniqueKey::Builder builder(&key, kDomain, 9, "RoundRect Blur Mask");
-        builder[0] = SkScalarCeilToInt(xformedSigma - 1 / 6.0f);
-
-        int index = 1;
-        for (auto c : {SkRRect::kUpperLeft_Corner, SkRRect::kUpperRight_Corner,
-                       SkRRect::kLowerRight_Corner, SkRRect::kLowerLeft_Corner}) {
-            SkASSERT(SkScalarIsInt(rrectToDraw.radii(c).fX) &&
-                     SkScalarIsInt(rrectToDraw.radii(c).fY));
-            builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fX);
-            builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fY);
-        }
-        builder.finish();
-
-        GrProxyProvider* proxyProvider = context->priv().proxyProvider();
-
-        sk_sp<GrTextureProxy> mask(
-                proxyProvider->findOrCreateProxyByUniqueKey(key, kBottomLeft_GrSurfaceOrigin));
-        if (!mask) {
-            GrBackendFormat format =
-                    context->priv().caps()->getBackendFormatFromColorType(kAlpha_8_SkColorType);
-            // TODO: this could be approx but the texture coords will need to be updated
-            sk_sp<GrRenderTargetContext> rtc(
-                    context->priv().makeDeferredRenderTargetContextWithFallback(
-                            format, SkBackingFit::kExact, size.fWidth, size.fHeight,
-                            kAlpha_8_GrPixelConfig, nullptr));
-            if (!rtc) {
-                return nullptr;
-            }
-
-            GrPaint paint;
-
-            rtc->clear(nullptr, SK_PMColor4fTRANSPARENT,
-                       GrRenderTargetContext::CanClearFullscreen::kYes);
-            rtc->drawRRect(GrNoClip(), std::move(paint), GrAA::kYes, SkMatrix::I(), rrectToDraw,
-                           GrStyle::SimpleFill());
-
-            sk_sp<GrTextureProxy> srcProxy(rtc->asTextureProxyRef());
-            if (!srcProxy) {
-                return nullptr;
-            }
-            sk_sp<GrRenderTargetContext> rtc2(
-                    SkGpuBlurUtils::GaussianBlur(context,
-                                                 std::move(srcProxy),
-                                                 nullptr,
-                                                 SkIRect::MakeWH(size.fWidth, size.fHeight),
-                                                 SkIRect::EmptyIRect(),
-                                                 xformedSigma,
-                                                 xformedSigma,
-                                                 GrTextureDomain::kIgnore_Mode,
-                                                 kPremul_SkAlphaType,
-                                                 SkBackingFit::kExact));
-            if (!rtc2) {
-                return nullptr;
-            }
-
-            mask = rtc2->asTextureProxyRef();
-            if (!mask) {
-                return nullptr;
-            }
-            SkASSERT(mask->origin() == kBottomLeft_GrSurfaceOrigin);
-            proxyProvider->assignUniqueKeyToProxy(key, mask.get());
-        }
-
-        return mask;
-    }
-    float sigma() const { return fSigma; }
-    const SkRect& rect() const { return fRect; }
-    float cornerRadius() const { return fCornerRadius; }
-
-    static std::unique_ptr<GrFragmentProcessor> Make(GrContext* context, float sigma,
-                                                     float xformedSigma, const SkRRect& srcRRect,
-                                                     const SkRRect& devRRect);
-    GrRRectBlurEffect(const GrRRectBlurEffect& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "RRectBlurEffect"; }
-
-private:
-    GrRRectBlurEffect(float sigma, SkRect rect, float cornerRadius,
-                      sk_sp<GrTextureProxy> ninePatchSampler)
-            : INHERITED(kGrRRectBlurEffect_ClassID,
-                        (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag)
-            , fSigma(sigma)
-            , fRect(rect)
-            , fCornerRadius(cornerRadius)
-            , fNinePatchSampler(std::move(ninePatchSampler)) {
-        this->setTextureSamplerCnt(1);
-    }
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    const TextureSampler& onTextureSampler(int) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    float fSigma;
-    SkRect fRect;
-    float fCornerRadius;
-    TextureSampler fNinePatchSampler;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/effects/GrRRectEffect.cpp b/src/gpu/effects/GrRRectEffect.cpp
index 66aed33..db39c4f 100644
--- a/src/gpu/effects/GrRRectEffect.cpp
+++ b/src/gpu/effects/GrRRectEffect.cpp
@@ -536,7 +536,7 @@
         case SkRRect::kSimple_Type: {
             const char *invRadiiXYSqdName;
             fInvRadiiSqdUniform = uniformHandler->addUniform(kFragment_GrShaderFlag,
-                                                             kHalf2_GrSLType,
+                                                             kFloat2_GrSLType,
                                                              "invRadiiXY",
                                                              &invRadiiXYSqdName);
             fragBuilder->codeAppend("float2 dxy = max(max(dxy0, dxy1), 0.0);");
@@ -550,7 +550,7 @@
         case SkRRect::kNinePatch_Type: {
             const char *invRadiiLTRBSqdName;
             fInvRadiiSqdUniform = uniformHandler->addUniform(kFragment_GrShaderFlag,
-                                                             kHalf4_GrSLType,
+                                                             kFloat4_GrSLType,
                                                              "invRadiiLTRB",
                                                              &invRadiiLTRBSqdName);
             if (scaleName) {
diff --git a/src/gpu/effects/GrRectBlurEffect.cpp b/src/gpu/effects/GrRectBlurEffect.cpp
deleted file mode 100644
index e0cf0d7..0000000
--- a/src/gpu/effects/GrRectBlurEffect.cpp
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrRectBlurEffect.fp; do not modify.
- **************************************************************************************************/
-#include "GrRectBlurEffect.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLRectBlurEffect : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLRectBlurEffect() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrRectBlurEffect& _outer = args.fFp.cast<GrRectBlurEffect>();
-        (void)_outer;
-        auto rect = _outer.rect();
-        (void)rect;
-        auto sigma = _outer.sigma();
-        (void)sigma;
-        highPrecision = ((((abs(rect.left()) > 16000.0 || abs(rect.top()) > 16000.0) ||
-                           abs(rect.right()) > 16000.0) ||
-                          abs(rect.bottom()) > 16000.0) ||
-                         abs(rect.right() - rect.left()) > 16000.0) ||
-                        abs(rect.bottom() - rect.top()) > 16000.0;
-        fRectVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
-                                                    kDefault_GrSLPrecision, "rect");
-        if (!highPrecision) {
-            fProxyRectHalfVar =
-                    args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType,
-                                                     kDefault_GrSLPrecision, "proxyRectHalf");
-        }
-        if (highPrecision) {
-            fProxyRectFloatVar =
-                    args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
-                                                     kDefault_GrSLPrecision, "proxyRectFloat");
-        }
-        fProfileSizeVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType,
-                                                           kDefault_GrSLPrecision, "profileSize");
-        fragBuilder->codeAppendf(
-                "/* key */ bool highPrecision = %s;\n@if (highPrecision) {\n    float2 "
-                "translatedPos = sk_FragCoord.xy - %s.xy;\n    float width = %s.z - %s.x;\n    "
-                "float height = %s.w - %s.y;\n    float2 smallDims = float2(width - float(%s), "
-                "height - float(%s));\n    float center = float(2.0 * floor(%s / 2.0 + 0.25) - "
-                "1.0);\n    float2 wh = smallDims - float2(center, center);\n    half hcoord = "
-                "half((abs(translatedPos.x - 0.5 * width) - 0.5 * wh.x) / float(%s));\n    half "
-                "hlookup = texture(%s, float2(float(hcoord), 0.5)).",
-                (highPrecision ? "true" : "false"), args.fUniformHandler->getUniformCStr(fRectVar),
-                args.fUniformHandler->getUniformCStr(fRectVar),
-                args.fUniformHandler->getUniformCStr(fRectVar),
-                args.fUniformHandler->getUniformCStr(fRectVar),
-                args.fUniformHandler->getUniformCStr(fRectVar),
-                args.fUniformHandler->getUniformCStr(fProfileSizeVar),
-                args.fUniformHandler->getUniformCStr(fProfileSizeVar),
-                args.fUniformHandler->getUniformCStr(fProfileSizeVar),
-                args.fUniformHandler->getUniformCStr(fProfileSizeVar),
-                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str());
-        fragBuilder->codeAppendf(
-                "%s.w;\n    half vcoord = half((abs(translatedPos.y - 0.5 * height) - 0.5 * wh.y) "
-                "/ float(%s));\n    half vlookup = texture(%s, float2(float(vcoord), 0.5)).%s.w;\n "
-                "   %s = (%s * hlookup) * vlookup;\n} else {\n    half2 translatedPos = "
-                "half2(sk_FragCoord.xy - %s.xy);\n    half width = half(%s.z - %s.x);\n    half "
-                "height = half(%s.w - %s.y);\n    half2 smallDims = half2(width - %s, height - "
-                "%s);\n    half center = 2.0 * floor(%s / 2.0 + 0.25) - 1.0;\n    half2 wh = "
-                "smallDims - half2(center, center);\n    half ",
-                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str(),
-                args.fUniformHandler->getUniformCStr(fProfileSizeVar),
-                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(),
-                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str(),
-                args.fOutputColor, args.fInputColor, args.fUniformHandler->getUniformCStr(fRectVar),
-                args.fUniformHandler->getUniformCStr(fRectVar),
-                args.fUniformHandler->getUniformCStr(fRectVar),
-                args.fUniformHandler->getUniformCStr(fRectVar),
-                args.fUniformHandler->getUniformCStr(fRectVar),
-                args.fUniformHandler->getUniformCStr(fProfileSizeVar),
-                args.fUniformHandler->getUniformCStr(fProfileSizeVar),
-                args.fUniformHandler->getUniformCStr(fProfileSizeVar));
-        fragBuilder->codeAppendf(
-                "hcoord = (abs(translatedPos.x - 0.5 * width) - 0.5 * wh.x) / %s;\n    half "
-                "hlookup = texture(%s, float2(float(hcoord), 0.5)).%s.w;\n    half vcoord = "
-                "(abs(translatedPos.y - 0.5 * height) - 0.5 * wh.y) / %s;\n    half vlookup = "
-                "texture(%s, float2(float(vcoord), 0.5)).%s.w;\n    %s = (%s * hlookup) * "
-                "vlookup;\n}\n",
-                args.fUniformHandler->getUniformCStr(fProfileSizeVar),
-                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(),
-                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str(),
-                args.fUniformHandler->getUniformCStr(fProfileSizeVar),
-                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(),
-                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str(),
-                args.fOutputColor, args.fInputColor);
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {
-        const GrRectBlurEffect& _outer = _proc.cast<GrRectBlurEffect>();
-        { pdman.set4fv(fRectVar, 1, reinterpret_cast<const float*>(&(_outer.rect()))); }
-        UniformHandle& rect = fRectVar;
-        (void)rect;
-        auto sigma = _outer.sigma();
-        (void)sigma;
-        GrSurfaceProxy& blurProfileProxy = *_outer.textureSampler(0).proxy();
-        GrTexture& blurProfile = *blurProfileProxy.peekTexture();
-        (void)blurProfile;
-        UniformHandle& proxyRectHalf = fProxyRectHalfVar;
-        (void)proxyRectHalf;
-        UniformHandle& proxyRectFloat = fProxyRectFloatVar;
-        (void)proxyRectFloat;
-        UniformHandle& profileSize = fProfileSizeVar;
-        (void)profileSize;
-
-        pdman.set1f(profileSize, SkScalarCeilToScalar(6 * sigma));
-    }
-    bool highPrecision = false;
-    UniformHandle fProxyRectHalfVar;
-    UniformHandle fProxyRectFloatVar;
-    UniformHandle fProfileSizeVar;
-    UniformHandle fRectVar;
-};
-GrGLSLFragmentProcessor* GrRectBlurEffect::onCreateGLSLInstance() const {
-    return new GrGLSLRectBlurEffect();
-}
-void GrRectBlurEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                             GrProcessorKeyBuilder* b) const {}
-bool GrRectBlurEffect::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrRectBlurEffect& that = other.cast<GrRectBlurEffect>();
-    (void)that;
-    if (fRect != that.fRect) return false;
-    if (fSigma != that.fSigma) return false;
-    if (fBlurProfile != that.fBlurProfile) return false;
-    return true;
-}
-GrRectBlurEffect::GrRectBlurEffect(const GrRectBlurEffect& src)
-        : INHERITED(kGrRectBlurEffect_ClassID, src.optimizationFlags())
-        , fRect(src.fRect)
-        , fSigma(src.fSigma)
-        , fBlurProfile(src.fBlurProfile) {
-    this->setTextureSamplerCnt(1);
-}
-std::unique_ptr<GrFragmentProcessor> GrRectBlurEffect::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrRectBlurEffect(*this));
-}
-const GrFragmentProcessor::TextureSampler& GrRectBlurEffect::onTextureSampler(int index) const {
-    return IthTextureSampler(index, fBlurProfile);
-}
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRectBlurEffect);
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrRectBlurEffect::TestCreate(GrProcessorTestData* data) {
-    float sigma = data->fRandom->nextRangeF(3, 8);
-    float width = data->fRandom->nextRangeF(200, 300);
-    float height = data->fRandom->nextRangeF(200, 300);
-    return GrRectBlurEffect::Make(data->proxyProvider(), *data->caps()->shaderCaps(),
-                                  SkRect::MakeWH(width, height), sigma);
-}
-#endif
diff --git a/src/gpu/effects/GrRectBlurEffect.fp b/src/gpu/effects/GrRectBlurEffect.fp
index 5fda20c..50f833c 100644
--- a/src/gpu/effects/GrRectBlurEffect.fp
+++ b/src/gpu/effects/GrRectBlurEffect.fp
@@ -7,6 +7,7 @@
 
 @header {
     #include "GrProxyProvider.h"
+    #include "GrShaderCaps.h"
     #include "SkBlurMask.h"
     #include "SkScalar.h"
 }
diff --git a/src/gpu/effects/GrRectBlurEffect.h b/src/gpu/effects/GrRectBlurEffect.h
deleted file mode 100644
index bad6be1..0000000
--- a/src/gpu/effects/GrRectBlurEffect.h
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrRectBlurEffect.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrRectBlurEffect_DEFINED
-#define GrRectBlurEffect_DEFINED
-#include "SkTypes.h"
-
-#include "GrProxyProvider.h"
-#include "SkBlurMask.h"
-#include "SkScalar.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrRectBlurEffect : public GrFragmentProcessor {
-public:
-    static sk_sp<GrTextureProxy> CreateBlurProfileTexture(GrProxyProvider* proxyProvider,
-                                                          float sigma) {
-        unsigned int profileSize = SkScalarCeilToInt(6 * sigma);
-
-        static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
-        GrUniqueKey key;
-        GrUniqueKey::Builder builder(&key, kDomain, 1, "Rect Blur Mask");
-        builder[0] = profileSize;
-        builder.finish();
-
-        sk_sp<GrTextureProxy> blurProfile(
-                proxyProvider->findOrCreateProxyByUniqueKey(key, kTopLeft_GrSurfaceOrigin));
-        if (!blurProfile) {
-            SkImageInfo ii = SkImageInfo::MakeA8(profileSize, 1);
-
-            SkBitmap bitmap;
-            if (!bitmap.tryAllocPixels(ii)) {
-                return nullptr;
-            }
-
-            SkBlurMask::ComputeBlurProfile(bitmap.getAddr8(0, 0), profileSize, sigma);
-            bitmap.setImmutable();
-
-            sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
-            if (!image) {
-                return nullptr;
-            }
-
-            blurProfile =
-                    proxyProvider->createTextureProxy(std::move(image), kNone_GrSurfaceFlags, 1,
-                                                      SkBudgeted::kYes, SkBackingFit::kExact);
-            if (!blurProfile) {
-                return nullptr;
-            }
-
-            SkASSERT(blurProfile->origin() == kTopLeft_GrSurfaceOrigin);
-            proxyProvider->assignUniqueKeyToProxy(key, blurProfile.get());
-        }
-
-        return blurProfile;
-    }
-    const SkRect& rect() const { return fRect; }
-    float sigma() const { return fSigma; }
-
-    static std::unique_ptr<GrFragmentProcessor> Make(GrProxyProvider* proxyProvider,
-                                                     const GrShaderCaps& caps, const SkRect& rect,
-                                                     float sigma) {
-        if (!caps.floatIs32Bits()) {
-            // We promote the rect uniform from half to float when it has large values for
-            // precision. If we don't have full float then fail.
-            if (SkScalarAbs(rect.fLeft) > 16000.f || SkScalarAbs(rect.fTop) > 16000.f ||
-                SkScalarAbs(rect.fRight) > 16000.f || SkScalarAbs(rect.fBottom) > 16000.f ||
-                SkScalarAbs(rect.width()) > 16000.f || SkScalarAbs(rect.height()) > 16000.f) {
-                return nullptr;
-            }
-        }
-        int doubleProfileSize = SkScalarCeilToInt(12 * sigma);
-
-        if (doubleProfileSize >= rect.width() || doubleProfileSize >= rect.height()) {
-            // if the blur sigma is too large so the gaussian overlaps the whole
-            // rect in either direction, fall back to CPU path for now.
-            return nullptr;
-        }
-
-        sk_sp<GrTextureProxy> blurProfile(CreateBlurProfileTexture(proxyProvider, sigma));
-        if (!blurProfile) {
-            return nullptr;
-        }
-
-        return std::unique_ptr<GrFragmentProcessor>(new GrRectBlurEffect(
-                rect, sigma, std::move(blurProfile),
-                GrSamplerState(GrSamplerState::WrapMode::kClamp, GrSamplerState::Filter::kBilerp)));
-    }
-    GrRectBlurEffect(const GrRectBlurEffect& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "RectBlurEffect"; }
-
-private:
-    GrRectBlurEffect(SkRect rect, float sigma, sk_sp<GrTextureProxy> blurProfile,
-                     GrSamplerState samplerParams)
-            : INHERITED(kGrRectBlurEffect_ClassID,
-                        (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag)
-            , fRect(rect)
-            , fSigma(sigma)
-            , fBlurProfile(std::move(blurProfile), samplerParams) {
-        this->setTextureSamplerCnt(1);
-    }
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    const TextureSampler& onTextureSampler(int) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    SkRect fRect;
-    float fSigma;
-    TextureSampler fBlurProfile;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/effects/GrSimpleTextureEffect.cpp b/src/gpu/effects/GrSimpleTextureEffect.cpp
deleted file mode 100644
index 5b70a3e..0000000
--- a/src/gpu/effects/GrSimpleTextureEffect.cpp
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrSimpleTextureEffect.fp; do not modify.
- **************************************************************************************************/
-#include "GrSimpleTextureEffect.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLSimpleTextureEffect : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLSimpleTextureEffect() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrSimpleTextureEffect& _outer = args.fFp.cast<GrSimpleTextureEffect>();
-        (void)_outer;
-        auto matrix = _outer.matrix();
-        (void)matrix;
-        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
-        fragBuilder->codeAppendf(
-                "%s = %s * texture(%s, %s).%s;\n", args.fOutputColor, args.fInputColor,
-                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(),
-                sk_TransformedCoords2D_0.c_str(),
-                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str());
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {}
-};
-GrGLSLFragmentProcessor* GrSimpleTextureEffect::onCreateGLSLInstance() const {
-    return new GrGLSLSimpleTextureEffect();
-}
-void GrSimpleTextureEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                                  GrProcessorKeyBuilder* b) const {}
-bool GrSimpleTextureEffect::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrSimpleTextureEffect& that = other.cast<GrSimpleTextureEffect>();
-    (void)that;
-    if (fImage != that.fImage) return false;
-    if (fMatrix != that.fMatrix) return false;
-    return true;
-}
-GrSimpleTextureEffect::GrSimpleTextureEffect(const GrSimpleTextureEffect& src)
-        : INHERITED(kGrSimpleTextureEffect_ClassID, src.optimizationFlags())
-        , fImage(src.fImage)
-        , fMatrix(src.fMatrix)
-        , fImageCoordTransform(src.fImageCoordTransform) {
-    this->setTextureSamplerCnt(1);
-    this->addCoordTransform(&fImageCoordTransform);
-}
-std::unique_ptr<GrFragmentProcessor> GrSimpleTextureEffect::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrSimpleTextureEffect(*this));
-}
-const GrFragmentProcessor::TextureSampler& GrSimpleTextureEffect::onTextureSampler(
-        int index) const {
-    return IthTextureSampler(index, fImage);
-}
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrSimpleTextureEffect);
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrSimpleTextureEffect::TestCreate(
-        GrProcessorTestData* testData) {
-    int texIdx = testData->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx
-                                               : GrProcessorUnitTest::kAlphaTextureIdx;
-    GrSamplerState::WrapMode wrapModes[2];
-    GrTest::TestWrapModes(testData->fRandom, wrapModes);
-    if (!testData->caps()->npotTextureTileSupport()) {
-        // Performing repeat sampling on npot textures will cause asserts on HW
-        // that lacks support.
-        wrapModes[0] = GrSamplerState::WrapMode::kClamp;
-        wrapModes[1] = GrSamplerState::WrapMode::kClamp;
-    }
-
-    GrSamplerState params(wrapModes, testData->fRandom->nextBool()
-                                             ? GrSamplerState::Filter::kBilerp
-                                             : GrSamplerState::Filter::kNearest);
-
-    const SkMatrix& matrix = GrTest::TestMatrix(testData->fRandom);
-    return GrSimpleTextureEffect::Make(testData->textureProxy(texIdx), matrix, params);
-}
-#endif
diff --git a/src/gpu/effects/GrSimpleTextureEffect.h b/src/gpu/effects/GrSimpleTextureEffect.h
deleted file mode 100644
index 5e8730a..0000000
--- a/src/gpu/effects/GrSimpleTextureEffect.h
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrSimpleTextureEffect.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrSimpleTextureEffect_DEFINED
-#define GrSimpleTextureEffect_DEFINED
-#include "SkTypes.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrSimpleTextureEffect : public GrFragmentProcessor {
-public:
-    const SkMatrix44& matrix() const { return fMatrix; }
-
-    static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy> proxy,
-                                                     const SkMatrix& matrix) {
-        return std::unique_ptr<GrFragmentProcessor>(
-                new GrSimpleTextureEffect(std::move(proxy), matrix,
-                                          GrSamplerState(GrSamplerState::WrapMode::kClamp,
-                                                         GrSamplerState::Filter::kNearest)));
-    }
-
-    /* clamp mode */
-    static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy> proxy,
-                                                     const SkMatrix& matrix,
-                                                     GrSamplerState::Filter filter) {
-        return std::unique_ptr<GrFragmentProcessor>(new GrSimpleTextureEffect(
-                std::move(proxy), matrix,
-                GrSamplerState(GrSamplerState::WrapMode::kClamp, filter)));
-    }
-
-    static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy> proxy,
-                                                     const SkMatrix& matrix,
-                                                     const GrSamplerState& p) {
-        return std::unique_ptr<GrFragmentProcessor>(
-                new GrSimpleTextureEffect(std::move(proxy), matrix, p));
-    }
-    GrSimpleTextureEffect(const GrSimpleTextureEffect& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "SimpleTextureEffect"; }
-
-private:
-    GrSimpleTextureEffect(sk_sp<GrTextureProxy> image, SkMatrix44 matrix,
-                          GrSamplerState samplerParams)
-            : INHERITED(kGrSimpleTextureEffect_ClassID,
-                        (OptimizationFlags)ModulateForSamplerOptFlags(
-                                image->config(),
-                                samplerParams.wrapModeX() ==
-                                                GrSamplerState::WrapMode::kClampToBorder ||
-                                        samplerParams.wrapModeY() ==
-                                                GrSamplerState::WrapMode::kClampToBorder))
-            , fImage(std::move(image), samplerParams)
-            , fMatrix(matrix)
-            , fImageCoordTransform(matrix, fImage.proxy()) {
-        this->setTextureSamplerCnt(1);
-        this->addCoordTransform(&fImageCoordTransform);
-    }
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    const TextureSampler& onTextureSampler(int) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    TextureSampler fImage;
-    SkMatrix44 fMatrix;
-    GrCoordTransform fImageCoordTransform;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/effects/GrSkSLFP.cpp b/src/gpu/effects/GrSkSLFP.cpp
index 3b2b43c..2e628ea 100644
--- a/src/gpu/effects/GrSkSLFP.cpp
+++ b/src/gpu/effects/GrSkSLFP.cpp
@@ -6,21 +6,23 @@
  */
 
 #include "GrSkSLFP.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrContext.h"
-#include "GrContextPriv.h"
+
+#include "GrBaseContextPriv.h"
+#include "GrContext_Base.h"
 #include "GrTexture.h"
 #include "SkSLUtil.h"
 
-GrSkSLFPFactory::GrSkSLFPFactory(const char* name, const GrShaderCaps* shaderCaps, const char* sksl)
-        : fName(name) {
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+
+GrSkSLFPFactory::GrSkSLFPFactory(const char* name, const GrShaderCaps* shaderCaps, const char* sksl,
+                                 SkSL::Program::Kind kind)
+        : fKind(kind)
+        , fName(name) {
     SkSL::Program::Settings settings;
     settings.fCaps = shaderCaps;
-    fBaseProgram = fCompiler.convertProgram(SkSL::Program::kPipelineStage_Kind,
-                                            SkSL::String(sksl),
-                                            settings);
+    fBaseProgram = fCompiler.convertProgram(fKind, SkSL::String(sksl), settings);
     if (fCompiler.errorCount()) {
         SkDebugf("%s\n", fCompiler.errorText().c_str());
     }
@@ -53,11 +55,18 @@
     size_t offset = 0;
     for (const auto& v : fInputVars) {
         SkSL::String name(v->fName);
-        if (&v->fType == fCompiler.context().fInt_Type.get()) {
+        if (&v->fType == fCompiler.context().fInt_Type.get() ||
+            &v->fType == fCompiler.context().fShort_Type.get()) {
             offset = SkAlign4(offset);
             int32_t v = *(int32_t*) (((uint8_t*) inputs) + offset);
             inputMap.insert(std::make_pair(name, SkSL::Program::Settings::Value(v)));
             offset += sizeof(int32_t);
+        } else if (&v->fType == fCompiler.context().fFloat_Type.get() ||
+                   &v->fType == fCompiler.context().fHalf_Type.get()) {
+            offset = SkAlign4(offset);
+            float v = *(float*) (((uint8_t*) inputs) + offset);
+            inputMap.insert(std::make_pair(name, SkSL::Program::Settings::Value(v)));
+            offset += sizeof(float);
         } else if (&v->fType == fCompiler.context().fBool_Type.get()) {
             bool v = *(((bool*) inputs) + offset);
             inputMap.insert(std::make_pair(name, SkSL::Program::Settings::Value(v)));
@@ -74,12 +83,36 @@
     }
 
     std::unique_ptr<SkSL::Program> specialized = fCompiler.specialize(*fBaseProgram, inputMap);
-    SkAssertResult(fCompiler.optimize(*specialized));
+    bool optimized = fCompiler.optimize(*specialized);
+    if (!optimized) {
+        SkDebugf("%s\n", fCompiler.errorText().c_str());
+        SkASSERT(false);
+    }
     const SkSL::Program* result = specialized.get();
     fSpecializations.insert(std::make_pair(key, std::move(specialized)));
     return result;
 }
 
+static SkSL::Layout::CType get_ctype(const SkSL::Context& context, const SkSL::Variable& v) {
+   SkSL::Layout::CType result = v.fModifiers.fLayout.fCType;
+   if (result == SkSL::Layout::CType::kDefault) {
+        if (&v.fType == context.fFloat_Type.get()) {
+            result = SkSL::Layout::CType::kFloat;
+        } else if (&v.fType == context.fFloat4_Type.get()) {
+           result = SkSL::Layout::CType::kSkRect;
+        } else if (&v.fType == context.fHalf4_Type.get()) {
+           result = SkSL::Layout::CType::kSkPMColor;
+        } else if (&v.fType == context.fInt_Type.get()) {
+            result = SkSL::Layout::CType::kInt32;
+        } else if (&v.fType == context.fBool_Type.get()) {
+            result = SkSL::Layout::CType::kBool;
+        } else {
+            return SkSL::Layout::CType::kDefault;
+        }
+    }
+    return result;
+}
+
 class GrGLSLSkSLFP : public GrGLSLFragmentProcessor {
 public:
     GrGLSLSkSLFP(const SkSL::Context* context, const std::vector<const SkSL::Variable*>* inputVars,
@@ -123,7 +156,6 @@
                 fUniformHandles.push_back(args.fUniformHandler->addUniform(
                                                                    kFragment_GrShaderFlag,
                                                                    this->uniformType(v->fType),
-                                                                   kDefault_GrSLPrecision,
                                                                    SkSL::String(v->fName).c_str()));
             }
         }
@@ -178,47 +210,55 @@
         size_t offset = 0;
         const GrSkSLFP& outer = _proc.cast<GrSkSLFP>();
         char* inputs = (char*) outer.fInputs.get();
-        const SkSL::Context& context = outer.fFactory->fCompiler.context();
         for (const auto& v : outer.fFactory->fInputVars) {
-            if (&v->fType == context.fFloat4_Type.get() ||
-                &v->fType == context.fHalf4_Type.get()) {
-                float f1, f2, f3, f4;
-                switch (v->fModifiers.fLayout.fCType) {
-                    case SkSL::Layout::CType::kSkPMColor:
-                        f1 = ((uint8_t*) inputs)[offset++] / 255.0;
-                        f2 = ((uint8_t*) inputs)[offset++] / 255.0;
-                        f3 = ((uint8_t*) inputs)[offset++] / 255.0;
-                        f4 = ((uint8_t*) inputs)[offset++] / 255.0;
-                        break;
-                    case SkSL::Layout::CType::kSkRect: // fall through
-                    case SkSL::Layout::CType::kDefault:
-                        offset = SkAlign4(offset);
-                        f1 = *(float*) (inputs + offset);
-                        offset += sizeof(float);
-                        f2 = *(float*) (inputs + offset);
-                        offset += sizeof(float);
-                        f3 = *(float*) (inputs + offset);
-                        offset += sizeof(float);
-                        f4 = *(float*) (inputs + offset);
-                        offset += sizeof(float);
-                        break;
-                    default:
-                        SK_ABORT("unsupported uniform ctype");
+            switch (get_ctype(fContext, *v))  {
+                case SkSL::Layout::CType::kSkPMColor: {
+                    float f1 = ((uint8_t*) inputs)[offset++] / 255.0;
+                    float f2 = ((uint8_t*) inputs)[offset++] / 255.0;
+                    float f3 = ((uint8_t*) inputs)[offset++] / 255.0;
+                    float f4 = ((uint8_t*) inputs)[offset++] / 255.0;
+                    if (v->fModifiers.fFlags & SkSL::Modifiers::kUniform_Flag) {
+                        pdman.set4f(fUniformHandles[uniformIndex++], f1, f2, f3, f4);
+                    }
+                    break;
                 }
-                if (v->fModifiers.fFlags & SkSL::Modifiers::kUniform_Flag) {
-                    pdman.set4f(fUniformHandles[uniformIndex++], f1, f2, f3, f4);
+                case SkSL::Layout::CType::kSkRect: {
+                    offset = SkAlign4(offset);
+                    float f1 = *(float*) (inputs + offset);
+                    offset += sizeof(float);
+                    float f2 = *(float*) (inputs + offset);
+                    offset += sizeof(float);
+                    float f3 = *(float*) (inputs + offset);
+                    offset += sizeof(float);
+                    float f4 = *(float*) (inputs + offset);
+                    offset += sizeof(float);
+                    if (v->fModifiers.fFlags & SkSL::Modifiers::kUniform_Flag) {
+                        pdman.set4f(fUniformHandles[uniformIndex++], f1, f2, f3, f4);
+                    }
+                    break;
                 }
-            } else if (&v->fType == context.fInt_Type.get()) {
-                int32_t i = *(int*) (inputs + offset);
-                offset += sizeof(int32_t);
-                if (v->fModifiers.fFlags & SkSL::Modifiers::kUniform_Flag) {
-                    pdman.set1i(fUniformHandles[uniformIndex++], i);
+                case SkSL::Layout::CType::kInt32: {
+                    int32_t i = *(int32_t*) (inputs + offset);
+                    offset += sizeof(int32_t);
+                    if (v->fModifiers.fFlags & SkSL::Modifiers::kUniform_Flag) {
+                        pdman.set1i(fUniformHandles[uniformIndex++], i);
+                    }
+                    break;
                 }
-            } else if (&v->fType == context.fBool_Type.get()) {
-                SkASSERT(!(v->fModifiers.fFlags & SkSL::Modifiers::kUniform_Flag));
-                ++offset;
-            } else {
-                SkASSERT(&v->fType == context.fFragmentProcessor_Type.get());
+                case SkSL::Layout::CType::kFloat: {
+                    float f = *(float*) (inputs + offset);
+                    offset += sizeof(float);
+                    if (v->fModifiers.fFlags & SkSL::Modifiers::kUniform_Flag) {
+                        pdman.set1f(fUniformHandles[uniformIndex++], f);
+                    }
+                    break;
+                }
+                case SkSL::Layout::CType::kBool:
+                    SkASSERT(!(v->fModifiers.fFlags & SkSL::Modifiers::kUniform_Flag));
+                    ++offset;
+                    break;
+                default:
+                    SkASSERT(&v->fType == fContext.fFragmentProcessor_Type.get());
             }
         }
     }
@@ -231,26 +271,40 @@
     std::vector<UniformHandle> fUniformHandles;
 };
 
-std::unique_ptr<GrSkSLFP> GrSkSLFP::Make(GrContext* context, int index, const char* name,
+std::unique_ptr<GrSkSLFP> GrSkSLFP::Make(GrContext_Base* context, int index, const char* name,
                                          const char* sksl, const void* inputs,
-                                         size_t inputSize) {
+                                         size_t inputSize, SkSL::Program::Kind kind) {
     return std::unique_ptr<GrSkSLFP>(new GrSkSLFP(context->priv().fpFactoryCache(),
                                                   context->priv().caps()->shaderCaps(),
-                                                  index, name, sksl, inputs, inputSize));
+                                                  kind, index, name, sksl, SkString(),
+                                                  inputs, inputSize));
+}
+
+std::unique_ptr<GrSkSLFP> GrSkSLFP::Make(GrContext_Base* context, int index, const char* name,
+                                         SkString sksl, const void* inputs, size_t inputSize,
+                                         SkSL::Program::Kind kind) {
+    return std::unique_ptr<GrSkSLFP>(new GrSkSLFP(context->priv().fpFactoryCache(),
+                                                  context->priv().caps()->shaderCaps(),
+                                                  kind, index, name, nullptr, std::move(sksl),
+                                                  inputs, inputSize));
 }
 
 GrSkSLFP::GrSkSLFP(sk_sp<GrSkSLFPFactoryCache> factoryCache, const GrShaderCaps* shaderCaps,
-                   int index, const char* name, const char* sksl, const void* inputs,
-                   size_t inputSize)
+                   SkSL::Program::Kind kind, int index, const char* name, const char* sksl,
+                   SkString skslString, const void* inputs, size_t inputSize)
         : INHERITED(kGrSkSLFP_ClassID, kNone_OptimizationFlags)
         , fFactoryCache(factoryCache)
         , fShaderCaps(sk_ref_sp(shaderCaps))
+        , fKind(kind)
         , fIndex(index)
         , fName(name)
-        , fSkSL(sksl)
+        , fSkSLString(skslString)
+        , fSkSL(sksl ? sksl : fSkSLString.c_str())
         , fInputs(new int8_t[inputSize])
         , fInputSize(inputSize) {
-    memcpy(fInputs.get(), inputs, inputSize);
+    if (fInputSize) {
+        memcpy(fInputs.get(), inputs, inputSize);
+    }
 }
 
 GrSkSLFP::GrSkSLFP(const GrSkSLFP& other)
@@ -258,12 +312,16 @@
         , fFactoryCache(other.fFactoryCache)
         , fShaderCaps(other.fShaderCaps)
         , fFactory(other.fFactory)
+        , fKind(other.fKind)
         , fIndex(other.fIndex)
         , fName(other.fName)
+        , fSkSLString(other.fSkSLString)
         , fSkSL(other.fSkSL)
         , fInputs(new int8_t[other.fInputSize])
         , fInputSize(other.fInputSize) {
-    memcpy(fInputs.get(), other.fInputs.get(), fInputSize);
+    if (fInputSize) {
+        memcpy(fInputs.get(), other.fInputs.get(), fInputSize);
+    }
 }
 
 const char* GrSkSLFP::name() const {
@@ -274,7 +332,8 @@
     if (!fFactory) {
         fFactory = fFactoryCache->get(fIndex);
         if (!fFactory) {
-            fFactory = sk_sp<GrSkSLFPFactory>(new GrSkSLFPFactory(fName, fShaderCaps.get(), fSkSL));
+            fFactory = sk_sp<GrSkSLFPFactory>(new GrSkSLFPFactory(fName, fShaderCaps.get(), fSkSL,
+                                                                  fKind));
             fFactoryCache->set(fIndex, fFactory);
         }
     }
@@ -299,49 +358,68 @@
 void GrSkSLFP::onGetGLSLProcessorKey(const GrShaderCaps& caps,
                                      GrProcessorKeyBuilder* b) const {
     this->createFactory();
+    b->add32(fIndex);
     size_t offset = 0;
     char* inputs = (char*) fInputs.get();
     const SkSL::Context& context = fFactory->fCompiler.context();
     for (const auto& v : fFactory->fInputVars) {
-        if (&v->fType == context.fInt_Type.get()) {
-            offset = SkAlign4(offset);
-            if (v->fModifiers.fLayout.fKey) {
-                fKey += inputs[offset + 0];
-                fKey += inputs[offset + 1];
-                fKey += inputs[offset + 2];
-                fKey += inputs[offset + 3];
-                b->add32(*(int32_t*) (inputs + offset));
-            }
-            offset += sizeof(int32_t);
-        } else if (&v->fType == context.fFloat4_Type.get() ||
-                   &v->fType == context.fHalf4_Type.get()) {
-            if (v->fModifiers.fLayout.fKey) {
-                for (size_t i = 0; i < sizeof(float) * 4; ++i) {
-                    fKey += inputs[offset + i];
-                }
-                b->add32(*(int32_t*) (inputs + offset));
-                offset += sizeof(float);
-                b->add32(*(int32_t*) (inputs + offset));
-                offset += sizeof(float);
-                b->add32(*(int32_t*) (inputs + offset));
-                offset += sizeof(float);
-                b->add32(*(int32_t*) (inputs + offset));
-                offset += sizeof(float);
-            } else {
-                offset += sizeof(float) * 4;
-            }
-        } else if (&v->fType == context.fBool_Type.get()) {
-            if (v->fModifiers.fLayout.fKey) {
-                fKey += inputs[offset];
-                b->add32(inputs[offset]);
-            }
-            ++offset;
-        } else if (&v->fType == context.fFragmentProcessor_Type.get()) {
+        if (&v->fType == context.fFragmentProcessor_Type.get()) {
             continue;
-        } else {
-            // unsupported input var type
-            printf("%s\n", SkSL::String(v->fType.fName).c_str());
-            SkASSERT(false);
+        }
+        switch (get_ctype(context, *v)) {
+            case SkSL::Layout::CType::kBool:
+                if (v->fModifiers.fLayout.fKey) {
+                    fKey += inputs[offset];
+                    b->add32(inputs[offset]);
+                }
+                ++offset;
+                break;
+            case SkSL::Layout::CType::kInt32: {
+                offset = SkAlign4(offset);
+                if (v->fModifiers.fLayout.fKey) {
+                    fKey += inputs[offset + 0];
+                    fKey += inputs[offset + 1];
+                    fKey += inputs[offset + 2];
+                    fKey += inputs[offset + 3];
+                    b->add32(*(int32_t*) (inputs + offset));
+                }
+                offset += sizeof(int32_t);
+                break;
+            }
+            case SkSL::Layout::CType::kFloat: {
+                offset = SkAlign4(offset);
+                if (v->fModifiers.fLayout.fKey) {
+                    fKey += inputs[offset + 0];
+                    fKey += inputs[offset + 1];
+                    fKey += inputs[offset + 2];
+                    fKey += inputs[offset + 3];
+                    b->add32(*(float*) (inputs + offset));
+                }
+                offset += sizeof(float);
+                break;
+            }
+            case SkSL::Layout::CType::kSkPMColor: // fall through
+            case SkSL::Layout::CType::kSkRect:
+                if (v->fModifiers.fLayout.fKey) {
+                    for (size_t i = 0; i < sizeof(float) * 4; ++i) {
+                        fKey += inputs[offset + i];
+                    }
+                    b->add32(*(int32_t*) (inputs + offset));
+                    offset += sizeof(float);
+                    b->add32(*(int32_t*) (inputs + offset));
+                    offset += sizeof(float);
+                    b->add32(*(int32_t*) (inputs + offset));
+                    offset += sizeof(float);
+                    b->add32(*(int32_t*) (inputs + offset));
+                    offset += sizeof(float);
+                } else {
+                    offset += sizeof(float) * 4;
+                }
+                break;
+            default:
+                // unsupported input var type
+                printf("%s\n", SkSL::String(v->fType.fName).c_str());
+                SkASSERT(false);
         }
     }
 }
@@ -396,7 +474,8 @@
 
 #if GR_TEST_UTILS
 
-#include "GrConstColorProcessor.h"
+#include "generated/GrConstColorProcessor.h"
+#include "GrContext.h"
 #include "SkArithmeticImageFilter.h"
 
 extern const char* SKSL_ARITHMETIC_SRC;
diff --git a/src/gpu/effects/GrSkSLFP.h b/src/gpu/effects/GrSkSLFP.h
index 912ba23..fb7369f 100644
--- a/src/gpu/effects/GrSkSLFP.h
+++ b/src/gpu/effects/GrSkSLFP.h
@@ -24,7 +24,7 @@
 #define GR_FP_SRC_STRING static const char*
 #endif
 
-class GrContext;
+class GrContext_Base;
 class GrSkSLFPFactory;
 
 class GrSkSLFP : public GrFragmentProcessor {
@@ -68,12 +68,22 @@
      * associated with it.
      */
     static std::unique_ptr<GrSkSLFP> Make(
-                   GrContext* context,
+                   GrContext_Base* context,
                    int index,
                    const char* name,
                    const char* sksl,
                    const void* inputs,
-                   size_t inputSize);
+                   size_t inputSize,
+                   SkSL::Program::Kind kind = SkSL::Program::kPipelineStage_Kind);
+
+    static std::unique_ptr<GrSkSLFP> Make(
+                   GrContext_Base* context,
+                   int index,
+                   const char* name,
+                   SkString sksl,
+                   const void* inputs,
+                   size_t inputSize,
+                   SkSL::Program::Kind kind = SkSL::Program::kPipelineStage_Kind);
 
     const char* name() const override;
 
@@ -82,8 +92,9 @@
     std::unique_ptr<GrFragmentProcessor> clone() const override;
 
 private:
-    GrSkSLFP(sk_sp<GrSkSLFPFactoryCache> factoryCache, const GrShaderCaps* shaderCaps, int fIndex,
-             const char* name, const char* sksl, const void* inputs, size_t inputSize);
+    GrSkSLFP(sk_sp<GrSkSLFPFactoryCache> factoryCache, const GrShaderCaps* shaderCaps,
+             SkSL::Program::Kind kind, int fIndex, const char* name, const char* sksl,
+             SkString skslString, const void* inputs, size_t inputSize);
 
     GrSkSLFP(const GrSkSLFP& other);
 
@@ -101,10 +112,20 @@
 
     mutable sk_sp<GrSkSLFPFactory> fFactory;
 
+    SkSL::Program::Kind fKind;
+
     int fIndex;
 
     const char* fName;
 
+    // For object lifetime purposes, we have fields for the SkSL as both a const char* and a
+    // SkString. The const char* is the one we actually use, but it may point to the SkString's
+    // bytes. Since GrSkSLFPs are frequently created from constant strings, this allows us to
+    // generally avoid the overhead of copying the bytes into an SkString (in which case fSkSLString
+    // is the empty string), while still allowing the GrSkSLFP to manage the string's lifetime when
+    // needed.
+    SkString fSkSLString;
+
     const char* fSkSL;
 
     const std::unique_ptr<int8_t[]> fInputs;
@@ -135,11 +156,14 @@
      * the produced shaders to differ), so it is important to reuse the same factory instance for
      * the same shader in order to avoid repeatedly re-parsing the SkSL.
      */
-    GrSkSLFPFactory(const char* name, const GrShaderCaps* shaderCaps, const char* sksl);
+    GrSkSLFPFactory(const char* name, const GrShaderCaps* shaderCaps, const char* sksl,
+                    SkSL::Program::Kind kind = SkSL::Program::kPipelineStage_Kind);
 
     const SkSL::Program* getSpecialization(const SkSL::String& key, const void* inputs,
                                            size_t inputSize);
 
+    SkSL::Program::Kind fKind;
+
     const char* fName;
 
     SkSL::Compiler fCompiler;
diff --git a/src/gpu/effects/GrTextureDomain.cpp b/src/gpu/effects/GrTextureDomain.cpp
index 3cc2a1f..7121bac 100644
--- a/src/gpu/effects/GrTextureDomain.cpp
+++ b/src/gpu/effects/GrTextureDomain.cpp
@@ -9,10 +9,10 @@
 
 #include "GrProxyProvider.h"
 #include "GrShaderCaps.h"
-#include "GrSimpleTextureEffect.h"
 #include "GrSurfaceProxyPriv.h"
 #include "GrTexture.h"
 #include "SkFloatingPoint.h"
+#include "generated/GrSimpleTextureEffect.h"
 #include "glsl/GrGLSLFragmentProcessor.h"
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
 #include "glsl/GrGLSLProgramDataManager.h"
diff --git a/src/gpu/effects/GrXfermodeFragmentProcessor.cpp b/src/gpu/effects/GrXfermodeFragmentProcessor.cpp
index e207e15..e3064bf 100644
--- a/src/gpu/effects/GrXfermodeFragmentProcessor.cpp
+++ b/src/gpu/effects/GrXfermodeFragmentProcessor.cpp
@@ -7,8 +7,8 @@
 
 #include "GrXfermodeFragmentProcessor.h"
 
-#include "GrConstColorProcessor.h"
 #include "GrFragmentProcessor.h"
+#include "generated/GrConstColorProcessor.h"
 #include "glsl/GrGLSLFragmentProcessor.h"
 #include "glsl/GrGLSLBlend.h"
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
diff --git a/src/gpu/effects/GrYUVtoRGBEffect.cpp b/src/gpu/effects/GrYUVtoRGBEffect.cpp
index cf4c941..b8b93fd 100644
--- a/src/gpu/effects/GrYUVtoRGBEffect.cpp
+++ b/src/gpu/effects/GrYUVtoRGBEffect.cpp
@@ -50,20 +50,8 @@
         filterModes[i] = (size == YSize) ? filterMode : minimizeFilterMode;
     }
 
-    SkMatrix44 mat;
-    switch (yuvColorSpace) {
-        case kJPEG_SkYUVColorSpace:
-            mat.setColMajorf(kJPEGConversionMatrix);
-            break;
-        case kRec601_SkYUVColorSpace:
-            mat.setColMajorf(kRec601ConversionMatrix);
-            break;
-        case kRec709_SkYUVColorSpace:
-            mat.setColMajorf(kRec709ConversionMatrix);
-            break;
-    }
     return std::unique_ptr<GrFragmentProcessor>(new GrYUVtoRGBEffect(
-            proxies, scales, filterModes, numPlanes, yuvaIndices, mat));
+            proxies, scales, filterModes, numPlanes, yuvaIndices, yuvColorSpace));
 }
 
 #ifdef SK_DEBUG
@@ -89,16 +77,17 @@
 class GrGLSLYUVtoRGBEffect : public GrGLSLFragmentProcessor {
 public:
     GrGLSLYUVtoRGBEffect() {}
+
     void emitCode(EmitArgs& args) override {
         GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
         const GrYUVtoRGBEffect& _outer = args.fFp.cast<GrYUVtoRGBEffect>();
         (void)_outer;
 
-        auto colorSpaceMatrix = _outer.colorSpaceMatrix();
-        (void)colorSpaceMatrix;
-        fColorSpaceMatrixVar =
-                args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4x4_GrSLType,
-                                                 kDefault_GrSLPrecision, "colorSpaceMatrix");
+        if (kIdentity_SkYUVColorSpace != _outer.yuvColorSpace()) {
+            fColorSpaceMatrixVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
+                                                                    kHalf4x4_GrSLType,
+                                                                    "colorSpaceMatrix");
+        }
 
         int numSamplers = args.fTexSamplers.count();
 
@@ -119,11 +108,16 @@
         static const char kChannelToChar[4] = { 'x', 'y', 'z', 'w' };
 
         fragBuilder->codeAppendf(
-            "half4 yuvOne = half4(half(tmp%d.%c), half(tmp%d.%c), half(tmp%d.%c), 1.0) * %s;",
+            "half4 yuvOne = half4(half(tmp%d.%c), half(tmp%d.%c), half(tmp%d.%c), 1.0);",
                 _outer.yuvaIndex(0).fIndex, kChannelToChar[(int)_outer.yuvaIndex(0).fChannel],
                 _outer.yuvaIndex(1).fIndex, kChannelToChar[(int)_outer.yuvaIndex(1).fChannel],
-                _outer.yuvaIndex(2).fIndex, kChannelToChar[(int)_outer.yuvaIndex(2).fChannel],
-                args.fUniformHandler->getUniformCStr(fColorSpaceMatrixVar));
+                _outer.yuvaIndex(2).fIndex, kChannelToChar[(int)_outer.yuvaIndex(2).fChannel]);
+
+        if (kIdentity_SkYUVColorSpace != _outer.yuvColorSpace()) {
+            SkASSERT(fColorSpaceMatrixVar.isValid());
+            fragBuilder->codeAppendf(
+                "yuvOne *= %s;", args.fUniformHandler->getUniformCStr(fColorSpaceMatrixVar));
+        }
 
 
         if (_outer.yuvaIndex(3).fIndex >= 0) {
@@ -143,10 +137,28 @@
     void onSetData(const GrGLSLProgramDataManager& pdman,
                    const GrFragmentProcessor& _proc) override {
         const GrYUVtoRGBEffect& _outer = _proc.cast<GrYUVtoRGBEffect>();
-        { pdman.setSkMatrix44(fColorSpaceMatrixVar, (_outer.colorSpaceMatrix())); }
+
+        switch (_outer.yuvColorSpace()) {
+            case kJPEG_SkYUVColorSpace:
+                SkASSERT(fColorSpaceMatrixVar.isValid());
+                pdman.setMatrix4f(fColorSpaceMatrixVar, kJPEGConversionMatrix);
+                break;
+            case kRec601_SkYUVColorSpace:
+                SkASSERT(fColorSpaceMatrixVar.isValid());
+                pdman.setMatrix4f(fColorSpaceMatrixVar, kRec601ConversionMatrix);
+                break;
+            case kRec709_SkYUVColorSpace:
+                SkASSERT(fColorSpaceMatrixVar.isValid());
+                pdman.setMatrix4f(fColorSpaceMatrixVar, kRec709ConversionMatrix);
+                break;
+            case kIdentity_SkYUVColorSpace:
+                break;
+        }
     }
+
     UniformHandle fColorSpaceMatrixVar;
 };
+
 GrGLSLFragmentProcessor* GrYUVtoRGBEffect::onCreateGLSLInstance() const {
     return new GrGLSLYUVtoRGBEffect();
 }
@@ -167,6 +179,10 @@
 
         packed |= (index | (chann << 2)) << (i * 4);
     }
+    if (kIdentity_SkYUVColorSpace == this->yuvColorSpace()) {
+        packed |= 0x1 << 16;
+    }
+
     b->add32(packed);
 }
 bool GrYUVtoRGBEffect::onIsEqual(const GrFragmentProcessor& other) const {
@@ -185,7 +201,7 @@
         }
     }
 
-    if (fColorSpaceMatrix != that.fColorSpaceMatrix) {
+    if (fYUVColorSpace != that.fYUVColorSpace) {
         return false;
     }
 
@@ -193,7 +209,7 @@
 }
 GrYUVtoRGBEffect::GrYUVtoRGBEffect(const GrYUVtoRGBEffect& src)
         : INHERITED(kGrYUVtoRGBEffect_ClassID, src.optimizationFlags())
-        , fColorSpaceMatrix(src.fColorSpaceMatrix) {
+        , fYUVColorSpace(src.fYUVColorSpace) {
     int numPlanes = src.numTextureSamplers();
     for (int i = 0; i < numPlanes; ++i) {
         fSamplers[i].reset(sk_ref_sp(src.fSamplers[i].proxy()), src.fSamplers[i].samplerState());
diff --git a/src/gpu/effects/GrYUVtoRGBEffect.h b/src/gpu/effects/GrYUVtoRGBEffect.h
index 3d94402..93ec5238 100644
--- a/src/gpu/effects/GrYUVtoRGBEffect.h
+++ b/src/gpu/effects/GrYUVtoRGBEffect.h
@@ -25,7 +25,7 @@
     SkString dumpInfo() const override;
 #endif
 
-    const SkMatrix44& colorSpaceMatrix() const { return fColorSpaceMatrix; }
+    SkYUVColorSpace yuvColorSpace() const { return fYUVColorSpace; }
     const SkYUVAIndex& yuvaIndex(int i) const { return fYUVAIndices[i]; }
 
     GrYUVtoRGBEffect(const GrYUVtoRGBEffect& src);
@@ -35,9 +35,9 @@
 private:
     GrYUVtoRGBEffect(const sk_sp<GrTextureProxy> proxies[], const SkSize scales[],
                      const GrSamplerState::Filter filterModes[], int numPlanes,
-                     const SkYUVAIndex yuvaIndices[4], const SkMatrix44& colorSpaceMatrix)
+                     const SkYUVAIndex yuvaIndices[4], SkYUVColorSpace yuvColorSpace)
             : INHERITED(kGrYUVtoRGBEffect_ClassID, kNone_OptimizationFlags)
-            , fColorSpaceMatrix(colorSpaceMatrix) {
+            , fYUVColorSpace(yuvColorSpace) {
         for (int i = 0; i < numPlanes; ++i) {
             fSamplers[i].reset(std::move(proxies[i]),
                                GrSamplerState(GrSamplerState::WrapMode::kClamp, filterModes[i]));
@@ -63,7 +63,7 @@
     SkMatrix44       fSamplerTransforms[4];
     GrCoordTransform fSamplerCoordTransforms[4];
     SkYUVAIndex      fYUVAIndices[4];
-    SkMatrix44       fColorSpaceMatrix;
+    SkYUVColorSpace  fYUVColorSpace;
 
     typedef GrFragmentProcessor INHERITED;
 };
diff --git a/src/gpu/effects/generated/GrAARectEffect.cpp b/src/gpu/effects/generated/GrAARectEffect.cpp
new file mode 100644
index 0000000..62d9f52
--- /dev/null
+++ b/src/gpu/effects/generated/GrAARectEffect.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrAARectEffect.fp; do not modify.
+ **************************************************************************************************/
+#include "GrAARectEffect.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLAARectEffect : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLAARectEffect() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrAARectEffect& _outer = args.fFp.cast<GrAARectEffect>();
+        (void)_outer;
+        auto edgeType = _outer.edgeType;
+        (void)edgeType;
+        auto rect = _outer.rect;
+        (void)rect;
+        prevRect = float4(-1.0);
+        rectUniformVar = args.fUniformHandler->addUniform(
+                kFragment_GrShaderFlag, kFloat4_GrSLType, "rectUniform");
+        fragBuilder->codeAppendf(
+                "float4 prevRect = float4(%f, %f, %f, %f);\nhalf alpha;\n@switch (%d) {\n    case "
+                "0:\n    case 2:\n        alpha = half(all(greaterThan(float4(sk_FragCoord.xy, "
+                "%s.zw), float4(%s.xy, sk_FragCoord.xy))) ? 1 : 0);\n        break;\n    "
+                "default:\n        half xSub, ySub;\n        xSub = min(half(sk_FragCoord.x - "
+                "%s.x), 0.0);\n        xSub += min(half(%s.z - sk_FragCoord.x), 0.0);\n        "
+                "ySub = min(half(sk_FragCoord.y - %s.y), 0.0);\n        ySub += min(half(%s.w - "
+                "sk_FragCoord.y), 0.0);\n        alpha = (1.0 + ",
+                prevRect.left(),
+                prevRect.top(),
+                prevRect.right(),
+                prevRect.bottom(),
+                (int)_outer.edgeType,
+                args.fUniformHandler->getUniformCStr(rectUniformVar),
+                args.fUniformHandler->getUniformCStr(rectUniformVar),
+                args.fUniformHandler->getUniformCStr(rectUniformVar),
+                args.fUniformHandler->getUniformCStr(rectUniformVar),
+                args.fUniformHandler->getUniformCStr(rectUniformVar),
+                args.fUniformHandler->getUniformCStr(rectUniformVar));
+        fragBuilder->codeAppendf(
+                "max(xSub, -1.0)) * (1.0 + max(ySub, -1.0));\n}\n@if (%d == 2 || %d == 3) {\n    "
+                "alpha = 1.0 - alpha;\n}\n%s = %s * alpha;\n",
+                (int)_outer.edgeType,
+                (int)_outer.edgeType,
+                args.fOutputColor,
+                args.fInputColor);
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {
+        const GrAARectEffect& _outer = _proc.cast<GrAARectEffect>();
+        auto edgeType = _outer.edgeType;
+        (void)edgeType;
+        auto rect = _outer.rect;
+        (void)rect;
+        UniformHandle& rectUniform = rectUniformVar;
+        (void)rectUniform;
+
+        const SkRect& newRect = GrProcessorEdgeTypeIsAA(edgeType) ? rect.makeInset(.5f, .5f) : rect;
+        if (newRect != prevRect) {
+            pdman.set4f(rectUniform, newRect.fLeft, newRect.fTop, newRect.fRight, newRect.fBottom);
+            prevRect = newRect;
+        }
+    }
+    SkRect prevRect = float4(0);
+    UniformHandle rectUniformVar;
+};
+GrGLSLFragmentProcessor* GrAARectEffect::onCreateGLSLInstance() const {
+    return new GrGLSLAARectEffect();
+}
+void GrAARectEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                           GrProcessorKeyBuilder* b) const {
+    b->add32((int32_t)edgeType);
+}
+bool GrAARectEffect::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrAARectEffect& that = other.cast<GrAARectEffect>();
+    (void)that;
+    if (edgeType != that.edgeType) return false;
+    if (rect != that.rect) return false;
+    return true;
+}
+GrAARectEffect::GrAARectEffect(const GrAARectEffect& src)
+        : INHERITED(kGrAARectEffect_ClassID, src.optimizationFlags())
+        , edgeType(src.edgeType)
+        , rect(src.rect) {}
+std::unique_ptr<GrFragmentProcessor> GrAARectEffect::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrAARectEffect(*this));
+}
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrAARectEffect);
+#if GR_TEST_UTILS
+std::unique_ptr<GrFragmentProcessor> GrAARectEffect::TestCreate(GrProcessorTestData* d) {
+    SkRect rect = SkRect::MakeLTRB(d->fRandom->nextSScalar1(),
+                                   d->fRandom->nextSScalar1(),
+                                   d->fRandom->nextSScalar1(),
+                                   d->fRandom->nextSScalar1());
+    std::unique_ptr<GrFragmentProcessor> fp;
+    do {
+        GrClipEdgeType edgeType =
+                static_cast<GrClipEdgeType>(d->fRandom->nextULessThan(kGrClipEdgeTypeCnt));
+
+        fp = GrAARectEffect::Make(edgeType, rect);
+    } while (nullptr == fp);
+    return fp;
+}
+#endif
diff --git a/src/gpu/effects/generated/GrAARectEffect.h b/src/gpu/effects/generated/GrAARectEffect.h
new file mode 100644
index 0000000..0c302c5
--- /dev/null
+++ b/src/gpu/effects/generated/GrAARectEffect.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrAARectEffect.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrAARectEffect_DEFINED
+#define GrAARectEffect_DEFINED
+#include "SkTypes.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrAARectEffect : public GrFragmentProcessor {
+public:
+    static std::unique_ptr<GrFragmentProcessor> Make(GrClipEdgeType edgeType, SkRect rect) {
+        return std::unique_ptr<GrFragmentProcessor>(new GrAARectEffect(edgeType, rect));
+    }
+    GrAARectEffect(const GrAARectEffect& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "AARectEffect"; }
+    GrClipEdgeType edgeType;
+    SkRect rect;
+
+private:
+    GrAARectEffect(GrClipEdgeType edgeType, SkRect rect)
+            : INHERITED(kGrAARectEffect_ClassID,
+                        (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag)
+            , edgeType(edgeType)
+            , rect(rect) {}
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.cpp b/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.cpp
new file mode 100644
index 0000000..70fe78a
--- /dev/null
+++ b/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrAlphaThresholdFragmentProcessor.fp; do not modify.
+ **************************************************************************************************/
+#include "GrAlphaThresholdFragmentProcessor.h"
+
+inline GrFragmentProcessor::OptimizationFlags GrAlphaThresholdFragmentProcessor::optFlags(
+        float outerThreshold) {
+    if (outerThreshold >= 1.0) {
+        return kPreservesOpaqueInput_OptimizationFlag |
+               kCompatibleWithCoverageAsAlpha_OptimizationFlag;
+    } else {
+        return kCompatibleWithCoverageAsAlpha_OptimizationFlag;
+    }
+}
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLAlphaThresholdFragmentProcessor : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLAlphaThresholdFragmentProcessor() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrAlphaThresholdFragmentProcessor& _outer =
+                args.fFp.cast<GrAlphaThresholdFragmentProcessor>();
+        (void)_outer;
+        auto innerThreshold = _outer.innerThreshold;
+        (void)innerThreshold;
+        auto outerThreshold = _outer.outerThreshold;
+        (void)outerThreshold;
+        innerThresholdVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType,
+                                                             "innerThreshold");
+        outerThresholdVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType,
+                                                             "outerThreshold");
+        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+        fragBuilder->codeAppendf(
+                "half4 color = %s;\nhalf4 mask_color = texture(%s, %s).%s;\nif (mask_color.w < "
+                "0.5) {\n    if (color.w > %s) {\n        half scale = %s / color.w;\n        "
+                "color.xyz *= scale;\n        color.w = %s;\n    }\n} else if (color.w < %s) {\n   "
+                " half scale = %s / max(0.001, color.w);\n    color.xyz *= scale;\n    color.w = "
+                "%s;\n}\n%s = color;\n",
+                args.fInputColor,
+                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(),
+                sk_TransformedCoords2D_0.c_str(),
+                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str(),
+                args.fUniformHandler->getUniformCStr(outerThresholdVar),
+                args.fUniformHandler->getUniformCStr(outerThresholdVar),
+                args.fUniformHandler->getUniformCStr(outerThresholdVar),
+                args.fUniformHandler->getUniformCStr(innerThresholdVar),
+                args.fUniformHandler->getUniformCStr(innerThresholdVar),
+                args.fUniformHandler->getUniformCStr(innerThresholdVar), args.fOutputColor);
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {
+        const GrAlphaThresholdFragmentProcessor& _outer =
+                _proc.cast<GrAlphaThresholdFragmentProcessor>();
+        {
+            pdman.set1f(innerThresholdVar, (_outer.innerThreshold));
+            pdman.set1f(outerThresholdVar, (_outer.outerThreshold));
+        }
+    }
+    UniformHandle innerThresholdVar;
+    UniformHandle outerThresholdVar;
+};
+GrGLSLFragmentProcessor* GrAlphaThresholdFragmentProcessor::onCreateGLSLInstance() const {
+    return new GrGLSLAlphaThresholdFragmentProcessor();
+}
+void GrAlphaThresholdFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                              GrProcessorKeyBuilder* b) const {}
+bool GrAlphaThresholdFragmentProcessor::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrAlphaThresholdFragmentProcessor& that = other.cast<GrAlphaThresholdFragmentProcessor>();
+    (void)that;
+    if (mask != that.mask) return false;
+    if (innerThreshold != that.innerThreshold) return false;
+    if (outerThreshold != that.outerThreshold) return false;
+    return true;
+}
+GrAlphaThresholdFragmentProcessor::GrAlphaThresholdFragmentProcessor(
+        const GrAlphaThresholdFragmentProcessor& src)
+        : INHERITED(kGrAlphaThresholdFragmentProcessor_ClassID, src.optimizationFlags())
+        , maskCoordTransform(src.maskCoordTransform)
+        , mask(src.mask)
+        , innerThreshold(src.innerThreshold)
+        , outerThreshold(src.outerThreshold) {
+    this->setTextureSamplerCnt(1);
+    this->addCoordTransform(&maskCoordTransform);
+}
+std::unique_ptr<GrFragmentProcessor> GrAlphaThresholdFragmentProcessor::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrAlphaThresholdFragmentProcessor(*this));
+}
+const GrFragmentProcessor::TextureSampler& GrAlphaThresholdFragmentProcessor::onTextureSampler(
+        int index) const {
+    return IthTextureSampler(index, mask);
+}
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrAlphaThresholdFragmentProcessor);
+#if GR_TEST_UTILS
+std::unique_ptr<GrFragmentProcessor> GrAlphaThresholdFragmentProcessor::TestCreate(
+        GrProcessorTestData* testData) {
+    sk_sp<GrTextureProxy> maskProxy = testData->textureProxy(GrProcessorUnitTest::kAlphaTextureIdx);
+    // Make the inner and outer thresholds be in (0, 1) exclusive and be sorted correctly.
+    float innerThresh = testData->fRandom->nextUScalar1() * .99f + 0.005f;
+    float outerThresh = testData->fRandom->nextUScalar1() * .99f + 0.005f;
+    const int kMaxWidth = 1000;
+    const int kMaxHeight = 1000;
+    uint32_t width = testData->fRandom->nextULessThan(kMaxWidth);
+    uint32_t height = testData->fRandom->nextULessThan(kMaxHeight);
+    uint32_t x = testData->fRandom->nextULessThan(kMaxWidth - width);
+    uint32_t y = testData->fRandom->nextULessThan(kMaxHeight - height);
+    SkIRect bounds = SkIRect::MakeXYWH(x, y, width, height);
+    return GrAlphaThresholdFragmentProcessor::Make(std::move(maskProxy), innerThresh, outerThresh,
+                                                   bounds);
+}
+#endif
diff --git a/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.h b/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.h
new file mode 100644
index 0000000..8067db5
--- /dev/null
+++ b/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrAlphaThresholdFragmentProcessor.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrAlphaThresholdFragmentProcessor_DEFINED
+#define GrAlphaThresholdFragmentProcessor_DEFINED
+#include "SkTypes.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrAlphaThresholdFragmentProcessor : public GrFragmentProcessor {
+public:
+    inline OptimizationFlags optFlags(float outerThreshold);
+
+    static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy> mask,
+                                                     float innerThreshold,
+                                                     float outerThreshold,
+                                                     const SkIRect& bounds) {
+        return std::unique_ptr<GrFragmentProcessor>(new GrAlphaThresholdFragmentProcessor(
+                mask, innerThreshold, outerThreshold, bounds));
+    }
+    GrAlphaThresholdFragmentProcessor(const GrAlphaThresholdFragmentProcessor& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "AlphaThresholdFragmentProcessor"; }
+    GrCoordTransform maskCoordTransform;
+    TextureSampler mask;
+    float innerThreshold;
+    float outerThreshold;
+
+private:
+    GrAlphaThresholdFragmentProcessor(sk_sp<GrTextureProxy> mask, float innerThreshold,
+                                      float outerThreshold, const SkIRect& bounds)
+            : INHERITED(kGrAlphaThresholdFragmentProcessor_ClassID, kNone_OptimizationFlags)
+            , maskCoordTransform(
+                      SkMatrix::MakeTrans(SkIntToScalar(-bounds.x()), SkIntToScalar(-bounds.y())),
+                      mask.get())
+            , mask(std::move(mask))
+            , innerThreshold(innerThreshold)
+            , outerThreshold(outerThreshold) {
+        this->setTextureSamplerCnt(1);
+        this->addCoordTransform(&maskCoordTransform);
+    }
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    const TextureSampler& onTextureSampler(int) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.cpp b/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.cpp
new file mode 100644
index 0000000..b91e148
--- /dev/null
+++ b/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrBlurredEdgeFragmentProcessor.fp; do not modify.
+ **************************************************************************************************/
+#include "GrBlurredEdgeFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLBlurredEdgeFragmentProcessor : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLBlurredEdgeFragmentProcessor() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrBlurredEdgeFragmentProcessor& _outer =
+                args.fFp.cast<GrBlurredEdgeFragmentProcessor>();
+        (void)_outer;
+        auto mode = _outer.mode;
+        (void)mode;
+        fragBuilder->codeAppendf(
+                "half factor = 1.0 - %s.w;\n@switch (%d) {\n    case 0:\n        factor = "
+                "exp((-factor * factor) * 4.0) - 0.017999999999999999;\n        break;\n    case "
+                "1:\n        factor = smoothstep(1.0, 0.0, factor);\n        break;\n}\n%s = "
+                "half4(factor);\n",
+                args.fInputColor, (int)_outer.mode, args.fOutputColor);
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {}
+};
+GrGLSLFragmentProcessor* GrBlurredEdgeFragmentProcessor::onCreateGLSLInstance() const {
+    return new GrGLSLBlurredEdgeFragmentProcessor();
+}
+void GrBlurredEdgeFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                           GrProcessorKeyBuilder* b) const {
+    b->add32((int32_t)mode);
+}
+bool GrBlurredEdgeFragmentProcessor::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrBlurredEdgeFragmentProcessor& that = other.cast<GrBlurredEdgeFragmentProcessor>();
+    (void)that;
+    if (mode != that.mode) return false;
+    return true;
+}
+GrBlurredEdgeFragmentProcessor::GrBlurredEdgeFragmentProcessor(
+        const GrBlurredEdgeFragmentProcessor& src)
+        : INHERITED(kGrBlurredEdgeFragmentProcessor_ClassID, src.optimizationFlags())
+        , mode(src.mode) {}
+std::unique_ptr<GrFragmentProcessor> GrBlurredEdgeFragmentProcessor::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrBlurredEdgeFragmentProcessor(*this));
+}
diff --git a/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.h b/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.h
new file mode 100644
index 0000000..c149bcc
--- /dev/null
+++ b/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrBlurredEdgeFragmentProcessor.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrBlurredEdgeFragmentProcessor_DEFINED
+#define GrBlurredEdgeFragmentProcessor_DEFINED
+#include "SkTypes.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrBlurredEdgeFragmentProcessor : public GrFragmentProcessor {
+public:
+    enum class Mode { kGaussian = 0, kSmoothStep = 1 };
+    static std::unique_ptr<GrFragmentProcessor> Make(Mode mode) {
+        return std::unique_ptr<GrFragmentProcessor>(new GrBlurredEdgeFragmentProcessor(mode));
+    }
+    GrBlurredEdgeFragmentProcessor(const GrBlurredEdgeFragmentProcessor& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "BlurredEdgeFragmentProcessor"; }
+    Mode mode;
+
+private:
+    GrBlurredEdgeFragmentProcessor(Mode mode)
+            : INHERITED(kGrBlurredEdgeFragmentProcessor_ClassID, kNone_OptimizationFlags)
+            , mode(mode) {}
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.cpp b/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.cpp
new file mode 100644
index 0000000..28cdbd5
--- /dev/null
+++ b/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.cpp
@@ -0,0 +1,350 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrCircleBlurFragmentProcessor.fp; do not modify.
+ **************************************************************************************************/
+#include "GrCircleBlurFragmentProcessor.h"
+
+#include "GrProxyProvider.h"
+
+// Computes an unnormalized half kernel (right side). Returns the summation of all the half
+// kernel values.
+static float make_unnormalized_half_kernel(float* halfKernel, int halfKernelSize, float sigma) {
+    const float invSigma = 1.f / sigma;
+    const float b = -0.5f * invSigma * invSigma;
+    float tot = 0.0f;
+    // Compute half kernel values at half pixel steps out from the center.
+    float t = 0.5f;
+    for (int i = 0; i < halfKernelSize; ++i) {
+        float value = expf(t * t * b);
+        tot += value;
+        halfKernel[i] = value;
+        t += 1.f;
+    }
+    return tot;
+}
+
+// Create a Gaussian half-kernel (right side) and a summed area table given a sigma and number
+// of discrete steps. The half kernel is normalized to sum to 0.5.
+static void make_half_kernel_and_summed_table(float* halfKernel, float* summedHalfKernel,
+                                              int halfKernelSize, float sigma) {
+    // The half kernel should sum to 0.5 not 1.0.
+    const float tot = 2.f * make_unnormalized_half_kernel(halfKernel, halfKernelSize, sigma);
+    float sum = 0.f;
+    for (int i = 0; i < halfKernelSize; ++i) {
+        halfKernel[i] /= tot;
+        sum += halfKernel[i];
+        summedHalfKernel[i] = sum;
+    }
+}
+
+// Applies the 1D half kernel vertically at points along the x axis to a circle centered at the
+// origin with radius circleR.
+void apply_kernel_in_y(float* results, int numSteps, float firstX, float circleR,
+                       int halfKernelSize, const float* summedHalfKernelTable) {
+    float x = firstX;
+    for (int i = 0; i < numSteps; ++i, x += 1.f) {
+        if (x < -circleR || x > circleR) {
+            results[i] = 0;
+            continue;
+        }
+        float y = sqrtf(circleR * circleR - x * x);
+        // In the column at x we exit the circle at +y and -y
+        // The summed table entry j is actually reflects an offset of j + 0.5.
+        y -= 0.5f;
+        int yInt = SkScalarFloorToInt(y);
+        SkASSERT(yInt >= -1);
+        if (y < 0) {
+            results[i] = (y + 0.5f) * summedHalfKernelTable[0];
+        } else if (yInt >= halfKernelSize - 1) {
+            results[i] = 0.5f;
+        } else {
+            float yFrac = y - yInt;
+            results[i] = (1.f - yFrac) * summedHalfKernelTable[yInt] +
+                         yFrac * summedHalfKernelTable[yInt + 1];
+        }
+    }
+}
+
+// Apply a Gaussian at point (evalX, 0) to a circle centered at the origin with radius circleR.
+// This relies on having a half kernel computed for the Gaussian and a table of applications of
+// the half kernel in y to columns at (evalX - halfKernel, evalX - halfKernel + 1, ..., evalX +
+// halfKernel) passed in as yKernelEvaluations.
+static uint8_t eval_at(float evalX, float circleR, const float* halfKernel, int halfKernelSize,
+                       const float* yKernelEvaluations) {
+    float acc = 0;
+
+    float x = evalX - halfKernelSize;
+    for (int i = 0; i < halfKernelSize; ++i, x += 1.f) {
+        if (x < -circleR || x > circleR) {
+            continue;
+        }
+        float verticalEval = yKernelEvaluations[i];
+        acc += verticalEval * halfKernel[halfKernelSize - i - 1];
+    }
+    for (int i = 0; i < halfKernelSize; ++i, x += 1.f) {
+        if (x < -circleR || x > circleR) {
+            continue;
+        }
+        float verticalEval = yKernelEvaluations[i + halfKernelSize];
+        acc += verticalEval * halfKernel[i];
+    }
+    // Since we applied a half kernel in y we multiply acc by 2 (the circle is symmetric about
+    // the x axis).
+    return SkUnitScalarClampToByte(2.f * acc);
+}
+
+// This function creates a profile of a blurred circle. It does this by computing a kernel for
+// half the Gaussian and a matching summed area table. The summed area table is used to compute
+// an array of vertical applications of the half kernel to the circle along the x axis. The
+// table of y evaluations has 2 * k + n entries where k is the size of the half kernel and n is
+// the size of the profile being computed. Then for each of the n profile entries we walk out k
+// steps in each horizontal direction multiplying the corresponding y evaluation by the half
+// kernel entry and sum these values to compute the profile entry.
+static void create_circle_profile(uint8_t* weights, float sigma, float circleR,
+                                  int profileTextureWidth) {
+    const int numSteps = profileTextureWidth;
+
+    // The full kernel is 6 sigmas wide.
+    int halfKernelSize = SkScalarCeilToInt(6.0f * sigma);
+    // round up to next multiple of 2 and then divide by 2
+    halfKernelSize = ((halfKernelSize + 1) & ~1) >> 1;
+
+    // Number of x steps at which to apply kernel in y to cover all the profile samples in x.
+    int numYSteps = numSteps + 2 * halfKernelSize;
+
+    SkAutoTArray<float> bulkAlloc(halfKernelSize + halfKernelSize + numYSteps);
+    float* halfKernel = bulkAlloc.get();
+    float* summedKernel = bulkAlloc.get() + halfKernelSize;
+    float* yEvals = bulkAlloc.get() + 2 * halfKernelSize;
+    make_half_kernel_and_summed_table(halfKernel, summedKernel, halfKernelSize, sigma);
+
+    float firstX = -halfKernelSize + 0.5f;
+    apply_kernel_in_y(yEvals, numYSteps, firstX, circleR, halfKernelSize, summedKernel);
+
+    for (int i = 0; i < numSteps - 1; ++i) {
+        float evalX = i + 0.5f;
+        weights[i] = eval_at(evalX, circleR, halfKernel, halfKernelSize, yEvals + i);
+    }
+    // Ensure the tail of the Gaussian goes to zero.
+    weights[numSteps - 1] = 0;
+}
+
+static void create_half_plane_profile(uint8_t* profile, int profileWidth) {
+    SkASSERT(!(profileWidth & 0x1));
+    // The full kernel is 6 sigmas wide.
+    float sigma = profileWidth / 6.f;
+    int halfKernelSize = profileWidth / 2;
+
+    SkAutoTArray<float> halfKernel(halfKernelSize);
+
+    // The half kernel should sum to 0.5.
+    const float tot = 2.f * make_unnormalized_half_kernel(halfKernel.get(), halfKernelSize, sigma);
+    float sum = 0.f;
+    // Populate the profile from the right edge to the middle.
+    for (int i = 0; i < halfKernelSize; ++i) {
+        halfKernel[halfKernelSize - i - 1] /= tot;
+        sum += halfKernel[halfKernelSize - i - 1];
+        profile[profileWidth - i - 1] = SkUnitScalarClampToByte(sum);
+    }
+    // Populate the profile from the middle to the left edge (by flipping the half kernel and
+    // continuing the summation).
+    for (int i = 0; i < halfKernelSize; ++i) {
+        sum += halfKernel[i];
+        profile[halfKernelSize - i - 1] = SkUnitScalarClampToByte(sum);
+    }
+    // Ensure tail goes to 0.
+    profile[profileWidth - 1] = 0;
+}
+
+static sk_sp<GrTextureProxy> create_profile_texture(GrProxyProvider* proxyProvider,
+                                                    const SkRect& circle, float sigma,
+                                                    float* solidRadius, float* textureRadius) {
+    float circleR = circle.width() / 2.0f;
+    if (circleR < SK_ScalarNearlyZero) {
+        return nullptr;
+    }
+    // Profile textures are cached by the ratio of sigma to circle radius and by the size of the
+    // profile texture (binned by powers of 2).
+    SkScalar sigmaToCircleRRatio = sigma / circleR;
+    // When sigma is really small this becomes a equivalent to convolving a Gaussian with a
+    // half-plane. Similarly, in the extreme high ratio cases circle becomes a point WRT to the
+    // Guassian and the profile texture is a just a Gaussian evaluation. However, we haven't yet
+    // implemented this latter optimization.
+    sigmaToCircleRRatio = SkTMin(sigmaToCircleRRatio, 8.f);
+    SkFixed sigmaToCircleRRatioFixed;
+    static const SkScalar kHalfPlaneThreshold = 0.1f;
+    bool useHalfPlaneApprox = false;
+    if (sigmaToCircleRRatio <= kHalfPlaneThreshold) {
+        useHalfPlaneApprox = true;
+        sigmaToCircleRRatioFixed = 0;
+        *solidRadius = circleR - 3 * sigma;
+        *textureRadius = 6 * sigma;
+    } else {
+        // Convert to fixed point for the key.
+        sigmaToCircleRRatioFixed = SkScalarToFixed(sigmaToCircleRRatio);
+        // We shave off some bits to reduce the number of unique entries. We could probably
+        // shave off more than we do.
+        sigmaToCircleRRatioFixed &= ~0xff;
+        sigmaToCircleRRatio = SkFixedToScalar(sigmaToCircleRRatioFixed);
+        sigma = circleR * sigmaToCircleRRatio;
+        *solidRadius = 0;
+        *textureRadius = circleR + 3 * sigma;
+    }
+
+    static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
+    GrUniqueKey key;
+    GrUniqueKey::Builder builder(&key, kDomain, 1, "1-D Circular Blur");
+    builder[0] = sigmaToCircleRRatioFixed;
+    builder.finish();
+
+    sk_sp<GrTextureProxy> blurProfile =
+            proxyProvider->findOrCreateProxyByUniqueKey(key, kTopLeft_GrSurfaceOrigin);
+    if (!blurProfile) {
+        static constexpr int kProfileTextureWidth = 512;
+
+        SkBitmap bm;
+        if (!bm.tryAllocPixels(SkImageInfo::MakeA8(kProfileTextureWidth, 1))) {
+            return nullptr;
+        }
+
+        if (useHalfPlaneApprox) {
+            create_half_plane_profile(bm.getAddr8(0, 0), kProfileTextureWidth);
+        } else {
+            // Rescale params to the size of the texture we're creating.
+            SkScalar scale = kProfileTextureWidth / *textureRadius;
+            create_circle_profile(bm.getAddr8(0, 0), sigma * scale, circleR * scale,
+                                  kProfileTextureWidth);
+        }
+
+        bm.setImmutable();
+        sk_sp<SkImage> image = SkImage::MakeFromBitmap(bm);
+
+        blurProfile = proxyProvider->createTextureProxy(std::move(image), kNone_GrSurfaceFlags, 1,
+                                                        SkBudgeted::kYes, SkBackingFit::kExact);
+        if (!blurProfile) {
+            return nullptr;
+        }
+
+        SkASSERT(blurProfile->origin() == kTopLeft_GrSurfaceOrigin);
+        proxyProvider->assignUniqueKeyToProxy(key, blurProfile.get());
+    }
+
+    return blurProfile;
+}
+
+std::unique_ptr<GrFragmentProcessor> GrCircleBlurFragmentProcessor::Make(
+        GrProxyProvider* proxyProvider, const SkRect& circle, float sigma) {
+    float solidRadius;
+    float textureRadius;
+    sk_sp<GrTextureProxy> profile(
+            create_profile_texture(proxyProvider, circle, sigma, &solidRadius, &textureRadius));
+    if (!profile) {
+        return nullptr;
+    }
+    return std::unique_ptr<GrFragmentProcessor>(new GrCircleBlurFragmentProcessor(
+            circle, textureRadius, solidRadius, std::move(profile)));
+}
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLCircleBlurFragmentProcessor : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLCircleBlurFragmentProcessor() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrCircleBlurFragmentProcessor& _outer =
+                args.fFp.cast<GrCircleBlurFragmentProcessor>();
+        (void)_outer;
+        auto circleRect = _outer.circleRect;
+        (void)circleRect;
+        auto textureRadius = _outer.textureRadius;
+        (void)textureRadius;
+        auto solidRadius = _outer.solidRadius;
+        (void)solidRadius;
+        circleDataVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType,
+                                                         "circleData");
+        fragBuilder->codeAppendf(
+                "half2 vec = half2(half((sk_FragCoord.x - float(%s.x)) * float(%s.w)), "
+                "half((sk_FragCoord.y - float(%s.y)) * float(%s.w)));\nhalf dist = length(vec) + "
+                "(0.5 - %s.z) * %s.w;\n%s = %s * texture(%s, float2(half2(dist, 0.5))).%s.w;\n",
+                args.fUniformHandler->getUniformCStr(circleDataVar),
+                args.fUniformHandler->getUniformCStr(circleDataVar),
+                args.fUniformHandler->getUniformCStr(circleDataVar),
+                args.fUniformHandler->getUniformCStr(circleDataVar),
+                args.fUniformHandler->getUniformCStr(circleDataVar),
+                args.fUniformHandler->getUniformCStr(circleDataVar), args.fOutputColor,
+                args.fInputColor,
+                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(),
+                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str());
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& data,
+                   const GrFragmentProcessor& _proc) override {
+        const GrCircleBlurFragmentProcessor& _outer = _proc.cast<GrCircleBlurFragmentProcessor>();
+        auto circleRect = _outer.circleRect;
+        (void)circleRect;
+        auto textureRadius = _outer.textureRadius;
+        (void)textureRadius;
+        auto solidRadius = _outer.solidRadius;
+        (void)solidRadius;
+        GrSurfaceProxy& blurProfileSamplerProxy = *_outer.textureSampler(0).proxy();
+        GrTexture& blurProfileSampler = *blurProfileSamplerProxy.peekTexture();
+        (void)blurProfileSampler;
+        UniformHandle& circleData = circleDataVar;
+        (void)circleData;
+
+        data.set4f(circleData, circleRect.centerX(), circleRect.centerY(), solidRadius,
+                   1.f / textureRadius);
+    }
+    UniformHandle circleDataVar;
+};
+GrGLSLFragmentProcessor* GrCircleBlurFragmentProcessor::onCreateGLSLInstance() const {
+    return new GrGLSLCircleBlurFragmentProcessor();
+}
+void GrCircleBlurFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                          GrProcessorKeyBuilder* b) const {}
+bool GrCircleBlurFragmentProcessor::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrCircleBlurFragmentProcessor& that = other.cast<GrCircleBlurFragmentProcessor>();
+    (void)that;
+    if (circleRect != that.circleRect) return false;
+    if (textureRadius != that.textureRadius) return false;
+    if (solidRadius != that.solidRadius) return false;
+    if (blurProfileSampler != that.blurProfileSampler) return false;
+    return true;
+}
+GrCircleBlurFragmentProcessor::GrCircleBlurFragmentProcessor(
+        const GrCircleBlurFragmentProcessor& src)
+        : INHERITED(kGrCircleBlurFragmentProcessor_ClassID, src.optimizationFlags())
+        , circleRect(src.circleRect)
+        , textureRadius(src.textureRadius)
+        , solidRadius(src.solidRadius)
+        , blurProfileSampler(src.blurProfileSampler) {
+    this->setTextureSamplerCnt(1);
+}
+std::unique_ptr<GrFragmentProcessor> GrCircleBlurFragmentProcessor::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrCircleBlurFragmentProcessor(*this));
+}
+const GrFragmentProcessor::TextureSampler& GrCircleBlurFragmentProcessor::onTextureSampler(
+        int index) const {
+    return IthTextureSampler(index, blurProfileSampler);
+}
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor);
+#if GR_TEST_UTILS
+std::unique_ptr<GrFragmentProcessor> GrCircleBlurFragmentProcessor::TestCreate(
+        GrProcessorTestData* testData) {
+    SkScalar wh = testData->fRandom->nextRangeScalar(100.f, 1000.f);
+    SkScalar sigma = testData->fRandom->nextRangeF(1.f, 10.f);
+    SkRect circle = SkRect::MakeWH(wh, wh);
+    return GrCircleBlurFragmentProcessor::Make(testData->proxyProvider(), circle, sigma);
+}
+#endif
diff --git a/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h b/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h
new file mode 100644
index 0000000..1969fc3
--- /dev/null
+++ b/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrCircleBlurFragmentProcessor.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrCircleBlurFragmentProcessor_DEFINED
+#define GrCircleBlurFragmentProcessor_DEFINED
+#include "SkTypes.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrCircleBlurFragmentProcessor : public GrFragmentProcessor {
+public:
+    static std::unique_ptr<GrFragmentProcessor> Make(GrProxyProvider*, const SkRect& circle,
+                                                     float sigma);
+    GrCircleBlurFragmentProcessor(const GrCircleBlurFragmentProcessor& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "CircleBlurFragmentProcessor"; }
+    SkRect circleRect;
+    float textureRadius;
+    float solidRadius;
+    TextureSampler blurProfileSampler;
+
+private:
+    GrCircleBlurFragmentProcessor(SkRect circleRect, float textureRadius, float solidRadius,
+                                  sk_sp<GrTextureProxy> blurProfileSampler)
+            : INHERITED(kGrCircleBlurFragmentProcessor_ClassID,
+                        (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag)
+            , circleRect(circleRect)
+            , textureRadius(textureRadius)
+            , solidRadius(solidRadius)
+            , blurProfileSampler(std::move(blurProfileSampler)) {
+        this->setTextureSamplerCnt(1);
+    }
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    const TextureSampler& onTextureSampler(int) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/effects/generated/GrCircleEffect.cpp b/src/gpu/effects/generated/GrCircleEffect.cpp
new file mode 100644
index 0000000..f24f2d7
--- /dev/null
+++ b/src/gpu/effects/generated/GrCircleEffect.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrCircleEffect.fp; do not modify.
+ **************************************************************************************************/
+#include "GrCircleEffect.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLCircleEffect : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLCircleEffect() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrCircleEffect& _outer = args.fFp.cast<GrCircleEffect>();
+        (void)_outer;
+        auto edgeType = _outer.edgeType;
+        (void)edgeType;
+        auto center = _outer.center;
+        (void)center;
+        auto radius = _outer.radius;
+        (void)radius;
+        prevRadius = -1.0;
+        circleVar =
+                args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "circle");
+        fragBuilder->codeAppendf(
+                "half2 prevCenter;\nhalf prevRadius = %f;\nhalf d;\n@if (%d == 2 || %d == 3) {\n   "
+                " d = half((length((float2(%s.xy) - sk_FragCoord.xy) * float(%s.w)) - 1.0) * "
+                "float(%s.z));\n} else {\n    d = half((1.0 - length((float2(%s.xy) - "
+                "sk_FragCoord.xy) * float(%s.w))) * float(%s.z));\n}\n@if ((%d == 1 || %d == 3) || "
+                "%d == 4) {\n    d = clamp(d, 0.0, 1.0);\n} else {\n    d = d > 0.5 ? 1.0 : "
+                "0.0;\n}\n%s = %s * d;\n",
+                prevRadius, (int)_outer.edgeType, (int)_outer.edgeType,
+                args.fUniformHandler->getUniformCStr(circleVar),
+                args.fUniformHandler->getUniformCStr(circleVar),
+                args.fUniformHandler->getUniformCStr(circleVar),
+                args.fUniformHandler->getUniformCStr(circleVar),
+                args.fUniformHandler->getUniformCStr(circleVar),
+                args.fUniformHandler->getUniformCStr(circleVar), (int)_outer.edgeType,
+                (int)_outer.edgeType, (int)_outer.edgeType, args.fOutputColor, args.fInputColor);
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {
+        const GrCircleEffect& _outer = _proc.cast<GrCircleEffect>();
+        auto edgeType = _outer.edgeType;
+        (void)edgeType;
+        auto center = _outer.center;
+        (void)center;
+        auto radius = _outer.radius;
+        (void)radius;
+        UniformHandle& circle = circleVar;
+        (void)circle;
+
+        if (radius != prevRadius || center != prevCenter) {
+            SkScalar effectiveRadius = radius;
+            if (GrProcessorEdgeTypeIsInverseFill((GrClipEdgeType)edgeType)) {
+                effectiveRadius -= 0.5f;
+                // When the radius is 0.5 effectiveRadius is 0 which causes an inf * 0 in the
+                // shader.
+                effectiveRadius = SkTMax(0.001f, effectiveRadius);
+            } else {
+                effectiveRadius += 0.5f;
+            }
+            pdman.set4f(circle, center.fX, center.fY, effectiveRadius,
+                        SkScalarInvert(effectiveRadius));
+            prevCenter = center;
+            prevRadius = radius;
+        }
+    }
+    SkPoint prevCenter = half2(0);
+    float prevRadius = 0;
+    UniformHandle circleVar;
+};
+GrGLSLFragmentProcessor* GrCircleEffect::onCreateGLSLInstance() const {
+    return new GrGLSLCircleEffect();
+}
+void GrCircleEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                           GrProcessorKeyBuilder* b) const {
+    b->add32((int32_t)edgeType);
+}
+bool GrCircleEffect::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrCircleEffect& that = other.cast<GrCircleEffect>();
+    (void)that;
+    if (edgeType != that.edgeType) return false;
+    if (center != that.center) return false;
+    if (radius != that.radius) return false;
+    return true;
+}
+GrCircleEffect::GrCircleEffect(const GrCircleEffect& src)
+        : INHERITED(kGrCircleEffect_ClassID, src.optimizationFlags())
+        , edgeType(src.edgeType)
+        , center(src.center)
+        , radius(src.radius) {}
+std::unique_ptr<GrFragmentProcessor> GrCircleEffect::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrCircleEffect(*this));
+}
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleEffect);
+#if GR_TEST_UTILS
+std::unique_ptr<GrFragmentProcessor> GrCircleEffect::TestCreate(GrProcessorTestData* testData) {
+    SkPoint center;
+    center.fX = testData->fRandom->nextRangeScalar(0.f, 1000.f);
+    center.fY = testData->fRandom->nextRangeScalar(0.f, 1000.f);
+    SkScalar radius = testData->fRandom->nextRangeF(1.f, 1000.f);
+    GrClipEdgeType et;
+    do {
+        et = (GrClipEdgeType)testData->fRandom->nextULessThan(kGrClipEdgeTypeCnt);
+    } while (GrClipEdgeType::kHairlineAA == et);
+    return GrCircleEffect::Make(et, center, radius);
+}
+#endif
diff --git a/src/gpu/effects/generated/GrCircleEffect.h b/src/gpu/effects/generated/GrCircleEffect.h
new file mode 100644
index 0000000..81ab40e
--- /dev/null
+++ b/src/gpu/effects/generated/GrCircleEffect.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrCircleEffect.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrCircleEffect_DEFINED
+#define GrCircleEffect_DEFINED
+#include "SkTypes.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrCircleEffect : public GrFragmentProcessor {
+public:
+    static std::unique_ptr<GrFragmentProcessor> Make(GrClipEdgeType edgeType, SkPoint center,
+                                                     float radius) {
+        // A radius below half causes the implicit insetting done by this processor to become
+        // inverted. We could handle this case by making the processor code more complicated.
+        if (radius < .5f && GrProcessorEdgeTypeIsInverseFill(edgeType)) {
+            return nullptr;
+        }
+        return std::unique_ptr<GrFragmentProcessor>(new GrCircleEffect(edgeType, center, radius));
+    }
+    GrCircleEffect(const GrCircleEffect& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "CircleEffect"; }
+    GrClipEdgeType edgeType;
+    SkPoint center;
+    float radius;
+
+private:
+    GrCircleEffect(GrClipEdgeType edgeType, SkPoint center, float radius)
+            : INHERITED(kGrCircleEffect_ClassID,
+                        (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag)
+            , edgeType(edgeType)
+            , center(center)
+            , radius(radius) {}
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/effects/generated/GrComposeLerpEffect.cpp b/src/gpu/effects/generated/GrComposeLerpEffect.cpp
new file mode 100644
index 0000000..a768b5c
--- /dev/null
+++ b/src/gpu/effects/generated/GrComposeLerpEffect.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrComposeLerpEffect.fp; do not modify.
+ **************************************************************************************************/
+#include "GrComposeLerpEffect.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLComposeLerpEffect : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLComposeLerpEffect() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrComposeLerpEffect& _outer = args.fFp.cast<GrComposeLerpEffect>();
+        (void)_outer;
+        auto weight = _outer.weight;
+        (void)weight;
+        weightVar =
+                args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat_GrSLType, "weight");
+        SkString _child0("_child0");
+        if (_outer.child1_index >= 0) {
+            this->emitChild(_outer.child1_index, &_child0, args);
+        } else {
+            fragBuilder->codeAppendf("half4 %s;", _child0.c_str());
+        }
+        SkString _child1("_child1");
+        if (_outer.child2_index >= 0) {
+            this->emitChild(_outer.child2_index, &_child1, args);
+        } else {
+            fragBuilder->codeAppendf("half4 %s;", _child1.c_str());
+        }
+        fragBuilder->codeAppendf("%s = mix(%s ? %s : %s, %s ? %s : %s, half(%s));\n",
+                                 args.fOutputColor, _outer.child1_index >= 0 ? "true" : "false",
+                                 _child0.c_str(), args.fInputColor,
+                                 _outer.child2_index >= 0 ? "true" : "false", _child1.c_str(),
+                                 args.fInputColor, args.fUniformHandler->getUniformCStr(weightVar));
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {
+        const GrComposeLerpEffect& _outer = _proc.cast<GrComposeLerpEffect>();
+        { pdman.set1f(weightVar, (_outer.weight)); }
+    }
+    UniformHandle weightVar;
+};
+GrGLSLFragmentProcessor* GrComposeLerpEffect::onCreateGLSLInstance() const {
+    return new GrGLSLComposeLerpEffect();
+}
+void GrComposeLerpEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                GrProcessorKeyBuilder* b) const {}
+bool GrComposeLerpEffect::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrComposeLerpEffect& that = other.cast<GrComposeLerpEffect>();
+    (void)that;
+    if (weight != that.weight) return false;
+    return true;
+}
+GrComposeLerpEffect::GrComposeLerpEffect(const GrComposeLerpEffect& src)
+        : INHERITED(kGrComposeLerpEffect_ClassID, src.optimizationFlags())
+        , child1_index(src.child1_index)
+        , child2_index(src.child2_index)
+        , weight(src.weight) {
+    if (child1_index >= 0) {
+        this->registerChildProcessor(src.childProcessor(child1_index).clone());
+    }
+    if (child2_index >= 0) {
+        this->registerChildProcessor(src.childProcessor(child2_index).clone());
+    }
+}
+std::unique_ptr<GrFragmentProcessor> GrComposeLerpEffect::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrComposeLerpEffect(*this));
+}
diff --git a/src/gpu/effects/generated/GrComposeLerpEffect.h b/src/gpu/effects/generated/GrComposeLerpEffect.h
new file mode 100644
index 0000000..823d44d
--- /dev/null
+++ b/src/gpu/effects/generated/GrComposeLerpEffect.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrComposeLerpEffect.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrComposeLerpEffect_DEFINED
+#define GrComposeLerpEffect_DEFINED
+#include "SkTypes.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrComposeLerpEffect : public GrFragmentProcessor {
+public:
+    static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> child1,
+                                                     std::unique_ptr<GrFragmentProcessor> child2,
+                                                     float weight) {
+        return std::unique_ptr<GrFragmentProcessor>(
+                new GrComposeLerpEffect(std::move(child1), std::move(child2), weight));
+    }
+    GrComposeLerpEffect(const GrComposeLerpEffect& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "ComposeLerpEffect"; }
+    int child1_index = -1;
+    int child2_index = -1;
+    float weight;
+
+private:
+    GrComposeLerpEffect(std::unique_ptr<GrFragmentProcessor> child1,
+                        std::unique_ptr<GrFragmentProcessor> child2, float weight)
+            : INHERITED(kGrComposeLerpEffect_ClassID, kNone_OptimizationFlags), weight(weight) {
+        if (child1) {
+            child1_index = this->numChildProcessors();
+            this->registerChildProcessor(std::move(child1));
+        }
+        if (child2) {
+            child2_index = this->numChildProcessors();
+            this->registerChildProcessor(std::move(child2));
+        }
+    }
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/effects/generated/GrComposeLerpRedEffect.cpp b/src/gpu/effects/generated/GrComposeLerpRedEffect.cpp
new file mode 100644
index 0000000..2f3a5e4
--- /dev/null
+++ b/src/gpu/effects/generated/GrComposeLerpRedEffect.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrComposeLerpRedEffect.fp; do not modify.
+ **************************************************************************************************/
+#include "GrComposeLerpRedEffect.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLComposeLerpRedEffect : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLComposeLerpRedEffect() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrComposeLerpRedEffect& _outer = args.fFp.cast<GrComposeLerpRedEffect>();
+        (void)_outer;
+        SkString _child0("_child0");
+        if (_outer.child1_index >= 0) {
+            this->emitChild(_outer.child1_index, &_child0, args);
+        } else {
+            fragBuilder->codeAppendf("half4 %s;", _child0.c_str());
+        }
+        SkString _child1("_child1");
+        if (_outer.child2_index >= 0) {
+            this->emitChild(_outer.child2_index, &_child1, args);
+        } else {
+            fragBuilder->codeAppendf("half4 %s;", _child1.c_str());
+        }
+        SkString _child2("_child2");
+        this->emitChild(_outer.lerp_index, &_child2, args);
+        fragBuilder->codeAppendf("%s = mix(%s ? %s : %s, %s ? %s : %s, %s.x);\n", args.fOutputColor,
+                                 _outer.child1_index >= 0 ? "true" : "false", _child0.c_str(),
+                                 args.fInputColor, _outer.child2_index >= 0 ? "true" : "false",
+                                 _child1.c_str(), args.fInputColor, _child2.c_str());
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {}
+};
+GrGLSLFragmentProcessor* GrComposeLerpRedEffect::onCreateGLSLInstance() const {
+    return new GrGLSLComposeLerpRedEffect();
+}
+void GrComposeLerpRedEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                   GrProcessorKeyBuilder* b) const {}
+bool GrComposeLerpRedEffect::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrComposeLerpRedEffect& that = other.cast<GrComposeLerpRedEffect>();
+    (void)that;
+    return true;
+}
+GrComposeLerpRedEffect::GrComposeLerpRedEffect(const GrComposeLerpRedEffect& src)
+        : INHERITED(kGrComposeLerpRedEffect_ClassID, src.optimizationFlags())
+        , child1_index(src.child1_index)
+        , child2_index(src.child2_index)
+        , lerp_index(src.lerp_index) {
+    if (child1_index >= 0) {
+        this->registerChildProcessor(src.childProcessor(child1_index).clone());
+    }
+    if (child2_index >= 0) {
+        this->registerChildProcessor(src.childProcessor(child2_index).clone());
+    }
+    this->registerChildProcessor(src.childProcessor(lerp_index).clone());
+}
+std::unique_ptr<GrFragmentProcessor> GrComposeLerpRedEffect::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrComposeLerpRedEffect(*this));
+}
diff --git a/src/gpu/effects/generated/GrComposeLerpRedEffect.h b/src/gpu/effects/generated/GrComposeLerpRedEffect.h
new file mode 100644
index 0000000..f109c8c
--- /dev/null
+++ b/src/gpu/effects/generated/GrComposeLerpRedEffect.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrComposeLerpRedEffect.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrComposeLerpRedEffect_DEFINED
+#define GrComposeLerpRedEffect_DEFINED
+#include "SkTypes.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrComposeLerpRedEffect : public GrFragmentProcessor {
+public:
+    static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> child1,
+                                                     std::unique_ptr<GrFragmentProcessor> child2,
+                                                     std::unique_ptr<GrFragmentProcessor> lerp) {
+        return std::unique_ptr<GrFragmentProcessor>(
+                new GrComposeLerpRedEffect(std::move(child1), std::move(child2), std::move(lerp)));
+    }
+    GrComposeLerpRedEffect(const GrComposeLerpRedEffect& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "ComposeLerpRedEffect"; }
+    int child1_index = -1;
+    int child2_index = -1;
+    int lerp_index = -1;
+
+private:
+    GrComposeLerpRedEffect(std::unique_ptr<GrFragmentProcessor> child1,
+                           std::unique_ptr<GrFragmentProcessor> child2,
+                           std::unique_ptr<GrFragmentProcessor> lerp)
+            : INHERITED(kGrComposeLerpRedEffect_ClassID, kNone_OptimizationFlags) {
+        if (child1) {
+            child1_index = this->numChildProcessors();
+            this->registerChildProcessor(std::move(child1));
+        }
+        if (child2) {
+            child2_index = this->numChildProcessors();
+            this->registerChildProcessor(std::move(child2));
+        }
+        SkASSERT(lerp);
+        lerp_index = this->numChildProcessors();
+        this->registerChildProcessor(std::move(lerp));
+    }
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/effects/generated/GrConfigConversionEffect.cpp b/src/gpu/effects/generated/GrConfigConversionEffect.cpp
new file mode 100644
index 0000000..5acd214
--- /dev/null
+++ b/src/gpu/effects/generated/GrConfigConversionEffect.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrConfigConversionEffect.fp; do not modify.
+ **************************************************************************************************/
+#include "GrConfigConversionEffect.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLConfigConversionEffect : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLConfigConversionEffect() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrConfigConversionEffect& _outer = args.fFp.cast<GrConfigConversionEffect>();
+        (void)_outer;
+        auto pmConversion = _outer.pmConversion;
+        (void)pmConversion;
+
+        fragBuilder->forceHighPrecision();
+        fragBuilder->codeAppendf(
+                "%s = floor(%s * 255.0 + 0.5) / 255.0;\n@switch (%d) {\n    case 0:\n        "
+                "%s.xyz = floor((%s.xyz * %s.w) * 255.0 + 0.5) / 255.0;\n        break;\n    case "
+                "1:\n        %s.xyz = %s.w <= 0.0 ? half3(0.0) : floor((%s.xyz / %s.w) * 255.0 + "
+                "0.5) / 255.0;\n        break;\n}\n",
+                args.fOutputColor, args.fInputColor, (int)_outer.pmConversion, args.fOutputColor,
+                args.fOutputColor, args.fOutputColor, args.fOutputColor, args.fOutputColor,
+                args.fOutputColor, args.fOutputColor);
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {}
+};
+GrGLSLFragmentProcessor* GrConfigConversionEffect::onCreateGLSLInstance() const {
+    return new GrGLSLConfigConversionEffect();
+}
+void GrConfigConversionEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                     GrProcessorKeyBuilder* b) const {
+    b->add32((int32_t)pmConversion);
+}
+bool GrConfigConversionEffect::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrConfigConversionEffect& that = other.cast<GrConfigConversionEffect>();
+    (void)that;
+    if (pmConversion != that.pmConversion) return false;
+    return true;
+}
+GrConfigConversionEffect::GrConfigConversionEffect(const GrConfigConversionEffect& src)
+        : INHERITED(kGrConfigConversionEffect_ClassID, src.optimizationFlags())
+        , pmConversion(src.pmConversion) {}
+std::unique_ptr<GrFragmentProcessor> GrConfigConversionEffect::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrConfigConversionEffect(*this));
+}
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrConfigConversionEffect);
+#if GR_TEST_UTILS
+std::unique_ptr<GrFragmentProcessor> GrConfigConversionEffect::TestCreate(
+        GrProcessorTestData* data) {
+    PMConversion pmConv = static_cast<PMConversion>(
+            data->fRandom->nextULessThan((int)PMConversion::kPMConversionCnt));
+    return std::unique_ptr<GrFragmentProcessor>(new GrConfigConversionEffect(pmConv));
+}
+#endif
diff --git a/src/gpu/effects/generated/GrConfigConversionEffect.h b/src/gpu/effects/generated/GrConfigConversionEffect.h
new file mode 100644
index 0000000..1b8dac2
--- /dev/null
+++ b/src/gpu/effects/generated/GrConfigConversionEffect.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrConfigConversionEffect.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrConfigConversionEffect_DEFINED
+#define GrConfigConversionEffect_DEFINED
+#include "SkTypes.h"
+
+#include "GrClip.h"
+#include "GrContext.h"
+#include "GrContextPriv.h"
+#include "GrProxyProvider.h"
+#include "GrRenderTargetContext.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrConfigConversionEffect : public GrFragmentProcessor {
+public:
+    static bool TestForPreservingPMConversions(GrContext* context) {
+        static constexpr int kSize = 256;
+        static constexpr GrPixelConfig kConfig = kRGBA_8888_GrPixelConfig;
+        static constexpr SkColorType kColorType = kRGBA_8888_SkColorType;
+        const GrBackendFormat format =
+                context->priv().caps()->getBackendFormatFromColorType(kColorType);
+        SkAutoTMalloc<uint32_t> data(kSize * kSize * 3);
+        uint32_t* srcData = data.get();
+        uint32_t* firstRead = data.get() + kSize * kSize;
+        uint32_t* secondRead = data.get() + 2 * kSize * kSize;
+
+        // Fill with every possible premultiplied A, color channel value. There will be 256-y
+        // duplicate values in row y. We set r, g, and b to the same value since they are handled
+        // identically.
+        for (int y = 0; y < kSize; ++y) {
+            for (int x = 0; x < kSize; ++x) {
+                uint8_t* color = reinterpret_cast<uint8_t*>(&srcData[kSize * y + x]);
+                color[3] = y;
+                color[2] = SkTMin(x, y);
+                color[1] = SkTMin(x, y);
+                color[0] = SkTMin(x, y);
+            }
+        }
+        memset(firstRead, 0, kSize * kSize * sizeof(uint32_t));
+        memset(secondRead, 0, kSize * kSize * sizeof(uint32_t));
+
+        const SkImageInfo ii =
+                SkImageInfo::Make(kSize, kSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+
+        sk_sp<GrRenderTargetContext> readRTC(context->priv().makeDeferredRenderTargetContext(
+                format, SkBackingFit::kExact, kSize, kSize, kConfig, nullptr));
+        sk_sp<GrRenderTargetContext> tempRTC(context->priv().makeDeferredRenderTargetContext(
+                format, SkBackingFit::kExact, kSize, kSize, kConfig, nullptr));
+        if (!readRTC || !readRTC->asTextureProxy() || !tempRTC) {
+            return false;
+        }
+        // Adding discard to appease vulkan validation warning about loading uninitialized data on
+        // draw
+        readRTC->discard();
+
+        GrProxyProvider* proxyProvider = context->priv().proxyProvider();
+
+        SkPixmap pixmap(ii, srcData, 4 * kSize);
+
+        // This function is only ever called if we are in a GrContext that has a GrGpu since we are
+        // calling read pixels here. Thus the pixel data will be uploaded immediately and we don't
+        // need to keep the pixel data alive in the proxy. Therefore the ReleaseProc is nullptr.
+        sk_sp<SkImage> image = SkImage::MakeFromRaster(pixmap, nullptr, nullptr);
+
+        sk_sp<GrTextureProxy> dataProxy = proxyProvider->createTextureProxy(
+                std::move(image), kNone_GrSurfaceFlags, 1, SkBudgeted::kYes, SkBackingFit::kExact);
+        if (!dataProxy) {
+            return false;
+        }
+
+        static const SkRect kRect = SkRect::MakeIWH(kSize, kSize);
+
+        // We do a PM->UPM draw from dataTex to readTex and read the data. Then we do a UPM->PM draw
+        // from readTex to tempTex followed by a PM->UPM draw to readTex and finally read the data.
+        // We then verify that two reads produced the same values.
+
+        GrPaint paint1;
+        GrPaint paint2;
+        GrPaint paint3;
+        std::unique_ptr<GrFragmentProcessor> pmToUPM(
+                new GrConfigConversionEffect(PMConversion::kToUnpremul));
+        std::unique_ptr<GrFragmentProcessor> upmToPM(
+                new GrConfigConversionEffect(PMConversion::kToPremul));
+
+        paint1.addColorTextureProcessor(dataProxy, SkMatrix::I());
+        paint1.addColorFragmentProcessor(pmToUPM->clone());
+        paint1.setPorterDuffXPFactory(SkBlendMode::kSrc);
+
+        readRTC->fillRectToRect(GrNoClip(), std::move(paint1), GrAA::kNo, SkMatrix::I(), kRect,
+                                kRect);
+        if (!readRTC->readPixels(ii, firstRead, 0, 0, 0)) {
+            return false;
+        }
+
+        // Adding discard to appease vulkan validation warning about loading uninitialized data on
+        // draw
+        tempRTC->discard();
+
+        paint2.addColorTextureProcessor(readRTC->asTextureProxyRef(), SkMatrix::I());
+        paint2.addColorFragmentProcessor(std::move(upmToPM));
+        paint2.setPorterDuffXPFactory(SkBlendMode::kSrc);
+
+        tempRTC->fillRectToRect(GrNoClip(), std::move(paint2), GrAA::kNo, SkMatrix::I(), kRect,
+                                kRect);
+
+        paint3.addColorTextureProcessor(tempRTC->asTextureProxyRef(), SkMatrix::I());
+        paint3.addColorFragmentProcessor(std::move(pmToUPM));
+        paint3.setPorterDuffXPFactory(SkBlendMode::kSrc);
+
+        readRTC->fillRectToRect(GrNoClip(), std::move(paint3), GrAA::kNo, SkMatrix::I(), kRect,
+                                kRect);
+
+        if (!readRTC->readPixels(ii, secondRead, 0, 0, 0)) {
+            return false;
+        }
+
+        for (int y = 0; y < kSize; ++y) {
+            for (int x = 0; x <= y; ++x) {
+                if (firstRead[kSize * y + x] != secondRead[kSize * y + x]) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> fp,
+                                                     PMConversion pmConversion) {
+        if (!fp) {
+            return nullptr;
+        }
+        std::unique_ptr<GrFragmentProcessor> ccFP(new GrConfigConversionEffect(pmConversion));
+        std::unique_ptr<GrFragmentProcessor> fpPipeline[] = {std::move(fp), std::move(ccFP)};
+        return GrFragmentProcessor::RunInSeries(fpPipeline, 2);
+    }
+    GrConfigConversionEffect(const GrConfigConversionEffect& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "ConfigConversionEffect"; }
+    PMConversion pmConversion;
+
+private:
+    GrConfigConversionEffect(PMConversion pmConversion)
+            : INHERITED(kGrConfigConversionEffect_ClassID, kNone_OptimizationFlags)
+            , pmConversion(pmConversion) {}
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/effects/generated/GrConstColorProcessor.cpp b/src/gpu/effects/generated/GrConstColorProcessor.cpp
new file mode 100644
index 0000000..ca23d07
--- /dev/null
+++ b/src/gpu/effects/generated/GrConstColorProcessor.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrConstColorProcessor.fp; do not modify.
+ **************************************************************************************************/
+#include "GrConstColorProcessor.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLConstColorProcessor : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLConstColorProcessor() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrConstColorProcessor& _outer = args.fFp.cast<GrConstColorProcessor>();
+        (void)_outer;
+        auto color = _outer.color;
+        (void)color;
+        auto mode = _outer.mode;
+        (void)mode;
+        colorVar =
+                args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "color");
+        fragBuilder->codeAppendf(
+                "@switch (%d) {\n    case 0:\n        %s = %s;\n        break;\n    case 1:\n      "
+                "  %s = %s * %s;\n        break;\n    case 2:\n        %s = %s.w * %s;\n        "
+                "break;\n}\n",
+                (int)_outer.mode, args.fOutputColor, args.fUniformHandler->getUniformCStr(colorVar),
+                args.fOutputColor, args.fInputColor, args.fUniformHandler->getUniformCStr(colorVar),
+                args.fOutputColor, args.fInputColor,
+                args.fUniformHandler->getUniformCStr(colorVar));
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {
+        const GrConstColorProcessor& _outer = _proc.cast<GrConstColorProcessor>();
+        {
+            const SkPMColor4f& colorValue = _outer.color;
+            if (colorPrev != colorValue) {
+                colorPrev = colorValue;
+                pdman.set4fv(colorVar, 1, colorValue.vec());
+            }
+        }
+    }
+    SkPMColor4f colorPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN};
+    UniformHandle colorVar;
+};
+GrGLSLFragmentProcessor* GrConstColorProcessor::onCreateGLSLInstance() const {
+    return new GrGLSLConstColorProcessor();
+}
+void GrConstColorProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                  GrProcessorKeyBuilder* b) const {
+    b->add32((int32_t)mode);
+}
+bool GrConstColorProcessor::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrConstColorProcessor& that = other.cast<GrConstColorProcessor>();
+    (void)that;
+    if (color != that.color) return false;
+    if (mode != that.mode) return false;
+    return true;
+}
+GrConstColorProcessor::GrConstColorProcessor(const GrConstColorProcessor& src)
+        : INHERITED(kGrConstColorProcessor_ClassID, src.optimizationFlags())
+        , color(src.color)
+        , mode(src.mode) {}
+std::unique_ptr<GrFragmentProcessor> GrConstColorProcessor::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrConstColorProcessor(*this));
+}
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrConstColorProcessor);
+#if GR_TEST_UTILS
+std::unique_ptr<GrFragmentProcessor> GrConstColorProcessor::TestCreate(GrProcessorTestData* d) {
+    SkPMColor4f color;
+    int colorPicker = d->fRandom->nextULessThan(3);
+    switch (colorPicker) {
+        case 0: {
+            uint32_t a = d->fRandom->nextULessThan(0x100);
+            uint32_t r = d->fRandom->nextULessThan(a + 1);
+            uint32_t g = d->fRandom->nextULessThan(a + 1);
+            uint32_t b = d->fRandom->nextULessThan(a + 1);
+            color = SkPMColor4f::FromBytes_RGBA(GrColorPackRGBA(r, g, b, a));
+            break;
+        }
+        case 1:
+            color = SK_PMColor4fTRANSPARENT;
+            break;
+        case 2:
+            uint32_t c = d->fRandom->nextULessThan(0x100);
+            color = SkPMColor4f::FromBytes_RGBA(c | (c << 8) | (c << 16) | (c << 24));
+            break;
+    }
+    InputMode mode = static_cast<InputMode>(d->fRandom->nextULessThan(kInputModeCnt));
+    return GrConstColorProcessor::Make(color, mode);
+}
+#endif
diff --git a/src/gpu/effects/generated/GrConstColorProcessor.h b/src/gpu/effects/generated/GrConstColorProcessor.h
new file mode 100644
index 0000000..9959cf5
--- /dev/null
+++ b/src/gpu/effects/generated/GrConstColorProcessor.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrConstColorProcessor.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrConstColorProcessor_DEFINED
+#define GrConstColorProcessor_DEFINED
+#include "SkTypes.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrConstColorProcessor : public GrFragmentProcessor {
+public:
+    enum class InputMode { kIgnore = 0, kLast = 2, kModulateA = 2, kModulateRGBA = 1 };
+
+    static const int kInputModeCnt = (int)InputMode::kLast + 1;
+
+    static OptimizationFlags OptFlags(const SkPMColor4f& color, InputMode mode) {
+        OptimizationFlags flags = kConstantOutputForConstantInput_OptimizationFlag;
+        if (mode != InputMode::kIgnore) {
+            flags |= kCompatibleWithCoverageAsAlpha_OptimizationFlag;
+        }
+        if (color.isOpaque()) {
+            flags |= kPreservesOpaqueInput_OptimizationFlag;
+        }
+        return flags;
+    }
+
+    SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override {
+        switch (mode) {
+            case InputMode::kIgnore:
+                return color;
+            case InputMode::kModulateA:
+                return color * input.fA;
+            case InputMode::kModulateRGBA:
+                return color * input;
+        }
+        SK_ABORT("Unexpected mode");
+        return SK_PMColor4fTRANSPARENT;
+    }
+    static std::unique_ptr<GrFragmentProcessor> Make(SkPMColor4f color, InputMode mode) {
+        return std::unique_ptr<GrFragmentProcessor>(new GrConstColorProcessor(color, mode));
+    }
+    GrConstColorProcessor(const GrConstColorProcessor& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "ConstColorProcessor"; }
+    SkPMColor4f color;
+    InputMode mode;
+
+private:
+    GrConstColorProcessor(SkPMColor4f color, InputMode mode)
+            : INHERITED(kGrConstColorProcessor_ClassID, (OptimizationFlags)OptFlags(color, mode))
+            , color(color)
+            , mode(mode) {}
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/effects/generated/GrEllipseEffect.cpp b/src/gpu/effects/generated/GrEllipseEffect.cpp
new file mode 100644
index 0000000..42998d0
--- /dev/null
+++ b/src/gpu/effects/generated/GrEllipseEffect.cpp
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrEllipseEffect.fp; do not modify.
+ **************************************************************************************************/
+#include "GrEllipseEffect.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLEllipseEffect : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLEllipseEffect() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrEllipseEffect& _outer = args.fFp.cast<GrEllipseEffect>();
+        (void)_outer;
+        auto edgeType = _outer.edgeType;
+        (void)edgeType;
+        auto center = _outer.center;
+        (void)center;
+        auto radii = _outer.radii;
+        (void)radii;
+        prevRadii = float2(-1.0);
+        medPrecision = !sk_Caps.floatIs32Bits;
+        ellipseVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
+                                                      "ellipse");
+        if (medPrecision) {
+            scaleVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat2_GrSLType,
+                                                        "scale");
+        }
+        fragBuilder->codeAppendf(
+                "float2 prevCenter;\nfloat2 prevRadii = float2(%f, %f);\nbool medPrecision = "
+                "%s;\nfloat2 d = sk_FragCoord.xy - %s.xy;\n@if (medPrecision) {\n    d *= "
+                "%s.y;\n}\nfloat2 Z = d * %s.zw;\nfloat implicit = dot(Z, d) - 1.0;\nfloat "
+                "grad_dot = 4.0 * dot(Z, Z);\n@if (medPrecision) {\n    grad_dot = max(grad_dot, "
+                "6.1036000000000003e-05);\n} else {\n    grad_dot = max(grad_dot, "
+                "1.1755e-38);\n}\nfloat approx_dist = implicit * inversesqrt(grad_dot);\n@if "
+                "(medPrecision) {\n    approx_dist *= %s.x;\n}\nhalf alpha;\n@switch ",
+                prevRadii.fX, prevRadii.fY, (medPrecision ? "true" : "false"),
+                args.fUniformHandler->getUniformCStr(ellipseVar),
+                scaleVar.isValid() ? args.fUniformHandler->getUniformCStr(scaleVar) : "float2(0)",
+                args.fUniformHandler->getUniformCStr(ellipseVar),
+                scaleVar.isValid() ? args.fUniformHandler->getUniformCStr(scaleVar) : "float2(0)");
+        fragBuilder->codeAppendf(
+                "(%d) {\n    case 0:\n        alpha = approx_dist > 0.0 ? 0.0 : 1.0;\n        "
+                "break;\n    case 1:\n        alpha = clamp(0.5 - half(approx_dist), 0.0, 1.0);\n  "
+                "      break;\n    case 2:\n        alpha = approx_dist > 0.0 ? 1.0 : 0.0;\n       "
+                " break;\n    case 3:\n        alpha = clamp(0.5 + half(approx_dist), 0.0, 1.0);\n "
+                "       break;\n    default:\n        discard;\n}\n%s = %s * alpha;\n",
+                (int)_outer.edgeType, args.fOutputColor, args.fInputColor);
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {
+        const GrEllipseEffect& _outer = _proc.cast<GrEllipseEffect>();
+        auto edgeType = _outer.edgeType;
+        (void)edgeType;
+        auto center = _outer.center;
+        (void)center;
+        auto radii = _outer.radii;
+        (void)radii;
+        UniformHandle& ellipse = ellipseVar;
+        (void)ellipse;
+        UniformHandle& scale = scaleVar;
+        (void)scale;
+
+        if (radii != prevRadii || center != prevCenter) {
+            float invRXSqd;
+            float invRYSqd;
+            // If we're using a scale factor to work around precision issues, choose the larger
+            // radius as the scale factor. The inv radii need to be pre-adjusted by the scale
+            // factor.
+            if (scale.isValid()) {
+                if (radii.fX > radii.fY) {
+                    invRXSqd = 1.f;
+                    invRYSqd = (radii.fX * radii.fX) / (radii.fY * radii.fY);
+                    pdman.set2f(scale, radii.fX, 1.f / radii.fX);
+                } else {
+                    invRXSqd = (radii.fY * radii.fY) / (radii.fX * radii.fX);
+                    invRYSqd = 1.f;
+                    pdman.set2f(scale, radii.fY, 1.f / radii.fY);
+                }
+            } else {
+                invRXSqd = 1.f / (radii.fX * radii.fX);
+                invRYSqd = 1.f / (radii.fY * radii.fY);
+            }
+            pdman.set4f(ellipse, center.fX, center.fY, invRXSqd, invRYSqd);
+            prevCenter = center;
+            prevRadii = radii;
+        }
+    }
+    SkPoint prevCenter = float2(0);
+    SkPoint prevRadii = float2(0);
+    bool medPrecision = false;
+    UniformHandle ellipseVar;
+    UniformHandle scaleVar;
+};
+GrGLSLFragmentProcessor* GrEllipseEffect::onCreateGLSLInstance() const {
+    return new GrGLSLEllipseEffect();
+}
+void GrEllipseEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                            GrProcessorKeyBuilder* b) const {
+    b->add32((int32_t)edgeType);
+}
+bool GrEllipseEffect::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrEllipseEffect& that = other.cast<GrEllipseEffect>();
+    (void)that;
+    if (edgeType != that.edgeType) return false;
+    if (center != that.center) return false;
+    if (radii != that.radii) return false;
+    return true;
+}
+GrEllipseEffect::GrEllipseEffect(const GrEllipseEffect& src)
+        : INHERITED(kGrEllipseEffect_ClassID, src.optimizationFlags())
+        , edgeType(src.edgeType)
+        , center(src.center)
+        , radii(src.radii) {}
+std::unique_ptr<GrFragmentProcessor> GrEllipseEffect::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrEllipseEffect(*this));
+}
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrEllipseEffect);
+#if GR_TEST_UTILS
+std::unique_ptr<GrFragmentProcessor> GrEllipseEffect::TestCreate(GrProcessorTestData* testData) {
+    SkPoint center;
+    center.fX = testData->fRandom->nextRangeScalar(0.f, 1000.f);
+    center.fY = testData->fRandom->nextRangeScalar(0.f, 1000.f);
+    SkScalar rx = testData->fRandom->nextRangeF(0.f, 1000.f);
+    SkScalar ry = testData->fRandom->nextRangeF(0.f, 1000.f);
+    GrClipEdgeType et;
+    do {
+        et = (GrClipEdgeType)testData->fRandom->nextULessThan(kGrClipEdgeTypeCnt);
+    } while (GrClipEdgeType::kHairlineAA == et);
+    return GrEllipseEffect::Make(et, center, SkPoint::Make(rx, ry),
+                                 *testData->caps()->shaderCaps());
+}
+#endif
diff --git a/src/gpu/effects/generated/GrEllipseEffect.h b/src/gpu/effects/generated/GrEllipseEffect.h
new file mode 100644
index 0000000..c2a76d8
--- /dev/null
+++ b/src/gpu/effects/generated/GrEllipseEffect.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrEllipseEffect.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrEllipseEffect_DEFINED
+#define GrEllipseEffect_DEFINED
+#include "SkTypes.h"
+
+#include "GrShaderCaps.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrEllipseEffect : public GrFragmentProcessor {
+public:
+    static std::unique_ptr<GrFragmentProcessor> Make(GrClipEdgeType edgeType, SkPoint center,
+                                                     SkPoint radii, const GrShaderCaps& caps) {
+        // Small radii produce bad results on devices without full float.
+        if (!caps.floatIs32Bits() && (radii.fX < 0.5f || radii.fY < 0.5f)) {
+            return nullptr;
+        }
+        // Very narrow ellipses produce bad results on devices without full float
+        if (!caps.floatIs32Bits() && (radii.fX > 255 * radii.fY || radii.fY > 255 * radii.fX)) {
+            return nullptr;
+        }
+        // Very large ellipses produce bad results on devices without full float
+        if (!caps.floatIs32Bits() && (radii.fX > 16384 || radii.fY > 16384)) {
+            return nullptr;
+        }
+        return std::unique_ptr<GrFragmentProcessor>(new GrEllipseEffect(edgeType, center, radii));
+    }
+    GrEllipseEffect(const GrEllipseEffect& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "EllipseEffect"; }
+    GrClipEdgeType edgeType;
+    SkPoint center;
+    SkPoint radii;
+
+private:
+    GrEllipseEffect(GrClipEdgeType edgeType, SkPoint center, SkPoint radii)
+            : INHERITED(kGrEllipseEffect_ClassID,
+                        (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag)
+            , edgeType(edgeType)
+            , center(center)
+            , radii(radii) {}
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/effects/generated/GrLumaColorFilterEffect.cpp b/src/gpu/effects/generated/GrLumaColorFilterEffect.cpp
new file mode 100644
index 0000000..d0b1e1c
--- /dev/null
+++ b/src/gpu/effects/generated/GrLumaColorFilterEffect.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrLumaColorFilterEffect.fp; do not modify.
+ **************************************************************************************************/
+#include "GrLumaColorFilterEffect.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLLumaColorFilterEffect : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLLumaColorFilterEffect() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrLumaColorFilterEffect& _outer = args.fFp.cast<GrLumaColorFilterEffect>();
+        (void)_outer;
+        fragBuilder->codeAppendf(
+                "\nhalf luma = clamp(dot(half3(0.21260000000000001, 0.71519999999999995, 0.0722), "
+                "%s.xyz), 0.0, 1.0);\n%s = half4(0.0, 0.0, 0.0, luma);\n",
+                args.fInputColor, args.fOutputColor);
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {}
+};
+GrGLSLFragmentProcessor* GrLumaColorFilterEffect::onCreateGLSLInstance() const {
+    return new GrGLSLLumaColorFilterEffect();
+}
+void GrLumaColorFilterEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                    GrProcessorKeyBuilder* b) const {}
+bool GrLumaColorFilterEffect::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrLumaColorFilterEffect& that = other.cast<GrLumaColorFilterEffect>();
+    (void)that;
+    return true;
+}
+GrLumaColorFilterEffect::GrLumaColorFilterEffect(const GrLumaColorFilterEffect& src)
+        : INHERITED(kGrLumaColorFilterEffect_ClassID, src.optimizationFlags()) {}
+std::unique_ptr<GrFragmentProcessor> GrLumaColorFilterEffect::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrLumaColorFilterEffect(*this));
+}
diff --git a/src/gpu/effects/generated/GrLumaColorFilterEffect.h b/src/gpu/effects/generated/GrLumaColorFilterEffect.h
new file mode 100644
index 0000000..c93f6a3
--- /dev/null
+++ b/src/gpu/effects/generated/GrLumaColorFilterEffect.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrLumaColorFilterEffect.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrLumaColorFilterEffect_DEFINED
+#define GrLumaColorFilterEffect_DEFINED
+#include "SkTypes.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrLumaColorFilterEffect : public GrFragmentProcessor {
+public:
+#include "SkColorData.h"
+
+    SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override {
+        float luma = SK_ITU_BT709_LUM_COEFF_R * input.fR + SK_ITU_BT709_LUM_COEFF_G * input.fG +
+                     SK_ITU_BT709_LUM_COEFF_B * input.fB;
+        return {0, 0, 0, SkTPin(luma, 0.0f, 1.0f)};
+    }
+    static std::unique_ptr<GrFragmentProcessor> Make() {
+        return std::unique_ptr<GrFragmentProcessor>(new GrLumaColorFilterEffect());
+    }
+    GrLumaColorFilterEffect(const GrLumaColorFilterEffect& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "LumaColorFilterEffect"; }
+
+private:
+    GrLumaColorFilterEffect()
+            : INHERITED(kGrLumaColorFilterEffect_ClassID, kNone_OptimizationFlags) {}
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/effects/generated/GrMagnifierEffect.cpp b/src/gpu/effects/generated/GrMagnifierEffect.cpp
new file mode 100644
index 0000000..3e34da7
--- /dev/null
+++ b/src/gpu/effects/generated/GrMagnifierEffect.cpp
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrMagnifierEffect.fp; do not modify.
+ **************************************************************************************************/
+#include "GrMagnifierEffect.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLMagnifierEffect : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLMagnifierEffect() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrMagnifierEffect& _outer = args.fFp.cast<GrMagnifierEffect>();
+        (void)_outer;
+        auto bounds = _outer.bounds;
+        (void)bounds;
+        auto srcRect = _outer.srcRect;
+        (void)srcRect;
+        auto xInvZoom = _outer.xInvZoom;
+        (void)xInvZoom;
+        auto yInvZoom = _outer.yInvZoom;
+        (void)yInvZoom;
+        auto xInvInset = _outer.xInvInset;
+        (void)xInvInset;
+        auto yInvInset = _outer.yInvInset;
+        (void)yInvInset;
+        boundsUniformVar = args.fUniformHandler->addUniform(
+                kFragment_GrShaderFlag, kFloat4_GrSLType, "boundsUniform");
+        xInvZoomVar = args.fUniformHandler->addUniform(
+                kFragment_GrShaderFlag, kFloat_GrSLType, "xInvZoom");
+        yInvZoomVar = args.fUniformHandler->addUniform(
+                kFragment_GrShaderFlag, kFloat_GrSLType, "yInvZoom");
+        xInvInsetVar = args.fUniformHandler->addUniform(
+                kFragment_GrShaderFlag, kFloat_GrSLType, "xInvInset");
+        yInvInsetVar = args.fUniformHandler->addUniform(
+                kFragment_GrShaderFlag, kFloat_GrSLType, "yInvInset");
+        offsetVar =
+                args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, "offset");
+        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+        fragBuilder->codeAppendf(
+                "float2 coord = %s;\nfloat2 zoom_coord = float2(%s) + coord * float2(%s, "
+                "%s);\nfloat2 delta = (coord - %s.xy) * %s.zw;\ndelta = min(delta, "
+                "float2(half2(1.0, 1.0)) - delta);\ndelta *= float2(%s, %s);\nfloat weight = "
+                "0.0;\nif (delta.x < 2.0 && delta.y < 2.0) {\n    delta = float2(half2(2.0, 2.0)) "
+                "- delta;\n    float dist = length(delta);\n    dist = max(2.0 - dist, 0.0);\n    "
+                "weight = min(dist * dist, 1.0);\n} else {\n    float2 delta_squared = delta * "
+                "delta;\n    weight = min(min(delta_squared.x, delta_square",
+                sk_TransformedCoords2D_0.c_str(),
+                args.fUniformHandler->getUniformCStr(offsetVar),
+                args.fUniformHandler->getUniformCStr(xInvZoomVar),
+                args.fUniformHandler->getUniformCStr(yInvZoomVar),
+                args.fUniformHandler->getUniformCStr(boundsUniformVar),
+                args.fUniformHandler->getUniformCStr(boundsUniformVar),
+                args.fUniformHandler->getUniformCStr(xInvInsetVar),
+                args.fUniformHandler->getUniformCStr(yInvInsetVar));
+        fragBuilder->codeAppendf(
+                "d.y), 1.0);\n}\n%s = texture(%s, mix(coord, zoom_coord, weight)).%s;\n",
+                args.fOutputColor,
+                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(),
+                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str());
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {
+        const GrMagnifierEffect& _outer = _proc.cast<GrMagnifierEffect>();
+        {
+            pdman.set1f(xInvZoomVar, (_outer.xInvZoom));
+            pdman.set1f(yInvZoomVar, (_outer.yInvZoom));
+            pdman.set1f(xInvInsetVar, (_outer.xInvInset));
+            pdman.set1f(yInvInsetVar, (_outer.yInvInset));
+        }
+        GrSurfaceProxy& srcProxy = *_outer.textureSampler(0).proxy();
+        GrTexture& src = *srcProxy.peekTexture();
+        (void)src;
+        auto bounds = _outer.bounds;
+        (void)bounds;
+        UniformHandle& boundsUniform = boundsUniformVar;
+        (void)boundsUniform;
+        auto srcRect = _outer.srcRect;
+        (void)srcRect;
+        UniformHandle& xInvZoom = xInvZoomVar;
+        (void)xInvZoom;
+        UniformHandle& yInvZoom = yInvZoomVar;
+        (void)yInvZoom;
+        UniformHandle& xInvInset = xInvInsetVar;
+        (void)xInvInset;
+        UniformHandle& yInvInset = yInvInsetVar;
+        (void)yInvInset;
+        UniformHandle& offset = offsetVar;
+        (void)offset;
+
+        SkScalar invW = 1.0f / src.width();
+        SkScalar invH = 1.0f / src.height();
+
+        {
+            SkScalar y = srcRect.y() * invH;
+            if (srcProxy.origin() != kTopLeft_GrSurfaceOrigin) {
+                y = 1.0f - (srcRect.height() / bounds.height()) - y;
+            }
+
+            pdman.set2f(offset, srcRect.x() * invW, y);
+        }
+
+        {
+            SkScalar y = bounds.y() * invH;
+            if (srcProxy.origin() != kTopLeft_GrSurfaceOrigin) {
+                y = 1.0f - bounds.height() * invH;
+            }
+
+            pdman.set4f(boundsUniform,
+                        bounds.x() * invW,
+                        y,
+                        SkIntToScalar(src.width()) / bounds.width(),
+                        SkIntToScalar(src.height()) / bounds.height());
+        }
+    }
+    UniformHandle boundsUniformVar;
+    UniformHandle offsetVar;
+    UniformHandle xInvZoomVar;
+    UniformHandle yInvZoomVar;
+    UniformHandle xInvInsetVar;
+    UniformHandle yInvInsetVar;
+};
+GrGLSLFragmentProcessor* GrMagnifierEffect::onCreateGLSLInstance() const {
+    return new GrGLSLMagnifierEffect();
+}
+void GrMagnifierEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                              GrProcessorKeyBuilder* b) const {}
+bool GrMagnifierEffect::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrMagnifierEffect& that = other.cast<GrMagnifierEffect>();
+    (void)that;
+    if (src != that.src) return false;
+    if (bounds != that.bounds) return false;
+    if (srcRect != that.srcRect) return false;
+    if (xInvZoom != that.xInvZoom) return false;
+    if (yInvZoom != that.yInvZoom) return false;
+    if (xInvInset != that.xInvInset) return false;
+    if (yInvInset != that.yInvInset) return false;
+    return true;
+}
+GrMagnifierEffect::GrMagnifierEffect(const GrMagnifierEffect& src)
+        : INHERITED(kGrMagnifierEffect_ClassID, src.optimizationFlags())
+        , srcCoordTransform(src.srcCoordTransform)
+        , src(src.src)
+        , bounds(src.bounds)
+        , srcRect(src.srcRect)
+        , xInvZoom(src.xInvZoom)
+        , yInvZoom(src.yInvZoom)
+        , xInvInset(src.xInvInset)
+        , yInvInset(src.yInvInset) {
+    this->setTextureSamplerCnt(1);
+    this->addCoordTransform(&srcCoordTransform);
+}
+std::unique_ptr<GrFragmentProcessor> GrMagnifierEffect::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrMagnifierEffect(*this));
+}
+const GrFragmentProcessor::TextureSampler& GrMagnifierEffect::onTextureSampler(int index) const {
+    return IthTextureSampler(index, src);
+}
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrMagnifierEffect);
+#if GR_TEST_UTILS
+std::unique_ptr<GrFragmentProcessor> GrMagnifierEffect::TestCreate(GrProcessorTestData* d) {
+    sk_sp<GrTextureProxy> proxy = d->textureProxy(0);
+    const int kMaxWidth = 200;
+    const int kMaxHeight = 200;
+    const SkScalar kMaxInset = 20.0f;
+    uint32_t width = d->fRandom->nextULessThan(kMaxWidth);
+    uint32_t height = d->fRandom->nextULessThan(kMaxHeight);
+    SkScalar inset = d->fRandom->nextRangeScalar(1.0f, kMaxInset);
+
+    SkIRect bounds = SkIRect::MakeWH(SkIntToScalar(kMaxWidth), SkIntToScalar(kMaxHeight));
+    SkRect srcRect = SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height));
+
+    auto effect = GrMagnifierEffect::Make(std::move(proxy),
+                                          bounds,
+                                          srcRect,
+                                          srcRect.width() / bounds.width(),
+                                          srcRect.height() / bounds.height(),
+                                          bounds.width() / inset,
+                                          bounds.height() / inset);
+    SkASSERT(effect);
+    return effect;
+}
+#endif
diff --git a/src/gpu/effects/generated/GrMagnifierEffect.h b/src/gpu/effects/generated/GrMagnifierEffect.h
new file mode 100644
index 0000000..c1bf22b
--- /dev/null
+++ b/src/gpu/effects/generated/GrMagnifierEffect.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrMagnifierEffect.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrMagnifierEffect_DEFINED
+#define GrMagnifierEffect_DEFINED
+#include "SkTypes.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrMagnifierEffect : public GrFragmentProcessor {
+public:
+    static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy> src, SkIRect bounds,
+                                                     SkRect srcRect, float xInvZoom, float yInvZoom,
+                                                     float xInvInset, float yInvInset) {
+        return std::unique_ptr<GrFragmentProcessor>(new GrMagnifierEffect(
+                src, bounds, srcRect, xInvZoom, yInvZoom, xInvInset, yInvInset));
+    }
+    GrMagnifierEffect(const GrMagnifierEffect& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "MagnifierEffect"; }
+    GrCoordTransform srcCoordTransform;
+    TextureSampler src;
+    SkIRect bounds;
+    SkRect srcRect;
+    float xInvZoom;
+    float yInvZoom;
+    float xInvInset;
+    float yInvInset;
+
+private:
+    GrMagnifierEffect(sk_sp<GrTextureProxy> src, SkIRect bounds, SkRect srcRect, float xInvZoom,
+                      float yInvZoom, float xInvInset, float yInvInset)
+            : INHERITED(kGrMagnifierEffect_ClassID, kNone_OptimizationFlags)
+            , srcCoordTransform(SkMatrix::I(), src.get())
+            , src(std::move(src))
+            , bounds(bounds)
+            , srcRect(srcRect)
+            , xInvZoom(xInvZoom)
+            , yInvZoom(yInvZoom)
+            , xInvInset(xInvInset)
+            , yInvInset(yInvInset) {
+        this->setTextureSamplerCnt(1);
+        this->addCoordTransform(&srcCoordTransform);
+    }
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    const TextureSampler& onTextureSampler(int) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/effects/generated/GrMixerEffect.cpp b/src/gpu/effects/generated/GrMixerEffect.cpp
new file mode 100644
index 0000000..98e9a47
--- /dev/null
+++ b/src/gpu/effects/generated/GrMixerEffect.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrMixerEffect.fp; do not modify.
+ **************************************************************************************************/
+#include "GrMixerEffect.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLMixerEffect : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLMixerEffect() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrMixerEffect& _outer = args.fFp.cast<GrMixerEffect>();
+        (void)_outer;
+        auto weight = _outer.weight;
+        (void)weight;
+        weightVar =
+                args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, "weight");
+        SkString _input0 = SkStringPrintf("%s", args.fInputColor);
+        SkString _child0("_child0");
+        this->emitChild(_outer.fp0_index, _input0.c_str(), &_child0, args);
+        fragBuilder->codeAppendf("half4 in0 = %s;", _child0.c_str());
+        SkString _input1 = SkStringPrintf("%s", args.fInputColor);
+        SkString _child1("_child1");
+        if (_outer.fp1_index >= 0) {
+            this->emitChild(_outer.fp1_index, _input1.c_str(), &_child1, args);
+        } else {
+            fragBuilder->codeAppendf("half4 %s;", _child1.c_str());
+        }
+        fragBuilder->codeAppendf("\nhalf4 in1 = %s ? %s : %s;\n%s = mix(in0, in1, %s);\n",
+                                 _outer.fp1_index >= 0 ? "true" : "false", _child1.c_str(),
+                                 args.fInputColor, args.fOutputColor,
+                                 args.fUniformHandler->getUniformCStr(weightVar));
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {
+        const GrMixerEffect& _outer = _proc.cast<GrMixerEffect>();
+        { pdman.set1f(weightVar, (_outer.weight)); }
+    }
+    UniformHandle weightVar;
+};
+GrGLSLFragmentProcessor* GrMixerEffect::onCreateGLSLInstance() const {
+    return new GrGLSLMixerEffect();
+}
+void GrMixerEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                          GrProcessorKeyBuilder* b) const {}
+bool GrMixerEffect::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrMixerEffect& that = other.cast<GrMixerEffect>();
+    (void)that;
+    if (weight != that.weight) return false;
+    return true;
+}
+GrMixerEffect::GrMixerEffect(const GrMixerEffect& src)
+        : INHERITED(kGrMixerEffect_ClassID, src.optimizationFlags())
+        , fp0_index(src.fp0_index)
+        , fp1_index(src.fp1_index)
+        , weight(src.weight) {
+    this->registerChildProcessor(src.childProcessor(fp0_index).clone());
+    if (fp1_index >= 0) {
+        this->registerChildProcessor(src.childProcessor(fp1_index).clone());
+    }
+}
+std::unique_ptr<GrFragmentProcessor> GrMixerEffect::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrMixerEffect(*this));
+}
diff --git a/src/gpu/effects/generated/GrMixerEffect.h b/src/gpu/effects/generated/GrMixerEffect.h
new file mode 100644
index 0000000..d318285
--- /dev/null
+++ b/src/gpu/effects/generated/GrMixerEffect.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrMixerEffect.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrMixerEffect_DEFINED
+#define GrMixerEffect_DEFINED
+#include "SkTypes.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrMixerEffect : public GrFragmentProcessor {
+public:
+    static OptimizationFlags OptFlags(const std::unique_ptr<GrFragmentProcessor>& fp0,
+                                      const std::unique_ptr<GrFragmentProcessor>& fp1) {
+        auto get_flags = [](const std::unique_ptr<GrFragmentProcessor>& fp) {
+            auto flags = kNone_OptimizationFlags;
+
+            if (fp->compatibleWithCoverageAsAlpha()) {
+                flags |= kCompatibleWithCoverageAsAlpha_OptimizationFlag;
+            }
+
+            if (fp->preservesOpaqueInput()) {
+                flags |= kPreservesOpaqueInput_OptimizationFlag;
+            }
+
+            if (fp->hasConstantOutputForConstantInput()) {
+                flags |= kConstantOutputForConstantInput_OptimizationFlag;
+            }
+
+            return flags;
+        };
+
+        const auto fp0_flags = get_flags(fp0);
+
+        return fp1 ? (fp0_flags & get_flags(fp1)) : fp0_flags;
+    }
+
+    SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override {
+        const auto c0 = ConstantOutputForConstantInput(this->childProcessor(0), input),
+                   c1 = (this->numChildProcessors() > 1)
+                                ? ConstantOutputForConstantInput(this->childProcessor(1), input)
+                                : input;
+        return {c0.fR + (c1.fR - c0.fR) * weight, c0.fG + (c1.fG - c0.fG) * weight,
+                c0.fB + (c1.fB - c0.fB) * weight, c0.fA + (c1.fA - c0.fA) * weight};
+    }
+    static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> fp0,
+                                                     std::unique_ptr<GrFragmentProcessor>
+                                                             fp1,
+                                                     float weight) {
+        return std::unique_ptr<GrFragmentProcessor>(
+                new GrMixerEffect(std::move(fp0), std::move(fp1), weight));
+    }
+    GrMixerEffect(const GrMixerEffect& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "MixerEffect"; }
+    int fp0_index = -1;
+    int fp1_index = -1;
+    float weight;
+
+private:
+    GrMixerEffect(std::unique_ptr<GrFragmentProcessor> fp0,
+                  std::unique_ptr<GrFragmentProcessor>
+                          fp1,
+                  float weight)
+            : INHERITED(kGrMixerEffect_ClassID, (OptimizationFlags)OptFlags(fp0, fp1))
+            , weight(weight) {
+        SkASSERT(fp0);
+        fp0_index = this->numChildProcessors();
+        this->registerChildProcessor(std::move(fp0));
+        if (fp1) {
+            fp1_index = this->numChildProcessors();
+            this->registerChildProcessor(std::move(fp1));
+        }
+    }
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/effects/GrPremulInputFragmentProcessor.cpp b/src/gpu/effects/generated/GrPremulInputFragmentProcessor.cpp
similarity index 100%
rename from src/gpu/effects/GrPremulInputFragmentProcessor.cpp
rename to src/gpu/effects/generated/GrPremulInputFragmentProcessor.cpp
diff --git a/src/gpu/effects/GrPremulInputFragmentProcessor.h b/src/gpu/effects/generated/GrPremulInputFragmentProcessor.h
similarity index 100%
rename from src/gpu/effects/GrPremulInputFragmentProcessor.h
rename to src/gpu/effects/generated/GrPremulInputFragmentProcessor.h
diff --git a/src/gpu/effects/generated/GrRRectBlurEffect.cpp b/src/gpu/effects/generated/GrRRectBlurEffect.cpp
new file mode 100644
index 0000000..878678c
--- /dev/null
+++ b/src/gpu/effects/generated/GrRRectBlurEffect.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrRRectBlurEffect.fp; do not modify.
+ **************************************************************************************************/
+#include "GrRRectBlurEffect.h"
+
+std::unique_ptr<GrFragmentProcessor> GrRRectBlurEffect::Make(GrRecordingContext* context,
+                                                             float sigma,
+                                                             float xformedSigma,
+                                                             const SkRRect& srcRRect,
+                                                             const SkRRect& devRRect) {
+    SkASSERT(!SkRRectPriv::IsCircle(devRRect) &&
+             !devRRect.isRect());  // Should've been caught up-stream
+
+    // TODO: loosen this up
+    if (!SkRRectPriv::IsSimpleCircular(devRRect)) {
+        return nullptr;
+    }
+
+    // Make sure we can successfully ninepatch this rrect -- the blur sigma has to be
+    // sufficiently small relative to both the size of the corner radius and the
+    // width (and height) of the rrect.
+    SkRRect rrectToDraw;
+    SkISize size;
+    SkScalar ignored[kSkBlurRRectMaxDivisions];
+    int ignoredSize;
+    uint32_t ignored32;
+
+    bool ninePatchable = SkComputeBlurredRRectParams(
+            srcRRect, devRRect, SkRect::MakeEmpty(), sigma, xformedSigma, &rrectToDraw, &size,
+            ignored, ignored, ignored, ignored, &ignoredSize, &ignoredSize, &ignored32);
+    if (!ninePatchable) {
+        return nullptr;
+    }
+
+    sk_sp<GrTextureProxy> mask(
+            find_or_create_rrect_blur_mask(context, rrectToDraw, size, xformedSigma));
+    if (!mask) {
+        return nullptr;
+    }
+
+    return std::unique_ptr<GrFragmentProcessor>(
+            new GrRRectBlurEffect(xformedSigma, devRRect.getBounds(),
+                                  SkRRectPriv::GetSimpleRadii(devRRect).fX, std::move(mask)));
+}
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLRRectBlurEffect : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLRRectBlurEffect() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrRRectBlurEffect& _outer = args.fFp.cast<GrRRectBlurEffect>();
+        (void)_outer;
+        auto sigma = _outer.sigma;
+        (void)sigma;
+        auto rect = _outer.rect;
+        (void)rect;
+        auto cornerRadius = _outer.cornerRadius;
+        (void)cornerRadius;
+        cornerRadiusVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType,
+                                                           "cornerRadius");
+        proxyRectVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
+                                                        "proxyRect");
+        blurRadiusVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType,
+                                                         "blurRadius");
+        fragBuilder->codeAppendf(
+                "\nhalf2 translatedFragPos = half2(sk_FragCoord.xy - %s.xy);\nhalf threshold = %s "
+                "+ 2.0 * %s;\nhalf2 middle = half2((%s.zw - %s.xy) - float(2.0 * threshold));\nif "
+                "(translatedFragPos.x >= threshold && translatedFragPos.x < middle.x + threshold) "
+                "{\n    translatedFragPos.x = threshold;\n} else if (translatedFragPos.x >= "
+                "middle.x + threshold) {\n    translatedFragPos.x -= middle.x - 1.0;\n}\nif "
+                "(translatedFragPos.y > threshold && translatedFragPos.y < middle.y + threshold) "
+                "{\n    translatedFragPos.y = threshold;",
+                args.fUniformHandler->getUniformCStr(proxyRectVar),
+                args.fUniformHandler->getUniformCStr(cornerRadiusVar),
+                args.fUniformHandler->getUniformCStr(blurRadiusVar),
+                args.fUniformHandler->getUniformCStr(proxyRectVar),
+                args.fUniformHandler->getUniformCStr(proxyRectVar));
+        fragBuilder->codeAppendf(
+                "\n} else if (translatedFragPos.y >= middle.y + threshold) {\n    "
+                "translatedFragPos.y -= middle.y - 1.0;\n}\nhalf2 proxyDims = half2(2.0 * "
+                "threshold + 1.0);\nhalf2 texCoord = translatedFragPos / proxyDims;\n%s = %s * "
+                "texture(%s, float2(texCoord)).%s;\n",
+                args.fOutputColor, args.fInputColor,
+                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(),
+                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str());
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {
+        const GrRRectBlurEffect& _outer = _proc.cast<GrRRectBlurEffect>();
+        { pdman.set1f(cornerRadiusVar, (_outer.cornerRadius)); }
+        auto sigma = _outer.sigma;
+        (void)sigma;
+        auto rect = _outer.rect;
+        (void)rect;
+        UniformHandle& cornerRadius = cornerRadiusVar;
+        (void)cornerRadius;
+        GrSurfaceProxy& ninePatchSamplerProxy = *_outer.textureSampler(0).proxy();
+        GrTexture& ninePatchSampler = *ninePatchSamplerProxy.peekTexture();
+        (void)ninePatchSampler;
+        UniformHandle& proxyRect = proxyRectVar;
+        (void)proxyRect;
+        UniformHandle& blurRadius = blurRadiusVar;
+        (void)blurRadius;
+
+        float blurRadiusValue = 3.f * SkScalarCeilToScalar(sigma - 1 / 6.0f);
+        pdman.set1f(blurRadius, blurRadiusValue);
+
+        SkRect outset = rect;
+        outset.outset(blurRadiusValue, blurRadiusValue);
+        pdman.set4f(proxyRect, outset.fLeft, outset.fTop, outset.fRight, outset.fBottom);
+    }
+    UniformHandle proxyRectVar;
+    UniformHandle blurRadiusVar;
+    UniformHandle cornerRadiusVar;
+};
+GrGLSLFragmentProcessor* GrRRectBlurEffect::onCreateGLSLInstance() const {
+    return new GrGLSLRRectBlurEffect();
+}
+void GrRRectBlurEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                              GrProcessorKeyBuilder* b) const {}
+bool GrRRectBlurEffect::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrRRectBlurEffect& that = other.cast<GrRRectBlurEffect>();
+    (void)that;
+    if (sigma != that.sigma) return false;
+    if (rect != that.rect) return false;
+    if (cornerRadius != that.cornerRadius) return false;
+    if (ninePatchSampler != that.ninePatchSampler) return false;
+    return true;
+}
+GrRRectBlurEffect::GrRRectBlurEffect(const GrRRectBlurEffect& src)
+        : INHERITED(kGrRRectBlurEffect_ClassID, src.optimizationFlags())
+        , sigma(src.sigma)
+        , rect(src.rect)
+        , cornerRadius(src.cornerRadius)
+        , ninePatchSampler(src.ninePatchSampler) {
+    this->setTextureSamplerCnt(1);
+}
+std::unique_ptr<GrFragmentProcessor> GrRRectBlurEffect::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrRRectBlurEffect(*this));
+}
+const GrFragmentProcessor::TextureSampler& GrRRectBlurEffect::onTextureSampler(int index) const {
+    return IthTextureSampler(index, ninePatchSampler);
+}
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRRectBlurEffect);
+#if GR_TEST_UTILS
+std::unique_ptr<GrFragmentProcessor> GrRRectBlurEffect::TestCreate(GrProcessorTestData* d) {
+    SkScalar w = d->fRandom->nextRangeScalar(100.f, 1000.f);
+    SkScalar h = d->fRandom->nextRangeScalar(100.f, 1000.f);
+    SkScalar r = d->fRandom->nextRangeF(1.f, 9.f);
+    SkScalar sigma = d->fRandom->nextRangeF(1.f, 10.f);
+    SkRRect rrect;
+    rrect.setRectXY(SkRect::MakeWH(w, h), r, r);
+    return GrRRectBlurEffect::Make(d->context(), sigma, sigma, rrect, rrect);
+}
+#endif
diff --git a/src/gpu/effects/generated/GrRRectBlurEffect.h b/src/gpu/effects/generated/GrRRectBlurEffect.h
new file mode 100644
index 0000000..7c7da9a
--- /dev/null
+++ b/src/gpu/effects/generated/GrRRectBlurEffect.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrRRectBlurEffect.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrRRectBlurEffect_DEFINED
+#define GrRRectBlurEffect_DEFINED
+#include "SkTypes.h"
+
+#include "GrCaps.h"
+#include "GrClip.h"
+#include "GrContext.h"
+#include "GrPaint.h"
+#include "GrProxyProvider.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
+#include "GrRenderTargetContext.h"
+#include "GrStyle.h"
+#include "SkBlurMaskFilter.h"
+#include "SkBlurPriv.h"
+#include "SkGpuBlurUtils.h"
+#include "SkRRectPriv.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrRRectBlurEffect : public GrFragmentProcessor {
+public:
+    static sk_sp<GrTextureProxy> find_or_create_rrect_blur_mask(GrRecordingContext* context,
+                                                                const SkRRect& rrectToDraw,
+                                                                const SkISize& size,
+                                                                float xformedSigma) {
+        static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
+        GrUniqueKey key;
+        GrUniqueKey::Builder builder(&key, kDomain, 9, "RoundRect Blur Mask");
+        builder[0] = SkScalarCeilToInt(xformedSigma - 1 / 6.0f);
+
+        int index = 1;
+        for (auto c : {SkRRect::kUpperLeft_Corner, SkRRect::kUpperRight_Corner,
+                       SkRRect::kLowerRight_Corner, SkRRect::kLowerLeft_Corner}) {
+            SkASSERT(SkScalarIsInt(rrectToDraw.radii(c).fX) &&
+                     SkScalarIsInt(rrectToDraw.radii(c).fY));
+            builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fX);
+            builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fY);
+        }
+        builder.finish();
+
+        GrProxyProvider* proxyProvider = context->priv().proxyProvider();
+
+        sk_sp<GrTextureProxy> mask(
+                proxyProvider->findOrCreateProxyByUniqueKey(key, kBottomLeft_GrSurfaceOrigin));
+        if (!mask) {
+            GrBackendFormat format =
+                    context->priv().caps()->getBackendFormatFromColorType(kAlpha_8_SkColorType);
+            // TODO: this could be approx but the texture coords will need to be updated
+            sk_sp<GrRenderTargetContext> rtc(
+                    context->priv().makeDeferredRenderTargetContextWithFallback(
+                            format, SkBackingFit::kExact, size.fWidth, size.fHeight,
+                            kAlpha_8_GrPixelConfig, nullptr));
+            if (!rtc) {
+                return nullptr;
+            }
+
+            GrPaint paint;
+
+            rtc->clear(nullptr, SK_PMColor4fTRANSPARENT,
+                       GrRenderTargetContext::CanClearFullscreen::kYes);
+            rtc->drawRRect(GrNoClip(), std::move(paint), GrAA::kYes, SkMatrix::I(), rrectToDraw,
+                           GrStyle::SimpleFill());
+
+            sk_sp<GrTextureProxy> srcProxy(rtc->asTextureProxyRef());
+            if (!srcProxy) {
+                return nullptr;
+            }
+            sk_sp<GrRenderTargetContext> rtc2(
+                    SkGpuBlurUtils::GaussianBlur(context,
+                                                 std::move(srcProxy),
+                                                 nullptr,
+                                                 SkIRect::MakeWH(size.fWidth, size.fHeight),
+                                                 SkIRect::EmptyIRect(),
+                                                 xformedSigma,
+                                                 xformedSigma,
+                                                 GrTextureDomain::kIgnore_Mode,
+                                                 kPremul_SkAlphaType,
+                                                 SkBackingFit::kExact));
+            if (!rtc2) {
+                return nullptr;
+            }
+
+            mask = rtc2->asTextureProxyRef();
+            if (!mask) {
+                return nullptr;
+            }
+            SkASSERT(mask->origin() == kBottomLeft_GrSurfaceOrigin);
+            proxyProvider->assignUniqueKeyToProxy(key, mask.get());
+        }
+
+        return mask;
+    }
+
+    static std::unique_ptr<GrFragmentProcessor> Make(GrRecordingContext* context,
+                                                     float sigma,
+                                                     float xformedSigma,
+                                                     const SkRRect& srcRRect,
+                                                     const SkRRect& devRRect);
+    GrRRectBlurEffect(const GrRRectBlurEffect& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "RRectBlurEffect"; }
+    float sigma;
+    SkRect rect;
+    float cornerRadius;
+    TextureSampler ninePatchSampler;
+
+private:
+    GrRRectBlurEffect(float sigma, SkRect rect, float cornerRadius,
+                      sk_sp<GrTextureProxy> ninePatchSampler)
+            : INHERITED(kGrRRectBlurEffect_ClassID,
+                        (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag)
+            , sigma(sigma)
+            , rect(rect)
+            , cornerRadius(cornerRadius)
+            , ninePatchSampler(std::move(ninePatchSampler)) {
+        this->setTextureSamplerCnt(1);
+    }
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    const TextureSampler& onTextureSampler(int) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/effects/generated/GrRectBlurEffect.cpp b/src/gpu/effects/generated/GrRectBlurEffect.cpp
new file mode 100644
index 0000000..b03c8f9
--- /dev/null
+++ b/src/gpu/effects/generated/GrRectBlurEffect.cpp
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrRectBlurEffect.fp; do not modify.
+ **************************************************************************************************/
+#include "GrRectBlurEffect.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLRectBlurEffect : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLRectBlurEffect() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrRectBlurEffect& _outer = args.fFp.cast<GrRectBlurEffect>();
+        (void)_outer;
+        auto rect = _outer.rect;
+        (void)rect;
+        auto sigma = _outer.sigma;
+        (void)sigma;
+        highPrecision = ((((abs(rect.left()) > 16000.0 || abs(rect.top()) > 16000.0) ||
+                           abs(rect.right()) > 16000.0) ||
+                          abs(rect.bottom()) > 16000.0) ||
+                         abs(rect.right() - rect.left()) > 16000.0) ||
+                        abs(rect.bottom() - rect.top()) > 16000.0;
+        rectVar =
+                args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType, "rect");
+        if (!highPrecision) {
+            proxyRectHalfVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
+                                                                kHalf4_GrSLType, "proxyRectHalf");
+        }
+        if (highPrecision) {
+            proxyRectFloatVar = args.fUniformHandler->addUniform(
+                    kFragment_GrShaderFlag, kFloat4_GrSLType, "proxyRectFloat");
+        }
+        profileSizeVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType,
+                                                          "profileSize");
+        fragBuilder->codeAppendf(
+                "/* key */ bool highPrecision = %s;\n@if (highPrecision) {\n    float2 "
+                "translatedPos = sk_FragCoord.xy - %s.xy;\n    float width = %s.z - %s.x;\n    "
+                "float height = %s.w - %s.y;\n    float2 smallDims = float2(width - float(%s), "
+                "height - float(%s));\n    float center = float(2.0 * floor(%s / 2.0 + 0.25) - "
+                "1.0);\n    float2 wh = smallDims - float2(center, center);\n    half hcoord = "
+                "half((abs(translatedPos.x - 0.5 * width) - 0.5 * wh.x) / float(%s));\n    half "
+                "hlookup = texture(%s, float2(float(hcoord), 0.5)).",
+                (highPrecision ? "true" : "false"), args.fUniformHandler->getUniformCStr(rectVar),
+                args.fUniformHandler->getUniformCStr(rectVar),
+                args.fUniformHandler->getUniformCStr(rectVar),
+                args.fUniformHandler->getUniformCStr(rectVar),
+                args.fUniformHandler->getUniformCStr(rectVar),
+                args.fUniformHandler->getUniformCStr(profileSizeVar),
+                args.fUniformHandler->getUniformCStr(profileSizeVar),
+                args.fUniformHandler->getUniformCStr(profileSizeVar),
+                args.fUniformHandler->getUniformCStr(profileSizeVar),
+                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str());
+        fragBuilder->codeAppendf(
+                "%s.w;\n    half vcoord = half((abs(translatedPos.y - 0.5 * height) - 0.5 * wh.y) "
+                "/ float(%s));\n    half vlookup = texture(%s, float2(float(vcoord), 0.5)).%s.w;\n "
+                "   %s = (%s * hlookup) * vlookup;\n} else {\n    half2 translatedPos = "
+                "half2(sk_FragCoord.xy - %s.xy);\n    half width = half(%s.z - %s.x);\n    half "
+                "height = half(%s.w - %s.y);\n    half2 smallDims = half2(width - %s, height - "
+                "%s);\n    half center = 2.0 * floor(%s / 2.0 + 0.25) - 1.0;\n    half2 wh = "
+                "smallDims - half2(center, center);\n    half ",
+                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str(),
+                args.fUniformHandler->getUniformCStr(profileSizeVar),
+                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(),
+                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str(),
+                args.fOutputColor, args.fInputColor, args.fUniformHandler->getUniformCStr(rectVar),
+                args.fUniformHandler->getUniformCStr(rectVar),
+                args.fUniformHandler->getUniformCStr(rectVar),
+                args.fUniformHandler->getUniformCStr(rectVar),
+                args.fUniformHandler->getUniformCStr(rectVar),
+                args.fUniformHandler->getUniformCStr(profileSizeVar),
+                args.fUniformHandler->getUniformCStr(profileSizeVar),
+                args.fUniformHandler->getUniformCStr(profileSizeVar));
+        fragBuilder->codeAppendf(
+                "hcoord = (abs(translatedPos.x - 0.5 * width) - 0.5 * wh.x) / %s;\n    half "
+                "hlookup = texture(%s, float2(float(hcoord), 0.5)).%s.w;\n    half vcoord = "
+                "(abs(translatedPos.y - 0.5 * height) - 0.5 * wh.y) / %s;\n    half vlookup = "
+                "texture(%s, float2(float(vcoord), 0.5)).%s.w;\n    %s = (%s * hlookup) * "
+                "vlookup;\n}\n",
+                args.fUniformHandler->getUniformCStr(profileSizeVar),
+                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(),
+                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str(),
+                args.fUniformHandler->getUniformCStr(profileSizeVar),
+                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(),
+                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str(),
+                args.fOutputColor, args.fInputColor);
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {
+        const GrRectBlurEffect& _outer = _proc.cast<GrRectBlurEffect>();
+        { pdman.set4fv(rectVar, 1, reinterpret_cast<const float*>(&(_outer.rect))); }
+        UniformHandle& rect = rectVar;
+        (void)rect;
+        auto sigma = _outer.sigma;
+        (void)sigma;
+        GrSurfaceProxy& blurProfileProxy = *_outer.textureSampler(0).proxy();
+        GrTexture& blurProfile = *blurProfileProxy.peekTexture();
+        (void)blurProfile;
+        UniformHandle& proxyRectHalf = proxyRectHalfVar;
+        (void)proxyRectHalf;
+        UniformHandle& proxyRectFloat = proxyRectFloatVar;
+        (void)proxyRectFloat;
+        UniformHandle& profileSize = profileSizeVar;
+        (void)profileSize;
+
+        pdman.set1f(profileSize, SkScalarCeilToScalar(6 * sigma));
+    }
+    bool highPrecision = false;
+    UniformHandle proxyRectHalfVar;
+    UniformHandle proxyRectFloatVar;
+    UniformHandle profileSizeVar;
+    UniformHandle rectVar;
+};
+GrGLSLFragmentProcessor* GrRectBlurEffect::onCreateGLSLInstance() const {
+    return new GrGLSLRectBlurEffect();
+}
+void GrRectBlurEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                             GrProcessorKeyBuilder* b) const {}
+bool GrRectBlurEffect::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrRectBlurEffect& that = other.cast<GrRectBlurEffect>();
+    (void)that;
+    if (rect != that.rect) return false;
+    if (sigma != that.sigma) return false;
+    if (blurProfile != that.blurProfile) return false;
+    return true;
+}
+GrRectBlurEffect::GrRectBlurEffect(const GrRectBlurEffect& src)
+        : INHERITED(kGrRectBlurEffect_ClassID, src.optimizationFlags())
+        , rect(src.rect)
+        , sigma(src.sigma)
+        , blurProfile(src.blurProfile) {
+    this->setTextureSamplerCnt(1);
+}
+std::unique_ptr<GrFragmentProcessor> GrRectBlurEffect::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrRectBlurEffect(*this));
+}
+const GrFragmentProcessor::TextureSampler& GrRectBlurEffect::onTextureSampler(int index) const {
+    return IthTextureSampler(index, blurProfile);
+}
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRectBlurEffect);
+#if GR_TEST_UTILS
+std::unique_ptr<GrFragmentProcessor> GrRectBlurEffect::TestCreate(GrProcessorTestData* data) {
+    float sigma = data->fRandom->nextRangeF(3, 8);
+    float width = data->fRandom->nextRangeF(200, 300);
+    float height = data->fRandom->nextRangeF(200, 300);
+    return GrRectBlurEffect::Make(data->proxyProvider(), *data->caps()->shaderCaps(),
+                                  SkRect::MakeWH(width, height), sigma);
+}
+#endif
diff --git a/src/gpu/effects/generated/GrRectBlurEffect.h b/src/gpu/effects/generated/GrRectBlurEffect.h
new file mode 100644
index 0000000..6e09aae
--- /dev/null
+++ b/src/gpu/effects/generated/GrRectBlurEffect.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrRectBlurEffect.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrRectBlurEffect_DEFINED
+#define GrRectBlurEffect_DEFINED
+#include "SkTypes.h"
+
+#include "GrProxyProvider.h"
+#include "GrShaderCaps.h"
+#include "SkBlurMask.h"
+#include "SkScalar.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrRectBlurEffect : public GrFragmentProcessor {
+public:
+    static sk_sp<GrTextureProxy> CreateBlurProfileTexture(GrProxyProvider* proxyProvider,
+                                                          float sigma) {
+        unsigned int profileSize = SkScalarCeilToInt(6 * sigma);
+
+        static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
+        GrUniqueKey key;
+        GrUniqueKey::Builder builder(&key, kDomain, 1, "Rect Blur Mask");
+        builder[0] = profileSize;
+        builder.finish();
+
+        sk_sp<GrTextureProxy> blurProfile(
+                proxyProvider->findOrCreateProxyByUniqueKey(key, kTopLeft_GrSurfaceOrigin));
+        if (!blurProfile) {
+            SkImageInfo ii = SkImageInfo::MakeA8(profileSize, 1);
+
+            SkBitmap bitmap;
+            if (!bitmap.tryAllocPixels(ii)) {
+                return nullptr;
+            }
+
+            SkBlurMask::ComputeBlurProfile(bitmap.getAddr8(0, 0), profileSize, sigma);
+            bitmap.setImmutable();
+
+            sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
+            if (!image) {
+                return nullptr;
+            }
+
+            blurProfile =
+                    proxyProvider->createTextureProxy(std::move(image), kNone_GrSurfaceFlags, 1,
+                                                      SkBudgeted::kYes, SkBackingFit::kExact);
+            if (!blurProfile) {
+                return nullptr;
+            }
+
+            SkASSERT(blurProfile->origin() == kTopLeft_GrSurfaceOrigin);
+            proxyProvider->assignUniqueKeyToProxy(key, blurProfile.get());
+        }
+
+        return blurProfile;
+    }
+
+    static std::unique_ptr<GrFragmentProcessor> Make(GrProxyProvider* proxyProvider,
+                                                     const GrShaderCaps& caps, const SkRect& rect,
+                                                     float sigma) {
+        if (!caps.floatIs32Bits()) {
+            // We promote the rect uniform from half to float when it has large values for
+            // precision. If we don't have full float then fail.
+            if (SkScalarAbs(rect.fLeft) > 16000.f || SkScalarAbs(rect.fTop) > 16000.f ||
+                SkScalarAbs(rect.fRight) > 16000.f || SkScalarAbs(rect.fBottom) > 16000.f ||
+                SkScalarAbs(rect.width()) > 16000.f || SkScalarAbs(rect.height()) > 16000.f) {
+                return nullptr;
+            }
+        }
+        int doubleProfileSize = SkScalarCeilToInt(12 * sigma);
+
+        if (doubleProfileSize >= rect.width() || doubleProfileSize >= rect.height()) {
+            // if the blur sigma is too large so the gaussian overlaps the whole
+            // rect in either direction, fall back to CPU path for now.
+            return nullptr;
+        }
+
+        sk_sp<GrTextureProxy> blurProfile(CreateBlurProfileTexture(proxyProvider, sigma));
+        if (!blurProfile) {
+            return nullptr;
+        }
+
+        return std::unique_ptr<GrFragmentProcessor>(new GrRectBlurEffect(
+                rect, sigma, std::move(blurProfile),
+                GrSamplerState(GrSamplerState::WrapMode::kClamp, GrSamplerState::Filter::kBilerp)));
+    }
+    GrRectBlurEffect(const GrRectBlurEffect& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "RectBlurEffect"; }
+    SkRect rect;
+    float sigma;
+    TextureSampler blurProfile;
+
+private:
+    GrRectBlurEffect(SkRect rect, float sigma, sk_sp<GrTextureProxy> blurProfile,
+                     GrSamplerState samplerParams)
+            : INHERITED(kGrRectBlurEffect_ClassID,
+                        (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag)
+            , rect(rect)
+            , sigma(sigma)
+            , blurProfile(std::move(blurProfile), samplerParams) {
+        this->setTextureSamplerCnt(1);
+    }
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    const TextureSampler& onTextureSampler(int) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/effects/generated/GrSimpleTextureEffect.cpp b/src/gpu/effects/generated/GrSimpleTextureEffect.cpp
new file mode 100644
index 0000000..8236ebc
--- /dev/null
+++ b/src/gpu/effects/generated/GrSimpleTextureEffect.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrSimpleTextureEffect.fp; do not modify.
+ **************************************************************************************************/
+#include "GrSimpleTextureEffect.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLSimpleTextureEffect : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLSimpleTextureEffect() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrSimpleTextureEffect& _outer = args.fFp.cast<GrSimpleTextureEffect>();
+        (void)_outer;
+        auto matrix = _outer.matrix;
+        (void)matrix;
+        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+        fragBuilder->codeAppendf(
+                "%s = %s * texture(%s, %s).%s;\n", args.fOutputColor, args.fInputColor,
+                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(),
+                sk_TransformedCoords2D_0.c_str(),
+                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str());
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {}
+};
+GrGLSLFragmentProcessor* GrSimpleTextureEffect::onCreateGLSLInstance() const {
+    return new GrGLSLSimpleTextureEffect();
+}
+void GrSimpleTextureEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                  GrProcessorKeyBuilder* b) const {}
+bool GrSimpleTextureEffect::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrSimpleTextureEffect& that = other.cast<GrSimpleTextureEffect>();
+    (void)that;
+    if (image != that.image) return false;
+    if (matrix != that.matrix) return false;
+    return true;
+}
+GrSimpleTextureEffect::GrSimpleTextureEffect(const GrSimpleTextureEffect& src)
+        : INHERITED(kGrSimpleTextureEffect_ClassID, src.optimizationFlags())
+        , imageCoordTransform(src.imageCoordTransform)
+        , image(src.image)
+        , matrix(src.matrix) {
+    this->setTextureSamplerCnt(1);
+    this->addCoordTransform(&imageCoordTransform);
+}
+std::unique_ptr<GrFragmentProcessor> GrSimpleTextureEffect::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrSimpleTextureEffect(*this));
+}
+const GrFragmentProcessor::TextureSampler& GrSimpleTextureEffect::onTextureSampler(
+        int index) const {
+    return IthTextureSampler(index, image);
+}
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrSimpleTextureEffect);
+#if GR_TEST_UTILS
+std::unique_ptr<GrFragmentProcessor> GrSimpleTextureEffect::TestCreate(
+        GrProcessorTestData* testData) {
+    int texIdx = testData->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx
+                                               : GrProcessorUnitTest::kAlphaTextureIdx;
+    GrSamplerState::WrapMode wrapModes[2];
+    GrTest::TestWrapModes(testData->fRandom, wrapModes);
+    if (!testData->caps()->npotTextureTileSupport()) {
+        // Performing repeat sampling on npot textures will cause asserts on HW
+        // that lacks support.
+        wrapModes[0] = GrSamplerState::WrapMode::kClamp;
+        wrapModes[1] = GrSamplerState::WrapMode::kClamp;
+    }
+
+    GrSamplerState params(wrapModes, testData->fRandom->nextBool()
+                                             ? GrSamplerState::Filter::kBilerp
+                                             : GrSamplerState::Filter::kNearest);
+
+    const SkMatrix& matrix = GrTest::TestMatrix(testData->fRandom);
+    return GrSimpleTextureEffect::Make(testData->textureProxy(texIdx), matrix, params);
+}
+#endif
diff --git a/src/gpu/effects/generated/GrSimpleTextureEffect.h b/src/gpu/effects/generated/GrSimpleTextureEffect.h
new file mode 100644
index 0000000..ac54e6a
--- /dev/null
+++ b/src/gpu/effects/generated/GrSimpleTextureEffect.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrSimpleTextureEffect.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrSimpleTextureEffect_DEFINED
+#define GrSimpleTextureEffect_DEFINED
+#include "SkTypes.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrSimpleTextureEffect : public GrFragmentProcessor {
+public:
+    static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy> proxy,
+                                                     const SkMatrix& matrix) {
+        return std::unique_ptr<GrFragmentProcessor>(
+                new GrSimpleTextureEffect(std::move(proxy), matrix,
+                                          GrSamplerState(GrSamplerState::WrapMode::kClamp,
+                                                         GrSamplerState::Filter::kNearest)));
+    }
+
+    /* clamp mode */
+    static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy> proxy,
+                                                     const SkMatrix& matrix,
+                                                     GrSamplerState::Filter filter) {
+        return std::unique_ptr<GrFragmentProcessor>(new GrSimpleTextureEffect(
+                std::move(proxy), matrix,
+                GrSamplerState(GrSamplerState::WrapMode::kClamp, filter)));
+    }
+
+    static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy> proxy,
+                                                     const SkMatrix& matrix,
+                                                     const GrSamplerState& p) {
+        return std::unique_ptr<GrFragmentProcessor>(
+                new GrSimpleTextureEffect(std::move(proxy), matrix, p));
+    }
+    GrSimpleTextureEffect(const GrSimpleTextureEffect& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "SimpleTextureEffect"; }
+    GrCoordTransform imageCoordTransform;
+    TextureSampler image;
+    SkMatrix44 matrix;
+
+private:
+    GrSimpleTextureEffect(sk_sp<GrTextureProxy> image, SkMatrix44 matrix,
+                          GrSamplerState samplerParams)
+            : INHERITED(kGrSimpleTextureEffect_ClassID,
+                        (OptimizationFlags)ModulateForSamplerOptFlags(
+                                image->config(),
+                                samplerParams.wrapModeX() ==
+                                                GrSamplerState::WrapMode::kClampToBorder ||
+                                        samplerParams.wrapModeY() ==
+                                                GrSamplerState::WrapMode::kClampToBorder))
+            , imageCoordTransform(matrix, image.get())
+            , image(std::move(image), samplerParams)
+            , matrix(matrix) {
+        this->setTextureSamplerCnt(1);
+        this->addCoordTransform(&imageCoordTransform);
+    }
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    const TextureSampler& onTextureSampler(int) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/gl/GrGLAssembleGLESInterfaceAutogen.cpp b/src/gpu/gl/GrGLAssembleGLESInterfaceAutogen.cpp
new file mode 100644
index 0000000..d5225f3
--- /dev/null
+++ b/src/gpu/gl/GrGLAssembleGLESInterfaceAutogen.cpp
@@ -0,0 +1,506 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * THIS FILE IS AUTOGENERATED
+ * Make edits to tools/gpu/gl/interface/templates.go or they will
+ * be overwritten.
+ */
+
+#include "gl/GrGLAssembleInterface.h"
+#include "gl/GrGLAssembleHelpers.h"
+#include "gl/GrGLUtil.h"
+
+#define GET_PROC(F) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F)
+#define GET_PROC_SUFFIX(F, S) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F #S)
+#define GET_PROC_LOCAL(F) GrGL##F##Fn* F = (GrGL##F##Fn*)get(ctx, "gl" #F)
+
+#define GET_EGL_PROC_SUFFIX(F, S) functions->fEGL##F = (GrEGL##F##Fn*)get(ctx, "egl" #F #S)
+
+#if SK_DISABLE_GL_ES_INTERFACE
+sk_sp<const GrGLInterface> GrGLMakeAssembledGLESInterface(void *ctx, GrGLGetProc get) {
+    return nullptr;
+}
+#else
+sk_sp<const GrGLInterface> GrGLMakeAssembledGLESInterface(void *ctx, GrGLGetProc get) {
+    GET_PROC_LOCAL(GetString);
+    if (nullptr == GetString) {
+        return nullptr;
+    }
+
+    const char* verStr = reinterpret_cast<const char*>(GetString(GR_GL_VERSION));
+    GrGLVersion glVer = GrGLGetVersionFromString(verStr);
+
+    if (glVer < GR_GL_VER(2,0)) {
+        return nullptr;
+    }
+
+    GET_PROC_LOCAL(GetIntegerv);
+    GET_PROC_LOCAL(GetStringi);
+    GrEGLQueryStringFn* queryString;
+    GrEGLDisplay display;
+    GrGetEGLQueryAndDisplay(&queryString, &display, ctx, get);
+    GrGLExtensions extensions;
+    if (!extensions.init(kGLES_GrGLStandard, GetString, GetStringi, GetIntegerv, queryString,
+                         display)) {
+        return nullptr;
+    }
+
+    sk_sp<GrGLInterface> interface(new GrGLInterface);
+    GrGLInterface::Functions* functions = &interface->fFunctions;
+
+    // Autogenerated content follows
+    GET_PROC(ActiveTexture);
+    GET_PROC(AttachShader);
+    GET_PROC(BindAttribLocation);
+    GET_PROC(BindBuffer);
+    GET_PROC(BindTexture);
+    GET_PROC(BlendColor);
+    GET_PROC(BlendEquation);
+    GET_PROC(BlendFunc);
+    GET_PROC(BufferData);
+    GET_PROC(BufferSubData);
+    GET_PROC(Clear);
+    GET_PROC(ClearColor);
+    GET_PROC(ClearStencil);
+    GET_PROC(ColorMask);
+    GET_PROC(CompileShader);
+    GET_PROC(CompressedTexImage2D);
+    GET_PROC(CompressedTexSubImage2D);
+    GET_PROC(CopyTexSubImage2D);
+    GET_PROC(CreateProgram);
+    GET_PROC(CreateShader);
+    GET_PROC(CullFace);
+    GET_PROC(DeleteBuffers);
+    GET_PROC(DeleteProgram);
+    GET_PROC(DeleteShader);
+    GET_PROC(DeleteTextures);
+    GET_PROC(DepthMask);
+    GET_PROC(Disable);
+    GET_PROC(DisableVertexAttribArray);
+    GET_PROC(DrawArrays);
+    GET_PROC(DrawElements);
+    GET_PROC(Enable);
+    GET_PROC(EnableVertexAttribArray);
+    GET_PROC(Finish);
+    GET_PROC(Flush);
+    GET_PROC(FrontFace);
+    GET_PROC(GenBuffers);
+    GET_PROC(GenTextures);
+    GET_PROC(GetBufferParameteriv);
+    GET_PROC(GetError);
+    GET_PROC(GetIntegerv);
+    GET_PROC(GetProgramInfoLog);
+    GET_PROC(GetProgramiv);
+    GET_PROC(GetShaderInfoLog);
+    GET_PROC(GetShaderiv);
+    GET_PROC(GetString);
+    GET_PROC(GetUniformLocation);
+    GET_PROC(IsTexture);
+    GET_PROC(LineWidth);
+    GET_PROC(LinkProgram);
+    GET_PROC(PixelStorei);
+    GET_PROC(ReadPixels);
+    GET_PROC(Scissor);
+    GET_PROC(ShaderSource);
+    GET_PROC(StencilFunc);
+    GET_PROC(StencilFuncSeparate);
+    GET_PROC(StencilMask);
+    GET_PROC(StencilMaskSeparate);
+    GET_PROC(StencilOp);
+    GET_PROC(StencilOpSeparate);
+    GET_PROC(TexImage2D);
+    GET_PROC(TexParameterf);
+    GET_PROC(TexParameterfv);
+    GET_PROC(TexParameteri);
+    GET_PROC(TexParameteriv);
+    GET_PROC(TexSubImage2D);
+    GET_PROC(Uniform1f);
+    GET_PROC(Uniform1fv);
+    GET_PROC(Uniform1i);
+    GET_PROC(Uniform1iv);
+    GET_PROC(Uniform2f);
+    GET_PROC(Uniform2fv);
+    GET_PROC(Uniform2i);
+    GET_PROC(Uniform2iv);
+    GET_PROC(Uniform3f);
+    GET_PROC(Uniform3fv);
+    GET_PROC(Uniform3i);
+    GET_PROC(Uniform3iv);
+    GET_PROC(Uniform4f);
+    GET_PROC(Uniform4fv);
+    GET_PROC(Uniform4i);
+    GET_PROC(Uniform4iv);
+    GET_PROC(UniformMatrix2fv);
+    GET_PROC(UniformMatrix3fv);
+    GET_PROC(UniformMatrix4fv);
+    GET_PROC(UseProgram);
+    GET_PROC(VertexAttrib1f);
+    GET_PROC(VertexAttrib2fv);
+    GET_PROC(VertexAttrib3fv);
+    GET_PROC(VertexAttrib4fv);
+    GET_PROC(VertexAttribPointer);
+    GET_PROC(Viewport);
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(GetStringi);
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(BindVertexArray);
+        GET_PROC(DeleteVertexArrays);
+        GET_PROC(GenVertexArrays);
+    } else if (extensions.has("GL_OES_vertex_array_object")) {
+        GET_PROC_SUFFIX(BindVertexArray, OES);
+        GET_PROC_SUFFIX(DeleteVertexArrays, OES);
+        GET_PROC_SUFFIX(GenVertexArrays, OES);
+    }
+
+    if (glVer >= GR_GL_VER(3,0) && extensions.has("GL_EXT_blend_func_extended")) {
+        GET_PROC_SUFFIX(BindFragDataLocation, EXT);
+    }
+
+    if (glVer >= GR_GL_VER(3,0) && extensions.has("GL_EXT_blend_func_extended")) {
+        GET_PROC_SUFFIX(BindFragDataLocationIndexed, EXT);
+    }
+
+    if (extensions.has("GL_KHR_blend_equation_advanced")) {
+        GET_PROC_SUFFIX(BlendBarrier, KHR);
+    } else if (extensions.has("GL_NV_blend_equation_advanced")) {
+        GET_PROC_SUFFIX(BlendBarrier, NV);
+    }
+
+    if (extensions.has("GL_EXT_clear_texture")) {
+        GET_PROC_SUFFIX(ClearTexImage, EXT);
+        GET_PROC_SUFFIX(ClearTexSubImage, EXT);
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(DrawArraysInstanced);
+        GET_PROC(DrawElementsInstanced);
+    } else if (extensions.has("GL_EXT_draw_instanced")) {
+        GET_PROC_SUFFIX(DrawArraysInstanced, EXT);
+        GET_PROC_SUFFIX(DrawElementsInstanced, EXT);
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(DrawBuffers);
+        GET_PROC(ReadBuffer);
+    }
+
+    if (glVer >= GR_GL_VER(3,1)) {
+        GET_PROC(DrawArraysIndirect);
+        GET_PROC(DrawElementsIndirect);
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(DrawRangeElements);
+    }
+
+    if (glVer >= GR_GL_VER(3,1)) {
+        GET_PROC(GetMultisamplefv);
+    }
+
+    if (glVer >= GR_GL_VER(3,1)) {
+        GET_PROC(GetTexLevelParameteriv);
+    }
+
+    if (extensions.has("GL_EXT_multi_draw_indirect")) {
+        GET_PROC_SUFFIX(MultiDrawArraysIndirect, EXT);
+        GET_PROC_SUFFIX(MultiDrawElementsIndirect, EXT);
+    }
+
+    if (glVer >= GR_GL_VER(3,2)) {
+        GET_PROC(TexBuffer);
+    } else if (extensions.has("GL_OES_texture_buffer")) {
+        GET_PROC_SUFFIX(TexBuffer, OES);
+    } else if (extensions.has("GL_EXT_texture_buffer")) {
+        GET_PROC_SUFFIX(TexBuffer, EXT);
+    }
+
+    if (glVer >= GR_GL_VER(3,2)) {
+        GET_PROC(TexBufferRange);
+    } else if (extensions.has("GL_OES_texture_buffer")) {
+        GET_PROC_SUFFIX(TexBufferRange, OES);
+    } else if (extensions.has("GL_EXT_texture_buffer")) {
+        GET_PROC_SUFFIX(TexBufferRange, EXT);
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(TexStorage2D);
+    } else if (extensions.has("GL_EXT_texture_storage")) {
+        GET_PROC_SUFFIX(TexStorage2D, EXT);
+    }
+
+    if (extensions.has("GL_NV_texture_barrier")) {
+        GET_PROC_SUFFIX(TextureBarrier, NV);
+    }
+
+    if (extensions.has("GL_EXT_discard_framebuffer")) {
+        GET_PROC_SUFFIX(DiscardFramebuffer, EXT);
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(VertexAttribDivisor);
+    } else if (extensions.has("GL_EXT_instanced_arrays")) {
+        GET_PROC_SUFFIX(VertexAttribDivisor, EXT);
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(VertexAttribIPointer);
+    }
+
+    GET_PROC(BindFramebuffer);
+    GET_PROC(BindRenderbuffer);
+    GET_PROC(CheckFramebufferStatus);
+    GET_PROC(DeleteFramebuffers);
+    GET_PROC(DeleteRenderbuffers);
+    GET_PROC(FramebufferRenderbuffer);
+    GET_PROC(FramebufferTexture2D);
+    GET_PROC(GenFramebuffers);
+    GET_PROC(GenRenderbuffers);
+    GET_PROC(GenerateMipmap);
+    GET_PROC(GetFramebufferAttachmentParameteriv);
+    GET_PROC(GetRenderbufferParameteriv);
+    GET_PROC(RenderbufferStorage);
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(BlitFramebuffer);
+    } else if (extensions.has("GL_CHROMIUM_framebuffer_multisample")) {
+        GET_PROC_SUFFIX(BlitFramebuffer, CHROMIUM);
+    } else if (extensions.has("GL_ANGLE_framebuffer_blit")) {
+        GET_PROC_SUFFIX(BlitFramebuffer, ANGLE);
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(RenderbufferStorageMultisample);
+    } else if (extensions.has("GL_CHROMIUM_framebuffer_multisample")) {
+        GET_PROC_SUFFIX(RenderbufferStorageMultisample, CHROMIUM);
+    } else if (extensions.has("GL_ANGLE_framebuffer_multisample")) {
+        GET_PROC_SUFFIX(RenderbufferStorageMultisample, ANGLE);
+    }
+
+    if (extensions.has("GL_CHROMIUM_map_sub")) {
+        GET_PROC_SUFFIX(MapBufferSubData, CHROMIUM);
+        GET_PROC_SUFFIX(MapTexSubImage2D, CHROMIUM);
+        GET_PROC_SUFFIX(UnmapBufferSubData, CHROMIUM);
+        GET_PROC_SUFFIX(UnmapTexSubImage2D, CHROMIUM);
+    }
+
+    if (extensions.has("GL_EXT_multisampled_render_to_texture")) {
+        GET_PROC_SUFFIX(FramebufferTexture2DMultisample, EXT);
+    } else if (extensions.has("GL_IMG_multisampled_render_to_texture")) {
+        GET_PROC_SUFFIX(FramebufferTexture2DMultisample, IMG);
+    }
+
+    if (extensions.has("GL_EXT_multisampled_render_to_texture")) {
+        functions->fRenderbufferStorageMultisampleES2EXT =(GrGLRenderbufferStorageMultisampleFn*)get(ctx, "glRenderbufferStorageMultisampleEXT");
+    }
+
+    if (extensions.has("GL_IMG_multisampled_render_to_texture")) {
+        functions->fRenderbufferStorageMultisampleES2EXT =(GrGLRenderbufferStorageMultisampleFn*)get(ctx, "glRenderbufferStorageMultisampleIMG");
+    }
+
+    if (extensions.has("GL_APPLE_framebuffer_multisample")) {
+        GET_PROC_SUFFIX(ResolveMultisampleFramebuffer, APPLE);
+        functions->fRenderbufferStorageMultisampleES2APPLE =(GrGLRenderbufferStorageMultisampleFn*)get(ctx, "glRenderbufferStorageMultisampleAPPLE");
+    }
+
+    if (extensions.has("GL_OES_mapbuffer")) {
+        GET_PROC_SUFFIX(MapBuffer, OES);
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(UnmapBuffer);
+    } else if (extensions.has("GL_OES_mapbuffer")) {
+        GET_PROC_SUFFIX(UnmapBuffer, OES);
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(FlushMappedBufferRange);
+        GET_PROC(MapBufferRange);
+    } else if (extensions.has("GL_EXT_map_buffer_range")) {
+        GET_PROC_SUFFIX(FlushMappedBufferRange, EXT);
+        GET_PROC_SUFFIX(MapBufferRange, EXT);
+    }
+
+    if (extensions.has("GL_EXT_debug_marker")) {
+        GET_PROC_SUFFIX(InsertEventMarker, EXT);
+        GET_PROC_SUFFIX(PopGroupMarker, EXT);
+        GET_PROC_SUFFIX(PushGroupMarker, EXT);
+    }
+
+    if (glVer >= GR_GL_VER(3,1)) {
+        GET_PROC(GetProgramResourceLocation);
+    }
+
+    if (extensions.has("GL_CHROMIUM_path_rendering")) {
+        GET_PROC_SUFFIX(MatrixLoadIdentity, CHROMIUM);
+        GET_PROC_SUFFIX(MatrixLoadf, CHROMIUM);
+    } else if (extensions.has("GL_NV_path_rendering")) {
+        GET_PROC_SUFFIX(MatrixLoadIdentity, EXT);
+        GET_PROC_SUFFIX(MatrixLoadf, EXT);
+    }
+
+    if (extensions.has("GL_CHROMIUM_path_rendering")) {
+        GET_PROC_SUFFIX(CoverFillPath, CHROMIUM);
+        GET_PROC_SUFFIX(CoverFillPathInstanced, CHROMIUM);
+        GET_PROC_SUFFIX(CoverStrokePath, CHROMIUM);
+        GET_PROC_SUFFIX(CoverStrokePathInstanced, CHROMIUM);
+        GET_PROC_SUFFIX(DeletePaths, CHROMIUM);
+        GET_PROC_SUFFIX(GenPaths, CHROMIUM);
+        GET_PROC_SUFFIX(IsPath, CHROMIUM);
+        GET_PROC_SUFFIX(PathCommands, CHROMIUM);
+        GET_PROC_SUFFIX(PathParameterf, CHROMIUM);
+        GET_PROC_SUFFIX(PathParameteri, CHROMIUM);
+        GET_PROC_SUFFIX(PathStencilFunc, CHROMIUM);
+        GET_PROC_SUFFIX(ProgramPathFragmentInputGen, CHROMIUM);
+        GET_PROC_SUFFIX(StencilFillPath, CHROMIUM);
+        GET_PROC_SUFFIX(StencilFillPathInstanced, CHROMIUM);
+        GET_PROC_SUFFIX(StencilStrokePath, CHROMIUM);
+        GET_PROC_SUFFIX(StencilStrokePathInstanced, CHROMIUM);
+        GET_PROC_SUFFIX(StencilThenCoverFillPath, CHROMIUM);
+        GET_PROC_SUFFIX(StencilThenCoverFillPathInstanced, CHROMIUM);
+        GET_PROC_SUFFIX(StencilThenCoverStrokePath, CHROMIUM);
+        GET_PROC_SUFFIX(StencilThenCoverStrokePathInstanced, CHROMIUM);
+    } else if (extensions.has("GL_NV_path_rendering")) {
+        GET_PROC_SUFFIX(CoverFillPath, NV);
+        GET_PROC_SUFFIX(CoverFillPathInstanced, NV);
+        GET_PROC_SUFFIX(CoverStrokePath, NV);
+        GET_PROC_SUFFIX(CoverStrokePathInstanced, NV);
+        GET_PROC_SUFFIX(DeletePaths, NV);
+        GET_PROC_SUFFIX(GenPaths, NV);
+        GET_PROC_SUFFIX(IsPath, NV);
+        GET_PROC_SUFFIX(PathCommands, NV);
+        GET_PROC_SUFFIX(PathParameterf, NV);
+        GET_PROC_SUFFIX(PathParameteri, NV);
+        GET_PROC_SUFFIX(PathStencilFunc, NV);
+        GET_PROC_SUFFIX(ProgramPathFragmentInputGen, NV);
+        GET_PROC_SUFFIX(StencilFillPath, NV);
+        GET_PROC_SUFFIX(StencilFillPathInstanced, NV);
+        GET_PROC_SUFFIX(StencilStrokePath, NV);
+        GET_PROC_SUFFIX(StencilStrokePathInstanced, NV);
+        GET_PROC_SUFFIX(StencilThenCoverFillPath, NV);
+        GET_PROC_SUFFIX(StencilThenCoverFillPathInstanced, NV);
+        GET_PROC_SUFFIX(StencilThenCoverStrokePath, NV);
+        GET_PROC_SUFFIX(StencilThenCoverStrokePathInstanced, NV);
+    }
+
+    if (extensions.has("GL_CHROMIUM_path_rendering")) {
+        GET_PROC_SUFFIX(BindFragmentInputLocation, CHROMIUM);
+    }
+
+    if (extensions.has("GL_CHROMIUM_framebuffer_mixed_samples")) {
+        GET_PROC_SUFFIX(CoverageModulation, CHROMIUM);
+    } else if (extensions.has("GL_NV_framebuffer_mixed_samples")) {
+        GET_PROC_SUFFIX(CoverageModulation, NV);
+    }
+
+    if (extensions.has("GL_KHR_debug")) {
+        GET_PROC_SUFFIX(DebugMessageCallback, KHR);
+        GET_PROC_SUFFIX(DebugMessageControl, KHR);
+        GET_PROC_SUFFIX(DebugMessageInsert, KHR);
+        GET_PROC_SUFFIX(GetDebugMessageLog, KHR);
+        GET_PROC_SUFFIX(ObjectLabel, KHR);
+        GET_PROC_SUFFIX(PopDebugGroup, KHR);
+        GET_PROC_SUFFIX(PushDebugGroup, KHR);
+    }
+
+    if (extensions.has("GL_CHROMIUM_bind_uniform_location")) {
+        GET_PROC_SUFFIX(BindUniformLocation, CHROMIUM);
+    }
+
+    if (extensions.has("GL_EXT_window_rectangles")) {
+        GET_PROC_SUFFIX(WindowRectangles, EXT);
+    }
+
+    if (extensions.has("EGL_KHR_image")) {
+        GET_EGL_PROC_SUFFIX(CreateImage, KHR);
+        GET_EGL_PROC_SUFFIX(DestroyImage, KHR);
+    } else if (extensions.has("EGL_KHR_image_base")) {
+        GET_EGL_PROC_SUFFIX(CreateImage, KHR);
+        GET_EGL_PROC_SUFFIX(DestroyImage, KHR);
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(ClientWaitSync);
+        GET_PROC(DeleteSync);
+        GET_PROC(FenceSync);
+        GET_PROC(IsSync);
+        GET_PROC(WaitSync);
+    } else if (extensions.has("GL_APPLE_sync")) {
+        GET_PROC_SUFFIX(ClientWaitSync, APPLE);
+        GET_PROC_SUFFIX(DeleteSync, APPLE);
+        GET_PROC_SUFFIX(FenceSync, APPLE);
+        GET_PROC_SUFFIX(IsSync, APPLE);
+        GET_PROC_SUFFIX(WaitSync, APPLE);
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(GetInternalformativ);
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(GetProgramBinary);
+        GET_PROC(ProgramBinary);
+        GET_PROC(ProgramParameteri);
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(BindSampler);
+        GET_PROC(DeleteSamplers);
+        GET_PROC(GenSamplers);
+        GET_PROC(SamplerParameteri);
+        GET_PROC(SamplerParameteriv);
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+#if GR_TEST_UTILS
+        GET_PROC(BeginQuery);
+        GET_PROC(DeleteQueries);
+        GET_PROC(EndQuery);
+        GET_PROC(GenQueries);
+        GET_PROC(GetQueryObjectuiv);
+        GET_PROC(GetQueryiv);
+#endif
+    } else if (extensions.has("GL_EXT_occlusion_query_boolean")) {
+#if GR_TEST_UTILS
+        GET_PROC_SUFFIX(BeginQuery, EXT);
+        GET_PROC_SUFFIX(DeleteQueries, EXT);
+        GET_PROC_SUFFIX(EndQuery, EXT);
+        GET_PROC_SUFFIX(GenQueries, EXT);
+        GET_PROC_SUFFIX(GetQueryObjectuiv, EXT);
+        GET_PROC_SUFFIX(GetQueryiv, EXT);
+#endif
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(InvalidateFramebuffer);
+        GET_PROC(InvalidateSubFramebuffer);
+    }
+
+    GET_PROC(GetShaderPrecisionFormat);
+
+
+    // End autogenerated content
+    // TODO(kjlubick): Do we want a feature that removes the extension if it doesn't have
+    // the function? This is common on some low-end GPUs.
+
+    if (extensions.has("GL_KHR_debug")) {
+        // In general we have a policy against removing extension strings when the driver does
+        // not provide function pointers for an advertised extension. However, because there is a
+        // known device that advertises GL_KHR_debug but fails to provide the functions and this is
+        // a debugging- only extension we've made an exception. This also can happen when using
+        // APITRACE.
+        if (!interface->fFunctions.fDebugMessageControl) {
+            extensions.remove("GL_KHR_debug");
+        }
+    }
+    interface->fStandard = kGLES_GrGLStandard;
+    interface->fExtensions.swap(&extensions);
+
+    return std::move(interface);
+}
+#endif
diff --git a/src/gpu/gl/GrGLAssembleGLInterfaceAutogen.cpp b/src/gpu/gl/GrGLAssembleGLInterfaceAutogen.cpp
new file mode 100644
index 0000000..be47aba
--- /dev/null
+++ b/src/gpu/gl/GrGLAssembleGLInterfaceAutogen.cpp
@@ -0,0 +1,506 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * THIS FILE IS AUTOGENERATED
+ * Make edits to tools/gpu/gl/interface/templates.go or they will
+ * be overwritten.
+ */
+
+#include "gl/GrGLAssembleInterface.h"
+#include "gl/GrGLAssembleHelpers.h"
+#include "gl/GrGLUtil.h"
+
+#define GET_PROC(F) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F)
+#define GET_PROC_SUFFIX(F, S) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F #S)
+#define GET_PROC_LOCAL(F) GrGL##F##Fn* F = (GrGL##F##Fn*)get(ctx, "gl" #F)
+
+#define GET_EGL_PROC_SUFFIX(F, S) functions->fEGL##F = (GrEGL##F##Fn*)get(ctx, "egl" #F #S)
+
+#if SK_DISABLE_GL_INTERFACE
+sk_sp<const GrGLInterface> GrGLMakeAssembledGLInterface(void *ctx, GrGLGetProc get) {
+    return nullptr;
+}
+#else
+sk_sp<const GrGLInterface> GrGLMakeAssembledGLInterface(void *ctx, GrGLGetProc get) {
+    GET_PROC_LOCAL(GetString);
+    GET_PROC_LOCAL(GetStringi);
+    GET_PROC_LOCAL(GetIntegerv);
+
+    // GetStringi may be nullptr depending on the GL version.
+    if (nullptr == GetString || nullptr == GetIntegerv) {
+        return nullptr;
+    }
+
+    const char* versionString = (const char*) GetString(GR_GL_VERSION);
+    GrGLVersion glVer = GrGLGetVersionFromString(versionString);
+
+    if (glVer < GR_GL_VER(2,0) || GR_GL_INVALID_VER == glVer) {
+        // This is our minimum for non-ES GL.
+        return nullptr;
+    }
+
+    GrEGLQueryStringFn* queryString;
+    GrEGLDisplay display;
+    GrGetEGLQueryAndDisplay(&queryString, &display, ctx, get);
+    GrGLExtensions extensions;
+    if (!extensions.init(kGL_GrGLStandard, GetString, GetStringi, GetIntegerv, queryString,
+                         display)) {
+        return nullptr;
+    }
+
+    sk_sp<GrGLInterface> interface(new GrGLInterface());
+    GrGLInterface::Functions* functions = &interface->fFunctions;
+
+    // Autogenerated content follows
+    GET_PROC(ActiveTexture);
+    GET_PROC(AttachShader);
+    GET_PROC(BindAttribLocation);
+    GET_PROC(BindBuffer);
+    GET_PROC(BindTexture);
+    GET_PROC(BlendColor);
+    GET_PROC(BlendEquation);
+    GET_PROC(BlendFunc);
+    GET_PROC(BufferData);
+    GET_PROC(BufferSubData);
+    GET_PROC(Clear);
+    GET_PROC(ClearColor);
+    GET_PROC(ClearStencil);
+    GET_PROC(ColorMask);
+    GET_PROC(CompileShader);
+    GET_PROC(CompressedTexImage2D);
+    GET_PROC(CompressedTexSubImage2D);
+    GET_PROC(CopyTexSubImage2D);
+    GET_PROC(CreateProgram);
+    GET_PROC(CreateShader);
+    GET_PROC(CullFace);
+    GET_PROC(DeleteBuffers);
+    GET_PROC(DeleteProgram);
+    GET_PROC(DeleteShader);
+    GET_PROC(DeleteTextures);
+    GET_PROC(DepthMask);
+    GET_PROC(Disable);
+    GET_PROC(DisableVertexAttribArray);
+    GET_PROC(DrawArrays);
+    GET_PROC(DrawElements);
+    GET_PROC(Enable);
+    GET_PROC(EnableVertexAttribArray);
+    GET_PROC(Finish);
+    GET_PROC(Flush);
+    GET_PROC(FrontFace);
+    GET_PROC(GenBuffers);
+    GET_PROC(GenTextures);
+    GET_PROC(GetBufferParameteriv);
+    GET_PROC(GetError);
+    GET_PROC(GetIntegerv);
+    GET_PROC(GetProgramInfoLog);
+    GET_PROC(GetProgramiv);
+    GET_PROC(GetShaderInfoLog);
+    GET_PROC(GetShaderiv);
+    GET_PROC(GetString);
+    GET_PROC(GetUniformLocation);
+    GET_PROC(IsTexture);
+    GET_PROC(LineWidth);
+    GET_PROC(LinkProgram);
+    GET_PROC(PixelStorei);
+    GET_PROC(ReadPixels);
+    GET_PROC(Scissor);
+    GET_PROC(ShaderSource);
+    GET_PROC(StencilFunc);
+    GET_PROC(StencilFuncSeparate);
+    GET_PROC(StencilMask);
+    GET_PROC(StencilMaskSeparate);
+    GET_PROC(StencilOp);
+    GET_PROC(StencilOpSeparate);
+    GET_PROC(TexImage2D);
+    GET_PROC(TexParameterf);
+    GET_PROC(TexParameterfv);
+    GET_PROC(TexParameteri);
+    GET_PROC(TexParameteriv);
+    GET_PROC(TexSubImage2D);
+    GET_PROC(Uniform1f);
+    GET_PROC(Uniform1fv);
+    GET_PROC(Uniform1i);
+    GET_PROC(Uniform1iv);
+    GET_PROC(Uniform2f);
+    GET_PROC(Uniform2fv);
+    GET_PROC(Uniform2i);
+    GET_PROC(Uniform2iv);
+    GET_PROC(Uniform3f);
+    GET_PROC(Uniform3fv);
+    GET_PROC(Uniform3i);
+    GET_PROC(Uniform3iv);
+    GET_PROC(Uniform4f);
+    GET_PROC(Uniform4fv);
+    GET_PROC(Uniform4i);
+    GET_PROC(Uniform4iv);
+    GET_PROC(UniformMatrix2fv);
+    GET_PROC(UniformMatrix3fv);
+    GET_PROC(UniformMatrix4fv);
+    GET_PROC(UseProgram);
+    GET_PROC(VertexAttrib1f);
+    GET_PROC(VertexAttrib2fv);
+    GET_PROC(VertexAttrib3fv);
+    GET_PROC(VertexAttrib4fv);
+    GET_PROC(VertexAttribPointer);
+    GET_PROC(Viewport);
+
+    GET_PROC(DrawBuffer);
+    GET_PROC(PolygonMode);
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(GetStringi);
+    }
+
+    GET_PROC(BindVertexArray);
+    GET_PROC(DeleteVertexArrays);
+    GET_PROC(GenVertexArrays);
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(BindFragDataLocation);
+    }
+
+    if (glVer >= GR_GL_VER(3,3)) {
+        GET_PROC(BindFragDataLocationIndexed);
+    } else if (extensions.has("GL_ARB_blend_func_extended")) {
+        GET_PROC(BindFragDataLocationIndexed);
+    }
+
+    if (extensions.has("GL_KHR_blend_equation_advanced")) {
+        GET_PROC_SUFFIX(BlendBarrier, KHR);
+    } else if (extensions.has("GL_NV_blend_equation_advanced")) {
+        GET_PROC_SUFFIX(BlendBarrier, NV);
+    }
+
+    if (glVer >= GR_GL_VER(4,4)) {
+        GET_PROC(ClearTexImage);
+        GET_PROC(ClearTexSubImage);
+    } else if (extensions.has("GL_ARB_clear_texture")) {
+        GET_PROC(ClearTexImage);
+        GET_PROC(ClearTexSubImage);
+    }
+
+    if (glVer >= GR_GL_VER(3,1)) {
+        GET_PROC(DrawArraysInstanced);
+        GET_PROC(DrawElementsInstanced);
+    } else if (extensions.has("GL_ARB_draw_instanced")) {
+        GET_PROC(DrawArraysInstanced);
+        GET_PROC(DrawElementsInstanced);
+    } else if (extensions.has("GL_EXT_draw_instanced")) {
+        GET_PROC_SUFFIX(DrawArraysInstanced, EXT);
+        GET_PROC_SUFFIX(DrawElementsInstanced, EXT);
+    }
+
+    GET_PROC(DrawBuffers);
+    GET_PROC(ReadBuffer);
+
+    if (glVer >= GR_GL_VER(4,0)) {
+        GET_PROC(DrawArraysIndirect);
+        GET_PROC(DrawElementsIndirect);
+    } else if (extensions.has("GL_ARB_draw_indirect")) {
+        GET_PROC(DrawArraysIndirect);
+        GET_PROC(DrawElementsIndirect);
+    }
+
+    GET_PROC(DrawRangeElements);
+
+    if (glVer >= GR_GL_VER(3,2)) {
+        GET_PROC(GetMultisamplefv);
+    } else if (extensions.has("GL_ARB_texture_multisample")) {
+        GET_PROC(GetMultisamplefv);
+    }
+
+    GET_PROC(GetTexLevelParameteriv);
+
+    if (glVer >= GR_GL_VER(4,3)) {
+        GET_PROC(MultiDrawArraysIndirect);
+        GET_PROC(MultiDrawElementsIndirect);
+    } else if (extensions.has("GL_ARB_multi_draw_indirect")) {
+        GET_PROC(MultiDrawArraysIndirect);
+        GET_PROC(MultiDrawElementsIndirect);
+    }
+
+    if (glVer >= GR_GL_VER(3,1)) {
+        GET_PROC(TexBuffer);
+    }
+
+    if (glVer >= GR_GL_VER(4,3)) {
+        GET_PROC(TexBufferRange);
+    }
+
+    if (glVer >= GR_GL_VER(4,2)) {
+        GET_PROC(TexStorage2D);
+    } else if (extensions.has("GL_ARB_texture_storage")) {
+        GET_PROC(TexStorage2D);
+    } else if (extensions.has("GL_EXT_texture_storage")) {
+        GET_PROC_SUFFIX(TexStorage2D, EXT);
+    }
+
+    if (glVer >= GR_GL_VER(4,5)) {
+        GET_PROC(TextureBarrier);
+    } else if (extensions.has("GL_ARB_texture_barrier")) {
+        GET_PROC(TextureBarrier);
+    } else if (extensions.has("GL_NV_texture_barrier")) {
+        GET_PROC_SUFFIX(TextureBarrier, NV);
+    }
+
+    if (glVer >= GR_GL_VER(3,2)) {
+        GET_PROC(VertexAttribDivisor);
+    } else if (extensions.has("GL_ARB_instanced_arrays")) {
+        GET_PROC(VertexAttribDivisor);
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(VertexAttribIPointer);
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(BindFramebuffer);
+        GET_PROC(BindRenderbuffer);
+        GET_PROC(CheckFramebufferStatus);
+        GET_PROC(DeleteFramebuffers);
+        GET_PROC(DeleteRenderbuffers);
+        GET_PROC(FramebufferRenderbuffer);
+        GET_PROC(FramebufferTexture2D);
+        GET_PROC(GenFramebuffers);
+        GET_PROC(GenRenderbuffers);
+        GET_PROC(GenerateMipmap);
+        GET_PROC(GetFramebufferAttachmentParameteriv);
+        GET_PROC(GetRenderbufferParameteriv);
+        GET_PROC(RenderbufferStorage);
+    } else if (extensions.has("GL_ARB_framebuffer_object")) {
+        GET_PROC(BindFramebuffer);
+        GET_PROC(BindRenderbuffer);
+        GET_PROC(CheckFramebufferStatus);
+        GET_PROC(DeleteFramebuffers);
+        GET_PROC(DeleteRenderbuffers);
+        GET_PROC(FramebufferRenderbuffer);
+        GET_PROC(FramebufferTexture2D);
+        GET_PROC(GenFramebuffers);
+        GET_PROC(GenRenderbuffers);
+        GET_PROC(GenerateMipmap);
+        GET_PROC(GetFramebufferAttachmentParameteriv);
+        GET_PROC(GetRenderbufferParameteriv);
+        GET_PROC(RenderbufferStorage);
+    } else if (extensions.has("GL_EXT_framebuffer_object")) {
+        GET_PROC_SUFFIX(BindFramebuffer, EXT);
+        GET_PROC_SUFFIX(BindRenderbuffer, EXT);
+        GET_PROC_SUFFIX(CheckFramebufferStatus, EXT);
+        GET_PROC_SUFFIX(DeleteFramebuffers, EXT);
+        GET_PROC_SUFFIX(DeleteRenderbuffers, EXT);
+        GET_PROC_SUFFIX(FramebufferRenderbuffer, EXT);
+        GET_PROC_SUFFIX(FramebufferTexture2D, EXT);
+        GET_PROC_SUFFIX(GenFramebuffers, EXT);
+        GET_PROC_SUFFIX(GenRenderbuffers, EXT);
+        GET_PROC_SUFFIX(GenerateMipmap, EXT);
+        GET_PROC_SUFFIX(GetFramebufferAttachmentParameteriv, EXT);
+        GET_PROC_SUFFIX(GetRenderbufferParameteriv, EXT);
+        GET_PROC_SUFFIX(RenderbufferStorage, EXT);
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(BlitFramebuffer);
+    } else if (extensions.has("GL_ARB_framebuffer_object")) {
+        GET_PROC(BlitFramebuffer);
+    } else if (extensions.has("GL_EXT_framebuffer_blit")) {
+        GET_PROC_SUFFIX(BlitFramebuffer, EXT);
+    }
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(RenderbufferStorageMultisample);
+    } else if (extensions.has("GL_ARB_framebuffer_object")) {
+        GET_PROC(RenderbufferStorageMultisample);
+    } else if (extensions.has("GL_EXT_framebuffer_multisample")) {
+        GET_PROC_SUFFIX(RenderbufferStorageMultisample, EXT);
+    }
+
+    GET_PROC(MapBuffer);
+
+    GET_PROC(UnmapBuffer);
+
+    if (glVer >= GR_GL_VER(3,0)) {
+        GET_PROC(FlushMappedBufferRange);
+        GET_PROC(MapBufferRange);
+    } else if (extensions.has("GL_ARB_map_buffer_range")) {
+        GET_PROC(FlushMappedBufferRange);
+        GET_PROC(MapBufferRange);
+    }
+
+    if (extensions.has("GL_EXT_debug_marker")) {
+        GET_PROC_SUFFIX(InsertEventMarker, EXT);
+        GET_PROC_SUFFIX(PopGroupMarker, EXT);
+        GET_PROC_SUFFIX(PushGroupMarker, EXT);
+    }
+
+    if (glVer >= GR_GL_VER(4,3)) {
+        GET_PROC(GetProgramResourceLocation);
+    } else if (extensions.has("GL_ARB_program_interface_query")) {
+        GET_PROC(GetProgramResourceLocation);
+    }
+
+    if (extensions.has("GL_NV_path_rendering")) {
+        GET_PROC_SUFFIX(MatrixLoadIdentity, EXT);
+        GET_PROC_SUFFIX(MatrixLoadf, EXT);
+    }
+
+    if (extensions.has("GL_NV_path_rendering")) {
+        GET_PROC_SUFFIX(CoverFillPath, NV);
+        GET_PROC_SUFFIX(CoverFillPathInstanced, NV);
+        GET_PROC_SUFFIX(CoverStrokePath, NV);
+        GET_PROC_SUFFIX(CoverStrokePathInstanced, NV);
+        GET_PROC_SUFFIX(DeletePaths, NV);
+        GET_PROC_SUFFIX(GenPaths, NV);
+        GET_PROC_SUFFIX(IsPath, NV);
+        GET_PROC_SUFFIX(PathCommands, NV);
+        GET_PROC_SUFFIX(PathParameterf, NV);
+        GET_PROC_SUFFIX(PathParameteri, NV);
+        GET_PROC_SUFFIX(PathStencilFunc, NV);
+        GET_PROC_SUFFIX(ProgramPathFragmentInputGen, NV);
+        GET_PROC_SUFFIX(StencilFillPath, NV);
+        GET_PROC_SUFFIX(StencilFillPathInstanced, NV);
+        GET_PROC_SUFFIX(StencilStrokePath, NV);
+        GET_PROC_SUFFIX(StencilStrokePathInstanced, NV);
+        GET_PROC_SUFFIX(StencilThenCoverFillPath, NV);
+        GET_PROC_SUFFIX(StencilThenCoverFillPathInstanced, NV);
+        GET_PROC_SUFFIX(StencilThenCoverStrokePath, NV);
+        GET_PROC_SUFFIX(StencilThenCoverStrokePathInstanced, NV);
+    }
+
+    if (extensions.has("GL_NV_framebuffer_mixed_samples")) {
+        GET_PROC_SUFFIX(CoverageModulation, NV);
+    }
+
+    if (glVer >= GR_GL_VER(4,3)) {
+        GET_PROC(DebugMessageCallback);
+        GET_PROC(DebugMessageControl);
+        GET_PROC(DebugMessageInsert);
+        GET_PROC(GetDebugMessageLog);
+        GET_PROC(ObjectLabel);
+        GET_PROC(PopDebugGroup);
+        GET_PROC(PushDebugGroup);
+    } else if (extensions.has("GL_KHR_debug")) {
+        GET_PROC(DebugMessageCallback);
+        GET_PROC(DebugMessageControl);
+        GET_PROC(DebugMessageInsert);
+        GET_PROC(GetDebugMessageLog);
+        GET_PROC(ObjectLabel);
+        GET_PROC(PopDebugGroup);
+        GET_PROC(PushDebugGroup);
+    }
+
+    if (extensions.has("GL_EXT_window_rectangles")) {
+        GET_PROC_SUFFIX(WindowRectangles, EXT);
+    }
+
+    if (extensions.has("EGL_KHR_image")) {
+        GET_EGL_PROC_SUFFIX(CreateImage, KHR);
+        GET_EGL_PROC_SUFFIX(DestroyImage, KHR);
+    } else if (extensions.has("EGL_KHR_image_base")) {
+        GET_EGL_PROC_SUFFIX(CreateImage, KHR);
+        GET_EGL_PROC_SUFFIX(DestroyImage, KHR);
+    }
+
+    if (glVer >= GR_GL_VER(3,2)) {
+        GET_PROC(ClientWaitSync);
+        GET_PROC(DeleteSync);
+        GET_PROC(FenceSync);
+        GET_PROC(IsSync);
+        GET_PROC(WaitSync);
+    } else if (extensions.has("GL_ARB_sync")) {
+        GET_PROC(ClientWaitSync);
+        GET_PROC(DeleteSync);
+        GET_PROC(FenceSync);
+        GET_PROC(IsSync);
+        GET_PROC(WaitSync);
+    }
+
+    if (glVer >= GR_GL_VER(4,2)) {
+        GET_PROC(GetInternalformativ);
+    } else if (extensions.has("GL_ARB_internalformat_query")) {
+        GET_PROC(GetInternalformativ);
+    }
+
+    if (glVer >= GR_GL_VER(4,1)) {
+        GET_PROC(GetProgramBinary);
+        GET_PROC(ProgramBinary);
+        GET_PROC(ProgramParameteri);
+    }
+
+    if (glVer >= GR_GL_VER(3,2)) {
+        GET_PROC(BindSampler);
+        GET_PROC(DeleteSamplers);
+        GET_PROC(GenSamplers);
+        GET_PROC(SamplerParameteri);
+        GET_PROC(SamplerParameteriv);
+    } else if (extensions.has("GL_ARB_sampler_objects")) {
+        GET_PROC(BindSampler);
+        GET_PROC(DeleteSamplers);
+        GET_PROC(GenSamplers);
+        GET_PROC(SamplerParameteri);
+        GET_PROC(SamplerParameteriv);
+    }
+
+    GET_PROC(GetQueryObjectiv);
+
+#if GR_TEST_UTILS
+    GET_PROC(BeginQuery);
+    GET_PROC(DeleteQueries);
+    GET_PROC(EndQuery);
+    GET_PROC(GenQueries);
+    GET_PROC(GetQueryObjectuiv);
+    GET_PROC(GetQueryiv);
+#endif
+
+    if (glVer >= GR_GL_VER(3,3)) {
+        GET_PROC(GetQueryObjecti64v);
+        GET_PROC(GetQueryObjectui64v);
+    } else if (extensions.has("GL_ARB_timer_query")) {
+        GET_PROC(GetQueryObjecti64v);
+        GET_PROC(GetQueryObjectui64v);
+    } else if (extensions.has("GL_EXT_timer_query")) {
+        GET_PROC_SUFFIX(GetQueryObjecti64v, EXT);
+        GET_PROC_SUFFIX(GetQueryObjectui64v, EXT);
+    }
+
+    if (glVer >= GR_GL_VER(3,3)) {
+        GET_PROC(QueryCounter);
+    } else if (extensions.has("GL_ARB_timer_query")) {
+        GET_PROC(QueryCounter);
+    }
+
+    if (glVer >= GR_GL_VER(4,3)) {
+        GET_PROC(InvalidateBufferData);
+        GET_PROC(InvalidateBufferSubData);
+        GET_PROC(InvalidateTexImage);
+        GET_PROC(InvalidateTexSubImage);
+    } else if (extensions.has("GL_ARB_invalidate_subdata")) {
+        GET_PROC(InvalidateBufferData);
+        GET_PROC(InvalidateBufferSubData);
+        GET_PROC(InvalidateTexImage);
+        GET_PROC(InvalidateTexSubImage);
+    }
+
+    if (glVer >= GR_GL_VER(4,3)) {
+        GET_PROC(InvalidateFramebuffer);
+        GET_PROC(InvalidateSubFramebuffer);
+    } else if (extensions.has("GL_ARB_invalidate_subdata")) {
+        GET_PROC(InvalidateFramebuffer);
+        GET_PROC(InvalidateSubFramebuffer);
+    }
+
+    if (glVer >= GR_GL_VER(4,3)) {
+        GET_PROC(GetShaderPrecisionFormat);
+    } else if (extensions.has("GL_ARB_ES2_compatibility")) {
+        GET_PROC(GetShaderPrecisionFormat);
+    }
+
+
+    // End autogenerated content
+    interface->fStandard = kGL_GrGLStandard;
+    interface->fExtensions.swap(&extensions);
+
+    return std::move(interface);
+}
+#endif
diff --git a/src/gpu/gl/GrGLAssembleHelpers.cpp b/src/gpu/gl/GrGLAssembleHelpers.cpp
new file mode 100644
index 0000000..94116f7
--- /dev/null
+++ b/src/gpu/gl/GrGLAssembleHelpers.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gl/GrGLAssembleHelpers.h"
+#include "gl/GrGLUtil.h"
+
+void GrGetEGLQueryAndDisplay(GrEGLQueryStringFn** queryString, GrEGLDisplay* display,
+                             void* ctx, GrGLGetProc get) {
+    *queryString = (GrEGLQueryStringFn*)get(ctx, "eglQueryString");
+    *display = GR_EGL_NO_DISPLAY;
+    if (*queryString) {
+        GrEGLGetCurrentDisplayFn* getCurrentDisplay =
+                (GrEGLGetCurrentDisplayFn*)get(ctx, "eglGetCurrentDisplay");
+        if (getCurrentDisplay) {
+            *display = getCurrentDisplay();
+        } else {
+            *queryString = nullptr;
+        }
+    }
+}
diff --git a/src/gpu/gl/GrGLAssembleInterface.cpp b/src/gpu/gl/GrGLAssembleInterface.cpp
index 138a4c1..850a0e4 100644
--- a/src/gpu/gl/GrGLAssembleInterface.cpp
+++ b/src/gpu/gl/GrGLAssembleInterface.cpp
@@ -7,14 +7,11 @@
 
 
 #include "gl/GrGLAssembleInterface.h"
-#include "GrGLUtil.h"
+#include "gl/GrGLAssembleHelpers.h"
+#include "gl/GrGLUtil.h"
 
-#define GET_PROC(F) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F)
-#define GET_PROC_SUFFIX(F, S) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F #S)
 #define GET_PROC_LOCAL(F) GrGL##F##Fn* F = (GrGL##F##Fn*)get(ctx, "gl" #F)
 
-#define GET_EGL_PROC_SUFFIX(F, S) functions->fEGL##F = (GrEGL##F##Fn*)get(ctx, "egl" #F #S)
-
 sk_sp<const GrGLInterface> GrGLMakeAssembledInterface(void *ctx, GrGLGetProc get) {
     GET_PROC_LOCAL(GetString);
     if (nullptr == GetString) {
@@ -27,844 +24,19 @@
     }
 
     GrGLStandard standard = GrGLGetStandardInUseFromString(verStr);
+    // standard can be unused (optimzed away) if SK_ASSUME_GL_ES is set
+    sk_ignore_unused_variable(standard);
 
-    if (kGLES_GrGLStandard == standard) {
+    if (GR_IS_GR_GL_ES(standard)) {
         return GrGLMakeAssembledGLESInterface(ctx, get);
-    } else if (kGL_GrGLStandard == standard) {
+    } else if (GR_IS_GR_GL(standard)) {
         return GrGLMakeAssembledGLInterface(ctx, get);
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        return GrGLMakeAssembledWebGLInterface(ctx, get);
     }
     return nullptr;
 }
 
-static void get_egl_query_and_display(GrEGLQueryStringFn** queryString, GrEGLDisplay* display,
-                                      void* ctx, GrGLGetProc get) {
-    *queryString = (GrEGLQueryStringFn*)get(ctx, "eglQueryString");
-    *display = GR_EGL_NO_DISPLAY;
-    if (*queryString) {
-        GrEGLGetCurrentDisplayFn* getCurrentDisplay =
-                (GrEGLGetCurrentDisplayFn*)get(ctx, "eglGetCurrentDisplay");
-        if (getCurrentDisplay) {
-            *display = getCurrentDisplay();
-        } else {
-            *queryString = nullptr;
-        }
-    }
-}
-
-sk_sp<const GrGLInterface> GrGLMakeAssembledGLInterface(void *ctx, GrGLGetProc get) {
-    GET_PROC_LOCAL(GetString);
-    GET_PROC_LOCAL(GetStringi);
-    GET_PROC_LOCAL(GetIntegerv);
-
-    // GetStringi may be nullptr depending on the GL version.
-    if (nullptr == GetString || nullptr == GetIntegerv) {
-        return nullptr;
-    }
-
-    const char* versionString = (const char*) GetString(GR_GL_VERSION);
-    GrGLVersion glVer = GrGLGetVersionFromString(versionString);
-
-    if (glVer < GR_GL_VER(2,0) || GR_GL_INVALID_VER == glVer) {
-        // This is our minimum for non-ES GL.
-        return nullptr;
-    }
-
-    GrEGLQueryStringFn* queryString;
-    GrEGLDisplay display;
-    get_egl_query_and_display(&queryString, &display, ctx, get);
-    GrGLExtensions extensions;
-    if (!extensions.init(kGL_GrGLStandard, GetString, GetStringi, GetIntegerv, queryString,
-                         display)) {
-        return nullptr;
-    }
-
-    sk_sp<GrGLInterface> interface(new GrGLInterface());
-    GrGLInterface::Functions* functions = &interface->fFunctions;
-
-    GET_PROC(ActiveTexture);
-    GET_PROC(AttachShader);
-    GET_PROC(BindAttribLocation);
-    GET_PROC(BindBuffer);
-    if (glVer >= GR_GL_VER(3,0)) {
-        GET_PROC(BindFragDataLocation);
-    }
-    GET_PROC(BeginQuery);
-    GET_PROC(BindTexture);
-
-    if (extensions.has("GL_KHR_blend_equation_advanced")) {
-        GET_PROC_SUFFIX(BlendBarrier, KHR);
-    } else if (extensions.has("GL_NV_blend_equation_advanced")) {
-        GET_PROC_SUFFIX(BlendBarrier, NV);
-    }
-
-    GET_PROC(BlendColor);
-    GET_PROC(BlendEquation);
-    GET_PROC(BlendFunc);
-    GET_PROC(BufferData);
-    GET_PROC(BufferSubData);
-    GET_PROC(Clear);
-    GET_PROC(ClearColor);
-    GET_PROC(ClearStencil);
-    if (glVer >= GR_GL_VER(4,4) || extensions.has("GL_ARB_clear_texture")) {
-        GET_PROC(ClearTexImage);
-        GET_PROC(ClearTexSubImage);
-    }
-    GET_PROC(ColorMask);
-    GET_PROC(CompileShader);
-    GET_PROC(CompressedTexImage2D);
-    GET_PROC(CompressedTexSubImage2D);
-    GET_PROC(CopyTexSubImage2D);
-    GET_PROC(CreateProgram);
-    GET_PROC(CreateShader);
-    GET_PROC(CullFace);
-    GET_PROC(DeleteBuffers);
-    GET_PROC(DeleteProgram);
-    GET_PROC(DeleteQueries);
-    GET_PROC(DeleteShader);
-    GET_PROC(DeleteTextures);
-    GET_PROC(DepthMask);
-    GET_PROC(Disable);
-    GET_PROC(DisableVertexAttribArray);
-    GET_PROC(DrawArrays);
-    GET_PROC(DrawBuffer);
-    GET_PROC(DrawBuffers);
-    GET_PROC(DrawElements);
-
-    if (glVer >= GR_GL_VER(3,1) || extensions.has("GL_ARB_draw_instanced") ||
-        extensions.has("GL_EXT_draw_instanced")) {
-        GET_PROC(DrawArraysInstanced);
-        GET_PROC(DrawElementsInstanced);
-    }
-
-    if (glVer >= GR_GL_VER(4,0) || extensions.has("GL_ARB_draw_indirect")) {
-        GET_PROC(DrawArraysIndirect);
-        GET_PROC(DrawElementsIndirect);
-    }
-    GET_PROC(DrawRangeElements);
-    GET_PROC(Enable);
-    GET_PROC(EnableVertexAttribArray);
-    GET_PROC(EndQuery);
-    GET_PROC(Finish);
-    GET_PROC(Flush);
-    GET_PROC(FrontFace);
-    GET_PROC(GenBuffers);
-    GET_PROC(GetBufferParameteriv);
-    GET_PROC(GetError);
-    GET_PROC(GetIntegerv);
-    if (glVer >= GR_GL_VER(3,2) || extensions.has("GL_ARB_texture_multisample")) {
-        GET_PROC(GetMultisamplefv);
-    }
-    GET_PROC(GetQueryObjectiv);
-    GET_PROC(GetQueryObjectuiv);
-    if (glVer >= GR_GL_VER(3,3) || extensions.has("GL_ARB_timer_query")) {
-        GET_PROC(GetQueryObjecti64v);
-        GET_PROC(GetQueryObjectui64v);
-        GET_PROC(QueryCounter);
-    } else if (extensions.has("GL_EXT_timer_query")) {
-        GET_PROC_SUFFIX(GetQueryObjecti64v, EXT);
-        GET_PROC_SUFFIX(GetQueryObjectui64v, EXT);
-    }
-    GET_PROC(GetQueryiv);
-    GET_PROC(GetProgramInfoLog);
-    GET_PROC(GetProgramiv);
-    GET_PROC(GetShaderInfoLog);
-    GET_PROC(GetShaderiv);
-    GET_PROC(GetString);
-    GET_PROC(GetStringi);
-    GET_PROC(GetShaderPrecisionFormat);
-    GET_PROC(GetTexLevelParameteriv);
-    GET_PROC(GenQueries);
-    GET_PROC(GenTextures);
-    GET_PROC(GetUniformLocation);
-    GET_PROC(IsTexture);
-    GET_PROC(LineWidth);
-    GET_PROC(LinkProgram);
-    GET_PROC(MapBuffer);
-
-    if (glVer >= GR_GL_VER(4,3) || extensions.has("GL_ARB_multi_draw_indirect")) {
-        GET_PROC(MultiDrawArraysIndirect);
-        GET_PROC(MultiDrawElementsIndirect);
-    }
-
-    GET_PROC(PixelStorei);
-    GET_PROC(PolygonMode);
-    if (extensions.has("GL_EXT_raster_multisample")) {
-        GET_PROC_SUFFIX(RasterSamples, EXT);
-    }
-    GET_PROC(ReadBuffer);
-    GET_PROC(ReadPixels);
-    GET_PROC(Scissor);
-    GET_PROC(ShaderSource);
-    GET_PROC(StencilFunc);
-    GET_PROC(StencilFuncSeparate);
-    GET_PROC(StencilMask);
-    GET_PROC(StencilMaskSeparate);
-    GET_PROC(StencilOp);
-    GET_PROC(StencilOpSeparate);
-    if (glVer >= GR_GL_VER(3,1)) {
-        GET_PROC(TexBuffer);
-    }
-    if (glVer >= GR_GL_VER(4,3)) {
-        GET_PROC(TexBufferRange);
-    }
-    GET_PROC(TexImage2D);
-    GET_PROC(TexParameterf);
-    GET_PROC(TexParameterfv);
-    GET_PROC(TexParameteri);
-    GET_PROC(TexParameteriv);
-    if (glVer >= GR_GL_VER(4,2) || extensions.has("GL_ARB_texture_storage")) {
-        GET_PROC(TexStorage2D);
-    } else if (extensions.has("GL_EXT_texture_storage")) {
-        GET_PROC_SUFFIX(TexStorage2D, EXT);
-    }
-    GET_PROC(TexSubImage2D);
-    if (glVer >= GR_GL_VER(4,5) || extensions.has("GL_ARB_texture_barrier")) {
-        GET_PROC(TextureBarrier);
-    } else if (extensions.has("GL_NV_texture_barrier")) {
-        GET_PROC_SUFFIX(TextureBarrier, NV);
-    }
-    GET_PROC(Uniform1f);
-    GET_PROC(Uniform1i);
-    GET_PROC(Uniform1fv);
-    GET_PROC(Uniform1iv);
-    GET_PROC(Uniform2f);
-    GET_PROC(Uniform2i);
-    GET_PROC(Uniform2fv);
-    GET_PROC(Uniform2iv);
-    GET_PROC(Uniform3f);
-    GET_PROC(Uniform3i);
-    GET_PROC(Uniform3fv);
-    GET_PROC(Uniform3iv);
-    GET_PROC(Uniform4f);
-    GET_PROC(Uniform4i);
-    GET_PROC(Uniform4fv);
-    GET_PROC(Uniform4iv);
-    GET_PROC(UniformMatrix2fv);
-    GET_PROC(UniformMatrix3fv);
-    GET_PROC(UniformMatrix4fv);
-    GET_PROC(UnmapBuffer);
-    GET_PROC(UseProgram);
-    GET_PROC(VertexAttrib1f);
-    GET_PROC(VertexAttrib2fv);
-    GET_PROC(VertexAttrib3fv);
-    GET_PROC(VertexAttrib4fv);
-
-    if (glVer >= GR_GL_VER(3,2) || extensions.has("GL_ARB_instanced_arrays")) {
-        GET_PROC(VertexAttribDivisor);
-    }
-
-    if (glVer >= GR_GL_VER(3,0)) {
-        GET_PROC(VertexAttribIPointer);
-    }
-
-    GET_PROC(VertexAttribPointer);
-    GET_PROC(Viewport);
-    GET_PROC(BindFragDataLocationIndexed);
-
-    if (glVer >= GR_GL_VER(3,0) || extensions.has("GL_ARB_vertex_array_object")) {
-        // no ARB suffix for GL_ARB_vertex_array_object
-        GET_PROC(BindVertexArray);
-        GET_PROC(GenVertexArrays);
-        GET_PROC(DeleteVertexArrays);
-    } else if (extensions.has("GL_APPLE_vertex_array_object")) {
-        GET_PROC_SUFFIX(BindVertexArray, APPLE);
-        GET_PROC_SUFFIX(GenVertexArrays, APPLE);
-        GET_PROC_SUFFIX(DeleteVertexArrays, APPLE);
-    }
-
-    if (glVer >= GR_GL_VER(3,0) || extensions.has("GL_ARB_map_buffer_range")) {
-        GET_PROC(MapBufferRange);
-        GET_PROC(FlushMappedBufferRange);
-    }
-
-    // First look for GL3.0 FBO or GL_ARB_framebuffer_object (same since
-    // GL_ARB_framebuffer_object doesn't use ARB suffix.)
-    if (glVer >= GR_GL_VER(3,0) || extensions.has("GL_ARB_framebuffer_object")) {
-        GET_PROC(GenerateMipmap);
-        GET_PROC(GenFramebuffers);
-        GET_PROC(GetFramebufferAttachmentParameteriv);
-        GET_PROC(GetRenderbufferParameteriv);
-        GET_PROC(BindFramebuffer);
-        GET_PROC(FramebufferTexture2D);
-        GET_PROC(CheckFramebufferStatus);
-        GET_PROC(DeleteFramebuffers);
-        GET_PROC(RenderbufferStorage);
-        GET_PROC(GenRenderbuffers);
-        GET_PROC(DeleteRenderbuffers);
-        GET_PROC(FramebufferRenderbuffer);
-        GET_PROC(BindRenderbuffer);
-        GET_PROC(RenderbufferStorageMultisample);
-        GET_PROC(BlitFramebuffer);
-    } else if (extensions.has("GL_EXT_framebuffer_object")) {
-        GET_PROC_SUFFIX(GenerateMipmap, EXT);
-        GET_PROC_SUFFIX(GenFramebuffers, EXT);
-        GET_PROC_SUFFIX(GetFramebufferAttachmentParameteriv, EXT);
-        GET_PROC_SUFFIX(GetRenderbufferParameteriv, EXT);
-        GET_PROC_SUFFIX(BindFramebuffer, EXT);
-        GET_PROC_SUFFIX(FramebufferTexture2D, EXT);
-        GET_PROC_SUFFIX(CheckFramebufferStatus, EXT);
-        GET_PROC_SUFFIX(DeleteFramebuffers, EXT);
-        GET_PROC_SUFFIX(RenderbufferStorage, EXT);
-        GET_PROC_SUFFIX(GenRenderbuffers, EXT);
-        GET_PROC_SUFFIX(DeleteRenderbuffers, EXT);
-        GET_PROC_SUFFIX(FramebufferRenderbuffer, EXT);
-        GET_PROC_SUFFIX(BindRenderbuffer, EXT);
-        if (extensions.has("GL_EXT_framebuffer_multisample")) {
-            GET_PROC_SUFFIX(RenderbufferStorageMultisample, EXT);
-        }
-        if (extensions.has("GL_EXT_framebuffer_blit")) {
-            GET_PROC_SUFFIX(BlitFramebuffer, EXT);
-        }
-    } else {
-        // we must have FBOs
-        return nullptr;
-    }
-
-    if (extensions.has("GL_NV_path_rendering")) {
-        GET_PROC_SUFFIX(MatrixLoadf, EXT);
-        GET_PROC_SUFFIX(MatrixLoadIdentity, EXT);
-        GET_PROC_SUFFIX(PathCommands, NV);
-        GET_PROC_SUFFIX(PathParameteri, NV);
-        GET_PROC_SUFFIX(PathParameterf, NV);
-        GET_PROC_SUFFIX(GenPaths, NV);
-        GET_PROC_SUFFIX(DeletePaths, NV);
-        GET_PROC_SUFFIX(IsPath, NV);
-        GET_PROC_SUFFIX(PathStencilFunc, NV);
-        GET_PROC_SUFFIX(StencilFillPath, NV);
-        GET_PROC_SUFFIX(StencilStrokePath, NV);
-        GET_PROC_SUFFIX(StencilFillPathInstanced, NV);
-        GET_PROC_SUFFIX(StencilStrokePathInstanced, NV);
-        GET_PROC_SUFFIX(CoverFillPath, NV);
-        GET_PROC_SUFFIX(CoverStrokePath, NV);
-        GET_PROC_SUFFIX(CoverFillPathInstanced, NV);
-        GET_PROC_SUFFIX(CoverStrokePathInstanced, NV);
-        GET_PROC_SUFFIX(StencilThenCoverFillPath, NV);
-        GET_PROC_SUFFIX(StencilThenCoverStrokePath, NV);
-        GET_PROC_SUFFIX(StencilThenCoverFillPathInstanced, NV);
-        GET_PROC_SUFFIX(StencilThenCoverStrokePathInstanced, NV);
-        GET_PROC_SUFFIX(ProgramPathFragmentInputGen, NV);
-    }
-
-    if (extensions.has("GL_NV_framebuffer_mixed_samples")) {
-        GET_PROC_SUFFIX(CoverageModulation, NV);
-    }
-
-    if (extensions.has("GL_EXT_debug_marker")) {
-        GET_PROC_SUFFIX(InsertEventMarker, EXT);
-        GET_PROC_SUFFIX(PushGroupMarker, EXT);
-        GET_PROC_SUFFIX(PopGroupMarker, EXT);
-    }
-
-    if (glVer >= GR_GL_VER(4,3) || extensions.has("GL_ARB_invalidate_subdata")) {
-        GET_PROC(InvalidateBufferData);
-        GET_PROC(InvalidateBufferSubData);
-        GET_PROC(InvalidateFramebuffer);
-        GET_PROC(InvalidateSubFramebuffer);
-        GET_PROC(InvalidateTexImage);
-        GET_PROC(InvalidateTexSubImage);
-    }
-
-    if (glVer >= GR_GL_VER(4,3) || extensions.has("GL_ARB_program_interface_query")) {
-        GET_PROC(GetProgramResourceLocation);
-    }
-
-    if (glVer >= GR_GL_VER(4,3) || extensions.has("GL_KHR_debug")) {
-        // KHR_debug defines these methods to have no suffix in an OpenGL (not ES) context.
-        GET_PROC(DebugMessageControl);
-        GET_PROC(DebugMessageInsert);
-        GET_PROC(DebugMessageCallback);
-        GET_PROC(GetDebugMessageLog);
-        GET_PROC(PushDebugGroup);
-        GET_PROC(PopDebugGroup);
-        GET_PROC(ObjectLabel);
-    }
-
-    if (extensions.has("GL_EXT_window_rectangles")) {
-        GET_PROC_SUFFIX(WindowRectangles, EXT);
-    }
-
-    if (extensions.has("EGL_KHR_image") || extensions.has("EGL_KHR_image_base")) {
-        GET_EGL_PROC_SUFFIX(CreateImage, KHR);
-        GET_EGL_PROC_SUFFIX(DestroyImage, KHR);
-    }
-
-    if (glVer >= GR_GL_VER(3, 2) || extensions.has("GL_ARB_sync")) {
-        GET_PROC(FenceSync);
-        GET_PROC(IsSync);
-        GET_PROC(ClientWaitSync);
-        GET_PROC(WaitSync);
-        GET_PROC(DeleteSync);
-    }
-
-    if (glVer >= GR_GL_VER(4,2) || extensions.has("GL_ARB_internalformat_query")) {
-        GET_PROC(GetInternalformativ);
-    }
-
-    if (glVer >= GR_GL_VER(4, 1)) {
-        GET_PROC(GetProgramBinary);
-        GET_PROC(ProgramBinary);
-        GET_PROC(ProgramParameteri);
-    }
-
-    if (glVer >= GR_GL_VER(3,2) || extensions.has("GL_ARB_sampler_objects")) {
-        GET_PROC(BindSampler);
-        GET_PROC(DeleteSamplers);
-        GET_PROC(GenSamplers);
-        GET_PROC(SamplerParameteri);
-        GET_PROC(SamplerParameteriv);
-    }
-
-    interface->fStandard = kGL_GrGLStandard;
-    interface->fExtensions.swap(&extensions);
-
-    return std::move(interface);
-}
-
-sk_sp<const GrGLInterface> GrGLMakeAssembledGLESInterface(void *ctx, GrGLGetProc get) {
-    GET_PROC_LOCAL(GetString);
-    if (nullptr == GetString) {
-        return nullptr;
-    }
-
-    const char* verStr = reinterpret_cast<const char*>(GetString(GR_GL_VERSION));
-    GrGLVersion version = GrGLGetVersionFromString(verStr);
-
-    if (version < GR_GL_VER(2,0)) {
-        return nullptr;
-    }
-
-    GET_PROC_LOCAL(GetIntegerv);
-    GET_PROC_LOCAL(GetStringi);
-    GrEGLQueryStringFn* queryString;
-    GrEGLDisplay display;
-    get_egl_query_and_display(&queryString, &display, ctx, get);
-    GrGLExtensions extensions;
-    if (!extensions.init(kGLES_GrGLStandard, GetString, GetStringi, GetIntegerv, queryString,
-                         display)) {
-        return nullptr;
-    }
-
-    sk_sp<GrGLInterface> interface(new GrGLInterface);
-    GrGLInterface::Functions* functions = &interface->fFunctions;
-
-    GET_PROC(ActiveTexture);
-    GET_PROC(AttachShader);
-    GET_PROC(BindAttribLocation);
-    GET_PROC(BindBuffer);
-    GET_PROC(BindTexture);
-
-    if (version >= GR_GL_VER(3,0)) {
-        GET_PROC(BindVertexArray);
-        GET_PROC(DeleteVertexArrays);
-        GET_PROC(GenVertexArrays);
-    } else if (extensions.has("GL_OES_vertex_array_object")) {
-        GET_PROC_SUFFIX(BindVertexArray, OES);
-        GET_PROC_SUFFIX(DeleteVertexArrays, OES);
-        GET_PROC_SUFFIX(GenVertexArrays, OES);
-    }
-
-    if (version >= GR_GL_VER(3,0) && extensions.has("GL_EXT_blend_func_extended")) {
-        GET_PROC_SUFFIX(BindFragDataLocation, EXT);
-        GET_PROC_SUFFIX(BindFragDataLocationIndexed, EXT);
-    }
-
-    if (extensions.has("GL_KHR_blend_equation_advanced")) {
-        GET_PROC_SUFFIX(BlendBarrier, KHR);
-    } else if (extensions.has("GL_NV_blend_equation_advanced")) {
-        GET_PROC_SUFFIX(BlendBarrier, NV);
-    }
-
-    GET_PROC(BlendColor);
-    GET_PROC(BlendEquation);
-    GET_PROC(BlendFunc);
-    GET_PROC(BufferData);
-    GET_PROC(BufferSubData);
-    GET_PROC(Clear);
-    GET_PROC(ClearColor);
-    GET_PROC(ClearStencil);
-    if (extensions.has("GL_EXT_clear_texture")) {
-        GET_PROC_SUFFIX(ClearTexImage, EXT);
-        GET_PROC_SUFFIX(ClearTexSubImage, EXT);
-    }
-    GET_PROC(ColorMask);
-    GET_PROC(CompileShader);
-    GET_PROC(CompressedTexImage2D);
-    GET_PROC(CompressedTexSubImage2D);
-    GET_PROC(CopyTexSubImage2D);
-    GET_PROC(CreateProgram);
-    GET_PROC(CreateShader);
-    GET_PROC(CullFace);
-    GET_PROC(DeleteBuffers);
-    GET_PROC(DeleteProgram);
-    GET_PROC(DeleteShader);
-    GET_PROC(DeleteTextures);
-    GET_PROC(DepthMask);
-    GET_PROC(Disable);
-    GET_PROC(DisableVertexAttribArray);
-    GET_PROC(DrawArrays);
-
-    if (version >= GR_GL_VER(3,0)) {
-        GET_PROC(DrawArraysInstanced);
-        GET_PROC(DrawBuffers);
-        GET_PROC(DrawElementsInstanced);
-    } else if (extensions.has("GL_EXT_draw_instanced")) {
-        GET_PROC_SUFFIX(DrawArraysInstanced, EXT);
-        GET_PROC_SUFFIX(DrawElementsInstanced, EXT);
-    }
-
-    if (version >= GR_GL_VER(3,1)) {
-        GET_PROC(DrawArraysIndirect);
-        GET_PROC(DrawElementsIndirect);
-    }
-
-    GET_PROC(DrawElements);
-    if (version >= GR_GL_VER(3,0)) {
-        GET_PROC(DrawRangeElements);
-    }
-    GET_PROC(Enable);
-    GET_PROC(EnableVertexAttribArray);
-    GET_PROC(Finish);
-    GET_PROC(Flush);
-    GET_PROC(FrontFace);
-    GET_PROC(GenBuffers);
-    GET_PROC(GenerateMipmap);
-    GET_PROC(GenTextures);
-    GET_PROC(GetBufferParameteriv);
-    GET_PROC(GetError);
-    GET_PROC(GetIntegerv);
-
-    if (version >= GR_GL_VER(3,1)) {
-        GET_PROC(GetMultisamplefv);
-    }
-
-    GET_PROC(GetProgramInfoLog);
-    GET_PROC(GetProgramiv);
-    GET_PROC(GetShaderInfoLog);
-    GET_PROC(GetShaderPrecisionFormat);
-    GET_PROC(GetShaderiv);
-    GET_PROC(GetString);
-    GET_PROC(GetStringi);
-    if (version >= GR_GL_VER(3,1)) {
-        GET_PROC(GetTexLevelParameteriv);
-    }
-    GET_PROC(GetUniformLocation);
-    GET_PROC(IsTexture);
-    GET_PROC(LineWidth);
-    GET_PROC(LinkProgram);
-
-    if (extensions.has("GL_EXT_multi_draw_indirect")) {
-        GET_PROC_SUFFIX(MultiDrawArraysIndirect, EXT);
-        GET_PROC_SUFFIX(MultiDrawElementsIndirect, EXT);
-    }
-
-    GET_PROC(PixelStorei);
-
-    if (extensions.has("GL_EXT_raster_multisample")) {
-        GET_PROC_SUFFIX(RasterSamples, EXT);
-    }
-
-    if (version >= GR_GL_VER(3,0)) {
-        GET_PROC(ReadBuffer);
-    }
-    GET_PROC(ReadPixels);
-    GET_PROC(Scissor);
-    GET_PROC(ShaderSource);
-    GET_PROC(StencilFunc);
-    GET_PROC(StencilFuncSeparate);
-    GET_PROC(StencilMask);
-    GET_PROC(StencilMaskSeparate);
-    GET_PROC(StencilOp);
-    GET_PROC(StencilOpSeparate);
-
-    if (version >= GR_GL_VER(3,2)) {
-        GET_PROC(TexBuffer);
-        GET_PROC(TexBufferRange);
-    } else if (extensions.has("GL_OES_texture_buffer")) {
-        GET_PROC_SUFFIX(TexBuffer, OES);
-        GET_PROC_SUFFIX(TexBufferRange, OES);
-    } else if (extensions.has("GL_EXT_texture_buffer")) {
-        GET_PROC_SUFFIX(TexBuffer, EXT);
-        GET_PROC_SUFFIX(TexBufferRange, EXT);
-    }
-
-    GET_PROC(TexImage2D);
-    GET_PROC(TexParameterf);
-    GET_PROC(TexParameterfv);
-    GET_PROC(TexParameteri);
-    GET_PROC(TexParameteriv);
-    GET_PROC(TexSubImage2D);
-
-    if (version >= GR_GL_VER(3,0)) {
-        GET_PROC(TexStorage2D);
-    } else {
-        GET_PROC_SUFFIX(TexStorage2D, EXT);
-    }
-
-    if (extensions.has("GL_NV_texture_barrier")) {
-        GET_PROC_SUFFIX(TextureBarrier, NV);
-    }
-
-    GET_PROC_SUFFIX(DiscardFramebuffer, EXT);
-    GET_PROC(Uniform1f);
-    GET_PROC(Uniform1i);
-    GET_PROC(Uniform1fv);
-    GET_PROC(Uniform1iv);
-    GET_PROC(Uniform2f);
-    GET_PROC(Uniform2i);
-    GET_PROC(Uniform2fv);
-    GET_PROC(Uniform2iv);
-    GET_PROC(Uniform3f);
-    GET_PROC(Uniform3i);
-    GET_PROC(Uniform3fv);
-    GET_PROC(Uniform3iv);
-    GET_PROC(Uniform4f);
-    GET_PROC(Uniform4i);
-    GET_PROC(Uniform4fv);
-    GET_PROC(Uniform4iv);
-    GET_PROC(UniformMatrix2fv);
-    GET_PROC(UniformMatrix3fv);
-    GET_PROC(UniformMatrix4fv);
-    GET_PROC(UseProgram);
-    GET_PROC(VertexAttrib1f);
-    GET_PROC(VertexAttrib2fv);
-    GET_PROC(VertexAttrib3fv);
-    GET_PROC(VertexAttrib4fv);
-
-    if (version >= GR_GL_VER(3,0)) {
-        GET_PROC(VertexAttribDivisor);
-    } else if (extensions.has("GL_EXT_instanced_arrays")) {
-        GET_PROC_SUFFIX(VertexAttribDivisor, EXT);
-    }
-
-    if (version >= GR_GL_VER(3,0)) {
-        GET_PROC(VertexAttribIPointer);
-    }
-
-    GET_PROC(VertexAttribPointer);
-    GET_PROC(Viewport);
-    GET_PROC(BindFramebuffer);
-    GET_PROC(BindRenderbuffer);
-    GET_PROC(CheckFramebufferStatus);
-    GET_PROC(DeleteFramebuffers);
-    GET_PROC(DeleteRenderbuffers);
-    GET_PROC(FramebufferRenderbuffer);
-    GET_PROC(FramebufferTexture2D);
-
-    if (version >= GR_GL_VER(3,0)) {
-        GET_PROC(RenderbufferStorageMultisample);
-        GET_PROC(BlitFramebuffer);
-    } else if (extensions.has("GL_CHROMIUM_framebuffer_multisample")) {
-        GET_PROC_SUFFIX(RenderbufferStorageMultisample, CHROMIUM);
-        GET_PROC_SUFFIX(BlitFramebuffer, CHROMIUM);
-    } else {
-        if (extensions.has("GL_ANGLE_framebuffer_multisample")) {
-            GET_PROC_SUFFIX(RenderbufferStorageMultisample, ANGLE);
-        }
-        if (extensions.has("GL_ANGLE_framebuffer_blit")) {
-            GET_PROC_SUFFIX(BlitFramebuffer, ANGLE);
-        }
-    }
-
-    if (extensions.has("GL_CHROMIUM_map_sub")) {
-        GET_PROC_SUFFIX(MapBufferSubData, CHROMIUM);
-        GET_PROC_SUFFIX(MapTexSubImage2D, CHROMIUM);
-        GET_PROC_SUFFIX(UnmapBufferSubData, CHROMIUM);
-        GET_PROC_SUFFIX(UnmapTexSubImage2D, CHROMIUM);
-    }
-
-    if (extensions.has("GL_EXT_multisampled_render_to_texture")) {
-        GET_PROC_SUFFIX(FramebufferTexture2DMultisample, EXT);
-        functions->fRenderbufferStorageMultisampleES2EXT =
-                (GrGLRenderbufferStorageMultisampleFn*)get(ctx,
-                                                           "glRenderbufferStorageMultisampleEXT");
-    } else if (extensions.has("GL_IMG_multisampled_render_to_texture")) {
-        GET_PROC_SUFFIX(FramebufferTexture2DMultisample, IMG);
-        functions->fRenderbufferStorageMultisampleES2EXT =
-                (GrGLRenderbufferStorageMultisampleFn*)get(ctx,
-                                                           "glRenderbufferStorageMultisampleIMG");
-    } else if (extensions.has("GL_APPLE_framebuffer_multisample")) {
-        functions->fRenderbufferStorageMultisampleES2APPLE =
-                (GrGLRenderbufferStorageMultisampleFn*)get(ctx,
-                                                           "glRenderbufferStorageMultisampleAPPLE");
-        GET_PROC_SUFFIX(ResolveMultisampleFramebuffer, APPLE);
-    }
-
-    GET_PROC(GenFramebuffers);
-    GET_PROC(GenRenderbuffers);
-    GET_PROC(GetFramebufferAttachmentParameteriv);
-    GET_PROC(GetRenderbufferParameteriv);
-    GET_PROC(RenderbufferStorage);
-
-    // There are several APIs for buffer mapping:
-    // ES2 + GL_OES_mapbuffer: MapBufferOES and UnmapBufferOES
-    // ES2 + GL_EXT_map_buffer_range: Adds MapBufferRangeEXT and FlushMappedBufferRangeEXT
-    // ES3: MapBufferRange, FlushMappedBufferRange, and UnmapBuffer are core (so no suffix).
-    //
-    // MapBuffer is not part of ES3, but implementations may still report the OES versions of
-    // MapBuffer and UnmapBuffer, per the older GL_OES_mapbuffer extension. Some implementations
-    // let us mix the newer MapBufferRange with the older UnmapBufferOES, but we've hit others that
-    // don't permit it. Note that in GrGLBuffer, we choose which API to use based on version and
-    // extensions. This code is written so that we never mix OES and non-OES functions.
-    GET_PROC_SUFFIX(MapBuffer, OES);
-    if (version >= GR_GL_VER(3, 0)) {
-        GET_PROC(UnmapBuffer);
-    } else {
-        GET_PROC_SUFFIX(UnmapBuffer, OES);
-    }
-
-    if (version >= GR_GL_VER(3,0)) {
-        GET_PROC(MapBufferRange);
-        GET_PROC(FlushMappedBufferRange);
-    } else if (extensions.has("GL_EXT_map_buffer_range")) {
-        GET_PROC_SUFFIX(MapBufferRange, EXT);
-        GET_PROC_SUFFIX(FlushMappedBufferRange, EXT);
-    }
-
-    if (extensions.has("GL_EXT_debug_marker")) {
-        GET_PROC_SUFFIX(InsertEventMarker, EXT);
-        GET_PROC_SUFFIX(PushGroupMarker, EXT);
-        GET_PROC_SUFFIX(PopGroupMarker, EXT);
-    }
-
-    GET_PROC(InvalidateFramebuffer);
-    GET_PROC(InvalidateSubFramebuffer);
-    GET_PROC(InvalidateBufferData);
-    GET_PROC(InvalidateBufferSubData);
-    GET_PROC(InvalidateTexImage);
-    GET_PROC(InvalidateTexSubImage);
-
-    if (version >= GR_GL_VER(3,1)) {
-        GET_PROC(GetProgramResourceLocation);
-    }
-
-    if (extensions.has("GL_NV_path_rendering")) {
-        GET_PROC_SUFFIX(MatrixLoadf, EXT);
-        GET_PROC_SUFFIX(MatrixLoadIdentity, EXT);
-        GET_PROC_SUFFIX(PathCommands, NV);
-        GET_PROC_SUFFIX(PathParameteri, NV);
-        GET_PROC_SUFFIX(PathParameterf, NV);
-        GET_PROC_SUFFIX(GenPaths, NV);
-        GET_PROC_SUFFIX(DeletePaths, NV);
-        GET_PROC_SUFFIX(IsPath, NV);
-        GET_PROC_SUFFIX(PathStencilFunc, NV);
-        GET_PROC_SUFFIX(StencilFillPath, NV);
-        GET_PROC_SUFFIX(StencilStrokePath, NV);
-        GET_PROC_SUFFIX(StencilFillPathInstanced, NV);
-        GET_PROC_SUFFIX(StencilStrokePathInstanced, NV);
-        GET_PROC_SUFFIX(CoverFillPath, NV);
-        GET_PROC_SUFFIX(CoverStrokePath, NV);
-        GET_PROC_SUFFIX(CoverFillPathInstanced, NV);
-        GET_PROC_SUFFIX(CoverStrokePathInstanced, NV);
-        GET_PROC_SUFFIX(StencilThenCoverFillPath, NV);
-        GET_PROC_SUFFIX(StencilThenCoverStrokePath, NV);
-        GET_PROC_SUFFIX(StencilThenCoverFillPathInstanced, NV);
-        GET_PROC_SUFFIX(StencilThenCoverStrokePathInstanced, NV);
-        GET_PROC_SUFFIX(ProgramPathFragmentInputGen, NV);
-    }
-
-    if (extensions.has("GL_CHROMIUM_path_rendering")) {
-        GET_PROC_SUFFIX(MatrixLoadf, CHROMIUM);
-        GET_PROC_SUFFIX(MatrixLoadIdentity, CHROMIUM);
-        GET_PROC_SUFFIX(PathCommands, CHROMIUM);
-        GET_PROC_SUFFIX(PathParameteri, CHROMIUM);
-        GET_PROC_SUFFIX(PathParameterf, CHROMIUM);
-        GET_PROC_SUFFIX(GenPaths, CHROMIUM);
-        GET_PROC_SUFFIX(DeletePaths, CHROMIUM);
-        GET_PROC_SUFFIX(IsPath, CHROMIUM);
-        GET_PROC_SUFFIX(PathStencilFunc, CHROMIUM);
-        GET_PROC_SUFFIX(StencilFillPath, CHROMIUM);
-        GET_PROC_SUFFIX(StencilStrokePath, CHROMIUM);
-        GET_PROC_SUFFIX(StencilFillPathInstanced, CHROMIUM);
-        GET_PROC_SUFFIX(StencilStrokePathInstanced, CHROMIUM);
-        GET_PROC_SUFFIX(CoverFillPath, CHROMIUM);
-        GET_PROC_SUFFIX(CoverStrokePath, CHROMIUM);
-        GET_PROC_SUFFIX(CoverFillPathInstanced, CHROMIUM);
-        GET_PROC_SUFFIX(CoverStrokePathInstanced, CHROMIUM);
-        GET_PROC_SUFFIX(StencilThenCoverFillPath, CHROMIUM);
-        GET_PROC_SUFFIX(StencilThenCoverStrokePath, CHROMIUM);
-        GET_PROC_SUFFIX(StencilThenCoverFillPathInstanced, CHROMIUM);
-        GET_PROC_SUFFIX(StencilThenCoverStrokePathInstanced, CHROMIUM);
-        GET_PROC_SUFFIX(ProgramPathFragmentInputGen, CHROMIUM);
-        // GL_CHROMIUM_path_rendering additions:
-        GET_PROC_SUFFIX(BindFragmentInputLocation, CHROMIUM);
-    }
-
-    if (extensions.has("GL_NV_framebuffer_mixed_samples")) {
-        GET_PROC_SUFFIX(CoverageModulation, NV);
-    }
-    if (extensions.has("GL_CHROMIUM_framebuffer_mixed_samples")) {
-        GET_PROC_SUFFIX(CoverageModulation, CHROMIUM);
-    }
-
-    if (extensions.has("GL_KHR_debug")) {
-        GET_PROC_SUFFIX(DebugMessageControl, KHR);
-        GET_PROC_SUFFIX(DebugMessageInsert, KHR);
-        GET_PROC_SUFFIX(DebugMessageCallback, KHR);
-        GET_PROC_SUFFIX(GetDebugMessageLog, KHR);
-        GET_PROC_SUFFIX(PushDebugGroup, KHR);
-        GET_PROC_SUFFIX(PopDebugGroup, KHR);
-        GET_PROC_SUFFIX(ObjectLabel, KHR);
-        // In general we have a policy against removing extension strings when the driver does
-        // not provide function pointers for an advertised extension. However, because there is a
-        // known device that advertises GL_KHR_debug but fails to provide the functions and this is
-        // a debugging- only extension we've made an exception. This also can happen when using
-        // APITRACE.
-        if (!interface->fFunctions.fDebugMessageControl) {
-            extensions.remove("GL_KHR_debug");
-        }
-    }
-
-    if (extensions.has("GL_CHROMIUM_bind_uniform_location")) {
-        GET_PROC_SUFFIX(BindUniformLocation, CHROMIUM);
-    }
-
-    if (extensions.has("GL_EXT_window_rectangles")) {
-        GET_PROC_SUFFIX(WindowRectangles, EXT);
-    }
-
-    if (extensions.has("EGL_KHR_image") || extensions.has("EGL_KHR_image_base")) {
-        GET_EGL_PROC_SUFFIX(CreateImage, KHR);
-        GET_EGL_PROC_SUFFIX(DestroyImage, KHR);
-    }
-
-    if (version >= GR_GL_VER(3, 0)) {
-        GET_PROC(FenceSync);
-        GET_PROC(IsSync);
-        GET_PROC(ClientWaitSync);
-        GET_PROC(WaitSync);
-        GET_PROC(DeleteSync);
-    } else if (extensions.has("GL_APPLE_sync")) {
-        GET_PROC_SUFFIX(FenceSync, APPLE);
-        GET_PROC_SUFFIX(IsSync, APPLE);
-        GET_PROC_SUFFIX(ClientWaitSync, APPLE);
-        GET_PROC_SUFFIX(WaitSync, APPLE);
-        GET_PROC_SUFFIX(DeleteSync, APPLE);
-    }
-
-    if (version >= GR_GL_VER(3,0)) {
-        GET_PROC(GetInternalformativ);
-    }
-
-    if (version >= GR_GL_VER(3, 0)) {
-        GET_PROC(GetProgramBinary);
-        GET_PROC(ProgramBinary);
-        GET_PROC(ProgramParameteri);
-    }
-
-    if (version >= GR_GL_VER(3,0)) {
-        GET_PROC(BindSampler);
-        GET_PROC(DeleteSamplers);
-        GET_PROC(GenSamplers);
-        GET_PROC(SamplerParameteri);
-        GET_PROC(SamplerParameteriv);
-    }
-
-    interface->fStandard = kGLES_GrGLStandard;
-    interface->fExtensions.swap(&extensions);
-
-    return std::move(interface);
-}
-
 SK_API const GrGLInterface* GrGLAssembleInterface(void *ctx, GrGLGetProc get) {
     return GrGLMakeAssembledInterface(ctx, get).release();
 }
diff --git a/src/gpu/gl/GrGLAssembleWebGLInterfaceAutogen.cpp b/src/gpu/gl/GrGLAssembleWebGLInterfaceAutogen.cpp
new file mode 100644
index 0000000..cc77b41
--- /dev/null
+++ b/src/gpu/gl/GrGLAssembleWebGLInterfaceAutogen.cpp
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * THIS FILE IS AUTOGENERATED
+ * Make edits to tools/gpu/gl/interface/templates.go or they will
+ * be overwritten.
+ */
+
+#include "gl/GrGLAssembleInterface.h"
+#include "gl/GrGLAssembleHelpers.h"
+#include "gl/GrGLUtil.h"
+
+#define GET_PROC(F) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F)
+#define GET_PROC_SUFFIX(F, S) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F #S)
+#define GET_PROC_LOCAL(F) GrGL##F##Fn* F = (GrGL##F##Fn*)get(ctx, "gl" #F)
+
+#define GET_EGL_PROC_SUFFIX(F, S) functions->fEGL##F = (GrEGL##F##Fn*)get(ctx, "egl" #F #S)
+
+#if SK_DISABLE_WEBGL_INTERFACE
+sk_sp<const GrGLInterface> GrGLMakeAssembledWebGLInterface(void *ctx, GrGLGetProc get) {
+    return nullptr;
+}
+#else
+sk_sp<const GrGLInterface> GrGLMakeAssembledWebGLInterface(void *ctx, GrGLGetProc get) {
+    GET_PROC_LOCAL(GetString);
+    if (nullptr == GetString) {
+        return nullptr;
+    }
+
+    const char* verStr = reinterpret_cast<const char*>(GetString(GR_GL_VERSION));
+    GrGLVersion glVer = GrGLGetVersionFromString(verStr);
+
+    if (glVer < GR_GL_VER(1,0)) {
+        return nullptr;
+    }
+
+    GET_PROC_LOCAL(GetIntegerv);
+    GET_PROC_LOCAL(GetStringi);
+    GrEGLQueryStringFn* queryString;
+    GrEGLDisplay display;
+    GrGetEGLQueryAndDisplay(&queryString, &display, ctx, get);
+    GrGLExtensions extensions;
+    if (!extensions.init(kWebGL_GrGLStandard, GetString, GetStringi, GetIntegerv, queryString,
+                         display)) {
+        return nullptr;
+    }
+
+    sk_sp<GrGLInterface> interface(new GrGLInterface);
+    GrGLInterface::Functions* functions = &interface->fFunctions;
+
+    // Autogenerated content follows
+    GET_PROC(ActiveTexture);
+    GET_PROC(AttachShader);
+    GET_PROC(BindAttribLocation);
+    GET_PROC(BindBuffer);
+    GET_PROC(BindTexture);
+    GET_PROC(BlendColor);
+    GET_PROC(BlendEquation);
+    GET_PROC(BlendFunc);
+    GET_PROC(BufferData);
+    GET_PROC(BufferSubData);
+    GET_PROC(Clear);
+    GET_PROC(ClearColor);
+    GET_PROC(ClearStencil);
+    GET_PROC(ColorMask);
+    GET_PROC(CompileShader);
+    GET_PROC(CompressedTexImage2D);
+    GET_PROC(CompressedTexSubImage2D);
+    GET_PROC(CopyTexSubImage2D);
+    GET_PROC(CreateProgram);
+    GET_PROC(CreateShader);
+    GET_PROC(CullFace);
+    GET_PROC(DeleteBuffers);
+    GET_PROC(DeleteProgram);
+    GET_PROC(DeleteShader);
+    GET_PROC(DeleteTextures);
+    GET_PROC(DepthMask);
+    GET_PROC(Disable);
+    GET_PROC(DisableVertexAttribArray);
+    GET_PROC(DrawArrays);
+    GET_PROC(DrawElements);
+    GET_PROC(Enable);
+    GET_PROC(EnableVertexAttribArray);
+    GET_PROC(Finish);
+    GET_PROC(Flush);
+    GET_PROC(FrontFace);
+    GET_PROC(GenBuffers);
+    GET_PROC(GenTextures);
+    GET_PROC(GetBufferParameteriv);
+    GET_PROC(GetError);
+    GET_PROC(GetIntegerv);
+    GET_PROC(GetProgramInfoLog);
+    GET_PROC(GetProgramiv);
+    GET_PROC(GetShaderInfoLog);
+    GET_PROC(GetShaderiv);
+    GET_PROC(GetString);
+    GET_PROC(GetUniformLocation);
+    GET_PROC(IsTexture);
+    GET_PROC(LineWidth);
+    GET_PROC(LinkProgram);
+    GET_PROC(PixelStorei);
+    GET_PROC(ReadPixels);
+    GET_PROC(Scissor);
+    GET_PROC(ShaderSource);
+    GET_PROC(StencilFunc);
+    GET_PROC(StencilFuncSeparate);
+    GET_PROC(StencilMask);
+    GET_PROC(StencilMaskSeparate);
+    GET_PROC(StencilOp);
+    GET_PROC(StencilOpSeparate);
+    GET_PROC(TexImage2D);
+    GET_PROC(TexParameterf);
+    GET_PROC(TexParameterfv);
+    GET_PROC(TexParameteri);
+    GET_PROC(TexParameteriv);
+    GET_PROC(TexSubImage2D);
+    GET_PROC(Uniform1f);
+    GET_PROC(Uniform1fv);
+    GET_PROC(Uniform1i);
+    GET_PROC(Uniform1iv);
+    GET_PROC(Uniform2f);
+    GET_PROC(Uniform2fv);
+    GET_PROC(Uniform2i);
+    GET_PROC(Uniform2iv);
+    GET_PROC(Uniform3f);
+    GET_PROC(Uniform3fv);
+    GET_PROC(Uniform3i);
+    GET_PROC(Uniform3iv);
+    GET_PROC(Uniform4f);
+    GET_PROC(Uniform4fv);
+    GET_PROC(Uniform4i);
+    GET_PROC(Uniform4iv);
+    GET_PROC(UniformMatrix2fv);
+    GET_PROC(UniformMatrix3fv);
+    GET_PROC(UniformMatrix4fv);
+    GET_PROC(UseProgram);
+    GET_PROC(VertexAttrib1f);
+    GET_PROC(VertexAttrib2fv);
+    GET_PROC(VertexAttrib3fv);
+    GET_PROC(VertexAttrib4fv);
+    GET_PROC(VertexAttribPointer);
+    GET_PROC(Viewport);
+
+    if (glVer >= GR_GL_VER(2,0)) {
+        GET_PROC(GetStringi);
+    }
+
+    if (glVer >= GR_GL_VER(2,0)) {
+        GET_PROC(BindVertexArray);
+        GET_PROC(DeleteVertexArrays);
+        GET_PROC(GenVertexArrays);
+    } else if (extensions.has("GL_OES_vertex_array_object")) {
+        GET_PROC_SUFFIX(BindVertexArray, OES);
+        GET_PROC_SUFFIX(DeleteVertexArrays, OES);
+        GET_PROC_SUFFIX(GenVertexArrays, OES);
+    } else if (extensions.has("OES_vertex_array_object")) {
+        GET_PROC_SUFFIX(BindVertexArray, OES);
+        GET_PROC_SUFFIX(DeleteVertexArrays, OES);
+        GET_PROC_SUFFIX(GenVertexArrays, OES);
+    }
+
+    if (glVer >= GR_GL_VER(2,0)) {
+        GET_PROC(DrawArraysInstanced);
+        GET_PROC(DrawElementsInstanced);
+    }
+
+    if (glVer >= GR_GL_VER(2,0)) {
+        GET_PROC(DrawBuffers);
+        GET_PROC(ReadBuffer);
+    }
+
+    if (glVer >= GR_GL_VER(2,0)) {
+        GET_PROC(DrawRangeElements);
+    }
+
+    if (glVer >= GR_GL_VER(2,0)) {
+        GET_PROC(TexStorage2D);
+    }
+
+    if (glVer >= GR_GL_VER(2,0)) {
+        GET_PROC(VertexAttribIPointer);
+    }
+
+    GET_PROC(BindFramebuffer);
+    GET_PROC(BindRenderbuffer);
+    GET_PROC(CheckFramebufferStatus);
+    GET_PROC(DeleteFramebuffers);
+    GET_PROC(DeleteRenderbuffers);
+    GET_PROC(FramebufferRenderbuffer);
+    GET_PROC(FramebufferTexture2D);
+    GET_PROC(GenFramebuffers);
+    GET_PROC(GenRenderbuffers);
+    GET_PROC(GenerateMipmap);
+    GET_PROC(GetFramebufferAttachmentParameteriv);
+    GET_PROC(GetRenderbufferParameteriv);
+    GET_PROC(RenderbufferStorage);
+
+    if (glVer >= GR_GL_VER(2,0)) {
+        GET_PROC(RenderbufferStorageMultisample);
+    }
+
+    if (glVer >= GR_GL_VER(2,0)) {
+        GET_PROC(ClientWaitSync);
+        GET_PROC(DeleteSync);
+        GET_PROC(FenceSync);
+        GET_PROC(IsSync);
+        GET_PROC(WaitSync);
+    }
+
+    if (glVer >= GR_GL_VER(2,0)) {
+        GET_PROC(BindSampler);
+        GET_PROC(DeleteSamplers);
+        GET_PROC(GenSamplers);
+        GET_PROC(SamplerParameteri);
+        GET_PROC(SamplerParameteriv);
+    }
+
+    if (glVer >= GR_GL_VER(2,0)) {
+        GET_PROC(InvalidateFramebuffer);
+        GET_PROC(InvalidateSubFramebuffer);
+    }
+
+    GET_PROC(GetShaderPrecisionFormat);
+
+
+    // End autogenerated content
+
+    interface->fStandard = kWebGL_GrGLStandard;
+    interface->fExtensions.swap(&extensions);
+
+    return std::move(interface);
+}
+#endif
diff --git a/src/gpu/gl/GrGLBuffer.cpp b/src/gpu/gl/GrGLBuffer.cpp
index dd3e1c5..4335409 100644
--- a/src/gpu/gl/GrGLBuffer.cpp
+++ b/src/gpu/gl/GrGLBuffer.cpp
@@ -176,8 +176,8 @@
         case GrGLCaps::kMapBuffer_MapBufferType: {
             GrGLenum target = this->glGpu()->bindBuffer(fIntendedType, this);
             // Let driver know it can discard the old data
-            if (this->glCaps().useBufferDataNullHint() || fGLSizeInBytes != this->sizeInBytes()) {
-                GL_CALL(BufferData(target, this->sizeInBytes(), nullptr, fUsage));
+            if (this->glCaps().useBufferDataNullHint() || fGLSizeInBytes != this->size()) {
+                GL_CALL(BufferData(target, this->size(), nullptr, fUsage));
             }
             GL_CALL_RET(fMapPtr, MapBuffer(target, readOnly ? GR_GL_READ_ONLY : GR_GL_WRITE_ONLY));
             break;
@@ -185,30 +185,30 @@
         case GrGLCaps::kMapBufferRange_MapBufferType: {
             GrGLenum target = this->glGpu()->bindBuffer(fIntendedType, this);
             // Make sure the GL buffer size agrees with fDesc before mapping.
-            if (fGLSizeInBytes != this->sizeInBytes()) {
-                GL_CALL(BufferData(target, this->sizeInBytes(), nullptr, fUsage));
+            if (fGLSizeInBytes != this->size()) {
+                GL_CALL(BufferData(target, this->size(), nullptr, fUsage));
             }
             GrGLbitfield writeAccess = GR_GL_MAP_WRITE_BIT;
             if (GrGpuBufferType::kXferCpuToGpu != fIntendedType) {
                 // TODO: Make this a function parameter.
                 writeAccess |= GR_GL_MAP_INVALIDATE_BUFFER_BIT;
             }
-            GL_CALL_RET(fMapPtr, MapBufferRange(target, 0, this->sizeInBytes(),
-                                                readOnly ?  GR_GL_MAP_READ_BIT : writeAccess));
+            GL_CALL_RET(fMapPtr, MapBufferRange(target, 0, this->size(),
+                                                readOnly ? GR_GL_MAP_READ_BIT : writeAccess));
             break;
         }
         case GrGLCaps::kChromium_MapBufferType: {
             GrGLenum target = this->glGpu()->bindBuffer(fIntendedType, this);
             // Make sure the GL buffer size agrees with fDesc before mapping.
-            if (fGLSizeInBytes != this->sizeInBytes()) {
-                GL_CALL(BufferData(target, this->sizeInBytes(), nullptr, fUsage));
+            if (fGLSizeInBytes != this->size()) {
+                GL_CALL(BufferData(target, this->size(), nullptr, fUsage));
             }
-            GL_CALL_RET(fMapPtr, MapBufferSubData(target, 0, this->sizeInBytes(),
-                                                  readOnly ?  GR_GL_READ_ONLY : GR_GL_WRITE_ONLY));
+            GL_CALL_RET(fMapPtr, MapBufferSubData(target, 0, this->size(),
+                                                  readOnly ? GR_GL_READ_ONLY : GR_GL_WRITE_ONLY));
             break;
         }
     }
-    fGLSizeInBytes = this->sizeInBytes();
+    fGLSizeInBytes = this->size();
     VALIDATE();
 }
 
@@ -251,15 +251,15 @@
 
     SkASSERT(!this->isMapped());
     VALIDATE();
-    if (srcSizeInBytes > this->sizeInBytes()) {
+    if (srcSizeInBytes > this->size()) {
         return false;
     }
-    SkASSERT(srcSizeInBytes <= this->sizeInBytes());
+    SkASSERT(srcSizeInBytes <= this->size());
     // bindbuffer handles dirty context
     GrGLenum target = this->glGpu()->bindBuffer(fIntendedType, this);
 
     if (this->glCaps().useBufferDataNullHint()) {
-        if (this->sizeInBytes() == srcSizeInBytes) {
+        if (this->size() == srcSizeInBytes) {
             GL_CALL(BufferData(target, (GrGLsizeiptr) srcSizeInBytes, src, fUsage));
         } else {
             // Before we call glBufferSubData we give the driver a hint using
@@ -269,10 +269,10 @@
             // assign a different allocation for the new contents to avoid
             // flushing the gpu past draws consuming the old contents.
             // TODO I think we actually want to try calling bufferData here
-            GL_CALL(BufferData(target, this->sizeInBytes(), nullptr, fUsage));
+            GL_CALL(BufferData(target, this->size(), nullptr, fUsage));
             GL_CALL(BufferSubData(target, 0, (GrGLsizeiptr) srcSizeInBytes, src));
         }
-        fGLSizeInBytes = this->sizeInBytes();
+        fGLSizeInBytes = this->size();
     } else {
         // Note that we're cheating on the size here. Currently no methods
         // allow a partial update that preserves contents of non-updated
@@ -296,7 +296,7 @@
 
 void GrGLBuffer::validate() const {
     SkASSERT(0 != fBufferID || 0 == fGLSizeInBytes);
-    SkASSERT(nullptr == fMapPtr || fGLSizeInBytes <= this->sizeInBytes());
+    SkASSERT(nullptr == fMapPtr || fGLSizeInBytes <= this->size());
 }
 
 #endif
diff --git a/src/gpu/gl/GrGLBuffer.h b/src/gpu/gl/GrGLBuffer.h
index 18480ff..76d902c 100644
--- a/src/gpu/gl/GrGLBuffer.h
+++ b/src/gpu/gl/GrGLBuffer.h
@@ -8,13 +8,13 @@
 #ifndef GrGLBuffer_DEFINED
 #define GrGLBuffer_DEFINED
 
-#include "GrBuffer.h"
+#include "GrGpuBuffer.h"
 #include "gl/GrGLTypes.h"
 
 class GrGLGpu;
 class GrGLCaps;
 
-class GrGLBuffer : public GrBuffer {
+class GrGLBuffer : public GrGpuBuffer {
 public:
     static sk_sp<GrGLBuffer> Make(GrGLGpu*, size_t size, GrGpuBufferType intendedType,
                                   GrAccessPattern, const void* data = nullptr);
@@ -28,7 +28,7 @@
 
     /**
      * Returns the actual size of the underlying GL buffer object. In certain cases we may make this
-     * smaller than the size reported by GrBuffer.
+     * smaller than the size reported by GrGpuBuffer.
      */
     size_t glSizeInBytes() const { return fGLSizeInBytes; }
 
@@ -62,7 +62,7 @@
     size_t          fGLSizeInBytes;
     bool            fHasAttachedToTexture;
 
-    typedef GrBuffer INHERITED;
+    typedef GrGpuBuffer INHERITED;
 };
 
 #endif
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index a3da939..7e6f942 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -19,12 +19,6 @@
 #include "SkTSearch.h"
 #include "SkTSort.h"
 
-#if IS_WEBGL
-static constexpr bool kIsWebGL = true;
-#else
-static constexpr bool kIsWebGL = false;
-#endif
-
 GrGLCaps::GrGLCaps(const GrContextOptions& contextOptions,
                    const GrGLContextInfo& ctxInfo,
                    const GrGLInterface* glInterface) : INHERITED(contextOptions) {
@@ -82,13 +76,11 @@
                     const GrGLContextInfo& ctxInfo,
                     const GrGLInterface* gli) {
     GrGLStandard standard = ctxInfo.standard();
+    // standard can be unused (optimzed away) if SK_ASSUME_GL_ES is set
+    sk_ignore_unused_variable(standard);
     GrGLVersion version = ctxInfo.version();
 
-    if (kGLES_GrGLStandard == standard) {
-        GR_GL_GetIntegerv(gli, GR_GL_MAX_FRAGMENT_UNIFORM_VECTORS,
-                          &fMaxFragmentUniformVectors);
-    } else {
-        SkASSERT(kGL_GrGLStandard == standard);
+    if (GR_IS_GR_GL(standard)) {
         GrGLint max;
         GR_GL_GetIntegerv(gli, GR_GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &max);
         fMaxFragmentUniformVectors = max / 4;
@@ -97,23 +89,31 @@
             GR_GL_GetIntegerv(gli, GR_GL_CONTEXT_PROFILE_MASK, &profileMask);
             fIsCoreProfile = SkToBool(profileMask & GR_GL_CONTEXT_CORE_PROFILE_BIT);
         }
+    } else if (GR_IS_GR_GL_ES(standard) || GR_IS_GR_WEBGL(standard)) {
+        GR_GL_GetIntegerv(gli, GR_GL_MAX_FRAGMENT_UNIFORM_VECTORS,
+                          &fMaxFragmentUniformVectors);
     }
+
     if (fDriverBugWorkarounds.max_fragment_uniform_vectors_32) {
         fMaxFragmentUniformVectors = SkMin32(fMaxFragmentUniformVectors, 32);
     }
     GR_GL_GetIntegerv(gli, GR_GL_MAX_VERTEX_ATTRIBS, &fMaxVertexAttributes);
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         fUnpackRowLengthSupport = true;
         fPackRowLengthSupport = true;
         fPackFlipYSupport = false;
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         fUnpackRowLengthSupport = version >= GR_GL_VER(3,0) ||
                                   ctxInfo.hasExtension("GL_EXT_unpack_subimage");
         fPackRowLengthSupport = version >= GR_GL_VER(3,0) ||
                                 ctxInfo.hasExtension("GL_NV_pack_subimage");
         fPackFlipYSupport =
             ctxInfo.hasExtension("GL_ANGLE_pack_reverse_row_order");
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        // WebGL 2.0 has these
+        fUnpackRowLengthSupport = version >= GR_GL_VER(2,0);
+        fPackRowLengthSupport = version >= GR_GL_VER(2,0);
     }
 
     if (fDriverBugWorkarounds.pack_parameters_workaround_with_pack_buffer) {
@@ -125,29 +125,29 @@
         fPackRowLengthSupport = false;
     }
 
-    fTextureUsageSupport = (kGLES_GrGLStandard == standard) &&
-                            ctxInfo.hasExtension("GL_ANGLE_texture_usage");
+    fTextureUsageSupport = GR_IS_GR_GL_ES(standard) &&
+                           ctxInfo.hasExtension("GL_ANGLE_texture_usage");
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         fTextureBarrierSupport = version >= GR_GL_VER(4,5) ||
                                  ctxInfo.hasExtension("GL_ARB_texture_barrier") ||
                                  ctxInfo.hasExtension("GL_NV_texture_barrier");
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         fTextureBarrierSupport = ctxInfo.hasExtension("GL_NV_texture_barrier");
-    }
+    } // no WebGL support
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         fSampleLocationsSupport = version >= GR_GL_VER(3,2) ||
                                   ctxInfo.hasExtension("GL_ARB_texture_multisample");
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         fSampleLocationsSupport = version >= GR_GL_VER(3,1);
-    }
+    }  // no WebGL support
 
-    fImagingSupport = kGL_GrGLStandard == standard &&
+    fImagingSupport = GR_IS_GR_GL(standard) &&
                       ctxInfo.hasExtension("GL_ARB_imaging");
 
-    if (((kGL_GrGLStandard == standard && version >= GR_GL_VER(4,3)) ||
-         (kGLES_GrGLStandard == standard && version >= GR_GL_VER(3,0)) ||
+    if (((GR_IS_GR_GL(standard) && version >= GR_GL_VER(4,3)) ||
+         (GR_IS_GR_GL_ES(standard) && version >= GR_GL_VER(3,0)) ||
          ctxInfo.hasExtension("GL_ARB_invalidate_subdata"))) {
         fDiscardRenderTargetSupport = true;
         fInvalidateFBType = kInvalidate_InvalidateFBType;
@@ -158,7 +158,7 @@
 
     // For future reference on Desktop GL, GL_PRIMITIVE_RESTART_FIXED_INDEX appears in 4.3, and
     // GL_PRIMITIVE_RESTART (where the client must call glPrimitiveRestartIndex) appears in 3.1.
-    if (kGLES_GrGLStandard == standard) {
+    if (GR_IS_GR_GL_ES(standard)) {
         // Primitive restart can cause a 3x slowdown on Adreno. Enable conservatively.
         // FIXME: Primitive restart would likely be a win on iOS if we had an enum value for it.
         if (kARM_GrGLVendor == ctxInfo.vendor()) {
@@ -172,103 +172,115 @@
         fPreferFullscreenClears = true;
     }
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         fVertexArrayObjectSupport = version >= GR_GL_VER(3, 0) ||
                                     ctxInfo.hasExtension("GL_ARB_vertex_array_object") ||
                                     ctxInfo.hasExtension("GL_APPLE_vertex_array_object");
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         fVertexArrayObjectSupport = version >= GR_GL_VER(3, 0) ||
                                     ctxInfo.hasExtension("GL_OES_vertex_array_object");
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        fVertexArrayObjectSupport = version >= GR_GL_VER(2, 0) ||
+                                    ctxInfo.hasExtension("GL_OES_vertex_array_object") ||
+                                    ctxInfo.hasExtension("OES_vertex_array_object");
     }
 
-    if (kGL_GrGLStandard == standard && version >= GR_GL_VER(4,3)) {
+    if (GR_IS_GR_GL(standard) && version >= GR_GL_VER(4,3)) {
         fDebugSupport = true;
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         fDebugSupport = ctxInfo.hasExtension("GL_KHR_debug");
-    }
+    } // no WebGL support
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         fES2CompatibilitySupport = ctxInfo.hasExtension("GL_ARB_ES2_compatibility");
     }
-    else {
+    else if (GR_IS_GR_GL_ES(standard)) {
+        fES2CompatibilitySupport = true;
+    } else if (GR_IS_GR_WEBGL(standard)) {
         fES2CompatibilitySupport = true;
     }
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         fMultisampleDisableSupport = true;
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         fMultisampleDisableSupport = ctxInfo.hasExtension("GL_EXT_multisample_compatibility");
-    }
+    } // no WebGL support
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         // 3.1 has draw_instanced but not instanced_arrays, for the time being we only care about
         // instanced arrays, but we could make this more granular if we wanted
         fInstanceAttribSupport =
                 version >= GR_GL_VER(3, 2) ||
                 (ctxInfo.hasExtension("GL_ARB_draw_instanced") &&
                  ctxInfo.hasExtension("GL_ARB_instanced_arrays"));
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         fInstanceAttribSupport =
                 version >= GR_GL_VER(3, 0) ||
                 (ctxInfo.hasExtension("GL_EXT_draw_instanced") &&
                  ctxInfo.hasExtension("GL_EXT_instanced_arrays"));
+    }  else if (GR_IS_GR_WEBGL(standard)) {
+        // WebGL 2.0 has DrawArraysInstanced and drawElementsInstanced
+        fInstanceAttribSupport = version >= GR_GL_VER(2, 0);
     }
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         if (version >= GR_GL_VER(3, 0)) {
             fBindFragDataLocationSupport = true;
         }
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         if (version >= GR_GL_VER(3, 0) && ctxInfo.hasExtension("GL_EXT_blend_func_extended")) {
             fBindFragDataLocationSupport = true;
         }
-    }
+    } // no WebGL support
 
     fBindUniformLocationSupport = ctxInfo.hasExtension("GL_CHROMIUM_bind_uniform_location");
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         if (version >= GR_GL_VER(3, 1) || ctxInfo.hasExtension("GL_ARB_texture_rectangle") ||
             ctxInfo.hasExtension("GL_ANGLE_texture_rectangle")) {
             fRectangleTextureSupport = true;
         }
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         // Command buffer exposes this in GL ES context for Chromium reasons,
         // but it should not be used. Also, at the time of writing command buffer
         // lacks TexImage2D support and ANGLE lacks GL ES 3.0 support.
-    }
+    } // no WebGL support
 
     // GrCaps defaults fClampToBorderSupport to true, so disable when unsupported
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         // Clamp to border added in 1.3
         if (version < GR_GL_VER(1, 3) && !ctxInfo.hasExtension("GL_ARB_texture_border_clamp")) {
             fClampToBorderSupport = false;
         }
-    } else if (kGLES_GrGLStandard == standard) {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         // GLES didn't have clamp to border until 3.2, but provides several alternative extensions
         if (version < GR_GL_VER(3, 2) && !ctxInfo.hasExtension("GL_EXT_texture_border_clamp") &&
             !ctxInfo.hasExtension("GL_NV_texture_border_clamp") &&
             !ctxInfo.hasExtension("GL_OES_texture_border_clamp")) {
             fClampToBorderSupport = false;
         }
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        // WebGL appears to only have REPEAT, CLAMP_TO_EDGE and MIRRORED_REPEAT
+        fClampToBorderSupport = false;
     }
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         if (version >= GR_GL_VER(3,3) || ctxInfo.hasExtension("GL_ARB_texture_swizzle")) {
             fTextureSwizzleSupport = true;
         }
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         if (version >= GR_GL_VER(3,0)) {
             fTextureSwizzleSupport = true;
         }
-    }
+    } // no WebGL support
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         fMipMapLevelAndLodControlSupport = true;
-    } else if (kGLES_GrGLStandard == standard) {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         if (version >= GR_GL_VER(3,0)) {
             fMipMapLevelAndLodControlSupport = true;
         }
-    }
+    } // no WebGL support
 
 #ifdef SK_BUILD_FOR_WIN
     // We're assuming that on Windows Chromium we're using ANGLE.
@@ -293,16 +305,20 @@
 
     // Chrome's command buffer will zero out a buffer if null is passed to glBufferData to
     // avoid letting an application see uninitialized memory.
-    fUseBufferDataNullHint = !kIsWebGL && kChromium_GrGLDriver != ctxInfo.driver();
-
-    if (kGL_GrGLStandard == standard) {
-        if (version >= GR_GL_VER(4,4) || ctxInfo.hasExtension("GL_ARB_clear_texture")) {
-            fClearTextureSupport = true;
-        }
-    } else if (ctxInfo.hasExtension("GL_EXT_clear_texture")) {
-        fClearTextureSupport = true;
+    if (GR_IS_GR_GL(standard) || GR_IS_GR_GL_ES(standard)) {
+        fUseBufferDataNullHint = kChromium_GrGLDriver != ctxInfo.driver();
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        // WebGL spec explicitly disallows null values.
+        fUseBufferDataNullHint = false;
     }
 
+    if (GR_IS_GR_GL(standard)) {
+        fClearTextureSupport = (version >= GR_GL_VER(4,4) ||
+                                ctxInfo.hasExtension("GL_ARB_clear_texture"));
+    } else if (GR_IS_GR_GL_ES(standard)) {
+        fClearTextureSupport = ctxInfo.hasExtension("GL_EXT_clear_texture");
+    }  // no WebGL support
+
 #if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26
     fSupportsAHardwareBufferImages = true;
 #endif
@@ -323,15 +339,15 @@
 #endif
 
     // Enable supported shader-related caps
-    if (kGL_GrGLStandard == standard) {
-        shaderCaps->fDualSourceBlendingSupport = (ctxInfo.version() >= GR_GL_VER(3, 3) ||
+    if (GR_IS_GR_GL(standard)) {
+        shaderCaps->fDualSourceBlendingSupport = (version >= GR_GL_VER(3, 3) ||
             ctxInfo.hasExtension("GL_ARB_blend_func_extended")) &&
             ctxInfo.glslGeneration() >= k130_GrGLSLGeneration;
 
         shaderCaps->fShaderDerivativeSupport = true;
 
         // we don't support GL_ARB_geometry_shader4, just GL 3.2+ GS
-        shaderCaps->fGeometryShaderSupport = ctxInfo.version() >= GR_GL_VER(3, 2) &&
+        shaderCaps->fGeometryShaderSupport = version >= GR_GL_VER(3, 2) &&
             ctxInfo.glslGeneration() >= k150_GrGLSLGeneration;
         if (shaderCaps->fGeometryShaderSupport) {
             if (ctxInfo.glslGeneration() >= k400_GrGLSLGeneration) {
@@ -342,12 +358,12 @@
             }
         }
 
-        shaderCaps->fIntegerSupport = ctxInfo.version() >= GR_GL_VER(3, 0) &&
+        shaderCaps->fIntegerSupport = version >= GR_GL_VER(3, 0) &&
             ctxInfo.glslGeneration() >= k130_GrGLSLGeneration;
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         shaderCaps->fDualSourceBlendingSupport = ctxInfo.hasExtension("GL_EXT_blend_func_extended");
 
-        shaderCaps->fShaderDerivativeSupport = ctxInfo.version() >= GR_GL_VER(3, 0) ||
+        shaderCaps->fShaderDerivativeSupport = version >= GR_GL_VER(3, 0) ||
             ctxInfo.hasExtension("GL_OES_standard_derivatives");
 
         // Mali and early Adreno both have support for geometry shaders, but they appear to be
@@ -357,7 +373,7 @@
             kAdreno3xx_GrGLRenderer != ctxInfo.renderer() &&
             kAdreno4xx_other_GrGLRenderer != ctxInfo.renderer()) {
 
-            if (ctxInfo.version() >= GR_GL_VER(3,2)) {
+            if (version >= GR_GL_VER(3,2)) {
                 shaderCaps->fGeometryShaderSupport = true;
             } else if (ctxInfo.hasExtension("GL_EXT_geometry_shader")) {
                 shaderCaps->fGeometryShaderSupport = true;
@@ -366,8 +382,11 @@
             shaderCaps->fGSInvocationsSupport = shaderCaps->fGeometryShaderSupport;
         }
 
-        shaderCaps->fIntegerSupport = ctxInfo.version() >= GR_GL_VER(3, 0) &&
+        shaderCaps->fIntegerSupport = version >= GR_GL_VER(3, 0) &&
             ctxInfo.glslGeneration() >= k330_GrGLSLGeneration; // We use this value for GLSL ES 3.0.
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        shaderCaps->fShaderDerivativeSupport = ctxInfo.hasExtension("GL_OES_standard_derivatives") ||
+                                               ctxInfo.hasExtension("OES_standard_derivatives");
     }
 
     // Protect ourselves against tracking huge amounts of texture state.
@@ -382,11 +401,13 @@
     // families rather than basing it on the vendor alone.
     // The Chrome command buffer blocks the use of client side buffers (but may emulate VBOs with
     // them). Client side buffers are not allowed in core profiles.
-    if (ctxInfo.driver() != kChromium_GrGLDriver && !fIsCoreProfile && !kIsWebGL &&
-        (ctxInfo.vendor() == kARM_GrGLVendor || ctxInfo.vendor() == kImagination_GrGLVendor ||
-         ctxInfo.vendor() == kQualcomm_GrGLVendor)) {
-        fPreferClientSideDynamicBuffers = true;
-    }
+    if (GR_IS_GR_GL(standard) || GR_IS_GR_GL_ES(standard)) {
+        if (ctxInfo.driver() != kChromium_GrGLDriver && !fIsCoreProfile &&
+            (ctxInfo.vendor() == kARM_GrGLVendor || ctxInfo.vendor() == kImagination_GrGLVendor ||
+             ctxInfo.vendor() == kQualcomm_GrGLVendor)) {
+            fPreferClientSideDynamicBuffers = true;
+        }
+    } // No client side arrays in WebGL https://www.khronos.org/registry/webgl/specs/1.0/#6.2
 
     if (!contextOptions.fAvoidStencilBuffers) {
         // To reduce surface area, if we avoid stencil buffers, we also disable MSAA.
@@ -395,8 +416,15 @@
     }
 
     // Setup blit framebuffer
-    if (kGL_GrGLStandard != ctxInfo.standard()) {
-        if (ctxInfo.version() >= GR_GL_VER(3, 0)) {
+    if (GR_IS_GR_GL(standard)) {
+        if (fUsesMixedSamples ||
+            version >= GR_GL_VER(3,0) ||
+            ctxInfo.hasExtension("GL_ARB_framebuffer_object") ||
+            ctxInfo.hasExtension("GL_EXT_framebuffer_blit")) {
+            fBlitFramebufferFlags = 0;
+        }
+    } else if (GR_IS_GR_GL_ES(standard)) {
+        if (version >= GR_GL_VER(3, 0)) {
             fBlitFramebufferFlags = kNoFormatConversionForMSAASrc_BlitFramebufferFlag |
                                     kNoMSAADst_BlitFramebufferFlag |
                                     kRectsMustMatchForMSAASrc_BlitFramebufferFlag;
@@ -410,18 +438,11 @@
                                     kNoFormatConversion_BlitFramebufferFlag |
                                     kRectsMustMatchForMSAASrc_BlitFramebufferFlag;
         }
-    } else {
-        if (fUsesMixedSamples ||
-            ctxInfo.version() >= GR_GL_VER(3,0) ||
-            ctxInfo.hasExtension("GL_ARB_framebuffer_object") ||
-            ctxInfo.hasExtension("GL_EXT_framebuffer_blit")) {
-            fBlitFramebufferFlags = 0;
-        }
-    }
+    } // No WebGL 1.0 support for BlitFramebuffer
 
     this->initBlendEqationSupport(ctxInfo);
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         fMapBufferFlags = kCanMap_MapFlag; // we require VBO support and the desktop VBO
                                             // extension includes glMapBuffer.
         if (version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_ARB_map_buffer_range")) {
@@ -430,7 +451,7 @@
         } else {
             fMapBufferType = kMapBuffer_MapBufferType;
         }
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         // Unextended GLES2 doesn't have any buffer mapping.
         fMapBufferFlags = kNone_MapBufferType;
         if (ctxInfo.hasExtension("GL_CHROMIUM_map_sub")) {
@@ -443,23 +464,30 @@
             fMapBufferFlags = kCanMap_MapFlag;
             fMapBufferType = kMapBuffer_MapBufferType;
         }
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        // explicitly removed https://www.khronos.org/registry/webgl/specs/2.0/#5.14
+        fMapBufferFlags = kNone_MapBufferType;
     }
 
-    if (kGL_GrGLStandard == standard) {
-        if (version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_ARB_pixel_buffer_object")) {
+    if (GR_IS_GR_GL(standard)) {
+        if (version >= GR_GL_VER(2, 1) || ctxInfo.hasExtension("GL_ARB_pixel_buffer_object") ||
+            ctxInfo.hasExtension("GL_EXT_pixel_buffer_object")) {
+            fTransferBufferSupport = true;
             fTransferBufferType = kPBO_TransferBufferType;
         }
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         if (version >= GR_GL_VER(3, 0) ||
             (ctxInfo.hasExtension("GL_NV_pixel_buffer_object") &&
              // GL_EXT_unpack_subimage needed to support subtexture rectangles
              ctxInfo.hasExtension("GL_EXT_unpack_subimage"))) {
+            fTransferBufferSupport = true;
             fTransferBufferType = kPBO_TransferBufferType;
 // TODO: get transfer buffers working in Chrome
 //        } else if (ctxInfo.hasExtension("GL_CHROMIUM_pixel_transfer_buffer_object")) {
+//            fTransferBufferSupport = true;
 //            fTransferBufferType = kChromium_TransferBufferType;
         }
-    }
+    } // no WebGL support
 
     // On many GPUs, map memory is very expensive, so we effectively disable it here by setting the
     // threshold to the maximum unless the client gives us a hint that map memory is cheap.
@@ -474,19 +502,25 @@
 #endif
     }
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         fNPOTTextureTileSupport = true;
         fMipMapSupport = true;
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         // Unextended ES2 supports NPOT textures with clamp_to_edge and non-mip filters only
         // ES3 has no limitations.
-        fNPOTTextureTileSupport = ctxInfo.version() >= GR_GL_VER(3,0) ||
+        fNPOTTextureTileSupport = version >= GR_GL_VER(3,0) ||
                                   ctxInfo.hasExtension("GL_OES_texture_npot");
         // ES2 supports MIP mapping for POT textures but our caps don't allow for limited MIP
         // support. The OES extension or ES 3.0 allow for MIPS on NPOT textures. So, apparently,
         // does the undocumented GL_IMG_texture_npot extension. This extension does not seem to
         // to alllow arbitrary wrap modes, however.
         fMipMapSupport = fNPOTTextureTileSupport || ctxInfo.hasExtension("GL_IMG_texture_npot");
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        // Texture access works in the WebGL 2.0 API as in the OpenGL ES 3.0 API
+        fNPOTTextureTileSupport = version >= GR_GL_VER(2,0);
+        // All mipmapping and all wrapping modes are supported for non-power-of-
+        // two images [in WebGL 2.0].
+        fMipMapSupport = fNPOTTextureTileSupport;
     }
 
     GR_GL_GetIntegerv(gli, GR_GL_MAX_TEXTURE_SIZE, &fMaxTextureSize);
@@ -525,24 +559,45 @@
     fPreferVRAMUseOverFlushes = !isANGLE;
 #endif
 
+    if (kARM_GrGLVendor == ctxInfo.vendor()) {
+        // ARM seems to do better with larger quantities of fine triangles, as opposed to using the
+        // sample mask. (At least in our current round rect op.)
+        fPreferTrianglesOverSampleMask = true;
+    }
+
     if (kChromium_GrGLDriver == ctxInfo.driver()) {
         fMustClearUploadedBufferData = true;
     }
 
-    if (kGL_GrGLStandard == standard) {
+    // In a WASM build on Firefox, we see warnings like
+    // WebGL warning: texSubImage2D: This operation requires zeroing texture data. This is slow.
+    // WebGL warning: texSubImage2D: Texture has not been initialized prior to a partial upload,
+    //                forcing the browser to clear it. This may be slow.
+    // Setting the initial clear seems to make those warnings go away and offers a substantial
+    // boost in performance in Firefox. Chrome sees a more modest increase.
+    if (GR_IS_GR_WEBGL(standard)) {
+        fShouldInitializeTextures = true;
+    }
+
+    if (GR_IS_GR_GL(standard)) {
         // ARB allows mixed size FBO attachments, EXT does not.
-        if (ctxInfo.version() >= GR_GL_VER(3, 0) ||
+        if (version >= GR_GL_VER(3, 0) ||
             ctxInfo.hasExtension("GL_ARB_framebuffer_object")) {
             fOversizedStencilSupport = true;
         } else {
             SkASSERT(ctxInfo.hasExtension("GL_EXT_framebuffer_object"));
         }
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         // ES 3.0 supports mixed size FBO attachments, 2.0 does not.
-        fOversizedStencilSupport = ctxInfo.version() >= GR_GL_VER(3, 0);
+        fOversizedStencilSupport = version >= GR_GL_VER(3, 0);
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        // WebGL 1.0 has some constraints for FBO attachments:
+        // https://www.khronos.org/registry/webgl/specs/1.0/index.html#6.6
+        // These constraints "no longer apply in WebGL 2"
+        fOversizedStencilSupport = version >= GR_GL_VER(2, 0);
     }
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         fDrawIndirectSupport = version >= GR_GL_VER(4,0) ||
                                ctxInfo.hasExtension("GL_ARB_draw_indirect");
         fBaseInstanceSupport = version >= GR_GL_VER(4,2);
@@ -551,22 +606,26 @@
                                      !fBaseInstanceSupport && // The ARB extension has no base inst.
                                      ctxInfo.hasExtension("GL_ARB_multi_draw_indirect"));
         fDrawRangeElementsSupport = version >= GR_GL_VER(2,0);
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         fDrawIndirectSupport = version >= GR_GL_VER(3,1);
         fMultiDrawIndirectSupport = fDrawIndirectSupport &&
                                     ctxInfo.hasExtension("GL_EXT_multi_draw_indirect");
         fBaseInstanceSupport = fDrawIndirectSupport &&
                                ctxInfo.hasExtension("GL_EXT_base_instance");
         fDrawRangeElementsSupport = version >= GR_GL_VER(3,0);
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        // WebGL lacks indirect support, but drawRange was added in WebGL 2.0
+        fDrawRangeElementsSupport = version >= GR_GL_VER(2,0);
     }
 
     // TODO: support CHROMIUM_sync_point and maybe KHR_fence_sync
-    if (kGL_GrGLStandard == standard) {
-        if (version >= GR_GL_VER(3, 2) || ctxInfo.hasExtension("GL_ARB_sync")) {
-            fFenceSyncSupport = true;
-        }
-    } else if (version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_APPLE_sync")) {
-        fFenceSyncSupport = true;
+    if (GR_IS_GR_GL(standard)) {
+        fFenceSyncSupport = (version >= GR_GL_VER(3, 2) || ctxInfo.hasExtension("GL_ARB_sync"));
+    } else if (GR_IS_GR_GL_ES(standard)) {
+        fFenceSyncSupport = (version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_APPLE_sync"));
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        // Only in WebGL 2.0
+        fFenceSyncSupport = version >= GR_GL_VER(2, 0);
     }
 
     // Safely moving textures between contexts requires fences.
@@ -574,33 +633,35 @@
 
     // Half float vertex attributes requires GL3 or ES3
     // It can also work with OES_VERTEX_HALF_FLOAT, but that requires a different enum.
-    if (kGL_GrGLStandard == standard) {
-        if (version >= GR_GL_VER(3, 0)) {
-            fHalfFloatVertexAttributeSupport = true;
-        }
-    } else if (version >= GR_GL_VER(3, 0)) {
-        fHalfFloatVertexAttributeSupport = true;
+    if (GR_IS_GR_GL(standard)) {
+        fHalfFloatVertexAttributeSupport = (version >= GR_GL_VER(3, 0));
+    } else if (GR_IS_GR_GL_ES(standard)) {
+        fHalfFloatVertexAttributeSupport = (version >= GR_GL_VER(3, 0));
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        // This appears to be supported in 2.0, looking at the spec.
+        fHalfFloatVertexAttributeSupport = (version >= GR_GL_VER(2, 0));
     }
 
     fDynamicStateArrayGeometryProcessorTextureSupport = true;
 
-    if (kGL_GrGLStandard == standard) {
-        if (version >= GR_GL_VER(4, 1)) {
-            fProgramBinarySupport = true;
-        }
-    } else if (version >= GR_GL_VER(3, 0)) {
-        fProgramBinarySupport = true;
-    }
+    if (GR_IS_GR_GL(standard)) {
+        fProgramBinarySupport = (version >= GR_GL_VER(4, 1));
+    } else if (GR_IS_GR_GL_ES(standard)) {
+        fProgramBinarySupport = (version >= GR_GL_VER(3, 0));
+    } // Explicitly not supported in WebGL 2.0
+      // https://www.khronos.org/registry/webgl/specs/2.0/#5.4
     if (fProgramBinarySupport) {
         GrGLint count;
         GR_GL_GetIntegerv(gli, GR_GL_NUM_PROGRAM_BINARY_FORMATS, &count);
         fProgramBinarySupport = count > 0;
     }
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         fSamplerObjectSupport =
                 version >= GR_GL_VER(3,3) || ctxInfo.hasExtension("GL_ARB_sampler_objects");
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         fSamplerObjectSupport = version >= GR_GL_VER(3,0);
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        fSamplerObjectSupport = version >= GR_GL_VER(2,0);
     }
     // Requires fTextureRedSupport, fTextureSwizzleSupport, msaa support, ES compatibility have
     // already been detected.
@@ -619,67 +680,62 @@
 
 const char* get_glsl_version_decl_string(GrGLStandard standard, GrGLSLGeneration generation,
                                          bool isCoreProfile) {
-    switch (generation) {
-        case k110_GrGLSLGeneration:
-            if (kGLES_GrGLStandard == standard) {
-                // ES2s shader language is based on version 1.20 but is version
-                // 1.00 of the ES language.
-                return "#version 100\n";
-            } else {
-                SkASSERT(kGL_GrGLStandard == standard);
+    if (GR_IS_GR_GL(standard)) {
+        switch (generation) {
+            case k110_GrGLSLGeneration:
                 return "#version 110\n";
-            }
-        case k130_GrGLSLGeneration:
-            SkASSERT(kGL_GrGLStandard == standard);
-            return "#version 130\n";
-        case k140_GrGLSLGeneration:
-            SkASSERT(kGL_GrGLStandard == standard);
-            return "#version 140\n";
-        case k150_GrGLSLGeneration:
-            SkASSERT(kGL_GrGLStandard == standard);
-            if (isCoreProfile) {
-                return "#version 150\n";
-            } else {
-                return "#version 150 compatibility\n";
-            }
-        case k330_GrGLSLGeneration:
-            if (kGLES_GrGLStandard == standard) {
-                return "#version 300 es\n";
-            } else {
-                SkASSERT(kGL_GrGLStandard == standard);
+            case k130_GrGLSLGeneration:
+                return "#version 130\n";
+            case k140_GrGLSLGeneration:
+                return "#version 140\n";
+            case k150_GrGLSLGeneration:
+                if (isCoreProfile) {
+                    return "#version 150\n";
+                } else {
+                    return "#version 150 compatibility\n";
+                }
+            case k330_GrGLSLGeneration:
                 if (isCoreProfile) {
                     return "#version 330\n";
                 } else {
                     return "#version 330 compatibility\n";
                 }
-            }
-        case k400_GrGLSLGeneration:
-            SkASSERT(kGL_GrGLStandard == standard);
-            if (isCoreProfile) {
-                return "#version 400\n";
-            } else {
-                return "#version 400 compatibility\n";
-            }
-        case k420_GrGLSLGeneration:
-            SkASSERT(kGL_GrGLStandard == standard);
-            if (isCoreProfile) {
-                return "#version 420\n";
-            }
-            else {
-                return "#version 420 compatibility\n";
-            }
-        case k310es_GrGLSLGeneration:
-            SkASSERT(kGLES_GrGLStandard == standard);
-            return "#version 310 es\n";
-        case k320es_GrGLSLGeneration:
-            SkASSERT(kGLES_GrGLStandard == standard);
-            return "#version 320 es\n";
+            case k400_GrGLSLGeneration:
+                if (isCoreProfile) {
+                    return "#version 400\n";
+                } else {
+                    return "#version 400 compatibility\n";
+                }
+            case k420_GrGLSLGeneration:
+                if (isCoreProfile) {
+                    return "#version 420\n";
+                } else {
+                    return "#version 420 compatibility\n";
+                }
+            default:
+                break;
+        }
+    } else if (GR_IS_GR_GL_ES(standard) || GR_IS_GR_WEBGL(standard)) {
+        switch (generation) {
+            case k110_GrGLSLGeneration:
+                // ES2s shader language is based on version 1.20 but is version
+                // 1.00 of the ES language.
+                return "#version 100\n";
+            case k330_GrGLSLGeneration:
+                return "#version 300 es\n";
+            case k310es_GrGLSLGeneration:
+                return "#version 310 es\n";
+            case k320es_GrGLSLGeneration:
+                return "#version 320 es\n";
+            default:
+                break;
+        }
     }
     return "<no version>";
 }
 
 bool is_float_fp32(const GrGLContextInfo& ctxInfo, const GrGLInterface* gli, GrGLenum precision) {
-    if (kGLES_GrGLStandard != ctxInfo.standard() &&
+    if (GR_IS_GR_GL(ctxInfo.standard()) &&
         ctxInfo.version() < GR_GL_VER(4,1) &&
         !ctxInfo.hasExtension("GL_ARB_ES2_compatibility")) {
         // We're on a desktop GL that doesn't have precision info. Assume they're all 32bit float.
@@ -708,7 +764,7 @@
 
     GrShaderCaps* shaderCaps = fShaderCaps.get();
     shaderCaps->fGLSLGeneration = ctxInfo.glslGeneration();
-    if (kGLES_GrGLStandard == standard) {
+    if (GR_IS_GR_GL_ES(standard)) {
         // fFBFetchRequiresEnablePerSample is not a shader cap but is initialized below to keep it
         // with related FB fetch logic.
         if (ctxInfo.hasExtension("GL_EXT_shader_framebuffer_fetch")) {
@@ -733,47 +789,70 @@
             fFBFetchRequiresEnablePerSample = true;
         }
         shaderCaps->fUsesPrecisionModifiers = true;
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        shaderCaps->fUsesPrecisionModifiers = true;
     }
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         shaderCaps->fFlatInterpolationSupport = ctxInfo.glslGeneration() >= k130_GrGLSLGeneration;
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard) || GR_IS_GR_WEBGL(standard)) {
         shaderCaps->fFlatInterpolationSupport =
             ctxInfo.glslGeneration() >= k330_GrGLSLGeneration; // This is the value for GLSL ES 3.0.
-    }
+    } // not sure for WebGL
+
     // Flat interpolation appears to be slow on Qualcomm GPUs (tested Adreno 405 and 530). ANGLE
     // Avoid on ANGLE too, it inserts a geometry shader into the pipeline to implement flat interp.
     shaderCaps->fPreferFlatInterpolation = shaderCaps->fFlatInterpolationSupport &&
                                            kQualcomm_GrGLVendor != ctxInfo.vendor() &&
                                            kANGLE_GrGLDriver != ctxInfo.driver();
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         shaderCaps->fNoPerspectiveInterpolationSupport =
             ctxInfo.glslGeneration() >= k130_GrGLSLGeneration;
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         if (ctxInfo.hasExtension("GL_NV_shader_noperspective_interpolation") &&
             ctxInfo.glslGeneration() >= k330_GrGLSLGeneration /* GLSL ES 3.0 */) {
             shaderCaps->fNoPerspectiveInterpolationSupport = true;
             shaderCaps->fNoPerspectiveInterpolationExtensionString =
                 "GL_NV_shader_noperspective_interpolation";
         }
+    }  // Not sure for WebGL
+
+    if (GR_IS_GR_GL(standard)) {
+        shaderCaps->fSampleVariablesSupport = ctxInfo.glslGeneration() >= k400_GrGLSLGeneration;
+    } else if (GR_IS_GR_GL_ES(standard)) {
+        if (ctxInfo.glslGeneration() >= k320es_GrGLSLGeneration) {
+            shaderCaps->fSampleVariablesSupport = true;
+        } else if (ctxInfo.hasExtension("GL_OES_sample_variables")) {
+            shaderCaps->fSampleVariablesSupport = true;
+            shaderCaps->fSampleVariablesExtensionString = "GL_OES_sample_variables";
+        }
+    }
+
+    // FIXME: The sample mask round rect op draws nothing on several Adreno and Radeon bots.
+    // Temporarily disable while we investigate.
+    // http://skbug.com/8921
+    if (kQualcomm_GrGLVendor == ctxInfo.vendor() || kATI_GrGLVendor == ctxInfo.vendor()) {
+        shaderCaps->fSampleVariablesSupport = false;
     }
 
     shaderCaps->fVersionDeclString = get_glsl_version_decl_string(standard,
                                                                   shaderCaps->fGLSLGeneration,
                                                                   fIsCoreProfile);
 
-    if (kGLES_GrGLStandard == standard && k110_GrGLSLGeneration == shaderCaps->fGLSLGeneration) {
-        shaderCaps->fShaderDerivativeExtensionString = "GL_OES_standard_derivatives";
-    }
+    if (GR_IS_GR_GL_ES(standard) || GR_IS_GR_WEBGL(standard)) {
+        if (k110_GrGLSLGeneration == shaderCaps->fGLSLGeneration) {
+            shaderCaps->fShaderDerivativeExtensionString = "GL_OES_standard_derivatives";
+        }
+    } // WebGL might have to check for OES_standard_derivatives
 
     // Frag Coords Convention support is not part of ES
-    if (kGLES_GrGLStandard != standard &&
+    if (GR_IS_GR_GL(standard) &&
         (ctxInfo.glslGeneration() >= k150_GrGLSLGeneration ||
          ctxInfo.hasExtension("GL_ARB_fragment_coord_conventions"))) {
         shaderCaps->fFragCoordConventionsExtensionString = "GL_ARB_fragment_coord_conventions";
     }
 
-    if (kGLES_GrGLStandard == standard) {
+    if (GR_IS_GR_GL_ES(standard)) {
         shaderCaps->fSecondaryOutputExtensionString = "GL_EXT_blend_func_extended";
     }
 
@@ -789,28 +868,29 @@
         }
     }
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         shaderCaps->fVertexIDSupport = true;
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard) || GR_IS_GR_WEBGL(standard)) {
         // Desktop GLSL 3.30 == ES GLSL 3.00.
         shaderCaps->fVertexIDSupport = ctxInfo.glslGeneration() >= k330_GrGLSLGeneration;
     }
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         shaderCaps->fFPManipulationSupport = ctxInfo.glslGeneration() >= k400_GrGLSLGeneration;
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard) || GR_IS_GR_WEBGL(standard)) {
         shaderCaps->fFPManipulationSupport = ctxInfo.glslGeneration() >= k310es_GrGLSLGeneration;
     }
 
     shaderCaps->fFloatIs32Bits = is_float_fp32(ctxInfo, gli, GR_GL_HIGH_FLOAT);
     shaderCaps->fHalfIs32Bits = is_float_fp32(ctxInfo, gli, GR_GL_MEDIUM_FLOAT);
+    shaderCaps->fHasLowFragmentPrecision = kMali4xx_GrGLRenderer == ctxInfo.renderer();
 
     // Unsigned integers only supported in and after GLSL 1.30.
     shaderCaps->fUnsignedSupport = ctxInfo.glslGeneration() >= k130_GrGLSLGeneration;
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         shaderCaps->fBuiltinFMASupport = ctxInfo.glslGeneration() >= k400_GrGLSLGeneration;
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         shaderCaps->fBuiltinFMASupport = ctxInfo.glslGeneration() >= k320es_GrGLSLGeneration;
     }
 }
@@ -822,16 +902,19 @@
         return false;
     }
 
-    if (kGL_GrGLStandard == ctxInfo.standard()) {
+    if (GR_IS_GR_GL(ctxInfo.standard())) {
         if (ctxInfo.version() < GR_GL_VER(4, 3) &&
             !ctxInfo.hasExtension("GL_ARB_program_interface_query")) {
             return false;
         }
-    } else {
+    } else if (GR_IS_GR_GL_ES(ctxInfo.standard())) {
         if (!hasChromiumPathRendering &&
             ctxInfo.version() < GR_GL_VER(3, 1)) {
             return false;
         }
+    } else if (GR_IS_GR_WEBGL(ctxInfo.standard())) {
+        // No WebGL support
+        return false;
     }
     // We only support v1.3+ of GL_NV_path_rendering which allows us to
     // set individual fragment inputs with ProgramPathFragmentInputGen. The API
@@ -865,7 +948,7 @@
         return false;
     }
 
-    if (kGL_GrGLStandard == fStandard) {
+    if (GR_IS_GR_GL(fStandard)) {
         // Some OpenGL implementations allow GL_ALPHA as a format to glReadPixels. However,
         // the manual (https://www.opengl.org/sdk/docs/man/) says only these formats are allowed:
         // GL_STENCIL_INDEX, GL_DEPTH_COMPONENT, GL_DEPTH_STENCIL, GL_RED, GL_GREEN, GL_BLUE,
@@ -937,7 +1020,25 @@
                             ctxInfo.hasExtension("GL_CHROMIUM_framebuffer_mixed_samples");
     }
 
-    if (kGL_GrGLStandard != ctxInfo.standard()) {
+    if (GR_IS_GR_GL(ctxInfo.standard())) {
+        if (fUsesMixedSamples) {
+            fMSFBOType = kMixedSamples_MSFBOType;
+        } else if (ctxInfo.version() >= GR_GL_VER(3,0) ||
+                   ctxInfo.hasExtension("GL_ARB_framebuffer_object")) {
+
+            fMSFBOType = kStandard_MSFBOType;
+            if (!fIsCoreProfile && ctxInfo.renderer() != kOSMesa_GrGLRenderer) {
+                // Core profile removes ALPHA8 support.
+                // OpenGL 3.0+ (and GL_ARB_framebuffer_object) supports ALPHA8 as renderable.
+                // However, osmesa fails if it is used even when GL_ARB_framebuffer_object is
+                // present.
+                fAlpha8IsRenderable = true;
+            }
+        } else if (ctxInfo.hasExtension("GL_EXT_framebuffer_multisample") &&
+                   ctxInfo.hasExtension("GL_EXT_framebuffer_blit")) {
+            fMSFBOType = kStandard_MSFBOType;
+        }
+    } else if (GR_IS_GR_GL_ES(ctxInfo.standard())) {
         if (ctxInfo.version() >= GR_GL_VER(3,0) &&
             ctxInfo.renderer() != kGalliumLLVM_GrGLRenderer) {
             // The gallium llvmpipe renderer for es3.0 does not have textureRed support even though
@@ -962,44 +1063,26 @@
         } else if (ctxInfo.hasExtension("GL_APPLE_framebuffer_multisample")) {
             fMSFBOType = kES_Apple_MSFBOType;
         }
-    } else {
-        if (fUsesMixedSamples) {
-            fMSFBOType = kMixedSamples_MSFBOType;
-        } else if (ctxInfo.version() >= GR_GL_VER(3,0) ||
-                   ctxInfo.hasExtension("GL_ARB_framebuffer_object")) {
-
-            fMSFBOType = kStandard_MSFBOType;
-            if (!fIsCoreProfile && ctxInfo.renderer() != kOSMesa_GrGLRenderer) {
-                // Core profile removes ALPHA8 support.
-                // OpenGL 3.0+ (and GL_ARB_framebuffer_object) supports ALPHA8 as renderable.
-                // However, osmesa fails if it is used even when GL_ARB_framebuffer_object is
-                // present.
-                fAlpha8IsRenderable = true;
-            }
-        } else if (ctxInfo.hasExtension("GL_EXT_framebuffer_multisample") &&
-                   ctxInfo.hasExtension("GL_EXT_framebuffer_blit")) {
-            fMSFBOType = kStandard_MSFBOType;
-        }
+    } else if (GR_IS_GR_WEBGL(ctxInfo.standard())) {
+        // No support in WebGL
+        fMSFBOType = kNone_MSFBOType;
     }
 
     // We disable MSAA across the board for Intel GPUs for performance reasons.
     if (kIntel_GrGLVendor == ctxInfo.vendor()) {
         fMSFBOType = kNone_MSFBOType;
     }
-
-    // We only have a use for raster multisample if there is coverage modulation from mixed samples.
-    if (fUsesMixedSamples && ctxInfo.hasExtension("GL_EXT_raster_multisample")) {
-        GR_GL_GetIntegerv(gli, GR_GL_MAX_RASTER_SAMPLES, &fMaxRasterSamples);
-    }
 }
 
 void GrGLCaps::initBlendEqationSupport(const GrGLContextInfo& ctxInfo) {
     GrShaderCaps* shaderCaps = static_cast<GrShaderCaps*>(fShaderCaps.get());
 
     bool layoutQualifierSupport = false;
-    if ((kGL_GrGLStandard == fStandard && shaderCaps->generation() >= k140_GrGLSLGeneration)  ||
-        (kGLES_GrGLStandard == fStandard && shaderCaps->generation() >= k330_GrGLSLGeneration)) {
+    if ((GR_IS_GR_GL(fStandard) && shaderCaps->generation() >= k140_GrGLSLGeneration)  ||
+        (GR_IS_GR_GL_ES(fStandard) && shaderCaps->generation() >= k330_GrGLSLGeneration)) {
         layoutQualifierSupport = true;
+    } else if (GR_IS_GR_WEBGL(fStandard)) {
+        return;
     }
 
     if (ctxInfo.hasExtension("GL_NV_blend_equation_advanced_coherent")) {
@@ -1041,7 +1124,7 @@
     //  gS     = {GR_GL_STENCIL_INDEX,    kUnknownBitCount, kUnknownBitCount, false},
         gDS    = {GR_GL_DEPTH_STENCIL,    kUnknownBitCount, kUnknownBitCount, true };
 
-    if (kGL_GrGLStandard == ctxInfo.standard()) {
+    if (GR_IS_GR_GL(ctxInfo.standard())) {
         bool supportsPackedDS =
             ctxInfo.version() >= GR_GL_VER(3,0) ||
             ctxInfo.hasExtension("GL_EXT_packed_depth_stencil") ||
@@ -1059,7 +1142,7 @@
         if (supportsPackedDS) {
             fStencilFormats.push_back() = gDS;
         }
-    } else {
+    } else if (GR_IS_GR_GL_ES(ctxInfo.standard())) {
         // ES2 has STENCIL_INDEX8 without extensions but requires extensions
         // for other formats.
         // ES doesn't support using the unsized format.
@@ -1073,6 +1156,11 @@
         if (ctxInfo.hasExtension("GL_OES_stencil4")) {
             fStencilFormats.push_back() = gS4;
         }
+    } else if (GR_IS_GR_WEBGL(ctxInfo.standard())) {
+        fStencilFormats.push_back() = gS8;
+        if (ctxInfo.version() >= GR_GL_VER(2,0)) {
+            fStencilFormats.push_back() = gD24S8;
+        }
     }
 }
 
@@ -1340,6 +1428,11 @@
             Adds R16F, RG16F, RGBA16F, R32F, RG32F, RGBA32F, R11F_G11F_B10F.
     */
 
+    GrGLStandard standard = ctxInfo.standard();
+    // standard can be unused (optimzed away) if SK_ASSUME_GL_ES is set
+    sk_ignore_unused_variable(standard);
+    GrGLVersion version = ctxInfo.version();
+
     // Correctness workarounds.
     bool disableTextureRedForMesa = false;
     bool disableSRGBForX86PowerVR = false;
@@ -1348,6 +1441,8 @@
     bool disableSRGBRenderWithMSAAForMacAMD = false;
     bool disableRGB8ForMali400 = false;
     bool disableGrayLumFBOForMesa = false;
+    bool disableBGRATextureStorageForIntelWindowsES = false;
+    bool disablePerFormatTextureStorageForCommandBufferES2 = false;
 
     if (!contextOptions.fDisableDriverCorrectnessWorkarounds) {
         // ARB_texture_rg is part of OpenGL 3.0, but osmesa doesn't support GL_RED
@@ -1380,6 +1475,18 @@
 #endif
         // Mali-400 fails ReadPixels tests, mostly with non-0xFF alpha values when read as GL_RGBA8.
         disableRGB8ForMali400 = kMali4xx_GrGLRenderer == ctxInfo.renderer();
+
+#if defined(SK_BUILD_FOR_WIN)
+        // On Intel Windows ES contexts it seems that using texture storage with BGRA causes
+        // problems with cross-context SkImages.
+        disableBGRATextureStorageForIntelWindowsES = kIntel_GrGLDriver == ctxInfo.driver() &&
+                                                   GR_IS_GR_GL_ES(standard);
+#endif
+        // ES2 Command Buffer has several TexStorage restrictions. It appears to fail for any format
+        // not explicitly allowed by the original GL_EXT_texture_storage, particularly those from
+        // other extensions even when later revisions define the interactions.
+        disablePerFormatTextureStorageForCommandBufferES2 =
+                kChromium_GrGLDriver == ctxInfo.driver() && version < GR_GL_VER(3, 0);
     }
 
     uint32_t nonMSAARenderFlags = ConfigInfo::kRenderable_Flag |
@@ -1388,34 +1495,38 @@
     if (kNone_MSFBOType != fMSFBOType) {
         allRenderFlags |= ConfigInfo::kRenderableWithMSAA_Flag;
     }
-    GrGLStandard standard = ctxInfo.standard();
-    GrGLVersion version = ctxInfo.version();
 
     bool texStorageSupported = false;
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         // The EXT version can apply to either GL or GLES.
         texStorageSupported = version >= GR_GL_VER(4,2) ||
                               ctxInfo.hasExtension("GL_ARB_texture_storage") ||
                               ctxInfo.hasExtension("GL_EXT_texture_storage");
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         texStorageSupported = version >= GR_GL_VER(3,0) ||
                               ctxInfo.hasExtension("GL_EXT_texture_storage");
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        texStorageSupported = version >= GR_GL_VER(2,0);
     }
     if (fDriverBugWorkarounds.disable_texture_storage) {
         texStorageSupported = false;
     }
-
+#ifdef SK_BUILD_FOR_ANDROID
+    // crbug.com/945506. Telemetry reported a memory usage regression for Android Go Chrome/WebView
+    // when using glTexStorage2D. This appears to affect OOP-R (so not just over command buffer).
+    texStorageSupported = false;
+#endif
     bool textureRedSupport = false;
 
     if (!disableTextureRedForMesa) {
-        if (kGL_GrGLStandard == standard) {
+        if (GR_IS_GR_GL(standard)) {
             textureRedSupport =
                     version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_ARB_texture_rg");
-        } else {
+        } else if (GR_IS_GR_GL_ES(standard)) {
             textureRedSupport =
                     version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_EXT_texture_rg");
         }
-    }
+    } // No WebGL support
 
     fConfigTable[kUnknown_GrPixelConfig].fFormats.fBaseInternalFormat = 0;
     fConfigTable[kUnknown_GrPixelConfig].fFormats.fSizedInternalFormat = 0;
@@ -1431,16 +1542,19 @@
     fConfigTable[kRGBA_8888_GrPixelConfig].fFormats.fExternalType = GR_GL_UNSIGNED_BYTE;
     fConfigTable[kRGBA_8888_GrPixelConfig].fFormatType = kNormalizedFixedPoint_FormatType;
     fConfigTable[kRGBA_8888_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag;
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         // We require some form of FBO support and all GLs with FBO support can render to RGBA8
         fConfigTable[kRGBA_8888_GrPixelConfig].fFlags |= allRenderFlags;
-    } else {
-        // hack for skbug:8378 - assume support on WebGL.
-        if (kIsWebGL || version >= GR_GL_VER(3,0) || ctxInfo.hasExtension("GL_OES_rgb8_rgba8") ||
+    } else if (GR_IS_GR_GL_ES(standard)) {
+        if (version >= GR_GL_VER(3,0) || ctxInfo.hasExtension("GL_OES_rgb8_rgba8") ||
             ctxInfo.hasExtension("GL_ARM_rgba8")) {
             fConfigTable[kRGBA_8888_GrPixelConfig].fFlags |= allRenderFlags;
         }
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        // WebGL seems to support RBGA8
+        fConfigTable[kRGBA_8888_GrPixelConfig].fFlags |= allRenderFlags;
     }
+
     if (texStorageSupported) {
         fConfigTable[kRGBA_8888_GrPixelConfig].fFlags |= ConfigInfo::kCanUseTexStorage_Flag;
     }
@@ -1457,7 +1571,7 @@
     fConfigTable[kRGB_888_GrPixelConfig].fFormats.fExternalType = GR_GL_UNSIGNED_BYTE;
     fConfigTable[kRGB_888_GrPixelConfig].fFormatType = kNormalizedFixedPoint_FormatType;
     fConfigTable[kRGB_888_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag;
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         // Even in OpenGL 4.6 GL_RGB8 is required to be color renderable but not required to be a
         // supported render buffer format. Since we usually use render buffers for MSAA on non-ES GL
         // we don't support MSAA for GL_RGB8. On 4.2+ we could check using
@@ -1466,11 +1580,14 @@
         // This also would probably work in mixed-samples mode where there is no MSAA color buffer
         // but we don't support that just for simplicity's sake.
         fConfigTable[kRGB_888_GrPixelConfig].fFlags |= nonMSAARenderFlags;
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         // 3.0 and the extension support this as a render buffer format.
         if (version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_OES_rgb8_rgba8")) {
             fConfigTable[kRGB_888_GrPixelConfig].fFlags |= allRenderFlags;
         }
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        // WebGL seems to support RBG8
+        fConfigTable[kRGB_888_GrPixelConfig].fFlags |= allRenderFlags;
     }
     if (texStorageSupported) {
         fConfigTable[kRGB_888_GrPixelConfig].fFlags |= ConfigInfo::kCanUseTexStorage_Flag;
@@ -1480,9 +1597,12 @@
         fConfigTable[kRGB_888_GrPixelConfig].fFlags = 0;
     }
 
-    // ES2 Command Buffer has several TexStorage restrictions. It appears to fail for any format
-    // not explicitly allowed by GL_EXT_texture_storage, particularly those from other extensions.
-    bool isCommandBufferES2 = kChromium_GrGLDriver == ctxInfo.driver() && version < GR_GL_VER(3, 0);
+    fConfigTable[kRGB_888X_GrPixelConfig] = fConfigTable[kRGBA_8888_GrPixelConfig];
+    fConfigTable[kRGB_888X_GrPixelConfig].fSwizzle = GrSwizzle::RGB1();
+    // Currently we don't allow RGB_888X to be renderable because we don't have a way to handle
+    // blends that reference the dst alpha when the values in the dst alpha channel are
+    // uninitialized.
+    fConfigTable[kRGB_888X_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag;
 
     fConfigTable[kRG_88_GrPixelConfig].fFormats.fBaseInternalFormat = GR_GL_RG;
     fConfigTable[kRG_88_GrPixelConfig].fFormats.fSizedInternalFormat = GR_GL_RG8;
@@ -1493,7 +1613,7 @@
     if (textureRedSupport) {
         fConfigTable[kRG_88_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag | allRenderFlags;
         // ES2 Command Buffer does not allow TexStorage with RG8_EXT
-        if (texStorageSupported && !isCommandBufferES2) {
+        if (texStorageSupported && !disablePerFormatTextureStorageForCommandBufferES2) {
             fConfigTable[kRG_88_GrPixelConfig].fFlags |= ConfigInfo::kCanUseTexStorage_Flag;
         }
     } else {
@@ -1507,11 +1627,11 @@
     fConfigTable[kBGRA_8888_GrPixelConfig].fFormatType = kNormalizedFixedPoint_FormatType;
 
    // TexStorage requires using a sized internal format and BGRA8 is only supported if we have the
-   // GL_APPLE_texture_format_BGRA8888 extension or if we have GL_EXT_texutre_storage and
+   // GL_APPLE_texture_format_BGRA8888 extension or if we have GL_EXT_texture_storage and
    // GL_EXT_texture_format_BGRA8888.
     bool supportsBGRATexStorage = false;
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         fConfigTable[kBGRA_8888_GrPixelConfig].fFormats.fBaseInternalFormat = GR_GL_RGBA;
         fConfigTable[kBGRA_8888_GrPixelConfig].fFormats.fSizedInternalFormat = GR_GL_RGBA8;
         if (version >= GR_GL_VER(1, 2) || ctxInfo.hasExtension("GL_EXT_bgra")) {
@@ -1521,14 +1641,16 @@
         }
         // Since we are using RGBA8 we can use tex storage.
         supportsBGRATexStorage = true;
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         fConfigTable[kBGRA_8888_GrPixelConfig].fFormats.fBaseInternalFormat = GR_GL_BGRA;
         fConfigTable[kBGRA_8888_GrPixelConfig].fFormats.fSizedInternalFormat = GR_GL_BGRA8;
         if (ctxInfo.hasExtension("GL_EXT_texture_format_BGRA8888")) {
             fConfigTable[kBGRA_8888_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag |
                                                             nonMSAARenderFlags;
 
-            if (ctxInfo.hasExtension("GL_EXT_texture_storage")) {
+            // GL_EXT_texture storage has defined interactions with GL_EXT_texture_format_BGRA8888.
+            if (ctxInfo.hasExtension("GL_EXT_texture_storage") &&
+                !disableBGRATextureStorageForIntelWindowsES) {
                 supportsBGRATexStorage = true;
             }
             if (ctxInfo.hasExtension("GL_CHROMIUM_renderbuffer_format_BGRA8888") &&
@@ -1545,11 +1667,17 @@
             // already too tricky. Instead, we opt not to support BGRA on ES2 with this extension.
             // This also side-steps some ambiguous interactions with the texture storage extension.
             if (version >= GR_GL_VER(3,0)) {
-                // The APPLE extension doesn't make this renderable.
-                fConfigTable[kBGRA_8888_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag;
+                // The APPLE extension doesn't explicitly make this renderable, but
+                // internally it appears to use RGBA8, which we'll patch up below.
+                fConfigTable[kBGRA_8888_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag |
+                                                                allRenderFlags;
                 supportsBGRATexStorage = true;
             }
         }
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        // Guess based on ES 2.0 support
+        fConfigTable[kBGRA_8888_GrPixelConfig].fFormats.fBaseInternalFormat = GR_GL_BGRA;
+        fConfigTable[kBGRA_8888_GrPixelConfig].fFormats.fSizedInternalFormat = GR_GL_BGRA8;
     }
 
     if (texStorageSupported && supportsBGRATexStorage) {
@@ -1558,8 +1686,8 @@
     fConfigTable[kBGRA_8888_GrPixelConfig].fSwizzle = GrSwizzle::RGBA();
 
     // We only enable srgb support if both textures and FBOs support srgb.
-    if (kGL_GrGLStandard == standard) {
-        if (ctxInfo.version() >= GR_GL_VER(3,0)) {
+    if (GR_IS_GR_GL(standard)) {
+        if (version >= GR_GL_VER(3,0)) {
             fSRGBSupport = true;
         } else if (ctxInfo.hasExtension("GL_EXT_texture_sRGB")) {
             if (ctxInfo.hasExtension("GL_ARB_framebuffer_sRGB") ||
@@ -1571,8 +1699,8 @@
         if (fSRGBSupport) {
             fSRGBWriteControl = true;
         }
-    } else {
-        fSRGBSupport = ctxInfo.version() >= GR_GL_VER(3,0) || ctxInfo.hasExtension("GL_EXT_sRGB");
+    } else if (GR_IS_GR_GL_ES(standard)) {
+        fSRGBSupport = version >= GR_GL_VER(3,0) || ctxInfo.hasExtension("GL_EXT_sRGB");
         if (disableSRGBForX86PowerVR) {
             fSRGBSupport = false;
         }
@@ -1581,6 +1709,11 @@
         // See https://bug.skia.org/5329 for Adreno4xx issue.
         fSRGBWriteControl = !disableSRGBWriteControlForAdreno4xx &&
             ctxInfo.hasExtension("GL_EXT_sRGB_write_control");
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        // sRGB extension should be on most WebGL 1.0 contexts, although
+        // sometimes under 2 names.
+        fSRGBSupport = version >= GR_GL_VER(2,0) || ctxInfo.hasExtension("GL_EXT_sRGB") ||
+                                                    ctxInfo.hasExtension("EXT_sRGB");
     }
 
     // This is very conservative, if we're on a platform where N32 is BGRA, and using ES, disable
@@ -1590,7 +1723,8 @@
     // of formats that can be used for TexImage calls to upload BGRA data to sRGBA (which is what
     // we *have* to use as the internal format, because sBGRA doesn't exist). This primarily
     // affects Windows.
-    if (kSkia8888_GrPixelConfig == kBGRA_8888_GrPixelConfig && kGLES_GrGLStandard == standard) {
+    if (kSkia8888_GrPixelConfig == kBGRA_8888_GrPixelConfig &&
+        (GR_IS_GR_GL_ES(standard) || GR_IS_GR_WEBGL(standard))) {
         fSRGBSupport = false;
     }
 
@@ -1612,7 +1746,7 @@
                                                          srgbRenderFlags;
     }
     // ES2 Command Buffer does not allow TexStorage with SRGB8_ALPHA8_EXT
-    if (texStorageSupported && !isCommandBufferES2) {
+    if (texStorageSupported && !disablePerFormatTextureStorageForCommandBufferES2) {
         fConfigTable[kSRGBA_8888_GrPixelConfig].fFlags |= ConfigInfo::kCanUseTexStorage_Flag;
     }
     fConfigTable[kSRGBA_8888_GrPixelConfig].fSwizzle = GrSwizzle::RGBA();
@@ -1627,7 +1761,7 @@
         GR_GL_BGRA;
     fConfigTable[kSBGRA_8888_GrPixelConfig].fFormats.fExternalType = GR_GL_UNSIGNED_BYTE;
     fConfigTable[kSBGRA_8888_GrPixelConfig].fFormatType = kNormalizedFixedPoint_FormatType;
-    if (fSRGBSupport && kGL_GrGLStandard == standard) {
+    if (fSRGBSupport && GR_IS_GR_GL(standard)) {
         fConfigTable[kSBGRA_8888_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag |
                                                          srgbRenderFlags;
     }
@@ -1648,11 +1782,13 @@
     fConfigTable[kRGB_565_GrPixelConfig].fFormats.fExternalType = GR_GL_UNSIGNED_SHORT_5_6_5;
     fConfigTable[kRGB_565_GrPixelConfig].fFormatType = kNormalizedFixedPoint_FormatType;
     fConfigTable[kRGB_565_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag;
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         if (version >= GR_GL_VER(4, 2) || ctxInfo.hasExtension("GL_ARB_ES2_compatibility")) {
             fConfigTable[kRGB_565_GrPixelConfig].fFlags |= allRenderFlags;
         }
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
+        fConfigTable[kRGB_565_GrPixelConfig].fFlags |= allRenderFlags;
+    } else if (GR_IS_GR_WEBGL(standard)) {
         fConfigTable[kRGB_565_GrPixelConfig].fFlags |= allRenderFlags;
     }
     // 565 is not a sized internal format on desktop GL. So on desktop with
@@ -1662,7 +1798,7 @@
     //
     // TODO: As of 4.2, regular GL supports 565. This logic is due for an
     // update.
-    if (texStorageSupported && kGL_GrGLStandard != standard) {
+    if (texStorageSupported && GR_IS_GR_GL_ES(standard)) {
         fConfigTable[kRGB_565_GrPixelConfig].fFlags |= ConfigInfo::kCanUseTexStorage_Flag;
     }
     fConfigTable[kRGB_565_GrPixelConfig].fSwizzle = GrSwizzle::RGBA();
@@ -1674,11 +1810,13 @@
     fConfigTable[kRGBA_4444_GrPixelConfig].fFormats.fExternalType = GR_GL_UNSIGNED_SHORT_4_4_4_4;
     fConfigTable[kRGBA_4444_GrPixelConfig].fFormatType = kNormalizedFixedPoint_FormatType;
     fConfigTable[kRGBA_4444_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag;
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         if (version >= GR_GL_VER(4, 2)) {
             fConfigTable[kRGBA_4444_GrPixelConfig].fFlags |= allRenderFlags;
         }
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
+        fConfigTable[kRGBA_4444_GrPixelConfig].fFlags |= allRenderFlags;
+    } else if (GR_IS_GR_WEBGL(standard)) {
         fConfigTable[kRGBA_4444_GrPixelConfig].fFlags |= allRenderFlags;
     }
     if (texStorageSupported) {
@@ -1693,22 +1831,29 @@
     fConfigTable[kRGBA_1010102_GrPixelConfig].fFormats.fExternalType =
         GR_GL_UNSIGNED_INT_2_10_10_10_REV;
     fConfigTable[kRGBA_1010102_GrPixelConfig].fFormatType = kNormalizedFixedPoint_FormatType;
-    if (kGL_GrGLStandard == standard || version >= GR_GL_VER(3, 0)) {
+    if (GR_IS_GR_GL(standard) ||
+       (GR_IS_GR_GL_ES(standard) && version >= GR_GL_VER(3, 0))) {
         fConfigTable[kRGBA_1010102_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag |
                                                            allRenderFlags;
-    }
+    } else if (GR_IS_GR_GL_ES(standard) &&
+               ctxInfo.hasExtension("GL_EXT_texture_type_2_10_10_10_REV")) {
+        fConfigTable[kRGBA_1010102_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag;
+    } // No WebGL support
+
     if (texStorageSupported) {
         fConfigTable[kRGBA_1010102_GrPixelConfig].fFlags |= ConfigInfo::kCanUseTexStorage_Flag;
     }
     fConfigTable[kRGBA_1010102_GrPixelConfig].fSwizzle = GrSwizzle::RGBA();
 
-    bool alpha8IsValidForGL = kGL_GrGLStandard == standard &&
+    bool alpha8IsValidForGL = GR_IS_GR_GL(standard) &&
             (!fIsCoreProfile || version <= GR_GL_VER(3, 0));
+    bool alpha8IsValidForGLES = GR_IS_GR_GL_ES(standard) && version < GR_GL_VER(3, 0);
+    bool alpha8IsValidForWebGL = GR_IS_GR_WEBGL(standard);
 
     ConfigInfo& alphaInfo = fConfigTable[kAlpha_8_as_Alpha_GrPixelConfig];
     alphaInfo.fFormats.fExternalType = GR_GL_UNSIGNED_BYTE;
     alphaInfo.fFormatType = kNormalizedFixedPoint_FormatType;
-    if (alpha8IsValidForGL || (kGL_GrGLStandard != standard && version < GR_GL_VER(3, 0))) {
+    if (alpha8IsValidForGL || alpha8IsValidForGLES || alpha8IsValidForWebGL) {
         alphaInfo.fFlags = ConfigInfo::kTextureable_Flag;
     }
     alphaInfo.fFormats.fBaseInternalFormat = GR_GL_ALPHA;
@@ -1728,7 +1873,7 @@
     redInfo.fSwizzle = GrSwizzle::RRRR();
 
     // ES2 Command Buffer does not allow TexStorage with R8_EXT (so Alpha_8 and Gray_8)
-    if (texStorageSupported && !isCommandBufferES2) {
+    if (texStorageSupported && !disablePerFormatTextureStorageForCommandBufferES2) {
         if (!disableR8TexStorageForANGLEGL) {
             alphaInfo.fFlags |= ConfigInfo::kCanUseTexStorage_Flag;
         }
@@ -1751,8 +1896,9 @@
     grayLumInfo.fFormats.fSizedInternalFormat = GR_GL_LUMINANCE8;
     grayLumInfo.fFormats.fExternalFormat[kReadPixels_ExternalFormatUsage] = GR_GL_LUMINANCE;
     grayLumInfo.fSwizzle = GrSwizzle::RGBA();
-    if ((standard == kGL_GrGLStandard && version <= GR_GL_VER(3, 0)) ||
-        (standard == kGLES_GrGLStandard && version < GR_GL_VER(3, 0))) {
+    if ((GR_IS_GR_GL(standard) && version <= GR_GL_VER(3, 0)) ||
+        (GR_IS_GR_GL_ES(standard) && version < GR_GL_VER(3, 0)) ||
+        (GR_IS_GR_WEBGL(standard))) {
         grayLumInfo.fFlags = ConfigInfo::kTextureable_Flag;
     }
 
@@ -1768,14 +1914,14 @@
     // Leaving Gray8 as non-renderable, to keep things simple and match raster. However, we do
     // enable the FBOColorAttachment_Flag so that we can bind it to an FBO for copies.
     grayRedInfo.fFlags |= ConfigInfo::kFBOColorAttachment_Flag;
-    if (kStandard_MSFBOType == this->msFBOType() && kGL_GrGLStandard == standard &&
+    if (kStandard_MSFBOType == this->msFBOType() && GR_IS_GR_GL(standard) &&
         !disableGrayLumFBOForMesa) {
         // 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.
         grayLumInfo.fFlags |= ConfigInfo::kFBOColorAttachment_Flag;
     }
-    if (texStorageSupported && !isCommandBufferES2) {
+    if (texStorageSupported && !disablePerFormatTextureStorageForCommandBufferES2) {
         if (!disableR8TexStorageForANGLEGL) {
             grayLumInfo.fFlags |= ConfigInfo::kCanUseTexStorage_Flag;
         }
@@ -1800,9 +1946,9 @@
     enum class HalfFPRenderTargetSupport { kNone, kRGBAOnly, kAll };
     HalfFPRenderTargetSupport halfFPRenderTargetSupport = HalfFPRenderTargetSupport::kNone;
     // for now we don't support floating point MSAA on ES
-    uint32_t fpRenderFlags = (kGL_GrGLStandard == standard) ? allRenderFlags : nonMSAARenderFlags;
+    uint32_t fpRenderFlags = (GR_IS_GR_GL(standard)) ? allRenderFlags : nonMSAARenderFlags;
 
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         if (version >= GR_GL_VER(3, 0)) {
             hasFP32Textures = true;
             hasFP16Textures = true;
@@ -1810,7 +1956,7 @@
             hasFP32RenderTargets = true;
             halfFPRenderTargetSupport = HalfFPRenderTargetSupport::kAll;
         }
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         if (version >= GR_GL_VER(3, 0)) {
             hasFP32Textures = true;
             hasFP16Textures = true;
@@ -1838,6 +1984,31 @@
             // This extension only enables half float support rendering for RGBA.
             halfFPRenderTargetSupport = HalfFPRenderTargetSupport::kRGBAOnly;
         }
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        if ((ctxInfo.hasExtension("GL_OES_texture_float_linear") &&
+             ctxInfo.hasExtension("GL_OES_texture_float")) ||
+            (ctxInfo.hasExtension("OES_texture_float_linear") &&
+             ctxInfo.hasExtension("OES_texture_float"))) {
+            hasFP32Textures = true;
+            hasFP16Textures = true;
+        } else if ((ctxInfo.hasExtension("GL_OES_texture_half_float_linear") &&
+                    ctxInfo.hasExtension("GL_OES_texture_half_float")) ||
+                   (ctxInfo.hasExtension("OES_texture_half_float_linear") &&
+                    ctxInfo.hasExtension("OES_texture_half_float"))) {
+            hasFP16Textures = true;
+        }
+
+        if (ctxInfo.hasExtension("GL_WEBGL_color_buffer_float") ||
+            ctxInfo.hasExtension("WEBGL_color_buffer_float")) {
+            // For now we only enable rendering to fp32 on desktop, because on WebGL
+            // there might be precision issues (see ES above)
+            // hasFP32RenderTargets = true;
+            halfFPRenderTargetSupport = HalfFPRenderTargetSupport::kAll;
+        } else if (ctxInfo.hasExtension("GL_EXT_color_buffer_half_float") ||
+                   ctxInfo.hasExtension("EXT_color_buffer_half_float")) {
+            // This extension only enables half float support rendering for RGBA.
+            halfFPRenderTargetSupport = HalfFPRenderTargetSupport::kRGBAOnly;
+        }
     }
 
     for (auto fpconfig : {kRGBA_float_GrPixelConfig, kRG_float_GrPixelConfig}) {
@@ -1861,7 +2032,8 @@
     }
 
     GrGLenum redHalfExternalType;
-    if (kGL_GrGLStandard == ctxInfo.standard() || ctxInfo.version() >= GR_GL_VER(3, 0)) {
+    if (GR_IS_GR_GL(standard) ||
+       (GR_IS_GR_GL_ES(standard) && version >= GR_GL_VER(3, 0))) {
         redHalfExternalType = GR_GL_HALF_FLOAT;
     } else {
         redHalfExternalType = GR_GL_HALF_FLOAT_OES;
@@ -1880,7 +2052,7 @@
             redHalf.fFlags |= fpRenderFlags;
         }
 
-        if (texStorageSupported && !isCommandBufferES2) {
+        if (texStorageSupported && !disablePerFormatTextureStorageForCommandBufferES2) {
             redHalf.fFlags |= ConfigInfo::kCanUseTexStorage_Flag;
         }
     }
@@ -1890,7 +2062,8 @@
     fConfigTable[kRGBA_half_GrPixelConfig].fFormats.fSizedInternalFormat = GR_GL_RGBA16F;
     fConfigTable[kRGBA_half_GrPixelConfig].fFormats.fExternalFormat[kReadPixels_ExternalFormatUsage] =
         GR_GL_RGBA;
-    if (kGL_GrGLStandard == ctxInfo.standard() || ctxInfo.version() >= GR_GL_VER(3, 0)) {
+    if (GR_IS_GR_GL(standard) ||
+       (GR_IS_GR_GL_ES(standard) && version >= GR_GL_VER(3, 0))) {
         fConfigTable[kRGBA_half_GrPixelConfig].fFormats.fExternalType = GR_GL_HALF_FLOAT;
     } else {
         fConfigTable[kRGBA_half_GrPixelConfig].fFormats.fExternalType = GR_GL_HALF_FLOAT_OES;
@@ -1903,11 +2076,15 @@
             fConfigTable[kRGBA_half_GrPixelConfig].fFlags |= fpRenderFlags;
         }
     }
-    if (texStorageSupported) {
+    if (texStorageSupported && !disablePerFormatTextureStorageForCommandBufferES2) {
         fConfigTable[kRGBA_half_GrPixelConfig].fFlags |= ConfigInfo::kCanUseTexStorage_Flag;
     }
     fConfigTable[kRGBA_half_GrPixelConfig].fSwizzle = GrSwizzle::RGBA();
 
+    // kRGBA_half_Clamped is just distinguished by clamps added to the shader. At the API level,
+    // it's identical to kRGBA_half.
+    fConfigTable[kRGBA_half_Clamped_GrPixelConfig] = fConfigTable[kRGBA_half_GrPixelConfig];
+
     // Compressed texture support
 
     // glCompressedTexImage2D is available on all OpenGL ES devices. It is available on standard
@@ -1925,11 +2102,11 @@
         = 0;
     fConfigTable[kRGB_ETC1_GrPixelConfig].fFormats.fExternalType = 0;
     fConfigTable[kRGB_ETC1_GrPixelConfig].fFormatType = kNormalizedFixedPoint_FormatType;
-    if (kGL_GrGLStandard == standard) {
+    if (GR_IS_GR_GL(standard)) {
         if (version >= GR_GL_VER(4, 3) || ctxInfo.hasExtension("GL_ARB_ES3_compatibility")) {
             fConfigTable[kRGB_ETC1_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag;
         }
-    } else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         if (version >= GR_GL_VER(3, 0) ||
             ctxInfo.hasExtension("GL_OES_compressed_ETC2_RGB8_texture")) {
             fConfigTable[kRGB_ETC1_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag;
@@ -1940,17 +2117,17 @@
                 GR_GL_COMPRESSED_ETC1_RGB8;
             fConfigTable[kRGB_ETC1_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag;
         }
-    }
+    } // No WebGL support
     fConfigTable[kRGB_ETC1_GrPixelConfig].fSwizzle = GrSwizzle::RGBA();
 
     // Bulk populate the texture internal/external formats here and then deal with exceptions below.
 
     // ES 2.0 requires that the internal/external formats match.
-    bool useSizedTexFormats = (kGL_GrGLStandard == ctxInfo.standard() ||
-                               ctxInfo.version() >= GR_GL_VER(3,0));
+    bool useSizedTexFormats = (GR_IS_GR_GL(standard) ||
+                              (GR_IS_GR_GL_ES(standard) && version >= GR_GL_VER(3,0)));
     // All ES versions (thus far) require sized internal formats for render buffers.
     // TODO: Always use sized internal format?
-    bool useSizedRbFormats = kGLES_GrGLStandard == ctxInfo.standard();
+    bool useSizedRbFormats = GR_IS_GR_GL_ES(standard) || GR_IS_GR_WEBGL(standard);
 
     for (int i = 0; i < kGrPixelConfigCnt; ++i) {
         // Almost always we want to pass fExternalFormat[kReadPixels_ExternalFormatUsage] as the
@@ -1968,7 +2145,7 @@
     // kAlpha_8_GrPixelConfig then we actually have to use a base internal format rather than a
     // sized internal format. This is because there is no valid 8 bit alpha sized internal format
     // in ES.
-    if (useSizedTexFormats && kGLES_GrGLStandard == ctxInfo.standard() && !textureRedSupport) {
+    if (useSizedTexFormats && GR_IS_GR_GL_ES(standard) && !textureRedSupport) {
         SkASSERT(fConfigTable[kAlpha_8_GrPixelConfig].fFormats.fBaseInternalFormat == GR_GL_ALPHA8);
         SkASSERT(fConfigTable[kAlpha_8_as_Alpha_GrPixelConfig].fFormats.fBaseInternalFormat ==
                      GR_GL_ALPHA8);
@@ -1982,7 +2159,7 @@
     // param to Tex(Sub)Image. ES 2.0 requires the <internalFormat> and <format> params to match.
     // Thus, on ES 2.0 we will use GL_SRGB_ALPHA as the <format> param.
     // On OpenGL and ES 3.0+ GL_SRGB_ALPHA does not work for the <format> param to glTexImage.
-    if (ctxInfo.standard() == kGLES_GrGLStandard && ctxInfo.version() == GR_GL_VER(2,0)) {
+    if (GR_IS_GR_GL_ES(standard) && version == GR_GL_VER(2,0)) {
         fConfigTable[kSRGBA_8888_GrPixelConfig].fFormats.fExternalFormat[kTexImage_ExternalFormatUsage] =
             GR_GL_SRGB_ALPHA;
 
@@ -2009,7 +2186,13 @@
     //     ES 3.0: the extension explicitly states GL_BGRA8 is not a valid internal format for
     //             glTexImage (just for glTexStorage).
     if (useSizedTexFormats && this->bgraIsInternalFormat()) {
-        fConfigTable[kBGRA_8888_GrPixelConfig].fFormats.fInternalFormatTexImage = GR_GL_BGRA;
+        if (ctxInfo.hasExtension("GL_APPLE_texture_format_BGRA8888")) {
+            fConfigTable[kBGRA_8888_GrPixelConfig].fFormats.fInternalFormatTexImage = GR_GL_RGBA;
+            fConfigTable[kBGRA_8888_GrPixelConfig].fFormats.fInternalFormatRenderbuffer =
+                GR_GL_RGBA8;
+        } else {
+            fConfigTable[kBGRA_8888_GrPixelConfig].fFormats.fInternalFormatTexImage = GR_GL_BGRA;
+        }
     }
 
     // If we don't have texture swizzle support then the shader generator must insert the
@@ -2037,10 +2220,10 @@
         if (ConfigInfo::kRenderableWithMSAA_Flag & fConfigTable[i].fFlags) {
             // We assume that MSAA rendering is supported only if we support non-MSAA rendering.
             SkASSERT(ConfigInfo::kRenderable_Flag & fConfigTable[i].fFlags);
-            if ((kGL_GrGLStandard == ctxInfo.standard() &&
-                 (ctxInfo.version() >= GR_GL_VER(4,2) ||
-                  ctxInfo.hasExtension("GL_ARB_internalformat_query"))) ||
-                (kGLES_GrGLStandard == ctxInfo.standard() && ctxInfo.version() >= GR_GL_VER(3,0))) {
+            if ((GR_IS_GR_GL(standard) &&
+                  (version >= GR_GL_VER(4,2) ||
+                   ctxInfo.hasExtension("GL_ARB_internalformat_query"))) ||
+                (GR_IS_GR_GL_ES(standard) && version >= GR_GL_VER(3,0))) {
                 int count;
                 GrGLenum format = fConfigTable[i].fFormats.fInternalFormatRenderbuffer;
                 GR_GL_GetInternalformativ(gli, GR_GL_RENDERBUFFER, format, GR_GL_NUM_SAMPLE_COUNTS,
@@ -2093,7 +2276,8 @@
     }
 
 #ifdef SK_DEBUG
-    // Make sure we initialized everything.
+    // Make sure we initialized everything by comparing all configs to the
+    // default (un-set) values and making sure they are not equal.
     ConfigInfo defaultEntry;
     for (int i = 0; i < kGrPixelConfigCnt; ++i) {
         // Make sure we didn't set renderable and not blittable or renderable with msaa and not
@@ -2102,6 +2286,7 @@
                   !(fConfigTable[i].fFlags & ConfigInfo::kFBOColorAttachment_Flag)));
         SkASSERT(!((fConfigTable[i].fFlags & ConfigInfo::kRenderableWithMSAA_Flag) &&
                   !(fConfigTable[i].fFlags & ConfigInfo::kRenderable_Flag)));
+
         SkASSERT(defaultEntry.fFormats.fBaseInternalFormat !=
                  fConfigTable[i].fFormats.fBaseInternalFormat);
         SkASSERT(defaultEntry.fFormats.fSizedInternalFormat !=
@@ -2124,7 +2309,7 @@
     // Table 3.9 of the ES2 spec indicates the supported formats with CopyTexSubImage
     // and BGRA isn't in the spec. There doesn't appear to be any extension that adds it. Perhaps
     // many drivers would allow it to work, but ANGLE does not.
-    if (kGLES_GrGLStandard == fStandard && this->bgraIsInternalFormat() &&
+    if (GR_IS_GR_GL_ES(fStandard) && this->bgraIsInternalFormat() &&
         (kBGRA_8888_GrPixelConfig == dstConfig || kBGRA_8888_GrPixelConfig == srcConfig)) {
         return false;
     }
@@ -2408,7 +2593,7 @@
 
     // glClearTexImage seems to have a bug in NVIDIA drivers that was fixed sometime between
     // 340.96 and 367.57.
-    if (kGL_GrGLStandard == ctxInfo.standard() &&
+    if (GR_IS_GR_GL(ctxInfo.standard()) &&
         ctxInfo.driver() == kNVIDIA_GrGLDriver &&
         ctxInfo.driverVersion() < GR_GL_DRIVER_VER(367, 57, 0)) {
         fClearTextureSupport = false;
@@ -2441,6 +2626,8 @@
     // See skbug.com/7058
     fMapBufferType = kNone_MapBufferType;
     fMapBufferFlags = kNone_MapFlags;
+    fTransferBufferSupport = false;
+    fTransferBufferType = kNone_TransferBufferType;
 #endif
 #endif
 
@@ -2452,10 +2639,13 @@
         ctxInfo.driverVersion() > GR_GL_DRIVER_VER(127, 0, 0)) {
         fMapBufferType = kNone_MapBufferType;
         fMapBufferFlags = kNone_MapFlags;
+        fTransferBufferSupport = false;
+        fTransferBufferType = kNone_TransferBufferType;
     }
 
     // TODO: re-enable for ANGLE
     if (kANGLE_GrGLDriver == ctxInfo.driver()) {
+        fTransferBufferSupport = false;
         fTransferBufferType = kNone_TransferBufferType;
     }
 
@@ -2704,6 +2894,11 @@
         shaderCaps->fRemovePowWithConstantExponent = true;
     }
 
+    if (kAdreno3xx_GrGLRenderer == ctxInfo.renderer() ||
+        kAdreno4xx_other_GrGLRenderer == ctxInfo.renderer()) {
+        shaderCaps->fMustWriteToFragColor = true;
+    }
+
     // Disabling advanced blend on various platforms with major known issues. We also block Chrome
     // for now until its own blacklists can be updated.
     if (kAdreno430_GrGLRenderer == ctxInfo.renderer() ||
@@ -2786,7 +2981,7 @@
     if (kMesa_GrGLDriver == ctxInfo.driver() &&
         (kIntelSandyBridge_GrGLRenderer == ctxInfo.renderer() ||
          kIntelBayTrail_GrGLRenderer == ctxInfo.renderer())) {
-        fBlacklistCoverageCounting = true;
+        fDriverBlacklistCCPR = true;
     }
 
 #ifdef SK_BUILD_FOR_ANDROID
@@ -2811,6 +3006,9 @@
     if (options.fDoManualMipmapping) {
         fDoManualMipmapping = true;
     }
+    if (options.fDisallowGLSLBinaryCaching) {
+        fProgramBinarySupport = false;
+    }
 }
 
 bool GrGLCaps::onSurfaceSupportsWritePixels(const GrSurface* surface) const {
@@ -2850,6 +3048,9 @@
     // this as makes sense to increase performance and correctness.
     switch (fConfigTable[config].fFormatType) {
         case kNormalizedFixedPoint_FormatType:
+            if (kRGB_888X_GrPixelConfig == config) {
+                return GrColorType::kRGB_888x;
+            }
             return GrColorType::kRGBA_8888;
         case kFloat_FormatType:
             if ((kAlpha_half_GrPixelConfig == config ||
@@ -2939,15 +3140,17 @@
         case kRGB_888x_SkColorType:
             if (GR_GL_RGB8 == format) {
                 return kRGB_888_GrPixelConfig;
+            } else if (GR_GL_RGBA8 == format) {
+                return kRGB_888X_GrPixelConfig;
             }
             break;
         case kBGRA_8888_SkColorType:
             if (GR_GL_RGBA8 == format) {
-                if (kGL_GrGLStandard == standard) {
+                if (GR_IS_GR_GL(standard)) {
                     return kBGRA_8888_GrPixelConfig;
                 }
             } else if (GR_GL_BGRA8 == format) {
-                if (kGLES_GrGLStandard == standard) {
+                if (GR_IS_GR_GL_ES(standard) || GR_IS_GR_WEBGL(standard)) {
                     return kBGRA_8888_GrPixelConfig;
                 }
             } else if (GR_GL_SRGB8_ALPHA8 == format) {
@@ -2968,6 +3171,11 @@
                 return kGray_8_as_Red_GrPixelConfig;
             }
             break;
+        case kRGBA_F16Norm_SkColorType:
+            if (GR_GL_RGBA16F == format) {
+                return kRGBA_half_Clamped_GrPixelConfig;
+            }
+            break;
         case kRGBA_F16_SkColorType:
             if (GR_GL_RGBA16F == format) {
                 return kRGBA_half_GrPixelConfig;
@@ -2979,7 +3187,7 @@
             }
             break;
     }
-
+    SkDebugf("Unknown pixel config %d\n", format);
     return kUnknown_GrPixelConfig;
 }
 
@@ -3023,6 +3231,12 @@
         case GR_GL_BGRA8:
             config = kBGRA_8888_GrPixelConfig;
             break;
+        case GR_GL_RGB10_A2:
+            config = kRGBA_1010102_GrPixelConfig;
+            break;
+        case GR_GL_R16F:
+            config = kAlpha_half_as_Red_GrPixelConfig;
+            break;
     }
 
     return config;
diff --git a/src/gpu/gl/GrGLContext.cpp b/src/gpu/gl/GrGLContext.cpp
index a2a5a3b..1c570e5 100644
--- a/src/gpu/gl/GrGLContext.cpp
+++ b/src/gpu/gl/GrGLContext.cpp
@@ -58,7 +58,7 @@
     // extension to be enabled, even when using ESSL3. Some devices appear to only support the ES2
     // extension. As an extreme (optional) solution, we can fallback to using ES2 shading language
     // if we want to prioritize external texture support. skbug.com/7713
-    if (kGLES_GrGLStandard == interface->fStandard &&
+    if (GR_IS_GR_GL_ES(interface->fStandard) &&
         options.fPreferExternalImagesOverES3 &&
         !options.fDisableDriverCorrectnessWorkarounds &&
         interface->hasExtension("GL_OES_EGL_image_external") &&
diff --git a/src/gpu/gl/GrGLCreateNullInterface.cpp b/src/gpu/gl/GrGLCreateNullInterface.cpp
deleted file mode 100644
index ebc11b4..0000000
--- a/src/gpu/gl/GrGLCreateNullInterface.cpp
+++ /dev/null
@@ -1,831 +0,0 @@
-/*
- * Copyright 2011 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrGLTestInterface.h"
-#include "GrNonAtomicRef.h"
-#include "SkMutex.h"
-#include "SkTDArray.h"
-#include "SkTo.h"
-#include "gl/GrGLInterface.h"
-
-#include <type_traits>
-
-// added to suppress 'no previous prototype' warning and because this code is duplicated in
-// SkNullGLContext.cpp
-namespace {
-
-class GLObject : public GrNonAtomicRef<GLObject> {
-public:
-    GLObject(GrGLuint id) : fID(id) {}
-    virtual ~GLObject() {}
-
-    GrGLuint id() const { return fID; }
-
-private:
-    GrGLuint fID;
-};
-
-// This class maintains a sparsely populated array of object pointers.
-template<typename T> class TGLObjectManager {
-   static_assert(std::is_convertible<T*, GLObject*>::value, "T must be a subclass of GLObject");
-
-public:
-    TGLObjectManager() : fFreeListHead(kFreeListEnd) {
-        *fGLObjects.append() = nullptr; // 0 is not a valid GL object id.
-    }
-
-    ~TGLObjectManager() {
-        // nullptr out the entries that are really free list links rather than ptrs before deleting.
-        intptr_t curr = fFreeListHead;
-        while (kFreeListEnd != curr) {
-            intptr_t next = reinterpret_cast<intptr_t>(fGLObjects[SkToS32(curr)]);
-            fGLObjects[SkToS32(curr)] = nullptr;
-            curr = next;
-        }
-
-        fGLObjects.safeUnrefAll();
-    }
-
-    T* lookUp(GrGLuint id) {
-        T* object = fGLObjects[id];
-        SkASSERT(object && object->id() == id);
-        return object;
-    }
-
-    T* create() {
-        GrGLuint id;
-        T* object;
-
-        if (kFreeListEnd == fFreeListHead) {
-            // no free slots - create a new one
-            id = fGLObjects.count();
-            object = new T(id);
-            *fGLObjects.append() = object;
-        } else {
-            // grab the head of the free list and advance the head to the next free slot.
-            id = static_cast<GrGLuint>(fFreeListHead);
-            fFreeListHead = reinterpret_cast<intptr_t>(fGLObjects[id]);
-
-            object = new T(id);
-            fGLObjects[id] = object;
-        }
-
-        return object;
-    }
-
-    void free(T* object) {
-        SkASSERT(object);
-        SkASSERT(fGLObjects.count() > 0);
-
-        GrGLuint id = object->id();
-        object->unref();
-
-        fGLObjects[id] = reinterpret_cast<T*>(fFreeListHead);
-        fFreeListHead = id;
-    }
-
-private:
-    static const intptr_t kFreeListEnd = -1;
-    // Index of the first entry of fGLObjects in the free list. Free slots in fGLObjects are indices
-    // to the next free slot. The last free slot has a value of kFreeListEnd.
-    intptr_t        fFreeListHead;
-    SkTDArray<T*>   fGLObjects;
-};
-
-class Buffer : public GLObject {
-public:
-    Buffer(GrGLuint id) : INHERITED(id), fDataPtr(nullptr), fSize(0), fMapped(false) {}
-    ~Buffer() { delete[] fDataPtr; }
-
-    void allocate(GrGLsizeiptr size, const GrGLchar* dataPtr) {
-        if (fDataPtr) {
-            SkASSERT(0 != fSize);
-            delete[] fDataPtr;
-        }
-
-        fSize = size;
-        fDataPtr = new char[size];
-    }
-
-    GrGLchar* dataPtr()          { return fDataPtr; }
-    GrGLsizeiptr size() const    { return fSize; }
-
-    void setMapped(bool mapped)  { fMapped = mapped; }
-    bool mapped() const          { return fMapped; }
-
-private:
-    GrGLchar*    fDataPtr;
-    GrGLsizeiptr fSize;         // size in bytes
-    bool         fMapped;
-
-    typedef GLObject INHERITED;
-};
-
-class FramebufferAttachment : public GLObject {
-public:
-    int numSamples() const { return fNumSamples; }
-
-protected:
-    FramebufferAttachment(int id) : INHERITED(id), fNumSamples(1) {}
-
-    int fNumSamples;
-
-    typedef GLObject INHERITED;
-};
-
-class Renderbuffer : public FramebufferAttachment {
-public:
-    Renderbuffer(int id) : INHERITED(id) {}
-    void setNumSamples(int numSamples) { fNumSamples = numSamples; }
-
-private:
-    typedef FramebufferAttachment INHERITED;
-};
-
-class Texture : public FramebufferAttachment {
-public:
-    Texture() : INHERITED(1) {}
-
-private:
-    typedef FramebufferAttachment INHERITED;
-};
-
-class Framebuffer : public GLObject {
-public:
-    Framebuffer(int id) : INHERITED(id) {}
-
-    void setAttachment(GrGLenum attachmentPoint, const FramebufferAttachment* attachment) {
-        switch (attachmentPoint) {
-            default:
-                SK_ABORT("Invalid framebuffer attachment.");
-                break;
-            case GR_GL_STENCIL_ATTACHMENT:
-                fAttachments[(int)AttachmentPoint::kStencil].reset(SkRef(attachment));
-                break;
-            case GR_GL_DEPTH_ATTACHMENT:
-                fAttachments[(int)AttachmentPoint::kDepth].reset(SkRef(attachment));
-                break;
-            case GR_GL_COLOR_ATTACHMENT0:
-                fAttachments[(int)AttachmentPoint::kColor].reset(SkRef(attachment));
-                break;
-        }
-    }
-
-    void notifyAttachmentDeleteWhileBound(const FramebufferAttachment* deleted) {
-        for (auto& attachment : fAttachments) {
-            if (attachment.get() == deleted) {
-                attachment.reset(nullptr);
-            }
-        }
-    }
-
-    int numSamples() const {
-        int numSamples = 0;
-        for (auto& attachment : fAttachments) {
-            if (!attachment) {
-                continue;
-            }
-            if (numSamples) {
-                GrAlwaysAssert(attachment->numSamples() == numSamples);
-                continue;
-            }
-            numSamples = attachment->numSamples();
-        }
-        GrAlwaysAssert(numSamples);
-        return numSamples;
-    }
-
-private:
-    enum AttachmentPoint {
-        kStencil,
-        kDepth,
-        kColor
-    };
-    constexpr int static kNumAttachmentPoints = 1 + (int)AttachmentPoint::kColor;
-
-    sk_sp<const FramebufferAttachment> fAttachments[kNumAttachmentPoints];
-
-    typedef GLObject INHERITED;
-};
-
-/** Null interface implementation */
-class NullInterface : public GrGLTestInterface {
-public:
-    NullInterface(bool enableNVPR)
-        : fCurrDrawFramebuffer(0)
-        , fCurrReadFramebuffer(0)
-        , fCurrRenderbuffer(0)
-        , fCurrProgramID(0)
-        , fCurrShaderID(0)
-        , fCurrGenericID(0)
-        , fCurrUniformLocation(0)
-        , fCurrPathID(0) {
-        memset(fBoundBuffers, 0, sizeof(fBoundBuffers));
-        fAdvertisedExtensions.push_back("GL_ARB_framebuffer_object");
-        fAdvertisedExtensions.push_back("GL_ARB_blend_func_extended");
-        fAdvertisedExtensions.push_back("GL_ARB_timer_query");
-        fAdvertisedExtensions.push_back("GL_ARB_draw_buffers");
-        fAdvertisedExtensions.push_back("GL_ARB_occlusion_query");
-        fAdvertisedExtensions.push_back("GL_EXT_stencil_wrap");
-        if (enableNVPR) {
-            fAdvertisedExtensions.push_back("GL_NV_path_rendering");
-            fAdvertisedExtensions.push_back("GL_ARB_program_interface_query");
-        }
-        fAdvertisedExtensions.push_back(nullptr);
-
-        this->init(kGL_GrGLStandard);
-    }
-
-    GrGLenum checkFramebufferStatus(GrGLenum target) override {
-        return GR_GL_FRAMEBUFFER_COMPLETE;
-    }
-
-    GrGLvoid genBuffers(GrGLsizei n, GrGLuint* ids) override {
-        for (int i = 0; i < n; ++i) {
-            Buffer* buffer = fBufferManager.create();
-            ids[i] = buffer->id();
-        }
-    }
-
-    GrGLvoid bufferData(GrGLenum target, GrGLsizeiptr size, const GrGLvoid* data,
-                        GrGLenum usage) override {
-        GrGLuint id = fBoundBuffers[GetBufferIndex(target)];
-        if (id > 0) {
-            Buffer* buffer = fBufferManager.lookUp(id);
-            buffer->allocate(size, (const GrGLchar*) data);
-        }
-    }
-
-    GrGLuint createProgram() override {
-        return ++fCurrProgramID;
-    }
-
-    GrGLuint createShader(GrGLenum type) override {
-        return ++fCurrShaderID;
-    }
-
-    GrGLvoid bindBuffer(GrGLenum target, GrGLuint buffer) override {
-        fBoundBuffers[GetBufferIndex(target)] = buffer;
-    }
-
-   // deleting a bound buffer has the side effect of binding 0
-   GrGLvoid deleteBuffers(GrGLsizei n, const GrGLuint* ids) override {
-        // First potentially unbind the buffers.
-        for (int buffIdx = 0; buffIdx < kNumBufferTargets; ++buffIdx) {
-            if (!fBoundBuffers[buffIdx]) {
-                continue;
-            }
-            for (int i = 0; i < n; ++i) {
-                if (ids[i] == fBoundBuffers[buffIdx]) {
-                    fBoundBuffers[buffIdx] = 0;
-                    break;
-                }
-            }
-        }
-
-        // Then actually "delete" the buffers.
-        for (int i = 0; i < n; ++i) {
-            if (ids[i] > 0) {
-                Buffer* buffer = fBufferManager.lookUp(ids[i]);
-                fBufferManager.free(buffer);
-            }
-        }
-    }
-
-    GrGLvoid genFramebuffers(GrGLsizei n, GrGLuint *framebuffers) override {
-        for (int i = 0; i < n; ++i) {
-            Framebuffer* framebuffer = fFramebufferManager.create();
-            framebuffers[i] = framebuffer->id();
-        }
-    }
-
-    GrGLvoid bindFramebuffer(GrGLenum target, GrGLuint framebuffer) override {
-        SkASSERT(GR_GL_FRAMEBUFFER == target || GR_GL_DRAW_FRAMEBUFFER == target ||
-                 GR_GL_READ_FRAMEBUFFER == target);
-        if (GR_GL_READ_FRAMEBUFFER != target) {
-            fCurrDrawFramebuffer = framebuffer;
-        }
-        if (GR_GL_DRAW_FRAMEBUFFER != target) {
-            fCurrReadFramebuffer = framebuffer;
-        }
-    }
-
-    GrGLvoid deleteFramebuffers(GrGLsizei n, const GrGLuint* ids) override {
-        for (int i = 0; i < n; ++i) {
-            if (ids[i] == fCurrDrawFramebuffer) {
-                fCurrDrawFramebuffer = 0;
-            }
-            if (ids[i] == fCurrReadFramebuffer) {
-                fCurrReadFramebuffer = 0;
-            }
-
-            if (ids[i] > 0) {
-                Framebuffer* framebuffer = fFramebufferManager.lookUp(ids[i]);
-                fFramebufferManager.free(framebuffer);
-            }
-        }
-    }
-
-    GrGLvoid genQueries(GrGLsizei n, GrGLuint *ids) override { this->genGenericIds(n, ids); }
-
-    GrGLvoid genRenderbuffers(GrGLsizei n, GrGLuint *renderbuffers) override {
-        for (int i = 0; i < n; ++i) {
-            Renderbuffer* renderbuffer = fRenderbufferManager.create();
-            renderbuffers[i] = renderbuffer->id();
-        }
-    }
-
-    GrGLvoid bindRenderbuffer(GrGLenum target, GrGLuint renderbuffer) override {
-        SkASSERT(GR_GL_RENDERBUFFER == target);
-        fCurrRenderbuffer = renderbuffer;
-    }
-
-    GrGLvoid deleteRenderbuffers(GrGLsizei n, const GrGLuint* ids) override {
-        for (int i = 0; i < n; ++i) {
-            if (ids[i] <= 0) {
-                continue;
-            }
-            if (ids[i] == fCurrRenderbuffer) {
-                fCurrRenderbuffer = 0;
-            }
-            Renderbuffer* renderbuffer = fRenderbufferManager.lookUp(ids[i]);
-
-            if (fCurrDrawFramebuffer) {
-                Framebuffer* drawFramebuffer = fFramebufferManager.lookUp(fCurrDrawFramebuffer);
-                drawFramebuffer->notifyAttachmentDeleteWhileBound(renderbuffer);
-            }
-            if (fCurrReadFramebuffer) {
-                Framebuffer* readFramebuffer = fFramebufferManager.lookUp(fCurrReadFramebuffer);
-                readFramebuffer->notifyAttachmentDeleteWhileBound(renderbuffer);
-            }
-
-            fRenderbufferManager.free(renderbuffer);
-        }
-    }
-
-    GrGLvoid renderbufferStorage(GrGLenum target, GrGLenum internalformat, GrGLsizei width,
-                                 GrGLsizei height) override {
-        GrAlwaysAssert(GR_GL_RENDERBUFFER == target);
-        GrAlwaysAssert(fCurrRenderbuffer);
-        Renderbuffer* renderbuffer = fRenderbufferManager.lookUp(fCurrRenderbuffer);
-        renderbuffer->setNumSamples(1);
-    }
-
-    GrGLvoid renderbufferStorageMultisample(GrGLenum target, GrGLsizei samples,
-                                            GrGLenum internalformat, GrGLsizei width,
-                                            GrGLsizei height) override {
-        GrAlwaysAssert(GR_GL_RENDERBUFFER == target);
-        GrAlwaysAssert(samples > 0);
-        GrAlwaysAssert(fCurrRenderbuffer);
-        Renderbuffer* renderbuffer = fRenderbufferManager.lookUp(fCurrRenderbuffer);
-        renderbuffer->setNumSamples(samples);
-    }
-
-    GrGLvoid namedRenderbufferStorage(GrGLuint renderbuffer, GrGLenum GrGLinternalformat,
-                                      GrGLsizei width, GrGLsizei height) override {
-        SK_ABORT("Not implemented");
-    }
-
-    GrGLvoid namedRenderbufferStorageMultisample(GrGLuint renderbuffer, GrGLsizei samples,
-                                                 GrGLenum GrGLinternalformat, GrGLsizei width,
-                                                 GrGLsizei height) override {
-        SK_ABORT("Not implemented");
-    }
-
-    GrGLvoid framebufferRenderbuffer(GrGLenum target, GrGLenum attachment,
-                                     GrGLenum renderbuffertarget,
-                                     GrGLuint renderBufferID) override {
-        GrGLuint id = this->getBoundFramebufferID(target);
-        GrAlwaysAssert(id);
-        Framebuffer* framebuffer = fFramebufferManager.lookUp(id);
-
-        GrAlwaysAssert(GR_GL_RENDERBUFFER == renderbuffertarget);
-        if (!renderBufferID && !fCurrRenderbuffer) {
-           return;
-        }
-        GrAlwaysAssert(fCurrRenderbuffer);
-        Renderbuffer* renderbuffer = fRenderbufferManager.lookUp(fCurrRenderbuffer);
-
-        framebuffer->setAttachment(attachment, renderbuffer);
-    }
-
-    GrGLvoid namedFramebufferRenderbuffer(GrGLuint framebuffer, GrGLenum attachment,
-                                          GrGLenum renderbuffertarget,
-                                          GrGLuint renderbuffer) override {
-        SK_ABORT("Not implemented");
-    }
-
-    GrGLvoid genSamplers(GrGLsizei n, GrGLuint* samplers) override {
-        this->genGenericIds(n, samplers);
-    }
-
-    GrGLvoid genTextures(GrGLsizei n, GrGLuint *textures) override {
-        this->genGenericIds(n, textures);
-    }
-
-    GrGLvoid framebufferTexture2D(GrGLenum target, GrGLenum attachment, GrGLenum textarget,
-                                  GrGLuint textureID, GrGLint level) override {
-        GrGLuint id = this->getBoundFramebufferID(target);
-        GrAlwaysAssert(id);
-        Framebuffer* framebuffer = fFramebufferManager.lookUp(id);
-        framebuffer->setAttachment(attachment, this->getSingleTextureObject());
-    }
-
-    GrGLvoid framebufferTexture2DMultisample(GrGLenum target, GrGLenum attachment,
-                                             GrGLenum textarget, GrGLuint texture, GrGLint level,
-                                             GrGLsizei samples) override {
-        SK_ABORT("Not implemented");
-    }
-
-    GrGLvoid namedFramebufferTexture1D(GrGLuint framebuffer, GrGLenum attachment,
-                                       GrGLenum textarget, GrGLuint texture,
-                                       GrGLint level) override {
-        SK_ABORT("Not implemented");
-    }
-
-    GrGLvoid namedFramebufferTexture2D(GrGLuint framebuffer, GrGLenum attachment,
-                                       GrGLenum textarget, GrGLuint texture,
-                                       GrGLint level) override {
-        SK_ABORT("Not implemented");
-    }
-
-    GrGLvoid namedFramebufferTexture3D(GrGLuint framebuffer, GrGLenum attachment,
-                                       GrGLenum textarget, GrGLuint texture, GrGLint level,
-                                       GrGLint zoffset) override {
-        SK_ABORT("Not implemented");
-    }
-
-    GrGLvoid genVertexArrays(GrGLsizei n, GrGLuint *arrays) override {
-        this->genGenericIds(n, arrays);
-    }
-
-    GrGLenum getError() override { return GR_GL_NO_ERROR; }
-
-    GrGLvoid getIntegerv(GrGLenum pname, GrGLint* params) override {
-        // TODO: remove from Ganesh the #defines for gets we don't use.
-        // We would like to minimize gets overall due to performance issues
-        switch (pname) {
-            case GR_GL_CONTEXT_PROFILE_MASK:
-                *params = GR_GL_CONTEXT_COMPATIBILITY_PROFILE_BIT;
-                break;
-            case GR_GL_STENCIL_BITS:
-                *params = 8;
-                break;
-            case GR_GL_SAMPLES: {
-                GrAlwaysAssert(fCurrDrawFramebuffer);
-                Framebuffer* framebuffer = fFramebufferManager.lookUp(fCurrDrawFramebuffer);
-                *params = framebuffer->numSamples();
-                break;
-            }
-            case GR_GL_FRAMEBUFFER_BINDING:
-                *params = 0;
-                break;
-            case GR_GL_VIEWPORT:
-                params[0] = 0;
-                params[1] = 0;
-                params[2] = 800;
-                params[3] = 600;
-                break;
-            case GR_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS:
-            case GR_GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS:
-            case GR_GL_MAX_TEXTURE_IMAGE_UNITS:
-            case GR_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS:
-                *params = 8;
-                break;
-            case GR_GL_MAX_TEXTURE_COORDS:
-                *params = 8;
-                break;
-            case GR_GL_MAX_VERTEX_UNIFORM_VECTORS:
-                *params = kDefaultMaxVertexUniformVectors;
-                break;
-            case GR_GL_MAX_FRAGMENT_UNIFORM_VECTORS:
-                *params = kDefaultMaxFragmentUniformVectors;
-                break;
-            case GR_GL_MAX_FRAGMENT_UNIFORM_COMPONENTS:
-                *params = 16 * 4;
-                break;
-            case GR_GL_NUM_COMPRESSED_TEXTURE_FORMATS:
-                *params = 0;
-                break;
-            case GR_GL_COMPRESSED_TEXTURE_FORMATS:
-                break;
-            case GR_GL_MAX_TEXTURE_SIZE:
-                *params = 8192;
-                break;
-            case GR_GL_MAX_RENDERBUFFER_SIZE:
-                *params = 8192;
-                break;
-            case GR_GL_MAX_SAMPLES:
-                *params = 32;
-                break;
-            case GR_GL_MAX_VERTEX_ATTRIBS:
-                *params = kDefaultMaxVertexAttribs;
-                break;
-            case GR_GL_MAX_VARYING_VECTORS:
-                *params = kDefaultMaxVaryingVectors;
-                break;
-            case GR_GL_NUM_EXTENSIONS: {
-                GrGLint i = 0;
-                while (fAdvertisedExtensions[i++]);
-                *params = i;
-                break;
-            }
-            default:
-                SK_ABORT("Unexpected pname to GetIntegerv");
-        }
-    }
-
-    GrGLvoid getProgramiv(GrGLuint program, GrGLenum pname, GrGLint* params) override {
-        this->getShaderOrProgramiv(program, pname, params);
-    }
-
-    GrGLvoid getProgramInfoLog(GrGLuint program, GrGLsizei bufsize, GrGLsizei* length,
-                               char* infolog) override {
-        this->getInfoLog(program, bufsize, length, infolog);
-    }
-
-    GrGLvoid getMultisamplefv(GrGLenum pname, GrGLuint index, GrGLfloat* val) override {
-        val[0] = val[1] = 0.5f;
-    }
-
-    GrGLvoid getQueryiv(GrGLenum GLtarget, GrGLenum pname, GrGLint *params) override {
-        switch (pname) {
-            case GR_GL_CURRENT_QUERY:
-                *params = 0;
-                break;
-            case GR_GL_QUERY_COUNTER_BITS:
-                *params = 32;
-                break;
-            default:
-                SK_ABORT("Unexpected pname passed GetQueryiv.");
-        }
-    }
-
-    GrGLvoid getQueryObjecti64v(GrGLuint id, GrGLenum pname, GrGLint64 *params) override {
-        this->queryResult(id, pname, params);
-    }
-
-    GrGLvoid getQueryObjectiv(GrGLuint id, GrGLenum pname, GrGLint *params) override {
-        this->queryResult(id, pname, params);
-    }
-
-    GrGLvoid getQueryObjectui64v(GrGLuint id, GrGLenum pname, GrGLuint64 *params) override {
-        this->queryResult(id, pname, params);
-    }
-
-    GrGLvoid getQueryObjectuiv(GrGLuint id, GrGLenum pname, GrGLuint *params) override {
-        this->queryResult(id, pname, params);
-    }
-
-    GrGLvoid getShaderiv(GrGLuint shader, GrGLenum pname, GrGLint* params) override {
-        this->getShaderOrProgramiv(shader, pname, params);
-    }
-
-    GrGLvoid getShaderInfoLog(GrGLuint shader, GrGLsizei bufsize, GrGLsizei* length,
-                              char* infolog) override {
-        this->getInfoLog(shader, bufsize, length, infolog);
-    }
-
-    const GrGLubyte* getString(GrGLenum name) override {
-        switch (name) {
-            case GR_GL_EXTENSIONS:
-                return CombinedExtensionString();
-            case GR_GL_VERSION:
-                return (const GrGLubyte*)"4.0 Null GL";
-            case GR_GL_SHADING_LANGUAGE_VERSION:
-                return (const GrGLubyte*)"4.20.8 Null GLSL";
-            case GR_GL_VENDOR:
-                return (const GrGLubyte*)"Null Vendor";
-            case GR_GL_RENDERER:
-                return (const GrGLubyte*)"The Null (Non-)Renderer";
-            default:
-                SK_ABORT("Unexpected name passed to GetString");
-                return nullptr;
-        }
-    }
-
-    const GrGLubyte* getStringi(GrGLenum name, GrGLuint i) override {
-        switch (name) {
-            case GR_GL_EXTENSIONS: {
-                GrGLint count;
-                this->getIntegerv(GR_GL_NUM_EXTENSIONS, &count);
-                if ((GrGLint)i <= count) {
-                    return (const GrGLubyte*) fAdvertisedExtensions[i];
-                } else {
-                    return nullptr;
-                }
-            }
-            default:
-                SK_ABORT("Unexpected name passed to GetStringi");
-                return nullptr;
-        }
-    }
-
-    GrGLint getUniformLocation(GrGLuint program, const char* name) override {
-        return ++fCurrUniformLocation;
-    }
-
-    GrGLvoid* mapBufferRange(GrGLenum target, GrGLintptr offset, GrGLsizeiptr length,
-                             GrGLbitfield access) override {
-        GrGLuint id = fBoundBuffers[GetBufferIndex(target)];
-        if (id > 0) {
-            // We just ignore the offset and length here.
-            Buffer* buffer = fBufferManager.lookUp(id);
-            SkASSERT(!buffer->mapped());
-            buffer->setMapped(true);
-            return buffer->dataPtr();
-        }
-        return nullptr;
-    }
-
-    GrGLvoid* mapBuffer(GrGLenum target, GrGLenum access) override {
-        GrGLuint id = fBoundBuffers[GetBufferIndex(target)];
-        if (id > 0) {
-            Buffer* buffer = fBufferManager.lookUp(id);
-            SkASSERT(!buffer->mapped());
-            buffer->setMapped(true);
-            return buffer->dataPtr();
-        }
-
-        SkASSERT(false);
-        return nullptr;            // no buffer bound to target
-    }
-
-    GrGLboolean unmapBuffer(GrGLenum target) override {
-        GrGLuint id = fBoundBuffers[GetBufferIndex(target)];
-        if (id > 0) {
-            Buffer* buffer = fBufferManager.lookUp(id);
-            SkASSERT(buffer->mapped());
-            buffer->setMapped(false);
-            return GR_GL_TRUE;
-        }
-
-        GrAlwaysAssert(false);
-        return GR_GL_FALSE; // GR_GL_INVALID_OPERATION;
-    }
-
-    GrGLvoid getBufferParameteriv(GrGLenum target, GrGLenum pname, GrGLint* params) override {
-        switch (pname) {
-            case GR_GL_BUFFER_MAPPED: {
-                *params = GR_GL_FALSE;
-                GrGLuint id = fBoundBuffers[GetBufferIndex(target)];
-                if (id > 0) {
-                    Buffer* buffer = fBufferManager.lookUp(id);
-                    if (buffer->mapped()) {
-                        *params = GR_GL_TRUE;
-                    }
-                }
-                break; }
-            default:
-                SK_ABORT("Unexpected pname to GetBufferParamateriv");
-                break;
-        }
-    }
-
-    // NV_path_rendering
-    GrGLuint genPaths(GrGLsizei range) override {
-        return ++fCurrPathID;
-    }
-
-
-private:
-    inline int static GetBufferIndex(GrGLenum glTarget) {
-        switch (glTarget) {
-            default:                           SK_ABORT("Unexpected GL target to GetBufferIndex");
-            case GR_GL_ARRAY_BUFFER:           return 0;
-            case GR_GL_ELEMENT_ARRAY_BUFFER:   return 1;
-            case GR_GL_TEXTURE_BUFFER:         return 2;
-            case GR_GL_DRAW_INDIRECT_BUFFER:   return 3;
-            case GR_GL_PIXEL_PACK_BUFFER:      return 4;
-            case GR_GL_PIXEL_UNPACK_BUFFER:    return 5;
-        }
-    }
-    constexpr int static kNumBufferTargets = 6;
-
-    TGLObjectManager<Buffer>         fBufferManager;
-    GrGLuint                         fBoundBuffers[kNumBufferTargets];
-    TGLObjectManager<Framebuffer>    fFramebufferManager;
-    GrGLuint                         fCurrDrawFramebuffer;
-    GrGLuint                         fCurrReadFramebuffer;
-    TGLObjectManager<Renderbuffer>   fRenderbufferManager;
-    GrGLuint                         fCurrRenderbuffer;
-    GrGLuint                         fCurrProgramID;
-    GrGLuint                         fCurrShaderID;
-    GrGLuint                         fCurrGenericID;
-    GrGLuint                         fCurrUniformLocation;
-    GrGLuint                         fCurrPathID;
-    sk_sp<const Texture>             fSingleTextureObject;
-    SkTArray<const char*>            fAdvertisedExtensions;
-
-    // the OpenGLES 2.0 spec says this must be >= 128
-    static const GrGLint kDefaultMaxVertexUniformVectors = 128;
-
-    // the OpenGLES 2.0 spec says this must be >=16
-    static const GrGLint kDefaultMaxFragmentUniformVectors = 16;
-
-    // the OpenGLES 2.0 spec says this must be >= 8
-    static const GrGLint kDefaultMaxVertexAttribs = 8;
-
-    // the OpenGLES 2.0 spec says this must be >= 8
-    static const GrGLint kDefaultMaxVaryingVectors = 8;
-
-    GrGLuint getBoundFramebufferID(GrGLenum target) {
-        switch (target) {
-            case GR_GL_FRAMEBUFFER:
-            case GR_GL_DRAW_FRAMEBUFFER:
-                return fCurrDrawFramebuffer;
-            case GR_GL_READ_FRAMEBUFFER:
-                return fCurrReadFramebuffer;
-            default:
-                SK_ABORT("Invalid framebuffer target.");
-                return 0;
-        }
-    }
-
-    const Texture* getSingleTextureObject() {
-        // We currently only use FramebufferAttachment objects for a sample count, and all textures
-        // in Skia have one sample, so there is no need as of yet to track individual textures. This
-        // also works around a bug in chromium's cc_unittests where they send us texture IDs that
-        // were generated by cc::TestGLES2Interface.
-        if (!fSingleTextureObject) {
-            fSingleTextureObject.reset(new Texture);
-        }
-        return fSingleTextureObject.get();
-    }
-
-    const GrGLubyte* CombinedExtensionString() {
-        static SkString gExtString;
-        static SkMutex gMutex;
-        gMutex.acquire();
-        if (0 == gExtString.size()) {
-            int i = 0;
-            while (fAdvertisedExtensions[i]) {
-                if (i > 0) {
-                    gExtString.append(" ");
-                }
-                gExtString.append(fAdvertisedExtensions[i]);
-                ++i;
-            }
-        }
-        gMutex.release();
-        return (const GrGLubyte*) gExtString.c_str();
-    }
-
-    GrGLvoid genGenericIds(GrGLsizei n, GrGLuint* ids) {
-        for (int i = 0; i < n; ++i) {
-            ids[i] = ++fCurrGenericID;
-        }
-    }
-
-    GrGLvoid getInfoLog(GrGLuint object, GrGLsizei bufsize, GrGLsizei* length,
-                        char* infolog) {
-        if (length) {
-            *length = 0;
-        }
-        if (bufsize > 0) {
-            *infolog = 0;
-        }
-    }
-
-    GrGLvoid getShaderOrProgramiv(GrGLuint object,  GrGLenum pname, GrGLint* params) {
-        switch (pname) {
-            case GR_GL_LINK_STATUS:  // fallthru
-            case GR_GL_COMPILE_STATUS:
-                *params = GR_GL_TRUE;
-                break;
-            case GR_GL_INFO_LOG_LENGTH: // fallthru
-            case GL_PROGRAM_BINARY_LENGTH:
-                *params = 0;
-                break;
-                // we don't expect any other pnames
-            default:
-                SK_ABORT("Unexpected pname to GetProgramiv");
-                break;
-        }
-    }
-
-    template <typename T>
-    void queryResult(GrGLenum GLtarget, GrGLenum pname, T *params) {
-        switch (pname) {
-            case GR_GL_QUERY_RESULT_AVAILABLE:
-                *params = GR_GL_TRUE;
-                break;
-            case GR_GL_QUERY_RESULT:
-                *params = 0;
-                break;
-            default:
-                SK_ABORT("Unexpected pname passed to GetQueryObject.");
-                break;
-        }
-    }
-
-    typedef GrGLTestInterface INHERITED;
-};
-
-}  // anonymous namespace
-
-const GrGLInterface* GrGLCreateNullInterface(bool enableNVPR) { return new NullInterface(enableNVPR); }
diff --git a/src/gpu/gl/GrGLDefines.h b/src/gpu/gl/GrGLDefines.h
index 995dbf3..28b3798 100644
--- a/src/gpu/gl/GrGLDefines.h
+++ b/src/gpu/gl/GrGLDefines.h
@@ -989,14 +989,6 @@
 /*  ARM specific define for MSAA support on framebuffer fetch */
 #define GR_GL_FETCH_PER_SAMPLE                              0x8F65
 
-/* GL_EXT_raster_multisample */
-#define GR_GL_RASTER_MULTISAMPLE                            0x9327
-#define GR_GL_RASTER_SAMPLES                                0x9328
-#define GR_GL_MAX_RASTER_SAMPLES                            0x9329
-#define GR_GL_RASTER_FIXED_SAMPLE_LOCATIONS                 0x932A
-#define GR_GL_MULTISAMPLE_RASTERIZATION_ALLOWED             0x932B
-#define GR_GL_EFFECTIVE_RASTER_SAMPLES                      0x932C
-
 /* GL_KHR_debug */
 #define GR_GL_DEBUG_OUTPUT                                  0x92E0
 #define GR_GL_DEBUG_OUTPUT_SYNCHRONOUS                      0x8242
@@ -1038,9 +1030,11 @@
 
 /* GL_OES_EGL_image_external */
 #define GR_GL_TEXTURE_EXTERNAL                              0x8D65
+#define GR_GL_TEXTURE_BINDING_EXTERNAL                      0x8D67
 
 /* GL_ARB_texture_rectangle or GL_ANGLE_texture_rectangle */
 #define GR_GL_TEXTURE_RECTANGLE                             0x84F5
+#define GR_GL_TEXTURE_BINDING_RECTANGLE                     0x84F6
 
 /* GL_EXT_window_rectangles */
 #define GR_GL_MAX_WINDOW_RECTANGLES                         0x8f14
diff --git a/src/gpu/gl/GrGLExtensions.cpp b/src/gpu/gl/GrGLExtensions.cpp
index d0b7162..bf5a321 100644
--- a/src/gpu/gl/GrGLExtensions.cpp
+++ b/src/gpu/gl/GrGLExtensions.cpp
@@ -78,14 +78,21 @@
         return false;
     }
 
-    // glGetStringi and indexed extensions were added in version 3.0 of desktop GL and ES.
     const GrGLubyte* verString = getString(GR_GL_VERSION);
     GrGLVersion version = GrGLGetVersionFromString((const char*) verString);
     if (GR_GL_INVALID_VER == version) {
         return false;
     }
 
-    bool indexed = version >= GR_GL_VER(3, 0);
+    bool indexed = false;
+    if (GR_IS_GR_GL(standard) || GR_IS_GR_GL_ES(standard)) {
+        // glGetStringi and indexed extensions were added in version 3.0 of desktop GL and ES.
+        indexed = version >= GR_GL_VER(3, 0);
+    } else if (GR_IS_GR_WEBGL(standard)) {
+        // WebGL (1.0 or 2.0) doesn't natively support glGetStringi, but enscripten adds it in
+        // https://github.com/emscripten-core/emscripten/issues/3472
+        indexed = version >= GR_GL_VER(2, 0);
+    }
 
     if (indexed) {
         if (!getStringi || !getIntegerv) {
diff --git a/src/gpu/gl/GrGLGLSL.cpp b/src/gpu/gl/GrGLGLSL.cpp
index 4050d36..6aeaa63 100644
--- a/src/gpu/gl/GrGLGLSL.cpp
+++ b/src/gpu/gl/GrGLGLSL.cpp
@@ -14,39 +14,37 @@
     if (GR_GLSL_INVALID_VER == ver) {
         return false;
     }
-    switch (gl->fStandard) {
-        case kGL_GrGLStandard:
-            SkASSERT(ver >= GR_GLSL_VER(1,10));
-            if (ver >= GR_GLSL_VER(4,20)) {
-                *generation = k420_GrGLSLGeneration;
-            } else if (ver >= GR_GLSL_VER(4,00)) {
-                *generation = k400_GrGLSLGeneration;
-            } else if (ver >= GR_GLSL_VER(3,30)) {
-                *generation = k330_GrGLSLGeneration;
-            } else if (ver >= GR_GLSL_VER(1,50)) {
-                *generation = k150_GrGLSLGeneration;
-            } else if (ver >= GR_GLSL_VER(1,40)) {
-                *generation = k140_GrGLSLGeneration;
-            } else if (ver >= GR_GLSL_VER(1,30)) {
-                *generation = k130_GrGLSLGeneration;
-            } else {
-                *generation = k110_GrGLSLGeneration;
-            }
-            return true;
-        case kGLES_GrGLStandard:
-            SkASSERT(ver >= GR_GL_VER(1,00));
-            if (ver >= GR_GLSL_VER(3,20)) {
-                *generation = k320es_GrGLSLGeneration;
-            } else if (ver >= GR_GLSL_VER(3,10)) {
-                *generation = k310es_GrGLSLGeneration;
-            } else if (ver >= GR_GLSL_VER(3,00)) {
-                *generation = k330_GrGLSLGeneration;
-            } else {
-                *generation = k110_GrGLSLGeneration;
-            }
-            return true;
-        default:
-            SK_ABORT("Unknown GL Standard");
-            return false;
+    if (GR_IS_GR_GL(gl->fStandard)) {
+        SkASSERT(ver >= GR_GLSL_VER(1,10));
+        if (ver >= GR_GLSL_VER(4,20)) {
+            *generation = k420_GrGLSLGeneration;
+        } else if (ver >= GR_GLSL_VER(4,00)) {
+            *generation = k400_GrGLSLGeneration;
+        } else if (ver >= GR_GLSL_VER(3,30)) {
+            *generation = k330_GrGLSLGeneration;
+        } else if (ver >= GR_GLSL_VER(1,50)) {
+            *generation = k150_GrGLSLGeneration;
+        } else if (ver >= GR_GLSL_VER(1,40)) {
+            *generation = k140_GrGLSLGeneration;
+        } else if (ver >= GR_GLSL_VER(1,30)) {
+            *generation = k130_GrGLSLGeneration;
+        } else {
+            *generation = k110_GrGLSLGeneration;
+        }
+        return true;
+    } else if (GR_IS_GR_GL_ES(gl->fStandard) || GR_IS_GR_WEBGL(gl->fStandard)) {
+        SkASSERT(ver >= GR_GL_VER(1,00));
+        if (ver >= GR_GLSL_VER(3,20)) {
+            *generation = k320es_GrGLSLGeneration;
+        } else if (ver >= GR_GLSL_VER(3,10)) {
+            *generation = k310es_GrGLSLGeneration;
+        } else if (ver >= GR_GLSL_VER(3,00)) {
+            *generation = k330_GrGLSLGeneration;
+        } else {
+            *generation = k110_GrGLSLGeneration;
+        }
+        return true;
     }
+    SK_ABORT("Unknown GL Standard");
+    return false;
 }
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index a22df4f..506d06a 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -8,6 +8,7 @@
 #include "GrGLGpu.h"
 #include "GrBackendSemaphore.h"
 #include "GrBackendSurface.h"
+#include "GrCpuBuffer.h"
 #include "GrFixedClip.h"
 #include "GrGLBuffer.h"
 #include "GrGLGpuCommandBuffer.h"
@@ -182,6 +183,50 @@
     GR_STATIC_ASSERT(kGrBlendCoeffCnt == SK_ARRAY_COUNT(gXfermodeCoeff2Blend));
 }
 
+//////////////////////////////////////////////////////////////////////////////
+
+static int gl_target_to_binding_index(GrGLenum target) {
+    switch (target) {
+        case GR_GL_TEXTURE_2D:
+            return 0;
+        case GR_GL_TEXTURE_RECTANGLE:
+            return 1;
+        case GR_GL_TEXTURE_EXTERNAL:
+            return 2;
+    }
+    SK_ABORT("Unexpected GL texture target.");
+    return 0;
+}
+
+GrGpuResource::UniqueID GrGLGpu::TextureUnitBindings::boundID(GrGLenum target) const {
+    return fTargetBindings[gl_target_to_binding_index(target)].fBoundResourceID;
+}
+
+bool GrGLGpu::TextureUnitBindings::hasBeenModified(GrGLenum target) const {
+    return fTargetBindings[gl_target_to_binding_index(target)].fHasBeenModified;
+}
+
+void GrGLGpu::TextureUnitBindings::setBoundID(GrGLenum target, GrGpuResource::UniqueID resourceID) {
+    int targetIndex = gl_target_to_binding_index(target);
+    fTargetBindings[targetIndex].fBoundResourceID = resourceID;
+    fTargetBindings[targetIndex].fHasBeenModified = true;
+}
+
+void GrGLGpu::TextureUnitBindings::invalidateForScratchUse(GrGLenum target) {
+    this->setBoundID(target, GrGpuResource::UniqueID());
+}
+
+void GrGLGpu::TextureUnitBindings::invalidateAllTargets(bool markUnmodified) {
+    for (auto& targetBinding : fTargetBindings) {
+        targetBinding.fBoundResourceID.makeInvalid();
+        if (markUnmodified) {
+            targetBinding.fHasBeenModified = false;
+        }
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
 static GrGLenum filter_to_gl_mag_filter(GrSamplerState::Filter filter) {
     switch (filter) {
         case GrSamplerState::Filter::kNearest: return GR_GL_NEAREST;
@@ -341,7 +386,7 @@
     GrGLClearErr(this->glInterface());
     fCaps = sk_ref_sp(fGLContext->caps());
 
-    fHWBoundTextureUniqueIDs.reset(this->caps()->shaderCaps()->maxFragmentSamplers());
+    fHWTextureUnitBindings.reset(this->numTextureUnits());
 
     this->hwBufferState(GrGpuBufferType::kVertex)->fGLTarget = GR_GL_ARRAY_BUFFER;
     this->hwBufferState(GrGpuBufferType::kIndex)->fGLTarget = GR_GL_ELEMENT_ARRAY_BUFFER;
@@ -485,7 +530,7 @@
         this->hwBufferState(GrGpuBufferType::kXferCpuToGpu)->invalidate();
         this->hwBufferState(GrGpuBufferType::kXferGpuToCpu)->invalidate();
 
-        if (kGL_GrGLStandard == this->glStandard()) {
+        if (GR_IS_GR_GL(this->glStandard())) {
 #ifndef USE_NSIGHT
             // Desktop-only state that we never change
             if (!this->glCaps().isCoreProfile()) {
@@ -516,7 +561,7 @@
 
         }
 
-        if (kGLES_GrGLStandard == this->glStandard() &&
+        if (GR_IS_GR_GL_ES(this->glStandard()) &&
             this->glCaps().fbFetchRequiresEnablePerSample()) {
             // The arm extension requires specifically enabling MSAA fetching per sample.
             // On some devices this may have a perf hit.  Also multiple render targets are disabled
@@ -534,11 +579,6 @@
         fMSAAEnabled = kUnknown_TriState;
 
         if (this->caps()->usesMixedSamples()) {
-            if (0 != this->caps()->maxRasterSamples()) {
-                fHWRasterMultisampleEnabled = kUnknown_TriState;
-                fHWNumRasterSamples = 0;
-            }
-
             // The skia blend modes all use premultiplied alpha and therefore expect RGBA coverage
             // modulation. This state has no effect when not rendering to a mixed sampled target.
             GL_CALL(CoverageModulation(GR_GL_RGBA));
@@ -549,8 +589,8 @@
     fLastPrimitiveType = static_cast<GrPrimitiveType>(-1);
 
     if (resetBits & kTextureBinding_GrGLBackendState) {
-        for (int s = 0; s < fHWBoundTextureUniqueIDs.count(); ++s) {
-            fHWBoundTextureUniqueIDs[s].makeInvalid();
+        for (int s = 0; s < this->numTextureUnits(); ++s) {
+            fHWTextureUnitBindings[s].invalidateAllTargets(false);
         }
         if (fSamplerObjectCache) {
             fSamplerObjectCache->invalidateBindings();
@@ -789,8 +829,7 @@
         return false;
     }
 
-    this->setScratchTextureUnit();
-    GL_CALL(BindTexture(glTex->target(), glTex->textureID()));
+    this->bindTextureToScratchUnit(glTex->target(), glTex->textureID());
 
     // No sRGB transformation occurs in uploadTexData. We choose to make the src config match the
     // srgb-ness of the surface to avoid issues in ES2 where internal/external formats must match.
@@ -823,9 +862,11 @@
         case kAlpha_half_GrPixelConfig:
         case kAlpha_half_as_Red_GrPixelConfig:
         case kRGBA_half_GrPixelConfig:
+        case kRGBA_half_Clamped_GrPixelConfig:
             return 2;
         case kRGBA_8888_GrPixelConfig:
         case kRGB_888_GrPixelConfig:  // We're really talking about GrColorType::kRGB_888x here.
+        case kRGB_888X_GrPixelConfig:
         case kBGRA_8888_GrPixelConfig:
         case kSRGBA_8888_GrPixelConfig:
         case kSBGRA_8888_GrPixelConfig:
@@ -841,9 +882,9 @@
     return 0;
 }
 
-bool GrGLGpu::onTransferPixels(GrTexture* texture, int left, int top, int width, int height,
-                               GrColorType bufferColorType, GrBuffer* transferBuffer, size_t offset,
-                               size_t rowBytes) {
+bool GrGLGpu::onTransferPixelsTo(GrTexture* texture, int left, int top, int width, int height,
+                                 GrColorType bufferColorType, GrGpuBuffer* transferBuffer,
+                                 size_t offset, size_t rowBytes) {
     GrGLTexture* glTex = static_cast<GrGLTexture*>(texture);
     GrPixelConfig texConfig = glTex->config();
     SkASSERT(this->caps()->isConfigTexturable(texConfig));
@@ -860,11 +901,10 @@
         return false;
     }
 
-    this->setScratchTextureUnit();
-    GL_CALL(BindTexture(glTex->target(), glTex->textureID()));
+    this->bindTextureToScratchUnit(glTex->target(), glTex->textureID());
 
     SkASSERT(!transferBuffer->isMapped());
-    SkASSERT(!transferBuffer->isCPUBacked());
+    SkASSERT(!transferBuffer->isCpuBuffer());
     const GrGLBuffer* glBuffer = static_cast<const GrGLBuffer*>(transferBuffer);
     this->bindBuffer(GrGpuBufferType::kXferCpuToGpu, glBuffer);
 
@@ -919,6 +959,33 @@
     return true;
 }
 
+size_t GrGLGpu::onTransferPixelsFrom(GrSurface* surface, int left, int top, int width, int height,
+                                     GrColorType dstColorType, GrGpuBuffer* transferBuffer,
+                                     size_t offset) {
+    size_t offsetAlignment;
+    size_t rowBytes;
+    if (!this->glCaps().transferFromBufferRequirements(dstColorType, width, &rowBytes,
+                                                       &offsetAlignment)) {
+        return false;
+    }
+    if (offset % offsetAlignment) {
+        return false;
+    }
+    if (offset + rowBytes * height > transferBuffer->size()) {
+        return false;
+    }
+
+    auto* glBuffer = static_cast<GrGLBuffer*>(transferBuffer);
+    this->bindBuffer(GrGpuBufferType::kXferGpuToCpu, glBuffer);
+
+    auto offsetAsPtr = reinterpret_cast<void*>(offset);
+    if (this->readOrTransferPixelsFrom(surface, left, top, width, height, dstColorType, offsetAsPtr,
+                                       rowBytes)) {
+        return rowBytes;
+    }
+    return 0;
+}
+
 /**
  * Creates storage space for the texture and fills it with texels.
  *
@@ -947,15 +1014,7 @@
                                           int baseWidth, int baseHeight) {
     CLEAR_ERROR_BEFORE_ALLOC(&interface);
 
-    bool useTexStorage = caps.isConfigTexSupportEnabled(config);
-    // We can only use TexStorage if we know we will not later change the storage requirements.
-    // This means if we may later want to add mipmaps, we cannot use TexStorage.
-    // Right now, we cannot know if we will later add mipmaps or not.
-    // The only time we can use TexStorage is when we already have the
-    // mipmaps.
-    useTexStorage &= mipLevelCount > 1;
-
-    if (useTexStorage) {
+    if (caps.isConfigTexSupportEnabled(config)) {
         // We never resize or change formats of textures.
         GL_ALLOC_CALL(&interface,
                       TexStorage2D(target, SkTMax(mipLevelCount, 1), internalFormatForTexStorage,
@@ -1357,17 +1416,17 @@
         return false;
     }
 
-    if (mipMapsStatus && mipLevelCount <= 1) {
-        *mipMapsStatus = GrMipMapsStatus::kNotAllocated;
-    } else {
-        *mipMapsStatus = GrMipMapsStatus::kValid;
+    if (mipMapsStatus) {
+        if (mipLevelCount <= 1) {
+            *mipMapsStatus = GrMipMapsStatus::kNotAllocated;
+        } else {
+            *mipMapsStatus = GrMipMapsStatus::kValid;
+        }
     }
 
     return allocate_and_populate_compressed_texture(texConfig, *interface, caps, target,
                                                     internalFormat, texels, mipLevelCount,
                                                     texWidth, texHeight);
-
-    return true;
 }
 
 static bool renderbuffer_storage_msaa(const GrGLContext& ctx,
@@ -1648,8 +1707,7 @@
         // Create color texture
         GrGLuint colorID = 0;
         GL_CALL(GenTextures(1, &colorID));
-        this->setScratchTextureUnit();
-        GL_CALL(BindTexture(GR_GL_TEXTURE_2D, colorID));
+        this->bindTextureToScratchUnit(GR_GL_TEXTURE_2D, colorID);
         GL_CALL(TexParameteri(GR_GL_TEXTURE_2D,
                               GR_GL_TEXTURE_MAG_FILTER,
                               GR_GL_NEAREST));
@@ -1763,8 +1821,7 @@
         return false;
     }
 
-    this->setScratchTextureUnit();
-    GL_CALL(BindTexture(info->fTarget, info->fID));
+    this->bindTextureToScratchUnit(info->fTarget, info->fID);
 
     if (renderTarget && this->glCaps().textureUsageSupport()) {
         // provides a hint about how this texture will be used
@@ -1847,8 +1904,8 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-sk_sp<GrBuffer> GrGLGpu::onCreateBuffer(size_t size, GrGpuBufferType intendedType,
-                                        GrAccessPattern accessPattern, const void* data) {
+sk_sp<GrGpuBuffer> GrGLGpu::onCreateBuffer(size_t size, GrGpuBufferType intendedType,
+                                           GrAccessPattern accessPattern, const void* data) {
     return GrGLBuffer::Make(this, size, intendedType, accessPattern, data);
 }
 
@@ -2016,7 +2073,7 @@
         this->disableScissor();
     }
     this->flushWindowRectangles(pipeline.getWindowRectsState(), glRT, origin);
-    this->flushHWAAState(glRT, pipeline.isHWAntialiasState(), !stencil.isDisabled());
+    this->flushHWAAState(glRT, pipeline.isHWAntialiasState());
 
     // This must come after textures are flushed because a texture may need
     // to be msaa-resolved (which will modify bound FBO state).
@@ -2063,7 +2120,8 @@
 
     GrGLAttribArrayState* attribState;
     if (indexBuffer) {
-        SkASSERT(indexBuffer && !indexBuffer->isMapped());
+        SkASSERT(indexBuffer->isCpuBuffer() ||
+                 !static_cast<const GrGpuBuffer*>(indexBuffer)->isMapped());
         attribState = fHWVertexArrayState.bindInternalVertexArray(this, indexBuffer);
     } else {
         attribState = fHWVertexArrayState.bindInternalVertexArray(this);
@@ -2073,9 +2131,10 @@
     attribState->enableVertexArrays(this, numAttribs, enablePrimitiveRestart);
 
     if (int vertexStride = fHWProgram->vertexStride()) {
-        SkASSERT(vertexBuffer && !vertexBuffer->isMapped());
-        size_t bufferOffset = vertexBuffer->baseOffset();
-        bufferOffset += baseVertex * static_cast<size_t>(vertexStride);
+        SkASSERT(vertexBuffer);
+        SkASSERT(vertexBuffer->isCpuBuffer() ||
+                 !static_cast<const GrGpuBuffer*>(vertexBuffer)->isMapped());
+        size_t bufferOffset = baseVertex * static_cast<size_t>(vertexStride);
         for (int i = 0; i < fHWProgram->numVertexAttributes(); ++i) {
             const auto& attrib = fHWProgram->vertexAttribute(i);
             static constexpr int kDivisor = 0;
@@ -2084,9 +2143,10 @@
         }
     }
     if (int instanceStride = fHWProgram->instanceStride()) {
-        SkASSERT(instanceBuffer && !instanceBuffer->isMapped());
-        size_t bufferOffset = instanceBuffer->baseOffset();
-        bufferOffset += baseInstance * static_cast<size_t>(instanceStride);
+        SkASSERT(instanceBuffer);
+        SkASSERT(instanceBuffer->isCpuBuffer() ||
+                 !static_cast<const GrGpuBuffer*>(instanceBuffer)->isMapped());
+        size_t bufferOffset = baseInstance * static_cast<size_t>(instanceStride);
         int attribIdx = fHWProgram->numVertexAttributes();
         for (int i = 0; i < fHWProgram->numInstanceAttributes(); ++i, ++attribIdx) {
             const auto& attrib = fHWProgram->instanceAttribute(i);
@@ -2107,13 +2167,14 @@
     }
 
     auto* bufferState = this->hwBufferState(type);
-    if (buffer->isCPUBacked()) {
+    if (buffer->isCpuBuffer()) {
         if (!bufferState->fBufferZeroKnownBound) {
             GL_CALL(BindBuffer(bufferState->fGLTarget, 0));
             bufferState->fBufferZeroKnownBound = true;
             bufferState->fBoundBufferUniqueID.makeInvalid();
         }
-    } else if (buffer->uniqueID() != bufferState->fBoundBufferUniqueID) {
+    } else if (static_cast<const GrGpuBuffer*>(buffer)->uniqueID() !=
+               bufferState->fBoundBufferUniqueID) {
         const GrGLBuffer* glBuffer = static_cast<const GrGLBuffer*>(buffer);
         GL_CALL(BindBuffer(bufferState->fGLTarget, glBuffer->bufferID()));
         bufferState->fBufferZeroKnownBound = false;
@@ -2300,8 +2361,9 @@
     }
 }
 
-bool GrGLGpu::onReadPixels(GrSurface* surface, int left, int top, int width, int height,
-                           GrColorType dstColorType, void* buffer, size_t rowBytes) {
+bool GrGLGpu::readOrTransferPixelsFrom(GrSurface* surface, int left, int top, int width, int height,
+                                       GrColorType dstColorType, void* offsetOrPtr,
+                                       size_t rowBytes) {
     SkASSERT(surface);
 
     GrGLRenderTarget* renderTarget = static_cast<GrGLRenderTarget*>(surface->asRenderTarget());
@@ -2353,21 +2415,11 @@
 
     int bytesPerPixel = GrBytesPerPixel(dstAsConfig);
     size_t tightRowBytes = bytesPerPixel * width;
-
-    size_t readDstRowBytes = tightRowBytes;
-    void* readDst = buffer;
-
     // determine if GL can read using the passed rowBytes or if we need a scratch buffer.
-    SkAutoSMalloc<32 * sizeof(GrColor)> scratch;
     if (rowBytes != tightRowBytes) {
-        if (this->glCaps().packRowLengthSupport() && !(rowBytes % bytesPerPixel)) {
-            GL_CALL(PixelStorei(GR_GL_PACK_ROW_LENGTH,
-                                static_cast<GrGLint>(rowBytes / bytesPerPixel)));
-            readDstRowBytes = rowBytes;
-        } else {
-            scratch.reset(tightRowBytes * height);
-            readDst = scratch.get();
-        }
+        SkASSERT(this->glCaps().packRowLengthSupport());
+        SkASSERT(!(rowBytes % bytesPerPixel));
+        GL_CALL(PixelStorei(GR_GL_PACK_ROW_LENGTH, static_cast<GrGLint>(rowBytes / bytesPerPixel)));
     }
     GL_CALL(PixelStorei(GR_GL_PACK_ALIGNMENT, config_alignment(dstAsConfig)));
 
@@ -2382,9 +2434,8 @@
                                         GR_GL_RENDERBUFFER, 0));
     }
 
-    GL_CALL(ReadPixels(readRect.fLeft, readRect.fBottom,
-                       readRect.fWidth, readRect.fHeight,
-                       externalFormat, externalType, readDst));
+    GL_CALL(ReadPixels(readRect.fLeft, readRect.fBottom, readRect.fWidth, readRect.fHeight,
+                       externalFormat, externalType, offsetOrPtr));
 
     if (reattachStencil) {
         GrGLStencilAttachment* stencilAttachment = static_cast<GrGLStencilAttachment*>(
@@ -2393,11 +2444,45 @@
                                         GR_GL_RENDERBUFFER, stencilAttachment->renderbufferID()));
     }
 
-    if (readDstRowBytes != tightRowBytes) {
+    if (rowBytes != tightRowBytes) {
         SkASSERT(this->glCaps().packRowLengthSupport());
         GL_CALL(PixelStorei(GR_GL_PACK_ROW_LENGTH, 0));
     }
 
+    if (!renderTarget) {
+        this->unbindTextureFBOForPixelOps(GR_GL_FRAMEBUFFER, surface);
+    }
+    return true;
+}
+
+bool GrGLGpu::onReadPixels(GrSurface* surface, int left, int top, int width, int height,
+                           GrColorType dstColorType, void* buffer, size_t rowBytes) {
+    SkASSERT(surface);
+
+    // TODO: Avoid this conversion by making GrGLCaps work with color types.
+    auto dstAsConfig = GrColorTypeToPixelConfig(dstColorType, GrSRGBEncoded::kNo);
+
+    int bytesPerPixel = GrBytesPerPixel(dstAsConfig);
+    size_t tightRowBytes = bytesPerPixel * width;
+
+    size_t readDstRowBytes = tightRowBytes;
+    void* readDst = buffer;
+
+    // determine if GL can read using the passed rowBytes or if we need a scratch buffer.
+    SkAutoSMalloc<32 * sizeof(GrColor)> scratch;
+    if (rowBytes != tightRowBytes) {
+        if (this->glCaps().packRowLengthSupport() && !(rowBytes % bytesPerPixel)) {
+            readDstRowBytes = rowBytes;
+        } else {
+            scratch.reset(tightRowBytes * height);
+            readDst = scratch.get();
+        }
+    }
+    if (!this->readOrTransferPixelsFrom(surface, left, top, width, height, dstColorType, readDst,
+                                        readDstRowBytes)) {
+        return false;
+    }
+
     if (readDst != buffer) {
         SkASSERT(readDst != buffer);
         SkASSERT(rowBytes != tightRowBytes);
@@ -2405,9 +2490,6 @@
         char* dst = reinterpret_cast<char*>(buffer);
         SkRectMemcpy(dst, rowBytes, src, readDstRowBytes, tightRowBytes, height);
     }
-    if (!renderTarget) {
-        this->unbindTextureFBOForPixelOps(GR_GL_FRAMEBUFFER, surface);
-    }
     return true;
 }
 
@@ -2608,21 +2690,29 @@
     fStats.incNumDraws();
 }
 
+static const GrGLvoid* element_ptr(const GrBuffer* indexBuffer, int baseIndex) {
+    size_t baseOffset = baseIndex * sizeof(uint16_t);
+    if (indexBuffer->isCpuBuffer()) {
+        return static_cast<const GrCpuBuffer*>(indexBuffer)->data() + baseOffset;
+    } else {
+        return reinterpret_cast<const GrGLvoid*>(baseOffset);
+    }
+}
+
 void GrGLGpu::sendIndexedMeshToGpu(GrPrimitiveType primitiveType, const GrBuffer* indexBuffer,
                                    int indexCount, int baseIndex, uint16_t minIndexValue,
                                    uint16_t maxIndexValue, const GrBuffer* vertexBuffer,
                                    int baseVertex, GrPrimitiveRestart enablePrimitiveRestart) {
     const GrGLenum glPrimType = gr_primitive_type_to_gl_mode(primitiveType);
-    GrGLvoid* const indices = reinterpret_cast<void*>(indexBuffer->baseOffset() +
-                                                      sizeof(uint16_t) * baseIndex);
+    const GrGLvoid* elementPtr = element_ptr(indexBuffer, baseIndex);
 
     this->setupGeometry(indexBuffer, vertexBuffer, baseVertex, nullptr, 0, enablePrimitiveRestart);
 
     if (this->glCaps().drawRangeElementsSupport()) {
         GL_CALL(DrawRangeElements(glPrimType, minIndexValue, maxIndexValue, indexCount,
-                                  GR_GL_UNSIGNED_SHORT, indices));
+                                  GR_GL_UNSIGNED_SHORT, elementPtr));
     } else {
-        GL_CALL(DrawElements(glPrimType, indexCount, GR_GL_UNSIGNED_SHORT, indices));
+        GL_CALL(DrawElements(glPrimType, indexCount, GR_GL_UNSIGNED_SHORT, elementPtr));
     }
     fStats.incNumDraws();
 }
@@ -2649,13 +2739,12 @@
                                             int instanceCount, int baseInstance,
                                             GrPrimitiveRestart enablePrimitiveRestart) {
     const GrGLenum glPrimType = gr_primitive_type_to_gl_mode(primitiveType);
-    GrGLvoid* indices = reinterpret_cast<void*>(indexBuffer->baseOffset() +
-                                                sizeof(uint16_t) * baseIndex);
+    const GrGLvoid* elementPtr = element_ptr(indexBuffer, baseIndex);
     int maxInstances = this->glCaps().maxInstancesPerDrawWithoutCrashing(instanceCount);
     for (int i = 0; i < instanceCount; i += maxInstances) {
         this->setupGeometry(indexBuffer, vertexBuffer, baseVertex, instanceBuffer, baseInstance + i,
                             enablePrimitiveRestart);
-        GL_CALL(DrawElementsInstanced(glPrimType, indexCount, GR_GL_UNSIGNED_SHORT, indices,
+        GL_CALL(DrawElementsInstanced(glPrimType, indexCount, GR_GL_UNSIGNED_SHORT, elementPtr,
                                       SkTMin(instanceCount - i, maxInstances)));
         fStats.incNumDraws();
     }
@@ -2799,7 +2888,7 @@
     }
 }
 
-void GrGLGpu::flushHWAAState(GrRenderTarget* rt, bool useHWAA, bool stencilEnabled) {
+void GrGLGpu::flushHWAAState(GrRenderTarget* rt, bool useHWAA) {
     // rt is only optional if useHWAA is false.
     SkASSERT(rt || !useHWAA);
     SkASSERT(!useHWAA || rt->isStencilBufferMultisampled());
@@ -2817,32 +2906,6 @@
             }
         }
     }
-
-    if (0 != this->caps()->maxRasterSamples()) {
-        if (useHWAA && GrFSAAType::kMixedSamples == rt->fsaaType() && !stencilEnabled) {
-            // Since stencil is disabled and we want more samples than are in the color buffer, we
-            // need to tell the rasterizer explicitly how many to run.
-            if (kYes_TriState != fHWRasterMultisampleEnabled) {
-                GL_CALL(Enable(GR_GL_RASTER_MULTISAMPLE));
-                fHWRasterMultisampleEnabled = kYes_TriState;
-            }
-            int numStencilSamples = rt->numStencilSamples();
-            // convert to GL's understanding of sample counts where 0 means nonMSAA.
-            numStencilSamples = 1 == numStencilSamples ? 0 : numStencilSamples;
-            if (numStencilSamples != fHWNumRasterSamples) {
-                SkASSERT(numStencilSamples <= this->caps()->maxRasterSamples());
-                GL_CALL(RasterSamples(numStencilSamples, GR_GL_TRUE));
-                fHWNumRasterSamples = numStencilSamples;
-            }
-        } else {
-            if (kNo_TriState != fHWRasterMultisampleEnabled) {
-                GL_CALL(Disable(GR_GL_RASTER_MULTISAMPLE));
-                fHWRasterMultisampleEnabled = kNo_TriState;
-            }
-        }
-    } else {
-        SkASSERT(!useHWAA || GrFSAAType::kMixedSamples != rt->fsaaType() || stencilEnabled);
-    }
 }
 
 void GrGLGpu::flushBlend(const GrXferProcessor::BlendInfo& blendInfo, const GrSwizzle& swizzle) {
@@ -2917,6 +2980,7 @@
             case 'g': glValues[i] = GR_GL_GREEN; break;
             case 'b': glValues[i] = GR_GL_BLUE;  break;
             case 'a': glValues[i] = GR_GL_ALPHA; break;
+            case '1': glValues[i] = GR_GL_ONE;   break;
             default:  SK_ABORT("Unsupported component");
         }
     }
@@ -2945,10 +3009,10 @@
 
     GrGpuResource::UniqueID textureID = texture->uniqueID();
     GrGLenum target = texture->target();
-    if (fHWBoundTextureUniqueIDs[unitIdx] != textureID) {
+    if (fHWTextureUnitBindings[unitIdx].boundID(target) != textureID) {
         this->setTextureUnit(unitIdx);
         GL_CALL(BindTexture(target, texture->textureID()));
-        fHWBoundTextureUniqueIDs[unitIdx] = textureID;
+        fHWTextureUnitBindings[unitIdx].setBoundID(target, textureID);
     }
 
     if (samplerState.filter() == GrSamplerState::Filter::kMipMap) {
@@ -3033,16 +3097,16 @@
             GrGLenum glValues[4];
             get_gl_swizzle_values(swizzle, glValues);
             this->setTextureUnit(unitIdx);
-            if (this->glStandard() == kGLES_GrGLStandard) {
+            if (GR_IS_GR_GL(this->glStandard())) {
+                GR_STATIC_ASSERT(sizeof(glValues[0]) == sizeof(GrGLint));
+                GL_CALL(TexParameteriv(target, GR_GL_TEXTURE_SWIZZLE_RGBA,
+                                       reinterpret_cast<const GrGLint*>(glValues)));
+            } else if (GR_IS_GR_GL_ES(this->glStandard())) {
                 // ES3 added swizzle support but not GL_TEXTURE_SWIZZLE_RGBA.
                 GL_CALL(TexParameteri(target, GR_GL_TEXTURE_SWIZZLE_R, glValues[0]));
                 GL_CALL(TexParameteri(target, GR_GL_TEXTURE_SWIZZLE_G, glValues[1]));
                 GL_CALL(TexParameteri(target, GR_GL_TEXTURE_SWIZZLE_B, glValues[2]));
                 GL_CALL(TexParameteri(target, GR_GL_TEXTURE_SWIZZLE_A, glValues[3]));
-            } else {
-                GR_STATIC_ASSERT(sizeof(glValues[0]) == sizeof(GrGLint));
-                GL_CALL(TexParameteriv(target, GR_GL_TEXTURE_SWIZZLE_RGBA,
-                                       reinterpret_cast<const GrGLint*>(glValues)));
             }
         }
     }
@@ -3064,6 +3128,20 @@
     texture->setCachedParams(samplerParamsToRecord, newNonSamplerParams, this->getResetTimestamp());
 }
 
+void GrGLGpu::onResetTextureBindings() {
+    static constexpr GrGLenum kTargets[] = {GR_GL_TEXTURE_2D, GR_GL_TEXTURE_RECTANGLE,
+                                            GR_GL_TEXTURE_EXTERNAL};
+    for (int i = 0; i < this->numTextureUnits(); ++i) {
+        this->setTextureUnit(i);
+        for (auto target : kTargets) {
+            if (fHWTextureUnitBindings[i].hasBeenModified(target)) {
+                GL_CALL(BindTexture(target, 0));
+            }
+        }
+        fHWTextureUnitBindings[i].invalidateAllTargets(true);
+    }
+}
+
 void GrGLGpu::flushColorWrite(bool writeColor) {
     if (!writeColor) {
         if (kNo_TriState != fHWWriteToColor) {
@@ -3091,23 +3169,24 @@
 }
 
 void GrGLGpu::setTextureUnit(int unit) {
-    SkASSERT(unit >= 0 && unit < fHWBoundTextureUniqueIDs.count());
+    SkASSERT(unit >= 0 && unit < this->numTextureUnits());
     if (unit != fHWActiveTextureUnitIdx) {
         GL_CALL(ActiveTexture(GR_GL_TEXTURE0 + unit));
         fHWActiveTextureUnitIdx = unit;
     }
 }
 
-void GrGLGpu::setScratchTextureUnit() {
+void GrGLGpu::bindTextureToScratchUnit(GrGLenum target, GrGLint textureID) {
     // Bind the last texture unit since it is the least likely to be used by GrGLProgram.
-    int lastUnitIdx = fHWBoundTextureUniqueIDs.count() - 1;
+    int lastUnitIdx = this->numTextureUnits() - 1;
     if (lastUnitIdx != fHWActiveTextureUnitIdx) {
         GL_CALL(ActiveTexture(GR_GL_TEXTURE0 + lastUnitIdx));
         fHWActiveTextureUnitIdx = lastUnitIdx;
     }
-    // clear out the this field so that if a program does use this unit it will rebind the correct
-    // texture.
-    fHWBoundTextureUniqueIDs[lastUnitIdx].makeInvalid();
+    // Clear out the this field so that if a GrGLProgram does use this unit it will rebind the
+    // correct texture.
+    fHWTextureUnitBindings[lastUnitIdx].invalidateForScratchUse(target);
+    GL_CALL(BindTexture(target, textureID));
 }
 
 // Determines whether glBlitFramebuffer could be used between src and dst by onCopySurface.
@@ -3657,7 +3736,7 @@
     blendInfo.reset();
     this->flushBlend(blendInfo, GrSwizzle::RGBA());
     this->flushColorWrite(true);
-    this->flushHWAAState(nullptr, false, false);
+    this->flushHWAAState(nullptr, false);
     this->disableScissor();
     this->disableWindowRectangles();
     this->disableStencil();
@@ -3686,8 +3765,7 @@
     GrGLIRect srcGLRect;
     srcGLRect.setRelativeTo(srcVP, srcRect, srcOrigin);
 
-    this->setScratchTextureUnit();
-    GL_CALL(BindTexture(dstTex->target(), dstTex->textureID()));
+    this->bindTextureToScratchUnit(dstTex->target(), dstTex->textureID());
     GrGLint dstY;
     if (kBottomLeft_GrSurfaceOrigin == dstOrigin) {
         dstY = dst->height() - (dstPoint.fY + srcGLRect.fHeight);
@@ -3773,8 +3851,7 @@
     if (!this->glCaps().doManualMipmapping() ||
         !this->glCaps().canConfigBeFBOColorAttachment(texture->config())) {
         GrGLenum target = glTex->target();
-        this->setScratchTextureUnit();
-        GL_CALL(BindTexture(target, glTex->textureID()));
+        this->bindTextureToScratchUnit(target, glTex->textureID());
         GL_CALL(GenerateMipmap(glTex->target()));
         return true;
     }
@@ -3823,7 +3900,7 @@
     blendInfo.reset();
     this->flushBlend(blendInfo, GrSwizzle::RGBA());
     this->flushColorWrite(true);
-    this->flushHWAAState(nullptr, false, false);
+    this->flushHWAAState(nullptr, false);
     this->disableScissor();
     this->disableWindowRectangles();
     this->disableStencil();
@@ -3882,6 +3959,23 @@
     return true;
 }
 
+void GrGLGpu::querySampleLocations(
+        GrRenderTarget* renderTarget, const GrStencilSettings& stencilSettings,
+        SkTArray<SkPoint>* sampleLocations) {
+    this->flushStencil(stencilSettings);
+    this->flushHWAAState(renderTarget, true);
+    this->flushRenderTarget(static_cast<GrGLRenderTarget*>(renderTarget));
+
+    int effectiveSampleCnt;
+    GR_GL_GetIntegerv(this->glInterface(), GR_GL_SAMPLES, &effectiveSampleCnt);
+    SkASSERT(effectiveSampleCnt >= renderTarget->numStencilSamples());
+
+    sampleLocations->reset(effectiveSampleCnt);
+    for (int i = 0; i < effectiveSampleCnt; ++i) {
+        GL_CALL(GetMultisamplefv(GR_GL_SAMPLE_POSITION, i, &(*sampleLocations)[i].fX));
+    }
+}
+
 void GrGLGpu::xferBarrier(GrRenderTarget* rt, GrXferBarrierType type) {
     SkASSERT(type);
     switch (type) {
@@ -3938,10 +4032,8 @@
     info.fTarget = GR_GL_TEXTURE_2D;
     info.fID = 0;
     GL_CALL(GenTextures(1, &info.fID));
-    GL_CALL(ActiveTexture(GR_GL_TEXTURE0));
+    this->bindTextureToScratchUnit(info.fTarget, info.fID);
     GL_CALL(PixelStorei(GR_GL_UNPACK_ALIGNMENT, 1));
-    GL_CALL(BindTexture(info.fTarget, info.fID));
-    fHWBoundTextureUniqueIDs[0].makeInvalid();
     GL_CALL(TexParameteri(info.fTarget, GR_GL_TEXTURE_MAG_FILTER, GR_GL_NEAREST));
     GL_CALL(TexParameteri(info.fTarget, GR_GL_TEXTURE_MIN_FILTER, GR_GL_NEAREST));
     GL_CALL(TexParameteri(info.fTarget, GR_GL_TEXTURE_WRAP_S, GR_GL_CLAMP_TO_EDGE));
@@ -4113,8 +4205,7 @@
 
     this->bindFramebuffer(GR_GL_FRAMEBUFFER, info.fFBOID);
     if (useTexture) {
-        this->setScratchTextureUnit();
-        GL_CALL(BindTexture(GR_GL_TEXTURE_2D, colorID));
+        this->bindTextureToScratchUnit(GR_GL_TEXTURE_2D, colorID);
         GL_CALL(TexImage2D(GR_GL_TEXTURE_2D, 0, colorBufferFormat, w, h, 0, externalFormat,
                            externalType, nullptr));
         GL_CALL(FramebufferTexture2D(GR_GL_FRAMEBUFFER, GR_GL_COLOR_ATTACHMENT0, GR_GL_TEXTURE_2D,
@@ -4215,11 +4306,15 @@
     return attribState;
 }
 
-void GrGLGpu::onFinishFlush(bool insertedSemaphore) {
+void GrGLGpu::onFinishFlush(GrSurfaceProxy*, SkSurface::BackendSurfaceAccess access,
+                            GrFlushFlags flags, bool insertedSemaphore) {
     // If we inserted semaphores during the flush, we need to call GLFlush.
     if (insertedSemaphore) {
         GL_CALL(Flush());
     }
+    if (flags & kSyncCpu_GrFlushFlag) {
+        GL_CALL(Finish());
+    }
 }
 
 void GrGLGpu::submit(GrGpuCommandBuffer* buffer) {
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index 5167b4a..b6d6609 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -182,13 +182,18 @@
     // GrGpu overrides
     void onResetContext(uint32_t resetBits) override;
 
+    void onResetTextureBindings() override;
+
+    void querySampleLocations(
+            GrRenderTarget*, const GrStencilSettings&, SkTArray<SkPoint>*) override;
+
     void xferBarrier(GrRenderTarget*, GrXferBarrierType) override;
 
     sk_sp<GrTexture> onCreateTexture(const GrSurfaceDesc& desc, SkBudgeted budgeted,
                                      const GrMipLevel texels[], int mipLevelCount) override;
 
-    sk_sp<GrBuffer> onCreateBuffer(size_t size, GrGpuBufferType intendedType, GrAccessPattern,
-                                   const void* data) override;
+    sk_sp<GrGpuBuffer> onCreateBuffer(size_t size, GrGpuBufferType intendedType, GrAccessPattern,
+                                      const void* data) override;
 
     sk_sp<GrTexture> onWrapBackendTexture(const GrBackendTexture&, GrWrapOwnership, GrWrapCacheable,
                                           GrIOType) override;
@@ -232,8 +237,12 @@
     bool onWritePixels(GrSurface*, int left, int top, int width, int height, GrColorType,
                        const GrMipLevel texels[], int mipLevelCount) override;
 
-    bool onTransferPixels(GrTexture*, int left, int top, int width, int height, GrColorType,
-                          GrBuffer* transferBuffer, size_t offset, size_t rowBytes) override;
+    bool onTransferPixelsTo(GrTexture*, int left, int top, int width, int height, GrColorType,
+                            GrGpuBuffer* transferBuffer, size_t offset, size_t rowBytes) override;
+    size_t onTransferPixelsFrom(GrSurface*, int left, int top, int width, int height, GrColorType,
+                                GrGpuBuffer* transferBuffer, size_t offset) override;
+    bool readOrTransferPixelsFrom(GrSurface*, int left, int top, int width, int height, GrColorType,
+                                  void* offsetOrPtr, size_t rowBytes);
 
     // Before calling any variation of TexImage, TexSubImage, etc..., call this to ensure that the
     // PIXEL_UNPACK_BUFFER is unbound.
@@ -284,7 +293,8 @@
 
     void flushBlend(const GrXferProcessor::BlendInfo& blendInfo, const GrSwizzle&);
 
-    void onFinishFlush(bool insertedSemaphores) override;
+    void onFinishFlush(GrSurfaceProxy*, SkSurface::BackendSurfaceAccess access,
+                       GrFlushFlags flags, bool insertedSemaphores) override;
 
     bool copySurfaceAsDraw(GrSurface* dst, GrSurfaceOrigin dstOrigin,
                            GrSurface* src, GrSurfaceOrigin srcOrigin,
@@ -351,9 +361,13 @@
     void flushWindowRectangles(const GrWindowRectsState&, const GrGLRenderTarget*, GrSurfaceOrigin);
     void disableWindowRectangles();
 
-    // sets a texture unit to use for texture operations other than binding a texture to a program.
-    // ensures that such operations don't negatively interact with tracking bound textures.
-    void setScratchTextureUnit();
+    int numTextureUnits() const { return this->caps()->shaderCaps()->maxFragmentSamplers(); }
+
+    // Binds a texture to a target on the "scratch" texture unit to use for texture operations
+    // other than usual draw flow (i.e. a GrGLProgram derived from a GrPipeline used to draw
+    // GrMesh). It ensures that such operations don't negatively interact with draws.
+    // The active texture unit and the binding for 'target' will change.
+    void bindTextureToScratchUnit(GrGLenum target, GrGLint textureID);
 
     // The passed bounds contains the render target's color values that will subsequently be
     // written.
@@ -370,7 +384,7 @@
     void disableStencil();
 
     // rt is used only if useHWAA is true.
-    void flushHWAAState(GrRenderTarget* rt, bool useHWAA, bool stencilEnabled);
+    void flushHWAAState(GrRenderTarget* rt, bool useHWAA);
 
     void flushFramebufferSRGB(bool enable);
 
@@ -591,17 +605,32 @@
     TriState                                fHWWriteToColor;
     GrGpuResource::UniqueID                 fHWBoundRenderTargetUniqueID;
     TriState                                fHWSRGBFramebuffer;
-    SkTArray<GrGpuResource::UniqueID, true> fHWBoundTextureUniqueIDs;
+
+    class TextureUnitBindings {
+    public:
+        TextureUnitBindings() = default;
+        TextureUnitBindings(const TextureUnitBindings&) = delete;
+        TextureUnitBindings& operator=(const TextureUnitBindings&) = delete;
+
+        GrGpuResource::UniqueID boundID(GrGLenum target) const;
+        bool hasBeenModified(GrGLenum target) const;
+        void setBoundID(GrGLenum target, GrGpuResource::UniqueID);
+        void invalidateForScratchUse(GrGLenum target);
+        void invalidateAllTargets(bool markUnmodified);
+
+    private:
+        struct TargetBinding {
+            GrGpuResource::UniqueID fBoundResourceID;
+            bool fHasBeenModified = false;
+        };
+        TargetBinding fTargetBindings[3];
+    };
+    SkAutoTArray<TextureUnitBindings> fHWTextureUnitBindings;
 
     GrGLfloat fHWClearColor[4];
 
     GrGLuint fBoundDrawFramebuffer = 0;
 
-    // EXT_raster_multisample.
-    TriState                                fHWRasterMultisampleEnabled;
-    int                                     fHWNumRasterSamples;
-    ///@}
-
     /** IDs for copy surface program. (3 sampler types) */
     struct {
         GrGLuint    fProgram = 0;
diff --git a/src/gpu/gl/GrGLGpuCommandBuffer.cpp b/src/gpu/gl/GrGLGpuCommandBuffer.cpp
index 502c845..425a6a1 100644
--- a/src/gpu/gl/GrGLGpuCommandBuffer.cpp
+++ b/src/gpu/gl/GrGLGpuCommandBuffer.cpp
@@ -7,6 +7,7 @@
 
 #include "GrGLGpuCommandBuffer.h"
 
+#include "GrContextPriv.h"
 #include "GrFixedClip.h"
 #include "GrRenderTargetPriv.h"
 
diff --git a/src/gpu/gl/GrGLGpuProgramCache.cpp b/src/gpu/gl/GrGLGpuProgramCache.cpp
index ade5b49..2cb5726 100644
--- a/src/gpu/gl/GrGLGpuProgramCache.cpp
+++ b/src/gpu/gl/GrGLGpuProgramCache.cpp
@@ -81,7 +81,7 @@
 
     // Get GrGLProgramDesc
     GrProgramDesc desc;
-    if (!GrProgramDesc::Build(&desc, renderTarget->config(), primProc, isPoints, pipeline, gpu)) {
+    if (!GrProgramDesc::Build(&desc, renderTarget, primProc, isPoints, pipeline, gpu)) {
         GrCapsDebugf(gpu->caps(), "Failed to gl program descriptor!\n");
         return nullptr;
     }
diff --git a/src/gpu/gl/GrGLInterface.cpp b/src/gpu/gl/GrGLInterface.cpp
deleted file mode 100644
index aa8fb63..0000000
--- a/src/gpu/gl/GrGLInterface.cpp
+++ /dev/null
@@ -1,661 +0,0 @@
-/*
- * Copyright 2011 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-
-#include "gl/GrGLInterface.h"
-#include "gl/GrGLExtensions.h"
-#include "gl/GrGLUtil.h"
-
-#include <stdio.h>
-
-GrGLInterface::GrGLInterface() {
-    fStandard = kNone_GrGLStandard;
-}
-
-#ifdef SK_DEBUG
-    static int kIsDebug = 1;
-#else
-    static int kIsDebug = 0;
-#endif
-
-#define RETURN_FALSE_INTERFACE                                                                   \
-    if (kIsDebug) { SkDebugf("%s:%d GrGLInterface::validate() failed.\n", __FILE__, __LINE__); } \
-    return false
-
-bool GrGLInterface::validate() const {
-
-    if (kNone_GrGLStandard == fStandard) {
-        RETURN_FALSE_INTERFACE;
-    }
-
-    if (!fExtensions.isInitialized()) {
-        RETURN_FALSE_INTERFACE;
-    }
-
-    // functions that are always required
-    if (!fFunctions.fActiveTexture ||
-        !fFunctions.fAttachShader ||
-        !fFunctions.fBindAttribLocation ||
-        !fFunctions.fBindBuffer ||
-        !fFunctions.fBindTexture ||
-        !fFunctions.fBlendColor ||      // -> GL >= 1.4 or extension, ES >= 2.0
-        !fFunctions.fBlendEquation ||   // -> GL >= 1.4 or extension, ES >= 2.0
-        !fFunctions.fBlendFunc ||
-        !fFunctions.fBufferData ||
-        !fFunctions.fBufferSubData ||
-        !fFunctions.fClear ||
-        !fFunctions.fClearColor ||
-        !fFunctions.fClearStencil ||
-        !fFunctions.fColorMask ||
-        !fFunctions.fCompileShader ||
-        !fFunctions.fCompressedTexImage2D ||
-        !fFunctions.fCompressedTexSubImage2D ||
-        !fFunctions.fCopyTexSubImage2D ||
-        !fFunctions.fCreateProgram ||
-        !fFunctions.fCreateShader ||
-        !fFunctions.fCullFace ||
-        !fFunctions.fDeleteBuffers ||
-        !fFunctions.fDeleteProgram ||
-        !fFunctions.fDeleteShader ||
-        !fFunctions.fDeleteTextures ||
-        !fFunctions.fDepthMask ||
-        !fFunctions.fDisable ||
-        !fFunctions.fDisableVertexAttribArray ||
-        !fFunctions.fDrawArrays ||
-        !fFunctions.fDrawElements ||
-        !fFunctions.fEnable ||
-        !fFunctions.fEnableVertexAttribArray ||
-        !fFunctions.fFrontFace ||
-        !fFunctions.fGenBuffers ||
-        !fFunctions.fGenTextures ||
-        !fFunctions.fGetBufferParameteriv ||
-        !fFunctions.fGenerateMipmap ||
-        !fFunctions.fGetError ||
-        !fFunctions.fGetIntegerv ||
-        !fFunctions.fGetProgramInfoLog ||
-        !fFunctions.fGetProgramiv ||
-        !fFunctions.fGetShaderInfoLog ||
-        !fFunctions.fGetShaderiv ||
-        !fFunctions.fGetString ||
-        !fFunctions.fGetUniformLocation ||
-        !fFunctions.fIsTexture ||
-        !fFunctions.fLinkProgram ||
-        !fFunctions.fLineWidth ||
-        !fFunctions.fPixelStorei ||
-        !fFunctions.fReadPixels ||
-        !fFunctions.fScissor ||
-        !fFunctions.fShaderSource ||
-        !fFunctions.fStencilFunc ||
-        !fFunctions.fStencilFuncSeparate ||
-        !fFunctions.fStencilMask ||
-        !fFunctions.fStencilMaskSeparate ||
-        !fFunctions.fStencilOp ||
-        !fFunctions.fStencilOpSeparate ||
-        !fFunctions.fTexImage2D ||
-        !fFunctions.fTexParameterf ||
-        !fFunctions.fTexParameterfv ||
-        !fFunctions.fTexParameteri ||
-        !fFunctions.fTexParameteriv ||
-        !fFunctions.fTexSubImage2D ||
-        !fFunctions.fUniform1f ||
-        !fFunctions.fUniform1i ||
-        !fFunctions.fUniform1fv ||
-        !fFunctions.fUniform1iv ||
-        !fFunctions.fUniform2f ||
-        !fFunctions.fUniform2i ||
-        !fFunctions.fUniform2fv ||
-        !fFunctions.fUniform2iv ||
-        !fFunctions.fUniform3f ||
-        !fFunctions.fUniform3i ||
-        !fFunctions.fUniform3fv ||
-        !fFunctions.fUniform3iv ||
-        !fFunctions.fUniform4f ||
-        !fFunctions.fUniform4i ||
-        !fFunctions.fUniform4fv ||
-        !fFunctions.fUniform4iv ||
-        !fFunctions.fUniformMatrix2fv ||
-        !fFunctions.fUniformMatrix3fv ||
-        !fFunctions.fUniformMatrix4fv ||
-        !fFunctions.fUseProgram ||
-        !fFunctions.fVertexAttrib1f ||
-        !fFunctions.fVertexAttrib2fv ||
-        !fFunctions.fVertexAttrib3fv ||
-        !fFunctions.fVertexAttrib4fv ||
-        !fFunctions.fVertexAttribPointer ||
-        !fFunctions.fViewport ||
-        !fFunctions.fBindFramebuffer ||
-        !fFunctions.fBindRenderbuffer ||
-        !fFunctions.fCheckFramebufferStatus ||
-        !fFunctions.fDeleteFramebuffers ||
-        !fFunctions.fDeleteRenderbuffers ||
-        !fFunctions.fFinish ||
-        !fFunctions.fFlush ||
-        !fFunctions.fFramebufferRenderbuffer ||
-        !fFunctions.fFramebufferTexture2D ||
-        !fFunctions.fGetFramebufferAttachmentParameteriv ||
-        !fFunctions.fGetRenderbufferParameteriv ||
-        !fFunctions.fGenFramebuffers ||
-        !fFunctions.fGenRenderbuffers ||
-        !fFunctions.fRenderbufferStorage) {
-        RETURN_FALSE_INTERFACE;
-    }
-
-    GrGLVersion glVer = GrGLGetVersion(this);
-    if (GR_GL_INVALID_VER == glVer) {
-        RETURN_FALSE_INTERFACE;
-    }
-
-    // Now check that baseline ES/Desktop fns not covered above are present
-    // and that we have fn pointers for any advertised fExtensions that we will
-    // try to use.
-
-    // these functions are part of ES2, we assume they are available
-    // On the desktop we assume they are available if the extension
-    // is present or GL version is high enough.
-    if (kGL_GrGLStandard == fStandard) {
-        if (glVer >= GR_GL_VER(3,0) && !fFunctions.fBindFragDataLocation) {
-            RETURN_FALSE_INTERFACE;
-        }
-
-        if (glVer >= GR_GL_VER(3,3) ||
-            fExtensions.has("GL_ARB_timer_query") ||
-            fExtensions.has("GL_EXT_timer_query")) {
-            if (!fFunctions.fGetQueryObjecti64v ||
-                !fFunctions.fGetQueryObjectui64v) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-        if (glVer >= GR_GL_VER(3,3) || fExtensions.has("GL_ARB_timer_query")) {
-            if (!fFunctions.fQueryCounter) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-    }
-
-    // part of desktop GL, but not ES
-    if (kGL_GrGLStandard == fStandard &&
-        (!fFunctions.fDrawBuffer ||
-         !fFunctions.fPolygonMode)) {
-        RETURN_FALSE_INTERFACE;
-    }
-
-    // ES 3.0 (or ES 2.0 extended) has glDrawBuffers but not glDrawBuffer
-    if (kGL_GrGLStandard == fStandard || glVer >= GR_GL_VER(3,0)) {
-        if (!fFunctions.fDrawBuffers) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    if (kGL_GrGLStandard == fStandard || glVer >= GR_GL_VER(3,0)) {
-        if (!fFunctions.fReadBuffer) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    // glGetTexLevelParameteriv was added to ES in 3.1.
-    if (kGL_GrGLStandard == fStandard || glVer >= GR_GL_VER(3,1)) {
-        if (!fFunctions.fGetTexLevelParameteriv) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    // GL_EXT_texture_storage is part of desktop 4.2
-    // There is a desktop ARB extension and an ES+desktop EXT extension
-    if (kGL_GrGLStandard == fStandard) {
-        if (glVer >= GR_GL_VER(4,2) ||
-            fExtensions.has("GL_ARB_texture_storage") ||
-            fExtensions.has("GL_EXT_texture_storage")) {
-            if (!fFunctions.fTexStorage2D) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-    } else if (glVer >= GR_GL_VER(3,0) || fExtensions.has("GL_EXT_texture_storage")) {
-        if (!fFunctions.fTexStorage2D) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    // glTextureBarrier is part of desktop 4.5. There are also ARB and NV extensions.
-    if (kGL_GrGLStandard == fStandard) {
-        if (glVer >= GR_GL_VER(4,5) ||
-            fExtensions.has("GL_ARB_texture_barrier") ||
-            fExtensions.has("GL_NV_texture_barrier")) {
-            if (!fFunctions.fTextureBarrier) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-    } else if (fExtensions.has("GL_NV_texture_barrier")) {
-        if (!fFunctions.fTextureBarrier) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    if (fExtensions.has("GL_KHR_blend_equation_advanced") ||
-        fExtensions.has("GL_NV_blend_equation_advanced")) {
-        if (!fFunctions.fBlendBarrier) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    if (fExtensions.has("GL_EXT_discard_framebuffer")) {
-        if (!fFunctions.fDiscardFramebuffer) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    // Required since OpenGL 1.5 and ES 3.0 or with GL_EXT_occlusion_query_boolean
-    if (kGL_GrGLStandard == fStandard || glVer >= GR_GL_VER(3,0) ||
-        fExtensions.has("GL_EXT_occlusion_query_boolean")) {
-#if 0 // Not yet added to chrome's bindings.
-        if (!fFunctions.fGenQueries ||
-            !fFunctions.fDeleteQueries ||
-            !fFunctions.fBeginQuery ||
-            !fFunctions.fEndQuery ||
-            !fFunctions.fGetQueryiv ||
-            !fFunctions.fGetQueryObjectuiv) {
-            RETURN_FALSE_INTERFACE;
-        }
-#endif
-    }
-    // glGetQueryObjectiv doesn't exist in ES.
-    if (kGL_GrGLStandard == fStandard && !fFunctions.fGetQueryObjectiv) {
-        RETURN_FALSE_INTERFACE;
-    }
-
-    // FBO MSAA
-    if (kGL_GrGLStandard == fStandard) {
-        // GL 3.0 and the ARB extension have multisample + blit
-        if (glVer >= GR_GL_VER(3,0) || fExtensions.has("GL_ARB_framebuffer_object")) {
-            if (!fFunctions.fRenderbufferStorageMultisample ||
-                !fFunctions.fBlitFramebuffer) {
-                RETURN_FALSE_INTERFACE;
-            }
-        } else {
-            if (fExtensions.has("GL_EXT_framebuffer_blit") &&
-                !fFunctions.fBlitFramebuffer) {
-                RETURN_FALSE_INTERFACE;
-            }
-            if (fExtensions.has("GL_EXT_framebuffer_multisample") &&
-                !fFunctions.fRenderbufferStorageMultisample) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-    } else {
-        if (glVer >= GR_GL_VER(3,0) || fExtensions.has("GL_CHROMIUM_framebuffer_multisample")) {
-            if (!fFunctions.fRenderbufferStorageMultisample ||
-                !fFunctions.fBlitFramebuffer) {
-                RETURN_FALSE_INTERFACE;
-            }
-        } else {
-            if (fExtensions.has("GL_ANGLE_framebuffer_multisample") &&
-                !fFunctions.fRenderbufferStorageMultisample) {
-                RETURN_FALSE_INTERFACE;
-            }
-            if (fExtensions.has("GL_ANGLE_framebuffer_blit") &&
-                !fFunctions.fBlitFramebuffer) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-        if (fExtensions.has("GL_APPLE_framebuffer_multisample")) {
-            if (!fFunctions.fRenderbufferStorageMultisampleES2APPLE ||
-                !fFunctions.fResolveMultisampleFramebuffer) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-        if (fExtensions.has("GL_IMG_multisampled_render_to_texture") ||
-            fExtensions.has("GL_EXT_multisampled_render_to_texture")) {
-            if (!fFunctions.fRenderbufferStorageMultisampleES2EXT ||
-                !fFunctions.fFramebufferTexture2DMultisample) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-    }
-
-    // On ES buffer mapping is an extension. On Desktop
-    // buffer mapping was part of original VBO extension
-    // which we require.
-    if (kGL_GrGLStandard == fStandard || fExtensions.has("GL_OES_mapbuffer")) {
-        if (!fFunctions.fMapBuffer ||
-            !fFunctions.fUnmapBuffer) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    // Dual source blending
-    if (kGL_GrGLStandard == fStandard) {
-        if (glVer >= GR_GL_VER(3,3) || fExtensions.has("GL_ARB_blend_func_extended")) {
-            if (!fFunctions.fBindFragDataLocationIndexed) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-    } else {
-        if (glVer >= GR_GL_VER(3,0) && fExtensions.has("GL_EXT_blend_func_extended")) {
-            if (!fFunctions.fBindFragDataLocation ||
-                !fFunctions.fBindFragDataLocationIndexed) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-    }
-
-
-    // glGetStringi was added in version 3.0 of both desktop and ES.
-    if (glVer >= GR_GL_VER(3, 0)) {
-        if (!fFunctions.fGetStringi) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    // glVertexAttribIPointer was added in version 3.0 of both desktop and ES.
-    if (glVer >= GR_GL_VER(3, 0)) {
-        if (!fFunctions.fVertexAttribIPointer) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    if (kGL_GrGLStandard == fStandard) {
-        if (glVer >= GR_GL_VER(3,1)) {
-            if (!fFunctions.fTexBuffer) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-        if (glVer >= GR_GL_VER(4,3)) {
-            if (!fFunctions.fTexBufferRange) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-    } else {
-        if (glVer >= GR_GL_VER(3,2) || fExtensions.has("GL_OES_texture_buffer") ||
-            fExtensions.has("GL_EXT_texture_buffer")) {
-            if (!fFunctions.fTexBuffer ||
-                !fFunctions.fTexBufferRange) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-    }
-
-    if (kGL_GrGLStandard == fStandard) {
-        if (glVer >= GR_GL_VER(3, 0) || fExtensions.has("GL_ARB_vertex_array_object")) {
-            if (!fFunctions.fBindVertexArray ||
-                !fFunctions.fDeleteVertexArrays ||
-                !fFunctions.fGenVertexArrays) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-    } else {
-        if (glVer >= GR_GL_VER(3,0) || fExtensions.has("GL_OES_vertex_array_object")) {
-            if (!fFunctions.fBindVertexArray ||
-                !fFunctions.fDeleteVertexArrays ||
-                !fFunctions.fGenVertexArrays) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-    }
-
-    if (fExtensions.has("GL_EXT_debug_marker")) {
-        if (!fFunctions.fInsertEventMarker ||
-            !fFunctions.fPushGroupMarker ||
-            !fFunctions.fPopGroupMarker) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    if ((kGL_GrGLStandard == fStandard && glVer >= GR_GL_VER(4,3)) ||
-        fExtensions.has("GL_ARB_invalidate_subdata")) {
-        if (!fFunctions.fInvalidateBufferData ||
-            !fFunctions.fInvalidateBufferSubData ||
-            !fFunctions.fInvalidateFramebuffer ||
-            !fFunctions.fInvalidateSubFramebuffer ||
-            !fFunctions.fInvalidateTexImage ||
-            !fFunctions.fInvalidateTexSubImage) {
-            RETURN_FALSE_INTERFACE;
-        }
-    } else if (kGLES_GrGLStandard == fStandard && glVer >= GR_GL_VER(3,0)) {
-        // ES 3.0 adds the framebuffer functions but not the others.
-        if (!fFunctions.fInvalidateFramebuffer ||
-            !fFunctions.fInvalidateSubFramebuffer) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    if (kGLES_GrGLStandard == fStandard && fExtensions.has("GL_CHROMIUM_map_sub")) {
-        if (!fFunctions.fMapBufferSubData ||
-            !fFunctions.fMapTexSubImage2D ||
-            !fFunctions.fUnmapBufferSubData ||
-            !fFunctions.fUnmapTexSubImage2D) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    // These functions are added to the 3.0 version of both GLES and GL.
-    if (glVer >= GR_GL_VER(3,0) ||
-        (kGLES_GrGLStandard == fStandard && fExtensions.has("GL_EXT_map_buffer_range")) ||
-        (kGL_GrGLStandard == fStandard && fExtensions.has("GL_ARB_map_buffer_range"))) {
-        if (!fFunctions.fMapBufferRange ||
-            !fFunctions.fFlushMappedBufferRange) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    if ((kGL_GrGLStandard == fStandard &&
-         (glVer >= GR_GL_VER(3,2) || fExtensions.has("GL_ARB_texture_multisample"))) ||
-        (kGLES_GrGLStandard == fStandard && glVer >= GR_GL_VER(3,1))) {
-        if (!fFunctions.fGetMultisamplefv) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    if ((kGL_GrGLStandard == fStandard &&
-         (glVer >= GR_GL_VER(4,3) || fExtensions.has("GL_ARB_program_interface_query"))) ||
-        (kGLES_GrGLStandard == fStandard && glVer >= GR_GL_VER(3,1))) {
-        if (!fFunctions.fGetProgramResourceLocation) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    if (kGLES_GrGLStandard == fStandard || glVer >= GR_GL_VER(4,1) ||
-        fExtensions.has("GL_ARB_ES2_compatibility")) {
-        if (!fFunctions.fGetShaderPrecisionFormat) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    if (fExtensions.has("GL_NV_path_rendering") || fExtensions.has("GL_CHROMIUM_path_rendering")) {
-        if (!fFunctions.fMatrixLoadf ||
-            !fFunctions.fMatrixLoadIdentity ||
-            !fFunctions.fPathCommands ||
-            !fFunctions.fPathParameteri ||
-            !fFunctions.fPathParameterf ||
-            !fFunctions.fGenPaths ||
-            !fFunctions.fDeletePaths ||
-            !fFunctions.fIsPath ||
-            !fFunctions.fPathStencilFunc ||
-            !fFunctions.fStencilFillPath ||
-            !fFunctions.fStencilStrokePath ||
-            !fFunctions.fStencilFillPathInstanced ||
-            !fFunctions.fStencilStrokePathInstanced ||
-            !fFunctions.fCoverFillPath ||
-            !fFunctions.fCoverStrokePath ||
-            !fFunctions.fCoverFillPathInstanced ||
-            !fFunctions.fCoverStrokePathInstanced
-#if 0
-            // List of functions that Skia uses, but which have been added since the initial release
-            // of NV_path_rendering driver. We do not want to fail interface validation due to
-            // missing features, we will just not use the extension.
-            // Update this list -> update GrGLCaps::hasPathRenderingSupport too.
-            || !fFunctions.fStencilThenCoverFillPath ||
-            !fFunctions.fStencilThenCoverStrokePath ||
-            !fFunctions.fStencilThenCoverFillPathInstanced ||
-            !fFunctions.fStencilThenCoverStrokePathInstanced ||
-            !fFunctions.fProgramPathFragmentInputGen
-#endif
-            ) {
-            RETURN_FALSE_INTERFACE;
-        }
-        if (fExtensions.has("GL_CHROMIUM_path_rendering")) {
-            if (!fFunctions.fBindFragmentInputLocation) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-    }
-
-    if (fExtensions.has("GL_EXT_raster_multisample")) {
-        if (!fFunctions.fRasterSamples) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    if (fExtensions.has("GL_NV_framebuffer_mixed_samples") ||
-        fExtensions.has("GL_CHROMIUM_framebuffer_mixed_samples")) {
-        if (!fFunctions.fCoverageModulation) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    if (kGL_GrGLStandard == fStandard) {
-        if (glVer >= GR_GL_VER(3,1) ||
-            fExtensions.has("GL_EXT_draw_instanced") || fExtensions.has("GL_ARB_draw_instanced")) {
-            if (!fFunctions.fDrawArraysInstanced ||
-                !fFunctions.fDrawElementsInstanced) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-    } else if (kGLES_GrGLStandard == fStandard) {
-        if (glVer >= GR_GL_VER(3,0) || fExtensions.has("GL_EXT_draw_instanced")) {
-            if (!fFunctions.fDrawArraysInstanced ||
-                !fFunctions.fDrawElementsInstanced) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-    }
-
-    if (kGL_GrGLStandard == fStandard) {
-        if (glVer >= GR_GL_VER(3,2) || fExtensions.has("GL_ARB_instanced_arrays")) {
-            if (!fFunctions.fVertexAttribDivisor) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-    } else if (kGLES_GrGLStandard == fStandard) {
-        if (glVer >= GR_GL_VER(3,0) || fExtensions.has("GL_EXT_instanced_arrays")) {
-            if (!fFunctions.fVertexAttribDivisor) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-    }
-
-    if ((kGL_GrGLStandard == fStandard &&
-         (glVer >= GR_GL_VER(4,0) || fExtensions.has("GL_ARB_draw_indirect"))) ||
-        (kGLES_GrGLStandard == fStandard && glVer >= GR_GL_VER(3,1))) {
-        if (!fFunctions.fDrawArraysIndirect ||
-            !fFunctions.fDrawElementsIndirect) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    if ((kGL_GrGLStandard == fStandard &&
-         (glVer >= GR_GL_VER(4,3) || fExtensions.has("GL_ARB_multi_draw_indirect"))) ||
-        (kGLES_GrGLStandard == fStandard && fExtensions.has("GL_EXT_multi_draw_indirect"))) {
-        if (!fFunctions.fMultiDrawArraysIndirect ||
-            !fFunctions.fMultiDrawElementsIndirect) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    if ((kGL_GrGLStandard == fStandard && glVer >= GR_GL_VER(4,3)) ||
-        fExtensions.has("GL_KHR_debug")) {
-        if (!fFunctions.fDebugMessageControl ||
-            !fFunctions.fDebugMessageInsert ||
-            !fFunctions.fDebugMessageCallback ||
-            !fFunctions.fGetDebugMessageLog ||
-            !fFunctions.fPushDebugGroup ||
-            !fFunctions.fPopDebugGroup ||
-            !fFunctions.fObjectLabel) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    if (fExtensions.has("GL_EXT_window_rectangles")) {
-        if (!fFunctions.fWindowRectangles) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    if (kGL_GrGLStandard == fStandard) {
-        if (glVer >= GR_GL_VER(3, 2) || fExtensions.has("GL_ARB_sync")) {
-            if (!fFunctions.fFenceSync ||
-                !fFunctions.fIsSync ||
-                !fFunctions.fClientWaitSync ||
-                !fFunctions.fWaitSync ||
-                !fFunctions.fDeleteSync) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-    } else if (kGLES_GrGLStandard == fStandard) {
-        if (glVer >= GR_GL_VER(3, 0) || fExtensions.has("GL_APPLE_sync")) {
-            if (!fFunctions.fFenceSync ||
-                !fFunctions.fIsSync ||
-                !fFunctions.fClientWaitSync ||
-                !fFunctions.fWaitSync ||
-                !fFunctions.fDeleteSync) {
-                RETURN_FALSE_INTERFACE;
-            }
-        }
-    }
-
-    if (fExtensions.has("EGL_KHR_image") || fExtensions.has("EGL_KHR_image_base")) {
-        if (!fFunctions.fEGLCreateImage ||
-            !fFunctions.fEGLDestroyImage) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    // glDrawRangeElements was added to ES in 3.0.
-    if (kGL_GrGLStandard == fStandard || glVer >= GR_GL_VER(3,0)) {
-        if (!fFunctions.fDrawRangeElements) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    // getInternalformativ was added in GL 4.2, ES 3.0, and with extension ARB_internalformat_query
-    if ((kGL_GrGLStandard == fStandard &&
-         (glVer >= GR_GL_VER(4,2) || fExtensions.has("GL_ARB_internalformat_query"))) ||
-        (kGLES_GrGLStandard == fStandard && glVer >= GR_GL_VER(3,0))) {
-        if (!fFunctions.fGetInternalformativ) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    if ((kGL_GrGLStandard == fStandard && glVer >= GR_GL_VER(4,1)) ||
-        (kGLES_GrGLStandard == fStandard && glVer >= GR_GL_VER(3,0))) {
-        if (!fFunctions.fGetProgramBinary ||
-            !fFunctions.fProgramBinary ||
-            !fFunctions.fProgramParameteri) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    if ((kGL_GrGLStandard == fStandard && glVer >= GR_GL_VER(4,1)) ||
-        (kGLES_GrGLStandard == fStandard && glVer >= GR_GL_VER(3,0))) {
-        if (!fFunctions.fBindSampler ||
-            !fFunctions.fDeleteSamplers  ||
-            !fFunctions.fGenSamplers ||
-            !fFunctions.fSamplerParameteri ||
-            !fFunctions.fSamplerParameteriv) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
-    return true;
-}
-
-#if GR_TEST_UTILS
-
-void GrGLInterface::abandon() const {
-    const_cast<GrGLInterface*>(this)->fFunctions = GrGLInterface::Functions();
-}
-
-#endif // GR_TEST_UTILS
-
diff --git a/src/gpu/gl/GrGLInterfaceAutogen.cpp b/src/gpu/gl/GrGLInterfaceAutogen.cpp
new file mode 100644
index 0000000..4811883
--- /dev/null
+++ b/src/gpu/gl/GrGLInterfaceAutogen.cpp
@@ -0,0 +1,735 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * THIS FILE IS AUTOGENERATED
+ * Make edits to tools/gpu/gl/interface/templates.go or they will
+ * be overwritten.
+ */
+
+#include "gl/GrGLInterface.h"
+#include "gl/GrGLExtensions.h"
+#include "gl/GrGLUtil.h"
+
+#include <stdio.h>
+
+GrGLInterface::GrGLInterface() {
+    fStandard = kNone_GrGLStandard;
+}
+
+#define RETURN_FALSE_INTERFACE                                                 \
+    SkDEBUGF("%s:%d GrGLInterface::validate() failed.\n", __FILE__, __LINE__); \
+    return false
+
+bool GrGLInterface::validate() const {
+
+    if (kNone_GrGLStandard == fStandard) {
+        RETURN_FALSE_INTERFACE;
+    }
+
+    if (!fExtensions.isInitialized()) {
+        RETURN_FALSE_INTERFACE;
+    }
+
+    GrGLVersion glVer = GrGLGetVersion(this);
+    if (GR_GL_INVALID_VER == glVer) {
+        RETURN_FALSE_INTERFACE;
+    }
+    // Autogenerated content follows
+    if (!fFunctions.fActiveTexture ||
+        !fFunctions.fAttachShader ||
+        !fFunctions.fBindAttribLocation ||
+        !fFunctions.fBindBuffer ||
+        !fFunctions.fBindTexture ||
+        !fFunctions.fBlendColor ||
+        !fFunctions.fBlendEquation ||
+        !fFunctions.fBlendFunc ||
+        !fFunctions.fBufferData ||
+        !fFunctions.fBufferSubData ||
+        !fFunctions.fClear ||
+        !fFunctions.fClearColor ||
+        !fFunctions.fClearStencil ||
+        !fFunctions.fColorMask ||
+        !fFunctions.fCompileShader ||
+        !fFunctions.fCompressedTexImage2D ||
+        !fFunctions.fCompressedTexSubImage2D ||
+        !fFunctions.fCopyTexSubImage2D ||
+        !fFunctions.fCreateProgram ||
+        !fFunctions.fCreateShader ||
+        !fFunctions.fCullFace ||
+        !fFunctions.fDeleteBuffers ||
+        !fFunctions.fDeleteProgram ||
+        !fFunctions.fDeleteShader ||
+        !fFunctions.fDeleteTextures ||
+        !fFunctions.fDepthMask ||
+        !fFunctions.fDisable ||
+        !fFunctions.fDisableVertexAttribArray ||
+        !fFunctions.fDrawArrays ||
+        !fFunctions.fDrawElements ||
+        !fFunctions.fEnable ||
+        !fFunctions.fEnableVertexAttribArray ||
+        !fFunctions.fFinish ||
+        !fFunctions.fFlush ||
+        !fFunctions.fFrontFace ||
+        !fFunctions.fGenBuffers ||
+        !fFunctions.fGenTextures ||
+        !fFunctions.fGetBufferParameteriv ||
+        !fFunctions.fGetError ||
+        !fFunctions.fGetIntegerv ||
+        !fFunctions.fGetProgramInfoLog ||
+        !fFunctions.fGetProgramiv ||
+        !fFunctions.fGetShaderInfoLog ||
+        !fFunctions.fGetShaderiv ||
+        !fFunctions.fGetString ||
+        !fFunctions.fGetUniformLocation ||
+        !fFunctions.fIsTexture ||
+        !fFunctions.fLineWidth ||
+        !fFunctions.fLinkProgram ||
+        !fFunctions.fPixelStorei ||
+        !fFunctions.fReadPixels ||
+        !fFunctions.fScissor ||
+        !fFunctions.fShaderSource ||
+        !fFunctions.fStencilFunc ||
+        !fFunctions.fStencilFuncSeparate ||
+        !fFunctions.fStencilMask ||
+        !fFunctions.fStencilMaskSeparate ||
+        !fFunctions.fStencilOp ||
+        !fFunctions.fStencilOpSeparate ||
+        !fFunctions.fTexImage2D ||
+        !fFunctions.fTexParameterf ||
+        !fFunctions.fTexParameterfv ||
+        !fFunctions.fTexParameteri ||
+        !fFunctions.fTexParameteriv ||
+        !fFunctions.fTexSubImage2D ||
+        !fFunctions.fUniform1f ||
+        !fFunctions.fUniform1fv ||
+        !fFunctions.fUniform1i ||
+        !fFunctions.fUniform1iv ||
+        !fFunctions.fUniform2f ||
+        !fFunctions.fUniform2fv ||
+        !fFunctions.fUniform2i ||
+        !fFunctions.fUniform2iv ||
+        !fFunctions.fUniform3f ||
+        !fFunctions.fUniform3fv ||
+        !fFunctions.fUniform3i ||
+        !fFunctions.fUniform3iv ||
+        !fFunctions.fUniform4f ||
+        !fFunctions.fUniform4fv ||
+        !fFunctions.fUniform4i ||
+        !fFunctions.fUniform4iv ||
+        !fFunctions.fUniformMatrix2fv ||
+        !fFunctions.fUniformMatrix3fv ||
+        !fFunctions.fUniformMatrix4fv ||
+        !fFunctions.fUseProgram ||
+        !fFunctions.fVertexAttrib1f ||
+        !fFunctions.fVertexAttrib2fv ||
+        !fFunctions.fVertexAttrib3fv ||
+        !fFunctions.fVertexAttrib4fv ||
+        !fFunctions.fVertexAttribPointer ||
+        !fFunctions.fViewport) {
+        RETURN_FALSE_INTERFACE;
+    }
+
+    if (GR_IS_GR_GL(fStandard)) {
+        if (!fFunctions.fDrawBuffer ||
+            !fFunctions.fPolygonMode) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)))) ||
+       (GR_IS_GR_WEBGL(fStandard) && (
+          (glVer >= GR_GL_VER(2,0))))) {
+        if (!fFunctions.fGetStringi) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if (GR_IS_GR_GL(fStandard) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)) ||
+          fExtensions.has("GL_OES_vertex_array_object"))) ||
+       (GR_IS_GR_WEBGL(fStandard) && (
+          (glVer >= GR_GL_VER(2,0)) ||
+          fExtensions.has("GL_OES_vertex_array_object") ||
+          fExtensions.has("OES_vertex_array_object")))) {
+        if (!fFunctions.fBindVertexArray ||
+            !fFunctions.fDeleteVertexArrays ||
+            !fFunctions.fGenVertexArrays) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0) && fExtensions.has("GL_EXT_blend_func_extended"))))) {
+        if (!fFunctions.fBindFragDataLocation) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(3,3)) ||
+          fExtensions.has("GL_ARB_blend_func_extended"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0) && fExtensions.has("GL_EXT_blend_func_extended"))))) {
+        if (!fFunctions.fBindFragDataLocationIndexed) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          fExtensions.has("GL_KHR_blend_equation_advanced") ||
+          fExtensions.has("GL_NV_blend_equation_advanced"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("GL_KHR_blend_equation_advanced") ||
+          fExtensions.has("GL_NV_blend_equation_advanced")))) {
+        if (!fFunctions.fBlendBarrier) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(4,4)) ||
+          fExtensions.has("GL_ARB_clear_texture"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("GL_EXT_clear_texture")))) {
+        // all functions were marked optional or test_only
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(3,1)) ||
+          fExtensions.has("GL_ARB_draw_instanced") ||
+          fExtensions.has("GL_EXT_draw_instanced"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)) ||
+          fExtensions.has("GL_EXT_draw_instanced"))) ||
+       (GR_IS_GR_WEBGL(fStandard) && (
+          (glVer >= GR_GL_VER(2,0))))) {
+        if (!fFunctions.fDrawArraysInstanced ||
+            !fFunctions.fDrawElementsInstanced) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if (GR_IS_GR_GL(fStandard) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)))) ||
+       (GR_IS_GR_WEBGL(fStandard) && (
+          (glVer >= GR_GL_VER(2,0))))) {
+        if (!fFunctions.fDrawBuffers ||
+            !fFunctions.fReadBuffer) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(4,0)) ||
+          fExtensions.has("GL_ARB_draw_indirect"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,1))))) {
+        if (!fFunctions.fDrawArraysIndirect ||
+            !fFunctions.fDrawElementsIndirect) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if (GR_IS_GR_GL(fStandard) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)))) ||
+       (GR_IS_GR_WEBGL(fStandard) && (
+          (glVer >= GR_GL_VER(2,0))))) {
+        if (!fFunctions.fDrawRangeElements) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(3,2)) ||
+          fExtensions.has("GL_ARB_texture_multisample"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,1))))) {
+        if (!fFunctions.fGetMultisamplefv) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if (GR_IS_GR_GL(fStandard) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,1))))) {
+        if (!fFunctions.fGetTexLevelParameteriv) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(4,3)) ||
+          fExtensions.has("GL_ARB_multi_draw_indirect"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("GL_EXT_multi_draw_indirect")))) {
+        if (!fFunctions.fMultiDrawArraysIndirect ||
+            !fFunctions.fMultiDrawElementsIndirect) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(3,1)))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,2)) ||
+          fExtensions.has("GL_OES_texture_buffer") ||
+          fExtensions.has("GL_EXT_texture_buffer")))) {
+        if (!fFunctions.fTexBuffer) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(4,3)))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,2)) ||
+          fExtensions.has("GL_OES_texture_buffer") ||
+          fExtensions.has("GL_EXT_texture_buffer")))) {
+        if (!fFunctions.fTexBufferRange) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(4,2)) ||
+          fExtensions.has("GL_ARB_texture_storage") ||
+          fExtensions.has("GL_EXT_texture_storage"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)) ||
+          fExtensions.has("GL_EXT_texture_storage"))) ||
+       (GR_IS_GR_WEBGL(fStandard) && (
+          (glVer >= GR_GL_VER(2,0))))) {
+        if (!fFunctions.fTexStorage2D) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(4,5)) ||
+          fExtensions.has("GL_ARB_texture_barrier") ||
+          fExtensions.has("GL_NV_texture_barrier"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("GL_NV_texture_barrier")))) {
+        if (!fFunctions.fTextureBarrier) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("GL_EXT_discard_framebuffer")))) {
+        if (!fFunctions.fDiscardFramebuffer) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(3,2)) ||
+          fExtensions.has("GL_ARB_instanced_arrays"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)) ||
+          fExtensions.has("GL_EXT_instanced_arrays")))) {
+        if (!fFunctions.fVertexAttribDivisor) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)))) ||
+       (GR_IS_GR_WEBGL(fStandard) && (
+          (glVer >= GR_GL_VER(2,0))))) {
+        if (!fFunctions.fVertexAttribIPointer) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)) ||
+          fExtensions.has("GL_ARB_framebuffer_object") ||
+          fExtensions.has("GL_EXT_framebuffer_object"))) ||
+       GR_IS_GR_GL_ES(fStandard) ||
+       GR_IS_GR_WEBGL(fStandard)) {
+        if (!fFunctions.fBindFramebuffer ||
+            !fFunctions.fBindRenderbuffer ||
+            !fFunctions.fCheckFramebufferStatus ||
+            !fFunctions.fDeleteFramebuffers ||
+            !fFunctions.fDeleteRenderbuffers ||
+            !fFunctions.fFramebufferRenderbuffer ||
+            !fFunctions.fFramebufferTexture2D ||
+            !fFunctions.fGenFramebuffers ||
+            !fFunctions.fGenRenderbuffers ||
+            !fFunctions.fGenerateMipmap ||
+            !fFunctions.fGetFramebufferAttachmentParameteriv ||
+            !fFunctions.fGetRenderbufferParameteriv ||
+            !fFunctions.fRenderbufferStorage) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)) ||
+          fExtensions.has("GL_ARB_framebuffer_object") ||
+          fExtensions.has("GL_EXT_framebuffer_blit"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)) ||
+          fExtensions.has("GL_CHROMIUM_framebuffer_multisample") ||
+          fExtensions.has("GL_ANGLE_framebuffer_blit")))) {
+        if (!fFunctions.fBlitFramebuffer) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)) ||
+          fExtensions.has("GL_ARB_framebuffer_object") ||
+          fExtensions.has("GL_EXT_framebuffer_multisample"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)) ||
+          fExtensions.has("GL_CHROMIUM_framebuffer_multisample") ||
+          fExtensions.has("GL_ANGLE_framebuffer_multisample"))) ||
+       (GR_IS_GR_WEBGL(fStandard) && (
+          (glVer >= GR_GL_VER(2,0))))) {
+        if (!fFunctions.fRenderbufferStorageMultisample) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("GL_CHROMIUM_map_sub")))) {
+        if (!fFunctions.fMapBufferSubData ||
+            !fFunctions.fMapTexSubImage2D ||
+            !fFunctions.fUnmapBufferSubData ||
+            !fFunctions.fUnmapTexSubImage2D) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("GL_EXT_multisampled_render_to_texture") ||
+          fExtensions.has("GL_IMG_multisampled_render_to_texture")))) {
+        if (!fFunctions.fFramebufferTexture2DMultisample) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("GL_EXT_multisampled_render_to_texture")))) {
+        if (!fFunctions.fRenderbufferStorageMultisampleES2EXT) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("GL_IMG_multisampled_render_to_texture")))) {
+        if (!fFunctions.fRenderbufferStorageMultisampleES2EXT) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("GL_APPLE_framebuffer_multisample")))) {
+        if (!fFunctions.fResolveMultisampleFramebuffer ||
+            !fFunctions.fRenderbufferStorageMultisampleES2APPLE) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if (GR_IS_GR_GL(fStandard) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("GL_OES_mapbuffer")))) {
+        if (!fFunctions.fMapBuffer) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if (GR_IS_GR_GL(fStandard) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)) ||
+          fExtensions.has("GL_OES_mapbuffer")))) {
+        if (!fFunctions.fUnmapBuffer) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)) ||
+          fExtensions.has("GL_ARB_map_buffer_range"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)) ||
+          fExtensions.has("GL_EXT_map_buffer_range")))) {
+        if (!fFunctions.fFlushMappedBufferRange ||
+            !fFunctions.fMapBufferRange) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          fExtensions.has("GL_EXT_debug_marker"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("GL_EXT_debug_marker")))) {
+        if (!fFunctions.fInsertEventMarker ||
+            !fFunctions.fPopGroupMarker ||
+            !fFunctions.fPushGroupMarker) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(4,3)) ||
+          fExtensions.has("GL_ARB_program_interface_query"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,1))))) {
+        if (!fFunctions.fGetProgramResourceLocation) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          fExtensions.has("GL_NV_path_rendering"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("GL_CHROMIUM_path_rendering") ||
+          fExtensions.has("GL_NV_path_rendering")))) {
+        if (!fFunctions.fMatrixLoadIdentity ||
+            !fFunctions.fMatrixLoadf) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          fExtensions.has("GL_NV_path_rendering"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("GL_CHROMIUM_path_rendering") ||
+          fExtensions.has("GL_NV_path_rendering")))) {
+        if (!fFunctions.fCoverFillPath ||
+            !fFunctions.fCoverFillPathInstanced ||
+            !fFunctions.fCoverStrokePath ||
+            !fFunctions.fCoverStrokePathInstanced ||
+            !fFunctions.fDeletePaths ||
+            !fFunctions.fGenPaths ||
+            !fFunctions.fIsPath ||
+            !fFunctions.fPathCommands ||
+            !fFunctions.fPathParameterf ||
+            !fFunctions.fPathParameteri ||
+            !fFunctions.fPathStencilFunc ||
+            !fFunctions.fStencilFillPath ||
+            !fFunctions.fStencilFillPathInstanced ||
+            !fFunctions.fStencilStrokePath ||
+            !fFunctions.fStencilStrokePathInstanced) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("GL_CHROMIUM_path_rendering")))) {
+        if (!fFunctions.fBindFragmentInputLocation) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          fExtensions.has("GL_NV_framebuffer_mixed_samples"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("GL_CHROMIUM_framebuffer_mixed_samples") ||
+          fExtensions.has("GL_NV_framebuffer_mixed_samples")))) {
+        if (!fFunctions.fCoverageModulation) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(4,3)) ||
+          fExtensions.has("GL_KHR_debug"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("GL_KHR_debug")))) {
+        if (!fFunctions.fDebugMessageCallback ||
+            !fFunctions.fDebugMessageControl ||
+            !fFunctions.fDebugMessageInsert ||
+            !fFunctions.fGetDebugMessageLog ||
+            !fFunctions.fObjectLabel ||
+            !fFunctions.fPopDebugGroup ||
+            !fFunctions.fPushDebugGroup) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("GL_CHROMIUM_bind_uniform_location")))) {
+        if (!fFunctions.fBindUniformLocation) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          fExtensions.has("GL_EXT_window_rectangles"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("GL_EXT_window_rectangles")))) {
+        if (!fFunctions.fWindowRectangles) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          fExtensions.has("EGL_KHR_image") ||
+          fExtensions.has("EGL_KHR_image_base"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          fExtensions.has("EGL_KHR_image") ||
+          fExtensions.has("EGL_KHR_image_base")))) {
+        if (!fFunctions.fEGLCreateImage ||
+            !fFunctions.fEGLDestroyImage) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(3,2)) ||
+          fExtensions.has("GL_ARB_sync"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)) ||
+          fExtensions.has("GL_APPLE_sync"))) ||
+       (GR_IS_GR_WEBGL(fStandard) && (
+          (glVer >= GR_GL_VER(2,0))))) {
+        if (!fFunctions.fClientWaitSync ||
+            !fFunctions.fDeleteSync ||
+            !fFunctions.fFenceSync ||
+            !fFunctions.fIsSync ||
+            !fFunctions.fWaitSync) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(4,2)) ||
+          fExtensions.has("GL_ARB_internalformat_query"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0))))) {
+        if (!fFunctions.fGetInternalformativ) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(4,1)))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0))))) {
+        if (!fFunctions.fGetProgramBinary ||
+            !fFunctions.fProgramBinary ||
+            !fFunctions.fProgramParameteri) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(3,2)) ||
+          fExtensions.has("GL_ARB_sampler_objects"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)))) ||
+       (GR_IS_GR_WEBGL(fStandard) && (
+          (glVer >= GR_GL_VER(2,0))))) {
+        if (!fFunctions.fBindSampler ||
+            !fFunctions.fDeleteSamplers ||
+            !fFunctions.fGenSamplers ||
+            !fFunctions.fSamplerParameteri ||
+            !fFunctions.fSamplerParameteriv) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if (GR_IS_GR_GL(fStandard)) {
+        if (!fFunctions.fGetQueryObjectiv) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if (GR_IS_GR_GL(fStandard) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)) ||
+          fExtensions.has("GL_EXT_occlusion_query_boolean")))) {
+#if GR_TEST_UTILS
+        if (!fFunctions.fBeginQuery ||
+            !fFunctions.fDeleteQueries ||
+            !fFunctions.fEndQuery ||
+            !fFunctions.fGenQueries ||
+            !fFunctions.fGetQueryObjectuiv ||
+            !fFunctions.fGetQueryiv) {
+            RETURN_FALSE_INTERFACE;
+        }
+#endif
+        // all functions were marked optional or test_only
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(3,3)) ||
+          fExtensions.has("GL_ARB_timer_query") ||
+          fExtensions.has("GL_EXT_timer_query")))) {
+        if (!fFunctions.fGetQueryObjecti64v ||
+            !fFunctions.fGetQueryObjectui64v) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(3,3)) ||
+          fExtensions.has("GL_ARB_timer_query")))) {
+        if (!fFunctions.fQueryCounter) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(4,3)) ||
+          fExtensions.has("GL_ARB_invalidate_subdata")))) {
+        if (!fFunctions.fInvalidateBufferData ||
+            !fFunctions.fInvalidateBufferSubData ||
+            !fFunctions.fInvalidateTexImage ||
+            !fFunctions.fInvalidateTexSubImage) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(4,3)) ||
+          fExtensions.has("GL_ARB_invalidate_subdata"))) ||
+       (GR_IS_GR_GL_ES(fStandard) && (
+          (glVer >= GR_GL_VER(3,0)))) ||
+       (GR_IS_GR_WEBGL(fStandard) && (
+          (glVer >= GR_GL_VER(2,0))))) {
+        if (!fFunctions.fInvalidateFramebuffer ||
+            !fFunctions.fInvalidateSubFramebuffer) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+    if ((GR_IS_GR_GL(fStandard) && (
+          (glVer >= GR_GL_VER(4,3)) ||
+          fExtensions.has("GL_ARB_ES2_compatibility"))) ||
+       GR_IS_GR_GL_ES(fStandard) ||
+       GR_IS_GR_WEBGL(fStandard)) {
+        if (!fFunctions.fGetShaderPrecisionFormat) {
+            RETURN_FALSE_INTERFACE;
+        }
+    }
+
+
+    // End autogenerated content
+    return true;
+}
+
+#if GR_TEST_UTILS
+
+void GrGLInterface::abandon() const {
+    const_cast<GrGLInterface*>(this)->fFunctions = GrGLInterface::Functions();
+}
+
+#endif // GR_TEST_UTILS
diff --git a/src/gpu/gl/GrGLPathRendering.cpp b/src/gpu/gl/GrGLPathRendering.cpp
index c1f05af..19e8bba 100644
--- a/src/gpu/gl/GrGLPathRendering.cpp
+++ b/src/gpu/gl/GrGLPathRendering.cpp
@@ -92,7 +92,7 @@
     SkISize size = SkISize::Make(rt->width(), rt->height());
     this->setProjectionMatrix(*args.fViewMatrix, size, args.fProxy->origin());
     gpu->flushScissor(*args.fScissor, rt->getViewport(), args.fProxy->origin());
-    gpu->flushHWAAState(rt, args.fUseHWAA, true);
+    gpu->flushHWAAState(rt, args.fUseHWAA);
     gpu->flushRenderTarget(rt);
 
     const GrGLPath* glPath = static_cast<const GrGLPath*>(path);
diff --git a/src/gpu/gl/GrGLRenderTarget.h b/src/gpu/gl/GrGLRenderTarget.h
index caedf08..49f1631 100644
--- a/src/gpu/gl/GrGLRenderTarget.h
+++ b/src/gpu/gl/GrGLRenderTarget.h
@@ -95,8 +95,6 @@
 
     size_t onGpuMemorySize() const override;
 
-    void onSetRelease(sk_sp<GrReleaseProcHelper> releaseHelper) override {}
-
     int msaaSamples() const;
     // The number total number of samples, including both MSAA and resolve texture samples.
     int totalSamples() const;
diff --git a/src/gpu/gl/GrGLTestInterface.cpp b/src/gpu/gl/GrGLTestInterface.cpp
deleted file mode 100644
index c6539c1..0000000
--- a/src/gpu/gl/GrGLTestInterface.cpp
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * 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 "GrGLTestInterface.h"
-
-namespace {
-template <typename R, typename... A>
-GrGLFunction<R GR_GL_FUNCTION_TYPE(A...)> bind_to_member(GrGLTestInterface* interface,
-                                                         R (GrGLTestInterface::*member)(A...)) {
-    return [interface, member](A... a) -> R { return (interface->*member)(a...); };
-}
-}  // anonymous namespace
-
-GrGLTestInterface::GrGLTestInterface() {
-    fFunctions.fActiveTexture = bind_to_member(this, &GrGLTestInterface::activeTexture);
-    fFunctions.fAttachShader = bind_to_member(this, &GrGLTestInterface::attachShader);
-    fFunctions.fBeginQuery = bind_to_member(this, &GrGLTestInterface::beginQuery);
-    fFunctions.fBindAttribLocation = bind_to_member(this, &GrGLTestInterface::bindAttribLocation);
-    fFunctions.fBindBuffer = bind_to_member(this, &GrGLTestInterface::bindBuffer);
-    fFunctions.fBindFramebuffer = bind_to_member(this, &GrGLTestInterface::bindFramebuffer);
-    fFunctions.fBindRenderbuffer = bind_to_member(this, &GrGLTestInterface::bindRenderbuffer);
-    fFunctions.fBindSampler = bind_to_member(this, &GrGLTestInterface::bindSampler);
-    fFunctions.fBindTexture = bind_to_member(this, &GrGLTestInterface::bindTexture);
-    fFunctions.fBindFragDataLocation = bind_to_member(this, &GrGLTestInterface::bindFragDataLocation);
-    fFunctions.fBindFragDataLocationIndexed = bind_to_member(this, &GrGLTestInterface::bindFragDataLocationIndexed);
-    fFunctions.fBindVertexArray = bind_to_member(this, &GrGLTestInterface::bindVertexArray);
-    fFunctions.fBlendBarrier = bind_to_member(this, &GrGLTestInterface::blendBarrier);
-    fFunctions.fBlendColor = bind_to_member(this, &GrGLTestInterface::blendColor);
-    fFunctions.fBlendEquation = bind_to_member(this, &GrGLTestInterface::blendEquation);
-    fFunctions.fBlendFunc = bind_to_member(this, &GrGLTestInterface::blendFunc);
-    fFunctions.fBlitFramebuffer = bind_to_member(this, &GrGLTestInterface::blitFramebuffer);
-    fFunctions.fBufferData = bind_to_member(this, &GrGLTestInterface::bufferData);
-    fFunctions.fBufferSubData = bind_to_member(this, &GrGLTestInterface::bufferSubData);
-    fFunctions.fCheckFramebufferStatus = bind_to_member(this, &GrGLTestInterface::checkFramebufferStatus);
-    fFunctions.fClear = bind_to_member(this, &GrGLTestInterface::clear);
-    fFunctions.fClearColor = bind_to_member(this, &GrGLTestInterface::clearColor);
-    fFunctions.fClearStencil = bind_to_member(this, &GrGLTestInterface::clearStencil);
-    fFunctions.fColorMask = bind_to_member(this, &GrGLTestInterface::colorMask);
-    fFunctions.fCompileShader = bind_to_member(this, &GrGLTestInterface::compileShader);
-    fFunctions.fCompressedTexImage2D = bind_to_member(this, &GrGLTestInterface::compressedTexImage2D);
-    fFunctions.fCompressedTexSubImage2D = bind_to_member(this, &GrGLTestInterface::compressedTexSubImage2D);
-    fFunctions.fCopyTexSubImage2D = bind_to_member(this, &GrGLTestInterface::copyTexSubImage2D);
-    fFunctions.fCreateProgram = bind_to_member(this, &GrGLTestInterface::createProgram);
-    fFunctions.fCreateShader = bind_to_member(this, &GrGLTestInterface::createShader);
-    fFunctions.fCullFace = bind_to_member(this, &GrGLTestInterface::cullFace);
-    fFunctions.fDeleteBuffers = bind_to_member(this, &GrGLTestInterface::deleteBuffers);
-    fFunctions.fDeleteFramebuffers = bind_to_member(this, &GrGLTestInterface::deleteFramebuffers);
-    fFunctions.fDeleteProgram = bind_to_member(this, &GrGLTestInterface::deleteProgram);
-    fFunctions.fDeleteQueries = bind_to_member(this, &GrGLTestInterface::deleteQueries);
-    fFunctions.fDeleteRenderbuffers = bind_to_member(this, &GrGLTestInterface::deleteRenderbuffers);
-    fFunctions.fDeleteSamplers = bind_to_member(this, &GrGLTestInterface::deleteSamplers);
-    fFunctions.fDeleteShader = bind_to_member(this, &GrGLTestInterface::deleteShader);
-    fFunctions.fDeleteTextures = bind_to_member(this, &GrGLTestInterface::deleteTextures);
-    fFunctions.fDeleteVertexArrays = bind_to_member(this, &GrGLTestInterface::deleteVertexArrays);
-    fFunctions.fDepthMask = bind_to_member(this, &GrGLTestInterface::depthMask);
-    fFunctions.fDisable = bind_to_member(this, &GrGLTestInterface::disable);
-    fFunctions.fDisableVertexAttribArray = bind_to_member(this, &GrGLTestInterface::disableVertexAttribArray);
-    fFunctions.fDrawArrays = bind_to_member(this, &GrGLTestInterface::drawArrays);
-    fFunctions.fDrawArraysInstanced = bind_to_member(this, &GrGLTestInterface::drawArraysInstanced);
-    fFunctions.fDrawArraysIndirect = bind_to_member(this, &GrGLTestInterface::drawArraysIndirect);
-    fFunctions.fDrawBuffer = bind_to_member(this, &GrGLTestInterface::drawBuffer);
-    fFunctions.fDrawBuffers = bind_to_member(this, &GrGLTestInterface::drawBuffers);
-    fFunctions.fDrawElements = bind_to_member(this, &GrGLTestInterface::drawElements);
-    fFunctions.fDrawElementsInstanced = bind_to_member(this, &GrGLTestInterface::drawElementsInstanced);
-    fFunctions.fDrawElementsIndirect = bind_to_member(this, &GrGLTestInterface::drawElementsIndirect);
-    fFunctions.fDrawRangeElements = bind_to_member(this, &GrGLTestInterface::drawRangeElements);
-    fFunctions.fEnable = bind_to_member(this, &GrGLTestInterface::enable);
-    fFunctions.fEnableVertexAttribArray = bind_to_member(this, &GrGLTestInterface::enableVertexAttribArray);
-    fFunctions.fEndQuery = bind_to_member(this, &GrGLTestInterface::endQuery);
-    fFunctions.fFinish = bind_to_member(this, &GrGLTestInterface::finish);
-    fFunctions.fFlush = bind_to_member(this, &GrGLTestInterface::flush);
-    fFunctions.fFlushMappedBufferRange = bind_to_member(this, &GrGLTestInterface::flushMappedBufferRange);
-    fFunctions.fFramebufferRenderbuffer = bind_to_member(this, &GrGLTestInterface::framebufferRenderbuffer);
-    fFunctions.fFramebufferTexture2D = bind_to_member(this, &GrGLTestInterface::framebufferTexture2D);
-    fFunctions.fFramebufferTexture2DMultisample = bind_to_member(this, &GrGLTestInterface::framebufferTexture2DMultisample);
-    fFunctions.fFrontFace = bind_to_member(this, &GrGLTestInterface::frontFace);
-    fFunctions.fGenBuffers = bind_to_member(this, &GrGLTestInterface::genBuffers);
-    fFunctions.fGenFramebuffers = bind_to_member(this, &GrGLTestInterface::genFramebuffers);
-    fFunctions.fGenerateMipmap = bind_to_member(this, &GrGLTestInterface::generateMipmap);
-    fFunctions.fGenQueries = bind_to_member(this, &GrGLTestInterface::genQueries);
-    fFunctions.fGenRenderbuffers = bind_to_member(this, &GrGLTestInterface::genRenderbuffers);
-    fFunctions.fGenSamplers = bind_to_member(this, &GrGLTestInterface::genSamplers);
-    fFunctions.fGenTextures = bind_to_member(this, &GrGLTestInterface::genTextures);
-    fFunctions.fGenVertexArrays = bind_to_member(this, &GrGLTestInterface::genVertexArrays);
-    fFunctions.fGetBufferParameteriv = bind_to_member(this, &GrGLTestInterface::getBufferParameteriv);
-    fFunctions.fGetError = bind_to_member(this, &GrGLTestInterface::getError);
-    fFunctions.fGetFramebufferAttachmentParameteriv = bind_to_member(this, &GrGLTestInterface::getFramebufferAttachmentParameteriv);
-    fFunctions.fGetIntegerv = bind_to_member(this, &GrGLTestInterface::getIntegerv);
-    fFunctions.fGetMultisamplefv = bind_to_member(this, &GrGLTestInterface::getMultisamplefv);
-    fFunctions.fGetProgramInfoLog = bind_to_member(this, &GrGLTestInterface::getProgramInfoLog);
-    fFunctions.fGetProgramiv = bind_to_member(this, &GrGLTestInterface::getProgramiv);
-    fFunctions.fGetQueryiv = bind_to_member(this, &GrGLTestInterface::getQueryiv);
-    fFunctions.fGetQueryObjecti64v = bind_to_member(this, &GrGLTestInterface::getQueryObjecti64v);
-    fFunctions.fGetQueryObjectiv = bind_to_member(this, &GrGLTestInterface::getQueryObjectiv);
-    fFunctions.fGetQueryObjectui64v = bind_to_member(this, &GrGLTestInterface::getQueryObjectui64v);
-    fFunctions.fGetQueryObjectuiv = bind_to_member(this, &GrGLTestInterface::getQueryObjectuiv);
-    fFunctions.fGetRenderbufferParameteriv = bind_to_member(this, &GrGLTestInterface::getRenderbufferParameteriv);
-    fFunctions.fGetShaderInfoLog = bind_to_member(this, &GrGLTestInterface::getShaderInfoLog);
-    fFunctions.fGetShaderiv = bind_to_member(this, &GrGLTestInterface::getShaderiv);
-    fFunctions.fGetShaderPrecisionFormat = bind_to_member(this, &GrGLTestInterface::getShaderPrecisionFormat);
-    fFunctions.fGetString = bind_to_member(this, &GrGLTestInterface::getString);
-    fFunctions.fGetStringi = bind_to_member(this, &GrGLTestInterface::getStringi);
-    fFunctions.fGetTexLevelParameteriv = bind_to_member(this, &GrGLTestInterface::getTexLevelParameteriv);
-    fFunctions.fGetUniformLocation = bind_to_member(this, &GrGLTestInterface::getUniformLocation);
-    fFunctions.fInsertEventMarker = bind_to_member(this, &GrGLTestInterface::insertEventMarker);
-    fFunctions.fInvalidateBufferData = bind_to_member(this, &GrGLTestInterface::invalidateBufferData);
-    fFunctions.fInvalidateBufferSubData = bind_to_member(this, &GrGLTestInterface::invalidateBufferSubData);
-    fFunctions.fInvalidateFramebuffer = bind_to_member(this, &GrGLTestInterface::invalidateFramebuffer);
-    fFunctions.fInvalidateSubFramebuffer = bind_to_member(this, &GrGLTestInterface::invalidateSubFramebuffer);
-    fFunctions.fInvalidateTexImage = bind_to_member(this, &GrGLTestInterface::invalidateTexImage);
-    fFunctions.fInvalidateTexSubImage = bind_to_member(this, &GrGLTestInterface::invalidateTexSubImage);
-    fFunctions.fIsTexture = bind_to_member(this, &GrGLTestInterface::isTexture);
-    fFunctions.fLineWidth = bind_to_member(this, &GrGLTestInterface::lineWidth);
-    fFunctions.fLinkProgram = bind_to_member(this, &GrGLTestInterface::linkProgram);
-    fFunctions.fMapBuffer = bind_to_member(this, &GrGLTestInterface::mapBuffer);
-    fFunctions.fMapBufferRange = bind_to_member(this, &GrGLTestInterface::mapBufferRange);
-    fFunctions.fMapBufferSubData = bind_to_member(this, &GrGLTestInterface::mapBufferSubData);
-    fFunctions.fMapTexSubImage2D = bind_to_member(this, &GrGLTestInterface::mapTexSubImage2D);
-    fFunctions.fPixelStorei = bind_to_member(this, &GrGLTestInterface::pixelStorei);
-    fFunctions.fPolygonMode = bind_to_member(this, &GrGLTestInterface::polygonMode);
-    fFunctions.fPopGroupMarker = bind_to_member(this, &GrGLTestInterface::popGroupMarker);
-    fFunctions.fPushGroupMarker = bind_to_member(this, &GrGLTestInterface::pushGroupMarker);
-    fFunctions.fQueryCounter = bind_to_member(this, &GrGLTestInterface::queryCounter);
-    fFunctions.fRasterSamples = bind_to_member(this, &GrGLTestInterface::rasterSamples);
-    fFunctions.fReadBuffer = bind_to_member(this, &GrGLTestInterface::readBuffer);
-    fFunctions.fReadPixels = bind_to_member(this, &GrGLTestInterface::readPixels);
-    fFunctions.fRenderbufferStorage = bind_to_member(this, &GrGLTestInterface::renderbufferStorage);
-    fFunctions.fRenderbufferStorageMultisample = bind_to_member(this, &GrGLTestInterface::renderbufferStorageMultisample);
-    fFunctions.fResolveMultisampleFramebuffer = bind_to_member(this, &GrGLTestInterface::resolveMultisampleFramebuffer);
-    fFunctions.fScissor = bind_to_member(this, &GrGLTestInterface::scissor);
-    fFunctions.fBindUniformLocation = bind_to_member(this, &GrGLTestInterface::bindUniformLocation);
-    fFunctions.fSamplerParameteri = bind_to_member(this, &GrGLTestInterface::samplerParameteri);
-    fFunctions.fSamplerParameteriv = bind_to_member(this, &GrGLTestInterface::samplerParameteriv);
-    fFunctions.fShaderSource = bind_to_member(this, &GrGLTestInterface::shaderSource);
-    fFunctions.fStencilFunc = bind_to_member(this, &GrGLTestInterface::stencilFunc);
-    fFunctions.fStencilFuncSeparate = bind_to_member(this, &GrGLTestInterface::stencilFuncSeparate);
-    fFunctions.fStencilMask = bind_to_member(this, &GrGLTestInterface::stencilMask);
-    fFunctions.fStencilMaskSeparate = bind_to_member(this, &GrGLTestInterface::stencilMaskSeparate);
-    fFunctions.fStencilOp = bind_to_member(this, &GrGLTestInterface::stencilOp);
-    fFunctions.fStencilOpSeparate = bind_to_member(this, &GrGLTestInterface::stencilOpSeparate);
-    fFunctions.fTexBuffer = bind_to_member(this, &GrGLTestInterface::texBuffer);
-    fFunctions.fTexImage2D = bind_to_member(this, &GrGLTestInterface::texImage2D);
-    fFunctions.fTexParameterf = bind_to_member(this, &GrGLTestInterface::texParameterf);
-    fFunctions.fTexParameterfv = bind_to_member(this, &GrGLTestInterface::texParameterfv);
-    fFunctions.fTexParameteri = bind_to_member(this, &GrGLTestInterface::texParameteri);
-    fFunctions.fTexParameteriv = bind_to_member(this, &GrGLTestInterface::texParameteriv);
-    fFunctions.fTexStorage2D = bind_to_member(this, &GrGLTestInterface::texStorage2D);
-    fFunctions.fDiscardFramebuffer = bind_to_member(this, &GrGLTestInterface::discardFramebuffer);
-    fFunctions.fTexSubImage2D = bind_to_member(this, &GrGLTestInterface::texSubImage2D);
-    fFunctions.fTextureBarrier = bind_to_member(this, &GrGLTestInterface::textureBarrier);
-    fFunctions.fUniform1f = bind_to_member(this, &GrGLTestInterface::uniform1f);
-    fFunctions.fUniform1i = bind_to_member(this, &GrGLTestInterface::uniform1i);
-    fFunctions.fUniform1fv = bind_to_member(this, &GrGLTestInterface::uniform1fv);
-    fFunctions.fUniform1iv = bind_to_member(this, &GrGLTestInterface::uniform1iv);
-    fFunctions.fUniform2f = bind_to_member(this, &GrGLTestInterface::uniform2f);
-    fFunctions.fUniform2i = bind_to_member(this, &GrGLTestInterface::uniform2i);
-    fFunctions.fUniform2fv = bind_to_member(this, &GrGLTestInterface::uniform2fv);
-    fFunctions.fUniform2iv = bind_to_member(this, &GrGLTestInterface::uniform2iv);
-    fFunctions.fUniform3f = bind_to_member(this, &GrGLTestInterface::uniform3f);
-    fFunctions.fUniform3i = bind_to_member(this, &GrGLTestInterface::uniform3i);
-    fFunctions.fUniform3fv = bind_to_member(this, &GrGLTestInterface::uniform3fv);
-    fFunctions.fUniform3iv = bind_to_member(this, &GrGLTestInterface::uniform3iv);
-    fFunctions.fUniform4f = bind_to_member(this, &GrGLTestInterface::uniform4f);
-    fFunctions.fUniform4i = bind_to_member(this, &GrGLTestInterface::uniform4i);
-    fFunctions.fUniform4fv = bind_to_member(this, &GrGLTestInterface::uniform4fv);
-    fFunctions.fUniform4iv = bind_to_member(this, &GrGLTestInterface::uniform4iv);
-    fFunctions.fUniformMatrix2fv = bind_to_member(this, &GrGLTestInterface::uniformMatrix2fv);
-    fFunctions.fUniformMatrix3fv = bind_to_member(this, &GrGLTestInterface::uniformMatrix3fv);
-    fFunctions.fUniformMatrix4fv = bind_to_member(this, &GrGLTestInterface::uniformMatrix4fv);
-    fFunctions.fUnmapBuffer = bind_to_member(this, &GrGLTestInterface::unmapBuffer);
-    fFunctions.fUnmapBufferSubData = bind_to_member(this, &GrGLTestInterface::unmapBufferSubData);
-    fFunctions.fUnmapTexSubImage2D = bind_to_member(this, &GrGLTestInterface::unmapTexSubImage2D);
-    fFunctions.fUseProgram = bind_to_member(this, &GrGLTestInterface::useProgram);
-    fFunctions.fVertexAttrib1f = bind_to_member(this, &GrGLTestInterface::vertexAttrib1f);
-    fFunctions.fVertexAttrib2fv = bind_to_member(this, &GrGLTestInterface::vertexAttrib2fv);
-    fFunctions.fVertexAttrib3fv = bind_to_member(this, &GrGLTestInterface::vertexAttrib3fv);
-    fFunctions.fVertexAttrib4fv = bind_to_member(this, &GrGLTestInterface::vertexAttrib4fv);
-    fFunctions.fVertexAttribDivisor = bind_to_member(this, &GrGLTestInterface::vertexAttribDivisor);
-    fFunctions.fVertexAttribIPointer = bind_to_member(this, &GrGLTestInterface::vertexAttribIPointer);
-    fFunctions.fVertexAttribPointer = bind_to_member(this, &GrGLTestInterface::vertexAttribPointer);
-    fFunctions.fViewport = bind_to_member(this, &GrGLTestInterface::viewport);
-    fFunctions.fMatrixLoadf = bind_to_member(this, &GrGLTestInterface::matrixLoadf);
-    fFunctions.fMatrixLoadIdentity = bind_to_member(this, &GrGLTestInterface::matrixLoadIdentity);
-    fFunctions.fPathCommands = bind_to_member(this, &GrGLTestInterface::pathCommands);
-    fFunctions.fPathParameteri = bind_to_member(this, &GrGLTestInterface::pathParameteri);
-    fFunctions.fPathParameterf = bind_to_member(this, &GrGLTestInterface::pathParameterf);
-    fFunctions.fGenPaths = bind_to_member(this, &GrGLTestInterface::genPaths);
-    fFunctions.fDeletePaths = bind_to_member(this, &GrGLTestInterface::deletePaths);
-    fFunctions.fIsPath = bind_to_member(this, &GrGLTestInterface::isPath);
-    fFunctions.fPathStencilFunc = bind_to_member(this, &GrGLTestInterface::pathStencilFunc);
-    fFunctions.fStencilFillPath = bind_to_member(this, &GrGLTestInterface::stencilFillPath);
-    fFunctions.fStencilStrokePath = bind_to_member(this, &GrGLTestInterface::stencilStrokePath);
-    fFunctions.fStencilFillPathInstanced = bind_to_member(this, &GrGLTestInterface::stencilFillPathInstanced);
-    fFunctions.fStencilStrokePathInstanced = bind_to_member(this, &GrGLTestInterface::stencilStrokePathInstanced);
-    fFunctions.fCoverFillPath = bind_to_member(this, &GrGLTestInterface::coverFillPath);
-    fFunctions.fCoverStrokePath = bind_to_member(this, &GrGLTestInterface::coverStrokePath);
-    fFunctions.fCoverFillPathInstanced = bind_to_member(this, &GrGLTestInterface::coverFillPathInstanced);
-    fFunctions.fCoverStrokePathInstanced = bind_to_member(this, &GrGLTestInterface::coverStrokePathInstanced);
-    fFunctions.fStencilThenCoverFillPath = bind_to_member(this, &GrGLTestInterface::stencilThenCoverFillPath);
-    fFunctions.fStencilThenCoverStrokePath = bind_to_member(this, &GrGLTestInterface::stencilThenCoverStrokePath);
-    fFunctions.fStencilThenCoverFillPathInstanced = bind_to_member(this, &GrGLTestInterface::stencilThenCoverFillPathInstanced);
-    fFunctions.fStencilThenCoverStrokePathInstanced = bind_to_member(this, &GrGLTestInterface::stencilThenCoverStrokePathInstanced);
-    fFunctions.fProgramPathFragmentInputGen = bind_to_member(this, &GrGLTestInterface::programPathFragmentInputGen);
-    fFunctions.fBindFragmentInputLocation = bind_to_member(this, &GrGLTestInterface::bindFragmentInputLocation);
-    fFunctions.fGetProgramResourceLocation = bind_to_member(this, &GrGLTestInterface::getProgramResourceLocation);
-    fFunctions.fCoverageModulation = bind_to_member(this, &GrGLTestInterface::coverageModulation);
-    fFunctions.fMultiDrawArraysIndirect = bind_to_member(this, &GrGLTestInterface::multiDrawArraysIndirect);
-    fFunctions.fMultiDrawElementsIndirect = bind_to_member(this, &GrGLTestInterface::multiDrawElementsIndirect);
-    fFunctions.fFenceSync = bind_to_member(this, &GrGLTestInterface::fenceSync);
-    fFunctions.fIsSync = bind_to_member(this, &GrGLTestInterface::isSync);
-    fFunctions.fClientWaitSync = bind_to_member(this, &GrGLTestInterface::clientWaitSync);
-    fFunctions.fWaitSync = bind_to_member(this, &GrGLTestInterface::waitSync);
-    fFunctions.fDeleteSync = bind_to_member(this, &GrGLTestInterface::deleteSync);
-    fFunctions.fDebugMessageControl = bind_to_member(this, &GrGLTestInterface::debugMessageControl);
-    fFunctions.fDebugMessageInsert = bind_to_member(this, &GrGLTestInterface::debugMessageInsert);
-    fFunctions.fDebugMessageCallback = bind_to_member(this, &GrGLTestInterface::debugMessageCallback);
-    fFunctions.fGetDebugMessageLog = bind_to_member(this, &GrGLTestInterface::getDebugMessageLog);
-    fFunctions.fPushDebugGroup = bind_to_member(this, &GrGLTestInterface::pushDebugGroup);
-    fFunctions.fPopDebugGroup = bind_to_member(this, &GrGLTestInterface::popDebugGroup);
-    fFunctions.fObjectLabel = bind_to_member(this, &GrGLTestInterface::objectLabel);
-    fFunctions.fGetInternalformativ = bind_to_member(this, &GrGLTestInterface::getInternalformativ);
-    fFunctions.fProgramBinary = bind_to_member(this, &GrGLTestInterface::programBinary);
-    fFunctions.fGetProgramBinary = bind_to_member(this, &GrGLTestInterface::getProgramBinary);
-    fFunctions.fProgramParameteri = bind_to_member(this, &GrGLTestInterface::programParameteri);
-}
diff --git a/src/gpu/gl/GrGLTestInterface.h b/src/gpu/gl/GrGLTestInterface.h
deleted file mode 100644
index 4c791f2..0000000
--- a/src/gpu/gl/GrGLTestInterface.h
+++ /dev/null
@@ -1,350 +0,0 @@
-/*
- * 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 GrGLTestInterface_DEFINED
-#define GrGLTestInterface_DEFINED
-
-#include "gl/GrGLInterface.h"
-#include "GrGLDefines.h"
-
-/**
- * Base class for interfaces used for Skia testing. We would like to move this to tools/gpu/gl
- * when Chromium is no longer using GrGLCreateNullInterface in its unit testing.
- */
-class GrGLTestInterface : public GrGLInterface {
-public:
-    virtual GrGLvoid activeTexture(GrGLenum texture) {}
-    virtual GrGLvoid attachShader(GrGLuint program, GrGLuint shader) {}
-    virtual GrGLvoid beginQuery(GrGLenum target, GrGLuint id) {}
-    virtual GrGLvoid bindAttribLocation(GrGLuint program, GrGLuint index, const char* name) {}
-    virtual GrGLvoid bindBuffer(GrGLenum target, GrGLuint buffer) {}
-    virtual GrGLvoid bindFramebuffer(GrGLenum target, GrGLuint framebuffer) {}
-    virtual GrGLvoid bindRenderbuffer(GrGLenum target, GrGLuint renderbuffer) {}
-    virtual GrGLvoid bindSampler(GrGLuint unit, GrGLuint sampler) {}
-    virtual GrGLvoid bindTexture(GrGLenum target, GrGLuint texture) {}
-    virtual GrGLvoid bindFragDataLocation(GrGLuint program, GrGLuint colorNumber, const GrGLchar* name) {}
-    virtual GrGLvoid bindFragDataLocationIndexed(GrGLuint program, GrGLuint colorNumber, GrGLuint index, const GrGLchar * name) {}
-    virtual GrGLvoid bindVertexArray(GrGLuint array) {}
-    virtual GrGLvoid blendBarrier() {}
-    virtual GrGLvoid blendColor(GrGLclampf red, GrGLclampf green, GrGLclampf blue, GrGLclampf alpha) {}
-    virtual GrGLvoid blendEquation(GrGLenum mode) {}
-    virtual GrGLvoid blendFunc(GrGLenum sfactor, GrGLenum dfactor) {}
-    virtual GrGLvoid blitFramebuffer(GrGLint srcX0, GrGLint srcY0, GrGLint srcX1, GrGLint srcY1, GrGLint dstX0, GrGLint dstY0, GrGLint dstX1, GrGLint dstY1, GrGLbitfield mask, GrGLenum filter) {}
-    virtual GrGLvoid bufferData(GrGLenum target, GrGLsizeiptr size, const GrGLvoid* data, GrGLenum usage) {}
-    virtual GrGLvoid bufferSubData(GrGLenum target, GrGLintptr offset, GrGLsizeiptr size, const GrGLvoid* data) {}
-    virtual GrGLenum checkFramebufferStatus(GrGLenum target) { return GR_GL_FRAMEBUFFER_COMPLETE; }
-    virtual GrGLvoid clear(GrGLbitfield mask) {}
-    virtual GrGLvoid clearColor(GrGLclampf red, GrGLclampf green, GrGLclampf blue, GrGLclampf alpha) {}
-    virtual GrGLvoid clearStencil(GrGLint s) {}
-    virtual GrGLvoid colorMask(GrGLboolean red, GrGLboolean green, GrGLboolean blue, GrGLboolean alpha) {}
-    virtual GrGLvoid compileShader(GrGLuint shader) {}
-    virtual GrGLvoid compressedTexImage2D(GrGLenum target, GrGLint level, GrGLenum internalformat, GrGLsizei width, GrGLsizei height, GrGLint border, GrGLsizei imageSize, const GrGLvoid* data) {}
-    virtual GrGLvoid compressedTexSubImage2D(GrGLenum target, GrGLint level, GrGLint xoffset, GrGLint yoffset, GrGLsizei width, GrGLsizei height, GrGLenum format, GrGLsizei imageSize, const GrGLvoid* data) {}
-    virtual GrGLvoid copyTexSubImage2D(GrGLenum target, GrGLint level, GrGLint xoffset, GrGLint yoffset, GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height) {}
-    virtual GrGLuint createProgram() { return 0; }
-    virtual GrGLuint createShader(GrGLenum type) { return 0; }
-    virtual GrGLvoid cullFace(GrGLenum mode) {}
-    virtual GrGLvoid deleteBuffers(GrGLsizei n, const GrGLuint* buffers) {}
-    virtual GrGLvoid deleteFramebuffers(GrGLsizei n, const GrGLuint *framebuffers) {}
-    virtual GrGLvoid deleteProgram(GrGLuint program) {}
-    virtual GrGLvoid deleteQueries(GrGLsizei n, const GrGLuint *ids) {}
-    virtual GrGLvoid deleteRenderbuffers(GrGLsizei n, const GrGLuint *renderbuffers) {}
-    virtual GrGLvoid deleteSamplers(GrGLsizei n, const GrGLuint* samplers) {}
-    virtual GrGLvoid deleteShader(GrGLuint shader) {}
-    virtual GrGLvoid deleteTextures(GrGLsizei n, const GrGLuint* textures) {}
-    virtual GrGLvoid deleteVertexArrays(GrGLsizei n, const GrGLuint *arrays) {}
-    virtual GrGLvoid depthMask(GrGLboolean flag) {}
-    virtual GrGLvoid disable(GrGLenum cap) {}
-    virtual GrGLvoid disableVertexAttribArray(GrGLuint index) {}
-    virtual GrGLvoid drawArrays(GrGLenum mode, GrGLint first, GrGLsizei count) {}
-    virtual GrGLvoid drawArraysInstanced(GrGLenum mode, GrGLint first, GrGLsizei count, GrGLsizei primcount) {}
-    virtual GrGLvoid drawArraysIndirect(GrGLenum mode, const GrGLvoid* indirect) {}
-    virtual GrGLvoid drawBuffer(GrGLenum mode) {}
-    virtual GrGLvoid drawBuffers(GrGLsizei n, const GrGLenum* bufs) {}
-    virtual GrGLvoid drawElements(GrGLenum mode, GrGLsizei count, GrGLenum type, const GrGLvoid* indices) {}
-    virtual GrGLvoid drawElementsInstanced(GrGLenum mode, GrGLsizei count, GrGLenum type, const GrGLvoid *indices, GrGLsizei primcount) {}
-    virtual GrGLvoid drawElementsIndirect(GrGLenum mode, GrGLenum type, const GrGLvoid* indirect) {}
-    virtual GrGLvoid drawRangeElements(GrGLenum mode, GrGLuint start, GrGLuint end, GrGLsizei count, GrGLenum type, const GrGLvoid* indices) {}
-    virtual GrGLvoid enable(GrGLenum cap) {}
-    virtual GrGLvoid enableVertexAttribArray(GrGLuint index) {}
-    virtual GrGLvoid endQuery(GrGLenum target) {}
-    virtual GrGLvoid finish() {}
-    virtual GrGLvoid flush() {}
-    virtual GrGLvoid flushMappedBufferRange(GrGLenum target, GrGLintptr offset, GrGLsizeiptr length) {}
-    virtual GrGLvoid framebufferRenderbuffer(GrGLenum target, GrGLenum attachment, GrGLenum renderbuffertarget, GrGLuint renderbuffer) {}
-    virtual GrGLvoid framebufferTexture2D(GrGLenum target, GrGLenum attachment, GrGLenum textarget, GrGLuint texture, GrGLint level) {}
-    virtual GrGLvoid framebufferTexture2DMultisample(GrGLenum target, GrGLenum attachment, GrGLenum textarget, GrGLuint texture, GrGLint level, GrGLsizei samples) {}
-    virtual GrGLvoid frontFace(GrGLenum mode) {}
-    virtual GrGLvoid genBuffers(GrGLsizei n, GrGLuint* buffers) {}
-    virtual GrGLvoid genFramebuffers(GrGLsizei n, GrGLuint *framebuffers) {}
-    virtual GrGLvoid generateMipmap(GrGLenum target) {}
-    virtual GrGLvoid genQueries(GrGLsizei n, GrGLuint *ids) {}
-    virtual GrGLvoid genRenderbuffers(GrGLsizei n, GrGLuint *renderbuffers) {}
-    virtual GrGLvoid genSamplers(GrGLsizei n, GrGLuint *samplers) {}
-    virtual GrGLvoid genTextures(GrGLsizei n, GrGLuint* textures) {}
-    virtual GrGLvoid genVertexArrays(GrGLsizei n, GrGLuint *arrays) {}
-    virtual GrGLvoid getBufferParameteriv(GrGLenum target, GrGLenum pname, GrGLint* params) {}
-    virtual GrGLenum getError() { return GR_GL_NO_ERROR; }
-    virtual GrGLvoid getFramebufferAttachmentParameteriv(GrGLenum target, GrGLenum attachment, GrGLenum pname, GrGLint* params) {}
-    virtual GrGLvoid getIntegerv(GrGLenum pname, GrGLint* params) {}
-    virtual GrGLvoid getMultisamplefv(GrGLenum pname, GrGLuint index, GrGLfloat* val) {}
-    virtual GrGLvoid getProgramInfoLog(GrGLuint program, GrGLsizei bufsize, GrGLsizei* length, char* infolog) {}
-    virtual GrGLvoid getProgramiv(GrGLuint program, GrGLenum pname, GrGLint* params) {}
-    virtual GrGLvoid getQueryiv(GrGLenum GLtarget, GrGLenum pname, GrGLint *params) {}
-    virtual GrGLvoid getQueryObjecti64v(GrGLuint id, GrGLenum pname, GrGLint64 *params) {}
-    virtual GrGLvoid getQueryObjectiv(GrGLuint id, GrGLenum pname, GrGLint *params) {}
-    virtual GrGLvoid getQueryObjectui64v(GrGLuint id, GrGLenum pname, GrGLuint64 *params) {}
-    virtual GrGLvoid getQueryObjectuiv(GrGLuint id, GrGLenum pname, GrGLuint *params) {}
-    virtual GrGLvoid getRenderbufferParameteriv(GrGLenum target, GrGLenum pname, GrGLint* params) {}
-    virtual GrGLvoid getShaderInfoLog(GrGLuint shader, GrGLsizei bufsize, GrGLsizei* length, char* infolog) {}
-    virtual GrGLvoid getShaderiv(GrGLuint shader, GrGLenum pname, GrGLint* params) {}
-    virtual GrGLvoid getShaderPrecisionFormat(GrGLenum shadertype, GrGLenum precisiontype, GrGLint *range, GrGLint *precision) {}
-    virtual const GrGLubyte*  getString(GrGLenum name) { return nullptr; }
-    virtual const GrGLubyte* getStringi(GrGLenum name, GrGLuint index) { return nullptr; }
-    virtual GrGLvoid getTexLevelParameteriv(GrGLenum target, GrGLint level, GrGLenum pname, GrGLint* params) {}
-    virtual GrGLint getUniformLocation(GrGLuint program, const char* name) { return 0; }
-    virtual GrGLvoid insertEventMarker(GrGLsizei length, const char* marker) {}
-    virtual GrGLvoid invalidateBufferData(GrGLuint buffer) {}
-    virtual GrGLvoid invalidateBufferSubData(GrGLuint buffer, GrGLintptr offset, GrGLsizeiptr length) {}
-    virtual GrGLvoid invalidateFramebuffer(GrGLenum target, GrGLsizei numAttachments,  const GrGLenum *attachments) {}
-    virtual GrGLvoid invalidateSubFramebuffer(GrGLenum target, GrGLsizei numAttachments, const GrGLenum *attachments, GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height) {}
-    virtual GrGLvoid invalidateTexImage(GrGLuint texture, GrGLint level) {}
-    virtual GrGLvoid invalidateTexSubImage(GrGLuint texture, GrGLint level, GrGLint xoffset, GrGLint yoffset, GrGLint zoffset, GrGLsizei width, GrGLsizei height, GrGLsizei depth) {}
-    virtual GrGLboolean isTexture(GrGLuint texture) { return GR_GL_FALSE; }
-    virtual GrGLvoid lineWidth(GrGLfloat width) {}
-    virtual GrGLvoid linkProgram(GrGLuint program) {}
-    virtual GrGLvoid* mapBuffer(GrGLenum target, GrGLenum access) { return nullptr; }
-    virtual GrGLvoid* mapBufferRange(GrGLenum target, GrGLintptr offset, GrGLsizeiptr length, GrGLbitfield access) { return nullptr; }
-    virtual GrGLvoid* mapBufferSubData(GrGLuint target, GrGLintptr offset, GrGLsizeiptr size, GrGLenum access) { return nullptr; }
-    virtual GrGLvoid* mapTexSubImage2D(GrGLenum target, GrGLint level, GrGLint xoffset, GrGLint yoffset, GrGLsizei width, GrGLsizei height, GrGLenum format, GrGLenum type, GrGLenum access) { return nullptr; }
-    virtual GrGLvoid pixelStorei(GrGLenum pname, GrGLint param) {}
-    virtual GrGLvoid polygonMode(GrGLenum face, GrGLenum mode) {}
-    virtual GrGLvoid popGroupMarker() {}
-    virtual GrGLvoid pushGroupMarker(GrGLsizei length, const char* marker) {}
-    virtual GrGLvoid queryCounter(GrGLuint id, GrGLenum target) {}
-    virtual GrGLvoid rasterSamples(GrGLuint samples, GrGLboolean fixedsamplelocations) {}
-    virtual GrGLvoid readBuffer(GrGLenum src) {}
-    virtual GrGLvoid readPixels(GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height, GrGLenum format, GrGLenum type, GrGLvoid* pixels) {}
-    virtual GrGLvoid renderbufferStorage(GrGLenum target, GrGLenum internalformat, GrGLsizei width, GrGLsizei height) {}
-    virtual GrGLvoid renderbufferStorageMultisample(GrGLenum target, GrGLsizei samples, GrGLenum internalformat, GrGLsizei width, GrGLsizei height) {}
-    virtual GrGLvoid resolveMultisampleFramebuffer() {}
-    virtual GrGLvoid samplerParameteri(GrGLuint sampler, GrGLenum pname, GrGLint param) {}
-    virtual GrGLvoid samplerParameteriv(GrGLuint sampler, GrGLenum pname, const GrGLint* param) {}
-    virtual GrGLvoid scissor(GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height) {}
-    virtual GrGLvoid bindUniformLocation(GrGLuint program, GrGLint location, const char* name) {}
-    virtual GrGLvoid shaderSource(GrGLuint shader, GrGLsizei count, const char* const * str, const GrGLint* length) {}
-    virtual GrGLvoid stencilFunc(GrGLenum func, GrGLint ref, GrGLuint mask) {}
-    virtual GrGLvoid stencilFuncSeparate(GrGLenum face, GrGLenum func, GrGLint ref, GrGLuint mask) {}
-    virtual GrGLvoid stencilMask(GrGLuint mask) {}
-    virtual GrGLvoid stencilMaskSeparate(GrGLenum face, GrGLuint mask) {}
-    virtual GrGLvoid stencilOp(GrGLenum fail, GrGLenum zfail, GrGLenum zpass) {}
-    virtual GrGLvoid stencilOpSeparate(GrGLenum face, GrGLenum fail, GrGLenum zfail, GrGLenum zpass) {}
-    virtual GrGLvoid texBuffer(GrGLenum target, GrGLenum internalformat, GrGLuint buffer) {}
-    virtual GrGLvoid texImage2D(GrGLenum target, GrGLint level, GrGLint internalformat, GrGLsizei width, GrGLsizei height, GrGLint border, GrGLenum format, GrGLenum type, const GrGLvoid* pixels) {}
-    virtual GrGLvoid texParameterf(GrGLenum target, GrGLenum pname, GrGLfloat param) {}
-    virtual GrGLvoid texParameterfv(GrGLenum target, GrGLenum pname, const GrGLfloat* params) {}
-    virtual GrGLvoid texParameteri(GrGLenum target, GrGLenum pname, GrGLint param) {}
-    virtual GrGLvoid texParameteriv(GrGLenum target, GrGLenum pname, const GrGLint* params) {}
-    virtual GrGLvoid texStorage2D(GrGLenum target, GrGLsizei levels, GrGLenum internalformat, GrGLsizei width, GrGLsizei height) {}
-    virtual GrGLvoid discardFramebuffer(GrGLenum target, GrGLsizei numAttachments, const GrGLenum* attachments) {}
-    virtual GrGLvoid texSubImage2D(GrGLenum target, GrGLint level, GrGLint xoffset, GrGLint yoffset, GrGLsizei width, GrGLsizei height, GrGLenum format, GrGLenum type, const GrGLvoid* pixels) {}
-    virtual GrGLvoid textureBarrier() {}
-    virtual GrGLvoid uniform1f(GrGLint location, GrGLfloat v0) {}
-    virtual GrGLvoid uniform1i(GrGLint location, GrGLint v0) {}
-    virtual GrGLvoid uniform1fv(GrGLint location, GrGLsizei count, const GrGLfloat* v) {}
-    virtual GrGLvoid uniform1iv(GrGLint location, GrGLsizei count, const GrGLint* v) {}
-    virtual GrGLvoid uniform2f(GrGLint location, GrGLfloat v0, GrGLfloat v1) {}
-    virtual GrGLvoid uniform2i(GrGLint location, GrGLint v0, GrGLint v1) {}
-    virtual GrGLvoid uniform2fv(GrGLint location, GrGLsizei count, const GrGLfloat* v) {}
-    virtual GrGLvoid uniform2iv(GrGLint location, GrGLsizei count, const GrGLint* v) {}
-    virtual GrGLvoid uniform3f(GrGLint location, GrGLfloat v0, GrGLfloat v1, GrGLfloat v2) {}
-    virtual GrGLvoid uniform3i(GrGLint location, GrGLint v0, GrGLint v1, GrGLint v2) {}
-    virtual GrGLvoid uniform3fv(GrGLint location, GrGLsizei count, const GrGLfloat* v) {}
-    virtual GrGLvoid uniform3iv(GrGLint location, GrGLsizei count, const GrGLint* v) {}
-    virtual GrGLvoid uniform4f(GrGLint location, GrGLfloat v0, GrGLfloat v1, GrGLfloat v2, GrGLfloat v3) {}
-    virtual GrGLvoid uniform4i(GrGLint location, GrGLint v0, GrGLint v1, GrGLint v2, GrGLint v3) {}
-    virtual GrGLvoid uniform4fv(GrGLint location, GrGLsizei count, const GrGLfloat* v) {}
-    virtual GrGLvoid uniform4iv(GrGLint location, GrGLsizei count, const GrGLint* v) {}
-    virtual GrGLvoid uniformMatrix2fv(GrGLint location, GrGLsizei count, GrGLboolean transpose, const GrGLfloat* value) {}
-    virtual GrGLvoid uniformMatrix3fv(GrGLint location, GrGLsizei count, GrGLboolean transpose, const GrGLfloat* value) {}
-    virtual GrGLvoid uniformMatrix4fv(GrGLint location, GrGLsizei count, GrGLboolean transpose, const GrGLfloat* value) {}
-    virtual GrGLboolean unmapBuffer(GrGLenum target) { return GR_GL_TRUE; }
-    virtual GrGLvoid unmapBufferSubData(const GrGLvoid* mem) {}
-    virtual GrGLvoid unmapTexSubImage2D(const GrGLvoid* mem) {}
-    virtual GrGLvoid useProgram(GrGLuint program) {}
-    virtual GrGLvoid vertexAttrib1f(GrGLuint indx, const GrGLfloat value) {}
-    virtual GrGLvoid vertexAttrib2fv(GrGLuint indx, const GrGLfloat* values) {}
-    virtual GrGLvoid vertexAttrib3fv(GrGLuint indx, const GrGLfloat* values) {}
-    virtual GrGLvoid vertexAttrib4fv(GrGLuint indx, const GrGLfloat* values) {}
-    virtual GrGLvoid vertexAttribDivisor(GrGLuint index, GrGLuint divisor) {}
-    virtual GrGLvoid vertexAttribIPointer(GrGLuint indx, GrGLint size, GrGLenum type, GrGLsizei stride, const GrGLvoid* ptr) {}
-    virtual GrGLvoid vertexAttribPointer(GrGLuint indx, GrGLint size, GrGLenum type, GrGLboolean normalized, GrGLsizei stride, const GrGLvoid* ptr) {}
-    virtual GrGLvoid viewport(GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height) {}
-    virtual GrGLvoid matrixLoadf(GrGLenum matrixMode, const GrGLfloat* m) {}
-    virtual GrGLvoid matrixLoadIdentity(GrGLenum) {}
-    virtual GrGLvoid pathCommands(GrGLuint path, GrGLsizei numCommands, const GrGLubyte *commands, GrGLsizei numCoords, GrGLenum coordType, const GrGLvoid *coords) {}
-    virtual GrGLvoid pathParameteri(GrGLuint path, GrGLenum pname, GrGLint value) {}
-    virtual GrGLvoid pathParameterf(GrGLuint path, GrGLenum pname, GrGLfloat value) {}
-    virtual GrGLuint genPaths(GrGLsizei range) { return 0; }
-    virtual GrGLvoid deletePaths(GrGLuint path, GrGLsizei range) {}
-    virtual GrGLboolean isPath(GrGLuint path) { return true; }
-    virtual GrGLvoid pathStencilFunc(GrGLenum func, GrGLint ref, GrGLuint mask) {}
-    virtual GrGLvoid stencilFillPath(GrGLuint path, GrGLenum fillMode, GrGLuint mask) {}
-    virtual GrGLvoid stencilStrokePath(GrGLuint path, GrGLint reference, GrGLuint mask) {}
-    virtual GrGLvoid stencilFillPathInstanced(GrGLsizei numPaths, GrGLenum pathNameType, const GrGLvoid *paths, GrGLuint pathBase, GrGLenum fillMode, GrGLuint mask, GrGLenum transformType, const GrGLfloat *transformValues) {}
-    virtual GrGLvoid stencilStrokePathInstanced(GrGLsizei numPaths, GrGLenum pathNameType, const GrGLvoid *paths, GrGLuint pathBase, GrGLint reference, GrGLuint mask, GrGLenum transformType, const GrGLfloat *transformValues) {}
-    virtual GrGLvoid coverFillPath(GrGLuint path, GrGLenum coverMode) {}
-    virtual GrGLvoid coverStrokePath(GrGLuint name, GrGLenum coverMode) {}
-    virtual GrGLvoid coverFillPathInstanced(GrGLsizei numPaths, GrGLenum pathNameType, const GrGLvoid *paths, GrGLuint pathBase, GrGLenum coverMode, GrGLenum transformType, const GrGLfloat *transformValues) {}
-    virtual GrGLvoid coverStrokePathInstanced(GrGLsizei numPaths, GrGLenum pathNameType, const GrGLvoid *paths, GrGLuint pathBase, GrGLenum coverMode, GrGLenum transformType, const GrGLfloat* transformValues) {}
-    virtual GrGLvoid stencilThenCoverFillPath(GrGLuint path, GrGLenum fillMode, GrGLuint mask, GrGLenum coverMode) {}
-    virtual GrGLvoid stencilThenCoverStrokePath(GrGLuint path, GrGLint reference, GrGLuint mask, GrGLenum coverMode) {}
-    virtual GrGLvoid stencilThenCoverFillPathInstanced(GrGLsizei numPaths, GrGLenum pathNameType, const GrGLvoid *paths, GrGLuint pathBase, GrGLenum fillMode, GrGLuint mask, GrGLenum coverMode, GrGLenum transformType, const GrGLfloat *transformValues) {}
-    virtual GrGLvoid stencilThenCoverStrokePathInstanced(GrGLsizei numPaths, GrGLenum pathNameType, const GrGLvoid *paths, GrGLuint pathBase, GrGLint reference, GrGLuint mask, GrGLenum coverMode, GrGLenum transformType, const GrGLfloat *transformValues) {}
-    virtual GrGLvoid programPathFragmentInputGen(GrGLuint program, GrGLint location, GrGLenum genMode, GrGLint components,const GrGLfloat *coeffs) {}
-    virtual GrGLvoid bindFragmentInputLocation(GrGLuint program, GrGLint location, const GrGLchar* name) {}
-    virtual GrGLint getProgramResourceLocation(GrGLuint program, GrGLenum programInterface, const GrGLchar *name) { return 0; }
-    virtual GrGLvoid coverageModulation(GrGLenum components) {}
-    virtual GrGLvoid multiDrawArraysIndirect(GrGLenum mode, const GrGLvoid *indirect, GrGLsizei drawcount, GrGLsizei stride) {}
-    virtual GrGLvoid multiDrawElementsIndirect(GrGLenum mode, GrGLenum type, const GrGLvoid *indirect, GrGLsizei drawcount, GrGLsizei stride) {}
-    virtual GrGLuint64 getTextureHandle(GrGLuint texture) { return 0; }
-    virtual GrGLuint64 getTextureSamplerHandle(GrGLuint texture, GrGLuint sampler) { return 0; }
-    virtual GrGLvoid makeTextureHandleResident(GrGLuint64 handle) {}
-    virtual GrGLvoid makeTextureHandleNonResident(GrGLuint64 handle) {}
-    virtual GrGLuint64 getImageHandle(GrGLuint texture, GrGLint level, GrGLboolean layered, GrGLint layer, GrGLint format) { return 0; }
-    virtual GrGLvoid makeImageHandleResident(GrGLuint64 handle, GrGLenum access) {}
-    virtual GrGLvoid makeImageHandleNonResident(GrGLuint64 handle) {}
-    virtual GrGLboolean isTextureHandleResident(GrGLuint64 handle) { return GR_GL_FALSE; }
-    virtual GrGLboolean isImageHandleResident(GrGLuint64 handle) { return GR_GL_FALSE; }
-    virtual GrGLvoid uniformHandleui64(GrGLint location, GrGLuint64 v0) {}
-    virtual GrGLvoid uniformHandleui64v(GrGLint location, GrGLsizei count, const GrGLuint64 *value) {}
-    virtual GrGLvoid programUniformHandleui64(GrGLuint program, GrGLint location, GrGLuint64 v0) {}
-    virtual GrGLvoid programUniformHandleui64v(GrGLuint program, GrGLint location, GrGLsizei count, const GrGLuint64 *value) {}
-    virtual GrGLvoid textureParameteri(GrGLuint texture, GrGLenum target, GrGLenum pname, GrGLint param) {}
-    virtual GrGLvoid textureParameteriv(GrGLuint texture, GrGLenum target, GrGLenum pname, const GrGLint *param) {}
-    virtual GrGLvoid textureParameterf(GrGLuint texture, GrGLenum target, GrGLenum pname, float param) {}
-    virtual GrGLvoid textureParameterfv(GrGLuint texture, GrGLenum target, GrGLenum pname, const float *param) {}
-    virtual GrGLvoid textureImage1D(GrGLuint texture, GrGLenum target, GrGLint level, GrGLint GrGLinternalformat, GrGLsizei width, GrGLint border, GrGLenum format, GrGLenum type, const GrGLvoid *pixels) {}
-    virtual GrGLvoid textureImage2D(GrGLuint texture, GrGLenum target, GrGLint level, GrGLint GrGLinternalformat, GrGLsizei width, GrGLsizei height, GrGLint border, GrGLenum format, GrGLenum type, const GrGLvoid *pixels) {}
-    virtual GrGLvoid textureSubImage1D(GrGLuint texture, GrGLenum target, GrGLint level, GrGLint xoffset, GrGLsizei width, GrGLenum format, GrGLenum type, const GrGLvoid *pixels) {}
-    virtual GrGLvoid textureSubImage2D(GrGLuint texture, GrGLenum target, GrGLint level, GrGLint xoffset, GrGLint yoffset, GrGLsizei width, GrGLsizei height, GrGLenum format, GrGLenum type, const GrGLvoid *pixels) {}
-    virtual GrGLvoid copyTextureImage1D(GrGLuint texture, GrGLenum target, GrGLint level, GrGLenum GrGLinternalformat, GrGLint x, GrGLint y, GrGLsizei width, GrGLint border) {}
-    virtual GrGLvoid copyTextureImage2D(GrGLuint texture, GrGLenum target, GrGLint level, GrGLenum GrGLinternalformat, GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height, GrGLint border) {}
-    virtual GrGLvoid copyTextureSubImage1D(GrGLuint texture, GrGLenum target, GrGLint level, GrGLint xoffset, GrGLint x, GrGLint y, GrGLsizei width) {}
-    virtual GrGLvoid copyTextureSubImage2D(GrGLuint texture, GrGLenum target, GrGLint level, GrGLint xoffset, GrGLint yoffset, GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height) {}
-    virtual GrGLvoid getTextureImage(GrGLuint texture, GrGLenum target, GrGLint level, GrGLenum format, GrGLenum type, GrGLvoid *pixels) {}
-    virtual GrGLvoid getTextureParameterfv(GrGLuint texture, GrGLenum target, GrGLenum pname, float *params) {}
-    virtual GrGLvoid getTextureParameteriv(GrGLuint texture, GrGLenum target, GrGLenum pname, GrGLint *params) {}
-    virtual GrGLvoid getTextureLevelParameterfv(GrGLuint texture, GrGLenum target, GrGLint level, GrGLenum pname, float *params) {}
-    virtual GrGLvoid getTextureLevelParameteriv(GrGLuint texture, GrGLenum target, GrGLint level, GrGLenum pname, GrGLint *params) {}
-    virtual GrGLvoid textureImage3D(GrGLuint texture, GrGLenum target, GrGLint level, GrGLint GrGLinternalformat, GrGLsizei width, GrGLsizei height, GrGLsizei depth, GrGLint border, GrGLenum format, GrGLenum type, const GrGLvoid *pixels) {}
-    virtual GrGLvoid textureSubImage3D(GrGLuint texture, GrGLenum target, GrGLint level, GrGLint xoffset, GrGLint yoffset, GrGLint zoffset, GrGLsizei width, GrGLsizei height, GrGLsizei depth, GrGLenum format, GrGLenum type, const GrGLvoid *pixels) {}
-    virtual GrGLvoid copyTextureSubImage3D(GrGLuint texture, GrGLenum target, GrGLint level, GrGLint xoffset, GrGLint yoffset, GrGLint zoffset, GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height) {}
-    virtual GrGLvoid compressedTextureImage3D(GrGLuint texture, GrGLenum target, GrGLint level, GrGLenum GrGLinternalformat, GrGLsizei width, GrGLsizei height, GrGLsizei depth, GrGLint border, GrGLsizei imageSize, const GrGLvoid *data) {}
-    virtual GrGLvoid compressedTextureImage2D(GrGLuint texture, GrGLenum target, GrGLint level, GrGLenum GrGLinternalformat, GrGLsizei width, GrGLsizei height, GrGLint border, GrGLsizei imageSize, const GrGLvoid *data) {}
-    virtual GrGLvoid compressedTextureImage1D(GrGLuint texture, GrGLenum target, GrGLint level, GrGLenum GrGLinternalformat, GrGLsizei width, GrGLint border, GrGLsizei imageSize, const GrGLvoid *data) {}
-    virtual GrGLvoid compressedTextureSubImage3D(GrGLuint texture, GrGLenum target, GrGLint level, GrGLint xoffset, GrGLint yoffset, GrGLint zoffset, GrGLsizei width, GrGLsizei height, GrGLsizei depth, GrGLenum format, GrGLsizei imageSize, const GrGLvoid *data) {}
-    virtual GrGLvoid compressedTextureSubImage2D(GrGLuint texture, GrGLenum target, GrGLint level, GrGLint xoffset, GrGLint yoffset, GrGLsizei width, GrGLsizei height, GrGLenum format, GrGLsizei imageSize, const GrGLvoid *data) {}
-    virtual GrGLvoid compressedTextureSubImage1D(GrGLuint texture, GrGLenum target, GrGLint level, GrGLint xoffset, GrGLsizei width, GrGLenum format, GrGLsizei imageSize, const GrGLvoid *data) {}
-    virtual GrGLvoid getCompressedTextureImage(GrGLuint texture, GrGLenum target, GrGLint level, GrGLvoid *img) {}
-    virtual GrGLvoid namedBufferData(GrGLuint buffer, GrGLsizeiptr size, const GrGLvoid *data, GrGLenum usage) {}
-    virtual GrGLvoid namedBufferSubData(GrGLuint buffer, GrGLintptr offset, GrGLsizeiptr size, const GrGLvoid *data) {}
-    virtual GrGLvoid* mapNamedBuffer(GrGLuint buffer, GrGLenum access) { return nullptr; }
-    virtual GrGLboolean unmapNamedBuffer(GrGLuint buffer) { return GR_GL_FALSE; }
-    virtual GrGLvoid getNamedBufferParameteriv(GrGLuint buffer, GrGLenum pname, GrGLint *params) {}
-    virtual GrGLvoid getNamedBufferPointerv(GrGLuint buffer, GrGLenum pname, GrGLvoid* *params) {}
-    virtual GrGLvoid getNamedBufferSubData(GrGLuint buffer, GrGLintptr offset, GrGLsizeiptr size, GrGLvoid *data) {}
-    virtual GrGLvoid programUniform1f(GrGLuint program, GrGLint location, float v0) {}
-    virtual GrGLvoid programUniform2f(GrGLuint program, GrGLint location, float v0, float v1) {}
-    virtual GrGLvoid programUniform3f(GrGLuint program, GrGLint location, float v0, float v1, float v2) {}
-    virtual GrGLvoid programUniform4f(GrGLuint program, GrGLint location, float v0, float v1, float v2, float v3) {}
-    virtual GrGLvoid programUniform1i(GrGLuint program, GrGLint location, GrGLint v0) {}
-    virtual GrGLvoid programUniform2i(GrGLuint program, GrGLint location, GrGLint v0, GrGLint v1) {}
-    virtual GrGLvoid programUniform3i(GrGLuint program, GrGLint location, GrGLint v0, GrGLint v1, GrGLint v2) {}
-    virtual GrGLvoid programUniform4i(GrGLuint program, GrGLint location, GrGLint v0, GrGLint v1, GrGLint v2, GrGLint v3) {}
-    virtual GrGLvoid programUniform1fv(GrGLuint program, GrGLint location, GrGLsizei count, const float *value) {}
-    virtual GrGLvoid programUniform2fv(GrGLuint program, GrGLint location, GrGLsizei count, const float *value) {}
-    virtual GrGLvoid programUniform3fv(GrGLuint program, GrGLint location, GrGLsizei count, const float *value) {}
-    virtual GrGLvoid programUniform4fv(GrGLuint program, GrGLint location, GrGLsizei count, const float *value) {}
-    virtual GrGLvoid programUniform1iv(GrGLuint program, GrGLint location, GrGLsizei count, const GrGLint *value) {}
-    virtual GrGLvoid programUniform2iv(GrGLuint program, GrGLint location, GrGLsizei count, const GrGLint *value) {}
-    virtual GrGLvoid programUniform3iv(GrGLuint program, GrGLint location, GrGLsizei count, const GrGLint *value) {}
-    virtual GrGLvoid programUniform4iv(GrGLuint program, GrGLint location, GrGLsizei count, const GrGLint *value) {}
-    virtual GrGLvoid programUniformMatrix2fv(GrGLuint program, GrGLint location, GrGLsizei count, GrGLboolean transpose, const float *value) {}
-    virtual GrGLvoid programUniformMatrix3fv(GrGLuint program, GrGLint location, GrGLsizei count, GrGLboolean transpose, const float *value) {}
-    virtual GrGLvoid programUniformMatrix4fv(GrGLuint program, GrGLint location, GrGLsizei count, GrGLboolean transpose, const float *value) {}
-    virtual GrGLvoid programUniformMatrix2x3fv(GrGLuint program, GrGLint location, GrGLsizei count, GrGLboolean transpose, const float *value) {}
-    virtual GrGLvoid programUniformMatrix3x2fv(GrGLuint program, GrGLint location, GrGLsizei count, GrGLboolean transpose, const float *value) {}
-    virtual GrGLvoid programUniformMatrix2x4fv(GrGLuint program, GrGLint location, GrGLsizei count, GrGLboolean transpose, const float *value) {}
-    virtual GrGLvoid programUniformMatrix4x2fv(GrGLuint program, GrGLint location, GrGLsizei count, GrGLboolean transpose, const float *value) {}
-    virtual GrGLvoid programUniformMatrix3x4fv(GrGLuint program, GrGLint location, GrGLsizei count, GrGLboolean transpose, const float *value) {}
-    virtual GrGLvoid programUniformMatrix4x3fv(GrGLuint program, GrGLint location, GrGLsizei count, GrGLboolean transpose, const float *value) {}
-    virtual GrGLvoid namedRenderbufferStorage(GrGLuint renderbuffer, GrGLenum GrGLinternalformat, GrGLsizei width, GrGLsizei height) {}
-    virtual GrGLvoid getNamedRenderbufferParameteriv(GrGLuint renderbuffer, GrGLenum pname, GrGLint *params) {}
-    virtual GrGLvoid namedRenderbufferStorageMultisample(GrGLuint renderbuffer, GrGLsizei samples, GrGLenum GrGLinternalformat, GrGLsizei width, GrGLsizei height) {}
-    virtual GrGLenum checkNamedFramebufferStatus(GrGLuint framebuffer, GrGLenum target) { return GR_GL_FRAMEBUFFER_COMPLETE; }
-    virtual GrGLvoid namedFramebufferTexture1D(GrGLuint framebuffer, GrGLenum attachment, GrGLenum textarget, GrGLuint texture, GrGLint level) {}
-    virtual GrGLvoid namedFramebufferTexture2D(GrGLuint framebuffer, GrGLenum attachment, GrGLenum textarget, GrGLuint texture, GrGLint level) {}
-    virtual GrGLvoid namedFramebufferTexture3D(GrGLuint framebuffer, GrGLenum attachment, GrGLenum textarget, GrGLuint texture, GrGLint level, GrGLint zoffset) {}
-    virtual GrGLvoid namedFramebufferRenderbuffer(GrGLuint framebuffer, GrGLenum attachment, GrGLenum renderbuffertarget, GrGLuint renderbuffer) {}
-    virtual GrGLvoid getNamedFramebufferAttachmentParameteriv(GrGLuint framebuffer, GrGLenum attachment, GrGLenum pname, GrGLint *params) {}
-    virtual GrGLvoid generateTextureMipmap(GrGLuint texture, GrGLenum target) {}
-    virtual GrGLvoid framebufferDrawBuffer(GrGLuint framebuffer, GrGLenum mode) {}
-    virtual GrGLvoid framebufferDrawBuffers(GrGLuint framebuffer, GrGLsizei n, const GrGLenum *bufs) {}
-    virtual GrGLvoid framebufferReadBuffer(GrGLuint framebuffer, GrGLenum mode) {}
-    virtual GrGLvoid getFramebufferParameteriv(GrGLuint framebuffer, GrGLenum pname, GrGLint *param) {}
-    virtual GrGLvoid namedCopyBufferSubData(GrGLuint readBuffer, GrGLuint writeBuffer, GrGLintptr readOffset, GrGLintptr writeOffset, GrGLsizeiptr size) {}
-    virtual GrGLvoid vertexArrayVertexOffset(GrGLuint vaobj, GrGLuint buffer, GrGLint size, GrGLenum type, GrGLsizei stride, GrGLintptr offset) {}
-    virtual GrGLvoid vertexArrayColorOffset(GrGLuint vaobj, GrGLuint buffer, GrGLint size, GrGLenum type, GrGLsizei stride, GrGLintptr offset) {}
-    virtual GrGLvoid vertexArrayEdgeFlagOffset(GrGLuint vaobj, GrGLuint buffer, GrGLsizei stride, GrGLintptr offset) {}
-    virtual GrGLvoid vertexArrayIndexOffset(GrGLuint vaobj, GrGLuint buffer, GrGLenum type, GrGLsizei stride, GrGLintptr offset) {}
-    virtual GrGLvoid vertexArrayNormalOffset(GrGLuint vaobj, GrGLuint buffer, GrGLenum type, GrGLsizei stride, GrGLintptr offset) {}
-    virtual GrGLvoid vertexArrayTexCoordOffset(GrGLuint vaobj, GrGLuint buffer, GrGLint size, GrGLenum type, GrGLsizei stride, GrGLintptr offset) {}
-    virtual GrGLvoid vertexArrayMultiTexCoordOffset(GrGLuint vaobj, GrGLuint buffer, GrGLenum texunit, GrGLint size, GrGLenum type, GrGLsizei stride, GrGLintptr offset) {}
-    virtual GrGLvoid vertexArrayFogCoordOffset(GrGLuint vaobj, GrGLuint buffer, GrGLenum type, GrGLsizei stride, GrGLintptr offset) {}
-    virtual GrGLvoid vertexArraySecondaryColorOffset(GrGLuint vaobj, GrGLuint buffer, GrGLint size, GrGLenum type, GrGLsizei stride, GrGLintptr offset) {}
-    virtual GrGLvoid vertexArrayVertexAttribOffset(GrGLuint vaobj, GrGLuint buffer, GrGLuint index, GrGLint size, GrGLenum type, GrGLboolean normalized, GrGLsizei stride, GrGLintptr offset) {}
-    virtual GrGLvoid vertexArrayVertexAttribIOffset(GrGLuint vaobj, GrGLuint buffer, GrGLuint index, GrGLint size, GrGLenum type, GrGLsizei stride, GrGLintptr offset) {}
-    virtual GrGLvoid enableVertexArray(GrGLuint vaobj, GrGLenum array) {}
-    virtual GrGLvoid disableVertexArray(GrGLuint vaobj, GrGLenum array) {}
-    virtual GrGLvoid enableVertexArrayAttrib(GrGLuint vaobj, GrGLuint index) {}
-    virtual GrGLvoid disableVertexArrayAttrib(GrGLuint vaobj, GrGLuint index) {}
-    virtual GrGLvoid getVertexArrayIntegerv(GrGLuint vaobj, GrGLenum pname, GrGLint *param) {}
-    virtual GrGLvoid getVertexArrayPointerv(GrGLuint vaobj, GrGLenum pname, GrGLvoid **param) {}
-    virtual GrGLvoid getVertexArrayIntegeri_v(GrGLuint vaobj, GrGLuint index, GrGLenum pname, GrGLint *param) {}
-    virtual GrGLvoid getVertexArrayPointeri_v(GrGLuint vaobj, GrGLuint index, GrGLenum pname, GrGLvoid **param) {}
-    virtual GrGLvoid* mapNamedBufferRange(GrGLuint buffer, GrGLintptr offset, GrGLsizeiptr length, GrGLbitfield access) { return nullptr; }
-    virtual GrGLvoid flushMappedNamedBufferRange(GrGLuint buffer, GrGLintptr offset, GrGLsizeiptr length) {}
-    virtual GrGLvoid textureBuffer(GrGLuint texture, GrGLenum target, GrGLenum internalformat, GrGLuint buffer) {}
-    virtual GrGLsync fenceSync(GrGLenum condition, GrGLbitfield flags) { return nullptr;  }
-    virtual GrGLboolean isSync(GrGLsync) { return false;  }
-    virtual GrGLenum clientWaitSync(GrGLsync sync, GrGLbitfield flags, GrGLuint64 timeout) { return GR_GL_WAIT_FAILED;  }
-    virtual GrGLvoid waitSync(GrGLsync sync, GrGLbitfield flags, GrGLuint64 timeout) {}
-    virtual GrGLvoid deleteSync(GrGLsync sync) {}
-    virtual GrGLvoid debugMessageControl(GrGLenum source, GrGLenum type, GrGLenum severity, GrGLsizei count, const GrGLuint* ids, GrGLboolean enabled) {}
-    virtual GrGLvoid debugMessageInsert(GrGLenum source, GrGLenum type, GrGLuint id, GrGLenum severity, GrGLsizei length,  const GrGLchar* buf) {}
-    virtual GrGLvoid debugMessageCallback(GRGLDEBUGPROC callback, const GrGLvoid* userParam) {}
-    virtual GrGLuint getDebugMessageLog(GrGLuint count, GrGLsizei bufSize, GrGLenum* sources, GrGLenum* types, GrGLuint* ids, GrGLenum* severities, GrGLsizei* lengths,  GrGLchar* messageLog) { return 0; }
-    virtual GrGLvoid pushDebugGroup(GrGLenum source, GrGLuint id, GrGLsizei length,  const GrGLchar * message) {}
-    virtual GrGLvoid popDebugGroup() {}
-    virtual GrGLvoid objectLabel(GrGLenum identifier, GrGLuint name, GrGLsizei length, const GrGLchar *label) {}
-    virtual GrGLvoid getInternalformativ(GrGLenum target, GrGLenum internalformat, GrGLenum pname, GrGLsizei bufSize, GrGLint *params) {}
-    virtual GrGLvoid programBinary(GrGLuint program, GrGLenum binaryFormat, void *binary, GrGLsizei length) {}
-    virtual GrGLvoid getProgramBinary(GrGLuint program, GrGLsizei bufsize, GrGLsizei* length, GrGLenum *binaryFormat, void *binary) {}
-    virtual GrGLvoid programParameteri(GrGLuint program, GrGLenum pname, GrGLint value) {}
-
-protected:
-    // This must be called by leaf class
-    void init(GrGLStandard standard) {
-        fStandard = standard;
-        fExtensions.init(standard, fFunctions.fGetString, fFunctions.fGetStringi,
-                         fFunctions.fGetIntegerv, nullptr, GR_EGL_NO_DISPLAY);
-    }
-    GrGLTestInterface();
-};
-
-#endif
diff --git a/src/gpu/gl/GrGLTexture.h b/src/gpu/gl/GrGLTexture.h
index 2449fc0..8ca2737 100644
--- a/src/gpu/gl/GrGLTexture.h
+++ b/src/gpu/gl/GrGLTexture.h
@@ -75,12 +75,6 @@
         fNonSamplerParams.invalidate();
     }
 
-    void setIdleProc(IdleProc proc, void* context) override {
-        fIdleProc = proc;
-        fIdleProcContext = context;
-    }
-    void* idleContext() const override { return fIdleProcContext; }
-
     // These functions are used to track the texture parameters associated with the texture.
     GrGpu::ResetTimestamp getCachedParamsTimestamp() const { return fParamsTimestamp; }
     const SamplerParams& getCachedSamplerParams() const { return fSamplerParams; }
@@ -124,21 +118,9 @@
     bool onStealBackendTexture(GrBackendTexture*, SkImage::BackendTextureReleaseProc*) override;
 
 private:
-    void onSetRelease(sk_sp<GrReleaseProcHelper> releaseHelper) override {}
-
-    void removedLastRefOrPendingIO() override {
-        if (fIdleProc) {
-            fIdleProc(fIdleProcContext);
-            fIdleProc = nullptr;
-            fIdleProcContext = nullptr;
-        }
-    }
-
     SamplerParams fSamplerParams;
     NonSamplerParams fNonSamplerParams;
     GrGpu::ResetTimestamp fParamsTimestamp;
-    IdleProc* fIdleProc = nullptr;
-    void* fIdleProcContext = nullptr;
     GrGLuint fID;
     GrGLenum fFormat;
     GrBackendObjectOwnership fTextureIDOwnership;
diff --git a/src/gpu/gl/GrGLTextureRenderTarget.h b/src/gpu/gl/GrGLTextureRenderTarget.h
index 12a8ea0..ffb5643 100644
--- a/src/gpu/gl/GrGLTextureRenderTarget.h
+++ b/src/gpu/gl/GrGLTextureRenderTarget.h
@@ -66,8 +66,6 @@
                             GrMipMapsStatus);
 
     size_t onGpuMemorySize() const override;
-
-    void onSetRelease(sk_sp<GrReleaseProcHelper> releaseHelper) override {}
 };
 
 #ifdef SK_BUILD_FOR_WIN
diff --git a/src/gpu/gl/GrGLUniformHandler.cpp b/src/gpu/gl/GrGLUniformHandler.cpp
index 24f4ac9..2448bc6 100644
--- a/src/gpu/gl/GrGLUniformHandler.cpp
+++ b/src/gpu/gl/GrGLUniformHandler.cpp
@@ -27,7 +27,6 @@
 GrGLSLUniformHandler::UniformHandle GrGLUniformHandler::internalAddUniformArray(
                                                                             uint32_t visibility,
                                                                             GrSLType type,
-                                                                            GrSLPrecision precision,
                                                                             const char* name,
                                                                             bool mangleName,
                                                                             int arrayCount,
@@ -35,7 +34,6 @@
     SkASSERT(name && strlen(name));
     SkASSERT(valid_name(name));
     SkASSERT(0 != visibility);
-    SkASSERT(kDefault_GrSLPrecision == precision || GrSLTypeTemporarilyAcceptsPrecision(type));
 
     UniformInfo& uni = fUniforms.push_back();
     uni.fVariable.setType(type);
@@ -53,7 +51,6 @@
     fProgramBuilder->nameVariable(uni.fVariable.accessName(), prefix, name, mangleName);
     uni.fVariable.setArrayCount(arrayCount);
     uni.fVisibility = visibility;
-    uni.fVariable.setPrecision(precision);
     uni.fLocation = -1;
 
     if (outName) {
@@ -72,14 +69,12 @@
     char prefix = 'u';
     fProgramBuilder->nameVariable(&mangleName, prefix, name, true);
 
-    GrSLPrecision precision = GrSLSamplerPrecision(texture->config());
     GrSwizzle swizzle = shaderCaps->configTextureSwizzle(texture->config());
     GrTextureType type = texture->texturePriv().textureType();
 
     UniformInfo& sampler = fSamplers.push_back();
     sampler.fVariable.setType(GrSLCombinedSamplerTypeForTextureType(type));
     sampler.fVariable.setTypeModifier(GrShaderVar::kUniform_TypeModifier);
-    sampler.fVariable.setPrecision(precision);
     sampler.fVariable.setName(mangleName);
     sampler.fLocation = -1;
     sampler.fVisibility = kFragment_GrShaderFlag;
diff --git a/src/gpu/gl/GrGLUniformHandler.h b/src/gpu/gl/GrGLUniformHandler.h
index 962beee..04653d4 100644
--- a/src/gpu/gl/GrGLUniformHandler.h
+++ b/src/gpu/gl/GrGLUniformHandler.h
@@ -33,7 +33,6 @@
 
     UniformHandle internalAddUniformArray(uint32_t visibility,
                                           GrSLType type,
-                                          GrSLPrecision precision,
                                           const char* name,
                                           bool mangleName,
                                           int arrayCount,
diff --git a/src/gpu/gl/GrGLUtil.cpp b/src/gpu/gl/GrGLUtil.cpp
index a031f95..24b9341 100644
--- a/src/gpu/gl/GrGLUtil.cpp
+++ b/src/gpu/gl/GrGLUtil.cpp
@@ -77,6 +77,13 @@
         return kGL_GrGLStandard;
     }
 
+    // WebGL might look like "OpenGL ES 2.0 (WebGL 1.0 (OpenGL ES 2.0 Chromium))"
+    int esMajor, esMinor;
+    n = sscanf(versionString, "OpenGL ES %d.%d (WebGL %d.%d", &esMajor, &esMinor, &major, &minor);
+    if (4 == n) {
+        return kWebGL_GrGLStandard;
+    }
+
     // check for ES 1
     char profile[2];
     n = sscanf(versionString, "OpenGL ES-%c%c %d.%d", profile, profile+1, &major, &minor);
@@ -121,7 +128,7 @@
         return;
     }
 
-    if (standard == kGL_GrGLStandard) {
+    if (GR_IS_GR_GL(standard)) {
         if (kNVIDIA_GrGLVendor == vendor) {
             *outDriver = kNVIDIA_GrGLDriver;
             int n = sscanf(versionString, "%d.%d.%d NVIDIA %d.%d",
@@ -143,8 +150,7 @@
             *outVersion = GR_GL_DRIVER_VER(driverMajor, driverMinor, 0);
             return;
         }
-    }
-    else {
+    } else if (GR_IS_GR_GL_ES(standard)) {
         if (kNVIDIA_GrGLVendor == vendor) {
             *outDriver = kNVIDIA_GrGLDriver;
             int n = sscanf(versionString, "OpenGL ES %d.%d NVIDIA %d.%d",
@@ -233,6 +239,13 @@
         return GR_GL_VER(major, minor);
     }
 
+    // WebGL might look like "OpenGL ES 2.0 (WebGL 1.0 (OpenGL ES 2.0 Chromium))"
+    int esMajor, esMinor;
+    n = sscanf(versionString, "OpenGL ES %d.%d (WebGL %d.%d", &esMajor, &esMinor, &major, &minor);
+    if (4 == n) {
+        return GR_GL_VER(major, minor);
+    }
+
     char profile[2];
     n = sscanf(versionString, "OpenGL ES-%c%c %d.%d", profile, profile+1,
                &major, &minor);
diff --git a/src/gpu/gl/GrGLVertexArray.cpp b/src/gpu/gl/GrGLVertexArray.cpp
index 6b12a3f..ab52b25 100644
--- a/src/gpu/gl/GrGLVertexArray.cpp
+++ b/src/gpu/gl/GrGLVertexArray.cpp
@@ -6,6 +6,7 @@
  */
 
 #include "GrGLVertexArray.h"
+#include "GrCpuBuffer.h"
 #include "GrGLBuffer.h"
 #include "GrGLGpu.h"
 
@@ -89,14 +90,32 @@
     SkASSERT(index >= 0 && index < fAttribArrayStates.count());
     SkASSERT(0 == divisor || gpu->caps()->instanceAttribSupport());
     AttribArrayState* array = &fAttribArrayStates[index];
-    if (array->fVertexBufferUniqueID != vertexBuffer->uniqueID() ||
+    const char* offsetAsPtr;
+    bool bufferChanged = false;
+    if (vertexBuffer->isCpuBuffer()) {
+        if (!array->fUsingCpuBuffer) {
+            bufferChanged = true;
+            array->fUsingCpuBuffer = true;
+        }
+        offsetAsPtr = static_cast<const GrCpuBuffer*>(vertexBuffer)->data() + offsetInBytes;
+    } else {
+        auto gpuBuffer = static_cast<const GrGpuBuffer*>(vertexBuffer);
+        if (array->fUsingCpuBuffer || array->fVertexBufferUniqueID != gpuBuffer->uniqueID()) {
+            bufferChanged = true;
+            array->fVertexBufferUniqueID = gpuBuffer->uniqueID();
+        }
+        offsetAsPtr = reinterpret_cast<const char*>(offsetInBytes);
+    }
+    if (bufferChanged ||
         array->fCPUType != cpuType ||
         array->fGPUType != gpuType ||
         array->fStride != stride ||
-        array->fOffset != offsetInBytes) {
+        array->fOffset != offsetAsPtr) {
+        // We always have to call this if we're going to change the array pointer. 'array' is
+        // tracking the last buffer used to setup attrib pointers, not the last buffer bound.
+        // GrGLGpu will avoid redundant binds.
         gpu->bindBuffer(GrGpuBufferType::kVertex, vertexBuffer);
         const AttribLayout& layout = attrib_layout(cpuType);
-        const GrGLvoid* offsetAsPtr = reinterpret_cast<const GrGLvoid*>(offsetInBytes);
         if (GrSLTypeIsFloatType(gpuType)) {
             GR_GL_CALL(gpu->glInterface(), VertexAttribPointer(index,
                                                                layout.fCount,
@@ -113,11 +132,10 @@
                                                                 stride,
                                                                 offsetAsPtr));
         }
-        array->fVertexBufferUniqueID = vertexBuffer->uniqueID();
         array->fCPUType = cpuType;
         array->fGPUType = gpuType;
         array->fStride = stride;
-        array->fOffset = offsetInBytes;
+        array->fOffset = offsetAsPtr;
     }
     if (gpu->caps()->instanceAttribSupport() && array->fDivisor != divisor) {
         SkASSERT(0 == divisor || 1 == divisor); // not necessarily a requirement but what we expect.
@@ -179,15 +197,19 @@
 
 GrGLAttribArrayState* GrGLVertexArray::bindWithIndexBuffer(GrGLGpu* gpu, const GrBuffer* ibuff) {
     GrGLAttribArrayState* state = this->bind(gpu);
-    if (state && fIndexBufferUniqueID != ibuff->uniqueID()) {
-        if (ibuff->isCPUBacked()) {
-            GR_GL_CALL(gpu->glInterface(), BindBuffer(GR_GL_ELEMENT_ARRAY_BUFFER, 0));
-        } else {
+    if (!state) {
+        return nullptr;
+    }
+    if (ibuff->isCpuBuffer()) {
+        GR_GL_CALL(gpu->glInterface(), BindBuffer(GR_GL_ELEMENT_ARRAY_BUFFER, 0));
+    } else {
+        const GrGLBuffer* glBuffer = static_cast<const GrGLBuffer*>(ibuff);
+        if (fIndexBufferUniqueID != glBuffer->uniqueID()) {
             const GrGLBuffer* glBuffer = static_cast<const GrGLBuffer*>(ibuff);
-            GR_GL_CALL(gpu->glInterface(), BindBuffer(GR_GL_ELEMENT_ARRAY_BUFFER,
-                                                      glBuffer->bufferID()));
+            GR_GL_CALL(gpu->glInterface(),
+                       BindBuffer(GR_GL_ELEMENT_ARRAY_BUFFER, glBuffer->bufferID()));
+            fIndexBufferUniqueID = glBuffer->uniqueID();
         }
-        fIndexBufferUniqueID = ibuff->uniqueID();
     }
     return state;
 }
diff --git a/src/gpu/gl/GrGLVertexArray.h b/src/gpu/gl/GrGLVertexArray.h
index 93bd526..4e28e62 100644
--- a/src/gpu/gl/GrGLVertexArray.h
+++ b/src/gpu/gl/GrGLVertexArray.h
@@ -75,13 +75,15 @@
         void invalidate() {
             fVertexBufferUniqueID.makeInvalid();
             fDivisor = kInvalidDivisor;
+            fUsingCpuBuffer = false;
         }
 
         GrGpuResource::UniqueID   fVertexBufferUniqueID;
+        bool                      fUsingCpuBuffer;
         GrVertexAttribType        fCPUType;
         GrSLType                  fGPUType;
         GrGLsizei                 fStride;
-        size_t                    fOffset;
+        const GrGLvoid*           fOffset;
         int                       fDivisor;
     };
 
diff --git a/src/gpu/gl/builders/GrGLProgramBuilder.cpp b/src/gpu/gl/builders/GrGLProgramBuilder.cpp
index 8640da3..6b6e155 100644
--- a/src/gpu/gl/builders/GrGLProgramBuilder.cpp
+++ b/src/gpu/gl/builders/GrGLProgramBuilder.cpp
@@ -12,12 +12,15 @@
 #include "GrContextPriv.h"
 #include "GrCoordTransform.h"
 #include "GrGLProgramBuilder.h"
+#include "GrPersistentCacheUtils.h"
 #include "GrProgramDesc.h"
 #include "GrShaderCaps.h"
 #include "GrSwizzle.h"
 #include "SkAutoMalloc.h"
 #include "SkATrace.h"
+#include "SkReader32.h"
 #include "SkTraceEvent.h"
+#include "SkWriter32.h"
 #include "gl/GrGLGpu.h"
 #include "gl/GrGLProgram.h"
 #include "gl/builders/GrGLShaderStringBuilder.h"
@@ -109,29 +112,6 @@
     return true;
 }
 
-bool GrGLProgramBuilder::compileAndAttachShaders(GrGLSLShaderBuilder& shader,
-                                                 GrGLuint programId,
-                                                 GrGLenum type,
-                                                 SkTDArray<GrGLuint>* shaderIds,
-                                                 const SkSL::Program::Settings& settings,
-                                                 SkSL::Program::Inputs* outInputs) {
-    SkSL::String glsl;
-    std::unique_ptr<SkSL::Program> program = GrSkSLtoGLSL(gpu()->glContext(), type,
-                                                 shader.fCompilerStrings.begin(),
-                                                 shader.fCompilerStringLengths.begin(),
-                                                 shader.fCompilerStrings.count(),
-                                                 settings,
-                                                 &glsl);
-    *outInputs = program->fInputs;
-    return this->compileAndAttachShaders(glsl.c_str(),
-                                         glsl.size(),
-                                         programId,
-                                         type,
-                                         shaderIds,
-                                         settings,
-                                         *outInputs);
-}
-
 void GrGLProgramBuilder::computeCountsAndStrides(GrGLuint programID,
                                                  const GrPrimitiveProcessor& primProc,
                                                  bool bindAttribLocations) {
@@ -172,7 +152,7 @@
 }
 
 void GrGLProgramBuilder::storeShaderInCache(const SkSL::Program::Inputs& inputs, GrGLuint programID,
-                                            const SkSL::String& glsl) {
+                                            const SkSL::String glsl[]) {
     if (!this->gpu()->getContext()->priv().getPersistentCache()) {
         return;
     }
@@ -182,30 +162,24 @@
         GrGLsizei length = 0;
         GL_CALL(GetProgramiv(programID, GL_PROGRAM_BINARY_LENGTH, &length));
         if (length > 0) {
+            SkWriter32 writer;
+            writer.writePad(&inputs, sizeof(inputs));
+            writer.write32(length);
+
+            void* binary = writer.reservePad(length);
             GrGLenum binaryFormat;
-            std::unique_ptr<char[]> binary(new char[length]);
-            GL_CALL(GetProgramBinary(programID, length, &length, &binaryFormat, binary.get()));
-            size_t dataLength = sizeof(inputs) + sizeof(binaryFormat) + length;
-            std::unique_ptr<uint8_t[]> data(new uint8_t[dataLength]);
-            size_t offset = 0;
-            memcpy(data.get() + offset, &inputs, sizeof(inputs));
-            offset += sizeof(inputs);
-            memcpy(data.get() + offset, &binaryFormat, sizeof(binaryFormat));
-            offset += sizeof(binaryFormat);
-            memcpy(data.get() + offset, binary.get(), length);
-            this->gpu()->getContext()->priv().getPersistentCache()->store(
-                                            *key, *SkData::MakeWithoutCopy(data.get(), dataLength));
+            GL_CALL(GetProgramBinary(programID, length, &length, &binaryFormat, binary));
+            writer.write32(binaryFormat);
+
+            auto data = writer.snapshotAsData();
+            this->gpu()->getContext()->priv().getPersistentCache()->store(*key, *data);
         }
     } else {
         // source cache
-        size_t dataLength = sizeof(inputs) + glsl.length();
-        std::unique_ptr<uint8_t[]> data(new uint8_t[dataLength]);
-        size_t offset = 0;
-        memcpy(data.get() + offset, &inputs, sizeof(inputs));
-        offset += sizeof(inputs);
-        memcpy(data.get() + offset, glsl.data(), glsl.length());
-        this->gpu()->getContext()->priv().getPersistentCache()->store(
-                                            *key, *SkData::MakeWithoutCopy(data.get(), dataLength));
+        SkWriter32 writer;
+        GrPersistentCacheUtils::PackCachedGLSL(writer, inputs, glsl);
+        auto data = writer.snapshotAsData();
+        this->gpu()->getContext()->priv().getPersistentCache()->store(*key, *data);
     }
 }
 
@@ -243,21 +217,20 @@
     checkLinked = true;
 #endif
     bool cached = fCached.get() != nullptr;
-    SkSL::String glsl;
+    SkSL::String glsl[kGrShaderTypeCount];
     if (cached) {
-        const uint8_t* bytes = fCached->bytes();
-        size_t offset = 0;
-        memcpy(&inputs, bytes + offset, sizeof(inputs));
-        offset += sizeof(inputs);
+        SkReader32 reader(fCached->data(), fCached->size());
+
         if (fGpu->glCaps().programBinarySupport()) {
             // binary cache hit, just hand the binary to GL
-            int binaryFormat;
-            memcpy(&binaryFormat, bytes + offset, sizeof(binaryFormat));
-            offset += sizeof(binaryFormat);
+            reader.read(&inputs, sizeof(inputs));
+            GrGLsizei length = reader.readInt();
+            const void* binary = reader.skip(length);
+            GrGLenum binaryFormat = reader.readU32();
             GrGLClearErr(this->gpu()->glInterface());
             GR_GL_CALL_NOERRCHECK(this->gpu()->glInterface(),
-                                  ProgramBinary(programID, binaryFormat, (void*) (bytes + offset),
-                                                fCached->size() - offset));
+                                  ProgramBinary(programID, binaryFormat, const_cast<void*>(binary),
+                                                length));
             if (GR_GL_GET_ERROR(this->gpu()->glInterface()) == GR_GL_NO_ERROR) {
                 if (checkLinked) {
                     cached = this->checkLinkStatus(programID);
@@ -271,13 +244,13 @@
             }
         } else {
             // source cache hit, we don't need to compile the SkSL->GLSL
-            glsl = SkSL::String(((const char*) bytes) + offset, fCached->size() - offset);
+            GrPersistentCacheUtils::UnpackCachedGLSL(reader, &inputs, glsl);
         }
     }
     if (!cached || !fGpu->glCaps().programBinarySupport()) {
         // either a cache miss, or we can't store binaries in the cache
-        if (glsl == "" || true) {
-            // don't have cached GLSL, need to compile SkSL->GLSL
+        if (glsl[kFragment_GrShaderType].empty()) {
+            // Don't have cached GLSL, need to compile SkSL->GLSL
             if (fFS.fForceHighPrecision) {
                 settings.fForceHighPrecision = true;
             }
@@ -287,35 +260,44 @@
                                                              fFS.fCompilerStringLengths.begin(),
                                                              fFS.fCompilerStrings.count(),
                                                              settings,
-                                                             &glsl);
+                                                             &glsl[kFragment_GrShaderType]);
             if (!fs) {
                 this->cleanupProgram(programID, shadersToDelete);
                 return nullptr;
             }
             inputs = fs->fInputs;
+            this->addInputVars(inputs);
         } else {
             // we've pulled GLSL and inputs from the cache, but still need to do some setup
             this->addInputVars(inputs);
             this->computeCountsAndStrides(programID, primProc, false);
         }
-        this->addInputVars(inputs);
-        if (!this->compileAndAttachShaders(glsl.c_str(), glsl.size(), programID,
+        if (!this->compileAndAttachShaders(glsl[kFragment_GrShaderType].c_str(),
+                                           glsl[kFragment_GrShaderType].size(), programID,
                                            GR_GL_FRAGMENT_SHADER, &shadersToDelete, settings,
                                            inputs)) {
             this->cleanupProgram(programID, shadersToDelete);
             return nullptr;
         }
 
-        std::unique_ptr<SkSL::Program> vs = GrSkSLtoGLSL(gpu()->glContext(),
-                                                         GR_GL_VERTEX_SHADER,
-                                                         fVS.fCompilerStrings.begin(),
-                                                         fVS.fCompilerStringLengths.begin(),
-                                                         fVS.fCompilerStrings.count(),
-                                                         settings,
-                                                         &glsl);
-        if (!vs || !this->compileAndAttachShaders(glsl.c_str(), glsl.size(), programID,
-                                                  GR_GL_VERTEX_SHADER, &shadersToDelete, settings,
-                                                  inputs)) {
+        if (glsl[kVertex_GrShaderType].empty()) {
+            // Don't have cached GLSL, need to compile SkSL->GLSL
+            std::unique_ptr<SkSL::Program> vs = GrSkSLtoGLSL(gpu()->glContext(),
+                                                             GR_GL_VERTEX_SHADER,
+                                                             fVS.fCompilerStrings.begin(),
+                                                             fVS.fCompilerStringLengths.begin(),
+                                                             fVS.fCompilerStrings.count(),
+                                                             settings,
+                                                             &glsl[kVertex_GrShaderType]);
+            if (!vs) {
+                this->cleanupProgram(programID, shadersToDelete);
+                return nullptr;
+            }
+        }
+        if (!this->compileAndAttachShaders(glsl[kVertex_GrShaderType].c_str(),
+                                           glsl[kVertex_GrShaderType].size(), programID,
+                                           GR_GL_VERTEX_SHADER, &shadersToDelete, settings,
+                                           inputs)) {
             this->cleanupProgram(programID, shadersToDelete);
             return nullptr;
         }
@@ -327,17 +309,25 @@
         }
 
         if (primProc.willUseGeoShader()) {
-            std::unique_ptr<SkSL::Program> gs;
-            gs = GrSkSLtoGLSL(gpu()->glContext(),
-                              GR_GL_GEOMETRY_SHADER,
-                              fGS.fCompilerStrings.begin(),
-                              fGS.fCompilerStringLengths.begin(),
-                              fGS.fCompilerStrings.count(),
-                              settings,
-                              &glsl);
-            if (!gs || !this->compileAndAttachShaders(glsl.c_str(), glsl.size(), programID,
-                                                      GR_GL_GEOMETRY_SHADER, &shadersToDelete,
-                                                      settings, inputs)) {
+            if (glsl[kGeometry_GrShaderType].empty()) {
+                // Don't have cached GLSL, need to compile SkSL->GLSL
+                std::unique_ptr<SkSL::Program> gs;
+                gs = GrSkSLtoGLSL(gpu()->glContext(),
+                                  GR_GL_GEOMETRY_SHADER,
+                                  fGS.fCompilerStrings.begin(),
+                                  fGS.fCompilerStringLengths.begin(),
+                                  fGS.fCompilerStrings.count(),
+                                  settings,
+                                  &glsl[kGeometry_GrShaderType]);
+                if (!gs) {
+                    this->cleanupProgram(programID, shadersToDelete);
+                    return nullptr;
+                }
+            }
+            if (!this->compileAndAttachShaders(glsl[kGeometry_GrShaderType].c_str(),
+                                               glsl[kGeometry_GrShaderType].size(), programID,
+                                               GR_GL_GEOMETRY_SHADER, &shadersToDelete, settings,
+                                               inputs)) {
                 this->cleanupProgram(programID, shadersToDelete);
                 return nullptr;
             }
diff --git a/src/gpu/gl/builders/GrGLProgramBuilder.h b/src/gpu/gl/builders/GrGLProgramBuilder.h
index 5fa23cb..494ad25 100644
--- a/src/gpu/gl/builders/GrGLProgramBuilder.h
+++ b/src/gpu/gl/builders/GrGLProgramBuilder.h
@@ -60,16 +60,10 @@
                                  const SkSL::Program::Settings& settings,
                                  const SkSL::Program::Inputs& inputs);
 
-    bool compileAndAttachShaders(GrGLSLShaderBuilder& shader,
-                                 GrGLuint programId,
-                                 GrGLenum type,
-                                 SkTDArray<GrGLuint>* shaderIds,
-                                 const SkSL::Program::Settings& settings,
-                                 SkSL::Program::Inputs* outInputs);
     void computeCountsAndStrides(GrGLuint programID, const GrPrimitiveProcessor& primProc,
                                  bool bindAttribLocations);
     void storeShaderInCache(const SkSL::Program::Inputs& inputs, GrGLuint programID,
-                            const SkSL::String& glsl);
+                            const SkSL::String glsl[]);
     GrGLProgram* finalize();
     void bindProgramResourceLocations(GrGLuint programID);
     bool checkLinkStatus(GrGLuint programID);
diff --git a/src/gpu/gl/win/GrGLMakeNativeInterface_win.cpp b/src/gpu/gl/win/GrGLMakeNativeInterface_win.cpp
index cb69244..5502bf4 100644
--- a/src/gpu/gl/win/GrGLMakeNativeInterface_win.cpp
+++ b/src/gpu/gl/win/GrGLMakeNativeInterface_win.cpp
@@ -85,9 +85,9 @@
     const char* verStr = reinterpret_cast<const char*>(getString(GR_GL_VERSION));
     GrGLStandard standard = GrGLGetStandardInUseFromString(verStr);
 
-    if (kGLES_GrGLStandard == standard) {
+    if (GR_IS_GR_GL_ES(standard)) {
         return GrGLMakeAssembledGLESInterface(&getter, win_get_gl_proc);
-    } else if (kGL_GrGLStandard == standard) {
+    } else if (GR_IS_GR_GL(standard)) {
         return GrGLMakeAssembledGLInterface(&getter, win_get_gl_proc);
     }
     return nullptr;
diff --git a/src/gpu/glsl/GrGLSL.cpp b/src/gpu/glsl/GrGLSL.cpp
index 694aaa7..8941b73 100644
--- a/src/gpu/glsl/GrGLSL.cpp
+++ b/src/gpu/glsl/GrGLSL.cpp
@@ -8,7 +8,7 @@
 #include "GrGLSL.h"
 #include "GrShaderCaps.h"
 
-const char* GrGLSLTypeString(const GrShaderCaps* shaderCaps, GrSLType t) {
+const char* GrGLSLTypeString(GrSLType t) {
     switch (t) {
         case kVoid_GrSLType:
             return "void";
diff --git a/src/gpu/glsl/GrGLSL.h b/src/gpu/glsl/GrGLSL.h
index 743eef1..6f4980c 100644
--- a/src/gpu/glsl/GrGLSL.h
+++ b/src/gpu/glsl/GrGLSL.h
@@ -53,6 +53,6 @@
     k320es_GrGLSLGeneration,
 };
 
-const char* GrGLSLTypeString(const GrShaderCaps*, GrSLType);
+const char* GrGLSLTypeString(GrSLType);
 
 #endif
diff --git a/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp b/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
index 511df10..5670f58 100644
--- a/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
@@ -67,16 +67,8 @@
 }
 
 GrGLSLFragmentShaderBuilder::GrGLSLFragmentShaderBuilder(GrGLSLProgramBuilder* program)
-    : GrGLSLFragmentBuilder(program)
-    , fSetupFragPosition(false)
-    , fHasCustomColorOutput(false)
-    , fCustomColorOutputIndex(-1)
-    , fHasSecondaryOutput(false)
-    , fForceHighPrecision(false) {
+        : GrGLSLFragmentBuilder(program) {
     fSubstageIndices.push_back(0);
-#ifdef SK_DEBUG
-    fHasReadDstColor = false;
-#endif
 }
 
 SkString GrGLSLFragmentShaderBuilder::ensureCoords2D(const GrShaderVar& coords) {
@@ -92,8 +84,80 @@
     return coords2D;
 }
 
+const char* GrGLSLFragmentShaderBuilder::sampleOffsets() {
+    SkASSERT(CustomFeatures::kSampleLocations & fProgramBuilder->header().processorFeatures());
+    SkDEBUGCODE(fUsedProcessorFeaturesThisStage_DebugOnly |= CustomFeatures::kSampleLocations);
+    SkDEBUGCODE(fUsedProcessorFeaturesAllStages_DebugOnly |= CustomFeatures::kSampleLocations);
+    return "_sampleOffsets";
+}
+
+void GrGLSLFragmentShaderBuilder::maskOffMultisampleCoverage(
+        const char* mask, ScopeFlags scopeFlags) {
+    const GrShaderCaps& shaderCaps = *fProgramBuilder->shaderCaps();
+    if (!shaderCaps.sampleVariablesSupport()) {
+        SkDEBUGFAIL("Attempted to mask sample coverage without support.");
+        return;
+    }
+    if (const char* extension = shaderCaps.sampleVariablesExtensionString()) {
+        this->addFeature(1 << kSampleVariables_GLSLPrivateFeature, extension);
+    }
+
+    if (!fHasModifiedSampleMask) {
+        fHasModifiedSampleMask = true;
+        if (ScopeFlags::kTopLevel != scopeFlags) {
+            this->codePrependf("gl_SampleMask[0] = ~0;");
+        }
+        if (!(ScopeFlags::kInsideLoop & scopeFlags)) {
+            this->codeAppendf("gl_SampleMask[0] = (%s);", mask);
+            return;
+        }
+    }
+
+    this->codeAppendf("gl_SampleMask[0] &= (%s);", mask);
+}
+
+void GrGLSLFragmentShaderBuilder::applyFnToMultisampleMask(
+        const char* fn, const char* grad, ScopeFlags scopeFlags) {
+    SkASSERT(CustomFeatures::kSampleLocations & fProgramBuilder->header().processorFeatures());
+    SkDEBUGCODE(fUsedProcessorFeaturesThisStage_DebugOnly |= CustomFeatures::kSampleLocations);
+    SkDEBUGCODE(fUsedProcessorFeaturesAllStages_DebugOnly |= CustomFeatures::kSampleLocations);
+
+    int sampleCnt = fProgramBuilder->effectiveSampleCnt();
+    SkASSERT(sampleCnt > 1);
+
+    this->codeAppendf("{");
+
+    if (!grad) {
+        SkASSERT(fProgramBuilder->shaderCaps()->shaderDerivativeSupport());
+        // In order to use HW derivatives, our neighbors within the same primitive must also be
+        // executing the same code. A per-pixel branch makes this pre-condition impossible to
+        // fulfill.
+        SkASSERT(!(ScopeFlags::kInsidePerPixelBranch & scopeFlags));
+        this->codeAppendf("float2 grad = float2(dFdx(fn), dFdy(fn));");
+        this->codeAppendf("float fnwidth = fwidth(fn);");
+        grad = "grad";
+    } else {
+        this->codeAppendf("float fnwidth = abs(%s.x) + abs(%s.y);", grad, grad);
+    }
+
+    this->codeAppendf("int mask = 0;");
+    this->codeAppendf("if (%s*2 < fnwidth) {", fn);  // Are ANY samples inside the implicit fn?
+    this->codeAppendf(    "if (%s*-2 >= fnwidth) {", fn);  // Are ALL samples inside the implicit?
+    this->codeAppendf(        "mask = ~0;");
+    this->codeAppendf(    "} else for (int i = 0; i < %i; ++i) {", sampleCnt);
+    this->codeAppendf(        "float fnsample = dot(%s, _sampleOffsets[i]) + %s;", grad, fn);
+    this->codeAppendf(        "if (fnsample < 0) {");
+    this->codeAppendf(            "mask |= (1 << i);");
+    this->codeAppendf(        "}");
+    this->codeAppendf(    "}");
+    this->codeAppendf("}");
+    this->maskOffMultisampleCoverage("mask", scopeFlags);
+
+    this->codeAppendf("}");
+}
+
 const char* GrGLSLFragmentShaderBuilder::dstColor() {
-    SkDEBUGCODE(fHasReadDstColor = true;)
+    SkDEBUGCODE(fHasReadDstColorThisStage_DebugOnly = true;)
 
     const GrShaderCaps* shaderCaps = fProgramBuilder->shaderCaps();
     if (shaderCaps->fbFetchSupport()) {
@@ -178,13 +242,16 @@
 }
 
 const char* GrGLSLFragmentShaderBuilder::getSecondaryColorOutputName() const {
-    const GrShaderCaps& caps = *fProgramBuilder->shaderCaps();
-    return caps.mustDeclareFragmentShaderOutput() ? DeclaredSecondaryColorOutputName()
-                                                  : "gl_SecondaryFragColorEXT";
+    if (this->hasSecondaryOutput()) {
+        return (fProgramBuilder->shaderCaps()->mustDeclareFragmentShaderOutput())
+                ? DeclaredSecondaryColorOutputName()
+                : "gl_SecondaryFragColorEXT";
+    }
+    return nullptr;
 }
 
 GrSurfaceOrigin GrGLSLFragmentShaderBuilder::getSurfaceOrigin() const {
-    SkASSERT(fProgramBuilder->header().fSurfaceOriginKey);
+    SkASSERT(fProgramBuilder->header().hasSurfaceOriginKey());
     return static_cast<GrSurfaceOrigin>(fProgramBuilder->header().fSurfaceOriginKey-1);
 
     GR_STATIC_ASSERT(0 == kTopLeft_GrSurfaceOrigin);
@@ -192,6 +259,24 @@
 }
 
 void GrGLSLFragmentShaderBuilder::onFinalize() {
+    SkASSERT(fProgramBuilder->header().processorFeatures()
+                     == fUsedProcessorFeaturesAllStages_DebugOnly);
+
+    if (CustomFeatures::kSampleLocations & fProgramBuilder->header().processorFeatures()) {
+        const GrPipeline& pipeline = fProgramBuilder->pipeline();
+        const SkTArray<SkPoint>& sampleLocations =
+                fProgramBuilder->renderTarget()->renderTargetPriv().getSampleLocations(pipeline);
+        this->definitions().append("const float2 _sampleOffsets[] = float2[](");
+        for (int i = 0; i < sampleLocations.count(); ++i) {
+            SkPoint offset = sampleLocations[i] - SkPoint::Make(.5f, .5f);
+            if (kBottomLeft_GrSurfaceOrigin == this->getSurfaceOrigin()) {
+                offset.fY = -offset.fY;
+            }
+            this->definitions().appendf("float2(%f, %f)", offset.x(), offset.y());
+            this->definitions().append((i + 1 != sampleLocations.count()) ? ", " : ");");
+        }
+    }
+
     fProgramBuilder->varyingHandler()->getFragDecls(&this->inputs(), &this->outputs());
 }
 
diff --git a/src/gpu/glsl/GrGLSLFragmentShaderBuilder.h b/src/gpu/glsl/GrGLSLFragmentShaderBuilder.h
index 0e0794f..3f1c2c5 100644
--- a/src/gpu/glsl/GrGLSLFragmentShaderBuilder.h
+++ b/src/gpu/glsl/GrGLSLFragmentShaderBuilder.h
@@ -47,14 +47,50 @@
     /** Appease the compiler; the derived class initializes GrGLSLFragmentBuilder. */
     GrGLSLFPFragmentBuilder() : GrGLSLFragmentBuilder(nullptr) {}
 
-    enum Coordinates {
-        kSkiaDevice_Coordinates,
-        kGLSLWindow_Coordinates,
+    /**
+     * Returns the variable name that holds the array of sample offsets from pixel center to each
+     * sample location. Before this is called, a processor must have advertised that it will use
+     * CustomFeatures::kSampleLocations.
+     */
+    virtual const char* sampleOffsets() = 0;
 
-        kLast_Coordinates = kGLSLWindow_Coordinates
+    enum class ScopeFlags {
+        // Every fragment will always execute this code, and will do it exactly once.
+        kTopLevel = 0,
+        // Either all fragments in a given primitive, or none, will execute this code.
+        kInsidePerPrimitiveBranch = (1 << 0),
+        // Any given fragment may or may not execute this code.
+        kInsidePerPixelBranch = (1 << 1),
+        // This code will be executed more than once.
+        kInsideLoop = (1 << 2)
     };
 
     /**
+     * Subtracts multisample coverage by AND-ing the sample mask with the provided "mask".
+     * Sample N corresponds to bit "1 << N".
+     *
+     * If the given scope is "kTopLevel" and the sample mask has not yet been modified, this method
+     * assigns the sample mask in place rather than pre-initializing it to ~0 then AND-ing it.
+     *
+     * Requires MSAA and GLSL support for sample variables.
+     */
+    virtual void maskOffMultisampleCoverage(const char* mask, ScopeFlags) = 0;
+
+    /**
+     * Turns off coverage at each sample where the implicit function fn > 0.
+     *
+     * The provided "fn" value represents the implicit function at pixel center. We then approximate
+     * the implicit at each sample by riding the gradient, "grad", linearly from pixel center to
+     * each sample location.
+     *
+     * If "grad" is null, we approximate the gradient using HW derivatives.
+     *
+     * Requires MSAA and GLSL support for sample variables. Also requires HW derivatives if not
+     * providing a gradient.
+     */
+    virtual void applyFnToMultisampleMask(const char* fn, const char* grad, ScopeFlags) = 0;
+
+    /**
      * Fragment procs with child procs should call these functions before/after calling emitCode
      * on a child proc.
      */
@@ -66,6 +102,8 @@
     virtual void forceHighPrecision() = 0;
 };
 
+GR_MAKE_BITFIELD_CLASS_OPS(GrGLSLFPFragmentBuilder::ScopeFlags);
+
 /*
  * This class is used by Xfer processors to build their fragment code.
  */
@@ -102,6 +140,9 @@
     virtual SkString ensureCoords2D(const GrShaderVar&) override;
 
     // GrGLSLFPFragmentBuilder interface.
+    const char* sampleOffsets() override;
+    void maskOffMultisampleCoverage(const char* mask, ScopeFlags) override;
+    void applyFnToMultisampleMask(const char* fn, const char* grad, ScopeFlags) override;
     const SkString& getMangleString() const override { return fMangleString; }
     void onBeforeChildProcEmitCode() override;
     void onAfterChildProcEmitCode() override;
@@ -114,6 +155,8 @@
     void enableAdvancedBlendEquationIfNeeded(GrBlendEquation) override;
 
 private:
+    using CustomFeatures = GrProcessor::CustomFeatures;
+
     // Private public interface, used by GrGLProgramBuilder to build a fragment shader
     void enableCustomOutput();
     void enableSecondaryOutput();
@@ -124,9 +167,13 @@
 #ifdef SK_DEBUG
     // As GLSLProcessors emit code, there are some conditions we need to verify.  We use the below
     // state to track this.  The reset call is called per processor emitted.
-    bool hasReadDstColor() const { return fHasReadDstColor; }
-    void resetVerification() {
-        fHasReadDstColor = false;
+    bool fHasReadDstColorThisStage_DebugOnly = false;
+    CustomFeatures fUsedProcessorFeaturesThisStage_DebugOnly = CustomFeatures::kNone;
+    CustomFeatures fUsedProcessorFeaturesAllStages_DebugOnly = CustomFeatures::kNone;
+
+    void debugOnly_resetPerStageVerification() {
+        fHasReadDstColorThisStage_DebugOnly = false;
+        fUsedProcessorFeaturesThisStage_DebugOnly = CustomFeatures::kNone;
     }
 #endif
 
@@ -159,17 +206,12 @@
      */
     SkString fMangleString;
 
-    bool fSetupFragPosition;
-    bool fHasCustomColorOutput;
-    int fCustomColorOutputIndex;
-    bool fHasSecondaryOutput;
-    bool fForceHighPrecision;
-
-#ifdef SK_DEBUG
-    // some state to verify shaders and effects are consistent, this is reset between effects by
-    // the program creator
-    bool fHasReadDstColor;
-#endif
+    bool fSetupFragPosition = false;
+    bool fHasCustomColorOutput = false;
+    int fCustomColorOutputIndex = -1;
+    bool fHasSecondaryOutput = false;
+    bool fHasModifiedSampleMask = false;
+    bool fForceHighPrecision = false;
 
     friend class GrGLSLProgramBuilder;
     friend class GrGLProgramBuilder;
diff --git a/src/gpu/glsl/GrGLSLProgramBuilder.cpp b/src/gpu/glsl/GrGLSLProgramBuilder.cpp
index de892b5..d0f0a53 100644
--- a/src/gpu/glsl/GrGLSLProgramBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLProgramBuilder.cpp
@@ -29,8 +29,7 @@
         , fGS(this)
         , fFS(this)
         , fStageIndex(-1)
-        , fConfig(renderTarget->config())
-        , fNumColorSamples(renderTarget->numColorSamples())
+        , fRenderTarget(renderTarget)
         , fOrigin(origin)
         , fPipeline(pipeline)
         , fPrimProc(primProc)
@@ -63,7 +62,6 @@
     this->emitAndInstallPrimProc(&inputColor, &inputCoverage);
     this->emitAndInstallFragProcs(&inputColor, &inputCoverage);
     this->emitAndInstallXferProc(inputColor, inputCoverage);
-    this->emitFSOutputSwizzle(this->pipeline().getXferProcessor().hasSecondaryOutput());
 
     return this->checkSamplerCounts();
 }
@@ -254,16 +252,28 @@
         SkASSERT(dstTexture->texturePriv().textureType() != GrTextureType::kExternal);
     }
 
+    SkString finalInColor;
+    if (colorIn.size()) {
+        if (this->desc()->header().fClampBlendInput) {
+            finalInColor.printf("saturate(%s)", colorIn.c_str());
+        } else {
+            finalInColor = colorIn;
+        }
+    } else {
+        finalInColor = "float4(1)";
+    }
+
     GrGLSLXferProcessor::EmitArgs args(&fFS,
                                        this->uniformHandler(),
                                        this->shaderCaps(),
                                        xp,
-                                       colorIn.size() ? colorIn.c_str() : "float4(1)",
+                                       finalInColor.c_str(),
                                        coverageIn.size() ? coverageIn.c_str() : "float4(1)",
                                        fFS.getPrimaryColorOutputName(),
                                        fFS.getSecondaryColorOutputName(),
                                        dstTextureSamplerHandle,
-                                       dstTextureOrigin);
+                                       dstTextureOrigin,
+                                       this->desc()->header().fOutputSwizzle);
     fXferProcessor->emitCode(args);
 
     // We have to check that effects and the code they emit are consistent, ie if an effect
@@ -279,22 +289,6 @@
     return this->uniformHandler()->addSampler(texture, state, name, this->shaderCaps());
 }
 
-void GrGLSLProgramBuilder::emitFSOutputSwizzle(bool hasSecondaryOutput) {
-    // Swizzle the fragment shader outputs if necessary.
-    GrSwizzle swizzle;
-    swizzle.setFromKey(this->desc()->header().fOutputSwizzle);
-    if (swizzle != GrSwizzle::RGBA()) {
-        fFS.codeAppendf("%s = %s.%s;", fFS.getPrimaryColorOutputName(),
-                        fFS.getPrimaryColorOutputName(),
-                        swizzle.c_str());
-        if (hasSecondaryOutput) {
-            fFS.codeAppendf("%s = %s.%s;", fFS.getSecondaryColorOutputName(),
-                            fFS.getSecondaryColorOutputName(),
-                            swizzle.c_str());
-        }
-    }
-}
-
 bool GrGLSLProgramBuilder::checkSamplerCounts() {
     const GrShaderCaps& shaderCaps = *this->shaderCaps();
     if (fNumFragmentSamplers > shaderCaps.maxFragmentSamplers()) {
@@ -306,13 +300,18 @@
 
 #ifdef SK_DEBUG
 void GrGLSLProgramBuilder::verify(const GrPrimitiveProcessor& gp) {
-}
-
-void GrGLSLProgramBuilder::verify(const GrXferProcessor& xp) {
-    SkASSERT(fFS.hasReadDstColor() == xp.willReadDstColor());
+    SkASSERT(!fFS.fHasReadDstColorThisStage_DebugOnly);
+    SkASSERT(fFS.fUsedProcessorFeaturesThisStage_DebugOnly == gp.requestedFeatures());
 }
 
 void GrGLSLProgramBuilder::verify(const GrFragmentProcessor& fp) {
+    SkASSERT(!fFS.fHasReadDstColorThisStage_DebugOnly);
+    SkASSERT(fFS.fUsedProcessorFeaturesThisStage_DebugOnly == fp.requestedFeatures());
+}
+
+void GrGLSLProgramBuilder::verify(const GrXferProcessor& xp) {
+    SkASSERT(xp.willReadDstColor() == fFS.fHasReadDstColorThisStage_DebugOnly);
+    SkASSERT(fFS.fUsedProcessorFeaturesThisStage_DebugOnly == xp.requestedFeatures());
 }
 #endif
 
@@ -353,18 +352,16 @@
         SkASSERT(!fUniformHandles.fRTWidthUni.isValid());
         GrGLSLUniformHandler* uniformHandler = this->uniformHandler();
         fUniformHandles.fRTWidthUni =
-            uniformHandler->internalAddUniformArray(kFragment_GrShaderFlag,
-                                                    kHalf_GrSLType, kDefault_GrSLPrecision,
-                                                    name, false, 0, nullptr);
+            uniformHandler->internalAddUniformArray(kFragment_GrShaderFlag, kHalf_GrSLType, name,
+                                                    false, 0, nullptr);
 }
 
 void GrGLSLProgramBuilder::addRTHeightUniform(const char* name) {
         SkASSERT(!fUniformHandles.fRTHeightUni.isValid());
         GrGLSLUniformHandler* uniformHandler = this->uniformHandler();
         fUniformHandles.fRTHeightUni =
-            uniformHandler->internalAddUniformArray(kFragment_GrShaderFlag,
-                                                    kHalf_GrSLType, kDefault_GrSLPrecision,
-                                                    name, false, 0, nullptr);
+            uniformHandler->internalAddUniformArray(kFragment_GrShaderFlag, kHalf_GrSLType, name,
+                                                    false, 0, nullptr);
 }
 
 void GrGLSLProgramBuilder::finalizeShaders() {
diff --git a/src/gpu/glsl/GrGLSLProgramBuilder.h b/src/gpu/glsl/GrGLSLProgramBuilder.h
index 4fbee10..d36e425 100644
--- a/src/gpu/glsl/GrGLSLProgramBuilder.h
+++ b/src/gpu/glsl/GrGLSLProgramBuilder.h
@@ -11,6 +11,8 @@
 #include "GrCaps.h"
 #include "GrGeometryProcessor.h"
 #include "GrProgramDesc.h"
+#include "GrRenderTarget.h"
+#include "GrRenderTargetPriv.h"
 #include "glsl/GrGLSLFragmentProcessor.h"
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
 #include "glsl/GrGLSLPrimitiveProcessor.h"
@@ -36,8 +38,12 @@
 
     const GrPrimitiveProcessor& primitiveProcessor() const { return fPrimProc; }
     const GrTextureProxy* const* primProcProxies() const { return fPrimProcProxies; }
-    GrPixelConfig config() const { return fConfig; }
-    int numColorSamples() const { return fNumColorSamples; }
+    const GrRenderTarget* renderTarget() const { return fRenderTarget; }
+    GrPixelConfig config() const { return fRenderTarget->config(); }
+    int effectiveSampleCnt() const {
+        SkASSERT(GrProcessor::CustomFeatures::kSampleLocations & header().processorFeatures());
+        return fRenderTarget->renderTargetPriv().getSampleLocations(fPipeline).count();
+    }
     GrSurfaceOrigin origin() const { return fOrigin; }
     const GrPipeline& pipeline() const { return fPipeline; }
     GrProgramDesc* desc() { return fDesc; }
@@ -84,8 +90,7 @@
 
     int fStageIndex;
 
-    const GrPixelConfig          fConfig;
-    const int                    fNumColorSamples;
+    const GrRenderTarget*        fRenderTarget;
     const GrSurfaceOrigin        fOrigin;
     const GrPipeline&            fPipeline;
     const GrPrimitiveProcessor&  fPrimProc;
@@ -121,7 +126,7 @@
     // fragment shader are cleared.
     void reset() {
         this->addStage();
-        SkDEBUGCODE(fFS.resetVerification();)
+        SkDEBUGCODE(fFS.debugOnly_resetPerStageVerification();)
     }
     void addStage() { fStageIndex++; }
 
@@ -151,13 +156,12 @@
                                     SkTArray<std::unique_ptr<GrGLSLFragmentProcessor>>*);
     void emitAndInstallXferProc(const SkString& colorIn, const SkString& coverageIn);
     SamplerHandle emitSampler(const GrTexture*, const GrSamplerState&, const char* name);
-    void emitFSOutputSwizzle(bool hasSecondaryOutput);
     bool checkSamplerCounts();
 
 #ifdef SK_DEBUG
     void verify(const GrPrimitiveProcessor&);
-    void verify(const GrXferProcessor&);
     void verify(const GrFragmentProcessor&);
+    void verify(const GrXferProcessor&);
 #endif
 
     // These are used to check that we don't excede the allowable number of resources in a shader.
diff --git a/src/gpu/glsl/GrGLSLProgramDataManager.cpp b/src/gpu/glsl/GrGLSLProgramDataManager.cpp
index 0803f7a..e89fce5 100644
--- a/src/gpu/glsl/GrGLSLProgramDataManager.cpp
+++ b/src/gpu/glsl/GrGLSLProgramDataManager.cpp
@@ -25,9 +25,3 @@
     this->setMatrix3f(u, mt);
 }
 
-void GrGLSLProgramDataManager::setSkMatrix44(UniformHandle u, const SkMatrix44& matrix) const {
-    // TODO: We could skip this temporary buffer if we had direct access to the matrix storage
-    float m[16];
-    matrix.asColMajorf(m);
-    this->setMatrix4f(u, m);
-}
diff --git a/src/gpu/glsl/GrGLSLProgramDataManager.h b/src/gpu/glsl/GrGLSLProgramDataManager.h
index 30e3d0c..a8dce9d 100644
--- a/src/gpu/glsl/GrGLSLProgramDataManager.h
+++ b/src/gpu/glsl/GrGLSLProgramDataManager.h
@@ -56,9 +56,6 @@
     // convenience method for uploading a SkMatrix to a 3x3 matrix uniform
     void setSkMatrix(UniformHandle, const SkMatrix&) const;
 
-    // convenience method for uploading a SkMatrix44 to a 4x4 matrix uniform
-    void setSkMatrix44(UniformHandle, const SkMatrix44&) const;
-
     // for nvpr only
     GR_DEFINE_RESOURCE_HANDLE_CLASS(VaryingHandle);
     virtual void setPathFragmentInputTransform(VaryingHandle u, int components,
diff --git a/src/gpu/glsl/GrGLSLShaderBuilder.cpp b/src/gpu/glsl/GrGLSLShaderBuilder.cpp
index fd9669a..96eb4fd 100644
--- a/src/gpu/glsl/GrGLSLShaderBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLShaderBuilder.cpp
@@ -46,7 +46,7 @@
                                        const GrShaderVar* args,
                                        const char* body,
                                        SkString* outName) {
-    this->functions().append(GrGLSLTypeString(fProgramBuilder->shaderCaps(), returnType));
+    this->functions().append(GrGLSLTypeString(returnType));
     fProgramBuilder->nameVariable(outName, '\0', name);
     this->functions().appendf(" %s", outName->c_str());
     this->functions().append("(");
diff --git a/src/gpu/glsl/GrGLSLShaderBuilder.h b/src/gpu/glsl/GrGLSLShaderBuilder.h
index 855011f..0616997 100644
--- a/src/gpu/glsl/GrGLSLShaderBuilder.h
+++ b/src/gpu/glsl/GrGLSLShaderBuilder.h
@@ -164,7 +164,8 @@
         kBlendFuncExtended_GLSLPrivateFeature,
         kFramebufferFetch_GLSLPrivateFeature,
         kNoPerspectiveInterpolation_GLSLPrivateFeature,
-        kLastGLSLPrivateFeature = kNoPerspectiveInterpolation_GLSLPrivateFeature
+        kSampleVariables_GLSLPrivateFeature,
+        kLastGLSLPrivateFeature = kSampleVariables_GLSLPrivateFeature
     };
 
     /*
@@ -224,12 +225,14 @@
         kFunctions,
         kMain,
         kCode,
+
+        kPrealloc = kCode + 6,  // 6 == Reasonable upper bound on number of processor stages
     };
 
     GrGLSLProgramBuilder* fProgramBuilder;
-    SkSTArray<kCode, const char*, true> fCompilerStrings;
-    SkSTArray<kCode, int, true> fCompilerStringLengths;
-    SkSTArray<kCode, SkString> fShaderStrings;
+    SkSTArray<kPrealloc, const char*, true> fCompilerStrings;
+    SkSTArray<kPrealloc, int, true> fCompilerStringLengths;
+    SkSTArray<kPrealloc, SkString> fShaderStrings;
     SkString fCode;
     SkString fFunctions;
     SkString fExtensions;
diff --git a/src/gpu/glsl/GrGLSLUniformHandler.h b/src/gpu/glsl/GrGLSLUniformHandler.h
index 5ee524c..607232e 100644
--- a/src/gpu/glsl/GrGLSLUniformHandler.h
+++ b/src/gpu/glsl/GrGLSLUniformHandler.h
@@ -45,30 +45,10 @@
         to add an array of uniforms. */
     UniformHandle addUniform(uint32_t visibility,
                              GrSLType type,
-                             GrSLPrecision precision,
                              const char* name,
                              const char** outName = nullptr) {
         SkASSERT(!GrSLTypeIsCombinedSamplerType(type));
-        return this->addUniformArray(visibility, type, precision, name, 0, outName);
-    }
-
-    UniformHandle addUniform(uint32_t visibility,
-                             GrSLType type,
-                             const char* name,
-                             const char** outName = nullptr) {
-        return this->addUniform(visibility, type, kDefault_GrSLPrecision, name, outName);
-    }
-
-    UniformHandle addUniformArray(uint32_t visibility,
-                                  GrSLType type,
-                                  GrSLPrecision precision,
-                                  const char* name,
-                                  int arrayCount,
-                                  const char** outName = nullptr) {
-        SkASSERT(!GrSLTypeIsCombinedSamplerType(type));
-        bool mangle = strncmp(name, GR_NO_MANGLE_PREFIX, strlen(GR_NO_MANGLE_PREFIX));
-        return this->internalAddUniformArray(visibility, type, precision, name, mangle, arrayCount,
-                                             outName);
+        return this->addUniformArray(visibility, type, name, 0, outName);
     }
 
     UniformHandle addUniformArray(uint32_t visibility,
@@ -78,8 +58,7 @@
                                   const char** outName = nullptr) {
         SkASSERT(!GrSLTypeIsCombinedSamplerType(type));
         bool mangle = strncmp(name, GR_NO_MANGLE_PREFIX, strlen(GR_NO_MANGLE_PREFIX));
-        return this->internalAddUniformArray(visibility, type, kDefault_GrSLPrecision, name, mangle,
-                                             arrayCount, outName);
+        return this->internalAddUniformArray(visibility, type, name, mangle, arrayCount, outName);
     }
 
     virtual const GrShaderVar& getUniformVariable(UniformHandle u) const = 0;
@@ -104,7 +83,6 @@
 
     virtual UniformHandle internalAddUniformArray(uint32_t visibility,
                                                   GrSLType type,
-                                                  GrSLPrecision precision,
                                                   const char* name,
                                                   bool mangleName,
                                                   int arrayCount,
diff --git a/src/gpu/glsl/GrGLSLVarying.cpp b/src/gpu/glsl/GrGLSLVarying.cpp
index a435f24..53f4533 100644
--- a/src/gpu/glsl/GrGLSLVarying.cpp
+++ b/src/gpu/glsl/GrGLSLVarying.cpp
@@ -109,22 +109,21 @@
         const char* modifier = v.fIsFlat ? "flat" : fDefaultInterpolationModifier;
         if (v.fVisibility & kVertex_GrShaderFlag) {
             fVertexOutputs.push_back().set(v.fType, v.fVsOut, GrShaderVar::kOut_TypeModifier,
-                                           kDefault_GrSLPrecision, nullptr, modifier);
+                                           nullptr, modifier);
             if (v.fVisibility & kGeometry_GrShaderFlag) {
                 fGeomInputs.push_back().set(v.fType, v.fVsOut, GrShaderVar::kUnsizedArray,
-                                            GrShaderVar::kIn_TypeModifier, kDefault_GrSLPrecision,
-                                            nullptr, modifier);
+                                            GrShaderVar::kIn_TypeModifier, nullptr, modifier);
             }
         }
         if (v.fVisibility & kFragment_GrShaderFlag) {
             const char* fsIn = v.fVsOut.c_str();
             if (v.fVisibility & kGeometry_GrShaderFlag) {
                 fGeomOutputs.push_back().set(v.fType, v.fGsOut, GrShaderVar::kOut_TypeModifier,
-                                             kDefault_GrSLPrecision, nullptr, modifier);
+                                             nullptr, modifier);
                 fsIn = v.fGsOut.c_str();
             }
-            fFragInputs.push_back().set(v.fType, fsIn, GrShaderVar::kIn_TypeModifier,
-                                        kDefault_GrSLPrecision, nullptr, modifier);
+            fFragInputs.push_back().set(v.fType, fsIn, GrShaderVar::kIn_TypeModifier, nullptr,
+                                        modifier);
         }
     }
     this->onFinalize();
diff --git a/src/gpu/glsl/GrGLSLXferProcessor.cpp b/src/gpu/glsl/GrGLSLXferProcessor.cpp
index 012fce7..4af5303 100644
--- a/src/gpu/glsl/GrGLSLXferProcessor.cpp
+++ b/src/gpu/glsl/GrGLSLXferProcessor.cpp
@@ -31,77 +31,92 @@
     if (!args.fXP.willReadDstColor()) {
         adjust_for_lcd_coverage(args.fXPFragBuilder, args.fInputCoverage, args.fXP);
         this->emitOutputsForBlendState(args);
-        return;
-    }
+    } else {
+        GrGLSLXPFragmentBuilder* fragBuilder = args.fXPFragBuilder;
+        GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
+        const char* dstColor = fragBuilder->dstColor();
 
-    GrGLSLXPFragmentBuilder* fragBuilder = args.fXPFragBuilder;
-    GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
-    const char* dstColor = fragBuilder->dstColor();
+        bool needsLocalOutColor = false;
 
-    bool needsLocalOutColor = false;
+        if (args.fDstTextureSamplerHandle.isValid()) {
+            bool flipY = kBottomLeft_GrSurfaceOrigin == args.fDstTextureOrigin;
 
-    if (args.fDstTextureSamplerHandle.isValid()) {
-        bool flipY = kBottomLeft_GrSurfaceOrigin == args.fDstTextureOrigin;
+            if (args.fInputCoverage) {
+                // We don't think any shaders actually output negative coverage, but just as a
+                // safety check for floating point precision errors we compare with <= here. We just
+                // check the rgb values of the coverage since the alpha may not have been set when
+                // using lcd. If we are using single channel coverage alpha will equal to rgb
+                // anyways.
+                //
+                // The discard here also helps for batching text draws together which need to read
+                // from a dst copy for blends. Though this only helps the case where the outer
+                // bounding boxes of each letter overlap and not two actually parts of the text.
+                fragBuilder->codeAppendf("if (all(lessThanEqual(%s.rgb, half3(0)))) {"
+                                         "    discard;"
+                                         "}", args.fInputCoverage);
+            }
 
-        if (args.fInputCoverage) {
-            // We don't think any shaders actually output negative coverage, but just as a safety
-            // check for floating point precision errors we compare with <= here. We just check the
-            // rgb values of the coverage since the alpha may not have been set when using lcd. If
-            // we are using single channel coverage alpha will equal to rgb anyways.
-            //
-            // The discard here also helps for batching text draws together which need to read from
-            // a dst copy for blends. Though this only helps the case where the outer bounding boxes
-            // of each letter overlap and not two actually parts of the text.
-            fragBuilder->codeAppendf("if (all(lessThanEqual(%s.rgb, half3(0)))) {"
-                                     "    discard;"
-                                     "}", args.fInputCoverage);
+            const char* dstTopLeftName;
+            const char* dstCoordScaleName;
+
+            fDstTopLeftUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
+                                                        kHalf2_GrSLType,
+                                                        "DstTextureUpperLeft",
+                                                        &dstTopLeftName);
+            fDstScaleUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
+                                                      kHalf2_GrSLType,
+                                                      "DstTextureCoordScale",
+                                                      &dstCoordScaleName);
+
+            fragBuilder->codeAppend("// Read color from copy of the destination.\n");
+            fragBuilder->codeAppendf("half2 _dstTexCoord = (half2(sk_FragCoord.xy) - %s) * %s;",
+                                     dstTopLeftName, dstCoordScaleName);
+
+            if (flipY) {
+                fragBuilder->codeAppend("_dstTexCoord.y = 1.0 - _dstTexCoord.y;");
+            }
+
+            fragBuilder->codeAppendf("half4 %s = ", dstColor);
+            fragBuilder->appendTextureLookup(args.fDstTextureSamplerHandle, "_dstTexCoord",
+                                             kHalf2_GrSLType);
+            fragBuilder->codeAppend(";");
+        } else {
+            needsLocalOutColor = args.fShaderCaps->requiresLocalOutputColorForFBFetch();
         }
 
-        const char* dstTopLeftName;
-        const char* dstCoordScaleName;
-
-        fDstTopLeftUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
-                                                    kHalf2_GrSLType,
-                                                    "DstTextureUpperLeft",
-                                                    &dstTopLeftName);
-        fDstScaleUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
-                                                  kHalf2_GrSLType,
-                                                  "DstTextureCoordScale",
-                                                  &dstCoordScaleName);
-
-        fragBuilder->codeAppend("// Read color from copy of the destination.\n");
-        fragBuilder->codeAppendf("half2 _dstTexCoord = (half2(sk_FragCoord.xy) - %s) * %s;",
-                                 dstTopLeftName, dstCoordScaleName);
-
-        if (flipY) {
-            fragBuilder->codeAppend("_dstTexCoord.y = 1.0 - _dstTexCoord.y;");
+        const char* outColor = "_localColorOut";
+        if (!needsLocalOutColor) {
+            outColor = args.fOutputPrimary;
+        } else {
+            fragBuilder->codeAppendf("half4 %s;", outColor);
         }
 
-        fragBuilder->codeAppendf("half4 %s = ", dstColor);
-        fragBuilder->appendTextureLookup(args.fDstTextureSamplerHandle, "_dstTexCoord",
-                                         kHalf2_GrSLType);
-        fragBuilder->codeAppend(";");
-    } else {
-        needsLocalOutColor = args.fShaderCaps->requiresLocalOutputColorForFBFetch();
+        this->emitBlendCodeForDstRead(fragBuilder,
+                                      uniformHandler,
+                                      args.fInputColor,
+                                      args.fInputCoverage,
+                                      dstColor,
+                                      outColor,
+                                      args.fOutputSecondary,
+                                      args.fXP);
+        if (needsLocalOutColor) {
+            fragBuilder->codeAppendf("%s = %s;", args.fOutputPrimary, outColor);
+        }
     }
 
-    const char* outColor = "_localColorOut";
-    if (!needsLocalOutColor) {
-        outColor = args.fOutputPrimary;
-    } else {
-        fragBuilder->codeAppendf("half4 %s;", outColor);
-    }
+    // Swizzle the fragment shader outputs if necessary.
+    this->emitOutputSwizzle(
+            args.fXPFragBuilder, args.fOutputSwizzle, args.fOutputPrimary, args.fOutputSecondary);
+}
 
-    this->emitBlendCodeForDstRead(fragBuilder,
-                                  uniformHandler,
-                                  args.fInputColor,
-                                  args.fInputCoverage,
-                                  dstColor,
-                                  outColor,
-                                  args.fOutputSecondary,
-                                  args.fXP);
-    if (needsLocalOutColor) {
-        fragBuilder->codeAppendf("%s = %s;", args.fOutputPrimary, outColor);
+void GrGLSLXferProcessor::emitOutputSwizzle(
+        GrGLSLXPFragmentBuilder* x, const GrSwizzle& swizzle, const char* outColor,
+        const char* outColorSecondary) const {
+    if (GrSwizzle::RGBA() != swizzle) {
+        x->codeAppendf("%s = %s.%s;", outColor, outColor, swizzle.c_str());
+        if (outColorSecondary) {
+            x->codeAppendf("%s = %s.%s;", outColorSecondary, outColorSecondary, swizzle.c_str());
+        }
     }
 }
 
diff --git a/src/gpu/glsl/GrGLSLXferProcessor.h b/src/gpu/glsl/GrGLSLXferProcessor.h
index 465e2a7..3e189ea 100644
--- a/src/gpu/glsl/GrGLSLXferProcessor.h
+++ b/src/gpu/glsl/GrGLSLXferProcessor.h
@@ -35,7 +35,8 @@
                  const char* outputPrimary,
                  const char* outputSecondary,
                  const SamplerHandle dstTextureSamplerHandle,
-                 GrSurfaceOrigin dstTextureOrigin)
+                 GrSurfaceOrigin dstTextureOrigin,
+                 uint16_t outputSwizzleKey)
                 : fXPFragBuilder(fragBuilder)
                 , fUniformHandler(uniformHandler)
                 , fShaderCaps(caps)
@@ -45,7 +46,9 @@
                 , fOutputPrimary(outputPrimary)
                 , fOutputSecondary(outputSecondary)
                 , fDstTextureSamplerHandle(dstTextureSamplerHandle)
-                , fDstTextureOrigin(dstTextureOrigin) {}
+                , fDstTextureOrigin(dstTextureOrigin) {
+            fOutputSwizzle.setFromKey(outputSwizzleKey);
+        }
         GrGLSLXPFragmentBuilder* fXPFragBuilder;
         GrGLSLUniformHandler* fUniformHandler;
         const GrShaderCaps* fShaderCaps;
@@ -56,6 +59,7 @@
         const char* fOutputSecondary;
         const SamplerHandle fDstTextureSamplerHandle;
         GrSurfaceOrigin fDstTextureOrigin;
+        GrSwizzle fOutputSwizzle;
     };
     /**
      * This is similar to emitCode() in the base class, except it takes a full shader builder.
@@ -107,6 +111,11 @@
         SK_ABORT("emitBlendCodeForDstRead not implemented.");
     }
 
+    virtual void emitOutputSwizzle(GrGLSLXPFragmentBuilder*,
+                                   const GrSwizzle&,
+                                   const char* outColor,
+                                   const char* outColorSecondary) const;
+
     virtual void onSetData(const GrGLSLProgramDataManager&, const GrXferProcessor&) = 0;
 
     GrGLSLProgramDataManager::UniformHandle fDstTopLeftUni;
diff --git a/src/gpu/gradients/GrClampedGradientEffect.cpp b/src/gpu/gradients/GrClampedGradientEffect.cpp
deleted file mode 100644
index 13da0ea..0000000
--- a/src/gpu/gradients/GrClampedGradientEffect.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrClampedGradientEffect.fp; do not modify.
- **************************************************************************************************/
-#include "GrClampedGradientEffect.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLClampedGradientEffect : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLClampedGradientEffect() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrClampedGradientEffect& _outer = args.fFp.cast<GrClampedGradientEffect>();
-        (void)_outer;
-        auto leftBorderColor = _outer.leftBorderColor();
-        (void)leftBorderColor;
-        auto rightBorderColor = _outer.rightBorderColor();
-        (void)rightBorderColor;
-        auto makePremul = _outer.makePremul();
-        (void)makePremul;
-        auto colorsAreOpaque = _outer.colorsAreOpaque();
-        (void)colorsAreOpaque;
-        fLeftBorderColorVar = args.fUniformHandler->addUniform(
-                kFragment_GrShaderFlag, kHalf4_GrSLType, kDefault_GrSLPrecision, "leftBorderColor");
-        fRightBorderColorVar =
-                args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType,
-                                                 kDefault_GrSLPrecision, "rightBorderColor");
-        SkString _child1("_child1");
-        this->emitChild(1, &_child1, args);
-        fragBuilder->codeAppendf(
-                "half4 t = %s;\nif (!%s && t.y < 0.0) {\n    %s = half4(0.0);\n} else if (t.x < "
-                "0.0) {\n    %s = %s;\n} else if (t.x > 1.0) {\n    %s = %s;\n} else {",
-                _child1.c_str(),
-                (_outer.childProcessor(1).preservesOpaqueInput() ? "true" : "false"),
-                args.fOutputColor, args.fOutputColor,
-                args.fUniformHandler->getUniformCStr(fLeftBorderColorVar), args.fOutputColor,
-                args.fUniformHandler->getUniformCStr(fRightBorderColorVar));
-        SkString _input0("t");
-        SkString _child0("_child0");
-        this->emitChild(0, _input0.c_str(), &_child0, args);
-        fragBuilder->codeAppendf("\n    %s = %s;\n}\n@if (%s) {\n    %s.xyz *= %s.w;\n}\n",
-                                 args.fOutputColor, _child0.c_str(),
-                                 (_outer.makePremul() ? "true" : "false"), args.fOutputColor,
-                                 args.fOutputColor);
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {
-        const GrClampedGradientEffect& _outer = _proc.cast<GrClampedGradientEffect>();
-        {
-            const SkPMColor4f& leftBorderColorValue = _outer.leftBorderColor();
-            if (fLeftBorderColorPrev != leftBorderColorValue) {
-                fLeftBorderColorPrev = leftBorderColorValue;
-                pdman.set4fv(fLeftBorderColorVar, 1, leftBorderColorValue.vec());
-            }
-            const SkPMColor4f& rightBorderColorValue = _outer.rightBorderColor();
-            if (fRightBorderColorPrev != rightBorderColorValue) {
-                fRightBorderColorPrev = rightBorderColorValue;
-                pdman.set4fv(fRightBorderColorVar, 1, rightBorderColorValue.vec());
-            }
-        }
-    }
-    SkPMColor4f fLeftBorderColorPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN};
-    SkPMColor4f fRightBorderColorPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN};
-    UniformHandle fLeftBorderColorVar;
-    UniformHandle fRightBorderColorVar;
-};
-GrGLSLFragmentProcessor* GrClampedGradientEffect::onCreateGLSLInstance() const {
-    return new GrGLSLClampedGradientEffect();
-}
-void GrClampedGradientEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                                    GrProcessorKeyBuilder* b) const {
-    b->add32((int32_t)fMakePremul);
-}
-bool GrClampedGradientEffect::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrClampedGradientEffect& that = other.cast<GrClampedGradientEffect>();
-    (void)that;
-    if (fLeftBorderColor != that.fLeftBorderColor) return false;
-    if (fRightBorderColor != that.fRightBorderColor) return false;
-    if (fMakePremul != that.fMakePremul) return false;
-    if (fColorsAreOpaque != that.fColorsAreOpaque) return false;
-    return true;
-}
-GrClampedGradientEffect::GrClampedGradientEffect(const GrClampedGradientEffect& src)
-        : INHERITED(kGrClampedGradientEffect_ClassID, src.optimizationFlags())
-        , fLeftBorderColor(src.fLeftBorderColor)
-        , fRightBorderColor(src.fRightBorderColor)
-        , fMakePremul(src.fMakePremul)
-        , fColorsAreOpaque(src.fColorsAreOpaque) {
-    this->registerChildProcessor(src.childProcessor(0).clone());
-    this->registerChildProcessor(src.childProcessor(1).clone());
-}
-std::unique_ptr<GrFragmentProcessor> GrClampedGradientEffect::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrClampedGradientEffect(*this));
-}
diff --git a/src/gpu/gradients/GrClampedGradientEffect.h b/src/gpu/gradients/GrClampedGradientEffect.h
deleted file mode 100644
index 26e6d72..0000000
--- a/src/gpu/gradients/GrClampedGradientEffect.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrClampedGradientEffect.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrClampedGradientEffect_DEFINED
-#define GrClampedGradientEffect_DEFINED
-#include "SkTypes.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrClampedGradientEffect : public GrFragmentProcessor {
-public:
-    const SkPMColor4f& leftBorderColor() const { return fLeftBorderColor; }
-    const SkPMColor4f& rightBorderColor() const { return fRightBorderColor; }
-    bool makePremul() const { return fMakePremul; }
-    bool colorsAreOpaque() const { return fColorsAreOpaque; }
-    static std::unique_ptr<GrFragmentProcessor> Make(
-            std::unique_ptr<GrFragmentProcessor> colorizer,
-            std::unique_ptr<GrFragmentProcessor> gradLayout, SkPMColor4f leftBorderColor,
-            SkPMColor4f rightBorderColor, bool makePremul, bool colorsAreOpaque) {
-        return std::unique_ptr<GrFragmentProcessor>(new GrClampedGradientEffect(
-                std::move(colorizer), std::move(gradLayout), leftBorderColor, rightBorderColor,
-                makePremul, colorsAreOpaque));
-    }
-    GrClampedGradientEffect(const GrClampedGradientEffect& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "ClampedGradientEffect"; }
-
-private:
-    GrClampedGradientEffect(std::unique_ptr<GrFragmentProcessor> colorizer,
-                            std::unique_ptr<GrFragmentProcessor> gradLayout,
-                            SkPMColor4f leftBorderColor, SkPMColor4f rightBorderColor,
-                            bool makePremul, bool colorsAreOpaque)
-            : INHERITED(kGrClampedGradientEffect_ClassID,
-                        (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag |
-                                (colorsAreOpaque && gradLayout->preservesOpaqueInput()
-                                         ? kPreservesOpaqueInput_OptimizationFlag
-                                         : kNone_OptimizationFlags))
-            , fLeftBorderColor(leftBorderColor)
-            , fRightBorderColor(rightBorderColor)
-            , fMakePremul(makePremul)
-            , fColorsAreOpaque(colorsAreOpaque) {
-        this->registerChildProcessor(std::move(colorizer));
-        this->registerChildProcessor(std::move(gradLayout));
-    }
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    SkPMColor4f fLeftBorderColor;
-    SkPMColor4f fRightBorderColor;
-    bool fMakePremul;
-    bool fColorsAreOpaque;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/gradients/GrDualIntervalGradientColorizer.cpp b/src/gpu/gradients/GrDualIntervalGradientColorizer.cpp
deleted file mode 100644
index e18f4d7..0000000
--- a/src/gpu/gradients/GrDualIntervalGradientColorizer.cpp
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrDualIntervalGradientColorizer.fp; do not modify.
- **************************************************************************************************/
-#include "GrDualIntervalGradientColorizer.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLDualIntervalGradientColorizer : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLDualIntervalGradientColorizer() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrDualIntervalGradientColorizer& _outer =
-                args.fFp.cast<GrDualIntervalGradientColorizer>();
-        (void)_outer;
-        auto scale01 = _outer.scale01();
-        (void)scale01;
-        auto bias01 = _outer.bias01();
-        (void)bias01;
-        auto scale23 = _outer.scale23();
-        (void)scale23;
-        auto bias23 = _outer.bias23();
-        (void)bias23;
-        auto threshold = _outer.threshold();
-        (void)threshold;
-        fScale01Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
-                                                       kDefault_GrSLPrecision, "scale01");
-        fBias01Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
-                                                      kDefault_GrSLPrecision, "bias01");
-        fScale23Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
-                                                       kDefault_GrSLPrecision, "scale23");
-        fBias23Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
-                                                      kDefault_GrSLPrecision, "bias23");
-        fThresholdVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType,
-                                                         kDefault_GrSLPrecision, "threshold");
-        fragBuilder->codeAppendf(
-                "half t = %s.x;\nfloat4 scale, bias;\nif (t < %s) {\n    scale = %s;\n    bias = "
-                "%s;\n} else {\n    scale = %s;\n    bias = %s;\n}\n%s = half4(float(t) * scale + "
-                "bias);\n",
-                args.fInputColor, args.fUniformHandler->getUniformCStr(fThresholdVar),
-                args.fUniformHandler->getUniformCStr(fScale01Var),
-                args.fUniformHandler->getUniformCStr(fBias01Var),
-                args.fUniformHandler->getUniformCStr(fScale23Var),
-                args.fUniformHandler->getUniformCStr(fBias23Var), args.fOutputColor);
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {
-        const GrDualIntervalGradientColorizer& _outer =
-                _proc.cast<GrDualIntervalGradientColorizer>();
-        {
-            const SkPMColor4f& scale01Value = _outer.scale01();
-            if (fScale01Prev != scale01Value) {
-                fScale01Prev = scale01Value;
-                pdman.set4fv(fScale01Var, 1, scale01Value.vec());
-            }
-            const SkPMColor4f& bias01Value = _outer.bias01();
-            if (fBias01Prev != bias01Value) {
-                fBias01Prev = bias01Value;
-                pdman.set4fv(fBias01Var, 1, bias01Value.vec());
-            }
-            const SkPMColor4f& scale23Value = _outer.scale23();
-            if (fScale23Prev != scale23Value) {
-                fScale23Prev = scale23Value;
-                pdman.set4fv(fScale23Var, 1, scale23Value.vec());
-            }
-            const SkPMColor4f& bias23Value = _outer.bias23();
-            if (fBias23Prev != bias23Value) {
-                fBias23Prev = bias23Value;
-                pdman.set4fv(fBias23Var, 1, bias23Value.vec());
-            }
-            float thresholdValue = _outer.threshold();
-            if (fThresholdPrev != thresholdValue) {
-                fThresholdPrev = thresholdValue;
-                pdman.set1f(fThresholdVar, thresholdValue);
-            }
-        }
-    }
-    SkPMColor4f fScale01Prev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN};
-    SkPMColor4f fBias01Prev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN};
-    SkPMColor4f fScale23Prev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN};
-    SkPMColor4f fBias23Prev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN};
-    float fThresholdPrev = SK_FloatNaN;
-    UniformHandle fScale01Var;
-    UniformHandle fBias01Var;
-    UniformHandle fScale23Var;
-    UniformHandle fBias23Var;
-    UniformHandle fThresholdVar;
-};
-GrGLSLFragmentProcessor* GrDualIntervalGradientColorizer::onCreateGLSLInstance() const {
-    return new GrGLSLDualIntervalGradientColorizer();
-}
-void GrDualIntervalGradientColorizer::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                                            GrProcessorKeyBuilder* b) const {}
-bool GrDualIntervalGradientColorizer::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrDualIntervalGradientColorizer& that = other.cast<GrDualIntervalGradientColorizer>();
-    (void)that;
-    if (fScale01 != that.fScale01) return false;
-    if (fBias01 != that.fBias01) return false;
-    if (fScale23 != that.fScale23) return false;
-    if (fBias23 != that.fBias23) return false;
-    if (fThreshold != that.fThreshold) return false;
-    return true;
-}
-GrDualIntervalGradientColorizer::GrDualIntervalGradientColorizer(
-        const GrDualIntervalGradientColorizer& src)
-        : INHERITED(kGrDualIntervalGradientColorizer_ClassID, src.optimizationFlags())
-        , fScale01(src.fScale01)
-        , fBias01(src.fBias01)
-        , fScale23(src.fScale23)
-        , fBias23(src.fBias23)
-        , fThreshold(src.fThreshold) {}
-std::unique_ptr<GrFragmentProcessor> GrDualIntervalGradientColorizer::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrDualIntervalGradientColorizer(*this));
-}
-
-std::unique_ptr<GrFragmentProcessor> GrDualIntervalGradientColorizer::Make(const SkPMColor4f& c0,
-                                                                           const SkPMColor4f& c1,
-                                                                           const SkPMColor4f& c2,
-                                                                           const SkPMColor4f& c3,
-                                                                           float threshold) {
-    // Derive scale and biases from the 4 colors and threshold
-    auto vc0 = Sk4f::Load(c0.vec());
-    auto vc1 = Sk4f::Load(c1.vec());
-    auto scale01 = (vc1 - vc0) / threshold;
-    // bias01 = c0
-
-    auto vc2 = Sk4f::Load(c2.vec());
-    auto vc3 = Sk4f::Load(c3.vec());
-    auto scale23 = (vc3 - vc2) / (1 - threshold);
-    auto bias23 = vc2 - threshold * scale23;
-
-    return std::unique_ptr<GrFragmentProcessor>(new GrDualIntervalGradientColorizer(
-            {scale01[0], scale01[1], scale01[2], scale01[3]}, c0,
-            {scale23[0], scale23[1], scale23[2], scale23[3]},
-            {bias23[0], bias23[1], bias23[2], bias23[3]}, threshold));
-}
diff --git a/src/gpu/gradients/GrDualIntervalGradientColorizer.h b/src/gpu/gradients/GrDualIntervalGradientColorizer.h
deleted file mode 100644
index 5a99e5b..0000000
--- a/src/gpu/gradients/GrDualIntervalGradientColorizer.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrDualIntervalGradientColorizer.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrDualIntervalGradientColorizer_DEFINED
-#define GrDualIntervalGradientColorizer_DEFINED
-#include "SkTypes.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrDualIntervalGradientColorizer : public GrFragmentProcessor {
-public:
-    const SkPMColor4f& scale01() const { return fScale01; }
-    const SkPMColor4f& bias01() const { return fBias01; }
-    const SkPMColor4f& scale23() const { return fScale23; }
-    const SkPMColor4f& bias23() const { return fBias23; }
-    float threshold() const { return fThreshold; }
-
-    static std::unique_ptr<GrFragmentProcessor> Make(const SkPMColor4f& c0, const SkPMColor4f& c1,
-                                                     const SkPMColor4f& c2, const SkPMColor4f& c3,
-                                                     float threshold);
-    GrDualIntervalGradientColorizer(const GrDualIntervalGradientColorizer& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "DualIntervalGradientColorizer"; }
-
-private:
-    GrDualIntervalGradientColorizer(SkPMColor4f scale01, SkPMColor4f bias01, SkPMColor4f scale23,
-                                    SkPMColor4f bias23, float threshold)
-            : INHERITED(kGrDualIntervalGradientColorizer_ClassID, kNone_OptimizationFlags)
-            , fScale01(scale01)
-            , fBias01(bias01)
-            , fScale23(scale23)
-            , fBias23(bias23)
-            , fThreshold(threshold) {}
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    SkPMColor4f fScale01;
-    SkPMColor4f fBias01;
-    SkPMColor4f fScale23;
-    SkPMColor4f fBias23;
-    float fThreshold;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/gradients/GrGradientShader.cpp b/src/gpu/gradients/GrGradientShader.cpp
index 7bf1866..ac60b21 100644
--- a/src/gpu/gradients/GrGradientShader.cpp
+++ b/src/gpu/gradients/GrGradientShader.cpp
@@ -7,24 +7,26 @@
 
 #include "GrGradientShader.h"
 
-#include "GrClampedGradientEffect.h"
-#include "GrTiledGradientEffect.h"
+#include "generated/GrClampedGradientEffect.h"
+#include "generated/GrTiledGradientEffect.h"
 
-#include "GrLinearGradientLayout.h"
-#include "GrRadialGradientLayout.h"
-#include "GrSweepGradientLayout.h"
-#include "GrTwoPointConicalGradientLayout.h"
+#include "generated/GrLinearGradientLayout.h"
+#include "generated/GrRadialGradientLayout.h"
+#include "generated/GrSweepGradientLayout.h"
+#include "generated/GrTwoPointConicalGradientLayout.h"
 
-#include "GrDualIntervalGradientColorizer.h"
-#include "GrSingleIntervalGradientColorizer.h"
-#include "GrTextureGradientColorizer.h"
-#include "GrUnrolledBinaryGradientColorizer.h"
+#include "generated/GrDualIntervalGradientColorizer.h"
+#include "generated/GrSingleIntervalGradientColorizer.h"
+#include "generated/GrTextureGradientColorizer.h"
+#include "generated/GrUnrolledBinaryGradientColorizer.h"
 #include "GrGradientBitmapCache.h"
 
-#include "SkGr.h"
+#include "GrCaps.h"
 #include "GrColor.h"
-#include "GrContext.h"
-#include "GrContextPriv.h"
+#include "GrColorSpaceInfo.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
+#include "SkGr.h"
 
 // Intervals smaller than this (that aren't hard stops) on low-precision-only devices force us to
 // use the textured gradient
@@ -205,15 +207,15 @@
     // All tile modes are supported (unless something was added to SkShader)
     std::unique_ptr<GrFragmentProcessor> master;
     switch(shader.getTileMode()) {
-        case SkShader::kRepeat_TileMode:
+        case SkTileMode::kRepeat:
             master = GrTiledGradientEffect::Make(std::move(colorizer), std::move(layout),
                                                  /* mirror */ false, makePremul, allOpaque);
             break;
-        case SkShader::kMirror_TileMode:
+        case SkTileMode::kMirror:
             master = GrTiledGradientEffect::Make(std::move(colorizer), std::move(layout),
                                                  /* mirror */ true, makePremul, allOpaque);
             break;
-        case SkShader::kClamp_TileMode:
+        case SkTileMode::kClamp:
             // For the clamped mode, the border colors are the first and last colors, corresponding
             // to t=0 and t=1, because SkGradientShaderBase enforces that by adding color stops as
             // appropriate. If there is a hard stop, this grabs the expected outer colors for the
@@ -221,7 +223,7 @@
             master = GrClampedGradientEffect::Make(std::move(colorizer), std::move(layout),
                     colors[0], colors[shader.fColorCount - 1], makePremul, allOpaque);
             break;
-        case SkShader::kDecal_TileMode:
+        case SkTileMode::kDecal:
             // Even if the gradient colors are opaque, the decal borders are transparent so
             // disable that optimization
             master = GrClampedGradientEffect::Make(std::move(colorizer), std::move(layout),
@@ -294,7 +296,7 @@
             stop = i < fColorCount - 1 ? stop + random->nextUScalar1() * (1.f - stop) : 1.f;
         }
     }
-    fTileMode = static_cast<SkShader::TileMode>(random->nextULessThan(SkShader::kTileModeCount));
+    fTileMode = static_cast<SkTileMode>(random->nextULessThan(kSkTileModeCount));
 }
 #endif
 
diff --git a/src/gpu/gradients/GrGradientShader.h b/src/gpu/gradients/GrGradientShader.h
index e74f562..0200e17 100644
--- a/src/gpu/gradients/GrGradientShader.h
+++ b/src/gpu/gradients/GrGradientShader.h
@@ -54,7 +54,7 @@
         SkColor4f fColors4f[kMaxRandomGradientColors];
         sk_sp<SkColorSpace> fColorSpace;
         SkScalar fStopStorage[kMaxRandomGradientColors];
-        SkShader::TileMode fTileMode;
+        SkTileMode fTileMode;
         int fColorCount;
         SkScalar* fStops;
     };
diff --git a/src/gpu/gradients/GrLinearGradientLayout.cpp b/src/gpu/gradients/GrLinearGradientLayout.cpp
deleted file mode 100644
index cd53b36..0000000
--- a/src/gpu/gradients/GrLinearGradientLayout.cpp
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrLinearGradientLayout.fp; do not modify.
- **************************************************************************************************/
-#include "GrLinearGradientLayout.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLLinearGradientLayout : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLLinearGradientLayout() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrLinearGradientLayout& _outer = args.fFp.cast<GrLinearGradientLayout>();
-        (void)_outer;
-        auto gradientMatrix = _outer.gradientMatrix();
-        (void)gradientMatrix;
-        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
-        fragBuilder->codeAppendf("half t = half(%s.x);\n%s = half4(t, 1.0, 0.0, 0.0);\n",
-                                 sk_TransformedCoords2D_0.c_str(), args.fOutputColor);
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {}
-};
-GrGLSLFragmentProcessor* GrLinearGradientLayout::onCreateGLSLInstance() const {
-    return new GrGLSLLinearGradientLayout();
-}
-void GrLinearGradientLayout::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                                   GrProcessorKeyBuilder* b) const {}
-bool GrLinearGradientLayout::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrLinearGradientLayout& that = other.cast<GrLinearGradientLayout>();
-    (void)that;
-    if (fGradientMatrix != that.fGradientMatrix) return false;
-    return true;
-}
-GrLinearGradientLayout::GrLinearGradientLayout(const GrLinearGradientLayout& src)
-        : INHERITED(kGrLinearGradientLayout_ClassID, src.optimizationFlags())
-        , fGradientMatrix(src.fGradientMatrix)
-        , fCoordTransform0(src.fCoordTransform0) {
-    this->addCoordTransform(&fCoordTransform0);
-}
-std::unique_ptr<GrFragmentProcessor> GrLinearGradientLayout::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrLinearGradientLayout(*this));
-}
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrLinearGradientLayout);
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrLinearGradientLayout::TestCreate(GrProcessorTestData* d) {
-    SkScalar scale = GrGradientShader::RandomParams::kGradientScale;
-    SkPoint points[] = {
-            {d->fRandom->nextRangeScalar(0.0f, scale), d->fRandom->nextRangeScalar(0.0f, scale)},
-            {d->fRandom->nextRangeScalar(0.0f, scale), d->fRandom->nextRangeScalar(0.0f, scale)}};
-
-    GrGradientShader::RandomParams params(d->fRandom);
-    auto shader = params.fUseColors4f
-                          ? SkGradientShader::MakeLinear(points, params.fColors4f,
-                                                         params.fColorSpace, params.fStops,
-                                                         params.fColorCount, params.fTileMode)
-                          : SkGradientShader::MakeLinear(points, params.fColors, params.fStops,
-                                                         params.fColorCount, params.fTileMode);
-    GrTest::TestAsFPArgs asFPArgs(d);
-    std::unique_ptr<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args());
-    GrAlwaysAssert(fp);
-    return fp;
-}
-#endif
-
-std::unique_ptr<GrFragmentProcessor> GrLinearGradientLayout::Make(const SkLinearGradient& grad,
-                                                                  const GrFPArgs& args) {
-    SkMatrix matrix;
-    if (!grad.totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) {
-        return nullptr;
-    }
-    matrix.postConcat(grad.getGradientMatrix());
-    return std::unique_ptr<GrFragmentProcessor>(new GrLinearGradientLayout(matrix));
-}
diff --git a/src/gpu/gradients/GrLinearGradientLayout.fp b/src/gpu/gradients/GrLinearGradientLayout.fp
index d12ee6c..7c61f0e 100644
--- a/src/gpu/gradients/GrLinearGradientLayout.fp
+++ b/src/gpu/gradients/GrLinearGradientLayout.fp
@@ -12,7 +12,14 @@
 }
 
 void main() {
-    half t = half(sk_TransformedCoords2D[0].x);
+    // We add a tiny delta to t. When gradient stops are set up so that a hard stop in a vertically
+    // or horizontally oriented gradient falls exactly at a column or row of pixel centers we can
+    // we can get slightly different interpolated t values along the column/row. By adding the delta
+    // we will consistently get the color to the "right" of the stop. Of course if the hard stop
+    // falls at X.5 - delta then we still could get inconsistent results, but that is much less
+    // likely. crbug.com/938592
+    // If/when we add filtering of the gradient this can be removed.
+    half t = half(sk_TransformedCoords2D[0].x) + 0.00001;
     sk_OutColor = half4(t, 1, 0, 0); // y = 1 for always valid
 }
 
@@ -20,7 +27,7 @@
 
 @header {
     #include "SkLinearGradient.h"
-    #include "GrGradientShader.h"
+    #include "../GrGradientShader.h"
 }
 
 // The linear gradient never rejects a pixel so it doesn't change opacity
diff --git a/src/gpu/gradients/GrLinearGradientLayout.h b/src/gpu/gradients/GrLinearGradientLayout.h
deleted file mode 100644
index 76ed59a..0000000
--- a/src/gpu/gradients/GrLinearGradientLayout.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrLinearGradientLayout.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrLinearGradientLayout_DEFINED
-#define GrLinearGradientLayout_DEFINED
-#include "SkTypes.h"
-
-#include "SkLinearGradient.h"
-#include "GrGradientShader.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrLinearGradientLayout : public GrFragmentProcessor {
-public:
-    const SkMatrix44& gradientMatrix() const { return fGradientMatrix; }
-
-    static std::unique_ptr<GrFragmentProcessor> Make(const SkLinearGradient& gradient,
-                                                     const GrFPArgs& args);
-    GrLinearGradientLayout(const GrLinearGradientLayout& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "LinearGradientLayout"; }
-
-private:
-    GrLinearGradientLayout(SkMatrix44 gradientMatrix)
-            : INHERITED(kGrLinearGradientLayout_ClassID,
-                        (OptimizationFlags)kPreservesOpaqueInput_OptimizationFlag)
-            , fGradientMatrix(gradientMatrix)
-            , fCoordTransform0(gradientMatrix) {
-        this->addCoordTransform(&fCoordTransform0);
-    }
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    SkMatrix44 fGradientMatrix;
-    GrCoordTransform fCoordTransform0;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/gradients/GrRadialGradientLayout.cpp b/src/gpu/gradients/GrRadialGradientLayout.cpp
deleted file mode 100644
index f7f7de5..0000000
--- a/src/gpu/gradients/GrRadialGradientLayout.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrRadialGradientLayout.fp; do not modify.
- **************************************************************************************************/
-#include "GrRadialGradientLayout.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLRadialGradientLayout : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLRadialGradientLayout() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrRadialGradientLayout& _outer = args.fFp.cast<GrRadialGradientLayout>();
-        (void)_outer;
-        auto gradientMatrix = _outer.gradientMatrix();
-        (void)gradientMatrix;
-        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
-        fragBuilder->codeAppendf("half t = half(length(%s));\n%s = half4(t, 1.0, 0.0, 0.0);\n",
-                                 sk_TransformedCoords2D_0.c_str(), args.fOutputColor);
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {}
-};
-GrGLSLFragmentProcessor* GrRadialGradientLayout::onCreateGLSLInstance() const {
-    return new GrGLSLRadialGradientLayout();
-}
-void GrRadialGradientLayout::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                                   GrProcessorKeyBuilder* b) const {}
-bool GrRadialGradientLayout::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrRadialGradientLayout& that = other.cast<GrRadialGradientLayout>();
-    (void)that;
-    if (fGradientMatrix != that.fGradientMatrix) return false;
-    return true;
-}
-GrRadialGradientLayout::GrRadialGradientLayout(const GrRadialGradientLayout& src)
-        : INHERITED(kGrRadialGradientLayout_ClassID, src.optimizationFlags())
-        , fGradientMatrix(src.fGradientMatrix)
-        , fCoordTransform0(src.fCoordTransform0) {
-    this->addCoordTransform(&fCoordTransform0);
-}
-std::unique_ptr<GrFragmentProcessor> GrRadialGradientLayout::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrRadialGradientLayout(*this));
-}
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRadialGradientLayout);
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrRadialGradientLayout::TestCreate(GrProcessorTestData* d) {
-    SkScalar scale = GrGradientShader::RandomParams::kGradientScale;
-    sk_sp<SkShader> shader;
-    do {
-        GrGradientShader::RandomParams params(d->fRandom);
-        SkPoint center = {d->fRandom->nextRangeScalar(0.0f, scale),
-                          d->fRandom->nextRangeScalar(0.0f, scale)};
-        SkScalar radius = d->fRandom->nextRangeScalar(0.0f, scale);
-        shader = params.fUseColors4f
-                         ? SkGradientShader::MakeRadial(center, radius, params.fColors4f,
-                                                        params.fColorSpace, params.fStops,
-                                                        params.fColorCount, params.fTileMode)
-                         : SkGradientShader::MakeRadial(center, radius, params.fColors,
-                                                        params.fStops, params.fColorCount,
-                                                        params.fTileMode);
-    } while (!shader);
-    GrTest::TestAsFPArgs asFPArgs(d);
-    std::unique_ptr<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args());
-    GrAlwaysAssert(fp);
-    return fp;
-}
-#endif
-
-std::unique_ptr<GrFragmentProcessor> GrRadialGradientLayout::Make(const SkRadialGradient& grad,
-                                                                  const GrFPArgs& args) {
-    SkMatrix matrix;
-    if (!grad.totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) {
-        return nullptr;
-    }
-    matrix.postConcat(grad.getGradientMatrix());
-    return std::unique_ptr<GrFragmentProcessor>(new GrRadialGradientLayout(matrix));
-}
diff --git a/src/gpu/gradients/GrRadialGradientLayout.fp b/src/gpu/gradients/GrRadialGradientLayout.fp
index 6d186e5..fd47295 100644
--- a/src/gpu/gradients/GrRadialGradientLayout.fp
+++ b/src/gpu/gradients/GrRadialGradientLayout.fp
@@ -20,7 +20,7 @@
 
 @header {
     #include "SkRadialGradient.h"
-    #include "GrGradientShader.h"
+    #include "../GrGradientShader.h"
 }
 
 // The radial gradient never rejects a pixel so it doesn't change opacity
diff --git a/src/gpu/gradients/GrRadialGradientLayout.h b/src/gpu/gradients/GrRadialGradientLayout.h
deleted file mode 100644
index aeff4d5..0000000
--- a/src/gpu/gradients/GrRadialGradientLayout.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrRadialGradientLayout.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrRadialGradientLayout_DEFINED
-#define GrRadialGradientLayout_DEFINED
-#include "SkTypes.h"
-
-#include "SkRadialGradient.h"
-#include "GrGradientShader.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrRadialGradientLayout : public GrFragmentProcessor {
-public:
-    const SkMatrix44& gradientMatrix() const { return fGradientMatrix; }
-
-    static std::unique_ptr<GrFragmentProcessor> Make(const SkRadialGradient& gradient,
-                                                     const GrFPArgs& args);
-    GrRadialGradientLayout(const GrRadialGradientLayout& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "RadialGradientLayout"; }
-
-private:
-    GrRadialGradientLayout(SkMatrix44 gradientMatrix)
-            : INHERITED(kGrRadialGradientLayout_ClassID,
-                        (OptimizationFlags)kPreservesOpaqueInput_OptimizationFlag)
-            , fGradientMatrix(gradientMatrix)
-            , fCoordTransform0(gradientMatrix) {
-        this->addCoordTransform(&fCoordTransform0);
-    }
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    SkMatrix44 fGradientMatrix;
-    GrCoordTransform fCoordTransform0;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/gradients/GrSingleIntervalGradientColorizer.cpp b/src/gpu/gradients/GrSingleIntervalGradientColorizer.cpp
deleted file mode 100644
index f5de696..0000000
--- a/src/gpu/gradients/GrSingleIntervalGradientColorizer.cpp
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrSingleIntervalGradientColorizer.fp; do not modify.
- **************************************************************************************************/
-#include "GrSingleIntervalGradientColorizer.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLSingleIntervalGradientColorizer : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLSingleIntervalGradientColorizer() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrSingleIntervalGradientColorizer& _outer =
-                args.fFp.cast<GrSingleIntervalGradientColorizer>();
-        (void)_outer;
-        auto start = _outer.start();
-        (void)start;
-        auto end = _outer.end();
-        (void)end;
-        fStartVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType,
-                                                     kDefault_GrSLPrecision, "start");
-        fEndVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType,
-                                                   kDefault_GrSLPrecision, "end");
-        fragBuilder->codeAppendf("half t = %s.x;\n%s = (1.0 - t) * %s + t * %s;\n",
-                                 args.fInputColor, args.fOutputColor,
-                                 args.fUniformHandler->getUniformCStr(fStartVar),
-                                 args.fUniformHandler->getUniformCStr(fEndVar));
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {
-        const GrSingleIntervalGradientColorizer& _outer =
-                _proc.cast<GrSingleIntervalGradientColorizer>();
-        {
-            const SkPMColor4f& startValue = _outer.start();
-            if (fStartPrev != startValue) {
-                fStartPrev = startValue;
-                pdman.set4fv(fStartVar, 1, startValue.vec());
-            }
-            const SkPMColor4f& endValue = _outer.end();
-            if (fEndPrev != endValue) {
-                fEndPrev = endValue;
-                pdman.set4fv(fEndVar, 1, endValue.vec());
-            }
-        }
-    }
-    SkPMColor4f fStartPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN};
-    SkPMColor4f fEndPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN};
-    UniformHandle fStartVar;
-    UniformHandle fEndVar;
-};
-GrGLSLFragmentProcessor* GrSingleIntervalGradientColorizer::onCreateGLSLInstance() const {
-    return new GrGLSLSingleIntervalGradientColorizer();
-}
-void GrSingleIntervalGradientColorizer::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                                              GrProcessorKeyBuilder* b) const {}
-bool GrSingleIntervalGradientColorizer::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrSingleIntervalGradientColorizer& that = other.cast<GrSingleIntervalGradientColorizer>();
-    (void)that;
-    if (fStart != that.fStart) return false;
-    if (fEnd != that.fEnd) return false;
-    return true;
-}
-GrSingleIntervalGradientColorizer::GrSingleIntervalGradientColorizer(
-        const GrSingleIntervalGradientColorizer& src)
-        : INHERITED(kGrSingleIntervalGradientColorizer_ClassID, src.optimizationFlags())
-        , fStart(src.fStart)
-        , fEnd(src.fEnd) {}
-std::unique_ptr<GrFragmentProcessor> GrSingleIntervalGradientColorizer::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrSingleIntervalGradientColorizer(*this));
-}
diff --git a/src/gpu/gradients/GrSingleIntervalGradientColorizer.h b/src/gpu/gradients/GrSingleIntervalGradientColorizer.h
deleted file mode 100644
index 1487c17..0000000
--- a/src/gpu/gradients/GrSingleIntervalGradientColorizer.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrSingleIntervalGradientColorizer.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrSingleIntervalGradientColorizer_DEFINED
-#define GrSingleIntervalGradientColorizer_DEFINED
-#include "SkTypes.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrSingleIntervalGradientColorizer : public GrFragmentProcessor {
-public:
-    const SkPMColor4f& start() const { return fStart; }
-    const SkPMColor4f& end() const { return fEnd; }
-    static std::unique_ptr<GrFragmentProcessor> Make(SkPMColor4f start, SkPMColor4f end) {
-        return std::unique_ptr<GrFragmentProcessor>(
-                new GrSingleIntervalGradientColorizer(start, end));
-    }
-    GrSingleIntervalGradientColorizer(const GrSingleIntervalGradientColorizer& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "SingleIntervalGradientColorizer"; }
-
-private:
-    GrSingleIntervalGradientColorizer(SkPMColor4f start, SkPMColor4f end)
-            : INHERITED(kGrSingleIntervalGradientColorizer_ClassID, kNone_OptimizationFlags)
-            , fStart(start)
-            , fEnd(end) {}
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    SkPMColor4f fStart;
-    SkPMColor4f fEnd;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/gradients/GrSweepGradientLayout.cpp b/src/gpu/gradients/GrSweepGradientLayout.cpp
deleted file mode 100644
index 2269e25..0000000
--- a/src/gpu/gradients/GrSweepGradientLayout.cpp
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrSweepGradientLayout.fp; do not modify.
- **************************************************************************************************/
-#include "GrSweepGradientLayout.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLSweepGradientLayout : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLSweepGradientLayout() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrSweepGradientLayout& _outer = args.fFp.cast<GrSweepGradientLayout>();
-        (void)_outer;
-        auto gradientMatrix = _outer.gradientMatrix();
-        (void)gradientMatrix;
-        auto bias = _outer.bias();
-        (void)bias;
-        auto scale = _outer.scale();
-        (void)scale;
-        fBiasVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType,
-                                                    kDefault_GrSLPrecision, "bias");
-        fScaleVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType,
-                                                     kDefault_GrSLPrecision, "scale");
-        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
-        fragBuilder->codeAppendf(
-                "half angle;\nif (sk_Caps.atan2ImplementedAsAtanYOverX) {\n    angle = half(2.0 * "
-                "atan(-%s.y, length(%s) - %s.x));\n} else {\n    angle = half(atan(-%s.y, "
-                "-%s.x));\n}\nhalf t = ((angle * 0.15915494309180001 + 0.5) + %s) * %s;\n%s = "
-                "half4(t, 1.0, 0.0, 0.0);\n",
-                sk_TransformedCoords2D_0.c_str(), sk_TransformedCoords2D_0.c_str(),
-                sk_TransformedCoords2D_0.c_str(), sk_TransformedCoords2D_0.c_str(),
-                sk_TransformedCoords2D_0.c_str(), args.fUniformHandler->getUniformCStr(fBiasVar),
-                args.fUniformHandler->getUniformCStr(fScaleVar), args.fOutputColor);
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {
-        const GrSweepGradientLayout& _outer = _proc.cast<GrSweepGradientLayout>();
-        {
-            float biasValue = _outer.bias();
-            if (fBiasPrev != biasValue) {
-                fBiasPrev = biasValue;
-                pdman.set1f(fBiasVar, biasValue);
-            }
-            float scaleValue = _outer.scale();
-            if (fScalePrev != scaleValue) {
-                fScalePrev = scaleValue;
-                pdman.set1f(fScaleVar, scaleValue);
-            }
-        }
-    }
-    float fBiasPrev = SK_FloatNaN;
-    float fScalePrev = SK_FloatNaN;
-    UniformHandle fBiasVar;
-    UniformHandle fScaleVar;
-};
-GrGLSLFragmentProcessor* GrSweepGradientLayout::onCreateGLSLInstance() const {
-    return new GrGLSLSweepGradientLayout();
-}
-void GrSweepGradientLayout::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                                  GrProcessorKeyBuilder* b) const {}
-bool GrSweepGradientLayout::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrSweepGradientLayout& that = other.cast<GrSweepGradientLayout>();
-    (void)that;
-    if (fGradientMatrix != that.fGradientMatrix) return false;
-    if (fBias != that.fBias) return false;
-    if (fScale != that.fScale) return false;
-    return true;
-}
-GrSweepGradientLayout::GrSweepGradientLayout(const GrSweepGradientLayout& src)
-        : INHERITED(kGrSweepGradientLayout_ClassID, src.optimizationFlags())
-        , fGradientMatrix(src.fGradientMatrix)
-        , fBias(src.fBias)
-        , fScale(src.fScale)
-        , fCoordTransform0(src.fCoordTransform0) {
-    this->addCoordTransform(&fCoordTransform0);
-}
-std::unique_ptr<GrFragmentProcessor> GrSweepGradientLayout::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrSweepGradientLayout(*this));
-}
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrSweepGradientLayout);
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrSweepGradientLayout::TestCreate(GrProcessorTestData* d) {
-    SkScalar scale = GrGradientShader::RandomParams::kGradientScale;
-    SkPoint center = {d->fRandom->nextRangeScalar(0.0f, scale),
-                      d->fRandom->nextRangeScalar(0.0f, scale)};
-
-    GrGradientShader::RandomParams params(d->fRandom);
-    auto shader = params.fUseColors4f
-                          ? SkGradientShader::MakeSweep(center.fX, center.fY, params.fColors4f,
-                                                        params.fColorSpace, params.fStops,
-                                                        params.fColorCount)
-                          : SkGradientShader::MakeSweep(center.fX, center.fY, params.fColors,
-                                                        params.fStops, params.fColorCount);
-    GrTest::TestAsFPArgs asFPArgs(d);
-    std::unique_ptr<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args());
-    GrAlwaysAssert(fp);
-    return fp;
-}
-#endif
-
-std::unique_ptr<GrFragmentProcessor> GrSweepGradientLayout::Make(const SkSweepGradient& grad,
-                                                                 const GrFPArgs& args) {
-    SkMatrix matrix;
-    if (!grad.totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) {
-        return nullptr;
-    }
-    matrix.postConcat(grad.getGradientMatrix());
-    return std::unique_ptr<GrFragmentProcessor>(
-            new GrSweepGradientLayout(matrix, grad.getTBias(), grad.getTScale()));
-}
diff --git a/src/gpu/gradients/GrSweepGradientLayout.fp b/src/gpu/gradients/GrSweepGradientLayout.fp
index 4f98612..0c29194 100644
--- a/src/gpu/gradients/GrSweepGradientLayout.fp
+++ b/src/gpu/gradients/GrSweepGradientLayout.fp
@@ -37,7 +37,7 @@
 
 @header {
     #include "SkSweepGradient.h"
-    #include "GrGradientShader.h"
+    #include "../GrGradientShader.h"
 }
 
 // The sweep gradient never rejects a pixel so it doesn't change opacity
diff --git a/src/gpu/gradients/GrSweepGradientLayout.h b/src/gpu/gradients/GrSweepGradientLayout.h
deleted file mode 100644
index c0ceef3..0000000
--- a/src/gpu/gradients/GrSweepGradientLayout.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrSweepGradientLayout.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrSweepGradientLayout_DEFINED
-#define GrSweepGradientLayout_DEFINED
-#include "SkTypes.h"
-
-#include "SkSweepGradient.h"
-#include "GrGradientShader.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrSweepGradientLayout : public GrFragmentProcessor {
-public:
-    const SkMatrix44& gradientMatrix() const { return fGradientMatrix; }
-    float bias() const { return fBias; }
-    float scale() const { return fScale; }
-
-    static std::unique_ptr<GrFragmentProcessor> Make(const SkSweepGradient& gradient,
-                                                     const GrFPArgs& args);
-    GrSweepGradientLayout(const GrSweepGradientLayout& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "SweepGradientLayout"; }
-
-private:
-    GrSweepGradientLayout(SkMatrix44 gradientMatrix, float bias, float scale)
-            : INHERITED(kGrSweepGradientLayout_ClassID,
-                        (OptimizationFlags)kPreservesOpaqueInput_OptimizationFlag)
-            , fGradientMatrix(gradientMatrix)
-            , fBias(bias)
-            , fScale(scale)
-            , fCoordTransform0(gradientMatrix) {
-        this->addCoordTransform(&fCoordTransform0);
-    }
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    SkMatrix44 fGradientMatrix;
-    float fBias;
-    float fScale;
-    GrCoordTransform fCoordTransform0;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/gradients/GrTextureGradientColorizer.cpp b/src/gpu/gradients/GrTextureGradientColorizer.cpp
deleted file mode 100644
index f7fdfa4..0000000
--- a/src/gpu/gradients/GrTextureGradientColorizer.cpp
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrTextureGradientColorizer.fp; do not modify.
- **************************************************************************************************/
-#include "GrTextureGradientColorizer.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLTextureGradientColorizer : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLTextureGradientColorizer() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrTextureGradientColorizer& _outer = args.fFp.cast<GrTextureGradientColorizer>();
-        (void)_outer;
-        fragBuilder->codeAppendf(
-                "half2 coord = half2(%s.x, 0.5);\n%s = texture(%s, float2(coord)).%s;\n",
-                args.fInputColor, args.fOutputColor,
-                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(),
-                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str());
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {}
-};
-GrGLSLFragmentProcessor* GrTextureGradientColorizer::onCreateGLSLInstance() const {
-    return new GrGLSLTextureGradientColorizer();
-}
-void GrTextureGradientColorizer::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                                       GrProcessorKeyBuilder* b) const {}
-bool GrTextureGradientColorizer::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrTextureGradientColorizer& that = other.cast<GrTextureGradientColorizer>();
-    (void)that;
-    if (fGradient != that.fGradient) return false;
-    return true;
-}
-GrTextureGradientColorizer::GrTextureGradientColorizer(const GrTextureGradientColorizer& src)
-        : INHERITED(kGrTextureGradientColorizer_ClassID, src.optimizationFlags())
-        , fGradient(src.fGradient) {
-    this->setTextureSamplerCnt(1);
-}
-std::unique_ptr<GrFragmentProcessor> GrTextureGradientColorizer::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrTextureGradientColorizer(*this));
-}
-const GrFragmentProcessor::TextureSampler& GrTextureGradientColorizer::onTextureSampler(
-        int index) const {
-    return IthTextureSampler(index, fGradient);
-}
diff --git a/src/gpu/gradients/GrTextureGradientColorizer.h b/src/gpu/gradients/GrTextureGradientColorizer.h
deleted file mode 100644
index 9f39247..0000000
--- a/src/gpu/gradients/GrTextureGradientColorizer.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrTextureGradientColorizer.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrTextureGradientColorizer_DEFINED
-#define GrTextureGradientColorizer_DEFINED
-#include "SkTypes.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrTextureGradientColorizer : public GrFragmentProcessor {
-public:
-    static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy> gradient) {
-        return std::unique_ptr<GrFragmentProcessor>(new GrTextureGradientColorizer(gradient));
-    }
-    GrTextureGradientColorizer(const GrTextureGradientColorizer& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "TextureGradientColorizer"; }
-
-private:
-    GrTextureGradientColorizer(sk_sp<GrTextureProxy> gradient)
-            : INHERITED(kGrTextureGradientColorizer_ClassID, kNone_OptimizationFlags)
-            , fGradient(std::move(gradient), GrSamplerState::ClampBilerp()) {
-        this->setTextureSamplerCnt(1);
-    }
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    const TextureSampler& onTextureSampler(int) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    TextureSampler fGradient;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/gradients/GrTiledGradientEffect.cpp b/src/gpu/gradients/GrTiledGradientEffect.cpp
deleted file mode 100644
index 0657cad..0000000
--- a/src/gpu/gradients/GrTiledGradientEffect.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrTiledGradientEffect.fp; do not modify.
- **************************************************************************************************/
-#include "GrTiledGradientEffect.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLTiledGradientEffect : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLTiledGradientEffect() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrTiledGradientEffect& _outer = args.fFp.cast<GrTiledGradientEffect>();
-        (void)_outer;
-        auto mirror = _outer.mirror();
-        (void)mirror;
-        auto makePremul = _outer.makePremul();
-        (void)makePremul;
-        auto colorsAreOpaque = _outer.colorsAreOpaque();
-        (void)colorsAreOpaque;
-        SkString _child1("_child1");
-        this->emitChild(1, &_child1, args);
-        fragBuilder->codeAppendf(
-                "half4 t = %s;\nif (!%s && t.y < 0.0) {\n    %s = half4(0.0);\n} else {\n    @if "
-                "(%s) {\n        half t_1 = t.x - 1.0;\n        half tiled_t = (t_1 - 2.0 * "
-                "floor(t_1 * 0.5)) - 1.0;\n        if (sk_Caps.mustDoOpBetweenFloorAndAbs) {\n     "
-                "       tiled_t = clamp(tiled_t, -1.0, 1.0);\n        }\n        t.x = "
-                "abs(tiled_t);\n    } else {\n        t.x = fract(t.x);\n    }",
-                _child1.c_str(),
-                (_outer.childProcessor(1).preservesOpaqueInput() ? "true" : "false"),
-                args.fOutputColor, (_outer.mirror() ? "true" : "false"));
-        SkString _input0("t");
-        SkString _child0("_child0");
-        this->emitChild(0, _input0.c_str(), &_child0, args);
-        fragBuilder->codeAppendf("\n    %s = %s;\n}\n@if (%s) {\n    %s.xyz *= %s.w;\n}\n",
-                                 args.fOutputColor, _child0.c_str(),
-                                 (_outer.makePremul() ? "true" : "false"), args.fOutputColor,
-                                 args.fOutputColor);
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {}
-};
-GrGLSLFragmentProcessor* GrTiledGradientEffect::onCreateGLSLInstance() const {
-    return new GrGLSLTiledGradientEffect();
-}
-void GrTiledGradientEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                                  GrProcessorKeyBuilder* b) const {
-    b->add32((int32_t)fMirror);
-    b->add32((int32_t)fMakePremul);
-}
-bool GrTiledGradientEffect::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrTiledGradientEffect& that = other.cast<GrTiledGradientEffect>();
-    (void)that;
-    if (fMirror != that.fMirror) return false;
-    if (fMakePremul != that.fMakePremul) return false;
-    if (fColorsAreOpaque != that.fColorsAreOpaque) return false;
-    return true;
-}
-GrTiledGradientEffect::GrTiledGradientEffect(const GrTiledGradientEffect& src)
-        : INHERITED(kGrTiledGradientEffect_ClassID, src.optimizationFlags())
-        , fMirror(src.fMirror)
-        , fMakePremul(src.fMakePremul)
-        , fColorsAreOpaque(src.fColorsAreOpaque) {
-    this->registerChildProcessor(src.childProcessor(0).clone());
-    this->registerChildProcessor(src.childProcessor(1).clone());
-}
-std::unique_ptr<GrFragmentProcessor> GrTiledGradientEffect::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrTiledGradientEffect(*this));
-}
diff --git a/src/gpu/gradients/GrTiledGradientEffect.h b/src/gpu/gradients/GrTiledGradientEffect.h
deleted file mode 100644
index ffaca8e..0000000
--- a/src/gpu/gradients/GrTiledGradientEffect.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrTiledGradientEffect.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrTiledGradientEffect_DEFINED
-#define GrTiledGradientEffect_DEFINED
-#include "SkTypes.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrTiledGradientEffect : public GrFragmentProcessor {
-public:
-    bool mirror() const { return fMirror; }
-    bool makePremul() const { return fMakePremul; }
-    bool colorsAreOpaque() const { return fColorsAreOpaque; }
-    static std::unique_ptr<GrFragmentProcessor> Make(
-            std::unique_ptr<GrFragmentProcessor> colorizer,
-            std::unique_ptr<GrFragmentProcessor> gradLayout, bool mirror, bool makePremul,
-            bool colorsAreOpaque) {
-        return std::unique_ptr<GrFragmentProcessor>(new GrTiledGradientEffect(
-                std::move(colorizer), std::move(gradLayout), mirror, makePremul, colorsAreOpaque));
-    }
-    GrTiledGradientEffect(const GrTiledGradientEffect& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "TiledGradientEffect"; }
-
-private:
-    GrTiledGradientEffect(std::unique_ptr<GrFragmentProcessor> colorizer,
-                          std::unique_ptr<GrFragmentProcessor> gradLayout, bool mirror,
-                          bool makePremul, bool colorsAreOpaque)
-            : INHERITED(kGrTiledGradientEffect_ClassID,
-                        (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag |
-                                (colorsAreOpaque && gradLayout->preservesOpaqueInput()
-                                         ? kPreservesOpaqueInput_OptimizationFlag
-                                         : kNone_OptimizationFlags))
-            , fMirror(mirror)
-            , fMakePremul(makePremul)
-            , fColorsAreOpaque(colorsAreOpaque) {
-        this->registerChildProcessor(std::move(colorizer));
-        this->registerChildProcessor(std::move(gradLayout));
-    }
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    bool fMirror;
-    bool fMakePremul;
-    bool fColorsAreOpaque;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/gradients/GrTwoPointConicalGradientLayout.cpp b/src/gpu/gradients/GrTwoPointConicalGradientLayout.cpp
deleted file mode 100644
index f268e16..0000000
--- a/src/gpu/gradients/GrTwoPointConicalGradientLayout.cpp
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrTwoPointConicalGradientLayout.fp; do not modify.
- **************************************************************************************************/
-#include "GrTwoPointConicalGradientLayout.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLTwoPointConicalGradientLayout : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLTwoPointConicalGradientLayout() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrTwoPointConicalGradientLayout& _outer =
-                args.fFp.cast<GrTwoPointConicalGradientLayout>();
-        (void)_outer;
-        auto gradientMatrix = _outer.gradientMatrix();
-        (void)gradientMatrix;
-        auto type = _outer.type();
-        (void)type;
-        auto isRadiusIncreasing = _outer.isRadiusIncreasing();
-        (void)isRadiusIncreasing;
-        auto isFocalOnCircle = _outer.isFocalOnCircle();
-        (void)isFocalOnCircle;
-        auto isWellBehaved = _outer.isWellBehaved();
-        (void)isWellBehaved;
-        auto isSwapped = _outer.isSwapped();
-        (void)isSwapped;
-        auto isNativelyFocal = _outer.isNativelyFocal();
-        (void)isNativelyFocal;
-        auto focalParams = _outer.focalParams();
-        (void)focalParams;
-        fFocalParamsVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType,
-                                                           kDefault_GrSLPrecision, "focalParams");
-        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
-        fragBuilder->codeAppendf(
-                "float2 p = %s;\nfloat t = -1.0;\nhalf v = 1.0;\n@switch (%d) {\n    case 1:\n     "
-                "   {\n            half r0_2 = %s.y;\n            t = float(r0_2) - p.y * p.y;\n   "
-                "         if (t >= 0.0) {\n                t = p.x + sqrt(t);\n            } else "
-                "{\n                v = -1.0;\n            }\n        }\n        break;\n    case "
-                "0:\n        {\n            half r0 = %s.x;\n            @if (%s) {\n              "
-                "  t = length(p) - float(r0);\n            } else {\n                t = "
-                "-length(p) - float(r0);\n       ",
-                sk_TransformedCoords2D_0.c_str(), (int)_outer.type(),
-                args.fUniformHandler->getUniformCStr(fFocalParamsVar),
-                args.fUniformHandler->getUniformCStr(fFocalParamsVar),
-                (_outer.isRadiusIncreasing() ? "true" : "false"));
-        fragBuilder->codeAppendf(
-                "     }\n        }\n        break;\n    case 2:\n        {\n            half invR1 "
-                "= %s.x;\n            half fx = %s.y;\n            float x_t = -1.0;\n            "
-                "@if (%s) {\n                x_t = dot(p, p) / p.x;\n            } else if (%s) "
-                "{\n                x_t = length(p) - p.x * float(invR1);\n            } else {\n  "
-                "              float temp = p.x * p.x - p.y * p.y;\n                if (temp >= "
-                "0.0) {\n                    @if (%s || !%s) {\n                        x_t = "
-                "-sqrt(temp) - p.x * float(invR1)",
-                args.fUniformHandler->getUniformCStr(fFocalParamsVar),
-                args.fUniformHandler->getUniformCStr(fFocalParamsVar),
-                (_outer.isFocalOnCircle() ? "true" : "false"),
-                (_outer.isWellBehaved() ? "true" : "false"),
-                (_outer.isSwapped() ? "true" : "false"),
-                (_outer.isRadiusIncreasing() ? "true" : "false"));
-        fragBuilder->codeAppendf(
-                ";\n                    } else {\n                        x_t = sqrt(temp) - p.x * "
-                "float(invR1);\n                    }\n                }\n            }\n          "
-                "  @if (!%s) {\n                if (x_t <= 0.0) {\n                    v = -1.0;\n "
-                "               }\n            }\n            @if (%s) {\n                @if (%s) "
-                "{\n                    t = x_t;\n                } else {\n                    t "
-                "= x_t + float(fx);\n                }\n            } else {\n                @if "
-                "(%s) {\n              ",
-                (_outer.isWellBehaved() ? "true" : "false"),
-                (_outer.isRadiusIncreasing() ? "true" : "false"),
-                (_outer.isNativelyFocal() ? "true" : "false"),
-                (_outer.isNativelyFocal() ? "true" : "false"));
-        fragBuilder->codeAppendf(
-                "      t = -x_t;\n                } else {\n                    t = -x_t + "
-                "float(fx);\n                }\n            }\n            @if (%s) {\n            "
-                "    t = 1.0 - t;\n            }\n        }\n        break;\n}\n%s = "
-                "half4(half(t), v, 0.0, 0.0);\n",
-                (_outer.isSwapped() ? "true" : "false"), args.fOutputColor);
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {
-        const GrTwoPointConicalGradientLayout& _outer =
-                _proc.cast<GrTwoPointConicalGradientLayout>();
-        {
-            const SkPoint& focalParamsValue = _outer.focalParams();
-            if (fFocalParamsPrev != focalParamsValue) {
-                fFocalParamsPrev = focalParamsValue;
-                pdman.set2f(fFocalParamsVar, focalParamsValue.fX, focalParamsValue.fY);
-            }
-        }
-    }
-    SkPoint fFocalParamsPrev = SkPoint::Make(SK_FloatNaN, SK_FloatNaN);
-    UniformHandle fFocalParamsVar;
-};
-GrGLSLFragmentProcessor* GrTwoPointConicalGradientLayout::onCreateGLSLInstance() const {
-    return new GrGLSLTwoPointConicalGradientLayout();
-}
-void GrTwoPointConicalGradientLayout::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                                            GrProcessorKeyBuilder* b) const {
-    b->add32((int32_t)fType);
-    b->add32((int32_t)fIsRadiusIncreasing);
-    b->add32((int32_t)fIsFocalOnCircle);
-    b->add32((int32_t)fIsWellBehaved);
-    b->add32((int32_t)fIsSwapped);
-    b->add32((int32_t)fIsNativelyFocal);
-}
-bool GrTwoPointConicalGradientLayout::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrTwoPointConicalGradientLayout& that = other.cast<GrTwoPointConicalGradientLayout>();
-    (void)that;
-    if (fGradientMatrix != that.fGradientMatrix) return false;
-    if (fType != that.fType) return false;
-    if (fIsRadiusIncreasing != that.fIsRadiusIncreasing) return false;
-    if (fIsFocalOnCircle != that.fIsFocalOnCircle) return false;
-    if (fIsWellBehaved != that.fIsWellBehaved) return false;
-    if (fIsSwapped != that.fIsSwapped) return false;
-    if (fIsNativelyFocal != that.fIsNativelyFocal) return false;
-    if (fFocalParams != that.fFocalParams) return false;
-    return true;
-}
-GrTwoPointConicalGradientLayout::GrTwoPointConicalGradientLayout(
-        const GrTwoPointConicalGradientLayout& src)
-        : INHERITED(kGrTwoPointConicalGradientLayout_ClassID, src.optimizationFlags())
-        , fGradientMatrix(src.fGradientMatrix)
-        , fType(src.fType)
-        , fIsRadiusIncreasing(src.fIsRadiusIncreasing)
-        , fIsFocalOnCircle(src.fIsFocalOnCircle)
-        , fIsWellBehaved(src.fIsWellBehaved)
-        , fIsSwapped(src.fIsSwapped)
-        , fIsNativelyFocal(src.fIsNativelyFocal)
-        , fFocalParams(src.fFocalParams)
-        , fCoordTransform0(src.fCoordTransform0) {
-    this->addCoordTransform(&fCoordTransform0);
-}
-std::unique_ptr<GrFragmentProcessor> GrTwoPointConicalGradientLayout::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrTwoPointConicalGradientLayout(*this));
-}
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrTwoPointConicalGradientLayout);
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrTwoPointConicalGradientLayout::TestCreate(
-        GrProcessorTestData* d) {
-    SkScalar scale = GrGradientShader::RandomParams::kGradientScale;
-    SkScalar offset = scale / 32.0f;
-
-    SkPoint center1 = {d->fRandom->nextRangeScalar(0.0f, scale),
-                       d->fRandom->nextRangeScalar(0.0f, scale)};
-    SkPoint center2 = {d->fRandom->nextRangeScalar(0.0f, scale),
-                       d->fRandom->nextRangeScalar(0.0f, scale)};
-    SkScalar radius1 = d->fRandom->nextRangeScalar(0.0f, scale);
-    SkScalar radius2 = d->fRandom->nextRangeScalar(0.0f, scale);
-
-    constexpr int kTestTypeMask = (1 << 2) - 1, kTestNativelyFocalBit = (1 << 2),
-                  kTestFocalOnCircleBit = (1 << 3), kTestSwappedBit = (1 << 4);
-    // We won't treat isWellDefined and isRadiusIncreasing specially because they
-    // should have high probability to be turned on and off as we're getting random
-    // radii and centers.
-
-    int mask = d->fRandom->nextU();
-    int type = mask & kTestTypeMask;
-    if (type == static_cast<int>(Type::kRadial)) {
-        center2 = center1;
-        // Make sure that the radii are different
-        if (SkScalarNearlyZero(radius1 - radius2)) {
-            radius2 += offset;
-        }
-    } else if (type == static_cast<int>(Type::kStrip)) {
-        radius1 = SkTMax(radius1, .1f);  // Make sure that the radius is non-zero
-        radius2 = radius1;
-        // Make sure that the centers are different
-        if (SkScalarNearlyZero(SkPoint::Distance(center1, center2))) {
-            center2.fX += offset;
-        }
-    } else {  // kFocal_Type
-        // Make sure that the centers are different
-        if (SkScalarNearlyZero(SkPoint::Distance(center1, center2))) {
-            center2.fX += offset;
-        }
-
-        if (kTestNativelyFocalBit & mask) {
-            radius1 = 0;
-        }
-        if (kTestFocalOnCircleBit & mask) {
-            radius2 = radius1 + SkPoint::Distance(center1, center2);
-        }
-        if (kTestSwappedBit & mask) {
-            std::swap(radius1, radius2);
-            radius2 = 0;
-        }
-
-        // Make sure that the radii are different
-        if (SkScalarNearlyZero(radius1 - radius2)) {
-            radius2 += offset;
-        }
-    }
-
-    if (SkScalarNearlyZero(radius1 - radius2) &&
-        SkScalarNearlyZero(SkPoint::Distance(center1, center2))) {
-        radius2 += offset;  // make sure that we're not degenerated
-    }
-
-    GrGradientShader::RandomParams params(d->fRandom);
-    auto shader = params.fUseColors4f
-                          ? SkGradientShader::MakeTwoPointConical(
-                                    center1, radius1, center2, radius2, params.fColors4f,
-                                    params.fColorSpace, params.fStops, params.fColorCount,
-                                    params.fTileMode)
-                          : SkGradientShader::MakeTwoPointConical(
-                                    center1, radius1, center2, radius2, params.fColors,
-                                    params.fStops, params.fColorCount, params.fTileMode);
-    GrTest::TestAsFPArgs asFPArgs(d);
-    std::unique_ptr<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args());
-
-    GrAlwaysAssert(fp);
-    return fp;
-}
-#endif
-
-// .fp files do not let you reference outside enum definitions, so we have to explicitly map
-// between the two compatible enum defs
-GrTwoPointConicalGradientLayout::Type convert_type(SkTwoPointConicalGradient::Type type) {
-    switch (type) {
-        case SkTwoPointConicalGradient::Type::kRadial:
-            return GrTwoPointConicalGradientLayout::Type::kRadial;
-        case SkTwoPointConicalGradient::Type::kStrip:
-            return GrTwoPointConicalGradientLayout::Type::kStrip;
-        case SkTwoPointConicalGradient::Type::kFocal:
-            return GrTwoPointConicalGradientLayout::Type::kFocal;
-    }
-    SkDEBUGFAIL("Should not be reachable");
-    return GrTwoPointConicalGradientLayout::Type::kRadial;
-}
-
-std::unique_ptr<GrFragmentProcessor> GrTwoPointConicalGradientLayout::Make(
-        const SkTwoPointConicalGradient& grad, const GrFPArgs& args) {
-    GrTwoPointConicalGradientLayout::Type grType = convert_type(grad.getType());
-
-    // The focalData struct is only valid if isFocal is true
-    const SkTwoPointConicalGradient::FocalData& focalData = grad.getFocalData();
-    bool isFocal = grType == Type::kFocal;
-
-    // Calculate optimization switches from gradient specification
-    bool isFocalOnCircle = isFocal && focalData.isFocalOnCircle();
-    bool isWellBehaved = isFocal && focalData.isWellBehaved();
-    bool isSwapped = isFocal && focalData.isSwapped();
-    bool isNativelyFocal = isFocal && focalData.isNativelyFocal();
-
-    // Type-specific calculations: isRadiusIncreasing, focalParams, and the gradient matrix.
-    // However, all types start with the total inverse local matrix calculated from the shader
-    // and args
-    bool isRadiusIncreasing;
-    SkPoint focalParams;  // really just a 2D tuple
-    SkMatrix matrix;
-
-    // Initialize the base matrix
-    if (!grad.totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) {
-        return nullptr;
-    }
-
-    if (isFocal) {
-        isRadiusIncreasing = (1 - focalData.fFocalX) > 0;
-
-        focalParams.set(1.0 / focalData.fR1, focalData.fFocalX);
-
-        matrix.postConcat(grad.getGradientMatrix());
-    } else if (grType == Type::kRadial) {
-        SkScalar dr = grad.getDiffRadius();
-        isRadiusIncreasing = dr >= 0;
-
-        SkScalar r0 = grad.getStartRadius() / dr;
-        focalParams.set(r0, r0 * r0);
-
-        // GPU radial matrix is different from the original matrix, since we map the diff radius
-        // to have |dr| = 1, so manually compute the final gradient matrix here.
-
-        // Map center to (0, 0)
-        matrix.postTranslate(-grad.getStartCenter().fX, -grad.getStartCenter().fY);
-
-        // scale |diffRadius| to 1
-        matrix.postScale(1 / dr, 1 / dr);
-    } else {                         // kStrip
-        isRadiusIncreasing = false;  // kStrip doesn't use this flag
-
-        SkScalar r0 = grad.getStartRadius() / grad.getCenterX1();
-        focalParams.set(r0, r0 * r0);
-
-        matrix.postConcat(grad.getGradientMatrix());
-    }
-
-    return std::unique_ptr<GrFragmentProcessor>(new GrTwoPointConicalGradientLayout(
-            matrix, grType, isRadiusIncreasing, isFocalOnCircle, isWellBehaved, isSwapped,
-            isNativelyFocal, focalParams));
-}
diff --git a/src/gpu/gradients/GrTwoPointConicalGradientLayout.fp b/src/gpu/gradients/GrTwoPointConicalGradientLayout.fp
index 1c6ff67..cb8fb10 100644
--- a/src/gpu/gradients/GrTwoPointConicalGradientLayout.fp
+++ b/src/gpu/gradients/GrTwoPointConicalGradientLayout.fp
@@ -126,7 +126,7 @@
 
 @header {
     #include "SkTwoPointConicalGradient.h"
-    #include "GrGradientShader.h"
+    #include "../GrGradientShader.h"
 }
 
 // The 2 point conical gradient can reject a pixel so it does change opacity
diff --git a/src/gpu/gradients/GrTwoPointConicalGradientLayout.h b/src/gpu/gradients/GrTwoPointConicalGradientLayout.h
deleted file mode 100644
index ab833d4..0000000
--- a/src/gpu/gradients/GrTwoPointConicalGradientLayout.h
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrTwoPointConicalGradientLayout.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrTwoPointConicalGradientLayout_DEFINED
-#define GrTwoPointConicalGradientLayout_DEFINED
-#include "SkTypes.h"
-
-#include "SkTwoPointConicalGradient.h"
-#include "GrGradientShader.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrTwoPointConicalGradientLayout : public GrFragmentProcessor {
-public:
-    enum class Type { kFocal = 2, kRadial = 0, kStrip = 1 };
-    const SkMatrix44& gradientMatrix() const { return fGradientMatrix; }
-    const Type& type() const { return fType; }
-    bool isRadiusIncreasing() const { return fIsRadiusIncreasing; }
-    bool isFocalOnCircle() const { return fIsFocalOnCircle; }
-    bool isWellBehaved() const { return fIsWellBehaved; }
-    bool isSwapped() const { return fIsSwapped; }
-    bool isNativelyFocal() const { return fIsNativelyFocal; }
-    const SkPoint& focalParams() const { return fFocalParams; }
-
-    static std::unique_ptr<GrFragmentProcessor> Make(const SkTwoPointConicalGradient& gradient,
-                                                     const GrFPArgs& args);
-    GrTwoPointConicalGradientLayout(const GrTwoPointConicalGradientLayout& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "TwoPointConicalGradientLayout"; }
-
-private:
-    GrTwoPointConicalGradientLayout(SkMatrix44 gradientMatrix, Type type, bool isRadiusIncreasing,
-                                    bool isFocalOnCircle, bool isWellBehaved, bool isSwapped,
-                                    bool isNativelyFocal, SkPoint focalParams)
-            : INHERITED(kGrTwoPointConicalGradientLayout_ClassID,
-                        (OptimizationFlags)kNone_OptimizationFlags)
-            , fGradientMatrix(gradientMatrix)
-            , fType(type)
-            , fIsRadiusIncreasing(isRadiusIncreasing)
-            , fIsFocalOnCircle(isFocalOnCircle)
-            , fIsWellBehaved(isWellBehaved)
-            , fIsSwapped(isSwapped)
-            , fIsNativelyFocal(isNativelyFocal)
-            , fFocalParams(focalParams)
-            , fCoordTransform0(gradientMatrix) {
-        this->addCoordTransform(&fCoordTransform0);
-    }
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    SkMatrix44 fGradientMatrix;
-    Type fType;
-    bool fIsRadiusIncreasing;
-    bool fIsFocalOnCircle;
-    bool fIsWellBehaved;
-    bool fIsSwapped;
-    bool fIsNativelyFocal;
-    SkPoint fFocalParams;
-    GrCoordTransform fCoordTransform0;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/gradients/GrUnrolledBinaryGradientColorizer.cpp b/src/gpu/gradients/GrUnrolledBinaryGradientColorizer.cpp
deleted file mode 100644
index 3082c6a..0000000
--- a/src/gpu/gradients/GrUnrolledBinaryGradientColorizer.cpp
+++ /dev/null
@@ -1,380 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrUnrolledBinaryGradientColorizer.fp; do not modify.
- **************************************************************************************************/
-#include "GrUnrolledBinaryGradientColorizer.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLUnrolledBinaryGradientColorizer : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLUnrolledBinaryGradientColorizer() {}
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrUnrolledBinaryGradientColorizer& _outer =
-                args.fFp.cast<GrUnrolledBinaryGradientColorizer>();
-        (void)_outer;
-        auto intervalCount = _outer.intervalCount();
-        (void)intervalCount;
-        auto scale0_1 = _outer.scale0_1();
-        (void)scale0_1;
-        auto scale2_3 = _outer.scale2_3();
-        (void)scale2_3;
-        auto scale4_5 = _outer.scale4_5();
-        (void)scale4_5;
-        auto scale6_7 = _outer.scale6_7();
-        (void)scale6_7;
-        auto scale8_9 = _outer.scale8_9();
-        (void)scale8_9;
-        auto scale10_11 = _outer.scale10_11();
-        (void)scale10_11;
-        auto scale12_13 = _outer.scale12_13();
-        (void)scale12_13;
-        auto scale14_15 = _outer.scale14_15();
-        (void)scale14_15;
-        auto bias0_1 = _outer.bias0_1();
-        (void)bias0_1;
-        auto bias2_3 = _outer.bias2_3();
-        (void)bias2_3;
-        auto bias4_5 = _outer.bias4_5();
-        (void)bias4_5;
-        auto bias6_7 = _outer.bias6_7();
-        (void)bias6_7;
-        auto bias8_9 = _outer.bias8_9();
-        (void)bias8_9;
-        auto bias10_11 = _outer.bias10_11();
-        (void)bias10_11;
-        auto bias12_13 = _outer.bias12_13();
-        (void)bias12_13;
-        auto bias14_15 = _outer.bias14_15();
-        (void)bias14_15;
-        auto thresholds1_7 = _outer.thresholds1_7();
-        (void)thresholds1_7;
-        auto thresholds9_13 = _outer.thresholds9_13();
-        (void)thresholds9_13;
-        fScale0_1Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
-                                                        kDefault_GrSLPrecision, "scale0_1");
-        if (intervalCount > 1) {
-            fScale2_3Var = args.fUniformHandler->addUniform(
-                    kFragment_GrShaderFlag, kFloat4_GrSLType, kDefault_GrSLPrecision, "scale2_3");
-        }
-        if (intervalCount > 2) {
-            fScale4_5Var = args.fUniformHandler->addUniform(
-                    kFragment_GrShaderFlag, kFloat4_GrSLType, kDefault_GrSLPrecision, "scale4_5");
-        }
-        if (intervalCount > 3) {
-            fScale6_7Var = args.fUniformHandler->addUniform(
-                    kFragment_GrShaderFlag, kFloat4_GrSLType, kDefault_GrSLPrecision, "scale6_7");
-        }
-        if (intervalCount > 4) {
-            fScale8_9Var = args.fUniformHandler->addUniform(
-                    kFragment_GrShaderFlag, kFloat4_GrSLType, kDefault_GrSLPrecision, "scale8_9");
-        }
-        if (intervalCount > 5) {
-            fScale10_11Var = args.fUniformHandler->addUniform(
-                    kFragment_GrShaderFlag, kFloat4_GrSLType, kDefault_GrSLPrecision, "scale10_11");
-        }
-        if (intervalCount > 6) {
-            fScale12_13Var = args.fUniformHandler->addUniform(
-                    kFragment_GrShaderFlag, kFloat4_GrSLType, kDefault_GrSLPrecision, "scale12_13");
-        }
-        if (intervalCount > 7) {
-            fScale14_15Var = args.fUniformHandler->addUniform(
-                    kFragment_GrShaderFlag, kFloat4_GrSLType, kDefault_GrSLPrecision, "scale14_15");
-        }
-        fBias0_1Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
-                                                       kDefault_GrSLPrecision, "bias0_1");
-        if (intervalCount > 1) {
-            fBias2_3Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
-                                                           kDefault_GrSLPrecision, "bias2_3");
-        }
-        if (intervalCount > 2) {
-            fBias4_5Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
-                                                           kDefault_GrSLPrecision, "bias4_5");
-        }
-        if (intervalCount > 3) {
-            fBias6_7Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
-                                                           kDefault_GrSLPrecision, "bias6_7");
-        }
-        if (intervalCount > 4) {
-            fBias8_9Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
-                                                           kDefault_GrSLPrecision, "bias8_9");
-        }
-        if (intervalCount > 5) {
-            fBias10_11Var = args.fUniformHandler->addUniform(
-                    kFragment_GrShaderFlag, kFloat4_GrSLType, kDefault_GrSLPrecision, "bias10_11");
-        }
-        if (intervalCount > 6) {
-            fBias12_13Var = args.fUniformHandler->addUniform(
-                    kFragment_GrShaderFlag, kFloat4_GrSLType, kDefault_GrSLPrecision, "bias12_13");
-        }
-        if (intervalCount > 7) {
-            fBias14_15Var = args.fUniformHandler->addUniform(
-                    kFragment_GrShaderFlag, kFloat4_GrSLType, kDefault_GrSLPrecision, "bias14_15");
-        }
-        fThresholds1_7Var = args.fUniformHandler->addUniform(
-                kFragment_GrShaderFlag, kHalf4_GrSLType, kDefault_GrSLPrecision, "thresholds1_7");
-        fThresholds9_13Var = args.fUniformHandler->addUniform(
-                kFragment_GrShaderFlag, kHalf4_GrSLType, kDefault_GrSLPrecision, "thresholds9_13");
-        fragBuilder->codeAppendf(
-                "half t = %s.x;\nfloat4 scale, bias;\nif (%d <= 4 || t < %s.w) {\n    if (%d <= 2 "
-                "|| t < %s.y) {\n        if (%d <= 1 || t < %s.x) {\n            scale = %s;\n     "
-                "       bias = %s;\n        } else {\n            scale = %s;\n            bias = "
-                "%s;\n        }\n    } else {\n        if (%d <= 3 || t < %s.z) {\n            "
-                "scale = %s;\n            bias = %s;\n        } else {\n            scale = %s;\n  "
-                "          bias = %s;\n        }\n    }\n} else {\n    if (%d <= 6 || t < %s.y) "
-                "{\n        if (%d <= 5 || t <",
-                args.fInputColor, _outer.intervalCount(),
-                args.fUniformHandler->getUniformCStr(fThresholds1_7Var), _outer.intervalCount(),
-                args.fUniformHandler->getUniformCStr(fThresholds1_7Var), _outer.intervalCount(),
-                args.fUniformHandler->getUniformCStr(fThresholds1_7Var),
-                args.fUniformHandler->getUniformCStr(fScale0_1Var),
-                args.fUniformHandler->getUniformCStr(fBias0_1Var),
-                fScale2_3Var.isValid() ? args.fUniformHandler->getUniformCStr(fScale2_3Var)
-                                       : "float4(0)",
-                fBias2_3Var.isValid() ? args.fUniformHandler->getUniformCStr(fBias2_3Var)
-                                      : "float4(0)",
-                _outer.intervalCount(), args.fUniformHandler->getUniformCStr(fThresholds1_7Var),
-                fScale4_5Var.isValid() ? args.fUniformHandler->getUniformCStr(fScale4_5Var)
-                                       : "float4(0)",
-                fBias4_5Var.isValid() ? args.fUniformHandler->getUniformCStr(fBias4_5Var)
-                                      : "float4(0)",
-                fScale6_7Var.isValid() ? args.fUniformHandler->getUniformCStr(fScale6_7Var)
-                                       : "float4(0)",
-                fBias6_7Var.isValid() ? args.fUniformHandler->getUniformCStr(fBias6_7Var)
-                                      : "float4(0)",
-                _outer.intervalCount(), args.fUniformHandler->getUniformCStr(fThresholds9_13Var),
-                _outer.intervalCount());
-        fragBuilder->codeAppendf(
-                " %s.x) {\n            scale = %s;\n            bias = %s;\n        } else {\n     "
-                "       scale = %s;\n            bias = %s;\n        }\n    } else {\n        if "
-                "(%d <= 7 || t < %s.z) {\n            scale = %s;\n            bias = %s;\n        "
-                "} else {\n            scale = %s;\n            bias = %s;\n        }\n    "
-                "}\n}\n%s = half4(float(t) * scale + bias);\n",
-                args.fUniformHandler->getUniformCStr(fThresholds9_13Var),
-                fScale8_9Var.isValid() ? args.fUniformHandler->getUniformCStr(fScale8_9Var)
-                                       : "float4(0)",
-                fBias8_9Var.isValid() ? args.fUniformHandler->getUniformCStr(fBias8_9Var)
-                                      : "float4(0)",
-                fScale10_11Var.isValid() ? args.fUniformHandler->getUniformCStr(fScale10_11Var)
-                                         : "float4(0)",
-                fBias10_11Var.isValid() ? args.fUniformHandler->getUniformCStr(fBias10_11Var)
-                                        : "float4(0)",
-                _outer.intervalCount(), args.fUniformHandler->getUniformCStr(fThresholds9_13Var),
-                fScale12_13Var.isValid() ? args.fUniformHandler->getUniformCStr(fScale12_13Var)
-                                         : "float4(0)",
-                fBias12_13Var.isValid() ? args.fUniformHandler->getUniformCStr(fBias12_13Var)
-                                        : "float4(0)",
-                fScale14_15Var.isValid() ? args.fUniformHandler->getUniformCStr(fScale14_15Var)
-                                         : "float4(0)",
-                fBias14_15Var.isValid() ? args.fUniformHandler->getUniformCStr(fBias14_15Var)
-                                        : "float4(0)",
-                args.fOutputColor);
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {
-        const GrUnrolledBinaryGradientColorizer& _outer =
-                _proc.cast<GrUnrolledBinaryGradientColorizer>();
-        {
-            pdman.set4fv(fScale0_1Var, 1, (_outer.scale0_1()).vec());
-            if (fScale2_3Var.isValid()) {
-                pdman.set4fv(fScale2_3Var, 1, (_outer.scale2_3()).vec());
-            }
-            if (fScale4_5Var.isValid()) {
-                pdman.set4fv(fScale4_5Var, 1, (_outer.scale4_5()).vec());
-            }
-            if (fScale6_7Var.isValid()) {
-                pdman.set4fv(fScale6_7Var, 1, (_outer.scale6_7()).vec());
-            }
-            if (fScale8_9Var.isValid()) {
-                pdman.set4fv(fScale8_9Var, 1, (_outer.scale8_9()).vec());
-            }
-            if (fScale10_11Var.isValid()) {
-                pdman.set4fv(fScale10_11Var, 1, (_outer.scale10_11()).vec());
-            }
-            if (fScale12_13Var.isValid()) {
-                pdman.set4fv(fScale12_13Var, 1, (_outer.scale12_13()).vec());
-            }
-            if (fScale14_15Var.isValid()) {
-                pdman.set4fv(fScale14_15Var, 1, (_outer.scale14_15()).vec());
-            }
-            pdman.set4fv(fBias0_1Var, 1, (_outer.bias0_1()).vec());
-            if (fBias2_3Var.isValid()) {
-                pdman.set4fv(fBias2_3Var, 1, (_outer.bias2_3()).vec());
-            }
-            if (fBias4_5Var.isValid()) {
-                pdman.set4fv(fBias4_5Var, 1, (_outer.bias4_5()).vec());
-            }
-            if (fBias6_7Var.isValid()) {
-                pdman.set4fv(fBias6_7Var, 1, (_outer.bias6_7()).vec());
-            }
-            if (fBias8_9Var.isValid()) {
-                pdman.set4fv(fBias8_9Var, 1, (_outer.bias8_9()).vec());
-            }
-            if (fBias10_11Var.isValid()) {
-                pdman.set4fv(fBias10_11Var, 1, (_outer.bias10_11()).vec());
-            }
-            if (fBias12_13Var.isValid()) {
-                pdman.set4fv(fBias12_13Var, 1, (_outer.bias12_13()).vec());
-            }
-            if (fBias14_15Var.isValid()) {
-                pdman.set4fv(fBias14_15Var, 1, (_outer.bias14_15()).vec());
-            }
-            pdman.set4fv(fThresholds1_7Var, 1,
-                         reinterpret_cast<const float*>(&(_outer.thresholds1_7())));
-            pdman.set4fv(fThresholds9_13Var, 1,
-                         reinterpret_cast<const float*>(&(_outer.thresholds9_13())));
-        }
-    }
-    UniformHandle fScale0_1Var;
-    UniformHandle fScale2_3Var;
-    UniformHandle fScale4_5Var;
-    UniformHandle fScale6_7Var;
-    UniformHandle fScale8_9Var;
-    UniformHandle fScale10_11Var;
-    UniformHandle fScale12_13Var;
-    UniformHandle fScale14_15Var;
-    UniformHandle fBias0_1Var;
-    UniformHandle fBias2_3Var;
-    UniformHandle fBias4_5Var;
-    UniformHandle fBias6_7Var;
-    UniformHandle fBias8_9Var;
-    UniformHandle fBias10_11Var;
-    UniformHandle fBias12_13Var;
-    UniformHandle fBias14_15Var;
-    UniformHandle fThresholds1_7Var;
-    UniformHandle fThresholds9_13Var;
-};
-GrGLSLFragmentProcessor* GrUnrolledBinaryGradientColorizer::onCreateGLSLInstance() const {
-    return new GrGLSLUnrolledBinaryGradientColorizer();
-}
-void GrUnrolledBinaryGradientColorizer::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                                              GrProcessorKeyBuilder* b) const {
-    b->add32((int32_t)fIntervalCount);
-}
-bool GrUnrolledBinaryGradientColorizer::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrUnrolledBinaryGradientColorizer& that = other.cast<GrUnrolledBinaryGradientColorizer>();
-    (void)that;
-    if (fIntervalCount != that.fIntervalCount) return false;
-    if (fScale0_1 != that.fScale0_1) return false;
-    if (fScale2_3 != that.fScale2_3) return false;
-    if (fScale4_5 != that.fScale4_5) return false;
-    if (fScale6_7 != that.fScale6_7) return false;
-    if (fScale8_9 != that.fScale8_9) return false;
-    if (fScale10_11 != that.fScale10_11) return false;
-    if (fScale12_13 != that.fScale12_13) return false;
-    if (fScale14_15 != that.fScale14_15) return false;
-    if (fBias0_1 != that.fBias0_1) return false;
-    if (fBias2_3 != that.fBias2_3) return false;
-    if (fBias4_5 != that.fBias4_5) return false;
-    if (fBias6_7 != that.fBias6_7) return false;
-    if (fBias8_9 != that.fBias8_9) return false;
-    if (fBias10_11 != that.fBias10_11) return false;
-    if (fBias12_13 != that.fBias12_13) return false;
-    if (fBias14_15 != that.fBias14_15) return false;
-    if (fThresholds1_7 != that.fThresholds1_7) return false;
-    if (fThresholds9_13 != that.fThresholds9_13) return false;
-    return true;
-}
-GrUnrolledBinaryGradientColorizer::GrUnrolledBinaryGradientColorizer(
-        const GrUnrolledBinaryGradientColorizer& src)
-        : INHERITED(kGrUnrolledBinaryGradientColorizer_ClassID, src.optimizationFlags())
-        , fIntervalCount(src.fIntervalCount)
-        , fScale0_1(src.fScale0_1)
-        , fScale2_3(src.fScale2_3)
-        , fScale4_5(src.fScale4_5)
-        , fScale6_7(src.fScale6_7)
-        , fScale8_9(src.fScale8_9)
-        , fScale10_11(src.fScale10_11)
-        , fScale12_13(src.fScale12_13)
-        , fScale14_15(src.fScale14_15)
-        , fBias0_1(src.fBias0_1)
-        , fBias2_3(src.fBias2_3)
-        , fBias4_5(src.fBias4_5)
-        , fBias6_7(src.fBias6_7)
-        , fBias8_9(src.fBias8_9)
-        , fBias10_11(src.fBias10_11)
-        , fBias12_13(src.fBias12_13)
-        , fBias14_15(src.fBias14_15)
-        , fThresholds1_7(src.fThresholds1_7)
-        , fThresholds9_13(src.fThresholds9_13) {}
-std::unique_ptr<GrFragmentProcessor> GrUnrolledBinaryGradientColorizer::clone() const {
-    return std::unique_ptr<GrFragmentProcessor>(new GrUnrolledBinaryGradientColorizer(*this));
-}
-
-static const int kMaxIntervals = 8;
-std::unique_ptr<GrFragmentProcessor> GrUnrolledBinaryGradientColorizer::Make(
-        const SkPMColor4f* colors, const SkScalar* positions, int count) {
-    // Depending on how the positions resolve into hard stops or regular stops, the number of
-    // intervals specified by the number of colors/positions can change. For instance, a plain
-    // 3 color gradient is two intervals, but a 4 color gradient with a hard stop is also
-    // two intervals. At the most extreme end, an 8 interval gradient made entirely of hard
-    // stops has 16 colors.
-
-    if (count > kMaxColorCount) {
-        // Definitely cannot represent this gradient configuration
-        return nullptr;
-    }
-
-    // The raster implementation also uses scales and biases, but since they must be calculated
-    // after the dst color space is applied, it limits our ability to cache their values.
-    SkPMColor4f scales[kMaxIntervals];
-    SkPMColor4f biases[kMaxIntervals];
-    SkScalar thresholds[kMaxIntervals];
-
-    int intervalCount = 0;
-
-    for (int i = 0; i < count - 1; i++) {
-        if (intervalCount >= kMaxIntervals) {
-            // Already reached kMaxIntervals, and haven't run out of color stops so this
-            // gradient cannot be represented by this shader.
-            return nullptr;
-        }
-
-        SkScalar t0 = positions[i];
-        SkScalar t1 = positions[i + 1];
-        SkScalar dt = t1 - t0;
-        // If the interval is empty, skip to the next interval. This will automatically create
-        // distinct hard stop intervals as needed. It also protects against malformed gradients
-        // that have repeated hard stops at the very beginning that are effectively unreachable.
-        if (SkScalarNearlyZero(dt)) {
-            continue;
-        }
-
-        auto c0 = Sk4f::Load(colors[i].vec());
-        auto c1 = Sk4f::Load(colors[i + 1].vec());
-
-        auto scale = (c1 - c0) / dt;
-        auto bias = c0 - t0 * scale;
-
-        scale.store(scales + intervalCount);
-        bias.store(biases + intervalCount);
-        thresholds[intervalCount] = t1;
-        intervalCount++;
-    }
-
-    // For isEqual to make sense, set the unused values to something consistent
-    for (int i = intervalCount; i < kMaxIntervals; i++) {
-        scales[i] = SK_PMColor4fTRANSPARENT;
-        biases[i] = SK_PMColor4fTRANSPARENT;
-        thresholds[i] = 0.0;
-    }
-
-    return std::unique_ptr<GrFragmentProcessor>(new GrUnrolledBinaryGradientColorizer(
-            intervalCount, scales[0], scales[1], scales[2], scales[3], scales[4], scales[5],
-            scales[6], scales[7], biases[0], biases[1], biases[2], biases[3], biases[4], biases[5],
-            biases[6], biases[7],
-            SkRect::MakeLTRB(thresholds[0], thresholds[1], thresholds[2], thresholds[3]),
-            SkRect::MakeLTRB(thresholds[4], thresholds[5], thresholds[6], 0.0)));
-}
diff --git a/src/gpu/gradients/GrUnrolledBinaryGradientColorizer.h b/src/gpu/gradients/GrUnrolledBinaryGradientColorizer.h
deleted file mode 100644
index 50aa605..0000000
--- a/src/gpu/gradients/GrUnrolledBinaryGradientColorizer.h
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**************************************************************************************************
- *** This file was autogenerated from GrUnrolledBinaryGradientColorizer.fp; do not modify.
- **************************************************************************************************/
-#ifndef GrUnrolledBinaryGradientColorizer_DEFINED
-#define GrUnrolledBinaryGradientColorizer_DEFINED
-#include "SkTypes.h"
-#include "GrFragmentProcessor.h"
-#include "GrCoordTransform.h"
-class GrUnrolledBinaryGradientColorizer : public GrFragmentProcessor {
-public:
-    static const int kMaxColorCount = 16;
-    int32_t intervalCount() const { return fIntervalCount; }
-    const SkPMColor4f& scale0_1() const { return fScale0_1; }
-    const SkPMColor4f& scale2_3() const { return fScale2_3; }
-    const SkPMColor4f& scale4_5() const { return fScale4_5; }
-    const SkPMColor4f& scale6_7() const { return fScale6_7; }
-    const SkPMColor4f& scale8_9() const { return fScale8_9; }
-    const SkPMColor4f& scale10_11() const { return fScale10_11; }
-    const SkPMColor4f& scale12_13() const { return fScale12_13; }
-    const SkPMColor4f& scale14_15() const { return fScale14_15; }
-    const SkPMColor4f& bias0_1() const { return fBias0_1; }
-    const SkPMColor4f& bias2_3() const { return fBias2_3; }
-    const SkPMColor4f& bias4_5() const { return fBias4_5; }
-    const SkPMColor4f& bias6_7() const { return fBias6_7; }
-    const SkPMColor4f& bias8_9() const { return fBias8_9; }
-    const SkPMColor4f& bias10_11() const { return fBias10_11; }
-    const SkPMColor4f& bias12_13() const { return fBias12_13; }
-    const SkPMColor4f& bias14_15() const { return fBias14_15; }
-    const SkRect& thresholds1_7() const { return fThresholds1_7; }
-    const SkRect& thresholds9_13() const { return fThresholds9_13; }
-
-    static std::unique_ptr<GrFragmentProcessor> Make(const SkPMColor4f* colors,
-                                                     const SkScalar* positions,
-                                                     int count);
-    GrUnrolledBinaryGradientColorizer(const GrUnrolledBinaryGradientColorizer& src);
-    std::unique_ptr<GrFragmentProcessor> clone() const override;
-    const char* name() const override { return "UnrolledBinaryGradientColorizer"; }
-
-private:
-    GrUnrolledBinaryGradientColorizer(int32_t intervalCount,
-                                      SkPMColor4f scale0_1,
-                                      SkPMColor4f scale2_3,
-                                      SkPMColor4f scale4_5,
-                                      SkPMColor4f scale6_7,
-                                      SkPMColor4f scale8_9,
-                                      SkPMColor4f scale10_11,
-                                      SkPMColor4f scale12_13,
-                                      SkPMColor4f scale14_15,
-                                      SkPMColor4f bias0_1,
-                                      SkPMColor4f bias2_3,
-                                      SkPMColor4f bias4_5,
-                                      SkPMColor4f bias6_7,
-                                      SkPMColor4f bias8_9,
-                                      SkPMColor4f bias10_11,
-                                      SkPMColor4f bias12_13,
-                                      SkPMColor4f bias14_15,
-                                      SkRect thresholds1_7,
-                                      SkRect thresholds9_13)
-            : INHERITED(kGrUnrolledBinaryGradientColorizer_ClassID, kNone_OptimizationFlags)
-            , fIntervalCount(intervalCount)
-            , fScale0_1(scale0_1)
-            , fScale2_3(scale2_3)
-            , fScale4_5(scale4_5)
-            , fScale6_7(scale6_7)
-            , fScale8_9(scale8_9)
-            , fScale10_11(scale10_11)
-            , fScale12_13(scale12_13)
-            , fScale14_15(scale14_15)
-            , fBias0_1(bias0_1)
-            , fBias2_3(bias2_3)
-            , fBias4_5(bias4_5)
-            , fBias6_7(bias6_7)
-            , fBias8_9(bias8_9)
-            , fBias10_11(bias10_11)
-            , fBias12_13(bias12_13)
-            , fBias14_15(bias14_15)
-            , fThresholds1_7(thresholds1_7)
-            , fThresholds9_13(thresholds9_13) {}
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    int32_t fIntervalCount;
-    SkPMColor4f fScale0_1;
-    SkPMColor4f fScale2_3;
-    SkPMColor4f fScale4_5;
-    SkPMColor4f fScale6_7;
-    SkPMColor4f fScale8_9;
-    SkPMColor4f fScale10_11;
-    SkPMColor4f fScale12_13;
-    SkPMColor4f fScale14_15;
-    SkPMColor4f fBias0_1;
-    SkPMColor4f fBias2_3;
-    SkPMColor4f fBias4_5;
-    SkPMColor4f fBias6_7;
-    SkPMColor4f fBias8_9;
-    SkPMColor4f fBias10_11;
-    SkPMColor4f fBias12_13;
-    SkPMColor4f fBias14_15;
-    SkRect fThresholds1_7;
-    SkRect fThresholds9_13;
-    typedef GrFragmentProcessor INHERITED;
-};
-#endif
diff --git a/src/gpu/gradients/generated/GrClampedGradientEffect.cpp b/src/gpu/gradients/generated/GrClampedGradientEffect.cpp
new file mode 100644
index 0000000..c0718ec
--- /dev/null
+++ b/src/gpu/gradients/generated/GrClampedGradientEffect.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrClampedGradientEffect.fp; do not modify.
+ **************************************************************************************************/
+#include "GrClampedGradientEffect.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLClampedGradientEffect : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLClampedGradientEffect() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrClampedGradientEffect& _outer = args.fFp.cast<GrClampedGradientEffect>();
+        (void)_outer;
+        auto leftBorderColor = _outer.leftBorderColor;
+        (void)leftBorderColor;
+        auto rightBorderColor = _outer.rightBorderColor;
+        (void)rightBorderColor;
+        auto makePremul = _outer.makePremul;
+        (void)makePremul;
+        auto colorsAreOpaque = _outer.colorsAreOpaque;
+        (void)colorsAreOpaque;
+        leftBorderColorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
+                                                              kHalf4_GrSLType, "leftBorderColor");
+        rightBorderColorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
+                                                               kHalf4_GrSLType, "rightBorderColor");
+        SkString _child1("_child1");
+        this->emitChild(_outer.gradLayout_index, &_child1, args);
+        fragBuilder->codeAppendf(
+                "half4 t = %s;\nif (!%s && t.y < 0.0) {\n    %s = half4(0.0);\n} else if (t.x < "
+                "0.0) {\n    %s = %s;\n} else if (t.x > 1.0) {\n    %s = %s;\n} else {",
+                _child1.c_str(),
+                (_outer.childProcessor(_outer.gradLayout_index).preservesOpaqueInput() ? "true"
+                                                                                       : "false"),
+                args.fOutputColor, args.fOutputColor,
+                args.fUniformHandler->getUniformCStr(leftBorderColorVar), args.fOutputColor,
+                args.fUniformHandler->getUniformCStr(rightBorderColorVar));
+        SkString _input0("t");
+        SkString _child0("_child0");
+        this->emitChild(_outer.colorizer_index, _input0.c_str(), &_child0, args);
+        fragBuilder->codeAppendf("\n    %s = %s;\n}\n@if (%s) {\n    %s.xyz *= %s.w;\n}\n",
+                                 args.fOutputColor, _child0.c_str(),
+                                 (_outer.makePremul ? "true" : "false"), args.fOutputColor,
+                                 args.fOutputColor);
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {
+        const GrClampedGradientEffect& _outer = _proc.cast<GrClampedGradientEffect>();
+        {
+            const SkPMColor4f& leftBorderColorValue = _outer.leftBorderColor;
+            if (leftBorderColorPrev != leftBorderColorValue) {
+                leftBorderColorPrev = leftBorderColorValue;
+                pdman.set4fv(leftBorderColorVar, 1, leftBorderColorValue.vec());
+            }
+            const SkPMColor4f& rightBorderColorValue = _outer.rightBorderColor;
+            if (rightBorderColorPrev != rightBorderColorValue) {
+                rightBorderColorPrev = rightBorderColorValue;
+                pdman.set4fv(rightBorderColorVar, 1, rightBorderColorValue.vec());
+            }
+        }
+    }
+    SkPMColor4f leftBorderColorPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN};
+    SkPMColor4f rightBorderColorPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN};
+    UniformHandle leftBorderColorVar;
+    UniformHandle rightBorderColorVar;
+};
+GrGLSLFragmentProcessor* GrClampedGradientEffect::onCreateGLSLInstance() const {
+    return new GrGLSLClampedGradientEffect();
+}
+void GrClampedGradientEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                    GrProcessorKeyBuilder* b) const {
+    b->add32((int32_t)makePremul);
+}
+bool GrClampedGradientEffect::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrClampedGradientEffect& that = other.cast<GrClampedGradientEffect>();
+    (void)that;
+    if (leftBorderColor != that.leftBorderColor) return false;
+    if (rightBorderColor != that.rightBorderColor) return false;
+    if (makePremul != that.makePremul) return false;
+    if (colorsAreOpaque != that.colorsAreOpaque) return false;
+    return true;
+}
+GrClampedGradientEffect::GrClampedGradientEffect(const GrClampedGradientEffect& src)
+        : INHERITED(kGrClampedGradientEffect_ClassID, src.optimizationFlags())
+        , colorizer_index(src.colorizer_index)
+        , gradLayout_index(src.gradLayout_index)
+        , leftBorderColor(src.leftBorderColor)
+        , rightBorderColor(src.rightBorderColor)
+        , makePremul(src.makePremul)
+        , colorsAreOpaque(src.colorsAreOpaque) {
+    this->registerChildProcessor(src.childProcessor(colorizer_index).clone());
+    this->registerChildProcessor(src.childProcessor(gradLayout_index).clone());
+}
+std::unique_ptr<GrFragmentProcessor> GrClampedGradientEffect::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrClampedGradientEffect(*this));
+}
diff --git a/src/gpu/gradients/generated/GrClampedGradientEffect.h b/src/gpu/gradients/generated/GrClampedGradientEffect.h
new file mode 100644
index 0000000..43aba19
--- /dev/null
+++ b/src/gpu/gradients/generated/GrClampedGradientEffect.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrClampedGradientEffect.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrClampedGradientEffect_DEFINED
+#define GrClampedGradientEffect_DEFINED
+#include "SkTypes.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrClampedGradientEffect : public GrFragmentProcessor {
+public:
+    static std::unique_ptr<GrFragmentProcessor> Make(
+            std::unique_ptr<GrFragmentProcessor> colorizer,
+            std::unique_ptr<GrFragmentProcessor> gradLayout, SkPMColor4f leftBorderColor,
+            SkPMColor4f rightBorderColor, bool makePremul, bool colorsAreOpaque) {
+        return std::unique_ptr<GrFragmentProcessor>(new GrClampedGradientEffect(
+                std::move(colorizer), std::move(gradLayout), leftBorderColor, rightBorderColor,
+                makePremul, colorsAreOpaque));
+    }
+    GrClampedGradientEffect(const GrClampedGradientEffect& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "ClampedGradientEffect"; }
+    int colorizer_index = -1;
+    int gradLayout_index = -1;
+    SkPMColor4f leftBorderColor;
+    SkPMColor4f rightBorderColor;
+    bool makePremul;
+    bool colorsAreOpaque;
+
+private:
+    GrClampedGradientEffect(std::unique_ptr<GrFragmentProcessor> colorizer,
+                            std::unique_ptr<GrFragmentProcessor> gradLayout,
+                            SkPMColor4f leftBorderColor, SkPMColor4f rightBorderColor,
+                            bool makePremul, bool colorsAreOpaque)
+            : INHERITED(kGrClampedGradientEffect_ClassID,
+                        (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag |
+                                (colorsAreOpaque && gradLayout->preservesOpaqueInput()
+                                         ? kPreservesOpaqueInput_OptimizationFlag
+                                         : kNone_OptimizationFlags))
+            , leftBorderColor(leftBorderColor)
+            , rightBorderColor(rightBorderColor)
+            , makePremul(makePremul)
+            , colorsAreOpaque(colorsAreOpaque) {
+        SkASSERT(colorizer);
+        colorizer_index = this->numChildProcessors();
+        this->registerChildProcessor(std::move(colorizer));
+        SkASSERT(gradLayout);
+        gradLayout_index = this->numChildProcessors();
+        this->registerChildProcessor(std::move(gradLayout));
+    }
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/gradients/generated/GrDualIntervalGradientColorizer.cpp b/src/gpu/gradients/generated/GrDualIntervalGradientColorizer.cpp
new file mode 100644
index 0000000..91b7aa0
--- /dev/null
+++ b/src/gpu/gradients/generated/GrDualIntervalGradientColorizer.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrDualIntervalGradientColorizer.fp; do not modify.
+ **************************************************************************************************/
+#include "GrDualIntervalGradientColorizer.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLDualIntervalGradientColorizer : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLDualIntervalGradientColorizer() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrDualIntervalGradientColorizer& _outer =
+                args.fFp.cast<GrDualIntervalGradientColorizer>();
+        (void)_outer;
+        auto scale01 = _outer.scale01;
+        (void)scale01;
+        auto bias01 = _outer.bias01;
+        (void)bias01;
+        auto scale23 = _outer.scale23;
+        (void)scale23;
+        auto bias23 = _outer.bias23;
+        (void)bias23;
+        auto threshold = _outer.threshold;
+        (void)threshold;
+        scale01Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
+                                                      "scale01");
+        bias01Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
+                                                     "bias01");
+        scale23Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
+                                                      "scale23");
+        bias23Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
+                                                     "bias23");
+        thresholdVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType,
+                                                        "threshold");
+        fragBuilder->codeAppendf(
+                "half t = %s.x;\nfloat4 scale, bias;\nif (t < %s) {\n    scale = %s;\n    bias = "
+                "%s;\n} else {\n    scale = %s;\n    bias = %s;\n}\n%s = half4(float(t) * scale + "
+                "bias);\n",
+                args.fInputColor, args.fUniformHandler->getUniformCStr(thresholdVar),
+                args.fUniformHandler->getUniformCStr(scale01Var),
+                args.fUniformHandler->getUniformCStr(bias01Var),
+                args.fUniformHandler->getUniformCStr(scale23Var),
+                args.fUniformHandler->getUniformCStr(bias23Var), args.fOutputColor);
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {
+        const GrDualIntervalGradientColorizer& _outer =
+                _proc.cast<GrDualIntervalGradientColorizer>();
+        {
+            const SkPMColor4f& scale01Value = _outer.scale01;
+            if (scale01Prev != scale01Value) {
+                scale01Prev = scale01Value;
+                pdman.set4fv(scale01Var, 1, scale01Value.vec());
+            }
+            const SkPMColor4f& bias01Value = _outer.bias01;
+            if (bias01Prev != bias01Value) {
+                bias01Prev = bias01Value;
+                pdman.set4fv(bias01Var, 1, bias01Value.vec());
+            }
+            const SkPMColor4f& scale23Value = _outer.scale23;
+            if (scale23Prev != scale23Value) {
+                scale23Prev = scale23Value;
+                pdman.set4fv(scale23Var, 1, scale23Value.vec());
+            }
+            const SkPMColor4f& bias23Value = _outer.bias23;
+            if (bias23Prev != bias23Value) {
+                bias23Prev = bias23Value;
+                pdman.set4fv(bias23Var, 1, bias23Value.vec());
+            }
+            float thresholdValue = _outer.threshold;
+            if (thresholdPrev != thresholdValue) {
+                thresholdPrev = thresholdValue;
+                pdman.set1f(thresholdVar, thresholdValue);
+            }
+        }
+    }
+    SkPMColor4f scale01Prev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN};
+    SkPMColor4f bias01Prev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN};
+    SkPMColor4f scale23Prev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN};
+    SkPMColor4f bias23Prev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN};
+    float thresholdPrev = SK_FloatNaN;
+    UniformHandle scale01Var;
+    UniformHandle bias01Var;
+    UniformHandle scale23Var;
+    UniformHandle bias23Var;
+    UniformHandle thresholdVar;
+};
+GrGLSLFragmentProcessor* GrDualIntervalGradientColorizer::onCreateGLSLInstance() const {
+    return new GrGLSLDualIntervalGradientColorizer();
+}
+void GrDualIntervalGradientColorizer::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                            GrProcessorKeyBuilder* b) const {}
+bool GrDualIntervalGradientColorizer::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrDualIntervalGradientColorizer& that = other.cast<GrDualIntervalGradientColorizer>();
+    (void)that;
+    if (scale01 != that.scale01) return false;
+    if (bias01 != that.bias01) return false;
+    if (scale23 != that.scale23) return false;
+    if (bias23 != that.bias23) return false;
+    if (threshold != that.threshold) return false;
+    return true;
+}
+GrDualIntervalGradientColorizer::GrDualIntervalGradientColorizer(
+        const GrDualIntervalGradientColorizer& src)
+        : INHERITED(kGrDualIntervalGradientColorizer_ClassID, src.optimizationFlags())
+        , scale01(src.scale01)
+        , bias01(src.bias01)
+        , scale23(src.scale23)
+        , bias23(src.bias23)
+        , threshold(src.threshold) {}
+std::unique_ptr<GrFragmentProcessor> GrDualIntervalGradientColorizer::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrDualIntervalGradientColorizer(*this));
+}
+
+std::unique_ptr<GrFragmentProcessor> GrDualIntervalGradientColorizer::Make(const SkPMColor4f& c0,
+                                                                           const SkPMColor4f& c1,
+                                                                           const SkPMColor4f& c2,
+                                                                           const SkPMColor4f& c3,
+                                                                           float threshold) {
+    // Derive scale and biases from the 4 colors and threshold
+    auto vc0 = Sk4f::Load(c0.vec());
+    auto vc1 = Sk4f::Load(c1.vec());
+    auto scale01 = (vc1 - vc0) / threshold;
+    // bias01 = c0
+
+    auto vc2 = Sk4f::Load(c2.vec());
+    auto vc3 = Sk4f::Load(c3.vec());
+    auto scale23 = (vc3 - vc2) / (1 - threshold);
+    auto bias23 = vc2 - threshold * scale23;
+
+    return std::unique_ptr<GrFragmentProcessor>(new GrDualIntervalGradientColorizer(
+            {scale01[0], scale01[1], scale01[2], scale01[3]}, c0,
+            {scale23[0], scale23[1], scale23[2], scale23[3]},
+            {bias23[0], bias23[1], bias23[2], bias23[3]}, threshold));
+}
diff --git a/src/gpu/gradients/generated/GrDualIntervalGradientColorizer.h b/src/gpu/gradients/generated/GrDualIntervalGradientColorizer.h
new file mode 100644
index 0000000..00a524b
--- /dev/null
+++ b/src/gpu/gradients/generated/GrDualIntervalGradientColorizer.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrDualIntervalGradientColorizer.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrDualIntervalGradientColorizer_DEFINED
+#define GrDualIntervalGradientColorizer_DEFINED
+#include "SkTypes.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrDualIntervalGradientColorizer : public GrFragmentProcessor {
+public:
+    static std::unique_ptr<GrFragmentProcessor> Make(const SkPMColor4f& c0, const SkPMColor4f& c1,
+                                                     const SkPMColor4f& c2, const SkPMColor4f& c3,
+                                                     float threshold);
+    GrDualIntervalGradientColorizer(const GrDualIntervalGradientColorizer& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "DualIntervalGradientColorizer"; }
+    SkPMColor4f scale01;
+    SkPMColor4f bias01;
+    SkPMColor4f scale23;
+    SkPMColor4f bias23;
+    float threshold;
+
+private:
+    GrDualIntervalGradientColorizer(SkPMColor4f scale01, SkPMColor4f bias01, SkPMColor4f scale23,
+                                    SkPMColor4f bias23, float threshold)
+            : INHERITED(kGrDualIntervalGradientColorizer_ClassID, kNone_OptimizationFlags)
+            , scale01(scale01)
+            , bias01(bias01)
+            , scale23(scale23)
+            , bias23(bias23)
+            , threshold(threshold) {}
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/gradients/generated/GrLinearGradientLayout.cpp b/src/gpu/gradients/generated/GrLinearGradientLayout.cpp
new file mode 100644
index 0000000..05dfb70
--- /dev/null
+++ b/src/gpu/gradients/generated/GrLinearGradientLayout.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrLinearGradientLayout.fp; do not modify.
+ **************************************************************************************************/
+#include "GrLinearGradientLayout.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLLinearGradientLayout : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLLinearGradientLayout() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrLinearGradientLayout& _outer = args.fFp.cast<GrLinearGradientLayout>();
+        (void)_outer;
+        auto gradientMatrix = _outer.gradientMatrix;
+        (void)gradientMatrix;
+        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+        fragBuilder->codeAppendf(
+                "half t = half(%s.x) + 1.0000000000000001e-05;\n%s = half4(t, 1.0, 0.0, 0.0);\n",
+                sk_TransformedCoords2D_0.c_str(), args.fOutputColor);
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {}
+};
+GrGLSLFragmentProcessor* GrLinearGradientLayout::onCreateGLSLInstance() const {
+    return new GrGLSLLinearGradientLayout();
+}
+void GrLinearGradientLayout::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                   GrProcessorKeyBuilder* b) const {}
+bool GrLinearGradientLayout::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrLinearGradientLayout& that = other.cast<GrLinearGradientLayout>();
+    (void)that;
+    if (gradientMatrix != that.gradientMatrix) return false;
+    return true;
+}
+GrLinearGradientLayout::GrLinearGradientLayout(const GrLinearGradientLayout& src)
+        : INHERITED(kGrLinearGradientLayout_ClassID, src.optimizationFlags())
+        , fCoordTransform0(src.fCoordTransform0)
+        , gradientMatrix(src.gradientMatrix) {
+    this->addCoordTransform(&fCoordTransform0);
+}
+std::unique_ptr<GrFragmentProcessor> GrLinearGradientLayout::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrLinearGradientLayout(*this));
+}
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrLinearGradientLayout);
+#if GR_TEST_UTILS
+std::unique_ptr<GrFragmentProcessor> GrLinearGradientLayout::TestCreate(GrProcessorTestData* d) {
+    SkScalar scale = GrGradientShader::RandomParams::kGradientScale;
+    SkPoint points[] = {
+            {d->fRandom->nextRangeScalar(0.0f, scale), d->fRandom->nextRangeScalar(0.0f, scale)},
+            {d->fRandom->nextRangeScalar(0.0f, scale), d->fRandom->nextRangeScalar(0.0f, scale)}};
+
+    GrGradientShader::RandomParams params(d->fRandom);
+    auto shader = params.fUseColors4f
+                          ? SkGradientShader::MakeLinear(points, params.fColors4f,
+                                                         params.fColorSpace, params.fStops,
+                                                         params.fColorCount, params.fTileMode)
+                          : SkGradientShader::MakeLinear(points, params.fColors, params.fStops,
+                                                         params.fColorCount, params.fTileMode);
+    GrTest::TestAsFPArgs asFPArgs(d);
+    std::unique_ptr<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args());
+    GrAlwaysAssert(fp);
+    return fp;
+}
+#endif
+
+std::unique_ptr<GrFragmentProcessor> GrLinearGradientLayout::Make(const SkLinearGradient& grad,
+                                                                  const GrFPArgs& args) {
+    SkMatrix matrix;
+    if (!grad.totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) {
+        return nullptr;
+    }
+    matrix.postConcat(grad.getGradientMatrix());
+    return std::unique_ptr<GrFragmentProcessor>(new GrLinearGradientLayout(matrix));
+}
diff --git a/src/gpu/gradients/generated/GrLinearGradientLayout.h b/src/gpu/gradients/generated/GrLinearGradientLayout.h
new file mode 100644
index 0000000..57da10f
--- /dev/null
+++ b/src/gpu/gradients/generated/GrLinearGradientLayout.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrLinearGradientLayout.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrLinearGradientLayout_DEFINED
+#define GrLinearGradientLayout_DEFINED
+#include "SkTypes.h"
+
+#include "SkLinearGradient.h"
+#include "../GrGradientShader.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrLinearGradientLayout : public GrFragmentProcessor {
+public:
+    static std::unique_ptr<GrFragmentProcessor> Make(const SkLinearGradient& gradient,
+                                                     const GrFPArgs& args);
+    GrLinearGradientLayout(const GrLinearGradientLayout& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "LinearGradientLayout"; }
+    GrCoordTransform fCoordTransform0;
+    SkMatrix44 gradientMatrix;
+
+private:
+    GrLinearGradientLayout(SkMatrix44 gradientMatrix)
+            : INHERITED(kGrLinearGradientLayout_ClassID,
+                        (OptimizationFlags)kPreservesOpaqueInput_OptimizationFlag)
+            , fCoordTransform0(gradientMatrix)
+            , gradientMatrix(gradientMatrix) {
+        this->addCoordTransform(&fCoordTransform0);
+    }
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/gradients/generated/GrRadialGradientLayout.cpp b/src/gpu/gradients/generated/GrRadialGradientLayout.cpp
new file mode 100644
index 0000000..4643c9b
--- /dev/null
+++ b/src/gpu/gradients/generated/GrRadialGradientLayout.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrRadialGradientLayout.fp; do not modify.
+ **************************************************************************************************/
+#include "GrRadialGradientLayout.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLRadialGradientLayout : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLRadialGradientLayout() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrRadialGradientLayout& _outer = args.fFp.cast<GrRadialGradientLayout>();
+        (void)_outer;
+        auto gradientMatrix = _outer.gradientMatrix;
+        (void)gradientMatrix;
+        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+        fragBuilder->codeAppendf("half t = half(length(%s));\n%s = half4(t, 1.0, 0.0, 0.0);\n",
+                                 sk_TransformedCoords2D_0.c_str(), args.fOutputColor);
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {}
+};
+GrGLSLFragmentProcessor* GrRadialGradientLayout::onCreateGLSLInstance() const {
+    return new GrGLSLRadialGradientLayout();
+}
+void GrRadialGradientLayout::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                   GrProcessorKeyBuilder* b) const {}
+bool GrRadialGradientLayout::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrRadialGradientLayout& that = other.cast<GrRadialGradientLayout>();
+    (void)that;
+    if (gradientMatrix != that.gradientMatrix) return false;
+    return true;
+}
+GrRadialGradientLayout::GrRadialGradientLayout(const GrRadialGradientLayout& src)
+        : INHERITED(kGrRadialGradientLayout_ClassID, src.optimizationFlags())
+        , fCoordTransform0(src.fCoordTransform0)
+        , gradientMatrix(src.gradientMatrix) {
+    this->addCoordTransform(&fCoordTransform0);
+}
+std::unique_ptr<GrFragmentProcessor> GrRadialGradientLayout::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrRadialGradientLayout(*this));
+}
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRadialGradientLayout);
+#if GR_TEST_UTILS
+std::unique_ptr<GrFragmentProcessor> GrRadialGradientLayout::TestCreate(GrProcessorTestData* d) {
+    SkScalar scale = GrGradientShader::RandomParams::kGradientScale;
+    sk_sp<SkShader> shader;
+    do {
+        GrGradientShader::RandomParams params(d->fRandom);
+        SkPoint center = {d->fRandom->nextRangeScalar(0.0f, scale),
+                          d->fRandom->nextRangeScalar(0.0f, scale)};
+        SkScalar radius = d->fRandom->nextRangeScalar(0.0f, scale);
+        shader = params.fUseColors4f
+                         ? SkGradientShader::MakeRadial(center, radius, params.fColors4f,
+                                                        params.fColorSpace, params.fStops,
+                                                        params.fColorCount, params.fTileMode)
+                         : SkGradientShader::MakeRadial(center, radius, params.fColors,
+                                                        params.fStops, params.fColorCount,
+                                                        params.fTileMode);
+    } while (!shader);
+    GrTest::TestAsFPArgs asFPArgs(d);
+    std::unique_ptr<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args());
+    GrAlwaysAssert(fp);
+    return fp;
+}
+#endif
+
+std::unique_ptr<GrFragmentProcessor> GrRadialGradientLayout::Make(const SkRadialGradient& grad,
+                                                                  const GrFPArgs& args) {
+    SkMatrix matrix;
+    if (!grad.totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) {
+        return nullptr;
+    }
+    matrix.postConcat(grad.getGradientMatrix());
+    return std::unique_ptr<GrFragmentProcessor>(new GrRadialGradientLayout(matrix));
+}
diff --git a/src/gpu/gradients/generated/GrRadialGradientLayout.h b/src/gpu/gradients/generated/GrRadialGradientLayout.h
new file mode 100644
index 0000000..a70202c
--- /dev/null
+++ b/src/gpu/gradients/generated/GrRadialGradientLayout.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrRadialGradientLayout.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrRadialGradientLayout_DEFINED
+#define GrRadialGradientLayout_DEFINED
+#include "SkTypes.h"
+
+#include "SkRadialGradient.h"
+#include "../GrGradientShader.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrRadialGradientLayout : public GrFragmentProcessor {
+public:
+    static std::unique_ptr<GrFragmentProcessor> Make(const SkRadialGradient& gradient,
+                                                     const GrFPArgs& args);
+    GrRadialGradientLayout(const GrRadialGradientLayout& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "RadialGradientLayout"; }
+    GrCoordTransform fCoordTransform0;
+    SkMatrix44 gradientMatrix;
+
+private:
+    GrRadialGradientLayout(SkMatrix44 gradientMatrix)
+            : INHERITED(kGrRadialGradientLayout_ClassID,
+                        (OptimizationFlags)kPreservesOpaqueInput_OptimizationFlag)
+            , fCoordTransform0(gradientMatrix)
+            , gradientMatrix(gradientMatrix) {
+        this->addCoordTransform(&fCoordTransform0);
+    }
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.cpp b/src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.cpp
new file mode 100644
index 0000000..4ad3efe
--- /dev/null
+++ b/src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrSingleIntervalGradientColorizer.fp; do not modify.
+ **************************************************************************************************/
+#include "GrSingleIntervalGradientColorizer.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLSingleIntervalGradientColorizer : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLSingleIntervalGradientColorizer() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrSingleIntervalGradientColorizer& _outer =
+                args.fFp.cast<GrSingleIntervalGradientColorizer>();
+        (void)_outer;
+        auto start = _outer.start;
+        (void)start;
+        auto end = _outer.end;
+        (void)end;
+        startVar =
+                args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "start");
+        endVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "end");
+        fragBuilder->codeAppendf("half t = %s.x;\n%s = (1.0 - t) * %s + t * %s;\n",
+                                 args.fInputColor, args.fOutputColor,
+                                 args.fUniformHandler->getUniformCStr(startVar),
+                                 args.fUniformHandler->getUniformCStr(endVar));
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {
+        const GrSingleIntervalGradientColorizer& _outer =
+                _proc.cast<GrSingleIntervalGradientColorizer>();
+        {
+            const SkPMColor4f& startValue = _outer.start;
+            if (startPrev != startValue) {
+                startPrev = startValue;
+                pdman.set4fv(startVar, 1, startValue.vec());
+            }
+            const SkPMColor4f& endValue = _outer.end;
+            if (endPrev != endValue) {
+                endPrev = endValue;
+                pdman.set4fv(endVar, 1, endValue.vec());
+            }
+        }
+    }
+    SkPMColor4f startPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN};
+    SkPMColor4f endPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN};
+    UniformHandle startVar;
+    UniformHandle endVar;
+};
+GrGLSLFragmentProcessor* GrSingleIntervalGradientColorizer::onCreateGLSLInstance() const {
+    return new GrGLSLSingleIntervalGradientColorizer();
+}
+void GrSingleIntervalGradientColorizer::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                              GrProcessorKeyBuilder* b) const {}
+bool GrSingleIntervalGradientColorizer::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrSingleIntervalGradientColorizer& that = other.cast<GrSingleIntervalGradientColorizer>();
+    (void)that;
+    if (start != that.start) return false;
+    if (end != that.end) return false;
+    return true;
+}
+GrSingleIntervalGradientColorizer::GrSingleIntervalGradientColorizer(
+        const GrSingleIntervalGradientColorizer& src)
+        : INHERITED(kGrSingleIntervalGradientColorizer_ClassID, src.optimizationFlags())
+        , start(src.start)
+        , end(src.end) {}
+std::unique_ptr<GrFragmentProcessor> GrSingleIntervalGradientColorizer::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrSingleIntervalGradientColorizer(*this));
+}
diff --git a/src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.h b/src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.h
new file mode 100644
index 0000000..0a04c31
--- /dev/null
+++ b/src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrSingleIntervalGradientColorizer.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrSingleIntervalGradientColorizer_DEFINED
+#define GrSingleIntervalGradientColorizer_DEFINED
+#include "SkTypes.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrSingleIntervalGradientColorizer : public GrFragmentProcessor {
+public:
+    static std::unique_ptr<GrFragmentProcessor> Make(SkPMColor4f start, SkPMColor4f end) {
+        return std::unique_ptr<GrFragmentProcessor>(
+                new GrSingleIntervalGradientColorizer(start, end));
+    }
+    GrSingleIntervalGradientColorizer(const GrSingleIntervalGradientColorizer& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "SingleIntervalGradientColorizer"; }
+    SkPMColor4f start;
+    SkPMColor4f end;
+
+private:
+    GrSingleIntervalGradientColorizer(SkPMColor4f start, SkPMColor4f end)
+            : INHERITED(kGrSingleIntervalGradientColorizer_ClassID, kNone_OptimizationFlags)
+            , start(start)
+            , end(end) {}
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/gradients/generated/GrSweepGradientLayout.cpp b/src/gpu/gradients/generated/GrSweepGradientLayout.cpp
new file mode 100644
index 0000000..3856a21
--- /dev/null
+++ b/src/gpu/gradients/generated/GrSweepGradientLayout.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrSweepGradientLayout.fp; do not modify.
+ **************************************************************************************************/
+#include "GrSweepGradientLayout.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLSweepGradientLayout : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLSweepGradientLayout() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrSweepGradientLayout& _outer = args.fFp.cast<GrSweepGradientLayout>();
+        (void)_outer;
+        auto gradientMatrix = _outer.gradientMatrix;
+        (void)gradientMatrix;
+        auto bias = _outer.bias;
+        (void)bias;
+        auto scale = _outer.scale;
+        (void)scale;
+        biasVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, "bias");
+        scaleVar =
+                args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, "scale");
+        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+        fragBuilder->codeAppendf(
+                "half angle;\nif (sk_Caps.atan2ImplementedAsAtanYOverX) {\n    angle = half(2.0 * "
+                "atan(-%s.y, length(%s) - %s.x));\n} else {\n    angle = half(atan(-%s.y, "
+                "-%s.x));\n}\nhalf t = ((angle * 0.15915494309180001 + 0.5) + %s) * %s;\n%s = "
+                "half4(t, 1.0, 0.0, 0.0);\n",
+                sk_TransformedCoords2D_0.c_str(), sk_TransformedCoords2D_0.c_str(),
+                sk_TransformedCoords2D_0.c_str(), sk_TransformedCoords2D_0.c_str(),
+                sk_TransformedCoords2D_0.c_str(), args.fUniformHandler->getUniformCStr(biasVar),
+                args.fUniformHandler->getUniformCStr(scaleVar), args.fOutputColor);
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {
+        const GrSweepGradientLayout& _outer = _proc.cast<GrSweepGradientLayout>();
+        {
+            float biasValue = _outer.bias;
+            if (biasPrev != biasValue) {
+                biasPrev = biasValue;
+                pdman.set1f(biasVar, biasValue);
+            }
+            float scaleValue = _outer.scale;
+            if (scalePrev != scaleValue) {
+                scalePrev = scaleValue;
+                pdman.set1f(scaleVar, scaleValue);
+            }
+        }
+    }
+    float biasPrev = SK_FloatNaN;
+    float scalePrev = SK_FloatNaN;
+    UniformHandle biasVar;
+    UniformHandle scaleVar;
+};
+GrGLSLFragmentProcessor* GrSweepGradientLayout::onCreateGLSLInstance() const {
+    return new GrGLSLSweepGradientLayout();
+}
+void GrSweepGradientLayout::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                  GrProcessorKeyBuilder* b) const {}
+bool GrSweepGradientLayout::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrSweepGradientLayout& that = other.cast<GrSweepGradientLayout>();
+    (void)that;
+    if (gradientMatrix != that.gradientMatrix) return false;
+    if (bias != that.bias) return false;
+    if (scale != that.scale) return false;
+    return true;
+}
+GrSweepGradientLayout::GrSweepGradientLayout(const GrSweepGradientLayout& src)
+        : INHERITED(kGrSweepGradientLayout_ClassID, src.optimizationFlags())
+        , fCoordTransform0(src.fCoordTransform0)
+        , gradientMatrix(src.gradientMatrix)
+        , bias(src.bias)
+        , scale(src.scale) {
+    this->addCoordTransform(&fCoordTransform0);
+}
+std::unique_ptr<GrFragmentProcessor> GrSweepGradientLayout::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrSweepGradientLayout(*this));
+}
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrSweepGradientLayout);
+#if GR_TEST_UTILS
+std::unique_ptr<GrFragmentProcessor> GrSweepGradientLayout::TestCreate(GrProcessorTestData* d) {
+    SkScalar scale = GrGradientShader::RandomParams::kGradientScale;
+    SkPoint center = {d->fRandom->nextRangeScalar(0.0f, scale),
+                      d->fRandom->nextRangeScalar(0.0f, scale)};
+
+    GrGradientShader::RandomParams params(d->fRandom);
+    auto shader = params.fUseColors4f
+                          ? SkGradientShader::MakeSweep(center.fX, center.fY, params.fColors4f,
+                                                        params.fColorSpace, params.fStops,
+                                                        params.fColorCount)
+                          : SkGradientShader::MakeSweep(center.fX, center.fY, params.fColors,
+                                                        params.fStops, params.fColorCount);
+    GrTest::TestAsFPArgs asFPArgs(d);
+    std::unique_ptr<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args());
+    GrAlwaysAssert(fp);
+    return fp;
+}
+#endif
+
+std::unique_ptr<GrFragmentProcessor> GrSweepGradientLayout::Make(const SkSweepGradient& grad,
+                                                                 const GrFPArgs& args) {
+    SkMatrix matrix;
+    if (!grad.totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) {
+        return nullptr;
+    }
+    matrix.postConcat(grad.getGradientMatrix());
+    return std::unique_ptr<GrFragmentProcessor>(
+            new GrSweepGradientLayout(matrix, grad.getTBias(), grad.getTScale()));
+}
diff --git a/src/gpu/gradients/generated/GrSweepGradientLayout.h b/src/gpu/gradients/generated/GrSweepGradientLayout.h
new file mode 100644
index 0000000..b62ed3e
--- /dev/null
+++ b/src/gpu/gradients/generated/GrSweepGradientLayout.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrSweepGradientLayout.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrSweepGradientLayout_DEFINED
+#define GrSweepGradientLayout_DEFINED
+#include "SkTypes.h"
+
+#include "SkSweepGradient.h"
+#include "../GrGradientShader.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrSweepGradientLayout : public GrFragmentProcessor {
+public:
+    static std::unique_ptr<GrFragmentProcessor> Make(const SkSweepGradient& gradient,
+                                                     const GrFPArgs& args);
+    GrSweepGradientLayout(const GrSweepGradientLayout& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "SweepGradientLayout"; }
+    GrCoordTransform fCoordTransform0;
+    SkMatrix44 gradientMatrix;
+    float bias;
+    float scale;
+
+private:
+    GrSweepGradientLayout(SkMatrix44 gradientMatrix, float bias, float scale)
+            : INHERITED(kGrSweepGradientLayout_ClassID,
+                        (OptimizationFlags)kPreservesOpaqueInput_OptimizationFlag)
+            , fCoordTransform0(gradientMatrix)
+            , gradientMatrix(gradientMatrix)
+            , bias(bias)
+            , scale(scale) {
+        this->addCoordTransform(&fCoordTransform0);
+    }
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/gradients/generated/GrTextureGradientColorizer.cpp b/src/gpu/gradients/generated/GrTextureGradientColorizer.cpp
new file mode 100644
index 0000000..22681b3
--- /dev/null
+++ b/src/gpu/gradients/generated/GrTextureGradientColorizer.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrTextureGradientColorizer.fp; do not modify.
+ **************************************************************************************************/
+#include "GrTextureGradientColorizer.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLTextureGradientColorizer : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLTextureGradientColorizer() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrTextureGradientColorizer& _outer = args.fFp.cast<GrTextureGradientColorizer>();
+        (void)_outer;
+        fragBuilder->codeAppendf(
+                "half2 coord = half2(%s.x, 0.5);\n%s = texture(%s, float2(coord)).%s;\n",
+                args.fInputColor, args.fOutputColor,
+                fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(),
+                fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str());
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {}
+};
+GrGLSLFragmentProcessor* GrTextureGradientColorizer::onCreateGLSLInstance() const {
+    return new GrGLSLTextureGradientColorizer();
+}
+void GrTextureGradientColorizer::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                       GrProcessorKeyBuilder* b) const {}
+bool GrTextureGradientColorizer::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrTextureGradientColorizer& that = other.cast<GrTextureGradientColorizer>();
+    (void)that;
+    if (gradient != that.gradient) return false;
+    return true;
+}
+GrTextureGradientColorizer::GrTextureGradientColorizer(const GrTextureGradientColorizer& src)
+        : INHERITED(kGrTextureGradientColorizer_ClassID, src.optimizationFlags())
+        , gradient(src.gradient) {
+    this->setTextureSamplerCnt(1);
+}
+std::unique_ptr<GrFragmentProcessor> GrTextureGradientColorizer::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrTextureGradientColorizer(*this));
+}
+const GrFragmentProcessor::TextureSampler& GrTextureGradientColorizer::onTextureSampler(
+        int index) const {
+    return IthTextureSampler(index, gradient);
+}
diff --git a/src/gpu/gradients/generated/GrTextureGradientColorizer.h b/src/gpu/gradients/generated/GrTextureGradientColorizer.h
new file mode 100644
index 0000000..792a018
--- /dev/null
+++ b/src/gpu/gradients/generated/GrTextureGradientColorizer.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrTextureGradientColorizer.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrTextureGradientColorizer_DEFINED
+#define GrTextureGradientColorizer_DEFINED
+#include "SkTypes.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrTextureGradientColorizer : public GrFragmentProcessor {
+public:
+    static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy> gradient) {
+        return std::unique_ptr<GrFragmentProcessor>(new GrTextureGradientColorizer(gradient));
+    }
+    GrTextureGradientColorizer(const GrTextureGradientColorizer& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "TextureGradientColorizer"; }
+    TextureSampler gradient;
+
+private:
+    GrTextureGradientColorizer(sk_sp<GrTextureProxy> gradient)
+            : INHERITED(kGrTextureGradientColorizer_ClassID, kNone_OptimizationFlags)
+            , gradient(std::move(gradient), GrSamplerState::ClampBilerp()) {
+        this->setTextureSamplerCnt(1);
+    }
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    const TextureSampler& onTextureSampler(int) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/gradients/generated/GrTiledGradientEffect.cpp b/src/gpu/gradients/generated/GrTiledGradientEffect.cpp
new file mode 100644
index 0000000..8a49c59
--- /dev/null
+++ b/src/gpu/gradients/generated/GrTiledGradientEffect.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrTiledGradientEffect.fp; do not modify.
+ **************************************************************************************************/
+#include "GrTiledGradientEffect.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLTiledGradientEffect : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLTiledGradientEffect() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrTiledGradientEffect& _outer = args.fFp.cast<GrTiledGradientEffect>();
+        (void)_outer;
+        auto mirror = _outer.mirror;
+        (void)mirror;
+        auto makePremul = _outer.makePremul;
+        (void)makePremul;
+        auto colorsAreOpaque = _outer.colorsAreOpaque;
+        (void)colorsAreOpaque;
+        SkString _child1("_child1");
+        this->emitChild(_outer.gradLayout_index, &_child1, args);
+        fragBuilder->codeAppendf(
+                "half4 t = %s;\nif (!%s && t.y < 0.0) {\n    %s = half4(0.0);\n} else {\n    @if "
+                "(%s) {\n        half t_1 = t.x - 1.0;\n        half tiled_t = (t_1 - 2.0 * "
+                "floor(t_1 * 0.5)) - 1.0;\n        if (sk_Caps.mustDoOpBetweenFloorAndAbs) {\n     "
+                "       tiled_t = clamp(tiled_t, -1.0, 1.0);\n        }\n        t.x = "
+                "abs(tiled_t);\n    } else {\n        t.x = fract(t.x);\n    }",
+                _child1.c_str(),
+                (_outer.childProcessor(_outer.gradLayout_index).preservesOpaqueInput() ? "true"
+                                                                                       : "false"),
+                args.fOutputColor, (_outer.mirror ? "true" : "false"));
+        SkString _input0("t");
+        SkString _child0("_child0");
+        this->emitChild(_outer.colorizer_index, _input0.c_str(), &_child0, args);
+        fragBuilder->codeAppendf("\n    %s = %s;\n}\n@if (%s) {\n    %s.xyz *= %s.w;\n}\n",
+                                 args.fOutputColor, _child0.c_str(),
+                                 (_outer.makePremul ? "true" : "false"), args.fOutputColor,
+                                 args.fOutputColor);
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {}
+};
+GrGLSLFragmentProcessor* GrTiledGradientEffect::onCreateGLSLInstance() const {
+    return new GrGLSLTiledGradientEffect();
+}
+void GrTiledGradientEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                  GrProcessorKeyBuilder* b) const {
+    b->add32((int32_t)mirror);
+    b->add32((int32_t)makePremul);
+}
+bool GrTiledGradientEffect::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrTiledGradientEffect& that = other.cast<GrTiledGradientEffect>();
+    (void)that;
+    if (mirror != that.mirror) return false;
+    if (makePremul != that.makePremul) return false;
+    if (colorsAreOpaque != that.colorsAreOpaque) return false;
+    return true;
+}
+GrTiledGradientEffect::GrTiledGradientEffect(const GrTiledGradientEffect& src)
+        : INHERITED(kGrTiledGradientEffect_ClassID, src.optimizationFlags())
+        , colorizer_index(src.colorizer_index)
+        , gradLayout_index(src.gradLayout_index)
+        , mirror(src.mirror)
+        , makePremul(src.makePremul)
+        , colorsAreOpaque(src.colorsAreOpaque) {
+    this->registerChildProcessor(src.childProcessor(colorizer_index).clone());
+    this->registerChildProcessor(src.childProcessor(gradLayout_index).clone());
+}
+std::unique_ptr<GrFragmentProcessor> GrTiledGradientEffect::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrTiledGradientEffect(*this));
+}
diff --git a/src/gpu/gradients/generated/GrTiledGradientEffect.h b/src/gpu/gradients/generated/GrTiledGradientEffect.h
new file mode 100644
index 0000000..f9e00fb
--- /dev/null
+++ b/src/gpu/gradients/generated/GrTiledGradientEffect.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrTiledGradientEffect.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrTiledGradientEffect_DEFINED
+#define GrTiledGradientEffect_DEFINED
+#include "SkTypes.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrTiledGradientEffect : public GrFragmentProcessor {
+public:
+    static std::unique_ptr<GrFragmentProcessor> Make(
+            std::unique_ptr<GrFragmentProcessor> colorizer,
+            std::unique_ptr<GrFragmentProcessor> gradLayout, bool mirror, bool makePremul,
+            bool colorsAreOpaque) {
+        return std::unique_ptr<GrFragmentProcessor>(new GrTiledGradientEffect(
+                std::move(colorizer), std::move(gradLayout), mirror, makePremul, colorsAreOpaque));
+    }
+    GrTiledGradientEffect(const GrTiledGradientEffect& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "TiledGradientEffect"; }
+    int colorizer_index = -1;
+    int gradLayout_index = -1;
+    bool mirror;
+    bool makePremul;
+    bool colorsAreOpaque;
+
+private:
+    GrTiledGradientEffect(std::unique_ptr<GrFragmentProcessor> colorizer,
+                          std::unique_ptr<GrFragmentProcessor> gradLayout, bool mirror,
+                          bool makePremul, bool colorsAreOpaque)
+            : INHERITED(kGrTiledGradientEffect_ClassID,
+                        (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag |
+                                (colorsAreOpaque && gradLayout->preservesOpaqueInput()
+                                         ? kPreservesOpaqueInput_OptimizationFlag
+                                         : kNone_OptimizationFlags))
+            , mirror(mirror)
+            , makePremul(makePremul)
+            , colorsAreOpaque(colorsAreOpaque) {
+        SkASSERT(colorizer);
+        colorizer_index = this->numChildProcessors();
+        this->registerChildProcessor(std::move(colorizer));
+        SkASSERT(gradLayout);
+        gradLayout_index = this->numChildProcessors();
+        this->registerChildProcessor(std::move(gradLayout));
+    }
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.cpp b/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.cpp
new file mode 100644
index 0000000..632d17d
--- /dev/null
+++ b/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.cpp
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrTwoPointConicalGradientLayout.fp; do not modify.
+ **************************************************************************************************/
+#include "GrTwoPointConicalGradientLayout.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLTwoPointConicalGradientLayout : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLTwoPointConicalGradientLayout() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrTwoPointConicalGradientLayout& _outer =
+                args.fFp.cast<GrTwoPointConicalGradientLayout>();
+        (void)_outer;
+        auto gradientMatrix = _outer.gradientMatrix;
+        (void)gradientMatrix;
+        auto type = _outer.type;
+        (void)type;
+        auto isRadiusIncreasing = _outer.isRadiusIncreasing;
+        (void)isRadiusIncreasing;
+        auto isFocalOnCircle = _outer.isFocalOnCircle;
+        (void)isFocalOnCircle;
+        auto isWellBehaved = _outer.isWellBehaved;
+        (void)isWellBehaved;
+        auto isSwapped = _outer.isSwapped;
+        (void)isSwapped;
+        auto isNativelyFocal = _outer.isNativelyFocal;
+        (void)isNativelyFocal;
+        auto focalParams = _outer.focalParams;
+        (void)focalParams;
+        focalParamsVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType,
+                                                          "focalParams");
+        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+        fragBuilder->codeAppendf(
+                "float2 p = %s;\nfloat t = -1.0;\nhalf v = 1.0;\n@switch (%d) {\n    case 1:\n     "
+                "   {\n            half r0_2 = %s.y;\n            t = float(r0_2) - p.y * p.y;\n   "
+                "         if (t >= 0.0) {\n                t = p.x + sqrt(t);\n            } else "
+                "{\n                v = -1.0;\n            }\n        }\n        break;\n    case "
+                "0:\n        {\n            half r0 = %s.x;\n            @if (%s) {\n              "
+                "  t = length(p) - float(r0);\n            } else {\n                t = "
+                "-length(p) - float(r0);\n       ",
+                sk_TransformedCoords2D_0.c_str(), (int)_outer.type,
+                args.fUniformHandler->getUniformCStr(focalParamsVar),
+                args.fUniformHandler->getUniformCStr(focalParamsVar),
+                (_outer.isRadiusIncreasing ? "true" : "false"));
+        fragBuilder->codeAppendf(
+                "     }\n        }\n        break;\n    case 2:\n        {\n            half invR1 "
+                "= %s.x;\n            half fx = %s.y;\n            float x_t = -1.0;\n            "
+                "@if (%s) {\n                x_t = dot(p, p) / p.x;\n            } else if (%s) "
+                "{\n                x_t = length(p) - p.x * float(invR1);\n            } else {\n  "
+                "              float temp = p.x * p.x - p.y * p.y;\n                if (temp >= "
+                "0.0) {\n                    @if (%s || !%s) {\n                        x_t = "
+                "-sqrt(temp) - p.x * float(invR1)",
+                args.fUniformHandler->getUniformCStr(focalParamsVar),
+                args.fUniformHandler->getUniformCStr(focalParamsVar),
+                (_outer.isFocalOnCircle ? "true" : "false"),
+                (_outer.isWellBehaved ? "true" : "false"), (_outer.isSwapped ? "true" : "false"),
+                (_outer.isRadiusIncreasing ? "true" : "false"));
+        fragBuilder->codeAppendf(
+                ";\n                    } else {\n                        x_t = sqrt(temp) - p.x * "
+                "float(invR1);\n                    }\n                }\n            }\n          "
+                "  @if (!%s) {\n                if (x_t <= 0.0) {\n                    v = -1.0;\n "
+                "               }\n            }\n            @if (%s) {\n                @if (%s) "
+                "{\n                    t = x_t;\n                } else {\n                    t "
+                "= x_t + float(fx);\n                }\n            } else {\n                @if "
+                "(%s) {\n              ",
+                (_outer.isWellBehaved ? "true" : "false"),
+                (_outer.isRadiusIncreasing ? "true" : "false"),
+                (_outer.isNativelyFocal ? "true" : "false"),
+                (_outer.isNativelyFocal ? "true" : "false"));
+        fragBuilder->codeAppendf(
+                "      t = -x_t;\n                } else {\n                    t = -x_t + "
+                "float(fx);\n                }\n            }\n            @if (%s) {\n            "
+                "    t = 1.0 - t;\n            }\n        }\n        break;\n}\n%s = "
+                "half4(half(t), v, 0.0, 0.0);\n",
+                (_outer.isSwapped ? "true" : "false"), args.fOutputColor);
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {
+        const GrTwoPointConicalGradientLayout& _outer =
+                _proc.cast<GrTwoPointConicalGradientLayout>();
+        {
+            const SkPoint& focalParamsValue = _outer.focalParams;
+            if (focalParamsPrev != focalParamsValue) {
+                focalParamsPrev = focalParamsValue;
+                pdman.set2f(focalParamsVar, focalParamsValue.fX, focalParamsValue.fY);
+            }
+        }
+    }
+    SkPoint focalParamsPrev = SkPoint::Make(SK_FloatNaN, SK_FloatNaN);
+    UniformHandle focalParamsVar;
+};
+GrGLSLFragmentProcessor* GrTwoPointConicalGradientLayout::onCreateGLSLInstance() const {
+    return new GrGLSLTwoPointConicalGradientLayout();
+}
+void GrTwoPointConicalGradientLayout::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                            GrProcessorKeyBuilder* b) const {
+    b->add32((int32_t)type);
+    b->add32((int32_t)isRadiusIncreasing);
+    b->add32((int32_t)isFocalOnCircle);
+    b->add32((int32_t)isWellBehaved);
+    b->add32((int32_t)isSwapped);
+    b->add32((int32_t)isNativelyFocal);
+}
+bool GrTwoPointConicalGradientLayout::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrTwoPointConicalGradientLayout& that = other.cast<GrTwoPointConicalGradientLayout>();
+    (void)that;
+    if (gradientMatrix != that.gradientMatrix) return false;
+    if (type != that.type) return false;
+    if (isRadiusIncreasing != that.isRadiusIncreasing) return false;
+    if (isFocalOnCircle != that.isFocalOnCircle) return false;
+    if (isWellBehaved != that.isWellBehaved) return false;
+    if (isSwapped != that.isSwapped) return false;
+    if (isNativelyFocal != that.isNativelyFocal) return false;
+    if (focalParams != that.focalParams) return false;
+    return true;
+}
+GrTwoPointConicalGradientLayout::GrTwoPointConicalGradientLayout(
+        const GrTwoPointConicalGradientLayout& src)
+        : INHERITED(kGrTwoPointConicalGradientLayout_ClassID, src.optimizationFlags())
+        , fCoordTransform0(src.fCoordTransform0)
+        , gradientMatrix(src.gradientMatrix)
+        , type(src.type)
+        , isRadiusIncreasing(src.isRadiusIncreasing)
+        , isFocalOnCircle(src.isFocalOnCircle)
+        , isWellBehaved(src.isWellBehaved)
+        , isSwapped(src.isSwapped)
+        , isNativelyFocal(src.isNativelyFocal)
+        , focalParams(src.focalParams) {
+    this->addCoordTransform(&fCoordTransform0);
+}
+std::unique_ptr<GrFragmentProcessor> GrTwoPointConicalGradientLayout::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrTwoPointConicalGradientLayout(*this));
+}
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrTwoPointConicalGradientLayout);
+#if GR_TEST_UTILS
+std::unique_ptr<GrFragmentProcessor> GrTwoPointConicalGradientLayout::TestCreate(
+        GrProcessorTestData* d) {
+    SkScalar scale = GrGradientShader::RandomParams::kGradientScale;
+    SkScalar offset = scale / 32.0f;
+
+    SkPoint center1 = {d->fRandom->nextRangeScalar(0.0f, scale),
+                       d->fRandom->nextRangeScalar(0.0f, scale)};
+    SkPoint center2 = {d->fRandom->nextRangeScalar(0.0f, scale),
+                       d->fRandom->nextRangeScalar(0.0f, scale)};
+    SkScalar radius1 = d->fRandom->nextRangeScalar(0.0f, scale);
+    SkScalar radius2 = d->fRandom->nextRangeScalar(0.0f, scale);
+
+    constexpr int kTestTypeMask = (1 << 2) - 1, kTestNativelyFocalBit = (1 << 2),
+                  kTestFocalOnCircleBit = (1 << 3), kTestSwappedBit = (1 << 4);
+    // We won't treat isWellDefined and isRadiusIncreasing specially because they
+    // should have high probability to be turned on and off as we're getting random
+    // radii and centers.
+
+    int mask = d->fRandom->nextU();
+    int type = mask & kTestTypeMask;
+    if (type == static_cast<int>(Type::kRadial)) {
+        center2 = center1;
+        // Make sure that the radii are different
+        if (SkScalarNearlyZero(radius1 - radius2)) {
+            radius2 += offset;
+        }
+    } else if (type == static_cast<int>(Type::kStrip)) {
+        radius1 = SkTMax(radius1, .1f);  // Make sure that the radius is non-zero
+        radius2 = radius1;
+        // Make sure that the centers are different
+        if (SkScalarNearlyZero(SkPoint::Distance(center1, center2))) {
+            center2.fX += offset;
+        }
+    } else {  // kFocal_Type
+        // Make sure that the centers are different
+        if (SkScalarNearlyZero(SkPoint::Distance(center1, center2))) {
+            center2.fX += offset;
+        }
+
+        if (kTestNativelyFocalBit & mask) {
+            radius1 = 0;
+        }
+        if (kTestFocalOnCircleBit & mask) {
+            radius2 = radius1 + SkPoint::Distance(center1, center2);
+        }
+        if (kTestSwappedBit & mask) {
+            std::swap(radius1, radius2);
+            radius2 = 0;
+        }
+
+        // Make sure that the radii are different
+        if (SkScalarNearlyZero(radius1 - radius2)) {
+            radius2 += offset;
+        }
+    }
+
+    if (SkScalarNearlyZero(radius1 - radius2) &&
+        SkScalarNearlyZero(SkPoint::Distance(center1, center2))) {
+        radius2 += offset;  // make sure that we're not degenerated
+    }
+
+    GrGradientShader::RandomParams params(d->fRandom);
+    auto shader = params.fUseColors4f
+                          ? SkGradientShader::MakeTwoPointConical(
+                                    center1, radius1, center2, radius2, params.fColors4f,
+                                    params.fColorSpace, params.fStops, params.fColorCount,
+                                    params.fTileMode)
+                          : SkGradientShader::MakeTwoPointConical(
+                                    center1, radius1, center2, radius2, params.fColors,
+                                    params.fStops, params.fColorCount, params.fTileMode);
+    GrTest::TestAsFPArgs asFPArgs(d);
+    std::unique_ptr<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args());
+
+    GrAlwaysAssert(fp);
+    return fp;
+}
+#endif
+
+// .fp files do not let you reference outside enum definitions, so we have to explicitly map
+// between the two compatible enum defs
+GrTwoPointConicalGradientLayout::Type convert_type(SkTwoPointConicalGradient::Type type) {
+    switch (type) {
+        case SkTwoPointConicalGradient::Type::kRadial:
+            return GrTwoPointConicalGradientLayout::Type::kRadial;
+        case SkTwoPointConicalGradient::Type::kStrip:
+            return GrTwoPointConicalGradientLayout::Type::kStrip;
+        case SkTwoPointConicalGradient::Type::kFocal:
+            return GrTwoPointConicalGradientLayout::Type::kFocal;
+    }
+    SkDEBUGFAIL("Should not be reachable");
+    return GrTwoPointConicalGradientLayout::Type::kRadial;
+}
+
+std::unique_ptr<GrFragmentProcessor> GrTwoPointConicalGradientLayout::Make(
+        const SkTwoPointConicalGradient& grad, const GrFPArgs& args) {
+    GrTwoPointConicalGradientLayout::Type grType = convert_type(grad.getType());
+
+    // The focalData struct is only valid if isFocal is true
+    const SkTwoPointConicalGradient::FocalData& focalData = grad.getFocalData();
+    bool isFocal = grType == Type::kFocal;
+
+    // Calculate optimization switches from gradient specification
+    bool isFocalOnCircle = isFocal && focalData.isFocalOnCircle();
+    bool isWellBehaved = isFocal && focalData.isWellBehaved();
+    bool isSwapped = isFocal && focalData.isSwapped();
+    bool isNativelyFocal = isFocal && focalData.isNativelyFocal();
+
+    // Type-specific calculations: isRadiusIncreasing, focalParams, and the gradient matrix.
+    // However, all types start with the total inverse local matrix calculated from the shader
+    // and args
+    bool isRadiusIncreasing;
+    SkPoint focalParams;  // really just a 2D tuple
+    SkMatrix matrix;
+
+    // Initialize the base matrix
+    if (!grad.totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) {
+        return nullptr;
+    }
+
+    if (isFocal) {
+        isRadiusIncreasing = (1 - focalData.fFocalX) > 0;
+
+        focalParams.set(1.0 / focalData.fR1, focalData.fFocalX);
+
+        matrix.postConcat(grad.getGradientMatrix());
+    } else if (grType == Type::kRadial) {
+        SkScalar dr = grad.getDiffRadius();
+        isRadiusIncreasing = dr >= 0;
+
+        SkScalar r0 = grad.getStartRadius() / dr;
+        focalParams.set(r0, r0 * r0);
+
+        // GPU radial matrix is different from the original matrix, since we map the diff radius
+        // to have |dr| = 1, so manually compute the final gradient matrix here.
+
+        // Map center to (0, 0)
+        matrix.postTranslate(-grad.getStartCenter().fX, -grad.getStartCenter().fY);
+
+        // scale |diffRadius| to 1
+        matrix.postScale(1 / dr, 1 / dr);
+    } else {                         // kStrip
+        isRadiusIncreasing = false;  // kStrip doesn't use this flag
+
+        SkScalar r0 = grad.getStartRadius() / grad.getCenterX1();
+        focalParams.set(r0, r0 * r0);
+
+        matrix.postConcat(grad.getGradientMatrix());
+    }
+
+    return std::unique_ptr<GrFragmentProcessor>(new GrTwoPointConicalGradientLayout(
+            matrix, grType, isRadiusIncreasing, isFocalOnCircle, isWellBehaved, isSwapped,
+            isNativelyFocal, focalParams));
+}
diff --git a/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.h b/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.h
new file mode 100644
index 0000000..ef4dc2d
--- /dev/null
+++ b/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrTwoPointConicalGradientLayout.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrTwoPointConicalGradientLayout_DEFINED
+#define GrTwoPointConicalGradientLayout_DEFINED
+#include "SkTypes.h"
+
+#include "SkTwoPointConicalGradient.h"
+#include "../GrGradientShader.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrTwoPointConicalGradientLayout : public GrFragmentProcessor {
+public:
+    enum class Type { kFocal = 2, kRadial = 0, kStrip = 1 };
+
+    static std::unique_ptr<GrFragmentProcessor> Make(const SkTwoPointConicalGradient& gradient,
+                                                     const GrFPArgs& args);
+    GrTwoPointConicalGradientLayout(const GrTwoPointConicalGradientLayout& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "TwoPointConicalGradientLayout"; }
+    GrCoordTransform fCoordTransform0;
+    SkMatrix44 gradientMatrix;
+    Type type;
+    bool isRadiusIncreasing;
+    bool isFocalOnCircle;
+    bool isWellBehaved;
+    bool isSwapped;
+    bool isNativelyFocal;
+    SkPoint focalParams;
+
+private:
+    GrTwoPointConicalGradientLayout(SkMatrix44 gradientMatrix, Type type, bool isRadiusIncreasing,
+                                    bool isFocalOnCircle, bool isWellBehaved, bool isSwapped,
+                                    bool isNativelyFocal, SkPoint focalParams)
+            : INHERITED(kGrTwoPointConicalGradientLayout_ClassID,
+                        (OptimizationFlags)kNone_OptimizationFlags)
+            , fCoordTransform0(gradientMatrix)
+            , gradientMatrix(gradientMatrix)
+            , type(type)
+            , isRadiusIncreasing(isRadiusIncreasing)
+            , isFocalOnCircle(isFocalOnCircle)
+            , isWellBehaved(isWellBehaved)
+            , isSwapped(isSwapped)
+            , isNativelyFocal(isNativelyFocal)
+            , focalParams(focalParams) {
+        this->addCoordTransform(&fCoordTransform0);
+    }
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.cpp b/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.cpp
new file mode 100644
index 0000000..47741326
--- /dev/null
+++ b/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.cpp
@@ -0,0 +1,380 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrUnrolledBinaryGradientColorizer.fp; do not modify.
+ **************************************************************************************************/
+#include "GrUnrolledBinaryGradientColorizer.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLUnrolledBinaryGradientColorizer : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLUnrolledBinaryGradientColorizer() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrUnrolledBinaryGradientColorizer& _outer =
+                args.fFp.cast<GrUnrolledBinaryGradientColorizer>();
+        (void)_outer;
+        auto intervalCount = _outer.intervalCount;
+        (void)intervalCount;
+        auto scale0_1 = _outer.scale0_1;
+        (void)scale0_1;
+        auto scale2_3 = _outer.scale2_3;
+        (void)scale2_3;
+        auto scale4_5 = _outer.scale4_5;
+        (void)scale4_5;
+        auto scale6_7 = _outer.scale6_7;
+        (void)scale6_7;
+        auto scale8_9 = _outer.scale8_9;
+        (void)scale8_9;
+        auto scale10_11 = _outer.scale10_11;
+        (void)scale10_11;
+        auto scale12_13 = _outer.scale12_13;
+        (void)scale12_13;
+        auto scale14_15 = _outer.scale14_15;
+        (void)scale14_15;
+        auto bias0_1 = _outer.bias0_1;
+        (void)bias0_1;
+        auto bias2_3 = _outer.bias2_3;
+        (void)bias2_3;
+        auto bias4_5 = _outer.bias4_5;
+        (void)bias4_5;
+        auto bias6_7 = _outer.bias6_7;
+        (void)bias6_7;
+        auto bias8_9 = _outer.bias8_9;
+        (void)bias8_9;
+        auto bias10_11 = _outer.bias10_11;
+        (void)bias10_11;
+        auto bias12_13 = _outer.bias12_13;
+        (void)bias12_13;
+        auto bias14_15 = _outer.bias14_15;
+        (void)bias14_15;
+        auto thresholds1_7 = _outer.thresholds1_7;
+        (void)thresholds1_7;
+        auto thresholds9_13 = _outer.thresholds9_13;
+        (void)thresholds9_13;
+        scale0_1Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
+                                                       "scale0_1");
+        if (intervalCount > 1) {
+            scale2_3Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
+                                                           "scale2_3");
+        }
+        if (intervalCount > 2) {
+            scale4_5Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
+                                                           "scale4_5");
+        }
+        if (intervalCount > 3) {
+            scale6_7Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
+                                                           "scale6_7");
+        }
+        if (intervalCount > 4) {
+            scale8_9Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
+                                                           "scale8_9");
+        }
+        if (intervalCount > 5) {
+            scale10_11Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
+                                                             kFloat4_GrSLType, "scale10_11");
+        }
+        if (intervalCount > 6) {
+            scale12_13Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
+                                                             kFloat4_GrSLType, "scale12_13");
+        }
+        if (intervalCount > 7) {
+            scale14_15Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
+                                                             kFloat4_GrSLType, "scale14_15");
+        }
+        bias0_1Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
+                                                      "bias0_1");
+        if (intervalCount > 1) {
+            bias2_3Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
+                                                          "bias2_3");
+        }
+        if (intervalCount > 2) {
+            bias4_5Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
+                                                          "bias4_5");
+        }
+        if (intervalCount > 3) {
+            bias6_7Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
+                                                          "bias6_7");
+        }
+        if (intervalCount > 4) {
+            bias8_9Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
+                                                          "bias8_9");
+        }
+        if (intervalCount > 5) {
+            bias10_11Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
+                                                            kFloat4_GrSLType, "bias10_11");
+        }
+        if (intervalCount > 6) {
+            bias12_13Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
+                                                            kFloat4_GrSLType, "bias12_13");
+        }
+        if (intervalCount > 7) {
+            bias14_15Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
+                                                            kFloat4_GrSLType, "bias14_15");
+        }
+        thresholds1_7Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType,
+                                                            "thresholds1_7");
+        thresholds9_13Var = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
+                                                             kHalf4_GrSLType, "thresholds9_13");
+        fragBuilder->codeAppendf(
+                "half t = %s.x;\nfloat4 scale, bias;\nif (%d <= 4 || t < %s.w) {\n    if (%d <= 2 "
+                "|| t < %s.y) {\n        if (%d <= 1 || t < %s.x) {\n            scale = %s;\n     "
+                "       bias = %s;\n        } else {\n            scale = %s;\n            bias = "
+                "%s;\n        }\n    } else {\n        if (%d <= 3 || t < %s.z) {\n            "
+                "scale = %s;\n            bias = %s;\n        } else {\n            scale = %s;\n  "
+                "          bias = %s;\n        }\n    }\n} else {\n    if (%d <= 6 || t < %s.y) "
+                "{\n        if (%d <= 5 || t <",
+                args.fInputColor, _outer.intervalCount,
+                args.fUniformHandler->getUniformCStr(thresholds1_7Var), _outer.intervalCount,
+                args.fUniformHandler->getUniformCStr(thresholds1_7Var), _outer.intervalCount,
+                args.fUniformHandler->getUniformCStr(thresholds1_7Var),
+                args.fUniformHandler->getUniformCStr(scale0_1Var),
+                args.fUniformHandler->getUniformCStr(bias0_1Var),
+                scale2_3Var.isValid() ? args.fUniformHandler->getUniformCStr(scale2_3Var)
+                                      : "float4(0)",
+                bias2_3Var.isValid() ? args.fUniformHandler->getUniformCStr(bias2_3Var)
+                                     : "float4(0)",
+                _outer.intervalCount, args.fUniformHandler->getUniformCStr(thresholds1_7Var),
+                scale4_5Var.isValid() ? args.fUniformHandler->getUniformCStr(scale4_5Var)
+                                      : "float4(0)",
+                bias4_5Var.isValid() ? args.fUniformHandler->getUniformCStr(bias4_5Var)
+                                     : "float4(0)",
+                scale6_7Var.isValid() ? args.fUniformHandler->getUniformCStr(scale6_7Var)
+                                      : "float4(0)",
+                bias6_7Var.isValid() ? args.fUniformHandler->getUniformCStr(bias6_7Var)
+                                     : "float4(0)",
+                _outer.intervalCount, args.fUniformHandler->getUniformCStr(thresholds9_13Var),
+                _outer.intervalCount);
+        fragBuilder->codeAppendf(
+                " %s.x) {\n            scale = %s;\n            bias = %s;\n        } else {\n     "
+                "       scale = %s;\n            bias = %s;\n        }\n    } else {\n        if "
+                "(%d <= 7 || t < %s.z) {\n            scale = %s;\n            bias = %s;\n        "
+                "} else {\n            scale = %s;\n            bias = %s;\n        }\n    "
+                "}\n}\n%s = half4(float(t) * scale + bias);\n",
+                args.fUniformHandler->getUniformCStr(thresholds9_13Var),
+                scale8_9Var.isValid() ? args.fUniformHandler->getUniformCStr(scale8_9Var)
+                                      : "float4(0)",
+                bias8_9Var.isValid() ? args.fUniformHandler->getUniformCStr(bias8_9Var)
+                                     : "float4(0)",
+                scale10_11Var.isValid() ? args.fUniformHandler->getUniformCStr(scale10_11Var)
+                                        : "float4(0)",
+                bias10_11Var.isValid() ? args.fUniformHandler->getUniformCStr(bias10_11Var)
+                                       : "float4(0)",
+                _outer.intervalCount, args.fUniformHandler->getUniformCStr(thresholds9_13Var),
+                scale12_13Var.isValid() ? args.fUniformHandler->getUniformCStr(scale12_13Var)
+                                        : "float4(0)",
+                bias12_13Var.isValid() ? args.fUniformHandler->getUniformCStr(bias12_13Var)
+                                       : "float4(0)",
+                scale14_15Var.isValid() ? args.fUniformHandler->getUniformCStr(scale14_15Var)
+                                        : "float4(0)",
+                bias14_15Var.isValid() ? args.fUniformHandler->getUniformCStr(bias14_15Var)
+                                       : "float4(0)",
+                args.fOutputColor);
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {
+        const GrUnrolledBinaryGradientColorizer& _outer =
+                _proc.cast<GrUnrolledBinaryGradientColorizer>();
+        {
+            pdman.set4fv(scale0_1Var, 1, (_outer.scale0_1).vec());
+            if (scale2_3Var.isValid()) {
+                pdman.set4fv(scale2_3Var, 1, (_outer.scale2_3).vec());
+            }
+            if (scale4_5Var.isValid()) {
+                pdman.set4fv(scale4_5Var, 1, (_outer.scale4_5).vec());
+            }
+            if (scale6_7Var.isValid()) {
+                pdman.set4fv(scale6_7Var, 1, (_outer.scale6_7).vec());
+            }
+            if (scale8_9Var.isValid()) {
+                pdman.set4fv(scale8_9Var, 1, (_outer.scale8_9).vec());
+            }
+            if (scale10_11Var.isValid()) {
+                pdman.set4fv(scale10_11Var, 1, (_outer.scale10_11).vec());
+            }
+            if (scale12_13Var.isValid()) {
+                pdman.set4fv(scale12_13Var, 1, (_outer.scale12_13).vec());
+            }
+            if (scale14_15Var.isValid()) {
+                pdman.set4fv(scale14_15Var, 1, (_outer.scale14_15).vec());
+            }
+            pdman.set4fv(bias0_1Var, 1, (_outer.bias0_1).vec());
+            if (bias2_3Var.isValid()) {
+                pdman.set4fv(bias2_3Var, 1, (_outer.bias2_3).vec());
+            }
+            if (bias4_5Var.isValid()) {
+                pdman.set4fv(bias4_5Var, 1, (_outer.bias4_5).vec());
+            }
+            if (bias6_7Var.isValid()) {
+                pdman.set4fv(bias6_7Var, 1, (_outer.bias6_7).vec());
+            }
+            if (bias8_9Var.isValid()) {
+                pdman.set4fv(bias8_9Var, 1, (_outer.bias8_9).vec());
+            }
+            if (bias10_11Var.isValid()) {
+                pdman.set4fv(bias10_11Var, 1, (_outer.bias10_11).vec());
+            }
+            if (bias12_13Var.isValid()) {
+                pdman.set4fv(bias12_13Var, 1, (_outer.bias12_13).vec());
+            }
+            if (bias14_15Var.isValid()) {
+                pdman.set4fv(bias14_15Var, 1, (_outer.bias14_15).vec());
+            }
+            pdman.set4fv(thresholds1_7Var, 1,
+                         reinterpret_cast<const float*>(&(_outer.thresholds1_7)));
+            pdman.set4fv(thresholds9_13Var, 1,
+                         reinterpret_cast<const float*>(&(_outer.thresholds9_13)));
+        }
+    }
+    UniformHandle scale0_1Var;
+    UniformHandle scale2_3Var;
+    UniformHandle scale4_5Var;
+    UniformHandle scale6_7Var;
+    UniformHandle scale8_9Var;
+    UniformHandle scale10_11Var;
+    UniformHandle scale12_13Var;
+    UniformHandle scale14_15Var;
+    UniformHandle bias0_1Var;
+    UniformHandle bias2_3Var;
+    UniformHandle bias4_5Var;
+    UniformHandle bias6_7Var;
+    UniformHandle bias8_9Var;
+    UniformHandle bias10_11Var;
+    UniformHandle bias12_13Var;
+    UniformHandle bias14_15Var;
+    UniformHandle thresholds1_7Var;
+    UniformHandle thresholds9_13Var;
+};
+GrGLSLFragmentProcessor* GrUnrolledBinaryGradientColorizer::onCreateGLSLInstance() const {
+    return new GrGLSLUnrolledBinaryGradientColorizer();
+}
+void GrUnrolledBinaryGradientColorizer::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                              GrProcessorKeyBuilder* b) const {
+    b->add32((int32_t)intervalCount);
+}
+bool GrUnrolledBinaryGradientColorizer::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrUnrolledBinaryGradientColorizer& that = other.cast<GrUnrolledBinaryGradientColorizer>();
+    (void)that;
+    if (intervalCount != that.intervalCount) return false;
+    if (scale0_1 != that.scale0_1) return false;
+    if (scale2_3 != that.scale2_3) return false;
+    if (scale4_5 != that.scale4_5) return false;
+    if (scale6_7 != that.scale6_7) return false;
+    if (scale8_9 != that.scale8_9) return false;
+    if (scale10_11 != that.scale10_11) return false;
+    if (scale12_13 != that.scale12_13) return false;
+    if (scale14_15 != that.scale14_15) return false;
+    if (bias0_1 != that.bias0_1) return false;
+    if (bias2_3 != that.bias2_3) return false;
+    if (bias4_5 != that.bias4_5) return false;
+    if (bias6_7 != that.bias6_7) return false;
+    if (bias8_9 != that.bias8_9) return false;
+    if (bias10_11 != that.bias10_11) return false;
+    if (bias12_13 != that.bias12_13) return false;
+    if (bias14_15 != that.bias14_15) return false;
+    if (thresholds1_7 != that.thresholds1_7) return false;
+    if (thresholds9_13 != that.thresholds9_13) return false;
+    return true;
+}
+GrUnrolledBinaryGradientColorizer::GrUnrolledBinaryGradientColorizer(
+        const GrUnrolledBinaryGradientColorizer& src)
+        : INHERITED(kGrUnrolledBinaryGradientColorizer_ClassID, src.optimizationFlags())
+        , intervalCount(src.intervalCount)
+        , scale0_1(src.scale0_1)
+        , scale2_3(src.scale2_3)
+        , scale4_5(src.scale4_5)
+        , scale6_7(src.scale6_7)
+        , scale8_9(src.scale8_9)
+        , scale10_11(src.scale10_11)
+        , scale12_13(src.scale12_13)
+        , scale14_15(src.scale14_15)
+        , bias0_1(src.bias0_1)
+        , bias2_3(src.bias2_3)
+        , bias4_5(src.bias4_5)
+        , bias6_7(src.bias6_7)
+        , bias8_9(src.bias8_9)
+        , bias10_11(src.bias10_11)
+        , bias12_13(src.bias12_13)
+        , bias14_15(src.bias14_15)
+        , thresholds1_7(src.thresholds1_7)
+        , thresholds9_13(src.thresholds9_13) {}
+std::unique_ptr<GrFragmentProcessor> GrUnrolledBinaryGradientColorizer::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrUnrolledBinaryGradientColorizer(*this));
+}
+
+static const int kMaxIntervals = 8;
+std::unique_ptr<GrFragmentProcessor> GrUnrolledBinaryGradientColorizer::Make(
+        const SkPMColor4f* colors, const SkScalar* positions, int count) {
+    // Depending on how the positions resolve into hard stops or regular stops, the number of
+    // intervals specified by the number of colors/positions can change. For instance, a plain
+    // 3 color gradient is two intervals, but a 4 color gradient with a hard stop is also
+    // two intervals. At the most extreme end, an 8 interval gradient made entirely of hard
+    // stops has 16 colors.
+
+    if (count > kMaxColorCount) {
+        // Definitely cannot represent this gradient configuration
+        return nullptr;
+    }
+
+    // The raster implementation also uses scales and biases, but since they must be calculated
+    // after the dst color space is applied, it limits our ability to cache their values.
+    SkPMColor4f scales[kMaxIntervals];
+    SkPMColor4f biases[kMaxIntervals];
+    SkScalar thresholds[kMaxIntervals];
+
+    int intervalCount = 0;
+
+    for (int i = 0; i < count - 1; i++) {
+        if (intervalCount >= kMaxIntervals) {
+            // Already reached kMaxIntervals, and haven't run out of color stops so this
+            // gradient cannot be represented by this shader.
+            return nullptr;
+        }
+
+        SkScalar t0 = positions[i];
+        SkScalar t1 = positions[i + 1];
+        SkScalar dt = t1 - t0;
+        // If the interval is empty, skip to the next interval. This will automatically create
+        // distinct hard stop intervals as needed. It also protects against malformed gradients
+        // that have repeated hard stops at the very beginning that are effectively unreachable.
+        if (SkScalarNearlyZero(dt)) {
+            continue;
+        }
+
+        auto c0 = Sk4f::Load(colors[i].vec());
+        auto c1 = Sk4f::Load(colors[i + 1].vec());
+
+        auto scale = (c1 - c0) / dt;
+        auto bias = c0 - t0 * scale;
+
+        scale.store(scales + intervalCount);
+        bias.store(biases + intervalCount);
+        thresholds[intervalCount] = t1;
+        intervalCount++;
+    }
+
+    // For isEqual to make sense, set the unused values to something consistent
+    for (int i = intervalCount; i < kMaxIntervals; i++) {
+        scales[i] = SK_PMColor4fTRANSPARENT;
+        biases[i] = SK_PMColor4fTRANSPARENT;
+        thresholds[i] = 0.0;
+    }
+
+    return std::unique_ptr<GrFragmentProcessor>(new GrUnrolledBinaryGradientColorizer(
+            intervalCount, scales[0], scales[1], scales[2], scales[3], scales[4], scales[5],
+            scales[6], scales[7], biases[0], biases[1], biases[2], biases[3], biases[4], biases[5],
+            biases[6], biases[7],
+            SkRect::MakeLTRB(thresholds[0], thresholds[1], thresholds[2], thresholds[3]),
+            SkRect::MakeLTRB(thresholds[4], thresholds[5], thresholds[6], 0.0)));
+}
diff --git a/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.h b/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.h
new file mode 100644
index 0000000..300a389
--- /dev/null
+++ b/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrUnrolledBinaryGradientColorizer.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrUnrolledBinaryGradientColorizer_DEFINED
+#define GrUnrolledBinaryGradientColorizer_DEFINED
+#include "SkTypes.h"
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+class GrUnrolledBinaryGradientColorizer : public GrFragmentProcessor {
+public:
+    static const int kMaxColorCount = 16;
+
+    static std::unique_ptr<GrFragmentProcessor> Make(const SkPMColor4f* colors,
+                                                     const SkScalar* positions,
+                                                     int count);
+    GrUnrolledBinaryGradientColorizer(const GrUnrolledBinaryGradientColorizer& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "UnrolledBinaryGradientColorizer"; }
+    int32_t intervalCount;
+    SkPMColor4f scale0_1;
+    SkPMColor4f scale2_3;
+    SkPMColor4f scale4_5;
+    SkPMColor4f scale6_7;
+    SkPMColor4f scale8_9;
+    SkPMColor4f scale10_11;
+    SkPMColor4f scale12_13;
+    SkPMColor4f scale14_15;
+    SkPMColor4f bias0_1;
+    SkPMColor4f bias2_3;
+    SkPMColor4f bias4_5;
+    SkPMColor4f bias6_7;
+    SkPMColor4f bias8_9;
+    SkPMColor4f bias10_11;
+    SkPMColor4f bias12_13;
+    SkPMColor4f bias14_15;
+    SkRect thresholds1_7;
+    SkRect thresholds9_13;
+
+private:
+    GrUnrolledBinaryGradientColorizer(int32_t intervalCount,
+                                      SkPMColor4f scale0_1,
+                                      SkPMColor4f scale2_3,
+                                      SkPMColor4f scale4_5,
+                                      SkPMColor4f scale6_7,
+                                      SkPMColor4f scale8_9,
+                                      SkPMColor4f scale10_11,
+                                      SkPMColor4f scale12_13,
+                                      SkPMColor4f scale14_15,
+                                      SkPMColor4f bias0_1,
+                                      SkPMColor4f bias2_3,
+                                      SkPMColor4f bias4_5,
+                                      SkPMColor4f bias6_7,
+                                      SkPMColor4f bias8_9,
+                                      SkPMColor4f bias10_11,
+                                      SkPMColor4f bias12_13,
+                                      SkPMColor4f bias14_15,
+                                      SkRect thresholds1_7,
+                                      SkRect thresholds9_13)
+            : INHERITED(kGrUnrolledBinaryGradientColorizer_ClassID, kNone_OptimizationFlags)
+            , intervalCount(intervalCount)
+            , scale0_1(scale0_1)
+            , scale2_3(scale2_3)
+            , scale4_5(scale4_5)
+            , scale6_7(scale6_7)
+            , scale8_9(scale8_9)
+            , scale10_11(scale10_11)
+            , scale12_13(scale12_13)
+            , scale14_15(scale14_15)
+            , bias0_1(bias0_1)
+            , bias2_3(bias2_3)
+            , bias4_5(bias4_5)
+            , bias6_7(bias6_7)
+            , bias8_9(bias8_9)
+            , bias10_11(bias10_11)
+            , bias12_13(bias12_13)
+            , bias14_15(bias14_15)
+            , thresholds1_7(thresholds1_7)
+            , thresholds9_13(thresholds9_13) {}
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
diff --git a/src/gpu/mock/GrMockBuffer.h b/src/gpu/mock/GrMockBuffer.h
index cf915ee..efb959b 100644
--- a/src/gpu/mock/GrMockBuffer.h
+++ b/src/gpu/mock/GrMockBuffer.h
@@ -8,11 +8,11 @@
 #ifndef GrMockBuffer_DEFINED
 #define GrMockBuffer_DEFINED
 
-#include "GrBuffer.h"
 #include "GrCaps.h"
+#include "GrGpuBuffer.h"
 #include "GrMockGpu.h"
 
-class GrMockBuffer : public GrBuffer {
+class GrMockBuffer : public GrGpuBuffer {
 public:
     GrMockBuffer(GrMockGpu* gpu, size_t sizeInBytes, GrGpuBufferType type,
                  GrAccessPattern accessPattern)
@@ -23,13 +23,13 @@
 private:
     void onMap() override {
         if (GrCaps::kNone_MapFlags != this->getGpu()->caps()->mapBufferFlags()) {
-            fMapPtr = sk_malloc_throw(this->sizeInBytes());
+            fMapPtr = sk_malloc_throw(this->size());
         }
     }
     void onUnmap() override { sk_free(fMapPtr); }
     bool onUpdateData(const void* src, size_t srcSizeInBytes) override { return true; }
 
-    typedef GrBuffer INHERITED;
+    typedef GrGpuBuffer INHERITED;
 };
 
 #endif
diff --git a/src/gpu/mock/GrMockCaps.h b/src/gpu/mock/GrMockCaps.h
index 65aee42..4ce5f89 100644
--- a/src/gpu/mock/GrMockCaps.h
+++ b/src/gpu/mock/GrMockCaps.h
@@ -31,6 +31,7 @@
         fShaderCaps->fFlatInterpolationSupport = options.fFlatInterpolationSupport;
         fShaderCaps->fMaxFragmentSamplers = options.fMaxFragmentSamplers;
         fShaderCaps->fShaderDerivativeSupport = options.fShaderDerivativeSupport;
+        fShaderCaps->fDualSourceBlendingSupport = options.fDualSourceBlendingSupport;
 
         this->applyOptionsOverrides(contextOptions);
     }
diff --git a/src/gpu/mock/GrMockGpu.cpp b/src/gpu/mock/GrMockGpu.cpp
index 4960fd0..1fdae0a 100644
--- a/src/gpu/mock/GrMockGpu.cpp
+++ b/src/gpu/mock/GrMockGpu.cpp
@@ -183,9 +183,9 @@
             new GrMockRenderTarget(this, GrMockRenderTarget::kWrapped, desc, rtInfo));
 }
 
-sk_sp<GrBuffer> GrMockGpu::onCreateBuffer(size_t sizeInBytes, GrGpuBufferType type,
-                                          GrAccessPattern accessPattern, const void*) {
-    return sk_sp<GrBuffer>(new GrMockBuffer(this, sizeInBytes, type, accessPattern));
+sk_sp<GrGpuBuffer> GrMockGpu::onCreateBuffer(size_t sizeInBytes, GrGpuBufferType type,
+                                             GrAccessPattern accessPattern, const void*) {
+    return sk_sp<GrGpuBuffer>(new GrMockBuffer(this, sizeInBytes, type, accessPattern));
 }
 
 GrStencilAttachment* GrMockGpu::createStencilAttachmentForRenderTarget(const GrRenderTarget* rt,
diff --git a/src/gpu/mock/GrMockGpu.h b/src/gpu/mock/GrMockGpu.h
index d574a5d..56c40ae 100644
--- a/src/gpu/mock/GrMockGpu.h
+++ b/src/gpu/mock/GrMockGpu.h
@@ -54,6 +54,12 @@
 
     void onResetContext(uint32_t resetBits) override {}
 
+    void querySampleLocations(
+            GrRenderTarget*, const GrStencilSettings&, SkTArray<SkPoint>*) override {
+        SkASSERT(!this->caps()->sampleLocationsSupport());
+        SK_ABORT("Sample locations not implemented for mock GPU.");
+    }
+
     void xferBarrier(GrRenderTarget*, GrXferBarrierType) override {}
 
     sk_sp<GrTexture> onCreateTexture(const GrSurfaceDesc&, SkBudgeted, const GrMipLevel[],
@@ -72,8 +78,8 @@
     sk_sp<GrRenderTarget> onWrapBackendTextureAsRenderTarget(const GrBackendTexture&,
                                                              int sampleCnt) override;
 
-    sk_sp<GrBuffer> onCreateBuffer(size_t sizeInBytes, GrGpuBufferType, GrAccessPattern,
-                                   const void*) override;
+    sk_sp<GrGpuBuffer> onCreateBuffer(size_t sizeInBytes, GrGpuBufferType, GrAccessPattern,
+                                      const void*) override;
 
     bool onReadPixels(GrSurface* surface, int left, int top, int width, int height, GrColorType,
                       void* buffer, size_t rowBytes) override {
@@ -85,11 +91,15 @@
         return true;
     }
 
-    bool onTransferPixels(GrTexture* texture, int left, int top, int width, int height, GrColorType,
-                          GrBuffer* transferBuffer, size_t offset, size_t rowBytes) override {
+    bool onTransferPixelsTo(GrTexture* texture, int left, int top, int width, int height,
+                            GrColorType, GrGpuBuffer* transferBuffer, size_t offset,
+                            size_t rowBytes) override {
         return true;
     }
-
+    size_t onTransferPixelsFrom(GrSurface* surface, int left, int top, int width, int height,
+                                GrColorType, GrGpuBuffer* transferBuffer, size_t offset) override {
+        return true;
+    }
     bool onCopySurface(GrSurface* dst, GrSurfaceOrigin dstOrigin, GrSurface* src,
                        GrSurfaceOrigin srcOrigin, const SkIRect& srcRect,
                        const SkIPoint& dstPoint, bool canDiscardOutsideDstRect) override {
@@ -100,7 +110,8 @@
 
     void onResolveRenderTarget(GrRenderTarget* target) override { return; }
 
-    void onFinishFlush(bool insertedSemaphores) override {}
+    void onFinishFlush(GrSurfaceProxy*, SkSurface::BackendSurfaceAccess access,
+                       GrFlushFlags flags, bool insertedSemaphores) override {}
 
     GrStencilAttachment* createStencilAttachmentForRenderTarget(const GrRenderTarget*,
                                                                 int width,
diff --git a/src/gpu/mock/GrMockTexture.h b/src/gpu/mock/GrMockTexture.h
index ac846f5..347b961 100644
--- a/src/gpu/mock/GrMockTexture.h
+++ b/src/gpu/mock/GrMockTexture.h
@@ -44,12 +44,6 @@
 
     void textureParamsModified() override {}
 
-    void setIdleProc(IdleProc proc, void* context) override {
-        fIdleProc = proc;
-        fIdleProcContext = context;
-    }
-    void* idleContext() const override { return fIdleProcContext; }
-
 protected:
     // constructor for subclasses
     GrMockTexture(GrMockGpu* gpu, const GrSurfaceDesc& desc, GrMipMapsStatus mipMapsStatus,
@@ -70,23 +64,8 @@
         return false;
     }
 
-    // protected so that GrMockTextureRenderTarget can call this to avoid "inheritance via
-    // dominance" warning.
-    void removedLastRefOrPendingIO() override {
-        if (fIdleProc) {
-            fIdleProc(fIdleProcContext);
-            fIdleProc = nullptr;
-            fIdleProcContext = nullptr;
-        }
-    }
-
 private:
-    void onSetRelease(sk_sp<GrReleaseProcHelper> releaseHelper) override {}
-
     GrMockTextureInfo fInfo;
-    sk_sp<GrReleaseProcHelper> fReleaseHelper;
-    IdleProc* fIdleProc = nullptr;
-    void* fIdleProcContext = nullptr;
 
     typedef GrTexture INHERITED;
 };
@@ -139,8 +118,6 @@
             : GrSurface(gpu, desc), INHERITED(gpu, desc), fInfo(info) {}
 
 private:
-    void onSetRelease(sk_sp<GrReleaseProcHelper> releaseHelper) override {}
-
     GrMockRenderTargetInfo fInfo;
 
     typedef GrRenderTarget INHERITED;
@@ -177,9 +154,11 @@
         return GrMockTexture::backendFormat();
     }
 
-private:
-    void onSetRelease(sk_sp<GrReleaseProcHelper> releaseHelper) override {}
+protected:
+    // This avoids an inherits via dominance warning on MSVC.
+    void willRemoveLastRefOrPendingIO() override { GrTexture::willRemoveLastRefOrPendingIO(); }
 
+private:
     void onAbandon() override {
         GrRenderTarget::onAbandon();
         GrMockTexture::onAbandon();
@@ -190,9 +169,6 @@
         GrMockTexture::onRelease();
     }
 
-    // We implement this to avoid the inheritance via dominance warning.
-    void removedLastRefOrPendingIO() override { GrMockTexture::removedLastRefOrPendingIO(); }
-
     size_t onGpuMemorySize() const override {
         int numColorSamples = this->numColorSamples();
         if (numColorSamples > 1) {
diff --git a/src/gpu/mtl/GrMtlBuffer.h b/src/gpu/mtl/GrMtlBuffer.h
index 63a924b..90e67fd 100644
--- a/src/gpu/mtl/GrMtlBuffer.h
+++ b/src/gpu/mtl/GrMtlBuffer.h
@@ -8,14 +8,14 @@
 #ifndef GrMtlBuffer_DEFINED
 #define GrMtlBuffer_DEFINED
 
-#include "GrBuffer.h"
+#include "GrGpuBuffer.h"
 
 #import <metal/metal.h>
 
 class GrMtlCaps;
 class GrMtlGpu;
 
-class GrMtlBuffer: public GrBuffer {
+class GrMtlBuffer: public GrGpuBuffer {
 public:
     static sk_sp<GrMtlBuffer> Make(GrMtlGpu*, size_t size, GrGpuBufferType intendedType,
                                    GrAccessPattern, const void* data = nullptr);
@@ -48,7 +48,7 @@
     id<MTLBuffer> fMtlBuffer;
     id<MTLBuffer> fMappedBuffer;
 
-    typedef GrBuffer INHERITED;
+    typedef GrGpuBuffer INHERITED;
 };
 
 #endif
diff --git a/src/gpu/mtl/GrMtlBuffer.mm b/src/gpu/mtl/GrMtlBuffer.mm
index be81357..1c45282 100644
--- a/src/gpu/mtl/GrMtlBuffer.mm
+++ b/src/gpu/mtl/GrMtlBuffer.mm
@@ -112,6 +112,7 @@
         fMappedBuffer = fMtlBuffer;
         fMapPtr = fMappedBuffer.contents;
     } else {
+        SK_BEGIN_AUTORELEASE_BLOCK
         // TODO: We can't ensure that map will only be called once on static access buffers until
         // we actually enable dynamic access.
         // SkASSERT(fMappedBuffer == nil);
@@ -123,6 +124,7 @@
                                                       options: MTLResourceStorageModeShared];
 #endif
         fMapPtr = fMappedBuffer.contents;
+        SK_END_AUTORELEASE_BLOCK
     }
     VALIDATE();
 }
@@ -145,6 +147,7 @@
     [fMappedBuffer didModifyRange: NSMakeRange(0, sizeInBytes)];
 #endif
     if (!fIsDynamic) {
+        SK_BEGIN_AUTORELEASE_BLOCK
         id<MTLBlitCommandEncoder> blitCmdEncoder =
                 [this->mtlGpu()->commandBuffer() blitCommandEncoder];
         [blitCmdEncoder copyFromBuffer: fMappedBuffer
@@ -153,6 +156,7 @@
                      destinationOffset: 0
                                   size: sizeInBytes];
         [blitCmdEncoder endEncoding];
+        SK_END_AUTORELEASE_BLOCK
     }
     fMappedBuffer = nil;
     fMapPtr = nullptr;
diff --git a/src/gpu/mtl/GrMtlCaps.h b/src/gpu/mtl/GrMtlCaps.h
index 2a96e4e..6ff4694 100644
--- a/src/gpu/mtl/GrMtlCaps.h
+++ b/src/gpu/mtl/GrMtlCaps.h
@@ -82,7 +82,7 @@
 
     void initConfigTable();
 
-    bool onSurfaceSupportsWritePixels(const GrSurface*) const override { return true; }
+    bool onSurfaceSupportsWritePixels(const GrSurface*) const override;
     bool onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
                           const SkIRect& srcRect, const SkIPoint& dstPoint) const override;
 
diff --git a/src/gpu/mtl/GrMtlCaps.mm b/src/gpu/mtl/GrMtlCaps.mm
index 0ef3031..0c9372f 100644
--- a/src/gpu/mtl/GrMtlCaps.mm
+++ b/src/gpu/mtl/GrMtlCaps.mm
@@ -9,6 +9,7 @@
 
 #include "GrBackendSurface.h"
 #include "GrMtlUtil.h"
+#include "GrRenderTarget.h"
 #include "GrRenderTargetProxy.h"
 #include "GrShaderCaps.h"
 #include "GrSurfaceProxy.h"
@@ -30,9 +31,7 @@
 
     // The following are disabled due to the unfinished Metal backend, not because Metal itself
     // doesn't support it.
-    fBlacklistCoverageCounting = true;   // CCPR shaders have some incompatabilities with SkSLC
     fFenceSyncSupport = false;           // Fences are not implemented yet
-    fMipMapSupport = false;              // GrMtlGpu::onRegenerateMipMapLevels() not implemented
     fMultisampleDisableSupport = true;   // MSAA and resolving not implemented yet
     fDiscardRenderTargetSupport = false; // GrMtlGpuCommandBuffer::discard() not implemented
     fCrossContextTextureSupport = false; // GrMtlGpu::prepareTextureForCrossContextUsage() not impl
@@ -304,6 +303,8 @@
         } else {
             if (kGray_8_GrPixelConfig == config) {
                 shaderCaps->fConfigTextureSwizzle[i] = GrSwizzle::RRRA();
+            } else if (kRGB_888X_GrPixelConfig == config || kRGB_888_GrPixelConfig == config ) {
+                shaderCaps->fConfigTextureSwizzle[i] = GrSwizzle::RGB1();
             } else {
                 shaderCaps->fConfigTextureSwizzle[i] = GrSwizzle::RGBA();
             }
@@ -358,10 +359,18 @@
     info = &fConfigTable[kAlpha_8_GrPixelConfig];
     info->fFlags = ConfigInfo::kAllFlags;
 
+    // Alpha_8_as_Red uses R8Unorm
+    info = &fConfigTable[kAlpha_8_as_Red_GrPixelConfig];
+    info->fFlags = ConfigInfo::kAllFlags;
+
     // Gray_8 uses R8Unorm
     info = &fConfigTable[kGray_8_GrPixelConfig];
     info->fFlags = ConfigInfo::kAllFlags;
 
+    // Gray_8_as_Red uses R8Unorm
+    info = &fConfigTable[kGray_8_as_Red_GrPixelConfig];
+    info->fFlags = ConfigInfo::kAllFlags;
+
     // RGB_565 uses B5G6R5Unorm, even though written opposite this format packs how we want
     info = &fConfigTable[kRGB_565_GrPixelConfig];
     if (this->isMac()) {
@@ -382,6 +391,18 @@
     info = &fConfigTable[kRGBA_8888_GrPixelConfig];
     info->fFlags = ConfigInfo::kAllFlags;
 
+    // RGB_888X uses RGBA8Unorm and we will swizzle the 1
+    info = &fConfigTable[kRGB_888X_GrPixelConfig];
+    info->fFlags = ConfigInfo::kTextureable_Flag;
+
+    // RGB_888 uses RGBA8Unorm and we will swizzle the 1
+    info = &fConfigTable[kRGB_888_GrPixelConfig];
+    info->fFlags = ConfigInfo::kTextureable_Flag;
+
+    // RG_88 uses RG8Unorm
+    info = &fConfigTable[kRG_88_GrPixelConfig];
+    info->fFlags = ConfigInfo::kTextureable_Flag;
+
     // BGRA_8888 uses BGRA8Unorm
     info = &fConfigTable[kBGRA_8888_GrPixelConfig];
     info->fFlags = ConfigInfo::kAllFlags;
@@ -407,7 +428,7 @@
     if (this->isMac()) {
         info->fFlags = ConfigInfo::kAllFlags;
     } else {
-        info->fFlags = ConfigInfo::kRenderable_Flag;
+        info->fFlags = ConfigInfo::kTextureable_Flag | ConfigInfo::kRenderable_Flag;
     }
 
     // Alpha_half uses R16Float
@@ -417,12 +438,22 @@
     // RGBA_half uses RGBA16Float
     info = &fConfigTable[kRGBA_half_GrPixelConfig];
     info->fFlags = ConfigInfo::kAllFlags;
+
+    info = &fConfigTable[kRGBA_half_Clamped_GrPixelConfig];
+    info->fFlags = ConfigInfo::kAllFlags;
 }
 
 void GrMtlCaps::initStencilFormat(id<MTLDevice> physDev) {
     fPreferredStencilFormat = StencilFormat{ MTLPixelFormatStencil8, 8, 8, true };
 }
 
+bool GrMtlCaps::onSurfaceSupportsWritePixels(const GrSurface* surface) const {
+    if (auto rt = surface->asRenderTarget()) {
+        return rt->numColorSamples() <= 1 && SkToBool(surface->asTexture());
+    }
+    return true;
+}
+
 GrPixelConfig validate_sized_format(GrMTLPixelFormat grFormat, SkColorType ct) {
     MTLPixelFormat format = static_cast<MTLPixelFormat>(grFormat);
     switch (ct) {
@@ -460,6 +491,9 @@
             }
             break;
         case kRGB_888x_SkColorType:
+            if (MTLPixelFormatRGBA8Unorm == format) {
+                return kRGB_888X_GrPixelConfig;
+            }
             break;
         case kBGRA_8888_SkColorType:
             if (MTLPixelFormatBGRA8Unorm == format) {
@@ -480,8 +514,13 @@
                 return kGray_8_as_Red_GrPixelConfig;
             }
             break;
+        case kRGBA_F16Norm_SkColorType:
+            if (MTLPixelFormatRGBA16Float == format) {
+                return kRGBA_half_Clamped_GrPixelConfig;
+            }
+            break;
         case kRGBA_F16_SkColorType:
-            if (MTLPixelFormatRG16Float == format) {
+            if (MTLPixelFormatRGBA16Float == format) {
                 return kRGBA_half_GrPixelConfig;
             }
             break;
@@ -525,7 +564,9 @@
         case MTLPixelFormatR8Unorm:
             return kAlpha_8_as_Red_GrPixelConfig;
             break;
-        // TODO: Add RG_88 format here
+        case MTLPixelFormatRG8Unorm:
+            return kRG_88_GrPixelConfig;
+            break;
         case MTLPixelFormatRGBA8Unorm:
             return kRGBA_8888_GrPixelConfig;
             break;
diff --git a/src/gpu/mtl/GrMtlCopyManager.mm b/src/gpu/mtl/GrMtlCopyManager.mm
index 2203c87..3cf1d11 100644
--- a/src/gpu/mtl/GrMtlCopyManager.mm
+++ b/src/gpu/mtl/GrMtlCopyManager.mm
@@ -188,7 +188,7 @@
         {sx1 - sx0, sy1 - sy0, sx0, sy0}, // texCoordXform
     };
 
-    MTLRenderPassDescriptor* renderPassDesc = [[MTLRenderPassDescriptor alloc] init];
+    MTLRenderPassDescriptor* renderPassDesc = [MTLRenderPassDescriptor renderPassDescriptor];
     renderPassDesc.colorAttachments[0].texture = dstTex;
     renderPassDesc.colorAttachments[0].slice = 0;
     renderPassDesc.colorAttachments[0].level = 0;
diff --git a/src/gpu/mtl/GrMtlCopyPipelineState.mm b/src/gpu/mtl/GrMtlCopyPipelineState.mm
index 27b52e6..6017d4b 100644
--- a/src/gpu/mtl/GrMtlCopyPipelineState.mm
+++ b/src/gpu/mtl/GrMtlCopyPipelineState.mm
@@ -16,7 +16,7 @@
         MTLVertexDescriptor* vertexDescriptor) {
 
     // Create pipeline state for copy as draw
-    MTLRenderPipelineDescriptor* pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
+    MTLRenderPipelineDescriptor* pipelineDescriptor = [MTLRenderPipelineDescriptor new];
     pipelineDescriptor.vertexFunction = vertexFunction;
     pipelineDescriptor.fragmentFunction = fragmentFunction;
     pipelineDescriptor.vertexDescriptor = vertexDescriptor;
diff --git a/src/gpu/mtl/GrMtlGpu.h b/src/gpu/mtl/GrMtlGpu.h
index d82a30a..18736eb 100644
--- a/src/gpu/mtl/GrMtlGpu.h
+++ b/src/gpu/mtl/GrMtlGpu.h
@@ -29,6 +29,10 @@
     class Compiler;
 }
 
+// Helper macros for autorelease pools
+#define SK_BEGIN_AUTORELEASE_BLOCK @autoreleasepool {
+#define SK_END_AUTORELEASE_BLOCK }
+
 class GrMtlGpu : public GrGpu {
 public:
     static sk_sp<GrGpu> Make(GrContext* context, const GrContextOptions& options,
@@ -40,10 +44,10 @@
 
     id<MTLDevice> device() const { return fDevice; }
 
-    id<MTLCommandBuffer> commandBuffer() const { return fCmdBuffer; }
-
     GrMtlResourceProvider& resourceProvider() { return fResourceProvider; }
 
+    id<MTLCommandBuffer> commandBuffer();
+
     enum SyncQueue {
         kForce_SyncQueue,
         kSkip_SyncQueue
@@ -54,7 +58,7 @@
     // command buffer to finish before creating a new buffer and returning.
     void submitCommandBuffer(SyncQueue sync);
 
-#ifdef GR_TEST_UTILS
+#if GR_TEST_UTILS
     GrBackendTexture createTestingOnlyBackendTexture(const void* pixels, int w, int h,
                                                      GrColorType colorType, bool isRT,
                                                      GrMipMapped, size_t rowBytes = 0) override;
@@ -127,6 +131,12 @@
 
     void onResetContext(uint32_t resetBits) override {}
 
+    void querySampleLocations(
+            GrRenderTarget*, const GrStencilSettings&, SkTArray<SkPoint>*) override {
+        SkASSERT(!this->caps()->sampleLocationsSupport());
+        SK_ABORT("Sample locations not yet implemented for Metal.");
+    }
+
     void xferBarrier(GrRenderTarget*, GrXferBarrierType) override {}
 
     sk_sp<GrTexture> onCreateTexture(const GrSurfaceDesc& desc, SkBudgeted budgeted,
@@ -143,7 +153,8 @@
     sk_sp<GrRenderTarget> onWrapBackendTextureAsRenderTarget(const GrBackendTexture&,
                                                              int sampleCnt) override;
 
-    sk_sp<GrBuffer> onCreateBuffer(size_t, GrGpuBufferType, GrAccessPattern, const void*) override;
+    sk_sp<GrGpuBuffer> onCreateBuffer(size_t, GrGpuBufferType, GrAccessPattern,
+                                      const void*) override;
 
     bool onReadPixels(GrSurface* surface, int left, int top, int width, int height, GrColorType,
                       void* buffer, size_t rowBytes) override;
@@ -151,24 +162,39 @@
     bool onWritePixels(GrSurface*, int left, int top, int width, int height, GrColorType,
                        const GrMipLevel[], int mipLevelCount) override;
 
-    bool onTransferPixels(GrTexture*,
-                          int left, int top, int width, int height,
-                          GrColorType, GrBuffer*,
-                          size_t offset, size_t rowBytes) override {
+    bool onTransferPixelsTo(GrTexture*,
+                            int left, int top, int width, int height,
+                            GrColorType, GrGpuBuffer*,
+                            size_t offset, size_t rowBytes) override {
+        // TODO: not sure this is worth the work since nobody uses it
         return false;
     }
+    size_t onTransferPixelsFrom(GrSurface*,
+                                int left, int top, int width, int height,
+                                GrColorType, GrGpuBuffer*,
+                                size_t offset) override {
+        // TODO: Will need to implement this to support async read backs.
+        return 0;
+    }
 
-    bool onRegenerateMipMapLevels(GrTexture*) override { return false; }
+    bool onRegenerateMipMapLevels(GrTexture*) override;
 
     void onResolveRenderTarget(GrRenderTarget* target) override { return; }
 
-    void onFinishFlush(bool insertedSemaphores) override {
-        this->submitCommandBuffer(kSkip_SyncQueue);
+    void onFinishFlush(GrSurfaceProxy*, SkSurface::BackendSurfaceAccess access,
+                       GrFlushFlags flags, bool insertedSemaphores) override {
+        if (flags & kSyncCpu_GrFlushFlag) {
+            this->submitCommandBuffer(kForce_SyncQueue);
+        } else {
+            this->submitCommandBuffer(kSkip_SyncQueue);
+        }
     }
 
     // Function that uploads data onto textures with private storage mode (GPU access only).
     bool uploadToTexture(GrMtlTexture* tex, int left, int top, int width, int height,
                          GrColorType dataColorType, const GrMipLevel texels[], int mipLevels);
+    // Function that fills texture with transparent black
+    bool clearTexture(GrMtlTexture*, GrColorType);
 
     GrStencilAttachment* createStencilAttachmentForRenderTarget(const GrRenderTarget*,
                                                                 int width,
diff --git a/src/gpu/mtl/GrMtlGpu.mm b/src/gpu/mtl/GrMtlGpu.mm
index 833dfdb0..b16ad61 100644
--- a/src/gpu/mtl/GrMtlGpu.mm
+++ b/src/gpu/mtl/GrMtlGpu.mm
@@ -96,21 +96,21 @@
         : INHERITED(context)
         , fDevice(device)
         , fQueue(queue)
+        , fCmdBuffer(nil)
         , fCompiler(new SkSL::Compiler())
         , fCopyManager(this)
         , fResourceProvider(this) {
-
     fMtlCaps.reset(new GrMtlCaps(options, fDevice, featureSet));
     fCaps = fMtlCaps;
-
-    fCmdBuffer = [fQueue commandBuffer];
 }
 
 GrGpuRTCommandBuffer* GrMtlGpu::getCommandBuffer(
             GrRenderTarget* renderTarget, GrSurfaceOrigin origin, const SkRect& bounds,
             const GrGpuRTCommandBuffer::LoadAndStoreInfo& colorInfo,
             const GrGpuRTCommandBuffer::StencilLoadAndStoreInfo& stencilInfo) {
+    SK_BEGIN_AUTORELEASE_BLOCK
     return new GrMtlGpuRTCommandBuffer(this, renderTarget, origin, bounds, colorInfo, stencilInfo);
+    SK_END_AUTORELEASE_BLOCK
 }
 
 GrGpuTextureCommandBuffer* GrMtlGpu::getCommandBuffer(GrTexture* texture,
@@ -119,20 +119,38 @@
 }
 
 void GrMtlGpu::submit(GrGpuCommandBuffer* buffer) {
+    GrMtlGpuRTCommandBuffer* mtlRTCmdBuffer =
+            reinterpret_cast<GrMtlGpuRTCommandBuffer*>(buffer->asRTCommandBuffer());
+    if (mtlRTCmdBuffer) {
+        mtlRTCmdBuffer->submit();
+    }
     delete buffer;
 }
 
+id<MTLCommandBuffer> GrMtlGpu::commandBuffer() {
+    if (!fCmdBuffer) {
+        SK_BEGIN_AUTORELEASE_BLOCK
+        fCmdBuffer = [fQueue commandBuffer];
+        SK_END_AUTORELEASE_BLOCK
+    }
+    return fCmdBuffer;
+}
+
 void GrMtlGpu::submitCommandBuffer(SyncQueue sync) {
-    SkASSERT(fCmdBuffer);
     [fCmdBuffer commit];
     if (SyncQueue::kForce_SyncQueue == sync) {
         [fCmdBuffer waitUntilCompleted];
     }
-    fCmdBuffer = [fQueue commandBuffer];
+    if (MTLCommandBufferStatusError == fCmdBuffer.status) {
+        NSString* description = fCmdBuffer.error.localizedDescription;
+        const char* errorString = [description UTF8String];
+        SkDebugf("Error submitting command buffer: %s\n", errorString);
+    }
+    fCmdBuffer = nil;
 }
 
-sk_sp<GrBuffer> GrMtlGpu::onCreateBuffer(size_t size, GrGpuBufferType type,
-                                         GrAccessPattern accessPattern, const void* data) {
+sk_sp<GrGpuBuffer> GrMtlGpu::onCreateBuffer(size_t size, GrGpuBufferType type,
+                                            GrAccessPattern accessPattern, const void* data) {
     return GrMtlBuffer::Make(this, size, type, accessPattern, data);
 }
 
@@ -148,6 +166,18 @@
                                GrColorType dataColorType, const GrMipLevel texels[],
                                int mipLevelCount) {
     SkASSERT(this->caps()->isConfigTexturable(tex->config()));
+    // The assumption is either that we have no mipmaps, or that our rect is the entire texture
+    SkASSERT(1 == mipLevelCount ||
+             (0 == left && 0 == top && width == tex->width() && height == tex->height()));
+
+    // We assume that if the texture has mip levels, we either upload to all the levels or just the
+    // first.
+    SkASSERT(1 == mipLevelCount || mipLevelCount == (tex->texturePriv().maxMipMapLevel() + 1));
+
+    // If we're uploading compressed data then we should be using uploadCompressedTexData
+    SkASSERT(!GrPixelConfigIsCompressed(GrColorTypeToPixelConfig(dataColorType,
+                                                                 GrSRGBEncoded::kNo)));
+
     if (!check_max_blit_width(width)) {
         return false;
     }
@@ -163,48 +193,181 @@
     // Either upload only the first miplevel or all miplevels
     SkASSERT(1 == mipLevelCount || mipLevelCount == (int)mtlTexture.mipmapLevelCount);
 
-    MTLTextureDescriptor* transferDesc = GrGetMTLTextureDescriptor(mtlTexture);
-    transferDesc.mipmapLevelCount = mipLevelCount;
-    transferDesc.cpuCacheMode = MTLCPUCacheModeWriteCombined;
-#ifdef SK_BUILD_FOR_MAC
-    transferDesc.storageMode = MTLStorageModeManaged;
-#else
-    transferDesc.storageMode = MTLStorageModeShared;
-#endif
-    // TODO: implement some way of reusing transfer textures
-    id<MTLTexture> transferTexture = [fDevice newTextureWithDescriptor:transferDesc];
-    SkASSERT(transferTexture);
+    // TODO: implement some way of reusing transfer buffers?
+    size_t bpp = GrColorTypeBytesPerPixel(dataColorType);
 
+    SkTArray<size_t> individualMipOffsets(mipLevelCount);
+    individualMipOffsets.push_back(0);
+    size_t combinedBufferSize = width * bpp * height;
     int currentWidth = width;
     int currentHeight = height;
-    size_t bpp = GrColorTypeBytesPerPixel(dataColorType);
+    if (!texels[0].fPixels) {
+        combinedBufferSize = 0;
+    }
+
+    // The alignment must be at least 4 bytes and a multiple of the bytes per pixel of the image
+    // config. This works with the assumption that the bytes in pixel config is always a power of 2.
+    SkASSERT((bpp & (bpp - 1)) == 0);
+    const size_t alignmentMask = 0x3 | (bpp - 1);
+    for (int currentMipLevel = 1; currentMipLevel < mipLevelCount; currentMipLevel++) {
+        currentWidth = SkTMax(1, currentWidth/2);
+        currentHeight = SkTMax(1, currentHeight/2);
+
+        if (texels[currentMipLevel].fPixels) {
+            const size_t trimmedSize = currentWidth * bpp * currentHeight;
+            const size_t alignmentDiff = combinedBufferSize & alignmentMask;
+            if (alignmentDiff != 0) {
+                combinedBufferSize += alignmentMask - alignmentDiff + 1;
+            }
+            individualMipOffsets.push_back(combinedBufferSize);
+            combinedBufferSize += trimmedSize;
+        } else {
+            individualMipOffsets.push_back(0);
+        }
+    }
+    if (0 == combinedBufferSize) {
+        // We don't actually have any data to upload so just return success
+        return true;
+    }
+
+    NSUInteger options = 0;
+#ifdef SK_BUILD_FOR_MAC
+    options |= MTLResourceStorageModeManaged;
+#else
+    options |= MTLResourceStorageModeShared;
+#endif
+    // TODO: Create GrMtlTransferBuffer
+    id<MTLBuffer> transferBuffer = [fDevice newBufferWithLength: combinedBufferSize
+                                                        options: options];
+    if (nil == transferBuffer) {
+        return false;
+    }
+
+    char* buffer = (char*) transferBuffer.contents;
+
+    currentWidth = width;
+    currentHeight = height;
+    int layerHeight = tex->height();
     MTLOrigin origin = MTLOriginMake(left, top, 0);
 
-    SkASSERT(mtlTexture.pixelFormat == transferTexture.pixelFormat);
-    SkASSERT(mtlTexture.sampleCount == transferTexture.sampleCount);
-
-    id<MTLBlitCommandEncoder> blitCmdEncoder = [fCmdBuffer blitCommandEncoder];
+    id<MTLBlitCommandEncoder> blitCmdEncoder = [this->commandBuffer() blitCommandEncoder];
     for (int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) {
-        size_t rowBytes = texels[currentMipLevel].fRowBytes ? texels[currentMipLevel].fRowBytes
-                                                            : bpp * currentWidth;
-        SkASSERT(texels[currentMipLevel].fPixels);
-        if (rowBytes < bpp * currentWidth || rowBytes % bpp) {
-            return false;
-        }
-        [transferTexture replaceRegion: MTLRegionMake2D(left, top, width, height)
-                           mipmapLevel: currentMipLevel
-                             withBytes: texels[currentMipLevel].fPixels
-                           bytesPerRow: rowBytes];
+        if (texels[currentMipLevel].fPixels) {
+            SkASSERT(1 == mipLevelCount || currentHeight == layerHeight);
+            const size_t trimRowBytes = currentWidth * bpp;
+            const size_t rowBytes = texels[currentMipLevel].fRowBytes
+                                    ? texels[currentMipLevel].fRowBytes
+                                    : trimRowBytes;
 
-        [blitCmdEncoder copyFromTexture: transferTexture
-                            sourceSlice: 0
-                            sourceLevel: currentMipLevel
-                           sourceOrigin: origin
-                             sourceSize: MTLSizeMake(width, height, 1)
-                              toTexture: mtlTexture
-                       destinationSlice: 0
-                       destinationLevel: currentMipLevel
-                      destinationOrigin: origin];
+            // copy data into the buffer, skipping the trailing bytes
+            char* dst = buffer + individualMipOffsets[currentMipLevel];
+            const char* src = (const char*)texels[currentMipLevel].fPixels;
+            SkRectMemcpy(dst, trimRowBytes, src, rowBytes, trimRowBytes, currentHeight);
+
+            [blitCmdEncoder copyFromBuffer: transferBuffer
+                              sourceOffset: individualMipOffsets[currentMipLevel]
+                         sourceBytesPerRow: trimRowBytes
+                       sourceBytesPerImage: trimRowBytes*currentHeight
+                                sourceSize: MTLSizeMake(currentWidth, currentHeight, 1)
+                                 toTexture: mtlTexture
+                          destinationSlice: 0
+                          destinationLevel: currentMipLevel
+                         destinationOrigin: origin];
+        }
+        currentWidth = SkTMax(1, currentWidth/2);
+        currentHeight = SkTMax(1, currentHeight/2);
+        layerHeight = currentHeight;
+    }
+    [blitCmdEncoder endEncoding];
+#ifdef SK_BUILD_FOR_MAC
+    // for Managed resources, need to tell driver to sync CPU and GPU copies
+    [transferBuffer didModifyRange: NSMakeRange(0, combinedBufferSize)];
+#endif
+
+    if (mipLevelCount < (int) tex->mtlTexture().mipmapLevelCount) {
+        tex->texturePriv().markMipMapsDirty();
+    }
+
+    return true;
+}
+
+bool GrMtlGpu::clearTexture(GrMtlTexture* tex, GrColorType dataColorType) {
+    SkASSERT(this->caps()->isConfigTexturable(tex->config()));
+
+    // If we're uploading compressed data then we should be using uploadCompressedTexData
+    SkASSERT(!GrPixelConfigIsCompressed(GrColorTypeToPixelConfig(dataColorType,
+                                                                 GrSRGBEncoded::kNo)));
+
+    id<MTLTexture> mtlTexture = tex->mtlTexture();
+    SkASSERT(mtlTexture);
+    // Either upload only the first miplevel or all miplevels
+    int mipLevelCount = (int)mtlTexture.mipmapLevelCount;
+
+    // TODO: implement some way of reusing transfer buffers?
+    size_t bpp = GrColorTypeBytesPerPixel(dataColorType);
+
+    SkTArray<size_t> individualMipOffsets(mipLevelCount);
+    individualMipOffsets.push_back(0);
+    int width = tex->width();
+    int height = tex->height();
+    size_t combinedBufferSize = width * bpp * height;
+    int currentWidth = width;
+    int currentHeight = height;
+
+    // The alignment must be at least 4 bytes and a multiple of the bytes per pixel of the image
+    // config. This works with the assumption that the bytes in pixel config is always a power of 2.
+    // TODO: can we just copy from a single buffer the size of the top level w/o a perf penalty?
+    SkASSERT((bpp & (bpp - 1)) == 0);
+    const size_t alignmentMask = 0x3 | (bpp - 1);
+    for (int currentMipLevel = 1; currentMipLevel < mipLevelCount; currentMipLevel++) {
+        currentWidth = SkTMax(1, currentWidth/2);
+        currentHeight = SkTMax(1, currentHeight/2);
+
+        const size_t trimmedSize = currentWidth * bpp * currentHeight;
+        const size_t alignmentDiff = combinedBufferSize & alignmentMask;
+        if (alignmentDiff != 0) {
+            combinedBufferSize += alignmentMask - alignmentDiff + 1;
+        }
+        individualMipOffsets.push_back(combinedBufferSize);
+        combinedBufferSize += trimmedSize;
+    }
+    if (0 == combinedBufferSize) {
+        // We don't actually have any data to upload so just return success
+        return true;
+    }
+
+    // TODO: Create GrMtlTransferBuffer
+    id<MTLBuffer> transferBuffer = [fDevice newBufferWithLength: combinedBufferSize
+                                                        options: MTLResourceStorageModePrivate];
+    if (nil == transferBuffer) {
+        return false;
+    }
+
+    id<MTLBlitCommandEncoder> blitCmdEncoder = [this->commandBuffer() blitCommandEncoder];
+    // clear the buffer to transparent black
+    NSRange clearRange;
+    clearRange.location = 0;
+    clearRange.length = combinedBufferSize;
+    [blitCmdEncoder fillBuffer: transferBuffer
+                         range: clearRange
+                         value: 0];
+
+    // now copy buffer to texture
+    currentWidth = width;
+    currentHeight = height;
+    MTLOrigin origin = MTLOriginMake(0, 0, 0);
+    for (int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) {
+        const size_t rowBytes = currentWidth * bpp;
+
+        [blitCmdEncoder copyFromBuffer: transferBuffer
+                          sourceOffset: individualMipOffsets[currentMipLevel]
+                     sourceBytesPerRow: rowBytes
+                   sourceBytesPerImage: rowBytes*currentHeight
+                            sourceSize: MTLSizeMake(currentWidth, currentHeight, 1)
+                             toTexture: mtlTexture
+                      destinationSlice: 0
+                      destinationLevel: currentMipLevel
+                     destinationOrigin: origin];
         currentWidth = SkTMax(1, currentWidth/2);
         currentHeight = SkTMax(1, currentHeight/2);
     }
@@ -213,6 +376,7 @@
     if (mipLevelCount < (int) tex->mtlTexture().mipmapLevelCount) {
         tex->texturePriv().markMipMapsDirty();
     }
+
     return true;
 }
 
@@ -226,6 +390,7 @@
 
     const GrMtlCaps::StencilFormat& sFmt = this->mtlCaps().preferredStencilFormat();
 
+    SK_BEGIN_AUTORELEASE_BLOCK
     GrMtlStencilAttachment* stencil(GrMtlStencilAttachment::Create(this,
                                                                    width,
                                                                    height,
@@ -233,6 +398,7 @@
                                                                    sFmt));
     fStats.incStencilAttachmentCreates();
     return stencil;
+    SK_END_AUTORELEASE_BLOCK
 }
 
 sk_sp<GrTexture> GrMtlGpu::onCreateTexture(const GrSurfaceDesc& desc, SkBudgeted budgeted,
@@ -253,9 +419,11 @@
 
     bool renderTarget = SkToBool(desc.fFlags & kRenderTarget_GrSurfaceFlag);
 
+    sk_sp<GrMtlTexture> tex;
+    SK_BEGIN_AUTORELEASE_BLOCK
     // This TexDesc refers to the texture that will be read by the client. Thus even if msaa is
-    // requested, this TexDesc describes the resolved texture. Therefore we always have samples set
-    // to 1.
+    // requested, this TexDesc describes the resolved texture. Therefore we always have samples
+    // set to 1.
     MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init];
     texDesc.textureType = MTLTextureType2D;
     texDesc.pixelFormat = format;
@@ -274,18 +442,15 @@
 
     GrMipMapsStatus mipMapsStatus = GrMipMapsStatus::kNotAllocated;
     if (mipLevels > 1) {
-        mipMapsStatus = texels[0].fPixels ? GrMipMapsStatus::kValid : GrMipMapsStatus::kDirty;
-#ifdef SK_DEBUG
-        for (int i = 1; i < mipLevels; ++i) {
-            if (mipMapsStatus == GrMipMapsStatus::kValid) {
-                SkASSERT(texels[i].fPixels);
-            } else {
-                SkASSERT(!texels[i].fPixels);
+        mipMapsStatus = GrMipMapsStatus::kValid;
+        for (int i = 0; i < mipLevels; ++i) {
+            if (!texels[i].fPixels) {
+                mipMapsStatus = GrMipMapsStatus::kDirty;
+                break;
             }
         }
-#endif
     }
-    sk_sp<GrMtlTexture> tex;
+
     if (renderTarget) {
         tex = GrMtlTextureRenderTarget::CreateNewTextureRenderTarget(this, budgeted,
                                                                      desc, texDesc, mipMapsStatus);
@@ -307,8 +472,10 @@
     }
 
     if (desc.fFlags & kPerformInitialClear_GrSurfaceFlag) {
-        // Do initial clear of the texture
+        this->clearTexture(tex.get(), colorType);
     }
+    SK_END_AUTORELEASE_BLOCK
+
     return std::move(tex);
 }
 
@@ -409,7 +576,25 @@
     return GrMtlRenderTarget::MakeWrappedRenderTarget(this, surfDesc, mtlTexture);
 }
 
-#ifdef GR_TEST_UTILS
+bool GrMtlGpu::onRegenerateMipMapLevels(GrTexture* texture) {
+    GrMtlTexture* grMtlTexture = static_cast<GrMtlTexture*>(texture);
+    id<MTLTexture> mtlTexture = grMtlTexture->mtlTexture();
+
+    // Automatic mipmap generation is only supported by color-renderable formats
+    if (!fMtlCaps->isConfigRenderable(texture->config()) &&
+        // We have pixel configs marked as textureable-only that use RGBA8 as the internal format
+        MTLPixelFormatRGBA8Unorm != mtlTexture.pixelFormat) {
+        return false;
+    }
+
+    id<MTLBlitCommandEncoder> blitCmdEncoder = [this->commandBuffer() blitCommandEncoder];
+    [blitCmdEncoder generateMipmapsForTexture: mtlTexture];
+    [blitCmdEncoder endEncoding];
+
+    return true;
+}
+
+#if GR_TEST_UTILS
 bool GrMtlGpu::createTestingOnlyMtlTextureInfo(GrColorType colorType, int w, int h, bool texturable,
                                                bool renderable, GrMipMapped mipMapped,
                                                const void* srcData, size_t srcRowBytes,
@@ -440,6 +625,7 @@
         return false;
     }
 
+    SK_BEGIN_AUTORELEASE_BLOCK
     bool mipmapped = mipMapped == GrMipMapped::kYes ? true : false;
     MTLTextureDescriptor* desc =
             [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: format
@@ -452,56 +638,59 @@
     desc.usage |= renderable ? MTLTextureUsageRenderTarget : 0;
     id<MTLTexture> testTexture = [fDevice newTextureWithDescriptor: desc];
 
-    SkAutoTMalloc<GrColor> srcBuffer;
-    if (!srcData) {
-        srcBuffer.reset(w * h);
-        memset(srcBuffer, 0, w * h * sizeof(GrColor));
-        srcData = srcBuffer;
-    }
-    SkASSERT(srcData);
-#ifdef SK_BUILD_FOR_MAC
-    desc.storageMode = MTLStorageModeManaged;
-#else
-    desc.storageMode = MTLStorageModeShared;
-#endif
-    id<MTLTexture> transferTexture = [fDevice newTextureWithDescriptor: desc];
-    size_t trimRowBytes = w * GrColorTypeBytesPerPixel(colorType);
+    size_t bpp = GrColorTypeBytesPerPixel(colorType);
     if (!srcRowBytes) {
-        srcRowBytes = trimRowBytes;
+        srcRowBytes = w * bpp;
+#ifdef SK_BUILD_FOR_MAC
+        // On MacOS, the fillBuffer command needs a range with a multiple of 4 bytes
+        srcRowBytes = ((srcRowBytes + 3) & (~3));
+#endif
     }
+    size_t bufferSize = srcRowBytes * h;
+    NSUInteger options = 0;  // TODO: consider other options here
+#ifdef SK_BUILD_FOR_MAC
+    options |= MTLResourceStorageModeManaged;
+#else
+    options |= MTLResourceStorageModeShared;
+#endif
 
-    MTLOrigin origin = MTLOriginMake(0, 0, 0);
-
-    SkASSERT(testTexture.pixelFormat == transferTexture.pixelFormat);
-    SkASSERT(testTexture.sampleCount == transferTexture.sampleCount);
+    // TODO: Create GrMtlTransferBuffer
+    id<MTLBuffer> transferBuffer;
+    if (srcData) {
+        transferBuffer = [fDevice newBufferWithBytes: srcData
+                                              length: bufferSize
+                                             options: options];
+    } else {
+        transferBuffer = [fDevice newBufferWithLength: bufferSize
+                                             options: options];
+    }
+    if (nil == transferBuffer) {
+        return false;
+    }
 
     id<MTLCommandBuffer> cmdBuffer = [fQueue commandBuffer];
     id<MTLBlitCommandEncoder> blitCmdEncoder = [cmdBuffer blitCommandEncoder];
-    int currentWidth = w;
-    int currentHeight = h;
-    for (int mipLevel = 0; mipLevel < (int)testTexture.mipmapLevelCount; mipLevel++) {
-        [transferTexture replaceRegion: MTLRegionMake2D(0, 0, currentWidth, currentHeight)
-                           mipmapLevel: mipLevel
-                             withBytes: srcData
-                           bytesPerRow: srcRowBytes];
-
-        [blitCmdEncoder copyFromTexture: transferTexture
-                            sourceSlice: 0
-                            sourceLevel: mipLevel
-                           sourceOrigin: origin
-                             sourceSize: MTLSizeMake(currentWidth, currentHeight, 1)
-                              toTexture: testTexture
-                       destinationSlice: 0
-                       destinationLevel: mipLevel
-                      destinationOrigin: origin];
-        currentWidth = SkTMax(1, currentWidth/2);
-        currentHeight = SkTMax(1, currentHeight/2);
+    if (!srcData) {
+        [blitCmdEncoder fillBuffer: transferBuffer
+                             range: NSMakeRange(0, bufferSize)
+                             value: 0];
     }
+    [blitCmdEncoder copyFromBuffer: transferBuffer
+                      sourceOffset: 0
+                 sourceBytesPerRow: srcRowBytes
+               sourceBytesPerImage: bufferSize
+                        sourceSize: MTLSizeMake(w, h, 1)
+                         toTexture: testTexture
+                  destinationSlice: 0
+                  destinationLevel: 0
+                 destinationOrigin: MTLOriginMake(0, 0, 0)];
     [blitCmdEncoder endEncoding];
     [cmdBuffer commit];
     [cmdBuffer waitUntilCompleted];
 
     info->fTexture = GrReleaseId(testTexture);
+    SK_END_AUTORELEASE_BLOCK
+
     return true;
 }
 
@@ -626,7 +815,7 @@
     }
     dstRect.fBottom = dstRect.fTop + srcMtlRect.height();
 
-    id<MTLBlitCommandEncoder> blitCmdEncoder = [fCmdBuffer blitCommandEncoder];
+    id<MTLBlitCommandEncoder> blitCmdEncoder = [this->commandBuffer() blitCommandEncoder];
     [blitCmdEncoder copyFromTexture: srcTex
                         sourceSlice: 0
                         sourceLevel: 0
@@ -723,6 +912,7 @@
         return false;
     }
 
+    SK_BEGIN_AUTORELEASE_BLOCK
     bool success = false;
     if (this->mtlCaps().canCopyAsDraw(dst->config(), SkToBool(dst->asRenderTarget()),
                                       src->config(), SkToBool(src->asTexture()))) {
@@ -743,12 +933,15 @@
         this->didWriteToSurface(dst, dstOrigin, &dstRect);
     }
     return success;
+    SK_END_AUTORELEASE_BLOCK
 }
 
 bool GrMtlGpu::onWritePixels(GrSurface* surface, int left, int top, int width, int height,
                              GrColorType srcColorType, const GrMipLevel texels[],
                              int mipLevelCount) {
     GrMtlTexture* mtlTexture = static_cast<GrMtlTexture*>(surface->asTexture());
+    // TODO: In principle we should be able to support pure rendertargets as well, but
+    // until we find a use case we'll only support texture rendertargets.
     if (!mtlTexture) {
         return false;
     }
@@ -760,8 +953,10 @@
         SkASSERT(texels[i].fPixels);
     }
 #endif
+    SK_BEGIN_AUTORELEASE_BLOCK
     return this->uploadToTexture(mtlTexture, left, top, width, height, srcColorType, texels,
                                  mipLevelCount);
+    SK_END_AUTORELEASE_BLOCK
 }
 
 bool GrMtlGpu::onReadPixels(GrSurface* surface, int left, int top, int width, int height,
@@ -774,21 +969,29 @@
         return false;
     }
 
+    id<MTLBuffer> transferBuffer;
+    int bpp = GrColorTypeBytesPerPixel(dstColorType);
+    size_t transBufferRowBytes = bpp * width;
+    SK_BEGIN_AUTORELEASE_BLOCK
     bool doResolve = get_surface_sample_cnt(surface) > 1;
     id<MTLTexture> mtlTexture = GrGetMTLTextureFromSurface(surface, doResolve);
-    if (!mtlTexture) {
+    if (!mtlTexture || [mtlTexture isFramebufferOnly]) {
         return false;
     }
 
-    int bpp = GrColorTypeBytesPerPixel(dstColorType);
-    size_t transBufferRowBytes = bpp * width;
     size_t transBufferImageBytes = transBufferRowBytes * height;
 
     // TODO: implement some way of reusing buffers instead of making a new one every time.
-    id<MTLBuffer> transferBuffer = [fDevice newBufferWithLength: transBufferImageBytes
-                                                        options: MTLResourceStorageModeShared];
+    NSUInteger options = 0;
+#ifdef SK_BUILD_FOR_MAC
+    options |= MTLResourceStorageModeManaged;
+#else
+    options |= MTLResourceStorageModeShared;
+#endif
+    transferBuffer = [fDevice newBufferWithLength: transBufferImageBytes
+                                                        options: options];
 
-    id<MTLBlitCommandEncoder> blitCmdEncoder = [fCmdBuffer blitCommandEncoder];
+    id<MTLBlitCommandEncoder> blitCmdEncoder = [this->commandBuffer() blitCommandEncoder];
     [blitCmdEncoder copyFromTexture: mtlTexture
                         sourceSlice: 0
                         sourceLevel: 0
@@ -798,12 +1001,19 @@
                   destinationOffset: 0
              destinationBytesPerRow: transBufferRowBytes
            destinationBytesPerImage: transBufferImageBytes];
+#ifdef SK_BUILD_FOR_MAC
+    // Sync GPU data back to the CPU
+    [blitCmdEncoder synchronizeResource: transferBuffer];
+#endif
     [blitCmdEncoder endEncoding];
+    SK_END_AUTORELEASE_BLOCK
 
     this->submitCommandBuffer(kForce_SyncQueue);
     const void* mappedMemory = transferBuffer.contents;
 
     SkRectMemcpy(buffer, rowBytes, mappedMemory, transBufferRowBytes, transBufferRowBytes, height);
+
     return true;
+
 }
 
diff --git a/src/gpu/mtl/GrMtlGpuCommandBuffer.h b/src/gpu/mtl/GrMtlGpuCommandBuffer.h
index 1505789..f673354 100644
--- a/src/gpu/mtl/GrMtlGpuCommandBuffer.h
+++ b/src/gpu/mtl/GrMtlGpuCommandBuffer.h
@@ -64,8 +64,7 @@
     void submit();
 
 private:
-    void internalBegin();
-    void internalEnd();
+    void addNullCommand();
 
     GrGpu* gpu() override { return fGpu; }
 
@@ -73,8 +72,7 @@
             const GrPrimitiveProcessor& primProc,
             const GrPipeline& pipeline,
             const GrPipeline::FixedDynamicState* fixedDynamicState,
-            const GrMesh meshes[],
-            int meshCount);
+            GrPrimitiveType primType);
 
     void onDraw(const GrPrimitiveProcessor& primProc,
                 const GrPipeline& pipeline,
@@ -128,7 +126,7 @@
     GrGpuRTCommandBuffer::StencilLoadAndStoreInfo fStencilLoadAndStoreInfo;
 
     id<MTLRenderCommandEncoder> fActiveRenderCmdEncoder;
-    MTLRenderPassDescriptor* fRenderPassDesc;
+    MTLRenderPassDescriptor*    fRenderPassDesc;
 
     struct CommandBufferInfo {
         SkRect fBounds;
diff --git a/src/gpu/mtl/GrMtlGpuCommandBuffer.mm b/src/gpu/mtl/GrMtlGpuCommandBuffer.mm
index 7df017c..1849673 100644
--- a/src/gpu/mtl/GrMtlGpuCommandBuffer.mm
+++ b/src/gpu/mtl/GrMtlGpuCommandBuffer.mm
@@ -13,6 +13,7 @@
 #include "GrMtlPipelineStateBuilder.h"
 #include "GrMtlRenderTarget.h"
 #include "GrRenderTargetPriv.h"
+#include "GrTexturePriv.h"
 
 GrMtlGpuRTCommandBuffer::GrMtlGpuRTCommandBuffer(
         GrMtlGpu* gpu, GrRenderTarget* rt, GrSurfaceOrigin origin, const SkRect& bounds,
@@ -35,8 +36,7 @@
     if (fColorLoadAndStoreInfo.fLoadOp == GrLoadOp::kClear) {
         fCommandBufferInfo.fBounds = SkRect::MakeWH(fRenderTarget->width(),
                                                     fRenderTarget->height());
-        this->internalBegin();
-        this->internalEnd();
+        this->addNullCommand();
         fRenderPassDesc.colorAttachments[0].loadAction = MTLLoadActionLoad;
     } else {
         fCommandBufferInfo.fBounds.setEmpty();
@@ -49,8 +49,7 @@
             fCommandBufferInfo.fBounds = SkRect::MakeWH(fRenderTarget->width(),
                                                         fRenderTarget->height());
             fRenderPassDesc.stencilAttachment.loadAction = MTLLoadActionClear;
-            this->internalBegin();
-            this->internalEnd();
+            this->addNullCommand();
             fRenderPassDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
             break;
         case GrLoadOp::kDiscard:
@@ -68,23 +67,17 @@
 }
 
 GrMtlGpuRTCommandBuffer::~GrMtlGpuRTCommandBuffer() {
-    SkASSERT(fActiveRenderCmdEncoder == nil);
+    SkASSERT(nil == fActiveRenderCmdEncoder);
 }
 
-void GrMtlGpuRTCommandBuffer::internalBegin() {
-    SkASSERT(fActiveRenderCmdEncoder == nil);
-    fActiveRenderCmdEncoder =
-            [fGpu->commandBuffer()
-                    renderCommandEncoderWithDescriptor: fRenderPassDesc];
-    SkASSERT(fActiveRenderCmdEncoder);
-    [fActiveRenderCmdEncoder setFrontFacingWinding: MTLWindingCounterClockwise];
-}
-
-void GrMtlGpuRTCommandBuffer::internalEnd() {
-    SkASSERT(fActiveRenderCmdEncoder);
-    [fActiveRenderCmdEncoder endEncoding];
-    fActiveRenderCmdEncoder = nil;
-    SkASSERT(fActiveRenderCmdEncoder == nil);
+void GrMtlGpuRTCommandBuffer::addNullCommand() {
+    SkASSERT(nil == fActiveRenderCmdEncoder);
+    SK_BEGIN_AUTORELEASE_BLOCK
+    id<MTLRenderCommandEncoder> cmdEncoder =
+            [fGpu->commandBuffer() renderCommandEncoderWithDescriptor:fRenderPassDesc];
+    SkASSERT(nil != cmdEncoder);
+    [cmdEncoder endEncoding];
+    SK_END_AUTORELEASE_BLOCK
 }
 
 void GrMtlGpuRTCommandBuffer::submit() {
@@ -100,7 +93,7 @@
                                    const SkIRect& srcRect, const SkIPoint& dstPoint) {
     // We cannot have an active encoder when we call copy since it requires its own
     // command encoder.
-    SkASSERT(fActiveRenderCmdEncoder == nil);
+    SkASSERT(nil == fActiveRenderCmdEncoder);
     fGpu->copySurface(fRenderTarget, fOrigin, src, srcOrigin, srcRect, dstPoint);
 }
 
@@ -108,21 +101,8 @@
         const GrPrimitiveProcessor& primProc,
         const GrPipeline& pipeline,
         const GrPipeline::FixedDynamicState* fixedDynamicState,
-        const GrMesh meshes[],
-        int meshCount) {
+        GrPrimitiveType primType) {
     // TODO: resolve textures and regenerate mipmaps as needed
-    bool hasPoints = false;
-    for (int i = 0; i < meshCount; ++i) {
-        if (meshes[i].primitiveType() == GrPrimitiveType::kPoints) {
-            hasPoints = true;
-            break;
-        }
-    }
-    GrProgramDesc desc;
-    if (!GrProgramDesc::Build(&desc, fRenderTarget->config(), primProc, hasPoints,
-                              pipeline, fGpu)) {
-        return nullptr;
-    }
 
     const GrTextureProxy* const* primProcProxies = nullptr;
     if (fixedDynamicState) {
@@ -130,17 +110,15 @@
     }
     SkASSERT(SkToBool(primProcProxies) == SkToBool(primProc.numTextureSamplers()));
 
-    // TODO: use resource provider for pipeline
     GrMtlPipelineState* pipelineState =
-            GrMtlPipelineStateBuilder::CreatePipelineState(fRenderTarget, fOrigin, primProc,
-                                                           primProcProxies, pipeline,
-                                                           &desc, fGpu);
+        fGpu->resourceProvider().findOrCreateCompatiblePipelineState(fRenderTarget, fOrigin,
+                                                                     pipeline,
+                                                                     primProc,
+                                                                     primProcProxies,
+                                                                     primType);
     if (!pipelineState) {
         return nullptr;
     }
-    // We cannot have an active encoder when we set the pipeline data since it requires its own
-    // command encoder.
-    SkASSERT(fActiveRenderCmdEncoder == nil);
     pipelineState->setData(fRenderTarget, fOrigin, primProc, pipeline, primProcProxies);
 
     return pipelineState;
@@ -153,34 +131,111 @@
                                      const GrMesh meshes[],
                                      int meshCount,
                                      const SkRect& bounds) {
+    SK_BEGIN_AUTORELEASE_BLOCK
     if (!meshCount) {
         return;
     }
-    if (pipeline.isScissorEnabled()) {
-        return; // TODO: ScissorRects are not supported.
+
+    auto prepareSampledImage = [&](GrTexture* texture, GrSamplerState::Filter filter) {
+        // Check if we need to regenerate any mip maps
+        if (GrSamplerState::Filter::kMipMap == filter &&
+            (texture->width() != 1 || texture->height() != 1)) {
+            SkASSERT(texture->texturePriv().mipMapped() == GrMipMapped::kYes);
+            if (texture->texturePriv().mipMapsAreDirty()) {
+                fGpu->regenerateMipMapLevels(texture);
+            }
+        }
+    };
+
+    if (dynamicStateArrays && dynamicStateArrays->fPrimitiveProcessorTextures) {
+        for (int m = 0, i = 0; m < meshCount; ++m) {
+            for (int s = 0; s < primProc.numTextureSamplers(); ++s, ++i) {
+                auto texture = dynamicStateArrays->fPrimitiveProcessorTextures[i]->peekTexture();
+                prepareSampledImage(texture, primProc.textureSampler(s).samplerState().filter());
+            }
+        }
+    } else {
+        for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
+            auto texture = fixedDynamicState->fPrimitiveProcessorTextures[i]->peekTexture();
+            prepareSampledImage(texture, primProc.textureSampler(i).samplerState().filter());
+        }
+    }
+    GrFragmentProcessor::Iter iter(pipeline);
+    while (const GrFragmentProcessor* fp = iter.next()) {
+        for (int i = 0; i < fp->numTextureSamplers(); ++i) {
+            const GrFragmentProcessor::TextureSampler& sampler = fp->textureSampler(i);
+            prepareSampledImage(sampler.peekTexture(), sampler.samplerState().filter());
+        }
     }
 
-    std::unique_ptr<GrMtlPipelineState> pipelineState(
-            this->prepareDrawState(primProc, pipeline, fixedDynamicState, meshes, meshCount));
+    GrPrimitiveType primitiveType = meshes[0].primitiveType();
+    GrMtlPipelineState* pipelineState = this->prepareDrawState(primProc, pipeline,
+                                                               fixedDynamicState, primitiveType);
     if (!pipelineState) {
         return;
     }
 
-    this->internalBegin();
-    [fActiveRenderCmdEncoder setRenderPipelineState: pipelineState->mtlPipelineState()];
+    SkASSERT(nil == fActiveRenderCmdEncoder);
+    fActiveRenderCmdEncoder = [fGpu->commandBuffer()
+                               renderCommandEncoderWithDescriptor:fRenderPassDesc];
+    SkASSERT(fActiveRenderCmdEncoder);
+    // TODO: can we set this once somewhere at the beginning of the draw?
+    [fActiveRenderCmdEncoder setFrontFacingWinding:MTLWindingCounterClockwise];
+    // Strictly speaking we shouldn't have to set this, as the default viewport is the size of
+    // the drawable used to generate the renderCommandEncoder -- but just in case.
+    MTLViewport viewport = { 0.0, 0.0,
+                             (double) fRenderTarget->width(), (double) fRenderTarget->height(),
+                             0.0, 1.0 };
+    [fActiveRenderCmdEncoder setViewport:viewport];
 
-    pipelineState->bind(fActiveRenderCmdEncoder);
-    pipelineState->setBlendConstants(fActiveRenderCmdEncoder, fRenderTarget->config(),
-                                     pipeline.getXferProcessor());
-    pipelineState->setDepthStencilState(fActiveRenderCmdEncoder);
+    [fActiveRenderCmdEncoder setRenderPipelineState:pipelineState->mtlPipelineState()];
+    pipelineState->setDrawState(fActiveRenderCmdEncoder, fRenderTarget->config(),
+                                pipeline.getXferProcessor());
+
+    bool dynamicScissor =
+            pipeline.isScissorEnabled() && dynamicStateArrays && dynamicStateArrays->fScissorRects;
+    if (!pipeline.isScissorEnabled()) {
+        GrMtlPipelineState::SetDynamicScissorRectState(fActiveRenderCmdEncoder,
+                                                       fRenderTarget, fOrigin,
+                                                       SkIRect::MakeWH(fRenderTarget->width(),
+                                                                       fRenderTarget->height()));
+    } else if (!dynamicScissor) {
+        SkASSERT(fixedDynamicState);
+        GrMtlPipelineState::SetDynamicScissorRectState(fActiveRenderCmdEncoder,
+                                                       fRenderTarget, fOrigin,
+                                                       fixedDynamicState->fScissorRect);
+    }
 
     for (int i = 0; i < meshCount; ++i) {
         const GrMesh& mesh = meshes[i];
-        SkASSERT(fActiveRenderCmdEncoder);
+        SkASSERT(nil != fActiveRenderCmdEncoder);
+        if (mesh.primitiveType() != primitiveType) {
+            SkDEBUGCODE(pipelineState = nullptr);
+            primitiveType = mesh.primitiveType();
+            pipelineState = this->prepareDrawState(primProc, pipeline, fixedDynamicState,
+                                                   primitiveType);
+            if (!pipelineState) {
+                return;
+            }
+
+            [fActiveRenderCmdEncoder setRenderPipelineState:pipelineState->mtlPipelineState()];
+            pipelineState->setDrawState(fActiveRenderCmdEncoder, fRenderTarget->config(),
+                                        pipeline.getXferProcessor());
+        }
+
+        if (dynamicScissor) {
+            GrMtlPipelineState::SetDynamicScissorRectState(fActiveRenderCmdEncoder, fRenderTarget,
+                                                           fOrigin,
+                                                           dynamicStateArrays->fScissorRects[i]);
+        }
+
         mesh.sendToGpu(this);
     }
-    this->internalEnd();
+
+    [fActiveRenderCmdEncoder endEncoding];
+    fActiveRenderCmdEncoder = nil;
     fCommandBufferInfo.fBounds.join(bounds);
+    SK_END_AUTORELEASE_BLOCK
 }
 
 void GrMtlGpuRTCommandBuffer::onClear(const GrFixedClip& clip, const SkPMColor4f& color) {
@@ -190,8 +245,7 @@
     fRenderPassDesc.colorAttachments[0].clearColor = MTLClearColorMake(color.fR, color.fG, color.fB,
                                                                        color.fA);
     fRenderPassDesc.colorAttachments[0].loadAction = MTLLoadActionClear;
-    this->internalBegin();
-    this->internalEnd();
+    this->addNullCommand();
     fRenderPassDesc.colorAttachments[0].loadAction = MTLLoadActionLoad;
 }
 
@@ -213,8 +267,7 @@
     }
 
     fRenderPassDesc.stencilAttachment.loadAction = MTLLoadActionClear;
-    this->internalBegin();
-    this->internalEnd();
+    this->addNullCommand();
     fRenderPassDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
 }
 
@@ -237,7 +290,7 @@
     GR_STATIC_ASSERT((int)GrStoreOp::kDiscard == 1);
     SkASSERT(fColorLoadAndStoreInfo.fStoreOp <= GrStoreOp::kDiscard);
 
-    auto renderPassDesc = [[MTLRenderPassDescriptor alloc] init];
+    auto renderPassDesc = [MTLRenderPassDescriptor renderPassDescriptor];
     renderPassDesc.colorAttachments[0].texture =
             static_cast<GrMtlRenderTarget*>(fRenderTarget)->mtlRenderTexture();
     renderPassDesc.colorAttachments[0].slice = 0;
@@ -274,24 +327,24 @@
                                            const GrBuffer* instanceBuffer) {
     size_t bufferIndex = GrMtlUniformHandler::kLastUniformBinding + 1;
     if (vertexBuffer) {
-        SkASSERT(!vertexBuffer->isCPUBacked());
-        SkASSERT(!vertexBuffer->isMapped());
+        SkASSERT(!vertexBuffer->isCpuBuffer());
+        SkASSERT(!static_cast<const GrGpuBuffer*>(vertexBuffer)->isMapped());
 
         auto mtlVertexBuffer = static_cast<const GrMtlBuffer*>(vertexBuffer)->mtlBuffer();
         SkASSERT(mtlVertexBuffer);
-        [fActiveRenderCmdEncoder setVertexBuffer: mtlVertexBuffer
-                                          offset: 0
-                                         atIndex: bufferIndex++];
+        [fActiveRenderCmdEncoder setVertexBuffer:mtlVertexBuffer
+                                          offset:0
+                                         atIndex:bufferIndex++];
     }
     if (instanceBuffer) {
-        SkASSERT(!instanceBuffer->isCPUBacked());
-        SkASSERT(!instanceBuffer->isMapped());
+        SkASSERT(!instanceBuffer->isCpuBuffer());
+        SkASSERT(!static_cast<const GrGpuBuffer*>(instanceBuffer)->isMapped());
 
         auto mtlInstanceBuffer = static_cast<const GrMtlBuffer*>(instanceBuffer)->mtlBuffer();
         SkASSERT(mtlInstanceBuffer);
-        [fActiveRenderCmdEncoder setVertexBuffer: mtlInstanceBuffer
-                                          offset: 0
-                                         atIndex: bufferIndex++];
+        [fActiveRenderCmdEncoder setVertexBuffer:mtlInstanceBuffer
+                                          offset:0
+                                         atIndex:bufferIndex++];
     }
 }
 
@@ -305,11 +358,11 @@
     this->bindGeometry(vertexBuffer, instanceBuffer);
 
     SkASSERT(primitiveType != GrPrimitiveType::kLinesAdjacency); // Geometry shaders not supported.
-    [fActiveRenderCmdEncoder drawPrimitives: gr_to_mtl_primitive(primitiveType)
-                                vertexStart: baseVertex
-                                vertexCount: vertexCount
-                              instanceCount: instanceCount
-                               baseInstance: baseInstance];
+    [fActiveRenderCmdEncoder drawPrimitives:gr_to_mtl_primitive(primitiveType)
+                                vertexStart:baseVertex
+                                vertexCount:vertexCount
+                              instanceCount:instanceCount
+                               baseInstance:baseInstance];
 }
 
 void GrMtlGpuRTCommandBuffer::sendIndexedInstancedMeshToGpu(GrPrimitiveType primitiveType,
@@ -327,20 +380,21 @@
     SkASSERT(primitiveType != GrPrimitiveType::kLinesAdjacency); // Geometry shaders not supported.
     id<MTLBuffer> mtlIndexBuffer;
     if (indexBuffer) {
-        SkASSERT(!indexBuffer->isCPUBacked());
-        SkASSERT(!indexBuffer->isMapped());
+        SkASSERT(!indexBuffer->isCpuBuffer());
+        SkASSERT(!static_cast<const GrGpuBuffer*>(indexBuffer)->isMapped());
 
         mtlIndexBuffer = static_cast<const GrMtlBuffer*>(indexBuffer)->mtlBuffer();
         SkASSERT(mtlIndexBuffer);
     }
 
     SkASSERT(restart == GrPrimitiveRestart::kNo);
-    [fActiveRenderCmdEncoder drawIndexedPrimitives: gr_to_mtl_primitive(primitiveType)
-                                        indexCount: indexCount
-                                         indexType: MTLIndexTypeUInt16
-                                       indexBuffer: mtlIndexBuffer
-                                 indexBufferOffset: sizeof(uint16_t) * baseIndex
-                                     instanceCount: instanceCount
-                                        baseVertex: baseVertex
-                                      baseInstance: baseInstance];
+    [fActiveRenderCmdEncoder drawIndexedPrimitives:gr_to_mtl_primitive(primitiveType)
+                                        indexCount:indexCount
+                                         indexType:MTLIndexTypeUInt16
+                                       indexBuffer:mtlIndexBuffer
+                                 indexBufferOffset:sizeof(uint16_t) * baseIndex
+                                     instanceCount:instanceCount
+                                        baseVertex:baseVertex
+                                      baseInstance:baseInstance];
+    fGpu->stats()->incNumDraws();
 }
diff --git a/src/gpu/mtl/GrMtlPipelineState.h b/src/gpu/mtl/GrMtlPipelineState.h
index bcb62b2..b7371e6 100644
--- a/src/gpu/mtl/GrMtlPipelineState.h
+++ b/src/gpu/mtl/GrMtlPipelineState.h
@@ -51,11 +51,12 @@
                  const GrPrimitiveProcessor& primPRoc, const GrPipeline& pipeline,
                  const GrTextureProxy* const primProcTextures[]);
 
-    void bind(id<MTLRenderCommandEncoder>);
+    void setDrawState(id<MTLRenderCommandEncoder>, GrPixelConfig, const GrXferProcessor&);
 
-    void setBlendConstants(id<MTLRenderCommandEncoder>, GrPixelConfig, const GrXferProcessor&);
-
-    void setDepthStencilState(id<MTLRenderCommandEncoder> renderCmdEncoder);
+    static void SetDynamicScissorRectState(id<MTLRenderCommandEncoder> renderCmdEncoder,
+                                           const GrRenderTarget* renderTarget,
+                                           GrSurfaceOrigin rtOrigin,
+                                           SkIRect scissorRect);
 
 private:
     /**
@@ -96,6 +97,12 @@
 
     void setRenderTargetState(const GrRenderTarget*, GrSurfaceOrigin);
 
+    void bind(id<MTLRenderCommandEncoder>);
+
+    void setBlendConstants(id<MTLRenderCommandEncoder>, GrPixelConfig, const GrXferProcessor&);
+
+    void setDepthStencilState(id<MTLRenderCommandEncoder> renderCmdEncoder);
+
     struct SamplerBindings {
         id<MTLSamplerState> fSampler;
         id<MTLTexture> fTexture;
diff --git a/src/gpu/mtl/GrMtlPipelineState.mm b/src/gpu/mtl/GrMtlPipelineState.mm
index e1e4977..0416159 100644
--- a/src/gpu/mtl/GrMtlPipelineState.mm
+++ b/src/gpu/mtl/GrMtlPipelineState.mm
@@ -55,8 +55,8 @@
         , fXferProcessor(std::move(xferProcessor))
         , fFragmentProcessors(std::move(fragmentProcessors))
         , fFragmentProcessorCnt(fragmentProcessorCnt)
-        , fDataManager(uniforms, fGeometryUniformBuffer->sizeInBytes(),
-                       fFragmentUniformBuffer->sizeInBytes()) {
+        , fDataManager(uniforms, fGeometryUniformBuffer->size(),
+                       fFragmentUniformBuffer->size()) {
     (void) fPixelFormat; // Suppress unused-var warning.
 }
 
@@ -118,6 +118,13 @@
     }
 }
 
+void GrMtlPipelineState::setDrawState(id<MTLRenderCommandEncoder> renderCmdEncoder,
+                                      GrPixelConfig config, const GrXferProcessor& xferProcessor) {
+    this->bind(renderCmdEncoder);
+    this->setBlendConstants(renderCmdEncoder, config, xferProcessor);
+    this->setDepthStencilState(renderCmdEncoder);
+}
+
 void GrMtlPipelineState::bind(id<MTLRenderCommandEncoder> renderCmdEncoder) {
     if (fGeometryUniformBuffer) {
         [renderCmdEncoder setVertexBuffer: fGeometryUniformBuffer->mtlBuffer()
@@ -273,3 +280,28 @@
         [renderCmdEncoder setDepthStencilState:state];
     }
 }
+
+void GrMtlPipelineState::SetDynamicScissorRectState(id<MTLRenderCommandEncoder> renderCmdEncoder,
+                                                    const GrRenderTarget* renderTarget,
+                                                    GrSurfaceOrigin rtOrigin,
+                                                    SkIRect scissorRect) {
+    if (!scissorRect.intersect(SkIRect::MakeWH(renderTarget->width(), renderTarget->height()))) {
+        scissorRect.setEmpty();
+    }
+
+    MTLScissorRect scissor;
+    scissor.x = scissorRect.fLeft;
+    scissor.width = scissorRect.width();
+    if (kTopLeft_GrSurfaceOrigin == rtOrigin) {
+        scissor.y = scissorRect.fTop;
+    } else {
+        SkASSERT(kBottomLeft_GrSurfaceOrigin == rtOrigin);
+        scissor.y = renderTarget->height() - scissorRect.fBottom;
+    }
+    scissor.height = scissorRect.height();
+
+    SkASSERT(scissor.x >= 0);
+    SkASSERT(scissor.y >= 0);
+
+    [renderCmdEncoder setScissorRect: scissor];
+}
diff --git a/src/gpu/mtl/GrMtlPipelineStateBuilder.h b/src/gpu/mtl/GrMtlPipelineStateBuilder.h
index ce41618..470fa87 100644
--- a/src/gpu/mtl/GrMtlPipelineStateBuilder.h
+++ b/src/gpu/mtl/GrMtlPipelineStateBuilder.h
@@ -22,28 +22,63 @@
 
 class GrMtlPipelineStateBuilder : public GrGLSLProgramBuilder {
 public:
-    static GrMtlPipelineState* CreatePipelineState(GrRenderTarget*, GrSurfaceOrigin,
+    /**
+     * For Metal we want to cache the entire pipeline for reuse of draws. The Desc here holds all
+     * the information needed to differentiate one pipeline from another.
+     *
+     * The GrProgramDesc contains all the information need to create the actual shaders for the
+     * pipeline.
+     *
+     * For Metal we need to add to the GrProgramDesc to include the rest of the state on the
+     * pipeline. This includes blending information and primitive type. The pipeline is immutable
+     * so any remaining dynamic state is set via the MtlRenderCmdEncoder.
+     */
+    class Desc : public GrProgramDesc {
+    public:
+        static bool Build(Desc*,
+                          GrRenderTarget*,
+                          const GrPrimitiveProcessor&,
+                          const GrPipeline&,
+                          GrPrimitiveType,
+                          GrMtlGpu* gpu);
+
+        size_t shaderKeyLength() const { return fShaderKeyLength; }
+
+    private:
+        size_t fShaderKeyLength;
+
+        typedef GrProgramDesc INHERITED;
+    };
+
+    /** Generates a pipeline state.
+     *
+     * The GrMtlPipelineState implements what is specified in the GrPipeline and
+     * GrPrimitiveProcessor as input. After successful generation, the builder result objects are
+     * available to be used. This function may modify the program key by setting the surface origin
+     * key to 0 (unspecified) if it turns out the program does not care about the surface origin.
+     * @return true if generation was successful.
+     */
+    static GrMtlPipelineState* CreatePipelineState(GrMtlGpu*,
+                                                   GrRenderTarget*, GrSurfaceOrigin,
                                                    const GrPrimitiveProcessor&,
                                                    const GrTextureProxy* const primProcProxies[],
                                                    const GrPipeline&,
-                                                   GrProgramDesc*,
-                                                   GrMtlGpu*);
+                                                   Desc*);
 
 private:
-    GrMtlPipelineStateBuilder(GrRenderTarget*, GrSurfaceOrigin,
+    GrMtlPipelineStateBuilder(GrMtlGpu*, GrRenderTarget*, GrSurfaceOrigin,
+                              const GrPipeline&,
                               const GrPrimitiveProcessor&,
                               const GrTextureProxy* const primProcProxies[],
-                              const GrPipeline&,
-                              GrProgramDesc*, GrMtlGpu*);
+                              GrProgramDesc*);
+
+    GrMtlPipelineState* finalize(GrRenderTarget* renderTarget,
+                                 const GrPrimitiveProcessor& primProc,
+                                 const GrPipeline& pipeline,
+                                 Desc*);
 
     const GrCaps* caps() const override;
 
-    GrGLSLUniformHandler* uniformHandler() override { return &fUniformHandler; }
-
-    const GrGLSLUniformHandler* uniformHandler() const override { return &fUniformHandler; }
-
-    GrGLSLVaryingHandler* varyingHandler() override { return &fVaryingHandler; }
-
     void finalizeFragmentOutputColor(GrShaderVar& outputColor) override;
 
     void finalizeFragmentSecondaryColor(GrShaderVar& outputColor) override;
@@ -53,7 +88,9 @@
                                           const SkSL::Program::Settings& settings,
                                           GrProgramDesc* desc);
 
-    GrMtlPipelineState* finalize(const GrPrimitiveProcessor&, const GrPipeline&, GrProgramDesc*);
+    GrGLSLUniformHandler* uniformHandler() override { return &fUniformHandler; }
+    const GrGLSLUniformHandler* uniformHandler() const override { return &fUniformHandler; }
+    GrGLSLVaryingHandler* varyingHandler() override { return &fVaryingHandler; }
 
     GrMtlGpu* fGpu;
     GrMtlUniformHandler fUniformHandler;
diff --git a/src/gpu/mtl/GrMtlPipelineStateBuilder.mm b/src/gpu/mtl/GrMtlPipelineStateBuilder.mm
index 5168358..0a3d7a5 100644
--- a/src/gpu/mtl/GrMtlPipelineStateBuilder.mm
+++ b/src/gpu/mtl/GrMtlPipelineStateBuilder.mm
@@ -14,30 +14,33 @@
 #include "GrMtlPipelineState.h"
 #include "GrMtlUtil.h"
 
+#include "GrRenderTargetPriv.h"
+
 #import <simd/simd.h>
 
 GrMtlPipelineState* GrMtlPipelineStateBuilder::CreatePipelineState(
+        GrMtlGpu* gpu,
         GrRenderTarget* renderTarget, GrSurfaceOrigin origin,
         const GrPrimitiveProcessor& primProc,
         const GrTextureProxy* const primProcProxies[],
         const GrPipeline& pipeline,
-        GrProgramDesc* desc,
-        GrMtlGpu* gpu) {
-    GrMtlPipelineStateBuilder builder(renderTarget, origin, primProc, primProcProxies, pipeline,
-                                      desc, gpu);
+        Desc* desc) {
+    GrMtlPipelineStateBuilder builder(gpu, renderTarget, origin, pipeline, primProc,
+                                      primProcProxies, desc);
 
     if (!builder.emitAndInstallProcs()) {
         return nullptr;
     }
-    return builder.finalize(primProc, pipeline, desc);
+    return builder.finalize(renderTarget, primProc, pipeline, desc);
 }
 
-GrMtlPipelineStateBuilder::GrMtlPipelineStateBuilder(GrRenderTarget* renderTarget, GrSurfaceOrigin origin,
+GrMtlPipelineStateBuilder::GrMtlPipelineStateBuilder(GrMtlGpu* gpu,
+                                                     GrRenderTarget* renderTarget,
+                                                     GrSurfaceOrigin origin,
+                                                     const GrPipeline& pipeline,
                                                      const GrPrimitiveProcessor& primProc,
                                                      const GrTextureProxy* const primProcProxies[],
-                                                     const GrPipeline& pipeline,
-                                                     GrProgramDesc* desc,
-                                                     GrMtlGpu* gpu)
+                                                     GrProgramDesc* desc)
         : INHERITED(renderTarget, origin, primProc, primProcProxies, pipeline, desc)
         , fGpu(gpu)
         , fUniformHandler(this)
@@ -304,10 +307,21 @@
     return mtlColorAttachment;
 }
 
-GrMtlPipelineState* GrMtlPipelineStateBuilder::finalize(const GrPrimitiveProcessor& primProc,
+uint32_t buffer_size(uint32_t offset, uint32_t maxAlignment) {
+    // Metal expects the buffer to be padded at the end according to the alignment
+    // of the largest element in the buffer.
+    uint32_t offsetDiff = offset & maxAlignment;
+    if (offsetDiff != 0) {
+        offsetDiff = maxAlignment - offsetDiff + 1;
+    }
+    return offset + offsetDiff;
+}
+
+GrMtlPipelineState* GrMtlPipelineStateBuilder::finalize(GrRenderTarget* renderTarget,
+                                                        const GrPrimitiveProcessor& primProc,
                                                         const GrPipeline& pipeline,
-                                                        GrProgramDesc* desc) {
-    auto pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
+                                                        Desc* desc) {
+    auto pipelineDescriptor = [MTLRenderPipelineDescriptor new];
 
     fVS.extensions().appendf("#extension GL_ARB_separate_shader_objects : enable\n");
     fFS.extensions().appendf("#extension GL_ARB_separate_shader_objects : enable\n");
@@ -322,6 +336,7 @@
     settings.fSharpenTextures = fGpu->getContext()->priv().options().fSharpenMipmappedTextures;
     SkASSERT(!this->fragColorIsInOut());
 
+    // TODO: Store shaders in cache
     id<MTLLibrary> vertexLibrary = nil;
     id<MTLLibrary> fragmentLibrary = nil;
     vertexLibrary = this->createMtlShaderLibrary(fVS,
@@ -344,6 +359,11 @@
     pipelineDescriptor.fragmentFunction = fragmentFunction;
     pipelineDescriptor.vertexDescriptor = create_vertex_descriptor(primProc);
     pipelineDescriptor.colorAttachments[0] = create_color_attachment(this->config(), pipeline);
+    bool hasStencilAttachment = SkToBool(renderTarget->renderTargetPriv().getStencilAttachment());
+    GrMtlCaps* mtlCaps = (GrMtlCaps*)this->caps();
+    pipelineDescriptor.stencilAttachmentPixelFormat =
+        hasStencilAttachment ? mtlCaps->preferredStencilFormat().fInternalFormat
+                             : MTLPixelFormatInvalid;
 
     SkASSERT(pipelineDescriptor.vertexFunction);
     SkASSERT(pipelineDescriptor.fragmentFunction);
@@ -359,17 +379,21 @@
                  [[error localizedDescription] cStringUsingEncoding: NSASCIIStringEncoding]);
         return nullptr;
     }
+    uint32_t geomBufferSize = buffer_size(fUniformHandler.fCurrentGeometryUBOOffset,
+                                          fUniformHandler.fCurrentGeometryUBOMaxAlignment);
+    uint32_t fragBufferSize = buffer_size(fUniformHandler.fCurrentFragmentUBOOffset,
+                                          fUniformHandler.fCurrentFragmentUBOMaxAlignment);
     return new GrMtlPipelineState(fGpu,
                                   pipelineState,
                                   pipelineDescriptor.colorAttachments[0].pixelFormat,
                                   fUniformHandles,
                                   fUniformHandler.fUniforms,
                                   GrMtlBuffer::Make(fGpu,
-                                                    fUniformHandler.fCurrentGeometryUBOOffset,
+                                                    geomBufferSize,
                                                     GrGpuBufferType::kVertex,
                                                     kStatic_GrAccessPattern),
                                   GrMtlBuffer::Make(fGpu,
-                                                    fUniformHandler.fCurrentFragmentUBOOffset,
+                                                    fragBufferSize,
                                                     GrGpuBufferType::kVertex,
                                                     kStatic_GrAccessPattern),
                                   (uint32_t)fUniformHandler.numSamplers(),
@@ -378,3 +402,37 @@
                                   std::move(fFragmentProcessors),
                                   fFragmentProcessorCnt);
 }
+
+//////////////////////////////////////////////////////////////////////////////
+
+bool GrMtlPipelineStateBuilder::Desc::Build(Desc* desc,
+                                            GrRenderTarget* renderTarget,
+                                            const GrPrimitiveProcessor& primProc,
+                                            const GrPipeline& pipeline,
+                                            GrPrimitiveType primitiveType,
+                                            GrMtlGpu* gpu) {
+    if (!INHERITED::Build(desc, renderTarget, primProc,
+                          GrPrimitiveType::kLines == primitiveType, pipeline, gpu)) {
+        return false;
+    }
+
+    GrProcessorKeyBuilder b(&desc->key());
+
+    int keyLength = desc->key().count();
+    SkASSERT(0 == (keyLength % 4));
+    desc->fShaderKeyLength = SkToU32(keyLength);
+
+    b.add32(renderTarget->config());
+    b.add32(renderTarget->numColorSamples());
+    bool hasStencilAttachment = SkToBool(renderTarget->renderTargetPriv().getStencilAttachment());
+    b.add32(hasStencilAttachment ? gpu->mtlCaps().preferredStencilFormat().fInternalFormat
+                                 : MTLPixelFormatInvalid);
+    b.add32((uint32_t)pipeline.isStencilEnabled());
+    // Stencil samples don't seem to be tracked in the MTLRenderPipeline
+
+    b.add32(pipeline.getBlendInfoKey());
+
+    b.add32((uint32_t)primitiveType);
+
+    return true;
+}
diff --git a/src/gpu/mtl/GrMtlRenderTarget.h b/src/gpu/mtl/GrMtlRenderTarget.h
index c6a13aa..50022af 100644
--- a/src/gpu/mtl/GrMtlRenderTarget.h
+++ b/src/gpu/mtl/GrMtlRenderTarget.h
@@ -81,8 +81,6 @@
 
     bool completeStencilAttachment() override;
 
-    void onSetRelease(sk_sp<GrReleaseProcHelper> releaseHelper) override {}
-
     typedef GrRenderTarget INHERITED;
 };
 
diff --git a/src/gpu/mtl/GrMtlResourceProvider.h b/src/gpu/mtl/GrMtlResourceProvider.h
index d5b66ed..c0c0545 100644
--- a/src/gpu/mtl/GrMtlResourceProvider.h
+++ b/src/gpu/mtl/GrMtlResourceProvider.h
@@ -9,6 +9,8 @@
 #define GrMtlResourceProvider_DEFINED
 
 #include "GrMtlCopyPipelineState.h"
+#include "GrMtlPipelineStateBuilder.h"
+#include "SkLRUCache.h"
 #include "SkTArray.h"
 
 #import <metal/metal.h>
@@ -17,17 +19,67 @@
 
 class GrMtlResourceProvider {
 public:
-    GrMtlResourceProvider(GrMtlGpu* gpu) : fGpu(gpu) {}
+    GrMtlResourceProvider(GrMtlGpu* gpu);
 
     GrMtlCopyPipelineState* findOrCreateCopyPipelineState(MTLPixelFormat dstPixelFormat,
                                                           id<MTLFunction> vertexFunction,
                                                           id<MTLFunction> fragmentFunction,
                                                           MTLVertexDescriptor* vertexDescriptor);
 
+    GrMtlPipelineState* findOrCreateCompatiblePipelineState(
+        GrRenderTarget*, GrSurfaceOrigin,
+        const GrPipeline&,
+        const GrPrimitiveProcessor&,
+        const GrTextureProxy* const primProcProxies[],
+        GrPrimitiveType);
+
 private:
+#ifdef SK_DEBUG
+#define GR_PIPELINE_STATE_CACHE_STATS
+#endif
+
+    class PipelineStateCache : public ::SkNoncopyable {
+    public:
+        PipelineStateCache(GrMtlGpu* gpu);
+        ~PipelineStateCache();
+
+        GrMtlPipelineState* refPipelineState(GrRenderTarget*, GrSurfaceOrigin,
+                                             const GrPrimitiveProcessor&,
+                                             const GrTextureProxy* const primProcProxies[],
+                                             const GrPipeline&,
+                                             GrPrimitiveType);
+
+    private:
+        enum {
+            // We may actually have kMaxEntries+1 PipelineStates in context because we create a new
+            // PipelineState before evicting from the cache.
+            kMaxEntries = 128,
+        };
+
+        struct Entry;
+
+        struct DescHash {
+            uint32_t operator()(const GrProgramDesc& desc) const {
+                return SkOpts::hash_fn(desc.asKey(), desc.keyLength(), 0);
+            }
+        };
+
+        SkLRUCache<const GrMtlPipelineStateBuilder::Desc, std::unique_ptr<Entry>, DescHash> fMap;
+
+        GrMtlGpu*                    fGpu;
+
+#ifdef GR_PIPELINE_STATE_CACHE_STATS
+        int                         fTotalRequests;
+        int                         fCacheMisses;
+#endif
+    };
+
     SkTArray<std::unique_ptr<GrMtlCopyPipelineState>> fCopyPipelineStateCache;
 
     GrMtlGpu* fGpu;
+
+    // Cache of GrMtlPipelineStates
+    std::unique_ptr<PipelineStateCache> fPipelineStateCache;
 };
 
 #endif
diff --git a/src/gpu/mtl/GrMtlResourceProvider.mm b/src/gpu/mtl/GrMtlResourceProvider.mm
index 2e1e40d..9708978 100644
--- a/src/gpu/mtl/GrMtlResourceProvider.mm
+++ b/src/gpu/mtl/GrMtlResourceProvider.mm
@@ -9,10 +9,17 @@
 
 #include "GrMtlCopyManager.h"
 #include "GrMtlGpu.h"
+#include "GrMtlPipelineState.h"
 #include "GrMtlUtil.h"
 
 #include "SkSLCompiler.h"
 
+
+GrMtlResourceProvider::GrMtlResourceProvider(GrMtlGpu* gpu)
+    : fGpu(gpu) {
+    fPipelineStateCache.reset(new PipelineStateCache(gpu));
+}
+
 GrMtlCopyPipelineState* GrMtlResourceProvider::findOrCreateCopyPipelineState(
         MTLPixelFormat dstPixelFormat,
         id<MTLFunction> vertexFunction,
@@ -29,3 +36,92 @@
              fGpu, dstPixelFormat, vertexFunction, fragmentFunction, vertexDescriptor));
     return fCopyPipelineStateCache.back().get();
 }
+
+GrMtlPipelineState* GrMtlResourceProvider::findOrCreateCompatiblePipelineState(
+        GrRenderTarget* renderTarget, GrSurfaceOrigin origin,
+        const GrPipeline& pipeline, const GrPrimitiveProcessor& proc,
+        const GrTextureProxy* const primProcProxies[], GrPrimitiveType primType) {
+    return fPipelineStateCache->refPipelineState(renderTarget, origin, proc, primProcProxies,
+                                                 pipeline, primType);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////
+
+#ifdef GR_PIPELINE_STATE_CACHE_STATS
+// Display pipeline state cache usage
+static const bool c_DisplayMtlPipelineCache{false};
+#endif
+
+struct GrMtlResourceProvider::PipelineStateCache::Entry {
+    Entry(GrMtlGpu* gpu, GrMtlPipelineState* pipelineState)
+    : fGpu(gpu)
+    , fPipelineState(pipelineState) {}
+
+    GrMtlGpu* fGpu;
+    std::unique_ptr<GrMtlPipelineState> fPipelineState;
+};
+
+GrMtlResourceProvider::PipelineStateCache::PipelineStateCache(GrMtlGpu* gpu)
+    : fMap(kMaxEntries)
+    , fGpu(gpu)
+#ifdef GR_PIPELINE_STATE_CACHE_STATS
+    , fTotalRequests(0)
+    , fCacheMisses(0)
+#endif
+{}
+
+GrMtlResourceProvider::PipelineStateCache::~PipelineStateCache() {
+    // TODO: determine if we need abandon/release methods
+    fMap.reset();
+    // dump stats
+#ifdef GR_PIPELINE_STATE_CACHE_STATS
+    if (c_DisplayMtlPipelineCache) {
+        SkDebugf("--- Pipeline State Cache ---\n");
+        SkDebugf("Total requests: %d\n", fTotalRequests);
+        SkDebugf("Cache misses: %d\n", fCacheMisses);
+        SkDebugf("Cache miss %%: %f\n", (fTotalRequests > 0) ?
+                 100.f * fCacheMisses / fTotalRequests :
+                 0.f);
+        SkDebugf("---------------------\n");
+    }
+#endif
+}
+
+GrMtlPipelineState* GrMtlResourceProvider::PipelineStateCache::refPipelineState(
+        GrRenderTarget* renderTarget,
+        GrSurfaceOrigin origin,
+        const GrPrimitiveProcessor& primProc,
+        const GrTextureProxy* const primProcProxies[],
+        const GrPipeline& pipeline,
+        GrPrimitiveType primType) {
+#ifdef GR_PIPELINE_STATE_CACHE_STATS
+    ++fTotalRequests;
+#endif
+    // Get GrMtlProgramDesc
+    GrMtlPipelineStateBuilder::Desc desc;
+    if (!GrMtlPipelineStateBuilder::Desc::Build(&desc, renderTarget, primProc, pipeline, primType,
+                                                fGpu)) {
+        GrCapsDebugf(fGpu->caps(), "Failed to build mtl program descriptor!\n");
+        return nullptr;
+    }
+
+    std::unique_ptr<Entry>* entry = fMap.find(desc);
+    if (!entry) {
+        // Didn't find an origin-independent version, check with the specific origin
+        desc.setSurfaceOriginKey(GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(origin));
+        entry = fMap.find(desc);
+    }
+    if (!entry) {
+#ifdef GR_PIPELINE_STATE_CACHE_STATS
+        ++fCacheMisses;
+#endif
+        GrMtlPipelineState* pipelineState(GrMtlPipelineStateBuilder::CreatePipelineState(
+                fGpu, renderTarget, origin, primProc, primProcProxies, pipeline, &desc));
+        if (nullptr == pipelineState) {
+            return nullptr;
+        }
+        entry = fMap.insert(desc, std::unique_ptr<Entry>(new Entry(fGpu, pipelineState)));
+        return (*entry)->fPipelineState.get();
+    }
+    return (*entry)->fPipelineState.get();
+}
diff --git a/src/gpu/mtl/GrMtlStencilAttachment.mm b/src/gpu/mtl/GrMtlStencilAttachment.mm
index b6fb18c..a2d8fa3 100644
--- a/src/gpu/mtl/GrMtlStencilAttachment.mm
+++ b/src/gpu/mtl/GrMtlStencilAttachment.mm
@@ -23,12 +23,13 @@
                                                        int height,
                                                        int sampleCnt,
                                                        const Format& format) {
-    MTLTextureDescriptor* desc = [MTLTextureDescriptor
-                              texture2DDescriptorWithPixelFormat:MTLPixelFormatStencil8
-                              width:width
-                              height:height
-                              mipmapped:NO];
+    MTLTextureDescriptor* desc =
+        [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format.fInternalFormat
+                                                           width:width
+                                                          height:height
+                                                       mipmapped:NO];
     desc.resourceOptions = MTLResourceStorageModePrivate;
+    desc.usage = MTLTextureUsageRenderTarget;
     return new GrMtlStencilAttachment(gpu, format, [gpu->device() newTextureWithDescriptor:desc]);
 }
 
diff --git a/src/gpu/mtl/GrMtlTexture.h b/src/gpu/mtl/GrMtlTexture.h
index 13555e4..c2c3c77 100644
--- a/src/gpu/mtl/GrMtlTexture.h
+++ b/src/gpu/mtl/GrMtlTexture.h
@@ -36,12 +36,6 @@
 
     bool reallocForMipmap(GrMtlGpu* gpu, uint32_t mipLevels);
 
-    void setIdleProc(IdleProc proc, void* context) override {
-        fIdleProc = proc;
-        fIdleProcContext = context;
-    }
-    void* idleContext() const override { return fIdleProcContext; }
-
 protected:
     GrMtlTexture(GrMtlGpu*, const GrSurfaceDesc&, id<MTLTexture>, GrMipMapsStatus);
 
@@ -63,19 +57,6 @@
 private:
     enum Wrapped { kWrapped };
 
-    // Since all MTLResources are inherently ref counted, we can call the Release proc when we
-    // delete the GrMtlTexture without worry of the MTLTexture getting deleted before it is done on
-    // the GPU. Thus we do nothing special here with the releaseHelper.
-    void onSetRelease(sk_sp<GrReleaseProcHelper> releaseHelper) override {}
-
-    void removedLastRefOrPendingIO() override {
-        if (fIdleProc) {
-            fIdleProc(fIdleProcContext);
-            fIdleProc = nullptr;
-            fIdleProcContext = nullptr;
-        }
-    }
-
     GrMtlTexture(GrMtlGpu*, SkBudgeted, const GrSurfaceDesc&, id<MTLTexture>,
                  GrMipMapsStatus);
 
@@ -83,9 +64,6 @@
                  GrWrapCacheable, GrIOType);
 
     id<MTLTexture> fTexture;
-    sk_sp<GrReleaseProcHelper> fReleaseHelper;
-    IdleProc* fIdleProc = nullptr;
-    void* fIdleProcContext = nullptr;
 
     typedef GrTexture INHERITED;
 };
diff --git a/src/gpu/mtl/GrMtlTextureRenderTarget.h b/src/gpu/mtl/GrMtlTextureRenderTarget.h
index b15d289..50a4e8e 100644
--- a/src/gpu/mtl/GrMtlTextureRenderTarget.h
+++ b/src/gpu/mtl/GrMtlTextureRenderTarget.h
@@ -75,8 +75,6 @@
         return GrSurface::ComputeSize(this->config(), this->width(), this->height(),
                                       numColorSamples, GrMipMapped::kNo, false);
     }
-
-    void onSetRelease(sk_sp<GrReleaseProcHelper> releaseHelper) override {}
 };
 
 #endif
diff --git a/src/gpu/mtl/GrMtlUniformHandler.h b/src/gpu/mtl/GrMtlUniformHandler.h
index 226b4a4..b117e9c 100644
--- a/src/gpu/mtl/GrMtlUniformHandler.h
+++ b/src/gpu/mtl/GrMtlUniformHandler.h
@@ -48,12 +48,13 @@
         , fUniforms(kUniformsPerBlock)
         , fSamplers(kUniformsPerBlock)
         , fCurrentGeometryUBOOffset(0)
-        , fCurrentFragmentUBOOffset(0) {
+        , fCurrentGeometryUBOMaxAlignment(0x0)
+        , fCurrentFragmentUBOOffset(0)
+        , fCurrentFragmentUBOMaxAlignment(0x0) {
     }
 
     UniformHandle internalAddUniformArray(uint32_t visibility,
                                           GrSLType type,
-                                          GrSLPrecision precision,
                                           const char* name,
                                           bool mangleName,
                                           int arrayCount,
@@ -80,18 +81,18 @@
     bool hasGeometryUniforms() const { return fCurrentGeometryUBOOffset > 0; }
     bool hasFragmentUniforms() const { return fCurrentFragmentUBOOffset > 0; }
 
-
     const UniformInfo& getUniformInfo(UniformHandle u) const {
         return fUniforms[u.toIndex()];
     }
 
-
     UniformInfoArray    fUniforms;
     UniformInfoArray    fSamplers;
     SkTArray<GrSwizzle> fSamplerSwizzles;
 
     uint32_t            fCurrentGeometryUBOOffset;
+    uint32_t            fCurrentGeometryUBOMaxAlignment;
     uint32_t            fCurrentFragmentUBOOffset;
+    uint32_t            fCurrentFragmentUBOMaxAlignment;
 
     friend class GrMtlPipelineStateBuilder;
 
diff --git a/src/gpu/mtl/GrMtlUniformHandler.mm b/src/gpu/mtl/GrMtlUniformHandler.mm
index 0d27eab..505e894 100644
--- a/src/gpu/mtl/GrMtlUniformHandler.mm
+++ b/src/gpu/mtl/GrMtlUniformHandler.mm
@@ -174,9 +174,13 @@
 // the new uniform, and currentOffset is updated to be the offset to the end of the new uniform.
 static void get_ubo_aligned_offset(uint32_t* uniformOffset,
                                    uint32_t* currentOffset,
+                                   uint32_t* maxAlignment,
                                    GrSLType type,
                                    int arrayCount) {
     uint32_t alignmentMask = grsltype_to_alignment_mask(type);
+    if (alignmentMask > *maxAlignment) {
+        *maxAlignment = alignmentMask;
+    }
     uint32_t offsetDiff = *currentOffset & alignmentMask;
     if (offsetDiff != 0) {
         offsetDiff = alignmentMask - offsetDiff + 1;
@@ -195,7 +199,6 @@
 GrGLSLUniformHandler::UniformHandle GrMtlUniformHandler::internalAddUniformArray(
                                                                             uint32_t visibility,
                                                                             GrSLType type,
-                                                                            GrSLPrecision precision,
                                                                             const char* name,
                                                                             bool mangleName,
                                                                             int arrayCount,
@@ -207,7 +210,6 @@
              kGeometry_GrShaderFlag == visibility ||
              (kVertex_GrShaderFlag | kGeometry_GrShaderFlag) == visibility ||
              kFragment_GrShaderFlag == visibility);
-    SkASSERT(kDefault_GrSLPrecision == precision || GrSLTypeIsFloatType(type));
     GrSLTypeIsFloatType(type);
 
     UniformInfo& uni = fUniforms.push_back();
@@ -225,20 +227,22 @@
     fProgramBuilder->nameVariable(uni.fVariable.accessName(), prefix, name, mangleName);
     uni.fVariable.setArrayCount(arrayCount);
     uni.fVisibility = visibility;
-    uni.fVariable.setPrecision(precision);
     // When outputing the GLSL, only the outer uniform block will get the Uniform modifier. Thus
     // we set the modifier to none for all uniforms declared inside the block.
     uni.fVariable.setTypeModifier(GrShaderVar::kNone_TypeModifier);
 
     uint32_t* currentOffset;
+    uint32_t* maxAlignment;
     uint32_t geomStages = kVertex_GrShaderFlag | kGeometry_GrShaderFlag;
     if (geomStages & visibility) {
         currentOffset = &fCurrentGeometryUBOOffset;
+        maxAlignment = &fCurrentGeometryUBOMaxAlignment;
     } else {
         SkASSERT(kFragment_GrShaderFlag == visibility);
         currentOffset = &fCurrentFragmentUBOOffset;
+        maxAlignment = &fCurrentFragmentUBOMaxAlignment;
     }
-    get_ubo_aligned_offset(&uni.fUBOffset, currentOffset, type, arrayCount);
+    get_ubo_aligned_offset(&uni.fUBOffset, currentOffset, maxAlignment, type, arrayCount);
 
     SkString layoutQualifier;
     layoutQualifier.appendf("offset=%d", uni.fUBOffset);
@@ -260,14 +264,12 @@
     char prefix = 'u';
     fProgramBuilder->nameVariable(&mangleName, prefix, name, true);
 
-    GrSLPrecision precision = GrSLSamplerPrecision(texture->config());
     GrSwizzle swizzle = caps->configTextureSwizzle(texture->config());
     GrTextureType type = texture->texturePriv().textureType();
 
     UniformInfo& info = fSamplers.push_back();
     info.fVariable.setType(GrSLCombinedSamplerTypeForTextureType(type));
     info.fVariable.setTypeModifier(GrShaderVar::kUniform_TypeModifier);
-    info.fVariable.setPrecision(precision);
     info.fVariable.setName(mangleName);
     SkString layoutQualifier;
     layoutQualifier.appendf("binding=%d", fSamplers.count() - 1);
diff --git a/src/gpu/mtl/GrMtlUtil.h b/src/gpu/mtl/GrMtlUtil.h
index fa43295..a622534 100644
--- a/src/gpu/mtl/GrMtlUtil.h
+++ b/src/gpu/mtl/GrMtlUtil.h
@@ -22,11 +22,6 @@
 bool GrPixelConfigToMTLFormat(GrPixelConfig config, MTLPixelFormat* format);
 
 /**
-* Returns the GrPixelConfig for the given Metal texture format
-*/
-GrPixelConfig GrMTLFormatToPixelConfig(MTLPixelFormat format);
-
-/**
  * Returns a id<MTLTexture> to the MTLTexture pointed at by the const void*. Will use
  * __bridge_transfer if we are adopting ownership.
  */
diff --git a/src/gpu/mtl/GrMtlUtil.mm b/src/gpu/mtl/GrMtlUtil.mm
index 824bbf5..b8d4898 100644
--- a/src/gpu/mtl/GrMtlUtil.mm
+++ b/src/gpu/mtl/GrMtlUtil.mm
@@ -31,11 +31,14 @@
             *format = MTLPixelFormatRGBA8Unorm;
             return true;
         case kRGB_888_GrPixelConfig:
-            // TODO: MTLPixelFormatRGB8Unorm
-            return false;
+            *format = MTLPixelFormatRGBA8Unorm;
+            return true;
+        case kRGB_888X_GrPixelConfig:
+            *format = MTLPixelFormatRGBA8Unorm;
+            return true;
         case kRG_88_GrPixelConfig:
-            // TODO: MTLPixelFormatRG8Unorm
-            return false;
+            *format = MTLPixelFormatRG8Unorm;
+            return true;
         case kBGRA_8888_GrPixelConfig:
             *format = MTLPixelFormatBGRA8Unorm;
             return true;
@@ -83,6 +86,9 @@
         case kRGBA_half_GrPixelConfig:
             *format = MTLPixelFormatRGBA16Float;
             return true;
+        case kRGBA_half_Clamped_GrPixelConfig:
+            *format = MTLPixelFormatRGBA16Float;
+            return true;
         case kAlpha_half_GrPixelConfig: // fall through
         case kAlpha_half_as_Red_GrPixelConfig:
             *format = MTLPixelFormatR16Float;
@@ -99,46 +105,6 @@
     return false;
 }
 
-GrPixelConfig GrMTLFormatToPixelConfig(MTLPixelFormat format) {
-    switch (format) {
-        case MTLPixelFormatRGBA8Unorm:
-            return kRGBA_8888_GrPixelConfig;
-        case MTLPixelFormatBGRA8Unorm:
-            return kBGRA_8888_GrPixelConfig;
-        case MTLPixelFormatRGBA8Unorm_sRGB:
-            return kSRGBA_8888_GrPixelConfig;
-        case MTLPixelFormatBGRA8Unorm_sRGB:
-            return kSBGRA_8888_GrPixelConfig;
-        case MTLPixelFormatRGB10A2Unorm:
-            return kRGBA_1010102_GrPixelConfig;
-#ifdef SK_BUILD_FOR_IOS
-        case MTLPixelFormatB5G6R5Unorm:
-            return kRGB_565_GrPixelConfig;
-        case MTLPixelFormatABGR4Unorm:
-            return kRGBA_4444_GrPixelConfig;
-#endif
-        case MTLPixelFormatRG8Unorm:
-            return kRG_88_GrPixelConfig;
-        case MTLPixelFormatR8Unorm:
-            // We currently set this to be Alpha_8 and have no way to go to Gray_8
-            return kAlpha_8_GrPixelConfig;
-        case MTLPixelFormatRGBA32Float:
-            return kRGBA_float_GrPixelConfig;
-        case MTLPixelFormatRG32Float:
-            return kRG_float_GrPixelConfig;
-        case MTLPixelFormatRGBA16Float:
-            return kRGBA_half_GrPixelConfig;
-        case MTLPixelFormatR16Float:
-            return kAlpha_half_GrPixelConfig;
-#ifdef SK_BUILD_FOR_IOS
-        case MTLPixelFormatETC2_RGB8:
-            return kRGB_ETC1_GrPixelConfig;
-#endif
-        default:
-            return kUnknown_GrPixelConfig;
-    }
-}
-
 id<MTLTexture> GrGetMTLTexture(const void* mtlTexture, GrWrapOwnership wrapOwnership) {
     if (GrWrapOwnership::kAdopt_GrWrapOwnership == wrapOwnership) {
         return (__bridge_transfer id<MTLTexture>)mtlTexture;
diff --git a/src/gpu/mtl/GrMtlVaryingHandler.mm b/src/gpu/mtl/GrMtlVaryingHandler.mm
index 038b2bf..2692873 100644
--- a/src/gpu/mtl/GrMtlVaryingHandler.mm
+++ b/src/gpu/mtl/GrMtlVaryingHandler.mm
@@ -9,10 +9,12 @@
 
 static void finalize_helper(GrMtlVaryingHandler::VarArray& vars) {
     int locationIndex;
+    int componentCount = 0;
     for (locationIndex = 0; locationIndex < vars.count(); locationIndex++) {
         GrShaderVar& var = vars[locationIndex];
         // Metal only allows scalars (including bool and char) and vectors as varyings
         SkASSERT(GrSLTypeVecLength(var.getType()) != -1);
+        componentCount += GrSLTypeVecLength(var.getType());
 
         SkString location;
         location.appendf("location = %d", locationIndex);
@@ -20,9 +22,10 @@
     }
     // The max number of inputs is 60 for iOS and 32 for macOS. The max number of components is 60
     // for iOS and 128 for macOS. To be conservative, we are going to assert that we have less than
-    // 15 varyings because in the worst case scenario, they are all vec4s (15 * 4 = 60). If we hit
-    // this assert, we can implement a function in GrMtlCaps to be less conservative.
-    SkASSERT(locationIndex <= 15);
+    // 32 varyings and less than 60 components across all varyings. If we hit this assert, we can
+    // implement a function in GrMtlCaps to be less conservative.
+    SkASSERT(locationIndex <= 32);
+    SkASSERT(componentCount <= 60);
 }
 
 void GrMtlVaryingHandler::onFinalize() {
diff --git a/src/gpu/ops/GrAAConvexPathRenderer.cpp b/src/gpu/ops/GrAAConvexPathRenderer.cpp
index 98537df..47f5047 100644
--- a/src/gpu/ops/GrAAConvexPathRenderer.cpp
+++ b/src/gpu/ops/GrAAConvexPathRenderer.cpp
@@ -7,7 +7,6 @@
 
 #include "GrAAConvexPathRenderer.h"
 #include "GrCaps.h"
-#include "GrContext.h"
 #include "GrDrawOpTest.h"
 #include "GrGeometryProcessor.h"
 #include "GrPathUtils.h"
@@ -243,8 +242,7 @@
 
 static inline void add_quad_segment(const SkPoint pts[3],
                                     SegmentArray* segments) {
-    if (SkPointPriv::DistanceToSqd(pts[0], pts[1]) < kCloseSqd ||
-        SkPointPriv::DistanceToSqd(pts[1], pts[2]) < kCloseSqd) {
+    if (SkPointPriv::DistanceToLineSegmentBetweenSqd(pts[1], pts[0], pts[2]) < kCloseSqd) {
         if (pts[0] != pts[2]) {
             add_line_to_segment(pts[2], segments);
         }
@@ -655,7 +653,7 @@
 GrPathRenderer::CanDrawPath
 GrAAConvexPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
     if (args.fCaps->shaderCaps()->shaderDerivativeSupport() &&
-        (GrAAType::kCoverage == args.fAAType) && args.fShape->style().isSimpleFill() &&
+        (AATypeFlags::kCoverage & args.fAATypeFlags) && args.fShape->style().isSimpleFill() &&
         !args.fShape->inverseFilled() && args.fShape->knownToBeConvex()) {
         return CanDrawPath::kYes;
     }
@@ -671,7 +669,7 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const SkMatrix& viewMatrix,
                                           const SkPath& path,
@@ -686,7 +684,6 @@
             : INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage, stencilSettings) {
         fPaths.emplace_back(PathData{viewMatrix, path, color});
         this->setTransformedBounds(path.getBounds(), viewMatrix, HasAABloat::kYes, IsZeroArea::kNo);
-        fWideColor = !SkPMColor4fFitsInBytes(color);
     }
 
     const char* name() const override { return "AAConvexPathOp"; }
@@ -707,14 +704,15 @@
 
     FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
-        return fHelper.finalizeProcessors(caps, clip, GrProcessorAnalysisCoverage::kSingleChannel,
-                                          &fPaths.back().fColor);
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
+        return fHelper.finalizeProcessors(
+                caps, clip, fsaaType, clampType, GrProcessorAnalysisCoverage::kSingleChannel,
+                &fPaths.back().fColor, &fWideColor);
     }
 
 private:
     void onPrepareDraws(Target* target) override {
-        auto pipe = fHelper.makePipeline(target);
         int instanceCount = fPaths.count();
 
         SkMatrix invert;
@@ -795,11 +793,14 @@
                 firstIndex += draw.fIndexCnt;
                 firstVertex += draw.fVertexCnt;
             }
-            target->draw(quadProcessor, pipe.fPipeline, pipe.fFixedDynamicState, nullptr, meshes,
-                         draws.count());
+            target->recordDraw(quadProcessor, meshes, draws.count());
         }
     }
 
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
+    }
+
     CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
         AAConvexPathOp* that = t->cast<AAConvexPathOp>();
         if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
diff --git a/src/gpu/ops/GrAAFillRRectOp.cpp b/src/gpu/ops/GrAAFillRRectOp.cpp
deleted file mode 100644
index 464462f..0000000
--- a/src/gpu/ops/GrAAFillRRectOp.cpp
+++ /dev/null
@@ -1,532 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrAAFillRRectOp.h"
-
-#include "GrCaps.h"
-#include "GrContextPriv.h"
-#include "GrGpuCommandBuffer.h"
-#include "GrMemoryPool.h"
-#include "SkRRectPriv.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLGeometryProcessor.h"
-#include "glsl/GrGLSLVarying.h"
-#include "glsl/GrGLSLVertexGeoBuilder.h"
-
-// Hardware derivatives are not always accurate enough for highly elliptical corners. This method
-// checks to make sure the corners will still all look good if we use HW derivatives.
-static bool can_use_hw_derivatives(const GrShaderCaps&, const SkMatrix&, const SkRRect&);
-
-std::unique_ptr<GrAAFillRRectOp> GrAAFillRRectOp::Make(
-        GrContext* ctx, const SkMatrix& viewMatrix, const SkRRect& rrect, const GrCaps& caps,
-        GrPaint&& paint) {
-    if (!caps.instanceAttribSupport()) {
-        return nullptr;
-    }
-
-    // TODO: Support perspective in a follow-on CL. This shouldn't be difficult, since we already
-    // use HW derivatives. The only trick will be adjusting the AA outset to account for
-    // perspective.  (i.e., outset = 0.5 * z.)
-    if (viewMatrix.hasPerspective()) {
-        return nullptr;
-    }
-
-    GrOpMemoryPool* pool = ctx->priv().opMemoryPool();
-    return pool->allocate<GrAAFillRRectOp>(*caps.shaderCaps(), viewMatrix, rrect, std::move(paint));
-}
-
-GrAAFillRRectOp::GrAAFillRRectOp(const GrShaderCaps& shaderCaps, const SkMatrix& viewMatrix,
-                                 const SkRRect& rrect, GrPaint&& paint)
-        : GrDrawOp(ClassID())
-        , fOriginalColor(paint.getColor4f())
-        , fLocalRect(rrect.rect())
-        , fProcessors(std::move(paint)) {
-    if (can_use_hw_derivatives(shaderCaps, viewMatrix, rrect)) {
-        fFlags |= Flags::kUseHWDerivatives;
-    }
-
-    // Produce a matrix that draws the round rect from normalized [-1, -1, +1, +1] space.
-    float l = rrect.rect().left(), r = rrect.rect().right(),
-          t = rrect.rect().top(), b = rrect.rect().bottom();
-    SkMatrix m;
-    // Unmap the normalized rect [-1, -1, +1, +1] back to [l, t, r, b].
-    m.setScaleTranslate((r - l)/2, (b - t)/2, (l + r)/2, (t + b)/2);
-    // Map to device space.
-    m.postConcat(viewMatrix);
-
-    // Since m is an affine matrix that maps the rect [-1, -1, +1, +1] into the shape's
-    // device-space quad, it's quite simple to find the bounding rectangle:
-    SkASSERT(!m.hasPerspective());
-    SkRect bounds = SkRect::MakeXYWH(m.getTranslateX(), m.getTranslateY(), 0, 0);
-    bounds.outset(SkScalarAbs(m.getScaleX()) + SkScalarAbs(m.getSkewX()),
-                  SkScalarAbs(m.getSkewY()) + SkScalarAbs(m.getScaleY()));
-    this->setBounds(bounds, GrOp::HasAABloat::kYes, GrOp::IsZeroArea::kNo);
-
-    // Write the matrix attribs.
-    this->writeInstanceData(m.getScaleX(), m.getSkewX(), m.getSkewY(), m.getScaleY());
-    this->writeInstanceData(m.getTranslateX(), m.getTranslateY());
-
-    // Convert the radii to [-1, -1, +1, +1] space and write their attribs.
-    Sk4f radiiX, radiiY;
-    Sk4f::Load2(SkRRectPriv::GetRadiiArray(rrect), &radiiX, &radiiY);
-    (radiiX * (2/(r - l))).store(this->appendInstanceData<float>(4));
-    (radiiY * (2/(b - t))).store(this->appendInstanceData<float>(4));
-
-    // We will write the color and local rect attribs during finalize().
-}
-
-GrProcessorSet::Analysis GrAAFillRRectOp::finalize(const GrCaps& caps, const GrAppliedClip* clip) {
-    SkASSERT(1 == fInstanceCount);
-
-    SkPMColor4f overrideColor;
-    const GrProcessorSet::Analysis& analysis = fProcessors.finalize(
-            fOriginalColor, GrProcessorAnalysisCoverage::kSingleChannel, clip, false, caps,
-            &overrideColor);
-
-    // Finish writing the instance attribs.
-    this->writeInstanceData(
-            (analysis.inputColorIsOverridden() ? overrideColor : fOriginalColor).toBytes_RGBA());
-    if (analysis.usesLocalCoords()) {
-        this->writeInstanceData(fLocalRect);
-        fFlags |= Flags::kHasLocalCoords;
-    }
-    fInstanceStride = fInstanceData.count();
-
-    return analysis;
-}
-
-GrDrawOp::CombineResult GrAAFillRRectOp::onCombineIfPossible(GrOp* op, const GrCaps&) {
-    const auto& that = *op->cast<GrAAFillRRectOp>();
-    if (fFlags != that.fFlags || fProcessors != that.fProcessors ||
-        fInstanceData.count() > std::numeric_limits<int>::max() - that.fInstanceData.count()) {
-        return CombineResult::kCannotCombine;
-    }
-
-    fInstanceData.push_back_n(that.fInstanceData.count(), that.fInstanceData.begin());
-    fInstanceCount += that.fInstanceCount;
-    SkASSERT(fInstanceStride == that.fInstanceStride);
-    return CombineResult::kMerged;
-}
-
-void GrAAFillRRectOp::onPrepare(GrOpFlushState* flushState) {
-    if (void* instanceData = flushState->makeVertexSpace(fInstanceStride, fInstanceCount,
-                                                         &fInstanceBuffer, &fBaseInstance)) {
-        SkASSERT(fInstanceStride * fInstanceCount == fInstanceData.count());
-        memcpy(instanceData, fInstanceData.begin(), fInstanceData.count());
-    }
-}
-
-namespace {
-
-// Our round rect geometry consists of an inset octagon with solid coverage, surrounded by linear
-// coverage ramps on the horizontal and vertical edges, and "arc coverage" pieces on the diagonal
-// edges. The Vertex struct tells the shader where to place its vertex within a normalized
-// ([l, t, r, b] = [-1, -1, +1, +1]) space, and how to calculate coverage. See onEmitCode.
-struct Vertex {
-    std::array<float, 4> fRadiiSelector;
-    std::array<float, 2> fCorner;
-    std::array<float, 2> fRadiusOutset;
-    std::array<float, 2> fAABloatDirection;
-    float fCoverage;
-    float fIsLinearCoverage;
-};
-
-// This is the offset (when multiplied by radii) from the corners of a bounding box to the vertices
-// of its inscribed octagon. We draw the outside portion of arcs with quarter-octagons rather than
-// rectangles.
-static constexpr float kOctoOffset = 1/(1 + SK_ScalarRoot2Over2);
-
-static constexpr Vertex kVertexData[] = {
-        // Left inset edge.
-        {{{0,0,0,1}},  {{-1,+1}},  {{0,-1}},  {{+1,0}},  1,  1},
-        {{{1,0,0,0}},  {{-1,-1}},  {{0,+1}},  {{+1,0}},  1,  1},
-
-        // Top inset edge.
-        {{{1,0,0,0}},  {{-1,-1}},  {{+1,0}},  {{0,+1}},  1,  1},
-        {{{0,1,0,0}},  {{+1,-1}},  {{-1,0}},  {{0,+1}},  1,  1},
-
-        // Right inset edge.
-        {{{0,1,0,0}},  {{+1,-1}},  {{0,+1}},  {{-1,0}},  1,  1},
-        {{{0,0,1,0}},  {{+1,+1}},  {{0,-1}},  {{-1,0}},  1,  1},
-
-        // Bottom inset edge.
-        {{{0,0,1,0}},  {{+1,+1}},  {{-1,0}},  {{0,-1}},  1,  1},
-        {{{0,0,0,1}},  {{-1,+1}},  {{+1,0}},  {{0,-1}},  1,  1},
-
-
-        // Left outset edge.
-        {{{0,0,0,1}},  {{-1,+1}},  {{0,-1}},  {{-1,0}},  0,  1},
-        {{{1,0,0,0}},  {{-1,-1}},  {{0,+1}},  {{-1,0}},  0,  1},
-
-        // Top outset edge.
-        {{{1,0,0,0}},  {{-1,-1}},  {{+1,0}},  {{0,-1}},  0,  1},
-        {{{0,1,0,0}},  {{+1,-1}},  {{-1,0}},  {{0,-1}},  0,  1},
-
-        // Right outset edge.
-        {{{0,1,0,0}},  {{+1,-1}},  {{0,+1}},  {{+1,0}},  0,  1},
-        {{{0,0,1,0}},  {{+1,+1}},  {{0,-1}},  {{+1,0}},  0,  1},
-
-        // Bottom outset edge.
-        {{{0,0,1,0}},  {{+1,+1}},  {{-1,0}},  {{0,+1}},  0,  1},
-        {{{0,0,0,1}},  {{-1,+1}},  {{+1,0}},  {{0,+1}},  0,  1},
-
-
-        // Top-left corner.
-        {{{1,0,0,0}},  {{-1,-1}},  {{ 0,+1}},  {{-1, 0}},  0,  0},
-        {{{1,0,0,0}},  {{-1,-1}},  {{ 0,+1}},  {{+1, 0}},  1,  0},
-        {{{1,0,0,0}},  {{-1,-1}},  {{+1, 0}},  {{ 0,+1}},  1,  0},
-        {{{1,0,0,0}},  {{-1,-1}},  {{+1, 0}},  {{ 0,-1}},  0,  0},
-        {{{1,0,0,0}},  {{-1,-1}},  {{+kOctoOffset,0}},  {{-1,-1}},  0,  0},
-        {{{1,0,0,0}},  {{-1,-1}},  {{0,+kOctoOffset}},  {{-1,-1}},  0,  0},
-
-        // Top-right corner.
-        {{{0,1,0,0}},  {{+1,-1}},  {{-1, 0}},  {{ 0,-1}},  0,  0},
-        {{{0,1,0,0}},  {{+1,-1}},  {{-1, 0}},  {{ 0,+1}},  1,  0},
-        {{{0,1,0,0}},  {{+1,-1}},  {{ 0,+1}},  {{-1, 0}},  1,  0},
-        {{{0,1,0,0}},  {{+1,-1}},  {{ 0,+1}},  {{+1, 0}},  0,  0},
-        {{{0,1,0,0}},  {{+1,-1}},  {{0,+kOctoOffset}},  {{+1,-1}},  0,  0},
-        {{{0,1,0,0}},  {{+1,-1}},  {{-kOctoOffset,0}},  {{+1,-1}},  0,  0},
-
-        // Bottom-right corner.
-        {{{0,0,1,0}},  {{+1,+1}},  {{ 0,-1}},  {{+1, 0}},  0,  0},
-        {{{0,0,1,0}},  {{+1,+1}},  {{ 0,-1}},  {{-1, 0}},  1,  0},
-        {{{0,0,1,0}},  {{+1,+1}},  {{-1, 0}},  {{ 0,-1}},  1,  0},
-        {{{0,0,1,0}},  {{+1,+1}},  {{-1, 0}},  {{ 0,+1}},  0,  0},
-        {{{0,0,1,0}},  {{+1,+1}},  {{-kOctoOffset,0}},  {{+1,+1}},  0,  0},
-        {{{0,0,1,0}},  {{+1,+1}},  {{0,-kOctoOffset}},  {{+1,+1}},  0,  0},
-
-        // Bottom-left corner.
-        {{{0,0,0,1}},  {{-1,+1}},  {{+1, 0}},  {{ 0,+1}},  0,  0},
-        {{{0,0,0,1}},  {{-1,+1}},  {{+1, 0}},  {{ 0,-1}},  1,  0},
-        {{{0,0,0,1}},  {{-1,+1}},  {{ 0,-1}},  {{+1, 0}},  1,  0},
-        {{{0,0,0,1}},  {{-1,+1}},  {{ 0,-1}},  {{-1, 0}},  0,  0},
-        {{{0,0,0,1}},  {{-1,+1}},  {{0,-kOctoOffset}},  {{-1,+1}},  0,  0},
-        {{{0,0,0,1}},  {{-1,+1}},  {{+kOctoOffset,0}},  {{-1,+1}},  0,  0}};
-
-GR_DECLARE_STATIC_UNIQUE_KEY(gVertexBufferKey);
-
-static constexpr uint16_t kIndexData[] = {
-        // Inset octagon (solid coverage).
-        0, 1, 7,
-        1, 2, 7,
-        7, 2, 6,
-        2, 3, 6,
-        6, 3, 5,
-        3, 4, 5,
-
-        // AA borders (linear coverage).
-        0, 1, 8, 1, 9, 8,
-        2, 3, 10, 3, 11, 10,
-        4, 5, 12, 5, 13, 12,
-        6, 7, 14, 7, 15, 14,
-
-        // Top-left arc.
-        16, 17, 21,
-        17, 21, 18,
-        21, 18, 20,
-        18, 20, 19,
-
-        // Top-right arc.
-        22, 23, 27,
-        23, 27, 24,
-        27, 24, 26,
-        24, 26, 25,
-
-        // Bottom-right arc.
-        28, 29, 33,
-        29, 33, 30,
-        33, 30, 32,
-        30, 32, 31,
-
-        // Bottom-left arc.
-        34, 35, 39,
-        35, 39, 36,
-        39, 36, 38,
-        36, 38, 37};
-
-GR_DECLARE_STATIC_UNIQUE_KEY(gIndexBufferKey);
-
-}
-
-class GrAAFillRRectOp::Processor : public GrGeometryProcessor {
-public:
-    Processor(Flags flags)
-            : GrGeometryProcessor(kGrAAFillRRectOp_Processor_ClassID)
-            , fFlags(flags) {
-        this->setVertexAttributes(kVertexAttribs, 3);
-        this->setInstanceAttributes(kInstanceAttribs, (flags & Flags::kHasLocalCoords) ? 6 : 5);
-        SkASSERT(this->vertexStride() == sizeof(Vertex));
-    }
-
-    const char* name() const override { return "GrAAFillRRectOp::Processor"; }
-
-    void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
-        b->add32(static_cast<uint32_t>(fFlags));
-    }
-
-    GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override;
-
-private:
-    static constexpr Attribute kVertexAttribs[] = {
-            {"radii_selector", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
-            {"corner_and_radius_outsets", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
-            {"aa_bloat_and_coverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType}};
-
-    static constexpr Attribute kInstanceAttribs[] = {
-            {"skew", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
-            {"translate", kFloat2_GrVertexAttribType, kFloat2_GrSLType},
-            {"radii_x", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
-            {"radii_y", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
-            {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType},
-            {"local_rect", kFloat4_GrVertexAttribType, kFloat4_GrSLType}};  // Conditional.
-
-    static constexpr int kColorAttribIdx = 4;
-
-    const Flags fFlags;
-
-    class Impl;
-};
-
-constexpr GrPrimitiveProcessor::Attribute GrAAFillRRectOp::Processor::kVertexAttribs[];
-constexpr GrPrimitiveProcessor::Attribute GrAAFillRRectOp::Processor::kInstanceAttribs[];
-
-class GrAAFillRRectOp::Processor::Impl : public GrGLSLGeometryProcessor {
-public:
-    void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
-        const auto& proc = args.fGP.cast<Processor>();
-        bool useHWDerivatives = (proc.fFlags & Flags::kUseHWDerivatives);
-
-        GrGLSLVaryingHandler* varyings = args.fVaryingHandler;
-        varyings->emitAttributes(proc);
-        varyings->addPassThroughAttribute(proc.kInstanceAttribs[kColorAttribIdx], args.fOutputColor,
-                                          GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
-
-        // Emit the vertex shader.
-        GrGLSLVertexBuilder* v = args.fVertBuilder;
-
-        // Unpack vertex attribs.
-        v->codeAppend("float2 corner = corner_and_radius_outsets.xy;");
-        v->codeAppend("float2 radius_outset = corner_and_radius_outsets.zw;");
-        v->codeAppend("float2 aa_bloat_direction = aa_bloat_and_coverage.xy;");
-        v->codeAppend("float coverage = aa_bloat_and_coverage.z;");
-        v->codeAppend("float is_linear_coverage = aa_bloat_and_coverage.w;");
-
-        // Find the amount to bloat each edge for AA (in source space).
-        v->codeAppend("float2 pixellength = inversesqrt("
-                              "float2(dot(skew.xz, skew.xz), dot(skew.yw, skew.yw)));");
-        v->codeAppend("float4 normalized_axis_dirs = skew * pixellength.xyxy;");
-        v->codeAppend("float2 axiswidths = (abs(normalized_axis_dirs.xy) + "
-                                           "abs(normalized_axis_dirs.zw));");
-        v->codeAppend("float2 aa_bloatradius = axiswidths * pixellength * .5;");
-
-        // Identify our radii.
-        v->codeAppend("float4 radii_and_neighbors = radii_selector"
-                              "* float4x4(radii_x, radii_y, radii_x.yxwz, radii_y.wzyx);");
-        v->codeAppend("float2 radii = radii_and_neighbors.xy;");
-        v->codeAppend("float2 neighbor_radii = radii_and_neighbors.zw;");
-
-        v->codeAppend("if (any(greaterThan(aa_bloatradius, float2(1)))) {");
-                          // The rrect is more narrow than an AA coverage ramp. We can't draw as-is
-                          // or else opposite AA borders will overlap. Instead, fudge the size up to
-                          // the width of a coverage ramp, and then reduce total coverage to make
-                          // the rect appear more thin.
-        v->codeAppend(    "corner = max(abs(corner), aa_bloatradius) * sign(corner);");
-        v->codeAppend(    "coverage /= max(aa_bloatradius.x, 1) * max(aa_bloatradius.y, 1);");
-                          // Set radii to zero to ensure we take the "linear coverage" codepath.
-                          // (The "coverage" variable only has effect in the linear codepath.)
-        v->codeAppend(    "radii = float2(0);");
-        v->codeAppend("}");
-
-        v->codeAppend("if (any(lessThan(radii, aa_bloatradius * 1.25))) {");
-                          // The radii are very small. Demote this arc to a sharp 90 degree corner.
-        v->codeAppend(    "radii = aa_bloatradius;");
-                          // Snap octagon vertices to the corner of the bounding box.
-        v->codeAppend(    "radius_outset = floor(abs(radius_outset)) * radius_outset;");
-        v->codeAppend(    "is_linear_coverage = 1;");
-        v->codeAppend("} else {");
-                          // Don't let radii get smaller than a pixel.
-        v->codeAppend(    "radii = clamp(radii, pixellength, 2 - pixellength);");
-        v->codeAppend(    "neighbor_radii = clamp(neighbor_radii, pixellength, 2 - pixellength);");
-                          // Don't let neighboring radii get closer together than 1/16 pixel.
-        v->codeAppend(    "float2 spacing = 2 - radii - neighbor_radii;");
-        v->codeAppend(    "float2 extra_pad = max(pixellength * .0625 - spacing, float2(0));");
-        v->codeAppend(    "radii -= extra_pad * .5;");
-        v->codeAppend("}");
-
-        // Find our vertex position, adjusted for radii and bloated for AA. Our rect is drawn in
-        // normalized [-1,-1,+1,+1] space.
-        v->codeAppend("float2 aa_outset = aa_bloat_direction.xy * aa_bloatradius;");
-        v->codeAppend("float2 vertexpos = corner + radius_outset * radii + aa_outset;");
-
-        // Emit transforms.
-        GrShaderVar localCoord("", kFloat2_GrSLType);
-        if (proc.fFlags & Flags::kHasLocalCoords) {
-            v->codeAppend("float2 localcoord = (local_rect.xy * (1 - vertexpos) + "
-                                               "local_rect.zw * (1 + vertexpos)) * .5;");
-            localCoord.set(kFloat2_GrSLType, "localcoord");
-        }
-        this->emitTransforms(v, varyings, args.fUniformHandler, localCoord,
-                             args.fFPCoordTransformHandler);
-
-        // Transform to device space.
-        v->codeAppend("float2x2 skewmatrix = float2x2(skew.xy, skew.zw);");
-        v->codeAppend("float2 devcoord = vertexpos * skewmatrix + translate;");
-        gpArgs->fPositionVar.set(kFloat2_GrSLType, "devcoord");
-
-        // Setup interpolants for coverage.
-        GrGLSLVarying arcCoord(useHWDerivatives ? kFloat2_GrSLType : kFloat4_GrSLType);
-        varyings->addVarying("arccoord", &arcCoord);
-        v->codeAppend("if (0 != is_linear_coverage) {");
-                           // We are a non-corner piece: Set x=0 to indicate built-in coverage, and
-                           // interpolate linear coverage across y.
-        v->codeAppendf(    "%s.xy = float2(0, coverage);", arcCoord.vsOut());
-        v->codeAppend("} else {");
-                           // Find the normalized arc coordinates for our corner ellipse.
-                           // (i.e., the coordinate system where x^2 + y^2 == 1).
-        v->codeAppend(    "float2 arccoord = 1 - abs(radius_outset) + aa_outset/radii * corner;");
-                           // We are a corner piece: Interpolate the arc coordinates for coverage.
-                           // Emit x+1 to ensure no pixel in the arc has a x value of 0 (since x=0
-                           // instructs the fragment shader to use linear coverage).
-        v->codeAppendf(    "%s.xy = float2(arccoord.x+1, arccoord.y);", arcCoord.vsOut());
-        if (!useHWDerivatives) {
-            // The gradient is order-1: Interpolate it across arccoord.zw.
-            v->codeAppendf("float2x2 derivatives = inverse(skewmatrix);");
-            v->codeAppendf("%s.zw = derivatives * (arccoord/radii * 2);", arcCoord.vsOut());
-        }
-        v->codeAppend("}");
-
-        // Emit the fragment shader.
-        GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
-
-        f->codeAppendf("float x_plus_1=%s.x, y=%s.y;", arcCoord.fsIn(), arcCoord.fsIn());
-        f->codeAppendf("half coverage;");
-        f->codeAppendf("if (0 == x_plus_1) {");
-        f->codeAppendf(    "coverage = half(y);");  // We are a non-arc pixel (i.e., linear coverage).
-        f->codeAppendf("} else {");
-        f->codeAppendf(    "float fn = x_plus_1 * (x_plus_1 - 2);");  // fn = (x+1)*(x-1) = x^2-1
-        f->codeAppendf(    "fn = fma(y,y, fn);");  // fn = x^2 + y^2 - 1
-        if (useHWDerivatives) {
-            f->codeAppendf("float fnwidth = fwidth(fn);");
-        } else {
-            // The gradient is interpolated across arccoord.zw.
-            f->codeAppendf("float gx=%s.z, gy=%s.w;", arcCoord.fsIn(), arcCoord.fsIn());
-            f->codeAppendf("float fnwidth = abs(gx) + abs(gy);");
-        }
-        f->codeAppendf(    "half d = half(fn/fnwidth);");
-        f->codeAppendf(    "coverage = clamp(.5 - d, 0, 1);");
-        f->codeAppendf("}");
-        f->codeAppendf("%s = half4(coverage);", args.fOutputCoverage);
-    }
-
-    void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
-                 FPCoordTransformIter&& transformIter) override {
-        this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
-    }
-};
-
-GrGLSLPrimitiveProcessor* GrAAFillRRectOp::Processor::createGLSLInstance(
-        const GrShaderCaps&) const {
-    return new Impl();
-}
-
-void GrAAFillRRectOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
-    if (!fInstanceBuffer) {
-        return;  // Setup failed.
-    }
-
-    GR_DEFINE_STATIC_UNIQUE_KEY(gIndexBufferKey);
-
-    sk_sp<const GrBuffer> indexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer(
-            GrGpuBufferType::kIndex, sizeof(kIndexData), kIndexData, gIndexBufferKey);
-    if (!indexBuffer) {
-        return;
-    }
-
-    GR_DEFINE_STATIC_UNIQUE_KEY(gVertexBufferKey);
-
-    sk_sp<const GrBuffer> vertexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer(
-            GrGpuBufferType::kVertex, sizeof(kVertexData), kVertexData, gVertexBufferKey);
-    if (!vertexBuffer) {
-        return;
-    }
-
-    Processor proc(fFlags);
-    SkASSERT(proc.instanceStride() == (size_t)fInstanceStride);
-
-    GrPipeline::InitArgs initArgs;
-    initArgs.fCaps = &flushState->caps();
-    initArgs.fResourceProvider = flushState->resourceProvider();
-    initArgs.fDstProxy = flushState->drawOpArgs().fDstProxy;
-    auto clip = flushState->detachAppliedClip();
-    GrPipeline::FixedDynamicState fixedDynamicState(clip.scissorState().rect());
-    GrPipeline pipeline(initArgs, std::move(fProcessors), std::move(clip));
-
-    GrMesh mesh(GrPrimitiveType::kTriangles);
-    mesh.setIndexedInstanced(std::move(indexBuffer), SK_ARRAY_COUNT(kIndexData), fInstanceBuffer,
-                             fInstanceCount, fBaseInstance, GrPrimitiveRestart::kNo);
-    mesh.setVertexData(std::move(vertexBuffer));
-    flushState->rtCommandBuffer()->draw(proc, pipeline, &fixedDynamicState, nullptr, &mesh, 1,
-                                        this->bounds());
-}
-
-// Will the given corner look good if we use HW derivatives?
-static bool can_use_hw_derivatives(const Sk2f& devScale, const Sk2f& cornerRadii) {
-    Sk2f devRadii = devScale * cornerRadii;
-    if (devRadii[1] < devRadii[0]) {
-        devRadii = SkNx_shuffle<1,0>(devRadii);
-    }
-    float minDevRadius = SkTMax(devRadii[0], 1.f);  // Shader clamps radius at a minimum of 1.
-    // Is the gradient smooth enough for this corner look ok if we use hardware derivatives?
-    // This threshold was arrived at subjevtively on an NVIDIA chip.
-    return minDevRadius * minDevRadius * 5 > devRadii[1];
-}
-
-static bool can_use_hw_derivatives(const Sk2f& devScale, const SkVector& cornerRadii) {
-    return can_use_hw_derivatives(devScale, Sk2f::Load(&cornerRadii));
-}
-
-// Will the given round rect look good if we use HW derivatives?
-static bool can_use_hw_derivatives(const GrShaderCaps& shaderCaps, const SkMatrix& viewMatrix,
-                                   const SkRRect& rrect) {
-    if (!shaderCaps.shaderDerivativeSupport()) {
-        return false;
-    }
-
-    Sk2f x = Sk2f(viewMatrix.getScaleX(), viewMatrix.getSkewX());
-    Sk2f y = Sk2f(viewMatrix.getSkewY(), viewMatrix.getScaleY());
-    Sk2f devScale = (x*x + y*y).sqrt();
-    switch (rrect.getType()) {
-        case SkRRect::kEmpty_Type:
-        case SkRRect::kRect_Type:
-            return true;
-
-        case SkRRect::kOval_Type:
-        case SkRRect::kSimple_Type:
-            return can_use_hw_derivatives(devScale, rrect.getSimpleRadii());
-
-        case SkRRect::kNinePatch_Type: {
-            Sk2f r0 = Sk2f::Load(SkRRectPriv::GetRadiiArray(rrect));
-            Sk2f r1 = Sk2f::Load(SkRRectPriv::GetRadiiArray(rrect) + 2);
-            Sk2f minRadii = Sk2f::Min(r0, r1);
-            Sk2f maxRadii = Sk2f::Max(r0, r1);
-            return can_use_hw_derivatives(devScale, Sk2f(minRadii[0], maxRadii[1])) &&
-                   can_use_hw_derivatives(devScale, Sk2f(maxRadii[0], minRadii[1]));
-        }
-
-        case SkRRect::kComplex_Type: {
-            for (int i = 0; i < 4; ++i) {
-                auto corner = static_cast<SkRRect::Corner>(i);
-                if (!can_use_hw_derivatives(devScale, rrect.radii(corner))) {
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
-    SK_ABORT("Unreachable code.");
-    return false;  // Add this return to keep GCC happy.
-}
diff --git a/src/gpu/ops/GrAAFillRRectOp.h b/src/gpu/ops/GrAAFillRRectOp.h
deleted file mode 100644
index cb29b91..0000000
--- a/src/gpu/ops/GrAAFillRRectOp.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef GrAAFillRRectOp_DEFINED
-#define GrAAFillRRectOp_DEFINED
-
-#include "GrDrawOp.h"
-
-class GrAAFillRRectOp : public GrDrawOp {
-public:
-    DEFINE_OP_CLASS_ID
-
-    static std::unique_ptr<GrAAFillRRectOp> Make(GrContext*, const SkMatrix&, const SkRRect&,
-                                                 const GrCaps&, GrPaint&&);
-
-    const char* name() const override { return "GrAAFillRRectOp"; }
-    FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
-    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*) override;
-    CombineResult onCombineIfPossible(GrOp*, const GrCaps&) override;
-    void visitProxies(const VisitProxyFunc& fn, VisitorType) const override {
-        fProcessors.visitProxies(fn);
-    }
-    void onPrepare(GrOpFlushState*) override;
-
-    void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
-
-private:
-    enum class Flags {
-        kNone = 0,
-        kUseHWDerivatives = 1 << 0,
-        kHasLocalCoords = 1 << 1
-    };
-
-    GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(Flags)
-
-    class Processor;
-
-    GrAAFillRRectOp(const GrShaderCaps&, const SkMatrix&, const SkRRect&, GrPaint&&);
-
-    // These methods are used to append data of various POD types to our internal array of instance
-    // data. The actual layout of the instance buffer can vary from Op to Op.
-    template <typename T> inline void* appendInstanceData(int count) {
-        static_assert(std::is_pod<T>::value, "");
-        static_assert(4 == alignof(T), "");
-        return fInstanceData.push_back_n(sizeof(T) * count);
-    }
-
-    template <typename T, typename... Args>
-    inline void writeInstanceData(const T& val, const Args&... remainder) {
-        memcpy(this->appendInstanceData<T>(1), &val, sizeof(T));
-        this->writeInstanceData(remainder...);
-    }
-
-    void writeInstanceData() {}  // Halt condition.
-
-    const SkPMColor4f fOriginalColor;
-    const SkRect fLocalRect;
-    Flags fFlags = Flags::kNone;
-    GrProcessorSet fProcessors;
-
-    SkSTArray<sizeof(float) * 16 * 4, char, /*MEM_MOVE=*/ true> fInstanceData;
-    int fInstanceCount = 1;
-    int fInstanceStride = 0;
-
-    sk_sp<const GrBuffer> fInstanceBuffer;
-    int fBaseInstance;
-
-    friend class GrOpMemoryPool;
-};
-
-GR_MAKE_BITFIELD_CLASS_OPS(GrAAFillRRectOp::Flags)
-
-#endif
diff --git a/src/gpu/ops/GrAAHairLinePathRenderer.cpp b/src/gpu/ops/GrAAHairLinePathRenderer.cpp
index 578eea8..c06e86a 100644
--- a/src/gpu/ops/GrAAHairLinePathRenderer.cpp
+++ b/src/gpu/ops/GrAAHairLinePathRenderer.cpp
@@ -9,7 +9,6 @@
 #include "GrBuffer.h"
 #include "GrCaps.h"
 #include "GrClip.h"
-#include "GrContext.h"
 #include "GrDefaultGeoProcFactory.h"
 #include "GrDrawOpTest.h"
 #include "GrOpFlushState.h"
@@ -714,7 +713,7 @@
 
 GrPathRenderer::CanDrawPath
 GrAAHairLinePathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
-    if (GrAAType::kCoverage != args.fAAType) {
+    if (!(AATypeFlags::kCoverage & args.fAATypeFlags)) {
         return CanDrawPath::kNo;
     }
 
@@ -781,7 +780,7 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const SkMatrix& viewMatrix,
                                           const SkPath& path,
@@ -839,13 +838,17 @@
 
     FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
-        return fHelper.finalizeProcessors(caps, clip, GrProcessorAnalysisCoverage::kSingleChannel,
-                                          &fColor);
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
+        // This Op uses uniform (not vertex) color, so doesn't need to track wide color.
+        return fHelper.finalizeProcessors(caps, clip, fsaaType, clampType,
+                                          GrProcessorAnalysisCoverage::kSingleChannel, &fColor,
+                                          nullptr);
     }
 
 private:
     void onPrepareDraws(Target*) override;
+    void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
 
     typedef SkTArray<SkPoint, true> PtArray;
     typedef SkTArray<int, true> IntArray;
@@ -955,7 +958,6 @@
         return;
     }
 
-    auto pipe = fHelper.makePipeline(target);
     // do lines first
     if (lineCount) {
         sk_sp<GrGeometryProcessor> lineGP;
@@ -994,7 +996,7 @@
         mesh->setIndexedPatterned(std::move(linesIndexBuffer), kIdxsPerLineSeg, kLineSegNumVertices,
                                   lineCount, kLineSegsNumInIdxBuffer);
         mesh->setVertexData(std::move(vertexBuffer), firstVertex);
-        target->draw(std::move(lineGP), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
+        target->recordDraw(std::move(lineGP), mesh);
     }
 
     if (quadCount || conicCount) {
@@ -1049,7 +1051,7 @@
             mesh->setIndexedPatterned(quadsIndexBuffer, kIdxsPerQuad, kQuadNumVertices, quadCount,
                                       kQuadsNumInIdxBuffer);
             mesh->setVertexData(vertexBuffer, firstVertex);
-            target->draw(std::move(quadGP), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
+            target->recordDraw(std::move(quadGP), mesh);
             firstVertex += quadCount * kQuadNumVertices;
         }
 
@@ -1058,11 +1060,15 @@
             mesh->setIndexedPatterned(std::move(quadsIndexBuffer), kIdxsPerQuad, kQuadNumVertices,
                                       conicCount, kQuadsNumInIdxBuffer);
             mesh->setVertexData(std::move(vertexBuffer), firstVertex);
-            target->draw(std::move(conicGP), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
+            target->recordDraw(std::move(conicGP), mesh);
         }
     }
 }
 
+void AAHairlineOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
+    fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
+}
+
 bool GrAAHairLinePathRenderer::onDrawPath(const DrawPathArgs& args) {
     GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(),
                               "GrAAHairlinePathRenderer::onDrawPath");
diff --git a/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp b/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp
index 3d1020d..92e77cc 100644
--- a/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp
+++ b/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp
@@ -7,7 +7,7 @@
 
 #include "GrAALinearizingConvexPathRenderer.h"
 #include "GrAAConvexTessellator.h"
-#include "GrContext.h"
+#include "GrCaps.h"
 #include "GrDefaultGeoProcFactory.h"
 #include "GrDrawOpTest.h"
 #include "GrGeometryProcessor.h"
@@ -39,7 +39,7 @@
 
 GrPathRenderer::CanDrawPath
 GrAALinearizingConvexPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
-    if (GrAAType::kCoverage != args.fAAType) {
+    if (!(AATypeFlags::kCoverage & args.fAATypeFlags)) {
         return CanDrawPath::kNo;
     }
     if (!args.fShape->knownToBeConvex()) {
@@ -120,7 +120,7 @@
 
 public:
     DEFINE_OP_CLASS_ID
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const SkMatrix& viewMatrix,
                                           const SkPath& path,
@@ -160,7 +160,6 @@
             bounds.outset(w, w);
         }
         this->setTransformedBounds(bounds, viewMatrix, HasAABloat::kYes, IsZeroArea::kNo);
-        fWideColor = !SkPMColor4fFitsInBytes(color);
     }
 
     const char* name() const override { return "AAFlatteningConvexPathOp"; }
@@ -187,15 +186,16 @@
 
     FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
-        return fHelper.finalizeProcessors(caps, clip, GrProcessorAnalysisCoverage::kSingleChannel,
-                                          &fPaths.back().fColor);
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
+        return fHelper.finalizeProcessors(
+                caps, clip, fsaaType, clampType, GrProcessorAnalysisCoverage::kSingleChannel,
+                &fPaths.back().fColor, &fWideColor);
     }
 
 private:
-    void draw(Target* target, sk_sp<const GrGeometryProcessor> gp, const GrPipeline* pipeline,
-              const GrPipeline::FixedDynamicState* fixedDynamicState, int vertexCount,
-              size_t vertexStride, void* vertices, int indexCount, uint16_t* indices) const {
+    void recordDraw(Target* target, sk_sp<const GrGeometryProcessor> gp, int vertexCount,
+                    size_t vertexStride, void* vertices, int indexCount, uint16_t* indices) const {
         if (vertexCount == 0 || indexCount == 0) {
             return;
         }
@@ -221,14 +221,13 @@
         mesh->setIndexed(std::move(indexBuffer), indexCount, firstIndex, 0, vertexCount - 1,
                          GrPrimitiveRestart::kNo);
         mesh->setVertexData(std::move(vertexBuffer), firstVertex);
-        target->draw(std::move(gp), pipeline, fixedDynamicState, mesh);
+        target->recordDraw(std::move(gp), mesh);
     }
 
     void onPrepareDraws(Target* target) override {
-        auto pipe = fHelper.makePipeline(target);
         // Setup GrGeometryProcessor
         sk_sp<GrGeometryProcessor> gp(create_lines_only_gp(target->caps().shaderCaps(),
-                                                           fHelper.compatibleWithAlphaAsCoverage(),
+                                                           fHelper.compatibleWithCoverageAsAlpha(),
                                                            this->viewMatrix(),
                                                            fHelper.usesLocalCoords(),
                                                            fWideColor));
@@ -259,8 +258,8 @@
             if (vertexCount + currentVertices > static_cast<int>(UINT16_MAX)) {
                 // if we added the current instance, we would overflow the indices we can store in a
                 // uint16_t. Draw what we've got so far and reset.
-                this->draw(target, gp, pipe.fPipeline, pipe.fFixedDynamicState, vertexCount,
-                           vertexStride, vertices, indexCount, indices);
+                this->recordDraw(
+                        target, gp, vertexCount, vertexStride, vertices, indexCount, indices);
                 vertexCount = 0;
                 indexCount = 0;
             }
@@ -291,13 +290,17 @@
             indexCount += currentIndices;
         }
         if (vertexCount <= SK_MaxS32 && indexCount <= SK_MaxS32) {
-            this->draw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState, vertexCount,
-                       vertexStride, vertices, indexCount, indices);
+            this->recordDraw(target, std::move(gp), vertexCount, vertexStride, vertices, indexCount,
+                             indices);
         }
         sk_free(vertices);
         sk_free(indices);
     }
 
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
+    }
+
     CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
         AAFlatteningConvexPathOp* that = t->cast<AAFlatteningConvexPathOp>();
         if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
diff --git a/src/gpu/ops/GrAtlasTextOp.cpp b/src/gpu/ops/GrAtlasTextOp.cpp
index d4ffc7c..7588c6c 100644
--- a/src/gpu/ops/GrAtlasTextOp.cpp
+++ b/src/gpu/ops/GrAtlasTextOp.cpp
@@ -8,10 +8,10 @@
 #include "GrAtlasTextOp.h"
 
 #include "GrCaps.h"
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrMemoryPool.h"
 #include "GrOpFlushState.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrResourceProvider.h"
 #include "SkMathPriv.h"
 #include "SkMatrixPriv.h"
@@ -24,7 +24,7 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-std::unique_ptr<GrAtlasTextOp> GrAtlasTextOp::MakeBitmap(GrContext* context,
+std::unique_ptr<GrAtlasTextOp> GrAtlasTextOp::MakeBitmap(GrRecordingContext* context,
                                                          GrPaint&& paint,
                                                          GrMaskFormat maskFormat,
                                                          int glyphCount,
@@ -52,7 +52,7 @@
     }
 
 std::unique_ptr<GrAtlasTextOp> GrAtlasTextOp::MakeDistanceField(
-                                            GrContext* context,
+                                            GrRecordingContext* context,
                                             GrPaint&& paint,
                                             int glyphCount,
                                             const GrDistanceFieldAdjustTable* distanceAdjustTable,
@@ -140,7 +140,8 @@
     return FixedFunctionFlags::kNone;
 }
 
-GrProcessorSet::Analysis GrAtlasTextOp::finalize(const GrCaps& caps, const GrAppliedClip* clip) {
+GrProcessorSet::Analysis GrAtlasTextOp::finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                                 GrFSAAType fsaaType, GrClampType clampType) {
     GrProcessorAnalysisCoverage coverage;
     GrProcessorAnalysisColor color;
     if (kColorBitmapMask_MaskType == fMaskType) {
@@ -163,7 +164,9 @@
             coverage = GrProcessorAnalysisCoverage::kNone;
             break;
     }
-    auto analysis = fProcessors.finalize(color, coverage, clip, false, caps, &fGeoData[0].fColor);
+    auto analysis = fProcessors.finalize(
+            color, coverage, clip, &GrUserStencilSettings::kUnused, fsaaType, caps, clampType,
+            &fGeoData[0].fColor);
     fUsesLocalCoords = analysis.usesLocalCoords();
     return analysis;
 }
@@ -298,16 +301,13 @@
     GR_STATIC_ASSERT(GrDistanceFieldA8TextGeoProc::kMaxTextures == kMaxTextures);
     GR_STATIC_ASSERT(GrDistanceFieldLCDTextGeoProc::kMaxTextures == kMaxTextures);
 
-    static const uint32_t kPipelineFlags = 0;
-    auto pipe = target->makePipeline(kPipelineFlags, std::move(fProcessors),
-                                     target->detachAppliedClip(), kMaxTextures);
+    auto fixedDynamicState = target->makeFixedDynamicState(kMaxTextures);
     for (unsigned i = 0; i < numActiveProxies; ++i) {
-        pipe.fFixedDynamicState->fPrimitiveProcessorTextures[i] = proxies[i].get();
+        fixedDynamicState->fPrimitiveProcessorTextures[i] = proxies[i].get();
     }
 
     FlushInfo flushInfo;
-    flushInfo.fPipeline = pipe.fPipeline;
-    flushInfo.fFixedDynamicState = pipe.fFixedDynamicState;
+    flushInfo.fFixedDynamicState = fixedDynamicState;
 
     bool vmPerspective = fGeoData[0].fViewMatrix.hasPerspective();
     if (this->usesDistanceFields()) {
@@ -389,6 +389,11 @@
     this->flush(target, &flushInfo);
 }
 
+void GrAtlasTextOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
+    flushState->executeDrawsAndUploadsForMeshDrawOp(
+            this, chainBounds, std::move(fProcessors), GrPipeline::InputFlags::kNone);
+}
+
 void GrAtlasTextOp::flush(GrMeshDrawOp::Target* target, FlushInfo* flushInfo) const {
     if (!flushInfo->fGlyphsToFlush) {
         return;
@@ -423,14 +428,13 @@
                                                                       samplerState);
         }
     }
-    int maxGlyphsPerDraw =
-            static_cast<int>(flushInfo->fIndexBuffer->gpuMemorySize() / sizeof(uint16_t) / 6);
+    int maxGlyphsPerDraw = static_cast<int>(flushInfo->fIndexBuffer->size() / sizeof(uint16_t) / 6);
     GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles);
     mesh->setIndexedPatterned(flushInfo->fIndexBuffer, kIndicesPerGlyph, kVerticesPerGlyph,
                               flushInfo->fGlyphsToFlush, maxGlyphsPerDraw);
     mesh->setVertexData(flushInfo->fVertexBuffer, flushInfo->fVertexOffset);
-    target->draw(flushInfo->fGeometryProcessor, flushInfo->fPipeline, flushInfo->fFixedDynamicState,
-                 mesh);
+    target->recordDraw(
+            flushInfo->fGeometryProcessor, mesh, 1, flushInfo->fFixedDynamicState, nullptr);
     flushInfo->fVertexOffset += kVerticesPerGlyph * flushInfo->fGlyphsToFlush;
     flushInfo->fGlyphsToFlush = 0;
 }
diff --git a/src/gpu/ops/GrAtlasTextOp.h b/src/gpu/ops/GrAtlasTextOp.h
index 5faf5fa..973b00b 100644
--- a/src/gpu/ops/GrAtlasTextOp.h
+++ b/src/gpu/ops/GrAtlasTextOp.h
@@ -12,8 +12,8 @@
 #include "text/GrTextBlob.h"
 #include "text/GrDistanceFieldAdjustTable.h"
 
+class GrRecordingContext;
 class SkAtlasTextTarget;
-class GrContext;
 
 class GrAtlasTextOp final : public GrMeshDrawOp {
 public:
@@ -40,20 +40,20 @@
         SkPMColor4f fColor;
     };
 
-    static std::unique_ptr<GrAtlasTextOp> MakeBitmap(GrContext* context,
-                                                     GrPaint&& paint,
-                                                     GrMaskFormat maskFormat,
+    static std::unique_ptr<GrAtlasTextOp> MakeBitmap(GrRecordingContext*,
+                                                     GrPaint&&,
+                                                     GrMaskFormat,
                                                      int glyphCount,
                                                      bool needsTransform);
 
     static std::unique_ptr<GrAtlasTextOp> MakeDistanceField(
-            GrContext* context,
-            GrPaint&& paint,
+            GrRecordingContext*,
+            GrPaint&&,
             int glyphCount,
-            const GrDistanceFieldAdjustTable* distanceAdjustTable,
+            const GrDistanceFieldAdjustTable*,
             bool useGammaCorrectDistanceTable,
             SkColor luminanceColor,
-            const SkSurfaceProps& props,
+            const SkSurfaceProps&,
             bool isAntiAliased,
             bool useLCD);
 
@@ -75,7 +75,8 @@
 
     FixedFunctionFlags fixedFunctionFlags() const override;
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override;
+    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrFSAAType,
+                                      GrClampType) override;
 
     enum MaskType {
         kGrayscaleCoverageMask_MaskType,
@@ -107,13 +108,13 @@
         sk_sp<const GrBuffer> fVertexBuffer;
         sk_sp<const GrBuffer> fIndexBuffer;
         sk_sp<GrGeometryProcessor> fGeometryProcessor;
-        const GrPipeline* fPipeline;
         GrPipeline::FixedDynamicState* fFixedDynamicState;
         int fGlyphsToFlush;
         int fVertexOffset;
     };
 
     void onPrepareDraws(Target*) override;
+    void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
 
     GrMaskFormat maskFormat() const {
         switch (fMaskType) {
diff --git a/src/gpu/ops/GrClearOp.cpp b/src/gpu/ops/GrClearOp.cpp
index d0ee441..9fa0091 100644
--- a/src/gpu/ops/GrClearOp.cpp
+++ b/src/gpu/ops/GrClearOp.cpp
@@ -11,8 +11,10 @@
 #include "GrMemoryPool.h"
 #include "GrOpFlushState.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 
-std::unique_ptr<GrClearOp> GrClearOp::Make(GrContext* context,
+std::unique_ptr<GrClearOp> GrClearOp::Make(GrRecordingContext* context,
                                            const GrFixedClip& clip,
                                            const SkPMColor4f& color,
                                            GrSurfaceProxy* dstProxy) {
@@ -26,7 +28,7 @@
     return pool->allocate<GrClearOp>(clip, color, dstProxy);
 }
 
-std::unique_ptr<GrClearOp> GrClearOp::Make(GrContext* context,
+std::unique_ptr<GrClearOp> GrClearOp::Make(GrRecordingContext* context,
                                            const SkIRect& rect,
                                            const SkPMColor4f& color,
                                            bool fullScreen) {
diff --git a/src/gpu/ops/GrClearOp.h b/src/gpu/ops/GrClearOp.h
index 89cde7b..89a9dec 100644
--- a/src/gpu/ops/GrClearOp.h
+++ b/src/gpu/ops/GrClearOp.h
@@ -12,17 +12,18 @@
 #include "GrOp.h"
 
 class GrOpFlushState;
+class GrRecordingContext;
 
 class GrClearOp final : public GrOp {
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrClearOp> Make(GrContext* context,
+    static std::unique_ptr<GrClearOp> Make(GrRecordingContext* context,
                                            const GrFixedClip& clip,
                                            const SkPMColor4f& color,
                                            GrSurfaceProxy* dstProxy);
 
-    static std::unique_ptr<GrClearOp> Make(GrContext* context,
+    static std::unique_ptr<GrClearOp> Make(GrRecordingContext* context,
                                            const SkIRect& rect,
                                            const SkPMColor4f& color,
                                            bool fullScreen);
diff --git a/src/gpu/ops/GrClearStencilClipOp.cpp b/src/gpu/ops/GrClearStencilClipOp.cpp
index 99cfd5f..58e20a4 100644
--- a/src/gpu/ops/GrClearStencilClipOp.cpp
+++ b/src/gpu/ops/GrClearStencilClipOp.cpp
@@ -9,8 +9,11 @@
 
 #include "GrGpuCommandBuffer.h"
 #include "GrMemoryPool.h"
+#include "GrOpFlushState.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 
-std::unique_ptr<GrOp> GrClearStencilClipOp::Make(GrContext* context,
+std::unique_ptr<GrOp> GrClearStencilClipOp::Make(GrRecordingContext* context,
                                                  const GrFixedClip& clip,
                                                  bool insideStencilMask,
                                                  GrRenderTargetProxy* proxy) {
diff --git a/src/gpu/ops/GrClearStencilClipOp.h b/src/gpu/ops/GrClearStencilClipOp.h
index a04088e..fd9733a 100644
--- a/src/gpu/ops/GrClearStencilClipOp.h
+++ b/src/gpu/ops/GrClearStencilClipOp.h
@@ -13,12 +13,13 @@
 #include "GrRenderTargetProxy.h"
 
 class GrOpFlushState;
+class GrRecordingContext;
 
 class GrClearStencilClipOp final : public GrOp {
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrOp> Make(GrContext* context,
+    static std::unique_ptr<GrOp> Make(GrRecordingContext* context,
                                       const GrFixedClip& clip,
                                       bool insideStencilMask,
                                       GrRenderTargetProxy* proxy);
diff --git a/src/gpu/ops/GrCopySurfaceOp.cpp b/src/gpu/ops/GrCopySurfaceOp.cpp
index 8d8e9f1..548afb9 100644
--- a/src/gpu/ops/GrCopySurfaceOp.cpp
+++ b/src/gpu/ops/GrCopySurfaceOp.cpp
@@ -7,10 +7,10 @@
 
 #include "GrCopySurfaceOp.h"
 
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrGpu.h"
 #include "GrMemoryPool.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 
 // returns true if the read/written rect intersects the src/dst and false if not.
 static bool clip_src_rect_and_dst_point(const GrSurfaceProxy* dst,
@@ -63,7 +63,7 @@
     return !clippedSrcRect->isEmpty();
 }
 
-std::unique_ptr<GrOp> GrCopySurfaceOp::Make(GrContext* context,
+std::unique_ptr<GrOp> GrCopySurfaceOp::Make(GrRecordingContext* context,
                                             GrSurfaceProxy* dstProxy,
                                             GrSurfaceProxy* srcProxy,
                                             const SkIRect& srcRect,
@@ -87,7 +87,9 @@
 }
 
 void GrCopySurfaceOp::onExecute(GrOpFlushState* state, const SkRect& chainBounds) {
-    if (!fSrc.get()->instantiate(state->resourceProvider())) {
+    if (state->resourceProvider()->explicitlyAllocateGPUResources()) {
+        SkASSERT(fSrc.get()->isInstantiated());
+    } else if (!fSrc.get()->instantiate(state->resourceProvider())) {
         return;
     }
 
diff --git a/src/gpu/ops/GrCopySurfaceOp.h b/src/gpu/ops/GrCopySurfaceOp.h
index f17351f..eeaa237 100644
--- a/src/gpu/ops/GrCopySurfaceOp.h
+++ b/src/gpu/ops/GrCopySurfaceOp.h
@@ -11,11 +11,13 @@
 #include "GrOp.h"
 #include "GrOpFlushState.h"
 
+class GrRecordingContext;
+
 class GrCopySurfaceOp final : public GrOp {
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrOp> Make(GrContext*,
+    static std::unique_ptr<GrOp> Make(GrRecordingContext*,
                                       GrSurfaceProxy* dst,
                                       GrSurfaceProxy* src,
                                       const SkIRect& srcRect,
diff --git a/src/gpu/ops/GrDashLinePathRenderer.cpp b/src/gpu/ops/GrDashLinePathRenderer.cpp
index 5e9b369..6bd8441 100644
--- a/src/gpu/ops/GrDashLinePathRenderer.cpp
+++ b/src/gpu/ops/GrDashLinePathRenderer.cpp
@@ -18,7 +18,7 @@
     SkPoint pts[2];
     bool inverted;
     if (args.fShape->style().isDashed() && args.fShape->asLine(pts, &inverted)) {
-        if (args.fAAType == GrAAType::kMixedSamples) {
+        if (args.fAATypeFlags == AATypeFlags::kMixedSampledStencilThenCover) {
             return CanDrawPath::kNo;
         }
         // We should never have an inverse dashed case.
@@ -35,18 +35,15 @@
     GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(),
                               "GrDashLinePathRenderer::onDrawPath");
     GrDashOp::AAMode aaMode = GrDashOp::AAMode::kNone;
-    switch (args.fAAType) {
-        case GrAAType::kNone:
-            break;
-        case GrAAType::kCoverage:
-        case GrAAType::kMixedSamples:
-            aaMode = GrDashOp::AAMode::kCoverage;
-            break;
-        case GrAAType::kMSAA:
+    if (AATypeFlags::kNone != args.fAATypeFlags) {
+        if (AATypeFlags::kMSAA & args.fAATypeFlags) {
             // In this mode we will use aa between dashes but the outer border uses MSAA. Otherwise,
             // we can wind up with external edges antialiased and internal edges unantialiased.
             aaMode = GrDashOp::AAMode::kCoverageWithMSAA;
-            break;
+        } else {
+            SkASSERT(AATypeFlags::kCoverage & args.fAATypeFlags);
+            aaMode = GrDashOp::AAMode::kCoverage;
+        }
     }
     SkPoint pts[2];
     SkAssertResult(args.fShape->asLine(pts, nullptr));
diff --git a/src/gpu/ops/GrDashOp.cpp b/src/gpu/ops/GrDashOp.cpp
index e334c72..ec5e22e 100644
--- a/src/gpu/ops/GrDashOp.cpp
+++ b/src/gpu/ops/GrDashOp.cpp
@@ -8,8 +8,6 @@
 #include "GrDashOp.h"
 #include "GrAppliedClip.h"
 #include "GrCaps.h"
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrCoordTransform.h"
 #include "GrDefaultGeoProcFactory.h"
 #include "GrDrawOpTest.h"
@@ -18,6 +16,8 @@
 #include "GrOpFlushState.h"
 #include "GrProcessor.h"
 #include "GrQuad.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrStyle.h"
 #include "GrVertexWriter.h"
 #include "SkGr.h"
@@ -162,7 +162,7 @@
         SkScalar radius = SkScalarHalf(strokeWidth) - 0.5f;
         SkScalar centerX = SkScalarHalf(endInterval);
 
-        vertices.writeQuad(GrQuad(rect, matrix),
+        vertices.writeQuad(GrQuad::MakeFromRect(rect, matrix),
                            GrVertexWriter::TriStripFromRect(dashRect),
                            intervalLength,
                            radius,
@@ -175,7 +175,7 @@
         rectParam.set(halfOffLen                 + 0.5f, -halfStroke + 0.5f,
                       halfOffLen + startInterval - 0.5f,  halfStroke - 0.5f);
 
-        vertices.writeQuad(GrQuad(rect, matrix),
+        vertices.writeQuad(GrQuad::MakeFromRect(rect, matrix),
                            GrVertexWriter::TriStripFromRect(dashRect),
                            intervalLength,
                            rectParam);
@@ -209,7 +209,7 @@
         SkScalar fPerpendicularScale;
     };
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const LineData& geometry,
                                           SkPaint::Cap cap,
@@ -257,14 +257,17 @@
         return flags;
     }
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
+    GrProcessorSet::Analysis finalize(
+            const GrCaps& caps, const GrAppliedClip* clip, GrFSAAType fsaaType,
+            GrClampType clampType) override {
         GrProcessorAnalysisCoverage coverage;
         if (AAMode::kNone == fAAMode && !clip->numClipCoverageFragmentProcessors()) {
             coverage = GrProcessorAnalysisCoverage::kNone;
         } else {
             coverage = GrProcessorAnalysisCoverage::kSingleChannel;
         }
-        auto analysis = fProcessorSet.finalize(fColor, coverage, clip, false, caps, &fColor);
+        auto analysis = fProcessorSet.finalize(
+                fColor, coverage, clip, fStencilSettings, fsaaType, caps, clampType, &fColor);
         fUsesLocalCoords = analysis.usesLocalCoords();
         return analysis;
     }
@@ -586,7 +589,7 @@
                             draws[i].fLineLength, draws[i].fHalfDevStroke, draws[i].fIntervals[0],
                             draws[i].fIntervals[1], draws[i].fStrokeWidth, capType);
                 } else {
-                    vertices.writeQuad(GrQuad(rects[rectIndex], geom.fSrcRotInv));
+                    vertices.writeQuad(GrQuad::MakeFromRect(rects[rectIndex], geom.fSrcRotInv));
                 }
             }
             rectIndex++;
@@ -599,7 +602,7 @@
                             draws[i].fIntervals[0], draws[i].fHalfDevStroke, draws[i].fIntervals[0],
                             draws[i].fIntervals[1], draws[i].fStrokeWidth, capType);
                 } else {
-                    vertices.writeQuad(GrQuad(rects[rectIndex], geom.fSrcRotInv));
+                    vertices.writeQuad(GrQuad::MakeFromRect(rects[rectIndex], geom.fSrcRotInv));
                 }
             }
             rectIndex++;
@@ -612,18 +615,21 @@
                             draws[i].fIntervals[0], draws[i].fHalfDevStroke, draws[i].fIntervals[0],
                             draws[i].fIntervals[1], draws[i].fStrokeWidth, capType);
                 } else {
-                    vertices.writeQuad(GrQuad(rects[rectIndex], geom.fSrcRotInv));
+                    vertices.writeQuad(GrQuad::MakeFromRect(rects[rectIndex], geom.fSrcRotInv));
                 }
             }
             rectIndex++;
         }
-        uint32_t pipelineFlags = 0;
+        helper.recordDraw(target, std::move(gp));
+    }
+
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        auto pipelineFlags = GrPipeline::InputFlags::kNone;
         if (AAMode::kCoverageWithMSAA == fAAMode) {
-            pipelineFlags |= GrPipeline::kHWAntialias_Flag;
+            pipelineFlags |= GrPipeline::InputFlags::kHWAntialias;
         }
-        auto pipe = target->makePipeline(pipelineFlags, std::move(fProcessorSet),
-                                         target->detachAppliedClip());
-        helper.recordDraw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState);
+        flushState->executeDrawsAndUploadsForMeshDrawOp(
+                this, chainBounds, std::move(fProcessorSet), pipelineFlags, fStencilSettings);
     }
 
     CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
@@ -679,7 +685,7 @@
     typedef GrMeshDrawOp INHERITED;
 };
 
-std::unique_ptr<GrDrawOp> GrDashOp::MakeDashLineOp(GrContext* context,
+std::unique_ptr<GrDrawOp> GrDashOp::MakeDashLineOp(GrRecordingContext* context,
                                                    GrPaint&& paint,
                                                    const SkMatrix& viewMatrix,
                                                    const SkPoint pts[2],
diff --git a/src/gpu/ops/GrDashOp.h b/src/gpu/ops/GrDashOp.h
index eb48280..19ece56 100644
--- a/src/gpu/ops/GrDashOp.h
+++ b/src/gpu/ops/GrDashOp.h
@@ -11,9 +11,9 @@
 #include "GrTypes.h"
 #include "SkPathEffect.h"
 
-class GrContext;
 class GrDrawOp;
 class GrPaint;
+class GrRecordingContext;
 class GrStyle;
 struct GrUserStencilSettings;
 
@@ -25,7 +25,7 @@
 };
 static const int kAAModeCnt = static_cast<int>(AAMode::kCoverageWithMSAA) + 1;
 
-std::unique_ptr<GrDrawOp> MakeDashLineOp(GrContext*,
+std::unique_ptr<GrDrawOp> MakeDashLineOp(GrRecordingContext*,
                                          GrPaint&&,
                                          const SkMatrix& viewMatrix,
                                          const SkPoint pts[2],
diff --git a/src/gpu/ops/GrDebugMarkerOp.cpp b/src/gpu/ops/GrDebugMarkerOp.cpp
index a6d06dce1..69672b5 100644
--- a/src/gpu/ops/GrDebugMarkerOp.cpp
+++ b/src/gpu/ops/GrDebugMarkerOp.cpp
@@ -8,13 +8,13 @@
 #include "GrDebugMarkerOp.h"
 
 #include "GrCaps.h"
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrGpuCommandBuffer.h"
 #include "GrMemoryPool.h"
 #include "GrOpFlushState.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 
-std::unique_ptr<GrOp> GrDebugMarkerOp::Make(GrContext* context,
+std::unique_ptr<GrOp> GrDebugMarkerOp::Make(GrRecordingContext* context,
                                             GrRenderTargetProxy* proxy,
                                             const SkString& str) {
     GrOpMemoryPool* pool = context->priv().opMemoryPool();
diff --git a/src/gpu/ops/GrDebugMarkerOp.h b/src/gpu/ops/GrDebugMarkerOp.h
index e8fc78c..998c80a 100644
--- a/src/gpu/ops/GrDebugMarkerOp.h
+++ b/src/gpu/ops/GrDebugMarkerOp.h
@@ -12,12 +12,13 @@
 #include "GrRenderTargetProxy.h"
 
 class GrOpFlushState;
+class GrRecordingContext;
 
 class GrDebugMarkerOp final : public GrOp {
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrOp> Make(GrContext*,
+    static std::unique_ptr<GrOp> Make(GrRecordingContext*,
                                       GrRenderTargetProxy*,
                                       const SkString&);
 
diff --git a/src/gpu/ops/GrDefaultPathRenderer.cpp b/src/gpu/ops/GrDefaultPathRenderer.cpp
index c8a7966..8ea4bbf 100644
--- a/src/gpu/ops/GrDefaultPathRenderer.cpp
+++ b/src/gpu/ops/GrDefaultPathRenderer.cpp
@@ -6,7 +6,8 @@
  */
 
 #include "GrDefaultPathRenderer.h"
-#include "GrContext.h"
+
+#include "GrCaps.h"
 #include "GrDefaultGeoProcFactory.h"
 #include "GrDrawOpTest.h"
 #include "GrFillRectOp.h"
@@ -65,14 +66,11 @@
 class PathGeoBuilder {
 public:
     PathGeoBuilder(GrPrimitiveType primitiveType, GrMeshDrawOp::Target* target,
-                   sk_sp<const GrGeometryProcessor> geometryProcessor, const GrPipeline* pipeline,
-                   const GrPipeline::FixedDynamicState* fixedDynamicState)
+                   sk_sp<const GrGeometryProcessor> geometryProcessor)
             : fPrimitiveType(primitiveType)
             , fTarget(target)
             , fVertexStride(sizeof(SkPoint))
             , fGeometryProcessor(std::move(geometryProcessor))
-            , fPipeline(pipeline)
-            , fFixedDynamicState(fixedDynamicState)
             , fFirstIndex(0)
             , fIndicesInChunk(0)
             , fIndices(nullptr) {
@@ -278,7 +276,7 @@
                                  vertexCount - 1, GrPrimitiveRestart::kNo);
             }
             mesh->setVertexData(std::move(fVertexBuffer), fFirstVertex);
-            fTarget->draw(fGeometryProcessor, fPipeline, fFixedDynamicState, mesh);
+            fTarget->recordDraw(fGeometryProcessor, mesh);
         }
 
         fTarget->putBackIndices((size_t)(fIndicesInChunk - indexCount));
@@ -315,8 +313,6 @@
     GrMeshDrawOp::Target* fTarget;
     size_t fVertexStride;
     sk_sp<const GrGeometryProcessor> fGeometryProcessor;
-    const GrPipeline* fPipeline;
-    const GrPipeline::FixedDynamicState* fFixedDynamicState;
 
     sk_sp<const GrBuffer> fVertexBuffer;
     int fFirstVertex;
@@ -339,7 +335,7 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const SkPath& path,
                                           SkScalar tolerance,
@@ -391,11 +387,14 @@
 
     FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
         GrProcessorAnalysisCoverage gpCoverage =
                 this->coverage() == 0xFF ? GrProcessorAnalysisCoverage::kNone
                                          : GrProcessorAnalysisCoverage::kSingleChannel;
-        return fHelper.finalizeProcessors(caps, clip, gpCoverage, &fColor);
+        // This Op uses uniform (not vertex) color, so doesn't need to track wide color.
+        return fHelper.finalizeProcessors(
+                caps, clip, fsaaType, clampType, gpCoverage, &fColor, nullptr);
     }
 
 private:
@@ -429,9 +428,7 @@
         } else {
             primitiveType = GrPrimitiveType::kTriangles;
         }
-        auto pipe = fHelper.makePipeline(target);
-        PathGeoBuilder pathGeoBuilder(primitiveType, target, std::move(gp), pipe.fPipeline,
-                                      pipe.fFixedDynamicState);
+        PathGeoBuilder pathGeoBuilder(primitiveType, target, std::move(gp));
 
         // fill buffers
         for (int i = 0; i < instanceCount; i++) {
@@ -440,6 +437,10 @@
         }
     }
 
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
+    }
+
     CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
         DefaultPathOp* that = t->cast<DefaultPathOp>();
         if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
@@ -496,7 +497,7 @@
                                              const SkMatrix& viewMatrix,
                                              const GrShape& shape,
                                              bool stencilOnly) {
-    GrContext* context = renderTargetContext->surfPriv().getContext();
+    auto context = renderTargetContext->surfPriv().getContext();
 
     SkASSERT(GrAAType::kCoverage != aaType);
     SkPath path;
@@ -638,15 +639,19 @@
 
 GrPathRenderer::CanDrawPath
 GrDefaultPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
-    bool isHairline = IsStrokeHairlineOrEquivalent(args.fShape->style(), *args.fViewMatrix, nullptr);
+    bool isHairline = IsStrokeHairlineOrEquivalent(
+            args.fShape->style(), *args.fViewMatrix, nullptr);
     // If we aren't a single_pass_shape or hairline, we require stencil buffers.
     if (!(single_pass_shape(*args.fShape) || isHairline) &&
         (args.fCaps->avoidStencilBuffers() || args.fTargetIsWrappedVkSecondaryCB)) {
         return CanDrawPath::kNo;
     }
-    // This can draw any path with any simple fill style but doesn't do coverage-based antialiasing.
-    if (GrAAType::kCoverage == args.fAAType ||
-        (!args.fShape->style().isSimpleFill() && !isHairline)) {
+    // If antialiasing is required, we only support MSAA.
+    if (AATypeFlags::kNone != args.fAATypeFlags && !(AATypeFlags::kMSAA & args.fAATypeFlags)) {
+        return CanDrawPath::kNo;
+    }
+    // This can draw any path with any simple fill style.
+    if (!args.fShape->style().isSimpleFill() && !isHairline) {
         return CanDrawPath::kNo;
     }
     // This is the fallback renderer for when a path is too complicated for the others to draw.
@@ -656,14 +661,13 @@
 bool GrDefaultPathRenderer::onDrawPath(const DrawPathArgs& args) {
     GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(),
                               "GrDefaultPathRenderer::onDrawPath");
-    return this->internalDrawPath(args.fRenderTargetContext,
-                                  std::move(args.fPaint),
-                                  args.fAAType,
-                                  *args.fUserStencilSettings,
-                                  *args.fClip,
-                                  *args.fViewMatrix,
-                                  *args.fShape,
-                                  false);
+    GrAAType aaType = (AATypeFlags::kNone != args.fAATypeFlags)
+            ? GrAAType::kMSAA
+            : GrAAType::kNone;
+
+    return this->internalDrawPath(
+            args.fRenderTargetContext, std::move(args.fPaint), aaType, *args.fUserStencilSettings,
+            *args.fClip, *args.fViewMatrix, *args.fShape, false);
 }
 
 void GrDefaultPathRenderer::onStencilPath(const StencilPathArgs& args) {
@@ -674,9 +678,11 @@
     GrPaint paint;
     paint.setXPFactory(GrDisableColorXPFactory::Get());
 
-    this->internalDrawPath(args.fRenderTargetContext, std::move(paint), args.fAAType,
-                           GrUserStencilSettings::kUnused, *args.fClip, *args.fViewMatrix,
-                           *args.fShape, true);
+    auto aaType = (GrAA::kYes == args.fDoStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone;
+
+    this->internalDrawPath(
+            args.fRenderTargetContext, std::move(paint), aaType, GrUserStencilSettings::kUnused,
+            *args.fClip, *args.fViewMatrix, *args.fShape, true);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/gpu/ops/GrDrawAtlasOp.cpp b/src/gpu/ops/GrDrawAtlasOp.cpp
index fdabd64..ed940f5 100644
--- a/src/gpu/ops/GrDrawAtlasOp.cpp
+++ b/src/gpu/ops/GrDrawAtlasOp.cpp
@@ -6,13 +6,73 @@
  */
 
 #include "GrDrawAtlasOp.h"
+
+#include "GrCaps.h"
+#include "GrDefaultGeoProcFactory.h"
 #include "GrDrawOpTest.h"
 #include "GrOpFlushState.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
+#include "GrSimpleMeshDrawOpHelper.h"
 #include "SkGr.h"
 #include "SkRSXform.h"
 #include "SkRandom.h"
 #include "SkRectPriv.h"
 
+namespace {
+
+class DrawAtlasOp final : public GrMeshDrawOp {
+private:
+    using Helper = GrSimpleMeshDrawOpHelper;
+
+public:
+    DEFINE_OP_CLASS_ID
+
+    DrawAtlasOp(const Helper::MakeArgs&, const SkPMColor4f& color,
+                const SkMatrix& viewMatrix, GrAAType, int spriteCount, const SkRSXform* xforms,
+                const SkRect* rects, const SkColor* colors);
+
+    const char* name() const override { return "DrawAtlasOp"; }
+
+    void visitProxies(const VisitProxyFunc& func, VisitorType) const override {
+        fHelper.visitProxies(func);
+    }
+
+#ifdef SK_DEBUG
+    SkString dumpInfo() const override;
+#endif
+
+    FixedFunctionFlags fixedFunctionFlags() const override;
+
+    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrFSAAType,
+                                      GrClampType) override;
+
+private:
+    void onPrepareDraws(Target*) override;
+    void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
+
+    const SkPMColor4f& color() const { return fColor; }
+    const SkMatrix& viewMatrix() const { return fViewMatrix; }
+    bool hasColors() const { return fHasColors; }
+    int quadCount() const { return fQuadCount; }
+
+    CombineResult onCombineIfPossible(GrOp* t, const GrCaps&) override;
+
+    struct Geometry {
+        SkPMColor4f fColor;
+        SkTArray<uint8_t, true> fVerts;
+    };
+
+    SkSTArray<1, Geometry, true> fGeoData;
+    Helper fHelper;
+    SkMatrix fViewMatrix;
+    SkPMColor4f fColor;
+    int fQuadCount;
+    bool fHasColors;
+
+    typedef GrMeshDrawOp INHERITED;
+};
+
 static sk_sp<GrGeometryProcessor> make_gp(const GrShaderCaps* shaderCaps,
                                           bool hasColors,
                                           const SkPMColor4f& color,
@@ -27,9 +87,9 @@
                                          LocalCoords::kHasExplicit_Type, viewMatrix);
 }
 
-GrDrawAtlasOp::GrDrawAtlasOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
-                             const SkMatrix& viewMatrix, GrAAType aaType, int spriteCount,
-                             const SkRSXform* xforms, const SkRect* rects, const SkColor* colors)
+DrawAtlasOp::DrawAtlasOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
+                         const SkMatrix& viewMatrix, GrAAType aaType, int spriteCount,
+                         const SkRSXform* xforms, const SkRect* rects, const SkColor* colors)
         : INHERITED(ClassID()), fHelper(helperArgs, aaType), fColor(color) {
     SkASSERT(xforms);
     SkASSERT(rects);
@@ -110,7 +170,7 @@
 }
 
 #ifdef SK_DEBUG
-SkString GrDrawAtlasOp::dumpInfo() const {
+SkString DrawAtlasOp::dumpInfo() const {
     SkString string;
     for (const auto& geo : fGeoData) {
         string.appendf("Color: 0x%08x, Quads: %d\n", geo.fColor.toBytes_RGBA(),
@@ -122,7 +182,7 @@
 }
 #endif
 
-void GrDrawAtlasOp::onPrepareDraws(Target* target) {
+void DrawAtlasOp::onPrepareDraws(Target* target) {
     // Setup geometry processor
     sk_sp<GrGeometryProcessor> gp(make_gp(target->caps().shaderCaps(),
                                           this->hasColors(),
@@ -148,12 +208,15 @@
         memcpy(vertPtr, args.fVerts.begin(), allocSize);
         vertPtr += allocSize;
     }
-    auto pipe = fHelper.makePipeline(target);
-    helper.recordDraw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState);
+    helper.recordDraw(target, std::move(gp));
 }
 
-GrOp::CombineResult GrDrawAtlasOp::onCombineIfPossible(GrOp* t, const GrCaps& caps) {
-    GrDrawAtlasOp* that = t->cast<GrDrawAtlasOp>();
+void DrawAtlasOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
+    fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
+}
+
+GrOp::CombineResult DrawAtlasOp::onCombineIfPossible(GrOp* t, const GrCaps& caps) {
+    DrawAtlasOp* that = t->cast<DrawAtlasOp>();
 
     if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
         return CombineResult::kCannotCombine;
@@ -178,25 +241,42 @@
     return CombineResult::kMerged;
 }
 
-GrDrawOp::FixedFunctionFlags GrDrawAtlasOp::fixedFunctionFlags() const {
+GrDrawOp::FixedFunctionFlags DrawAtlasOp::fixedFunctionFlags() const {
     return fHelper.fixedFunctionFlags();
 }
 
-GrProcessorSet::Analysis GrDrawAtlasOp::finalize(const GrCaps& caps, const GrAppliedClip* clip) {
+GrProcessorSet::Analysis DrawAtlasOp::finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                               GrFSAAType fsaaType, GrClampType clampType) {
     GrProcessorAnalysisColor gpColor;
     if (this->hasColors()) {
         gpColor.setToUnknown();
     } else {
         gpColor.setToConstant(fColor);
     }
-    auto result = fHelper.finalizeProcessors(caps, clip, GrProcessorAnalysisCoverage::kNone,
-                                             &gpColor);
+    auto result = fHelper.finalizeProcessors(
+            caps, clip, fsaaType, clampType, GrProcessorAnalysisCoverage::kNone, &gpColor);
     if (gpColor.isConstant(&fColor)) {
         fHasColors = false;
     }
     return result;
 }
 
+} // anonymous namespace
+
+std::unique_ptr<GrDrawOp> GrDrawAtlasOp::Make(GrRecordingContext* context,
+                                              GrPaint&& paint,
+                                              const SkMatrix& viewMatrix,
+                                              GrAAType aaType,
+                                              int spriteCount,
+                                              const SkRSXform* xforms,
+                                              const SkRect* rects,
+                                              const SkColor* colors) {
+    return GrSimpleMeshDrawOpHelper::FactoryHelper<DrawAtlasOp>(context, std::move(paint),
+                                                                viewMatrix, aaType,
+                                                                spriteCount, xforms,
+                                                                rects, colors);
+}
+
 #if GR_TEST_UTILS
 
 static SkRSXform random_xform(SkRandom* random) {
@@ -240,7 +320,7 @@
     }
 }
 
-GR_DRAW_OP_TEST_DEFINE(GrDrawAtlasOp) {
+GR_DRAW_OP_TEST_DEFINE(DrawAtlasOp) {
     uint32_t spriteCount = random->nextRangeU(1, 100);
 
     SkTArray<SkRSXform> xforms(spriteCount);
diff --git a/src/gpu/ops/GrDrawAtlasOp.h b/src/gpu/ops/GrDrawAtlasOp.h
index 92820e9..278ae42 100644
--- a/src/gpu/ops/GrDrawAtlasOp.h
+++ b/src/gpu/ops/GrDrawAtlasOp.h
@@ -8,71 +8,23 @@
 #ifndef GrDrawAtlasOp_DEFINED
 #define GrDrawAtlasOp_DEFINED
 
-#include "GrColor.h"
-#include "GrDefaultGeoProcFactory.h"
-#include "GrMeshDrawOp.h"
-#include "GrSimpleMeshDrawOpHelper.h"
+#include "GrTypesPriv.h"
+#include "SkRefCnt.h"
 
-class GrDrawAtlasOp final : public GrMeshDrawOp {
-private:
-    using Helper = GrSimpleMeshDrawOpHelper;
+class GrDrawOp;
+class GrPaint;
+class GrRecordingContext;
+class SkMatrix;
 
-public:
-    DEFINE_OP_CLASS_ID
-
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
-                                          GrPaint&& paint,
-                                          const SkMatrix& viewMatrix,
-                                          GrAAType aaType,
-                                          int spriteCount,
-                                          const SkRSXform* xforms,
-                                          const SkRect* rects,
-                                          const SkColor* colors) {
-        return Helper::FactoryHelper<GrDrawAtlasOp>(context, std::move(paint), viewMatrix, aaType,
-                                                    spriteCount, xforms, rects, colors);
-    }
-
-    GrDrawAtlasOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
-                  const SkMatrix& viewMatrix, GrAAType, int spriteCount, const SkRSXform* xforms,
-                  const SkRect* rects, const SkColor* colors);
-
-    const char* name() const override { return "DrawAtlasOp"; }
-
-    void visitProxies(const VisitProxyFunc& func, VisitorType) const override {
-        fHelper.visitProxies(func);
-    }
-
-#ifdef SK_DEBUG
-    SkString dumpInfo() const override;
-#endif
-
-    FixedFunctionFlags fixedFunctionFlags() const override;
-
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override;
-
-private:
-    void onPrepareDraws(Target*) override;
-
-    const SkPMColor4f& color() const { return fColor; }
-    const SkMatrix& viewMatrix() const { return fViewMatrix; }
-    bool hasColors() const { return fHasColors; }
-    int quadCount() const { return fQuadCount; }
-
-    CombineResult onCombineIfPossible(GrOp* t, const GrCaps&) override;
-
-    struct Geometry {
-        SkPMColor4f fColor;
-        SkTArray<uint8_t, true> fVerts;
-    };
-
-    SkSTArray<1, Geometry, true> fGeoData;
-    Helper fHelper;
-    SkMatrix fViewMatrix;
-    SkPMColor4f fColor;
-    int fQuadCount;
-    bool fHasColors;
-
-    typedef GrMeshDrawOp INHERITED;
+namespace GrDrawAtlasOp {
+    std::unique_ptr<GrDrawOp> Make(GrRecordingContext*,
+                                   GrPaint&&,
+                                   const SkMatrix& viewMatrix,
+                                   GrAAType,
+                                   int spriteCount,
+                                   const SkRSXform* xforms,
+                                   const SkRect* rects,
+                                   const SkColor* colors);
 };
 
 #endif
diff --git a/src/gpu/ops/GrDrawOp.h b/src/gpu/ops/GrDrawOp.h
index da54132..bc574fe 100644
--- a/src/gpu/ops/GrDrawOp.h
+++ b/src/gpu/ops/GrDrawOp.h
@@ -43,7 +43,8 @@
      * at this time the op must report whether a copy of the destination (or destination texture
      * itself) needs to be provided to the GrXferProcessor when this op executes.
      */
-    virtual GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*) = 0;
+    virtual GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrFSAAType,
+                                              GrClampType) = 0;
 
 #ifdef SK_DEBUG
     bool fAddDrawOpCalled = false;
diff --git a/src/gpu/ops/GrDrawPathOp.cpp b/src/gpu/ops/GrDrawPathOp.cpp
index e162b05..72910a1 100644
--- a/src/gpu/ops/GrDrawPathOp.cpp
+++ b/src/gpu/ops/GrDrawPathOp.cpp
@@ -8,17 +8,29 @@
 #include "GrDrawPathOp.h"
 #include "GrAppliedClip.h"
 #include "GrMemoryPool.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrRenderTargetPriv.h"
 #include "SkTemplates.h"
 
+static constexpr GrUserStencilSettings kCoverPass{
+        GrUserStencilSettings::StaticInit<
+                0x0000,
+                GrUserStencilTest::kNotEqual,
+                0xffff,
+                GrUserStencilOp::kZero,
+                GrUserStencilOp::kKeep,
+                0xffff>()
+};
+
 GrDrawPathOpBase::GrDrawPathOpBase(uint32_t classID, const SkMatrix& viewMatrix, GrPaint&& paint,
-                                   GrPathRendering::FillType fill, GrAAType aaType)
+                                   GrPathRendering::FillType fill, GrAA aa)
         : INHERITED(classID)
         , fViewMatrix(viewMatrix)
         , fInputColor(paint.getColor4f())
         , fFillType(fill)
-        , fAAType(aaType)
+        , fDoAA(GrAA::kYes == aa)
         , fProcessorSet(std::move(paint)) {}
 
 #ifdef SK_DEBUG
@@ -31,18 +43,9 @@
 #endif
 
 GrPipeline::InitArgs GrDrawPathOpBase::pipelineInitArgs(const GrOpFlushState& state) {
-    static constexpr GrUserStencilSettings kCoverPass{
-            GrUserStencilSettings::StaticInit<
-                    0x0000,
-                    GrUserStencilTest::kNotEqual,
-                    0xffff,
-                    GrUserStencilOp::kZero,
-                    GrUserStencilOp::kKeep,
-                    0xffff>()
-    };
     GrPipeline::InitArgs args;
-    if (GrAATypeIsHW(fAAType)) {
-        args.fFlags |= GrPipeline::kHWAntialias_Flag;
+    if (fDoAA) {
+        args.fInputFlags |= GrPipeline::InputFlags::kHWAntialias;
     }
     args.fUserStencil = &kCoverPass;
     args.fCaps = &state.caps();
@@ -51,6 +54,15 @@
     return args;
 }
 
+const GrProcessorSet::Analysis& GrDrawPathOpBase::doProcessorAnalysis(
+        const GrCaps& caps, const GrAppliedClip* clip, GrFSAAType fsaaType,
+        GrClampType clampType) {
+    fAnalysis = fProcessorSet.finalize(
+            fInputColor, GrProcessorAnalysisCoverage::kNone, clip, &kCoverPass, fsaaType, caps,
+            clampType, &fInputColor);
+    return fAnalysis;
+}
+
 //////////////////////////////////////////////////////////////////////////////
 
 void init_stencil_pass_settings(const GrOpFlushState& flushState,
@@ -63,14 +75,14 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
-std::unique_ptr<GrDrawOp> GrDrawPathOp::Make(GrContext* context,
+std::unique_ptr<GrDrawOp> GrDrawPathOp::Make(GrRecordingContext* context,
                                              const SkMatrix& viewMatrix,
                                              GrPaint&& paint,
-                                             GrAAType aaType,
+                                             GrAA aa,
                                              GrPath* path) {
     GrOpMemoryPool* pool = context->priv().opMemoryPool();
 
-    return pool->allocate<GrDrawPathOp>(viewMatrix, std::move(paint), aaType, path);
+    return pool->allocate<GrDrawPathOp>(viewMatrix, std::move(paint), aa, path);
 }
 
 void GrDrawPathOp::onExecute(GrOpFlushState* state, const SkRect& chainBounds) {
diff --git a/src/gpu/ops/GrDrawPathOp.h b/src/gpu/ops/GrDrawPathOp.h
index 8f67104..3d7c48c 100644
--- a/src/gpu/ops/GrDrawPathOp.h
+++ b/src/gpu/ops/GrDrawPathOp.h
@@ -17,20 +17,21 @@
 #include "GrStencilSettings.h"
 
 class GrPaint;
+class GrRecordingContext;
 
 class GrDrawPathOpBase : public GrDrawOp {
 protected:
     GrDrawPathOpBase(uint32_t classID, const SkMatrix& viewMatrix, GrPaint&&,
-                     GrPathRendering::FillType, GrAAType);
+                     GrPathRendering::FillType, GrAA);
 
     FixedFunctionFlags fixedFunctionFlags() const override {
-        if (GrAATypeIsHW(fAAType)) {
-            return FixedFunctionFlags::kUsesHWAA | FixedFunctionFlags::kUsesStencil;
-        }
-        return FixedFunctionFlags::kUsesStencil;
+        return (fDoAA)
+                ? FixedFunctionFlags::kUsesHWAA | FixedFunctionFlags::kUsesStencil
+                : FixedFunctionFlags::kUsesStencil;
     }
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
-        return this->doProcessorAnalysis(caps, clip);
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
+        return this->doProcessorAnalysis(caps, clip, fsaaType, clampType);
     }
 
     void visitProxies(const VisitProxyFunc& func, VisitorType) const override {
@@ -44,13 +45,8 @@
     const GrProcessorSet& processors() const { return fProcessorSet; }
     GrProcessorSet detachProcessors() { return std::move(fProcessorSet); }
     inline GrPipeline::InitArgs pipelineInitArgs(const GrOpFlushState&);
-    const GrProcessorSet::Analysis& doProcessorAnalysis(const GrCaps& caps,
-                                                        const GrAppliedClip* clip) {
-        bool isMixedSamples = GrAAType::kMixedSamples == fAAType;
-        fAnalysis = fProcessorSet.finalize(fInputColor, GrProcessorAnalysisCoverage::kNone, clip,
-                                           isMixedSamples, caps, &fInputColor);
-        return fAnalysis;
-    }
+    const GrProcessorSet::Analysis& doProcessorAnalysis(
+            const GrCaps&, const GrAppliedClip*, GrFSAAType, GrClampType);
     const GrProcessorSet::Analysis& processorAnalysis() const {
         SkASSERT(fAnalysis.isInitialized());
         return fAnalysis;
@@ -63,7 +59,7 @@
     SkPMColor4f fInputColor;
     GrProcessorSet::Analysis fAnalysis;
     GrPathRendering::FillType fFillType;
-    GrAAType fAAType;
+    bool fDoAA;
     GrProcessorSet fProcessorSet;
 
     typedef GrDrawOp INHERITED;
@@ -73,11 +69,8 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext*,
-                                          const SkMatrix& viewMatrix,
-                                          GrPaint&&,
-                                          GrAAType,
-                                          GrPath*);
+    static std::unique_ptr<GrDrawOp> Make(
+            GrRecordingContext*, const SkMatrix& viewMatrix, GrPaint&&, GrAA, GrPath*);
 
     const char* name() const override { return "DrawPath"; }
 
@@ -88,8 +81,9 @@
 private:
     friend class GrOpMemoryPool; // for ctor
 
-    GrDrawPathOp(const SkMatrix& viewMatrix, GrPaint&& paint, GrAAType aaType, const GrPath* path)
-            : GrDrawPathOpBase(ClassID(), viewMatrix, std::move(paint), path->getFillType(), aaType)
+    GrDrawPathOp(const SkMatrix& viewMatrix, GrPaint&& paint, GrAA aa, const GrPath* path)
+            : GrDrawPathOpBase(
+                    ClassID(), viewMatrix, std::move(paint), path->getFillType(), aa)
             , fPath(path) {
         this->setTransformedBounds(path->getBounds(), viewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
     }
diff --git a/src/gpu/ops/GrDrawVerticesOp.cpp b/src/gpu/ops/GrDrawVerticesOp.cpp
index 2b368fe..bb51887 100644
--- a/src/gpu/ops/GrDrawVerticesOp.cpp
+++ b/src/gpu/ops/GrDrawVerticesOp.cpp
@@ -9,31 +9,134 @@
 #include "GrCaps.h"
 #include "GrDefaultGeoProcFactory.h"
 #include "GrOpFlushState.h"
+#include "GrSimpleMeshDrawOpHelper.h"
 #include "SkGr.h"
 #include "SkRectPriv.h"
 
-std::unique_ptr<GrDrawOp> GrDrawVerticesOp::Make(GrContext* context,
-                                                 GrPaint&& paint,
-                                                 sk_sp<SkVertices> vertices,
-                                                 const SkVertices::Bone bones[],
-                                                 int boneCount,
-                                                 const SkMatrix& viewMatrix,
-                                                 GrAAType aaType,
-                                                 sk_sp<GrColorSpaceXform> colorSpaceXform,
-                                                 GrPrimitiveType* overridePrimType) {
-    SkASSERT(vertices);
-    GrPrimitiveType primType = overridePrimType ? *overridePrimType
-                                                : SkVertexModeToGrPrimitiveType(vertices->mode());
-    return Helper::FactoryHelper<GrDrawVerticesOp>(context, std::move(paint), std::move(vertices),
-                                                   bones, boneCount, primType, aaType,
-                                                   std::move(colorSpaceXform), viewMatrix);
-}
+namespace {
 
-GrDrawVerticesOp::GrDrawVerticesOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
-                                   sk_sp<SkVertices> vertices, const SkVertices::Bone bones[],
-                                   int boneCount, GrPrimitiveType primitiveType, GrAAType aaType,
-                                   sk_sp<GrColorSpaceXform> colorSpaceXform,
-                                   const SkMatrix& viewMatrix)
+class DrawVerticesOp final : public GrMeshDrawOp {
+private:
+    using Helper = GrSimpleMeshDrawOpHelper;
+
+public:
+    DEFINE_OP_CLASS_ID
+
+    DrawVerticesOp(const Helper::MakeArgs&, const SkPMColor4f&, sk_sp<SkVertices>,
+                   const SkVertices::Bone bones[], int boneCount, GrPrimitiveType, GrAAType,
+                   sk_sp<GrColorSpaceXform>, const SkMatrix& viewMatrix);
+
+    const char* name() const override { return "DrawVerticesOp"; }
+
+    void visitProxies(const VisitProxyFunc& func, VisitorType) const override {
+        fHelper.visitProxies(func);
+    }
+
+#ifdef SK_DEBUG
+    SkString dumpInfo() const override;
+#endif
+
+    FixedFunctionFlags fixedFunctionFlags() const override;
+
+    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrFSAAType,
+                                      GrClampType) override;
+
+private:
+    enum class ColorArrayType {
+        kPremulGrColor,
+        kSkColor,
+    };
+
+    void onPrepareDraws(Target*) override;
+    void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
+
+    void drawVolatile(Target*);
+    void drawNonVolatile(Target*);
+
+    void fillBuffers(bool hasColorAttribute,
+                     bool hasLocalCoordsAttribute,
+                     size_t vertexStride,
+                     void* verts,
+                     uint16_t* indices) const;
+
+    void drawVertices(Target*,
+                      sk_sp<const GrGeometryProcessor>,
+                      sk_sp<const GrBuffer> vertexBuffer,
+                      int firstVertex,
+                      sk_sp<const GrBuffer> indexBuffer,
+                      int firstIndex);
+
+    sk_sp<GrGeometryProcessor> makeGP(const GrShaderCaps* shaderCaps,
+                                      bool* hasColorAttribute,
+                                      bool* hasLocalCoordAttribute) const;
+
+    GrPrimitiveType primitiveType() const { return fPrimitiveType; }
+    bool combinablePrimitive() const {
+        return GrPrimitiveType::kTriangles == fPrimitiveType ||
+               GrPrimitiveType::kLines == fPrimitiveType ||
+               GrPrimitiveType::kPoints == fPrimitiveType;
+    }
+
+    CombineResult onCombineIfPossible(GrOp* t, const GrCaps&) override;
+
+    struct Mesh {
+        SkPMColor4f fColor;  // Used if this->hasPerVertexColors() is false.
+        sk_sp<SkVertices> fVertices;
+        SkMatrix fViewMatrix;
+        bool fIgnoreTexCoords;
+        bool fIgnoreColors;
+
+        bool hasExplicitLocalCoords() const {
+            return fVertices->hasTexCoords() && !fIgnoreTexCoords;
+        }
+
+        bool hasPerVertexColors() const {
+            return fVertices->hasColors() && !fIgnoreColors;
+        }
+    };
+
+    bool isIndexed() const {
+        // Consistency enforced in onCombineIfPossible.
+        return fMeshes[0].fVertices->hasIndices();
+    }
+
+    bool requiresPerVertexColors() const {
+        return SkToBool(kRequiresPerVertexColors_Flag & fFlags);
+    }
+
+    bool anyMeshHasExplicitLocalCoords() const {
+        return SkToBool(kAnyMeshHasExplicitLocalCoords_Flag & fFlags);
+    }
+
+    bool hasMultipleViewMatrices() const {
+        return SkToBool(kHasMultipleViewMatrices_Flag & fFlags);
+    }
+
+    enum Flags {
+        kRequiresPerVertexColors_Flag       = 0x1,
+        kAnyMeshHasExplicitLocalCoords_Flag = 0x2,
+        kHasMultipleViewMatrices_Flag       = 0x4,
+    };
+
+    Helper fHelper;
+    SkSTArray<1, Mesh, true> fMeshes;
+    // GrPrimitiveType is more expressive than fVertices.mode() so it is used instead and we ignore
+    // the SkVertices mode (though fPrimitiveType may have been inferred from it).
+    GrPrimitiveType fPrimitiveType;
+    uint32_t fFlags;
+    int fVertexCount;
+    int fIndexCount;
+    ColorArrayType fColorArrayType;
+    sk_sp<GrColorSpaceXform> fColorSpaceXform;
+
+    typedef GrMeshDrawOp INHERITED;
+};
+
+DrawVerticesOp::DrawVerticesOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
+                               sk_sp<SkVertices> vertices, const SkVertices::Bone bones[],
+                               int boneCount, GrPrimitiveType primitiveType, GrAAType aaType,
+                               sk_sp<GrColorSpaceXform> colorSpaceXform,
+                               const SkMatrix& viewMatrix)
         : INHERITED(ClassID())
         , fHelper(helperArgs, aaType)
         , fPrimitiveType(primitiveType)
@@ -51,19 +154,12 @@
     mesh.fVertices = std::move(vertices);
     mesh.fIgnoreTexCoords = false;
     mesh.fIgnoreColors = false;
-    mesh.fIgnoreBones = false;
 
     if (mesh.fVertices->hasBones() && bones) {
         // Perform the transformations on the CPU instead of the GPU.
         mesh.fVertices = mesh.fVertices->applyBones(bones, boneCount);
     } else {
-        if (bones && boneCount > 1) {
-            // NOTE: This should never be used. All bone transforms are being done on the CPU
-            // instead of the GPU.
-
-            // Copy the bone data.
-            fBones.assign(bones, bones + boneCount);
-        }
+        SkASSERT(!bones || boneCount == 1);
     }
 
     fFlags = 0;
@@ -73,9 +169,6 @@
     if (mesh.hasExplicitLocalCoords()) {
         fFlags |= kAnyMeshHasExplicitLocalCoords_Flag;
     }
-    if (mesh.hasBones()) {
-        fFlags |= kHasBones_Flag;
-    }
 
     // Special case for meshes with a world transform but no bone weights.
     // These will be considered normal vertices draws without bones.
@@ -92,30 +185,14 @@
         zeroArea = IsZeroArea::kNo;
     }
 
-    if (this->hasBones()) {
-        // We don't know the bounds if there are deformations involved, so attempt to calculate
-        // the maximum possible.
-        SkRect bounds = SkRect::MakeEmpty();
-        const SkRect originalBounds = bones[0].mapRect(mesh.fVertices->bounds());
-        for (int i = 1; i < boneCount; i++) {
-            const SkVertices::Bone& matrix = bones[i];
-            bounds.join(matrix.mapRect(originalBounds));
-        }
-
-        this->setTransformedBounds(bounds,
-                                   mesh.fViewMatrix,
-                                   HasAABloat::kNo,
-                                   zeroArea);
-    } else {
-        this->setTransformedBounds(mesh.fVertices->bounds(),
-                                   mesh.fViewMatrix,
-                                   HasAABloat::kNo,
-                                   zeroArea);
-    }
+    this->setTransformedBounds(mesh.fVertices->bounds(),
+                                mesh.fViewMatrix,
+                                HasAABloat::kNo,
+                                zeroArea);
 }
 
 #ifdef SK_DEBUG
-SkString GrDrawVerticesOp::dumpInfo() const {
+SkString DrawVerticesOp::dumpInfo() const {
     SkString string;
     string.appendf("PrimType: %d, MeshCount %d, VCount: %d, ICount: %d\n", (int)fPrimitiveType,
                    fMeshes.count(), fVertexCount, fIndexCount);
@@ -125,19 +202,20 @@
 }
 #endif
 
-GrDrawOp::FixedFunctionFlags GrDrawVerticesOp::fixedFunctionFlags() const {
+GrDrawOp::FixedFunctionFlags DrawVerticesOp::fixedFunctionFlags() const {
     return fHelper.fixedFunctionFlags();
 }
 
-GrProcessorSet::Analysis GrDrawVerticesOp::finalize(const GrCaps& caps, const GrAppliedClip* clip) {
+GrProcessorSet::Analysis DrawVerticesOp::finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                                  GrFSAAType fsaaType, GrClampType clampType) {
     GrProcessorAnalysisColor gpColor;
     if (this->requiresPerVertexColors()) {
         gpColor.setToUnknown();
     } else {
         gpColor.setToConstant(fMeshes.front().fColor);
     }
-    auto result = fHelper.finalizeProcessors(caps, clip, GrProcessorAnalysisCoverage::kNone,
-                                             &gpColor);
+    auto result = fHelper.finalizeProcessors(
+            caps, clip, fsaaType, clampType, GrProcessorAnalysisCoverage::kNone, &gpColor);
     if (gpColor.isConstant(&fMeshes.front().fColor)) {
         fMeshes.front().fIgnoreColors = true;
         fFlags &= ~kRequiresPerVertexColors_Flag;
@@ -150,10 +228,9 @@
     return result;
 }
 
-sk_sp<GrGeometryProcessor> GrDrawVerticesOp::makeGP(const GrShaderCaps* shaderCaps,
-                                                    bool* hasColorAttribute,
-                                                    bool* hasLocalCoordAttribute,
-                                                    bool* hasBoneAttribute) const {
+sk_sp<GrGeometryProcessor> DrawVerticesOp::makeGP(const GrShaderCaps* shaderCaps,
+                                                  bool* hasColorAttribute,
+                                                  bool* hasLocalCoordAttribute) const {
     using namespace GrDefaultGeoProcFactory;
     LocalCoords::Type localCoordsType;
     if (fHelper.usesLocalCoords()) {
@@ -186,28 +263,14 @@
 
     const SkMatrix& vm = this->hasMultipleViewMatrices() ? SkMatrix::I() : fMeshes[0].fViewMatrix;
 
-    // The bones are packed as 6 floats in column major order, so we can directly upload them to
-    // the GPU as groups of 3 vec2s.
-    Bones bones(reinterpret_cast<const float*>(fBones.data()), fBones.size());
-    *hasBoneAttribute = this->hasBones();
-
-    if (this->hasBones()) {
-        return GrDefaultGeoProcFactory::MakeWithBones(shaderCaps,
-                                                      color,
-                                                      Coverage::kSolid_Type,
-                                                      localCoordsType,
-                                                      bones,
-                                                      vm);
-    } else {
-        return GrDefaultGeoProcFactory::Make(shaderCaps,
-                                             color,
-                                             Coverage::kSolid_Type,
-                                             localCoordsType,
-                                             vm);
-    }
+    return GrDefaultGeoProcFactory::Make(shaderCaps,
+                                            color,
+                                            Coverage::kSolid_Type,
+                                            localCoordsType,
+                                            vm);
 }
 
-void GrDrawVerticesOp::onPrepareDraws(Target* target) {
+void DrawVerticesOp::onPrepareDraws(Target* target) {
     bool hasMapBufferSupport = GrCaps::kNone_MapFlags != target->caps().mapBufferFlags();
     if (fMeshes[0].fVertices->isVolatile() || !hasMapBufferSupport) {
         this->drawVolatile(target);
@@ -216,18 +279,16 @@
     }
 }
 
-void GrDrawVerticesOp::drawVolatile(Target* target) {
+void DrawVerticesOp::drawVolatile(Target* target) {
     bool hasColorAttribute;
     bool hasLocalCoordsAttribute;
-    bool hasBoneAttribute;
     sk_sp<GrGeometryProcessor> gp = this->makeGP(target->caps().shaderCaps(),
                                                  &hasColorAttribute,
-                                                 &hasLocalCoordsAttribute,
-                                                 &hasBoneAttribute);
+                                                 &hasLocalCoordsAttribute);
 
     // Allocate buffers.
     size_t vertexStride = gp->vertexStride();
-    sk_sp<const GrBuffer> vertexBuffer = nullptr;
+    sk_sp<const GrBuffer> vertexBuffer;
     int firstVertex = 0;
     void* verts = target->makeVertexSpace(vertexStride, fVertexCount, &vertexBuffer, &firstVertex);
     if (!verts) {
@@ -235,7 +296,7 @@
         return;
     }
 
-    sk_sp<const GrBuffer> indexBuffer = nullptr;
+    sk_sp<const GrBuffer> indexBuffer;
     int firstIndex = 0;
     uint16_t* indices = nullptr;
     if (this->isIndexed()) {
@@ -249,7 +310,6 @@
     // Fill the buffers.
     this->fillBuffers(hasColorAttribute,
                       hasLocalCoordsAttribute,
-                      hasBoneAttribute,
                       vertexStride,
                       verts,
                       indices);
@@ -259,16 +319,14 @@
                        firstIndex);
 }
 
-void GrDrawVerticesOp::drawNonVolatile(Target* target) {
+void DrawVerticesOp::drawNonVolatile(Target* target) {
     static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
 
     bool hasColorAttribute;
     bool hasLocalCoordsAttribute;
-    bool hasBoneAttribute;
     sk_sp<GrGeometryProcessor> gp = this->makeGP(target->caps().shaderCaps(),
                                                  &hasColorAttribute,
-                                                 &hasLocalCoordsAttribute,
-                                                 &hasBoneAttribute);
+                                                 &hasLocalCoordsAttribute);
 
     SkASSERT(fMeshes.count() == 1); // Non-volatile meshes should never combine.
 
@@ -286,10 +344,9 @@
     indexKeyBuilder.finish();
 
     // Try to grab data from the cache.
-    sk_sp<GrBuffer> vertexBuffer = rp->findByUniqueKey<GrBuffer>(vertexKey);
-    sk_sp<GrBuffer> indexBuffer = this->isIndexed() ?
-            rp->findByUniqueKey<GrBuffer>(indexKey) :
-            nullptr;
+    sk_sp<GrGpuBuffer> vertexBuffer = rp->findByUniqueKey<GrGpuBuffer>(vertexKey);
+    sk_sp<GrGpuBuffer> indexBuffer =
+            this->isIndexed() ? rp->findByUniqueKey<GrGpuBuffer>(indexKey) : nullptr;
 
     // Draw using the cached buffers if possible.
     if (vertexBuffer && (!this->isIndexed() || indexBuffer)) {
@@ -300,10 +357,8 @@
 
     // Allocate vertex buffer.
     size_t vertexStride = gp->vertexStride();
-    vertexBuffer = rp->createBuffer(fVertexCount * vertexStride,
-                                    GrGpuBufferType::kVertex,
-                                    kStatic_GrAccessPattern,
-                                    GrResourceProvider::Flags::kNone);
+    vertexBuffer = rp->createBuffer(
+            fVertexCount * vertexStride, GrGpuBufferType::kVertex, kStatic_GrAccessPattern);
     void* verts = vertexBuffer ? vertexBuffer->map() : nullptr;
     if (!verts) {
         SkDebugf("Could not allocate vertices\n");
@@ -313,10 +368,8 @@
     // Allocate index buffer.
     uint16_t* indices = nullptr;
     if (this->isIndexed()) {
-        indexBuffer = rp->createBuffer(fIndexCount * sizeof(uint16_t),
-                                       GrGpuBufferType::kIndex,
-                                       kStatic_GrAccessPattern,
-                                       GrResourceProvider::Flags::kNone);
+        indexBuffer = rp->createBuffer(
+                fIndexCount * sizeof(uint16_t), GrGpuBufferType::kIndex, kStatic_GrAccessPattern);
         indices = indexBuffer ? static_cast<uint16_t*>(indexBuffer->map()) : nullptr;
         if (!indices) {
             SkDebugf("Could not allocate indices\n");
@@ -327,7 +380,6 @@
     // Fill the buffers.
     this->fillBuffers(hasColorAttribute,
                       hasLocalCoordsAttribute,
-                      hasBoneAttribute,
                       vertexStride,
                       verts,
                       indices);
@@ -347,20 +399,18 @@
                        0);
 }
 
-void GrDrawVerticesOp::fillBuffers(bool hasColorAttribute,
-                                   bool hasLocalCoordsAttribute,
-                                   bool hasBoneAttribute,
-                                   size_t vertexStride,
-                                   void* verts,
-                                   uint16_t* indices) const {
+void DrawVerticesOp::fillBuffers(bool hasColorAttribute,
+                                 bool hasLocalCoordsAttribute,
+                                 size_t vertexStride,
+                                 void* verts,
+                                 uint16_t* indices) const {
     int instanceCount = fMeshes.count();
 
     // Copy data into the buffers.
     int vertexOffset = 0;
     // We have a fast case below for uploading the vertex data when the matrix is translate
-    // only and there are colors but not local coords. Fast case does not apply when there are bone
-    // transformations.
-    bool fastAttrs = hasColorAttribute && !hasLocalCoordsAttribute && !hasBoneAttribute;
+    // only and there are colors but not local coords.
+    bool fastAttrs = hasColorAttribute && !hasLocalCoordsAttribute;
     for (int i = 0; i < instanceCount; i++) {
         // Get each mesh.
         const Mesh& mesh = fMeshes[i];
@@ -378,8 +428,6 @@
         const SkPoint* positions = mesh.fVertices->positions();
         const SkColor* colors = mesh.fVertices->colors();
         const SkPoint* localCoords = mesh.fVertices->texCoords();
-        const SkVertices::BoneIndices* boneIndices = mesh.fVertices->boneIndices();
-        const SkVertices::BoneWeights* boneWeights = mesh.fVertices->boneWeights();
         bool fastMesh = (!this->hasMultipleViewMatrices() ||
                          mesh.fViewMatrix.getType() <= SkMatrix::kTranslate_Mask) &&
                         mesh.hasPerVertexColors();
@@ -412,11 +460,6 @@
             if (hasLocalCoordsAttribute) {
                 offset += sizeof(SkPoint);
             }
-            size_t boneIndexOffset = offset;
-            if (hasBoneAttribute) {
-                offset += 4 * sizeof(int8_t);
-            }
-            size_t boneWeightOffset = offset;
 
             // TODO4F: Preserve float colors
             GrColor color = mesh.fColor.toBytes_RGBA();
@@ -441,16 +484,6 @@
                         *(SkPoint*)((intptr_t)verts + localCoordOffset) = positions[j];
                     }
                 }
-                if (hasBoneAttribute) {
-                    const SkVertices::BoneIndices& indices = boneIndices[j];
-                    const SkVertices::BoneWeights& weights = boneWeights[j];
-                    for (int k = 0; k < 4; k++) {
-                        size_t indexOffset = boneIndexOffset + sizeof(int8_t) * k;
-                        size_t weightOffset = boneWeightOffset + sizeof(uint8_t) * k;
-                        *(int8_t*)((intptr_t)verts + indexOffset) = indices.indices[k];
-                        *(uint8_t*)((intptr_t)verts + weightOffset) = weights.weights[k] * 255.0f;
-                    }
-                }
                 verts = (void*)((intptr_t)verts + vertexStride);
             }
         }
@@ -458,12 +491,12 @@
     }
 }
 
-void GrDrawVerticesOp::drawVertices(Target* target,
-                                    sk_sp<const GrGeometryProcessor> gp,
-                                    sk_sp<const GrBuffer> vertexBuffer,
-                                    int firstVertex,
-                                    sk_sp<const GrBuffer> indexBuffer,
-                                    int firstIndex) {
+void DrawVerticesOp::drawVertices(Target* target,
+                                  sk_sp<const GrGeometryProcessor> gp,
+                                  sk_sp<const GrBuffer> vertexBuffer,
+                                  int firstVertex,
+                                  sk_sp<const GrBuffer> indexBuffer,
+                                  int firstIndex) {
     GrMesh* mesh = target->allocMesh(this->primitiveType());
     if (this->isIndexed()) {
         mesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertexCount - 1,
@@ -472,24 +505,20 @@
         mesh->setNonIndexedNonInstanced(fVertexCount);
     }
     mesh->setVertexData(std::move(vertexBuffer), firstVertex);
-    auto pipe = fHelper.makePipeline(target);
-    target->draw(std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
+    target->recordDraw(std::move(gp), mesh);
 }
 
-GrOp::CombineResult GrDrawVerticesOp::onCombineIfPossible(GrOp* t, const GrCaps& caps) {
-    GrDrawVerticesOp* that = t->cast<GrDrawVerticesOp>();
+void DrawVerticesOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
+    fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
+}
+
+GrOp::CombineResult DrawVerticesOp::onCombineIfPossible(GrOp* t, const GrCaps& caps) {
+    DrawVerticesOp* that = t->cast<DrawVerticesOp>();
 
     if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
         return CombineResult::kCannotCombine;
     }
 
-    // Meshes with bones cannot be combined because different meshes use different bones, so to
-    // combine them, the matrices would have to be combined, and the bone indices on each vertex
-    // would change, thus making the vertices uncacheable.
-    if (this->hasBones() || that->hasBones()) {
-        return CombineResult::kCannotCombine;
-    }
-
     // Non-volatile meshes cannot batch, because if a non-volatile mesh batches with another mesh,
     // then on the next frame, if that non-volatile mesh is drawn, it will draw the other mesh
     // that was saved in its vertex buffer, which is not necessarily there anymore.
@@ -537,6 +566,28 @@
     return CombineResult::kMerged;
 }
 
+} // anonymous namespace
+
+std::unique_ptr<GrDrawOp> GrDrawVerticesOp::Make(GrRecordingContext* context,
+                                                 GrPaint&& paint,
+                                                 sk_sp<SkVertices> vertices,
+                                                 const SkVertices::Bone bones[],
+                                                 int boneCount,
+                                                 const SkMatrix& viewMatrix,
+                                                 GrAAType aaType,
+                                                 sk_sp<GrColorSpaceXform> colorSpaceXform,
+                                                 GrPrimitiveType* overridePrimType) {
+    SkASSERT(vertices);
+    GrPrimitiveType primType = overridePrimType ? *overridePrimType
+                                                : SkVertexModeToGrPrimitiveType(vertices->mode());
+    return GrSimpleMeshDrawOpHelper::FactoryHelper<DrawVerticesOp>(context, std::move(paint),
+                                                                   std::move(vertices),
+                                                                   bones, boneCount,
+                                                                   primType, aaType,
+                                                                   std::move(colorSpaceXform),
+                                                                   viewMatrix);
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 #if GR_TEST_UTILS
@@ -604,7 +655,7 @@
     }
 }
 
-GR_DRAW_OP_TEST_DEFINE(GrDrawVerticesOp) {
+GR_DRAW_OP_TEST_DEFINE(DrawVerticesOp) {
     GrPrimitiveType type;
     do {
        type = GrPrimitiveType(random->nextULessThan(kNumGrPrimitiveTypes));
diff --git a/src/gpu/ops/GrDrawVerticesOp.h b/src/gpu/ops/GrDrawVerticesOp.h
index c72bb36..94f9de9f 100644
--- a/src/gpu/ops/GrDrawVerticesOp.h
+++ b/src/gpu/ops/GrDrawVerticesOp.h
@@ -8,26 +8,16 @@
 #ifndef GrDrawVerticesOp_DEFINED
 #define GrDrawVerticesOp_DEFINED
 
-#include "GrColor.h"
-#include "GrMeshDrawOp.h"
-#include "GrRenderTargetContext.h"
-#include "GrSimpleMeshDrawOpHelper.h"
-#include "GrTypes.h"
-#include "SkMatrix.h"
-#include "SkRect.h"
-#include "SkTDArray.h"
+#include "GrTypesPriv.h"
+#include "SkRefCnt.h"
 #include "SkVertices.h"
 
-class GrOpFlushState;
-class SkVertices;
-struct GrInitInvariantOutput;
+class GrColorSpaceXform;
+class GrDrawOp;
+class GrPaint;
+class GrRecordingContext;
 
-class GrDrawVerticesOp final : public GrMeshDrawOp {
-private:
-    using Helper = GrSimpleMeshDrawOpHelper;
-
-public:
-    DEFINE_OP_CLASS_ID
+namespace GrDrawVerticesOp {
 
     /**
      * Draw a SkVertices. The GrPaint param's color is used if the vertices lack per-vertex color.
@@ -35,135 +25,15 @@
      * primitive type drawn is derived from the SkVertices object, unless overridePrimType is
      * specified.
      */
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
-                                          GrPaint&&,
-                                          sk_sp<SkVertices>,
-                                          const SkVertices::Bone bones[],
-                                          int boneCount,
-                                          const SkMatrix& viewMatrix,
-                                          GrAAType,
-                                          sk_sp<GrColorSpaceXform>,
-                                          GrPrimitiveType* overridePrimType = nullptr);
-
-    GrDrawVerticesOp(const Helper::MakeArgs&, const SkPMColor4f&, sk_sp<SkVertices>,
-                     const SkVertices::Bone bones[], int boneCount, GrPrimitiveType, GrAAType,
-                     sk_sp<GrColorSpaceXform>, const SkMatrix& viewMatrix);
-
-    const char* name() const override { return "DrawVerticesOp"; }
-
-    void visitProxies(const VisitProxyFunc& func, VisitorType) const override {
-        fHelper.visitProxies(func);
-    }
-
-#ifdef SK_DEBUG
-    SkString dumpInfo() const override;
-#endif
-
-    FixedFunctionFlags fixedFunctionFlags() const override;
-
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override;
-
-private:
-    enum class ColorArrayType {
-        kPremulGrColor,
-        kSkColor,
-    };
-
-    void onPrepareDraws(Target*) override;
-
-    void drawVolatile(Target*);
-    void drawNonVolatile(Target*);
-
-    void fillBuffers(bool hasColorAttribute,
-                     bool hasLocalCoordsAttribute,
-                     bool hasBoneAttribute,
-                     size_t vertexStride,
-                     void* verts,
-                     uint16_t* indices) const;
-
-    void drawVertices(Target*,
-                      sk_sp<const GrGeometryProcessor>,
-                      sk_sp<const GrBuffer> vertexBuffer,
-                      int firstVertex,
-                      sk_sp<const GrBuffer> indexBuffer,
-                      int firstIndex);
-
-    sk_sp<GrGeometryProcessor> makeGP(const GrShaderCaps* shaderCaps,
-                                      bool* hasColorAttribute,
-                                      bool* hasLocalCoordAttribute,
-                                      bool* hasBoneAttribute) const;
-
-    GrPrimitiveType primitiveType() const { return fPrimitiveType; }
-    bool combinablePrimitive() const {
-        return GrPrimitiveType::kTriangles == fPrimitiveType ||
-               GrPrimitiveType::kLines == fPrimitiveType ||
-               GrPrimitiveType::kPoints == fPrimitiveType;
-    }
-
-    CombineResult onCombineIfPossible(GrOp* t, const GrCaps&) override;
-
-    struct Mesh {
-        SkPMColor4f fColor;  // Used if this->hasPerVertexColors() is false.
-        sk_sp<SkVertices> fVertices;
-        SkMatrix fViewMatrix;
-        bool fIgnoreTexCoords;
-        bool fIgnoreColors;
-        bool fIgnoreBones;
-
-        bool hasExplicitLocalCoords() const {
-            return fVertices->hasTexCoords() && !fIgnoreTexCoords;
-        }
-
-        bool hasPerVertexColors() const {
-            return fVertices->hasColors() && !fIgnoreColors;
-        }
-
-        bool hasBones() const {
-            return fVertices->hasBones() && !fIgnoreBones;
-        }
-    };
-
-    bool isIndexed() const {
-        // Consistency enforced in onCombineIfPossible.
-        return fMeshes[0].fVertices->hasIndices();
-    }
-
-    bool requiresPerVertexColors() const {
-        return SkToBool(kRequiresPerVertexColors_Flag & fFlags);
-    }
-
-    bool anyMeshHasExplicitLocalCoords() const {
-        return SkToBool(kAnyMeshHasExplicitLocalCoords_Flag & fFlags);
-    }
-
-    bool hasMultipleViewMatrices() const {
-        return SkToBool(kHasMultipleViewMatrices_Flag & fFlags);
-    }
-
-    bool hasBones() const {
-        return SkToBool(kHasBones_Flag & fFlags);
-    }
-
-    enum Flags {
-        kRequiresPerVertexColors_Flag       = 0x1,
-        kAnyMeshHasExplicitLocalCoords_Flag = 0x2,
-        kHasMultipleViewMatrices_Flag       = 0x4,
-        kHasBones_Flag                      = 0x8,
-    };
-
-    Helper fHelper;
-    SkSTArray<1, Mesh, true> fMeshes;
-    std::vector<SkVertices::Bone> fBones; // Bone transformation matrices.
-    // GrPrimitiveType is more expressive than fVertices.mode() so it is used instead and we ignore
-    // the SkVertices mode (though fPrimitiveType may have been inferred from it).
-    GrPrimitiveType fPrimitiveType;
-    uint32_t fFlags;
-    int fVertexCount;
-    int fIndexCount;
-    ColorArrayType fColorArrayType;
-    sk_sp<GrColorSpaceXform> fColorSpaceXform;
-
-    typedef GrMeshDrawOp INHERITED;
+    std::unique_ptr<GrDrawOp> Make(GrRecordingContext*,
+                                   GrPaint&&,
+                                   sk_sp<SkVertices>,
+                                   const SkVertices::Bone bones[],
+                                   int boneCount,
+                                   const SkMatrix& viewMatrix,
+                                   GrAAType,
+                                   sk_sp<GrColorSpaceXform>,
+                                   GrPrimitiveType* overridePrimType = nullptr);
 };
 
 #endif
diff --git a/src/gpu/ops/GrDrawableOp.cpp b/src/gpu/ops/GrDrawableOp.cpp
index 834fedd..1539ea4 100644
--- a/src/gpu/ops/GrDrawableOp.cpp
+++ b/src/gpu/ops/GrDrawableOp.cpp
@@ -7,15 +7,15 @@
 
 #include "GrDrawableOp.h"
 
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrGpuCommandBuffer.h"
 #include "GrMemoryPool.h"
 #include "GrOpFlushState.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "SkDrawable.h"
 
 std::unique_ptr<GrDrawableOp> GrDrawableOp::Make(
-        GrContext* context, std::unique_ptr<SkDrawable::GpuDrawHandler> drawable,
+        GrRecordingContext* context, std::unique_ptr<SkDrawable::GpuDrawHandler> drawable,
         const SkRect& bounds) {
     GrOpMemoryPool* pool = context->priv().opMemoryPool();
     return pool->allocate<GrDrawableOp>(std::move(drawable), bounds);
diff --git a/src/gpu/ops/GrDrawableOp.h b/src/gpu/ops/GrDrawableOp.h
index 302078b..29c4855 100644
--- a/src/gpu/ops/GrDrawableOp.h
+++ b/src/gpu/ops/GrDrawableOp.h
@@ -14,11 +14,13 @@
 #include "SkDrawable.h"
 #include "SkMatrix.h"
 
+class GrRecordingContext;
+
 class GrDrawableOp final : public GrOp {
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrDrawableOp> Make(GrContext*,
+    static std::unique_ptr<GrDrawableOp> Make(GrRecordingContext*,
                                               std::unique_ptr<SkDrawable::GpuDrawHandler> drawable,
                                               const SkRect& bounds);
 
diff --git a/src/gpu/ops/GrFillRRectOp.cpp b/src/gpu/ops/GrFillRRectOp.cpp
new file mode 100644
index 0000000..a302099
--- /dev/null
+++ b/src/gpu/ops/GrFillRRectOp.cpp
@@ -0,0 +1,819 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrFillRRectOp.h"
+
+#include "GrCaps.h"
+#include "GrGpuCommandBuffer.h"
+#include "GrMemoryPool.h"
+#include "GrOpFlushState.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
+#include "SkRRectPriv.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLGeometryProcessor.h"
+#include "glsl/GrGLSLVarying.h"
+#include "glsl/GrGLSLVertexGeoBuilder.h"
+
+// Hardware derivatives are not always accurate enough for highly elliptical corners. This method
+// checks to make sure the corners will still all look good if we use HW derivatives.
+static bool can_use_hw_derivatives_with_coverage(
+        const GrShaderCaps&, const SkMatrix&, const SkRRect&);
+
+std::unique_ptr<GrFillRRectOp> GrFillRRectOp::Make(
+        GrRecordingContext* ctx, GrAAType aaType, const SkMatrix& viewMatrix, const SkRRect& rrect,
+        const GrCaps& caps, GrPaint&& paint) {
+    if (!caps.instanceAttribSupport()) {
+        return nullptr;
+    }
+
+    Flags flags = Flags::kNone;
+    if (GrAAType::kCoverage == aaType) {
+        // TODO: Support perspective in a follow-on CL. This shouldn't be difficult, since we
+        // already use HW derivatives. The only trick will be adjusting the AA outset to account for
+        // perspective. (i.e., outset = 0.5 * z.)
+        if (viewMatrix.hasPerspective()) {
+            return nullptr;
+        }
+        if (can_use_hw_derivatives_with_coverage(*caps.shaderCaps(), viewMatrix, rrect)) {
+            // HW derivatives (more specifically, fwidth()) are consistently faster on all platforms
+            // in coverage mode. We use them as long as the approximation will be accurate enough.
+            flags |= Flags::kUseHWDerivatives;
+        }
+    } else {
+        if (GrAAType::kMSAA == aaType) {
+            if (!caps.sampleLocationsSupport() || !caps.shaderCaps()->sampleVariablesSupport()) {
+                return nullptr;
+            }
+        }
+        if (viewMatrix.hasPerspective()) {
+            // HW derivatives are consistently slower on all platforms in sample mask mode. We
+            // therefore only use them when there is perspective, since then we can't interpolate
+            // the symbolic screen-space gradient.
+            flags |= Flags::kUseHWDerivatives | Flags::kHasPerspective;
+        }
+    }
+
+    // Produce a matrix that draws the round rect from normalized [-1, -1, +1, +1] space.
+    float l = rrect.rect().left(), r = rrect.rect().right(),
+          t = rrect.rect().top(), b = rrect.rect().bottom();
+    SkMatrix m;
+    // Unmap the normalized rect [-1, -1, +1, +1] back to [l, t, r, b].
+    m.setScaleTranslate((r - l)/2, (b - t)/2, (l + r)/2, (t + b)/2);
+    // Map to device space.
+    m.postConcat(viewMatrix);
+
+    SkRect devBounds;
+    if (!(flags & Flags::kHasPerspective)) {
+        // Since m is an affine matrix that maps the rect [-1, -1, +1, +1] into the shape's
+        // device-space quad, it's quite simple to find the bounding rectangle:
+        devBounds = SkRect::MakeXYWH(m.getTranslateX(), m.getTranslateY(), 0, 0);
+        devBounds.outset(SkScalarAbs(m.getScaleX()) + SkScalarAbs(m.getSkewX()),
+                         SkScalarAbs(m.getSkewY()) + SkScalarAbs(m.getScaleY()));
+    } else {
+        viewMatrix.mapRect(&devBounds, rrect.rect());
+    }
+
+    if (GrAAType::kMSAA == aaType && caps.preferTrianglesOverSampleMask()) {
+        // We are on a platform that prefers fine triangles instead of using the sample mask. See if
+        // the round rect is large enough that it will be faster for us to send it off to the
+        // default path renderer instead. The 200x200 threshold was arrived at using the
+        // "shapes_rrect" benchmark on an ARM Galaxy S9.
+        if (devBounds.height() * devBounds.width() > 200 * 200) {
+            return nullptr;
+        }
+    }
+
+    GrOpMemoryPool* pool = ctx->priv().opMemoryPool();
+    return pool->allocate<GrFillRRectOp>(aaType, rrect, flags, m, std::move(paint), devBounds);
+}
+
+GrFillRRectOp::GrFillRRectOp(
+        GrAAType aaType, const SkRRect& rrect, Flags flags,
+        const SkMatrix& totalShapeMatrix, GrPaint&& paint, const SkRect& devBounds)
+        : GrDrawOp(ClassID())
+        , fAAType(aaType)
+        , fOriginalColor(paint.getColor4f())
+        , fLocalRect(rrect.rect())
+        , fFlags(flags)
+        , fProcessors(std::move(paint)) {
+    SkASSERT((fFlags & Flags::kHasPerspective) == totalShapeMatrix.hasPerspective());
+    this->setBounds(devBounds, GrOp::HasAABloat::kYes, GrOp::IsZeroArea::kNo);
+
+    // Write the matrix attribs.
+    const SkMatrix& m = totalShapeMatrix;
+    if (!(fFlags & Flags::kHasPerspective)) {
+        // Affine 2D transformation (float2x2 plus float2 translate).
+        SkASSERT(!m.hasPerspective());
+        this->writeInstanceData(m.getScaleX(), m.getSkewX(), m.getSkewY(), m.getScaleY());
+        this->writeInstanceData(m.getTranslateX(), m.getTranslateY());
+    } else {
+        // Perspective float3x3 transformation matrix.
+        SkASSERT(m.hasPerspective());
+        m.get9(this->appendInstanceData<float>(9));
+    }
+
+    // Convert the radii to [-1, -1, +1, +1] space and write their attribs.
+    Sk4f radiiX, radiiY;
+    Sk4f::Load2(SkRRectPriv::GetRadiiArray(rrect), &radiiX, &radiiY);
+    (radiiX * (2/rrect.width())).store(this->appendInstanceData<float>(4));
+    (radiiY * (2/rrect.height())).store(this->appendInstanceData<float>(4));
+
+    // We will write the color and local rect attribs during finalize().
+}
+
+GrProcessorSet::Analysis GrFillRRectOp::finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                                 GrFSAAType fsaaType, GrClampType clampType) {
+    SkASSERT(1 == fInstanceCount);
+
+    SkPMColor4f overrideColor;
+    const GrProcessorSet::Analysis& analysis = fProcessors.finalize(
+
+            fOriginalColor, GrProcessorAnalysisCoverage::kSingleChannel, clip,
+            &GrUserStencilSettings::kUnused, fsaaType, caps, clampType, &overrideColor);
+
+    // Finish writing the instance attribs.
+    SkPMColor4f finalColor = analysis.inputColorIsOverridden() ? overrideColor : fOriginalColor;
+    if (!SkPMColor4fFitsInBytes(finalColor)) {
+        fFlags |= Flags::kWideColor;
+        uint32_t halfColor[2];
+        SkFloatToHalf_finite_ftz(Sk4f::Load(finalColor.vec())).store(&halfColor);
+        this->writeInstanceData(halfColor[0], halfColor[1]);
+    } else {
+        this->writeInstanceData(finalColor.toBytes_RGBA());
+    }
+
+    if (analysis.usesLocalCoords()) {
+        this->writeInstanceData(fLocalRect);
+        fFlags |= Flags::kHasLocalCoords;
+    }
+    fInstanceStride = fInstanceData.count();
+
+    return analysis;
+}
+
+GrDrawOp::CombineResult GrFillRRectOp::onCombineIfPossible(GrOp* op, const GrCaps&) {
+    const auto& that = *op->cast<GrFillRRectOp>();
+    if (fFlags != that.fFlags || fProcessors != that.fProcessors ||
+        fInstanceData.count() > std::numeric_limits<int>::max() - that.fInstanceData.count()) {
+        return CombineResult::kCannotCombine;
+    }
+
+    fInstanceData.push_back_n(that.fInstanceData.count(), that.fInstanceData.begin());
+    fInstanceCount += that.fInstanceCount;
+    SkASSERT(fInstanceStride == that.fInstanceStride);
+    return CombineResult::kMerged;
+}
+
+void GrFillRRectOp::onPrepare(GrOpFlushState* flushState) {
+    if (void* instanceData = flushState->makeVertexSpace(fInstanceStride, fInstanceCount,
+                                                         &fInstanceBuffer, &fBaseInstance)) {
+        SkASSERT(fInstanceStride * fInstanceCount == fInstanceData.count());
+        memcpy(instanceData, fInstanceData.begin(), fInstanceData.count());
+    }
+}
+
+class GrFillRRectOp::Processor : public GrGeometryProcessor {
+public:
+    Processor(GrAAType aaType, Flags flags)
+            : GrGeometryProcessor(kGrFillRRectOp_Processor_ClassID)
+            , fAAType(aaType)
+            , fFlags(flags) {
+        int numVertexAttribs = (GrAAType::kCoverage == fAAType) ? 3 : 2;
+        this->setVertexAttributes(kVertexAttribs, numVertexAttribs);
+
+        if (!(flags & Flags::kHasPerspective)) {
+            // Affine 2D transformation (float2x2 plus float2 translate).
+            fInstanceAttribs.emplace_back("skew", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
+            fInstanceAttribs.emplace_back(
+                    "translate", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
+        } else {
+            // Perspective float3x3 transformation matrix.
+            fInstanceAttribs.emplace_back("persp_x", kFloat3_GrVertexAttribType, kFloat3_GrSLType);
+            fInstanceAttribs.emplace_back("persp_y", kFloat3_GrVertexAttribType, kFloat3_GrSLType);
+            fInstanceAttribs.emplace_back("persp_z", kFloat3_GrVertexAttribType, kFloat3_GrSLType);
+        }
+        fInstanceAttribs.emplace_back("radii_x", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
+        fInstanceAttribs.emplace_back("radii_y", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
+        fColorAttrib = &fInstanceAttribs.push_back(
+                MakeColorAttribute("color", (flags & Flags::kWideColor)));
+        if (fFlags & Flags::kHasLocalCoords) {
+            fInstanceAttribs.emplace_back(
+                    "local_rect", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
+        }
+        this->setInstanceAttributes(fInstanceAttribs.begin(), fInstanceAttribs.count());
+
+        if (GrAAType::kMSAA == fAAType) {
+            this->setWillUseCustomFeature(CustomFeatures::kSampleLocations);
+        }
+    }
+
+    const char* name() const override { return "GrFillRRectOp::Processor"; }
+
+    void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
+        b->add32(((uint32_t)fFlags << 16) | (uint32_t)fAAType);
+    }
+
+    GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override;
+
+private:
+    static constexpr Attribute kVertexAttribs[] = {
+            {"radii_selector", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
+            {"corner_and_radius_outsets", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
+            // Coverage only.
+            {"aa_bloat_and_coverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType}};
+
+    const GrAAType fAAType;
+    const Flags fFlags;
+
+    SkSTArray<6, Attribute> fInstanceAttribs;
+    const Attribute* fColorAttrib;
+
+    class CoverageImpl;
+    class MSAAImpl;
+};
+
+constexpr GrPrimitiveProcessor::Attribute GrFillRRectOp::Processor::kVertexAttribs[];
+
+// Our coverage geometry consists of an inset octagon with solid coverage, surrounded by linear
+// coverage ramps on the horizontal and vertical edges, and "arc coverage" pieces on the diagonal
+// edges. The Vertex struct tells the shader where to place its vertex within a normalized
+// ([l, t, r, b] = [-1, -1, +1, +1]) space, and how to calculate coverage. See onEmitCode.
+struct CoverageVertex {
+    std::array<float, 4> fRadiiSelector;
+    std::array<float, 2> fCorner;
+    std::array<float, 2> fRadiusOutset;
+    std::array<float, 2> fAABloatDirection;
+    float fCoverage;
+    float fIsLinearCoverage;
+};
+
+// This is the offset (when multiplied by radii) from the corners of a bounding box to the vertices
+// of its inscribed octagon. We draw the outside portion of arcs with quarter-octagons rather than
+// rectangles.
+static constexpr float kOctoOffset = 1/(1 + SK_ScalarRoot2Over2);
+
+static constexpr CoverageVertex kCoverageVertexData[] = {
+        // Left inset edge.
+        {{{0,0,0,1}},  {{-1,+1}},  {{0,-1}},  {{+1,0}},  1,  1},
+        {{{1,0,0,0}},  {{-1,-1}},  {{0,+1}},  {{+1,0}},  1,  1},
+
+        // Top inset edge.
+        {{{1,0,0,0}},  {{-1,-1}},  {{+1,0}},  {{0,+1}},  1,  1},
+        {{{0,1,0,0}},  {{+1,-1}},  {{-1,0}},  {{0,+1}},  1,  1},
+
+        // Right inset edge.
+        {{{0,1,0,0}},  {{+1,-1}},  {{0,+1}},  {{-1,0}},  1,  1},
+        {{{0,0,1,0}},  {{+1,+1}},  {{0,-1}},  {{-1,0}},  1,  1},
+
+        // Bottom inset edge.
+        {{{0,0,1,0}},  {{+1,+1}},  {{-1,0}},  {{0,-1}},  1,  1},
+        {{{0,0,0,1}},  {{-1,+1}},  {{+1,0}},  {{0,-1}},  1,  1},
+
+
+        // Left outset edge.
+        {{{0,0,0,1}},  {{-1,+1}},  {{0,-1}},  {{-1,0}},  0,  1},
+        {{{1,0,0,0}},  {{-1,-1}},  {{0,+1}},  {{-1,0}},  0,  1},
+
+        // Top outset edge.
+        {{{1,0,0,0}},  {{-1,-1}},  {{+1,0}},  {{0,-1}},  0,  1},
+        {{{0,1,0,0}},  {{+1,-1}},  {{-1,0}},  {{0,-1}},  0,  1},
+
+        // Right outset edge.
+        {{{0,1,0,0}},  {{+1,-1}},  {{0,+1}},  {{+1,0}},  0,  1},
+        {{{0,0,1,0}},  {{+1,+1}},  {{0,-1}},  {{+1,0}},  0,  1},
+
+        // Bottom outset edge.
+        {{{0,0,1,0}},  {{+1,+1}},  {{-1,0}},  {{0,+1}},  0,  1},
+        {{{0,0,0,1}},  {{-1,+1}},  {{+1,0}},  {{0,+1}},  0,  1},
+
+
+        // Top-left corner.
+        {{{1,0,0,0}},  {{-1,-1}},  {{ 0,+1}},  {{-1, 0}},  0,  0},
+        {{{1,0,0,0}},  {{-1,-1}},  {{ 0,+1}},  {{+1, 0}},  1,  0},
+        {{{1,0,0,0}},  {{-1,-1}},  {{+1, 0}},  {{ 0,+1}},  1,  0},
+        {{{1,0,0,0}},  {{-1,-1}},  {{+1, 0}},  {{ 0,-1}},  0,  0},
+        {{{1,0,0,0}},  {{-1,-1}},  {{+kOctoOffset,0}},  {{-1,-1}},  0,  0},
+        {{{1,0,0,0}},  {{-1,-1}},  {{0,+kOctoOffset}},  {{-1,-1}},  0,  0},
+
+        // Top-right corner.
+        {{{0,1,0,0}},  {{+1,-1}},  {{-1, 0}},  {{ 0,-1}},  0,  0},
+        {{{0,1,0,0}},  {{+1,-1}},  {{-1, 0}},  {{ 0,+1}},  1,  0},
+        {{{0,1,0,0}},  {{+1,-1}},  {{ 0,+1}},  {{-1, 0}},  1,  0},
+        {{{0,1,0,0}},  {{+1,-1}},  {{ 0,+1}},  {{+1, 0}},  0,  0},
+        {{{0,1,0,0}},  {{+1,-1}},  {{0,+kOctoOffset}},  {{+1,-1}},  0,  0},
+        {{{0,1,0,0}},  {{+1,-1}},  {{-kOctoOffset,0}},  {{+1,-1}},  0,  0},
+
+        // Bottom-right corner.
+        {{{0,0,1,0}},  {{+1,+1}},  {{ 0,-1}},  {{+1, 0}},  0,  0},
+        {{{0,0,1,0}},  {{+1,+1}},  {{ 0,-1}},  {{-1, 0}},  1,  0},
+        {{{0,0,1,0}},  {{+1,+1}},  {{-1, 0}},  {{ 0,-1}},  1,  0},
+        {{{0,0,1,0}},  {{+1,+1}},  {{-1, 0}},  {{ 0,+1}},  0,  0},
+        {{{0,0,1,0}},  {{+1,+1}},  {{-kOctoOffset,0}},  {{+1,+1}},  0,  0},
+        {{{0,0,1,0}},  {{+1,+1}},  {{0,-kOctoOffset}},  {{+1,+1}},  0,  0},
+
+        // Bottom-left corner.
+        {{{0,0,0,1}},  {{-1,+1}},  {{+1, 0}},  {{ 0,+1}},  0,  0},
+        {{{0,0,0,1}},  {{-1,+1}},  {{+1, 0}},  {{ 0,-1}},  1,  0},
+        {{{0,0,0,1}},  {{-1,+1}},  {{ 0,-1}},  {{+1, 0}},  1,  0},
+        {{{0,0,0,1}},  {{-1,+1}},  {{ 0,-1}},  {{-1, 0}},  0,  0},
+        {{{0,0,0,1}},  {{-1,+1}},  {{0,-kOctoOffset}},  {{-1,+1}},  0,  0},
+        {{{0,0,0,1}},  {{-1,+1}},  {{+kOctoOffset,0}},  {{-1,+1}},  0,  0}};
+
+GR_DECLARE_STATIC_UNIQUE_KEY(gCoverageVertexBufferKey);
+
+static constexpr uint16_t kCoverageIndexData[] = {
+        // Inset octagon (solid coverage).
+        0, 1, 7,
+        1, 2, 7,
+        7, 2, 6,
+        2, 3, 6,
+        6, 3, 5,
+        3, 4, 5,
+
+        // AA borders (linear coverage).
+        0, 1, 8, 1, 9, 8,
+        2, 3, 10, 3, 11, 10,
+        4, 5, 12, 5, 13, 12,
+        6, 7, 14, 7, 15, 14,
+
+        // Top-left arc.
+        16, 17, 21,
+        17, 21, 18,
+        21, 18, 20,
+        18, 20, 19,
+
+        // Top-right arc.
+        22, 23, 27,
+        23, 27, 24,
+        27, 24, 26,
+        24, 26, 25,
+
+        // Bottom-right arc.
+        28, 29, 33,
+        29, 33, 30,
+        33, 30, 32,
+        30, 32, 31,
+
+        // Bottom-left arc.
+        34, 35, 39,
+        35, 39, 36,
+        39, 36, 38,
+        36, 38, 37};
+
+GR_DECLARE_STATIC_UNIQUE_KEY(gCoverageIndexBufferKey);
+
+class GrFillRRectOp::Processor::CoverageImpl : public GrGLSLGeometryProcessor {
+    void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
+        const auto& proc = args.fGP.cast<Processor>();
+        bool useHWDerivatives = (proc.fFlags & Flags::kUseHWDerivatives);
+
+        SkASSERT(proc.vertexStride() == sizeof(CoverageVertex));
+
+        GrGLSLVaryingHandler* varyings = args.fVaryingHandler;
+        varyings->emitAttributes(proc);
+        varyings->addPassThroughAttribute(*proc.fColorAttrib, args.fOutputColor,
+                                          GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
+
+        // Emit the vertex shader.
+        GrGLSLVertexBuilder* v = args.fVertBuilder;
+
+        // Unpack vertex attribs.
+        v->codeAppend("float2 corner = corner_and_radius_outsets.xy;");
+        v->codeAppend("float2 radius_outset = corner_and_radius_outsets.zw;");
+        v->codeAppend("float2 aa_bloat_direction = aa_bloat_and_coverage.xy;");
+        v->codeAppend("float coverage = aa_bloat_and_coverage.z;");
+        v->codeAppend("float is_linear_coverage = aa_bloat_and_coverage.w;");
+
+        // Find the amount to bloat each edge for AA (in source space).
+        v->codeAppend("float2 pixellength = inversesqrt("
+                              "float2(dot(skew.xz, skew.xz), dot(skew.yw, skew.yw)));");
+        v->codeAppend("float4 normalized_axis_dirs = skew * pixellength.xyxy;");
+        v->codeAppend("float2 axiswidths = (abs(normalized_axis_dirs.xy) + "
+                                           "abs(normalized_axis_dirs.zw));");
+        v->codeAppend("float2 aa_bloatradius = axiswidths * pixellength * .5;");
+
+        // Identify our radii.
+        v->codeAppend("float4 radii_and_neighbors = radii_selector"
+                              "* float4x4(radii_x, radii_y, radii_x.yxwz, radii_y.wzyx);");
+        v->codeAppend("float2 radii = radii_and_neighbors.xy;");
+        v->codeAppend("float2 neighbor_radii = radii_and_neighbors.zw;");
+
+        v->codeAppend("if (any(greaterThan(aa_bloatradius, float2(1)))) {");
+                          // The rrect is more narrow than an AA coverage ramp. We can't draw as-is
+                          // or else opposite AA borders will overlap. Instead, fudge the size up to
+                          // the width of a coverage ramp, and then reduce total coverage to make
+                          // the rect appear more thin.
+        v->codeAppend(    "corner = max(abs(corner), aa_bloatradius) * sign(corner);");
+        v->codeAppend(    "coverage /= max(aa_bloatradius.x, 1) * max(aa_bloatradius.y, 1);");
+                          // Set radii to zero to ensure we take the "linear coverage" codepath.
+                          // (The "coverage" variable only has effect in the linear codepath.)
+        v->codeAppend(    "radii = float2(0);");
+        v->codeAppend("}");
+
+        v->codeAppend("if (any(lessThan(radii, aa_bloatradius * 1.25))) {");
+                          // The radii are very small. Demote this arc to a sharp 90 degree corner.
+        v->codeAppend(    "radii = aa_bloatradius;");
+                          // Snap octagon vertices to the corner of the bounding box.
+        v->codeAppend(    "radius_outset = floor(abs(radius_outset)) * radius_outset;");
+        v->codeAppend(    "is_linear_coverage = 1;");
+        v->codeAppend("} else {");
+                          // Don't let radii get smaller than a pixel.
+        v->codeAppend(    "radii = clamp(radii, pixellength, 2 - pixellength);");
+        v->codeAppend(    "neighbor_radii = clamp(neighbor_radii, pixellength, 2 - pixellength);");
+                          // Don't let neighboring radii get closer together than 1/16 pixel.
+        v->codeAppend(    "float2 spacing = 2 - radii - neighbor_radii;");
+        v->codeAppend(    "float2 extra_pad = max(pixellength * .0625 - spacing, float2(0));");
+        v->codeAppend(    "radii -= extra_pad * .5;");
+        v->codeAppend("}");
+
+        // Find our vertex position, adjusted for radii and bloated for AA. Our rect is drawn in
+        // normalized [-1,-1,+1,+1] space.
+        v->codeAppend("float2 aa_outset = aa_bloat_direction.xy * aa_bloatradius;");
+        v->codeAppend("float2 vertexpos = corner + radius_outset * radii + aa_outset;");
+
+        // Emit transforms.
+        GrShaderVar localCoord("", kFloat2_GrSLType);
+        if (proc.fFlags & Flags::kHasLocalCoords) {
+            v->codeAppend("float2 localcoord = (local_rect.xy * (1 - vertexpos) + "
+                                               "local_rect.zw * (1 + vertexpos)) * .5;");
+            localCoord.set(kFloat2_GrSLType, "localcoord");
+        }
+        this->emitTransforms(v, varyings, args.fUniformHandler, localCoord,
+                             args.fFPCoordTransformHandler);
+
+        // Transform to device space.
+        SkASSERT(!(proc.fFlags & Flags::kHasPerspective));
+        v->codeAppend("float2x2 skewmatrix = float2x2(skew.xy, skew.zw);");
+        v->codeAppend("float2 devcoord = vertexpos * skewmatrix + translate;");
+        gpArgs->fPositionVar.set(kFloat2_GrSLType, "devcoord");
+
+        // Setup interpolants for coverage.
+        GrGLSLVarying arcCoord(useHWDerivatives ? kFloat2_GrSLType : kFloat4_GrSLType);
+        varyings->addVarying("arccoord", &arcCoord);
+        v->codeAppend("if (0 != is_linear_coverage) {");
+                           // We are a non-corner piece: Set x=0 to indicate built-in coverage, and
+                           // interpolate linear coverage across y.
+        v->codeAppendf(    "%s.xy = float2(0, coverage);", arcCoord.vsOut());
+        v->codeAppend("} else {");
+                           // Find the normalized arc coordinates for our corner ellipse.
+                           // (i.e., the coordinate system where x^2 + y^2 == 1).
+        v->codeAppend(    "float2 arccoord = 1 - abs(radius_outset) + aa_outset/radii * corner;");
+                           // We are a corner piece: Interpolate the arc coordinates for coverage.
+                           // Emit x+1 to ensure no pixel in the arc has a x value of 0 (since x=0
+                           // instructs the fragment shader to use linear coverage).
+        v->codeAppendf(    "%s.xy = float2(arccoord.x+1, arccoord.y);", arcCoord.vsOut());
+        if (!useHWDerivatives) {
+            // The gradient is order-1: Interpolate it across arccoord.zw.
+            v->codeAppendf("float2x2 derivatives = inverse(skewmatrix);");
+            v->codeAppendf("%s.zw = derivatives * (arccoord/radii * 2);", arcCoord.vsOut());
+        }
+        v->codeAppend("}");
+
+        // Emit the fragment shader.
+        GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
+
+        f->codeAppendf("float x_plus_1=%s.x, y=%s.y;", arcCoord.fsIn(), arcCoord.fsIn());
+        f->codeAppendf("half coverage;");
+        f->codeAppendf("if (0 == x_plus_1) {");
+        f->codeAppendf(    "coverage = half(y);");  // We are a non-arc pixel (linear coverage).
+        f->codeAppendf("} else {");
+        f->codeAppendf(    "float fn = x_plus_1 * (x_plus_1 - 2);");  // fn = (x+1)*(x-1) = x^2-1
+        f->codeAppendf(    "fn = fma(y,y, fn);");  // fn = x^2 + y^2 - 1
+        if (useHWDerivatives) {
+            f->codeAppendf("float fnwidth = fwidth(fn);");
+        } else {
+            // The gradient is interpolated across arccoord.zw.
+            f->codeAppendf("float gx=%s.z, gy=%s.w;", arcCoord.fsIn(), arcCoord.fsIn());
+            f->codeAppendf("float fnwidth = abs(gx) + abs(gy);");
+        }
+        f->codeAppendf(    "half d = half(fn/fnwidth);");
+        f->codeAppendf(    "coverage = clamp(.5 - d, 0, 1);");
+        f->codeAppendf("}");
+        f->codeAppendf("%s = half4(coverage);", args.fOutputCoverage);
+    }
+
+    void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
+                 FPCoordTransformIter&& transformIter) override {
+        this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
+    }
+};
+
+// Our MSAA geometry consists of an inset octagon with full sample mask coverage, circumscribed
+// by a larger octagon that modifies the sample mask for the arc at each corresponding corner.
+struct MSAAVertex {
+    std::array<float, 4> fRadiiSelector;
+    std::array<float, 2> fCorner;
+    std::array<float, 2> fRadiusOutset;
+};
+
+static constexpr MSAAVertex kMSAAVertexData[] = {
+        // Left edge. (Negative radii selector indicates this is not an arc section.)
+        {{{0,0,0,-1}},  {{-1,+1}},  {{0,-1}}},
+        {{{-1,0,0,0}},  {{-1,-1}},  {{0,+1}}},
+
+        // Top edge.
+        {{{-1,0,0,0}},  {{-1,-1}},  {{+1,0}}},
+        {{{0,-1,0,0}},  {{+1,-1}},  {{-1,0}}},
+
+        // Right edge.
+        {{{0,-1,0,0}},  {{+1,-1}},  {{0,+1}}},
+        {{{0,0,-1,0}},  {{+1,+1}},  {{0,-1}}},
+
+        // Bottom edge.
+        {{{0,0,-1,0}},  {{+1,+1}},  {{-1,0}}},
+        {{{0,0,0,-1}},  {{-1,+1}},  {{+1,0}}},
+
+        // Top-left corner.
+        {{{1,0,0,0}},  {{-1,-1}},  {{0,+1}}},
+        {{{1,0,0,0}},  {{-1,-1}},  {{0,+kOctoOffset}}},
+        {{{1,0,0,0}},  {{-1,-1}},  {{+1,0}}},
+        {{{1,0,0,0}},  {{-1,-1}},  {{+kOctoOffset,0}}},
+
+        // Top-right corner.
+        {{{0,1,0,0}},  {{+1,-1}},  {{-1,0}}},
+        {{{0,1,0,0}},  {{+1,-1}},  {{-kOctoOffset,0}}},
+        {{{0,1,0,0}},  {{+1,-1}},  {{0,+1}}},
+        {{{0,1,0,0}},  {{+1,-1}},  {{0,+kOctoOffset}}},
+
+        // Bottom-right corner.
+        {{{0,0,1,0}},  {{+1,+1}},  {{0,-1}}},
+        {{{0,0,1,0}},  {{+1,+1}},  {{0,-kOctoOffset}}},
+        {{{0,0,1,0}},  {{+1,+1}},  {{-1,0}}},
+        {{{0,0,1,0}},  {{+1,+1}},  {{-kOctoOffset,0}}},
+
+        // Bottom-left corner.
+        {{{0,0,0,1}},  {{-1,+1}},  {{+1,0}}},
+        {{{0,0,0,1}},  {{-1,+1}},  {{+kOctoOffset,0}}},
+        {{{0,0,0,1}},  {{-1,+1}},  {{0,-1}}},
+        {{{0,0,0,1}},  {{-1,+1}},  {{0,-kOctoOffset}}}};
+
+GR_DECLARE_STATIC_UNIQUE_KEY(gMSAAVertexBufferKey);
+
+static constexpr uint16_t kMSAAIndexData[] = {
+        // Inset octagon. (Full sample mask.)
+        0, 1, 2,
+        0, 2, 3,
+        0, 3, 6,
+        3, 4, 5,
+        3, 5, 6,
+        6, 7, 0,
+
+        // Top-left arc. (Sample mask is set to the arc.)
+         8,  9, 10,
+         9, 11, 10,
+
+        // Top-right arc.
+        12, 13, 14,
+        13, 15, 14,
+
+        // Bottom-right arc.
+        16, 17, 18,
+        17, 19, 18,
+
+        // Bottom-left arc.
+        20, 21, 22,
+        21, 23, 22};
+
+GR_DECLARE_STATIC_UNIQUE_KEY(gMSAAIndexBufferKey);
+
+class GrFillRRectOp::Processor::MSAAImpl : public GrGLSLGeometryProcessor {
+    void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
+        const auto& proc = args.fGP.cast<Processor>();
+        bool useHWDerivatives = (proc.fFlags & Flags::kUseHWDerivatives);
+        bool hasPerspective = (proc.fFlags & Flags::kHasPerspective);
+        bool hasLocalCoords = (proc.fFlags & Flags::kHasLocalCoords);
+        SkASSERT(useHWDerivatives == hasPerspective);
+
+        SkASSERT(proc.vertexStride() == sizeof(MSAAVertex));
+
+        // Emit the vertex shader.
+        GrGLSLVertexBuilder* v = args.fVertBuilder;
+
+        GrGLSLVaryingHandler* varyings = args.fVaryingHandler;
+        varyings->emitAttributes(proc);
+        varyings->addPassThroughAttribute(*proc.fColorAttrib, args.fOutputColor,
+                                          GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
+
+        // Unpack vertex attribs.
+        v->codeAppendf("float2 corner = corner_and_radius_outsets.xy;");
+        v->codeAppendf("float2 radius_outset = corner_and_radius_outsets.zw;");
+
+        // Identify our radii.
+        v->codeAppend("float2 radii;");
+        v->codeAppend("radii.x = dot(radii_selector, radii_x);");
+        v->codeAppend("radii.y = dot(radii_selector, radii_y);");
+        v->codeAppendf("bool is_arc_section = (radii.x > 0);");
+        v->codeAppendf("radii = abs(radii);");
+
+        // Find our vertex position, adjusted for radii. Our rect is drawn in normalized
+        // [-1,-1,+1,+1] space.
+        v->codeAppend("float2 vertexpos = corner + radius_outset * radii;");
+
+        // Emit transforms.
+        GrShaderVar localCoord("", kFloat2_GrSLType);
+        if (hasLocalCoords) {
+            v->codeAppend("float2 localcoord = (local_rect.xy * (1 - vertexpos) + "
+                                               "local_rect.zw * (1 + vertexpos)) * .5;");
+            localCoord.set(kFloat2_GrSLType, "localcoord");
+        }
+        this->emitTransforms(v, varyings, args.fUniformHandler, localCoord,
+                             args.fFPCoordTransformHandler);
+
+        // Transform to device space.
+        if (!hasPerspective) {
+            v->codeAppend("float2x2 skewmatrix = float2x2(skew.xy, skew.zw);");
+            v->codeAppend("float2 devcoord = vertexpos * skewmatrix + translate;");
+            gpArgs->fPositionVar.set(kFloat2_GrSLType, "devcoord");
+        } else {
+            v->codeAppend("float3x3 persp_matrix = float3x3(persp_x, persp_y, persp_z);");
+            v->codeAppend("float3 devcoord = float3(vertexpos, 1) * persp_matrix;");
+            gpArgs->fPositionVar.set(kFloat3_GrSLType, "devcoord");
+        }
+
+        // Determine normalized arc coordinates for the implicit function.
+        GrGLSLVarying arcCoord((useHWDerivatives) ? kFloat2_GrSLType : kFloat4_GrSLType);
+        varyings->addVarying("arccoord", &arcCoord);
+        v->codeAppendf("if (is_arc_section) {");
+        v->codeAppendf(    "%s.xy = 1 - abs(radius_outset);", arcCoord.vsOut());
+        if (!useHWDerivatives) {
+            // The gradient is order-1: Interpolate it across arccoord.zw.
+            // This doesn't work with perspective.
+            SkASSERT(!hasPerspective);
+            v->codeAppendf("float2x2 derivatives = inverse(skewmatrix);");
+            v->codeAppendf("%s.zw = derivatives * (%s.xy/radii * corner * 2);",
+                           arcCoord.vsOut(), arcCoord.vsOut());
+        }
+        v->codeAppendf("} else {");
+        if (useHWDerivatives) {
+            v->codeAppendf("%s = float2(0);", arcCoord.vsOut());
+        } else {
+            v->codeAppendf("%s = float4(0);", arcCoord.vsOut());
+        }
+        v->codeAppendf("}");
+
+        // Emit the fragment shader.
+        GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
+
+        f->codeAppendf("%s = half4(1);", args.fOutputCoverage);
+
+        // If x,y == 0, then we are drawing a triangle that does not track an arc.
+        f->codeAppendf("if (float2(0) != %s.xy) {", arcCoord.fsIn());
+        f->codeAppendf(    "float fn = dot(%s.xy, %s.xy) - 1;", arcCoord.fsIn(), arcCoord.fsIn());
+        if (GrAAType::kMSAA == proc.fAAType) {
+            using ScopeFlags = GrGLSLFPFragmentBuilder::ScopeFlags;
+            if (!useHWDerivatives) {
+                f->codeAppendf("float2 grad = %s.zw;", arcCoord.fsIn());
+                f->applyFnToMultisampleMask("fn", "grad", ScopeFlags::kInsidePerPrimitiveBranch);
+            } else {
+                f->applyFnToMultisampleMask("fn", nullptr, ScopeFlags::kInsidePerPrimitiveBranch);
+            }
+        } else {
+            f->codeAppendf("if (fn > 0) {");
+            f->codeAppendf(    "%s = half4(0);", args.fOutputCoverage);
+            f->codeAppendf("}");
+        }
+        f->codeAppendf("}");
+    }
+
+    void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
+                 FPCoordTransformIter&& transformIter) override {
+        this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
+    }
+};
+
+GrGLSLPrimitiveProcessor* GrFillRRectOp::Processor::createGLSLInstance(
+        const GrShaderCaps&) const {
+    if (GrAAType::kCoverage != fAAType) {
+        return new MSAAImpl();
+    }
+    return new CoverageImpl();
+}
+
+void GrFillRRectOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
+    if (!fInstanceBuffer) {
+        return;  // Setup failed.
+    }
+
+    sk_sp<const GrBuffer> indexBuffer, vertexBuffer;
+    int indexCount;
+
+    if (GrAAType::kCoverage == fAAType) {
+        GR_DEFINE_STATIC_UNIQUE_KEY(gCoverageIndexBufferKey);
+
+        indexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer(
+                GrGpuBufferType::kIndex, sizeof(kCoverageIndexData), kCoverageIndexData,
+                gCoverageIndexBufferKey);
+
+        GR_DEFINE_STATIC_UNIQUE_KEY(gCoverageVertexBufferKey);
+
+        vertexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer(
+                GrGpuBufferType::kVertex, sizeof(kCoverageVertexData), kCoverageVertexData,
+                gCoverageVertexBufferKey);
+
+        indexCount = SK_ARRAY_COUNT(kCoverageIndexData);
+    } else {
+        GR_DEFINE_STATIC_UNIQUE_KEY(gMSAAIndexBufferKey);
+
+        indexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer(
+                GrGpuBufferType::kIndex, sizeof(kMSAAIndexData), kMSAAIndexData,
+                gMSAAIndexBufferKey);
+
+        GR_DEFINE_STATIC_UNIQUE_KEY(gMSAAVertexBufferKey);
+
+        vertexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer(
+                GrGpuBufferType::kVertex, sizeof(kMSAAVertexData), kMSAAVertexData,
+                gMSAAVertexBufferKey);
+
+        indexCount = SK_ARRAY_COUNT(kMSAAIndexData);
+    }
+
+    if (!indexBuffer || !vertexBuffer) {
+        return;
+    }
+
+    Processor proc(fAAType, fFlags);
+    SkASSERT(proc.instanceStride() == (size_t)fInstanceStride);
+
+    GrPipeline::InitArgs initArgs;
+    if (GrAAType::kMSAA == fAAType) {
+        initArgs.fInputFlags = GrPipeline::InputFlags::kHWAntialias;
+    }
+    initArgs.fCaps = &flushState->caps();
+    initArgs.fResourceProvider = flushState->resourceProvider();
+    initArgs.fDstProxy = flushState->drawOpArgs().fDstProxy;
+    auto clip = flushState->detachAppliedClip();
+    GrPipeline::FixedDynamicState fixedDynamicState(clip.scissorState().rect());
+    GrPipeline pipeline(initArgs, std::move(fProcessors), std::move(clip));
+
+    GrMesh mesh(GrPrimitiveType::kTriangles);
+    mesh.setIndexedInstanced(
+            std::move(indexBuffer), indexCount, fInstanceBuffer, fInstanceCount, fBaseInstance,
+            GrPrimitiveRestart::kNo);
+    mesh.setVertexData(std::move(vertexBuffer));
+    flushState->rtCommandBuffer()->draw(
+            proc, pipeline, &fixedDynamicState, nullptr, &mesh, 1, this->bounds());
+}
+
+// Will the given corner look good if we use HW derivatives?
+static bool can_use_hw_derivatives_with_coverage(const Sk2f& devScale, const Sk2f& cornerRadii) {
+    Sk2f devRadii = devScale * cornerRadii;
+    if (devRadii[1] < devRadii[0]) {
+        devRadii = SkNx_shuffle<1,0>(devRadii);
+    }
+    float minDevRadius = SkTMax(devRadii[0], 1.f);  // Shader clamps radius at a minimum of 1.
+    // Is the gradient smooth enough for this corner look ok if we use hardware derivatives?
+    // This threshold was arrived at subjevtively on an NVIDIA chip.
+    return minDevRadius * minDevRadius * 5 > devRadii[1];
+}
+
+static bool can_use_hw_derivatives_with_coverage(
+        const Sk2f& devScale, const SkVector& cornerRadii) {
+    return can_use_hw_derivatives_with_coverage(devScale, Sk2f::Load(&cornerRadii));
+}
+
+// Will the given round rect look good if we use HW derivatives?
+static bool can_use_hw_derivatives_with_coverage(
+        const GrShaderCaps& shaderCaps, const SkMatrix& viewMatrix, const SkRRect& rrect) {
+    if (!shaderCaps.shaderDerivativeSupport()) {
+        return false;
+    }
+
+    Sk2f x = Sk2f(viewMatrix.getScaleX(), viewMatrix.getSkewX());
+    Sk2f y = Sk2f(viewMatrix.getSkewY(), viewMatrix.getScaleY());
+    Sk2f devScale = (x*x + y*y).sqrt();
+    switch (rrect.getType()) {
+        case SkRRect::kEmpty_Type:
+        case SkRRect::kRect_Type:
+            return true;
+
+        case SkRRect::kOval_Type:
+        case SkRRect::kSimple_Type:
+            return can_use_hw_derivatives_with_coverage(devScale, rrect.getSimpleRadii());
+
+        case SkRRect::kNinePatch_Type: {
+            Sk2f r0 = Sk2f::Load(SkRRectPriv::GetRadiiArray(rrect));
+            Sk2f r1 = Sk2f::Load(SkRRectPriv::GetRadiiArray(rrect) + 2);
+            Sk2f minRadii = Sk2f::Min(r0, r1);
+            Sk2f maxRadii = Sk2f::Max(r0, r1);
+            return can_use_hw_derivatives_with_coverage(devScale, Sk2f(minRadii[0], maxRadii[1])) &&
+                   can_use_hw_derivatives_with_coverage(devScale, Sk2f(maxRadii[0], minRadii[1]));
+        }
+
+        case SkRRect::kComplex_Type: {
+            for (int i = 0; i < 4; ++i) {
+                auto corner = static_cast<SkRRect::Corner>(i);
+                if (!can_use_hw_derivatives_with_coverage(devScale, rrect.radii(corner))) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+    SK_ABORT("Invalid round rect type.");
+    return false;  // Add this return to keep GCC happy.
+}
diff --git a/src/gpu/ops/GrFillRRectOp.h b/src/gpu/ops/GrFillRRectOp.h
new file mode 100644
index 0000000..9bc88a0
--- /dev/null
+++ b/src/gpu/ops/GrFillRRectOp.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrFillRRectOp_DEFINED
+#define GrFillRRectOp_DEFINED
+
+#include "GrDrawOp.h"
+
+class GrRecordingContext;
+
+class GrFillRRectOp : public GrDrawOp {
+public:
+    DEFINE_OP_CLASS_ID
+
+    static std::unique_ptr<GrFillRRectOp> Make(
+            GrRecordingContext*, GrAAType, const SkMatrix& viewMatrix, const SkRRect&,
+            const GrCaps&, GrPaint&&);
+
+    const char* name() const override { return "GrFillRRectOp"; }
+    FixedFunctionFlags fixedFunctionFlags() const override {
+        return (GrAAType::kMSAA == fAAType)
+                ? FixedFunctionFlags::kUsesHWAA
+                : FixedFunctionFlags::kNone;
+    }
+    GrProcessorSet::Analysis finalize(
+            const GrCaps&, const GrAppliedClip*, GrFSAAType, GrClampType) override;
+    CombineResult onCombineIfPossible(GrOp*, const GrCaps&) override;
+    void visitProxies(const VisitProxyFunc& fn, VisitorType) const override {
+        fProcessors.visitProxies(fn);
+    }
+    void onPrepare(GrOpFlushState*) override;
+
+    void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
+
+private:
+    enum class Flags {
+        kNone = 0,
+        kUseHWDerivatives = 1 << 0,
+        kHasPerspective = 1 << 1,
+        kHasLocalCoords = 1 << 2,
+        kWideColor = 1 << 3
+    };
+
+    GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(Flags);
+
+    class Processor;
+
+    GrFillRRectOp(GrAAType, const SkRRect&, Flags, const SkMatrix& totalShapeMatrix,
+                  GrPaint&&, const SkRect& devBounds);
+
+    // These methods are used to append data of various POD types to our internal array of instance
+    // data. The actual layout of the instance buffer can vary from Op to Op.
+    template <typename T> inline T* appendInstanceData(int count) {
+        static_assert(std::is_pod<T>::value, "");
+        static_assert(4 == alignof(T), "");
+        return reinterpret_cast<T*>(fInstanceData.push_back_n(sizeof(T) * count));
+    }
+
+    template <typename T, typename... Args>
+    inline void writeInstanceData(const T& val, const Args&... remainder) {
+        memcpy(this->appendInstanceData<T>(1), &val, sizeof(T));
+        this->writeInstanceData(remainder...);
+    }
+
+    void writeInstanceData() {}  // Halt condition.
+
+    const GrAAType fAAType;
+    const SkPMColor4f fOriginalColor;
+    const SkRect fLocalRect;
+    Flags fFlags;
+    GrProcessorSet fProcessors;
+
+    SkSTArray<sizeof(float) * 16 * 4, char, /*MEM_MOVE=*/ true> fInstanceData;
+    int fInstanceCount = 1;
+    int fInstanceStride = 0;
+
+    sk_sp<const GrBuffer> fInstanceBuffer;
+    int fBaseInstance;
+
+    friend class GrOpMemoryPool;
+};
+
+GR_MAKE_BITFIELD_CLASS_OPS(GrFillRRectOp::Flags)
+
+#endif
diff --git a/src/gpu/ops/GrFillRectOp.cpp b/src/gpu/ops/GrFillRectOp.cpp
index a54e0d6..6810a6f 100644
--- a/src/gpu/ops/GrFillRectOp.cpp
+++ b/src/gpu/ops/GrFillRectOp.cpp
@@ -7,12 +7,14 @@
 
 #include "GrFillRectOp.h"
 
+#include "GrCaps.h"
 #include "GrGeometryProcessor.h"
 #include "GrMeshDrawOp.h"
 #include "GrPaint.h"
 #include "GrQuad.h"
 #include "GrQuadPerEdgeAA.h"
 #include "GrSimpleMeshDrawOpHelper.h"
+#include "SkGr.h"
 #include "SkMatrix.h"
 #include "SkRect.h"
 #include "glsl/GrGLSLColorSpaceXformHelper.h"
@@ -56,7 +58,7 @@
     using Helper = GrSimpleMeshDrawOpHelperWithStencil;
 
 public:
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           GrAAType aaType,
                                           GrQuadAAFlags edgeAA,
@@ -78,8 +80,7 @@
                const GrPerspQuad& deviceQuad, GrQuadType deviceQuadType,
                const GrPerspQuad& localQuad, GrQuadType localQuadType)
             : INHERITED(ClassID())
-            , fHelper(args, aaType, stencil)
-            , fWideColor(!SkPMColor4fFitsInBytes(paintColor)) {
+            , fHelper(args, aaType, stencil) {
         // The color stored with the quad is the clear color if a scissor-clear is decided upon
         // when executing the op.
         fDeviceQuads.push_back(deviceQuad, deviceQuadType, { paintColor, edgeFlags });
@@ -121,7 +122,8 @@
     }
 #endif
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
         // Initialize aggregate color analysis with the first quad's color (which always exists)
         SkASSERT(this->quadCount() > 0);
         GrProcessorAnalysisColor quadColors(fDeviceQuads.metadata(0).fColor);
@@ -141,14 +143,32 @@
         GrProcessorAnalysisCoverage coverage = fHelper.aaType() == GrAAType::kCoverage ?
                 GrProcessorAnalysisCoverage::kSingleChannel :
                 GrProcessorAnalysisCoverage::kNone;
-        auto result = fHelper.finalizeProcessors(caps, clip, coverage, &quadColors);
+        auto result = fHelper.finalizeProcessors(
+                caps, clip, fsaaType, clampType, coverage, &quadColors);
         // If there is a constant color after analysis, that means all of the quads should be set
         // to the same color (even if they started out with different colors).
         SkPMColor4f colorOverride;
         if (quadColors.isConstant(&colorOverride)) {
+            fColorType = GrQuadPerEdgeAA::MinColorType(colorOverride, clampType, caps);
             for (int i = 0; i < this->quadCount(); ++i) {
                 fDeviceQuads.metadata(i).fColor = colorOverride;
             }
+        } else {
+            // Otherwise compute the color type needed as the max over all quads.
+            fColorType = ColorType::kNone;
+            for (int i = 0; i < this->quadCount(); ++i) {
+                SkPMColor4f* color = &fDeviceQuads.metadata(i).fColor;
+                fColorType = SkTMax(fColorType,
+                                    GrQuadPerEdgeAA::MinColorType(*color, clampType, caps));
+            }
+        }
+        // Most SkShaders' FPs multiply their calculated color by the paint color or alpha. We want
+        // to use ColorType::kNone to optimize out that multiply. However, if there are no color
+        // FPs then were really writing a special shader for white rectangles and not saving any
+        // multiples. So in that case use bytes to avoid the extra shader (and possibly work around
+        // an ANGLE issue: crbug.com/942565).
+        if (fColorType == ColorType::kNone && !result.hasColorFragmentProcessor()) {
+            fColorType = ColorType::kByte;
         }
 
         return result;
@@ -164,10 +184,12 @@
 
 private:
     // For GrFillRectOp::MakeSet's use of addQuad
-    friend std::unique_ptr<GrDrawOp> GrFillRectOp::MakeSet(GrContext* context, GrPaint&& paint,
-            GrAAType aaType, const SkMatrix& viewMatrix,
+    friend std::unique_ptr<GrDrawOp> GrFillRectOp::MakeSet(
+            GrRecordingContext*,
+            GrPaint&&,
+            GrAAType, const SkMatrix& viewMatrix,
             const GrRenderTargetContext::QuadSetEntry quads[], int quadCount,
-            const GrUserStencilSettings* stencilSettings);
+            const GrUserStencilSettings*);
 
     void onPrepareDraws(Target* target) override {
         TRACE_EVENT0("skia", TRACE_FUNC);
@@ -175,10 +197,9 @@
         using Domain = GrQuadPerEdgeAA::Domain;
         static constexpr SkRect kEmptyDomain = SkRect::MakeEmpty();
 
-        VertexSpec vertexSpec(fDeviceQuads.quadType(),
-                              fWideColor ? ColorType::kHalf : ColorType::kByte,
-                              fLocalQuads.quadType(), fHelper.usesLocalCoords(), Domain::kNo,
-                              fHelper.aaType(), fHelper.compatibleWithAlphaAsCoverage());
+        VertexSpec vertexSpec(fDeviceQuads.quadType(), fColorType, fLocalQuads.quadType(),
+                              fHelper.usesLocalCoords(), Domain::kNo, fHelper.aaType(),
+                              fHelper.compatibleWithCoverageAsAlpha());
         // Make sure that if the op thought it was a solid color, the vertex spec does not use
         // local coords.
         SkASSERT(!fHelper.isTrivial() || !fHelper.usesLocalCoords());
@@ -202,7 +223,8 @@
         void* vertices = vdata;
         if (fHelper.isTrivial()) {
             SkASSERT(fLocalQuads.count() == 0); // No local coords, so send an ignored dummy quad
-            static const GrPerspQuad kIgnoredLocal(SkRect::MakeEmpty(), SkMatrix::I());
+            static const GrPerspQuad kIgnoredLocal(SkRect::MakeEmpty());
+
             for (int i = 0; i < this->quadCount(); ++i) {
                 const ColorAndAA& info = fDeviceQuads.metadata(i);
                 vertices = GrQuadPerEdgeAA::Tessellate(vertices, vertexSpec, fDeviceQuads[i],
@@ -224,10 +246,12 @@
             return;
         }
         mesh->setVertexData(std::move(vbuffer), vertexOffsetInBuffer);
+        target->recordDraw(std::move(gp), mesh);
+    }
 
-        auto pipe = fHelper.makePipeline(target);
-        target->draw(std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
-   }
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
+    }
 
     CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
         TRACE_EVENT0("skia", TRACE_FUNC);
@@ -252,7 +276,7 @@
         // If the processor sets are compatible, the two ops are always compatible; it just needs to
         // adjust the state of the op to be the more general quad and aa types of the two ops and
         // then concatenate the per-quad data.
-        fWideColor |= that->fWideColor;
+        fColorType = SkTMax(fColorType, that->fColorType);
 
         // The helper stores the aa type, but isCompatible(with true arg) allows the two ops' aa
         // types to be none and coverage, in which case this op's aa type must be lifted to coverage
@@ -290,10 +314,6 @@
             // reset the op's accumulated aa type.
         }
 
-        // clear compatible won't need to be updated, since device quad type and paint is the same,
-        // but this quad has a new color, so maybe update wide color
-        fWideColor |= !SkPMColor4fFitsInBytes(color);
-
         // Update the bounds and add the quad to this op's storage
         SkRect newBounds = this->bounds();
         newBounds.joinPossiblyEmptyRect(deviceQuad.bounds(fDeviceQuads.quadType()));
@@ -322,7 +342,7 @@
     // No metadata attached to the local quads; this list is empty when local coords are not needed.
     GrQuadList fLocalQuads;
 
-    unsigned fWideColor: 1;
+    ColorType fColorType;
 
     typedef GrMeshDrawOp INHERITED;
 };
@@ -331,19 +351,20 @@
 
 namespace GrFillRectOp {
 
-std::unique_ptr<GrDrawOp> MakePerEdge(GrContext* context,
+std::unique_ptr<GrDrawOp> MakePerEdge(GrRecordingContext* context,
                                       GrPaint&& paint,
                                       GrAAType aaType,
                                       GrQuadAAFlags edgeAA,
                                       const SkMatrix& viewMatrix,
                                       const SkRect& rect,
                                       const GrUserStencilSettings* stencilSettings) {
+    GrQuadType dstQuadType = GrQuadTypeForTransformedRect(viewMatrix);
     return FillRectOp::Make(context, std::move(paint), aaType, edgeAA, stencilSettings,
-                            GrPerspQuad(rect, viewMatrix), GrQuadTypeForTransformedRect(viewMatrix),
-                            GrPerspQuad(rect, SkMatrix::I()), GrQuadType::kRect);
+                            GrPerspQuad::MakeFromRect(rect, viewMatrix),  dstQuadType,
+                            GrPerspQuad(rect), GrQuadType::kRect);
 }
 
-std::unique_ptr<GrDrawOp> MakePerEdgeWithLocalMatrix(GrContext* context,
+std::unique_ptr<GrDrawOp> MakePerEdgeWithLocalMatrix(GrRecordingContext* context,
                                                      GrPaint&& paint,
                                                      GrAAType aaType,
                                                      GrQuadAAFlags edgeAA,
@@ -351,13 +372,14 @@
                                                      const SkMatrix& localMatrix,
                                                      const SkRect& rect,
                                                      const GrUserStencilSettings* stencilSettings) {
+    GrQuadType dstQuadType = GrQuadTypeForTransformedRect(viewMatrix);
     GrQuadType localQuadType = GrQuadTypeForTransformedRect(localMatrix);
     return FillRectOp::Make(context, std::move(paint), aaType, edgeAA, stencilSettings,
-                            GrPerspQuad(rect, viewMatrix), GrQuadTypeForTransformedRect(viewMatrix),
-                            GrPerspQuad(rect, localMatrix), localQuadType);
+                            GrPerspQuad::MakeFromRect(rect, viewMatrix), dstQuadType,
+                            GrPerspQuad::MakeFromRect(rect, localMatrix), localQuadType);
 }
 
-std::unique_ptr<GrDrawOp> MakePerEdgeWithLocalRect(GrContext* context,
+std::unique_ptr<GrDrawOp> MakePerEdgeWithLocalRect(GrRecordingContext* context,
                                                    GrPaint&& paint,
                                                    GrAAType aaType,
                                                    GrQuadAAFlags edgeAA,
@@ -365,12 +387,29 @@
                                                    const SkRect& rect,
                                                    const SkRect& localRect,
                                                    const GrUserStencilSettings* stencilSettings) {
+    GrQuadType dstQuadType = GrQuadTypeForTransformedRect(viewMatrix);
     return FillRectOp::Make(context, std::move(paint), aaType, edgeAA, stencilSettings,
-                            GrPerspQuad(rect, viewMatrix), GrQuadTypeForTransformedRect(viewMatrix),
-                            GrPerspQuad(localRect, SkMatrix::I()), GrQuadType::kRect);
+                            GrPerspQuad::MakeFromRect(rect, viewMatrix), dstQuadType,
+                            GrPerspQuad(localRect), GrQuadType::kRect);
 }
 
-std::unique_ptr<GrDrawOp> MakeSet(GrContext* context,
+std::unique_ptr<GrDrawOp> MakePerEdgeQuad(GrRecordingContext* context,
+                                          GrPaint&& paint,
+                                          GrAAType aaType,
+                                          GrQuadAAFlags edgeAA,
+                                          const SkMatrix& viewMatrix,
+                                          const SkPoint quad[4],
+                                          const SkPoint localQuad[4],
+                                          const GrUserStencilSettings* stencilSettings) {
+    GrQuadType deviceType = GrQuadTypeForPoints(quad, viewMatrix);
+    GrQuadType localType = GrQuadTypeForPoints(localQuad ? localQuad : quad, SkMatrix::I());
+    return FillRectOp::Make(context, std::move(paint), aaType, edgeAA, stencilSettings,
+                            GrPerspQuad::MakeFromSkQuad(quad, viewMatrix), deviceType,
+                            GrPerspQuad::MakeFromSkQuad(localQuad ? localQuad : quad,
+                                                        SkMatrix::I()), localType);
+}
+
+std::unique_ptr<GrDrawOp> MakeSet(GrRecordingContext* context,
                                   GrPaint&& paint,
                                   GrAAType aaType,
                                   const SkMatrix& viewMatrix,
@@ -383,21 +422,23 @@
 
     paint.setColor4f(quads[0].fColor);
     std::unique_ptr<GrDrawOp> op = FillRectOp::Make(context, std::move(paint), aaType,
-            quads[0].fAAFlags, stencilSettings, GrPerspQuad(quads[0].fRect, viewMatrix),
-            deviceQuadType, GrPerspQuad(quads[0].fRect, quads[0].fLocalMatrix),
+            quads[0].fAAFlags, stencilSettings,
+            GrPerspQuad::MakeFromRect(quads[0].fRect, viewMatrix), deviceQuadType,
+            GrPerspQuad::MakeFromRect(quads[0].fRect, quads[0].fLocalMatrix),
             GrQuadTypeForTransformedRect(quads[0].fLocalMatrix));
     auto* fillRects = op->cast<FillRectOp>();
 
     // Accumulate remaining quads similar to onCombineIfPossible() without creating an op
     for (int i = 1; i < cnt; ++i) {
-        GrPerspQuad deviceQuad(quads[i].fRect, viewMatrix);
+        GrPerspQuad deviceQuad = GrPerspQuad::MakeFromRect(quads[i].fRect, viewMatrix);
 
         GrAAType resolvedAA;
         GrQuadAAFlags resolvedEdgeFlags;
         GrResolveAATypeForQuad(aaType, quads[i].fAAFlags, deviceQuad, deviceQuadType,
                                &resolvedAA, &resolvedEdgeFlags);
 
-        fillRects->addQuad(deviceQuad, GrPerspQuad(quads[i].fRect, quads[i].fLocalMatrix),
+        fillRects->addQuad(deviceQuad,
+                           GrPerspQuad::MakeFromRect(quads[i].fRect, quads[i].fLocalMatrix),
                            GrQuadTypeForTransformedRect(quads[i].fLocalMatrix), quads[i].fColor,
                            resolvedEdgeFlags,resolvedAA);
     }
@@ -405,7 +446,7 @@
     return op;
 }
 
-std::unique_ptr<GrDrawOp> Make(GrContext* context,
+std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                GrPaint&& paint,
                                GrAAType aaType,
                                const SkMatrix& viewMatrix,
@@ -416,7 +457,7 @@
             viewMatrix, rect, stencil);
 }
 
-std::unique_ptr<GrDrawOp> MakeWithLocalMatrix(GrContext* context,
+std::unique_ptr<GrDrawOp> MakeWithLocalMatrix(GrRecordingContext* context,
                                               GrPaint&& paint,
                                               GrAAType aaType,
                                               const SkMatrix& viewMatrix,
@@ -428,7 +469,7 @@
             viewMatrix, localMatrix, rect, stencil);
 }
 
-std::unique_ptr<GrDrawOp> MakeWithLocalRect(GrContext* context,
+std::unique_ptr<GrDrawOp> MakeWithLocalRect(GrRecordingContext* context,
                                             GrPaint&& paint,
                                             GrAAType aaType,
                                             const SkMatrix& viewMatrix,
diff --git a/src/gpu/ops/GrFillRectOp.h b/src/gpu/ops/GrFillRectOp.h
index 27c6f9e..2891720 100644
--- a/src/gpu/ops/GrFillRectOp.h
+++ b/src/gpu/ops/GrFillRectOp.h
@@ -13,6 +13,7 @@
 
 class GrDrawOp;
 class GrPaint;
+class GrRecordingContext;
 struct GrUserStencilSettings;
 class SkMatrix;
 struct SkRect;
@@ -26,7 +27,7 @@
 namespace GrFillRectOp {
 
 // General purpose factory functions that handle per-edge anti-aliasing
-std::unique_ptr<GrDrawOp> MakePerEdge(GrContext* context,
+std::unique_ptr<GrDrawOp> MakePerEdge(GrRecordingContext* context,
                                       GrPaint&& paint,
                                       GrAAType aaType,
                                       GrQuadAAFlags edgeAA,
@@ -34,7 +35,7 @@
                                       const SkRect& rect,
                                       const GrUserStencilSettings* stencil = nullptr);
 
-std::unique_ptr<GrDrawOp> MakePerEdgeWithLocalMatrix(GrContext* context,
+std::unique_ptr<GrDrawOp> MakePerEdgeWithLocalMatrix(GrRecordingContext* context,
                                                      GrPaint&& paint,
                                                      GrAAType aaType,
                                                      GrQuadAAFlags edgeAA,
@@ -43,7 +44,7 @@
                                                      const SkRect& rect,
                                                      const GrUserStencilSettings* stl = nullptr);
 
-std::unique_ptr<GrDrawOp> MakePerEdgeWithLocalRect(GrContext* context,
+std::unique_ptr<GrDrawOp> MakePerEdgeWithLocalRect(GrRecordingContext* context,
                                                    GrPaint&& paint,
                                                    GrAAType aaType,
                                                    GrQuadAAFlags edgeAA,
@@ -52,9 +53,20 @@
                                                    const SkRect& localRect,
                                                    const GrUserStencilSettings* stencil = nullptr);
 
+// Generalization that accepts 2D convex quads instead of just rectangles. If 'localQuad' is not
+// null, this is equivalent to the "WithLocalRect" versions. Quad arrays match SkRect::toQuad order.
+std::unique_ptr<GrDrawOp> MakePerEdgeQuad(GrRecordingContext* context,
+                                          GrPaint&& paint,
+                                          GrAAType aaType,
+                                          GrQuadAAFlags edgeAA,
+                                          const SkMatrix& viewMatrix,
+                                          const SkPoint quad[4],
+                                          const SkPoint localQuad[4],
+                                          const GrUserStencilSettings* stencil = nullptr);
+
 // Bulk API for drawing quads with a single op
 // TODO(michaelludwig) - remove if the bulk API is not useful for SkiaRenderer
-std::unique_ptr<GrDrawOp> MakeSet(GrContext* context,
+std::unique_ptr<GrDrawOp> MakeSet(GrRecordingContext* context,
                                   GrPaint&& paint,
                                   GrAAType aaType,
                                   const SkMatrix& viewMatrix,
@@ -64,14 +76,14 @@
 
 // Specializations where all edges are treated the same. If the aa type is coverage, then the
 // edges will be anti-aliased, otherwise per-edge AA will be disabled.
-std::unique_ptr<GrDrawOp> Make(GrContext* context,
+std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                GrPaint&& paint,
                                GrAAType aaType,
                                const SkMatrix& viewMatrix,
                                const SkRect& rect,
                                const GrUserStencilSettings* stencil = nullptr);
 
-std::unique_ptr<GrDrawOp> MakeWithLocalMatrix(GrContext* context,
+std::unique_ptr<GrDrawOp> MakeWithLocalMatrix(GrRecordingContext* context,
                                               GrPaint&& paint,
                                               GrAAType aaType,
                                               const SkMatrix& viewMatrix,
@@ -79,7 +91,7 @@
                                               const SkRect& rect,
                                               const GrUserStencilSettings* stencil = nullptr);
 
-std::unique_ptr<GrDrawOp> MakeWithLocalRect(GrContext* context,
+std::unique_ptr<GrDrawOp> MakeWithLocalRect(GrRecordingContext* context,
                                             GrPaint&& paint,
                                             GrAAType aaType,
                                             const SkMatrix& viewMatrix,
diff --git a/src/gpu/ops/GrLatticeOp.cpp b/src/gpu/ops/GrLatticeOp.cpp
index 948b7db..677e301 100644
--- a/src/gpu/ops/GrLatticeOp.cpp
+++ b/src/gpu/ops/GrLatticeOp.cpp
@@ -16,6 +16,7 @@
 #include "GrSimpleMeshDrawOpHelper.h"
 #include "GrVertexWriter.h"
 #include "SkBitmap.h"
+#include "SkGr.h"
 #include "SkLatticeIter.h"
 #include "SkMatrixPriv.h"
 #include "SkRect.h"
@@ -133,7 +134,7 @@
     static const int kVertsPerRect = 4;
     static const int kIndicesPerRect = 6;
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const SkMatrix& viewMatrix,
                                           sk_sp<GrTextureProxy> proxy,
@@ -165,7 +166,6 @@
 
         // setup bounds
         this->setTransformedBounds(patch.fDst, viewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
-        fWideColor = !SkPMColor4fFitsInBytes(color);
     }
 
     const char* name() const override { return "NonAALatticeOp"; }
@@ -193,14 +193,17 @@
 
     FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
         auto opaque = fPatches[0].fColor.isOpaque() && GrPixelConfigIsOpaque(fProxy->config())
                               ? GrProcessorAnalysisColor::Opaque::kYes
                               : GrProcessorAnalysisColor::Opaque::kNo;
         auto analysisColor = GrProcessorAnalysisColor(opaque);
-        auto result = fHelper.finalizeProcessors(
-                caps, clip, GrProcessorAnalysisCoverage::kNone, &analysisColor);
+        auto result = fHelper.finalizeProcessors(caps, clip, fsaaType, clampType,
+                                                 GrProcessorAnalysisCoverage::kNone,
+                                                 &analysisColor);
         analysisColor.isConstant(&fPatches[0].fColor);
+        fWideColor = SkPMColor4fNeedsWideColor(fPatches[0].fColor, clampType, caps);
         return result;
     }
 
@@ -284,9 +287,13 @@
                                                   kVertsPerRect * patch.fIter->numRectsToDraw());
             }
         }
-        auto pipe = fHelper.makePipeline(target, 1);
-        pipe.fFixedDynamicState->fPrimitiveProcessorTextures[0] = fProxy.get();
-        helper.recordDraw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState);
+        auto fixedDynamicState = target->makeFixedDynamicState(1);
+        fixedDynamicState->fPrimitiveProcessorTextures[0] = fProxy.get();
+        helper.recordDraw(target, std::move(gp), fixedDynamicState);
+    }
+
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
     }
 
     CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
@@ -329,7 +336,7 @@
 }  // anonymous namespace
 
 namespace GrLatticeOp {
-std::unique_ptr<GrDrawOp> MakeNonAA(GrContext* context,
+std::unique_ptr<GrDrawOp> MakeNonAA(GrRecordingContext* context,
                                     GrPaint&& paint,
                                     const SkMatrix& viewMatrix,
                                     sk_sp<GrTextureProxy> proxy,
@@ -343,8 +350,8 @@
 };
 
 #if GR_TEST_UTILS
-#include "GrContextPriv.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContextPriv.h"
 
 /** Randomly divides subset into count divs. */
 static void init_random_divs(int divs[], int count, int subsetStart, int subsetStop,
diff --git a/src/gpu/ops/GrLatticeOp.h b/src/gpu/ops/GrLatticeOp.h
index ef723ce..cc3f301 100644
--- a/src/gpu/ops/GrLatticeOp.h
+++ b/src/gpu/ops/GrLatticeOp.h
@@ -12,17 +12,17 @@
 #include "GrSamplerState.h"
 #include "SkRefCnt.h"
 
-class GrContext;
+class GrColorSpaceXform;
 class GrDrawOp;
 class GrPaint;
 class SkLatticeIter;
+class GrRecordingContext;
 class GrTextureProxy;
-class GrColorSpaceXform;
 class SkMatrix;
 struct SkRect;
 
 namespace GrLatticeOp {
-std::unique_ptr<GrDrawOp> MakeNonAA(GrContext*,
+std::unique_ptr<GrDrawOp> MakeNonAA(GrRecordingContext*,
                                     GrPaint&&,
                                     const SkMatrix& viewMatrix,
                                     sk_sp<GrTextureProxy>,
diff --git a/src/gpu/ops/GrMeshDrawOp.cpp b/src/gpu/ops/GrMeshDrawOp.cpp
index d467a6b..a8ad863 100644
--- a/src/gpu/ops/GrMeshDrawOp.cpp
+++ b/src/gpu/ops/GrMeshDrawOp.cpp
@@ -14,10 +14,6 @@
 
 void GrMeshDrawOp::onPrepare(GrOpFlushState* state) { this->onPrepareDraws(state); }
 
-void GrMeshDrawOp::onExecute(GrOpFlushState* state, const SkRect& chainBounds) {
-    state->executeDrawsAndUploadsForMeshDrawOp(this, chainBounds);
-}
-
 //////////////////////////////////////////////////////////////////////////////
 
 GrMeshDrawOp::PatternHelper::PatternHelper(Target* target, GrPrimitiveType primitiveType,
@@ -45,24 +41,29 @@
         return;
     }
     SkASSERT(vertexBuffer);
-    size_t ibSize = indexBuffer->gpuMemorySize();
+    size_t ibSize = indexBuffer->size();
     int maxRepetitions = static_cast<int>(ibSize / (sizeof(uint16_t) * indicesPerRepetition));
     fMesh = target->allocMesh(primitiveType);
-    fMesh->setIndexedPatterned(indexBuffer, indicesPerRepetition, verticesPerRepetition,
+    fMesh->setIndexedPatterned(std::move(indexBuffer), indicesPerRepetition, verticesPerRepetition,
                                repeatCount, maxRepetitions);
     fMesh->setVertexData(std::move(vertexBuffer), firstVertex);
 }
 
 void GrMeshDrawOp::PatternHelper::recordDraw(
-        Target* target, sk_sp<const GrGeometryProcessor> gp, const GrPipeline* pipeline,
+        Target* target, sk_sp<const GrGeometryProcessor> gp) const {
+    target->recordDraw(std::move(gp), fMesh);
+}
+
+void GrMeshDrawOp::PatternHelper::recordDraw(
+        Target* target, sk_sp<const GrGeometryProcessor> gp,
         const GrPipeline::FixedDynamicState* fixedDynamicState) const {
-    target->draw(std::move(gp), pipeline, fixedDynamicState, fMesh);
+    target->recordDraw(std::move(gp), fMesh, 1, fixedDynamicState, nullptr);
 }
 
 //////////////////////////////////////////////////////////////////////////////
 
 GrMeshDrawOp::QuadHelper::QuadHelper(Target* target, size_t vertexStride, int quadsToDraw) {
-    sk_sp<const GrBuffer> quadIndexBuffer = target->resourceProvider()->refQuadIndexBuffer();
+    sk_sp<const GrGpuBuffer> quadIndexBuffer = target->resourceProvider()->refQuadIndexBuffer();
     if (!quadIndexBuffer) {
         SkDebugf("Could not get quad index buffer.");
         return;
@@ -73,45 +74,32 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
-GrPipeline::FixedDynamicState* GrMeshDrawOp::Target::allocFixedDynamicState(
-        const SkIRect& rect, int numPrimitiveProcessorTextures) {
-    auto result = this->pipelineArena()->make<GrPipeline::FixedDynamicState>(rect);
-    if (numPrimitiveProcessorTextures) {
-        result->fPrimitiveProcessorTextures =
-                this->allocPrimitiveProcessorTextureArray(numPrimitiveProcessorTextures);
-    }
-    return result;
-}
-
 GrPipeline::DynamicStateArrays* GrMeshDrawOp::Target::allocDynamicStateArrays(
         int numMeshes, int numPrimitiveProcessorTextures, bool allocScissors) {
-    auto result = this->pipelineArena()->make<GrPipeline::DynamicStateArrays>();
+    auto result = this->allocator()->make<GrPipeline::DynamicStateArrays>();
     if (allocScissors) {
-        result->fScissorRects = this->pipelineArena()->makeArray<SkIRect>(numMeshes);
+        result->fScissorRects = this->allocator()->makeArray<SkIRect>(numMeshes);
     }
     if (numPrimitiveProcessorTextures) {
-        result->fPrimitiveProcessorTextures = this->allocPrimitiveProcessorTextureArray(
-                numPrimitiveProcessorTextures * numMeshes);
+        result->fPrimitiveProcessorTextures =
+                this->allocator()->makeArrayDefault<GrTextureProxy*>(
+                        numPrimitiveProcessorTextures * numMeshes);
     }
     return result;
 }
 
-GrMeshDrawOp::Target::PipelineAndFixedDynamicState GrMeshDrawOp::Target::makePipeline(
-        uint32_t pipelineFlags, GrProcessorSet&& processorSet, GrAppliedClip&& clip,
+GrPipeline::FixedDynamicState* GrMeshDrawOp::Target::makeFixedDynamicState(
         int numPrimProcTextures) {
-    GrPipeline::InitArgs pipelineArgs;
-    pipelineArgs.fFlags = pipelineFlags;
-    pipelineArgs.fDstProxy = this->dstProxy();
-    pipelineArgs.fCaps = &this->caps();
-    pipelineArgs.fResourceProvider = this->resourceProvider();
-    GrPipeline::FixedDynamicState* fixedDynamicState = nullptr;
-    if (clip.scissorState().enabled() || numPrimProcTextures) {
-        fixedDynamicState = this->allocFixedDynamicState(clip.scissorState().rect());
+    const GrAppliedClip* clip = this->appliedClip();
+    if ((clip && clip->scissorState().enabled()) || numPrimProcTextures) {
+        const SkIRect& scissor = (clip) ? clip->scissorState().rect() : SkIRect::MakeEmpty();
+        auto fixedDynamicState =
+                this->allocator()->make<GrPipeline::FixedDynamicState>(scissor);
         if (numPrimProcTextures) {
             fixedDynamicState->fPrimitiveProcessorTextures =
-                    this->allocPrimitiveProcessorTextureArray(numPrimProcTextures);
+                    this->allocator()->makeArrayDefault<GrTextureProxy*>(numPrimProcTextures);
         }
+        return fixedDynamicState;
     }
-    return {this->allocPipeline(pipelineArgs, std::move(processorSet), std::move(clip)),
-            fixedDynamicState};
+    return nullptr;
 }
diff --git a/src/gpu/ops/GrMeshDrawOp.h b/src/gpu/ops/GrMeshDrawOp.h
index bba173d..f0c11a4 100644
--- a/src/gpu/ops/GrMeshDrawOp.h
+++ b/src/gpu/ops/GrMeshDrawOp.h
@@ -34,18 +34,20 @@
         space for the vertices and flushes the draws to the GrMeshDrawOp::Target. */
     class PatternHelper {
     public:
-        PatternHelper(Target*, GrPrimitiveType, size_t vertexStride, sk_sp<const GrBuffer>,
-                      int verticesPerRepetition, int indicesPerRepetition, int repeatCount);
+        PatternHelper(Target*, GrPrimitiveType, size_t vertexStride,
+                      sk_sp<const GrBuffer> indexBuffer, int verticesPerRepetition,
+                      int indicesPerRepetition, int repeatCount);
 
         /** Called to issue draws to the GrMeshDrawOp::Target.*/
-        void recordDraw(Target*, sk_sp<const GrGeometryProcessor>, const GrPipeline*,
+        void recordDraw(Target*, sk_sp<const GrGeometryProcessor>) const;
+        void recordDraw(Target*, sk_sp<const GrGeometryProcessor>,
                         const GrPipeline::FixedDynamicState*) const;
 
         void* vertices() const { return fVertices; }
 
     protected:
         PatternHelper() = default;
-        void init(Target*, GrPrimitiveType, size_t vertexStride, sk_sp<const GrBuffer>,
+        void init(Target*, GrPrimitiveType, size_t vertexStride, sk_sp<const GrBuffer> indexBuffer,
                   int verticesPerRepetition, int indicesPerRepetition, int repeatCount);
 
     private:
@@ -71,7 +73,6 @@
 
 private:
     void onPrepare(GrOpFlushState* state) final;
-    void onExecute(GrOpFlushState* state, const SkRect& chainBounds) final;
     virtual void onPrepareDraws(Target*) = 0;
     typedef GrDrawOp INHERITED;
 };
@@ -81,18 +82,18 @@
     virtual ~Target() {}
 
     /** Adds a draw of a mesh. */
-    virtual void draw(sk_sp<const GrGeometryProcessor>,
-                      const GrPipeline*,
-                      const GrPipeline::FixedDynamicState*,
-                      const GrPipeline::DynamicStateArrays*,
-                      const GrMesh[],
-                      int meshCount) = 0;
-    /** Helper for drawing a single GrMesh. */
-    void draw(sk_sp<const GrGeometryProcessor> gp,
-              const GrPipeline* pipeline,
-              const GrPipeline::FixedDynamicState* fixedDynamicState,
-              const GrMesh* mesh) {
-        this->draw(std::move(gp), pipeline, fixedDynamicState, nullptr, mesh, 1);
+    virtual void recordDraw(
+            sk_sp<const GrGeometryProcessor>, const GrMesh[], int meshCnt,
+            const GrPipeline::FixedDynamicState*, const GrPipeline::DynamicStateArrays*) = 0;
+
+    /**
+     * Helper for drawing GrMesh(es) with zero primProc textures and no dynamic state besides the
+     * scissor clip.
+     */
+    void recordDraw(sk_sp<const GrGeometryProcessor> gp, const GrMesh meshes[], int meshCnt = 1) {
+        static constexpr int kZeroPrimProcTextures = 0;
+        auto fixedDynamicState = this->makeFixedDynamicState(kZeroPrimProcTextures);
+        this->recordDraw(std::move(gp), meshes, meshCnt, fixedDynamicState, nullptr);
     }
 
     /**
@@ -134,55 +135,21 @@
     virtual void putBackIndices(int indices) = 0;
     virtual void putBackVertices(int vertices, size_t vertexStride) = 0;
 
-    /**
-     * Allocate space for a pipeline. The target ensures this pipeline lifetime is at least
-     * as long as any deferred execution of draws added via draw().
-     * @tparam Args
-     * @param args
-     * @return
-     */
-    template <typename... Args>
-    GrPipeline* allocPipeline(Args&&... args) {
-        return this->pipelineArena()->make<GrPipeline>(std::forward<Args>(args)...);
-    }
-
     GrMesh* allocMesh(GrPrimitiveType primitiveType) {
-        return this->pipelineArena()->make<GrMesh>(primitiveType);
+        return this->allocator()->make<GrMesh>(primitiveType);
     }
 
-    GrMesh* allocMeshes(int n) { return this->pipelineArena()->makeArray<GrMesh>(n); }
-
-    GrPipeline::FixedDynamicState* allocFixedDynamicState(const SkIRect& rect,
-                                                          int numPrimitiveProcessorTextures = 0);
+    GrMesh* allocMeshes(int n) { return this->allocator()->makeArray<GrMesh>(n); }
 
     GrPipeline::DynamicStateArrays* allocDynamicStateArrays(int numMeshes,
                                                             int numPrimitiveProcessorTextures,
                                                             bool allocScissors);
 
-    GrTextureProxy** allocPrimitiveProcessorTextureArray(int n) {
-        SkASSERT(n > 0);
-        return this->pipelineArena()->makeArrayDefault<GrTextureProxy*>(n);
-    }
-
-    // Once we have C++17 structured bindings make this just be a tuple because then we can do:
-    //      auto [pipeline, fixedDynamicState] = target->makePipeline(...);
-    // in addition to:
-    //      std::tie(flushInfo.fPipeline, flushInfo.fFixedState) = target->makePipeline(...);
-    struct PipelineAndFixedDynamicState {
-        const GrPipeline* fPipeline;
-        GrPipeline::FixedDynamicState* fFixedDynamicState;
-    };
-
-    /**
-     * Helper that makes a pipeline targeting the op's render target that incorporates the op's
-     * GrAppliedClip and uses a fixed dynamic state.
-     */
-    PipelineAndFixedDynamicState makePipeline(uint32_t pipelineFlags, GrProcessorSet&&,
-                                              GrAppliedClip&&,
-                                              int numPrimitiveProcessorTextures = 0);
+    GrPipeline::FixedDynamicState* makeFixedDynamicState(int numPrimitiveProcessorTextures);
 
     virtual GrRenderTargetProxy* proxy() const = 0;
 
+    virtual const GrAppliedClip* appliedClip() = 0;
     virtual GrAppliedClip detachAppliedClip() = 0;
 
     virtual const GrXferProcessor::DstProxy& dstProxy() const = 0;
@@ -198,7 +165,7 @@
     virtual GrDeferredUploadTarget* deferredUploadTarget() = 0;
 
 private:
-    virtual SkArenaAlloc* pipelineArena() = 0;
+    virtual SkArenaAlloc* allocator() = 0;
 };
 
 #endif
diff --git a/src/gpu/ops/GrOvalOpFactory.cpp b/src/gpu/ops/GrOvalOpFactory.cpp
index 53e776b..f7ea93e 100644
--- a/src/gpu/ops/GrOvalOpFactory.cpp
+++ b/src/gpu/ops/GrOvalOpFactory.cpp
@@ -6,6 +6,7 @@
  */
 
 #include "GrOvalOpFactory.h"
+#include "GrCaps.h"
 #include "GrDrawOpTest.h"
 #include "GrGeometryProcessor.h"
 #include "GrOpFlushState.h"
@@ -506,15 +507,21 @@
 
 class EllipseGeometryProcessor : public GrGeometryProcessor {
 public:
-    EllipseGeometryProcessor(bool stroke, bool wideColor, const SkMatrix& localMatrix)
+    EllipseGeometryProcessor(bool stroke, bool wideColor, bool useScale,
+                             const SkMatrix& localMatrix)
     : INHERITED(kEllipseGeometryProcessor_ClassID)
-    , fLocalMatrix(localMatrix) {
+    , fLocalMatrix(localMatrix)
+    , fStroke(stroke)
+    , fUseScale(useScale) {
         fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
         fInColor = MakeColorAttribute("inColor", wideColor);
-        fInEllipseOffset = {"inEllipseOffset", kFloat2_GrVertexAttribType, kHalf2_GrSLType};
-        fInEllipseRadii = {"inEllipseRadii", kFloat4_GrVertexAttribType, kHalf4_GrSLType};
+        if (useScale) {
+            fInEllipseOffset = {"inEllipseOffset", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
+        } else {
+            fInEllipseOffset = {"inEllipseOffset", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
+        }
+        fInEllipseRadii = {"inEllipseRadii", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
         this->setVertexAttributes(&fInPosition, 4);
-        fStroke = stroke;
     }
 
     ~EllipseGeometryProcessor() override {}
@@ -543,12 +550,13 @@
             // emit attributes
             varyingHandler->emitAttributes(egp);
 
-            GrGLSLVarying ellipseOffsets(kHalf2_GrSLType);
+            GrSLType offsetType = egp.fUseScale ? kFloat3_GrSLType : kFloat2_GrSLType;
+            GrGLSLVarying ellipseOffsets(offsetType);
             varyingHandler->addVarying("EllipseOffsets", &ellipseOffsets);
             vertBuilder->codeAppendf("%s = %s;", ellipseOffsets.vsOut(),
                                      egp.fInEllipseOffset.name());
 
-            GrGLSLVarying ellipseRadii(kHalf4_GrSLType);
+            GrGLSLVarying ellipseRadii(kFloat4_GrSLType);
             varyingHandler->addVarying("EllipseRadii", &ellipseRadii);
             vertBuilder->codeAppendf("%s = %s;", ellipseRadii.vsOut(), egp.fInEllipseRadii.name());
 
@@ -573,31 +581,63 @@
             // the distance by the gradient, non-uniformly scaled by the inverse of the
             // ellipse size.
 
+            // On medium precision devices, we scale the denominator of the distance equation
+            // before taking the inverse square root to minimize the chance that we're dividing
+            // by zero, then we scale the result back.
+
             // for outer curve
-            fragBuilder->codeAppendf("half2 offset = %s;", ellipseOffsets.fsIn());
+            fragBuilder->codeAppendf("float2 offset = %s.xy;", ellipseOffsets.fsIn());
             if (egp.fStroke) {
                 fragBuilder->codeAppendf("offset *= %s.xy;", ellipseRadii.fsIn());
             }
-            fragBuilder->codeAppend("half test = dot(offset, offset) - 1.0;");
-            fragBuilder->codeAppendf("half2 grad = 2.0*offset*%s.xy;", ellipseRadii.fsIn());
-            fragBuilder->codeAppend("half grad_dot = dot(grad, grad);");
+            fragBuilder->codeAppend("float test = dot(offset, offset) - 1.0;");
+            if (egp.fUseScale) {
+                fragBuilder->codeAppendf("float2 grad = 2.0*offset*(%s.z*%s.xy);",
+                                         ellipseOffsets.fsIn(), ellipseRadii.fsIn());
+            } else {
+                fragBuilder->codeAppendf("float2 grad = 2.0*offset*%s.xy;", ellipseRadii.fsIn());
+            }
+            fragBuilder->codeAppend("float grad_dot = dot(grad, grad);");
 
             // avoid calling inversesqrt on zero.
-            fragBuilder->codeAppend("grad_dot = max(grad_dot, 1.0e-4);");
-            fragBuilder->codeAppend("half invlen = half(inversesqrt(grad_dot));");
-            fragBuilder->codeAppend("half edgeAlpha = saturate(0.5-test*invlen);");
+            if (args.fShaderCaps->floatIs32Bits()) {
+                fragBuilder->codeAppend("grad_dot = max(grad_dot, 1.1755e-38);");
+            } else {
+                fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);");
+            }
+            if (egp.fUseScale) {
+                fragBuilder->codeAppendf("float invlen = %s.z*inversesqrt(grad_dot);",
+                                         ellipseOffsets.fsIn());
+            } else {
+                fragBuilder->codeAppend("float invlen = inversesqrt(grad_dot);");
+            }
+            fragBuilder->codeAppend("float edgeAlpha = saturate(0.5-test*invlen);");
 
             // for inner curve
             if (egp.fStroke) {
-                fragBuilder->codeAppendf("offset = %s*%s.zw;", ellipseOffsets.fsIn(),
+                fragBuilder->codeAppendf("offset = %s.xy*%s.zw;", ellipseOffsets.fsIn(),
                                          ellipseRadii.fsIn());
                 fragBuilder->codeAppend("test = dot(offset, offset) - 1.0;");
-                fragBuilder->codeAppendf("grad = 2.0*offset*%s.zw;", ellipseRadii.fsIn());
-                fragBuilder->codeAppend("invlen = half(inversesqrt(dot(grad, grad)));");
+                if (egp.fUseScale) {
+                    fragBuilder->codeAppendf("grad = 2.0*offset*(%s.z*%s.zw);",
+                                             ellipseOffsets.fsIn(), ellipseRadii.fsIn());
+                } else {
+                    fragBuilder->codeAppendf("grad = 2.0*offset*%s.zw;", ellipseRadii.fsIn());
+                }
+                fragBuilder->codeAppend("grad_dot = dot(grad, grad);");
+                if (!args.fShaderCaps->floatIs32Bits()) {
+                    fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);");
+                }
+                if (egp.fUseScale) {
+                    fragBuilder->codeAppendf("invlen = %s.z*inversesqrt(grad_dot);",
+                                             ellipseOffsets.fsIn());
+                } else {
+                    fragBuilder->codeAppend("invlen = inversesqrt(grad_dot);");
+                }
                 fragBuilder->codeAppend("edgeAlpha *= saturate(0.5+test*invlen);");
             }
 
-            fragBuilder->codeAppendf("%s = half4(edgeAlpha);", args.fOutputCoverage);
+            fragBuilder->codeAppendf("%s = half4(half(edgeAlpha));", args.fOutputCoverage);
         }
 
         static void GenKey(const GrGeometryProcessor& gp,
@@ -626,6 +666,7 @@
 
     SkMatrix fLocalMatrix;
     bool fStroke;
+    bool fUseScale;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
 
@@ -638,7 +679,7 @@
 sk_sp<GrGeometryProcessor> EllipseGeometryProcessor::TestCreate(GrProcessorTestData* d) {
     return sk_sp<GrGeometryProcessor>(
             new EllipseGeometryProcessor(d->fRandom->nextBool(), d->fRandom->nextBool(),
-                                         GrTest::TestMatrix(d->fRandom)));
+                                         d->fRandom->nextBool(), GrTest::TestMatrix(d->fRandom)));
 }
 #endif
 
@@ -657,14 +698,22 @@
 
 class DIEllipseGeometryProcessor : public GrGeometryProcessor {
 public:
-    DIEllipseGeometryProcessor(bool wideColor, const SkMatrix& viewMatrix, DIEllipseStyle style)
+    DIEllipseGeometryProcessor(bool wideColor, bool useScale, const SkMatrix& viewMatrix,
+                               DIEllipseStyle style)
             : INHERITED(kDIEllipseGeometryProcessor_ClassID)
-            , fViewMatrix(viewMatrix) {
-        fStyle = style;
+            , fViewMatrix(viewMatrix)
+            , fUseScale(useScale)
+            , fStyle(style) {
         fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
         fInColor = MakeColorAttribute("inColor", wideColor);
-        fInEllipseOffsets0 = {"inEllipseOffsets0", kFloat2_GrVertexAttribType, kHalf2_GrSLType};
-        fInEllipseOffsets1 = {"inEllipseOffsets1", kFloat2_GrVertexAttribType, kHalf2_GrSLType};
+        if (useScale) {
+            fInEllipseOffsets0 = {"inEllipseOffsets0", kFloat3_GrVertexAttribType,
+                                  kFloat3_GrSLType};
+        } else {
+            fInEllipseOffsets0 = {"inEllipseOffsets0", kFloat2_GrVertexAttribType,
+                                  kFloat2_GrSLType};
+        }
+        fInEllipseOffsets1 = {"inEllipseOffsets1", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
         this->setVertexAttributes(&fInPosition, 4);
     }
 
@@ -694,11 +743,12 @@
             // emit attributes
             varyingHandler->emitAttributes(diegp);
 
-            GrGLSLVarying offsets0(kHalf2_GrSLType);
+            GrSLType offsetType = (diegp.fUseScale) ? kFloat3_GrSLType : kFloat2_GrSLType;
+            GrGLSLVarying offsets0(offsetType);
             varyingHandler->addVarying("EllipseOffsets0", &offsets0);
             vertBuilder->codeAppendf("%s = %s;", offsets0.vsOut(), diegp.fInEllipseOffsets0.name());
 
-            GrGLSLVarying offsets1(kHalf2_GrSLType);
+            GrGLSLVarying offsets1(kFloat2_GrSLType);
             varyingHandler->addVarying("EllipseOffsets1", &offsets1);
             vertBuilder->codeAppendf("%s = %s;", offsets1.vsOut(), diegp.fInEllipseOffsets1.name());
 
@@ -721,42 +771,62 @@
                                  args.fFPCoordTransformHandler);
 
             // for outer curve
-            fragBuilder->codeAppendf("half2 scaledOffset = %s.xy;", offsets0.fsIn());
-            fragBuilder->codeAppend("half test = dot(scaledOffset, scaledOffset) - 1.0;");
-            fragBuilder->codeAppendf("half2 duvdx = half2(dFdx(%s));", offsets0.fsIn());
-            fragBuilder->codeAppendf("half2 duvdy = half2(dFdy(%s));", offsets0.fsIn());
+            fragBuilder->codeAppendf("float2 scaledOffset = %s.xy;", offsets0.fsIn());
+            fragBuilder->codeAppend("float test = dot(scaledOffset, scaledOffset) - 1.0;");
+            fragBuilder->codeAppendf("float2 duvdx = dFdx(%s.xy);", offsets0.fsIn());
+            fragBuilder->codeAppendf("float2 duvdy = dFdy(%s.xy);", offsets0.fsIn());
             fragBuilder->codeAppendf(
-                    "half2 grad = half2(2.0*%s.x*duvdx.x + 2.0*%s.y*duvdx.y,"
-                    "                  2.0*%s.x*duvdy.x + 2.0*%s.y*duvdy.y);",
+                    "float2 grad = float2(%s.x*duvdx.x + %s.y*duvdx.y,"
+                    "                     %s.x*duvdy.x + %s.y*duvdy.y);",
                     offsets0.fsIn(), offsets0.fsIn(), offsets0.fsIn(), offsets0.fsIn());
+            if (diegp.fUseScale) {
+                fragBuilder->codeAppendf("grad *= %s.z;", offsets0.fsIn());
+            }
 
-            fragBuilder->codeAppend("half grad_dot = dot(grad, grad);");
+            fragBuilder->codeAppend("float grad_dot = 4.0*dot(grad, grad);");
             // avoid calling inversesqrt on zero.
-            fragBuilder->codeAppend("grad_dot = max(grad_dot, 1.0e-4);");
-            fragBuilder->codeAppend("half invlen = half(inversesqrt(grad_dot));");
+            if (args.fShaderCaps->floatIs32Bits()) {
+                fragBuilder->codeAppend("grad_dot = max(grad_dot, 1.1755e-38);");
+            } else {
+                fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);");
+            }
+            fragBuilder->codeAppend("float invlen = inversesqrt(grad_dot);");
+            if (diegp.fUseScale) {
+                fragBuilder->codeAppendf("invlen *= %s.z;", offsets0.fsIn());
+            }
             if (DIEllipseStyle::kHairline == diegp.fStyle) {
                 // can probably do this with one step
-                fragBuilder->codeAppend("half edgeAlpha = saturate(1.0-test*invlen);");
+                fragBuilder->codeAppend("float edgeAlpha = saturate(1.0-test*invlen);");
                 fragBuilder->codeAppend("edgeAlpha *= saturate(1.0+test*invlen);");
             } else {
-                fragBuilder->codeAppend("half edgeAlpha = saturate(0.5-test*invlen);");
+                fragBuilder->codeAppend("float edgeAlpha = saturate(0.5-test*invlen);");
             }
 
             // for inner curve
             if (DIEllipseStyle::kStroke == diegp.fStyle) {
                 fragBuilder->codeAppendf("scaledOffset = %s.xy;", offsets1.fsIn());
                 fragBuilder->codeAppend("test = dot(scaledOffset, scaledOffset) - 1.0;");
-                fragBuilder->codeAppendf("duvdx = half2(dFdx(%s));", offsets1.fsIn());
-                fragBuilder->codeAppendf("duvdy = half2(dFdy(%s));", offsets1.fsIn());
+                fragBuilder->codeAppendf("duvdx = float2(dFdx(%s));", offsets1.fsIn());
+                fragBuilder->codeAppendf("duvdy = float2(dFdy(%s));", offsets1.fsIn());
                 fragBuilder->codeAppendf(
-                        "grad = half2(2.0*%s.x*duvdx.x + 2.0*%s.y*duvdx.y,"
-                        "             2.0*%s.x*duvdy.x + 2.0*%s.y*duvdy.y);",
+                        "grad = float2(%s.x*duvdx.x + %s.y*duvdx.y,"
+                        "              %s.x*duvdy.x + %s.y*duvdy.y);",
                         offsets1.fsIn(), offsets1.fsIn(), offsets1.fsIn(), offsets1.fsIn());
-                fragBuilder->codeAppend("invlen = half(inversesqrt(dot(grad, grad)));");
+                if (diegp.fUseScale) {
+                    fragBuilder->codeAppendf("grad *= %s.z;", offsets0.fsIn());
+                }
+                fragBuilder->codeAppend("grad_dot = 4.0*dot(grad, grad);");
+                if (!args.fShaderCaps->floatIs32Bits()) {
+                    fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);");
+                }
+                fragBuilder->codeAppend("invlen = inversesqrt(grad_dot);");
+                if (diegp.fUseScale) {
+                    fragBuilder->codeAppendf("invlen *= %s.z;", offsets0.fsIn());
+                }
                 fragBuilder->codeAppend("edgeAlpha *= saturate(0.5+test*invlen);");
             }
 
-            fragBuilder->codeAppendf("%s = half4(edgeAlpha);", args.fOutputCoverage);
+            fragBuilder->codeAppendf("%s = half4(half(edgeAlpha));", args.fOutputCoverage);
         }
 
         static void GenKey(const GrGeometryProcessor& gp,
@@ -795,6 +865,7 @@
     Attribute fInEllipseOffsets1;
 
     SkMatrix fViewMatrix;
+    bool fUseScale;
     DIEllipseStyle fStyle;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
@@ -807,7 +878,7 @@
 #if GR_TEST_UTILS
 sk_sp<GrGeometryProcessor> DIEllipseGeometryProcessor::TestCreate(GrProcessorTestData* d) {
     return sk_sp<GrGeometryProcessor>(new DIEllipseGeometryProcessor(
-            d->fRandom->nextBool(), GrTest::TestMatrix(d->fRandom),
+            d->fRandom->nextBool(), d->fRandom->nextBool(), GrTest::TestMatrix(d->fRandom),
             (DIEllipseStyle)(d->fRandom->nextRangeU(0, 2))));
 }
 #endif
@@ -903,7 +974,7 @@
         bool fUseCenter;
     };
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const SkMatrix& viewMatrix,
                                           SkPoint center,
@@ -953,7 +1024,6 @@
         SkStrokeRec::Style recStyle = stroke.getStyle();
 
         fRoundCaps = false;
-        fWideColor = !SkPMColor4fFitsInBytes(color);
 
         viewMatrix.mapPoints(&center, 1);
         radius = viewMatrix.mapRadius(radius);
@@ -999,9 +1069,11 @@
             // The shader operates in a space where the circle is translated to be centered at the
             // origin. Here we compute points on the unit circle at the starting and ending angles.
             SkPoint startPoint, stopPoint;
-            startPoint.fY = SkScalarSinCos(arcParams->fStartAngleRadians, &startPoint.fX);
+            startPoint.fY = SkScalarSin(arcParams->fStartAngleRadians);
+            startPoint.fX = SkScalarCos(arcParams->fStartAngleRadians);
             SkScalar endAngle = arcParams->fStartAngleRadians + arcParams->fSweepAngleRadians;
-            stopPoint.fY = SkScalarSinCos(endAngle, &stopPoint.fX);
+            stopPoint.fY = SkScalarSin(endAngle);
+            stopPoint.fX = SkScalarCos(endAngle);
 
             // Adjust the start and end points based on the view matrix (to handle rotated arcs)
             startPoint = viewMatrix.mapVector(startPoint.fX, startPoint.fY);
@@ -1149,10 +1221,12 @@
     }
 #endif
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
         SkPMColor4f* color = &fCircles.front().fColor;
-        return fHelper.finalizeProcessors(caps, clip, GrProcessorAnalysisCoverage::kSingleChannel,
-                                          color);
+        return fHelper.finalizeProcessors(caps, clip, fsaaType, clampType,
+                                          GrProcessorAnalysisCoverage::kSingleChannel, color,
+                                          &fWideColor);
     }
 
     FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
@@ -1289,8 +1363,11 @@
         mesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1,
                          GrPrimitiveRestart::kNo);
         mesh->setVertexData(std::move(vertexBuffer), firstVertex);
-        auto pipe = fHelper.makePipeline(target);
-        target->draw(std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
+        target->recordDraw(std::move(gp), mesh);
+    }
+
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
     }
 
     CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
@@ -1359,7 +1436,7 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const SkMatrix& viewMatrix,
                                           SkPoint center,
@@ -1393,7 +1470,8 @@
         if (!startAngle) {
             start = {1, 0};
         } else {
-            start.fY = SkScalarSinCos(startAngle, &start.fX);
+            start.fY = SkScalarSin(startAngle);
+            start.fX = SkScalarCos(startAngle);
         }
         viewMatrix.mapVectors(&start, 1);
         startAngle = SkScalarATan2(start.fY, start.fX);
@@ -1446,7 +1524,6 @@
                 HasAABloat::kYes, IsZeroArea::kNo);
         fVertCount = circle_type_to_vert_count(true);
         fIndexCount = circle_type_to_index_count(true);
-        fWideColor = !SkPMColor4fFitsInBytes(color);
     }
 
     const char* name() const override { return "ButtCappedDashedCircleOp"; }
@@ -1475,10 +1552,12 @@
     }
 #endif
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
         SkPMColor4f* color = &fCircles.front().fColor;
-        return fHelper.finalizeProcessors(caps, clip, GrProcessorAnalysisCoverage::kSingleChannel,
-                                          color);
+        return fHelper.finalizeProcessors(caps, clip, fsaaType, clampType,
+                                          GrProcessorAnalysisCoverage::kSingleChannel, color,
+                                          &fWideColor);
     }
 
     FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
@@ -1573,8 +1652,11 @@
         mesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1,
                          GrPrimitiveRestart::kNo);
         mesh->setVertexData(std::move(vertexBuffer), firstVertex);
-        auto pipe = fHelper.makePipeline(target);
-        target->draw(std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
+        target->recordDraw(std::move(gp), mesh);
+    }
+
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
     }
 
     CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
@@ -1639,7 +1721,7 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const SkMatrix& viewMatrix,
                                           const SkRect& ellipse,
@@ -1701,6 +1783,17 @@
             params.fXRadius += scaledStroke.fX;
             params.fYRadius += scaledStroke.fY;
         }
+
+        // For large ovals with low precision floats, we fall back to the path renderer.
+        // To compute the AA at the edge we divide by the gradient, which is clamped to a
+        // minimum value to avoid divides by zero. With large ovals and low precision this
+        // leads to blurring at the edge of the oval.
+        const SkScalar kMaxOvalRadius = 16384;
+        if (!context->priv().caps()->shaderCaps()->floatIs32Bits() &&
+            (params.fXRadius >= kMaxOvalRadius || params.fYRadius >= kMaxOvalRadius)) {
+            return nullptr;
+        }
+
         return Helper::FactoryHelper<EllipseOp>(context, std::move(paint), viewMatrix,
                                                 params, stroke);
     }
@@ -1708,7 +1801,9 @@
     EllipseOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
               const SkMatrix& viewMatrix, const DeviceSpaceParams& params,
               const SkStrokeRec& stroke)
-            : INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage) {
+            : INHERITED(ClassID())
+            , fHelper(helperArgs, GrAAType::kCoverage)
+            , fUseScale(false) {
         SkStrokeRec::Style style = stroke.getStyle();
         bool isStrokeOnly =
                 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
@@ -1727,7 +1822,6 @@
 
         fStroked = isStrokeOnly && params.fInnerXRadius > 0 && params.fInnerYRadius > 0;
         fViewMatrixIfUsingLocalCoords = viewMatrix;
-        fWideColor = !SkPMColor4fFitsInBytes(color);
     }
 
     const char* name() const override { return "EllipseOp"; }
@@ -1754,10 +1848,14 @@
     }
 #endif
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
+        fUseScale = !caps.shaderCaps()->floatIs32Bits() &&
+                    !caps.shaderCaps()->hasLowFragmentPrecision();
         SkPMColor4f* color = &fEllipses.front().fColor;
-        return fHelper.finalizeProcessors(caps, clip, GrProcessorAnalysisCoverage::kSingleChannel,
-                                          color);
+        return fHelper.finalizeProcessors(caps, clip, fsaaType, clampType,
+                                          GrProcessorAnalysisCoverage::kSingleChannel, color,
+                                          &fWideColor);
     }
 
     FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
@@ -1770,7 +1868,7 @@
         }
 
         // Setup geometry processor
-        sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fStroked, fWideColor,
+        sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fStroked, fWideColor, fUseScale,
                                                                    localMatrix));
         QuadHelper helper(target, gp->vertexStride(), fEllipses.count());
         GrVertexWriter verts{helper.vertices()};
@@ -1804,10 +1902,14 @@
             verts.writeQuad(GrVertexWriter::TriStripFromRect(ellipse.fDevBounds),
                             color,
                             origin_centered_tri_strip(xMaxOffset, yMaxOffset),
+                            GrVertexWriter::If(fUseScale, SkTMax(xRadius, yRadius)),
                             invRadii);
         }
-        auto pipe = fHelper.makePipeline(target);
-        helper.recordDraw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState);
+        helper.recordDraw(target, std::move(gp));
+    }
+
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
     }
 
     CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
@@ -1844,6 +1946,7 @@
     Helper fHelper;
     bool fStroked;
     bool fWideColor;
+    bool fUseScale;
     SkSTArray<1, Ellipse, true> fEllipses;
 
     typedef GrMeshDrawOp INHERITED;
@@ -1867,7 +1970,7 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const SkMatrix& viewMatrix,
                                           const SkRect& ellipse,
@@ -1921,6 +2024,17 @@
             params.fXRadius += strokeWidth;
             params.fYRadius += strokeWidth;
         }
+
+        // For large ovals with low precision floats, we fall back to the path renderer.
+        // To compute the AA at the edge we divide by the gradient, which is clamped to a
+        // minimum value to avoid divides by zero. With large ovals and low precision this
+        // leads to blurring at the edge of the oval.
+        const SkScalar kMaxOvalRadius = 16384;
+        if (!context->priv().caps()->shaderCaps()->floatIs32Bits() &&
+            (params.fXRadius >= kMaxOvalRadius || params.fYRadius >= kMaxOvalRadius)) {
+            return nullptr;
+        }
+
         if (DIEllipseStyle::kStroke == params.fStyle &&
             (params.fInnerXRadius <= 0 || params.fInnerYRadius <= 0)) {
             params.fStyle = DIEllipseStyle::kFill;
@@ -1930,7 +2044,9 @@
 
     DIEllipseOp(Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
                 const DeviceSpaceParams& params, const SkMatrix& viewMatrix)
-            : INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage) {
+            : INHERITED(ClassID())
+            , fHelper(helperArgs, GrAAType::kCoverage)
+            , fUseScale(false) {
         // This expands the outer rect so that after CTM we end up with a half-pixel border
         SkScalar a = viewMatrix[SkMatrix::kMScaleX];
         SkScalar b = viewMatrix[SkMatrix::kMSkewX];
@@ -1948,7 +2064,6 @@
                                          params.fCenter.fY + params.fYRadius + geoDy)});
         this->setTransformedBounds(fEllipses[0].fBounds, viewMatrix, HasAABloat::kYes,
                                    IsZeroArea::kNo);
-        fWideColor = !SkPMColor4fFitsInBytes(color);
     }
 
     const char* name() const override { return "DIEllipseOp"; }
@@ -1975,10 +2090,14 @@
     }
 #endif
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
+        fUseScale = !caps.shaderCaps()->floatIs32Bits() &&
+                    !caps.shaderCaps()->hasLowFragmentPrecision();
         SkPMColor4f* color = &fEllipses.front().fColor;
-        return fHelper.finalizeProcessors(caps, clip, GrProcessorAnalysisCoverage::kSingleChannel,
-                                          color);
+        return fHelper.finalizeProcessors(caps, clip, fsaaType, clampType,
+                                          GrProcessorAnalysisCoverage::kSingleChannel, color,
+                                          &fWideColor);
     }
 
     FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
@@ -1987,7 +2106,8 @@
     void onPrepareDraws(Target* target) override {
         // Setup geometry processor
         sk_sp<GrGeometryProcessor> gp(
-                new DIEllipseGeometryProcessor(fWideColor, this->viewMatrix(), this->style()));
+                new DIEllipseGeometryProcessor(fWideColor, fUseScale, this->viewMatrix(),
+                                               this->style()));
 
         QuadHelper helper(target, gp->vertexStride(), fEllipses.count());
         GrVertexWriter verts{helper.vertices()};
@@ -2017,11 +2137,15 @@
             verts.writeQuad(GrVertexWriter::TriStripFromRect(ellipse.fBounds),
                             color,
                             origin_centered_tri_strip(1.0f + offsetDx, 1.0f + offsetDy),
+                            GrVertexWriter::If(fUseScale, SkTMax(xRadius, yRadius)),
                             origin_centered_tri_strip(innerRatioX + offsetDx,
                                                       innerRatioY + offsetDy));
         }
-        auto pipe = fHelper.makePipeline(target);
-        helper.recordDraw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState);
+        helper.recordDraw(target, std::move(gp));
+    }
+
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
     }
 
     CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
@@ -2062,6 +2186,7 @@
 
     Helper fHelper;
     bool fWideColor;
+    bool fUseScale;
     SkSTArray<1, Ellipse, true> fEllipses;
 
     typedef GrMeshDrawOp INHERITED;
@@ -2202,7 +2327,7 @@
 
     // A devStrokeWidth <= 0 indicates a fill only. If devStrokeWidth > 0 then strokeOnly indicates
     // whether the rrect is only stroked or stroked and filled.
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const SkMatrix& viewMatrix,
                                           const SkRect& devRect,
@@ -2263,7 +2388,6 @@
         fVertCount = rrect_type_to_vert_count(type);
         fIndexCount = rrect_type_to_index_count(type);
         fAllFill = (kFill_RRectType == type);
-        fWideColor = !SkPMColor4fFitsInBytes(color);
     }
 
     const char* name() const override { return "CircularRRectOp"; }
@@ -2290,10 +2414,12 @@
     }
 #endif
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
         SkPMColor4f* color = &fRRects.front().fColor;
-        return fHelper.finalizeProcessors(caps, clip, GrProcessorAnalysisCoverage::kSingleChannel,
-                                          color);
+        return fHelper.finalizeProcessors(caps, clip, fsaaType, clampType,
+                                          GrProcessorAnalysisCoverage::kSingleChannel, color,
+                                          &fWideColor);
     }
 
     FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
@@ -2448,8 +2574,11 @@
         mesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1,
                          GrPrimitiveRestart::kNo);
         mesh->setVertexData(std::move(vertexBuffer), firstVertex);
-        auto pipe = fHelper.makePipeline(target);
-        target->draw(std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
+        target->recordDraw(std::move(gp), mesh);
+    }
+
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
     }
 
     CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
@@ -2528,7 +2657,7 @@
 
     // If devStrokeWidths values are <= 0 indicates then fill only. Otherwise, strokeOnly indicates
     // whether the rrect is only stroked or stroked and filled.
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const SkMatrix& viewMatrix,
                                           const SkRect& devRect,
@@ -2573,7 +2702,9 @@
     EllipticalRRectOp(Helper::MakeArgs helperArgs, const SkPMColor4f& color,
                       const SkMatrix& viewMatrix, const SkRect& devRect, float devXRadius,
                       float devYRadius, SkVector devStrokeHalfWidths, bool strokeOnly)
-            : INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage) {
+            : INHERITED(ClassID())
+            , fHelper(helperArgs, GrAAType::kCoverage)
+            , fUseScale(false) {
         SkScalar innerXRadius = 0.0f;
         SkScalar innerYRadius = 0.0f;
         SkRect bounds = devRect;
@@ -2596,7 +2727,6 @@
         this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo);
         // Expand the rect for aa in order to generate the correct vertices.
         bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
-        fWideColor = !SkPMColor4fFitsInBytes(color);
         fRRects.emplace_back(
                 RRect{color, devXRadius, devYRadius, innerXRadius, innerYRadius, bounds});
     }
@@ -2625,10 +2755,13 @@
     }
 #endif
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
+        fUseScale = !caps.shaderCaps()->floatIs32Bits();
         SkPMColor4f* color = &fRRects.front().fColor;
-        return fHelper.finalizeProcessors(caps, clip, GrProcessorAnalysisCoverage::kSingleChannel,
-                                          color);
+        return fHelper.finalizeProcessors(caps, clip, fsaaType, clampType,
+                                          GrProcessorAnalysisCoverage::kSingleChannel, color,
+                                          &fWideColor);
     }
 
     FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
@@ -2641,7 +2774,7 @@
         }
 
         // Setup geometry processor
-        sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fStroked, fWideColor,
+        sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fStroked, fWideColor, fUseScale,
                                                                    localMatrix));
 
         // drop out the middle quad if we're stroked
@@ -2694,30 +2827,38 @@
                                                                // shader, so can't be exactly 0
                                          SK_ScalarNearlyZero, yMaxOffset};
 
+            auto maybeScale = GrVertexWriter::If(fUseScale, SkTMax(rrect.fXRadius, rrect.fYRadius));
             for (int i = 0; i < 4; ++i) {
                 verts.write(bounds.fLeft, yCoords[i],
                             color,
                             xMaxOffset, yOuterOffsets[i],
+                            maybeScale,
                             reciprocalRadii);
 
                 verts.write(bounds.fLeft + xOuterRadius, yCoords[i],
                             color,
                             SK_ScalarNearlyZero, yOuterOffsets[i],
+                            maybeScale,
                             reciprocalRadii);
 
                 verts.write(bounds.fRight - xOuterRadius, yCoords[i],
                             color,
                             SK_ScalarNearlyZero, yOuterOffsets[i],
+                            maybeScale,
                             reciprocalRadii);
 
                 verts.write(bounds.fRight, yCoords[i],
                             color,
                             xMaxOffset, yOuterOffsets[i],
+                            maybeScale,
                             reciprocalRadii);
             }
         }
-        auto pipe = fHelper.makePipeline(target);
-        helper.recordDraw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState);
+        helper.recordDraw(target, std::move(gp));
+    }
+
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
     }
 
     CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
@@ -2754,12 +2895,13 @@
     Helper fHelper;
     bool fStroked;
     bool fWideColor;
+    bool fUseScale;
     SkSTArray<1, RRect, true> fRRects;
 
     typedef GrMeshDrawOp INHERITED;
 };
 
-static std::unique_ptr<GrDrawOp> make_rrect_op(GrContext* context,
+static std::unique_ptr<GrDrawOp> make_rrect_op(GrRecordingContext* context,
                                                GrPaint&& paint,
                                                const SkMatrix& viewMatrix,
                                                const SkRRect& rrect,
@@ -2830,7 +2972,7 @@
     }
 }
 
-std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeRRectOp(GrContext* context,
+std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeRRectOp(GrRecordingContext* context,
                                                        GrPaint&& paint,
                                                        const SkMatrix& viewMatrix,
                                                        const SkRRect& rrect,
@@ -2850,7 +2992,7 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeOvalOp(GrContext* context,
+std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeOvalOp(GrRecordingContext* context,
                                                       GrPaint&& paint,
                                                       const SkMatrix& viewMatrix,
                                                       const SkRect& oval,
@@ -2919,7 +3061,7 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeArcOp(GrContext* context,
+std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeArcOp(GrRecordingContext* context,
                                                      GrPaint&& paint,
                                                      const SkMatrix& viewMatrix,
                                                      const SkRect& oval, SkScalar startAngle,
diff --git a/src/gpu/ops/GrOvalOpFactory.h b/src/gpu/ops/GrOvalOpFactory.h
index 117d31d..b0f0034 100644
--- a/src/gpu/ops/GrOvalOpFactory.h
+++ b/src/gpu/ops/GrOvalOpFactory.h
@@ -11,9 +11,9 @@
 #include "GrColor.h"
 #include "SkRefCnt.h"
 
-class GrContext;
 class GrDrawOp;
 class GrPaint;
+class GrRecordingContext;
 class GrShaderCaps;
 class GrStyle;
 class SkMatrix;
@@ -26,21 +26,21 @@
  */
 class GrOvalOpFactory {
 public:
-    static std::unique_ptr<GrDrawOp> MakeOvalOp(GrContext*,
+    static std::unique_ptr<GrDrawOp> MakeOvalOp(GrRecordingContext*,
                                                 GrPaint&&,
                                                 const SkMatrix&,
                                                 const SkRect& oval,
                                                 const GrStyle& style,
                                                 const GrShaderCaps*);
 
-    static std::unique_ptr<GrDrawOp> MakeRRectOp(GrContext*,
+    static std::unique_ptr<GrDrawOp> MakeRRectOp(GrRecordingContext*,
                                                  GrPaint&&,
                                                  const SkMatrix&,
                                                  const SkRRect&,
                                                  const SkStrokeRec&,
                                                  const GrShaderCaps*);
 
-    static std::unique_ptr<GrDrawOp> MakeArcOp(GrContext*,
+    static std::unique_ptr<GrDrawOp> MakeArcOp(GrRecordingContext*,
                                                GrPaint&&,
                                                const SkMatrix&,
                                                const SkRect& oval,
diff --git a/src/gpu/ops/GrQuadPerEdgeAA.cpp b/src/gpu/ops/GrQuadPerEdgeAA.cpp
index 70caf55..2c70fd8 100644
--- a/src/gpu/ops/GrQuadPerEdgeAA.cpp
+++ b/src/gpu/ops/GrQuadPerEdgeAA.cpp
@@ -14,12 +14,42 @@
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
 #include "glsl/GrGLSLVarying.h"
 #include "glsl/GrGLSLVertexGeoBuilder.h"
+#include "SkGr.h"
 #include "SkNx.h"
 
 #define AI SK_ALWAYS_INLINE
 
 namespace {
 
+// Helper data types since there is a lot of information that needs to be passed around to
+// avoid recalculation in the different procedures for tessellating an AA quad.
+
+struct Vertices {
+    // X, Y, and W coordinates in device space. If not perspective, w should be set to 1.f
+    Sk4f fX, fY, fW;
+    // U, V, and R coordinates representing local quad. Ignored depending on uvrCount (0, 1, 2).
+    Sk4f fU, fV, fR;
+    int fUVRCount;
+};
+
+struct QuadMetadata {
+    // Normalized edge vectors of the device space quad, ordered L, B, T, R (i.e. nextCCW(x) - x).
+    Sk4f fDX, fDY;
+    // 1 / edge length of the device space quad
+    Sk4f fInvLengths;
+    // Edge mask (set to all 1s if aa flags is kAll), otherwise 1.f if edge was AA, 0.f if non-AA.
+    Sk4f fMask;
+};
+
+struct Edges {
+    // a * x + b * y + c = 0; positive distance is inside the quad; ordered LBTR.
+    Sk4f fA, fB, fC;
+    // Whether or not the edge normals had to be flipped to preserve positive distance on the inside
+    bool fFlipped;
+};
+
+static constexpr float kTolerance = 1e-2f;
+
 static AI Sk4f fma(const Sk4f& f, const Sk4f& m, const Sk4f& a) {
     return SkNx_fma<4, float>(f, m, a);
 }
@@ -34,281 +64,562 @@
     return SkNx_shuffle<1, 3, 0, 2>(v);
 }
 
-// Fills Sk4f with 1f if edge bit is set, 0f otherwise. Edges are ordered LBTR to match CCW ordering
-// of vertices in the quad.
-static AI Sk4f compute_edge_mask(GrQuadAAFlags aaFlags) {
-    return Sk4f((GrQuadAAFlags::kLeft & aaFlags) ? 1.f : 0.f,
-                (GrQuadAAFlags::kBottom & aaFlags) ? 1.f : 0.f,
-                (GrQuadAAFlags::kTop & aaFlags) ? 1.f : 0.f,
-                (GrQuadAAFlags::kRight & aaFlags) ? 1.f : 0.f);
+// Replaces zero-length 'bad' edge vectors with the reversed opposite edge vector.
+// e3 may be null if only 2D edges need to be corrected for.
+static AI void correct_bad_edges(const Sk4f& bad, Sk4f* e1, Sk4f* e2, Sk4f* e3) {
+    if (bad.anyTrue()) {
+        // Want opposite edges, L B T R -> R T B L but with flipped sign to preserve winding
+        *e1 = bad.thenElse(-SkNx_shuffle<3, 2, 1, 0>(*e1), *e1);
+        *e2 = bad.thenElse(-SkNx_shuffle<3, 2, 1, 0>(*e2), *e2);
+        if (e3) {
+            *e3 = bad.thenElse(-SkNx_shuffle<3, 2, 1, 0>(*e3), *e3);
+        }
+    }
 }
 
-// Outputs normalized edge vectors in xdiff and ydiff, as well as the reciprocal of the original
-// edge lengths in invLengths
-static AI void compute_edge_vectors(const Sk4f& x, const Sk4f& y, const Sk4f& xnext,
-                                    const Sk4f& ynext, Sk4f* xdiff, Sk4f* ydiff, Sk4f* invLengths) {
-    *xdiff = xnext - x;
-    *ydiff = ynext - y;
-    *invLengths = fma(*xdiff, *xdiff, *ydiff * *ydiff).rsqrt();
-    *xdiff *= *invLengths;
-    *ydiff *= *invLengths;
+// Replace 'bad' coordinates by rotating CCW to get the next point. c3 may be null for 2D points.
+static AI void correct_bad_coords(const Sk4f& bad, Sk4f* c1, Sk4f* c2, Sk4f* c3) {
+    if (bad.anyTrue()) {
+        *c1 = bad.thenElse(nextCCW(*c1), *c1);
+        *c2 = bad.thenElse(nextCCW(*c2), *c2);
+        if (c3) {
+            *c3 = bad.thenElse(nextCCW(*c3), *c3);
+        }
+    }
 }
 
-// outset and outsetCW are provided separately to allow for different magnitude outsets for
-// with-edge and "perpendicular" edge shifts. This is needed when one axis cannot be inset the full
-// half pixel without crossing over the other side.
-static AI void outset_masked_vertices(const Sk4f& outset, const Sk4f& outsetCW, const Sk4f& xdiff,
-                                      const Sk4f& ydiff, const Sk4f& invLengths, const Sk4f& mask,
-                                      Sk4f* x, Sk4f* y, Sk4f* u, Sk4f* v, Sk4f* r, int uvrCount) {
+static AI QuadMetadata get_metadata(const Vertices& vertices, GrQuadAAFlags aaFlags) {
+    Sk4f dx = nextCCW(vertices.fX) - vertices.fX;
+    Sk4f dy = nextCCW(vertices.fY) - vertices.fY;
+    Sk4f invLengths = fma(dx, dx, dy * dy).rsqrt();
+
+    Sk4f mask = aaFlags == GrQuadAAFlags::kAll ? Sk4f(1.f) :
+            Sk4f((GrQuadAAFlags::kLeft & aaFlags) ? 1.f : 0.f,
+                 (GrQuadAAFlags::kBottom & aaFlags) ? 1.f : 0.f,
+                 (GrQuadAAFlags::kTop & aaFlags) ? 1.f : 0.f,
+                 (GrQuadAAFlags::kRight & aaFlags) ? 1.f : 0.f);
+    return { dx * invLengths, dy * invLengths, invLengths, mask };
+}
+
+static AI Edges get_edge_equations(const QuadMetadata& metadata, const Vertices& vertices) {
+    Sk4f dx = metadata.fDX;
+    Sk4f dy = metadata.fDY;
+    // Correct for bad edges by copying adjacent edge information into the bad component
+    correct_bad_edges(metadata.fInvLengths >= 1.f / kTolerance, &dx, &dy, nullptr);
+
+    Sk4f c = fma(dx, vertices.fY, -dy * vertices.fX);
+    // Make sure normals point into the shape
+    Sk4f test = fma(dy, nextCW(vertices.fX), fma(-dx, nextCW(vertices.fY), c));
+    if ((test < -kTolerance).anyTrue()) {
+        return {-dy, dx, -c, true};
+    } else {
+        return {dy, -dx, c, false};
+    }
+}
+
+// Sets 'outset' to the magnitude of outset/inset to adjust each corner of a quad given the
+// edge angles and lengths. If the quad is too small, has empty edges, or too sharp of angles,
+// false is returned and the degenerate slow-path should be used.
+static bool get_optimized_outset(const QuadMetadata& metadata, bool rectilinear, Sk4f* outset) {
+    if (rectilinear) {
+        *outset = 0.5f;
+        // Stay in the fast path as long as all edges are at least a pixel long (so 1/len <= 1)
+        return (metadata.fInvLengths <= 1.f).allTrue();
+    }
+
+    if ((metadata.fInvLengths >= 1.f / kTolerance).anyTrue()) {
+        // Have an empty edge from a degenerate quad, so there's no hope
+        return false;
+    }
+
+    // The distance the point needs to move is 1/2sin(theta), where theta is the angle between the
+    // two edges at that point. cos(theta) is equal to dot(dxy, nextCW(dxy))
+    Sk4f cosTheta = fma(metadata.fDX, nextCW(metadata.fDX), metadata.fDY * nextCW(metadata.fDY));
+    // If the angle is too shallow between edges, go through the degenerate path, otherwise adding
+    // and subtracting very large vectors in almost opposite directions leads to float errors
+    if ((cosTheta.abs() >= 0.9f).anyTrue()) {
+        return false;
+    }
+    *outset = 0.5f * (1.f - cosTheta * cosTheta).rsqrt(); // 1/2sin(theta)
+
+    // When outsetting or insetting, the current edge's AA adds to the length:
+    //   cos(pi - theta)/2sin(theta) + cos(pi-ccw(theta))/2sin(ccw(theta))
+    // Moving an adjacent edge updates the length by 1/2sin(theta|ccw(theta))
+    Sk4f halfTanTheta = -cosTheta * (*outset); // cos(pi - theta) = -cos(theta)
+    Sk4f edgeAdjust = metadata.fMask * (halfTanTheta + nextCCW(halfTanTheta)) +
+                      nextCCW(metadata.fMask) * nextCCW(*outset) +
+                      nextCW(metadata.fMask) * (*outset);
+    // If either outsetting (plus edgeAdjust) or insetting (minus edgeAdjust) make edgeLen negative
+    // then use the slow path
+    Sk4f threshold = 0.1f - metadata.fInvLengths.invert();
+    return (edgeAdjust > threshold).allTrue() && (edgeAdjust < -threshold).allTrue();
+}
+
+// Ignores the quad's fW, use outset_projected_vertices if it's known to need 3D.
+static AI void outset_vertices(const Sk4f& outset, const QuadMetadata& metadata, Vertices* quad) {
     // The mask is rotated compared to the outsets and edge vectors, since if the edge is "on"
     // both its points need to be moved along their other edge vectors.
-    auto maskedOutset = -outset * nextCW(mask);
-    auto maskedOutsetCW = outsetCW * mask;
-    // x = x + outsetCW * mask * nextCW(xdiff) - outset * nextCW(mask) * xdiff
-    *x += fma(maskedOutsetCW, nextCW(xdiff), maskedOutset * xdiff);
-    *y += fma(maskedOutsetCW, nextCW(ydiff), maskedOutset * ydiff);
-    if (uvrCount > 0) {
+    auto maskedOutset = -outset * nextCW(metadata.fMask);
+    auto maskedOutsetCW = outset * metadata.fMask;
+    // x = x + outset * mask * nextCW(xdiff) - outset * nextCW(mask) * xdiff
+    quad->fX += fma(maskedOutsetCW, nextCW(metadata.fDX), maskedOutset * metadata.fDX);
+    quad->fY += fma(maskedOutsetCW, nextCW(metadata.fDY), maskedOutset * metadata.fDY);
+    if (quad->fUVRCount > 0) {
         // We want to extend the texture coords by the same proportion as the positions.
-        maskedOutset *= invLengths;
-        maskedOutsetCW *= nextCW(invLengths);
-        Sk4f udiff = nextCCW(*u) - *u;
-        Sk4f vdiff = nextCCW(*v) - *v;
-        *u += fma(maskedOutsetCW, nextCW(udiff), maskedOutset * udiff);
-        *v += fma(maskedOutsetCW, nextCW(vdiff), maskedOutset * vdiff);
-        if (uvrCount == 3) {
-            Sk4f rdiff = nextCCW(*r) - *r;
-            *r += fma(maskedOutsetCW, nextCW(rdiff), maskedOutset * rdiff);
+        maskedOutset *= metadata.fInvLengths;
+        maskedOutsetCW *= nextCW(metadata.fInvLengths);
+        Sk4f du = nextCCW(quad->fU) - quad->fU;
+        Sk4f dv = nextCCW(quad->fV) - quad->fV;
+        quad->fU += fma(maskedOutsetCW, nextCW(du), maskedOutset * du);
+        quad->fV += fma(maskedOutsetCW, nextCW(dv), maskedOutset * dv);
+        if (quad->fUVRCount == 3) {
+            Sk4f dr = nextCCW(quad->fR) - quad->fR;
+            quad->fR += fma(maskedOutsetCW, nextCW(dr), maskedOutset * dr);
         }
     }
 }
 
-static AI void outset_vertices(const Sk4f& outset, const Sk4f& outsetCW, const Sk4f& xdiff,
-                               const Sk4f& ydiff, const Sk4f& invLengths,
-                               Sk4f* x, Sk4f* y, Sk4f* u, Sk4f* v, Sk4f* r, int uvrCount) {
-    // x = x + outsetCW * nextCW(xdiff) - outset * xdiff (as above, but where mask = (1,1,1,1))
-    *x += fma(outsetCW, nextCW(xdiff), -outset * xdiff);
-    *y += fma(outsetCW, nextCW(ydiff), -outset * ydiff);
-    if (uvrCount > 0) {
-        Sk4f t = -outset * invLengths; // Bake minus sign in here
-        Sk4f tCW = outsetCW * nextCW(invLengths);
-        Sk4f udiff = nextCCW(*u) - *u;
-        Sk4f vdiff = nextCCW(*v) - *v;
-        *u += fma(tCW, nextCW(udiff), t * udiff);
-        *v += fma(tCW, nextCW(vdiff), t * vdiff);
-        if (uvrCount == 3) {
-            Sk4f rdiff = nextCCW(*r) - *r;
-            *r += fma(tCW, nextCW(rdiff), t * rdiff);
+// Updates (x,y,w) to be at (x2d,y2d) once projected. Updates (u,v,r) to match if provided.
+// Gracefully handles 2D content if *w holds all 1s.
+static void outset_projected_vertices(const Sk4f& x2d, const Sk4f& y2d,
+                                      GrQuadAAFlags aaFlags, Vertices* quad) {
+    // Left to right, in device space, for each point
+    Sk4f e1x = SkNx_shuffle<2, 3, 2, 3>(quad->fX) - SkNx_shuffle<0, 1, 0, 1>(quad->fX);
+    Sk4f e1y = SkNx_shuffle<2, 3, 2, 3>(quad->fY) - SkNx_shuffle<0, 1, 0, 1>(quad->fY);
+    Sk4f e1w = SkNx_shuffle<2, 3, 2, 3>(quad->fW) - SkNx_shuffle<0, 1, 0, 1>(quad->fW);
+    correct_bad_edges(fma(e1x, e1x, e1y * e1y) < kTolerance * kTolerance, &e1x, &e1y, &e1w);
+
+    // // Top to bottom, in device space, for each point
+    Sk4f e2x = SkNx_shuffle<1, 1, 3, 3>(quad->fX) - SkNx_shuffle<0, 0, 2, 2>(quad->fX);
+    Sk4f e2y = SkNx_shuffle<1, 1, 3, 3>(quad->fY) - SkNx_shuffle<0, 0, 2, 2>(quad->fY);
+    Sk4f e2w = SkNx_shuffle<1, 1, 3, 3>(quad->fW) - SkNx_shuffle<0, 0, 2, 2>(quad->fW);
+    correct_bad_edges(fma(e2x, e2x, e2y * e2y) < kTolerance * kTolerance, &e2x, &e2y, &e2w);
+
+    // Can only move along e1 and e2 to reach the new 2D point, so we have
+    // x2d = (x + a*e1x + b*e2x) / (w + a*e1w + b*e2w) and
+    // y2d = (y + a*e1y + b*e2y) / (w + a*e1w + b*e2w) for some a, b
+    // This can be rewritten to a*c1x + b*c2x + c3x = 0; a * c1y + b*c2y + c3y = 0, where
+    // the cNx and cNy coefficients are:
+    Sk4f c1x = e1w * x2d - e1x;
+    Sk4f c1y = e1w * y2d - e1y;
+    Sk4f c2x = e2w * x2d - e2x;
+    Sk4f c2y = e2w * y2d - e2y;
+    Sk4f c3x = quad->fW * x2d - quad->fX;
+    Sk4f c3y = quad->fW * y2d - quad->fY;
+
+    // Solve for a and b
+    Sk4f a, b, denom;
+    if (aaFlags == GrQuadAAFlags::kAll) {
+        // When every edge is outset/inset, each corner can use both edge vectors
+        denom = c1x * c2y - c2x * c1y;
+        a = (c2x * c3y - c3x * c2y) / denom;
+        b = (c3x * c1y - c1x * c3y) / denom;
+    } else {
+        // Force a or b to be 0 if that edge cannot be used due to non-AA
+        // FIXME requires the extra > 0.f, since Sk4f's thenElse only works if true values have
+        // all their bits set to 1.
+        Sk4f aMask = Sk4f((aaFlags & GrQuadAAFlags::kLeft) ? 1.f : 0.f,
+                   (aaFlags & GrQuadAAFlags::kLeft) ? 1.f : 0.f,
+                   (aaFlags & GrQuadAAFlags::kRight) ? 1.f : 0.f,
+                   (aaFlags & GrQuadAAFlags::kRight) ? 1.f : 0.f) > 0.f;
+        Sk4f bMask = Sk4f((aaFlags & GrQuadAAFlags::kTop) ? 1.f : 0.f,
+                   (aaFlags & GrQuadAAFlags::kBottom) ? 1.f : 0.f,
+                   (aaFlags & GrQuadAAFlags::kTop) ? 1.f : 0.f,
+                   (aaFlags & GrQuadAAFlags::kBottom) ? 1.f : 0.f) > 0.f;
+
+        // When aMask[i]&bMask[i], then a[i], b[i], denom[i] match the kAll case.
+        // When aMask[i]&!bMask[i], then b[i] = 0, a[i] = -c3x/c1x or -c3y/c1y, using better denom
+        // When !aMask[i]&bMask[i], then a[i] = 0, b[i] = -c3x/c2x or -c3y/c2y, ""
+        // When !aMask[i]&!bMask[i], then both a[i] = 0 and b[i] = 0
+        Sk4f useC1x = c1x.abs() > c1y.abs();
+        Sk4f useC2x = c2x.abs() > c2y.abs();
+        //                                    -------- A & B ------      --------- A & !B ---------
+        denom = aMask.thenElse(bMask.thenElse(c1x * c2y - c2x * c1y,     useC1x.thenElse(c1x, c1y)),
+        //                                    ------- !A & B ----------  - !A & !B -
+                               bMask.thenElse(useC2x.thenElse(c2x, c2y), 1.0f));
+        //                                -------- A & B ------  ---------- A & !B ----------
+        a = aMask.thenElse(bMask.thenElse(c2x * c3y - c3x * c2y, useC1x.thenElse(-c3x, -c3y)),
+        //                 - !A -
+                           0.0f) / denom;
+        //                                -------- A & B ------  ---------- !A & B ----------
+        b = bMask.thenElse(aMask.thenElse(c3x * c1y - c1x * c3y, useC2x.thenElse(-c3x, -c3y)),
+        //                 - !B -
+                           0.0f) / denom;
+    }
+
+    quad->fX += a * e1x + b * e2x;
+    quad->fY += a * e1y + b * e2y;
+    quad->fW += a * e1w + b * e2w;
+    correct_bad_coords(denom.abs() < kTolerance, &quad->fX, &quad->fY, &quad->fW);
+
+    if (quad->fUVRCount > 0) {
+        // Calculate R here so it can be corrected with U and V in case it's needed later
+        Sk4f e1u = SkNx_shuffle<2, 3, 2, 3>(quad->fU) - SkNx_shuffle<0, 1, 0, 1>(quad->fU);
+        Sk4f e1v = SkNx_shuffle<2, 3, 2, 3>(quad->fV) - SkNx_shuffle<0, 1, 0, 1>(quad->fV);
+        Sk4f e1r = SkNx_shuffle<2, 3, 2, 3>(quad->fR) - SkNx_shuffle<0, 1, 0, 1>(quad->fR);
+        correct_bad_edges(fma(e1u, e1u, e1v * e1v) < kTolerance * kTolerance, &e1u, &e1v, &e1r);
+
+        Sk4f e2u = SkNx_shuffle<1, 1, 3, 3>(quad->fU) - SkNx_shuffle<0, 0, 2, 2>(quad->fU);
+        Sk4f e2v = SkNx_shuffle<1, 1, 3, 3>(quad->fV) - SkNx_shuffle<0, 0, 2, 2>(quad->fV);
+        Sk4f e2r = SkNx_shuffle<1, 1, 3, 3>(quad->fR) - SkNx_shuffle<0, 0, 2, 2>(quad->fR);
+        correct_bad_edges(fma(e2u, e2u, e2v * e2v) < kTolerance * kTolerance, &e2u, &e2v, &e2r);
+
+        quad->fU += a * e1u + b * e2u;
+        quad->fV += a * e1v + b * e2v;
+        if (quad->fUVRCount == 3) {
+            quad->fR += a * e1r + b * e2r;
+            correct_bad_coords(denom.abs() < kTolerance, &quad->fU, &quad->fV, &quad->fR);
+        } else {
+            correct_bad_coords(denom.abs() < kTolerance, &quad->fU, &quad->fV, nullptr);
         }
     }
 }
 
-// Updates outset in place to account for non-90 degree angles of the quad edges stored in
-// xdiff, ydiff (which are assumed to be normalized).
-static void adjust_non_rectilinear_outset(const Sk4f& xdiff, const Sk4f& ydiff, Sk4f* outset) {
-    // The distance the point needs to move is outset/sqrt(1-cos^2(theta)), where theta is the angle
-    // between the two edges at that point. cos(theta) is equal to dot(xydiff, nextCW(xydiff)),
-    Sk4f cosTheta = fma(xdiff, nextCW(xdiff), ydiff * nextCW(ydiff));
-    *outset *= (1.f - cosTheta * cosTheta).rsqrt();
-    // But clamp to make sure we don't expand by a giant amount if the sheer is really high
-    *outset = Sk4f::Max(-3.f, Sk4f::Min(*outset, 3.f));
+// Calculate area of intersection between quad (xs, ys) and a pixel at 'pixelCenter'.
+// a, b, c are edge equations of the quad, flipped is true if the line equations had their normals
+// reversed to correct for matrix transforms.
+static float get_exact_coverage(const SkPoint& pixelCenter, const Vertices& quad,
+                                const Edges& edges) {
+     // Ordering of vertices given default tri-strip that produces CCW points
+    static const int kCCW[] = {0, 1, 3, 2};
+    // Ordering of vertices given inverted tri-strip that produces CCW
+    static const int kFlippedCCW[] = {0, 2, 3, 1};
+
+    // Edge boundaries of the pixel
+    float left = pixelCenter.fX - 0.5f;
+    float right = pixelCenter.fX + 0.5f;
+    float top = pixelCenter.fY - 0.5f;
+    float bot = pixelCenter.fY + 0.5f;
+
+    // Whether or not the 4 corners of the pixel are inside the quad geometry. Variable names are
+    // intentional to work easily with the helper macros.
+    bool topleftInside = ((edges.fA * left + edges.fB * top + edges.fC) >= 0.f).allTrue();
+    bool botleftInside = ((edges.fA * left + edges.fB * bot + edges.fC) >= 0.f).allTrue();
+    bool botrightInside = ((edges.fA * right + edges.fB * bot + edges.fC) >= 0.f).allTrue();
+    bool toprightInside = ((edges.fA * right + edges.fB * top + edges.fC) >= 0.f).allTrue();
+    if (topleftInside && botleftInside && botrightInside && toprightInside) {
+        // Quad fully contains the pixel, so we know the area will be 1.f
+        return 1.f;
+    }
+
+    // Track whether or not the quad vertices in (xs, ys) are on the proper sides of l, t, r, and b
+    Sk4f left4f = quad.fX >= left;
+    Sk4f right4f = quad.fX <= right;
+    Sk4f top4f = quad.fY >= top;
+    Sk4f bot4f = quad.fY <= bot;
+    // Use bit casting so that overflows don't occur on WASM (will be cleaned up in SkVx port)
+    Sk4i leftValid = Sk4i::Load(&left4f);
+    Sk4i rightValid = Sk4i::Load(&right4f);
+    Sk4i topValid = Sk4i::Load(&top4f);
+    Sk4i botValid = Sk4i::Load(&bot4f);
+
+    // Intercepts of quad lines with the 4 pixel edges
+    Sk4f leftCross = -(edges.fC + edges.fA * left) / edges.fB;
+    Sk4f rightCross = -(edges.fC + edges.fA * right) / edges.fB;
+    Sk4f topCross = -(edges.fC + edges.fB * top) / edges.fA;
+    Sk4f botCross = -(edges.fC + edges.fB * bot) / edges.fA;
+
+    // State for implicitly tracking the intersection boundary and area
+    SkPoint firstPoint = {0.f, 0.f};
+    SkPoint lastPoint = {0.f, 0.f};
+    bool intersected = false;
+    float area = 0.f;
+
+    // Adds a point to the intersection hull, remembering first point (for closing) and the
+    // current point, and updates the running area total.
+    // See http://mathworld.wolfram.com/PolygonArea.html
+    auto accumulate = [&](const SkPoint& p) {
+        if (intersected) {
+            float da = lastPoint.fX * p.fY - p.fX * lastPoint.fY;
+            area += da;
+        } else {
+            firstPoint = p;
+            intersected = true;
+        }
+        lastPoint = p;
+    };
+
+    // Used during iteration over the quad points to check if edge intersections are valid and
+    // should be accumulated.
+#define ADD_EDGE_CROSSING_X(SIDE) \
+    do { \
+        if (SIDE##Cross[ei] >= top && SIDE##Cross[ei] <= bot) { \
+            accumulate({SIDE, SIDE##Cross[ei]}); \
+            addedIntersection = true; \
+        } \
+    } while(false)
+#define ADD_EDGE_CROSSING_Y(SIDE) \
+    do { \
+        if (SIDE##Cross[ei] >= left && SIDE##Cross[ei] <= right) { \
+            accumulate({SIDE##Cross[ei], SIDE}); \
+            addedIntersection = true; \
+        } \
+    } while(false)
+#define TEST_EDGES(SIDE, AXIS, I, NI) \
+    do { \
+        if (!SIDE##Valid[I] && SIDE##Valid[NI]) { \
+            ADD_EDGE_CROSSING_##AXIS(SIDE); \
+            crossedEdges = true; \
+        } \
+    } while(false)
+    // Used during iteration over the quad points to check if a pixel corner should be included
+    // in the intersection boundary
+#define ADD_CORNER(CHECK, SIDE_LR, SIDE_TB) \
+    if (!CHECK##Valid[i] || !CHECK##Valid[ni]) { \
+        if (SIDE_TB##SIDE_LR##Inside) { \
+            accumulate({SIDE_LR, SIDE_TB}); \
+        } \
+    }
+#define TEST_CORNER_X(SIDE, I, NI) \
+    do { \
+        if (!SIDE##Valid[I] && SIDE##Valid[NI]) { \
+            ADD_CORNER(top, SIDE, top) else ADD_CORNER(bot, SIDE, bot) \
+        } \
+    } while(false)
+#define TEST_CORNER_Y(SIDE, I, NI) \
+    do { \
+        if (!SIDE##Valid[I] && SIDE##Valid[NI]) { \
+            ADD_CORNER(left, left, SIDE) else ADD_CORNER(right, right, SIDE) \
+        } \
+    } while(false)
+
+    // Iterate over the 4 points of the quad, adding valid intersections with the pixel edges
+    // or adding interior pixel corners as it goes. This automatically keeps all accumulated points
+    // in CCW ordering so the area can be calculated on the fly and there's no need to store the
+    // list of hull points. This is somewhat inspired by the Sutherland-Hodgman algorithm but since
+    // there are only 4 points in each source polygon, there is no point list maintenance.
+    for (int j = 0; j < 4; ++j) {
+        // Current vertex
+        int i = edges.fFlipped ? kFlippedCCW[j] : kCCW[j];
+        // Moving to this vertex
+        int ni = edges.fFlipped ? kFlippedCCW[(j + 1) % 4] : kCCW[(j + 1) % 4];
+        // Index in edge vectors corresponding to move from i to ni
+        int ei = edges.fFlipped ? ni : i;
+
+        bool crossedEdges = false;
+        bool addedIntersection = false;
+
+        // First check if there are any outside -> inside edge crossings. There can be 0, 1, or 2.
+        // 2 can occur if one crossing is still outside the pixel, or if they both go through
+        // the corner (in which case a duplicate point is added, but that doesn't change area).
+
+        // Outside to inside crossing
+        TEST_EDGES(left, X, i, ni);
+        TEST_EDGES(right, X, i, ni);
+        TEST_EDGES(top, Y, i, ni);
+        TEST_EDGES(bot, Y, i, ni);
+        // Inside to outside crossing (swapping ni and i in the boolean test)
+        TEST_EDGES(left, X, ni, i);
+        TEST_EDGES(right, X, ni, i);
+        TEST_EDGES(top, Y, ni, i);
+        TEST_EDGES(bot, Y, ni, i);
+
+        // If we crossed edges but didn't add any intersections, check the corners of the pixel.
+        // If the pixel corners are inside the quad, include them in the boundary.
+        if (crossedEdges && !addedIntersection) {
+            // This can lead to repeated points, but those just accumulate zero area
+            TEST_CORNER_X(left, i, ni);
+            TEST_CORNER_X(right, i, ni);
+            TEST_CORNER_Y(top, i, ni);
+            TEST_CORNER_Y(bot, i, ni);
+
+            TEST_CORNER_X(left, ni, i);
+            TEST_CORNER_X(right, ni, i);
+            TEST_CORNER_Y(top, ni, i);
+            TEST_CORNER_Y(bot, ni, i);
+        }
+
+        // Lastly, if the next point is completely inside the pixel it gets included in the boundary
+        if (leftValid[ni] && rightValid[ni] && topValid[ni] && botValid[ni]) {
+            accumulate({quad.fX[ni], quad.fY[ni]});
+        }
+    }
+
+#undef TEST_CORNER_Y
+#undef TEST_CORNER_X
+#undef ADD_CORNER
+
+#undef TEST_EDGES
+#undef ADD_EDGE_CROSSING_Y
+#undef ADD_EDGE_CROSSING_X
+
+    // After all points have been considered, close the boundary to get final area. If we never
+    // added any points, it means the quad didn't intersect the pixel rectangle.
+    if (intersected) {
+        // Final equation for area of convex polygon is to multiply by -1/2 (minus since the points
+        // were in CCW order).
+        accumulate(firstPoint);
+        return -0.5f * area;
+    } else {
+        return 0.f;
+    }
+}
+
+// Outsets or insets xs/ys in place. To be used when the interior is very small, edges are near
+// parallel, or edges are very short/zero-length. Returns coverage for each vertex.
+// Requires (dx, dy) to already be fixed for empty edges.
+static Sk4f compute_degenerate_quad(GrQuadAAFlags aaFlags, const Sk4f& mask, const Edges& edges,
+                                    bool outset, Vertices* quad) {
+    // Move the edge 1/2 pixel in or out depending on 'outset'.
+    Sk4f oc = edges.fC + mask * (outset ? 0.5f : -0.5f);
+
+    // There are 6 points that we care about to determine the final shape of the polygon, which
+    // are the intersections between (e0,e2), (e1,e0), (e2,e3), (e3,e1) (corresponding to the
+    // 4 corners), and (e1, e2), (e0, e3) (representing the intersections of opposite edges).
+    Sk4f denom = edges.fA * nextCW(edges.fB) - edges.fB * nextCW(edges.fA);
+    Sk4f px = (edges.fB * nextCW(oc) - oc * nextCW(edges.fB)) / denom;
+    Sk4f py = (oc * nextCW(edges.fA) - edges.fA * nextCW(oc)) / denom;
+    correct_bad_coords(denom.abs() < kTolerance, &px, &py, nullptr);
+
+    // Calculate the signed distances from these 4 corners to the other two edges that did not
+    // define the intersection. So p(0) is compared to e3,e1, p(1) to e3,e2 , p(2) to e0,e1, and
+    // p(3) to e0,e2
+    Sk4f dists1 = px * SkNx_shuffle<3, 3, 0, 0>(edges.fA) +
+                  py * SkNx_shuffle<3, 3, 0, 0>(edges.fB) +
+                  SkNx_shuffle<3, 3, 0, 0>(oc);
+    Sk4f dists2 = px * SkNx_shuffle<1, 2, 1, 2>(edges.fA) +
+                  py * SkNx_shuffle<1, 2, 1, 2>(edges.fB) +
+                  SkNx_shuffle<1, 2, 1, 2>(oc);
+
+    // If all the distances are >= 0, the 4 corners form a valid quadrilateral, so use them as
+    // the 4 points. If any point is on the wrong side of both edges, the interior has collapsed
+    // and we need to use a central point to represent it. If all four points are only on the
+    // wrong side of 1 edge, one edge has crossed over another and we use a line to represent it.
+    // Otherwise, use a triangle that replaces the bad points with the intersections of
+    // (e1, e2) or (e0, e3) as needed.
+    Sk4f d1v0 = dists1 < kTolerance;
+    Sk4f d2v0 = dists2 < kTolerance;
+    // FIXME(michaelludwig): Sk4f has anyTrue() and allTrue(), but not & or |. Sk4i has & or | but
+    // not anyTrue() and allTrue(). Moving to SkVx from SkNx will clean this up.
+    Sk4i d1And2 = Sk4i::Load(&d1v0) & Sk4i::Load(&d2v0);
+    Sk4i d1Or2 = Sk4i::Load(&d1v0) | Sk4i::Load(&d2v0);
+
+    Sk4f coverage;
+    if (!d1Or2[0] && !d1Or2[1] && !d1Or2[2] && !d1Or2[3]) {
+        // Every dists1 and dists2 >= kTolerance so it's not degenerate, use all 4 corners as-is
+        // and use full coverage
+        coverage = 1.f;
+    } else if (d1And2[0] || d1And2[1] || d1And2[2] || d1And2[3]) {
+        // A point failed against two edges, so reduce the shape to a single point, which we take as
+        // the center of the original quad to ensure it is contained in the intended geometry. Since
+        // it has collapsed, we know the shape cannot cover a pixel so update the coverage.
+        SkPoint center = {0.25f * (quad->fX[0] + quad->fX[1] + quad->fX[2] + quad->fX[3]),
+                          0.25f * (quad->fY[0] + quad->fY[1] + quad->fY[2] + quad->fY[3])};
+        coverage = get_exact_coverage(center, *quad, edges);
+        px = center.fX;
+        py = center.fY;
+    } else if (d1Or2[0] && d1Or2[1] && d1Or2[2] && d1Or2[3]) {
+        // Degenerates to a line. Compare p[2] and p[3] to edge 0. If they are on the wrong side,
+        // that means edge 0 and 3 crossed, and otherwise edge 1 and 2 crossed.
+        if (dists1[2] < kTolerance && dists1[3] < kTolerance) {
+            // Edges 0 and 3 have crossed over, so make the line from average of (p0,p2) and (p1,p3)
+            px = 0.5f * (SkNx_shuffle<0, 1, 0, 1>(px) + SkNx_shuffle<2, 3, 2, 3>(px));
+            py = 0.5f * (SkNx_shuffle<0, 1, 0, 1>(py) + SkNx_shuffle<2, 3, 2, 3>(py));
+            float mc02 = get_exact_coverage({px[0], py[0]}, *quad, edges);
+            float mc13 = get_exact_coverage({px[1], py[1]}, *quad, edges);
+            coverage = Sk4f(mc02, mc13, mc02, mc13);
+        } else {
+            // Edges 1 and 2 have crossed over, so make the line from average of (p0,p1) and (p2,p3)
+            px = 0.5f * (SkNx_shuffle<0, 0, 2, 2>(px) + SkNx_shuffle<1, 1, 3, 3>(px));
+            py = 0.5f * (SkNx_shuffle<0, 0, 2, 2>(py) + SkNx_shuffle<1, 1, 3, 3>(py));
+            float mc01 = get_exact_coverage({px[0], py[0]}, *quad, edges);
+            float mc23 = get_exact_coverage({px[2], py[2]}, *quad, edges);
+            coverage = Sk4f(mc01, mc01, mc23, mc23);
+        }
+    } else {
+        // This turns into a triangle. Replace corners as needed with the intersections between
+        // (e0,e3) and (e1,e2), which must now be calculated
+        Sk2f eDenom = SkNx_shuffle<0, 1>(edges.fA) * SkNx_shuffle<3, 2>(edges.fB) -
+                      SkNx_shuffle<0, 1>(edges.fB) * SkNx_shuffle<3, 2>(edges.fA);
+        Sk2f ex = (SkNx_shuffle<0, 1>(edges.fB) * SkNx_shuffle<3, 2>(oc) -
+                   SkNx_shuffle<0, 1>(oc) * SkNx_shuffle<3, 2>(edges.fB)) / eDenom;
+        Sk2f ey = (SkNx_shuffle<0, 1>(oc) * SkNx_shuffle<3, 2>(edges.fA) -
+                   SkNx_shuffle<0, 1>(edges.fA) * SkNx_shuffle<3, 2>(oc)) / eDenom;
+
+        if (SkScalarAbs(eDenom[0]) > kTolerance) {
+            px = d1v0.thenElse(ex[0], px);
+            py = d1v0.thenElse(ey[0], py);
+        }
+        if (SkScalarAbs(eDenom[1]) > kTolerance) {
+            px = d2v0.thenElse(ex[1], px);
+            py = d2v0.thenElse(ey[1], py);
+        }
+
+        coverage = 1.f;
+    }
+
+    outset_projected_vertices(px, py, aaFlags, quad);
+    return coverage;
 }
 
 // Computes the vertices for the two nested quads used to create AA edges. The original single quad
-// should be duplicated as input in x1 and x2, y1 and y2, and possibly u1|u2, v1|v2, [r1|r2]
-// (controlled by uvrChannelCount).  While the values should be duplicated, they should be separate
-// pointers. The outset quad is written in-place back to x1, y1, etc. and the inset inner quad is
-// written to x2, y2, etc.
-static float compute_nested_quad_vertices(GrQuadAAFlags aaFlags, Sk4f* x1, Sk4f* y1,
-        Sk4f* u1, Sk4f* v1, Sk4f* r1, Sk4f* x2, Sk4f* y2, Sk4f* u2, Sk4f* v2, Sk4f* r2,
-        int uvrCount, bool rectilinear) {
-    SkASSERT(uvrCount == 0 || uvrCount == 2 || uvrCount == 3);
+// should be duplicated as input in 'inner' and 'outer', and the resulting quad frame will be
+// stored in-place on return. Returns per-vertex coverage for the inner vertices.
+static Sk4f compute_nested_quad_vertices(GrQuadAAFlags aaFlags, bool rectilinear,
+                                         Vertices* inner, Vertices* outer, SkRect* domain) {
+    SkASSERT(inner->fUVRCount == 0 || inner->fUVRCount == 2 || inner->fUVRCount == 3);
+    SkASSERT(outer->fUVRCount == inner->fUVRCount);
 
-    // Compute edge vectors for the quad.
-    auto xnext = nextCCW(*x1);
-    auto ynext = nextCCW(*y1);
-    // xdiff and ydiff will comprise the normalized vectors pointing along each quad edge.
-    Sk4f xdiff, ydiff, invLengths;
-    compute_edge_vectors(*x1, *y1, xnext, ynext, &xdiff, &ydiff, &invLengths);
+    QuadMetadata metadata = get_metadata(*inner, aaFlags);
+
+    // Calculate domain first before updating vertices. It's only used when not rectilinear.
+    if (!rectilinear) {
+        SkASSERT(domain);
+        // The domain is the bounding box of the quad, outset by 0.5. Don't worry about edge masks
+        // since the FP only applies the domain on the exterior triangles, which are degenerate for
+        // non-AA edges.
+        domain->fLeft = outer->fX.min() - 0.5f;
+        domain->fRight = outer->fX.max() + 0.5f;
+        domain->fTop = outer->fY.min() - 0.5f;
+        domain->fBottom = outer->fY.max() + 0.5f;
+    }
 
     // When outsetting, we want the new edge to be .5px away from the old line, which means the
-    // corners may need to be adjusted by more than .5px if the matrix had sheer.
+    // corners may need to be adjusted by more than .5px if the matrix had sheer. This adjustment
+    // is only computed if there are no empty edges, and it may signal going through the slow path.
     Sk4f outset = 0.5f;
-    if (!rectilinear) {
-        adjust_non_rectilinear_outset(xdiff, ydiff, &outset);
+    if (get_optimized_outset(metadata, rectilinear, &outset)) {
+       // Since it's not subpixel, outsetting and insetting are trivial vector additions.
+        outset_vertices(outset, metadata, outer);
+        outset_vertices(-outset, metadata, inner);
+        return 1.f;
     }
 
-    // When insetting, cap the inset amount to be half of the edge length, except that each edge
-    // has to remain parallel, so we separately limit LR and TB to half of the smallest of the
-    // opposing edges.
-    Sk4f lengths = invLengths.invert();
-    Sk2f sides(SkMinScalar(lengths[0], lengths[3]), SkMinScalar(lengths[1], lengths[2]));
-    Sk4f edgeLimits = 0.5f * SkNx_shuffle<0, 1, 1, 0>(sides);
+    // Only compute edge equations once since they are the same for inner and outer quads
+    Edges edges = get_edge_equations(metadata, *inner);
 
-    if ((edgeLimits < 0.5f).anyTrue()) {
-        // Dealing with a subpixel rectangle, so must calculate clamped insets and padded outsets.
-        // The outsets are padded to ensure that the quad spans 2 pixels for improved interpolation.
-        Sk4f inset = -Sk4f::Min(outset, edgeLimits);
-        Sk4f insetCW = -Sk4f::Min(outset, nextCW(edgeLimits));
-
-        // The parallel distance shift caused by outset is currently 0.5, but need to scale it up to
-        // 0.5*(2 - side) so that (side + 2*shift) = 2px. Thus scale outsets for thin edges by
-        // (2 - side) since it already has the 1/2.
-        Sk4f outsetScale = 2.f - 2.f * Sk4f::Min(edgeLimits, 0.5f); // == 1 for non-thin edges
-        Sk4f outsetCW = outset * nextCW(outsetScale);
-        outset *= outsetScale;
-
-        if (aaFlags != GrQuadAAFlags::kAll) {
-            Sk4f mask = compute_edge_mask(aaFlags);
-            outset_masked_vertices(outset, outsetCW, xdiff, ydiff, invLengths, mask, x1, y1,
-                                   u1, v1, r1, uvrCount);
-            outset_masked_vertices(inset, insetCW, xdiff, ydiff, invLengths, mask, x2, y2,
-                                   u2, v2, r2, uvrCount);
-        } else {
-            outset_vertices(outset, outsetCW, xdiff, ydiff, invLengths, x1, y1, u1, v1, r1, uvrCount);
-            outset_vertices(inset, insetCW, xdiff, ydiff, invLengths, x2, y2, u2, v2, r2, uvrCount);
-        }
-    } else {
-        // Since it's not subpixel, the inset is just the opposite of the outset and there's no
-        // difference between CCW and CW behavior.
-        Sk4f inset = -outset;
-        if (aaFlags != GrQuadAAFlags::kAll) {
-            Sk4f mask = compute_edge_mask(aaFlags);
-            outset_masked_vertices(outset, outset, xdiff, ydiff, invLengths, mask, x1, y1,
-                                   u1, v1, r1, uvrCount);
-            outset_masked_vertices(inset, inset, xdiff, ydiff, invLengths, mask, x2, y2,
-                                   u2, v2, r2, uvrCount);
-        } else {
-            outset_vertices(outset, outset, xdiff, ydiff, invLengths, x1, y1, u1, v1, r1, uvrCount);
-            outset_vertices(inset, inset, xdiff, ydiff, invLengths, x2, y2, u2, v2, r2, uvrCount);
-        }
-    }
-
-    // An approximation of the pixel area covered by the quad
-    sides = Sk2f::Min(1.f, sides);
-    return sides[0] * sides[1];
+    // Calculate both outset and inset, returning the coverage reported for the inset, since the
+    // outset will always have 0.0f.
+    compute_degenerate_quad(aaFlags, metadata.fMask, edges, true, outer);
+    return compute_degenerate_quad(aaFlags, metadata.fMask, edges, false, inner);
 }
 
-// For each device space corner, devP, label its left/right or top/bottom opposite device space
-// point opDevPt. The new device space point is opDevPt + s (devPt - opDevPt) where s is
-// (length(devPt - opDevPt) + outset) / length(devPt - opDevPt); This returns the interpolant s,
-// adjusted for any subpixel corrections. If subpixel, it also updates the max coverage.
-static Sk4f get_projected_interpolant(const Sk4f& len, const Sk4f& outsets, float* maxCoverage) {
-    if ((len < 1.f).anyTrue()) {
-        *maxCoverage *= len.min();
+// Generalizes compute_nested_quad_vertices to extrapolate local coords such that after perspective
+// division of the device coordinates, the original local coordinate value is at the original
+// un-outset device position.
+static Sk4f compute_nested_persp_quad_vertices(const GrQuadAAFlags aaFlags, Vertices* inner,
+                                               Vertices* outer, SkRect* domain) {
+    SkASSERT(inner->fUVRCount == 0 || inner->fUVRCount == 2 || inner->fUVRCount == 3);
+    SkASSERT(outer->fUVRCount == inner->fUVRCount);
 
-        // When insetting, the amount is clamped to be half the minimum edge length to prevent
-        // overlap. When outsetting, the amount is padded to cover 2 pixels.
-        if ((outsets < 0.f).anyTrue()) {
-            return (len - 0.5f * len.min()) / len;
-        } else {
-            return (len + outsets * (2.f - len.min())) / len;
-        }
-    } else {
-        return (len + outsets) / len;
-    }
-}
+    // Calculate the projected 2D quad and use it to form projeccted inner/outer quads
+    // Don't use Sk4f.invert() here because it does not preserve 1/1 == 1, which creates rendering
+    // mismatches for 2D content that was batched into a 3D op, vs. 2D on its own.
+    Sk4f iw = 1.0f / inner->fW;
+    Sk4f x2d = inner->fX * iw;
+    Sk4f y2d = inner->fY * iw;
 
-// Generalizes compute_nested_quad_vertices to extrapolate local coords such that
-// after perspective division of the device coordinate, the original local coordinate value is at
-// the original un-outset device position. r is the local coordinate's w component. However, since
-// the projected edges will be different for inner and outer quads, there isn't much reuse between
-// the calculations, so it's easier to just have this operate on one quad a time.
-static float compute_quad_persp_vertices(GrQuadAAFlags aaFlags, Sk4f* x, Sk4f* y,
-        Sk4f* w, Sk4f* u, Sk4f* v, Sk4f* r, int uvrCount, bool inset) {
-    SkASSERT(uvrCount == 0 || uvrCount == 2 || uvrCount == 3);
+    Vertices inner2D = { x2d, y2d, /*w*/ 1.f, 0.f, 0.f, 0.f, 0 }; // No uvr outsetting in 2D
+    Vertices outer2D = inner2D;
 
-    auto iw = (*w).invert();
-    auto x2d = (*x) * iw;
-    auto y2d = (*y) * iw;
+    Sk4f coverage = compute_nested_quad_vertices(
+            aaFlags, /* rect */ false, &inner2D, &outer2D, domain);
 
-    // Must compute non-rectilinear outset quantity using the projected 2d edge vectors
-    Sk4f xdiff, ydiff, invLengths;
-    compute_edge_vectors(x2d, y2d, nextCCW(x2d), nextCCW(y2d), &xdiff, &ydiff, &invLengths);
-    Sk4f outset = inset ? -0.5f : 0.5f;
-    adjust_non_rectilinear_outset(xdiff, ydiff, &outset);
+    // Now map from the 2D inset/outset back to 3D and update the local coordinates as well
+    outset_projected_vertices(inner2D.fX, inner2D.fY, aaFlags, inner);
+    outset_projected_vertices(outer2D.fX, outer2D.fY, aaFlags, outer);
 
-    float maxProjectedCoverage = 1.f;
-
-    if ((GrQuadAAFlags::kLeft | GrQuadAAFlags::kRight) & aaFlags) {
-        // For each entry in x the equivalent entry in opX is the left/right opposite and so on.
-        Sk4f opX = SkNx_shuffle<2, 3, 0, 1>(*x);
-        Sk4f opW = SkNx_shuffle<2, 3, 0, 1>(*w);
-        Sk4f opY = SkNx_shuffle<2, 3, 0, 1>(*y);
-        // vx/vy holds the device space left-to-right vectors along top and bottom of the quad.
-        Sk2f vx = SkNx_shuffle<2, 3>(x2d) - SkNx_shuffle<0, 1>(x2d);
-        Sk2f vy = SkNx_shuffle<2, 3>(y2d) - SkNx_shuffle<0, 1>(y2d);
-        Sk4f len = SkNx_shuffle<0, 1, 0, 1>(SkNx_fma(vx, vx, vy * vy).sqrt());
-
-        // Compute t in homogeneous space from s using similar triangles so that we can produce
-        // homogeneous outset vertices for perspective-correct interpolation.
-        Sk4f s = get_projected_interpolant(len, outset, &maxProjectedCoverage);
-        Sk4f sOpW = s * opW;
-        Sk4f t = sOpW / (sOpW + (1.f - s) * (*w));
-        // mask is used to make the t values be 1 when the left/right side is not antialiased.
-        Sk4f mask(GrQuadAAFlags::kLeft & aaFlags  ? 1.f : 0.f,
-                  GrQuadAAFlags::kLeft & aaFlags  ? 1.f : 0.f,
-                  GrQuadAAFlags::kRight & aaFlags ? 1.f : 0.f,
-                  GrQuadAAFlags::kRight & aaFlags ? 1.f : 0.f);
-        t = t * mask + (1.f - mask);
-        *x = opX + t * (*x - opX);
-        *y = opY + t * (*y - opY);
-        *w = opW + t * (*w - opW);
-
-        if (uvrCount > 0) {
-            Sk4f opU = SkNx_shuffle<2, 3, 0, 1>(*u);
-            Sk4f opV = SkNx_shuffle<2, 3, 0, 1>(*v);
-            *u = opU + t * (*u - opU);
-            *v = opV + t * (*v - opV);
-            if (uvrCount == 3) {
-                Sk4f opR = SkNx_shuffle<2, 3, 0, 1>(*r);
-                *r = opR + t * (*r - opR);
-            }
-        }
-
-        if ((GrQuadAAFlags::kTop | GrQuadAAFlags::kBottom) & aaFlags) {
-            // Update the 2D points for the top/bottom calculation.
-            iw = (*w).invert();
-            x2d = (*x) * iw;
-            y2d = (*y) * iw;
-        }
-    }
-
-    if ((GrQuadAAFlags::kTop | GrQuadAAFlags::kBottom) & aaFlags) {
-        // This operates the same as above but for top/bottom rather than left/right.
-        Sk4f opX = SkNx_shuffle<1, 0, 3, 2>(*x);
-        Sk4f opW = SkNx_shuffle<1, 0, 3, 2>(*w);
-        Sk4f opY = SkNx_shuffle<1, 0, 3, 2>(*y);
-
-        Sk2f vx = SkNx_shuffle<1, 3>(x2d) - SkNx_shuffle<0, 2>(x2d);
-        Sk2f vy = SkNx_shuffle<1, 3>(y2d) - SkNx_shuffle<0, 2>(y2d);
-        Sk4f len = SkNx_shuffle<0, 0, 1, 1>(SkNx_fma(vx, vx, vy * vy).sqrt());
-
-        Sk4f s = get_projected_interpolant(len, outset, &maxProjectedCoverage);
-        Sk4f sOpW = s * opW;
-        Sk4f t = sOpW / (sOpW + (1.f - s) * (*w));
-
-        Sk4f mask(GrQuadAAFlags::kTop    & aaFlags ? 1.f : 0.f,
-                  GrQuadAAFlags::kBottom & aaFlags ? 1.f : 0.f,
-                  GrQuadAAFlags::kTop    & aaFlags ? 1.f : 0.f,
-                  GrQuadAAFlags::kBottom & aaFlags ? 1.f : 0.f);
-        t = t * mask + (1.f - mask);
-        *x = opX + t * (*x - opX);
-        *y = opY + t * (*y - opY);
-        *w = opW + t * (*w - opW);
-
-        if (uvrCount > 0) {
-            Sk4f opU = SkNx_shuffle<1, 0, 3, 2>(*u);
-            Sk4f opV = SkNx_shuffle<1, 0, 3, 2>(*v);
-            *u = opU + t * (*u - opU);
-            *v = opV + t * (*v - opV);
-            if (uvrCount == 3) {
-                Sk4f opR = SkNx_shuffle<1, 0, 3, 2>(*r);
-                *r = opR + t * (*r - opR);
-            }
-        }
-    }
-
-    return maxProjectedCoverage;
+    return coverage;
 }
 
 enum class CoverageMode {
@@ -319,7 +630,11 @@
 
 static CoverageMode get_mode_for_spec(const GrQuadPerEdgeAA::VertexSpec& spec) {
     if (spec.usesCoverageAA()) {
-        if (spec.compatibleWithAlphaAsCoverage() && spec.hasVertexColors()) {
+        if (spec.compatibleWithCoverageAsAlpha() && spec.hasVertexColors() &&
+            !spec.requiresGeometryDomain()) {
+            // Using a geometric domain acts as a second source of coverage and folding the original
+            // coverage into color makes it impossible to apply the color's alpha to the geometric
+            // domain's coverage when the original shape is clipped.
             return CoverageMode::kWithColor;
         } else {
             return CoverageMode::kWithPosition;
@@ -330,41 +645,40 @@
 }
 
 // Writes four vertices in triangle strip order, including the additional data for local
-// coordinates, domain, color, and coverage as needed to satisfy the vertex spec.
+// coordinates, geometry + texture domains, color, and coverage as needed to satisfy the vertex spec
 static void write_quad(GrVertexWriter* vb, const GrQuadPerEdgeAA::VertexSpec& spec,
-                       CoverageMode mode, float coverage,
-                       SkPMColor4f color4f, bool wideColor,
-                       const SkRect& domain,
-                       const Sk4f& x, const Sk4f& y, const Sk4f& w,
-                       const Sk4f& u, const Sk4f& v, const Sk4f& r) {
+                       CoverageMode mode, Sk4f coverage, SkPMColor4f color4f,
+                       const SkRect& geomDomain, const SkRect& texDomain, const Vertices& quad) {
     static constexpr auto If = GrVertexWriter::If<float>;
 
-    if (mode == CoverageMode::kWithColor) {
-        // Multiply the color by the coverage up front
-        SkASSERT(spec.hasVertexColors());
-        color4f = color4f * coverage;
-    }
-    GrVertexColor color(color4f, wideColor);
-
     for (int i = 0; i < 4; ++i) {
         // save position, this is a float2 or float3 or float4 depending on the combination of
         // perspective and coverage mode.
-        vb->write(x[i], y[i], If(spec.deviceQuadType() == GrQuadType::kPerspective, w[i]),
-                  If(mode == CoverageMode::kWithPosition, coverage));
+        vb->write(quad.fX[i], quad.fY[i],
+                  If(spec.deviceQuadType() == GrQuadType::kPerspective, quad.fW[i]),
+                  If(mode == CoverageMode::kWithPosition, coverage[i]));
 
         // save color
         if (spec.hasVertexColors()) {
-            vb->write(color);
+            bool wide = spec.colorType() == GrQuadPerEdgeAA::ColorType::kHalf;
+            vb->write(GrVertexColor(
+                    color4f * (mode == CoverageMode::kWithColor ? coverage[i] : 1.f), wide));
         }
 
         // save local position
         if (spec.hasLocalCoords()) {
-            vb->write(u[i], v[i], If(spec.localQuadType() == GrQuadType::kPerspective, r[i]));
+            vb->write(quad.fU[i], quad.fV[i],
+                      If(spec.localQuadType() == GrQuadType::kPerspective, quad.fR[i]));
         }
 
-        // save the domain
+        // save the geometry domain
+        if (spec.requiresGeometryDomain()) {
+            vb->write(geomDomain);
+        }
+
+        // save the texture domain
         if (spec.hasDomain()) {
-            vb->write(domain);
+            vb->write(texDomain);
         }
     }
 }
@@ -374,7 +688,7 @@
 static const int kVertsPerAAFillRect = 8;
 static const int kIndicesPerAAFillRect = 30;
 
-static sk_sp<const GrBuffer> get_index_buffer(GrResourceProvider* resourceProvider) {
+static sk_sp<const GrGpuBuffer> get_index_buffer(GrResourceProvider* resourceProvider) {
     GR_DEFINE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
 
     // clang-format off
@@ -397,67 +711,71 @@
 
 namespace GrQuadPerEdgeAA {
 
+// This is a more elaborate version of SkPMColor4fNeedsWideColor that allows "no color" for white
+ColorType MinColorType(SkPMColor4f color, GrClampType clampType, const GrCaps& caps) {
+    if (color == SK_PMColor4fWHITE) {
+        return ColorType::kNone;
+    } else {
+        return SkPMColor4fNeedsWideColor(color, clampType, caps) ? ColorType::kHalf
+                                                                 : ColorType::kByte;
+    }
+}
+
 ////////////////// Tessellate Implementation
 
 void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& deviceQuad,
                  const SkPMColor4f& color4f, const GrPerspQuad& localQuad, const SkRect& domain,
                  GrQuadAAFlags aaFlags) {
-    bool wideColor = GrQuadPerEdgeAA::ColorType::kHalf == spec.colorType();
     CoverageMode mode = get_mode_for_spec(spec);
 
     // Load position data into Sk4fs (always x, y, and load w to avoid branching down the road)
-    Sk4f oX = deviceQuad.x4f();
-    Sk4f oY = deviceQuad.y4f();
-    Sk4f oW = deviceQuad.w4f(); // Guaranteed to be 1f if it's not perspective
+    Vertices outer;
+    outer.fX = deviceQuad.x4f();
+    outer.fY = deviceQuad.y4f();
+    outer.fW = deviceQuad.w4f(); // Guaranteed to be 1f if it's not perspective
 
     // Load local position data into Sk4fs (either none, just u,v or all three)
-    Sk4f oU, oV, oR;
+    outer.fUVRCount = spec.localDimensionality();
     if (spec.hasLocalCoords()) {
-        oU = localQuad.x4f();
-        oV = localQuad.y4f();
-        oR = localQuad.w4f(); // Will be ignored if the local quad type isn't perspective
+        outer.fU = localQuad.x4f();
+        outer.fV = localQuad.y4f();
+        outer.fR = localQuad.w4f(); // Will be ignored if the local quad type isn't perspective
     }
 
     GrVertexWriter vb{vertices};
     if (spec.usesCoverageAA()) {
         SkASSERT(mode == CoverageMode::kWithPosition || mode == CoverageMode::kWithColor);
-
         // Must calculate two new quads, an outset and inset by .5 in projected device space, so
-        // duplicate the original quad into new Sk4fs for the inset.
-        Sk4f iX = oX, iY = oY, iW = oW;
-        Sk4f iU = oU, iV = oV, iR = oR;
+        // duplicate the original quad for the inner space
+        Vertices inner = outer;
 
-        float maxCoverage = 1.f;
-        if (aaFlags != GrQuadAAFlags::kNone) {
-            if (spec.deviceQuadType() == GrQuadType::kPerspective) {
-                // Outset and inset the quads independently because perspective makes each shift
-                // unique. Since iX copied pre-outset oX, this will compute the proper inset too.
-                compute_quad_persp_vertices(aaFlags, &oX, &oY, &oW, &oU, &oV, &oW,
-                                            spec.localDimensionality(), /* inset */ false);
-                // Save coverage limit when computing inset quad
-                maxCoverage = compute_quad_persp_vertices(aaFlags, &iX, &iY, &iW, &iU, &iV, &iW,
-                                                          spec.localDimensionality(), true);
-            } else {
-                // In the 2D case, insetting and outsetting can reuse the edge vectors, so the
-                // nested quads are computed together
-                maxCoverage = compute_nested_quad_vertices(aaFlags, &oX, &oY, &oU, &oV, &oR,
-                        &iX, &iY, &iU, &iV, &iR, spec.localDimensionality(),
-                        spec.deviceQuadType() <= GrQuadType::kRectilinear);
-            }
-            // NOTE: could provide an even more optimized tessellation function for axis-aligned
-            // rects since the positions can be outset by constants without doing vector math,
-            // except it must handle identifying the winding of the quad vertices if the transform
-            // applied a mirror, etc. The current 2D case is already adequately fast.
-        } // else don't adjust any positions, let the outer quad form degenerate triangles
+        SkRect geomDomain;
+        Sk4f maxCoverage = 1.f;
+        if (spec.deviceQuadType() == GrQuadType::kPerspective) {
+            // For perspective, send quads with all edges non-AA through the tessellation to ensure
+            // their corners are processed the same as adjacent quads. This approach relies on
+            // solving edge equations to reconstruct corners, which can create seams if an inner
+            // fully non-AA quad is not similarly processed.
+            maxCoverage = compute_nested_persp_quad_vertices(aaFlags, &inner, &outer, &geomDomain);
+        } else if (aaFlags != GrQuadAAFlags::kNone) {
+            // In 2D, the simpler corner math does not cause issues with seaming against non-AA
+            // inner quads.
+            maxCoverage = compute_nested_quad_vertices(
+                    aaFlags, spec.deviceQuadType() <= GrQuadType::kRectilinear, &inner, &outer,
+                    &geomDomain);
+        }
+        // NOTE: could provide an even more optimized tessellation function for axis-aligned
+        // rects since the positions can be outset by constants without doing vector math,
+        // except it must handle identifying the winding of the quad vertices if the transform
+        // applied a mirror, etc. The current 2D case is already adequately fast.
 
         // Write two quads for inner and outer, inner will use the
-        write_quad(&vb, spec, mode, maxCoverage, color4f, wideColor, domain,
-                   iX, iY, iW, iU, iV, iR);
-        write_quad(&vb, spec, mode, 0.f, color4f, wideColor, domain, oX, oY, oW, oU, oV, oR);
+        write_quad(&vb, spec, mode, maxCoverage, color4f, geomDomain, domain, inner);
+        write_quad(&vb, spec, mode, 0.f, color4f, geomDomain, domain, outer);
     } else {
         // No outsetting needed, just write a single quad with full coverage
-        SkASSERT(mode == CoverageMode::kNone);
-        write_quad(&vb, spec, mode, 1.f, color4f, wideColor, domain, oX, oY, oW, oU, oV, oR);
+        SkASSERT(mode == CoverageMode::kNone && !spec.requiresGeometryDomain());
+        write_quad(&vb, spec, mode, 1.f, color4f, SkRect::MakeEmpty(), domain, outer);
     }
 
     return vb.fPtr;
@@ -467,7 +785,7 @@
                           int quadCount) {
     if (spec.usesCoverageAA()) {
         // AA quads use 8 vertices, basically nested rectangles
-        sk_sp<const GrBuffer> ibuffer = get_index_buffer(target->resourceProvider());
+        sk_sp<const GrGpuBuffer> ibuffer = get_index_buffer(target->resourceProvider());
         if (!ibuffer) {
             return false;
         }
@@ -478,7 +796,7 @@
     } else {
         // Non-AA quads use 4 vertices, and regular triangle strip layout
         if (quadCount > 1) {
-            sk_sp<const GrBuffer> ibuffer = target->resourceProvider()->refQuadIndexBuffer();
+            sk_sp<const GrGpuBuffer> ibuffer = target->resourceProvider()->refQuadIndexBuffer();
             if (!ibuffer) {
                 return false;
             }
@@ -527,8 +845,8 @@
     const char* name() const override { return "QuadPerEdgeAAGeometryProcessor"; }
 
     void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
-        // domain, texturing, device-dimensions are single bit flags
-        uint32_t x = fDomain.isInitialized() ? 0 : 1;
+        // texturing, device-dimensions are single bit flags
+        uint32_t x = fTexDomain.isInitialized() ? 0 : 1;
         x |= fSampler.isInitialized() ? 0 : 2;
         x |= fNeedsPerspective ? 0 : 4;
         // local coords require 2 bits (3 choices), 00 for none, 01 for 2d, 10 for 3d
@@ -539,9 +857,12 @@
         if (fColor.isInitialized()) {
             x |= kUByte4_norm_GrVertexAttribType == fColor.cpuType() ? 32 : 64;
         }
-        // and coverage mode, 00 for none, 01 for withposition, 10 for withcolor
+        // and coverage mode, 00 for none, 01 for withposition, 10 for withcolor, 11 for
+        // position+geomdomain
+        SkASSERT(!fGeomDomain.isInitialized() || fCoverageMode == CoverageMode::kWithPosition);
         if (fCoverageMode != CoverageMode::kNone) {
-            x |= CoverageMode::kWithPosition == fCoverageMode ? 128 : 256;
+            x |= fGeomDomain.isInitialized() ?
+                    384 : (CoverageMode::kWithPosition == fCoverageMode ? 128 : 256);
         }
 
         b->add32(GrColorSpaceXform::XformKey(fTextureColorSpaceXform.get()));
@@ -603,6 +924,7 @@
 
                 // Solid color before any texturing gets modulated in
                 if (gp.fColor.isInitialized()) {
+                    SkASSERT(gp.fCoverageMode != CoverageMode::kWithColor || !gp.fNeedsPerspective);
                     // The color cannot be flat if the varying coverage has been modulated into it
                     args.fVaryingHandler->addPassThroughAttribute(gp.fColor, args.fOutputColor,
                             gp.fCoverageMode == CoverageMode::kWithColor ?
@@ -631,9 +953,9 @@
                     }
 
                     // Clamp the now 2D localCoordName variable by the domain if it is provided
-                    if (gp.fDomain.isInitialized()) {
+                    if (gp.fTexDomain.isInitialized()) {
                         args.fFragBuilder->codeAppend("float4 domain;");
-                        args.fVaryingHandler->addPassThroughAttribute(gp.fDomain, "domain",
+                        args.fVaryingHandler->addPassThroughAttribute(gp.fTexDomain, "domain",
                                                                       Interpolation::kCanBeFlat);
                         args.fFragBuilder->codeAppend(
                                 "texCoord = clamp(texCoord, domain.xy, domain.zw);");
@@ -652,18 +974,42 @@
                     GrGLSLVarying coverage(kFloat_GrSLType);
                     args.fVaryingHandler->addVarying("coverage", &coverage);
                     if (gp.fNeedsPerspective) {
-                        args.fVertBuilder->codeAppendf("%s = %s.w;",
-                                                       coverage.vsOut(), gp.fPosition.name());
+                        // Multiply by "W" in the vertex shader, then by 1/w (sk_FragCoord.w) in
+                        // the fragment shader to get screen-space linear coverage.
+                        args.fVertBuilder->codeAppendf("%s = %s.w * %s.z;",
+                                                       coverage.vsOut(), gp.fPosition.name(),
+                                                       gp.fPosition.name());
+                        args.fFragBuilder->codeAppendf("float coverage = %s * sk_FragCoord.w;",
+                                                        coverage.fsIn());
                     } else {
                         args.fVertBuilder->codeAppendf("%s = %s.z;",
                                                        coverage.vsOut(), gp.fPosition.name());
+                        args.fFragBuilder->codeAppendf("float coverage = %s;", coverage.fsIn());
                     }
 
-                    args.fFragBuilder->codeAppendf("%s = half4(half(%s));",
-                                                   args.fOutputCoverage, coverage.fsIn());
+                    if (gp.fGeomDomain.isInitialized()) {
+                        // Calculate distance from sk_FragCoord to the 4 edges of the domain
+                        // and clamp them to (0, 1). Use the minimum of these and the original
+                        // coverage. This only has to be done in the exterior triangles, the
+                        // interior of the quad geometry can never be clipped by the domain box.
+                        args.fFragBuilder->codeAppend("float4 geoDomain;");
+                        args.fVaryingHandler->addPassThroughAttribute(gp.fGeomDomain, "geoDomain",
+                                        Interpolation::kCanBeFlat);
+                        args.fFragBuilder->codeAppend(
+                                "if (coverage < 0.5) {"
+                                "   float4 dists4 = clamp(float4(1, 1, -1, -1) * "
+                                        "(sk_FragCoord.xyxy - geoDomain), 0, 1);"
+                                "   float2 dists2 = dists4.xy * dists4.zw;"
+                                "   coverage = min(coverage, dists2.x * dists2.y);"
+                                "}");
+                    }
+
+                    args.fFragBuilder->codeAppendf("%s = half4(half(coverage));",
+                                                   args.fOutputCoverage);
                 } else {
                     // Set coverage to 1, since it's either non-AA or the coverage was already
                     // folded into the output color
+                    SkASSERT(!gp.fGeomDomain.isInitialized());
                     args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputCoverage);
                 }
             }
@@ -712,6 +1058,12 @@
             }
         }
 
+        // Need a geometry domain when the quads are AA and not rectilinear, since their AA
+        // outsetting can go beyond a half pixel.
+        if (spec.requiresGeometryDomain()) {
+            fGeomDomain = {"geomDomain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
+        }
+
         int localDim = spec.localDimensionality();
         if (localDim == 3) {
             fLocalCoord = {"localCoord", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
@@ -726,10 +1078,10 @@
         }
 
         if (spec.hasDomain()) {
-            fDomain = {"domain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
+            fTexDomain = {"texDomain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
         }
 
-        this->setVertexAttributes(&fPosition, 4);
+        this->setVertexAttributes(&fPosition, 5);
     }
 
     const TextureSampler& onTextureSampler(int) const override { return fSampler; }
@@ -737,7 +1089,8 @@
     Attribute fPosition; // May contain coverage as last channel
     Attribute fColor; // May have coverage modulated in if the FPs support it
     Attribute fLocalCoord;
-    Attribute fDomain;
+    Attribute fGeomDomain; // Screen-space bounding box on geometry+aa outset
+    Attribute fTexDomain; // Texture-space bounding box on local coords
 
     // The positions attribute may have coverage built into it, so float3 is an ambiguous type
     // and may mean 2d with coverage, or 3d with no coverage
diff --git a/src/gpu/ops/GrQuadPerEdgeAA.h b/src/gpu/ops/GrQuadPerEdgeAA.h
index 232a10d..f031dbf 100644
--- a/src/gpu/ops/GrQuadPerEdgeAA.h
+++ b/src/gpu/ops/GrQuadPerEdgeAA.h
@@ -17,6 +17,7 @@
 #include "SkPoint.h"
 #include "SkPoint3.h"
 
+class GrCaps;
 class GrColorSpaceXform;
 class GrShaderCaps;
 
@@ -26,6 +27,9 @@
     enum class ColorType { kNone, kByte, kHalf, kLast = kHalf };
     static const int kColorTypeCount = static_cast<int>(ColorType::kLast) + 1;
 
+    // Gets the minimum ColorType that can represent a color.
+    ColorType MinColorType(SkPMColor4f, GrClampType, const GrCaps&);
+
     // Specifies the vertex configuration for an op that renders per-edge AA quads. The vertex
     // order (when enabled) is device position, color, local position, domain, aa edge equations.
     // This order matches the constructor argument order of VertexSpec and is the order that
@@ -33,14 +37,16 @@
     struct VertexSpec {
     public:
         VertexSpec(GrQuadType deviceQuadType, ColorType colorType, GrQuadType localQuadType,
-                   bool hasLocalCoords, Domain domain, GrAAType aa, bool alphaAsCoverage)
+                   bool hasLocalCoords, Domain domain, GrAAType aa, bool coverageAsAlpha)
                 : fDeviceQuadType(static_cast<unsigned>(deviceQuadType))
                 , fLocalQuadType(static_cast<unsigned>(localQuadType))
                 , fHasLocalCoords(hasLocalCoords)
                 , fColorType(static_cast<unsigned>(colorType))
                 , fHasDomain(static_cast<unsigned>(domain))
                 , fUsesCoverageAA(aa == GrAAType::kCoverage)
-                , fCompatibleWithAlphaAsCoverage(alphaAsCoverage) { }
+                , fCompatibleWithCoverageAsAlpha(coverageAsAlpha)
+                , fRequiresGeometryDomain(aa == GrAAType::kCoverage &&
+                                          deviceQuadType > GrQuadType::kRectilinear) { }
 
         GrQuadType deviceQuadType() const { return static_cast<GrQuadType>(fDeviceQuadType); }
         GrQuadType localQuadType() const { return static_cast<GrQuadType>(fLocalQuadType); }
@@ -49,8 +55,8 @@
         bool hasVertexColors() const { return ColorType::kNone != this->colorType(); }
         bool hasDomain() const { return fHasDomain; }
         bool usesCoverageAA() const { return fUsesCoverageAA; }
-        bool compatibleWithAlphaAsCoverage() const { return fCompatibleWithAlphaAsCoverage; }
-
+        bool compatibleWithCoverageAsAlpha() const { return fCompatibleWithCoverageAsAlpha; }
+        bool requiresGeometryDomain() const { return fRequiresGeometryDomain; }
         // Will always be 2 or 3
         int deviceDimensionality() const;
         // Will always be 0 if hasLocalCoords is false, otherwise will be 2 or 3
@@ -67,7 +73,10 @@
         unsigned fColorType : 2;
         unsigned fHasDomain: 1;
         unsigned fUsesCoverageAA: 1;
-        unsigned fCompatibleWithAlphaAsCoverage: 1;
+        unsigned fCompatibleWithCoverageAsAlpha: 1;
+        // The geometry domain serves to clip off pixels touched by quads with sharp corners that
+        // would otherwise exceed the miter limit for the AA-outset geometry.
+        unsigned fRequiresGeometryDomain: 1;
     };
 
     sk_sp<GrGeometryProcessor> MakeProcessor(const VertexSpec& spec);
diff --git a/src/gpu/ops/GrRegionOp.cpp b/src/gpu/ops/GrRegionOp.cpp
index 1725435..e34bb32 100644
--- a/src/gpu/ops/GrRegionOp.cpp
+++ b/src/gpu/ops/GrRegionOp.cpp
@@ -6,8 +6,10 @@
  */
 
 #include "GrRegionOp.h"
-#include <GrDrawOpTest.h>
+
+#include "GrCaps.h"
 #include "GrDefaultGeoProcFactory.h"
+#include "GrDrawOpTest.h"
 #include "GrMeshDrawOp.h"
 #include "GrOpFlushState.h"
 #include "GrResourceProvider.h"
@@ -38,7 +40,7 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const SkMatrix& viewMatrix,
                                           const SkRegion& region,
@@ -60,7 +62,6 @@
 
         SkRect bounds = SkRect::Make(region.getBounds());
         this->setTransformedBounds(bounds, viewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
-        fWideColor = !SkPMColor4fFitsInBytes(color);
     }
 
     const char* name() const override { return "GrRegionOp"; }
@@ -86,9 +87,11 @@
 
     FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
-        return fHelper.finalizeProcessors(caps, clip, GrProcessorAnalysisCoverage::kNone,
-                                          &fRegions[0].fColor);
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
+        return fHelper.finalizeProcessors(caps, clip, fsaaType, clampType,
+                                          GrProcessorAnalysisCoverage::kNone, &fRegions[0].fColor,
+                                          &fWideColor);
     }
 
 private:
@@ -109,7 +112,7 @@
         if (!numRects) {
             return;
         }
-        sk_sp<const GrBuffer> indexBuffer = target->resourceProvider()->refQuadIndexBuffer();
+        sk_sp<const GrGpuBuffer> indexBuffer = target->resourceProvider()->refQuadIndexBuffer();
         if (!indexBuffer) {
             SkDebugf("Could not allocate indices\n");
             return;
@@ -132,8 +135,11 @@
                 iter.next();
             }
         }
-        auto pipe = fHelper.makePipeline(target);
-        helper.recordDraw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState);
+        helper.recordDraw(target, std::move(gp));
+    }
+
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
     }
 
     CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
@@ -168,7 +174,7 @@
 
 namespace GrRegionOp {
 
-std::unique_ptr<GrDrawOp> Make(GrContext* context,
+std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                GrPaint&& paint,
                                const SkMatrix& viewMatrix,
                                const SkRegion& region,
diff --git a/src/gpu/ops/GrRegionOp.h b/src/gpu/ops/GrRegionOp.h
index e9281f8..badea79 100644
--- a/src/gpu/ops/GrRegionOp.h
+++ b/src/gpu/ops/GrRegionOp.h
@@ -10,8 +10,8 @@
 
 #include "GrTypesPriv.h"
 
-class GrContext;
 class GrDrawOp;
+class GrRecordingContext;
 class SkMatrix;
 class SkRegion;
 class GrPaint;
@@ -19,7 +19,7 @@
 
 namespace GrRegionOp {
 /** GrAAType must be kNone or kMSAA. */
-std::unique_ptr<GrDrawOp> Make(GrContext*,
+std::unique_ptr<GrDrawOp> Make(GrRecordingContext*,
                                GrPaint&&,
                                const SkMatrix& viewMatrix,
                                const SkRegion&,
diff --git a/src/gpu/ops/GrSemaphoreOp.cpp b/src/gpu/ops/GrSemaphoreOp.cpp
index 7160f20..518a460 100644
--- a/src/gpu/ops/GrSemaphoreOp.cpp
+++ b/src/gpu/ops/GrSemaphoreOp.cpp
@@ -7,17 +7,17 @@
 
 #include "GrSemaphoreOp.h"
 
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrGpu.h"
 #include "GrMemoryPool.h"
 #include "GrOpFlushState.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 
 class GrWaitSemaphoreOp final : public GrSemaphoreOp {
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrOp> Make(GrContext* context,
+    static std::unique_ptr<GrOp> Make(GrRecordingContext* context,
                                       sk_sp<GrSemaphore> semaphore,
                                       GrRenderTargetProxy* proxy) {
         GrOpMemoryPool* pool = context->priv().opMemoryPool();
@@ -42,7 +42,7 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-std::unique_ptr<GrOp> GrSemaphoreOp::MakeWait(GrContext* context,
+std::unique_ptr<GrOp> GrSemaphoreOp::MakeWait(GrRecordingContext* context,
                                               sk_sp<GrSemaphore> semaphore,
                                               GrRenderTargetProxy* proxy) {
     return GrWaitSemaphoreOp::Make(context, std::move(semaphore), proxy);
diff --git a/src/gpu/ops/GrSemaphoreOp.h b/src/gpu/ops/GrSemaphoreOp.h
index 9876d62..648749d 100644
--- a/src/gpu/ops/GrSemaphoreOp.h
+++ b/src/gpu/ops/GrSemaphoreOp.h
@@ -14,15 +14,18 @@
 #include "GrSemaphore.h"
 #include "SkRefCnt.h"
 
+class GrRecordingContext;
+
 class GrSemaphoreOp : public GrOp {
 public:
-    static std::unique_ptr<GrOp> MakeWait(GrContext*,
+    static std::unique_ptr<GrOp> MakeWait(GrRecordingContext*,
                                           sk_sp<GrSemaphore>,
                                           GrRenderTargetProxy*);
 
 protected:
     GrSemaphoreOp(uint32_t classId, sk_sp<GrSemaphore> semaphore, GrRenderTargetProxy* proxy)
-        : INHERITED(classId), fSemaphore(std::move(semaphore)) {
+            : INHERITED(classId)
+            , fSemaphore(std::move(semaphore)) {
         this->makeFullScreen(proxy);
     }
 
diff --git a/src/gpu/ops/GrShadowRRectOp.cpp b/src/gpu/ops/GrShadowRRectOp.cpp
index dd091a3..5ee0b37 100644
--- a/src/gpu/ops/GrShadowRRectOp.cpp
+++ b/src/gpu/ops/GrShadowRRectOp.cpp
@@ -7,11 +7,11 @@
 
 #include "GrShadowRRectOp.h"
 
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrDrawOpTest.h"
 #include "GrMemoryPool.h"
 #include "GrOpFlushState.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "SkRRectPriv.h"
 #include "effects/GrShadowGeoProc.h"
 
@@ -255,7 +255,8 @@
 
     FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
 
-    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*) override {
+    GrProcessorSet::Analysis finalize(
+            const GrCaps&, const GrAppliedClip*, GrFSAAType, GrClampType) override {
         return GrProcessorSet::EmptySetAnalysis();
     }
 
@@ -589,15 +590,16 @@
             }
         }
 
-        static const uint32_t kPipelineFlags = 0;
-        auto pipe = target->makePipeline(kPipelineFlags, GrProcessorSet::MakeEmptySet(),
-                                         target->detachAppliedClip());
-
         GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles);
         mesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1,
                          GrPrimitiveRestart::kNo);
         mesh->setVertexData(std::move(vertexBuffer), firstVertex);
-        target->draw(std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
+        target->recordDraw(std::move(gp), mesh);
+    }
+
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        flushState->executeDrawsAndUploadsForMeshDrawOp(
+                this, chainBounds, GrProcessorSet::MakeEmptySet());
     }
 
     CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
@@ -620,7 +622,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 namespace GrShadowRRectOp {
-std::unique_ptr<GrDrawOp> Make(GrContext* context,
+std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                GrColor color,
                                const SkMatrix& viewMatrix,
                                const SkRRect& rrect,
diff --git a/src/gpu/ops/GrShadowRRectOp.h b/src/gpu/ops/GrShadowRRectOp.h
index a4607cb..1b2052a 100644
--- a/src/gpu/ops/GrShadowRRectOp.h
+++ b/src/gpu/ops/GrShadowRRectOp.h
@@ -11,18 +11,18 @@
 #include <memory>
 #include "GrColor.h"
 
-class GrContext;
 class GrDrawOp;
+class GrRecordingContext;
+
 class SkMatrix;
 class SkRRect;
-class SkStrokeRec;
 
 namespace GrShadowRRectOp {
 
-std::unique_ptr<GrDrawOp> Make(GrContext*,
+std::unique_ptr<GrDrawOp> Make(GrRecordingContext*,
                                GrColor,
                                const SkMatrix& viewMatrix,
-                               const SkRRect& rrect,
+                               const SkRRect&,
                                SkScalar blurWidth,
                                SkScalar insetWidth);
 }
diff --git a/src/gpu/ops/GrSimpleMeshDrawOpHelper.cpp b/src/gpu/ops/GrSimpleMeshDrawOpHelper.cpp
index ad7fdf5..4826b3e 100644
--- a/src/gpu/ops/GrSimpleMeshDrawOpHelper.cpp
+++ b/src/gpu/ops/GrSimpleMeshDrawOpHelper.cpp
@@ -10,21 +10,19 @@
 #include "GrProcessorSet.h"
 #include "GrRect.h"
 #include "GrUserStencilSettings.h"
+#include "SkGr.h"
 
 GrSimpleMeshDrawOpHelper::GrSimpleMeshDrawOpHelper(const MakeArgs& args, GrAAType aaType,
-                                                   Flags flags)
+                                                   InputFlags inputFlags)
         : fProcessors(args.fProcessorSet)
-        , fPipelineFlags(0)
+        , fPipelineFlags((GrPipeline::InputFlags)inputFlags)
         , fAAType((int)aaType)
         , fUsesLocalCoords(false)
-        , fCompatibleWithAlphaAsCoveage(false) {
+        , fCompatibleWithCoverageAsAlpha(false) {
     SkDEBUGCODE(fDidAnalysis = false);
     SkDEBUGCODE(fMadePipeline = false);
     if (GrAATypeIsHW(aaType)) {
-        fPipelineFlags |= GrPipeline::kHWAntialias_Flag;
-    }
-    if (flags & Flags::kSnapVerticesToPixelCenters) {
-        fPipelineFlags |= GrPipeline::kSnapVerticesToPixelCenters_Flag;
+        fPipelineFlags |= GrPipeline::InputFlags::kHWAntialias;
     }
 }
 
@@ -57,13 +55,27 @@
     }
     bool result = fPipelineFlags == that.fPipelineFlags && (fAAType == that.fAAType ||
             (noneAsCoverageAA && none_as_coverage_aa_compatible(this->aaType(), that.aaType())));
-    SkASSERT(!result || fCompatibleWithAlphaAsCoveage == that.fCompatibleWithAlphaAsCoveage);
+    SkASSERT(!result || fCompatibleWithCoverageAsAlpha == that.fCompatibleWithCoverageAsAlpha);
     SkASSERT(!result || fUsesLocalCoords == that.fUsesLocalCoords);
     return result;
 }
 
 GrProcessorSet::Analysis GrSimpleMeshDrawOpHelper::finalizeProcessors(
-        const GrCaps& caps, const GrAppliedClip* clip, GrProcessorAnalysisCoverage geometryCoverage,
+        const GrCaps& caps, const GrAppliedClip* clip, GrFSAAType fsaaType, GrClampType clampType,
+        GrProcessorAnalysisCoverage geometryCoverage, SkPMColor4f* geometryColor, bool* wideColor) {
+    GrProcessorAnalysisColor color = *geometryColor;
+    auto result = this->finalizeProcessors(
+            caps, clip, fsaaType, clampType, geometryCoverage, &color);
+    color.isConstant(geometryColor);
+    if (wideColor) {
+        *wideColor = SkPMColor4fNeedsWideColor(*geometryColor, clampType, caps);
+    }
+    return result;
+}
+
+GrProcessorSet::Analysis GrSimpleMeshDrawOpHelper::finalizeProcessors(
+        const GrCaps& caps, const GrAppliedClip* clip, const GrUserStencilSettings* userStencil,
+        GrFSAAType fsaaType, GrClampType clampType, GrProcessorAnalysisCoverage geometryCoverage,
         GrProcessorAnalysisColor* geometryColor) {
     SkDEBUGCODE(fDidAnalysis = true);
     GrProcessorSet::Analysis analysis;
@@ -74,10 +86,9 @@
                                ? GrProcessorAnalysisCoverage::kSingleChannel
                                : GrProcessorAnalysisCoverage::kNone;
         }
-        bool isMixedSamples = this->aaType() == GrAAType::kMixedSamples;
         SkPMColor4f overrideColor;
-        analysis = fProcessors->finalize(*geometryColor, coverage, clip, isMixedSamples, caps,
-                                         &overrideColor);
+        analysis = fProcessors->finalize(*geometryColor, coverage, clip, userStencil, fsaaType,
+                                         caps, clampType, &overrideColor);
         if (analysis.inputColorIsOverridden()) {
             *geometryColor = overrideColor;
         }
@@ -85,20 +96,35 @@
         analysis = GrProcessorSet::EmptySetAnalysis();
     }
     fUsesLocalCoords = analysis.usesLocalCoords();
-    fCompatibleWithAlphaAsCoveage = analysis.isCompatibleWithCoverageAsAlpha();
+    fCompatibleWithCoverageAsAlpha = analysis.isCompatibleWithCoverageAsAlpha();
     return analysis;
 }
 
-GrProcessorSet::Analysis GrSimpleMeshDrawOpHelper::finalizeProcessors(
-        const GrCaps& caps, const GrAppliedClip* clip, GrProcessorAnalysisCoverage geometryCoverage,
-        SkPMColor4f* geometryColor) {
-    GrProcessorAnalysisColor color = *geometryColor;
-    auto result = this->finalizeProcessors(caps, clip, geometryCoverage, &color);
-    color.isConstant(geometryColor);
-    return result;
+void GrSimpleMeshDrawOpHelper::executeDrawsAndUploads(
+        const GrOp* op, GrOpFlushState* flushState, const SkRect& chainBounds) {
+    if (fProcessors) {
+        flushState->executeDrawsAndUploadsForMeshDrawOp(
+                op, chainBounds, std::move(*fProcessors), fPipelineFlags);
+    } else {
+        flushState->executeDrawsAndUploadsForMeshDrawOp(
+                op, chainBounds, GrProcessorSet::MakeEmptySet(), fPipelineFlags);
+    }
 }
 
 #ifdef SK_DEBUG
+static void dump_pipeline_flags(GrPipeline::InputFlags flags, SkString* result) {
+    if (GrPipeline::InputFlags::kNone != flags) {
+        if (flags & GrPipeline::InputFlags::kSnapVerticesToPixelCenters) {
+            result->append("Snap vertices to pixel center.\n");
+        }
+        if (flags & GrPipeline::InputFlags::kHWAntialias) {
+            result->append("HW Antialiasing enabled.\n");
+        }
+        return;
+    }
+    result->append("No pipeline flags\n");
+}
+
 SkString GrSimpleMeshDrawOpHelper::dumpInfo() const {
     const GrProcessorSet& processors = fProcessors ? *fProcessors : GrProcessorSet::EmptySet();
     SkString result = processors.dumpProcessors();
@@ -117,51 +143,15 @@
             result.append(" mixed samples\n");
             break;
     }
-    result.append(GrPipeline::DumpFlags(fPipelineFlags));
+    dump_pipeline_flags(fPipelineFlags, &result);
     return result;
 }
 #endif
 
-GrPipeline::InitArgs GrSimpleMeshDrawOpHelper::pipelineInitArgs(
-        GrMeshDrawOp::Target* target) const {
-    GrPipeline::InitArgs args;
-    args.fFlags = this->pipelineFlags();
-    args.fDstProxy = target->dstProxy();
-    args.fCaps = &target->caps();
-    args.fResourceProvider = target->resourceProvider();
-    return args;
-}
-
-auto GrSimpleMeshDrawOpHelper::internalMakePipeline(GrMeshDrawOp::Target* target,
-                                                    const GrPipeline::InitArgs& args,
-                                                    int numPrimitiveProcessorProxies)
-        -> PipelineAndFixedDynamicState {
-    // A caller really should only call this once as the processor set and applied clip get
-    // moved into the GrPipeline.
-    SkASSERT(!fMadePipeline);
-    SkDEBUGCODE(fMadePipeline = true);
-    auto clip = target->detachAppliedClip();
-    GrPipeline::FixedDynamicState* fixedDynamicState = nullptr;
-    if (clip.scissorState().enabled() || numPrimitiveProcessorProxies) {
-        fixedDynamicState = target->allocFixedDynamicState(clip.scissorState().rect());
-        if (numPrimitiveProcessorProxies) {
-            fixedDynamicState->fPrimitiveProcessorTextures =
-                    target->allocPrimitiveProcessorTextureArray(numPrimitiveProcessorProxies);
-        }
-    }
-    if (fProcessors) {
-        return {target->allocPipeline(args, std::move(*fProcessors), std::move(clip)),
-                fixedDynamicState};
-    } else {
-        return {target->allocPipeline(args, GrProcessorSet::MakeEmptySet(), std::move(clip)),
-                fixedDynamicState};
-    }
-}
-
 GrSimpleMeshDrawOpHelperWithStencil::GrSimpleMeshDrawOpHelperWithStencil(
         const MakeArgs& args, GrAAType aaType, const GrUserStencilSettings* stencilSettings,
-        Flags flags)
-        : INHERITED(args, aaType, flags)
+        InputFlags inputFlags)
+        : INHERITED(args, aaType, inputFlags)
         , fStencilSettings(stencilSettings ? stencilSettings : &GrUserStencilSettings::kUnused) {}
 
 GrDrawOp::FixedFunctionFlags GrSimpleMeshDrawOpHelperWithStencil::fixedFunctionFlags() const {
@@ -172,6 +162,19 @@
     return flags;
 }
 
+GrProcessorSet::Analysis GrSimpleMeshDrawOpHelperWithStencil::finalizeProcessors(
+        const GrCaps& caps, const GrAppliedClip* clip, GrFSAAType fsaaType, GrClampType clampType,
+        GrProcessorAnalysisCoverage geometryCoverage, SkPMColor4f* geometryColor, bool* wideColor) {
+    GrProcessorAnalysisColor color = *geometryColor;
+    auto result = this->finalizeProcessors(
+            caps, clip, fsaaType, clampType, geometryCoverage, &color);
+    color.isConstant(geometryColor);
+    if (wideColor) {
+        *wideColor = SkPMColor4fNeedsWideColor(*geometryColor, clampType, caps);
+    }
+    return result;
+}
+
 bool GrSimpleMeshDrawOpHelperWithStencil::isCompatible(
         const GrSimpleMeshDrawOpHelperWithStencil& that, const GrCaps& caps,
         const SkRect& thisBounds, const SkRect& thatBounds, bool noneAsCoverageAA) const {
@@ -179,12 +182,15 @@
            fStencilSettings == that.fStencilSettings;
 }
 
-auto GrSimpleMeshDrawOpHelperWithStencil::makePipeline(GrMeshDrawOp::Target* target,
-                                                       int numPrimitiveProcessorTextures)
-        -> PipelineAndFixedDynamicState {
-    auto args = INHERITED::pipelineInitArgs(target);
-    args.fUserStencil = fStencilSettings;
-    return this->internalMakePipeline(target, args, numPrimitiveProcessorTextures);
+void GrSimpleMeshDrawOpHelperWithStencil::executeDrawsAndUploads(
+        const GrOp* op, GrOpFlushState* flushState, const SkRect& chainBounds) {
+    if (fProcessors) {
+        flushState->executeDrawsAndUploadsForMeshDrawOp(
+                op, chainBounds, std::move(*fProcessors), fPipelineFlags, fStencilSettings);
+    } else {
+        flushState->executeDrawsAndUploadsForMeshDrawOp(
+                op, chainBounds, GrProcessorSet::MakeEmptySet(), fPipelineFlags, fStencilSettings);
+    }
 }
 
 #ifdef SK_DEBUG
diff --git a/src/gpu/ops/GrSimpleMeshDrawOpHelper.h b/src/gpu/ops/GrSimpleMeshDrawOpHelper.h
index 8ed33af..fee013f 100644
--- a/src/gpu/ops/GrSimpleMeshDrawOpHelper.h
+++ b/src/gpu/ops/GrSimpleMeshDrawOpHelper.h
@@ -8,12 +8,12 @@
 #ifndef GrSimpleMeshDrawOpHelper_DEFINED
 #define GrSimpleMeshDrawOpHelper_DEFINED
 
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrMemoryPool.h" // only here bc of the templated FactoryHelper
 #include "GrMeshDrawOp.h"
 #include "GrOpFlushState.h"
 #include "GrPipeline.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include <new>
 
 struct SkRect;
@@ -36,15 +36,16 @@
      * which is public or made accessible via 'friend'.
      */
     template <typename Op, typename... OpArgs>
-    static std::unique_ptr<GrDrawOp> FactoryHelper(GrContext*, GrPaint&& paint, OpArgs... opArgs);
+    static std::unique_ptr<GrDrawOp> FactoryHelper(GrRecordingContext*, GrPaint&&, OpArgs...);
 
-    enum class Flags : uint32_t {
-        kNone = 0x0,
-        kSnapVerticesToPixelCenters = 0x1,
+    // Here we allow callers to specify a subset of the GrPipeline::InputFlags upon creation.
+    enum class InputFlags : uint8_t {
+        kNone = 0,
+        kSnapVerticesToPixelCenters = (uint8_t)GrPipeline::InputFlags::kSnapVerticesToPixelCenters,
     };
-    GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(Flags);
+    GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(InputFlags);
 
-    GrSimpleMeshDrawOpHelper(const MakeArgs&, GrAAType, Flags = Flags::kNone);
+    GrSimpleMeshDrawOpHelper(const MakeArgs&, GrAAType, InputFlags = InputFlags::kNone);
     ~GrSimpleMeshDrawOpHelper();
 
     GrSimpleMeshDrawOpHelper() = delete;
@@ -68,18 +69,23 @@
      *                      this may be set to a known color in which case the op must output this
      *                      color from its geometry processor instead.
      */
-    GrProcessorSet::Analysis finalizeProcessors(const GrCaps& caps, const GrAppliedClip*,
-                                                GrProcessorAnalysisCoverage geometryCoverage,
-                                                GrProcessorAnalysisColor* geometryColor);
+    GrProcessorSet::Analysis finalizeProcessors(
+            const GrCaps& caps, const GrAppliedClip* clip, GrFSAAType fsaaType,
+            GrClampType clampType, GrProcessorAnalysisCoverage geometryCoverage,
+            GrProcessorAnalysisColor* geometryColor) {
+        return this->finalizeProcessors(caps, clip, &GrUserStencilSettings::kUnused, fsaaType,
+                                        clampType, geometryCoverage, geometryColor);
+    }
 
     /**
      * Version of above that can be used by ops that have a constant color geometry processor
      * output. The op passes this color as 'geometryColor' and after return if 'geometryColor' has
      * changed the op must override its geometry processor color output with the new color.
      */
-    GrProcessorSet::Analysis finalizeProcessors(const GrCaps&, const GrAppliedClip*,
-                                                GrProcessorAnalysisCoverage geometryCoverage,
-                                                SkPMColor4f* geometryColor);
+    GrProcessorSet::Analysis finalizeProcessors(
+            const GrCaps&, const GrAppliedClip*, GrFSAAType, GrClampType,
+            GrProcessorAnalysisCoverage geometryCoverage, SkPMColor4f* geometryColor,
+            bool* wideColor);
 
     bool isTrivial() const {
       return fProcessors == nullptr;
@@ -90,15 +96,7 @@
         return fUsesLocalCoords;
     }
 
-    bool compatibleWithAlphaAsCoverage() const { return fCompatibleWithAlphaAsCoveage; }
-
-    using PipelineAndFixedDynamicState = GrOpFlushState::PipelineAndFixedDynamicState;
-    /** Makes a pipeline that consumes the processor set and the op's applied clip. */
-    PipelineAndFixedDynamicState makePipeline(GrMeshDrawOp::Target* target,
-                                              int numPrimitiveProcessorTextures = 0) {
-        return this->internalMakePipeline(target, this->pipelineInitArgs(target),
-                                          numPrimitiveProcessorTextures);
-    }
+    bool compatibleWithCoverageAsAlpha() const { return fCompatibleWithCoverageAsAlpha; }
 
     struct MakeArgs {
     private:
@@ -124,21 +122,21 @@
       fAAType = static_cast<unsigned>(aaType);
     }
 
+    void executeDrawsAndUploads(const GrOp*, GrOpFlushState*, const SkRect& chainBounds);
+
 protected:
-    uint32_t pipelineFlags() const { return fPipelineFlags; }
+    GrPipeline::InputFlags pipelineFlags() const { return fPipelineFlags; }
 
-    GrPipeline::InitArgs pipelineInitArgs(GrMeshDrawOp::Target* target) const;
+    GrProcessorSet::Analysis finalizeProcessors(
+            const GrCaps& caps, const GrAppliedClip*, const GrUserStencilSettings*, GrFSAAType,
+            GrClampType, GrProcessorAnalysisCoverage geometryCoverage,
+            GrProcessorAnalysisColor* geometryColor);
 
-    PipelineAndFixedDynamicState internalMakePipeline(GrMeshDrawOp::Target*,
-                                                      const GrPipeline::InitArgs&,
-                                                      int numPrimitiveProcessorTextures);
-
-private:
     GrProcessorSet* fProcessors;
-    unsigned fPipelineFlags : 8;
+    GrPipeline::InputFlags fPipelineFlags;
     unsigned fAAType : 2;
     unsigned fUsesLocalCoords : 1;
-    unsigned fCompatibleWithAlphaAsCoveage : 1;
+    unsigned fCompatibleWithCoverageAsAlpha : 1;
     SkDEBUGCODE(unsigned fMadePipeline : 1;)
     SkDEBUGCODE(unsigned fDidAnalysis : 1;)
 };
@@ -151,37 +149,47 @@
 class GrSimpleMeshDrawOpHelperWithStencil : private GrSimpleMeshDrawOpHelper {
 public:
     using MakeArgs = GrSimpleMeshDrawOpHelper::MakeArgs;
-    using Flags = GrSimpleMeshDrawOpHelper::Flags;
-    using PipelineAndFixedDynamicState = GrOpFlushState::PipelineAndFixedDynamicState;
+    using InputFlags = GrSimpleMeshDrawOpHelper::InputFlags;
 
     using GrSimpleMeshDrawOpHelper::visitProxies;
 
     // using declarations can't be templated, so this is a pass through function instead.
     template <typename Op, typename... OpArgs>
-    static std::unique_ptr<GrDrawOp> FactoryHelper(GrContext* context, GrPaint&& paint,
+    static std::unique_ptr<GrDrawOp> FactoryHelper(GrRecordingContext* context, GrPaint&& paint,
                                                    OpArgs... opArgs) {
         return GrSimpleMeshDrawOpHelper::FactoryHelper<Op, OpArgs...>(
                 context, std::move(paint), std::forward<OpArgs>(opArgs)...);
     }
 
     GrSimpleMeshDrawOpHelperWithStencil(const MakeArgs&, GrAAType, const GrUserStencilSettings*,
-                                        Flags = Flags::kNone);
+                                        InputFlags = InputFlags::kNone);
 
     GrDrawOp::FixedFunctionFlags fixedFunctionFlags() const;
 
+    GrProcessorSet::Analysis finalizeProcessors(
+            const GrCaps& caps, const GrAppliedClip* clip, GrFSAAType fsaaType,
+            GrClampType clampType, GrProcessorAnalysisCoverage geometryCoverage,
+            GrProcessorAnalysisColor* geometryColor) {
+        return this->INHERITED::finalizeProcessors(
+                caps, clip, fStencilSettings, fsaaType, clampType, geometryCoverage, geometryColor);
+    }
+
+    GrProcessorSet::Analysis finalizeProcessors(
+            const GrCaps&, const GrAppliedClip*, GrFSAAType, GrClampType,
+            GrProcessorAnalysisCoverage geometryCoverage, SkPMColor4f* geometryColor,
+            bool* wideColor);
+
     using GrSimpleMeshDrawOpHelper::aaType;
     using GrSimpleMeshDrawOpHelper::setAAType;
     using GrSimpleMeshDrawOpHelper::isTrivial;
-    using GrSimpleMeshDrawOpHelper::finalizeProcessors;
     using GrSimpleMeshDrawOpHelper::usesLocalCoords;
-    using GrSimpleMeshDrawOpHelper::compatibleWithAlphaAsCoverage;
+    using GrSimpleMeshDrawOpHelper::compatibleWithCoverageAsAlpha;
 
     bool isCompatible(const GrSimpleMeshDrawOpHelperWithStencil& that, const GrCaps&,
                       const SkRect& thisBounds, const SkRect& thatBounds,
                       bool noneAACompatibleWithCoverage = false) const;
 
-    PipelineAndFixedDynamicState makePipeline(GrMeshDrawOp::Target*,
-                                              int numPrimitiveProcessorTextures = 0);
+    void executeDrawsAndUploads(const GrOp*, GrOpFlushState*, const SkRect& chainBounds);
 
 #ifdef SK_DEBUG
     SkString dumpInfo() const;
@@ -193,7 +201,7 @@
 };
 
 template <typename Op, typename... OpArgs>
-std::unique_ptr<GrDrawOp> GrSimpleMeshDrawOpHelper::FactoryHelper(GrContext* context,
+std::unique_ptr<GrDrawOp> GrSimpleMeshDrawOpHelper::FactoryHelper(GrRecordingContext* context,
                                                                   GrPaint&& paint,
                                                                   OpArgs... opArgs) {
     GrOpMemoryPool* pool = context->priv().opMemoryPool();
@@ -213,6 +221,6 @@
     }
 }
 
-GR_MAKE_BITFIELD_CLASS_OPS(GrSimpleMeshDrawOpHelper::Flags)
+GR_MAKE_BITFIELD_CLASS_OPS(GrSimpleMeshDrawOpHelper::InputFlags)
 
 #endif
diff --git a/src/gpu/ops/GrSmallPathRenderer.cpp b/src/gpu/ops/GrSmallPathRenderer.cpp
index 5132319..6737ba5 100644
--- a/src/gpu/ops/GrSmallPathRenderer.cpp
+++ b/src/gpu/ops/GrSmallPathRenderer.cpp
@@ -7,8 +7,9 @@
  */
 
 #include "GrSmallPathRenderer.h"
+
 #include "GrBuffer.h"
-#include "GrContext.h"
+#include "GrCaps.h"
 #include "GrDistanceFieldGenFromVector.h"
 #include "GrDrawOpTest.h"
 #include "GrQuad.h"
@@ -187,7 +188,7 @@
         return CanDrawPath::kNo;
     }
     // This does non-inverse coverage-based antialiased fills.
-    if (GrAAType::kCoverage != args.fAAType) {
+    if (!(AATypeFlags::kCoverage & args.fAATypeFlags)) {
         return CanDrawPath::kNo;
     }
     // TODO: Support inverse fill
@@ -229,7 +230,7 @@
     using ShapeCache = SkTDynamicHash<ShapeData, ShapeDataKey>;
     using ShapeDataList = GrSmallPathRenderer::ShapeDataList;
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const GrShape& shape,
                                           const SkMatrix& viewMatrix,
@@ -267,8 +268,6 @@
         fShapeCache = shapeCache;
         fShapeList = shapeList;
         fGammaCorrect = gammaCorrect;
-        fWideColor = !SkPMColor4fFitsInBytes(color);
-
     }
 
     const char* name() const override { return "SmallPathOp"; }
@@ -297,9 +296,11 @@
 
     FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
-        return fHelper.finalizeProcessors(caps, clip, GrProcessorAnalysisCoverage::kSingleChannel,
-                                          &fShapes.front().fColor);
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
+        return fHelper.finalizeProcessors(
+                caps, clip, fsaaType, clampType, GrProcessorAnalysisCoverage::kSingleChannel,
+                &fShapes.front().fColor, &fWideColor);
     }
 
 private:
@@ -307,7 +308,6 @@
         sk_sp<const GrBuffer> fVertexBuffer;
         sk_sp<const GrBuffer> fIndexBuffer;
         sk_sp<GrGeometryProcessor>   fGeometryProcessor;
-        const GrPipeline* fPipeline;
         GrPipeline::FixedDynamicState* fFixedDynamicState;
         int fVertexOffset;
         int fInstancesToFlush;
@@ -319,17 +319,14 @@
         static constexpr int kMaxTextures = GrDistanceFieldPathGeoProc::kMaxTextures;
         GR_STATIC_ASSERT(GrBitmapTextGeoProc::kMaxTextures == kMaxTextures);
 
-        auto pipe = fHelper.makePipeline(target, kMaxTextures);
+        FlushInfo flushInfo;
+        flushInfo.fFixedDynamicState = target->makeFixedDynamicState(kMaxTextures);
         int numActiveProxies = fAtlas->numActivePages();
         const auto proxies = fAtlas->getProxies();
         for (int i = 0; i < numActiveProxies; ++i) {
-            pipe.fFixedDynamicState->fPrimitiveProcessorTextures[i] = proxies[i].get();
+            flushInfo.fFixedDynamicState->fPrimitiveProcessorTextures[i] = proxies[i].get();
         }
 
-        FlushInfo flushInfo;
-        flushInfo.fPipeline = pipe.fPipeline;
-        flushInfo.fFixedDynamicState = pipe.fFixedDynamicState;
-
         // Setup GrGeometryProcessor
         const SkMatrix& ctm = fShapes[0].fViewMatrix;
         if (fUsesDistanceField) {
@@ -761,8 +758,7 @@
         };
 
         if (fUsesDistanceField && !ctm.hasPerspective()) {
-            GrQuad quad(translatedBounds, ctm);
-            vertices.writeQuad(quad,
+            vertices.writeQuad(GrQuad::MakeFromRect(translatedBounds, ctm),
                                color,
                                texCoords);
         } else {
@@ -794,17 +790,21 @@
         if (flushInfo->fInstancesToFlush) {
             GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles);
             int maxInstancesPerDraw =
-                static_cast<int>(flushInfo->fIndexBuffer->gpuMemorySize() / sizeof(uint16_t) / 6);
+                    static_cast<int>(flushInfo->fIndexBuffer->size() / sizeof(uint16_t) / 6);
             mesh->setIndexedPatterned(flushInfo->fIndexBuffer, kIndicesPerQuad, kVerticesPerQuad,
                                       flushInfo->fInstancesToFlush, maxInstancesPerDraw);
             mesh->setVertexData(flushInfo->fVertexBuffer, flushInfo->fVertexOffset);
-            target->draw(flushInfo->fGeometryProcessor, flushInfo->fPipeline,
-                         flushInfo->fFixedDynamicState, mesh);
+            target->recordDraw(
+                    flushInfo->fGeometryProcessor, mesh, 1, flushInfo->fFixedDynamicState, nullptr);
             flushInfo->fVertexOffset += kVerticesPerQuad * flushInfo->fInstancesToFlush;
             flushInfo->fInstancesToFlush = 0;
         }
     }
 
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
+    }
+
     const SkPMColor4f& color() const { return fShapes[0].fColor; }
     bool usesDistanceField() const { return fUsesDistanceField; }
 
@@ -940,7 +940,7 @@
 };
 
 std::unique_ptr<GrDrawOp> GrSmallPathRenderer::createOp_TestingOnly(
-                                                        GrContext* context,
+                                                        GrRecordingContext* context,
                                                         GrPaint&& paint,
                                                         const GrShape& shape,
                                                         const SkMatrix& viewMatrix,
diff --git a/src/gpu/ops/GrSmallPathRenderer.h b/src/gpu/ops/GrSmallPathRenderer.h
index c5f8eb8..d106fc0 100644
--- a/src/gpu/ops/GrSmallPathRenderer.h
+++ b/src/gpu/ops/GrSmallPathRenderer.h
@@ -17,7 +17,7 @@
 #include "SkOpts.h"
 #include "SkTDynamicHash.h"
 
-class GrContext;
+class GrRecordingContext;
 
 class ShapeData;
 class ShapeDataKey;
@@ -50,7 +50,7 @@
     using ShapeCache = SkTDynamicHash<ShapeData, ShapeDataKey>;
     typedef SkTInternalLList<ShapeData> ShapeDataList;
 
-    static std::unique_ptr<GrDrawOp> createOp_TestingOnly(GrContext*,
+    static std::unique_ptr<GrDrawOp> createOp_TestingOnly(GrRecordingContext*,
                                                           GrPaint&&,
                                                           const GrShape&,
                                                           const SkMatrix& viewMatrix,
diff --git a/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp b/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp
index 16db3b3..1da8fa5 100644
--- a/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp
+++ b/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp
@@ -7,11 +7,11 @@
 
 #include "GrStencilAndCoverPathRenderer.h"
 #include "GrCaps.h"
-#include "GrContext.h"
 #include "GrDrawPathOp.h"
 #include "GrFixedClip.h"
 #include "GrGpu.h"
 #include "GrPath.h"
+#include "GrRecordingContext.h"
 #include "GrRenderTargetContextPriv.h"
 #include "GrResourceProvider.h"
 #include "GrShape.h"
@@ -46,7 +46,7 @@
         return CanDrawPath::kNo;
     }
     // doesn't do per-path AA, relies on the target having MSAA.
-    if (GrAAType::kCoverage == args.fAAType) {
+    if (AATypeFlags::kCoverage == args.fAATypeFlags) {
         return CanDrawPath::kNo;
     }
     return CanDrawPath::kYes;
@@ -81,8 +81,8 @@
     GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(),
                               "GrStencilAndCoverPathRenderer::onStencilPath");
     sk_sp<GrPath> p(get_gr_path(fResourceProvider, *args.fShape));
-    args.fRenderTargetContext->priv().stencilPath(*args.fClip, args.fAAType,
-                                                  *args.fViewMatrix, p.get());
+    args.fRenderTargetContext->priv().stencilPath(
+            *args.fClip, args.fDoStencilMSAA, *args.fViewMatrix, p.get());
 }
 
 bool GrStencilAndCoverPathRenderer::onDrawPath(const DrawPathArgs& args) {
@@ -92,6 +92,9 @@
 
     const SkMatrix& viewMatrix = *args.fViewMatrix;
 
+    bool doStencilMSAA = AATypeFlags::kNone != args.fAATypeFlags;
+    SkASSERT(!doStencilMSAA ||
+             (AATypeFlags::kMSAA | AATypeFlags::kMixedSampledStencilThenCover) & args.fAATypeFlags);
 
     sk_sp<GrPath> path(get_gr_path(fResourceProvider, *args.fShape));
 
@@ -106,8 +109,9 @@
 
         // fake inverse with a stencil and cover
         GrAppliedClip appliedClip;
-        if (!args.fClip->apply(args.fContext, args.fRenderTargetContext,
-                               GrAATypeIsHW(args.fAAType), true, &appliedClip, &devBounds)) {
+        if (!args.fClip->apply(
+                args.fContext, args.fRenderTargetContext, doStencilMSAA, true, &appliedClip,
+                &devBounds)) {
             return true;
         }
         GrStencilClip stencilClip(appliedClip.stencilStackID());
@@ -120,8 +124,8 @@
         }
         // Just ignore the analytic FPs (if any) during the stencil pass. They will still clip the
         // final draw and it is meaningless to multiply by coverage when drawing to stencil.
-        args.fRenderTargetContext->priv().stencilPath(stencilClip, args.fAAType, viewMatrix,
-                                                      path.get());
+        args.fRenderTargetContext->priv().stencilPath(
+                stencilClip, GrAA(doStencilMSAA), viewMatrix, path.get());
 
         {
             static constexpr GrUserStencilSettings kInvertedCoverPass(
@@ -153,23 +157,22 @@
 
             // We have to suppress enabling MSAA for mixed samples or we will get seams due to
             // coverage modulation along the edge where two triangles making up the rect meet.
-            GrAAType coverAAType = args.fAAType;
-            if (GrAAType::kMixedSamples == coverAAType) {
-                coverAAType = GrAAType::kNone;
+            GrAAType coverAAType = GrAAType::kNone;
+            if (AATypeFlags::kMSAA & args.fAATypeFlags) {
+                SkASSERT(!(AATypeFlags::kMixedSampledStencilThenCover & args.fAATypeFlags));
+                coverAAType = GrAAType::kMSAA;
             }
             // This is a non-coverage aa rect operation
             SkASSERT(coverAAType == GrAAType::kNone || coverAAType == GrAAType::kMSAA);
             std::unique_ptr<GrDrawOp> op = GrFillRectOp::MakeWithLocalMatrix(
-                                                         args.fContext, std::move(args.fPaint),
-                                                         coverAAType, coverMatrix, localMatrix,
-                                                         coverBounds, &kInvertedCoverPass);
+                    args.fContext, std::move(args.fPaint), coverAAType, coverMatrix, localMatrix,
+                    coverBounds, &kInvertedCoverPass);
 
             args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op));
         }
     } else {
-        std::unique_ptr<GrDrawOp> op =
-                GrDrawPathOp::Make(args.fContext, viewMatrix, std::move(args.fPaint),
-                                   args.fAAType, path.get());
+        std::unique_ptr<GrDrawOp> op = GrDrawPathOp::Make(
+                args.fContext, viewMatrix, std::move(args.fPaint), GrAA(doStencilMSAA), path.get());
         args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op));
     }
 
diff --git a/src/gpu/ops/GrStencilAndCoverPathRenderer.h b/src/gpu/ops/GrStencilAndCoverPathRenderer.h
index 8649be2..ed82cf6 100644
--- a/src/gpu/ops/GrStencilAndCoverPathRenderer.h
+++ b/src/gpu/ops/GrStencilAndCoverPathRenderer.h
@@ -10,7 +10,6 @@
 
 #include "GrPathRenderer.h"
 
-class GrContext;
 class GrGpu;
 class GrResourceProvider;
 
diff --git a/src/gpu/ops/GrStencilPathOp.cpp b/src/gpu/ops/GrStencilPathOp.cpp
index 7d913ea..ac12f5f 100644
--- a/src/gpu/ops/GrStencilPathOp.cpp
+++ b/src/gpu/ops/GrStencilPathOp.cpp
@@ -7,14 +7,14 @@
 
 #include "GrStencilPathOp.h"
 
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrGpu.h"
 #include "GrMemoryPool.h"
 #include "GrOpFlushState.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetPriv.h"
 
-std::unique_ptr<GrOp> GrStencilPathOp::Make(GrContext* context,
+std::unique_ptr<GrOp> GrStencilPathOp::Make(GrRecordingContext* context,
                                             const SkMatrix& viewMatrix,
                                             bool useHWAA,
                                             GrPathRendering::FillType fillType,
diff --git a/src/gpu/ops/GrStencilPathOp.h b/src/gpu/ops/GrStencilPathOp.h
index 9facfe9..5b7bf86 100644
--- a/src/gpu/ops/GrStencilPathOp.h
+++ b/src/gpu/ops/GrStencilPathOp.h
@@ -13,14 +13,14 @@
 #include "GrPathRendering.h"
 #include "GrStencilSettings.h"
 
-class GrContext;
 class GrOpFlushState;
+class GrRecordingContext;
 
 class GrStencilPathOp final : public GrOp {
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrOp> Make(GrContext* context,
+    static std::unique_ptr<GrOp> Make(GrRecordingContext* context,
                                       const SkMatrix& viewMatrix,
                                       bool useHWAA,
                                       GrPathRendering::FillType fillType,
diff --git a/src/gpu/ops/GrStrokeRectOp.cpp b/src/gpu/ops/GrStrokeRectOp.cpp
index 9495333..c7b469c 100644
--- a/src/gpu/ops/GrStrokeRectOp.cpp
+++ b/src/gpu/ops/GrStrokeRectOp.cpp
@@ -7,6 +7,7 @@
 
 #include "GrStrokeRectOp.h"
 
+#include "GrCaps.h"
 #include "GrColor.h"
 #include "GrDefaultGeoProcFactory.h"
 #include "GrDrawOpTest.h"
@@ -106,7 +107,7 @@
     }
 #endif
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const SkMatrix& viewMatrix,
                                           const SkRect& rect,
@@ -116,22 +117,22 @@
         if (!allowed_stroke(stroke, GrAA::kNo, &isMiter)) {
             return nullptr;
         }
-        Helper::Flags flags = Helper::Flags::kNone;
+        Helper::InputFlags inputFlags = Helper::InputFlags::kNone;
         // Depending on sub-pixel coordinates and the particular GPU, we may lose a corner of
         // hairline rects. We jam all the vertices to pixel centers to avoid this, but not
         // when MSAA is enabled because it can cause ugly artifacts.
         if (stroke.getStyle() == SkStrokeRec::kHairline_Style && aaType != GrAAType::kMSAA) {
-            flags |= Helper::Flags::kSnapVerticesToPixelCenters;
+            inputFlags |= Helper::InputFlags::kSnapVerticesToPixelCenters;
         }
-        return Helper::FactoryHelper<NonAAStrokeRectOp>(context, std::move(paint), flags,
+        return Helper::FactoryHelper<NonAAStrokeRectOp>(context, std::move(paint), inputFlags,
                                                         viewMatrix, rect,
                                                         stroke, aaType);
     }
 
     NonAAStrokeRectOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
-                      Helper::Flags flags, const SkMatrix& viewMatrix, const SkRect& rect,
+                      Helper::InputFlags inputFlags, const SkMatrix& viewMatrix, const SkRect& rect,
                       const SkStrokeRec& stroke, GrAAType aaType)
-            : INHERITED(ClassID()), fHelper(helperArgs, aaType, flags) {
+            : INHERITED(ClassID()), fHelper(helperArgs, aaType, inputFlags) {
         fColor = color;
         fViewMatrix = viewMatrix;
         fRect = rect;
@@ -144,7 +145,7 @@
         bounds.outset(rad, rad);
 
         // If our caller snaps to pixel centers then we have to round out the bounds
-        if (flags & Helper::Flags::kSnapVerticesToPixelCenters) {
+        if (inputFlags & Helper::InputFlags::kSnapVerticesToPixelCenters) {
             viewMatrix.mapRect(&bounds);
             // We want to be consistent with how we snap non-aa lines. To match what we do in
             // GrGLSLVertexShaderBuilder, we first floor all the vertex values and then add half a
@@ -162,8 +163,11 @@
 
     FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
-        return fHelper.finalizeProcessors(caps, clip, GrProcessorAnalysisCoverage::kNone, &fColor);
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
+        // This Op uses uniform (not vertex) color, so doesn't need to track wide color.
+        return fHelper.finalizeProcessors(caps, clip, fsaaType, clampType,
+                                          GrProcessorAnalysisCoverage::kNone, &fColor, nullptr);
     }
 
 private:
@@ -216,8 +220,11 @@
         GrMesh* mesh = target->allocMesh(primType);
         mesh->setNonIndexedNonInstanced(vertexCount);
         mesh->setVertexData(std::move(vertexBuffer), firstVertex);
-        auto pipe = fHelper.makePipeline(target);
-        target->draw(std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
+        target->recordDraw(std::move(gp), mesh);
+    }
+
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
     }
 
     // TODO: override onCombineIfPossible
@@ -317,7 +324,7 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const SkMatrix& viewMatrix,
                                           const SkRect& devOutside,
@@ -337,10 +344,9 @@
         fRects.emplace_back(RectInfo{color, devOutside, devOutside, devInside, false});
         this->setBounds(devOutside, HasAABloat::kYes, IsZeroArea::kNo);
         fMiterStroke = true;
-        fWideColor = !SkPMColor4fFitsInBytes(color);
     }
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const SkMatrix& viewMatrix,
                                           const SkRect& rect,
@@ -360,7 +366,6 @@
             , fHelper(helperArgs, GrAAType::kCoverage)
             , fViewMatrix(viewMatrix) {
         fMiterStroke = isMiter;
-        fWideColor = !SkPMColor4fFitsInBytes(color);
         RectInfo& info = fRects.push_back();
         compute_aa_rects(&info.fDevOutside, &info.fDevOutsideAssist, &info.fDevInside,
                          &info.fDegenerate, viewMatrix, rect, stroke.getWidth(), isMiter);
@@ -404,13 +409,16 @@
 
     FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
-        return fHelper.finalizeProcessors(caps, clip, GrProcessorAnalysisCoverage::kSingleChannel,
-                                          &fRects.back().fColor);
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
+        return fHelper.finalizeProcessors(
+                caps, clip, fsaaType, clampType, GrProcessorAnalysisCoverage::kSingleChannel,
+                &fRects.back().fColor, &fWideColor);
     }
 
 private:
     void onPrepareDraws(Target*) override;
+    void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
 
     static const int kMiterIndexCnt = 3 * 24;
     static const int kMiterVertexCnt = 16;
@@ -420,7 +428,7 @@
     static const int kBevelVertexCnt = 24;
     static const int kNumBevelRectsInIndexBuffer = 256;
 
-    static sk_sp<const GrBuffer> GetIndexBuffer(GrResourceProvider*, bool miterStroke);
+    static sk_sp<const GrGpuBuffer> GetIndexBuffer(GrResourceProvider*, bool miterStroke);
 
     const SkMatrix& viewMatrix() const { return fViewMatrix; }
     bool miterStroke() const { return fMiterStroke; }
@@ -457,7 +465,7 @@
 
 void AAStrokeRectOp::onPrepareDraws(Target* target) {
     sk_sp<GrGeometryProcessor> gp(create_aa_stroke_rect_gp(target->caps().shaderCaps(),
-                                                           fHelper.compatibleWithAlphaAsCoverage(),
+                                                           fHelper.compatibleWithCoverageAsAlpha(),
                                                            this->viewMatrix(),
                                                            fHelper.usesLocalCoords(),
                                                            fWideColor));
@@ -472,7 +480,7 @@
     int indicesPerInstance = this->miterStroke() ? kMiterIndexCnt : kBevelIndexCnt;
     int instanceCount = fRects.count();
 
-    sk_sp<const GrBuffer> indexBuffer =
+    sk_sp<const GrGpuBuffer> indexBuffer =
             GetIndexBuffer(target->resourceProvider(), this->miterStroke());
     if (!indexBuffer) {
         SkDebugf("Could not allocate indices\n");
@@ -497,14 +505,17 @@
                                            info.fDevInside,
                                            fMiterStroke,
                                            info.fDegenerate,
-                                           fHelper.compatibleWithAlphaAsCoverage());
+                                           fHelper.compatibleWithCoverageAsAlpha());
     }
-    auto pipe = fHelper.makePipeline(target);
-    helper.recordDraw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState);
+    helper.recordDraw(target, std::move(gp));
 }
 
-sk_sp<const GrBuffer> AAStrokeRectOp::GetIndexBuffer(GrResourceProvider* resourceProvider,
-                                                     bool miterStroke) {
+void AAStrokeRectOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
+    fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
+}
+
+sk_sp<const GrGpuBuffer> AAStrokeRectOp::GetIndexBuffer(GrResourceProvider* resourceProvider,
+                                                        bool miterStroke) {
     if (miterStroke) {
         // clang-format off
         static const uint16_t gMiterIndices[] = {
@@ -732,7 +743,7 @@
 
 namespace GrStrokeRectOp {
 
-std::unique_ptr<GrDrawOp> Make(GrContext* context,
+std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                GrPaint&& paint,
                                GrAAType aaType,
                                const SkMatrix& viewMatrix,
@@ -749,7 +760,7 @@
     }
 }
 
-std::unique_ptr<GrDrawOp> MakeNested(GrContext* context,
+std::unique_ptr<GrDrawOp> MakeNested(GrRecordingContext* context,
                                      GrPaint&& paint,
                                      const SkMatrix& viewMatrix,
                                      const SkRect rects[2]) {
diff --git a/src/gpu/ops/GrStrokeRectOp.h b/src/gpu/ops/GrStrokeRectOp.h
index 97ea865..510579e 100644
--- a/src/gpu/ops/GrStrokeRectOp.h
+++ b/src/gpu/ops/GrStrokeRectOp.h
@@ -12,6 +12,7 @@
 
 class GrDrawOp;
 class GrPaint;
+class GrRecordingContext;
 class SkMatrix;
 struct SkRect;
 class SkStrokeRec;
@@ -24,7 +25,7 @@
  */
 namespace GrStrokeRectOp {
 
-std::unique_ptr<GrDrawOp> Make(GrContext* context,
+std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                GrPaint&& paint,
                                GrAAType aaType,
                                const SkMatrix& viewMatrix,
@@ -34,7 +35,7 @@
 // rects[0] == outer rectangle, rects[1] == inner rectangle. Null return means there is nothing to
 // draw rather than failure. The area between the rectangles will be filled by the paint, and it
 // will be anti-aliased with coverage AA. viewMatrix.rectStaysRect() must be true.
-std::unique_ptr<GrDrawOp> MakeNested(GrContext* context,
+std::unique_ptr<GrDrawOp> MakeNested(GrRecordingContext* context,
                                      GrPaint&& paint,
                                      const SkMatrix& viewMatrix,
                                      const SkRect rects[2]);
diff --git a/src/gpu/ops/GrTessellatingPathRenderer.cpp b/src/gpu/ops/GrTessellatingPathRenderer.cpp
index 0314e4a..a091eaf 100644
--- a/src/gpu/ops/GrTessellatingPathRenderer.cpp
+++ b/src/gpu/ops/GrTessellatingPathRenderer.cpp
@@ -8,6 +8,7 @@
 #include "GrTessellatingPathRenderer.h"
 #include <stdio.h>
 #include "GrAuditTrail.h"
+#include "GrCaps.h"
 #include "GrClip.h"
 #include "GrDefaultGeoProcFactory.h"
 #include "GrDrawOpTest.h"
@@ -53,7 +54,7 @@
     }
 };
 
-bool cache_match(GrBuffer* vertexBuffer, SkScalar tol, int* actualCount) {
+bool cache_match(GrGpuBuffer* vertexBuffer, SkScalar tol, int* actualCount) {
     if (!vertexBuffer) {
         return false;
     }
@@ -78,8 +79,7 @@
     void* lock(int vertexCount) override {
         size_t size = vertexCount * stride();
         fVertexBuffer = fResourceProvider->createBuffer(size, GrGpuBufferType::kVertex,
-                                                        kStatic_GrAccessPattern,
-                                                        GrResourceProvider::Flags::kNone);
+                                                        kStatic_GrAccessPattern);
         if (!fVertexBuffer.get()) {
             return nullptr;
         }
@@ -99,10 +99,10 @@
         }
         fVertices = nullptr;
     }
-    sk_sp<GrBuffer> detachVertexBuffer() { return std::move(fVertexBuffer); }
+    sk_sp<GrGpuBuffer> detachVertexBuffer() { return std::move(fVertexBuffer); }
 
 private:
-    sk_sp<GrBuffer> fVertexBuffer;
+    sk_sp<GrGpuBuffer> fVertexBuffer;
     GrResourceProvider* fResourceProvider;
     bool fCanMapVB;
     void* fVertices;
@@ -145,21 +145,25 @@
     // This path renderer can draw fill styles, and can do screenspace antialiasing via a
     // one-pixel coverage ramp. It can do convex and concave paths, but we'll leave the convex
     // ones to simpler algorithms. We pass on paths that have styles, though they may come back
-    // around after applying the styling information to the geometry to create a filled path. In
-    // the non-AA case, We skip paths that don't have a key since the real advantage of this path
-    // renderer comes from caching the tessellated geometry. In the AA case, we do not cache, so we
-    // accept paths without keys.
+    // around after applying the styling information to the geometry to create a filled path.
     if (!args.fShape->style().isSimpleFill() || args.fShape->knownToBeConvex()) {
         return CanDrawPath::kNo;
     }
-    if (GrAAType::kCoverage == args.fAAType) {
+    if (AATypeFlags::kNone == args.fAATypeFlags || (AATypeFlags::kMSAA & args.fAATypeFlags)) {
+        // Prefer MSAA, if any antialiasing. In the non-analytic-AA case, We skip paths that don't
+        // have a key since the real advantage of this path renderer comes from caching the
+        // tessellated geometry.
+        if (!args.fShape->hasUnstyledKey()) {
+            return CanDrawPath::kNo;
+        }
+    } else if (AATypeFlags::kCoverage & args.fAATypeFlags) {
+        // Use analytic AA if we don't have MSAA. In this case, we do not cache, so we accept paths
+        // without keys.
         SkPath path;
         args.fShape->asPath(&path);
         if (path.countVerbs() > GR_AA_TESSELLATOR_MAX_VERB_COUNT) {
             return CanDrawPath::kNo;
         }
-    } else if (!args.fShape->hasUnstyledKey()) {
-        return CanDrawPath::kNo;
     }
     return CanDrawPath::kYes;
 }
@@ -173,7 +177,7 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const GrShape& shape,
                                           const SkMatrix& viewMatrix,
@@ -227,11 +231,14 @@
 
     FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
         GrProcessorAnalysisCoverage coverage = fAntiAlias
                                                        ? GrProcessorAnalysisCoverage::kSingleChannel
                                                        : GrProcessorAnalysisCoverage::kNone;
-        return fHelper.finalizeProcessors(caps, clip, coverage, &fColor);
+        // This Op uses uniform (not vertex) color, so doesn't need to track wide color.
+        return fHelper.finalizeProcessors(
+                caps, clip, fsaaType, clampType, coverage, &fColor, nullptr);
     }
 
 private:
@@ -261,7 +268,7 @@
             memset(&builder[shapeKeyDataCnt], 0, sizeof(fDevClipBounds));
         }
         builder.finish();
-        sk_sp<GrBuffer> cachedVertexBuffer(rp->findByUniqueKey<GrBuffer>(key));
+        sk_sp<GrGpuBuffer> cachedVertexBuffer(rp->findByUniqueKey<GrGpuBuffer>(key));
         int actualCount;
         SkScalar tol = GrPathUtils::kDefaultTolerance;
         tol = GrPathUtils::scaleToleranceToSrc(tol, fViewMatrix, fShape.bounds());
@@ -286,7 +293,7 @@
         if (count == 0) {
             return;
         }
-        sk_sp<GrBuffer> vb = allocator.detachVertexBuffer();
+        sk_sp<GrGpuBuffer> vb = allocator.detachVertexBuffer();
         TessInfo info;
         info.fTolerance = isLinear ? 0 : tol;
         info.fCount = count;
@@ -328,7 +335,7 @@
                                                         : LocalCoords::kUnused_Type;
             Coverage::Type coverageType;
             if (fAntiAlias) {
-                if (fHelper.compatibleWithAlphaAsCoverage()) {
+                if (fHelper.compatibleWithCoverageAsAlpha()) {
                     coverageType = Coverage::kAttributeTweakAlpha_Type;
                 } else {
                     coverageType = Coverage::kAttribute_Type;
@@ -363,8 +370,11 @@
                                                                : GrPrimitiveType::kTriangles);
         mesh->setNonIndexedNonInstanced(count);
         mesh->setVertexData(std::move(vb), firstVertex);
-        auto pipe = fHelper.makePipeline(target);
-        target->draw(std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
+        target->recordDraw(std::move(gp), mesh);
+    }
+
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
     }
 
     Helper fHelper;
@@ -386,13 +396,15 @@
     args.fClip->getConservativeBounds(args.fRenderTargetContext->width(),
                                       args.fRenderTargetContext->height(),
                                       &clipBoundsI);
-    std::unique_ptr<GrDrawOp> op = TessellatingPathOp::Make(args.fContext,
-                                                            std::move(args.fPaint),
-                                                            *args.fShape,
-                                                            *args.fViewMatrix,
-                                                            clipBoundsI,
-                                                            args.fAAType,
-                                                            args.fUserStencilSettings);
+    GrAAType aaType = GrAAType::kNone;
+    if (AATypeFlags::kMSAA & args.fAATypeFlags) {
+        aaType = GrAAType::kMSAA;
+    } else if (AATypeFlags::kCoverage & args.fAATypeFlags) {
+        aaType = GrAAType::kCoverage;
+    }
+    std::unique_ptr<GrDrawOp> op = TessellatingPathOp::Make(
+            args.fContext, std::move(args.fPaint), *args.fShape, *args.fViewMatrix, clipBoundsI,
+            aaType, args.fUserStencilSettings);
     args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op));
     return true;
 }
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index 4ebe9f6..6426199 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -9,8 +9,6 @@
 #include <new>
 #include "GrAppliedClip.h"
 #include "GrCaps.h"
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrDrawOpTest.h"
 #include "GrGeometryProcessor.h"
 #include "GrGpu.h"
@@ -19,6 +17,8 @@
 #include "GrOpFlushState.h"
 #include "GrQuad.h"
 #include "GrQuadPerEdgeAA.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrResourceProvider.h"
 #include "GrResourceProviderPriv.h"
 #include "GrShaderCaps.h"
@@ -74,8 +74,8 @@
 
 // If normalizing the src quad then pass 1/width, 1/height, 1 for iw, ih, h. Otherwise pass
 // 1, 1, and height.
-static GrPerspQuad compute_src_quad(GrSurfaceOrigin origin, const SkRect& srcRect, float iw,
-                                    float ih, float h) {
+static GrPerspQuad compute_src_quad_from_rect(GrSurfaceOrigin origin, const SkRect& srcRect,
+                                              float iw, float ih, float h) {
     // Convert the pixel-space src rectangle into normalized texture coordinates
     SkRect texRect = {
         iw * srcRect.fLeft,
@@ -87,7 +87,19 @@
         texRect.fTop = h - texRect.fTop;
         texRect.fBottom = h - texRect.fBottom;
     }
-    return GrPerspQuad(texRect, SkMatrix::I());
+    return GrPerspQuad(texRect);
+}
+// Normalizes logical src coords and corrects for origin
+static GrPerspQuad compute_src_quad(GrSurfaceOrigin origin, const GrPerspQuad& srcQuad,
+                                    float iw, float ih, float h) {
+    // The src quad should not have any perspective
+    SkASSERT(!srcQuad.hasPerspective());
+    Sk4f xs = srcQuad.x4f() * iw;
+    Sk4f ys = srcQuad.y4f() * ih;
+    if (origin == kBottomLeft_GrSurfaceOrigin) {
+        ys = h - ys;
+    }
+    return GrPerspQuad(xs, ys);
 }
 
 /**
@@ -96,7 +108,7 @@
  */
 class TextureOp final : public GrMeshDrawOp {
 public:
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           sk_sp<GrTextureProxy> proxy,
                                           GrSamplerState::Filter filter,
                                           const SkPMColor4f& color,
@@ -107,22 +119,68 @@
                                           SkCanvas::SrcRectConstraint constraint,
                                           const SkMatrix& viewMatrix,
                                           sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
-        GrOpMemoryPool* pool = context->priv().opMemoryPool();
+        GrPerspQuad dstQuad = GrPerspQuad::MakeFromRect(dstRect, viewMatrix);
+        GrQuadType dstQuadType = GrQuadTypeForTransformedRect(viewMatrix);
 
+        if (dstQuadType == GrQuadType::kRect) {
+            // Disable filtering if possible (note AA optimizations for rects are automatically
+            // handled above in GrResolveAATypeForQuad).
+            if (filter != GrSamplerState::Filter::kNearest &&
+                !GrTextureOp::GetFilterHasEffect(viewMatrix, srcRect, dstRect)) {
+                filter = GrSamplerState::Filter::kNearest;
+            }
+        }
+
+        GrOpMemoryPool* pool = context->priv().opMemoryPool();
+        // srcRect provides both local coords and domain (if needed), so use nullptr for srcQuad
         return pool->allocate<TextureOp>(
-                std::move(proxy), filter, color, srcRect, dstRect, aaType, aaFlags, constraint,
-                viewMatrix, std::move(textureColorSpaceXform));
+                std::move(proxy), filter, color, dstQuad, dstQuadType, srcRect, constraint,
+                nullptr, GrQuadType::kRect, aaType, aaFlags, std::move(textureColorSpaceXform));
     }
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
+                                          sk_sp<GrTextureProxy> proxy,
+                                          GrSamplerState::Filter filter,
+                                          const SkPMColor4f& color,
+                                          const SkPoint srcQuad[4],
+                                          const SkPoint dstQuad[4],
+                                          GrAAType aaType,
+                                          GrQuadAAFlags aaFlags,
+                                          const SkRect* domain,
+                                          const SkMatrix& viewMatrix,
+                                          sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
+        GrPerspQuad grDstQuad = GrPerspQuad::MakeFromSkQuad(dstQuad, viewMatrix);
+        GrQuadType dstQuadType = GrQuadTypeForPoints(dstQuad, viewMatrix);
+        GrPerspQuad grSrcQuad = GrPerspQuad::MakeFromSkQuad(srcQuad, SkMatrix::I());
+        GrQuadType srcQuadType = GrQuadTypeForPoints(srcQuad, SkMatrix::I());
+
+        // If constraint remains fast, the value in srcRect will be ignored since srcQuads provides
+        // the local coordinates and a domain won't be used.
+        SkRect srcRect = SkRect::MakeEmpty();
+        SkCanvas::SrcRectConstraint constraint = SkCanvas::kFast_SrcRectConstraint;
+        if (domain) {
+            srcRect = *domain;
+            constraint = SkCanvas::kStrict_SrcRectConstraint;
+        }
+
+        GrOpMemoryPool* pool = context->priv().opMemoryPool();
+        // Pass domain as srcRect if provided, but send srcQuad as a GrPerspQuad for local coords
+        return pool->allocate<TextureOp>(
+                std::move(proxy), filter, color, grDstQuad, dstQuadType, srcRect, constraint,
+                &grSrcQuad, srcQuadType, aaType, aaFlags,
+                std::move(textureColorSpaceXform));
+    }
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           const GrRenderTargetContext::TextureSetEntry set[],
                                           int cnt, GrSamplerState::Filter filter, GrAAType aaType,
+                                          SkCanvas::SrcRectConstraint constraint,
                                           const SkMatrix& viewMatrix,
                                           sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
         size_t size = sizeof(TextureOp) + sizeof(Proxy) * (cnt - 1);
         GrOpMemoryPool* pool = context->priv().opMemoryPool();
         void* mem = pool->allocate(size);
-        return std::unique_ptr<GrDrawOp>(new (mem) TextureOp(set, cnt, filter, aaType, viewMatrix,
-                                                             std::move(textureColorSpaceXform)));
+        return std::unique_ptr<GrDrawOp>(new (mem) TextureOp(
+                set, cnt, filter, aaType, constraint, viewMatrix,
+                std::move(textureColorSpaceXform)));
     }
 
     ~TextureOp() override {
@@ -172,13 +230,20 @@
     }
 #endif
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
+    GrProcessorSet::Analysis finalize(
+            const GrCaps& caps, const GrAppliedClip*, GrFSAAType, GrClampType clampType) override {
         SkASSERT(!fFinalized);
         fFinalized = true;
         for (unsigned p = 0; p < fProxyCnt; ++p) {
             fProxies[p].fProxy->addPendingRead();
             fProxies[p].fProxy->unref();
         }
+        fColorType = static_cast<unsigned>(ColorType::kNone);
+        for (int q = 0; q < fQuads.count(); ++q) {
+            const ColorDomainAndAA& info = fQuads.metadata(q);
+            auto colorType = GrQuadPerEdgeAA::MinColorType(info.fColor, clampType, caps);
+            fColorType = SkTMax(fColorType, static_cast<unsigned>(colorType));
+        }
         return GrProcessorSet::EmptySetAnalysis();
     }
 
@@ -192,33 +257,28 @@
 private:
     friend class ::GrOpMemoryPool;
 
+    // dstQuad and dstQuadType should be the geometry transformed by the view matrix.
+    // srcRect represents original src rect and will be used as the domain when constraint is strict
+    // If srcQuad is provided, it will be used for the local coords instead of srcRect, although
+    // srcRect will still specify the domain constraint if needed.
     TextureOp(sk_sp<GrTextureProxy> proxy, GrSamplerState::Filter filter, const SkPMColor4f& color,
-              const SkRect& srcRect, const SkRect& dstRect, GrAAType aaType, GrQuadAAFlags aaFlags,
-              SkCanvas::SrcRectConstraint constraint, const SkMatrix& viewMatrix,
-              sk_sp<GrColorSpaceXform> textureColorSpaceXform)
+              const GrPerspQuad& dstQuad, GrQuadType dstQuadType,
+              const SkRect& srcRect, SkCanvas::SrcRectConstraint constraint,
+              const GrPerspQuad* srcQuad, GrQuadType srcQuadType, GrAAType aaType,
+              GrQuadAAFlags aaFlags, sk_sp<GrColorSpaceXform> textureColorSpaceXform)
             : INHERITED(ClassID())
             , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
             , fFilter(static_cast<unsigned>(filter))
             , fFinalized(0) {
-        GrQuadType quadType = GrQuadTypeForTransformedRect(viewMatrix);
-        auto quad = GrPerspQuad(dstRect, viewMatrix);
-
         // Clean up disparities between the overall aa type and edge configuration and apply
         // optimizations based on the rect and matrix when appropriate
-        GrResolveAATypeForQuad(aaType, aaFlags, quad, quadType, &aaType, &aaFlags);
+        GrResolveAATypeForQuad(aaType, aaFlags, dstQuad, dstQuadType, &aaType, &aaFlags);
         fAAType = static_cast<unsigned>(aaType);
 
         // We expect our caller to have already caught this optimization.
         SkASSERT(!srcRect.contains(proxy->getWorstCaseBoundsRect()) ||
                  constraint == SkCanvas::kFast_SrcRectConstraint);
-        if (quadType == GrQuadType::kRect) {
-            // Disable filtering if possible (note AA optimizations for rects are automatically
-            // handled above in GrResolveAATypeForQuad).
-            if (this->filter() != GrSamplerState::Filter::kNearest &&
-                !GrTextureOp::GetFilterHasEffect(viewMatrix, srcRect, dstRect)) {
-                fFilter = static_cast<unsigned>(GrSamplerState::Filter::kNearest);
-            }
-        }
+
         // We may have had a strict constraint with nearest filter solely due to possible AA bloat.
         // If we don't have (or determined we don't need) coverage AA then we can skip using a
         // domain.
@@ -230,18 +290,22 @@
 
         Domain domain = constraint == SkCanvas::kStrict_SrcRectConstraint ? Domain::kYes
                                                                           : Domain::kNo;
-        fQuads.push_back(quad, quadType, {color, srcRect, domain, aaFlags});
+        // Initially, if srcQuad is provided it will always be at index 0 of fSrcQuads
+        fQuads.push_back(dstQuad, dstQuadType, {color, srcRect, srcQuad ? 0 : -1, domain, aaFlags});
+        if (srcQuad) {
+            fSrcQuads.push_back(*srcQuad, srcQuadType);
+        }
         fProxyCnt = 1;
         fProxies[0] = {proxy.release(), 1};
-        auto bounds = quad.bounds(quadType);
+        auto bounds = dstQuad.bounds(dstQuadType);
         this->setBounds(bounds, HasAABloat(aaType == GrAAType::kCoverage), IsZeroArea::kNo);
         fDomain = static_cast<unsigned>(domain);
-        fWideColor = !SkPMColor4fFitsInBytes(color);
         fCanSkipAllocatorGather =
                 static_cast<unsigned>(fProxies[0].fProxy->canSkipResourceAllocator());
     }
     TextureOp(const GrRenderTargetContext::TextureSetEntry set[], int cnt,
-              GrSamplerState::Filter filter, GrAAType aaType, const SkMatrix& viewMatrix,
+              GrSamplerState::Filter filter, GrAAType aaType,
+              SkCanvas::SrcRectConstraint constraint, const SkMatrix& viewMatrix,
               sk_sp<GrColorSpaceXform> textureColorSpaceXform)
             : INHERITED(ClassID())
             , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
@@ -252,10 +316,12 @@
         GrAAType overallAAType = GrAAType::kNone; // aa type maximally compatible with all dst rects
         bool mustFilter = false;
         fCanSkipAllocatorGather = static_cast<unsigned>(true);
-        // All dst rects are transformed by the same view matrix, so their quad types are identical
-        GrQuadType quadType = GrQuadTypeForTransformedRect(viewMatrix);
-        fQuads.reserve(cnt, quadType);
-
+        // Most dst rects are transformed by the same view matrix, so their quad types start
+        // identical, unless an entry provides a dstClip or additional transform that changes it.
+        // The quad list will automatically adapt to that.
+        fQuads.reserve(cnt, GrQuadTypeForTransformedRect(viewMatrix));
+        bool allOpaque = true;
+        Domain netDomain = Domain::kNo;
         for (unsigned p = 0; p < fProxyCnt; ++p) {
             fProxies[p].fProxy = SkRef(set[p].fProxy.get());
             fProxies[p].fQuadCnt = 1;
@@ -264,7 +330,20 @@
             if (!fProxies[p].fProxy->canSkipResourceAllocator()) {
                 fCanSkipAllocatorGather = static_cast<unsigned>(false);
             }
-            auto quad = GrPerspQuad(set[p].fDstRect, viewMatrix);
+
+            SkMatrix ctm = viewMatrix;
+            if (set[p].fPreViewMatrix) {
+                ctm.preConcat(*set[p].fPreViewMatrix);
+            }
+
+            // Use dstRect unless dstClip is provided, which is assumed to be a quad
+            auto quad = set[p].fDstClipQuad == nullptr ?
+                    GrPerspQuad::MakeFromRect(set[p].fDstRect, ctm) :
+                    GrPerspQuad::MakeFromSkQuad(set[p].fDstClipQuad, ctm);
+            GrQuadType quadType =
+                    set[p].fDstClipQuad ? GrQuadTypeForPoints(set[p].fDstClipQuad, ctm)
+                                        : GrQuadTypeForTransformedRect(ctm);
+
             bounds.joinPossiblyEmptyRect(quad.bounds(quadType));
             GrQuadAAFlags aaFlags;
             // Don't update the overall aaType, might be inappropriate for some of the quads
@@ -277,20 +356,42 @@
             }
             if (!mustFilter && this->filter() != GrSamplerState::Filter::kNearest) {
                 mustFilter = quadType != GrQuadType::kRect ||
-                             GrTextureOp::GetFilterHasEffect(viewMatrix, set[p].fSrcRect,
+                             GrTextureOp::GetFilterHasEffect(ctm, set[p].fSrcRect,
                                                              set[p].fDstRect);
             }
+            Domain domainForQuad = Domain::kNo;
+            if (constraint == SkCanvas::kStrict_SrcRectConstraint) {
+                // Check (briefly) if the strict constraint is needed for this set entry
+                if (!set[p].fSrcRect.contains(fProxies[p].fProxy->getWorstCaseBoundsRect()) &&
+                    (mustFilter || aaForQuad == GrAAType::kCoverage)) {
+                    // Can't rely on hardware clamping and the draw will access outer texels
+                    // for AA and/or bilerp
+                    netDomain = Domain::kYes;
+                    domainForQuad = Domain::kYes;
+                }
+            }
             float alpha = SkTPin(set[p].fAlpha, 0.f, 1.f);
+            allOpaque &= (1.f == alpha);
             SkPMColor4f color{alpha, alpha, alpha, alpha};
-            fQuads.push_back(quad, quadType, {color, set[p].fSrcRect, Domain::kNo, aaFlags});
+            int srcQuadIndex = -1;
+            if (set[p].fDstClipQuad) {
+                // Derive new source coordinates that match dstClip's relative locations in dstRect,
+                // but with respect to srcRect
+                SkPoint srcQuad[4];
+                GrMapRectPoints(set[p].fDstRect, set[p].fSrcRect, set[p].fDstClipQuad, srcQuad, 4);
+                fSrcQuads.push_back(GrPerspQuad::MakeFromSkQuad(srcQuad, SkMatrix::I()),
+                                    GrQuadTypeForPoints(srcQuad, SkMatrix::I()));
+                srcQuadIndex = fSrcQuads.count() - 1;
+            }
+            fQuads.push_back(quad, quadType,
+                             {color, set[p].fSrcRect, srcQuadIndex, domainForQuad, aaFlags});
         }
         fAAType = static_cast<unsigned>(overallAAType);
         if (!mustFilter) {
             fFilter = static_cast<unsigned>(GrSamplerState::Filter::kNearest);
         }
         this->setBounds(bounds, HasAABloat(this->aaType() == GrAAType::kCoverage), IsZeroArea::kNo);
-        fDomain = static_cast<unsigned>(false);
-        fWideColor = static_cast<unsigned>(false);
+        fDomain = static_cast<unsigned>(netDomain);
     }
 
     void tess(void* v, const VertexSpec& spec, const GrTextureProxy* proxy, int start,
@@ -312,7 +413,9 @@
             const GrPerspQuad& device = fQuads[i];
             const ColorDomainAndAA& info = fQuads.metadata(i);
 
-            GrPerspQuad srcQuad = compute_src_quad(origin, info.fSrcRect, iw, ih, h);
+            GrPerspQuad srcQuad = info.fSrcQuadIndex >= 0 ?
+                    compute_src_quad(origin, fSrcQuads[info.fSrcQuadIndex], iw, ih, h) :
+                    compute_src_quad_from_rect(origin, info.fSrcRect, iw, ih, h);
             SkRect domain =
                     compute_domain(info.domain(), this->filter(), origin, info.fSrcRect, iw, ih, h);
             v = GrQuadPerEdgeAA::Tessellate(v, spec, device, info.fColor, srcQuad, domain,
@@ -323,8 +426,9 @@
     void onPrepareDraws(Target* target) override {
         TRACE_EVENT0("skia", TRACE_FUNC);
         GrQuadType quadType = GrQuadType::kRect;
+        GrQuadType srcQuadType = GrQuadType::kRect;
         Domain domain = Domain::kNo;
-        bool wideColor = false;
+        ColorType colorType = ColorType::kNone;
         int numProxies = 0;
         int numTotalQuads = 0;
         auto textureType = fProxies[0].fProxy->textureType();
@@ -334,15 +438,24 @@
             if (op.fQuads.quadType() > quadType) {
                 quadType = op.fQuads.quadType();
             }
+            if (op.fSrcQuads.quadType() > srcQuadType) {
+                // Should only become more general if there are quads to use instead of fSrcRect
+                SkASSERT(op.fSrcQuads.count() > 0);
+                srcQuadType = op.fSrcQuads.quadType();
+            }
             if (op.fDomain) {
                 domain = Domain::kYes;
             }
-            wideColor |= op.fWideColor;
+            colorType = SkTMax(colorType, static_cast<ColorType>(op.fColorType));
             numProxies += op.fProxyCnt;
             for (unsigned p = 0; p < op.fProxyCnt; ++p) {
                 numTotalQuads += op.fProxies[p].fQuadCnt;
                 auto* proxy = op.fProxies[p].fProxy;
-                if (!proxy->instantiate(target->resourceProvider())) {
+                if (target->resourceProvider()->explicitlyAllocateGPUResources()) {
+                    if (!proxy->isInstantiated()) {
+                        return;
+                    }
+                } else if (!proxy->instantiate(target->resourceProvider())) {
                     return;
                 }
                 SkASSERT(proxy->config() == config);
@@ -354,8 +467,7 @@
             }
         }
 
-        VertexSpec vertexSpec(quadType, wideColor ? ColorType::kHalf : ColorType::kByte,
-                              GrQuadType::kRect, /* hasLocal */ true, domain, aaType,
+        VertexSpec vertexSpec(quadType, colorType, srcQuadType, /* hasLocal */ true, domain, aaType,
                               /* alpha as coverage */ true);
 
         GrSamplerState samplerState = GrSamplerState(GrSamplerState::WrapMode::kClamp,
@@ -369,28 +481,17 @@
                 textureType, config, samplerState, extraSamplerKey,
                 std::move(fTextureColorSpaceXform));
 
-        GrPipeline::InitArgs args;
-        args.fCaps = &target->caps();
-        args.fResourceProvider = target->resourceProvider();
-        args.fFlags = 0;
-        if (aaType == GrAAType::kMSAA) {
-            args.fFlags |= GrPipeline::kHWAntialias_Flag;
-        }
-
-        auto clip = target->detachAppliedClip();
         // We'll use a dynamic state array for the GP textures when there are multiple ops.
         // Otherwise, we use fixed dynamic state to specify the single op's proxy.
         GrPipeline::DynamicStateArrays* dynamicStateArrays = nullptr;
         GrPipeline::FixedDynamicState* fixedDynamicState;
         if (numProxies > 1) {
             dynamicStateArrays = target->allocDynamicStateArrays(numProxies, 1, false);
-            fixedDynamicState = target->allocFixedDynamicState(clip.scissorState().rect(), 0);
+            fixedDynamicState = target->makeFixedDynamicState(0);
         } else {
-            fixedDynamicState = target->allocFixedDynamicState(clip.scissorState().rect(), 1);
+            fixedDynamicState = target->makeFixedDynamicState(1);
             fixedDynamicState->fPrimitiveProcessorTextures[0] = fProxies[0].fProxy;
         }
-        const auto* pipeline =
-                target->allocPipeline(args, GrProcessorSet::MakeEmptySet(), std::move(clip));
 
         size_t vertexSize = gp->vertexStride();
 
@@ -441,8 +542,16 @@
         }
         SkASSERT(!numQuadVerticesLeft);
         SkASSERT(!numAllocatedVertices);
-        target->draw(std::move(gp), pipeline, fixedDynamicState, dynamicStateArrays, meshes,
-                     numProxies);
+        target->recordDraw(
+                std::move(gp), meshes, numProxies, fixedDynamicState, dynamicStateArrays);
+    }
+
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        auto pipelineFlags = (GrAAType::kMSAA == this->aaType())
+                ? GrPipeline::InputFlags::kHWAntialias
+                : GrPipeline::InputFlags::kNone;
+        flushState->executeDrawsAndUploadsForMeshDrawOp(
+                this, chainBounds, GrProcessorSet::MakeEmptySet(), pipelineFlags);
     }
 
     CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
@@ -474,13 +583,45 @@
             }
             return CombineResult::kCannotCombine;
         }
-        fProxies[0].fQuadCnt += that->fQuads.count();
-        fQuads.concat(that->fQuads);
+
         fDomain |= that->fDomain;
-        fWideColor |= that->fWideColor;
+        fColorType = SkTMax(fColorType, that->fColorType);
         if (upgradeToCoverageAAOnMerge) {
             fAAType = static_cast<unsigned>(GrAAType::kCoverage);
         }
+
+        // Concatenate quad lists together, updating the fSrcQuadIndex in the appended quads
+        // to account for the new starting index in fSrcQuads
+        int srcQuadOffset = fSrcQuads.count();
+        int oldQuadCount = fQuads.count();
+
+        fSrcQuads.concat(that->fSrcQuads);
+        fQuads.concat(that->fQuads);
+        fProxies[0].fQuadCnt += that->fQuads.count();
+
+        if (that->fSrcQuads.count() > 0) {
+            // Some of the concatenated quads pointed to fSrcQuads, so adjust the indices for the
+            // newly appended quads
+            for (int i = oldQuadCount; i < fQuads.count(); ++i) {
+                if (fQuads.metadata(i).fSrcQuadIndex >= 0) {
+                    fQuads.metadata(i).fSrcQuadIndex += srcQuadOffset;
+                }
+            }
+        }
+
+        // Confirm all tracked state makes sense when in debug builds
+#ifdef SK_DEBUG
+        SkASSERT(fSrcQuads.count() <= fQuads.count());
+        for (int i = 0; i < fQuads.count(); ++i) {
+            int srcIndex = fQuads.metadata(i).fSrcQuadIndex;
+            if (srcIndex >= 0) {
+                // Make sure it points to a valid index, in the right region of the list
+                SkASSERT(srcIndex < fSrcQuads.count());
+                SkASSERT((i < oldQuadCount && srcIndex < srcQuadOffset) ||
+                         (i >= oldQuadCount && srcIndex >= srcQuadOffset));
+            }
+        }
+#endif
         return CombineResult::kMerged;
     }
 
@@ -491,10 +632,11 @@
         // Special constructor to convert enums into the packed bits, which should not delete
         // the implicit move constructor (but it does require us to declare an empty ctor for
         // use with the GrTQuadList).
-        ColorDomainAndAA(const SkPMColor4f& color, const SkRect& srcRect,
+        ColorDomainAndAA(const SkPMColor4f& color, const SkRect& srcRect, int srcQuadIndex,
                          Domain hasDomain, GrQuadAAFlags aaFlags)
                 : fColor(color)
                 , fSrcRect(srcRect)
+                , fSrcQuadIndex(srcQuadIndex)
                 , fHasDomain(static_cast<unsigned>(hasDomain))
                 , fAAFlags(static_cast<unsigned>(aaFlags)) {
             SkASSERT(fHasDomain == static_cast<unsigned>(hasDomain));
@@ -503,7 +645,10 @@
         ColorDomainAndAA() = default;
 
         SkPMColor4f fColor;
+        // Even if fSrcQuadIndex provides source coords, use fSrcRect for domain constraint
         SkRect fSrcRect;
+        // If >= 0, use to access fSrcQuads instead of fSrcRect for the source coordinates
+        int fSrcQuadIndex;
         unsigned fHasDomain : 1;
         unsigned fAAFlags : 4;
 
@@ -515,15 +660,19 @@
         int fQuadCnt;
     };
     GrTQuadList<ColorDomainAndAA> fQuads;
+    // The majority of texture ops will not track a complete src quad so this is indexed separately
+    // and may be of different size to fQuads.
+    GrQuadList fSrcQuads;
     sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
     unsigned fFilter : 2;
     unsigned fAAType : 2;
     unsigned fDomain : 1;
-    unsigned fWideColor : 1;
+    unsigned fColorType : 2;
+    GR_STATIC_ASSERT(GrQuadPerEdgeAA::kColorTypeCount <= 4);
     // Used to track whether fProxy is ref'ed or has a pending IO after finalize() is called.
     unsigned fFinalized : 1;
     unsigned fCanSkipAllocatorGather : 1;
-    unsigned fProxyCnt : 32 - 8;
+    unsigned fProxyCnt : 32 - 9;
     Proxy fProxies[1];
 
     static_assert(kGrQuadTypeCount <= 4, "GrQuadType does not fit in 2 bits");
@@ -535,7 +684,7 @@
 
 namespace GrTextureOp {
 
-std::unique_ptr<GrDrawOp> Make(GrContext* context,
+std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                sk_sp<GrTextureProxy> proxy,
                                GrSamplerState::Filter filter,
                                const SkPMColor4f& color,
@@ -550,14 +699,30 @@
                            aaFlags, constraint, viewMatrix, std::move(textureColorSpaceXform));
 }
 
-std::unique_ptr<GrDrawOp> Make(GrContext* context,
-                               const GrRenderTargetContext::TextureSetEntry set[],
-                               int cnt,
-                               GrSamplerState::Filter filter,
-                               GrAAType aaType,
-                               const SkMatrix& viewMatrix,
-                               sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
-    return TextureOp::Make(context, set, cnt, filter, aaType, viewMatrix,
+std::unique_ptr<GrDrawOp> MakeQuad(GrRecordingContext* context,
+                                  sk_sp<GrTextureProxy> proxy,
+                                  GrSamplerState::Filter filter,
+                                  const SkPMColor4f& color,
+                                  const SkPoint srcQuad[4],
+                                  const SkPoint dstQuad[4],
+                                  GrAAType aaType,
+                                  GrQuadAAFlags aaFlags,
+                                  const SkRect* domain,
+                                  const SkMatrix& viewMatrix,
+                                  sk_sp<GrColorSpaceXform> textureXform) {
+    return TextureOp::Make(context, std::move(proxy), filter, color, srcQuad, dstQuad, aaType,
+                           aaFlags, domain, viewMatrix, std::move(textureXform));
+}
+
+std::unique_ptr<GrDrawOp> MakeSet(GrRecordingContext* context,
+                                  const GrRenderTargetContext::TextureSetEntry set[],
+                                  int cnt,
+                                  GrSamplerState::Filter filter,
+                                  GrAAType aaType,
+                                  SkCanvas::SrcRectConstraint constraint,
+                                  const SkMatrix& viewMatrix,
+                                  sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
+    return TextureOp::Make(context, set, cnt, filter, aaType, constraint, viewMatrix,
                            std::move(textureColorSpaceXform));
 }
 
@@ -587,9 +752,9 @@
 }  // namespace GrTextureOp
 
 #if GR_TEST_UTILS
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 
 GR_DRAW_OP_TEST_DEFINE(TextureOp) {
     GrSurfaceDesc desc;
diff --git a/src/gpu/ops/GrTextureOp.h b/src/gpu/ops/GrTextureOp.h
index 1942344..c59f706 100644
--- a/src/gpu/ops/GrTextureOp.h
+++ b/src/gpu/ops/GrTextureOp.h
@@ -27,7 +27,7 @@
  * space. 'viewMatrix' must be affine. If GrAAType is kCoverage then AA is applied to the edges
  * indicated by GrQuadAAFlags. Otherwise, GrQuadAAFlags is ignored.
  */
-std::unique_ptr<GrDrawOp> Make(GrContext*,
+std::unique_ptr<GrDrawOp> Make(GrRecordingContext*,
                                sk_sp<GrTextureProxy>,
                                GrSamplerState::Filter,
                                const SkPMColor4f&,
@@ -39,13 +39,29 @@
                                const SkMatrix& viewMatrix,
                                sk_sp<GrColorSpaceXform> textureXform);
 
-std::unique_ptr<GrDrawOp> Make(GrContext*,
-                               const GrRenderTargetContext::TextureSetEntry[],
-                               int cnt,
-                               GrSamplerState::Filter,
-                               GrAAType,
-                               const SkMatrix& viewMatrix,
-                               sk_sp<GrColorSpaceXform> textureXform);
+// Generalizes the above subrect drawing operation to draw a subquad of an image, where srcQuad
+// and dstQuad correspond to srcRect and dstRect. If domain is not null, this behaves as if it
+// had a strict constraint relying on the given domain.
+std::unique_ptr<GrDrawOp> MakeQuad(GrRecordingContext* context,
+                                  sk_sp<GrTextureProxy>,
+                                  GrSamplerState::Filter,
+                                  const SkPMColor4f&,
+                                  const SkPoint srcQuad[4],
+                                  const SkPoint dstQuad[4],
+                                  GrAAType,
+                                  GrQuadAAFlags,
+                                  const SkRect* domain,
+                                  const SkMatrix& viewMatrix,
+                                  sk_sp<GrColorSpaceXform> textureXform);
+
+std::unique_ptr<GrDrawOp> MakeSet(GrRecordingContext*,
+                                  const GrRenderTargetContext::TextureSetEntry[],
+                                  int cnt,
+                                  GrSamplerState::Filter,
+                                  GrAAType,
+                                  SkCanvas::SrcRectConstraint,
+                                  const SkMatrix& viewMatrix,
+                                  sk_sp<GrColorSpaceXform> textureXform);
 
 /**
  * Returns true if bilerp texture filtering matters when rendering the src rect
diff --git a/src/gpu/text/GrStrikeCache.cpp b/src/gpu/text/GrStrikeCache.cpp
index 8edc977..293ba29 100644
--- a/src/gpu/text/GrStrikeCache.cpp
+++ b/src/gpu/text/GrStrikeCache.cpp
@@ -17,7 +17,7 @@
 GrStrikeCache::GrStrikeCache(const GrCaps* caps, size_t maxTextureBytes)
         : fPreserveStrike(nullptr)
         , f565Masks(SkMasks::CreateMasks({0xF800, 0x07E0, 0x001F, 0},
-                    GrMaskFormatBytesPerPixel(kA565_GrMaskFormat) * 8)) { }
+                    GrMaskFormatBytesPerPixel(kA565_GrMaskFormat))) { }
 
 GrStrikeCache::~GrStrikeCache() {
     StrikeHash::Iter iter(&fCache);
diff --git a/src/gpu/text/GrStrikeCache.h b/src/gpu/text/GrStrikeCache.h
index 0874ffc..15fc9c3 100644
--- a/src/gpu/text/GrStrikeCache.h
+++ b/src/gpu/text/GrStrikeCache.h
@@ -112,10 +112,10 @@
     // another client of the cache may cause the strike to be purged while it is still reffed.
     // Therefore, the caller must check GrTextStrike::isAbandoned() if there are other
     // interactions with the cache since the strike was received.
-    sk_sp<GrTextStrike> getStrike(const SkStrike* cache) {
-        sk_sp<GrTextStrike> strike = sk_ref_sp(fCache.find(cache->getDescriptor()));
+    sk_sp<GrTextStrike> getStrike(const SkDescriptor& desc) {
+        sk_sp<GrTextStrike> strike = sk_ref_sp(fCache.find(desc));
         if (!strike) {
-            strike = this->generateStrike(cache);
+            strike = this->generateStrike(desc);
         }
         return strike;
     }
@@ -127,9 +127,9 @@
     static void HandleEviction(GrDrawOpAtlas::AtlasID, void*);
 
 private:
-    sk_sp<GrTextStrike> generateStrike(const SkStrike* cache) {
+    sk_sp<GrTextStrike> generateStrike(const SkDescriptor& desc) {
         // 'fCache' get the construction ref
-        sk_sp<GrTextStrike> strike = sk_ref_sp(new GrTextStrike(cache->getDescriptor()));
+        sk_sp<GrTextStrike> strike = sk_ref_sp(new GrTextStrike(desc));
         fCache.add(strike.get());
         return strike;
     }
diff --git a/src/gpu/text/GrTextBlob.cpp b/src/gpu/text/GrTextBlob.cpp
index 0f5b7ea..0ab8f6d 100644
--- a/src/gpu/text/GrTextBlob.cpp
+++ b/src/gpu/text/GrTextBlob.cpp
@@ -23,7 +23,10 @@
     return ((s + (N-1)) / N) * N;
 }
 
-sk_sp<GrTextBlob> GrTextBlob::Make(int glyphCount, int runCount, GrColor color) {
+sk_sp<GrTextBlob> GrTextBlob::Make(int glyphCount,
+                                   int runCount,
+                                   GrColor color,
+                                   GrStrikeCache* strikeCache) {
     // We allocate size for the GrTextBlob itself, plus size for the vertices array,
     // and size for the glyphIds array.
     size_t verticesCount = glyphCount * kVerticesPerGlyph * kMaxVASize;
@@ -40,7 +43,7 @@
         sk_bzero(allocation, size);
     }
 
-    sk_sp<GrTextBlob> blob{new (allocation) GrTextBlob{}};
+    sk_sp<GrTextBlob> blob{new (allocation) GrTextBlob{strikeCache}};
     blob->fSize = size;
 
     // setup offsets for vertices / glyphs
@@ -56,18 +59,15 @@
     return blob;
 }
 
-void GrTextBlob::Run::setupFont(const SkPaint& skPaint,
-                                const SkFont& skFont,
-                                const SkDescriptor& cacheDescriptor) {
-    fTypeface = skFont.refTypefaceOrDefault();
-    SkScalerContextEffects effects{skPaint};
-    fPathEffect = sk_ref_sp(effects.fPathEffect);
-    fMaskFilter = sk_ref_sp(effects.fMaskFilter);
+void GrTextBlob::Run::setupFont(const SkStrikeSpec& strikeSpec) {
+    fTypeface = sk_ref_sp(&strikeSpec.typeface());
+    fPathEffect = sk_ref_sp(strikeSpec.effects().fPathEffect);
+    fMaskFilter = sk_ref_sp(strikeSpec.effects().fMaskFilter);
     // if we have an override descriptor for the run, then we should use that
     SkAutoDescriptor* desc =
             fARGBFallbackDescriptor.get() ? fARGBFallbackDescriptor.get() : &fDescriptor;
     // Set up the descriptor for possible cache lookups during regen.
-    desc->reset(cacheDescriptor);
+    desc->reset(strikeSpec.desc());
 }
 
 void GrTextBlob::Run::appendPathGlyph(const SkPath& path, SkPoint position,
diff --git a/src/gpu/text/GrTextBlob.h b/src/gpu/text/GrTextBlob.h
index 2f37dab..f0862b5 100644
--- a/src/gpu/text/GrTextBlob.h
+++ b/src/gpu/text/GrTextBlob.h
@@ -48,15 +48,14 @@
  *
  * *WARNING* If you add new fields to this struct, then you may need to to update AssertEqual
  */
-class GrTextBlob : public SkNVRefCnt<GrTextBlob> {
+class GrTextBlob : public SkNVRefCnt<GrTextBlob>, public SkGlyphRunPainterInterface {
     struct Run;
 public:
     SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrTextBlob);
 
     class VertexRegenerator;
 
-    void generateFromGlyphRunList(GrStrikeCache* glyphCache,
-                                  const GrShaderCaps& shaderCaps,
+    void generateFromGlyphRunList(const GrShaderCaps& shaderCaps,
                                   const GrTextContext::Options& options,
                                   const SkPaint& paint,
                                   SkScalerContextFlags scalerContextFlags,
@@ -65,7 +64,11 @@
                                   const SkGlyphRunList& glyphRunList,
                                   SkGlyphRunListPainter* glyphPainter);
 
-    static sk_sp<GrTextBlob> Make(int glyphCount, int runCount, GrColor color);
+    static sk_sp<GrTextBlob> Make(
+            int glyphCount,
+            int runCount,
+            GrColor color,
+            GrStrikeCache* strikeCache);
 
     /**
      * We currently force regeneration of a blob if old or new matrix differ in having perspective.
@@ -144,13 +147,13 @@
         }
 
         fRunCount++;
-        return &fRuns[fRunCount - 1];
+        return this->currentRun();
     }
 
-    void setMinAndMaxScale(SkScalar scaledMax, SkScalar scaledMin) {
+    void setMinAndMaxScale(SkScalar scaledMin, SkScalar scaledMax) {
         // we init fMaxMinScale and fMinMaxScale in the constructor
-        fMaxMinScale = SkMaxScalar(scaledMax, fMaxMinScale);
-        fMinMaxScale = SkMinScalar(scaledMin, fMinMaxScale);
+        fMaxMinScale = SkMaxScalar(scaledMin, fMaxMinScale);
+        fMinMaxScale = SkMinScalar(scaledMax, fMinMaxScale);
     }
 
     static size_t GetVertexStride(GrMaskFormat maskFormat, bool hasWCoord) {
@@ -235,7 +238,7 @@
 
     size_t size() const { return fSize; }
 
-    ~GrTextBlob() {
+    ~GrTextBlob() override {
         for (int i = 0; i < fRunCountLimit; i++) {
             fRuns[i].~Run();
         }
@@ -250,10 +253,7 @@
                                           GrTextTarget*);
 
 private:
-    GrTextBlob()
-        : fMaxMinScale(-SK_ScalarMax)
-        , fMinMaxScale(SK_ScalarMax)
-        , fTextType(0) {}
+    GrTextBlob(GrStrikeCache* strikeCache) : fStrikeCache{strikeCache} { }
 
     // This function will only be called when we are generating a blob from scratch. We record the
     // initial view matrix and initial offsets(x,y), because we record vertex bounds relative to
@@ -445,9 +445,7 @@
                                     SkPoint origin,
                                     SkScalar textScale);
 
-        void setupFont(const SkPaint& skPaint,
-                       const SkFont& skFont,
-                       const SkDescriptor& skCache);
+        void setupFont(const SkStrikeSpec& strikeSpec);
 
         void setRunFontAntiAlias(bool aa) {
             fAntiAlias = aa;
@@ -513,12 +511,42 @@
         GrColor fColor;
     };  // Run
 
-    inline std::unique_ptr<GrAtlasTextOp> makeOp(
+    std::unique_ptr<GrAtlasTextOp> makeOp(
             const SubRun& info, int glyphCount, uint16_t run, uint16_t subRun,
             const SkMatrix& viewMatrix, SkScalar x, SkScalar y, const SkIRect& clipRect,
             const SkPaint& paint, const SkPMColor4f& filteredColor, const SkSurfaceProps&,
             const GrDistanceFieldAdjustTable*, GrTextTarget*);
 
+    // currentRun, startRun, and the process* calls are all used by the SkGlyphRunPainter, and
+    // live in SkGlyphRunPainter.cpp file.
+    Run* currentRun();
+
+    void startRun(const SkGlyphRun& glyphRun, bool useSDFT) override;
+
+    void processDeviceMasks(SkSpan<const SkGlyphPos> masks,
+                            SkStrikeInterface* strike) override;
+
+    void processSourcePaths(SkSpan<const SkGlyphPos> paths,
+                            SkStrikeInterface* strike, SkScalar cacheToSourceScale) override;
+
+    void processDevicePaths(SkSpan<const SkGlyphPos> paths) override;
+
+    void processSourceSDFT(SkSpan<const SkGlyphPos> masks,
+                           SkStrikeInterface* strike,
+                           const SkFont& runFont,
+                           SkScalar cacheToSourceScale,
+                           SkScalar minScale,
+                           SkScalar maxScale,
+                           bool hasWCoord) override;
+
+    void processSourceFallback(SkSpan<const SkGlyphPos> masks,
+                               SkStrikeInterface* strike,
+                               SkScalar cacheToSourceScale,
+                               bool hasW) override;
+
+    void processDeviceFallback(SkSpan<const SkGlyphPos> masks,
+                               SkStrikeInterface* strike) override;
+
     struct StrokeInfo {
         SkScalar fFrameWidth;
         SkScalar fMiterLimit;
@@ -534,6 +562,10 @@
     char* fVertices;
     GrGlyph** fGlyphs;
     Run* fRuns;
+
+    // Lifetime: The GrStrikeCache is owned by and has the same lifetime as the GrRecordingContext.
+    // The GrRecordingContext also owns the GrTextBlob cache which owns this GrTextBlob.
+    GrStrikeCache* const fStrikeCache;
     SkMaskFilterBase::BlurRec fBlurRec;
     StrokeInfo fStrokeInfo;
     Key fKey;
@@ -547,11 +579,11 @@
     // We can reuse distance field text, but only if the new viewmatrix would not result in
     // a mip change.  Because there can be multiple runs in a blob, we track the overall
     // maximum minimum scale, and minimum maximum scale, we can support before we need to regen
-    SkScalar fMaxMinScale;
-    SkScalar fMinMaxScale;
+    SkScalar fMaxMinScale{-SK_ScalarMax};
+    SkScalar fMinMaxScale{SK_ScalarMax};
     int fRunCount{0};
     int fRunCountLimit;
-    uint8_t fTextType;
+    uint8_t fTextType{0};
 };
 
 /**
diff --git a/src/gpu/text/GrTextBlobCache.h b/src/gpu/text/GrTextBlobCache.h
index 4e9b5ce..03d48ad 100644
--- a/src/gpu/text/GrTextBlobCache.h
+++ b/src/gpu/text/GrTextBlobCache.h
@@ -33,16 +33,20 @@
     }
     ~GrTextBlobCache();
 
-    sk_sp<GrTextBlob> makeBlob(const SkGlyphRunList& glyphRunList, GrColor color) {
-        return GrTextBlob::Make(glyphRunList.totalGlyphCount(), glyphRunList.size(), color);
+    sk_sp<GrTextBlob> makeBlob(const SkGlyphRunList& glyphRunList,
+                               GrColor color,
+                               GrStrikeCache* strikeCache) {
+        return GrTextBlob::Make(
+                glyphRunList.totalGlyphCount(), glyphRunList.size(), color, strikeCache);
     }
 
     sk_sp<GrTextBlob> makeCachedBlob(const SkGlyphRunList& glyphRunList,
                                      const GrTextBlob::Key& key,
                                      const SkMaskFilterBase::BlurRec& blurRec,
                                      const SkPaint& paint,
-                                     GrColor color) {
-        sk_sp<GrTextBlob> cacheBlob(makeBlob(glyphRunList, color));
+                                     GrColor color,
+                                     GrStrikeCache* strikeCache) {
+        sk_sp<GrTextBlob> cacheBlob(makeBlob(glyphRunList, color, strikeCache));
         cacheBlob->setupKey(key, blurRec, paint);
         this->add(cacheBlob);
         glyphRunList.temporaryShuntBlobNotifyAddedToCache(fUniqueID);
diff --git a/src/gpu/text/GrTextBlobVertexRegenerator.cpp b/src/gpu/text/GrTextBlobVertexRegenerator.cpp
index 77d2b8d..1b2b117 100644
--- a/src/gpu/text/GrTextBlobVertexRegenerator.cpp
+++ b/src/gpu/text/GrTextBlobVertexRegenerator.cpp
@@ -176,7 +176,7 @@
         }
 
         if (regenGlyphs) {
-            strike = fGlyphCache->getStrike(fLazyCache->get());
+            strike = fGlyphCache->getStrike((*fLazyCache)->getDescriptor());
         } else {
             strike = fSubRun->refStrike();
         }
diff --git a/src/gpu/text/GrTextContext.cpp b/src/gpu/text/GrTextContext.cpp
index bf6b23980..5446edd 100644
--- a/src/gpu/text/GrTextContext.cpp
+++ b/src/gpu/text/GrTextContext.cpp
@@ -129,14 +129,7 @@
     return true;
 }
 
-void GrTextContext::InitDistanceFieldPaint(const SkScalar textSize,
-                                           const SkMatrix& viewMatrix,
-                                           const Options& options,
-                                           GrTextBlob* blob,
-                                           SkPaint* skPaint,
-                                           SkFont* skFont,
-                                           SkScalar* textRatio,
-                                           SkScalerContextFlags* flags) {
+SkScalar scaled_text_size(const SkScalar textSize, const SkMatrix& viewMatrix) {
     SkScalar scaledTextSize = textSize;
 
     if (viewMatrix.hasPerspective()) {
@@ -153,6 +146,45 @@
         }
     }
 
+    return scaledTextSize;
+}
+
+SkFont GrTextContext::InitDistanceFieldFont(const SkFont& font,
+                                            const SkMatrix& viewMatrix,
+                                            const Options& options,
+                                            SkScalar* textRatio) {
+    SkScalar textSize = font.getSize();
+    SkScalar scaledTextSize = scaled_text_size(textSize, viewMatrix);
+
+    SkFont dfFont{font};
+
+    if (scaledTextSize <= kSmallDFFontLimit) {
+        *textRatio = textSize / kSmallDFFontSize;
+        dfFont.setSize(SkIntToScalar(kSmallDFFontSize));
+    } else if (scaledTextSize <= kMediumDFFontLimit) {
+        *textRatio = textSize / kMediumDFFontSize;
+        dfFont.setSize(SkIntToScalar(kMediumDFFontSize));
+    } else {
+        *textRatio = textSize / kLargeDFFontSize;
+        dfFont.setSize(SkIntToScalar(kLargeDFFontSize));
+    }
+
+    dfFont.setEdging(SkFont::Edging::kAntiAlias);
+    dfFont.setForceAutoHinting(false);
+    dfFont.setHinting(kNormal_SkFontHinting);
+
+    // The sub-pixel position will always happen when transforming to the screen.
+    dfFont.setSubpixel(false);
+    return dfFont;
+}
+
+std::pair<SkScalar, SkScalar> GrTextContext::InitDistanceFieldMinMaxScale(
+        SkScalar textSize,
+        const SkMatrix& viewMatrix,
+        const GrTextContext::Options& options) {
+
+    SkScalar scaledTextSize = scaled_text_size(textSize, viewMatrix);
+
     // We have three sizes of distance field text, and within each size 'bucket' there is a floor
     // and ceiling.  A scale outside of this range would require regenerating the distance fields
     SkScalar dfMaskScaleFloor;
@@ -160,18 +192,12 @@
     if (scaledTextSize <= kSmallDFFontLimit) {
         dfMaskScaleFloor = options.fMinDistanceFieldFontSize;
         dfMaskScaleCeil = kSmallDFFontLimit;
-        *textRatio = textSize / kSmallDFFontSize;
-        skFont->setSize(SkIntToScalar(kSmallDFFontSize));
     } else if (scaledTextSize <= kMediumDFFontLimit) {
         dfMaskScaleFloor = kSmallDFFontLimit;
         dfMaskScaleCeil = kMediumDFFontLimit;
-        *textRatio = textSize / kMediumDFFontSize;
-        skFont->setSize(SkIntToScalar(kMediumDFFontSize));
     } else {
         dfMaskScaleFloor = kMediumDFFontLimit;
         dfMaskScaleCeil = options.fMaxDistanceFieldFontSize;
-        *textRatio = textSize / kLargeDFFontSize;
-        skFont->setSize(SkIntToScalar(kLargeDFFontSize));
     }
 
     // Because there can be multiple runs in the blob, we want the overall maxMinScale, and
@@ -182,21 +208,14 @@
     // against these values to decide if we can reuse or not(ie, will a given scale change our mip
     // level)
     SkASSERT(dfMaskScaleFloor <= scaledTextSize && scaledTextSize <= dfMaskScaleCeil);
-    if (blob) {
-        blob->setMinAndMaxScale(dfMaskScaleFloor / scaledTextSize,
-                                dfMaskScaleCeil / scaledTextSize);
-    }
 
-    skFont->setEdging(SkFont::Edging::kAntiAlias);
-    skFont->setForceAutoHinting(false);
-    skFont->setHinting(kNormal_SkFontHinting);
-    skFont->setSubpixel(true);
+    return std::make_pair(dfMaskScaleFloor / scaledTextSize, dfMaskScaleCeil / scaledTextSize);
+}
 
-    skPaint->setMaskFilter(GrSDFMaskFilter::Make());
-
-    // We apply the fake-gamma by altering the distance in the shader, so we ignore the
-    // passed-in scaler context flags. (It's only used when we fall-back to bitmap text).
-    *flags = SkScalerContextFlags::kNone;
+SkPaint GrTextContext::InitDistanceFieldPaint(const SkPaint& paint) {
+    SkPaint dfPaint{paint};
+    dfPaint.setMaskFilter(GrSDFMaskFilter::Make());
+    return dfPaint;
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/gpu/text/GrTextContext.h b/src/gpu/text/GrTextContext.h
index ff9043c..d4dfd77 100644
--- a/src/gpu/text/GrTextContext.h
+++ b/src/gpu/text/GrTextContext.h
@@ -18,6 +18,7 @@
 #endif
 
 class GrDrawOp;
+class GrRecordingContext;
 class GrTextBlobCache;
 class SkGlyph;
 class GrTextBlob;
@@ -44,10 +45,10 @@
 
     static std::unique_ptr<GrTextContext> Make(const Options& options);
 
-    void drawGlyphRunList(GrContext*, GrTextTarget*, const GrClip&,
+    void drawGlyphRunList(GrRecordingContext*, GrTextTarget*, const GrClip&,
                           const SkMatrix& viewMatrix, const SkSurfaceProps&, const SkGlyphRunList&);
 
-    std::unique_ptr<GrDrawOp> createOp_TestingOnly(GrContext*,
+    std::unique_ptr<GrDrawOp> createOp_TestingOnly(GrRecordingContext*,
                                                    GrTextContext*,
                                                    GrRenderTargetContext*,
                                                    const SkPaint&, const SkFont&,
@@ -61,14 +62,17 @@
                                         const SkSurfaceProps& props,
                                         bool contextSupportsDistanceFieldText,
                                         const Options& options);
-    static void InitDistanceFieldPaint(SkScalar textSize,
-                                       const SkMatrix& viewMatrix,
-                                       const Options& options,
-                                       GrTextBlob* blob,
-                                       SkPaint* skPaint,
-                                       SkFont* skFont,
-                                       SkScalar* textRatio,
-                                       SkScalerContextFlags* flags);
+
+    static SkFont InitDistanceFieldFont(const SkFont& font,
+                                        const SkMatrix& viewMatrix,
+                                        const Options& options,
+                                        SkScalar* textRatio);
+
+    static SkPaint InitDistanceFieldPaint(const SkPaint& paint);
+
+    static std::pair<SkScalar, SkScalar> InitDistanceFieldMinMaxScale(SkScalar textSize,
+                                                                      const SkMatrix& viewMatrix,
+                                                                      const Options& options);
 
 private:
     GrTextContext(const Options& options);
diff --git a/src/gpu/text/GrTextTarget.h b/src/gpu/text/GrTextTarget.h
index 1742f59..9ad9a7c 100644
--- a/src/gpu/text/GrTextTarget.h
+++ b/src/gpu/text/GrTextTarget.h
@@ -14,6 +14,7 @@
 class GrAtlasTextOp;
 class GrClip;
 class GrPaint;
+class GrRecordingContext;
 class GrShape;
 class SkGlyphRunListPainter;
 class SkMatrix;
@@ -37,7 +38,7 @@
     virtual void makeGrPaint(GrMaskFormat, const SkPaint&, const SkMatrix& viewMatrix,
                              GrPaint*) = 0;
 
-    virtual GrContext* getContext() = 0;
+    virtual GrRecordingContext* getContext() = 0;
 
     virtual SkGlyphRunListPainter* glyphPainter() = 0;
 
diff --git a/src/gpu/vk/GrVkBuffer.cpp b/src/gpu/vk/GrVkBuffer.cpp
index 2ce5d8b..2bf3320 100644
--- a/src/gpu/vk/GrVkBuffer.cpp
+++ b/src/gpu/vk/GrVkBuffer.cpp
@@ -99,7 +99,8 @@
     };
 
     // TODO: restrict to area of buffer we're interested in
-    gpu->addBufferMemoryBarrier(srcStageMask, dstStageMask, byRegion, &bufferMemoryBarrier);
+    gpu->addBufferMemoryBarrier(this->resource(), srcStageMask, dstStageMask, byRegion,
+                                &bufferMemoryBarrier);
 }
 
 void GrVkBuffer::Resource::freeGPUData(GrVkGpu* gpu) const {
@@ -183,6 +184,33 @@
     VALIDATE();
 }
 
+void GrVkBuffer::copyCpuDataToGpuBuffer(GrVkGpu* gpu, const void* src, size_t size) {
+    SkASSERT(src);
+    // The vulkan api restricts the use of vkCmdUpdateBuffer to updates that are less than or equal
+    // to 65536 bytes and a size the is 4 byte aligned.
+    if ((size <= 65536) && (0 == (size & 0x3)) && !gpu->vkCaps().avoidUpdateBuffers()) {
+        gpu->updateBuffer(this, src, this->offset(), size);
+    } else {
+        sk_sp<GrVkTransferBuffer> transferBuffer =
+                GrVkTransferBuffer::Make(gpu, size, GrVkBuffer::kCopyRead_Type);
+        if (!transferBuffer) {
+            return;
+        }
+
+        char* buffer = (char*) transferBuffer->map();
+        memcpy (buffer, src, size);
+        transferBuffer->unmap();
+
+        gpu->copyBuffer(transferBuffer.get(), this, 0, this->offset(), size);
+    }
+    this->addMemoryBarrier(gpu,
+                           VK_ACCESS_TRANSFER_WRITE_BIT,
+                           buffer_type_to_access_flags(fDesc.fType),
+                           VK_PIPELINE_STAGE_TRANSFER_BIT,
+                           VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
+                           false);
+}
+
 void GrVkBuffer::internalUnmap(GrVkGpu* gpu, size_t size) {
     VALIDATE();
     SkASSERT(this->vkIsMapped());
@@ -198,29 +226,8 @@
         GrVkMemory::UnmapAlloc(gpu, alloc);
         fMapPtr = nullptr;
     } else {
-        // vkCmdUpdateBuffer requires size < 64k and 4-byte alignment.
-        // https://bugs.chromium.org/p/skia/issues/detail?id=7488
-        if (size <= 65536 && 0 == (size & 0x3)) {
-            gpu->updateBuffer(this, fMapPtr, this->offset(), size);
-        } else {
-            sk_sp<GrVkTransferBuffer> transferBuffer =
-                    GrVkTransferBuffer::Make(gpu, size, GrVkBuffer::kCopyRead_Type);
-            if (!transferBuffer) {
-                return;
-            }
-
-            char* buffer = (char*) transferBuffer->map();
-            memcpy (buffer, fMapPtr, size);
-            transferBuffer->unmap();
-
-            gpu->copyBuffer(transferBuffer.get(), this, 0, this->offset(), size);
-        }
-        this->addMemoryBarrier(gpu,
-                               VK_ACCESS_TRANSFER_WRITE_BIT,
-                               buffer_type_to_access_flags(fDesc.fType),
-                               VK_PIPELINE_STAGE_TRANSFER_BIT,
-                               VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
-                               false);
+        SkASSERT(fMapPtr);
+        this->copyCpuDataToGpuBuffer(gpu, fMapPtr, size);
     }
 }
 
@@ -235,14 +242,18 @@
         return false;
     }
 
-    this->internalMap(gpu, srcSizeInBytes, createdNewBuffer);
-    if (!fMapPtr) {
-        return false;
+    if (fDesc.fDynamic) {
+        this->internalMap(gpu, srcSizeInBytes, createdNewBuffer);
+        if (!fMapPtr) {
+            return false;
+        }
+
+        memcpy(fMapPtr, src, srcSizeInBytes);
+        this->internalUnmap(gpu, srcSizeInBytes);
+    } else {
+        this->copyCpuDataToGpuBuffer(gpu, src, srcSizeInBytes);
     }
 
-    memcpy(fMapPtr, src, srcSizeInBytes);
-
-    this->internalUnmap(gpu, srcSizeInBytes);
 
     return true;
 }
diff --git a/src/gpu/vk/GrVkBuffer.h b/src/gpu/vk/GrVkBuffer.h
index f096921..d65f318 100644
--- a/src/gpu/vk/GrVkBuffer.h
+++ b/src/gpu/vk/GrVkBuffer.h
@@ -106,6 +106,7 @@
 
     void internalMap(GrVkGpu* gpu, size_t size, bool* createdNewBuffer = nullptr);
     void internalUnmap(GrVkGpu* gpu, size_t size);
+    void copyCpuDataToGpuBuffer(GrVkGpu* gpu, const void* srcData, size_t size);
 
     void validate() const;
     bool vkIsMapped() const;
diff --git a/src/gpu/vk/GrVkCaps.cpp b/src/gpu/vk/GrVkCaps.cpp
index a720e72..59ac518 100644
--- a/src/gpu/vk/GrVkCaps.cpp
+++ b/src/gpu/vk/GrVkCaps.cpp
@@ -11,6 +11,7 @@
 #include "GrRenderTarget.h"
 #include "GrShaderCaps.h"
 #include "GrVkInterface.h"
+#include "GrVkTexture.h"
 #include "GrVkUtil.h"
 #include "SkGr.h"
 #include "vk/GrVkBackendContext.h"
@@ -41,6 +42,7 @@
 
     fMapBufferFlags = kNone_MapFlags; //TODO: figure this out
     fBufferMapThreshold = SK_MaxS32;  //TODO: figure this out
+    fTransferBufferSupport = true;
 
     fMaxRenderTargetSize = 4096; // minimum required by spec
     fMaxTextureSize = 4096; // minimum required by spec
@@ -75,12 +77,16 @@
 }
 
 bool GrVkCaps::canCopyImage(GrPixelConfig dstConfig, int dstSampleCnt, GrSurfaceOrigin dstOrigin,
-                            GrPixelConfig srcConfig, int srcSampleCnt,
-                            GrSurfaceOrigin srcOrigin) const {
+                            bool dstHasYcbcr, GrPixelConfig srcConfig, int srcSampleCnt,
+                            GrSurfaceOrigin srcOrigin, bool srcHasYcbcr) const {
     if ((dstSampleCnt > 1 || srcSampleCnt > 1) && dstSampleCnt != srcSampleCnt) {
         return false;
     }
 
+    if (dstHasYcbcr || srcHasYcbcr) {
+        return false;
+    }
+
     // We require that all vulkan GrSurfaces have been created with transfer_dst and transfer_src
     // as image usage flags.
     if (srcOrigin != dstOrigin || GrBytesPerPixel(srcConfig) != GrBytesPerPixel(dstConfig)) {
@@ -96,7 +102,8 @@
 }
 
 bool GrVkCaps::canCopyAsBlit(GrPixelConfig dstConfig, int dstSampleCnt, bool dstIsLinear,
-                             GrPixelConfig srcConfig, int srcSampleCnt, bool srcIsLinear) const {
+                             bool dstHasYcbcr, GrPixelConfig srcConfig, int srcSampleCnt,
+                             bool srcIsLinear, bool srcHasYcbcr) const {
     // We require that all vulkan GrSurfaces have been created with transfer_dst and transfer_src
     // as image usage flags.
     if (!this->configCanBeDstofBlit(dstConfig, dstIsLinear) ||
@@ -115,12 +122,17 @@
         return false;
     }
 
+    if (dstHasYcbcr || srcHasYcbcr) {
+        return false;
+    }
+
     return true;
 }
 
 bool GrVkCaps::canCopyAsResolve(GrPixelConfig dstConfig, int dstSampleCnt,
-                                GrSurfaceOrigin dstOrigin, GrPixelConfig srcConfig,
-                                int srcSampleCnt, GrSurfaceOrigin srcOrigin) const {
+                                GrSurfaceOrigin dstOrigin, bool dstHasYcbcr,
+                                GrPixelConfig srcConfig, int srcSampleCnt,
+                                GrSurfaceOrigin srcOrigin, bool srcHasYcbcr) const {
     // The src surface must be multisampled.
     if (srcSampleCnt <= 1) {
         return false;
@@ -141,11 +153,16 @@
         return false;
     }
 
+    if (dstHasYcbcr || srcHasYcbcr) {
+        return false;
+    }
+
     return true;
 }
 
-bool GrVkCaps::canCopyAsDraw(GrPixelConfig dstConfig, bool dstIsRenderable,
-                             GrPixelConfig srcConfig, bool srcIsTextureable) const {
+bool GrVkCaps::canCopyAsDraw(GrPixelConfig dstConfig, bool dstIsRenderable, bool dstHasYcbcr,
+                             GrPixelConfig srcConfig, bool srcIsTextureable,
+                             bool srcHasYcbcr) const {
     // TODO: Make copySurfaceAsDraw handle the swizzle
     if (this->shaderCaps()->configOutputSwizzle(srcConfig) !=
         this->shaderCaps()->configOutputSwizzle(dstConfig)) {
@@ -157,6 +174,10 @@
         return false;
     }
 
+    if (dstHasYcbcr) {
+        return false;
+    }
+
     return true;
 }
 
@@ -198,14 +219,28 @@
     SkASSERT((dstSampleCnt > 0) == SkToBool(dst->asRenderTargetProxy()));
     SkASSERT((srcSampleCnt > 0) == SkToBool(src->asRenderTargetProxy()));
 
-    return this->canCopyImage(dstConfig, dstSampleCnt, dstOrigin,
-                              srcConfig, srcSampleCnt, srcOrigin) ||
-           this->canCopyAsBlit(dstConfig, dstSampleCnt, dstIsLinear,
-                               srcConfig, srcSampleCnt, srcIsLinear) ||
-           this->canCopyAsResolve(dstConfig, dstSampleCnt, dstOrigin,
-                                  srcConfig, srcSampleCnt, srcOrigin) ||
-           this->canCopyAsDraw(dstConfig, dstSampleCnt > 0,
-                               srcConfig, SkToBool(src->asTextureProxy()));
+    bool dstHasYcbcr = false;
+    if (auto ycbcr = dst->backendFormat().getVkYcbcrConversionInfo()) {
+        if (ycbcr->isValid()) {
+            dstHasYcbcr = true;
+        }
+    }
+
+    bool srcHasYcbcr = false;
+    if (auto ycbcr = src->backendFormat().getVkYcbcrConversionInfo()) {
+        if (ycbcr->isValid()) {
+            srcHasYcbcr = true;
+        }
+    }
+
+    return this->canCopyImage(dstConfig, dstSampleCnt, dstOrigin, dstHasYcbcr,
+                              srcConfig, srcSampleCnt, srcOrigin, srcHasYcbcr) ||
+           this->canCopyAsBlit(dstConfig, dstSampleCnt, dstIsLinear, dstHasYcbcr,
+                               srcConfig, srcSampleCnt, srcIsLinear, srcHasYcbcr) ||
+           this->canCopyAsResolve(dstConfig, dstSampleCnt, dstOrigin, dstHasYcbcr,
+                                  srcConfig, srcSampleCnt, srcOrigin, srcHasYcbcr) ||
+           this->canCopyAsDraw(dstConfig, dstSampleCnt > 0, dstHasYcbcr,
+                               srcConfig, SkToBool(src->asTextureProxy()), srcHasYcbcr);
 }
 
 template<typename T> T* get_extension_feature_struct(const VkPhysicalDeviceFeatures2& features,
@@ -232,7 +267,6 @@
 void GrVkCaps::init(const GrContextOptions& contextOptions, const GrVkInterface* vkInterface,
                     VkPhysicalDevice physDev, const VkPhysicalDeviceFeatures2& features,
                     uint32_t physicalDeviceVersion, const GrVkExtensions& extensions) {
-
     VkPhysicalDeviceProperties properties;
     GR_VK_CALL(vkInterface, GetPhysicalDeviceProperties(physDev, &properties));
 
@@ -241,6 +275,10 @@
 
     SkASSERT(physicalDeviceVersion <= properties.apiVersion);
 
+    if (extensions.hasExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME, 1)) {
+        fSupportsSwapchain = true;
+    }
+
     if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) ||
         extensions.hasExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 1)) {
         fSupportsPhysicalDeviceProperties2 = true;
@@ -336,6 +374,25 @@
         fPreferFullscreenClears = true;
     }
 
+    if (kQualcomm_VkVendor == properties.vendorID || kARM_VkVendor == properties.vendorID) {
+        // On Qualcomm and ARM mapping a gpu buffer and doing both reads and writes to it is slow.
+        // Thus for index and vertex buffers we will force to use a cpu side buffer and then copy
+        // the whole buffer up to the gpu.
+        fBufferMapThreshold = SK_MaxS32;
+    }
+
+    if (kQualcomm_VkVendor == properties.vendorID) {
+        // On Qualcomm it looks like using vkCmdUpdateBuffer is slower than using a transfer buffer
+        // even for small sizes.
+        fAvoidUpdateBuffers = true;
+    }
+
+    if (kARM_VkVendor == properties.vendorID) {
+        // ARM seems to do better with more fine triangles as opposed to using the sample mask.
+        // (At least in our current round rect op.)
+        fPreferTrianglesOverSampleMask = true;
+    }
+
     this->initConfigTable(vkInterface, physDev, properties);
     this->initStencilFormat(vkInterface, physDev);
 
@@ -356,6 +413,8 @@
 void GrVkCaps::applyDriverCorrectnessWorkarounds(const VkPhysicalDeviceProperties& properties) {
     if (kQualcomm_VkVendor == properties.vendorID) {
         fMustDoCopiesFromOrigin = true;
+        // Transfer doesn't support this workaround.
+        fTransferBufferSupport = false;
     }
 
 #if defined(SK_BUILD_FOR_WIN)
@@ -517,6 +576,8 @@
                 // only extra work is the swizzle in the shader for all operations.
                 shaderCaps->fConfigTextureSwizzle[i] = GrSwizzle::BGRA();
                 shaderCaps->fConfigOutputSwizzle[i] = GrSwizzle::BGRA();
+            } else if (kRGB_888X_GrPixelConfig == config) {
+                shaderCaps->fConfigTextureSwizzle[i] = GrSwizzle::RGB1();
             } else {
                 shaderCaps->fConfigTextureSwizzle[i] = GrSwizzle::RGBA();
             }
@@ -593,19 +654,27 @@
         VkFormat format;
         if (GrPixelConfigToVkFormat(static_cast<GrPixelConfig>(i), &format)) {
             if (!GrPixelConfigIsSRGB(static_cast<GrPixelConfig>(i)) || fSRGBSupport) {
-                fConfigTable[i].init(interface, physDev, properties, format);
+                bool disableRendering = false;
+                if (static_cast<GrPixelConfig>(i) == kRGB_888X_GrPixelConfig) {
+                    // Currently we don't allow RGB_888X to be renderable because we don't have a
+                    // way to handle blends that reference dst alpha when the values in the dst
+                    // alpha channel are uninitialized.
+                    disableRendering = true;
+                }
+                fConfigTable[i].init(interface, physDev, properties, format, disableRendering);
             }
         }
     }
 }
 
-void GrVkCaps::ConfigInfo::InitConfigFlags(VkFormatFeatureFlags vkFlags, uint16_t* flags) {
+void GrVkCaps::ConfigInfo::InitConfigFlags(VkFormatFeatureFlags vkFlags, uint16_t* flags,
+                                           bool disableRendering) {
     if (SkToBool(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT & vkFlags) &&
         SkToBool(VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT & vkFlags)) {
         *flags = *flags | kTextureable_Flag;
 
         // Ganesh assumes that all renderable surfaces are also texturable
-        if (SkToBool(VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT & vkFlags)) {
+        if (SkToBool(VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT & vkFlags) & !disableRendering) {
             *flags = *flags | kRenderable_Flag;
         }
     }
@@ -666,12 +735,13 @@
 void GrVkCaps::ConfigInfo::init(const GrVkInterface* interface,
                                 VkPhysicalDevice physDev,
                                 const VkPhysicalDeviceProperties& properties,
-                                VkFormat format) {
+                                VkFormat format,
+                                bool disableRendering) {
     VkFormatProperties props;
     memset(&props, 0, sizeof(VkFormatProperties));
     GR_VK_CALL(interface, GetPhysicalDeviceFormatProperties(physDev, format, &props));
-    InitConfigFlags(props.linearTilingFeatures, &fLinearFlags);
-    InitConfigFlags(props.optimalTilingFeatures, &fOptimalFlags);
+    InitConfigFlags(props.linearTilingFeatures, &fLinearFlags, disableRendering);
+    InitConfigFlags(props.optimalTilingFeatures, &fOptimalFlags, disableRendering);
     if (fOptimalFlags & kRenderable_Flag) {
         this->initSampleCounts(interface, physDev, properties, format);
     }
@@ -707,21 +777,38 @@
     return table[table.count() - 1];
 }
 
-bool GrVkCaps::onSurfaceSupportsWritePixels(const GrSurface* surface) const {
-    if (auto rt = surface->asRenderTarget()) {
-        return rt->numColorSamples() <= 1 && SkToBool(surface->asTexture());
+bool GrVkCaps::surfaceSupportsReadPixels(const GrSurface* surface) const {
+    if (auto tex = static_cast<const GrVkTexture*>(surface->asTexture())) {
+        // We can't directly read from a VkImage that has a ycbcr sampler.
+        if (tex->ycbcrConversionInfo().isValid()) {
+            return false;
+        }
     }
     return true;
 }
 
-GrPixelConfig validate_image_info(VkFormat format, SkColorType ct, bool hasYcbcrConversion) {
+bool GrVkCaps::onSurfaceSupportsWritePixels(const GrSurface* surface) const {
+    if (auto rt = surface->asRenderTarget()) {
+        return rt->numColorSamples() <= 1 && SkToBool(surface->asTexture());
+    }
+    // We can't write to a texture that has a ycbcr sampler.
+    if (auto tex = static_cast<const GrVkTexture*>(surface->asTexture())) {
+        // We can't directly read from a VkImage that has a ycbcr sampler.
+        if (tex->ycbcrConversionInfo().isValid()) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static GrPixelConfig validate_image_info(VkFormat format, SkColorType ct, bool hasYcbcrConversion) {
     if (format == VK_FORMAT_UNDEFINED) {
         // If the format is undefined then it is only valid as an external image which requires that
         // we have a valid VkYcbcrConversion.
         if (hasYcbcrConversion) {
             // We don't actually care what the color type or config are since we won't use those
-            // values for external textures, but since our code requires setting a config here
-            // just default it to RGBA.
+            // values for external textures. However, for read pixels we will draw to a non ycbcr
+            // texture of this config so we set RGBA here for that.
             return kRGBA_8888_GrPixelConfig;
         } else {
             return kUnknown_GrPixelConfig;
@@ -762,6 +849,9 @@
             if (VK_FORMAT_R8G8B8_UNORM == format) {
                 return kRGB_888_GrPixelConfig;
             }
+            if (VK_FORMAT_R8G8B8A8_UNORM == format) {
+                return kRGB_888X_GrPixelConfig;
+            }
             break;
         case kBGRA_8888_SkColorType:
             if (VK_FORMAT_B8G8R8A8_UNORM == format) {
@@ -782,6 +872,11 @@
                 return kGray_8_as_Red_GrPixelConfig;
             }
             break;
+        case kRGBA_F16Norm_SkColorType:
+            if (VK_FORMAT_R16G16B16A16_SFLOAT == format) {
+                return kRGBA_half_Clamped_GrPixelConfig;
+            }
+            break;
         case kRGBA_F16_SkColorType:
             if (VK_FORMAT_R16G16B16A16_SFLOAT == format) {
                 return kRGBA_half_GrPixelConfig;
@@ -828,6 +923,8 @@
             return kRG_88_GrPixelConfig;
         case VK_FORMAT_B8G8R8A8_UNORM:
             return kBGRA_8888_GrPixelConfig;
+        case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
+            return kRGBA_1010102_GrPixelConfig;
         default:
             return kUnknown_GrPixelConfig;
     }
@@ -854,3 +951,30 @@
     return GrBackendFormat::MakeVk(format);
 }
 
+bool GrVkCaps::onTransferFromBufferRequirements(GrColorType bufferColorType, int width,
+                                                size_t* rowBytes, size_t* offsetAlignment) const {
+    // This GrColorType has 32 bpp but the Vulkan pixel format we use for with may have 24bpp
+    // (VK_FORMAT_R8G8B8_...) or may be 32 bpp. We don't support post transforming the pixel data
+    // for transfer-from currently and don't want to have to pass info about the src surface here.
+    if (bufferColorType == GrColorType::kRGB_888x) {
+        return false;
+    }
+    size_t bpp = GrColorTypeBytesPerPixel(bufferColorType);
+    // The VkBufferImageCopy bufferOffset field must be both a multiple of 4 and of a single texel.
+    switch (bpp & 0b11) {
+        case 0:  // bpp is a multiple of 4
+            *offsetAlignment = bpp;
+            break;
+        case 2:  // bpp is a multiple of 2
+            *offsetAlignment = 2 * bpp;
+            break;
+        case 1:
+        case 3:  // bpp neither a multiple of 2 nor 4.
+            *offsetAlignment = 4 * bpp;
+            break;
+    }
+    // The bufferRowLength member of VkBufferImageCopy is in texel units and must be >= the extent
+    // of the src VkImage region.
+    *rowBytes = width * bpp;
+    return true;
+}
diff --git a/src/gpu/vk/GrVkCaps.h b/src/gpu/vk/GrVkCaps.h
index 2309541..34abe70 100644
--- a/src/gpu/vk/GrVkCaps.h
+++ b/src/gpu/vk/GrVkCaps.h
@@ -43,7 +43,7 @@
     int getRenderTargetSampleCount(int requestedCount, GrPixelConfig config) const override;
     int maxRenderTargetSampleCount(GrPixelConfig config) const override;
 
-    bool surfaceSupportsReadPixels(const GrSurface*) const override { return true; }
+    bool surfaceSupportsReadPixels(const GrSurface*) const override;
 
     bool isConfigTexturableLinearly(GrPixelConfig config) const {
         return SkToBool(ConfigInfo::kTextureable_Flag & fConfigTable[config].fLinearFlags);
@@ -91,6 +91,11 @@
         return fShouldAlwaysUseDedicatedImageMemory;
     }
 
+    // Always use a transfer buffer instead of vkCmdUpdateBuffer to upload data to a VkBuffer.
+    bool avoidUpdateBuffers() const {
+        return fAvoidUpdateBuffers;
+    }
+
     /**
      * Returns both a supported and most preferred stencil format to use in draws.
      */
@@ -98,6 +103,12 @@
         return fPreferredStencilFormat;
     }
 
+    // Returns whether the device supports VK_KHR_Swapchain. Internally Skia never uses any of the
+    // swapchain functions, but we may need to transition to and from the
+    // VK_IMAGE_LAYOUT_PRESENT_SRC_KHR image layout, so we must know whether that layout is
+    // supported.
+    bool supportsSwapchain() const { return fSupportsSwapchain; }
+
     // Returns whether the device supports the ability to extend VkPhysicalDeviceProperties struct.
     bool supportsPhysicalDeviceProperties2() const { return fSupportsPhysicalDeviceProperties2; }
     // Returns whether the device supports the ability to extend VkMemoryRequirements struct.
@@ -131,17 +142,19 @@
      * target.
      */
     bool canCopyImage(GrPixelConfig dstConfig, int dstSampleCnt, GrSurfaceOrigin dstOrigin,
-                      GrPixelConfig srcConfig, int srcSamplecnt, GrSurfaceOrigin srcOrigin) const;
+                      bool dstHasYcbcr, GrPixelConfig srcConfig, int srcSamplecnt,
+                      GrSurfaceOrigin srcOrigin, bool srcHasYcbcr) const;
 
     bool canCopyAsBlit(GrPixelConfig dstConfig, int dstSampleCnt, bool dstIsLinear,
-                       GrPixelConfig srcConfig, int srcSampleCnt, bool srcIsLinear) const;
+                       bool dstHasYcbcr, GrPixelConfig srcConfig, int srcSampleCnt,
+                       bool srcIsLinear, bool srcHasYcbcr) const;
 
     bool canCopyAsResolve(GrPixelConfig dstConfig, int dstSampleCnt, GrSurfaceOrigin dstOrigin,
-                          GrPixelConfig srcConfig, int srcSamplecnt,
-                          GrSurfaceOrigin srcOrigin) const;
+                          bool dstHasYcbcr, GrPixelConfig srcConfig, int srcSamplecnt,
+                          GrSurfaceOrigin srcOrigin, bool srcHasYcbcr) const;
 
-    bool canCopyAsDraw(GrPixelConfig dstConfig, bool dstIsRenderable,
-                       GrPixelConfig srcConfig, bool srcIsTextureable) const;
+    bool canCopyAsDraw(GrPixelConfig dstConfig, bool dstIsRenderable, bool dstHasYcbcr,
+                       GrPixelConfig srcConfig, bool srcIsTextureable, bool srcHasYcbcr) const;
 
     bool initDescForDstCopy(const GrRenderTargetProxy* src, GrSurfaceDesc* desc, GrSurfaceOrigin*,
                             bool* rectsMustMatch, bool* disallowSubrect) const override;
@@ -186,13 +199,15 @@
     bool onSurfaceSupportsWritePixels(const GrSurface*) const override;
     bool onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
                           const SkIRect& srcRect, const SkIPoint& dstPoint) const override;
+    bool onTransferFromBufferRequirements(GrColorType bufferColorType, int width, size_t* rowBytes,
+                                          size_t* offsetAlignment) const override;
 
     struct ConfigInfo {
         ConfigInfo() : fOptimalFlags(0), fLinearFlags(0) {}
 
         void init(const GrVkInterface*, VkPhysicalDevice, const VkPhysicalDeviceProperties&,
-                  VkFormat);
-        static void InitConfigFlags(VkFormatFeatureFlags, uint16_t* flags);
+                  VkFormat, bool disableRendering);
+        static void InitConfigFlags(VkFormatFeatureFlags, uint16_t* flags, bool disableRendering);
         void initSampleCounts(const GrVkInterface*, VkPhysicalDevice,
                               const VkPhysicalDeviceProperties&, VkFormat);
 
@@ -219,6 +234,10 @@
     bool fNewCBOnPipelineChange = false;
     bool fShouldAlwaysUseDedicatedImageMemory = false;
 
+    bool fAvoidUpdateBuffers = false;
+
+    bool fSupportsSwapchain = false;
+
     bool fSupportsPhysicalDeviceProperties2 = false;
     bool fSupportsMemoryRequirements2 = false;
     bool fSupportsBindMemory2 = false;
diff --git a/src/gpu/vk/GrVkCommandBuffer.cpp b/src/gpu/vk/GrVkCommandBuffer.cpp
index 09e8aba..fb222af 100644
--- a/src/gpu/vk/GrVkCommandBuffer.cpp
+++ b/src/gpu/vk/GrVkCommandBuffer.cpp
@@ -128,11 +128,12 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 void GrVkCommandBuffer::pipelineBarrier(const GrVkGpu* gpu,
+                                        const GrVkResource* resource,
                                         VkPipelineStageFlags srcStageMask,
                                         VkPipelineStageFlags dstStageMask,
                                         bool byRegion,
                                         BarrierType barrierType,
-                                        void* barrier) const {
+                                        void* barrier) {
     SkASSERT(!this->isWrapped());
     SkASSERT(fIsActive);
     // For images we can have barriers inside of render passes but they require us to add more
@@ -140,44 +141,82 @@
     // never have buffer barriers inside of a render pass. For now we will just assert that we are
     // not in a render pass.
     SkASSERT(!fActiveRenderPass);
-    VkDependencyFlags dependencyFlags = byRegion ? VK_DEPENDENCY_BY_REGION_BIT : 0;
 
-    switch (barrierType) {
-        case kMemory_BarrierType: {
-            const VkMemoryBarrier* barrierPtr = reinterpret_cast<VkMemoryBarrier*>(barrier);
-            GR_VK_CALL(gpu->vkInterface(), CmdPipelineBarrier(fCmdBuffer, srcStageMask,
-                                                              dstStageMask, dependencyFlags,
-                                                              1, barrierPtr,
-                                                              0, nullptr,
-                                                              0, nullptr));
-            break;
+    if (barrierType == kBufferMemory_BarrierType) {
+        const VkBufferMemoryBarrier* barrierPtr = reinterpret_cast<VkBufferMemoryBarrier*>(barrier);
+        fBufferBarriers.push_back(*barrierPtr);
+    } else {
+        SkASSERT(barrierType == kImageMemory_BarrierType);
+        const VkImageMemoryBarrier* barrierPtr = reinterpret_cast<VkImageMemoryBarrier*>(barrier);
+        // We need to check if we are adding a pipeline barrier that covers part of the same
+        // subresource range as a barrier that is already in current batch. If it does, then we must
+        // submit the first batch because the vulkan spec does not define a specific ordering for
+        // barriers submitted in the same batch.
+        // TODO: Look if we can gain anything by merging barriers together instead of submitting
+        // the old ones.
+        for (int i = 0; i < fImageBarriers.count(); ++i) {
+            VkImageMemoryBarrier& currentBarrier = fImageBarriers[i];
+            if (barrierPtr->image == currentBarrier.image) {
+                const VkImageSubresourceRange newRange = barrierPtr->subresourceRange;
+                const VkImageSubresourceRange oldRange = currentBarrier.subresourceRange;
+                SkASSERT(newRange.aspectMask == oldRange.aspectMask);
+                SkASSERT(newRange.baseArrayLayer == oldRange.baseArrayLayer);
+                SkASSERT(newRange.layerCount == oldRange.layerCount);
+                uint32_t newStart = newRange.baseMipLevel;
+                uint32_t newEnd = newRange.baseMipLevel + newRange.levelCount - 1;
+                uint32_t oldStart = oldRange.baseMipLevel;
+                uint32_t oldEnd = oldRange.baseMipLevel + oldRange.levelCount - 1;
+                if (SkTMax(newStart, oldStart) <= SkTMin(newEnd, oldEnd)) {
+                    this->submitPipelineBarriers(gpu);
+                    break;
+                }
+            }
         }
-
-        case kBufferMemory_BarrierType: {
-            const VkBufferMemoryBarrier* barrierPtr =
-                                                 reinterpret_cast<VkBufferMemoryBarrier*>(barrier);
-            GR_VK_CALL(gpu->vkInterface(), CmdPipelineBarrier(fCmdBuffer, srcStageMask,
-                                                              dstStageMask, dependencyFlags,
-                                                              0, nullptr,
-                                                              1, barrierPtr,
-                                                              0, nullptr));
-            break;
-        }
-
-        case kImageMemory_BarrierType: {
-            const VkImageMemoryBarrier* barrierPtr =
-                                                  reinterpret_cast<VkImageMemoryBarrier*>(barrier);
-            GR_VK_CALL(gpu->vkInterface(), CmdPipelineBarrier(fCmdBuffer, srcStageMask,
-                                                              dstStageMask, dependencyFlags,
-                                                              0, nullptr,
-                                                              0, nullptr,
-                                                              1, barrierPtr));
-            break;
-        }
+        fImageBarriers.push_back(*barrierPtr);
     }
+    fBarriersByRegion |= byRegion;
 
+    fSrcStageMask = fSrcStageMask | srcStageMask;
+    fDstStageMask = fDstStageMask | dstStageMask;
+
+    fHasWork = true;
+    if (resource) {
+        this->addResource(resource);
+    }
 }
 
+void GrVkCommandBuffer::submitPipelineBarriers(const GrVkGpu* gpu) {
+    SkASSERT(fIsActive);
+
+    // Currently we never submit a pipeline barrier without at least one memory barrier.
+    if (fBufferBarriers.count() || fImageBarriers.count()) {
+        // For images we can have barriers inside of render passes but they require us to add more
+        // support in subpasses which need self dependencies to have barriers inside them. Also, we
+        // can never have buffer barriers inside of a render pass. For now we will just assert that
+        // we are not in a render pass.
+        SkASSERT(!fActiveRenderPass);
+        SkASSERT(!this->isWrapped());
+        SkASSERT(fSrcStageMask && fDstStageMask);
+
+        VkDependencyFlags dependencyFlags = fBarriersByRegion ? VK_DEPENDENCY_BY_REGION_BIT : 0;
+        GR_VK_CALL(gpu->vkInterface(), CmdPipelineBarrier(
+                fCmdBuffer, fSrcStageMask, fDstStageMask, dependencyFlags, 0, nullptr,
+                fBufferBarriers.count(), fBufferBarriers.begin(),
+                fImageBarriers.count(), fImageBarriers.begin()));
+        fBufferBarriers.reset();
+        fImageBarriers.reset();
+        fBarriersByRegion = false;
+        fSrcStageMask = 0;
+        fDstStageMask = 0;
+    }
+    SkASSERT(!fBufferBarriers.count());
+    SkASSERT(!fImageBarriers.count());
+    SkASSERT(!fBarriersByRegion);
+    SkASSERT(!fSrcStageMask);
+    SkASSERT(!fDstStageMask);
+}
+
+
 void GrVkCommandBuffer::bindInputBuffer(GrVkGpu* gpu, uint32_t binding,
                                         const GrVkVertexBuffer* vbuffer) {
     VkBuffer vkBuffer = vbuffer->buffer();
@@ -193,7 +232,7 @@
                                                             &vkBuffer,
                                                             &offset));
         fBoundInputBuffers[binding] = vkBuffer;
-        addResource(vbuffer->resource());
+        this->addResource(vbuffer->resource());
     }
 }
 
@@ -208,7 +247,7 @@
                                                           ibuffer->offset(),
                                                           VK_INDEX_TYPE_UINT16));
         fBoundIndexBuffer = vkBuffer;
-        addResource(ibuffer->resource());
+        this->addResource(ibuffer->resource());
     }
 }
 
@@ -216,11 +255,14 @@
                                          int numAttachments,
                                          const VkClearAttachment* attachments,
                                          int numRects,
-                                         const VkClearRect* clearRects) const {
+                                         const VkClearRect* clearRects) {
     SkASSERT(fIsActive);
     SkASSERT(fActiveRenderPass);
     SkASSERT(numAttachments > 0);
     SkASSERT(numRects > 0);
+
+    this->addingWork(gpu);
+
 #ifdef SK_DEBUG
     for (int i = 0; i < numAttachments; ++i) {
         if (attachments[i].aspectMask == VK_IMAGE_ASPECT_COLOR_BIT) {
@@ -297,9 +339,10 @@
                                     uint32_t instanceCount,
                                     uint32_t firstIndex,
                                     int32_t vertexOffset,
-                                    uint32_t firstInstance) const {
+                                    uint32_t firstInstance) {
     SkASSERT(fIsActive);
     SkASSERT(fActiveRenderPass);
+    this->addingWork(gpu);
     GR_VK_CALL(gpu->vkInterface(), CmdDrawIndexed(fCmdBuffer,
                                                   indexCount,
                                                   instanceCount,
@@ -312,9 +355,10 @@
                              uint32_t vertexCount,
                              uint32_t instanceCount,
                              uint32_t firstVertex,
-                             uint32_t firstInstance) const {
+                             uint32_t firstInstance) {
     SkASSERT(fIsActive);
     SkASSERT(fActiveRenderPass);
+    this->addingWork(gpu);
     GR_VK_CALL(gpu->vkInterface(), CmdDraw(fCmdBuffer,
                                            vertexCount,
                                            instanceCount,
@@ -361,6 +405,11 @@
     }
 }
 
+void GrVkCommandBuffer::addingWork(const GrVkGpu* gpu) {
+    this->submitPipelineBarriers(gpu);
+    fHasWork = true;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // PrimaryCommandBuffer
 ////////////////////////////////////////////////////////////////////////////////
@@ -406,6 +455,9 @@
 void GrVkPrimaryCommandBuffer::end(GrVkGpu* gpu) {
     SkASSERT(fIsActive);
     SkASSERT(!fActiveRenderPass);
+
+    this->submitPipelineBarriers(gpu);
+
     GR_VK_CALL_ERRCHECK(gpu->vkInterface(), EndCommandBuffer(fCmdBuffer));
     for (int i = 0; i < fTrackedRecordingResources.count(); ++i) {
         fTrackedRecordingResources[i]->unref(gpu);
@@ -413,6 +465,7 @@
     fTrackedRecordingResources.rewind();
     this->invalidateState();
     fIsActive = false;
+    fHasWork = false;
 }
 
 void GrVkPrimaryCommandBuffer::beginRenderPass(const GrVkGpu* gpu,
@@ -425,6 +478,8 @@
     SkASSERT(!fActiveRenderPass);
     SkASSERT(renderPass->isCompatible(target));
 
+    this->addingWork(gpu);
+
     VkRenderPassBeginInfo beginInfo;
     VkRect2D renderArea;
     renderArea.offset = { bounds.fLeft , bounds.fTop };
@@ -451,6 +506,7 @@
 void GrVkPrimaryCommandBuffer::endRenderPass(const GrVkGpu* gpu) {
     SkASSERT(fIsActive);
     SkASSERT(fActiveRenderPass);
+    this->addingWork(gpu);
     GR_VK_CALL(gpu->vkInterface(), CmdEndRenderPass(fCmdBuffer));
     fActiveRenderPass = nullptr;
 }
@@ -466,6 +522,8 @@
     SkASSERT(fActiveRenderPass);
     SkASSERT(fActiveRenderPass->isCompatible(*buffer->fActiveRenderPass));
 
+    this->addingWork(gpu);
+
     GR_VK_CALL(gpu->vkInterface(), CmdExecuteCommands(fCmdBuffer, 1, &buffer->fCmdBuffer));
     buffer->ref();
     fSecondaryCommandBuffers.push_back(buffer);
@@ -626,6 +684,7 @@
                                          const VkImageCopy* copyRegions) {
     SkASSERT(fIsActive);
     SkASSERT(!fActiveRenderPass);
+    this->addingWork(gpu);
     this->addResource(srcImage->resource());
     this->addResource(dstImage->resource());
     GR_VK_CALL(gpu->vkInterface(), CmdCopyImage(fCmdBuffer,
@@ -649,6 +708,7 @@
                                          VkFilter filter) {
     SkASSERT(fIsActive);
     SkASSERT(!fActiveRenderPass);
+    this->addingWork(gpu);
     this->addResource(srcResource);
     this->addResource(dstResource);
     GR_VK_CALL(gpu->vkInterface(), CmdBlitImage(fCmdBuffer,
@@ -688,6 +748,7 @@
                                                  const VkBufferImageCopy* copyRegions) {
     SkASSERT(fIsActive);
     SkASSERT(!fActiveRenderPass);
+    this->addingWork(gpu);
     this->addResource(srcImage->resource());
     this->addResource(dstBuffer->resource());
     GR_VK_CALL(gpu->vkInterface(), CmdCopyImageToBuffer(fCmdBuffer,
@@ -706,6 +767,7 @@
                                                  const VkBufferImageCopy* copyRegions) {
     SkASSERT(fIsActive);
     SkASSERT(!fActiveRenderPass);
+    this->addingWork(gpu);
     this->addResource(srcBuffer->resource());
     this->addResource(dstImage->resource());
     GR_VK_CALL(gpu->vkInterface(), CmdCopyBufferToImage(fCmdBuffer,
@@ -724,6 +786,7 @@
                                           const VkBufferCopy* regions) {
     SkASSERT(fIsActive);
     SkASSERT(!fActiveRenderPass);
+    this->addingWork(gpu);
 #ifdef SK_DEBUG
     for (uint32_t i = 0; i < regionCount; ++i) {
         const VkBufferCopy& region = regions[i];
@@ -754,6 +817,7 @@
     // TODO: handle larger transfer sizes
     SkASSERT(dataSize <= 65536);
     SkASSERT(0 == (dataSize & 0x03));    // four byte aligned
+    this->addingWork(gpu);
     this->addResource(dstBuffer->resource());
     GR_VK_CALL(gpu->vkInterface(), CmdUpdateBuffer(fCmdBuffer,
                                                    dstBuffer->buffer(),
@@ -769,6 +833,7 @@
                                                const VkImageSubresourceRange* subRanges) {
     SkASSERT(fIsActive);
     SkASSERT(!fActiveRenderPass);
+    this->addingWork(gpu);
     this->addResource(image->resource());
     GR_VK_CALL(gpu->vkInterface(), CmdClearColorImage(fCmdBuffer,
                                                       image->image(),
@@ -785,6 +850,7 @@
                                                       const VkImageSubresourceRange* subRanges) {
     SkASSERT(fIsActive);
     SkASSERT(!fActiveRenderPass);
+    this->addingWork(gpu);
     this->addResource(image->resource());
     GR_VK_CALL(gpu->vkInterface(), CmdClearDepthStencilImage(fCmdBuffer,
                                                              image->image(),
@@ -802,6 +868,7 @@
     SkASSERT(fIsActive);
     SkASSERT(!fActiveRenderPass);
 
+    this->addingWork(gpu);
     this->addResource(srcImage.resource());
     this->addResource(dstImage.resource());
 
@@ -900,4 +967,5 @@
     }
     this->invalidateState();
     fIsActive = false;
+    fHasWork = false;
 }
diff --git a/src/gpu/vk/GrVkCommandBuffer.h b/src/gpu/vk/GrVkCommandBuffer.h
index ab11565..8c91a61 100644
--- a/src/gpu/vk/GrVkCommandBuffer.h
+++ b/src/gpu/vk/GrVkCommandBuffer.h
@@ -33,17 +33,17 @@
     // CommandBuffer commands
     ////////////////////////////////////////////////////////////////////////////
     enum BarrierType {
-        kMemory_BarrierType,
         kBufferMemory_BarrierType,
         kImageMemory_BarrierType
     };
 
     void pipelineBarrier(const GrVkGpu* gpu,
+                         const GrVkResource* resource,
                          VkPipelineStageFlags srcStageMask,
                          VkPipelineStageFlags dstStageMask,
                          bool byRegion,
                          BarrierType barrierType,
-                         void* barrier) const;
+                         void* barrier);
 
     void bindInputBuffer(GrVkGpu* gpu, uint32_t binding, const GrVkVertexBuffer* vbuffer);
 
@@ -89,20 +89,20 @@
                           int numAttachments,
                           const VkClearAttachment* attachments,
                           int numRects,
-                          const VkClearRect* clearRects) const;
+                          const VkClearRect* clearRects);
 
     void drawIndexed(const GrVkGpu* gpu,
                      uint32_t indexCount,
                      uint32_t instanceCount,
                      uint32_t firstIndex,
                      int32_t vertexOffset,
-                     uint32_t firstInstance) const;
+                     uint32_t firstInstance);
 
     void draw(const GrVkGpu* gpu,
               uint32_t vertexCount,
               uint32_t instanceCount,
               uint32_t firstVertex,
-              uint32_t firstInstance) const;
+              uint32_t firstInstance);
 
     // Add ref-counted resource that will be tracked and released when this command buffer finishes
     // execution
@@ -130,6 +130,8 @@
 
     void releaseResources(GrVkGpu* gpu);
 
+    bool hasWork() const { return fHasWork; }
+
 protected:
         GrVkCommandBuffer(VkCommandBuffer cmdBuffer, GrVkCommandPool* cmdPool,
                           const GrVkRenderPass* rp = nullptr)
@@ -148,13 +150,18 @@
             return fCmdPool == nullptr;
         }
 
+        void addingWork(const GrVkGpu* gpu);
+
+        void submitPipelineBarriers(const GrVkGpu* gpu);
+
         SkTDArray<const GrVkResource*>          fTrackedResources;
         SkTDArray<const GrVkRecycledResource*>  fTrackedRecycledResources;
         SkTDArray<const GrVkResource*>          fTrackedRecordingResources;
 
         // Tracks whether we are in the middle of a command buffer begin/end calls and thus can add
         // new commands to the buffer;
-        bool fIsActive;
+        bool                      fIsActive;
+        bool                      fHasWork = false;
 
         // Stores a pointer to the current active render pass (i.e. begin has been called but not
         // end). A nullptr means there is no active render pass. The GrVKCommandBuffer does not own
@@ -197,6 +204,12 @@
 #ifdef SK_DEBUG
     mutable bool fResourcesReleased = false;
 #endif
+    // Tracking of memory barriers so that we can submit them all in a batch together.
+    SkSTArray<4, VkBufferMemoryBarrier> fBufferBarriers;
+    SkSTArray<1, VkImageMemoryBarrier> fImageBarriers;
+    bool fBarriersByRegion = false;
+    VkPipelineStageFlags fSrcStageMask = 0;
+    VkPipelineStageFlags fDstStageMask = 0;
 };
 
 class GrVkSecondaryCommandBuffer;
diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp
index ad143ee..e1032b5 100644
--- a/src/gpu/vk/GrVkGpu.cpp
+++ b/src/gpu/vk/GrVkGpu.cpp
@@ -318,6 +318,14 @@
 
 void GrVkGpu::submitCommandBuffer(SyncQueue sync) {
     SkASSERT(fCurrentCmdBuffer);
+
+    if (!fCurrentCmdBuffer->hasWork() && kForce_SyncQueue != sync &&
+        !fSemaphoresToSignal.count() && !fSemaphoresToWaitOn.count()) {
+        SkASSERT(fDrawables.empty());
+        fResourceProvider.checkCommandBuffers();
+        return;
+    }
+
     fCurrentCmdBuffer->end(this);
     fCmdPool->close();
     fCurrentCmdBuffer->submitToQueue(this, fQueue, sync, fSemaphoresToSignal, fSemaphoresToWaitOn);
@@ -343,9 +351,9 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-sk_sp<GrBuffer> GrVkGpu::onCreateBuffer(size_t size, GrGpuBufferType type,
-                                        GrAccessPattern accessPattern, const void* data) {
-    sk_sp<GrBuffer> buff;
+sk_sp<GrGpuBuffer> GrVkGpu::onCreateBuffer(size_t size, GrGpuBufferType type,
+                                           GrAccessPattern accessPattern, const void* data) {
+    sk_sp<GrGpuBuffer> buff;
     switch (type) {
         case GrGpuBufferType::kVertex:
             SkASSERT(kDynamic_GrAccessPattern == accessPattern ||
@@ -418,9 +426,9 @@
     return success;
 }
 
-bool GrVkGpu::onTransferPixels(GrTexture* texture, int left, int top, int width, int height,
-                               GrColorType bufferColorType, GrBuffer* transferBuffer,
-                               size_t bufferOffset, size_t rowBytes) {
+bool GrVkGpu::onTransferPixelsTo(GrTexture* texture, int left, int top, int width, int height,
+                                 GrColorType bufferColorType, GrGpuBuffer* transferBuffer,
+                                 size_t bufferOffset, size_t rowBytes) {
     // Can't transfer compressed data
     SkASSERT(!GrPixelConfigIsCompressed(texture->config()));
 
@@ -476,6 +484,88 @@
     return true;
 }
 
+size_t GrVkGpu::onTransferPixelsFrom(GrSurface* surface, int left, int top, int width, int height,
+                                     GrColorType bufferColorType, GrGpuBuffer* transferBuffer,
+                                     size_t offset) {
+    SkASSERT(surface);
+    SkASSERT(transferBuffer);
+
+    size_t rowBytes;
+    size_t offsetAlignment;
+    if (!this->vkCaps().transferFromBufferRequirements(bufferColorType, width, &rowBytes,
+                                                       &offsetAlignment)) {
+        return 0;
+    }
+
+    if (offset % offsetAlignment || offset + height * rowBytes > transferBuffer->size()) {
+        return 0;
+    }
+
+    GrVkTransferBuffer* vkBuffer = static_cast<GrVkTransferBuffer*>(transferBuffer);
+
+    GrVkImage* srcImage;
+    if (GrVkRenderTarget* rt = static_cast<GrVkRenderTarget*>(surface->asRenderTarget())) {
+        // Reading from render targets that wrap a secondary command buffer is not allowed since
+        // it would require us to know the VkImage, which we don't have, as well as need us to
+        // stop and start the VkRenderPass which we don't have access to.
+        if (rt->wrapsSecondaryCommandBuffer()) {
+            return false;
+        }
+        // resolve the render target if necessary
+        switch (rt->getResolveType()) {
+            case GrVkRenderTarget::kCantResolve_ResolveType:
+                return false;
+            case GrVkRenderTarget::kAutoResolves_ResolveType:
+                break;
+            case GrVkRenderTarget::kCanResolve_ResolveType:
+                this->resolveRenderTargetNoFlush(rt);
+                break;
+            default:
+                SK_ABORT("Unknown resolve type");
+        }
+        srcImage = rt;
+    } else {
+        srcImage = static_cast<GrVkTexture*>(surface->asTexture());
+    }
+
+    // Set up copy region
+    VkBufferImageCopy region;
+    memset(&region, 0, sizeof(VkBufferImageCopy));
+    region.bufferOffset = offset;
+    // We're assuming that GrVkCaps made the row bytes tight.
+    region.bufferRowLength = 0;
+#ifdef SK_DEBUG
+    int bpp = GrColorTypeBytesPerPixel(bufferColorType);
+    SkASSERT(rowBytes == width * (size_t)bpp);
+#endif
+    region.bufferImageHeight = 0;
+    region.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 };
+    region.imageOffset = { left, top, 0 };
+    region.imageExtent = { (uint32_t)width, (uint32_t)height, 1 };
+
+    srcImage->setImageLayout(this,
+                             VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+                             VK_ACCESS_TRANSFER_READ_BIT,
+                             VK_PIPELINE_STAGE_TRANSFER_BIT,
+                             false);
+
+    fCurrentCmdBuffer->copyImageToBuffer(this, srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+                                         vkBuffer, 1, &region);
+
+    // Make sure the copy to buffer has finished.
+    vkBuffer->addMemoryBarrier(this,
+                               VK_ACCESS_TRANSFER_WRITE_BIT,
+                               VK_ACCESS_HOST_READ_BIT,
+                               VK_PIPELINE_STAGE_TRANSFER_BIT,
+                               VK_PIPELINE_STAGE_HOST_BIT,
+                               false);
+
+    // The caller is responsible for syncing.
+    this->submitCommandBuffer(kSkip_SyncQueue);
+
+    return rowBytes;
+}
+
 void GrVkGpu::resolveImage(GrSurface* dst, GrVkRenderTarget* src, const SkIRect& srcRect,
                            const SkIPoint& dstPoint) {
     SkASSERT(dst);
@@ -611,9 +701,8 @@
 
     // For RGB_888x src data we are uploading it first to an RGBA texture and then copying it to the
     // dst RGB texture. Thus we do not upload mip levels for that.
-    if (dataColorType == GrColorType::kRGB_888x) {
-        SkASSERT(tex->imageFormat() == VK_FORMAT_R8G8B8_UNORM &&
-                 tex->config() == kRGB_888_GrPixelConfig);
+    if (dataColorType == GrColorType::kRGB_888x && tex->imageFormat() == VK_FORMAT_R8G8B8_UNORM) {
+        SkASSERT(tex->config() == kRGB_888_GrPixelConfig);
         // First check that we'll be able to do the copy to the to the R8G8B8 image in the end via a
         // blit or draw.
         if (!this->vkCaps().configCanBeDstofBlit(kRGB_888_GrPixelConfig, tex->isLinearTiled()) &&
@@ -681,7 +770,7 @@
     // For uploading RGB_888x data to an R8G8B8_UNORM texture we must first upload the data to an
     // R8G8B8A8_UNORM image and then copy it.
     sk_sp<GrVkTexture> copyTexture;
-    if (dataColorType == GrColorType::kRGB_888x) {
+    if (dataColorType == GrColorType::kRGB_888x && tex->imageFormat() == VK_FORMAT_R8G8B8_UNORM) {
         GrSurfaceDesc surfDesc;
         surfDesc.fFlags = kRenderTarget_GrSurfaceFlag;
         surfDesc.fWidth = width;
@@ -1023,8 +1112,13 @@
 
 static bool check_image_info(const GrVkCaps& caps,
                              const GrVkImageInfo& info,
-                             GrPixelConfig config) {
-    if (VK_NULL_HANDLE == info.fImage || VK_NULL_HANDLE == info.fAlloc.fMemory) {
+                             GrPixelConfig config,
+                             bool isWrappedRT) {
+    if (VK_NULL_HANDLE == info.fImage) {
+        return false;
+    }
+
+    if (VK_NULL_HANDLE == info.fAlloc.fMemory && !isWrappedRT) {
         return false;
     }
 
@@ -1034,6 +1128,10 @@
         }
     }
 
+    if (info.fImageLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR && !caps.supportsSwapchain()) {
+        return false;
+    }
+
     SkASSERT(GrVkFormatPixelConfigPairIsValid(info.fFormat, config));
     return true;
 }
@@ -1046,7 +1144,7 @@
         return nullptr;
     }
 
-    if (!check_image_info(this->vkCaps(), imageInfo, backendTex.config())) {
+    if (!check_image_info(this->vkCaps(), imageInfo, backendTex.config(), false)) {
         return nullptr;
     }
 
@@ -1072,7 +1170,7 @@
         return nullptr;
     }
 
-    if (!check_image_info(this->vkCaps(), imageInfo, backendTex.config())) {
+    if (!check_image_info(this->vkCaps(), imageInfo, backendTex.config(), false)) {
         return nullptr;
     }
 
@@ -1104,7 +1202,7 @@
         return nullptr;
     }
 
-    if (VK_NULL_HANDLE == info.fImage) {
+    if (!check_image_info(this->vkCaps(), info, backendRT.config(), true)) {
         return nullptr;
     }
 
@@ -1136,10 +1234,11 @@
     if (!tex.getVkImageInfo(&imageInfo)) {
         return nullptr;
     }
-    if (VK_NULL_HANDLE == imageInfo.fImage) {
+    if (!check_image_info(this->vkCaps(), imageInfo, tex.config(), false)) {
         return nullptr;
     }
 
+
     GrSurfaceDesc desc;
     desc.fFlags = kRenderTarget_GrSurfaceFlag;
     desc.fWidth = tex.width();
@@ -1240,8 +1339,8 @@
         height = SkTMax(1, height / 2);
 
         imageMemoryBarrier.subresourceRange.baseMipLevel = mipLevel - 1;
-        this->addImageMemoryBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
-                                    false, &imageMemoryBarrier);
+        this->addImageMemoryBarrier(vkTex->resource(), VK_PIPELINE_STAGE_TRANSFER_BIT,
+                                    VK_PIPELINE_STAGE_TRANSFER_BIT, false, &imageMemoryBarrier);
 
         blitRegion.srcSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, mipLevel - 1, 0, 1 };
         blitRegion.srcOffsets[0] = { 0, 0, 0 };
@@ -1261,13 +1360,16 @@
                                      VK_FILTER_LINEAR);
         ++mipLevel;
     }
-    // This barrier logically is not needed, but it changes the final level to the same layout as
-    // all the others, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL. This makes tracking of the layouts and
-    // future layout changes easier.
-    imageMemoryBarrier.subresourceRange.baseMipLevel = mipLevel - 1;
-    this->addImageMemoryBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
-                                false, &imageMemoryBarrier);
-    vkTex->updateImageLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+    if (levelCount > 1) {
+        // This barrier logically is not needed, but it changes the final level to the same layout
+        // as all the others, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL. This makes tracking of the
+        // layouts and future layout changes easier. The alternative here would be to track layout
+        // and memory accesses per layer which doesn't seem work it.
+        imageMemoryBarrier.subresourceRange.baseMipLevel = mipLevel - 1;
+        this->addImageMemoryBarrier(vkTex->resource(), VK_PIPELINE_STAGE_TRANSFER_BIT,
+                                    VK_PIPELINE_STAGE_TRANSFER_BIT, false, &imageMemoryBarrier);
+        vkTex->updateImageLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+    }
     return true;
 }
 
@@ -1748,25 +1850,15 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-void GrVkGpu::addMemoryBarrier(VkPipelineStageFlags srcStageMask,
-                               VkPipelineStageFlags dstStageMask,
-                               bool byRegion,
-                               VkMemoryBarrier* barrier) const {
-    SkASSERT(fCurrentCmdBuffer);
-    fCurrentCmdBuffer->pipelineBarrier(this,
-                                       srcStageMask,
-                                       dstStageMask,
-                                       byRegion,
-                                       GrVkCommandBuffer::kMemory_BarrierType,
-                                       barrier);
-}
-
-void GrVkGpu::addBufferMemoryBarrier(VkPipelineStageFlags srcStageMask,
+void GrVkGpu::addBufferMemoryBarrier(const GrVkResource* resource,
+                                     VkPipelineStageFlags srcStageMask,
                                      VkPipelineStageFlags dstStageMask,
                                      bool byRegion,
                                      VkBufferMemoryBarrier* barrier) const {
     SkASSERT(fCurrentCmdBuffer);
+    SkASSERT(resource);
     fCurrentCmdBuffer->pipelineBarrier(this,
+                                       resource,
                                        srcStageMask,
                                        dstStageMask,
                                        byRegion,
@@ -1774,12 +1866,15 @@
                                        barrier);
 }
 
-void GrVkGpu::addImageMemoryBarrier(VkPipelineStageFlags srcStageMask,
+void GrVkGpu::addImageMemoryBarrier(const GrVkResource* resource,
+                                    VkPipelineStageFlags srcStageMask,
                                     VkPipelineStageFlags dstStageMask,
                                     bool byRegion,
                                     VkImageMemoryBarrier* barrier) const {
     SkASSERT(fCurrentCmdBuffer);
+    SkASSERT(resource);
     fCurrentCmdBuffer->pipelineBarrier(this,
+                                       resource,
                                        srcStageMask,
                                        dstStageMask,
                                        byRegion,
@@ -1787,10 +1882,27 @@
                                        barrier);
 }
 
-void GrVkGpu::onFinishFlush(bool insertedSemaphore) {
+void GrVkGpu::onFinishFlush(GrSurfaceProxy* proxy, SkSurface::BackendSurfaceAccess access,
+                            GrFlushFlags flags, bool insertedSemaphore) {
     // Submit the current command buffer to the Queue. Whether we inserted semaphores or not does
     // not effect what we do here.
-    this->submitCommandBuffer(kSkip_SyncQueue);
+    if (proxy && access == SkSurface::BackendSurfaceAccess::kPresent) {
+        GrVkImage* image;
+        SkASSERT(proxy->isInstantiated());
+        if (GrTexture* tex = proxy->peekTexture()) {
+            image = static_cast<GrVkTexture*>(tex);
+        } else {
+            GrRenderTarget* rt = proxy->peekRenderTarget();
+            SkASSERT(rt);
+            image = static_cast<GrVkRenderTarget*>(rt);
+        }
+        image->prepareForPresent(this);
+    }
+    if (flags & kSyncCpu_GrFlushFlag) {
+        this->submitCommandBuffer(kForce_SyncQueue);
+    } else {
+        this->submitCommandBuffer(kSkip_SyncQueue);
+    }
 }
 
 static int get_surface_sample_cnt(GrSurface* surf) {
@@ -1809,8 +1921,10 @@
 #ifdef SK_DEBUG
     int dstSampleCnt = get_surface_sample_cnt(dst);
     int srcSampleCnt = get_surface_sample_cnt(src);
-    SkASSERT(this->vkCaps().canCopyImage(dst->config(), dstSampleCnt, dstOrigin,
-                                         src->config(), srcSampleCnt, srcOrigin));
+    bool dstHasYcbcr = dstImage->ycbcrConversionInfo().isValid();
+    bool srcHasYcbcr = srcImage->ycbcrConversionInfo().isValid();
+    SkASSERT(this->vkCaps().canCopyImage(dst->config(), dstSampleCnt, dstOrigin, dstHasYcbcr,
+                                         src->config(), srcSampleCnt, srcOrigin, srcHasYcbcr));
 
 #endif
 
@@ -1869,8 +1983,11 @@
 #ifdef SK_DEBUG
     int dstSampleCnt = get_surface_sample_cnt(dst);
     int srcSampleCnt = get_surface_sample_cnt(src);
+    bool dstHasYcbcr = dstImage->ycbcrConversionInfo().isValid();
+    bool srcHasYcbcr = srcImage->ycbcrConversionInfo().isValid();
     SkASSERT(this->vkCaps().canCopyAsBlit(dst->config(), dstSampleCnt, dstImage->isLinearTiled(),
-                                          src->config(), srcSampleCnt, srcImage->isLinearTiled()));
+                                          dstHasYcbcr, src->config(), srcSampleCnt,
+                                          srcImage->isLinearTiled(), srcHasYcbcr));
 
 #endif
     dstImage->setImageLayout(this,
@@ -1972,21 +2089,6 @@
     int dstSampleCnt = get_surface_sample_cnt(dst);
     int srcSampleCnt = get_surface_sample_cnt(src);
 
-    if (this->vkCaps().canCopyAsResolve(dstConfig, dstSampleCnt, dstOrigin,
-                                        srcConfig, srcSampleCnt, srcOrigin)) {
-        this->copySurfaceAsResolve(dst, dstOrigin, src, srcOrigin, srcRect, dstPoint);
-        return true;
-    }
-
-    if (this->vkCaps().canCopyAsDraw(dstConfig, SkToBool(dst->asRenderTarget()),
-                                     srcConfig, SkToBool(src->asTexture()))) {
-        SkAssertResult(fCopyManager.copySurfaceAsDraw(this, dst, dstOrigin, src, srcOrigin, srcRect,
-                                                      dstPoint, canDiscardOutsideDstRect));
-        auto dstRect = srcRect.makeOffset(dstPoint.fX, dstPoint.fY);
-        this->didWriteToSurface(dst, dstOrigin, &dstRect);
-        return true;
-    }
-
     GrVkImage* dstImage;
     GrVkImage* srcImage;
     GrRenderTarget* dstRT = dst->asRenderTarget();
@@ -2009,15 +2111,34 @@
         srcImage = static_cast<GrVkTexture*>(src->asTexture());
     }
 
-    if (this->vkCaps().canCopyImage(dstConfig, dstSampleCnt, dstOrigin,
-                                    srcConfig, srcSampleCnt, srcOrigin)) {
+    bool dstHasYcbcr = dstImage->ycbcrConversionInfo().isValid();
+    bool srcHasYcbcr = srcImage->ycbcrConversionInfo().isValid();
+
+    if (this->vkCaps().canCopyAsResolve(dstConfig, dstSampleCnt, dstOrigin, dstHasYcbcr,
+                                        srcConfig, srcSampleCnt, srcOrigin, srcHasYcbcr)) {
+        this->copySurfaceAsResolve(dst, dstOrigin, src, srcOrigin, srcRect, dstPoint);
+        return true;
+    }
+
+    if (this->vkCaps().canCopyAsDraw(dstConfig, SkToBool(dst->asRenderTarget()), dstHasYcbcr,
+                                     srcConfig, SkToBool(src->asTexture()), srcHasYcbcr)) {
+        SkAssertResult(fCopyManager.copySurfaceAsDraw(this, dst, dstOrigin, src, srcOrigin, srcRect,
+                                                      dstPoint, canDiscardOutsideDstRect));
+        auto dstRect = srcRect.makeOffset(dstPoint.fX, dstPoint.fY);
+        this->didWriteToSurface(dst, dstOrigin, &dstRect);
+        return true;
+    }
+
+    if (this->vkCaps().canCopyImage(dstConfig, dstSampleCnt, dstOrigin, dstHasYcbcr,
+                                    srcConfig, srcSampleCnt, srcOrigin, srcHasYcbcr)) {
         this->copySurfaceAsCopyImage(dst, dstOrigin, src, srcOrigin, dstImage, srcImage,
                                      srcRect, dstPoint);
         return true;
     }
 
     if (this->vkCaps().canCopyAsBlit(dstConfig, dstSampleCnt, dstImage->isLinearTiled(),
-                                     srcConfig, srcSampleCnt, srcImage->isLinearTiled())) {
+                                     dstHasYcbcr, srcConfig, srcSampleCnt,
+                                     srcImage->isLinearTiled(), srcHasYcbcr)) {
         this->copySurfaceAsBlit(dst, dstOrigin, src, srcOrigin, dstImage, srcImage,
                                 srcRect, dstPoint);
         return true;
@@ -2066,9 +2187,8 @@
     // 32 bits, but the Vulkan format is only 24. So we first copy the surface into an R8G8B8A8
     // image and then do the read pixels from that.
     sk_sp<GrVkTextureRenderTarget> copySurface;
-    if (dstColorType == GrColorType::kRGB_888x) {
-        SkASSERT(image->imageFormat() == VK_FORMAT_R8G8B8_UNORM &&
-                 surface->config() == kRGB_888_GrPixelConfig);
+    if (dstColorType == GrColorType::kRGB_888x && image->imageFormat() == VK_FORMAT_R8G8B8_UNORM) {
+        SkASSERT(surface->config() == kRGB_888_GrPixelConfig);
 
         // Make a new surface that is RGBA to copy the RGB surface into.
         GrSurfaceDesc surfDesc;
@@ -2104,11 +2224,14 @@
         if (rt) {
             srcSampleCount = rt->numColorSamples();
         }
+        bool srcHasYcbcr = image->ycbcrConversionInfo().isValid();
         static const GrSurfaceOrigin kOrigin = kTopLeft_GrSurfaceOrigin;
-        if (!this->vkCaps().canCopyAsBlit(copySurface->config(), 1, kOrigin,
-                                          surface->config(), srcSampleCount, kOrigin) &&
-            !this->vkCaps().canCopyAsDraw(copySurface->config(), false,
-                                          surface->config(), SkToBool(surface->asTexture()))) {
+        if (!this->vkCaps().canCopyAsBlit(copySurface->config(), 1, kOrigin, false,
+                                          surface->config(), srcSampleCount, kOrigin,
+                                          srcHasYcbcr) &&
+            !this->vkCaps().canCopyAsDraw(copySurface->config(), false, false,
+                                          surface->config(), SkToBool(surface->asTexture()),
+                                          srcHasYcbcr)) {
             return false;
         }
         SkIRect srcRect = SkIRect::MakeXYWH(left, top, width, height);
diff --git a/src/gpu/vk/GrVkGpu.h b/src/gpu/vk/GrVkGpu.h
index a52a982..963016f 100644
--- a/src/gpu/vk/GrVkGpu.h
+++ b/src/gpu/vk/GrVkGpu.h
@@ -73,6 +73,12 @@
         kSkip_SyncQueue
     };
 
+    void querySampleLocations(
+            GrRenderTarget*, const GrStencilSettings&, SkTArray<SkPoint>*) override {
+        SkASSERT(!this->caps()->sampleLocationsSupport());
+        SK_ABORT("Sample locations not yet implemented for Vulkan.");
+    }
+
     void xferBarrier(GrRenderTarget*, GrXferBarrierType) override {}
 
 #if GR_TEST_UTILS
@@ -100,15 +106,13 @@
     GrGpuTextureCommandBuffer* getCommandBuffer(GrTexture*, GrSurfaceOrigin) override;
 
 
-    void addMemoryBarrier(VkPipelineStageFlags srcStageMask,
-                          VkPipelineStageFlags dstStageMask,
-                          bool byRegion,
-                          VkMemoryBarrier* barrier) const;
-    void addBufferMemoryBarrier(VkPipelineStageFlags srcStageMask,
+    void addBufferMemoryBarrier(const GrVkResource*,
+                                VkPipelineStageFlags srcStageMask,
                                 VkPipelineStageFlags dstStageMask,
                                 bool byRegion,
                                 VkBufferMemoryBarrier* barrier) const;
-    void addImageMemoryBarrier(VkPipelineStageFlags srcStageMask,
+    void addImageMemoryBarrier(const GrVkResource*,
+                               VkPipelineStageFlags srcStageMask,
                                VkPipelineStageFlags dstStageMask,
                                bool byRegion,
                                VkImageMemoryBarrier* barrier) const;
@@ -198,8 +202,8 @@
     sk_sp<GrRenderTarget> onWrapVulkanSecondaryCBAsRenderTarget(const SkImageInfo&,
                                                                 const GrVkDrawableInfo&) override;
 
-    sk_sp<GrBuffer> onCreateBuffer(size_t size, GrGpuBufferType type, GrAccessPattern,
-                                   const void* data) override;
+    sk_sp<GrGpuBuffer> onCreateBuffer(size_t size, GrGpuBufferType type, GrAccessPattern,
+                                      const void* data) override;
 
     bool onReadPixels(GrSurface* surface, int left, int top, int width, int height, GrColorType,
                       void* buffer, size_t rowBytes) override;
@@ -207,14 +211,17 @@
     bool onWritePixels(GrSurface* surface, int left, int top, int width, int height, GrColorType,
                        const GrMipLevel texels[], int mipLevelCount) override;
 
-    bool onTransferPixels(GrTexture*, int left, int top, int width, int height, GrColorType,
-                          GrBuffer* transferBuffer, size_t offset, size_t rowBytes) override;
+    bool onTransferPixelsTo(GrTexture*, int left, int top, int width, int height, GrColorType,
+                            GrGpuBuffer* transferBuffer, size_t offset, size_t rowBytes) override;
+    size_t onTransferPixelsFrom(GrSurface* surface, int left, int top, int width, int height,
+                                GrColorType, GrGpuBuffer* transferBuffer, size_t offset) override;
 
     bool onCopySurface(GrSurface* dst, GrSurfaceOrigin dstOrigin, GrSurface* src,
                        GrSurfaceOrigin srcOrigin, const SkIRect& srcRect,
                        const SkIPoint& dstPoint, bool canDiscardOutsideDstRect) override;
 
-    void onFinishFlush(bool insertedSemaphores) override;
+    void onFinishFlush(GrSurfaceProxy*, SkSurface::BackendSurfaceAccess access, GrFlushFlags flags,
+                       bool insertedSemaphores) override;
 
     // Ends and submits the current command buffer to the queue and then creates a new command
     // buffer and begins it. If sync is set to kForce_SyncQueue, the function will wait for all
diff --git a/src/gpu/vk/GrVkGpuCommandBuffer.cpp b/src/gpu/vk/GrVkGpuCommandBuffer.cpp
index 33c4cd9..515aea3 100644
--- a/src/gpu/vk/GrVkGpuCommandBuffer.cpp
+++ b/src/gpu/vk/GrVkGpuCommandBuffer.cpp
@@ -8,6 +8,7 @@
 #include "GrVkGpuCommandBuffer.h"
 
 #include "GrBackendDrawableInfo.h"
+#include "GrContextPriv.h"
 #include "GrFixedClip.h"
 #include "GrMesh.h"
 #include "GrOpFlushState.h"
@@ -38,8 +39,8 @@
 void GrVkGpuTextureCommandBuffer::submit() {
     for (int i = 0; i < fCopies.count(); ++i) {
         CopyInfo& copyInfo = fCopies[i];
-        fGpu->copySurface(fTexture, fOrigin, copyInfo.fSrc, copyInfo.fSrcOrigin, copyInfo.fSrcRect,
-                          copyInfo.fDstPoint);
+        fGpu->copySurface(fTexture, fOrigin, copyInfo.fSrc.get(), copyInfo.fSrcOrigin,
+                          copyInfo.fSrcRect, copyInfo.fDstPoint);
     }
 }
 
@@ -174,7 +175,7 @@
 
         for (int j = 0; j < cbInfo.fPreCopies.count(); ++j) {
             CopyInfo& copyInfo = cbInfo.fPreCopies[j];
-            fGpu->copySurface(fRenderTarget, fOrigin, copyInfo.fSrc, copyInfo.fSrcOrigin,
+            fGpu->copySurface(fRenderTarget, fOrigin, copyInfo.fSrc.get(), copyInfo.fSrcOrigin,
                               copyInfo.fSrcRect, copyInfo.fDstPoint, copyInfo.fShouldDiscardDst);
         }
 
@@ -194,12 +195,10 @@
         // We don't want to actually submit the secondary command buffer if it is wrapped.
         if (this->wrapsSecondaryCommandBuffer()) {
             // If we have any sampled images set their layout now.
-            for (int j = 0; j < cbInfo.fSampledImages.count(); ++j) {
-                cbInfo.fSampledImages[j]->setImageLayout(fGpu,
-                                                         VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
-                                                         VK_ACCESS_SHADER_READ_BIT,
-                                                         VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
-                                                         false);
+            for (int j = 0; j < cbInfo.fSampledTextures.count(); ++j) {
+                cbInfo.fSampledTextures[j]->setImageLayout(
+                        fGpu, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_SHADER_READ_BIT,
+                        VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, false);
             }
 
             // There should have only been one secondary command buffer in the wrapped case so it is
@@ -248,12 +247,10 @@
             }
 
             // If we have any sampled images set their layout now.
-            for (int j = 0; j < cbInfo.fSampledImages.count(); ++j) {
-                cbInfo.fSampledImages[j]->setImageLayout(fGpu,
-                                                         VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
-                                                         VK_ACCESS_SHADER_READ_BIT,
-                                                         VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
-                                                         false);
+            for (int j = 0; j < cbInfo.fSampledTextures.count(); ++j) {
+                cbInfo.fSampledTextures[j]->setImageLayout(
+                        fGpu, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_SHADER_READ_BIT,
+                        VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, false);
             }
 
             SkIRect iBounds;
@@ -593,9 +590,9 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-void GrVkGpuRTCommandBuffer::bindGeometry(const GrBuffer* indexBuffer,
-                                          const GrBuffer* vertexBuffer,
-                                          const GrBuffer* instanceBuffer) {
+void GrVkGpuRTCommandBuffer::bindGeometry(const GrGpuBuffer* indexBuffer,
+                                          const GrGpuBuffer* vertexBuffer,
+                                          const GrGpuBuffer* instanceBuffer) {
     GrVkSecondaryCommandBuffer* currCmdBuf = fCommandBufferInfos[fCurrentCmdInfo].currentCmdBuf();
     // There is no need to put any memory barriers to make sure host writes have finished here.
     // When a command buffer is submitted to a queue, there is an implicit memory barrier that
@@ -608,7 +605,6 @@
 
     if (vertexBuffer) {
         SkASSERT(vertexBuffer);
-        SkASSERT(!vertexBuffer->isCPUBacked());
         SkASSERT(!vertexBuffer->isMapped());
 
         currCmdBuf->bindInputBuffer(fGpu, binding++,
@@ -617,7 +613,6 @@
 
     if (instanceBuffer) {
         SkASSERT(instanceBuffer);
-        SkASSERT(!instanceBuffer->isCPUBacked());
         SkASSERT(!instanceBuffer->isMapped());
 
         currCmdBuf->bindInputBuffer(fGpu, binding++,
@@ -626,7 +621,6 @@
     if (indexBuffer) {
         SkASSERT(indexBuffer);
         SkASSERT(!indexBuffer->isMapped());
-        SkASSERT(!indexBuffer->isCPUBacked());
 
         currCmdBuf->bindIndexBuffer(fGpu, static_cast<const GrVkIndexBuffer*>(indexBuffer));
     }
@@ -730,7 +724,7 @@
                 fGpu->regenerateMipMapLevels(vkTexture);
             }
         }
-        cbInfo.fSampledImages.push_back(vkTexture);
+        cbInfo.fSampledTextures.push_back(vkTexture);
     };
 
     if (dynamicStateArrays && dynamicStateArrays->fPrimitiveProcessorTextures) {
@@ -754,7 +748,7 @@
         }
     }
     if (GrTexture* dstTexture = pipeline.peekDstTexture()) {
-        cbInfo.fSampledImages.push_back(static_cast<GrVkTexture*>(dstTexture));
+        cbInfo.fSampledTextures.push_back(sk_ref_sp(static_cast<GrVkTexture*>(dstTexture)));
     }
 
     GrPrimitiveType primitiveType = meshes[0].primitiveType();
@@ -807,7 +801,11 @@
                                                     int instanceCount,
                                                     int baseInstance) {
     CommandBufferInfo& cbInfo = fCommandBufferInfos[fCurrentCmdInfo];
-    this->bindGeometry(nullptr, vertexBuffer, instanceBuffer);
+    SkASSERT(!vertexBuffer || !vertexBuffer->isCpuBuffer());
+    SkASSERT(!instanceBuffer || !instanceBuffer->isCpuBuffer());
+    auto gpuVertexBuffer = static_cast<const GrGpuBuffer*>(vertexBuffer);
+    auto gpuInstanceBuffer = static_cast<const GrGpuBuffer*>(instanceBuffer);
+    this->bindGeometry(nullptr, gpuVertexBuffer, gpuInstanceBuffer);
     cbInfo.currentCmdBuf()->draw(fGpu, vertexCount, instanceCount, baseVertex, baseInstance);
     fGpu->stats()->incNumDraws();
 }
@@ -824,7 +822,13 @@
                                                            GrPrimitiveRestart restart) {
     SkASSERT(restart == GrPrimitiveRestart::kNo);
     CommandBufferInfo& cbInfo = fCommandBufferInfos[fCurrentCmdInfo];
-    this->bindGeometry(indexBuffer, vertexBuffer, instanceBuffer);
+    SkASSERT(!vertexBuffer || !vertexBuffer->isCpuBuffer());
+    SkASSERT(!instanceBuffer || !instanceBuffer->isCpuBuffer());
+    SkASSERT(!indexBuffer->isCpuBuffer());
+    auto gpuIndexxBuffer = static_cast<const GrGpuBuffer*>(indexBuffer);
+    auto gpuVertexBuffer = static_cast<const GrGpuBuffer*>(vertexBuffer);
+    auto gpuInstanceBuffer = static_cast<const GrGpuBuffer*>(instanceBuffer);
+    this->bindGeometry(gpuIndexxBuffer, gpuVertexBuffer, gpuInstanceBuffer);
     cbInfo.currentCmdBuf()->drawIndexed(fGpu, indexCount, instanceCount,
                                         baseIndex, baseVertex, baseInstance);
     fGpu->stats()->incNumDraws();
@@ -848,11 +852,18 @@
     SkAssertResult(cbInfo.fRenderPass->colorAttachmentIndex(&vkInfo.fColorAttachmentIndex));
     vkInfo.fFormat = targetImage->imageFormat();
     vkInfo.fDrawBounds = &bounds;
+#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
+    vkInfo.fImage = targetImage->image();
+#else
+    vkInfo.fImage = VK_NULL_HANDLE;
+#endif //SK_BUILD_FOR_ANDROID_FRAMEWORK
 
     GrBackendDrawableInfo info(vkInfo);
 
     // After we draw into the command buffer via the drawable, cached state we have may be invalid.
     cbInfo.currentCmdBuf()->invalidateState();
+    // Also assume that the drawable produced output.
+    cbInfo.fIsEmpty = false;
 
     drawable->draw(info);
     fGpu->addDrawable(std::move(drawable));
diff --git a/src/gpu/vk/GrVkGpuCommandBuffer.h b/src/gpu/vk/GrVkGpuCommandBuffer.h
index 9dac586..4ff8875 100644
--- a/src/gpu/vk/GrVkGpuCommandBuffer.h
+++ b/src/gpu/vk/GrVkGpuCommandBuffer.h
@@ -44,12 +44,12 @@
     struct CopyInfo {
         CopyInfo(GrSurface* src, GrSurfaceOrigin srcOrigin, const SkIRect& srcRect,
                  const SkIPoint& dstPoint)
-            : fSrc(src), fSrcOrigin(srcOrigin), fSrcRect(srcRect), fDstPoint(dstPoint) {}
-
-        GrSurface*      fSrc;
-        GrSurfaceOrigin fSrcOrigin;
-        SkIRect         fSrcRect;
-        SkIPoint        fDstPoint;
+                : fSrc(src), fSrcOrigin(srcOrigin), fSrcRect(srcRect), fDstPoint(dstPoint) {}
+        using Src = GrPendingIOResource<GrSurface, kRead_GrIOType>;
+        Src              fSrc;
+        GrSurfaceOrigin  fSrcOrigin;
+        SkIRect          fSrcRect;
+        SkIPoint         fDstPoint;
     };
 
     GrVkGpu*                    fGpu;
@@ -96,9 +96,9 @@
     GrGpu* gpu() override;
 
     // Bind vertex and index buffers
-    void bindGeometry(const GrBuffer* indexBuffer,
-                      const GrBuffer* vertexBuffer,
-                      const GrBuffer* instanceBuffer);
+    void bindGeometry(const GrGpuBuffer* indexBuffer,
+                      const GrGpuBuffer* vertexBuffer,
+                      const GrGpuBuffer* instanceBuffer);
 
     GrVkPipelineState* prepareDrawState(const GrPrimitiveProcessor&,
                                         const GrPipeline&,
@@ -159,17 +159,17 @@
     struct CopyInfo {
         CopyInfo(GrSurface* src, GrSurfaceOrigin srcOrigin, const SkIRect& srcRect,
                  const SkIPoint& dstPoint, bool shouldDiscardDst)
-            : fSrc(src)
-            , fSrcOrigin(srcOrigin)
-            , fSrcRect(srcRect)
-            , fDstPoint(dstPoint)
-            , fShouldDiscardDst(shouldDiscardDst) {}
-
-        GrSurface*      fSrc;
-        GrSurfaceOrigin fSrcOrigin;
-        SkIRect         fSrcRect;
-        SkIPoint        fDstPoint;
-        bool            fShouldDiscardDst;
+                : fSrc(src)
+                , fSrcOrigin(srcOrigin)
+                , fSrcRect(srcRect)
+                , fDstPoint(dstPoint)
+                , fShouldDiscardDst(shouldDiscardDst) {}
+        using Src = GrPendingIOResource<GrSurface, kRead_GrIOType>;
+        Src              fSrc;
+        GrSurfaceOrigin  fSrcOrigin;
+        SkIRect          fSrcRect;
+        SkIPoint         fDstPoint;
+        bool             fShouldDiscardDst;
     };
 
     enum class LoadStoreState {
@@ -180,6 +180,7 @@
     };
 
     struct CommandBufferInfo {
+        using SampledTexture = GrPendingIOResource<GrVkTexture, kRead_GrIOType>;
         const GrVkRenderPass*                  fRenderPass;
         SkTArray<GrVkSecondaryCommandBuffer*>  fCommandBuffers;
         VkClearValue                           fColorClearValue;
@@ -190,10 +191,10 @@
         // command buffer.
         SkTArray<InlineUploadInfo>             fPreDrawUploads;
         SkTArray<CopyInfo>                     fPreCopies;
-        // Array of images that will be sampled and thus need to be transfered to sampled layout
+        // Array of images that will be sampled and thus need to be transferred to sampled layout
         // before submitting the secondary command buffers. This must happen after we do any predraw
         // uploads or copies.
-        SkTArray<GrVkImage*>                   fSampledImages;
+        SkTArray<SampledTexture>               fSampledTextures;
 
         GrVkSecondaryCommandBuffer* currentCmdBuf() {
             return fCommandBuffers.back();
diff --git a/src/gpu/vk/GrVkImage.cpp b/src/gpu/vk/GrVkImage.cpp
index 6ce5d02..1bbb08c 100644
--- a/src/gpu/vk/GrVkImage.cpp
+++ b/src/gpu/vk/GrVkImage.cpp
@@ -29,6 +29,8 @@
         return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
     } else if (VK_IMAGE_LAYOUT_PREINITIALIZED == layout) {
         return VK_PIPELINE_STAGE_HOST_BIT;
+    } else if (VK_IMAGE_LAYOUT_PRESENT_SRC_KHR == layout) {
+        return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
     }
 
     SkASSERT(VK_IMAGE_LAYOUT_UNDEFINED == layout);
@@ -60,10 +62,11 @@
         flags = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
     } else if (VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL == layout) {
         flags = VK_ACCESS_TRANSFER_WRITE_BIT;
-    } else if (VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL == layout) {
-        flags = VK_ACCESS_TRANSFER_READ_BIT;
-    } else if (VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL == layout) {
-        flags = VK_ACCESS_SHADER_READ_BIT;
+    } else if (VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL == layout ||
+               VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL == layout ||
+               VK_IMAGE_LAYOUT_PRESENT_SRC_KHR == layout) {
+        // There are no writes that need to be made available
+        flags = 0;
     }
     return flags;
 }
@@ -89,14 +92,17 @@
              VK_IMAGE_LAYOUT_PREINITIALIZED != newLayout);
     VkImageLayout currentLayout = this->currentLayout();
 
-    if (releaseFamilyQueue && fInfo.fCurrentQueueFamily == fInitialQueueFamily) {
+    if (releaseFamilyQueue && fInfo.fCurrentQueueFamily == fInitialQueueFamily &&
+        newLayout == currentLayout) {
         // We never transfered the image to this queue and we are releasing it so don't do anything.
         return;
     }
 
     // If the old and new layout are the same and the layout is a read only layout, there is no need
-    // to put in a barrier.
-    if (newLayout == currentLayout &&
+    // to put in a barrier unless we also need to switch queues.
+    if (newLayout == currentLayout && !releaseFamilyQueue &&
+        (fInfo.fCurrentQueueFamily == VK_QUEUE_FAMILY_IGNORED ||
+         fInfo.fCurrentQueueFamily == gpu->queueIndex()) &&
         (VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL == currentLayout ||
          VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL == currentLayout ||
          VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL == currentLayout)) {
@@ -142,7 +148,8 @@
         { aspectFlags, 0, fInfo.fLevelCount, 0, 1 }      // subresourceRange
     };
 
-    gpu->addImageMemoryBarrier(srcStageMask, dstStageMask, byRegion, &imageMemoryBarrier);
+    gpu->addImageMemoryBarrier(this->resource(), srcStageMask, dstStageMask, byRegion,
+                               &imageMemoryBarrier);
 
     this->updateImageLayout(newLayout);
 }
@@ -214,6 +221,17 @@
     SkASSERT(!fResource);
 }
 
+void GrVkImage::prepareForPresent(GrVkGpu* gpu) {
+    VkImageLayout layout = this->currentLayout();
+    if (fInitialQueueFamily != VK_QUEUE_FAMILY_EXTERNAL &&
+        fInitialQueueFamily != VK_QUEUE_FAMILY_FOREIGN_EXT) {
+        if (gpu->vkCaps().supportsSwapchain()) {
+            layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+        }
+    }
+    this->setImageLayout(gpu, layout, 0, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, false, true);
+}
+
 void GrVkImage::releaseImage(GrVkGpu* gpu) {
     if (fInfo.fCurrentQueueFamily != fInitialQueueFamily) {
         // The Vulkan spec is vague on what to put for the dstStageMask here. The spec for image
@@ -239,47 +257,49 @@
     }
 }
 
-void GrVkImage::setResourceRelease(sk_sp<GrReleaseProcHelper> releaseHelper) {
+void GrVkImage::setResourceRelease(sk_sp<GrRefCntedCallback> releaseHelper) {
     SkASSERT(fResource);
     // Forward the release proc on to GrVkImage::Resource
     fResource->setRelease(std::move(releaseHelper));
 }
 
 void GrVkImage::Resource::freeGPUData(GrVkGpu* gpu) const {
-    SkASSERT(!fReleaseHelper);
+    this->invokeReleaseProc();
     VK_CALL(gpu, DestroyImage(gpu->device(), fImage, nullptr));
     bool isLinear = (VK_IMAGE_TILING_LINEAR == fImageTiling);
     GrVkMemory::FreeImageMemory(gpu, isLinear, fAlloc);
 }
 
-void GrVkImage::Resource::setIdleProc(GrVkTexture* owner, GrTexture::IdleProc proc,
-                                      void* context) const {
-    fOwningTexture = owner;
-    fIdleProc = proc;
-    fIdleProcContext = context;
+void GrVkImage::Resource::addIdleProc(GrVkTexture* owningTexture,
+                                      sk_sp<GrRefCntedCallback> idleProc) const {
+    SkASSERT(!fOwningTexture || fOwningTexture == owningTexture);
+    fOwningTexture = owningTexture;
+    fIdleProcs.push_back(std::move(idleProc));
 }
 
+int GrVkImage::Resource::idleProcCnt() const { return fIdleProcs.count(); }
+
+sk_sp<GrRefCntedCallback> GrVkImage::Resource::idleProc(int i) const { return fIdleProcs[i]; }
+
+void GrVkImage::Resource::resetIdleProcs() const { fIdleProcs.reset(); }
+
 void GrVkImage::Resource::removeOwningTexture() const { fOwningTexture = nullptr; }
 
 void GrVkImage::Resource::notifyAddedToCommandBuffer() const { ++fNumCommandBufferOwners; }
 
 void GrVkImage::Resource::notifyRemovedFromCommandBuffer() const {
     SkASSERT(fNumCommandBufferOwners);
-    if (--fNumCommandBufferOwners || !fIdleProc) {
+    if (--fNumCommandBufferOwners || !fIdleProcs.count()) {
         return;
     }
-    if (fOwningTexture && fOwningTexture->resourcePriv().hasRefOrPendingIO()) {
-        return;
-    }
-    fIdleProc(fIdleProcContext);
     if (fOwningTexture) {
-        fOwningTexture->setIdleProc(nullptr, nullptr);
-        // Changing the texture's proc should change ours.
-        SkASSERT(!fIdleProc);
-        SkASSERT(!fIdleProc);
+        if (fOwningTexture->resourcePriv().hasRefOrPendingIO()) {
+            // Wait for the texture to become idle in the cache to call the procs.
+            return;
+        }
+        fOwningTexture->callIdleProcsOnBehalfOfResource();
     } else {
-        fIdleProc = nullptr;
-        fIdleProcContext = nullptr;
+        fIdleProcs.reset();
     }
 }
 
@@ -291,3 +311,9 @@
     this->invokeReleaseProc();
 }
 
+#if GR_TEST_UTILS
+void GrVkImage::setCurrentQueueFamilyToGraphicsQueue(GrVkGpu* gpu) {
+    fInfo.fCurrentQueueFamily = gpu->queueIndex();
+}
+#endif
+
diff --git a/src/gpu/vk/GrVkImage.h b/src/gpu/vk/GrVkImage.h
index dab3a75..ec3c15d 100644
--- a/src/gpu/vk/GrVkImage.h
+++ b/src/gpu/vk/GrVkImage.h
@@ -55,6 +55,11 @@
     }
     VkFormat imageFormat() const { return fInfo.fFormat; }
     GrBackendFormat getBackendFormat() const {
+        if (fResource && this->ycbcrConversionInfo().isValid()) {
+            SkASSERT(this->imageFormat() == VK_FORMAT_UNDEFINED);
+            return GrBackendFormat::MakeVk(this->ycbcrConversionInfo());
+        }
+        SkASSERT(this->imageFormat() != VK_FORMAT_UNDEFINED);
         return GrBackendFormat::MakeVk(this->imageFormat());
     }
     uint32_t mipLevels() const { return fInfo.fLevelCount; }
@@ -89,6 +94,10 @@
                         bool byRegion,
                         bool releaseFamilyQueue = false);
 
+    // Returns the image to its original queue family and changes the layout to present if the queue
+    // family is not external or foreign.
+    void prepareForPresent(GrVkGpu* gpu);
+
     // This simply updates our tracking of the image layout and does not actually do any gpu work.
     // This is only used for mip map generation where we are manually changing the layouts as we
     // blit each layer, and then at the end need to update our tracking.
@@ -130,12 +139,16 @@
     typedef void* ReleaseCtx;
     typedef void (*ReleaseProc)(ReleaseCtx);
 
-    void setResourceRelease(sk_sp<GrReleaseProcHelper> releaseHelper);
+    void setResourceRelease(sk_sp<GrRefCntedCallback> releaseHelper);
 
     // Helpers to use for setting the layout of the VkImage
     static VkPipelineStageFlags LayoutToPipelineSrcStageFlags(const VkImageLayout layout);
     static VkAccessFlags LayoutToSrcAccessMask(const VkImageLayout layout);
 
+#if GR_TEST_UTILS
+    void setCurrentQueueFamilyToGraphicsQueue(GrVkGpu* gpu);
+#endif
+
 protected:
     void releaseImage(GrVkGpu* gpu);
     void abandonImage();
@@ -169,17 +182,20 @@
             SkDebugf("GrVkImage: %d (%d refs)\n", fImage, this->getRefCnt());
         }
 #endif
-        void setRelease(sk_sp<GrReleaseProcHelper> releaseHelper) {
+        void setRelease(sk_sp<GrRefCntedCallback> releaseHelper) {
             fReleaseHelper = std::move(releaseHelper);
         }
 
         /**
-         * These are used to coordinate calling the idle proc between the GrVkTexture and the
-         * Resource. If the GrVkTexture becomes purgeable and if there are no command buffers
-         * referring to the Resource then it calls the proc. Otherwise, the Resource calls it
-         * when the last command buffer reference goes away and the GrVkTexture is purgeable.
+         * These are used to coordinate calling the "finished" idle procs between the GrVkTexture
+         * and the Resource. If the GrVkTexture becomes purgeable and if there are no command
+         * buffers referring to the Resource then it calls the procs. Otherwise, the Resource calls
+         * them when the last command buffer reference goes away and the GrVkTexture is purgeable.
          */
-        void setIdleProc(GrVkTexture* owner, GrTexture::IdleProc, void* context) const;
+        void addIdleProc(GrVkTexture*, sk_sp<GrRefCntedCallback>) const;
+        int idleProcCnt() const;
+        sk_sp<GrRefCntedCallback> idleProc(int) const;
+        void resetIdleProcs() const;
         void removeOwningTexture() const;
 
         /**
@@ -191,11 +207,20 @@
         bool isOwnedByCommandBuffer() const { return fNumCommandBufferOwners > 0; }
 
     protected:
-        mutable sk_sp<GrReleaseProcHelper> fReleaseHelper;
+        mutable sk_sp<GrRefCntedCallback> fReleaseHelper;
+
+        void invokeReleaseProc() const {
+            if (fReleaseHelper) {
+                // Depending on the ref count of fReleaseHelper this may or may not actually trigger
+                // the ReleaseProc to be called.
+                fReleaseHelper.reset();
+            }
+        }
 
     private:
         void freeGPUData(GrVkGpu* gpu) const override;
         void abandonGPUData() const override {
+            this->invokeReleaseProc();
             SkASSERT(!fReleaseHelper);
         }
 
@@ -203,8 +228,7 @@
         GrVkAlloc      fAlloc;
         VkImageTiling  fImageTiling;
         mutable int fNumCommandBufferOwners = 0;
-        mutable GrTexture::IdleProc* fIdleProc = nullptr;
-        mutable void* fIdleProcContext = nullptr;
+        mutable SkTArray<sk_sp<GrRefCntedCallback>> fIdleProcs;
         mutable GrVkTexture* fOwningTexture = nullptr;
 
         typedef GrVkResource INHERITED;
@@ -217,14 +241,6 @@
             : Resource(image, alloc, tiling) {
         }
     private:
-        void invokeReleaseProc() const {
-            if (fReleaseHelper) {
-                // Depending on the ref count of fReleaseHelper this may or may not actually trigger
-                // the ReleaseProc to be called.
-                fReleaseHelper.reset();
-            }
-        }
-
         void freeGPUData(GrVkGpu* gpu) const override;
         void abandonGPUData() const override;
     };
diff --git a/src/gpu/vk/GrVkIndexBuffer.cpp b/src/gpu/vk/GrVkIndexBuffer.cpp
index ec2e11e..f5a7bbc 100644
--- a/src/gpu/vk/GrVkIndexBuffer.cpp
+++ b/src/gpu/vk/GrVkIndexBuffer.cpp
@@ -50,7 +50,7 @@
 
 void GrVkIndexBuffer::onMap() {
     if (!this->wasDestroyed()) {
-        this->GrBuffer::fMapPtr = this->vkMap(this->getVkGpu());
+        this->GrGpuBuffer::fMapPtr = this->vkMap(this->getVkGpu());
     }
 }
 
diff --git a/src/gpu/vk/GrVkIndexBuffer.h b/src/gpu/vk/GrVkIndexBuffer.h
index ab5c349..c35ef12 100644
--- a/src/gpu/vk/GrVkIndexBuffer.h
+++ b/src/gpu/vk/GrVkIndexBuffer.h
@@ -8,13 +8,12 @@
 #ifndef GrVkIndexBuffer_DEFINED
 #define GrVkIndexBuffer_DEFINED
 
-#include "GrBuffer.h"
+#include "GrGpuBuffer.h"
 #include "GrVkBuffer.h"
 
 class GrVkGpu;
 
-class GrVkIndexBuffer : public GrBuffer, public GrVkBuffer {
-
+class GrVkIndexBuffer : public GrGpuBuffer, public GrVkBuffer {
 public:
     static sk_sp<GrVkIndexBuffer> Make(GrVkGpu* gpu, size_t size, bool dynamic);
 
@@ -32,7 +31,7 @@
 
     GrVkGpu* getVkGpu() const;
 
-    typedef GrBuffer INHERITED;
+    typedef GrGpuBuffer INHERITED;
 };
 
 #endif
diff --git a/src/gpu/vk/GrVkPipelineStateBuilder.cpp b/src/gpu/vk/GrVkPipelineStateBuilder.cpp
index 400c1e9..a00b2db 100644
--- a/src/gpu/vk/GrVkPipelineStateBuilder.cpp
+++ b/src/gpu/vk/GrVkPipelineStateBuilder.cpp
@@ -8,6 +8,7 @@
 #include "vk/GrVkPipelineStateBuilder.h"
 #include "GrContext.h"
 #include "GrContextPriv.h"
+#include "GrPersistentCacheUtils.h"
 #include "GrShaderCaps.h"
 #include "GrStencilSettings.h"
 #include "GrVkRenderTarget.h"
@@ -113,118 +114,47 @@
                                                    VkShaderModule* outFragShaderModule,
                                                    VkShaderModule* outGeomShaderModule,
                                                    VkPipelineShaderStageCreateInfo* outStageInfo) {
-    // format for shader cache entries is:
-    //     shader_size vertSize;
-    //     char[vertSize] vert;
-    //     SkSL::Program::Inputs vertInputs;
-    //     shader_size fragSize;
-    //     char[fragSize] frag;
-    //     SkSL::Program::Inputs fragInputs;
-    //     shader_size geomSize;
-    //     char[geomSize] geom;
-    //     SkSL::Program::Inputs geomInputs;
-    size_t offset = 0;
+    SkSL::String shaders[kGrShaderTypeCount];
+    SkSL::Program::Inputs inputs[kGrShaderTypeCount];
 
-    // vertex shader
-    shader_size vertSize = *((shader_size*) ((char*) cached.data() + offset));
-    offset += sizeof(shader_size);
-    SkSL::String vert((char*) cached.data() + offset, vertSize);
-    offset += vertSize;
-    SkSL::Program::Inputs vertInputs;
-    memcpy(&vertInputs, (char*) cached.data() + offset, sizeof(vertInputs));
-    offset += sizeof(vertInputs);
-
-    // fragment shader
-    shader_size fragSize = *((shader_size*) ((char*) cached.data() + offset));
-    offset += sizeof(shader_size);
-    SkSL::String frag((char*) cached.data() + offset, fragSize);
-    offset += fragSize;
-    SkSL::Program::Inputs fragInputs;
-    memcpy(&fragInputs, (char*) cached.data() + offset, sizeof(fragInputs));
-    offset += sizeof(fragInputs);
-
-    // geometry shader
-    shader_size geomSize = *((shader_size*) ((char*) cached.data() + offset));
-    offset += sizeof(shader_size);
-    SkSL::String geom((char*) cached.data() + offset, geomSize);
-    offset += geomSize;
-    SkSL::Program::Inputs geomInputs;
-    memcpy(&geomInputs, (char*) cached.data() + offset, sizeof(geomInputs));
-    offset += sizeof(geomInputs);
-
-    SkASSERT(offset == cached.size());
+    SkReader32 reader(cached.data(), cached.size());
+    GrPersistentCacheUtils::UnpackCachedSPIRV(reader, shaders, inputs);
 
     SkAssertResult(this->installVkShaderModule(VK_SHADER_STAGE_VERTEX_BIT,
                                                fVS,
                                                outVertShaderModule,
                                                &outStageInfo[0],
-                                               vert,
-                                               vertInputs));
+                                               shaders[kVertex_GrShaderType],
+                                               inputs[kVertex_GrShaderType]));
 
     SkAssertResult(this->installVkShaderModule(VK_SHADER_STAGE_FRAGMENT_BIT,
                                                fFS,
                                                outFragShaderModule,
                                                &outStageInfo[1],
-                                               frag,
-                                               fragInputs));
+                                               shaders[kFragment_GrShaderType],
+                                               inputs[kFragment_GrShaderType]));
 
-    if (geomSize) {
+    if (!shaders[kGeometry_GrShaderType].empty()) {
         SkAssertResult(this->installVkShaderModule(VK_SHADER_STAGE_GEOMETRY_BIT,
                                                    fGS,
                                                    outGeomShaderModule,
                                                    &outStageInfo[2],
-                                                   geom,
-                                                   geomInputs));
+                                                   shaders[kGeometry_GrShaderType],
+                                                   inputs[kGeometry_GrShaderType]));
         return 3;
     } else {
         return 2;
     }
 }
 
-void GrVkPipelineStateBuilder::storeShadersInCache(const SkSL::String& vert,
-                                                   const SkSL::Program::Inputs& vertInputs,
-                                                   const SkSL::String& frag,
-                                                   const SkSL::Program::Inputs& fragInputs,
-                                                   const SkSL::String& geom,
-                                                   const SkSL::Program::Inputs& geomInputs) {
+void GrVkPipelineStateBuilder::storeShadersInCache(const SkSL::String shaders[],
+                                                   const SkSL::Program::Inputs inputs[]) {
     Desc* desc = static_cast<Desc*>(this->desc());
-
-    // see loadShadersFromCache for the layout of cache entries
     sk_sp<SkData> key = SkData::MakeWithoutCopy(desc->asKey(), desc->shaderKeyLength());
-    size_t dataLength = (sizeof(shader_size) + sizeof(SkSL::Program::Inputs)) * 3 + vert.length() +
-                        frag.length() + geom.length();
-    std::unique_ptr<uint8_t[]> data(new uint8_t[dataLength]);
-    size_t offset = 0;
-
-    // vertex shader
-    *((shader_size*) (data.get() + offset)) = (shader_size) vert.length();
-    offset += sizeof(shader_size);
-    memcpy(data.get() + offset, vert.data(), vert.length());
-    offset += vert.length();
-    memcpy(data.get() + offset, &vertInputs, sizeof(vertInputs));
-    offset += sizeof(vertInputs);
-
-    // fragment shader
-    *((shader_size*) (data.get() + offset)) = (shader_size) frag.length();
-    offset += sizeof(shader_size);
-    memcpy(data.get() + offset, frag.data(), frag.length());
-    offset += frag.length();
-    memcpy(data.get() + offset, &fragInputs, sizeof(fragInputs));
-    offset += sizeof(fragInputs);
-
-    // geometry shader
-    *((shader_size*) (data.get() + offset)) = (shader_size) geom.length();
-    offset += sizeof(shader_size);
-    memcpy(data.get() + offset, geom.data(), geom.length());
-    offset += geom.length();
-    memcpy(data.get() + offset, &geomInputs, sizeof(geomInputs));
-    offset += sizeof(geomInputs);
-
-    SkASSERT(offset == dataLength);
-
-    this->gpu()->getContext()->priv().getPersistentCache()->store(
-                                                  *key,
-                                                  *SkData::MakeWithoutCopy(data.get(), dataLength));
+    SkWriter32 writer;
+    GrPersistentCacheUtils::PackCachedSPIRV(writer, shaders, inputs);
+    auto data = writer.snapshotAsData();
+    this->gpu()->getContext()->priv().getPersistentCache()->store(*key, *data);
 }
 
 GrVkPipelineState* GrVkPipelineStateBuilder::finalize(const GrStencilSettings& stencil,
@@ -292,20 +222,16 @@
                                                      &geomShaderModule, shaderStageInfo);
     } else {
         numShaderStages = 2; // We always have at least vertex and fragment stages.
-        SkSL::String vert;
-        SkSL::Program::Inputs vertInputs;
-        SkSL::String frag;
-        SkSL::Program::Inputs fragInputs;
-        SkSL::String geom;
-        SkSL::Program::Inputs geomInputs;
+        SkSL::String shaders[kGrShaderTypeCount];
+        SkSL::Program::Inputs inputs[kGrShaderTypeCount];
         SkAssertResult(this->createVkShaderModule(VK_SHADER_STAGE_VERTEX_BIT,
                                                   fVS,
                                                   &vertShaderModule,
                                                   &shaderStageInfo[0],
                                                   settings,
                                                   desc,
-                                                  &vert,
-                                                  &vertInputs));
+                                                  &shaders[kVertex_GrShaderType],
+                                                  &inputs[kVertex_GrShaderType]));
 
         SkAssertResult(this->createVkShaderModule(VK_SHADER_STAGE_FRAGMENT_BIT,
                                                   fFS,
@@ -313,8 +239,8 @@
                                                   &shaderStageInfo[1],
                                                   settings,
                                                   desc,
-                                                  &frag,
-                                                  &fragInputs));
+                                                  &shaders[kFragment_GrShaderType],
+                                                  &inputs[kFragment_GrShaderType]));
 
         if (this->primitiveProcessor().willUseGeoShader()) {
             SkAssertResult(this->createVkShaderModule(VK_SHADER_STAGE_GEOMETRY_BIT,
@@ -323,23 +249,17 @@
                                                       &shaderStageInfo[2],
                                                       settings,
                                                       desc,
-                                                      &geom,
-                                                      &geomInputs));
+                                                      &shaders[kGeometry_GrShaderType],
+                                                      &inputs[kGeometry_GrShaderType]));
             ++numShaderStages;
         }
         if (persistentCache) {
-            this->storeShadersInCache(vert, vertInputs, frag, fragInputs, geom, geomInputs);
+            this->storeShadersInCache(shaders, inputs);
         }
     }
-    GrVkPipeline* pipeline = resourceProvider.createPipeline(this->numColorSamples(),
-                                                             fPrimProc,
-                                                             fPipeline,
-                                                             stencil,
-                                                             shaderStageInfo,
-                                                             numShaderStages,
-                                                             primitiveType,
-                                                             compatibleRenderPass,
-                                                             pipelineLayout);
+    GrVkPipeline* pipeline = resourceProvider.createPipeline(
+            this->renderTarget()->numColorSamples(), fPrimProc, fPipeline, stencil, shaderStageInfo,
+            numShaderStages, primitiveType, compatibleRenderPass, pipelineLayout);
     GR_VK_CALL(fGpu->vkInterface(), DestroyShaderModule(fGpu->device(), vertShaderModule,
                                                         nullptr));
     GR_VK_CALL(fGpu->vkInterface(), DestroyShaderModule(fGpu->device(), fragShaderModule,
@@ -374,23 +294,6 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
-uint32_t get_blend_info_key(const GrPipeline& pipeline) {
-    GrXferProcessor::BlendInfo blendInfo;
-    pipeline.getXferProcessor().getBlendInfo(&blendInfo);
-
-    static const uint32_t kBlendWriteShift = 1;
-    static const uint32_t kBlendCoeffShift = 5;
-    GR_STATIC_ASSERT(kLast_GrBlendCoeff < (1 << kBlendCoeffShift));
-    GR_STATIC_ASSERT(kFirstAdvancedGrBlendEquation - 1 < 4);
-
-    uint32_t key = blendInfo.fWriteColor;
-    key |= (blendInfo.fSrcBlend << kBlendWriteShift);
-    key |= (blendInfo.fDstBlend << (kBlendWriteShift + kBlendCoeffShift));
-    key |= (blendInfo.fEquation << (kBlendWriteShift + 2 * kBlendCoeffShift));
-
-    return key;
-}
-
 bool GrVkPipelineStateBuilder::Desc::Build(Desc* desc,
                                            GrRenderTarget* renderTarget,
                                            const GrPrimitiveProcessor& primProc,
@@ -398,7 +301,7 @@
                                            const GrStencilSettings& stencil,
                                            GrPrimitiveType primitiveType,
                                            GrVkGpu* gpu) {
-    if (!INHERITED::Build(desc, renderTarget->config(), primProc,
+    if (!INHERITED::Build(desc, renderTarget, primProc,
                           primitiveType == GrPrimitiveType::kPoints, pipeline, gpu)) {
         return false;
     }
@@ -415,7 +318,7 @@
 
     stencil.genKey(&b);
 
-    b.add32(get_blend_info_key(pipeline));
+    b.add32(pipeline.getBlendInfoKey());
 
     b.add32((uint32_t)primitiveType);
 
diff --git a/src/gpu/vk/GrVkPipelineStateBuilder.h b/src/gpu/vk/GrVkPipelineStateBuilder.h
index 355d62c..2bd61db 100644
--- a/src/gpu/vk/GrVkPipelineStateBuilder.h
+++ b/src/gpu/vk/GrVkPipelineStateBuilder.h
@@ -97,12 +97,7 @@
                              VkShaderModule* outGeomShaderModule,
                              VkPipelineShaderStageCreateInfo* outStageInfo);
 
-    void storeShadersInCache(const SkSL::String& vert,
-                             const SkSL::Program::Inputs& vertInputs,
-                             const SkSL::String& frag,
-                             const SkSL::Program::Inputs& fragInputs,
-                             const SkSL::String& geom,
-                             const SkSL::Program::Inputs& geomInputs);
+    void storeShadersInCache(const SkSL::String shaders[], const SkSL::Program::Inputs inputs[]);
 
     bool createVkShaderModule(VkShaderStageFlagBits stage,
                               const GrGLSLShaderBuilder& builder,
diff --git a/src/gpu/vk/GrVkRenderTarget.h b/src/gpu/vk/GrVkRenderTarget.h
index 393d54d..bcfa823 100644
--- a/src/gpu/vk/GrVkRenderTarget.h
+++ b/src/gpu/vk/GrVkRenderTarget.h
@@ -162,7 +162,7 @@
 
     // In Vulkan we call the release proc after we are finished with the underlying
     // GrVkImage::Resource object (which occurs after the GPU has finished all work on it).
-    void onSetRelease(sk_sp<GrReleaseProcHelper> releaseHelper) override {
+    void onSetRelease(sk_sp<GrRefCntedCallback> releaseHelper) override {
         // Forward the release proc on to GrVkImage
         this->setResourceRelease(std::move(releaseHelper));
     }
diff --git a/src/gpu/vk/GrVkSecondaryCBDrawContext.cpp b/src/gpu/vk/GrVkSecondaryCBDrawContext.cpp
index 7618141..8083f57 100644
--- a/src/gpu/vk/GrVkSecondaryCBDrawContext.cpp
+++ b/src/gpu/vk/GrVkSecondaryCBDrawContext.cpp
@@ -9,10 +9,13 @@
 
 #include "GrContext.h"
 #include "GrContextPriv.h"
+#include "GrContextThreadSafeProxyPriv.h"
 #include "GrRenderTargetContext.h"
+#include "SkDeferredDisplayList.h"
 #include "SkGpuDevice.h"
 #include "SkImageInfo.h"
-#include "SkSurfaceProps.h"
+#include "SkSurfaceCharacterization.h"
+#include "SkSurfacePriv.h"
 #include "vk/GrVkTypes.h"
 
 sk_sp<GrVkSecondaryCBDrawContext> GrVkSecondaryCBDrawContext::Make(GrContext* ctx,
@@ -39,11 +42,14 @@
         return nullptr;
     }
 
-    return sk_sp<GrVkSecondaryCBDrawContext>(new GrVkSecondaryCBDrawContext(std::move(device)));
+    return sk_sp<GrVkSecondaryCBDrawContext>(new GrVkSecondaryCBDrawContext(std::move(device),
+                                                                            props));
 }
 
-GrVkSecondaryCBDrawContext::GrVkSecondaryCBDrawContext(sk_sp<SkGpuDevice> device)
-    : fDevice(device) {}
+GrVkSecondaryCBDrawContext::GrVkSecondaryCBDrawContext(sk_sp<SkGpuDevice> device,
+                                                       const SkSurfaceProps* props)
+    : fDevice(device)
+    , fProps(SkSurfacePropsCopyOrDefault(props)) {}
 
 GrVkSecondaryCBDrawContext::~GrVkSecondaryCBDrawContext() {
     SkASSERT(!fDevice);
@@ -71,3 +77,96 @@
     fDevice.reset();
 }
 
+bool GrVkSecondaryCBDrawContext::characterize(SkSurfaceCharacterization* characterization) const {
+    GrRenderTargetContext* rtc = fDevice->accessRenderTargetContext();
+    GrContext* ctx = fDevice->context();
+
+    int maxResourceCount;
+    size_t maxResourceBytes;
+    ctx->getResourceCacheLimits(&maxResourceCount, &maxResourceBytes);
+
+    // We current don't support textured GrVkSecondaryCBDrawContexts.
+    SkASSERT(!rtc->asTextureProxy());
+
+    // TODO: the addition of colorType to the surfaceContext should remove this calculation
+    SkColorType ct;
+    if (!GrPixelConfigToColorType(rtc->colorSpaceInfo().config(), &ct)) {
+        return false;
+    }
+
+    SkImageInfo ii = SkImageInfo::Make(rtc->width(), rtc->height(), ct, kPremul_SkAlphaType,
+                                       rtc->colorSpaceInfo().refColorSpace());
+
+    characterization->set(ctx->threadSafeProxy(), maxResourceBytes, ii, rtc->origin(),
+                          rtc->colorSpaceInfo().config(), rtc->fsaaType(), rtc->numStencilSamples(),
+                          SkSurfaceCharacterization::Textureable(false),
+                          SkSurfaceCharacterization::MipMapped(false),
+                          SkSurfaceCharacterization::UsesGLFBO0(false),
+                          SkSurfaceCharacterization::VulkanSecondaryCBCompatible(true),
+                          this->props());
+
+    return true;
+}
+
+bool GrVkSecondaryCBDrawContext::isCompatible(
+        const SkSurfaceCharacterization& characterization) const {
+    GrRenderTargetContext* rtc = fDevice->accessRenderTargetContext();
+    GrContext* ctx = fDevice->context();
+
+    if (!characterization.isValid()) {
+        return false;
+    }
+
+    if (!characterization.vulkanSecondaryCBCompatible()) {
+        return false;
+    }
+
+    // As long as the current state in the context allows for greater or equal resources,
+    // we allow the DDL to be replayed.
+    // DDL TODO: should we just remove the resource check and ignore the cache limits on playback?
+    int maxResourceCount;
+    size_t maxResourceBytes;
+    ctx->getResourceCacheLimits(&maxResourceCount, &maxResourceBytes);
+
+    if (characterization.isTextureable()) {
+        // We don't support textureable DDL when rendering to a GrVkSecondaryCBDrawContext.
+        return false;
+    }
+
+    if (characterization.usesGLFBO0()) {
+        return false;
+    }
+
+    // TODO: the addition of colorType to the surfaceContext should remove this calculation
+    SkColorType rtcColorType;
+    if (!GrPixelConfigToColorType(rtc->colorSpaceInfo().config(), &rtcColorType)) {
+        return false;
+    }
+
+    return characterization.contextInfo() && characterization.contextInfo()->priv().matches(ctx) &&
+           characterization.cacheMaxResourceBytes() <= maxResourceBytes &&
+           characterization.origin() == rtc->origin() &&
+           characterization.config() == rtc->colorSpaceInfo().config() &&
+           characterization.width() == rtc->width() &&
+           characterization.height() == rtc->height() &&
+           characterization.colorType() == rtcColorType &&
+           characterization.fsaaType() == rtc->fsaaType() &&
+           characterization.stencilCount() == rtc->numStencilSamples() &&
+           SkColorSpace::Equals(characterization.colorSpace(),
+                                rtc->colorSpaceInfo().colorSpace()) &&
+           characterization.surfaceProps() == rtc->surfaceProps();
+}
+
+bool GrVkSecondaryCBDrawContext::draw(SkDeferredDisplayList* ddl) {
+    if (!ddl || !this->isCompatible(ddl->characterization())) {
+        return false;
+    }
+
+    GrRenderTargetContext* rtc = fDevice->accessRenderTargetContext();
+    GrContext* ctx = fDevice->context();
+
+    ctx->priv().copyOpListsFromDDL(ddl, rtc->asRenderTargetProxy());
+    return true;
+}
+
+
diff --git a/src/gpu/vk/GrVkSecondaryCBDrawContext.h b/src/gpu/vk/GrVkSecondaryCBDrawContext.h
index c6ea9f9..23d6903 100644
--- a/src/gpu/vk/GrVkSecondaryCBDrawContext.h
+++ b/src/gpu/vk/GrVkSecondaryCBDrawContext.h
@@ -10,6 +10,7 @@
 
 #include "SkTypes.h"
 #include "SkRefCnt.h"
+#include "SkSurfaceProps.h"
 
 class GrBackendSemaphore;
 class GrContext;
@@ -91,15 +92,20 @@
     // are still in use by the GPU.
     void releaseResources();
 
+    const SkSurfaceProps& props() const { return fProps; }
+
     // TODO: Fill out these calls to support DDL
     bool characterize(SkSurfaceCharacterization* characterization) const;
     bool draw(SkDeferredDisplayList* deferredDisplayList);
 
 private:
-    explicit GrVkSecondaryCBDrawContext(sk_sp<SkGpuDevice>);
+    explicit GrVkSecondaryCBDrawContext(sk_sp<SkGpuDevice>, const SkSurfaceProps*);
+
+    bool isCompatible(const SkSurfaceCharacterization& characterization) const;
 
     sk_sp<SkGpuDevice>        fDevice;
     std::unique_ptr<SkCanvas> fCachedCanvas;
+    const SkSurfaceProps      fProps;
 
     typedef SkRefCnt INHERITED;
 };
diff --git a/src/gpu/vk/GrVkTexture.cpp b/src/gpu/vk/GrVkTexture.cpp
index 6e8fe67..8610a86 100644
--- a/src/gpu/vk/GrVkTexture.cpp
+++ b/src/gpu/vk/GrVkTexture.cpp
@@ -120,12 +120,11 @@
 }
 
 void GrVkTexture::onRelease() {
-    // We're about to be severed from our GrVkResource. If there is an idle proc we have to decide
-    // who will handle it. If the resource is still tied to a command buffer we let it handle it.
-    // Otherwise, we handle it.
+    // We're about to be severed from our GrVkResource. If there are "finish" idle procs we have to
+    // decide who will handle them. If the resource is still tied to a command buffer we let it
+    // handle them. Otherwise, we handle them.
     if (this->hasResource() && this->resource()->isOwnedByCommandBuffer()) {
-        fIdleProc = nullptr;
-        fIdleProcContext = nullptr;
+        this->removeFinishIdleProcs();
     }
 
     // we create this and don't hand it off, so we should always destroy it
@@ -140,12 +139,11 @@
 }
 
 void GrVkTexture::onAbandon() {
-    // We're about to be severed from our GrVkResource. If there is an idle proc we have to decide
-    // who will handle it. If the resource is still tied to a command buffer we let it handle it.
-    // Otherwise, we handle it.
+    // We're about to be severed from our GrVkResource. If there are "finish" idle procs we have to
+    // decide who will handle them. If the resource is still tied to a command buffer we let it
+    // handle them. Otherwise, we handle them.
     if (this->hasResource() && this->resource()->isOwnedByCommandBuffer()) {
-        fIdleProc = nullptr;
-        fIdleProcContext = nullptr;
+        this->removeFinishIdleProcs();
     }
 
     // we create this and don't hand it off, so we should always destroy it
@@ -171,28 +169,70 @@
     return fTextureView;
 }
 
-void GrVkTexture::setIdleProc(IdleProc proc, void* context) {
-    fIdleProc = proc;
-    fIdleProcContext = context;
-    if (auto* resource = this->resource()) {
-        resource->setIdleProc(proc ? this : nullptr, proc, context);
+void GrVkTexture::addIdleProc(sk_sp<GrRefCntedCallback> idleProc, IdleState type) {
+    INHERITED::addIdleProc(idleProc, type);
+    if (type == IdleState::kFinished) {
+        if (auto* resource = this->resource()) {
+            resource->addIdleProc(this, std::move(idleProc));
+        }
     }
 }
 
-void GrVkTexture::removedLastRefOrPendingIO() {
-    if (!fIdleProc) {
+void GrVkTexture::callIdleProcsOnBehalfOfResource() {
+    // If we got here then the resource is being removed from its last command buffer and the
+    // texture is idle in the cache. Any kFlush idle procs should already have been called. So
+    // the texture and resource should have the same set of procs.
+    SkASSERT(this->resource());
+    SkASSERT(this->resource()->idleProcCnt() == fIdleProcs.count());
+#ifdef SK_DEBUG
+    for (int i = 0; i < fIdleProcs.count(); ++i) {
+        SkASSERT(fIdleProcs[i] == this->resource()->idleProc(i));
+    }
+#endif
+    fIdleProcs.reset();
+    this->resource()->resetIdleProcs();
+}
+
+void GrVkTexture::willRemoveLastRefOrPendingIO() {
+    if (!fIdleProcs.count()) {
         return;
     }
     // This is called when the GrTexture is purgeable. However, we need to check whether the
     // Resource is still owned by any command buffers. If it is then it will call the proc.
     auto* resource = this->hasResource() ? this->resource() : nullptr;
-    if (resource && resource->isOwnedByCommandBuffer()) {
-        return;
+    bool callFinishProcs = !resource || !resource->isOwnedByCommandBuffer();
+    if (callFinishProcs) {
+        // Everything must go!
+        fIdleProcs.reset();
+        resource->resetIdleProcs();
+    } else {
+        // The procs that should be called on flush but not finish are those that are owned
+        // by the GrVkTexture and not the Resource. We do this by copying the resource's array
+        // and thereby dropping refs to procs we own but the resource does not.
+        SkASSERT(resource);
+        fIdleProcs.reset(resource->idleProcCnt());
+        for (int i = 0; i < fIdleProcs.count(); ++i) {
+            fIdleProcs[i] = resource->idleProc(i);
+        }
     }
-    fIdleProc(fIdleProcContext);
-    fIdleProc = nullptr;
-    fIdleProcContext = nullptr;
-    if (resource) {
-        resource->setIdleProc(nullptr, nullptr, nullptr);
+}
+
+void GrVkTexture::removeFinishIdleProcs() {
+    // This should only be called by onRelease/onAbandon when we have already checked for a
+    // resource.
+    const auto* resource = this->resource();
+    SkASSERT(resource);
+    SkSTArray<4, sk_sp<GrRefCntedCallback>> procsToKeep;
+    int resourceIdx = 0;
+    // The idle procs that are common between the GrVkTexture and its Resource should be found in
+    // the same order.
+    for (int i = 0; i < fIdleProcs.count(); ++i) {
+        if (fIdleProcs[i] == resource->idleProc(resourceIdx)) {
+            ++resourceIdx;
+        } else {
+            procsToKeep.push_back(fIdleProcs[i]);
+        }
     }
+    SkASSERT(resourceIdx == resource->idleProcCnt());
+    fIdleProcs = procsToKeep;
 }
diff --git a/src/gpu/vk/GrVkTexture.h b/src/gpu/vk/GrVkTexture.h
index be84229..50d4cae 100644
--- a/src/gpu/vk/GrVkTexture.h
+++ b/src/gpu/vk/GrVkTexture.h
@@ -38,8 +38,8 @@
 
     const GrVkImageView* textureView();
 
-    void setIdleProc(IdleProc, void* context) override;
-    void* idleContext() const override { return fIdleProcContext; }
+    void addIdleProc(sk_sp<GrRefCntedCallback>, IdleState) override;
+    void callIdleProcsOnBehalfOfResource();
 
 protected:
     GrVkTexture(GrVkGpu*, const GrSurfaceDesc&, const GrVkImageInfo&, sk_sp<GrVkImageLayout>,
@@ -54,6 +54,8 @@
         return false;
     }
 
+    void willRemoveLastRefOrPendingIO() override;
+
 private:
     GrVkTexture(GrVkGpu*, SkBudgeted, const GrSurfaceDesc&, const GrVkImageInfo&,
                 sk_sp<GrVkImageLayout> layout, const GrVkImageView* imageView,
@@ -64,16 +66,14 @@
 
     // In Vulkan we call the release proc after we are finished with the underlying
     // GrVkImage::Resource object (which occurs after the GPU has finished all work on it).
-    void onSetRelease(sk_sp<GrReleaseProcHelper> releaseHelper) override {
+    void onSetRelease(sk_sp<GrRefCntedCallback> releaseHelper) override {
         // Forward the release proc on to GrVkImage
         this->setResourceRelease(std::move(releaseHelper));
     }
 
-    void removedLastRefOrPendingIO() override;
+    void removeFinishIdleProcs();
 
     const GrVkImageView* fTextureView;
-    GrTexture::IdleProc* fIdleProc = nullptr;
-    void* fIdleProcContext = nullptr;
 
     typedef GrTexture INHERITED;
 };
diff --git a/src/gpu/vk/GrVkTextureRenderTarget.h b/src/gpu/vk/GrVkTextureRenderTarget.h
index 5e90aa0..8093bac 100644
--- a/src/gpu/vk/GrVkTextureRenderTarget.h
+++ b/src/gpu/vk/GrVkTextureRenderTarget.h
@@ -107,7 +107,7 @@
 
     // In Vulkan we call the release proc after we are finished with the underlying
     // GrVkImage::Resource object (which occurs after the GPU has finished all work on it).
-    void onSetRelease(sk_sp<GrReleaseProcHelper> releaseHelper) override {
+    void onSetRelease(sk_sp<GrRefCntedCallback> releaseHelper) override {
         // Forward the release proc on to GrVkImage
         this->setResourceRelease(std::move(releaseHelper));
     }
diff --git a/src/gpu/vk/GrVkTransferBuffer.h b/src/gpu/vk/GrVkTransferBuffer.h
index 036f4f7..22f7286 100644
--- a/src/gpu/vk/GrVkTransferBuffer.h
+++ b/src/gpu/vk/GrVkTransferBuffer.h
@@ -8,14 +8,13 @@
 #ifndef GrVkTransferBuffer_DEFINED
 #define GrVkTransferBuffer_DEFINED
 
-#include "GrBuffer.h"
+#include "GrGpuBuffer.h"
 #include "GrVkBuffer.h"
 #include "vk/GrVkTypes.h"
 
 class GrVkGpu;
 
-class GrVkTransferBuffer : public GrBuffer, public GrVkBuffer {
-
+class GrVkTransferBuffer : public GrGpuBuffer, public GrVkBuffer {
 public:
     static sk_sp<GrVkTransferBuffer> Make(GrVkGpu* gpu, size_t size, GrVkBuffer::Type type);
 
@@ -31,7 +30,7 @@
 
     void onMap() override {
         if (!this->wasDestroyed()) {
-            this->GrBuffer::fMapPtr = this->vkMap(this->getVkGpu());
+            this->GrGpuBuffer::fMapPtr = this->vkMap(this->getVkGpu());
         }
     }
 
@@ -51,7 +50,7 @@
         return reinterpret_cast<GrVkGpu*>(this->getGpu());
     }
 
-    typedef GrBuffer INHERITED;
+    typedef GrGpuBuffer INHERITED;
 };
 
 #endif
diff --git a/src/gpu/vk/GrVkUniformHandler.cpp b/src/gpu/vk/GrVkUniformHandler.cpp
index 3a35cfc..e286328 100644
--- a/src/gpu/vk/GrVkUniformHandler.cpp
+++ b/src/gpu/vk/GrVkUniformHandler.cpp
@@ -203,7 +203,6 @@
 GrGLSLUniformHandler::UniformHandle GrVkUniformHandler::internalAddUniformArray(
                                                                             uint32_t visibility,
                                                                             GrSLType type,
-                                                                            GrSLPrecision precision,
                                                                             const char* name,
                                                                             bool mangleName,
                                                                             int arrayCount,
@@ -215,7 +214,6 @@
              kGeometry_GrShaderFlag == visibility ||
              (kVertex_GrShaderFlag | kGeometry_GrShaderFlag) == visibility ||
              kFragment_GrShaderFlag == visibility);
-    SkASSERT(kDefault_GrSLPrecision == precision || GrSLTypeIsFloatType(type));
     GrSLTypeIsFloatType(type);
 
     UniformInfo& uni = fUniforms.push_back();
@@ -233,7 +231,6 @@
     fProgramBuilder->nameVariable(uni.fVariable.accessName(), prefix, name, mangleName);
     uni.fVariable.setArrayCount(arrayCount);
     uni.fVisibility = visibility;
-    uni.fVariable.setPrecision(precision);
     // When outputing the GLSL, only the outer uniform block will get the Uniform modifier. Thus
     // we set the modifier to none for all uniforms declared inside the block.
     uni.fVariable.setTypeModifier(GrShaderVar::kNone_TypeModifier);
@@ -268,14 +265,12 @@
     char prefix = 'u';
     fProgramBuilder->nameVariable(&mangleName, prefix, name, true);
 
-    GrSLPrecision precision = GrSLSamplerPrecision(texture->config());
     GrSwizzle swizzle = shaderCaps->configTextureSwizzle(texture->config());
     GrTextureType type = texture->texturePriv().textureType();
 
     UniformInfo& info = fSamplers.push_back();
     info.fVariable.setType(GrSLCombinedSamplerTypeForTextureType(type));
     info.fVariable.setTypeModifier(GrShaderVar::kUniform_TypeModifier);
-    info.fVariable.setPrecision(precision);
     info.fVariable.setName(mangleName);
     SkString layoutQualifier;
     layoutQualifier.appendf("set=%d, binding=%d", kSamplerDescSet, fSamplers.count() - 1);
diff --git a/src/gpu/vk/GrVkUniformHandler.h b/src/gpu/vk/GrVkUniformHandler.h
index 73b17d2..08c3209 100644
--- a/src/gpu/vk/GrVkUniformHandler.h
+++ b/src/gpu/vk/GrVkUniformHandler.h
@@ -64,7 +64,6 @@
 
     UniformHandle internalAddUniformArray(uint32_t visibility,
                                           GrSLType type,
-                                          GrSLPrecision precision,
                                           const char* name,
                                           bool mangleName,
                                           int arrayCount,
diff --git a/src/gpu/vk/GrVkUtil.cpp b/src/gpu/vk/GrVkUtil.cpp
index 8dffe4c..0fccb86 100644
--- a/src/gpu/vk/GrVkUtil.cpp
+++ b/src/gpu/vk/GrVkUtil.cpp
@@ -25,6 +25,9 @@
         case kRGB_888_GrPixelConfig:
             *format = VK_FORMAT_R8G8B8_UNORM;
             return true;
+        case kRGB_888X_GrPixelConfig:
+            *format = VK_FORMAT_R8G8B8A8_UNORM;
+            return true;
         case kRG_88_GrPixelConfig:
             *format = VK_FORMAT_R8G8_UNORM;
             return true;
@@ -69,6 +72,9 @@
         case kRGBA_half_GrPixelConfig:
             *format = VK_FORMAT_R16G16B16A16_SFLOAT;
             return true;
+        case kRGBA_half_Clamped_GrPixelConfig:
+            *format = VK_FORMAT_R16G16B16A16_SFLOAT;
+            return true;
         case kRGB_ETC1_GrPixelConfig:
             // converting to ETC2 which is a superset of ETC1
             *format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
@@ -86,7 +92,8 @@
 bool GrVkFormatPixelConfigPairIsValid(VkFormat format, GrPixelConfig config) {
     switch (format) {
         case VK_FORMAT_R8G8B8A8_UNORM:
-            return kRGBA_8888_GrPixelConfig == config;
+            return kRGBA_8888_GrPixelConfig == config ||
+                   kRGB_888X_GrPixelConfig == config;
         case VK_FORMAT_B8G8R8A8_UNORM:
             return kBGRA_8888_GrPixelConfig == config;
         case VK_FORMAT_R8G8B8A8_SRGB:
@@ -117,7 +124,8 @@
         case VK_FORMAT_R32G32_SFLOAT:
             return kRG_float_GrPixelConfig == config;
         case VK_FORMAT_R16G16B16A16_SFLOAT:
-            return kRGBA_half_GrPixelConfig == config;
+            return kRGBA_half_GrPixelConfig == config ||
+                   kRGBA_half_Clamped_GrPixelConfig == config;
         case VK_FORMAT_R16_SFLOAT:
             return kAlpha_half_GrPixelConfig == config ||
                    kAlpha_half_as_Red_GrPixelConfig == config;
diff --git a/src/gpu/vk/GrVkVertexBuffer.cpp b/src/gpu/vk/GrVkVertexBuffer.cpp
index f8e55a8..af22cc1 100644
--- a/src/gpu/vk/GrVkVertexBuffer.cpp
+++ b/src/gpu/vk/GrVkVertexBuffer.cpp
@@ -49,7 +49,7 @@
 
 void GrVkVertexBuffer::onMap() {
     if (!this->wasDestroyed()) {
-        this->GrBuffer::fMapPtr = this->vkMap(this->getVkGpu());
+        this->GrGpuBuffer::fMapPtr = this->vkMap(this->getVkGpu());
     }
 }
 
diff --git a/src/gpu/vk/GrVkVertexBuffer.h b/src/gpu/vk/GrVkVertexBuffer.h
index af65a7e..9497fd3 100644
--- a/src/gpu/vk/GrVkVertexBuffer.h
+++ b/src/gpu/vk/GrVkVertexBuffer.h
@@ -8,12 +8,12 @@
 #ifndef GrVkVertexBuffer_DEFINED
 #define GrVkVertexBuffer_DEFINED
 
-#include "GrBuffer.h"
+#include "GrGpuBuffer.h"
 #include "GrVkBuffer.h"
 
 class GrVkGpu;
 
-class GrVkVertexBuffer : public GrBuffer, public GrVkBuffer {
+class GrVkVertexBuffer : public GrGpuBuffer, public GrVkBuffer {
 public:
     static sk_sp<GrVkVertexBuffer> Make(GrVkGpu* gpu, size_t size, bool dynamic);
 
@@ -31,7 +31,7 @@
 
     GrVkGpu* getVkGpu() const;
 
-    typedef GrBuffer INHERITED;
+    typedef GrGpuBuffer INHERITED;
 };
 
 #endif
diff --git a/src/image/SkImage.cpp b/src/image/SkImage.cpp
index a28028e..4ec23a3 100644
--- a/src/image/SkImage.cpp
+++ b/src/image/SkImage.cpp
@@ -26,19 +26,17 @@
 #include "SkSurface.h"
 
 #if SK_SUPPORT_GPU
-#include "GrTexture.h"
 #include "GrContext.h"
+#include "GrTexture.h"
 #include "SkImage_Gpu.h"
 #endif
 #include "GrBackendSurface.h"
 
-SkImage::SkImage(int width, int height, uint32_t uniqueID)
-    : fWidth(width)
-    , fHeight(height)
-    , fUniqueID(kNeedNewImageUniqueID == uniqueID ? SkNextID::ImageID() : uniqueID)
-{
-    SkASSERT(width > 0);
-    SkASSERT(height > 0);
+SkImage::SkImage(const SkImageInfo& info, uint32_t uniqueID)
+        : fInfo(info)
+        , fUniqueID(kNeedNewImageUniqueID == uniqueID ? SkNextID::ImageID() : uniqueID) {
+    SkASSERT(info.width() > 0);
+    SkASSERT(info.height() > 0);
 }
 
 bool SkImage::peekPixels(SkPixmap* pm) const {
@@ -49,8 +47,8 @@
     return as_IB(this)->onPeekPixels(pm);
 }
 
-bool SkImage::readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes,
-                           int srcX, int srcY, CachingHint chint) const {
+bool SkImage::readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, int srcX,
+                         int srcY, CachingHint chint) const {
     return as_IB(this)->onReadPixels(dstInfo, dstPixels, dstRowBytes, srcX, srcY, chint);
 }
 
@@ -76,25 +74,17 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-SkColorType SkImage::colorType() const {
-    return as_IB(this)->onImageInfo().colorType();
-}
+SkColorType SkImage::colorType() const { return fInfo.colorType(); }
 
-SkAlphaType SkImage::alphaType() const {
-    return as_IB(this)->onImageInfo().alphaType();
-}
+SkAlphaType SkImage::alphaType() const { return fInfo.alphaType(); }
 
-SkColorSpace* SkImage::colorSpace() const {
-    return as_IB(this)->onImageInfo().colorSpace();
-}
+SkColorSpace* SkImage::colorSpace() const { return fInfo.colorSpace(); }
 
-sk_sp<SkColorSpace> SkImage::refColorSpace() const {
-    return as_IB(this)->onImageInfo().refColorSpace();
-}
+sk_sp<SkColorSpace> SkImage::refColorSpace() const { return fInfo.refColorSpace(); }
 
-sk_sp<SkShader> SkImage::makeShader(SkShader::TileMode tileX, SkShader::TileMode tileY,
+sk_sp<SkShader> SkImage::makeShader(SkTileMode tmx, SkTileMode tmy,
                                     const SkMatrix* localMatrix) const {
-    return SkImageShader::Make(sk_ref_sp(const_cast<SkImage*>(this)), tileX, tileY, localMatrix);
+    return SkImageShader::Make(sk_ref_sp(const_cast<SkImage*>(this)), tmx, tmy, localMatrix);
 }
 
 sk_sp<SkData> SkImage::encodeToData(SkEncodedImageFormat type, int quality) const {
@@ -141,7 +131,13 @@
     if (bounds == subset) {
         return sk_ref_sp(const_cast<SkImage*>(this));
     }
-    return as_IB(this)->onMakeSubset(subset);
+
+    // CONTEXT TODO: propagate the context parameter to the top-level API
+#if SK_SUPPORT_GPU
+    return as_IB(this)->onMakeSubset(as_IB(this)->context(), subset);
+#else
+    return as_IB(this)->onMakeSubset(nullptr, subset);
+#endif
 }
 
 #if SK_SUPPORT_GPU
@@ -186,10 +182,8 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-SkImage_Base::SkImage_Base(int width, int height, uint32_t uniqueID)
-    : INHERITED(width, height, uniqueID)
-    , fAddedToRasterCache(false)
-{}
+SkImage_Base::SkImage_Base(const SkImageInfo& info, uint32_t uniqueID)
+        : INHERITED(info, uniqueID), fAddedToRasterCache(false) {}
 
 SkImage_Base::~SkImage_Base() {
     if (fAddedToRasterCache.load()) {
@@ -228,7 +222,7 @@
 bool SkImage_Base::onAsLegacyBitmap(SkBitmap* bitmap) const {
     // As the base-class, all we can do is make a copy (regardless of mode).
     // Subclasses that want to be more optimal should override.
-    SkImageInfo info = this->onImageInfo().makeColorType(kN32_SkColorType).makeColorSpace(nullptr);
+    SkImageInfo info = fInfo.makeColorType(kN32_SkColorType).makeColorSpace(nullptr);
     if (!bitmap->tryAllocPixels(info)) {
         return false;
     }
@@ -264,16 +258,19 @@
     if (!filter || !outSubset || !offset || !this->bounds().contains(subset)) {
         return nullptr;
     }
-    sk_sp<SkSpecialImage> srcSpecialImage = SkSpecialImage::MakeFromImage(
-        grContext, subset, sk_ref_sp(const_cast<SkImage*>(this)));
+    sk_sp<SkSpecialImage> srcSpecialImage =
+#if SK_SUPPORT_GPU
+        SkSpecialImage::MakeFromImage(grContext, subset, sk_ref_sp(const_cast<SkImage*>(this)));
+#else
+        SkSpecialImage::MakeFromImage(nullptr, subset, sk_ref_sp(const_cast<SkImage*>(this)));
+#endif
     if (!srcSpecialImage) {
         return nullptr;
     }
 
     sk_sp<SkImageFilterCache> cache(
         SkImageFilterCache::Create(SkImageFilterCache::kDefaultTransientSize));
-    SkImageFilter::OutputProperties outputProperties(as_IB(this)->onImageInfo().colorType(),
-                                                     as_IB(this)->onImageInfo().colorSpace());
+    SkImageFilter::OutputProperties outputProperties(fInfo.colorType(), fInfo.colorSpace());
     SkImageFilter::Context context(SkMatrix::I(), clipBounds, cache.get(), outputProperties);
 
     sk_sp<SkSpecialImage> result = filter->filterImage(srcSpecialImage.get(), context, offset);
@@ -297,9 +294,7 @@
     return as_IB(this)->onIsLazyGenerated();
 }
 
-bool SkImage::isAlphaOnly() const {
-    return as_IB(this)->onImageInfo().colorType() == kAlpha_8_SkColorType;
-}
+bool SkImage::isAlphaOnly() const { return SkColorTypeIsAlphaOnly(fInfo.colorType()); }
 
 sk_sp<SkImage> SkImage::makeColorSpace(sk_sp<SkColorSpace> target) const {
     if (!target) {
@@ -317,7 +312,13 @@
         return sk_ref_sp(const_cast<SkImage*>(this));
     }
 
-    return as_IB(this)->onMakeColorTypeAndColorSpace(this->colorType(), std::move(target));
+    // CONTEXT TODO: propagate the context parameter to the top-level API
+#if SK_SUPPORT_GPU
+    return as_IB(this)->onMakeColorTypeAndColorSpace(as_IB(this)->context(),
+#else
+    return as_IB(this)->onMakeColorTypeAndColorSpace(nullptr,
+#endif
+                                                     this->colorType(), std::move(target));
 }
 
 sk_sp<SkImage> SkImage::makeColorTypeAndColorSpace(SkColorType targetColorType,
@@ -336,7 +337,13 @@
         return sk_ref_sp(const_cast<SkImage*>(this));
     }
 
-    return as_IB(this)->onMakeColorTypeAndColorSpace(targetColorType, std::move(targetColorSpace));
+    // CONTEXT TODO: propagate the context parameter to the top-level API
+#if SK_SUPPORT_GPU
+    return as_IB(this)->onMakeColorTypeAndColorSpace(as_IB(this)->context(),
+#else
+    return as_IB(this)->onMakeColorTypeAndColorSpace(nullptr,
+#endif
+                                                     targetColorType, std::move(targetColorSpace));
 }
 
 sk_sp<SkImage> SkImage::makeNonTextureImage() const {
@@ -352,20 +359,19 @@
         return sk_ref_sp(const_cast<SkImage*>(this));
     }
 
-    const SkImageInfo info = as_IB(this)->onImageInfo();
-    const size_t rowBytes = info.minRowBytes();
-    size_t size = info.computeByteSize(rowBytes);
+    const size_t rowBytes = fInfo.minRowBytes();
+    size_t size = fInfo.computeByteSize(rowBytes);
     if (SkImageInfo::ByteSizeOverflowed(size)) {
         return nullptr;
     }
 
     sk_sp<SkData> data = SkData::MakeUninitialized(size);
-    pm = { info.makeColorSpace(nullptr), data->writable_data(), info.minRowBytes() };
+    pm = {fInfo.makeColorSpace(nullptr), data->writable_data(), fInfo.minRowBytes()};
     if (!this->readPixels(pm, 0, 0)) {
         return nullptr;
     }
 
-    return SkImage::MakeRasterData(info, std::move(data), rowBytes);
+    return SkImage::MakeRasterData(fInfo, std::move(data), rowBytes);
 }
 
 //////////////////////////////////////////////////////////////////////////////////////
@@ -476,7 +482,7 @@
 sk_sp<SkImage> SkImageMakeRasterCopyAndAssignColorSpace(const SkImage* src,
                                                         SkColorSpace* colorSpace) {
     // Read the pixels out of the source image, with no conversion
-    SkImageInfo info = as_IB(src)->onImageInfo();
+    const SkImageInfo& info = src->imageInfo();
     if (kUnknown_SkColorType == info.colorType()) {
         SkDEBUGFAIL("Unexpected color type");
         return nullptr;
diff --git a/src/image/SkImage_Base.h b/src/image/SkImage_Base.h
index 50c6ac9..e37a0b5 100644
--- a/src/image/SkImage_Base.h
+++ b/src/image/SkImage_Base.h
@@ -16,6 +16,7 @@
 #include "GrTextureProxy.h"
 #include "SkTDArray.h"
 
+class GrRecordingContext;
 class GrTexture;
 #endif
 
@@ -33,11 +34,6 @@
 public:
     virtual ~SkImage_Base();
 
-    // User: returns image info for this SkImage.
-    // Implementors: if you can not return the value, return an invalid ImageInfo with w=0 & h=0
-    // & unknown color space.
-    virtual SkImageInfo onImageInfo() const = 0;
-
     virtual SkIRect onGetSubset() const {
         return { 0, 0, this->width(), this->height() };
     }
@@ -50,14 +46,17 @@
                               int srcX, int srcY, CachingHint) const = 0;
 
     virtual GrContext* context() const { return nullptr; }
-    // TODO: remove this contextID and use GrContext_Base::matches
-    virtual uint32_t contextID() const { return 0; }
+
 #if SK_SUPPORT_GPU
+    // Return the proxy if this image is backed by a single proxy. For YUVA images, this
+    // will return nullptr unless the YUVA planes have been converted to RGBA in which case
+    // that single backing proxy will be returned.
     virtual GrTextureProxy* peekProxy() const { return nullptr; }
-    virtual sk_sp<GrTextureProxy> asTextureProxyRef() const { return nullptr; }
-    virtual sk_sp<GrTextureProxy> asTextureProxyRef(GrContext*, const GrSamplerState&,
+    virtual sk_sp<GrTextureProxy> asTextureProxyRef(GrRecordingContext*) const { return nullptr; }
+    virtual sk_sp<GrTextureProxy> asTextureProxyRef(GrRecordingContext*, const GrSamplerState&,
                                                     SkScalar scaleAdjust[2]) const = 0;
-    virtual sk_sp<GrTextureProxy> refPinnedTextureProxy(uint32_t* uniqueID) const {
+    virtual sk_sp<GrTextureProxy> refPinnedTextureProxy(GrRecordingContext*,
+                                                        uint32_t* uniqueID) const {
         return nullptr;
     }
     virtual bool isYUVA() const { return false; }
@@ -72,7 +71,7 @@
     // but only inspect them (or encode them).
     virtual bool getROPixels(SkBitmap*, CachingHint = kAllow_CachingHint) const = 0;
 
-    virtual sk_sp<SkImage> onMakeSubset(const SkIRect&) const = 0;
+    virtual sk_sp<SkImage> onMakeSubset(GrRecordingContext*, const SkIRect&) const = 0;
 
     virtual sk_sp<SkCachedData> getPlanes(SkYUVASizeInfo*, SkYUVAIndex[4],
                                           SkYUVColorSpace*, const void* planes[4]);
@@ -97,9 +96,10 @@
     virtual bool onPinAsTexture(GrContext*) const { return false; }
     virtual void onUnpinAsTexture(GrContext*) const {}
 
-    virtual sk_sp<SkImage> onMakeColorTypeAndColorSpace(SkColorType, sk_sp<SkColorSpace>) const = 0;
+    virtual sk_sp<SkImage> onMakeColorTypeAndColorSpace(GrRecordingContext*,
+                                                        SkColorType, sk_sp<SkColorSpace>) const = 0;
 protected:
-    SkImage_Base(int width, int height, uint32_t uniqueID);
+    SkImage_Base(const SkImageInfo& info, uint32_t uniqueID);
 
 private:
     // Set true by caches when they cache content that's derived from the current pixels.
diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp
index 1e0829d..1c67013 100644
--- a/src/image/SkImage_Gpu.cpp
+++ b/src/image/SkImage_Gpu.cpp
@@ -10,6 +10,7 @@
 #include <type_traits>
 
 #include "GrAHardwareBufferImageGenerator.h"
+#include "GrAHardwareBufferUtils.h"
 #include "GrBackendSurface.h"
 #include "GrBackendTextureImageGenerator.h"
 #include "GrBitmapTextureMaker.h"
@@ -18,9 +19,12 @@
 #include "GrColorSpaceXform.h"
 #include "GrContext.h"
 #include "GrContextPriv.h"
+#include "GrDrawingManager.h"
 #include "GrGpu.h"
 #include "GrImageTextureMaker.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrResourceProvider.h"
 #include "GrResourceProviderPriv.h"
@@ -28,6 +32,7 @@
 #include "GrSurfacePriv.h"
 #include "GrTexture.h"
 #include "GrTextureAdjuster.h"
+#include "GrTextureContext.h"
 #include "GrTexturePriv.h"
 #include "GrTextureProxy.h"
 #include "GrTextureProxyPriv.h"
@@ -43,30 +48,34 @@
 #include "effects/GrYUVtoRGBEffect.h"
 #include "gl/GrGLTexture.h"
 
+static SkColorType proxy_color_type(GrTextureProxy* proxy) {
+    SkColorType colorType;
+    if (!GrPixelConfigToColorType(proxy->config(), &colorType)) {
+        colorType = kUnknown_SkColorType;
+    }
+    return colorType;
+}
+
 SkImage_Gpu::SkImage_Gpu(sk_sp<GrContext> context, uint32_t uniqueID, SkAlphaType at,
                          sk_sp<GrTextureProxy> proxy, sk_sp<SkColorSpace> colorSpace)
         : INHERITED(std::move(context), proxy->worstCaseWidth(), proxy->worstCaseHeight(), uniqueID,
-                    at, colorSpace)
+                    proxy_color_type(proxy.get()), at, colorSpace)
         , fProxy(std::move(proxy)) {}
 
 SkImage_Gpu::~SkImage_Gpu() {}
 
-SkImageInfo SkImage_Gpu::onImageInfo() const {
-    SkColorType colorType;
-    if (!GrPixelConfigToColorType(fProxy->config(), &colorType)) {
-        colorType = kUnknown_SkColorType;
+sk_sp<SkImage> SkImage_Gpu::onMakeColorTypeAndColorSpace(GrRecordingContext* context,
+                                                         SkColorType targetCT,
+                                                         sk_sp<SkColorSpace> targetCS) const {
+    if (!context || !fContext->priv().matches(context)) {
+        return nullptr;
     }
 
-    return SkImageInfo::Make(fProxy->width(), fProxy->height(), colorType, fAlphaType, fColorSpace);
-}
-
-sk_sp<SkImage> SkImage_Gpu::onMakeColorTypeAndColorSpace(SkColorType targetCT,
-                                                         sk_sp<SkColorSpace> targetCS) const {
-    auto xform = GrColorSpaceXformEffect::Make(fColorSpace.get(), fAlphaType,
-                                               targetCS.get(), fAlphaType);
+    auto xform = GrColorSpaceXformEffect::Make(this->colorSpace(), this->alphaType(),
+                                               targetCS.get(), this->alphaType());
     SkASSERT(xform || targetCT != this->colorType());
 
-    sk_sp<GrTextureProxy> proxy = this->asTextureProxyRef();
+    sk_sp<GrTextureProxy> proxy = this->asTextureProxyRef(context);
 
     GrBackendFormat format = proxy->backendFormat().makeTexture2D();
     if (!format.isValid()) {
@@ -74,7 +83,7 @@
     }
 
     sk_sp<GrRenderTargetContext> renderTargetContext(
-        fContext->priv().makeDeferredRenderTargetContextWithFallback(
+        context->priv().makeDeferredRenderTargetContextWithFallback(
             format, SkBackingFit::kExact, this->width(), this->height(),
             SkColorType2GrPixelConfig(targetCT), nullptr));
     if (!renderTargetContext) {
@@ -95,7 +104,7 @@
     }
 
     // MDB: this call is okay bc we know 'renderTargetContext' was exact
-    return sk_make_sp<SkImage_Gpu>(fContext, kNeedNewImageUniqueID, fAlphaType,
+    return sk_make_sp<SkImage_Gpu>(fContext, kNeedNewImageUniqueID, this->alphaType(),
                                    renderTargetContext->asTextureProxyRef(), std::move(targetCS));
 }
 
@@ -350,11 +359,13 @@
     if (!context) {
         return nullptr;
     }
-    if (uint32_t incumbentID = as_IB(this)->contextID()) {
-        if (incumbentID != context->priv().contextID()) {
+
+    if (this->isTextureBacked()) {
+        if (!as_IB(this)->context()->priv().matches(context)) {
             return nullptr;
         }
-        sk_sp<GrTextureProxy> proxy = as_IB(this)->asTextureProxyRef();
+
+        sk_sp<GrTextureProxy> proxy = as_IB(this)->asTextureProxyRef(context);
         SkASSERT(proxy);
         if (GrMipMapped::kNo == mipMapped || proxy->mipMapped() == mipMapped) {
             return sk_ref_sp(const_cast<SkImage*>(this));
@@ -394,7 +405,7 @@
                                                PromiseImageTextureReleaseProc textureReleaseProc,
                                                PromiseImageTextureDoneProc textureDoneProc,
                                                PromiseImageTextureContext textureContext,
-                                               DelayReleaseCallback delayReleaseCallback) {
+                                               PromiseImageApiVersion version) {
     // The contract here is that if 'promiseDoneProc' is passed in it should always be called,
     // even if creation of the SkImage fails. Once we call MakePromiseImageLazyProxy it takes
     // responsibility for calling the done proc.
@@ -425,7 +436,7 @@
     callDone.clear();
     auto proxy = MakePromiseImageLazyProxy(context, width, height, origin, config, backendFormat,
                                            mipMapped, textureFulfillProc, textureReleaseProc,
-                                           textureDoneProc, textureContext, delayReleaseCallback);
+                                           textureDoneProc, textureContext, version);
     if (!proxy) {
         return nullptr;
     }
@@ -448,11 +459,16 @@
         return codecImage;
     }
 
+    // If non-power-of-two mipmapping isn't supported, ignore the client's request
+    if (!context->priv().caps()->mipMapSupport()) {
+        buildMips = false;
+    }
+
     auto maxTextureSize = context->priv().caps()->maxTextureSize();
     if (limitToMaxTextureSize &&
         (codecImage->width() > maxTextureSize || codecImage->height() > maxTextureSize)) {
         SkAutoPixmapStorage pmap;
-        SkImageInfo info = as_IB(codecImage)->onImageInfo();
+        SkImageInfo info = codecImage->imageInfo();
         if (!dstColorSpace) {
             info = info.makeColorSpace(nullptr);
         }
@@ -467,27 +483,29 @@
     GrSamplerState samplerState(
             GrSamplerState::WrapMode::kClamp,
             buildMips ? GrSamplerState::Filter::kMipMap : GrSamplerState::Filter::kBilerp);
-    sk_sp<GrTextureProxy> proxy(maker.refTextureProxyForParams(samplerState, nullptr));
+    SkScalar scaleAdjust[2] = { 1.0f, 1.0f };
+    sk_sp<GrTextureProxy> proxy(maker.refTextureProxyForParams(samplerState, scaleAdjust));
+    // Given that we disable mipmaps if non-power-of-two mipmapping isn't supported, we always
+    // expect the created texture to be unscaled.
+    SkASSERT(scaleAdjust[0] == 1.0f && scaleAdjust[1] == 1.0f);
     if (!proxy) {
         return codecImage;
     }
 
-    if (!proxy->instantiate(context->priv().resourceProvider())) {
-        return codecImage;
-    }
-    sk_sp<GrTexture> texture = sk_ref_sp(proxy->peekTexture());
-
     // Flush any writes or uploads
     context->priv().prepareSurfaceForExternalIO(proxy.get());
+    if (!proxy->isInstantiated()) {
+        return codecImage;
+    }
+
+    sk_sp<GrTexture> texture = sk_ref_sp(proxy->peekTexture());
 
     GrGpu* gpu = context->priv().getGpu();
     sk_sp<GrSemaphore> sema = gpu->prepareTextureForCrossContextUsage(texture.get());
 
-    auto gen = GrBackendTextureImageGenerator::Make(std::move(texture), proxy->origin(),
-                                                    std::move(sema),
-                                                    as_IB(codecImage)->onImageInfo().colorType(),
-                                                    codecImage->alphaType(),
-                                                    codecImage->refColorSpace());
+    auto gen = GrBackendTextureImageGenerator::Make(
+            std::move(texture), proxy->origin(), std::move(sema), codecImage->colorType(),
+            codecImage->alphaType(), codecImage->refColorSpace());
     return SkImage::MakeFromGenerator(std::move(gen));
 }
 
@@ -507,6 +525,11 @@
         return SkImage::MakeRasterCopy(originalPixmap);
     }
 
+    // If non-power-of-two mipmapping isn't supported, ignore the client's request
+    if (!context->priv().caps()->mipMapSupport()) {
+        buildMips = false;
+    }
+
     const SkPixmap* pixmap = &originalPixmap;
     SkAutoPixmapStorage resized;
     int maxTextureSize = context->priv().caps()->maxTextureSize();
@@ -523,23 +546,10 @@
     }
     GrProxyProvider* proxyProvider = context->priv().proxyProvider();
     // Turn the pixmap into a GrTextureProxy
-    sk_sp<GrTextureProxy> proxy;
-    if (buildMips) {
-        SkBitmap bmp;
-        bmp.installPixels(*pixmap);
-        proxy = proxyProvider->createMipMapProxyFromBitmap(bmp);
-    } else {
-        if (SkImageInfoIsValid(pixmap->info())) {
-            ATRACE_ANDROID_FRAMEWORK("Upload Texture [%ux%u]", pixmap->width(), pixmap->height());
-            // We don't need a release proc on the data in pixmap since we know we are in a
-            // GrContext that has a resource provider. Thus the createTextureProxy call will
-            // immediately upload the data.
-            sk_sp<SkImage> image = SkImage::MakeFromRaster(*pixmap, nullptr, nullptr);
-            proxy = proxyProvider->createTextureProxy(std::move(image), kNone_GrSurfaceFlags, 1,
-                                                      SkBudgeted::kYes, SkBackingFit::kExact);
-        }
-    }
-
+    SkBitmap bmp;
+    bmp.installPixels(*pixmap);
+    GrMipMapped mipMapped = buildMips ? GrMipMapped::kYes : GrMipMapped::kNo;
+    sk_sp<GrTextureProxy> proxy = proxyProvider->createProxyFromBitmap(bmp, mipMapped);
     if (!proxy) {
         return SkImage::MakeRasterCopy(*pixmap);
     }
@@ -566,6 +576,89 @@
     auto gen = GrAHardwareBufferImageGenerator::Make(graphicBuffer, at, cs, surfaceOrigin);
     return SkImage::MakeFromGenerator(std::move(gen));
 }
+
+sk_sp<SkImage> SkImage::MakeFromAHardwareBufferWithData(GrContext* context,
+                                                        const SkPixmap& pixmap,
+                                                        AHardwareBuffer* hardwareBuffer,
+                                                        GrSurfaceOrigin surfaceOrigin) {
+    AHardwareBuffer_Desc bufferDesc;
+    AHardwareBuffer_describe(hardwareBuffer, &bufferDesc);
+
+    if (!SkToBool(bufferDesc.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE)) {
+        return nullptr;
+    }
+
+    GrBackendFormat backendFormat = GrAHardwareBufferUtils::GetBackendFormat(context,
+                                                                             hardwareBuffer,
+                                                                             bufferDesc.format,
+                                                                             true);
+
+    if (!backendFormat.isValid()) {
+        return nullptr;
+    }
+
+    GrAHardwareBufferUtils::DeleteImageProc deleteImageProc = nullptr;
+    GrAHardwareBufferUtils::DeleteImageCtx deleteImageCtx = nullptr;
+
+    GrBackendTexture backendTexture =
+            GrAHardwareBufferUtils::MakeBackendTexture(context, hardwareBuffer,
+                                                       bufferDesc.width, bufferDesc.height,
+                                                       &deleteImageProc, &deleteImageCtx,
+                                                       false, backendFormat, true);
+    if (!backendTexture.isValid()) {
+        return nullptr;
+    }
+    SkASSERT(deleteImageProc);
+
+    SkColorType colorType =
+            GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(bufferDesc.format);
+
+    backendTexture.fConfig = context->priv().caps()->getConfigFromBackendFormat(backendFormat,
+                                                                                colorType);
+
+    GrProxyProvider* proxyProvider = context->priv().proxyProvider();
+    if (!proxyProvider) {
+        deleteImageProc(deleteImageCtx);
+        return nullptr;
+    }
+
+    sk_sp<GrTextureProxy> proxy =
+            proxyProvider->wrapBackendTexture(backendTexture, surfaceOrigin,
+                                              kBorrow_GrWrapOwnership, GrWrapCacheable::kNo,
+                                              kRW_GrIOType, deleteImageProc, deleteImageCtx);
+    if (!proxy) {
+        deleteImageProc(deleteImageCtx);
+        return nullptr;
+    }
+
+    sk_sp<SkColorSpace> cs = pixmap.refColorSpace();
+    SkAlphaType at =  pixmap.alphaType();
+
+    sk_sp<SkImage> image = sk_make_sp<SkImage_Gpu>(sk_ref_sp(context), kNeedNewImageUniqueID, at,
+                                                   proxy, cs);
+    if (!image) {
+        return nullptr;
+    }
+
+    GrDrawingManager* drawingManager = context->priv().drawingManager();
+    if (!drawingManager) {
+        return nullptr;
+    }
+
+    sk_sp<GrTextureContext> texContext = drawingManager->makeTextureContext(proxy, cs);
+    if (!texContext) {
+        return nullptr;
+    }
+
+    SkImageInfo srcInfo = SkImageInfo::Make(bufferDesc.width, bufferDesc.height, colorType, at,
+                                            std::move(cs));
+    texContext->writePixels(srcInfo, pixmap.addr(0, 0), pixmap.rowBytes(), 0, 0);
+
+    drawingManager->flush(proxy.get(), SkSurface::BackendSurfaceAccess::kNoAccess,
+                          kSyncCpu_GrFlushFlag, 0, nullptr);
+
+    return image;
+}
 #endif
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -605,7 +698,7 @@
     if (!image->unique() || !texture->surfacePriv().hasUniqueRef() ||
         texture->resourcePriv().refsWrappedObjects()) {
         // onMakeSubset will always copy the image.
-        image = as_IB(image)->onMakeSubset(image->bounds());
+        image = as_IB(image)->onMakeSubset(ctx, image->bounds());
         if (!image) {
             return false;
         }
diff --git a/src/image/SkImage_Gpu.h b/src/image/SkImage_Gpu.h
index 77ca872..f381f22 100644
--- a/src/image/SkImage_Gpu.h
+++ b/src/image/SkImage_Gpu.h
@@ -26,18 +26,17 @@
                 sk_sp<SkColorSpace>);
     ~SkImage_Gpu() override;
 
-    SkImageInfo onImageInfo() const override;
-
     GrTextureProxy* peekProxy() const override {
         return fProxy.get();
     }
-    sk_sp<GrTextureProxy> asTextureProxyRef() const override {
+    sk_sp<GrTextureProxy> asTextureProxyRef(GrRecordingContext*) const override {
         return fProxy;
     }
 
-    virtual bool onIsTextureBacked() const override { return SkToBool(fProxy.get()); }
+    bool onIsTextureBacked() const override { return SkToBool(fProxy.get()); }
 
-    sk_sp<SkImage> onMakeColorTypeAndColorSpace(SkColorType, sk_sp<SkColorSpace>) const final;
+    sk_sp<SkImage> onMakeColorTypeAndColorSpace(GrRecordingContext*,
+                                                SkColorType, sk_sp<SkColorSpace>) const final;
 
     /**
      * This is the implementation of SkDeferredDisplayListRecorder::makePromiseImage.
@@ -55,7 +54,7 @@
                                              PromiseImageTextureReleaseProc textureReleaseProc,
                                              PromiseImageTextureDoneProc textureDoneProc,
                                              PromiseImageTextureContext textureContext,
-                                             DelayReleaseCallback delayReleaseCallback);
+                                             PromiseImageApiVersion);
 
     static sk_sp<SkImage> ConvertYUVATexturesToRGB(GrContext*, SkYUVColorSpace yuvColorSpace,
                                                    const GrBackendTexture yuvaTextures[],
diff --git a/src/image/SkImage_GpuBase.cpp b/src/image/SkImage_GpuBase.cpp
index 35d5a2e..4679a73 100644
--- a/src/image/SkImage_GpuBase.cpp
+++ b/src/image/SkImage_GpuBase.cpp
@@ -10,6 +10,8 @@
 #include "GrClip.h"
 #include "GrContext.h"
 #include "GrContextPriv.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrTexture.h"
 #include "GrTextureAdjuster.h"
@@ -21,11 +23,9 @@
 #include "effects/GrYUVtoRGBEffect.h"
 
 SkImage_GpuBase::SkImage_GpuBase(sk_sp<GrContext> context, int width, int height, uint32_t uniqueID,
-                                 SkAlphaType at, sk_sp<SkColorSpace> cs)
-        : INHERITED(width, height, uniqueID)
-        , fContext(std::move(context))
-        , fAlphaType(at)
-        , fColorSpace(std::move(cs)) {}
+                                 SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs)
+        : INHERITED(SkImageInfo::Make(width, height, ct, at, std::move(cs)), uniqueID)
+        , fContext(std::move(context)) {}
 
 SkImage_GpuBase::~SkImage_GpuBase() {}
 
@@ -33,7 +33,7 @@
 
 #if GR_TEST_UTILS
 void SkImage_GpuBase::resetContext(sk_sp<GrContext> newContext) {
-    SkASSERT(fContext->priv().contextID() == newContext->priv().contextID());
+    SkASSERT(fContext->priv().matches(newContext.get()));
     fContext = newContext;
 }
 #endif
@@ -60,12 +60,9 @@
 
 //////////////////////////////////////////////////////////////////////////////////////////////////
 
-uint32_t SkImage_GpuBase::contextID() const {
-    return fContext->priv().contextID();
-}
-
 bool SkImage_GpuBase::getROPixels(SkBitmap* dst, CachingHint chint) const {
-    if (!fContext->priv().resourceProvider()) {
+    auto direct = fContext->priv().asDirectContext();
+    if (!direct) {
         // DDL TODO: buffer up the readback so it occurs when the DDL is drawn?
         return false;
     }
@@ -80,19 +77,18 @@
     SkBitmapCache::RecPtr rec = nullptr;
     SkPixmap pmap;
     if (kAllow_CachingHint == chint) {
-        rec = SkBitmapCache::Alloc(desc, this->onImageInfo(), &pmap);
+        rec = SkBitmapCache::Alloc(desc, this->imageInfo(), &pmap);
         if (!rec) {
             return false;
         }
     } else {
-        if (!dst->tryAllocPixels(this->onImageInfo()) || !dst->peekPixels(&pmap)) {
+        if (!dst->tryAllocPixels(this->imageInfo()) || !dst->peekPixels(&pmap)) {
             return false;
         }
     }
 
-    sk_sp<GrSurfaceContext> sContext = fContext->priv().makeWrappedSurfaceContext(
-        this->asTextureProxyRef(),
-        fColorSpace);
+    sk_sp<GrSurfaceContext> sContext = direct->priv().makeWrappedSurfaceContext(
+            this->asTextureProxyRef(direct), this->refColorSpace());
     if (!sContext) {
         return false;
     }
@@ -108,8 +104,13 @@
     return true;
 }
 
-sk_sp<SkImage> SkImage_GpuBase::onMakeSubset(const SkIRect& subset) const {
-    sk_sp<GrSurfaceProxy> proxy = this->asTextureProxyRef();
+sk_sp<SkImage> SkImage_GpuBase::onMakeSubset(GrRecordingContext* context,
+                                             const SkIRect& subset) const {
+    if (!context || !fContext->priv().matches(context)) {
+        return nullptr;
+    }
+
+    sk_sp<GrSurfaceProxy> proxy = this->asTextureProxyRef(context);
 
     GrSurfaceDesc desc;
     desc.fWidth = subset.width();
@@ -122,7 +123,7 @@
     }
 
     // TODO: Should this inherit our proxy's budgeted status?
-    sk_sp<GrSurfaceContext> sContext(fContext->priv().makeDeferredSurfaceContext(
+    sk_sp<GrSurfaceContext> sContext(context->priv().makeDeferredSurfaceContext(
             format, desc, proxy->origin(), GrMipMapped::kNo, SkBackingFit::kExact,
             proxy->isBudgeted()));
     if (!sContext) {
@@ -134,7 +135,7 @@
     }
 
     // MDB: this call is okay bc we know 'sContext' was kExact
-    return sk_make_sp<SkImage_Gpu>(fContext, kNeedNewImageUniqueID, fAlphaType,
+    return sk_make_sp<SkImage_Gpu>(fContext, kNeedNewImageUniqueID, this->alphaType(),
                                    sContext->asTextureProxyRef(), this->refColorSpace());
 }
 
@@ -162,12 +163,13 @@
 
 bool SkImage_GpuBase::onReadPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB,
                                    int srcX, int srcY, CachingHint) const {
-    if (!fContext->priv().resourceProvider()) {
+    auto direct = fContext->priv().asDirectContext();
+    if (!direct) {
         // DDL TODO: buffer up the readback so it occurs when the DDL is drawn?
         return false;
     }
 
-    if (!SkImageInfoValidConversion(dstInfo, this->onImageInfo())) {
+    if (!SkImageInfoValidConversion(dstInfo, this->imageInfo())) {
         return false;
     }
 
@@ -179,13 +181,14 @@
     // TODO: this seems to duplicate code in GrTextureContext::onReadPixels and
     // GrRenderTargetContext::onReadPixels
     uint32_t flags = 0;
-    if (kUnpremul_SkAlphaType == rec.fInfo.alphaType() && kPremul_SkAlphaType == fAlphaType) {
+    if (kUnpremul_SkAlphaType == rec.fInfo.alphaType() &&
+        kPremul_SkAlphaType == this->alphaType()) {
         // let the GPU perform this transformation for us
         flags = GrContextPriv::kUnpremul_PixelOpsFlag;
     }
 
-    sk_sp<GrSurfaceContext> sContext = fContext->priv().makeWrappedSurfaceContext(
-        this->asTextureProxyRef(), this->refColorSpace());
+    sk_sp<GrSurfaceContext> sContext = direct->priv().makeWrappedSurfaceContext(
+        this->asTextureProxyRef(direct), this->refColorSpace());
     if (!sContext) {
         return false;
     }
@@ -202,44 +205,49 @@
     //
     // Should this be handled by Ganesh? todo:?
     //
-    if (kPremul_SkAlphaType == rec.fInfo.alphaType() && kUnpremul_SkAlphaType == fAlphaType) {
+    if (kPremul_SkAlphaType == rec.fInfo.alphaType() &&
+        kUnpremul_SkAlphaType == this->alphaType()) {
         apply_premul(rec.fInfo, rec.fPixels, rec.fRowBytes);
     }
     return true;
 }
 
-sk_sp<GrTextureProxy> SkImage_GpuBase::asTextureProxyRef(GrContext* context,
+sk_sp<GrTextureProxy> SkImage_GpuBase::asTextureProxyRef(GrRecordingContext* context,
                                                          const GrSamplerState& params,
                                                          SkScalar scaleAdjust[2]) const {
-    if (context->priv().contextID() != fContext->priv().contextID()) {
+    if (!context || !fContext->priv().matches(context)) {
         SkASSERT(0);
         return nullptr;
     }
 
-    GrTextureAdjuster adjuster(fContext.get(), this->asTextureProxyRef(), fAlphaType,
-                               this->uniqueID(), fColorSpace.get());
+    GrTextureAdjuster adjuster(fContext.get(), this->asTextureProxyRef(context), this->alphaType(),
+                               this->uniqueID(), this->colorSpace());
     return adjuster.refTextureProxyForParams(params, scaleAdjust);
 }
 
 GrBackendTexture SkImage_GpuBase::onGetBackendTexture(bool flushPendingGrContextIO,
                                                       GrSurfaceOrigin* origin) const {
-    sk_sp<GrTextureProxy> proxy = this->asTextureProxyRef();
-    SkASSERT(proxy);
-
-    if (!fContext->priv().resourceProvider() && !proxy->isInstantiated()) {
+    auto direct = fContext->priv().asDirectContext();
+    if (!direct) {
         // This image was created with a DDL context and cannot be instantiated.
-        return GrBackendTexture();
-}
-
-    if (!proxy->instantiate(fContext->priv().resourceProvider())) {
         return GrBackendTexture(); // invalid
     }
 
-    GrTexture* texture = proxy->peekTexture();
+    sk_sp<GrTextureProxy> proxy = this->asTextureProxyRef(direct);
+    SkASSERT(proxy);
 
+    if (!proxy->isInstantiated()) {
+        auto resourceProvider = direct->priv().resourceProvider();
+
+        if (!proxy->instantiate(resourceProvider)) {
+            return GrBackendTexture(); // invalid
+        }
+    }
+
+    GrTexture* texture = proxy->peekTexture();
     if (texture) {
         if (flushPendingGrContextIO) {
-            fContext->priv().prepareSurfaceForExternalIO(proxy.get());
+            direct->priv().prepareSurfaceForExternalIO(proxy.get());
         }
         if (origin) {
             *origin = proxy->origin();
@@ -251,30 +259,33 @@
 
 GrTexture* SkImage_GpuBase::onGetTexture() const {
     GrTextureProxy* proxy = this->peekProxy();
-    if (!proxy) {
-        return nullptr;
+    if (proxy && proxy->isInstantiated()) {
+        return proxy->peekTexture();
     }
 
-    sk_sp<GrTextureProxy> proxyRef = this->asTextureProxyRef();
-    if (!fContext->priv().resourceProvider() && !proxyRef->isInstantiated()) {
+    auto direct = fContext->priv().asDirectContext();
+    if (!direct) {
         // This image was created with a DDL context and cannot be instantiated.
         return nullptr;
     }
 
-    if (!proxy->instantiate(fContext->priv().resourceProvider())) {
+    sk_sp<GrTextureProxy> proxyRef = this->asTextureProxyRef(direct);
+    SkASSERT(proxyRef && !proxyRef->isInstantiated());
+
+    if (!proxyRef->instantiate(direct->priv().resourceProvider())) {
         return nullptr;
     }
 
-    return proxy->peekTexture();
+    return proxyRef->peekTexture();
 }
 
 bool SkImage_GpuBase::onIsValid(GrContext* context) const {
     // The base class has already checked that context isn't abandoned (if it's not nullptr)
-    if (fContext->abandoned()) {
+    if (fContext->priv().abandoned()) {
         return false;
     }
 
-    if (context && context != fContext.get()) {
+    if (context && !fContext->priv().matches(context)) {
         return false;
     }
 
@@ -362,10 +373,6 @@
     paint.addColorFragmentProcessor(std::move(fp));
 
     renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), rect);
-
-    // DDL TODO: in the promise image version we must not flush here
-    ctx->priv().flushSurfaceWrites(renderTargetContext->asSurfaceProxy());
-
     return true;
 }
 
@@ -376,7 +383,7 @@
         PromiseImageTextureReleaseProc releaseProc,
         PromiseImageTextureDoneProc doneProc,
         PromiseImageTextureContext textureContext,
-        DelayReleaseCallback delayReleaseCallback) {
+        PromiseImageApiVersion version) {
     SkASSERT(context);
     SkASSERT(width > 0 && height > 0);
     SkASSERT(doneProc);
@@ -398,19 +405,16 @@
     /**
      * This class is the lazy instantiation callback for promise images. It manages calling the
      * client's Fulfill, Release, and Done procs. It attempts to reuse a GrTexture instance in
-     * cases where the client provides the same SkPromiseImageTexture for successive Fulfill calls.
-     * The created GrTexture is given a key based on a unique ID associated with the
-     * SkPromiseImageTexture. When the texture enters "idle" state (meaning it is not being used by
-     * the GPU and is at rest in the resource cache) the client's Release proc is called
-     * using GrTexture's idle proc mechanism. If the same SkPromiseImageTexture is provided for
-     * another fulfill we find the cached GrTexture. If the proxy, and therefore this object,
-     * is destroyed, we invalidate the GrTexture's key. Also if the client overwrites or
-     * destroys their SkPromiseImageTexture we invalidate the key.
+     * cases where the client provides the same SkPromiseImageTexture as Fulfill results for
+     * multiple SkImages. The created GrTexture is given a key based on a unique ID associated with
+     * the SkPromiseImageTexture.
      *
-     * Currently a GrTexture is only reused for a given SkPromiseImageTexture if the
-     * SkPromiseImageTexture is reused in Fulfill for the same promise SkImage. However, we'd
-     * like to relax that so that a SkPromiseImageTexture can be reused with different promise
-     * SkImages that will reuse a single GrTexture.
+     * The GrTexutre idle proc mechanism is used to call the Release and Done procs. We use this
+     * instead of the GrSurface release proc because the GrTexture is cached and therefore may
+     * outlive the proxy into which this callback is installed.
+     *
+     * A key invalidation message is installed on the SkPromiseImageTexture so that the GrTexture
+     * is deleted once it can no longer be used to instantiate a proxy.
      */
     class PromiseLazyInstantiateCallback {
     public:
@@ -418,193 +422,114 @@
                                        PromiseImageTextureReleaseProc releaseProc,
                                        PromiseImageTextureDoneProc doneProc,
                                        PromiseImageTextureContext context,
-                                       DelayReleaseCallback delayReleaseCallback,
-                                       GrPixelConfig config)
+                                       GrPixelConfig config,
+                                       PromiseImageApiVersion version)
                 : fFulfillProc(fulfillProc)
+                , fReleaseProc(releaseProc)
                 , fConfig(config)
-                , fDelayReleaseCallback(delayReleaseCallback) {
-            auto doneHelper = sk_make_sp<GrReleaseProcHelper>(doneProc, context);
-            fReleaseContext = sk_make_sp<IdleContext::PromiseImageReleaseContext>(
-                    releaseProc, context, std::move(doneHelper));
+                , fVersion(version) {
+            fDoneCallback = sk_make_sp<GrRefCntedCallback>(doneProc, context);
+        }
+        PromiseLazyInstantiateCallback(PromiseLazyInstantiateCallback&&) = default;
+        PromiseLazyInstantiateCallback(const PromiseLazyInstantiateCallback&) {
+            // Because we get wrapped in std::function we must be copyable. But we should never
+            // be copied.
+            SkASSERT(false);
+        }
+        PromiseLazyInstantiateCallback& operator=(PromiseLazyInstantiateCallback&&) = default;
+        PromiseLazyInstantiateCallback& operator=(const PromiseLazyInstantiateCallback&) {
+            SkASSERT(false);
+            return *this;
         }
 
-        ~PromiseLazyInstantiateCallback() = default;
+        ~PromiseLazyInstantiateCallback() {
+            // Our destructor can run on any thread. We trigger the unref of fTexture by message.
+            if (fTexture) {
+                SkMessageBus<GrGpuResourceFreedMessage>::Post({fTexture, fTextureContextID});
+            }
+        }
 
-        sk_sp<GrSurface> operator()(GrResourceProvider* resourceProvider) {
-            if (!resourceProvider) {
-                if (fDelayedReleaseTexture) {
-                    fDelayedReleaseTexture.reset();
-                }
-                return nullptr;
-            }
-            if (fDelayedReleaseTexture) {
-                return fDelayedReleaseTexture;
-            }
+        GrSurfaceProxy::LazyInstantiationResult operator()(GrResourceProvider* resourceProvider) {
+            // We use the unique key in a way that is unrelated to the SkImage-based key that the
+            // proxy may receive, hence kUnsynced.
+            static constexpr auto kKeySyncMode =
+                    GrSurfaceProxy::LazyInstantiationKeyMode::kUnsynced;
 
-            sk_sp<GrTexture> cachedTexture;
-            SkASSERT(fLastFulfilledKey.isValid() == (fLastFulfillID > 0));
-            if (fLastFulfilledKey.isValid()) {
-                auto surf = resourceProvider->findByUniqueKey<GrSurface>(fLastFulfilledKey);
-                if (surf) {
-                    cachedTexture = sk_ref_sp(surf->asTexture());
-                    SkASSERT(cachedTexture);
-                }
+            // Our proxy is getting instantiated for the second+ time. We are only allowed to call
+            // Fulfill once. So return our cached result.
+            if (fTexture) {
+                return {sk_ref_sp(fTexture), kKeySyncMode};
+            } else if (fConfig == kUnknown_GrPixelConfig) {
+                // We've already called fulfill and it failed. Our contract says that we should only
+                // call each callback once.
+                return {};
             }
-            // If the release callback hasn't been called already by releasing the GrTexture
-            // then we can be sure that won't happen so long as we have a ref to the texture.
-            if (cachedTexture && !fReleaseContext->isReleased()) {
-                return std::move(cachedTexture);
-            }
-            GrBackendTexture backendTexture;
-            sk_sp<SkPromiseImageTexture> promiseTexture =
-                    fFulfillProc(fReleaseContext->textureContext());
-            fReleaseContext->notifyWasFulfilled();
+            SkASSERT(fDoneCallback);
+            PromiseImageTextureContext textureContext = fDoneCallback->context();
+            sk_sp<SkPromiseImageTexture> promiseTexture = fFulfillProc(textureContext);
+            // From here on out our contract is that the release proc must be called, even if
+            // the return from fulfill was invalid or we fail for some other reason.
+            auto releaseCallback = sk_make_sp<GrRefCntedCallback>(fReleaseProc, textureContext);
             if (!promiseTexture) {
-                fReleaseContext->release();
-                return sk_sp<GrTexture>();
+                // This records that we have failed.
+                fConfig = kUnknown_GrPixelConfig;
+                return {};
             }
-            bool same = promiseTexture->uniqueID() == fLastFulfillID;
-            SkASSERT(!same || fLastFulfilledKey.isValid());
-            if (same && cachedTexture) {
-                SkASSERT(fReleaseContext->unique());
-                this->addToIdleContext(cachedTexture.get());
-                return std::move(cachedTexture);
-            } else if (cachedTexture) {
-                cachedTexture->resourcePriv().removeUniqueKey();
-            }
-            fLastFulfillID = promiseTexture->uniqueID();
 
-            backendTexture = promiseTexture->backendTexture();
+            auto backendTexture = promiseTexture->backendTexture();
             backendTexture.fConfig = fConfig;
             if (!backendTexture.isValid()) {
-                // Even though the GrBackendTexture is not valid, we must call the release
-                // proc to keep our contract of always calling Fulfill and Release in pairs.
-                fReleaseContext->release();
-                return sk_sp<GrTexture>();
+                return {};
             }
 
             sk_sp<GrTexture> tex;
             static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
-            GrUniqueKey::Builder builder(&fLastFulfilledKey, kDomain, 2, "promise");
+            GrUniqueKey key;
+            GrUniqueKey::Builder builder(&key, kDomain, 2, "promise");
             builder[0] = promiseTexture->uniqueID();
             builder[1] = fConfig;
             builder.finish();
             // A texture with this key may already exist from a different instance of this lazy
             // callback. This could happen if the client fulfills a promise image with a texture
             // that was previously used to fulfill a different promise image.
-            if (auto surf = resourceProvider->findByUniqueKey<GrSurface>(fLastFulfilledKey)) {
+            if (auto surf = resourceProvider->findByUniqueKey<GrSurface>(key)) {
                 tex = sk_ref_sp(surf->asTexture());
                 SkASSERT(tex);
             } else {
                 if ((tex = resourceProvider->wrapBackendTexture(
                              backendTexture, kBorrow_GrWrapOwnership, GrWrapCacheable::kYes,
                              kRead_GrIOType))) {
-                    tex->resourcePriv().setUniqueKey(fLastFulfilledKey);
+                    tex->resourcePriv().setUniqueKey(key);
                 } else {
-                    // Even though we failed to wrap the backend texture, we must call the release
-                    // proc to keep our contract of always calling Fulfill and Release in pairs.
-                    fReleaseContext->release();
-                    return sk_sp<GrTexture>();
+                    return {};
                 }
             }
-            this->addToIdleContext(tex.get());
-            if (fDelayReleaseCallback == DelayReleaseCallback::kYes) {
-                fDelayedReleaseTexture = tex;
-            }
-            tex->resourcePriv().setUniqueKey(fLastFulfilledKey);
-            SkASSERT(fContextID == SK_InvalidUniqueID ||
-                     fContextID == tex->getContext()->priv().contextID());
-            fContextID = tex->getContext()->priv().contextID();
-            promiseTexture->addKeyToInvalidate(fContextID, fLastFulfilledKey);
-            return std::move(tex);
+            auto releaseIdleState = fVersion == PromiseImageApiVersion::kLegacy
+                                            ? GrTexture::IdleState::kFinished
+                                            : GrTexture::IdleState::kFlushed;
+            tex->addIdleProc(std::move(releaseCallback), releaseIdleState);
+            tex->addIdleProc(std::move(fDoneCallback), GrTexture::IdleState::kFinished);
+            promiseTexture->addKeyToInvalidate(tex->getContext()->priv().contextID(), key);
+            fTexture = tex.get();
+            // We need to hold on to the GrTexture in case our proxy gets reinstantiated. However,
+            // we can't unref in our destructor because we may be on another thread then. So we
+            // let the cache know it is waiting on an unref message. We will send that message from
+            // our destructor.
+            GrContext* context = fTexture->getContext();
+            context->priv().getResourceCache()->insertDelayedResourceUnref(fTexture);
+            fTextureContextID = context->priv().contextID();
+            return {std::move(tex), kKeySyncMode};
         }
 
     private:
-        // The GrTexture's idle callback mechanism is used to call the client's Release proc via
-        // this class. This also owns a ref counted helper that calls the client's ReleaseProc when
-        // the ref count reaches zero. The callback and any Fulfilled but un-Released texture share
-        // ownership of the IdleContext. Thus, the IdleContext is destroyed and calls the Done proc
-        // after the last fulfilled texture goes idle and calls the Release proc or the proxy's
-        // destructor destroys the lazy callback, whichever comes last.
-        class IdleContext {
-        public:
-            class PromiseImageReleaseContext;
-
-            IdleContext() = default;
-
-            ~IdleContext() = default;
-
-            void addImageReleaseContext(sk_sp<PromiseImageReleaseContext> context) {
-                fReleaseContexts.addToHead(std::move(context));
-            }
-
-            static void IdleProc(void* context) {
-                IdleContext* idleContext = static_cast<IdleContext*>(context);
-                for (ReleaseContextList::Iter iter = idleContext->fReleaseContexts.headIter();
-                     iter.get();
-                     iter.next()) {
-                    (*iter.get())->release();
-                }
-                idleContext->fReleaseContexts.reset();
-                delete idleContext;
-            }
-
-            class PromiseImageReleaseContext : public SkNVRefCnt<PromiseImageReleaseContext> {
-            public:
-                PromiseImageReleaseContext(PromiseImageTextureReleaseProc releaseProc,
-                                           PromiseImageTextureContext textureContext,
-                                           sk_sp<GrReleaseProcHelper> doneHelper)
-                        : fReleaseProc(releaseProc)
-                        , fTextureContext(textureContext)
-                        , fDoneHelper(std::move(doneHelper)) {}
-
-                ~PromiseImageReleaseContext() { SkASSERT(fIsReleased); }
-
-                void release() {
-                    SkASSERT(!fIsReleased);
-                    fReleaseProc(fTextureContext);
-                    fIsReleased = true;
-                }
-
-                void notifyWasFulfilled() { fIsReleased = false; }
-                bool isReleased() const { return fIsReleased; }
-
-                PromiseImageTextureContext textureContext() const { return fTextureContext; }
-
-            private:
-                PromiseImageTextureReleaseProc fReleaseProc;
-                PromiseImageTextureContext fTextureContext;
-                sk_sp<GrReleaseProcHelper> fDoneHelper;
-                bool fIsReleased = true;
-            };
-
-        private:
-            using ReleaseContextList = SkTLList<sk_sp<PromiseImageReleaseContext>, 4>;
-            ReleaseContextList fReleaseContexts;
-        };
-
-        void addToIdleContext(GrTexture* texture) {
-            SkASSERT(!fReleaseContext->isReleased());
-            IdleContext* idleContext = static_cast<IdleContext*>(texture->idleContext());
-            if (!idleContext) {
-                idleContext = new IdleContext();
-                texture->setIdleProc(IdleContext::IdleProc, idleContext);
-            }
-            idleContext->addImageReleaseContext(fReleaseContext);
-        }
-
-        sk_sp<IdleContext::PromiseImageReleaseContext> fReleaseContext;
-        sk_sp<GrTexture> fDelayedReleaseTexture;
         PromiseImageTextureFulfillProc fFulfillProc;
+        PromiseImageTextureReleaseProc fReleaseProc;
+        sk_sp<GrRefCntedCallback> fDoneCallback;
+        GrTexture* fTexture = nullptr;
+        uint32_t fTextureContextID = SK_InvalidUniqueID;
         GrPixelConfig fConfig;
-        DelayReleaseCallback fDelayReleaseCallback;
-
-        // ID of the last SkPromiseImageTexture given to us by the client.
-        uint32_t fLastFulfillID = 0;
-        // ID of the GrContext that we are interacting with.
-        uint32_t fContextID = SK_InvalidUniqueID;
-        GrUniqueKey fLastFulfilledKey;
-    } callback(fulfillProc, releaseProc, doneProc, textureContext, delayReleaseCallback, config);
+        PromiseImageApiVersion fVersion;
+    } callback(fulfillProc, releaseProc, doneProc, textureContext, config, version);
 
     GrProxyProvider* proxyProvider = context->priv().proxyProvider();
 
diff --git a/src/image/SkImage_GpuBase.h b/src/image/SkImage_GpuBase.h
index 871fa59..d5a3665 100644
--- a/src/image/SkImage_GpuBase.h
+++ b/src/image/SkImage_GpuBase.h
@@ -20,30 +20,30 @@
 
 class SkImage_GpuBase : public SkImage_Base {
 public:
-    SkImage_GpuBase(sk_sp<GrContext>, int width, int height, uint32_t uniqueID, SkAlphaType,
-                    sk_sp<SkColorSpace>);
+    SkImage_GpuBase(sk_sp<GrContext>, int width, int height, uint32_t uniqueID, SkColorType,
+                    SkAlphaType, sk_sp<SkColorSpace>);
     ~SkImage_GpuBase() override;
 
     GrContext* context() const final { return fContext.get(); }
-    uint32_t contextID() const final;
 
     bool getROPixels(SkBitmap*, CachingHint) const final;
-    sk_sp<SkImage> onMakeSubset(const SkIRect& subset) const final;
+    sk_sp<SkImage> onMakeSubset(GrRecordingContext*, const SkIRect& subset) const final;
 
     bool onReadPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB,
                       int srcX, int srcY, CachingHint) const override;
 
-    sk_sp<GrTextureProxy> asTextureProxyRef() const override {
+    sk_sp<GrTextureProxy> asTextureProxyRef(GrRecordingContext* context) const override {
         // we shouldn't end up calling this
         SkASSERT(false);
-        return this->INHERITED::asTextureProxyRef();
+        return this->INHERITED::asTextureProxyRef(context);
     }
-    sk_sp<GrTextureProxy> asTextureProxyRef(GrContext*, const GrSamplerState&,
+    sk_sp<GrTextureProxy> asTextureProxyRef(GrRecordingContext*, const GrSamplerState&,
                                             SkScalar scaleAdjust[2]) const final;
 
-    sk_sp<GrTextureProxy> refPinnedTextureProxy(uint32_t* uniqueID) const final {
+    sk_sp<GrTextureProxy> refPinnedTextureProxy(GrRecordingContext* context,
+                                                uint32_t* uniqueID) const final {
         *uniqueID = this->uniqueID();
-        return this->asTextureProxyRef();
+        return this->asTextureProxyRef(context);
     }
 
     GrBackendTexture onGetBackendTexture(bool flushPendingGrContextIO,
@@ -76,9 +76,9 @@
     using PromiseImageTextureReleaseProc =
             SkDeferredDisplayListRecorder::PromiseImageTextureReleaseProc;
     using PromiseImageTextureDoneProc = SkDeferredDisplayListRecorder::PromiseImageTextureDoneProc;
-    using DelayReleaseCallback = SkDeferredDisplayListRecorder::DelayReleaseCallback;
 
 protected:
+    using PromiseImageApiVersion = SkDeferredDisplayListRecorder::PromiseImageApiVersion;
     // Helper for making a lazy proxy for a promise image. The PromiseDoneProc we be called,
     // if not null, immediately if this function fails. Othwerwise, it is installed in the
     // proxy along with the TextureFulfillProc and TextureReleaseProc. PromiseDoneProc must not
@@ -86,7 +86,7 @@
     static sk_sp<GrTextureProxy> MakePromiseImageLazyProxy(
             GrContext*, int width, int height, GrSurfaceOrigin, GrPixelConfig, GrBackendFormat,
             GrMipMapped, PromiseImageTextureFulfillProc, PromiseImageTextureReleaseProc,
-            PromiseImageTextureDoneProc, PromiseImageTextureContext, DelayReleaseCallback);
+            PromiseImageTextureDoneProc, PromiseImageTextureContext, PromiseImageApiVersion);
 
     static bool RenderYUVAToRGBA(GrContext* ctx, GrRenderTargetContext* renderTargetContext,
                                  const SkRect& rect, SkYUVColorSpace yuvColorSpace,
@@ -94,9 +94,7 @@
                                  const sk_sp<GrTextureProxy> proxies[4],
                                  const SkYUVAIndex yuvaIndices[4]);
 
-    sk_sp<GrContext>      fContext;
-    const SkAlphaType     fAlphaType;  // alpha type for final image
-    sk_sp<SkColorSpace>   fColorSpace; // color space for final image
+    sk_sp<GrContext> fContext;
 
 private:
     typedef SkImage_Base INHERITED;
diff --git a/src/image/SkImage_GpuYUVA.cpp b/src/image/SkImage_GpuYUVA.cpp
index c4898d1..17766b0 100644
--- a/src/image/SkImage_GpuYUVA.cpp
+++ b/src/image/SkImage_GpuYUVA.cpp
@@ -13,6 +13,8 @@
 #include "GrContext.h"
 #include "GrContextPriv.h"
 #include "GrGpu.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrTexture.h"
 #include "GrTextureProducer.h"
@@ -25,15 +27,17 @@
 #include "SkYUVASizeInfo.h"
 #include "effects/GrYUVtoRGBEffect.h"
 
+static constexpr auto kAssumedColorType = kRGBA_8888_SkColorType;
+
 SkImage_GpuYUVA::SkImage_GpuYUVA(sk_sp<GrContext> context, int width, int height, uint32_t uniqueID,
                                  SkYUVColorSpace colorSpace, sk_sp<GrTextureProxy> proxies[],
                                  int numProxies, const SkYUVAIndex yuvaIndices[4],
                                  GrSurfaceOrigin origin, sk_sp<SkColorSpace> imageColorSpace)
-        : INHERITED(std::move(context), width, height, uniqueID,
+        : INHERITED(std::move(context), width, height, uniqueID, kAssumedColorType,
                     // If an alpha channel is present we always switch to kPremul. This is because,
                     // although the planar data is always un-premul, the final interleaved RGB image
                     // is/would-be premul.
-                    GetAlphaTypeFromYUVAIndices(yuvaIndices), imageColorSpace)
+                    GetAlphaTypeFromYUVAIndices(yuvaIndices), std::move(imageColorSpace))
         , fNumProxies(numProxies)
         , fYUVColorSpace(colorSpace)
         , fOrigin(origin) {
@@ -50,16 +54,19 @@
 
 // For onMakeColorSpace()
 SkImage_GpuYUVA::SkImage_GpuYUVA(const SkImage_GpuYUVA* image, sk_sp<SkColorSpace> targetCS)
-    : INHERITED(image->fContext, image->width(), image->height(), kNeedNewImageUniqueID,
-                // If an alpha channel is present we always switch to kPremul. This is because,
-                // although the planar data is always un-premul, the final interleaved RGB image
-                // is/would-be premul.
-                GetAlphaTypeFromYUVAIndices(image->fYUVAIndices), image->fColorSpace)
-    , fNumProxies(image->fNumProxies)
-    , fYUVColorSpace(image->fYUVColorSpace)
-    , fOrigin(image->fOrigin)
-    , fTargetColorSpace(targetCS) {
-        // The caller should have done this work, just verifying
+        : INHERITED(image->fContext, image->width(), image->height(), kNeedNewImageUniqueID,
+                    kAssumedColorType,
+                    // If an alpha channel is present we always switch to kPremul. This is because,
+                    // although the planar data is always un-premul, the final interleaved RGB image
+                    // is/would-be premul.
+                    GetAlphaTypeFromYUVAIndices(image->fYUVAIndices), std::move(targetCS))
+        , fNumProxies(image->fNumProxies)
+        , fYUVColorSpace(image->fYUVColorSpace)
+        , fOrigin(image->fOrigin)
+        // Since null fFromColorSpace means no GrColorSpaceXform, we turn a null
+        // image->refColorSpace() into an explicit SRGB.
+        , fFromColorSpace(image->colorSpace() ? image->refColorSpace() : SkColorSpace::MakeSRGB()) {
+    // The caller should have done this work, just verifying
     SkDEBUGCODE(int textureCount;)
         SkASSERT(SkYUVAIndex::AreValidIndices(image->fYUVAIndices, &textureCount));
     SkASSERT(textureCount == fNumProxies);
@@ -72,13 +79,11 @@
 
 SkImage_GpuYUVA::~SkImage_GpuYUVA() {}
 
-SkImageInfo SkImage_GpuYUVA::onImageInfo() const {
-    // Note: this is the imageInfo for the flattened image, not the YUV planes
-    return SkImageInfo::Make(this->width(), this->height(), kRGBA_8888_SkColorType,
-                             fAlphaType, fTargetColorSpace ? fTargetColorSpace : fColorSpace);
-}
+bool SkImage_GpuYUVA::setupMipmapsForPlanes(GrRecordingContext* context) const {
+    if (!context || !fContext->priv().matches(context)) {
+        return false;
+    }
 
-bool SkImage_GpuYUVA::setupMipmapsForPlanes() const {
     for (int i = 0; i < fNumProxies; ++i) {
         GrTextureProducer::CopyParams copyParams;
         int mipCount = SkMipMap::ComputeLevelCount(fProxies[i]->width(), fProxies[i]->height());
@@ -86,7 +91,7 @@
                                                     fProxies[i].get(),
                                                     GrSamplerState::Filter::kMipMap,
                                                     &copyParams)) {
-            auto mippedProxy = GrCopyBaseMipMapToTextureProxy(fContext.get(), fProxies[i].get());
+            auto mippedProxy = GrCopyBaseMipMapToTextureProxy(context, fProxies[i].get());
             if (!mippedProxy) {
                 return false;
             }
@@ -98,43 +103,59 @@
 
 //////////////////////////////////////////////////////////////////////////////////////////////////
 
-sk_sp<GrTextureProxy> SkImage_GpuYUVA::asTextureProxyRef() const {
-    if (!fRGBProxy) {
-        const GrBackendFormat format =
-            fContext->priv().caps()->getBackendFormatFromColorType(kRGBA_8888_SkColorType);
+GrTextureProxy* SkImage_GpuYUVA::peekProxy() const {
+    return fRGBProxy.get();
+}
 
-        // Needs to create a render target in order to draw to it for the yuv->rgb conversion.
-        sk_sp<GrRenderTargetContext> renderTargetContext(
-            fContext->priv().makeDeferredRenderTargetContext(
-                format, SkBackingFit::kExact, this->width(), this->height(),
-                kRGBA_8888_GrPixelConfig, fColorSpace, 1, GrMipMapped::kNo, fOrigin));
-        if (!renderTargetContext) {
-            return nullptr;
-        }
-
-        auto colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(), fAlphaType,
-                                                       fTargetColorSpace.get(), fAlphaType);
-        const SkRect rect = SkRect::MakeIWH(this->width(), this->height());
-        if (!RenderYUVAToRGBA(fContext.get(), renderTargetContext.get(), rect, fYUVColorSpace,
-                              std::move(colorSpaceXform), fProxies, fYUVAIndices)) {
-            return nullptr;
-        }
-
-        fRGBProxy = renderTargetContext->asTextureProxyRef();
+sk_sp<GrTextureProxy> SkImage_GpuYUVA::asTextureProxyRef(GrRecordingContext* context) const {
+    if (fRGBProxy) {
+        return fRGBProxy;
     }
 
+    if (!context || !fContext->priv().matches(context)) {
+        return nullptr;
+    }
+
+    const GrBackendFormat format =
+        fContext->priv().caps()->getBackendFormatFromColorType(kAssumedColorType);
+
+    // Needs to create a render target in order to draw to it for the yuv->rgb conversion.
+    sk_sp<GrRenderTargetContext> renderTargetContext(
+            context->priv().makeDeferredRenderTargetContext(
+                    format, SkBackingFit::kExact, this->width(), this->height(),
+                    kRGBA_8888_GrPixelConfig, this->refColorSpace(), 1, GrMipMapped::kNo, fOrigin));
+    if (!renderTargetContext) {
+        return nullptr;
+    }
+
+    sk_sp<GrColorSpaceXform> colorSpaceXform;
+    if (fFromColorSpace) {
+        colorSpaceXform = GrColorSpaceXform::Make(fFromColorSpace.get(), this->alphaType(),
+                                                  this->colorSpace(), this->alphaType());
+    }
+    const SkRect rect = SkRect::MakeIWH(this->width(), this->height());
+    if (!RenderYUVAToRGBA(fContext.get(), renderTargetContext.get(), rect, fYUVColorSpace,
+                          std::move(colorSpaceXform), fProxies, fYUVAIndices)) {
+        return nullptr;
+    }
+
+    fRGBProxy = renderTargetContext->asTextureProxyRef();
     return fRGBProxy;
 }
 
-sk_sp<GrTextureProxy> SkImage_GpuYUVA::asMippedTextureProxyRef() const {
+sk_sp<GrTextureProxy> SkImage_GpuYUVA::asMippedTextureProxyRef(GrRecordingContext* context) const {
+    if (!context || !fContext->priv().matches(context)) {
+        return nullptr;
+    }
+
     // if invalid or already has miplevels
-    auto proxy = this->asTextureProxyRef();
+    auto proxy = this->asTextureProxyRef(context);
     if (!proxy || GrMipMapped::kYes == fRGBProxy->mipMapped()) {
         return proxy;
     }
 
     // need to generate mips for the proxy
-    if (auto mippedProxy = GrCopyBaseMipMapToTextureProxy(fContext.get(), proxy.get())) {
+    if (auto mippedProxy = GrCopyBaseMipMapToTextureProxy(context, proxy.get())) {
         fRGBProxy = mippedProxy;
         return mippedProxy;
     }
@@ -145,7 +166,8 @@
 
 //////////////////////////////////////////////////////////////////////////////////////////////////
 
-sk_sp<SkImage> SkImage_GpuYUVA::onMakeColorTypeAndColorSpace(SkColorType,
+sk_sp<SkImage> SkImage_GpuYUVA::onMakeColorTypeAndColorSpace(GrRecordingContext*,
+                                                             SkColorType,
                                                              sk_sp<SkColorSpace> targetCS) const {
     // We explicitly ignore color type changes, for now.
 
@@ -196,6 +218,10 @@
         return nullptr;
     }
 
+    if (!context->priv().caps()->mipMapSupport()) {
+        buildMips = false;
+    }
+
     // Make proxies
     GrProxyProvider* proxyProvider = context->priv().proxyProvider();
     sk_sp<GrTextureProxy> tempTextureProxies[4];
@@ -218,25 +244,10 @@
             pixmap = &resized;
         }
         // Turn the pixmap into a GrTextureProxy
-        if (buildMips) {
-            SkBitmap bmp;
-            bmp.installPixels(*pixmap);
-            tempTextureProxies[i] = proxyProvider->createMipMapProxyFromBitmap(bmp);
-        }
-        if (!tempTextureProxies[i]) {
-            if (SkImageInfoIsValid(pixmap->info())) {
-                ATRACE_ANDROID_FRAMEWORK("Upload Texture [%ux%u]",
-                                         pixmap->width(), pixmap->height());
-                // We don't need a release proc on the data in pixmap since we know we are in a
-                // GrContext that has a resource provider. Thus the createTextureProxy call will
-                // immediately upload the data.
-                sk_sp<SkImage> image = SkImage::MakeFromRaster(*pixmap, nullptr, nullptr);
-                tempTextureProxies[i] =
-                        proxyProvider->createTextureProxy(std::move(image), kNone_GrSurfaceFlags, 1,
-                                                          SkBudgeted::kYes, SkBackingFit::kExact);
-            }
-        }
-
+        SkBitmap bmp;
+        bmp.installPixels(*pixmap);
+        GrMipMapped mipMapped = buildMips ? GrMipMapped::kYes : GrMipMapped::kNo;
+        tempTextureProxies[i] = proxyProvider->createProxyFromBitmap(bmp, mipMapped);
         if (!tempTextureProxies[i]) {
             return nullptr;
         }
@@ -263,7 +274,7 @@
         PromiseImageTextureReleaseProc textureReleaseProc,
         PromiseImageTextureDoneProc promiseDoneProc,
         PromiseImageTextureContext textureContexts[],
-        DelayReleaseCallback delayReleaseCallback) {
+        PromiseImageApiVersion version) {
     int numTextures;
     bool valid = SkYUVAIndex::AreValidIndices(yuvaIndices, &numTextures);
 
@@ -288,13 +299,13 @@
         return nullptr;
     }
 
-    if (imageWidth <= 0 || imageWidth <= 0) {
+    if (imageWidth <= 0 || imageHeight <= 0) {
         return nullptr;
     }
 
     SkAlphaType at = (-1 != yuvaIndices[SkYUVAIndex::kA_Index].fIndex) ? kPremul_SkAlphaType
                                                                        : kOpaque_SkAlphaType;
-    SkImageInfo info = SkImageInfo::Make(imageWidth, imageHeight, kRGBA_8888_SkColorType,
+    SkImageInfo info = SkImageInfo::Make(imageWidth, imageHeight, kAssumedColorType,
                                          at, imageColorSpace);
     if (!SkImageInfoIsValid(info)) {
         return nullptr;
@@ -323,7 +334,7 @@
         proxies[texIdx] = MakePromiseImageLazyProxy(
                 context, yuvaSizes[texIdx].width(), yuvaSizes[texIdx].height(), imageOrigin, config,
                 yuvaFormats[texIdx], GrMipMapped::kNo, textureFulfillProc, textureReleaseProc,
-                promiseDoneProc, textureContexts[texIdx], delayReleaseCallback);
+                promiseDoneProc, textureContexts[texIdx], version);
         ++proxiesCreated;
         if (!proxies[texIdx]) {
             return nullptr;
diff --git a/src/image/SkImage_GpuYUVA.h b/src/image/SkImage_GpuYUVA.h
index a428761..eb4783f 100644
--- a/src/image/SkImage_GpuYUVA.h
+++ b/src/image/SkImage_GpuYUVA.h
@@ -29,14 +29,15 @@
                     GrSurfaceOrigin, sk_sp<SkColorSpace>);
     ~SkImage_GpuYUVA() override;
 
-    SkImageInfo onImageInfo() const override;
-
-    GrTextureProxy* peekProxy() const override { return this->asTextureProxyRef().get(); }
-    sk_sp<GrTextureProxy> asTextureProxyRef() const override;
+    // This returns the single backing proxy if the YUV channels have already been flattened but
+    // nullptr if they have not.
+    GrTextureProxy* peekProxy() const override;
+    sk_sp<GrTextureProxy> asTextureProxyRef(GrRecordingContext*) const override;
 
     virtual bool onIsTextureBacked() const override { return SkToBool(fProxies[0].get()); }
 
-    sk_sp<SkImage> onMakeColorTypeAndColorSpace(SkColorType, sk_sp<SkColorSpace>) const final;
+    sk_sp<SkImage> onMakeColorTypeAndColorSpace(GrRecordingContext*,
+                                                SkColorType, sk_sp<SkColorSpace>) const final;
 
     virtual bool isYUVA() const override { return true; }
     virtual bool asYUVATextureProxiesRef(sk_sp<GrTextureProxy> proxies[4],
@@ -50,10 +51,10 @@
         return true;
     }
 
-    bool setupMipmapsForPlanes() const;
+    bool setupMipmapsForPlanes(GrRecordingContext*) const;
 
     // Returns a ref-ed texture proxy with miplevels
-    sk_sp<GrTextureProxy> asMippedTextureProxyRef() const;
+    sk_sp<GrTextureProxy> asMippedTextureProxyRef(GrRecordingContext*) const;
 
     /**
      * This is the implementation of SkDeferredDisplayListRecorder::makeYUVAPromiseTexture.
@@ -71,7 +72,7 @@
                                                  PromiseImageTextureReleaseProc textureReleaseProc,
                                                  PromiseImageTextureDoneProc textureDoneProc,
                                                  PromiseImageTextureContext textureContexts[],
-                                                 DelayReleaseCallback delayReleaseCallback);
+                                                 PromiseImageApiVersion);
 
 private:
     SkImage_GpuYUVA(const SkImage_GpuYUVA* image, sk_sp<SkColorSpace>);
@@ -83,7 +84,10 @@
     SkYUVAIndex                      fYUVAIndices[4];
     const SkYUVColorSpace            fYUVColorSpace;
     GrSurfaceOrigin                  fOrigin;
-    const sk_sp<SkColorSpace>        fTargetColorSpace;
+    // If this is non-null then the planar data should be converted from fFromColorSpace to
+    // this->colorSpace(). Otherwise we assume the planar data (post YUV->RGB conversion) is already
+    // in this->colorSpace().
+    const sk_sp<SkColorSpace> fFromColorSpace;
 
     // Repeated calls to onMakeColorSpace will result in a proliferation of unique IDs and
     // SkImage_GpuYUVA instances. Cache the result of the last successful onMakeColorSpace call.
diff --git a/src/image/SkImage_Lazy.cpp b/src/image/SkImage_Lazy.cpp
index 47bf690..d1494c8 100644
--- a/src/image/SkImage_Lazy.cpp
+++ b/src/image/SkImage_Lazy.cpp
@@ -16,12 +16,13 @@
 #include "SkNextID.h"
 
 #if SK_SUPPORT_GPU
-#include "GrContext.h"
-#include "GrContextPriv.h"
+#include "GrCaps.h"
 #include "GrGpuResourcePriv.h"
 #include "GrImageTextureMaker.h"
 #include "GrResourceKey.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrSamplerState.h"
 #include "GrYUVProvider.h"
 #include "SkGr.h"
@@ -122,9 +123,8 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 SkImage_Lazy::SkImage_Lazy(Validator* validator)
-        : INHERITED(validator->fInfo.width(), validator->fInfo.height(), validator->fUniqueID)
+        : INHERITED(validator->fInfo, validator->fUniqueID)
         , fSharedGenerator(std::move(validator->fSharedGenerator))
-        , fInfo(validator->fInfo)
         , fOrigin(validator->fOrigin) {
     SkASSERT(fSharedGenerator);
     fUniqueID = validator->fUniqueID;
@@ -192,7 +192,7 @@
 
     if (SkImage::kAllow_CachingHint == chint) {
         SkPixmap pmap;
-        SkBitmapCache::RecPtr cacheRec = SkBitmapCache::Alloc(desc, fInfo, &pmap);
+        SkBitmapCache::RecPtr cacheRec = SkBitmapCache::Alloc(desc, this->imageInfo(), &pmap);
         if (!cacheRec ||
             !generate_pixels(ScopedGenerator(fSharedGenerator), pmap,
                              fOrigin.x(), fOrigin.y())) {
@@ -201,9 +201,9 @@
         SkBitmapCache::Add(std::move(cacheRec), bitmap);
         this->notifyAddedToRasterCache();
     } else {
-        if (!bitmap->tryAllocPixels(fInfo) ||
-            !generate_pixels(ScopedGenerator(fSharedGenerator), bitmap->pixmap(),
-                             fOrigin.x(), fOrigin.y())) {
+        if (!bitmap->tryAllocPixels(this->imageInfo()) ||
+            !generate_pixels(ScopedGenerator(fSharedGenerator), bitmap->pixmap(), fOrigin.x(),
+                             fOrigin.y())) {
             return false;
         }
         bitmap->setImmutable();
@@ -237,7 +237,7 @@
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 #if SK_SUPPORT_GPU
-sk_sp<GrTextureProxy> SkImage_Lazy::asTextureProxyRef(GrContext* context,
+sk_sp<GrTextureProxy> SkImage_Lazy::asTextureProxyRef(GrRecordingContext* context,
                                                       const GrSamplerState& params,
                                                       SkScalar scaleAdjust[2]) const {
     if (!context) {
@@ -249,17 +249,19 @@
 }
 #endif
 
-sk_sp<SkImage> SkImage_Lazy::onMakeSubset(const SkIRect& subset) const {
-    SkASSERT(fInfo.bounds().contains(subset));
-    SkASSERT(fInfo.bounds() != subset);
+sk_sp<SkImage> SkImage_Lazy::onMakeSubset(GrRecordingContext* context,
+                                          const SkIRect& subset) const {
+    SkASSERT(this->bounds().contains(subset));
+    SkASSERT(this->bounds() != subset);
 
     const SkIRect generatorSubset = subset.makeOffset(fOrigin.x(), fOrigin.y());
-    const SkColorType colorType = fInfo.colorType();
-    Validator validator(fSharedGenerator, &generatorSubset, &colorType, fInfo.refColorSpace());
+    const SkColorType colorType = this->colorType();
+    Validator validator(fSharedGenerator, &generatorSubset, &colorType, this->refColorSpace());
     return validator ? sk_sp<SkImage>(new SkImage_Lazy(&validator)) : nullptr;
 }
 
-sk_sp<SkImage> SkImage_Lazy::onMakeColorTypeAndColorSpace(SkColorType targetCT,
+sk_sp<SkImage> SkImage_Lazy::onMakeColorTypeAndColorSpace(GrRecordingContext*,
+                                                          SkColorType targetCT,
                                                           sk_sp<SkColorSpace> targetCS) const {
     SkAutoExclusive autoAquire(fOnMakeColorTypeAndSpaceMutex);
     if (fOnMakeColorTypeAndSpaceResult &&
@@ -268,7 +270,7 @@
         return fOnMakeColorTypeAndSpaceResult;
     }
     const SkIRect generatorSubset =
-            SkIRect::MakeXYWH(fOrigin.x(), fOrigin.y(), fInfo.width(), fInfo.height());
+            SkIRect::MakeXYWH(fOrigin.x(), fOrigin.y(), this->width(), this->height());
     Validator validator(fSharedGenerator, &generatorSubset, &targetCT, targetCS);
     sk_sp<SkImage> result = validator ? sk_sp<SkImage>(new SkImage_Lazy(&validator)) : nullptr;
     if (result) {
@@ -363,7 +365,7 @@
  *  4. Ask the generator to return RGB(A) data, which the GPU can convert
  */
 sk_sp<GrTextureProxy> SkImage_Lazy::lockTextureProxy(
-        GrContext* ctx,
+        GrRecordingContext* ctx,
         const GrUniqueKey& origKey,
         SkImage::CachingHint chint,
         bool willBeMipped,
@@ -411,7 +413,7 @@
                 SkImageGenerator::TexGenType::kCheap != generator->onCanGenerateTexture()) {
             return nullptr;
         }
-        if ((proxy = generator->generateTexture(ctx, fInfo, fOrigin, willBeMipped))) {
+        if ((proxy = generator->generateTexture(ctx, this->imageInfo(), fOrigin, willBeMipped))) {
             SK_HISTOGRAM_ENUMERATION("LockTexturePath", kNative_LockTexturePath,
                                      kLockTexturePathCount);
             set_key_on_proxy(proxyProvider, proxy.get(), nullptr, key);
@@ -426,9 +428,9 @@
     // 3. Ask the generator to return YUV planes, which the GPU can convert. If we will be mipping
     //    the texture we fall through here and have the CPU generate the mip maps for us.
     if (!proxy && !willBeMipped && !ctx->priv().options().fDisableGpuYUVConversion) {
-        const GrSurfaceDesc desc = GrImageInfoToSurfaceDesc(fInfo);
+        const GrSurfaceDesc desc = GrImageInfoToSurfaceDesc(this->imageInfo());
 
-        SkColorType colorType = fInfo.colorType();
+        SkColorType colorType = this->colorType();
         GrBackendFormat format =
                 ctx->priv().caps()->getBackendFormatFromColorType(colorType);
 
@@ -440,7 +442,7 @@
         // color space. To correct this, apply a color space conversion from the generator's color
         // space to this image's color space.
         SkColorSpace* generatorColorSpace = fSharedGenerator->fGenerator->getInfo().colorSpace();
-        SkColorSpace* thisColorSpace = fInfo.colorSpace();
+        SkColorSpace* thisColorSpace = this->colorSpace();
 
         // TODO: Update to create the mipped surface in the YUV generator and draw the base
         // layer directly into the mipped surface.
@@ -458,12 +460,8 @@
     // 4. Ask the generator to return RGB(A) data, which the GPU can convert
     SkBitmap bitmap;
     if (!proxy && this->getROPixels(&bitmap, chint)) {
-        if (willBeMipped) {
-            proxy = proxyProvider->createMipMapProxyFromBitmap(bitmap);
-        }
-        if (!proxy) {
-            proxy = GrUploadBitmapToTextureProxy(proxyProvider, bitmap);
-        }
+        proxy = proxyProvider->createProxyFromBitmap(bitmap, willBeMipped ? GrMipMapped::kYes
+                                                                          : GrMipMapped::kNo);
         if (proxy && (!willBeMipped || GrMipMapped::kYes == proxy->mipMapped())) {
             SK_HISTOGRAM_ENUMERATION("LockTexturePath", kRGBA_LockTexturePath,
                                      kLockTexturePathCount);
diff --git a/src/image/SkImage_Lazy.h b/src/image/SkImage_Lazy.h
index 51b893a..6c9d2b7 100644
--- a/src/image/SkImage_Lazy.h
+++ b/src/image/SkImage_Lazy.h
@@ -35,28 +35,25 @@
     SkImage_Lazy(Validator* validator);
     ~SkImage_Lazy() override;
 
-    SkImageInfo onImageInfo() const override {
-        return fInfo;
-    }
-
     SkIRect onGetSubset() const override {
-        return SkIRect::MakeXYWH(fOrigin.fX, fOrigin.fY, fInfo.width(), fInfo.height());
+        return SkIRect::MakeXYWH(fOrigin.fX, fOrigin.fY, this->width(), this->height());
     }
 
     bool onReadPixels(const SkImageInfo&, void*, size_t, int srcX, int srcY,
                       CachingHint) const override;
 #if SK_SUPPORT_GPU
-    sk_sp<GrTextureProxy> asTextureProxyRef(GrContext*,
+    sk_sp<GrTextureProxy> asTextureProxyRef(GrRecordingContext*,
                                             const GrSamplerState&,
                                             SkScalar scaleAdjust[2]) const override;
     sk_sp<SkCachedData> getPlanes(SkYUVASizeInfo*, SkYUVAIndex[4],
                                   SkYUVColorSpace*, const void* planes[4]) override;
 #endif
     sk_sp<SkData> onRefEncoded() const override;
-    sk_sp<SkImage> onMakeSubset(const SkIRect&) const override;
+    sk_sp<SkImage> onMakeSubset(GrRecordingContext*, const SkIRect&) const override;
     bool getROPixels(SkBitmap*, CachingHint) const override;
     bool onIsLazyGenerated() const override { return true; }
-    sk_sp<SkImage> onMakeColorTypeAndColorSpace(SkColorType, sk_sp<SkColorSpace>) const override;
+    sk_sp<SkImage> onMakeColorTypeAndColorSpace(GrRecordingContext*,
+                                                SkColorType, sk_sp<SkColorSpace>) const override;
 
     bool onIsValid(GrContext*) const override;
 
@@ -64,7 +61,7 @@
     // Returns the texture proxy. If we're going to generate and cache the texture, we should use
     // the passed in key (if the key is valid). If genType is AllowedTexGenType::kCheap and the
     // texture is not trivial to construct, returns nullptr.
-    sk_sp<GrTextureProxy> lockTextureProxy(GrContext*,
+    sk_sp<GrTextureProxy> lockTextureProxy(GrRecordingContext*,
                                            const GrUniqueKey& key,
                                            SkImage::CachingHint,
                                            bool willBeMipped,
@@ -76,10 +73,10 @@
 private:
     class ScopedGenerator;
 
+    // Note that this->imageInfo() is not necessarily the info from the generator. It may be
+    // cropped by onMakeSubset and its color type/space may be changed by
+    // onMakeColorTypeAndColorSpace.
     sk_sp<SharedGenerator> fSharedGenerator;
-    // Note that fInfo is not necessarily the info from the generator. It may be cropped by
-    // onMakeSubset and its color type/space may be changed by onMakeColorTypeAndColorSpace.
-    const SkImageInfo      fInfo;
     const SkIPoint         fOrigin;
 
     uint32_t fUniqueID;
diff --git a/src/image/SkImage_Raster.cpp b/src/image/SkImage_Raster.cpp
index 806f366..8646e38 100644
--- a/src/image/SkImage_Raster.cpp
+++ b/src/image/SkImage_Raster.cpp
@@ -72,36 +72,31 @@
                    uint32_t id = kNeedNewImageUniqueID);
     ~SkImage_Raster() override;
 
-    SkImageInfo onImageInfo() const override {
-        return fBitmap.info();
-    }
-
     bool onReadPixels(const SkImageInfo&, void*, size_t, int srcX, int srcY, CachingHint) const override;
     bool onPeekPixels(SkPixmap*) const override;
     const SkBitmap* onPeekBitmap() const override { return &fBitmap; }
 
 #if SK_SUPPORT_GPU
-    sk_sp<GrTextureProxy> asTextureProxyRef(GrContext*, const GrSamplerState&,
+    sk_sp<GrTextureProxy> asTextureProxyRef(GrRecordingContext*, const GrSamplerState&,
                                             SkScalar scaleAdjust[2]) const override;
 #endif
 
     bool getROPixels(SkBitmap*, CachingHint) const override;
-    sk_sp<SkImage> onMakeSubset(const SkIRect&) const override;
+    sk_sp<SkImage> onMakeSubset(GrRecordingContext*, const SkIRect&) const override;
 
     SkPixelRef* getPixelRef() const { return fBitmap.pixelRef(); }
 
     bool onAsLegacyBitmap(SkBitmap*) const override;
 
     SkImage_Raster(const SkBitmap& bm, bool bitmapMayBeMutable = false)
-        : INHERITED(bm.width(), bm.height(),
-                    is_not_subset(bm) ? bm.getGenerationID()
-                                      : (uint32_t)kNeedNewImageUniqueID)
-        , fBitmap(bm)
-    {
+            : INHERITED(bm.info(),
+                        is_not_subset(bm) ? bm.getGenerationID() : (uint32_t)kNeedNewImageUniqueID)
+            , fBitmap(bm) {
         SkASSERT(bitmapMayBeMutable || fBitmap.isImmutable());
     }
 
-    sk_sp<SkImage> onMakeColorTypeAndColorSpace(SkColorType, sk_sp<SkColorSpace>) const override;
+    sk_sp<SkImage> onMakeColorTypeAndColorSpace(GrRecordingContext*,
+                                                SkColorType, sk_sp<SkColorSpace>) const override;
 
     bool onIsValid(GrContext* context) const override { return true; }
     void notifyAddedToRasterCache() const override {
@@ -113,7 +108,8 @@
     }
 
 #if SK_SUPPORT_GPU
-    sk_sp<GrTextureProxy> refPinnedTextureProxy(uint32_t* uniqueID) const override;
+    sk_sp<GrTextureProxy> refPinnedTextureProxy(GrRecordingContext*,
+                                                uint32_t* uniqueID) const override;
     bool onPinAsTexture(GrContext*) const override;
     void onUnpinAsTexture(GrContext*) const override;
 #endif
@@ -139,8 +135,7 @@
 
 SkImage_Raster::SkImage_Raster(const SkImageInfo& info, sk_sp<SkData> data, size_t rowBytes,
                                uint32_t id)
-    : INHERITED(info.width(), info.height(), id)
-{
+        : INHERITED(info, id) {
     void* addr = const_cast<void*>(data->data());
 
     fBitmap.installPixels(info, addr, rowBytes, release_data, data.release());
@@ -169,7 +164,7 @@
 }
 
 #if SK_SUPPORT_GPU
-sk_sp<GrTextureProxy> SkImage_Raster::asTextureProxyRef(GrContext* context,
+sk_sp<GrTextureProxy> SkImage_Raster::asTextureProxyRef(GrRecordingContext* context,
                                                         const GrSamplerState& params,
                                                         SkScalar scaleAdjust[2]) const {
     if (!context) {
@@ -177,7 +172,7 @@
     }
 
     uint32_t uniqueID;
-    sk_sp<GrTextureProxy> tex = this->refPinnedTextureProxy(&uniqueID);
+    sk_sp<GrTextureProxy> tex = this->refPinnedTextureProxy(context, &uniqueID);
     if (tex) {
         GrTextureAdjuster adjuster(context, fPinnedProxy, fBitmap.alphaType(), fPinnedUniqueID,
                                    fBitmap.colorSpace());
@@ -190,7 +185,8 @@
 
 #if SK_SUPPORT_GPU
 
-sk_sp<GrTextureProxy> SkImage_Raster::refPinnedTextureProxy(uint32_t* uniqueID) const {
+sk_sp<GrTextureProxy> SkImage_Raster::refPinnedTextureProxy(GrRecordingContext*,
+                                                            uint32_t* uniqueID) const {
     if (fPinnedProxy) {
         SkASSERT(fPinnedCount > 0);
         SkASSERT(fPinnedUniqueID != 0);
@@ -231,7 +227,7 @@
 }
 #endif
 
-sk_sp<SkImage> SkImage_Raster::onMakeSubset(const SkIRect& subset) const {
+sk_sp<SkImage> SkImage_Raster::onMakeSubset(GrRecordingContext*, const SkIRect& subset) const {
     SkImageInfo info = fBitmap.info().makeWH(subset.width(), subset.height());
     SkBitmap bitmap;
     if (!bitmap.tryAllocPixels(info)) {
@@ -337,7 +333,8 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-sk_sp<SkImage> SkImage_Raster::onMakeColorTypeAndColorSpace(SkColorType targetCT,
+sk_sp<SkImage> SkImage_Raster::onMakeColorTypeAndColorSpace(GrRecordingContext*,
+                                                            SkColorType targetCT,
                                                             sk_sp<SkColorSpace> targetCS) const {
     SkPixmap src;
     SkAssertResult(fBitmap.peekPixels(&src));
diff --git a/src/image/SkSurface.cpp b/src/image/SkSurface.cpp
index 272e18b..d7a05ee 100644
--- a/src/image/SkSurface.cpp
+++ b/src/image/SkSurface.cpp
@@ -244,12 +244,24 @@
 }
 
 void SkSurface::flush() {
-    asSB(this)->onFlush(0, nullptr);
+    asSB(this)->onFlush(BackendSurfaceAccess::kNoAccess, kNone_GrFlushFlags, 0, nullptr);
+}
+
+GrSemaphoresSubmitted SkSurface::flush(BackendSurfaceAccess access, GrFlushFlags flags,
+                                       int numSemaphores, GrBackendSemaphore signalSemaphores[]) {
+    return asSB(this)->onFlush(access, flags, numSemaphores, signalSemaphores);
+}
+
+GrSemaphoresSubmitted SkSurface::flush(BackendSurfaceAccess access, FlushFlags flags,
+                                       int numSemaphores, GrBackendSemaphore signalSemaphores[]) {
+    GrFlushFlags grFlags = flags == kSyncCpu_FlushFlag ? kSyncCpu_GrFlushFlag : kNone_GrFlushFlags;
+    return this->flush(access, grFlags, numSemaphores, signalSemaphores);
 }
 
 GrSemaphoresSubmitted SkSurface::flushAndSignalSemaphores(int numSemaphores,
                                                           GrBackendSemaphore signalSemaphores[]) {
-    return asSB(this)->onFlush(numSemaphores, signalSemaphores);
+    return asSB(this)->onFlush(BackendSurfaceAccess::kNoAccess, kNone_GrFlushFlags,
+                               numSemaphores, signalSemaphores);
 }
 
 bool SkSurface::wait(int numSemaphores, const GrBackendSemaphore* waitSemaphores) {
@@ -300,7 +312,7 @@
     return nullptr;
 }
 
-sk_sp<SkSurface> SkSurface::MakeRenderTarget(GrContext*, const SkSurfaceCharacterization&,
+sk_sp<SkSurface> SkSurface::MakeRenderTarget(GrRecordingContext*, const SkSurfaceCharacterization&,
                                              SkBudgeted) {
     return nullptr;
 }
diff --git a/src/image/SkSurface_Base.h b/src/image/SkSurface_Base.h
index dbbcc88..0e3cd95 100644
--- a/src/image/SkSurface_Base.h
+++ b/src/image/SkSurface_Base.h
@@ -80,7 +80,8 @@
      * Inserts the requested number of semaphores for the gpu to signal when work is complete on the
      * gpu and inits the array of GrBackendSemaphores with the signaled semaphores.
      */
-    virtual GrSemaphoresSubmitted onFlush(int numSemaphores,
+    virtual GrSemaphoresSubmitted onFlush(BackendSurfaceAccess access, GrFlushFlags flags,
+                                          int numSemaphores,
                                           GrBackendSemaphore signalSemaphores[]) {
         return GrSemaphoresSubmitted::kNo;
     }
diff --git a/src/image/SkSurface_Gpu.cpp b/src/image/SkSurface_Gpu.cpp
index 7163f21..c0c4042 100644
--- a/src/image/SkSurface_Gpu.cpp
+++ b/src/image/SkSurface_Gpu.cpp
@@ -6,9 +6,13 @@
  */
 
 #include "SkSurface_Gpu.h"
+#include "GrAHardwareBufferUtils.h"
 #include "GrBackendSurface.h"
 #include "GrCaps.h"
 #include "GrContextPriv.h"
+#include "GrContextThreadSafeProxyPriv.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTarget.h"
 #include "GrRenderTargetContextPriv.h"
 #include "GrRenderTargetProxyPriv.h"
@@ -46,7 +50,8 @@
     }
 
     // Grab the render target *after* firing notifications, as it may get switched if CoW kicks in.
-    surface->getDevice()->flush();
+    surface->getDevice()->flushAndSignalSemaphores(SkSurface::BackendSurfaceAccess::kNoAccess,
+                                                   kNone_GrFlushFlags, 0, nullptr);
     GrRenderTargetContext* rtc = surface->getDevice()->accessRenderTargetContext();
     return rtc->accessRenderTarget();
 }
@@ -153,9 +158,10 @@
     fDevice->accessRenderTargetContext()->discard();
 }
 
-GrSemaphoresSubmitted SkSurface_Gpu::onFlush(int numSemaphores,
+GrSemaphoresSubmitted SkSurface_Gpu::onFlush(BackendSurfaceAccess access, GrFlushFlags flags,
+                                             int numSemaphores,
                                              GrBackendSemaphore signalSemaphores[]) {
-    return fDevice->flushAndSignalSemaphores(numSemaphores, signalSemaphores);
+    return fDevice->flushAndSignalSemaphores(access, flags, numSemaphores, signalSemaphores);
 }
 
 bool SkSurface_Gpu::onWait(int numSemaphores, const GrBackendSemaphore* waitSemaphores) {
@@ -198,6 +204,42 @@
     return true;
 }
 
+void SkSurface_Gpu::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y, const SkPaint* paint) {
+    // If the dst is also GPU we try to not force a new image snapshot (by calling the base class
+    // onDraw) since that may not always perform the copy-on-write optimization.
+    auto tryDraw = [&] {
+        SkASSERT(fDevice->context()->priv().asDirectContext());
+        GrContext* context = fDevice->context();
+        GrContext* canvasContext = canvas->getGrContext();
+        if (!canvasContext) {
+            return false;
+        }
+        if (!canvasContext->priv().asDirectContext() ||
+            canvasContext->priv().contextID() != context->priv().contextID()) {
+            return false;
+        }
+        GrRenderTargetContext* rtc = fDevice->accessRenderTargetContext();
+        if (!rtc) {
+            return false;
+        }
+        sk_sp<GrTextureProxy> srcProxy = rtc->asTextureProxyRef();
+        if (!srcProxy) {
+            return false;
+        }
+        // Possibly we could skip making an image here if SkGpuDevice exposed a lower level way
+        // of drawing a texture proxy.
+        const SkImageInfo info = fDevice->imageInfo();
+        sk_sp<SkImage> image;
+        image = sk_make_sp<SkImage_Gpu>(sk_ref_sp(context), kNeedNewImageUniqueID, info.alphaType(),
+                                        std::move(srcProxy), info.refColorSpace());
+        canvas->drawImage(image, x, y, paint);
+        return true;
+    };
+    if (!tryDraw()) {
+        INHERITED::onDraw(canvas, x, y, paint);
+    }
+}
+
 bool SkSurface_Gpu::isCompatible(const SkSurfaceCharacterization& characterization) const {
     GrRenderTargetContext* rtc = fDevice->accessRenderTargetContext();
     GrContext* ctx = fDevice->context();
@@ -206,6 +248,10 @@
         return false;
     }
 
+    if (characterization.vulkanSecondaryCBCompatible()) {
+        return false;
+    }
+
     // As long as the current state if the context allows for greater or equal resources,
     // we allow the DDL to be replayed.
     // DDL TODO: should we just remove the resource check and ignore the cache limits on playback?
@@ -239,7 +285,7 @@
         return false;
     }
 
-    return characterization.contextInfo() && characterization.contextInfo()->matches(ctx) &&
+    return characterization.contextInfo() && characterization.contextInfo()->priv().matches(ctx) &&
            characterization.cacheMaxResourceBytes() <= maxResourceBytes &&
            characterization.origin() == rtc->origin() &&
            characterization.config() == rtc->colorSpaceInfo().config() &&
@@ -282,7 +328,7 @@
     }
 }
 
-sk_sp<SkSurface> SkSurface::MakeRenderTarget(GrContext* context,
+sk_sp<SkSurface> SkSurface::MakeRenderTarget(GrRecordingContext* context,
                                              const SkSurfaceCharacterization& c,
                                              SkBudgeted budgeted) {
     if (!context || !c.isValid()) {
@@ -320,7 +366,9 @@
         return nullptr;
     }
 
-    sk_sp<SkGpuDevice> device(SkGpuDevice::Make(context, sk_ref_sp(sc->asRenderTargetContext()),
+    // CONTEXT TODO: remove this use of 'backdoor' to create an SkGpuDevice
+    sk_sp<SkGpuDevice> device(SkGpuDevice::Make(context->priv().backdoor(),
+                                                sk_ref_sp(sc->asRenderTargetContext()),
                                                 c.width(), c.height(),
                                                 SkGpuDevice::kClear_InitContents));
     if (!device) {
@@ -571,4 +619,65 @@
     return sk_make_sp<SkSurface_Gpu>(std::move(device));
 }
 
+#if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26
+sk_sp<SkSurface> SkSurface::MakeFromAHardwareBuffer(GrContext* context,
+                                                    AHardwareBuffer* hardwareBuffer,
+                                                    GrSurfaceOrigin origin,
+                                                    sk_sp<SkColorSpace> colorSpace,
+                                                    const SkSurfaceProps* surfaceProps) {
+    AHardwareBuffer_Desc bufferDesc;
+    AHardwareBuffer_describe(hardwareBuffer, &bufferDesc);
+
+    if (!SkToBool(bufferDesc.usage & AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT)) {
+        return nullptr;
+    }
+
+    bool isTextureable = SkToBool(bufferDesc.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE);
+    bool isProtectedContent = SkToBool(bufferDesc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
+
+    // We currently don't support protected content
+    if (isProtectedContent) {
+        return nullptr;
+    }
+
+    GrBackendFormat backendFormat = GrAHardwareBufferUtils::GetBackendFormat(context,
+                                                                             hardwareBuffer,
+                                                                             bufferDesc.format,
+                                                                             true);
+    if (!backendFormat.isValid()) {
+        return nullptr;
+    }
+
+    if (isTextureable) {
+        GrAHardwareBufferUtils::DeleteImageProc deleteImageProc = nullptr;
+        GrAHardwareBufferUtils::DeleteImageCtx deleteImageCtx = nullptr;
+
+        GrBackendTexture backendTexture =
+                GrAHardwareBufferUtils::MakeBackendTexture(context, hardwareBuffer,
+                                                           bufferDesc.width, bufferDesc.height,
+                                                           &deleteImageProc, &deleteImageCtx,
+                                                           isProtectedContent, backendFormat,
+                                                           true);
+        if (!backendTexture.isValid()) {
+            return nullptr;
+        }
+
+        SkColorType colorType =
+                GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(bufferDesc.format);
+
+        sk_sp<SkSurface> surface = SkSurface::MakeFromBackendTexture(context, backendTexture,
+                origin, 0, colorType, std::move(colorSpace), surfaceProps, deleteImageProc,
+                deleteImageCtx);
+
+        if (!surface) {
+            SkASSERT(deleteImageProc);
+            deleteImageProc(deleteImageCtx);
+        }
+        return surface;
+    } else {
+        return nullptr;
+    }
+}
+#endif
+
 #endif
diff --git a/src/image/SkSurface_Gpu.h b/src/image/SkSurface_Gpu.h
index d5fea6d..13b79c6 100644
--- a/src/image/SkSurface_Gpu.h
+++ b/src/image/SkSurface_Gpu.h
@@ -32,10 +32,12 @@
     void onWritePixels(const SkPixmap&, int x, int y) override;
     void onCopyOnWrite(ContentChangeMode) override;
     void onDiscard() override;
-    GrSemaphoresSubmitted onFlush(int numSemaphores,
+    GrSemaphoresSubmitted onFlush(BackendSurfaceAccess access, GrFlushFlags flags,
+                                  int numSemaphores,
                                   GrBackendSemaphore signalSemaphores[]) override;
     bool onWait(int numSemaphores, const GrBackendSemaphore* waitSemaphores) override;
     bool onCharacterize(SkSurfaceCharacterization*) const override;
+    void onDraw(SkCanvas* canvas, SkScalar x, SkScalar y, const SkPaint* paint) override;
     bool isCompatible(const SkSurfaceCharacterization&) const;
     bool onDraw(const SkDeferredDisplayList*) override;
 
diff --git a/src/image/SkSurface_Raster.cpp b/src/image/SkSurface_Raster.cpp
index 5a4fcd2..ad56bcb 100644
--- a/src/image/SkSurface_Raster.cpp
+++ b/src/image/SkSurface_Raster.cpp
@@ -198,3 +198,8 @@
     }
     return sk_make_sp<SkSurface_Raster>(info, std::move(pr), props);
 }
+
+sk_sp<SkSurface> SkSurface::MakeRasterN32Premul(int width, int height,
+                                                const SkSurfaceProps* surfaceProps) {
+    return MakeRaster(SkImageInfo::MakeN32Premul(width, height), surfaceProps);
+}
diff --git a/src/images/SkImageEncoder.cpp b/src/images/SkImageEncoder.cpp
index 946a67f..d5cf2ec 100644
--- a/src/images/SkImageEncoder.cpp
+++ b/src/images/SkImageEncoder.cpp
@@ -48,8 +48,25 @@
             }
             case SkEncodedImageFormat::kWEBP: {
                 SkWebpEncoder::Options opts;
-                opts.fCompression = SkWebpEncoder::Compression::kLossy;
-                opts.fQuality = quality;
+                if (quality == 100) {
+                    opts.fCompression = SkWebpEncoder::Compression::kLossless;
+                    // Note: SkEncodeImage treats 0 quality as the lowest quality
+                    // (greatest compression) and 100 as the highest quality (least
+                    // compression). For kLossy, this matches libwebp's
+                    // interpretation, so it is passed directly to libwebp. But
+                    // with kLossless, libwebp always creates the highest quality
+                    // image. In this case, fQuality is reinterpreted as how much
+                    // effort (time) to put into making a smaller file. This API
+                    // does not provide a way to specify this value (though it can
+                    // be specified by using SkWebpEncoder::Encode) so we have to
+                    // pick one arbitrarily. This value matches that chosen by
+                    // blink::ImageEncoder::ComputeWebpOptions as well
+                    // WebPConfigInit.
+                    opts.fQuality = 75;
+                } else {
+                    opts.fCompression = SkWebpEncoder::Compression::kLossy;
+                    opts.fQuality = quality;
+                }
                 return SkWebpEncoder::Encode(dst, src, opts);
             }
             default:
diff --git a/src/images/SkPngEncoder.cpp b/src/images/SkPngEncoder.cpp
index c452a71..bac6921 100644
--- a/src/images/SkPngEncoder.cpp
+++ b/src/images/SkPngEncoder.cpp
@@ -106,6 +106,7 @@
     png_color_8 sigBit;
     int bitDepth = 8;
     switch (srcInfo.colorType()) {
+        case kRGBA_F16Norm_SkColorType:
         case kRGBA_F16_SkColorType:
         case kRGBA_F32_SkColorType:
             sigBit.red = 16;
@@ -232,6 +233,9 @@
 
 static transform_scanline_proc choose_proc(const SkImageInfo& info) {
     switch (info.colorType()) {
+        case kUnknown_SkColorType:
+            break;
+
         case kRGBA_8888_SkColorType:
             switch (info.alphaType()) {
                 case kOpaque_SkAlphaType:
@@ -272,6 +276,8 @@
             }
         case kGray_8_SkColorType:
             return transform_scanline_memcpy;
+
+        case kRGBA_F16Norm_SkColorType:
         case kRGBA_F16_SkColorType:
             switch (info.alphaType()) {
                 case kOpaque_SkAlphaType:
@@ -309,10 +315,9 @@
             return transform_scanline_101010x;
         case kAlpha_8_SkColorType:
             return transform_scanline_A8_to_GrayAlpha;
-        default:
-            SkASSERT(false);
-            return nullptr;
     }
+    SkASSERT(false);
+    return nullptr;
 }
 
 static void set_icc(png_structp png_ptr, png_infop info_ptr, const SkImageInfo& info) {
diff --git a/src/opts/SkBlitMask_opts.h b/src/opts/SkBlitMask_opts.h
index 64c7c1e..6c174a2 100644
--- a/src/opts/SkBlitMask_opts.h
+++ b/src/opts/SkBlitMask_opts.h
@@ -97,7 +97,7 @@
                         + SkAlphaMulQ(*device, SkAlpha255To256(255 - aa));
                 }
                 device += 1;
-            };
+            }
 
             device = (uint32_t*)((char*)device + dstRB);
             mask += maskRB;
@@ -148,7 +148,7 @@
                 *device = (aa << SK_A32_SHIFT)
                             + SkAlphaMulQ(*device, SkAlpha255To256(255 - aa));
                 device += 1;
-            };
+            }
             device = (uint32_t*)((char*)device + dstRB);
             mask += maskRB;
         } while (--height != 0);
diff --git a/src/opts/SkRasterPipeline_opts.h b/src/opts/SkRasterPipeline_opts.h
index cf819862..543a2e0 100644
--- a/src/opts/SkRasterPipeline_opts.h
+++ b/src/opts/SkRasterPipeline_opts.h
@@ -1066,7 +1066,7 @@
 }
 
 // load registers r,g,b,a from context (mirrors store_rgba)
-STAGE(load_rgba, const float* ptr) {
+STAGE(load_src, const float* ptr) {
     r = unaligned_load<F>(ptr + 0*N);
     g = unaligned_load<F>(ptr + 1*N);
     b = unaligned_load<F>(ptr + 2*N);
@@ -1074,13 +1074,29 @@
 }
 
 // store registers r,g,b,a into context (mirrors load_rgba)
-STAGE(store_rgba, float* ptr) {
+STAGE(store_src, float* ptr) {
     unaligned_store(ptr + 0*N, r);
     unaligned_store(ptr + 1*N, g);
     unaligned_store(ptr + 2*N, b);
     unaligned_store(ptr + 3*N, a);
 }
 
+// load registers dr,dg,db,da from context (mirrors store_dst)
+STAGE(load_dst, const float* ptr) {
+    dr = unaligned_load<F>(ptr + 0*N);
+    dg = unaligned_load<F>(ptr + 1*N);
+    db = unaligned_load<F>(ptr + 2*N);
+    da = unaligned_load<F>(ptr + 3*N);
+}
+
+// store registers dr,dg,db,da into context (mirrors load_dst)
+STAGE(store_dst, float* ptr) {
+    unaligned_store(ptr + 0*N, dr);
+    unaligned_store(ptr + 1*N, dg);
+    unaligned_store(ptr + 2*N, db);
+    unaligned_store(ptr + 3*N, da);
+}
+
 // Most blend modes apply the same logic to each channel.
 #define BLEND_MODE(name)                       \
     SI F name##_channel(F s, F d, F sa, F da); \
@@ -1315,13 +1331,6 @@
     b = min(b, a);
 }
 
-STAGE(clamp_a_dst, Ctx::None) {
-    da = min(da, 1.0f);
-    dr = min(dr, da);
-    dg = min(dg, da);
-    db = min(db, da);
-}
-
 STAGE(clamp_gamut, Ctx::None) {
     // If you're using this stage, a should already be in [0,1].
     r = min(max(r, 0), a);
@@ -1475,6 +1484,13 @@
     b = lerp(db, b, *c);
     a = lerp(da, a, *c);
 }
+STAGE(lerp_native, const float scales[]) {
+    auto c = unaligned_load<F>(scales);
+    r = lerp(dr, r, c);
+    g = lerp(dg, g, c);
+    b = lerp(db, b, c);
+    a = lerp(da, a, c);
+}
 STAGE(lerp_u8, const SkRasterPipeline_MemoryCtx* ctx) {
     auto ptr = ptr_at_xy<const uint8_t>(ctx, dx,dy);
 
@@ -2331,21 +2347,17 @@
 
 #if JUMPER_NARROW_STAGES
     #define STAGE_GG(name, ...)                                                                \
-        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y,           \
-                         U16    , U16    , U16    , U16    ,                                   \
-                         U16    , U16    , U16    , U16    );                                  \
+        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y);          \
         static void ABI name(Params* params, void** program, U16 r, U16 g, U16 b, U16 a) {     \
             auto x = join<F>(r,g),                                                             \
                  y = join<F>(b,a);                                                             \
-            name##_k(Ctx{program}, params->dx,params->dy,params->tail, x,y, 0,0,0,0, 0,0,0,0); \
+            name##_k(Ctx{program}, params->dx,params->dy,params->tail, x,y);                   \
             split(x, &r,&g);                                                                   \
             split(y, &b,&a);                                                                   \
             auto next = (Stage)load_and_inc(program);                                          \
             next(params,program, r,g,b,a);                                                     \
         }                                                                                      \
-        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y,           \
-                         U16    , U16    , U16    , U16    ,                                   \
-                         U16    , U16    , U16    , U16    )
+        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y)
 
     #define STAGE_GP(name, ...)                                                            \
         SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F x, F y,         \
@@ -2364,37 +2376,33 @@
                          U16& dr, U16& dg, U16& db, U16& da)
 
     #define STAGE_PP(name, ...)                                                            \
-        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F  , F  ,         \
+        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail,                   \
                          U16&  r, U16&  g, U16&  b, U16&  a,                               \
                          U16& dr, U16& dg, U16& db, U16& da);                              \
         static void ABI name(Params* params, void** program, U16 r, U16 g, U16 b, U16 a) { \
-            name##_k(Ctx{program}, params->dx,params->dy,params->tail, 0,0, r,g,b,a,       \
+            name##_k(Ctx{program}, params->dx,params->dy,params->tail, r,g,b,a,            \
                      params->dr,params->dg,params->db,params->da);                         \
             auto next = (Stage)load_and_inc(program);                                      \
             next(params,program, r,g,b,a);                                                 \
         }                                                                                  \
-        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F  , F  ,         \
+        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail,                   \
                          U16&  r, U16&  g, U16&  b, U16&  a,                               \
                          U16& dr, U16& dg, U16& db, U16& da)
 #else
     #define STAGE_GG(name, ...)                                                            \
-        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y,       \
-                         U16    , U16    , U16    , U16    ,                               \
-                         U16    , U16    , U16    , U16    );                              \
+        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y);      \
         static void ABI name(size_t tail, void** program, size_t dx, size_t dy,            \
                              U16  r, U16  g, U16  b, U16  a,                               \
                              U16 dr, U16 dg, U16 db, U16 da) {                             \
             auto x = join<F>(r,g),                                                         \
                  y = join<F>(b,a);                                                         \
-            name##_k(Ctx{program}, dx,dy,tail, x,y, 0,0,0,0, 0,0,0,0);                     \
+            name##_k(Ctx{program}, dx,dy,tail, x,y);                                       \
             split(x, &r,&g);                                                               \
             split(y, &b,&a);                                                               \
             auto next = (Stage)load_and_inc(program);                                      \
             next(tail,program,dx,dy, r,g,b,a, dr,dg,db,da);                                \
         }                                                                                  \
-        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y,       \
-                         U16    , U16    , U16    , U16    ,                               \
-                         U16    , U16    , U16    , U16    )
+        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y)
 
     #define STAGE_GP(name, ...)                                                            \
         SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F x, F y,         \
@@ -2414,17 +2422,17 @@
                          U16& dr, U16& dg, U16& db, U16& da)
 
     #define STAGE_PP(name, ...)                                                            \
-        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F  , F  ,         \
+        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail,                   \
                          U16&  r, U16&  g, U16&  b, U16&  a,                               \
                          U16& dr, U16& dg, U16& db, U16& da);                              \
         static void ABI name(size_t tail, void** program, size_t dx, size_t dy,            \
                              U16  r, U16  g, U16  b, U16  a,                               \
                              U16 dr, U16 dg, U16 db, U16 da) {                             \
-            name##_k(Ctx{program}, dx,dy,tail, 0,0, r,g,b,a, dr,dg,db,da);                 \
+            name##_k(Ctx{program}, dx,dy,tail, r,g,b,a, dr,dg,db,da);                      \
             auto next = (Stage)load_and_inc(program);                                      \
             next(tail,program,dx,dy, r,g,b,a, dr,dg,db,da);                                \
         }                                                                                  \
-        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F  , F  ,         \
+        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail,                   \
                          U16&  r, U16&  g, U16&  b, U16&  a,                               \
                          U16& dr, U16& dg, U16& db, U16& da)
 #endif
@@ -2617,11 +2625,6 @@
     g = min(g, a);
     b = min(b, a);
 }
-STAGE_PP(clamp_a_dst, Ctx::None) {
-    dr = min(dr, da);
-    dg = min(dg, da);
-    db = min(db, da);
-}
 
 STAGE_PP(clamp_gamut, Ctx::None) {
     // It shouldn't be possible to get out-of-gamut
@@ -2930,10 +2933,12 @@
     from_565(load<U16>(ptr, tail), r,g,b);
 }
 SI void store_565_(uint16_t* ptr, size_t tail, U16 r, U16 g, U16 b) {
-    // Select the top 5,6,5 bits.
-    U16 R = r >> 3,
-        G = g >> 2,
-        B = b >> 3;
+    // Round from [0,255] to [0,31] or [0,63], as if x * (31/255.0f) + 0.5f.
+    // (Don't feel like you need to find some fundamental truth in these...
+    // they were brute-force searched.)
+    U16 R = (r *  9 + 36) / 74,   //  9/74 ≈ 31/255, plus 36/74, about half.
+        G = (g * 21 + 42) / 85,   // 21/85 = 63/255 exactly.
+        B = (b *  9 + 36) / 74;
     // Pack them back into 15|rrrrr gggggg bbbbb|0.
     store(ptr, tail, R << 11
                    | G <<  5
@@ -2975,11 +2980,11 @@
     from_4444(load<U16>(ptr, tail), r,g,b,a);
 }
 SI void store_4444_(uint16_t* ptr, size_t tail, U16 r, U16 g, U16 b, U16 a) {
-    // Select the top 4 bits of each.
-    U16 R = r >> 4,
-        G = g >> 4,
-        B = b >> 4,
-        A = a >> 4;
+    // Round from [0,255] to [0,15], producing the same value as (x*(15/255.0f) + 0.5f).
+    U16 R = (r + 8) / 17,
+        G = (g + 8) / 17,
+        B = (b + 8) / 17,
+        A = (a + 8) / 17;
     // Pack them back into 15|rrrr gggg bbbb aaaa|0.
     store(ptr, tail, R << 12
                    | G <<  8
@@ -3044,6 +3049,33 @@
 
 // ~~~~~~ Coverage scales / lerps ~~~~~~ //
 
+STAGE_PP(load_src, const uint16_t* ptr) {
+    r = unaligned_load<U16>(ptr + 0*N);
+    g = unaligned_load<U16>(ptr + 1*N);
+    b = unaligned_load<U16>(ptr + 2*N);
+    a = unaligned_load<U16>(ptr + 3*N);
+}
+STAGE_PP(store_src, uint16_t* ptr) {
+    unaligned_store(ptr + 0*N, r);
+    unaligned_store(ptr + 1*N, g);
+    unaligned_store(ptr + 2*N, b);
+    unaligned_store(ptr + 3*N, a);
+}
+STAGE_PP(load_dst, const uint16_t* ptr) {
+    dr = unaligned_load<U16>(ptr + 0*N);
+    dg = unaligned_load<U16>(ptr + 1*N);
+    db = unaligned_load<U16>(ptr + 2*N);
+    da = unaligned_load<U16>(ptr + 3*N);
+}
+STAGE_PP(store_dst, uint16_t* ptr) {
+    unaligned_store(ptr + 0*N, dr);
+    unaligned_store(ptr + 1*N, dg);
+    unaligned_store(ptr + 2*N, db);
+    unaligned_store(ptr + 3*N, da);
+}
+
+// ~~~~~~ Coverage scales / lerps ~~~~~~ //
+
 STAGE_PP(scale_1_float, const float* f) {
     U16 c = from_float(*f);
     r = div255( r * c );
@@ -3058,6 +3090,13 @@
     b = lerp(db, b, c);
     a = lerp(da, a, c);
 }
+STAGE_PP(lerp_native, const uint16_t scales[]) {
+    auto c = unaligned_load<U16>(scales);
+    r = lerp(dr, r, c);
+    g = lerp(dg, g, c);
+    b = lerp(db, b, c);
+    a = lerp(da, a, c);
+}
 
 STAGE_PP(scale_u8, const SkRasterPipeline_MemoryCtx* ctx) {
     U16 c = load_8(ptr_at_xy<const uint8_t>(ctx, dx,dy), tail);
@@ -3271,16 +3310,81 @@
     store_8888_(ptr, tail, r,g,b,a);
 }
 
+#if defined(SK_DISABLE_LOWP_BILERP_CLAMP_CLAMP_STAGE)
+    static void(*bilerp_clamp_8888)(void) = nullptr;
+#else
+STAGE_GP(bilerp_clamp_8888, const SkRasterPipeline_GatherCtx* ctx) {
+    // (cx,cy) are the center of our sample.
+    F cx = x,
+      cy = y;
+
+    // All sample points are at the same fractional offset (fx,fy).
+    // They're the 4 corners of a logical 1x1 pixel surrounding (x,y) at (0.5,0.5) offsets.
+    F fx = fract(cx + 0.5f),
+      fy = fract(cy + 0.5f);
+
+    // We'll accumulate the color of all four samples into {r,g,b,a} directly.
+    r = g = b = a = 0;
+
+    // The first three sample points will calculate their area using math
+    // just like in the float code above, but the fourth will take up all the rest.
+    //
+    // Logically this is the same as doing the math for the fourth pixel too,
+    // but rounding error makes this a better strategy, keeping opaque opaque, etc.
+    //
+    // We can keep up to 8 bits of fractional precision without overflowing 16-bit,
+    // so our "1.0" area is 256.
+    const uint16_t bias = 256;
+    U16 remaining = bias;
+
+    for (float dy = -0.5f; dy <= +0.5f; dy += 1.0f)
+    for (float dx = -0.5f; dx <= +0.5f; dx += 1.0f) {
+        // (x,y) are the coordinates of this sample point.
+        F x = cx + dx,
+          y = cy + dy;
+
+        // ix_and_ptr() will clamp to the image's bounds for us.
+        const uint32_t* ptr;
+        U32 ix = ix_and_ptr(&ptr, ctx, x,y);
+
+        U16 sr,sg,sb,sa;
+        from_8888(gather<U32>(ptr, ix), &sr,&sg,&sb,&sa);
+
+        // In bilinear interpolation, the 4 pixels at +/- 0.5 offsets from the sample pixel center
+        // are combined in direct proportion to their area overlapping that logical query pixel.
+        // At positive offsets, the x-axis contribution to that rectangle is fx,
+        // or (1-fx) at negative x.  Same deal for y.
+        F sx = (dx > 0) ? fx : 1.0f - fx,
+          sy = (dy > 0) ? fy : 1.0f - fy;
+
+        U16 area = (dy == 0.5f && dx == 0.5f) ? remaining
+                                              : cast<U16>(sx * sy * bias);
+        for (size_t i = 0; i < N; i++) {
+            SkASSERT(remaining[i] >= area[i]);
+        }
+        remaining -= area;
+
+        r += sr * area;
+        g += sg * area;
+        b += sb * area;
+        a += sa * area;
+    }
+
+    r = (r + bias/2) / bias;
+    g = (g + bias/2) / bias;
+    b = (b + bias/2) / bias;
+    a = (a + bias/2) / bias;
+}
+#endif
+
 // Now we'll add null stand-ins for stages we haven't implemented in lowp.
 // If a pipeline uses these stages, it'll boot it out of lowp into highp.
 #define NOT_IMPLEMENTED(st) static void (*st)(void) = nullptr;
     NOT_IMPLEMENTED(callback)
-    NOT_IMPLEMENTED(load_rgba)
-    NOT_IMPLEMENTED(store_rgba)
     NOT_IMPLEMENTED(unbounded_set_rgb)
     NOT_IMPLEMENTED(unbounded_uniform_color)
     NOT_IMPLEMENTED(unpremul)
-    NOT_IMPLEMENTED(dither)
+    NOT_IMPLEMENTED(dither)  // TODO
     NOT_IMPLEMENTED(from_srgb)
     NOT_IMPLEMENTED(to_srgb)
     NOT_IMPLEMENTED(load_f16)
@@ -3296,7 +3400,7 @@
     NOT_IMPLEMENTED(store_1010102)
     NOT_IMPLEMENTED(gather_1010102)
     NOT_IMPLEMENTED(store_u16_be)
-    NOT_IMPLEMENTED(byte_tables)
+    NOT_IMPLEMENTED(byte_tables)  // TODO
     NOT_IMPLEMENTED(colorburn)
     NOT_IMPLEMENTED(colordodge)
     NOT_IMPLEMENTED(softlight)
@@ -3306,33 +3410,32 @@
     NOT_IMPLEMENTED(luminosity)
     NOT_IMPLEMENTED(matrix_3x3)
     NOT_IMPLEMENTED(matrix_3x4)
-    NOT_IMPLEMENTED(matrix_4x5)
-    NOT_IMPLEMENTED(matrix_4x3)
+    NOT_IMPLEMENTED(matrix_4x5)  // TODO
+    NOT_IMPLEMENTED(matrix_4x3)  // TODO
     NOT_IMPLEMENTED(parametric)
     NOT_IMPLEMENTED(gamma)
     NOT_IMPLEMENTED(rgb_to_hsl)
     NOT_IMPLEMENTED(hsl_to_rgb)
-    NOT_IMPLEMENTED(gauss_a_to_rgba)
-    NOT_IMPLEMENTED(mirror_x)
-    NOT_IMPLEMENTED(repeat_x)
-    NOT_IMPLEMENTED(mirror_y)
-    NOT_IMPLEMENTED(repeat_y)
+    NOT_IMPLEMENTED(gauss_a_to_rgba)  // TODO
+    NOT_IMPLEMENTED(mirror_x)         // TODO
+    NOT_IMPLEMENTED(repeat_x)         // TODO
+    NOT_IMPLEMENTED(mirror_y)         // TODO
+    NOT_IMPLEMENTED(repeat_y)         // TODO
     NOT_IMPLEMENTED(negate_x)
-    NOT_IMPLEMENTED(bilerp_clamp_8888)
-    NOT_IMPLEMENTED(bilinear_nx)
-    NOT_IMPLEMENTED(bilinear_ny)
-    NOT_IMPLEMENTED(bilinear_px)
-    NOT_IMPLEMENTED(bilinear_py)
-    NOT_IMPLEMENTED(bicubic_n3x)
-    NOT_IMPLEMENTED(bicubic_n1x)
-    NOT_IMPLEMENTED(bicubic_p1x)
-    NOT_IMPLEMENTED(bicubic_p3x)
-    NOT_IMPLEMENTED(bicubic_n3y)
-    NOT_IMPLEMENTED(bicubic_n1y)
-    NOT_IMPLEMENTED(bicubic_p1y)
-    NOT_IMPLEMENTED(bicubic_p3y)
-    NOT_IMPLEMENTED(save_xy)
-    NOT_IMPLEMENTED(accumulate)
+    NOT_IMPLEMENTED(bilinear_nx)      // TODO
+    NOT_IMPLEMENTED(bilinear_ny)      // TODO
+    NOT_IMPLEMENTED(bilinear_px)      // TODO
+    NOT_IMPLEMENTED(bilinear_py)      // TODO
+    NOT_IMPLEMENTED(bicubic_n3x)      // TODO
+    NOT_IMPLEMENTED(bicubic_n1x)      // TODO
+    NOT_IMPLEMENTED(bicubic_p1x)      // TODO
+    NOT_IMPLEMENTED(bicubic_p3x)      // TODO
+    NOT_IMPLEMENTED(bicubic_n3y)      // TODO
+    NOT_IMPLEMENTED(bicubic_n1y)      // TODO
+    NOT_IMPLEMENTED(bicubic_p1y)      // TODO
+    NOT_IMPLEMENTED(bicubic_p3y)      // TODO
+    NOT_IMPLEMENTED(save_xy)          // TODO
+    NOT_IMPLEMENTED(accumulate)       // TODO
     NOT_IMPLEMENTED(xy_to_2pt_conical_well_behaved)
     NOT_IMPLEMENTED(xy_to_2pt_conical_strip)
     NOT_IMPLEMENTED(xy_to_2pt_conical_focal_on_circle)
diff --git a/src/pathops/SkPathOpsTSect.cpp b/src/pathops/SkPathOpsTSect.cpp
index 81ec84e..8c438cc 100644
--- a/src/pathops/SkPathOpsTSect.cpp
+++ b/src/pathops/SkPathOpsTSect.cpp
@@ -885,16 +885,16 @@
     SkOPASSERT(oppStartT < oppEndT);
     SkASSERT(coinStart == first->fStartT);
     SkASSERT(coinEnd == last->fEndT);
-    SkOPASSERT(oppStartT == oppFirst->fStartT);
-    SkOPASSERT(oppEndT == oppLast->fEndT);
     if (!oppFirst) {
         *result = nullptr;
         return true;
     }
+    SkOPASSERT(oppStartT == oppFirst->fStartT);
     if (!oppLast) {
         *result = nullptr;
         return true;
     }
+    SkOPASSERT(oppEndT == oppLast->fEndT);
     // reduce coincident runs to single entries
     this->validate();
     sect2->validate();
diff --git a/src/pdf/SkClusterator.cpp b/src/pdf/SkClusterator.cpp
index 2827810..6a75015 100644
--- a/src/pdf/SkClusterator.cpp
+++ b/src/pdf/SkClusterator.cpp
@@ -30,10 +30,10 @@
     , fUtf8Text(run.text().data())
     , fGlyphCount(SkToU32(run.glyphsIDs().size()))
     , fTextByteLength(SkToU32(run.text().size()))
+    , fReversedChars(fClusters ? is_reversed(fClusters, fGlyphCount) : false)
 {
     if (fClusters) {
         SkASSERT(fUtf8Text && fTextByteLength > 0 && fGlyphCount > 0);
-        fReversedChars = is_reversed(fClusters, fGlyphCount);
     } else {
         SkASSERT(!fUtf8Text && fTextByteLength == 0);
     }
diff --git a/src/pdf/SkClusterator.h b/src/pdf/SkClusterator.h
index 40d9994..ec87d4d 100644
--- a/src/pdf/SkClusterator.h
+++ b/src/pdf/SkClusterator.h
@@ -35,12 +35,12 @@
     Cluster next();
 
 private:
-    const uint32_t* fClusters;
-    const char* fUtf8Text;
-    uint32_t fGlyphCount;
-    uint32_t fTextByteLength;
+    uint32_t const * const fClusters;
+    char const * const fUtf8Text;
+    uint32_t const fGlyphCount;
+    uint32_t const fTextByteLength;
+    bool const fReversedChars;
     uint32_t fCurrentGlyphIndex = 0;
-    bool fReversedChars = false;
 };
 
 
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index 74c2ca6..a4998e1 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -127,14 +127,6 @@
     draw.drawPoints(mode, count, points, paint, device);
 }
 
-// If the paint will definitely draw opaquely, replace kSrc with
-// kSrcOver.  http://crbug.com/473572
-static void replace_srcmode_on_opaque_paint(SkPaint* paint) {
-    if (kSrcOver_SkXfermodeInterpretation == SkInterpretXfermode(*paint, false)) {
-        paint->setBlendMode(SkBlendMode::kSrcOver);
-    }
-}
-
 // A shader's matrix is:  CTMM x LocalMatrix x WrappingLocalMatrix.  We want to
 // switch to device space, where CTM = I, while keeping the original behavior.
 //
@@ -144,6 +136,7 @@
 //                                 NewWrappingMatrix = InvLocalMatrix * CTM * LocalMatrix
 //
 static void transform_shader(SkPaint* paint, const SkMatrix& ctm) {
+    SkASSERT(!ctm.isIdentity());
     SkMatrix lm = SkPDFUtils::GetShaderLocalMatrix(paint->getShader());
     SkMatrix lmInv;
     if (lm.invert(&lmInv)) {
@@ -152,53 +145,33 @@
     }
 }
 
-static void emit_pdf_color(SkColor4f color, SkWStream* result) {
-    SkASSERT(color.fA == 1);  // We handle alpha elsewhere.
-    SkPDFUtils::AppendColorComponentF(color.fR, result);
-    result->writeText(" ");
-    SkPDFUtils::AppendColorComponentF(color.fG, result);
-    result->writeText(" ");
-    SkPDFUtils::AppendColorComponentF(color.fB, result);
-    result->writeText(" ");
-}
-
-// If the paint has a color filter, apply the color filter to the shader or the
-// paint color.  Remove the color filter.
-void remove_color_filter(SkPaint* paint) {
+static SkTCopyOnFirstWrite<SkPaint> clean_paint(const SkPaint& srcPaint) {
+    SkTCopyOnFirstWrite<SkPaint> paint(srcPaint);
+    // If the paint will definitely draw opaquely, replace kSrc with
+    // kSrcOver.  http://crbug.com/473572
+    if (SkBlendMode::kSrcOver != paint->getBlendMode() &&
+        kSrcOver_SkXfermodeInterpretation == SkInterpretXfermode(*paint, false))
+    {
+        paint.writable()->setBlendMode(SkBlendMode::kSrcOver);
+    }
+    // If the paint has a color filter, apply the color filter to the shader or the
+    // paint color.  Remove the color filter.
     if (SkColorFilter* cf = paint->getColorFilter()) {
+        SkPaint* p = paint.writable();
         if (SkShader* shader = paint->getShader()) {
-            paint->setShader(shader->makeWithColorFilter(paint->refColorFilter()));
+            p->setShader(shader->makeWithColorFilter(paint->refColorFilter()));
         } else {
-            paint->setColor4f(cf->filterColor4f(paint->getColor4f(), nullptr), nullptr);
+            p->setColor4f(cf->filterColor4f(paint->getColor4f(), nullptr), nullptr);
         }
-        paint->setColorFilter(nullptr);
+        p->setColorFilter(nullptr);
     }
+    return paint;
 }
 
-SkPDFDevice::GraphicStackState::GraphicStackState(SkDynamicMemoryWStream* s) : fContentStream(s) {
-}
-
-void SkPDFDevice::GraphicStackState::drainStack() {
-    if (fContentStream) {
-        while (fStackDepth) {
-            this->pop();
-        }
+static void set_style(SkTCopyOnFirstWrite<SkPaint>* paint, SkPaint::Style style) {
+    if (paint->get()->getStyle() != style) {
+        paint->writable()->setStyle(style);
     }
-    SkASSERT(fStackDepth == 0);
-}
-
-void SkPDFDevice::GraphicStackState::push() {
-    SkASSERT(fStackDepth < kMaxStackDepth);
-    fContentStream->writeText("q\n");
-    fStackDepth++;
-    fEntries[fStackDepth] = fEntries[fStackDepth - 1];
-}
-
-void SkPDFDevice::GraphicStackState::pop() {
-    SkASSERT(fStackDepth > 0);
-    fContentStream->writeText("Q\n");
-    fEntries[fStackDepth] = SkPDFDevice::GraphicStateEntry();
-    fStackDepth--;
 }
 
 /* Calculate an inverted path's equivalent non-inverted path, given the
@@ -211,170 +184,6 @@
     return Op(to_path(bounds), invPath, kIntersect_SkPathOp, outPath);
 }
 
-static SkRect rect_intersect(SkRect u, SkRect v) {
-    if (u.isEmpty() || v.isEmpty()) { return {0, 0, 0, 0}; }
-    return u.intersect(v) ? u : SkRect{0, 0, 0, 0};
-}
-
-// Test to see if the clipstack is a simple rect, If so, we can avoid all PathOps code
-// and speed thing up.
-static bool is_rect(const SkClipStack& clipStack, const SkRect& bounds, SkRect* dst) {
-    SkRect currentClip = bounds;
-    SkClipStack::Iter iter(clipStack, SkClipStack::Iter::kBottom_IterStart);
-    while (const SkClipStack::Element* element = iter.next()) {
-        SkRect elementRect{0, 0, 0, 0};
-        switch (element->getDeviceSpaceType()) {
-            case SkClipStack::Element::DeviceSpaceType::kEmpty:
-                break;
-            case SkClipStack::Element::DeviceSpaceType::kRect:
-                elementRect = element->getDeviceSpaceRect();
-                break;
-            default:
-                return false;
-        }
-        switch (element->getOp()) {
-            case kReplace_SkClipOp:
-                currentClip = rect_intersect(bounds, elementRect);
-                break;
-            case SkClipOp::kIntersect:
-                currentClip = rect_intersect(currentClip, elementRect);
-                break;
-            default:
-                return false;
-        }
-    }
-    *dst = currentClip;
-    return true;
-}
-
-static void append_clip(const SkClipStack& clipStack,
-                        const SkIRect& bounds,
-                        SkWStream* wStream) {
-    // The bounds are slightly outset to ensure this is correct in the
-    // face of floating-point accuracy and possible SkRegion bitmap
-    // approximations.
-    SkRect outsetBounds = SkRect::Make(bounds.makeOutset(1, 1));
-
-    SkRect clipStackRect;
-    if (is_rect(clipStack, outsetBounds, &clipStackRect)) {
-        SkPDFUtils::AppendRectangle(clipStackRect, wStream);
-        wStream->writeText("W* n\n");
-        return;
-    }
-
-    SkPath clipPath;
-    (void)clipStack.asPath(&clipPath);
-
-    if (Op(clipPath, to_path(outsetBounds), kIntersect_SkPathOp, &clipPath)) {
-        SkPDFUtils::EmitPath(clipPath, SkPaint::kFill_Style, wStream);
-        SkPath::FillType clipFill = clipPath.getFillType();
-        NOT_IMPLEMENTED(clipFill == SkPath::kInverseEvenOdd_FillType, false);
-        NOT_IMPLEMENTED(clipFill == SkPath::kInverseWinding_FillType, false);
-        if (clipFill == SkPath::kEvenOdd_FillType) {
-            wStream->writeText("W* n\n");
-        } else {
-            wStream->writeText("W n\n");
-        }
-    }
-    // If Op() fails (pathological case; e.g. input values are
-    // extremely large or NaN), emit no clip at all.
-}
-
-// TODO(vandebo): Take advantage of SkClipStack::getSaveCount(), the PDF
-// graphic state stack, and the fact that we can know all the clips used
-// on the page to optimize this.
-void SkPDFDevice::GraphicStackState::updateClip(const SkClipStack* clipStack,
-                                                const SkIRect& bounds) {
-    uint32_t clipStackGenID = clipStack ? clipStack->getTopmostGenID()
-                                        : SkClipStack::kWideOpenGenID;
-    if (clipStackGenID == currentEntry()->fClipStackGenID) {
-        return;
-    }
-
-    while (fStackDepth > 0) {
-        this->pop();
-        if (clipStackGenID == currentEntry()->fClipStackGenID) {
-            return;
-        }
-    }
-    SkASSERT(currentEntry()->fClipStackGenID == SkClipStack::kWideOpenGenID);
-    if (clipStackGenID != SkClipStack::kWideOpenGenID) {
-        SkASSERT(clipStack);
-        this->push();
-
-        currentEntry()->fClipStackGenID = clipStackGenID;
-        append_clip(*clipStack, bounds, fContentStream);
-    }
-}
-
-static void append_transform(const SkMatrix& matrix, SkWStream* content) {
-    SkScalar values[6];
-    if (!matrix.asAffine(values)) {
-        SkMatrix::SetAffineIdentity(values);
-    }
-    for (SkScalar v : values) {
-        SkPDFUtils::AppendScalar(v, content);
-        content->writeText(" ");
-    }
-    content->writeText("cm\n");
-}
-
-void SkPDFDevice::GraphicStackState::updateMatrix(const SkMatrix& matrix) {
-    if (matrix == currentEntry()->fMatrix) {
-        return;
-    }
-
-    if (currentEntry()->fMatrix.getType() != SkMatrix::kIdentity_Mask) {
-        SkASSERT(fStackDepth > 0);
-        SkASSERT(fEntries[fStackDepth].fClipStackGenID ==
-                 fEntries[fStackDepth -1].fClipStackGenID);
-        this->pop();
-
-        SkASSERT(currentEntry()->fMatrix.getType() == SkMatrix::kIdentity_Mask);
-    }
-    if (matrix.getType() == SkMatrix::kIdentity_Mask) {
-        return;
-    }
-
-    this->push();
-    append_transform(matrix, fContentStream);
-    currentEntry()->fMatrix = matrix;
-}
-
-void SkPDFDevice::GraphicStackState::updateDrawingState(const SkPDFDevice::GraphicStateEntry& state) {
-    // PDF treats a shader as a color, so we only set one or the other.
-    if (state.fShaderIndex >= 0) {
-        if (state.fShaderIndex != currentEntry()->fShaderIndex) {
-            SkPDFUtils::ApplyPattern(state.fShaderIndex, fContentStream);
-            currentEntry()->fShaderIndex = state.fShaderIndex;
-        }
-    } else {
-        if (state.fColor != currentEntry()->fColor ||
-                currentEntry()->fShaderIndex >= 0) {
-            emit_pdf_color(state.fColor, fContentStream);
-            fContentStream->writeText("RG ");
-            emit_pdf_color(state.fColor, fContentStream);
-            fContentStream->writeText("rg\n");
-            currentEntry()->fColor = state.fColor;
-            currentEntry()->fShaderIndex = -1;
-        }
-    }
-
-    if (state.fGraphicStateIndex != currentEntry()->fGraphicStateIndex) {
-        SkPDFUtils::ApplyGraphicState(state.fGraphicStateIndex, fContentStream);
-        currentEntry()->fGraphicStateIndex = state.fGraphicStateIndex;
-    }
-
-    if (state.fTextScaleX) {
-        if (state.fTextScaleX != currentEntry()->fTextScaleX) {
-            SkScalar pdfScale = state.fTextScaleX * 100;
-            SkPDFUtils::AppendScalar(pdfScale, fContentStream);
-            fContentStream->writeText(" Tz\n");
-            currentEntry()->fTextScaleX = state.fTextScaleX;
-        }
-    }
-}
-
 SkBaseDevice* SkPDFDevice::onCreateDevice(const CreateInfo& cinfo, const SkPaint* layerPaint) {
     // PDF does not support image filters, so render them on CPU.
     // Note that this rendering is done at "screen" resolution (100dpi), not
@@ -484,21 +293,20 @@
 SkPDFDevice::~SkPDFDevice() = default;
 
 void SkPDFDevice::reset() {
-    fLinkToURLs = std::vector<RectWithData>();
-    fLinkToDestinations = std::vector<RectWithData>();
-    fNamedDestinations = std::vector<NamedDestination>();
     fGraphicStateResources.reset();
     fXObjectResources.reset();
     fShaderResources.reset();
     fFontResources.reset();
     fContent.reset();
-    fActiveStackState = GraphicStackState();
+    fActiveStackState = SkPDFGraphicStackState();
 }
 
 void SkPDFDevice::drawAnnotation(const SkRect& rect, const char key[], SkData* value) {
     if (!value) {
         return;
     }
+    const SkMatrix& pageXform = fDocument->currentPageTransform();
+    SkPoint deviceOffset = {(float)this->getOrigin().x(), (float)this->getOrigin().y()};
     if (rect.isEmpty()) {
         if (!strcmp(key, SkPDFGetNodeIdKey())) {
             int nodeID;
@@ -508,9 +316,10 @@
             return;
         }
         if (!strcmp(SkAnnotationKeys::Define_Named_Dest_Key(), key)) {
-            SkPoint transformedPoint;
-            this->ctm().mapXY(rect.x(), rect.y(), &transformedPoint);
-            fNamedDestinations.emplace_back(NamedDestination{sk_ref_sp(value), transformedPoint});
+            SkPoint p = deviceOffset + this->ctm().mapXY(rect.x(), rect.y());
+            pageXform.mapPoints(&p, 1);
+            auto pg = fDocument->currentPage();
+            fDocument->fNamedDestinations.push_back(SkPDFNamedDestination{sk_ref_sp(value), p, pg});
         }
         return;
     }
@@ -521,14 +330,17 @@
     (void)this->cs().asPath(&clip);
     Op(clip, path, kIntersect_SkPathOp, &path);
     // PDF wants a rectangle only.
-    SkRect transformedRect = path.getBounds();
+    SkRect transformedRect =
+        pageXform.mapRect(path.getBounds().makeOffset(deviceOffset.x(), deviceOffset.y()));
     if (transformedRect.isEmpty()) {
         return;
     }
     if (!strcmp(SkAnnotationKeys::URL_Key(), key)) {
-        fLinkToURLs.emplace_back(RectWithData{transformedRect, sk_ref_sp(value)});
+        fDocument->fCurrentPageLinkToURLs.push_back(
+                std::make_pair(sk_ref_sp(value), transformedRect));
     } else if (!strcmp(SkAnnotationKeys::Link_Named_Dest_Key(), key)) {
-        fLinkToDestinations.emplace_back(RectWithData{transformedRect, sk_ref_sp(value)});
+        fDocument->fCurrentPageLinkToDestinations.emplace_back(
+                std::make_pair(sk_ref_sp(value), transformedRect));
     }
 }
 
@@ -555,46 +367,44 @@
     if (this->hasEmptyClip()) {
         return;
     }
-    SkPaint passedPaint = srcPaint;
-    remove_color_filter(&passedPaint);
-    replace_srcmode_on_opaque_paint(&passedPaint);
-    if (SkCanvas::kPoints_PointMode != mode) {
-        passedPaint.setStyle(SkPaint::kStroke_Style);
-    }
     if (count == 0) {
         return;
     }
+    SkTCopyOnFirstWrite<SkPaint> paint(clean_paint(srcPaint));
+
+
+
+    if (SkCanvas::kPoints_PointMode != mode) {
+        set_style(&paint, SkPaint::kStroke_Style);
+    }
 
     // SkDraw::drawPoints converts to multiple calls to fDevice->drawPath.
     // We only use this when there's a path effect because of the overhead
     // of multiple calls to setUpContentEntry it causes.
-    if (passedPaint.getPathEffect()) {
-        draw_points(mode, count, points, passedPaint,
+    if (paint->getPathEffect()) {
+        draw_points(mode, count, points, *paint,
                     this->devClipBounds(), this->ctm(), this);
         return;
     }
 
-    const SkPaint* paint = &passedPaint;
-    SkPaint modifiedPaint;
 
-    if (mode == SkCanvas::kPoints_PointMode &&
-            paint->getStrokeCap() != SkPaint::kRound_Cap) {
-        modifiedPaint = *paint;
-        paint = &modifiedPaint;
+    if (mode == SkCanvas::kPoints_PointMode && paint->getStrokeCap() != SkPaint::kRound_Cap) {
         if (paint->getStrokeWidth()) {
             // PDF won't draw a single point with square/butt caps because the
             // orientation is ambiguous.  Draw a rectangle instead.
-            modifiedPaint.setStyle(SkPaint::kFill_Style);
+            set_style(&paint, SkPaint::kFill_Style);
             SkScalar strokeWidth = paint->getStrokeWidth();
             SkScalar halfStroke = SkScalarHalf(strokeWidth);
             for (size_t i = 0; i < count; i++) {
                 SkRect r = SkRect::MakeXYWH(points[i].fX, points[i].fY, 0, 0);
                 r.inset(-halfStroke, -halfStroke);
-                this->drawRect(r, modifiedPaint);
+                this->drawRect(r, *paint);
             }
             return;
         } else {
-            modifiedPaint.setStrokeCap(SkPaint::kRound_Cap);
+            if (paint->getStrokeCap() != SkPaint::kRound_Cap) {
+                paint.writable()->setStrokeCap(SkPaint::kRound_Cap);
+            }
         }
     }
 
@@ -631,96 +441,26 @@
     }
 }
 
-static std::unique_ptr<SkPDFDict> create_link_annotation(const SkRect& translatedRect) {
-    auto annotation = SkPDFMakeDict("Annot");
-    annotation->insertName("Subtype", "Link");
-    annotation->insertInt("F", 4);  // required by ISO 19005
-    // Border: 0 = Horizontal corner radius.
-    //         0 = Vertical corner radius.
-    //         0 = Width, 0 = no border.
-    annotation->insertObject("Border", SkPDFMakeArray(0, 0, 0));
-
-    annotation->insertObject("Rect", SkPDFMakeArray(translatedRect.fLeft,
-                                                    translatedRect.fTop,
-                                                    translatedRect.fRight,
-                                                    translatedRect.fBottom));
-    return annotation;
-}
-
-static std::unique_ptr<SkPDFDict> create_link_to_url(const SkData* urlData, const SkRect& r) {
-    std::unique_ptr<SkPDFDict> annotation = create_link_annotation(r);
-    SkString url(static_cast<const char *>(urlData->data()),
-                 urlData->size() - 1);
-    auto action = SkPDFMakeDict("Action");
-    action->insertName("S", "URI");
-    action->insertString("URI", url);
-    annotation->insertObject("A", std::move(action));
-    return annotation;
-}
-
-static std::unique_ptr<SkPDFDict> create_link_named_dest(const SkData* nameData,
-                                                         const SkRect& r) {
-    std::unique_ptr<SkPDFDict> annotation = create_link_annotation(r);
-    SkString name(static_cast<const char *>(nameData->data()),
-                  nameData->size() - 1);
-    annotation->insertName("Dest", name);
-    return annotation;
-}
-
-void SkPDFDevice::drawRect(const SkRect& rect,
-                           const SkPaint& srcPaint) {
-    if (this->hasEmptyClip()) {
-        return;
-    }
-    SkPaint paint = srcPaint;
-    remove_color_filter(&paint);
-    replace_srcmode_on_opaque_paint(&paint);
+void SkPDFDevice::drawRect(const SkRect& rect, const SkPaint& paint) {
     SkRect r = rect;
     r.sort();
-
-    if (paint.getPathEffect() || paint.getMaskFilter() || this->ctm().hasPerspective()) {
-        this->drawPath(to_path(r), paint, true);
-        return;
-    }
-
-    ScopedContentEntry content(this, paint);
-    if (!content) {
-        return;
-    }
-    SkPDFUtils::AppendRectangle(r, content.stream());
-    SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType, content.stream());
+    this->internalDrawPath(this->cs(), this->ctm(), to_path(r), paint, true);
 }
 
-void SkPDFDevice::drawRRect(const SkRRect& rrect,
-                            const SkPaint& srcPaint) {
-    if (this->hasEmptyClip()) {
-        return;
-    }
-    SkPaint paint = srcPaint;
-    remove_color_filter(&paint);
-    replace_srcmode_on_opaque_paint(&paint);
-    SkPath  path;
+void SkPDFDevice::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
+    SkPath path;
     path.addRRect(rrect);
-    this->drawPath(path, paint, true);
+    this->internalDrawPath(this->cs(), this->ctm(), path, paint, true);
 }
 
-void SkPDFDevice::drawOval(const SkRect& oval,
-                           const SkPaint& srcPaint) {
-    if (this->hasEmptyClip()) {
-        return;
-    }
-    SkPaint paint = srcPaint;
-    remove_color_filter(&paint);
-    replace_srcmode_on_opaque_paint(&paint);
-    SkPath  path;
+void SkPDFDevice::drawOval(const SkRect& oval, const SkPaint& paint) {
+    SkPath path;
     path.addOval(oval);
-    this->drawPath(path, paint, true);
+    this->internalDrawPath(this->cs(), this->ctm(), path, paint, true);
 }
 
-void SkPDFDevice::drawPath(const SkPath& origPath,
-                           const SkPaint& srcPaint,
-                           bool pathIsMutable) {
-    this->internalDrawPath(this->cs(), this->ctm(), origPath, srcPaint, pathIsMutable);
+void SkPDFDevice::drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable) {
+    this->internalDrawPath(this->cs(), this->ctm(), path, paint, pathIsMutable);
 }
 
 void SkPDFDevice::internalDrawPathWithFilter(const SkClipStack& clipStack,
@@ -765,7 +505,9 @@
     if (!content) {
         return;
     }
-    this->addSMaskGraphicState(std::move(maskDevice), content.stream());
+    this->setGraphicState(SkPDFGraphicState::GetSMaskGraphicState(
+            maskDevice->makeFormXObjectFromDevice(dstMaskBounds, true), false,
+            SkPDFGraphicState::kLuminosity_SMaskMode, fDocument), content.stream());
     SkPDFUtils::AppendRectangle(SkRect::Make(dstMaskBounds), content.stream());
     SkPDFUtils::PaintPath(SkPaint::kFill_Style, path.getFillType(), content.stream());
     this->clearMaskOnGraphicState(content.stream());
@@ -775,13 +517,6 @@
     SkPDFUtils::ApplyGraphicState(add_resource(fGraphicStateResources, gs), content);
 }
 
-void SkPDFDevice::addSMaskGraphicState(sk_sp<SkPDFDevice> maskDevice,
-                                       SkDynamicMemoryWStream* contentStream) {
-    this->setGraphicState(SkPDFGraphicState::GetSMaskGraphicState(
-            maskDevice->makeFormXObjectFromDevice(true), false,
-            SkPDFGraphicState::kLuminosity_SMaskMode, fDocument), contentStream);
-}
-
 void SkPDFDevice::clearMaskOnGraphicState(SkDynamicMemoryWStream* contentStream) {
     // The no-softmask graphic state is used to "turn off" the mask for later draw calls.
     SkPDFIndirectReference& noSMaskGS = fDocument->fNoSmaskGraphicState;
@@ -801,20 +536,18 @@
     if (clipStack.isEmpty(this->bounds())) {
         return;
     }
-    SkPaint paint = srcPaint;
-    remove_color_filter(&paint);
-    replace_srcmode_on_opaque_paint(&paint);
+    SkTCopyOnFirstWrite<SkPaint> paint(clean_paint(srcPaint));
     SkPath modifiedPath;
     SkPath* pathPtr = const_cast<SkPath*>(&origPath);
 
-    if (paint.getMaskFilter()) {
-        this->internalDrawPathWithFilter(clipStack, ctm, origPath, paint);
+    if (paint->getMaskFilter()) {
+        this->internalDrawPathWithFilter(clipStack, ctm, origPath, *paint);
         return;
     }
 
     SkMatrix matrix = ctm;
 
-    if (paint.getPathEffect()) {
+    if (paint->getPathEffect()) {
         if (clipStack.isEmpty(this->bounds())) {
             return;
         }
@@ -823,16 +556,18 @@
             pathPtr = &modifiedPath;
             pathIsMutable = true;
         }
-        if (paint.getFillPath(*pathPtr, pathPtr)) {
-            paint.setStyle(SkPaint::kFill_Style);
+        if (paint->getFillPath(*pathPtr, pathPtr)) {
+            set_style(&paint, SkPaint::kFill_Style);
         } else {
-            paint.setStyle(SkPaint::kStroke_Style);
-            paint.setStrokeWidth(0);
+            set_style(&paint, SkPaint::kStroke_Style);
+            if (paint->getStrokeWidth() != 0) {
+                paint.writable()->setStrokeWidth(0);
+            }
         }
-        paint.setPathEffect(nullptr);
+        paint.writable()->setPathEffect(nullptr);
     }
 
-    if (this->handleInversePath(*pathPtr, paint, pathIsMutable)) {
+    if (this->handleInversePath(*pathPtr, *paint, pathIsMutable)) {
         return;
     }
     if (matrix.getType() & SkMatrix::kPerspective_Mask) {
@@ -842,13 +577,13 @@
             pathIsMutable = true;
         }
         pathPtr->transform(matrix);
-        if (paint.getShader()) {
-            transform_shader(&paint, matrix);
+        if (paint->getShader()) {
+            transform_shader(paint.writable(), matrix);
         }
         matrix = SkMatrix::I();
     }
 
-    ScopedContentEntry content(this, &clipStack, matrix, paint);
+    ScopedContentEntry content(this, &clipStack, matrix, *paint);
     if (!content) {
         return;
     }
@@ -856,12 +591,12 @@
     SkScalar matrixScale = matrix.mapRadius(1.0f);
     SkScalar tolerance = matrixScale > 0.0f ? kToleranceScale / matrixScale : kToleranceScale;
     bool consumeDegeratePathSegments =
-           paint.getStyle() == SkPaint::kFill_Style ||
-           (paint.getStrokeCap() != SkPaint::kRound_Cap &&
-            paint.getStrokeCap() != SkPaint::kSquare_Cap);
-    SkPDFUtils::EmitPath(*pathPtr, paint.getStyle(), consumeDegeratePathSegments, content.stream(),
+           paint->getStyle() == SkPaint::kFill_Style ||
+           (paint->getStrokeCap() != SkPaint::kRound_Cap &&
+            paint->getStrokeCap() != SkPaint::kSquare_Cap);
+    SkPDFUtils::EmitPath(*pathPtr, paint->getStyle(), consumeDegeratePathSegments, content.stream(),
                          tolerance);
-    SkPDFUtils::PaintPath(paint.getStyle(), pathPtr->getFillType(), content.stream());
+    SkPDFUtils::PaintPath(paint->getStyle(), pathPtr->getFillType(), content.stream());
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -885,25 +620,12 @@
     this->internalDrawImageRect(SkKeyedImage(bm), src, dst, paint, this->ctm());
 }
 
-void SkPDFDevice::drawBitmap(const SkBitmap& bm, SkScalar x, SkScalar y, const SkPaint& paint) {
-    SkASSERT(!bm.drawsNothing());
-    auto r = SkRect::MakeXYWH(x, y, bm.width(), bm.height());
-    this->internalDrawImageRect(SkKeyedImage(bm), nullptr, r, paint, this->ctm());
-}
-
 void SkPDFDevice::drawSprite(const SkBitmap& bm, int x, int y, const SkPaint& paint) {
     SkASSERT(!bm.drawsNothing());
     auto r = SkRect::MakeXYWH(x, y, bm.width(), bm.height());
     this->internalDrawImageRect(SkKeyedImage(bm), nullptr, r, paint, SkMatrix::I());
 }
 
-void SkPDFDevice::drawImage(const SkImage* image, SkScalar x, SkScalar y, const SkPaint& paint) {
-    SkASSERT(image);
-    auto r = SkRect::MakeXYWH(x, y, image->width(), image->height());
-    this->internalDrawImageRect(SkKeyedImage(sk_ref_sp(const_cast<SkImage*>(image))),
-                                nullptr, r, paint, this->ctm());
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 
 namespace {
@@ -1028,7 +750,7 @@
                       }
                       rec->fPos += 1; // move to the next glyph's position
                   }, &rec);
-    this->drawPath(path, runPaint, true);
+    this->internalDrawPath(this->cs(), this->ctm(), path, runPaint, true);
 
     SkFont transparentFont = glyphRun.font();
     transparentFont.setEmbolden(false); // Stop Recursion
@@ -1112,10 +834,8 @@
 
     SkRect clipStackBounds = this->cs().bounds(this->bounds());
 
-    SkPaint paint(runPaint);
-    remove_color_filter(&paint);
-    replace_srcmode_on_opaque_paint(&paint);
-    ScopedContentEntry content(this, paint, glyphRunFont.getScaleX());
+    SkTCopyOnFirstWrite<SkPaint> paint(clean_paint(runPaint));
+    ScopedContentEntry content(this, *paint, glyphRunFont.getScaleX());
     if (!content) {
         return;
     }
@@ -1262,21 +982,6 @@
     // our onCreateCompatibleDevice() always creates SkPDFDevice subclasses.
     SkPDFDevice* pdfDevice = static_cast<SkPDFDevice*>(device);
 
-    SkScalar scalarX = SkIntToScalar(x);
-    SkScalar scalarY = SkIntToScalar(y);
-    for (const RectWithData& l : pdfDevice->fLinkToURLs) {
-        SkRect r = l.rect.makeOffset(scalarX, scalarY);
-        fLinkToURLs.emplace_back(RectWithData{r, l.data});
-    }
-    for (const RectWithData& l : pdfDevice->fLinkToDestinations) {
-        SkRect r = l.rect.makeOffset(scalarX, scalarY);
-        fLinkToDestinations.emplace_back(RectWithData{r, l.data});
-    }
-    for (const NamedDestination& d : pdfDevice->fNamedDestinations) {
-        SkPoint p = d.point + SkPoint::Make(scalarX, scalarY);
-        fNamedDestinations.emplace_back(NamedDestination{d.nameData, p});
-    }
-
     if (pdfDevice->isContentEmpty()) {
         return;
     }
@@ -1319,14 +1024,14 @@
 std::unique_ptr<SkStreamAsset> SkPDFDevice::content() {
     if (fActiveStackState.fContentStream) {
         fActiveStackState.drainStack();
-        fActiveStackState = GraphicStackState();
+        fActiveStackState = SkPDFGraphicStackState();
     }
     if (fContent.bytesWritten() == 0) {
         return skstd::make_unique<SkMemoryStream>();
     }
     SkDynamicMemoryWStream buffer;
     if (fInitialTransform.getType() != SkMatrix::kIdentity_Mask) {
-        append_transform(fInitialTransform, &buffer);
+        SkPDFUtils::AppendTransform(fInitialTransform, &buffer);
     }
     if (fNeedsExtraSave) {
         buffer.writeText("q\n");
@@ -1373,7 +1078,7 @@
             // To be consistent with the raster output, hairline strokes
             // are rendered as non-inverted.
             modifiedPath.toggleInverseFillType();
-            this->drawPath(modifiedPath, paint, true);
+            this->internalDrawPath(this->cs(), this->ctm(), modifiedPath, paint, true);
             return true;
         }
     }
@@ -1398,48 +1103,11 @@
         return false;
     }
 
-    this->drawPath(modifiedPath, noInversePaint, true);
+    this->internalDrawPath(this->cs(), this->ctm(), modifiedPath, noInversePaint, true);
     return true;
 }
 
-std::unique_ptr<SkPDFArray> SkPDFDevice::getAnnotations() {
-    std::unique_ptr<SkPDFArray> array;
-    size_t count = fLinkToURLs.size() + fLinkToDestinations.size();
-    if (0 == count) {
-        return array;
-    }
-    array = SkPDFMakeArray();
-    array->reserve(count);
-    for (const RectWithData& rectWithURL : fLinkToURLs) {
-        SkRect r;
-        fInitialTransform.mapRect(&r, rectWithURL.rect);
-        array->appendRef(fDocument->emit(*create_link_to_url(rectWithURL.data.get(), r)));
-    }
-    for (const RectWithData& linkToDestination : fLinkToDestinations) {
-        SkRect r;
-        fInitialTransform.mapRect(&r, linkToDestination.rect);
-        array->appendRef(
-                fDocument->emit(*create_link_named_dest(linkToDestination.data.get(), r)));
-    }
-    return array;
-}
-
-void SkPDFDevice::appendDestinations(SkPDFDict* dict, SkPDFIndirectReference page) const {
-    for (const NamedDestination& dest : fNamedDestinations) {
-        SkPoint p = fInitialTransform.mapXY(dest.point.x(), dest.point.y());
-        auto pdfDest = SkPDFMakeArray();
-        pdfDest->reserve(5);
-        pdfDest->appendRef(page);
-        pdfDest->appendName("XYZ");
-        pdfDest->appendScalar(p.x());
-        pdfDest->appendScalar(p.y());
-        pdfDest->appendInt(0);  // Leave zoom unchanged
-        dict->insertObject(SkString(static_cast<const char*>(dest.nameData->data())),
-                           std::move(pdfDest));
-    }
-}
-
-SkPDFIndirectReference SkPDFDevice::makeFormXObjectFromDevice(bool alpha) {
+SkPDFIndirectReference SkPDFDevice::makeFormXObjectFromDevice(SkIRect bounds, bool alpha) {
     SkMatrix inverseTransform = SkMatrix::I();
     if (!fInitialTransform.isIdentity()) {
         if (!fInitialTransform.invert(&inverseTransform)) {
@@ -1451,7 +1119,8 @@
 
     SkPDFIndirectReference xobject =
         SkPDFMakeFormXObject(fDocument, this->content(),
-                             SkPDFMakeArray(0, 0, this->width(), this->height()),
+                             SkPDFMakeArray(bounds.left(), bounds.top(),
+                                            bounds.right(), bounds.bottom()),
                              this->makeResourceDict(), inverseTransform, colorSpace);
     // We always draw the form xobjects that we create back into the device, so
     // we simply preserve the font usage instead of pulling it out and merging
@@ -1460,6 +1129,10 @@
     return xobject;
 }
 
+SkPDFIndirectReference SkPDFDevice::makeFormXObjectFromDevice(bool alpha) {
+    return this->makeFormXObjectFromDevice(SkIRect{0, 0, this->width(), this->height()}, alpha);
+}
+
 void SkPDFDevice::drawFormXObjectWithMask(SkPDFIndirectReference xObject,
                                           SkPDFIndirectReference sMask,
                                           SkBlendMode mode,
@@ -1491,7 +1164,7 @@
         const SkPaint& paint,
         const SkMatrix& initialTransform,
         SkScalar textScale,
-        SkPDFDevice::GraphicStateEntry* entry,
+        SkPDFGraphicStackState::Entry* entry,
         SkTHashSet<SkPDFIndirectReference>* shaderResources,
         SkTHashSet<SkPDFIndirectReference>* graphicStateResources) {
     NOT_IMPLEMENTED(paint.getPathEffect() != nullptr, false);
@@ -1593,16 +1266,16 @@
                 fContent.writeText("Q\nq\n");
                 fNeedsExtraSave = true;
             }
-            fActiveStackState = GraphicStackState(&fContent);
+            fActiveStackState = SkPDFGraphicStackState(&fContent);
         } else {
             SkASSERT(fActiveStackState.fContentStream = &fContent);
         }
     } else {
         fActiveStackState.drainStack();
-        fActiveStackState = GraphicStackState(&fContentBuffer);
+        fActiveStackState = SkPDFGraphicStackState(&fContentBuffer);
     }
     SkASSERT(fActiveStackState.fContentStream);
-    GraphicStateEntry entry;
+    SkPDFGraphicStackState::Entry entry;
     populate_graphic_state_entry_from_paint(
             fDocument,
             matrix,
@@ -1634,7 +1307,7 @@
     SkASSERT(fActiveStackState.fContentStream);
 
     fActiveStackState.drainStack();
-    fActiveStackState = GraphicStackState();
+    fActiveStackState = SkPDFGraphicStackState();
 
     if (blendMode == SkBlendMode::kDstOver) {
         SkASSERT(!dst);
@@ -1823,15 +1496,18 @@
     }
 
     // If the image is opaque and the paint's alpha is too, replace
-    // kSrc blendmode with kSrcOver.
-    SkPaint paint = srcPaint;
-    if (imageSubset.image()->isOpaque()) {
-        replace_srcmode_on_opaque_paint(&paint);
+    // kSrc blendmode with kSrcOver.  http://crbug.com/473572
+    SkTCopyOnFirstWrite<SkPaint> paint(srcPaint);
+    if (SkBlendMode::kSrcOver != paint->getBlendMode() &&
+        imageSubset.image()->isOpaque() &&
+        kSrcOver_SkXfermodeInterpretation == SkInterpretXfermode(*paint, false))
+    {
+        paint.writable()->setBlendMode(SkBlendMode::kSrcOver);
     }
 
     // Alpha-only images need to get their color from the shader, before
     // applying the colorfilter.
-    if (imageSubset.image()->isAlphaOnly() && paint.getColorFilter()) {
+    if (imageSubset.image()->isAlphaOnly() && paint->getColorFilter()) {
         // must blend alpha image and shader before applying colorfilter.
         auto surface =
             SkSurface::MakeRaster(SkImageInfo::MakeN32Premul(imageSubset.image()->dimensions()));
@@ -1839,18 +1515,20 @@
         SkPaint tmpPaint;
         // In the case of alpha images with shaders, the shader's coordinate
         // system is the image's coordiantes.
-        tmpPaint.setShader(sk_ref_sp(paint.getShader()));
-        tmpPaint.setColor4f(paint.getColor4f(), nullptr);
+        tmpPaint.setShader(sk_ref_sp(paint->getShader()));
+        tmpPaint.setColor4f(paint->getColor4f(), nullptr);
         canvas->clear(0x00000000);
         canvas->drawImage(imageSubset.image().get(), 0, 0, &tmpPaint);
-        paint.setShader(nullptr);
+        if (paint->getShader() != nullptr) {
+            paint.writable()->setShader(nullptr);
+        }
         imageSubset = SkKeyedImage(surface->makeImageSnapshot());
         SkASSERT(!imageSubset.image()->isAlphaOnly());
     }
 
     if (imageSubset.image()->isAlphaOnly()) {
         // The ColorFilter applies to the paint color/shader, not the alpha layer.
-        SkASSERT(nullptr == paint.getColorFilter());
+        SkASSERT(nullptr == paint->getColorFilter());
 
         sk_sp<SkImage> mask = alpha_image_to_greyscale_image(imageSubset.image().get());
         if (!mask) {
@@ -1861,17 +1539,16 @@
         sk_sp<SkPDFDevice> maskDevice = this->makeCongruentDevice();
         {
             SkCanvas canvas(maskDevice);
-            if (paint.getMaskFilter()) {
-                // This clip prevents the mask image shader from covering
-                // entire device if unnecessary.
-                canvas.clipRect(this->cs().bounds(this->bounds()));
-                canvas.concat(ctm);
+            // This clip prevents the mask image shader from covering
+            // entire device if unnecessary.
+            canvas.clipRect(this->cs().bounds(this->bounds()));
+            canvas.concat(ctm);
+            if (paint->getMaskFilter()) {
                 SkPaint tmpPaint;
                 tmpPaint.setShader(mask->makeShader(&transform));
-                tmpPaint.setMaskFilter(sk_ref_sp(paint.getMaskFilter()));
+                tmpPaint.setMaskFilter(sk_ref_sp(paint->getMaskFilter()));
                 canvas.drawRect(dst, tmpPaint);
             } else {
-                canvas.concat(ctm);
                 if (src && !is_integral(*src)) {
                     canvas.clipRect(dst);
                 }
@@ -1879,23 +1556,26 @@
                 canvas.drawImage(mask, 0, 0);
             }
         }
-        if (!ctm.isIdentity() && paint.getShader()) {
-            transform_shader(&paint, ctm); // Since we are using identity matrix.
+        SkIRect maskDeviceBounds = maskDevice->cs().bounds(maskDevice->bounds()).roundOut();
+        if (!ctm.isIdentity() && paint->getShader()) {
+            transform_shader(paint.writable(), ctm); // Since we are using identity matrix.
         }
-        ScopedContentEntry content(this, &this->cs(), SkMatrix::I(), paint);
+        ScopedContentEntry content(this, &this->cs(), SkMatrix::I(), *paint);
         if (!content) {
             return;
         }
-        this->addSMaskGraphicState(std::move(maskDevice), content.stream());
+        this->setGraphicState(SkPDFGraphicState::GetSMaskGraphicState(
+                maskDevice->makeFormXObjectFromDevice(maskDeviceBounds, true), false,
+                SkPDFGraphicState::kLuminosity_SMaskMode, fDocument), content.stream());
         SkPDFUtils::AppendRectangle(SkRect::Make(this->size()), content.stream());
         SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPath::kWinding_FillType, content.stream());
         this->clearMaskOnGraphicState(content.stream());
         return;
     }
-    if (paint.getMaskFilter()) {
-        paint.setShader(imageSubset.image()->makeShader(&transform));
+    if (paint->getMaskFilter()) {
+        paint.writable()->setShader(imageSubset.image()->makeShader(&transform));
         SkPath path = to_path(dst); // handles non-integral clipping.
-        this->internalDrawPath(this->cs(), this->ctm(), path, paint, true);
+        this->internalDrawPath(this->cs(), this->ctm(), path, *paint, true);
         return;
     }
     transform.postConcat(ctm);
@@ -1964,7 +1644,7 @@
         canvas->setMatrix(offsetMatrix);
         canvas->drawImage(imageSubset.image(), 0, 0);
         // Make sure the final bits are in the bitmap.
-        canvas->flush();
+        surface->flush();
 
         // In the new space, we use the identity matrix translated
         // and scaled to reflect DPI.
@@ -1986,7 +1666,7 @@
     scaled.postScale(SkIntToScalar(subset.width()),
                      SkIntToScalar(subset.height()));
     scaled.postConcat(matrix);
-    ScopedContentEntry content(this, &this->cs(), scaled, paint);
+    ScopedContentEntry content(this, &this->cs(), scaled, *paint);
     if (!content) {
         return;
     }
@@ -1999,7 +1679,7 @@
         return;
     }
 
-    if (SkColorFilter* colorFilter = paint.getColorFilter()) {
+    if (SkColorFilter* colorFilter = paint->getColorFilter()) {
         sk_sp<SkImage> img = color_filter(imageSubset.image().get(), colorFilter);
         imageSubset = SkKeyedImage(std::move(img));
         if (!imageSubset) {
diff --git a/src/pdf/SkPDFDevice.h b/src/pdf/SkPDFDevice.h
index 8ba8d18..354840f 100644
--- a/src/pdf/SkPDFDevice.h
+++ b/src/pdf/SkPDFDevice.h
@@ -14,6 +14,7 @@
 #include "SkClipStackDevice.h"
 #include "SkData.h"
 #include "SkKeyedImage.h"
+#include "SkPDFGraphicStackState.h"
 #include "SkPDFTypes.h"
 #include "SkPaint.h"
 #include "SkRect.h"
@@ -78,13 +79,9 @@
     void drawPath(const SkPath& origpath, const SkPaint& paint, bool pathIsMutable) override;
     void drawBitmapRect(const SkBitmap& bitmap, const SkRect* src,
                         const SkRect& dst, const SkPaint&, SkCanvas::SrcRectConstraint) override;
-    void drawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y, const SkPaint&) override;
     void drawSprite(const SkBitmap& bitmap, int x, int y,
                     const SkPaint& paint) override;
-    void drawImage(const SkImage*,
-                   SkScalar x,
-                   SkScalar y,
-                   const SkPaint&) override;
+
     void drawImageRect(const SkImage*,
                        const SkRect* src,
                        const SkRect& dst,
@@ -101,15 +98,6 @@
     /** Create the resource dictionary for this device. Destructive. */
     std::unique_ptr<SkPDFDict> makeResourceDict();
 
-    /** return annotations (link to urls and destinations) or nulltpr */
-    std::unique_ptr<SkPDFArray> getAnnotations();
-
-    /** Add our named destinations to the supplied dictionary.
-     *  @param dict  Dictionary to add destinations to.
-     *  @param page  The PDF object representing the page for this device.
-     */
-    void appendDestinations(SkPDFDict* dict, SkPDFIndirectReference page) const;
-
     /** Returns a SkStream with the page contents.
      */
     std::unique_ptr<SkStreamAsset> content();
@@ -117,20 +105,10 @@
     SkISize size() const { return this->imageInfo().dimensions(); }
     SkIRect bounds() const { return this->imageInfo().bounds(); }
 
-    // It is important to not confuse GraphicStateEntry with SkPDFGraphicState, the
-    // later being our representation of an object in the PDF file.
-    struct GraphicStateEntry {
-        SkMatrix fMatrix = SkMatrix::I();
-        uint32_t fClipStackGenID = SkClipStack::kWideOpenGenID;
-        SkColor4f fColor = {0, 0, 0, 1};
-        SkScalar fTextScaleX = 1;  // Zero means we don't care what the value is.
-        SkPaint::Style fTextFill = SkPaint::kFill_Style;  // Only if TextScaleX is non-zero.
-        int fShaderIndex = -1;
-        int fGraphicStateIndex = -1;
-    };
-
     void DrawGlyphRunAsPath(SkPDFDevice* dev, const SkGlyphRun& glyphRun, SkPoint offset);
 
+    const SkMatrix& initialTransform() const { return fInitialTransform; }
+
 protected:
     sk_sp<SkSurface> makeSurface(const SkImageInfo&, const SkSurfaceProps&) override;
 
@@ -144,26 +122,12 @@
     SkImageFilterCache* getImageFilterCache() override;
 
 private:
-    struct RectWithData {
-        SkRect rect;
-        sk_sp<SkData> data;
-    };
-
-    struct NamedDestination {
-        sk_sp<SkData> nameData;
-        SkPoint point;
-    };
-
     // TODO(vandebo): push most of SkPDFDevice's state into a core object in
     // order to get the right access levels without using friend.
     friend class ScopedContentEntry;
 
     SkMatrix fInitialTransform;
 
-    std::vector<RectWithData> fLinkToURLs;
-    std::vector<RectWithData> fLinkToDestinations;
-    std::vector<NamedDestination> fNamedDestinations;
-
     SkTHashSet<SkPDFIndirectReference> fGraphicStateResources;
     SkTHashSet<SkPDFIndirectReference> fXObjectResources;
     SkTHashSet<SkPDFIndirectReference> fShaderResources;
@@ -173,22 +137,7 @@
     SkDynamicMemoryWStream fContent;
     SkDynamicMemoryWStream fContentBuffer;
     bool fNeedsExtraSave = false;
-    struct GraphicStackState {
-        GraphicStackState(SkDynamicMemoryWStream* s = nullptr);
-        void updateClip(const SkClipStack* clipStack, const SkIRect& bounds);
-        void updateMatrix(const SkMatrix& matrix);
-        void updateDrawingState(const SkPDFDevice::GraphicStateEntry& state);
-        void push();
-        void pop();
-        void drainStack();
-        SkPDFDevice::GraphicStateEntry* currentEntry() { return &fEntries[fStackDepth]; }
-        // Must use stack for matrix, and for clip, plus one for no matrix or clip.
-        static constexpr int kMaxStackDepth = 2;
-        SkPDFDevice::GraphicStateEntry fEntries[kMaxStackDepth + 1];
-        int fStackDepth = 0;
-        SkDynamicMemoryWStream* fContentStream;
-    };
-    GraphicStackState fActiveStackState;
+    SkPDFGraphicStackState fActiveStackState;
     SkPDFDocument* fDocument;
 
     ////////////////////////////////////////////////////////////////////////////
@@ -197,6 +146,7 @@
 
     // Set alpha to true if making a transparency group form x-objects.
     SkPDFIndirectReference makeFormXObjectFromDevice(bool alpha = false);
+    SkPDFIndirectReference makeFormXObjectFromDevice(SkIRect bbox, bool alpha = false);
 
     void drawFormXObjectWithMask(SkPDFIndirectReference xObject,
                                  SkPDFIndirectReference sMask,
@@ -237,7 +187,6 @@
 
     bool handleInversePath(const SkPath& origPath, const SkPaint& paint, bool pathIsMutable);
 
-    void addSMaskGraphicState(sk_sp<SkPDFDevice> maskDevice, SkDynamicMemoryWStream*);
     void clearMaskOnGraphicState(SkDynamicMemoryWStream*);
     void setGraphicState(SkPDFIndirectReference gs, SkDynamicMemoryWStream*);
     void drawFormXObject(SkPDFIndirectReference xObject, SkDynamicMemoryWStream*);
diff --git a/src/pdf/SkPDFDocument.cpp b/src/pdf/SkPDFDocument.cpp
index ea44de1..f2de46f 100644
--- a/src/pdf/SkPDFDocument.cpp
+++ b/src/pdf/SkPDFDocument.cpp
@@ -263,9 +263,70 @@
     return &fCanvas;
 }
 
+static void populate_link_annotation(SkPDFDict* annotation, const SkRect& r) {
+    annotation->insertName("Subtype", "Link");
+    annotation->insertInt("F", 4);  // required by ISO 19005
+    // Border: 0 = Horizontal corner radius.
+    //         0 = Vertical corner radius.
+    //         0 = Width, 0 = no border.
+    annotation->insertObject("Border", SkPDFMakeArray(0, 0, 0));
+    annotation->insertObject("Rect", SkPDFMakeArray(r.fLeft, r.fTop, r.fRight, r.fBottom));
+}
+
+static SkString to_string(const SkData& d) {
+    return SkString(static_cast<const char*>(d.data()), d.size() - 1);
+}
+
+static std::unique_ptr<SkPDFArray> get_annotations(
+        SkPDFDocument* doc,
+        const std::vector<std::pair<sk_sp<SkData>, SkRect>>& linkToURLs,
+        const std::vector<std::pair<sk_sp<SkData>, SkRect>>& linkToDestinations)
+{
+    std::unique_ptr<SkPDFArray> array;
+    size_t count = linkToURLs.size() + linkToDestinations.size();
+    if (0 == count) {
+        return array;  // is nullptr
+    }
+    array = SkPDFMakeArray();
+    array->reserve(count);
+    for (const auto& rectWithURL : linkToURLs) {
+        SkPDFDict annotation("Annot");
+        populate_link_annotation(&annotation, rectWithURL.second);
+        std::unique_ptr<SkPDFDict> action = SkPDFMakeDict("Action");
+        action->insertName("S", "URI");
+        action->insertString("URI", to_string(*rectWithURL.first));
+        annotation.insertObject("A", std::move(action));
+        array->appendRef(doc->emit(annotation));
+    }
+    for (const auto& linkToDestination : linkToDestinations) {
+        SkPDFDict annotation("Annot");
+        populate_link_annotation(&annotation, linkToDestination.second);
+        annotation.insertName("Dest", to_string(*linkToDestination.first));
+        array->appendRef(doc->emit(annotation));
+    }
+    return array;
+}
+
+static SkPDFIndirectReference append_destinations(
+        SkPDFDocument* doc,
+        const std::vector<SkPDFNamedDestination>& namedDestinations)
+{
+    SkPDFDict destinations;
+    for (const SkPDFNamedDestination& dest : namedDestinations) {
+        auto pdfDest = SkPDFMakeArray();
+        pdfDest->reserve(5);
+        pdfDest->appendRef(dest.fPage);
+        pdfDest->appendName("XYZ");
+        pdfDest->appendScalar(dest.fPoint.x());
+        pdfDest->appendScalar(dest.fPoint.y());
+        pdfDest->appendInt(0);  // Leave zoom unchanged
+        destinations.insertObject(SkString((const char*)dest.fName->data()), std::move(pdfDest));
+    }
+    return doc->emit(destinations);
+}
+
 void SkPDFDocument::onEndPage() {
     SkASSERT(!fCanvas.imageInfo().dimensions().isZero());
-    fCanvas.flush();
     reset_object(&fCanvas);
     SkASSERT(fPageDevice);
 
@@ -274,17 +335,19 @@
     SkSize mediaSize = fPageDevice->imageInfo().dimensions() * fInverseRasterScale;
     std::unique_ptr<SkStreamAsset> pageContent = fPageDevice->content();
     auto resourceDict = fPageDevice->makeResourceDict();
-    auto annotations = fPageDevice->getAnnotations();
     SkASSERT(fPageRefs.size() > 0);
-    fPageDevice->appendDestinations(&fDests, fPageRefs.back());
     fPageDevice = nullptr;
 
     page->insertObject("Resources", std::move(resourceDict));
     page->insertObject("MediaBox", SkPDFUtils::RectToArray(SkRect::MakeSize(mediaSize)));
 
-    if (annotations) {
+    if (std::unique_ptr<SkPDFArray> annotations =
+            get_annotations(this, fCurrentPageLinkToURLs, fCurrentPageLinkToDestinations)) {
         page->insertObject("Annots", std::move(annotations));
+        fCurrentPageLinkToURLs.clear();
+        fCurrentPageLinkToDestinations.clear();
     }
+
     page->insertRef("Contents", SkPDFStreamOut(nullptr, std::move(pageContent), this));
     // The StructParents unique identifier for each page is just its
     // 0-based page index.
@@ -436,6 +499,10 @@
     return fPageRefs[pageIndex];
 }
 
+const SkMatrix& SkPDFDocument::currentPageTransform() const {
+    return fPageDevice->initialTransform();
+}
+
 int SkPDFDocument::getMarkIdForNodeId(int nodeId) {
     return fTagTree.getMarkIdForNodeId(nodeId, SkToUInt(this->currentPageIndex()));
 }
@@ -468,9 +535,9 @@
 
     docCatalog->insertRef("Pages", generate_page_tree(this, std::move(fPages), fPageRefs));
 
-    if (fDests.size() > 0) {
-        docCatalog->insertRef("Dests", this->emit(fDests));
-        reset_object(&fDests);
+    if (!fNamedDestinations.empty()) {
+        docCatalog->insertRef("Dests", append_destinations(this, fNamedDestinations));
+        fNamedDestinations.clear();
     }
 
     // Handle tagged PDFs.
diff --git a/src/pdf/SkPDFDocumentPriv.h b/src/pdf/SkPDFDocumentPriv.h
index 68a8781..50485f4 100644
--- a/src/pdf/SkPDFDocumentPriv.h
+++ b/src/pdf/SkPDFDocumentPriv.h
@@ -47,6 +47,13 @@
     size_t fBaseOffset = SIZE_MAX;
 };
 
+
+struct SkPDFNamedDestination {
+    sk_sp<SkData> fName;
+    SkPoint fPoint;
+    SkPDFIndirectReference fPage;
+};
+
 /** Concrete implementation of SkDocument that creates PDF files. This
     class does not produced linearized or optimized PDFs; instead it
     it attempts to use a minimum amount of RAM. */
@@ -84,6 +91,9 @@
     const SkPDF::Metadata& metadata() const { return fMetadata; }
 
     SkPDFIndirectReference getPage(size_t pageIndex) const;
+    SkPDFIndirectReference currentPage() const {
+        return SkASSERT(!fPageRefs.empty()), fPageRefs.back();
+    }
     // Returns -1 if no mark ID.
     int getMarkIdForNodeId(int nodeId);
 
@@ -95,6 +105,8 @@
     size_t currentPageIndex() { return fPages.size(); }
     size_t pageCount() { return fPageRefs.size(); }
 
+    const SkMatrix& currentPageTransform() const;
+
     // Canonicalized objects
     SkTHashMap<SkPDFImageShaderKey, SkPDFIndirectReference> fImageShaderMap;
     SkTHashMap<SkPDFGradientShader::Key, SkPDFIndirectReference, SkPDFGradientShader::KeyHash>
@@ -111,12 +123,16 @@
     SkPDFIndirectReference fInvertFunction;
     SkPDFIndirectReference fNoSmaskGraphicState;
 
+    std::vector<std::pair<sk_sp<SkData>, SkRect>> fCurrentPageLinkToURLs;
+    std::vector<std::pair<sk_sp<SkData>, SkRect>> fCurrentPageLinkToDestinations;
+    std::vector<SkPDFNamedDestination> fNamedDestinations;
+
 private:
     SkPDFOffsetMap fOffsetMap;
     SkCanvas fCanvas;
     std::vector<std::unique_ptr<SkPDFDict>> fPages;
     std::vector<SkPDFIndirectReference> fPageRefs;
-    SkPDFDict fDests;
+
     sk_sp<SkPDFDevice> fPageDevice;
     std::atomic<int> fNextObjectNumber = {1};
     std::atomic<int> fJobCount = {0};
diff --git a/src/pdf/SkPDFFont.cpp b/src/pdf/SkPDFFont.cpp
index be56387..d1d416d 100644
--- a/src/pdf/SkPDFFont.cpp
+++ b/src/pdf/SkPDFFont.cpp
@@ -269,8 +269,6 @@
 //  Type0Font
 ///////////////////////////////////////////////////////////////////////////////
 
-#ifdef SK_PDF_SUBSET_SUPPORTED
-
 // if possible, make no copy.
 static sk_sp<SkData> stream_to_data(std::unique_ptr<SkStreamAsset> stream) {
     SkASSERT(stream);
@@ -284,7 +282,6 @@
     }
     return SkData::MakeFromStream(stream.get(), size);
 }
-#endif  // SK_PDF_SUBSET_SUPPORTED
 
 static void emit_subset_type0(const SkPDFFont& font, SkPDFDocument* doc) {
     const SkAdvancedTypefaceMetrics* metricsPtr =
@@ -302,7 +299,7 @@
     add_common_font_descriptor_entries(descriptor.get(), metrics, emSize , 0);
 
     int ttcIndex;
-    std::unique_ptr<SkStreamAsset> fontAsset(face->openStream(&ttcIndex));
+    std::unique_ptr<SkStreamAsset> fontAsset = face->openStream(&ttcIndex);
     size_t fontSize = fontAsset ? fontAsset->getLength() : 0;
     if (0 == fontSize) {
         SkDebugf("Error: (SkTypeface)(%p)::openStream() returned "
@@ -311,12 +308,12 @@
     } else {
         switch (type) {
             case SkAdvancedTypefaceMetrics::kTrueType_Font: {
-                #ifdef SK_PDF_SUBSET_SUPPORTED
                 if (!SkToBool(metrics.fFlags &
                               SkAdvancedTypefaceMetrics::kNotSubsettable_FontFlag)) {
                     SkASSERT(font.firstGlyphID() == 1);
                     sk_sp<SkData> subsetFontData = SkPDFSubsetFont(
                             stream_to_data(std::move(fontAsset)), font.glyphUsage(),
+                            doc->metadata().fSubsetter,
                             metrics.fFontName.c_str(), ttcIndex);
                     if (subsetFontData) {
                         std::unique_ptr<SkPDFDict> tmp = SkPDFMakeDict();
@@ -329,12 +326,11 @@
                         break;
                     }
                     // If subsetting fails, fall back to original font data.
-                    fontAsset.reset(face->openStream(&ttcIndex));
+                    fontAsset = face->openStream(&ttcIndex);
                     SkASSERT(fontAsset);
                     SkASSERT(fontAsset->getLength() == fontSize);
                     if (!fontAsset || fontAsset->getLength() == 0) { break; }
                 }
-                #endif  // SK_PDF_SUBSET_SUPPORTED
                 std::unique_ptr<SkPDFDict> tmp = SkPDFMakeDict();
                 tmp->insertInt("Length1", fontSize);
                 descriptor->insertRef("FontFile2",
@@ -429,7 +425,7 @@
             size_t header SK_INIT_TO_AVOID_WARNING;
             size_t data SK_INIT_TO_AVOID_WARNING;
             size_t trailer SK_INIT_TO_AVOID_WARNING;
-            std::unique_ptr<SkStreamAsset> rawFontData(typeface->openStream(&ttcIndex));
+            std::unique_ptr<SkStreamAsset> rawFontData = typeface->openStream(&ttcIndex);
             sk_sp<SkData> fontData = SkPDFConvertType1FontStream(std::move(rawFontData),
                                                                  &header, &data, &trailer);
             if (fontData) {
diff --git a/src/pdf/SkPDFGradientShader.cpp b/src/pdf/SkPDFGradientShader.cpp
index 92ff8b3..f23b9a3 100644
--- a/src/pdf/SkPDFGradientShader.cpp
+++ b/src/pdf/SkPDFGradientShader.cpp
@@ -292,15 +292,14 @@
 }
 
 /* Map a value of t on the stack into [0, 1) for Repeat or Mirror tile mode. */
-static void tileModeCode(SkShader::TileMode mode,
-                         SkDynamicMemoryWStream* result) {
-    if (mode == SkShader::kRepeat_TileMode) {
+static void tileModeCode(SkTileMode mode, SkDynamicMemoryWStream* result) {
+    if (mode == SkTileMode::kRepeat) {
         result->writeText("dup truncate sub\n");  // Get the fractional part.
         result->writeText("dup 0 le {1 add} if\n");  // Map (-1,0) => (0,1)
         return;
     }
 
-    if (mode == SkShader::kMirror_TileMode) {
+    if (mode == SkTileMode::kMirror) {
         // Map t mod 2 into [0, 1, 1, 0].
         //               Code                     Stack
         result->writeText("abs "                 // Map negative to positive.
@@ -371,7 +370,7 @@
     apply_perspective_to_coordinates(perspectiveRemover, function);
 
     function->writeText("pop\n");  // Just ditch the y value.
-    tileModeCode(info.fTileMode, function);
+    tileModeCode((SkTileMode)info.fTileMode, function);
     gradient_function_code(info, function);
     function->writeText("}");
 }
@@ -392,7 +391,7 @@
                     "add "      // y^2+x^2
                     "sqrt\n");  // sqrt(y^2+x^2)
 
-    tileModeCode(info.fTileMode, function);
+    tileModeCode((SkTileMode)info.fTileMode, function);
     gradient_function_code(info, function);
     function->writeText("}");
 }
@@ -504,7 +503,7 @@
 
     // if the pixel is in the cone, proceed to compute a color
     function->writeText("{");
-    tileModeCode(info.fTileMode, function);
+    tileModeCode((SkTileMode)info.fTileMode, function);
     gradient_function_code(info, function);
 
     // otherwise, just write black
@@ -515,7 +514,7 @@
                           const SkMatrix& perspectiveRemover,
                           SkDynamicMemoryWStream* function) {
     function->writeText("{exch atan 360 div\n");
-    tileModeCode(info.fTileMode, function);
+    tileModeCode((SkTileMode)info.fTileMode, function);
     gradient_function_code(info, function);
     function->writeText("}");
 }
@@ -595,8 +594,8 @@
     bool doStitchFunctions = (state.fType == SkShader::kLinear_GradientType ||
                               state.fType == SkShader::kRadial_GradientType ||
                               state.fType == SkShader::kConical_GradientType) &&
-                             info.fTileMode == SkShader::kClamp_TileMode &&
-                             !finalMatrix.hasPerspective();
+                              (SkTileMode)info.fTileMode == SkTileMode::kClamp &&
+                              !finalMatrix.hasPerspective();
 
     int32_t shadingType = 1;
     auto pdfShader = SkPDFMakeDict();
@@ -874,7 +873,13 @@
                                          const SkIRect& bbox) {
     SkPDFGradientShader::Key key = {
          SkShader::kNone_GradientType,
-         {0, nullptr, nullptr, {{0, 0}, {0, 0}}, {0, 0}, SkShader::kClamp_TileMode, 0},
+         {0, nullptr, nullptr, {{0, 0}, {0, 0}}, {0, 0},
+#ifdef SK_SUPPORT_LEGACY_TILEMODE_ENUM
+             SkShader::kClamp_TileMode,
+#else
+             SkTileMode::kClamp,
+#endif
+             0},
          nullptr,
          nullptr,
          canvasTransform,
diff --git a/src/pdf/SkPDFGraphicStackState.cpp b/src/pdf/SkPDFGraphicStackState.cpp
new file mode 100644
index 0000000..b324a88
--- /dev/null
+++ b/src/pdf/SkPDFGraphicStackState.cpp
@@ -0,0 +1,250 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+
+#include "SkPDFGraphicStackState.h"
+
+#include "SkStream.h"
+#include "SkPDFUtils.h"
+#include "SkPathOps.h"
+
+static SkPath to_path(const SkRect& r) {
+    SkPath p;
+    p.addRect(r);
+    return p;
+}
+
+static void emit_pdf_color(SkColor4f color, SkWStream* result) {
+    SkASSERT(color.fA == 1);  // We handle alpha elsewhere.
+    SkPDFUtils::AppendColorComponentF(color.fR, result);
+    result->writeText(" ");
+    SkPDFUtils::AppendColorComponentF(color.fG, result);
+    result->writeText(" ");
+    SkPDFUtils::AppendColorComponentF(color.fB, result);
+    result->writeText(" ");
+}
+
+static SkRect rect_intersect(SkRect u, SkRect v) {
+    if (u.isEmpty() || v.isEmpty()) { return {0, 0, 0, 0}; }
+    return u.intersect(v) ? u : SkRect{0, 0, 0, 0};
+}
+
+// Test to see if the clipstack is a simple rect, If so, we can avoid all PathOps code
+// and speed thing up.
+static bool is_rect(const SkClipStack& clipStack, const SkRect& bounds, SkRect* dst) {
+    SkRect currentClip = bounds;
+    SkClipStack::Iter iter(clipStack, SkClipStack::Iter::kBottom_IterStart);
+    while (const SkClipStack::Element* element = iter.next()) {
+        SkRect elementRect{0, 0, 0, 0};
+        switch (element->getDeviceSpaceType()) {
+            case SkClipStack::Element::DeviceSpaceType::kEmpty:
+                break;
+            case SkClipStack::Element::DeviceSpaceType::kRect:
+                elementRect = element->getDeviceSpaceRect();
+                break;
+            default:
+                return false;
+        }
+        switch (element->getOp()) {
+            case kReplace_SkClipOp:
+                currentClip = rect_intersect(bounds, elementRect);
+                break;
+            case SkClipOp::kIntersect:
+                currentClip = rect_intersect(currentClip, elementRect);
+                break;
+            default:
+                return false;
+        }
+    }
+    *dst = currentClip;
+    return true;
+}
+
+static bool is_complex_clip(const SkClipStack& stack) {
+    SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
+    while (const SkClipStack::Element* element = iter.next()) {
+        switch (element->getOp()) {
+            case SkClipOp::kDifference:
+            case SkClipOp::kIntersect:
+                break;
+            default:
+                return true;
+        }
+    }
+    return false;
+}
+
+template <typename F>
+static void apply_clip(const SkClipStack& stack, const SkRect& outerBounds, F fn) {
+    // assumes clipstack is not complex.
+    constexpr SkRect kHuge{-30000, -30000, 30000, 30000};
+    SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
+    SkRect bounds = outerBounds;
+    while (const SkClipStack::Element* element = iter.next()) {
+        SkPath operand;
+        element->asDeviceSpacePath(&operand);
+        SkPathOp op;
+        switch (element->getOp()) {
+            case SkClipOp::kDifference: op = kDifference_SkPathOp; break;
+            case SkClipOp::kIntersect:  op = kIntersect_SkPathOp;  break;
+            default: SkASSERT(false); return;
+        }
+        if (op == kDifference_SkPathOp  ||
+            operand.isInverseFillType() ||
+            !kHuge.contains(operand.getBounds()))
+        {
+            Op(to_path(bounds), operand, op, &operand);
+        }
+        SkASSERT(!operand.isInverseFillType());
+        fn(operand);
+        if (!bounds.intersect(operand.getBounds())) {
+            return; // return early;
+        }
+    }
+}
+
+static void append_clip_path(const SkPath& clipPath, SkWStream* wStream) {
+    SkPDFUtils::EmitPath(clipPath, SkPaint::kFill_Style, wStream);
+    SkPath::FillType clipFill = clipPath.getFillType();
+    NOT_IMPLEMENTED(clipFill == SkPath::kInverseEvenOdd_FillType, false);
+    NOT_IMPLEMENTED(clipFill == SkPath::kInverseWinding_FillType, false);
+    if (clipFill == SkPath::kEvenOdd_FillType) {
+        wStream->writeText("W* n\n");
+    } else {
+        wStream->writeText("W n\n");
+    }
+}
+
+static void append_clip(const SkClipStack& clipStack,
+                        const SkIRect& bounds,
+                        SkWStream* wStream) {
+    // The bounds are slightly outset to ensure this is correct in the
+    // face of floating-point accuracy and possible SkRegion bitmap
+    // approximations.
+    SkRect outsetBounds = SkRect::Make(bounds.makeOutset(1, 1));
+
+    SkRect clipStackRect;
+    if (is_rect(clipStack, outsetBounds, &clipStackRect)) {
+        SkPDFUtils::AppendRectangle(clipStackRect, wStream);
+        wStream->writeText("W* n\n");
+        return;
+    }
+
+    if (is_complex_clip(clipStack)) {
+        SkPath clipPath;
+        (void)clipStack.asPath(&clipPath);
+        if (Op(clipPath, to_path(outsetBounds), kIntersect_SkPathOp, &clipPath)) {
+            append_clip_path(clipPath, wStream);
+        }
+        // If Op() fails (pathological case; e.g. input values are
+        // extremely large or NaN), emit no clip at all.
+    } else {
+        apply_clip(clipStack, outsetBounds, [wStream](const SkPath& path) {
+            append_clip_path(path, wStream);
+        });
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void SkPDFGraphicStackState::updateClip(const SkClipStack* clipStack, const SkIRect& bounds) {
+    uint32_t clipStackGenID = clipStack ? clipStack->getTopmostGenID()
+                                        : SkClipStack::kWideOpenGenID;
+    if (clipStackGenID == currentEntry()->fClipStackGenID) {
+        return;
+    }
+    while (fStackDepth > 0) {
+        this->pop();
+        if (clipStackGenID == currentEntry()->fClipStackGenID) {
+            return;
+        }
+    }
+    SkASSERT(currentEntry()->fClipStackGenID == SkClipStack::kWideOpenGenID);
+    if (clipStackGenID != SkClipStack::kWideOpenGenID) {
+        SkASSERT(clipStack);
+        this->push();
+
+        currentEntry()->fClipStackGenID = clipStackGenID;
+        append_clip(*clipStack, bounds, fContentStream);
+    }
+}
+
+
+void SkPDFGraphicStackState::updateMatrix(const SkMatrix& matrix) {
+    if (matrix == currentEntry()->fMatrix) {
+        return;
+    }
+
+    if (currentEntry()->fMatrix.getType() != SkMatrix::kIdentity_Mask) {
+        SkASSERT(fStackDepth > 0);
+        SkASSERT(fEntries[fStackDepth].fClipStackGenID ==
+                 fEntries[fStackDepth -1].fClipStackGenID);
+        this->pop();
+
+        SkASSERT(currentEntry()->fMatrix.getType() == SkMatrix::kIdentity_Mask);
+    }
+    if (matrix.getType() == SkMatrix::kIdentity_Mask) {
+        return;
+    }
+
+    this->push();
+    SkPDFUtils::AppendTransform(matrix, fContentStream);
+    currentEntry()->fMatrix = matrix;
+}
+
+void SkPDFGraphicStackState::updateDrawingState(const SkPDFGraphicStackState::Entry& state) {
+    // PDF treats a shader as a color, so we only set one or the other.
+    if (state.fShaderIndex >= 0) {
+        if (state.fShaderIndex != currentEntry()->fShaderIndex) {
+            SkPDFUtils::ApplyPattern(state.fShaderIndex, fContentStream);
+            currentEntry()->fShaderIndex = state.fShaderIndex;
+        }
+    } else {
+        if (state.fColor != currentEntry()->fColor ||
+                currentEntry()->fShaderIndex >= 0) {
+            emit_pdf_color(state.fColor, fContentStream);
+            fContentStream->writeText("RG ");
+            emit_pdf_color(state.fColor, fContentStream);
+            fContentStream->writeText("rg\n");
+            currentEntry()->fColor = state.fColor;
+            currentEntry()->fShaderIndex = -1;
+        }
+    }
+
+    if (state.fGraphicStateIndex != currentEntry()->fGraphicStateIndex) {
+        SkPDFUtils::ApplyGraphicState(state.fGraphicStateIndex, fContentStream);
+        currentEntry()->fGraphicStateIndex = state.fGraphicStateIndex;
+    }
+
+    if (state.fTextScaleX) {
+        if (state.fTextScaleX != currentEntry()->fTextScaleX) {
+            SkScalar pdfScale = state.fTextScaleX * 100;
+            SkPDFUtils::AppendScalar(pdfScale, fContentStream);
+            fContentStream->writeText(" Tz\n");
+            currentEntry()->fTextScaleX = state.fTextScaleX;
+        }
+    }
+}
+
+void SkPDFGraphicStackState::push() {
+    SkASSERT(fStackDepth < kMaxStackDepth);
+    fContentStream->writeText("q\n");
+    ++fStackDepth;
+    fEntries[fStackDepth] = fEntries[fStackDepth - 1];
+}
+
+void SkPDFGraphicStackState::pop() {
+    SkASSERT(fStackDepth > 0);
+    fContentStream->writeText("Q\n");
+    fEntries[fStackDepth] = SkPDFGraphicStackState::Entry();
+    --fStackDepth;
+}
+
+void SkPDFGraphicStackState::drainStack() {
+    if (fContentStream) {
+        while (fStackDepth) {
+            this->pop();
+        }
+    }
+    SkASSERT(fStackDepth == 0);
+}
+
diff --git a/src/pdf/SkPDFGraphicStackState.h b/src/pdf/SkPDFGraphicStackState.h
new file mode 100644
index 0000000..acaebc8
--- /dev/null
+++ b/src/pdf/SkPDFGraphicStackState.h
@@ -0,0 +1,40 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#ifndef SkPDFGraphicStackState_DEFINED
+#define SkPDFGraphicStackState_DEFINED
+
+#include "SkClipStack.h"
+#include "SkColor.h"
+#include "SkMatrix.h"
+#include "SkScalar.h"
+
+class SkDynamicMemoryWStream;
+
+// It is important to not confuse SkPDFGraphicStackState with SkPDFGraphicState, the
+// later being our representation of an object in the PDF file.
+struct SkPDFGraphicStackState {
+    struct Entry {
+        SkMatrix fMatrix = SkMatrix::I();
+        uint32_t fClipStackGenID = SkClipStack::kWideOpenGenID;
+        SkColor4f fColor = {0, 0, 0, 1};
+        SkScalar fTextScaleX = 1;  // Zero means we don't care what the value is.
+        int fShaderIndex = -1;
+        int fGraphicStateIndex = -1;
+    };
+    // Must use stack for matrix, and for clip, plus one for no matrix or clip.
+    static constexpr int kMaxStackDepth = 2;
+    Entry fEntries[kMaxStackDepth + 1];
+    int fStackDepth = 0;
+    SkDynamicMemoryWStream* fContentStream;
+
+    SkPDFGraphicStackState(SkDynamicMemoryWStream* s = nullptr) : fContentStream(s) {}
+    void updateClip(const SkClipStack* clipStack, const SkIRect& bounds);
+    void updateMatrix(const SkMatrix& matrix);
+    void updateDrawingState(const Entry& state);
+    void push();
+    void pop();
+    void drainStack();
+    Entry* currentEntry() { return &fEntries[fStackDepth]; }
+};
+
+#endif  // SkPDFGraphicStackState_DEFINED
diff --git a/src/pdf/SkPDFMetadata.cpp b/src/pdf/SkPDFMetadata.cpp
index efae8bb..a5dbddd 100644
--- a/src/pdf/SkPDFMetadata.cpp
+++ b/src/pdf/SkPDFMetadata.cpp
@@ -171,8 +171,7 @@
         md5.write(value.c_str(), value.size());
         md5.write("\036", 1);
     }
-    SkMD5::Digest digest;
-    md5.finish(digest);
+    SkMD5::Digest digest = md5.finish();
     // See RFC 4122, page 6-7.
     digest.data[6] = (digest.data[6] & 0x0F) | 0x30;
     digest.data[8] = (digest.data[6] & 0x3F) | 0x80;
diff --git a/src/pdf/SkPDFShader.cpp b/src/pdf/SkPDFShader.cpp
index 5301be7..263d95d 100644
--- a/src/pdf/SkPDFShader.cpp
+++ b/src/pdf/SkPDFShader.cpp
@@ -61,11 +61,10 @@
     // otherwise the bitmap gets clipped out and the shader is empty and awful.
     // For clamp modes, we're only interested in the clip region, whether
     // or not the main bitmap is in it.
-    SkShader::TileMode tileModes[2];
+    SkTileMode tileModes[2];
     tileModes[0] = key.fImageTileModes[0];
     tileModes[1] = key.fImageTileModes[1];
-    if (tileModes[0] != SkShader::kClamp_TileMode ||
-            tileModes[1] != SkShader::kClamp_TileMode) {
+    if (tileModes[0] != SkTileMode::kClamp || tileModes[1] != SkTileMode::kClamp) {
         deviceBounds.join(bitmapBounds);
     }
 
@@ -93,22 +92,21 @@
     SkPaint paint;
     paint.setColor(key.fPaintColor);
     // Tiling is implied.  First we handle mirroring.
-    if (tileModes[0] == SkShader::kMirror_TileMode) {
+    if (tileModes[0] == SkTileMode::kMirror) {
         SkMatrix xMirror;
         xMirror.setScale(-1, 1);
         xMirror.postTranslate(2 * width, 0);
         draw_image_matrix(&canvas, image, xMirror, paint);
         patternBBox.fRight += width;
     }
-    if (tileModes[1] == SkShader::kMirror_TileMode) {
+    if (tileModes[1] == SkTileMode::kMirror) {
         SkMatrix yMirror;
         yMirror.setScale(SK_Scalar1, -SK_Scalar1);
         yMirror.postTranslate(0, 2 * height);
         draw_image_matrix(&canvas, image, yMirror, paint);
         patternBBox.fBottom += height;
     }
-    if (tileModes[0] == SkShader::kMirror_TileMode &&
-            tileModes[1] == SkShader::kMirror_TileMode) {
+    if (tileModes[0] == SkTileMode::kMirror && tileModes[1] == SkTileMode::kMirror) {
         SkMatrix mirror;
         mirror.setScale(-1, -1);
         mirror.postTranslate(2 * width, 2 * height);
@@ -119,8 +117,7 @@
     // cover the entire surfaceBBox.
 
     SkBitmap bitmap;
-    if (tileModes[0] == SkShader::kClamp_TileMode ||
-        tileModes[1] == SkShader::kClamp_TileMode) {
+    if (tileModes[0] == SkTileMode::kClamp || tileModes[1] == SkTileMode::kClamp) {
         // For now, the easiest way to access the colors in the corners and sides is
         // to just make a bitmap from the image.
         if (!SkPDFUtils::ToBitmap(image, &bitmap)) {
@@ -131,8 +128,7 @@
 
     // If both x and y are in clamp mode, we start by filling in the corners.
     // (Which are just a rectangles of the corner colors.)
-    if (tileModes[0] == SkShader::kClamp_TileMode &&
-            tileModes[1] == SkShader::kClamp_TileMode) {
+    if (tileModes[0] == SkTileMode::kClamp && tileModes[1] == SkTileMode::kClamp) {
         SkASSERT(!bitmap.drawsNothing());
         SkPaint paint;
         SkRect rect;
@@ -166,7 +162,7 @@
     }
 
     // Then expand the left, right, top, then bottom.
-    if (tileModes[0] == SkShader::kClamp_TileMode) {
+    if (tileModes[0] == SkTileMode::kClamp) {
         SkASSERT(!bitmap.drawsNothing());
         SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, bitmap.height());
         if (deviceBounds.left() < 0) {
@@ -178,7 +174,7 @@
             leftMatrix.postTranslate(deviceBounds.left(), 0);
             draw_bitmap_matrix(&canvas, left, leftMatrix, paint);
 
-            if (tileModes[1] == SkShader::kMirror_TileMode) {
+            if (tileModes[1] == SkTileMode::kMirror) {
                 leftMatrix.postScale(SK_Scalar1, -SK_Scalar1);
                 leftMatrix.postTranslate(0, 2 * height);
                 draw_bitmap_matrix(&canvas, left, leftMatrix, paint);
@@ -196,7 +192,7 @@
             rightMatrix.postTranslate(width, 0);
             draw_bitmap_matrix(&canvas, right, rightMatrix, paint);
 
-            if (tileModes[1] == SkShader::kMirror_TileMode) {
+            if (tileModes[1] == SkTileMode::kMirror) {
                 rightMatrix.postScale(SK_Scalar1, -SK_Scalar1);
                 rightMatrix.postTranslate(0, 2 * height);
                 draw_bitmap_matrix(&canvas, right, rightMatrix, paint);
@@ -205,7 +201,7 @@
         }
     }
 
-    if (tileModes[1] == SkShader::kClamp_TileMode) {
+    if (tileModes[1] == SkTileMode::kClamp) {
         SkASSERT(!bitmap.drawsNothing());
         SkIRect subset = SkIRect::MakeXYWH(0, 0, bitmap.width(), 1);
         if (deviceBounds.top() < 0) {
@@ -217,7 +213,7 @@
             topMatrix.postTranslate(0, deviceBounds.top());
             draw_bitmap_matrix(&canvas, top, topMatrix, paint);
 
-            if (tileModes[0] == SkShader::kMirror_TileMode) {
+            if (tileModes[0] == SkTileMode::kMirror) {
                 topMatrix.postScale(-1, 1);
                 topMatrix.postTranslate(2 * width, 0);
                 draw_bitmap_matrix(&canvas, top, topMatrix, paint);
@@ -235,7 +231,7 @@
             bottomMatrix.postTranslate(0, height);
             draw_bitmap_matrix(&canvas, bottom, bottomMatrix, paint);
 
-            if (tileModes[0] == SkShader::kMirror_TileMode) {
+            if (tileModes[0] == SkTileMode::kMirror) {
                 bottomMatrix.postScale(-1, 1);
                 bottomMatrix.postTranslate(2 * width, 0);
                 draw_bitmap_matrix(&canvas, bottom, bottomMatrix, paint);
@@ -270,10 +266,10 @@
         SkMatrix::I(),
         surfaceBBox,
         {{0, 0, 0, 0}, 0},  // don't need the key; won't de-dup.
-        {SkShader::kClamp_TileMode, SkShader::kClamp_TileMode},
+        {SkTileMode::kClamp, SkTileMode::kClamp},
         paintColor};
 
-    key.fShaderTransform = shader->getLocalMatrix();
+    key.fShaderTransform = as_SB(shader)->getLocalMatrix();
 
     // surfaceBBox is in device space. While that's exactly what we
     // want for sizing our bitmap, we need to map it into
@@ -316,7 +312,7 @@
 }
 
 static SkColor adjust_color(SkShader* shader, SkColor paintColor) {
-    if (SkImage* img = shader->isAImage(nullptr, nullptr)) {
+    if (SkImage* img = shader->isAImage(nullptr, (SkTileMode*)nullptr)) {
         if (img->isAlphaOnly()) {
             return paintColor;
         }
@@ -344,7 +340,7 @@
         SkMatrix::I(),
         surfaceBBox,
         {{0, 0, 0, 0}, 0},
-        {SkShader::kClamp_TileMode, SkShader::kClamp_TileMode},
+        {SkTileMode::kClamp, SkTileMode::kClamp},
         adjust_color(shader, paintColor)};
 
     SkASSERT(shader->asAGradient(nullptr) == SkShader::kNone_GradientType) ;
diff --git a/src/pdf/SkPDFShader.h b/src/pdf/SkPDFShader.h
index 3d12686..7220395 100644
--- a/src/pdf/SkPDFShader.h
+++ b/src/pdf/SkPDFShader.h
@@ -50,7 +50,7 @@
     SkMatrix fShaderTransform;
     SkIRect fBBox;
     SkBitmapKey fBitmapKey;
-    SkShader::TileMode fImageTileModes[2];
+    SkTileMode fImageTileModes[2];
     SkColor fPaintColor;
 };
 SK_END_REQUIRE_DENSE
diff --git a/src/pdf/SkPDFSubsetFont.cpp b/src/pdf/SkPDFSubsetFont.cpp
index a52cbbb..0f87ad9 100644
--- a/src/pdf/SkPDFSubsetFont.cpp
+++ b/src/pdf/SkPDFSubsetFont.cpp
@@ -3,19 +3,88 @@
 
 #include "SkPDFSubsetFont.h"
 
-#if defined(SK_PDF_USE_SFNTLY)
-
 #if defined(SK_USING_THIRD_PARTY_ICU)
 #include "SkLoadICU.h"
 #endif
 
+#if defined(SK_PDF_USE_HARFBUZZ_SUBSET)
+
+#include "SkTo.h"
+#include "SkTemplates.h"
+
+#include "hb.h"
+#include "hb-subset.h"
+
+template <class T, void(*P)(T*)> using resource = std::unique_ptr<T, SkFunctionWrapper<void, T, P>>;
+using HBBlob = resource<hb_blob_t, hb_blob_destroy>;
+using HBFace = resource<hb_face_t, hb_face_destroy>;
+using HBSubsetInput = resource<hb_subset_input_t, hb_subset_input_destroy>;
+using HBSet = resource<hb_set_t, hb_set_destroy>;
+
+static HBBlob to_blob(sk_sp<SkData> data) {
+    return HBBlob(hb_blob_create((char*)data->data(), SkToUInt(data->size()),
+                                 HB_MEMORY_MODE_READONLY,
+                                 data.release(), [](void* p){ ((SkData*)p)->unref(); }));
+}
+
+static sk_sp<SkData> to_data(HBBlob blob) {
+    if (!blob) {
+        return nullptr;
+    }
+    unsigned int length;
+    const char* data = hb_blob_get_data(blob.get(), &length);
+    if (!data || !length) {
+        return nullptr;
+    }
+    return SkData::MakeWithProc(data, SkToSizeT(length),
+                                [](const void*, void* ctx) { hb_blob_destroy((hb_blob_t*)ctx); },
+                                blob.release());
+}
+
+static sk_sp<SkData> subset_harfbuzz(sk_sp<SkData> fontData,
+                                     const SkPDFGlyphUse& glyphUsage,
+                                     int ttcIndex) {
+#if defined(SK_USING_THIRD_PARTY_ICU)
+    if (!SkLoadICU()) {
+        return nullptr;
+    }
+#endif
+    if (!fontData) {
+        return nullptr;
+    }
+    HBFace face(hb_face_create(to_blob(std::move(fontData)).get(), ttcIndex));
+    SkASSERT(face);
+
+    HBSubsetInput input(hb_subset_input_create_or_fail());
+    SkASSERT(input);
+    if (!face || !input) {
+        return nullptr;
+    }
+    hb_set_t* glyphs = hb_subset_input_glyph_set(input.get());
+    hb_set_add(glyphs, 0);
+    glyphUsage.getSetValues([&glyphs](unsigned gid) { hb_set_add(glyphs, gid);});
+
+    hb_subset_input_set_retain_gids(input.get(), true);
+    hb_subset_input_set_drop_hints(input.get(), true);
+    hb_subset_input_set_drop_layout(input.get(), true);
+    HBFace subset(hb_subset(face.get(), input.get()));
+    HBBlob result(hb_face_reference_blob(subset.get()));
+    return to_data(std::move(result));
+}
+
+#endif  // defined(SK_PDF_USE_HARFBUZZ_SUBSET)
+
+////////////////////////////////////////////////////////////////////////////////
+
+#if defined(SK_PDF_USE_SFNTLY)
+
 #include "sample/chromium/font_subsetter.h"
 #include <vector>
 
-sk_sp<SkData> SkPDFSubsetFont(sk_sp<SkData> fontData,
-                              const SkPDFGlyphUse& glyphUsage,
-                              const char* fontName,
-                              int ttcIndex) {
+static sk_sp<SkData> subset_sfntly(sk_sp<SkData> fontData,
+                                   const SkPDFGlyphUse& glyphUsage,
+                                   const char* fontName,
+                                   int ttcIndex) {
 #if defined(SK_USING_THIRD_PARTY_ICU)
     if (!SkLoadICU()) {
         return nullptr;
@@ -39,7 +108,7 @@
                                                    subset.data(),
                                                    subset.size(),
                                                    &subsetFont);
-#else
+#else  // defined(SK_BUILD_FOR_GOOGLE3)
     (void)fontName;
     int subsetFontSize = SfntlyWrapper::SubsetFont(ttcIndex,
                                                    fontData->bytes(),
@@ -47,7 +116,7 @@
                                                    subset.data(),
                                                    subset.size(),
                                                    &subsetFont);
-#endif
+#endif  // defined(SK_BUILD_FOR_GOOGLE3)
     SkASSERT(subsetFontSize > 0 || subsetFont == nullptr);
     if (subsetFontSize < 1 || subsetFont == nullptr) {
         return nullptr;
@@ -56,4 +125,52 @@
                                 [](const void* p, void*) { delete[] (unsigned char*)p; },
                                 nullptr);
 }
-#endif
+
+#endif  // defined(SK_PDF_USE_SFNTLY)
+
+////////////////////////////////////////////////////////////////////////////////
+
+#if defined(SK_PDF_USE_SFNTLY) && defined(SK_PDF_USE_HARFBUZZ_SUBSET)
+
+sk_sp<SkData> SkPDFSubsetFont(sk_sp<SkData> fontData,
+                              const SkPDFGlyphUse& glyphUsage,
+                              SkPDF::Metadata::Subsetter subsetter,
+                              const char* fontName,
+                              int ttcIndex) {
+    switch (subsetter) {
+        case SkPDF::Metadata::kHarfbuzz_Subsetter:
+            return subset_harfbuzz(std::move(fontData), glyphUsage, ttcIndex);
+        case SkPDF::Metadata::kSfntly_Subsetter:
+            return subset_sfntly(std::move(fontData), glyphUsage, fontName, ttcIndex);
+    }
+    return nullptr;
+}
+
+#elif defined(SK_PDF_USE_SFNTLY)
+
+sk_sp<SkData> SkPDFSubsetFont(sk_sp<SkData> fontData,
+                              const SkPDFGlyphUse& glyphUsage,
+                              SkPDF::Metadata::Subsetter,
+                              const char* fontName,
+                              int ttcIndex) {
+    return subset_sfntly(std::move(fontData), glyphUsage, fontName, ttcIndex);
+}
+
+#elif defined(SK_PDF_USE_HARFBUZZ_SUBSET)
+
+sk_sp<SkData> SkPDFSubsetFont(sk_sp<SkData> fontData,
+                              const SkPDFGlyphUse& glyphUsage,
+                              SkPDF::Metadata::Subsetter,
+                              const char*,
+                              int ttcIndex) {
+    return subset_harfbuzz(std::move(fontData), glyphUsage, ttcIndex);
+}
+
+#else
+
+sk_sp<SkData> SkPDFSubsetFont(sk_sp<SkData>, const SkPDFGlyphUse&, SkPDF::Metadata::Subsetter,
+                              const char*, int) {
+    return nullptr;
+}
+#endif  // defined(SK_PDF_USE_SFNTLY)
+
diff --git a/src/pdf/SkPDFSubsetFont.h b/src/pdf/SkPDFSubsetFont.h
index 37b2701..d41b14a 100644
--- a/src/pdf/SkPDFSubsetFont.h
+++ b/src/pdf/SkPDFSubsetFont.h
@@ -4,15 +4,13 @@
 #define SkPDFSubsetFont_DEFINED
 
 #include "SkData.h"
+#include "SkPDFDocument.h"
 #include "SkPDFGlyphUse.h"
 
-#ifdef SK_PDF_USE_SFNTLY
-#define SK_PDF_SUBSET_SUPPORTED
-
 sk_sp<SkData> SkPDFSubsetFont(sk_sp<SkData> fontData,
                               const SkPDFGlyphUse& glyphUsage,
+                              SkPDF::Metadata::Subsetter subsetter,
                               const char* fontName,
                               int ttcIndex);
-#endif
 
 #endif  // SkPDFSubsetFont_DEFINED
diff --git a/src/pdf/SkPDFUtils.cpp b/src/pdf/SkPDFUtils.cpp
index e53accf..28471af 100644
--- a/src/pdf/SkPDFUtils.cpp
+++ b/src/pdf/SkPDFUtils.cpp
@@ -372,3 +372,15 @@
     }
 }
 #endif //  SK_PDF_BASE85_BINARY
+
+void SkPDFUtils::AppendTransform(const SkMatrix& matrix, SkWStream* content) {
+    SkScalar values[6];
+    if (!matrix.asAffine(values)) {
+        SkMatrix::SetAffineIdentity(values);
+    }
+    for (SkScalar v : values) {
+        SkPDFUtils::AppendScalar(v, content);
+        content->writeText(" ");
+    }
+    content->writeText("cm\n");
+}
diff --git a/src/pdf/SkPDFUtils.h b/src/pdf/SkPDFUtils.h
index 0624c9a..a506f68 100644
--- a/src/pdf/SkPDFUtils.h
+++ b/src/pdf/SkPDFUtils.h
@@ -12,6 +12,7 @@
 #include "SkPaint.h"
 #include "SkPath.h"
 #include "SkShader.h"
+#include "SkShaderBase.h"
 #include "SkStream.h"
 #include "SkUtils.h"
 
@@ -113,10 +114,10 @@
 
 inline SkMatrix GetShaderLocalMatrix(const SkShader* shader) {
     SkMatrix localMatrix;
-    if (sk_sp<SkShader> s = shader->makeAsALocalMatrixShader(&localMatrix)) {
-        return SkMatrix::Concat(s->getLocalMatrix(), localMatrix);
+    if (sk_sp<SkShader> s = as_SB(shader)->makeAsALocalMatrixShader(&localMatrix)) {
+        return SkMatrix::Concat(as_SB(s)->getLocalMatrix(), localMatrix);
     }
-    return shader->getLocalMatrix();
+    return as_SB(shader)->getLocalMatrix();
 }
 bool InverseTransformBBox(const SkMatrix& matrix, SkRect* bbox);
 void PopulateTilingPatternDict(SkPDFDict* pattern,
@@ -130,6 +131,7 @@
 void Base85Encode(std::unique_ptr<SkStreamAsset> src, SkDynamicMemoryWStream* dst);
 #endif //  SK_PDF_BASE85_BINARY
 
+void AppendTransform(const SkMatrix&, SkWStream*);
 }  // namespace SkPDFUtils
 
 #endif
diff --git a/src/ports/SkFontConfigInterface_direct.cpp b/src/ports/SkFontConfigInterface_direct.cpp
index 56c8466..bbfcfdf 100644
--- a/src/ports/SkFontConfigInterface_direct.cpp
+++ b/src/ports/SkFontConfigInterface_direct.cpp
@@ -539,6 +539,13 @@
     if (!c_filename) {
         return false;
     }
+    const char* sysroot = (const char*)FcConfigGetSysRoot(nullptr);
+    SkString resolvedFilename;
+    if (sysroot) {
+        resolvedFilename = sysroot;
+        resolvedFilename += c_filename;
+        c_filename = resolvedFilename.c_str();
+    }
     return this->isAccessible(c_filename);
 }
 
@@ -670,6 +677,13 @@
         FcFontSetDestroy(font_set);
         return false;
     }
+    const char* sysroot = (const char*)FcConfigGetSysRoot(nullptr);
+    SkString resolvedFilename;
+    if (sysroot) {
+        resolvedFilename = sysroot;
+        resolvedFilename += c_filename;
+        c_filename = resolvedFilename.c_str();
+    }
 
     int face_index = get_int(match, FC_INDEX, 0);
 
diff --git a/src/ports/SkFontConfigTypeface.h b/src/ports/SkFontConfigTypeface.h
index 395c262..49f9853 100644
--- a/src/ports/SkFontConfigTypeface.h
+++ b/src/ports/SkFontConfigTypeface.h
@@ -75,7 +75,7 @@
 
     void onGetFamilyName(SkString* familyName) const override { *familyName = fFamilyName; }
     void onGetFontDescriptor(SkFontDescriptor*, bool*) const override;
-    SkStreamAsset* onOpenStream(int* ttcIndex) const override;
+    std::unique_ptr<SkStreamAsset> onOpenStream(int* ttcIndex) const override;
     std::unique_ptr<SkFontData> onMakeFontData() const override;
 
 private:
diff --git a/src/ports/SkFontHost_FreeType.cpp b/src/ports/SkFontHost_FreeType.cpp
index 8006f11..5ca73a0 100644
--- a/src/ports/SkFontHost_FreeType.cpp
+++ b/src/ports/SkFontHost_FreeType.cpp
@@ -88,6 +88,12 @@
 //#define SK_FONTHOST_FREETYPE_RUNTIME_VERSION
 //#define SK_GAMMA_APPLY_TO_A8
 
+#if 1
+    #define LOG_INFO(...)
+#else
+    #define LOG_INFO SkDEBUGF
+#endif
+
 static bool isLCD(const SkScalerContextRec& rec) {
     return SkMask::kLCD16_Format == rec.fMaskFormat;
 }
@@ -337,14 +343,14 @@
     SkDEBUGCODE(
         FT_MM_Var* variations = nullptr;
         if (FT_Get_MM_Var(rec->fFace.get(), &variations)) {
-            SkDEBUGF("INFO: font %s claims variations, but none found.\n",
+            LOG_INFO("INFO: font %s claims variations, but none found.\n",
                      rec->fFace->family_name);
             return;
         }
         SkAutoFree autoFreeVariations(variations);
 
         if (static_cast<FT_UInt>(data.getAxisCount()) != variations->num_axis) {
-            SkDEBUGF("INFO: font %s has %d variations, but %d were specified.\n",
+            LOG_INFO("INFO: font %s has %d variations, but %d were specified.\n",
                      rec->fFace->family_name, variations->num_axis, data.getAxisCount());
             return;
         }
@@ -355,7 +361,7 @@
         coords[i] = data.getAxis()[i];
     }
     if (FT_Set_Var_Design_Coordinates(rec->fFace.get(), data.getAxisCount(), coords.get())) {
-        SkDEBUGF("INFO: font %s has variations, but specified variations could not be set.\n",
+        LOG_INFO("INFO: font %s has variations, but specified variations could not be set.\n",
                  rec->fFace->family_name);
         return;
     }
@@ -698,7 +704,7 @@
     Scanner::computeAxisValues(axisDefinitions, args.getVariationDesignPosition(),
                                axisValues, name);
     int ttcIndex;
-    auto stream = std::unique_ptr<SkStreamAsset>(this->openStream(&ttcIndex));
+    std::unique_ptr<SkStreamAsset> stream = this->openStream(&ttcIndex);
     return skstd::make_unique<SkFontData>(std::move(stream), ttcIndex, axisValues.get(),
                                           axisDefinitions.count());
 }
@@ -789,7 +795,7 @@
 /** Returns the bitmap strike equal to or just larger than the requested size. */
 static FT_Int chooseBitmapStrike(FT_Face face, FT_F26Dot6 scaleY) {
     if (face == nullptr) {
-        SkDEBUGF("chooseBitmapStrike aborted due to nullptr face.\n");
+        LOG_INFO("chooseBitmapStrike aborted due to nullptr face.\n");
         return -1;
     }
 
@@ -833,7 +839,7 @@
 
     // load the font file
     if (nullptr == fFaceRec) {
-        SkDEBUGF("Could not create FT_Face.\n");
+        LOG_INFO("Could not create FT_Face.\n");
         return;
     }
 
@@ -874,7 +880,7 @@
                 }
                 break;
             default:
-                SkDebugf("---------- UNKNOWN hinting %d\n", fRec.getHinting());
+                LOG_INFO("---------- UNKNOWN hinting %d\n", fRec.getHinting());
                 break;
             }
         }
@@ -917,7 +923,7 @@
         return size;
     }());
     if (nullptr == ftSize) {
-        SkDEBUGF("Could not create FT_Size.\n");
+        LOG_INFO("Could not create FT_Size.\n");
         return;
     }
 
@@ -953,7 +959,7 @@
     } else if (FT_HAS_FIXED_SIZES(fFaceRec->fFace)) {
         fStrikeIndex = chooseBitmapStrike(fFaceRec->fFace.get(), scaleY);
         if (fStrikeIndex == -1) {
-            SkDEBUGF("No glyphs for font \"%s\" size %f.\n",
+            LOG_INFO("No glyphs for font \"%s\" size %f.\n",
                      fFaceRec->fFace->family_name, fScale.fY);
             return;
         }
@@ -982,7 +988,7 @@
         // Force this flag off for bitmap only fonts.
         fLoadGlyphFlags &= ~FT_LOAD_NO_BITMAP;
     } else {
-        SkDEBUGF("Unknown kind of font \"%s\" size %f.\n", fFaceRec->fFace->family_name, fScale.fY);
+        LOG_INFO("Unknown kind of font \"%s\" size %f.\n", fFaceRec->fFace->family_name, fScale.fY);
         return;
     }
 
@@ -1294,7 +1300,7 @@
     }
 
 #ifdef ENABLE_GLYPH_SPEW
-    SkDEBUGF("Metrics(glyph:%d flags:0x%x) w:%d\n", glyph->getGlyphID(), fLoadGlyphFlags, glyph->fWidth);
+    LOG_INFO("Metrics(glyph:%d flags:0x%x) w:%d\n", glyph->getGlyphID(), fLoadGlyphFlags, glyph->fWidth);
 #endif
 }
 
@@ -1904,7 +1910,7 @@
         if (index >= 0) {
             weight = commonWeights[index].weight;
         } else {
-            SkDEBUGF("Do not know weight for: %s (%s) \n", face->family_name, psFontInfo.weight);
+            LOG_INFO("Do not know weight for: %s (%s) \n", face->family_name, psFontInfo.weight);
         }
     }
 
@@ -1928,7 +1934,7 @@
         FT_MM_Var* variations = nullptr;
         FT_Error err = FT_Get_MM_Var(face, &variations);
         if (err) {
-            SkDEBUGF("INFO: font %s claims to have variations, but none found.\n",
+            LOG_INFO("INFO: font %s claims to have variations, but none found.\n",
                      face->family_name);
             return false;
         }
@@ -1964,7 +1970,7 @@
             if (axisDefinition.fTag == coordinate.axis) {
                 const SkScalar axisValue = SkTPin(coordinate.value, axisMin, axisMax);
                 if (coordinate.value != axisValue) {
-                    SkDEBUGF("Requested font axis value out of range: "
+                    LOG_INFO("Requested font axis value out of range: "
                              "%s '%c%c%c%c' %f; pinned to %f.\n",
                              name.c_str(),
                              (axisDefinition.fTag >> 24) & 0xFF,
@@ -1993,7 +1999,7 @@
                 }
             }
             if (!found) {
-                SkDEBUGF("Requested font axis not found: %s '%c%c%c%c'\n",
+                LOG_INFO("Requested font axis not found: %s '%c%c%c%c'\n",
                          name.c_str(),
                          (skTag >> 24) & 0xFF,
                          (skTag >> 16) & 0xFF,
diff --git a/src/ports/SkFontHost_mac.cpp b/src/ports/SkFontHost_mac.cpp
index 4bfc7fa..32e5bc3 100644
--- a/src/ports/SkFontHost_mac.cpp
+++ b/src/ports/SkFontHost_mac.cpp
@@ -710,13 +710,14 @@
 public:
     SkTypeface_Mac(SkUniqueCFRef<CTFontRef> fontRef, SkUniqueCFRef<CFTypeRef> resourceRef,
                    const SkFontStyle& fs, bool isFixedPitch,
-                   bool isLocalStream)
+                   std::unique_ptr<SkStreamAsset> providedData)
         : SkTypeface(fs, isFixedPitch)
         , fFontRef(std::move(fontRef))
         , fOriginatingCFTypeRef(std::move(resourceRef))
         , fHasColorGlyphs(
                 SkToBool(CTFontGetSymbolicTraits(fFontRef.get()) & kCTFontColorGlyphsTrait))
-        , fIsLocalStream(isLocalStream)
+        , fStream(std::move(providedData))
+        , fIsFromStream(fStream)
     {
         SkASSERT(fFontRef);
     }
@@ -727,7 +728,7 @@
 
 protected:
     int onGetUPEM() const override;
-    SkStreamAsset* onOpenStream(int* ttcIndex) const override;
+    std::unique_ptr<SkStreamAsset> onOpenStream(int* ttcIndex) const override;
     std::unique_ptr<SkFontData> onMakeFontData() const override;
     int onGetVariationDesignPosition(SkFontArguments::VariationPosition::Coordinate coordinates[],
                                      int coordinateCount) const override;
@@ -748,7 +749,9 @@
     void* onGetCTFontRef() const override { return (void*)fFontRef.get(); }
 
 private:
-    bool fIsLocalStream;
+    mutable std::unique_ptr<SkStreamAsset> fStream;
+    bool fIsFromStream;
+    mutable SkOnce fInitStream;
 
     typedef SkTypeface INHERITED;
 };
@@ -763,27 +766,29 @@
 /** Creates a typeface, searching the cache if isLocalStream is false. */
 static sk_sp<SkTypeface> create_from_CTFontRef(SkUniqueCFRef<CTFontRef> font,
                                                SkUniqueCFRef<CFTypeRef> resource,
-                                               bool isLocalStream) {
+                                               std::unique_ptr<SkStreamAsset> providedData) {
     SkASSERT(font);
+    const bool isFromStream(providedData);
 
-    if (!isLocalStream) {
-        SkTypeface* face = SkTypefaceCache::FindByProcAndRef(find_by_CTFontRef, (void*)font.get());
+    if (!isFromStream) {
+        sk_sp<SkTypeface> face = SkTypefaceCache::FindByProcAndRef(find_by_CTFontRef,
+                                                                   (void*)font.get());
         if (face) {
-            return sk_sp<SkTypeface>(face);
+            return face;
         }
     }
 
     SkUniqueCFRef<CTFontDescriptorRef> desc(CTFontCopyFontDescriptor(font.get()));
-    SkFontStyle style = fontstyle_from_descriptor(desc.get(), isLocalStream);
+    SkFontStyle style = fontstyle_from_descriptor(desc.get(), isFromStream);
     CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(font.get());
     bool isFixedPitch = SkToBool(traits & kCTFontMonoSpaceTrait);
 
-    SkTypeface* face = new SkTypeface_Mac(std::move(font), std::move(resource),
-                                          style, isFixedPitch, isLocalStream);
-    if (!isLocalStream) {
+    sk_sp<SkTypeface> face(new SkTypeface_Mac(std::move(font), std::move(resource),
+                                              style, isFixedPitch, std::move(providedData)));
+    if (!isFromStream) {
         SkTypefaceCache::Add(face);
     }
-    return sk_sp<SkTypeface>(face);
+    return face;
 }
 
 /** Creates a typeface from a descriptor, searching the cache. */
@@ -793,7 +798,7 @@
         return nullptr;
     }
 
-    return create_from_CTFontRef(std::move(ctFont), nullptr, false);
+    return create_from_CTFontRef(std::move(ctFont), nullptr, nullptr);
 }
 
 static SkUniqueCFRef<CTFontDescriptorRef> create_descriptor(const char familyName[],
@@ -872,7 +877,7 @@
     }
     return create_from_CTFontRef(SkUniqueCFRef<CTFontRef>(font),
                                  SkUniqueCFRef<CFTypeRef>(resource),
-                                 false).release();
+                                 nullptr).release();
 }
 
 static const char* map_css_names(const char* name) {
@@ -1556,6 +1561,7 @@
 // Returns nullptr on failure
 // Call must still manage its ownership of provider
 static sk_sp<SkTypeface> create_from_dataProvider(SkUniqueCFRef<CGDataProviderRef> provider,
+                                                  std::unique_ptr<SkStreamAsset> providedData,
                                                   int ttcIndex) {
     if (ttcIndex != 0) {
         return nullptr;
@@ -1568,7 +1574,7 @@
     if (!ct) {
         return nullptr;
     }
-    return create_from_CTFontRef(std::move(ct), nullptr, true);
+    return create_from_CTFontRef(std::move(ct), nullptr, std::move(providedData));
 }
 
 // Web fonts added to the CTFont registry do not return their character set.
@@ -1581,7 +1587,7 @@
     while (glyphCount > 0) {
         CGGlyph glyph;
         if (CTFontGetGlyphsForCharacters(ctFont, &unichar, &glyph, 1)) {
-            if (out[glyph] != 0) {
+            if (out[glyph] == 0) {
                 out[glyph] = unichar;
                 --glyphCount;
             }
@@ -1631,7 +1637,9 @@
             CGGlyph glyph;
             UniChar unichar = static_cast<UniChar>((i << 3) + j);
             if (mask & (1 << j) && CTFontGetGlyphsForCharacters(ctFont, &unichar, &glyph, 1)) {
-                glyphToUnicode[glyph] = unichar;
+                if (glyphToUnicode[glyph] == 0) {
+                    glyphToUnicode[glyph] = unichar;
+                }
             }
         }
     }
@@ -1783,7 +1791,14 @@
     }
 }
 
-SkStreamAsset* SkTypeface_Mac::onOpenStream(int* ttcIndex) const {
+std::unique_ptr<SkStreamAsset> SkTypeface_Mac::onOpenStream(int* ttcIndex) const {
+    *ttcIndex = 0;
+
+    fInitStream([this]{
+    if (fStream) {
+        return;
+    }
+
     SK_SFNT_ULONG fontType = get_font_type_tag(fFontRef.get());
 
     // get table tags
@@ -1840,8 +1855,8 @@
     }
 
     // reserve memory for stream, and zero it (tables must be zero padded)
-    SkMemoryStream* stream = new SkMemoryStream(totalSize);
-    char* dataStart = (char*)stream->getMemoryBase();
+    fStream.reset(new SkMemoryStream(totalSize));
+    char* dataStart = (char*)fStream->getMemoryBase();
     sk_bzero(dataStart, totalSize);
     char* dataPtr = dataStart;
 
@@ -1879,9 +1894,8 @@
         dataPtr += (tableSize + 3) & ~3;
         ++entry;
     }
-
-    *ttcIndex = 0;
-    return stream;
+    });
+    return fStream->duplicate();
 }
 
 struct NonDefaultAxesContext {
@@ -2227,19 +2241,20 @@
 
     rec->fFlags &= ~flagsWeDontSupport;
 
-    SmoothBehavior smoothBehavior = smooth_behavior();
+    const SmoothBehavior smoothBehavior = smooth_behavior();
 
     // Only two levels of hinting are supported.
-    // kNo_Hinting means avoid CoreGraphics outline dilation.
-    // kNormal_Hinting means CoreGraphics outline dilation is allowed.
-    // If there is no lcd support, hinting (dilation) cannot be supported.
-    SkFontHinting hinting = rec->getHinting();
-    if (kSlight_SkFontHinting == hinting || smoothBehavior == SmoothBehavior::none) {
-        hinting = kNo_SkFontHinting;
-    } else if (kFull_SkFontHinting == hinting) {
-        hinting = kNormal_SkFontHinting;
+    // kNo_Hinting means avoid CoreGraphics outline dilation (smoothing).
+    // kNormal_Hinting means CoreGraphics outline dilation (smoothing) is allowed.
+    if (kSlight_SkFontHinting == rec->getHinting()) {
+        rec->setHinting(kNo_SkFontHinting);
+    } else if (kFull_SkFontHinting == rec->getHinting()) {
+        rec->setHinting(kNormal_SkFontHinting);
     }
-    rec->setHinting(hinting);
+    // If smoothing has no effect, don't request it.
+    if (smoothBehavior == SmoothBehavior::none) {
+        rec->setHinting(kNo_SkFontHinting);
+    }
 
     // FIXME: lcd smoothed un-hinted rasterization unsupported.
     // Tracked by http://code.google.com/p/skia/issues/detail?id=915 .
@@ -2259,7 +2274,6 @@
     // [LCD][no-hint]: curently unable to honor, and must pick which to respect.
     // Currenly side with LCD, effectively ignoring the hinting setting.
     // [LCD][yes-hint]: generate LCD using CoreGraphic's LCD output.
-
     if (rec->fMaskFormat == SkMask::kLCD16_Format) {
         if (smoothBehavior == SmoothBehavior::subpixel) {
             //CoreGraphics creates 555 masks for smoothed text anyway.
@@ -2267,7 +2281,7 @@
             rec->setHinting(kNormal_SkFontHinting);
         } else {
             rec->fMaskFormat = SkMask::kA8_Format;
-            if (smoothBehavior == SmoothBehavior::some) {
+            if (smoothBehavior != SmoothBehavior::none) {
                 rec->setHinting(kNormal_SkFontHinting);
             }
         }
@@ -2282,13 +2296,32 @@
 
     // Unhinted A8 masks (those not derived from LCD masks) must respect SK_GAMMA_APPLY_TO_A8.
     // All other masks can use regular gamma.
-    if (SkMask::kA8_Format == rec->fMaskFormat && kNo_SkFontHinting == hinting) {
+    if (SkMask::kA8_Format == rec->fMaskFormat && kNo_SkFontHinting == rec->getHinting()) {
 #ifndef SK_GAMMA_APPLY_TO_A8
         // SRGBTODO: Is this correct? Do we want contrast boost?
         rec->ignorePreBlend();
 #endif
     } else {
-        //CoreGraphics dialates smoothed text as needed.
+#ifndef SK_IGNORE_MAC_BLENDING_MATCH_FIX
+        SkColor color = rec->getLuminanceColor();
+        if (smoothBehavior == SmoothBehavior::some) {
+            // CoreGraphics smoothed text without subpixel coverage blitting goes from a gamma of
+            // 2.0 for black foreground to a gamma of 1.0 for white foreground. Emulate this
+            // through the mask gamma by reducing the color values to 1/2.
+            color = SkColorSetRGB(SkColorGetR(color) * 1/2,
+                                  SkColorGetG(color) * 1/2,
+                                  SkColorGetB(color) * 1/2);
+        } else if (smoothBehavior == SmoothBehavior::subpixel) {
+            // CoreGraphics smoothed text with subpixel coverage blitting goes from a gamma of
+            // 2.0 for black foreground to a gamma of ~1.4? for white foreground. Emulate this
+            // through the mask gamma by reducing the color values to 3/4.
+            color = SkColorSetRGB(SkColorGetR(color) * 3/4,
+                                  SkColorGetG(color) * 3/4,
+                                  SkColorGetB(color) * 3/4);
+        }
+        rec->setLuminanceColor(color);
+#endif
+        // CoreGraphics dialates smoothed text to provide contrast.
         rec->setContrast(0);
     }
 }
@@ -2315,7 +2348,7 @@
     desc->setFullName(get_str(CTFontCopyFullName(fFontRef.get()), &tmpStr));
     desc->setPostscriptName(get_str(CTFontCopyPostScriptName(fFontRef.get()), &tmpStr));
     desc->setStyle(this->fontStyle());
-    *isLocalStream = fIsLocalStream;
+    *isLocalStream = fIsFromStream;
 }
 
 int SkTypeface_Mac::onCharsToGlyphs(const void* chars, Encoding encoding,
@@ -2594,7 +2627,7 @@
         CFRange range = CFRangeMake(0, CFStringGetLength(string.get()));  // in UniChar units.
         SkUniqueCFRef<CTFontRef> fallbackFont(
                 CTFontCreateForString(familyFont.get(), string.get(), range));
-        return create_from_CTFontRef(std::move(fallbackFont), nullptr, false).release();
+        return create_from_CTFontRef(std::move(fallbackFont), nullptr, nullptr).release();
     }
 
     SkTypeface* onMatchFaceStyle(const SkTypeface* familyMember,
@@ -2603,20 +2636,21 @@
     }
 
     sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData> data, int ttcIndex) const override {
-        SkUniqueCFRef<CGDataProviderRef> pr(SkCreateDataProviderFromData(std::move(data)));
+        SkUniqueCFRef<CGDataProviderRef> pr(SkCreateDataProviderFromData(data));
         if (!pr) {
             return nullptr;
         }
-        return create_from_dataProvider(std::move(pr), ttcIndex);
+        return create_from_dataProvider(std::move(pr), SkMemoryStream::Make(std::move(data)),
+                                        ttcIndex);
     }
 
     sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset> stream,
                                             int ttcIndex) const override {
-        SkUniqueCFRef<CGDataProviderRef> pr(SkCreateDataProviderFromStream(std::move(stream)));
+        SkUniqueCFRef<CGDataProviderRef> pr(SkCreateDataProviderFromStream(stream->duplicate()));
         if (!pr) {
             return nullptr;
         }
-        return create_from_dataProvider(std::move(pr), ttcIndex);
+        return create_from_dataProvider(std::move(pr), std::move(stream), ttcIndex);
     }
 
     /** Creates a dictionary suitable for setting the axes on a CGFont. */
@@ -2712,7 +2746,7 @@
         if (args.getCollectionIndex() != 0) {
             return nullptr;
         }
-        SkUniqueCFRef<CGDataProviderRef> provider(SkCreateDataProviderFromStream(std::move(s)));
+        SkUniqueCFRef<CGDataProviderRef> provider(SkCreateDataProviderFromStream(s->duplicate()));
         if (!provider) {
             return nullptr;
         }
@@ -2737,7 +2771,7 @@
         if (!ct) {
             return nullptr;
         }
-        return create_from_CTFontRef(std::move(ct), std::move(cg), true);
+        return create_from_CTFontRef(std::move(ct), std::move(cg), std::move(s));
     }
 
     /** Creates a dictionary suitable for setting the axes on a CGFont. */
@@ -2799,7 +2833,7 @@
             return nullptr;
         }
         SkUniqueCFRef<CGDataProviderRef> provider(
-                SkCreateDataProviderFromStream(fontData->detachStream()));
+                SkCreateDataProviderFromStream(fontData->getStream()->duplicate()));
         if (!provider) {
             return nullptr;
         }
@@ -2824,7 +2858,7 @@
         if (!ct) {
             return nullptr;
         }
-        return create_from_CTFontRef(std::move(ct), std::move(cg), true);
+        return create_from_CTFontRef(std::move(ct), std::move(cg), fontData->detachStream());
     }
 
     sk_sp<SkTypeface> onMakeFromFile(const char path[], int ttcIndex) const override {
@@ -2832,7 +2866,7 @@
         if (!pr) {
             return nullptr;
         }
-        return create_from_dataProvider(std::move(pr), ttcIndex);
+        return create_from_dataProvider(std::move(pr), SkFILEStream::Make(path), ttcIndex);
     }
 
     sk_sp<SkTypeface> onLegacyMakeTypeface(const char familyName[], SkFontStyle style) const override {
diff --git a/src/ports/SkFontHost_win.cpp b/src/ports/SkFontHost_win.cpp
index e546f81..d811458 100644
--- a/src/ports/SkFontHost_win.cpp
+++ b/src/ports/SkFontHost_win.cpp
@@ -250,8 +250,8 @@
     bool fSerializeAsStream;
     bool fCanBeLCD;
 
-    static LogFontTypeface* Create(const LOGFONT& lf) {
-        return new LogFontTypeface(get_style(lf), lf, false);
+    static sk_sp<LogFontTypeface> Make(const LOGFONT& lf) {
+        return sk_sp<LogFontTypeface>(new LogFontTypeface(get_style(lf), lf, false));
     }
 
     static void EnsureAccessible(const SkTypeface* face) {
@@ -259,7 +259,7 @@
     }
 
 protected:
-    SkStreamAsset* onOpenStream(int* ttcIndex) const override;
+    std::unique_ptr<SkStreamAsset> onOpenStream(int* ttcIndex) const override;
     sk_sp<SkTypeface> onMakeClone(const SkFontArguments& args) const override;
     SkScalerContext* onCreateScalerContext(const SkScalerContextEffects&,
                                            const SkDescriptor*) const override;
@@ -292,8 +292,9 @@
     /**
      *  The created FontMemResourceTypeface takes ownership of fontMemResource.
      */
-    static FontMemResourceTypeface* Create(const LOGFONT& lf, HANDLE fontMemResource) {
-        return new FontMemResourceTypeface(get_style(lf), lf, fontMemResource);
+    static sk_sp<FontMemResourceTypeface> Make(const LOGFONT& lf, HANDLE fontMemResource) {
+        return sk_sp<FontMemResourceTypeface>(
+            new FontMemResourceTypeface(get_style(lf), lf, fontMemResource));
     }
 
 protected:
@@ -335,22 +336,22 @@
 SkTypeface* SkCreateTypefaceFromLOGFONT(const LOGFONT& origLF) {
     LOGFONT lf = origLF;
     make_canonical(&lf);
-    SkTypeface* face = SkTypefaceCache::FindByProcAndRef(FindByLogFont, &lf);
-    if (nullptr == face) {
-        face = LogFontTypeface::Create(lf);
+    sk_sp<SkTypeface> face = SkTypefaceCache::FindByProcAndRef(FindByLogFont, &lf);
+    if (!face) {
+        face = LogFontTypeface::Make(lf);
         SkTypefaceCache::Add(face);
     }
-    return face;
+    return face.release();
 }
 
 /**
  *  The created SkTypeface takes ownership of fontMemResource.
  */
-SkTypeface* SkCreateFontMemResourceTypefaceFromLOGFONT(const LOGFONT& origLF, HANDLE fontMemResource) {
+sk_sp<SkTypeface> SkCreateFontMemResourceTypefaceFromLOGFONT(const LOGFONT& origLF, HANDLE fontMemResource) {
     LOGFONT lf = origLF;
     make_canonical(&lf);
     // We'll never get a cache hit, so no point in putting this in SkTypefaceCache.
-    return FontMemResourceTypeface::Create(lf, fontMemResource);
+    return FontMemResourceTypeface::Make(lf, fontMemResource);
 }
 
 /**
@@ -1855,18 +1856,17 @@
     return sk_sp<SkTypeface>(SkCreateFontMemResourceTypefaceFromLOGFONT(lf, fontReference));
 }
 
-SkStreamAsset* LogFontTypeface::onOpenStream(int* ttcIndex) const {
+std::unique_ptr<SkStreamAsset> LogFontTypeface::onOpenStream(int* ttcIndex) const {
     *ttcIndex = 0;
 
-    const DWORD kTTCTag =
-        SkEndian_SwapBE32(SkSetFourByteTag('t', 't', 'c', 'f'));
+    const DWORD kTTCTag = SkEndian_SwapBE32(SkSetFourByteTag('t', 't', 'c', 'f'));
     LOGFONT lf = fLogFont;
 
     HDC hdc = ::CreateCompatibleDC(nullptr);
     HFONT font = CreateFontIndirect(&lf);
     HFONT savefont = (HFONT)SelectObject(hdc, font);
 
-    SkMemoryStream* stream = nullptr;
+    std::unique_ptr<SkStreamAsset> stream;
     DWORD tables[2] = {kTTCTag, 0};
     for (size_t i = 0; i < SK_ARRAY_COUNT(tables); i++) {
         DWORD bufferSize = GetFontData(hdc, tables[i], 0, nullptr, 0);
@@ -1875,12 +1875,11 @@
             bufferSize = GetFontData(hdc, tables[i], 0, nullptr, 0);
         }
         if (bufferSize != GDI_ERROR) {
-            stream = new SkMemoryStream(bufferSize);
+            stream.reset(new SkMemoryStream(bufferSize));
             if (GetFontData(hdc, tables[i], 0, (void*)stream->getMemoryBase(), bufferSize)) {
                 break;
             } else {
-                delete stream;
-                stream = nullptr;
+                stream.reset();
             }
         }
     }
diff --git a/src/ports/SkFontMgr_FontConfigInterface.cpp b/src/ports/SkFontMgr_FontConfigInterface.cpp
index 8b24703..7cdd12c 100644
--- a/src/ports/SkFontMgr_FontConfigInterface.cpp
+++ b/src/ports/SkFontMgr_FontConfigInterface.cpp
@@ -19,7 +19,7 @@
 #include "SkResourceCache.h"
 #include <new>
 
-SkStreamAsset* SkTypeface_FCI::onOpenStream(int* ttcIndex) const {
+std::unique_ptr<SkStreamAsset> SkTypeface_FCI::onOpenStream(int* ttcIndex) const {
     *ttcIndex =  this->getIdentity().fTTCIndex;
 
     if (fFontData) {
@@ -27,10 +27,10 @@
         if (!stream) {
             return nullptr;
         }
-        return stream->duplicate().release();
+        return stream->duplicate();
     }
 
-    return fFCI->openStream(this->getIdentity());
+    return std::unique_ptr<SkStreamAsset>(fFCI->openStream(this->getIdentity()));
 }
 
 std::unique_ptr<SkFontData> SkTypeface_FCI::onMakeFontData() const {
@@ -103,9 +103,8 @@
 
 private:
     struct Result : public SkResourceCache::Rec {
-        Result(Request* request, SkTypeface* typeface)
-            : fRequest(request)
-            , fFace(SkSafeRef(typeface)) {}
+        Result(Request* request, sk_sp<SkTypeface> typeface)
+            : fRequest(request), fFace(std::move(typeface)) {}
         Result(Result&&) = default;
         Result& operator=(Result&&) = default;
 
@@ -124,20 +123,20 @@
     SkFontRequestCache(size_t maxSize) : fCachedResults(maxSize) {}
 
     /** Takes ownership of request. It will be deleted when no longer needed. */
-    void add(SkTypeface* face, Request* request) {
-        fCachedResults.add(new Result(request, face));
+    void add(sk_sp<SkTypeface> face, Request* request) {
+        fCachedResults.add(new Result(request, std::move(face)));
     }
     /** Does not take ownership of request. */
-    SkTypeface* findAndRef(Request* request) {
-        SkTypeface* face = nullptr;
+    sk_sp<SkTypeface> findAndRef(Request* request) {
+        sk_sp<SkTypeface> face;
         fCachedResults.find(*request, [](const SkResourceCache::Rec& rec, void* context) -> bool {
             const Result& result = static_cast<const Result&>(rec);
-            SkTypeface** face = static_cast<SkTypeface**>(context);
+            sk_sp<SkTypeface>* face = static_cast<sk_sp<SkTypeface>*>(context);
 
-            *face = result.fFace.get();
+            *face = result.fFace;
             return true;
         }, &face);
-        return SkSafeRef(face);
+        return face;
     }
 };
 
@@ -206,13 +205,13 @@
         }
 
         // Check if a typeface with this FontIdentity is already in the FontIdentity cache.
-        SkTypeface* face = fTFCache.findByProcAndRef(find_by_FontIdentity, &identity);
+        sk_sp<SkTypeface> face = fTFCache.findByProcAndRef(find_by_FontIdentity, &identity);
         if (!face) {
-            face = SkTypeface_FCI::Create(fFCI, identity, std::move(outFamilyName), outStyle);
+            face.reset(SkTypeface_FCI::Create(fFCI, identity, std::move(outFamilyName), outStyle));
             // Add this FontIdentity to the FontIdentity cache.
             fTFCache.add(face);
         }
-        return face;
+        return face.release();
     }
 
     SkTypeface* onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle&,
@@ -300,7 +299,7 @@
         // Check if this request is already in the request cache.
         using Request = SkFontRequestCache::Request;
         std::unique_ptr<Request> request(Request::Create(requestedFamilyName, requestedStyle));
-        SkTypeface* face = fCache.findAndRef(request.get());
+        sk_sp<SkTypeface> face = fCache.findAndRef(request.get());
         if (face) {
             return sk_sp<SkTypeface>(face);
         }
@@ -317,14 +316,14 @@
         // Check if a typeface with this FontIdentity is already in the FontIdentity cache.
         face = fTFCache.findByProcAndRef(find_by_FontIdentity, &identity);
         if (!face) {
-            face = SkTypeface_FCI::Create(fFCI, identity, std::move(outFamilyName), outStyle);
+            face.reset(SkTypeface_FCI::Create(fFCI, identity, std::move(outFamilyName), outStyle));
             // Add this FontIdentity to the FontIdentity cache.
             fTFCache.add(face);
         }
         // Add this request to the request cache.
         fCache.add(face, request.release());
 
-        return sk_sp<SkTypeface>(face);
+        return face;
     }
 };
 
diff --git a/src/ports/SkFontMgr_android.cpp b/src/ports/SkFontMgr_android.cpp
index 6518b2a..fdce4f9 100644
--- a/src/ports/SkFontMgr_android.cpp
+++ b/src/ports/SkFontMgr_android.cpp
@@ -90,9 +90,9 @@
         desc->setStyle(this->fontStyle());
         *serialize = false;
     }
-    SkStreamAsset* onOpenStream(int* ttcIndex) const override {
+    std::unique_ptr<SkStreamAsset> onOpenStream(int* ttcIndex) const override {
         *ttcIndex = fIndex;
-        return this->makeStream().release();
+        return this->makeStream();
     }
     std::unique_ptr<SkFontData> onMakeFontData() const override {
         return skstd::make_unique<SkFontData>(this->makeStream(), fIndex,
@@ -143,9 +143,9 @@
         *serialize = true;
     }
 
-    SkStreamAsset* onOpenStream(int* ttcIndex) const override {
+    std::unique_ptr<SkStreamAsset> onOpenStream(int* ttcIndex) const override {
         *ttcIndex = fData->getIndex();
-        return fData->getStream()->duplicate().release();
+        return fData->getStream()->duplicate();
     }
 
     std::unique_ptr<SkFontData> onMakeFontData() const override {
diff --git a/src/ports/SkFontMgr_custom.cpp b/src/ports/SkFontMgr_custom.cpp
index 708f4a6..7e1c668 100644
--- a/src/ports/SkFontMgr_custom.cpp
+++ b/src/ports/SkFontMgr_custom.cpp
@@ -48,7 +48,7 @@
 
 SkTypeface_Empty::SkTypeface_Empty() : INHERITED(SkFontStyle(), false, true, SkString(), 0) {}
 
-SkStreamAsset* SkTypeface_Empty::onOpenStream(int*) const { return nullptr; }
+std::unique_ptr<SkStreamAsset> SkTypeface_Empty::onOpenStream(int*) const { return nullptr; }
 
 sk_sp<SkTypeface> SkTypeface_Empty::onMakeClone(const SkFontArguments& args) const {
     return sk_ref_sp(this);
@@ -61,9 +61,9 @@
     , fData(std::move(fontData))
 { }
 
-SkStreamAsset* SkTypeface_Stream::onOpenStream(int* ttcIndex) const {
+std::unique_ptr<SkStreamAsset> SkTypeface_Stream::onOpenStream(int* ttcIndex) const {
     *ttcIndex = fData->getIndex();
-    return fData->getStream()->duplicate().release();
+    return fData->getStream()->duplicate();
 }
 
 std::unique_ptr<SkFontData> SkTypeface_Stream::onMakeFontData() const {
@@ -92,9 +92,9 @@
     , fPath(path)
 { }
 
-SkStreamAsset* SkTypeface_File::onOpenStream(int* ttcIndex) const {
+std::unique_ptr<SkStreamAsset> SkTypeface_File::onOpenStream(int* ttcIndex) const {
     *ttcIndex = this->getIndex();
-    return SkStream::MakeFromFile(fPath.c_str()).release();
+    return SkStream::MakeFromFile(fPath.c_str());
 }
 
 sk_sp<SkTypeface> SkTypeface_File::onMakeClone(const SkFontArguments& args) const {
diff --git a/src/ports/SkFontMgr_custom.h b/src/ports/SkFontMgr_custom.h
index 02bbf45..c9dea3f 100644
--- a/src/ports/SkFontMgr_custom.h
+++ b/src/ports/SkFontMgr_custom.h
@@ -49,7 +49,7 @@
     SkTypeface_Empty() ;
 
 protected:
-    SkStreamAsset* onOpenStream(int*) const override;
+    std::unique_ptr<SkStreamAsset> onOpenStream(int*) const override;
     sk_sp<SkTypeface> onMakeClone(const SkFontArguments& args) const override;
 
 private:
@@ -64,7 +64,7 @@
                       const SkString familyName);
 
 protected:
-    SkStreamAsset* onOpenStream(int* ttcIndex) const override;
+    std::unique_ptr<SkStreamAsset> onOpenStream(int* ttcIndex) const override;
     std::unique_ptr<SkFontData> onMakeFontData() const override;
     sk_sp<SkTypeface> onMakeClone(const SkFontArguments& args) const override;
 
@@ -81,7 +81,7 @@
                     const SkString familyName, const char path[], int index);
 
 protected:
-    SkStreamAsset* onOpenStream(int* ttcIndex) const override;
+    std::unique_ptr<SkStreamAsset> onOpenStream(int* ttcIndex) const override;
     sk_sp<SkTypeface> onMakeClone(const SkFontArguments& args) const override;
 
 private:
diff --git a/src/ports/SkFontMgr_fontconfig.cpp b/src/ports/SkFontMgr_fontconfig.cpp
index 79e8e0f..259bd8f 100644
--- a/src/ports/SkFontMgr_fontconfig.cpp
+++ b/src/ports/SkFontMgr_fontconfig.cpp
@@ -419,7 +419,6 @@
 
 class SkTypeface_stream : public SkTypeface_FreeType {
 public:
-    /** @param data takes ownership of the font data.*/
     SkTypeface_stream(std::unique_ptr<SkFontData> data,
                       SkString familyName, const SkFontStyle& style, bool fixedWidth)
         : INHERITED(style, fixedWidth)
@@ -435,9 +434,9 @@
         *serialize = true;
     }
 
-    SkStreamAsset* onOpenStream(int* ttcIndex) const override {
+    std::unique_ptr<SkStreamAsset> onOpenStream(int* ttcIndex) const override {
         *ttcIndex = fData->getIndex();
-        return fData->getStream()->duplicate().release();
+        return fData->getStream()->duplicate();
     }
 
     std::unique_ptr<SkFontData> onMakeFontData() const override {
@@ -464,9 +463,8 @@
 
 class SkTypeface_fontconfig : public SkTypeface_FreeType {
 public:
-    /** @param pattern takes ownership of the reference. */
-    static SkTypeface_fontconfig* Create(FcPattern* pattern) {
-        return new SkTypeface_fontconfig(pattern);
+    static sk_sp<SkTypeface_fontconfig> Make(SkAutoFcPattern pattern) {
+        return sk_sp<SkTypeface_fontconfig>(new SkTypeface_fontconfig(std::move(pattern)));
     }
     mutable SkAutoFcPattern fPattern;
 
@@ -483,15 +481,20 @@
         *serialize = false;
     }
 
-    SkStreamAsset* onOpenStream(int* ttcIndex) const override {
+    std::unique_ptr<SkStreamAsset> onOpenStream(int* ttcIndex) const override {
         FCLocker lock;
         *ttcIndex = get_int(fPattern, FC_INDEX, 0);
-        return SkStream::MakeFromFile(get_string(fPattern, FC_FILE)).release();
+        return SkStream::MakeFromFile(get_string(fPattern, FC_FILE));
     }
 
     void onFilterRec(SkScalerContextRec* rec) const override {
+        // FontConfig provides 10-scale-bitmap-fonts.conf which applies an inverse "pixelsize"
+        // matrix. It is not known if this .conf is active or not, so it is not clear if
+        // "pixelsize" should be applied before this matrix. Since using a matrix with a bitmap
+        // font isn't a great idea, only apply the matrix to outline fonts.
         const FcMatrix* fcMatrix = get_matrix(fPattern, FC_MATRIX);
-        if (fcMatrix) {
+        bool fcOutline = get_bool(fPattern, FC_OUTLINE, true);
+        if (fcOutline && fcMatrix) {
             // fPost2x2 is column-major, left handed (y down).
             // FcMatrix is column-major, right handed (y up).
             SkMatrix fm;
@@ -547,11 +550,10 @@
     }
 
 private:
-    /** @param pattern takes ownership of the reference. */
-    SkTypeface_fontconfig(FcPattern* pattern)
+    SkTypeface_fontconfig(SkAutoFcPattern pattern)
         : INHERITED(skfontstyle_from_fcpattern(pattern),
                     FC_PROPORTIONAL != get_int(pattern, FC_SPACING, FC_PROPORTIONAL))
-        , fPattern(pattern)
+        , fPattern(std::move(pattern))
     { }
 
     typedef SkTypeface_FreeType INHERITED;
@@ -564,11 +566,8 @@
 
     class StyleSet : public SkFontStyleSet {
     public:
-        /** @param parent does not take ownership of the reference.
-         *  @param fontSet takes ownership of the reference.
-         */
-        StyleSet(const SkFontMgr_fontconfig* parent, FcFontSet* fontSet)
-            : fFontMgr(SkRef(parent)), fFontSet(fontSet)
+        StyleSet(sk_sp<SkFontMgr_fontconfig> parent, SkAutoFcFontSet fontSet)
+            : fFontMgr(std::move(parent)), fFontSet(std::move(fontSet))
         { }
 
         ~StyleSet() override {
@@ -597,7 +596,7 @@
             FCLocker lock;
 
             FcPattern* match = fFontSet->fonts[index];
-            return fFontMgr->createTypefaceFromFcPattern(match);
+            return fFontMgr->createTypefaceFromFcPattern(match).release();
         }
 
         SkTypeface* matchStyle(const SkFontStyle& style) override {
@@ -617,11 +616,11 @@
                 return nullptr;
             }
 
-            return fFontMgr->createTypefaceFromFcPattern(match);
+            return fFontMgr->createTypefaceFromFcPattern(match).release();
         }
 
     private:
-        sk_sp<const SkFontMgr_fontconfig> fFontMgr;
+        sk_sp<SkFontMgr_fontconfig> fFontMgr;
         SkAutoFcFontSet fFontSet;
     };
 
@@ -684,13 +683,13 @@
     /** Creates a typeface using a typeface cache.
      *  @param pattern a complete pattern from FcFontRenderPrepare.
      */
-    SkTypeface* createTypefaceFromFcPattern(FcPattern* pattern) const {
+    sk_sp<SkTypeface> createTypefaceFromFcPattern(FcPattern* pattern) const {
         FCLocker::AssertHeld();
         SkAutoMutexAcquire ama(fTFCacheMutex);
-        SkTypeface* face = fTFCache.findByProcAndRef(FindByFcPattern, pattern);
-        if (nullptr == face) {
+        sk_sp<SkTypeface> face = fTFCache.findByProcAndRef(FindByFcPattern, pattern);
+        if (!face) {
             FcPatternReference(pattern);
-            face = SkTypeface_fontconfig::Create(pattern);
+            face = SkTypeface_fontconfig::Make(SkAutoFcPattern(pattern));
             if (face) {
                 // Cannot hold the lock when calling add; an evicted typeface may need to lock.
                 FCLocker::Suspend suspend;
@@ -831,11 +830,11 @@
             }
         }
 
-        return new StyleSet(this, matches.release());
+        return new StyleSet(sk_ref_sp(this), std::move(matches));
     }
 
-    virtual SkTypeface* onMatchFamilyStyle(const char familyName[],
-                                           const SkFontStyle& style) const override
+    SkTypeface* onMatchFamilyStyle(const char familyName[],
+                                   const SkFontStyle& style) const override
     {
         FCLocker lock;
 
@@ -869,14 +868,14 @@
             return nullptr;
         }
 
-        return createTypefaceFromFcPattern(font);
+        return createTypefaceFromFcPattern(font).release();
     }
 
-    virtual SkTypeface* onMatchFamilyStyleCharacter(const char familyName[],
-                                                    const SkFontStyle& style,
-                                                    const char* bcp47[],
-                                                    int bcp47Count,
-                                                    SkUnichar character) const override
+    SkTypeface* onMatchFamilyStyleCharacter(const char familyName[],
+                                            const SkFontStyle& style,
+                                            const char* bcp47[],
+                                            int bcp47Count,
+                                            SkUnichar character) const override
     {
         FCLocker lock;
 
@@ -911,11 +910,11 @@
             return nullptr;
         }
 
-        return createTypefaceFromFcPattern(font);
+        return createTypefaceFromFcPattern(font).release();
     }
 
-    virtual SkTypeface* onMatchFaceStyle(const SkTypeface* typeface,
-                                         const SkFontStyle& style) const override
+    SkTypeface* onMatchFaceStyle(const SkTypeface* typeface,
+                                 const SkFontStyle& style) const override
     {
         //TODO: should the SkTypeface_fontconfig know its family?
         const SkTypeface_fontconfig* fcTypeface =
diff --git a/src/ports/SkFontMgr_fuchsia.cpp b/src/ports/SkFontMgr_fuchsia.cpp
index 187a5a3..885f528 100644
--- a/src/ports/SkFontMgr_fuchsia.cpp
+++ b/src/ports/SkFontMgr_fuchsia.cpp
@@ -264,11 +264,7 @@
     if (result != ZX_OK || !familyInfo) return nullptr;
 
     std::vector<SkFontStyle> styles;
-#ifdef USE_STD_FOR_NON_NULLABLE_FIDL_FIELDS
     for (auto& style : familyInfo->styles) {
-#else
-    for (auto& style : *(familyInfo->styles)) {
-#endif
         styles.push_back(SkFontStyle(style.weight, style.width, FuchsiaToSkSlant(style.slant)));
     }
 
@@ -331,11 +327,7 @@
     request.weight = style.weight();
     request.width = style.width();
     request.slant = SkToFuchsiaSlant(style.slant());
-#ifdef USE_STD_FOR_NON_NULLABLE_FIDL_FIELDS
     request.language.reset(std::vector<std::string>(bcp47, bcp47 + bcp47Count));
-#else
-    request.language.reset(std::vector<fidl::StringPtr>(bcp47, bcp47 + bcp47Count));
-#endif
     request.character = character;
     request.fallback_group = GetFallbackGroupByName(familyName);
 
@@ -398,14 +390,14 @@
                                                          const fuchsia::mem::Buffer& buffer) const {
     SkAutoMutexAcquire mutexLock(fCacheMutex);
 
-    SkTypeface* cached = fTypefaceCache.findByProcAndRef(FindByTypefaceId, &id);
-    if (cached) return sk_sp<SkTypeface>(cached);
+    sk_sp<SkTypeface> cached = fTypefaceCache.findByProcAndRef(FindByTypefaceId, &id);
+    if (cached) return cached;
 
     sk_sp<SkData> data = GetOrCreateSkData(id.bufferId, buffer);
     if (!data) return nullptr;
 
     auto result = CreateTypefaceFromSkData(std::move(data), id);
-    fTypefaceCache.add(result.get());
+    fTypefaceCache.add(result);
     return result;
 }
 
diff --git a/src/ports/SkFontMgr_win_dw.cpp b/src/ports/SkFontMgr_win_dw.cpp
index 01d09c4..60e3e28 100644
--- a/src/ports/SkFontMgr_win_dw.cpp
+++ b/src/ports/SkFontMgr_win_dw.cpp
@@ -460,14 +460,14 @@
         IDWriteFontFamily* fontFamily) const {
     SkAutoMutexAcquire ama(fTFCacheMutex);
     ProtoDWriteTypeface spec = { fontFace, font, fontFamily };
-    SkTypeface* face = fTFCache.findByProcAndRef(FindByDWriteFont, &spec);
+    sk_sp<SkTypeface> face = fTFCache.findByProcAndRef(FindByDWriteFont, &spec);
     if (nullptr == face) {
-        face = DWriteFontTypeface::Create(fFactory.get(), fontFace, font, fontFamily);
+        face = DWriteFontTypeface::Make(fFactory.get(), fontFace, font, fontFamily);
         if (face) {
             fTFCache.add(face);
         }
     }
-    return sk_sp<SkTypeface>(face);
+    return face;
 }
 
 int SkFontMgr_DirectWrite::onCountFamilies() const {
@@ -917,10 +917,10 @@
 
             int faceIndex = fontFace->GetIndex();
             if (faceIndex == ttcIndex) {
-                return sk_sp<SkTypeface>(DWriteFontTypeface::Create(fFactory.get(),
-                                                  fontFace.get(), font.get(), fontFamily.get(),
-                                                  autoUnregisterFontFileLoader.detatch(),
-                                                  autoUnregisterFontCollectionLoader.detatch()));
+                return DWriteFontTypeface::Make(fFactory.get(),
+                                                fontFace.get(), font.get(), fontFamily.get(),
+                                                autoUnregisterFontFileLoader.detatch(),
+                                                autoUnregisterFontCollectionLoader.detatch());
             }
         }
     }
@@ -1005,10 +1005,10 @@
 
 #endif
 
-            return sk_sp<SkTypeface>(DWriteFontTypeface::Create(
+            return DWriteFontTypeface::Make(
                     fFactory.get(), fontFace.get(), font.get(), fontFamily.get(),
                     autoUnregisterFontFileLoader.detatch(),
-                    autoUnregisterFontCollectionLoader.detatch()));
+                    autoUnregisterFontCollectionLoader.detatch());
         }
     }
 
@@ -1162,7 +1162,7 @@
         localeNameLen = getUserDefaultLocaleNameProc(localeNameStorage, LOCALE_NAME_MAX_LENGTH);
         if (localeNameLen) {
             localeName = localeNameStorage;
-        };
+        }
     }
 
     return sk_make_sp<SkFontMgr_DirectWrite>(factory, collection, fallback,
diff --git a/src/ports/SkGlobalInitialization_default.cpp b/src/ports/SkGlobalInitialization_default.cpp
index 7241468..be0dae8 100644
--- a/src/ports/SkGlobalInitialization_default.cpp
+++ b/src/ports/SkGlobalInitialization_default.cpp
@@ -45,7 +45,6 @@
     #include "SkShaderBase.h"
     #include "SkShaderMaskFilter.h"
     #include "SkTableColorFilter.h"
-    #include "SkToSRGBColorFilter.h"
 
     #include "SkAlphaThresholdFilter.h"
     #include "SkBlurImageFilter.h"
@@ -79,7 +78,9 @@
         SK_REGISTER_FLATTENABLE(SkColor4Shader);
         SK_REGISTER_FLATTENABLE(SkColorFilterShader);
         SK_REGISTER_FLATTENABLE(SkColorShader);
-        SK_REGISTER_FLATTENABLE(SkComposeShader);
+        SK_REGISTER_FLATTENABLE(SkShader_Blend);
+        SK_REGISTER_FLATTENABLE(SkShader_Lerp);
+        SK_REGISTER_FLATTENABLE(SkShader_LerpRed);
         SK_REGISTER_FLATTENABLE(SkEmptyShader);
         SK_REGISTER_FLATTENABLE(SkLocalMatrixShader);
         SK_REGISTER_FLATTENABLE(SkPictureShader);
@@ -91,7 +92,6 @@
         // Color filters.
         SK_REGISTER_FLATTENABLE(SkColorMatrixFilterRowMajor255);
         SK_REGISTER_FLATTENABLE(SkLumaColorFilter);
-        SK_REGISTER_FLATTENABLE(SkToSRGBColorFilter);
         SkColorFilter::RegisterFlattenables();
         SkHighContrastFilter::RegisterFlattenables();
         SkOverdrawColorFilter::RegisterFlattenables();
diff --git a/src/ports/SkOSFile_ios.h b/src/ports/SkOSFile_ios.h
index c095ba3..1d370cb 100644
--- a/src/ports/SkOSFile_ios.h
+++ b/src/ports/SkOSFile_ios.h
@@ -39,6 +39,7 @@
 
     // Convert the string reference into an SkString
     result->set(CFStringGetCStringPtr(imagePath, encodingMethod));
+    CFRelease(imagePath);
     return true;
 }
 #endif
diff --git a/src/ports/SkTypeface_win_dw.cpp b/src/ports/SkTypeface_win_dw.cpp
index 3266ee9..c7802aa 100644
--- a/src/ports/SkTypeface_win_dw.cpp
+++ b/src/ports/SkTypeface_win_dw.cpp
@@ -299,7 +299,7 @@
     }
 
     int ttcIndex;
-    std::unique_ptr<SkStream> stream(this->openStream(&ttcIndex));
+    std::unique_ptr<SkStreamAsset> stream = this->openStream(&ttcIndex);
     return stream.get() ? SkFontStream::GetTableTags(stream.get(), ttcIndex, tags) : 0;
 }
 
@@ -357,12 +357,12 @@
 
         SkTScopedComPtr<IDWriteFontFace> newFontFace;
         HRN(newFontFace5->QueryInterface(&newFontFace));
-        return sk_sp<SkTypeface>(DWriteFontTypeface::Create(fFactory.get(),
-                                                            newFontFace.get(),
-                                                            fDWriteFont.get(),
-                                                            fDWriteFontFamily.get(),
-                                                            fDWriteFontFileLoader.get(),
-                                                            fDWriteFontCollectionLoader.get()));
+        return DWriteFontTypeface::Make(fFactory.get(),
+                                        newFontFace.get(),
+                                        fDWriteFont.get(),
+                                        fDWriteFontFamily.get(),
+                                        fDWriteFontFileLoader.get(),
+                                        fDWriteFontCollectionLoader.get());
     }
 
 #endif
@@ -370,7 +370,7 @@
     return sk_ref_sp(this);
 }
 
-SkStreamAsset* DWriteFontTypeface::onOpenStream(int* ttcIndex) const {
+std::unique_ptr<SkStreamAsset> DWriteFontTypeface::onOpenStream(int* ttcIndex) const {
     *ttcIndex = fDWriteFontFace->GetIndex();
 
     UINT32 numFiles;
@@ -396,7 +396,7 @@
                                              &fontFileStream),
          "Could not create font file stream.");
 
-    return new SkDWriteFontFileStream(fontFileStream.get());
+    return std::unique_ptr<SkStreamAsset>(new SkDWriteFontFileStream(fontFileStream.get()));
 }
 
 SkScalerContext* DWriteFontTypeface::onCreateScalerContext(const SkScalerContextEffects& effects,
diff --git a/src/ports/SkTypeface_win_dw.h b/src/ports/SkTypeface_win_dw.h
index ce0e753..855ef79 100644
--- a/src/ports/SkTypeface_win_dw.h
+++ b/src/ports/SkTypeface_win_dw.h
@@ -82,14 +82,17 @@
     SkTScopedComPtr<IDWriteFontFace2> fDWriteFontFace2;
     SkTScopedComPtr<IDWriteFontFace4> fDWriteFontFace4;
 
-    static DWriteFontTypeface* Create(IDWriteFactory* factory,
-                                      IDWriteFontFace* fontFace,
-                                      IDWriteFont* font,
-                                      IDWriteFontFamily* fontFamily,
-                                      IDWriteFontFileLoader* fontFileLoader = nullptr,
-                                      IDWriteFontCollectionLoader* fontCollectionLoader = nullptr) {
-        return new DWriteFontTypeface(get_style(font), factory, fontFace, font, fontFamily,
-                                      fontFileLoader, fontCollectionLoader);
+    static sk_sp<DWriteFontTypeface> Make(
+        IDWriteFactory* factory,
+        IDWriteFontFace* fontFace,
+        IDWriteFont* font,
+        IDWriteFontFamily* fontFamily,
+        IDWriteFontFileLoader* fontFileLoader = nullptr,
+        IDWriteFontCollectionLoader* fontCollectionLoader = nullptr)
+    {
+        return sk_sp<DWriteFontTypeface>(
+            new DWriteFontTypeface(get_style(font), factory, fontFace, font, fontFamily,
+                                   fontFileLoader, fontCollectionLoader));
     }
 
 protected:
@@ -106,7 +109,7 @@
     }
 
     sk_sp<SkTypeface> onMakeClone(const SkFontArguments&) const override;
-    SkStreamAsset* onOpenStream(int* ttcIndex) const override;
+    std::unique_ptr<SkStreamAsset> onOpenStream(int* ttcIndex) const override;
     SkScalerContext* onCreateScalerContext(const SkScalerContextEffects&,
                                            const SkDescriptor*) const override;
     void onFilterRec(SkScalerContextRec*) const override;
diff --git a/src/shaders/SkBitmapProcShader.cpp b/src/shaders/SkBitmapProcShader.cpp
index 402fa2f..e141f51 100644
--- a/src/shaders/SkBitmapProcShader.cpp
+++ b/src/shaders/SkBitmapProcShader.cpp
@@ -93,7 +93,7 @@
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 SkShaderBase::Context* SkBitmapProcLegacyShader::MakeContext(
-    const SkShaderBase& shader, TileMode tmx, TileMode tmy,
+    const SkShaderBase& shader, SkTileMode tmx, SkTileMode tmy,
     const SkBitmapProvider& provider, const ContextRec& rec, SkArenaAlloc* alloc)
 {
     SkMatrix totalInverse;
diff --git a/src/shaders/SkBitmapProcShader.h b/src/shaders/SkBitmapProcShader.h
index 7c5cdcf..05640a5 100644
--- a/src/shaders/SkBitmapProcShader.h
+++ b/src/shaders/SkBitmapProcShader.h
@@ -16,7 +16,7 @@
 private:
     friend class SkImageShader;
 
-    static Context* MakeContext(const SkShaderBase&, TileMode tmx, TileMode tmy,
+    static Context* MakeContext(const SkShaderBase&, SkTileMode tmx, SkTileMode tmy,
                                 const SkBitmapProvider&, const ContextRec&, SkArenaAlloc* alloc);
 
     typedef SkShaderBase INHERITED;
diff --git a/src/shaders/SkColorFilterShader.cpp b/src/shaders/SkColorFilterShader.cpp
index 16bf565..de27053 100644
--- a/src/shaders/SkColorFilterShader.cpp
+++ b/src/shaders/SkColorFilterShader.cpp
@@ -7,7 +7,6 @@
 
 #include "SkArenaAlloc.h"
 #include "SkColorFilterShader.h"
-#include "SkColorSpaceXformer.h"
 #include "SkReadBuffer.h"
 #include "SkWriteBuffer.h"
 #include "SkShader.h"
@@ -39,21 +38,19 @@
     buffer.writeFlattenable(fFilter.get());
 }
 
-bool SkColorFilterShader::onAppendStages(const StageRec& rec) const {
+bool SkColorFilterShader::onAppendStages(const SkStageRec& rec) const {
     if (!as_SB(fShader)->appendStages(rec)) {
         return false;
     }
-    fFilter->appendStages(rec.fPipeline, rec.fDstCS, rec.fAlloc, fShader->isOpaque());
+    fFilter->appendStages(rec, fShader->isOpaque());
     return true;
 }
 
-sk_sp<SkShader> SkColorFilterShader::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    return xformer->apply(fShader.get())->makeWithColorFilter(xformer->apply(fFilter.get()));
-}
-
 #if SK_SUPPORT_GPU
 /////////////////////////////////////////////////////////////////////
 
+#include "GrContext.h"
+
 std::unique_ptr<GrFragmentProcessor> SkColorFilterShader::asFragmentProcessor(
         const GrFPArgs& args) const {
     auto fp1 = as_SB(fShader)->asFragmentProcessor(args);
diff --git a/src/shaders/SkColorFilterShader.h b/src/shaders/SkColorFilterShader.h
index 59c7004..b274ae7 100644
--- a/src/shaders/SkColorFilterShader.h
+++ b/src/shaders/SkColorFilterShader.h
@@ -23,8 +23,7 @@
 
 protected:
     void flatten(SkWriteBuffer&) const override;
-    sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override;
-    bool onAppendStages(const StageRec&) const override;
+    bool onAppendStages(const SkStageRec&) const override;
 
 private:
     SK_FLATTENABLE_HOOKS(SkColorFilterShader)
diff --git a/src/shaders/SkColorShader.cpp b/src/shaders/SkColorShader.cpp
index 1af2e7c..9884e9a 100644
--- a/src/shaders/SkColorShader.cpp
+++ b/src/shaders/SkColorShader.cpp
@@ -34,7 +34,11 @@
             info->fColors[0] = fColor;
         }
         info->fColorCount = 1;
+#ifdef SK_SUPPORT_LEGACY_TILEMODE_ENUM
         info->fTileMode = SkShader::kRepeat_TileMode;
+#else
+        info->fTileMode = SkTileMode::kRepeat;
+#endif
     }
     return kColor_GradientType;
 }
@@ -52,7 +56,7 @@
         sk_sp<SkData> data = buffer.readByteArrayAsData();
         colorSpace = data ? SkColorSpace::Deserialize(data->data(), data->size()) : nullptr;
     }
-    return SkShader::MakeColorShader(color, std::move(colorSpace));
+    return SkShaders::Color(color, std::move(colorSpace));
 }
 
 void SkColor4Shader::flatten(SkWriteBuffer& buffer) const {
@@ -67,21 +71,20 @@
 }
 
 
-sk_sp<SkShader> SkColor4Shader::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    SkColor4f color = fColor;
-    SkColorSpaceXformSteps(fColorSpace.get(),    kUnpremul_SkAlphaType,
-                           xformer->dst().get(), kUnpremul_SkAlphaType).apply(color.vec());
-    return SkShader::MakeColorShader(color.toSkColor());
-}
-
+#ifdef SK_SUPPORT_LEGACY_SHADER_FACTORIES
 sk_sp<SkShader> SkShader::MakeColorShader(const SkColor4f& color, sk_sp<SkColorSpace> space) {
+    return SkShaders::Color(color, std::move(space));
+}
+#endif
+
+sk_sp<SkShader> SkShaders::Color(const SkColor4f& color, sk_sp<SkColorSpace> space) {
     if (!SkScalarsAreFinite(color.vec(), 4)) {
         return nullptr;
     }
     return sk_make_sp<SkColor4Shader>(color, std::move(space));
 }
 
-bool SkColorShader::onAppendStages(const StageRec& rec) const {
+bool SkColorShader::onAppendStages(const SkStageRec& rec) const {
     SkColor4f color = SkColor4f::FromColor(fColor);
     SkColorSpaceXformSteps(sk_srgb_singleton(), kUnpremul_SkAlphaType,
                            rec.fDstCS,          kUnpremul_SkAlphaType).apply(color.vec());
@@ -89,7 +92,7 @@
     return true;
 }
 
-bool SkColor4Shader::onAppendStages(const StageRec& rec) const {
+bool SkColor4Shader::onAppendStages(const SkStageRec& rec) const {
     SkColor4f color = fColor;
     SkColorSpaceXformSteps(fColorSpace.get(), kUnpremul_SkAlphaType,
                            rec.fDstCS,        kUnpremul_SkAlphaType).apply(color.vec());
@@ -102,7 +105,7 @@
 #include "GrColorSpaceInfo.h"
 #include "GrColorSpaceXform.h"
 #include "SkGr.h"
-#include "effects/GrConstColorProcessor.h"
+#include "effects/generated/GrConstColorProcessor.h"
 
 std::unique_ptr<GrFragmentProcessor> SkColorShader::asFragmentProcessor(
         const GrFPArgs& args) const {
diff --git a/src/shaders/SkColorShader.h b/src/shaders/SkColorShader.h
index 0ceb985..e15da4b 100644
--- a/src/shaders/SkColorShader.h
+++ b/src/shaders/SkColorShader.h
@@ -8,7 +8,6 @@
 #ifndef SkColorShader_DEFINED
 #define SkColorShader_DEFINED
 
-#include "SkColorSpaceXformer.h"
 #include "SkShaderBase.h"
 
 /** \class SkColorShader
@@ -43,11 +42,7 @@
         return true;
     }
 
-    bool onAppendStages(const StageRec&) const override;
-
-    sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override {
-        return SkShader::MakeColorShader(xformer->apply(fColor));
-    }
+    bool onAppendStages(const SkStageRec&) const override;
 
     SkColor fColor;
 };
@@ -67,8 +62,7 @@
     SK_FLATTENABLE_HOOKS(SkColor4Shader)
 
     void flatten(SkWriteBuffer&) const override;
-    bool onAppendStages(const StageRec&) const override;
-    sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override;
+    bool onAppendStages(const SkStageRec&) const override;
 
     sk_sp<SkColorSpace> fColorSpace;
     const SkColor4f     fColor;
diff --git a/src/shaders/SkComposeShader.cpp b/src/shaders/SkComposeShader.cpp
index 2dee742..d4a7baa 100644
--- a/src/shaders/SkComposeShader.cpp
+++ b/src/shaders/SkComposeShader.cpp
@@ -16,127 +16,207 @@
 #include "SkWriteBuffer.h"
 #include "SkString.h"
 
-sk_sp<SkShader> SkShader::MakeCompose(sk_sp<SkShader> dst, sk_sp<SkShader> src, SkBlendMode mode,
-                                      float lerpT) {
-    if (!src || !dst || SkScalarIsNaN(lerpT)) {
+#ifdef SK_SUPPORT_LEGACY_SHADER_FACTORIES
+sk_sp<SkShader> SkShader::MakeBlend(SkBlendMode mode, sk_sp<SkShader> dst, sk_sp<SkShader> src) {
+    return SkShaders::Blend(mode, std::move(dst), std::move(src));
+}
+
+sk_sp<SkShader> SkShader::MakeLerp(float weight, sk_sp<SkShader> dst, sk_sp<SkShader> src) {
+    return SkShaders::Lerp(weight, std::move(dst), std::move(src));
+}
+#endif
+
+sk_sp<SkShader> SkShaders::Blend(SkBlendMode mode, sk_sp<SkShader> dst, sk_sp<SkShader> src) {
+    switch (mode) {
+        case SkBlendMode::kClear: return Color(0);
+        case SkBlendMode::kDst:   return dst;
+        case SkBlendMode::kSrc:   return src;
+        default: break;
+    }
+    return sk_sp<SkShader>(new SkShader_Blend(mode, std::move(dst), std::move(src)));
+}
+
+sk_sp<SkShader> SkShaders::Lerp(float weight, sk_sp<SkShader> dst, sk_sp<SkShader> src) {
+    if (SkScalarIsNaN(weight)) {
         return nullptr;
     }
-    lerpT = SkScalarPin(lerpT, 0, 1);
-
-    if (lerpT == 0) {
+    if (dst == src) {
         return dst;
-    } else if (lerpT == 1) {
-        if (mode == SkBlendMode::kSrc) {
-            return src;
-        }
-        if (mode == SkBlendMode::kDst) {
-            return dst;
-        }
     }
-    return sk_sp<SkShader>(new SkComposeShader(std::move(dst), std::move(src), mode, lerpT));
+    if (weight <= 0) {
+        return dst;
+    } else if (weight >= 1) {
+        return src;
+    }
+    return sk_sp<SkShader>(new SkShader_Lerp(weight, std::move(dst), std::move(src)));
+}
+
+sk_sp<SkShader> SkShaders::Lerp(sk_sp<SkShader> red, sk_sp<SkShader> dst, sk_sp<SkShader> src) {
+    if (!red) {
+        return nullptr;
+    }
+    if (dst == src) {
+        return dst;
+    }
+    return sk_sp<SkShader>(new SkShader_LerpRed(std::move(red), std::move(dst), std::move(src)));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
-sk_sp<SkFlattenable> SkComposeShader::CreateProc(SkReadBuffer& buffer) {
+static bool append_shader_or_paint(const SkStageRec& rec, SkShader* shader) {
+    if (shader) {
+        if (!as_SB(shader)->appendStages(rec)) {
+            return false;
+        }
+    } else {
+        rec.fPipeline->append_constant_color(rec.fAlloc, rec.fPaint.getColor4f().premul().vec());
+    }
+    return true;
+}
+
+// Returns the output of e0, and leaves the output of e1 in r,g,b,a
+static float* append_two_shaders(const SkStageRec& rec, SkShader* s0, SkShader* s1) {
+    struct Storage {
+        float   fRes0[4 * SkRasterPipeline_kMaxStride];
+    };
+    auto storage = rec.fAlloc->make<Storage>();
+
+    if (!append_shader_or_paint(rec, s0)) {
+        return nullptr;
+    }
+    rec.fPipeline->append(SkRasterPipeline::store_src, storage->fRes0);
+
+    if (!append_shader_or_paint(rec, s1)) {
+        return nullptr;
+    }
+    return storage->fRes0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+sk_sp<SkFlattenable> SkShader_Blend::CreateProc(SkReadBuffer& buffer) {
     sk_sp<SkShader> dst(buffer.readShader());
     sk_sp<SkShader> src(buffer.readShader());
     unsigned        mode = buffer.read32();
-    float           lerp = buffer.readScalar();
 
     // check for valid mode before we cast to the enum type
     if (!buffer.validate(mode <= (unsigned)SkBlendMode::kLastMode)) {
         return nullptr;
     }
-    return MakeCompose(std::move(dst), std::move(src), static_cast<SkBlendMode>(mode), lerp);
+    return SkShaders::Blend(static_cast<SkBlendMode>(mode), std::move(dst), std::move(src));
 }
 
-void SkComposeShader::flatten(SkWriteBuffer& buffer) const {
+void SkShader_Blend::flatten(SkWriteBuffer& buffer) const {
     buffer.writeFlattenable(fDst.get());
     buffer.writeFlattenable(fSrc.get());
     buffer.write32((int)fMode);
-    buffer.writeScalar(fLerpT);
 }
 
-sk_sp<SkShader> SkComposeShader::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    return MakeCompose(xformer->apply(fDst.get()), xformer->apply(fSrc.get()),
-                       fMode, fLerpT);
-}
-
-#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
-bool SkComposeShader::asACompose(ComposeRec* rec) const {
-    if (!this->isJustMode()) {
+bool SkShader_Blend::onAppendStages(const SkStageRec& rec) const {
+    float* res0 = append_two_shaders(rec, fDst.get(), fSrc.get());
+    if (!res0) {
         return false;
     }
 
-    if (rec) {
-        rec->fShaderA   = fDst.get();
-        rec->fShaderB   = fSrc.get();
-        rec->fBlendMode = fMode;
-    }
+    rec.fPipeline->append(SkRasterPipeline::load_dst, res0);
+    SkBlendMode_AppendStages(fMode, rec.fPipeline);
     return true;
 }
-#endif
 
-bool SkComposeShader::onAppendStages(const StageRec& rec) const {
+sk_sp<SkFlattenable> SkShader_Lerp::CreateProc(SkReadBuffer& buffer) {
+    sk_sp<SkShader> dst(buffer.readShader());
+    sk_sp<SkShader> src(buffer.readShader());
+    float t = buffer.readScalar();
+    return buffer.isValid() ? SkShaders::Lerp(t, std::move(dst), std::move(src)) : nullptr;
+}
+
+void SkShader_Lerp::flatten(SkWriteBuffer& buffer) const {
+    buffer.writeFlattenable(fDst.get());
+    buffer.writeFlattenable(fSrc.get());
+    buffer.writeScalar(fWeight);
+}
+
+bool SkShader_Lerp::onAppendStages(const SkStageRec& rec) const {
+    float* res0 = append_two_shaders(rec, fDst.get(), fSrc.get());
+    if (!res0) {
+        return false;
+    }
+
+    rec.fPipeline->append(SkRasterPipeline::load_dst, res0);
+    rec.fPipeline->append(SkRasterPipeline::lerp_1_float, &fWeight);
+    return true;
+}
+
+sk_sp<SkFlattenable> SkShader_LerpRed::CreateProc(SkReadBuffer& buffer) {
+    sk_sp<SkShader> dst(buffer.readShader());
+    sk_sp<SkShader> src(buffer.readShader());
+    sk_sp<SkShader> red(buffer.readShader());
+    return buffer.isValid() ?
+           SkShaders::Lerp(std::move(red), std::move(dst), std::move(src)) : nullptr;
+}
+
+void SkShader_LerpRed::flatten(SkWriteBuffer& buffer) const {
+    buffer.writeFlattenable(fDst.get());
+    buffer.writeFlattenable(fSrc.get());
+    buffer.writeFlattenable(fRed.get());
+}
+
+bool SkShader_LerpRed::onAppendStages(const SkStageRec& rec) const {
     struct Storage {
-        float   fRGBA[4 * SkRasterPipeline_kMaxStride];
-        float   fAlpha;
+        float   fRed[4 * SkRasterPipeline_kMaxStride];
     };
     auto storage = rec.fAlloc->make<Storage>();
-
-    if (!as_SB(fSrc)->appendStages(rec)) {
+    if (!as_SB(fRed)->appendStages(rec)) {
         return false;
     }
-    // This outputs r,g,b,a, which we'll need later when we apply the mode, but we save it off now
-    // since fShaderB will overwrite them.
-    rec.fPipeline->append(SkRasterPipeline::store_rgba, storage->fRGBA);
+    // actually, we just need the first (red) channel, but for now we store rgba
+    rec.fPipeline->append(SkRasterPipeline::store_src, storage->fRed);
 
-    if (!as_SB(fDst)->appendStages(rec)) {
+    float* res0 = append_two_shaders(rec, fDst.get(), fSrc.get());
+    if (!res0) {
         return false;
     }
-    // We now have our logical 'dst' in r,g,b,a, but we need it in dr,dg,db,da for the mode/lerp
-    // so we have to shuttle them. If we had a stage the would load_into_dst, then we could
-    // reverse the two shader invocations, and avoid this move...
-    rec.fPipeline->append(SkRasterPipeline::move_src_dst);
-    rec.fPipeline->append(SkRasterPipeline::load_rgba, storage->fRGBA);
 
-    if (!this->isJustLerp()) {
-        SkBlendMode_AppendStages(fMode, rec.fPipeline);
-    }
-    if (!this->isJustMode()) {
-        rec.fPipeline->append(SkRasterPipeline::lerp_1_float, &fLerpT);
-    }
+    rec.fPipeline->append(SkRasterPipeline::load_dst, res0);
+    rec.fPipeline->append(SkRasterPipeline::lerp_native, &storage->fRed[0]);
     return true;
 }
 
 #if SK_SUPPORT_GPU
 
-#include "effects/GrConstColorProcessor.h"
+#include "effects/generated/GrConstColorProcessor.h"
 #include "effects/GrXfermodeFragmentProcessor.h"
+#include "GrRecordingContext.h"
+#include "effects/generated/GrComposeLerpEffect.h"
+#include "effects/generated/GrComposeLerpRedEffect.h"
 
-/////////////////////////////////////////////////////////////////////
+static std::unique_ptr<GrFragmentProcessor> as_fp(const GrFPArgs& args, SkShader* shader) {
+    return shader ? as_SB(shader)->asFragmentProcessor(args) : nullptr;
+}
 
-std::unique_ptr<GrFragmentProcessor> SkComposeShader::asFragmentProcessor(
-        const GrFPArgs& args) const {
-    if (this->isJustMode()) {
-        SkASSERT(fMode != SkBlendMode::kSrc && fMode != SkBlendMode::kDst); // caught in factory
-        if (fMode == SkBlendMode::kClear) {
-            return GrConstColorProcessor::Make(SK_PMColor4fTRANSPARENT,
-                                               GrConstColorProcessor::InputMode::kIgnore);
-        }
-    }
-
-    std::unique_ptr<GrFragmentProcessor> fpA(as_SB(fDst)->asFragmentProcessor(args));
-    if (!fpA) {
+std::unique_ptr<GrFragmentProcessor> SkShader_Blend::asFragmentProcessor(const GrFPArgs& args) const {
+    auto fpA = as_fp(args, fDst.get());
+    auto fpB = as_fp(args, fSrc.get());
+    if (!fpA || !fpB) {
         return nullptr;
     }
-    std::unique_ptr<GrFragmentProcessor> fpB(as_SB(fSrc)->asFragmentProcessor(args));
-    if (!fpB) {
-        return nullptr;
-    }
-    // TODO: account for fLerpT when it is < 1
     return GrXfermodeFragmentProcessor::MakeFromTwoProcessors(std::move(fpB),
                                                               std::move(fpA), fMode);
 }
+
+std::unique_ptr<GrFragmentProcessor> SkShader_Lerp::asFragmentProcessor(const GrFPArgs& args) const {
+    auto fpA = as_fp(args, fDst.get());
+    auto fpB = as_fp(args, fSrc.get());
+    return GrComposeLerpEffect::Make(std::move(fpA), std::move(fpB), fWeight);
+}
+
+std::unique_ptr<GrFragmentProcessor> SkShader_LerpRed::asFragmentProcessor(const GrFPArgs& args) const {
+    auto fpA = as_fp(args, fDst.get());
+    auto fpB = as_fp(args, fSrc.get());
+    auto red = as_SB(fRed)->asFragmentProcessor(args);
+    if (!red) {
+        return nullptr;
+    }
+    return GrComposeLerpRedEffect::Make(std::move(fpA), std::move(fpB), std::move(red));
+}
 #endif
diff --git a/src/shaders/SkComposeShader.h b/src/shaders/SkComposeShader.h
index 7027d33..4d2aa5a 100644
--- a/src/shaders/SkComposeShader.h
+++ b/src/shaders/SkComposeShader.h
@@ -11,46 +11,85 @@
 #include "SkShaderBase.h"
 #include "SkBlendMode.h"
 
-class SkComposeShader final : public SkShaderBase {
+class SkShader_Blend final : public SkShaderBase {
 public:
-    SkComposeShader(sk_sp<SkShader> dst, sk_sp<SkShader> src, SkBlendMode mode, float lerpT)
+    SkShader_Blend(SkBlendMode mode, sk_sp<SkShader> dst, sk_sp<SkShader> src)
         : fDst(std::move(dst))
         , fSrc(std::move(src))
-        , fLerpT(lerpT)
         , fMode(mode)
+    {}
+
+#if SK_SUPPORT_GPU
+    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&) const override;
+#endif
+
+protected:
+    SkShader_Blend(SkReadBuffer&);
+    void flatten(SkWriteBuffer&) const override;
+    bool onAppendStages(const SkStageRec&) const override;
+
+private:
+    SK_FLATTENABLE_HOOKS(SkShader_Blend)
+
+    sk_sp<SkShader>     fDst;
+    sk_sp<SkShader>     fSrc;
+    const SkBlendMode   fMode;
+
+    typedef SkShaderBase INHERITED;
+};
+
+class SkShader_Lerp final : public SkShaderBase {
+public:
+    SkShader_Lerp(float weight, sk_sp<SkShader> dst, sk_sp<SkShader> src)
+        : fDst(std::move(dst))
+        , fSrc(std::move(src))
+        , fWeight(weight)
     {
-        SkASSERT(lerpT >= 0 && lerpT <= 1);
+        SkASSERT(weight >= 0 && weight <= 1);
     }
 
 #if SK_SUPPORT_GPU
     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&) const override;
 #endif
 
-#ifdef SK_DEBUGx
-    SkShader* getShaderA() { return fShaderA.get(); }
-    SkShader* getShaderB() { return fShaderB.get(); }
-#endif
+protected:
+    SkShader_Lerp(SkReadBuffer&);
+    void flatten(SkWriteBuffer&) const override;
+    bool onAppendStages(const SkStageRec&) const override;
 
-#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
-    bool asACompose(ComposeRec* rec) const override;
+private:
+    SK_FLATTENABLE_HOOKS(SkShader_Lerp)
+
+    sk_sp<SkShader> fDst;
+    sk_sp<SkShader> fSrc;
+    const float     fWeight;
+
+    typedef SkShaderBase INHERITED;
+};
+
+class SkShader_LerpRed final : public SkShaderBase {
+public:
+    SkShader_LerpRed(sk_sp<SkShader> red, sk_sp<SkShader> dst, sk_sp<SkShader> src)
+        : fDst(std::move(dst))
+        , fSrc(std::move(src))
+        , fRed(std::move(red))
+    {}
+
+#if SK_SUPPORT_GPU
+    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&) const override;
 #endif
 
 protected:
-    SkComposeShader(SkReadBuffer&);
+    SkShader_LerpRed(SkReadBuffer&);
     void flatten(SkWriteBuffer&) const override;
-    sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override;
-    bool onAppendStages(const StageRec&) const override;
+    bool onAppendStages(const SkStageRec&) const override;
 
 private:
-    SK_FLATTENABLE_HOOKS(SkComposeShader)
+    SK_FLATTENABLE_HOOKS(SkShader_LerpRed)
 
-    sk_sp<SkShader>     fDst;
-    sk_sp<SkShader>     fSrc;
-    const float         fLerpT;
-    const SkBlendMode   fMode;
-
-    bool isJustMode() const { return fLerpT == 1; }
-    bool isJustLerp() const { return fMode == SkBlendMode::kSrc; }
+    sk_sp<SkShader> fDst;
+    sk_sp<SkShader> fSrc;
+    sk_sp<SkShader> fRed;
 
     typedef SkShaderBase INHERITED;
 };
diff --git a/src/shaders/SkEmptyShader.h b/src/shaders/SkEmptyShader.h
index b0dae05..7d2df0e 100644
--- a/src/shaders/SkEmptyShader.h
+++ b/src/shaders/SkEmptyShader.h
@@ -33,7 +33,7 @@
         // which will write data we don't care to serialize or decode.
     }
 
-    bool onAppendStages(const StageRec&) const override {
+    bool onAppendStages(const SkStageRec&) const override {
         return false;
     }
 
diff --git a/src/shaders/SkImageShader.cpp b/src/shaders/SkImageShader.cpp
index c1d14d8..581a310 100644
--- a/src/shaders/SkImageShader.cpp
+++ b/src/shaders/SkImageShader.cpp
@@ -14,25 +14,26 @@
 #include "SkEmptyShader.h"
 #include "SkImage_Base.h"
 #include "SkImageShader.h"
+#include "SkRasterPipeline.h"
 #include "SkReadBuffer.h"
 #include "SkWriteBuffer.h"
 
 /**
  *  We are faster in clamp, so always use that tiling when we can.
  */
-static SkShader::TileMode optimize(SkShader::TileMode tm, int dimension) {
+static SkTileMode optimize(SkTileMode tm, int dimension) {
     SkASSERT(dimension > 0);
 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
     // need to update frameworks/base/libs/hwui/tests/unit/SkiaBehaviorTests.cpp:55 to allow
     // for transforming to clamp.
     return tm;
 #else
-    return dimension == 1 ? SkShader::kClamp_TileMode : tm;
+    return dimension == 1 ? SkTileMode::kClamp : tm;
 #endif
 }
 
 SkImageShader::SkImageShader(sk_sp<SkImage> img,
-                             TileMode tmx, TileMode tmy,
+                             SkTileMode tmx, SkTileMode tmy,
                              const SkMatrix* localMatrix,
                              bool clampAsIfUnpremul)
     : INHERITED(localMatrix)
@@ -46,27 +47,28 @@
 // so there's no need to read or write it here.
 
 sk_sp<SkFlattenable> SkImageShader::CreateProc(SkReadBuffer& buffer) {
-    const TileMode tx = (TileMode)buffer.readUInt();
-    const TileMode ty = (TileMode)buffer.readUInt();
+    auto tmx = buffer.read32LE<SkTileMode>(SkTileMode::kLastTileMode);
+    auto tmy = buffer.read32LE<SkTileMode>(SkTileMode::kLastTileMode);
     SkMatrix localMatrix;
     buffer.readMatrix(&localMatrix);
     sk_sp<SkImage> img = buffer.readImage();
     if (!img) {
         return nullptr;
     }
-    return SkImageShader::Make(std::move(img), tx, ty, &localMatrix);
+    return SkImageShader::Make(std::move(img), tmx, tmy, &localMatrix);
 }
 
 void SkImageShader::flatten(SkWriteBuffer& buffer) const {
-    buffer.writeUInt(fTileModeX);
-    buffer.writeUInt(fTileModeY);
+    buffer.writeUInt((unsigned)fTileModeX);
+    buffer.writeUInt((unsigned)fTileModeY);
     buffer.writeMatrix(this->getLocalMatrix());
     buffer.writeImage(fImage.get());
     SkASSERT(fClampAsIfUnpremul == false);
 }
 
 bool SkImageShader::isOpaque() const {
-    return fImage->isOpaque() && fTileModeX != kDecal_TileMode && fTileModeY != kDecal_TileMode;
+    return fImage->isOpaque() &&
+           fTileModeX != SkTileMode::kDecal && fTileModeY != SkTileMode::kDecal;
 }
 
 #ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
@@ -103,7 +105,7 @@
     if (fTileModeX != fTileModeY) {
         return nullptr;
     }
-    if (fTileModeX == kDecal_TileMode || fTileModeY == kDecal_TileMode) {
+    if (fTileModeX == SkTileMode::kDecal || fTileModeY == SkTileMode::kDecal) {
         return nullptr;
     }
 
@@ -128,52 +130,57 @@
         return nullptr;
     }
 
+    if (!rec.isLegacyCompatible(fImage->colorSpace())) {
+        return nullptr;
+    }
+
     return SkBitmapProcLegacyShader::MakeContext(*this, fTileModeX, fTileModeY,
                                                  SkBitmapProvider(fImage.get()), rec, alloc);
 }
 #endif
 
-SkImage* SkImageShader::onIsAImage(SkMatrix* texM, TileMode xy[]) const {
+SkImage* SkImageShader::onIsAImage(SkMatrix* texM, SkTileMode xy[]) const {
     if (texM) {
         *texM = this->getLocalMatrix();
     }
     if (xy) {
-        xy[0] = (TileMode)fTileModeX;
-        xy[1] = (TileMode)fTileModeY;
+        xy[0] = fTileModeX;
+        xy[1] = fTileModeY;
     }
     return const_cast<SkImage*>(fImage.get());
 }
 
 sk_sp<SkShader> SkImageShader::Make(sk_sp<SkImage> image,
-                                    TileMode tx, TileMode ty,
+                                    SkTileMode tmx, SkTileMode tmy,
                                     const SkMatrix* localMatrix,
                                     bool clampAsIfUnpremul) {
     if (!image) {
         return sk_make_sp<SkEmptyShader>();
     }
-    return sk_sp<SkShader>{ new SkImageShader(image, tx,ty, localMatrix, clampAsIfUnpremul) };
+    return sk_sp<SkShader>{ new SkImageShader(image, tmx, tmy, localMatrix, clampAsIfUnpremul) };
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 #if SK_SUPPORT_GPU
 
+#include "GrCaps.h"
 #include "GrColorSpaceInfo.h"
-#include "GrContext.h"
-#include "GrContextPriv.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "SkGr.h"
 #include "effects/GrBicubicEffect.h"
-#include "effects/GrSimpleTextureEffect.h"
+#include "effects/generated/GrSimpleTextureEffect.h"
 
-static GrSamplerState::WrapMode tile_mode_to_wrap_mode(const SkShader::TileMode tileMode) {
+static GrSamplerState::WrapMode tile_mode_to_wrap_mode(const SkTileMode tileMode) {
     switch (tileMode) {
-        case SkShader::TileMode::kClamp_TileMode:
+        case SkTileMode::kClamp:
             return GrSamplerState::WrapMode::kClamp;
-        case SkShader::TileMode::kRepeat_TileMode:
+        case SkTileMode::kRepeat:
             return GrSamplerState::WrapMode::kRepeat;
-        case SkShader::TileMode::kMirror_TileMode:
+        case SkTileMode::kMirror:
             return GrSamplerState::WrapMode::kMirrorRepeat;
-        case SkShader::kDecal_TileMode:
+        case SkTileMode::kDecal:
             return GrSamplerState::WrapMode::kClampToBorder;
     }
     SK_ABORT("Unknown tile mode.");
@@ -232,7 +239,8 @@
     if (doBicubic) {
         // domainX and domainY will properly apply the decal effect with the texture domain used in
         // the bicubic filter if clamp to border was unsupported in hardware
-        inner = GrBicubicEffect::Make(std::move(proxy), lmInverse, wrapModes, domainX, domainY);
+        inner = GrBicubicEffect::Make(std::move(proxy), lmInverse, wrapModes, domainX, domainY,
+                                      fImage->alphaType());
     } else {
         if (domainX != GrTextureDomain::kIgnore_Mode || domainY != GrTextureDomain::kIgnore_Mode) {
             SkRect domain = GrTextureDomain::MakeTexelDomain(
@@ -258,16 +266,31 @@
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 #include "SkImagePriv.h"
 
-sk_sp<SkShader> SkMakeBitmapShader(const SkBitmap& src, SkShader::TileMode tmx,
-                                   SkShader::TileMode tmy, const SkMatrix* localMatrix,
-                                   SkCopyPixelsMode cpm) {
+sk_sp<SkShader> SkMakeBitmapShader(const SkBitmap& src, SkTileMode tmx, SkTileMode tmy,
+                                   const SkMatrix* localMatrix, SkCopyPixelsMode cpm) {
     return SkImageShader::Make(SkMakeImageFromRasterBitmap(src, cpm),
                                tmx, tmy, localMatrix);
 }
 
+sk_sp<SkShader> SkMakeBitmapShaderForPaint(const SkPaint& paint, const SkBitmap& src,
+                                           SkTileMode tmx, SkTileMode tmy,
+                                           const SkMatrix* localMatrix, SkCopyPixelsMode mode) {
+    auto s = SkMakeBitmapShader(src, tmx, tmy, localMatrix, mode);
+    if (!s) {
+        return nullptr;
+    }
+    if (src.colorType() == kAlpha_8_SkColorType && paint.getShader()) {
+        // Compose the image shader with the paint's shader. Alpha images+shaders should output the
+        // texture's alpha multiplied by the shader's color. DstIn (d*sa) will achieve this with
+        // the source image and dst shader (MakeBlend takes dst first, src second).
+        s = SkShaders::Blend(SkBlendMode::kDstIn, paint.refShader(), std::move(s));
+    }
+    return s;
+}
+
 void SkShaderBase::RegisterFlattenables() { SK_REGISTER_FLATTENABLE(SkImageShader); }
 
-bool SkImageShader::onAppendStages(const StageRec& rec) const {
+bool SkImageShader::onAppendStages(const SkStageRec& rec) const {
     SkRasterPipeline* p = rec.fPipeline;
     SkArenaAlloc* alloc = rec.fAlloc;
 
@@ -325,8 +348,8 @@
     limit_y->invScale = 1.0f / pm.height();
 
     SkRasterPipeline_DecalTileCtx* decal_ctx = nullptr;
-    bool decal_x_and_y = fTileModeX == kDecal_TileMode && fTileModeY == kDecal_TileMode;
-    if (fTileModeX == kDecal_TileMode || fTileModeY == kDecal_TileMode) {
+    bool decal_x_and_y = fTileModeX == SkTileMode::kDecal && fTileModeY == SkTileMode::kDecal;
+    if (fTileModeX == SkTileMode::kDecal || fTileModeY == SkTileMode::kDecal) {
         decal_ctx = alloc->make<SkRasterPipeline_DecalTileCtx>();
         decal_ctx->limit_x = limit_x->scale;
         decal_ctx->limit_y = limit_y->scale;
@@ -337,16 +360,16 @@
             p->append(SkRasterPipeline::decal_x_and_y,  decal_ctx);
         } else {
             switch (fTileModeX) {
-                case kClamp_TileMode:  /* The gather_xxx stage will clamp for us. */     break;
-                case kMirror_TileMode: p->append(SkRasterPipeline::mirror_x, limit_x);   break;
-                case kRepeat_TileMode: p->append(SkRasterPipeline::repeat_x, limit_x);   break;
-                case kDecal_TileMode:  p->append(SkRasterPipeline::decal_x,  decal_ctx); break;
+                case SkTileMode::kClamp:  /* The gather_xxx stage will clamp for us. */     break;
+                case SkTileMode::kMirror: p->append(SkRasterPipeline::mirror_x, limit_x);   break;
+                case SkTileMode::kRepeat: p->append(SkRasterPipeline::repeat_x, limit_x);   break;
+                case SkTileMode::kDecal:  p->append(SkRasterPipeline::decal_x,  decal_ctx); break;
             }
             switch (fTileModeY) {
-                case kClamp_TileMode:  /* The gather_xxx stage will clamp for us. */     break;
-                case kMirror_TileMode: p->append(SkRasterPipeline::mirror_y, limit_y);   break;
-                case kRepeat_TileMode: p->append(SkRasterPipeline::repeat_y, limit_y);   break;
-                case kDecal_TileMode:  p->append(SkRasterPipeline::decal_y,  decal_ctx); break;
+                case SkTileMode::kClamp:  /* The gather_xxx stage will clamp for us. */     break;
+                case SkTileMode::kMirror: p->append(SkRasterPipeline::mirror_y, limit_y);   break;
+                case SkTileMode::kRepeat: p->append(SkRasterPipeline::repeat_y, limit_y);   break;
+                case SkTileMode::kDecal:  p->append(SkRasterPipeline::decal_y,  decal_ctx); break;
             }
         }
 
@@ -357,6 +380,7 @@
             case kARGB_4444_SkColorType:    p->append(SkRasterPipeline::gather_4444,    ctx); break;
             case kRGBA_8888_SkColorType:    p->append(SkRasterPipeline::gather_8888,    ctx); break;
             case kRGBA_1010102_SkColorType: p->append(SkRasterPipeline::gather_1010102, ctx); break;
+            case kRGBA_F16Norm_SkColorType:
             case kRGBA_F16_SkColorType:     p->append(SkRasterPipeline::gather_f16,     ctx); break;
             case kRGBA_F32_SkColorType:     p->append(SkRasterPipeline::gather_f32,     ctx); break;
 
@@ -372,7 +396,7 @@
             case kBGRA_8888_SkColorType:    p->append(SkRasterPipeline::gather_8888,    ctx);
                                             p->append(SkRasterPipeline::swap_rb            ); break;
 
-            default: SkASSERT(false);
+            case kUnknown_SkColorType: SkASSERT(false);
         }
         if (decal_ctx) {
             p->append(SkRasterPipeline::check_decal_mask, decal_ctx);
@@ -419,8 +443,7 @@
     if (true
         && (ct == kRGBA_8888_SkColorType || ct == kBGRA_8888_SkColorType)
         && quality == kLow_SkFilterQuality
-        && fTileModeX == SkShader::kClamp_TileMode
-        && fTileModeY == SkShader::kClamp_TileMode) {
+        && fTileModeX == SkTileMode::kClamp && fTileModeY == SkTileMode::kClamp) {
 
         p->append(SkRasterPipeline::bilerp_clamp_8888, gather);
         if (ct == kBGRA_8888_SkColorType) {
diff --git a/src/shaders/SkImageShader.h b/src/shaders/SkImageShader.h
index 0da2760..d851b1c 100644
--- a/src/shaders/SkImageShader.h
+++ b/src/shaders/SkImageShader.h
@@ -9,15 +9,14 @@
 #define SkImageShader_DEFINED
 
 #include "SkBitmapProcShader.h"
-#include "SkColorSpaceXformer.h"
 #include "SkImage.h"
 #include "SkShaderBase.h"
 
 class SkImageShader : public SkShaderBase {
 public:
     static sk_sp<SkShader> Make(sk_sp<SkImage>,
-                                SkShader::TileMode tx,
-                                SkShader::TileMode ty,
+                                SkTileMode tmx,
+                                SkTileMode tmy,
                                 const SkMatrix* localMatrix,
                                 bool clampAsIfUnpremul = false);
 
@@ -31,8 +30,8 @@
     SK_FLATTENABLE_HOOKS(SkImageShader)
 
     SkImageShader(sk_sp<SkImage>,
-                  SkShader::TileMode tx,
-                  SkShader::TileMode ty,
+                  SkTileMode tmx,
+                  SkTileMode tmy,
                   const SkMatrix* localMatrix,
                   bool clampAsIfUnpremul);
 
@@ -40,19 +39,14 @@
 #ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
     Context* onMakeContext(const ContextRec&, SkArenaAlloc* storage) const override;
 #endif
-    SkImage* onIsAImage(SkMatrix*, SkShader::TileMode*) const override;
+    SkImage* onIsAImage(SkMatrix*, SkTileMode*) const override;
 
-    bool onAppendStages(const StageRec&) const override;
+    bool onAppendStages(const SkStageRec&) const override;
 
-    sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override {
-        return xformer->apply(fImage.get())->makeShader(fTileModeX, fTileModeY,
-                                                        &this->getLocalMatrix());
-    }
-
-    sk_sp<SkImage>           fImage;
-    const SkShader::TileMode fTileModeX;
-    const SkShader::TileMode fTileModeY;
-    const bool               fClampAsIfUnpremul;
+    sk_sp<SkImage>   fImage;
+    const SkTileMode fTileModeX;
+    const SkTileMode fTileModeY;
+    const bool       fClampAsIfUnpremul;
 
     friend class SkShaderBase;
     typedef SkShaderBase INHERITED;
diff --git a/src/shaders/SkLightingShader.cpp b/src/shaders/SkLightingShader.cpp
index 8318980..e01f019 100644
--- a/src/shaders/SkLightingShader.cpp
+++ b/src/shaders/SkLightingShader.cpp
@@ -9,7 +9,6 @@
 #include "SkBitmapProcShader.h"
 #include "SkBitmapProcState.h"
 #include "SkColor.h"
-#include "SkColorSpaceXformer.h"
 #include "SkEmptyShader.h"
 #include "SkLightingShader.h"
 #include "SkMathPriv.h"
@@ -85,7 +84,6 @@
 #ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
     Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override;
 #endif
-    sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override;
 
 private:
     SK_FLATTENABLE_HOOKS(SkLightingShaderImpl)
@@ -150,22 +148,19 @@
                 fLightDirsUni = uniformHandler->addUniformArray(
                         kFragment_GrShaderFlag,
                         kFloat3_GrSLType,
-                        kDefault_GrSLPrecision,
                         "LightDir",
                         lightingFP.fDirectionalLights.count(),
                         &lightDirsUniName);
                 fLightColorsUni = uniformHandler->addUniformArray(
                         kFragment_GrShaderFlag,
                         kFloat3_GrSLType,
-                        kDefault_GrSLPrecision,
                         "LightColor",
                         lightingFP.fDirectionalLights.count(),
                         &lightColorsUniName);
             }
 
             const char* ambientColorUniName = nullptr;
-            fAmbientColorUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
-                                                          kFloat3_GrSLType, kDefault_GrSLPrecision,
+            fAmbientColorUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kFloat3_GrSLType,
                                                           "AmbientColor", &ambientColorUniName);
 
             fragBuilder->codeAppendf("half4 diffuseColor = %s;", args.fInputColor);
@@ -470,17 +465,12 @@
         return nullptr;
     }
 
+    // The diffuse shader can inspect the rec and make its decision about rec's colorspace.
+    // What about the lighting shader? Is lighting sensitive to the rec's (device) colorspace?
     return alloc->make<LightingShaderContext>(*this, rec, diffuseContext, normalProvider, nullptr);
 }
 #endif
 
-sk_sp<SkShader> SkLightingShaderImpl::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    sk_sp<SkShader> xformedDiffuseShader =
-            fDiffuseShader ? xformer->apply(fDiffuseShader.get()) : nullptr;
-    return SkLightingShader::Make(std::move(xformedDiffuseShader), fNormalSource,
-                                  fLights->makeColorSpace(xformer));
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 
 sk_sp<SkShader> SkLightingShader::Make(sk_sp<SkShader> diffuseShader,
diff --git a/src/shaders/SkLights.cpp b/src/shaders/SkLights.cpp
index e6c4796..144dcdb 100644
--- a/src/shaders/SkLights.cpp
+++ b/src/shaders/SkLights.cpp
@@ -6,7 +6,6 @@
  * found in the LICENSE file.
  */
 
-#include "SkColorSpaceXformer.h"
 #include "SkLights.h"
 #include "SkReadBuffer.h"
 
@@ -49,28 +48,6 @@
     return builder.finish();
 }
 
-static SkColor3f xform_color(const SkColor3f& color, SkColorSpaceXformer* xformer) {
-    SkColor origColor = SkColorSetARGB(0xFF,
-                                       SkScalarRoundToInt(color.fX),
-                                       SkScalarRoundToInt(color.fY),
-                                       SkScalarRoundToInt(color.fZ));
-    SkColor xformedColor = xformer->apply(origColor);
-    return SkColor3f::Make(SkIntToScalar(SkGetPackedR32(xformedColor)),
-                           SkIntToScalar(SkGetPackedG32(xformedColor)),
-                           SkIntToScalar(SkGetPackedB32(xformedColor)));
-}
-
-sk_sp<SkLights> SkLights::makeColorSpace(SkColorSpaceXformer* xformer) const {
-    SkLights::Builder builder;
-    for (int i = 0; i < this->numLights(); i++) {
-        Light light(fLights[i].type(), xform_color(fLights[i].color(), xformer),
-                    fLights[i].fDirOrPos, fLights[i].fIntensity);
-        builder.add(light);
-    }
-    builder.setAmbientLightColor(xform_color(fAmbientLightColor, xformer));
-    return builder.finish();
-}
-
 void SkLights::flatten(SkWriteBuffer& buf) const {
     buf.writeScalarArray(&this->ambientLightColor().fX, 3);
 
diff --git a/src/shaders/SkLights.h b/src/shaders/SkLights.h
index c4262a0..74360cf 100644
--- a/src/shaders/SkLights.h
+++ b/src/shaders/SkLights.h
@@ -13,7 +13,6 @@
 #include "SkRefCnt.h"
 #include "../private/SkTArray.h"
 
-class SkColorSpaceXformer;
 class SkReadBuffer;
 class SkWriteBuffer;
 
@@ -184,8 +183,6 @@
 
     SkLights() : fAmbientLightColor(SkColor3f::Make(0.0f, 0.0f, 0.0f)) {}
 
-    sk_sp<SkLights> makeColorSpace(SkColorSpaceXformer* xformer) const;
-
     SkTArray<Light> fLights;
     SkColor3f       fAmbientLightColor;
 
diff --git a/src/shaders/SkLocalMatrixShader.cpp b/src/shaders/SkLocalMatrixShader.cpp
index e0ca930..aae1f1f 100644
--- a/src/shaders/SkLocalMatrixShader.cpp
+++ b/src/shaders/SkLocalMatrixShader.cpp
@@ -51,7 +51,7 @@
 }
 #endif
 
-SkImage* SkLocalMatrixShader::onIsAImage(SkMatrix* outMatrix, enum TileMode* mode) const {
+SkImage* SkLocalMatrixShader::onIsAImage(SkMatrix* outMatrix, SkTileMode* mode) const {
     SkMatrix imageMatrix;
     SkImage* image = fProxyShader->isAImage(&imageMatrix, mode);
     if (image && outMatrix) {
@@ -62,13 +62,13 @@
     return image;
 }
 
-bool SkLocalMatrixShader::onAppendStages(const StageRec& rec) const {
+bool SkLocalMatrixShader::onAppendStages(const SkStageRec& rec) const {
     SkTCopyOnFirstWrite<SkMatrix> lm(this->getLocalMatrix());
     if (rec.fLocalM) {
         lm.writable()->preConcat(*rec.fLocalM);
     }
 
-    StageRec newRec = rec;
+    SkStageRec newRec = rec;
     newRec.fLocalM = lm;
     return as_SB(fProxyShader)->appendStages(newRec);
 }
diff --git a/src/shaders/SkLocalMatrixShader.h b/src/shaders/SkLocalMatrixShader.h
index 810220e..1d81f8d 100644
--- a/src/shaders/SkLocalMatrixShader.h
+++ b/src/shaders/SkLocalMatrixShader.h
@@ -14,7 +14,6 @@
 
 class GrFragmentProcessor;
 class SkArenaAlloc;
-class SkColorSpaceXformer;
 
 class SkLocalMatrixShader final : public SkShaderBase {
 public:
@@ -45,14 +44,9 @@
     Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override;
 #endif
 
-    SkImage* onIsAImage(SkMatrix* matrix, TileMode* mode) const override;
+    SkImage* onIsAImage(SkMatrix* matrix, SkTileMode* mode) const override;
 
-    bool onAppendStages(const StageRec&) const override;
-
-    sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override {
-        return as_SB(fProxyShader)->makeColorSpace(xformer)->makeWithLocalMatrix(
-            this->getLocalMatrix());
-    }
+    bool onAppendStages(const SkStageRec&) const override;
 
 private:
     SK_FLATTENABLE_HOOKS(SkLocalMatrixShader)
diff --git a/src/shaders/SkPerlinNoiseShader.cpp b/src/shaders/SkPerlinNoiseShader.cpp
index 5dd8090..096acec 100644
--- a/src/shaders/SkPerlinNoiseShader.cpp
+++ b/src/shaders/SkPerlinNoiseShader.cpp
@@ -17,11 +17,11 @@
 #include "SkWriteBuffer.h"
 
 #if SK_SUPPORT_GPU
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrCoordTransform.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "SkGr.h"
-#include "effects/GrConstColorProcessor.h"
+#include "effects/generated/GrConstColorProcessor.h"
 #include "glsl/GrGLSLFragmentProcessor.h"
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
 #include "glsl/GrGLSLProgramDataManager.h"
@@ -654,6 +654,7 @@
 #ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
 SkShaderBase::Context* SkPerlinNoiseShaderImpl::onMakeContext(const ContextRec& rec,
                                                               SkArenaAlloc* alloc) const {
+    // should we pay attention to rec's device-colorspace?
     return alloc->make<PerlinNoiseShaderContext>(*this, rec);
 }
 #endif
diff --git a/src/shaders/SkPictureShader.cpp b/src/shaders/SkPictureShader.cpp
index d14d5a4..ce10107 100644
--- a/src/shaders/SkPictureShader.cpp
+++ b/src/shaders/SkPictureShader.cpp
@@ -11,7 +11,6 @@
 #include "SkBitmap.h"
 #include "SkBitmapProcShader.h"
 #include "SkCanvas.h"
-#include "SkColorSpaceXformCanvas.h"
 #include "SkImage.h"
 #include "SkImageShader.h"
 #include "SkMatrixUtils.h"
@@ -23,12 +22,25 @@
 #if SK_SUPPORT_GPU
 #include "GrCaps.h"
 #include "GrColorSpaceInfo.h"
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrFragmentProcessor.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "SkGr.h"
 #endif
 
+sk_sp<SkShader> SkPicture::makeShader(SkTileMode tmx, SkTileMode tmy, const SkMatrix* localMatrix,
+                                      const SkRect* tile) const {
+    if (localMatrix && !localMatrix->invert(nullptr)) {
+        return nullptr;
+    }
+    return SkPictureShader::Make(sk_ref_sp(this), tmx, tmy, localMatrix, tile);
+}
+
+sk_sp<SkShader> SkPicture::makeShader(SkTileMode tmx, SkTileMode tmy,
+                                      const SkMatrix* localMatrix) const {
+    return this->makeShader(tmx, tmy, localMatrix, nullptr);
+}
+
 namespace {
 static unsigned gBitmapShaderKeyNamespaceLabel;
 
@@ -106,15 +118,13 @@
 
 } // namespace
 
-SkPictureShader::SkPictureShader(sk_sp<SkPicture> picture, TileMode tmx, TileMode tmy,
-                                 const SkMatrix* localMatrix, const SkRect* tile,
-                                 sk_sp<SkColorSpace> colorSpace)
+SkPictureShader::SkPictureShader(sk_sp<SkPicture> picture, SkTileMode tmx, SkTileMode tmy,
+                                 const SkMatrix* localMatrix, const SkRect* tile)
     : INHERITED(localMatrix)
     , fPicture(std::move(picture))
     , fTile(tile ? *tile : fPicture->cullRect())
     , fTmx(tmx)
     , fTmy(tmy)
-    , fColorSpace(std::move(colorSpace))
     , fUniqueID(next_id())
     , fAddedToCache(false) {}
 
@@ -124,20 +134,19 @@
     }
 }
 
-sk_sp<SkShader> SkPictureShader::Make(sk_sp<SkPicture> picture, TileMode tmx, TileMode tmy,
+sk_sp<SkShader> SkPictureShader::Make(sk_sp<SkPicture> picture, SkTileMode tmx, SkTileMode tmy,
                                       const SkMatrix* localMatrix, const SkRect* tile) {
     if (!picture || picture->cullRect().isEmpty() || (tile && tile->isEmpty())) {
-        return SkShader::MakeEmptyShader();
+        return SkShaders::Empty();
     }
-    return sk_sp<SkShader>(new SkPictureShader(std::move(picture), tmx, tmy, localMatrix, tile,
-                                               nullptr));
+    return sk_sp<SkShader>(new SkPictureShader(std::move(picture), tmx, tmy, localMatrix, tile));
 }
 
 sk_sp<SkFlattenable> SkPictureShader::CreateProc(SkReadBuffer& buffer) {
     SkMatrix lm;
     buffer.readMatrix(&lm);
-    TileMode mx = (TileMode)buffer.read32();
-    TileMode my = (TileMode)buffer.read32();
+    auto tmx = buffer.read32LE(SkTileMode::kLastTileMode);
+    auto tmy = buffer.read32LE(SkTileMode::kLastTileMode);
     SkRect tile;
     buffer.readRect(&tile);
 
@@ -147,13 +156,13 @@
     if (didSerialize) {
         picture = SkPicturePriv::MakeFromBuffer(buffer);
     }
-    return SkPictureShader::Make(picture, mx, my, &lm, &tile);
+    return SkPictureShader::Make(picture, tmx, tmy, &lm, &tile);
 }
 
 void SkPictureShader::flatten(SkWriteBuffer& buffer) const {
     buffer.writeMatrix(this->getLocalMatrix());
-    buffer.write32(fTmx);
-    buffer.write32(fTmy);
+    buffer.write32((unsigned)fTmx);
+    buffer.write32((unsigned)fTmy);
     buffer.writeRect(fTile);
 
     buffer.writeBool(true);
@@ -205,26 +214,17 @@
 
     const SkISize tileSize = scaledSize.toCeil();
     if (tileSize.isEmpty()) {
-        return SkShader::MakeEmptyShader();
+        return SkShaders::Empty();
     }
 
     // The actual scale, compensating for rounding & clamping.
     const SkSize tileScale = SkSize::Make(SkIntToScalar(tileSize.width()) / fTile.width(),
                                           SkIntToScalar(tileSize.height()) / fTile.height());
 
-    // |fColorSpace| will only be set when using an SkColorSpaceXformCanvas to do pre-draw xforms.
-    // A non-null |dstColorSpace| indicates that the surface we're drawing to is tagged. In all
-    // cases, picture-backed images behave the same (using a tagged surface for rasterization),
-    // and (as sources) they require a valid color space, so default to sRGB.
 
-    // With SkColorSpaceXformCanvas, the surface should never have a color space attached.
-    SkASSERT(!fColorSpace || !dstColorSpace);
-
-    sk_sp<SkColorSpace> imgCS = dstColorSpace ? sk_ref_sp(dstColorSpace)
-                                              : fColorSpace ? fColorSpace
-                                                            : SkColorSpace::MakeSRGB();
+    sk_sp<SkColorSpace> imgCS = dstColorSpace ? sk_ref_sp(dstColorSpace): SkColorSpace::MakeSRGB();
     SkImage::BitDepth bitDepth =
-            kRGBA_F16_SkColorType == dstColorType || kRGBA_F32_SkColorType == dstColorType
+            dstColorType >= kRGBA_F16Norm_SkColorType
             ? SkImage::BitDepth::kF16 : SkImage::BitDepth::kU8;
 
     BitmapShaderKey key(imgCS.get(), bitDepth, fUniqueID, tileScale);
@@ -254,7 +254,7 @@
     return tileShader;
 }
 
-bool SkPictureShader::onAppendStages(const StageRec& rec) const {
+bool SkPictureShader::onAppendStages(const SkStageRec& rec) const {
     auto lm = this->totalLocalMatrix(rec.fLocalM);
 
     // Keep bitmapShader alive by using alloc instead of stack memory
@@ -265,7 +265,7 @@
         return false;
     }
 
-    StageRec localRec = rec;
+    SkStageRec localRec = rec;
     localRec.fLocalM = lm->isIdentity() ? nullptr : lm.get();
 
     return as_SB(bitmapShader)->appendStages(localRec);
@@ -295,16 +295,6 @@
 }
 #endif
 
-sk_sp<SkShader> SkPictureShader::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    sk_sp<SkColorSpace> dstCS = xformer->dst();
-    if (SkColorSpace::Equals(dstCS.get(), fColorSpace.get())) {
-        return sk_ref_sp(const_cast<SkPictureShader*>(this));
-    }
-
-    return sk_sp<SkPictureShader>(new SkPictureShader(fPicture, fTmx, fTmy, &this->getLocalMatrix(),
-                                                      &fTile, std::move(dstCS)));
-}
-
 /////////////////////////////////////////////////////////////////////////////////////////
 
 SkPictureShader::PictureShaderContext::PictureShaderContext(
@@ -328,6 +318,9 @@
 }
 
 #if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "GrContextPriv.h"
+
 std::unique_ptr<GrFragmentProcessor> SkPictureShader::asFragmentProcessor(
         const GrFPArgs& args) const {
     int maxTextureSize = 0;
diff --git a/src/shaders/SkPictureShader.h b/src/shaders/SkPictureShader.h
index e941c1f..aeeb74e 100644
--- a/src/shaders/SkPictureShader.h
+++ b/src/shaders/SkPictureShader.h
@@ -9,6 +9,7 @@
 #define SkPictureShader_DEFINED
 
 #include "SkShaderBase.h"
+#include "SkTileMode.h"
 #include <atomic>
 
 class SkArenaAlloc;
@@ -25,7 +26,7 @@
 public:
     ~SkPictureShader() override;
 
-    static sk_sp<SkShader> Make(sk_sp<SkPicture>, TileMode, TileMode, const SkMatrix*,
+    static sk_sp<SkShader> Make(sk_sp<SkPicture>, SkTileMode, SkTileMode, const SkMatrix*,
                                 const SkRect*);
 
 #if SK_SUPPORT_GPU
@@ -35,17 +36,15 @@
 protected:
     SkPictureShader(SkReadBuffer&);
     void flatten(SkWriteBuffer&) const override;
-    bool onAppendStages(const StageRec&) const override;
+    bool onAppendStages(const SkStageRec&) const override;
 #ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
     Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override;
 #endif
-    sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override;
 
 private:
     SK_FLATTENABLE_HOOKS(SkPictureShader)
 
-    SkPictureShader(sk_sp<SkPicture>, TileMode, TileMode, const SkMatrix*, const SkRect*,
-                    sk_sp<SkColorSpace>);
+    SkPictureShader(sk_sp<SkPicture>, SkTileMode, SkTileMode, const SkMatrix*, const SkRect*);
 
     sk_sp<SkShader> refBitmapShader(const SkMatrix&, SkTCopyOnFirstWrite<SkMatrix>* localMatrix,
                                     SkColorType dstColorType, SkColorSpace* dstColorSpace,
@@ -69,11 +68,7 @@
 
     sk_sp<SkPicture>    fPicture;
     SkRect              fTile;
-    TileMode            fTmx, fTmy;
-
-    // Should never be set by a public constructor.  This is only used when onMakeColorSpace()
-    // forces a deferred color space xform.
-    sk_sp<SkColorSpace>    fColorSpace;
+    SkTileMode          fTmx, fTmy;
 
     const uint32_t            fUniqueID;
     mutable std::atomic<bool> fAddedToCache;
diff --git a/src/shaders/SkShader.cpp b/src/shaders/SkShader.cpp
index b9d8824..2356db8 100644
--- a/src/shaders/SkShader.cpp
+++ b/src/shaders/SkShader.cpp
@@ -8,7 +8,8 @@
 #include "SkArenaAlloc.h"
 #include "SkBitmapProcShader.h"
 #include "SkColorShader.h"
-#include "SkColorSpaceXformer.h"
+#include "SkColorSpacePriv.h"
+#include "SkColorSpaceXformSteps.h"
 #include "SkEmptyShader.h"
 #include "SkMallocPixelRef.h"
 #include "SkPaint.h"
@@ -25,6 +26,15 @@
 #include "GrFragmentProcessor.h"
 #endif
 
+#ifdef SK_SUPPORT_LEGACY_SHADER_LOCALMATRIX
+SkMatrix SkShader::getLocalMatrix() const {
+    return as_SB(this)->getLocalMatrix();
+}
+sk_sp<SkShader> SkShader::makeAsALocalMatrixShader(SkMatrix* localMatrix) const {
+    return as_SB(this)->makeAsALocalMatrixShader(localMatrix);
+}
+#endif
+
 SkShaderBase::SkShaderBase(const SkMatrix* localMatrix)
     : fLocalMatrix(localMatrix ? *localMatrix : SkMatrix::I()) {
     // Pre-cache so future calls to fLocalMatrix.getType() are threadsafe.
@@ -109,11 +119,11 @@
 
 SkShaderBase::Context::~Context() {}
 
-const SkMatrix& SkShader::getLocalMatrix() const {
-    return as_SB(this)->getLocalMatrix();
+bool SkShaderBase::ContextRec::isLegacyCompatible(SkColorSpace* shaderColorSpace) const {
+    return !SkColorSpaceXformSteps::Required(shaderColorSpace, fDstColorSpace);
 }
 
-SkImage* SkShader::isAImage(SkMatrix* localMatrix, TileMode xy[2]) const {
+SkImage* SkShader::isAImage(SkMatrix* localMatrix, SkTileMode xy[2]) const {
     return as_SB(this)->onIsAImage(localMatrix, xy);
 }
 
@@ -127,35 +137,53 @@
 }
 #endif
 
-sk_sp<SkShader> SkShader::makeAsALocalMatrixShader(SkMatrix*) const {
+sk_sp<SkShader> SkShaderBase::makeAsALocalMatrixShader(SkMatrix*) const {
     return nullptr;
 }
 
-sk_sp<SkShader> SkShader::MakeEmptyShader() { return sk_make_sp<SkEmptyShader>(); }
+#ifdef SK_SUPPORT_LEGACY_SHADER_FACTORIES
+sk_sp<SkShader> SkShader::MakeEmptyShader() { return SkShaders::Empty(); }
+sk_sp<SkShader> SkShader::MakeColorShader(SkColor color) { return SkShaders::Color(color); }
+#endif
 
-sk_sp<SkShader> SkShader::MakeColorShader(SkColor color) { return sk_make_sp<SkColorShader>(color); }
+sk_sp<SkShader> SkShaders::Empty() { return sk_make_sp<SkEmptyShader>(); }
+sk_sp<SkShader> SkShaders::Color(SkColor color) { return sk_make_sp<SkColorShader>(color); }
 
+#ifdef SK_SUPPORT_LEGACY_BITMAPSHADER_FACTORY
+sk_sp<SkShader> SkShader::MakeBitmapShader(const SkBitmap& src, SkTileMode tmx, SkTileMode tmy,
+                                           const SkMatrix* localMatrix) {
+    return src.makeShader(tmx, tmy, localMatrix);
+}
+#endif
+
+sk_sp<SkShader> SkBitmap::makeShader(SkTileMode tmx, SkTileMode tmy, const SkMatrix* lm) const {
+    if (lm && !lm->invert(nullptr)) {
+        return nullptr;
+    }
+    return SkMakeBitmapShader(*this, tmx, tmy, lm, kIfMutable_SkCopyPixelsMode);
+}
+
+sk_sp<SkShader> SkBitmap::makeShader(const SkMatrix* lm) const {
+    return this->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, lm);
+}
+
+#ifdef SK_SUPPORT_LEGACY_TILEMODE_ENUM
 sk_sp<SkShader> SkShader::MakeBitmapShader(const SkBitmap& src, TileMode tmx, TileMode tmy,
                                            const SkMatrix* localMatrix) {
-    if (localMatrix && !localMatrix->invert(nullptr)) {
-        return nullptr;
-    }
-    return SkMakeBitmapShader(src, tmx, tmy, localMatrix, kIfMutable_SkCopyPixelsMode);
+    return src.makeShader((SkTileMode)tmx, (SkTileMode)tmy, localMatrix);
 }
-
 sk_sp<SkShader> SkShader::MakePictureShader(sk_sp<SkPicture> src, TileMode tmx, TileMode tmy,
                                             const SkMatrix* localMatrix, const SkRect* tile) {
-    if (localMatrix && !localMatrix->invert(nullptr)) {
-        return nullptr;
-    }
-    return SkPictureShader::Make(std::move(src), tmx, tmy, localMatrix, tile);
+    return src ? src->makeShader((SkTileMode)tmx, (SkTileMode)tmy, localMatrix, tile)
+               : MakeEmptyShader();
 }
+#endif
 
-bool SkShaderBase::appendStages(const StageRec& rec) const {
+bool SkShaderBase::appendStages(const SkStageRec& rec) const {
     return this->onAppendStages(rec);
 }
 
-bool SkShaderBase::onAppendStages(const StageRec& rec) const {
+bool SkShaderBase::onAppendStages(const SkStageRec& rec) const {
     // SkShader::Context::shadeSpan() handles the paint opacity internally,
     // but SkRasterPipelineBlitter applies it as a separate stage.
     // We skip the internal shadeSpan() step by forcing the paint opaque.
@@ -164,16 +192,15 @@
         opaquePaint.writable()->setAlpha(SK_AlphaOPAQUE);
     }
 
-    ContextRec cr(*opaquePaint, rec.fCTM, rec.fLocalM, rec.fDstColorType, rec.fDstCS);
+    ContextRec cr(*opaquePaint, rec.fCTM, rec.fLocalM, rec.fDstColorType, sk_srgb_singleton());
 
     struct CallbackCtx : SkRasterPipeline_CallbackCtx {
-        sk_sp<SkShader> shader;
-        Context*        ctx;
+        sk_sp<const SkShader> shader;
+        Context*              ctx;
     };
     auto cb = rec.fAlloc->make<CallbackCtx>();
-    cb->shader = rec.fDstCS ? SkColorSpaceXformer::Make(sk_ref_sp(rec.fDstCS))->apply(this)
-                            : sk_ref_sp((SkShader*)this);
-    cb->ctx = as_SB(cb->shader)->makeContext(cr, rec.fAlloc);
+    cb->shader = sk_ref_sp(this);
+    cb->ctx = as_SB(this)->makeContext(cr, rec.fAlloc);
     cb->fn  = [](SkRasterPipeline_CallbackCtx* self, int active_pixels) {
         auto c = (CallbackCtx*)self;
         int x = (int)c->rgba[0],
@@ -190,6 +217,9 @@
     if (cb->ctx) {
         rec.fPipeline->append(SkRasterPipeline::seed_shader);
         rec.fPipeline->append(SkRasterPipeline::callback, cb);
+        rec.fAlloc->make<SkColorSpaceXformSteps>(sk_srgb_singleton(), kPremul_SkAlphaType,
+                                                 rec.fDstCS,          kPremul_SkAlphaType)
+            ->apply(rec.fPipeline, true);
         return true;
     }
     return false;
@@ -198,5 +228,5 @@
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 sk_sp<SkFlattenable> SkEmptyShader::CreateProc(SkReadBuffer&) {
-    return SkShader::MakeEmptyShader();
+    return SkShaders::Empty();
 }
diff --git a/src/shaders/SkShaderBase.h b/src/shaders/SkShaderBase.h
index 36e152f..64c730e 100644
--- a/src/shaders/SkShaderBase.h
+++ b/src/shaders/SkShaderBase.h
@@ -8,6 +8,7 @@
 #ifndef SkShaderBase_DEFINED
 #define SkShaderBase_DEFINED
 
+#include "SkEffectPriv.h"
 #include "SkFilterQuality.h"
 #include "SkMask.h"
 #include "SkMatrix.h"
@@ -20,11 +21,9 @@
 #endif
 
 class GrContext;
-class GrColorSpaceInfo;
 class GrFragmentProcessor;
 class SkArenaAlloc;
 class SkColorSpace;
-class SkColorSpaceXformer;
 class SkImage;
 struct SkImageInfo;
 class SkPaint;
@@ -75,6 +74,8 @@
         const SkMatrix* fLocalMatrix;      // optional local matrix
         SkColorType     fDstColorType;     // the color type of the dest surface
         SkColorSpace*   fDstColorSpace;    // the color space of the dest surface (if any)
+
+        bool isLegacyCompatible(SkColorSpace* shadersColorSpace) const;
     };
 
     class Context : public ::SkNoncopyable {
@@ -149,25 +150,8 @@
      */
     bool asLuminanceColor(SkColor*) const;
 
-    /**
-     *  Returns a shader transformed into a new color space via the |xformer|.
-     */
-    sk_sp<SkShader> makeColorSpace(SkColorSpaceXformer* xformer) const {
-        return this->onMakeColorSpace(xformer);
-    }
-
-    struct StageRec {
-        SkRasterPipeline*   fPipeline;
-        SkArenaAlloc*       fAlloc;
-        SkColorType         fDstColorType;
-        SkColorSpace*       fDstCS;         // may be nullptr
-        const SkPaint&      fPaint;
-        const SkMatrix*     fLocalM;        // may be nullptr
-        SkMatrix            fCTM;
-    };
-
     // If this returns false, then we draw nothing (do not fall back to shader context)
-    bool appendStages(const StageRec&) const;
+    bool appendStages(const SkStageRec&) const;
 
     bool SK_WARN_UNUSED_RESULT computeTotalInverse(const SkMatrix& ctm,
                                                    const SkMatrix* outerLocalMatrix,
@@ -180,7 +164,7 @@
     SkTCopyOnFirstWrite<SkMatrix> totalLocalMatrix(const SkMatrix* preLocalMatrix,
                                                    const SkMatrix* postLocalMatrix = nullptr) const;
 
-    virtual SkImage* onIsAImage(SkMatrix*, TileMode[2]) const {
+    virtual SkImage* onIsAImage(SkMatrix*, SkTileMode[2]) const {
         return nullptr;
     }
 
@@ -194,6 +178,12 @@
     }
     static void RegisterFlattenables();
 
+    /** DEPRECATED. skbug.com/8941
+     *  If this shader can be represented by another shader + a localMatrix, return that shader and
+     *  the localMatrix. If not, return nullptr and ignore the localMatrix parameter.
+     */
+    virtual sk_sp<SkShader> makeAsALocalMatrixShader(SkMatrix* localMatrix) const;
+
 protected:
     SkShaderBase(const SkMatrix* localMatrix = nullptr);
 
@@ -220,12 +210,8 @@
         return false;
     }
 
-    virtual sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer*) const {
-        return sk_ref_sp(const_cast<SkShaderBase*>(this));
-    }
-
     // Default impl creates shadercontext and calls that (not very efficient)
-    virtual bool onAppendStages(const StageRec&) const;
+    virtual bool onAppendStages(const SkStageRec&) const;
 
 private:
     // This is essentially const, but not officially so it can be modified in constructors.
diff --git a/src/shaders/gradients/Sk4fGradientBase.cpp b/src/shaders/gradients/Sk4fGradientBase.cpp
index 18c5d26..a554691 100644
--- a/src/shaders/gradients/Sk4fGradientBase.cpp
+++ b/src/shaders/gradients/Sk4fGradientBase.cpp
@@ -138,7 +138,7 @@
 }
 
 void Sk4fGradientIntervalBuffer::init(const SkGradientShaderBase& shader, SkColorSpace* dstCS,
-                                      SkShader::TileMode tileMode, bool premulColors,
+                                      SkTileMode tileMode, bool premulColors,
                                       SkScalar alpha, bool reverse) {
     // The main job here is to build a specialized interval list: a different
     // representation of the color stops data, optimized for efficient scan line
@@ -198,14 +198,14 @@
     // Transform all of the colors to destination color space
     SkColor4fXformer xformedColors(shader.fOrigColors4f, count, shader.fColorSpace.get(), dstCS);
 
-    if (tileMode == SkShader::kClamp_TileMode) {
+    if (tileMode == SkTileMode::kClamp) {
         // synthetic edge interval: -/+inf .. P0
         const Sk4f clamp_color = pack_color(xformedColors.fColors[first_index],
                                             premulColors, componentScale);
         const SkScalar clamp_pos = reverse ? SK_ScalarInfinity : SK_ScalarNegativeInfinity;
         fIntervals.emplace_back(clamp_color, clamp_pos,
                                 clamp_color, first_pos);
-    } else if (tileMode == SkShader::kMirror_TileMode && reverse) {
+    } else if (tileMode == SkTileMode::kMirror && reverse) {
         // synthetic mirror intervals injected before main intervals: (2 .. 1]
         addMirrorIntervals(shader, xformedColors.fColors, componentScale, premulColors, false,
                            &fIntervals);
@@ -220,14 +220,14 @@
                                 pack_color(c1, premulColors, componentScale), t1);
     });
 
-    if (tileMode == SkShader::kClamp_TileMode) {
+    if (tileMode == SkTileMode::kClamp) {
         // synthetic edge interval: Pn .. +/-inf
         const Sk4f clamp_color = pack_color(xformedColors.fColors[last_index],
                                             premulColors, componentScale);
         const SkScalar clamp_pos = reverse ? SK_ScalarNegativeInfinity : SK_ScalarInfinity;
         fIntervals.emplace_back(clamp_color, last_pos,
                                 clamp_color, clamp_pos);
-    } else if (tileMode == SkShader::kMirror_TileMode && !reverse) {
+    } else if (tileMode == SkTileMode::kMirror && !reverse) {
         // synthetic mirror intervals injected after main intervals: [1 .. 2)
         addMirrorIntervals(shader, xformedColors.fColors, componentScale, premulColors, true,
                            &fIntervals);
diff --git a/src/shaders/gradients/Sk4fGradientBase.h b/src/shaders/gradients/Sk4fGradientBase.h
index 8c28d60..b5711e4 100644
--- a/src/shaders/gradients/Sk4fGradientBase.h
+++ b/src/shaders/gradients/Sk4fGradientBase.h
@@ -36,7 +36,7 @@
 
 class Sk4fGradientIntervalBuffer {
 public:
-    void init(const SkGradientShaderBase&, SkColorSpace* dstCS, SkShader::TileMode tileMode,
+    void init(const SkGradientShaderBase&, SkColorSpace* dstCS, SkTileMode tileMode,
               bool premulColors, SkScalar alpha, bool reverse);
 
     const Sk4fGradientInterval* find(SkScalar t) const;
diff --git a/src/shaders/gradients/Sk4fLinearGradient.cpp b/src/shaders/gradients/Sk4fLinearGradient.cpp
index 29dfab6..aa2a56b 100644
--- a/src/shaders/gradients/Sk4fLinearGradient.cpp
+++ b/src/shaders/gradients/Sk4fLinearGradient.cpp
@@ -46,16 +46,16 @@
     }
 }
 
-template<SkShader::TileMode>
+template<SkTileMode>
 SkScalar pinFx(SkScalar);
 
 template<>
-SkScalar pinFx<SkShader::kClamp_TileMode>(SkScalar fx) {
+SkScalar pinFx<SkTileMode::kClamp>(SkScalar fx) {
     return fx;
 }
 
 template<>
-SkScalar pinFx<SkShader::kRepeat_TileMode>(SkScalar fx) {
+SkScalar pinFx<SkTileMode::kRepeat>(SkScalar fx) {
     SkScalar f = SkScalarIsFinite(fx) ? SkScalarFraction(fx) : 0;
     if (f < 0) {
         f = SkTMin(f + 1, nextafterf(1, 0));
@@ -66,7 +66,7 @@
 }
 
 template<>
-SkScalar pinFx<SkShader::kMirror_TileMode>(SkScalar fx) {
+SkScalar pinFx<SkTileMode::kMirror>(SkScalar fx) {
     SkScalar f = SkScalarIsFinite(fx) ? SkScalarMod(fx, 2.0f) : 0;
     if (f < 0) {
         f = SkTMin(f + 2, nextafterf(2, 0));
@@ -189,22 +189,22 @@
                                          float bias0, float bias1) const {
     const SkLinearGradient& shader = static_cast<const SkLinearGradient&>(fShader);
     switch (shader.fTileMode) {
-    case kDecal_TileMode:
+        case SkTileMode::kDecal:
         SkASSERT(false);    // decal only supported via stages
         // fall-through
-    case kClamp_TileMode:
-        this->shadeSpanInternal<premul, kClamp_TileMode >(x, y, dst, count, bias0, bias1);
+        case SkTileMode::kClamp:
+            this->shadeSpanInternal<premul, SkTileMode::kClamp >(x, y, dst, count, bias0, bias1);
         break;
-    case kRepeat_TileMode:
-        this->shadeSpanInternal<premul, kRepeat_TileMode>(x, y, dst, count, bias0, bias1);
+        case SkTileMode::kRepeat:
+            this->shadeSpanInternal<premul, SkTileMode::kRepeat>(x, y, dst, count, bias0, bias1);
         break;
-    case kMirror_TileMode:
-        this->shadeSpanInternal<premul, kMirror_TileMode>(x, y, dst, count, bias0, bias1);
+        case SkTileMode::kMirror:
+            this->shadeSpanInternal<premul, SkTileMode::kMirror>(x, y, dst, count, bias0, bias1);
         break;
     }
 }
 
-template<ApplyPremul premul, SkShader::TileMode tileMode>
+template<ApplyPremul premul, SkTileMode tileMode>
 void SkLinearGradient::
 LinearGradient4fContext::shadeSpanInternal(int x, int y, SkPMColor dst[], int count,
                                            float bias0, float bias1) const {
@@ -254,7 +254,7 @@
     }
 }
 
-template<ApplyPremul premul, SkShader::TileMode tileMode>
+template<ApplyPremul premul, SkTileMode tileMode>
 class SkLinearGradient::
 LinearGradient4fContext::LinearIntervalProcessor {
 public:
@@ -274,7 +274,7 @@
         SkASSERT(fAdvX >= 0);
         SkASSERT(firstInterval <= lastInterval);
 
-        if (tileMode != kClamp_TileMode && !is_vertical) {
+        if (tileMode != SkTileMode::kClamp && !is_vertical) {
             const auto spanX = (lastInterval->fT1 - firstInterval->fT0) / dx;
             SkASSERT(spanX >= 0);
 
@@ -350,7 +350,7 @@
         SkASSERT(i <= fLastInterval);
         i++;
 
-        if (tileMode == kClamp_TileMode) {
+        if (tileMode == SkTileMode::kClamp) {
             SkASSERT(i <= fLastInterval);
             return i;
         }
diff --git a/src/shaders/gradients/Sk4fLinearGradient.h b/src/shaders/gradients/Sk4fLinearGradient.h
index 885b589..2ba36ca 100644
--- a/src/shaders/gradients/Sk4fLinearGradient.h
+++ b/src/shaders/gradients/Sk4fLinearGradient.h
@@ -21,14 +21,14 @@
 private:
     using INHERITED = GradientShaderBase4fContext;
 
-    template<ApplyPremul, TileMode>
+    template<ApplyPremul, SkTileMode>
     class LinearIntervalProcessor;
 
     template <ApplyPremul premul>
     void shadePremulSpan(int x, int y, SkPMColor dst[], int count,
                          float bias0, float bias1) const;
 
-    template <ApplyPremul premul, SkShader::TileMode tileMode>
+    template <ApplyPremul premul, SkTileMode tileMode>
     void shadeSpanInternal(int x, int y, SkPMColor dst[], int count,
                            float bias0, float bias1) const;
 
diff --git a/src/shaders/gradients/SkGradientShader.cpp b/src/shaders/gradients/SkGradientShader.cpp
index c1d5f0c..e9c32c8 100644
--- a/src/shaders/gradients/SkGradientShader.cpp
+++ b/src/shaders/gradients/SkGradientShader.cpp
@@ -8,7 +8,6 @@
 #include <algorithm>
 #include "Sk4fLinearGradient.h"
 #include "SkColorSpacePriv.h"
-#include "SkColorSpaceXformer.h"
 #include "SkConvertPixels.h"
 #include "SkFloatBits.h"
 #include "SkGradientShaderPriv.h"
@@ -51,7 +50,7 @@
         flags |= kHasColorSpace_GSF;
     }
     SkASSERT(static_cast<uint32_t>(fTileMode) <= kTileModeMask_GSF);
-    flags |= (fTileMode << kTileModeShift_GSF);
+    flags |= ((unsigned)fTileMode << kTileModeShift_GSF);
     SkASSERT(fGradFlags <= kGradFlagsMask_GSF);
     flags |= (fGradFlags << kGradFlagsShift_GSF);
 
@@ -83,7 +82,7 @@
     // New gradient format. Includes floating point color, color space, densely packed flags
     uint32_t flags = buffer.readUInt();
 
-    fTileMode = (SkShader::TileMode)((flags >> kTileModeShift_GSF) & kTileModeMask_GSF);
+    fTileMode = (SkTileMode)((flags >> kTileModeShift_GSF) & kTileModeMask_GSF);
     fGradFlags = (flags >> kGradFlagsShift_GSF) & kGradFlagsMask_GSF;
 
     fCount = buffer.getArrayCount();
@@ -131,7 +130,7 @@
 
     fGradFlags = static_cast<uint8_t>(desc.fGradFlags);
 
-    SkASSERT((unsigned)desc.fTileMode < SkShader::kTileModeCount);
+    SkASSERT((unsigned)desc.fTileMode < kSkTileModeCount);
     fTileMode = desc.fTileMode;
 
     /*  Note: we let the caller skip the first and/or last position.
@@ -271,7 +270,7 @@
     add_stop_color(ctx, stop, Fs, Bs);
 }
 
-bool SkGradientShaderBase::onAppendStages(const StageRec& rec) const {
+bool SkGradientShaderBase::onAppendStages(const SkStageRec& rec) const {
     SkRasterPipeline* p = rec.fPipeline;
     SkArenaAlloc* alloc = rec.fAlloc;
     SkRasterPipeline_DecalTileCtx* decal_ctx = nullptr;
@@ -289,15 +288,15 @@
     this->appendGradientStages(alloc, p, &postPipeline);
 
     switch(fTileMode) {
-        case kMirror_TileMode: p->append(SkRasterPipeline::mirror_x_1); break;
-        case kRepeat_TileMode: p->append(SkRasterPipeline::repeat_x_1); break;
-        case kDecal_TileMode:
+        case SkTileMode::kMirror: p->append(SkRasterPipeline::mirror_x_1); break;
+        case SkTileMode::kRepeat: p->append(SkRasterPipeline::repeat_x_1); break;
+        case SkTileMode::kDecal:
             decal_ctx = alloc->make<SkRasterPipeline_DecalTileCtx>();
             decal_ctx->limit_x = SkBits2Float(SkFloat2Bits(1.0f) + 1);
             // reuse mask + limit_x stage, or create a custom decal_1 that just stores the mask
             p->append(SkRasterPipeline::decal_x, decal_ctx);
             // fall-through to clamp
-        case kClamp_TileMode:
+        case SkTileMode::kClamp:
             if (!fOrigPos) {
                 // We clamp only when the stops are evenly spaced.
                 // If not, there may be hard stops, and clamping ruins hard stops at 0 and/or 1.
@@ -417,7 +416,7 @@
 
 
 bool SkGradientShaderBase::isOpaque() const {
-    return fColorsAreOpaque && (this->getTileMode() != SkShader::kDecal_TileMode);
+    return fColorsAreOpaque && (this->getTileMode() != SkTileMode::kDecal);
 }
 
 static unsigned rounded_divide(unsigned numer, unsigned denom) {
@@ -443,19 +442,6 @@
     return true;
 }
 
-SkGradientShaderBase::AutoXformColors::AutoXformColors(const SkGradientShaderBase& grad,
-                                                       SkColorSpaceXformer* xformer)
-    : fColors(grad.fColorCount) {
-    // TODO: stay in 4f to preserve precision?
-
-    SkAutoSTMalloc<8, SkColor> origColors(grad.fColorCount);
-    for (int i = 0; i < grad.fColorCount; ++i) {
-        origColors[i] = grad.getLegacyColor(i);
-    }
-
-    xformer->apply(fColors.get(), origColors.get(), grad.fColorCount);
-}
-
 SkColor4fXformer::SkColor4fXformer(const SkColor4f* colors, int colorCount,
                                    SkColorSpace* src, SkColorSpace* dst) {
     fColors = colors;
@@ -487,7 +473,11 @@
             }
         }
         info->fColorCount = fColorCount;
+#ifdef SK_SUPPORT_LEGACY_TILEMODE_ENUM
+        info->fTileMode = (SkShader::TileMode)fTileMode;
+#else
         info->fTileMode = fTileMode;
+#endif
         info->fGradientFlags = fGradFlags;
     }
 }
@@ -498,14 +488,14 @@
 // Return true if these parameters are valid/legal/safe to construct a gradient
 //
 static bool valid_grad(const SkColor4f colors[], const SkScalar pos[], int count,
-                       unsigned tileMode) {
-    return nullptr != colors && count >= 1 && tileMode < (unsigned)SkShader::kTileModeCount;
+                       SkTileMode tileMode) {
+    return nullptr != colors && count >= 1 && (unsigned)tileMode < kSkTileModeCount;
 }
 
 static void desc_init(SkGradientShaderBase::Descriptor* desc,
                       const SkColor4f colors[], sk_sp<SkColorSpace> colorSpace,
                       const SkScalar pos[], int colorCount,
-                      SkShader::TileMode mode, uint32_t flags, const SkMatrix* localMatrix) {
+                      SkTileMode mode, uint32_t flags, const SkMatrix* localMatrix) {
     SkASSERT(colorCount > 1);
 
     desc->fColors       = colors;
@@ -566,27 +556,26 @@
 // clamped conditions separately, this will always return the last color for clamped gradients.
 static sk_sp<SkShader> make_degenerate_gradient(const SkColor4f colors[], const SkScalar pos[],
                                                 int colorCount, sk_sp<SkColorSpace> colorSpace,
-                                                SkShader::TileMode mode) {
+                                                SkTileMode mode) {
     switch(mode) {
-        case SkShader::kDecal_TileMode:
+        case SkTileMode::kDecal:
             // normally this would reject the area outside of the interpolation region, so since
             // inside region is empty when the radii are equal, the entire draw region is empty
-            return SkShader::MakeEmptyShader();
-        case SkShader::kRepeat_TileMode:
-        case SkShader::kMirror_TileMode:
+            return SkShaders::Empty();
+        case SkTileMode::kRepeat:
+        case SkTileMode::kMirror:
             // repeat and mirror are treated the same: the border colors are never visible,
             // but approximate the final color as infinite repetitions of the colors, so
             // it can be represented as the average color of the gradient.
-            return SkShader::MakeColorShader(
+            return SkShaders::Color(
                     average_gradient_color(colors, pos, colorCount), std::move(colorSpace));
-        case SkShader::kClamp_TileMode:
+        case SkTileMode::kClamp:
             // Depending on how the gradient shape degenerates, there may be a more specialized
             // fallback representation for the factories to use, but this is a reasonable default.
-            return SkShader::MakeColorShader(colors[colorCount - 1], std::move(colorSpace));
-        default:
-            SkDEBUGFAIL("Should not be reached");
-            return nullptr;
+            return SkShaders::Color(colors[colorCount - 1], std::move(colorSpace));
     }
+    SkDEBUGFAIL("Should not be reached");
+    return nullptr;
 }
 
 // assumes colors is SkColor4f* and pos is SkScalar*
@@ -602,8 +591,7 @@
      } while (0)
 
 struct ColorStopOptimizer {
-    ColorStopOptimizer(const SkColor4f* colors, const SkScalar* pos,
-                       int count, SkShader::TileMode mode)
+    ColorStopOptimizer(const SkColor4f* colors, const SkScalar* pos, int count, SkTileMode mode)
         : fColors(colors)
         , fPos(pos)
         , fCount(count) {
@@ -616,8 +604,7 @@
                 SkScalarNearlyEqual(pos[1], 0.0f) &&
                 SkScalarNearlyEqual(pos[2], 1.0f)) {
 
-                if (SkShader::kRepeat_TileMode == mode ||
-                    SkShader::kMirror_TileMode == mode ||
+                if (SkTileMode::kRepeat == mode || SkTileMode::kMirror == mode ||
                     colors[0] == colors[1]) {
 
                     // Ignore the leftmost color/pos.
@@ -629,8 +616,7 @@
                        SkScalarNearlyEqual(pos[1], 1.0f) &&
                        SkScalarNearlyEqual(pos[2], 1.0f)) {
 
-                if (SkShader::kRepeat_TileMode == mode ||
-                    SkShader::kMirror_TileMode == mode ||
+                if (SkTileMode::kRepeat == mode || SkTileMode::kMirror == mode ||
                     colors[1] == colors[2]) {
 
                     // Ignore the rightmost color/pos.
@@ -662,7 +648,7 @@
 sk_sp<SkShader> SkGradientShader::MakeLinear(const SkPoint pts[2],
                                              const SkColor colors[],
                                              const SkScalar pos[], int colorCount,
-                                             SkShader::TileMode mode,
+                                             SkTileMode mode,
                                              uint32_t flags,
                                              const SkMatrix* localMatrix) {
     ColorConverter converter(colors, colorCount);
@@ -674,7 +660,7 @@
                                              const SkColor4f colors[],
                                              sk_sp<SkColorSpace> colorSpace,
                                              const SkScalar pos[], int colorCount,
-                                             SkShader::TileMode mode,
+                                             SkTileMode mode,
                                              uint32_t flags,
                                              const SkMatrix* localMatrix) {
     if (!pts || !SkScalarIsFinite((pts[1] - pts[0]).length())) {
@@ -684,7 +670,7 @@
         return nullptr;
     }
     if (1 == colorCount) {
-        return SkShader::MakeColorShader(colors[0], std::move(colorSpace));
+        return SkShaders::Color(colors[0], std::move(colorSpace));
     }
     if (localMatrix && !localMatrix->invert(nullptr)) {
         return nullptr;
@@ -709,7 +695,7 @@
 sk_sp<SkShader> SkGradientShader::MakeRadial(const SkPoint& center, SkScalar radius,
                                              const SkColor colors[],
                                              const SkScalar pos[], int colorCount,
-                                             SkShader::TileMode mode,
+                                             SkTileMode mode,
                                              uint32_t flags,
                                              const SkMatrix* localMatrix) {
     ColorConverter converter(colors, colorCount);
@@ -721,7 +707,7 @@
                                              const SkColor4f colors[],
                                              sk_sp<SkColorSpace> colorSpace,
                                              const SkScalar pos[], int colorCount,
-                                             SkShader::TileMode mode,
+                                             SkTileMode mode,
                                              uint32_t flags,
                                              const SkMatrix* localMatrix) {
     if (radius < 0) {
@@ -731,7 +717,7 @@
         return nullptr;
     }
     if (1 == colorCount) {
-        return SkShader::MakeColorShader(colors[0], std::move(colorSpace));
+        return SkShaders::Color(colors[0], std::move(colorSpace));
     }
     if (localMatrix && !localMatrix->invert(nullptr)) {
         return nullptr;
@@ -757,7 +743,7 @@
                                                       const SkColor colors[],
                                                       const SkScalar pos[],
                                                       int colorCount,
-                                                      SkShader::TileMode mode,
+                                                      SkTileMode mode,
                                                       uint32_t flags,
                                                       const SkMatrix* localMatrix) {
     ColorConverter converter(colors, colorCount);
@@ -773,7 +759,7 @@
                                                       sk_sp<SkColorSpace> colorSpace,
                                                       const SkScalar pos[],
                                                       int colorCount,
-                                                      SkShader::TileMode mode,
+                                                      SkTileMode mode,
                                                       uint32_t flags,
                                                       const SkMatrix* localMatrix) {
     if (startRadius < 0 || endRadius < 0) {
@@ -790,7 +776,7 @@
             // Degenerate case, where the interpolation region area approaches zero. The proper
             // behavior depends on the tile mode, which is consistent with the default degenerate
             // gradient behavior, except when mode = clamp and the radii > 0.
-            if (mode == SkShader::TileMode::kClamp_TileMode && endRadius > kDegenerateThreshold) {
+            if (mode == SkTileMode::kClamp && endRadius > kDegenerateThreshold) {
                 // The interpolation region becomes an infinitely thin ring at the radius, so the
                 // final gradient will be the first color repeated from p=0 to 1, and then a hard
                 // stop switching to the last color at p=1.
@@ -830,7 +816,7 @@
                                             const SkColor colors[],
                                             const SkScalar pos[],
                                             int colorCount,
-                                            SkShader::TileMode mode,
+                                            SkTileMode mode,
                                             SkScalar startAngle,
                                             SkScalar endAngle,
                                             uint32_t flags,
@@ -845,7 +831,7 @@
                                             sk_sp<SkColorSpace> colorSpace,
                                             const SkScalar pos[],
                                             int colorCount,
-                                            SkShader::TileMode mode,
+                                            SkTileMode mode,
                                             SkScalar startAngle,
                                             SkScalar endAngle,
                                             uint32_t flags,
@@ -854,7 +840,7 @@
         return nullptr;
     }
     if (1 == colorCount) {
-        return SkShader::MakeColorShader(colors[0], std::move(colorSpace));
+        return SkShaders::Color(colors[0], std::move(colorSpace));
     }
     if (!SkScalarIsFinite(startAngle) || !SkScalarIsFinite(endAngle) || startAngle > endAngle) {
         return nullptr;
@@ -866,7 +852,7 @@
     if (SkScalarNearlyEqual(startAngle, endAngle, kDegenerateThreshold)) {
         // Degenerate gradient, which should follow default degenerate behavior unless it is
         // clamped and the angle is greater than 0.
-        if (mode == SkShader::kClamp_TileMode && endAngle > kDegenerateThreshold) {
+        if (mode == SkTileMode::kClamp && endAngle > kDegenerateThreshold) {
             // In this case, the first color is repeated from 0 to the angle, then a hardstop
             // switches to the last color (all other colors are compressed to the infinitely thin
             // interpolation region).
@@ -881,7 +867,7 @@
 
     if (startAngle <= 0 && endAngle >= 360) {
         // If the t-range includes [0,1], then we can always use clamping (presumably faster).
-        mode = SkShader::kClamp_TileMode;
+        mode = SkTileMode::kClamp;
     }
 
     ColorStopOptimizer opt(colors, pos, colorCount, mode);
diff --git a/src/shaders/gradients/SkGradientShaderPriv.h b/src/shaders/gradients/SkGradientShaderPriv.h
index b5c0145..913b7c8 100644
--- a/src/shaders/gradients/SkGradientShaderPriv.h
+++ b/src/shaders/gradients/SkGradientShaderPriv.h
@@ -17,7 +17,6 @@
 #include "SkTemplates.h"
 
 class SkColorSpace;
-class SkColorSpaceXformer;
 class SkRasterPipeline;
 class SkReadBuffer;
 class SkWriteBuffer;
@@ -27,7 +26,7 @@
     struct Descriptor {
         Descriptor() {
             sk_bzero(this, sizeof(*this));
-            fTileMode = SkShader::kClamp_TileMode;
+            fTileMode = SkTileMode::kClamp;
         }
 
         const SkMatrix*     fLocalMatrix;
@@ -35,7 +34,7 @@
         sk_sp<SkColorSpace> fColorSpace;
         const SkScalar*     fPos;
         int                 fCount;
-        SkShader::TileMode  fTileMode;
+        SkTileMode          fTileMode;
         uint32_t            fGradFlags;
 
         void flatten(SkWriteBuffer&) const;
@@ -77,7 +76,7 @@
 
     bool onAsLuminanceColor(SkColor*) const override;
 
-    bool onAppendStages(const StageRec&) const override;
+    bool onAppendStages(const SkStageRec&) const override;
 
     virtual void appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* tPipeline,
                                       SkRasterPipeline* postPipeline) const = 0;
@@ -91,14 +90,8 @@
         return ctx;
     }
 
-    struct AutoXformColors {
-        AutoXformColors(const SkGradientShaderBase&, SkColorSpaceXformer*);
-
-        SkAutoSTMalloc<8, SkColor> fColors;
-    };
-
     const SkMatrix fPtsToUnit;
-    TileMode       fTileMode;
+    SkTileMode      fTileMode;
     uint8_t        fGradFlags;
 
 public:
@@ -112,14 +105,22 @@
         return fOrigColors4f[i].toSkColor();
     }
 
-    SkColor4f*          fOrigColors4f; // original colors, as linear floats
+    bool colorsCanConvertToSkColor() const {
+        bool canConvert = true;
+        for (int i = 0; i < fColorCount; ++i) {
+            canConvert &= fOrigColors4f[i].fitsInBytes();
+        }
+        return canConvert;
+    }
+
+    SkColor4f*          fOrigColors4f; // original colors, as floats
     SkScalar*           fOrigPos;      // original positions
     int                 fColorCount;
     sk_sp<SkColorSpace> fColorSpace;   // color space of gradient stops
 
     bool colorsAreOpaque() const { return fColorsAreOpaque; }
 
-    TileMode getTileMode() const { return fTileMode; }
+    SkTileMode getTileMode() const { return fTileMode; }
 
 private:
     // Reserve inline space for up to 4 stops.
diff --git a/src/shaders/gradients/SkLinearGradient.cpp b/src/shaders/gradients/SkLinearGradient.cpp
index 77473ec..cbc1161 100644
--- a/src/shaders/gradients/SkLinearGradient.cpp
+++ b/src/shaders/gradients/SkLinearGradient.cpp
@@ -8,7 +8,6 @@
 #include "SkLinearGradient.h"
 
 #include "Sk4fLinearGradient.h"
-#include "SkColorSpaceXformer.h"
 #include "SkReadBuffer.h"
 #include "SkWriteBuffer.h"
 
@@ -56,7 +55,16 @@
 SkShaderBase::Context* SkLinearGradient::onMakeContext(
     const ContextRec& rec, SkArenaAlloc* alloc) const
 {
-    return fTileMode != kDecal_TileMode
+    // make sure our colorspaces are compatible with legacy blits
+    if (!rec.isLegacyCompatible(fColorSpace.get())) {
+        return nullptr;
+    }
+    // Can't use legacy blit if we can't represent our colors as SkColors
+    if (!this->colorsCanConvertToSkColor()) {
+        return nullptr;
+    }
+
+    return fTileMode != SkTileMode::kDecal
         ? CheckedMakeContext<LinearGradient4fContext>(alloc, *this, rec)
         : nullptr;
 }
@@ -64,7 +72,7 @@
 SkShaderBase::Context* SkLinearGradient::onMakeBurstPipelineContext(
     const ContextRec& rec, SkArenaAlloc* alloc) const {
 
-    if (fTileMode == SkShader::kDecal_TileMode) {
+    if (fTileMode == SkTileMode::kDecal) {
         // we only support decal w/ stages
         return nullptr;
     }
@@ -79,13 +87,6 @@
     // No extra stage needed for linear gradients.
 }
 
-sk_sp<SkShader> SkLinearGradient::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    const AutoXformColors xformedColors(*this, xformer);
-    SkPoint pts[2] = { fStart, fEnd };
-    return SkGradientShader::MakeLinear(pts, xformedColors.fColors.get(), fOrigPos, fColorCount,
-                                        fTileMode, fGradFlags, &this->getLocalMatrix());
-}
-
 SkShader::GradientType SkLinearGradient::asAGradient(GradientInfo* info) const {
     if (info) {
         commonAsAGradient(info);
diff --git a/src/shaders/gradients/SkLinearGradient.h b/src/shaders/gradients/SkLinearGradient.h
index 9950e6d..a5eda73 100644
--- a/src/shaders/gradients/SkLinearGradient.h
+++ b/src/shaders/gradients/SkLinearGradient.h
@@ -31,8 +31,6 @@
                               SkRasterPipeline* postPipeline) const final;
 
 
-    sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override;
-
 private:
     SK_FLATTENABLE_HOOKS(SkLinearGradient)
 
diff --git a/src/shaders/gradients/SkRadialGradient.cpp b/src/shaders/gradients/SkRadialGradient.cpp
index 7209486..08524188 100644
--- a/src/shaders/gradients/SkRadialGradient.cpp
+++ b/src/shaders/gradients/SkRadialGradient.cpp
@@ -5,7 +5,6 @@
  * found in the LICENSE file.
  */
 
-#include "SkColorSpaceXformer.h"
 #include "SkRadialGradient.h"
 #include "SkRasterPipeline.h"
 #include "SkReadBuffer.h"
@@ -59,13 +58,6 @@
     buffer.writeScalar(fRadius);
 }
 
-sk_sp<SkShader> SkRadialGradient::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    const AutoXformColors xformedColors(*this, xformer);
-    return SkGradientShader::MakeRadial(fCenter, fRadius, xformedColors.fColors.get(), fOrigPos,
-                                        fColorCount, fTileMode, fGradFlags,
-                                        &this->getLocalMatrix());
-}
-
 void SkRadialGradient::appendGradientStages(SkArenaAlloc*, SkRasterPipeline* p,
                                             SkRasterPipeline*) const {
     p->append(SkRasterPipeline::xy_to_radius);
diff --git a/src/shaders/gradients/SkRadialGradient.h b/src/shaders/gradients/SkRadialGradient.h
index caff786..2646e51 100644
--- a/src/shaders/gradients/SkRadialGradient.h
+++ b/src/shaders/gradients/SkRadialGradient.h
@@ -22,7 +22,6 @@
 protected:
     SkRadialGradient(SkReadBuffer& buffer);
     void flatten(SkWriteBuffer& buffer) const override;
-    sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override;
 
     void appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* tPipeline,
                               SkRasterPipeline* postPipeline) const override;
diff --git a/src/shaders/gradients/SkSweepGradient.cpp b/src/shaders/gradients/SkSweepGradient.cpp
index d3f1c86..14d907b6 100644
--- a/src/shaders/gradients/SkSweepGradient.cpp
+++ b/src/shaders/gradients/SkSweepGradient.cpp
@@ -5,7 +5,6 @@
  * found in the LICENSE file.
  */
 
-#include "SkColorSpaceXformer.h"
 #include "SkFloatingPoint.h"
 #include "SkRasterPipeline.h"
 #include "SkReadBuffer.h"
@@ -62,17 +61,6 @@
     buffer.writeScalar(fTScale);
 }
 
-sk_sp<SkShader> SkSweepGradient::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    const AutoXformColors xformedColors(*this, xformer);
-
-    SkScalar startAngle, endAngle;
-    std::tie(startAngle, endAngle) = angles_from_t_coeff(fTBias, fTScale);
-
-    return SkGradientShader::MakeSweep(fCenter.fX, fCenter.fY, xformedColors.fColors.get(),
-                                       fOrigPos, fColorCount, fTileMode, startAngle, endAngle,
-                                       fGradFlags, &this->getLocalMatrix());
-}
-
 void SkSweepGradient::appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* p,
                                            SkRasterPipeline*) const {
     p->append(SkRasterPipeline::xy_to_unit_angle);
diff --git a/src/shaders/gradients/SkSweepGradient.h b/src/shaders/gradients/SkSweepGradient.h
index 7fea967..99d428f 100644
--- a/src/shaders/gradients/SkSweepGradient.h
+++ b/src/shaders/gradients/SkSweepGradient.h
@@ -26,7 +26,6 @@
 
 protected:
     void flatten(SkWriteBuffer& buffer) const override;
-    sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override;
 
     void appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* tPipeline,
                               SkRasterPipeline* postPipeline) const override;
diff --git a/src/shaders/gradients/SkTwoPointConicalGradient.cpp b/src/shaders/gradients/SkTwoPointConicalGradient.cpp
index 469c8ad..36797a6 100644
--- a/src/shaders/gradients/SkTwoPointConicalGradient.cpp
+++ b/src/shaders/gradients/SkTwoPointConicalGradient.cpp
@@ -177,13 +177,6 @@
     buffer.writeScalar(fRadius2);
 }
 
-sk_sp<SkShader> SkTwoPointConicalGradient::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    const AutoXformColors xformedColors(*this, xformer);
-    return SkGradientShader::MakeTwoPointConical(fCenter1, fRadius1, fCenter2, fRadius2,
-                                                 xformedColors.fColors.get(), fOrigPos, fColorCount,
-                                                 fTileMode, fGradFlags, &this->getLocalMatrix());
-}
-
 void SkTwoPointConicalGradient::appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* p,
                                                      SkRasterPipeline* postPipeline) const {
     const auto dRadius = fRadius2 - fRadius1;
diff --git a/src/shaders/gradients/SkTwoPointConicalGradient.h b/src/shaders/gradients/SkTwoPointConicalGradient.h
index f764c01..65fb731 100644
--- a/src/shaders/gradients/SkTwoPointConicalGradient.h
+++ b/src/shaders/gradients/SkTwoPointConicalGradient.h
@@ -8,7 +8,6 @@
 #ifndef SkTwoPointConicalGradient_DEFINED
 #define SkTwoPointConicalGradient_DEFINED
 
-#include "SkColorSpaceXformer.h"
 #include "SkGradientShaderPriv.h"
 
 class SkTwoPointConicalGradient final : public SkGradientShaderBase {
@@ -66,7 +65,6 @@
 
 protected:
     void flatten(SkWriteBuffer& buffer) const override;
-    sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override;
 
     void appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* tPipeline,
                               SkRasterPipeline* postPipeline) const override;
diff --git a/src/sksl/README b/src/sksl/README
index 897130c..36617ce 100644
--- a/src/sksl/README
+++ b/src/sksl/README
@@ -55,6 +55,9 @@
 * creating a smaller vector from a larger vector (e.g. float2(float3(1))) is
   intentionally disallowed, as it is just a wordier way of performing a swizzle.
   Use swizzles instead.
+* The last swizzle component, in addition to the normal rgba / xyzw components,
+  can also be the constants '0' or '1'. foo.rgb1 is equivalent to
+  float4(foo.rgb, 1).
 * Use texture() instead of textureProj(), e.g. texture(sampler2D, float3) is
   equivalent to GLSL's textureProj(sampler2D, float3)
 * Render target width and height are available via sk_Width and sk_Height
@@ -145,6 +148,10 @@
   The first variant emits the child with a solid white input color. The second
   variant emits the child with the result of the 2nd argument's expression,
   which must evaluate to a half4. The process function returns a half4.
+* By default, fragment processors must be non-null. The type for a nullable
+  fragment processor is 'fragmentProcessor?', as in
+  'in fragmentProcessor? <name>'. You can check for the presence of such a
+  fragment processor by comparing it to 'null'.
 
 
 Creating a new .fp file
diff --git a/src/sksl/SkSLByteCode.h b/src/sksl/SkSLByteCode.h
new file mode 100644
index 0000000..df097e4
--- /dev/null
+++ b/src/sksl/SkSLByteCode.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_BYTECODE
+#define SKSL_BYTECODE
+
+#include "ir/SkSLFunctionDeclaration.h"
+
+namespace SkSL {
+
+enum class ByteCodeInstruction : uint8_t {
+    kInvalid,
+    // B = bool, F = float, I = int, S = signed, U = unsigned
+    kAddF,
+    kAddI,
+    kAndB,
+    kAndI,
+    kBranch,
+    kCompareIEQ,
+    kCompareINEQ,
+    kCompareFEQ,
+    kCompareFGT,
+    kCompareFGTEQ,
+    kCompareFLT,
+    kCompareFLTEQ,
+    kCompareFNEQ,
+    kCompareSGT,
+    kCompareSGTEQ,
+    kCompareSLT,
+    kCompareSLTEQ,
+    kCompareUGT,
+    kCompareUGTEQ,
+    kCompareULT,
+    kCompareULTEQ,
+    // Followed by a 16 bit address
+    kConditionalBranch,
+    // Pops and prints the top value from the stack
+    kDebugPrint,
+    kDivideF,
+    kDivideS,
+    kDivideU,
+    // Duplicates the top stack value
+    kDup,
+    // Followed by a byte indicating number of slots to copy below the underlying element.
+    // dupdown 2 yields: ... value3 value2 value1 => .. value2 value1 value3 value2 value2
+    kDupDown,
+    kFloatToInt,
+    kSignedToFloat,
+    kUnsignedToFloat,
+    kLoad,
+    // Followed by a byte indicating global slot to load
+    kLoadGlobal,
+    // Followed by a count byte (1-4), and then one byte per swizzle component (0-3).
+    kLoadSwizzle,
+    kNegateF,
+    kNegateS,
+    kMultiplyF,
+    kMultiplyS,
+    kMultiplyU,
+    kNot,
+    kOrB,
+    kOrI,
+    // Followed by a byte indicating parameter slot to load
+    kParameter,
+    kPop,
+    // Followed by a 32 bit value containing the value to push
+    kPushImmediate,
+    kRemainderS,
+    kRemainderU,
+    kStore,
+    kStoreGlobal,
+    // Followed by a count byte (1-4), and then one byte per swizzle component (0-3). Expects the
+    // stack to look like: ... target v1 v2 v3 v4, where the number of 'v's is equal to the number
+    // of swizzle components. After the store, the target and all v's are popped from the stack.
+    kStoreSwizzle,
+    // Followed by two count bytes (1-4), and then one byte per swizzle component (0-3). The first
+    // count byte provides the current vector size (the vector is the top n stack elements), and the
+    // second count byte provides the swizzle component count.
+    kSwizzle,
+    kSubtractF,
+    kSubtractI,
+    // Followed by a byte indicating vector count. Modifies the next instruction to operate on the
+    // indicated number of columns, e.g. kVector 2 kMultiplyf performs a float2 * float2 operation.
+    kVector,
+};
+
+struct ByteCode;
+
+struct ByteCodeFunction {
+    ByteCodeFunction(const ByteCode* owner, const FunctionDeclaration* declaration)
+        : fOwner(*owner)
+        , fDeclaration(*declaration) {}
+
+    const ByteCode& fOwner;
+    const FunctionDeclaration& fDeclaration;
+    int fParameterCount = 0;
+    int fLocalCount = 0;
+    std::vector<uint8_t> fCode;
+};
+
+struct ByteCode {
+    int fGlobalCount = 0;
+    int fInputCount = 0;
+    // one entry per input slot, contains the global slot to which the input slot maps
+    std::vector<uint8_t> fInputSlots;
+    std::vector<std::unique_ptr<ByteCodeFunction>> fFunctions;
+};
+
+}
+
+#endif
diff --git a/src/sksl/SkSLByteCodeGenerator.cpp b/src/sksl/SkSLByteCodeGenerator.cpp
new file mode 100644
index 0000000..8e2a008
--- /dev/null
+++ b/src/sksl/SkSLByteCodeGenerator.cpp
@@ -0,0 +1,787 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkSLByteCodeGenerator.h"
+
+namespace SkSL {
+
+static int slot_count(const Type& type) {
+    return type.columns() * type.rows();
+}
+
+bool ByteCodeGenerator::generateCode() {
+    for (const auto& e : fProgram) {
+        switch (e.fKind) {
+            case ProgramElement::kFunction_Kind: {
+                std::unique_ptr<ByteCodeFunction> f = this->writeFunction((FunctionDefinition&) e);
+                if (!f) {
+                    return false;
+                }
+                fOutput->fFunctions.push_back(std::move(f));
+                break;
+            }
+            case ProgramElement::kVar_Kind: {
+                VarDeclarations& decl = (VarDeclarations&) e;
+                for (const auto& v : decl.fVars) {
+                    const Variable* declVar = ((VarDeclaration&) *v).fVar;
+                    if (declVar->fModifiers.fLayout.fBuiltin >= 0) {
+                        continue;
+                    }
+                    if (declVar->fModifiers.fFlags & Modifiers::kIn_Flag) {
+                        for (int i = slot_count(declVar->fType); i > 0; --i) {
+                            fOutput->fInputSlots.push_back(fOutput->fGlobalCount++);
+                        }
+                    } else {
+                        fOutput->fGlobalCount += slot_count(declVar->fType);
+                    }
+                }
+                break;
+            }
+            default:
+                ; // ignore
+        }
+    }
+    return true;
+}
+
+std::unique_ptr<ByteCodeFunction> ByteCodeGenerator::writeFunction(const FunctionDefinition& f) {
+    fFunction = &f;
+    std::unique_ptr<ByteCodeFunction> result(new ByteCodeFunction(fOutput, &f.fDeclaration));
+    fParameterCount = 0;
+    for (const auto& p : f.fDeclaration.fParameters) {
+        fParameterCount += p->fType.columns() * p->fType.rows();
+    }
+    fCode = &result->fCode;
+    this->writeStatement(*f.fBody);
+    result->fParameterCount = fParameterCount;
+    result->fLocalCount = fLocals.size();
+    fLocals.clear();
+    fFunction = nullptr;
+    return result;
+}
+
+enum class TypeCategory {
+    kBool,
+    kSigned,
+    kUnsigned,
+    kFloat,
+};
+
+static TypeCategory type_category(const Type& type) {
+    switch (type.kind()) {
+        case Type::Kind::kVector_Kind:
+        case Type::Kind::kMatrix_Kind:
+            return type_category(type.componentType());
+        default:
+            if (type.fName == "bool") {
+                return TypeCategory::kBool;
+            } else if (type.fName == "int" || type.fName == "short") {
+                return TypeCategory::kSigned;
+            } else if (type.fName == "uint" || type.fName == "ushort") {
+                return TypeCategory::kUnsigned;
+            } else {
+                SkASSERT(type.fName == "float" || type.fName == "half");
+                return TypeCategory::kFloat;
+            }
+            ABORT("unsupported type: %s\n", type.description().c_str());
+    }
+}
+
+int ByteCodeGenerator::getLocation(const Variable& var) {
+    // given that we seldom have more than a couple of variables, linear search is probably the most
+    // efficient way to handle lookups
+    switch (var.fStorage) {
+        case Variable::kLocal_Storage: {
+            for (int i = fLocals.size() - 1; i >= 0; --i) {
+                if (fLocals[i] == &var) {
+                    return fParameterCount + i;
+                }
+            }
+            int result = fParameterCount + fLocals.size();
+            fLocals.push_back(&var);
+            for (int i = 0; i < slot_count(var.fType) - 1; ++i) {
+                fLocals.push_back(nullptr);
+            }
+            return result;
+        }
+        case Variable::kParameter_Storage: {
+            int offset = 0;
+            for (const auto& p : fFunction->fDeclaration.fParameters) {
+                if (p == &var) {
+                    return offset;
+                }
+                offset += slot_count(p->fType);
+            }
+            SkASSERT(false);
+            return -1;
+        }
+        case Variable::kGlobal_Storage: {
+            int offset = 0;
+            for (const auto& e : fProgram) {
+                if (e.fKind == ProgramElement::kVar_Kind) {
+                    VarDeclarations& decl = (VarDeclarations&) e;
+                    for (const auto& v : decl.fVars) {
+                        const Variable* declVar = ((VarDeclaration&) *v).fVar;
+                        if (declVar->fModifiers.fLayout.fBuiltin >= 0) {
+                            continue;
+                        }
+                        if (declVar == &var) {
+                            return offset;
+                        }
+                        offset += slot_count(declVar->fType);
+                    }
+                }
+            }
+            SkASSERT(false);
+            return -1;
+        }
+        default:
+            SkASSERT(false);
+            return 0;
+    }
+}
+
+void ByteCodeGenerator::write8(uint8_t b) {
+    fCode->push_back(b);
+}
+
+void ByteCodeGenerator::write16(uint16_t i) {
+    this->write8(i >> 8);
+    this->write8(i);
+}
+
+void ByteCodeGenerator::write32(uint32_t i) {
+    this->write8((i >> 24) & 0xFF);
+    this->write8((i >> 16) & 0xFF);
+    this->write8((i >>  8) & 0xFF);
+    this->write8((i >>  0) & 0xFF);
+}
+
+void ByteCodeGenerator::write(ByteCodeInstruction i) {
+    this->write8((uint8_t) i);
+}
+
+void ByteCodeGenerator::writeTypedInstruction(const Type& type, ByteCodeInstruction s,
+                                              ByteCodeInstruction u, ByteCodeInstruction f) {
+    switch (type_category(type)) {
+        case TypeCategory::kSigned:
+            this->write(s);
+            break;
+        case TypeCategory::kUnsigned:
+            this->write(u);
+            break;
+        case TypeCategory::kFloat:
+            this->write(f);
+            break;
+        default:
+            SkASSERT(false);
+    }
+}
+
+void ByteCodeGenerator::writeBinaryExpression(const BinaryExpression& b) {
+    if (b.fOperator == Token::Kind::EQ) {
+        std::unique_ptr<LValue> lvalue = this->getLValue(*b.fLeft);
+        this->writeExpression(*b.fRight);
+        this->write(ByteCodeInstruction::kDupDown);
+        this->write8(slot_count(b.fRight->fType));
+        lvalue->store();
+        return;
+    }
+    Token::Kind op;
+    std::unique_ptr<LValue> lvalue;
+    if (is_assignment(b.fOperator)) {
+        lvalue = this->getLValue(*b.fLeft);
+        lvalue->load();
+        op = remove_assignment(b.fOperator);
+    } else {
+        this->writeExpression(*b.fLeft);
+        op = b.fOperator;
+        if (b.fLeft->fType.kind() == Type::kScalar_Kind &&
+            b.fRight->fType.kind() == Type::kVector_Kind) {
+            for (int i = b.fRight->fType.columns(); i > 1; --i) {
+                this->write(ByteCodeInstruction::kDup);
+            }
+        }
+    }
+    this->writeExpression(*b.fRight);
+    if (b.fLeft->fType.kind() == Type::kVector_Kind &&
+        b.fRight->fType.kind() == Type::kScalar_Kind) {
+        for (int i = b.fLeft->fType.columns(); i > 1; --i) {
+            this->write(ByteCodeInstruction::kDup);
+        }
+    }
+    int count = slot_count(b.fType);
+    if (count > 1) {
+        this->write(ByteCodeInstruction::kVector);
+        this->write8(count);
+    }
+    switch (op) {
+        case Token::Kind::EQEQ:
+            this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kCompareIEQ,
+                                        ByteCodeInstruction::kCompareIEQ,
+                                        ByteCodeInstruction::kCompareFEQ);
+            break;
+        case Token::Kind::GT:
+            this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kCompareSGT,
+                                        ByteCodeInstruction::kCompareUGT,
+                                        ByteCodeInstruction::kCompareFGT);
+            break;
+        case Token::Kind::GTEQ:
+            this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kCompareSGTEQ,
+                                        ByteCodeInstruction::kCompareUGTEQ,
+                                        ByteCodeInstruction::kCompareFGTEQ);
+            break;
+        case Token::Kind::LT:
+            this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kCompareSLT,
+                                        ByteCodeInstruction::kCompareULT,
+                                        ByteCodeInstruction::kCompareFLT);
+            break;
+        case Token::Kind::LTEQ:
+            this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kCompareSLTEQ,
+                                        ByteCodeInstruction::kCompareULTEQ,
+                                        ByteCodeInstruction::kCompareFLTEQ);
+            break;
+        case Token::Kind::MINUS:
+            this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kSubtractI,
+                                        ByteCodeInstruction::kSubtractI,
+                                        ByteCodeInstruction::kSubtractF);
+            break;
+        case Token::Kind::NEQ:
+            this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kCompareINEQ,
+                                        ByteCodeInstruction::kCompareINEQ,
+                                        ByteCodeInstruction::kCompareFNEQ);
+            break;
+        case Token::Kind::PERCENT:
+            this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kRemainderS,
+                                        ByteCodeInstruction::kRemainderU,
+                                        ByteCodeInstruction::kInvalid);
+            break;
+        case Token::Kind::PLUS:
+            this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kAddI,
+                                        ByteCodeInstruction::kAddI,
+                                        ByteCodeInstruction::kAddF);
+            break;
+        case Token::Kind::SLASH:
+            this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kDivideS,
+                                        ByteCodeInstruction::kDivideU,
+                                        ByteCodeInstruction::kDivideF);
+            break;
+        case Token::Kind::STAR:
+            this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kMultiplyS,
+                                        ByteCodeInstruction::kMultiplyU,
+                                        ByteCodeInstruction::kMultiplyF);
+            break;
+        default:
+            SkASSERT(false);
+    }
+    if (lvalue) {
+        this->write(ByteCodeInstruction::kDupDown);
+        this->write8(slot_count(b.fType));
+        lvalue->store();
+    }
+}
+
+void ByteCodeGenerator::writeBoolLiteral(const BoolLiteral& b) {
+    this->write(ByteCodeInstruction::kPushImmediate);
+    this->write32(1);
+}
+
+void ByteCodeGenerator::writeConstructor(const Constructor& c) {
+    if (c.fArguments.size() == 1 &&
+        type_category(c.fType) == type_category(c.fArguments[0]->fType)) {
+        // cast from float to half or similar no-op
+        this->writeExpression(*c.fArguments[0]);
+        return;
+    }
+    for (const auto& arg : c.fArguments) {
+        this->writeExpression(*arg);
+    }
+    if (c.fArguments.size() == 1) {
+        TypeCategory inCategory = type_category(c.fArguments[0]->fType);
+        TypeCategory outCategory = type_category(c.fType);
+        if (inCategory != outCategory) {
+            int count = c.fType.columns();
+            if (count > 1) {
+                this->write(ByteCodeInstruction::kVector);
+                this->write8(count);
+            }
+            if (inCategory == TypeCategory::kFloat) {
+                SkASSERT(outCategory == TypeCategory::kSigned ||
+                         outCategory == TypeCategory::kUnsigned);
+                this->write(ByteCodeInstruction::kFloatToInt);
+            } else if (outCategory == TypeCategory::kFloat) {
+                if (inCategory == TypeCategory::kSigned) {
+                    this->write(ByteCodeInstruction::kSignedToFloat);
+                } else {
+                    SkASSERT(inCategory == TypeCategory::kUnsigned);
+                    this->write(ByteCodeInstruction::kUnsignedToFloat);
+                }
+            } else {
+                SkASSERT(false);
+            }
+        }
+    }
+}
+
+void ByteCodeGenerator::writeFieldAccess(const FieldAccess& f) {
+    // not yet implemented
+    abort();
+}
+
+void ByteCodeGenerator::writeFloatLiteral(const FloatLiteral& f) {
+    this->write(ByteCodeInstruction::kPushImmediate);
+    union { float f; uint32_t u; } pun = { (float) f.fValue };
+    this->write32(pun.u);
+}
+
+void ByteCodeGenerator::writeFunctionCall(const FunctionCall& f) {
+    // not yet implemented
+    abort();
+}
+
+void ByteCodeGenerator::writeIndexExpression(const IndexExpression& i) {
+    // not yet implemented
+    abort();
+}
+
+void ByteCodeGenerator::writeIntLiteral(const IntLiteral& i) {
+    this->write(ByteCodeInstruction::kPushImmediate);
+    this->write32(i.fValue);
+}
+
+void ByteCodeGenerator::writeNullLiteral(const NullLiteral& n) {
+    // not yet implemented
+    abort();
+}
+
+void ByteCodeGenerator::writePrefixExpression(const PrefixExpression& p) {
+    switch (p.fOperator) {
+        case Token::Kind::PLUSPLUS: // fall through
+        case Token::Kind::MINUSMINUS: {
+            std::unique_ptr<LValue> lvalue = this->getLValue(*p.fOperand);
+            lvalue->load();
+            this->write(ByteCodeInstruction::kPushImmediate);
+            this->write32(1);
+            if (p.fOperator == Token::Kind::PLUSPLUS) {
+                this->writeTypedInstruction(p.fType,
+                                            ByteCodeInstruction::kAddI,
+                                            ByteCodeInstruction::kAddI,
+                                            ByteCodeInstruction::kAddF);
+            } else {
+                this->writeTypedInstruction(p.fType,
+                                            ByteCodeInstruction::kSubtractI,
+                                            ByteCodeInstruction::kSubtractI,
+                                            ByteCodeInstruction::kSubtractF);
+            }
+            this->write(ByteCodeInstruction::kDupDown);
+            this->write8(slot_count(p.fType));
+            lvalue->store();
+            break;
+        }
+        case Token::Kind::MINUS:
+            this->writeTypedInstruction(p.fType,
+                                        ByteCodeInstruction::kNegateS,
+                                        ByteCodeInstruction::kInvalid,
+                                        ByteCodeInstruction::kNegateF);
+            break;
+        default:
+            SkASSERT(false);
+    }
+}
+
+void ByteCodeGenerator::writePostfixExpression(const PostfixExpression& p) {
+    // not yet implemented
+    abort();
+}
+
+void ByteCodeGenerator::writeSwizzle(const Swizzle& s) {
+    switch (s.fBase->fKind) {
+        case Expression::kVariableReference_Kind: {
+            const Variable& var = ((VariableReference&) *s.fBase).fVariable;
+            int location = this->getLocation(var);
+            this->write(ByteCodeInstruction::kPushImmediate);
+            this->write32(location);
+            this->write(ByteCodeInstruction::kLoadSwizzle);
+            this->write8(s.fComponents.size());
+            for (int c : s.fComponents) {
+                this->write8(c);
+            }
+            break;
+        }
+        default:
+            this->writeExpression(*s.fBase);
+            this->write(ByteCodeInstruction::kSwizzle);
+            this->write8(s.fBase->fType.columns());
+            this->write8(s.fComponents.size());
+            for (int c : s.fComponents) {
+                this->write8(c);
+            }
+    }
+}
+
+void ByteCodeGenerator::writeVariableReference(const VariableReference& v) {
+    if (v.fVariable.fStorage == Variable::kGlobal_Storage) {
+        this->write(ByteCodeInstruction::kLoadGlobal);
+        int location = this->getLocation(v.fVariable);
+        SkASSERT(location <= 255);
+        this->write8(location);
+    } else {
+        this->write(ByteCodeInstruction::kPushImmediate);
+        this->write32(this->getLocation(v.fVariable));
+        int count = slot_count(v.fType);
+        if (count > 1) {
+            this->write(ByteCodeInstruction::kVector);
+            this->write8(count);
+        }
+        this->write(ByteCodeInstruction::kLoad);
+    }
+}
+
+void ByteCodeGenerator::writeTernaryExpression(const TernaryExpression& t) {
+    // not yet implemented
+    abort();
+}
+
+void ByteCodeGenerator::writeExpression(const Expression& e) {
+    switch (e.fKind) {
+        case Expression::kBinary_Kind:
+            this->writeBinaryExpression((BinaryExpression&) e);
+            break;
+        case Expression::kBoolLiteral_Kind:
+            this->writeBoolLiteral((BoolLiteral&) e);
+            break;
+        case Expression::kConstructor_Kind:
+            this->writeConstructor((Constructor&) e);
+            break;
+        case Expression::kFieldAccess_Kind:
+            this->writeFieldAccess((FieldAccess&) e);
+            break;
+        case Expression::kFloatLiteral_Kind:
+            this->writeFloatLiteral((FloatLiteral&) e);
+            break;
+        case Expression::kFunctionCall_Kind:
+            this->writeFunctionCall((FunctionCall&) e);
+            break;
+        case Expression::kIndex_Kind:
+            this->writeIndexExpression((IndexExpression&) e);
+            break;
+        case Expression::kIntLiteral_Kind:
+            this->writeIntLiteral((IntLiteral&) e);
+            break;
+        case Expression::kNullLiteral_Kind:
+            this->writeNullLiteral((NullLiteral&) e);
+            break;
+        case Expression::kPrefix_Kind:
+            this->writePrefixExpression((PrefixExpression&) e);
+            break;
+        case Expression::kPostfix_Kind:
+            this->writePostfixExpression((PostfixExpression&) e);
+            break;
+        case Expression::kSwizzle_Kind:
+            this->writeSwizzle((Swizzle&) e);
+            break;
+        case Expression::kVariableReference_Kind:
+            this->writeVariableReference((VariableReference&) e);
+            break;
+        case Expression::kTernary_Kind:
+            this->writeTernaryExpression((TernaryExpression&) e);
+            break;
+        default:
+            printf("unsupported expression %s\n", e.description().c_str());
+            SkASSERT(false);
+    }
+}
+
+void ByteCodeGenerator::writeTarget(const Expression& e) {
+    switch (e.fKind) {
+        case Expression::kVariableReference_Kind:
+            this->write(ByteCodeInstruction::kPushImmediate);
+            this->write32(this->getLocation(((VariableReference&) e).fVariable));
+            break;
+        case Expression::kIndex_Kind:
+        case Expression::kTernary_Kind:
+        default:
+            printf("unsupported target %s\n", e.description().c_str());
+            SkASSERT(false);
+    }
+}
+
+class ByteCodeSwizzleLValue : public ByteCodeGenerator::LValue {
+public:
+    ByteCodeSwizzleLValue(ByteCodeGenerator* generator, const Swizzle& swizzle)
+        : INHERITED(*generator)
+        , fSwizzle(swizzle) {
+        fGenerator.writeTarget(*swizzle.fBase);
+    }
+
+    void load() override {
+        fGenerator.write(ByteCodeInstruction::kDup);
+        fGenerator.write(ByteCodeInstruction::kLoadSwizzle);
+        fGenerator.write8(fSwizzle.fComponents.size());
+        for (int c : fSwizzle.fComponents) {
+            fGenerator.write8(c);
+        }
+    }
+
+    void store() override {
+        fGenerator.write(ByteCodeInstruction::kStoreSwizzle);
+        fGenerator.write8(fSwizzle.fComponents.size());
+        for (int c : fSwizzle.fComponents) {
+            fGenerator.write8(c);
+        }
+    }
+
+private:
+    const Swizzle& fSwizzle;
+
+    typedef LValue INHERITED;
+};
+
+class ByteCodeVariableLValue : public ByteCodeGenerator::LValue {
+public:
+    ByteCodeVariableLValue(ByteCodeGenerator* generator, const Variable& var)
+        : INHERITED(*generator)
+        , fCount(slot_count(var.fType))
+        , fIsGlobal(var.fStorage == Variable::kGlobal_Storage) {
+        fGenerator.write(ByteCodeInstruction::kPushImmediate);
+        fGenerator.write32(generator->getLocation(var));
+    }
+
+    void load() override {
+        fGenerator.write(ByteCodeInstruction::kDup);
+        if (fCount > 1) {
+            fGenerator.write(ByteCodeInstruction::kVector);
+            fGenerator.write8(fCount);
+        }
+        fGenerator.write(fIsGlobal ? ByteCodeInstruction::kLoadGlobal : ByteCodeInstruction::kLoad);
+    }
+
+    void store() override {
+        if (fCount > 1) {
+            fGenerator.write(ByteCodeInstruction::kVector);
+            fGenerator.write8(fCount);
+        }
+        fGenerator.write(fIsGlobal ? ByteCodeInstruction::kStoreGlobal
+                                  : ByteCodeInstruction::kStore);
+    }
+
+private:
+    typedef LValue INHERITED;
+
+    int fCount;
+
+    bool fIsGlobal;
+};
+
+std::unique_ptr<ByteCodeGenerator::LValue> ByteCodeGenerator::getLValue(const Expression& e) {
+    switch (e.fKind) {
+        case Expression::kIndex_Kind:
+            // not yet implemented
+            abort();
+        case Expression::kVariableReference_Kind:
+            return std::unique_ptr<LValue>(new ByteCodeVariableLValue(this,
+                                                               ((VariableReference&) e).fVariable));
+        case Expression::kSwizzle_Kind:
+            return std::unique_ptr<LValue>(new ByteCodeSwizzleLValue(this, (Swizzle&) e));
+        case Expression::kTernary_Kind:
+        default:
+            printf("unsupported lvalue %s\n", e.description().c_str());
+            return nullptr;
+    }
+}
+
+void ByteCodeGenerator::writeBlock(const Block& b) {
+    for (const auto& s : b.fStatements) {
+        this->writeStatement(*s);
+    }
+}
+
+void ByteCodeGenerator::setBreakTargets() {
+    std::vector<DeferredLocation>& breaks = fBreakTargets.top();
+    for (DeferredLocation& b : breaks) {
+        b.set();
+    }
+    fBreakTargets.pop();
+}
+
+void ByteCodeGenerator::setContinueTargets() {
+    std::vector<DeferredLocation>& continues = fContinueTargets.top();
+    for (DeferredLocation& c : continues) {
+        c.set();
+    }
+    fContinueTargets.pop();
+}
+
+void ByteCodeGenerator::writeBreakStatement(const BreakStatement& b) {
+    this->write(ByteCodeInstruction::kBranch);
+    fBreakTargets.top().emplace_back(this);
+}
+
+void ByteCodeGenerator::writeContinueStatement(const ContinueStatement& c) {
+    this->write(ByteCodeInstruction::kBranch);
+    fContinueTargets.top().emplace_back(this);
+}
+
+void ByteCodeGenerator::writeDoStatement(const DoStatement& d) {
+    fContinueTargets.emplace();
+    fBreakTargets.emplace();
+    size_t start = fCode->size();
+    this->writeStatement(*d.fStatement);
+    this->setContinueTargets();
+    this->writeExpression(*d.fTest);
+    this->write(ByteCodeInstruction::kConditionalBranch);
+    this->write16(start);
+    this->setBreakTargets();
+}
+
+void ByteCodeGenerator::writeForStatement(const ForStatement& f) {
+    fContinueTargets.emplace();
+    fBreakTargets.emplace();
+    if (f.fInitializer) {
+        this->writeStatement(*f.fInitializer);
+    }
+    size_t start = fCode->size();
+    if (f.fTest) {
+        this->writeExpression(*f.fTest);
+        this->write(ByteCodeInstruction::kNot);
+        this->write(ByteCodeInstruction::kConditionalBranch);
+        DeferredLocation endLocation(this);
+        this->writeStatement(*f.fStatement);
+        this->setContinueTargets();
+        if (f.fNext) {
+            this->writeExpression(*f.fNext);
+            this->write(ByteCodeInstruction::kPop);
+            this->write8(slot_count(f.fNext->fType));
+        }
+        this->write(ByteCodeInstruction::kBranch);
+        this->write16(start);
+        endLocation.set();
+    } else {
+        this->writeStatement(*f.fStatement);
+        this->setContinueTargets();
+        if (f.fNext) {
+            this->writeExpression(*f.fNext);
+            this->write(ByteCodeInstruction::kPop);
+            this->write8(slot_count(f.fNext->fType));
+        }
+        this->write(ByteCodeInstruction::kBranch);
+        this->write16(start);
+    }
+    this->setBreakTargets();
+}
+
+void ByteCodeGenerator::writeIfStatement(const IfStatement& i) {
+    this->writeExpression(*i.fTest);
+    this->write(ByteCodeInstruction::kNot);
+    this->write(ByteCodeInstruction::kConditionalBranch);
+    DeferredLocation elseLocation(this);
+    this->writeStatement(*i.fIfTrue);
+    this->write(ByteCodeInstruction::kBranch);
+    DeferredLocation endLocation(this);
+    elseLocation.set();
+    if (i.fIfFalse) {
+        this->writeStatement(*i.fIfFalse);
+    }
+    endLocation.set();
+}
+
+void ByteCodeGenerator::writeReturnStatement(const ReturnStatement& r) {
+    // not yet implemented
+    abort();
+}
+
+void ByteCodeGenerator::writeSwitchStatement(const SwitchStatement& r) {
+    // not yet implemented
+    abort();
+}
+
+void ByteCodeGenerator::writeVarDeclarations(const VarDeclarations& v) {
+    for (const auto& declStatement : v.fVars) {
+        const VarDeclaration& decl = (VarDeclaration&) *declStatement;
+        // we need to grab the location even if we don't use it, to ensure it
+        // has been allocated
+        int location = getLocation(*decl.fVar);
+        if (decl.fValue) {
+            this->write(ByteCodeInstruction::kPushImmediate);
+            this->write32(location);
+            this->writeExpression(*decl.fValue);
+            int count = slot_count(decl.fValue->fType);
+            if (count > 1) {
+                this->write(ByteCodeInstruction::kVector);
+                this->write8(count);
+            }
+            this->write(ByteCodeInstruction::kStore);
+        }
+    }
+}
+
+void ByteCodeGenerator::writeWhileStatement(const WhileStatement& w) {
+    fContinueTargets.emplace();
+    fBreakTargets.emplace();
+    size_t start = fCode->size();
+    this->writeExpression(*w.fTest);
+    this->write(ByteCodeInstruction::kNot);
+    this->write(ByteCodeInstruction::kConditionalBranch);
+    DeferredLocation endLocation(this);
+    this->writeStatement(*w.fStatement);
+    this->setContinueTargets();
+    this->write(ByteCodeInstruction::kBranch);
+    this->write16(start);
+    endLocation.set();
+    this->setBreakTargets();
+}
+
+void ByteCodeGenerator::writeStatement(const Statement& s) {
+    switch (s.fKind) {
+        case Statement::kBlock_Kind:
+            this->writeBlock((Block&) s);
+            break;
+        case Statement::kBreak_Kind:
+            this->writeBreakStatement((BreakStatement&) s);
+            break;
+        case Statement::kContinue_Kind:
+            this->writeContinueStatement((ContinueStatement&) s);
+            break;
+        case Statement::kDiscard_Kind:
+            // not yet implemented
+            abort();
+        case Statement::kDo_Kind:
+            this->writeDoStatement((DoStatement&) s);
+            break;
+        case Statement::kExpression_Kind: {
+            const Expression& expr = *((ExpressionStatement&) s).fExpression;
+            this->writeExpression(expr);
+            this->write(ByteCodeInstruction::kPop);
+            this->write8(slot_count(expr.fType));
+            break;
+        }
+        case Statement::kFor_Kind:
+            this->writeForStatement((ForStatement&) s);
+            break;
+        case Statement::kIf_Kind:
+            this->writeIfStatement((IfStatement&) s);
+            break;
+        case Statement::kNop_Kind:
+            break;
+        case Statement::kReturn_Kind:
+            this->writeReturnStatement((ReturnStatement&) s);
+            break;
+        case Statement::kSwitch_Kind:
+            this->writeSwitchStatement((SwitchStatement&) s);
+            break;
+        case Statement::kVarDeclarations_Kind:
+            this->writeVarDeclarations(*((VarDeclarationsStatement&) s).fDeclaration);
+            break;
+        case Statement::kWhile_Kind:
+            this->writeWhileStatement((WhileStatement&) s);
+            break;
+        default:
+            SkASSERT(false);
+    }
+}
+
+}
diff --git a/src/sksl/SkSLByteCodeGenerator.h b/src/sksl/SkSLByteCodeGenerator.h
new file mode 100644
index 0000000..e518861
--- /dev/null
+++ b/src/sksl/SkSLByteCodeGenerator.h
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_BYTECODEGENERATOR
+#define SKSL_BYTECODEGENERATOR
+
+#include <stack>
+#include <tuple>
+#include <unordered_map>
+
+#include "SkSLByteCode.h"
+#include "SkSLCodeGenerator.h"
+#include "SkSLMemoryLayout.h"
+#include "ir/SkSLBinaryExpression.h"
+#include "ir/SkSLBoolLiteral.h"
+#include "ir/SkSLBlock.h"
+#include "ir/SkSLBreakStatement.h"
+#include "ir/SkSLConstructor.h"
+#include "ir/SkSLContinueStatement.h"
+#include "ir/SkSLDoStatement.h"
+#include "ir/SkSLExpressionStatement.h"
+#include "ir/SkSLFloatLiteral.h"
+#include "ir/SkSLIfStatement.h"
+#include "ir/SkSLIndexExpression.h"
+#include "ir/SkSLInterfaceBlock.h"
+#include "ir/SkSLIntLiteral.h"
+#include "ir/SkSLFieldAccess.h"
+#include "ir/SkSLForStatement.h"
+#include "ir/SkSLFunctionCall.h"
+#include "ir/SkSLFunctionDeclaration.h"
+#include "ir/SkSLFunctionDefinition.h"
+#include "ir/SkSLNullLiteral.h"
+#include "ir/SkSLPrefixExpression.h"
+#include "ir/SkSLPostfixExpression.h"
+#include "ir/SkSLProgramElement.h"
+#include "ir/SkSLReturnStatement.h"
+#include "ir/SkSLStatement.h"
+#include "ir/SkSLSwitchStatement.h"
+#include "ir/SkSLSwizzle.h"
+#include "ir/SkSLTernaryExpression.h"
+#include "ir/SkSLVarDeclarations.h"
+#include "ir/SkSLVarDeclarationsStatement.h"
+#include "ir/SkSLVariableReference.h"
+#include "ir/SkSLWhileStatement.h"
+#include "spirv.h"
+
+namespace SkSL {
+
+class ByteCodeGenerator : public CodeGenerator {
+public:
+    class LValue {
+    public:
+        LValue(ByteCodeGenerator& generator)
+            : fGenerator(generator) {}
+
+        virtual ~LValue() {}
+
+        /**
+         * Stack before call: ... lvalue
+         * Stack after call: ... lvalue load
+         */
+        virtual void load() = 0;
+
+        /**
+         * Stack before call: ... lvalue value
+         * Stack after call: ...
+         */
+        virtual void store() = 0;
+
+    protected:
+        ByteCodeGenerator& fGenerator;
+    };
+
+    ByteCodeGenerator(const Context* context, const Program* program, ErrorReporter* errors,
+                      ByteCode* output)
+    : INHERITED(program, errors, nullptr)
+    , fContext(*context)
+    , fOutput(output) {}
+
+    bool generateCode() override;
+
+    void write8(uint8_t b);
+
+    void write16(uint16_t b);
+
+    void write32(uint32_t b);
+
+    void write(ByteCodeInstruction inst);
+
+    /**
+     * Based on 'type', writes the s (signed), u (unsigned), or f (float) instruction.
+     */
+    void writeTypedInstruction(const Type& type, ByteCodeInstruction s, ByteCodeInstruction u,
+                               ByteCodeInstruction f);
+
+    /**
+     * Pushes the storage location of an lvalue to the stack.
+     */
+    void writeTarget(const Expression& expr);
+
+private:
+    // reserves 16 bits in the output code, to be filled in later with an address once we determine
+    // it
+    class DeferredLocation {
+    public:
+        DeferredLocation(ByteCodeGenerator* generator)
+            : fGenerator(*generator)
+            , fOffset(generator->fCode->size()) {
+            generator->write16(0);
+        }
+
+#ifdef SK_DEBUG
+        ~DeferredLocation() {
+            SkASSERT(fSet);
+        }
+#endif
+
+        void set() {
+            int target = fGenerator.fCode->size();
+            SkASSERT(target <= 65535);
+            (*fGenerator.fCode)[fOffset] = target >> 8;
+            (*fGenerator.fCode)[fOffset + 1] = target;
+#ifdef SK_DEBUG
+            fSet = true;
+#endif
+        }
+
+    private:
+        ByteCodeGenerator& fGenerator;
+        size_t fOffset;
+#ifdef SK_DEBUG
+        bool fSet = false;
+#endif
+    };
+
+    /**
+     * Returns the local slot into which var should be stored, allocating a new slot if it has not
+     * already been assigned one. Compound variables (e.g. vectors) will consume more than one local
+     * slot, with the getLocation return value indicating where the first element should be stored.
+     */
+    int getLocation(const Variable& var);
+
+    std::unique_ptr<ByteCodeFunction> writeFunction(const FunctionDefinition& f);
+
+    void writeVarDeclarations(const VarDeclarations& decl);
+
+    void writeVariableReference(const VariableReference& ref);
+
+    void writeExpression(const Expression& expr);
+
+    /**
+     * Pushes whatever values are required by the lvalue onto the stack, and returns an LValue
+     * permitting loads and stores to it.
+     */
+    std::unique_ptr<LValue> getLValue(const Expression& expr);
+
+    void writeFunctionCall(const FunctionCall& c);
+
+    void writeConstructor(const Constructor& c);
+
+    void writeFieldAccess(const FieldAccess& f);
+
+    void writeSwizzle(const Swizzle& swizzle);
+
+    void writeBinaryExpression(const BinaryExpression& b);
+
+    void writeTernaryExpression(const TernaryExpression& t);
+
+    void writeIndexExpression(const IndexExpression& expr);
+
+    void writeLogicalAnd(const BinaryExpression& b);
+
+    void writeLogicalOr(const BinaryExpression& o);
+
+    void writeNullLiteral(const NullLiteral& n);
+
+    void writePrefixExpression(const PrefixExpression& p);
+
+    void writePostfixExpression(const PostfixExpression& p);
+
+    void writeBoolLiteral(const BoolLiteral& b);
+
+    void writeIntLiteral(const IntLiteral& i);
+
+    void writeFloatLiteral(const FloatLiteral& f);
+
+    void writeStatement(const Statement& s);
+
+    void writeBlock(const Block& b);
+
+    void writeBreakStatement(const BreakStatement& b);
+
+    void writeContinueStatement(const ContinueStatement& c);
+
+    void writeIfStatement(const IfStatement& stmt);
+
+    void writeForStatement(const ForStatement& f);
+
+    void writeWhileStatement(const WhileStatement& w);
+
+    void writeDoStatement(const DoStatement& d);
+
+    void writeSwitchStatement(const SwitchStatement& s);
+
+    void writeReturnStatement(const ReturnStatement& r);
+
+    // updates the current set of breaks to branch to the current location
+    void setBreakTargets();
+
+    // updates the current set of continues to branch to the current location
+    void setContinueTargets();
+
+    const Context& fContext;
+
+    ByteCode* fOutput;
+
+    const FunctionDefinition* fFunction;
+
+    std::vector<uint8_t>* fCode;
+
+    std::vector<const Variable*> fLocals;
+
+    std::stack<std::vector<DeferredLocation>> fContinueTargets;
+
+    std::stack<std::vector<DeferredLocation>> fBreakTargets;
+
+    int fParameterCount;
+
+    friend class DeferredLocation;
+    friend class ByteCodeVariableLValue;
+
+    typedef CodeGenerator INHERITED;
+};
+
+}
+
+#endif
diff --git a/src/sksl/SkSLCFGGenerator.cpp b/src/sksl/SkSLCFGGenerator.cpp
index c267058..d86892e 100644
--- a/src/sksl/SkSLCFGGenerator.cpp
+++ b/src/sksl/SkSLCFGGenerator.cpp
@@ -391,6 +391,7 @@
         case Expression::kBoolLiteral_Kind:  // fall through
         case Expression::kFloatLiteral_Kind: // fall through
         case Expression::kIntLiteral_Kind:   // fall through
+        case Expression::kNullLiteral_Kind:   // fall through
         case Expression::kSetting_Kind:      // fall through
         case Expression::kVariableReference_Kind:
             cfg.fBlocks[cfg.fCurrent].fNodes.push_back({ BasicBlock::Node::kExpression_Kind,
@@ -450,6 +451,10 @@
     }
 }
 
+static bool is_true(Expression& expr) {
+    return expr.fKind == Expression::kBoolLiteral_Kind && ((BoolLiteral&) expr).fValue;
+}
+
 void CFGGenerator::addStatement(CFG& cfg, std::unique_ptr<Statement>* s) {
     switch ((*s)->fKind) {
         case Statement::kBlock_Kind:
@@ -535,7 +540,9 @@
             fLoopExits.push(loopExit);
             this->addExpression(cfg, &w.fTest, true);
             BlockId test = cfg.fCurrent;
-            cfg.addExit(test, loopExit);
+            if (!is_true(*w.fTest)) {
+                cfg.addExit(test, loopExit);
+            }
             cfg.newBlock();
             this->addStatement(cfg, &w.fStatement);
             cfg.addExit(cfg.fCurrent, loopStart);
diff --git a/src/sksl/SkSLCPPCodeGenerator.cpp b/src/sksl/SkSLCPPCodeGenerator.cpp
index 237148a..570c5e8 100644
--- a/src/sksl/SkSLCPPCodeGenerator.cpp
+++ b/src/sksl/SkSLCPPCodeGenerator.cpp
@@ -77,6 +77,32 @@
         if (precedence >= parentPrecedence) {
             this->write(")");
         }
+    } else if (b.fLeft->fKind == Expression::kNullLiteral_Kind ||
+               b.fRight->fKind == Expression::kNullLiteral_Kind) {
+        const Variable* var;
+        if (b.fLeft->fKind != Expression::kNullLiteral_Kind) {
+            SkASSERT(b.fLeft->fKind == Expression::kVariableReference_Kind);
+            var = &((VariableReference&) *b.fLeft).fVariable;
+        } else {
+            SkASSERT(b.fRight->fKind == Expression::kVariableReference_Kind);
+            var = &((VariableReference&) *b.fRight).fVariable;
+        }
+        SkASSERT(var->fType.kind() == Type::kNullable_Kind &&
+                 var->fType.componentType() == *fContext.fFragmentProcessor_Type);
+        this->write("%s");
+        const char* op;
+        switch (b.fOperator) {
+            case Token::EQEQ:
+                op = "<";
+                break;
+            case Token::NEQ:
+                op = ">=";
+                break;
+            default:
+                SkASSERT(false);
+        }
+        fFormatArgs.push_back("_outer." + String(var->fName) + "_index " + op + " 0 ? \"true\" "
+                              ": \"false\"");
     } else {
         INHERITED::writeBinaryExpression(b, parentPrecedence);
     }
@@ -295,7 +321,7 @@
             } else if (SectionAndParameterHelper::IsParameter(ref.fVariable)) {
                 String name(ref.fVariable.fName);
                 this->writeRuntimeValue(ref.fVariable.fType, ref.fVariable.fModifiers.fLayout,
-                                        String::printf("_outer.%s()", name.c_str()).c_str());
+                                        String::printf("_outer.%s", name.c_str()).c_str());
             } else {
                 this->write(ref.fVariable.fName);
             }
@@ -333,9 +359,10 @@
         }
 
         const Type::Field& field = fContext.fFragmentProcessor_Type->fields()[access.fFieldIndex];
-        int index = getChildFPIndex((const VariableReference&) *access.fBase);
-        String cppAccess = String::printf("_outer.childProcessor(%s).%s()",
-                                          to_string(index).c_str(), String(field.fName).c_str());
+        const Variable& var = ((const VariableReference&) *access.fBase).fVariable;
+        String cppAccess = String::printf("_outer.childProcessor(_outer.%s_index).%s()",
+                                          String(var.fName).c_str(),
+                                          String(field.fName).c_str());
 
         if (fCPPMode) {
             this->write(cppAccess.c_str());
@@ -347,7 +374,7 @@
     INHERITED::writeFieldAccess(access);
 }
 
-int CPPCodeGenerator::getChildFPIndex(const VariableReference& reference) const {
+int CPPCodeGenerator::getChildFPIndex(const Variable& var) const {
     int index = 0;
     bool found = false;
     for (const auto& p : fProgram) {
@@ -355,9 +382,9 @@
             const VarDeclarations& decls = (const VarDeclarations&) p;
             for (const auto& raw : decls.fVars) {
                 const VarDeclaration& decl = (VarDeclaration&) *raw;
-                if (decl.fVar == &reference.fVariable) {
+                if (decl.fVar == &var) {
                     found = true;
-                } else if (decl.fVar->fType == *fContext.fFragmentProcessor_Type) {
+                } else if (decl.fVar->fType.nonnullable() == *fContext.fFragmentProcessor_Type) {
                     ++index;
                 }
             }
@@ -374,7 +401,8 @@
     if (c.fFunction.fBuiltin && c.fFunction.fName == "process") {
         // Sanity checks that are detected by function definition in sksl_fp.inc
         SkASSERT(c.fArguments.size() == 1 || c.fArguments.size() == 2);
-        SkASSERT("fragmentProcessor" == c.fArguments[0]->fType.name());
+        SkASSERT("fragmentProcessor"  == c.fArguments[0]->fType.name() ||
+                 "fragmentProcessor?" == c.fArguments[0]->fType.name());
 
         // Actually fail during compilation if arguments with valid types are
         // provided that are not variable references, since process() is a
@@ -388,7 +416,8 @@
             // Second argument must also be a half4 expression
             SkASSERT("half4" == c.fArguments[1]->fType.name());
         }
-        int index = getChildFPIndex((const VariableReference&) *c.fArguments[0]);
+        const Variable& child = ((const VariableReference&) *c.fArguments[0]).fVariable;
+        int index = getChildFPIndex(child);
 
         // Start a new extra emit code section so that the emitted child processor can depend on
         // sksl variables defined in earlier sksl code.
@@ -412,9 +441,22 @@
         // Write the output handling after the possible input handling
         String childName = "_child" + to_string(index);
         addExtraEmitCodeLine("SkString " + childName + "(\"" + childName + "\");");
-        addExtraEmitCodeLine("this->emitChild(" + to_string(index) + inputArg +
-                             ", &" + childName + ", args);");
-
+        if (c.fArguments[0]->fType.kind() == Type::kNullable_Kind) {
+            addExtraEmitCodeLine("if (_outer." + String(child.fName) + "_index >= 0) {\n    ");
+        }
+        addExtraEmitCodeLine("this->emitChild(_outer." + String(child.fName) + "_index" +
+                             inputArg + ", &" + childName + ", args);");
+        if (c.fArguments[0]->fType.kind() == Type::kNullable_Kind) {
+            // Null FPs are not emitted, but their output can still be referenced in dependent
+            // expressions - thus we always declare the variable.
+            // Note: this is essentially dead code required to satisfy the compiler, because
+            // 'process' function calls should always be guarded at a higher level, in the .fp
+            // source.
+            addExtraEmitCodeLine(
+                "} else {"
+                "   fragBuilder->codeAppendf(\"half4 %s;\", " + childName + ".c_str());"
+                "}");
+        }
         this->write("%s");
         fFormatArgs.push_back(childName + ".c_str()");
         return;
@@ -492,16 +534,6 @@
     if (!needs_uniform_var(var)) {
         return;
     }
-    const char* precision;
-    if (var.fModifiers.fFlags & Modifiers::kHighp_Flag) {
-        precision = "kHigh_GrSLPrecision";
-    } else if (var.fModifiers.fFlags & Modifiers::kMediump_Flag) {
-        precision = "kMedium_GrSLPrecision";
-    } else if (var.fModifiers.fFlags & Modifiers::kLowp_Flag) {
-        precision = "kLow_GrSLPrecision";
-    } else {
-        precision = "kDefault_GrSLPrecision";
-    }
     const char* type;
     if (var.fType == *fContext.fFloat_Type) {
         type = "kFloat_GrSLType";
@@ -528,7 +560,7 @@
     }
     String name(var.fName);
     this->writef("        %sVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, %s, "
-                 "%s, \"%s\");\n", HCodeGenerator::FieldName(name.c_str()).c_str(), type, precision,
+                 "\"%s\");\n", HCodeGenerator::FieldName(name.c_str()).c_str(), type,
                  name.c_str());
     if (var.fModifiers.fLayout.fWhen.size()) {
         this->write("        }\n");
@@ -597,8 +629,9 @@
 }
 
 static bool is_accessible(const Variable& var) {
-    return Type::kSampler_Kind != var.fType.kind() &&
-           Type::kOther_Kind != var.fType.kind();
+    const Type& type = var.fType.nonnullable();
+    return Type::kSampler_Kind != type.kind() &&
+           Type::kOther_Kind != type.kind();
 }
 
 void CPPCodeGenerator::newExtraEmitCodeBlock() {
@@ -813,7 +846,7 @@
                 const char* name = nameString.c_str();
                 if (SectionAndParameterHelper::IsParameter(*decl.fVar) &&
                     is_accessible(*decl.fVar)) {
-                    this->writef("        auto %s = _outer.%s();\n"
+                    this->writef("        auto %s = _outer.%s;\n"
                                  "        (void) %s;\n",
                                  name, name, name);
                 }
@@ -885,12 +918,12 @@
                 // Use AccessType since that will match the return type of _outer's public API.
                 String valueType = HCodeGenerator::AccessType(fContext, u->fType,
                                                               u->fModifiers.fLayout);
-                this->writef("%s%s %s = _outer.%s();\n",
+                this->writef("%s%s %s = _outer.%s;\n",
                              indent.c_str(), valueType.c_str(), valueVar.c_str(), name);
             } else {
                 // Not tracked and the mapper only needs to use the value once
                 // so send it a safe expression instead of the variable name
-                valueVar.appendf("(_outer.%s())", name);
+                valueVar.appendf("(_outer.%s)", name);
             }
 
             if (isTracked) {
@@ -946,7 +979,7 @@
                                          fullName);
                             wroteProcessor = true;
                         }
-                        this->writef("        auto %s = _outer.%s();\n"
+                        this->writef("        auto %s = _outer.%s;\n"
                                      "        (void) %s;\n",
                                      name, name, name);
                     }
@@ -990,30 +1023,39 @@
         this->writef("%s::%s(const %s& src)\n"
                      ": INHERITED(k%s_ClassID, src.optimizationFlags())", fFullName.c_str(),
                      fFullName.c_str(), fFullName.c_str(), fFullName.c_str());
-        for (const auto& param : fSectionAndParameterHelper.getParameters()) {
-            if (param->fType == *fContext.fFragmentProcessor_Type) {
-                continue;
-            }
-            String fieldName = HCodeGenerator::FieldName(String(param->fName).c_str());
-            this->writef("\n, %s(src.%s)",
-                         fieldName.c_str(),
-                         fieldName.c_str());
-        }
         const auto transforms = fSectionAndParameterHelper.getSections(COORD_TRANSFORM_SECTION);
         for (size_t i = 0; i < transforms.size(); ++i) {
             const Section& s = *transforms[i];
             String fieldName = HCodeGenerator::CoordTransformName(s.fArgument, i);
             this->writef("\n, %s(src.%s)", fieldName.c_str(), fieldName.c_str());
         }
+        for (const auto& param : fSectionAndParameterHelper.getParameters()) {
+            String fieldName = HCodeGenerator::FieldName(String(param->fName).c_str());
+            if (param->fType.nonnullable() == *fContext.fFragmentProcessor_Type) {
+                this->writef("\n, %s_index(src.%s_index)",
+                             fieldName.c_str(),
+                             fieldName.c_str());
+            } else {
+                this->writef("\n, %s(src.%s)",
+                             fieldName.c_str(),
+                             fieldName.c_str());
+            }
+        }
         this->writef(" {\n");
-        int childCount = 0;
         int samplerCount = 0;
         for (const auto& param : fSectionAndParameterHelper.getParameters()) {
             if (param->fType.kind() == Type::kSampler_Kind) {
                 ++samplerCount;
-            } else if (param->fType == *fContext.fFragmentProcessor_Type) {
-                this->writef("    this->registerChildProcessor(src.childProcessor(%d).clone());"
-                             "\n", childCount++);
+            } else if (param->fType.nonnullable() == *fContext.fFragmentProcessor_Type) {
+                String fieldName = HCodeGenerator::FieldName(String(param->fName).c_str());
+                if (param->fType.kind() == Type::kNullable_Kind) {
+                    this->writef("    if (%s_index >= 0) {\n    ", fieldName.c_str());
+                }
+                this->writef("    this->registerChildProcessor(src.childProcessor(%s_index)."
+                             "clone());\n", fieldName.c_str());
+                if (param->fType.kind() == Type::kNullable_Kind) {
+                    this->writef("    }\n");
+                }
             }
         }
         if (samplerCount) {
@@ -1063,6 +1105,9 @@
         }
         switch (param->fModifiers.fLayout.fKey) {
             case Layout::kKey_Key:
+                if (param->fModifiers.fLayout.fWhen.size()) {
+                    this->writef("if (%s) ", param->fModifiers.fLayout.fWhen.c_str());
+                }
                 if (param->fType == *fContext.fFloat4x4_Type) {
                     ABORT("no automatic key handling for float4x4\n");
                 } else if (param->fType == *fContext.fFloat2_Type) {
@@ -1182,7 +1227,7 @@
                  "    (void) that;\n",
                  fullName, fullName, fullName);
     for (const auto& param : fSectionAndParameterHelper.getParameters()) {
-        if (param->fType == *fContext.fFragmentProcessor_Type) {
+        if (param->fType.nonnullable() == *fContext.fFragmentProcessor_Type) {
             continue;
         }
         String nameString(param->fName);
diff --git a/src/sksl/SkSLCPPCodeGenerator.h b/src/sksl/SkSLCPPCodeGenerator.h
index 08d88a9..bc74d3c 100644
--- a/src/sksl/SkSLCPPCodeGenerator.h
+++ b/src/sksl/SkSLCPPCodeGenerator.h
@@ -115,7 +115,7 @@
     // Append CPP code to the current extra emit code block.
     void addExtraEmitCodeLine(const String& toAppend);
 
-    int getChildFPIndex(const VariableReference& reference) const;
+    int getChildFPIndex(const Variable& var) const;
 
     String fName;
     String fFullName;
diff --git a/src/sksl/SkSLCompiler.cpp b/src/sksl/SkSLCompiler.cpp
index 41ff5cc..9d4238f 100644
--- a/src/sksl/SkSLCompiler.cpp
+++ b/src/sksl/SkSLCompiler.cpp
@@ -7,6 +7,7 @@
 
 #include "SkSLCompiler.h"
 
+#include "SkSLByteCodeGenerator.h"
 #include "SkSLCFGGenerator.h"
 #include "SkSLCPPCodeGenerator.h"
 #include "SkSLGLSLCodeGenerator.h"
@@ -60,6 +61,10 @@
 #include "sksl_pipeline.inc"
 ;
 
+static const char* SKSL_MIXER_INCLUDE =
+#include "sksl_mixer.inc"
+;
+
 namespace SkSL {
 
 Compiler::Compiler(Flags flags)
@@ -1267,6 +1272,13 @@
                                          strlen(SKSL_PIPELINE_STAGE_INCLUDE), *fTypes, &elements);
             fIRGenerator->fSymbolTable->markAllFunctionsBuiltin();
             break;
+        case Program::kMixer_Kind:
+            inherited = nullptr;
+            fIRGenerator->start(&settings, nullptr);
+            fIRGenerator->convertProgram(kind, SKSL_MIXER_INCLUDE, strlen(SKSL_MIXER_INCLUDE),
+                                         *fTypes, &elements);
+            fIRGenerator->fSymbolTable->markAllFunctionsBuiltin();
+            break;
     }
     for (auto& element : elements) {
         if (element->fKind == ProgramElement::kEnum_Kind) {
@@ -1301,7 +1313,26 @@
                 this->scanCFG((FunctionDefinition&) element);
             }
         }
-        fSource = nullptr;
+        if (program.fKind != Program::kFragmentProcessor_Kind) {
+            for (auto iter = program.fElements.begin(); iter != program.fElements.end();) {
+                if ((*iter)->fKind == ProgramElement::kVar_Kind) {
+                    VarDeclarations& vars = (VarDeclarations&) **iter;
+                    for (auto varIter = vars.fVars.begin(); varIter != vars.fVars.end();) {
+                        const Variable& var = *((VarDeclaration&) **varIter).fVar;
+                        if (var.dead()) {
+                            varIter = vars.fVars.erase(varIter);
+                        } else {
+                            ++varIter;
+                        }
+                    }
+                    if (vars.fVars.size() == 0) {
+                        iter = program.fElements.erase(iter);
+                        continue;
+                    }
+                }
+                ++iter;
+            }
+        }
     }
     return fErrorCount == 0;
 }
@@ -1447,6 +1478,18 @@
     return result;
 }
 
+std::unique_ptr<ByteCode> Compiler::toByteCode(Program& program) {
+    if (!this->optimize(program)) {
+        return nullptr;
+    }
+    std::unique_ptr<ByteCode> result(new ByteCode());
+    ByteCodeGenerator cg(fContext.get(), &program, this, result.get());
+    if (cg.generateCode()) {
+        return result;
+    }
+    return nullptr;
+}
+
 const char* Compiler::OperatorName(Token::Kind kind) {
     switch (kind) {
         case Token::PLUS:         return "+";
diff --git a/src/sksl/SkSLCompiler.h b/src/sksl/SkSLCompiler.h
index cd70278..c000ca8 100644
--- a/src/sksl/SkSLCompiler.h
+++ b/src/sksl/SkSLCompiler.h
@@ -13,6 +13,7 @@
 #include <vector>
 #include "ir/SkSLProgram.h"
 #include "ir/SkSLSymbolTable.h"
+#include "SkSLByteCode.h"
 #include "SkSLCFGGenerator.h"
 #include "SkSLContext.h"
 #include "SkSLErrorReporter.h"
@@ -50,7 +51,7 @@
  *
  * See the README for information about SkSL.
  */
-class Compiler : public ErrorReporter {
+class SK_API Compiler : public ErrorReporter {
 public:
     static constexpr const char* RTADJUST_NAME  = "sk_RTAdjust";
     static constexpr const char* PERVERTEX_NAME = "sk_PerVertex";
@@ -87,6 +88,9 @@
 
     ~Compiler() override;
 
+    Compiler(const Compiler&) = delete;
+    Compiler& operator=(const Compiler&) = delete;
+
     std::unique_ptr<Program> convertProgram(Program::Kind kind, String text,
                                             const Program::Settings& settings);
 
@@ -111,6 +115,8 @@
 
     bool toH(Program& program, String name, OutputStream& out);
 
+    std::unique_ptr<ByteCode> toByteCode(Program& program);
+
     bool toPipelineStage(const Program& program, String* out,
                          std::vector<FormatArg>* outFormatArgs);
 
diff --git a/src/sksl/SkSLContext.h b/src/sksl/SkSLContext.h
index b8c68c2..eef7dd5 100644
--- a/src/sksl/SkSLContext.h
+++ b/src/sksl/SkSLContext.h
@@ -21,13 +21,14 @@
     Context()
     : fInvalid_Type(new Type("<INVALID>"))
     , fVoid_Type(new Type("void"))
+    , fNull_Type(new Type("null"))
     , fFloatLiteral_Type(new Type("$floatLiteral", Type::kFloat_NumberKind, 3))
     , fIntLiteral_Type(new Type("$intLiteral", Type::kSigned_NumberKind, 1))
-    , fDouble_Type(new Type("double", Type::kFloat_NumberKind, 6))
+    , fDouble_Type(new Type("double", Type::kFloat_NumberKind, 6, true))
     , fDouble2_Type(new Type("double2", *fDouble_Type, 2))
     , fDouble3_Type(new Type("double3", *fDouble_Type, 3))
     , fDouble4_Type(new Type("double4", *fDouble_Type, 4))
-    , fFloat_Type(new Type("float", Type::kFloat_NumberKind, 5))
+    , fFloat_Type(new Type("float", Type::kFloat_NumberKind, 5, true))
     , fFloat2_Type(new Type("float2", *fFloat_Type, 2))
     , fFloat3_Type(new Type("float3", *fFloat_Type, 3))
     , fFloat4_Type(new Type("float4", *fFloat_Type, 4))
@@ -35,11 +36,11 @@
     , fHalf2_Type(new Type("half2", *fHalf_Type, 2))
     , fHalf3_Type(new Type("half3", *fHalf_Type, 3))
     , fHalf4_Type(new Type("half4", *fHalf_Type, 4))
-    , fUInt_Type(new Type("uint", Type::kUnsigned_NumberKind, 2))
+    , fUInt_Type(new Type("uint", Type::kUnsigned_NumberKind, 2, true))
     , fUInt2_Type(new Type("uint2", *fUInt_Type, 2))
     , fUInt3_Type(new Type("uint3", *fUInt_Type, 3))
     , fUInt4_Type(new Type("uint4", *fUInt_Type, 4))
-    , fInt_Type(new Type("int", Type::kSigned_NumberKind, 2))
+    , fInt_Type(new Type("int", Type::kSigned_NumberKind, 2, true))
     , fInt2_Type(new Type("int2", *fInt_Type, 2))
     , fInt3_Type(new Type("int3", *fInt_Type, 3))
     , fInt4_Type(new Type("int4", *fInt_Type, 4))
@@ -208,6 +209,7 @@
 
     const std::unique_ptr<Type> fInvalid_Type;
     const std::unique_ptr<Type> fVoid_Type;
+    const std::unique_ptr<Type> fNull_Type;
     const std::unique_ptr<Type> fFloatLiteral_Type;
     const std::unique_ptr<Type> fIntLiteral_Type;
 
diff --git a/src/sksl/SkSLGLSLCodeGenerator.cpp b/src/sksl/SkSLGLSLCodeGenerator.cpp
index 090c7ba..d003dc9 100644
--- a/src/sksl/SkSLGLSLCodeGenerator.cpp
+++ b/src/sksl/SkSLGLSLCodeGenerator.cpp
@@ -460,9 +460,9 @@
         (*fFunctionClasses)["abs"]         = FunctionClass::kAbs;
         (*fFunctionClasses)["atan"]        = FunctionClass::kAtan;
         (*fFunctionClasses)["determinant"] = FunctionClass::kDeterminant;
-        (*fFunctionClasses)["dFdx"]        = FunctionClass::kDerivative;
-        (*fFunctionClasses)["dFdy"]        = FunctionClass::kDerivative;
-        (*fFunctionClasses)["fwidth"]      = FunctionClass::kDerivative;
+        (*fFunctionClasses)["dFdx"]        = FunctionClass::kDFdx;
+        (*fFunctionClasses)["dFdy"]        = FunctionClass::kDFdy;
+        (*fFunctionClasses)["fwidth"]      = FunctionClass::kFwidth;
         (*fFunctionClasses)["fma"]         = FunctionClass::kFMA;
         (*fFunctionClasses)["fract"]       = FunctionClass::kFract;
         (*fFunctionClasses)["inverse"]     = FunctionClass::kInverse;
@@ -517,7 +517,15 @@
                     }
                 }
                 break;
-            case FunctionClass::kDerivative:
+            case FunctionClass::kDFdy:
+                if (fProgram.fSettings.fFlipY) {
+                    // Flipping Y also negates the Y derivatives.
+                    this->write("-dFdy");
+                    nameWritten = true;
+                }
+                // fallthru
+            case FunctionClass::kDFdx:
+            case FunctionClass::kFwidth:
                 if (!fFoundDerivatives &&
                     fProgram.fSettings.fCaps->shaderDerivativeExtensionString()) {
                     SkASSERT(fProgram.fSettings.fCaps->shaderDerivativeSupport());
@@ -840,10 +848,23 @@
 }
 
 void GLSLCodeGenerator::writeSwizzle(const Swizzle& swizzle) {
+    int last = swizzle.fComponents.back();
+    if (last == SKSL_SWIZZLE_0 || last == SKSL_SWIZZLE_1) {
+        this->writeType(swizzle.fType);
+        this->write("(");
+    }
     this->writeExpression(*swizzle.fBase, kPostfix_Precedence);
     this->write(".");
     for (int c : swizzle.fComponents) {
-        this->write(&("x\0y\0z\0w\0"[c * 2]));
+        if (c >= 0) {
+            this->write(&("x\0y\0z\0w\0"[c * 2]));
+        }
+    }
+    if (last == SKSL_SWIZZLE_0) {
+        this->write(", 0)");
+    }
+    else if (last == SKSL_SWIZZLE_1) {
+        this->write(", 1)");
     }
 }
 
@@ -1119,16 +1140,24 @@
     if (modifiers.fFlags & Modifiers::kPLSOut_Flag) {
         this->write("__pixel_local_outEXT ");
     }
-    if (usesPrecisionModifiers()) {
-        if (modifiers.fFlags & Modifiers::kLowp_Flag) {
-            this->write("lowp ");
-        }
-        if (modifiers.fFlags & Modifiers::kMediump_Flag) {
-            this->write("mediump ");
-        }
-        if (modifiers.fFlags & Modifiers::kHighp_Flag) {
+    switch (modifiers.fLayout.fFormat) {
+        case Layout::Format::kUnspecified:
+            break;
+        case Layout::Format::kRGBA32F: // fall through
+        case Layout::Format::kR32F:
             this->write("highp ");
-        }
+            break;
+        case Layout::Format::kRGBA16F: // fall through
+        case Layout::Format::kR16F:    // fall through
+        case Layout::Format::kRG16F:
+            this->write("mediump ");
+            break;
+        case Layout::Format::kRGBA8:  // fall through
+        case Layout::Format::kR8:     // fall through
+        case Layout::Format::kRGBA8I: // fall through
+        case Layout::Format::kR8I:
+            this->write("lowp ");
+            break;
     }
 }
 
@@ -1547,14 +1576,20 @@
         Layout layout;
         switch (fProgram.fKind) {
             case Program::kVertex_Kind: {
-                Modifiers modifiers(layout, Modifiers::kOut_Flag | Modifiers::kHighp_Flag);
+                Modifiers modifiers(layout, Modifiers::kOut_Flag);
                 this->writeModifiers(modifiers, true);
+                if (this->usesPrecisionModifiers()) {
+                    this->write("highp ");
+                }
                 this->write("vec4 sk_FragCoord_Workaround;\n");
                 break;
             }
             case Program::kFragment_Kind: {
-                Modifiers modifiers(layout, Modifiers::kIn_Flag | Modifiers::kHighp_Flag);
+                Modifiers modifiers(layout, Modifiers::kIn_Flag);
                 this->writeModifiers(modifiers, true);
+                if (this->usesPrecisionModifiers()) {
+                    this->write("highp ");
+                }
                 this->write("vec4 sk_FragCoord_Workaround;\n");
                 break;
             }
diff --git a/src/sksl/SkSLGLSLCodeGenerator.h b/src/sksl/SkSLGLSLCodeGenerator.h
index 99e7d47..2a743ea 100644
--- a/src/sksl/SkSLGLSLCodeGenerator.h
+++ b/src/sksl/SkSLGLSLCodeGenerator.h
@@ -227,7 +227,9 @@
         kAbs,
         kAtan,
         kDeterminant,
-        kDerivative,
+        kDFdx,
+        kDFdy,
+        kFwidth,
         kFMA,
         kFract,
         kInverse,
diff --git a/src/sksl/SkSLHCodeGenerator.cpp b/src/sksl/SkSLHCodeGenerator.cpp
index 8c6b8f4..7127415 100644
--- a/src/sksl/SkSLHCodeGenerator.cpp
+++ b/src/sksl/SkSLHCodeGenerator.cpp
@@ -41,7 +41,9 @@
     if (layout.fCType != Layout::CType::kDefault) {
         return layout.fCType;
     }
-    if (type == *context.fFloat_Type || type == *context.fHalf_Type) {
+    if (type.kind() == Type::kNullable_Kind) {
+        return ParameterCType(context, type.componentType(), layout);
+    } else if (type == *context.fFloat_Type || type == *context.fHalf_Type) {
         return Layout::CType::kFloat;
     } else if (type == *context.fInt_Type ||
                type == *context.fShort_Type ||
@@ -110,6 +112,7 @@
         vsprintf(heap.get(), s, copy);
         fOut->write(heap.get(), length);
     }
+    va_end(copy);
 }
 
 void HCodeGenerator::writef(const char* s, ...) {
@@ -188,7 +191,7 @@
                      fFullName.c_str());
         separator = "";
         for (const auto& param : fSectionAndParameterHelper.getParameters()) {
-            if (param->fType == *fContext.fFragmentProcessor_Type) {
+            if (param->fType.nonnullable() == *fContext.fFragmentProcessor_Type) {
                 this->writef("%sstd::move(%s)", separator, String(param->fName).c_str());
             } else {
                 this->writef("%s%s", separator, String(param->fName).c_str());
@@ -233,10 +236,23 @@
     }
     this->writef(")");
     this->writeSection(INITIALIZERS_SECTION, "\n    , ");
+    const auto transforms = fSectionAndParameterHelper.getSections(COORD_TRANSFORM_SECTION);
+    for (size_t i = 0; i < transforms.size(); ++i) {
+        const Section& s = *transforms[i];
+        String field = CoordTransformName(s.fArgument.c_str(), i);
+        if (s.fArgument.size()) {
+            this->writef("\n    , %s(%s, %s.get())", field.c_str(), s.fText.c_str(),
+                         FieldName(s.fArgument.c_str()).c_str());
+        }
+        else {
+            this->writef("\n    , %s(%s)", field.c_str(), s.fText.c_str());
+        }
+    }
     for (const auto& param : fSectionAndParameterHelper.getParameters()) {
         String nameString(param->fName);
         const char* name = nameString.c_str();
-        if (param->fType.kind() == Type::kSampler_Kind) {
+        const Type& type = param->fType.nonnullable();
+        if (type.kind() == Type::kSampler_Kind) {
             this->writef("\n    , %s(std::move(%s)", FieldName(name).c_str(), name);
             for (const Section* s : fSectionAndParameterHelper.getSections(
                                                                           SAMPLER_PARAMS_SECTION)) {
@@ -245,33 +261,31 @@
                 }
             }
             this->writef(")");
-        } else if (param->fType == *fContext.fFragmentProcessor_Type) {
+        } else if (type == *fContext.fFragmentProcessor_Type) {
             // do nothing
         } else {
             this->writef("\n    , %s(%s)", FieldName(name).c_str(), name);
         }
     }
-    const auto transforms = fSectionAndParameterHelper.getSections(COORD_TRANSFORM_SECTION);
-    for (size_t i = 0; i < transforms.size(); ++i) {
-        const Section& s = *transforms[i];
-        String field = CoordTransformName(s.fArgument.c_str(), i);
-        if (s.fArgument.size()) {
-            this->writef("\n    , %s(%s, %s.proxy())", field.c_str(), s.fText.c_str(),
-                         FieldName(s.fArgument.c_str()).c_str());
-        }
-        else {
-            this->writef("\n    , %s(%s)", field.c_str(), s.fText.c_str());
-        }
-    }
     this->writef(" {\n");
     this->writeSection(CONSTRUCTOR_CODE_SECTION);
     int samplerCount = 0;
     for (const auto& param : fSectionAndParameterHelper.getParameters()) {
         if (param->fType.kind() == Type::kSampler_Kind) {
             ++samplerCount;
-        } else if (param->fType == *fContext.fFragmentProcessor_Type) {
-            this->writef("        this->registerChildProcessor(std::move(%s));",
+        } else if (param->fType.nonnullable() == *fContext.fFragmentProcessor_Type) {
+            if (param->fType.kind() == Type::kNullable_Kind) {
+                this->writef("        if (%s) {\n", String(param->fName).c_str());
+            } else {
+                this->writef("        SkASSERT(%s);", String(param->fName).c_str());
+            }
+            this->writef("            %s_index = this->numChildProcessors();",
+                         FieldName(String(param->fName).c_str()).c_str());
+            this->writef("            this->registerChildProcessor(std::move(%s));",
                          String(param->fName).c_str());
+            if (param->fType.kind() == Type::kNullable_Kind) {
+                this->writef("       }");
+            }
         }
     }
     if (samplerCount) {
@@ -287,20 +301,22 @@
 
 void HCodeGenerator::writeFields() {
     this->writeSection(FIELDS_SECTION);
-    for (const auto& param : fSectionAndParameterHelper.getParameters()) {
-        if (param->fType == *fContext.fFragmentProcessor_Type) {
-            continue;
-        }
-        this->writef("    %s %s;\n", FieldType(fContext, param->fType,
-                                               param->fModifiers.fLayout).c_str(),
-                     FieldName(String(param->fName).c_str()).c_str());
-    }
     const auto transforms = fSectionAndParameterHelper.getSections(COORD_TRANSFORM_SECTION);
     for (size_t i = 0; i < transforms.size(); ++i) {
         const Section& s = *transforms[i];
         this->writef("    GrCoordTransform %s;\n",
                      CoordTransformName(s.fArgument.c_str(), i).c_str());
     }
+    for (const auto& param : fSectionAndParameterHelper.getParameters()) {
+        String name = FieldName(String(param->fName).c_str());
+        if (param->fType.nonnullable() == *fContext.fFragmentProcessor_Type) {
+            this->writef("    int %s_index = -1;\n", name.c_str());
+        } else {
+            this->writef("    %s %s;\n", FieldType(fContext, param->fType,
+                                                   param->fModifiers.fLayout).c_str(),
+                                         name.c_str());
+        }
+    }
 }
 
 String HCodeGenerator::GetHeader(const Program& program, ErrorReporter& errors) {
@@ -339,23 +355,13 @@
         }
     }
     this->writeSection(CLASS_SECTION);
-    for (const auto& param : fSectionAndParameterHelper.getParameters()) {
-        if (param->fType.kind() == Type::kSampler_Kind ||
-            param->fType.kind() == Type::kOther_Kind) {
-            continue;
-        }
-        String nameString(param->fName);
-        const char* name = nameString.c_str();
-        this->writef("    %s %s() const { return %s; }\n",
-                     AccessType(fContext, param->fType, param->fModifiers.fLayout).c_str(), name,
-                     FieldName(name).c_str());
-    }
     this->writeMake();
     this->writef("    %s(const %s& src);\n"
                  "    std::unique_ptr<GrFragmentProcessor> clone() const override;\n"
-                 "    const char* name() const override { return \"%s\"; }\n"
-                 "private:\n",
+                 "    const char* name() const override { return \"%s\"; }\n",
                  fFullName.c_str(), fFullName.c_str(), fName.c_str());
+    this->writeFields();
+    this->writef("private:\n");
     this->writeConstructor();
     this->writef("    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;\n"
                  "    void onGetGLSLProcessorKey(const GrShaderCaps&,"
@@ -368,7 +374,6 @@
         }
     }
     this->writef("    GR_DECLARE_FRAGMENT_PROCESSOR_TEST\n");
-    this->writeFields();
     this->writef("    typedef GrFragmentProcessor INHERITED;\n"
                 "};\n");
     this->writeSection(HEADER_END_SECTION);
diff --git a/src/sksl/SkSLHCodeGenerator.h b/src/sksl/SkSLHCodeGenerator.h
index 875bbf9..6561984 100644
--- a/src/sksl/SkSLHCodeGenerator.h
+++ b/src/sksl/SkSLHCodeGenerator.h
@@ -42,7 +42,7 @@
     static String AccessType(const Context& context, const Type& type, const Layout& layout);
 
     static String FieldName(const char* varName) {
-        return String::printf("f%c%s", toupper(varName[0]), varName + 1);
+        return String(varName);
     }
 
     static String CoordTransformName(const String& arg, int index) {
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
index bf017ad..542889a 100644
--- a/src/sksl/SkSLIRGenerator.cpp
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -17,6 +17,7 @@
 #include "ast/SkSLASTFloatLiteral.h"
 #include "ast/SkSLASTIndexSuffix.h"
 #include "ast/SkSLASTIntLiteral.h"
+#include "ast/SkSLASTNullLiteral.h"
 #include "ir/SkSLAppendStage.h"
 #include "ir/SkSLBinaryExpression.h"
 #include "ir/SkSLBoolLiteral.h"
@@ -40,6 +41,7 @@
 #include "ir/SkSLInterfaceBlock.h"
 #include "ir/SkSLIntLiteral.h"
 #include "ir/SkSLLayout.h"
+#include "ir/SkSLNullLiteral.h"
 #include "ir/SkSLPostfixExpression.h"
 #include "ir/SkSLPrefixExpression.h"
 #include "ir/SkSLReturnStatement.h"
@@ -130,6 +132,7 @@
     CAP(dropsTileOnZeroDivide);
     CAP(flatInterpolationSupport);
     CAP(noperspectiveInterpolationSupport);
+    CAP(sampleVariablesSupport);
     CAP(externalTextureSupport);
     CAP(imageLoadStoreSupport);
     CAP(mustEnableAdvBlendEqs);
@@ -265,6 +268,18 @@
         baseType->kind() == Type::Kind::kMatrix_Kind) {
         fErrors.error(decl.fOffset, "'in' variables may not have matrix type");
     }
+    if (decl.fModifiers.fLayout.fWhen.length() && fKind != Program::kFragmentProcessor_Kind &&
+        fKind != Program::kPipelineStage_Kind) {
+        fErrors.error(decl.fOffset, "'when' is only permitted within fragment processors");
+    }
+    if (decl.fModifiers.fLayout.fKey) {
+        if (fKind != Program::kFragmentProcessor_Kind && fKind != Program::kPipelineStage_Kind) {
+            fErrors.error(decl.fOffset, "'key' is only permitted within fragment processors");
+        }
+        if ((decl.fModifiers.fFlags & Modifiers::kUniform_Flag) != 0) {
+            fErrors.error(decl.fOffset, "'key' is not permitted on 'uniform' variables");
+        }
+    }
     for (const auto& varDecl : decl.fVars) {
         if (decl.fModifiers.fLayout.fLocation == 0 && decl.fModifiers.fLayout.fIndex == 0 &&
             (decl.fModifiers.fFlags & Modifiers::kOut_Flag) && fKind == Program::kFragment_Kind &&
@@ -345,6 +360,10 @@
                                                                  const ASTModifiersDeclaration& m) {
     Modifiers modifiers = m.fModifiers;
     if (modifiers.fLayout.fInvocations != -1) {
+        if (fKind != Program::kGeometry_Kind) {
+            fErrors.error(m.fOffset, "'invocations' is only legal in geometry shaders");
+            return nullptr;
+        }
         fInvocations = modifiers.fLayout.fInvocations;
         if (fSettings->fCaps && !fSettings->fCaps->gsInvocationsSupport()) {
             modifiers.fLayout.fInvocations = -1;
@@ -666,6 +685,10 @@
     return std::unique_ptr<Statement>(new ExpressionStatement(std::move(result)));
 }
 
+// returns true if the modifiers are (explicitly or implicitly) nothing but 'in'
+static bool is_in(const Modifiers& modifiers) {
+    return (modifiers.fFlags & ~Modifiers::kIn_Flag) == 0;
+}
 
 void IRGenerator::convertFunction(const ASTFunction& f) {
     const Type* returnType = this->convertType(*f.fReturnType);
@@ -693,22 +716,51 @@
     }
 
     if (f.fName == "main") {
-        if (fKind == Program::kPipelineStage_Kind) {
-            bool valid = parameters.size() == 3 &&
-                         parameters[0]->fType == *fContext.fInt_Type &&
-                         parameters[0]->fModifiers.fFlags == 0 &&
-                         parameters[1]->fType == *fContext.fInt_Type &&
-                         parameters[1]->fModifiers.fFlags == 0 &&
-                         parameters[2]->fType == *fContext.fHalf4_Type &&
-                         parameters[2]->fModifiers.fFlags == (Modifiers::kIn_Flag |
-                                                              Modifiers::kOut_Flag);
-            if (!valid) {
-                fErrors.error(f.fOffset, "pipeline stage 'main' must be declared main(int, "
-                                         "int, inout half4)");
-                return;
+        switch (fKind) {
+            case Program::kPipelineStage_Kind: {
+                bool valid;
+                switch (parameters.size()) {
+                    case 3:
+                        valid = parameters[0]->fType == *fContext.fInt_Type &&
+                                parameters[0]->fModifiers.fFlags == 0 &&
+                                parameters[1]->fType == *fContext.fInt_Type &&
+                                parameters[1]->fModifiers.fFlags == 0 &&
+                                parameters[2]->fType == *fContext.fHalf4_Type &&
+                                parameters[2]->fModifiers.fFlags == (Modifiers::kIn_Flag |
+                                                                     Modifiers::kOut_Flag);
+                        break;
+                    case 1:
+                        valid = parameters[0]->fType == *fContext.fHalf4_Type &&
+                                parameters[0]->fModifiers.fFlags == (Modifiers::kIn_Flag |
+                                                                     Modifiers::kOut_Flag);
+                        break;
+                    default:
+                        valid = false;
+                }
+                if (!valid) {
+                    fErrors.error(f.fOffset, "pipeline stage 'main' must be declared main(int, "
+                                             "int, inout half4) or main(inout half4)");
+                    return;
+                }
+                break;
             }
-        } else if (parameters.size()) {
-            fErrors.error(f.fOffset, "shader 'main' must have zero parameters");
+            case Program::kMixer_Kind: {
+                if (*returnType != *fContext.fVoid_Type ||
+                    parameters.size() != 2 ||
+                    parameters[0]->fType != *fContext.fHalf4_Type ||
+                    !is_in(parameters[0]->fModifiers) ||
+                    parameters[1]->fType != *fContext.fHalf4_Type ||
+                    !is_in(parameters[1]->fModifiers)) {
+                    fErrors.error(f.fOffset, "mixer stage 'main' must be declared void main("
+                                             "half4, half4)");
+                    return;
+                }
+                break;
+            }
+            default:
+                if (parameters.size()) {
+                    fErrors.error(f.fOffset, "shader 'main' must have zero parameters");
+                }
         }
     }
 
@@ -783,9 +835,14 @@
         std::shared_ptr<SymbolTable> old = fSymbolTable;
         AutoSymbolTable table(this);
         if (f.fName == "main" && fKind == Program::kPipelineStage_Kind) {
-            parameters[0]->fModifiers.fLayout.fBuiltin = SK_MAIN_X_BUILTIN;
-            parameters[1]->fModifiers.fLayout.fBuiltin = SK_MAIN_Y_BUILTIN;
-            parameters[2]->fModifiers.fLayout.fBuiltin = SK_OUTCOLOR_BUILTIN;
+            if (parameters.size() == 3) {
+                parameters[0]->fModifiers.fLayout.fBuiltin = SK_MAIN_X_BUILTIN;
+                parameters[1]->fModifiers.fLayout.fBuiltin = SK_MAIN_Y_BUILTIN;
+                parameters[2]->fModifiers.fLayout.fBuiltin = SK_OUTCOLOR_BUILTIN;
+            } else {
+                SkASSERT(parameters.size() == 1);
+                parameters[0]->fModifiers.fLayout.fBuiltin = SK_OUTCOLOR_BUILTIN;
+            }
         }
         for (size_t i = 0; i < parameters.size(); i++) {
             fSymbolTable->addWithoutOwnership(parameters[i]->fName, decl->fParameters[i]);
@@ -938,7 +995,7 @@
     std::vector<Variable*> variables;
     int64_t currentValue = 0;
     Layout layout;
-    ASTType enumType(e.fOffset, e.fTypeName, ASTType::kIdentifier_Kind, {});
+    ASTType enumType(e.fOffset, e.fTypeName, ASTType::kIdentifier_Kind, {}, false);
     const Type* type = this->convertType(enumType);
     Modifiers modifiers(layout, Modifiers::kConst_Flag);
     std::shared_ptr<SymbolTable> symbols(new SymbolTable(fSymbolTable, &fErrors));
@@ -970,6 +1027,19 @@
 const Type* IRGenerator::convertType(const ASTType& type) {
     const Symbol* result = (*fSymbolTable)[type.fName];
     if (result && result->fKind == Symbol::kType_Kind) {
+        if (type.fNullable) {
+            if (((Type&) *result) == *fContext.fFragmentProcessor_Type) {
+                if (type.fSizes.size()) {
+                    fErrors.error(type.fOffset, "type '" + type.fName + "' may not be used in "
+                                                "an array");
+                }
+                result = new Type(String(result->fName) + "?", Type::kNullable_Kind,
+                                  (const Type&) *result);
+                fSymbolTable->takeOwnership((Type*) result);
+            } else {
+                fErrors.error(type.fOffset, "type '" + type.fName + "' may not be nullable");
+            }
+        }
         for (int size : type.fSizes) {
             String name(result->fName);
             name += "[";
@@ -1001,6 +1071,8 @@
                                                                 ((ASTFloatLiteral&) expr).fValue));
         case ASTExpression::kBinary_Kind:
             return this->convertBinaryExpression((ASTBinaryExpression&) expr);
+        case ASTExpression::kNull_Kind:
+            return std::unique_ptr<Expression>(new NullLiteral(fContext, expr.fOffset));
         case ASTExpression::kPrefix_Kind:
             return this->convertPrefixExpression((ASTPrefixExpression&) expr);
         case ASTExpression::kSuffix_Kind:
@@ -1118,6 +1190,10 @@
         SkASSERT(ctor);
         return this->call(-1, std::move(ctor), std::move(args));
     }
+    if (expr->fKind == Expression::kNullLiteral_Kind) {
+        SkASSERT(type.kind() == Type::kNullable_Kind);
+        return std::unique_ptr<Expression>(new NullLiteral(expr->fOffset, type));
+    }
     std::vector<std::unique_ptr<Expression>> args;
     args.push_back(std::move(expr));
     return std::unique_ptr<Expression>(new Constructor(-1, type, std::move(args)));
@@ -1945,6 +2021,20 @@
     std::vector<int> swizzleComponents;
     for (size_t i = 0; i < fields.fLength; i++) {
         switch (fields[i]) {
+            case '0':
+                if (i != fields.fLength - 1) {
+                    fErrors.error(base->fOffset,
+                                  "only the last swizzle component can be a constant");
+                }
+                swizzleComponents.push_back(SKSL_SWIZZLE_0);
+                break;
+            case '1':
+                if (i != fields.fLength - 1) {
+                    fErrors.error(base->fOffset,
+                                  "only the last swizzle component can be a constant");
+                }
+                swizzleComponents.push_back(SKSL_SWIZZLE_1);
+                break;
             case 'x': // fall through
             case 'r': // fall through
             case 's':
diff --git a/src/sksl/SkSLInterpreter.cpp b/src/sksl/SkSLInterpreter.cpp
index c39c7b2..f427cf2 100644
--- a/src/sksl/SkSLInterpreter.cpp
+++ b/src/sksl/SkSLInterpreter.cpp
@@ -27,189 +27,42 @@
 
 namespace SkSL {
 
-void Interpreter::run() {
-    for (const auto& e : *fProgram) {
-        if (ProgramElement::kFunction_Kind == e.fKind) {
-            const FunctionDefinition& f = (const FunctionDefinition&) e;
-            if ("appendStages" == f.fDeclaration.fName) {
-                this->run(f);
-                return;
+static constexpr int UNINITIALIZED = 0xDEADBEEF;
+
+Interpreter::Value Interpreter::run(const ByteCodeFunction& f, Interpreter::Value args[],
+                                    Interpreter::Value inputs[]) {
+    fIP = 0;
+    fCurrentFunction = &f;
+    fStack.clear();
+    fGlobals.clear();
+#ifdef TRACE
+    this->disassemble(f);
+#endif
+    for (int i = 0; i < f.fParameterCount; ++i) {
+        this->push(args[i]);
+    }
+    for (int i = 0; i < f.fLocalCount; ++i) {
+        this->push(Value((int) UNINITIALIZED));
+    }
+    for (int i = 0; i < f.fOwner.fGlobalCount; ++i) {
+        fGlobals.push_back(Value((int) UNINITIALIZED));
+    }
+    for (int i = f.fOwner.fInputSlots.size() - 1; i >= 0; --i) {
+        fGlobals[f.fOwner.fInputSlots[i]] = inputs[i];
+    }
+    run();
+    int offset = 0;
+    for (const auto& p : f.fDeclaration.fParameters) {
+        if (p->fModifiers.fFlags & Modifiers::kOut_Flag) {
+            for (int i = p->fType.columns() * p->fType.rows() - 1; i >= 0; --i) {
+                args[offset] = fStack[offset];
+                ++offset;
             }
+        } else {
+            offset += p->fType.columns() * p->fType.rows();
         }
     }
-    SkASSERT(false);
-}
-
-static int SizeOf(const Type& type) {
-    return 1;
-}
-
-void Interpreter::run(const FunctionDefinition& f) {
-    fVars.emplace_back();
-    StackIndex current = (StackIndex) fStack.size();
-    for (int i = f.fDeclaration.fParameters.size() - 1; i >= 0; --i) {
-        current -= SizeOf(f.fDeclaration.fParameters[i]->fType);
-        fVars.back()[f.fDeclaration.fParameters[i]] = current;
-    }
-    fCurrentIndex.push_back({ f.fBody.get(), 0 });
-    while (fCurrentIndex.size()) {
-        this->runStatement();
-    }
-}
-
-void Interpreter::push(Value value) {
-    fStack.push_back(value);
-}
-
-Interpreter::Value Interpreter::pop() {
-    auto iter = fStack.end() - 1;
-    Value result = *iter;
-    fStack.erase(iter);
-    return result;
-}
-
- Interpreter::StackIndex Interpreter::stackAlloc(int count) {
-    int result = fStack.size();
-    for (int i = 0; i < count; ++i) {
-        fStack.push_back(Value((int) 0xDEADBEEF));
-    }
-    return result;
-}
-
-void Interpreter::runStatement() {
-    const Statement& stmt = *fCurrentIndex.back().fStatement;
-    const size_t index = fCurrentIndex.back().fIndex;
-    fCurrentIndex.pop_back();
-    switch (stmt.fKind) {
-        case Statement::kBlock_Kind: {
-            const Block& b = (const Block&) stmt;
-            if (!b.fStatements.size()) {
-                break;
-            }
-            SkASSERT(index < b.fStatements.size());
-            if (index < b.fStatements.size() - 1) {
-                fCurrentIndex.push_back({ &b, index + 1 });
-            }
-            fCurrentIndex.push_back({ b.fStatements[index].get(), 0 });
-            break;
-        }
-        case Statement::kBreak_Kind:
-            SkASSERT(index == 0);
-            abort();
-        case Statement::kContinue_Kind:
-            SkASSERT(index == 0);
-            abort();
-        case Statement::kDiscard_Kind:
-            SkASSERT(index == 0);
-            abort();
-        case Statement::kDo_Kind:
-            abort();
-        case Statement::kExpression_Kind:
-            SkASSERT(index == 0);
-            this->evaluate(*((const ExpressionStatement&) stmt).fExpression);
-            break;
-        case Statement::kFor_Kind: {
-            ForStatement& f = (ForStatement&) stmt;
-            switch (index) {
-                case 0:
-                    // initializer
-                    fCurrentIndex.push_back({ &f, 1 });
-                    if (f.fInitializer) {
-                        fCurrentIndex.push_back({ f.fInitializer.get(), 0 });
-                    }
-                    break;
-                case 1:
-                    // test & body
-                    if (f.fTest && !evaluate(*f.fTest).fBool) {
-                        break;
-                    } else {
-                        fCurrentIndex.push_back({ &f, 2 });
-                        fCurrentIndex.push_back({ f.fStatement.get(), 0 });
-                    }
-                    break;
-                case 2:
-                    // next
-                    if (f.fNext) {
-                        this->evaluate(*f.fNext);
-                    }
-                    fCurrentIndex.push_back({ &f, 1 });
-                    break;
-                default:
-                    SkASSERT(false);
-            }
-            break;
-        }
-        case Statement::kGroup_Kind:
-            abort();
-        case Statement::kIf_Kind: {
-            IfStatement& i = (IfStatement&) stmt;
-            if (evaluate(*i.fTest).fBool) {
-                fCurrentIndex.push_back({ i.fIfTrue.get(), 0 });
-            } else if (i.fIfFalse) {
-                fCurrentIndex.push_back({ i.fIfFalse.get(), 0 });
-            }
-            break;
-        }
-        case Statement::kNop_Kind:
-            SkASSERT(index == 0);
-            break;
-        case Statement::kReturn_Kind:
-            SkASSERT(index == 0);
-            abort();
-        case Statement::kSwitch_Kind:
-            abort();
-        case Statement::kVarDeclarations_Kind:
-            SkASSERT(index == 0);
-            for (const auto& decl :((const VarDeclarationsStatement&) stmt).fDeclaration->fVars) {
-                const Variable* var = ((VarDeclaration&) *decl).fVar;
-                StackIndex pos = this->stackAlloc(SizeOf(var->fType));
-                fVars.back()[var] = pos;
-                if (var->fInitialValue) {
-                    fStack[pos] = this->evaluate(*var->fInitialValue);
-                }
-            }
-            break;
-        case Statement::kWhile_Kind:
-            abort();
-        default:
-            abort();
-    }
-}
-
-static Interpreter::TypeKind type_kind(const Type& type) {
-    if (type.fName == "int") {
-        return Interpreter::kInt_TypeKind;
-    } else if (type.fName == "float") {
-        return Interpreter::kFloat_TypeKind;
-    }
-    ABORT("unsupported type: %s\n", type.description().c_str());
-}
-
-Interpreter::StackIndex Interpreter::getLValue(const Expression& expr) {
-    switch (expr.fKind) {
-        case Expression::kFieldAccess_Kind:
-            break;
-        case Expression::kIndex_Kind: {
-            const IndexExpression& idx = (const IndexExpression&) expr;
-            return this->evaluate(*idx.fBase).fInt + this->evaluate(*idx.fIndex).fInt;
-        }
-        case Expression::kSwizzle_Kind:
-            break;
-        case Expression::kVariableReference_Kind:
-            SkASSERT(fVars.size());
-            SkASSERT(fVars.back().find(&((VariableReference&) expr).fVariable) !=
-                   fVars.back().end());
-            return fVars.back()[&((VariableReference&) expr).fVariable];
-        case Expression::kTernary_Kind: {
-            const TernaryExpression& t = (const TernaryExpression&) expr;
-            return this->getLValue(this->evaluate(*t.fTest).fBool ? *t.fIfTrue : *t.fIfFalse);
-        }
-        case Expression::kTypeReference_Kind:
-            break;
-        default:
-            break;
-    }
-    ABORT("unsupported lvalue");
+    return fReturnValue;
 }
 
 struct CallbackCtx : public SkRasterPipeline_CallbackCtx {
@@ -217,254 +70,421 @@
     const FunctionDefinition* fFunction;
 };
 
-static void do_callback(SkRasterPipeline_CallbackCtx* raw, int activePixels) {
-    CallbackCtx& ctx = (CallbackCtx&) *raw;
-    for (int i = 0; i < activePixels; ++i) {
-        ctx.fInterpreter->push(Interpreter::Value(ctx.rgba[i * 4 + 0]));
-        ctx.fInterpreter->push(Interpreter::Value(ctx.rgba[i * 4 + 1]));
-        ctx.fInterpreter->push(Interpreter::Value(ctx.rgba[i * 4 + 2]));
-        ctx.fInterpreter->run(*ctx.fFunction);
-        ctx.read_from[i * 4 + 2] = ctx.fInterpreter->pop().fFloat;
-        ctx.read_from[i * 4 + 1] = ctx.fInterpreter->pop().fFloat;
-        ctx.read_from[i * 4 + 0] = ctx.fInterpreter->pop().fFloat;
+uint8_t Interpreter::read8() {
+    return fCurrentFunction->fCode[fIP++];
+}
+
+uint16_t Interpreter::read16() {
+    uint16_t result = (fCurrentFunction->fCode[fIP ] << 8) +
+                       fCurrentFunction->fCode[fIP + 1];
+    fIP += 2;
+    return result;
+}
+
+uint32_t Interpreter::read32() {
+    uint32_t result = (fCurrentFunction->fCode[fIP]     << 24) +
+                      (fCurrentFunction->fCode[fIP + 1] << 16) +
+                      (fCurrentFunction->fCode[fIP + 2] <<  8) +
+                       fCurrentFunction->fCode[fIP + 3];
+    fIP += 4;
+    return result;
+}
+
+void Interpreter::push(Value v) {
+    fStack.push_back(v);
+}
+
+Interpreter::Value Interpreter::pop() {
+    Value v = fStack.back();
+    fStack.pop_back();
+    return v;
+}
+
+static String value_string(uint32_t v) {
+    union { uint32_t u; float f; } pun = { v };
+    return to_string(v) + "(" + to_string(pun.f) + ")";
+}
+
+void Interpreter::disassemble(const ByteCodeFunction& f) {
+    SkASSERT(fIP == 0);
+    while (fIP < (int) f.fCode.size()) {
+        printf("%d: ", fIP);
+        switch ((ByteCodeInstruction) this->read8()) {
+            case ByteCodeInstruction::kAddF: printf("addf"); break;
+            case ByteCodeInstruction::kAddI: printf("addi"); break;
+            case ByteCodeInstruction::kAndB: printf("andb"); break;
+            case ByteCodeInstruction::kAndI: printf("andi"); break;
+            case ByteCodeInstruction::kBranch: printf("branch %d", this->read16()); break;
+            case ByteCodeInstruction::kCompareIEQ: printf("comparei eq"); break;
+            case ByteCodeInstruction::kCompareINEQ: printf("comparei neq"); break;
+            case ByteCodeInstruction::kCompareFEQ: printf("comparef eq"); break;
+            case ByteCodeInstruction::kCompareFGT: printf("comparef gt"); break;
+            case ByteCodeInstruction::kCompareFGTEQ: printf("comparef gteq"); break;
+            case ByteCodeInstruction::kCompareFLT: printf("comparef lt"); break;
+            case ByteCodeInstruction::kCompareFLTEQ: printf("comparef lteq"); break;
+            case ByteCodeInstruction::kCompareFNEQ: printf("comparef neq"); break;
+            case ByteCodeInstruction::kCompareSGT: printf("compares sgt"); break;
+            case ByteCodeInstruction::kCompareSGTEQ: printf("compares sgteq"); break;
+            case ByteCodeInstruction::kCompareSLT: printf("compares lt"); break;
+            case ByteCodeInstruction::kCompareSLTEQ: printf("compares lteq"); break;
+            case ByteCodeInstruction::kCompareUGT: printf("compareu gt"); break;
+            case ByteCodeInstruction::kCompareUGTEQ: printf("compareu gteq"); break;
+            case ByteCodeInstruction::kCompareULT: printf("compareu lt"); break;
+            case ByteCodeInstruction::kCompareULTEQ: printf("compareu lteq"); break;
+            case ByteCodeInstruction::kConditionalBranch:
+                printf("conditionalbranch %d", this->read16());
+                break;
+            case ByteCodeInstruction::kDebugPrint: printf("debugprint"); break;
+            case ByteCodeInstruction::kDivideF: printf("dividef"); break;
+            case ByteCodeInstruction::kDivideS: printf("divides"); break;
+            case ByteCodeInstruction::kDivideU: printf("divideu"); break;
+            case ByteCodeInstruction::kDup: printf("dup"); break;
+            case ByteCodeInstruction::kDupDown: printf("dupdown %d", this->read8()); break;
+            case ByteCodeInstruction::kFloatToInt: printf("floattoint"); break;
+            case ByteCodeInstruction::kLoad: printf("load"); break;
+            case ByteCodeInstruction::kLoadGlobal: printf("loadglobal"); break;
+            case ByteCodeInstruction::kLoadSwizzle: {
+                int count = this->read8();
+                printf("loadswizzle %d", count);
+                for (int i = 0; i < count; ++i) {
+                    printf(", %d", this->read8());
+                }
+                break;
+            }
+            case ByteCodeInstruction::kMultiplyF: printf("multiplyf"); break;
+            case ByteCodeInstruction::kMultiplyS: printf("multiplys"); break;
+            case ByteCodeInstruction::kMultiplyU: printf("multiplyu"); break;
+            case ByteCodeInstruction::kNegateF: printf("negatef"); break;
+            case ByteCodeInstruction::kNegateS: printf("negates"); break;
+            case ByteCodeInstruction::kNot: printf("not"); break;
+            case ByteCodeInstruction::kOrB: printf("orb"); break;
+            case ByteCodeInstruction::kOrI: printf("ori"); break;
+            case ByteCodeInstruction::kParameter: printf("parameter"); break;
+            case ByteCodeInstruction::kPop: printf("pop %d", this->read8()); break;
+            case ByteCodeInstruction::kPushImmediate:
+                printf("pushimmediate %s", value_string(this->read32()).c_str());
+                break;
+            case ByteCodeInstruction::kRemainderS: printf("remainders"); break;
+            case ByteCodeInstruction::kRemainderU: printf("remainderu"); break;
+            case ByteCodeInstruction::kSignedToFloat: printf("signedtofloat"); break;
+            case ByteCodeInstruction::kStore: printf("store"); break;
+            case ByteCodeInstruction::kStoreSwizzle: {
+                int count = this->read8();
+                printf("storeswizzle %d", count);
+                for (int i = 0; i < count; ++i) {
+                    printf(", %d", this->read8());
+                }
+                break;
+            }
+            case ByteCodeInstruction::kSubtractF: printf("subtractf"); break;
+            case ByteCodeInstruction::kSubtractI: printf("subtracti"); break;
+            case ByteCodeInstruction::kSwizzle: {
+                printf("swizzle %d, ", this->read8());
+                int count = this->read8();
+                printf("%d", count);
+                for (int i = 0; i < count; ++i) {
+                    printf(", %d", this->read8());
+                }
+                break;
+            }
+            case ByteCodeInstruction::kUnsignedToFloat: printf("unsignedtofloat"); break;
+            case ByteCodeInstruction::kVector: printf("vector%d", this->read8()); break;
+            default: SkASSERT(false);
+        }
+        printf("\n");
     }
+    fIP = 0;
 }
 
-void Interpreter::appendStage(const AppendStage& a) {
-    switch (a.fStage) {
-        case SkRasterPipeline::matrix_4x5: {
-            SkASSERT(a.fArguments.size() == 1);
-            StackIndex transpose = evaluate(*a.fArguments[0]).fInt;
-            fPipeline.append(SkRasterPipeline::matrix_4x5, &fStack[transpose]);
-            break;
-        }
-        case SkRasterPipeline::callback: {
-            SkASSERT(a.fArguments.size() == 1);
-            CallbackCtx* ctx = new CallbackCtx();
-            ctx->fInterpreter = this;
-            ctx->fn = do_callback;
-            for (const auto& e : *fProgram) {
-                if (ProgramElement::kFunction_Kind == e.fKind) {
-                    const FunctionDefinition& f = (const FunctionDefinition&) e;
-                    if (&f.fDeclaration ==
-                                      ((const FunctionReference&) *a.fArguments[0]).fFunctions[0]) {
-                        ctx->fFunction = &f;
-                    }
-                }
-            }
-            fPipeline.append(SkRasterPipeline::callback, ctx);
-            break;
-        }
-        default:
-            fPipeline.append(a.fStage);
+void Interpreter::dumpStack() {
+    printf("STACK:");
+    for (size_t i = 0; i < fStack.size(); ++i) {
+        printf(" %d(%f)", fStack[i].fSigned, fStack[i].fFloat);
     }
+    printf("\n");
 }
 
-Interpreter::Value Interpreter::call(const FunctionCall& c) {
-    abort();
-}
+#define BINARY_OP(inst, type, field, op) \
+    case ByteCodeInstruction::inst: {    \
+        type b = this->pop().field;      \
+        type a = this->pop().field;      \
+        this->push(Value(a op b));       \
+        break;                           \
+    }
 
-Interpreter::Value Interpreter::evaluate(const Expression& expr) {
-    switch (expr.fKind) {
-        case Expression::kAppendStage_Kind:
-            this->appendStage((const AppendStage&) expr);
-            return Value((int) 0xDEADBEEF);
-        case Expression::kBinary_Kind: {
-            #define ARITHMETIC(op) {                               \
-                Value left = this->evaluate(*b.fLeft);             \
-                Value right = this->evaluate(*b.fRight);           \
-                switch (type_kind(b.fLeft->fType)) {               \
-                    case kFloat_TypeKind:                          \
-                        return Value(left.fFloat op right.fFloat); \
-                    case kInt_TypeKind:                            \
-                        return Value(left.fInt op right.fInt);     \
-                    default:                                       \
-                        abort();                                   \
-                }                                                  \
-            }
-            #define BITWISE(op) {                                  \
-                Value left = this->evaluate(*b.fLeft);             \
-                Value right = this->evaluate(*b.fRight);           \
-                switch (type_kind(b.fLeft->fType)) {               \
-                    case kInt_TypeKind:                            \
-                        return Value(left.fInt op right.fInt);     \
-                    default:                                       \
-                        abort();                                   \
-                }                                                  \
-            }
-            #define LOGIC(op) {                                    \
-                Value left = this->evaluate(*b.fLeft);             \
-                Value right = this->evaluate(*b.fRight);           \
-                switch (type_kind(b.fLeft->fType)) {               \
-                    case kFloat_TypeKind:                          \
-                        return Value(left.fFloat op right.fFloat); \
-                    case kInt_TypeKind:                            \
-                        return Value(left.fInt op right.fInt);     \
-                    default:                                       \
-                        abort();                                   \
-                }                                                  \
-            }
-            #define COMPOUND_ARITHMETIC(op) {                      \
-                StackIndex left = this->getLValue(*b.fLeft);       \
-                Value right = this->evaluate(*b.fRight);           \
-                Value result = fStack[left];                       \
-                switch (type_kind(b.fLeft->fType)) {               \
-                    case kFloat_TypeKind:                          \
-                        result.fFloat op right.fFloat;             \
-                        break;                                     \
-                    case kInt_TypeKind:                            \
-                        result.fInt op right.fInt;                 \
-                        break;                                     \
-                    default:                                       \
-                        abort();                                   \
-                }                                                  \
-                fStack[left] = result;                             \
-                return result;                                     \
-            }
-            #define COMPOUND_BITWISE(op) {                         \
-                StackIndex left = this->getLValue(*b.fLeft);       \
-                Value right = this->evaluate(*b.fRight);           \
-                Value result = fStack[left];                       \
-                switch (type_kind(b.fLeft->fType)) {               \
-                    case kInt_TypeKind:                            \
-                        result.fInt op right.fInt;                 \
-                        break;                                     \
-                    default:                                       \
-                        abort();                                   \
-                }                                                  \
-                fStack[left] = result;                             \
-                return result;                                     \
-            }
-            const BinaryExpression& b = (const BinaryExpression&) expr;
-            switch (b.fOperator) {
-                case Token::PLUS:       ARITHMETIC(+)
-                case Token::MINUS:      ARITHMETIC(-)
-                case Token::STAR:       ARITHMETIC(*)
-                case Token::SLASH:      ARITHMETIC(/)
-                case Token::BITWISEAND: BITWISE(&)
-                case Token::BITWISEOR:  BITWISE(|)
-                case Token::BITWISEXOR: BITWISE(^)
-                case Token::LT:         LOGIC(<)
-                case Token::GT:         LOGIC(>)
-                case Token::LTEQ:       LOGIC(<=)
-                case Token::GTEQ:       LOGIC(>=)
-                case Token::LOGICALAND: {
-                    Value result = this->evaluate(*b.fLeft);
-                    if (result.fBool) {
-                        result = this->evaluate(*b.fRight);
-                    }
-                    return result;
-                }
-                case Token::LOGICALOR: {
-                    Value result = this->evaluate(*b.fLeft);
-                    if (!result.fBool) {
-                        result = this->evaluate(*b.fRight);
-                    }
-                    return result;
-                }
-                case Token::EQ: {
-                    StackIndex left = this->getLValue(*b.fLeft);
-                    Value right = this->evaluate(*b.fRight);
-                    fStack[left] = right;
-                    return right;
-                }
-                case Token::PLUSEQ:       COMPOUND_ARITHMETIC(+=)
-                case Token::MINUSEQ:      COMPOUND_ARITHMETIC(-=)
-                case Token::STAREQ:       COMPOUND_ARITHMETIC(*=)
-                case Token::SLASHEQ:      COMPOUND_ARITHMETIC(/=)
-                case Token::BITWISEANDEQ: COMPOUND_BITWISE(&=)
-                case Token::BITWISEOREQ:  COMPOUND_BITWISE(|=)
-                case Token::BITWISEXOREQ: COMPOUND_BITWISE(^=)
-                default:
-                    ABORT("unsupported operator: %s\n", expr.description().c_str());
+void Interpreter::next() {
+#ifdef TRACE
+    printf("at %d\n", fIP);
+#endif
+    ByteCodeInstruction inst = (ByteCodeInstruction) this->read8();
+    switch (inst) {
+        BINARY_OP(kAddI, int32_t, fSigned, +)
+        BINARY_OP(kAddF, float, fFloat, +)
+        case ByteCodeInstruction::kBranch:
+            fIP = this->read16();
+            break;
+        BINARY_OP(kCompareIEQ, int32_t, fSigned, ==)
+        BINARY_OP(kCompareFEQ, float, fFloat, ==)
+        BINARY_OP(kCompareINEQ, int32_t, fSigned, !=)
+        BINARY_OP(kCompareFNEQ, float, fFloat, !=)
+        BINARY_OP(kCompareSGT, int32_t, fSigned, >)
+        BINARY_OP(kCompareUGT, uint32_t, fUnsigned, >)
+        BINARY_OP(kCompareFGT, float, fFloat, >)
+        BINARY_OP(kCompareSGTEQ, int32_t, fSigned, >=)
+        BINARY_OP(kCompareUGTEQ, uint32_t, fUnsigned, >=)
+        BINARY_OP(kCompareFGTEQ, float, fFloat, >=)
+        BINARY_OP(kCompareSLT, int32_t, fSigned, <)
+        BINARY_OP(kCompareULT, uint32_t, fUnsigned, <)
+        BINARY_OP(kCompareFLT, float, fFloat, <)
+        BINARY_OP(kCompareSLTEQ, int32_t, fSigned, <=)
+        BINARY_OP(kCompareULTEQ, uint32_t, fUnsigned, <=)
+        BINARY_OP(kCompareFLTEQ, float, fFloat, <=)
+        case ByteCodeInstruction::kConditionalBranch: {
+            int target = this->read16();
+            if (this->pop().fBool) {
+                fIP = target;
             }
             break;
         }
-        case Expression::kBoolLiteral_Kind:
-            return Value(((const BoolLiteral&) expr).fValue);
-        case Expression::kConstructor_Kind:
+        case ByteCodeInstruction::kDebugPrint: {
+            Value v = this->pop();
+            printf("Debug: %d(int), %d(uint), %f(float)\n", v.fSigned, v.fUnsigned, v.fFloat);
             break;
-        case Expression::kIntLiteral_Kind:
-            return Value((int) ((const IntLiteral&) expr).fValue);
-        case Expression::kFieldAccess_Kind:
-            break;
-        case Expression::kFloatLiteral_Kind:
-            return Value((float) ((const FloatLiteral&) expr).fValue);
-        case Expression::kFunctionCall_Kind:
-            return this->call((const FunctionCall&) expr);
-        case Expression::kIndex_Kind: {
-            const IndexExpression& idx = (const IndexExpression&) expr;
-            StackIndex pos = this->evaluate(*idx.fBase).fInt +
-                             this->evaluate(*idx.fIndex).fInt;
-            return fStack[pos];
         }
-        case Expression::kPrefix_Kind: {
-            const PrefixExpression& p = (const PrefixExpression&) expr;
-            switch (p.fOperator) {
-                case Token::MINUS: {
-                    Value base = this->evaluate(*p.fOperand);
-                    switch (type_kind(p.fType)) {
-                        case kFloat_TypeKind:
-                            return Value(-base.fFloat);
-                        case kInt_TypeKind:
-                            return Value(-base.fInt);
-                        default:
-                            abort();
-                    }
-                }
-                case Token::LOGICALNOT: {
-                    Value base = this->evaluate(*p.fOperand);
-                    return Value(!base.fBool);
-                }
-                default:
-                    abort();
+        BINARY_OP(kDivideS, int32_t, fSigned, /)
+        BINARY_OP(kDivideU, uint32_t, fUnsigned, /)
+        BINARY_OP(kDivideF, float, fFloat, /)
+        case ByteCodeInstruction::kDup:
+            this->push(fStack.back());
+            break;
+        case ByteCodeInstruction::kDupDown: {
+            int count = this->read8();
+            for (int i = 0; i < count; ++i) {
+                fStack.insert(fStack.end() - i - count - 1, fStack[fStack.size() - i - 1]);
             }
+            break;
         }
-        case Expression::kPostfix_Kind: {
-            const PostfixExpression& p = (const PostfixExpression&) expr;
-            StackIndex lvalue = this->getLValue(*p.fOperand);
-            Value result = fStack[lvalue];
-            switch (type_kind(p.fType)) {
-                case kFloat_TypeKind:
-                    if (Token::PLUSPLUS == p.fOperator) {
-                        ++fStack[lvalue].fFloat;
-                    } else {
-                        SkASSERT(Token::MINUSMINUS == p.fOperator);
-                        --fStack[lvalue].fFloat;
-                    }
-                    break;
-                case kInt_TypeKind:
-                    if (Token::PLUSPLUS == p.fOperator) {
-                        ++fStack[lvalue].fInt;
-                    } else {
-                        SkASSERT(Token::MINUSMINUS == p.fOperator);
-                        --fStack[lvalue].fInt;
-                    }
-                    break;
-                default:
-                    abort();
+        case ByteCodeInstruction::kFloatToInt: {
+            Value& top = fStack.back();
+            top.fSigned = (int) top.fFloat;
+            break;
+        }
+        case ByteCodeInstruction::kSignedToFloat: {
+            Value& top = fStack.back();
+            top.fFloat = (float) top.fSigned;
+            break;
+        }
+        case ByteCodeInstruction::kUnsignedToFloat: {
+            Value& top = fStack.back();
+            top.fFloat = (float) top.fUnsigned;
+            break;
+        }
+        case ByteCodeInstruction::kLoad: {
+            int target = this->pop().fSigned;
+            SkASSERT(target < (int) fStack.size());
+            this->push(fStack[target]);
+            break;
+        }
+        case ByteCodeInstruction::kLoadGlobal: {
+            int target = this->read8();
+            SkASSERT(target < (int) fGlobals.size());
+            this->push(fGlobals[target]);
+            break;
+        }
+        case ByteCodeInstruction::kLoadSwizzle: {
+            Value target = this->pop();
+            int count = read8();
+            for (int i = 0; i < count; ++i) {
+                SkASSERT(target.fSigned + fCurrentFunction->fCode[fIP + i] < (int) fStack.size());
+                this->push(fStack[target.fSigned + fCurrentFunction->fCode[fIP + i]]);
             }
-            return result;
-        }
-        case Expression::kSetting_Kind:
+            fIP += count;
             break;
-        case Expression::kSwizzle_Kind:
-            break;
-        case Expression::kVariableReference_Kind:
-            SkASSERT(fVars.size());
-            SkASSERT(fVars.back().find(&((VariableReference&) expr).fVariable) !=
-                   fVars.back().end());
-            return fStack[fVars.back()[&((VariableReference&) expr).fVariable]];
-        case Expression::kTernary_Kind: {
-            const TernaryExpression& t = (const TernaryExpression&) expr;
-            return this->evaluate(this->evaluate(*t.fTest).fBool ? *t.fIfTrue : *t.fIfFalse);
         }
-        case Expression::kTypeReference_Kind:
+        BINARY_OP(kMultiplyS, int32_t, fSigned, *)
+        BINARY_OP(kMultiplyU, uint32_t, fUnsigned, *)
+        BINARY_OP(kMultiplyF, float, fFloat, *)
+        case ByteCodeInstruction::kNot: {
+            Value& top = fStack.back();
+            top.fBool = !top.fBool;
+            break;
+        }
+        case ByteCodeInstruction::kNegateF:
+            this->push(-this->pop().fFloat);
+        case ByteCodeInstruction::kNegateS:
+            this->push(-this->pop().fSigned);
+        case ByteCodeInstruction::kPop:
+            for (int i = read8(); i > 0; --i) {
+                this->pop();
+            }
+            break;
+        case ByteCodeInstruction::kPushImmediate:
+            this->push(Value((int) read32()));
+            break;
+        BINARY_OP(kRemainderS, int32_t, fSigned, %)
+        BINARY_OP(kRemainderU, uint32_t, fUnsigned, %)
+        case ByteCodeInstruction::kStore: {
+            Value value = this->pop();
+            int target = this->pop().fSigned;
+            SkASSERT(target < (int) fStack.size());
+            fStack[target] = value;
+            break;
+        }
+        case ByteCodeInstruction::kStoreGlobal: {
+            Value value = this->pop();
+            int target = this->pop().fSigned;
+            SkASSERT(target < (int) fGlobals.size());
+            fGlobals[target] = value;
+            break;
+        }
+        case ByteCodeInstruction::kStoreSwizzle: {
+            int count = read8();
+            int target = fStack[fStack.size() - count - 1].fSigned;
+            for (int i = count - 1; i >= 0; --i) {
+                SkASSERT(target + fCurrentFunction->fCode[fIP + i] < (int) fStack.size());
+                fStack[target + fCurrentFunction->fCode[fIP + i]] = this->pop();
+            }
+            this->pop();
+            fIP += count;
+            break;
+        }
+        BINARY_OP(kSubtractI, int32_t, fSigned, -)
+        BINARY_OP(kSubtractF, float, fFloat, -)
+        case ByteCodeInstruction::kSwizzle: {
+            Value vec[4];
+            for (int i = this->read8() - 1; i >= 0; --i) {
+                vec[i] = this->pop();
+            }
+            for (int i = this->read8() - 1; i >= 0; --i) {
+                this->push(vec[this->read8()]);
+            }
+            break;
+        }
+        case ByteCodeInstruction::kVector:
+            this->nextVector(this->read8());
             break;
         default:
-            break;
+            printf("unsupported instruction %d\n", (int) inst);
+            SkASSERT(false);
     }
-    ABORT("unsupported expression: %s\n", expr.description().c_str());
+#ifdef TRACE
+    this->dumpStack();
+#endif
+}
+
+static constexpr int VECTOR_MAX = 16;
+
+#define VECTOR_BINARY_OP(inst, type, field, op)               \
+    case ByteCodeInstruction::inst: {                         \
+        Value result[VECTOR_MAX];                             \
+        for (int i = count - 1; i >= 0; --i) {                \
+            result[i] = this->pop();                          \
+        }                                                     \
+        for (int i = count - 1; i >= 0; --i) {                \
+            result[i] = this->pop().field op result[i].field; \
+        }                                                     \
+        for (int i = 0; i < count; ++i) {                     \
+            this->push(result[i]);                            \
+        }                                                     \
+        break;                                                \
+    }
+
+void Interpreter::nextVector(int count) {
+    ByteCodeInstruction inst = (ByteCodeInstruction) this->read8();
+    switch (inst) {
+        VECTOR_BINARY_OP(kAddI, int32_t, fSigned, +)
+        VECTOR_BINARY_OP(kAddF, float, fFloat, +)
+        case ByteCodeInstruction::kBranch:
+            fIP = this->read16();
+            break;
+        VECTOR_BINARY_OP(kCompareIEQ, int32_t, fSigned, ==)
+        VECTOR_BINARY_OP(kCompareFEQ, float, fFloat, ==)
+        VECTOR_BINARY_OP(kCompareINEQ, int32_t, fSigned, !=)
+        VECTOR_BINARY_OP(kCompareFNEQ, float, fFloat, !=)
+        VECTOR_BINARY_OP(kCompareSGT, int32_t, fSigned, >)
+        VECTOR_BINARY_OP(kCompareUGT, uint32_t, fUnsigned, >)
+        VECTOR_BINARY_OP(kCompareFGT, float, fFloat, >)
+        VECTOR_BINARY_OP(kCompareSGTEQ, int32_t, fSigned, >=)
+        VECTOR_BINARY_OP(kCompareUGTEQ, uint32_t, fUnsigned, >=)
+        VECTOR_BINARY_OP(kCompareFGTEQ, float, fFloat, >=)
+        VECTOR_BINARY_OP(kCompareSLT, int32_t, fSigned, <)
+        VECTOR_BINARY_OP(kCompareULT, uint32_t, fUnsigned, <)
+        VECTOR_BINARY_OP(kCompareFLT, float, fFloat, <)
+        VECTOR_BINARY_OP(kCompareSLTEQ, int32_t, fSigned, <=)
+        VECTOR_BINARY_OP(kCompareULTEQ, uint32_t, fUnsigned, <=)
+        VECTOR_BINARY_OP(kCompareFLTEQ, float, fFloat, <=)
+        case ByteCodeInstruction::kConditionalBranch: {
+            int target = this->read16();
+            if (this->pop().fBool) {
+                fIP = target;
+            }
+            break;
+        }
+        VECTOR_BINARY_OP(kDivideS, int32_t, fSigned, /)
+        VECTOR_BINARY_OP(kDivideU, uint32_t, fUnsigned, /)
+        VECTOR_BINARY_OP(kDivideF, float, fFloat, /)
+        case ByteCodeInstruction::kFloatToInt: {
+            for (int i = 0; i < count; ++i) {
+                Value& v = fStack[fStack.size() - i - 1];
+                v.fSigned = (int) v.fFloat;
+            }
+            break;
+        }
+        case ByteCodeInstruction::kSignedToFloat: {
+            for (int i = 0; i < count; ++i) {
+                Value& v = fStack[fStack.size() - i - 1];
+                v.fFloat = (float) v.fSigned;
+            }
+            break;
+        }
+        case ByteCodeInstruction::kUnsignedToFloat: {
+            for (int i = 0; i < count; ++i) {
+                Value& v = fStack[fStack.size() - i - 1];
+                v.fFloat = (float) v.fUnsigned;
+            }
+            break;
+        }
+        case ByteCodeInstruction::kLoad: {
+            int target = this->pop().fSigned;
+            for (int i = 0; i < count; ++i) {
+                SkASSERT(target < (int) fStack.size());
+                this->push(fStack[target++]);
+            }
+            break;
+        }
+        case ByteCodeInstruction::kLoadGlobal: {
+            int target = this->read8();
+            SkASSERT(target < (int) fGlobals.size());
+            this->push(fGlobals[target]);
+            break;
+        }
+        VECTOR_BINARY_OP(kMultiplyS, int32_t, fSigned, *)
+        VECTOR_BINARY_OP(kMultiplyU, uint32_t, fUnsigned, *)
+        VECTOR_BINARY_OP(kMultiplyF, float, fFloat, *)
+        VECTOR_BINARY_OP(kRemainderS, int32_t, fSigned, %)
+        VECTOR_BINARY_OP(kRemainderU, uint32_t, fUnsigned, %)
+        case ByteCodeInstruction::kStore: {
+            int target = fStack[fStack.size() - count - 1].fSigned + count;
+            for (int i = count - 1; i >= 0; --i) {
+                SkASSERT(target < (int) fStack.size());
+                fStack[--target] = this->pop();
+            }
+            break;
+        }
+        VECTOR_BINARY_OP(kSubtractI, int32_t, fSigned, -)
+        VECTOR_BINARY_OP(kSubtractF, float, fFloat, -)
+        case ByteCodeInstruction::kVector:
+            this->nextVector(this->read8());
+        default:
+            printf("unsupported instruction %d\n", (int) inst);
+            SkASSERT(false);
+    }
+}
+
+void Interpreter::run() {
+    while (fIP < (int) fCurrentFunction->fCode.size()) {
+        next();
+    }
 }
 
 } // namespace
diff --git a/src/sksl/SkSLInterpreter.h b/src/sksl/SkSLInterpreter.h
index 449e380..018768a 100644
--- a/src/sksl/SkSLInterpreter.h
+++ b/src/sksl/SkSLInterpreter.h
@@ -8,6 +8,7 @@
 #ifndef SKSL_INTERPRETER
 #define SKSL_INTERPRETER
 
+#include "SkSLByteCode.h"
 #include "ir/SkSLAppendStage.h"
 #include "ir/SkSLExpression.h"
 #include "ir/SkSLFunctionCall.h"
@@ -17,31 +18,30 @@
 
 #include <stack>
 
-class SkRasterPipeline;
-
 namespace SkSL {
 
 class Interpreter {
     typedef int StackIndex;
 
-    struct StatementIndex {
-        const Statement* fStatement;
-        size_t fIndex;
-    };
-
 public:
     union Value {
+        Value() {}
+
         Value(float f)
         : fFloat(f) {}
 
-        Value(int i)
-        : fInt(i) {}
+        Value(int32_t s)
+        : fSigned(s) {}
+
+        Value(uint32_t u)
+        : fUnsigned(u) {}
 
         Value(bool b)
         : fBool(b) {}
 
         float fFloat;
-        int fInt;
+        int32_t fSigned;
+        uint32_t fUnsigned;
         bool fBool;
     };
 
@@ -51,37 +51,49 @@
         kBool_TypeKind
     };
 
-    Interpreter(std::unique_ptr<Program> program, SkRasterPipeline* pipeline, std::vector<Value>* stack)
+    Interpreter(std::unique_ptr<Program> program, std::unique_ptr<ByteCode> byteCode)
     : fProgram(std::move(program))
-    , fPipeline(*pipeline)
-    , fStack(*stack) {}
+    , fByteCode(std::move(byteCode))
+    , fReturnValue(0) {}
+
+    /**
+     * Invokes the specified function with the given arguments, returning its return value. 'out'
+     * and 'inout' parameters will result in the 'args' array being modified.
+     */
+    Value run(const ByteCodeFunction& f, Value args[], Value inputs[]);
+
+private:
+    StackIndex stackAlloc(int count);
+
+    uint8_t read8();
+
+    uint16_t read16();
+
+    uint32_t read32();
+
+    void next();
+
+    void nextVector(int count);
 
     void run();
 
-    void run(const FunctionDefinition& f);
-
-    void push(Value value);
+    void push(Value v);
 
     Value pop();
 
-    StackIndex stackAlloc(int count);
+    void swizzle();
 
-    void runStatement();
+    void disassemble(const ByteCodeFunction& f);
 
-    StackIndex getLValue(const Expression& expr);
+    void dumpStack();
 
-    Value call(const FunctionCall& c);
-
-    void appendStage(const AppendStage& c);
-
-    Value evaluate(const Expression& expr);
-
-private:
     std::unique_ptr<Program> fProgram;
-    SkRasterPipeline& fPipeline;
-    std::vector<StatementIndex> fCurrentIndex;
-    std::vector<std::unordered_map<const Variable*, StackIndex>> fVars;
-    std::vector<Value> &fStack;
+    std::unique_ptr<ByteCode> fByteCode;
+    int fIP;
+    const ByteCodeFunction* fCurrentFunction;
+    std::vector<Value> fGlobals;
+    std::vector<Value> fStack;
+    Value fReturnValue;
 };
 
 } // namespace
diff --git a/src/sksl/SkSLLexer.cpp b/src/sksl/SkSLLexer.cpp
index 2b90582..74dfbc4 100644
--- a/src/sksl/SkSLLexer.cpp
+++ b/src/sksl/SkSLLexer.cpp
@@ -11,1045 +11,994 @@
 
 namespace SkSL {
 
+static const uint8_t INVALID_CHAR = 18;
 static int8_t mappings[127] = {
         0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  2,  3,  3,  1,  3,  3,  3,  3,  3,  3,  3,  3,
         3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  1,  4,  3,  5,  6,  7,  8,  3,  9,  10, 11, 12,
         13, 14, 15, 16, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 20, 21, 22, 23, 24, 25, 26,
         26, 26, 26, 27, 26, 6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  28, 6,  6,  6,
-        29, 6,  6,  30, 3,  31, 32, 33, 3,  34, 35, 36, 37, 38, 39, 40, 41, 42, 6,  43, 44, 45,
-        46, 47, 48, 6,  49, 50, 51, 52, 53, 54, 55, 56, 6,  57, 58, 59, 60};
-static int16_t transitions[61][316] = {
+        29, 6,  6,  30, 3,  31, 32, 33, 3,  34, 35, 36, 37, 38, 39, 6,  40, 41, 6,  42, 43, 44,
+        45, 46, 47, 6,  48, 49, 50, 51, 52, 53, 54, 55, 6,  56, 57, 58, 59};
+static int16_t transitions[60][304] = {
         {
-                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, 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, 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,
-                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, 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, 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,
-                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, 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, 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, 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, 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, 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, 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, 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, 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,
+                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, 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, 0, 0, 0, 0, 0, 0, 0, 0,
         },
         {
-                0, 2, 3, 3, 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, 35, 35, 0, 38, 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, 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, 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,  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, 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, 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,  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, 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, 2, 3, 3, 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, 35, 35, 0, 38, 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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,
         },
         {
-                0, 3, 3, 3, 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, 35, 35, 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, 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, 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,  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, 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, 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,  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, 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, 3, 3, 3, 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, 35, 35, 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, 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, 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, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 0,  0,  0, 0, 0, 0, 0, 0, 0,
         },
         {
-                0, 4, 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, 35, 35, 0, 38, 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, 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, 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,  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, 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, 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,  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, 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, 4, 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, 35, 35, 0, 38, 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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,
         },
         {
-                0, 5, 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, 35, 35, 0, 38, 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, 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, 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,  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, 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, 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,  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, 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, 5, 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, 35, 35, 0, 38, 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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,
         },
         {
-                0, 7, 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, 35, 35, 0, 38, 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, 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, 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,  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, 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, 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,  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, 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, 7, 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, 35, 35, 0, 38, 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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,
         },
         {
-                0,  9,  0,  0,  0,  0,  0,  8,  8,  10, 10, 0,  0,  0,  0,  0,  0,  0,  0,  0,
-                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  35, 35, 0,  38, 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,  71, 71, 71, 71, 71, 71, 71, 71, 71, 71,
-                0,  0,  0,  0,  0,  0,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 0,  0,  0,  0,  0,  0,  0,
+                0,  9,  0,  0,  0,  0,  0,  8,  8,  10, 10, 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  35, 35, 0,  38, 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,  71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 0,  0,  0,  0,
+                0,  0,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 0,  0,  0,  0,  0,  0,  0,
         },
         {
-                0, 11, 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, 35, 35, 0, 38, 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, 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, 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,  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, 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, 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,  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, 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, 11, 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, 35, 35, 0, 38, 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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,
         },
         {
-                0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0,  0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 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,
-                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,  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, 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, 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, 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, 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, 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, 13, 0, 0, 0, 0, 0, 0, 0, 0,  0,  0, 0,  14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                0, 0,  0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0, 0,  0,  0, 0,  0,  0, 0, 0, 0,
         },
         {
-                0, 17, 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, 35, 35, 0, 38, 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, 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, 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,  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, 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, 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,  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, 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, 17, 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, 35, 35, 0, 38, 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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,
         },
         {
-                0, 18, 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, 35, 35, 0, 38, 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, 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, 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,  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, 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, 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,  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, 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, 18, 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, 35, 35, 0, 38, 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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,
         },
         {
-                0, 19, 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, 35, 36, 35, 0, 38, 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,
-                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,  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, 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, 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, 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, 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, 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, 19, 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, 35, 36, 35, 0, 38, 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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0,  0,  0,  0, 0,  0, 0, 0, 0, 0,
         },
         {
-                0,  21, 0, 0, 0, 0, 0,  0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,  0, 0, 22, 0, 0, 0,
-                0,  0,  0, 0, 0, 0, 32, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 44, 0, 0, 47, 0, 0, 0,
-                51, 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,  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, 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, 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, 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, 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, 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, 0, 0, 0, 0,  0, 0, 0, 0,  0,  0, 0,  0, 0,
+                0, 21, 0,  0, 0, 0, 0, 0,  0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,  0, 22, 0,  0,
+                0, 0,  0,  0, 0, 0, 0, 32, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 44, 0, 0,  47, 0,
+                0, 0,  51, 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,  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,  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,  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,  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,  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,  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,  0,
         },
         {
-                0, 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, 35, 35, 0, 38, 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, 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, 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,  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, 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, 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,  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, 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, 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, 35, 35, 0, 38, 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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,
         },
         {
-                0,  25, 0, 0, 0, 0, 0,  0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,  0, 0, 0,  0, 0, 0,
-                26, 0,  0, 0, 0, 0, 32, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 44, 0, 0, 47, 0, 0, 0,
-                51, 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,  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, 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, 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, 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, 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, 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, 0, 0, 0, 0,  0, 0, 0, 0,  0,  0, 0,  0, 0,
+                0, 25, 0,  0, 0, 0, 0, 0,  0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,  0, 0, 0,  0,
+                0, 26, 0,  0, 0, 0, 0, 32, 0, 0, 0, 35, 35, 0, 38, 0, 0, 0, 0, 44, 0, 0, 47, 0,
+                0, 0,  51, 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,  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,  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,  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,  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,  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,  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,  0,
         },
         {
-                0, 29, 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, 35, 35, 0, 38, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 41,
-                0, 0,  0, 0, 0, 41, 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, 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, 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,  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,
-                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,  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, 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, 0, 0, 0,  0,  0, 0,  0, 0,
+                0, 29, 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, 35, 35, 0, 38, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0,
+                0, 0,  0, 41, 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, 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,  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, 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, 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,  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, 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, 34, 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, 38, 35, 37, 0, 38, 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,
-                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,  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, 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, 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, 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, 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, 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, 34, 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, 38, 35, 37, 0, 38, 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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0,  0,  0,  0, 0,  0, 0, 0, 0, 0,
         },
         {
-                0,  40, 0,  0,  0,  0,  0,  0,  8,  10, 10, 0,  0,  0,  0,  0,  0,  0,  0,  0,
-                0,  0,  0,  0,  0,  0,  0,  0,  0,  30, 30, 33, 33, 33, 0,  35, 35, 0,  38, 0,
-                49, 42, 42, 45, 45, 45, 48, 48, 48, 49, 52, 52, 52, 54, 54, 49, 0,  0,  0,  0,
-                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  71, 71, 71, 71, 71, 71, 71, 71, 71,
-                0,  0,  0,  0,  0,  0,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 0,  0,  0,  0,  0,  0,  0,
+                0,  40, 0,  0,  0,  0,  0,  0,  8,  10, 10, 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  30, 30, 33, 33, 33, 0,  35, 35, 0,  38, 0,  49, 42,
+                42, 45, 45, 45, 48, 48, 48, 49, 52, 52, 52, 54, 54, 49, 0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  71, 71, 71, 71, 71, 71, 71, 71, 71, 0,  0,  0,  0,
+                0,  0,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 0,  0,  0,  0,  0,  0,  0,
         },
         {
-                0,  55, 0,  0,  0,  0,  0,  0,  8,  10, 10, 0,  0,  0,  0,  0,  0,  0,  0,  0,
-                0,  0,  0,  0,  0,  0,  0,  0,  0,  30, 30, 33, 33, 33, 0,  35, 35, 0,  38, 0,
-                49, 42, 42, 45, 45, 45, 48, 48, 48, 49, 52, 52, 52, 54, 54, 49, 0,  0,  0,  0,
-                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  71, 71, 71, 71, 71, 71, 71, 71, 71,
-                0,  0,  0,  0,  0,  0,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 0,  0,  0,  0,  0,  0,  0,
+                0,  55, 0,  0,  0,  0,  0,  0,  8,  10, 10, 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  30, 30, 33, 33, 33, 0,  35, 35, 0,  38, 0,  49, 42,
+                42, 45, 45, 45, 48, 48, 48, 49, 52, 52, 52, 54, 54, 49, 0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  71, 71, 71, 71, 71, 71, 71, 71, 71, 0,  0,  0,  0,
+                0,  0,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 0,  0,  0,  0,  0,  0,  0,
         },
         {
-                0, 56, 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, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                0, 0,  0, 0, 0, 0, 57, 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, 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, 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, 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, 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,  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, 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, 0, 0,  0,  0, 0,  0, 0,
+                0, 56, 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, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                0, 0,  0, 0, 57, 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, 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, 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, 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, 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, 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,  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, 58, 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, 35, 35, 0, 38, 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, 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, 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,  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, 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, 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,  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, 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, 58, 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, 35, 35, 0, 38, 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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,
         },
         {
-                0, 59, 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,  35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                0, 0,  0, 0, 0, 0, 0, 0, 0, 60, 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, 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, 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, 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, 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, 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, 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,  0, 0,  0, 0,
+                0, 59, 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, 35, 35, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                0, 0,  0, 0, 0, 0, 0, 60, 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, 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,  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, 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, 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, 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, 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, 63, 0,  0, 0, 6, 0, 0, 0, 0, 0, 12,  0,  16,  15, 0,  0, 0,  0, 20, 0, 23, 0,
-                0, 0,  27, 0, 0, 0, 0, 0, 0, 0, 0, 39,  35, 35,  0,  38, 0, 0,  0, 0,  0, 0,  0,
-                0, 0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,   0,  62,  61, 0,  0, 64, 0, 66, 0, 68, 0,
-                0, 0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,   0,  83,  0,  85, 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, 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, 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, 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,  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,   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,  0,  0, 0, 0, 0, 0, 0, 0, 0, 311, 0,  313, 0,  0,  0,
+                0, 63, 0, 0, 0, 6, 0, 0, 0, 0, 0,   12, 0,   16, 15, 0,  0, 0,  0, 20, 0, 23, 0, 0,
+                0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 39,  35, 35,  0,  38, 0,  0, 0,  0, 0,  0, 0,  0, 0,
+                0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0,   62, 61,  0,  0,  64, 0, 66, 0, 68, 0, 0,  0, 0,
+                0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 83,  0,  85,  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, 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,   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, 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,   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, 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, 299, 0,  301, 0,  0,  0,
         },
         {
-                0,  65, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0,  0, 0,  0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0,
-                28, 0,  0, 0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 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, 67, 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,  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, 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, 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, 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, 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, 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, 65, 0, 0, 0, 0, 0, 0, 0, 0,  0,  0, 0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28,
+                0, 0,  0, 0, 0, 0, 0, 0, 0, 35, 35, 0, 38, 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,  67, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0,
+                0, 0,  0, 0, 0, 0, 0, 0, 0, 0,  0,  0, 0,  0,  0, 0, 0, 0,
         },
         {
-                0, 69, 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, 35, 35, 0, 38, 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, 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, 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,  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, 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, 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,  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, 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, 69, 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, 35, 35, 0, 38, 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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,
         },
         {
-                0, 70, 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, 35, 35, 0, 38, 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, 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, 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,  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, 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, 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,  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, 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, 70, 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, 35, 35, 0, 38, 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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,
         },
         {
-                0,  9,  0,  0,  0,  0,  0,  8,  8,  10, 10, 0,  0,  0,  0,  0,  0,  0,  0,  0,
-                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  35, 35, 0,  38, 0,
-                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  54, 54, 0,  0,  0,  0,  0,
-                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  71, 71, 71, 71, 71, 71, 71, 71, 71, 71,
-                0,  0,  0,  0,  0,  0,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 0,  0,  0,  0,  0,  0,  0,
+                0,  9,  0,  0,  0,  0,  0,  8,  8,  10, 10, 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  35, 35, 0,  38, 0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  54, 54, 0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 0,  0,  0,  0,
+                0,  0,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 0,  0,  0,  0,  0,  0,  0,
         },
         {
-                0,  9,  0,  0,  0,   0,  0,  8,  8,  10, 10,  0,  0,  0,  0,  0,  0,  0,  0,  0,
-                0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  31,  0,  0,  0,  0,  35, 35, 0,  38, 0,
-                50, 46, 43, 0,  0,   0,  0,  0,  0,  50, 0,   0,  0,  54, 54, 50, 0,  0,  0,  0,
-                0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  71,  71, 71, 71, 71, 71, 71, 71, 71, 71,
-                0,  0,  0,  0,  0,   0,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 99, 10,
-                10, 10, 10, 10, 105, 10, 10, 10, 10, 10, 111, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10,  10, 10, 10, 10, 0,  0,   0,  0,  0,  0,  0,
+                0,  9,  0,  0,  0,  0,  0,  8,  8,  10,  10, 0,  0,  0,  0,  0,   0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   0,  31, 0,  0,  0,  0,   35, 35, 0,
+                38, 0,  50, 46, 43, 0,  0,  0,  0,  0,   0,  50, 0,  0,  0,  54,  54, 50, 0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   0,  0,  0,  71, 71, 71,  71, 71, 71,
+                71, 71, 71, 71, 0,  0,  0,  0,  0,  0,   10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10, 10, 99, 10, 10, 10, 10, 10, 105, 10, 10, 10, 10, 10, 111, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 0,  0,  0,  0,   0,  0,  0,
         },
         {
-                0,   9,  0,  0,  0,  0,  0,   8,  8,  10, 10, 0,  0,   0,  0,  0,  0,  0,  0,  0,
-                0,   0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,   0,  0,  35, 35, 0,  38, 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,  71, 71, 71,  71, 71, 71, 71, 71, 71, 71,
-                0,   0,  0,  0,  0,  0,  10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10,
-                101, 10, 10, 10, 10, 10, 107, 10, 10, 10, 10, 10, 113, 10, 10, 10, 10, 10, 10, 10,
-                10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10,
-                10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10,
-                10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10,
-                10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10,
-                10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10,
-                10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10,
-                10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10,
-                10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10,
-                10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10,
-                10,  10, 10, 10, 10, 10, 10,  10, 10, 0,  0,  0,  0,   0,  0,  0,
+                0,  9,  0,  0,  0,  0,   0,  8,  8,  10, 10, 0,   0,  0,  0,  0,  0,  0,   0,
+                0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  35, 35,  0,
+                38, 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,  71, 71, 71, 71, 71,  71,
+                71, 71, 71, 71, 0,  0,   0,  0,  0,  0,  10, 10,  10, 10, 10, 10, 10, 10,  10,
+                10, 10, 10, 10, 10, 101, 10, 10, 10, 10, 10, 107, 10, 10, 10, 10, 10, 113, 10,
+                10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10,
+                10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10,
+                10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10,
+                10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10,
+                10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10,
+                10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10,
+                10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10,
+                10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10,
+                10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10,
+                10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  0,  0,  0,  0,  0,  0,   0,
         },
         {
-                0,  9,  0,  0,  0,  0,   0,  8,  8,  10, 10, 0,   0,  0,  0,  0,  0,  0,  0,  0,
-                0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,   0,  0,  0,  35, 35, 0,  38, 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,  71, 71,  71, 71, 71, 71, 71, 71, 71, 71,
-                0,  0,  0,  0,  0,  0,   10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 100,
-                10, 10, 10, 10, 10, 106, 10, 10, 10, 10, 10, 112, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10,  10, 10, 10, 0,  0,  0,   0,  0,  0,  0,
+                0,  9,  0,  0,  0,   0,  0,  8,  8,  10, 10,  0,  0,  0,  0,  0,  0,   0,  0,
+                0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  35,  35, 0,
+                38, 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,  71, 71, 71, 71,  71, 71,
+                71, 71, 71, 71, 0,   0,  0,  0,  0,  0,  10,  10, 10, 10, 10, 10, 10,  10, 10,
+                10, 10, 10, 10, 100, 10, 10, 10, 10, 10, 106, 10, 10, 10, 10, 10, 112, 10, 10,
+                10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10,
+                10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10,
+                10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10,
+                10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10,
+                10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10,
+                10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10,
+                10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10,
+                10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10,
+                10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10,
+                10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 0,  0,  0,  0,  0,   0,  0,
         },
         {
-                0, 80, 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, 35, 35, 0, 38, 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, 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, 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,  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, 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, 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,  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, 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, 80, 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, 35, 35, 0, 38, 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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,
         },
         {
-                0, 81, 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, 35, 35, 0, 38, 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, 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, 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,  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, 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, 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,  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, 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, 81, 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, 35, 35, 0, 38, 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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,
         },
         {
-                0, 82, 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, 35, 35, 0, 38, 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, 0, 0, 0, 0, 0, 0,
-                0, 84, 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, 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, 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,  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, 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, 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,  0, 0, 0, 0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0, 0, 0,
+                0, 82, 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, 35, 35, 0, 38, 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, 0, 0, 0,
+                0, 0,  0, 0, 84, 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, 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, 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, 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, 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, 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,  0, 0, 0, 0, 0,
         },
         {
-                0,  86, 0,  0,  0,  0,  0,  8,  8,  10,  10, 0,  0,  0,   0,  0,  0,  0,  0,   0,
-                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   0,  0,  0,  0,   0,  35, 35, 0,  38,  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,   71, 71, 71, 71,  71, 71, 71, 71, 71,  71,
-                0,  0,  0,  0,  0,  0,  87, 10, 10, 10,  10, 10, 93, 10,  10, 10, 10, 10, 102, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10,  10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10,  10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10,  10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10,  10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10,  10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10,  10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10,  10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 250, 10, 10, 10, 254, 10, 10, 10, 10, 259, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10,  10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10,  10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 0,   0,  0,  0,  0,   0,  0,
+                0,  86, 0,  0,   0,  0,  0,  8,  8,  10,  10, 0,  0,  0,   0,  0,  0,  0,  0,
+                0,  0,  0,  0,   0,  0,  0,  0,  0,  0,   0,  0,  0,  0,   0,  0,  35, 35, 0,
+                38, 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,  71,  71, 71, 71, 71, 71,
+                71, 71, 71, 71,  0,  0,  0,  0,  0,  0,   87, 10, 10, 10,  10, 10, 93, 10, 10,
+                10, 10, 10, 102, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10,
+                10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10,
+                10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10,
+                10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10,
+                10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10,
+                10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10,
+                10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10,
+                10, 10, 10, 10,  10, 10, 10, 10, 10, 238, 10, 10, 10, 242, 10, 10, 10, 10, 247,
+                10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10,
+                10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10,
+                10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 0,  0,   0,  0,  0,  0,  0,
         },
         {
-                0,  9,  0,   0,   0,   0,   0,  8,  8,  10, 10,  0,  0,  0,  0,   0,  0,   0,  0,
-                0,  0,  0,   0,   0,   0,   0,  0,  0,  0,  0,   0,  0,  0,  0,   0,  35,  35, 0,
-                38, 0,  0,   0,   0,   0,   0,  0,  0,  0,  0,   0,  0,  0,  0,   54, 54,  0,  0,
-                0,  0,  0,   0,   0,   0,   0,  0,  0,  0,  0,   0,  0,  71, 71,  71, 71,  71, 71,
-                71, 71, 71,  71,  0,   0,   0,  0,  0,  0,  10,  10, 10, 10, 10,  10, 10,  10, 10,
-                10, 97, 10,  10,  10,  10,  10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10,  10, 10,
-                10, 10, 117, 10,  10,  10,  10, 10, 10, 10, 125, 10, 10, 10, 129, 10, 10,  10, 10,
-                10, 10, 10,  10,  10,  10,  10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 150, 10, 10,
-                10, 10, 10,  10,  157, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 169, 10, 10,
-                10, 10, 174, 10,  10,  10,  10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10,  10, 190,
-                10, 10, 10,  10,  10,  10,  10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10,  10, 10,
-                10, 10, 10,  10,  10,  10,  10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10,  10, 10,
-                10, 10, 10,  232, 10,  10,  10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10,  10, 10,
-                10, 10, 10,  10,  252, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10,  10, 10,
-                10, 10, 10,  10,  10,  10,  10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10,  10, 10,
-                10, 10, 10,  10,  10,  291, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10,  10, 10,
-                10, 10, 10,  10,  10,  0,   0,  0,  0,  0,  0,   0,
+                0,  9,  0,   0,  0,   0,  0,  8,  8,  10, 10,  0,   0,   0,   0,   0,  0,   0,  0,
+                0,  0,  0,   0,  0,   0,  0,  0,  0,  0,  0,   0,   0,   0,   0,   0,  35,  35, 0,
+                38, 0,  0,   0,  0,   0,  0,  0,  0,  0,  0,   0,   0,   0,   0,   54, 54,  0,  0,
+                0,  0,  0,   0,  0,   0,  0,  0,  0,  0,  0,   0,   0,   71,  71,  71, 71,  71, 71,
+                71, 71, 71,  71, 0,   0,  0,  0,  0,  0,  10,  10,  10,  10,  10,  10, 10,  10, 10,
+                10, 97, 10,  10, 10,  10, 10, 10, 10, 10, 10,  10,  10,  10,  10,  10, 10,  10, 10,
+                10, 10, 117, 10, 10,  10, 10, 10, 10, 10, 125, 10,  10,  10,  129, 10, 10,  10, 10,
+                10, 10, 10,  10, 10,  10, 10, 10, 10, 10, 10,  10,  10,  10,  10,  10, 150, 10, 10,
+                10, 10, 10,  10, 157, 10, 10, 10, 10, 10, 10,  10,  10,  10,  10,  10, 169, 10, 10,
+                10, 10, 174, 10, 10,  10, 10, 10, 10, 10, 10,  10,  10,  185, 10,  10, 10,  10, 10,
+                10, 10, 10,  10, 10,  10, 10, 10, 10, 10, 10,  10,  10,  10,  10,  10, 10,  10, 10,
+                10, 10, 10,  10, 10,  10, 10, 10, 10, 10, 220, 10,  10,  10,  10,  10, 10,  10, 10,
+                10, 10, 10,  10, 10,  10, 10, 10, 10, 10, 10,  240, 10,  10,  10,  10, 10,  10, 10,
+                10, 10, 10,  10, 10,  10, 10, 10, 10, 10, 10,  10,  10,  10,  10,  10, 10,  10, 10,
+                10, 10, 10,  10, 10,  10, 10, 10, 10, 10, 10,  10,  279, 10,  10,  10, 10,  10, 10,
+                10, 10, 10,  10, 10,  10, 10, 10, 10, 10, 10,  10,  0,   0,   0,   0,  0,   0,  0,
         },
         {
-                0,  114, 0,  0,  0,  0,  0,  8,  8,  10, 10, 0,  0,  0,  0,  0,  0,  0,  0,  0,
-                0,  0,   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  35, 35, 0,  38, 0,
-                0,  0,   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  54, 54, 0,  0,  0,  0,  0,
-                0,  0,   0,  0,  0,  0,  0,  0,  0,  0,  71, 71, 71, 71, 71, 71, 71, 71, 71, 71,
-                0,  0,   0,  0,  0,  0,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 0,  0,  0,  0,  0,  0,  0,
+                0,  114, 0,  0,  0,  0,  0,  8,  8,  10, 10, 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  35, 35, 0,  38, 0,  0,  0,
+                0,  0,   0,  0,  0,  0,  0,  0,  0,  0,  0,  54, 54, 0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,   0,  0,  0,  0,  0,  71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 0,  0,  0,  0,
+                0,  0,   10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 0,  0,  0,  0,  0,  0,  0,
         },
         {
-                0,  124, 0,  0,   0,  0,  0,  8,  8,   10, 10, 0,  0,  0,   0,   0,  0,   0,  0,
-                0,  0,   0,  0,   0,  0,  0,  0,  0,   0,  0,  0,  0,  0,   0,   0,  35,  35, 0,
-                38, 0,   0,  0,   0,  0,  0,  0,  0,   0,  0,  0,  0,  0,   0,   54, 54,  0,  0,
-                0,  0,   0,  0,   0,  0,  0,  0,  0,   0,  0,  0,  0,  71,  71,  71, 71,  71, 71,
-                71, 78,  71, 71,  0,  0,  0,  0,  0,   0,  10, 10, 10, 10,  10,  10, 10,  10, 10,
-                96, 10,  10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10,  10, 10,  10, 10,
-                10, 10,  10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10,  10, 10,  10, 10,
-                10, 10,  10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10,  10, 10,  10, 10,
-                10, 10,  10, 156, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10,  10, 10,  10, 10,
-                10, 10,  10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10,  10, 10,  10, 10,
-                10, 10,  10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10,  10, 10,  10, 10,
-                10, 10,  10, 213, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  224, 10, 10,  10, 10,
-                10, 10,  10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 242, 10,  10, 10,  10, 10,
-                10, 10,  10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10,  10, 264, 10, 10,
-                10, 10,  10, 270, 10, 10, 10, 10, 275, 10, 10, 10, 10, 10,  10,  10, 10,  10, 10,
-                10, 10,  10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10,  10, 10,  10, 10,
-                10, 10,  10, 10,  10, 0,  0,  0,  0,   0,  0,  0,
+                0,  124, 0,   0,   0,   0,  0,  8,   8,  10, 10,  0,  0,  0,  0,  0,   0,  0,  0,
+                0,  0,   0,   0,   0,   0,  0,  0,   0,  0,  0,   0,  0,  0,  0,  0,   35, 35, 0,
+                38, 0,   0,   0,   0,   0,  0,  0,   0,  0,  0,   0,  0,  0,  0,  54,  54, 0,  0,
+                0,  0,   0,   0,   0,   0,  0,  0,   0,  0,  0,   0,  0,  71, 71, 71,  71, 71, 71,
+                71, 78,  71,  71,  0,   0,  0,  0,   0,  0,  10,  10, 10, 10, 10, 10,  10, 10, 10,
+                96, 10,  10,  10,  10,  10, 10, 10,  10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10,  10,  10,  10,  10, 10, 10,  10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10,  10,  10,  10,  10, 10, 10,  10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10,  10,  156, 10,  10, 10, 10,  10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10,  10,  10,  10,  10, 10, 10,  10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10,  10,  10,  10,  10, 10, 198, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10,  212, 10,  10,  10, 10, 10,  10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10,
+                10, 230, 10,  10,  10,  10, 10, 10,  10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10,  10,  10,  252, 10, 10, 10,  10, 10, 258, 10, 10, 10, 10, 263, 10, 10, 10,
+                10, 10,  10,  10,  10,  10, 10, 10,  10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10,  10,  10,  10,  10, 10, 10,  10, 10, 10,  10, 0,  0,  0,  0,   0,  0,  0,
         },
         {
-                0,  147, 0,  0,  0,  0,  0,  8,  8,  10, 10, 0,  0,   0,  0,  0,  0,   0,  0,   0,
-                0,  0,   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   0,  0,  35, 35,  0,  38,  0,
-                0,  0,   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   54, 54, 0,  0,   0,  0,   0,
-                0,  0,   0,  0,  0,  0,  0,  0,  0,  0,  71, 71, 71,  71, 71, 71, 71,  71, 71,  71,
-                0,  0,   0,  0,  0,  0,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10,  10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10,  10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10,  10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 159, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10,  10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10,  200,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10,  10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 233, 10, 10, 10, 10,  10, 10,  10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 257, 10, 10,  10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10,  10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10, 10,  10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 0,  0,  0,  0,   0,  0,  0,
+                0,  147, 0,  0,  0,  0,  0,   8,  8,  10, 10, 0,   0,  0,  0,  0,  0,   0,  0,
+                0,  0,   0,  0,  0,  0,  0,   0,  0,  0,  0,  0,   0,  0,  0,  0,  35,  35, 0,
+                38, 0,   0,  0,  0,  0,  0,   0,  0,  0,  0,  0,   0,  0,  0,  54, 54,  0,  0,
+                0,  0,   0,  0,  0,  0,  0,   0,  0,  0,  0,  0,   0,  71, 71, 71, 71,  71, 71,
+                71, 71,  71, 71, 0,  0,  0,   0,  0,  0,  10, 10,  10, 10, 10, 10, 10,  10, 10,
+                10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10,
+                10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10,
+                10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10,
+                10, 10,  10, 10, 10, 10, 159, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10,
+                10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10,
+                10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10,
+                10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 221, 10, 10, 10, 10, 10,  10, 10,
+                10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 245, 10, 10,
+                10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10,
+                10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10,
+                10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10,  0,  0,  0,  0,  0,   0,  0,
         },
         {
-                0,   161, 0,  0,   0,  0,   0,   8,   8,  10,  10,  0,   0,  0,   0,   0,   0,  0,
-                0,   0,   0,  0,   0,  0,   0,   0,   0,  0,   0,   0,   31, 0,   0,   0,   0,  35,
-                35,  0,   38, 0,   50, 46,  43,  0,   0,  0,   0,   0,   0,  50,  0,   0,   0,  54,
-                54,  50,  0,  0,   0,  0,   0,   0,   0,  0,   0,   0,   0,  0,   0,   0,   71, 71,
-                71,  71,  71, 71,  71, 71,  71,  71,  0,  0,   0,   0,   0,  0,   10,  10,  10, 10,
-                91,  10,  10, 10,  10, 10,  10,  10,  10, 10,  10,  10,  10, 10,  10,  10,  10, 10,
-                10,  10,  10, 10,  10, 10,  10,  116, 10, 10,  10,  10,  10, 122, 10,  10,  10, 10,
-                127, 10,  10, 10,  10, 10,  10,  134, 10, 136, 10,  10,  10, 10,  10,  10,  10, 10,
-                10,  146, 10, 148, 10, 10,  10,  10,  10, 10,  10,  10,  10, 10,  10,  10,  10, 10,
-                10,  164, 10, 10,  10, 10,  10,  10,  10, 172, 10,  10,  10, 10,  10,  10,  10, 10,
-                10,  10,  10, 10,  10, 10,  10,  10,  10, 10,  10,  10,  10, 10,  10,  10,  10, 10,
-                199, 10,  10, 10,  10, 10,  10,  10,  10, 208, 10,  10,  10, 212, 10,  10,  10, 10,
-                217, 10,  10, 10,  10, 10,  223, 10,  10, 10,  10,  10,  10, 10,  231, 10,  10, 10,
-                10,  10,  10, 10,  10, 10,  10,  10,  10, 10,  10,  10,  10, 10,  10,  10,  10, 10,
-                10,  10,  10, 10,  10, 258, 10,  260, 10, 10,  263, 10,  10, 10,  10,  10,  10, 10,
-                10,  10,  10, 10,  10, 10,  10,  10,  10, 280, 10,  10,  10, 10,  10,  10,  10, 10,
-                10,  10,  10, 10,  10, 10,  295, 10,  10, 10,  10,  300, 10, 10,  10,  304, 10, 10,
-                10,  10,  10, 0,   0,  0,   0,   0,   0,  0,
+                0,   161, 0,   0,   0,  0,  0,  8,   8,  10,  10, 0,   0,   0,   0,  0,   0,   0,
+                0,   0,   0,   0,   0,  0,  0,  0,   0,  0,   0,  0,   31,  0,   0,  0,   0,   35,
+                35,  0,   38,  0,   50, 46, 43, 0,   0,  0,   0,  0,   0,   50,  0,  0,   0,   54,
+                54,  50,  0,   0,   0,  0,  0,  0,   0,  0,   0,  0,   0,   0,   0,  0,   71,  71,
+                71,  71,  71,  71,  71, 71, 71, 71,  0,  0,   0,  0,   0,   0,   10, 10,  10,  10,
+                91,  10,  10,  10,  10, 10, 10, 10,  10, 10,  10, 10,  10,  10,  10, 10,  10,  10,
+                10,  10,  10,  10,  10, 10, 10, 116, 10, 10,  10, 10,  10,  122, 10, 10,  10,  10,
+                127, 10,  10,  10,  10, 10, 10, 134, 10, 136, 10, 10,  10,  10,  10, 10,  10,  10,
+                10,  146, 10,  148, 10, 10, 10, 10,  10, 10,  10, 10,  10,  10,  10, 10,  10,  10,
+                10,  164, 10,  10,  10, 10, 10, 10,  10, 172, 10, 10,  10,  10,  10, 10,  10,  10,
+                10,  10,  10,  10,  10, 10, 10, 10,  10, 10,  10, 10,  193, 10,  10, 10,  197, 10,
+                10,  10,  10,  202, 10, 10, 10, 10,  10, 10,  10, 10,  211, 10,  10, 10,  10,  10,
+                10,  10,  219, 10,  10, 10, 10, 10,  10, 10,  10, 10,  10,  10,  10, 10,  10,  10,
+                10,  10,  10,  10,  10, 10, 10, 10,  10, 10,  10, 246, 10,  248, 10, 10,  251, 10,
+                10,  10,  10,  10,  10, 10, 10, 10,  10, 10,  10, 10,  10,  10,  10, 268, 10,  10,
+                10,  10,  10,  10,  10, 10, 10, 10,  10, 10,  10, 10,  283, 10,  10, 10,  10,  288,
+                10,  10,  10,  292, 10, 10, 10, 10,  10, 0,   0,  0,   0,   0,   0,  0,
         },
         {
-                0,   168, 0,  0,   0,  0,  0,  8,  8,   10, 10, 0,  0,  0,  0,  0,  0,  0,  0,  0,
-                0,   0,   0,  0,   0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,  35, 35, 0,  38, 0,
-                0,   0,   0,  0,   0,  0,  0,  0,  0,   0,  0,  0,  0,  54, 54, 0,  0,  0,  0,  0,
-                0,   0,   0,  0,   0,  0,  0,  0,  0,   0,  71, 71, 73, 71, 71, 71, 71, 71, 71, 71,
-                0,   0,   0,  0,   0,  0,  10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10,  10,  10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 120,
-                121, 10,  10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10,  10,  10, 10,  10, 10, 10, 10, 149, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10,  10,  10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10,  10,  10, 184, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10,  10,  10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10,  10,  10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10,  10,  10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                261, 262, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10,  10,  10, 284, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10,  10,  10, 10,  10, 10, 10, 10, 10,  0,  0,  0,  0,  0,  0,  0,
+                0,  168, 0,   0,  0,  0,   0,   8,   8,  10, 10, 0,  0,  0,  0,  0,   0,  0,  0,
+                0,  0,   0,   0,  0,  0,   0,   0,   0,  0,  0,  0,  0,  0,  0,  0,   35, 35, 0,
+                38, 0,   0,   0,  0,  0,   0,   0,   0,  0,  0,  0,  0,  0,  0,  54,  54, 0,  0,
+                0,  0,   0,   0,  0,  0,   0,   0,   0,  0,  0,  0,  0,  71, 71, 73,  71, 71, 71,
+                71, 71,  71,  71, 0,  0,   0,   0,   0,  0,  10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10,  10,  10, 10, 10,  10,  10,  10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10,  10,  10, 10, 120, 121, 10,  10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10,  10,  10, 10, 10,  10,  10,  10, 10, 10, 10, 10, 10, 10, 149, 10, 10, 10,
+                10, 10,  10,  10, 10, 10,  10,  10,  10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10,  10,  10, 10, 10,  10,  179, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10,  10,  10, 10, 10,  10,  10,  10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10,  10,  10, 10, 10,  10,  10,  10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10,  10,  10, 10, 10,  10,  10,  10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 249, 250, 10, 10, 10,  10,  10,  10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10,  10,  10, 10, 272, 10,  10,  10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10,
+                10, 10,  10,  10, 10, 10,  10,  10,  10, 10, 10, 10, 0,  0,  0,  0,   0,  0,  0,
         },
         {
-                0,  9,  0,  0,  0,  0,  0,  8,  8,  10, 10, 0,  0,  0,  0,  0,  0,  0,  0,  0,
-                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  35, 35, 0,  38, 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,  71, 71, 71, 71, 71, 71, 71, 71, 71, 71,
-                0,  0,  0,  0,  0,  0,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 180,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 0,  0,  0,  0,  0,  0,  0,
+                0,  9,  0,  0,  0,  0,  0,  8,  8,  10, 10,  0,  0,  0,  0,  0,  0,   0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  35,  35, 0,
+                38, 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,  71, 71, 71, 71,  71, 71,
+                71, 71, 79, 71, 0,  0,  0,  0,  0,  0,  10,  10, 10, 10, 10, 10, 10,  10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 133,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 239, 10, 10, 10, 10, 10, 10,  10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 264, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 285,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 0,  0,  0,  0,  0,   0,  0,
         },
         {
-                0,  178, 0,  0,   0,  0,  0,  8,  8,  10,  10, 0,   0,  0,  0,  0,  0,  0,  0,
-                0,  0,   0,  0,   0,  0,  0,  0,  0,  0,   0,  0,   0,  0,  0,  0,  35, 35, 0,
-                38, 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,  71, 71, 71, 71, 71, 71,
-                71, 71,  79, 71,  0,  0,  0,  0,  0,  0,   10, 10,  10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10,  10, 10, 10, 10, 10, 10,  10, 10,  10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10,  10, 10, 10, 10, 10, 10,  10, 10,  10, 10, 10, 10, 10, 10, 133,
-                10, 10,  10, 10,  10, 10, 10, 10, 10, 10,  10, 10,  10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10,  10, 10, 10, 10, 10, 10,  10, 10,  10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10,  10, 10, 10, 10, 10, 181, 10, 10,  10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10,  10, 10, 10, 10, 10, 10,  10, 10,  10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10,  10, 10, 10, 10, 10, 10,  10, 10,  10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10,  10, 10, 10, 10, 10, 10,  10, 10,  10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 251, 10, 10, 10, 10, 10, 10,  10, 10,  10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10,  10, 10, 10, 10, 10, 276, 10, 10,  10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10,  10, 10, 10, 10, 10, 10,  10, 297, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10, 10,  10, 0,  0,  0,  0,  0,   0,  0,
+                0,   178, 0,  0,   0,  0,  0,  8,  8,   10,  10,  0,  0,   0,  0,   0,   0,   0,
+                0,   0,   0,  0,   0,  0,  0,  0,  0,   0,   0,   0,  0,   0,  0,   0,   0,   35,
+                35,  0,   38, 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,   72,  71,
+                71,  71,  71, 76,  71, 71, 71, 71, 0,   0,   0,   0,  0,   0,  10,  10,  89,  10,
+                10,  10,  10, 10,  10, 10, 10, 10, 10,  10,  10,  10, 103, 10, 10,  10,  10,  10,
+                10,  10,  10, 10,  10, 10, 10, 10, 10,  10,  10,  10, 10,  10, 10,  10,  10,  10,
+                10,  10,  10, 10,  10, 10, 10, 10, 10,  10,  10,  10, 10,  10, 10,  10,  143, 10,
+                10,  10,  10, 154, 10, 10, 10, 10, 10,  10,  10,  10, 10,  10, 10,  10,  10,  10,
+                10,  10,  10, 10,  10, 10, 10, 10, 10,  10,  10,  10, 10,  10, 10,  10,  10,  10,
+                10,  10,  10, 10,  10, 10, 10, 10, 10,  10,  10,  10, 10,  10, 10,  10,  10,  10,
+                10,  200, 10, 10,  10, 10, 10, 10, 10,  10,  10,  10, 10,  10, 213, 10,  215, 10,
+                10,  10,  10, 10,  10, 10, 10, 10, 10,  10,  10,  10, 229, 10, 10,  10,  10,  10,
+                10,  10,  10, 10,  10, 10, 10, 10, 10,  244, 10,  10, 10,  10, 10,  10,  10,  10,
+                10,  10,  10, 10,  10, 10, 10, 10, 261, 10,  10,  10, 10,  10, 10,  10,  10,  10,
+                271, 10,  10, 10,  10, 10, 10, 10, 10,  10,  281, 10, 10,  10, 10,  286, 10,  10,
+                10,  290, 10, 10,  10, 10, 10, 10, 10,  0,   0,   0,  0,   0,  0,   0,
         },
         {
-                0,  183, 0,   0,   0,   0,  0,   8,  8,   10,  10,  0,  0,   0,   0,  0,  0,   0,
-                0,  0,   0,   0,   0,   0,  0,   0,  0,   0,   0,   0,  0,   0,   0,  0,  0,   35,
-                35, 0,   38,  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,  72,  71,
-                71, 71,  71,  76,  71,  71, 71,  71, 0,   0,   0,   0,  0,   0,   10, 10, 89,  10,
-                10, 10,  10,  10,  10,  10, 10,  10, 10,  10,  10,  10, 103, 10,  10, 10, 10,  10,
-                10, 10,  10,  10,  10,  10, 10,  10, 10,  10,  10,  10, 10,  10,  10, 10, 10,  10,
-                10, 10,  10,  10,  10,  10, 10,  10, 10,  10,  10,  10, 10,  10,  10, 10, 143, 10,
-                10, 10,  10,  154, 10,  10, 10,  10, 10,  10,  10,  10, 10,  10,  10, 10, 10,  10,
-                10, 10,  10,  10,  10,  10, 10,  10, 10,  10,  10,  10, 10,  10,  10, 10, 179, 10,
-                10, 10,  10,  10,  10,  10, 10,  10, 10,  10,  10,  10, 10,  10,  10, 10, 10,  10,
-                10, 10,  201, 10,  10,  10, 10,  10, 10,  10,  10,  10, 10,  10,  10, 10, 215, 10,
-                10, 10,  10,  10,  10,  10, 10,  10, 225, 10,  227, 10, 10,  10,  10, 10, 10,  10,
-                10, 10,  10,  10,  10,  10, 241, 10, 10,  10,  10,  10, 10,  10,  10, 10, 10,  10,
-                10, 10,  10,  256, 10,  10, 10,  10, 10,  10,  10,  10, 10,  10,  10, 10, 10,  10,
-                10, 10,  273, 10,  10,  10, 10,  10, 10,  10,  10,  10, 283, 10,  10, 10, 10,  10,
-                10, 10,  10,  10,  293, 10, 10,  10, 10,  298, 10,  10, 10,  302, 10, 10, 10,  10,
-                10, 10,  10,  0,   0,   0,  0,   0,  0,   0,
+                0,  9,  0,  0,   0,  0,  0,  8,  8,   10, 10, 0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,   0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0,  35, 35, 0,
+                38, 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,  71, 71, 71, 71, 71, 71,
+                71, 71, 71, 71,  0,  0,  0,  0,  0,   0,  10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 118, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10,  10, 10, 10, 10, 237, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10,  10, 10, 10, 10, 10,  10, 10, 10, 0,  0,  0,  0,  0,  0,  0,
         },
         {
-                0,  9,  0,  0,  0,  0,  0,  8,  8,   10, 10, 0,  0,  0,  0,  0,  0,  0,   0,  0,
-                0,  0,  0,  0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,  35, 35, 0,   38, 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,  71, 71, 71, 71, 71, 71, 71, 71,  71, 71,
-                0,  0,  0,  0,  0,  0,  10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 118, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 249, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10,  0,  0,  0,  0,  0,  0,  0,
+                0,  184, 0,  0,  0,  0,   0,   8,   8,  10, 10, 0,   0,  0,  0,  0,  0,   0,
+                0,  0,   0,  0,  0,  0,   0,   0,   0,  0,  0,  0,   0,  0,  0,  0,  0,   35,
+                35, 0,   38, 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,  71,  71,
+                71, 71,  71, 71, 71, 71,  71,  71,  0,  0,  0,  0,   0,  0,  10, 10, 10,  10,
+                10, 92,  10, 94, 10, 10,  10,  98,  10, 10, 10, 10,  10, 10, 10, 10, 10,  10,
+                10, 10,  10, 10, 10, 10,  10,  10,  10, 10, 10, 10,  10, 10, 10, 10, 128, 10,
+                10, 10,  10, 10, 10, 10,  10,  10,  10, 10, 10, 10,  10, 10, 10, 10, 10,  10,
+                10, 10,  10, 10, 10, 10,  10,  152, 10, 10, 10, 10,  10, 10, 10, 10, 10,  162,
+                10, 10,  10, 10, 10, 10,  173, 170, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10,
+                10, 10,  10, 10, 10, 10,  10,  10,  10, 10, 10, 10,  10, 10, 10, 10, 10,  10,
+                10, 10,  10, 10, 10, 204, 205, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10,  10,
+                10, 10,  10, 10, 10, 10,  10,  224, 10, 10, 10, 10,  10, 10, 10, 10, 10,  10,
+                10, 10,  10, 10, 10, 10,  10,  10,  10, 10, 10, 10,  10, 10, 10, 10, 10,  10,
+                10, 10,  10, 10, 10, 10,  10,  10,  10, 10, 10, 10,  10, 10, 10, 10, 10,  10,
+                10, 10,  10, 10, 10, 10,  10,  278, 10, 10, 10, 282, 10, 10, 10, 10, 287, 10,
+                10, 10,  10, 10, 10, 10,  295, 10,  10, 0,  0,  0,   0,  0,  0,  0,
         },
         {
-                0,   189, 0,  0,  0,  0,   0,   8,   8,  10, 10,  0,  0,  0,  0,  0,  0,   0,
-                0,   0,   0,  0,  0,  0,   0,   0,   0,  0,  0,   0,  0,  0,  0,  0,  0,   35,
-                35,  0,   38, 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,  71,  71,
-                71,  71,  71, 71, 71, 71,  71,  71,  0,  0,  0,   0,  0,  0,  10, 10, 10,  10,
-                10,  92,  10, 94, 10, 10,  10,  98,  10, 10, 10,  10, 10, 10, 10, 10, 10,  10,
-                10,  10,  10, 10, 10, 10,  10,  10,  10, 10, 10,  10, 10, 10, 10, 10, 128, 10,
-                10,  10,  10, 10, 10, 10,  10,  10,  10, 10, 10,  10, 10, 10, 10, 10, 10,  10,
-                10,  10,  10, 10, 10, 10,  10,  152, 10, 10, 10,  10, 10, 10, 10, 10, 10,  162,
-                10,  10,  10, 10, 10, 10,  173, 170, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10,
-                10,  10,  10, 10, 10, 10,  10,  10,  10, 10, 10,  10, 10, 10, 10, 10, 10,  10,
-                10,  10,  10, 10, 10, 10,  10,  10,  10, 10, 10,  10, 10, 10, 10, 10, 10,  10,
-                10,  10,  10, 10, 10, 10,  10,  10,  10, 10, 10,  10, 10, 10, 10, 10, 10,  10,
-                10,  236, 10, 10, 10, 10,  10,  10,  10, 10, 10,  10, 10, 10, 10, 10, 10,  10,
-                10,  10,  10, 10, 10, 10,  10,  10,  10, 10, 10,  10, 10, 10, 10, 10, 10,  10,
-                10,  10,  10, 10, 10, 10,  10,  10,  10, 10, 10,  10, 10, 10, 10, 10, 10,  10,
-                10,  290, 10, 10, 10, 294, 10,  10,  10, 10, 299, 10, 10, 10, 10, 10, 10,  10,
-                307, 10,  10, 0,  0,  0,   0,   0,   0,  0,
+                0,  9,  0,  0,  0,  0,  0,  8,  8,   10, 10, 0,  0,  0,  0,   0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,   0,  35, 35, 0,
+                38, 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,  71, 71,  71, 71, 71, 71,
+                71, 71, 71, 71, 0,  0,  0,  0,  0,   0,  10, 10, 10, 10, 10,  10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 167, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 275, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 0,  0,  0,   0,  0,  0,  0,
         },
         {
-                0,  198, 0,   0,  0,  0,  0,   8,  8,  10, 10, 0,  0,  0,  0,  0,  0,  0,  0,  0,
-                0,  0,   0,   0,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0,  0,  35, 35, 0,  38, 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,  71, 71, 71, 71, 71, 71, 71, 71, 71, 71,
-                0,  0,   0,   0,  0,  0,  10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10,  10, 10, 10, 167, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  203, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10,  10, 10, 10, 287, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10,  10,  10, 10, 10, 10,  10, 10, 0,  0,  0,  0,  0,  0,  0,
+                0,  190, 0,  0,   0,  0,  0,   8,   8,   10,  10,  0,  0,  0,   0,  0,  0,  0,  0,
+                0,  0,   0,  0,   0,  0,  0,   0,   0,   0,   0,   0,  0,  0,   0,  0,  35, 35, 0,
+                38, 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,  71,  71, 71, 71, 71, 71,
+                71, 71,  71, 71,  0,  0,  0,   0,   0,   0,   10,  10, 10, 10,  10, 10, 10, 10, 10,
+                10, 10,  10, 10,  10, 10, 10,  10,  104, 10,  10,  10, 10, 10,  10, 10, 10, 10, 10,
+                10, 10,  10, 10,  10, 10, 10,  10,  10,  10,  10,  10, 10, 10,  10, 10, 10, 10, 139,
+                10, 10,  10, 137, 10, 10, 10,  10,  10,  10,  144, 10, 10, 10,  10, 10, 10, 10, 10,
+                10, 10,  10, 10,  10, 10, 10,  10,  10,  165, 10,  10, 10, 10,  10, 10, 10, 10, 10,
+                10, 10,  10, 10,  10, 10, 10,  180, 10,  10,  10,  10, 10, 10,  10, 10, 10, 10, 10,
+                10, 10,  10, 10,  10, 10, 10,  10,  10,  10,  10,  10, 10, 10,  10, 10, 10, 10, 10,
+                10, 10,  10, 10,  10, 10, 10,  217, 10,  10,  10,  10, 10, 223, 10, 10, 10, 10, 10,
+                10, 10,  10, 10,  10, 10, 235, 10,  10,  10,  10,  10, 10, 10,  10, 10, 10, 10, 10,
+                10, 10,  10, 10,  10, 10, 10,  10,  10,  10,  10,  10, 10, 10,  10, 10, 10, 10, 10,
+                10, 10,  10, 270, 10, 10, 10,  10,  10,  10,  10,  10, 10, 10,  10, 10, 10, 10, 10,
+                10, 10,  10, 10,  10, 10, 10,  10,  294, 10,  10,  10, 0,  0,   0,  0,  0,  0,  0,
         },
         {
-                0,   205, 0,  0,   0,  0,  0,   8,  8,  10, 10,  0,   0,   0,   0,  0,  0,  0,
-                0,   0,   0,  0,   0,  0,  0,   0,  0,  0,  0,   0,   0,   0,   0,  0,  0,  35,
-                35,  0,   38, 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,  71, 71,
-                71,  71,  71, 71,  71, 71, 71,  71, 0,  0,  0,   0,   0,   0,   10, 10, 10, 10,
-                10,  10,  10, 10,  10, 10, 10,  10, 10, 10, 10,  10,  10,  104, 10, 10, 10, 10,
-                10,  10,  10, 10,  10, 10, 10,  10, 10, 10, 10,  10,  10,  10,  10, 10, 10, 10,
-                10,  10,  10, 10,  10, 10, 139, 10, 10, 10, 137, 10,  10,  10,  10, 10, 10, 144,
-                10,  10,  10, 10,  10, 10, 10,  10, 10, 10, 10,  10,  10,  10,  10, 10, 10, 165,
-                10,  10,  10, 10,  10, 10, 10,  10, 10, 10, 10,  10,  10,  10,  10, 10, 10, 10,
-                10,  10,  10, 185, 10, 10, 10,  10, 10, 10, 10,  10,  10,  10,  10, 10, 10, 10,
-                10,  10,  10, 10,  10, 10, 10,  10, 10, 10, 10,  10,  10,  10,  10, 10, 10, 10,
-                10,  10,  10, 10,  10, 10, 10,  10, 10, 10, 10,  10,  229, 10,  10, 10, 10, 10,
-                235, 10,  10, 10,  10, 10, 10,  10, 10, 10, 10,  10,  247, 10,  10, 10, 10, 10,
-                10,  10,  10, 10,  10, 10, 10,  10, 10, 10, 10,  10,  10,  10,  10, 10, 10, 10,
-                10,  10,  10, 10,  10, 10, 10,  10, 10, 10, 10,  282, 10,  10,  10, 10, 10, 10,
-                10,  10,  10, 10,  10, 10, 10,  10, 10, 10, 10,  10,  10,  10,  10, 10, 10, 306,
-                10,  10,  10, 0,   0,  0,  0,   0,  0,  0,
+                0,   206, 0,   0,   0,   0,   0,   8,  8,  10, 10,  0,  0,   0,  0,  0,  0,   0,
+                0,   0,   0,   0,   0,   0,   0,   0,  0,  0,  0,   0,  0,   0,  0,  0,  0,   35,
+                35,  0,   38,  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,  71,  71,
+                71,  71,  71,  71,  71,  71,  71,  71, 0,  0,  0,   0,  0,   0,  10, 10, 10,  10,
+                10,  10,  10,  10,  95,  10,  10,  10, 10, 10, 10,  10, 108, 10, 10, 10, 10,  10,
+                10,  10,  10,  10,  10,  10,  10,  10, 10, 10, 10,  10, 10,  10, 10, 10, 132, 10,
+                10,  10,  10,  10,  10,  10,  10,  10, 10, 10, 10,  10, 10,  10, 10, 10, 10,  10,
+                10,  10,  10,  160, 10,  10,  10,  10, 10, 10, 10,  10, 10,  10, 10, 10, 10,  10,
+                10,  10,  10,  10,  10,  10,  176, 10, 10, 10, 10,  10, 10,  10, 10, 10, 10,  10,
+                181, 10,  10,  10,  10,  10,  187, 10, 10, 10, 191, 10, 10,  10, 10, 10, 10,  10,
+                10,  10,  10,  10,  10,  10,  10,  10, 10, 10, 10,  10, 10,  10, 10, 10, 10,  216,
+                10,  10,  10,  10,  10,  222, 10,  10, 10, 10, 10,  10, 10,  10, 10, 10, 10,  10,
+                10,  10,  10,  10,  10,  10,  10,  10, 10, 10, 10,  10, 10,  10, 10, 10, 10,  10,
+                10,  10,  10,  10,  10,  10,  10,  10, 10, 10, 10,  10, 10,  10, 10, 10, 10,  10,
+                10,  10,  273, 10,  10,  10,  277, 10, 10, 10, 10,  10, 10,  10, 10, 10, 10,  10,
+                10,  10,  10,  10,  293, 10,  10,  10, 10, 0,  0,   0,  0,   0,  0,  0,
         },
         {
-                0,   218, 0,  0,   0,  0,   0,   8,   8,  10,  10, 0,   0,   0,  0,   0,  0,   0,
-                0,   0,   0,  0,   0,  0,   0,   0,   0,  0,   0,  0,   0,   0,  0,   0,  0,   35,
-                35,  0,   38, 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,  71,  71,
-                71,  71,  71, 71,  71, 71,  71,  71,  0,  0,   0,  0,   0,   0,  10,  10, 10,  10,
-                10,  10,  10, 10,  95, 10,  10,  10,  10, 10,  10, 10,  108, 10, 10,  10, 10,  10,
-                10,  10,  10, 10,  10, 10,  10,  10,  10, 10,  10, 10,  10,  10, 10,  10, 132, 10,
-                10,  10,  10, 10,  10, 10,  10,  10,  10, 10,  10, 10,  10,  10, 10,  10, 10,  10,
-                10,  10,  10, 160, 10, 10,  10,  10,  10, 10,  10, 10,  10,  10, 10,  10, 10,  10,
-                10,  10,  10, 10,  10, 10,  176, 10,  10, 10,  10, 10,  10,  10, 10,  10, 10,  10,
-                10,  10,  10, 10,  10, 186, 10,  10,  10, 195, 10, 192, 10,  10, 10,  10, 10,  10,
-                10,  10,  10, 10,  10, 10,  10,  206, 10, 10,  10, 10,  10,  10, 10,  10, 10,  10,
-                10,  10,  10, 10,  10, 10,  10,  10,  10, 10,  10, 228, 10,  10, 10,  10, 10,  234,
-                10,  10,  10, 10,  10, 10,  10,  10,  10, 10,  10, 10,  10,  10, 10,  10, 10,  10,
-                10,  10,  10, 10,  10, 10,  10,  10,  10, 10,  10, 10,  10,  10, 10,  10, 10,  10,
-                10,  10,  10, 10,  10, 10,  10,  10,  10, 10,  10, 10,  10,  10, 285, 10, 10,  10,
-                289, 10,  10, 10,  10, 10,  10,  10,  10, 10,  10, 10,  10,  10, 10,  10, 305, 10,
-                10,  10,  10, 0,   0,  0,   0,   0,   0,  0,
+                0,  209, 0,  0,  0,  0,   0,  8,  8,  10, 10, 0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,   0,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  35, 35, 0,
+                38, 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,  71, 71, 71, 71, 71, 71,
+                71, 71,  71, 71, 0,  0,   0,  0,  0,  0,  10, 88, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 192, 10, 10, 10, 196, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 0,  0,  0,  0,  0,  0,  0,
         },
         {
-                0,  221, 0,  0,   0,  0,  0,   8,  8,  10, 10,  0,  0,  0,  0,  0,  0,   0,  0,  0,
-                0,  0,   0,  0,   0,  0,  0,   0,  0,  0,  0,   0,  0,  0,  0,  35, 35,  0,  38, 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,  71,  71, 71, 71, 71, 71, 71,  71, 71, 71,
-                0,  0,   0,  0,   0,  0,  10,  88, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 10,  10, 10,  10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 10,  10, 10,  10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 10,  10, 10,  10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 10,  10, 10,  10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 182, 10, 10,  10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 197, 10, 10, 10,
-                10, 10,  10, 204, 10, 10, 207, 10, 10, 10, 211, 10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 10,  10, 10,  10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 10,  10, 10,  10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 10,  10, 10,  10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 10,  10, 10,  10, 10, 10,  10, 10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 10,  10, 10,  10, 10, 10,  10, 10, 0,  0,   0,  0,  0,  0,  0,
+                0,   218, 0,  0,   0,  0,   0,  8,   8,   10, 10, 0,  0,  0,  0,  0,  0,  0,  0,
+                0,   0,   0,  0,   0,  0,   0,  0,   0,   0,  0,  0,  0,  0,  0,  0,  35, 35, 0,
+                38,  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,  71, 71, 71, 71, 71, 71,
+                71,  71,  71, 71,  0,  0,   0,  0,   0,   0,  10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10,  10,  10, 10,  10, 10,  10, 10,  10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                115, 10,  10, 10,  10, 10,  10, 10,  123, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10,  135, 10, 10,  10, 10,  10, 10,  10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10,  10,  10, 10,  10, 158, 10, 10,  10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10,  10,  10, 10,  10, 177, 10, 10,  10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10,  10,  10, 194, 10, 10,  10, 10,  10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                210, 10,  10, 10,  10, 10,  10, 10,  10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 228,
+                10,  10,  10, 10,  10, 234, 10, 10,  10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10,  10,  10, 10,  10, 10,  10, 10,  256, 10, 10, 10, 10, 10, 10, 10, 10, 10, 266,
+                10,  10,  10, 10,  10, 10,  10, 274, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 289,
+                10,  10,  10, 10,  10, 10,  10, 10,  10,  10, 10, 10, 0,  0,  0,  0,  0,  0,  0,
         },
         {
-                0,   230, 0,  0,  0,  0,   0,  8,  8,   10, 10, 0,   0,   0,  0,  0,  0,  0,   0,
-                0,   0,   0,  0,  0,  0,   0,  0,  0,   0,  0,  0,   0,   0,  0,  0,  35, 35,  0,
-                38,  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,   71, 71, 71, 71, 71,  71,
-                71,  71,  71, 71, 0,  0,   0,  0,  0,   0,  10, 10,  10,  10, 10, 10, 10, 10,  10,
-                10,  10,  10, 10, 10, 10,  10, 10, 10,  10, 10, 10,  10,  10, 10, 10, 10, 10,  10,
-                115, 10,  10, 10, 10, 10,  10, 10, 123, 10, 10, 10,  10,  10, 10, 10, 10, 10,  10,
-                10,  135, 10, 10, 10, 10,  10, 10, 10,  10, 10, 10,  10,  10, 10, 10, 10, 10,  10,
-                10,  10,  10, 10, 10, 158, 10, 10, 10,  10, 10, 10,  10,  10, 10, 10, 10, 10,  10,
-                10,  10,  10, 10, 10, 177, 10, 10, 10,  10, 10, 10,  10,  10, 10, 10, 10, 10,  10,
-                10,  10,  10, 10, 10, 10,  10, 10, 10,  10, 10, 10,  10,  10, 10, 10, 10, 10,  209,
-                10,  10,  10, 10, 10, 10,  10, 10, 10,  10, 10, 10,  222, 10, 10, 10, 10, 10,  10,
-                10,  10,  10, 10, 10, 10,  10, 10, 10,  10, 10, 240, 10,  10, 10, 10, 10, 246, 10,
-                10,  10,  10, 10, 10, 10,  10, 10, 10,  10, 10, 10,  10,  10, 10, 10, 10, 10,  10,
-                10,  268, 10, 10, 10, 10,  10, 10, 10,  10, 10, 278, 10,  10, 10, 10, 10, 10,  10,
-                286, 10,  10, 10, 10, 10,  10, 10, 10,  10, 10, 301, 10,  10, 10, 10, 10, 10,  10,
-                10,  10,  10, 10, 10, 0,   0,  0,  0,   0,  0,  0,
+                0,   236, 0,  0,   0,   0,  0,   8,  8,   10, 10,  0,  0,  0,   0,   0,   0,  0,
+                0,   0,   0,  0,   0,   0,  0,   0,  0,   0,  0,   0,  0,  0,   0,   0,   0,  35,
+                35,  0,   38, 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,   74, 71,
+                71,  71,  71, 71,  71,  71, 71,  71, 0,   0,  0,   0,  0,  0,   10,  10,  10, 10,
+                10,  10,  10, 10,  10,  10, 10,  10, 10,  10, 10,  10, 10, 10,  10,  10,  10, 10,
+                10,  10,  10, 10,  10,  10, 10,  10, 10,  10, 10,  10, 10, 10,  10,  10,  10, 126,
+                10,  10,  10, 130, 131, 10, 10,  10, 10,  10, 10,  10, 10, 140, 10,  10,  10, 10,
+                10,  10,  10, 10,  10,  10, 10,  10, 10,  10, 155, 10, 10, 10,  10,  10,  10, 10,
+                163, 10,  10, 10,  10,  10, 10,  10, 171, 10, 10,  10, 10, 10,  10,  10,  10, 10,
+                10,  10,  10, 10,  10,  10, 10,  10, 10,  10, 10,  10, 10, 10,  195, 10,  10, 10,
+                10,  10,  10, 10,  10,  10, 10,  10, 10,  10, 10,  10, 10, 10,  10,  214, 10, 10,
+                10,  10,  10, 226, 10,  10, 10,  10, 10,  10, 10,  10, 10, 10,  10,  10,  10, 10,
+                10,  10,  10, 10,  10,  10, 241, 10, 243, 10, 10,  10, 10, 10,  10,  10,  10, 10,
+                10,  254, 10, 10,  10,  10, 10,  10, 10,  10, 10,  10, 10, 10,  10,  10,  10, 10,
+                10,  10,  10, 10,  10,  10, 10,  10, 10,  10, 10,  10, 10, 10,  10,  10,  10, 10,
+                10,  10,  10, 10,  10,  10, 10,  10, 10,  0,  0,   0,  0,  0,   0,   0,
         },
         {
-                0,   248, 0,   0,   0,   0,  0,  8,  8,   10,  10,  0,   0,  0,   0,  0,   0,  0,
-                0,   0,   0,   0,   0,   0,  0,  0,  0,   0,   0,   0,   0,  0,   0,  0,   0,  35,
-                35,  0,   38,  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,   74, 71,
-                71,  71,  71,  71,  71,  71, 71, 71, 0,   0,   0,   0,   0,  0,   10, 10,  10, 10,
-                10,  10,  10,  10,  10,  10, 10, 10, 10,  10,  10,  10,  10, 10,  10, 10,  10, 10,
-                10,  10,  10,  10,  10,  10, 10, 10, 10,  10,  10,  10,  10, 10,  10, 10,  10, 126,
-                10,  10,  10,  130, 131, 10, 10, 10, 10,  10,  10,  10,  10, 140, 10, 10,  10, 10,
-                10,  10,  10,  10,  10,  10, 10, 10, 10,  10,  155, 10,  10, 10,  10, 10,  10, 10,
-                163, 10,  10,  10,  10,  10, 10, 10, 171, 10,  10,  10,  10, 10,  10, 10,  10, 10,
-                10,  10,  10,  10,  10,  10, 10, 10, 10,  10,  10,  10,  10, 10,  10, 10,  10, 10,
-                10,  10,  10,  10,  10,  10, 10, 10, 10,  10,  10,  210, 10, 10,  10, 10,  10, 10,
-                10,  10,  10,  10,  10,  10, 10, 10, 10,  226, 10,  10,  10, 10,  10, 238, 10, 10,
-                10,  10,  10,  10,  10,  10, 10, 10, 10,  10,  10,  10,  10, 10,  10, 10,  10, 10,
-                253, 10,  255, 10,  10,  10, 10, 10, 10,  10,  10,  10,  10, 266, 10, 10,  10, 10,
-                10,  10,  10,  10,  10,  10, 10, 10, 10,  10,  10,  10,  10, 10,  10, 10,  10, 10,
-                10,  10,  10,  10,  10,  10, 10, 10, 10,  10,  10,  10,  10, 10,  10, 10,  10, 10,
-                10,  10,  10,  0,   0,   0,  0,  0,  0,   0,
+                0,   265, 0,   0,   0,  0,  0,   8,  8,   10,  10,  0,   0,   0,   0,   0,  0,  0,
+                0,   0,   0,   0,   0,  0,  0,   0,  0,   0,   0,   0,   0,   0,   0,   0,  0,  35,
+                35,  0,   38,  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,  71, 71,
+                71,  71,  71,  71,  77, 71, 71,  71, 0,   0,   0,   0,   0,   0,   10,  10, 10, 10,
+                10,  10,  10,  10,  10, 10, 10,  10, 10,  10,  10,  10,  10,  10,  10,  10, 10, 10,
+                10,  110, 10,  10,  10, 10, 10,  10, 10,  10,  10,  10,  10,  10,  10,  10, 10, 10,
+                10,  10,  10,  10,  10, 10, 10,  10, 10,  10,  10,  138, 10,  142, 141, 10, 10, 10,
+                10,  10,  10,  10,  10, 10, 10,  10, 153, 10,  10,  10,  10,  10,  10,  10, 10, 10,
+                10,  10,  10,  10,  10, 10, 10,  10, 10,  10,  10,  10,  175, 10,  10,  10, 10, 10,
+                10,  10,  183, 10,  10, 10, 10,  10, 189, 10,  10,  10,  10,  10,  10,  10, 10, 10,
+                199, 10,  10,  10,  10, 10, 10,  10, 10,  208, 10,  10,  10,  10,  10,  10, 10, 10,
+                10,  10,  10,  232, 10, 10, 10,  10, 10,  10,  227, 10,  10,  10,  231, 10, 10, 10,
+                10,  10,  255, 10,  10, 10, 10,  10, 10,  10,  10,  10,  10,  10,  10,  10, 10, 10,
+                253, 10,  10,  10,  10, 10, 259, 10, 10,  262, 10,  10,  10,  10,  10,  10, 10, 10,
+                10,  10,  10,  10,  10, 10, 10,  10, 10,  280, 10,  10,  10,  10,  10,  10, 10, 10,
+                10,  10,  291, 10,  10, 10, 10,  10, 10,  0,   0,   0,   0,   0,   0,   0,
         },
         {
-                0,   277, 0,  0,   0,   0,  0,  8,   8,   10, 10, 0,   0,   0,   0,   0,   0,  0,
-                0,   0,   0,  0,   0,   0,  0,  0,   0,   0,  0,  0,   0,   0,   0,   0,   0,  35,
-                35,  0,   38, 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,   71, 71,
-                71,  71,  71, 71,  77,  71, 71, 71,  0,   0,  0,  0,   0,   0,   10,  10,  10, 10,
-                10,  10,  10, 10,  10,  10, 10, 10,  10,  10, 10, 10,  10,  10,  10,  10,  10, 10,
-                10,  110, 10, 10,  10,  10, 10, 10,  10,  10, 10, 10,  10,  10,  10,  10,  10, 10,
-                10,  10,  10, 10,  10,  10, 10, 10,  10,  10, 10, 138, 10,  142, 141, 10,  10, 10,
-                10,  10,  10, 10,  10,  10, 10, 10,  153, 10, 10, 10,  10,  10,  10,  10,  10, 10,
-                10,  10,  10, 10,  10,  10, 10, 10,  10,  10, 10, 10,  175, 10,  10,  10,  10, 10,
-                10,  10,  10, 10,  10,  10, 10, 188, 10,  10, 10, 10,  10,  194, 10,  10,  10, 10,
-                10,  10,  10, 10,  10,  10, 10, 10,  10,  10, 10, 10,  10,  10,  10,  214, 10, 10,
-                10,  10,  10, 220, 10,  10, 10, 10,  10,  10, 10, 10,  10,  10,  10,  244, 10, 10,
-                10,  10,  10, 10,  239, 10, 10, 10,  243, 10, 10, 10,  10,  10,  267, 10,  10, 10,
-                10,  10,  10, 10,  10,  10, 10, 10,  10,  10, 10, 10,  265, 10,  10,  10,  10, 10,
-                271, 10,  10, 274, 10,  10, 10, 10,  10,  10, 10, 10,  10,  10,  10,  10,  10, 10,
-                10,  10,  10, 292, 10,  10, 10, 10,  10,  10, 10, 10,  10,  10,  303, 10,  10, 10,
-                10,  10,  10, 0,   0,   0,  0,  0,   0,   0,
+                0,   269, 0,  0,  0,   0,  0,  8,  8,  10,  10,  0,   0,  0,   0,  0,  0,   0,   0,
+                0,   0,   0,  0,  0,   0,  0,  0,  0,  0,   0,   0,   0,  0,   0,  0,  35,  35,  0,
+                38,  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,  71,  71, 71, 71,  71,  71,
+                71,  71,  71, 71, 0,   0,  0,  0,  0,  0,   10,  10,  10, 10,  10, 10, 10,  10,  10,
+                10,  10,  10, 10, 10,  10, 10, 10, 10, 10,  10,  10,  10, 109, 10, 10, 10,  10,  10,
+                119, 10,  10, 10, 10,  10, 10, 10, 10, 10,  10,  10,  10, 10,  10, 10, 10,  10,  10,
+                10,  10,  10, 10, 10,  10, 10, 10, 10, 10,  10,  145, 10, 10,  10, 10, 10,  151, 10,
+                10,  10,  10, 10, 10,  10, 10, 10, 10, 10,  10,  10,  10, 166, 10, 10, 10,  10,  10,
+                10,  10,  10, 10, 10,  10, 10, 10, 10, 10,  182, 10,  10, 10,  10, 10, 188, 10,  10,
+                203, 10,  10, 10, 10,  10, 10, 10, 10, 10,  10,  10,  10, 10,  10, 10, 207, 10,  10,
+                10,  10,  10, 10, 10,  10, 10, 10, 10, 10,  10,  10,  10, 10,  10, 10, 10,  10,  10,
+                10,  10,  10, 10, 233, 10, 10, 10, 10, 10,  10,  10,  10, 10,  10, 10, 10,  10,  10,
+                10,  10,  10, 10, 10,  10, 10, 10, 10, 257, 10,  10,  10, 10,  10, 10, 10,  10,  10,
+                267, 10,  10, 10, 10,  10, 10, 10, 10, 10,  10,  10,  10, 10,  10, 10, 10,  10,  10,
+                10,  10,  10, 10, 10,  10, 10, 10, 10, 10,  10,  10,  0,  0,   0,  0,  0,   0,   0,
         },
         {
-                0,   281, 0,   0,   0,  0,  0,   8,  8,   10, 10,  0,  0,   0,  0,  0,  0,   0,
-                0,   0,   0,   0,   0,  0,  0,   0,  0,   0,  0,   0,  0,   0,  0,  0,  0,   35,
-                35,  0,   38,  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,  71,  71,
-                71,  71,  71,  71,  71, 71, 71,  71, 0,   0,  0,   0,  0,   0,  10, 10, 10,  10,
-                10,  10,  10,  10,  10, 10, 10,  10, 10,  10, 10,  10, 10,  10, 10, 10, 10,  10,
-                109, 10,  10,  10,  10, 10, 119, 10, 10,  10, 10,  10, 10,  10, 10, 10, 10,  10,
-                10,  10,  10,  10,  10, 10, 10,  10, 10,  10, 10,  10, 10,  10, 10, 10, 10,  10,
-                145, 10,  10,  10,  10, 10, 151, 10, 10,  10, 10,  10, 10,  10, 10, 10, 10,  10,
-                10,  10,  10,  166, 10, 10, 10,  10, 10,  10, 10,  10, 10,  10, 10, 10, 10,  10,
-                10,  10,  10,  10,  10, 10, 187, 10, 10,  10, 10,  10, 193, 10, 10, 10, 10,  10,
-                10,  10,  10,  202, 10, 10, 10,  10, 10,  10, 10,  10, 10,  10, 10, 10, 10,  10,
-                10,  10,  219, 10,  10, 10, 10,  10, 10,  10, 10,  10, 10,  10, 10, 10, 10,  10,
-                10,  10,  10,  10,  10, 10, 10,  10, 10,  10, 245, 10, 10,  10, 10, 10, 10,  10,
-                10,  10,  10,  10,  10, 10, 10,  10, 10,  10, 10,  10, 10,  10, 10, 10, 269, 10,
-                10,  10,  10,  10,  10, 10, 10,  10, 279, 10, 10,  10, 10,  10, 10, 10, 10,  10,
-                10,  10,  10,  10,  10, 10, 10,  10, 10,  10, 10,  10, 10,  10, 10, 10, 10,  10,
-                10,  10,  10,  0,   0,  0,  0,   0,  0,   0,
+                0,  276, 0,  0,  0,  0,  0,  8,  8,  10, 10,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,   0,  0,  0,  0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  35, 35, 0,
+                38, 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,  71, 71, 71, 71, 71, 71,
+                71, 71,  71, 71, 0,  0,  0,  0,  0,  0,  10,  10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 201, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 0,  0,  0,  0,  0,  0,  0,
         },
         {
-                0,  288, 0,  0,  0,  0,  0,  8,  8,  10, 10, 0,  0,  0,  0,  0,   0,  0,  0,  0,
-                0,  0,   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  35,  35, 0,  38, 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,  71, 71, 71, 71, 71, 71,  71, 71, 71, 71,
-                0,  0,   0,  0,  0,  0,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 216, 10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10, 0,  0,  0,  0,  0,  0,  0,
+                0,  284, 0,  0,  0,  0,  0,  8,  8,   10, 10, 0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,   0,  0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0,  35, 35, 0,
+                38, 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,  71, 71, 71, 71, 75, 71,
+                71, 71,  71, 71, 0,  0,  0,  0,  0,   0,  10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 260, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 0,  0,  0,  0,  0,  0,  0,
         },
         {
-                0,  296, 0,  0,  0,  0,  0,  8,  8,   10, 10, 0,  0,  0,  0,  0,   0,  0,  0,  0,
-                0,  0,   0,  0,  0,  0,  0,  0,  0,   0,  0,  0,  0,  0,  0,  35,  35, 0,  38, 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,  71, 71, 71, 71, 75, 71,  71, 71, 71, 71,
-                0,  0,   0,  0,  0,  0,  10, 10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 196, 10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 272, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,
-                10, 10,  10, 10, 10, 10, 10, 10, 10,  0,  0,  0,  0,  0,  0,  0,
+                0,  9,  0,  0,  0,  0,  0,  8,  8,  10, 10, 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  35, 35, 0,  38, 0,  53, 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,  71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 0,  0,  0,  0,
+                0,  0,  10, 10, 10, 90, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+                10, 10, 10, 0,  0,  0,  0,  0,  0,  0,
         },
         {
-                0,  9,  0,  0,  0,  0,  0,  8,  8,  10, 10, 0,  0,  0,  0,  0,  0,  0,  0,  0,
-                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  35, 35, 0,  38, 0,
-                53, 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,  71, 71, 71, 71, 71, 71, 71, 71, 71, 71,
-                0,  0,  0,  0,  0,  0,  10, 10, 10, 90, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10, 10, 0,  0,  0,  0,  0,  0,  0,
+                0,  9,  0,  0,  0,  0,  0,  8,  8,  10, 10,  0,  0,  0,  0,   0,   0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   0,  0,  0,  0,   0,   35, 35, 0,
+                38, 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,  71, 71,  71,  71, 71, 71,
+                71, 71, 71, 71, 0,  0,  0,  0,  0,  0,  10,  10, 10, 10, 10,  10,  10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10,  10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10,  10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10,  10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10,  10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 186, 10,  10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10,  10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  225, 10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10,  10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10,  10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10, 10,  10,  10, 10, 10,
+                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 296, 10, 0,  0,  0,   0,   0,  0,  0,
         },
         {
-                0,  9,  0,  0,  0,  0,  0,  8,   8,  10, 10,  0,  0,  0,  0,  0,  0,   0,  0,  0,
-                0,  0,  0,  0,  0,  0,  0,  0,   0,  0,  0,   0,  0,  0,  0,  35, 35,  0,  38, 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,  71,  71, 71, 71, 71, 71, 71,  71, 71, 71,
-                0,  0,  0,  0,  0,  0,  10, 10,  10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 191, 10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10,  10, 10, 10, 10, 10, 237, 10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 10,  10, 10, 10,  10, 10, 10, 10, 10, 10,  10, 10, 10,
-                10, 10, 10, 10, 10, 10, 10, 308, 10, 0,  0,   0,  0,  0,  0,  0,
+                0, 297, 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, 35, 35, 0, 38, 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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,
         },
         {
-                0, 309, 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, 35, 35, 0, 38, 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,
-                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,  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, 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, 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, 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, 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, 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, 298, 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, 35, 35, 0, 38,  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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0, 0,  0,  0, 300, 0, 0, 0, 0, 0,
         },
         {
-                0, 310, 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, 35,  35, 0, 38, 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,
-                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,   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, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 312, 0,  0, 0,  0, 0,
+                0, 302, 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, 35, 35, 0, 38, 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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,
         },
         {
-                0, 314, 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, 35, 35, 0, 38, 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,
-                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,  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, 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, 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, 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, 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, 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, 315, 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, 35, 35, 0, 38, 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,
-                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,  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, 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, 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, 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, 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, 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, 303, 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, 35, 35, 0, 38, 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, 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, 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, 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, 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,  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, 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, 0, 0, 0, 0, 0,  0,  0, 0,  0, 0, 0, 0, 0,
         },
 };
 
-static int8_t accepts[316] = {
-        -1, -1, 97, 97, 100, 71, 77, 100, 45, 44, 44, 61, 86, 66, 70, 94, 91, 47, 48, 59, 84, 57,
-        55, 82, 54, 58, 56,  83, 96, 53,  1,  -1, -1, 1,  60, -1, -1, 99, 98, 85, 2,  1,  1,  -1,
-        -1, 1,  -1, -1, 1,   2,  -1, -1,  1,  -1, 2,  2,  74, 73, 95, 79, 62, 87, 81, 75, 76, 78,
-        80, 63, 88, 72, 100, 46, 46, 6,   46, 46, 46, 46, 46, 12, 51, 52, 65, 90, 69, 93, 44, 44,
-        44, 44, 44, 44, 44,  44, 44, 44,  44, 44, 44, 44, 44, 36, 44, 44, 44, 44, 44, 37, 44, 44,
-        44, 44, 44, 38, 44,  44, 44, 44,  15, 44, 44, 44, 44, 34, 44, 44, 44, 13, 44, 44, 44, 43,
-        44, 44, 44, 44, 44,  44, 31, 44,  44, 23, 44, 44, 44, 44, 16, 44, 44, 44, 44, 44, 44, 14,
-        44, 44, 44, 44, 44,  17, 10, 44,  44, 44, 7,  44, 44, 42, 44, 44, 44, 44, 4,  44, 44, 27,
-        44, 8,  44, 44, 44,  44, 26, 44,  5,  19, 44, 44, 21, 44, 44, 44, 44, 44, 40, 44, 44, 24,
-        44, 44, 44, 44, 44,  44, 25, 44,  44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 28, 44, 44,
-        20, 44, 44, 44, 44,  44, 44, 44,  44, 41, 44, 44, 44, 44, 44, 44, 44, 29, 44, 44, 44, 44,
-        44, 33, 44, 44, 44,  18, 44, 44,  44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
-        44, 44, 35, 44, 44,  44, 44, 39,  44, 44, 44, 44, 11, 44, 44, 44, 3,  44, 44, 44, 44, 44,
-        44, 22, 44, 44, 44,  44, 44, 44,  44, 32, 44, 44, 44, 44, 9,  44, 44, 44, 44, 44, 44, 44,
-        30, 49, 64, 89, 68,  92, 50, 67,
+static int8_t accepts[304] = {
+        -1, -1, 95, 95, 98, 69, 75, 98, 43, 42, 42, 59, 84, 64, 68, 92, 89, 45, 46, 57, 82, 55,
+        53, 80, 52, 56, 54, 81, 94, 51, 1,  -1, -1, 1,  58, -1, -1, 97, 96, 83, 2,  1,  1,  -1,
+        -1, 1,  -1, -1, 1,  2,  -1, -1, 1,  -1, 2,  2,  72, 71, 93, 77, 60, 85, 79, 73, 74, 76,
+        78, 61, 86, 70, 98, 44, 44, 6,  44, 44, 44, 44, 44, 12, 49, 50, 63, 88, 67, 91, 42, 42,
+        42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 34, 42, 42, 42, 42, 42, 35, 42, 42,
+        42, 42, 42, 36, 42, 42, 42, 42, 15, 42, 42, 42, 42, 32, 42, 42, 42, 13, 42, 42, 42, 41,
+        42, 42, 42, 42, 42, 42, 29, 42, 42, 24, 42, 42, 42, 42, 16, 42, 42, 42, 42, 42, 42, 14,
+        42, 42, 42, 42, 42, 17, 10, 42, 42, 42, 7,  42, 42, 40, 42, 42, 42, 42, 4,  42, 42, 25,
+        42, 8,  42, 5,  20, 42, 42, 22, 42, 42, 42, 42, 42, 38, 42, 42, 42, 42, 42, 42, 42, 42,
+        42, 42, 42, 42, 26, 42, 42, 19, 42, 42, 21, 42, 42, 42, 42, 42, 42, 42, 42, 39, 42, 42,
+        42, 42, 42, 42, 42, 27, 42, 42, 42, 42, 42, 31, 42, 42, 42, 18, 42, 42, 42, 42, 42, 42,
+        42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 33, 42, 42, 42, 42, 37, 42, 42, 42, 42,
+        11, 42, 42, 42, 3,  42, 42, 42, 42, 42, 42, 23, 42, 42, 42, 42, 42, 42, 42, 30, 42, 42,
+        42, 42, 9,  42, 42, 42, 42, 42, 42, 42, 28, 47, 62, 87, 66, 90, 48, 65,
 };
 
 Token Lexer::next() {
@@ -1063,12 +1012,18 @@
         return Token(Token::END_OF_FILE, startOffset, 0);
     }
     int16_t state = 1;
-    while (fOffset < fLength) {
-        if ((uint8_t)fText[fOffset] >= 127) {
-            ++fOffset;
+    for (;;) {
+        if (fOffset >= fLength) {
+            if (accepts[state] == -1) {
+                return Token(Token::END_OF_FILE, startOffset, 0);
+            }
             break;
         }
-        int16_t newState = transitions[mappings[(int)fText[fOffset]]][state];
+        uint8_t c = (uint8_t)fText[fOffset];
+        if (c <= 8 || c >= 127) {
+            c = INVALID_CHAR;
+        }
+        int16_t newState = transitions[mappings[c]][state];
         if (!newState) {
             break;
         }
@@ -1079,4 +1034,4 @@
     return Token(kind, startOffset, fOffset - startOffset);
 }
 
-}  // namespace
+}  // namespace SkSL
diff --git a/src/sksl/SkSLLexer.h b/src/sksl/SkSLLexer.h
index 0ad8d26..9a16696 100644
--- a/src/sksl/SkSLLexer.h
+++ b/src/sksl/SkSLLexer.h
@@ -53,6 +53,8 @@
         DISCARD,
 #undef RETURN
         RETURN,
+#undef NULL_LITERAL
+        NULL_LITERAL,
 #undef IN
         IN,
 #undef OUT
@@ -63,12 +65,6 @@
         UNIFORM,
 #undef CONST
         CONST,
-#undef LOWP
-        LOWP,
-#undef MEDIUMP
-        MEDIUMP,
-#undef HIGHP
-        HIGHP,
 #undef FLAT
         FLAT,
 #undef NOPERSPECTIVE
@@ -245,5 +241,5 @@
     int32_t fOffset;
 };
 
-}  // namespace
+}  // namespace SkSL
 #endif
diff --git a/src/sksl/SkSLMetalCodeGenerator.cpp b/src/sksl/SkSLMetalCodeGenerator.cpp
index 13c171a..3aede1f 100644
--- a/src/sksl/SkSLMetalCodeGenerator.cpp
+++ b/src/sksl/SkSLMetalCodeGenerator.cpp
@@ -26,6 +26,8 @@
 #define SPECIAL(x) std::make_pair(kSpecial_IntrinsicKind, k ## x ## _SpecialIntrinsic)
     fIntrinsicMap[String("texture")]            = SPECIAL(Texture);
     fIntrinsicMap[String("mod")]                = SPECIAL(Mod);
+    fIntrinsicMap[String("equal")]              = METAL(Equal);
+    fIntrinsicMap[String("notEqual")]           = METAL(NotEqual);
     fIntrinsicMap[String("lessThan")]           = METAL(LessThan);
     fIntrinsicMap[String("lessThanEqual")]      = METAL(LessThanEqual);
     fIntrinsicMap[String("greaterThan")]        = METAL(GreaterThan);
@@ -172,6 +174,12 @@
         case kMetal_IntrinsicKind:
             this->writeExpression(*c.fArguments[0], kSequence_Precedence);
             switch ((MetalIntrinsic) intrinsicId) {
+                case kEqual_MetalIntrinsic:
+                    this->write(" == ");
+                    break;
+                case kNotEqual_MetalIntrinsic:
+                    this->write(" != ");
+                    break;
                 case kLessThan_MetalIntrinsic:
                     this->write(" < ");
                     break;
@@ -210,7 +218,8 @@
     } else if (c.fFunction.fBuiltin && "dFdx" == c.fFunction.fName) {
         this->write("dfdx");
     } else if (c.fFunction.fBuiltin && "dFdy" == c.fFunction.fName) {
-        this->write("dfdy");
+        // Flipping Y also negates the Y derivatives.
+        this->write((fProgram.fSettings.fFlipY) ? "-dfdy" : "dfdy");
     } else {
         this->writeName(c.fFunction.fName);
     }
@@ -248,18 +257,82 @@
 }
 
 void MetalCodeGenerator::writeInverseHack(const Expression& mat) {
-    String name = "ERROR_MatrixInverseNotImplementedFor_" + mat.fType.name();
-    if (mat.fType == *fContext.fFloat2x2_Type) {
-        name = "_inverse2";
+    String typeName = mat.fType.name();
+    String name = typeName + "_inverse";
+    if (mat.fType == *fContext.fFloat2x2_Type || mat.fType == *fContext.fHalf2x2_Type) {
         if (fWrittenIntrinsics.find(name) == fWrittenIntrinsics.end()) {
             fWrittenIntrinsics.insert(name);
             fExtraFunctions.writeText((
-                "float2x2 " + name + "(float2x2 m) {"
+                typeName + " " + name + "(" + typeName + " m) {"
                 "    return float2x2(m[1][1], -m[0][1], -m[1][0], m[0][0]) * (1/determinant(m));"
                 "}"
             ).c_str());
         }
     }
+    else if (mat.fType == *fContext.fFloat3x3_Type || mat.fType == *fContext.fHalf3x3_Type) {
+        if (fWrittenIntrinsics.find(name) == fWrittenIntrinsics.end()) {
+            fWrittenIntrinsics.insert(name);
+            fExtraFunctions.writeText((
+                typeName + " " +  name + "(" + typeName + " m) {"
+                "    float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2];"
+                "    float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2];"
+                "    float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2];"
+                "    float b01 = a22 * a11 - a12 * a21;"
+                "    float b11 = -a22 * a10 + a12 * a20;"
+                "    float b21 = a21 * a10 - a11 * a20;"
+                "    float det = a00 * b01 + a01 * b11 + a02 * b21;"
+                "    return " + typeName +
+                "                   (b01, (-a22 * a01 + a02 * a21), (a12 * a01 - a02 * a11),"
+                "                    b11, (a22 * a00 - a02 * a20), (-a12 * a00 + a02 * a10),"
+                "                    b21, (-a21 * a00 + a01 * a20), (a11 * a00 - a01 * a10)) * "
+                "                   (1/det);"
+                "}"
+            ).c_str());
+        }
+    }
+    else if (mat.fType == *fContext.fFloat4x4_Type || mat.fType == *fContext.fHalf4x4_Type) {
+        if (fWrittenIntrinsics.find(name) == fWrittenIntrinsics.end()) {
+            fWrittenIntrinsics.insert(name);
+            fExtraFunctions.writeText((
+                typeName + " " +  name + "(" + typeName + " m) {"
+                "    float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3];"
+                "    float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3];"
+                "    float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3];"
+                "    float a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3];"
+                "    float b00 = a00 * a11 - a01 * a10;"
+                "    float b01 = a00 * a12 - a02 * a10;"
+                "    float b02 = a00 * a13 - a03 * a10;"
+                "    float b03 = a01 * a12 - a02 * a11;"
+                "    float b04 = a01 * a13 - a03 * a11;"
+                "    float b05 = a02 * a13 - a03 * a12;"
+                "    float b06 = a20 * a31 - a21 * a30;"
+                "    float b07 = a20 * a32 - a22 * a30;"
+                "    float b08 = a20 * a33 - a23 * a30;"
+                "    float b09 = a21 * a32 - a22 * a31;"
+                "    float b10 = a21 * a33 - a23 * a31;"
+                "    float b11 = a22 * a33 - a23 * a32;"
+                "    float det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - "
+                "                b04 * b07 + b05 * b06;"
+                "    return " + typeName + "(a11 * b11 - a12 * b10 + a13 * b09,"
+                "                            a02 * b10 - a01 * b11 - a03 * b09,"
+                "                            a31 * b05 - a32 * b04 + a33 * b03,"
+                "                            a22 * b04 - a21 * b05 - a23 * b03,"
+                "                            a12 * b08 - a10 * b11 - a13 * b07,"
+                "                            a00 * b11 - a02 * b08 + a03 * b07,"
+                "                            a32 * b02 - a30 * b05 - a33 * b01,"
+                "                            a20 * b05 - a22 * b02 + a23 * b01,"
+                "                            a10 * b10 - a11 * b08 + a13 * b06,"
+                "                            a01 * b08 - a00 * b10 - a03 * b06,"
+                "                            a30 * b04 - a31 * b02 + a33 * b00,"
+                "                            a21 * b02 - a20 * b04 - a23 * b00,"
+                "                            a11 * b07 - a10 * b09 - a12 * b06,"
+                "                            a00 * b09 - a01 * b07 + a02 * b06,"
+                "                            a31 * b01 - a30 * b03 - a32 * b00,"
+                "                            a20 * b03 - a21 * b01 + a22 * b00) / det;"
+                "}"
+            ).c_str());
+        }
+    }
     this->write(name);
 }
 
@@ -300,8 +373,8 @@
 // of type 'arg'.
 String MetalCodeGenerator::getMatrixConstructHelper(const Type& matrix, const Type& arg) {
     String key = matrix.name() + arg.name();
-    auto found = fMatrixConstructHelpers.find(key);
-    if (found != fMatrixConstructHelpers.end()) {
+    auto found = fHelpers.find(key);
+    if (found != fHelpers.end()) {
         return found->second;
     }
     String name;
@@ -331,8 +404,34 @@
             fExtraFunctions.writeText(")");
         }
         fExtraFunctions.writeText(");\n}\n");
-    }
-    else if (matrix.rows() == 2 && matrix.columns() == 2) {
+    } else if (arg.kind() == Type::kMatrix_Kind) {
+        // creating a matrix from another matrix
+        int argColumns = arg.columns();
+        int argRows = arg.rows();
+        name = "float" + to_string(columns) + "x" + to_string(rows) + "_from_float" +
+               to_string(argColumns) + "x" + to_string(argRows);
+        fExtraFunctions.printf("float%dx%d %s(float%dx%d m) {\n",
+                               columns, rows, name.c_str(), argColumns, argRows);
+        fExtraFunctions.printf("    return float%dx%d(", columns, rows);
+        for (int i = 0; i < columns; ++i) {
+            if (i > 0) {
+                fExtraFunctions.writeText(", ");
+            }
+            fExtraFunctions.printf("float%d(", rows);
+            for (int j = 0; j < rows; ++j) {
+                if (j > 0) {
+                    fExtraFunctions.writeText(", ");
+                }
+                if (i < argColumns && j < argRows) {
+                    fExtraFunctions.printf("m[%d][%d]", i, j);
+                } else {
+                    fExtraFunctions.writeText("0");
+                }
+            }
+            fExtraFunctions.writeText(")");
+        }
+        fExtraFunctions.writeText(");\n}\n");
+    } else if (matrix.rows() == 2 && matrix.columns() == 2 && arg == *fContext.fFloat4_Type) {
         // float2x2(float4) doesn't work, need to split it into float2x2(float2, float2)
         name = "float2x2_from_float4";
         fExtraFunctions.printf(
@@ -341,12 +440,11 @@
             "}\n",
             name.c_str()
         );
-    }
-    else {
+    } else {
         SkASSERT(false);
         name = "<error>";
     }
-    fMatrixConstructHelpers[key] = name;
+    fHelpers[key] = name;
     return name;
 }
 
@@ -380,15 +478,14 @@
         for (const auto& arg : c.fArguments) {
             this->write(separator);
             separator = ", ";
-            if (Type::kMatrix_Kind == c.fType.kind() && Type::kScalar_Kind == arg->fType.kind()) {
-                // float2x2(float, float, float, float) doesn't work in Metal 1, so we need to merge
-                // to float2x2(float2, float2).
+            if (Type::kMatrix_Kind == c.fType.kind() && arg->fType.columns() != c.fType.rows()) {
+                // merge scalars and smaller vectors together
                 if (!scalarCount) {
                     this->writeType(c.fType.componentType());
                     this->write(to_string(c.fType.rows()));
                     this->write("(");
                 }
-                ++scalarCount;
+                scalarCount += arg->fType.columns();
             }
             this->writeExpression(*arg, kSequence_Precedence);
             if (scalarCount && scalarCount == c.fType.rows()) {
@@ -401,8 +498,12 @@
 }
 
 void MetalCodeGenerator::writeFragCoord() {
-    this->write("float4(_fragCoord.x, _anonInterface0.u_skRTHeight - _fragCoord.y, 0.0, "
-                "_fragCoord.w)");
+    if (fProgram.fInputs.fRTHeight) {
+        this->write("float4(_fragCoord.x, _anonInterface0.u_skRTHeight - _fragCoord.y, 0.0, "
+                    "_fragCoord.w)");
+    } else {
+        this->write("float4(_fragCoord.x, _fragCoord.y, 0.0, _fragCoord.w)");
+    }
 }
 
 void MetalCodeGenerator::writeVariableReference(const VariableReference& ref) {
@@ -476,10 +577,23 @@
 }
 
 void MetalCodeGenerator::writeSwizzle(const Swizzle& swizzle) {
+    int last = swizzle.fComponents.back();
+    if (last == SKSL_SWIZZLE_0 || last == SKSL_SWIZZLE_1) {
+        this->writeType(swizzle.fType);
+        this->write("(");
+    }
     this->writeExpression(*swizzle.fBase, kPostfix_Precedence);
     this->write(".");
     for (int c : swizzle.fComponents) {
-        this->write(&("x\0y\0z\0w\0"[c * 2]));
+        if (c >= 0) {
+            this->write(&("x\0y\0z\0w\0"[c * 2]));
+        }
+    }
+    if (last == SKSL_SWIZZLE_0) {
+        this->write(", 0)");
+    }
+    else if (last == SKSL_SWIZZLE_1) {
+        this->write(", 1)");
     }
 }
 
@@ -523,10 +637,39 @@
     }
 }
 
+void MetalCodeGenerator::writeMatrixTimesEqualHelper(const Type& left, const Type& right,
+                                                     const Type& result) {
+    String key = "TimesEqual" + left.name() + right.name();
+    if (fHelpers.find(key) == fHelpers.end()) {
+        fExtraFunctions.printf("%s operator*=(thread %s& left, thread const %s& right) {\n"
+                               "    left = left * right;\n"
+                               "    return left;\n"
+                               "}", result.name().c_str(), left.name().c_str(),
+                                    right.name().c_str());
+    }
+}
+
 void MetalCodeGenerator::writeBinaryExpression(const BinaryExpression& b,
                                                Precedence parentPrecedence) {
     Precedence precedence = GetBinaryPrecedence(b.fOperator);
-    if (precedence >= parentPrecedence) {
+    bool needParens = precedence >= parentPrecedence;
+    switch (b.fOperator) {
+        case Token::EQEQ:
+            if (b.fLeft->fType.kind() == Type::kVector_Kind) {
+                this->write("all");
+                needParens = true;
+            }
+            break;
+        case Token::NEQ:
+            if (b.fLeft->fType.kind() == Type::kVector_Kind) {
+                this->write("!all");
+                needParens = true;
+            }
+            break;
+        default:
+            break;
+    }
+    if (needParens) {
         this->write("(");
     }
     if (Compiler::IsAssignment(b.fOperator) &&
@@ -537,6 +680,10 @@
         // dereference it here.
         this->write("*");
     }
+    if (b.fOperator == Token::STAREQ && b.fLeft->fType.kind() == Type::kMatrix_Kind &&
+        b.fRight->fType.kind() == Type::kMatrix_Kind) {
+        this->writeMatrixTimesEqualHelper(b.fLeft->fType, b.fRight->fType, b.fType);
+    }
     this->writeExpression(*b.fLeft, precedence);
     if (b.fOperator != Token::EQ && Compiler::IsAssignment(b.fOperator) &&
         Expression::kSwizzle_Kind == b.fLeft->fKind && !b.fLeft->hasSideEffects()) {
@@ -557,7 +704,7 @@
         this->write(String(" ") + Compiler::OperatorName(b.fOperator) + " ");
     }
     this->writeExpression(*b.fRight, precedence);
-    if (precedence >= parentPrecedence) {
+    if (needParens) {
         this->write(")");
     }
 }
@@ -688,9 +835,7 @@
             }
         }
         if (fProgram.fKind == Program::kFragment_Kind) {
-            if (fInterfaceBlockNameMap.empty()) {
-            // FIXME - Possibly have a different way of passing in u_skRTHeight or flip y axis
-            // in a different way altogether.
+            if (fProgram.fInputs.fRTHeight && fInterfaceBlockNameMap.empty()) {
 #ifdef SK_MOLTENVK
                 this->write(", constant sksl_synthetic_uniforms& _anonInterface0 [[buffer(0)]]");
 #else
@@ -844,7 +989,7 @@
     }
     fIndentation++;
     writeFields(structType->fields(), structType->fOffset, &intf);
-    if (fProgram.fKind == Program::kFragment_Kind) {
+    if (fProgram.fInputs.fRTHeight) {
         this->writeLine("float u_skRTHeight;");
     }
     fIndentation--;
@@ -1261,9 +1406,7 @@
             wroteInterfaceBlock = true;
         }
     }
-    if (!wroteInterfaceBlock && (fProgram.fKind == Program::kFragment_Kind)) {
-        // FIXME - Possibly have a different way of passing in u_skRTHeight or flip y axis
-        // in a different way altogether.
+    if (!wroteInterfaceBlock && fProgram.fInputs.fRTHeight) {
         this->writeLine("struct sksl_synthetic_uniforms {");
         this->writeLine("    float u_skRTHeight;");
         this->writeLine("};");
diff --git a/src/sksl/SkSLMetalCodeGenerator.h b/src/sksl/SkSLMetalCodeGenerator.h
index 3ed2524..16be50f 100644
--- a/src/sksl/SkSLMetalCodeGenerator.h
+++ b/src/sksl/SkSLMetalCodeGenerator.h
@@ -107,6 +107,8 @@
     };
 
     enum MetalIntrinsic {
+        kEqual_MetalIntrinsic,
+        kNotEqual_MetalIntrinsic,
         kLessThan_MetalIntrinsic,
         kLessThanEqual_MetalIntrinsic,
         kGreaterThan_MetalIntrinsic,
@@ -186,6 +188,8 @@
 
     String getMatrixConstructHelper(const Type& matrix, const Type& arg);
 
+    void writeMatrixTimesEqualHelper(const Type& left, const Type& right, const Type& result);
+
     void writeSpecialIntrinsic(const FunctionCall& c, SpecialIntrinsic kind);
 
     bool canCoerce(const Type& t1, const Type& t2);
@@ -272,7 +276,7 @@
     std::unordered_map<const FunctionDeclaration*, Requirements> fRequirements;
     bool fSetupFragPositionGlobal = false;
     bool fSetupFragPositionLocal = false;
-    std::unordered_map<String, String> fMatrixConstructHelpers;
+    std::unordered_map<String, String> fHelpers;
     int fUniformBuffer = -1;
 
     typedef CodeGenerator INHERITED;
diff --git a/src/sksl/SkSLParser.cpp b/src/sksl/SkSLParser.cpp
index 7f6551c..7c381f2 100644
--- a/src/sksl/SkSLParser.cpp
+++ b/src/sksl/SkSLParser.cpp
@@ -29,6 +29,7 @@
 #include "ast/SkSLASTInterfaceBlock.h"
 #include "ast/SkSLASTIntLiteral.h"
 #include "ast/SkSLASTModifiersDeclaration.h"
+#include "ast/SkSLASTNullLiteral.h"
 #include "ast/SkSLASTParameter.h"
 #include "ast/SkSLASTPrefixExpression.h"
 #include "ast/SkSLASTReturnStatement.h"
@@ -125,6 +126,9 @@
     TOKEN(SKRECT,                       "SkRect");
     TOKEN(SKIRECT,                      "SkIRect");
     TOKEN(SKPMCOLOR,                    "SkPMColor");
+    TOKEN(BOOL,                         "bool");
+    TOKEN(INT,                          "int");
+    TOKEN(FLOAT,                        "float");
     #undef TOKEN
 }
 
@@ -517,7 +521,7 @@
     fTypes.add(this->text(name), std::unique_ptr<Type>(new Type(name.fOffset, this->text(name),
                                                                 fields)));
     return std::unique_ptr<ASTType>(new ASTType(name.fOffset, this->text(name),
-                                                ASTType::kStruct_Kind, std::vector<int>()));
+                                                ASTType::kStruct_Kind, std::vector<int>(), false));
 }
 
 /* structDeclaration ((IDENTIFIER varDeclarationEnd) | SEMICOLON) */
@@ -736,6 +740,12 @@
                     return Layout::CType::kSkIRect;
                 case LayoutToken::SKPMCOLOR:
                     return Layout::CType::kSkPMColor;
+                case LayoutToken::BOOL:
+                    return Layout::CType::kBool;
+                case LayoutToken::INT:
+                    return Layout::CType::kInt32;
+                case LayoutToken::FLOAT:
+                    return Layout::CType::kFloat;
                 default:
                     break;
             }
@@ -942,18 +952,6 @@
                 flags |= Modifiers::kIn_Flag;
                 flags |= Modifiers::kOut_Flag;
                 break;
-            case Token::LOWP:
-                this->nextToken();
-                flags |= Modifiers::kLowp_Flag;
-                break;
-            case Token::MEDIUMP:
-                this->nextToken();
-                flags |= Modifiers::kMediump_Flag;
-                break;
-            case Token::HIGHP:
-                this->nextToken();
-                flags |= Modifiers::kHighp_Flag;
-                break;
             case Token::FLAT:
                 this->nextToken();
                 flags |= Modifiers::kFlat_Flag;
@@ -1046,10 +1044,7 @@
             this->nextToken();
             return std::unique_ptr<ASTStatement>(new ASTBlock(start.fOffset,
                                                      std::vector<std::unique_ptr<ASTStatement>>()));
-        case Token::CONST:   // fall through
-        case Token::HIGHP:   // fall through
-        case Token::MEDIUMP: // fall through
-        case Token::LOWP: {
+        case Token::CONST: {
             auto decl = this->varDeclarations();
             if (!decl) {
                 return nullptr;
@@ -1071,7 +1066,7 @@
     }
 }
 
-/* IDENTIFIER(type) (LBRACKET intLiteral? RBRACKET)* */
+/* IDENTIFIER(type) (LBRACKET intLiteral? RBRACKET)* QUESTION? */
 std::unique_ptr<ASTType> Parser::type() {
     Token type;
     if (!this->expect(Token::IDENTIFIER, "a type", &type)) {
@@ -1095,8 +1090,9 @@
         }
         this->expect(Token::RBRACKET, "']'");
     }
+    bool nullable = this->checkNext(Token::QUESTION);
     return std::unique_ptr<ASTType>(new ASTType(type.fOffset, this->text(type),
-                                                ASTType::kIdentifier_Kind, sizes));
+                                                ASTType::kIdentifier_Kind, sizes, nullable));
 }
 
 /* IDENTIFIER LBRACE varDeclaration* RBRACE (IDENTIFIER (LBRACKET expression? RBRACKET)*)? */
@@ -1816,6 +1812,10 @@
         case Token::BITWISENOT: // fall through
         case Token::PLUSPLUS:   // fall through
         case Token::MINUSMINUS: {
+            AutoDepth depth(this);
+            if (!depth.checkValid()) {
+                return nullptr;
+            }
             Token t = this->nextToken();
             std::unique_ptr<ASTExpression> expr = this->unaryExpression();
             if (!expr) {
@@ -1913,7 +1913,7 @@
     }
 }
 
-/* IDENTIFIER | intLiteral | floatLiteral | boolLiteral | '(' expression ')' */
+/* IDENTIFIER | intLiteral | floatLiteral | boolLiteral | NULL_LITERAL | '(' expression ')' */
 std::unique_ptr<ASTExpression> Parser::term() {
     std::unique_ptr<ASTExpression> result;
     Token t = this->peek();
@@ -1947,6 +1947,10 @@
             }
             break;
         }
+        case Token::NULL_LITERAL:
+            this->nextToken();
+            result.reset(new ASTNullLiteral(t.fOffset));
+            break;
         case Token::LPAREN: {
             this->nextToken();
             result = this->expression();
diff --git a/src/sksl/SkSLParser.h b/src/sksl/SkSLParser.h
index 596ea63..3d423e5 100644
--- a/src/sksl/SkSLParser.h
+++ b/src/sksl/SkSLParser.h
@@ -95,6 +95,9 @@
         SKRECT,
         SKIRECT,
         SKPMCOLOR,
+        BOOL,
+        INT,
+        FLOAT,
     };
 
     Parser(const char* text, size_t length, SymbolTable& types, ErrorReporter& errors);
diff --git a/src/sksl/SkSLPipelineStageCodeGenerator.cpp b/src/sksl/SkSLPipelineStageCodeGenerator.cpp
index 4ca839d..5e36535 100644
--- a/src/sksl/SkSLPipelineStageCodeGenerator.cpp
+++ b/src/sksl/SkSLPipelineStageCodeGenerator.cpp
@@ -37,6 +37,7 @@
         vsprintf(heap.get(), s, copy);
         fOut->write(heap.get(), length);
     }
+    va_end(copy);
 }
 
 void PipelineStageCodeGenerator::writef(const char* s, ...) {
@@ -58,7 +59,7 @@
 }
 
 void PipelineStageCodeGenerator::writeBinaryExpression(const BinaryExpression& b,
-                                             Precedence parentPrecedence) {
+                                                       Precedence parentPrecedence) {
     if (b.fOperator == Token::PERCENT) {
         // need to use "%%" instead of "%" b/c the code will be inside of a printf
         Precedence precedence = GetBinaryPrecedence(b.fOperator);
@@ -146,8 +147,7 @@
                                 found = true;
                                 break;
                             }
-                            if (var.fModifiers.fFlags & (Modifiers::kIn_Flag |
-                                                         Modifiers::kUniform_Flag)) {
+                            if (var.fModifiers.fFlags & Modifiers::kUniform_Flag) {
                                 ++index;
                             }
                         }
@@ -156,8 +156,20 @@
                 SkASSERT(found);
                 fFormatArgs->push_back(Compiler::FormatArg(Compiler::FormatArg::Kind::kUniform,
                                                            index));
-            }
-            else {
+            } else if (fProgramKind == Program::kMixer_Kind &&
+                     ref.fVariable.fStorage == Variable::kParameter_Storage &&
+                     fCurrentFunction->fName == "main") {
+                this->write("%s");
+                for (size_t i = 0; i < fCurrentFunction->fParameters.size(); ++i) {
+                    if (fCurrentFunction->fParameters[i] == &ref.fVariable) {
+                        fFormatArgs->push_back(Compiler::FormatArg(
+                                                         Compiler::FormatArg::Kind::kChildProcessor,
+                                                         i));
+                        return;
+                    }
+                }
+                SkASSERT(false);
+            } else {
                 this->write(ref.fVariable.fName);
             }
     }
@@ -178,6 +190,7 @@
 }
 
 void PipelineStageCodeGenerator::writeFunction(const FunctionDefinition& f) {
+    fCurrentFunction = &f.fDeclaration;
     if (f.fDeclaration.fName == "main") {
         fFunctionHeader = "";
         OutputStream* oldOut = fOut;
diff --git a/src/sksl/SkSLPipelineStageCodeGenerator.h b/src/sksl/SkSLPipelineStageCodeGenerator.h
index 9de7a4a..7357b30 100644
--- a/src/sksl/SkSLPipelineStageCodeGenerator.h
+++ b/src/sksl/SkSLPipelineStageCodeGenerator.h
@@ -58,6 +58,7 @@
     String fExtraEmitCodeCode;
     std::set<int> fWrittenTransformedCoords;
     std::vector<Compiler::FormatArg>* fFormatArgs;
+    const FunctionDeclaration* fCurrentFunction;
 
     typedef GLSLCodeGenerator INHERITED;
 };
diff --git a/src/sksl/SkSLSPIRVCodeGenerator.cpp b/src/sksl/SkSLSPIRVCodeGenerator.cpp
index daafe30..c688594 100644
--- a/src/sksl/SkSLSPIRVCodeGenerator.cpp
+++ b/src/sksl/SkSLSPIRVCodeGenerator.cpp
@@ -98,8 +98,7 @@
     fIntrinsicMap[String("findMSB")]     = BY_TYPE_GLSL(FindSMsb, FindSMsb, FindUMsb);
     fIntrinsicMap[String("dFdx")]        = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpDPdx,
                                                            SpvOpUndef, SpvOpUndef, SpvOpUndef);
-    fIntrinsicMap[String("dFdy")]        = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpDPdy,
-                                                           SpvOpUndef, SpvOpUndef, SpvOpUndef);
+    fIntrinsicMap[String("dFdy")]        = SPECIAL(DFdy);
     fIntrinsicMap[String("fwidth")]      = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpFwidth,
                                                            SpvOpUndef, SpvOpUndef, SpvOpUndef);
     fIntrinsicMap[String("texture")]     = SPECIAL(Texture);
@@ -917,6 +916,21 @@
             this->writeWord(args[1], out);
             break;
         }
+        case kDFdy_SpecialIntrinsic: {
+            SpvId fn = this->writeExpression(*c.fArguments[0], out);
+            this->writeOpCode(SpvOpDPdy, 4, out);
+            this->writeWord(this->getType(c.fType), out);
+            this->writeWord(result, out);
+            this->writeWord(fn, out);
+            if (fProgram.fSettings.fFlipY) {
+                // Flipping Y also negates the Y derivatives.
+                SpvId flipped = this->nextId();
+                this->writeInstruction(SpvOpFNegate, this->getType(c.fType), flipped, result, out);
+                this->writePrecisionModifier(c.fType, flipped);
+                return flipped;
+            }
+            break;
+        }
         case kClamp_SpecialIntrinsic: {
             std::vector<SpvId> args = this->vectorize(c.fArguments, out);
             SkASSERT(args.size() == 3);
@@ -966,7 +980,7 @@
         return this->writeIntrinsicCall(c, out);
     }
     // stores (variable, type, lvalue) pairs to extract and save after the function call is complete
-    std::vector<std::tuple<SpvId, SpvId, std::unique_ptr<LValue>>> lvalues;
+    std::vector<std::tuple<SpvId, const Type*, std::unique_ptr<LValue>>> lvalues;
     std::vector<SpvId> arguments;
     for (size_t i = 0; i < c.fArguments.size(); i++) {
         // id of temporary variable that we will use to hold this argument, or 0 if it is being
@@ -986,8 +1000,7 @@
                 // update the lvalue.
                 tmpValueId = lv->load(out);
                 tmpVar = this->nextId();
-                lvalues.push_back(std::make_tuple(tmpVar, this->getType(c.fArguments[i]->fType),
-                                  std::move(lv)));
+                lvalues.push_back(std::make_tuple(tmpVar, &c.fArguments[i]->fType, std::move(lv)));
             }
         } else {
             // see getFunctionType for an explanation of why we're always using pointer parameters
@@ -1015,7 +1028,9 @@
     // arguments
     for (const auto& tuple : lvalues) {
         SpvId load = this->nextId();
-        this->writeInstruction(SpvOpLoad, std::get<1>(tuple), load, std::get<0>(tuple), out);
+        this->writeInstruction(SpvOpLoad, getType(*std::get<1>(tuple)), load, std::get<0>(tuple),
+                               out);
+        this->writePrecisionModifier(*std::get<1>(tuple), load);
         std::get<2>(tuple)->store(load, out);
     }
     return result;
@@ -1208,6 +1223,27 @@
     }
 }
 
+void SPIRVCodeGenerator::addColumnEntry(SpvId columnType, std::vector<SpvId>* currentColumn,
+                                        std::vector<SpvId>* columnIds,
+                                        int* currentCount, int rows, SpvId entry,
+                                        OutputStream& out) {
+    SkASSERT(*currentCount < rows);
+    ++(*currentCount);
+    currentColumn->push_back(entry);
+    if (*currentCount == rows) {
+        *currentCount = 0;
+        this->writeOpCode(SpvOpCompositeConstruct, 3 + currentColumn->size(), out);
+        this->writeWord(columnType, out);
+        SpvId columnId = this->nextId();
+        this->writeWord(columnId, out);
+        columnIds->push_back(columnId);
+        for (SpvId id : *currentColumn) {
+            this->writeWord(id, out);
+        }
+        currentColumn->clear();
+    }
+}
+
 SpvId SPIRVCodeGenerator::writeMatrixConstructor(const Constructor& c, OutputStream& out) {
     SkASSERT(c.fType.kind() == Type::kMatrix_Kind);
     // go ahead and write the arguments so we don't try to write new instructions in the middle of
@@ -1240,45 +1276,31 @@
         this->writeInstruction(SpvOpCompositeConstruct, this->getType(c.fType), result, column1,
                                column2, out);
     } else {
+        SpvId columnType = this->getType(c.fType.componentType().toCompound(fContext, rows, 1));
         std::vector<SpvId> columnIds;
         // ids of vectors and scalars we have written to the current column so far
         std::vector<SpvId> currentColumn;
         // the total number of scalars represented by currentColumn's entries
         int currentCount = 0;
         for (size_t i = 0; i < arguments.size(); i++) {
-            if (c.fArguments[i]->fType.kind() == Type::kVector_Kind &&
+            if (currentCount == 0 && c.fArguments[i]->fType.kind() == Type::kVector_Kind &&
                     c.fArguments[i]->fType.columns() == c.fType.rows()) {
                 // this is a complete column by itself
-                SkASSERT(currentCount == 0);
                 columnIds.push_back(arguments[i]);
             } else {
                 if (c.fArguments[i]->fType.columns() == 1) {
-                    currentColumn.push_back(arguments[i]);
+                    this->addColumnEntry(columnType, &currentColumn, &columnIds, &currentCount,
+                                         rows, arguments[i], out);
                 } else {
                     SpvId componentType = this->getType(c.fArguments[i]->fType.componentType());
                     for (int j = 0; j < c.fArguments[i]->fType.columns(); ++j) {
                         SpvId swizzle = this->nextId();
                         this->writeInstruction(SpvOpCompositeExtract, componentType, swizzle,
                                                arguments[i], j, out);
-                        currentColumn.push_back(swizzle);
+                        this->addColumnEntry(columnType, &currentColumn, &columnIds, &currentCount,
+                                             rows, swizzle, out);
                     }
                 }
-                currentCount += c.fArguments[i]->fType.columns();
-                if (currentCount == rows) {
-                    currentCount = 0;
-                    this->writeOpCode(SpvOpCompositeConstruct, 3 + currentColumn.size(), out);
-                    this->writeWord(this->getType(c.fType.componentType().toCompound(fContext, rows,
-                                                                                     1)),
-                                    out);
-                    SpvId columnId = this->nextId();
-                    this->writeWord(columnId, out);
-                    columnIds.push_back(columnId);
-                    for (SpvId id : currentColumn) {
-                        this->writeWord(id, out);
-                    }
-                    currentColumn.clear();
-                }
-                SkASSERT(currentCount < rows);
             }
         }
         SkASSERT(columnIds.size() == (size_t) columns);
@@ -1512,10 +1534,12 @@
 
 class PointerLValue : public SPIRVCodeGenerator::LValue {
 public:
-    PointerLValue(SPIRVCodeGenerator& gen, SpvId pointer, SpvId type)
+    PointerLValue(SPIRVCodeGenerator& gen, SpvId pointer, SpvId type,
+                  SPIRVCodeGenerator::Precision precision)
     : fGen(gen)
     , fPointer(pointer)
-    , fType(type) {}
+    , fType(type)
+    , fPrecision(precision) {}
 
     virtual SpvId getPointer() override {
         return fPointer;
@@ -1524,6 +1548,7 @@
     virtual SpvId load(OutputStream& out) override {
         SpvId result = fGen.nextId();
         fGen.writeInstruction(SpvOpLoad, fType, result, fPointer, out);
+        fGen.writePrecisionModifier(fPrecision, result);
         return result;
     }
 
@@ -1535,17 +1560,20 @@
     SPIRVCodeGenerator& fGen;
     const SpvId fPointer;
     const SpvId fType;
+    const SPIRVCodeGenerator::Precision fPrecision;
 };
 
 class SwizzleLValue : public SPIRVCodeGenerator::LValue {
 public:
     SwizzleLValue(SPIRVCodeGenerator& gen, SpvId vecPointer, const std::vector<int>& components,
-                  const Type& baseType, const Type& swizzleType)
+                  const Type& baseType, const Type& swizzleType,
+                  SPIRVCodeGenerator::Precision precision)
     : fGen(gen)
     , fVecPointer(vecPointer)
     , fComponents(components)
     , fBaseType(baseType)
-    , fSwizzleType(swizzleType) {}
+    , fSwizzleType(swizzleType)
+    , fPrecision(precision) {}
 
     virtual SpvId getPointer() override {
         return 0;
@@ -1554,6 +1582,7 @@
     virtual SpvId load(OutputStream& out) override {
         SpvId base = fGen.nextId();
         fGen.writeInstruction(SpvOpLoad, fGen.getType(fBaseType), base, fVecPointer, out);
+        fGen.writePrecisionModifier(fPrecision, base);
         SpvId result = fGen.nextId();
         fGen.writeOpCode(SpvOpVectorShuffle, 5 + (int32_t) fComponents.size(), out);
         fGen.writeWord(fGen.getType(fSwizzleType), out);
@@ -1563,6 +1592,7 @@
         for (int component : fComponents) {
             fGen.writeWord(component, out);
         }
+        fGen.writePrecisionModifier(fPrecision, result);
         return result;
     }
 
@@ -1601,6 +1631,7 @@
             }
             fGen.writeWord(offset, out);
         }
+        fGen.writePrecisionModifier(fPrecision, shuffle);
         fGen.writeInstruction(SpvOpStore, fVecPointer, shuffle, out);
     }
 
@@ -1610,10 +1641,12 @@
     const std::vector<int>& fComponents;
     const Type& fBaseType;
     const Type& fSwizzleType;
+    const SPIRVCodeGenerator::Precision fPrecision;
 };
 
 std::unique_ptr<SPIRVCodeGenerator::LValue> SPIRVCodeGenerator::getLValue(const Expression& expr,
                                                                           OutputStream& out) {
+    Precision precision = expr.fType.highPrecision() ? Precision::kHigh : Precision::kLow;
     switch (expr.fKind) {
         case Expression::kVariableReference_Kind: {
             SpvId type;
@@ -1628,7 +1661,8 @@
             SkASSERT(entry != fVariableMap.end());
             return std::unique_ptr<SPIRVCodeGenerator::LValue>(new PointerLValue(*this,
                                                                                  entry->second,
-                                                                                 type));
+                                                                                 type,
+                                                                                 precision));
         }
         case Expression::kIndex_Kind: // fall through
         case Expression::kFieldAccess_Kind: {
@@ -1643,7 +1677,8 @@
             return std::unique_ptr<SPIRVCodeGenerator::LValue>(new PointerLValue(
                                                                         *this,
                                                                         member,
-                                                                        this->getType(expr.fType)));
+                                                                        this->getType(expr.fType),
+                                                                        precision));
         }
         case Expression::kSwizzle_Kind: {
             Swizzle& swizzle = (Swizzle&) expr;
@@ -1663,14 +1698,16 @@
                 return std::unique_ptr<SPIRVCodeGenerator::LValue>(new PointerLValue(
                                                                        *this,
                                                                        member,
-                                                                       this->getType(expr.fType)));
+                                                                       this->getType(expr.fType),
+                                                                       precision));
             } else {
                 return std::unique_ptr<SPIRVCodeGenerator::LValue>(new SwizzleLValue(
                                                                               *this,
                                                                               base,
                                                                               swizzle.fComponents,
                                                                               swizzle.fBase->fType,
-                                                                              expr.fType));
+                                                                              expr.fType,
+                                                                              precision));
             }
         }
         case Expression::kTernary_Kind: {
@@ -1696,7 +1733,8 @@
             return std::unique_ptr<SPIRVCodeGenerator::LValue>(new PointerLValue(
                                                                        *this,
                                                                        result,
-                                                                       this->getType(expr.fType)));
+                                                                       this->getType(expr.fType),
+                                                                       precision));
         }
         default:
             // expr isn't actually an lvalue, create a dummy variable for it. This case happens due
@@ -1711,7 +1749,8 @@
             return std::unique_ptr<SPIRVCodeGenerator::LValue>(new PointerLValue(
                                                                        *this,
                                                                        result,
-                                                                       this->getType(expr.fType)));
+                                                                       this->getType(expr.fType),
+                                                                       precision));
     }
 }
 
@@ -1721,6 +1760,7 @@
     SkASSERT(entry != fVariableMap.end());
     SpvId var = entry->second;
     this->writeInstruction(SpvOpLoad, this->getType(ref.fVariable.fType), result, var, out);
+    this->writePrecisionModifier(ref.fVariable.fType, result);
     if (ref.fVariable.fModifiers.fLayout.fBuiltin == SK_FRAGCOORD_BUILTIN &&
         fProgram.fSettings.fFlipY) {
         // need to remap to a top-left coordinate system
@@ -1824,9 +1864,35 @@
         this->writeWord(this->getType(swizzle.fType), out);
         this->writeWord(result, out);
         this->writeWord(base, out);
-        this->writeWord(base, out);
+        SpvId other;
+        int last = swizzle.fComponents.back();
+        if (last < 0) {
+            if (!fConstantZeroOneVector) {
+                FloatLiteral zero(fContext, -1, 0);
+                SpvId zeroId = this->writeFloatLiteral(zero);
+                FloatLiteral one(fContext, -1, 1);
+                SpvId oneId = this->writeFloatLiteral(one);
+                SpvId type = this->getType(*fContext.fFloat2_Type);
+                fConstantZeroOneVector = this->nextId();
+                this->writeOpCode(SpvOpConstantComposite, 5, fConstantBuffer);
+                this->writeWord(type, fConstantBuffer);
+                this->writeWord(fConstantZeroOneVector, fConstantBuffer);
+                this->writeWord(zeroId, fConstantBuffer);
+                this->writeWord(oneId, fConstantBuffer);
+            }
+            other = fConstantZeroOneVector;
+        } else {
+            other = base;
+        }
+        this->writeWord(other, out);
         for (int component : swizzle.fComponents) {
-            this->writeWord(component, out);
+            if (component == SKSL_SWIZZLE_0) {
+                this->writeWord(swizzle.fBase->fType.columns(), out);
+            } else if (component == SKSL_SWIZZLE_1) {
+                this->writeWord(swizzle.fBase->fType.columns() + 1, out);
+            } else {
+                this->writeWord(component, out);
+            }
         }
     }
     return result;
@@ -1845,32 +1911,15 @@
         this->writeInstruction(ifUInt, this->getType(resultType), result, lhs, rhs, out);
     } else if (operandType == *fContext.fBool_Type) {
         this->writeInstruction(ifBool, this->getType(resultType), result, lhs, rhs, out);
+        return result; // skip RelaxedPrecision check
     } else {
         ABORT("invalid operandType: %s", operandType.description().c_str());
     }
-    return result;
-}
-
-bool is_assignment(Token::Kind op) {
-    switch (op) {
-        case Token::EQ:           // fall through
-        case Token::PLUSEQ:       // fall through
-        case Token::MINUSEQ:      // fall through
-        case Token::STAREQ:       // fall through
-        case Token::SLASHEQ:      // fall through
-        case Token::PERCENTEQ:    // fall through
-        case Token::SHLEQ:        // fall through
-        case Token::SHREQ:        // fall through
-        case Token::BITWISEOREQ:  // fall through
-        case Token::BITWISEXOREQ: // fall through
-        case Token::BITWISEANDEQ: // fall through
-        case Token::LOGICALOREQ:  // fall through
-        case Token::LOGICALXOREQ: // fall through
-        case Token::LOGICALANDEQ:
-            return true;
-        default:
-            return false;
+    if (getActualType(resultType) == operandType && !resultType.highPrecision()) {
+        this->writeInstruction(SpvOpDecorate, result, SpvDecorationRelaxedPrecision,
+                               fDecorationBuffer);
     }
+    return result;
 }
 
 SpvId SPIRVCodeGenerator::foldToBool(SpvId id, const Type& operandType, SpvOp op,
@@ -2343,6 +2392,7 @@
     this->writeLabel(end, out);
     SpvId result = this->nextId();
     this->writeInstruction(SpvOpLoad, this->getType(t.fType), result, var, out);
+    this->writePrecisionModifier(t.fType, result);
     return result;
 }
 
@@ -2369,6 +2419,7 @@
         } else {
             ABORT("unsupported prefix expression %s", p.description().c_str());
         }
+        this->writePrecisionModifier(p.fType, result);
         return result;
     }
     switch (p.fOperator) {
@@ -2673,9 +2724,12 @@
     return result;
 }
 
-void SPIRVCodeGenerator::writePrecisionModifier(const Modifiers& modifiers, SpvId id) {
-    if ((modifiers.fFlags & Modifiers::kLowp_Flag) |
-        (modifiers.fFlags & Modifiers::kMediump_Flag)) {
+void SPIRVCodeGenerator::writePrecisionModifier(const Type& type, SpvId id) {
+    this->writePrecisionModifier(type.highPrecision() ? Precision::kHigh : Precision::kLow, id);
+}
+
+void SPIRVCodeGenerator::writePrecisionModifier(Precision precision, SpvId id) {
+    if (precision == Precision::kLow) {
         this->writeInstruction(SpvOpDecorate, id, SpvDecorationRelaxedPrecision, fDecorationBuffer);
     }
 }
@@ -2739,7 +2793,7 @@
         }
         this->writeInstruction(SpvOpVariable, type, id, storageClass, fConstantBuffer);
         this->writeInstruction(SpvOpName, id, var->fName, fNameBuffer);
-        this->writePrecisionModifier(var->fModifiers, id);
+        this->writePrecisionModifier(var->fType, id);
         if (varDecl.fValue) {
             SkASSERT(!fCurrentBlock);
             fCurrentBlock = -1;
@@ -2900,23 +2954,16 @@
 }
 
 void SPIRVCodeGenerator::writeWhileStatement(const WhileStatement& w, OutputStream& out) {
-    // We believe the while loop code below will work, but Skia doesn't actually use them and
-    // adequately testing this code in the absence of Skia exercising it isn't straightforward. For
-    // the time being, we just fail with an error due to the lack of testing. If you encounter this
-    // message, simply remove the error call below to see whether our while loop support actually
-    // works.
-    fErrors.error(w.fOffset, "internal error: while loop support has been disabled in SPIR-V, "
-                  "see SkSLSPIRVCodeGenerator.cpp for details");
-
     SpvId header = this->nextId();
     SpvId start = this->nextId();
     SpvId body = this->nextId();
-    fContinueTarget.push(start);
+    SpvId continueTarget = this->nextId();
+    fContinueTarget.push(continueTarget);
     SpvId end = this->nextId();
     fBreakTarget.push(end);
     this->writeInstruction(SpvOpBranch, header, out);
     this->writeLabel(header, out);
-    this->writeInstruction(SpvOpLoopMerge, end, start, SpvLoopControlMaskNone, out);
+    this->writeInstruction(SpvOpLoopMerge, end, continueTarget, SpvLoopControlMaskNone, out);
     this->writeInstruction(SpvOpBranch, start, out);
     this->writeLabel(start, out);
     SpvId test = this->writeExpression(*w.fTest, out);
@@ -2924,8 +2971,10 @@
     this->writeLabel(body, out);
     this->writeStatement(*w.fStatement, out);
     if (fCurrentBlock) {
-        this->writeInstruction(SpvOpBranch, start, out);
+        this->writeInstruction(SpvOpBranch, continueTarget, out);
     }
+    this->writeLabel(continueTarget, out);
+    this->writeInstruction(SpvOpBranch, header, out);
     this->writeLabel(end, out);
     fBreakTarget.pop();
     fContinueTarget.pop();
@@ -2943,12 +2992,13 @@
     SpvId header = this->nextId();
     SpvId start = this->nextId();
     SpvId next = this->nextId();
-    fContinueTarget.push(next);
+    SpvId continueTarget = this->nextId();
+    fContinueTarget.push(continueTarget);
     SpvId end = this->nextId();
     fBreakTarget.push(end);
     this->writeInstruction(SpvOpBranch, header, out);
     this->writeLabel(header, out);
-    this->writeInstruction(SpvOpLoopMerge, end, start, SpvLoopControlMaskNone, out);
+    this->writeInstruction(SpvOpLoopMerge, end, continueTarget, SpvLoopControlMaskNone, out);
     this->writeInstruction(SpvOpBranch, start, out);
     this->writeLabel(start, out);
     this->writeStatement(*d.fStatement, out);
@@ -2957,7 +3007,9 @@
     }
     this->writeLabel(next, out);
     SpvId test = this->writeExpression(*d.fTest, out);
-    this->writeInstruction(SpvOpBranchConditional, test, start, end, out);
+    this->writeInstruction(SpvOpBranchConditional, test, continueTarget, end, out);
+    this->writeLabel(continueTarget, out);
+    this->writeInstruction(SpvOpBranch, header, out);
     this->writeLabel(end, out);
     fBreakTarget.pop();
     fContinueTarget.pop();
diff --git a/src/sksl/SkSLSPIRVCodeGenerator.h b/src/sksl/SkSLSPIRVCodeGenerator.h
index 8b88c75..0dcbadc 100644
--- a/src/sksl/SkSLSPIRVCodeGenerator.h
+++ b/src/sksl/SkSLSPIRVCodeGenerator.h
@@ -95,11 +95,17 @@
         kMin_SpecialIntrinsic,
         kMix_SpecialIntrinsic,
         kMod_SpecialIntrinsic,
+        kDFdy_SpecialIntrinsic,
         kSaturate_SpecialIntrinsic,
         kSubpassLoad_SpecialIntrinsic,
         kTexture_SpecialIntrinsic,
     };
 
+    enum class Precision {
+        kLow,
+        kHigh,
+    };
+
     void setupIntrinsics();
 
     SpvId nextId();
@@ -119,7 +125,9 @@
     SpvId getPointerType(const Type& type, const MemoryLayout& layout,
                          SpvStorageClass_ storageClass);
 
-    void writePrecisionModifier(const Modifiers& modifiers, SpvId id);
+    void writePrecisionModifier(Precision precision, SpvId id);
+
+    void writePrecisionModifier(const Type& type, SpvId id);
 
     std::vector<SpvId> getAccessChain(const Expression& expr, OutputStream& out);
 
@@ -191,6 +199,10 @@
     void writeMatrixCopy(SpvId id, SpvId src, const Type& srcType, const Type& dstType,
                          OutputStream& out);
 
+    void addColumnEntry(SpvId columnType, std::vector<SpvId>* currentColumn,
+                        std::vector<SpvId>* columnIds, int* currentCount, int rows, SpvId entry,
+                        OutputStream& out);
+
     SpvId writeMatrixConstructor(const Constructor& c, OutputStream& out);
 
     SpvId writeVectorConstructor(const Constructor& c, OutputStream& out);
@@ -336,6 +348,8 @@
     std::unordered_map<uint64_t, SpvId> fUIntConstants;
     std::unordered_map<float, SpvId> fFloatConstants;
     std::unordered_map<double, SpvId> fDoubleConstants;
+    // The constant float2(0, 1), used in swizzling
+    SpvId fConstantZeroOneVector = 0;
     bool fSetupFragPosition;
     // label of the current block, or 0 if we are not in a block
     SpvId fCurrentBlock;
diff --git a/src/sksl/SkSLString.cpp b/src/sksl/SkSLString.cpp
index effb3db..4ef7770 100644
--- a/src/sksl/SkSLString.cpp
+++ b/src/sksl/SkSLString.cpp
@@ -244,34 +244,23 @@
 }
 
 String to_string(double value) {
-#ifdef SKSL_BUILD_FOR_WIN
-    #define SNPRINTF    _snprintf
-#else
-    #define SNPRINTF    snprintf
-#endif
-#define MAX_DOUBLE_CHARS 25
-    char buffer[MAX_DOUBLE_CHARS];
-    int len = SNPRINTF(buffer, sizeof(buffer), "%.17g", value);
-    SkASSERT(len < MAX_DOUBLE_CHARS);
+    std::stringstream buffer;
+    buffer.imbue(std::locale::classic());
+    buffer.precision(17);
+    buffer << value;
     bool needsDotZero = true;
-    for (int i = 0; i < len; ++i) {
-        char c = buffer[i];
-        if (c == ',') {
-            buffer[i] = '.';
-            needsDotZero = false;
-            break;
-        } else if (c == '.' || c == 'e') {
+    const std::string str = buffer.str();
+    for (int i = str.size() - 1; i >= 0; --i) {
+        char c = str[i];
+        if (c == '.' || c == 'e') {
             needsDotZero = false;
             break;
         }
     }
-    String result(buffer);
     if (needsDotZero) {
-        result += ".0";
+        buffer << ".0";
     }
-    return result;
-#undef SNPRINTF
-#undef MAX_DOUBLE_CHARS
+    return String(buffer.str().c_str());
 }
 
 int stoi(const String& s) {
diff --git a/src/sksl/SkSLUtil.cpp b/src/sksl/SkSLUtil.cpp
index 49d37e3..4684df4 100644
--- a/src/sksl/SkSLUtil.cpp
+++ b/src/sksl/SkSLUtil.cpp
@@ -30,4 +30,45 @@
     out.write(s.str().c_str(), s.str().size());
 }
 
+bool is_assignment(Token::Kind op) {
+    switch (op) {
+        case Token::EQ:           // fall through
+        case Token::PLUSEQ:       // fall through
+        case Token::MINUSEQ:      // fall through
+        case Token::STAREQ:       // fall through
+        case Token::SLASHEQ:      // fall through
+        case Token::PERCENTEQ:    // fall through
+        case Token::SHLEQ:        // fall through
+        case Token::SHREQ:        // fall through
+        case Token::BITWISEOREQ:  // fall through
+        case Token::BITWISEXOREQ: // fall through
+        case Token::BITWISEANDEQ: // fall through
+        case Token::LOGICALOREQ:  // fall through
+        case Token::LOGICALXOREQ: // fall through
+        case Token::LOGICALANDEQ:
+            return true;
+        default:
+            return false;
+    }
+}
+
+Token::Kind remove_assignment(Token::Kind op) {
+    switch (op) {
+        case Token::PLUSEQ:       return Token::PLUS;
+        case Token::MINUSEQ:      return Token::MINUS;
+        case Token::STAREQ:       return Token::STAR;
+        case Token::SLASHEQ:      return Token::SLASH;
+        case Token::PERCENTEQ:    return Token::PERCENT;
+        case Token::SHLEQ:        return Token::SHL;
+        case Token::SHREQ:        return Token::SHR;
+        case Token::BITWISEOREQ:  return Token::BITWISEOR;
+        case Token::BITWISEXOREQ: return Token::BITWISEXOR;
+        case Token::BITWISEANDEQ: return Token::BITWISEAND;
+        case Token::LOGICALOREQ:  return Token::LOGICALOR;
+        case Token::LOGICALXOREQ: return Token::LOGICALXOR;
+        case Token::LOGICALANDEQ: return Token::LOGICALAND;
+        default: return Token::INVALID;
+    }
+}
+
 } // namespace
diff --git a/src/sksl/SkSLUtil.h b/src/sksl/SkSLUtil.h
index 3f8e093..d221f4e 100644
--- a/src/sksl/SkSLUtil.h
+++ b/src/sksl/SkSLUtil.h
@@ -12,6 +12,7 @@
 #include <memory>
 #include "stdlib.h"
 #include "string.h"
+#include "SkSLLexer.h"
 #include "SkSLDefines.h"
 #include "SkSLString.h"
 #include "SkSLStringStream.h"
@@ -31,6 +32,10 @@
 class OutputStream;
 class StringStream;
 
+#ifdef SKSL_STANDALONE
+#define SK_API
+#endif
+
 #if defined(SKSL_STANDALONE) || !SK_SUPPORT_GPU
 
 // we're being compiled standalone, so we don't have access to caps...
@@ -101,6 +106,10 @@
         return true;
     }
 
+    bool sampleVariablesSupport() const {
+        return true;
+    }
+
     bool externalTextureSupport() const {
         return true;
     }
@@ -388,6 +397,13 @@
 
 void write_stringstream(const StringStream& d, OutputStream& out);
 
+// Returns true if op is '=' or any compound assignment operator ('+=', '-=', etc.)
+bool is_assignment(Token::Kind op);
+
+// Given a compound assignment operator, returns the non-assignment version of the operator (e.g.
+// '+=' becomes '+')
+Token::Kind remove_assignment(Token::Kind op);
+
 NORETURN void sksl_abort();
 
 } // namespace
diff --git a/src/sksl/ast/SkSLASTExpression.h b/src/sksl/ast/SkSLASTExpression.h
index 0d8ddc7..edcb09d 100644
--- a/src/sksl/ast/SkSLASTExpression.h
+++ b/src/sksl/ast/SkSLASTExpression.h
@@ -17,13 +17,14 @@
  */
 struct ASTExpression : public ASTPositionNode {
     enum Kind {
+        kBinary_Kind,
+        kBool_Kind,
         kFloat_Kind,
         kIdentifier_Kind,
         kInt_Kind,
-        kBool_Kind,
+        kNull_Kind,
         kPrefix_Kind,
         kSuffix_Kind,
-        kBinary_Kind,
         kTernary_Kind
     };
 
diff --git a/src/sksl/ast/SkSLASTNullLiteral.h b/src/sksl/ast/SkSLASTNullLiteral.h
new file mode 100644
index 0000000..7d16d70
--- /dev/null
+++ b/src/sksl/ast/SkSLASTNullLiteral.h
@@ -0,0 +1,31 @@
+/*
+ * 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 SKSL_ASTNULLLITERAL
+#define SKSL_ASTNULLLITERAL
+
+#include "SkSLASTExpression.h"
+
+namespace SkSL {
+
+/**
+ * Represents "null".
+ */
+struct ASTNullLiteral : public ASTExpression {
+    ASTNullLiteral(int offset)
+    : INHERITED(offset, kNull_Kind) {}
+
+    String description() const override {
+        return "null";
+    }
+
+    typedef ASTExpression INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ast/SkSLASTType.h b/src/sksl/ast/SkSLASTType.h
index 44fd95b..52ada84 100644
--- a/src/sksl/ast/SkSLASTType.h
+++ b/src/sksl/ast/SkSLASTType.h
@@ -21,11 +21,12 @@
         kStruct_Kind
     };
 
-    ASTType(int offset, StringFragment name, Kind kind, std::vector<int> sizes)
+    ASTType(int offset, StringFragment name, Kind kind, std::vector<int> sizes, bool nullable)
     : INHERITED(offset)
     , fName(name)
     , fKind(kind)
-    , fSizes(std::move(sizes)) {}
+    , fSizes(std::move(sizes))
+    , fNullable(nullable) {}
 
     String description() const override {
         return fName;
@@ -38,6 +39,8 @@
     // array sizes, -1 meaning unspecified
     const std::vector<int> fSizes;
 
+    bool fNullable;
+
     typedef ASTPositionNode INHERITED;
 };
 
diff --git a/src/sksl/ir/SkSLConstructor.h b/src/sksl/ir/SkSLConstructor.h
index 1070a0c..8ef1a70 100644
--- a/src/sksl/ir/SkSLConstructor.h
+++ b/src/sksl/ir/SkSLConstructor.h
@@ -12,6 +12,7 @@
 #include "SkSLFloatLiteral.h"
 #include "SkSLIntLiteral.h"
 #include "SkSLIRGenerator.h"
+#include "SkSLPrefixExpression.h"
 
 namespace SkSL {
 
@@ -102,22 +103,9 @@
         // a constant scalar constructor should have been collapsed down to the appropriate
         // literal
         SkASSERT(fType.kind() == Type::kMatrix_Kind);
-        const FloatLiteral fzero(context, -1, 0);
-        const IntLiteral izero(context, -1, 0);
-        const Expression* zero;
-        if (fType.componentType().isFloat()) {
-            zero = &fzero;
-        } else {
-            SkASSERT(fType.componentType().isInteger());
-            zero = &izero;
-        }
         for (int col = 0; col < fType.columns(); col++) {
             for (int row = 0; row < fType.rows(); row++) {
-                const Expression* component1 = getMatComponent(col, row);
-                const Expression* component2 = c.getMatComponent(col, row);
-                if (!(component1 ? component1 : zero)->compareConstant(
-                                                                context,
-                                                                component2 ? *component2 : *zero)) {
+                if (getMatComponent(col, row) != c.getMatComponent(col, row)) {
                     return false;
                 }
             }
@@ -139,8 +127,6 @@
                 }
                 current++;
             } else {
-                SkASSERT(arg->fType.kind() == Type::kVector_Kind);
-                SkASSERT(arg->fKind == Expression::kConstructor_Kind);
                 if (current + arg->fType.columns() > index) {
                     return ((const Constructor&) *arg).getVecComponent(index - current);
                 }
@@ -158,8 +144,7 @@
         return this->getVecComponent(index).getConstantInt();
     }
 
-    // null return should be interpreted as zero
-    const Expression* getMatComponent(int col, int row) const {
+    double getMatComponent(int col, int row) const {
         SkASSERT(this->isConstant());
         SkASSERT(fType.kind() == Type::kMatrix_Kind);
         SkASSERT(col < fType.columns() && row < fType.rows());
@@ -170,7 +155,7 @@
                 // 0 x 0
                 // 0 0 x
                 // return x if col == row
-                return col == row ? fArguments[0].get() : nullptr;
+                return col == row ? fArguments[0]->getConstantFloat() : 0.0;
             }
             if (fArguments[0]->fType.kind() == Type::kMatrix_Kind) {
                 SkASSERT(fArguments[0]->fKind == Expression::kConstructor_Kind);
@@ -180,8 +165,8 @@
                     // within bounds, defer to argument
                     return ((Constructor&) *fArguments[0]).getMatComponent(col, row);
                 }
-                // out of bounds, return 0
-                return nullptr;
+                // out of bounds
+                return 0.0;
             }
         }
         int currentIndex = 0;
@@ -191,11 +176,21 @@
             SkASSERT(arg->fType.rows() == 1);
             if (currentIndex + arg->fType.columns() > targetIndex) {
                 if (arg->fType.columns() == 1) {
-                    return arg.get();
+                    return arg->getConstantFloat();
                 } else {
-                    SkASSERT(arg->fType.kind() == Type::kVector_Kind);
-                    SkASSERT(arg->fKind == Expression::kConstructor_Kind);
-                    return &((Constructor&) *arg).getVecComponent(targetIndex - currentIndex);
+                    int index = targetIndex - currentIndex;
+                    switch (arg->fKind) {
+                        case Expression::kPrefix_Kind: {
+                            PrefixExpression& p = (PrefixExpression&) *arg;
+                            SkASSERT(p.fOperator == Token::MINUS);
+                            SkASSERT(p.fOperand->fKind == Expression::kConstructor_Kind);
+                            return -((Constructor&) *p.fOperand).getFVecComponent(index);
+                        }
+                        case Expression::kConstructor_Kind:
+                            return ((Constructor&) *arg).getFVecComponent(index);
+                        default:
+                            SkASSERT(false);
+                    }
                 }
             }
             currentIndex += arg->fType.columns();
diff --git a/src/sksl/ir/SkSLExpression.h b/src/sksl/ir/SkSLExpression.h
index ddeed44..126f3a0 100644
--- a/src/sksl/ir/SkSLExpression.h
+++ b/src/sksl/ir/SkSLExpression.h
@@ -35,6 +35,7 @@
         kFunctionReference_Kind,
         kFunctionCall_Kind,
         kIndex_Kind,
+        kNullLiteral_Kind,
         kPrefix_Kind,
         kPostfix_Kind,
         kSetting_Kind,
diff --git a/src/sksl/ir/SkSLLayout.h b/src/sksl/ir/SkSLLayout.h
index c2c79e0..df25e749 100644
--- a/src/sksl/ir/SkSLLayout.h
+++ b/src/sksl/ir/SkSLLayout.h
@@ -79,6 +79,7 @@
 
     enum class CType {
         kDefault,
+        kBool,
         kFloat,
         kInt32,
         kSkRect,
diff --git a/src/sksl/ir/SkSLModifiers.h b/src/sksl/ir/SkSLModifiers.h
index c8ece61..d95331e 100644
--- a/src/sksl/ir/SkSLModifiers.h
+++ b/src/sksl/ir/SkSLModifiers.h
@@ -21,22 +21,19 @@
         kConst_Flag          = 1 <<  0,
         kIn_Flag             = 1 <<  1,
         kOut_Flag            = 1 <<  2,
-        kLowp_Flag           = 1 <<  3,
-        kMediump_Flag        = 1 <<  4,
-        kHighp_Flag          = 1 <<  5,
-        kUniform_Flag        = 1 <<  6,
-        kFlat_Flag           = 1 <<  7,
-        kNoPerspective_Flag  = 1 <<  8,
-        kReadOnly_Flag       = 1 <<  9,
-        kWriteOnly_Flag      = 1 << 10,
-        kCoherent_Flag       = 1 << 11,
-        kVolatile_Flag       = 1 << 12,
-        kRestrict_Flag       = 1 << 13,
-        kBuffer_Flag         = 1 << 14,
-        kHasSideEffects_Flag = 1 << 15,
-        kPLS_Flag            = 1 << 16,
-        kPLSIn_Flag          = 1 << 17,
-        kPLSOut_Flag         = 1 << 18,
+        kUniform_Flag        = 1 <<  3,
+        kFlat_Flag           = 1 <<  4,
+        kNoPerspective_Flag  = 1 <<  5,
+        kReadOnly_Flag       = 1 <<  6,
+        kWriteOnly_Flag      = 1 <<  7,
+        kCoherent_Flag       = 1 <<  8,
+        kVolatile_Flag       = 1 <<  9,
+        kRestrict_Flag       = 1 << 10,
+        kBuffer_Flag         = 1 << 11,
+        kHasSideEffects_Flag = 1 << 12,
+        kPLS_Flag            = 1 << 13,
+        kPLSIn_Flag          = 1 << 14,
+        kPLSOut_Flag         = 1 << 15,
     };
 
     Modifiers()
@@ -55,15 +52,6 @@
         if (fFlags & kConst_Flag) {
             result += "const ";
         }
-        if (fFlags & kLowp_Flag) {
-            result += "lowp ";
-        }
-        if (fFlags & kMediump_Flag) {
-            result += "mediump ";
-        }
-        if (fFlags & kHighp_Flag) {
-            result += "highp ";
-        }
         if (fFlags & kFlat_Flag) {
             result += "flat ";
         }
diff --git a/src/sksl/ir/SkSLNullLiteral.h b/src/sksl/ir/SkSLNullLiteral.h
new file mode 100644
index 0000000..8728b17
--- /dev/null
+++ b/src/sksl/ir/SkSLNullLiteral.h
@@ -0,0 +1,51 @@
+/*
+ * 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 SKSL_NULLLITERAL
+#define SKSL_NULLLITERAL
+
+#include "SkSLContext.h"
+#include "SkSLExpression.h"
+
+namespace SkSL {
+
+/**
+ * Represents 'null'.
+ */
+struct NullLiteral : public Expression {
+    NullLiteral(const Context& context, int offset)
+    : INHERITED(offset, kNullLiteral_Kind, *context.fNull_Type) {}
+
+    NullLiteral(int offset, const Type& type)
+    : INHERITED(offset, kNullLiteral_Kind, type) {}
+
+    String description() const override {
+        return "null";
+    }
+
+    bool hasSideEffects() const override {
+        return false;
+    }
+
+    bool isConstant() const override {
+        return true;
+    }
+
+    bool compareConstant(const Context& context, const Expression& other) const override {
+        return true;
+    }
+
+    std::unique_ptr<Expression> clone() const override {
+        return std::unique_ptr<Expression>(new NullLiteral(fOffset, fType));
+    }
+
+    typedef Expression INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLProgram.h b/src/sksl/ir/SkSLProgram.h
index 4cbde55..8871455 100644
--- a/src/sksl/ir/SkSLProgram.h
+++ b/src/sksl/ir/SkSLProgram.h
@@ -13,6 +13,7 @@
 
 #include "SkSLBoolLiteral.h"
 #include "SkSLExpression.h"
+#include "SkSLFloatLiteral.h"
 #include "SkSLIntLiteral.h"
 #include "SkSLModifiers.h"
 #include "SkSLProgramElement.h"
@@ -46,6 +47,10 @@
             : fKind(kInt_Kind)
             , fValue(i) {}
 
+            Value(float f)
+            : fKind(kFloat_Kind)
+            , fValue(f) {}
+
             std::unique_ptr<Expression> literal(const Context& context, int offset) const {
                 switch (fKind) {
                     case Program::Settings::Value::kBool_Kind:
@@ -56,6 +61,10 @@
                         return std::unique_ptr<Expression>(new IntLiteral(context,
                                                                           offset,
                                                                           fValue));
+                    case Program::Settings::Value::kFloat_Kind:
+                        return std::unique_ptr<Expression>(new FloatLiteral(context,
+                                                                          offset,
+                                                                          fValue));
                     default:
                         SkASSERT(false);
                         return nullptr;
@@ -65,6 +74,7 @@
             enum {
                 kBool_Kind,
                 kInt_Kind,
+                kFloat_Kind,
             } fKind;
 
             int fValue;
@@ -203,7 +213,8 @@
         kVertex_Kind,
         kGeometry_Kind,
         kFragmentProcessor_Kind,
-        kPipelineStage_Kind
+        kPipelineStage_Kind,
+        kMixer_Kind
     };
 
     Program(Kind kind,
diff --git a/src/sksl/ir/SkSLSwizzle.h b/src/sksl/ir/SkSLSwizzle.h
index 0702866..d318289 100644
--- a/src/sksl/ir/SkSLSwizzle.h
+++ b/src/sksl/ir/SkSLSwizzle.h
@@ -16,10 +16,16 @@
 
 namespace SkSL {
 
+// represents a swizzle component of constant 0, as in x.rgb0
+const int SKSL_SWIZZLE_0 = -2;
+
+// represents a swizzle component of constant 1, as in x.rgb1
+const int SKSL_SWIZZLE_1 = -1;
+
 /**
  * Given a type and a swizzle component count, returns the type that will result from swizzling. For
- * instance, swizzling a float3with two components will result in a float2 It is possible to swizzle
- * with more components than the source vector, as in 'float21).xxxx'.
+ * instance, swizzling a float3 with two components will result in a float2. It is possible to
+ * swizzle with more components than the source vector, as in 'float2(1).xxxx'.
  */
 static const Type& get_type(const Context& context, Expression& value, size_t count) {
     const Type& base = value.fType.componentType();
@@ -134,7 +140,7 @@
     String description() const override {
         String result = fBase->description() + ".";
         for (int x : fComponents) {
-            result += "xyzw"[x];
+            result += "01xyzw"[x + 2];
         }
         return result;
     }
diff --git a/src/sksl/ir/SkSLType.cpp b/src/sksl/ir/SkSLType.cpp
index 691ff7b..a3d5c59 100644
--- a/src/sksl/ir/SkSLType.cpp
+++ b/src/sksl/ir/SkSLType.cpp
@@ -14,6 +14,16 @@
     if (*this == other) {
         return 0;
     }
+    if (this->kind() == kNullable_Kind && other.kind() != kNullable_Kind) {
+        int result = this->componentType().coercionCost(other);
+        if (result != INT_MAX) {
+            ++result;
+        }
+        return result;
+    }
+    if (this->fName == "null" && other.kind() == kNullable_Kind) {
+        return 0;
+    }
     if (this->kind() == kVector_Kind && other.kind() == kVector_Kind) {
         if (this->columns() == other.columns()) {
             return this->componentType().coercionCost(other.componentType());
diff --git a/src/sksl/ir/SkSLType.h b/src/sksl/ir/SkSLType.h
index ba97e63..532bfbc 100644
--- a/src/sksl/ir/SkSLType.h
+++ b/src/sksl/ir/SkSLType.h
@@ -45,6 +45,7 @@
         kArray_Kind,
         kEnum_Kind,
         kGeneric_Kind,
+        kNullable_Kind,
         kMatrix_Kind,
         kOther_Kind,
         kSampler_Kind,
@@ -115,14 +116,15 @@
     }
 
     // Create a scalar type.
-    Type(const char* name, NumberKind numberKind, int priority)
+    Type(const char* name, NumberKind numberKind, int priority, bool highPrecision = false)
     : INHERITED(-1, kType_Kind, StringFragment())
     , fNameString(name)
     , fTypeKind(kScalar_Kind)
     , fNumberKind(numberKind)
     , fPriority(priority)
     , fColumns(1)
-    , fRows(1) {
+    , fRows(1)
+    , fHighPrecision(highPrecision) {
         fName.fChars = fNameString.c_str();
         fName.fLength = fNameString.size();
     }
@@ -144,6 +146,20 @@
         fName.fLength = fNameString.size();
     }
 
+    // Create a nullable type.
+    Type(String name, Kind kind, const Type& componentType)
+    : INHERITED(-1, kType_Kind, StringFragment())
+    , fNameString(std::move(name))
+    , fTypeKind(kind)
+    , fNumberKind(kNonnumeric_NumberKind)
+    , fComponentType(&componentType)
+    , fColumns(1)
+    , fRows(1)
+    , fDimensions(SpvDim1D) {
+        fName.fChars = fNameString.c_str();
+        fName.fLength = fNameString.size();
+    }
+
     // Create a vector type.
     Type(const char* name, const Type& componentType, int columns)
     : Type(name, kVector_Kind, componentType, columns) {}
@@ -289,13 +305,23 @@
     }
 
     /**
+     * For nullable types, returns the base type, otherwise returns the type itself.
+     */
+    const Type& nonnullable() const {
+        if (fTypeKind == kNullable_Kind) {
+            return this->componentType();
+        }
+        return *this;
+    }
+
+    /**
      * For matrices and vectors, returns the number of columns (e.g. both mat3 and float3return 3).
      * For scalars, returns 1. For arrays, returns either the size of the array (if known) or -1.
      * For all other types, causes an SkASSERTion failure.
      */
     int columns() const {
         SkASSERT(fTypeKind == kScalar_Kind || fTypeKind == kVector_Kind ||
-               fTypeKind == kMatrix_Kind || fTypeKind == kArray_Kind);
+                 fTypeKind == kMatrix_Kind || fTypeKind == kArray_Kind);
         return fColumns;
     }
 
@@ -347,6 +373,13 @@
         return fIsSampled;
     }
 
+    bool highPrecision() const {
+        if (fComponentType) {
+            return fComponentType->highPrecision();
+        }
+        return fHighPrecision;
+    }
+
     /**
      * Returns the corresponding vector or matrix type with the specified number of columns and
      * rows.
@@ -371,6 +404,7 @@
     bool fIsArrayed = false;
     bool fIsMultisampled = false;
     bool fIsSampled = false;
+    bool fHighPrecision = false;
 };
 
 } // namespace
diff --git a/src/sksl/ir/SkSLVariable.h b/src/sksl/ir/SkSLVariable.h
index 5624483..8559711 100644
--- a/src/sksl/ir/SkSLVariable.h
+++ b/src/sksl/ir/SkSLVariable.h
@@ -53,9 +53,13 @@
     }
 
     bool dead() const {
-        return !fWriteCount || (!fReadCount && !(fModifiers.fFlags & (Modifiers::kOut_Flag |
-                                                                      Modifiers::kPLS_Flag |
-                                                                      Modifiers::kPLSOut_Flag)));
+        if (fModifiers.fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag |
+                                 Modifiers::kUniform_Flag)) {
+            return false;
+        }
+        return !fWriteCount ||
+               (!fReadCount && !(fModifiers.fFlags & (Modifiers::kPLS_Flag |
+                                                      Modifiers::kPLSOut_Flag)));
     }
 
     mutable Modifiers fModifiers;
diff --git a/src/sksl/ir/SkSLVariableReference.cpp b/src/sksl/ir/SkSLVariableReference.cpp
index e6092c9..aaaa179 100644
--- a/src/sksl/ir/SkSLVariableReference.cpp
+++ b/src/sksl/ir/SkSLVariableReference.cpp
@@ -95,7 +95,8 @@
     }
     if (irGenerator.fKind == Program::kPipelineStage_Kind &&
         fVariable.fStorage == Variable::kGlobal_Storage &&
-        (fVariable.fModifiers.fFlags & Modifiers::kIn_Flag)) {
+        (fVariable.fModifiers.fFlags & Modifiers::kIn_Flag) &&
+        !(fVariable.fModifiers.fFlags & Modifiers::kUniform_Flag)) {
         return irGenerator.getArg(fOffset, fVariable.fName);
     }
     if ((fVariable.fModifiers.fFlags & Modifiers::kConst_Flag) && fVariable.fInitialValue &&
diff --git a/src/sksl/lex/DFAState.h b/src/sksl/lex/DFAState.h
index ae5aa4e..01d34a4 100644
--- a/src/sksl/lex/DFAState.h
+++ b/src/sksl/lex/DFAState.h
@@ -58,7 +58,7 @@
 namespace std {
     template<> struct hash<DFAState::Label> {
         size_t operator()(const DFAState::Label& s) const {
-            int result = 0;
+            size_t result = 0;
             for (int i : s.fStates) {
                 result = result * 101 + i;
             }
diff --git a/src/sksl/lex/Main.cpp b/src/sksl/lex/Main.cpp
index 04f266b..f08035e 100644
--- a/src/sksl/lex/Main.cpp
+++ b/src/sksl/lex/Main.cpp
@@ -98,6 +98,9 @@
     for (const auto& row : dfa.fTransitions) {
         states = std::max(states, row.size());
     }
+    // arbitrarily-chosen character which is greater than START_CHAR and should not appear in actual
+    // input
+    out << "static const uint8_t INVALID_CHAR = 18;";
     out << "static int8_t mappings[" << dfa.fCharMappings.size() << "] = {\n    ";
     const char* separator = "";
     for (int m : dfa.fCharMappings) {
@@ -142,12 +145,18 @@
     out << "        return " << token << "(" << token << "::END_OF_FILE, startOffset, 0);\n";
     out << "    }\n";
     out << "    int16_t state = 1;\n";
-    out << "    while (fOffset < fLength) {\n";
-    out << "        if ((uint8_t) fText[fOffset] >= " << dfa.fCharMappings.size() << ") {";
-    out << "            ++fOffset;\n";
-    out << "            break;";
+    out << "    for (;;) {\n";
+    out << "        if (fOffset >= fLength) {\n";
+    out << "            if (accepts[state] == -1) {\n";
+    out << "                return Token(Token::END_OF_FILE, startOffset, 0);\n";
+    out << "            }\n";
+    out << "            break;\n";
+    out << "        }\n";
+    out << "        uint8_t c = (uint8_t) fText[fOffset];";
+    out << "        if (c <= 8 || c >= " << dfa.fCharMappings.size() << ") {";
+    out << "            c = INVALID_CHAR;";
     out << "        }";
-    out << "        int16_t newState = transitions[mappings[(int) fText[fOffset]]][state];\n";
+    out << "        int16_t newState = transitions[mappings[c]][state];\n";
     out << "        if (!newState) {\n";
     out << "            break;\n";
     out << "        }\n";
diff --git a/src/sksl/lex/sksl.lex b/src/sksl/lex/sksl.lex
index f5ba759..3ed96fc 100644
--- a/src/sksl/lex/sksl.lex
+++ b/src/sksl/lex/sksl.lex
@@ -16,14 +16,12 @@
 CONTINUE       = "continue"
 DISCARD        = "discard"
 RETURN         = "return"
+NULL_LITERAL   = "null"
 IN             = "in"
 OUT            = "out"
 INOUT          = "inout"
 UNIFORM        = "uniform"
 CONST          = "const"
-LOWP           = "lowp"
-MEDIUMP        = "mediump"
-HIGHP          = "highp"
 FLAT           = "flat"
 NOPERSPECTIVE  = "noperspective"
 READONLY       = "readonly"
diff --git a/src/sksl/sksl_fp.inc b/src/sksl/sksl_fp.inc
index 7a3963d..a1deaca 100644
--- a/src/sksl/sksl_fp.inc
+++ b/src/sksl/sksl_fp.inc
@@ -25,4 +25,6 @@
 
 half4 process(fragmentProcessor fp);
 half4 process(fragmentProcessor fp, half4 input);
+half4 process(fragmentProcessor? fp);
+half4 process(fragmentProcessor? fp, half4 input);
 )
diff --git a/src/sksl/sksl_mixer.inc b/src/sksl/sksl_mixer.inc
new file mode 100644
index 0000000..88143d4
--- /dev/null
+++ b/src/sksl/sksl_mixer.inc
@@ -0,0 +1,8 @@
+STRINGIFY(
+in fragmentProcessor _child1;
+in fragmentProcessor _child2;
+
+layout(builtin=10004) out half4 sk_OutColor;
+
+half4 process(fragmentProcessor fp);
+)
diff --git a/src/svg/SkSVGCanvas.cpp b/src/svg/SkSVGCanvas.cpp
index 3d1ff0e..55993af 100644
--- a/src/svg/SkSVGCanvas.cpp
+++ b/src/svg/SkSVGCanvas.cpp
@@ -10,15 +10,12 @@
 #include "SkMakeUnique.h"
 #include "SkXMLWriter.h"
 
-std::unique_ptr<SkCanvas> SkSVGCanvas::Make(const SkRect& bounds, SkXMLWriter* writer) {
+std::unique_ptr<SkCanvas> SkSVGCanvas::Make(const SkRect& bounds, SkWStream* writer) {
     // TODO: pass full bounds to the device
     SkISize size = bounds.roundOut().size();
-    sk_sp<SkBaseDevice> device(SkSVGDevice::Create(size, writer));
 
-    return skstd::make_unique<SkCanvas>(device);
-}
+    auto svgDevice = SkSVGDevice::Make(size, skstd::make_unique<SkXMLStreamWriter>(writer));
 
-std::unique_ptr<SkCanvas> SkSVGCanvas::Make(const SkRect& bounds, SkWStream* writer) {
-    SkXMLStreamWriter xmlWriter(writer);
-    return Make(bounds, &xmlWriter);
+    return svgDevice ? skstd::make_unique<SkCanvas>(svgDevice)
+                     : nullptr;
 }
diff --git a/src/svg/SkSVGDevice.cpp b/src/svg/SkSVGDevice.cpp
index c93fc13..af4a78d 100644
--- a/src/svg/SkSVGDevice.cpp
+++ b/src/svg/SkSVGDevice.cpp
@@ -24,6 +24,7 @@
 #include "SkParsePath.h"
 #include "SkPngCodec.h"
 #include "SkShader.h"
+#include "SkShaderBase.h"
 #include "SkStream.h"
 #include "SkTHash.h"
 #include "SkTo.h"
@@ -116,14 +117,14 @@
   if (!shader)
     return false;
 
-  SkShader::TileMode xy[2];
+  SkTileMode xy[2];
   SkImage* image = shader->isAImage(nullptr, xy);
 
   if (!image)
     return false;
 
   for (int i = 0; i < 2; i++) {
-    if (xy[i] == SkShader::kRepeat_TileMode)
+    if (xy[i] == SkTileMode::kRepeat)
       return true;
   }
   return false;
@@ -190,9 +191,12 @@
         fWriter->startElement(name);
     }
 
-    AutoElement(const char name[], SkXMLWriter* writer, ResourceBucket* bucket,
-                const MxCp& mc, const SkPaint& paint)
-        : fWriter(writer)
+    AutoElement(const char name[], const std::unique_ptr<SkXMLWriter>& writer)
+        : AutoElement(name, writer.get()) {}
+
+    AutoElement(const char name[], const std::unique_ptr<SkXMLWriter>& writer,
+                ResourceBucket* bucket, const MxCp& mc, const SkPaint& paint)
+        : fWriter(writer.get())
         , fResourceBucket(bucket) {
 
         Resources res = this->addResources(mc, paint);
@@ -439,7 +443,7 @@
                                                        Resources* resources) {
     SkMatrix outMatrix;
 
-    SkShader::TileMode xy[2];
+    SkTileMode xy[2];
     SkImage* image = shader->isAImage(&outMatrix, xy);
     SkASSERT(image);
 
@@ -453,7 +457,7 @@
     for (int i = 0; i < 2; i++) {
         int imageDimension = i == 0 ? imageSize.width() : imageSize.height();
         switch (xy[i]) {
-            case SkShader::kRepeat_TileMode:
+            case SkTileMode::kRepeat:
                 patternDims[i].appendScalar(imageDimension);
             break;
             default:
@@ -544,8 +548,8 @@
         gradient.addAttribute("x2", info.fPoint[1].x());
         gradient.addAttribute("y2", info.fPoint[1].y());
 
-        if (!shader->getLocalMatrix().isIdentity()) {
-            this->addAttribute("gradientTransform", svg_transform(shader->getLocalMatrix()));
+        if (!as_SB(shader)->getLocalMatrix().isIdentity()) {
+            this->addAttribute("gradientTransform", svg_transform(as_SB(shader)->getLocalMatrix()));
         }
 
         SkASSERT(info.fColorCount >= 2);
@@ -634,21 +638,18 @@
     }
 }
 
-SkBaseDevice* SkSVGDevice::Create(const SkISize& size, SkXMLWriter* writer) {
-    if (!writer) {
-        return nullptr;
-    }
-
-    return new SkSVGDevice(size, writer);
+sk_sp<SkBaseDevice> SkSVGDevice::Make(const SkISize& size, std::unique_ptr<SkXMLWriter> writer) {
+    return writer ? sk_sp<SkBaseDevice>(new SkSVGDevice(size, std::move(writer)))
+                  : nullptr;
 }
 
-SkSVGDevice::SkSVGDevice(const SkISize& size, SkXMLWriter* writer)
+SkSVGDevice::SkSVGDevice(const SkISize& size, std::unique_ptr<SkXMLWriter> writer)
     : INHERITED(SkImageInfo::MakeUnknown(size.fWidth, size.fHeight),
                 SkSurfaceProps(0, kUnknown_SkPixelGeometry))
-    , fWriter(writer)
+    , fWriter(std::move(writer))
     , fResourceBucket(new ResourceBucket)
 {
-    SkASSERT(writer);
+    SkASSERT(fWriter);
 
     fWriter->writeHeader();
 
@@ -661,8 +662,7 @@
     fRootElement->addAttribute("height", size.height());
 }
 
-SkSVGDevice::~SkSVGDevice() {
-}
+SkSVGDevice::~SkSVGDevice() = default;
 
 void SkSVGDevice::drawPaint(const SkPaint& paint) {
     AutoElement rect("rect", fWriter, fResourceBucket.get(), MxCp(this), paint);
@@ -807,16 +807,6 @@
     }
 }
 
-void SkSVGDevice::drawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y,
-                             const SkPaint& paint) {
-    MxCp mc(this);
-    SkMatrix adjustedMatrix = *mc.fMatrix;
-    adjustedMatrix.preTranslate(x, y);
-    mc.fMatrix = &adjustedMatrix;
-
-    drawBitmapCommon(mc, bitmap, paint);
-}
-
 void SkSVGDevice::drawSprite(const SkBitmap& bitmap,
                              int x, int y, const SkPaint& paint) {
     MxCp mc(this);
diff --git a/src/svg/SkSVGDevice.h b/src/svg/SkSVGDevice.h
index b6c4d9d..2d7922a 100644
--- a/src/svg/SkSVGDevice.h
+++ b/src/svg/SkSVGDevice.h
@@ -15,7 +15,7 @@
 
 class SkSVGDevice : public SkClipStackDevice {
 public:
-    static SkBaseDevice* Create(const SkISize& size, SkXMLWriter* writer);
+    static sk_sp<SkBaseDevice> Make(const SkISize& size, std::unique_ptr<SkXMLWriter>);
 
 protected:
     void drawPaint(const SkPaint& paint) override;
@@ -29,7 +29,6 @@
                   const SkPaint& paint,
                   bool pathIsMutable = false) override;
 
-    void drawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y, const SkPaint& paint) override;
     void drawSprite(const SkBitmap& bitmap,
                     int x, int y, const SkPaint& paint) override;
     void drawBitmapRect(const SkBitmap&,
@@ -43,7 +42,7 @@
                     const SkPaint&) override;
 
 private:
-    SkSVGDevice(const SkISize& size, SkXMLWriter* writer);
+    SkSVGDevice(const SkISize& size, std::unique_ptr<SkXMLWriter>);
     ~SkSVGDevice() override;
 
     struct MxCp;
@@ -52,7 +51,7 @@
     class AutoElement;
     class ResourceBucket;
 
-    SkXMLWriter*                    fWriter;
+    std::unique_ptr<SkXMLWriter>    fWriter;
     std::unique_ptr<AutoElement>    fRootElement;
     std::unique_ptr<ResourceBucket> fResourceBucket;
 
diff --git a/src/utils/SkCamera.cpp b/src/utils/SkCamera.cpp
index 0811e63..ca71061 100644
--- a/src/utils/SkCamera.cpp
+++ b/src/utils/SkCamera.cpp
@@ -105,27 +105,27 @@
 }
 
 void SkMatrix3D::setRotateX(SkScalar degX) {
-    SkScalar    s, c;
-
-    s = SkScalarSinCos(SkDegreesToRadians(degX), &c);
+    SkScalar r = SkDegreesToRadians(degX),
+             s = SkScalarSin(r),
+             c = SkScalarCos(r);
     this->setRow(0, SK_Scalar1, 0, 0);
     this->setRow(1, 0, c, -s);
     this->setRow(2, 0, s, c);
 }
 
 void SkMatrix3D::setRotateY(SkScalar degY) {
-    SkScalar    s, c;
-
-    s = SkScalarSinCos(SkDegreesToRadians(degY), &c);
+    SkScalar r = SkDegreesToRadians(degY),
+             s = SkScalarSin(r),
+             c = SkScalarCos(r);
     this->setRow(0, c, 0, -s);
     this->setRow(1, 0, SK_Scalar1, 0);
     this->setRow(2, s, 0, c);
 }
 
 void SkMatrix3D::setRotateZ(SkScalar degZ) {
-    SkScalar    s, c;
-
-    s = SkScalarSinCos(SkDegreesToRadians(degZ), &c);
+    SkScalar r = SkDegreesToRadians(degZ),
+             s = SkScalarSin(r),
+             c = SkScalarCos(r);
     this->setRow(0, c, -s, 0);
     this->setRow(1, s, c, 0);
     this->setRow(2, 0, 0, SK_Scalar1);
diff --git a/src/utils/SkLua.cpp b/src/utils/SkLua.cpp
index a7e978a..aef0889 100644
--- a/src/utils/SkLua.cpp
+++ b/src/utils/SkLua.cpp
@@ -1019,10 +1019,10 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-static const char* mode2string(SkShader::TileMode mode) {
-    static const char* gNames[] = { "clamp", "repeat", "mirror" };
+static const char* mode2string(SkTileMode mode) {
+    static const char* gNames[] = { "clamp", "repeat", "mirror", "decal" };
     SkASSERT((unsigned)mode < SK_ARRAY_COUNT(gNames));
-    return gNames[mode];
+    return gNames[static_cast<int>(mode)];
 }
 
 static const char* gradtype2string(SkShader::GradientType t) {
@@ -1042,7 +1042,7 @@
     SkShader* shader = get_ref<SkShader>(L, 1);
     if (shader) {
         SkMatrix matrix;
-        SkShader::TileMode modes[2];
+        SkTileMode modes[2];
         if (SkImage* image = shader->isAImage(&matrix, modes)) {
             lua_newtable(L);
             setfield_number(L, "id", image->uniqueID());
@@ -1071,7 +1071,7 @@
 
             lua_newtable(L);
             setfield_string(L,  "type",           gradtype2string(t));
-            setfield_string(L,  "tile",           mode2string(info.fTileMode));
+            setfield_string(L,  "tile",           mode2string((SkTileMode)info.fTileMode));
             setfield_number(L,  "colorCount",     info.fColorCount);
 
             lua_newtable(L);
@@ -1525,7 +1525,7 @@
 }
 
 static int limage_newShader(lua_State* L) {
-    SkShader::TileMode tmode = SkShader::kClamp_TileMode;
+    SkTileMode tmode = SkTileMode::kClamp;
     const SkMatrix* localM = nullptr;
     push_ref(L, get_ref<SkImage>(L, 1)->makeShader(tmode, tmode, localM));
     return 1;
@@ -1832,8 +1832,7 @@
 
     SkPoint pts[] = { { x0, y0 }, { x1, y1 } };
     SkColor colors[] = { c0, c1 };
-    sk_sp<SkShader> s(SkGradientShader::MakeLinear(pts, colors, nullptr, 2,
-                                                   SkShader::kClamp_TileMode));
+    sk_sp<SkShader> s(SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp));
     if (!s) {
         lua_pushnil(L);
     } else {
@@ -1873,7 +1872,7 @@
     SkRect bounds;
     lua2rect(L, 2, &bounds);
 
-    SkShaper shaper(nullptr);
+    std::unique_ptr<SkShaper> shaper = SkShaper::Make();
 
     // TODO: restore this logic based on SkFont instead of SkPaint
 #if 0
@@ -1882,12 +1881,11 @@
 #else
     SkFont font;
 #endif
-    SkTextBlobBuilderRunHandler builder;
-    SkPoint end = shaper.shape(&builder, font, text, strlen(text), true,
-                               { bounds.left(), bounds.top() }, bounds.width());
+    SkTextBlobBuilderRunHandler builder(text, { bounds.left(), bounds.top() });
+    shaper->shape(text, strlen(text), font, true, bounds.width(), &builder);
 
     push_ref<SkTextBlob>(L, builder.makeBlob());
-    SkLua(L).pushScalar(end.fY);
+    SkLua(L).pushScalar(builder.endPoint().fY);
     return 2;
 }
 
diff --git a/src/utils/SkNWayCanvas.cpp b/src/utils/SkNWayCanvas.cpp
index 1e6cd89..39d5a40 100644
--- a/src/utils/SkNWayCanvas.cpp
+++ b/src/utils/SkNWayCanvas.cpp
@@ -162,14 +162,6 @@
     }
 }
 
-void SkNWayCanvas::onDrawEdgeAARect(const SkRect& rect, SkCanvas::QuadAAFlags aa, SkColor color,
-                                    SkBlendMode mode) {
-    Iter iter(fList);
-    while (iter.next()) {
-        iter->experimental_DrawEdgeAARectV1(rect, aa, color, mode);
-    }
-}
-
 void SkNWayCanvas::onDrawRegion(const SkRegion& region, const SkPaint& paint) {
     Iter iter(fList);
     while (iter.next()) {
@@ -277,14 +269,6 @@
     }
 }
 
-void SkNWayCanvas::onDrawImageSet(const SkCanvas::ImageSetEntry set[], int count,
-                                  SkFilterQuality filterQuality, SkBlendMode mode) {
-    Iter iter(fList);
-    while (iter.next()) {
-        iter->experimental_DrawImageSetV1(set, count, filterQuality, mode);
-    }
-}
-
 void SkNWayCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                                   const SkPaint &paint) {
     Iter iter(fList);
@@ -348,6 +332,24 @@
     }
 }
 
+void SkNWayCanvas::onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
+                                    QuadAAFlags aa, SkColor color, SkBlendMode mode) {
+    Iter iter(fList);
+    while (iter.next()) {
+        iter->experimental_DrawEdgeAAQuad(rect, clip, aa, color, mode);
+    }
+}
+
+void SkNWayCanvas::onDrawEdgeAAImageSet(const ImageSetEntry set[], int count,
+                                        const SkPoint dstClips[], const SkMatrix preViewMatrices[],
+                                        const SkPaint* paint, SrcRectConstraint constraint) {
+    Iter iter(fList);
+    while (iter.next()) {
+        iter->experimental_DrawEdgeAAImageSet(
+                set, count, dstClips, preViewMatrices, paint, constraint);
+    }
+}
+
 void SkNWayCanvas::onFlush() {
     Iter iter(fList);
     while (iter.next()) {
diff --git a/src/utils/SkPaintFilterCanvas.cpp b/src/utils/SkPaintFilterCanvas.cpp
index ded7fc2..f09e6c2 100644
--- a/src/utils/SkPaintFilterCanvas.cpp
+++ b/src/utils/SkPaintFilterCanvas.cpp
@@ -64,17 +64,6 @@
     }
 }
 
-void SkPaintFilterCanvas::onDrawEdgeAARect(const SkRect& rect, SkCanvas::QuadAAFlags aa,
-                                           SkColor color, SkBlendMode mode) {
-    SkPaint paint;
-    paint.setColor(color);
-    paint.setBlendMode(mode);
-    AutoPaintFilter apf(this, kRect_Type, paint);
-    if (apf.shouldDraw()) {
-        this->SkNWayCanvas::onDrawEdgeAARect(rect, aa, paint.getColor(), paint.getBlendMode());
-    }
-}
-
 void SkPaintFilterCanvas::onDrawRRect(const SkRRect& rrect, const SkPaint& paint) {
     AutoPaintFilter apf(this, kRRect_Type, paint);
     if (apf.shouldDraw()) {
@@ -184,17 +173,6 @@
     }
 }
 
-void SkPaintFilterCanvas::onDrawImageSet(const SkCanvas::ImageSetEntry set[], int count,
-                                         SkFilterQuality filterQuality, SkBlendMode mode) {
-    SkPaint paint;
-    paint.setBlendMode(mode);
-    AutoPaintFilter apf(this, kBitmap_Type, &paint);
-    mode = paint.getBlendMode();
-    if (apf.shouldDraw()) {
-        this->SkNWayCanvas::onDrawImageSet(set, count, filterQuality, mode);
-    }
-}
-
 void SkPaintFilterCanvas::onDrawVerticesObject(const SkVertices* vertices,
                                                const SkVertices::Bone bones[], int boneCount,
                                                SkBlendMode bmode, const SkPaint& paint) {
@@ -256,6 +234,29 @@
     this->SkNWayCanvas::onDrawShadowRec(path, rec);
 }
 
+void SkPaintFilterCanvas::onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
+                                           QuadAAFlags aa, SkColor color, SkBlendMode mode) {
+    SkPaint paint;
+    paint.setColor(color);
+    paint.setBlendMode(mode);
+    AutoPaintFilter apf(this, kRect_Type, paint);
+    if (apf.shouldDraw()) {
+        this->SkNWayCanvas::onDrawEdgeAAQuad(rect, clip, aa, apf.paint()->getColor(),
+                                             apf.paint()->getBlendMode());
+    }
+}
+
+void SkPaintFilterCanvas::onDrawEdgeAAImageSet(const ImageSetEntry set[], int count,
+                                               const SkPoint dstClips[],
+                                               const SkMatrix preViewMatrices[],
+                                               const SkPaint* paint, SrcRectConstraint constraint) {
+    AutoPaintFilter apf(this, kBitmap_Type, paint);
+    if (apf.shouldDraw()) {
+        this->SkNWayCanvas::onDrawEdgeAAImageSet(
+                set, count, dstClips, preViewMatrices, apf.paint(), constraint);
+    }
+}
+
 sk_sp<SkSurface> SkPaintFilterCanvas::onNewSurface(const SkImageInfo& info,
                                                    const SkSurfaceProps& props) {
     return proxy()->makeSurface(info, &props);
diff --git a/src/utils/SkPolyUtils.cpp b/src/utils/SkPolyUtils.cpp
index cea76fc..acd4371 100644
--- a/src/utils/SkPolyUtils.cpp
+++ b/src/utils/SkPolyUtils.cpp
@@ -318,6 +318,19 @@
         return false;
     }
 
+    // can't inset by a negative or non-finite amount
+    if (inset < -SK_ScalarNearlyZero || !SkScalarIsFinite(inset)) {
+        return false;
+    }
+
+    // insetting close to zero just returns the original poly
+    if (inset <= SK_ScalarNearlyZero) {
+        for (int i = 0; i < inputPolygonSize; ++i) {
+            *insetPolygon->push() = inputPolygonVerts[i];
+        }
+        return true;
+    }
+
     // get winding direction
     int winding = SkGetPolygonWinding(inputPolygonVerts, inputPolygonSize);
     if (0 == winding) {
@@ -468,7 +481,8 @@
     int steps = SkScalarRoundToInt(floatSteps);
 
     SkScalar dTheta = steps > 0 ? theta / steps : 0;
-    *rotSin = SkScalarSinCos(dTheta, rotCos);
+    *rotSin = SkScalarSin(dTheta);
+    *rotCos = SkScalarCos(dTheta);
     *n = steps;
     return true;
 }
@@ -1147,6 +1161,17 @@
         return false;
     }
 
+    // offsetting close to zero just returns the original poly
+    if (SkScalarNearlyZero(offset)) {
+        for (int i = 0; i < inputPolygonSize; ++i) {
+            *offsetPolygon->push() = inputPolygonVerts[i];
+            if (polygonIndices) {
+                *polygonIndices->push() = i;
+            }
+        }
+        return true;
+    }
+
     // get winding direction
     int winding = SkGetPolygonWinding(inputPolygonVerts, inputPolygonSize);
     if (0 == winding) {
diff --git a/src/utils/SkShadowUtils.cpp b/src/utils/SkShadowUtils.cpp
index a382d53..163314f 100644
--- a/src/utils/SkShadowUtils.cpp
+++ b/src/utils/SkShadowUtils.cpp
@@ -12,6 +12,7 @@
 #include "SkColorData.h"
 #include "SkDevice.h"
 #include "SkDrawShadowInfo.h"
+#include "SkEffectPriv.h"
 #include "SkMaskFilter.h"
 #include "SkPath.h"
 #include "SkRandom.h"
@@ -24,7 +25,7 @@
 #include <new>
 #if SK_SUPPORT_GPU
 #include "GrShape.h"
-#include "effects/GrBlurredEdgeFragmentProcessor.h"
+#include "effects/generated/GrBlurredEdgeFragmentProcessor.h"
 #endif
 
 /**
@@ -41,14 +42,14 @@
 
 #if SK_SUPPORT_GPU
     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
-            GrContext*, const GrColorSpaceInfo&) const override;
+            GrRecordingContext*, const GrColorSpaceInfo&) const override;
 #endif
 
 protected:
     void flatten(SkWriteBuffer&) const override {}
-    void onAppendStages(SkRasterPipeline* pipeline, SkColorSpace* dstCS, SkArenaAlloc* alloc,
-                        bool shaderIsOpaque) const override {
-        pipeline->append(SkRasterPipeline::gauss_a_to_rgba);
+    bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override {
+        rec.fPipeline->append(SkRasterPipeline::gauss_a_to_rgba);
+        return true;
     }
 private:
     SK_FLATTENABLE_HOOKS(SkGaussianColorFilter)
@@ -65,7 +66,7 @@
 #if SK_SUPPORT_GPU
 
 std::unique_ptr<GrFragmentProcessor> SkGaussianColorFilter::asFragmentProcessor(
-        GrContext*, const GrColorSpaceInfo&) const {
+        GrRecordingContext*, const GrColorSpaceInfo&) const {
     return GrBlurredEdgeFragmentProcessor::Make(GrBlurredEdgeFragmentProcessor::Mode::kGaussian);
 }
 #endif
@@ -386,7 +387,7 @@
 template <typename FACTORY>
 bool draw_shadow(const FACTORY& factory,
                  std::function<void(const SkVertices*, SkBlendMode, const SkPaint&,
-                 SkScalar tx, SkScalar ty)> drawProc, ShadowedPath& path, SkColor color) {
+                 SkScalar tx, SkScalar ty, bool)> drawProc, ShadowedPath& path, SkColor color) {
     FindContext<FACTORY> context(&path.viewMatrix(), &factory);
 
     SkResourceCache::Key* key = nullptr;
@@ -434,11 +435,11 @@
     // Run the vertex color through a GaussianColorFilter and then modulate the grayscale result of
     // that against our 'color' param.
     paint.setColorFilter(
-         SkColorFilter::MakeModeFilter(color, SkBlendMode::kModulate)->makeComposed(
-                                                                    SkGaussianColorFilter::Make()));
+         SkColorFilters::Blend(color, SkBlendMode::kModulate)->makeComposed(
+                                                                SkGaussianColorFilter::Make()));
 
     drawProc(vertices.get(), SkBlendMode::kModulate, paint,
-             context.fTranslate.fX, context.fTranslate.fY);
+             context.fTranslate.fX, context.fTranslate.fY, path.viewMatrix().hasPerspective());
 
     return true;
 }
@@ -539,10 +540,15 @@
 
 void SkBaseDevice::drawShadow(const SkPath& path, const SkDrawShadowRec& rec) {
     auto drawVertsProc = [this](const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint,
-                                SkScalar tx, SkScalar ty) {
+                                SkScalar tx, SkScalar ty, bool hasPerspective) {
         if (vertices->vertexCount()) {
-            SkAutoDeviceCTMRestore adr(this, SkMatrix::Concat(this->ctm(),
-                                                              SkMatrix::MakeTrans(tx, ty)));
+            // For perspective shadows we've already computed the shadow in world space,
+            // and we can't translate it without changing it. Otherwise we concat the
+            // change in translation from the cached version.
+            SkAutoDeviceCTMRestore adr(
+                this,
+                hasPerspective ? SkMatrix::I()
+                               : SkMatrix::Concat(this->ctm(), SkMatrix::MakeTrans(tx, ty)));
             this->drawVertices(vertices, nullptr, 0, mode, paint);
         }
     };
@@ -575,7 +581,7 @@
                 // Run the vertex color through a GaussianColorFilter and then modulate the
                 // grayscale result of that against our 'color' param.
                 paint.setColorFilter(
-                    SkColorFilter::MakeModeFilter(rec.fAmbientColor,
+                    SkColorFilters::Blend(rec.fAmbientColor,
                                                   SkBlendMode::kModulate)->makeComposed(
                                                                    SkGaussianColorFilter::Make()));
                 this->drawVertices(vertices.get(), nullptr, 0, SkBlendMode::kModulate, paint);
@@ -598,6 +604,7 @@
                 // Pretransform the path to avoid transforming the stroke, below.
                 SkPath devSpacePath;
                 path.transform(viewMatrix, &devSpacePath);
+                devSpacePath.setIsVolatile(true);
 
                 // The tesselator outsets by AmbientBlurRadius (or 'r') to get the outer ring of
                 // the tesselation, and sets the alpha on the path to 1/AmbientRecipAlpha (or 'a').
@@ -655,7 +662,7 @@
                 // Run the vertex color through a GaussianColorFilter and then modulate the
                 // grayscale result of that against our 'color' param.
                 paint.setColorFilter(
-                    SkColorFilter::MakeModeFilter(rec.fSpotColor,
+                    SkColorFilters::Blend(rec.fSpotColor,
                                                   SkBlendMode::kModulate)->makeComposed(
                                                       SkGaussianColorFilter::Make()));
                 this->drawVertices(vertices.get(), nullptr, 0, SkBlendMode::kModulate, paint);
diff --git a/src/utils/SkUTF.cpp b/src/utils/SkUTF.cpp
index 0670ae0..00015f6 100644
--- a/src/utils/SkUTF.cpp
+++ b/src/utils/SkUTF.cpp
@@ -153,7 +153,7 @@
 
 SkUnichar SkUTF::NextUTF16(const uint16_t** ptr, const uint16_t* end) {
     if (!ptr || !end ) {
-        return next_fail(ptr, end);
+        return -1;
     }
     const uint16_t* src = *ptr;
     if (!src || src + 1 > end || !is_align2(intptr_t(src))) {
diff --git a/src/utils/win/SkWGL_win.cpp b/src/utils/win/SkWGL_win.cpp
index 1a42c8a..d5a6632 100644
--- a/src/utils/win/SkWGL_win.cpp
+++ b/src/utils/win/SkWGL_win.cpp
@@ -410,10 +410,6 @@
 
     wglMakeCurrent(prevDC, prevGLRC);
 
-    // This might help make the context non-vsynced.
-    if (extensions.hasExtension(dc, "WGL_EXT_swap_control")) {
-        extensions.swapInterval(-1);
-    }
     return glrc;
 }
 
diff --git a/src/xps/SkXPSDevice.cpp b/src/xps/SkXPSDevice.cpp
index 713b74b..f3c6f4e 100644
--- a/src/xps/SkXPSDevice.cpp
+++ b/src/xps/SkXPSDevice.cpp
@@ -26,7 +26,6 @@
 #include "SkData.h"
 #include "SkDraw.h"
 #include "SkEndian.h"
-#include "SkFindAndPlaceGlyph.h"
 #include "SkGeometry.h"
 #include "SkHRESULT.h"
 #include "SkIStream.h"
@@ -41,6 +40,7 @@
 #include "SkRasterClip.h"
 #include "SkSFNTHeader.h"
 #include "SkShader.h"
+#include "SkShaderBase.h"
 #include "SkSize.h"
 #include "SkStream.h"
 #include "SkStrikeCache.h"
@@ -465,13 +465,13 @@
     return xps_point(skTransformedPoint);
 }
 
-static XPS_SPREAD_METHOD xps_spread_method(SkShader::TileMode tileMode) {
+static XPS_SPREAD_METHOD xps_spread_method(SkTileMode tileMode) {
     switch (tileMode) {
-    case SkShader::kClamp_TileMode:
+    case SkTileMode::kClamp:
         return XPS_SPREAD_METHOD_PAD;
-    case SkShader::kRepeat_TileMode:
+    case SkTileMode::kRepeat:
         return XPS_SPREAD_METHOD_REPEAT;
-    case SkShader::kMirror_TileMode:
+    case SkTileMode::kMirror:
         return XPS_SPREAD_METHOD_REFLECT;
     default:
         SkDEBUGFAIL("Unknown tile mode.");
@@ -614,8 +614,8 @@
 //TODO(bungeman): In the future, should skia add None,
 //handle None+Mirror and None+Repeat correctly.
 //None is currently an internal hack so masks don't repeat (None+None only).
-static XPS_TILE_MODE SkToXpsTileMode[SkShader::kTileModeCount+1]
-                                    [SkShader::kTileModeCount+1] = {
+static XPS_TILE_MODE gSkToXpsTileMode[kSkTileModeCount+1]
+                                     [kSkTileModeCount+1] = {
                //Clamp  //Repeat //Mirror //None
     /*Clamp */ {XTM_N,  XTM_T,   XTM_Y,   XTM_N},
     /*Repeat*/ {XTM_T,  XTM_T,   XTM_Y,   XTM_N},
@@ -623,10 +623,14 @@
     /*None  */ {XTM_N,  XTM_N,   XTM_Y,   XTM_N},
 };
 
+static XPS_TILE_MODE SkToXpsTileMode(SkTileMode tmx, SkTileMode tmy) {
+    return gSkToXpsTileMode[(unsigned)tmx][(unsigned)tmy];
+}
+
 HRESULT SkXPSDevice::createXpsImageBrush(
         const SkBitmap& bitmap,
         const SkMatrix& localMatrix,
-        const SkShader::TileMode (&xy)[2],
+        const SkTileMode (&xy)[2],
         const SkAlpha alpha,
         IXpsOMTileBrush** xpsBrush) {
     SkDynamicMemoryWStream write;
@@ -668,10 +672,10 @@
                                             &xpsImageBrush),
         "Could not create image brush.");
 
-    if (SkShader::kClamp_TileMode != xy[0] &&
-        SkShader::kClamp_TileMode != xy[1]) {
+    if (SkTileMode::kClamp != xy[0] &&
+        SkTileMode::kClamp != xy[1]) {
 
-        HRM(xpsImageBrush->SetTileMode(SkToXpsTileMode[xy[0]][xy[1]]),
+        HRM(xpsImageBrush->SetTileMode(SkToXpsTileMode(xy[0], xy[1])),
             "Could not set image tile mode");
         HRM(xpsImageBrush->SetOpacity(alpha / 255.0f),
             "Could not set image opacity.");
@@ -706,7 +710,7 @@
             "Could not set fill brush for image brush central path.");
 
         //add left/right
-        if (SkShader::kClamp_TileMode == xy[0]) {
+        if (SkTileMode::kClamp == xy[0]) {
             SkRect leftArea = SkRect::MakeLTRB(-BIG, 0, 0, bHeight);
             XPS_RECT leftImageViewBox = {
                 0.0, 0.0,
@@ -727,7 +731,7 @@
         }
 
         //add top/bottom
-        if (SkShader::kClamp_TileMode == xy[1]) {
+        if (SkTileMode::kClamp == xy[1]) {
             SkRect topArea = SkRect::MakeLTRB(0, -BIG, bWidth, 0);
             XPS_RECT topImageViewBox = {
                 0.0, 0.0,
@@ -748,8 +752,8 @@
         }
 
         //add tl, tr, bl, br
-        if (SkShader::kClamp_TileMode == xy[0] &&
-            SkShader::kClamp_TileMode == xy[1]) {
+        if (SkTileMode::kClamp == xy[0] &&
+            SkTileMode::kClamp == xy[1]) {
 
             const SkColor tlColor = bitmap.getColor(0,0);
             const SkRect tlArea = SkRect::MakeLTRB(-BIG, -BIG, 0, 0);
@@ -771,19 +775,19 @@
 
         //create visual brush from canvas
         XPS_RECT bound = {};
-        if (SkShader::kClamp_TileMode == xy[0] &&
-            SkShader::kClamp_TileMode == xy[1]) {
+        if (SkTileMode::kClamp == xy[0] &&
+            SkTileMode::kClamp == xy[1]) {
 
             bound.x = BIG_F / -2;
             bound.y = BIG_F / -2;
             bound.width = BIG_F;
             bound.height = BIG_F;
-        } else if (SkShader::kClamp_TileMode == xy[0]) {
+        } else if (SkTileMode::kClamp == xy[0]) {
             bound.x = BIG_F / -2;
             bound.y = 0.0f;
             bound.width = BIG_F;
             bound.height = static_cast<FLOAT>(bitmap.height());
-        } else if (SkShader::kClamp_TileMode == xy[1]) {
+        } else if (SkTileMode::kClamp == xy[1]) {
             bound.x = 0;
             bound.y = BIG_F / -2;
             bound.width = static_cast<FLOAT>(bitmap.width());
@@ -794,7 +798,7 @@
             "Could not create visual brush for image brush.");
         HRM(clampBrush->SetVisualLocal(brushCanvas.get()),
             "Could not set canvas on visual brush for image brush.");
-        HRM(clampBrush->SetTileMode(SkToXpsTileMode[xy[0]][xy[1]]),
+        HRM(clampBrush->SetTileMode(SkToXpsTileMode(xy[0], xy[1])),
             "Could not set tile mode on visual brush for image brush.");
         HRM(clampBrush->SetOpacity(alpha / 255.0f),
             "Could not set opacity on visual brush for image brush.");
@@ -878,7 +882,7 @@
             "Could not add linear gradient stop.");
     }
 
-    HRM(gradientBrush->SetSpreadMethod(xps_spread_method(info.fTileMode)),
+    HRM(gradientBrush->SetSpreadMethod(xps_spread_method((SkTileMode)info.fTileMode)),
         "Could not set spread method of linear gradient.");
 
     HRM(gradientBrush->SetOpacity(alpha / 255.0f),
@@ -955,7 +959,7 @@
             "Could not add radial gradient stop.");
     }
 
-    HRM(gradientBrush->SetSpreadMethod(xps_spread_method(info.fTileMode)),
+    HRM(gradientBrush->SetSpreadMethod(xps_spread_method((SkTileMode)info.fTileMode)),
         "Could not set spread method of radial gradient.");
 
     HRM(gradientBrush->SetOpacity(alpha / 255.0f),
@@ -1013,7 +1017,7 @@
             return S_OK;
         }
 
-        SkMatrix localMatrix = shader->getLocalMatrix();
+        SkMatrix localMatrix = as_SB(shader)->getLocalMatrix();
         if (parentTransform) {
             localMatrix.preConcat(*parentTransform);
         }
@@ -1049,11 +1053,11 @@
 
     SkBitmap outTexture;
     SkMatrix outMatrix;
-    SkShader::TileMode xy[2];
+    SkTileMode xy[2];
     SkImage* image = shader->isAImage(&outMatrix, xy);
     if (image && image->asLegacyBitmap(&outTexture)) {
         //TODO: outMatrix??
-        SkMatrix localMatrix = shader->getLocalMatrix();
+        SkMatrix localMatrix = as_SB(shader)->getLocalMatrix();
         if (parentTransform) {
             localMatrix.postConcat(*parentTransform);
         }
@@ -1421,9 +1425,9 @@
                    SkIntToScalar(mask.fBounds.fTop));
     m.postScale(SkScalarInvert(ppuScale.fX), SkScalarInvert(ppuScale.fY));
 
-    SkShader::TileMode xy[2];
-    xy[0] = (SkShader::TileMode)3;
-    xy[1] = (SkShader::TileMode)3;
+    SkTileMode xy[2];
+    xy[0] = (SkTileMode)3;
+    xy[1] = (SkTileMode)3;
 
     SkBitmap bm;
     bm.installMaskPixels(mask);
@@ -1731,80 +1735,6 @@
     return S_OK;
 }
 
-void SkXPSDevice::drawBitmap(const SkBitmap& bitmap,
-                             SkScalar x,
-                             SkScalar y,
-                             const SkPaint& paint) {
-    if (this->cs().isEmpty(size(*this))) {
-        return;
-    }
-
-    SkIRect srcRect;
-    srcRect.set(0, 0, bitmap.width(), bitmap.height());
-
-    //Create the new shaded path.
-    SkTScopedComPtr<IXpsOMPath> shadedPath;
-    HRVM(this->fXpsFactory->CreatePath(&shadedPath),
-         "Could not create path for bitmap.");
-
-    //Create the shaded geometry.
-    SkTScopedComPtr<IXpsOMGeometry> shadedGeometry;
-    HRVM(this->fXpsFactory->CreateGeometry(&shadedGeometry),
-         "Could not create geometry for bitmap.");
-
-    //Add the shaded geometry to the shaded path.
-    HRVM(shadedPath->SetGeometryLocal(shadedGeometry.get()),
-         "Could not set the geometry for bitmap.");
-
-    //Get the shaded figures from the shaded geometry.
-    SkTScopedComPtr<IXpsOMGeometryFigureCollection> shadedFigures;
-    HRVM(shadedGeometry->GetFigures(&shadedFigures),
-         "Could not get the figures for bitmap.");
-
-    SkMatrix transform = SkMatrix::MakeTrans(x, y);
-    transform.postConcat(this->ctm());
-
-    SkTScopedComPtr<IXpsOMMatrixTransform> xpsTransform;
-    HRV(this->createXpsTransform(transform, &xpsTransform));
-    if (xpsTransform.get()) {
-        HRVM(shadedGeometry->SetTransformLocal(xpsTransform.get()),
-             "Could not set transform for bitmap.");
-    } else {
-        //TODO: perspective that bitmap!
-    }
-
-    SkTScopedComPtr<IXpsOMGeometryFigure> rectFigure;
-    if (xpsTransform.get()) {
-        const SkShader::TileMode xy[2] = {
-            SkShader::kClamp_TileMode,
-            SkShader::kClamp_TileMode,
-        };
-        SkTScopedComPtr<IXpsOMTileBrush> xpsImageBrush;
-        HRV(this->createXpsImageBrush(bitmap,
-                                      transform,
-                                      xy,
-                                      paint.getAlpha(),
-                                      &xpsImageBrush));
-        HRVM(shadedPath->SetFillBrushLocal(xpsImageBrush.get()),
-             "Could not set bitmap brush.");
-
-        const SkRect bitmapRect = SkRect::MakeLTRB(0, 0,
-            SkIntToScalar(srcRect.width()), SkIntToScalar(srcRect.height()));
-        HRV(this->createXpsRect(bitmapRect, FALSE, TRUE, &rectFigure));
-    }
-    HRVM(shadedFigures->Append(rectFigure.get()),
-         "Could not add bitmap figure.");
-
-    //Get the current visual collection and add the shaded path to it.
-    SkTScopedComPtr<IXpsOMVisualCollection> currentVisuals;
-    HRVM(this->fCurrentXpsCanvas->GetVisuals(&currentVisuals),
-         "Could not get current visuals for bitmap");
-    HRVM(currentVisuals->Append(shadedPath.get()),
-         "Could not add bitmap to current visuals.");
-
-    HRV(this->clip(shadedPath.get()));
-}
-
 void SkXPSDevice::drawSprite(const SkBitmap& bitmap, int x, int y, const SkPaint& paint) {
     //TODO: override this for XPS
     SkDEBUGF("XPS drawSprite not yet implemented.");
@@ -1835,9 +1765,9 @@
 
     SkTScopedComPtr<IStream> fontStream;
     int ttcIndex;
-    SkStream* fontData = typeface->openStream(&ttcIndex);
+    std::unique_ptr<SkStreamAsset> fontData = typeface->openStream(&ttcIndex);
     //TODO: cannot handle FON fonts.
-    HRM(SkIStream::CreateFromSkStream(fontData, true, &fontStream),
+    HRM(SkIStream::CreateFromSkStream(fontData.release(), true, &fontStream),
         "Could not create font stream.");
 
     const size_t size =
@@ -2158,9 +2088,9 @@
         }
         matrix.mapRect(&actualDst, srcBounds);
     }
-    auto bitmapShader = SkMakeBitmapShader(bitmap, SkShader::kClamp_TileMode,
-                                           SkShader::kClamp_TileMode, &matrix,
-                                           kNever_SkCopyPixelsMode);
+    auto bitmapShader = SkMakeBitmapShaderForPaint(paint, bitmap, SkTileMode::kClamp,
+                                                   SkTileMode::kClamp, &matrix,
+                                                   kNever_SkCopyPixelsMode);
     SkASSERT(bitmapShader);
     if (!bitmapShader) { return; }
     SkPaint paintWithShader(paint);
diff --git a/src/xps/SkXPSDevice.h b/src/xps/SkXPSDevice.h
index 55558c0..9bafb02 100644
--- a/src/xps/SkXPSDevice.h
+++ b/src/xps/SkXPSDevice.h
@@ -88,10 +88,6 @@
     void drawPath(const SkPath& path,
                   const SkPaint& paint,
                   bool pathIsMutable = false) override;
-    void drawBitmap(const SkBitmap& bitmap,
-                    SkScalar x,
-                    SkScalar y,
-                    const SkPaint& paint) override;
     void drawSprite(const SkBitmap& bitmap,
                     int x, int y, const SkPaint& paint) override;
     void drawBitmapRect(const SkBitmap&,
@@ -173,7 +169,7 @@
     HRESULT createXpsImageBrush(
         const SkBitmap& bitmap,
         const SkMatrix& localMatrix,
-        const SkShader::TileMode (&xy)[2],
+        const SkTileMode (&xy)[2],
         const SkAlpha alpha,
         IXpsOMTileBrush** xpsBrush);
 
diff --git a/src/xps/SkXPSDocument.cpp b/src/xps/SkXPSDocument.cpp
index a8785a0..b0a88b0 100644
--- a/src/xps/SkXPSDocument.cpp
+++ b/src/xps/SkXPSDocument.cpp
@@ -62,7 +62,6 @@
 
 void SkXPSDocument::onEndPage() {
     SkASSERT(fCanvas.get());
-    fCanvas->flush();
     fCanvas.reset(nullptr);
     fDevice.endSheet();
 }
diff --git a/tests/AdvancedBlendTest.cpp b/tests/AdvancedBlendTest.cpp
index cda9dc2..3ff2a71 100644
--- a/tests/AdvancedBlendTest.cpp
+++ b/tests/AdvancedBlendTest.cpp
@@ -24,14 +24,16 @@
         const GrXPFactory* xpf = GrCustomXfermode::Get(blendMode);
 
         GrXPFactory::AnalysisProperties xpfAnalysis =
-                GrXPFactory::GetAnalysisProperties(xpf, opaque, coverage, caps);
+                GrXPFactory::GetAnalysisProperties(xpf, opaque, coverage, caps, GrClampType::kAuto);
 
         GrPaint paint;
         paint.setXPFactory(xpf);
         GrProcessorSet procs(std::move(paint));
         SkPMColor4f overrideColor;
         GrProcessorSet::Analysis processorAnalysis =
-                procs.finalize(opaque, coverage, nullptr, false, caps, &overrideColor);
+                procs.finalize(
+                        opaque, coverage, nullptr, &GrUserStencilSettings::kUnused,
+                        GrFSAAType::kNone, caps, GrClampType::kAuto, &overrideColor);
 
         if (caps.advancedBlendEquationSupport() &&
                 !caps.isAdvancedBlendEquationBlacklisted(blendEquation)) {
diff --git a/tests/AnimatedImageTest.cpp b/tests/AnimatedImageTest.cpp
index 5f5dadb..93292d1 100644
--- a/tests/AnimatedImageTest.cpp
+++ b/tests/AnimatedImageTest.cpp
@@ -22,7 +22,7 @@
 #include "SkTypes.h"
 #include "SkUnPreMultiply.h"
 #include "Test.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 #include <algorithm>
 #include <memory>
@@ -47,10 +47,10 @@
     }
 
     // Force the drawable follow its special case that requires scaling.
-    auto size = codec->getInfo().dimensions();
-    size.set(size.width() - 5, size.height() - 5);
-    auto rect = SkIRect::MakeSize(size);
-    auto image = SkAnimatedImage::Make(std::move(codec), size, rect, nullptr);
+    auto info = codec->getInfo();
+    info = info.makeWH(info.width() - 5, info.height() - 5);
+    auto rect = info.bounds();
+    auto image = SkAnimatedImage::Make(std::move(codec), info, rect, nullptr);
     if (!image) {
         ERRORF(r, "Failed to create animated image for %s", file);
         return;
@@ -59,12 +59,12 @@
     // Clear a bitmap to non-transparent and draw to it. pixels that are transparent
     // in the image should not replace the original non-transparent color.
     SkBitmap bm;
-    bm.allocPixels(SkImageInfo::MakeN32Premul(size.width(), size.height()));
+    bm.allocPixels(SkImageInfo::MakeN32Premul(info.width(), info.height()));
     bm.eraseColor(SK_ColorBLUE);
     SkCanvas canvas(bm);
     image->draw(&canvas);
-    for (int i = 0; i < size.width();  ++i)
-    for (int j = 0; j < size.height(); ++j) {
+    for (int i = 0; i < info.width();  ++i)
+    for (int j = 0; j < info.height(); ++j) {
         if (*bm.getAddr32(i, j) == SK_ColorTRANSPARENT) {
             ERRORF(r, "Erased color underneath!");
             return;
@@ -207,7 +207,7 @@
                 bm.eraseColor(0);
             } else {
                 const SkBitmap& priorFrame = frames[options.fPriorFrame];
-                if (!sk_tool_utils::copy_to(&bm, priorFrame.colorType(), priorFrame)) {
+                if (!ToolUtils::copy_to(&bm, priorFrame.colorType(), priorFrame)) {
                     ERRORF(r, "Failed to copy %s frame %i", file, options.fPriorFrame);
                     options.fPriorFrame = SkCodec::kNoFrame;
                 }
diff --git a/tests/AnnotationTest.cpp b/tests/AnnotationTest.cpp
index 70c9703..aec85cd 100644
--- a/tests/AnnotationTest.cpp
+++ b/tests/AnnotationTest.cpp
@@ -93,16 +93,14 @@
 
     DEF_TEST(Annotation_SvgLink, reporter) {
         SkDynamicMemoryWStream outStream;
-        std::unique_ptr<SkXMLWriter> xmlWriter(new SkXMLStreamWriter(&outStream));
         SkRect bounds = SkRect::MakeIWH(400, 400);
-        std::unique_ptr<SkCanvas> canvas = SkSVGCanvas::Make(bounds, xmlWriter.get());
+        std::unique_ptr<SkCanvas> canvas = SkSVGCanvas::Make(bounds, &outStream);
 
         SkRect r = SkRect::MakeXYWH(SkIntToScalar(72), SkIntToScalar(72), SkIntToScalar(288),
                                     SkIntToScalar(72));
         sk_sp<SkData> data(SkData::MakeWithCString("http://www.gooogle.com"));
         SkAnnotateRectWithURL(canvas.get(), r, data.get());
 
-        canvas->flush();
         sk_sp<SkData> out = outStream.detachAsData();
         const char* rawOutput = (const char*)out->data();
 
@@ -112,16 +110,14 @@
 
     DEF_TEST(Annotation_SvgLinkNamedDestination, reporter) {
         SkDynamicMemoryWStream outStream;
-        std::unique_ptr<SkXMLWriter> xmlWriter(new SkXMLStreamWriter(&outStream));
         SkRect bounds = SkRect::MakeIWH(400, 400);
-        std::unique_ptr<SkCanvas> canvas = SkSVGCanvas::Make(bounds, xmlWriter.get());
+        std::unique_ptr<SkCanvas> canvas = SkSVGCanvas::Make(bounds, &outStream);
 
         SkRect r = SkRect::MakeXYWH(SkIntToScalar(72), SkIntToScalar(72), SkIntToScalar(288),
                                     SkIntToScalar(72));
         sk_sp<SkData> data(SkData::MakeWithCString("http://www.gooogle.com/#NamedDestination"));
         SkAnnotateLinkToDestination(canvas.get(), r, data.get());
 
-        canvas->flush();
         sk_sp<SkData> out = outStream.detachAsData();
         const char* rawOutput = (const char*)out->data();
 
diff --git a/tests/ApplyGammaTest.cpp b/tests/ApplyGammaTest.cpp
index 2b64722..62d843a 100644
--- a/tests/ApplyGammaTest.cpp
+++ b/tests/ApplyGammaTest.cpp
@@ -132,15 +132,15 @@
         SkCanvas* dstCanvas = dst->getCanvas();
 
         dstCanvas->clear(SK_ColorRED);
-        dstCanvas->flush();
+        dst->flush();
 
         SkPaint gammaPaint;
         gammaPaint.setBlendMode(SkBlendMode::kSrc);
-        gammaPaint.setColorFilter(toSRGB ? SkColorFilter::MakeLinearToSRGBGamma()
-                                         : SkColorFilter::MakeSRGBToLinearGamma());
+        gammaPaint.setColorFilter(toSRGB ? SkColorFilters::LinearToSRGBGamma()
+                                         : SkColorFilters::SRGBToLinearGamma());
 
         dstCanvas->drawBitmap(bm, 0, 0, &gammaPaint);
-        dstCanvas->flush();
+        dst->flush();
 
         sk_memset32(read.get(), 0, kW * kH);
         if (!dst->readPixels(ii, read.get(), kRowBytes, 0, 0)) {
diff --git a/tests/BitmapCopyTest.cpp b/tests/BitmapCopyTest.cpp
index c2a8f25..5b02491 100644
--- a/tests/BitmapCopyTest.cpp
+++ b/tests/BitmapCopyTest.cpp
@@ -15,7 +15,7 @@
 #include "SkSize.h"
 #include "SkTypes.h"
 #include "Test.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 static void init_src(const SkBitmap& bitmap) {
     if (bitmap.getPixels()) {
@@ -102,7 +102,7 @@
             // Test copying an extracted subset.
             for (size_t j = 0; j < SK_ARRAY_COUNT(gPairs); j++) {
                 SkBitmap copy;
-                bool success = sk_tool_utils::copy_to(&copy, gPairs[j].fColorType, subset);
+                bool     success = ToolUtils::copy_to(&copy, gPairs[j].fColorType, subset);
                 if (!success) {
                     // Skip checking that success matches fValid, which is redundant
                     // with the code below.
diff --git a/tests/BitmapTest.cpp b/tests/BitmapTest.cpp
index d1462f8..be5b972 100644
--- a/tests/BitmapTest.cpp
+++ b/tests/BitmapTest.cpp
@@ -15,7 +15,7 @@
 #include "SkRefCnt.h"
 #include "SkTypes.h"
 #include "Test.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 static void test_peekpixels(skiatest::Reporter* reporter) {
     const SkImageInfo info = SkImageInfo::MakeN32Premul(10, 10);
@@ -143,7 +143,7 @@
     };
     for (SkColorType ct : colorTypes) {
         SkBitmap copy;
-        if (!sk_tool_utils::copy_to(&copy, ct, source)) {
+        if (!ToolUtils::copy_to(&copy, ct, source)) {
             ERRORF(r, "SkBitmap::copy failed %d", (int)ct);
             continue;
         }
diff --git a/tests/BlendTest.cpp b/tests/BlendTest.cpp
index 0dd5506..8e52c25 100644
--- a/tests/BlendTest.cpp
+++ b/tests/BlendTest.cpp
@@ -99,7 +99,8 @@
 
     auto resourceProvider = context->priv().resourceProvider();
 
-    *backingSurface = resourceProvider->createTexture(backingDesc, SkBudgeted::kNo);
+    *backingSurface = resourceProvider->createTexture(backingDesc, SkBudgeted::kNo,
+                                                      GrResourceProvider::Flags::kNoPendingIO);
     if (!(*backingSurface)) {
         return nullptr;
     }
diff --git a/tests/BlurTest.cpp b/tests/BlurTest.cpp
index 36723f6..40a911a 100644
--- a/tests/BlurTest.cpp
+++ b/tests/BlurTest.cpp
@@ -38,7 +38,7 @@
 #include "SkSurface.h"
 #include "SkTypes.h"
 #include "Test.h"
-#include "sk_pixel_iter.h"
+#include "ToolUtils.h"
 
 #include "GrContextFactory.h"
 
@@ -686,7 +686,7 @@
         paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, sigma));
         surf->getCanvas()->drawRect(r, paint);
 
-        sk_tool_utils::PixelIter iter(surf.get());
+        ToolUtils::PixelIter iter(surf.get());
         SkIPoint  loc;
         while (const SkPMColor* p = (const SkPMColor*)iter.next(&loc)) {
             if (ir.contains(loc.fX, loc.fY)) {
diff --git a/tests/CachedDecodingPixelRefTest.cpp b/tests/CachedDecodingPixelRefTest.cpp
index 95f9d8d..a09fe62 100644
--- a/tests/CachedDecodingPixelRefTest.cpp
+++ b/tests/CachedDecodingPixelRefTest.cpp
@@ -17,7 +17,7 @@
 #include "SkTypes.h"
 #include "SkUtils.h"
 #include "Test.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 #include <utility>
 
@@ -31,7 +31,7 @@
     static int Width() { return 10; }
     static int Height() { return 10; }
     // value choosen so that there is no loss when converting to to RGB565 and back
-    static SkColor Color() { return sk_tool_utils::color_to_565(0xffaabbcc); }
+    static SkColor   Color() { return ToolUtils::color_to_565(0xffaabbcc); }
     static SkPMColor PMColor() { return SkPreMultiplyColor(Color()); }
 
     TestImageGenerator(TestType type, skiatest::Reporter* reporter,
diff --git a/tests/CanvasStateTest.cpp b/tests/CanvasStateTest.cpp
index 5b3b3f3..f4ed5a4 100644
--- a/tests/CanvasStateTest.cpp
+++ b/tests/CanvasStateTest.cpp
@@ -6,12 +6,12 @@
  */
 
 #include "CanvasStateHelpers.h"
+#include "CommandLineFlags.h"
 #include "SkBitmap.h"
 #include "SkCanvasPriv.h"
 #include "SkCanvasStateUtils.h"
 #include "SkClipOpPriv.h"
 #include "SkColor.h"
-#include "SkCommandLineFlags.h"
 #include "SkImageInfo.h"
 #include "SkPaint.h"
 #include "SkRRect.h"
@@ -32,12 +32,13 @@
 #ifdef SK_SUPPORT_LEGACY_CLIPTOLAYERFLAG
 #include <dlfcn.h>
 
-DEFINE_string(library, "", "Support library to use for CanvasState test. If empty (the default), "
-                           "the test will be run without crossing a library boundary. Otherwise, "
-                           "it is expected to be a full path to a shared library file, which will"
-                           " be dynamically loaded. Functions from the library will be called to "
-                           "test SkCanvasState. Instructions for generating the library are in "
-                           "gyp/canvas_state_lib.gyp");
+static DEFINE_string(library, "",
+                     "Support library to use for CanvasState test. If empty (the default), "
+                     "the test will be run without crossing a library boundary. Otherwise, "
+                     "it is expected to be a full path to a shared library file, which will"
+                     " be dynamically loaded. Functions from the library will be called to "
+                     "test SkCanvasState. Instructions for generating the library are in "
+                     "gyp/canvas_state_lib.gyp");
 
 
 // This class calls dlopen on the library passed in to the command line flag library, and handles
diff --git a/tests/CanvasTest.cpp b/tests/CanvasTest.cpp
index 9bd9fcb..8bd0092 100644
--- a/tests/CanvasTest.cpp
+++ b/tests/CanvasTest.cpp
@@ -88,7 +88,6 @@
 #include <memory>
 #include <utility>
 
-class SkColorSpaceXformer;
 class SkReadBuffer;
 template <typename T> class SkTCopyOnFirstWrite;
 
@@ -477,8 +476,7 @@
     pts[2].set(SkIntToScalar(d.fWidth), SkIntToScalar(d.fHeight));
     pts[3].set(0, SkIntToScalar(d.fHeight));
     SkPaint paint;
-    paint.setShader(SkShader::MakeBitmapShader(d.fBitmap, SkShader::kClamp_TileMode,
-                                               SkShader::kClamp_TileMode));
+    paint.setShader(d.fBitmap.makeShader());
     canvas->drawVertices(SkVertices::MakeCopy(SkVertices::kTriangleFan_VertexMode, 4, pts, pts,
                                               nullptr),
                          SkBlendMode::kModulate, paint);
@@ -841,7 +839,6 @@
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage*, const Context&, SkIPoint*) const override {
         return nullptr;
     }
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override { return nullptr; }
     SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix&,
                                MapDirection, const SkIRect* inputRect) const override {
         return SkIRect::MakeEmpty();
diff --git a/tests/CodecAnimTest.cpp b/tests/CodecAnimTest.cpp
index d3f385e..ec4ca18 100644
--- a/tests/CodecAnimTest.cpp
+++ b/tests/CodecAnimTest.cpp
@@ -20,7 +20,7 @@
 #include "SkString.h"
 #include "SkTypes.h"
 #include "Test.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 #include <cstring>
 #include <memory>
@@ -317,8 +317,8 @@
                 bm->allocPixels(decodeInfo);
                 if (cachedIndex != SkCodec::kNoFrame) {
                     // First copy the pixels from the cached frame
-                    const bool success = sk_tool_utils::copy_to(bm, kN32_SkColorType,
-                            cachedFrames[cachedIndex]);
+                    const bool success =
+                            ToolUtils::copy_to(bm, kN32_SkColorType, cachedFrames[cachedIndex]);
                     REPORTER_ASSERT(r, success);
                 }
                 SkCodec::Options opts;
diff --git a/tests/CodecPriv.h b/tests/CodecPriv.h
index 88de79c..d4457b9 100644
--- a/tests/CodecPriv.h
+++ b/tests/CodecPriv.h
@@ -7,15 +7,18 @@
 #ifndef CodecPriv_DEFINED
 #define CodecPriv_DEFINED
 
+#include "CommandLineFlags.h"
 #include "SkBitmap.h"
 #include "SkCodec.h"
-#include "SkCommonFlags.h"
 #include "SkData.h"
 #include "SkEncodedImageFormat.h"
 #include "SkImageEncoder.h"
 #include "SkOSPath.h"
 #include "SkStream.h"
 
+static DEFINE_string(codecWritePath, "",
+                     "Dump image decodes from codec unit tests here.");
+
 inline bool decode_memory(const void* mem, size_t size, SkBitmap* bm) {
     std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(SkData::MakeWithoutCopy(mem, size)));
     if (!codec) {
@@ -29,11 +32,11 @@
 }
 
 inline void write_bm(const char* name, const SkBitmap& bm) {
-    if (FLAGS_writePath.isEmpty()) {
+    if (FLAGS_codecWritePath.isEmpty()) {
         return;
     }
 
-    SkString filename = SkOSPath::Join(FLAGS_writePath[0], name);
+    SkString filename = SkOSPath::Join(FLAGS_codecWritePath[0], name);
     filename.appendf(".png");
     SkFILEWStream file(filename.c_str());
     if (!SkEncodeImage(&file, bm, SkEncodedImageFormat::kPNG, 100)) {
diff --git a/tests/CodecTest.cpp b/tests/CodecTest.cpp
index 8c918a8..2be8b20 100644
--- a/tests/CodecTest.cpp
+++ b/tests/CodecTest.cpp
@@ -41,8 +41,8 @@
 #include "SkUnPreMultiply.h"
 #include "SkWebpEncoder.h"
 #include "Test.h"
+#include "ToolUtils.h"
 #include "png.h"
-#include "sk_tool_utils.h"
 
 #include <setjmp.h>
 #include <cstring>
@@ -57,14 +57,14 @@
     #define SK_PNG_DISABLE_TESTS
 #endif
 
-static void md5(const SkBitmap& bm, SkMD5::Digest* digest) {
+static SkMD5::Digest md5(const SkBitmap& bm) {
     SkASSERT(bm.getPixels());
     SkMD5 md5;
     size_t rowLen = bm.info().bytesPerPixel() * bm.width();
     for (int y = 0; y < bm.height(); ++y) {
         md5.write(bm.getAddr(0, y), rowLen);
     }
-    md5.finish(*digest);
+    return md5.finish();
 }
 
 /**
@@ -75,8 +75,7 @@
  */
 static void compare_to_good_digest(skiatest::Reporter* r, const SkMD5::Digest& goodDigest,
                            const SkBitmap& bm) {
-    SkMD5::Digest digest;
-    md5(bm, &digest);
+    SkMD5::Digest digest = md5(bm);
     REPORTER_ASSERT(r, digest == goodDigest);
 }
 
@@ -178,7 +177,7 @@
     SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
     REPORTER_ASSERT(r, result == expectedResult);
 
-    md5(bm, digest);
+    *digest = md5(bm);
     if (goodDigest) {
         REPORTER_ASSERT(r, *digest == *goodDigest);
     }
@@ -196,8 +195,7 @@
 
             auto actualResult = codec->getPixels(info565, bm565.getPixels(), bm565.rowBytes());
             if (actualResult == expectedResult) {
-                SkMD5::Digest digest565;
-                md5(bm565, &digest565);
+                SkMD5::Digest digest565 = md5(bm565);
 
                 // A request for non-opaque should also succeed.
                 for (auto alpha : { kPremul_SkAlphaType, kUnpremul_SkAlphaType }) {
@@ -225,8 +223,7 @@
         REPORTER_ASSERT(r, expectedResult == codec->getPixels(grayInfo,
                 grayBm.getPixels(), grayBm.rowBytes()));
 
-        SkMD5::Digest grayDigest;
-        md5(grayBm, &grayDigest);
+        SkMD5::Digest grayDigest = md5(grayBm);
 
         for (auto alpha : { kPremul_SkAlphaType, kUnpremul_SkAlphaType }) {
             grayInfo = grayInfo.makeAlphaType(alpha);
@@ -702,8 +699,7 @@
     bm.setInfo(bmInfo);
     bm.allocPixels();
     bm.eraseColor(SK_ColorBLUE);
-    SkMD5::Digest goodDigest;
-    md5(bm, &goodDigest);
+    SkMD5::Digest goodDigest = md5(bm);
 
     // Write to a png file.
     png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
@@ -818,7 +814,7 @@
 
     if (decodedBm.colorType() != bm.colorType()) {
         SkBitmap tmp;
-        bool success = sk_tool_utils::copy_to(&tmp, bm.colorType(), decodedBm);
+        bool     success = ToolUtils::copy_to(&tmp, bm.colorType(), decodedBm);
         REPORTER_ASSERT(r, success);
         if (!success) {
             return;
@@ -1080,10 +1076,7 @@
     result = codec->getPixels(info, bm2.getPixels(), bm2.rowBytes());
     REPORTER_ASSERT(r, SkCodec::kSuccess == result);
 
-    SkMD5::Digest d1, d2;
-    md5(bm1, &d1);
-    md5(bm2, &d2);
-    REPORTER_ASSERT(r, d1 == d2);
+    REPORTER_ASSERT(r, md5(bm1) == md5(bm2));
 }
 
 DEF_TEST(Codec_PngRoundTrip, r) {
diff --git a/tests/ColorFilterTest.cpp b/tests/ColorFilterTest.cpp
index 669ced2..2b05f94 100644
--- a/tests/ColorFilterTest.cpp
+++ b/tests/ColorFilterTest.cpp
@@ -35,7 +35,7 @@
 
 static sk_sp<SkColorFilter> make_filter() {
     // pick a filter that cannot compose with itself via newComposed()
-    return SkColorFilter::MakeModeFilter(SK_ColorRED, SkBlendMode::kColorBurn);
+    return SkColorFilters::Blend(SK_ColorRED, SkBlendMode::kColorBurn);
 }
 
 static void test_composecolorfilter_limit(skiatest::Reporter* reporter) {
@@ -65,7 +65,7 @@
         // special case that would return nullptr (if color's alpha is 0 or 0xFF)
         color = SkColorSetA(color, 0x7F);
 
-        auto cf = SkColorFilter::MakeModeFilter(color, (SkBlendMode)mode);
+        auto cf = SkColorFilters::Blend(color, (SkBlendMode)mode);
 
         // allow for no filter if we're in Dst mode (its a no op)
         if (SkBlendMode::kDst == (SkBlendMode)mode && nullptr == cf) {
diff --git a/tests/ColorMatrixTest.cpp b/tests/ColorMatrixTest.cpp
index 8c25669..371c332 100644
--- a/tests/ColorMatrixTest.cpp
+++ b/tests/ColorMatrixTest.cpp
@@ -47,7 +47,7 @@
             0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
             0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
             0.0f, 0.0f, 0.0f, 1.0f, 0.0f };
-    paint.setColorFilter(SkColorFilter::MakeMatrixFilterRowMajor255(blueToCyan));
+    paint.setColorFilter(SkColorFilters::MatrixRowMajor255(blueToCyan));
 
     paint.setColor(SK_ColorBLUE);
     canvas.drawPoint(0, 0, paint);
@@ -72,7 +72,7 @@
             0.0f, 0.0f, 1.0f, 0.0f, 64.0f,
            -0.5f, 0.0f, 0.0f, 1.0f, 0.0f
     };
-    paint.setColorFilter(SkColorFilter::MakeMatrixFilterRowMajor255(transparentRedAddBlue));
+    paint.setColorFilter(SkColorFilters::MatrixRowMajor255(transparentRedAddBlue));
     bitmap.eraseColor(SK_ColorTRANSPARENT);
 
     paint.setColor(SK_ColorRED);
@@ -93,7 +93,7 @@
     assert_color(reporter, SK_ColorCYAN, bitmap.getColor(0, 0));
 
     // create a new filter with the changed matrix
-    paint.setColorFilter(SkColorFilter::MakeMatrixFilterRowMajor255(transparentRedAddBlue));
+    paint.setColorFilter(SkColorFilters::MatrixRowMajor255(transparentRedAddBlue));
     canvas.drawPoint(0, 0, paint);
     assert_color(reporter, SK_ColorBLUE, bitmap.getColor(0, 0));
 }
@@ -113,7 +113,7 @@
         0, 0, 1, 0, 0,
         0, 0, 0, 1, 32,
     };
-    auto filter = SkColorFilter::MakeMatrixFilterRowMajor255(m);
+    auto filter = SkColorFilters::MatrixRowMajor255(m);
 
     SkColor filtered = filter->filterColor(0xff0a0b0c);
     REPORTER_ASSERT(r, SkColorGetA(filtered) == 0xff);
diff --git a/tests/CubicMapTest.cpp b/tests/CubicMapTest.cpp
index ff4aae4..db44dc0 100644
--- a/tests/CubicMapTest.cpp
+++ b/tests/CubicMapTest.cpp
@@ -43,7 +43,7 @@
     for (SkScalar x = dx; x < 1; x += dx) {
         SkScalar y = cmap.computeYFromX(x);
         // are we valid and (mostly) monotonic?
-        if (y < 0 || y > 1 || !nearly_le(prev_y, y)) {
+        if (!nearly_le(prev_y, y)) {
             cmap.computeYFromX(x);
             REPORTER_ASSERT(reporter, false);
         }
diff --git a/tests/DefaultPathRendererTest.cpp b/tests/DefaultPathRendererTest.cpp
index 9b61663..7475729 100644
--- a/tests/DefaultPathRendererTest.cpp
+++ b/tests/DefaultPathRendererTest.cpp
@@ -28,7 +28,7 @@
 #include "SkRefCnt.h"
 #include "SkStrokeRec.h"
 #include "Test.h"
-#include "effects/GrConstColorProcessor.h"
+#include "effects/generated/GrConstColorProcessor.h"
 
 #include <utility>
 
@@ -99,7 +99,8 @@
         rtc->drawPath(GrNoClip(), std::move(paint), GrAA::kNo,
                       SkMatrix::I(), invPath, style);
 
-        rtc->prepareForExternalIO(0, nullptr);
+        rtc->prepareForExternalIO(SkSurface::BackendSurfaceAccess::kNoAccess,
+                                  kNone_GrFlushFlags, 0, nullptr);
     }
 
     {
diff --git a/tests/DeferredDisplayListTest.cpp b/tests/DeferredDisplayListTest.cpp
index 9347748..29cffb0 100644
--- a/tests/DeferredDisplayListTest.cpp
+++ b/tests/DeferredDisplayListTest.cpp
@@ -46,209 +46,6 @@
 #include <vulkan/vulkan_core.h>
 #endif
 
-// Try to create a backend format from the provided colorType and config. Return an invalid
-// backend format if the combination is infeasible.
-static GrBackendFormat create_backend_format(GrContext* context,
-                                             SkColorType ct,
-                                             GrPixelConfig config) {
-    const GrCaps* caps = context->priv().caps();
-
-    switch (context->backend()) {
-    case GrBackendApi::kOpenGL: {
-        const GrGLCaps* glCaps = static_cast<const GrGLCaps*>(caps);
-        GrGLStandard standard = glCaps->standard();
-
-        switch (ct) {
-            case kUnknown_SkColorType:
-                return GrBackendFormat();
-            case kAlpha_8_SkColorType:
-                if (kAlpha_8_as_Alpha_GrPixelConfig == config) {
-                    return GrBackendFormat::MakeGL(GR_GL_ALPHA8, GR_GL_TEXTURE_2D);
-                } else if (kAlpha_8_GrPixelConfig == config ||
-                           kAlpha_8_as_Red_GrPixelConfig == config) {
-                    return GrBackendFormat::MakeGL(GR_GL_R8, GR_GL_TEXTURE_2D);
-                }
-                break;
-            case kRGB_565_SkColorType:
-                if (kRGB_565_GrPixelConfig == config) {
-                    return GrBackendFormat::MakeGL(GR_GL_RGB565, GR_GL_TEXTURE_2D);
-                }
-                break;
-            case kARGB_4444_SkColorType:
-                if (kRGBA_4444_GrPixelConfig == config) {
-                    return GrBackendFormat::MakeGL(GR_GL_RGBA4, GR_GL_TEXTURE_2D);
-                }
-                break;
-            case kRGBA_8888_SkColorType:
-                if (kRGBA_8888_GrPixelConfig == config) {
-                    return GrBackendFormat::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_2D);
-                }
-                break;
-            case kRGB_888x_SkColorType:
-                if (kRGB_888_GrPixelConfig == config) {
-                    return GrBackendFormat::MakeGL(GR_GL_RGB8, GR_GL_TEXTURE_2D);
-                }
-                break;
-            case kBGRA_8888_SkColorType:
-                if (kBGRA_8888_GrPixelConfig == config) {
-                    if (kGL_GrGLStandard == standard) {
-                        return GrBackendFormat::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_2D);
-                    } else if (kGLES_GrGLStandard == standard) {
-                        return GrBackendFormat::MakeGL(GR_GL_BGRA8, GR_GL_TEXTURE_2D);
-                    }
-                }
-                break;
-            case kRGBA_1010102_SkColorType:
-                if (kRGBA_1010102_GrPixelConfig == config) {
-                    return GrBackendFormat::MakeGL(GR_GL_RGB10_A2, GR_GL_TEXTURE_2D);
-                }
-                break;
-            case kRGB_101010x_SkColorType:
-                return GrBackendFormat();
-            case kGray_8_SkColorType:
-                if (kGray_8_as_Lum_GrPixelConfig == config) {
-                    return GrBackendFormat::MakeGL(GR_GL_LUMINANCE8, GR_GL_TEXTURE_2D);
-                } else if (kGray_8_GrPixelConfig == config ||
-                           kGray_8_as_Red_GrPixelConfig == config) {
-                    return GrBackendFormat::MakeGL(GR_GL_R8, GR_GL_TEXTURE_2D);
-                }
-                break;
-            case kRGBA_F16_SkColorType:
-                if (kRGBA_half_GrPixelConfig == config) {
-                    return GrBackendFormat::MakeGL(GR_GL_RGBA16F, GR_GL_TEXTURE_2D);
-                }
-                break;
-            case kRGBA_F32_SkColorType:
-                return GrBackendFormat();
-        }
-    }
-    break;
-#ifdef SK_VULKAN
-    case GrBackendApi::kVulkan:
-        switch (ct) {
-            case kUnknown_SkColorType:
-                return GrBackendFormat();
-            case kAlpha_8_SkColorType:
-                // TODO: what about kAlpha_8_GrPixelConfig and kAlpha_8_as_Alpha_GrPixelConfig
-                if (kAlpha_8_as_Red_GrPixelConfig == config) {
-                    return  GrBackendFormat::MakeVk(VK_FORMAT_R8_UNORM);
-                }
-                break;
-            case kRGB_565_SkColorType:
-                if (kRGB_565_GrPixelConfig == config) {
-                    return  GrBackendFormat::MakeVk(VK_FORMAT_R5G6B5_UNORM_PACK16);
-                }
-                break;
-            case kARGB_4444_SkColorType:
-                if (kRGBA_4444_GrPixelConfig == config) {
-                    return  GrBackendFormat::MakeVk(VK_FORMAT_B4G4R4A4_UNORM_PACK16);
-                }
-                break;
-            case kRGBA_8888_SkColorType:
-                if (kRGBA_8888_GrPixelConfig == config) {
-                    return GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8A8_UNORM);
-                }
-                break;
-            case kRGB_888x_SkColorType:
-                if (kRGB_888_GrPixelConfig == config) {
-                    return GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8_UNORM);
-                }
-                break;
-            case kBGRA_8888_SkColorType:
-                if (kBGRA_8888_GrPixelConfig == config) {
-                    return GrBackendFormat::MakeVk(VK_FORMAT_B8G8R8A8_UNORM);
-                }
-                break;
-            case kRGBA_1010102_SkColorType:
-                if (kRGBA_1010102_GrPixelConfig == config) {
-                    return  GrBackendFormat::MakeVk(VK_FORMAT_A2B10G10R10_UNORM_PACK32);
-                }
-                break;
-            case kRGB_101010x_SkColorType:
-                return GrBackendFormat();
-            case kGray_8_SkColorType:
-                // TODO: what about kAlpha_8_GrPixelConfig and kGray_8_as_Lum_GrPixelConfig?
-                if (kGray_8_as_Red_GrPixelConfig == config) {
-                    return  GrBackendFormat::MakeVk(VK_FORMAT_R8_UNORM);
-                }
-                break;
-            case kRGBA_F16_SkColorType:
-                if (kRGBA_half_GrPixelConfig == config) {
-                    return  GrBackendFormat::MakeVk(VK_FORMAT_R16G16B16A16_SFLOAT);
-                }
-                break;
-            case kRGBA_F32_SkColorType:
-                return GrBackendFormat();
-        }
-        break;
-#endif
-    case GrBackendApi::kMock:
-        switch (ct) {
-            case kUnknown_SkColorType:
-                return GrBackendFormat();
-            case kAlpha_8_SkColorType:
-                if (kAlpha_8_GrPixelConfig == config ||
-                    kAlpha_8_as_Alpha_GrPixelConfig == config ||
-                    kAlpha_8_as_Red_GrPixelConfig == config) {
-                    return  GrBackendFormat::MakeMock(config);
-                }
-                break;
-            case kRGB_565_SkColorType:
-                if (kRGB_565_GrPixelConfig == config) {
-                    return  GrBackendFormat::MakeMock(config);
-                }
-                break;
-            case kARGB_4444_SkColorType:
-                if (kRGBA_4444_GrPixelConfig == config) {
-                    return  GrBackendFormat::MakeMock(config);
-                }
-                break;
-            case kRGBA_8888_SkColorType:
-                if (kRGBA_8888_GrPixelConfig == config) {
-                    return GrBackendFormat::MakeMock(config);
-                }
-                break;
-            case kRGB_888x_SkColorType:
-                if (kRGB_888_GrPixelConfig == config) {
-                    return GrBackendFormat::MakeMock(config);
-                }
-                break;
-            case kBGRA_8888_SkColorType:
-                if (kBGRA_8888_GrPixelConfig == config) {
-                    return GrBackendFormat::MakeMock(config);
-                }
-                break;
-            case kRGBA_1010102_SkColorType:
-                if (kRGBA_1010102_GrPixelConfig == config) {
-                    return  GrBackendFormat::MakeMock(config);
-                }
-                break;
-            case kRGB_101010x_SkColorType:
-                return GrBackendFormat();
-            case kGray_8_SkColorType:
-                if (kGray_8_GrPixelConfig == config ||
-                    kGray_8_as_Lum_GrPixelConfig == config ||
-                    kGray_8_as_Red_GrPixelConfig == config) {
-                    return  GrBackendFormat::MakeMock(config);
-                }
-                break;
-            case kRGBA_F16_SkColorType:
-                if (kRGBA_half_GrPixelConfig == config) {
-                    return  GrBackendFormat::MakeMock(config);
-                }
-                break;
-            case kRGBA_F32_SkColorType:
-                return GrBackendFormat();
-        }
-        break;
-    default:
-        return GrBackendFormat(); // return an invalid format
-    }
-
-    return GrBackendFormat(); // return an invalid format
-}
-
-
 class SurfaceParameters {
 public:
     static const int kNumParams   = 11;
@@ -262,7 +59,6 @@
             , fHeight(64)
             , fOrigin(kTopLeft_GrSurfaceOrigin)
             , fColorType(kRGBA_8888_SkColorType)
-            , fConfig(kRGBA_8888_GrPixelConfig)
             , fColorSpace(SkColorSpace::MakeSRGB())
             , fSampleCount(1)
             , fSurfaceProps(0x0, kUnknown_SkPixelGeometry)
@@ -275,7 +71,6 @@
 
     void setColorType(SkColorType ct) { fColorType = ct; }
     void setColorSpace(sk_sp<SkColorSpace> cs) { fColorSpace = std::move(cs); }
-    void setConfig(GrPixelConfig config) { fConfig = config; }
     void setTextureable(bool isTextureable) { fIsTextureable = isTextureable; }
 
     // Modify the SurfaceParameters in just one way
@@ -291,9 +86,7 @@
             fOrigin = kBottomLeft_GrSurfaceOrigin;
             break;
         case 3:
-            // The color type and config need to be changed together.
             fColorType = kRGBA_F16_SkColorType;
-            fConfig = kRGBA_half_GrPixelConfig;
             break;
         case 4:
             // This just needs to be a colorSpace different from that returned by MakeSRGB().
@@ -334,7 +127,8 @@
         SkImageInfo ii = SkImageInfo::Make(fWidth, fHeight, fColorType,
                                            kPremul_SkAlphaType, fColorSpace);
 
-        GrBackendFormat backendFormat = create_backend_format(context, fColorType, fConfig);
+        const GrCaps* caps = context->priv().caps();
+        GrBackendFormat backendFormat = caps->getBackendFormatFromColorType(fColorType);
         if (!backendFormat.isValid()) {
             return SkSurfaceCharacterization();
         }
@@ -379,7 +173,6 @@
             fboInfo.fFormat = GR_GL_RGBA8;
             static constexpr int kStencilBits = 8;
             GrBackendRenderTarget backendRT(fWidth, fHeight, 1, kStencilBits, fboInfo);
-            backendRT.setPixelConfig(fConfig);
 
             if (!backendRT.isValid()) {
                 return nullptr;
@@ -431,7 +224,6 @@
     int                 fHeight;
     GrSurfaceOrigin     fOrigin;
     SkColorType         fColorType;
-    GrPixelConfig       fConfig;
     sk_sp<SkColorSpace> fColorSpace;
     int                 fSampleCount;
     SkSurfaceProps      fSurfaceProps;
@@ -705,7 +497,6 @@
         GrBackendTexture backend;
         sk_sp<SkSurface> s = params.make(context, &backend);
         if (!s) {
-            params.cleanUpBackEnd(context, backend);
             continue;
         }
 
@@ -873,8 +664,9 @@
         REPORTER_ASSERT(reporter, !recorder.getCanvas());
         REPORTER_ASSERT(reporter, !recorder.detach());
 
-        GrBackendFormat format = create_backend_format(context, kRGBA_8888_SkColorType,
-                                                       kRGBA_8888_GrPixelConfig);
+        const GrCaps* caps = context->priv().caps();
+        GrBackendFormat format = caps->getBackendFormatFromColorType(kRGBA_8888_SkColorType);
+
         sk_sp<SkImage> image = recorder.makePromiseTexture(
                 format, 32, 32, GrMipMapped::kNo,
                 kTopLeft_GrSurfaceOrigin,
@@ -884,7 +676,7 @@
                 dummy_release_proc,
                 dummy_done_proc,
                 nullptr,
-                SkDeferredDisplayListRecorder::DelayReleaseCallback::kNo);
+                SkDeferredDisplayListRecorder::PromiseImageApiVersion::kNew);
         REPORTER_ASSERT(reporter, !image);
     }
 
@@ -904,7 +696,6 @@
     SkDeferredDisplayListRecorder recorder(characterization);
     SkCanvas* canvas = recorder.getCanvas();
 
-    canvas->flush();
     canvas->getGrContext()->flush();
 }
 
@@ -990,7 +781,7 @@
                     dummy_release_proc,
                     dummy_done_proc,
                     nullptr,
-                    SkDeferredDisplayListRecorder::DelayReleaseCallback::kNo);
+                    SkDeferredDisplayListRecorder::PromiseImageApiVersion::kNew);
             if (GR_GL_TEXTURE_2D != target && mipMapped == GrMipMapped::kYes) {
                 REPORTER_ASSERT(reporter, !image);
                 continue;
@@ -1018,57 +809,47 @@
     for (int ct = 0; ct <= kLastEnum_SkColorType; ++ct) {
         SkColorType colorType = static_cast<SkColorType>(ct);
 
-        for (int config = 0; config < kPrivateConfig1_GrPixelConfig; ++config) {
-            GrPixelConfig pixelConfig = static_cast<GrPixelConfig>(config);
+        SurfaceParameters params(context->backend());
+        params.setColorType(colorType);
+        params.setColorSpace(nullptr);
 
-            SurfaceParameters params(context->backend());
-            params.setColorType(colorType);
-            params.setConfig(pixelConfig);
-            params.setColorSpace(nullptr);
+        SkSurfaceCharacterization c = params.createCharacterization(context);
+        GrBackendTexture backend;
 
-            SkSurfaceCharacterization c = params.createCharacterization(context);
-            GrBackendTexture backend;
+        if (!c.isValid()) {
+            sk_sp<SkSurface> tmp = params.make(context, &backend);
 
-            if (!c.isValid()) {
-                // TODO: this would be cool to enable but there is, currently, too much crossover
-                // allowed internally (e.g., kAlpha_8_SkColorType/kGray_8_as_Red_GrPixelConfig
-                // is permitted on GL).
-#if 0
-                sk_sp<SkSurface> tmp = params.make(context, &backend, false);
-
-                // If we couldn't characterize the surface we shouldn't be able to create it either
-                REPORTER_ASSERT(reporter, !tmp);
-                if (tmp) {
-                    tmp = nullptr;
-                    params.cleanUpBackEnd(context, backend);
-                }
-#endif
-                continue;
-            }
-
-            sk_sp<SkSurface> s = params.make(context, &backend);
-            REPORTER_ASSERT(reporter, s);
-            if (!s) {
-                s = nullptr;
+            // If we couldn't characterize the surface we shouldn't be able to create it either
+            REPORTER_ASSERT(reporter, !tmp);
+            if (tmp) {
+                tmp = nullptr;
                 params.cleanUpBackEnd(context, backend);
-                continue;
             }
+            continue;
+        }
 
-            SkSurface_Gpu* gpuSurface = static_cast<SkSurface_Gpu*>(s.get());
-            REPORTER_ASSERT(reporter, gpuSurface->isCompatible(c));
-
+        sk_sp<SkSurface> s = params.make(context, &backend);
+        REPORTER_ASSERT(reporter, s);
+        if (!s) {
             s = nullptr;
             params.cleanUpBackEnd(context, backend);
-
-            s = SkSurface::MakeRenderTarget(context, c, SkBudgeted::kYes);
-            REPORTER_ASSERT(reporter, s);
-            if (!s) {
-                continue;
-            }
-
-            gpuSurface = static_cast<SkSurface_Gpu*>(s.get());
-            REPORTER_ASSERT(reporter, gpuSurface->isCompatible(c));
+            continue;
         }
+
+        SkSurface_Gpu* gpuSurface = static_cast<SkSurface_Gpu*>(s.get());
+        REPORTER_ASSERT(reporter, gpuSurface->isCompatible(c));
+
+        s = nullptr;
+        params.cleanUpBackEnd(context, backend);
+
+        s = SkSurface::MakeRenderTarget(context, c, SkBudgeted::kYes);
+        REPORTER_ASSERT(reporter, s);
+        if (!s) {
+            continue;
+        }
+
+        gpuSurface = static_cast<SkSurface_Gpu*>(s.get());
+        REPORTER_ASSERT(reporter, gpuSurface->isCompatible(c));
     }
 
 }
diff --git a/tests/DescriptorTest.cpp b/tests/DescriptorTest.cpp
new file mode 100644
index 0000000..42f0727
--- /dev/null
+++ b/tests/DescriptorTest.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDescriptor.h"
+#include "Test.h"
+
+class SkDescriptorTestHelper {
+public:
+    static void SetLength(SkDescriptor* desc, size_t length) { desc->fLength = length; }
+};
+
+DEF_TEST(Descriptor_empty, r) {
+    const size_t size = sizeof(SkDescriptor);
+
+    auto desc = SkDescriptor::Alloc(size);
+    desc->init();
+    REPORTER_ASSERT(r, desc->isValid());
+    REPORTER_ASSERT(r, desc->getLength() == size);
+}
+
+DEF_TEST(Descriptor_valid_simple, r) {
+    const size_t size =
+            sizeof(SkDescriptor) + sizeof(SkDescriptor::Entry) + sizeof(SkScalerContextRec);
+
+    auto desc = SkDescriptor::Alloc(size);
+    desc->init();
+    SkScalerContextRec rec;
+    desc->addEntry(kRec_SkDescriptorTag, sizeof(rec), &rec);
+    REPORTER_ASSERT(r, desc->isValid());
+    REPORTER_ASSERT(r, desc->getLength() == size);
+
+    SkDescriptorTestHelper::SetLength(desc.get(), size - 4);
+    REPORTER_ASSERT(r, !desc->isValid());
+}
+
+DEF_TEST(Descriptor_valid_simple_extra_space, r) {
+    const size_t extra_space = 100;
+    const size_t size =
+            sizeof(SkDescriptor) + sizeof(SkDescriptor::Entry) + sizeof(SkScalerContextRec);
+
+    auto desc = SkDescriptor::Alloc(size + extra_space);
+    desc->init();
+    SkScalerContextRec rec;
+    desc->addEntry(kRec_SkDescriptorTag, sizeof(rec), &rec);
+    REPORTER_ASSERT(r, desc->isValid());
+    REPORTER_ASSERT(r, desc->getLength() == size);
+
+    SkDescriptorTestHelper::SetLength(desc.get(), size - 4);
+    REPORTER_ASSERT(r, !desc->isValid());
+}
+
+DEF_TEST(Descriptor_valid_more_tags, r) {
+    const size_t effectSize = 16;
+    const size_t testSize = 32;
+    const size_t size = sizeof(SkDescriptor) + 3 * sizeof(SkDescriptor::Entry) +
+                        sizeof(SkScalerContextRec) + effectSize + testSize;
+
+    auto desc = SkDescriptor::Alloc(size);
+    desc->init();
+    SkScalerContextRec rec;
+    desc->addEntry(kRec_SkDescriptorTag, sizeof(rec), &rec);
+    desc->addEntry(kEffects_SkDescriptorTag, effectSize, nullptr);
+    desc->addEntry(SkSetFourByteTag('t', 'e', 's', 't'), testSize, nullptr);
+    REPORTER_ASSERT(r, desc->isValid());
+    REPORTER_ASSERT(r, desc->getLength() == size);
+
+    SkDescriptorTestHelper::SetLength(desc.get(), size - 4);
+    REPORTER_ASSERT(r, !desc->isValid());
+}
+
+DEF_TEST(Descriptor_invalid_rec_size, r) {
+    const size_t size =
+            sizeof(SkDescriptor) + sizeof(SkDescriptor::Entry) + sizeof(SkScalerContextRec) - 4;
+
+    auto desc = SkDescriptor::Alloc(size);
+    desc->init();
+    SkScalerContextRec rec;
+    desc->addEntry(kRec_SkDescriptorTag, sizeof(rec) - 4, &rec);
+    REPORTER_ASSERT(r, desc->getLength() == size);
+    REPORTER_ASSERT(r, !desc->isValid());
+}
+
+DEF_TEST(Descriptor_invalid_length, r) {
+    const size_t size = sizeof(SkDescriptor) + sizeof(SkDescriptor::Entry);
+    const size_t effect_size = 1000;
+
+    auto desc = SkDescriptor::Alloc(size);
+    desc->init();
+    SkScalerContextRec rec;
+    desc->addEntry(kEffects_SkDescriptorTag, effect_size, nullptr);
+
+    SkDescriptorTestHelper::SetLength(desc.get(), size);
+    REPORTER_ASSERT(r, !desc->isValid());
+
+    SkDescriptorTestHelper::SetLength(desc.get(), size + effect_size);
+    REPORTER_ASSERT(r, desc->isValid());
+}
diff --git a/tests/DrawBitmapRectTest.cpp b/tests/DrawBitmapRectTest.cpp
index b8fed49..6d3c1be 100644
--- a/tests/DrawBitmapRectTest.cpp
+++ b/tests/DrawBitmapRectTest.cpp
@@ -139,8 +139,7 @@
                   SkIntToScalar(239),
                   0, 0, SK_Scalar1);
     SkPaint paint;
-    paint.setShader(SkShader::MakeBitmapShader(bm, SkShader::kRepeat_TileMode,
-                                               SkShader::kRepeat_TileMode, &matrix));
+    paint.setShader(bm.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &matrix));
 
     SkRect r = SkRect::MakeXYWH(681, 239, 695, 253);
     c.drawRect(r, paint);
diff --git a/tests/DrawOpAtlasTest.cpp b/tests/DrawOpAtlasTest.cpp
index d85e324..dc6cd2a 100644
--- a/tests/DrawOpAtlasTest.cpp
+++ b/tests/DrawOpAtlasTest.cpp
@@ -184,6 +184,7 @@
 
     auto gpu = context->priv().getGpu();
     auto resourceProvider = context->priv().resourceProvider();
+    auto resourceCache = context->priv().getResourceCache();
     auto drawingManager = context->priv().drawingManager();
     auto textContext = drawingManager->getTextContext();
     auto opMemoryPool = context->priv().opMemoryPool();
@@ -207,12 +208,12 @@
 
     std::unique_ptr<GrDrawOp> op = textContext->createOp_TestingOnly(
             context, textContext, rtc.get(), paint, font, SkMatrix::I(), text, 16, 16);
-    op->finalize(*context->priv().caps(), nullptr);
+    op->finalize(*context->priv().caps(), nullptr, GrFSAAType::kNone, GrClampType::kAuto);
 
     TestingUploadTarget uploadTarget;
 
-    GrOpFlushState flushState(gpu, resourceProvider, uploadTarget.writeableTokenTracker(), nullptr,
-                              nullptr);
+    GrOpFlushState flushState(gpu, resourceProvider, resourceCache,
+                              uploadTarget.writeableTokenTracker());
     GrOpFlushState::OpArgs opArgs = {
         op.get(),
         rtc->asRenderTargetProxy(),
diff --git a/tests/EncodeTest.cpp b/tests/EncodeTest.cpp
index 30b5302..367dc35 100644
--- a/tests/EncodeTest.cpp
+++ b/tests/EncodeTest.cpp
@@ -23,6 +23,11 @@
 #include <string>
 #include <vector>
 
+// FIXME: Update the Google3 build's dependencies so it can run this test.
+#ifndef SK_BUILD_FOR_GOOGLE3
+#include "webp/decode.h"
+#endif
+
 static bool encode(SkEncodedImageFormat format, SkWStream* dst, const SkPixmap& src) {
     switch (format) {
         case SkEncodedImageFormat::kJPEG:
@@ -286,6 +291,54 @@
     REPORTER_ASSERT(r, almost_equals(bm0, bm2, 0));
 }
 
+#ifndef SK_BUILD_FOR_GOOGLE3
+DEF_TEST(Encode_WebpQuality, r) {
+    SkBitmap bm;
+    bm.allocN32Pixels(100, 100);
+    bm.eraseColor(SK_ColorBLUE);
+
+    auto dataLossy    = SkEncodeBitmap(bm, SkEncodedImageFormat::kWEBP, 99);
+    auto dataLossLess = SkEncodeBitmap(bm, SkEncodedImageFormat::kWEBP, 100);
+
+    enum Format {
+        kMixed    = 0,
+        kLossy    = 1,
+        kLossless = 2,
+    };
+
+    auto test = [&r](const sk_sp<SkData>& data, Format expected) {
+        auto printFormat = [](int f) {
+            switch (f) {
+                case kMixed:    return "mixed";
+                case kLossy:    return "lossy";
+                case kLossless: return "lossless";
+                default:        return "unknown";
+            }
+        };
+
+        if (!data) {
+            ERRORF(r, "Failed to encode. Expected %s", printFormat(expected));
+            return;
+        }
+
+        WebPBitstreamFeatures features;
+        auto status = WebPGetFeatures(data->bytes(), data->size(), &features);
+        if (status != VP8_STATUS_OK) {
+            ERRORF(r, "Encode had an error %i. Expected %s", status, printFormat(expected));
+            return;
+        }
+
+        if (expected != features.format) {
+            ERRORF(r, "Expected %s encode, but got format %s", printFormat(expected),
+                                                               printFormat(features.format));
+        }
+    };
+
+    test(dataLossy,    kLossy);
+    test(dataLossLess, kLossless);
+}
+#endif
+
 DEF_TEST(Encode_WebpOptions, r) {
     SkBitmap bitmap;
     bool success = GetResourceAsBitmap("images/google_chrome.ico", &bitmap);
diff --git a/tests/EncodedInfoTest.cpp b/tests/EncodedInfoTest.cpp
index a8b7437..3babbd0 100644
--- a/tests/EncodedInfoTest.cpp
+++ b/tests/EncodedInfoTest.cpp
@@ -7,7 +7,7 @@
 
 #include "Resources.h"
 #include "Test.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 #include "SkBitmap.h"
 #include "SkCodec.h"
@@ -38,5 +38,5 @@
     result = codec->getPixels(bm2.pixmap());
     REPORTER_ASSERT(r, result == SkCodec::kSuccess);
 
-    REPORTER_ASSERT(r, sk_tool_utils::equal_pixels(bm.pixmap(), bm2.pixmap()));
+    REPORTER_ASSERT(r, ToolUtils::equal_pixels(bm.pixmap(), bm2.pixmap()));
 }
diff --git a/tests/FontHostStreamTest.cpp b/tests/FontHostStreamTest.cpp
index 41dcb29..8fb699b 100644
--- a/tests/FontHostStreamTest.cpp
+++ b/tests/FontHostStreamTest.cpp
@@ -88,7 +88,7 @@
 
         sk_sp<SkTypeface> typeface = font.refTypefaceOrDefault();
         int ttcIndex;
-        std::unique_ptr<SkStreamAsset> fontData(typeface->openStream(&ttcIndex));
+        std::unique_ptr<SkStreamAsset> fontData = typeface->openStream(&ttcIndex);
         if (!fontData) {
             // We're using a SkTypeface that can't give us a stream.
             // This happens with portable or system fonts.  End the test now.
diff --git a/tests/FontMgrAndroidParserTest.cpp b/tests/FontMgrAndroidParserTest.cpp
index 06ee34a..6adcfe6 100644
--- a/tests/FontMgrAndroidParserTest.cpp
+++ b/tests/FontMgrAndroidParserTest.cpp
@@ -7,9 +7,9 @@
 
 #include "Test.h"
 
+#include "CommandLineFlags.h"
 #include "Resources.h"
 #include "SkCanvas.h"
-#include "SkCommandLineFlags.h"
 #include "SkFixed.h"
 #include "SkFont.h"
 #include "SkFontMgr_android.h"
diff --git a/tests/FontMgrTest.cpp b/tests/FontMgrTest.cpp
index 308d791e..67867b5 100644
--- a/tests/FontMgrTest.cpp
+++ b/tests/FontMgrTest.cpp
@@ -5,11 +5,12 @@
  * found in the LICENSE file.
  */
 
+#include "CommandLineFlags.h"
 #include "SkAdvancedTypefaceMetrics.h"
-#include "SkCommandLineFlags.h"
 #include "SkFont.h"
 #include "SkFontMgr.h"
 #include "SkPaint.h"
+#include "SkStream.h"
 #include "SkTypeface.h"
 #include "Test.h"
 
@@ -122,7 +123,7 @@
     public:
         TestTypeface(const SkFontStyle& fontStyle) : SkTypeface(fontStyle, false){}
     protected:
-        SkStreamAsset* onOpenStream(int* ttcIndex) const override { return nullptr; }
+        std::unique_ptr<SkStreamAsset> onOpenStream(int* ttcIndex) const override { return nullptr; }
         sk_sp<SkTypeface> onMakeClone(const SkFontArguments& args) const override {
             return sk_ref_sp(this);
         }
diff --git a/tests/FontNamesTest.cpp b/tests/FontNamesTest.cpp
index e3538dc..de6bcc9 100644
--- a/tests/FontNamesTest.cpp
+++ b/tests/FontNamesTest.cpp
@@ -5,7 +5,7 @@
  * found in the LICENSE file.
  */
 
-#include "SkCommandLineFlags.h"
+#include "CommandLineFlags.h"
 #include "SkFontMgr.h"
 #include "SkOTTable_name.h"
 #include "SkTypeface.h"
@@ -216,7 +216,7 @@
 
 } // namespace
 
-DEFINE_bool(verboseFontNames, false, "verbose FontNames test.");
+static DEFINE_bool(verboseFontNames, false, "verbose FontNames test.");
 
 DEF_TEST(FontNames, reporter) {
     test_synthetic(reporter, FLAGS_verboseFontNames);
diff --git a/tests/GLProgramsTest.cpp b/tests/GLProgramsTest.cpp
index 60b878b..78d3749 100644
--- a/tests/GLProgramsTest.cpp
+++ b/tests/GLProgramsTest.cpp
@@ -25,7 +25,7 @@
 
 #include "ops/GrDrawOp.h"
 
-#include "effects/GrConfigConversionEffect.h"
+#include "effects/generated/GrConfigConversionEffect.h"
 #include "effects/GrPorterDuffXferProcessor.h"
 #include "effects/GrXfermodeFragmentProcessor.h"
 
@@ -313,7 +313,8 @@
         GrDrawRandomOp(&random, renderTargetContext.get(), std::move(paint));
     }
     // Flush everything, test passes if flush is successful(ie, no asserts are hit, no crashes)
-    drawingManager->flush(nullptr);
+    drawingManager->flush(nullptr, SkSurface::BackendSurfaceAccess::kNoAccess,
+                          kNone_GrFlushFlags, 0, nullptr);
 
     const GrBackendFormat format =
             context->priv().caps()->getBackendFormatFromColorType(kRGBA_8888_SkColorType);
@@ -342,7 +343,8 @@
             auto blockFP = BlockInputFragmentProcessor::Make(std::move(fp));
             paint.addColorFragmentProcessor(std::move(blockFP));
             GrDrawRandomOp(&random, renderTargetContext.get(), std::move(paint));
-            drawingManager->flush(nullptr);
+            drawingManager->flush(nullptr, SkSurface::BackendSurfaceAccess::kNoAccess,
+                                  kNone_GrFlushFlags, 0, nullptr);
         }
     }
 
diff --git a/tests/GrAHardwareBufferTest.cpp b/tests/GrAHardwareBufferTest.cpp
index c15fb1a..8fed798 100644
--- a/tests/GrAHardwareBufferTest.cpp
+++ b/tests/GrAHardwareBufferTest.cpp
@@ -207,4 +207,72 @@
     basic_draw_test_helper(reporter, context_info, kBottomLeft_GrSurfaceOrigin);
 }
 
+static void surface_draw_test_helper(skiatest::Reporter* reporter,
+                                     const sk_gpu_test::ContextInfo& info,
+                                     GrSurfaceOrigin surfaceOrigin) {
+
+    GrContext* context = info.grContext();
+    if (!context->priv().caps()->supportsAHardwareBufferImages()) {
+        return;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Setup SkBitmaps
+    ///////////////////////////////////////////////////////////////////////////
+
+    const SkBitmap srcBitmap = make_src_bitmap();
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Setup AHardwareBuffer
+    ///////////////////////////////////////////////////////////////////////////
+
+    AHardwareBuffer* buffer = nullptr;
+
+    AHardwareBuffer_Desc hwbDesc;
+    hwbDesc.width = DEV_W;
+    hwbDesc.height = DEV_H;
+    hwbDesc.layers = 1;
+    hwbDesc.usage = AHARDWAREBUFFER_USAGE_CPU_READ_NEVER |
+                    AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER |
+                    AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE |
+                    AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT;
+
+    hwbDesc.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
+    // The following three are not used in the allocate
+    hwbDesc.stride = 0;
+    hwbDesc.rfu0= 0;
+    hwbDesc.rfu1= 0;
+
+    if (int error = AHardwareBuffer_allocate(&hwbDesc, &buffer)) {
+        ERRORF(reporter, "Failed to allocated hardware buffer, error: %d", error);
+        cleanup_resources(buffer);
+        return;
+    }
+
+    sk_sp<SkSurface> surface = SkSurface::MakeFromAHardwareBuffer(context, buffer, surfaceOrigin,
+                                                                  nullptr, nullptr);
+    if (!surface) {
+        ERRORF(reporter, "Failed to make SkSurface.");
+        cleanup_resources(buffer);
+        return;
+    }
+
+    surface->getCanvas()->drawBitmap(srcBitmap, 0, 0);
+
+    SkBitmap readbackBitmap;
+    readbackBitmap.allocN32Pixels(DEV_W, DEV_H);
+
+    REPORTER_ASSERT(reporter, surface->readPixels(readbackBitmap, 0, 0));
+    REPORTER_ASSERT(reporter, check_read(reporter, srcBitmap, readbackBitmap));
+
+    cleanup_resources(buffer);
+}
+
+// Test to make sure we can import an AHardwareBuffer into an SkSurface and draw into it.
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrAHardwareBuffer_ImportAsSurface,
+                                   reporter, context_info) {
+    surface_draw_test_helper(reporter, context_info, kTopLeft_GrSurfaceOrigin);
+    surface_draw_test_helper(reporter, context_info, kBottomLeft_GrSurfaceOrigin);
+}
+
 #endif
diff --git a/tests/GrCCPRTest.cpp b/tests/GrCCPRTest.cpp
index 31ffa53..7cd8f10 100644
--- a/tests/GrCCPRTest.cpp
+++ b/tests/GrCCPRTest.cpp
@@ -8,12 +8,13 @@
 #include "SkTypes.h"
 #include "Test.h"
 
-#include "GrContext.h"
-#include "GrContextPriv.h"
 #include "GrClip.h"
+#include "GrContextPriv.h"
 #include "GrDrawingManager.h"
-#include "GrPathRenderer.h"
 #include "GrPaint.h"
+#include "GrPathRenderer.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrRenderTargetContextPriv.h"
 #include "GrShape.h"
@@ -22,9 +23,9 @@
 #include "SkMatrix.h"
 #include "SkPathPriv.h"
 #include "SkRect.h"
-#include "sk_tool_utils.h"
-#include "ccpr/GrCoverageCountingPathRenderer.h"
+#include "ToolUtils.h"
 #include "ccpr/GrCCPathCache.h"
+#include "ccpr/GrCoverageCountingPathRenderer.h"
 #include "mock/GrMockTypes.h"
 
 #include <cmath>
@@ -36,8 +37,8 @@
     CCPRClip(GrCoverageCountingPathRenderer* ccpr, const SkPath& path) : fCCPR(ccpr), fPath(path) {}
 
 private:
-    bool apply(GrContext* context, GrRenderTargetContext* rtc, bool, bool, GrAppliedClip* out,
-               SkRect* bounds) const override {
+    bool apply(GrRecordingContext* context, GrRenderTargetContext* rtc, bool useHWAA,
+               bool hasUserStencilSettings, GrAppliedClip* out, SkRect* bounds) const override {
         out->addCoverageFP(fCCPR->makeClipProcessor(rtc->priv().testingOnly_getOpListID(), fPath,
                                                     SkIRect::MakeWH(rtc->width(), rtc->height()),
                                                     rtc->width(), rtc->height(),
@@ -111,7 +112,7 @@
 
         fCCPR->testingOnly_drawPathDirectly({
                 fCtx.get(), std::move(paint), &GrUserStencilSettings::kUnused, fRTC.get(), &noClip,
-                &clipBounds, &matrix, &shape, GrAAType::kCoverage, false});
+                &clipBounds, &matrix, &shape, GrPathRenderer::AATypeFlags::kCoverage, false});
     }
 
     void clipFullscreenRect(SkPath clipPath, SkPMColor4f color = { 0, 1, 0, 1 }) {
@@ -154,6 +155,7 @@
         mockOptions.fFlatInterpolationSupport = true;
 
         GrContextOptions ctxOptions;
+        ctxOptions.fDisableCoverageCountingPaths = false;
         ctxOptions.fAllowPathMaskCaching = false;
         ctxOptions.fGpuPathRenderers = GpuPathRenderers::kCoverageCounting;
 
@@ -353,7 +355,7 @@
             do {
                 step = primes[rand.nextU() % SK_ARRAY_COUNT(primes)];
             } while (step == numPts);
-            fPaths[i] = sk_tool_utils::make_star(SkRect::MakeLTRB(0,0,1,1), numPts, step);
+            fPaths[i] = ToolUtils::make_star(SkRect::MakeLTRB(0, 0, 1, 1), numPts, step);
         }
     }
 
diff --git a/tests/GrGLExtensionsTest.cpp b/tests/GrGLExtensionsTest.cpp
index 0c78081..ca59ede 100644
--- a/tests/GrGLExtensionsTest.cpp
+++ b/tests/GrGLExtensionsTest.cpp
@@ -31,7 +31,7 @@
 
 DEF_TEST(GrGLExtensionsTest_remove, reporter) {
     GrGLExtensions ext;
-    ext.init(kNone_GrGLStandard,
+    ext.init(kGL_GrGLStandard,
              &simpleGetString,
              &simpleGetStringi,
              &simpleGetIntegerv,
diff --git a/tests/GrMeshTest.cpp b/tests/GrMeshTest.cpp
index 309a29b..ae8251f 100644
--- a/tests/GrMeshTest.cpp
+++ b/tests/GrMeshTest.cpp
@@ -131,8 +131,6 @@
         }
     }
 
-    goldCanvas.flush();
-
     // ---- tests ----------
 
 #define VALIDATE(buff)                           \
@@ -279,7 +277,8 @@
 
     const char* name() const override { return "GrMeshTestOp"; }
     FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
-    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*) override {
+    GrProcessorSet::Analysis finalize(
+            const GrCaps&, const GrAppliedClip*, GrFSAAType, GrClampType) override {
         return GrProcessorSet::EmptySetAnalysis();
     }
     void onPrepare(GrOpFlushState*) override {}
@@ -375,8 +374,7 @@
 template<typename T>
 sk_sp<const GrBuffer> DrawMeshHelper::makeVertexBuffer(const T* data, int count) {
     return sk_sp<const GrBuffer>(fState->resourceProvider()->createBuffer(
-            count * sizeof(T), GrGpuBufferType::kVertex, kDynamic_GrAccessPattern,
-            GrResourceProvider::Flags::kRequireGpuMemory, data));
+            count * sizeof(T), GrGpuBufferType::kVertex, kDynamic_GrAccessPattern, data));
 }
 
 sk_sp<const GrBuffer> DrawMeshHelper::getIndexBuffer() {
diff --git a/tests/GrMipMappedTest.cpp b/tests/GrMipMappedTest.cpp
index fc29d82..7e26a44 100644
--- a/tests/GrMipMappedTest.cpp
+++ b/tests/GrMipMappedTest.cpp
@@ -64,7 +64,7 @@
                                                  kRGBA_8888_SkColorType,
                                                  kPremul_SkAlphaType, nullptr,
                                                  nullptr, nullptr);
-                proxy = as_IB(image)->asTextureProxyRef();
+                proxy = as_IB(image)->asTextureProxyRef(context);
             }
             REPORTER_ASSERT(reporter, proxy);
             if (!proxy) {
@@ -204,6 +204,22 @@
                     ERRORF(reporter, "Failed to get GrVkImageInfo");
                 }
 #endif
+#ifdef SK_METAL
+            } else if (GrBackendApi::kMetal == genBackendTex.backend()) {
+                GrMtlTextureInfo genImageInfo;
+                GrMtlTextureInfo origImageInfo;
+                if (genBackendTex.getMtlTextureInfo(&genImageInfo) &&
+                    backendTex.getMtlTextureInfo(&origImageInfo)) {
+                    if (willUseMips && GrMipMapped::kNo == mipMapped) {
+                        // We did a copy so the texture IDs should be different
+                        REPORTER_ASSERT(reporter, origImageInfo.fTexture != genImageInfo.fTexture);
+                    } else {
+                        REPORTER_ASSERT(reporter, origImageInfo.fTexture == genImageInfo.fTexture);
+                    }
+                } else {
+                    ERRORF(reporter, "Failed to get GrMtlTextureInfo");
+                }
+#endif
             } else {
                 REPORTER_ASSERT(reporter, false);
             }
diff --git a/tests/GrPipelineDynamicStateTest.cpp b/tests/GrPipelineDynamicStateTest.cpp
index 5e2d36c..a44990a 100644
--- a/tests/GrPipelineDynamicStateTest.cpp
+++ b/tests/GrPipelineDynamicStateTest.cpp
@@ -8,12 +8,15 @@
 #include "SkTypes.h"
 #include "Test.h"
 
-#include "GrContext.h"
 #include "GrColor.h"
+#include "GrContext.h"
+#include "GrContextPriv.h"
 #include "GrGeometryProcessor.h"
 #include "GrGpuCommandBuffer.h"
 #include "GrMemoryPool.h"
 #include "GrOpFlushState.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrRenderTargetContextPriv.h"
 #include "GrResourceProvider.h"
@@ -110,7 +113,7 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrScissorTest scissorTest,
                                           sk_sp<const GrBuffer> vbuff) {
         GrOpMemoryPool* pool = context->priv().opMemoryPool();
@@ -131,7 +134,8 @@
 
     const char* name() const override { return "GrPipelineDynamicStateTestOp"; }
     FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
-    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*) override {
+    GrProcessorSet::Analysis finalize(
+            const GrCaps&, const GrAppliedClip*, GrFSAAType, GrClampType) override {
         return GrProcessorSet::EmptySetAnalysis();
     }
     void onPrepare(GrOpFlushState*) override {}
@@ -194,9 +198,8 @@
         {d, d, kMeshColors[3]}
     };
 
-    sk_sp<const GrBuffer> vbuff(
-            rp->createBuffer(sizeof(vdata), GrGpuBufferType::kVertex, kDynamic_GrAccessPattern,
-                             GrResourceProvider::Flags::kRequireGpuMemory, vdata));
+    sk_sp<const GrBuffer> vbuff(rp->createBuffer(sizeof(vdata), GrGpuBufferType::kVertex,
+                                                 kDynamic_GrAccessPattern, vdata));
     if (!vbuff) {
         ERRORF(reporter, "vbuff is null.");
         return;
diff --git a/tests/GrPorterDuffTest.cpp b/tests/GrPorterDuffTest.cpp
index 611d675..d386090 100644
--- a/tests/GrPorterDuffTest.cpp
+++ b/tests/GrPorterDuffTest.cpp
@@ -27,8 +27,12 @@
 static void test_lcd_coverage(skiatest::Reporter* reporter, const GrCaps& caps);
 static void test_lcd_coverage_fallback_case(skiatest::Reporter* reporter, const GrCaps& caps);
 
-DEF_GPUTEST_FOR_NULLGL_CONTEXT(GrPorterDuff, reporter, ctxInfo) {
-    const GrCaps& caps = *ctxInfo.grContext()->priv().getGpu()->caps();
+DEF_GPUTEST(GrPorterDuff, reporter, /*ctxInfo*/) {
+    GrMockOptions mockOptions;
+    mockOptions.fDualSourceBlendingSupport = true;
+    auto context = GrContext::MakeMock(&mockOptions, GrContextOptions());
+    const GrCaps& caps = *context->priv().getGpu()->caps();
+
     if (!caps.shaderCaps()->dualSourceBlendingSupport()) {
         SK_ABORT("Null context does not support dual source blending.");
         return;
@@ -64,8 +68,9 @@
     paint.setXPFactory(xpf);
     GrProcessorSet procs(std::move(paint));
     SkPMColor4f overrideColor;
-    GrProcessorSet::Analysis analysis =
-            procs.finalize(colorInput, coverageInput, nullptr, false, caps, &overrideColor);
+    GrProcessorSet::Analysis analysis = procs.finalize(
+            colorInput, coverageInput, nullptr, &GrUserStencilSettings::kUnused, GrFSAAType::kNone,
+            caps, GrClampType::kAuto, &overrideColor);
     return analysis;
 }
 
@@ -82,7 +87,8 @@
             fCompatibleWithCoverageAsAlpha = analysis.isCompatibleWithCoverageAsAlpha();
             fIgnoresInputColor = analysis.inputColorIsIgnored();
             sk_sp<const GrXferProcessor> xp(
-                    GrXPFactory::MakeXferProcessor(xpf, inputColor, inputCoverage, false, caps));
+                    GrXPFactory::MakeXferProcessor(xpf, inputColor, inputCoverage, false, caps,
+                                                   GrClampType::kAuto));
             TEST_ASSERT(!analysis.requiresDstTexture() ||
                         (isLCD &&
                          !caps.shaderCaps()->dstReadInShaderSupport() &&
@@ -940,13 +946,14 @@
 }
 
 static void test_lcd_coverage_fallback_case(skiatest::Reporter* reporter, const GrCaps& caps) {
+    constexpr GrClampType autoClamp = GrClampType::kAuto;
     const GrXPFactory* xpf = GrPorterDuffXPFactory::Get(SkBlendMode::kSrcOver);
     GrProcessorAnalysisColor color = SkPMColor4f::FromBytes_RGBA(GrColorPackRGBA(123, 45, 67, 255));
     GrProcessorAnalysisCoverage coverage = GrProcessorAnalysisCoverage::kLCD;
-    TEST_ASSERT(!(GrXPFactory::GetAnalysisProperties(xpf, color, coverage, caps) &
+    TEST_ASSERT(!(GrXPFactory::GetAnalysisProperties(xpf, color, coverage, caps, autoClamp) &
                   GrXPFactory::AnalysisProperties::kRequiresDstTexture));
     sk_sp<const GrXferProcessor> xp_opaque(
-            GrXPFactory::MakeXferProcessor(xpf, color, coverage, false, caps));
+            GrXPFactory::MakeXferProcessor(xpf, color, coverage, false, caps, autoClamp));
     if (!xp_opaque) {
         ERRORF(reporter, "Failed to create an XP with LCD coverage.");
         return;
@@ -959,10 +966,10 @@
     // Test with non-opaque alpha
     color = SkPMColor4f::FromBytes_RGBA(GrColorPackRGBA(123, 45, 67, 221));
     coverage = GrProcessorAnalysisCoverage::kLCD;
-    TEST_ASSERT(!(GrXPFactory::GetAnalysisProperties(xpf, color, coverage, caps) &
+    TEST_ASSERT(!(GrXPFactory::GetAnalysisProperties(xpf, color, coverage, caps, autoClamp) &
                 GrXPFactory::AnalysisProperties::kRequiresDstTexture));
     sk_sp<const GrXferProcessor> xp(
-            GrXPFactory::MakeXferProcessor(xpf, color, coverage, false, caps));
+            GrXPFactory::MakeXferProcessor(xpf, color, coverage, false, caps, autoClamp));
     if (!xp) {
         ERRORF(reporter, "Failed to create an XP with LCD coverage.");
         return;
@@ -976,9 +983,9 @@
     GrContextOptions opts = options;
     opts.fSuppressDualSourceBlending = true;
     sk_gpu_test::GrContextFactory mockFactory(opts);
-    GrContext* ctx = mockFactory.get(sk_gpu_test::GrContextFactory::kNullGL_ContextType);
+    GrContext* ctx = mockFactory.get(sk_gpu_test::GrContextFactory::kMock_ContextType);
     if (!ctx) {
-        SK_ABORT("Failed to create null context without ARB_blend_func_extended.");
+        SK_ABORT("Failed to create mock context without ARB_blend_func_extended.");
         return;
     }
 
@@ -986,7 +993,7 @@
     GrProxyProvider* proxyProvider = ctx->priv().proxyProvider();
     const GrCaps& caps = *ctx->priv().caps();
     if (caps.shaderCaps()->dualSourceBlendingSupport()) {
-        SK_ABORT("Null context failed to honor request for no ARB_blend_func_extended.");
+        SK_ABORT("Mock context failed to honor request for no ARB_blend_func_extended.");
         return;
     }
 
@@ -1014,7 +1021,8 @@
                 SkBlendMode xfermode = static_cast<SkBlendMode>(m);
                 const GrXPFactory* xpf = GrPorterDuffXPFactory::Get(xfermode);
                 sk_sp<const GrXferProcessor> xp(
-                        GrXPFactory::MakeXferProcessor(xpf, colorInput, coverageType, false, caps));
+                        GrXPFactory::MakeXferProcessor(xpf, colorInput, coverageType, false, caps,
+                                                       GrClampType::kAuto));
                 if (!xp) {
                     ERRORF(reporter, "Failed to create an XP without dual source blending.");
                     return;
diff --git a/tests/GrQuadListTest.cpp b/tests/GrQuadListTest.cpp
index fd88dde..fbbfab7 100644
--- a/tests/GrQuadListTest.cpp
+++ b/tests/GrQuadListTest.cpp
@@ -30,7 +30,7 @@
 }
 
 static GrPerspQuad make_2d_persp_quad() {
-    return GrPerspQuad(SkRect::MakeLTRB(5.f, 6.f, 7.f, 8.f), SkMatrix::I());
+    return GrPerspQuad(SkRect::MakeLTRB(5.f, 6.f, 7.f, 8.f));
 }
 static bool is_2d_persp_quad(const GrPerspQuad& quad) {
     return quad.x(0) == 5.f && quad.x(1) == 5.f && quad.x(2) == 7.f && quad.x(3) == 7.f &&
@@ -43,7 +43,7 @@
     SkMatrix p = SkMatrix::I();
     p[SkMatrix::kMPersp2] = 13.f;
     SkASSERT(p.hasPerspective()); // Sanity check
-    return GrPerspQuad(SkRect::MakeLTRB(9.f, 10.f, 11.f, 12.f), p);
+    return GrPerspQuad::MakeFromRect(SkRect::MakeLTRB(9.f, 10.f, 11.f, 12.f), p);
 }
 static bool is_3d_persp_quad(const GrPerspQuad& quad) {
     return quad.x(0) == 9.f && quad.x(1) == 9.f && quad.x(2) == 11.f && quad.x(3) == 11.f &&
diff --git a/tests/GrSurfaceTest.cpp b/tests/GrSurfaceTest.cpp
index e62d953..d85b6f1 100644
--- a/tests/GrSurfaceTest.cpp
+++ b/tests/GrSurfaceTest.cpp
@@ -22,7 +22,7 @@
 
 // Tests that GrSurface::asTexture(), GrSurface::asRenderTarget(), and static upcasting of texture
 // and render targets to GrSurface all work as expected.
-DEF_GPUTEST_FOR_NULLGL_CONTEXT(GrSurface, reporter, ctxInfo) {
+DEF_GPUTEST_FOR_MOCK_CONTEXT(GrSurface, reporter, ctxInfo) {
     GrContext* context = ctxInfo.grContext();
     auto resourceProvider = context->priv().resourceProvider();
     GrGpu* gpu = context->priv().getGpu();
@@ -33,7 +33,8 @@
     desc.fHeight = 256;
     desc.fConfig = kRGBA_8888_GrPixelConfig;
     desc.fSampleCnt = 1;
-    sk_sp<GrSurface> texRT1 = resourceProvider->createTexture(desc, SkBudgeted::kNo);
+    sk_sp<GrSurface> texRT1 = resourceProvider->createTexture(
+        desc, SkBudgeted::kNo, GrResourceProvider::Flags::kNoPendingIO);
 
     REPORTER_ASSERT(reporter, texRT1.get() == texRT1->asRenderTarget());
     REPORTER_ASSERT(reporter, texRT1.get() == texRT1->asTexture());
@@ -45,7 +46,8 @@
                     static_cast<GrSurface*>(texRT1->asTexture()));
 
     desc.fFlags = kNone_GrSurfaceFlags;
-    sk_sp<GrTexture> tex1 = resourceProvider->createTexture(desc, SkBudgeted::kNo);
+    sk_sp<GrTexture> tex1 = resourceProvider->createTexture(
+        desc, SkBudgeted::kNo, GrResourceProvider::Flags::kNoPendingIO);
     REPORTER_ASSERT(reporter, nullptr == tex1->asRenderTarget());
     REPORTER_ASSERT(reporter, tex1.get() == tex1->asTexture());
     REPORTER_ASSERT(reporter, static_cast<GrSurface*>(tex1.get()) == tex1->asTexture());
@@ -88,6 +90,7 @@
         kRGBA_4444_GrPixelConfig,
         kRGBA_8888_GrPixelConfig,
         kRGB_888_GrPixelConfig,
+        kRGB_888X_GrPixelConfig,
         kRG_88_GrPixelConfig,
         kBGRA_8888_GrPixelConfig,
         kSRGBA_8888_GrPixelConfig,
@@ -98,6 +101,7 @@
         kAlpha_half_GrPixelConfig,
         kAlpha_half_as_Red_GrPixelConfig,
         kRGBA_half_GrPixelConfig,
+        kRGBA_half_Clamped_GrPixelConfig,
         kRGB_ETC1_GrPixelConfig,
     };
     GR_STATIC_ASSERT(kGrPixelConfigCnt == SK_ARRAY_COUNT(configs));
@@ -112,7 +116,8 @@
             desc.fConfig = config;
             desc.fSampleCnt = 1;
 
-            sk_sp<GrSurface> tex = resourceProvider->createTexture(desc, SkBudgeted::kNo);
+            sk_sp<GrSurface> tex = resourceProvider->createTexture(
+                desc, SkBudgeted::kNo, GrResourceProvider::Flags::kNoPendingIO);
             bool ict = caps->isConfigTexturable(desc.fConfig);
             REPORTER_ASSERT(reporter, SkToBool(tex) == ict,
                             "config:%d, tex:%d, isConfigTexturable:%d", config, SkToBool(tex), ict);
@@ -129,14 +134,16 @@
                              caps->mipMapSupport()));
 
             desc.fFlags = kRenderTarget_GrSurfaceFlag;
-            tex = resourceProvider->createTexture(desc, SkBudgeted::kNo);
+            tex = resourceProvider->createTexture(desc, SkBudgeted::kNo,
+                                                  GrResourceProvider::Flags::kNoPendingIO);
             bool isRenderable = caps->isConfigRenderable(config);
             REPORTER_ASSERT(reporter, SkToBool(tex) == isRenderable,
                             "config:%d, tex:%d, isRenderable:%d", config, SkToBool(tex),
                             isRenderable);
 
             desc.fSampleCnt = 2;
-            tex = resourceProvider->createTexture(desc, SkBudgeted::kNo);
+            tex = resourceProvider->createTexture(desc, SkBudgeted::kNo,
+                                                  GrResourceProvider::Flags::kNoPendingIO);
             isRenderable = SkToBool(caps->getRenderTargetSampleCount(2, config));
             REPORTER_ASSERT(reporter, SkToBool(tex) == isRenderable,
                             "config:%d, tex:%d, isRenderable:%d", config, SkToBool(tex),
@@ -335,63 +342,52 @@
     }
 }
 
-DEF_GPUTEST(TextureIdleProcTest, reporter, options) {
-    static const int kS = 10;
-
-    // Helper to delete a backend texture in a GrTexture's release proc.
-    static const auto installBackendTextureReleaseProc = [](GrTexture* texture) {
-        auto backendTexture = texture->getBackendTexture();
-        auto context = texture->getContext();
-        struct ReleaseContext {
-            GrContext* fContext;
-            GrBackendTexture fBackendTexture;
-        };
-        auto release = [](void* rc) {
-            auto releaseContext = static_cast<ReleaseContext*>(rc);
-            if (!releaseContext->fContext->abandoned()) {
-                if (auto gpu = releaseContext->fContext->priv().getGpu()) {
-                    gpu->deleteTestingOnlyBackendTexture(releaseContext->fBackendTexture);
-                }
-            }
-            delete releaseContext;
-        };
-        texture->setRelease(sk_make_sp<GrReleaseProcHelper>(
-                release, new ReleaseContext{context, backendTexture}));
-    };
-
-    // Various ways of making textures.
-    auto makeWrapped = [](GrContext* context) {
-        auto backendTexture = context->priv().getGpu()->createTestingOnlyBackendTexture(
-                nullptr, kS, kS, GrColorType::kRGBA_8888, false, GrMipMapped::kNo);
-        auto texture = context->priv().resourceProvider()->wrapBackendTexture(
-                backendTexture, kBorrow_GrWrapOwnership, GrWrapCacheable::kNo, kRW_GrIOType);
-        installBackendTextureReleaseProc(texture.get());
-        return texture;
-    };
-
-    auto makeWrappedRenderable = [](GrContext* context) {
-        auto backendTexture = context->priv().getGpu()->createTestingOnlyBackendTexture(
-                nullptr, kS, kS, GrColorType::kRGBA_8888, true, GrMipMapped::kNo);
-        auto texture = context->priv().resourceProvider()->wrapRenderableBackendTexture(
+static sk_sp<GrTexture> make_wrapped_texture(GrContext* context, bool renderable) {
+    auto backendTexture = context->priv().getGpu()->createTestingOnlyBackendTexture(
+            nullptr, 10, 10, GrColorType::kRGBA_8888, renderable, GrMipMapped::kNo);
+    sk_sp<GrTexture> texture;
+    if (renderable) {
+        texture = context->priv().resourceProvider()->wrapRenderableBackendTexture(
                 backendTexture, 1, kBorrow_GrWrapOwnership, GrWrapCacheable::kNo);
-        installBackendTextureReleaseProc(texture.get());
-        return texture;
+    } else {
+        texture = context->priv().resourceProvider()->wrapBackendTexture(
+                backendTexture, kBorrow_GrWrapOwnership, GrWrapCacheable::kNo, kRW_GrIOType);
+    }
+    // Add a release proc that deletes the GrBackendTexture.
+    struct ReleaseContext {
+        GrContext* fContext;
+        GrBackendTexture fBackendTexture;
     };
+    auto release = [](void* rc) {
+        auto releaseContext = static_cast<ReleaseContext*>(rc);
+        if (!releaseContext->fContext->abandoned()) {
+            if (auto gpu = releaseContext->fContext->priv().getGpu()) {
+                gpu->deleteTestingOnlyBackendTexture(releaseContext->fBackendTexture);
+            }
+        }
+        delete releaseContext;
+    };
+    texture->setRelease(release, new ReleaseContext{context, backendTexture});
+    return texture;
+}
 
-    auto makeNormal = [](GrContext* context) {
-        GrSurfaceDesc desc;
-        desc.fConfig = kRGBA_8888_GrPixelConfig;
-        desc.fWidth = desc.fHeight = kS;
-        return context->priv().resourceProvider()->createTexture(desc, SkBudgeted::kNo);
-    };
+static sk_sp<GrTexture> make_normal_texture(GrContext* context, bool renderable) {
+    GrSurfaceDesc desc;
+    desc.fConfig = kRGBA_8888_GrPixelConfig;
+    desc.fWidth = desc.fHeight = 10;
+    desc.fFlags = renderable ? kRenderTarget_GrSurfaceFlag : kNone_GrSurfaceFlags;
+    return context->priv().resourceProvider()->createTexture(
+        desc, SkBudgeted::kNo, GrResourceProvider::Flags::kNoPendingIO);
+}
 
-    auto makeRenderable = [](GrContext* context) {
-        GrSurfaceDesc desc;
-        desc.fFlags = kRenderTarget_GrSurfaceFlag;
-        desc.fConfig = kRGBA_8888_GrPixelConfig;
-        desc.fWidth = desc.fHeight = kS;
-        return context->priv().resourceProvider()->createTexture(desc, SkBudgeted::kNo);
+DEF_GPUTEST(TextureIdleProcTest, reporter, options) {
+    // Various ways of making textures.
+    auto makeWrapped = [](GrContext* context) { return make_wrapped_texture(context, false); };
+    auto makeWrappedRenderable = [](GrContext* context) {
+        return make_wrapped_texture(context, true);
     };
+    auto makeNormal = [](GrContext* context) { return make_normal_texture(context, false); };
+    auto makeRenderable = [](GrContext* context) { return make_normal_texture(context, true); };
 
     std::function<sk_sp<GrTexture>(GrContext*)> makers[] = {makeWrapped, makeWrappedRenderable,
                                                             makeNormal, makeRenderable};
@@ -434,7 +430,8 @@
                 // Makes a texture, possibly adds a key, and sets the callback.
                 auto make = [&m, &keyAdder, &proc, &idleIDs](GrContext* context, int num) {
                     sk_sp<GrTexture> texture = m(context);
-                    texture->setIdleProc(proc, new Context{&idleIDs, num});
+                    texture->addIdleProc(proc, new Context{&idleIDs, num},
+                                         GrTexture::IdleState::kFinished);
                     keyAdder(texture.get());
                     return texture;
                 };
@@ -447,15 +444,22 @@
                 REPORTER_ASSERT(reporter, idleIDs.find(1) != idleIDs.end());
 
                 texture = make(context, 2);
+                int w = texture->width();
+                int h = texture->height();
                 SkImageInfo info =
-                        SkImageInfo::Make(kS, kS, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+                        SkImageInfo::Make(w, h, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
                 auto rt = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 0, nullptr);
                 auto rtc = rt->getCanvas()->internal_private_accessTopLayerRenderTargetContext();
                 auto singleUseLazyCB = [&texture](GrResourceProvider* rp) {
-                    return rp ? std::move(texture) : nullptr;
+                    auto mode = GrSurfaceProxy::LazyInstantiationKeyMode::kSynced;
+                    if (texture->getUniqueKey().isValid()) {
+                        mode = GrSurfaceProxy::LazyInstantiationKeyMode::kUnsynced;
+                    }
+                    return GrSurfaceProxy::LazyInstantiationResult{std::move(texture), mode};
                 };
                 GrSurfaceDesc desc;
-                desc.fWidth = desc.fHeight = kS;
+                desc.fWidth = w;
+                desc.fHeight = h;
                 desc.fConfig = kRGBA_8888_GrPixelConfig;
                 if (isRT) {
                     desc.fFlags = kRenderTarget_GrSurfaceFlag;
@@ -471,10 +475,10 @@
                         GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo,
                         GrInternalSurfaceFlags ::kNone, SkBackingFit::kExact, budgeted,
                         GrSurfaceProxy::LazyInstantiationType::kSingleUse);
-                rtc->drawTexture(GrNoClip(), proxy, GrSamplerState::Filter::kNearest, SkPMColor4f(),
-                                 SkRect::MakeWH(kS, kS), SkRect::MakeWH(kS, kS),
-                                 GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint,
-                                 SkMatrix::I(), nullptr);
+                rtc->drawTexture(GrNoClip(), proxy, GrSamplerState::Filter::kNearest,
+                                 SkBlendMode::kSrcOver, SkPMColor4f(), SkRect::MakeWH(w, h),
+                                 SkRect::MakeWH(w, h), GrAA::kNo, GrQuadAAFlags::kNone,
+                                 SkCanvas::kFast_SrcRectConstraint, SkMatrix::I(), nullptr);
                 // We still have the proxy, which should remain instantiated, thereby keeping the
                 // texture not purgeable.
                 REPORTER_ASSERT(reporter, idleIDs.find(2) == idleIDs.end());
@@ -485,9 +489,9 @@
 
                 // This time we move the proxy into the draw.
                 rtc->drawTexture(GrNoClip(), std::move(proxy), GrSamplerState::Filter::kNearest,
-                                 SkPMColor4f(), SkRect::MakeWH(kS, kS), SkRect::MakeWH(kS, kS),
-                                 GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint,
-                                 SkMatrix::I(), nullptr);
+                                 SkBlendMode::kSrcOver, SkPMColor4f(), SkRect::MakeWH(w, h),
+                                 SkRect::MakeWH(w, h), GrAA::kNo, GrQuadAAFlags::kNone,
+                                 SkCanvas::kFast_SrcRectConstraint, SkMatrix::I(), nullptr);
                 REPORTER_ASSERT(reporter, idleIDs.find(2) == idleIDs.end());
                 context->flush();
                 context->priv().getGpu()->testingOnly_flushGpuAndSync();
@@ -496,7 +500,12 @@
 
                 // Make a proxy that should deinstantiate even if we keep a ref on it.
                 auto deinstantiateLazyCB = [&make, &context](GrResourceProvider* rp) {
-                    return rp ? make(context, 3) : nullptr;
+                    auto texture = make(context, 3);
+                    auto mode = GrSurfaceProxy::LazyInstantiationKeyMode::kSynced;
+                    if (texture->getUniqueKey().isValid()) {
+                        mode = GrSurfaceProxy::LazyInstantiationKeyMode::kUnsynced;
+                    }
+                    return GrSurfaceProxy::LazyInstantiationResult{std::move(texture), mode};
                 };
                 proxy = context->priv().proxyProvider()->createLazyProxy(
                         deinstantiateLazyCB, backendFormat, desc,
@@ -504,9 +513,9 @@
                         GrInternalSurfaceFlags ::kNone, SkBackingFit::kExact, budgeted,
                         GrSurfaceProxy::LazyInstantiationType::kDeinstantiate);
                 rtc->drawTexture(GrNoClip(), std::move(proxy), GrSamplerState::Filter::kNearest,
-                                 SkPMColor4f(), SkRect::MakeWH(kS, kS), SkRect::MakeWH(kS, kS),
-                                 GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint,
-                                 SkMatrix::I(), nullptr);
+                                 SkBlendMode::kSrcOver, SkPMColor4f(), SkRect::MakeWH(w, h),
+                                 SkRect::MakeWH(w, h), GrAA::kNo, GrQuadAAFlags::kNone,
+                                 SkCanvas::kFast_SrcRectConstraint, SkMatrix::I(), nullptr);
                 // At this point the proxy shouldn't even be instantiated, there is no texture with
                 // id 3.
                 REPORTER_ASSERT(reporter, idleIDs.find(3) == idleIDs.end());
@@ -538,11 +547,12 @@
                 for (auto drawType :
                      {DrawType::kNoDraw, DrawType::kDraw, DrawType::kDrawAndFlush}) {
                     for (bool unrefFirst : {false, true}) {
-                        auto possiblyDrawAndFlush = [&context, &texture, drawType, unrefFirst] {
+                        auto possiblyDrawAndFlush = [&context, &texture, drawType, unrefFirst, w,
+                                                     h] {
                             if (drawType == DrawType::kNoDraw) {
                                 return;
                             }
-                            SkImageInfo info = SkImageInfo::Make(kS, kS, kRGBA_8888_SkColorType,
+                            SkImageInfo info = SkImageInfo::Make(w, h, kRGBA_8888_SkColorType,
                                                                  kPremul_SkAlphaType);
                             auto rt = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 0,
                                                                   nullptr);
@@ -550,11 +560,11 @@
                                             ->internal_private_accessTopLayerRenderTargetContext();
                             auto proxy = context->priv().proxyProvider()->testingOnly_createWrapped(
                                                          texture, kTopLeft_GrSurfaceOrigin);
-                            rtc->drawTexture(GrNoClip(), proxy, GrSamplerState::Filter::kNearest,
-                                             SkPMColor4f(), SkRect::MakeWH(kS, kS),
-                                             SkRect::MakeWH(kS, kS), GrQuadAAFlags::kNone,
-                                             SkCanvas::kFast_SrcRectConstraint, SkMatrix::I(),
-                                             nullptr);
+                            rtc->drawTexture(
+                                    GrNoClip(), proxy, GrSamplerState::Filter::kNearest,
+                                    SkBlendMode::kSrcOver, SkPMColor4f(), SkRect::MakeWH(w, h),
+                                    SkRect::MakeWH(w, h), GrAA::kNo, GrQuadAAFlags::kNone,
+                                    SkCanvas::kFast_SrcRectConstraint, SkMatrix::I(), nullptr);
                             if (drawType == DrawType::kDrawAndFlush) {
                                 context->flush();
                             }
@@ -603,3 +613,121 @@
         }
     }
 }
+
+// Tests an idle proc that unrefs another resource down to zero.
+DEF_GPUTEST_FOR_ALL_CONTEXTS(TextureIdleProcCacheManipulationTest, reporter, contextInfo) {
+    GrContext* context = contextInfo.grContext();
+
+    // idle proc that releases another texture.
+    auto idleProc = [](void* texture) { reinterpret_cast<GrTexture*>(texture)->unref(); };
+
+    for (const auto& idleMaker : {make_wrapped_texture, make_normal_texture}) {
+        for (const auto& otherMaker : {make_wrapped_texture, make_normal_texture}) {
+            for (auto idleState :
+                 {GrTexture::IdleState::kFlushed, GrTexture::IdleState::kFinished}) {
+                auto idleTexture = idleMaker(context, false);
+                auto otherTexture = otherMaker(context, false);
+                otherTexture->ref();
+                idleTexture->addIdleProc(idleProc, otherTexture.get(), idleState);
+                otherTexture.reset();
+                idleTexture.reset();
+            }
+        }
+    }
+}
+
+// Similar to above but more complicated. This flushes the context from the idle proc.
+// crbug.com/933526.
+DEF_GPUTEST_FOR_ALL_CONTEXTS(TextureIdleProcFlushTest, reporter, contextInfo) {
+    GrContext* context = contextInfo.grContext();
+
+    // idle proc that flushes the context.
+    auto idleProc = [](void* context) { reinterpret_cast<GrContext*>(context)->flush(); };
+
+    for (const auto& idleMaker : {make_wrapped_texture, make_normal_texture}) {
+        for (auto idleState : {GrTexture::IdleState::kFlushed, GrTexture::IdleState::kFinished}) {
+            auto idleTexture = idleMaker(context, false);
+            idleTexture->addIdleProc(idleProc, context, idleState);
+            auto info = SkImageInfo::Make(10, 10, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+            auto surf = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 1, nullptr);
+            // We'll draw two images to the canvas. One is a normal texture-backed image. The other
+            // is a wrapped-texture backed image.
+            surf->getCanvas()->clear(SK_ColorWHITE);
+            auto img1 = surf->makeImageSnapshot();
+            auto gpu = context->priv().getGpu();
+            std::unique_ptr<uint32_t[]> pixels(new uint32_t[info.width() * info.height()]);
+            auto backendTexture = gpu->createTestingOnlyBackendTexture(
+                    pixels.get(), info.width(), info.height(), kRGBA_8888_SkColorType, false,
+                    GrMipMapped::kNo);
+            auto img2 = SkImage::MakeFromTexture(context, backendTexture, kTopLeft_GrSurfaceOrigin,
+                                                 info.colorType(), info.alphaType(), nullptr);
+            surf->getCanvas()->drawImage(std::move(img1), 0, 0);
+            surf->getCanvas()->drawImage(std::move(img2), 1, 1);
+            idleTexture.reset();
+            gpu->deleteTestingOnlyBackendTexture(backendTexture);
+        }
+    }
+}
+
+DEF_GPUTEST_FOR_ALL_CONTEXTS(TextureIdleProcRerefTest, reporter, contextInfo) {
+    GrContext* context = contextInfo.grContext();
+    // idle proc that refs the texture
+    auto idleProc = [](void* texture) { reinterpret_cast<GrTexture*>(texture)->ref(); };
+    // release proc to check whether the texture was released or not.
+    auto releaseProc = [](void* isReleased) { *reinterpret_cast<bool*>(isReleased) = true; };
+    for (auto idleState : {GrTexture::IdleState::kFlushed, GrTexture::IdleState::kFinished}) {
+        bool isReleased = false;
+        auto idleTexture = make_normal_texture(context, false);
+        // This test assumes the texture won't be cached (or else the release proc doesn't get
+        // called).
+        idleTexture->resourcePriv().removeScratchKey();
+        context->flush();
+        idleTexture->addIdleProc(idleProc, idleTexture.get(), idleState);
+        idleTexture->setRelease(releaseProc, &isReleased);
+        auto* raw = idleTexture.get();
+        idleTexture.reset();
+        REPORTER_ASSERT(reporter, !isReleased);
+        raw->unref();
+        REPORTER_ASSERT(reporter, isReleased);
+    }
+}
+
+DEF_GPUTEST_FOR_ALL_CONTEXTS(TextureIdleStateTest, reporter, contextInfo) {
+    GrContext* context = contextInfo.grContext();
+    for (const auto& idleMaker : {make_wrapped_texture, make_normal_texture}) {
+        auto idleTexture = idleMaker(context, false);
+
+        uint32_t flags = 0;
+        static constexpr uint32_t kFlushFlag = 0x1;
+        static constexpr uint32_t kFinishFlag = 0x2;
+        auto flushProc = [](void* flags) { *static_cast<uint32_t*>(flags) |= kFlushFlag; };
+        auto finishProc = [](void* flags) { *static_cast<uint32_t*>(flags) |= kFinishFlag; };
+        idleTexture->addIdleProc(flushProc, &flags, GrTexture::IdleState::kFlushed);
+        idleTexture->addIdleProc(finishProc, &flags, GrTexture::IdleState::kFinished);
+
+        // Insert a copy from idleTexture to another texture so that we have some queued IO on
+        // idleTexture.
+        auto proxy = context->priv().proxyProvider()->testingOnly_createWrapped(
+                std::move(idleTexture), kTopLeft_GrSurfaceOrigin);
+        SkImageInfo info = SkImageInfo::Make(proxy->width(), proxy->height(),
+                                             kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+        auto rt = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 0, nullptr);
+        auto rtc = rt->getCanvas()->internal_private_accessTopLayerRenderTargetContext();
+        context->flush();
+        rtc->copy(proxy.get());
+        proxy.reset();
+        REPORTER_ASSERT(reporter, flags == 0);
+
+        // After a flush we expect idleTexture to have reached the kFlushed state on all backends.
+        // On "managed" backends we expect it to reach kFinished as well. On Vulkan, the only
+        // current "unmanaged" backend, we *may* need a sync to reach kFinished.
+        context->flush();
+        if (contextInfo.backend() == kVulkan_GrBackend) {
+            REPORTER_ASSERT(reporter, flags & kFlushFlag);
+        } else {
+            REPORTER_ASSERT(reporter, flags == (kFlushFlag | kFinishFlag));
+        }
+        context->priv().getGpu()->testingOnly_flushGpuAndSync();
+        REPORTER_ASSERT(reporter, flags == (kFlushFlag | kFinishFlag));
+    }
+}
diff --git a/tests/GrTextureMipMapInvalidationTest.cpp b/tests/GrTextureMipMapInvalidationTest.cpp
index b8ccc5a..d651e35 100644
--- a/tests/GrTextureMipMapInvalidationTest.cpp
+++ b/tests/GrTextureMipMapInvalidationTest.cpp
@@ -14,7 +14,12 @@
 #include "Test.h"
 
 // Tests that MIP maps are created and invalidated as expected when drawing to and from GrTextures.
-DEF_GPUTEST_FOR_NULLGL_CONTEXT(GrTextureMipMapInvalidationTest, reporter, ctxInfo) {
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrTextureMipMapInvalidationTest, reporter, ctxInfo) {
+    GrContext* context = ctxInfo.grContext();
+    if (!context->priv().caps()->mipMapSupport()) {
+        return;
+    }
+
     auto isMipped = [] (SkSurface* surf) {
         const GrTexture* texture = surf->makeImageSnapshot()->getTexture();
         return GrMipMapped::kYes == texture->texturePriv().mipMapped();
@@ -24,7 +29,6 @@
         return surf->makeImageSnapshot()->getTexture()->texturePriv().mipMapsAreDirty();
     };
 
-    GrContext* context = ctxInfo.grContext();
     auto info = SkImageInfo::MakeN32Premul(256, 256);
     for (auto allocateMips : {false, true}) {
         auto surf1 = SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info, 0,
@@ -33,7 +37,7 @@
         auto surf2 = SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info);
         // Draw something just in case we ever had a solid color optimization
         surf1->getCanvas()->drawCircle(128, 128, 50, SkPaint());
-        surf1->getCanvas()->flush();
+        surf1->flush();
 
         // No mipmaps initially
         REPORTER_ASSERT(reporter, isMipped(surf1.get()) == allocateMips);
diff --git a/tests/GradientTest.cpp b/tests/GradientTest.cpp
index cdc75e7..3bd11b1 100644
--- a/tests/GradientTest.cpp
+++ b/tests/GradientTest.cpp
@@ -22,8 +22,7 @@
     const SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
     const SkPoint pts[] = {{ 15, 14.7112684f }, { 0.709064007f, 12.6108112f }};
     SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, 2,
-                                                 SkShader::kClamp_TileMode));
+    paint.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp));
 
     SkBitmap bm;
     bm.allocN32Pixels(2000, 1);
@@ -45,7 +44,7 @@
     const SkScalar* fPos;
     const SkPoint*  fPoint;   // 2
     const SkScalar* fRadius; // 2
-    SkShader::TileMode fTileMode;
+    SkTileMode      fTileMode;
 
     void gradCheck(skiatest::Reporter* reporter, const sk_sp<SkShader>& shader,
                    SkShader::GradientInfo* info,
@@ -63,13 +62,13 @@
                         !memcmp(info->fColors, fColors, fColorCount * sizeof(SkColor)));
         REPORTER_ASSERT(reporter,
                         !memcmp(info->fColorOffsets, fPos, fColorCount * sizeof(SkScalar)));
-        REPORTER_ASSERT(reporter, fTileMode == info->fTileMode);
+        REPORTER_ASSERT(reporter, fTileMode == (SkTileMode)info->fTileMode);
     }
 };
 
 
 static void none_gradproc(skiatest::Reporter* reporter, const GradRec&, const GradRec&) {
-    sk_sp<SkShader> s(SkShader::MakeEmptyShader());
+    sk_sp<SkShader> s(SkShaders::Empty());
     REPORTER_ASSERT(reporter, SkShader::kNone_GradientType == s->asAGradient(nullptr));
 }
 
@@ -143,7 +142,7 @@
     SkColor colors[] = { SK_ColorBLUE, SK_ColorBLUE };
     const SkScalar pos[] = { 0, SK_Scalar1 };
     SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 2, SkShader::kClamp_TileMode));
+    paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 2, SkTileMode::kClamp));
     SkBitmap outBitmap;
     outBitmap.allocN32Pixels(10, 1);
     SkCanvas canvas(outBitmap);
@@ -173,7 +172,7 @@
     rec.fPos = gPos;
     rec.fPoint = gPts;
     rec.fRadius = gRad;
-    rec.fTileMode = SkShader::kClamp_TileMode;
+    rec.fTileMode = SkTileMode::kClamp;
 
     static const GradProc gProcs[] = {
         none_gradproc,
@@ -239,13 +238,13 @@
         { gC_0011, gP_0011, 4, gC_0011, gP_0011, 4, false },
     };
 
-    const SkShader::TileMode modes[] = {
-        SkShader::kClamp_TileMode, SkShader::kRepeat_TileMode, SkShader::kMirror_TileMode,
+    const SkTileMode modes[] = {
+        SkTileMode::kClamp, SkTileMode::kRepeat, SkTileMode::kMirror,
         // TODO: add kDecal_TileMode when it is implemented
     };
     for (size_t i = 0; i < SK_ARRAY_COUNT(gProcInfo); ++i) {
         for (auto mode : modes) {
-            if (gProcInfo[i].fIsClampRestricted && mode != SkShader::kClamp_TileMode) {
+            if (gProcInfo[i].fIsClampRestricted && mode != SkTileMode::kClamp) {
                 continue;
             }
 
@@ -254,12 +253,12 @@
                 rec.fColorCount = gTests[t].fCount;
                 rec.fColors     = gTests[t].fCol;
                 rec.fPos        = gTests[t].fPos;
-                rec.fTileMode   = static_cast<SkShader::TileMode>(mode);
+                rec.fTileMode   = mode;
                 rec.fPoint      = gPts;
                 rec.fRadius     = gRadii;
 
                 GradRec expected = rec;
-                if (!gTests[t].fRequiresNonClamp || mode != SkShader::kClamp_TileMode) {
+                if (!gTests[t].fRequiresNonClamp || mode != SkTileMode::kClamp) {
                     expected.fColorCount = gTests[t].fExpectedCount;
                     expected.fColors     = gTests[t].fExpectedCol;
                     expected.fPos        = gTests[t].fExpectedPos;
@@ -278,7 +277,7 @@
     const SkColor colors[] = { SK_ColorBLACK, SK_ColorWHITE };
     const SkScalar pos[] = { 0, 1 };
     SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 2, SkShader::kClamp_TileMode));
+    paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 2, SkTileMode::kClamp));
 
     surface->getCanvas()->drawPaint(paint);
 }
@@ -290,7 +289,7 @@
     const SkColor colors[] = { SK_ColorBLACK, SK_ColorWHITE };
     const SkScalar pos[] = { 0, 1 };
     SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 2, SkShader::kClamp_TileMode));
+    paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 2, SkTileMode::kClamp));
 
     surface->getCanvas()->drawPaint(paint);
 }
@@ -307,7 +306,7 @@
     const SkScalar pos[] = {0, 0.200000003f, 0.800000012f, 1 };
 
     SkPaint paint;
-    paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 4, SkShader::kClamp_TileMode));
+    paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 4, SkTileMode::kClamp));
 
     SkRect r = {0, 83, 1254, 620};
     surface->getCanvas()->drawRect(r, paint);
@@ -324,7 +323,7 @@
     p.setShader(SkGradientShader::MakeTwoPointConical(
         SkPoint::Make(2.5f, 2.5f), 0,
         SkPoint::Make(3.0f, 3.0f), 10,
-        colors, nullptr, SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode));
+        colors, nullptr, SK_ARRAY_COUNT(colors), SkTileMode::kClamp));
     surface->getCanvas()->drawPaint(p);
 
     // r == 0 for the center pixel.
@@ -340,14 +339,14 @@
     const SkColor colors[] = { SK_ColorRED, SK_ColorGREEN };
     const SkPoint pts1[] = { SkPoint::Make(1001, 1000001), SkPoint::Make(1000.99f, 1000000) };
 
-    p.setShader(SkGradientShader::MakeLinear(pts1, colors, nullptr, 2, SkShader::kClamp_TileMode));
+    p.setShader(SkGradientShader::MakeLinear(pts1, colors, nullptr, 2, SkTileMode::kClamp));
 
     sk_sp<SkSurface> surface(SkSurface::MakeRasterN32Premul(50, 50));
     surface->getCanvas()->scale(100, 100);
     surface->getCanvas()->drawPaint(p);
 
     const SkPoint pts2[] = { SkPoint::Make(10000.99f, 1000000), SkPoint::Make(10001, 1000001) };
-    p.setShader(SkGradientShader::MakeLinear(pts2, colors, nullptr, 2, SkShader::kClamp_TileMode));
+    p.setShader(SkGradientShader::MakeLinear(pts2, colors, nullptr, 2, SkTileMode::kClamp));
     surface->getCanvas()->drawPaint(p);
 
     // Passes if we don't trigger asserts.
@@ -362,7 +361,7 @@
         SkPoint::Make(SK_ScalarMax, 0)
     };
 
-    p.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode));
+    p.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp));
     sk_sp<SkSurface> surface(SkSurface::MakeRasterN32Premul(50, 50));
     surface->getCanvas()->drawPaint(p);
 
@@ -402,7 +401,7 @@
         const SkColor*     fColors;
         const SkScalar*    fPos;
         int                fCount;
-        SkShader::TileMode fTileMode;
+        SkTileMode         fTileMode;
         uint32_t           fFlags;
         const SkScalar*    fLocalMatrix;
         const SkScalar*    fGlobalMatrix;
@@ -412,7 +411,7 @@
             gColors0,
             nullptr,
             SK_ARRAY_COUNT(gColors0),
-            SkShader::kClamp_TileMode,
+            SkTileMode::kClamp,
             0,
             gMatrix0,
             nullptr
@@ -422,7 +421,7 @@
             gColors1,
             gPos1,
             SK_ARRAY_COUNT(gColors1),
-            SkShader::kClamp_TileMode,
+            SkTileMode::kClamp,
             0,
             nullptr,
             gMatrix1
@@ -432,7 +431,7 @@
             gColors1,
             gPos1,
             SK_ARRAY_COUNT(gColors1),
-            SkShader::kClamp_TileMode,
+            SkTileMode::kClamp,
             0,
             nullptr,
             gMatrix2
@@ -442,7 +441,7 @@
             gColors0,
             nullptr,
             SK_ARRAY_COUNT(gColors0),
-            SkShader::kClamp_TileMode,
+            SkTileMode::kClamp,
             0,
             gMatrix3,
             nullptr
diff --git a/tests/ImageFilterCacheTest.cpp b/tests/ImageFilterCacheTest.cpp
index 4d1f0f6..a21538f 100644
--- a/tests/ImageFilterCacheTest.cpp
+++ b/tests/ImageFilterCacheTest.cpp
@@ -28,7 +28,7 @@
 }
 
 static sk_sp<SkImageFilter> make_filter() {
-    sk_sp<SkColorFilter> filter(SkColorFilter::MakeModeFilter(SK_ColorBLUE,
+    sk_sp<SkColorFilter> filter(SkColorFilters::Blend(SK_ColorBLUE,
                                                               SkBlendMode::kSrcIn));
     return SkColorFilterImageFilter::Make(std::move(filter), nullptr, nullptr);
 }
@@ -203,7 +203,8 @@
     SkBitmap srcBM = create_bm();
     sk_sp<SkImage> srcImage(SkImage::MakeFromBitmap(srcBM));
     return proxyProvider->createTextureProxy(srcImage, kNone_GrSurfaceFlags, 1,
-                                             SkBudgeted::kYes, SkBackingFit::kExact);
+                                             SkBudgeted::kYes, SkBackingFit::kExact,
+                                             GrInternalSurfaceFlags::kNoPendingIO);
 }
 
 DEF_GPUTEST_FOR_RENDERING_CONTEXTS(ImageFilterCache_ImageBackedGPU, reporter, ctxInfo) {
diff --git a/tests/ImageFilterTest.cpp b/tests/ImageFilterTest.cpp
index 65c1f23..b40f17e 100644
--- a/tests/ImageFilterTest.cpp
+++ b/tests/ImageFilterTest.cpp
@@ -5,13 +5,13 @@
  * found in the LICENSE file.
  */
 
+#include "Resources.h"
 #include "SkArithmeticImageFilter.h"
 #include "SkBitmap.h"
 #include "SkBlurImageFilter.h"
 #include "SkCanvas.h"
 #include "SkColorFilterImageFilter.h"
 #include "SkColorMatrixFilter.h"
-#include "SkColorSpaceXformer.h"
 #include "SkComposeImageFilter.h"
 #include "SkDisplacementMapEffect.h"
 #include "SkDropShadowImageFilter.h"
@@ -38,9 +38,8 @@
 #include "SkTableColorFilter.h"
 #include "SkTileImageFilter.h"
 #include "SkXfermodeImageFilter.h"
-#include "Resources.h"
 #include "Test.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 #include "GrCaps.h"
 #include "GrContext.h"
@@ -64,9 +63,6 @@
         offset->fX = offset->fY = 0;
         return sk_ref_sp<SkSpecialImage>(source);
     }
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override {
-        return sk_ref_sp(const_cast<MatrixTestImageFilter*>(this));
-    }
 
     void flatten(SkWriteBuffer& buffer) const override {
         SkDEBUGFAIL("Should never get here");
@@ -96,9 +92,6 @@
                                         SkIPoint* offset) const override {
         return nullptr;
     }
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override {
-        return nullptr;
-    }
 
     SK_FLATTENABLE_HOOKS(FailImageFilter)
 
@@ -121,7 +114,7 @@
     colors[1] = SK_ColorBLACK;
     sk_sp<SkShader> shader(
         SkGradientShader::MakeRadial(SkPoint::Make(x, y), radius, colors, nullptr, 2,
-                                       SkShader::kClamp_TileMode)
+                                       SkTileMode::kClamp)
     );
     SkPaint paint;
     paint.setShader(shader);
@@ -142,7 +135,7 @@
         SkPoint3 location = SkPoint3::Make(0, 0, SK_Scalar1);
         const SkScalar five = SkIntToScalar(5);
         {
-            sk_sp<SkColorFilter> cf(SkColorFilter::MakeModeFilter(SK_ColorRED,
+            sk_sp<SkColorFilter> cf(SkColorFilters::Blend(SK_ColorRED,
                                                                   SkBlendMode::kSrcIn));
 
             this->addFilter("color filter",
@@ -193,7 +186,7 @@
 
         {
             SkPaint greenColorShaderPaint;
-            greenColorShaderPaint.setShader(SkShader::MakeColorShader(SK_ColorGREEN));
+            greenColorShaderPaint.setShader(SkShaders::Color(SK_ColorGREEN));
 
             SkImageFilter::CropRect leftSideCropRect(SkRect::MakeXYWH(0, 0, 32, 64));
             sk_sp<SkImageFilter> paintFilterLeft(SkPaintImageFilter::Make(greenColorShaderPaint,
@@ -295,7 +288,6 @@
                                         SkIPoint* offset) const override {
         return nullptr;
     }
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override { return nullptr; }
 
     SkIRect onFilterBounds(const SkIRect&, const SkMatrix&,
                            MapDirection, const SkIRect*) const override {
@@ -352,7 +344,7 @@
                             0, s, 0, 0, 0,
                             0, 0, s, 0, 0,
                             0, 0, 0, s, 0 };
-    sk_sp<SkColorFilter> filter(SkColorFilter::MakeMatrixFilterRowMajor255(matrix));
+    sk_sp<SkColorFilter> filter(SkColorFilters::MatrixRowMajor255(matrix));
     return SkColorFilterImageFilter::Make(std::move(filter), std::move(input));
 }
 
@@ -364,14 +356,13 @@
     matrix[1] = matrix[6] = matrix[11] = 0.7152f;
     matrix[2] = matrix[7] = matrix[12] = 0.0722f;
     matrix[18] = 1.0f;
-    sk_sp<SkColorFilter> filter(SkColorFilter::MakeMatrixFilterRowMajor255(matrix));
+    sk_sp<SkColorFilter> filter(SkColorFilters::MatrixRowMajor255(matrix));
     return SkColorFilterImageFilter::Make(std::move(filter), std::move(input), cropRect);
 }
 
 static sk_sp<SkImageFilter> make_blue(sk_sp<SkImageFilter> input,
                                       const SkImageFilter::CropRect* cropRect) {
-    sk_sp<SkColorFilter> filter(SkColorFilter::MakeModeFilter(SK_ColorBLUE,
-                                                              SkBlendMode::kSrcIn));
+    sk_sp<SkColorFilter> filter(SkColorFilters::Blend(SK_ColorBLUE, SkBlendMode::kSrcIn));
     return SkColorFilterImageFilter::Make(std::move(filter), std::move(input), cropRect);
 }
 
@@ -414,25 +405,13 @@
 
 DEF_TEST(ImageFilter, reporter) {
     {
-        // Check that two non-clipping color-matrice-filters concatenate into a single filter.
-        sk_sp<SkImageFilter> halfBrightness(make_scale(0.5f, nullptr));
-        sk_sp<SkImageFilter> quarterBrightness(make_scale(0.5f, std::move(halfBrightness)));
-        REPORTER_ASSERT(reporter, nullptr == quarterBrightness->getInput(0));
-        SkColorFilter* cf;
-        REPORTER_ASSERT(reporter, quarterBrightness->asColorFilter(&cf));
-        REPORTER_ASSERT(reporter, cf->asColorMatrix(nullptr));
-        cf->unref();
-    }
-
-    {
-        // Check that a clipping color-matrice-filter followed by a color-matrice-filters
-        // concatenates into a single filter, but not a matrixfilter (due to clamping).
+        // Check that a color matrix filter followed by a color matrix filter
+        // concatenates into a single filter.
         sk_sp<SkImageFilter> doubleBrightness(make_scale(2.0f, nullptr));
         sk_sp<SkImageFilter> halfBrightness(make_scale(0.5f, std::move(doubleBrightness)));
         REPORTER_ASSERT(reporter, nullptr == halfBrightness->getInput(0));
         SkColorFilter* cf;
         REPORTER_ASSERT(reporter, halfBrightness->asColorFilter(&cf));
-        REPORTER_ASSERT(reporter, !cf->asColorMatrix(nullptr));
         cf->unref();
     }
 
@@ -479,10 +458,10 @@
         blueToRedMatrix[2] = blueToRedMatrix[18] = SK_Scalar1;
         SkScalar redToGreenMatrix[20] = { 0 };
         redToGreenMatrix[5] = redToGreenMatrix[18] = SK_Scalar1;
-        sk_sp<SkColorFilter> blueToRed(SkColorFilter::MakeMatrixFilterRowMajor255(blueToRedMatrix));
+        sk_sp<SkColorFilter> blueToRed(SkColorFilters::MatrixRowMajor255(blueToRedMatrix));
         sk_sp<SkImageFilter> filter1(SkColorFilterImageFilter::Make(std::move(blueToRed),
                                                                     nullptr));
-        sk_sp<SkColorFilter> redToGreen(SkColorFilter::MakeMatrixFilterRowMajor255(redToGreenMatrix));
+        sk_sp<SkColorFilter> redToGreen(SkColorFilters::MatrixRowMajor255(redToGreenMatrix));
         sk_sp<SkImageFilter> filter2(SkColorFilterImageFilter::Make(std::move(redToGreen),
                                                                     std::move(filter1)));
 
@@ -686,7 +665,7 @@
     sk_sp<SkSpecialImage> source(create_empty_special_image(context, 5));
     SkImageFilter::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
     SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeXYWH(0, 0, 1, 1), nullptr, noColorSpace);
-    sk_sp<SkColorFilter> green(SkColorFilter::MakeModeFilter(SK_ColorGREEN, SkBlendMode::kSrc));
+    sk_sp<SkColorFilter> green(SkColorFilters::Blend(SK_ColorGREEN, SkBlendMode::kSrc));
     SkASSERT(green->affectsTransparentBlack());
     sk_sp<SkImageFilter> greenFilter(SkColorFilterImageFilter::Make(std::move(green),
                                                                     std::move(failFilter)));
@@ -725,7 +704,7 @@
 
     SkPaint textPaint;
     textPaint.setColor(SK_ColorWHITE);
-    SkFont font(sk_tool_utils::create_portable_typeface(), height);
+    SkFont font(ToolUtils::create_portable_typeface(), height);
 
     const char* text = "ABC";
     const SkScalar yPos = SkIntToScalar(height);
@@ -741,7 +720,6 @@
             untiledCanvas.scale(SkIntToScalar(scale), SkIntToScalar(scale));
             untiledCanvas.drawString(text, 0, yPos, font, combinedPaint);
             untiledCanvas.restore();
-            untiledCanvas.flush();
 
             tiledCanvas.clear(SK_ColorTRANSPARENT);
             for (int y = 0; y < height; y += tileSize) {
@@ -763,9 +741,8 @@
                     tiledCanvas.restore();
                 }
             }
-            tiledCanvas.flush();
 
-            if (!sk_tool_utils::equal_pixels(untiledResult, tiledResult)) {
+            if (!ToolUtils::equal_pixels(untiledResult, tiledResult)) {
                 REPORTER_ASSERT(reporter, false, filters.getName(i));
                 break;
             }
@@ -779,7 +756,7 @@
     SkMatrix matrix;
     matrix.setTranslate(SkIntToScalar(50), 0);
 
-    sk_sp<SkColorFilter> cf(SkColorFilter::MakeModeFilter(SK_ColorWHITE, SkBlendMode::kSrc));
+    sk_sp<SkColorFilter> cf(SkColorFilters::Blend(SK_ColorWHITE, SkBlendMode::kSrc));
     sk_sp<SkImageFilter> cfif(SkColorFilterImageFilter::Make(std::move(cf), nullptr));
     sk_sp<SkImageFilter> imageFilter(SkImageFilter::MakeMatrixFilter(matrix,
                                                                      kNone_SkFilterQuality,
@@ -1244,7 +1221,7 @@
     SkRTreeFactory factory;
     SkPictureRecorder recorder;
 
-    sk_sp<SkColorFilter> green(SkColorFilter::MakeModeFilter(SK_ColorGREEN,
+    sk_sp<SkColorFilter> green(SkColorFilters::Blend(SK_ColorGREEN,
                                                              SkBlendMode::kSrc));
     sk_sp<SkImageFilter> imageFilter(SkColorFilterImageFilter::Make(green, nullptr));
     SkPaint imageFilterPaint;
@@ -1371,8 +1348,7 @@
     bitmap.allocN32Pixels(1, 1);
     bitmap.eraseARGB(255, 255, 255, 255);
 
-    sk_sp<SkColorFilter> green(SkColorFilter::MakeModeFilter(SK_ColorGREEN,
-                                                             SkBlendMode::kSrcIn));
+    sk_sp<SkColorFilter> green(SkColorFilters::Blend(SK_ColorGREEN, SkBlendMode::kSrcIn));
     sk_sp<SkImageFilter> greenFilter(SkColorFilterImageFilter::Make(green, nullptr));
     SkImageFilter::CropRect cropRect(SkRect::MakeEmpty());
     sk_sp<SkImageFilter> croppedOut(SkColorFilterImageFilter::Make(green, nullptr, &cropRect));
@@ -1585,7 +1561,7 @@
                                      0, 0, 0, 0, 1,
                                      0, 0, 0, 0, 0,
                                      0, 0, 0, 0, 1 };
-        sk_sp<SkColorFilter> greenCF(SkColorFilter::MakeMatrixFilterRowMajor255(greenMatrix));
+        sk_sp<SkColorFilter> greenCF(SkColorFilters::MatrixRowMajor255(greenMatrix));
         sk_sp<SkImageFilter> green(SkColorFilterImageFilter::Make(greenCF, nullptr));
 
         REPORTER_ASSERT(reporter, greenCF->affectsTransparentBlack());
@@ -1796,7 +1772,7 @@
  */
 DEF_TEST(ImageFilterComplexCTM, reporter) {
     // just need a colorfilter to exercise the corresponding imagefilter
-    sk_sp<SkColorFilter> cf = SkColorFilter::MakeModeFilter(SK_ColorRED, SkBlendMode::kSrcATop);
+    sk_sp<SkColorFilter> cf = SkColorFilters::Blend(SK_ColorRED, SkBlendMode::kSrcATop);
     sk_sp<SkImageFilter> cfif = SkColorFilterImageFilter::Make(cf, nullptr);    // can handle
     sk_sp<SkImageFilter> blif = SkBlurImageFilter::Make(3, 3, nullptr);         // cannot handle
 
@@ -1822,48 +1798,6 @@
     }
 }
 
-// Test that transforming the filter DAG doesn't clone shared nodes multiple times.
-DEF_TEST(ImageFilterColorSpaceDAG, reporter) {
-
-    // Helper for counting makeColorSpace() clones.
-    class TestFilter final : public SkImageFilter {
-    public:
-        TestFilter() : INHERITED(nullptr, 0, nullptr) {}
-
-        Factory getFactory() const override { return nullptr; }
-        const char* getTypeName() const override { return nullptr; }
-
-        size_t cloneCount() const { return fCloneCount; }
-
-    protected:
-        sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* src, const Context&,
-                                            SkIPoint* offset) const override {
-            return nullptr;
-        }
-        sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override {
-            fCloneCount++;
-            return sk_ref_sp(const_cast<TestFilter*>(this));
-        }
-
-    private:
-        typedef SkImageFilter INHERITED;
-
-        mutable size_t fCloneCount = 0;
-    };
-
-    auto filter = sk_make_sp<TestFilter>();
-    REPORTER_ASSERT(reporter, filter->cloneCount() == 0u);
-
-    // Build a DAG referencing the filter twice.
-    auto complexFilter = SkMergeImageFilter::Make(filter, SkOffsetImageFilter::Make(1, 1, filter));
-    REPORTER_ASSERT(reporter, filter->cloneCount() == 0u);
-
-    auto xformer = SkColorSpaceXformer::Make(SkColorSpace::MakeSRGB());
-    auto xformedFilter = xformer->apply(complexFilter.get());
-
-    REPORTER_ASSERT(reporter, filter->cloneCount() == 1u);
-}
-
 // Test SkXfermodeImageFilter::filterBounds with different blending modes.
 DEF_TEST(XfermodeImageFilterBounds, reporter) {
     SkIRect background_rect = SkIRect::MakeXYWH(0, 0, 100, 100);
diff --git a/tests/ImageNewShaderTest.cpp b/tests/ImageNewShaderTest.cpp
index 092a422..d96a59f 100644
--- a/tests/ImageNewShaderTest.cpp
+++ b/tests/ImageNewShaderTest.cpp
@@ -42,8 +42,8 @@
 
     sk_sp<SkImage> sourceImage(sourceSurface->makeImageSnapshot());
     sk_sp<SkShader> sourceShader = sourceImage->makeShader(
-            SkShader::kRepeat_TileMode,
-            SkShader::kRepeat_TileMode);
+            SkTileMode::kRepeat,
+            SkTileMode::kRepeat);
 
     SkPaint paint;
     paint.setShader(sourceShader);
@@ -68,8 +68,8 @@
     matrix.setTranslate(SkIntToScalar(-1), SkIntToScalar(0));
 
     sk_sp<SkShader> sourceShaderTranslated = sourceImage->makeShader(
-            SkShader::kRepeat_TileMode,
-            SkShader::kRepeat_TileMode,
+            SkTileMode::kRepeat,
+            SkTileMode::kRepeat,
             &matrix);
 
     destinationCanvas->clear(SK_ColorTRANSPARENT);
diff --git a/tests/ImageTest.cpp b/tests/ImageTest.cpp
index b4e072b..f7fa7af 100644
--- a/tests/ImageTest.cpp
+++ b/tests/ImageTest.cpp
@@ -29,8 +29,7 @@
 #include "Test.h"
 
 #include "Resources.h"
-#include "sk_pixel_iter.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 #include "GrContextPriv.h"
 #include "GrContextThreadSafeProxy.h"
@@ -42,7 +41,7 @@
 using namespace sk_gpu_test;
 
 SkImageInfo read_pixels_info(SkImage* image) {
-    if (as_IB(image)->onImageInfo().colorSpace()) {
+    if (image->colorSpace()) {
         return SkImageInfo::MakeS32(image->width(), image->height(), image->alphaType());
     }
 
@@ -908,7 +907,7 @@
             sk_sp<SkImage> refImg(imageMaker(ctx));
 
             canvas->drawImage(refImg, 0, 0);
-            canvas->flush();
+            surface->flush();
 
             refImg.reset(nullptr); // force a release of the image
         }
@@ -920,7 +919,7 @@
             canvas->drawImage(refImg, 0, 0);
             refImg.reset(nullptr); // force a release of the image
 
-            canvas->flush();
+            surface->flush();
         }
 
         // Configure second context
@@ -945,7 +944,7 @@
 
             otherTestContext->makeCurrent();
             canvas->drawImage(refImg, 0, 0);
-            canvas->flush();
+            surface->flush();
 
             testContext->makeCurrent();
             refImg.reset(nullptr); // force a release of the image
@@ -963,7 +962,7 @@
             refImg.reset(nullptr); // force a release of the image
 
             otherTestContext->makeCurrent();
-            canvas->flush();
+            surface->flush();
 
             // This is specifically here for vulkan to guarantee the command buffer will finish
             // which is when we call the ReleaseProc.
@@ -1365,7 +1364,7 @@
             surf->getCanvas()->drawImageRect(img, dst, &paint);
 
             // we should draw nothing
-            sk_tool_utils::PixelIter iter(surf.get());
+            ToolUtils::PixelIter iter(surf.get());
             while (void* addr = iter.next()) {
                 REPORTER_ASSERT(reporter, *(SkPMColor*)addr == 0);
             }
diff --git a/tests/IncrTopoSortTest.cpp b/tests/IncrTopoSortTest.cpp
index 849c20a..a950785 100644
--- a/tests/IncrTopoSortTest.cpp
+++ b/tests/IncrTopoSortTest.cpp
@@ -9,7 +9,7 @@
 #include "SkTSort.h"
 #include "Test.h"
 
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 // A node in the graph. This corresponds to an opList in the MDB world.
 class Node : public SkRefCnt {
diff --git a/tests/LazyProxyTest.cpp b/tests/LazyProxyTest.cpp
index 8c582ad..6dcfb2b 100644
--- a/tests/LazyProxyTest.cpp
+++ b/tests/LazyProxyTest.cpp
@@ -12,6 +12,7 @@
 #include "GrMemoryPool.h"
 #include "GrOnFlushResourceProvider.h"
 #include "GrProxyProvider.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrRenderTargetContextPriv.h"
 #include "GrSurfaceProxy.h"
@@ -19,6 +20,7 @@
 #include "GrTexture.h"
 #include "GrTextureProxy.h"
 #include "GrTextureProxyPriv.h"
+#include "SkExchange.h"
 #include "SkMakeUnique.h"
 #include "SkRectPriv.h"
 #include "mock/GrMockGpu.h"
@@ -55,7 +57,7 @@
     public:
         DEFINE_OP_CLASS_ID
 
-        static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+        static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                               GrProxyProvider* proxyProvider,
                                               LazyProxyTest* test,
                                               bool nullTexture) {
@@ -76,27 +78,27 @@
     private:
         friend class GrOpMemoryPool; // for ctor
 
-        Op(GrContext* ctx, GrProxyProvider* proxyProvider, LazyProxyTest* test, bool nullTexture)
+        Op(GrRecordingContext* ctx, GrProxyProvider* proxyProvider,
+           LazyProxyTest* test, bool nullTexture)
                     : GrDrawOp(ClassID()), fTest(test) {
             const GrBackendFormat format =
                     ctx->priv().caps()->getBackendFormatFromColorType(kRGB_565_SkColorType);
             fProxy = GrProxyProvider::MakeFullyLazyProxy(
-                    [this, nullTexture](GrResourceProvider* rp) {
-                        if (!rp) {
-                            return sk_sp<GrTexture>();
-                        }
+                    [this, nullTexture](
+                            GrResourceProvider* rp) -> GrSurfaceProxy::LazyInstantiationResult {
                         REPORTER_ASSERT(fTest->fReporter, !fTest->fHasOpTexture);
                         fTest->fHasOpTexture = true;
                         if (nullTexture) {
-                            return sk_sp<GrTexture>();
+                            return {};
                         } else {
                             GrSurfaceDesc desc;
                             desc.fWidth = 1234;
                             desc.fHeight = 567;
                             desc.fConfig = kRGB_565_GrPixelConfig;
-                            sk_sp<GrTexture> texture = rp->createTexture(desc, SkBudgeted::kYes);
+                            sk_sp<GrTexture> texture = rp->createTexture(
+                                desc, SkBudgeted::kYes, GrResourceProvider::Flags::kNoPendingIO);
                             REPORTER_ASSERT(fTest->fReporter, texture);
-                            return texture;
+                            return std::move(texture);
                         }
                     },
                     format, GrProxyProvider::Renderable::kNo, kTopLeft_GrSurfaceOrigin,
@@ -108,7 +110,8 @@
 
         const char* name() const override { return "LazyProxyTest::Op"; }
         FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
-        GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*) override {
+        GrProcessorSet::Analysis finalize(
+                const GrCaps&, const GrAppliedClip*, GrFSAAType, GrClampType) override {
             return GrProcessorSet::EmptySetAnalysis();
         }
         void onPrepare(GrOpFlushState*) override {}
@@ -119,7 +122,7 @@
 
     class ClipFP : public GrFragmentProcessor {
     public:
-        ClipFP(GrContext* ctx, GrProxyProvider* proxyProvider, LazyProxyTest* test,
+        ClipFP(GrRecordingContext* ctx, GrProxyProvider* proxyProvider, LazyProxyTest* test,
                GrTextureProxy* atlas)
                 : GrFragmentProcessor(kTestFP_ClassID, kNone_OptimizationFlags)
                 , fContext(ctx)
@@ -130,19 +133,14 @@
                 ctx->priv().caps()->getBackendFormatFromGrColorType(GrColorType::kAlpha_F16,
                                                                     GrSRGBEncoded::kNo);
             fLazyProxy = GrProxyProvider::MakeFullyLazyProxy(
-                                [this](GrResourceProvider* rp) {
-                                    if (!rp) {
-                                        return sk_sp<GrTexture>();
-                                    }
-                                    REPORTER_ASSERT(fTest->fReporter, !fTest->fHasClipTexture);
-                                    fTest->fHasClipTexture = true;
-                                    fAtlas->instantiate(rp);
-                                    return sk_ref_sp(fAtlas->peekTexture());
-                                },
-                                format,
-                                GrProxyProvider::Renderable::kYes,
-                                kBottomLeft_GrSurfaceOrigin,
-                                kAlpha_half_GrPixelConfig, *proxyProvider->caps());
+                    [this](GrResourceProvider* rp) -> GrSurfaceProxy::LazyInstantiationResult {
+                        REPORTER_ASSERT(fTest->fReporter, !fTest->fHasClipTexture);
+                        fTest->fHasClipTexture = true;
+                        fAtlas->instantiate(rp);
+                        return sk_ref_sp(fAtlas->peekTexture());
+                    },
+                    format, GrProxyProvider::Renderable::kYes, kBottomLeft_GrSurfaceOrigin,
+                    kAlpha_half_GrPixelConfig, *proxyProvider->caps());
             fAccess.reset(fLazyProxy, GrSamplerState::Filter::kNearest,
                           GrSamplerState::WrapMode::kClamp);
             this->setTextureSamplerCnt(1);
@@ -158,7 +156,7 @@
         bool onIsEqual(const GrFragmentProcessor&) const override { return false; }
         const TextureSampler& onTextureSampler(int) const override { return fAccess; }
 
-        GrContext* const fContext;
+        GrRecordingContext* const fContext;
         GrProxyProvider* const fProxyProvider;
         LazyProxyTest* const fTest;
         GrTextureProxy* const fAtlas;
@@ -174,8 +172,8 @@
                 , fAtlas(atlas) {}
 
     private:
-        bool apply(GrContext* context, GrRenderTargetContext*, bool, bool, GrAppliedClip* out,
-                   SkRect* bounds) const override {
+        bool apply(GrRecordingContext* context, GrRenderTargetContext*, bool useHWAA,
+                   bool hasUserStencilSettings, GrAppliedClip* out, SkRect* bounds) const override {
             GrProxyProvider* proxyProvider = context->priv().proxyProvider();
             out->addCoverageFP(skstd::make_unique<ClipFP>(context, proxyProvider, fTest, fAtlas));
             return true;
@@ -244,23 +242,39 @@
             ctx->priv().caps()->getBackendFormatFromColorType(kRGBA_8888_SkColorType);
 
     using LazyInstantiationType = GrSurfaceProxy::LazyInstantiationType;
+    using LazyInstantiationResult = GrSurfaceProxy::LazyInstantiationResult;
     for (bool doInstantiate : {true, false}) {
         for (auto lazyType : {LazyInstantiationType::kSingleUse,
                               LazyInstantiationType::kMultipleUse,
                               LazyInstantiationType::kDeinstantiate}) {
             int testCount = 0;
-            int* testCountPtr = &testCount;
+            // Sets an integer to 1 when the callback is called and -1 when it is deleted.
+            class TestCallback {
+            public:
+                TestCallback(int* value) : fValue(value) {}
+                TestCallback(const TestCallback& that) { SkASSERT(0); }
+                TestCallback(TestCallback&& that) : fValue(that.fValue) { that.fValue = nullptr; }
+
+                ~TestCallback() { fValue ? (void)(*fValue = -1) : void(); }
+
+                TestCallback& operator=(TestCallback&& that) {
+                    fValue = skstd::exchange(that.fValue, nullptr);
+                    return *this;
+                }
+                TestCallback& operator=(const TestCallback& that) = delete;
+
+                LazyInstantiationResult operator()(GrResourceProvider* resourceProvider) const {
+                    *fValue = 1;
+                    return {};
+                }
+
+            private:
+                int* fValue = nullptr;
+            };
             sk_sp<GrTextureProxy> proxy = proxyProvider->createLazyProxy(
-                    [testCountPtr](GrResourceProvider* resourceProvider) {
-                        if (!resourceProvider) {
-                            *testCountPtr = -1;
-                            return sk_sp<GrTexture>();
-                        }
-                        *testCountPtr = 1;
-                        return sk_sp<GrTexture>();
-                    },
-                    format, desc, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo,
-                    GrInternalSurfaceFlags::kNone, SkBackingFit::kExact, SkBudgeted::kNo, lazyType);
+                    TestCallback(&testCount), format, desc, kTopLeft_GrSurfaceOrigin,
+                    GrMipMapped::kNo, GrInternalSurfaceFlags::kNone, SkBackingFit::kExact,
+                    SkBudgeted::kNo, lazyType);
 
             REPORTER_ASSERT(reporter, proxy.get());
             REPORTER_ASSERT(reporter, 0 == testCount);
@@ -318,18 +332,18 @@
                 ctx->priv().caps()->getBackendFormatFromColorType(kRGBA_8888_SkColorType);
 
         fLazyProxy = proxyProvider->createLazyProxy(
-                [testExecuteValue, shouldFailInstantiation, desc](GrResourceProvider* rp) {
-                    if (!rp) {
-                        return sk_sp<GrTexture>();
-                    }
+                [testExecuteValue, shouldFailInstantiation,
+                 desc](GrResourceProvider* rp) -> GrSurfaceProxy::LazyInstantiationResult {
                     if (shouldFailInstantiation) {
                         *testExecuteValue = 1;
-                        return sk_sp<GrTexture>();
+                        return {};
                     }
-                    return rp->createTexture(desc, SkBudgeted::kNo);
+                    return {rp->createTexture(desc, SkBudgeted::kNo,
+                                              GrResourceProvider::Flags::kNoPendingIO),
+                            GrSurfaceProxy::LazyInstantiationKeyMode::kUnsynced};
                 },
-                format, desc, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo,
-                SkBackingFit::kExact, SkBudgeted::kNo);
+                format, desc, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo, SkBackingFit::kExact,
+                SkBudgeted::kNo);
 
         SkASSERT(fLazyProxy.get());
 
@@ -339,7 +353,8 @@
 
     const char* name() const override { return "LazyFailedInstantiationTestOp"; }
     FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
-    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*) override {
+    GrProcessorSet::Analysis finalize(
+            const GrCaps&, const GrAppliedClip*, GrFSAAType, GrClampType) override {
         return GrProcessorSet::EmptySetAnalysis();
     }
     void onPrepare(GrOpFlushState*) override {}
@@ -414,7 +429,8 @@
 
     const char* name() const override { return "LazyDeinstantiateTestOp"; }
     FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
-    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*) override {
+    GrProcessorSet::Analysis finalize(
+            const GrCaps&, const GrAppliedClip*, GrFSAAType, GrClampType) override {
         return GrProcessorSet::EmptySetAnalysis();
     }
     void onPrepare(GrOpFlushState*) override {}
@@ -461,20 +477,17 @@
                 nullptr, kSize, kSize, GrColorType::kRGBA_8888, false, GrMipMapped::kNo);
 
         sk_sp<GrTextureProxy> lazyProxy = proxyProvider->createLazyProxy(
-                [instantiatePtr, releasePtr, backendTex](GrResourceProvider* rp) {
-                    if (!rp) {
-                        return sk_sp<GrTexture>();
-                    }
-
+                [instantiatePtr, releasePtr,
+                 backendTex](GrResourceProvider* rp) -> GrSurfaceProxy::LazyInstantiationResult {
                     sk_sp<GrTexture> texture =
                             rp->wrapBackendTexture(backendTex, kBorrow_GrWrapOwnership,
                                                    GrWrapCacheable::kNo, kRead_GrIOType);
                     if (!texture) {
-                        return sk_sp<GrTexture>();
+                        return {};
                     }
                     (*instantiatePtr)++;
                     texture->setRelease(DeinstantiateReleaseProc, releasePtr);
-                    return texture;
+                    return std::move(texture);
                 },
                 format, desc, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo,
                 GrInternalSurfaceFlags::kReadOnly, SkBackingFit::kExact, SkBudgeted::kNo, lazyType);
diff --git a/tests/MD5Test.cpp b/tests/MD5Test.cpp
index 214a7a8..9e08738 100644
--- a/tests/MD5Test.cpp
+++ b/tests/MD5Test.cpp
@@ -24,8 +24,7 @@
     {
         SkMD5 context;
         context.write(string, len);
-        SkMD5::Digest digest;
-        context.finish(digest);
+        SkMD5::Digest digest = context.finish();
 
         REPORTER_ASSERT(reporter, digests_equal(expectedDigest, digest));
     }
@@ -38,8 +37,7 @@
         for (; data < end; ++data) {
             context.write(data, 1);
         }
-        SkMD5::Digest digest;
-        context.finish(digest);
+        SkMD5::Digest digest = context.finish();
 
         REPORTER_ASSERT(reporter, digests_equal(expectedDigest, digest));
     }
diff --git a/tests/MathTest.cpp b/tests/MathTest.cpp
index 2d41118..89e25b0 100644
--- a/tests/MathTest.cpp
+++ b/tests/MathTest.cpp
@@ -729,3 +729,38 @@
     }
 
 #endif
+
+DEF_TEST(unit_floats, r) {
+    // pick a non-trivial, non-pow-2 value, to test the loop
+    float v[13];
+    constexpr int N = SK_ARRAY_COUNT(v);
+
+    // empty array reports true
+    REPORTER_ASSERT(r, sk_floats_are_unit(v, 0));
+
+    SkRandom rand;
+    for (int outer = 0; outer < 1000; ++outer) {
+        // check some good values
+        for (int i = 0; i < N; ++i) {
+            v[i] = rand.nextUScalar1();
+        }
+        const int index = rand.nextU() % N;
+
+        REPORTER_ASSERT(r, sk_floats_are_unit(v, N));
+        v[index] = -0.f;
+        REPORTER_ASSERT(r, sk_floats_are_unit(v, N));
+        v[index] = 1.0f;
+        REPORTER_ASSERT(r, sk_floats_are_unit(v, N));
+
+        // check some bad values
+        const float non_norms[] = {
+            1.0000001f, 2, SK_ScalarInfinity, SK_ScalarNaN
+        };
+        for (float bad : non_norms) {
+            v[index] = bad;
+            REPORTER_ASSERT(r, !sk_floats_are_unit(v, N));
+            v[index] = -bad;
+            REPORTER_ASSERT(r, !sk_floats_are_unit(v, N));
+        }
+    }
+}
diff --git a/tests/MatrixClipCollapseTest.cpp b/tests/MatrixClipCollapseTest.cpp
index de6af362..3d2db4e 100644
--- a/tests/MatrixClipCollapseTest.cpp
+++ b/tests/MatrixClipCollapseTest.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "Test.h"
+#include "DebugCanvas.h"
 #include "SkCanvas.h"
-#include "SkDebugCanvas.h"
 #include "SkPicture.h"
 #include "SkPictureFlat.h"
 #include "SkPictureRecord.h"
+#include "Test.h"
 
 // This test exercises the Matrix/Clip State collapsing system. It generates
 // example skps and the compares the actual stored operations to the expected
@@ -59,7 +59,7 @@
 
 // Extract the command ops from the input SkPicture
 static void gets_ops(SkPicture& input, SkTDArray<DrawType>* ops) {
-    SkDebugCanvas debugCanvas(input.width(), input.height());
+    DebugCanvas debugCanvas(input.width(), input.height());
     debugCanvas.setBounds(input.width(), input.height());
     input.draw(&debugCanvas);
 
@@ -643,13 +643,13 @@
 
     for (int i = 0; i < max; ++i) {
         if (i < expected.count()) {
-            SkDebugf("%16s,    ", SkDrawCommand::GetCommandString(expected[i]));
+            SkDebugf("%16s,    ", DrawCommand::GetCommandString(expected[i]));
         } else {
             SkDebugf("%16s,    ", " ");
         }
 
         if (i < actual.count()) {
-            SkDebugf("%s\n", SkDrawCommand::GetCommandString(actual[i]));
+            SkDebugf("%s\n", DrawCommand::GetCommandString(actual[i]));
         } else {
             SkDebugf("\n");
         }
diff --git a/tests/OnFlushCallbackTest.cpp b/tests/OnFlushCallbackTest.cpp
index 7830c4a..3c97842 100644
--- a/tests/OnFlushCallbackTest.cpp
+++ b/tests/OnFlushCallbackTest.cpp
@@ -20,7 +20,7 @@
 
 #include "SkBitmap.h"
 #include "SkPointPriv.h"
-#include "effects/GrSimpleTextureEffect.h"
+#include "effects/generated/GrSimpleTextureEffect.h"
 #include "ops/GrSimpleMeshDrawOpHelper.h"
 
 namespace {
@@ -34,14 +34,14 @@
     DEFINE_OP_CLASS_ID
 
     // This creates an instance of a simple non-AA solid color rect-drawing Op
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const SkRect& r) {
         return Helper::FactoryHelper<NonAARectOp>(context, std::move(paint), r, nullptr, ClassID());
     }
 
     // This creates an instance of a simple non-AA textured rect-drawing Op
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+    static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           const SkRect& r,
                                           const SkRect& local) {
@@ -72,14 +72,15 @@
 
     FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip*) override {
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip*,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
         // Set the color to unknown because the subclass may change the color later.
         GrProcessorAnalysisColor gpColor;
         gpColor.setToUnknown();
         // We ignore the clip so pass this rather than the GrAppliedClip param.
         static GrAppliedClip kNoClip;
-        return fHelper.finalizeProcessors(caps, &kNoClip, GrProcessorAnalysisCoverage::kNone,
-                                          &gpColor);
+        return fHelper.finalizeProcessors(
+                caps, &kNoClip, fsaaType, clampType, GrProcessorAnalysisCoverage::kNone, &gpColor);
     }
 
 protected:
@@ -158,8 +159,11 @@
         mesh->setIndexed(indexBuffer, 6, firstIndex, 0, 3, GrPrimitiveRestart::kNo);
         mesh->setVertexData(vertexBuffer, firstVertex);
 
-        auto pipe = fHelper.makePipeline(target);
-        target->draw(std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
+        target->recordDraw(std::move(gp), mesh);
+    }
+
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        fHelper.executeDrawsAndUploads(this, flushState, chainBounds);
     }
 
     Helper fHelper;
@@ -300,11 +304,8 @@
         const GrBackendFormat format = caps->getBackendFormatFromColorType(kRGBA_8888_SkColorType);
 
         fAtlasProxy = GrProxyProvider::MakeFullyLazyProxy(
-                [](GrResourceProvider* resourceProvider) {
-                    if (!resourceProvider) {
-                        return sk_sp<GrTexture>();
-                    }
-
+                [](GrResourceProvider* resourceProvider)
+                        -> GrSurfaceProxy::LazyInstantiationResult {
                     GrSurfaceDesc desc;
                     desc.fFlags = kRenderTarget_GrSurfaceFlag;
                     // TODO: until partial flushes in MDB lands we're stuck having
@@ -313,8 +314,9 @@
                     desc.fHeight = kAtlasTileSize;
                     desc.fConfig = kRGBA_8888_GrPixelConfig;
 
-                    return resourceProvider->createTexture(desc, SkBudgeted::kYes,
-                                                           GrResourceProvider::Flags::kNoPendingIO);
+                    auto texture = resourceProvider->createTexture(
+                            desc, SkBudgeted::kYes, GrResourceProvider::Flags::kNoPendingIO);
+                    return std::move(texture);
                 },
                 format,
                 GrProxyProvider::Renderable::kYes,
@@ -468,12 +470,12 @@
 // Enable this if you want to debug the final draws w/o having the atlasCallback create the
 // atlas
 #if 0
-#include "SkImageEncoder.h"
 #include "SkGrPriv.h"
-#include "sk_tool_utils.h"
+#include "SkImageEncoder.h"
+#include "ToolUtils.h"
 
 static void save_bm(const SkBitmap& bm, const char name[]) {
-    bool result = sk_tool_utils::EncodeImageToFile(name, bm, SkEncodedImageFormat::kPNG, 100);
+    bool result = ToolUtils::EncodeImageToFile(name, bm, SkEncodedImageFormat::kPNG, 100);
     SkASSERT(result);
 }
 
@@ -578,7 +580,8 @@
         rtc->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), r);
     }
 
-    rtc->prepareForExternalIO(0, nullptr);
+    rtc->prepareForExternalIO(SkSurface::BackendSurfaceAccess::kNoAccess,
+                              kNone_GrFlushFlags, 0, nullptr);
 
     SkBitmap readBack;
     readBack.allocN32Pixels(kFinalWidth, kFinalHeight);
diff --git a/tests/OpChainTest.cpp b/tests/OpChainTest.cpp
index 0b35873..27c7dbe 100644
--- a/tests/OpChainTest.cpp
+++ b/tests/OpChainTest.cpp
@@ -173,7 +173,7 @@
 
     auto proxy = context->priv().proxyProvider()->createProxy(
             format, desc, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo, SkBackingFit::kExact,
-            SkBudgeted::kNo, GrInternalSurfaceFlags::kNone);
+            SkBudgeted::kNo, GrInternalSurfaceFlags::kNoPendingIO);
     SkASSERT(proxy);
     proxy->instantiate(context->priv().resourceProvider());
     int result[result_width()];
@@ -203,12 +203,13 @@
                 init_combinable(g, &combinable, &random);
                 GrTokenTracker tracker;
                 GrOpFlushState flushState(context->priv().getGpu(),
-                                          context->priv().resourceProvider(), &tracker,
-                                          nullptr, nullptr);
+                                          context->priv().resourceProvider(),
+                                          context->priv().getResourceCache(),
+                                          &tracker);
                 GrRenderTargetOpList opList(context->priv().resourceProvider(),
                                             sk_ref_sp(context->priv().opMemoryPool()),
                                             proxy->asRenderTargetProxy(),
-                                            context->priv().getAuditTrail());
+                                            context->priv().auditTrail());
                 // This assumes the particular values of kRanges.
                 std::fill_n(result, result_width(), -1);
                 std::fill_n(validResult, result_width(), -1);
diff --git a/tests/PDFDocumentTest.cpp b/tests/PDFDocumentTest.cpp
index 95821e8..c3d6ed2 100644
--- a/tests/PDFDocumentTest.cpp
+++ b/tests/PDFDocumentTest.cpp
@@ -14,7 +14,7 @@
 #include "SkPDFDocument.h"
 #include "SkStream.h"
 
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 static void test_empty(skiatest::Reporter* reporter) {
     SkDynamicMemoryWStream stream;
diff --git a/tests/PDFJpegEmbedTest.cpp b/tests/PDFJpegEmbedTest.cpp
index 6c9a481..055f457 100644
--- a/tests/PDFJpegEmbedTest.cpp
+++ b/tests/PDFJpegEmbedTest.cpp
@@ -65,7 +65,6 @@
     sk_sp<SkImage> im2(SkImage::MakeFromEncoded(cmykData));
     canvas->drawImage(im2.get(), 0.0, 512.0, nullptr);
 
-    canvas->flush();
     document->endPage();
     document->close();
     sk_sp<SkData> pdfData = pdf.detachAsData();
diff --git a/tests/PDFPrimitivesTest.cpp b/tests/PDFPrimitivesTest.cpp
index f7ea80a..42804b9 100644
--- a/tests/PDFPrimitivesTest.cpp
+++ b/tests/PDFPrimitivesTest.cpp
@@ -32,7 +32,7 @@
 #include "SkStream.h"
 #include "SkTo.h"
 #include "SkTypes.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 #include <cstdlib>
 #include <cmath>
@@ -260,9 +260,6 @@
         offset->fX = offset->fY = 0;
         return sk_ref_sp<SkSpecialImage>(source);
     }
-    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override {
-        return sk_ref_sp(const_cast<DummyImageFilter*>(this));
-    }
 
 private:
     SK_FLATTENABLE_HOOKS(DummyImageFilter)
@@ -314,8 +311,7 @@
         REPORTER_ASSERT(reporter,
                         !SkPDFFont::CanEmbedTypeface(noEmbedTypeface.get(), &doc));
     }
-    sk_sp<SkTypeface> portableTypeface(
-            sk_tool_utils::create_portable_typeface(nullptr, SkFontStyle()));
+    sk_sp<SkTypeface> portableTypeface(ToolUtils::create_portable_typeface(nullptr, SkFontStyle()));
     REPORTER_ASSERT(reporter,
                     SkPDFFont::CanEmbedTypeface(portableTypeface.get(), &doc));
 }
diff --git a/tests/PackedConfigsTextureTest.cpp b/tests/PackedConfigsTextureTest.cpp
index 8983465..d66be09 100644
--- a/tests/PackedConfigsTextureTest.cpp
+++ b/tests/PackedConfigsTextureTest.cpp
@@ -138,9 +138,13 @@
 static const int CONTROL_ARRAY_SIZE = DEV_W * DEV_H;
 
 DEF_GPUTEST_FOR_RENDERING_CONTEXTS(RGBA4444TextureTest, reporter, ctxInfo) {
-    run_test(reporter, ctxInfo.grContext(), CONTROL_ARRAY_SIZE, kARGB_4444_SkColorType);
+    if (ctxInfo.grContext()->colorTypeSupportedAsImage(kARGB_4444_SkColorType)) {
+        run_test(reporter, ctxInfo.grContext(), CONTROL_ARRAY_SIZE, kARGB_4444_SkColorType);
+    }
 }
 
 DEF_GPUTEST_FOR_RENDERING_CONTEXTS(RGB565TextureTest, reporter, ctxInfo) {
-    run_test(reporter, ctxInfo.grContext(), CONTROL_ARRAY_SIZE, kRGB_565_SkColorType);
+    if (ctxInfo.grContext()->colorTypeSupportedAsImage(kRGB_565_SkColorType)) {
+        run_test(reporter, ctxInfo.grContext(), CONTROL_ARRAY_SIZE, kRGB_565_SkColorType);
+    }
 }
diff --git a/tests/PaintImageFilterTest.cpp b/tests/PaintImageFilterTest.cpp
index 7ca7bc3..b9035a3 100644
--- a/tests/PaintImageFilterTest.cpp
+++ b/tests/PaintImageFilterTest.cpp
@@ -33,7 +33,7 @@
 
     SkPaint gradientPaint;
     gradientPaint.setShader(SkGradientShader::MakeRadial(
-        center, radius, colors, pos, SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode));
+        center, radius, colors, pos, SK_ARRAY_COUNT(colors), SkTileMode::kClamp));
 
     // Test using the image filter
     {
@@ -79,7 +79,7 @@
 
     SkPaint gradientPaint;
     gradientPaint.setShader(SkGradientShader::MakeRadial(
-        center, radius, colors, pos, SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode));
+        center, radius, colors, pos, SK_ARRAY_COUNT(colors), SkTileMode::kClamp));
 
     // Test using the image filter
     {
diff --git a/tests/PaintTest.cpp b/tests/PaintTest.cpp
index 762cf1f..f22aaf5 100644
--- a/tests/PaintTest.cpp
+++ b/tests/PaintTest.cpp
@@ -320,11 +320,11 @@
 
     SkColorMatrix cm;
     cm.setIdentity();   // does not change alpha
-    paint.setColorFilter(SkColorFilter::MakeMatrixFilterRowMajor255(cm.fMat));
+    paint.setColorFilter(SkColorFilters::MatrixRowMajor255(cm.fMat));
     REPORTER_ASSERT(r, paint.nothingToDraw());
 
     cm.postTranslate(0, 0, 0, 1);    // wacks alpha
-    paint.setColorFilter(SkColorFilter::MakeMatrixFilterRowMajor255(cm.fMat));
+    paint.setColorFilter(SkColorFilters::MatrixRowMajor255(cm.fMat));
     REPORTER_ASSERT(r, !paint.nothingToDraw());
 }
 
diff --git a/tests/PathMeasureTest.cpp b/tests/PathMeasureTest.cpp
index c944f86..166fe44 100644
--- a/tests/PathMeasureTest.cpp
+++ b/tests/PathMeasureTest.cpp
@@ -265,6 +265,20 @@
     REPORTER_ASSERT(reporter, !fact.next());
 }
 
+static void test_MLM_contours(skiatest::Reporter* reporter) {
+    SkPath path;
+
+    // This odd sequence (with a trailing moveTo) used to return a 2nd contour, which is
+    // wrong, since the contract for a measure is to only return non-zero length contours.
+    path.moveTo(10, 10).lineTo(20, 20).moveTo(30, 30);
+
+    for (bool forceClosed : {false, true}) {
+        SkContourMeasureIter fact(path, forceClosed);
+        REPORTER_ASSERT(reporter, fact.next());
+        REPORTER_ASSERT(reporter, !fact.next());
+    }
+}
+
 DEF_TEST(contour_measure, reporter) {
     SkPath path;
     path.addCircle(0, 0, 100);
@@ -290,4 +304,5 @@
     REPORTER_ASSERT(reporter, !cm2);
 
     test_empty_contours(reporter);
+    test_MLM_contours(reporter);
 }
diff --git a/tests/PathOpsBuilderConicTest.cpp b/tests/PathOpsBuilderConicTest.cpp
index e6a6447..297dc02 100644
--- a/tests/PathOpsBuilderConicTest.cpp
+++ b/tests/PathOpsBuilderConicTest.cpp
@@ -88,14 +88,15 @@
     testOne(reporter, set);
 }
 
-#include "SkCommandLineFlags.h"
+#include "CommandLineFlags.h"
 
-DEFINE_int32(processOffset, 0, "Offset the test by this value. This permits multiple processes "
-                          "to exercise the same test in parallel with different test values.");
-DEFINE_int32(processCount, 1, "Test iteration count. This permits multiple "
-                          "processes "
-                          "to exercise the same test in parallel with different test values.");
-DEFINE_int32(trialRuns, 100, "Run this many tests (defaults to 100).");
+static DEFINE_int(processOffset, 0,
+                    "Offset the test by this value. This permits multiple processes "
+                    "to exercise the same test in parallel with different test values.");
+static DEFINE_int(processCount, 1,
+                    "Test iteration count. This permits multiple processes "
+                    "to exercise the same test in parallel with different test values.");
+static DEFINE_int(trialRuns, 100, "Run this many tests (defaults to 100).");
 
 DEF_TEST(SixtyOvals, reporter) {
     bool skipOneOffs = false;
diff --git a/tests/PathOpsConicIntersectionTest.cpp b/tests/PathOpsConicIntersectionTest.cpp
index 1de1583..7389556 100644
--- a/tests/PathOpsConicIntersectionTest.cpp
+++ b/tests/PathOpsConicIntersectionTest.cpp
@@ -110,8 +110,7 @@
     canvas.drawPath(path, paint);
     SkString filename("c:\\Users\\caryclark\\Documents\\");
     filename.appendf("%s.png", name);
-    sk_tool_utils::EncodeImageToFile(filename.c_str(), bitmap,
-            SkEncodedImageFormat::kPNG, 100);
+    ToolUtils::EncodeImageToFile(filename.c_str(), bitmap, SkEncodedImageFormat::kPNG, 100);
 }
 
 static void writeDPng(const SkDConic& dC, const char* name) {
@@ -152,8 +151,7 @@
     canvas.drawPath(path, paint);
     SkString filename("c:\\Users\\caryclark\\Documents\\");
     filename.appendf("%s.png", name);
-    sk_tool_utils::EncodeImageToFile(filename.c_str(), bitmap,
-            SkEncodedImageFormat::kPNG, 100);
+    ToolUtils::EncodeImageToFile(filename.c_str(), bitmap, SkEncodedImageFormat::kPNG, 100);
 }
 #endif
 
@@ -290,7 +288,7 @@
         }
         SkString filename("c:\\Users\\caryclark\\Documents\\");
         filename.appendf("f%d.png", index);
-        sk_tool_utils::EncodeImageToFile(filename.c_str(), bitmap, SkEncodedImageFormat::kPNG, 100);
+        ToolUtils::EncodeImageToFile(filename.c_str(), bitmap, SkEncodedImageFormat::kPNG, 100);
     }
 }
 #endif
diff --git a/tests/PathRendererCacheTests.cpp b/tests/PathRendererCacheTests.cpp
index 505e5e3..b02517c 100644
--- a/tests/PathRendererCacheTests.cpp
+++ b/tests/PathRendererCacheTests.cpp
@@ -28,11 +28,13 @@
     return path;
 }
 
+using AATypeFlags = GrPathRenderer::AATypeFlags;
+
 static void draw_path(GrContext* ctx,
                       GrRenderTargetContext* renderTargetContext,
                       const SkPath& path,
                       GrPathRenderer* pr,
-                      GrAAType aaType,
+                      AATypeFlags aaTypeFlags,
                       const GrStyle& style) {
     GrPaint paint;
     paint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
@@ -53,7 +55,7 @@
                                       &clipConservativeBounds,
                                       &matrix,
                                       &shape,
-                                      aaType,
+                                      aaTypeFlags,
                                       false};
     pr->drawPath(args);
 }
@@ -72,7 +74,7 @@
                       std::function<SkPath(void)> createPath,
                       std::function<GrPathRenderer*(GrContext*)> createPathRenderer,
                       int expected,
-                      GrAAType aaType = GrAAType::kNone,
+                      AATypeFlags aaTypeFlags = AATypeFlags::kNone,
                       GrStyle style = GrStyle(SkStrokeRec::kFill_InitStyle)) {
     sk_sp<GrContext> ctx = GrContext::MakeMock(nullptr);
     // The cache needs to be big enough that nothing gets flushed, or our expectations can be wrong
@@ -96,7 +98,7 @@
     REPORTER_ASSERT(reporter, cache_non_scratch_resources_equals(cache, 0));
 
     // Draw the path, check that new resource count matches expectations
-    draw_path(ctx.get(), rtc.get(), path, pathRenderer.get(), aaType, style);
+    draw_path(ctx.get(), rtc.get(), path, pathRenderer.get(), aaTypeFlags, style);
     ctx->flush();
     REPORTER_ASSERT(reporter, cache_non_scratch_resources_equals(cache, expected));
 
@@ -129,7 +131,8 @@
     paint.setStyle(SkPaint::kStroke_Style);
     paint.setStrokeWidth(1);
     GrStyle style(paint);
-    test_path(reporter, create_concave_path, createPR, kExpectedResources, GrAAType::kNone, style);
+    test_path(
+            reporter, create_concave_path, createPR, kExpectedResources, AATypeFlags::kNone, style);
 }
 
 // Test that deleting the original path invalidates the textures cached by the SW path renderer
@@ -142,7 +145,7 @@
     // only contains a single quad so GrFillRectOp doesn't need to use the shared index buffer.
     const int kExpectedResources = 1;
 
-    test_path(reporter, create_concave_path, createPR, kExpectedResources, GrAAType::kCoverage);
+    test_path(reporter, create_concave_path, createPR, kExpectedResources, AATypeFlags::kCoverage);
 
     // Test with a style that alters the path geometry. This needs to attach the invalidation logic
     // to the original path, not the modified path produced by the style.
@@ -150,6 +153,6 @@
     paint.setStyle(SkPaint::kStroke_Style);
     paint.setStrokeWidth(1);
     GrStyle style(paint);
-    test_path(reporter, create_concave_path, createPR, kExpectedResources, GrAAType::kCoverage,
+    test_path(reporter, create_concave_path, createPR, kExpectedResources, AATypeFlags::kCoverage,
               style);
 }
diff --git a/tests/PathTest.cpp b/tests/PathTest.cpp
index 0668098..8c9148d 100644
--- a/tests/PathTest.cpp
+++ b/tests/PathTest.cpp
@@ -2780,6 +2780,21 @@
         p.transform(matrix, &p1);
         REPORTER_ASSERT(reporter, SkPathPriv::CheapIsFirstDirection(p1, SkPathPriv::kUnknown_FirstDirection));
     }
+
+    {
+        SkPath p1;
+        p1.addRect({ 10, 20, 30, 40 });
+        SkPath p2;
+        p2.addRect({ 10, 20, 30, 40 });
+        uint32_t id1 = p1.getGenerationID();
+        uint32_t id2 = p2.getGenerationID();
+        SkMatrix matrix;
+        matrix.setScale(2, 2);
+        p1.transform(matrix, &p2);
+        p1.transform(matrix);
+        REPORTER_ASSERT(reporter, id1 != p1.getGenerationID());
+        REPORTER_ASSERT(reporter, id2 != p2.getGenerationID());
+    }
 }
 
 static void test_zero_length_paths(skiatest::Reporter* reporter) {
@@ -4411,6 +4426,18 @@
         p.rewind();
         REPORTER_ASSERT(reporter, changed);
 
+        // Check that listener is notified on transform().
+        {
+            SkPath q;
+            q.moveTo(10, 10);
+            SkPathPriv::AddGenIDChangeListener(q, sk_make_sp<ChangeListener>(&changed));
+            REPORTER_ASSERT(reporter, !changed);
+            SkMatrix matrix;
+            matrix.setScale(2, 2);
+            p.transform(matrix, &q);
+            REPORTER_ASSERT(reporter, changed);
+        }
+
         // Check that listener is notified when pathref is deleted.
         {
             SkPath q;
diff --git a/tests/PictureShaderTest.cpp b/tests/PictureShaderTest.cpp
index 742b72d..bdcbc90 100644
--- a/tests/PictureShaderTest.cpp
+++ b/tests/PictureShaderTest.cpp
@@ -13,6 +13,7 @@
 #include "SkSurface.h"
 #include "Test.h"
 
+#ifdef SK_SUPPORT_LEGACY_TILEMODE_ENUM
 // Test that attempting to create a picture shader with a nullptr picture or
 // empty picture returns a shader that draws nothing.
 DEF_TEST(PictureShader_empty, reporter) {
@@ -40,6 +41,7 @@
     canvas.drawRect(SkRect::MakeWH(1,1), paint);
     REPORTER_ASSERT(reporter, *bitmap.getAddr32(0,0) == SK_ColorGREEN);
 }
+#endif
 
 // Test that the SkPictureShader cache is purged on shader deletion.
 DEF_TEST(PictureShader_caching, reporter) {
@@ -56,9 +58,7 @@
 
     {
         SkPaint paint;
-        paint.setShader(SkPictureShader::Make(picture,
-                                              SkShader::kRepeat_TileMode,
-                                              SkShader::kRepeat_TileMode, nullptr, nullptr));
+        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat));
         surface->getCanvas()->drawPaint(paint);
 
         // We should have about 3 refs by now: local + shader + shader cache.
@@ -68,9 +68,7 @@
     // Draw another picture shader to have a chance to purge.
     {
         SkPaint paint;
-        paint.setShader(SkPictureShader::Make(makePicture(),
-                                              SkShader::kRepeat_TileMode,
-                                              SkShader::kRepeat_TileMode, nullptr, nullptr));
+        paint.setShader(makePicture()->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat));
         surface->getCanvas()->drawPaint(paint);
 
     }
diff --git a/tests/PictureTest.cpp b/tests/PictureTest.cpp
index 255e910..068189f 100644
--- a/tests/PictureTest.cpp
+++ b/tests/PictureTest.cpp
@@ -659,7 +659,6 @@
     make_bm(&replayBM, 100, 100, SK_ColorBLACK, false);
     SkCanvas replayCanvas(replayBM);
     picture->playback(&replayCanvas);
-    replayCanvas.flush();
 
     // With the bug present, at (55, 55) we would get a fully opaque red
     // intead of a dark red.
@@ -762,7 +761,7 @@
 DEF_TEST(MiniRecorderLeftHanging, r) {
     // Any shader or other ref-counted effect will do just fine here.
     SkPaint paint;
-    paint.setShader(SkShader::MakeColorShader(SK_ColorRED));
+    paint.setShader(SkShaders::Color(SK_ColorRED));
 
     SkMiniRecorder rec;
     REPORTER_ASSERT(r, rec.drawRect(SkRect::MakeWH(20,30), paint));
diff --git a/tests/PointTest.cpp b/tests/PointTest.cpp
index 4a8ffd9..23729b5 100644
--- a/tests/PointTest.cpp
+++ b/tests/PointTest.cpp
@@ -121,19 +121,6 @@
     REPORTER_ASSERT(reporter, SkScalarNearlyEqual(length, SK_Scalar1));
 }
 
-// test that we handle very small values correctly. i.e. that we can
-// report failure if we try to normalize them.
-static void test_underflow(skiatest::Reporter* reporter) {
-    SkPoint pt = { 1.0e-37f, 1.0e-37f };
-    const SkPoint empty = { 0, 0 };
-
-    REPORTER_ASSERT(reporter, 0 == SkPoint::Normalize(&pt));
-    REPORTER_ASSERT(reporter, pt == empty);
-
-    REPORTER_ASSERT(reporter, !pt.setLength(SK_Scalar1));
-    REPORTER_ASSERT(reporter, pt == empty);
-}
-
 DEF_TEST(Point, reporter) {
     test_casts(reporter);
 
@@ -150,7 +137,6 @@
         test_length(reporter, gRec[i].fX, gRec[i].fY, gRec[i].fLength);
     }
 
-    test_underflow(reporter);
     test_overflow(reporter);
     test_normalize_cannormalize_consistent(reporter);
 }
diff --git a/tests/PolyUtilsTest.cpp b/tests/PolyUtilsTest.cpp
index ba83f1e..837443e 100644
--- a/tests/PolyUtilsTest.cpp
+++ b/tests/PolyUtilsTest.cpp
@@ -201,11 +201,9 @@
         SkScalar rad = 0;
         const SkScalar drad = SK_ScalarPI / n;
         for (int i = 0; i < n; i++) {
-            SkScalar cosV, sinV = SkScalarSinCos(rad, &cosV);
-            *poly.push() = SkPoint::Make(c + cosV * r1, c + sinV * r1);
+            *poly.push() = SkPoint::Make(c + SkScalarCos(rad) * r1, c + SkScalarSin(rad) * r1);
             rad += drad;
-            sinV = SkScalarSinCos(rad, &cosV);
-            *poly.push() = SkPoint::Make(c + cosV * r2, c + sinV * r2);
+            *poly.push() = SkPoint::Make(c + SkScalarCos(rad) * r2, c + SkScalarSin(rad) * r2);
             rad += drad;
         }
         REPORTER_ASSERT(reporter, SkGetPolygonWinding(poly.begin(), poly.count()) > 0);
diff --git a/tests/PremulAlphaRoundTripTest.cpp b/tests/PremulAlphaRoundTripTest.cpp
index bc92bd8..3c26df5 100644
--- a/tests/PremulAlphaRoundTripTest.cpp
+++ b/tests/PremulAlphaRoundTripTest.cpp
@@ -8,7 +8,7 @@
 #include "SkCanvas.h"
 #include "SkSurface.h"
 #include "Test.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 #include "GrContext.h"
 
@@ -74,8 +74,7 @@
         readBmp2.eraseColor(0);
 
         surf->readPixels(readBmp1, 0, 0);
-        sk_tool_utils::write_pixels(surf, readBmp1, 0, 0, gUnpremul[upmaIdx].fColorType,
-                                    kUnpremul_SkAlphaType);
+        surf->writePixels(readBmp1, 0, 0);
         surf->readPixels(readBmp2, 0, 0);
 
         bool success = true;
diff --git a/tests/PrimitiveProcessorTest.cpp b/tests/PrimitiveProcessorTest.cpp
index ba6e148..38b3270 100644
--- a/tests/PrimitiveProcessorTest.cpp
+++ b/tests/PrimitiveProcessorTest.cpp
@@ -42,7 +42,8 @@
         return FixedFunctionFlags::kNone;
     }
 
-    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*) override {
+    GrProcessorSet::Analysis finalize(
+            const GrCaps&, const GrAppliedClip*, GrFSAAType, GrClampType) override {
         return GrProcessorSet::EmptySetAnalysis();
     }
 
@@ -62,8 +63,15 @@
                 fAttributes.reset(new Attribute[numAttribs]);
                 for (auto i = 0; i < numAttribs; ++i) {
                     fAttribNames[i].printf("attr%d", i);
-                    fAttributes[i] = {fAttribNames[i].c_str(), kFloat2_GrVertexAttribType,
-                                                               kFloat2_GrSLType};
+                    // This gives us more of a mix of attribute types, and allows the
+                    // component count to fit within the limits for iOS Metal.
+                    if (i & 0x1) {
+                        fAttributes[i] = {fAttribNames[i].c_str(), kFloat_GrVertexAttribType,
+                                                                   kFloat_GrSLType};
+                    } else {
+                        fAttributes[i] = {fAttribNames[i].c_str(), kFloat2_GrVertexAttribType,
+                                                                   kFloat2_GrSLType};
+                    }
                 }
                 this->setVertexAttributes(fAttributes.get(), numAttribs);
             }
@@ -104,9 +112,12 @@
         QuadHelper helper(target, vertexStride, 1);
         SkPoint* vertices = reinterpret_cast<SkPoint*>(helper.vertices());
         SkPointPriv::SetRectTriStrip(vertices, 0.f, 0.f, 1.f, 1.f, vertexStride);
-        auto pipe = target->makePipeline(0, GrProcessorSet::MakeEmptySet(),
-                                         target->detachAppliedClip());
-        helper.recordDraw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState);
+        helper.recordDraw(target, std::move(gp));
+    }
+
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        flushState->executeDrawsAndUploadsForMeshDrawOp(
+                this, chainBounds, GrProcessorSet::MakeEmptySet());
     }
 
     int fNumAttribs;
diff --git a/tests/ProcessorTest.cpp b/tests/ProcessorTest.cpp
index 6a5ce8c..256f56e 100644
--- a/tests/ProcessorTest.cpp
+++ b/tests/ProcessorTest.cpp
@@ -45,11 +45,13 @@
 
     FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
 
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrFSAAType fsaaType, GrClampType clampType) override {
         static constexpr GrProcessorAnalysisColor kUnknownColor;
         SkPMColor4f overrideColor;
-        return fProcessors.finalize(kUnknownColor, GrProcessorAnalysisCoverage::kNone, clip, false,
-                                    caps, &overrideColor);
+        return fProcessors.finalize(
+                kUnknownColor, GrProcessorAnalysisCoverage::kNone, clip,
+                &GrUserStencilSettings::kUnused, fsaaType, caps, clampType, &overrideColor);
     }
 
 private:
@@ -61,6 +63,7 @@
     }
 
     void onPrepareDraws(Target* target) override { return; }
+    void onExecute(GrOpFlushState*, const SkRect&) override { return; }
 
     GrProcessorSet fProcessors;
 
@@ -77,7 +80,7 @@
         return std::unique_ptr<GrFragmentProcessor>(new TestFP(std::move(child)));
     }
     static std::unique_ptr<GrFragmentProcessor> Make(const SkTArray<sk_sp<GrTextureProxy>>& proxies,
-                                                     const SkTArray<sk_sp<GrBuffer>>& buffers) {
+                                                     const SkTArray<sk_sp<GrGpuBuffer>>& buffers) {
         return std::unique_ptr<GrFragmentProcessor>(new TestFP(proxies, buffers));
     }
 
@@ -93,7 +96,8 @@
     }
 
 private:
-    TestFP(const SkTArray<sk_sp<GrTextureProxy>>& proxies, const SkTArray<sk_sp<GrBuffer>>& buffers)
+    TestFP(const SkTArray<sk_sp<GrTextureProxy>>& proxies,
+           const SkTArray<sk_sp<GrGpuBuffer>>& buffers)
             : INHERITED(kTestFP_ClassID, kNone_OptimizationFlags), fSamplers(4) {
         for (const auto& proxy : proxies) {
             fSamplers.emplace_back(proxy);
@@ -185,7 +189,7 @@
                         SkBudgeted::kYes);
                 {
                     SkTArray<sk_sp<GrTextureProxy>> proxies;
-                    SkTArray<sk_sp<GrBuffer>> buffers;
+                    SkTArray<sk_sp<GrGpuBuffer>> buffers;
                     proxies.push_back(proxy1);
                     auto fp = TestFP::Make(std::move(proxies), std::move(buffers));
                     for (int i = 0; i < parentCnt; ++i) {
@@ -226,10 +230,11 @@
 // This test uses the random GrFragmentProcessor test factory, which relies on static initializers.
 #if SK_ALLOW_STATIC_GLOBAL_INITIALIZERS
 
-#include "SkCommandLineFlags.h"
-DEFINE_bool(randomProcessorTest, false, "Use non-deterministic seed for random processor tests?");
-DEFINE_uint32(processorSeed, 0, "Use specific seed for processor tests. Overridden by " \
-                                "--randomProcessorTest.");
+#include "CommandLineFlags.h"
+static DEFINE_bool(randomProcessorTest, false,
+                   "Use non-deterministic seed for random processor tests?");
+static DEFINE_int(processorSeed, 0,
+                  "Use specific seed for processor tests. Overridden by --randomProcessorTest.");
 
 #if GR_TEST_UTILS
 
@@ -281,7 +286,9 @@
 }
 
 /** Initializes the two test texture proxies that are available to the FP test factories. */
-bool init_test_textures(GrProxyProvider* proxyProvider, SkRandom* random,
+bool init_test_textures(GrResourceProvider* resourceProvider,
+                        GrProxyProvider* proxyProvider,
+                        SkRandom* random,
                         sk_sp<GrTextureProxy> proxies[2]) {
     static const int kTestTextureSize = 256;
 
@@ -300,7 +307,12 @@
         SkPixmap pixmap(ii, rgbaData.get(), ii.minRowBytes());
         sk_sp<SkImage> img = SkImage::MakeRasterCopy(pixmap);
         proxies[0] = proxyProvider->createTextureProxy(img, kNone_GrSurfaceFlags, 1,
-                                                       SkBudgeted::kYes, SkBackingFit::kExact);
+                                                       SkBudgeted::kYes, SkBackingFit::kExact,
+                                                       GrInternalSurfaceFlags::kNoPendingIO);
+
+        if (resourceProvider->explicitlyAllocateGPUResources()) {
+            proxies[0]->instantiate(resourceProvider);
+        }
     }
 
     {
@@ -317,7 +329,12 @@
         SkPixmap pixmap(ii, alphaData.get(), ii.minRowBytes());
         sk_sp<SkImage> img = SkImage::MakeRasterCopy(pixmap);
         proxies[1] = proxyProvider->createTextureProxy(img, kNone_GrSurfaceFlags, 1,
-                                                       SkBudgeted::kYes, SkBackingFit::kExact);
+                                                       SkBudgeted::kYes, SkBackingFit::kExact,
+                                                       GrInternalSurfaceFlags::kNoPendingIO);
+
+        if (resourceProvider->explicitlyAllocateGPUResources()) {
+            proxies[1]->instantiate(resourceProvider);
+        }
     }
 
     return proxies[0] && proxies[1];
@@ -428,6 +445,10 @@
     auto resourceProvider = context->priv().resourceProvider();
     using FPFactory = GrFragmentProcessorTestFactory;
 
+    // This test side-steps the GrResourceAllocator thus violates some assumptions and
+    // asserts
+    bool orig = resourceProvider->testingOnly_setExplicitlyAllocateGPUResources(false);
+
     uint32_t seed = FLAGS_processorSeed;
     if (FLAGS_randomProcessorTest) {
         std::random_device rd;
@@ -447,7 +468,7 @@
             nullptr);
 
     sk_sp<GrTextureProxy> proxies[2];
-    if (!init_test_textures(proxyProvider, &random, proxies)) {
+    if (!init_test_textures(resourceProvider, proxyProvider, &random, proxies)) {
         ERRORF(reporter, "Could not create test textures");
         return;
     }
@@ -667,6 +688,8 @@
             }
         }
     }
+
+    resourceProvider->testingOnly_setExplicitlyAllocateGPUResources(orig);
 }
 
 // Tests that fragment processors returned by GrFragmentProcessor::clone() are equivalent to their
@@ -676,6 +699,10 @@
     GrProxyProvider* proxyProvider = context->priv().proxyProvider();
     auto resourceProvider = context->priv().resourceProvider();
 
+    // This test side-steps the GrResourceAllocator thus violates some assumptions and
+    // asserts
+    bool orig = resourceProvider->testingOnly_setExplicitlyAllocateGPUResources(false);
+
     SkRandom random;
 
     const GrBackendFormat format =
@@ -688,7 +715,7 @@
             nullptr);
 
     sk_sp<GrTextureProxy> proxies[2];
-    if (!init_test_textures(proxyProvider, &random, proxies)) {
+    if (!init_test_textures(resourceProvider, proxyProvider, &random, proxies)) {
         ERRORF(reporter, "Could not create test textures");
         return;
     }
@@ -746,6 +773,8 @@
             }
         }
     }
+
+    resourceProvider->testingOnly_setExplicitlyAllocateGPUResources(orig);
 }
 
 #endif  // GR_TEST_UTILS
diff --git a/tests/PromiseImageTest.cpp b/tests/PromiseImageTest.cpp
index 1c5f5ea..89bcefa 100644
--- a/tests/PromiseImageTest.cpp
+++ b/tests/PromiseImageTest.cpp
@@ -5,7 +5,6 @@
  * found in the LICENSE file.
  */
 
-#include "SkExchange.h"
 #include "Test.h"
 
 #include "GrBackendSurface.h"
@@ -13,6 +12,7 @@
 #include "GrGpu.h"
 #include "GrTexture.h"
 #include "SkImage_Gpu.h"
+#include "SkColorFilter.h"
 #include "SkPromiseImageTexture.h"
 
 using namespace sk_gpu_test;
@@ -34,18 +34,12 @@
     int fFulfillCount;
     int fReleaseCount;
     int fDoneCount;
-    GrBackendTexture fLastFulfilledTexture;
 
     /**
-     * Replaces the backend texture that this checker will return from fulfill. Also, transfers
-     * ownership of the previous PromiseImageTexture to the caller, if they want to control when
-     * it is deleted. The default argument will remove the existing texture without installing a
-     * valid replacement.
+     * Releases the SkPromiseImageTexture. Used to test that cached GrTexture representations
+     * in the cache are freed.
      */
-    sk_sp<const SkPromiseImageTexture> replaceTexture(
-            const GrBackendTexture& tex = GrBackendTexture()) {
-        return skstd::exchange(fTexture, SkPromiseImageTexture::Make(tex));
-    }
+    void releaseTexture() { fTexture.reset(); }
 
     SkTArray<GrUniqueKey> uniqueKeys() const {
         return fTexture->testingOnly_uniqueKeysToInvalidate();
@@ -54,7 +48,6 @@
     static sk_sp<SkPromiseImageTexture> Fulfill(void* self) {
         auto checker = static_cast<PromiseTextureChecker*>(self);
         checker->fFulfillCount++;
-        checker->fLastFulfilledTexture = checker->fTexture->backendTexture();
         return checker->fTexture;
     }
     static void Release(void* self) {
@@ -71,226 +64,103 @@
     }
 };
 
-enum class ReleaseBalanceExpecation {
+enum class ReleaseBalanceExpectation {
     kBalanced,
-    kBalancedOrPlusOne,
-    kAny
+    kAllUnbalanced,
+    kUnbalancedByOne,
 };
 
-static bool check_fulfill_and_release_cnts(const PromiseTextureChecker& promiseChecker,
-                                           ReleaseBalanceExpecation balanceExpecation,
+enum class DoneBalanceExpectation {
+    kBalanced,
+    kAllUnbalanced,
+    kUnknown,
+    kUnbalancedByOne,
+    kBalancedOrOffByOne,
+};
+
+static void check_fulfill_and_release_cnts(skiatest::Reporter* reporter,
+                                           const PromiseTextureChecker& promiseChecker,
                                            int expectedFulfillCnt,
-                                           int expectedReleaseCnt,
-                                           bool expectedRequired,
-                                           int expectedDoneCnt,
-                                           skiatest::Reporter* reporter) {
-    bool result = true;
-    int countDiff = promiseChecker.fFulfillCount - promiseChecker.fReleaseCount;
-    // FulfillCount should always equal ReleaseCount or be at most one higher
-    if (countDiff != 0) {
-        if (balanceExpecation == ReleaseBalanceExpecation::kBalanced) {
-            result = false;
-            REPORTER_ASSERT(reporter, 0 == countDiff);
-        } else if (countDiff != 1 &&
-                   balanceExpecation == ReleaseBalanceExpecation::kBalancedOrPlusOne) {
-            result = false;
-            REPORTER_ASSERT(reporter, 0 == countDiff || 1 == countDiff);
-        } else if (countDiff < 0) {
-            result = false;
-            REPORTER_ASSERT(reporter, countDiff >= 0);
-        }
+                                           ReleaseBalanceExpectation releaseBalanceExpecation,
+                                           DoneBalanceExpectation doneBalanceExpecation) {
+    REPORTER_ASSERT(reporter, promiseChecker.fFulfillCount == expectedFulfillCnt);
+    if (!expectedFulfillCnt) {
+        // Release and Done should only ever be called after Fulfill.
+        REPORTER_ASSERT(reporter, !promiseChecker.fReleaseCount);
+        REPORTER_ASSERT(reporter, !promiseChecker.fDoneCount);
+        return;
     }
-
-    int fulfillDiff = expectedFulfillCnt - promiseChecker.fFulfillCount;
-    REPORTER_ASSERT(reporter, fulfillDiff >= 0);
-    if (fulfillDiff != 0) {
-        if (expectedRequired) {
-            result = false;
-            REPORTER_ASSERT(reporter, expectedFulfillCnt == promiseChecker.fFulfillCount);
-        } else if (fulfillDiff > 1) {
-            result = false;
-            REPORTER_ASSERT(reporter, fulfillDiff <= 1);
-        }
+    int releaseDiff = promiseChecker.fFulfillCount - promiseChecker.fReleaseCount;
+    switch (releaseBalanceExpecation) {
+        case ReleaseBalanceExpectation::kBalanced:
+            REPORTER_ASSERT(reporter, !releaseDiff);
+            break;
+        case ReleaseBalanceExpectation::kAllUnbalanced:
+            REPORTER_ASSERT(reporter, releaseDiff == promiseChecker.fFulfillCount);
+            break;
+        case ReleaseBalanceExpectation::kUnbalancedByOne:
+            REPORTER_ASSERT(reporter, releaseDiff == 1);
+            break;
     }
-
-    int releaseDiff = expectedReleaseCnt - promiseChecker.fReleaseCount;
-    REPORTER_ASSERT(reporter, releaseDiff >= 0);
-    if (releaseDiff != 0) {
-        if (expectedRequired) {
-            result = false;
-            REPORTER_ASSERT(reporter, expectedReleaseCnt == promiseChecker.fReleaseCount);
-        } else if (releaseDiff > 1) {
-            result = false;
-            REPORTER_ASSERT(reporter, releaseDiff <= 1);
-        }
-    }
-
-    if (expectedDoneCnt != promiseChecker.fDoneCount) {
-        result = false;
-        REPORTER_ASSERT(reporter, expectedDoneCnt == promiseChecker.fDoneCount);
-    }
-
-    return result;
-}
-
-DEF_GPUTEST_FOR_RENDERING_CONTEXTS(PromiseImageTestNoDelayedRelease, reporter, ctxInfo) {
-    const int kWidth = 10;
-    const int kHeight = 10;
-
-    GrContext* ctx = ctxInfo.grContext();
-    GrGpu* gpu = ctx->priv().getGpu();
-
-    for (bool releaseImageEarly : {true, false}) {
-        GrBackendTexture backendTex = gpu->createTestingOnlyBackendTexture(
-                nullptr, kWidth, kHeight, GrColorType::kRGBA_8888, true, GrMipMapped::kNo);
-        REPORTER_ASSERT(reporter, backendTex.isValid());
-
-        GrBackendFormat backendFormat = backendTex.getBackendFormat();
-        REPORTER_ASSERT(reporter, backendFormat.isValid());
-
-        PromiseTextureChecker promiseChecker(backendTex, reporter, false);
-        GrSurfaceOrigin texOrigin = kTopLeft_GrSurfaceOrigin;
-        sk_sp<SkImage> refImg(
-                SkImage_Gpu::MakePromiseTexture(
-                        ctx, backendFormat, kWidth, kHeight,
-                        GrMipMapped::kNo, texOrigin,
-                        kRGBA_8888_SkColorType, kPremul_SkAlphaType,
-                        nullptr,
-                        PromiseTextureChecker::Fulfill,
-                        PromiseTextureChecker::Release,
-                        PromiseTextureChecker::Done,
-                        &promiseChecker,
-                        SkDeferredDisplayListRecorder::DelayReleaseCallback::kNo));
-
-        SkImageInfo info = SkImageInfo::MakeN32Premul(kWidth, kHeight);
-        sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info);
-        SkCanvas* canvas = surface->getCanvas();
-
-        int expectedFulfillCnt = 0;
-        int expectedReleaseCnt = 0;
-        int expectedDoneCnt = 0;
-        ReleaseBalanceExpecation balanceExpecation = ReleaseBalanceExpecation::kBalanced;
-
-        canvas->drawImage(refImg, 0, 0);
-        REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                                 balanceExpecation,
-                                                                 expectedFulfillCnt,
-                                                                 expectedReleaseCnt,
-                                                                 true,
-                                                                 expectedDoneCnt,
-                                                                 reporter));
-
-        bool isVulkan = GrBackendApi::kVulkan == ctx->backend();
-        canvas->flush();
-        expectedFulfillCnt++;
-        expectedReleaseCnt++;
-        if (isVulkan) {
-            balanceExpecation = ReleaseBalanceExpecation::kBalancedOrPlusOne;
-        }
-        REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                                 balanceExpecation,
-                                                                 expectedFulfillCnt,
-                                                                 expectedReleaseCnt,
-                                                                 !isVulkan,
-                                                                 expectedDoneCnt,
-                                                                 reporter));
-
-        gpu->testingOnly_flushGpuAndSync();
-        balanceExpecation = ReleaseBalanceExpecation::kBalanced;
-        REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                                 balanceExpecation,
-                                                                 expectedFulfillCnt,
-                                                                 expectedReleaseCnt,
-                                                                 true,
-                                                                 expectedDoneCnt,
-                                                                 reporter));
-
-        canvas->drawImage(refImg, 0, 0);
-        canvas->drawImage(refImg, 0, 0);
-
-        canvas->flush();
-        expectedFulfillCnt++;
-        expectedReleaseCnt++;
-
-        gpu->testingOnly_flushGpuAndSync();
-        REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                                 balanceExpecation,
-                                                                 expectedFulfillCnt,
-                                                                 expectedReleaseCnt,
-                                                                 true,
-                                                                 expectedDoneCnt,
-                                                                 reporter));
-
-        // Now test code path on Vulkan where we released the texture, but the GPU isn't done with
-        // resource yet and we do another draw. We should only call fulfill on the first draw and
-        // use the cached GrBackendTexture on the second. Release should only be called after the
-        // second draw is finished.
-        canvas->drawImage(refImg, 0, 0);
-        canvas->flush();
-        expectedFulfillCnt++;
-        expectedReleaseCnt++;
-        if (isVulkan) {
-            balanceExpecation = ReleaseBalanceExpecation::kBalancedOrPlusOne;
-        }
-        REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                                 balanceExpecation,
-                                                                 expectedFulfillCnt,
-                                                                 expectedReleaseCnt,
-                                                                 !isVulkan,
-                                                                 expectedDoneCnt,
-                                                                 reporter));
-
-        canvas->drawImage(refImg, 0, 0);
-
-        if (releaseImageEarly) {
-            refImg.reset();
-        }
-
-        REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                                 balanceExpecation,
-                                                                 expectedFulfillCnt,
-                                                                 expectedReleaseCnt,
-                                                                 !isVulkan,
-                                                                 expectedDoneCnt,
-                                                                 reporter));
-
-        canvas->flush();
-        expectedFulfillCnt++;
-
-        gpu->testingOnly_flushGpuAndSync();
-        expectedReleaseCnt++;
-        if (releaseImageEarly) {
-            expectedDoneCnt++;
-        }
-        balanceExpecation = ReleaseBalanceExpecation::kBalanced;
-        REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                                 balanceExpecation,
-                                                                 expectedFulfillCnt,
-                                                                 expectedReleaseCnt,
-                                                                 !isVulkan,
-                                                                 expectedDoneCnt,
-                                                                 reporter));
-        expectedFulfillCnt = promiseChecker.fFulfillCount;
-        expectedReleaseCnt = promiseChecker.fReleaseCount;
-
-        if (!releaseImageEarly) {
-            refImg.reset();
-            expectedDoneCnt++;
-        }
-
-        REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                                 balanceExpecation,
-                                                                 expectedFulfillCnt,
-                                                                 expectedReleaseCnt,
-                                                                 true,
-                                                                 expectedDoneCnt,
-                                                                 reporter));
-
-        gpu->deleteTestingOnlyBackendTexture(backendTex);
+    int doneDiff = promiseChecker.fFulfillCount - promiseChecker.fDoneCount;
+    switch (doneBalanceExpecation) {
+        case DoneBalanceExpectation::kBalanced:
+            REPORTER_ASSERT(reporter, !doneDiff);
+            break;
+        case DoneBalanceExpectation::kAllUnbalanced:
+            REPORTER_ASSERT(reporter, doneDiff == promiseChecker.fFulfillCount);
+            break;
+        case DoneBalanceExpectation::kUnknown:
+            REPORTER_ASSERT(reporter, doneDiff >= 0 && doneDiff <= promiseChecker.fFulfillCount);
+            break;
+        case DoneBalanceExpectation::kUnbalancedByOne:
+            REPORTER_ASSERT(reporter, doneDiff == 1);
+            break;
+        case DoneBalanceExpectation::kBalancedOrOffByOne:
+            REPORTER_ASSERT(reporter, doneDiff == 0 || doneDiff == 1);
+            break;
     }
 }
 
-DEF_GPUTEST_FOR_RENDERING_CONTEXTS(PromiseImageTestDelayedRelease, reporter, ctxInfo) {
+static void check_unfulfilled(const PromiseTextureChecker& promiseChecker,
+                              skiatest::Reporter* reporter) {
+    check_fulfill_and_release_cnts(reporter, promiseChecker, 0,
+                                   ReleaseBalanceExpectation::kBalanced,
+                                   DoneBalanceExpectation::kBalanced);
+}
+
+static void check_only_fulfilled(skiatest::Reporter* reporter,
+                                 const PromiseTextureChecker& promiseChecker,
+                                 int expectedFulfillCnt = 1) {
+    check_fulfill_and_release_cnts(reporter, promiseChecker, expectedFulfillCnt,
+                                   ReleaseBalanceExpectation::kAllUnbalanced,
+                                   DoneBalanceExpectation::kAllUnbalanced);
+}
+
+static void check_all_flushed_but_not_synced(skiatest::Reporter* reporter,
+                                             const PromiseTextureChecker& promiseChecker,
+                                             GrBackendApi api,
+                                             int expectedFulfillCnt = 1) {
+    DoneBalanceExpectation doneBalanceExpectation = DoneBalanceExpectation::kBalanced;
+    // On Vulkan Done isn't guaranteed to be called until a sync has occurred.
+    if (api == GrBackendApi::kVulkan) {
+        doneBalanceExpectation = expectedFulfillCnt == 1
+                                         ? DoneBalanceExpectation::kBalancedOrOffByOne
+                                         : DoneBalanceExpectation::kUnknown;
+    }
+    check_fulfill_and_release_cnts(reporter, promiseChecker, expectedFulfillCnt,
+                                   ReleaseBalanceExpectation::kBalanced, doneBalanceExpectation);
+}
+
+static void check_all_done(skiatest::Reporter* reporter,
+                           const PromiseTextureChecker& promiseChecker,
+                           int expectedFulfillCnt = 1) {
+    check_fulfill_and_release_cnts(reporter, promiseChecker, expectedFulfillCnt,
+                                   ReleaseBalanceExpectation::kBalanced,
+                                   DoneBalanceExpectation::kBalanced);
+}
+
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(PromiseImageTest, reporter, ctxInfo) {
     const int kWidth = 10;
     const int kHeight = 10;
 
@@ -316,376 +186,51 @@
                     PromiseTextureChecker::Release,
                     PromiseTextureChecker::Done,
                     &promiseChecker,
-                    SkDeferredDisplayListRecorder::DelayReleaseCallback::kYes));
+                    SkDeferredDisplayListRecorder::PromiseImageApiVersion::kNew));
 
     SkImageInfo info = SkImageInfo::MakeN32Premul(kWidth, kHeight);
     sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info);
     SkCanvas* canvas = surface->getCanvas();
 
-    int expectedFulfillCnt = 0;
-    int expectedReleaseCnt = 0;
-    int expectedDoneCnt = 0;
-    ReleaseBalanceExpecation balanceExpecation = ReleaseBalanceExpecation::kBalanced;
-
     canvas->drawImage(refImg, 0, 0);
-    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                             balanceExpecation,
-                                                             expectedFulfillCnt,
-                                                             expectedReleaseCnt,
-                                                             true,
-                                                             expectedDoneCnt,
-                                                             reporter));
+    check_unfulfilled(promiseChecker, reporter);
 
-    bool isVulkan = GrBackendApi::kVulkan == ctx->backend();
-    canvas->flush();
-    expectedFulfillCnt++;
-    // Because we've delayed release, we expect a +1 balance.
-    balanceExpecation = ReleaseBalanceExpecation::kBalancedOrPlusOne;
-    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                             balanceExpecation,
-                                                             expectedFulfillCnt,
-                                                             expectedReleaseCnt,
-                                                             !isVulkan,
-                                                             expectedDoneCnt,
-                                                             reporter));
+    surface->flush();
+    // We still own the image so we should not have called Release or Done.
+    check_only_fulfilled(reporter, promiseChecker);
 
     gpu->testingOnly_flushGpuAndSync();
-    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                             balanceExpecation,
-                                                             expectedFulfillCnt,
-                                                             expectedReleaseCnt,
-                                                             true,
-                                                             expectedDoneCnt,
-                                                             reporter));
+    check_only_fulfilled(reporter, promiseChecker);
 
     canvas->drawImage(refImg, 0, 0);
     canvas->drawImage(refImg, 0, 0);
 
-    canvas->flush();
+    surface->flush();
 
     gpu->testingOnly_flushGpuAndSync();
-    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                             balanceExpecation,
-                                                             expectedFulfillCnt,
-                                                             expectedReleaseCnt,
-                                                             true,
-                                                             expectedDoneCnt,
-                                                             reporter));
+    // Image should still be fulfilled from the first time we drew/flushed it.
+    check_only_fulfilled(reporter, promiseChecker);
 
     canvas->drawImage(refImg, 0, 0);
-    canvas->flush();
-    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                             balanceExpecation,
-                                                             expectedFulfillCnt,
-                                                             expectedReleaseCnt,
-                                                             !isVulkan,
-                                                             expectedDoneCnt,
-                                                             reporter));
+    surface->flush();
+    check_only_fulfilled(reporter, promiseChecker);
 
     canvas->drawImage(refImg, 0, 0);
-
     refImg.reset();
+    // We no longer own the image but the last draw is still unflushed.
+    check_only_fulfilled(reporter, promiseChecker);
 
-    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                             balanceExpecation,
-                                                             expectedFulfillCnt,
-                                                             expectedReleaseCnt,
-                                                             !isVulkan,
-                                                             expectedDoneCnt,
-                                                             reporter));
-
-    canvas->flush();
+    surface->flush();
+    // Flushing should have called Release. Depending on the backend and timing it may have called
+    // done.
+    check_all_flushed_but_not_synced(reporter, promiseChecker, ctx->backend());
     gpu->testingOnly_flushGpuAndSync();
-    // We released the image already and we flushed and synced.
-    balanceExpecation = ReleaseBalanceExpecation::kBalanced;
-    expectedReleaseCnt++;
-    expectedDoneCnt++;
-    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                             balanceExpecation,
-                                                             expectedFulfillCnt,
-                                                             expectedReleaseCnt,
-                                                             !isVulkan,
-                                                             expectedDoneCnt,
-                                                             reporter));
+    // Now Done should definitely have been called.
+    check_all_done(reporter, promiseChecker);
 
     gpu->deleteTestingOnlyBackendTexture(backendTex);
 }
 
-// Tests replacing the backing texture for a promise image after a release and then refulfilling in
-// the SkDeferredDisplayListRecorder::DelayReleaseCallback::kNo case.
-DEF_GPUTEST_FOR_RENDERING_CONTEXTS(PromiseImageTextureReuse, reporter, ctxInfo) {
-    const int kWidth = 10;
-    const int kHeight = 10;
-
-    GrContext* ctx = ctxInfo.grContext();
-    GrGpu* gpu = ctx->priv().getGpu();
-
-    GrBackendTexture backendTex1 = gpu->createTestingOnlyBackendTexture(
-            nullptr, kWidth, kHeight, GrColorType::kRGBA_8888, false, GrMipMapped::kNo);
-    GrBackendTexture backendTex2 = gpu->createTestingOnlyBackendTexture(
-            nullptr, kWidth, kHeight, GrColorType::kRGBA_8888, false, GrMipMapped::kNo);
-    GrBackendTexture backendTex3 = gpu->createTestingOnlyBackendTexture(
-            nullptr, kWidth, kHeight, GrColorType::kRGBA_8888, false, GrMipMapped::kNo);
-    REPORTER_ASSERT(reporter, backendTex1.isValid());
-    REPORTER_ASSERT(reporter, backendTex2.isValid());
-    REPORTER_ASSERT(reporter, backendTex3.isValid());
-
-    GrBackendFormat backendFormat = backendTex1.getBackendFormat();
-    REPORTER_ASSERT(reporter, backendFormat.isValid());
-    REPORTER_ASSERT(reporter, backendFormat == backendTex2.getBackendFormat());
-    REPORTER_ASSERT(reporter, backendFormat == backendTex3.getBackendFormat());
-
-    PromiseTextureChecker promiseChecker(backendTex1, reporter, true);
-    GrSurfaceOrigin texOrigin = kTopLeft_GrSurfaceOrigin;
-    sk_sp<SkImage> refImg(
-            SkImage_Gpu::MakePromiseTexture(
-                    ctx, backendFormat, kWidth, kHeight,
-                    GrMipMapped::kNo, texOrigin,
-                    kRGBA_8888_SkColorType, kPremul_SkAlphaType,
-                    nullptr,
-                    PromiseTextureChecker::Fulfill,
-                    PromiseTextureChecker::Release,
-                    PromiseTextureChecker::Done,
-                    &promiseChecker,
-                    SkDeferredDisplayListRecorder::DelayReleaseCallback::kNo));
-
-    SkImageInfo info =
-            SkImageInfo::Make(kWidth, kHeight, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
-    sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info);
-    SkCanvas* canvas = surface->getCanvas();
-
-    int expectedFulfillCnt = 0;
-    int expectedReleaseCnt = 0;
-    int expectedDoneCnt = 0;
-
-    canvas->drawImage(refImg, 0, 0);
-    canvas->drawImage(refImg, 5, 5);
-    ReleaseBalanceExpecation balanceExpecation = ReleaseBalanceExpecation::kBalanced;
-    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                             balanceExpecation,
-                                                             expectedFulfillCnt,
-                                                             expectedReleaseCnt,
-                                                             true,
-                                                             expectedDoneCnt,
-                                                             reporter));
-
-    bool isVulkan = GrBackendApi::kVulkan == ctx->backend();
-    canvas->flush();
-    expectedFulfillCnt++;
-    expectedReleaseCnt++;
-    if (isVulkan) {
-        balanceExpecation = ReleaseBalanceExpecation::kBalancedOrPlusOne;
-    }
-    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                             balanceExpecation,
-                                                             expectedFulfillCnt,
-                                                             expectedReleaseCnt,
-                                                             !isVulkan,
-                                                             expectedDoneCnt,
-                                                             reporter));
-    REPORTER_ASSERT(reporter, GrBackendTexture::TestingOnly_Equals(
-                                      promiseChecker.fLastFulfilledTexture, backendTex1));
-    // We should have put a GrTexture for this fulfillment into the cache.
-    auto keys = promiseChecker.uniqueKeys();
-    REPORTER_ASSERT(reporter, keys.count() == 1);
-    GrUniqueKey texKey1;
-    if (keys.count()) {
-        texKey1 = keys[0];
-    }
-    REPORTER_ASSERT(reporter, texKey1.isValid());
-    REPORTER_ASSERT(reporter, ctx->priv().resourceProvider()->findByUniqueKey<>(texKey1));
-
-    gpu->testingOnly_flushGpuAndSync();
-    balanceExpecation = ReleaseBalanceExpecation::kBalanced;
-    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                             balanceExpecation,
-                                                             expectedFulfillCnt,
-                                                             expectedReleaseCnt,
-                                                             true,
-                                                             expectedDoneCnt,
-                                                             reporter));
-    REPORTER_ASSERT(reporter,
-                    GrBackendTexture::TestingOnly_Equals(
-                            promiseChecker.replaceTexture()->backendTexture(), backendTex1));
-    gpu->deleteTestingOnlyBackendTexture(backendTex1);
-
-    ctx->priv().getResourceCache()->purgeAsNeeded();
-    // We should have invalidated the key on the previously cached texture (after ensuring
-    // invalidation messages have been processed by calling purgeAsNeeded.)
-    REPORTER_ASSERT(reporter, !ctx->priv().resourceProvider()->findByUniqueKey<>(texKey1));
-
-    promiseChecker.replaceTexture(backendTex2);
-
-    canvas->drawImage(refImg, 0, 0);
-    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                             balanceExpecation,
-                                                             expectedFulfillCnt,
-                                                             expectedReleaseCnt,
-                                                             true,
-                                                             expectedDoneCnt,
-                                                             reporter));
-
-    canvas->flush();
-    expectedFulfillCnt++;
-    expectedReleaseCnt++;
-    if (isVulkan) {
-        balanceExpecation = ReleaseBalanceExpecation::kBalancedOrPlusOne;
-    }
-    // Second texture should be in the cache.
-    keys = promiseChecker.uniqueKeys();
-    REPORTER_ASSERT(reporter, keys.count() == 1);
-    GrUniqueKey texKey2;
-    if (keys.count()) {
-        texKey2 = keys[0];
-    }
-    REPORTER_ASSERT(reporter, texKey2.isValid() && texKey2 != texKey1);
-    REPORTER_ASSERT(reporter, ctx->priv().resourceProvider()->findByUniqueKey<>(texKey2));
-
-    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                             balanceExpecation,
-                                                             expectedFulfillCnt,
-                                                             expectedReleaseCnt,
-                                                             !isVulkan,
-                                                             expectedDoneCnt,
-                                                             reporter));
-    REPORTER_ASSERT(reporter, GrBackendTexture::TestingOnly_Equals(
-                                      promiseChecker.fLastFulfilledTexture, backendTex2));
-
-    gpu->testingOnly_flushGpuAndSync();
-    balanceExpecation = ReleaseBalanceExpecation::kBalanced;
-    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                             balanceExpecation,
-                                                             expectedFulfillCnt,
-                                                             expectedReleaseCnt,
-                                                             true,
-                                                             expectedDoneCnt,
-                                                             reporter));
-
-    // Because we have kept the SkPromiseImageTexture alive, we should be able to use it again and
-    // hit the cache.
-    ctx->priv().getResourceCache()->purgeAsNeeded();
-    REPORTER_ASSERT(reporter, ctx->priv().resourceProvider()->findByUniqueKey<>(texKey2));
-
-    canvas->drawImage(refImg, 0, 0);
-
-    canvas->flush();
-    gpu->testingOnly_flushGpuAndSync();
-    expectedFulfillCnt++;
-    expectedReleaseCnt++;
-    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                             balanceExpecation,
-                                                             expectedFulfillCnt,
-                                                             expectedReleaseCnt,
-                                                             true,
-                                                             expectedDoneCnt,
-                                                             reporter));
-
-    // Make sure we didn't add another key and that the second texture is still alive in the cache.
-    keys = promiseChecker.uniqueKeys();
-    REPORTER_ASSERT(reporter, keys.count() == 1);
-    if (keys.count()) {
-        REPORTER_ASSERT(reporter, texKey2 == keys[0]);
-    }
-    ctx->priv().getResourceCache()->purgeAsNeeded();
-    REPORTER_ASSERT(reporter, ctx->priv().resourceProvider()->findByUniqueKey<>(texKey2));
-
-    // Now we test keeping tex2 alive but fulfilling with a new texture.
-    sk_sp<const SkPromiseImageTexture> promiseImageTexture2 =
-            promiseChecker.replaceTexture(backendTex3);
-    REPORTER_ASSERT(reporter, GrBackendTexture::TestingOnly_Equals(
-                                      promiseImageTexture2->backendTexture(), backendTex2));
-
-    canvas->drawImage(refImg, 0, 0);
-
-    canvas->flush();
-    gpu->testingOnly_flushGpuAndSync();
-    expectedFulfillCnt++;
-    expectedReleaseCnt++;
-    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                             balanceExpecation,
-                                                             expectedFulfillCnt,
-                                                             expectedReleaseCnt,
-                                                             true,
-                                                             expectedDoneCnt,
-                                                             reporter));
-
-    keys = promiseChecker.uniqueKeys();
-    REPORTER_ASSERT(reporter, keys.count() == 1);
-    GrUniqueKey texKey3;
-    if (keys.count()) {
-        texKey3 = keys[0];
-    }
-    ctx->priv().getResourceCache()->purgeAsNeeded();
-    REPORTER_ASSERT(reporter, !ctx->priv().resourceProvider()->findByUniqueKey<>(texKey2));
-    REPORTER_ASSERT(reporter, ctx->priv().resourceProvider()->findByUniqueKey<>(texKey3));
-    gpu->deleteTestingOnlyBackendTexture(promiseImageTexture2->backendTexture());
-
-    // Make a new promise image also backed by texture 3.
-    sk_sp<SkImage> refImg2(
-            SkImage_Gpu::MakePromiseTexture(
-                    ctx, backendFormat, kWidth, kHeight,
-                    GrMipMapped::kNo, texOrigin,
-                    kRGBA_8888_SkColorType, kPremul_SkAlphaType,
-                    nullptr,
-                    PromiseTextureChecker::Fulfill,
-                    PromiseTextureChecker::Release,
-                    PromiseTextureChecker::Done,
-                    &promiseChecker,
-                    SkDeferredDisplayListRecorder::DelayReleaseCallback::kNo));
-    canvas->drawImage(refImg, 0, 0);
-    canvas->drawImage(refImg2, 1, 1);
-
-    canvas->flush();
-    gpu->testingOnly_flushGpuAndSync();
-    expectedFulfillCnt += 2;
-    expectedReleaseCnt += 2;
-    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                             balanceExpecation,
-                                                             expectedFulfillCnt,
-                                                             expectedReleaseCnt,
-                                                             true,
-                                                             expectedDoneCnt,
-                                                             reporter));
-
-    // The two images should share a single GrTexture by using the same key. The key is only
-    // dependent on the pixel config and the PromiseImageTexture key.
-    keys = promiseChecker.uniqueKeys();
-    REPORTER_ASSERT(reporter, keys.count() == 1);
-    if (keys.count() > 0) {
-        REPORTER_ASSERT(reporter, texKey3 == keys[0]);
-    }
-    ctx->priv().getResourceCache()->purgeAsNeeded();
-
-    // If we delete the SkPromiseImageTexture we should trigger both key removals.
-    REPORTER_ASSERT(reporter,
-                    GrBackendTexture::TestingOnly_Equals(
-                            promiseChecker.replaceTexture()->backendTexture(), backendTex3));
-
-    ctx->priv().getResourceCache()->purgeAsNeeded();
-    REPORTER_ASSERT(reporter, !ctx->priv().resourceProvider()->findByUniqueKey<>(texKey3));
-    gpu->deleteTestingOnlyBackendTexture(backendTex3);
-
-    // After deleting each image we should get a done call.
-    refImg.reset();
-    ++expectedDoneCnt;
-    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                             balanceExpecation,
-                                                             expectedFulfillCnt,
-                                                             expectedReleaseCnt,
-                                                             true,
-                                                             expectedDoneCnt,
-                                                             reporter));
-    refImg2.reset();
-    ++expectedDoneCnt;
-    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                             balanceExpecation,
-                                                             expectedFulfillCnt,
-                                                             expectedReleaseCnt,
-                                                             true,
-                                                             expectedDoneCnt,
-                                                             reporter));
-}
-
 DEF_GPUTEST_FOR_RENDERING_CONTEXTS(PromiseImageTextureReuseDifferentConfig, reporter, ctxInfo) {
     // Try making two promise SkImages backed by the same texture but with different configs.
     // This will only be testable on backends where a single texture format (8bit red unorm) can
@@ -716,69 +261,61 @@
     sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info);
     SkCanvas* canvas = surface->getCanvas();
 
-    for (auto delayRelease : {SkDeferredDisplayListRecorder::DelayReleaseCallback::kNo,
-                              SkDeferredDisplayListRecorder::DelayReleaseCallback::kYes}) {
-        PromiseTextureChecker promiseChecker(backendTex1, reporter, true);
-        sk_sp<SkImage> alphaImg(SkImage_Gpu::MakePromiseTexture(
-                ctx, backendTex1.getBackendFormat(), kWidth, kHeight, GrMipMapped::kNo,
-                kTopLeft_GrSurfaceOrigin, kAlpha_8_SkColorType, kPremul_SkAlphaType, nullptr,
-                PromiseTextureChecker::Fulfill, PromiseTextureChecker::Release,
-                PromiseTextureChecker::Done, &promiseChecker, delayRelease));
-        REPORTER_ASSERT(reporter, alphaImg);
+    PromiseTextureChecker promiseChecker(backendTex1, reporter, true);
+    sk_sp<SkImage> alphaImg(SkImage_Gpu::MakePromiseTexture(
+            ctx, backendTex1.getBackendFormat(), kWidth, kHeight, GrMipMapped::kNo,
+            kTopLeft_GrSurfaceOrigin, kAlpha_8_SkColorType, kPremul_SkAlphaType, nullptr,
+            PromiseTextureChecker::Fulfill, PromiseTextureChecker::Release,
+            PromiseTextureChecker::Done, &promiseChecker,
+            SkDeferredDisplayListRecorder::PromiseImageApiVersion::kNew));
+    REPORTER_ASSERT(reporter, alphaImg);
 
-        sk_sp<SkImage> grayImg(SkImage_Gpu::MakePromiseTexture(
-                ctx, backendTex1.getBackendFormat(), kWidth, kHeight, GrMipMapped::kNo,
-                kBottomLeft_GrSurfaceOrigin, kGray_8_SkColorType, kOpaque_SkAlphaType, nullptr,
-                PromiseTextureChecker::Fulfill, PromiseTextureChecker::Release,
-                PromiseTextureChecker::Done, &promiseChecker, delayRelease));
-        REPORTER_ASSERT(reporter, grayImg);
+    sk_sp<SkImage> grayImg(SkImage_Gpu::MakePromiseTexture(
+            ctx, backendTex1.getBackendFormat(), kWidth, kHeight, GrMipMapped::kNo,
+            kBottomLeft_GrSurfaceOrigin, kGray_8_SkColorType, kOpaque_SkAlphaType, nullptr,
+            PromiseTextureChecker::Fulfill, PromiseTextureChecker::Release,
+            PromiseTextureChecker::Done, &promiseChecker,
+            SkDeferredDisplayListRecorder::PromiseImageApiVersion::kNew));
+    REPORTER_ASSERT(reporter, grayImg);
 
-        canvas->drawImage(alphaImg, 0, 0);
-        canvas->drawImage(grayImg, 1, 1);
-        canvas->flush();
-        gpu->testingOnly_flushGpuAndSync();
+    canvas->drawImage(alphaImg, 0, 0);
+    canvas->drawImage(grayImg, 1, 1);
+    surface->flush();
+    gpu->testingOnly_flushGpuAndSync();
+    check_only_fulfilled(reporter, promiseChecker, 2);
 
-        int expectedFulfillCnt = 2;
-        int expectedReleaseCnt = 0;
-        int expectedDoneCnt = 0;
-        ReleaseBalanceExpecation balanceExpecation = ReleaseBalanceExpecation::kAny;
-        if (delayRelease == SkDeferredDisplayListRecorder::DelayReleaseCallback::kNo) {
-            expectedReleaseCnt = 2;
-            balanceExpecation = ReleaseBalanceExpecation::kBalanced;
-        }
-        REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                                 balanceExpecation,
-                                                                 expectedFulfillCnt,
-                                                                 expectedReleaseCnt,
-                                                                 true,
-                                                                 expectedDoneCnt,
-                                                                 reporter));
+    // Because they use different configs, each image should have created a different GrTexture
+    // and they both should still be cached.
+    ctx->priv().getResourceCache()->purgeAsNeeded();
 
-        // Because they use different configs, each image should have created a different GrTexture
-        // and they both should still be cached.
-        ctx->priv().getResourceCache()->purgeAsNeeded();
-
-        auto keys = promiseChecker.uniqueKeys();
-        REPORTER_ASSERT(reporter, keys.count() == 2);
-        for (const auto& key : keys) {
-            auto surf = ctx->priv().resourceProvider()->findByUniqueKey<GrSurface>(key);
-            REPORTER_ASSERT(reporter, surf && surf->asTexture());
-            if (surf && surf->asTexture()) {
-                REPORTER_ASSERT(reporter,
-                                !GrBackendTexture::TestingOnly_Equals(
-                                        backendTex1, surf->asTexture()->getBackendTexture()));
-            }
-        }
-
-        // Change the backing texture, this should invalidate the keys.
-        promiseChecker.replaceTexture();
-        ctx->priv().getResourceCache()->purgeAsNeeded();
-
-        for (const auto& key : keys) {
-            auto surf = ctx->priv().resourceProvider()->findByUniqueKey<GrSurface>(key);
-            REPORTER_ASSERT(reporter, !surf);
+    auto keys = promiseChecker.uniqueKeys();
+    REPORTER_ASSERT(reporter, keys.count() == 2);
+    for (const auto& key : keys) {
+        auto surf = ctx->priv().resourceProvider()->findByUniqueKey<GrSurface>(key);
+        REPORTER_ASSERT(reporter, surf && surf->asTexture());
+        if (surf && surf->asTexture()) {
+            REPORTER_ASSERT(reporter,
+                            !GrBackendTexture::TestingOnly_Equals(
+                                    backendTex1, surf->asTexture()->getBackendTexture()));
         }
     }
+
+    // Invalidate the backing texture, this should invalidate the keys.
+    promiseChecker.releaseTexture();
+    ctx->priv().getResourceCache()->purgeAsNeeded();
+
+    for (const auto& key : keys) {
+        auto surf = ctx->priv().resourceProvider()->findByUniqueKey<GrSurface>(key);
+        REPORTER_ASSERT(reporter, !surf);
+    }
+    alphaImg.reset();
+    ctx->flush(); // We do this to pick up any unref messages that are sent by unref'ing the image.
+    check_fulfill_and_release_cnts(reporter, promiseChecker, 2,
+                                   ReleaseBalanceExpectation::kUnbalancedByOne,
+                                   DoneBalanceExpectation::kUnbalancedByOne);
+    grayImg.reset();
+    ctx->flush(); // We do this to pick up any unref messages that are sent by unref'ing the image.
+    check_all_done(reporter, promiseChecker, 2);
     gpu->deleteTestingOnlyBackendTexture(backendTex1);
 }
 
@@ -831,7 +368,7 @@
                     kTopLeft_GrSurfaceOrigin, kAlpha_8_SkColorType, kPremul_SkAlphaType, nullptr,
                     PromiseTextureChecker::Fulfill, PromiseTextureChecker::Release,
                     PromiseTextureChecker::Done, &promiseChecker,
-                    SkDeferredDisplayListRecorder::DelayReleaseCallback::kNo));
+                    SkDeferredDisplayListRecorder::PromiseImageApiVersion::kNew));
             REPORTER_ASSERT(reporter, image);
 
             canvas->drawImage(image, 0, 0);
@@ -843,17 +380,7 @@
             ctx->flush();
             contextDeath(&factory, ctx);
 
-            int expectedFulfillCnt = 1;
-            int expectedReleaseCnt = 1;
-            int expectedDoneCnt = 1;
-            ReleaseBalanceExpecation balanceExpecation = ReleaseBalanceExpecation::kBalanced;
-            REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
-                                                                     balanceExpecation,
-                                                                     expectedFulfillCnt,
-                                                                     expectedReleaseCnt,
-                                                                     true,
-                                                                     expectedDoneCnt,
-                                                                     reporter));
+            check_all_done(reporter, promiseChecker);
         }
     }
 }
@@ -880,7 +407,7 @@
             kTopLeft_GrSurfaceOrigin, kAlpha_8_SkColorType, kPremul_SkAlphaType, nullptr,
             PromiseTextureChecker::Fulfill, PromiseTextureChecker::Release,
             PromiseTextureChecker::Done, &promiseChecker,
-            SkDeferredDisplayListRecorder::DelayReleaseCallback::kNo));
+            SkDeferredDisplayListRecorder::PromiseImageApiVersion::kNew));
     REPORTER_ASSERT(reporter, image);
 
     // Make the cache full. This tests that we don't preemptively purge cached textures for
@@ -893,25 +420,87 @@
         GrSurfaceDesc desc;
         desc.fConfig = kRGBA_8888_GrPixelConfig;
         desc.fWidth = desc.fHeight = 100;
-        textures[i] = ctx->priv().resourceProvider()->createTexture(desc, SkBudgeted::kYes);
+        textures[i] = ctx->priv().resourceProvider()->createTexture(
+            desc, SkBudgeted::kYes, GrResourceProvider::Flags::kNoPendingIO);
         REPORTER_ASSERT(reporter, textures[i]);
     }
 
     // Relying on the asserts in the promiseImageChecker to ensure that fulfills and releases are
     // properly ordered.
     canvas->drawImage(image, 0, 0);
-    canvas->flush();
+    surface->flush();
     canvas->drawImage(image, 1, 0);
-    canvas->flush();
+    surface->flush();
     canvas->drawImage(image, 2, 0);
-    canvas->flush();
+    surface->flush();
     canvas->drawImage(image, 3, 0);
-    canvas->flush();
+    surface->flush();
     canvas->drawImage(image, 4, 0);
-    canvas->flush();
+    surface->flush();
     canvas->drawImage(image, 5, 0);
-    canvas->flush();
-    // Must call this to ensure that all callbacks are performed before the checker is destroyed.
+    surface->flush();
+    // Must call these to ensure that all callbacks are performed before the checker is destroyed.
+    image.reset();
+    ctx->flush();
     gpu->testingOnly_flushGpuAndSync();
+
     gpu->deleteTestingOnlyBackendTexture(backendTex);
 }
+
+// Test case where promise image fulfill returns nullptr.
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(PromiseImageNullFulfill, reporter, ctxInfo) {
+    const int kWidth = 10;
+    const int kHeight = 10;
+
+    GrContext* ctx = ctxInfo.grContext();
+    GrGpu* gpu = ctx->priv().getGpu();
+
+    // Do all this just to get a valid backend format for the image.
+    GrBackendTexture backendTex = gpu->createTestingOnlyBackendTexture(
+            nullptr, kWidth, kHeight, GrColorType::kRGBA_8888, true, GrMipMapped::kNo);
+    REPORTER_ASSERT(reporter, backendTex.isValid());
+    GrBackendFormat backendFormat = backendTex.getBackendFormat();
+    REPORTER_ASSERT(reporter, backendFormat.isValid());
+    gpu->deleteTestingOnlyBackendTexture(backendTex);
+
+    struct Counts {
+        int fFulfillCount = 0;
+        int fReleaseCount = 0;
+        int fDoneCount = 0;
+    } counts;
+    auto fulfill = [](SkDeferredDisplayListRecorder::PromiseImageTextureContext ctx) {
+        ++static_cast<Counts*>(ctx)->fFulfillCount;
+        return sk_sp<SkPromiseImageTexture>();
+    };
+    auto release = [](SkDeferredDisplayListRecorder::PromiseImageTextureContext ctx) {
+        ++static_cast<Counts*>(ctx)->fReleaseCount;
+    };
+    auto done = [](SkDeferredDisplayListRecorder::PromiseImageTextureContext ctx) {
+        ++static_cast<Counts*>(ctx)->fDoneCount;
+    };
+    GrSurfaceOrigin texOrigin = kTopLeft_GrSurfaceOrigin;
+    sk_sp<SkImage> refImg(SkImage_Gpu::MakePromiseTexture(
+            ctx, backendFormat, kWidth, kHeight, GrMipMapped::kNo, texOrigin,
+            kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr, fulfill, release, done, &counts,
+            SkDeferredDisplayListRecorder::PromiseImageApiVersion::kNew));
+
+    SkImageInfo info = SkImageInfo::MakeN32Premul(kWidth, kHeight);
+    sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info);
+    SkCanvas* canvas = surface->getCanvas();
+    // Draw the image a few different ways.
+    canvas->drawImage(refImg, 0, 0);
+    SkPaint paint;
+    paint.setColorFilter(SkColorFilters::LinearToSRGBGamma());
+    canvas->drawImage(refImg, 0, 0, &paint);
+    auto shader = refImg->makeShader(SkTileMode::kClamp, SkTileMode::kClamp);
+    REPORTER_ASSERT(reporter, shader);
+    paint.setShader(std::move(shader));
+    canvas->drawRect(SkRect::MakeWH(1,1), paint);
+    paint.setShader(nullptr);
+    refImg.reset();
+    surface->flush();
+    // We should only call each callback once and we should have made all the calls by this point.
+    REPORTER_ASSERT(reporter, counts.fFulfillCount == 1);
+    REPORTER_ASSERT(reporter, counts.fReleaseCount == 1);
+    REPORTER_ASSERT(reporter, counts.fDoneCount == 1);
+}
diff --git a/tests/ProxyTest.cpp b/tests/ProxyTest.cpp
index 87b92ee..c65d221 100644
--- a/tests/ProxyTest.cpp
+++ b/tests/ProxyTest.cpp
@@ -141,9 +141,10 @@
                                 sk_sp<GrTexture> tex;
                                 if (SkBackingFit::kApprox == fit) {
                                     tex = resourceProvider->createApproxTexture(
-                                            desc, GrResourceProvider::Flags::kNone);
+                                            desc, GrResourceProvider::Flags::kNoPendingIO);
                                 } else {
-                                    tex = resourceProvider->createTexture(desc, budgeted);
+                                    tex = resourceProvider->createTexture(
+                                        desc, budgeted, GrResourceProvider::Flags::kNoPendingIO);
                                 }
 
                                 sk_sp<GrTextureProxy> proxy =
@@ -176,9 +177,10 @@
                                 sk_sp<GrTexture> tex;
                                 if (SkBackingFit::kApprox == fit) {
                                     tex = resourceProvider->createApproxTexture(
-                                            desc, GrResourceProvider::Flags::kNone);
+                                            desc, GrResourceProvider::Flags::kNoPendingIO);
                                 } else {
-                                    tex = resourceProvider->createTexture(desc, budgeted);
+                                    tex = resourceProvider->createTexture(
+                                        desc, budgeted, GrResourceProvider::Flags::kNoPendingIO);
                                 }
 
                                 sk_sp<GrTextureProxy> proxy(
diff --git a/tests/QuickRejectTest.cpp b/tests/QuickRejectTest.cpp
index 4bffe13..4f5ad8f 100644
--- a/tests/QuickRejectTest.cpp
+++ b/tests/QuickRejectTest.cpp
@@ -8,7 +8,6 @@
 #include "SkArenaAlloc.h"
 #include "SkBitmap.h"
 #include "SkCanvas.h"
-#include "SkColorSpaceXformer.h"
 #include "SkDrawLooper.h"
 #include "SkLightingImageFilter.h"
 #include "SkPoint3.h"
@@ -25,10 +24,6 @@
         return alloc->make<TestDrawLooperContext>();
     }
 
-    sk_sp<SkDrawLooper> onMakeColorSpace(SkColorSpaceXformer*) const override {
-        return nullptr;
-    }
-
 private:
     SK_FLATTENABLE_HOOKS(TestLooper)
 
diff --git a/tests/RecordDrawTest.cpp b/tests/RecordDrawTest.cpp
index 4902338..7a82aeb 100644
--- a/tests/RecordDrawTest.cpp
+++ b/tests/RecordDrawTest.cpp
@@ -8,7 +8,7 @@
 #include "Test.h"
 #include "RecordTestUtils.h"
 
-#include "SkDebugCanvas.h"
+#include "DebugCanvas.h"
 #include "SkDropShadowImageFilter.h"
 #include "SkImagePriv.h"
 #include "SkRecord.h"
diff --git a/tests/RecordOptsTest.cpp b/tests/RecordOptsTest.cpp
index cb80987..9037753 100644
--- a/tests/RecordOptsTest.cpp
+++ b/tests/RecordOptsTest.cpp
@@ -236,7 +236,7 @@
     xfermodePaint.setBlendMode(SkBlendMode::kDstIn);
     SkPaint colorFilterPaint;
     colorFilterPaint.setColorFilter(
-        SkColorFilter::MakeModeFilter(SK_ColorLTGRAY, SkBlendMode::kSrcIn));
+        SkColorFilters::Blend(SK_ColorLTGRAY, SkBlendMode::kSrcIn));
 
     SkPaint opaqueFilterLayerPaint;
     opaqueFilterLayerPaint.setColor(0xFF020202);  // Opaque.
diff --git a/tests/RecorderTest.cpp b/tests/RecorderTest.cpp
index a709a5138..3e989e2 100644
--- a/tests/RecorderTest.cpp
+++ b/tests/RecorderTest.cpp
@@ -58,7 +58,7 @@
 
     SkRect bounds = SkRect::MakeWH(320, 240);
     SkPaint paint;
-    paint.setShader(SkShader::MakeEmptyShader());
+    paint.setShader(SkShaders::Empty());
 
     REPORTER_ASSERT(r, paint.getShader()->unique());
     {
diff --git a/tests/RectangleTextureTest.cpp b/tests/RectangleTextureTest.cpp
index e9f286f..ce6122a 100644
--- a/tests/RectangleTextureTest.cpp
+++ b/tests/RectangleTextureTest.cpp
@@ -165,8 +165,7 @@
 
         test_read_pixels(reporter, rectContext.get(), refPixels, "RectangleTexture-read");
 
-        test_copy_to_surface(reporter, context->priv().proxyProvider(),
-                              rectContext.get(), "RectangleTexture-copy-to");
+        test_copy_to_surface(reporter, context, rectContext.get(), "RectangleTexture-copy-to");
 
         test_write_pixels(reporter, rectContext.get(), true, "RectangleTexture-write");
 
diff --git a/tests/ResourceAllocatorTest.cpp b/tests/ResourceAllocatorTest.cpp
index a71221d..ce6b731 100644
--- a/tests/ResourceAllocatorTest.cpp
+++ b/tests/ResourceAllocatorTest.cpp
@@ -28,6 +28,7 @@
     SkBackingFit    fFit;
     int             fSampleCnt;
     GrSurfaceOrigin fOrigin;
+    SkBudgeted      fBudgeted;
     // TODO: do we care about mipmapping
 };
 
@@ -45,7 +46,7 @@
 
     const GrBackendFormat format = caps->getBackendFormatFromColorType(p.fColorType);
 
-    auto tmp = proxyProvider->createProxy(format, desc, p.fOrigin, p.fFit, SkBudgeted::kNo);
+    auto tmp = proxyProvider->createProxy(format, desc, p.fOrigin, p.fFit, p.fBudgeted);
     if (!tmp) {
         return nullptr;
     }
@@ -89,12 +90,15 @@
 // Basic test that two proxies with overlapping intervals and compatible descriptors are
 // assigned different GrSurfaces.
 static void overlap_test(skiatest::Reporter* reporter, GrResourceProvider* resourceProvider,
-                         GrSurfaceProxy* p1, GrSurfaceProxy* p2, bool expectedResult) {
-    GrDeinstantiateProxyTracker deinstantiateTracker;
-    GrResourceAllocator alloc(resourceProvider, &deinstantiateTracker);
+                         GrResourceCache* resourceCache, GrSurfaceProxy* p1, GrSurfaceProxy* p2,
+                         bool expectedResult) {
+    GrDeinstantiateProxyTracker deinstantiateTracker(resourceCache);
+    GrResourceAllocator alloc(resourceProvider, &deinstantiateTracker SkDEBUGCODE(, 1));
 
     alloc.addInterval(p1, 0, 4);
+    alloc.incOps();
     alloc.addInterval(p2, 1, 2);
+    alloc.incOps();
     alloc.markEndOfOpList(0);
 
     int startIndex, stopIndex;
@@ -111,10 +115,17 @@
 // Test various cases when two proxies do not have overlapping intervals.
 // This mainly acts as a test of the ResourceAllocator's free pool.
 static void non_overlap_test(skiatest::Reporter* reporter, GrResourceProvider* resourceProvider,
-                             GrSurfaceProxy* p1, GrSurfaceProxy* p2,
+                             GrResourceCache* resourceCache, GrSurfaceProxy* p1, GrSurfaceProxy* p2,
                              bool expectedResult) {
-    GrDeinstantiateProxyTracker deinstantiateTracker;
-    GrResourceAllocator alloc(resourceProvider, &deinstantiateTracker);
+    GrDeinstantiateProxyTracker deinstantiateTracker(resourceCache);
+    GrResourceAllocator alloc(resourceProvider, &deinstantiateTracker SkDEBUGCODE(, 1));
+
+    alloc.incOps();
+    alloc.incOps();
+    alloc.incOps();
+    alloc.incOps();
+    alloc.incOps();
+    alloc.incOps();
 
     alloc.addInterval(p1, 0, 2);
     alloc.addInterval(p2, 3, 5);
@@ -141,6 +152,7 @@
     const GrCaps* caps = ctxInfo.grContext()->priv().caps();
     GrProxyProvider* proxyProvider = ctxInfo.grContext()->priv().proxyProvider();
     GrResourceProvider* resourceProvider = ctxInfo.grContext()->priv().resourceProvider();
+    GrResourceCache* resourceCache = ctxInfo.grContext()->priv().getResourceCache();
 
     bool orig = resourceProvider->testingOnly_setExplicitlyAllocateGPUResources(true);
 
@@ -167,20 +179,22 @@
     const GrSurfaceOrigin kTL = kTopLeft_GrSurfaceOrigin;
     const GrSurfaceOrigin kBL = kBottomLeft_GrSurfaceOrigin;
 
+    const SkBudgeted kNotB = SkBudgeted::kNo;
+
     //--------------------------------------------------------------------------------------------
     TestCase gOverlappingTests[] = {
         //----------------------------------------------------------------------------------------
         // Two proxies with overlapping intervals and compatible descriptors should never share
         // RT version
-        { { 64,    kRT, kRGBA, kA, 0, kTL }, { 64,    kRT, kRGBA, kA, 0, kTL }, kDontShare },
+        { { 64,    kRT, kRGBA, kA, 0, kTL, kNotB }, { 64,    kRT, kRGBA, kA, 0, kTL, kNotB }, kDontShare },
         // non-RT version
-        { { 64, kNotRT, kRGBA, kA, 0, kTL }, { 64, kNotRT, kRGBA, kA, 0, kTL }, kDontShare },
+        { { 64, kNotRT, kRGBA, kA, 0, kTL, kNotB }, { 64, kNotRT, kRGBA, kA, 0, kTL, kNotB }, kDontShare },
     };
 
     for (auto test : gOverlappingTests) {
         GrSurfaceProxy* p1 = make_deferred(proxyProvider, caps, test.fP1);
         GrSurfaceProxy* p2 = make_deferred(proxyProvider, caps, test.fP2);
-        overlap_test(reporter, resourceProvider, p1, p2, test.fExpectation);
+        overlap_test(reporter, resourceProvider, resourceCache, p1, p2, test.fExpectation);
         p1->completedRead();
         p2->completedRead();
     }
@@ -195,28 +209,28 @@
         //----------------------------------------------------------------------------------------
         // Two non-overlapping intervals w/ compatible proxies should share
         // both same size & approx
-        { { 64,    kRT, kRGBA, kA, 0, kTL }, { 64,    kRT, kRGBA, kA, 0, kTL }, kShare },
-        { { 64, kNotRT, kRGBA, kA, 0, kTL }, { 64, kNotRT, kRGBA, kA, 0, kTL }, kConditionallyShare },
+        { { 64,    kRT, kRGBA, kA, 0, kTL, kNotB }, { 64,    kRT, kRGBA, kA, 0, kTL, kNotB }, kShare },
+        { { 64, kNotRT, kRGBA, kA, 0, kTL, kNotB }, { 64, kNotRT, kRGBA, kA, 0, kTL, kNotB }, kConditionallyShare },
         // diffs sizes but still approx
-        { { 64,    kRT, kRGBA, kA, 0, kTL }, { 50,    kRT, kRGBA, kA, 0, kTL }, kShare },
-        { { 64, kNotRT, kRGBA, kA, 0, kTL }, { 50, kNotRT, kRGBA, kA, 0, kTL }, kConditionallyShare },
+        { { 64,    kRT, kRGBA, kA, 0, kTL, kNotB }, { 50,    kRT, kRGBA, kA, 0, kTL, kNotB }, kShare },
+        { { 64, kNotRT, kRGBA, kA, 0, kTL, kNotB }, { 50, kNotRT, kRGBA, kA, 0, kTL, kNotB }, kConditionallyShare },
         // sames sizes but exact
-        { { 64,    kRT, kRGBA, kE, 0, kTL }, { 64,    kRT, kRGBA, kE, 0, kTL }, kShare },
-        { { 64, kNotRT, kRGBA, kE, 0, kTL }, { 64, kNotRT, kRGBA, kE, 0, kTL }, kConditionallyShare },
+        { { 64,    kRT, kRGBA, kE, 0, kTL, kNotB }, { 64,    kRT, kRGBA, kE, 0, kTL, kNotB }, kShare },
+        { { 64, kNotRT, kRGBA, kE, 0, kTL, kNotB }, { 64, kNotRT, kRGBA, kE, 0, kTL, kNotB }, kConditionallyShare },
         //----------------------------------------------------------------------------------------
         // Two non-overlapping intervals w/ different exact sizes should not share
-        { { 56,    kRT, kRGBA, kE, 0, kTL }, { 54,    kRT, kRGBA, kE, 0, kTL }, kDontShare },
+        { { 56,    kRT, kRGBA, kE, 0, kTL, kNotB }, { 54,    kRT, kRGBA, kE, 0, kTL, kNotB }, kDontShare },
         // Two non-overlapping intervals w/ _very different_ approx sizes should not share
-        { { 255,   kRT, kRGBA, kA, 0, kTL }, { 127,   kRT, kRGBA, kA, 0, kTL }, kDontShare },
+        { { 255,   kRT, kRGBA, kA, 0, kTL, kNotB }, { 127,   kRT, kRGBA, kA, 0, kTL, kNotB }, kDontShare },
         // Two non-overlapping intervals w/ different MSAA sample counts should not share
-        { { 64,    kRT, kRGBA, kA, k2, kTL },{ 64,    kRT, kRGBA, kA, k4, kTL}, k2 == k4 },
+        { { 64,    kRT, kRGBA, kA, k2, kTL, kNotB },{ 64,    kRT, kRGBA, kA, k4,kTL, kNotB}, k2 == k4 },
         // Two non-overlapping intervals w/ different configs should not share
-        { { 64,    kRT, kRGBA, kA, 0, kTL }, { 64,    kRT, kBGRA, kA, 0, kTL }, kDontShare },
+        { { 64,    kRT, kRGBA, kA, 0, kTL, kNotB }, { 64,    kRT, kBGRA, kA, 0, kTL, kNotB }, kDontShare },
         // Two non-overlapping intervals w/ different RT classifications should never share
-        { { 64,    kRT, kRGBA, kA, 0, kTL }, { 64, kNotRT, kRGBA, kA, 0, kTL }, kDontShare },
-        { { 64, kNotRT, kRGBA, kA, 0, kTL }, { 64,    kRT, kRGBA, kA, 0, kTL }, kDontShare },
+        { { 64,    kRT, kRGBA, kA, 0, kTL, kNotB }, { 64, kNotRT, kRGBA, kA, 0, kTL, kNotB }, kDontShare },
+        { { 64, kNotRT, kRGBA, kA, 0, kTL, kNotB }, { 64,    kRT, kRGBA, kA, 0, kTL, kNotB }, kDontShare },
         // Two non-overlapping intervals w/ different origins should share
-        { { 64,    kRT, kRGBA, kA, 0, kTL }, { 64,    kRT, kRGBA, kA, 0, kBL }, kShare },
+        { { 64,    kRT, kRGBA, kA, 0, kTL, kNotB }, { 64,    kRT, kRGBA, kA, 0, kBL, kNotB }, kShare },
     };
 
     for (auto test : gNonOverlappingTests) {
@@ -227,7 +241,7 @@
             continue; // creation can fail (i.e., for msaa4 on iOS)
         }
 
-        non_overlap_test(reporter, resourceProvider, p1, p2, test.fExpectation);
+        non_overlap_test(reporter, resourceProvider, resourceCache, p1, p2, test.fExpectation);
 
         p1->completedRead();
         p2->completedRead();
@@ -236,14 +250,14 @@
     {
         // Wrapped backend textures should never be reused
         TestCase t[1] = {
-            { { 64, kNotRT, kRGBA, kE, 0, kTL }, { 64, kNotRT, kRGBA, kE, 0, kTL }, kDontShare }
+            { { 64, kNotRT, kRGBA, kE, 0, kTL, kNotB }, { 64, kNotRT, kRGBA, kE, 0, kTL, kNotB }, kDontShare }
         };
 
         GrBackendTexture backEndTex;
         GrSurfaceProxy* p1 = make_backend(ctxInfo.grContext(), t[0].fP1, &backEndTex);
         GrSurfaceProxy* p2 = make_deferred(proxyProvider, caps, t[0].fP2);
 
-        non_overlap_test(reporter, resourceProvider, p1, p2, t[0].fExpectation);
+        non_overlap_test(reporter, resourceProvider, resourceCache, p1, p2, t[0].fExpectation);
 
         p1->completedRead();
         p2->completedRead();
@@ -300,62 +314,129 @@
     desc.fSampleCnt = p.fSampleCnt;
 
     SkBackingFit fit = p.fFit;
-    auto callback = [fit, desc](GrResourceProvider* resourceProvider) -> sk_sp<GrSurface> {
-        if (!resourceProvider) {
-            return nullptr;
-        }
+    auto callback = [fit, desc](GrResourceProvider* resourceProvider) {
+        sk_sp<GrTexture> texture;
         if (fit == SkBackingFit::kApprox) {
-            return resourceProvider->createApproxTexture(desc, GrResourceProvider::Flags::kNone);
+            texture = resourceProvider->createApproxTexture(
+                desc, GrResourceProvider::Flags::kNoPendingIO);
         } else {
-            return resourceProvider->createTexture(desc, SkBudgeted::kNo);
+            texture = resourceProvider->createTexture(desc, SkBudgeted::kNo,
+                                                      GrResourceProvider::Flags::kNoPendingIO);
         }
+        return GrSurfaceProxy::LazyInstantiationResult(std::move(texture));
     };
     const GrBackendFormat format = caps->getBackendFormatFromColorType(p.fColorType);
     auto lazyType = deinstantiate ? GrSurfaceProxy::LazyInstantiationType ::kDeinstantiate
                                   : GrSurfaceProxy::LazyInstantiationType ::kSingleUse;
     GrInternalSurfaceFlags flags = GrInternalSurfaceFlags::kNone;
     return proxyProvider->createLazyProxy(callback, format, desc, p.fOrigin, GrMipMapped::kNo,
-                                          flags, p.fFit, SkBudgeted::kNo, lazyType);
+                                          flags, p.fFit, p.fBudgeted, lazyType);
 }
 
 DEF_GPUTEST_FOR_RENDERING_CONTEXTS(LazyDeinstantiation, reporter, ctxInfo) {
     GrContext* context = ctxInfo.grContext();
     GrResourceProvider* resourceProvider = ctxInfo.grContext()->priv().resourceProvider();
-    for (auto explicitlyAllocating : {false, true}) {
-        resourceProvider->testingOnly_setExplicitlyAllocateGPUResources(explicitlyAllocating);
-        ProxyParams texParams;
-        texParams.fFit = SkBackingFit::kExact;
-        texParams.fOrigin = kTopLeft_GrSurfaceOrigin;
-        texParams.fColorType = kRGBA_8888_SkColorType;
-        texParams.fIsRT = false;
-        texParams.fSampleCnt = 1;
-        texParams.fSize = 100;
-        ProxyParams rtParams = texParams;
-        rtParams.fIsRT = true;
-        auto proxyProvider = context->priv().proxyProvider();
-        auto caps = context->priv().caps();
-        auto p0 = make_lazy(proxyProvider, caps, texParams, true);
-        auto p1 = make_lazy(proxyProvider, caps, texParams, false);
-        texParams.fFit = rtParams.fFit = SkBackingFit::kApprox;
-        auto p2 = make_lazy(proxyProvider, caps, rtParams, true);
-        auto p3 = make_lazy(proxyProvider, caps, rtParams, false);
+    GrResourceCache* resourceCache = ctxInfo.grContext()->priv().getResourceCache();
+    resourceProvider->testingOnly_setExplicitlyAllocateGPUResources(true);
+    ProxyParams texParams;
+    texParams.fFit = SkBackingFit::kExact;
+    texParams.fOrigin = kTopLeft_GrSurfaceOrigin;
+    texParams.fColorType = kRGBA_8888_SkColorType;
+    texParams.fIsRT = false;
+    texParams.fSampleCnt = 1;
+    texParams.fSize = 100;
+    texParams.fBudgeted = SkBudgeted::kNo;
+    ProxyParams rtParams = texParams;
+    rtParams.fIsRT = true;
+    auto proxyProvider = context->priv().proxyProvider();
+    auto caps = context->priv().caps();
+    auto p0 = make_lazy(proxyProvider, caps, texParams, true);
+    auto p1 = make_lazy(proxyProvider, caps, texParams, false);
+    texParams.fFit = rtParams.fFit = SkBackingFit::kApprox;
+    auto p2 = make_lazy(proxyProvider, caps, rtParams, true);
+    auto p3 = make_lazy(proxyProvider, caps, rtParams, false);
 
-        GrDeinstantiateProxyTracker deinstantiateTracker;
-        {
-            GrResourceAllocator alloc(resourceProvider, &deinstantiateTracker);
-            alloc.addInterval(p0.get(), 0, 1);
-            alloc.addInterval(p1.get(), 0, 1);
-            alloc.addInterval(p2.get(), 0, 1);
-            alloc.addInterval(p3.get(), 0, 1);
-            alloc.markEndOfOpList(0);
-            int startIndex, stopIndex;
-            GrResourceAllocator::AssignError error;
-            alloc.assign(&startIndex, &stopIndex, &error);
-        }
-        deinstantiateTracker.deinstantiateAllProxies();
-        REPORTER_ASSERT(reporter, !p0->isInstantiated());
-        REPORTER_ASSERT(reporter, p1->isInstantiated());
-        REPORTER_ASSERT(reporter, !p2->isInstantiated());
-        REPORTER_ASSERT(reporter, p3->isInstantiated());
+    GrDeinstantiateProxyTracker deinstantiateTracker(resourceCache);
+    {
+        GrResourceAllocator alloc(resourceProvider, &deinstantiateTracker SkDEBUGCODE(, 1));
+        alloc.addInterval(p0.get(), 0, 1);
+        alloc.addInterval(p1.get(), 0, 1);
+        alloc.addInterval(p2.get(), 0, 1);
+        alloc.addInterval(p3.get(), 0, 1);
+        alloc.incOps();
+        alloc.markEndOfOpList(0);
+        int startIndex, stopIndex;
+        GrResourceAllocator::AssignError error;
+        alloc.assign(&startIndex, &stopIndex, &error);
     }
+    deinstantiateTracker.deinstantiateAllProxies();
+    REPORTER_ASSERT(reporter, !p0->isInstantiated());
+    REPORTER_ASSERT(reporter, p1->isInstantiated());
+    REPORTER_ASSERT(reporter, !p2->isInstantiated());
+    REPORTER_ASSERT(reporter, p3->isInstantiated());
+}
+
+// Set up so there are two opLists that need to be flushed but the resource allocator thinks
+// it is over budget. The two opLists should be flushed separately and the opList indices
+// returned from assign should be correct.
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(ResourceAllocatorOverBudgetTest, reporter, ctxInfo) {
+    GrContext* context = ctxInfo.grContext();
+    const GrCaps* caps = context->priv().caps();
+    GrProxyProvider* proxyProvider = context->priv().proxyProvider();
+    GrResourceProvider* resourceProvider = context->priv().resourceProvider();
+    GrResourceCache* resourceCache = context->priv().getResourceCache();
+
+    bool orig = resourceProvider->testingOnly_setExplicitlyAllocateGPUResources(true);
+
+    int origMaxNum;
+    size_t origMaxBytes;
+    context->getResourceCacheLimits(&origMaxNum, &origMaxBytes);
+
+    // Force the resource allocator to always believe it is over budget
+    context->setResourceCacheLimits(0, 0);
+
+    const ProxyParams params  = { 64, false, kRGBA_8888_SkColorType,
+                                  SkBackingFit::kExact, 0, kTopLeft_GrSurfaceOrigin,
+                                  SkBudgeted::kYes };
+
+    {
+        GrSurfaceProxy* p1 = make_deferred(proxyProvider, caps, params);
+        GrSurfaceProxy* p2 = make_deferred(proxyProvider, caps, params);
+        GrSurfaceProxy* p3 = make_deferred(proxyProvider, caps, params);
+        GrSurfaceProxy* p4 = make_deferred(proxyProvider, caps, params);
+
+        GrDeinstantiateProxyTracker deinstantiateTracker(resourceCache);
+        GrResourceAllocator alloc(resourceProvider, &deinstantiateTracker SkDEBUGCODE(, 2));
+
+        alloc.addInterval(p1, 0, 0);
+        alloc.incOps();
+        alloc.addInterval(p2, 1, 1);
+        alloc.incOps();
+        alloc.markEndOfOpList(0);
+
+        alloc.addInterval(p3, 2, 2);
+        alloc.incOps();
+        alloc.addInterval(p4, 3, 3);
+        alloc.incOps();
+        alloc.markEndOfOpList(1);
+
+        int startIndex, stopIndex;
+        GrResourceAllocator::AssignError error;
+
+        alloc.assign(&startIndex, &stopIndex, &error);
+        REPORTER_ASSERT(reporter, GrResourceAllocator::AssignError::kNoError == error);
+        REPORTER_ASSERT(reporter, 0 == startIndex && 1 == stopIndex);
+
+        alloc.assign(&startIndex, &stopIndex, &error);
+        REPORTER_ASSERT(reporter, GrResourceAllocator::AssignError::kNoError == error);
+        REPORTER_ASSERT(reporter, 1 == startIndex && 2 == stopIndex);
+
+        p1->completedRead();
+        p2->completedRead();
+        p3->completedRead();
+        p4->completedRead();
+    }
+
+    context->setResourceCacheLimits(origMaxNum, origMaxBytes);
+    resourceProvider->testingOnly_setExplicitlyAllocateGPUResources(orig);
 }
diff --git a/tests/ResourceCacheTest.cpp b/tests/ResourceCacheTest.cpp
index 9bd5415..747bf2c 100644
--- a/tests/ResourceCacheTest.cpp
+++ b/tests/ResourceCacheTest.cpp
@@ -103,7 +103,8 @@
     desc.fConfig = kRGBA_8888_GrPixelConfig;
     desc.fSampleCnt = sampleCount;
 
-    sk_sp<GrTexture> tex(provider->createTexture(desc, budgeted));
+    sk_sp<GrTexture> tex(provider->createTexture(desc, budgeted,
+                                                 GrResourceProvider::Flags::kNoPendingIO));
     if (!tex || !tex->asRenderTarget()) {
         return nullptr;
     }
@@ -1545,18 +1546,18 @@
     GrGpu* gpu = context->priv().getGpu();
 
     TestResource* wrapped1 = TestResource::CreateWrapped(gpu, GrWrapCacheable::kYes);
-    cache->insertCrossContextGpuResource(wrapped1);
+    cache->insertDelayedResourceUnref(wrapped1);
 
     REPORTER_ASSERT(reporter, 1 == TestResource::NumAlive());
 
     TestResource* wrapped2 = TestResource::CreateWrapped(gpu, GrWrapCacheable::kYes);
-    cache->insertCrossContextGpuResource(wrapped2);
+    cache->insertDelayedResourceUnref(wrapped2);
 
     // An uncacheable cross-context should not be purged as soon as we drop our ref. This
     // is because inserting it as a cross-context resource actually holds a ref until the
     // message is received.
     TestResource* wrapped3 = TestResource::CreateWrapped(gpu, GrWrapCacheable::kNo);
-    cache->insertCrossContextGpuResource(wrapped3);
+    cache->insertDelayedResourceUnref(wrapped3);
 
     REPORTER_ASSERT(reporter, 3 == TestResource::NumAlive());
 
@@ -1624,7 +1625,8 @@
     desc.fConfig = kRGBA_8888_GrPixelConfig;
     desc.fSampleCnt = sampleCnt;
 
-    return provider->createTexture(desc, SkBudgeted::kYes);
+    return provider->createTexture(desc, SkBudgeted::kYes,
+                                   GrResourceProvider::Flags::kNoPendingIO);
 }
 
 static sk_sp<GrTextureProxy> make_mipmap_proxy(GrProxyProvider* proxyProvider,
@@ -1708,3 +1710,62 @@
         REPORTER_ASSERT(reporter, kSize*kSize*4+(kSize*kSize*4)/3 == size);
     }
 }
+
+#if GR_GPU_STATS
+DEF_GPUTEST_FOR_MOCK_CONTEXT(OverbudgetFlush, reporter, ctxInfo) {
+    GrContext* context = ctxInfo.grContext();
+    context->setResourceCacheLimits(1, 1);
+
+    // Helper that determines if cache is overbudget.
+    auto overbudget = [context] {
+         int uNum;
+         size_t uSize;
+         context->getResourceCacheUsage(&uNum, &uSize);
+         int bNum;
+         size_t bSize;
+         context->getResourceCacheLimits(&bNum, &bSize);
+         return uNum > bNum || uSize > bSize;
+    };
+
+    // Helper that does a trivial draw to a surface.
+    auto drawToSurf = [](SkSurface* surf) {
+        surf->getCanvas()->drawRect(SkRect::MakeWH(1,1), SkPaint());
+    };
+
+    // Helper that checks whether a flush has occurred between calls.
+    int baseFlushCount = 0;
+    auto getFlushCountDelta = [context, &baseFlushCount]() {
+        int cur = context->priv().getGpu()->stats()->numFinishFlushes();
+        int delta = cur - baseFlushCount;
+        baseFlushCount = cur;
+        return delta;
+    };
+
+    auto info = SkImageInfo::Make(10, 10, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+    auto surf1 = SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info, 1, nullptr);
+    auto surf2 = SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info, 1, nullptr);
+
+    drawToSurf(surf1.get());
+    drawToSurf(surf2.get());
+
+    // Flush each surface once to ensure that their backing stores are allocated.
+    surf1->flush();
+    surf2->flush();
+    REPORTER_ASSERT(reporter, overbudget());
+    getFlushCountDelta();
+
+    // Nothing should be purgeable so drawing to either surface doesn't cause a flush.
+    drawToSurf(surf1.get());
+    REPORTER_ASSERT(reporter, !getFlushCountDelta());
+    drawToSurf(surf2.get());
+    REPORTER_ASSERT(reporter, !getFlushCountDelta());
+    REPORTER_ASSERT(reporter, overbudget());
+
+    // Make surf1 purgeable. Drawing to surf2 should flush.
+    surf1->flush();
+    surf1.reset();
+    drawToSurf(surf2.get());
+    REPORTER_ASSERT(reporter, getFlushCountDelta());
+    REPORTER_ASSERT(reporter, overbudget());
+}
+#endif
diff --git a/tests/RoundRectTest.cpp b/tests/RoundRectTest.cpp
index 82c8875..f2f1eb8 100644
--- a/tests/RoundRectTest.cpp
+++ b/tests/RoundRectTest.cpp
@@ -570,8 +570,6 @@
 
     // Rotation fails.
     matrix.reset();
-    matrix.setRotate(SkIntToScalar(90));
-    assert_transform_failure(reporter, orig, matrix);
     matrix.setRotate(SkIntToScalar(37));
     assert_transform_failure(reporter, orig, matrix);
 
@@ -689,6 +687,219 @@
                                                   orig.rect().left() * xScale));
     REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().top(),
                                                   orig.rect().top() * yScale));
+
+
+    //  a-----b            d-----a
+    //  |     |     ->     |     |
+    //  |     |  Rotate 90 |     |
+    //  d-----c            c-----b
+    matrix.reset();
+    matrix.setRotate(SkIntToScalar(90));
+    dst.setEmpty();
+    success = orig.transform(matrix, &dst);
+    REPORTER_ASSERT(reporter, success);
+    {
+        GET_RADII;
+        // Radii have cycled clockwise and swapped their x and y axis.
+        REPORTER_ASSERT(reporter, dstUL.x() == origLL.y());
+        REPORTER_ASSERT(reporter, dstUL.y() == origLL.x());
+        REPORTER_ASSERT(reporter, dstUR.x() == origUL.y());
+        REPORTER_ASSERT(reporter, dstUR.y() == origUL.x());
+        REPORTER_ASSERT(reporter, dstLR.x() == origUR.y());
+        REPORTER_ASSERT(reporter, dstLR.y() == origUR.x());
+        REPORTER_ASSERT(reporter, dstLL.x() == origLR.y());
+        REPORTER_ASSERT(reporter, dstLL.y() == origLR.x());
+    }
+    // Width and height would get swapped.
+    REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().height());
+    REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().width());
+
+    //  a-----b        b-----a           c-----b
+    //  |     |   ->   |     |    ->     |     |
+    //  |     | Flip X |     | Rotate 90 |     |
+    //  d-----c        c-----d           d-----a
+    matrix.reset();
+    matrix.setRotate(SkIntToScalar(90));
+    matrix.postScale(SkIntToScalar(-1), SkIntToScalar(1));
+    dst.setEmpty();
+    success = orig.transform(matrix, &dst);
+    REPORTER_ASSERT(reporter, success);
+    {
+        GET_RADII;
+        REPORTER_ASSERT(reporter, dstUL.x() == origLR.y());
+        REPORTER_ASSERT(reporter, dstUL.y() == origLR.x());
+        REPORTER_ASSERT(reporter, dstUR.x() == origUR.y());
+        REPORTER_ASSERT(reporter, dstUR.y() == origUR.x());
+        REPORTER_ASSERT(reporter, dstLR.x() == origUL.y());
+        REPORTER_ASSERT(reporter, dstLR.y() == origUL.x());
+        REPORTER_ASSERT(reporter, dstLL.x() == origLL.y());
+        REPORTER_ASSERT(reporter, dstLL.y() == origLL.x());
+    }
+    // Width and height would get swapped.
+    REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().height());
+    REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().width());
+
+    //  a-----b           d-----a        c-----b
+    //  |     |    ->     |     |   ->   |     |
+    //  |     | Rotate 90 |     | Flip Y |     |
+    //  d-----c           c-----b        d-----a
+    //
+    // This is the same as Flip X and Rotate 90.
+    matrix.reset();
+    matrix.setScale(SkIntToScalar(1), SkIntToScalar(-1));
+    matrix.postRotate(SkIntToScalar(90));
+    SkRRect dst2;
+    dst2.setEmpty();
+    success = orig.transform(matrix, &dst2);
+    REPORTER_ASSERT(reporter, success);
+    REPORTER_ASSERT(reporter, dst == dst2);
+
+    //  a-----b            b-----c        c-----b
+    //  |     |     ->     |     |   ->   |     |
+    //  |     | Rotate 270 |     | Flip X |     |
+    //  d-----c            a-----d        d-----a
+    matrix.reset();
+    matrix.setScale(SkIntToScalar(-1), SkIntToScalar(1));
+    matrix.postRotate(SkIntToScalar(270));
+    dst2.setEmpty();
+    success = orig.transform(matrix, &dst2);
+    REPORTER_ASSERT(reporter, success);
+    REPORTER_ASSERT(reporter, dst == dst2);
+
+    //  a-----b        d-----c            c-----b
+    //  |     |   ->   |     |     ->     |     |
+    //  |     | Flip Y |     | Rotate 270 |     |
+    //  d-----c        a-----b            d-----a
+    matrix.reset();
+    matrix.setRotate(SkIntToScalar(270));
+    matrix.postScale(SkIntToScalar(1), SkIntToScalar(-1));
+    dst2.setEmpty();
+    success = orig.transform(matrix, &dst2);
+    REPORTER_ASSERT(reporter, success);
+    REPORTER_ASSERT(reporter, dst == dst2);
+
+    //  a-----b           d-----a        a-----d
+    //  |     |    ->     |     |   ->   |     |
+    //  |     | Rotate 90 |     | Flip X |     |
+    //  d-----c           c-----b        b-----c
+    matrix.reset();
+    matrix.setScale(SkIntToScalar(-1), SkIntToScalar(1));
+    matrix.postRotate(SkIntToScalar(90));
+    dst.setEmpty();
+    success = orig.transform(matrix, &dst);
+    REPORTER_ASSERT(reporter, success);
+    {
+        GET_RADII;
+        REPORTER_ASSERT(reporter, dstUL.x() == origUL.y());
+        REPORTER_ASSERT(reporter, dstUL.y() == origUL.x());
+        REPORTER_ASSERT(reporter, dstUR.x() == origLL.y());
+        REPORTER_ASSERT(reporter, dstUR.y() == origLL.x());
+        REPORTER_ASSERT(reporter, dstLR.x() == origLR.y());
+        REPORTER_ASSERT(reporter, dstLR.y() == origLR.x());
+        REPORTER_ASSERT(reporter, dstLL.x() == origUR.y());
+        REPORTER_ASSERT(reporter, dstLL.y() == origUR.x());
+    }
+    // Width and height would get swapped.
+    REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().height());
+    REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().width());
+
+    //  a-----b        d-----c           a-----d
+    //  |     |   ->   |     |    ->     |     |
+    //  |     | Flip Y |     | Rotate 90 |     |
+    //  d-----c        a-----b           b-----c
+    // This is the same as rotate 90 and flip x.
+    matrix.reset();
+    matrix.setRotate(SkIntToScalar(90));
+    matrix.postScale(SkIntToScalar(1), SkIntToScalar(-1));
+    dst2.setEmpty();
+    success = orig.transform(matrix, &dst2);
+    REPORTER_ASSERT(reporter, success);
+    REPORTER_ASSERT(reporter, dst == dst2);
+
+    //  a-----b        b-----a            a-----d
+    //  |     |   ->   |     |     ->     |     |
+    //  |     | Flip X |     | Rotate 270 |     |
+    //  d-----c        c-----d            b-----c
+    matrix.reset();
+    matrix.setRotate(SkIntToScalar(270));
+    matrix.postScale(SkIntToScalar(-1), SkIntToScalar(1));
+    dst2.setEmpty();
+    success = orig.transform(matrix, &dst2);
+    REPORTER_ASSERT(reporter, success);
+    REPORTER_ASSERT(reporter, dst == dst2);
+
+    //  a-----b            b-----c        a-----d
+    //  |     |     ->     |     |   ->   |     |
+    //  |     | Rotate 270 |     | Flip Y |     |
+    //  d-----c            a-----d        b-----c
+    matrix.reset();
+    matrix.setScale(SkIntToScalar(1), SkIntToScalar(-1));
+    matrix.postRotate(SkIntToScalar(270));
+    dst2.setEmpty();
+    success = orig.transform(matrix, &dst2);
+    REPORTER_ASSERT(reporter, success);
+    REPORTER_ASSERT(reporter, dst == dst2);
+
+
+    //  a-----b        b-----a        c-----d            b-----c
+    //  |     |   ->   |     |   ->   |     |    ->      |     |
+    //  |     | Flip X |     | Flip Y |     | Rotate 90  |     |
+    //  d-----c        c-----d        b-----a            a-----d
+    //
+    // This is the same as rotation by 270.
+    matrix.reset();
+    matrix.setRotate(SkIntToScalar(90));
+    matrix.postScale(SkIntToScalar(-1), SkIntToScalar(-1));
+    dst.setEmpty();
+    success = orig.transform(matrix, &dst);
+    REPORTER_ASSERT(reporter, success);
+    {
+        GET_RADII;
+        // Radii have cycled clockwise and swapped their x and y axis.
+        REPORTER_ASSERT(reporter, dstUL.x() == origUR.y());
+        REPORTER_ASSERT(reporter, dstUL.y() == origUR.x());
+        REPORTER_ASSERT(reporter, dstUR.x() == origLR.y());
+        REPORTER_ASSERT(reporter, dstUR.y() == origLR.x());
+        REPORTER_ASSERT(reporter, dstLR.x() == origLL.y());
+        REPORTER_ASSERT(reporter, dstLR.y() == origLL.x());
+        REPORTER_ASSERT(reporter, dstLL.x() == origUL.y());
+        REPORTER_ASSERT(reporter, dstLL.y() == origUL.x());
+    }
+    // Width and height would get swapped.
+    REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().height());
+    REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().width());
+
+    //  a-----b             b-----c
+    //  |     |     ->      |     |
+    //  |     | Rotate 270  |     |
+    //  d-----c             a-----d
+    //
+    dst2.setEmpty();
+    matrix.reset();
+    matrix.setRotate(SkIntToScalar(270));
+    success = orig.transform(matrix, &dst2);
+    REPORTER_ASSERT(reporter, success);
+    REPORTER_ASSERT(reporter, dst == dst2);
+
+    //  a-----b        b-----a        c-----d             d-----a
+    //  |     |   ->   |     |   ->   |     |     ->      |     |
+    //  |     | Flip X |     | Flip Y |     | Rotate 270  |     |
+    //  d-----c        c-----d        b-----a             c-----b
+    //
+    // This is the same as rotation by 90 degrees.
+    matrix.reset();
+    matrix.setRotate(SkIntToScalar(270));
+    matrix.postScale(SkIntToScalar(-1), SkIntToScalar(-1));
+    dst.setEmpty();
+    success = orig.transform(matrix, &dst);
+    REPORTER_ASSERT(reporter, success);
+
+    matrix.reset();
+    matrix.setRotate(SkIntToScalar(90));
+    dst2.setEmpty();
+    success = orig.transform(matrix, &dst2);
+    REPORTER_ASSERT(reporter, dst == dst2);
+
 }
 
 static void test_round_rect_transform(skiatest::Reporter* reporter) {
diff --git a/tests/SRGBReadWritePixelsTest.cpp b/tests/SRGBReadWritePixelsTest.cpp
index 461b662..54a1193 100644
--- a/tests/SRGBReadWritePixelsTest.cpp
+++ b/tests/SRGBReadWritePixelsTest.cpp
@@ -295,9 +295,10 @@
         // Reading untagged back as untagged should do no conversion.
         test_write_read(Encoding::kUntagged, writeEncoding, Encoding::kUntagged, error,
                         check_no_conversion, context, reporter);
-        // Reading untagged back as linear does no conversion.
+        // Reading untagged back as linear does convert (context is source, so treated as sRGB),
+        // dst is tagged.
         test_write_read(Encoding::kUntagged, writeEncoding, Encoding::kLinear, error,
-                        check_no_conversion, context, reporter);
+                        check_srgb_to_linear_conversion, context, reporter);
     }
 
     ///////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/tests/SVGDeviceTest.cpp b/tests/SVGDeviceTest.cpp
index 0a02f7c..9a99c0f 100644
--- a/tests/SVGDeviceTest.cpp
+++ b/tests/SVGDeviceTest.cpp
@@ -19,6 +19,7 @@
 #include "SkData.h"
 #include "SkImage.h"
 #include "SkImageShader.h"
+#include "SkMakeUnique.h"
 #include "SkParse.h"
 #include "SkShader.h"
 #include "SkStream.h"
@@ -30,9 +31,16 @@
 #ifdef SK_XML
 
 #include "SkDOM.h"
-#include "SkSVGCanvas.h"
+#include "../src/svg/SkSVGDevice.h"
 #include "SkXMLWriter.h"
 
+static std::unique_ptr<SkCanvas> MakeDOMCanvas(SkDOM* dom) {
+    auto svgDevice = SkSVGDevice::Make(SkISize::Make(100, 100),
+                                       skstd::make_unique<SkXMLParserWriter>(dom->beginParsing()));
+    return svgDevice ? skstd::make_unique<SkCanvas>(svgDevice)
+                     : nullptr;
+}
+
 #if 0
 Using the new system where devices only gets glyphs causes this to fail because the font has no
 glyph to unichar data.
@@ -118,8 +126,7 @@
     SkPoint offset = SkPoint::Make(10, 20);
 
     {
-        SkXMLParserWriter writer(dom.beginParsing());
-        std::unique_ptr<SkCanvas> svgCanvas = SkSVGCanvas::Make(SkRect::MakeWH(100, 100), &writer);
+        auto svgCanvas = MakeDOMCanvas(&dom);
         svgCanvas->drawSimpleText(txt, len, kUTF8_SkTextEncoding, offset.x(), offset.y(),
                                   font, paint);
     }
@@ -131,8 +138,7 @@
             xpos[i] = SkIntToScalar(txt[i]);
         }
 
-        SkXMLParserWriter writer(dom.beginParsing());
-        std::unique_ptr<SkCanvas> svgCanvas = SkSVGCanvas::Make(SkRect::MakeWH(100, 100), &writer);
+        auto svgCanvas = MakeDOMCanvas(&dom);
         auto blob = SkTextBlob::MakeFromPosTextH(txt, len, &xpos[0], offset.y(), font);
         svgCanvas->drawTextBlob(blob, 0, 0, paint);
     }
@@ -178,10 +184,10 @@
 #endif
 
 
-void SetImageShader(SkPaint* paint, int imageWidth, int imageHeight, SkShader::TileMode xTile,
-                    SkShader::TileMode yTile) {
+void SetImageShader(SkPaint* paint, int imageWidth, int imageHeight, SkTileMode xTile,
+                    SkTileMode yTile) {
     auto surface = SkSurface::MakeRasterN32Premul(imageWidth, imageHeight);
-    paint->setShader(SkImageShader::Make(surface->makeImageSnapshot(), xTile, yTile, nullptr));
+    paint->setShader(surface->makeImageSnapshot()->makeShader(xTile, yTile, nullptr));
 }
 
 // Attempt to find the three nodes on which we have expectations:
@@ -228,11 +234,9 @@
 }
 
 void ImageShaderTestSetup(SkDOM* dom, SkPaint* paint, int imageWidth, int imageHeight,
-                          int rectWidth, int rectHeight, SkShader::TileMode xTile,
-                          SkShader::TileMode yTile) {
+                          int rectWidth, int rectHeight, SkTileMode xTile, SkTileMode yTile) {
     SetImageShader(paint, imageWidth, imageHeight, xTile, yTile);
-    SkXMLParserWriter writer(dom->beginParsing());
-    std::unique_ptr<SkCanvas> svgCanvas = SkSVGCanvas::Make(SkRect::MakeWH(100, 100), &writer);
+    auto svgCanvas = MakeDOMCanvas(dom);
 
     SkRect bounds{0, 0, SkIntToScalar(rectWidth), SkIntToScalar(rectHeight)};
     svgCanvas->drawRect(bounds, *paint);
@@ -245,7 +249,7 @@
     int imageWidth = 3, imageHeight = 3;
     int rectWidth = 10, rectHeight = 10;
     ImageShaderTestSetup(&dom, &paint, imageWidth, imageHeight, rectWidth, rectHeight,
-                         SkShader::kClamp_TileMode, SkShader::kClamp_TileMode);
+                         SkTileMode::kClamp, SkTileMode::kClamp);
 
     const SkDOM::Node* root = dom.finishParsing();
 
@@ -270,7 +274,7 @@
     int imageWidth = 3, imageHeight = 3;
     int rectWidth = 10, rectHeight = 10;
     ImageShaderTestSetup(&dom, &paint, imageWidth, imageHeight, rectWidth, rectHeight,
-                         SkShader::kRepeat_TileMode, SkShader::kClamp_TileMode);
+                         SkTileMode::kRepeat, SkTileMode::kClamp);
 
     const SkDOM::Node* root = dom.finishParsing();
     const SkDOM::Node* innerSvg = dom.getFirstChild(root, "svg");
@@ -300,7 +304,7 @@
     int imageNodeWidth = 3, imageNodeHeight = 3;
     int rectNodeWidth = 10, rectNodeHeight = 10;
     ImageShaderTestSetup(&dom, &paint, imageNodeWidth, imageNodeHeight, rectNodeWidth,
-                         rectNodeHeight, SkShader::kClamp_TileMode, SkShader::kRepeat_TileMode);
+                         rectNodeHeight, SkTileMode::kClamp, SkTileMode::kRepeat);
 
     const SkDOM::Node* root = dom.finishParsing();
     const SkDOM::Node* innerSvg = dom.getFirstChild(root, "svg");
@@ -330,7 +334,7 @@
     int imageWidth = 3, imageHeight = 3;
     int rectWidth = 10, rectHeight = 10;
     ImageShaderTestSetup(&dom, &paint, imageWidth, imageHeight, rectWidth, rectHeight,
-                         SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode);
+                         SkTileMode::kRepeat, SkTileMode::kRepeat);
 
     const SkDOM::Node* root = dom.finishParsing();
 
@@ -355,10 +359,9 @@
 DEF_TEST(SVGDevice_ColorFilters, reporter) {
     SkDOM dom;
     SkPaint paint;
-    paint.setColorFilter(SkColorFilter::MakeModeFilter(SK_ColorRED, SkBlendMode::kSrcIn));
+    paint.setColorFilter(SkColorFilters::Blend(SK_ColorRED, SkBlendMode::kSrcIn));
     {
-        SkXMLParserWriter writer(dom.beginParsing());
-        std::unique_ptr<SkCanvas> svgCanvas = SkSVGCanvas::Make(SkRect::MakeWH(100, 100), &writer);
+        auto svgCanvas = MakeDOMCanvas(&dom);
         SkRect bounds{0, 0, SkIntToScalar(100), SkIntToScalar(100)};
         svgCanvas->drawRect(bounds, paint);
     }
diff --git a/tests/SamplePatternDictionaryTest.cpp b/tests/SamplePatternDictionaryTest.cpp
new file mode 100644
index 0000000..66b9b68
--- /dev/null
+++ b/tests/SamplePatternDictionaryTest.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTypes.h"
+#include "SkPoint.h"
+#include "SkRandom.h"
+#include "Test.h"
+#include <vector>
+
+#if SK_SUPPORT_GPU
+
+#include "GrSamplePatternDictionary.h"
+
+static SkTArray<SkPoint> make_sample_pattern(const std::vector<SkPoint>& sampleLocations) {
+    return SkTArray<SkPoint>(sampleLocations.data(), sampleLocations.size());
+}
+
+static SkTArray<SkPoint> make_random_sample_pattern(SkRandom* rand) {
+    SkTArray<SkPoint> pattern;
+    int count = rand->nextULessThan(20) + 1;
+    pattern.reset(count);
+    for (int i = 0; i < count; ++i) {
+        pattern[i] = SkPoint::Make(rand->nextF(), rand->nextF());
+    }
+    return pattern;
+}
+
+// This test ensures that the sample pattern dictionary caches and retrieves patterns correctly.
+DEF_TEST(SamplePatternDictionary, reporter) {
+    SkTArray<SkTArray<SkPoint>> testPatterns;
+    testPatterns.push_back() = make_sample_pattern({ // Intel on mac, msaa8, offscreen.
+        {0.562500, 0.312500},
+        {0.437500, 0.687500},
+        {0.812500, 0.562500},
+        {0.312500, 0.187500},
+        {0.187500, 0.812500},
+        {0.062500, 0.437500},
+        {0.687500, 0.937500},
+        {0.937500, 0.062500}
+    });
+
+    testPatterns.push_back() = make_sample_pattern({ // Intel on mac, msaa8, on-screen.
+        {0.562500, 0.687500},
+        {0.437500, 0.312500},
+        {0.812500, 0.437500},
+        {0.312500, 0.812500},
+        {0.187500, 0.187500},
+        {0.062500, 0.562500},
+        {0.687500, 0.062500},
+        {0.937500, 0.937500}
+    });
+
+    testPatterns.push_back() = make_sample_pattern({ // NVIDIA, msaa16.
+        {0.062500, 0.000000},
+        {0.250000, 0.125000},
+        {0.187500, 0.375000},
+        {0.437500, 0.312500},
+        {0.500000, 0.062500},
+        {0.687500, 0.187500},
+        {0.750000, 0.437500},
+        {0.937500, 0.250000},
+        {0.000000, 0.500000},
+        {0.312500, 0.625000},
+        {0.125000, 0.750000},
+        {0.375000, 0.875000},
+        {0.562500, 0.562500},
+        {0.812500, 0.687500},
+        {0.625000, 0.812500},
+        {0.875000, 0.937500}
+    });
+
+    testPatterns.push_back() = make_sample_pattern({ // NVIDIA, mixed samples, 16:1.
+        {0.250000, 0.125000},
+        {0.625000, 0.812500},
+        {0.500000, 0.062500},
+        {0.812500, 0.687500},
+        {0.187500, 0.375000},
+        {0.875000, 0.937500},
+        {0.125000, 0.750000},
+        {0.750000, 0.437500},
+        {0.937500, 0.250000},
+        {0.312500, 0.625000},
+        {0.437500, 0.312500},
+        {0.000000, 0.500000},
+        {0.375000, 0.875000},
+        {0.687500, 0.187500},
+        {0.062500, 0.000000},
+        {0.562500, 0.562500}
+    });
+
+    SkRandom rand;
+    for (int i = 0; i < 23; ++i) {
+        testPatterns.push_back(make_random_sample_pattern(&rand));
+    }
+
+    // Duplicate the initial 4 patterns, with slight differences.
+    testPatterns.push_back(testPatterns[0]);
+    testPatterns.back().back().fX += 0.001f;
+
+    testPatterns.push_back(testPatterns[1]);
+    testPatterns.back().back().fY -= 0.002f;
+
+    testPatterns.push_back(testPatterns[2]);
+    testPatterns.back().push_back(SkPoint::Make(.5f, .5f));
+
+    testPatterns.push_back(testPatterns[3]);
+    testPatterns.back().pop_back();
+
+    for (int i = 0; i < 13; ++i) {
+        testPatterns.push_back(make_random_sample_pattern(&rand));
+    }
+
+    GrSamplePatternDictionary dict;
+    for (int i = 0; i < 2; ++i) {
+        for (int j = 0; j < testPatterns.count(); ++j) {
+            for (int k = 0; k < 3; ++k) {
+                const SkTArray<SkPoint>& pattern = testPatterns[testPatterns.count() - j - 1];
+                REPORTER_ASSERT(reporter, j == dict.findOrAssignSamplePatternKey(pattern));
+            }
+        }
+    }
+    for (int j = 0; j < testPatterns.count(); ++j) {
+        const SkTArray<SkPoint>& pattern = testPatterns[testPatterns.count() - j - 1];
+        REPORTER_ASSERT(reporter, dict.retrieveSampleLocations(j) == pattern);
+    }
+}
+
+#endif
diff --git a/tests/SerialProcsTest.cpp b/tests/SerialProcsTest.cpp
index 6129e0a..2e11a0e 100644
--- a/tests/SerialProcsTest.cpp
+++ b/tests/SerialProcsTest.cpp
@@ -5,15 +5,15 @@
  * found in the LICENSE file.
  */
 
-#include "Test.h"
 #include "Resources.h"
-#include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkImageSource.h"
 #include "SkPicture.h"
 #include "SkPictureRecorder.h"
 #include "SkSerialProcs.h"
 #include "SkSurface.h"
+#include "Test.h"
+#include "ToolUtils.h"
 
 static sk_sp<SkImage> picture_to_image(sk_sp<SkPicture> pic) {
     SkIRect r = pic->cullRect().round();
@@ -77,7 +77,7 @@
         REPORTER_ASSERT(reporter, data);
 
         auto dst_img = picture_to_image(new_pic);
-        REPORTER_ASSERT(reporter, sk_tool_utils::equal_pixels(src_img.get(), dst_img.get()));
+        REPORTER_ASSERT(reporter, ToolUtils::equal_pixels(src_img.get(), dst_img.get()));
     }
 }
 
@@ -163,8 +163,8 @@
     // test inside effect
     p0 = make_pic([p1](SkCanvas* c) {
         SkPaint paint;
-        SkShader::TileMode tm = SkShader::kClamp_TileMode;
-        paint.setShader(SkShader::MakePictureShader(p1, tm, tm, nullptr, nullptr));
+        SkTileMode tm = SkTileMode::kClamp;
+        paint.setShader(p1->makeShader(tm, tm));
         c->drawPaint(paint);
     });
     test_pictures(reporter, p0, 1, true);
diff --git a/tests/SerializationTest.cpp b/tests/SerializationTest.cpp
index 6712575..ddfe073 100644
--- a/tests/SerializationTest.cpp
+++ b/tests/SerializationTest.cpp
@@ -19,9 +19,9 @@
 #include "SkMatrixPriv.h"
 #include "SkNormalSource.h"
 #include "SkOSFile.h"
-#include "SkReadBuffer.h"
 #include "SkPicturePriv.h"
 #include "SkPictureRecorder.h"
+#include "SkReadBuffer.h"
 #include "SkShaderBase.h"
 #include "SkTableColorFilter.h"
 #include "SkTemplates.h"
@@ -29,8 +29,8 @@
 #include "SkTypeface.h"
 #include "SkWriteBuffer.h"
 #include "SkXfermodeImageFilter.h"
-#include "sk_tool_utils.h"
 #include "Test.h"
+#include "ToolUtils.h"
 
 static const uint32_t kArraySize = 64;
 static const int kBitmapSize = 256;
@@ -588,11 +588,8 @@
 
         sk_sp<SkLights> fLights = builder.finish();
 
-        SkBitmap diffuse = sk_tool_utils::create_checkerboard_bitmap(
-                kTexSize, kTexSize,
-                0x00000000,
-                sk_tool_utils::color_to_565(0xFF804020),
-                8);
+        SkBitmap diffuse = ToolUtils::create_checkerboard_bitmap(
+                kTexSize, kTexSize, 0x00000000, ToolUtils::color_to_565(0xFF804020), 8);
 
         SkRect bitmapBounds = SkRect::MakeIWH(diffuse.width(), diffuse.height());
 
@@ -605,13 +602,11 @@
         SkBitmap normals;
         normals.allocN32Pixels(kTexSize, kTexSize);
 
-        sk_tool_utils::create_frustum_normal_map(&normals, SkIRect::MakeWH(kTexSize, kTexSize));
-        sk_sp<SkShader> normalMap = SkShader::MakeBitmapShader(normals, SkShader::kClamp_TileMode,
-                SkShader::kClamp_TileMode, &matrix);
+        ToolUtils::create_frustum_normal_map(&normals, SkIRect::MakeWH(kTexSize, kTexSize));
+        sk_sp<SkShader> normalMap = normals.makeShader(&matrix);
         sk_sp<SkNormalSource> normalSource = SkNormalSource::MakeFromNormalMap(std::move(normalMap),
                                                                                ctm);
-        sk_sp<SkShader> diffuseShader = SkShader::MakeBitmapShader(diffuse,
-                SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, &matrix);
+        sk_sp<SkShader> diffuseShader = diffuse.makeShader(&matrix);
 
         sk_sp<SkShader> lightingShader = SkLightingShader::Make(diffuseShader,
                                                                 normalSource,
diff --git a/tests/ShaderOpacityTest.cpp b/tests/ShaderOpacityTest.cpp
index b154446..53f3146 100644
--- a/tests/ShaderOpacityTest.cpp
+++ b/tests/ShaderOpacityTest.cpp
@@ -18,8 +18,7 @@
     bmp.setInfo(info);
 
     // test 1: bitmap without pixel data
-    auto shader = SkShader::MakeBitmapShader(bmp,
-        SkShader::kClamp_TileMode, SkShader::kClamp_TileMode);
+    auto shader = bmp.makeShader(SkTileMode::kClamp, SkTileMode::kClamp);
     REPORTER_ASSERT(reporter, shader);
     REPORTER_ASSERT(reporter, !shader->isOpaque());
 
@@ -27,22 +26,19 @@
     bmp.allocPixels(info);
 
     // test 2: not opaque by default
-    shader = SkShader::MakeBitmapShader(bmp,
-        SkShader::kClamp_TileMode, SkShader::kClamp_TileMode);
+    shader = bmp.makeShader();
     REPORTER_ASSERT(reporter, shader);
     REPORTER_ASSERT(reporter, !shader->isOpaque());
 
     // test 3: explicitly opaque
     bmp.setAlphaType(kOpaque_SkAlphaType);
-    shader = SkShader::MakeBitmapShader(bmp,
-        SkShader::kClamp_TileMode, SkShader::kClamp_TileMode);
+    shader = bmp.makeShader();
     REPORTER_ASSERT(reporter, shader);
     REPORTER_ASSERT(reporter, shader->isOpaque());
 
     // test 4: explicitly not opaque
     bmp.setAlphaType(kPremul_SkAlphaType);
-    shader = SkShader::MakeBitmapShader(bmp,
-        SkShader::kClamp_TileMode, SkShader::kClamp_TileMode);
+    shader = bmp.makeShader();
     REPORTER_ASSERT(reporter, shader);
     REPORTER_ASSERT(reporter, !shader->isOpaque());
 }
@@ -54,7 +50,7 @@
     SkColor colors[2];
     SkScalar pos[2] = {SkIntToScalar(0), SkIntToScalar(1)};
     int count = 2;
-    SkShader::TileMode mode = SkShader::kClamp_TileMode;
+    SkTileMode mode = SkTileMode::kClamp;
 
     // test 1: all opaque
     colors[0] = SkColorSetARGB(0xFF, 0, 0, 0);
diff --git a/tests/ShaderTest.cpp b/tests/ShaderTest.cpp
index 650eb5d..d2e9379 100644
--- a/tests/ShaderTest.cpp
+++ b/tests/ShaderTest.cpp
@@ -17,14 +17,14 @@
 
 static void check_isaimage(skiatest::Reporter* reporter, SkShader* shader,
                            int expectedW, int expectedH,
-                           SkShader::TileMode expectedX, SkShader::TileMode expectedY,
+                           SkTileMode expectedX, SkTileMode expectedY,
                            const SkMatrix& expectedM) {
-    SkShader::TileMode tileModes[2];
+    SkTileMode tileModes[2];
     SkMatrix localM;
 
     // wack these so we don't get a false positive
     localM.setScale(9999, -9999);
-    tileModes[0] = tileModes[1] = (SkShader::TileMode)99;
+    tileModes[0] = tileModes[1] = (SkTileMode)99;
 
     SkImage* image = shader->isAImage(&localM, tileModes);
     REPORTER_ASSERT(reporter, image);
@@ -42,10 +42,10 @@
     bm.allocN32Pixels(W, H);
     auto img = SkImage::MakeFromBitmap(bm);
     const SkMatrix localM = SkMatrix::MakeScale(2, 3);
-    const SkShader::TileMode tmx = SkShader::kRepeat_TileMode;
-    const SkShader::TileMode tmy = SkShader::kMirror_TileMode;
+    const SkTileMode tmx = SkTileMode::kRepeat;
+    const SkTileMode tmy = SkTileMode::kMirror;
 
-    auto shader0 = SkShader::MakeBitmapShader(bm, tmx, tmy, &localM);
+    auto shader0 = bm.makeShader(tmx, tmy, &localM);
     auto shader1 = SkImage::MakeFromBitmap(bm)->makeShader(tmx, tmy, &localM);
 
     check_isaimage(reporter, shader0.get(), W, H, tmx, tmy, localM);
@@ -60,10 +60,9 @@
     SkCanvas canvas(srcBitmap);
     SkPaint p;
     p.setShader(
-        SkShader::MakeComposeShader(
-        SkShader::MakeEmptyShader(),
-        SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 2, 0.0f),
-        SkBlendMode::kClear));
+        SkShaders::Blend(SkBlendMode::kClear,
+        SkShaders::Empty(),
+        SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 2, 0.0f)));
     SkRRect rr;
     SkVector rd[] = {{0, 0}, {0, 0}, {0, 0}, {0, 0}};
     rr.setRectRadii({0, 0, 0, 0}, rd);
diff --git a/tests/SkRemoteGlyphCacheTest.cpp b/tests/SkRemoteGlyphCacheTest.cpp
index 0dd504e..07cad45 100644
--- a/tests/SkRemoteGlyphCacheTest.cpp
+++ b/tests/SkRemoteGlyphCacheTest.cpp
@@ -15,10 +15,11 @@
 #include "SkStrikeCache.h"
 #include "SkSurface.h"
 #include "SkSurfacePriv.h"
-#include "SkTestEmptyTypeface.h"
 #include "SkTextBlob.h"
 #include "SkTypeface_remote.h"
 #include "Test.h"
+#include "TestEmptyTypeface.h"
+#include "ToolUtils.h"
 
 #include "text/GrTextContext.h"
 
@@ -30,30 +31,67 @@
 
     // Server implementation.
     SkDiscardableHandleId createHandle() override {
+        SkAutoMutexAcquire l(&fMutex);
+
         // Handles starts as locked.
         fLockedHandles.add(++fNextHandleId);
         return fNextHandleId;
     }
     bool lockHandle(SkDiscardableHandleId id) override {
+        SkAutoMutexAcquire l(&fMutex);
+
         if (id <= fLastDeletedHandleId) return false;
         fLockedHandles.add(id);
         return true;
     }
 
     // Client implementation.
-    bool deleteHandle(SkDiscardableHandleId id) override { return id <= fLastDeletedHandleId; }
-    void notifyCacheMiss(SkStrikeClient::CacheMissType type) override { fCacheMissCount[type]++; }
-    bool isHandleDeleted(SkDiscardableHandleId id) override { return id <= fLastDeletedHandleId; }
+    bool deleteHandle(SkDiscardableHandleId id) override {
+        SkAutoMutexAcquire l(&fMutex);
 
-    void unlockAll() { fLockedHandles.reset(); }
+        return id <= fLastDeletedHandleId;
+    }
+
+    void notifyCacheMiss(SkStrikeClient::CacheMissType type) override {
+        SkAutoMutexAcquire l(&fMutex);
+
+        fCacheMissCount[type]++;
+    }
+    bool isHandleDeleted(SkDiscardableHandleId id) override {
+        SkAutoMutexAcquire l(&fMutex);
+
+        return id <= fLastDeletedHandleId;
+    }
+
+    void unlockAll() {
+        SkAutoMutexAcquire l(&fMutex);
+
+        fLockedHandles.reset();
+    }
     void unlockAndDeleteAll() {
-        unlockAll();
+        SkAutoMutexAcquire l(&fMutex);
+
+        fLockedHandles.reset();
         fLastDeletedHandleId = fNextHandleId;
     }
-    const SkTHashSet<SkDiscardableHandleId>& lockedHandles() const { return fLockedHandles; }
-    SkDiscardableHandleId handleCount() { return fNextHandleId; }
-    int cacheMissCount(uint32_t type) { return fCacheMissCount[type]; }
+    const SkTHashSet<SkDiscardableHandleId>& lockedHandles() const {
+        SkAutoMutexAcquire l(&fMutex);
+
+        return fLockedHandles;
+    }
+    SkDiscardableHandleId handleCount() {
+        SkAutoMutexAcquire l(&fMutex);
+
+        return fNextHandleId;
+    }
+    int cacheMissCount(uint32_t type) {
+        SkAutoMutexAcquire l(&fMutex);
+
+        return fCacheMissCount[type];
+    }
     bool hasCacheMiss() const {
+        SkAutoMutexAcquire l(&fMutex);
+
         for (uint32_t i = 0; i <= SkStrikeClient::CacheMissType::kLast; ++i) {
             if (fCacheMissCount[i] > 0) return true;
         }
@@ -61,6 +99,11 @@
     }
 
 private:
+    // The tests below run in parallel on multiple threads and use the same
+    // process global SkStrikeCache. So the implementation needs to be
+    // thread-safe.
+    mutable SkMutex fMutex;
+
     SkDiscardableHandleId fNextHandleId = 0u;
     SkDiscardableHandleId fLastDeletedHandleId = 0u;
     SkTHashSet<SkDiscardableHandleId> fLockedHandles;
@@ -133,7 +176,7 @@
                     SkScalar x = 0) {
     auto surface = MakeSurface(width, height, context);
     if (matrix) surface->getCanvas()->concat(*matrix);
-    surface->getCanvas()->drawTextBlob(blob.get(), x, 0, paint);
+    surface->getCanvas()->drawTextBlob(blob.get(), x, height/2, paint);
     SkBitmap bitmap;
     bitmap.allocN32Pixels(width, height);
     surface->readPixels(bitmap, 0, 0);
@@ -170,7 +213,7 @@
     int glyphCount = 10;
     auto serverBlob = buildTextBlob(serverTf, glyphCount);
     auto props = FindSurfaceProps(ctxInfo.grContext());
-    SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, SkMatrix::I(), props, &server,
+    SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server,
                                                 MakeSettings(ctxInfo.grContext()));
     cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
 
@@ -198,7 +241,7 @@
     SkStrikeClient client(discardableManager, false);
 
     // Server.
-    auto serverTf = SkTestEmptyTypeface::Make();
+    auto serverTf     = TestEmptyTypeface::Make();
     auto serverTfData = server.serializeTypeface(serverTf.get());
     REPORTER_ASSERT(reporter, serverTf->unique());
 
@@ -207,7 +250,7 @@
         int glyphCount = 10;
         auto serverBlob = buildTextBlob(serverTf, glyphCount);
         const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
-        SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, SkMatrix::I(), props, &server,
+        SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server,
                                                     MakeSettings(ctxInfo.grContext()));
         cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
         REPORTER_ASSERT(reporter, !serverTf->unique());
@@ -232,7 +275,7 @@
     auto serverBlob = buildTextBlob(serverTf, glyphCount);
 
     const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
-    SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, SkMatrix::I(), props, &server);
+    SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server);
     SkPaint paint;
     cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
 
@@ -266,7 +309,7 @@
     auto serverBlob = buildTextBlob(serverTf, glyphCount);
 
     const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
-    SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, SkMatrix::I(), props, &server);
+    SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server);
     SkPaint paint;
     cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
     REPORTER_ASSERT(reporter, discardableManager->handleCount() == 1u);
@@ -296,7 +339,7 @@
     auto serverBlob = buildTextBlob(serverTf, glyphCount);
 
     const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
-    SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, SkMatrix::I(), props, &server);
+    SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server);
     SkPaint paint;
     cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
 
@@ -335,7 +378,7 @@
     auto serverBlob = buildTextBlob(serverTf, glyphCount);
 
     const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
-    SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, SkMatrix::I(), props, &server);
+    SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server);
     SkPaint paint;
     cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
 
@@ -363,7 +406,7 @@
         auto serverBlob = buildTextBlob(serverTf, glyphCount);
 
         const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
-        SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, SkMatrix::I(), props, &server);
+        SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server);
         SkPaint paint;
         REPORTER_ASSERT(reporter, server.remoteGlyphStateMapSizeForTesting() == 0u);
         cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
@@ -384,7 +427,7 @@
         auto serverBlob = buildTextBlob(serverTf, glyphCount);
 
         const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
-        SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, SkMatrix::I(), props, &server);
+        SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server);
         SkPaint paint;
         REPORTER_ASSERT(reporter, server.remoteGlyphStateMapSizeForTesting() == 1u);
         cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
@@ -411,7 +454,7 @@
     int glyphCount = 10;
     auto serverBlob = buildTextBlob(serverTf, glyphCount);
     auto props = FindSurfaceProps(ctxInfo.grContext());
-    SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, SkMatrix::I(), props, &server,
+    SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server,
                                                 MakeSettings(ctxInfo.grContext()));
     cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
 
@@ -485,7 +528,7 @@
     auto serverBlob = make_blob_causing_fallback(serverTf, serverTf.get(), reporter);
 
     auto props = FindSurfaceProps(ctxInfo.grContext());
-    SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, SkMatrix::I(), props, &server,
+    SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server,
                                                 MakeSettings(ctxInfo.grContext()));
     cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
 
@@ -509,6 +552,82 @@
     discardableManager->unlockAndDeleteAll();
 }
 
+#if 0
+// TODO: turn this one when I figure out how to deal with the pixel variance from linear
+//  interpolation from GPU to GPU.
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_DrawTextAsSDFTWithAllARGBFallback,
+                                   reporter, ctxInfo) {
+    sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
+    SkStrikeServer server(discardableManager.get());
+    SkStrikeClient client(discardableManager, false);
+
+    SkPaint paint;
+
+    auto serverTf = ToolUtils::planet_typeface();
+    // TODO: when the cq bots can handle this font remove the check.
+    if (serverTf == nullptr) {
+        return;
+    }
+    auto serverTfData = server.serializeTypeface(serverTf.get());
+
+    auto makeBlob = [&reporter](sk_sp<SkTypeface> typeface) {
+        SkFont font;
+        font.setSubpixel(true);
+        font.setSize(96);
+        font.setHinting(kNormal_SkFontHinting);
+        font.setTypeface(typeface);
+
+        REPORTER_ASSERT(reporter, !SkDraw::ShouldDrawTextAsPaths(font, SkPaint(), SkMatrix::I()));
+
+        // Mercury to Uranus.
+        SkGlyphID glyphs[] = {1, 2, 3, 4, 5, 6, 7, 8};
+
+        SkTextBlobBuilder builder;
+        SkRect bounds = SkRect::MakeIWH(100, 100);
+        const auto& runBuffer = builder.allocRunPosH(font, SK_ARRAY_COUNT(glyphs), 100, &bounds);
+        SkASSERT(runBuffer.utf8text == nullptr);
+        SkASSERT(runBuffer.clusters == nullptr);
+
+        std::copy(std::begin(glyphs), std::end(glyphs), runBuffer.glyphs);
+
+        for (size_t i = 0; i < SK_ARRAY_COUNT(glyphs); i++) {
+            runBuffer.pos[i] = i * 100;
+        }
+
+        return builder.make();
+    };
+
+    auto serverBlob = makeBlob(serverTf);
+
+    auto props = FindSurfaceProps(ctxInfo.grContext());
+    SkTextBlobCacheDiffCanvas cache_diff_canvas(800, 800, props, &server,
+                                                MakeSettings(ctxInfo.grContext()));
+    cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 400, paint);
+
+    std::vector<uint8_t> serverStrikeData;
+    server.writeStrikeData(&serverStrikeData);
+
+    // Client.
+    auto clientTf = client.deserializeTypeface(serverTfData->data(), serverTfData->size());
+    REPORTER_ASSERT(reporter,
+                    client.readStrikeData(serverStrikeData.data(), serverStrikeData.size()));
+
+    auto clientBlob = makeBlob(clientTf);
+
+    SkBitmap expected = RasterBlob(serverBlob, 800, 800, paint, ctxInfo.grContext());
+    SkBitmap actual = RasterBlob(clientBlob, 800, 800, paint, ctxInfo.grContext());
+
+    // Pixel variance can be high because of the atlas placement, and large scaling in the linear
+    // interpolation.
+    compare_blobs(expected, actual, reporter, 36);
+    REPORTER_ASSERT(reporter, !discardableManager->hasCacheMiss());
+    SkStrikeCache::ValidateGlyphCacheDataSize();
+
+    // Must unlock everything on termination, otherwise valgrind complains about memory leaks.
+    discardableManager->unlockAndDeleteAll();
+}
+#endif
+
 DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkRemoteGlyphCache_DrawTextXY, reporter, ctxInfo) {
     sk_sp<DiscardableManager> discardableManager = sk_make_sp<DiscardableManager>();
     SkStrikeServer server(discardableManager.get());
@@ -523,7 +642,7 @@
     int glyphCount = 10;
     auto serverBlob = buildTextBlob(serverTf, glyphCount);
     auto props = FindSurfaceProps(ctxInfo.grContext());
-    SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, SkMatrix::I(), props, &server,
+    SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server,
                                                 MakeSettings(ctxInfo.grContext()));
     cache_diff_canvas.drawTextBlob(serverBlob.get(), 0.5, 0, paint);
 
@@ -570,7 +689,7 @@
     int glyphCount = 10;
     auto serverBlob = buildTextBlob(serverTf, glyphCount);
     const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
-    SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, SkMatrix::I(), props, &server,
+    SkTextBlobCacheDiffCanvas cache_diff_canvas(10, 10, props, &server,
                                                 MakeSettings(ctxInfo.grContext()));
     cache_diff_canvas.concat(matrix);
     cache_diff_canvas.drawTextBlob(serverBlob.get(), 0, 0, paint);
@@ -831,12 +950,6 @@
         REPORTER_ASSERT(reporter, fallbackCache.get() != nullptr);
         auto glyph = fallbackCache->getRawGlyphByID(lostGlyphID);
         REPORTER_ASSERT(reporter, glyph->fMaskFormat == fakeMask);
-
-        // Try overriding the image, it should stay the same.
-        REPORTER_ASSERT(reporter,
-                        memcmp(glyph->fImage, glyphImage, glyph->computeImageSize()) == 0);
-        const uint8_t newGlyphImage[] = {0, 0};
-        fallbackCache->initializeImage(newGlyphImage, glyph->computeImageSize(), glyph);
         REPORTER_ASSERT(reporter,
                         memcmp(glyph->fImage, glyphImage, glyph->computeImageSize()) == 0);
     }
diff --git a/tests/SkSLErrorTest.cpp b/tests/SkSLErrorTest.cpp
index 2a6e9d1..bfc522a 100644
--- a/tests/SkSLErrorTest.cpp
+++ b/tests/SkSLErrorTest.cpp
@@ -510,3 +510,9 @@
                  "layout (location=0, index=0) out half4 duplicateOutput;",
                  "error: 1: out location=0, index=0 is reserved for sk_FragColor\n1 error\n");
 }
+
+DEF_TEST(SkSLConstantSwizzleNotLast, r) {
+    test_failure(r,
+                 "void main() { sk_FragColor = half4(1).rg00; }",
+                 "error: 1: only the last swizzle component can be a constant\n1 error\n");
+}
diff --git a/tests/SkSLFPTest.cpp b/tests/SkSLFPTest.cpp
index 8fadcf3..c4597f2 100644
--- a/tests/SkSLFPTest.cpp
+++ b/tests/SkSLFPTest.cpp
@@ -88,8 +88,8 @@
              "    : INHERITED(kGrTest_ClassID, kNone_OptimizationFlags) {\n"
              "    }\n"
              "    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;\n"
-             "    void onGetGLSLProcessorKey(const GrShaderCaps&,GrProcessorKeyBuilder*) "
-                    "const override;\n"
+             "    void onGetGLSLProcessorKey(const GrShaderCaps&,GrProcessorKeyBuilder*) const "
+                 "override;\n"
              "    bool onIsEqual(const GrFragmentProcessor&) const override;\n"
              "    GR_DECLARE_FRAGMENT_PROCESSOR_TEST\n"
              "    typedef GrFragmentProcessor INHERITED;\n"
@@ -97,8 +97,6 @@
              "#endif\n"
          },
          {
-             "/* HEADER */\n"
-             "\n"
              "/**************************************************************************************************\n"
              " *** This file was autogenerated from GrTest.fp; do not modify.\n"
              " **************************************************************************************************/\n"
@@ -151,17 +149,16 @@
          "}",
          *SkSL::ShaderCapsFactory::Default(),
          {
-             "const SkPoint& point() const { return fPoint; }",
              "static std::unique_ptr<GrFragmentProcessor> Make(SkPoint point) {",
              "return std::unique_ptr<GrFragmentProcessor>(new GrTest(point));",
              "GrTest(SkPoint point)",
-             ", fPoint(point)"
+             ", point(point)"
          },
          {
              "fragBuilder->codeAppendf(\"%s = half4(half2(%f, %f), half2(%f, %f));\\n\", "
-                                      "args.fOutputColor, _outer.point().fX, _outer.point().fY, "
-                                      "_outer.point().fX, _outer.point().fY);",
-             "if (fPoint != that.fPoint) return false;"
+                                      "args.fOutputColor, _outer.point.fX, _outer.point.fY, "
+                                      "_outer.point.fX, _outer.point.fY);",
+             "if (point != that.point) return false;"
          });
 }
 
@@ -176,8 +173,8 @@
              "static std::unique_ptr<GrFragmentProcessor> Make()"
          },
          {
-            "fColorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "
-                                                         "kDefault_GrSLPrecision, \"color\");",
+            "colorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "
+                                                        "\"color\");",
          });
 }
 
@@ -195,9 +192,9 @@
              "static std::unique_ptr<GrFragmentProcessor> Make(SkRect color) {",
          },
          {
-            "fColorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "
-                                                         "kDefault_GrSLPrecision, \"color\");",
-            "pdman.set4fv(fColorVar, 1, reinterpret_cast<const float*>(&(_outer.color())));"
+            "colorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "
+                                                        "\"color\");",
+            "pdman.set4fv(colorVar, 1, reinterpret_cast<const float*>(&(_outer.color)));"
          });
 }
 
@@ -213,9 +210,9 @@
              "static std::unique_ptr<GrFragmentProcessor> Make(SkPMColor4f color) {",
          },
          {
-            "fColorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "
-                                                         "kDefault_GrSLPrecision, \"color\");",
-            "pdman.set4fv(fColorVar, 1, (_outer.color()).vec());"
+            "colorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "
+                                                        "\"color\");",
+            "pdman.set4fv(colorVar, 1, (_outer.color).vec());"
          });
 }
 
@@ -233,13 +230,13 @@
              "static std::unique_ptr<GrFragmentProcessor> Make(SkRect color) {",
          },
          {
-            "SkRect fColorPrev = SkRect::MakeEmpty();",
-            "fColorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "
-                                                         "kDefault_GrSLPrecision, \"color\");",
-            "const SkRect& colorValue = _outer.color();",
-            "if (fColorPrev.isEmpty() || fColorPrev != colorValue) {",
-            "fColorPrev = colorValue;",
-            "pdman.set4fv(fColorVar, 1, reinterpret_cast<const float*>(&colorValue));"
+            "SkRect colorPrev = SkRect::MakeEmpty();",
+            "colorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "
+                                                        "\"color\");",
+            "const SkRect& colorValue = _outer.color;",
+            "if (colorPrev.isEmpty() || colorPrev != colorValue) {",
+            "colorPrev = colorValue;",
+            "pdman.set4fv(colorVar, 1, reinterpret_cast<const float*>(&colorValue));"
          });
 }
 
@@ -256,10 +253,10 @@
              "static std::unique_ptr<GrFragmentProcessor> Make(SkPoint point) {",
          },
          {
-            "fPointVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, "
-                                                         "kDefault_GrSLPrecision, \"point\");",
-            "const SkPoint& pointValue = _outer.point();",
-            "pdman.set2f(fPointVar, pointValue.fX, pointValue.fY);"
+            "pointVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, "
+                                                        "\"point\");",
+            "const SkPoint& pointValue = _outer.point;",
+            "pdman.set2f(pointVar, pointValue.fX, pointValue.fY);"
          });
 }
 
@@ -281,16 +278,16 @@
              "static std::unique_ptr<GrFragmentProcessor> Make(bool test, SkPMColor4f color) {",
          },
          {
-            "SkPMColor4f fColorPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}",
-            "auto test = _outer.test();",
+            "SkPMColor4f colorPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}",
+            "auto test = _outer.test;",
             "if (test) {",
-            "fColorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "
-                                                         "kDefault_GrSLPrecision, \"color\");",
-            "if (fColorVar.isValid()) {",
-            "const SkPMColor4f& colorValue = _outer.color();",
-            "if (fColorPrev != colorValue) {",
-            "fColorPrev = colorValue;",
-            "pdman.set4fv(fColorVar, 1, colorValue.vec());"
+            "colorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "
+                                                        "\"color\");",
+            "if (colorVar.isValid()) {",
+            "const SkPMColor4f& colorValue = _outer.color;",
+            "if (colorPrev != colorValue) {",
+            "colorPrev = colorValue;",
+            "pdman.set4fv(colorVar, 1, colorValue.vec());"
          });
 }
 
@@ -336,7 +333,7 @@
              "Make(float w,  int x, float y, std::vector<float> z )",
              "return std::unique_ptr<GrFragmentProcessor>(new GrTest(w, x, y, z));",
              "GrTest(float w,  int x, float y, std::vector<float> z )",
-             ", fW(w) {"
+             ", w(w) {"
          },
          {});
     test(r,
@@ -379,8 +376,8 @@
          "}",
          *SkSL::ShaderCapsFactory::Default(),
          {
-            "GR_DECLARE_FRAGMENT_PROCESSOR_TEST\n"
-            " fields section     typedef GrFragmentProcessor INHERITED;"
+            "const char* name() const override { return \"Test\"; }\n"
+            " fields section private:"
          },
          {});
     test(r,
@@ -406,8 +403,8 @@
          {
              "void onSetData(const GrGLSLProgramDataManager& varName, "
                             "const GrFragmentProcessor& _proc) override {\n",
-             "UniformHandle& calculated = fCalculatedVar;",
-             "auto provided = _outer.provided();",
+             "UniformHandle& calculated = calculatedVar;",
+             "auto provided = _outer.provided;",
              "varName.set1f(calculated, provided * 2);"
          });
     test(r,
@@ -450,7 +447,7 @@
          {},
          {
             "if (someExpression(someOtherExpression())) {\n"
-            "            fSometimesVar = args.fUniformHandler->addUniform"
+            "            sometimesVar = args.fUniformHandler->addUniform"
          });
 
 }
@@ -469,11 +466,11 @@
          },
          {
             "SkString _child0(\"_child0\");",
-            "this->emitChild(0, &_child0, args);",
+            "this->emitChild(_outer.child1_index, &_child0, args);",
             "SkString _child1(\"_child1\");",
-            "this->emitChild(1, &_child1, args);",
-            "this->registerChildProcessor(src.childProcessor(0).clone());",
-            "this->registerChildProcessor(src.childProcessor(1).clone());"
+            "this->emitChild(_outer.child2_index, &_child1, args);",
+            "this->registerChildProcessor(src.childProcessor(child1_index).clone());",
+            "this->registerChildProcessor(src.childProcessor(child2_index).clone());"
          });
 }
 
@@ -495,12 +492,12 @@
          {
             "SkString _input0(\"childIn\");",
             "SkString _child0(\"_child0\");",
-            "this->emitChild(0, _input0.c_str(), &_child0, args);",
+            "this->emitChild(_outer.child1_index, _input0.c_str(), &_child0, args);",
             "SkString _input1(\"childOut1\");",
             "SkString _child1(\"_child1\");",
-            "this->emitChild(1, _input1.c_str(), &_child1, args);",
-            "this->registerChildProcessor(src.childProcessor(0).clone());",
-            "this->registerChildProcessor(src.childProcessor(1).clone());"
+            "this->emitChild(_outer.child2_index, _input1.c_str(), &_child1, args);",
+            "this->registerChildProcessor(src.childProcessor(child1_index).clone());",
+            "this->registerChildProcessor(src.childProcessor(child2_index).clone());"
          });
 }
 
@@ -517,8 +514,8 @@
          {
             "SkString _input0 = SkStringPrintf(\"%s * half4(0.5)\", args.fInputColor);",
             "SkString _child0(\"_child0\");",
-            "this->emitChild(0, _input0.c_str(), &_child0, args);",
-            "this->registerChildProcessor(src.childProcessor(0).clone());",
+            "this->emitChild(_outer.child_index, _input0.c_str(), &_child0, args);",
+            "this->registerChildProcessor(src.childProcessor(child_index).clone());",
          });
 }
 
@@ -537,12 +534,12 @@
          {
             "SkString _input0 = SkStringPrintf(\"%s * half4(0.5)\", args.fInputColor);",
             "SkString _child0(\"_child0\");",
-            "this->emitChild(0, _input0.c_str(), &_child0, args);",
+            "this->emitChild(_outer.child1_index, _input0.c_str(), &_child0, args);",
             "SkString _input1 = SkStringPrintf(\"%s * %s\", args.fInputColor, _child0.c_str());",
             "SkString _child1(\"_child1\");",
-            "this->emitChild(1, _input1.c_str(), &_child1, args);",
-            "this->registerChildProcessor(src.childProcessor(0).clone());",
-            "this->registerChildProcessor(src.childProcessor(1).clone());"
+            "this->emitChild(_outer.child2_index, _input1.c_str(), &_child1, args);",
+            "this->registerChildProcessor(src.childProcessor(child1_index).clone());",
+            "this->registerChildProcessor(src.childProcessor(child2_index).clone());"
          });
 }
 
@@ -567,10 +564,10 @@
                     "(hasCap ? \"true\" : \"false\"));",
             "SkString _input0 = SkStringPrintf(\"%s\", args.fInputColor);",
             "SkString _child0(\"_child0\");",
-            "this->emitChild(0, _input0.c_str(), &_child0, args);",
+            "this->emitChild(_outer.child_index, _input0.c_str(), &_child0, args);",
             "fragBuilder->codeAppendf(\"\\n    %s = %s;\\n} else {\\n    %s = half4(1.0);\\n}"
                     "\\n\", args.fOutputColor, _child0.c_str(), args.fOutputColor);",
-            "this->registerChildProcessor(src.childProcessor(0).clone());"
+            "this->registerChildProcessor(src.childProcessor(child_index).clone());"
          });
 }
 
@@ -590,13 +587,14 @@
          },
          {
             "fragBuilder->codeAppendf(\"if (%s) {\", "
-                    "(_outer.childProcessor(0).preservesOpaqueInput() ? \"true\" : \"false\"));",
+                    "(_outer.childProcessor(_outer.child_index).preservesOpaqueInput() ? "
+                    "\"true\" : \"false\"));",
             "SkString _input0 = SkStringPrintf(\"%s\", args.fInputColor);",
             "SkString _child0(\"_child0\");",
-            "this->emitChild(0, _input0.c_str(), &_child0, args);",
+            "this->emitChild(_outer.child_index, _input0.c_str(), &_child0, args);",
             "fragBuilder->codeAppendf(\"\\n    %s = %s;\\n} else {\\n    %s = half4(1.0);\\n}\\n\""
                     ", args.fOutputColor, _child0.c_str(), args.fOutputColor);",
-            "this->registerChildProcessor(src.childProcessor(0).clone());"
+            "this->registerChildProcessor(src.childProcessor(child_index).clone());"
          });
 }
 
@@ -616,13 +614,37 @@
             "this->registerChildProcessor(std::move(child));"
          },
          {
-            "opaque = _outer.childProcessor(0).preservesOpaqueInput();",
+            "opaque = _outer.childProcessor(_outer.child_index).preservesOpaqueInput();",
             "fragBuilder->codeAppendf(\"bool opaque = %s;\\nif (opaque) {\", "
                     "(opaque ? \"true\" : \"false\"));",
             "SkString _child0(\"_child0\");",
-            "this->emitChild(0, &_child0, args);",
+            "this->emitChild(_outer.child_index, &_child0, args);",
             "fragBuilder->codeAppendf(\"\\n    %s = %s;\\n} else {\\n    %s = half4(0.5);\\n}\\n\""
                     ", args.fOutputColor, _child0.c_str(), args.fOutputColor);",
-            "this->registerChildProcessor(src.childProcessor(0).clone());"
+            "this->registerChildProcessor(src.childProcessor(child_index).clone());"
+         });
+}
+
+DEF_TEST(SkSLFPNullableChildProcessor, r) {
+    test(r,
+         "in fragmentProcessor? child;"
+         "void main() {"
+         "    if (child != null) {"
+         "        sk_OutColor = process(child);"
+         "    } else {"
+         "        sk_OutColor = half4(0.5);"
+         "    }"
+         "}",
+         *SkSL::ShaderCapsFactory::Default(),
+         {},
+         {
+            "SkString _child0(\"_child0\");",
+            "if (_outer.child_index >= 0) {",
+                "this->emitChild(_outer.child_index, &_child0, args);",
+            "} else {",
+                "fragBuilder->codeAppendf(\"half4 %s;\", _child0.c_str());",
+            "}",
+            "fragBuilder->codeAppendf(\"\\n    %s = %s;\\n} else {\\n    %s = half4(0.5);\\n}\\n\""
+                    ", args.fOutputColor, _child0.c_str(), args.fOutputColor);",
          });
 }
diff --git a/tests/SkSLGLSLTest.cpp b/tests/SkSLGLSLTest.cpp
index a7ef69c..6d0dae2 100644
--- a/tests/SkSLGLSLTest.cpp
+++ b/tests/SkSLGLSLTest.cpp
@@ -261,40 +261,46 @@
          "};"
          "B b1, b2, b3;"
          "void main() {"
+         "    a1.x = 0;"
+         "    b1.x = 0;"
+         "    sk_FragColor.r = half(a1.x + b1.x);"
          "}",
          *SkSL::ShaderCapsFactory::Default(),
          "#version 400\n"
+         "out vec4 sk_FragColor;\n"
          "struct A {\n"
          "    int x;\n"
          "    int y;\n"
-         "} a1, a2;\n"
-         "A a3;\n"
+         "} a1;\n"
          "struct B {\n"
          "    float x;\n"
          "    float[2] y;\n"
          "    layout (binding = 1) A z;\n"
-         "} b1, b2, b3;\n"
+         "} b1;\n"
          "void main() {\n"
+         "    a1.x = 0;\n"
+         "    b1.x = 0.0;\n"
+         "    sk_FragColor.x = float(a1.x) + b1.x;\n"
          "}\n");
 }
 
 DEF_TEST(SkSLVersion, r) {
     test(r,
-         "in float test; void main() { sk_FragColor = half4(0.75); }",
+         "in float test; void main() { sk_FragColor.r = half(test); }",
          *SkSL::ShaderCapsFactory::Version450Core(),
          "#version 450 core\n"
          "out vec4 sk_FragColor;\n"
          "in float test;\n"
          "void main() {\n"
-         "    sk_FragColor = vec4(0.75);\n"
+         "    sk_FragColor.x = test;\n"
          "}\n");
     test(r,
-         "in float test; void main() { sk_FragColor = half4(0.75); }",
+         "in float test; void main() { sk_FragColor.r = half(test); }",
          *SkSL::ShaderCapsFactory::Version110(),
          "#version 110\n"
          "varying float test;\n"
          "void main() {\n"
-         "    gl_FragColor = vec4(0.75);\n"
+         "    gl_FragColor.x = test;\n"
          "}\n");
 }
 
@@ -502,28 +508,43 @@
          "float3 v4 = float3(float2(1), 1.0);"
          "int2 v5 = int2(1);"
          "int2 v6 = int2(float2(1, 2));"
-         "float2 v7 = float2(int2(1, 2));",
+         "float2 v7 = float2(int2(1, 2));"
+         "void main() {"
+         "sk_FragColor.r = half(v1.x + v2.x + v3.x + v4.x + v5.x + v6.x + v7.x);"
+         "}",
          *SkSL::ShaderCapsFactory::Default(),
          "#version 400\n"
+         "out vec4 sk_FragColor;\n"
          "vec2 v1 = vec2(1.0);\n"
          "vec2 v2 = vec2(1.0, 2.0);\n"
          "vec2 v3 = vec2(1.0);\n"
          "vec3 v4 = vec3(vec2(1.0), 1.0);\n"
          "ivec2 v5 = ivec2(1);\n"
          "ivec2 v6 = ivec2(vec2(1.0, 2.0));\n"
-         "vec2 v7 = vec2(ivec2(1, 2));\n");
+         "vec2 v7 = vec2(ivec2(1, 2));\n"
+         "void main() {\n"
+         "    sk_FragColor.x = (((((v1.x + v2.x) + v3.x) + v4.x) + float(v5.x)) + float(v6.x)) + "
+         "v7.x;\n"
+         "}\n");
 }
 
 DEF_TEST(SkSLArrayConstructors, r) {
     test(r,
          "float test1[] = float[](1, 2, 3, 4);"
          "float2 test2[] = float2[](float2(1, 2), float2(3, 4));"
-         "float4x4 test3[] = float4x4[]();",
+         "float4x4 test3[] = float4x4[]();"
+         "void main() {"
+         "sk_FragColor.r = half(test1[0] + test2[0].x + test3[0][0][0]);"
+         "}",
          *SkSL::ShaderCapsFactory::Default(),
          "#version 400\n"
+         "out vec4 sk_FragColor;\n"
          "float test1[] = float[](1.0, 2.0, 3.0, 4.0);\n"
          "vec2 test2[] = vec2[](vec2(1.0, 2.0), vec2(3.0, 4.0));\n"
-         "mat4 test3[] = mat4[]();\n");
+         "mat4 test3[] = mat4[]();\n"
+         "void main() {\n"
+         "    sk_FragColor.x = (test1[0] + test2[0].x) + test3[0][0][0];\n"
+         "}\n");
 }
 
 DEF_TEST(SkSLDerivatives, r) {
@@ -554,6 +575,31 @@
          "void main() {\n"
          "    sk_FragColor.x = dFdx(1.0);\n"
          "}\n");
+
+    SkSL::Program::Settings settings;
+    settings.fFlipY = false;
+    auto caps = SkSL::ShaderCapsFactory::Default();
+    settings.fCaps = caps.get();
+    SkSL::Program::Inputs inputs;
+    test(r,
+         "void main() { sk_FragColor.r = half(dFdx(1)), sk_FragColor.g = half(dFdy(1)); }",
+         settings,
+         "#version 400\n"
+         "out vec4 sk_FragColor;\n"
+         "void main() {\n"
+         "    (sk_FragColor.x = dFdx(1.0) , sk_FragColor.y = dFdy(1.0));\n"
+         "}\n",
+         &inputs);
+    settings.fFlipY = true;
+    test(r,
+         "void main() { sk_FragColor.r = half(dFdx(1)), sk_FragColor.g = half(dFdy(1)); }",
+         settings,
+         "#version 400\n"
+         "out vec4 sk_FragColor;\n"
+         "void main() {\n"
+         "    (sk_FragColor.x = dFdx(1.0) , sk_FragColor.y = -dFdy(1.0));\n"
+         "}\n",
+         &inputs);
 }
 
 
@@ -1089,14 +1135,24 @@
          "layout(offset = 0) int x;"
          "layout(offset = 4) int y;"
          "int z;"
-         "} test;",
+         "} test;"
+         "void main() {"
+         "Test t;"
+         "t.x = 0;"
+         "sk_FragColor.r = half(t.x);"
+         "}",
          *SkSL::ShaderCapsFactory::Default(),
          "#version 400\n"
-         "struct Test {\n"
-         "    layout (offset = 0) int x;\n"
-         "    layout (offset = 4) int y;\n"
-         "    int z;\n"
-         "} test;\n");
+         "out vec4 sk_FragColor;\n"
+         "void main() {\n"
+         "    struct Test {\n"
+         "        layout (offset = 0) int x;\n"
+         "        layout (offset = 4) int y;\n"
+         "        int z;\n"
+         "    } t;\n"
+         "    t.x = 0;\n"
+         "    sk_FragColor.x = float(t.x);\n"
+         "}\n");
 }
 
 DEF_TEST(SkSLFragCoord, r) {
@@ -1752,9 +1808,14 @@
          "double4 d4 = double4(1, 2, 3, 4);"
          "float2x2 f22 = float2x2(1, 2, 3, 4);"
          "half2x4 h24 = half2x4(1, 2, 3, 4, 5, 6, 7, 8);"
-         "double4x2 d42 = double4x2(1, 2, 3, 4, 5, 6, 7, 8);",
+         "double4x2 d42 = double4x2(1, 2, 3, 4, 5, 6, 7, 8);"
+         "void main() {"
+         "sk_FragColor.r = half(f + h + d + f2.x + h3.x + d4.x + f22[0][0] + h24[0][0] + "
+                               "d42[0][0]);"
+         "}",
          *SkSL::ShaderCapsFactory::Default(),
          "#version 400\n"
+         "out vec4 sk_FragColor;\n"
          "float f = 1.0;\n"
          "float h = 2.0;\n"
          "double d = 3.0;\n"
@@ -1763,23 +1824,34 @@
          "dvec4 d4 = dvec4(1.0, 2.0, 3.0, 4.0);\n"
          "mat2 f22 = mat2(1.0, 2.0, 3.0, 4.0);\n"
          "mat2x4 h24 = mat2x4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0);\n"
-         "dmat4x2 d42 = dmat4x2(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0);\n");
+         "dmat4x2 d42 = dmat4x2(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0);\n"
+         "void main() {\n"
+         "    sk_FragColor.x = float(((((((double(f + h) + d) + double(f2.x)) + double(h3.x)) + "
+         "d4.x) + double(f22[0][0])) + double(h24[0][0])) + d42[0][0]);\n"
+         "}\n");
     test(r,
          "float f = 1;"
          "half h = 2;"
          "float2 f2 = float2(1, 2);"
          "half3 h3 = half3(1, 2, 3);"
          "float2x2 f22 = float2x2(1, 2, 3, 4);"
-         "half2x4 h24 = half2x4(1, 2, 3, 4, 5, 6, 7, 8);",
+         "half2x4 h24 = half2x4(1, 2, 3, 4, 5, 6, 7, 8);"
+         "void main() {"
+         "sk_FragColor.r = half(f + h + f2.x + h3.x + f22[0][0] + h24[0][0]);"
+         "}",
          *SkSL::ShaderCapsFactory::UsesPrecisionModifiers(),
          "#version 400\n"
          "precision mediump float;\n"
+         "out mediump vec4 sk_FragColor;\n"
          "highp float f = 1.0;\n"
          "mediump float h = 2.0;\n"
          "highp vec2 f2 = vec2(1.0, 2.0);\n"
          "mediump vec3 h3 = vec3(1.0, 2.0, 3.0);\n"
          "highp mat2 f22 = mat2(1.0, 2.0, 3.0, 4.0);\n"
-         "mediump mat2x4 h24 = mat2x4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0);\n");
+         "mediump mat2x4 h24 = mat2x4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0);\n"
+         "void main() {\n"
+         "    sk_FragColor.x = ((((f + h) + f2.x) + h3.x) + f22[0][0]) + h24[0][0];\n"
+         "}\n");
 }
 
 DEF_TEST(SkSLNumberConversions, r) {
@@ -1819,9 +1891,17 @@
          "float us2f = us;"
          "float ui2f = ui;"
          "float h2f = h;"
-         "float f2f = f;",
+         "float f2f = f;"
+         "void main() {"
+         "sk_FragColor.r = half(s + i + us + half(ui) + h + f + s2s + i2s + us2s + ui2s + h2s + "
+                               "f2s + s2i + i2i + us2i + ui2i + h2i + f2i + s2us + i2us + us2us + "
+                               "ui2us + h2us + f2us + half(s2ui) + half(i2ui) + half(us2ui) + "
+                               "half(ui2ui) + half(h2ui) + half(f2ui) + s2f + i2f + us2f + ui2f + "
+                               "h2f + f2f);"
+         "}",
          *SkSL::ShaderCapsFactory::Default(),
          "#version 400\n"
+         "out vec4 sk_FragColor;\n"
          "int s = int(sqrt(1.0));\n"
          "int i = int(sqrt(1.0));\n"
          "uint us = uint(sqrt(1.0));\n"
@@ -1857,7 +1937,16 @@
          "float us2f = float(us);\n"
          "float ui2f = float(ui);\n"
          "float h2f = h;\n"
-         "float f2f = f;\n");
+         "float f2f = f;\n"
+         "void main() {\n"
+         "    sk_FragColor.x = ((((((((((((((((((((((((((((((((float((s + i) + int(us)) + "
+         "float(ui)) + h) + f) + float(s2s)) + float(i2s)) + float(us2s)) + float(ui2s)) + "
+         "float(h2s)) + float(f2s)) + float(s2i)) + float(i2i)) + float(us2i)) + float(ui2i)) + "
+         "float(h2i)) + float(f2i)) + float(s2us)) + float(i2us)) + float(us2us)) + "
+         "float(ui2us)) + float(h2us)) + float(f2us)) + float(s2ui)) + float(i2ui)) + "
+         "float(us2ui)) + float(ui2ui)) + float(h2ui)) + float(f2ui)) + s2f) + i2f) + us2f) + "
+         "ui2f) + h2f) + f2f;\n"
+         "}\n");
 }
 
 DEF_TEST(SkSLForceHighPrecision, r) {
@@ -2187,3 +2276,23 @@
          SkSL::Program::kFragment_Kind
          );
 }
+
+DEF_TEST(SkSLSwizzleConstants, r) {
+    test(r,
+         "void main() {"
+         "    half4 v = half4(half(sqrt(1)));"
+         "    sk_FragColor = v.rgb1;"
+         "    half4 c = half4(1);"
+         "    sk_FragColor = c.rgb0;"
+         "}",
+         *SkSL::ShaderCapsFactory::RemovePowWithConstantExponent(),
+         "#version 400\n"
+         "out vec4 sk_FragColor;\n"
+         "void main() {\n"
+         "    vec4 v = vec4(sqrt(1.0));\n"
+         "    sk_FragColor = vec4(v.xyz, 1);\n"
+         "    sk_FragColor = vec4(vec4(1.0).xyz, 0);\n"
+         "}\n",
+         SkSL::Program::kFragment_Kind
+         );
+}
diff --git a/tests/SkSLInterpreterTest.cpp b/tests/SkSLInterpreterTest.cpp
new file mode 100644
index 0000000..10d2f32
--- /dev/null
+++ b/tests/SkSLInterpreterTest.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkSLCompiler.h"
+#include "SkSLInterpreter.h"
+
+#include "Test.h"
+
+void test(skiatest::Reporter* r, const char* src, float inR, float inG, float inB, float inA,
+        float expectedR, float expectedG, float expectedB, float expectedA) {
+    SkSL::Compiler compiler;
+    SkSL::Program::Settings settings;
+    std::unique_ptr<SkSL::Program> program = compiler.convertProgram(
+                                                             SkSL::Program::kPipelineStage_Kind,
+                                                             SkSL::String(src), settings);
+    REPORTER_ASSERT(r, program);
+    if (program) {
+        std::unique_ptr<SkSL::ByteCode> byteCode = compiler.toByteCode(*program);
+        REPORTER_ASSERT(r, !compiler.errorCount());
+        if (compiler.errorCount() > 0) {
+            printf("%s\n%s", src, compiler.errorText().c_str());
+            return;
+        }
+        SkSL::ByteCodeFunction* main = byteCode->fFunctions[0].get();
+        SkSL::Interpreter interpreter(std::move(program), std::move(byteCode));
+        float inoutColor[4] = { inR, inG, inB, inA };
+        interpreter.run(*main, (SkSL::Interpreter::Value*) inoutColor, nullptr);
+        if (inoutColor[0] != expectedR || inoutColor[1] != expectedG ||
+            inoutColor[2] != expectedB || inoutColor[3] != expectedA) {
+            printf("for program: %s\n", src);
+            printf("    expected (%f, %f, %f, %f), but received (%f, %f, %f, %f)\n", expectedR,
+                   expectedG, expectedB, expectedA, inoutColor[0], inoutColor[1], inoutColor[2],
+                   inoutColor[3]);
+        }
+        REPORTER_ASSERT(r, inoutColor[0] == expectedR);
+        REPORTER_ASSERT(r, inoutColor[1] == expectedG);
+        REPORTER_ASSERT(r, inoutColor[2] == expectedB);
+        REPORTER_ASSERT(r, inoutColor[3] == expectedA);
+    } else {
+        printf("%s\n%s", src, compiler.errorText().c_str());
+    }
+}
+
+DEF_TEST(SkSLInterpreterTEMP_TEST, r) {
+    test(r, "void main(inout half4 color) { half4 c = color; color += c; }", 0.25, 0.5, 0.75, 1,
+         0.5, 1, 1.5, 2);
+}
+
+DEF_TEST(SkSLInterpreterAdd, r) {
+    test(r, "void main(inout half4 color) { color.r = color.r + color.g; }", 0.25, 0.75, 0, 0, 1,
+         0.75, 0, 0);
+    test(r, "void main(inout half4 color) { color += half4(1, 2, 3, 4); }", 4, 3, 2, 1, 5, 5, 5, 5);
+    test(r, "void main(inout half4 color) { half4 c = color; color += c; }", 0.25, 0.5, 0.75, 1,
+         0.5, 1, 1.5, 2);
+    test(r, "void main(inout half4 color) { int a = 1; int b = 3; color.r = a + b; }", 1, 2, 3, 4,
+         4, 2, 3, 4);
+}
+
+DEF_TEST(SkSLInterpreterSubtract, r) {
+    test(r, "void main(inout half4 color) { color.r = color.r - color.g; }", 1, 0.75, 0, 0, 0.25,
+         0.75, 0, 0);
+    test(r, "void main(inout half4 color) { color -= half4(1, 2, 3, 4); }", 5, 5, 5, 5, 4, 3, 2, 1);
+    test(r, "void main(inout half4 color) { half4 c = color; color -= c; }", 4, 3, 2, 1,
+         0, 0, 0, 0);
+    test(r, "void main(inout half4 color) { int a = 3; int b = 1; color.r = a - b; }", 0, 0, 0, 0,
+         2, 0, 0, 0);
+}
+
+DEF_TEST(SkSLInterpreterMultiply, r) {
+    test(r, "void main(inout half4 color) { color.r = color.r * color.g; }", 2, 3, 0, 0, 6, 3, 0,
+         0);
+    test(r, "void main(inout half4 color) { color *= half4(1, 2, 3, 4); }", 2, 3, 4, 5, 2, 6, 12,
+         20);
+    test(r, "void main(inout half4 color) { half4 c = color; color *= c; }", 4, 3, 2, 1,
+         16, 9, 4, 1);
+    test(r, "void main(inout half4 color) { int a = 3; int b = -2; color.r = a * b; }", 0, 0, 0, 0,
+         -6, 0, 0, 0);
+}
+
+DEF_TEST(SkSLInterpreterDivide, r) {
+    test(r, "void main(inout half4 color) { color.r = color.r / color.g; }", 1, 2, 0, 0, 0.5, 2, 0,
+         0);
+    test(r, "void main(inout half4 color) { color /= half4(1, 2, 3, 4); }", 12, 12, 12, 12, 12, 6,
+         4, 3);
+    test(r, "void main(inout half4 color) { half4 c = color; color /= c; }", 4, 3, 2, 1,
+         1, 1, 1, 1);
+    test(r, "void main(inout half4 color) { int a = 8; int b = -2; color.r = a / b; }", 0, 0, 0, 0,
+         -4, 0, 0, 0);
+}
+
+DEF_TEST(SkSLInterpreterRemainder, r) {
+    test(r, "void main(inout half4 color) { int a = 8; int b = 3; a %= b; color.r = a; }", 0, 0, 0,
+         0, 2, 0, 0, 0);
+    test(r, "void main(inout half4 color) { int a = 8; int b = 3; color.r = a % b; }", 0, 0, 0, 0,
+         2, 0, 0, 0);
+    test(r, "void main(inout half4 color) { int2 a = int2(8, 10); a %= 6; color.rg = a; }", 0, 0, 0,
+         0, 2, 4, 0, 0);
+}
+
+DEF_TEST(SkSLInterpreterIf, r) {
+    test(r, "void main(inout half4 color) { if (color.r > color.g) color.a = 1; }", 5, 3, 0, 0,
+         5, 3, 0, 1);
+    test(r, "void main(inout half4 color) { if (color.r > color.g) color.a = 1; }", 5, 5, 0, 0,
+         5, 5, 0, 0);
+    test(r, "void main(inout half4 color) { if (color.r > color.g) color.a = 1; }", 5, 6, 0, 0,
+         5, 6, 0, 0);
+    test(r, "void main(inout half4 color) { if (color.r < color.g) color.a = 1; }", 3, 5, 0, 0,
+         3, 5, 0, 1);
+    test(r, "void main(inout half4 color) { if (color.r < color.g) color.a = 1; }", 5, 5, 0, 0,
+         5, 5, 0, 0);
+    test(r, "void main(inout half4 color) { if (color.r < color.g) color.a = 1; }", 6, 5, 0, 0,
+         6, 5, 0, 0);
+    test(r, "void main(inout half4 color) { if (color.r >= color.g) color.a = 1; }", 5, 3, 0, 0,
+         5, 3, 0, 1);
+    test(r, "void main(inout half4 color) { if (color.r >= color.g) color.a = 1; }", 5, 5, 0, 0,
+         5, 5, 0, 1);
+    test(r, "void main(inout half4 color) { if (color.r >= color.g) color.a = 1; }", 5, 6, 0, 0,
+         5, 6, 0, 0);
+    test(r, "void main(inout half4 color) { if (color.r <= color.g) color.a = 1; }", 3, 5, 0, 0,
+         3, 5, 0, 1);
+    test(r, "void main(inout half4 color) { if (color.r <= color.g) color.a = 1; }", 5, 5, 0, 0,
+         5, 5, 0, 1);
+    test(r, "void main(inout half4 color) { if (color.r <= color.g) color.a = 1; }", 6, 5, 0, 0,
+         6, 5, 0, 0);
+    test(r, "void main(inout half4 color) { if (color.r == color.g) color.a = 1; }", 2, 2, 0, 0,
+         2, 2, 0, 1);
+    test(r, "void main(inout half4 color) { if (color.r == color.g) color.a = 1; }", 2, -2, 0, 0,
+         2, -2, 0, 0);
+    test(r, "void main(inout half4 color) { if (color.r != color.g) color.a = 1; }", 2, 2, 0, 0,
+         2, 2, 0, 0);
+    test(r, "void main(inout half4 color) { if (color.r != color.g) color.a = 1; }", 2, -2, 0, 0,
+         2, -2, 0, 1);
+    test(r, "void main(inout half4 color) { if (color.r == color.g) color.a = 1; else "
+         "color.a = 2; }", 1, 1, 0, 0, 1, 1, 0, 1);
+    test(r, "void main(inout half4 color) { if (color.r == color.g) color.a = 1; else "
+         "color.a = 2; }", 2, -2, 0, 0, 2, -2, 0, 2);
+}
+
+DEF_TEST(SkSLInterpreterWhile, r) {
+    test(r, "void main(inout half4 color) { while (color.r < 1) color.r += 0.25; }", 0, 0, 0, 0, 1,
+         0, 0, 0);
+    test(r, "void main(inout half4 color) { while (color.r > 1) color.r += 0.25; }", 0, 0, 0, 0, 0,
+         0, 0, 0);
+    test(r, "void main(inout half4 color) { while (true) { color.r += 0.5; "
+         "if (color.r > 1) break; } }", 0, 0, 0, 0, 1.5, 0, 0, 0);
+    test(r, "void main(inout half4 color) { while (color.r < 10) { color.r += 0.5; "
+            "if (color.r < 5) continue; break; } }", 0, 0, 0, 0, 5, 0, 0, 0);
+}
+
+DEF_TEST(SkSLInterpreterDo, r) {
+    test(r, "void main(inout half4 color) { do color.r += 0.25; while (color.r < 1); }", 0, 0, 0, 0,
+         1, 0, 0, 0);
+    test(r, "void main(inout half4 color) { do color.r += 0.25; while (color.r > 1); }", 0, 0, 0, 0,
+         0.25, 0, 0, 0);
+    test(r, "void main(inout half4 color) { do { color.r += 0.5; if (color.r > 1) break; } while "
+            "(true); }", 0, 0, 0, 0, 1.5, 0, 0, 0);
+    test(r, "void main(inout half4 color) {do { color.r += 0.5; if (color.r < 5) "
+            "continue; if (color.r >= 5) break; } while (true); }", 0, 0, 0, 0, 5, 0, 0, 0);
+}
+
+DEF_TEST(SkSLInterpreterFor, r) {
+    test(r, "void main(inout half4 color) { for (int i = 1; i <= 10; ++i) color.r += i; }", 0, 0, 0,
+         0, 55, 0, 0, 0);
+    test(r,
+         "void main(inout half4 color) {"
+         "    for (int i = 1; i <= 10; ++i)"
+         "        for (int j = i; j <= 10; ++j)"
+         "            color.r += j;"
+         "}",
+         0, 0, 0, 0,
+         385, 0, 0, 0);
+    test(r,
+         "void main(inout half4 color) {"
+         "    for (int i = 1; i <= 10; ++i)"
+         "        for (int j = 1; ; ++j) {"
+         "            if (i == j) continue;"
+         "            if (j > 10) break;"
+         "            color.r += j;"
+         "        }"
+         "}",
+         0, 0, 0, 0,
+         495, 0, 0, 0);
+}
+
+DEF_TEST(SkSLInterpreterSwizzle, r) {
+    test(r, "void main(inout half4 color) { color = color.abgr; }", 1, 2, 3, 4, 4, 3, 2, 1);
+    test(r, "void main(inout half4 color) { color.rgb = half4(5, 6, 7, 8).bbg; }", 1, 2, 3, 4, 7, 7,
+         6, 4);
+    test(r, "void main(inout half4 color) { color.bgr = int3(5, 6, 7); }", 1, 2, 3, 4, 7, 6,
+         5, 4);
+}
+
+DEF_TEST(SkSLInterpreterGlobal, r) {
+    test(r, "int x; void main(inout half4 color) { x = 10; color.b = x; }", 1, 2, 3, 4, 1, 2, 10,
+         4);
+}
diff --git a/tests/SkSLMetalTest.cpp b/tests/SkSLMetalTest.cpp
index 38611ac..bd6daa5 100644
--- a/tests/SkSLMetalTest.cpp
+++ b/tests/SkSLMetalTest.cpp
@@ -52,10 +52,7 @@
          "struct Outputs {\n"
          "    float4 sk_FragColor [[color(0)]];\n"
          "};\n"
-         "struct sksl_synthetic_uniforms {\n"
-         "    float u_skRTHeight;\n"
-         "};\n"
-         "fragment Outputs fragmentMain(Inputs _in [[stage_in]], constant sksl_synthetic_uniforms& _anonInterface0 [[buffer(1)]], bool _frontFacing [[front_facing]], float4 _fragCoord [[position]]) {\n"
+         "fragment Outputs fragmentMain(Inputs _in [[stage_in]], bool _frontFacing [[front_facing]], float4 _fragCoord [[position]]) {\n"
          "    Outputs _outputStruct;\n"
          "    thread Outputs* _out = &_outputStruct;\n"
          "    _out->sk_FragColor = float4(0.75);\n"
@@ -89,9 +86,6 @@
          "struct Outputs {\n"
          "    float4 sk_FragColor [[color(0)]];\n"
          "};\n"
-         "struct sksl_synthetic_uniforms {\n"
-         "    float u_skRTHeight;\n"
-         "};\n"
          "float2x2 float2x2_from_float(float x) {\n"
          "    return float2x2(float2(x, 0), float2(0, x));\n"
          "}\n"
@@ -107,7 +101,7 @@
          "float4x4 float4x4_from_float(float x) {\n"
          "    return float4x4(float4(x, 0, 0, 0), float4(0, x, 0, 0), float4(0, 0, x, 0), float4(0, 0, 0, x));\n"
          "}\n"
-         "fragment Outputs fragmentMain(Inputs _in [[stage_in]], constant sksl_synthetic_uniforms& _anonInterface0 [[buffer(1)]], bool _frontFacing [[front_facing]], float4 _fragCoord [[position]]) {\n"
+         "fragment Outputs fragmentMain(Inputs _in [[stage_in]], bool _frontFacing [[front_facing]], float4 _fragCoord [[position]]) {\n"
          "    Outputs _outputStruct;\n"
          "    thread Outputs* _out = &_outputStruct;\n"
          "    float2x2 m5 = float2x2_from_float(float2x2_from_float4(float4(1.0, 2.0, 3.0, 4.0))[0][0]);\n"
@@ -115,3 +109,25 @@
          "    return *_out;\n"
          "}\n");
 }
+
+DEF_TEST(SkSLMetalConstantSwizzle, r) {
+    test(r,
+         "void main() {"
+         "sk_FragColor = half4(0.5).rgb1;"
+         "}",
+         *SkSL::ShaderCapsFactory::Default(),
+         "#include <metal_stdlib>\n"
+         "#include <simd/simd.h>\n"
+         "using namespace metal;\n"
+         "struct Inputs {\n"
+         "};\n"
+         "struct Outputs {\n"
+         "    float4 sk_FragColor [[color(0)]];\n"
+         "};\n"
+         "fragment Outputs fragmentMain(Inputs _in [[stage_in]], bool _frontFacing [[front_facing]], float4 _fragCoord [[position]]) {\n"
+         "    Outputs _outputStruct;\n"
+         "    thread Outputs* _out = &_outputStruct;\n"
+         "    _out->sk_FragColor = float4(float4(0.5).xyz, 1);\n"
+         "    return *_out;\n"
+         "}\n");
+}
diff --git a/tests/SkVxTest.cpp b/tests/SkVxTest.cpp
index 1ded42d..71c8fba 100644
--- a/tests/SkVxTest.cpp
+++ b/tests/SkVxTest.cpp
@@ -28,6 +28,24 @@
 using long4 = skvx::Vec<4,int64_t>;
 using long8 = skvx::Vec<8,int64_t>;
 
+// These are unused, and just here so I can look at the disassembly.
+float2 Sqrt(float2 x) { return sqrt(x); }
+float4 Sqrt(float4 x) { return sqrt(x); }
+float8 Sqrt(float8 x) { return sqrt(x); }
+
+float4 RSqrt(float4 x) { return rsqrt(x); }
+float4   Rcp(float4 x) { return   rcp(x); }
+float4  Ceil(float4 x) { return  ceil(x); }
+float4 Floor(float4 x) { return floor(x); }
+float4 Trunc(float4 x) { return trunc(x); }
+float4 Round(float4 x) { return round(x); }
+float4   Abs(float4 x) { return   abs(x); }
+
+float4 Min(float4 x, float4 y) { return min(x,y); }
+float4 Max(float4 x, float4 y) { return max(x,y); }
+
+float4 IfThenElse(int4 c, float4 t, float4 e) { return if_then_else(c,t,e); }
+
 DEF_TEST(SkVx, r) {
     static_assert(sizeof(float2) ==  8, "");
     static_assert(sizeof(float4) == 16, "");
@@ -90,29 +108,39 @@
     REPORTER_ASSERT(r, all(  rcp(float4{2,3,4,5}) < float4{1.0f,0.5f,0.5f,0.3f}));
     REPORTER_ASSERT(r, all(rsqrt(float4{2,3,4,5}) < float4{1.0f,1.0f,1.0f,0.5f}));
 
-    REPORTER_ASSERT(r, all(cast<int>(float4{-1.5f,0.5f,1.0f,1.5f}) == int4{-1,0,1,1}));
+    REPORTER_ASSERT(r, all( sqrt(float2{2,3}) < float2{2,2}));
+    REPORTER_ASSERT(r, all(  rcp(float2{2,3}) < float2{1.0f,0.5f}));
+    REPORTER_ASSERT(r, all(rsqrt(float2{2,3}) < float2{1.0f,1.0f}));
 
-    float buf[4] = {1,2,3,4};
+    REPORTER_ASSERT(r, all(skvx::cast<int>(float4{-1.5f,0.5f,1.0f,1.5f}) == int4{-1,0,1,1}));
+
+    float buf[] = {1,2,3,4,5,6};
     REPORTER_ASSERT(r, all(float4::Load(buf) == float4{1,2,3,4}));
     float4{2,3,4,5}.store(buf);
-    REPORTER_ASSERT(r, all(float4::Load(buf) == float4{2,3,4,5}));
-
-    {
-        int4 iota = {0,1,2,3};
-        int i = 0;
-        for (int val : iota) {
-            REPORTER_ASSERT(r, val == i++);
-        }
-        for (int& val : iota) {
-            val = 42;
-        }
-        REPORTER_ASSERT(r, all(iota == 42));
-    }
+    REPORTER_ASSERT(r, buf[0] == 2
+                    && buf[1] == 3
+                    && buf[2] == 4
+                    && buf[3] == 5
+                    && buf[4] == 5
+                    && buf[5] == 6);
+    REPORTER_ASSERT(r, all(float4::Load(buf+0) == float4{2,3,4,5}));
+    REPORTER_ASSERT(r, all(float4::Load(buf+2) == float4{4,5,5,6}));
 
     REPORTER_ASSERT(r, all(mad(float4{1,2,3,4}, 2.0f, 3.0f) == float4{5,7,9,11}));
 
-    REPORTER_ASSERT(r, all(shuffle<2,1,0,3>        (float4{1,2,3,4}) == float4{3,2,1,4}));
-    REPORTER_ASSERT(r, all(shuffle<2,1>            (float4{1,2,3,4}) == float2{3,2}));
-    REPORTER_ASSERT(r, all(shuffle<2,1,2,1,2,1,2,1>(float4{1,2,3,4}) == float8{3,2,3,2,3,2,3,2}));
-    REPORTER_ASSERT(r, all(shuffle<3,3,3,3>        (float4{1,2,3,4}) == float4{4,4,4,4}));
+    REPORTER_ASSERT(r, all(skvx::shuffle<2,1,0,3>        (float4{1,2,3,4}) == float4{3,2,1,4}));
+    REPORTER_ASSERT(r, all(skvx::shuffle<2,1>            (float4{1,2,3,4}) == float2{3,2}));
+    REPORTER_ASSERT(r, all(skvx::shuffle<3,3,3,3>        (float4{1,2,3,4}) == float4{4,4,4,4}));
+    REPORTER_ASSERT(r, all(skvx::shuffle<2,1,2,1,2,1,2,1>(float4{1,2,3,4})
+                           == float8{3,2,3,2,3,2,3,2}));
+
+    // Test that mixed types can be used where they make sense.  Mostly about ergonomics.
+    REPORTER_ASSERT(r, all(float4{1,2,3,4} < 5));
+    REPORTER_ASSERT(r, all( byte4{1,2,3,4} < 5));
+    REPORTER_ASSERT(r, all(  int4{1,2,3,4} < 5.0f));
+    float4 five = 5;
+    REPORTER_ASSERT(r, all(five == 5.0f));
+    REPORTER_ASSERT(r, all(five == 5));
+
+    REPORTER_ASSERT(r, all(max(2, min(float4{1,2,3,4}, 3)) == float4{2,2,3,3}));
 }
diff --git a/tests/StrokerTest.cpp b/tests/StrokerTest.cpp
index 1ccb518..04307a1 100644
--- a/tests/StrokerTest.cpp
+++ b/tests/StrokerTest.cpp
@@ -5,19 +5,19 @@
  * found in the LICENSE file.
  */
 
+#include "CommandLineFlags.h"
 #include "PathOpsCubicIntersectionTestData.h"
 #include "PathOpsQuadIntersectionTestData.h"
-#include "SkCommonFlags.h"
-#include "SkPathOpsCubic.h"
 #include "SkPaint.h"
 #include "SkPath.h"
+#include "SkPathOpsCubic.h"
 #include "SkPointPriv.h"
 #include "SkRandom.h"
 #include "SkStrokerPriv.h"
 #include "SkTime.h"
 #include "Test.h"
 
-DEFINE_bool(timeout, true, "run until alloted time expires");
+static DEFINE_bool(timeout, true, "run until alloted time expires");
 
 #define MS_TEST_DURATION 10
 
@@ -160,7 +160,7 @@
         p.getFillPath(path, &fill);
 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
         if (best < gMaxRecursion[2]) {
-            if (FLAGS_veryVerbose) {
+            if (reporter->verbose()) {
                 SkDebugf("\n%s quad=%d width=%1.9g\n", __FUNCTION__, gMaxRecursion[2],
                         p.getStrokeWidth());
                 path.dumpHex();
@@ -175,7 +175,7 @@
         }
     }
 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
-    if (FLAGS_veryVerbose) {
+    if (reporter->verbose()) {
        SkDebugf("\n%s max quad=%d\n", __FUNCTION__, best);
     }
 #endif
@@ -200,7 +200,7 @@
         p.getFillPath(path, &fill);
     #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
         if (bestTan < gMaxRecursion[0] || bestCubic < gMaxRecursion[1]) {
-            if (FLAGS_veryVerbose) {
+            if (reporter->verbose()) {
                 SkDebugf("\n%s tan=%d cubic=%d width=%1.9g\n", __FUNCTION__, gMaxRecursion[0],
                         gMaxRecursion[1], p.getStrokeWidth());
                 path.dumpHex();
@@ -216,7 +216,7 @@
         }
     }
 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
-    if (FLAGS_veryVerbose) {
+    if (reporter->verbose()) {
         SkDebugf("\n%s max tan=%d cubic=%d\n", __FUNCTION__, bestTan, bestCubic);
     }
 #endif
@@ -252,7 +252,7 @@
         p.getFillPath(path, &fill);
 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
         if (best < gMaxRecursion[2]) {
-            if (FLAGS_veryVerbose) {
+            if (reporter->verbose()) {
                 SkDebugf("\n%s quad=%d width=%1.9g\n", __FUNCTION__, gMaxRecursion[2],
                         p.getStrokeWidth());
                 path.dumpHex();
@@ -267,7 +267,7 @@
         }
     }
 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
-    if (FLAGS_veryVerbose) {
+    if (reporter->verbose()) {
         SkDebugf("\n%s max quad=%d\n", __FUNCTION__, best);
     }
 #endif
@@ -310,7 +310,7 @@
         p.getFillPath(path, &fill);
 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
         if (bestTan < gMaxRecursion[0] || bestCubic < gMaxRecursion[1]) {
-            if (FLAGS_veryVerbose) {
+            if (reporter->verbose()) {
                 SkDebugf("\n%s tan=%d cubic=%d width=%1.9g\n", __FUNCTION__, gMaxRecursion[0],
                         gMaxRecursion[1], p.getStrokeWidth());
                 path.dumpHex();
@@ -326,7 +326,7 @@
         }
     }
 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
-    if (FLAGS_veryVerbose) {
+    if (reporter->verbose()) {
         SkDebugf("\n%s max tan=%d cubic=%d\n", __FUNCTION__, bestTan, bestCubic);
     }
 #endif
@@ -356,7 +356,7 @@
         p.getFillPath(path, &fill);
 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
         if (best < gMaxRecursion[2]) {
-            if (FLAGS_veryVerbose) {
+            if (reporter->verbose()) {
                 SkDebugf("\n%s quad=%d width=%1.9g\n", __FUNCTION__, gMaxRecursion[2],
                         p.getStrokeWidth());
                 path.dumpHex();
@@ -371,7 +371,7 @@
         }
     }
 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
-    if (FLAGS_verbose) {
+    if (reporter->verbose()) {
         SkDebugf("\n%s max quad=%d\n", __FUNCTION__, best);
     }
 #endif
@@ -395,7 +395,7 @@
         p.getFillPath(path, &fill);
 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
         if (best[0] < gMaxRecursion[0] || best[1] < gMaxRecursion[1]) {
-            if (FLAGS_veryVerbose) {
+            if (reporter->verbose()) {
                 SkDebugf("\n%s tan=%d cubic=%d width=%1.9g\n", __FUNCTION__, gMaxRecursion[0],
                         gMaxRecursion[1], p.getStrokeWidth());
                 path.dumpHex();
@@ -411,7 +411,7 @@
         }
     }
 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
-    if (FLAGS_veryVerbose) {
+    if (reporter->verbose()) {
         SkDebugf("\n%s max tan=%d cubic=%d\n", __FUNCTION__, best[0], best[1]);
     }
 #endif
@@ -430,14 +430,14 @@
 path.moveTo(SkBits2Float(0x43c99223), SkBits2Float(0x42b7417e));
 path.quadTo(SkBits2Float(0x4285d839), SkBits2Float(0x43ed6645), SkBits2Float(0x43c941c8), SkBits2Float(0x42b3ace3));
     p.getFillPath(path, &fill);
-    if (FLAGS_veryVerbose) {
+    if (reporter->verbose()) {
         SkDebugf("\n%s path\n", __FUNCTION__);
         path.dump();
         SkDebugf("fill:\n");
         fill.dump();
     }
 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
-    if (FLAGS_veryVerbose) {
+    if (reporter->verbose()) {
         SkDebugf("max quad=%d\n", gMaxRecursion[2]);
     }
 #endif
@@ -455,14 +455,14 @@
 path.moveTo(SkBits2Float(0x433f5370), SkBits2Float(0x43d1f4b3));
 path.cubicTo(SkBits2Float(0x4331cb76), SkBits2Float(0x43ea3340), SkBits2Float(0x4388f498), SkBits2Float(0x42f7f08d), SkBits2Float(0x43f1cd32), SkBits2Float(0x42802ec1));
     p.getFillPath(path, &fill);
-    if (FLAGS_veryVerbose) {
+    if (reporter->verbose()) {
         SkDebugf("\n%s path\n", __FUNCTION__);
         path.dump();
         SkDebugf("fill:\n");
         fill.dump();
     }
 #if defined(SK_DEBUG) && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
-    if (FLAGS_veryVerbose) {
+    if (reporter->verbose()) {
         SkDebugf("max tan=%d cubic=%d\n", gMaxRecursion[0], gMaxRecursion[1]);
     }
 #endif
diff --git a/tests/SurfaceSemaphoreTest.cpp b/tests/SurfaceSemaphoreTest.cpp
index ccb05ff..fd3b449 100644
--- a/tests/SurfaceSemaphoreTest.cpp
+++ b/tests/SurfaceSemaphoreTest.cpp
@@ -144,7 +144,7 @@
 #endif
 
     if (flushContext) {
-        mainCtx->flushAndSignalSemaphores(2, semaphores.get());
+        mainCtx->flush(kNone_GrFlushFlags, 2, semaphores.get());
     } else {
         mainSurface->flushAndSignalSemaphores(2, semaphores.get());
     }
diff --git a/tests/SurfaceTest.cpp b/tests/SurfaceTest.cpp
index 78e068a..f2279b8 100644
--- a/tests/SurfaceTest.cpp
+++ b/tests/SurfaceTest.cpp
@@ -31,8 +31,7 @@
 #include <initializer_list>
 #include <vector>
 
-#include "sk_tool_utils.h"
-
+#include "ToolUtils.h"
 
 static void release_direct_surface_storage(void* pixels, void* context) {
     SkASSERT(pixels == context);
@@ -776,11 +775,10 @@
         [] (SkSurface* s){
             return sk_ref_sp(s->getCanvas()->internal_private_accessTopLayerRenderTargetContext());
         },
-        [] (SkSurface* s){
+        [context] (SkSurface* s){
             sk_sp<SkImage> i(s->makeImageSnapshot());
             SkImage_Gpu* gpuImage = (SkImage_Gpu *) as_IB(i);
-            sk_sp<GrTextureProxy> proxy = gpuImage->asTextureProxyRef();
-            GrContext* context = gpuImage->context();
+            sk_sp<GrTextureProxy> proxy = gpuImage->asTextureProxyRef(context);
             return context->priv().makeWrappedSurfaceContext(std::move(proxy),
                                                              gpuImage->refColorSpace());
         }
@@ -1020,9 +1018,10 @@
             auto img = surf->makeImageSnapshot();
             if (!img && false) {    // change to true to document the differences
                 SkDebugf("image failed: [%08X %08X] %14s %s\n",
-                         info.width(), info.height(),
-                         sk_tool_utils::colortype_name(info.colorType()),
-                         sk_tool_utils::alphatype_name(info.alphaType()));
+                         info.width(),
+                         info.height(),
+                         ToolUtils::colortype_name(info.colorType()),
+                         ToolUtils::alphatype_name(info.alphaType()));
                 return;
             }
             REPORTER_ASSERT(reporter, img != nullptr);
diff --git a/tests/TableColorFilterTest.cpp b/tests/TableColorFilterTest.cpp
deleted file mode 100644
index bb5df60..0000000
--- a/tests/TableColorFilterTest.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "Test.h"
-
-#include "SkColorSpace.h"
-#include "SkTableColorFilter.h"
-#include "SkToSRGBColorFilter.h"
-
-// SkToSRGBColorFilter makes it easy to create out of range (>1, <0) color values.
-// Those can be dangerous as inputs to naive implementation of SkTableColorFilter.
-// This tests that our implementation is safe.
-
-DEF_TEST(TableColorFilter, r) {
-    // Using a wide source gamut will make saturated colors go well out of range of sRGB.
-    auto rec2020 = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kRec2020);
-    sk_sp<SkColorFilter> to_srgb = SkToSRGBColorFilter::Make(rec2020);
-
-    // Any table will work fine here.  An identity table makes testing easy.
-    uint8_t identity[256];
-    for (int i = 0; i < 256; i++) {
-        identity[i] = i;
-    }
-    sk_sp<SkColorFilter> table = SkTableColorFilter::Make(identity);
-
-    // The rec2020 primaries are not representable in sRGB, but will clamp to the sRGB primaries.
-    SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE };
-    sk_sp<SkColorFilter> composed = table->makeComposed(to_srgb);
-    for (auto color : colors) {
-        REPORTER_ASSERT(r, composed->filterColor(color) == color);
-    }
-}
diff --git a/tests/TessellatingPathRendererTests.cpp b/tests/TessellatingPathRendererTests.cpp
index b2499ed..6457c9f 100644
--- a/tests/TessellatingPathRendererTests.cpp
+++ b/tests/TessellatingPathRendererTests.cpp
@@ -627,17 +627,19 @@
     SkPoint pts[2] = { {0, 0}, {1, 1} };
     SkColor colors[2] = { SK_ColorGREEN, SK_ColorBLUE };
     sk_sp<SkShader> shader = SkGradientShader::MakeLinear(
-        pts, colors, nullptr, SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode);
+        pts, colors, nullptr, SK_ARRAY_COUNT(colors), SkTileMode::kClamp);
     GrColorSpaceInfo colorSpaceInfo(nullptr, kRGBA_8888_GrPixelConfig);
     GrFPArgs args(ctx, &SkMatrix::I(), SkFilterQuality::kLow_SkFilterQuality, &colorSpaceInfo);
     return as_SB(shader)->asFragmentProcessor(args);
 }
 
+using AATypeFlags = GrPathRenderer::AATypeFlags;
+
 static void test_path(GrContext* ctx,
                       GrRenderTargetContext* renderTargetContext,
                       const SkPath& path,
                       const SkMatrix& matrix = SkMatrix::I(),
-                      GrAAType aaType = GrAAType::kNone,
+                      AATypeFlags aaTypeFlags = AATypeFlags::kNone,
                       std::unique_ptr<GrFragmentProcessor> fp = nullptr) {
     GrTessellatingPathRenderer tess;
 
@@ -660,7 +662,7 @@
                                       &clipConservativeBounds,
                                       &matrix,
                                       &shape,
-                                      aaType,
+                                      aaTypeFlags,
                                       false};
     tess.drawPath(args);
 }
@@ -699,31 +701,31 @@
     test_path(ctx, rtc.get(), create_path_16());
     SkMatrix nonInvertibleMatrix = SkMatrix::MakeScale(0, 0);
     std::unique_ptr<GrFragmentProcessor> fp(create_linear_gradient_processor(ctx));
-    test_path(ctx, rtc.get(), create_path_17(), nonInvertibleMatrix, GrAAType::kCoverage,
+    test_path(ctx, rtc.get(), create_path_17(), nonInvertibleMatrix, AATypeFlags::kCoverage,
               std::move(fp));
     test_path(ctx, rtc.get(), create_path_18());
     test_path(ctx, rtc.get(), create_path_19());
-    test_path(ctx, rtc.get(), create_path_20(), SkMatrix(), GrAAType::kCoverage);
-    test_path(ctx, rtc.get(), create_path_21(), SkMatrix(), GrAAType::kCoverage);
+    test_path(ctx, rtc.get(), create_path_20(), SkMatrix(), AATypeFlags::kCoverage);
+    test_path(ctx, rtc.get(), create_path_21(), SkMatrix(), AATypeFlags::kCoverage);
     test_path(ctx, rtc.get(), create_path_23());
     test_path(ctx, rtc.get(), create_path_24());
-    test_path(ctx, rtc.get(), create_path_25(), SkMatrix(), GrAAType::kCoverage);
-    test_path(ctx, rtc.get(), create_path_26(), SkMatrix(), GrAAType::kCoverage);
-    test_path(ctx, rtc.get(), create_path_27(), SkMatrix(), GrAAType::kCoverage);
-    test_path(ctx, rtc.get(), create_path_28(), SkMatrix(), GrAAType::kCoverage);
+    test_path(ctx, rtc.get(), create_path_25(), SkMatrix(), AATypeFlags::kCoverage);
+    test_path(ctx, rtc.get(), create_path_26(), SkMatrix(), AATypeFlags::kCoverage);
+    test_path(ctx, rtc.get(), create_path_27(), SkMatrix(), AATypeFlags::kCoverage);
+    test_path(ctx, rtc.get(), create_path_28(), SkMatrix(), AATypeFlags::kCoverage);
     test_path(ctx, rtc.get(), create_path_29());
     test_path(ctx, rtc.get(), create_path_30());
-    test_path(ctx, rtc.get(), create_path_31(), SkMatrix(), GrAAType::kCoverage);
+    test_path(ctx, rtc.get(), create_path_31(), SkMatrix(), AATypeFlags::kCoverage);
     test_path(ctx, rtc.get(), create_path_32());
     test_path(ctx, rtc.get(), create_path_33());
     test_path(ctx, rtc.get(), create_path_34());
     test_path(ctx, rtc.get(), create_path_35());
     test_path(ctx, rtc.get(), create_path_36());
     test_path(ctx, rtc.get(), create_path_37());
-    test_path(ctx, rtc.get(), create_path_38(), SkMatrix(), GrAAType::kCoverage);
+    test_path(ctx, rtc.get(), create_path_38(), SkMatrix(), AATypeFlags::kCoverage);
     test_path(ctx, rtc.get(), create_path_39());
     test_path(ctx, rtc.get(), create_path_40());
-    test_path(ctx, rtc.get(), create_path_41(), SkMatrix(), GrAAType::kCoverage);
+    test_path(ctx, rtc.get(), create_path_41(), SkMatrix(), AATypeFlags::kCoverage);
     test_path(ctx, rtc.get(), create_path_42());
-    test_path(ctx, rtc.get(), create_path_43(), SkMatrix(), GrAAType::kCoverage);
+    test_path(ctx, rtc.get(), create_path_43(), SkMatrix(), AATypeFlags::kCoverage);
 }
diff --git a/tests/Test.cpp b/tests/Test.cpp
index da260a0..06b0016 100644
--- a/tests/Test.cpp
+++ b/tests/Test.cpp
@@ -9,11 +9,11 @@
 
 #include <stdlib.h>
 
-#include "SkCommandLineFlags.h"
+#include "CommandLineFlags.h"
 #include "SkString.h"
 #include "SkTime.h"
 
-DEFINE_string2(tmpDir, t, nullptr, "Temp directory to use.");
+static DEFINE_string2(tmpDir, t, nullptr, "Temp directory to use.");
 
 void skiatest::Reporter::bumpTestCount() {}
 
@@ -21,6 +21,22 @@
 
 bool skiatest::Reporter::verbose() const { return false; }
 
+
+void skiatest::Reporter::reportFailedWithContext(const skiatest::Failure& f) {
+    SkString fullMessage = f.message;
+    if (!fContextStack.empty()) {
+        fullMessage.append(" [");
+        for (int i = 0; i < fContextStack.count(); ++i) {
+            if (i > 0) {
+                fullMessage.append(", ");
+            }
+            fullMessage.append(fContextStack[i]);
+        }
+        fullMessage.append("]");
+    }
+    this->reportFailed(skiatest::Failure(f.fileName, f.lineNo, f.condition, fullMessage));
+}
+
 SkString skiatest::Failure::toString() const {
     SkString result = SkStringPrintf("%s:%d\t", this->fileName, this->lineNo);
     if (!this->message.isEmpty()) {
diff --git a/tests/Test.h b/tests/Test.h
index 31be9a0..35e6dd8 100644
--- a/tests/Test.h
+++ b/tests/Test.h
@@ -37,20 +37,8 @@
     virtual bool verbose() const;
     virtual void* stats() const { return nullptr; }
 
-    void reportFailedWithContext(const skiatest::Failure& f) {
-        SkString fullMessage = f.message;
-        if (!fContextStack.empty()) {
-            fullMessage.append(" [");
-            for (int i = 0; i < fContextStack.count(); ++i) {
-                if (i > 0) {
-                    fullMessage.append(", ");
-                }
-                fullMessage.append(fContextStack[i]);
-            }
-            fullMessage.append("]");
-        }
-        this->reportFailed(skiatest::Failure(f.fileName, f.lineNo, f.condition, fullMessage));
-    }
+    void reportFailedWithContext(const skiatest::Failure&);
+
     void push(const SkString& message) {
         fContextStack.push_back(message);
     }
@@ -131,7 +119,7 @@
 extern bool IsVulkanContextType(GrContextFactoryContextType);
 extern bool IsMetalContextType(GrContextFactoryContextType);
 extern bool IsRenderingGLContextType(GrContextFactoryContextType);
-extern bool IsNullGLContextType(GrContextFactoryContextType);
+extern bool IsMockContextType(GrContextFactoryContextType);
 void RunWithGPUTestContexts(GrContextTestFn*, GrContextTypeFilterFn*, Reporter*,
                             const GrContextOptions&);
 
@@ -208,8 +196,8 @@
 #define DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(name, reporter, context_info)                 \
         DEF_GPUTEST_FOR_CONTEXTS(name, &skiatest::IsRenderingGLContextType,                 \
                                  reporter, context_info, nullptr)
-#define DEF_GPUTEST_FOR_NULLGL_CONTEXT(name, reporter, context_info)                        \
-        DEF_GPUTEST_FOR_CONTEXTS(name, &skiatest::IsNullGLContextType,                      \
+#define DEF_GPUTEST_FOR_MOCK_CONTEXT(name, reporter, context_info)                          \
+        DEF_GPUTEST_FOR_CONTEXTS(name, &skiatest::IsMockContextType,                        \
                                  reporter, context_info, nullptr)
 #define DEF_GPUTEST_FOR_VULKAN_CONTEXT(name, reporter, context_info)                        \
         DEF_GPUTEST_FOR_CONTEXTS(name, &skiatest::IsVulkanContextType,                      \
diff --git a/tests/TestTest.cpp b/tests/TestTest.cpp
index ee691f4..68bec94 100644
--- a/tests/TestTest.cpp
+++ b/tests/TestTest.cpp
@@ -34,9 +34,9 @@
     REPORTER_ASSERT(reporter, ctxInfo.grContext());
 }
 
-// This is an example of a GPU test that tests a property that uses the null GPU context.  It should
-// be used if the test tests some behavior that is mocked with the null context.
-DEF_GPUTEST_FOR_NULLGL_CONTEXT(TestGpuNullContext, reporter, ctxInfo) {
+// This is an example of a GPU test that tests a property that uses the mock context.  It should
+// be used if the test tests some behavior that is mocked with the mock context.
+DEF_GPUTEST_FOR_MOCK_CONTEXT(TestMockContext, reporter, ctxInfo) {
     REPORTER_ASSERT(reporter, reporter);
     REPORTER_ASSERT(reporter, ctxInfo.grContext());
 }
diff --git a/tests/TestUtils.cpp b/tests/TestUtils.cpp
index 62b651d..560894f 100644
--- a/tests/TestUtils.cpp
+++ b/tests/TestUtils.cpp
@@ -7,7 +7,7 @@
 
 #include "TestUtils.h"
 
-#include "GrProxyProvider.h"
+#include "GrContextPriv.h"
 #include "GrSurfaceContext.h"
 #include "GrSurfaceContextPriv.h"
 #include "GrSurfaceProxy.h"
@@ -94,8 +94,10 @@
     }
 }
 
-void test_copy_to_surface(skiatest::Reporter* reporter, GrProxyProvider* proxyProvider,
-                          GrSurfaceContext* dstContext, const char* testName) {
+void test_copy_to_surface(skiatest::Reporter* reporter,
+                          GrContext* context,
+                          GrSurfaceContext* dstContext,
+                          const char* testName) {
 
     int pixelCnt = dstContext->width() * dstContext->height();
     SkAutoTMalloc<uint32_t> pixels(pixelCnt);
@@ -109,7 +111,7 @@
     for (auto isRT : {false, true}) {
         for (auto origin : {kTopLeft_GrSurfaceOrigin, kBottomLeft_GrSurfaceOrigin}) {
             auto src = sk_gpu_test::MakeTextureProxyFromData(
-                    dstContext->surfPriv().getContext(), isRT, dstContext->width(),
+                    context, isRT, dstContext->width(),
                     dstContext->height(), GrColorType::kRGBA_8888, origin, pixels.get(), 0);
             dstContext->copy(src.get());
             test_read_pixels(reporter, dstContext, pixels.get(), testName);
diff --git a/tests/TestUtils.h b/tests/TestUtils.h
index 5fc9afd..1e0c1e7 100644
--- a/tests/TestUtils.h
+++ b/tests/TestUtils.h
@@ -29,7 +29,7 @@
                             bool onlyTestRTConfig, const char* testName);
 
 // Ensure that RGBA 8888 pixels can be copied into 'dstContext'
-void test_copy_to_surface(skiatest::Reporter*, GrProxyProvider*,
+void test_copy_to_surface(skiatest::Reporter*, GrContext*,
                           GrSurfaceContext* dstContext, const char* testName);
 
 // Fills data with a red-green gradient
diff --git a/tests/TextBlobCacheTest.cpp b/tests/TextBlobCacheTest.cpp
index 5523a9d..1c86de4 100644
--- a/tests/TextBlobCacheTest.cpp
+++ b/tests/TextBlobCacheTest.cpp
@@ -5,15 +5,15 @@
  * found in the LICENSE file.
  */
 
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
+#include "RandomScalerContext.h"
 #include "SkCanvas.h"
 #include "SkFontMgr.h"
 #include "SkGlyphRun.h"
 #include "SkGraphics.h"
 #include "SkPaint.h"
 #include "SkPoint.h"
-#include "SkRandomScalerContext.h"
 #include "SkSurface.h"
 #include "SkTextBlob.h"
 #include "SkTypeface.h"
@@ -61,7 +61,8 @@
         context->priv().testingOnly_setTextBlobCacheLimit(0);
     }
 
-    SkImageInfo info = SkImageInfo::Make(kWidth, kHeight, kN32_SkColorType, kPremul_SkAlphaType);
+    SkImageInfo info = SkImageInfo::Make(kWidth, kHeight, kRGBA_8888_SkColorType,
+                                         kPremul_SkAlphaType);
     auto surface(SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 0, &props));
     REPORTER_ASSERT(reporter, surface);
     if (!surface) {
@@ -129,7 +130,7 @@
     }
 
     // create surface where LCD is impossible
-    info = SkImageInfo::MakeN32Premul(kWidth, kHeight);
+    info = SkImageInfo::Make(kWidth, kHeight, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
     SkSurfaceProps propsNoLCD(0, kUnknown_SkPixelGeometry);
     auto surfaceNoLCD(canvas->makeSurface(info, &propsNoLCD));
     REPORTER_ASSERT(reporter, surface);
@@ -155,18 +156,18 @@
     draw(canvas, 1, blobs);
 }
 
-DEF_GPUTEST_FOR_NULLGL_CONTEXT(TextBlobCache, reporter, ctxInfo) {
+DEF_GPUTEST_FOR_MOCK_CONTEXT(TextBlobCache, reporter, ctxInfo) {
     text_blob_cache_inner(reporter, ctxInfo.grContext(), 1024, 256, 30, true, false);
 }
 
-DEF_GPUTEST_FOR_NULLGL_CONTEXT(TextBlobStressCache, reporter, ctxInfo) {
+DEF_GPUTEST_FOR_MOCK_CONTEXT(TextBlobStressCache, reporter, ctxInfo) {
     text_blob_cache_inner(reporter, ctxInfo.grContext(), 256, 256, 10, true, true);
 }
 
-DEF_GPUTEST_FOR_NULLGL_CONTEXT(TextBlobAbnormal, reporter, ctxInfo) {
+DEF_GPUTEST_FOR_MOCK_CONTEXT(TextBlobAbnormal, reporter, ctxInfo) {
     text_blob_cache_inner(reporter, ctxInfo.grContext(), 256, 256, 10, false, false);
 }
 
-DEF_GPUTEST_FOR_NULLGL_CONTEXT(TextBlobStressAbnormal, reporter, ctxInfo) {
+DEF_GPUTEST_FOR_MOCK_CONTEXT(TextBlobStressAbnormal, reporter, ctxInfo) {
     text_blob_cache_inner(reporter, ctxInfo.grContext(), 256, 256, 10, false, true);
 }
diff --git a/tests/TextBlobTest.cpp b/tests/TextBlobTest.cpp
index 2862208..d18d05b 100644
--- a/tests/TextBlobTest.cpp
+++ b/tests/TextBlobTest.cpp
@@ -13,7 +13,7 @@
 #include "SkTypeface.h"
 
 #include "Test.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 class TextBlobTester {
 public:
@@ -176,7 +176,7 @@
         // Kitchen sink font.
         font.setSize(42);
         font.setScaleX(4.2f);
-        font.setTypeface(sk_tool_utils::create_portable_typeface());
+        font.setTypeface(ToolUtils::create_portable_typeface());
         font.setSkewX(0.42f);
         font.setHinting(kFull_SkFontHinting);
         font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
@@ -429,7 +429,7 @@
     sk_sp<SkImage> img0 = render(blob0.get());
     sk_sp<SkImage> img1 = render(blob1.get());
     if (img0 && img1) {
-        REPORTER_ASSERT(reporter, sk_tool_utils::equal_pixels(img0.get(), img1.get()));
+        REPORTER_ASSERT(reporter, ToolUtils::equal_pixels(img0.get(), img1.get()));
     }
 }
 
diff --git a/tests/TextureBindingsResetTest.cpp b/tests/TextureBindingsResetTest.cpp
new file mode 100644
index 0000000..b338754
--- /dev/null
+++ b/tests/TextureBindingsResetTest.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrContextPriv.h"
+#include "SkSurface.h"
+#include "Test.h"
+#include "gl/GrGLDefines.h"
+#include "gl/GrGLGpu.h"
+#include "gl/GrGLUtil.h"
+
+DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(TextureBindingsResetTest, reporter, ctxInfo) {
+#define GL(F) GR_GL_CALL(ctxInfo.glContext()->gl(), F)
+
+    GrContext* context = ctxInfo.grContext();
+    GrGLGpu* gpu = static_cast<GrGLGpu*>(context->priv().getGpu());
+
+    struct Target {
+        GrGLenum fName;
+        GrGLenum fQuery;
+    };
+    SkTDArray<Target> targets;
+    targets.push_back({GR_GL_TEXTURE_2D, GR_GL_TEXTURE_BINDING_2D});
+    bool supportExternal;
+    if ((supportExternal = gpu->glCaps().shaderCaps()->externalTextureSupport())) {
+        targets.push_back({GR_GL_TEXTURE_EXTERNAL, GR_GL_TEXTURE_BINDING_EXTERNAL});
+    }
+    bool supportRectangle;
+    if ((supportRectangle = gpu->glCaps().rectangleTextureSupport())) {
+        targets.push_back({GR_GL_TEXTURE_RECTANGLE, GR_GL_TEXTURE_BINDING_RECTANGLE});
+    }
+    GrGLint numUnits;
+    GL(GetIntegerv(GR_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &numUnits));
+    SkTDArray<GrGLuint> claimedIDs;
+    claimedIDs.setCount(numUnits * targets.count());
+    GL(GenTextures(claimedIDs.count(), claimedIDs.begin()));
+
+    auto resetBindings = [&] {
+        int i = 0;
+        for (int u = 0; u < numUnits; ++u) {
+            GL(ActiveTexture(GR_GL_TEXTURE0 + u));
+            for (auto target : targets) {
+                GL(BindTexture(target.fName, claimedIDs[i++]));
+            }
+        }
+    };
+    auto checkBindings = [&] {
+        int i = 0;
+        for (int u = 0; u < numUnits; ++u) {
+            GL(ActiveTexture(GR_GL_TEXTURE0 + u));
+            for (auto target : targets) {
+                GrGLuint boundID = ~0;
+                GL(GetIntegerv(target.fQuery, reinterpret_cast<GrGLint*>(&boundID)));
+                if (boundID != claimedIDs[i] && boundID != 0) {
+                    ERRORF(reporter, "Unit %d, target 0x%04x has ID %d bound. Expected %d or 0.", u,
+                           target.fName, boundID, claimedIDs[i]);
+                    return;
+                }
+                ++i;
+            }
+        }
+    };
+
+    // Initialize texture unit/target combo bindings to 0.
+    context->flush();
+    resetBindings();
+    context->resetContext();
+
+    // Test creating a texture and then resetting bindings.
+    GrSurfaceDesc desc;
+    desc.fWidth = desc.fHeight = 10;
+    desc.fConfig = kRGBA_8888_GrPixelConfig;
+    auto tex = gpu->createTexture(desc, SkBudgeted::kNo);
+    REPORTER_ASSERT(reporter, tex);
+    context->resetGLTextureBindings();
+    checkBindings();
+    resetBindings();
+    context->resetContext();
+
+    // Test drawing and then resetting bindings. This should force a MIP regeneration if MIP
+    // maps are supported as well.
+    auto info = SkImageInfo::Make(10, 10, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+    auto surf = SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info, 1, nullptr);
+    surf->getCanvas()->clear(0x80FF0000);
+    auto img = surf->makeImageSnapshot();
+    surf->getCanvas()->clear(SK_ColorBLUE);
+    surf->getCanvas()->save();
+    surf->getCanvas()->scale(0.25, 0.25);
+    SkPaint paint;
+    paint.setFilterQuality(kHigh_SkFilterQuality);
+    surf->getCanvas()->drawImage(img, 0, 0, &paint);
+    surf->getCanvas()->restore();
+    surf->flush();
+    context->resetGLTextureBindings();
+    checkBindings();
+    resetBindings();
+    context->resetContext();
+
+    if (supportExternal) {
+        GrBackendTexture texture2D = gpu->createTestingOnlyBackendTexture(
+                nullptr, 10, 10, GrColorType::kRGBA_8888, false, GrMipMapped::kNo);
+        GrGLTextureInfo info2D;
+        REPORTER_ASSERT(reporter, texture2D.getGLTextureInfo(&info2D));
+        GrEGLImage eglImage = ctxInfo.glContext()->texture2DToEGLImage(info2D.fID);
+        REPORTER_ASSERT(reporter, eglImage);
+        GrGLTextureInfo infoExternal;
+        infoExternal.fID = ctxInfo.glContext()->eglImageToExternalTexture(eglImage);
+        infoExternal.fTarget = GR_GL_TEXTURE_EXTERNAL;
+        infoExternal.fFormat = info2D.fFormat;
+        REPORTER_ASSERT(reporter, infoExternal.fID);
+        GrBackendTexture backendTexture(10, 10, GrMipMapped::kNo, infoExternal);
+        // Above texture creation will have messed with GL state and bindings.
+        resetBindings();
+        context->resetContext();
+        img = SkImage::MakeFromTexture(context, backendTexture, kTopLeft_GrSurfaceOrigin,
+                                       kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr);
+        REPORTER_ASSERT(reporter, img);
+        surf->getCanvas()->drawImage(img, 0, 0);
+        img.reset();
+        surf->flush();
+        context->resetGLTextureBindings();
+        checkBindings();
+        resetBindings();
+        GL(DeleteTextures(1, &infoExternal.fID));
+        ctxInfo.glContext()->destroyEGLImage(eglImage);
+        gpu->deleteTestingOnlyBackendTexture(texture2D);
+        context->resetContext();
+    }
+
+    if (supportRectangle) {
+        GrGLuint id = ctxInfo.glContext()->createTextureRectangle(10, 10, GR_GL_RGBA, GR_GL_RGBA,
+                                                                  GR_GL_UNSIGNED_BYTE, nullptr);
+        // Above texture creation will have messed with GL state and bindings.
+        resetBindings();
+        context->resetContext();
+        if (id) {
+            GrGLTextureInfo info;
+            info.fTarget = GR_GL_TEXTURE_RECTANGLE;
+            info.fFormat = GR_GL_RGBA8;
+            info.fID = id;
+            GrBackendTexture backendTexture(10, 10, GrMipMapped::kNo, info);
+            img = SkImage::MakeFromTexture(context, backendTexture, kTopLeft_GrSurfaceOrigin,
+                                           kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr);
+            REPORTER_ASSERT(reporter, img);
+            surf->getCanvas()->drawImage(img, 0, 0);
+            img.reset();
+            surf->flush();
+            context->resetGLTextureBindings();
+            checkBindings();
+            resetBindings();
+            GL(DeleteTextures(1, &id));
+            context->resetContext();
+        }
+    }
+
+    GL(DeleteTextures(claimedIDs.count(), claimedIDs.begin()));
+
+#undef GL
+}
diff --git a/tests/TextureProxyTest.cpp b/tests/TextureProxyTest.cpp
index 9e6067e..f26dbb7 100644
--- a/tests/TextureProxyTest.cpp
+++ b/tests/TextureProxyTest.cpp
@@ -46,7 +46,7 @@
 
     sk_sp<GrTextureProxy> proxy =
             proxyProvider->createProxy(format, desc, kBottomLeft_GrSurfaceOrigin, fit,
-                                       SkBudgeted::kYes);
+                                       SkBudgeted::kYes, GrInternalSurfaceFlags::kNoPendingIO);
     // Only budgeted & wrapped external proxies get to carry uniqueKeys
     REPORTER_ASSERT(reporter, !proxy->getUniqueKey().isValid());
     return proxy;
@@ -59,7 +59,8 @@
             ctx->priv().caps()->getBackendFormatFromColorType(kRGBA_8888_SkColorType);
 
     sk_sp<GrTextureProxy> proxy =
-            proxyProvider->createProxy(format, desc, kBottomLeft_GrSurfaceOrigin, fit, SkBudgeted::kYes);
+            proxyProvider->createProxy(format, desc, kBottomLeft_GrSurfaceOrigin, fit,
+                                       SkBudgeted::kYes, GrInternalSurfaceFlags::kNoPendingIO);
     // Only budgeted & wrapped external proxies get to carry uniqueKeys
     REPORTER_ASSERT(reporter, !proxy->getUniqueKey().isValid());
     return proxy;
@@ -104,7 +105,8 @@
 
     const GrSurfaceDesc desc = make_desc(kNone_GrSurfaceFlags);
 
-    *backingSurface = resourceProvider->createTexture(desc, SkBudgeted::kNo);
+    *backingSurface = resourceProvider->createTexture(desc, SkBudgeted::kNo,
+                                                      GrResourceProvider::Flags::kNoPendingIO);
     if (!(*backingSurface)) {
         return nullptr;
     }
diff --git a/tests/TextureStripAtlasManagerTest.cpp b/tests/TextureStripAtlasManagerTest.cpp
index 0511e64..c1aeb44 100644
--- a/tests/TextureStripAtlasManagerTest.cpp
+++ b/tests/TextureStripAtlasManagerTest.cpp
@@ -33,7 +33,7 @@
                                                       gColors,
                                                       gPos,
                                                       7,
-                                                      SkShader::kClamp_TileMode));
+                                                      SkTileMode::kClamp));
 
     SkImageInfo info = SkImageInfo::MakeN32Premul(128, 128);
     auto surface(SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info));
diff --git a/tests/ToSRGBColorFilter.cpp b/tests/ToSRGBColorFilter.cpp
deleted file mode 100644
index 519ffb8..0000000
--- a/tests/ToSRGBColorFilter.cpp
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkColorSpace.h"
-#include "SkToSRGBColorFilter.h"
-#include "Test.h"
-
-
-DEF_TEST(SkToSRGBColorFilter, r) {
-
-    // sRGB -> sRGB is a no-op.
-    REPORTER_ASSERT(r, nullptr == SkToSRGBColorFilter::Make(SkColorSpace::MakeSRGB()));
-
-    // The transfer function matters just as much as the gamut.
-    REPORTER_ASSERT(r, nullptr != SkToSRGBColorFilter::Make(SkColorSpace::MakeSRGBLinear()));
-
-    // We generally interpret nullptr source spaces as sRGB.  See also chromium:787718.
-    REPORTER_ASSERT(r, nullptr == SkToSRGBColorFilter::Make(nullptr));
-
-    // Here's a realistic conversion.
-    auto dci_p3 = SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, SkNamedGamut::kDCIP3);
-    REPORTER_ASSERT(r, nullptr != SkToSRGBColorFilter::Make(dci_p3));
-
-}
diff --git a/tests/TopoSortTest.cpp b/tests/TopoSortTest.cpp
index 18ad75d..e45b6b8 100644
--- a/tests/TopoSortTest.cpp
+++ b/tests/TopoSortTest.cpp
@@ -9,9 +9,9 @@
 #include "SkTTopoSort.h"
 #include "Test.h"
 
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
-typedef void (*CreateGraphPF)(SkTArray<sk_sp<sk_tool_utils::TopoTestNode>>* graph);
+typedef void (*CreateGraphPF)(SkTArray<sk_sp<ToolUtils::TopoTestNode>>* graph);
 
 /* Simple diamond
  *       3
@@ -20,8 +20,8 @@
  *     \   /
  *       0
  */
-static void create_graph0(SkTArray<sk_sp<sk_tool_utils::TopoTestNode>>* graph) {
-    sk_tool_utils::TopoTestNode::AllocNodes(graph, 4);
+static void create_graph0(SkTArray<sk_sp<ToolUtils::TopoTestNode>>* graph) {
+    ToolUtils::TopoTestNode::AllocNodes(graph, 4);
 
     (*graph)[0]->dependsOn((*graph)[1].get());
     (*graph)[0]->dependsOn((*graph)[2].get());
@@ -38,8 +38,8 @@
  *     |
  *     0
  */
-static void create_graph1(SkTArray<sk_sp<sk_tool_utils::TopoTestNode>>* graph) {
-    sk_tool_utils::TopoTestNode::AllocNodes(graph, 4);
+static void create_graph1(SkTArray<sk_sp<ToolUtils::TopoTestNode>>* graph) {
+    ToolUtils::TopoTestNode::AllocNodes(graph, 4);
 
     (*graph)[0]->dependsOn((*graph)[1].get());
     (*graph)[1]->dependsOn((*graph)[2].get());
@@ -51,8 +51,8 @@
  *     /   \
  *    0 --- 1
  */
-static void create_graph2(SkTArray<sk_sp<sk_tool_utils::TopoTestNode>>* graph) {
-    sk_tool_utils::TopoTestNode::AllocNodes(graph, 3);
+static void create_graph2(SkTArray<sk_sp<ToolUtils::TopoTestNode>>* graph) {
+    ToolUtils::TopoTestNode::AllocNodes(graph, 3);
 
     (*graph)[0]->dependsOn((*graph)[1].get());
     (*graph)[1]->dependsOn((*graph)[2].get());
@@ -70,8 +70,8 @@
  *     \   /
  *       0
  */
-static void create_graph3(SkTArray<sk_sp<sk_tool_utils::TopoTestNode>>* graph) {
-    sk_tool_utils::TopoTestNode::AllocNodes(graph, 7);
+static void create_graph3(SkTArray<sk_sp<ToolUtils::TopoTestNode>>* graph) {
+    ToolUtils::TopoTestNode::AllocNodes(graph, 7);
 
     (*graph)[0]->dependsOn((*graph)[1].get());
     (*graph)[0]->dependsOn((*graph)[2].get());
@@ -91,8 +91,8 @@
  *     \   /       \   /
  *       0           4
  */
-static void create_graph4(SkTArray<sk_sp<sk_tool_utils::TopoTestNode>>* graph) {
-    sk_tool_utils::TopoTestNode::AllocNodes(graph, 8);
+static void create_graph4(SkTArray<sk_sp<ToolUtils::TopoTestNode>>* graph) {
+    ToolUtils::TopoTestNode::AllocNodes(graph, 8);
 
     (*graph)[0]->dependsOn((*graph)[1].get());
     (*graph)[0]->dependsOn((*graph)[2].get());
@@ -120,13 +120,13 @@
     };
 
     for (size_t i = 0; i < SK_ARRAY_COUNT(tests); ++i) {
-        SkTArray<sk_sp<sk_tool_utils::TopoTestNode>> graph;
+        SkTArray<sk_sp<ToolUtils::TopoTestNode>> graph;
 
         (tests[i].fCreate)(&graph);
 
-        sk_tool_utils::TopoTestNode::Shuffle(&graph, &rand);
+        ToolUtils::TopoTestNode::Shuffle(&graph, &rand);
 
-        bool actualResult = SkTTopoSort<sk_tool_utils::TopoTestNode>(&graph);
+        bool actualResult = SkTTopoSort<ToolUtils::TopoTestNode>(&graph);
         REPORTER_ASSERT(reporter, actualResult == tests[i].fExpectedResult);
 
         if (tests[i].fExpectedResult) {
diff --git a/tests/TracingTest.cpp b/tests/TracingTest.cpp
index e157cd7..266ed9c 100644
--- a/tests/TracingTest.cpp
+++ b/tests/TracingTest.cpp
@@ -5,7 +5,7 @@
  * found in the LICENSE file.
  */
 
-#include "SkCommandLineFlags.h"
+#include "CommandLineFlags.h"
 #include "SkImageInfo.h"
 #include "SkLeanWindows.h"
 #include "SkPoint.h"
@@ -13,7 +13,8 @@
 #include "SkTraceEvent.h"
 #include "Test.h"
 
-DEFINE_bool(slowTracingTest, false, "Artificially slow down tracing test to produce nicer JSON");
+static DEFINE_bool(slowTracingTest, false,
+                   "Artificially slow down tracing test to produce nicer JSON");
 
 namespace {
 
@@ -126,10 +127,8 @@
         // Recording multiple counters with separate COUNTER1 macros will make separate graphs.
         for (int i = 0; i < 180; ++i) {
             SkScalar rad = SkDegreesToRadians(SkIntToScalar(i));
-            SkScalar cos;
-            SkScalar sin = SkScalarSinCos(rad, &cos);
-            TRACE_COUNTER1("skia", "sin", sin * 1000.0f + 1000.0f);
-            TRACE_COUNTER1("skia", "cos", cos * 1000.0f + 1000.0f);
+            TRACE_COUNTER1("skia", "sin", SkScalarSin(rad) * 1000.0f + 1000.0f);
+            TRACE_COUNTER1("skia", "cos", SkScalarCos(rad) * 1000.0f + 1000.0f);
             do_work(10);
         }
     }
@@ -141,11 +140,9 @@
         // as a stacked bar graph. The combined graph needs a name, as does each data series.
         for (int i = 0; i < 180; ++i) {
             SkScalar rad = SkDegreesToRadians(SkIntToScalar(i));
-            SkScalar cos;
-            SkScalar sin = SkScalarSinCos(rad, &cos);
             TRACE_COUNTER2("skia", "trig",
-                           "sin", sin * 1000.0f + 1000.0f,
-                           "cos", cos * 1000.0f + 1000.0f);
+                           "sin", SkScalarSin(rad) * 1000.0f + 1000.0f,
+                           "cos", SkScalarCos(rad) * 1000.0f + 1000.0f);
             do_work(10);
         }
     }
diff --git a/tests/TransferPixelsTest.cpp b/tests/TransferPixelsTest.cpp
index dfc5a04..bb0a936 100644
--- a/tests/TransferPixelsTest.cpp
+++ b/tests/TransferPixelsTest.cpp
@@ -35,29 +35,34 @@
     }
 }
 
-bool does_full_buffer_contain_correct_values(GrColor* srcBuffer,
-                                             GrColor* dstBuffer,
-                                             int width,
-                                             int height,
-                                             int bufferWidth,
-                                             int bufferHeight) {
-    GrColor* srcPtr = srcBuffer;
-    GrColor* dstPtr = dstBuffer;
-
+bool do_buffers_contain_same_values(const GrColor* bufferA,
+                                    const GrColor* bufferB,
+                                    int width,
+                                    int height,
+                                    size_t rowBytesA,
+                                    size_t rowBytesB,
+                                    bool swiz) {
     for (int j = 0; j < height; ++j) {
         for (int i = 0; i < width; ++i) {
-            if (srcPtr[i] != dstPtr[i]) {
+            auto colorA = bufferA[i];
+            if (swiz) {
+                colorA = GrColorPackRGBA(GrColorUnpackB(colorA), GrColorUnpackG(colorA),
+                                         GrColorUnpackR(colorA), GrColorUnpackA(colorA));
+            }
+            if (colorA != bufferB[i]) {
                 return false;
             }
         }
-        srcPtr += bufferWidth;
-        dstPtr += bufferWidth;
+        bufferA = reinterpret_cast<const GrColor*>(reinterpret_cast<const char*>(bufferA) +
+                                                   rowBytesA);
+        bufferB = reinterpret_cast<const GrColor*>(reinterpret_cast<const char*>(bufferB) +
+                                                   rowBytesB);
     }
     return true;
 }
 
-void basic_transfer_test(skiatest::Reporter* reporter, GrContext* context, GrColorType colorType,
-                         bool renderTarget) {
+void basic_transfer_to_test(skiatest::Reporter* reporter, GrContext* context, GrColorType colorType,
+                            bool renderTarget) {
     if (GrCaps::kNone_MapFlags == context->priv().caps()->mapBufferFlags()) {
         return;
     }
@@ -83,9 +88,8 @@
 
     // create and fill transfer buffer
     size_t size = rowBytes*kBufferHeight;
-    auto bufferFlags = GrResourceProvider::Flags::kNoPendingIO;
-    sk_sp<GrBuffer> buffer(resourceProvider->createBuffer(size, GrGpuBufferType::kXferCpuToGpu,
-                                                          kDynamic_GrAccessPattern, bufferFlags));
+    sk_sp<GrGpuBuffer> buffer(resourceProvider->createBuffer(size, GrGpuBufferType::kXferCpuToGpu,
+                                                             kDynamic_GrAccessPattern));
     if (!buffer) {
         return;
     }
@@ -113,7 +117,8 @@
             continue;
         }
 
-        sk_sp<GrTexture> tex = resourceProvider->createTexture(desc, SkBudgeted::kNo);
+        sk_sp<GrTexture> tex = resourceProvider->createTexture(
+            desc, SkBudgeted::kNo, GrResourceProvider::Flags::kNoPendingIO);
         if (!tex) {
             continue;
         }
@@ -122,20 +127,21 @@
         // transfer full data
 
         bool result;
-        result = gpu->transferPixels(tex.get(), 0, 0, kTextureWidth, kTextureHeight, colorType,
-                                     buffer.get(), 0, rowBytes);
+        result = gpu->transferPixelsTo(tex.get(), 0, 0, kTextureWidth, kTextureHeight, colorType,
+                                       buffer.get(), 0, rowBytes);
         REPORTER_ASSERT(reporter, result);
 
         memset(dstBuffer.get(), 0xCDCD, size);
         result = gpu->readPixels(tex.get(), 0, 0, kTextureWidth, kTextureHeight, colorType,
                                  dstBuffer.get(), rowBytes);
         if (result) {
-            REPORTER_ASSERT(reporter, does_full_buffer_contain_correct_values(srcBuffer,
-                                                                              dstBuffer,
-                                                                              kTextureWidth,
-                                                                              kTextureHeight,
-                                                                              kBufferWidth,
-                                                                              kBufferHeight));
+            REPORTER_ASSERT(reporter, do_buffers_contain_same_values(srcBuffer,
+                                                                     dstBuffer,
+                                                                     kTextureWidth,
+                                                                     kTextureHeight,
+                                                                     rowBytes,
+                                                                     rowBytes,
+                                                                     false));
         }
 
         //////////////////////////
@@ -158,30 +164,201 @@
         buffer->unmap();
 
         size_t offset = sizeof(GrColor) * (kTop * kBufferWidth + kLeft);
-        result = gpu->transferPixels(tex.get(), kLeft, kTop, kWidth, kHeight, colorType,
-                                     buffer.get(), offset, rowBytes);
+        result = gpu->transferPixelsTo(tex.get(), kLeft, kTop, kWidth, kHeight, colorType,
+                                       buffer.get(), offset, rowBytes);
         REPORTER_ASSERT(reporter, result);
 
         memset(dstBuffer.get(), 0xCDCD, size);
         result = gpu->readPixels(tex.get(), 0, 0, kTextureWidth, kTextureHeight, colorType,
                                  dstBuffer.get(), rowBytes);
         if (result) {
-            REPORTER_ASSERT(reporter, does_full_buffer_contain_correct_values(srcBuffer,
-                                                                              dstBuffer,
-                                                                              kTextureWidth,
-                                                                              kTextureHeight,
-                                                                              kBufferWidth,
-                                                                              kBufferHeight));
+            REPORTER_ASSERT(reporter, do_buffers_contain_same_values(srcBuffer,
+                                                                     dstBuffer,
+                                                                     kTextureWidth,
+                                                                     kTextureHeight,
+                                                                     rowBytes,
+                                                                     rowBytes,
+                                                                     false));
         }
     }
 }
 
-DEF_GPUTEST_FOR_RENDERING_CONTEXTS(TransferPixelsTest, reporter, ctxInfo) {
+void basic_transfer_from_test(skiatest::Reporter* reporter, const sk_gpu_test::ContextInfo& ctxInfo,
+                              GrColorType colorType, bool renderTarget) {
+    auto context = ctxInfo.grContext();
+    if (GrCaps::kNone_MapFlags == context->priv().caps()->mapBufferFlags()) {
+        return;
+    }
+
+    // On OpenGL ES it may not be possible to read back in to BGRA becagse GL_RGBA/GL_UNSIGNED_BYTE
+    // may be the only allowed format/type params to glReadPixels. So read back into GL_RGBA.
+    // TODO(bsalomon): Make this work in GrGLGpu.
+    auto readColorType = colorType;
+    if (GrColorType::kBGRA_8888 == colorType &&
+        ctxInfo.type() == sk_gpu_test::GrContextFactory::kGLES_ContextType) {
+        readColorType = GrColorType::kRGBA_8888;
+    }
+
+    auto resourceProvider = context->priv().resourceProvider();
+    GrGpu* gpu = context->priv().getGpu();
+
+    const int kTextureWidth = 16;
+    const int kTextureHeight = 16;
+
+    // We'll do a full texture read into the buffer followed by a partial read. These values
+    // describe the partial read subrect.
+    const int kPartialLeft = 2;
+    const int kPartialTop = 10;
+    const int kPartialWidth = 10;
+    const int kPartialHeight = 2;
+
+    size_t fullBufferRowBytes;
+    size_t partialBufferRowBytes;
+    size_t fullBufferOffsetAlignment;
+    size_t partialBufferOffsetAlignment;
+
+    SkAssertResult(context->priv().caps()->transferFromBufferRequirements(
+            colorType, kTextureWidth, &fullBufferRowBytes, &fullBufferOffsetAlignment));
+    SkAssertResult(context->priv().caps()->transferFromBufferRequirements(
+            colorType, kPartialWidth, &partialBufferRowBytes, &partialBufferOffsetAlignment));
+
+    size_t bufferSize = fullBufferRowBytes * kTextureHeight;
+    // Arbitrary starting offset for the partial read.
+    size_t partialReadOffset = GrSizeAlignUp(11, partialBufferOffsetAlignment);
+    bufferSize =
+            SkTMax(bufferSize, partialReadOffset + partialBufferRowBytes * partialBufferRowBytes);
+
+    sk_sp<GrGpuBuffer> buffer(resourceProvider->createBuffer(
+            bufferSize, GrGpuBufferType::kXferGpuToCpu, kDynamic_GrAccessPattern));
+    REPORTER_ASSERT(reporter, buffer);
+    if (!buffer) {
+        return;
+    }
+
+    int expectedTransferCnt = 0;
+    gpu->stats()->reset();
+    for (auto srgbEncoding : {GrSRGBEncoded::kNo, GrSRGBEncoded::kYes}) {
+        // create texture
+        GrSurfaceDesc desc;
+        desc.fFlags = renderTarget ? kRenderTarget_GrSurfaceFlag : kNone_GrSurfaceFlags;
+        desc.fWidth = kTextureWidth;
+        desc.fHeight = kTextureHeight;
+        desc.fConfig = GrColorTypeToPixelConfig(colorType, srgbEncoding);
+        desc.fSampleCnt = 1;
+
+        if (kUnknown_GrPixelConfig == desc.fConfig) {
+            SkASSERT(GrSRGBEncoded::kYes == srgbEncoding);
+            continue;
+        }
+
+        if (!context->priv().caps()->isConfigTexturable(desc.fConfig) ||
+            (renderTarget && !context->priv().caps()->isConfigRenderable(desc.fConfig))) {
+            continue;
+        }
+
+        SkAutoTMalloc<GrColor> textureData(kTextureWidth * kTextureHeight);
+        size_t textureDataRowBytes = kTextureWidth * sizeof(GrColor);
+        fill_transfer_data(0, 0, kTextureWidth, kTextureHeight, kTextureWidth, textureData.get());
+        GrMipLevel data;
+        data.fPixels = textureData.get();
+        data.fRowBytes = kTextureWidth * sizeof(GrColor);
+        sk_sp<GrTexture> tex = resourceProvider->createTexture(desc, SkBudgeted::kNo, &data, 1);
+        if (!tex) {
+            continue;
+        }
+
+        //////////////////////////
+        // transfer full data
+        auto bufferRowBytes = gpu->transferPixelsFrom(
+                tex.get(), 0, 0, kTextureWidth, kTextureHeight, readColorType, buffer.get(), 0);
+        REPORTER_ASSERT(reporter, bufferRowBytes = fullBufferRowBytes);
+        if (!bufferRowBytes) {
+            continue;
+        }
+        ++expectedTransferCnt;
+
+        // TODO(bsalomon): caps to know if the map() is synchronous and skip the flush if so.
+        gpu->finishFlush(nullptr, SkSurface::BackendSurfaceAccess::kNoAccess,
+                         kSyncCpu_GrFlushFlag, 0, nullptr);
+
+        const auto* map = reinterpret_cast<const GrColor*>(buffer->map());
+        REPORTER_ASSERT(reporter, map);
+        if (!map) {
+            continue;
+        }
+        REPORTER_ASSERT(reporter, do_buffers_contain_same_values(textureData.get(),
+                                                                 map,
+                                                                 kTextureWidth,
+                                                                 kTextureHeight,
+                                                                 textureDataRowBytes,
+                                                                 bufferRowBytes,
+                                                                 readColorType != colorType));
+        buffer->unmap();
+
+        ///////////////////////
+        // Now test a partial read at an offset into the buffer.
+        bufferRowBytes = gpu->transferPixelsFrom(tex.get(), kPartialLeft, kPartialTop,
+                                                 kPartialWidth, kPartialHeight, readColorType,
+                                                 buffer.get(), partialReadOffset);
+        REPORTER_ASSERT(reporter, bufferRowBytes = partialBufferRowBytes);
+        if (!bufferRowBytes) {
+            continue;
+        }
+        ++expectedTransferCnt;
+
+        // TODO(bsalomon): caps to know if the map() is synchronous and skip the flush if so.
+        gpu->finishFlush(nullptr, SkSurface::BackendSurfaceAccess::kNoAccess,
+                         kSyncCpu_GrFlushFlag, 0, nullptr);
+
+        map = reinterpret_cast<const GrColor*>(buffer->map());
+        REPORTER_ASSERT(reporter, map);
+        if (!map) {
+            continue;
+        }
+        const GrColor* textureDataStart = reinterpret_cast<const GrColor*>(
+                reinterpret_cast<const char*>(textureData.get()) +
+                textureDataRowBytes * kPartialTop + sizeof(GrColor) * kPartialLeft);
+        const GrColor* bufferStart = reinterpret_cast<const GrColor*>(
+                reinterpret_cast<const char*>(map) + partialReadOffset);
+        REPORTER_ASSERT(reporter, do_buffers_contain_same_values(textureDataStart,
+                                                                 bufferStart,
+                                                                 kPartialWidth,
+                                                                 kPartialHeight,
+                                                                 textureDataRowBytes,
+                                                                 bufferRowBytes,
+                                                                 readColorType != colorType));
+        buffer->unmap();
+    }
+#if GR_GPU_STATS
+    REPORTER_ASSERT(reporter, gpu->stats()->transfersFromSurface() == expectedTransferCnt);
+#else
+    (void)expectedTransferCnt;
+#endif
+}
+
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(TransferPixelsToTest, reporter, ctxInfo) {
+    if (!ctxInfo.grContext()->priv().caps()->transferBufferSupport()) {
+        return;
+    }
     // RGBA
-    basic_transfer_test(reporter, ctxInfo.grContext(), GrColorType::kRGBA_8888, false);
-    basic_transfer_test(reporter, ctxInfo.grContext(), GrColorType::kRGBA_8888, true);
+    basic_transfer_to_test(reporter, ctxInfo.grContext(), GrColorType::kRGBA_8888, false);
+    basic_transfer_to_test(reporter, ctxInfo.grContext(), GrColorType::kRGBA_8888, true);
 
     // BGRA
-    basic_transfer_test(reporter, ctxInfo.grContext(), GrColorType::kBGRA_8888, false);
-    basic_transfer_test(reporter, ctxInfo.grContext(), GrColorType::kBGRA_8888, true);
+    basic_transfer_to_test(reporter, ctxInfo.grContext(), GrColorType::kBGRA_8888, false);
+    basic_transfer_to_test(reporter, ctxInfo.grContext(), GrColorType::kBGRA_8888, true);
+}
+
+// TODO(bsalomon): Metal
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(TransferPixelsFromTest, reporter, ctxInfo) {
+    if (!ctxInfo.grContext()->priv().caps()->transferBufferSupport()) {
+        return;
+    }
+    // RGBA
+    basic_transfer_from_test(reporter, ctxInfo, GrColorType::kRGBA_8888, false);
+    basic_transfer_from_test(reporter, ctxInfo, GrColorType::kRGBA_8888, true);
+
+    // BGRA
+    basic_transfer_from_test(reporter, ctxInfo, GrColorType::kBGRA_8888, false);
+    basic_transfer_from_test(reporter, ctxInfo, GrColorType::kBGRA_8888, true);
 }
diff --git a/tests/TypefaceTest.cpp b/tests/TypefaceTest.cpp
index 4248afd..3858cb2 100644
--- a/tests/TypefaceTest.cpp
+++ b/tests/TypefaceTest.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "Resources.h"
 #include "SkAdvancedTypefaceMetrics.h"
 #include "SkData.h"
 #include "SkFixed.h"
@@ -12,14 +13,13 @@
 #include "SkFontMgr.h"
 #include "SkMakeUnique.h"
 #include "SkOTTable_OS_2.h"
+#include "SkRefCnt.h"
 #include "SkSFNTHeader.h"
 #include "SkStream.h"
-#include "SkRefCnt.h"
-#include "SkTestEmptyTypeface.h"
 #include "SkTypeface.h"
 #include "SkTypefaceCache.h"
-#include "Resources.h"
 #include "Test.h"
+#include "TestEmptyTypeface.h"
 
 #include <memory>
 
@@ -104,7 +104,7 @@
     }
 
     int fontIndex;
-    std::unique_ptr<SkStreamAsset> stream(typeface->openStream(&fontIndex));
+    std::unique_ptr<SkStreamAsset> stream = typeface->openStream(&fontIndex);
 
     sk_sp<SkFontMgr> fm = SkFontMgr::RefDefault();
     sk_sp<SkTypeface> typeface2 = fm->makeFromStream(std::move(stream), fontIndex);
@@ -271,21 +271,21 @@
 }
 static int count(skiatest::Reporter* reporter, const SkTypefaceCache& cache) {
     int count = 0;
-    SkTypeface* none = cache.findByProcAndRef(count_proc, &count);
+    sk_sp<SkTypeface> none = cache.findByProcAndRef(count_proc, &count);
     REPORTER_ASSERT(reporter, none == nullptr);
     return count;
 }
 
 DEF_TEST(TypefaceCache, reporter) {
-    sk_sp<SkTypeface> t1(SkTestEmptyTypeface::Make());
+    sk_sp<SkTypeface> t1(TestEmptyTypeface::Make());
     {
         SkTypefaceCache cache;
         REPORTER_ASSERT(reporter, count(reporter, cache) == 0);
         {
-            sk_sp<SkTypeface> t0(SkTestEmptyTypeface::Make());
-            cache.add(t0.get());
+            sk_sp<SkTypeface> t0(TestEmptyTypeface::Make());
+            cache.add(t0);
             REPORTER_ASSERT(reporter, count(reporter, cache) == 1);
-            cache.add(t1.get());
+            cache.add(t1);
             REPORTER_ASSERT(reporter, count(reporter, cache) == 2);
             cache.purgeAll();
             REPORTER_ASSERT(reporter, count(reporter, cache) == 2);
diff --git a/tests/VerticesTest.cpp b/tests/VerticesTest.cpp
index b38a9de..27cc0a1 100644
--- a/tests/VerticesTest.cpp
+++ b/tests/VerticesTest.cpp
@@ -8,8 +8,8 @@
 #include "SkCanvas.h"
 #include "SkSurface.h"
 #include "SkVertices.h"
-#include "sk_pixel_iter.h"
 #include "Test.h"
+#include "ToolUtils.h"
 
 static bool equal(const SkVertices* v0, const SkVertices* v1) {
     if (v0->mode() != v1->mode()) {
@@ -136,7 +136,7 @@
     SkPoint pts[] = { { -10, 1 }, { -10, 2 }, { 1e9f, 1.5f } };
     fill_triangle(surf->getCanvas(), pts, SK_ColorBLACK);
 
-    sk_tool_utils::PixelIter iter(surf.get());
+    ToolUtils::PixelIter iter(surf.get());
     SkIPoint loc;
     while (void* addr = iter.next(&loc)) {
         SkPMColor c = *(SkPMColor*)addr;
diff --git a/tests/VkBackendSurfaceTest.cpp b/tests/VkBackendSurfaceTest.cpp
index 1f3cc65f..b30c0b8 100644
--- a/tests/VkBackendSurfaceTest.cpp
+++ b/tests/VkBackendSurfaceTest.cpp
@@ -63,7 +63,7 @@
                                                            kPremul_SkAlphaType, nullptr);
     REPORTER_ASSERT(reporter, wrappedImage.get());
 
-    sk_sp<GrTextureProxy> texProxy = as_IB(wrappedImage)->asTextureProxyRef();
+    sk_sp<GrTextureProxy> texProxy = as_IB(wrappedImage)->asTextureProxyRef(context);
     REPORTER_ASSERT(reporter, texProxy.get());
     REPORTER_ASSERT(reporter, texProxy->isInstantiated());
     GrTexture* texture = texProxy->peekTexture();
@@ -120,4 +120,137 @@
     gpu->deleteTestingOnlyBackendTexture(backendTex);
 }
 
+static void testing_release_proc(void* ctx) {
+    int* count = (int*)ctx;
+    *count += 1;
+}
+
+// Test to make sure we don't call our release proc on an image until we've transferred it back to
+// its original queue family.
+DEF_GPUTEST_FOR_VULKAN_CONTEXT(VkReleaseExternalQueueTest, reporter, ctxInfo) {
+    GrContext* context = ctxInfo.grContext();
+    GrVkGpu* gpu = static_cast<GrVkGpu*>(context->priv().getGpu());
+    if (!gpu->vkCaps().supportsExternalMemory()) {
+        return;
+    }
+
+    for (bool useExternal : {false, true}) {
+        GrBackendTexture backendTex = gpu->createTestingOnlyBackendTexture(nullptr, 1, 1,
+                                                                           GrColorType::kRGBA_8888,
+                                                                           false,
+                                                                           GrMipMapped::kNo);
+        sk_sp<SkImage> image;
+        int count = 0;
+        if (useExternal) {
+            // Make a backend texture with an external queue family;
+            GrVkImageInfo vkInfo;
+            if (!backendTex.getVkImageInfo(&vkInfo)) {
+                return;
+            }
+            vkInfo.fCurrentQueueFamily = VK_QUEUE_FAMILY_EXTERNAL;
+
+            GrBackendTexture vkExtTex(1, 1, vkInfo);
+            REPORTER_ASSERT(reporter, vkExtTex.isValid());
+            image = SkImage::MakeFromTexture(context, vkExtTex,
+                                             kTopLeft_GrSurfaceOrigin,
+                                             kRGBA_8888_SkColorType,
+                                             kPremul_SkAlphaType,
+                                             nullptr, testing_release_proc,
+                                             (void*)&count);
+
+        } else {
+            image = SkImage::MakeFromTexture(context, backendTex,
+                                             kTopLeft_GrSurfaceOrigin,
+                                             kRGBA_8888_SkColorType,
+                                             kPremul_SkAlphaType,
+                                             nullptr, testing_release_proc,
+                                             (void*)&count);
+        }
+
+        if (!image) {
+            continue;
+        }
+
+        REPORTER_ASSERT(reporter, !count);
+
+        GrTexture* texture = image->getTexture();
+        REPORTER_ASSERT(reporter, texture);
+        GrVkTexture* vkTex = static_cast<GrVkTexture*>(texture);
+
+        if (useExternal) {
+            // Testing helper so we claim that we don't need to transition from our fake external
+            // queue first.
+            vkTex->setCurrentQueueFamilyToGraphicsQueue(gpu);
+        }
+
+        image.reset();
+
+        // Resetting the image should only trigger the release proc if we are not using an external
+        // queue. When using an external queue when we free the SkImage and the underlying
+        // GrTexture, we submit a queue transition on the command buffer.
+        if (useExternal) {
+            REPORTER_ASSERT(reporter, !count);
+        } else {
+            REPORTER_ASSERT(reporter, count == 1);
+        }
+
+        gpu->testingOnly_flushGpuAndSync();
+
+        // Now that we flushed and waited the release proc should have be triggered.
+        REPORTER_ASSERT(reporter, count == 1);
+
+        gpu->deleteTestingOnlyBackendTexture(backendTex);
+    }
+}
+
+// Test to make sure we transition from the EXTERNAL queue even when no layout transition is needed.
+DEF_GPUTEST_FOR_VULKAN_CONTEXT(VkTransitionExternalQueueTest, reporter, ctxInfo) {
+    GrContext* context = ctxInfo.grContext();
+    GrVkGpu* gpu = static_cast<GrVkGpu*>(context->priv().getGpu());
+    if (!gpu->vkCaps().supportsExternalMemory()) {
+        return;
+    }
+
+    GrBackendTexture backendTex = gpu->createTestingOnlyBackendTexture(
+            nullptr, 1, 1, GrColorType::kRGBA_8888, false, GrMipMapped::kNo);
+    sk_sp<SkImage> image;
+    // Make a backend texture with an external queue family and general layout.
+    GrVkImageInfo vkInfo;
+    if (!backendTex.getVkImageInfo(&vkInfo)) {
+        return;
+    }
+    vkInfo.fCurrentQueueFamily = VK_QUEUE_FAMILY_EXTERNAL;
+    // Use a read-only layout as these are the ones where we can otherwise skip a transition.
+    vkInfo.fImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+    GrBackendTexture vkExtTex(1, 1, vkInfo);
+    REPORTER_ASSERT(reporter, vkExtTex.isValid());
+    image = SkImage::MakeFromTexture(context, vkExtTex, kTopLeft_GrSurfaceOrigin,
+                                     kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr, nullptr,
+                                     nullptr);
+
+    if (!image) {
+        return;
+    }
+
+    GrTexture* texture = image->getTexture();
+    REPORTER_ASSERT(reporter, texture);
+    GrVkTexture* vkTex = static_cast<GrVkTexture*>(texture);
+
+    // Change our backend texture to the internal queue, with the same layout. This should force a
+    // queue transition even though the layouts match.
+    vkTex->setImageLayout(gpu, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0,
+                          VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, false, false);
+
+    // Get our image info again and make sure we transitioned queues.
+    GrBackendTexture newBackendTexture = image->getBackendTexture(true);
+    GrVkImageInfo newVkInfo;
+    REPORTER_ASSERT(reporter, newBackendTexture.getVkImageInfo(&newVkInfo));
+    REPORTER_ASSERT(reporter, newVkInfo.fCurrentQueueFamily == gpu->queueIndex());
+
+    image.reset();
+    gpu->testingOnly_flushGpuAndSync();
+    gpu->deleteTestingOnlyBackendTexture(backendTex);
+}
+
 #endif
diff --git a/tests/VkHardwareBufferTest.cpp b/tests/VkHardwareBufferTest.cpp
index 4fa4b88..9209401 100644
--- a/tests/VkHardwareBufferTest.cpp
+++ b/tests/VkHardwareBufferTest.cpp
@@ -821,9 +821,9 @@
     if (!this->setupSemaphoreForSignaling(reporter, &semaphore)) {
         return false;
     }
-    GrSemaphoresSubmitted submitted = fGrContext->flushAndSignalSemaphores(1, &semaphore);
+    GrSemaphoresSubmitted submitted = fGrContext->flush(kNone_GrFlushFlags, 1, &semaphore);
     if (GrSemaphoresSubmitted::kNo == submitted) {
-        ERRORF(reporter, "Failing call to flushAndSignalSemaphores on SkSurface");
+        ERRORF(reporter, "Failing call to flush on GrContext");
         return false;
     }
     SkASSERT(semaphore.isInitialized());
diff --git a/tests/VkPriorityExtensionTest.cpp b/tests/VkPriorityExtensionTest.cpp
new file mode 100644
index 0000000..622620a
--- /dev/null
+++ b/tests/VkPriorityExtensionTest.cpp
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTypes.h"
+
+#if SK_SUPPORT_GPU && defined(SK_VULKAN)
+
+#include "SkAutoMalloc.h"
+#include "Test.h"
+#include "vk/GrVkTypes.h"
+#include "../tools/gpu/vk/VkTestUtils.h"
+
+#define ACQUIRE_VK_PROC_NOCHECK(name, instance, device) \
+    PFN_vk##name grVk##name = reinterpret_cast<PFN_vk##name>(getProc("vk" #name, instance, device))
+
+#define ACQUIRE_VK_PROC(name, instance, device)                                    \
+    PFN_vk##name grVk##name =                                                      \
+            reinterpret_cast<PFN_vk##name>(getProc("vk" #name, instance, device)); \
+    do {                                                                           \
+        if (grVk##name == nullptr) {                                               \
+            if (device != VK_NULL_HANDLE) {                                        \
+                destroy_instance(getProc, inst);                                   \
+            }                                                                      \
+            return;                                                                \
+        }                                                                          \
+    } while (0)
+
+#define ACQUIRE_VK_PROC_LOCAL(name, instance, device)                              \
+    PFN_vk##name grVk##name =                                                      \
+            reinterpret_cast<PFN_vk##name>(getProc("vk" #name, instance, device)); \
+    do {                                                                           \
+        if (grVk##name == nullptr) {                                               \
+            return;                                                                \
+        }                                                                          \
+    } while (0)
+
+#define GET_PROC_LOCAL(F, inst, device) PFN_vk ## F F = (PFN_vk ## F) getProc("vk" #F, inst, device)
+
+static void destroy_instance(GrVkGetProc getProc, VkInstance inst) {
+    ACQUIRE_VK_PROC_LOCAL(DestroyInstance, inst, VK_NULL_HANDLE);
+    grVkDestroyInstance(inst, nullptr);
+}
+
+// If the extension VK_EXT_GLOBAL_PRIORITY is supported, this test just tries to create a VkDevice
+// using the various global priorities. The test passes if no errors are reported or the test
+// doesn't crash.
+DEF_GPUTEST(VulkanPriorityExtension, reporter, options) {
+    PFN_vkGetInstanceProcAddr instProc;
+    PFN_vkGetDeviceProcAddr devProc;
+    if (!sk_gpu_test::LoadVkLibraryAndGetProcAddrFuncs(&instProc, &devProc)) {
+        return;
+    }
+    auto getProc = [instProc, devProc](const char* proc_name,
+                                       VkInstance instance, VkDevice device) {
+        if (device != VK_NULL_HANDLE) {
+            return devProc(device, proc_name);
+        }
+        return instProc(instance, proc_name);
+    };
+
+    VkResult err;
+
+    ACQUIRE_VK_PROC_NOCHECK(EnumerateInstanceVersion, VK_NULL_HANDLE, VK_NULL_HANDLE);
+    uint32_t instanceVersion = 0;
+    if (!grVkEnumerateInstanceVersion) {
+        instanceVersion = VK_MAKE_VERSION(1, 0, 0);
+    } else {
+        err = grVkEnumerateInstanceVersion(&instanceVersion);
+        if (err) {
+            ERRORF(reporter, "failed ot enumerate instance version. Err: %d", err);
+            return;
+        }
+    }
+    SkASSERT(instanceVersion >= VK_MAKE_VERSION(1, 0, 0));
+    uint32_t apiVersion = VK_MAKE_VERSION(1, 0, 0);
+    if (instanceVersion >= VK_MAKE_VERSION(1, 1, 0)) {
+        // If the instance version is 1.0 we must have the apiVersion also be 1.0. However, if the
+        // instance version is 1.1 or higher, we can set the apiVersion to be whatever the highest
+        // api we may use in skia (technically it can be arbitrary). So for now we set it to 1.1
+        // since that is the highest vulkan version.
+        apiVersion = VK_MAKE_VERSION(1, 1, 0);
+    }
+
+    instanceVersion = SkTMin(instanceVersion, apiVersion);
+
+    VkPhysicalDevice physDev;
+    VkDevice device;
+    VkInstance inst;
+
+    const VkApplicationInfo app_info = {
+        VK_STRUCTURE_TYPE_APPLICATION_INFO, // sType
+        nullptr,                            // pNext
+        "vktest",                           // pApplicationName
+        0,                                  // applicationVersion
+        "vktest",                           // pEngineName
+        0,                                  // engineVersion
+        apiVersion,                         // apiVersion
+    };
+
+    const VkInstanceCreateInfo instance_create = {
+        VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,    // sType
+        nullptr,                                   // pNext
+        0,                                         // flags
+        &app_info,                                 // pApplicationInfo
+        0,                                         // enabledLayerNameCount
+        nullptr,                                   // ppEnabledLayerNames
+        0,                                         // enabledExtensionNameCount
+        nullptr,                                   // ppEnabledExtensionNames
+    };
+
+    ACQUIRE_VK_PROC(CreateInstance, VK_NULL_HANDLE, VK_NULL_HANDLE);
+    err = grVkCreateInstance(&instance_create, nullptr, &inst);
+    if (err < 0) {
+        ERRORF(reporter, "Failed to create VkInstance");
+        return;
+    }
+
+    ACQUIRE_VK_PROC(EnumeratePhysicalDevices, inst, VK_NULL_HANDLE);
+    ACQUIRE_VK_PROC(GetPhysicalDeviceProperties, inst, VK_NULL_HANDLE);
+    ACQUIRE_VK_PROC(GetPhysicalDeviceQueueFamilyProperties, inst, VK_NULL_HANDLE);
+    ACQUIRE_VK_PROC(GetPhysicalDeviceFeatures, inst, VK_NULL_HANDLE);
+    ACQUIRE_VK_PROC(CreateDevice, inst, VK_NULL_HANDLE);
+    ACQUIRE_VK_PROC(GetDeviceQueue, inst, VK_NULL_HANDLE);
+    ACQUIRE_VK_PROC(DeviceWaitIdle, inst, VK_NULL_HANDLE);
+    ACQUIRE_VK_PROC(DestroyDevice, inst, VK_NULL_HANDLE);
+
+    uint32_t gpuCount;
+    err = grVkEnumeratePhysicalDevices(inst, &gpuCount, nullptr);
+    if (err) {
+        ERRORF(reporter, "vkEnumeratePhysicalDevices failed: %d", err);
+        destroy_instance(getProc, inst);
+        return;
+    }
+    if (!gpuCount) {
+        ERRORF(reporter, "vkEnumeratePhysicalDevices returned no supported devices.");
+        destroy_instance(getProc, inst);
+        return;
+    }
+    // Just returning the first physical device instead of getting the whole array.
+    // TODO: find best match for our needs
+    gpuCount = 1;
+    err = grVkEnumeratePhysicalDevices(inst, &gpuCount, &physDev);
+    // VK_INCOMPLETE is returned when the count we provide is less than the total device count.
+    if (err && VK_INCOMPLETE != err) {
+        ERRORF(reporter, "vkEnumeratePhysicalDevices failed: %d", err);
+        destroy_instance(getProc, inst);
+        return;
+    }
+
+    // query to get the initial queue props size
+    uint32_t queueCount;
+    grVkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, nullptr);
+    if (!queueCount) {
+        ERRORF(reporter, "vkGetPhysicalDeviceQueueFamilyProperties returned no queues.");
+        destroy_instance(getProc, inst);
+        return;
+    }
+
+    SkAutoMalloc queuePropsAlloc(queueCount * sizeof(VkQueueFamilyProperties));
+    // now get the actual queue props
+    VkQueueFamilyProperties* queueProps = (VkQueueFamilyProperties*)queuePropsAlloc.get();
+
+    grVkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, queueProps);
+
+    // iterate to find the graphics queue
+    uint32_t graphicsQueueIndex = queueCount;
+    for (uint32_t i = 0; i < queueCount; i++) {
+        if (queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+            graphicsQueueIndex = i;
+            break;
+        }
+    }
+    if (graphicsQueueIndex == queueCount) {
+        ERRORF(reporter, "Could not find any supported graphics queues.");
+        destroy_instance(getProc, inst);
+        return;
+    }
+
+    GET_PROC_LOCAL(EnumerateDeviceExtensionProperties, inst, VK_NULL_HANDLE);
+    GET_PROC_LOCAL(EnumerateDeviceLayerProperties, inst, VK_NULL_HANDLE);
+
+    if (!EnumerateDeviceExtensionProperties ||
+        !EnumerateDeviceLayerProperties) {
+        destroy_instance(getProc, inst);
+        return;
+    }
+
+    // device extensions
+    // via Vulkan implementation and implicitly enabled layers
+    uint32_t extensionCount = 0;
+    err = EnumerateDeviceExtensionProperties(physDev, nullptr, &extensionCount, nullptr);
+    if (VK_SUCCESS != err) {
+        ERRORF(reporter, "Could not  enumerate device extension properties.");
+        destroy_instance(getProc, inst);
+        return;
+    }
+    VkExtensionProperties* extensions = new VkExtensionProperties[extensionCount];
+    err = EnumerateDeviceExtensionProperties(physDev, nullptr, &extensionCount, extensions);
+    if (VK_SUCCESS != err) {
+        delete[] extensions;
+        ERRORF(reporter, "Could not  enumerate device extension properties.");
+        destroy_instance(getProc, inst);
+        return;
+    }
+    bool hasPriorityExt = false;
+    for (uint32_t i = 0; i < extensionCount; ++i) {
+        if (!strcmp(extensions[i].extensionName, VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME)) {
+            hasPriorityExt = true;
+        }
+    }
+    delete[] extensions;
+
+    if (!hasPriorityExt) {
+        destroy_instance(getProc, inst);
+        return;
+    }
+
+    const char* priorityExt = VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME;
+
+    VkPhysicalDeviceFeatures deviceFeatures;
+    grVkGetPhysicalDeviceFeatures(physDev, &deviceFeatures);
+
+    // this looks like it would slow things down,
+    // and we can't depend on it on all platforms
+    deviceFeatures.robustBufferAccess = VK_FALSE;
+
+    float queuePriorities[1] = { 0.0 };
+
+    VkDeviceQueueGlobalPriorityCreateInfoEXT queuePriorityCreateInfo;
+    queuePriorityCreateInfo.sType =
+            VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT;
+    queuePriorityCreateInfo.pNext = nullptr;
+
+    VkDeviceQueueCreateInfo queueInfo = {
+        VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, // sType
+        &queuePriorityCreateInfo,                   // pNext
+        0,                                          // VkDeviceQueueCreateFlags
+        graphicsQueueIndex,                         // queueFamilyIndex
+        1,                                          // queueCount
+        queuePriorities,                            // pQueuePriorities
+    };
+
+    for (VkQueueGlobalPriorityEXT globalPriority : { VK_QUEUE_GLOBAL_PRIORITY_LOW_EXT,
+                                                     VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_EXT,
+                                                     VK_QUEUE_GLOBAL_PRIORITY_HIGH_EXT,
+                                                     VK_QUEUE_GLOBAL_PRIORITY_REALTIME_EXT }) {
+        queuePriorityCreateInfo.globalPriority = globalPriority;
+
+        const VkDeviceCreateInfo deviceInfo = {
+            VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,        // sType
+            nullptr,                                     // pNext
+            0,                                           // VkDeviceCreateFlags
+            1,                                           // queueCreateInfoCount
+            &queueInfo,                                  // pQueueCreateInfos
+            0,                                           // layerCount
+            nullptr,                                     // ppEnabledLayerNames
+            1,                                           // extensionCount
+            &priorityExt,                                // ppEnabledExtensionNames
+            &deviceFeatures                              // ppEnabledFeatures
+        };
+
+        err = grVkCreateDevice(physDev, &deviceInfo, nullptr, &device);
+
+        if (err != VK_SUCCESS && err != VK_ERROR_NOT_PERMITTED_EXT) {
+            ERRORF(reporter, "CreateDevice failed: %d, priority %d", err, globalPriority);
+            destroy_instance(getProc, inst);
+            continue;
+        }
+        if (err != VK_ERROR_NOT_PERMITTED_EXT) {
+            grVkDestroyDevice(device, nullptr);
+        }
+    }
+    destroy_instance(getProc, inst);
+}
+
+#endif
diff --git a/tests/WritePixelsTest.cpp b/tests/WritePixelsTest.cpp
index a0400bf..e7ff433 100644
--- a/tests/WritePixelsTest.cpp
+++ b/tests/WritePixelsTest.cpp
@@ -11,7 +11,7 @@
 #include "SkMathPriv.h"
 #include "SkSurface.h"
 #include "Test.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 #include "GrBackendSurface.h"
 #include "GrContext.h"
@@ -396,7 +396,6 @@
                                                        rect.height(), SkToBool(tightBmp)));
                 uint32_t idBefore = surface->generationID();
 
-                // sk_tool_utils::write_pixels(&canvas, bmp, rect.fLeft, rect.fTop, ct, at);
                 surface->writePixels(bmp, rect.fLeft, rect.fTop);
 
                 uint32_t idAfter = surface->generationID();
@@ -533,7 +532,8 @@
             context->priv().caps()->getBackendFormatFromColorType(kRGBA_8888_SkColorType);
 
         sk_sp<GrTextureProxy> temp = proxyProvider->createProxy(
-                format, desc, kTopLeft_GrSurfaceOrigin, SkBackingFit::kApprox, SkBudgeted::kYes);
+                format, desc, kTopLeft_GrSurfaceOrigin, SkBackingFit::kApprox, SkBudgeted::kYes,
+                GrInternalSurfaceFlags::kNoPendingIO);
         temp->instantiate(context->priv().resourceProvider());
     }
 
diff --git a/tests/skbug6653.cpp b/tests/skbug6653.cpp
index 8585c4e..d775195 100644
--- a/tests/skbug6653.cpp
+++ b/tests/skbug6653.cpp
@@ -101,10 +101,3 @@
     test_bug_6653(ctx, reporter, "Default");
 }
 
-// Same as above, but without explicit resource allocation.
-DEF_GPUTEST_FOR_RENDERING_CONTEXTS(skbug6653_noExplicitResourceAllocation, reporter, ctxInfo) {
-    GrContext* ctx = ctxInfo.grContext();
-    ctx->flush();
-    ctx->priv().resourceProvider()->testingOnly_setExplicitlyAllocateGPUResources(false);
-    test_bug_6653(ctx, reporter, "No ERA");
-}
diff --git a/tests/skia_test.cpp b/tests/skia_test.cpp
index 5d3169b..776bd8e 100644
--- a/tests/skia_test.cpp
+++ b/tests/skia_test.cpp
@@ -5,13 +5,14 @@
  * found in the LICENSE file.
  */
 
+#include <atomic>
+#include "CommandLineFlags.h"
 #include "CrashHandler.h"
 #include "GrContext.h"
 #include "GrContextFactory.h"
 #include "OverwriteLine.h"
 #include "PathOpsDebug.h"
 #include "Resources.h"
-#include "SkCommonFlags.h"
 #include "SkGraphics.h"
 #include "SkOSFile.h"
 #include "SkPathOpsDebug.h"
@@ -20,19 +21,36 @@
 #include "SkTemplates.h"
 #include "SkTime.h"
 #include "Test.h"
-#include <atomic>
 
 using namespace skiatest;
 using namespace sk_gpu_test;
 
-DEFINE_bool2(dumpOp, d, false, "dump the pathOps to a file to recover mid-crash.");
-DEFINE_bool2(extendedTest, x, false, "run extended tests for pathOps.");
-DEFINE_bool2(runFail, f, false, "check for success on tests known to fail.");
-DEFINE_bool2(verifyOp, y, false, "compare the pathOps result against a region.");
-DEFINE_string2(json, J, "", "write json version of tests.");
+static DEFINE_bool2(dumpOp, d, false, "dump the pathOps to a file to recover mid-crash.");
+static DEFINE_bool2(extendedTest, x, false, "run extended tests for pathOps.");
+static DEFINE_bool2(runFail, f, false, "check for success on tests known to fail.");
+static DEFINE_bool2(verifyOp, y, false, "compare the pathOps result against a region.");
+static DEFINE_string2(json, J, "", "write json version of tests.");
+static DEFINE_bool2(verbose, v, false, "enable verbose output from the test driver.");
+static DEFINE_bool2(veryVerbose, V, false, "tell individual tests to be verbose.");
+static DEFINE_bool(cpu, true, "master switch for running CPU-bound work.");
+static DEFINE_bool(gpu, true, "master switch for running GPU-bound work.");
+
+static DEFINE_string2(match, m, nullptr,
+               "[~][^]substring[$] [...] of name to run.\n"
+               "Multiple matches may be separated by spaces.\n"
+               "~ causes a matching name to always be skipped\n"
+               "^ requires the start of the name to match\n"
+               "$ requires the end of the name to match\n"
+               "^ and $ requires an exact match\n"
+               "If a name does not match any list entry,\n"
+               "it is skipped unless some list entry starts with ~");
+
+static DEFINE_int_2(threads, j, -1,
+               "Run threadsafe tests on a threadpool with this many extra threads, "
+               "defaulting to one extra thread per core.");
 
 #if DEBUG_COIN
-DEFINE_bool2(coinTest, c, false, "detect unused coincidence algorithms.");
+static DEFINE_bool2(coinTest, c, false, "detect unused coincidence algorithms.");
 #endif
 
 // need to explicitly declare this, or we get some weird infinite loop llist
@@ -113,7 +131,7 @@
 };
 
 static bool should_run(const char* testName, bool isGPUTest) {
-    if (SkCommandLineFlags::ShouldSkip(FLAGS_match, testName)) {
+    if (CommandLineFlags::ShouldSkip(FLAGS_match, testName)) {
         return false;
     }
     if (!FLAGS_cpu && !isGPUTest) {
@@ -126,7 +144,7 @@
 }
 
 int main(int argc, char** argv) {
-    SkCommandLineFlags::Parse(argc, argv);
+    CommandLineFlags::Parse(argc, argv);
 #if DEBUG_DUMP_VERIFY
     SkPathOpsDebug::gDumpOp = FLAGS_dumpOp;
     SkPathOpsDebug::gVerifyOp = FLAGS_verifyOp;
diff --git a/third_party/angle2/BUILD.gn b/third_party/angle2/BUILD.gn
index 981acda..a816d4f 100644
--- a/third_party/angle2/BUILD.gn
+++ b/third_party/angle2/BUILD.gn
@@ -90,7 +90,7 @@
               angle_translator_hlsl_sources + libangle_sources +
               libangle_common_sources + libangle_image_util_sources +
               libglesv2_sources + libangle_gl_sources +
-              angle_system_utils_sources,
+              angle_system_utils_sources + xxhash_sources,
           ".",
           angle_root)
   if (is_win) {
diff --git a/third_party/gif/SkGifImageReader.cpp b/third_party/gif/SkGifImageReader.cpp
index 0cb77ba..3484561 100644
--- a/third_party/gif/SkGifImageReader.cpp
+++ b/third_party/gif/SkGifImageReader.cpp
@@ -211,10 +211,9 @@
 // Otherwise, decoding failed; returns false in this case, which will always cause the SkGifImageReader to set the "decode failed" flag.
 bool SkGIFLZWContext::doLZW(const unsigned char* block, size_t bytesInBlock)
 {
-    const int width = m_frameContext->width();
-
     if (rowIter == rowBuffer.end())
         return true;
+    const int width = m_frameContext->width();
 
     for (const unsigned char* ch = block; bytesInBlock-- > 0; ch++) {
         // Feed the next byte into the decoder's 32-bit input buffer.
@@ -246,40 +245,71 @@
             }
 
             const int tempCode = code;
-            unsigned short codeLength = 0;
-            if (code < avail) {
-                // This is a pre-existing code, so we already know what it
-                // encodes.
-                codeLength = suffixLength[code];
-                rowIter += codeLength;
-            } else if (code == avail && oldcode != -1) {
-                // This is a new code just being added to the dictionary.
-                // It must encode the contents of the previous code, plus
-                // the first character of the previous code again.
-                codeLength = suffixLength[oldcode] + 1;
-                rowIter += codeLength;
-                *--rowIter = firstchar;
-                code = oldcode;
-            } else {
+            if (code > avail) {
                 // This is an invalid code. The dictionary is just initialized
                 // and the code is incomplete. We don't know how to handle
                 // this case.
                 return false;
             }
 
-            while (code >= clearCode) {
-                *--rowIter = suffix[code];
-                code = prefix[code];
+            if (code == avail) {
+                if (oldcode != -1) {
+                    // This is a new code just being added to the dictionary.
+                    // It must encode the contents of the previous code, plus
+                    // the first character of the previous code again.
+                    // Now we know avail is the new code we can use oldcode
+                    // value to get the code related to that.
+                    code = oldcode;
+                } else {
+                    // This is an invalid code. The dictionary is just initialized
+                    // and the code is incomplete. We don't know how to handle
+                    // this case.
+                    return false;
+                }
             }
 
-            *--rowIter = firstchar = suffix[code];
+            // code length of the oldcode for new code which is
+            // avail = oldcode + firstchar of the oldcode
+            int remaining = suffixLength[code];
+
+            // Round remaining up to multiple of SK_DICTIONARY_WORD_SIZE, because that's
+            // the granularity of the chunks we copy.  The last chunk may contain
+            // some garbage but it'll be overwritten by the next code or left unused.
+            // The working buffer is padded to account for this.
+            remaining += -remaining & (SK_DICTIONARY_WORD_SIZE - 1) ;
+            unsigned char* p = rowIter + remaining;
+
+            // Place rowIter so that after writing pixels rowIter can be set to firstchar, thereby
+            // completing the code.
+            rowIter += suffixLength[code];
+
+            while (remaining > 0) {
+                p -= SK_DICTIONARY_WORD_SIZE;
+                std::copy_n(suffix[code].begin(), SK_DICTIONARY_WORD_SIZE, p);
+                code = prefix[code];
+                remaining -= SK_DICTIONARY_WORD_SIZE;
+            }
+            const int firstchar = static_cast<unsigned char>(code);  // (strictly `suffix[code][0]`)
+
+            // This completes the new code avail and writing the corresponding
+            // pixels on target.
+            if (tempCode == avail) {
+                *rowIter++ = firstchar;
+            }
 
             // Define a new codeword in the dictionary as long as we've read
             // more than one value from the stream.
             if (avail < SK_MAX_DICTIONARY_ENTRIES && oldcode != -1) {
-                prefix[avail] = oldcode;
-                suffix[avail] = firstchar;
-                suffixLength[avail] = suffixLength[oldcode] + 1;
+                // now add avail to the dictionary for future reference
+                unsigned short codeLength = suffixLength[oldcode] + 1;
+                int l = (codeLength - 1) & (SK_DICTIONARY_WORD_SIZE - 1);
+                // If the suffix buffer is full (l == 0) then oldcode becomes the new
+                // prefix, otherwise copy and extend oldcode's buffer and use the same
+                // prefix as oldcode used.
+                prefix[avail] = (l == 0) ? oldcode : prefix[oldcode];
+                suffix[avail] = suffix[oldcode];
+                suffix[avail][l] = firstchar;
+                suffixLength[avail] = codeLength;
                 ++avail;
 
                 // If we've used up all the codewords of a given length
@@ -291,7 +321,6 @@
                 }
             }
             oldcode = tempCode;
-            rowIter += codeLength;
 
             // Output as many rows as possible.
             unsigned char* rowBegin = rowBuffer.begin();
@@ -905,20 +934,24 @@
     // the longest sequence (SK_MAX_DICTIONARY_ENTIRES + 1) - 2 values long. Since
     // each value is a byte, this is also the number of bytes in the longest
     // encodable sequence.
-    const size_t maxBytes = SK_MAX_DICTIONARY_ENTRIES - 1;
+    constexpr size_t kMaxSequence = SK_MAX_DICTIONARY_ENTRIES - 1;
+    constexpr size_t kMaxBytes = (kMaxSequence + SK_DICTIONARY_WORD_SIZE - 1)
+                         & ~(SK_DICTIONARY_WORD_SIZE - 1);
 
     // Now allocate the output buffer. We decode directly into this buffer
     // until we have at least one row worth of data, then call outputRow().
     // This means worst case we may have (row width - 1) bytes in the buffer
-    // and then decode a sequence |maxBytes| long to append.
-    rowBuffer.reset(m_frameContext->width() - 1 + maxBytes);
+    // and then decode a sequence |kMaxBytes| long to append.
+    rowBuffer.reset(m_frameContext->width() - 1 + kMaxBytes);
     rowIter = rowBuffer.begin();
     rowsRemaining = m_frameContext->height();
 
     // Clearing the whole suffix table lets us be more tolerant of bad data.
     for (int i = 0; i < clearCode; ++i) {
-        suffix[i] = i;
+        std::fill_n(suffix[i].begin(), SK_DICTIONARY_WORD_SIZE, 0);
+        suffix[i][0] = i;
         suffixLength[i] = 1;
+        prefix[i] = i;  // ensure that we have a place to find firstchar
     }
     return true;
 }
diff --git a/third_party/gif/SkGifImageReader.h b/third_party/gif/SkGifImageReader.h
index a5cb26c..7ff424a 100644
--- a/third_party/gif/SkGifImageReader.h
+++ b/third_party/gif/SkGifImageReader.h
@@ -60,6 +60,7 @@
 #define SK_MAX_DICTIONARY_ENTRIES    4096 // 2^SK_MAX_DICTIONARY_ENTRY_BITS
 #define SK_MAX_COLORS                256
 #define SK_BYTES_PER_COLORMAP_ENTRY  3
+#define SK_DICTIONARY_WORD_SIZE      8
 
 // List of possible parsing states.
 enum SkGIFState {
@@ -97,7 +98,6 @@
         , clearCode(0)
         , avail(0)
         , oldcode(0)
-        , firstchar(0)
         , bits(0)
         , datum(0)
         , ipass(0)
@@ -120,7 +120,6 @@
     int clearCode; // Codeword used to trigger dictionary reset.
     int avail; // Index of next available slot in dictionary.
     int oldcode;
-    unsigned char firstchar;
     int bits; // Number of unread bits in "datum".
     int datum; // 32-bit input buffer.
     int ipass; // Interlace pass; Ranges 1-4 if interlaced.
@@ -128,7 +127,8 @@
     size_t rowsRemaining; // Rows remaining to be output.
 
     unsigned short prefix[SK_MAX_DICTIONARY_ENTRIES];
-    unsigned char suffix[SK_MAX_DICTIONARY_ENTRIES];
+    std::array<std::array<unsigned char, SK_DICTIONARY_WORD_SIZE>,
+                SK_MAX_DICTIONARY_ENTRIES> suffix;
     unsigned short suffixLength[SK_MAX_DICTIONARY_ENTRIES];
     SkGIFRow rowBuffer; // Single scanline temporary buffer.
     unsigned char* rowIter;
diff --git a/third_party/harfbuzz/BUILD.gn b/third_party/harfbuzz/BUILD.gn
index fe15402..4718c8c 100644
--- a/third_party/harfbuzz/BUILD.gn
+++ b/third_party/harfbuzz/BUILD.gn
@@ -3,13 +3,19 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-declare_args() {
-}
-
 import("../../gn/skia.gni")
 import("../third_party.gni")
 
-if (skia_use_icu) {
+declare_args() {
+  skia_use_system_harfbuzz = is_official_build
+}
+
+if (skia_use_system_harfbuzz) {
+  system("harfbuzz") {
+    include_dirs = [ "/usr/include/harfbuzz" ]
+    libs = [ "harfbuzz" ]
+  }
+} else {
   third_party("harfbuzz") {
     _src = "../externals/harfbuzz/src"
     public_include_dirs = [
@@ -20,7 +26,6 @@
       "HAVE_ICU",
       "HAVE_ICU_BUILTIN",
       "HAVE_OT",
-      "HB_NO_MT",
     ]
     deps = [
       "//third_party/icu",
@@ -67,6 +72,7 @@
       "$_src/hb-font.hh",
       "$_src/hb-icu.cc",
       "$_src/hb-icu.h",
+      "$_src/hb-map.cc",
       "$_src/hb-mutex.hh",
       "$_src/hb-object.hh",
       "$_src/hb-open-file.hh",
@@ -91,7 +97,6 @@
       "$_src/hb-ot-hmtx-table.hh",
       "$_src/hb-ot-kern-table.hh",
       "$_src/hb-ot-layout-base-table.hh",
-      "$_src/hb-ot-layout-base-table.hh",
       "$_src/hb-ot-layout-common.hh",
       "$_src/hb-ot-layout-gdef-table.hh",
       "$_src/hb-ot-layout-gpos-table.hh",
@@ -171,8 +176,12 @@
       "$_src/hb-subset-cff-common.hh",
       "$_src/hb-subset-cff1.cc",
       "$_src/hb-subset-cff2.cc",
+      "$_src/hb-subset-glyf.cc",
       "$_src/hb-subset-glyf.hh",
+      "$_src/hb-subset-input.cc",
+      "$_src/hb-subset-plan.cc",
       "$_src/hb-subset-plan.hh",
+      "$_src/hb-subset.cc",
       "$_src/hb-subset.h",
       "$_src/hb-subset.hh",
       "$_src/hb-unicode-emoji-table.hh",
diff --git a/third_party/icu/BUILD.gn b/third_party/icu/BUILD.gn
index 0b770f3..50157f0 100644
--- a/third_party/icu/BUILD.gn
+++ b/third_party/icu/BUILD.gn
@@ -3,31 +3,53 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-declare_args() {
-  skia_use_system_icu = is_official_build
-}
-
 import("../../gn/skia.gni")
 import("../third_party.gni")
 import("icu.gni")
 
-if (skia_use_icu) {
-  if (skia_use_system_icu) {
-    system("icu") {
-      libs = [ "icuuc" ]
-      defines = [ "U_USING_ICU_NAMESPACE=0" ]
-    }
+declare_args() {
+  skia_use_system_icu = is_official_build
+}
+
+if (skia_use_system_icu) {
+  system("icu") {
+    libs = [ "icuuc" ]
+    defines = [ "U_USING_ICU_NAMESPACE=0" ]
+  }
+} else {
+  if (target_cpu == "wasm") {
+    data_assembly = "$target_gen_dir/icudtl_dat.cpp"
   } else {
     data_assembly = "$target_gen_dir/icudtl_dat.S"
-    data_dir = "../externals/icu/"
-    if (is_android) {
-      data_dir += "android"
-    } else if (is_ios) {
-      data_dir += "ios"
+  }
+  data_dir = "../externals/icu/"
+  if (target_cpu == "wasm") {
+    # Use a super super super stripped down version for wasm,
+    # which is the same thing flutter is using.
+    data_dir += "flutter"
+  } else if (is_android) {
+    data_dir += "android"
+  } else if (is_ios) {
+    data_dir += "ios"
+  } else {
+    data_dir += "common"
+  }
+  action("make_data_assembly") {
+    if (target_cpu == "wasm") {
+      _u_icu_version_major_num = "63"  # defined in source/common/unicode/uvernum.h
+      script = "make_data_cpp.py"
+      inputs = [
+        "$data_dir/icudtl.dat",
+      ]
+      outputs = [
+        data_assembly,
+      ]
+      args = [
+        "icudt${_u_icu_version_major_num}_dat",
+        rebase_path(inputs[0], root_build_dir),
+        rebase_path(data_assembly, root_build_dir),
+      ]
     } else {
-      data_dir += "common"
-    }
-    action("make_data_assembly") {
       script = "../externals/icu/scripts/make_data_assembly.py"
       inputs = [
         "$data_dir/icudtl.dat",
@@ -43,56 +65,62 @@
         args += [ "--mac" ]
       }
     }
+  }
 
-    third_party("icu") {
-      public_include_dirs = [
-        "../externals/icu/source/common",
-        "../externals/icu/source/i18n",
-        ".",
-      ]
-      public_defines = [
-        "U_USING_ICU_NAMESPACE=0",
-        "SK_USING_THIRD_PARTY_ICU",
-      ]
-      configs -= [ "//gn:no_rtti" ]
-      defines = [
-        # http://userguide.icu-project.org/howtouseicu
-        "U_COMMON_IMPLEMENTATION",
-        "U_STATIC_IMPLEMENTATION",
-        "U_ENABLE_DYLOAD=0",
-        "U_I18N_IMPLEMENTATION",
-      ]
-      sources = icu_sources
-      if (is_win) {
-        deps = [
-          ":icudata",
-        ]
-        public_defines += [
-          "U_NOEXCEPT=",
-          "U_STATIC_IMPLEMENTATION",
-        ]
-        libs = [ "Advapi32.lib" ]
-        sources += [
-          "../externals/icu/source/stubdata/stubdata.cpp",
-          "SkLoadICU.cpp",
-        ]
-      } else {
-        sources += [ "$data_assembly" ]
-        deps = [
-          ":make_data_assembly",
-        ]
-      }
+  third_party("icu") {
+    public_include_dirs = [
+      "../externals/icu/source/common",
+      "../externals/icu/source/i18n",
+      ".",
+    ]
+    public_defines = [
+      "U_USING_ICU_NAMESPACE=0",
+      "SK_USING_THIRD_PARTY_ICU",
+    ]
+    configs -= [ "//gn:no_rtti" ]
+    defines = [
+      # http://userguide.icu-project.org/howtouseicu
+      "U_COMMON_IMPLEMENTATION",
+      "U_STATIC_IMPLEMENTATION",
+      "U_ENABLE_DYLOAD=0",
+      "U_I18N_IMPLEMENTATION",
+    ]
+    if (target_cpu == "wasm") {
+      # Tell ICU that we are a 32 bit platform, otherwise,
+      # double-conversion-utils.h doesn't know how to operate.
+      defines += [ "__i386__" ]
     }
+    sources = icu_sources
     if (is_win) {
-      copy("icudata") {
-        sources = [
-          "../externals/icu/windows/icudt.dll",
-        ]
-        outputs = [
-          "$root_out_dir/icudt.dll",
-        ]
-        data = outputs
-      }
+      deps = [
+        ":icudata",
+      ]
+      public_defines += [
+        "U_NOEXCEPT=",
+        "U_STATIC_IMPLEMENTATION",
+      ]
+      libs = [ "Advapi32.lib" ]
+      sources += [
+        "../externals/icu/source/stubdata/stubdata.cpp",
+        "SkLoadICU.cpp",
+      ]
+    } else {
+      sources += [ "$data_assembly" ]
+      deps = [
+        ":make_data_assembly",
+      ]
     }
   }
+
+  copy("icudata") {
+    sources = [
+      "../externals/icu/common/icudtl.dat",
+    ]
+    outputs = [
+      "$root_out_dir/icudtl.dat",
+    ]
+    data = [
+      "$root_out_dir/icudtl.dat",
+    ]
+  }
 }
diff --git a/third_party/icu/SkLoadICU.cpp b/third_party/icu/SkLoadICU.cpp
index ff93d21..ac7ba0c 100644
--- a/third_party/icu/SkLoadICU.cpp
+++ b/third_party/icu/SkLoadICU.cpp
@@ -9,47 +9,86 @@
 #define WIN32_LEAN_AND_MEAN
 #endif
 #include <windows.h>
+#include <io.h>
 
 #include <cstdio>
+#include <cstring>
 #include <mutex>
+#include <string>
 
 #include "unicode/udata.h"
 
-#define ICU_UTIL_DATA_SYMBOL "icudt" U_ICU_VERSION_SHORT "_dat"
-#define ICU_UTIL_DATA_SHARED_MODULE_NAME "icudt.dll"
+static void* win_mmap(const char* dataFile) {
+    if (!dataFile) {
+        return nullptr;
+    }
+    struct FCloseWrapper { void operator()(FILE* f) { fclose(f); } };
+    std::unique_ptr<FILE, FCloseWrapper> stream(fopen(dataFile, "rb"));
+    if (!stream) {
+        fprintf(stderr, "SkIcuLoader: datafile missing.\n");
+        return nullptr;
+    }
+    int fileno = _fileno(stream.get());
+    if (fileno < 0) {
+        fprintf(stderr, "SkIcuLoader: datafile fileno error.\n");
+        return nullptr;
+    }
+    HANDLE file = (HANDLE)_get_osfhandle(fileno);
+    if ((HANDLE)INVALID_HANDLE_VALUE == file) {
+        fprintf(stderr, "SkIcuLoader: datafile handle error.\n");
+        return nullptr;
+    }
+    struct CloseHandleWrapper { void operator()(HANDLE h) { CloseHandle(h); } };
+    std::unique_ptr<void, CloseHandleWrapper> mmapHandle(
+        CreateFileMapping(file, nullptr, PAGE_READONLY, 0, 0, nullptr));
+    if (!mmapHandle) {
+        fprintf(stderr, "SkIcuLoader: datafile mmap error.\n");
+        return nullptr;
+    }
+    void* addr = MapViewOfFile(mmapHandle.get(), FILE_MAP_READ, 0, 0, 0);
+    if (nullptr == addr) {
+        fprintf(stderr, "SkIcuLoader: datafile view error.\n");
+        return nullptr;
+    }
+    return addr;
+}
+
+static bool init_icu(void* addr) {
+    UErrorCode err = U_ZERO_ERROR;
+    udata_setCommonData(addr, &err);
+    if (err != U_ZERO_ERROR) {
+        fprintf(stderr, "udata_setCommonData() returned %d.\n", (int)err);
+        return false;
+    }
+    udata_setFileAccess(UDATA_ONLY_PACKAGES, &err);
+    if (err != U_ZERO_ERROR) {
+        fprintf(stderr, "udata_setFileAccess() returned %d.\n", (int)err);
+        return false;
+    }
+    return true;
+}
+
+static std::string executable_directory() {
+    HMODULE hModule = GetModuleHandleA(NULL);
+    char path[MAX_PATH];
+    GetModuleFileNameA(hModule, path, MAX_PATH);
+    const char* end = strrchr(path, '\\');
+    return end ? std::string(path, end - path) : std::string();
+}
 
 bool SkLoadICU() {
     static bool good = false;
     static std::once_flag flag;
     std::call_once(flag, []() {
-        HMODULE module = LoadLibraryA(ICU_UTIL_DATA_SHARED_MODULE_NAME);
-        if (!module) {
-            fprintf(stderr, "Failed to load " ICU_UTIL_DATA_SHARED_MODULE_NAME "\n");
-            return;
+        std::string sPath = executable_directory();
+        sPath += "\\icudtl.dat";
+        if (void* addr = win_mmap(sPath.c_str())) {
+            if (init_icu(addr)) {
+                good = true;
+            }
         }
-        FARPROC addr = GetProcAddress(module, ICU_UTIL_DATA_SYMBOL);
-        if (!addr) {
-            fprintf(stderr, "Symbol " ICU_UTIL_DATA_SYMBOL " missing in "
-                     ICU_UTIL_DATA_SHARED_MODULE_NAME ".\n");
-            return;
-        }
-        UErrorCode err = U_ZERO_ERROR;
-        udata_setCommonData(reinterpret_cast<void*>(addr), &err);
-        if (err != U_ZERO_ERROR) {
-            fprintf(stderr, "udata_setCommonData() returned %d.\n", (int)err);
-            return;
-        }
-        udata_setFileAccess(UDATA_ONLY_PACKAGES, &err);
-        if (err != U_ZERO_ERROR) {
-            fprintf(stderr, "udata_setFileAccess() returned %d.\n", (int)err);
-            return;
-        }
-        good = true;
     });
     return good;
 }
 
-#undef ICU_UTIL_DATA_SHARED_MODULE_NAME
-#undef ICU_UTIL_DATA_SYMBOL
-
 #endif  // defined(_WIN32) && defined(SK_USING_THIRD_PARTY_ICU)
diff --git a/third_party/icu/make_data_cpp.py b/third_party/icu/make_data_cpp.py
new file mode 100755
index 0000000..127a495
--- /dev/null
+++ b/third_party/icu/make_data_cpp.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+
+# Copyright 2019 Google LLC.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+'''
+Generate a source file containing the given binary data.
+
+Output type is C++.
+'''
+
+import os
+import struct
+import sys
+import mmap
+
+def iterate_as_uint32(path):
+    with open(path, 'rb') as f:
+        s = struct.Struct('@I')
+        assert s.size == 4
+        mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
+        assert (len(mm) % s.size) == 0
+        for offset in xrange(0, len(mm), s.size):
+            yield s.unpack_from(mm, offset)[0]
+        mm.close()
+
+
+def convert(fmt, name, src_path, dst_path):
+    header, line_begin, line_end, footer = fmt
+    assert os.path.exists(src_path)
+    src = iterate_as_uint32(src_path)
+    with open(dst_path, 'w') as o:
+        o.write(header.format(name))
+        while True:
+            line = ','.join('%d' % v for _, v in zip(range(8), src))
+            if not line:
+                break
+            o.write('%s%s%s\n' % (line_begin, line, line_end))
+        o.write(footer.format(name))
+
+
+cpp = ('#include <cstdint>\nextern "C" uint32_t {0}[] __attribute__((aligned(16))) = {{\n',
+       '', ',', '}};\n')
+
+if __name__ == '__main__':
+    print '\n'.join('>>>  %r' % x for x in sys.argv)
+    convert(cpp, sys.argv[1], sys.argv[2], sys.argv[3])
diff --git a/third_party/opencl/BUILD.gn b/third_party/opencl/BUILD.gn
new file mode 100644
index 0000000..03b2a90
--- /dev/null
+++ b/third_party/opencl/BUILD.gn
@@ -0,0 +1,15 @@
+import("../third_party.gni")
+
+third_party("opencl") {
+  # OpenCL C++ wrapper API, cl.hpp.
+  # (Some platforms only ship the C APIs, which cl.hpp builds on.)
+  public_include_dirs = [ "../externals/opencl-registry/api/2.1" ]
+
+  if (is_linux) {
+    libs = [ "OpenCL" ]
+  } else if (is_mac) {
+    libs = [ "OpenCL.framework" ]
+  } else if (is_win) {
+    libs = [ "OpenCL.lib" ]
+  }
+}
diff --git a/third_party/skcms/skcms.cc b/third_party/skcms/skcms.cc
index f41376e..3d97dd1 100644
--- a/third_party/skcms/skcms.cc
+++ b/third_party/skcms/skcms.cc
@@ -17,6 +17,19 @@
     #include <arm_neon.h>
 #elif defined(__SSE__)
     #include <immintrin.h>
+
+    #if defined(__clang__)
+        // That #include <immintrin.h> is usually enough, but Clang's headers
+        // "helpfully" skip including the whole kitchen sink when _MSC_VER is
+        // defined, because lots of programs on Windows would include that and
+        // it'd be a lot slower.  But we want all those headers included so we
+        // can use their features after runtime checks later.
+        #include <smmintrin.h>
+        #include <avxintrin.h>
+        #include <avx2intrin.h>
+        #include <avx512fintrin.h>
+        #include <avx512dqintrin.h>
+    #endif
 #endif
 
 // sizeof(x) will return size_t, which is 32-bit on some machines and 64-bit on others.
@@ -1709,7 +1722,7 @@
             int mid = (L + N) / 2;
             float mid_x = mid / (N - 1.0f);
             float mid_y = eval_curve(curve, mid_x);
-            tf.g = log2f_(mid_y) / log2f_(mid_x);;
+            tf.g = log2f_(mid_y) / log2f_(mid_x);
             tf.a = 1;
             tf.b = 0;
             tf.e =    tf.c*tf.d + tf.f
@@ -1806,15 +1819,6 @@
     Op_store_ffff,
 } Op;
 
-// Without this wasm would try to use the N=4 128-bit vector code path,
-// which while ideal, causes tons of compiler problems.  This would be
-// a good thing to revisit as emcc matures (currently 1.38.5).
-#if 1 && defined(__EMSCRIPTEN_major__)
-    #if !defined(SKCMS_PORTABLE)
-        #define  SKCMS_PORTABLE
-    #endif
-#endif
-
 #if defined(__clang__)
     template <int N, typename T> using Vec = T __attribute__((ext_vector_type(N)));
 #elif defined(__GNUC__)
@@ -1829,7 +1833,8 @@
 // First, instantiate our default exec_ops() implementation using the default compiliation target.
 
 namespace baseline {
-#if defined(SKCMS_PORTABLE) || !(defined(__clang__) || defined(__GNUC__))
+#if defined(SKCMS_PORTABLE) || !(defined(__clang__) || defined(__GNUC__)) \
+                            || (defined(__EMSCRIPTEN_major__) && !defined(__wasm_simd128__))
     #define N 1
     using F   = float;
     using U64 = uint64_t;
@@ -1872,80 +1877,127 @@
 #if !defined(SKCMS_PORTABLE) &&                           \
         (( defined(__clang__) && __clang_major__ >= 5) || \
          (!defined(__clang__) && defined(__GNUC__)))      \
-     && defined(__x86_64__) && !defined(__AVX2__)
+     && defined(__x86_64__)
 
-    #if defined(__clang__)
-        #pragma clang attribute push(__attribute__((target("avx2,f16c"))), apply_to=function)
-    #elif defined(__GNUC__)
-        #pragma GCC push_options
-        #pragma GCC target("avx2,f16c")
+    #if !defined(__AVX2__)
+        #if defined(__clang__)
+            #pragma clang attribute push(__attribute__((target("avx2,f16c"))), apply_to=function)
+        #elif defined(__GNUC__)
+            #pragma GCC push_options
+            #pragma GCC target("avx2,f16c")
+        #endif
+
+        namespace hsw {
+            #define USING_AVX
+            #define USING_AVX_F16C
+            #define USING_AVX2
+            #define N 8
+            using   F = Vec<N,float>;
+            using I32 = Vec<N,int32_t>;
+            using U64 = Vec<N,uint64_t>;
+            using U32 = Vec<N,uint32_t>;
+            using U16 = Vec<N,uint16_t>;
+            using  U8 = Vec<N,uint8_t>;
+
+            #include "src/Transform_inl.h"
+
+            // src/Transform_inl.h will undefine USING_* for us.
+            #undef N
+        }
+
+        #if defined(__clang__)
+            #pragma clang attribute pop
+        #elif defined(__GNUC__)
+            #pragma GCC pop_options
+        #endif
+
+        #define TEST_FOR_HSW
     #endif
 
-    namespace hsw {
-        #define USING_AVX
-        #define USING_AVX_F16C
-        #define USING_AVX2
-        #define N 8
-        using   F = Vec<N,float>;
-        using I32 = Vec<N,int32_t>;
-        using U64 = Vec<N,uint64_t>;
-        using U32 = Vec<N,uint32_t>;
-        using U16 = Vec<N,uint16_t>;
-        using  U8 = Vec<N,uint8_t>;
+    #if !defined(__AVX512F__)
+        #if defined(__clang__)
+            #pragma clang attribute push(__attribute__((target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl"))), apply_to=function)
+        #elif defined(__GNUC__)
+            #pragma GCC push_options
+            #pragma GCC target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl")
+        #endif
 
-        #include "src/Transform_inl.h"
+        namespace skx {
+            #define USING_AVX512F
+            #define N 16
+            using   F = Vec<N,float>;
+            using I32 = Vec<N,int32_t>;
+            using U64 = Vec<N,uint64_t>;
+            using U32 = Vec<N,uint32_t>;
+            using U16 = Vec<N,uint16_t>;
+            using  U8 = Vec<N,uint8_t>;
 
-        // src/Transform_inl.h will undefine USING_* for us.
-        #undef N
-    }
+            #include "src/Transform_inl.h"
 
-    #if defined(__clang__)
-        #pragma clang attribute pop
-    #elif defined(__GNUC__)
-        #pragma GCC pop_options
+            // src/Transform_inl.h will undefine USING_* for us.
+            #undef N
+        }
+
+        #if defined(__clang__)
+            #pragma clang attribute pop
+        #elif defined(__GNUC__)
+            #pragma GCC pop_options
+        #endif
+
+        #define TEST_FOR_SKX
     #endif
 
-    #define TEST_FOR_HSW
+    #if defined(TEST_FOR_HSW) || defined(TEST_FOR_SKX)
+        enum class CpuType { None, HSW, SKX };
+        static CpuType cpu_type() {
+            static const CpuType type = []{
+                // See http://www.sandpile.org/x86/cpuid.htm
 
-    static bool hsw_ok() {
-        static const bool ok = []{
-            // See http://www.sandpile.org/x86/cpuid.htm
+                // First, a basic cpuid(1) lets us check prerequisites for HSW, SKX.
+                uint32_t eax, ebx, ecx, edx;
+                __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
+                                             : "0"(1), "2"(0));
+                if ((edx & (1u<<25)) &&  // SSE
+                    (edx & (1u<<26)) &&  // SSE2
+                    (ecx & (1u<< 0)) &&  // SSE3
+                    (ecx & (1u<< 9)) &&  // SSSE3
+                    (ecx & (1u<<12)) &&  // FMA (N.B. not used, avoided even)
+                    (ecx & (1u<<19)) &&  // SSE4.1
+                    (ecx & (1u<<20)) &&  // SSE4.2
+                    (ecx & (1u<<26)) &&  // XSAVE
+                    (ecx & (1u<<27)) &&  // OSXSAVE
+                    (ecx & (1u<<28)) &&  // AVX
+                    (ecx & (1u<<29))) {  // F16C
 
-            // First, a basic cpuid(1).
-            uint32_t eax, ebx, ecx, edx;
-            __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
-                                         : "0"(1), "2"(0));
+                    // Call cpuid(7) to check for AVX2 and AVX-512 bits.
+                    __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
+                                                 : "0"(7), "2"(0));
+                    // eax from xgetbv(0) will tell us whether XMM, YMM, and ZMM state is saved.
+                    uint32_t xcr0, dont_need_edx;
+                    __asm__ __volatile__("xgetbv" : "=a"(xcr0), "=d"(dont_need_edx) : "c"(0));
 
-            // Sanity check for prerequisites.
-            if ((edx & (1<<25)) != (1<<25)) { return false; }   // SSE
-            if ((edx & (1<<26)) != (1<<26)) { return false; }   // SSE2
-            if ((ecx & (1<< 0)) != (1<< 0)) { return false; }   // SSE3
-            if ((ecx & (1<< 9)) != (1<< 9)) { return false; }   // SSSE3
-            if ((ecx & (1<<19)) != (1<<19)) { return false; }   // SSE4.1
-            if ((ecx & (1<<20)) != (1<<20)) { return false; }   // SSE4.2
-
-            if ((ecx & (3<<26)) != (3<<26)) { return false; }   // XSAVE + OSXSAVE
-
-            {
-                uint32_t eax_xgetbv, edx_xgetbv;
-                __asm__ __volatile__("xgetbv" : "=a"(eax_xgetbv), "=d"(edx_xgetbv) : "c"(0));
-                if ((eax_xgetbv & (3<<1)) != (3<<1)) { return false; }  // XMM+YMM state saved?
-            }
-
-            if ((ecx & (1<<28)) != (1<<28)) { return false; }   // AVX
-            if ((ecx & (1<<29)) != (1<<29)) { return false; }   // F16C
-            if ((ecx & (1<<12)) != (1<<12)) { return false; }   // FMA  (TODO: not currently used)
-
-            // Call cpuid(7) to check for our final AVX2 feature bit!
-            __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
-                                         : "0"(7), "2"(0));
-            if ((ebx & (1<< 5)) != (1<< 5)) { return false; }   // AVX2
-
-            return true;
-        }();
-
-        return ok;
-    }
+                    if ((xcr0 & (1u<<1)) &&  // XMM register state saved?
+                        (xcr0 & (1u<<2)) &&  // YMM register state saved?
+                        (ebx  & (1u<<5))) {  // AVX2
+                        // At this point we're at least HSW.  Continue checking for SKX.
+                        if ((xcr0 & (1u<< 5)) && // Opmasks state saved?
+                            (xcr0 & (1u<< 6)) && // First 16 ZMM registers saved?
+                            (xcr0 & (1u<< 7)) && // High 16 ZMM registers saved?
+                            (ebx  & (1u<<16)) && // AVX512F
+                            (ebx  & (1u<<17)) && // AVX512DQ
+                            (ebx  & (1u<<28)) && // AVX512CD
+                            (ebx  & (1u<<30)) && // AVX512BW
+                            (ebx  & (1u<<31))) { // AVX512VL
+                            return CpuType::SKX;
+                        }
+                        return CpuType::HSW;
+                    }
+                }
+                return CpuType::None;
+            }();
+            return type;
+        }
+    #endif
 
 #endif
 
@@ -1992,6 +2044,8 @@
         case skcms_PixelFormat_RGBA_16161616LE    >> 1: return  8;
         case skcms_PixelFormat_RGB_161616BE       >> 1: return  6;
         case skcms_PixelFormat_RGBA_16161616BE    >> 1: return  8;
+        case skcms_PixelFormat_RGB_hhh_Norm       >> 1: return  6;
+        case skcms_PixelFormat_RGBA_hhhh_Norm     >> 1: return  8;
         case skcms_PixelFormat_RGB_hhh            >> 1: return  6;
         case skcms_PixelFormat_RGBA_hhhh          >> 1: return  8;
         case skcms_PixelFormat_RGB_fff            >> 1: return 12;
@@ -2091,6 +2145,8 @@
         case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_load_16161616LE; break;
         case skcms_PixelFormat_RGB_161616BE    >> 1: *ops++ = Op_load_161616BE;   break;
         case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_load_16161616BE; break;
+        case skcms_PixelFormat_RGB_hhh_Norm    >> 1: *ops++ = Op_load_hhh;        break;
+        case skcms_PixelFormat_RGBA_hhhh_Norm  >> 1: *ops++ = Op_load_hhhh;       break;
         case skcms_PixelFormat_RGB_hhh         >> 1: *ops++ = Op_load_hhh;        break;
         case skcms_PixelFormat_RGBA_hhhh       >> 1: *ops++ = Op_load_hhhh;       break;
         case skcms_PixelFormat_RGB_fff         >> 1: *ops++ = Op_load_fff;        break;
@@ -2100,6 +2156,10 @@
                                                         *args++ = palette;
                                                         break;
     }
+    if (srcFmt == skcms_PixelFormat_RGB_hhh_Norm ||
+        srcFmt == skcms_PixelFormat_RGBA_hhhh_Norm) {
+        *ops++ = Op_clamp;
+    }
     if (srcFmt & 1) {
         *ops++ = Op_swap_rb;
     }
@@ -2221,8 +2281,8 @@
         if (!is_identity_tf(&inv_dst_tf_b)) { *ops++ = Op_tf_b; *args++ = &inv_dst_tf_b; }
     }
 
-    // Clamp here before premul to make sure we're clamping to fixed-point values _and_ gamut,
-    // not just to values that fit in the fixed point representation.
+    // Clamp here before premul to make sure we're clamping to normalized values _and_ gamut,
+    // not just to values that fit in [0,1].
     //
     // E.g. r = 1.1, a = 0.5 would fit fine in fixed point after premul (ra=0.55,a=0.5),
     // but would be carrying r > 1, which is really unexpected for downstream consumers.
@@ -2250,6 +2310,8 @@
         case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_store_16161616LE; break;
         case skcms_PixelFormat_RGB_161616BE    >> 1: *ops++ = Op_store_161616BE;   break;
         case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_store_16161616BE; break;
+        case skcms_PixelFormat_RGB_hhh_Norm    >> 1: *ops++ = Op_store_hhh;        break;
+        case skcms_PixelFormat_RGBA_hhhh_Norm  >> 1: *ops++ = Op_store_hhhh;       break;
         case skcms_PixelFormat_RGB_hhh         >> 1: *ops++ = Op_store_hhh;        break;
         case skcms_PixelFormat_RGBA_hhhh       >> 1: *ops++ = Op_store_hhhh;       break;
         case skcms_PixelFormat_RGB_fff         >> 1: *ops++ = Op_store_fff;        break;
@@ -2258,7 +2320,18 @@
 
     auto run = baseline::run_program;
 #if defined(TEST_FOR_HSW)
-    if (hsw_ok()) { run = hsw::run_program; }
+    switch (cpu_type()) {
+        case CpuType::None:                        break;
+        case CpuType::HSW: run = hsw::run_program; break;
+        case CpuType::SKX: run = hsw::run_program; break;
+    }
+#endif
+#if defined(TEST_FOR_SKX)
+    switch (cpu_type()) {
+        case CpuType::None:                        break;
+        case CpuType::HSW:                         break;
+        case CpuType::SKX: run = skx::run_program; break;
+    }
 #endif
     run(program, arguments, (const char*)src, (char*)dst, n, src_bpp,dst_bpp);
     return true;
diff --git a/third_party/skcms/skcms.h b/third_party/skcms/skcms.h
index d924f34..6ca8236 100644
--- a/third_party/skcms/skcms.h
+++ b/third_party/skcms/skcms.h
@@ -208,6 +208,11 @@
     skcms_PixelFormat_RGBA_16161616 = skcms_PixelFormat_RGBA_16161616BE,
     skcms_PixelFormat_BGRA_16161616 = skcms_PixelFormat_BGRA_16161616BE,
 
+    skcms_PixelFormat_RGB_hhh_Norm,   // 1-5-10 half-precision float in [0,1]
+    skcms_PixelFormat_BGR_hhh_Norm,   // Pointers must be 16-bit aligned.
+    skcms_PixelFormat_RGBA_hhhh_Norm,
+    skcms_PixelFormat_BGRA_hhhh_Norm,
+
     skcms_PixelFormat_RGB_hhh,        // 1-5-10 half-precision float.
     skcms_PixelFormat_BGR_hhh,        // Pointers must be 16-bit aligned.
     skcms_PixelFormat_RGBA_hhhh,
diff --git a/third_party/skcms/src/Transform_inl.h b/third_party/skcms/src/Transform_inl.h
index 69efc98..86854fc 100644
--- a/third_party/skcms/src/Transform_inl.h
+++ b/third_party/skcms/src/Transform_inl.h
@@ -43,6 +43,9 @@
 #if !defined(USING_AVX2)     && defined(USING_AVX) && defined(__AVX2__)
     #define  USING_AVX2
 #endif
+#if !defined(USING_AVX512F)  && N == 16 && defined(__AVX512F__)
+    #define  USING_AVX512F
+#endif
 
 // Similar to the AVX+ features, we define USING_NEON and USING_NEON_F16C.
 // This is more for organizational clarity... skcms.cc doesn't force these.
@@ -138,7 +141,7 @@
 SI F F_from_Half(U16 half) {
 #if defined(USING_NEON_F16C)
     return vcvt_f32_f16((float16x4_t)half);
-#elif defined(__AVX512F__)
+#elif defined(USING_AVX512F)
     return (F)_mm512_cvtph_ps((__m256i)half);
 #elif defined(USING_AVX_F16C)
     typedef int16_t __attribute__((vector_size(16))) I16;
@@ -165,7 +168,7 @@
 SI U16 Half_from_F(F f) {
 #if defined(USING_NEON_F16C)
     return (U16)vcvt_f16_f32(f);
-#elif defined(__AVX512F__)
+#elif defined(USING_AVX512F)
     return (U16)_mm512_cvtps_ph((__m512 )f, _MM_FROUND_CUR_DIRECTION );
 #elif defined(USING_AVX_F16C)
     return (U16)__builtin_ia32_vcvtps2ph256(f, 0x04/*_MM_FROUND_CUR_DIRECTION*/);
@@ -206,8 +209,12 @@
     return floorf_(x);
 #elif defined(__aarch64__)
     return vrndmq_f32(x);
-#elif defined(__AVX512F__)
-    return _mm512_floor_ps(x);
+#elif defined(USING_AVX512F)
+    // Clang's _mm512_floor_ps() passes its mask as -1, not (__mmask16)-1,
+    // and integer santizer catches that this implicit cast changes the
+    // value from -1 to 65535.  We'll cast manually to work around it.
+    // Read this as `return _mm512_floor_ps(x)`.
+    return _mm512_mask_floor_ps(x, (__mmask16)-1, x);
 #elif defined(USING_AVX)
     return __builtin_ia32_roundps256(x, 0x01/*_MM_FROUND_FLOOR*/);
 #elif defined(__SSE4_1__)
@@ -1238,6 +1245,9 @@
 #if defined(USING_AVX2)
     #undef  USING_AVX2
 #endif
+#if defined(USING_AVX512F)
+    #undef  USING_AVX512F
+#endif
 
 #if defined(USING_NEON)
     #undef  USING_NEON
diff --git a/third_party/skcms/version.sha1 b/third_party/skcms/version.sha1
index 9bce9d7..ad21f66 100755
--- a/third_party/skcms/version.sha1
+++ b/third_party/skcms/version.sha1
@@ -1 +1 @@
-0c0f6dee27794e35e513122f703b848709ec8bbd
\ No newline at end of file
+0da672fc2c69d3d7fd4c524c2d873ca725586d97
\ No newline at end of file
diff --git a/third_party/third_party.gni b/third_party/third_party.gni
index 3c1059a..e35c3ec 100644
--- a/third_party/third_party.gni
+++ b/third_party/third_party.gni
@@ -11,8 +11,15 @@
       if (defined(invoker.public_defines)) {
         defines = invoker.public_defines
       }
-      if (is_win) {
+      if (is_win && !is_clang) {
         include_dirs = invoker.public_include_dirs
+      } else if (is_win && is_clang) {
+        foreach(dir, invoker.public_include_dirs) {
+          cflags += [
+            "/imsvc",
+            rebase_path(dir),
+          ]
+        }
       } else {
         foreach(dir, invoker.public_include_dirs) {
           cflags += [
diff --git a/tools/DDLPromiseImageHelper.cpp b/tools/DDLPromiseImageHelper.cpp
index 6d9b318..5e56952 100644
--- a/tools/DDLPromiseImageHelper.cpp
+++ b/tools/DDLPromiseImageHelper.cpp
@@ -13,6 +13,7 @@
 #include "SkCachedData.h"
 #include "SkDeferredDisplayListRecorder.h"
 #include "SkImage_Base.h"
+#include "SkImage_GpuYUVA.h"
 #include "SkYUVAIndex.h"
 #include "SkYUVASizeInfo.h"
 
@@ -30,18 +31,12 @@
 
 void DDLPromiseImageHelper::PromiseImageCallbackContext::setBackendTexture(
         const GrBackendTexture& backendTexture) {
-    SkASSERT(!fUnreleasedFulfills);
-    if (fPromiseImageTexture) {
-        GrGpu* gpu = fContext->priv().getGpu();
-        gpu->deleteTestingOnlyBackendTexture(fPromiseImageTexture->backendTexture());
-    }
+    SkASSERT(!fPromiseImageTexture);
     fPromiseImageTexture = SkPromiseImageTexture::Make(backendTexture);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-DDLPromiseImageHelper::~DDLPromiseImageHelper() {}
-
 sk_sp<SkData> DDLPromiseImageHelper::deflateSKP(const SkPicture* inputPicture) {
     SkSerialProcs procs;
 
@@ -150,34 +145,6 @@
     }
 }
 
-void DDLPromiseImageHelper::replaceEveryOtherPromiseTexture(GrContext* context) {
-    GrGpu* gpu = context->priv().getGpu();
-    SkASSERT(gpu);
-
-    for (int i = 0; i < fImageInfo.count(); i += 2) {
-        PromiseImageInfo& info = fImageInfo[i];
-
-        // DDL TODO: how can we tell if we need mipmapping!
-        if (info.isYUV()) {
-            int numPixmaps;
-            SkAssertResult(SkYUVAIndex::AreValidIndices(info.yuvaIndices(), &numPixmaps));
-            for (int j = 0; j < numPixmaps; ++j) {
-                const SkPixmap& yuvPixmap = info.yuvPixmap(j);
-                info.callbackContext(j)->setBackendTexture(
-                        create_yuva_texture(gpu, yuvPixmap, info.yuvaIndices(), j));
-                SkASSERT(info.callbackContext(j)->promiseImageTexture());
-            }
-        } else {
-            const SkBitmap& bm = info.normalBitmap();
-            info.callbackContext(0)->setBackendTexture(gpu->createTestingOnlyBackendTexture(
-                    bm.getPixels(), bm.width(), bm.height(), bm.colorType(), false,
-                    GrMipMapped::kNo, bm.rowBytes()));
-            // The GMs sometimes request too large an image
-            // SkAssertResult(callbackContext->backendTexture().isValid());
-        }
-    }
-}
-
 sk_sp<SkPicture> DDLPromiseImageHelper::reinflateSKP(
                                                    SkDeferredDisplayListRecorder* recorder,
                                                    SkData* compressedPictureData,
@@ -235,22 +202,33 @@
             sizes[i] = SkISize::MakeEmpty();
         }
 
-        image = recorder->makeYUVAPromiseTexture(curImage.yuvColorSpace(),
-                                                 backendFormats,
-                                                 sizes,
-                                                 curImage.yuvaIndices(),
-                                                 curImage.overallWidth(),
-                                                 curImage.overallHeight(),
-                                                 GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
-                                                 curImage.refOverallColorSpace(),
-                                                 DDLPromiseImageHelper::PromiseImageFulfillProc,
-                                                 DDLPromiseImageHelper::PromiseImageReleaseProc,
-                                                 DDLPromiseImageHelper::PromiseImageDoneProc,
-                                                 contexts,
-                                                 helper->fDelayReleaseCallback);
+        image = recorder->makeYUVAPromiseTexture(
+                curImage.yuvColorSpace(),
+                backendFormats,
+                sizes,
+                curImage.yuvaIndices(),
+                curImage.overallWidth(),
+                curImage.overallHeight(),
+                GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
+                curImage.refOverallColorSpace(),
+                DDLPromiseImageHelper::PromiseImageFulfillProc,
+                DDLPromiseImageHelper::PromiseImageReleaseProc,
+                DDLPromiseImageHelper::PromiseImageDoneProc,
+                contexts,
+                SkDeferredDisplayListRecorder::PromiseImageApiVersion::kNew);
         for (int i = 0; i < textureCount; ++i) {
             curImage.callbackContext(i)->wasAddedToImage();
         }
+
+#ifdef SK_DEBUG
+        {
+            // By the peekProxy contract this image should not have a single backing proxy so
+            // should return null. The call should also not trigger the conversion to RGBA.
+            SkImage_GpuYUVA* yuva = reinterpret_cast<SkImage_GpuYUVA*>(image.get());
+            SkASSERT(!yuva->peekProxy());
+            SkASSERT(!yuva->peekProxy());  // the first call didn't force a conversion to RGBA
+        }
+#endif
     } else {
         const GrBackendTexture& backendTex = curImage.promiseTexture(0)->backendTexture();
         GrBackendFormat backendFormat = backendTex.getBackendFormat();
@@ -259,19 +237,20 @@
         // Each DDL recorder gets its own ref on the promise callback context for the
         // promise images it creates.
         // DDL TODO: sort out mipmapping
-        image = recorder->makePromiseTexture(backendFormat,
-                                             curImage.overallWidth(),
-                                             curImage.overallHeight(),
-                                             GrMipMapped::kNo,
-                                             GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
-                                             curImage.overallColorType(),
-                                             curImage.overallAlphaType(),
-                                             curImage.refOverallColorSpace(),
-                                             DDLPromiseImageHelper::PromiseImageFulfillProc,
-                                             DDLPromiseImageHelper::PromiseImageReleaseProc,
-                                             DDLPromiseImageHelper::PromiseImageDoneProc,
-                                             (void*)curImage.refCallbackContext(0).release(),
-                                             helper->fDelayReleaseCallback);
+        image = recorder->makePromiseTexture(
+                backendFormat,
+                curImage.overallWidth(),
+                curImage.overallHeight(),
+                GrMipMapped::kNo,
+                GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
+                curImage.overallColorType(),
+                curImage.overallAlphaType(),
+                curImage.refOverallColorSpace(),
+                DDLPromiseImageHelper::PromiseImageFulfillProc,
+                DDLPromiseImageHelper::PromiseImageReleaseProc,
+                DDLPromiseImageHelper::PromiseImageDoneProc,
+                (void*)curImage.refCallbackContext(0).release(),
+                SkDeferredDisplayListRecorder::PromiseImageApiVersion::kNew);
         curImage.callbackContext(0)->wasAddedToImage();
     }
     perRecorderContext->fPromiseImages->push_back(image);
diff --git a/tools/DDLPromiseImageHelper.h b/tools/DDLPromiseImageHelper.h
index 1a0b043..bbb7a75 100644
--- a/tools/DDLPromiseImageHelper.h
+++ b/tools/DDLPromiseImageHelper.h
@@ -47,20 +47,14 @@
 // all the replaying is complete. This will pin the GrBackendTextures in VRAM.
 class DDLPromiseImageHelper {
 public:
-    using DelayReleaseCallback = SkDeferredDisplayListRecorder::DelayReleaseCallback;
-    DDLPromiseImageHelper(DelayReleaseCallback delayReleaseCallback = DelayReleaseCallback::kNo)
-            : fDelayReleaseCallback(delayReleaseCallback) {}
-    ~DDLPromiseImageHelper();
+    DDLPromiseImageHelper() = default;
+    ~DDLPromiseImageHelper() = default;
 
     // Convert the SkPicture into SkData replacing all the SkImages with an index.
     sk_sp<SkData> deflateSKP(const SkPicture* inputPicture);
 
     void uploadAllToGPU(GrContext* context);
 
-    // Change the backing store texture for half the images. (Must ensure all fulfilled images are
-    // released before calling this.).
-    void replaceEveryOtherPromiseTexture(GrContext*);
-
     // reinflate a deflated SKP, replacing all the indices with promise images.
     sk_sp<SkPicture> reinflateSKP(SkDeferredDisplayListRecorder*,
                                   SkData* compressedPicture,
@@ -253,7 +247,6 @@
     // returns -1 on failure
     int findOrDefineImage(SkImage* image);
 
-    DelayReleaseCallback fDelayReleaseCallback;
     SkTArray<PromiseImageInfo> fImageInfo;
 };
 
diff --git a/tools/HashAndEncode.cpp b/tools/HashAndEncode.cpp
new file mode 100644
index 0000000..0bb44d5
--- /dev/null
+++ b/tools/HashAndEncode.cpp
@@ -0,0 +1,159 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+
+#include "HashAndEncode.h"
+#include "SkICC.h"
+#include "SkString.h"
+#include "png.h"
+
+static constexpr skcms_TransferFunction k2020_TF =
+    {2.22222f, 0.909672f, 0.0903276f, 0.222222f, 0.0812429f, 0, 0};
+
+static sk_sp<SkColorSpace> rec2020() {
+    return SkColorSpace::MakeRGB(k2020_TF, SkNamedGamut::kRec2020);
+}
+
+HashAndEncode::HashAndEncode(const SkBitmap& bitmap) : fSize(bitmap.info().dimensions()) {
+    skcms_AlphaFormat srcAlpha;
+    switch (bitmap.alphaType()) {
+        case kUnknown_SkAlphaType: return;
+
+        case kOpaque_SkAlphaType:
+        case kUnpremul_SkAlphaType: srcAlpha = skcms_AlphaFormat_Unpremul;        break;
+        case kPremul_SkAlphaType:   srcAlpha = skcms_AlphaFormat_PremulAsEncoded; break;
+    }
+
+    skcms_PixelFormat srcFmt;
+    switch (bitmap.colorType()) {
+        case kUnknown_SkColorType: return;
+
+        case kAlpha_8_SkColorType:      srcFmt = skcms_PixelFormat_A_8;          break;
+        case kRGB_565_SkColorType:      srcFmt = skcms_PixelFormat_BGR_565;      break;
+        case kARGB_4444_SkColorType:    srcFmt = skcms_PixelFormat_ABGR_4444;    break;
+        case kRGBA_8888_SkColorType:    srcFmt = skcms_PixelFormat_RGBA_8888;    break;
+        case kBGRA_8888_SkColorType:    srcFmt = skcms_PixelFormat_BGRA_8888;    break;
+        case kRGBA_1010102_SkColorType: srcFmt = skcms_PixelFormat_RGBA_1010102; break;
+        case kGray_8_SkColorType:       srcFmt = skcms_PixelFormat_G_8;          break;
+        case kRGBA_F16Norm_SkColorType: srcFmt = skcms_PixelFormat_RGBA_hhhh;    break;
+        case kRGBA_F16_SkColorType:     srcFmt = skcms_PixelFormat_RGBA_hhhh;    break;
+        case kRGBA_F32_SkColorType:     srcFmt = skcms_PixelFormat_RGBA_ffff;    break;
+
+        case kRGB_888x_SkColorType:     srcFmt = skcms_PixelFormat_RGBA_8888;
+                                        srcAlpha = skcms_AlphaFormat_Opaque;       break;
+        case kRGB_101010x_SkColorType:  srcFmt = skcms_PixelFormat_RGBA_1010102;
+                                        srcAlpha = skcms_AlphaFormat_Opaque;       break;
+    }
+
+    skcms_ICCProfile srcProfile = *skcms_sRGB_profile();
+    if (auto cs = bitmap.colorSpace()) {
+        cs->toProfile(&srcProfile);
+    }
+
+    // Our common format that can represent anything we draw and encode as a PNG:
+    //   - 16-bit big-endian RGBA
+    //   - unpremul
+    //   - Rec. 2020 gamut and transfer function
+    skcms_PixelFormat dstFmt   = skcms_PixelFormat_RGBA_16161616BE;
+    skcms_AlphaFormat dstAlpha = skcms_AlphaFormat_Unpremul;
+    skcms_ICCProfile dstProfile;
+    rec2020()->toProfile(&dstProfile);
+
+    int N = fSize.width() * fSize.height();
+    fPixels.reset(new uint64_t[N]);
+
+    if (!skcms_Transform(bitmap.getPixels(), srcFmt, srcAlpha, &srcProfile,
+                         fPixels.get(),      dstFmt, dstAlpha, &dstProfile, N)) {
+        SkASSERT(false);
+        fPixels.reset(nullptr);
+    }
+}
+
+void HashAndEncode::write(SkWStream* st) const {
+    st->write(&fSize, sizeof(fSize));
+    if (const uint64_t* px = fPixels.get()) {
+        st->write(px, sizeof(*px) * fSize.width() * fSize.height());
+    }
+
+    // N.B. changing salt will change the hash of all images produced by DM,
+    // and will cause tens of thousands of new images to be uploaded to Gold.
+    int salt = 1;
+    st->write(&salt, sizeof(salt));
+}
+
+bool HashAndEncode::writePngTo(const char* path,
+                               const char* md5,
+                               CommandLineFlags::StringArray key,
+                               CommandLineFlags::StringArray properties) const {
+    if (!fPixels) {
+        return false;
+    }
+
+    FILE* f = fopen(path, "wb");
+    if (!f) {
+        return false;
+    }
+
+    png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+    if (!png) {
+        fclose(f);
+        return false;
+    }
+
+    png_infop info = png_create_info_struct(png);
+    if (!info) {
+        png_destroy_write_struct(&png, &info);
+        fclose(f);
+        return false;
+    }
+
+    SkString description;
+    description.append("Key: ");
+    for (int i = 0; i < key.count(); i++) {
+        description.appendf("%s ", key[i]);
+    }
+    description.append("Properties: ");
+    for (int i = 0; i < properties.count(); i++) {
+        description.appendf("%s ", properties[i]);
+    }
+    description.appendf("MD5: %s", md5);
+
+    png_text text[2];
+    text[0].key  = (png_charp)"Author";
+    text[0].text = (png_charp)"DM unified Rec.2020";
+    text[0].compression = PNG_TEXT_COMPRESSION_NONE;
+    text[1].key  = (png_charp)"Description";
+    text[1].text = (png_charp)description.c_str();
+    text[1].compression = PNG_TEXT_COMPRESSION_NONE;
+    png_set_text(png, info, text, SK_ARRAY_COUNT(text));
+
+    png_init_io(png, f);
+    png_set_IHDR(png, info, (png_uint_32)fSize.width()
+                          , (png_uint_32)fSize.height()
+                          , 16/*bits per channel*/
+                          , PNG_COLOR_TYPE_RGB_ALPHA
+                          , PNG_INTERLACE_NONE
+                          , PNG_COMPRESSION_TYPE_DEFAULT
+                          , PNG_FILTER_TYPE_DEFAULT);
+
+    // Fastest encoding and decoding, at slight file size cost is no filtering, compression 1.
+    png_set_filter(png, PNG_FILTER_TYPE_BASE, PNG_FILTER_NONE);
+    png_set_compression_level(png, 1);
+
+    static const sk_sp<SkData> profile = SkWriteICCProfile(k2020_TF, SkNamedGamut::kRec2020);
+    png_set_iCCP(png, info,
+                 "Rec.2020",
+                 0/*compression type... no idea what options are available here*/,
+                 (png_const_bytep)profile->data(),
+                 (png_uint_32)    profile->size());
+
+    png_write_info(png, info);
+    for (int y = 0; y < fSize.height(); y++) {
+        png_write_row(png, (png_bytep)(fPixels.get() + y*fSize.width()));
+    }
+    png_write_end(png, info);
+
+    png_destroy_write_struct(&png, &info);
+    fclose(f);
+    return true;
+}
+
diff --git a/tools/HashAndEncode.h b/tools/HashAndEncode.h
new file mode 100644
index 0000000..e3808fc
--- /dev/null
+++ b/tools/HashAndEncode.h
@@ -0,0 +1,25 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+
+#pragma once
+
+#include "CommandLineFlags.h"
+#include "SkBitmap.h"
+#include "SkStream.h"
+
+class HashAndEncode {
+public:
+    explicit HashAndEncode(const SkBitmap&);
+
+    void write(SkWStream*) const;
+
+    bool writePngTo(const char* path,
+                    const char* md5,
+                    CommandLineFlags::StringArray key,
+                    CommandLineFlags::StringArray properties) const;
+
+private:
+    const SkISize               fSize;
+    std::unique_ptr<uint64_t[]> fPixels;
+};
+
diff --git a/tools/LsanSuppressions.cpp b/tools/LsanSuppressions.cpp
index 4839942..6dcb214 100644
--- a/tools/LsanSuppressions.cpp
+++ b/tools/LsanSuppressions.cpp
@@ -21,7 +21,6 @@
                "leak:libGLX_nvidia.so\n"    // For NVidia driver.
                "leak:libnvidia-glcore.so\n" // For NVidia driver.
                "leak:libnvidia-tls.so\n"    // For NVidia driver.
-               "leak:libxcb.so\n"           // For Mesa Intel driver. skia:8710
                ;
     }
 
diff --git a/tools/Resources.cpp b/tools/Resources.cpp
index 182f87d..8d10b59 100644
--- a/tools/Resources.cpp
+++ b/tools/Resources.cpp
@@ -5,10 +5,10 @@
  * found in the LICENSE file.
  */
 
-#include "ResourceFactory.h"
 #include "Resources.h"
+#include "CommandLineFlags.h"
+#include "ResourceFactory.h"
 #include "SkBitmap.h"
-#include "SkCommandLineFlags.h"
 #include "SkData.h"
 #include "SkImage.h"
 #include "SkImageGenerator.h"
@@ -17,7 +17,8 @@
 #include "SkStream.h"
 #include "SkTypeface.h"
 
-DEFINE_string2(resourcePath, i, "resources", "Directory with test resources: images, fonts, etc.");
+static DEFINE_string2(resourcePath, i, "resources",
+                      "Directory with test resources: images, fonts, etc.");
 
 sk_sp<SkData> (*gResourceFactory)(const char*) = nullptr;
 
diff --git a/tools/ToolUtils.cpp b/tools/ToolUtils.cpp
new file mode 100644
index 0000000..d937aa9
--- /dev/null
+++ b/tools/ToolUtils.cpp
@@ -0,0 +1,462 @@
+/*
+ * 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 "ToolUtils.h"
+#include "CommandLineFlags.h"
+#include "SkBitmap.h"
+#include "SkBlendMode.h"
+#include "SkCanvas.h"
+#include "SkColorData.h"
+#include "SkColorPriv.h"
+#include "SkFloatingPoint.h"
+#include "SkFontMgrPriv.h"
+#include "SkFontPriv.h"
+#include "SkImage.h"
+#include "SkMatrix.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkPixelRef.h"
+#include "SkPixmap.h"
+#include "SkPoint3.h"
+#include "SkRRect.h"
+#include "SkShader.h"
+#include "SkSurface.h"
+#include "SkTextBlob.h"
+#include "SkTypeface_win.h"
+#include "TestFontMgr.h"
+
+#include <cmath>
+#include <cstring>
+#include <memory>
+
+namespace ToolUtils {
+
+const char* alphatype_name(SkAlphaType at) {
+    switch (at) {
+        case kUnknown_SkAlphaType:  return "Unknown";
+        case kOpaque_SkAlphaType:   return "Opaque";
+        case kPremul_SkAlphaType:   return "Premul";
+        case kUnpremul_SkAlphaType: return "Unpremul";
+    }
+    SkASSERT(false);
+    return "unexpected alphatype";
+}
+
+const char* colortype_name(SkColorType ct) {
+    switch (ct) {
+        case kUnknown_SkColorType:      return "Unknown";
+        case kAlpha_8_SkColorType:      return "Alpha_8";
+        case kRGB_565_SkColorType:      return "RGB_565";
+        case kARGB_4444_SkColorType:    return "ARGB_4444";
+        case kRGBA_8888_SkColorType:    return "RGBA_8888";
+        case kRGB_888x_SkColorType:     return "RGB_888x";
+        case kBGRA_8888_SkColorType:    return "BGRA_8888";
+        case kRGBA_1010102_SkColorType: return "RGBA_1010102";
+        case kRGB_101010x_SkColorType:  return "RGB_101010x";
+        case kGray_8_SkColorType:       return "Gray_8";
+        case kRGBA_F16Norm_SkColorType: return "RGBA_F16Norm";
+        case kRGBA_F16_SkColorType:     return "RGBA_F16";
+        case kRGBA_F32_SkColorType:     return "RGBA_F32";
+    }
+    SkASSERT(false);
+    return "unexpected colortype";
+}
+
+const char* colortype_depth(SkColorType ct) {
+    switch (ct) {
+        case kUnknown_SkColorType:      return "Unknown";
+        case kAlpha_8_SkColorType:      return "A8";
+        case kRGB_565_SkColorType:      return "565";
+        case kARGB_4444_SkColorType:    return "4444";
+        case kRGBA_8888_SkColorType:    return "8888";
+        case kRGB_888x_SkColorType:     return "888";
+        case kBGRA_8888_SkColorType:    return "8888";
+        case kRGBA_1010102_SkColorType: return "1010102";
+        case kRGB_101010x_SkColorType:  return "101010";
+        case kGray_8_SkColorType:       return "G8";
+        case kRGBA_F16Norm_SkColorType: return "F16Norm";  // TODO: "F16"?
+        case kRGBA_F16_SkColorType:     return "F16";
+        case kRGBA_F32_SkColorType:     return "F32";
+    }
+    SkASSERT(false);
+    return "unexpected colortype";
+}
+
+
+SkColor color_to_565(SkColor color) {
+    // Not a good idea to use this function for greyscale colors...
+    // it will add an obvious purple or green tint.
+    SkASSERT(SkColorGetR(color) != SkColorGetG(color) || SkColorGetR(color) != SkColorGetB(color) ||
+             SkColorGetG(color) != SkColorGetB(color));
+
+    SkPMColor pmColor = SkPreMultiplyColor(color);
+    U16CPU    color16 = SkPixel32ToPixel16(pmColor);
+    return SkPixel16ToColor(color16);
+}
+
+sk_sp<SkShader> create_checkerboard_shader(SkColor c1, SkColor c2, int size) {
+    SkBitmap bm;
+    bm.allocPixels(SkImageInfo::MakeS32(2 * size, 2 * size, kPremul_SkAlphaType));
+    bm.eraseColor(c1);
+    bm.eraseArea(SkIRect::MakeLTRB(0, 0, size, size), c2);
+    bm.eraseArea(SkIRect::MakeLTRB(size, size, 2 * size, 2 * size), c2);
+    return bm.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat);
+}
+
+SkBitmap create_checkerboard_bitmap(int w, int h, SkColor c1, SkColor c2, int checkSize) {
+    SkBitmap bitmap;
+    bitmap.allocPixels(SkImageInfo::MakeS32(w, h, kPremul_SkAlphaType));
+    SkCanvas canvas(bitmap);
+
+    ToolUtils::draw_checkerboard(&canvas, c1, c2, checkSize);
+    return bitmap;
+}
+
+void draw_checkerboard(SkCanvas* canvas, SkColor c1, SkColor c2, int size) {
+    SkPaint paint;
+    paint.setShader(create_checkerboard_shader(c1, c2, size));
+    paint.setBlendMode(SkBlendMode::kSrc);
+    canvas->drawPaint(paint);
+}
+
+SkBitmap
+create_string_bitmap(int w, int h, SkColor c, int x, int y, int textSize, const char* str) {
+    SkBitmap bitmap;
+    bitmap.allocN32Pixels(w, h);
+    SkCanvas canvas(bitmap);
+
+    SkPaint paint;
+    paint.setColor(c);
+
+    SkFont font(ToolUtils::create_portable_typeface(), textSize);
+
+    canvas.clear(0x00000000);
+    canvas.drawSimpleText(str,
+                          strlen(str),
+                          kUTF8_SkTextEncoding,
+                          SkIntToScalar(x),
+                          SkIntToScalar(y),
+                          font,
+                          paint);
+
+    // Tag data as sRGB (without doing any color space conversion). Color-space aware configs
+    // will process this correctly but legacy configs will render as if this returned N32.
+    SkBitmap result;
+    result.setInfo(SkImageInfo::MakeS32(w, h, kPremul_SkAlphaType));
+    result.setPixelRef(sk_ref_sp(bitmap.pixelRef()), 0, 0);
+    return result;
+}
+
+void add_to_text_blob_w_len(SkTextBlobBuilder* builder,
+                            const char*        text,
+                            size_t             len,
+                            SkTextEncoding     encoding,
+                            const SkFont&      font,
+                            SkScalar           x,
+                            SkScalar           y) {
+    int  count = font.countText(text, len, encoding);
+    auto run   = builder->allocRun(font, count, x, y);
+    font.textToGlyphs(text, len, encoding, run.glyphs, count);
+}
+
+void add_to_text_blob(SkTextBlobBuilder* builder,
+                      const char*        text,
+                      const SkFont&      font,
+                      SkScalar           x,
+                      SkScalar           y) {
+    add_to_text_blob_w_len(builder, text, strlen(text), kUTF8_SkTextEncoding, font, x, y);
+}
+
+void get_text_path(const SkFont&  font,
+                   const void*    text,
+                   size_t         length,
+                   SkTextEncoding encoding,
+                   SkPath*        dst,
+                   const SkPoint  pos[]) {
+    SkAutoToGlyphs        atg(font, text, length, encoding);
+    const int             count = atg.count();
+    SkAutoTArray<SkPoint> computedPos;
+    if (pos == nullptr) {
+        computedPos.reset(count);
+        font.getPos(atg.glyphs(), count, &computedPos[0]);
+        pos = computedPos.get();
+    }
+
+    struct Rec {
+        SkPath*        fDst;
+        const SkPoint* fPos;
+    } rec = {dst, pos};
+    font.getPaths(atg.glyphs(),
+                  atg.count(),
+                  [](const SkPath* src, const SkMatrix& mx, void* ctx) {
+                      Rec* rec = (Rec*)ctx;
+                      if (src) {
+                          SkMatrix tmp(mx);
+                          tmp.postTranslate(rec->fPos->fX, rec->fPos->fY);
+                          rec->fDst->addPath(*src, tmp);
+                      }
+                      rec->fPos += 1;
+                  },
+                  &rec);
+}
+
+SkPath make_star(const SkRect& bounds, int numPts, int step) {
+    SkASSERT(numPts != step);
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(0, -1);
+    for (int i = 1; i < numPts; ++i) {
+        int      idx   = i * step % numPts;
+        SkScalar theta = idx * 2 * SK_ScalarPI / numPts + SK_ScalarPI / 2;
+        SkScalar x     = SkScalarCos(theta);
+        SkScalar y     = -SkScalarSin(theta);
+        path.lineTo(x, y);
+    }
+    path.transform(SkMatrix::MakeRectToRect(path.getBounds(), bounds, SkMatrix::kFill_ScaleToFit));
+    return path;
+}
+
+static inline void norm_to_rgb(SkBitmap* bm, int x, int y, const SkVector3& norm) {
+    SkASSERT(SkScalarNearlyEqual(norm.length(), 1.0f));
+    unsigned char r      = static_cast<unsigned char>((0.5f * norm.fX + 0.5f) * 255);
+    unsigned char g      = static_cast<unsigned char>((-0.5f * norm.fY + 0.5f) * 255);
+    unsigned char b      = static_cast<unsigned char>((0.5f * norm.fZ + 0.5f) * 255);
+    *bm->getAddr32(x, y) = SkPackARGB32(0xFF, r, g, b);
+}
+
+void create_hemi_normal_map(SkBitmap* bm, const SkIRect& dst) {
+    const SkPoint center =
+            SkPoint::Make(dst.fLeft + (dst.width() / 2.0f), dst.fTop + (dst.height() / 2.0f));
+    const SkPoint halfSize = SkPoint::Make(dst.width() / 2.0f, dst.height() / 2.0f);
+
+    SkVector3 norm;
+
+    for (int y = dst.fTop; y < dst.fBottom; ++y) {
+        for (int x = dst.fLeft; x < dst.fRight; ++x) {
+            norm.fX = (x + 0.5f - center.fX) / halfSize.fX;
+            norm.fY = (y + 0.5f - center.fY) / halfSize.fY;
+
+            SkScalar tmp = norm.fX * norm.fX + norm.fY * norm.fY;
+            if (tmp >= 1.0f) {
+                norm.set(0.0f, 0.0f, 1.0f);
+            } else {
+                norm.fZ = sqrtf(1.0f - tmp);
+            }
+
+            norm_to_rgb(bm, x, y, norm);
+        }
+    }
+}
+
+void create_frustum_normal_map(SkBitmap* bm, const SkIRect& dst) {
+    const SkPoint center =
+            SkPoint::Make(dst.fLeft + (dst.width() / 2.0f), dst.fTop + (dst.height() / 2.0f));
+
+    SkIRect inner = dst;
+    inner.inset(dst.width() / 4, dst.height() / 4);
+
+    SkPoint3       norm;
+    const SkPoint3 left  = SkPoint3::Make(-SK_ScalarRoot2Over2, 0.0f, SK_ScalarRoot2Over2);
+    const SkPoint3 up    = SkPoint3::Make(0.0f, -SK_ScalarRoot2Over2, SK_ScalarRoot2Over2);
+    const SkPoint3 right = SkPoint3::Make(SK_ScalarRoot2Over2, 0.0f, SK_ScalarRoot2Over2);
+    const SkPoint3 down  = SkPoint3::Make(0.0f, SK_ScalarRoot2Over2, SK_ScalarRoot2Over2);
+
+    for (int y = dst.fTop; y < dst.fBottom; ++y) {
+        for (int x = dst.fLeft; x < dst.fRight; ++x) {
+            if (inner.contains(x, y)) {
+                norm.set(0.0f, 0.0f, 1.0f);
+            } else {
+                SkScalar locX = x + 0.5f - center.fX;
+                SkScalar locY = y + 0.5f - center.fY;
+
+                if (locX >= 0.0f) {
+                    if (locY > 0.0f) {
+                        norm = locX >= locY ? right : down;  // LR corner
+                    } else {
+                        norm = locX > -locY ? right : up;  // UR corner
+                    }
+                } else {
+                    if (locY > 0.0f) {
+                        norm = -locX > locY ? left : down;  // LL corner
+                    } else {
+                        norm = locX > locY ? up : left;  // UL corner
+                    }
+                }
+            }
+
+            norm_to_rgb(bm, x, y, norm);
+        }
+    }
+}
+
+void create_tetra_normal_map(SkBitmap* bm, const SkIRect& dst) {
+    const SkPoint center =
+            SkPoint::Make(dst.fLeft + (dst.width() / 2.0f), dst.fTop + (dst.height() / 2.0f));
+
+    static const SkScalar k1OverRoot3 = 0.5773502692f;
+
+    SkPoint3       norm;
+    const SkPoint3 leftUp  = SkPoint3::Make(-k1OverRoot3, -k1OverRoot3, k1OverRoot3);
+    const SkPoint3 rightUp = SkPoint3::Make(k1OverRoot3, -k1OverRoot3, k1OverRoot3);
+    const SkPoint3 down    = SkPoint3::Make(0.0f, SK_ScalarRoot2Over2, SK_ScalarRoot2Over2);
+
+    for (int y = dst.fTop; y < dst.fBottom; ++y) {
+        for (int x = dst.fLeft; x < dst.fRight; ++x) {
+            SkScalar locX = x + 0.5f - center.fX;
+            SkScalar locY = y + 0.5f - center.fY;
+
+            if (locX >= 0.0f) {
+                if (locY > 0.0f) {
+                    norm = locX >= locY ? rightUp : down;  // LR corner
+                } else {
+                    norm = rightUp;
+                }
+            } else {
+                if (locY > 0.0f) {
+                    norm = -locX > locY ? leftUp : down;  // LL corner
+                } else {
+                    norm = leftUp;
+                }
+            }
+
+            norm_to_rgb(bm, x, y, norm);
+        }
+    }
+}
+
+#if !defined(__clang__) && defined(_MSC_VER)
+// MSVC takes ~2 minutes to compile this function with optimization.
+// We don't really care to wait that long for this function.
+#pragma optimize("", off)
+#endif
+void make_big_path(SkPath& path) {
+#include "BigPathBench.inc"  // IWYU pragma: keep
+}
+
+bool copy_to(SkBitmap* dst, SkColorType dstColorType, const SkBitmap& src) {
+    SkPixmap srcPM;
+    if (!src.peekPixels(&srcPM)) {
+        return false;
+    }
+
+    SkBitmap    tmpDst;
+    SkImageInfo dstInfo = srcPM.info().makeColorType(dstColorType);
+    if (!tmpDst.setInfo(dstInfo)) {
+        return false;
+    }
+
+    if (!tmpDst.tryAllocPixels()) {
+        return false;
+    }
+
+    SkPixmap dstPM;
+    if (!tmpDst.peekPixels(&dstPM)) {
+        return false;
+    }
+
+    if (!srcPM.readPixels(dstPM)) {
+        return false;
+    }
+
+    dst->swap(tmpDst);
+    return true;
+}
+
+void copy_to_g8(SkBitmap* dst, const SkBitmap& src) {
+    SkASSERT(kBGRA_8888_SkColorType == src.colorType() ||
+             kRGBA_8888_SkColorType == src.colorType());
+
+    SkImageInfo grayInfo = src.info().makeColorType(kGray_8_SkColorType);
+    dst->allocPixels(grayInfo);
+    uint8_t*        dst8  = (uint8_t*)dst->getPixels();
+    const uint32_t* src32 = (const uint32_t*)src.getPixels();
+
+    const int  w      = src.width();
+    const int  h      = src.height();
+    const bool isBGRA = (kBGRA_8888_SkColorType == src.colorType());
+    for (int y = 0; y < h; ++y) {
+        if (isBGRA) {
+            // BGRA
+            for (int x = 0; x < w; ++x) {
+                uint32_t s = src32[x];
+                dst8[x]    = SkComputeLuminance((s >> 16) & 0xFF, (s >> 8) & 0xFF, s & 0xFF);
+            }
+        } else {
+            // RGBA
+            for (int x = 0; x < w; ++x) {
+                uint32_t s = src32[x];
+                dst8[x]    = SkComputeLuminance(s & 0xFF, (s >> 8) & 0xFF, (s >> 16) & 0xFF);
+            }
+        }
+        src32 = (const uint32_t*)((const char*)src32 + src.rowBytes());
+        dst8 += dst->rowBytes();
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+bool equal_pixels(const SkPixmap& a, const SkPixmap& b) {
+    if (a.width() != b.width() || a.height() != b.height() || a.colorType() != b.colorType()) {
+        return false;
+    }
+
+    for (int y = 0; y < a.height(); ++y) {
+        const char* aptr = (const char*)a.addr(0, y);
+        const char* bptr = (const char*)b.addr(0, y);
+        if (memcmp(aptr, bptr, a.width() * a.info().bytesPerPixel())) {
+            return false;
+        }
+        aptr += a.rowBytes();
+        bptr += b.rowBytes();
+    }
+    return true;
+}
+
+bool equal_pixels(const SkBitmap& bm0, const SkBitmap& bm1) {
+    SkPixmap pm0, pm1;
+    return bm0.peekPixels(&pm0) && bm1.peekPixels(&pm1) && equal_pixels(pm0, pm1);
+}
+
+bool equal_pixels(const SkImage* a, const SkImage* b) {
+    // ensure that peekPixels will succeed
+    auto imga = a->makeRasterImage();
+    auto imgb = b->makeRasterImage();
+
+    SkPixmap pm0, pm1;
+    return imga->peekPixels(&pm0) && imgb->peekPixels(&pm1) && equal_pixels(pm0, pm1);
+}
+
+sk_sp<SkSurface> makeSurface(SkCanvas*             canvas,
+                             const SkImageInfo&    info,
+                             const SkSurfaceProps* props) {
+    auto surf = canvas->makeSurface(info, props);
+    if (!surf) {
+        surf = SkSurface::MakeRaster(info, props);
+    }
+    return surf;
+}
+
+static DEFINE_bool(nativeFonts, true,
+                   "If true, use native font manager and rendering. "
+                   "If false, fonts will draw as portably as possible.");
+#if defined(SK_BUILD_FOR_WIN)
+    static DEFINE_bool(gdi, false,
+                       "Use GDI instead of DirectWrite for font rendering.");
+#endif
+
+void SetDefaultFontMgr() {
+    if (!FLAGS_nativeFonts) {
+        gSkFontMgr_DefaultFactory = &ToolUtils::MakePortableFontMgr;
+    }
+#if defined(SK_BUILD_FOR_WIN)
+    if (FLAGS_gdi) {
+        gSkFontMgr_DefaultFactory = &SkFontMgr_New_GDI;
+    }
+#endif
+}
+
+}  // namespace ToolUtils
diff --git a/tools/ToolUtils.h b/tools/ToolUtils.h
new file mode 100644
index 0000000..ed747dc
--- /dev/null
+++ b/tools/ToolUtils.h
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef ToolUtils_DEFINED
+#define ToolUtils_DEFINED
+
+#include "SkColor.h"
+#include "SkData.h"
+#include "SkEncodedImageFormat.h"
+#include "SkFont.h"
+#include "SkFontStyle.h"
+#include "SkFontTypes.h"
+#include "SkImageEncoder.h"
+#include "SkImageInfo.h"
+#include "SkPixmap.h"
+#include "SkRandom.h"
+#include "SkRect.h"
+#include "SkRefCnt.h"
+#include "SkScalar.h"
+#include "SkStream.h"
+#include "SkSurface.h"
+#include "SkTArray.h"
+#include "SkTDArray.h"
+#include "SkTypeface.h"
+#include "SkTypes.h"
+
+class SkBitmap;
+class SkCanvas;
+class SkFontStyle;
+class SkImage;
+class SkPath;
+class SkPixmap;
+class SkRRect;
+class SkShader;
+class SkSurface;
+class SkSurfaceProps;
+class SkTextBlobBuilder;
+class SkTypeface;
+
+namespace ToolUtils {
+
+const char* alphatype_name (SkAlphaType);
+const char* colortype_name (SkColorType);
+const char* colortype_depth(SkColorType);  // like colortype_name, but channel order agnostic
+
+/**
+ * Map opaque colors from 8888 to 565.
+ */
+SkColor color_to_565(SkColor color);
+
+/* Return a color emoji typeface with planets to scale if available. */
+sk_sp<SkTypeface> planet_typeface();
+
+/** Return a color emoji typeface if available. */
+sk_sp<SkTypeface> emoji_typeface();
+
+/** Sample text for the emoji_typeface font. */
+const char* emoji_sample_text();
+
+/**
+ * Returns a platform-independent text renderer.
+ */
+sk_sp<SkTypeface> create_portable_typeface(const char* name, SkFontStyle style);
+
+static inline sk_sp<SkTypeface> create_portable_typeface() {
+    return create_portable_typeface(nullptr, SkFontStyle());
+}
+
+/**
+ *  Turn on portable (--nonativeFonts) or GDI font rendering (--gdi).
+ */
+void SetDefaultFontMgr();
+
+
+void get_text_path(const SkFont&,
+                   const void* text,
+                   size_t      length,
+                   SkTextEncoding,
+                   SkPath*,
+                   const SkPoint* positions = nullptr);
+
+/**
+ *  Returns true iff all of the pixels between the two images are identical.
+ *
+ *  If the configs differ, return false.
+ */
+bool equal_pixels(const SkPixmap&, const SkPixmap&);
+bool equal_pixels(const SkBitmap&, const SkBitmap&);
+bool equal_pixels(const SkImage* a, const SkImage* b);
+
+/** Returns a newly created CheckerboardShader. */
+sk_sp<SkShader> create_checkerboard_shader(SkColor c1, SkColor c2, int size);
+
+/** Draw a checkerboard pattern in the current canvas, restricted to
+    the current clip, using SkXfermode::kSrc_Mode. */
+void draw_checkerboard(SkCanvas* canvas, SkColor color1, SkColor color2, int checkSize);
+
+/** Make it easier to create a bitmap-based checkerboard */
+SkBitmap create_checkerboard_bitmap(int w, int h, SkColor c1, SkColor c2, int checkSize);
+
+/** A default checkerboard. */
+inline void draw_checkerboard(SkCanvas* canvas) {
+    ToolUtils::draw_checkerboard(canvas, 0xFF999999, 0xFF666666, 8);
+}
+
+SkBitmap create_string_bitmap(int w, int h, SkColor c, int x, int y, int textSize, const char* str);
+
+// If the canvas does't make a surface (e.g. recording), make a raster surface
+sk_sp<SkSurface> makeSurface(SkCanvas*, const SkImageInfo&, const SkSurfaceProps* = nullptr);
+
+// A helper for inserting a drawtext call into a SkTextBlobBuilder
+void add_to_text_blob_w_len(SkTextBlobBuilder*,
+                            const char* text,
+                            size_t      len,
+                            SkTextEncoding,
+                            const SkFont&,
+                            SkScalar x,
+                            SkScalar y);
+
+void add_to_text_blob(SkTextBlobBuilder*, const char* text, const SkFont&, SkScalar x, SkScalar y);
+
+// Constructs a star by walking a 'numPts'-sided regular polygon with even/odd fill:
+//
+//   moveTo(pts[0]);
+//   lineTo(pts[step % numPts]);
+//   ...
+//   lineTo(pts[(step * (N - 1)) % numPts]);
+//
+// numPts=5, step=2 will produce a classic five-point star.
+//
+// numPts and step must be co-prime.
+SkPath make_star(const SkRect& bounds, int numPts = 5, int step = 2);
+
+void create_hemi_normal_map(SkBitmap* bm, const SkIRect& dst);
+
+void create_frustum_normal_map(SkBitmap* bm, const SkIRect& dst);
+
+void create_tetra_normal_map(SkBitmap* bm, const SkIRect& dst);
+
+void make_big_path(SkPath& path);
+
+// A helper object to test the topological sorting code (TopoSortBench.cpp & TopoSortTest.cpp)
+class TopoTestNode : public SkRefCnt {
+public:
+    TopoTestNode(int id) : fID(id), fOutputPos(-1), fTempMark(false) {}
+
+    void dependsOn(TopoTestNode* src) { *fDependencies.append() = src; }
+
+    int  id() const { return fID; }
+    void reset() { fOutputPos = -1; }
+
+    int outputPos() const { return fOutputPos; }
+
+    // check that the topological sort is valid for this node
+    bool check() {
+        if (-1 == fOutputPos) {
+            return false;
+        }
+
+        for (int i = 0; i < fDependencies.count(); ++i) {
+            if (-1 == fDependencies[i]->outputPos()) {
+                return false;
+            }
+            // This node should've been output after all the nodes on which it depends
+            if (fOutputPos < fDependencies[i]->outputPos()) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    // The following 7 methods are needed by the topological sort
+    static void SetTempMark(TopoTestNode* node) { node->fTempMark = true; }
+    static void ResetTempMark(TopoTestNode* node) { node->fTempMark = false; }
+    static bool IsTempMarked(TopoTestNode* node) { return node->fTempMark; }
+    static void Output(TopoTestNode* node, int outputPos) {
+        SkASSERT(-1 != outputPos);
+        node->fOutputPos = outputPos;
+    }
+    static bool          WasOutput(TopoTestNode* node) { return (-1 != node->fOutputPos); }
+    static int           NumDependencies(TopoTestNode* node) { return node->fDependencies.count(); }
+    static TopoTestNode* Dependency(TopoTestNode* node, int index) {
+        return node->fDependencies[index];
+    }
+
+    // Helper functions for TopoSortBench & TopoSortTest
+    static void AllocNodes(SkTArray<sk_sp<ToolUtils::TopoTestNode>>* graph, int num) {
+        graph->reserve(num);
+
+        for (int i = 0; i < num; ++i) {
+            graph->push_back(sk_sp<TopoTestNode>(new TopoTestNode(i)));
+        }
+    }
+
+#ifdef SK_DEBUG
+    static void Print(const SkTArray<TopoTestNode*>& graph) {
+        for (int i = 0; i < graph.count(); ++i) {
+            SkDebugf("%d, ", graph[i]->id());
+        }
+        SkDebugf("\n");
+    }
+#endif
+
+    // randomize the array
+    static void Shuffle(SkTArray<sk_sp<TopoTestNode>>* graph, SkRandom* rand) {
+        for (int i = graph->count() - 1; i > 0; --i) {
+            int swap = rand->nextU() % (i + 1);
+
+            (*graph)[i].swap((*graph)[swap]);
+        }
+    }
+
+private:
+    int  fID;
+    int  fOutputPos;
+    bool fTempMark;
+
+    SkTDArray<TopoTestNode*> fDependencies;
+};
+
+template <typename T>
+inline bool EncodeImageToFile(const char* path, const T& src, SkEncodedImageFormat f, int q) {
+    SkFILEWStream file(path);
+    return file.isValid() && SkEncodeImage(&file, src, f, q);
+}
+
+bool copy_to(SkBitmap* dst, SkColorType dstCT, const SkBitmap& src);
+void copy_to_g8(SkBitmap* dst, const SkBitmap& src);
+
+class PixelIter {
+public:
+    PixelIter();
+    PixelIter(SkSurface* surf) {
+        SkPixmap pm;
+        if (!surf->peekPixels(&pm)) {
+            pm.reset();
+        }
+        this->reset(pm);
+    }
+
+    void reset(const SkPixmap& pm) {
+        fPM  = pm;
+        fLoc = {-1, 0};
+    }
+
+    void* next(SkIPoint* loc = nullptr) {
+        if (!fPM.addr()) {
+            return nullptr;
+        }
+        fLoc.fX += 1;
+        if (fLoc.fX >= fPM.width()) {
+            fLoc.fX = 0;
+            if (++fLoc.fY >= fPM.height()) {
+                this->setDone();
+                return nullptr;
+            }
+        }
+        if (loc) {
+            *loc = fLoc;
+        }
+        return fPM.writable_addr(fLoc.fX, fLoc.fY);
+    }
+
+    void setDone() { fPM.reset(); }
+
+private:
+    SkPixmap fPM;
+    SkIPoint fLoc;
+};
+
+}  // namespace ToolUtils
+
+#endif  // ToolUtils_DEFINED
diff --git a/tools/bookmaker/bmhParser.cpp b/tools/bookmaker/bmhParser.cpp
deleted file mode 100644
index 28085ca..0000000
--- a/tools/bookmaker/bmhParser.cpp
+++ /dev/null
@@ -1,2379 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "bmhParser.h"
-
-const string kSpellingFileName("spelling.txt");
-
-#define M(mt) (1LL << (int) MarkType::k##mt)
-#define M_D M(Description)
-#define M_CS M(Class) | M(Struct)
-#define M_MD M(Method) | M(Define)
-#define M_MDCM M_MD | M(Const) | M(Member)
-#define M_ST M(Subtopic) | M(Topic)
-#define M_CSST M_CS | M_ST
-#ifdef M_E
-#undef M_E
-#endif
-#define M_E M(Enum) | M(EnumClass)
-
-#define R_Y Resolvable::kYes
-#define R_N Resolvable::kNo
-#define R_O Resolvable::kOut
-#define R_K Resolvable::kCode
-#define R_F Resolvable::kFormula
-#define R_C Resolvable::kClone
-
-#define E_Y Exemplary::kYes
-#define E_N Exemplary::kNo
-#define E_O Exemplary::kOptional
-
-// ToDo: add column to denote which marks are one-liners
-BmhParser::MarkProps BmhParser::kMarkProps[] = {
-// names without formal definitions (e.g. Column) aren't included
-  { "",             MarkType::kNone,         R_Y, E_N, 0 }
-, { "A",            MarkType::kAnchor,       R_N, E_N, 0 }
-, { "Alias",        MarkType::kAlias,        R_N, E_N, M_ST | M(Const) }
-, { "Bug",          MarkType::kBug,          R_N, E_N, M_CSST | M_MDCM | M_E
-                                                     | M(Example) | M(NoExample) }
-, { "Class",        MarkType::kClass,        R_Y, E_O, M_CSST }
-, { "Code",         MarkType::kCode,         R_K, E_N, M_CSST | M_E | M_MD | M(Typedef) }
-, { "",             MarkType::kColumn,       R_Y, E_N, M(Row) }
-, { "",             MarkType::kComment,      R_N, E_N, 0 }
-, { "Const",        MarkType::kConst,        R_Y, E_O, M_E | M_CSST  }
-, { "Define",       MarkType::kDefine,       R_O, E_Y, M_ST }
-, { "Description",  MarkType::kDescription,  R_Y, E_N, M(Example) | M(NoExample) }
-, { "Details",      MarkType::kDetails,      R_N, E_N, M(Const) }
-, { "Duration",     MarkType::kDuration,     R_N, E_N, M(Example) | M(NoExample) }
-, { "Enum",         MarkType::kEnum,         R_Y, E_O, M_CSST }
-, { "EnumClass",    MarkType::kEnumClass,    R_Y, E_O, M_CSST }
-, { "Example",      MarkType::kExample,      R_O, E_N, M_CSST | M_E | M_MD | M(Const) }
-, { "External",     MarkType::kExternal,     R_Y, E_N, 0 }
-, { "File",         MarkType::kFile,         R_Y, E_N, M(Topic) }
-, { "Filter",       MarkType::kFilter,       R_N, E_N, M(Subtopic) | M(Code) }
-, { "Formula",      MarkType::kFormula,      R_F, E_N, M(Column) | M(Description)
-                                                     | M_E | M_ST | M_MDCM }
-, { "Function",     MarkType::kFunction,     R_O, E_N, M(Example) | M(NoExample) }
-, { "Height",       MarkType::kHeight,       R_N, E_N, M(Example) | M(NoExample) }
-, { "Illustration", MarkType::kIllustration, R_N, E_N, M_CSST | M_MD }
-, { "Image",        MarkType::kImage,        R_N, E_N, M(Example) | M(NoExample) }
-, { "In",           MarkType::kIn,           R_N, E_N, M_CSST | M_E | M(Method) | M(Typedef) | M(Code) }
-, { "Legend",       MarkType::kLegend,       R_Y, E_N, M(Table) }
-, { "Line",         MarkType::kLine,         R_N, E_N, M_CSST | M_E | M(Method) | M(Typedef) }
-, { "",             MarkType::kLink,         R_N, E_N, M(Anchor) }
-, { "List",         MarkType::kList,         R_Y, E_N, M(Method) | M_CSST | M_E | M_D }
-, { "Literal",      MarkType::kLiteral,      R_N, E_N, M(Code) }
-, { "",             MarkType::kMarkChar,     R_N, E_N, 0 }
-, { "Member",       MarkType::kMember,       R_Y, E_O, M_CSST }
-, { "Method",       MarkType::kMethod,       R_Y, E_Y, M_CSST }
-, { "NoExample",    MarkType::kNoExample,    R_N, E_N, M_CSST | M_E | M_MD }
-, { "NoJustify",    MarkType::kNoJustify,    R_N, E_N, M(Const) | M(Member) }
-, { "Outdent",      MarkType::kOutdent,      R_N, E_N, M(Code) }
-, { "Param",        MarkType::kParam,        R_Y, E_N, M(Method) | M(Define) }
-, { "PhraseDef",    MarkType::kPhraseDef,    R_Y, E_N, M_ST }
-, { "",             MarkType::kPhraseParam,  R_Y, E_N, 0 }
-, { "",             MarkType::kPhraseRef,    R_N, E_N, 0 }
-, { "Platform",     MarkType::kPlatform,     R_N, E_N, M(Example) | M(NoExample) }
-, { "Populate",     MarkType::kPopulate,     R_N, E_N, M(Code) | M(Method) }
-, { "Return",       MarkType::kReturn,       R_Y, E_N, M(Method) }
-, { "",             MarkType::kRow,          R_Y, E_N, M(Table) | M(List) }
-, { "SeeAlso",      MarkType::kSeeAlso,      R_C, E_N, M_CSST | M_E | M_MD | M(Typedef) }
-, { "Set",          MarkType::kSet,          R_N, E_N, M(Example) | M(NoExample) }
-, { "StdOut",       MarkType::kStdOut,       R_N, E_N, M(Example) | M(NoExample) }
-, { "Struct",       MarkType::kStruct,       R_Y, E_O, M(Class) | M_ST }
-, { "Substitute",   MarkType::kSubstitute,   R_N, E_N, M(Alias) | M_ST }
-, { "Subtopic",     MarkType::kSubtopic,     R_Y, E_Y, M_CSST | M_E }
-, { "Table",        MarkType::kTable,        R_Y, E_N, M(Method) | M_CSST | M_E }
-, { "Template",     MarkType::kTemplate,     R_Y, E_N, M_CSST }
-, { "",             MarkType::kText,         R_N, E_N, 0 }
-, { "ToDo",         MarkType::kToDo,         R_N, E_N, 0 }
-, { "Topic",        MarkType::kTopic,        R_Y, E_Y, 0 }
-, { "Typedef",      MarkType::kTypedef,      R_Y, E_O, M_CSST | M_E }
-, { "Union",        MarkType::kUnion,        R_Y, E_N, M_CSST }
-, { "Using",        MarkType::kUsing,        R_Y, E_O, M_CSST }
-, { "Volatile",     MarkType::kVolatile,     R_N, E_N, M(StdOut) }
-, { "Width",        MarkType::kWidth,        R_N, E_N, M(Example) | M(NoExample) }
-};
-
-#undef R_O
-#undef R_N
-#undef R_Y
-#undef R_K
-#undef R_F
-#undef R_C
-
-#undef M_E
-#undef M_CSST
-#undef M_ST
-#undef M_CS
-#undef M_MCD
-#undef M_D
-#undef M
-
-#undef E_Y
-#undef E_N
-#undef E_O
-
-bool BmhParser::addDefinition(const char* defStart, bool hasEnd, MarkType markType,
-        const vector<string>& typeNameBuilder, HasTag hasTag) {
-    Definition* definition = nullptr;
-    switch (markType) {
-        case MarkType::kComment:
-            if (!this->skipToDefinitionEnd(markType)) {
-                return false;
-            }
-            return true;
-        // these types may be referred to by name
-        case MarkType::kClass:
-        case MarkType::kStruct:
-        case MarkType::kConst:
-        case MarkType::kDefine:
-        case MarkType::kEnum:
-        case MarkType::kEnumClass:
-        case MarkType::kMember:
-        case MarkType::kMethod:
-        case MarkType::kTemplate:
-        case MarkType::kTypedef: {
-            if (!typeNameBuilder.size()) {
-                return this->reportError<bool>("unnamed markup");
-            }
-            if (typeNameBuilder.size() > 1) {
-                return this->reportError<bool>("expected one name only");
-            }
-            string name = typeNameBuilder[0];
-            if (nullptr == fRoot) {
-                fRoot = this->findBmhObject(markType, name);
-                fRoot->fFileName = fFileName;
-                fRoot->fName = name;
-                fRoot->fNames.fName = name;
-                fRoot->fNames.fParent = &fGlobalNames;
-                definition = fRoot;
-            } else {
-                if (nullptr == fParent) {
-                    return this->reportError<bool>("expected parent");
-                }
-                if (fParent == fRoot && hasEnd) {
-                    RootDefinition* rootParent = fRoot->rootParent();
-                    if (rootParent) {
-                        fRoot = rootParent;
-                    }
-                    definition = fParent;
-                } else {
-                    if (!hasEnd && fRoot->find(name, RootDefinition::AllowParens::kNo)) {
-                        return this->reportError<bool>("duplicate symbol");
-                    }
-                    if (MarkType::kStruct == markType || MarkType::kClass == markType
-                            || MarkType::kEnumClass == markType) {
-                        // if class or struct, build fRoot hierarchy
-                        // and change isDefined to search all parents of fRoot
-                        SkASSERT(!hasEnd);
-                        RootDefinition* childRoot = new RootDefinition;
-                        (fRoot->fBranches)[name] = childRoot;
-                        childRoot->setRootParent(fRoot);
-                        childRoot->fFileName = fFileName;
-                        SkASSERT(MarkType::kSubtopic != fRoot->fMarkType
-                                && MarkType::kTopic != fRoot->fMarkType);
-                        childRoot->fNames.fName = name;
-                        childRoot->fNames.fParent = &fRoot->fNames;
-                        fRoot = childRoot;
-                        definition = fRoot;
-                    } else {
-                        definition = &fRoot->fLeaves[name];
-                    }
-                }
-            }
-            if (hasEnd) {
-                Exemplary hasExample = Exemplary::kNo;
-                bool hasExcluder = false;
-                for (auto child : definition->fChildren) {
-                     if (MarkType::kExample == child->fMarkType) {
-                        hasExample = Exemplary::kYes;
-                     }
-                     hasExcluder |= MarkType::kNoExample == child->fMarkType;
-                }
-                if (kMarkProps[(int) markType].fExemplary != hasExample
-                        && kMarkProps[(int) markType].fExemplary != Exemplary::kOptional) {
-                    if (string::npos == fFileName.find("undocumented")
-                            && !hasExcluder) {
-                        hasExample == Exemplary::kNo ?
-                                this->reportWarning("missing example") :
-                                this->reportWarning("unexpected example");
-                    }
-
-                }
-                if (MarkType::kMethod == markType) {
-                    if (fCheckMethods && !definition->checkMethod()) {
-                        return false;
-                    }
-                }
-                if (HasTag::kYes == hasTag) {
-                    if (!this->checkEndMarker(markType, definition->fName)) {
-                        return false;
-                    }
-                }
-                if (!this->popParentStack(definition)) {
-                    return false;
-                }
-                if (fRoot == definition) {
-                    fRoot = nullptr;
-                }
-            } else {
-                definition->fStart = defStart;
-                this->skipSpace();
-                definition->fFileName = fFileName;
-                definition->fContentStart = fChar;
-                definition->fLineCount = fLineCount;
-                definition->fClone = fCloned;
-                if (MarkType::kConst == markType) {
-                    // todo: require that fChar points to def on same line as markup
-                    // additionally add definition to class children if it is not already there
-                    if (definition->fParent != fRoot) {
-//                        fRoot->fChildren.push_back(definition);
-                    }
-                }
-                SkASSERT(string::npos == name.find('\n'));
-                definition->fName = name;
-                if (MarkType::kMethod == markType) {
-                    if (string::npos != name.find(':', 0)) {
-                        definition->setCanonicalFiddle();
-                    } else {
-                        definition->fFiddle = name;
-                    }
-                } else {
-                    definition->fFiddle = Definition::NormalizedName(name);
-                }
-                definition->fMarkType = markType;
-                definition->fAnonymous = fAnonymous;
-                this->setAsParent(definition);
-            }
-            } break;
-        case MarkType::kTopic:
-        case MarkType::kSubtopic:
-            SkASSERT(1 == typeNameBuilder.size());
-            if (!hasEnd) {
-                if (!typeNameBuilder.size()) {
-                    return this->reportError<bool>("unnamed topic");
-                }
-                fTopics.emplace_front(markType, defStart, fLineCount, fParent, fMC);
-                RootDefinition* rootDefinition = &fTopics.front();
-                definition = rootDefinition;
-                definition->fFileName = fFileName;
-                definition->fContentStart = fChar;
-                if (MarkType::kTopic == markType) {
-                    if (fParent) {
-                        return this->reportError<bool>("#Topic must be root");
-                    }
-                    // topic name is unappended
-                    definition->fName = typeNameBuilder[0];
-                } else {
-                    if (!fParent) {
-                        return this->reportError<bool>("#Subtopic may not be root");
-                    }
-                    Definition* parent = fParent;
-                    while (MarkType::kTopic != parent->fMarkType && MarkType::kSubtopic != parent->fMarkType) {
-                        parent = parent->fParent;
-                        if (!parent) {
-                            // subtopic must have subtopic or topic in parent chain
-                            return this->reportError<bool>("#Subtopic missing parent");
-                        }
-                    }
-                    if (MarkType::kSubtopic == parent->fMarkType) {
-                        // subtopic prepends parent subtopic name, but not parent topic name
-                        definition->fName = parent->fName + '_';
-                    }
-                    definition->fName += typeNameBuilder[0];
-                    definition->fFiddle = parent->fFiddle + '_';
-                }
-                rootDefinition->fNames.fName = rootDefinition->fName;
-                rootDefinition->fNames.fParent = &fGlobalNames;
-                definition->fFiddle += Definition::NormalizedName(typeNameBuilder[0]);
-                this->setAsParent(definition);
-            }
-            {
-                SkASSERT(hasEnd ? fParent : definition);
-                string fullTopic = hasEnd ? fParent->fFiddle : definition->fFiddle;
-                Definition* defPtr = fTopicMap[fullTopic];
-                if (hasEnd) {
-                    if (HasTag::kYes == hasTag && !this->checkEndMarker(markType, fullTopic)) {
-                        return false;
-                    }
-                    if (!definition) {
-                        definition = defPtr;
-                    } else if (definition != defPtr) {
-                        return this->reportError<bool>("mismatched topic");
-                    }
-                } else {
-                    if (nullptr != defPtr) {
-                        return this->reportError<bool>("already declared topic");
-                    }
-                    fTopicMap[fullTopic] = definition;
-                }
-            }
-            if (hasEnd) {
-                if (!this->popParentStack(definition)) {
-                    return false;
-                }
-            }
-            break;
-        case MarkType::kFormula:
-            // hasEnd : single line / multiple line
-            if (!fParent || MarkType::kFormula != fParent->fMarkType) {
-                SkASSERT(!definition || MarkType::kFormula == definition->fMarkType);
-                fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
-                definition = &fMarkup.front();
-                definition->fContentStart = fChar;
-                definition->fName = typeNameBuilder[0];
-                definition->fFiddle = fParent->fFiddle;
-                fParent = definition;
-            } else {
-                SkASSERT(fParent && MarkType::kFormula == fParent->fMarkType);
-                SkASSERT(fMC == defStart[0]);
-                SkASSERT(fMC == defStart[-1]);
-                definition = fParent;
-                definition->fTerminator = fChar;
-                if (!this->popParentStack(definition)) {
-                    return false;
-                }
-                this->parseHashFormula(definition);
-                fParent->fChildren.push_back(definition);
-            }
-            break;
-        // these types are children of parents, but are not in named maps
-        case MarkType::kDescription:
-        case MarkType::kStdOut:
-        // may be one-liner
-        case MarkType::kAlias:
-        case MarkType::kNoExample:
-        case MarkType::kParam:
-        case MarkType::kPhraseDef:
-        case MarkType::kReturn:
-        case MarkType::kToDo:
-            if (hasEnd) {
-                if (markType == fParent->fMarkType) {
-                    definition = fParent;
-                    if (MarkType::kBug == markType || MarkType::kReturn == markType
-                            || MarkType::kToDo == markType) {
-                        this->skipNoName();
-                    }
-                    if (!this->popParentStack(fParent)) { // if not one liner, pop
-                        return false;
-                    }
-                    if (MarkType::kParam == markType || MarkType::kReturn == markType
-                            || MarkType::kPhraseDef == markType) {
-                        if (!this->checkParamReturn(definition)) {
-                            return false;
-                        }
-                    }
-                    if (MarkType::kPhraseDef == markType) {
-                        string key = definition->fName;
-                        if (fPhraseMap.end() != fPhraseMap.find(key)) {
-                            this->reportError<bool>("duplicate phrase key");
-                        }
-                        fPhraseMap[key] = definition;
-                    }
-                } else {
-                    fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
-                    definition = &fMarkup.front();
-                    definition->fName = typeNameBuilder[0];
-                    definition->fFiddle = fParent->fFiddle;
-                    definition->fContentStart = fChar;
-                    string endBracket;
-                    endBracket += fMC;
-                    endBracket += fMC;
-                    definition->fContentEnd = this->trimmedBracketEnd(endBracket);
-                    this->skipToEndBracket(endBracket.c_str());
-                    SkAssertResult(fMC == this->next());
-                    SkAssertResult(fMC == this->next());
-                    definition->fTerminator = fChar;
-                    TextParser checkForChildren(definition);
-                    if (checkForChildren.strnchr(fMC, definition->fContentEnd)) {
-                        this->reportError<bool>("put ## on separate line");
-                    }
-                    fParent->fChildren.push_back(definition);
-                }
-                if (MarkType::kAlias == markType) {
-                    const char* end = definition->fChildren.size() > 0 ?
-                            definition->fChildren[0]->fStart : definition->fContentEnd;
-                    TextParser parser(definition->fFileName, definition->fContentStart, end,
-                            definition->fLineCount);
-                    parser.trimEnd();
-                    string key = string(parser.fStart, parser.lineLength());
-                    if (fAliasMap.end() != fAliasMap.find(key)) {
-                        return this->reportError<bool>("duplicate alias");
-                    }
-                    fAliasMap[key] = definition;
-                    definition->fFiddle = definition->fParent->fFiddle;
-                }
-                break;
-            } else if (MarkType::kPhraseDef == markType) {
-                bool hasParams = '(' == this->next();
-                fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
-                definition = &fMarkup.front();
-                definition->fName = typeNameBuilder[0];
-                definition->fFiddle = fParent->fFiddle;
-                definition->fContentStart = fChar;
-                if (hasParams) {
-                    char lastChar;
-                    do {
-                        const char* subEnd = this->anyOf(",)\n");
-                        if (!subEnd || '\n' == *subEnd) {
-                            return this->reportError<bool>("unexpected phrase list end");
-                        }
-                        fMarkup.emplace_front(MarkType::kPhraseParam, fChar, fLineCount, fParent,
-                                fMC);
-                        Definition* phraseParam = &fMarkup.front();
-                        phraseParam->fContentStart = fChar;
-                        phraseParam->fContentEnd = subEnd;
-                        phraseParam->fName = string(fChar, subEnd - fChar);
-                        definition->fChildren.push_back(phraseParam);
-                        this->skipTo(subEnd);
-                        lastChar = this->next();
-                        phraseParam->fTerminator = fChar;
-                    } while (')' != lastChar);
-                    this->skipWhiteSpace();
-                    definition->fContentStart = fChar;
-                }
-                this->setAsParent(definition);
-                break;
-            }
-        // not one-liners
-        case MarkType::kCode:
-        case MarkType::kExample:
-        case MarkType::kFile:
-        case MarkType::kFunction:
-        case MarkType::kLegend:
-        case MarkType::kList:
-        case MarkType::kTable:
-            if (hasEnd) {
-                definition = fParent;
-                if (markType != fParent->fMarkType) {
-                    return this->reportError<bool>("end element mismatch");
-                } else if (!this->popParentStack(fParent)) {
-                    return false;
-                }
-                if (MarkType::kExample == markType) {
-                    if (definition->fChildren.size() == 0) {
-                        TextParser emptyCheck(definition);
-                        if (emptyCheck.eof() || !emptyCheck.skipWhiteSpace()) {
-                            return this->reportError<bool>("missing example body");
-                        }
-                    }
-// can't do this here; phrase refs may not have been defined yet
-//                    this->setWrapper(definition);
-                }
-            } else {
-                fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
-                definition = &fMarkup.front();
-                definition->fContentStart = fChar;
-                definition->fName = typeNameBuilder[0];
-                definition->fFiddle = fParent->fFiddle;
-                char suffix = '\0';
-                bool tryAgain;
-                do {
-                    tryAgain = false;
-                    for (const auto& child : fParent->fChildren) {
-                        if (child->fFiddle == definition->fFiddle) {
-                            if (MarkType::kExample != child->fMarkType) {
-                                continue;
-                            }
-                            if ('\0' == suffix) {
-                                suffix = 'a';
-                            } else if (++suffix > 'z') {
-                                return reportError<bool>("too many examples");
-                            }
-                            definition->fFiddle = fParent->fFiddle + '_';
-                            definition->fFiddle += suffix;
-                            tryAgain = true;
-                            break;
-                        }
-                    }
-                } while (tryAgain);
-                this->setAsParent(definition);
-            }
-            break;
-            // always treated as one-liners (can't detect misuse easily)
-        case MarkType::kAnchor:
-        case MarkType::kBug:
-        case MarkType::kDetails:
-        case MarkType::kDuration:
-        case MarkType::kFilter:
-        case MarkType::kHeight:
-        case MarkType::kIllustration:
-        case MarkType::kImage:
-		case MarkType::kIn:
-		case MarkType::kLine:
-		case MarkType::kLiteral:
-        case MarkType::kNoJustify:
-        case MarkType::kOutdent:
-        case MarkType::kPlatform:
-        case MarkType::kPopulate:
-        case MarkType::kSeeAlso:
-        case MarkType::kSet:
-        case MarkType::kSubstitute:
-        case MarkType::kVolatile:
-        case MarkType::kWidth:
-            // todo : add check disallowing children?
-            if (hasEnd && MarkType::kAnchor != markType && MarkType::kLine != markType) {
-                return this->reportError<bool>("one liners omit end element");
-            } else if (!hasEnd && MarkType::kAnchor == markType) {
-                return this->reportError<bool>("anchor line must have end element last");
-            }
-            fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
-            definition = &fMarkup.front();
-            definition->fName = typeNameBuilder[0];
-            definition->fFiddle = Definition::NormalizedName(typeNameBuilder[0]);
-            definition->fContentStart = fChar;
-            definition->fContentEnd = this->trimmedBracketEnd('\n');
-            definition->fTerminator = this->lineEnd() - 1;
-            fParent->fChildren.push_back(definition);
-            if (MarkType::kAnchor == markType) {
-                this->parseHashAnchor(definition);
-			} else if (MarkType::kLine == markType) {
-                this->parseHashLine(definition);
-			}
-            break;
-        case MarkType::kExternal:
-            (void) this->collectExternals();  // FIXME: detect errors in external defs?
-            break;
-        default:
-            SkASSERT(0);  // fixme : don't let any types be invisible
-            return true;
-    }
-    if (fParent) {
-        SkASSERT(definition);
-        SkASSERT(definition->fName.length() > 0);
-    }
-    return true;
-}
-
-void BmhParser::reportDuplicates(const Definition& def, string dup) const {
-    if (MarkType::kExample == def.fMarkType && dup == def.fFiddle) {
-        TextParser reporter(&def);
-        reporter.reportError("duplicate example name");
-    }
-    for (auto& child : def.fChildren ) {
-        reportDuplicates(*child, dup);
-    }
-}
-
-
-static Definition* find_fiddle(Definition* def, string name) {
-    if (MarkType::kExample == def->fMarkType && name == def->fFiddle) {
-        return def;
-    }
-    for (auto& child : def->fChildren) {
-        Definition* result = find_fiddle(child, name);
-        if (result) {
-            return result;
-        }
-    }
-    return nullptr;
-}
-
-Definition* BmhParser::findExample(string name) const {
-    for (const auto& topic : fTopicMap) {
-        if (topic.second->fParent) {
-            continue;
-        }
-        Definition* def = find_fiddle(topic.second, name);
-        if (def) {
-            return def;
-        }
-    }
-    return nullptr;
-}
-
-static bool check_example_hashes(Definition* def) {
-    if (MarkType::kExample == def->fMarkType) {
-        if (def->fHash.length()) {
-            return true;
-        }
-        for (auto child : def->fChildren) {
-            if (MarkType::kPlatform == child->fMarkType) {
-                if (string::npos != string(child->fContentStart, child->length()).find("!fiddle")) {
-                    return true;
-                }
-            }
-        }
-        return def->reportError<bool>("missing hash");
-    }
-    for (auto& child : def->fChildren) {
-        if (!check_example_hashes(child)) {
-            return false;
-        }
-    }
-    return true;
-}
-
-bool BmhParser::checkExampleHashes() const {
-    for (const auto& topic : fTopicMap) {
-        if (!topic.second->fParent && !check_example_hashes(topic.second)) {
-            return false;
-        }
-    }
-    return true;
-}
-
-static void reset_example_hashes(Definition* def) {
-    if (MarkType::kExample == def->fMarkType) {
-        def->fHash.clear();
-        return;
-    }
-    for (auto& child : def->fChildren) {
-        reset_example_hashes(child);
-    }
-}
-
-void BmhParser::resetExampleHashes() {
-    for (const auto& topic : fTopicMap) {
-        if (!topic.second->fParent) {
-            reset_example_hashes(topic.second);
-        }
-    }
-}
-
-static void find_examples(const Definition& def, vector<string>* exampleNames) {
-    if (MarkType::kExample == def.fMarkType) {
-        exampleNames->push_back(def.fFiddle);
-    }
-    for (auto& child : def.fChildren ) {
-        find_examples(*child, exampleNames);
-    }
-}
-
-bool BmhParser::checkEndMarker(MarkType markType, string match) const {
-    TextParser tp(fFileName, fLine, fChar, fLineCount);
-    tp.skipSpace();
-    if (fMC != tp.next()) {
-        return this->reportError<bool>("mismatched end marker expect #");
-    }
-    const char* nameStart = tp.fChar;
-    tp.skipToNonName();
-    string markName(nameStart, tp.fChar - nameStart);
-    if (kMarkProps[(int) markType].fName != markName) {
-        return this->reportError<bool>("expected #XXX ## to match");
-    }
-    tp.skipSpace();
-    nameStart = tp.fChar;
-    tp.skipToNonName();
-    markName = string(nameStart, tp.fChar - nameStart);
-    if ("" == markName) {
-        if (fMC != tp.next() || fMC != tp.next()) {
-            return this->reportError<bool>("expected ##");
-        }
-        return true;
-    }
-    std::replace(markName.begin(), markName.end(), '-', '_');
-    auto defPos = match.rfind(markName);
-    if (string::npos == defPos) {
-        return this->reportError<bool>("mismatched end marker v1");
-    }
-    if (markName.size() != match.size() - defPos) {
-        return this->reportError<bool>("mismatched end marker v2");
-    }
-    return true;
-}
-
-bool BmhParser::checkExamples() const {
-    vector<string> exampleNames;
-    for (const auto& topic : fTopicMap) {
-        if (topic.second->fParent) {
-            continue;
-        }
-        find_examples(*topic.second, &exampleNames);
-    }
-    std::sort(exampleNames.begin(), exampleNames.end());
-    string* last = nullptr;
-    string reported;
-    bool checkOK = true;
-    for (auto& nameIter : exampleNames) {
-        if (last && *last == nameIter && reported != *last) {
-            reported = *last;
-            SkDebugf("%s\n", reported.c_str());
-            for (const auto& topic : fTopicMap) {
-                if (topic.second->fParent) {
-                    continue;
-                }
-                this->reportDuplicates(*topic.second, reported);
-            }
-            checkOK = false;
-        }
-        last = &nameIter;
-    }
-    return checkOK;
-}
-
-bool BmhParser::checkParamReturn(const Definition* definition) const {
-    const char* parmEndCheck = definition->fContentEnd;
-    while (parmEndCheck < definition->fTerminator) {
-        if (fMC == parmEndCheck[0]) {
-            break;
-        }
-        if (' ' < parmEndCheck[0]) {
-            this->reportError<bool>(
-                    "use full end marker on multiline #Param and #Return");
-        }
-        ++parmEndCheck;
-    }
-    return true;
-}
-
-bool BmhParser::childOf(MarkType markType) const {
-    auto childError = [this](MarkType markType) -> bool {
-        string errStr = "expected ";
-        errStr += kMarkProps[(int) markType].fName;
-        errStr += " parent";
-        return this->reportError<bool>(errStr.c_str());
-    };
-
-    if (markType == fParent->fMarkType) {
-        return true;
-    }
-    if (this->hasEndToken()) {
-        if (!fParent->fParent) {
-            return this->reportError<bool>("expected grandparent");
-        }
-        if (markType == fParent->fParent->fMarkType) {
-            return true;
-        }
-    }
-    return childError(markType);
-}
-
-string BmhParser::className(MarkType markType) {
-    const char* end = this->lineEnd();
-    const char* mc = this->strnchr(fMC, end);
-    string classID;
-    TextParserSave savePlace(this);
-    this->skipSpace();
-    const char* wordStart = fChar;
-    this->skipToNonName();
-    const char* wordEnd = fChar;
-    classID = string(wordStart, wordEnd - wordStart);
-    if (!mc) {
-        savePlace.restore();
-    }
-    string builder;
-    const Definition* parent = this->parentSpace();
-    if (parent && parent->fName != classID) {
-        builder += parent->fName;
-    }
-    if (mc) {
-        if (mc + 1 < fEnd && fMC == mc[1]) {  // if ##
-            if (markType != fParent->fMarkType) {
-                return this->reportError<string>("unbalanced method");
-            }
-            if (builder.length() > 0 && classID.size() > 0) {
-                if (builder != fParent->fName) {
-                    builder += "::";
-                    builder += classID;
-                    if (builder != fParent->fName) {
-                        return this->reportError<string>("name mismatch");
-                    }
-                }
-            }
-            this->skipLine();
-            return fParent->fName;
-        } else if (' ' ==  mc[1] && MarkType::kConst == markType && fParent
-                && (MarkType::kEnum == fParent->fMarkType
-                || MarkType::kEnumClass == fParent->fMarkType)) {
-            this->skipToEndBracket('\n');
-            return builder + "::" + string(wordStart, wordEnd - wordStart);
-        }
-        fChar = mc;
-        this->next();
-    }
-    this->skipWhiteSpace();
-    if (MarkType::kEnum == markType && fChar >= end) {
-        fAnonymous = true;
-        builder += "::_anonymous";
-        return uniqueRootName(builder, markType);
-    }
-    builder = this->word(builder, "::");
-    return builder;
-}
-
-bool BmhParser::collectExternals() {
-    do {
-        this->skipWhiteSpace();
-        if (this->eof()) {
-            break;
-        }
-        if (fMC == this->peek()) {
-            this->next();
-            if (this->eof()) {
-                break;
-            }
-            if (fMC == this->peek()) {
-                this->skipLine();
-                break;
-            }
-            if (' ' >= this->peek()) {
-                this->skipLine();
-                continue;
-            }
-            if (this->startsWith(kMarkProps[(int) MarkType::kExternal].fName)) {
-                this->skipToNonName();
-                continue;
-            }
-        }
-        this->skipToAlpha();
-        const char* wordStart = fChar;
-        this->skipToWhiteSpace();
-        if (fChar - wordStart > 0) {
-            fExternals.emplace_front(MarkType::kExternal, wordStart, fChar, fLineCount, fParent,
-                    fMC);
-            RootDefinition* definition = &fExternals.front();
-            definition->fFileName = fFileName;
-            definition->fName = string(wordStart ,fChar - wordStart);
-            definition->fFiddle = Definition::NormalizedName(definition->fName);
-        }
-    } while (!this->eof());
-    return true;
-}
-
-bool BmhParser::dumpExamples(FILE* fiddleOut, Definition& def, bool* continuation) const {
-    if (MarkType::kExample == def.fMarkType) {
-        string result;
-        if (!this->exampleToScript(&def, BmhParser::ExampleOptions::kAll, &result)) {
-            return false;
-        }
-        if (result.length() > 0) {
-            result += "\n";
-            result += "}";
-            if (*continuation) {
-                fprintf(fiddleOut, ",\n");
-            } else {
-                *continuation = true;
-            }
-            fprintf(fiddleOut, "%s", result.c_str());
-        }
-        return true;
-    }
-    for (auto& child : def.fChildren ) {
-        if (!this->dumpExamples(fiddleOut, *child, continuation)) {
-            return false;
-        }
-    }
-    return true;
-}
-
-bool BmhParser::dumpExamples(const char* fiddleJsonFileName) const {
-    string oldFiddle(fiddleJsonFileName);
-    string newFiddle(fiddleJsonFileName);
-    newFiddle += "_new";
-    FILE* fiddleOut = fopen(newFiddle.c_str(), "wb");
-    if (!fiddleOut) {
-        SkDebugf("could not open output file %s\n", newFiddle.c_str());
-        return false;
-    }
-    fprintf(fiddleOut, "{\n");
-    bool continuation = false;
-    for (const auto& topic : fTopicMap) {
-        if (topic.second->fParent) {
-            continue;
-        }
-        this->dumpExamples(fiddleOut, *topic.second, &continuation);
-    }
-    fprintf(fiddleOut, "\n}\n");
-    fclose(fiddleOut);
-    if (ParserCommon::WrittenFileDiffers(oldFiddle, newFiddle)) {
-        ParserCommon::CopyToFile(oldFiddle, newFiddle);
-        SkDebugf("wrote %s\n", fiddleJsonFileName);
-    } else {
-        remove(newFiddle.c_str());
-    }
-    return true;
-}
-
-int BmhParser::endHashCount() const {
-    const char* end = fLine + this->lineLength();
-    int count = 0;
-    while (fLine < end && fMC == *--end) {
-        count++;
-    }
-    return count;
-}
-
-bool BmhParser::endTableColumn(const char* end, const char* terminator) {
-    if (!this->popParentStack(fParent)) {
-        return false;
-    }
-    fWorkingColumn->fContentEnd = end;
-    fWorkingColumn->fTerminator = terminator;
-    fColStart = fChar - 1;
-    this->skipSpace();
-    fTableState = TableState::kColumnStart;
-    return true;
-}
-
-static size_t count_indent(string text, size_t test, size_t end) {
-    size_t result = test;
-    while (test < end) {
-        if (' ' != text[test]) {
-            break;
-        }
-        ++test;
-    }
-    return test - result;
-}
-
-static void add_code(string text, int pos, int end,
-    size_t outIndent, size_t textIndent, string& example) {
-    do {
-        // fix this to move whole paragraph in, out, but preserve doc indent
-        int nextIndent = count_indent(text, pos, end);
-        size_t len = text.find('\n', pos);
-        if (string::npos == len) {
-            len = end;
-        }
-        if ((size_t) (pos + nextIndent) < len) {
-            size_t indent = outIndent + nextIndent;
-            SkASSERT(indent >= textIndent);
-            indent -= textIndent;
-            for (size_t index = 0; index < indent; ++index) {
-                example += ' ';
-            }
-            pos += nextIndent;
-            while ((size_t) pos < len) {
-                example += '"' == text[pos] ? "\\\"" :
-                    '\\' == text[pos] ? "\\\\" :
-                    text.substr(pos, 1);
-                ++pos;
-            }
-            example += "\\n";
-        } else {
-            pos += nextIndent;
-        }
-        if ('\n' == text[pos]) {
-            ++pos;
-        }
-    } while (pos < end);
-}
-
-bool BmhParser::IsExemplary(const Definition* def) {
-    return kMarkProps[(int) def->fMarkType].fExemplary != Exemplary::kNo;
-}
-
-bool BmhParser::exampleToScript(Definition* def, ExampleOptions exampleOptions,
-        string* result) const {
-    bool hasFiddle = true;
-    const Definition* platform = def->hasChild(MarkType::kPlatform);
-    if (platform) {
-        TextParser platParse(platform);
-        hasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd);
-    }
-    if (!hasFiddle) {
-        *result = "";
-        return true;
-    }
-    string text = this->extractText(def, TrimExtract::kNo);
-    bool textOut = string::npos != text.find("SkDebugf(")
-        || string::npos != text.find("dump(")
-        || string::npos != text.find("dumpHex(");
-    string heightStr = "256";
-    string widthStr = "256";
-    string normalizedName(def->fFiddle);
-    string code;
-    string imageStr = "0";
-    string srgbStr = "false";
-    string durationStr = "0";
-    for (auto iter : def->fChildren) {
-        switch (iter->fMarkType) {
-        case MarkType::kDuration:
-            durationStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
-            break;
-        case MarkType::kHeight:
-            heightStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
-            break;
-        case MarkType::kWidth:
-            widthStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
-            break;
-        case MarkType::kDescription:
-            // ignore for now
-            break;
-        case MarkType::kFunction: {
-            // emit this, but don't wrap this in draw()
-            string funcText = this->extractText(&*iter, TrimExtract::kNo);
-            size_t pos = 0;
-            while (pos < funcText.length() && ' ' > funcText[pos]) {
-                ++pos;
-            }
-            size_t indent = count_indent(funcText, pos, funcText.length());
-            add_code(funcText, pos, funcText.length(), 0, indent, code);
-            code += "\\n";
-        } break;
-        case MarkType::kComment:
-            break;
-        case MarkType::kImage:
-            imageStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
-            break;
-        case MarkType::kToDo:
-            break;
-        case MarkType::kBug:
-        case MarkType::kMarkChar:
-        case MarkType::kPlatform:
-        case MarkType::kPhraseRef:
-            // ignore for now
-            break;
-        case MarkType::kSet:
-            if ("sRGB" == string(iter->fContentStart,
-                iter->fContentEnd - iter->fContentStart)) {
-                srgbStr = "true";
-            } else {
-                SkASSERT(0);   // more work to do
-                return false;
-            }
-            break;
-        case MarkType::kStdOut:
-            textOut = true;
-            break;
-        default:
-            SkASSERT(0);  // more coding to do
-        }
-    }
-    string animatedStr = "0" != durationStr ? "true" : "false";
-    string textOutStr = textOut ? "true" : "false";
-    size_t pos = 0;
-    while (pos < text.length() && ' ' > text[pos]) {
-        ++pos;
-    }
-    size_t end = text.length();
-    size_t outIndent = 0;
-    size_t textIndent = count_indent(text, pos, end);
-    if ("" == def->fWrapper) {
-        this->setWrapper(def);
-    }
-    if (def->fWrapper.length() > 0) {
-        code += def->fWrapper;
-        code += "\\n";
-        outIndent = 4;
-    }
-    add_code(text, pos, end, outIndent, textIndent, code);
-    if (def->fWrapper.length() > 0) {
-        code += "}";
-    }
-    string example = "\"" + normalizedName + "\": {\n";
-    string filename = def->fileName();
-    string baseFile = filename.substr(0, filename.length() - 4);
-    if (ExampleOptions::kText == exampleOptions) {
-        example += "    \"code\": \"" + code + "\",\n";
-        example += "    \"hash\": \"" + def->fHash + "\",\n";
-        example += "    \"file\": \"" + baseFile + "\",\n";
-        example += "    \"name\": \"" + def->fName + "\",";
-    } else {
-        example += "    \"code\": \"" + code + "\",\n";
-        if (ExampleOptions::kPng == exampleOptions) {
-            example += "    \"width\": " + widthStr + ",\n";
-            example += "    \"height\": " + heightStr + ",\n";
-            example += "    \"hash\": \"" + def->fHash + "\",\n";
-            example += "    \"file\": \"" + baseFile + "\",\n";
-            example += "    \"name\": \"" + def->fName + "\"\n";
-            example += "}";
-        } else {
-            example += "    \"options\": {\n";
-            example += "        \"width\": " + widthStr + ",\n";
-            example += "        \"height\": " + heightStr + ",\n";
-            example += "        \"source\": " + imageStr + ",\n";
-            example += "        \"srgb\": " + srgbStr + ",\n";
-            example += "        \"f16\": false,\n";
-            example += "        \"textOnly\": " + textOutStr + ",\n";
-            example += "        \"animated\": " + animatedStr + ",\n";
-            example += "        \"duration\": " + durationStr + "\n";
-            example += "    },\n";
-            example += "    \"fast\": true";
-        }
-    }
-    *result = example;
-    return true;
-}
-
-string BmhParser::extractText(const Definition* def, TrimExtract trimExtract) const {
-    string result;
-    TextParser parser(def);
-    auto childIter = def->fChildren.begin();
-    while (!parser.eof()) {
-        const char* end = def->fChildren.end() == childIter ? parser.fEnd : (*childIter)->fStart;
-        string fragment(parser.fChar, end - parser.fChar);
-        trim_end(fragment);
-        if (TrimExtract::kYes == trimExtract) {
-            trim_start(fragment);
-            if (result.length()) {
-                result += '\n';
-                result += '\n';
-            }
-        }
-        if (TrimExtract::kYes == trimExtract || has_nonwhitespace(fragment)) {
-            result += fragment;
-        }
-        parser.skipTo(end);
-        if (def->fChildren.end() != childIter) {
-            Definition* child = *childIter;
-            if (MarkType::kPhraseRef == child->fMarkType) {
-                auto phraseIter = fPhraseMap.find(child->fName);
-                if (fPhraseMap.end() == phraseIter) {
-                    return def->reportError<string>("missing phrase definition");
-                }
-                Definition* phrase = phraseIter->second;
-                // count indent of last line in result
-                size_t lastLF = result.rfind('\n');
-                size_t startPos = string::npos == lastLF ? 0 : lastLF;
-                size_t lastLen = result.length() - startPos;
-                size_t indent = count_indent(result, startPos, result.length()) + 4;
-                string phraseStr = this->extractText(phrase, TrimExtract::kNo);
-                startPos = 0;
-                bool firstTime = true;
-                size_t endPos;
-                do {
-                    endPos = phraseStr.find('\n', startPos);
-                    size_t len = (string::npos != endPos ? endPos : phraseStr.length()) - startPos;
-                    if (firstTime && lastLen + len + 1 < 100) {  // FIXME: make 100 global const or something
-                        result += ' ';
-                    } else {
-                        result += '\n';
-                        result += string(indent, ' ');
-                    }
-                    firstTime = false;
-                    string tmp = phraseStr.substr(startPos, len);
-                    result += tmp;
-                    startPos = endPos + 1;
-                } while (string::npos != endPos);
-                result += '\n';
-            }
-            parser.skipTo(child->fTerminator);
-            std::advance(childIter, 1);
-        }
-    }
-    return result;
-}
-
-string BmhParser::loweredTopic(string name, Definition* def) {
-    string lowered;
-    SkASSERT('_' != name[0]);
-    char last = '_';
-    for (char c : name) {
-        SkASSERT(' ' != c);
-        if (isupper(last)) {
-            lowered += islower(c) ? tolower(last) : last;
-            last = '\0';
-        }
-        if ('_' == c) {
-            last = c;
-            c = ' ';
-        } else if ('_' == last && isupper(c)) {
-            last = c;
-            continue;
-        }
-        lowered += c;
-        if (' ' == c) {
-            this->setUpPartialSubstitute(lowered);
-        }
-    }
-    if (isupper(last)) {
-        lowered += tolower(last);
-    }
-    return lowered;
-}
-
-void BmhParser::setUpGlobalSubstitutes() {
-    for (auto& entry : fExternals) {
-        string externalName = entry.fName;
-        SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(externalName));
-        fGlobalNames.fRefMap[externalName] = nullptr;
-    }
-    for (auto bMap : { &fClassMap, &fConstMap, &fDefineMap, &fEnumMap, &fMethodMap,
-            &fTypedefMap } ) {
-        for (auto& entry : *bMap) {
-            Definition* parent = &entry.second;
-            string name = parent->fName;
-            SkASSERT(fGlobalNames.fLinkMap.end() == fGlobalNames.fLinkMap.find(name));
-            string ref = ParserCommon::HtmlFileName(parent->fFileName) + '#' + parent->fFiddle;
-            fGlobalNames.fLinkMap[name] = ref;
-            SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(name));
-            fGlobalNames.fRefMap[name] = parent;
-            NameMap* names = MarkType::kClass == parent->fMarkType
-                    || MarkType::kStruct == parent->fMarkType
-                    || MarkType::kEnumClass == parent->fMarkType ? &parent->asRoot()->fNames :
-                    &fGlobalNames;
-            this->setUpSubstitutes(parent, names);
-            if (names != &fGlobalNames) {
-                names->copyToParent(&fGlobalNames);
-            }
-        }
-    }
-    for (auto& tEntry : fTypedefMap) {
-        Definition* typeDef = &tEntry.second;
-        string defName = typeDef->fName;
-        TextParser parser(typeDef->fFileName, typeDef->fStart, typeDef->fContentStart,
-                typeDef->fLineCount);
-        parser.skipExact("#Typedef");
-        parser.skipWhiteSpace();
-        const char* refStart = parser.fChar;
-        parser.skipToWhiteSpace();
-        string refName(refStart, parser.fChar - refStart);
-        auto structIter = fClassMap.find(refName);
-        if (fClassMap.end() == structIter) {
-            continue;
-        }
-        for (auto& sEntry : structIter->second.fLeaves) {
-            Definition* def = &sEntry.second;
-            string name = def->fName;
-            size_t colonPos = name.find("::");
-            SkASSERT(string::npos != colonPos);
-            string typedName = defName + "::" + name.substr(colonPos + 2);
-            string ref = ParserCommon::HtmlFileName(def->fFileName) + '#' + def->fFiddle;
-            SkASSERT(fGlobalNames.fLinkMap.end() == fGlobalNames.fLinkMap.find(typedName));
-            fGlobalNames.fLinkMap[typedName] = ref;
-            SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(typedName));
-            fGlobalNames.fRefMap[typedName] = def;
-        }
-    }
-    for (auto& topic : fTopicMap) {
-        bool hasSubstitute = false;
-        for (auto& child : topic.second->fChildren) {
-            bool isAlias = MarkType::kAlias == child->fMarkType;
-            bool isSubstitute = MarkType::kSubstitute == child->fMarkType;
-            if (!isAlias && !isSubstitute) {
-                continue;
-            }
-            hasSubstitute |= isSubstitute;
-            string name(child->fContentStart, child->length());
-            if (isAlias) {
-                name = ParserCommon::ConvertRef(name, false);
-                for (auto aliasChild : child->fChildren) {
-                    if (MarkType::kSubstitute == aliasChild->fMarkType) {
-                        string sub(aliasChild->fContentStart, aliasChild->length());
-                        this->setUpSubstitute(sub, topic.second);
-                    }
-                }
-            }
-            this->setUpSubstitute(name, topic.second);
-        }
-        if (hasSubstitute) {
-            continue;
-        }
-        string lowered = this->loweredTopic(topic.first, topic.second);
-        SkDEBUGCODE(auto globalIter = fGlobalNames.fLinkMap.find(lowered));
-        SkASSERT(fGlobalNames.fLinkMap.end() == globalIter);
-        fGlobalNames.fLinkMap[lowered] =
-                ParserCommon::HtmlFileName(topic.second->fFileName) + '#' + topic.first;
-        SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(lowered));
-        fGlobalNames.fRefMap[lowered] = topic.second;
-    }
-    size_t slash = fRawFilePathDir.rfind('/');
-    size_t bslash = fRawFilePathDir.rfind('\\');
-    string spellFile;
-    if (string::npos == slash && string::npos == bslash) {
-        spellFile = fRawFilePathDir;
-    } else {
-        if (string::npos != bslash && bslash > slash) {
-            slash = bslash;
-        }
-        spellFile = fRawFilePathDir.substr(0, slash);
-    }
-    spellFile += '/';
-    spellFile += kSpellingFileName;
-    FILE* file = fopen(spellFile.c_str(), "r");
-    if (!file) {
-        SkDebugf("missing %s\n", spellFile.c_str());
-        return;
-    }
-    fseek(file, 0L, SEEK_END);
-    int sz = (int) ftell(file);
-    rewind(file);
-    char* buffer = new char[sz];
-    memset(buffer, ' ', sz);
-    int read = (int)fread(buffer, 1, sz, file);
-    SkAssertResult(read > 0);
-    sz = read;  // FIXME: ? why are sz and read different?
-    fclose(file);
-    int i = 0;
-    int start = i;
-    string last = " ";
-    string word;
-    do {
-        if (' ' < buffer[i]) {
-            ++i;
-            continue;
-        }
-        last = word;
-        word = string(&buffer[start], i - start);
-#ifdef SK_DEBUG
-        SkASSERT(last.compare(word) < 0);
-        for (char c : word) {
-            SkASSERT(islower(c) || '-' == c);
-        }
-#endif
-        if (fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(word)) {
-            fGlobalNames.fRefMap[word] = nullptr;
-        } else {
-            SkDebugf("%s ", word.c_str());  // debugging: word missing from spelling list
-        }
-        do {
-            ++i;
-        } while (i < sz && ' ' >= buffer[i]);
-        start = i;
-    } while (i < sz);
-    delete[] buffer;
-}
-
-void BmhParser::setUpSubstitutes(const Definition* parent, NameMap* names) {
-    for (const auto& child : parent->fChildren) {
-        MarkType markType = child->fMarkType;
-        if (MarkType::kAlias == markType) {
-            continue;
-        }
-        if (MarkType::kSubstitute == markType) {
-            continue;
-        }
-        string name(child->fName);
-        if (&fGlobalNames != names) {
-            size_t lastDC = name.rfind("::");
-            if (string::npos != lastDC) {
-                name = name.substr(lastDC + 2);
-            }
-            if ("" == name) {
-                continue;
-            }
-        }
-        string ref;
-        if (&fGlobalNames == names) {
-            ref = ParserCommon::HtmlFileName(child->fFileName);
-        }
-        ref += '#' + child->fFiddle;
-        if (MarkType::kClass == markType || MarkType::kStruct == markType
-                || (MarkType::kMethod == markType && !child->fClone)
-                || MarkType::kEnum == markType
-                || MarkType::kEnumClass == markType || MarkType::kConst == markType
-                || MarkType::kMember == markType || MarkType::kDefine == markType
-                || MarkType::kTypedef == markType) {
-            SkASSERT(names->fLinkMap.end() == names->fLinkMap.find(name));
-            names->fLinkMap[name] = ref;
-            SkASSERT(names->fRefMap.end() == names->fRefMap.find(name));
-            names->fRefMap[name] = child;
-        }
-        if (MarkType::kClass == markType || MarkType::kStruct == markType
-                || MarkType::kEnumClass == markType) {
-            RootDefinition* rootDef = child->asRoot();
-            NameMap* nameMap = &rootDef->fNames;
-            this->setUpSubstitutes(child, nameMap);
-            nameMap->copyToParent(names);
-        }
-        if (MarkType::kEnum == markType) {
-            this->setUpSubstitutes(child, names);
-        }
-        if (MarkType::kMethod == markType) {
-            if (child->fClone || child->fCloned) {
-                TextParser parser(child->fFileName, child->fStart, child->fContentStart,
-                        child->fLineCount);
-                parser.skipExact("#Method");
-                parser.skipSpace();
-                string name = child->methodName();
-                const char* nameInParser = parser.strnstr(name.c_str(), parser.fEnd);
-                parser.skipTo(nameInParser);
-                const char* paren = parser.strnchr('(', parser.fEnd);
-                parser.skipTo(paren);
-                parser.skipToBalancedEndBracket('(', ')');
-                if ("()" != string(paren, parser.fChar - paren)) {
-                    string fullName =
-                            trim_inline_spaces(string(nameInParser, parser.fChar - nameInParser));
-                    SkASSERT(names->fLinkMap.end() == names->fLinkMap.find(fullName));
-                    names->fLinkMap[fullName] = ref;
-                    SkASSERT(names->fRefMap.end() == names->fRefMap.find(fullName));
-                    names->fRefMap[fullName] = child;
-                }
-            }
-        }
-        if (MarkType::kSubtopic == markType) {
-            if (&fGlobalNames != names && string::npos != child->fName.find('_')) {
-                string lowered = this->loweredTopic(child->fName, child);
-                SkDEBUGCODE(auto refIter = names->fRefMap.find(lowered));
-                SkDEBUGCODE(auto iter = names->fLinkMap.find(lowered));
-                SkASSERT(names->fLinkMap.end() == iter);
-                names->fLinkMap[lowered] = '#' + child->fName;
-                SkASSERT(names->fRefMap.end() == refIter);
-                names->fRefMap[lowered] = child;
-            }
-            this->setUpSubstitutes(child, names);
-        }
-    }
-}
-
-void BmhParser::setUpPartialSubstitute(string name) {
-    auto iter = fGlobalNames.fRefMap.find(name);
-    if (fGlobalNames.fRefMap.end() != iter) {
-        SkASSERT(nullptr == iter->second);
-        return;
-    }
-    fGlobalNames.fRefMap[name] = nullptr;
-}
-
-void BmhParser::setUpSubstitute(string name, Definition* def) {
-    SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(name));
-    fGlobalNames.fRefMap[name] = def;
-    SkASSERT(fGlobalNames.fLinkMap.end() == fGlobalNames.fLinkMap.find(name));
-    string str = ParserCommon::HtmlFileName(def->fFileName) + '#' + def->fName;
-    fGlobalNames.fLinkMap[name] = str;
-    size_t stop = name.length();
-    do {
-        size_t space = name.rfind(' ', stop);
-        if (string::npos == space) {
-            break;
-        }
-        string partial = name.substr(0, space + 1);
-        stop = space - 1;
-        this->setUpPartialSubstitute(partial);
-    } while (true);
-}
-
-void BmhParser::setWrapper(Definition* def) const {
-    const char drawWrapper[] = "void draw(SkCanvas* canvas) {";
-    const char drawNoCanvas[] = "void draw(SkCanvas* ) {";
-    string text = this->extractText(def, TrimExtract::kNo);
-    size_t nonSpace = 0;
-    while (nonSpace < text.length() && ' ' >= text[nonSpace]) {
-        ++nonSpace;
-    }
-    bool hasFunc = !text.compare(nonSpace, sizeof(drawWrapper) - 1, drawWrapper);
-    bool noCanvas = !text.compare(nonSpace, sizeof(drawNoCanvas) - 1, drawNoCanvas);
-    bool hasCanvas = string::npos != text.find("SkCanvas canvas");
-    SkASSERT(!hasFunc || !noCanvas);
-    bool preprocessor = text[0] == '#';
-    bool wrapCode = !hasFunc && !noCanvas && !preprocessor;
-    if (wrapCode) {
-        def->fWrapper = hasCanvas ? string(drawNoCanvas) : string(drawWrapper);
-    }
-}
-
-RootDefinition* BmhParser::findBmhObject(MarkType markType, string typeName) {
-    const auto& mapIter = std::find_if(fMaps.begin(), fMaps.end(),
-            [markType](DefinitionMap& defMap){ return markType == defMap.fMarkType; } );
-    if (mapIter == fMaps.end()) {
-        return nullptr;
-    }
-    return &(*mapIter->fMap)[typeName];
-}
-
-// FIXME: some examples may produce different output on different platforms
-// if the text output can be different, think of how to author that
-
-bool BmhParser::findDefinitions() {
-    bool lineStart = true;
-    const char* lastChar = nullptr;
-    const char* lastMC = nullptr;
-    fParent = nullptr;
-    while (!this->eof()) {
-        if (this->peek() == fMC) {
-            lastMC = fChar;
-            this->next();
-            if (this->peek() == fMC) {
-                this->next();
-                if (!lineStart && ' ' < this->peek()) {
-                    if (!fParent || MarkType::kFormula != fParent->fMarkType) {
-                        return this->reportError<bool>("expected definition");
-                    }
-                }
-                if (this->peek() != fMC) {
-                    if (MarkType::kColumn == fParent->fMarkType) {
-                        SkASSERT(TableState::kColumnEnd == fTableState);
-                        if (!this->endTableColumn(lastChar, lastMC)) {
-                            return false;
-                        }
-                        SkASSERT(fRow);
-                        if (!this->popParentStack(fParent)) {
-                            return false;
-                        }
-                        fRow->fContentEnd = fWorkingColumn->fContentEnd;
-                        fWorkingColumn = nullptr;
-                        fRow = nullptr;
-                        fTableState = TableState::kNone;
-                    } else {
-                        vector<string> parentName;
-                        parentName.push_back(fParent->fName);
-                        if (!this->addDefinition(fChar - 1, true, fParent->fMarkType, parentName,
-                                HasTag::kNo)) {
-                            return false;
-                        }
-                    }
-                } else {
-                    SkAssertResult(this->next() == fMC);
-                    fMC = this->next();  // change markup character
-                    if (' ' >= fMC) {
-                        return this->reportError<bool>("illegal markup character");
-                    }
-                    fMarkup.emplace_front(MarkType::kMarkChar, fChar - 4, fLineCount, fParent, fMC);
-                    Definition* markChar = &fMarkup.front();
-                    markChar->fContentStart = fChar - 1;
-                    this->skipToEndBracket('\n');
-                    markChar->fContentEnd = fChar;
-                    markChar->fTerminator = fChar;
-                    fParent->fChildren.push_back(markChar);
-                }
-            } else if (this->peek() >= 'A' && this->peek() <= 'Z') {
-                const char* defStart = fChar - 1;
-                MarkType markType = this->getMarkType(MarkLookup::kRequire);
-                bool hasEnd = this->hasEndToken();
-                if (!hasEnd && fParent) {
-                    MarkType parentType = fParent->fMarkType;
-                    uint64_t parentMask = kMarkProps[(int) markType].fParentMask;
-                    if (parentMask && !(parentMask & (1LL << (int) parentType))) {
-                        return this->reportError<bool>("invalid parent");
-                    }
-                }
-                if (!this->skipName(kMarkProps[(int) markType].fName)) {
-                    return this->reportError<bool>("illegal markup character");
-                }
-                if (!this->skipSpace()) {
-                    return this->reportError<bool>("unexpected end");
-                }
-                lineStart = '\n' == this->peek();
-                bool expectEnd = true;
-                vector<string> typeNameBuilder = this->typeName(markType, &expectEnd);
-                if (fCloned && MarkType::kMethod != markType && MarkType::kExample != markType
-                        && !fAnonymous) {
-                    return this->reportError<bool>("duplicate name");
-                }
-                if (hasEnd && expectEnd) {
-                    if (fMC == this->peek()) {
-                        return this->reportError<bool>("missing body");
-                    }
-                }
-                if (!this->addDefinition(defStart, hasEnd, markType, typeNameBuilder,
-                        HasTag::kYes)) {
-                    return false;
-                }
-                continue;
-            } else if (this->peek() == ' ') {
-                if (!fParent || (MarkType::kFormula != fParent->fMarkType
-                        && MarkType::kLegend != fParent->fMarkType
-                        && MarkType::kList != fParent->fMarkType
-						&& MarkType::kLine != fParent->fMarkType
-                        && MarkType::kTable != fParent->fMarkType)) {
-                    int endHashes = this->endHashCount();
-                    if (endHashes <= 1) {
-                        if (fParent) {
-                            if (TableState::kColumnEnd == fTableState) {
-                                if (!this->endTableColumn(lastChar, lastMC)) {
-                                    return false;
-                                }
-                            } else {  // one line comment
-                                fMarkup.emplace_front(MarkType::kComment, fChar - 1, fLineCount,
-                                        fParent, fMC);
-                                Definition* comment = &fMarkup.front();
-                                comment->fContentStart = fChar - 1;
-                                this->skipToEndBracket('\n');
-                                comment->fContentEnd = fChar;
-                                comment->fTerminator = fChar;
-                                fParent->fChildren.push_back(comment);
-                            }
-                        } else {
-                            fChar = fLine + this->lineLength() - 1;
-                        }
-                    } else {  // table row
-                        if (2 != endHashes) {
-                            string errorStr = "expect ";
-                            errorStr += fMC;
-                            errorStr += fMC;
-                            return this->reportError<bool>(errorStr.c_str());
-                        }
-                        if (!fParent || MarkType::kTable != fParent->fMarkType) {
-                            return this->reportError<bool>("missing table");
-                        }
-                    }
-                } else if (TableState::kNone == fTableState) {
-                    // fixme? no nested tables for now
-                    fColStart = fChar - 1;
-                    fMarkup.emplace_front(MarkType::kRow, fColStart, fLineCount, fParent, fMC);
-                    fRow = &fMarkup.front();
-                    fRow->fName = fParent->fName;
-                    this->skipWhiteSpace();
-                    fRow->fContentStart = fChar;
-                    this->setAsParent(fRow);
-                    fTableState = TableState::kColumnStart;
-                }
-                if (TableState::kColumnStart == fTableState) {
-                    fMarkup.emplace_front(MarkType::kColumn, fColStart, fLineCount, fParent, fMC);
-                    fWorkingColumn = &fMarkup.front();
-                    fWorkingColumn->fName = fParent->fName;
-                    fWorkingColumn->fContentStart = fChar;
-                    this->setAsParent(fWorkingColumn);
-                    fTableState = TableState::kColumnEnd;
-                    continue;
-                }
-            } else if (this->peek() >= 'a' && this->peek() <= 'z') {
-                // expect zero or more letters and underscores (no spaces) then hash
-                const char* phraseNameStart = fChar;
-                this->skipPhraseName();
-                string phraseKey = string(phraseNameStart, fChar - phraseNameStart);
-                char delimiter = this->next();
-                vector<string> params;
-                vector<const char*> paramsLoc;
-                if (fMC != delimiter) {
-                    if ('(' != delimiter) {
-                        return this->reportError<bool>("expect # after phrase name");
-                    }
-                    // phrase may take comma delimited parameter list
-                    do {
-                        const char* subEnd = this->anyOf(",)\n");
-                        if (!subEnd || '\n' == *subEnd) {
-                            return this->reportError<bool>("unexpected phrase list end");
-                        }
-                        params.push_back(string(fChar, subEnd - fChar));
-                        paramsLoc.push_back(fChar);
-                        this->skipTo(subEnd);
-
-                    } while (')' != this->next());
-                }
-                const char* start = phraseNameStart;
-                SkASSERT('#' == start[-1]);
-                --start;
-                if (start > fStart && ' ' >= start[-1]) {
-                    --start;  // preserve whether to add whitespace before substitution
-                }
-                fMarkup.emplace_front(MarkType::kPhraseRef, start, fLineCount, fParent, fMC);
-                Definition* markChar = &fMarkup.front();
-                this->skipExact("#");
-                markChar->fContentStart = fChar;
-                markChar->fContentEnd = fChar;
-                markChar->fTerminator = fChar;
-                markChar->fName = phraseKey;
-                fParent->fChildren.push_back(markChar);
-                int paramLocIndex = 0;
-                for (auto param : params) {
-                    const char* paramLoc = paramsLoc[paramLocIndex++];
-                    fMarkup.emplace_front(MarkType::kPhraseParam, paramLoc, fLineCount, fParent,
-                            fMC);
-                    Definition* phraseParam = &fMarkup.front();
-                    phraseParam->fContentStart = paramLoc;
-                    phraseParam->fContentEnd = paramLoc + param.length();
-                    phraseParam->fTerminator = paramLoc + param.length();
-                    phraseParam->fName = param;
-                    markChar->fChildren.push_back(phraseParam);
-                }
-            }
-        }
-        char nextChar = this->next();
-        if (' ' < nextChar) {
-            lastChar = fChar;
-            lineStart = false;
-        } else if (nextChar == '\n') {
-            lineStart = true;
-        }
-    }
-    if (fParent) {
-        return fParent->reportError<bool>("mismatched end");
-    }
-    return true;
-}
-
-MarkType BmhParser::getMarkType(MarkLookup lookup) const {
-    for (int index = 0; index <= Last_MarkType; ++index) {
-        int typeLen = strlen(kMarkProps[index].fName);
-        if (typeLen == 0) {
-            continue;
-        }
-        if (fChar + typeLen >= fEnd || fChar[typeLen] > ' ') {
-            continue;
-        }
-        int chCompare = strncmp(fChar, kMarkProps[index].fName, typeLen);
-        if (chCompare < 0) {
-            goto fail;
-        }
-        if (chCompare == 0) {
-            return (MarkType) index;
-        }
-    }
-fail:
-    if (MarkLookup::kRequire == lookup) {
-        return this->reportError<MarkType>("unknown mark type");
-    }
-    return MarkType::kNone;
-}
-
-bool BmhParser::hasEndToken() const {
-    const char* ptr = fLine;
-    char test;
-    do {
-        if (ptr >= fEnd) {
-            return false;
-        }
-        test = *ptr++;
-        if ('\n' == test) {
-            return false;
-        }
-    } while (fMC != test || fMC != *ptr);
-    return true;
-}
-
-string BmhParser::memberName() {
-    const char* wordStart;
-    const char* prefixes[] = { "static", "const" };
-    do {
-        this->skipSpace();
-        wordStart = fChar;
-        this->skipToNonName();
-    } while (this->anyOf(wordStart, prefixes, SK_ARRAY_COUNT(prefixes)));
-    if ('*' == this->peek()) {
-        this->next();
-    }
-    return this->className(MarkType::kMember);
-}
-
-string BmhParser::methodName() {
-    if (this->hasEndToken()) {
-        if (!fParent || !fParent->fName.length()) {
-            return this->reportError<string>("missing parent method name");
-        }
-        SkASSERT(fMC == this->peek());
-        this->next();
-        SkASSERT(fMC == this->peek());
-        this->next();
-        SkASSERT(fMC != this->peek());
-        return fParent->fName;
-    }
-    string builder;
-    const char* end = this->lineEnd();
-    const char* paren = this->strnchr('(', end);
-    if (!paren) {
-        return this->reportError<string>("missing method name and reference");
-    }
-    {
-        TextParserSave endCheck(this);
-        while (end < fEnd && !this->strnchr(')', end)) {
-            fChar = end + 1;
-            end = this->lineEnd();
-        }
-        if (end >= fEnd) {
-            return this->reportError<string>("missing method end paren");
-        }
-        endCheck.restore();
-    }
-    const char* nameStart = paren;
-    char ch;
-    bool expectOperator = false;
-    bool isConstructor = false;
-    const char* nameEnd = nullptr;
-    while (nameStart > fChar && ' ' != (ch = *--nameStart)) {
-        if (!isalnum(ch) && '_' != ch) {
-            if (nameEnd) {
-                break;
-            }
-            expectOperator = true;
-            continue;
-        }
-        if (!nameEnd) {
-            nameEnd = nameStart + 1;
-        }
-    }
-    if (!nameEnd) {
-         return this->reportError<string>("unexpected method name char");
-    }
-    if (' ' == nameStart[0]) {
-        ++nameStart;
-    }
-    if (nameEnd <= nameStart) {
-        return this->reportError<string>("missing method name");
-    }
-    if (nameStart >= paren) {
-        return this->reportError<string>("missing method name length");
-    }
-    string name(nameStart, nameEnd - nameStart);
-    bool allLower = true;
-    for (int index = 0; index < (int) (nameEnd - nameStart); ++index) {
-        if (!islower(nameStart[index])) {
-            allLower = false;
-            break;
-        }
-    }
-    if (expectOperator && "operator" != name) {
-         return this->reportError<string>("expected operator");
-    }
-    const Definition* parent = this->parentSpace();
-    if (parent && parent->fName.length() > 0) {
-        size_t parentNameIndex = parent->fName.rfind(':');
-        parentNameIndex = string::npos == parentNameIndex ? 0 : parentNameIndex + 1;
-        string parentName = parent->fName.substr(parentNameIndex);
-        if (parentName == name) {
-            isConstructor = true;
-        } else if ('~' == name[0]) {
-            if (parentName != name.substr(1)) {
-                 return this->reportError<string>("expected destructor");
-            }
-            isConstructor = true;
-        }
-        builder = parent->fName + "::";
-    }
-    bool addConst = false;
-    if (isConstructor || expectOperator) {
-        paren = this->strnchr(')', end) + 1;
-        TextParserSave saveState(this);
-        this->skipTo(paren);
-        if (this->skipExact(" const")) {
-            addConst = true;
-        }
-        saveState.restore();
-    }
-    builder.append(nameStart, paren - nameStart);
-    if (addConst) {
-        builder.append(" const");
-    }
-    if (!expectOperator && allLower) {
-        builder.append("()");
-    }
-    int parens = 0;
-    while (fChar < end || parens > 0) {
-        if ('(' == this->peek()) {
-            ++parens;
-        } else if (')' == this->peek()) {
-            --parens;
-        }
-        this->next();
-    }
-    TextParserSave saveState(this);
-    this->skipWhiteSpace();
-    if (this->startsWith("const")) {
-        this->skipName("const");
-    } else {
-        saveState.restore();
-    }
-//    this->next();
-    if (string::npos != builder.find('\n')) {
-        builder.erase(std::remove(builder.begin(), builder.end(), '\n'), builder.end());
-    }
-    return uniqueRootName(builder, MarkType::kMethod);
-}
-
-const Definition* BmhParser::parentSpace() const {
-    Definition* parent = nullptr;
-    Definition* test = fParent;
-    while (test) {
-        if (MarkType::kClass == test->fMarkType ||
-                MarkType::kEnumClass == test->fMarkType ||
-                MarkType::kStruct == test->fMarkType) {
-            parent = test;
-            break;
-        }
-        test = test->fParent;
-    }
-    return parent;
-}
-
-// A full terminal statement is in the form:
-//     \n optional-white-space #MarkType white-space #[# white-space]
-//     \n optional-white-space #MarkType white-space Name white-space #[# white-space]
-// MarkType must match definition->fMarkType
-const char* BmhParser::checkForFullTerminal(const char* end, const Definition* definition) const {
-    const char* start = end;
-    while ('\n' != start[0] && start > fStart) {
-        --start;
-    }
-    SkASSERT (start < end);
-    // if end is preceeeded by \n#MarkType ## backup to there
-    TextParser parser(fFileName, start, fChar, fLineCount);
-    parser.skipWhiteSpace();
-    if (parser.eof() || fMC != parser.next()) {
-        return end;
-    }
-    const char* markName = kMarkProps[(int) definition->fMarkType].fName;
-    if (!parser.skipExact(markName)) {
-        return end;
-    }
-    parser.skipWhiteSpace();
-    TextParser startName(fFileName, definition->fStart, definition->fContentStart,
-            definition->fLineCount);
-    if ('#' == startName.next()) {
-        startName.skipToSpace();
-        if (!startName.eof() && startName.skipSpace()) {
-            const char* nameBegin = startName.fChar;
-            startName.skipToWhiteSpace();
-            string name(nameBegin, (int) (startName.fChar - nameBegin));
-            if (fMC != parser.peek() && !parser.skipExact(name.c_str())) {
-                return end;
-            }
-            parser.skipSpace();
-        }
-    }
-    if (parser.eof() || fMC != parser.next()) {
-        return end;
-    }
-    if (!parser.eof() && fMC != parser.next()) {
-        return end;
-    }
-    SkASSERT(parser.eof());
-    return start;
-}
-
-void BmhParser::parseHashAnchor(Definition* definition) {
-    this->skipToEndBracket(fMC);
-    fMarkup.emplace_front(MarkType::kLink, fChar, fLineCount, definition, fMC);
-    SkAssertResult(fMC == this->next());
-    this->skipWhiteSpace();
-    Definition* link = &fMarkup.front();
-    link->fContentStart = fChar;
-    link->fContentEnd = this->trimmedBracketEnd(fMC);
-    this->skipToEndBracket(fMC);
-    SkAssertResult(fMC == this->next());
-    SkAssertResult(fMC == this->next());
-    link->fTerminator = fChar;
-    definition->fContentEnd = link->fContentEnd;
-    definition->fTerminator = fChar;
-    definition->fChildren.emplace_back(link);
-}
-
-void BmhParser::parseHashFormula(Definition* definition) {
-    const char* start = definition->fContentStart;
-    definition->trimEnd();
-	const char* end = definition->fContentEnd;
-	fMarkup.emplace_front(MarkType::kText, start, fLineCount, definition, fMC);
-	Definition* text = &fMarkup.front();
-	text->fContentStart = start;
-	text->fContentEnd = end;
-	text->fTerminator = definition->fTerminator;
-	definition->fChildren.emplace_back(text);
-}
-
-void BmhParser::parseHashLine(Definition* definition) {
-	const char* nextLF = this->strnchr('\n', this->fEnd);
-	const char* start = fChar;
-	const char* end = this->trimmedBracketEnd(fMC);
-	this->skipToEndBracket(fMC, nextLF);
-	if (fMC != this->next() || fMC != this->next()) {
-		return this->reportError<void>("expected ## to delineate line");
-	}
-	fMarkup.emplace_front(MarkType::kText, start, fLineCount, definition, fMC);
-	Definition* text = &fMarkup.front();
-    if (!islower(start[0]) && (!isdigit(start[0])
-            || MarkType::kConst != definition->fParent->fMarkType)) {
-        return this->reportError<void>("expect lower case start");
-    }
-    string contents = string(start, end - start);
-    if (string::npos != contents.find('.')) {
-        return this->reportError<void>("expect phrase, not sentence");
-    }
-    size_t firstSpace = contents.find(' ');
-    if (string::npos == firstSpace || 0 == firstSpace || 's' != start[firstSpace - 1]) {
-        if (MarkType::kMethod == fParent->fMarkType && "experimental" != contents
-                    && "incomplete" != contents) {
-            return this->reportError<void>( "expect phrase in third person present"
-                    " tense (1st word should end in 's'");
-        }
-    }
-	text->fContentStart = start;
-	text->fContentEnd = end;
-	text->fTerminator = fChar;
-	definition->fContentEnd = text->fContentEnd;
-	definition->fTerminator = fChar;
-	definition->fChildren.emplace_back(text);
-}
-
-bool BmhParser::popParentStack(Definition* definition) {
-    if (!fParent) {
-        return this->reportError<bool>("missing parent");
-    }
-    if (definition != fParent) {
-        return this->reportError<bool>("definition end is not parent");
-    }
-    if (!definition->fStart) {
-        return this->reportError<bool>("definition missing start");
-    }
-    if (definition->fContentEnd) {
-        return this->reportError<bool>("definition already ended");
-    }
-    // more to figure out to handle table columns, at minimum
-    const char* end = fChar;
-    if (fMC != end[0]) {
-        while (end > definition->fContentStart && ' ' >= end[-1]) {
-            --end;
-        }
-        SkASSERT(&end[-1] >= definition->fContentStart && fMC == end[-1]
-                && (MarkType::kColumn == definition->fMarkType
-                || (&end[-2] >= definition->fContentStart && fMC == end[-2])));
-        end -= 2;
-    }
-    end = checkForFullTerminal(end, definition);
-    definition->fContentEnd = end;
-    definition->fTerminator = fChar;
-    fParent = definition->fParent;
-    if (!fParent || (MarkType::kTopic == fParent->fMarkType && !fParent->fParent)) {
-        fRoot = nullptr;
-    }
-    return true;
-}
-
-
-
-bool BmhParser::skipNoName() {
-    if ('\n' == this->peek()) {
-        this->next();
-        return true;
-    }
-    this->skipWhiteSpace();
-    if (fMC != this->peek()) {
-        return this->reportError<bool>("expected end mark 1");
-    }
-    this->next();
-    if (fMC != this->peek()) {
-        return this->reportError<bool>("expected end mark 2");
-    }
-    this->next();
-    return true;
-}
-
-bool BmhParser::skipToDefinitionEnd(MarkType markType) {
-    if (this->eof()) {
-        return this->reportError<bool>("missing end");
-    }
-    const char* start = fLine;
-    int startLineCount = fLineCount;
-    int stack = 1;
-    ptrdiff_t lineLen;
-    bool foundEnd = false;
-    do {
-        lineLen = this->lineLength();
-        if (fMC != *fChar++) {
-            continue;
-        }
-        if (fMC == *fChar) {
-            continue;
-        }
-        if (' ' == *fChar) {
-            continue;
-        }
-        MarkType nextType = this->getMarkType(MarkLookup::kAllowUnknown);
-        if (markType != nextType) {
-            continue;
-        }
-        bool hasEnd = this->hasEndToken();
-        if (hasEnd) {
-            if (!--stack) {
-                foundEnd = true;
-                continue;
-            }
-        } else {
-            ++stack;
-        }
-    } while ((void) ++fLineCount, (void) (fLine += lineLen), (void) (fChar = fLine),
-            !this->eof() && !foundEnd);
-    if (foundEnd) {
-        return true;
-    }
-    fLineCount = startLineCount;
-    fLine = start;
-    fChar = start;
-    return this->reportError<bool>("unbalanced stack");
-}
-
-bool BmhParser::skipToString() {
-	this->skipSpace();
-	if (fMC != this->peek()) {
-		return this->reportError<bool>("expected end mark 3");
-	}
-	this->next();
-	this->skipSpace();
-	// body is text from here to double fMC
-		// no single fMC allowed, no linefeed allowed
-	return true;
-}
-
-vector<string> BmhParser::topicName() {
-    vector<string> result;
-    this->skipWhiteSpace();
-    const char* lineEnd = fLine + this->lineLength();
-    const char* nameStart = fChar;
-    while (fChar < lineEnd) {
-        char ch = this->next();
-        SkASSERT(',' != ch);
-        if ('\n' == ch) {
-            break;
-        }
-        if (fMC == ch) {
-            break;
-        }
-    }
-    if (fChar - 1 > nameStart) {
-        string builder(nameStart, fChar - nameStart - 1);
-        trim_start_end(builder);
-        result.push_back(builder);
-    }
-    if (fChar < lineEnd && fMC == this->peek()) {
-        this->next();
-    }
-    return result;
-}
-
-// typeName parsing rules depend on mark type
-vector<string> BmhParser::typeName(MarkType markType, bool* checkEnd) {
-    fAnonymous = false;
-    fCloned = false;
-    vector<string> result;
-    string builder;
-    if (fParent) {
-        builder = fParent->fName;
-    }
-    switch (markType) {
-        case MarkType::kDefine:
-        case MarkType::kEnum:
-            // enums may be nameless
-        case MarkType::kConst:
-        case MarkType::kEnumClass:
-        case MarkType::kClass:
-        case MarkType::kStruct:
-        case MarkType::kTemplate:
-            // expect name
-            builder = this->className(markType);
-            break;
-        case MarkType::kExample:
-            // check to see if one already exists -- if so, number this one
-            builder = this->uniqueName(string(), markType);
-            this->skipNoName();
-            break;
-        case MarkType::kCode:
-        case MarkType::kDescription:
-        case MarkType::kExternal:
-        case MarkType::kFunction:
-        case MarkType::kLegend:
-        case MarkType::kList:
-        case MarkType::kNoExample:
-            this->skipNoName();
-            break;
-        case MarkType::kFormula:
-		case MarkType::kLine:
-			this->skipToString();
-			break;
-        case MarkType::kAlias:
-        case MarkType::kAnchor:
-        case MarkType::kBug:  // fixme: expect number
-        case MarkType::kDetails:
-        case MarkType::kDuration:
-        case MarkType::kFile:
-        case MarkType::kFilter:
-        case MarkType::kHeight:
-        case MarkType::kIllustration:
-        case MarkType::kImage:
-		case MarkType::kIn:
-        case MarkType::kLiteral:
-        case MarkType::kNoJustify:
-        case MarkType::kOutdent:
-        case MarkType::kPlatform:
-        case MarkType::kPopulate:
-        case MarkType::kReturn:
-        case MarkType::kSeeAlso:
-        case MarkType::kSet:
-        case MarkType::kSubstitute:
-        case MarkType::kToDo:
-        case MarkType::kVolatile:
-        case MarkType::kWidth:
-            *checkEnd = false;  // no name, may have text body
-            break;
-        case MarkType::kStdOut:
-            this->skipNoName();
-            break;  // unnamed
-        case MarkType::kMember:
-            builder = this->memberName();
-            break;
-        case MarkType::kMethod:
-            builder = this->methodName();
-            break;
-        case MarkType::kTypedef:
-            builder = this->typedefName();
-            break;
-        case MarkType::kParam:
-            // fixme: expect camelCase for param
-            builder = this->word("", "");
-            this->skipSpace();
-            *checkEnd = false;
-            break;
-        case MarkType::kPhraseDef: {
-            const char* nameEnd = this->anyOf("(\n");
-            builder = string(fChar, nameEnd - fChar);
-            this->skipLower();
-            if (fChar != nameEnd) {
-                this->reportError("expect lower case only");
-                break;
-            }
-            this->skipTo(nameEnd);
-            *checkEnd = false;
-            } break;
-        case MarkType::kTable:
-            this->skipNoName();
-            break;  // unnamed
-        case MarkType::kSubtopic:
-        case MarkType::kTopic:
-            // fixme: start with cap, allow space, hyphen, stop on comma
-            // one topic can have multiple type names delineated by comma
-            result = this->topicName();
-            if (result.size() == 0 && this->hasEndToken()) {
-                break;
-            }
-            return result;
-        default:
-            // fixme: don't allow silent failures
-            SkASSERT(0);
-    }
-    result.push_back(builder);
-    return result;
-}
-
-string BmhParser::typedefName() {
-    if (this->hasEndToken()) {
-        if (!fParent || !fParent->fName.length()) {
-            return this->reportError<string>("missing parent typedef name");
-        }
-        SkASSERT(fMC == this->peek());
-        this->next();
-        SkASSERT(fMC == this->peek());
-        this->next();
-        SkASSERT(fMC != this->peek());
-        return fParent->fName;
-    }
-    string builder;
-    const Definition* parent = this->parentSpace();
-    if (parent && parent->fName.length() > 0) {
-        builder = parent->fName + "::";
-    }
-    builder += TextParser::typedefName();
-    return uniqueRootName(builder, MarkType::kTypedef);
-}
-
-string BmhParser::uniqueName(string base, MarkType markType) {
-    string builder(base);
-    if (!builder.length()) {
-        builder = fParent->fName;
-    }
-    if (!fParent) {
-        return builder;
-    }
-    int number = 2;
-    string numBuilder(builder);
-    do {
-        for (auto& iter : fParent->fChildren) {
-            if (markType == iter->fMarkType) {
-                if (iter->fName == numBuilder) {
-                    fCloned = true;
-                    numBuilder = builder + '_' + to_string(number);
-                    goto tryNext;
-                }
-            }
-        }
-        break;
-tryNext: ;
-    } while (++number);
-    return numBuilder;
-}
-
-string BmhParser::uniqueRootName(string base, MarkType markType) {
-    auto checkName = [markType](const Definition& def, string numBuilder) -> bool {
-        return markType == def.fMarkType && def.fName == numBuilder;
-    };
-
-    string builder(base);
-    if (!builder.length()) {
-        builder = fParent->fName;
-    }
-    int number = 2;
-    string numBuilder(builder);
-    Definition* cloned = nullptr;
-    do {
-        if (fRoot) {
-            for (auto& iter : fRoot->fBranches) {
-                if (checkName(*iter.second, numBuilder)) {
-                    cloned = iter.second;
-                    goto tryNext;
-                }
-            }
-            for (auto& iter : fRoot->fLeaves) {
-                if (checkName(iter.second, numBuilder)) {
-                    cloned = &iter.second;
-                    goto tryNext;
-                }
-            }
-        } else if (fParent) {
-            for (auto& iter : fParent->fChildren) {
-                if (checkName(*iter, numBuilder)) {
-                    cloned = &*iter;
-                    goto tryNext;
-                }
-            }
-        }
-        break;
-tryNext: ;
-        if ("()" == builder.substr(builder.length() - 2)) {
-            builder = builder.substr(0, builder.length() - 2);
-        }
-        if (MarkType::kMethod == markType) {
-            cloned->fCloned = true;
-        }
-        fCloned = true;
-        numBuilder = builder + '_' + to_string(number);
-    } while (++number);
-    return numBuilder;
-}
-
-void BmhParser::validate() const {
-    for (int index = 0; index <= (int) Last_MarkType; ++index) {
-        SkASSERT(kMarkProps[index].fMarkType == (MarkType) index);
-    }
-    const char* last = "";
-    for (int index = 0; index <= (int) Last_MarkType; ++index) {
-        const char* next = kMarkProps[index].fName;
-        if (!last[0]) {
-            last = next;
-            continue;
-        }
-        if (!next[0]) {
-            continue;
-        }
-        SkASSERT(strcmp(last, next) < 0);
-        last = next;
-    }
-}
-
-string BmhParser::word(string prefix, string delimiter) {
-    string builder(prefix);
-    this->skipWhiteSpace();
-    const char* lineEnd = fLine + this->lineLength();
-    const char* nameStart = fChar;
-    while (fChar < lineEnd) {
-        char ch = this->next();
-        if (' ' >= ch) {
-            break;
-        }
-        if (',' == ch) {
-            return this->reportError<string>("no comma needed");
-            break;
-        }
-        if (fMC == ch) {
-            return builder;
-        }
-        if (!isalnum(ch) && '_' != ch && ':' != ch && '-' != ch) {
-            return this->reportError<string>("unexpected char");
-        }
-        if (':' == ch) {
-            // expect pair, and expect word to start with Sk
-            if (nameStart[0] != 'S' || nameStart[1] != 'k') {
-                return this->reportError<string>("expected Sk");
-            }
-            if (':' != this->peek()) {
-                return this->reportError<string>("expected ::");
-            }
-            this->next();
-        } else if ('-' == ch) {
-            // expect word not to start with Sk or kX where X is A-Z
-            if (nameStart[0] == 'k' && nameStart[1] >= 'A' && nameStart[1] <= 'Z') {
-                return this->reportError<string>("didn't expected kX");
-            }
-            if (nameStart[0] == 'S' && nameStart[1] == 'k') {
-                return this->reportError<string>("expected Sk");
-            }
-        }
-    }
-    if (prefix.size()) {
-        builder += delimiter;
-    }
-    builder.append(nameStart, fChar - nameStart - 1);
-    return builder;
-}
diff --git a/tools/bookmaker/bmhParser.h b/tools/bookmaker/bmhParser.h
deleted file mode 100644
index d5678d0..0000000
--- a/tools/bookmaker/bmhParser.h
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef bmhParser_DEFINED
-#define bmhParser_DEFINED
-
-#include "SkCommandLineFlags.h"
-
-#include "definition.h"
-#include "parserCommon.h"
-
-class BmhParser : public ParserCommon {
-public:
-    enum class MarkLookup {
-        kRequire,
-        kAllowUnknown,
-    };
-
-    enum class ExampleOptions {
-        kText,
-        kPng,
-        kAll
-    };
-
-    enum class Exemplary {
-        kNo,
-        kYes,
-        kOptional,
-    };
-
-    enum class TableState {
-        kNone,
-        kColumnStart,
-        kColumnEnd,
-    };
-
-    enum class HasTag {
-        kNo,
-        kYes,
-    };
-
-    enum class TrimExtract {
-        kNo,
-        kYes,
-    };
-
-    BmhParser(bool skip) : ParserCommon()
-        , fMaps {
-          { &fClassMap,    MarkType::kClass }
-        , { &fConstMap,    MarkType::kConst }
-        , { &fDefineMap,   MarkType::kDefine }
-        , { &fEnumMap,     MarkType::kEnum }
-        , { &fClassMap,    MarkType::kEnumClass }
-        , { &fMethodMap,   MarkType::kMethod }
-        , { &fClassMap,    MarkType::kStruct }
-        , { &fClassMap,    MarkType::kTemplate }
-        , { &fTypedefMap,  MarkType::kTypedef }
-        }
-        , fSkip(skip) {
-            this->reset();
-        }
-
-    ~BmhParser() override {}
-
-    bool addDefinition(const char* defStart, bool hasEnd, MarkType markType,
-            const vector<string>& typeNameBuilder, HasTag hasTag);
-    bool checkEndMarker(MarkType markType, string name) const;
-    bool checkExamples() const;
-    const char* checkForFullTerminal(const char* end, const Definition* ) const;
-    bool checkParamReturn(const Definition* definition) const;
-    bool dumpExamples(FILE* fiddleOut, Definition& def, bool* continuation) const;
-    bool dumpExamples(const char* fiddleJsonFileName) const;
-    bool checkExampleHashes() const;
-    bool childOf(MarkType markType) const;
-    string className(MarkType markType);
-    bool collectExternals();
-    int endHashCount() const;
-    bool endTableColumn(const char* end, const char* terminator);
-    bool exampleToScript(Definition*, ExampleOptions, string* result ) const;
-    string extractText(const Definition* , TrimExtract ) const;
-    RootDefinition* findBmhObject(MarkType markType, string typeName);
-    bool findDefinitions();
-    Definition* findExample(string name) const;
-    MarkType getMarkType(MarkLookup lookup) const;
-    bool hasEndToken() const;
-    static bool IsExemplary(const Definition* );
-    string loweredTopic(string name, Definition* def);
-    string memberName();
-    string methodName();
-    const Definition* parentSpace() const;
-
-    bool parseFromFile(const char* path) override {
-        if (!INHERITED::parseSetup(path)) {
-            return false;
-        }
-        fCheckMethods = !strstr(path, "undocumented.bmh");
-        return findDefinitions();
-    }
-
-    void parseHashAnchor(Definition* );
-    void parseHashFormula(Definition* );
-    void parseHashLine(Definition* );
-    bool popParentStack(Definition* );
-    void reportDuplicates(const Definition& , string dup) const;
-    void resetExampleHashes();
-
-    void reset() override {
-        INHERITED::resetCommon();
-        fRoot = nullptr;
-        fWorkingColumn = nullptr;
-        fRow = nullptr;
-        fTableState = TableState::kNone;
-        fMC = '#';
-        fInChar = false;
-        fInCharCommentString = false;
-        fInComment = false;
-        fInEnum = false;
-        fInString = false;
-        fCheckMethods = false;
-    }
-
-    void setUpGlobalSubstitutes();
-    void setUpPartialSubstitute(string name);
-    void setUpSubstitute(string name, Definition* def);
-    void setUpSubstitutes(const Definition* parent, NameMap* );
-    void setWrapper(Definition* def) const;
-    bool skipNoName();
-    bool skipToDefinitionEnd(MarkType markType);
-	bool skipToString();
-    void spellCheck(const char* match, SkCommandLineFlags::StringArray report) const;
-    void spellStatus(const char* match, SkCommandLineFlags::StringArray report) const;
-    vector<string> topicName();
-    vector<string> typeName(MarkType markType, bool* expectEnd);
-    string typedefName() override;
-    string uniqueName(string base, MarkType markType);
-    string uniqueRootName(string base, MarkType markType);
-    void validate() const;
-    string word(string prefix, string delimiter);
-
-public:
-    struct MarkProps {
-        const char* fName;
-        MarkType fMarkType;
-        Resolvable fResolve;
-        Exemplary fExemplary;  // worthy of an example
-        uint64_t fParentMask;
-    };
-
-    struct DefinitionMap {
-        unordered_map<string, RootDefinition>* fMap;
-        MarkType fMarkType;
-    };
-
-    vector<DefinitionMap> fMaps;
-
-    static MarkProps kMarkProps[Last_MarkType + 1];
-    forward_list<RootDefinition> fTopics;
-    forward_list<Definition> fMarkup;
-    forward_list<RootDefinition> fExternals;
-    vector<string> fInputFiles;
-    unordered_map<string, RootDefinition> fClassMap;
-    unordered_map<string, RootDefinition> fConstMap;
-    unordered_map<string, RootDefinition> fDefineMap;
-    unordered_map<string, RootDefinition> fEnumMap;
-    unordered_map<string, RootDefinition> fMethodMap;
-    unordered_map<string, RootDefinition> fTypedefMap;
-    unordered_map<string, Definition*> fTopicMap;
-    unordered_map<string, Definition*> fAliasMap;
-    unordered_map<string, Definition*> fPhraseMap;
-    NameMap fGlobalNames;
-    RootDefinition* fRoot;
-    Definition* fWorkingColumn;
-    Definition* fRow;
-    const char* fColStart;
-    TableState fTableState;
-    mutable char fMC;  // markup character
-    bool fAnonymous;
-    bool fCloned;
-    bool fInChar;
-    bool fInCharCommentString;
-    bool fInEnum;
-    bool fInComment;
-    bool fInString;
-    bool fCheckMethods;
-    bool fSkip = false;
-    bool fWroteOut = false;
-private:
-    typedef ParserCommon INHERITED;
-};
-
-#endif
diff --git a/tools/bookmaker/bookmaker.cpp b/tools/bookmaker/bookmaker.cpp
deleted file mode 100644
index e6cd4a1..0000000
--- a/tools/bookmaker/bookmaker.cpp
+++ /dev/null
@@ -1,410 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkOSPath.h"
-
-#include "bmhParser.h"
-#include "fiddleParser.h"
-#include "mdOut.h"
-#include "includeWriter.h"
-#include "selfCheck.h"
-
-DEFINE_string2(status, a, "", "File containing status of documentation. (Use in place of -b -i)");
-DEFINE_string2(bmh, b, "", "Path to a *.bmh file or a directory.");
-DEFINE_bool2(catalog, c, false, "Write example catalog.htm. (Requires -b -f -r)");
-DEFINE_string2(examples, e, "", "File of fiddlecli input, usually fiddle.json (For now, disables -r -f -s)");
-DEFINE_bool2(extract, E, false, "Extract examples into fiddle.json");
-DEFINE_string2(fiddle, f, "", "File of fiddlecli output, usually fiddleout.json.");
-DEFINE_bool2(hack, H, false, "Do a find/replace hack to update all *.bmh files. (Requires -b)");
-// h is reserved for help
-DEFINE_string2(include, i, "", "Path to a *.h file or a directory.");
-DEFINE_bool2(selfcheck, k, false, "Check bmh against itself. (Requires -b)");
-DEFINE_bool2(stdout, o, false, "Write file out to standard out.");
-DEFINE_bool2(populate, p, false, "Populate include from bmh. (Requires -b -i)");
-// q is reserved for quiet
-DEFINE_string2(ref, r, "", "Resolve refs and write *.md files to path. (Requires -b -f)");
-DEFINE_string2(spellcheck, s, "", "Spell-check [once, all, mispelling]. (Requires -b)");
-DEFINE_bool2(tokens, t, false, "Write bmh from include. (Requires -i)");
-DEFINE_bool2(crosscheck, x, false, "Check bmh against includes. (Requires -b -i)");
-// v is reserved for verbose
-DEFINE_bool2(validate, V, false, "Validate that all anchor references have definitions. (Requires -r)");
-DEFINE_bool2(skip, z, false, "Skip degenerate missed in legacy preprocessor.");
-
-// -b docs -i include/core/SkRect.h -f fiddleout.json -r site/user/api
-// -b docs/SkIRect_Reference.bmh -H
-/* todos:
-
-if #Subtopic contains #SeeAlso or #Example generate horizontal rule at end
-constexpr populated with filter inside subtopic does not have definition body
-
-#List needs '# content ##', formatting
-rewrap text to fit in some number of columns
-#Literal is inflexible, making the entire #Code block link-less (see $Literal in SkImageInfo)
-     would rather keep links for body above #Literal, and/or make it a block and not a one-liner
-add check to require #Const to contain #Code block if defining const or constexpr (enum consts have
-     #Code blocks inside the #Enum def)
-subclasses (e.g. Iter in SkPath) need to check for #Line and generate overview
-     subclass methods should also disallow #In
-
-It's awkward that phrase param is a child of the phrase def. Since phrase refs may also be children,
-there is special case code to skip phrase def when looking for additional substitutions in the
-phrase def. Could put it in the token list instead I guess, or make a definition subclass used
-by phrase def with an additional slot...
-
-rearrange const out for md so that const / value / short description comes first in a table,
-followed by more elaborate descriptions, examples, seealso. In md.cpp, look to see if #Subtopic
-has #Const children. If so, generate a summary table first.
-Or, only allow #Line and moderate text description in #Const. Put more verbose text, example,
-seealso, in subsequent #SubTopic. Alpha_Type does this and it looks good.
-
-IPoint is awkward. SkPoint and SkIPoint are named things; Point is a topic, which
-refers to float points or integer points. There needn't be an IPoint topic.
-One way to resolve this would be to combine SkPoint_Reference and SkIPoint_Reference into
-Point_Reference that then contains both structs (or just move SKIPoint into SkPoint_Reference).
-Most Point references would be replaced with SkPoint / SkIPoint (if that's what they mean),
-or remain Point if the text indicates the concept rather one of the C structs.
-
-see head of selfCheck.cpp for additional todos
-see head of spellCheck.cpp for additional todos
- */
-
-/*
-  class contains named struct, enum, enum-member, method, topic, subtopic
-     everything contained by class is uniquely named
-     contained names may be reused by other classes
-  method contains named parameters
-     parameters may be reused in other methods
- */
-
-
-// pass one: parse text, collect definitions
-// pass two: lookup references
-
-static int count_children(const Definition& def, MarkType markType) {
-    int count = 0;
-    if (markType == def.fMarkType) {
-        ++count;
-    }
-    for (auto& child : def.fChildren ) {
-        count += count_children(*child, markType);
-    }
-    return count;
-}
-
-int main(int argc, char** const argv) {
-    BmhParser bmhParser(FLAGS_skip);
-    bmhParser.validate();
-
-    SkCommandLineFlags::SetUsage(
-        "Common Usage: bookmaker -b path/to/bmh_files -i path/to/include.h -t\n"
-        "              bookmaker -b path/to/bmh_files -e fiddle.json\n"
-        "              ~/go/bin/fiddlecli --input fiddle.json --output fiddleout.json\n"
-        "              bookmaker -b path/to/bmh_files -f fiddleout.json -r path/to/md_files\n"
-        "              bookmaker -a path/to/status.json -x\n"
-        "              bookmaker -a path/to/status.json -p\n");
-    bool help = false;
-    for (int i = 1; i < argc; i++) {
-        if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
-            help = true;
-            for (int j = i + 1; j < argc; j++) {
-                if (SkStrStartsWith(argv[j], '-')) {
-                    break;
-                }
-                help = false;
-            }
-            break;
-        }
-    }
-    if (!help) {
-        SkCommandLineFlags::Parse(argc, argv);
-    } else {
-        SkCommandLineFlags::PrintUsage();
-        const char* const commands[] = { "", "-h", "bmh", "-h", "examples", "-h", "include",
-            "-h", "fiddle", "-h", "ref", "-h", "status", "-h", "tokens",
-            "-h", "crosscheck", "-h", "populate", "-h", "spellcheck" };
-        SkCommandLineFlags::Parse(SK_ARRAY_COUNT(commands), commands);
-        return 0;
-    }
-    bool runAll = false;
-    if (FLAGS_bmh.isEmpty() && FLAGS_include.isEmpty() && FLAGS_status.isEmpty()) {
-        FLAGS_status.set(0, "docs/status.json");
-        if (FLAGS_extract) {
-            FLAGS_examples.set(0, "fiddle.json");
-        } else {
-            FLAGS_fiddle.set(0, "fiddleout.json");
-            FLAGS_ref.set(0, "site/user/api");
-            runAll = true;
-        }
-    }
-    if (!FLAGS_bmh.isEmpty() && !FLAGS_status.isEmpty()) {
-        SkDebugf("requires -b or -a but not both\n");
-        SkCommandLineFlags::PrintUsage();
-        return 1;
-    }
-    if (!FLAGS_include.isEmpty() && !FLAGS_status.isEmpty()) {
-        SkDebugf("requires -i or -a but not both\n");
-        SkCommandLineFlags::PrintUsage();
-        return 1;
-    }
-    if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && FLAGS_catalog) {
-         SkDebugf("-c requires -b or -a\n");
-        SkCommandLineFlags::PrintUsage();
-        return 1;
-    }
-    if ((FLAGS_fiddle.isEmpty() || FLAGS_ref.isEmpty()) && FLAGS_catalog) {
-        SkDebugf("-c requires -f -r\n");
-        SkCommandLineFlags::PrintUsage();
-        return 1;
-    }
-    if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_examples.isEmpty()) {
-        SkDebugf("-e requires -b or -a\n");
-        SkCommandLineFlags::PrintUsage();
-        return 1;
-    }
-    if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_status.isEmpty() &&
-            FLAGS_populate) {
-        SkDebugf("-p requires -b -i or -a\n");
-        SkCommandLineFlags::PrintUsage();
-        return 1;
-    }
-    if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_ref.isEmpty()) {
-        SkDebugf("-r requires -b or -a\n");
-        SkCommandLineFlags::PrintUsage();
-        return 1;
-    }
-    if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_spellcheck.isEmpty()) {
-        SkDebugf("-s requires -b or -a\n");
-        SkCommandLineFlags::PrintUsage();
-        return 1;
-    }
-    if (FLAGS_include.isEmpty() && FLAGS_tokens) {
-        SkDebugf("-t requires -b -i\n");
-        SkCommandLineFlags::PrintUsage();
-        return 1;
-    }
-    if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_status.isEmpty() &&
-            FLAGS_crosscheck) {
-        SkDebugf("-x requires -b -i or -a\n");
-        SkCommandLineFlags::PrintUsage();
-        return 1;
-    }
-    bmhParser.reset();
-    if (!FLAGS_bmh.isEmpty()) {
-        if (!bmhParser.parseFile(FLAGS_bmh[0], ".bmh", ParserCommon::OneFile::kNo)) {
-            return -1;
-        }
-    } else if (!FLAGS_status.isEmpty()) {
-        if (!bmhParser.parseStatus(FLAGS_status[0], ".bmh", StatusFilter::kInProgress)) {
-            return -1;
-        }
-    }
-    if (FLAGS_hack) {
-        if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty()) {
-            SkDebugf("-H or --hack requires -a or -b\n");
-            SkCommandLineFlags::PrintUsage();
-            return 1;
-        }
-        HackParser hacker(bmhParser);
-        hacker.fDebugOut = FLAGS_stdout;
-        if (!FLAGS_status.isEmpty() && !hacker.parseStatus(FLAGS_status[0], ".bmh",
-                StatusFilter::kInProgress)) {
-            SkDebugf("hack failed\n");
-            return -1;
-        }
-        if (!FLAGS_bmh.isEmpty() && !hacker.parseFile(FLAGS_bmh[0], ".bmh",
-                ParserCommon::OneFile::kNo)) {
-            SkDebugf("hack failed\n");
-            return -1;
-        }
-        return 0;
-    }
-    if (FLAGS_selfcheck && !SelfCheck(bmhParser)) {
-        return -1;
-    }
-    if (!FLAGS_fiddle.isEmpty() && FLAGS_examples.isEmpty()) {
-        FiddleParser fparser(&bmhParser);
-        if (!fparser.parseFromFile(FLAGS_fiddle[0])) {
-            return -1;
-        }
-    }
-    if (runAll || (!FLAGS_catalog && !FLAGS_ref.isEmpty())) {
-        IncludeParser includeParser;
-        includeParser.validate();
-        if (!FLAGS_status.isEmpty() && !includeParser.parseStatus(FLAGS_status[0], ".h",
-                StatusFilter::kCompleted)) {
-            return -1;
-        }
-        if (!FLAGS_include.isEmpty() && !includeParser.parseFile(FLAGS_include[0], ".h",
-                ParserCommon::OneFile::kYes)) {
-            return -1;
-        }
-        includeParser.fDebugWriteCodeBlock = FLAGS_stdout;
-        includeParser.writeCodeBlock();
-        MdOut mdOut(bmhParser, includeParser);
-        mdOut.fDebugOut = FLAGS_stdout;
-        mdOut.fDebugWriteCodeBlock = FLAGS_stdout;
-        mdOut.fValidate = FLAGS_validate;
-        if (!FLAGS_bmh.isEmpty() && mdOut.buildReferences(FLAGS_bmh[0], FLAGS_ref[0])) {
-            bmhParser.fWroteOut = true;
-        }
-        if (!FLAGS_status.isEmpty() && mdOut.buildStatus(FLAGS_status[0], FLAGS_ref[0])) {
-            bmhParser.fWroteOut = true;
-        }
-        if (FLAGS_validate) {
-            mdOut.checkAnchors();
-        }
-    }
-    if (runAll || (FLAGS_catalog && !FLAGS_ref.isEmpty())) {
-        Catalog cparser(&bmhParser);
-        cparser.fDebugOut = FLAGS_stdout;
-        if (!FLAGS_bmh.isEmpty() && !cparser.openCatalog(FLAGS_bmh[0])) {
-            return -1;
-        }
-        if (!FLAGS_status.isEmpty() && !cparser.openStatus(FLAGS_status[0])) {
-            return -1;
-        }
-        if (!cparser.parseFile(FLAGS_fiddle[0], ".txt", ParserCommon::OneFile::kNo)) {
-            return -1;
-        }
-        if (!cparser.closeCatalog(FLAGS_ref[0])) {
-            return -1;
-        }
-        bmhParser.fWroteOut = true;
-    }
-    if (FLAGS_tokens) {
-        IncludeParser::RemoveFile(nullptr, FLAGS_include[0]);
-        IncludeParser includeParser;
-        includeParser.validate();
-        if (!includeParser.parseFile(FLAGS_include[0], ".h", ParserCommon::OneFile::kNo)) {
-            return -1;
-        }
-        includeParser.fDebugOut = FLAGS_stdout;
-        if (includeParser.dumpTokens()) {
-            bmhParser.fWroteOut = true;
-        }
-    }
-    if (runAll || FLAGS_crosscheck) {
-        IncludeParser includeParser;
-        includeParser.validate();
-        if (!FLAGS_include.isEmpty() &&
-                !includeParser.parseFile(FLAGS_include[0], ".h", ParserCommon::OneFile::kNo)) {
-            return -1;
-        }
-        if (!FLAGS_status.isEmpty() && !includeParser.parseStatus(FLAGS_status[0], ".h",
-                StatusFilter::kCompleted)) {
-            return -1;
-        }
-        if (!includeParser.crossCheck(bmhParser)) {
-            return -1;
-        }
-    }
-    if (runAll || FLAGS_populate) {
-        IncludeWriter includeWriter;
-        includeWriter.validate();
-        if (!FLAGS_include.isEmpty() &&
-                !includeWriter.parseFile(FLAGS_include[0], ".h", ParserCommon::OneFile::kNo)) {
-            return -1;
-        }
-        if (!FLAGS_status.isEmpty() && !includeWriter.parseStatus(FLAGS_status[0], ".h",
-                StatusFilter::kCompleted)) {
-            return -1;
-        }
-        includeWriter.fDebugOut = FLAGS_stdout;
-        if (!includeWriter.populate(bmhParser)) {
-            return -1;
-        }
-        bmhParser.fWroteOut = true;
-    }
-    if (!FLAGS_spellcheck.isEmpty()) {
-        if (!FLAGS_bmh.isEmpty()) {
-            bmhParser.spellCheck(FLAGS_bmh[0], FLAGS_spellcheck);
-        }
-        if (!FLAGS_status.isEmpty()) {
-            bmhParser.spellStatus(FLAGS_status[0], FLAGS_spellcheck);
-        }
-        bmhParser.fWroteOut = true;
-    }
-    if (!FLAGS_examples.isEmpty()) {
-        // check to see if examples have duplicate names
-        if (!bmhParser.checkExamples()) {
-            return -1;
-        }
-        bmhParser.fDebugOut = FLAGS_stdout;
-        if (!bmhParser.dumpExamples(FLAGS_examples[0])) {
-            return -1;
-        }
-        return 0;
-    }
-    if (!bmhParser.fWroteOut) {
-        int examples = 0;
-        int methods = 0;
-        int topics = 0;
-        for (const auto& topic : bmhParser.fTopicMap) {
-            if (topic.second->fParent) {
-                continue;
-            }
-            examples += count_children(*topic.second, MarkType::kExample);
-            methods += count_children(*topic.second, MarkType::kMethod);
-            topics += count_children(*topic.second, MarkType::kSubtopic);
-            topics += count_children(*topic.second, MarkType::kTopic);
-        }
-        SkDebugf("topics=%d classes=%d methods=%d examples=%d\n",
-                bmhParser.fTopicMap.size(), bmhParser.fClassMap.size(),
-                methods, examples);
-    }
-    return 0;
-}
-
-void NameMap::copyToParent(NameMap* parent) const {
-    size_t colons = fName.rfind("::");
-    string topName = string::npos == colons ? fName : fName.substr(colons + 2);
-    for (auto& entry : fRefMap) {
-        string scoped = topName + "::" + entry.first;
-        SkASSERT(parent->fRefMap.end() == parent->fRefMap.find(scoped));
-        parent->fRefMap[scoped] = entry.second;
-        auto scopedLinkIter = fLinkMap.find(entry.first);
-        if (fLinkMap.end() != scopedLinkIter) {
-            SkASSERT(parent->fLinkMap.end() == parent->fLinkMap.find(scoped));
-            parent->fLinkMap[scoped] = scopedLinkIter->second;
-        }
-    }
-}
-
-void NameMap::setParams(Definition* bmhDef, Definition* iMethod) {
-    Definition* pParent = bmhDef->csParent();
-    string parentName;
-    if (pParent) {
-        parentName = pParent->fName + "::";
-        fParent = &pParent->asRoot()->fNames;
-    }
-    fName = parentName + iMethod->fName;
-    TextParser methParams(iMethod);
-    for (auto& param : iMethod->fTokens) {
-        if (MarkType::kComment != param.fMarkType) {
-            continue;
-        }
-        TextParser paramParser(&param);
-        if (!paramParser.skipExact("@param ")) { // write parameters, if any
-            continue;
-        }
-        paramParser.skipSpace();
-        const char* start = paramParser.fChar;
-        paramParser.skipToSpace();
-        string paramName(start, paramParser.fChar - start);
-    #ifdef SK_DEBUG
-        for (char c : paramName) {
-            SkASSERT(isalnum(c) || '_' == c);
-        }
-    #endif
-        if (!methParams.containsWord(paramName.c_str(), methParams.fEnd, nullptr)) {
-            param.reportError<void>("mismatched param name");
-        }
-        fRefMap[paramName] = &param;
-        fLinkMap[paramName] = '#' + bmhDef->fFiddle + '_' + paramName;
-    }
-}
-
diff --git a/tools/bookmaker/bookmaker.h b/tools/bookmaker/bookmaker.h
deleted file mode 100644
index 72e2503..0000000
--- a/tools/bookmaker/bookmaker.h
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef bookmaker_DEFINED
-#define bookmaker_DEFINED
-
-#include <algorithm>
-#include <cmath>
-#include <cctype>
-#include <cstring>
-#include <forward_list>
-#include <list>
-#include <sstream>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-#include "SkTypes.h"
-
-using std::forward_list;
-using std::list;
-using std::string;
-using std::unordered_map;
-using std::vector;
-
-class Definition;
-
-class NonAssignable {
-public:
-    NonAssignable(NonAssignable const&) = delete;
-    NonAssignable& operator=(NonAssignable const&) = delete;
-    NonAssignable() {}
-};
-
-#define FPRINTF(...)                \
-    if (fDebugOut) {                \
-        SkDebugf(__VA_ARGS__);      \
-    }                               \
-    fprintf(fOut, __VA_ARGS__)
-
-// std::to_string isn't implemented on android
-template <typename T>
-string to_string(T value)
-{
-    std::ostringstream os ;
-    os << value ;
-    return os.str() ;
-}
-
-enum class KeyWord {
-    kNone,
-    kSK_API,
-    kSK_BEGIN_REQUIRE_DENSE,
-    kAlignAs,
-    kBool,
-    kChar,
-    kClass,
-    kConst,
-    kConstExpr,
-    kDefine,
-    kDouble,
-    kElif,
-    kElse,
-    kEndif,
-    kEnum,
-    kError,
-    kFloat,
-    kFriend,
-    kIf,
-    kIfdef,
-    kIfndef,
-    kInclude,
-    kInline,
-    kInt,
-    kOperator,
-    kPrivate,
-    kProtected,
-    kPublic,
-    kSigned,
-    kSize_t,
-    kStatic,
-    kStruct,
-    kTemplate,
-    kTypedef,
-    kTypename,
-    kUint16_t,
-    kUint32_t,
-    kUint64_t,
-    kUint8_t,
-    kUintPtr_t,
-    kUnion,
-    kUnsigned,
-    kUsing,
-    kVoid,
-};
-
-enum class MarkType {
-    kNone,
-    kAnchor,
-    kAlias,
-    kBug,
-    kClass,
-    kCode,
-    kColumn,
-    kComment,
-    kConst,
-    kDefine,
-    kDescription,
-    kDetails,  // used by #Const to specify #Subtopic details with examples and so on
-    kDuration,
-    kEnum,
-    kEnumClass,
-    kExample,
-    kExternal,
-    kFile,
-    kFilter,
-    kFormula,
-    kFunction,
-    kHeight,
-    kIllustration,
-    kImage,
-	kIn,
-    kLegend,
-	kLine,
-    kLink,     // used internally by #Anchor
-    kList,
-    kLiteral,  // don't lookup hyperlinks, do substitution, etc
-    kMarkChar,
-    kMember,
-    kMethod,
-    kNoExample,
-    kNoJustify, // don't contribute this #Line to tabular comment measure, even if it fits
-    kOutdent,
-    kParam,
-    kPhraseDef,
-    kPhraseParam,
-    kPhraseRef,
-    kPlatform,
-    kPopulate,
-    kReturn,
-    kRow,
-    kSeeAlso,
-    kSet,
-    kStdOut,
-    kStruct,
-    kSubstitute,
-    kSubtopic,
-    kTable,
-    kTemplate,
-    kText,
-    kToDo,
-    kTopic,
-    kTypedef,
-    kUnion,
-    kUsing,
-    kVolatile,
-    kWidth,
-};
-
-enum {
-    Last_MarkType = (int) MarkType::kWidth,
-};
-
-enum class Bracket {
-    kNone,
-    kParen,
-    kSquare,
-    kBrace,
-    kAngle,
-    kString,
-    kChar,
-    kSlashStar,
-    kSlashSlash,
-    kPound,
-    kColon,
-    kDebugCode,  // parens get special treatment so SkDEBUGCODE( isn't treated as method
-};
-
-enum class Punctuation {  // catch-all for misc symbols tracked in C
-    kNone,
-    kAsterisk,  // for pointer-to
-    kSemicolon,  // e.g., to delinate xxx() const ; const int* yyy()
-    kLeftBrace,
-    kColon,     // for foo() : bar(1), baz(2) {}
-};
-
-enum class KeyProperty {
-    kNone,
-    kClassSection,
-    kFunction,
-    kModifier,
-    kNumber,
-    kObject,
-    kPreprocessor,
-};
-
-struct IncludeKey {
-    const char* fName;
-    KeyWord fKeyWord;
-    KeyProperty fProperty;
-};
-
-extern const IncludeKey kKeyWords[];
-
-struct NameMap {
-    void copyToParent(NameMap* parent) const;
-    void setParams(Definition* bmhDef, Definition* iMethod);
-
-    string fName;
-    NameMap* fParent = nullptr;
-    unordered_map<string, string> fLinkMap;   // from SkRect to #Rect
-    // ref map includes "xxx", "xxx ", "xxx yyy", "xxx zzz", etc.
-    unordered_map<string, Definition*> fRefMap;    // e.g., from #Substitute entry to #Topic entry
-};
-
-enum class Resolvable {
-    kNo,      // neither resolved nor output
-    kYes,     // resolved, output
-    kOut,     // mostly resolved, output (FIXME: is this really different from kYes?)
-    kCode,    // resolve methods as they are used, not as they are prototyped
-    kFormula, // kCode, plus make most spaces non-breaking
-    kLiteral, // output untouched
-	kClone,   // resolved, output, with references to clones as well
-    kSimple,  // resolve simple words (used to resolve method declarations)
-    kInclude, // like simple, plus reverse resolve SkXXX to XXX
-};
-
-#endif
diff --git a/tools/bookmaker/cataloger.cpp b/tools/bookmaker/cataloger.cpp
deleted file mode 100644
index 6156fcb..0000000
--- a/tools/bookmaker/cataloger.cpp
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "bmhParser.h"
-#include "fiddleParser.h"
-
-#include "SkOSFile.h"
-#include "SkOSPath.h"
-
-const string kCatalogFileName("catalog.htm");
-
-bool Catalog::appendFile(string path) {
-    FILE* file = fopen(path.c_str(), "r");
-    if (!file) {
-        SkDebugf("could not append %s\n", path.c_str());
-        return false;
-    }
-    fseek(file, 0L, SEEK_END);
-    int sz = (int) ftell(file);
-    rewind(file);
-    char* buffer = new char[sz];
-    memset(buffer, ' ', sz);
-    SkAssertResult(sz == (int)fread(buffer, 1, sz, file));
-    fclose(file);
-    this->writeBlock(sz, buffer);
-    return true;
-}
-
-bool Catalog::openCatalog(const char* inDir) {
-    fDocsDir = inDir;
-    if ('/' != fDocsDir.back()) {
-        fDocsDir += '/';
-    }
-    fOut = fopen(kCatalogFileName.c_str(), "wb");
-    fFullName = kCatalogFileName;
-    if (!fOut) {
-        SkDebugf("could not open output file %s\n", kCatalogFileName.c_str());
-        return false;
-    }
-    fContinuation = false;
-    if (!appendFile(fDocsDir + "catalogHeader.txt")) {
-        return false;
-    }
-    this->lf(1);
-
-    return true;
-}
-
-bool Catalog::openStatus(const char* statusFile) {
-    StatusIter iter(statusFile, ".bmh", StatusFilter::kInProgress);
-    // FIXME: iterate through only chosen files by setting fDocsDir to iter
-    // read one file to find directory
-    if (!iter.next(nullptr, nullptr)) {
-        return false;
-    }
-    return openCatalog(iter.baseDir().c_str());
-}
-
-bool Catalog::closeCatalog(const char* outDir) {
-    if (fOut) {
-        this->lf(1);
-        this->writeString("}");
-        this->lf(1);
-        if (!appendFile(fDocsDir + "catalogTrailer.txt")) {
-            return false;
-        }
-        this->lf(1);
-        this->writePending();
-        fclose(fOut);
-        string outie = outDir;
-        if ('/' != outie.back()) {
-            outie += '/';
-        }
-        string fullName = outie + kCatalogFileName;
-        if (ParserCommon::WrittenFileDiffers(fullName, kCatalogFileName)) {
-            ParserCommon::CopyToFile(fullName, kCatalogFileName);
-            SkDebugf("wrote %s\n", fullName.c_str());
-        } else {
-            remove(kCatalogFileName.c_str());
-        }
-        fOut = nullptr;
-    }
-    return true;
-}
-
-bool Catalog::parseFromFile(const char* path) {
-    if (!INHERITED::parseFromFile(path)) {
-        return false;
-    }
-    fIndent = 4;
-    this->writeString("var text = {");
-    this->lf(1);
-    fTextOut = true;
-    if (!parseFiddles()) {
-        return false;
-    }
-    this->lf(1);
-    this->writeString("}");
-    this->lf(2);
-    this->writeString("var pngs = {");
-    fTextOut = false;
-    fPngOut = true;
-    JsonStatus* status = &fStack.back();
-    status->reset();
-    fContinuation = false;
-    return parseFiddles();
-}
-
-bool Catalog::pngOut(Definition* example) {
-    string result;
-    if (!fBmhParser->exampleToScript(example, BmhParser::ExampleOptions::kPng, &result)) {
-        return false;
-    }
-    if (result.length() > 0) {
-        if (fContinuation) {
-            this->writeString(",");
-            this->lf(1);
-        } else {
-            fContinuation = true;
-        }
-        this->writeBlock(result.size(), result.c_str());
-    }
-    return true;
-}
-
-bool Catalog::textOut(Definition* def, const char* stdOutStart,
-    const char* stdOutEnd) {
-    string result;
-    if (!fBmhParser->exampleToScript(def, BmhParser::ExampleOptions::kText, &result)) {
-        return false;
-    }
-    if (result.length() > 0) {
-        if (fContinuation) {
-            this->writeString(",");
-            this->lf(1);
-        } else {
-            fContinuation = true;
-        }
-        fIndent = 8;
-        this->writeBlock(result.size(), result.c_str());
-        this->lf(1);
-        this->writeString("\"stdout\": \"");
-        size_t pos = 0;
-        size_t len = stdOutEnd - stdOutStart;
-        string example;
-        const Definition* stdOut = def->hasChild(MarkType::kStdOut);
-        const Definition* outVolatile = stdOut ? stdOut->hasChild(MarkType::kVolatile) : nullptr;
-        if (outVolatile) {
-            stdOutStart = outVolatile->fContentStart;
-            while ('\n' == stdOutStart[0]) {
-                ++stdOutStart;
-            }
-            len = stdOut->fContentEnd - stdOutStart;
-        }
-        while ((size_t) pos < len) {
-            example += '"' == stdOutStart[pos] ? "\\\"" :
-                '\\' == stdOutStart[pos] ? "\\\\" :
-                '\n' == stdOutStart[pos] ? "\\\\n" :
-                string(&stdOutStart[pos], 1);
-            if (outVolatile && '\n' == stdOutStart[pos]) {
-                ++pos;
-                while ((size_t) pos < len && ' ' == stdOutStart[pos]) {
-                    ++pos;
-                }
-                continue;
-            }
-            ++pos;
-        }
-        if (outVolatile) {
-            example += "\\\\n";
-        }
-        this->writeBlock(example.length(), example.c_str());
-        this->writeString("\"");
-        this->lf(1);
-        fIndent = 4;
-        this->writeString("}");
-    }
-    return true;
-}
diff --git a/tools/bookmaker/definition.cpp b/tools/bookmaker/definition.cpp
deleted file mode 100644
index f693fd8..0000000
--- a/tools/bookmaker/definition.cpp
+++ /dev/null
@@ -1,1227 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkOSPath.h"
-
-#include "definition.h"
-#include "textParser.h"
-
-#ifdef CONST
-#undef CONST
-#endif
-
-#ifdef FRIEND
-#undef FRIEND
-#endif
-
-#ifdef BLANK
-#undef BLANK
-#endif
-
-#ifdef ANY
-#undef ANY
-#endif
-
-#ifdef DEFOP
-#undef DEFOP
-#endif
-
-#define CONST 1
-#define STATIC 2
-#define BLANK  0
-#define ANY -1
-#define DEFOP Definition::Operator
-
-enum class OpType : int8_t {
-    kNone,
-    kVoid,
-    kBool,
-    kChar,
-    kInt,
-    kScalar,
-    kSizeT,
-    kThis,
-    kAny,
-};
-
-enum class OpMod : int8_t {
-    kNone,
-    kArray,
-    kMove,
-    kPointer,
-    kReference,
-    kAny,
-};
-
-const struct OperatorParser {
-    DEFOP fOperator;
-    const char* fSymbol;
-    const char* fName;
-    int8_t fFriend;
-    OpType fReturnType;
-    OpMod fReturnMod;
-    int8_t fConstMethod;
-    struct Param {
-        int8_t fConst;
-        OpType fType;
-        OpMod fMod;
-    } fParams[2];
-} opData[] = {
-    { DEFOP::kUnknown, "??", "???",    BLANK,  OpType::kNone,   OpMod::kNone,         BLANK,
-                                    { } },
-    { DEFOP::kAdd,     "+",  "add",    BLANK,  OpType::kThis,   OpMod::kNone,         BLANK,
-                                    {{ CONST,  OpType::kThis,   OpMod::kReference, },
-                                     { CONST,  OpType::kThis,   OpMod::kReference, }}},
-    { DEFOP::kAddTo,   "+=", "addto",  BLANK,  OpType::kVoid,   OpMod::kNone,         BLANK,
-                                    {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
-    { DEFOP::kAddTo,   "+=", "addto1", BLANK,  OpType::kThis,   OpMod::kReference,    BLANK,
-                                    {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
-    { DEFOP::kAddTo,   "+=", "addto2", BLANK,  OpType::kThis,   OpMod::kReference,    BLANK,
-                                    {{ CONST,  OpType::kChar,   OpMod::kArray, }}},
-    { DEFOP::kAddTo,   "+=", "addto3", BLANK,  OpType::kThis,   OpMod::kReference,    BLANK,
-                                    {{ CONST,  OpType::kChar,   OpMod::kNone, }}},
-    { DEFOP::kArray,   "[]", "array",  BLANK,  OpType::kScalar, OpMod::kNone,         CONST,
-                                    {{ BLANK,  OpType::kInt,    OpMod::kNone, }}},
-    { DEFOP::kArray,   "[]", "array1", BLANK,  OpType::kScalar, OpMod::kReference,    BLANK,
-                                    {{ BLANK,  OpType::kInt,    OpMod::kNone, }}},
-    { DEFOP::kArray,   "[]", "array2", BLANK,  OpType::kChar,   OpMod::kNone,         CONST,
-                                    {{ BLANK,  OpType::kSizeT,  OpMod::kNone, }}},
-    { DEFOP::kArray,   "[]", "array3", BLANK,  OpType::kChar,   OpMod::kReference,    BLANK,
-                                    {{ BLANK,  OpType::kSizeT,  OpMod::kNone, }}},
-    { DEFOP::kCast,    "()", "cast",   BLANK,  OpType::kAny,    OpMod::kAny,          ANY,
-                                    {{ ANY,    OpType::kAny,    OpMod::kAny,  }}},
-    { DEFOP::kCopy,    "=",  "copy",   BLANK,  OpType::kThis,   OpMod::kReference,    BLANK,
-                                    {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
-    { DEFOP::kCopy,    "=",  "copy1",  BLANK,  OpType::kThis,   OpMod::kReference,    BLANK,
-                                    {{ CONST,  OpType::kChar,   OpMod::kArray, }}},
-    { DEFOP::kDelete,  "delete", "delete",  BLANK,  OpType::kVoid,   OpMod::kNone,    BLANK,
-                                    {{ BLANK,  OpType::kVoid,   OpMod::kPointer, }}},
-    { DEFOP::kDereference, "->", "deref",  ANY,  OpType::kThis, OpMod::kPointer,      CONST,
-                                    { } },
-    { DEFOP::kDereference, "*", "deref", BLANK,  OpType::kThis, OpMod::kReference,    CONST,
-                                    { } },
-    { DEFOP::kEqual,   "==", "equal",  BLANK,  OpType::kBool,   OpMod::kNone,         BLANK,
-                                    {{ CONST,  OpType::kThis,   OpMod::kReference, },
-                                     { CONST,  OpType::kThis,   OpMod::kReference, }}},
-    { DEFOP::kEqual,   "==", "equal1",  BLANK,  OpType::kBool,   OpMod::kNone,         CONST,
-                                    {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
-    { DEFOP::kEqual,   "==", "equal2", ANY,    OpType::kBool,   OpMod::kNone,         BLANK,
-                                    {{ CONST,  OpType::kThis,   OpMod::kReference, },
-                                     { CONST,  OpType::kThis,   OpMod::kReference, }}},
-    { DEFOP::kMinus,   "-",  "minus",  BLANK,  OpType::kThis,   OpMod::kNone,         CONST,
-                                    { } },
-    { DEFOP::kMove,    "=",  "move",   BLANK,  OpType::kThis,   OpMod::kReference,    BLANK,
-                                    {{ BLANK,  OpType::kThis,   OpMod::kMove, }}},
-    { DEFOP::kMultiply, "*", "multiply", BLANK, OpType::kThis, OpMod::kNone,         CONST,
-                                    {{ BLANK,  OpType::kScalar, OpMod::kNone, }}},
-    { DEFOP::kMultiply, "*", "multiply1", BLANK, OpType::kThis, OpMod::kNone,         CONST,
-                                    {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
-    { DEFOP::kMultiply, "*", "multiply2", BLANK, OpType::kThis, OpMod::kNone,         BLANK,
-                                    {{ CONST,  OpType::kThis,   OpMod::kReference, },
-                                     { CONST,  OpType::kThis,   OpMod::kReference, }}},
-    { DEFOP::kMultiplyBy, "*=", "multiplyby", BLANK,  OpType::kThis, OpMod::kReference, BLANK,
-                                    {{ BLANK,  OpType::kScalar, OpMod::kNone, }}},
-    { DEFOP::kNew,     "new", "new",   BLANK,  OpType::kVoid,   OpMod::kPointer,      BLANK,
-                                    {{ BLANK,  OpType::kSizeT,  OpMod::kNone, }}},
-    { DEFOP::kNotEqual, "!=", "notequal", BLANK, OpType::kBool,   OpMod::kNone,      BLANK,
-                                    {{ CONST,  OpType::kThis,   OpMod::kReference, },
-                                     { CONST,  OpType::kThis,   OpMod::kReference, }}},
-    { DEFOP::kNotEqual, "!=", "notequal1", BLANK,  OpType::kBool,   OpMod::kNone,     CONST,
-                                    {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
-    { DEFOP::kNotEqual, "!=", "notequal2", ANY, OpType::kBool,   OpMod::kNone,     BLANK,
-                                    {{ CONST,  OpType::kThis,   OpMod::kReference, },
-                                     { CONST,  OpType::kThis,   OpMod::kReference, }}},
-    { DEFOP::kSubtract, "-", "subtract", BLANK, OpType::kThis, OpMod::kNone,         BLANK,
-                                    {{ CONST,  OpType::kThis,   OpMod::kReference, },
-                                     { CONST,  OpType::kThis,   OpMod::kReference, }}},
-    { DEFOP::kSubtractFrom, "-=", "subtractfrom",  BLANK,  OpType::kVoid,   OpMod::kNone, BLANK,
-                                    {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
-};
-
-OpType lookup_type(string typeWord, string name) {
-    if (typeWord == name || (typeWord == "SkIVector" && name == "SkIPoint")
-                         || (typeWord == "SkVector" && name == "SkPoint")) {
-        return OpType::kThis;
-    }
-    if ("float" == typeWord || "double" == typeWord) {
-        return OpType::kScalar;
-    }
-    const char* keyWords[] = { "void", "bool", "char", "int", "SkScalar", "size_t" };
-    for (unsigned i = 0; i < SK_ARRAY_COUNT(keyWords); ++i) {
-        if (typeWord == keyWords[i]) {
-            return (OpType) (i + 1);
-        }
-    }
-    return OpType::kNone;
-}
-
-OpMod lookup_mod(TextParser& iParser) {
-    OpMod mod = OpMod::kNone;
-    if ('&' == iParser.peek()) {
-        mod = OpMod::kReference;
-        iParser.next();
-        if ('&' == iParser.peek()) {
-            mod = OpMod::kMove;
-            iParser.next();
-        }
-    } else if ('*' == iParser.peek()) {
-        mod = OpMod::kPointer;
-        iParser.next();
-    }
-    iParser.skipWhiteSpace();
-    return mod;
-}
-
-bool Definition::parseOperator(size_t doubleColons, string& result) {
-    const char operatorStr[] = "operator";
-    size_t opPos = fName.find(operatorStr, doubleColons);
-    if (string::npos == opPos) {
-        return false;
-    }
-    string className(fName, 0, doubleColons - 2);
-    TextParser iParser(fFileName, fStart, fContentStart, fLineCount);
-    SkAssertResult(iParser.skipWord("#Method"));
-    iParser.skipWhiteSpace();
-    bool isStatic = iParser.skipExact("static");
-    iParser.skipWhiteSpace();
-    bool returnsConst = iParser.skipExact("const");
-    if (returnsConst) {
-        SkASSERT(0);  // incomplete
-    }
-    SkASSERT(isStatic == false || returnsConst == false);
-    iParser.skipWhiteSpace();
-    const char* returnTypeStart = iParser.fChar;
-    iParser.skipToNonName();
-    SkASSERT(iParser.fChar > returnTypeStart);
-    string returnType(returnTypeStart, iParser.fChar - returnTypeStart);
-    OpType returnOpType = lookup_type(returnType, className);
-    iParser.skipWhiteSpace();
-    OpMod returnMod = lookup_mod(iParser);
-    SkAssertResult(iParser.skipExact("operator"));
-    iParser.skipWhiteSpace();
-    fMethodType = Definition::MethodType::kOperator;
-    TextParserSave save(&iParser);
-    for (auto parser : opData) {
-        save.restore();
-        if (!iParser.skipExact(parser.fSymbol)) {
-            continue;
-        }
-        iParser.skipWhiteSpace();
-        if ('(' != iParser.peek()) {
-            continue;
-        }
-        if (parser.fFriend != ANY && (parser.fFriend == STATIC) != isStatic) {
-            continue;
-        }
-        if (parser.fReturnType != OpType::kAny && parser.fReturnType != returnOpType) {
-            continue;
-        }
-        if (parser.fReturnMod != OpMod::kAny && parser.fReturnMod != returnMod) {
-            continue;
-        }
-        iParser.next();  // skip '('
-        iParser.skipWhiteSpace();
-        int parserCount = (parser.fParams[0].fType != OpType::kNone) +
-            (parser.fParams[1].fType != OpType::kNone);
-        bool countsMatch = true;
-        for (int pIndex = 0; pIndex < 2; ++pIndex) {
-            if (')' == iParser.peek()) {
-                countsMatch = pIndex == parserCount;
-                break;
-            }
-            if (',' == iParser.peek()) {
-                iParser.next();
-                iParser.skipWhiteSpace();
-            }
-            bool paramConst = iParser.skipExact("const");
-            if (parser.fParams[pIndex].fConst != ANY &&
-                    paramConst != (parser.fParams[pIndex].fConst == CONST)) {
-                countsMatch = false;
-                break;
-            }
-            iParser.skipWhiteSpace();
-            const char* paramStart = iParser.fChar;
-            iParser.skipToNonName();
-            SkASSERT(iParser.fChar > paramStart);
-            string paramType(paramStart, iParser.fChar - paramStart);
-            OpType paramOpType = lookup_type(paramType, className);
-            if (parser.fParams[pIndex].fType != OpType::kAny &&
-                    parser.fParams[pIndex].fType != paramOpType) {
-                countsMatch = false;
-                break;
-            }
-            iParser.skipWhiteSpace();
-            OpMod paramMod = lookup_mod(iParser);
-            if (parser.fParams[pIndex].fMod != OpMod::kAny &&
-                    parser.fParams[pIndex].fMod != paramMod) {
-                countsMatch = false;
-                break;
-            }
-            iParser.skipToNonName();
-            if ('[' == iParser.peek()) {
-                paramMod = OpMod::kArray;
-                SkAssertResult(iParser.skipExact("[]"));
-            }
-            iParser.skipWhiteSpace();
-        }
-        if (!countsMatch) {
-            continue;
-        }
-        if (')' != iParser.peek()) {
-            continue;
-        }
-        iParser.next();
-        bool constMethod = iParser.skipExact(" const");
-        if (parser.fConstMethod != ANY && (parser.fConstMethod == CONST) != constMethod) {
-            continue;
-        }
-        result += parser.fName;
-        result += "_operator";
-        fOperator = parser.fOperator;
-        fOperatorConst = constMethod;
-        return true;
-    }
-    SkASSERT(0); // incomplete
-    return false;
-}
-
-#undef CONST
-#undef FRIEND
-#undef BLANK
-#undef DEFOP
-
-bool Definition::boilerplateIfDef() {
-    const Definition& label = fTokens.front();
-    if (Type::kWord != label.fType) {
-        return false;
-    }
-    fName = string(label.fContentStart, label.fContentEnd - label.fContentStart);
-    return true;
-}
-
-
-// fixme: this will need to be more complicated to handle all of Skia
-// for now, just handle paint -- maybe fiddle will loosen naming restrictions
-void Definition::setCanonicalFiddle() {
-    fMethodType = Definition::MethodType::kNone;
-    size_t doubleColons = fName.rfind("::");
-    SkASSERT(string::npos != doubleColons);
-    string base = fName.substr(0, doubleColons);
-    string result = base + "_";
-    doubleColons += 2;
-    if (string::npos != fName.find('~', doubleColons)) {
-        fMethodType = Definition::MethodType::kDestructor;
-        result += "destructor";
-    } else if (!this->parseOperator(doubleColons, result)) {
-        bool isMove = string::npos != fName.find("&&", doubleColons);
-        size_t parens = fName.find("()", doubleColons);
-        if (string::npos != parens) {
-            string methodName = fName.substr(doubleColons, parens - doubleColons);
-            do {
-                size_t nextDouble = methodName.find("::");
-                if (string::npos == nextDouble) {
-                    break;
-                }
-                base = methodName.substr(0, nextDouble);
-                result += base + '_';
-                methodName = methodName.substr(nextDouble + 2);
-                doubleColons += nextDouble + 2;
-            } while (true);
-            if (base == methodName) {
-                fMethodType = Definition::MethodType::kConstructor;
-                result += "empty_constructor";
-            } else {
-                result += fName.substr(doubleColons, fName.length() - doubleColons - 2);
-            }
-        } else {
-            size_t openParen = fName.find('(', doubleColons);
-            if (string::npos == openParen) {
-                result += fName.substr(doubleColons);
-                // see if it is a constructor -- if second to last delimited name equals last
-                size_t nextColons = fName.find("::", doubleColons);
-                if (string::npos != nextColons) {
-                    nextColons += 2;
-                    if (!strncmp(&fName[doubleColons], &fName[nextColons],
-                            nextColons - doubleColons - 2)) {
-                        fMethodType = Definition::MethodType::kConstructor;
-                    }
-                }
-            } else {
-                size_t comma = fName.find(',', doubleColons);
-                if (string::npos == comma) {
-                    result += isMove ? "move_" : "copy_";
-                }
-                fMethodType = Definition::MethodType::kConstructor;
-                // name them by their param types,
-                //   e.g. SkCanvas__int_int_const_SkSurfaceProps_star
-                // TODO: move forward until parens are balanced and terminator =,)
-                TextParser params("", &fName[openParen] + 1, &*fName.end(), 0);
-                bool underline = false;
-                while (!params.eof()) {
-//                    SkDEBUGCODE(const char* end = params.anyOf("(),="));  // unused for now
-//                    SkASSERT(end[0] != '(');  // fixme: put off handling nested parentheseses
-                    if (params.startsWith("const") || params.startsWith("int")
-                            || params.startsWith("Sk")) {
-                        const char* wordStart = params.fChar;
-                        params.skipToNonName();
-                        if (underline) {
-                            result += '_';
-                        } else {
-                            underline = true;
-                        }
-                        result += string(wordStart, params.fChar - wordStart);
-                    } else {
-                        params.skipToNonName();
-                    }
-                    if (!params.eof() && '*' == params.peek()) {
-                        if (underline) {
-                            result += '_';
-                        } else {
-                            underline = true;
-                        }
-                        result += "star";
-                        params.next();
-                        params.skipSpace();
-                    }
-                    params.skipToAlpha();
-                }
-            }
-        }
-    }
-    fFiddle = Definition::NormalizedName(result);
-}
-
-static void space_pad(string* str) {
-    size_t len = str->length();
-    if (len == 0) {
-        return;
-    }
-    char last = (*str)[len - 1];
-    if ('~' == last || ' ' >= last) {
-        return;
-    }
-    *str += ' ';
-}
-
-//start here;
-// see if it possible to abstract this a little bit so it can
-// additionally be used to find params and return in method prototype that
-// does not have corresponding doxygen comments
-bool Definition::checkMethod() const {
-    SkASSERT(MarkType::kMethod == fMarkType);
-    // if method returns a value, look for a return child
-    // for each parameter, look for a corresponding child
-    const char* end = fContentStart;
-    while (end > fStart && ' ' >= end[-1]) {
-        --end;
-    }
-    TextParser methodParser(fFileName, fStart, end, fLineCount);
-    methodParser.skipWhiteSpace();
-    SkASSERT(methodParser.startsWith("#Method"));
-    methodParser.skipName("#Method");
-    methodParser.skipSpace();
-    string name = this->methodName();
-    if (MethodType::kNone == fMethodType && name.length() > 2 &&
-            "()" == name.substr(name.length() - 2)) {
-        name = name.substr(0, name.length() - 2);
-    }
-    bool expectReturn = this->methodHasReturn(name, &methodParser);
-    bool foundReturn = false;
-    bool foundPopulate = false;
-    for (auto& child : fChildren) {
-        foundPopulate |= MarkType::kPopulate == child->fMarkType;
-        if (MarkType::kReturn != child->fMarkType) {
-            if (MarkType::kParam == child->fMarkType) {
-                child->fVisited = false;
-            }
-            continue;
-        }
-        if (!expectReturn) {
-            return methodParser.reportError<bool>("no #Return expected");
-        }
-        if (foundReturn) {
-            return methodParser.reportError<bool>("multiple #Return markers");
-        }
-        foundReturn = true;
-    }
-    if (expectReturn && !foundReturn && !foundPopulate) {
-        return methodParser.reportError<bool>("missing #Return marker");
-    }
-    const char* paren = methodParser.strnchr('(', methodParser.fEnd);
-    if (!paren) {
-        return methodParser.reportError<bool>("missing #Method function definition");
-    }
-    const char* nextEnd = paren;
-    do {
-        string paramName;
-        methodParser.fChar = nextEnd + 1;
-        methodParser.skipSpace();
-        if (!this->nextMethodParam(&methodParser, &nextEnd, &paramName)) {
-            continue;
-        }
-        bool foundParam = false;
-        for (auto& child : fChildren) {
-            if (MarkType::kParam != child->fMarkType) {
-                continue;
-            }
-            if (paramName != child->fName) {
-                continue;
-            }
-            if (child->fVisited) {
-                return methodParser.reportError<bool>("multiple #Method param with same name");
-            }
-            child->fVisited = true;
-            if (foundParam) {
-                TextParser paramError(child);
-                return methodParser.reportError<bool>("multiple #Param with same name");
-            }
-            foundParam = true;
-
-        }
-        if (!foundParam && !foundPopulate) {
-            return methodParser.reportError<bool>("no #Param found");
-        }
-        if (')' == nextEnd[0]) {
-            break;
-        }
-    } while (')' != nextEnd[0]);
-    for (auto& child : fChildren) {
-        if (MarkType::kParam != child->fMarkType) {
-            continue;
-        }
-        if (!child->fVisited) {
-            TextParser paramError(child);
-            return paramError.reportError<bool>("#Param without param in #Method");
-        }
-    }
-    // check after end of #Line and before next child for description
-    const char* descStart = fContentStart;
-    const char* descEnd = nullptr;
-    const Definition* defEnd = nullptr;
-    const Definition* priorDef = nullptr;
-    bool incomplete = false;
-    for (auto& child : fChildren) {
-        if (MarkType::kAnchor == child->fMarkType) {
-            continue;
-        }
-        if (MarkType::kCode == child->fMarkType) {
-            priorDef = child;
-            continue;
-        }
-        if (MarkType::kFormula == child->fMarkType) {
-            continue;
-        }
-        if (MarkType::kLine == child->fMarkType) {
-            SkASSERT(child->fChildren.size() > 0);
-            TextParser childDesc(child->fChildren[0]);
-            incomplete |= childDesc.startsWith("incomplete");
-        }
-        if (MarkType::kList == child->fMarkType) {
-            priorDef = child;
-            continue;
-        }
-        if (MarkType::kMarkChar == child->fMarkType) {
-            continue;
-        }
-        if (MarkType::kPhraseRef == child->fMarkType) {
-            continue;
-        }
-        TextParser emptyCheck(fFileName, descStart, child->fStart, child->fLineCount);
-        if (!emptyCheck.eof() && emptyCheck.skipWhiteSpace()) {
-            descStart = emptyCheck.fChar;
-            emptyCheck.trimEnd();
-            defEnd = priorDef;
-            descEnd = emptyCheck.fEnd;
-            break;
-        }
-        descStart = child->fTerminator;
-        priorDef = nullptr;
-    }
-    if (!descEnd) {
-        return incomplete || foundPopulate ? true :
-                methodParser.reportError<bool>("missing description");
-    }
-    TextParser description(fFileName, descStart, descEnd, fLineCount);
-    // expect first word capitalized and pluralized. expect a trailing period
-    SkASSERT(descStart < descEnd);
-    if (!isupper(descStart[0])) {
-        description.reportWarning("expected capital");
-    } else if ('.' != descEnd[-1]) {
-        if (!defEnd || defEnd->fTerminator != descEnd) {
-            if (!incomplete) {
-                description.reportWarning("expected period");
-            }
-        }
-    } else {
-        if (!description.startsWith("For use by Android")) {
-            description.skipToSpace();
-            if (',' == description.fChar[-1]) {
-                --description.fChar;
-            }
-            if ('s' != description.fChar[-1]) {
-                if (!incomplete) {
-                    description.reportWarning("expected plural");
-                }
-            }
-        }
-    }
-    return true;
-}
-
-bool Definition::crossCheck2(const Definition& includeToken) const {
-    TextParser parser(fFileName, fStart, fContentStart, fLineCount);
-    parser.skipExact("#");
-    bool isMethod = parser.skipName("Method");
-    const char* contentEnd;
-    if (isMethod) {
-        contentEnd = fContentStart;
-    } else if (parser.skipName("DefinedBy")) {
-        contentEnd = fContentEnd;
-        while (parser.fChar < contentEnd && ' ' >= contentEnd[-1]) {
-            --contentEnd;
-        }
-        if (parser.fChar < contentEnd - 1 && ')' == contentEnd[-1] && '(' == contentEnd[-2]) {
-            contentEnd -= 2;
-        }
-    } else {
-        return parser.reportError<bool>("unexpected crosscheck marktype");
-    }
-    return crossCheckInside(parser.fChar, contentEnd, includeToken);
-}
-
-bool Definition::crossCheck(const Definition& includeToken) const {
-    return crossCheckInside(fContentStart, fContentEnd, includeToken);
-}
-
-const char* Definition::methodEnd() const {
-    const char defaultTag[] = " = default";
-    size_t tagSize = sizeof(defaultTag) - 1;
-    const char* tokenEnd = fContentEnd - tagSize;
-    if (tokenEnd <= fContentStart || strncmp(tokenEnd, defaultTag, tagSize)) {
-        tokenEnd = fContentEnd;
-    }
-    return tokenEnd;
-}
-
-bool Definition::SkipImplementationWords(TextParser& inc) {
-    if (inc.startsWith("SK_API")) {
-        inc.skipWord("SK_API");
-    }
-    if (inc.startsWith("inline")) {
-        inc.skipWord("inline");
-    }
-    if (inc.startsWith("friend")) {
-        inc.skipWord("friend");
-    }
-    if (inc.startsWith("SK_API")) {
-        inc.skipWord("SK_API");
-    }
-    return inc.skipExact("SkDEBUGCODE(");
-}
-
-bool Definition::crossCheckInside(const char* start, const char* end,
-        const Definition& includeToken) const {
-    TextParser def(fFileName, start, end, fLineCount);
-    const char* tokenEnd = includeToken.methodEnd();
-    TextParser inc("", includeToken.fContentStart, tokenEnd, 0);
-    if (inc.startsWith("static")) {
-        def.skipWhiteSpace();
-        if (!def.startsWith("static")) {
-            return false;
-        }
-        inc.skipWord("static");
-        def.skipWord("static");
-    }
-    (void) Definition::SkipImplementationWords(inc);
-    do {
-        bool defEof;
-        bool incEof;
-        do {
-            defEof = def.eof() || !def.skipWhiteSpace();
-            incEof = inc.eof() || !inc.skipWhiteSpace();
-            if (!incEof && '/' == inc.peek() && (defEof || '/' != def.peek())) {
-                inc.next();
-                if ('*' == inc.peek()) {
-                    inc.skipToEndBracket("*/");
-                    inc.next();
-                } else if ('/' == inc.peek()) {
-                    inc.skipToEndBracket('\n');
-                }
-            } else if (!incEof && '#' == inc.peek() && (defEof || '#' != def.peek())) {
-                inc.next();
-                if (inc.startsWith("if")) {
-                    inc.skipToEndBracket("\n");
-                } else if (inc.startsWith("endif")) {
-                    inc.skipToEndBracket("\n");
-                } else {
-                    SkASSERT(0); // incomplete
-                    return false;
-                }
-            } else {
-                break;
-            }
-            inc.next();
-        } while (true);
-        if (defEof || incEof) {
-            if (defEof == incEof || (!defEof && ';' == def.peek())) {
-                return true;
-            }
-            return false;  // allow setting breakpoint on failure
-        }
-        char defCh;
-        do {
-            defCh = def.next();
-            if (inc.skipExact("SK_WARN_UNUSED_RESULT")) {
-                inc.skipSpace();
-            }
-            char incCh = inc.next();
-            if (' ' >= defCh && ' ' >= incCh) {
-                break;
-            }
-            if (defCh != incCh) {
-                if ('_' != defCh || ' ' != incCh || !fOperatorConst || !def.startsWith("const")) {
-                    return false;
-                }
-            }
-            if (';' == defCh) {
-                return true;
-            }
-        } while (!def.eof() && !inc.eof());
-    } while (true);
-    return false;
-}
-
-string Definition::formatFunction(Format format) const {
-    const char* end = fContentStart;
-    while (end > fStart && ' ' >= end[-1]) {
-        --end;
-    }
-    TextParser methodParser(fFileName, fStart, end, fLineCount);
-    methodParser.skipWhiteSpace();
-    SkASSERT(methodParser.startsWith("#Method"));
-    methodParser.skipName("#Method");
-    methodParser.skipSpace();
-    const char* lastStart = methodParser.fChar;
-    const int limit = 100;  // todo: allow this to be set by caller or in global or something
-    string name = this->methodName();
-    const char* nameInParser = methodParser.strnstr(name.c_str(), methodParser.fEnd);
-    methodParser.skipTo(nameInParser);
-    const char* lastEnd = methodParser.fChar;
-    if (Format::kOmitReturn == format) {
-        lastStart = lastEnd;
-    }
-    const char* paren = methodParser.strnchr('(', methodParser.fEnd);
-    size_t indent;
-    if (paren) {
-        indent = (size_t) (paren - lastStart) + 1;
-    } else {
-        indent = (size_t) (lastEnd - lastStart);
-    }
-    // trim indent so longest line doesn't exceed box width
-    TextParserSave savePlace(&methodParser);
-    const char* saveStart = lastStart;
-    ptrdiff_t maxLine = 0;
-    do {
-        const char* nextStart = lastEnd;
-        const char* delimiter = methodParser.anyOf(",)");
-        const char* nextEnd = delimiter ? delimiter : methodParser.fEnd;
-        if (delimiter) {
-            while (nextStart < nextEnd && ' ' >= nextStart[0]) {
-                ++nextStart;
-            }
-        }
-        while (nextEnd > nextStart && ' ' >= nextEnd[-1]) {
-            --nextEnd;
-        }
-        if (delimiter) {
-            nextEnd += 1;
-            delimiter += 1;
-        }
-        if (lastEnd > lastStart) {
-            maxLine = SkTMax(maxLine, lastEnd - lastStart);
-        }
-        if (delimiter) {
-            methodParser.skipTo(delimiter);
-        }
-        lastStart = nextStart;
-        lastEnd = nextEnd;
-    } while (lastStart < lastEnd);
-    savePlace.restore();
-    lastStart = saveStart;
-    lastEnd = methodParser.fChar;
-    indent = SkTMin(indent, (size_t) (limit - maxLine));
-    // write string with trimmmed indent
-    string methodStr;
-    int written = 0;
-    do {
-        const char* nextStart = lastEnd;
-//        SkASSERT(written < limit);
-        const char* delimiter = methodParser.anyOf(",)");
-        const char* nextEnd = delimiter ? delimiter : methodParser.fEnd;
-        if (delimiter) {
-            while (nextStart < nextEnd && ' ' >= nextStart[0]) {
-                ++nextStart;
-            }
-        }
-        while (nextEnd > nextStart && ' ' >= nextEnd[-1]) {
-            --nextEnd;
-        }
-        if (delimiter) {
-            nextEnd += 1;
-            delimiter += 1;
-        }
-        if (lastEnd > lastStart) {
-            if (lastStart[0] != ' ') {
-                space_pad(&methodStr);
-            }
-            string addon(lastStart, (size_t) (lastEnd - lastStart));
-            if (" const" == addon) {
-                addon = "const";
-            }
-            methodStr += addon;
-            written += addon.length();
-        }
-        if (delimiter) {
-            if (nextEnd - nextStart >= (ptrdiff_t) (limit - written)) {
-                written = indent;
-                if (Format::kIncludeReturn == format) {
-                    methodStr += '\n';
-                    methodStr += string(indent, ' ');
-                }
-            }
-            methodParser.skipTo(delimiter);
-        }
-        lastStart = nextStart;
-        lastEnd = nextEnd;
-    } while (lastStart < lastEnd);
-    return methodStr;
-}
-
-string Definition::fiddleName() const {
-    string result;
-    size_t start = 0;
-    string parent;
-    const Definition* parentDef = this;
-    while ((parentDef = parentDef->fParent)) {
-        if (MarkType::kClass == parentDef->fMarkType || MarkType::kStruct == parentDef->fMarkType) {
-            parent = parentDef->fFiddle;
-            break;
-        }
-    }
-    if (parent.length() && 0 == fFiddle.compare(0, parent.length(), parent)) {
-        start = parent.length();
-        while (start < fFiddle.length() && '_' == fFiddle[start]) {
-            ++start;
-        }
-    }
-    size_t end = fFiddle.find_first_of('(', start);
-    return fFiddle.substr(start, end - start);
-}
-
-string Definition::fileName() const {
-    size_t nameStart = fFileName.rfind(SkOSPath::SEPARATOR);
-    if (SkOSPath::SEPARATOR != '/') {
-        size_t altNameStart = fFileName.rfind('/');
-        nameStart = string::npos == nameStart ? altNameStart :
-                string::npos != altNameStart && altNameStart > nameStart ? altNameStart : nameStart;
-    }
-    SkASSERT(string::npos != nameStart);
-    string baseFile = fFileName.substr(nameStart + 1);
-    return baseFile;
-}
-
-const Definition* Definition::findClone(string match) const {
-    for (auto child : fChildren) {
-        if (!child->fClone) {
-            continue;
-        }
-        if (match == child->fName) {
-            return child;
-        }
-        auto inner = child->findClone(match);
-        if (inner) {
-            return inner;
-        }
-    }
-    return nullptr;
-}
-
-const Definition* Definition::hasChild(MarkType markType) const {
-    for (auto iter : fChildren) {
-        if (markType == iter->fMarkType) {
-            return iter;
-        }
-    }
-    return nullptr;
-}
-
-Definition* Definition::hasParam(string ref) {
-    SkASSERT(MarkType::kMethod == fMarkType);
-    for (auto iter : fChildren) {
-        if (MarkType::kParam != iter->fMarkType) {
-            continue;
-        }
-        if (iter->fName == ref) {
-            return &*iter;
-        }
-    }
-    for (auto& iter : fTokens) {
-        if (MarkType::kComment != iter.fMarkType) {
-            continue;
-        }
-        TextParser parser(&iter);
-        if (!parser.skipExact("@param ")) {
-            continue;
-        }
-        if (parser.skipExact(ref.c_str()) && ' ' == parser.peek()) {
-            return &iter;
-        }
-    }
-    return nullptr;
-}
-
-bool Definition::hasMatch(string name) const {
-    for (auto child : fChildren) {
-        if (name == child->fName) {
-            return true;
-        }
-        if (child->hasMatch(name)) {
-            return true;
-        }
-    }
-    return false;
-}
-
-bool Definition::isStructOrClass() const {
-    if (MarkType::kStruct != fMarkType && MarkType::kClass != fMarkType) {
-        return false;
-    }
-    if (string::npos != fFileName.find("undocumented.bmh")) {
-        return false;
-    }
-    return true;
-}
-
-bool Definition::methodHasReturn(string name, TextParser* methodParser) const {
-    if (methodParser->skipExact("static")) {
-        methodParser->skipWhiteSpace();
-    }
-    if (methodParser->skipExact("virtual")) {
-        methodParser->skipWhiteSpace();
-    }
-    const char* lastStart = methodParser->fChar;
-    const char* nameInParser = methodParser->strnstr(name.c_str(), methodParser->fEnd);
-    methodParser->skipTo(nameInParser);
-    const char* lastEnd = methodParser->fChar;
-    const char* returnEnd = lastEnd;
-    while (returnEnd > lastStart && ' ' == returnEnd[-1]) {
-        --returnEnd;
-    }
-    bool expectReturn = 4 != returnEnd - lastStart || strncmp("void", lastStart, 4);
-    if (MethodType::kNone != fMethodType && MethodType::kOperator != fMethodType && !expectReturn) {
-        return methodParser->reportError<bool>("unexpected void");
-    }
-    switch (fMethodType) {
-        case MethodType::kNone:
-        case MethodType::kOperator:
-            // either is fine
-            break;
-        case MethodType::kConstructor:
-            expectReturn = true;
-            break;
-        case MethodType::kDestructor:
-            expectReturn = false;
-            break;
-    }
-    return expectReturn;
-}
-
-string Definition::methodName() const {
-    string result;
-    size_t start = 0;
-    string parent;
-    const Definition* parentDef = this;
-    while ((parentDef = parentDef->fParent)) {
-        if (MarkType::kClass == parentDef->fMarkType || MarkType::kStruct == parentDef->fMarkType) {
-            parent = parentDef->fName;
-            break;
-        }
-    }
-    if (parent.length() && 0 == fName.compare(0, parent.length(), parent)) {
-        start = parent.length();
-        while (start < fName.length() && ':' == fName[start]) {
-            ++start;
-        }
-    }
-    if (fClone) {
-        int lastUnder = fName.rfind('_');
-        return fName.substr(start, (size_t) (lastUnder - start));
-    }
-    size_t end = fName.find_first_of('(', start);
-    if (string::npos == end) {
-        return fName.substr(start);
-    }
-    return fName.substr(start, end - start);
-}
-
-bool Definition::nextMethodParam(TextParser* methodParser, const char** nextEndPtr,
-        string* paramName) const {
-    int parenCount = 0;
-    TextParserSave saveState(methodParser);
-    while (true) {
-        if (methodParser->eof()) {
-            return methodParser->reportError<bool>("#Method function missing close paren");
-        }
-        char ch = methodParser->peek();
-        if ('(' == ch || '{' == ch) {
-            ++parenCount;
-        }
-        if (parenCount == 0 && (')' == ch || ',' == ch)) {
-            *nextEndPtr = methodParser->fChar;
-            break;
-        }
-        if (')' == ch || '}' == ch) {
-            if (0 > --parenCount) {
-                return this->reportError<bool>("mismatched parentheses");
-            }
-        }
-        methodParser->next();
-    }
-    saveState.restore();
-    const char* nextEnd = *nextEndPtr;
-    const char* paramEnd = nextEnd;
-    const char* assign = methodParser->strnstr(" = ", paramEnd);
-    if (assign) {
-        paramEnd = assign;
-    }
-    const char* closeBracket = methodParser->strnstr("]", paramEnd);
-    if (closeBracket) {
-        const char* openBracket = methodParser->strnstr("[", paramEnd);
-        if (openBracket && openBracket < closeBracket) {
-            while (openBracket < --closeBracket && isdigit(closeBracket[0]))
-                ;
-            if (openBracket == closeBracket) {
-                paramEnd = openBracket;
-            }
-        }
-    }
-    const char* function = methodParser->strnstr(")(", paramEnd);
-    if (function) {
-        paramEnd = function;
-    }
-    while (paramEnd > methodParser->fChar && ' ' == paramEnd[-1]) {
-        --paramEnd;
-    }
-    const char* paramStart = paramEnd;
-    while (paramStart > methodParser->fChar && isalnum(paramStart[-1])) {
-        --paramStart;
-    }
-    if (paramStart > methodParser->fChar && paramStart >= paramEnd) {
-        return methodParser->reportError<bool>("#Method missing param name");
-    }
-    *paramName = string(paramStart, paramEnd - paramStart);
-    if (!paramName->length()) {
-        if (')' != nextEnd[0]) {
-            return methodParser->reportError<bool>("#Method malformed param");
-        }
-        return false;
-    }
-    return true;
-}
-
-string Definition::NormalizedName(string name) {
-    string normalizedName = name;
-    std::replace(normalizedName.begin(), normalizedName.end(), '-', '_');
-    do {
-        size_t doubleColon = normalizedName.find("::", 0);
-        if (string::npos == doubleColon) {
-            break;
-        }
-        normalizedName = normalizedName.substr(0, doubleColon)
-            + '_' + normalizedName.substr(doubleColon + 2);
-    } while (true);
-    return normalizedName;
-}
-
-static string unpreformat(string orig) {
-    string result;
-    int amp = 0;
-    for (auto c : orig) {
-        switch (amp) {
-        case 0:
-            if ('&' == c) {
-                amp = 1;
-            } else {
-                amp = 0;
-                result += c;
-            }
-            break;
-        case 1:
-            if ('l' == c) {
-                amp = 2;
-            } else if ('g' == c) {
-                amp = 3;
-            } else {
-                amp = 0;
-                result += "&";
-                result += c;
-            }
-            break;
-        case 2:
-            if ('t' == c) {
-                amp = 4;
-            } else {
-                amp = 0;
-                result += "&l";
-                result += c;
-            }
-            break;
-        case 3:
-            if ('t' == c) {
-                amp = 5;
-            } else {
-                amp = 0;
-                result += "&g";
-                result += c;
-            }
-            break;
-        case 4:
-            if (';' == c) {
-                result += '<';
-            } else {
-                result += "&lt";
-                result += c;
-            }
-            amp = 0;
-            break;
-        case 5:
-            if (';' == c) {
-                result += '>';
-            } else {
-                result += "&gt";
-                result += c;
-            }
-            amp = 0;
-            break;
-        }
-    }
-    return result;
-}
-
-bool Definition::paramsMatch(string matchFormatted, string name) const {
-    string match = unpreformat(matchFormatted);
-    TextParser def(fFileName, fStart, fContentStart, fLineCount);
-    const char* dName = def.strnstr(name.c_str(), fContentStart);
-    if (!dName) {
-        return false;
-    }
-    def.skipTo(dName);
-    TextParser m(fFileName, &match.front(), &match.back() + 1, fLineCount);
-    const char* mName = m.strnstr(name.c_str(), m.fEnd);
-    if (!mName) {
-        return false;
-    }
-    m.skipTo(mName);
-    while (!def.eof() && ')' != def.peek() && !m.eof() && ')' != m.peek()) {
-        const char* ds = def.fChar;
-        const char* ms = m.fChar;
-        const char* de = def.anyOf(") \n");
-        const char* me = m.anyOf(") \n");
-        def.skipTo(de);
-        m.skipTo(me);
-        if (def.fChar - ds != m.fChar - ms) {
-            return false;
-        }
-        if (strncmp(ds, ms, (int) (def.fChar - ds))) {
-            return false;
-        }
-        def.skipWhiteSpace();
-        m.skipWhiteSpace();
-    }
-    return !def.eof() && ')' == def.peek() && !m.eof() && ')' == m.peek();
-}
-
-
-void Definition::trimEnd() {
-    while (fContentEnd > fContentStart && ' ' >= fContentEnd[-1]) {
-        --fContentEnd;
-    }
-}
-
-void RootDefinition::clearVisited() {
-    fVisited = false;
-    for (auto& leaf : fLeaves) {
-        leaf.second.fVisited = false;
-    }
-    for (auto& branch : fBranches) {
-        branch.second->clearVisited();
-    }
-}
-
-bool RootDefinition::dumpUnVisited() {
-    bool success = true;
-    for (auto& leaf : fLeaves) {
-        if (!leaf.second.fVisited) {
-            // FIXME: bugs requiring long tail fixes, suppressed here:
-            // SkBitmap::validate() is wrapped in SkDEBUGCODE in .h and not parsed
-            if ("SkBitmap::validate()" == leaf.first) {
-                continue;
-            }
-            // FIXME: end of long tail bugs
-            SkDebugf("defined in bmh but missing in include: %s\n", leaf.first.c_str());
-            success = false;
-        }
-    }
-    for (auto& branch : fBranches) {
-        success &= branch.second->dumpUnVisited();
-    }
-    return success;
-}
-
-Definition* RootDefinition::find(string ref, AllowParens allowParens) {
-    const auto leafIter = fLeaves.find(ref);
-    if (leafIter != fLeaves.end()) {
-        return &leafIter->second;
-    }
-    if (AllowParens::kYes == allowParens) {
-        size_t leftParen = ref.find('(');
-        if (string::npos == leftParen
-                || (leftParen + 1 < ref.length() && ')' != ref[leftParen + 1])) {
-            string withParens = ref + "()";
-            const auto parensIter = fLeaves.find(withParens);
-            if (parensIter != fLeaves.end()) {
-                return &parensIter->second;
-            }
-        }
-        if (string::npos != leftParen) {
-            string name = ref.substr(0, leftParen);
-            size_t posInDefName = fName.find(name);
-            if (string::npos != posInDefName && posInDefName > 2
-                    && "::" == fName.substr(posInDefName - 2, 2)) {
-                string fullRef = fName + "::" + ref;
-                const auto fullIter = fLeaves.find(fullRef);
-                if (fullIter != fLeaves.end()) {
-                    return &fullIter->second;
-                }
-            }
-        }
-    }
-    const auto branchIter = fBranches.find(ref);
-    if (branchIter != fBranches.end()) {
-        RootDefinition* rootDef = branchIter->second;
-        return rootDef;
-    }
-    Definition* result = nullptr;
-    for (const auto& branch : fBranches) {
-        RootDefinition* rootDef = branch.second;
-        result = rootDef->find(ref, allowParens);
-        if (result) {
-            break;
-        }
-    }
-    return result;
-}
diff --git a/tools/bookmaker/definition.h b/tools/bookmaker/definition.h
deleted file mode 100644
index c882ccc..0000000
--- a/tools/bookmaker/definition.h
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef definition_DEFINED
-#define definition_DEFINED
-
-#include "textParser.h"
-
-class RootDefinition;
-class TextParser;
-
-class Definition : public NonAssignable {
-public:
-    enum Type {
-        kNone,
-        kWord,
-        kMark,
-        kKeyWord,
-        kBracket,
-        kPunctuation,
-        kFileType,
-    };
-
-    enum class MethodType {
-        kNone,
-        kConstructor,
-        kDestructor,
-        kOperator,
-    };
-
-    enum class Operator {
-        kUnknown,
-        kAdd,
-        kAddTo,
-        kArray,
-        kCast,
-        kCopy,
-        kDelete,
-        kDereference,
-        kEqual,
-        kMinus,
-        kMove,
-        kMultiply,
-        kMultiplyBy,
-        kNew,
-        kNotEqual,
-        kSubtract,
-        kSubtractFrom,
-    };
-
-    enum class Format {
-        kIncludeReturn,
-        kOmitReturn,
-    };
-
-    enum class Details {
-        kNone,
-        kSoonToBe_Deprecated,
-        kTestingOnly_Experiment,
-        kDoNotUse_Experiment,
-        kNotReady_Experiment,
-    };
-
-    enum class DetailsType {
-        kPhrase,
-        kSentence,
-    };
-
-    Definition() {}
-
-    Definition(const char* start, const char* end, int line, Definition* parent, char mc)
-        : fStart(start)
-        , fContentStart(start)
-        , fContentEnd(end)
-        , fParent(parent)
-        , fLineCount(line)
-        , fType(Type::kWord)
-        , fMC(mc) {
-        if (parent) {
-            SkASSERT(parent->fFileName.length() > 0);
-            fFileName = parent->fFileName;
-        }
-        this->setParentIndex();
-    }
-
-    Definition(MarkType markType, const char* start, int line, Definition* parent, char mc)
-        : Definition(markType, start, nullptr, line, parent, mc) {
-    }
-
-    Definition(MarkType markType, const char* start, const char* end, int line, Definition* parent, char mc)
-        : Definition(start, end, line, parent, mc) {
-        fMarkType = markType;
-        fType = Type::kMark;
-    }
-
-    Definition(Bracket bracket, const char* start, int lineCount, Definition* parent, char mc)
-        : Definition(start, nullptr, lineCount, parent, mc) {
-        fBracket = bracket;
-        fType = Type::kBracket;
-    }
-
-    Definition(KeyWord keyWord, const char* start, const char* end, int lineCount,
-            Definition* parent, char mc)
-        : Definition(start, end, lineCount, parent, mc) {
-        fKeyWord = keyWord;
-        fType = Type::kKeyWord;
-    }
-
-    Definition(Punctuation punctuation, const char* start, int lineCount, Definition* parent, char mc)
-        : Definition(start, nullptr, lineCount, parent, mc) {
-        fPunctuation = punctuation;
-        fType = Type::kPunctuation;
-    }
-
-    virtual ~Definition() {}
-
-    virtual RootDefinition* asRoot() { SkASSERT(0); return nullptr; }
-    bool boilerplateIfDef();
-
-    bool boilerplateEndIf() {
-        return true;
-    }
-
-    bool checkMethod() const;
-    bool crossCheck2(const Definition& includeToken) const;
-    bool crossCheck(const Definition& includeToken) const;
-    bool crossCheckInside(const char* start, const char* end, const Definition& includeToken) const;
-
-    Definition* csParent() {
-        Definition* test = fParent;
-        while (test) {
-            if (MarkType::kStruct == test->fMarkType || MarkType::kClass == test->fMarkType) {
-                return test;
-            }
-            test = test->fParent;
-        }
-        return nullptr;
-    }
-
-    string fiddleName() const;
-    string fileName() const;
-    const Definition* findClone(string match) const;
-    string formatFunction(Format format) const;
-    const Definition* hasChild(MarkType markType) const;
-    bool hasMatch(string name) const;
-    Definition* hasParam(string ref);
-    bool isClone() const { return fClone; }
-
-    const Definition* iRootParent() const {
-        const Definition* test = fParent;
-        while (test) {
-            if (KeyWord::kClass == test->fKeyWord || KeyWord::kStruct == test->fKeyWord) {
-                return test;
-            }
-            test = test->fParent;
-        }
-        return nullptr;
-    }
-
-    virtual bool isRoot() const { return false; }
-    bool isStructOrClass() const;
-
-    int length() const {
-        return (int) (fContentEnd - fContentStart);
-    }
-
-    const char* methodEnd() const;
-    bool methodHasReturn(string name, TextParser* methodParser) const;
-    string methodName() const;
-    bool nextMethodParam(TextParser* methodParser, const char** nextEndPtr,
-                         string* paramName) const;
-    static string NormalizedName(string name);
-    bool paramsMatch(string fullRef, string name) const;
-    bool parseOperator(size_t doubleColons, string& result);
-
-    string printableName() const {
-        string result(fName);
-        std::replace(result.begin(), result.end(), '_', ' ');
-        return result;
-    }
-
-    template <typename T> T reportError(const char* errorStr) const {
-        TextParser tp(this);
-        tp.reportError(errorStr);
-        return T();
-    }
-
-    virtual RootDefinition* rootParent() { SkASSERT(0); return nullptr; }
-    virtual const RootDefinition* rootParent() const { SkASSERT(0); return nullptr; }
-    void setCanonicalFiddle();
-
-    void setParentIndex() {
-        fParentIndex = fParent ? (int) fParent->fTokens.size() : -1;
-    }
-
-    static bool SkipImplementationWords(TextParser& inc);
-
-    string simpleName() {
-        size_t doubleColon = fName.rfind("::");
-        return string::npos == doubleColon ? fName : fName.substr(doubleColon + 2);
-    }
-
-    const Definition* subtopicParent() const {
-        Definition* test = fParent;
-        while (test) {
-            if (MarkType::kTopic == test->fMarkType || MarkType::kSubtopic == test->fMarkType) {
-                return test;
-            }
-            test = test->fParent;
-        }
-        return nullptr;
-    }
-
-    const Definition* topicParent() const {
-        Definition* test = fParent;
-        while (test) {
-            if (MarkType::kTopic == test->fMarkType) {
-                return test;
-            }
-            test = test->fParent;
-        }
-        return nullptr;
-    }
-
-    void trimEnd();
-
-    string fText;  // if text is constructed instead of in a file, it's put here
-    const char* fStart = nullptr;  // .. in original text file, or the start of fText
-    const char* fContentStart;  // start past optional markup name
-    string fName;
-    string fFiddle;  // if its a constructor or operator, fiddle name goes here
-    string fCode;  // suitable for autogeneration of #Code blocks in bmh
-    const char* fContentEnd = nullptr;  // the end of the contained text
-    const char* fTerminator = nullptr;  // the end of the markup, normally ##\n or \n
-    Definition* fParent = nullptr;
-    list<Definition> fTokens;
-    vector<Definition*> fChildren;
-    string fHash;  // generated by fiddle
-    string fFileName;
-    mutable string fWrapper; // used by Example to wrap into proper function
-    size_t fLineCount = 0;
-    int fParentIndex = 0;
-    MarkType fMarkType = MarkType::kNone;
-    KeyWord fKeyWord = KeyWord::kNone;
-    Bracket fBracket = Bracket::kNone;
-    Punctuation fPunctuation = Punctuation::kNone;
-    MethodType fMethodType = MethodType::kNone;
-    Operator fOperator = Operator::kUnknown;
-    Type fType = Type::kNone;
-    char fMC = '#';
-    bool fClone = false;
-    bool fCloned = false;
-    bool fOperatorConst = false;
-    bool fPrivate = false;
-    Details fDetails = Details::kNone;
-    bool fMemberStart = false;
-    bool fAnonymous = false;
-    bool fUndocumented = false;  // include symbol comment has deprecated, private, experimental
-    mutable bool fVisited = false;
-};
-
-class RootDefinition : public Definition {
-public:
-    enum class AllowParens {
-        kNo,
-        kYes,
-    };
-
-    struct SubtopicContents {
-        SubtopicContents()
-            : fShowClones(false) {
-        }
-
-        vector<Definition*> fMembers;
-        bool fShowClones;
-    };
-
-    RootDefinition() {
-    }
-
-    RootDefinition(MarkType markType, const char* start, int line, Definition* parent, char mc)
-            : Definition(markType, start, line, parent, mc) {
-        if (MarkType::kSubtopic != markType && MarkType::kTopic != markType) {
-            if (parent) {
-                fNames.fName = parent->fName;
-                fNames.fParent = &parent->asRoot()->fNames;
-            }
-        }
-    }
-
-    RootDefinition(MarkType markType, const char* start, const char* end, int line,
-            Definition* parent, char mc) : Definition(markType, start, end, line, parent, mc) {
-    }
-
-    ~RootDefinition() override {
-        for (auto& iter : fBranches) {
-            delete iter.second;
-        }
-    }
-
-    RootDefinition* asRoot() override { return this; }
-    void clearVisited();
-    bool dumpUnVisited();
-    Definition* find(string ref, AllowParens );
-    bool isRoot() const override { return true; }
-
-    SubtopicContents& populator(const char* key) {
-        return fPopulators[key];
-    }
-
-    RootDefinition* rootParent() override { return fRootParent; }
-    const RootDefinition* rootParent() const override { return fRootParent; }
-    void setRootParent(RootDefinition* rootParent) { fRootParent = rootParent; }
-
-    unordered_map<string, RootDefinition*> fBranches;
-    unordered_map<string, Definition> fLeaves;
-    unordered_map<string, SubtopicContents> fPopulators;
-    NameMap fNames;
-private:
-    RootDefinition* fRootParent = nullptr;
-};
-
-#endif
diff --git a/tools/bookmaker/fiddleParser.cpp b/tools/bookmaker/fiddleParser.cpp
deleted file mode 100644
index 8e8eb0f5..0000000
--- a/tools/bookmaker/fiddleParser.cpp
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "bmhParser.h"
-#include "fiddleParser.h"
-
-// could make this more elaborate and look up the example definition in the bmh file;
-// see if a simpler hint provided is sufficient
-static bool report_error(const char* blockName, const char* errorMessage) {
-    SkDebugf("%s: %s\n", blockName, errorMessage);
-    return false;
-}
-
-Definition* FiddleBase::findExample(string name) const {
-    return fBmhParser->findExample(name);
-}
-
-bool FiddleBase::parseFiddles() {
-    if (fStack.empty()) {
-        return false;
-    }
-    JsonStatus* status = &fStack.back();
-    while (!status->atEnd()) {
-        const char* blockName = status->fObjectIter->fKey.begin();
-        Definition* example = nullptr;
-        string textString;
-        if (!status->fObject) {
-            return report_error(blockName, "expected object");
-        }
-        const skjson::ObjectValue* obj = status->fObjectIter->fValue;
-        for (auto iter = obj->begin(); obj->end() != iter; ++iter) {
-            const char* memberName = iter->fKey.begin();
-            if (!strcmp("compile_errors", memberName)) {
-                if (!iter->fValue.is<skjson::ArrayValue>()) {
-                    return report_error(blockName, "expected array");
-                }
-                if (iter->fValue.as<skjson::ArrayValue>().size()) {
-                    return report_error(blockName, "fiddle compiler error");
-                }
-                continue;
-            }
-            if (!strcmp("runtime_error", memberName)) {
-                if (!iter->fValue.is<skjson::StringValue>()) {
-                    return report_error(blockName, "expected string 1");
-                }
-                if (iter->fValue.as<skjson::StringValue>().size()) {
-                    return report_error(blockName, "fiddle runtime error");
-                }
-                continue;
-            }
-            if (!strcmp("fiddleHash", memberName)) {
-                const skjson::StringValue* sv = iter->fValue;
-                if (!sv) {
-                    return report_error(blockName, "expected string 2");
-                }
-                example = this->findExample(blockName);
-                if (!example) {
-                    return report_error(blockName, "missing example");
-                }
-                if (example->fHash.length() && example->fHash != sv->begin()) {
-                    return example->reportError<bool>("mismatched hash");
-                }
-                example->fHash = sv->begin();
-                continue;
-            }
-            if (!strcmp("text", memberName)) {
-                const skjson::StringValue* sv = iter->fValue;
-                if (!sv) {
-                    return report_error(blockName, "expected string 3");
-                }
-                textString = sv->begin();
-                continue;
-            }
-            return report_error(blockName, "unexpected key");
-        }
-        if (!example) {
-            return report_error(blockName, "missing fiddleHash");
-        }
-        size_t strLen = textString.length();
-        if (strLen) {
-            if (fTextOut
-                    && !this->textOut(example, textString.c_str(), textString.c_str() + strLen)) {
-                return false;
-            }
-        } else if (fPngOut && !this->pngOut(example)) {
-            return false;
-        }
-        status->advance();
-    }
-    return true;
-}
-
-bool FiddleParser::parseFromFile(const char* path)  {
-    if (!INHERITED::parseFromFile(path)) {
-        return false;
-    }
-    fBmhParser->resetExampleHashes();
-    if (!INHERITED::parseFiddles()) {
-        return false;
-    }
-    return fBmhParser->checkExampleHashes();
-}
-
-bool FiddleParser::textOut(Definition* example, const char* stdOutStart,
-        const char* stdOutEnd) {
-    bool foundStdOut = false;
-    for (auto& textOut : example->fChildren) {
-        if (MarkType::kStdOut != textOut->fMarkType) {
-            continue;
-        }
-        foundStdOut = true;
-        bool foundVolatile = false;
-        for (auto& stdOutChild : textOut->fChildren) {
-                if (MarkType::kVolatile == stdOutChild->fMarkType) {
-                    foundVolatile = true;
-                    break;
-                }
-        }
-        TextParser bmh(textOut);
-        EscapeParser fiddle(stdOutStart, stdOutEnd);
-        do {
-            bmh.skipWhiteSpace();
-            fiddle.skipWhiteSpace();
-            const char* bmhEnd = bmh.trimmedLineEnd();
-            const char* fiddleEnd = fiddle.trimmedLineEnd();
-            ptrdiff_t bmhLen = bmhEnd - bmh.fChar;
-            SkASSERT(bmhLen > 0);
-            ptrdiff_t fiddleLen = fiddleEnd - fiddle.fChar;
-            SkASSERT(fiddleLen > 0);
-            if (bmhLen != fiddleLen) {
-                if (!foundVolatile) {
-                    bmh.reportError("mismatched stdout len\n");
-                }
-            } else  if (strncmp(bmh.fChar, fiddle.fChar, fiddleLen)) {
-                if (!foundVolatile) {
-                    SkDebugf("%.*s\n", fiddleLen, fiddle.fChar);
-                    bmh.reportError("mismatched stdout text\n");
-                }
-            }
-            bmh.skipToLineStart();
-            fiddle.skipToLineStart();
-        } while (!bmh.eof() && !fiddle.eof());
-        if (!foundStdOut) {
-            bmh.reportError("bmh %s missing stdout\n");
-        } else if (!bmh.eof() || !fiddle.eof()) {
-            if (!foundVolatile) {
-                bmh.reportError("%s mismatched stdout eof\n");
-            }
-        }
-    }
-    return true;
-}
diff --git a/tools/bookmaker/fiddleParser.h b/tools/bookmaker/fiddleParser.h
deleted file mode 100644
index e6fa9a4..0000000
--- a/tools/bookmaker/fiddleParser.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef fiddleParser_DEFINED
-#define fiddleParser_DEFINED
-
-#include "parserCommon.h"
-
-class BmhParser;
-
-class FiddleBase : public JsonCommon {
-protected:
-    FiddleBase(BmhParser* bmh)
-        : fBmhParser(bmh)
-        , fContinuation(false)
-        , fTextOut(false)
-        , fPngOut(false)
-    {
-        this->reset();
-    }
-
-    void reset() override {
-        INHERITED::reset();
-    }
-
-    Definition* findExample(string name) const;
-    bool parseFiddles();
-    virtual bool pngOut(Definition* example) = 0;
-    virtual bool textOut(Definition* example, const char* stdOutStart,
-        const char* stdOutEnd) = 0;
-
-    BmhParser* fBmhParser;  // must be writable; writes example hash
-    string fFullName;
-    bool fContinuation;
-    bool fTextOut;
-    bool fPngOut;
-private:
-    typedef JsonCommon INHERITED;
-};
-
-class FiddleParser : public FiddleBase {
-public:
-    FiddleParser(BmhParser* bmh) : FiddleBase(bmh) {
-       fTextOut = true;
-    }
-
-    bool parseFromFile(const char* path) override;
-
-private:
-    bool pngOut(Definition* example) override {
-        return true;
-    }
-
-    bool textOut(Definition* example, const char* stdOutStart,
-        const char* stdOutEnd) override;
-
-    typedef FiddleBase INHERITED;
-};
-
-class Catalog : public FiddleBase {
-public:
-    Catalog(BmhParser* bmh) : FiddleBase(bmh) {}
-
-    bool appendFile(string path);
-    bool closeCatalog(const char* outDir);
-    bool openCatalog(const char* inDir);
-    bool openStatus(const char* inDir);
-
-    bool parseFromFile(const char* path) override ;
-private:
-    bool pngOut(Definition* example) override;
-    bool textOut(Definition* example, const char* stdOutStart,
-        const char* stdOutEnd) override;
-
-    string fDocsDir;
-
-    typedef FiddleBase INHERITED;
-};
-
-#endif
diff --git a/tools/bookmaker/hackParser.cpp b/tools/bookmaker/hackParser.cpp
deleted file mode 100644
index dcd8346..0000000
--- a/tools/bookmaker/hackParser.cpp
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "bmhParser.h"
-
-    // replace #Method description, #Param, #Return with #Populate
-    // if description, params, return are free of phrase refs
-bool HackParser::hackFiles() {
-    string filename(fFileName);
-    size_t len = filename.length() - 1;
-    while (len > 0 && (isalnum(filename[len]) || '_' == filename[len] || '.' == filename[len])) {
-        --len;
-    }
-    filename = filename.substr(len + 1);
-    if (filename.substr(0, 2) != "Sk") {
-        return true;
-    }
-    size_t under = filename.find('_');
-    SkASSERT(under);
-    string className = filename.substr(0, under);
-    fOut = fopen(filename.c_str(), "wb");
-    if (!fOut) {
-        SkDebugf("could not open output file %s\n", filename.c_str());
-        return false;
-    }
-    auto mapEntry = fBmhParser.fClassMap.find(className);
-    if (fBmhParser.fClassMap.end() == mapEntry) {
-        remove(filename.c_str());
-        return true;
-    }
-    const Definition* classMarkup = &mapEntry->second;
-    const Definition* root = classMarkup->fParent;
-    SkASSERT(root);
-    SkASSERT(root->fTerminator);
-    SkASSERT('\n' == root->fTerminator[0]);
-    SkASSERT(!root->fParent);
-    fStart = root->fStart;
-    fChar = fStart;
-    fEnd = root->fTerminator;
-    this->replaceWithPop(root);
-    FPRINTF("%.*s", (int) (fEnd - fChar), fChar);
-    if ('\n' != fEnd[-1]) {
-        FPRINTF("\n");
-    }
-    fclose(fOut);
-    if (ParserCommon::WrittenFileDiffers(filename, root->fFileName)) {
-        SkDebugf("wrote %s\n", filename.c_str());
-    } else {
-        remove(filename.c_str());
-    }
-    return true;
-}
-
-// returns true if topic has method
-void HackParser::replaceWithPop(const Definition* root) {
-    for (auto child : root->fChildren) {
-        if (MarkType::kClass == child->fMarkType || MarkType::kStruct == child->fMarkType
-                || MarkType::kSubtopic == child->fMarkType) {
-            this->replaceWithPop(child);
-        }
-        if (MarkType::kMethod != child->fMarkType) {
-            continue;
-        }
-        auto& grans = child->fChildren;
-        if (grans.end() != std::find_if(grans.begin(), grans.end(),
-                [](const Definition* def) {
-                    return MarkType::kPopulate == def->fMarkType
-                        || MarkType::kPhraseRef == def->fMarkType
-                        || MarkType::kFormula == def->fMarkType
-                        || MarkType::kAnchor == def->fMarkType
-                        || MarkType::kList == def->fMarkType
-                        || MarkType::kTable == def->fMarkType;
-                } )) {
-            continue;
-        }
-        // write #Populate in place of description, #Param(s), #Return (if present)
-        const char* keep = child->fContentStart;
-        const char* next = nullptr;
-        for (auto gran : grans) {
-            if (MarkType::kIn == gran->fMarkType || MarkType::kLine == gran->fMarkType) {
-                keep = gran->fTerminator;
-                continue;
-            }
-            if (MarkType::kExample == gran->fMarkType
-                    || MarkType::kNoExample == gran->fMarkType) {
-                next = gran->fStart;
-                break;
-            }
-            if (MarkType::kParam == gran->fMarkType
-                    || MarkType::kReturn == gran->fMarkType
-                    || MarkType::kToDo == gran->fMarkType
-                    || MarkType::kComment == gran->fMarkType) {
-                continue;
-            }
-            SkDebugf("");  // convenient place to set a breakpoint
-        }
-        SkASSERT(next);
-        FPRINTF("%.*s", (int) (keep - fChar), fChar);
-        if ('\n' != keep[-1]) {
-            FPRINTF("\n");
-        }
-        FPRINTF("#Populate\n\n");
-        fChar = next;
-    }
-}
diff --git a/tools/bookmaker/includeParser.cpp b/tools/bookmaker/includeParser.cpp
deleted file mode 100644
index 48e7c5f..0000000
--- a/tools/bookmaker/includeParser.cpp
+++ /dev/null
@@ -1,3649 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkOSFile.h"
-#include "SkOSPath.h"
-
-#include "bmhParser.h"
-#include "includeParser.h"
-#include <map>
-
-const IncludeKey kKeyWords[] = {
-    { "",           KeyWord::kNone,         KeyProperty::kNone           },
-    { "SK_API",     KeyWord::kSK_API,       KeyProperty::kModifier       },
-    { "SK_BEGIN_REQUIRE_DENSE", KeyWord::kSK_BEGIN_REQUIRE_DENSE, KeyProperty::kModifier },
-    { "alignas",    KeyWord::kAlignAs,      KeyProperty::kModifier       },
-    { "bool",       KeyWord::kBool,         KeyProperty::kNumber         },
-    { "char",       KeyWord::kChar,         KeyProperty::kNumber         },
-    { "class",      KeyWord::kClass,        KeyProperty::kObject         },
-    { "const",      KeyWord::kConst,        KeyProperty::kModifier       },
-    { "constexpr",  KeyWord::kConstExpr,    KeyProperty::kModifier       },
-    { "define",     KeyWord::kDefine,       KeyProperty::kPreprocessor   },
-    { "double",     KeyWord::kDouble,       KeyProperty::kNumber         },
-    { "elif",       KeyWord::kElif,         KeyProperty::kPreprocessor   },
-    { "else",       KeyWord::kElse,         KeyProperty::kPreprocessor   },
-    { "endif",      KeyWord::kEndif,        KeyProperty::kPreprocessor   },
-    { "enum",       KeyWord::kEnum,         KeyProperty::kObject         },
-    { "error",      KeyWord::kError,        KeyProperty::kPreprocessor   },
-    { "float",      KeyWord::kFloat,        KeyProperty::kNumber         },
-    { "friend",     KeyWord::kFriend,       KeyProperty::kModifier       },
-    { "if",         KeyWord::kIf,           KeyProperty::kPreprocessor   },
-    { "ifdef",      KeyWord::kIfdef,        KeyProperty::kPreprocessor   },
-    { "ifndef",     KeyWord::kIfndef,       KeyProperty::kPreprocessor   },
-    { "include",    KeyWord::kInclude,      KeyProperty::kPreprocessor   },
-    { "inline",     KeyWord::kInline,       KeyProperty::kModifier       },
-    { "int",        KeyWord::kInt,          KeyProperty::kNumber         },
-    { "operator",   KeyWord::kOperator,     KeyProperty::kFunction       },
-    { "private",    KeyWord::kPrivate,      KeyProperty::kClassSection   },
-    { "protected",  KeyWord::kProtected,    KeyProperty::kClassSection   },
-    { "public",     KeyWord::kPublic,       KeyProperty::kClassSection   },
-    { "signed",     KeyWord::kSigned,       KeyProperty::kNumber         },
-    { "size_t",     KeyWord::kSize_t,       KeyProperty::kNumber         },
-    { "static",     KeyWord::kStatic,       KeyProperty::kModifier       },
-    { "struct",     KeyWord::kStruct,       KeyProperty::kObject         },
-    { "template",   KeyWord::kTemplate,     KeyProperty::kObject         },
-    { "typedef",    KeyWord::kTypedef,      KeyProperty::kObject         },
-    { "typename",   KeyWord::kTypename,     KeyProperty::kObject         },
-    { "uint16_t",   KeyWord::kUint16_t,     KeyProperty::kNumber         },
-    { "uint32_t",   KeyWord::kUint32_t,     KeyProperty::kNumber         },
-    { "uint64_t",   KeyWord::kUint64_t,     KeyProperty::kNumber         },
-    { "uint8_t",    KeyWord::kUint8_t,      KeyProperty::kNumber         },
-    { "uintptr_t",  KeyWord::kUintPtr_t,    KeyProperty::kNumber         },
-    { "union",      KeyWord::kUnion,        KeyProperty::kObject         },
-    { "unsigned",   KeyWord::kUnsigned,     KeyProperty::kNumber         },
-    { "using",      KeyWord::kUsing,        KeyProperty::kObject         },
-    { "void",       KeyWord::kVoid,         KeyProperty::kNumber         },
-};
-
-const size_t kKeyWordCount = SK_ARRAY_COUNT(kKeyWords);
-
-KeyWord IncludeParser::FindKey(const char* start, const char* end) {
-    int ch = 0;
-    for (size_t index = 0; index < kKeyWordCount; ) {
-        if (start[ch] > kKeyWords[index].fName[ch]) {
-            ++index;
-            if (ch > 0 && (index == kKeyWordCount ||
-                    kKeyWords[index - 1].fName[ch - 1] < kKeyWords[index].fName[ch - 1])) {
-                return KeyWord::kNone;
-            }
-            continue;
-        }
-        if (start[ch] < kKeyWords[index].fName[ch]) {
-            return KeyWord::kNone;
-        }
-        ++ch;
-        if (start + ch >= end) {
-            if (end - start < (int) strlen(kKeyWords[index].fName)) {
-                return KeyWord::kNone;
-            }
-            return kKeyWords[index].fKeyWord;
-        }
-    }
-    return KeyWord::kNone;
-}
-
-void IncludeParser::ValidateKeyWords() {
-    for (size_t index = 1; index < kKeyWordCount; ++index) {
-        SkASSERT((int) kKeyWords[index - 1].fKeyWord + 1
-                == (int) kKeyWords[index].fKeyWord);
-        SkASSERT(0 > strcmp(kKeyWords[index - 1].fName, kKeyWords[index].fName));
-    }
-}
-
-void IncludeParser::addKeyword(KeyWord keyWord) {
-    fParent->fTokens.emplace_back(keyWord, fIncludeWord, fChar, fLineCount, fParent, '\0');
-    fIncludeWord = nullptr;
-    if (KeyProperty::kObject == kKeyWords[(int) keyWord].fProperty) {
-        Definition* def = &fParent->fTokens.back();
-        this->addDefinition(def);
-        if (KeyWord::kEnum == fParent->fKeyWord) {
-            fInEnum = true;
-        }
-    }
-}
-
-static bool looks_like_method(const TextParser& tp) {
-    TextParser t(tp.fFileName, tp.fLine, tp.fChar, tp.fLineCount);
-    t.skipSpace();
-    if (!t.skipExact("struct") && !t.skipExact("class") && !t.skipExact("enum class")
-            && !t.skipExact("enum")) {
-        return true;
-    }
-    t.skipSpace();
-    if (t.skipExact("SK_API")) {
-        t.skipSpace();
-    }
-    if (!isupper(t.peek())) {
-        return true;
-    }
-    return nullptr != t.strnchr('(', t.fEnd);
-}
-
-static bool looks_like_forward_declaration(const TextParser& tp) {
-    TextParser t(tp.fFileName, tp.fChar, tp.lineEnd(), tp.fLineCount);
-    t.skipSpace();
-    if (!t.skipExact("struct") && !t.skipExact("class") && !t.skipExact("enum class")
-            && !t.skipExact("enum")) {
-        return false;
-    }
-    t.skipSpace();
-    if (t.skipExact("SK_API")) {
-        t.skipSpace();
-    }
-    if (!isupper(t.peek())) {
-        return false;
-    }
-    t.skipToNonAlphaNum();
-    if (t.eof() || ';' != t.next()) {
-        return false;
-    }
-    if (t.eof() || '\n' != t.next()) {
-        return false;
-    }
-    return t.eof();
-}
-
-static bool looks_like_constructor(const TextParser& tp) {
-    TextParser t(tp.fFileName, tp.fLine, tp.lineEnd(), tp.fLineCount);
-    t.skipSpace();
-    if (!isupper(t.peek())) {
-        if (':' == t.next() && ' ' >= t.peek()) {
-            return true;
-        }
-        return false;
-    }
-    t.skipToNonAlphaNum();
-    if ('(' != t.peek()) {
-        return false;
-    }
-    if (!t.skipToEndBracket(')')) {
-        return false;
-    }
-    SkAssertResult(')' == t.next());
-    t.skipSpace();
-    return tp.fChar == t.fChar;
-}
-
-static bool looks_like_class_decl(const TextParser& tp) {
-    TextParser t(tp.fFileName, tp.fLine, tp.fChar, tp.fLineCount);
-    t.skipSpace();
-    if (!t.skipExact("class")) {
-        return false;
-    }
-    t.skipSpace();
-    if (t.skipExact("SK_API")) {
-        t.skipSpace();
-    }
-    if (!isupper(t.peek())) {
-        return false;
-    }
-    t.skipToNonAlphaNum();
-    return !t.skipToEndBracket('(');
-}
-
-static bool looks_like_const(const TextParser& tp) {
-    TextParser t(tp.fFileName, tp.fChar, tp.lineEnd(), tp.fLineCount);
-    if (!t.startsWith("static constexpr ")) {
-        return false;
-    }
-    if (t.skipToEndBracket(" k")) {
-        SkAssertResult(t.skipExact(" k"));
-    } else if (t.skipToEndBracket(" SK_")) {
-        SkAssertResult(t.skipExact(" SK_"));
-    } else {
-        return false;
-    }
-    if (!isupper(t.peek())) {
-        return false;
-    }
-    return t.skipToEndBracket(" = ");
-}
-
-static bool looks_like_member(const TextParser& tp) {
-    TextParser t(tp.fFileName, tp.fChar, tp.lineEnd(), tp.fLineCount);
-    const char* end = t.anyOf("(;");
-    if (!end || '(' == *end) {
-        return false;
-    }
-    bool foundMember = false;
-    do {
-        const char* next = t.anyOf(" ;");
-        if (';' == *next) {
-            break;
-        }
-        t.skipTo(next);
-        t.skipSpace();
-        foundMember = 'f' == t.fChar[0] && isupper(t.fChar[1]);
-    } while (true);
-    return foundMember;
-}
-
-static void skip_constructor_initializers(TextParser& t) {
-    SkAssertResult(':' == t.next());
-    do {
-        t.skipWhiteSpace();
-        t.skipToNonAlphaNum();
-        t.skipWhiteSpace();
-        if ('{' == t.peek()) {
-            t.skipToBalancedEndBracket('{', '}');
-        }
-        do {
-            const char* limiter = t.anyOf("(,{");
-            t.skipTo(limiter);
-            if ('(' != t.peek()) {
-                break;
-            }
-            t.skipToBalancedEndBracket('(', ')');
-        } while (true);
-        if ('{' == t.peek()) {
-            return;
-        }
-        SkAssertResult(',' == t.next());
-    } while (true);
-}
-
-static const char kInline[] = "inline ";
-static const char kSK_API[] = "SK_API ";
-static const char kSK_WARN_UNUSED_RESULT[] = "SK_WARN_UNUSED_RESULT ";
-
-bool IncludeParser::advanceInclude(TextParser& i) {
-    if (!i.skipWhiteSpace(&fCheck.fIndent, &fCheck.fWriteReturn)) {
-        return false;
-    }
-    if (fCheck.fPrivateBrace) {
-        if (i.startsWith("};")) {
-            if (fCheck.fPrivateBrace == fCheck.fBraceCount) {
-                fCheck.fPrivateBrace = 0;
-                fCheck.fDoubleReturn = true;
-            } else {
-                i.skipExact("};");
-                fCheck.fBraceCount -= 1;
-            }
-            return false;
-        }
-        if (i.startsWith("public:")) {
-            if (fCheck.fBraceCount <= fCheck.fPrivateBrace) {
-                fCheck.fPrivateBrace = 0;
-                if (fCheck.fPrivateProtected) {
-                    i.skipExact("public:");
-                }
-            } else {
-                i.skipExact("public:");
-            }
-        } else {
-            fCheck.fBraceCount += i.skipToLineBalance('{', '}');
-        }
-        return false;
-    } else if (i.startsWith("};")) {
-        fCheck.fDoubleReturn = 2;
-    }
-    if (i.skipExact(kInline)) {
-        fCheck.fSkipInline = true;
-        return false;
-    }
-    if (i.skipExact(kSK_API)) {
-        fCheck.fSkipAPI = true;
-        return false;
-    }
-    if (i.skipExact(kSK_WARN_UNUSED_RESULT)) {
-        fCheck.fSkipWarnUnused = true;
-        return false;
-    }
-    if (i.skipExact("SK_ATTR_DEPRECATED")) {
-        i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
-        return false;
-    }
-    if (i.skipExact("SkDEBUGCODE")) {
-        i.skipWhiteSpace();
-        if ('(' != i.peek()) {
-            i.reportError("expected open paren");
-        }
-        TextParserSave save(&i);
-        SkAssertResult(i.skipToBalancedEndBracket('(', ')'));
-        fCheck.fInDebugCode = i.fChar - 1;
-        save.restore();
-        SkAssertResult('(' == i.next());
-    }
-    if ('{' == i.peek()) {
-        if (looks_like_method(i)) {
-            fCheck.fState = CheckCode::State::kMethod;
-            bool inBalance = false;
-            TextParser paren(i.fFileName, i.fStart, i.fEnd, i.fLineCount);
-            paren.skipToEndBracket('(');
-            paren.skipToBalancedEndBracket('(', ')');
-            inBalance = i.fChar < paren.fChar;
-            if (!inBalance) {
-                if (!i.skipToBalancedEndBracket('{', '}')) {
-                    i.reportError("unbalanced open brace");
-                }
-                i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
-                return false;
-            }
-        } else if (looks_like_class_decl(i)) {
-            fCheck.fState = CheckCode::State::kClassDeclaration;
-            fCheck.fPrivateBrace = fCheck.fBraceCount + 1;
-            fCheck.fPrivateProtected = false;
-        }
-    }
-    if (':' == i.peek() && looks_like_constructor(i)) {
-        fCheck.fState = CheckCode::State::kConstructor;
-        skip_constructor_initializers(i);
-        return false;
-    }
-    if ('#' == i.peek()) {
-        i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
-        return false;
-    }
-    if (i.startsWith("//")) {
-        i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
-        return false;
-    }
-    if (i.startsWith("/*")) {
-        i.skipToEndBracket("*/");
-        i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
-        return false;
-    }
-    if (looks_like_forward_declaration(i)) {
-        fCheck.fState = CheckCode::State::kForwardDeclaration;
-        i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
-        return false;
-    }
-    if (i.skipExact("private:") || i.skipExact("protected:")) {
-        if (!fCheck.fBraceCount) {
-            i.reportError("expect private in brace");
-        }
-        fCheck.fPrivateBrace = fCheck.fBraceCount;
-        fCheck.fPrivateProtected = true;
-        return false;
-    }
-    const char* funcEnd = i.anyOf("(\n");
-    if (funcEnd && '(' == funcEnd[0] && '_' == *i.anyOf("_(")
-            && (i.contains("internal_", funcEnd, nullptr)
-            || i.contains("private_", funcEnd, nullptr)
-            || i.contains("legacy_", funcEnd, nullptr)
-            || i.contains("temporary_", funcEnd, nullptr))) {
-        i.skipTo(funcEnd);
-        if (!i.skipToBalancedEndBracket('(', ')')) {
-            i.reportError("unbalanced open parent");
-        }
-        i.skipSpace();
-        i.skipExact("const ");
-        i.skipSpace();
-        if (';' == i.peek()) {
-            i.next();
-        }
-        fCheck.fState = CheckCode::State::kNone;
-        return false;
-    }
-    return true;
-}
-
-void IncludeParser::codeBlockAppend(string& result, string s) const {
-    for (char c : s) {
-        this->codeBlockAppend(result, c);
-    }
-}
-
-void IncludeParser::codeBlockAppend(string& result, char ch) const {
-    if (Elided::kYes == fElided && fCheck.fBraceCount) {
-        return;
-    }
-    this->stringAppend(result, ch);
-}
-
-void IncludeParser::codeBlockSpaces(string& result, int indent) const {
-    if (!indent) {
-        return;
-    }
-    if (Elided::kYes == fElided && fCheck.fBraceCount) {
-        return;
-    }
-    SkASSERT(indent > 0);
-    if (fDebugWriteCodeBlock) {
-        SkDebugf("%*c", indent, ' ');
-    }
-    result.append(indent, ' ');
-}
-
-string IncludeParser::writeCodeBlock(const Definition& iDef) {
-    if (MarkType::kComment == iDef.fMarkType) {
-        return "";
-    }
-    if (iDef.fUndocumented) {
-        return "";
-    }
-    TextParser i(&iDef);
-    (void) i.skipExact("SkDEBUGCODE(");
-    if (MarkType::kConst == iDef.fMarkType && !i.fEnd) {
-        // TODO: end should have been set earlier
-        auto iter = iDef.fParent->fTokens.begin();
-        std::advance(iter, iDef.fParentIndex + 1);
-        SkASSERT(iter != iDef.fParent->fTokens.end());
-        i.fEnd = iter->fContentStart;
-    }
-    const char* loc;
-    if (MarkType::kMember == iDef.fMarkType) {
-        const char* parentEnd = iDef.fParent->fContentEnd;
-        TextParser newEnd(&iDef);
-        newEnd.fEnd = parentEnd;
-        const char* memberEnd = newEnd.anyOf(",};");
-        if (memberEnd && (';' == memberEnd[0] || ',' == memberEnd[0])) {
-            i.fEnd = memberEnd + 1;
-        }
-    }
-    if (i.contains("//", i.fEnd, &loc)) {
-        i.fEnd = loc;
-    }
-    if (i.contains("/*", i.fEnd, &loc)) {
-        i.fEnd = loc;
-    }
-    if (i.contains("{", i.fEnd, &loc)) {
-        bool inBalance = false;
-        if (MarkType::kMethod == iDef.fMarkType) {
-            TextParser paren(&iDef);
-            paren.skipToEndBracket('(');
-            paren.skipToBalancedEndBracket('(', ')');
-            inBalance = loc < paren.fChar;
-        }
-        if (!inBalance) {
-            i.fEnd = loc + 1;
-            while (i.fEnd < iDef.fContentEnd && ' ' >= i.fEnd[0]) {
-                ++i.fEnd;
-            }
-        }
-    }
-    while (i.fEnd > i.fStart && ' ' == i.fEnd[-1]) {
-        --i.fEnd;
-    }
-    const char* before = iDef.fContentStart;
-    while (' ' == *--before)
-        ;
-    int startIndent = iDef.fContentStart - before - 1;
-    bool saveDebugWriteBlock = fDebugWriteCodeBlock;
-    fDebugWriteCodeBlock = false;
-    string result = writeCodeBlock(i, iDef.fMarkType, startIndent);
-    fDebugWriteCodeBlock = saveDebugWriteBlock;
-    if (!result.empty()) {
-        if (MarkType::kNone != fPreviousMarkType && iDef.fMarkType != fPreviousMarkType
-                && ((MarkType::kEnum != fPreviousMarkType
-                && MarkType::kEnumClass != fPreviousMarkType)
-                || MarkType::kMember != iDef.fMarkType)
-                && (MarkType::kMember != fPreviousMarkType
-                || iDef.fParent == fPreviousDef->fParent)) {
-            result = "\n" + result;
-        }
-        if (fDebugWriteCodeBlock) {
-            SkDebugf("%s", result.c_str());
-        }
-        fPreviousDef = &iDef;
-        fPreviousMarkType = iDef.fMarkType;
-    }
-    for (auto& token : iDef.fTokens) {
-        result += this->writeCodeBlock(token);
-    }
-    if (MarkType::kEnum == iDef.fMarkType || MarkType::kEnumClass == iDef.fMarkType
-            || MarkType::kStruct == iDef.fMarkType || MarkType::kClass == iDef.fMarkType) {
-        this->codeBlockSpaces(result, startIndent);
-        this->codeBlockAppend(result, "};\n\n");
-    }
-    return result;
-}
-
-string IncludeParser::writeCodeBlock(TextParser& i, MarkType markType, int startIndent) {
-    string result;
-    char last;
-    int lastIndent = 0;
-    bool lastDoubleMeUp = false;
-    fCheck.reset();
-    if (MarkType::kDefine == markType) {
-        result = "#define ";
-    } else {
-        this->codeBlockSpaces(result, startIndent);
-    }
-    do {
-        if (!this->advanceInclude(i)) {
-            continue;
-        }
-        do {
-            last = i.peek();
-            SkASSERT(' ' < last);
-            if (fCheck.fInDebugCode == i.fChar) {
-                fCheck.fInDebugCode = nullptr;
-                i.next();   // skip close paren
-                break;
-            }
-            if (CheckCode::State::kMethod == fCheck.fState) {
-                this->codeBlockAppend(result, ';');
-                fCheck.fState = CheckCode::State::kNone;
-            }
-            if (fCheck.fWriteReturn) {
-                this->codeBlockAppend(result, '\n');
-                bool doubleMeUp = i.startsWith("typedef ") || looks_like_const(i)
-                        || (!strncmp("struct ", i.fStart, 7) && looks_like_member(i));
-                if ((!--fCheck.fDoubleReturn && !i.startsWith("};")) || i.startsWith("enum ")
-                        || i.startsWith("typedef ") || doubleMeUp || fCheck.fTypedefReturn
-                        || (fCheck.fIndent && (i.startsWith("class ") || i.startsWith("struct ")))) {
-                    if (lastIndent > 0 && (!doubleMeUp || !lastDoubleMeUp)) {
-                        this->codeBlockAppend(result, '\n');
-                    }
-                    fCheck.fTypedefReturn = false;
-                    lastDoubleMeUp = doubleMeUp;
-                }
-                if (doubleMeUp) {
-                    fCheck.fTypedefReturn = true;
-                }
-                lastIndent = fCheck.fIndent;
-            }
-            if (fCheck.fIndent) {
-                size_t indent = fCheck.fIndent;
-                if (fCheck.fSkipInline && indent > sizeof(kInline)) {
-                    indent -= sizeof(kInline) - 1;
-                }
-                if (fCheck.fSkipAPI && indent > sizeof(kSK_API)) {
-                    indent -= sizeof(kSK_API) - 1;
-                }
-                if (fCheck.fSkipWarnUnused && indent > sizeof(kSK_WARN_UNUSED_RESULT)) {
-                    indent -= sizeof(kSK_WARN_UNUSED_RESULT) - 1;
-                }
-
-                this->codeBlockSpaces(result, indent);
-            }
-            this->codeBlockAppend(result, last);
-            fCheck.fWriteReturn = false;
-            fCheck.fIndent = 0;
-            fCheck.fBraceCount += '{' == last;
-            fCheck.fBraceCount -= '}' == last;
-            if (';' == last) {
-                fCheck.fSkipInline = false;
-                fCheck.fSkipAPI = false;
-                fCheck.fSkipWarnUnused = false;
-            }
-            if (fCheck.fBraceCount < 0) {
-                i.reportError("unbalanced close brace");
-                return result;
-            }
-            i.next();
-        } while (!i.eof() && ' ' < i.peek() && !i.startsWith("//"));
-    } while (!i.eof());
-    if (CheckCode::State::kMethod == fCheck.fState) {
-        this->codeBlockAppend(result, ';');
-    }
-    bool elided = Elided::kYes == fElided;
-    bool elidedTemplate = elided && !strncmp(i.fStart, "template ", 9);
-    bool elidedTClass = elidedTemplate && MarkType::kClass == markType;
-    bool statementEnd = !result.empty() && (MarkType::kMethod == markType
-            || MarkType::kTypedef == markType || '}' == result.back());
-    bool semiEnd = !result.empty() && (',' == result.back() || ';' == result.back());
-    if (fCheck.fWriteReturn || elidedTClass) {
-        this->codeBlockAppend(result, '\n');
-    }
-    if (elided && ((MarkType::kFunction != markType && lastIndent > startIndent) || elidedTClass)) {
-        this->codeBlockAppend(result, '}');
-        statementEnd = true;
-    }
-    if (elided || statementEnd) {
-        this->codeBlockAppend(result, ";\n");
-    } else if (elidedTemplate || semiEnd) {
-        this->codeBlockAppend(result, '\n');
-    }
-    return result;
-}
-
-void IncludeParser::checkForMissingParams(const vector<string>& methodParams,
-        const vector<string>& foundParams) {
-    for (auto& methodParam : methodParams) {
-        bool found = false;
-        for (auto& foundParam : foundParams) {
-            if (methodParam == foundParam) {
-                found = true;
-                break;
-            }
-        }
-        if (!found) {
-            this->writeIncompleteTag("Param", methodParam, 2);
-        }
-    }
-    for (auto& foundParam : foundParams) {
-        bool found = false;
-        for (auto& methodParam : methodParams) {
-            if (methodParam == foundParam) {
-                found = true;
-                break;
-            }
-        }
-        if (!found) {
-            this->reportError("doxygen param does not match method declaration");
-        }
-    }
-}
-
-bool IncludeParser::checkForWord() {
-    if (!fIncludeWord) {
-        return true;
-    }
-    KeyWord keyWord = FindKey(fIncludeWord, fChar);
-    if (KeyWord::kClass == keyWord || KeyWord::kStruct == keyWord) {
-        Bracket bracket = this->topBracket();
-        if (Bracket::kParen == bracket) {
-            return true;
-        }
-    }
-    if (KeyWord::kNone != keyWord) {
-        if (KeyProperty::kPreprocessor != kKeyWords[(int) keyWord].fProperty) {
-            this->addKeyword(keyWord);
-            return true;
-        }
-    } else {
-        this->addWord();
-        return true;
-    }
-    Definition* poundDef = fParent;
-    if (!fParent) {
-        return reportError<bool>("expected parent");
-    }
-    if (Definition::Type::kBracket != poundDef->fType) {
-        return reportError<bool>("expected bracket");
-    }
-    if (Bracket::kPound != poundDef->fBracket) {
-        return reportError<bool>("expected preprocessor");
-    }
-    if (KeyWord::kNone != poundDef->fKeyWord) {
-        return reportError<bool>("already found keyword");
-    }
-    poundDef->fKeyWord = keyWord;
-    fIncludeWord = nullptr;
-    switch (keyWord) {
-        // these do not link to other # directives
-        case KeyWord::kDefine:
-            if (!fInBrace) {
-                SkASSERT(!fInDefine);
-                fInDefine = true;
-            }
-        case KeyWord::kInclude:
-        case KeyWord::kError:
-        break;
-        // these start a # directive link
-        case KeyWord::kIf:
-        case KeyWord::kIfdef:
-        case KeyWord::kIfndef:
-        break;
-        // these continue a # directive link
-        case KeyWord::kElif:
-        case KeyWord::kElse:
-            this->popObject();  // pop elif
-            if (Bracket::kPound != fParent->fBracket) {
-                return this->reportError<bool>("expected preprocessor directive");
-            }
-            this->popBracket();  // pop if
-            poundDef->fParent = fParent;
-            fParent = poundDef;  // push elif back
-        break;
-        // this ends a # directive link
-        case KeyWord::kEndif:
-        // FIXME : should this be calling popBracket() instead?
-            this->popObject();  // pop endif
-            if (Bracket::kPound != fParent->fBracket) {
-                return this->reportError<bool>("expected preprocessor directive");
-            }
-            this->popBracket();  // pop if/else
-        break;
-        default:
-            SkASSERT(0);
-    }
-    return true;
-}
-
-string IncludeParser::className() const {
-    string name(fParent->fName);
-    size_t slash = name.find_last_of("/");
-    if (string::npos == slash) {
-        slash = name.find_last_of("\\");
-    }
-    SkASSERT(string::npos != slash);
-    string result = name.substr(slash);
-    result = result.substr(1, result.size() - 3);
-    return result;
-}
-
-void IncludeParser::writeCodeBlock() {
-    fElided = Elided::kNo;
-    for (auto& classMapper : fIClassMap) {
-        fPreviousMarkType = MarkType::kNone;
-        fPreviousDef = nullptr;
-        classMapper.second.fCode = this->writeCodeBlock(classMapper.second);
-    }
-    for (auto& enumMapper : fIEnumMap) {
-        fPreviousMarkType = MarkType::kNone;
-        fPreviousDef = nullptr;
-        enumMapper.second->fCode = this->writeCodeBlock(*enumMapper.second);
-    }
-    for (auto& typedefMapper : fITypedefMap) {
-        fPreviousMarkType = MarkType::kNone;
-        fPreviousDef = nullptr;
-        typedefMapper.second->fCode = this->writeCodeBlock(*typedefMapper.second);
-    }
-    for (auto& defineMapper : fIDefineMap) {
-        fPreviousMarkType = MarkType::kNone;
-        fPreviousDef = nullptr;
-        defineMapper.second->fCode = this->writeCodeBlock(*defineMapper.second);
-    }
-}
-
-void IncludeParser::checkName(Definition* def) {
-    SkASSERT(!def->fName.empty());
-    TextParser parser(def->fFileName, &def->fName.front(), &def->fName.back() + 1, def->fLineCount);
-    const vector<string> skipWords = { "deprecated", "experimental", "internal",  "private",
-            "legacy", "temporary" };
-    if (!parser.anyWord(skipWords, 0).empty()) {
-        def->fUndocumented = true;
-    }
-}
-
-#include <sstream>
-#include <iostream>
-
-void IncludeParser::checkTokens(list<Definition>& tokens, string key, string className,
-        RootDefinition* root, BmhParser& bmhParser) {
-    for (const auto& token : tokens) {
-        if (token.fPrivate) {
-            continue;
-        }
-        string fullName = key + "::" + token.fName;
-        const Definition* def = nullptr;
-        if (root) {
-            def = root->find(fullName, RootDefinition::AllowParens::kYes);
-        }
-        switch (token.fMarkType) {
-            case MarkType::kMethod: {
-                if (this->isInternalName(token)) {
-                    continue;
-                }
-                if (!root) {
-                    if (token.fUndocumented) {
-                        break;
-                    }
-                    auto methIter = bmhParser.fMethodMap.find(token.fName);
-                    if (bmhParser.fMethodMap.end() != methIter) {
-                        def = &methIter->second;
-                        if (def->crossCheck2(token)) {
-                            def->fVisited = true;
-                        } else {
-                            this->suggestFix(Suggest::kMethodDiffers, token, root, def);
-                            fFailed = true;
-                        }
-                    } else {
-                        this->suggestFix(Suggest::kMethodMissing, token, root, nullptr);
-                        fFailed = true;
-                    }
-                    break;
-                }
-                if (!def) {
-                    string paramName = className + "::";
-                    paramName += string(token.fContentStart,
-                            token.fContentEnd - token.fContentStart);
-                    if (string::npos != paramName.find('\n')) {
-                        paramName.erase(std::remove(paramName.begin(), paramName.end(), '\n'),
-                                paramName.end());
-                    }
-                    def = root->find(paramName, RootDefinition::AllowParens::kYes);
-                    if (!def && 0 == token.fName.find("operator")) {
-                        string operatorName = className + "::";
-                        TextParser oper("", token.fStart, token.fContentEnd, 0);
-                        const char* start = oper.strnstr("operator", token.fContentEnd);
-                        SkASSERT(start);
-                        oper.skipTo(start);
-                        oper.skipToEndBracket('(');
-                        int parens = 0;
-                        do {
-                            if ('(' == oper.peek()) {
-                                ++parens;
-                            } else if (')' == oper.peek()) {
-                                --parens;
-                            }
-                        } while (!oper.eof() && oper.next() && parens > 0);
-                        operatorName += string(start, oper.fChar - start);
-                        def = root->find(operatorName, RootDefinition::AllowParens::kYes);
-                    }
-                }
-                if (!def) {
-                    int skip = !strncmp(token.fContentStart, "explicit ", 9) ? 9 : 0;
-                    skip = !strncmp(token.fContentStart, "virtual ", 8) ? 8 : skip;
-                    const char* tokenEnd = token.methodEnd();
-                    string constructorName = className + "::";
-                    constructorName += string(token.fContentStart + skip,
-                            tokenEnd - token.fContentStart - skip);
-                    def = root->find(constructorName, RootDefinition::AllowParens::kYes);
-                }
-                if (!def && 0 == token.fName.find("SK_")) {
-                    string incName = token.fName + "()";
-                    string macroName = className + "::" + incName;
-                    def = root->find(macroName, RootDefinition::AllowParens::kYes);
-                    if (def) {
-                        if (def->fName == incName) {
-                            def->fVisited = true;
-                            if ("SK_TO_STRING_NONVIRT" == token.fName) {
-                                def = root->find(className + "::toString",
-                                        RootDefinition::AllowParens::kYes);
-                                if (def) {
-                                    def->fVisited = true;
-                                } else {
-                                    SkDebugf("missing toString bmh: %s\n", fullName.c_str());
-                                    fFailed = true;
-                                }
-                            }
-                            break;
-                        } else {
-                            SkDebugf("method macro differs from bmh: %s\n", fullName.c_str());
-                            fFailed = true;
-                        }
-                    }
-                }
-                if (!def) {
-                    bool allLower = true;
-                    for (size_t index = 0; index < token.fName.length(); ++index) {
-                        if (!islower(token.fName[index])) {
-                            allLower = false;
-                            break;
-                        }
-                    }
-                    if (allLower) {
-                        string lowerName = className + "::" + token.fName + "()";
-                        def = root->find(lowerName, RootDefinition::AllowParens::kYes);
-                    }
-                }
-                if (!def) {
-                    if (0 == token.fName.find("SkDEBUGCODE")) {
-                        break;
-                    }
-                }
-                if (!def) {
-        // simple method names inside nested classes have a bug and are missing trailing parens
-                    string withParens = fullName + "()"; // FIXME: this shouldn't be necessary
-                    def = root->find(withParens, RootDefinition::AllowParens::kNo);
-                }
-                if (!def) {
-                    if (!token.fUndocumented) {
-                        this->suggestFix(Suggest::kMethodMissing, token, root, nullptr);
-                        fFailed = true;
-                    }
-                    break;
-                }
-                if (token.fUndocumented) {
-                    // we can't report an error yet; if bmh documents this unnecessarily,
-                    // we'll detect that later. It may be that def points to similar
-                    // documented function.
-                    break;
-                }
-                if (def->crossCheck2(token)) {
-                    def->fVisited = true;
-                } else {
-                    SkDebugf("method differs from bmh: %s\n", fullName.c_str());
-                    fFailed = true;
-                }
-            } break;
-            case MarkType::kComment:
-                break;
-            case MarkType::kEnumClass:
-            case MarkType::kEnum: {
-                if (!def) {
-                    // work backwards from first word to deduce #Enum name
-                    TextParser firstMember("", token.fStart, token.fContentEnd, 0);
-                    SkAssertResult(firstMember.skipName("enum"));
-                    SkAssertResult(firstMember.skipToEndBracket('{'));
-                    firstMember.next();
-                    firstMember.skipWhiteSpace();
-                    SkASSERT('k' == firstMember.peek());
-                    const char* savePos = firstMember.fChar;
-                    firstMember.skipToNonName();
-                    const char* wordEnd = firstMember.fChar;
-                    firstMember.fChar = savePos;
-                    const char* lastUnderscore = nullptr;
-                    do {
-                        if (!firstMember.skipToEndBracket('_')) {
-                            break;
-                        }
-                        if (firstMember.fChar > wordEnd) {
-                            break;
-                        }
-                        lastUnderscore = firstMember.fChar;
-                    } while (firstMember.next());
-                    if (lastUnderscore) {
-                        ++lastUnderscore;
-                        string enumName(lastUnderscore, wordEnd - lastUnderscore);
-                        if (root) {
-                            string anonName = className + "::" + enumName + 's';
-                            def = root->find(anonName, RootDefinition::AllowParens::kYes);
-                        } else {
-                            auto enumIter = bmhParser.fEnumMap.find(enumName);
-                            if (bmhParser.fEnumMap.end() != enumIter) {
-                                RootDefinition* rootDef = &enumIter->second;
-                                def = rootDef;
-                            }
-                        }
-                    }
-                    if (!def && !root) {
-                        auto enumIter = bmhParser.fEnumMap.find(token.fName);
-                        if (bmhParser.fEnumMap.end() != enumIter) {
-                            def = &enumIter->second;
-                        }
-                        if (!def) {
-                            auto enumClassIter = bmhParser.fClassMap.find(token.fName);
-                            if (bmhParser.fClassMap.end() != enumClassIter) {
-                                def = &enumClassIter->second;
-                            }
-                        }
-                    }
-                    if (!def) {
-                        if (!token.fUndocumented) {
-                            SkDebugf("enum missing from bmh: %s\n", fullName.c_str());
-                            fFailed = true;
-                        }
-                        break;
-                    }
-                }
-                def->fVisited = true;
-                bool hasCode = false;
-                bool hasPopulate = true;
-                for (auto& child : def->fChildren) {
-                    if (MarkType::kCode == child->fMarkType) {
-                        hasPopulate = std::any_of(child->fChildren.begin(),
-                                child->fChildren.end(), [](auto grandChild){
-                                return MarkType::kPopulate == grandChild->fMarkType; });
-                        if (!hasPopulate) {
-                            def = child;
-                        }
-                        hasCode = true;
-                        break;
-                    }
-                }
-                if (!hasCode && !root) {
-                    const Definition* topic = def->topicParent();
-                    hasCode = std::any_of(topic->fChildren.begin(), topic->fChildren.end(),
-                            [](Definition* def){ return MarkType::kCode == def->fMarkType
-                            && def->fChildren.size() > 0 && MarkType::kPopulate ==
-                            def->fChildren.front()->fMarkType; });
-                }
-                if (!hasCode) {
-                    SkDebugf("enum code missing from bmh: %s\n", fullName.c_str());
-                    fFailed = true;
-                    break;
-                }
-                if (!hasPopulate) {
-                    if (def->crossCheck(token)) {
-                        def->fVisited = true;
-                    } else {
-                        SkDebugf("enum differs from bmh: %s\n", def->fName.c_str());
-                        fFailed = true;
-                    }
-                }
-                for (auto& member : token.fTokens) {
-                    if (MarkType::kMember != member.fMarkType) {
-                        continue;
-                    }
-                    string constName = MarkType::kEnumClass == token.fMarkType ?
-                            fullName : className;
-                    if (root) {
-                        constName += "::" + member.fName;
-                        def = root->find(constName, RootDefinition::AllowParens::kYes);
-                    } else {
-                        auto enumMapper = bmhParser.fEnumMap.find(token.fName);
-                        if (bmhParser.fEnumMap.end() != enumMapper) {
-                            auto& enumDoc = enumMapper->second;
-                            auto memberIter = enumDoc.fLeaves.find(member.fName);
-                            if (enumDoc.fLeaves.end() != memberIter) {
-                                def = &memberIter->second;
-                            }
-                        }
-                    }
-                    if (!def) {
-                        string innerName = key + "::" + member.fName;
-                        def = root->find(innerName, RootDefinition::AllowParens::kYes);
-                    }
-                    if (!def) {
-                        if (!member.fUndocumented) {
-                            SkDebugf("const missing from bmh: %s\n", constName.c_str());
-                            fFailed = true;
-                        }
-                    } else {
-                        def->fVisited = true;
-                    }
-                }
-                } break;
-            case MarkType::kMember:
-                if (def) {
-                    def->fVisited = true;
-                } else {
-                    SkDebugf("member missing from bmh: %s\n", fullName.c_str());
-                    fFailed = true;
-                }
-                break;
-            case MarkType::kTypedef:
-                if (!def && !root) {
-                    auto typedefIter = bmhParser.fTypedefMap.find(token.fName);
-                    if (bmhParser.fTypedefMap.end() != typedefIter) {
-                        def = &typedefIter->second;
-                    }
-                }
-                if (def) {
-                    def->fVisited = true;
-                } else {
-                    SkDebugf("typedef missing from bmh: %s\n", fullName.c_str());
-                    fFailed = true;
-                }
-                break;
-            case MarkType::kConst:
-                if (!def && !root) {
-                    auto constIter = bmhParser.fConstMap.find(token.fName);
-                    if (bmhParser.fConstMap.end() != constIter) {
-                        def = &constIter->second;
-                    }
-                }
-                if (def) {
-                    def->fVisited = true;
-                } else {
-                    if (!token.fUndocumented) {
-                        SkDebugf("const missing from bmh: %s\n", fullName.c_str());
-                        fFailed = true;
-                    }
-                }
-                break;
-            case MarkType::kDefine:
-                // TODO: incomplete
-                break;
-            default:
-                SkASSERT(0);  // unhandled
-                break;
-        }
-    }
-}
-
-bool IncludeParser::crossCheck(BmhParser& bmhParser) {
-    for (auto& classMapper : fIClassMap) {
-        string className = classMapper.first;
-        auto finder = bmhParser.fClassMap.find(className);
-        if (bmhParser.fClassMap.end() == finder) {
-            SkASSERT(string::npos != className.find("::"));
-            continue;
-        }
-    }
-    for (auto& classMapper : fIClassMap) {
-        if (classMapper.second.fUndocumented) {
-           continue;
-        }
-        string className = classMapper.first;
-        std::istringstream iss(className);
-        string classStr;
-        string classBase;
-        RootDefinition* root = nullptr;
-        while (std::getline(iss, classStr, ':')) {
-            if (root) {
-                if (!classStr.length()) {
-                    continue;
-                }
-                classBase += "::" + classStr;
-                auto finder = root->fBranches.find(classBase);
-                if (root->fBranches.end() != finder) {
-                    root = finder->second;
-                } else {
-                    SkASSERT(0);
-                }
-            } else {
-                classBase = classStr;
-                auto finder = bmhParser.fClassMap.find(classBase);
-                if (bmhParser.fClassMap.end() != finder) {
-                    root = &finder->second;
-                } else {
-                    SkASSERT(0);
-                }
-            }
-        }
-        this->checkTokens(classMapper.second.fTokens, classMapper.first, className, root,
-                bmhParser);
-    }
-    this->checkTokens(fGlobals, "", "", nullptr, bmhParser);
-    int crossChecks = 0;
-    string firstCheck;
-    for (auto& classMapper : fIClassMap) {
-        string className = classMapper.first;
-        auto finder = bmhParser.fClassMap.find(className);
-        if (bmhParser.fClassMap.end() == finder) {
-            continue;
-        }
-        RootDefinition* root = &finder->second;
-        if (!root->dumpUnVisited()) {
-            fFailed = true;
-        }
-        if (crossChecks) {
-            SkDebugf(".");
-        } else {
-            SkDebugf("cross-check");
-            firstCheck = className;
-        }
-        ++crossChecks;
-    }
-    if (crossChecks) {
-        if (1 == crossChecks) {
-            SkDebugf(" %s", firstCheck.c_str());
-        }
-        SkDebugf("\n");
-    }
-    bmhParser.fWroteOut = true;
-    return !fFailed;
-}
-
-IClassDefinition* IncludeParser::defineClass(const Definition& includeDef,
-        string name) {
-    string className;
-    const Definition* test = fParent;
-    while (Definition::Type::kFileType != test->fType) {
-        if (Definition::Type::kMark == test->fType && KeyWord::kClass == test->fKeyWord) {
-            className = test->fName + "::";
-            break;
-        }
-        test = test->fParent;
-    }
-    className += name;
-    unordered_map<string, IClassDefinition>& map = fIClassMap;
-    IClassDefinition& markupDef = map[className];
-    if (markupDef.fStart) {
-        typedef IClassDefinition* IClassDefPtr;
-        return INHERITED::reportError<IClassDefPtr>("class already defined");
-    }
-    markupDef.fFileName = fFileName;
-    markupDef.fStart = includeDef.fStart;
-    markupDef.fContentStart = includeDef.fStart;
-    markupDef.fName = className;
-    this->checkName(&markupDef);
-    markupDef.fContentEnd = includeDef.fContentEnd;
-    markupDef.fTerminator = includeDef.fTerminator;
-    markupDef.fParent = fParent;
-    markupDef.fLineCount = fLineCount;
-    markupDef.fMarkType = KeyWord::kStruct == includeDef.fKeyWord ?
-            MarkType::kStruct : MarkType::kClass;
-    markupDef.fKeyWord = includeDef.fKeyWord;
-    markupDef.fType = Definition::Type::kMark;
-    auto tokenIter = includeDef.fParent->fTokens.begin();
-    SkASSERT(includeDef.fParentIndex > 0);
-    std::advance(tokenIter, includeDef.fParentIndex - 1);
-    const Definition* priorComment = &*tokenIter;
-    markupDef.fUndocumented = priorComment->fUndocumented;
-    fParent = &markupDef;
-    return &markupDef;
-}
-
-void IncludeParser::dumpClassTokens(IClassDefinition& classDef) {
-    auto& tokens = classDef.fTokens;
-    bool wroteTail = true;
-    for (auto& token : tokens) {
-        if (Definition::Type::kMark == token.fType && MarkType::kComment == token.fMarkType) {
-            continue;
-        }
-        if (wroteTail && MarkType::kMember != token.fMarkType) {
-            this->writeBlockSeparator();
-        }
-        switch (token.fMarkType) {
-            case MarkType::kConst:
-                this->dumpConst(token, classDef.fName);
-            break;
-            case MarkType::kEnum:
-            case MarkType::kEnumClass:
-                this->dumpEnum(token, token.fName);
-            break;
-            case MarkType::kMethod:
-                if (!this->dumpMethod(token, classDef.fName)) {
-                    wroteTail = false;
-                    continue;
-                }
-            break;
-            case MarkType::kMember:
-                this->dumpMember(token);
-                continue;
-            break;
-            case MarkType::kTypedef:
-                this->dumpTypedef(token, classDef.fName);
-            break;
-            default:
-                SkASSERT(0);
-        }
-        this->dumpCommonTail(token);
-        wroteTail = true;
-    }
-}
-void IncludeParser::dumpComment(const Definition& token) {
-    fLineCount = token.fLineCount;
-    fChar = fLine = token.fContentStart;
-    fEnd = token.fContentEnd;
-    if (MarkType::kMethod == token.fMarkType) {
-        this->lf(2);
-        this->writeTag("Populate");
-        this->lf(2);
-        return;
-    }
-    for (const auto& child : token.fTokens) {
-        if (Definition::Type::kMark == child.fType && MarkType::kMember == child.fMarkType) {
-            break;
-        }
-        if (Definition::Type::kMark == child.fType && MarkType::kComment == child.fMarkType) {
-            if (child.fPrivate) {
-                break;
-            }
-            if (child.length() > 1) {
-                const char* start = child.fContentStart;
-                ptrdiff_t length = child.fContentEnd - start;
-                SkASSERT(length >= 0);
-                while (length && '/' == start[0]) {
-                    start += 1;
-                    --length;
-                }
-                while (length && '/' == start[length - 1]) {
-                    length -= 1;
-                    if (length && '*' == start[length - 1]) {
-                        length -= 1;
-                    }
-                }
-                if (length) {
-                    this->lf(2);
-                    if ("!< " == string(start, length).substr(0, 3)) {
-                        return;
-                    }
-                    this->writeBlock(length, start);
-                    this->lf(2);
-                }
-            }
-        }
-    }
-}
-
-void IncludeParser::dumpCommonTail(const Definition& token) {
-    this->lf(2);
-    this->writeTag("Example");
-    this->lf(1);
-    this->writeString("// incomplete");
-    this->lf(1);
-    this->writeEndTag();
-    this->lf(2);
-    this->writeTag("SeeAlso");
-    this->writeSpace();
-    this->writeString("incomplete");
-    this->lf(2);
-    this->writeEndTag(BmhParser::kMarkProps[(int) token.fMarkType].fName);
-    this->lf(2);
-}
-
-void IncludeParser::dumpConst(const Definition& token, string className) {
-    this->writeTag("Const");
-    this->writeSpace();
-    this->writeString(token.fName);
-    this->writeTagTable("Line", "incomplete");
-    this->lf(2);
-    this->dumpComment(token);
-}
-
-void IncludeParser::dumpDefine(const Definition& token) {
-    this->writeTag("Define", token.fName);
-    this->lf(2);
-    this->writeTag("Code");
-    this->lfAlways(1);
-    this->writeString("###$");
-    this->lfAlways(1);
-    this->indentToColumn(4);
-    this->writeBlock(token.fTerminator - token.fStart, token.fStart);
-    this->lf(1);
-    this->indentToColumn(0);
-    this->writeString("$$$#");
-
-    this->writeEndTag();
-    this->lf(2);
-    this->dumpComment(token);
-    for (auto& child : token.fTokens) {
-        if (MarkType::kComment == child.fMarkType) {
-            continue;
-        }
-        this->writeTag("Param", child.fName);
-        this->writeSpace();
-        this->writeString("incomplete");
-        this->writeSpace();
-        this->writeString("##");
-        this->lf(1);
-    }
-}
-
-void IncludeParser::dumpEnum(const Definition& token, string name) {
-    string tagType(MarkType::kEnum == token.fMarkType ? "Enum" : "EnumClass");
-    this->writeTag(tagType.c_str(), token.fName);
-    this->lf(2);
-    this->writeTag("Code");
-    this->writeTag("Populate");
-    this->writeEndTag();
-    this->lf(2);
-    this->dumpComment(token);
-    string prior;
-    for (auto& child : token.fTokens) {
-        if (MarkType::kComment == child.fMarkType) {
-            prior = string(child.fContentStart, child.length());
-        }
-        if (MarkType::kMember != child.fMarkType) {
-            continue;
-        }
-        this->writeTag("Const");
-        this->writeSpace();
-        this->writeString(child.fName);
-        this->writeSpace(2);
-        this->writeString("0 # incomplete; replace '0' with member value");
-        this->lf(1);
-        this->writeTagNoLF("Line", "#");
-        this->writeSpace();
-        if ("/!< " == prior.substr(0, 4)) {
-            this->writeString(prior.substr(4));
-        } else {
-            this->writeString("incomplete");
-        }
-        this->writeSpace();
-        this->writeString("##");
-        this->lf(1);
-        this->writeString("# incomplete; add description or delete");
-        this->writeEndTag();
-    }
-    this->lf(2);
-    this->writeString("# incomplete; add description or delete");
-    this->lf(2);
-}
-
-bool IncludeParser::dumpGlobals(string* globalFileName, long int* globalTell) {
-    bool hasGlobals = !fIDefineMap.empty() || !fIFunctionMap.empty() || !fIEnumMap.empty()
-            || !fITemplateMap.empty()|| !fITypedefMap.empty() || !fIUnionMap.empty();
-    if (!hasGlobals) {
-        return true;
-    }
-    size_t lastBSlash = fFileName.rfind('\\');
-    size_t lastSlash = fFileName.rfind('/');
-    size_t lastDotH = fFileName.rfind(".h");
-    SkASSERT(string::npos != lastDotH);
-    if (string::npos != lastBSlash && (string::npos == lastSlash
-            || lastBSlash < lastSlash)) {
-        lastSlash = lastBSlash;
-    } else if (string::npos == lastSlash) {
-        lastSlash = -1;
-    }
-    lastSlash += 1;
-    string globalsName = fFileName.substr(lastSlash, lastDotH - lastSlash);
-    string fileName = globalsName + "_Reference.bmh";
-    *globalFileName = fileName;
-    fOut = fopen(fileName.c_str(), "wb");
-    if (!fOut) {
-        SkDebugf("could not open output file %s\n", globalsName.c_str());
-        return false;
-    }
-    string prefixName = globalsName.substr(0, 2);
-    string topicName = globalsName.length() > 2 && isupper(globalsName[2]) &&
-        ("Sk" == prefixName || "Gr" == prefixName) ? globalsName.substr(2) : globalsName;
-    this->writeTagNoLF("Topic", topicName);
-    this->writeEndTag("Alias", topicName + "_Reference");
-    this->lf(2);
-    if (!fIDefineMap.empty() || !fIFunctionMap.empty() || !fIEnumMap.empty()
-            || !fITemplateMap.empty() || !fITypedefMap.empty() || !fIUnionMap.empty()) {
-        this->writeTag("Code");
-        this->writeTag("Populate");
-        this->writeEndTag();
-        this->lf(2);
-    }
-    std::map<int, Definition*> sortedDefs;
-    for (const auto& entry : fIDefineMap) {
-        sortedDefs[entry.second->fLineCount] = entry.second;
-    }
-    for (const auto& entry : fIFunctionMap) {
-        sortedDefs[entry.second->fLineCount] = entry.second;
-    }
-    for (const auto& entry : fIEnumMap) {
-        if (string::npos == entry.first.find("::")) {
-            sortedDefs[entry.second->fLineCount] = entry.second;
-        }
-    }
-    for (const auto& entry : fITemplateMap) {
-        sortedDefs[entry.second->fLineCount] = entry.second;
-    }
-    for (const auto& entry : fITypedefMap) {
-        sortedDefs[entry.second->fLineCount] = entry.second;
-    }
-    for (const auto& entry : fIUnionMap) {
-        sortedDefs[entry.second->fLineCount] = entry.second;
-    }
-    for (const auto& entry : sortedDefs) {
-        const Definition* def = entry.second;
-        this->writeBlockSeparator();
-        switch (def->fMarkType) {
-            case MarkType::kDefine:
-                this->dumpDefine(*def);
-                break;
-            case MarkType::kMethod:
-                if (!this->dumpMethod(*def, globalsName)) {
-                    continue;
-                }
-                break;
-            case MarkType::kEnum:
-            case MarkType::kEnumClass:
-                this->dumpEnum(*def, globalsName);
-                break;
-            case MarkType::kTemplate:
-                SkASSERT(0);  // incomplete
-                break;
-            case MarkType::kTypedef: {
-                this->writeTag("Typedef");
-                this->writeSpace();
-                TextParser parser(def);
-                if (!parser.skipExact("typedef")) {
-                    return false;
-                }
-                if (!parser.skipSpace()) {
-                    return false;
-                }
-                this->writeBlock(parser.fEnd - parser.fChar, parser.fChar);
-                this->lf(2);
-                this->dumpComment(*def);
-                this->writeEndTag(BmhParser::kMarkProps[(int) entry.second->fMarkType].fName);
-                this->lf(2);
-                } continue;
-            case MarkType::kUnion:
-                SkASSERT(0);  // incomplete
-                break;
-            default:
-                SkASSERT(0);
-        }
-        this->dumpCommonTail(*def);
-    }
-    *globalTell = ftell(fOut);
-    this->writeEndTag("Topic", topicName);
-    this->lfAlways(1);
-//    fclose(fOut);     // defer closing in case class needs to be also written here
-    return true;
-}
-
-bool IncludeParser::isClone(const Definition& token) {
-    string name = token.fName;
-    return name[name.length() - 2] == '_' && isdigit(name[name.length() - 1]);
-}
-
-bool IncludeParser::isConstructor(const Definition& token, string className) {
-    string name = token.fName;
-    return 0 == name.find(className) || '~' == name[0];
-}
-
-bool IncludeParser::isInternalName(const Definition& token) {
-    string name = token.fName;
-    // exception for this SkCanvas function .. for now
-    if (0 == token.fName.find("androidFramework_setDeviceClipRestriction")) {
-        return false;
-    }
-    return name.substr(0, 7) == "android"
-            || 0 == token.fName.find("internal_")
-            || 0 == token.fName.find("Internal_")
-            || 0 == token.fName.find("legacy_")
-            || 0 == token.fName.find("temporary_")
-            || 0 == token.fName.find("private_");
-}
-
-bool IncludeParser::isMember(const Definition& token) const {
-    if ('f' == token.fStart[0] && isupper(token.fStart[1])) {
-        return true;
-    }
-    if (!islower(token.fStart[0])) {
-        return false;
-    }
-    // make an exception for SkTextBlob::RunBuffer, sole struct with members not in fXxxx format
-    if (string::npos != token.fFileName.find("SkTextBlob.h")) {
-        const Definition* structToken = token.fParent;
-        if (!structToken) {
-            return false;
-        }
-        if (KeyWord::kStruct != structToken->fKeyWord) {
-            structToken = token.fParent->fParent;
-            if (!structToken) {
-                return false;
-            }
-            if (KeyWord::kStruct != structToken->fKeyWord) {
-                return false;
-            }
-        }
-        SkASSERT(structToken->fTokens.size() > 0);
-        const Definition& child = structToken->fTokens.front();
-        string structName(child.fContentStart, child.length());
-        if ("RunBuffer" != structName) {
-            return false;
-        }
-        string tokenName(token.fContentStart, token.length());
-        string allowed[] = { "glyphs", "pos", "utf8text", "clusters" };
-        for (auto allow : allowed) {
-            if (allow == tokenName) {
-                return true;
-            }
-        }
-    }
-    return false;
-}
-
-bool IncludeParser::isOperator(const Definition& token) {
-    return "operator" == token.fName.substr(0, 8);
-}
-
-bool IncludeParser::dumpMethod(const Definition& token, string className) {
-    if (std::any_of(token.fTokens.begin(), token.fTokens.end(),
-            [=](const Definition& def) { return MarkType::kComment == def.fMarkType
-            && this->isUndocumentable(def.fFileName, def.fContentStart, def.fContentEnd,
-            def.fLineCount); } )) {
-        return false;
-    }
-    this->writeTag("Method");
-    this->writeSpace();
-
-    string name = string(token.fStart ? token.fStart : token.fContentStart,
-            token.length());
-    this->writeBlock((int) name.size(), name.c_str());
-    string inType;
-    if (this->isConstructor(token, className)) {
-        inType = "Constructor";
-    } else if (this->isOperator(token)) {
-        inType = "Operator";
-    } else {
-        inType = "incomplete";
-    }
-    this->writeTag("In", inType);
-    this->writeTagTable("Line", "incomplete");
-    this->lf(2);
-    this->dumpComment(token);
-    return true;
-}
-
-void IncludeParser::dumpMember(const Definition& token) {
-    this->writeTag("Member");
-    this->writeSpace();
-    this->writeDefinition(token, token.fName, 2);
-    lf(1);
-    for (auto child : token.fChildren) {
-        this->writeDefinition(*child);
-    }
-    this->writeEndTag();
-    lf(2);
-}
-
-bool IncludeParser::dumpTokens() {
-    string globalFileName;
-    long int globalTell = 0;
-    if (!this->dumpGlobals(&globalFileName, &globalTell)) {
-        return false;
-    }
-    for (const auto& member : fIClassMap) {
-        if (string::npos != member.first.find("::")) {
-            continue;
-        }
-        if (!this->dumpTokens(member.first, globalFileName, &globalTell)) {
-            return false;
-        }
-    }
-    if (globalTell) {
-        fclose(fOut);
-        SkDebugf("wrote %s\n", globalFileName.c_str());
-    }
-    return true;
-}
-
-    // dump equivalent markup
-bool IncludeParser::dumpTokens(string skClassName, string globalFileName, long int* globalTell) {
-    string fileName = skClassName + "_Reference.bmh";
-    if (globalFileName != fileName) {
-        fOut = fopen(fileName.c_str(), "wb");
-        if (!fOut) {
-            SkDebugf("could not open output file %s\n", fileName.c_str());
-            return false;
-        }
-    } else {
-        fseek(fOut, *globalTell, SEEK_SET);
-        this->lf(2);
-        this->writeBlockSeparator();
-        *globalTell = 0;
-    }
-    string prefixName = skClassName.substr(0, 2);
-    string topicName = skClassName.length() > 2 && isupper(skClassName[2]) &&
-        ("Sk" == prefixName || "Gr" == prefixName) ? skClassName.substr(2) : skClassName;
-    if (globalFileName != fileName) {
-        this->writeTagNoLF("Topic", topicName);
-        this->writeEndTag("Alias", topicName + "_Reference");
-        this->lf(2);
-    }
-    auto& classMap = fIClassMap[skClassName];
-    SkASSERT(KeyWord::kClass == classMap.fKeyWord || KeyWord::kStruct == classMap.fKeyWord);
-    const char* containerType = KeyWord::kClass == classMap.fKeyWord ? "Class" : "Struct";
-    this->writeTag(containerType, skClassName);
-    this->lf(2);
-    auto& tokens = classMap.fTokens;
-    for (auto& token : tokens) {
-        if (Definition::Type::kMark != token.fType || MarkType::kComment != token.fMarkType) {
-            continue;
-        }
-        this->writeDefinition(token);
-        this->lf(1);
-    }
-    this->lf(2);
-    this->writeTag("Code");
-    this->writeTag("Populate");
-    this->writeEndTag();
-    this->lf(2);
-    for (auto& oneClass : fIClassMap) {
-        if (skClassName + "::" != oneClass.first.substr(0, skClassName.length() + 2)) {
-            continue;
-        }
-        string innerName = oneClass.first.substr(skClassName.length() + 2);
-        this->writeBlockSeparator();
-        KeyWord keyword = oneClass.second.fKeyWord;
-        SkASSERT(KeyWord::kClass == keyword || KeyWord::kStruct == keyword);
-        const char* containerType = KeyWord::kClass == keyword ? "Class" : "Struct";
-        this->writeTag(containerType, innerName);
-        this->writeTagTable("Line", "incomplete");
-        this->lf(2);
-        this->writeTag("Code");
-        this->writeEndTag("ToDo", "fill this in manually");
-        this->writeEndTag();
-        this->lf(2);
-        for (auto& token : oneClass.second.fTokens) {
-            if (Definition::Type::kMark != token.fType || MarkType::kComment != token.fMarkType) {
-                continue;
-            }
-            this->writeDefinition(token);
-        }
-        this->lf(2);
-        this->dumpClassTokens(oneClass.second);
-        this->lf(2);
-        this->writeEndTag(containerType, innerName);
-        this->lf(2);
-    }
-    this->dumpClassTokens(classMap);
-    this->writeEndTag(containerType, skClassName);
-    this->lf(2);
-    this->writeEndTag("Topic", topicName);
-    this->lfAlways(1);
-    fclose(fOut);
-    SkDebugf("wrote %s\n", fileName.c_str());
-    return true;
-}
-
-void IncludeParser::dumpTypedef(const Definition& token, string className) {
-    this->writeTag("Typedef");
-    this->writeSpace();
-    this->writeString(token.fName);
-    this->writeTagTable("Line", "incomplete");
-    this->lf(2);
-    this->dumpComment(token);
-}
-
-string IncludeParser::elidedCodeBlock(const Definition& iDef) {
-    SkASSERT(KeyWord::kStruct == iDef.fKeyWord || KeyWord::kClass == iDef.fKeyWord
-            || KeyWord::kTemplate == iDef.fKeyWord);
-    TextParser i(&iDef);
-    fElided = Elided::kYes;
-    MarkType markType = MarkType::kClass;
-    if (KeyWord::kTemplate == iDef.fKeyWord) {  // may be function
-        for (auto child : iDef.fChildren) {
-            if (MarkType::kMethod == child->fMarkType) {
-                markType = MarkType::kFunction;
-                break;
-            }
-        }
-    }
-    return this->writeCodeBlock(i, markType, 0);
-}
-
- string IncludeParser::filteredBlock(string inContents, string filterContents) {
-    string result;
-    const unordered_map<string, Definition*>* mapPtr = nullptr;
-    if ("Constant" == inContents) {
-        mapPtr = &fIConstMap;
-    } else {
-        SkASSERT(0); // only Constant supported for now
-    }
-    vector<Definition*> consts;
-    for (auto entry : *mapPtr) {
-        if (string::npos == entry.first.find(filterContents)) {
-            continue;
-        }
-        consts.push_back(entry.second);
-    }
-    std::sort(consts.begin(), consts.end(), [](Definition* def1, Definition* def2) {
-        return def1->fLineCount < def2->fLineCount;
-    } );
-    for (auto oneConst : consts) {
-        result += this->writeCodeBlock(*oneConst);
-    }
-    return result;
-}
-
-bool IncludeParser::findCommentAfter(const Definition& includeDef, Definition* markupDef) {
-    this->checkName(markupDef);
-    const Definition* parent = includeDef.fParent;
-    int index = includeDef.fParentIndex;
-    auto wordIter = parent->fTokens.begin();
-    std::advance(wordIter, index);
-    SkASSERT(&*wordIter == &includeDef);
-    size_t commentLine = 0;
-    do {
-        wordIter = std::next(wordIter);
-        if (parent->fTokens.end() == wordIter) {
-            break;
-        }
-        commentLine = wordIter->fLineCount;
-    } while (Punctuation::kSemicolon != wordIter->fPunctuation);
-    wordIter = std::next(wordIter);
-    if (parent->fTokens.end() != wordIter && Bracket::kSlashSlash == wordIter->fBracket
-            && wordIter->fLineCount == commentLine) {
-        return this->parseComment(wordIter->fFileName, wordIter->fContentStart,
-                wordIter->fContentEnd, wordIter->fLineCount, markupDef, &markupDef->fUndocumented);
-    }
-    return true;
-}
-
-bool IncludeParser::findComments(const Definition& includeDef, Definition* markupDef) {
-    this->checkName(markupDef);
-    // add comment preceding class, if any
-    Definition* parent = includeDef.fParent;
-    int index = includeDef.fParentIndex;
-    auto wordIter = parent->fTokens.begin();
-    std::advance(wordIter, index);
-    SkASSERT(&*wordIter == &includeDef);
-    while (parent->fTokens.begin() != wordIter) {
-        auto testIter = std::prev(wordIter);
-        if (Definition::Type::kWord != testIter->fType
-            && Definition::Type::kKeyWord != testIter->fType
-            && (Definition::Type::kBracket != testIter->fType
-            || Bracket::kAngle != testIter->fBracket)
-            && (Definition::Type::kPunctuation != testIter->fType
-            || Punctuation::kAsterisk != testIter->fPunctuation)) {
-            break;
-        }
-        wordIter = testIter;
-    }
-    auto commentIter = wordIter;
-    while (parent->fTokens.begin() != commentIter) {
-        auto testIter = std::prev(commentIter);
-        bool isComment = Definition::Type::kBracket == testIter->fType
-                && (Bracket::kSlashSlash == testIter->fBracket
-                || Bracket::kSlashStar == testIter->fBracket);
-        if (!isComment) {
-            break;
-        }
-        commentIter = testIter;
-    }
-    while (commentIter != wordIter) {
-        if (!this->parseComment(commentIter->fFileName, commentIter->fContentStart,
-                commentIter->fContentEnd, commentIter->fLineCount, markupDef,
-                &markupDef->fUndocumented)) {
-            return false;
-        }
-        commentIter->fUndocumented = markupDef->fUndocumented;
-        commentIter = std::next(commentIter);
-    }
-    return true;
-}
-
-Definition* IncludeParser::findIncludeObject(const Definition& includeDef, MarkType markType,
-        string typeName) {
-    typedef Definition* DefinitionPtr;
-    auto mapIter = std::find_if(fMaps.begin(), fMaps.end(),
-            [markType](DefinitionMap& defMap){ return markType == defMap.fMarkType; } );
-    if (mapIter == fMaps.end()) {
-        return nullptr;
-    }
-    if (mapIter->fInclude->end() == mapIter->fInclude->find(typeName)) {
-        return reportError<DefinitionPtr>("invalid mark type");
-    }
-    string name = this->uniqueName(*mapIter->fInclude, typeName);
-    Definition& markupDef = *(*mapIter->fInclude)[name];
-    if (markupDef.fStart) {
-        return reportError<DefinitionPtr>("definition already defined");
-    }
-    markupDef.fFileName = fFileName;
-    markupDef.fStart = includeDef.fStart;
-    markupDef.fContentStart = includeDef.fStart;
-    this->checkName(&markupDef);
-    markupDef.fName = name;
-    markupDef.fContentEnd = includeDef.fContentEnd;
-    markupDef.fTerminator = includeDef.fTerminator;
-    markupDef.fParent = fParent;
-    markupDef.fLineCount = includeDef.fLineCount;
-    markupDef.fMarkType = markType;
-    markupDef.fKeyWord = includeDef.fKeyWord;
-    markupDef.fType = Definition::Type::kMark;
-    return &markupDef;
-}
-
-Definition* IncludeParser::findMethod(const Definition& bmhDef) {
-    auto doubleColon = bmhDef.fName.rfind("::");
-    if (string::npos == doubleColon) {
-        const auto& iGlobalMethod = fIFunctionMap.find(bmhDef.fName);
-        SkASSERT(fIFunctionMap.end() != iGlobalMethod);
-        return iGlobalMethod->second;
-    }
-    string className = bmhDef.fName.substr(0, doubleColon);
-    const auto& iClass = fIClassMap.find(className);
-    if (fIClassMap.end() == iClass) {
-        return nullptr;
-    }
-    string methodName = bmhDef.fName.substr(doubleColon + 2);
-    auto& iTokens = iClass->second.fTokens;
-    const auto& iMethod = std::find_if(iTokens.begin(), iTokens.end(),
-            [methodName](Definition& token) {
-            return MarkType::kMethod == token.fMarkType
-                    && !token.fUndocumented
-                    && (methodName == token.fName
-                    || methodName == token.fName + "()"); } );
-    if (iTokens.end() != iMethod) {
-        return &*iMethod;
-    }
-    size_t subClassPos = className.rfind("::");
-    if (string::npos != subClassPos) {
-        className = className.substr(subClassPos + 2);
-    }
-    // match may be constructor; compare strings to see if this is so
-    if (string::npos == methodName.find('(')) {
-        return nullptr;
-    }
-    auto stripper = [](string s) -> string {
-        bool last = false;
-        string result;
-        for (char c : s) {
-            if (' ' >= c) {
-                if (!last) {
-                    last = true;
-                    result += ' ';
-                }
-                continue;
-            }
-            result += c;
-            last = false;
-        }
-        return result;
-    };
-    string strippedMethodName = stripper(methodName);
-    if (strippedMethodName == methodName) {
-        strippedMethodName = "";
-    }
-    const auto& cMethod = std::find_if(iTokens.begin(), iTokens.end(),
-            [className, methodName, stripper, strippedMethodName](Definition& token) {
-        if (MarkType::kMethod != token.fMarkType) {
-            return false;
-        }
-        if (token.fUndocumented) {
-            return false;
-        }
-        TextParser parser(&token);
-        const char* match = parser.strnstr(className.c_str(), parser.fEnd);
-        if (!match) {
-            return false;
-        }
-        parser.skipTo(match);
-        parser.skipExact(className.c_str());
-        if ('(' != parser.peek()) {
-            return false;
-        }
-        parser.skipToBalancedEndBracket('(', ')');
-        string iMethodName(match, parser.fChar - match);
-        if (methodName == iMethodName) {
-            return true;
-        }
-        if (strippedMethodName.empty()) {
-            return false;
-        }
-        string strippedIName = stripper(iMethodName);
-        return strippedIName == strippedMethodName;
-    } );
-    SkAssertResult(iTokens.end() != cMethod);
-    return &*cMethod;
-}
-
-Definition* IncludeParser::parentBracket(Definition* parent) const {
-    while (parent && Definition::Type::kBracket != parent->fType) {
-        parent = parent->fParent;
-    }
-    return parent;
-}
-
-Bracket IncludeParser::grandParentBracket() const {
-    Definition* parent = parentBracket(fParent);
-    parent = parentBracket(parent ? parent->fParent : nullptr);
-    return parent ? parent->fBracket : Bracket::kNone;
-}
-
-bool IncludeParser::inAlignAs() const {
-    if (fParent->fTokens.size() < 2) {
-        return false;
-    }
-    auto reverseIter = fParent->fTokens.end();
-    bool checkForBracket = true;
-    while (fParent->fTokens.begin() != reverseIter) {
-        std::advance(reverseIter, -1);
-        if (checkForBracket) {
-            if (Definition::Type::kBracket != reverseIter->fType) {
-                return false;
-            }
-            if (Bracket::kParen != reverseIter->fBracket) {
-                return false;
-            }
-            checkForBracket = false;
-            continue;
-        }
-        if (Definition::Type::kKeyWord != reverseIter->fType) {
-            return false;
-        }
-        return KeyWord::kAlignAs == reverseIter->fKeyWord;
-    }
-    return false;
-}
-
-const Definition* IncludeParser::include(string match) const {
-    for (auto& entry : fIncludeMap) {
-        if (string::npos == entry.first.find(match)) {
-            continue;
-        }
-        return &entry.second;
-    }
-    SkASSERT(0);
-    return nullptr;
-}
-
-// caller just returns, so report error here
-bool IncludeParser::parseClass(Definition* includeDef, IsStruct isStruct) {
-    SkASSERT(includeDef->fTokens.size() > 0);
-    // parse class header
-    auto iter = includeDef->fTokens.begin();
-    if (!strncmp(iter->fStart, "SK_API", iter->fContentEnd - iter->fStart)) {
-        // todo : documentation is ignoring this for now
-        iter = std::next(iter);
-    }
-    bool hasAlignAs = iter->fKeyWord == KeyWord::kAlignAs;
-    if (hasAlignAs) {
-        iter = std::next(iter);
-        if (Definition::Type::kBracket != iter->fType || Bracket::kParen != iter->fBracket) {
-            return includeDef->reportError<bool>("expected alignas argument");
-        }
-        iter = std::next(iter);
-    }
-    string nameStr(iter->fStart, iter->fContentEnd - iter->fStart);
-    includeDef->fName = nameStr;
-    this->checkName(includeDef);
-    iter = std::next(iter);
-    if (iter == includeDef->fTokens.end()) {
-        return true;  // forward declaration only
-    }
-    do {
-        if (iter == includeDef->fTokens.end()) {
-            return includeDef->reportError<bool>("unexpected end");
-        }
-        if ('{' == iter->fStart[0] && Definition::Type::kPunctuation == iter->fType) {
-            break;
-        }
-    } while (static_cast<void>(iter = std::next(iter)), true);
-    if (Punctuation::kLeftBrace != iter->fPunctuation) {
-        return iter->reportError<bool>("expected left brace");
-    }
-    IClassDefinition* markupDef = this->defineClass(*includeDef, nameStr);
-    if (!markupDef) {
-        return iter->reportError<bool>("expected markup definition");
-    }
-    markupDef->fStart = iter->fStart;
-    if (!this->findComments(*includeDef, markupDef)) {
-        return iter->reportError<bool>("find comments failed");
-    }
-    if (markupDef->fUndocumented) {
-        includeDef->fUndocumented = true;
-    }
-//    if (1 != includeDef->fChildren.size()) {
-//        return false;  // fix me: SkCanvasClipVisitor isn't correctly parsed
-//    }
-    auto includeDefIter = includeDef->fChildren.begin();
-    if (hasAlignAs) {
-        SkASSERT(includeDef->fChildren.end() != includeDefIter);
-        SkASSERT(Bracket::kParen == (*includeDefIter)->fBracket);
-        std::advance(includeDefIter, 1);
-    }
-    if (includeDef->fChildren.end() != includeDefIter
-            && Bracket::kAngle == (*includeDefIter)->fBracket) {
-        std::advance(includeDefIter, 1);
-    }
-    includeDef = *includeDefIter;
-    SkASSERT(Bracket::kBrace == includeDef->fBracket);
-    iter = includeDef->fTokens.begin();
-    // skip until public
-    int publicIndex = 0;
-    if (IsStruct::kNo == isStruct) {
-        const char* publicName = kKeyWords[(int) KeyWord::kPublic].fName;
-        size_t publicLen = strlen(publicName);
-        while (iter != includeDef->fTokens.end()
-                && (publicLen != (size_t) (iter->fContentEnd - iter->fStart)
-                || strncmp(iter->fStart, publicName, publicLen))) {
-            iter->fPrivate = true;
-            iter = std::next(iter);
-            ++publicIndex;
-        }
-    }
-    int keyIndex = publicIndex;
-    KeyWord currentKey = KeyWord::kPublic;
-    const char* publicName = kKeyWords[(int) KeyWord::kPublic].fName;
-    size_t publicLen = strlen(publicName);
-    const char* protectedName = kKeyWords[(int) KeyWord::kProtected].fName;
-    size_t protectedLen = strlen(protectedName);
-    const char* privateName = kKeyWords[(int) KeyWord::kPrivate].fName;
-    size_t privateLen = strlen(privateName);
-    auto childIter = includeDef->fChildren.begin();
-    while (includeDef->fChildren.end() != childIter && (*childIter)->fPrivate) {
-        std::advance(childIter, 1);
-    }
-    while (childIter != includeDef->fChildren.end()) {
-        Definition* child = *childIter;
-        while (child->fParentIndex > keyIndex && iter != includeDef->fTokens.end()) {
-            iter->fPrivate = KeyWord::kPublic != currentKey;
-            const char* testStart = iter->fStart;
-            size_t testLen = (size_t) (iter->fContentEnd - testStart);
-            iter = std::next(iter);
-            ++keyIndex;
-            if (publicLen == testLen && !strncmp(testStart, publicName, testLen)) {
-                currentKey = KeyWord::kPublic;
-                break;
-            }
-            if (protectedLen == testLen && !strncmp(testStart, protectedName, testLen)) {
-                currentKey = KeyWord::kProtected;
-                break;
-            }
-            if (privateLen == testLen && !strncmp(testStart, privateName, testLen)) {
-                currentKey = KeyWord::kPrivate;
-                break;
-            }
-        }
-        fLastObject = nullptr;
-        if (KeyWord::kPublic == currentKey) {
-            if (!this->parseObject(child, markupDef)) {
-                return false;
-            }
-        }
-        fLastObject = child;
-        childIter = std::next(childIter);
-    }
-    while (iter != includeDef->fTokens.end()) {
-        iter->fPrivate = KeyWord::kPublic != currentKey;
-        iter = std::next(iter);
-    }
-    SkASSERT(fParent->fParent);
-    fParent = fParent->fParent;
-    return true;
-}
-
-bool IncludeParser::isUndocumentable(string filename, const char* start, const char* end,
-        int lineCount) {
-    TextParser parser(filename, start, end, lineCount);
-    const vector<string> skipWords = { "deprecated", "experimental", "private" };
-    const vector<string> butNot = { "to be deprecated", "may be deprecated" };
-    const vector<string> alsoNot = { "todo" };
-    string match = parser.anyWord(skipWords, 0);
-    if ("" != match) {
-        if (parser.anyWord(alsoNot, 0).empty()
-                && ("deprecated" != match || parser.anyWord(butNot, 2).empty())) {
-            return true;
-        }
-    }
-    return false;
-}
-
-bool IncludeParser::parseComment(string filename, const char* start, const char* end,
-        int lineCount, Definition* markupDef, bool* undocumentedPtr) {
-    if (this->isUndocumentable(filename, start, end, lineCount)) {
-        *undocumentedPtr = true;
-    }
-    // parse doxygen if present
-    TextParser parser(filename, start, end, lineCount);
-    if (parser.startsWith("**")) {
-        parser.next();
-        parser.next();
-        parser.skipWhiteSpace();
-        if ('\\' == parser.peek()) {
-            parser.next();
-            // Doxygen tag may be "file" or "fn" in addition to "class", "enum", "struct"
-            if (parser.skipExact("file")) {
-                if (Definition::Type::kFileType != fParent->fType) {
-                    return reportError<bool>("expected parent is file");
-                }
-                string filename = markupDef->fileName();
-                if (!parser.skipWord(filename.c_str())) {
-                    return reportError<bool>("missing object type");
-                }
-            } else if (parser.skipExact("fn")) {
-                SkASSERT(0);  // incomplete
-            } else {
-                if (!parser.skipWord(kKeyWords[(int) markupDef->fKeyWord].fName)) {
-                    return reportError<bool>("missing object type");
-                }
-                if (!parser.skipWord(markupDef->fName.c_str()) &&
-                        KeyWord::kEnum != markupDef->fKeyWord) {
-                    return reportError<bool>("missing object name");
-                }
-            }
-        }
-    }
-    // remove leading '*' if present
-    Definition* parent = markupDef->fTokens.size() ? &markupDef->fTokens.back() : markupDef;
-    while (!parser.eof() && parser.skipWhiteSpace()) {
-        while ('*' == parser.peek()) {
-            parser.next();
-            if (parser.eof()) {
-                break;
-            }
-            parser.skipWhiteSpace();
-        }
-        if (parser.eof()) {
-            break;
-        }
-        const char* lineEnd = parser.trimmedLineEnd();
-        markupDef->fTokens.emplace_back(MarkType::kComment, parser.fChar, lineEnd,
-                parser.fLineCount, parent, '\0');
-        parser.skipToEndBracket('\n');
-    }
-    return true;
-}
-
-/*
-    find comment either in front of or after the const def and then extract if the
-    const is undocumented
- */
-bool IncludeParser::parseConst(Definition* child, Definition* markupDef) {
-    if (!markupDef) {
-        fGlobals.emplace_back(MarkType::kConst, child->fContentStart, child->fContentEnd,
-                child->fLineCount, fParent, '\0');
-        Definition* globalMarkupChild = &fGlobals.back();
-        string globalUniqueName = this->uniqueName(fIConstMap, child->fName);
-        globalMarkupChild->fName = globalUniqueName;
-        if (!this->findComments(*child, globalMarkupChild)) {
-            return false;
-        }
-        if (!this->findCommentAfter(*child, globalMarkupChild)) {
-            return false;
-        }
-        if (globalMarkupChild->fUndocumented) {
-            child->fUndocumented = true;
-        } else {
-            fIConstMap[globalUniqueName] = globalMarkupChild;
-        }
-        return true;
-    }
-    markupDef->fTokens.emplace_back(MarkType::kConst, child->fContentStart, child->fContentEnd,
-        child->fLineCount, markupDef, '\0');
-    Definition* markupChild = &markupDef->fTokens.back();
-    markupChild->fName = child->fName;
-    markupChild->fTerminator = markupChild->fContentEnd;
-    IClassDefinition& classDef = fIClassMap[markupDef->fName];
-    classDef.fConsts[child->fName] = markupChild;
-    if (!this->findComments(*child, markupChild)) {
-        return false;
-    }
-    if (!this->findCommentAfter(*child, markupChild)) {
-        return false;
-    }
-    if (markupChild->fUndocumented) {
-        child->fUndocumented = true;
-    } else {
-        fIConstMap[child->fName] = markupChild;
-    }
-    return true;
-}
-
-bool IncludeParser::parseDefine(Definition* child, Definition* markupDef) {
-    TextParser parser(child);
-    if (!parser.skipExact("#define")) {
-        return false;
-    }
-    if (!parser.skipSpace()) {
-        return false;
-    }
-    const char* nameStart = parser.fChar;
-    parser.skipToNonAlphaNum(); // FIXME: just want to skip isalnum() and '_'
-    if (parser.eof()) {
-        return true;    // do nothing if #define doesn't define anything
-    }
-    string nameStr(nameStart, parser.fChar - nameStart);
-    struct Param {
-        const char* fStart;
-        const char* fEnd;
-    };
-    vector<Param> params;
-    if ('(' == parser.peek()) {
-        parser.next();
-        if (!parser.skipSpace()) {
-            return false;
-        }
-        do {
-            const char* paramStart = parser.fChar;
-            if (!parser.skipExact("...")) {
-                parser.skipToNonAlphaNum();
-            }
-            if (parser.eof()) {
-                return false;
-            }
-            params.push_back({paramStart, parser.fChar});
-            if (!parser.skipSpace()) {
-                return false;
-            }
-            if (')' == parser.peek()) {
-                parser.next();
-                break;
-            }
-            if (',' != parser.next()) {
-                return false;
-            }
-            if (!parser.skipSpace()) {
-                return false;
-            }
-        } while (true);
-    }
-    if (!parser.skipSpace()) {
-        return false;
-    }
-    if (!markupDef) {
-        fGlobals.emplace_back(MarkType::kDefine, nameStart, child->fContentEnd,
-                child->fLineCount, fParent, '\0');
-        Definition* globalMarkupChild = &fGlobals.back();
-        string globalUniqueName = this->uniqueName(fIDefineMap, nameStr);
-        globalMarkupChild->fName = globalUniqueName;
-        globalMarkupChild->fTerminator = child->fContentEnd;
-        if (!this->findComments(*child, globalMarkupChild)) {
-            return false;
-        }
-        if (!globalMarkupChild->fUndocumented) {
-            fIDefineMap[globalUniqueName] = globalMarkupChild;
-        }
-        for (Param param : params) {
-            globalMarkupChild->fTokens.emplace_back(MarkType::kParam, param.fStart, param.fEnd,
-                    child->fLineCount, globalMarkupChild, '\0');
-            Definition* paramChild = &globalMarkupChild->fTokens.back();
-            paramChild->fName = string(param.fStart, param.fEnd - param.fStart);
-            this->checkName(paramChild);
-            paramChild->fTerminator = param.fEnd;
-        }
-        return true;
-    }
-    markupDef->fTokens.emplace_back(MarkType::kDefine, child->fContentStart, child->fContentEnd,
-            child->fLineCount, markupDef, '\0');
-    Definition* markupChild = &markupDef->fTokens.back();
-    markupChild->fName = nameStr;
-    markupChild->fTerminator = markupChild->fContentEnd;
-    IClassDefinition& classDef = fIClassMap[markupDef->fName];
-    if (!this->findComments(*child, markupChild)) {
-        return false;
-    }
-    if (markupChild->fUndocumented) {
-        child->fUndocumented = true;
-    } else {
-        classDef.fDefines[nameStr] = markupChild;
-        fIDefineMap[nameStr] = markupChild;
-    }
-    return true;
-}
-
-bool IncludeParser::parseEnum(Definition* child, Definition* markupDef) {
-	if (!child->fTokens.size()) {
-		return true;	// if enum is a forward declaration, do nothing
-	}
-    bool isEnumClass = false;
-    Definition* parent = child;
-    auto token = parent->fTokens.begin();
-    if (Definition::Type::kKeyWord == token->fType && KeyWord::kClass == token->fKeyWord) {
-        isEnumClass = true;
-        parent = &*token;
-        token = parent->fTokens.begin();
-    }
-    SkASSERT(Definition::Type::kWord == token->fType);
-    string nameStr = string(token->fStart, token->fContentEnd - token->fStart);
-    Definition* markupChild;
-    if (!markupDef) {
-        fGlobals.emplace_back(MarkType::kEnum, child->fContentStart, child->fContentEnd,
-                child->fLineCount, fParent, '\0');
-        markupChild = &fGlobals.back();
-        string globalUniqueName = this->uniqueName(fIEnumMap, nameStr);
-        markupChild->fName = globalUniqueName;
-        markupChild->fTerminator = child->fContentEnd;
-        if (!markupChild->fUndocumented) {
-            fIEnumMap[globalUniqueName] = markupChild;
-        }
-    } else {
-        markupDef->fTokens.emplace_back(MarkType::kEnum, child->fContentStart, child->fContentEnd,
-            child->fLineCount, markupDef, '\0');
-        markupChild = &markupDef->fTokens.back();
-    }
-    SkASSERT(KeyWord::kNone == markupChild->fKeyWord);
-    markupChild->fKeyWord = KeyWord::kEnum;
-    if (isEnumClass) {
-        markupChild->fMarkType = MarkType::kEnumClass;
-    }
-    if (markupDef) {
-        markupChild->fName = markupDef->fName + "::" + nameStr;
-    }
-    if (!this->findComments(*child, markupChild)) {
-        return false;
-    }
-    if (markupChild->fUndocumented) {
-        child->fUndocumented = true;
-    }
-    if (!this->parseEnumConst(token, parent->fTokens.end(), markupChild)) {
-        return false;
-    }
-    for (auto outsideMember : child->fChildren) {
-        if (Definition::Type::kBracket == outsideMember->fType) {
-            continue;
-        }
-        SkASSERT(Definition::Type::kKeyWord == outsideMember->fType);
-        if (KeyWord::kClass == outsideMember->fKeyWord) {
-            continue;
-        }
-        SkASSERT(KeyWord::kStatic == outsideMember->fKeyWord);
-        markupChild->fTokens.emplace_back(MarkType::kMember, outsideMember->fContentStart,
-                outsideMember->fContentEnd, outsideMember->fLineCount, markupChild, '\0');
-        Definition* member = &markupChild->fTokens.back();
-        member->fName = outsideMember->fName;
-        this->checkName(member);
-        // FIXME: ? add comment as well ?
-        markupChild->fChildren.push_back(member);
-    }
-    if (markupDef) {
-        IClassDefinition& classDef = fIClassMap[markupDef->fName];
-        SkASSERT(classDef.fStart);
-        string uniqueName = this->uniqueName(classDef.fEnums, nameStr);
-        string fullName = markupChild->fName;
-        markupChild->fName = uniqueName;
-        classDef.fEnums[uniqueName] = markupChild;
-        if (!markupChild->fUndocumented) {
-            fIEnumMap[fullName] = markupChild;
-        }
-    }
-    return true;
-}
-
-bool IncludeParser::parseOneEnumConst(list<Definition>& constList,
-        Definition* markupChild, bool skipWord) {
-    auto memberIter = constList.begin();
-    const auto memberIterEnd = constList.end();
-    if (skipWord) {
-        SkASSERT(Definition::Type::kWord == memberIter->fType);
-        memberIter = std::next(memberIter);
-        SkASSERT(memberIterEnd != memberIter);
-    }
-    // token array has parse atoms; child array has comments
-    bool undocumented = false;
-    while (memberIterEnd != memberIter) {
-        while (Bracket::kSlashStar == memberIter->fBracket) {
-            if (!this->parseComment(memberIter->fFileName, memberIter->fContentStart,
-                    memberIter->fContentEnd, memberIter->fLineCount, markupChild, &undocumented)) {
-                return false;
-            }
-            memberIter = std::next(memberIter);
-            if (memberIterEnd == memberIter) {
-                return false;
-            }
-        }
-        if (Bracket::kPound == memberIter->fBracket) {
-            KeyWord keyWord = memberIter->fKeyWord;
-            bool sawIf = KeyWord::kIfdef == keyWord || KeyWord::kIf == keyWord
-                    || KeyWord::kElif == keyWord;
-            if (sawIf || KeyWord::kElse == keyWord) {
-                if (!parseOneEnumConst(memberIter->fTokens, markupChild, sawIf)) {
-                    return false;
-                }
-            } else {
-                SkASSERT(KeyWord::kEndif == keyWord || KeyWord::kError == keyWord);
-            }
-            memberIter = std::next(memberIter);
-            if (memberIterEnd == memberIter) {
-                break;
-            }
-            continue;
-        }
-        while (Definition::Type::kWord != memberIter->fType) {
-            memberIter = std::next(memberIter);
-            if (memberIterEnd == memberIter) {
-                return false;
-            }
-        }
-        auto memberStart = memberIter;
-        Definition* memberEnd = nullptr;
-        const char* last;
-        do {
-            last = memberIter->fContentEnd;
-            memberIter = std::next(memberIter);
-            if (memberIterEnd == memberIter) {
-                break;
-            }
-            memberEnd = &*memberIter;
-        } while (string::npos == string(last, memberIter->fContentStart).find(','));
-        if (!memberEnd) {
-            return false;
-        }
-        if (memberIterEnd != memberIter && Bracket::kSlashSlash == memberIter->fBracket) {
-            if (!this->parseComment(memberIter->fFileName, memberIter->fContentStart,
-                    memberIter->fContentEnd, memberIter->fLineCount, markupChild, &undocumented)) {
-                return false;
-            }
-            memberIter = std::next(memberIter);
-        }
-        markupChild->fTokens.emplace_back(MarkType::kMember, memberStart->fContentStart,
-                memberEnd->fContentEnd, memberStart->fLineCount, markupChild, '\0');
-        Definition* markupMember = &markupChild->fTokens.back();
-        string name = string(memberStart->fContentStart, memberStart->length());
-        memberStart->fName = name;
-        markupMember->fName = name;
-        this->checkName(markupMember);
-        memberStart->fUndocumented = markupMember->fUndocumented;
-        memberStart->fMarkType = MarkType::kMember;
-        undocumented = false;
-    }
-    return true;
-}
-
-bool IncludeParser::parseEnumConst(list<Definition>::iterator& tokenIter,
-        const list<Definition>::iterator& tokenEnd, Definition* markupChild) {
-    SkASSERT(Definition::Type::kWord == tokenIter->fType);  // should be enum name
-    tokenIter = std::next(tokenIter);
-    SkASSERT(tokenEnd != tokenIter);
-    if (Definition::Type::kKeyWord == tokenIter->fType) {
-        SkASSERT((unsigned) tokenIter->fKeyWord < SK_ARRAY_COUNT(kKeyWords));
-        SkASSERT(KeyProperty::kNumber == kKeyWords[(int) tokenIter->fKeyWord].fProperty);
-        tokenIter = std::next(tokenIter);
-        SkASSERT(tokenEnd != tokenIter);
-    }
-    SkASSERT(Punctuation::kLeftBrace == tokenIter->fPunctuation);
-    tokenIter = std::next(tokenIter);
-    SkASSERT(tokenEnd != tokenIter);
-    SkASSERT(Bracket::kBrace == tokenIter->fBracket);
-    return parseOneEnumConst(tokenIter->fTokens, markupChild, false);
-}
-
-bool IncludeParser::parseInclude(string name) {
-    fParent = &fIncludeMap[name];
-    fParent->fName = name;
-    this->checkName(fParent);
-    fParent->fFileName = fFileName;
-    fParent->fType = Definition::Type::kFileType;
-    fParent->fContentStart = fChar;
-    fParent->fContentEnd = fEnd;
-    // parse include file into tree
-    while (fChar < fEnd) {
-        if (!this->parseChar()) {
-            return false;
-        }
-    }
-    // parse tree and add named objects to maps
-    fParent = &fIncludeMap[name];
-    if (!this->parseObjects(fParent, nullptr)) {
-        return false;
-    }
-    return true;
-}
-
-bool IncludeParser::parseMember(Definition* child, Definition* markupDef) {
-    const char* typeStart = child->fChildren[0]->fContentStart;
-    markupDef->fTokens.emplace_back(MarkType::kMember, typeStart, child->fContentStart,
-        child->fLineCount, markupDef, '\0');
-    Definition* markupChild = &markupDef->fTokens.back();
-    TextParser nameParser(child);
-    nameParser.skipToNonName();
-    string nameStr = string(child->fContentStart, nameParser.fChar - child->fContentStart);
-    IClassDefinition& classDef = fIClassMap[markupDef->fName];
-    string uniqueName = this->uniqueName(classDef.fMethods, nameStr);
-    markupChild->fName = uniqueName;
-    this->checkName(markupChild);
-    markupChild->fTerminator = markupChild->fContentEnd;
-    if (!markupChild->fUndocumented) {
-        classDef.fMembers[uniqueName] = markupChild;
-    }
-    if (child->fParentIndex >= 2) {
-        auto comment = child->fParent->fTokens.begin();
-        std::advance(comment, child->fParentIndex - 2);
-        if (Definition::Type::kBracket == comment->fType
-                && (Bracket::kSlashStar == comment->fBracket
-                || Bracket::kSlashSlash == comment->fBracket)) {
-            TextParser parser(&*comment);
-            do {
-                parser.skipToAlpha();
-                if (parser.eof()) {
-                    break;
-                }
-                const char* start = parser.fChar;
-                const char* end = parser.trimmedBracketEnd('\n');
-                if (Bracket::kSlashStar == comment->fBracket) {
-                    const char* commentEnd = parser.strnstr("*/", end);
-                    if (commentEnd) {
-                        end = commentEnd;
-                    }
-                }
-                markupDef->fTokens.emplace_back(MarkType::kComment, start, end, child->fLineCount,
-                        markupDef, '\0');
-                Definition* commentChild = &markupDef->fTokens.back();
-                markupChild->fChildren.emplace_back(commentChild);
-                parser.skipTo(end);
-            } while (!parser.eof());
-        }
-    }
-    return true;
-}
-
-bool IncludeParser::parseMethod(Definition* child, Definition* markupDef) {
-    auto tokenIter = child->fParent->fTokens.begin();
-    std::advance(tokenIter, child->fParentIndex);
-    tokenIter = std::prev(tokenIter);
-    const char* nameEnd = tokenIter->fContentEnd;
-    bool addConst = false;
-    auto operatorCheck = tokenIter;
-    if ('[' == tokenIter->fStart[0] || '*' == tokenIter->fStart[0]) {
-        operatorCheck = std::prev(tokenIter);
-    }
-    if (KeyWord::kOperator == operatorCheck->fKeyWord) {
-        auto closeParen = std::next(tokenIter);
-        SkASSERT(Definition::Type::kBracket == closeParen->fType &&
-                '(' == closeParen->fContentStart[0]);
-        nameEnd = closeParen->fContentEnd + 1;
-        closeParen = std::next(closeParen);
-        if (Definition::Type::kKeyWord == closeParen->fType &&
-                KeyWord::kConst == closeParen->fKeyWord) {
-            addConst = true;
-        }
-        tokenIter = operatorCheck;
-    }
-    string nameStr(tokenIter->fStart, nameEnd - tokenIter->fStart);
-    if (addConst) {
-        nameStr += " const";
-    }
-    while (tokenIter != child->fParent->fTokens.begin()) {
-        auto testIter = std::prev(tokenIter);
-        switch (testIter->fType) {
-            case Definition::Type::kWord:
-                if (testIter == child->fParent->fTokens.begin() &&
-                        (KeyWord::kIfdef == child->fParent->fKeyWord ||
-                        KeyWord::kIfndef == child->fParent->fKeyWord ||
-                        KeyWord::kIf == child->fParent->fKeyWord)) {
-                    std::next(tokenIter);
-                    break;
-                }
-                goto keepGoing;
-            case Definition::Type::kKeyWord: {
-                KeyProperty keyProperty = kKeyWords[(int) testIter->fKeyWord].fProperty;
-                if (KeyProperty::kNumber == keyProperty || KeyProperty::kModifier == keyProperty) {
-                    goto keepGoing;
-                }
-            } break;
-            case Definition::Type::kBracket:
-                if (Bracket::kAngle == testIter->fBracket) {
-                    goto keepGoing;
-                }
-                break;
-            case Definition::Type::kPunctuation:
-                if (Punctuation::kSemicolon == testIter->fPunctuation
-                        || Punctuation::kLeftBrace == testIter->fPunctuation
-                        || Punctuation::kColon == testIter->fPunctuation) {
-                    break;
-                }
-            keepGoing:
-                tokenIter = testIter;
-                continue;
-            default:
-                break;
-        }
-        break;
-    }
-    tokenIter->fName = nameStr;     // simple token stream, OK if name is duplicate
-    tokenIter->fMarkType = MarkType::kMethod;
-    tokenIter->fPrivate = string::npos != nameStr.find("::")
-            && KeyWord::kTemplate != child->fParent->fKeyWord;
-    this->checkName(&*tokenIter);
-    auto testIter = child->fParent->fTokens.begin();
-    SkASSERT(child->fParentIndex > 0);
-    std::advance(testIter, child->fParentIndex - 1);
-    if (tokenIter->fParent && KeyWord::kIfdef == tokenIter->fParent->fKeyWord &&
-            0 == tokenIter->fParentIndex) {
-        tokenIter = std::next(tokenIter);
-    }
-    const char* start = tokenIter->fContentStart;
-    const char* end = tokenIter->fContentEnd;
-    const char kDebugCodeStr[] = "SkDEBUGCODE";
-    const size_t kDebugCodeLen = sizeof(kDebugCodeStr) - 1;
-    if (end - start == kDebugCodeLen && !strncmp(start, kDebugCodeStr, kDebugCodeLen)) {
-        std::advance(testIter, 1);
-        start = testIter->fContentStart + 1;
-        end = testIter->fContentEnd - 1;
-    } else {
-        end = testIter->fContentEnd;
-        do {
-            std::advance(testIter, 1);
-            if (testIter == child->fParent->fTokens.end()) {
-                break;
-            }
-            switch (testIter->fType) {
-                case Definition::Type::kPunctuation:
-                    SkASSERT(Punctuation::kSemicolon == testIter->fPunctuation
-                            || Punctuation::kLeftBrace == testIter->fPunctuation
-                            || Punctuation::kColon == testIter->fPunctuation);
-                    end = testIter->fStart;
-                    break;
-                case Definition::Type::kKeyWord: {
-                    KeyProperty keyProperty = kKeyWords[(int) testIter->fKeyWord].fProperty;
-                    if (KeyProperty::kNumber == keyProperty || KeyProperty::kModifier == keyProperty) {
-                        continue;
-                    }
-                    } break;
-                default:
-                    continue;
-            }
-            break;
-        } while (true);
-    }
-    while (end > start && ' ' >= end[-1]) {
-        --end;
-    }
-    if (!markupDef) {
-        auto parentIter = child->fParent->fTokens.begin();
-        SkASSERT(child->fParentIndex > 0);
-        std::advance(parentIter, child->fParentIndex - 1);
-        Definition* methodName = &*parentIter;
-        TextParser nameParser(methodName);
-        if (nameParser.skipToEndBracket(':') && nameParser.startsWith("::")) {
-            return true;  // expect this is inline class definition outside of class
-        }
-        fGlobals.emplace_back(MarkType::kMethod, start, end, tokenIter->fLineCount,
-                fParent, '\0');
-        Definition* globalMarkupChild = &fGlobals.back();
-        string globalUniqueName = this->uniqueName(fIFunctionMap, nameStr);
-        globalMarkupChild->fName = globalUniqueName;
-        if (!this->findComments(*child, globalMarkupChild)) {
-            return false;
-        }
-        if (globalMarkupChild->fUndocumented) {
-            child->fUndocumented = true;
-        } else {
-            fIFunctionMap[globalUniqueName] = globalMarkupChild;
-        }
-        return true;
-    }
-    markupDef->fTokens.emplace_back(MarkType::kMethod, start, end, tokenIter->fLineCount,
-            markupDef, '\0');
-    Definition* markupChild = &markupDef->fTokens.back();
-    {
-        auto mapIter = fIClassMap.find(markupDef->fName);
-        SkASSERT(fIClassMap.end() != mapIter);
-        IClassDefinition& classDef = mapIter->second;
-        SkASSERT(classDef.fStart);
-        string uniqueName = this->uniqueName(classDef.fMethods, nameStr);
-        markupChild->fName = uniqueName;
-        if (!this->findComments(*child, markupChild)) {
-            return false;
-        }
-        if (markupChild->fUndocumented) {
-            tokenIter->fUndocumented = true;
-        } else {
-            classDef.fMethods[uniqueName] = markupChild;
-        }
-    }
-    return true;
-}
-
-bool IncludeParser::parseObjects(Definition* parent, Definition* markupDef) {
-    fPriorObject = nullptr;
-    for (auto child : parent->fChildren) {
-        if (!this->parseObject(child, markupDef)) {
-            return false;
-        }
-        fPriorObject = child;
-    }
-    return true;
-}
-
-bool IncludeParser::parseObject(Definition* child, Definition* markupDef) {
-    // set up for error reporting
-    fLine = fChar = child->fStart;
-    fEnd = child->fContentEnd;
-    // todo: put original line number in child as well
-    switch (child->fType) {
-        case Definition::Type::kKeyWord:
-            switch (child->fKeyWord) {
-                case KeyWord::kClass:
-                    if (!this->parseClass(child, IsStruct::kNo)) {
-                        return false;
-                    }
-                    break;
-                case KeyWord::kStatic:
-                case KeyWord::kConst:
-                case KeyWord::kConstExpr:
-                    if (!this->parseConst(child, markupDef)) {
-                        return child->reportError<bool>("failed to parse const or constexpr");
-                    }
-                    break;
-                case KeyWord::kEnum:
-                    if (!this->parseEnum(child, markupDef)) {
-                        return child->reportError<bool>("failed to parse enum");
-                    }
-                    break;
-                case KeyWord::kStruct:
-                    if (!this->parseClass(child, IsStruct::kYes)) {
-                        return child->reportError<bool>("failed to parse struct");
-                    }
-                    break;
-                case KeyWord::kTemplate:
-                    if (!this->parseTemplate(child, markupDef)) {
-                        return child->reportError<bool>("failed to parse template");
-                    }
-                    break;
-                case KeyWord::kTypedef:
-                    if (!this->parseTypedef(child, markupDef)) {
-                        return child->reportError<bool>("failed to parse typedef");
-                    }
-                    break;
-                case KeyWord::kUnion:
-                    if (!this->parseUnion()) {
-                        return child->reportError<bool>("failed to parse union");
-                    }
-                    break;
-                case KeyWord::kUsing:
-                    if (!this->parseUsing()) {
-                        return child->reportError<bool>("failed to parse using");
-                    }
-                    break;
-                default:
-                    return child->reportError<bool>("unhandled keyword");
-            }
-            break;
-        case Definition::Type::kBracket:
-            switch (child->fBracket) {
-                case Bracket::kParen:
-                    {
-                        auto tokenIter = child->fParent->fTokens.begin();
-                        std::advance(tokenIter, child->fParentIndex);
-                        tokenIter = std::prev(tokenIter);
-                        TextParser previousToken(&*tokenIter);
-                        if (this->isMember(*tokenIter)) {
-                            break;
-                        }
-                        if (Bracket::kPound == child->fParent->fBracket &&
-                                KeyWord::kIf == child->fParent->fKeyWord) {
-                            // TODO: this will skip methods named defined() -- for the
-                            // moment there aren't any
-                            if (previousToken.startsWith("defined")) {
-                                break;
-                            }
-                        }
-                        if (previousToken.startsWith("sizeof") && 6 == previousToken.lineLength()) {
-                            break;
-                        }
-                    }
-                    if (fPriorObject && MarkType::kConst == fPriorObject->fMarkType) {
-                        break;
-                    }
-                    if (!this->parseMethod(child, markupDef)) {
-                        return child->reportError<bool>("failed to parse method");
-                    }
-                break;
-                case Bracket::kSlashSlash:
-                case Bracket::kSlashStar:
-                    // comments are picked up by parsing objects first
-                    break;
-                case Bracket::kPound:
-                    // special-case the #xxx xxx_DEFINED entries
-                    switch (child->fKeyWord) {
-                        case KeyWord::kIf:
-                        case KeyWord::kIfndef:
-                        case KeyWord::kIfdef:
-                            if (child->boilerplateIfDef()) {
-                                if (!this->parseObjects(child, markupDef)) {
-                                    return false;
-                                }
-                                break;
-                            }
-                            goto preproError;
-                        case KeyWord::kDefine:
-                            if (this->parseDefine(child, markupDef)) {
-                                break;
-                            }
-                            goto preproError;
-                        case KeyWord::kEndif:
-                            if (child->boilerplateEndIf()) {
-                                break;
-                            }
-                        case KeyWord::kError:
-                        case KeyWord::kInclude:
-                            // ignored for now
-                            break;
-                        case KeyWord::kElse:
-                            if (!this->parseObjects(child, markupDef)) {
-                                return false;
-                            }
-                            break;
-                        case KeyWord::kElif:
-                            // todo: handle these
-                            break;
-                        default:
-                        preproError:
-                            return child->reportError<bool>("unhandled preprocessor");
-                    }
-                    break;
-                case Bracket::kAngle:
-                    // pick up templated function pieces when method is found
-                    break;
-                case Bracket::kDebugCode:
-                    if (!this->parseObjects(child, markupDef)) {
-                        return false;
-                    }
-                    break;
-                case Bracket::kSquare: {
-                    // check to see if parent is operator, the only case we handle so far
-                    auto prev = child->fParent->fTokens.begin();
-                    std::advance(prev, child->fParentIndex - 1);
-                    if (KeyWord::kOperator != prev->fKeyWord) {
-                        return child->reportError<bool>("expected operator overload");
-                    }
-                    } break;
-                default:
-                    return child->reportError<bool>("unhandled bracket");
-            }
-            break;
-        case Definition::Type::kWord:
-            if (MarkType::kMember != child->fMarkType) {
-                return child->reportError<bool>("unhandled word type");
-            }
-            if (!this->parseMember(child, markupDef)) {
-                return child->reportError<bool>("unparsable member");
-            }
-            break;
-        default:
-            return child->reportError<bool>("unhandled type");
-            break;
-    }
-    return true;
-}
-
-bool IncludeParser::parseTemplate(Definition* child, Definition* markupDef) {
-    return this->parseObjects(child, markupDef);
-}
-
-bool IncludeParser::parseTypedef(Definition* child, Definition* markupDef) {
-    TextParser typedefParser(child);
-    typedefParser.skipExact("typedef");
-    typedefParser.skipWhiteSpace();
-    string nameStr = typedefParser.typedefName();
-    if (!markupDef) {
-        fGlobals.emplace_back(MarkType::kTypedef, child->fContentStart, child->fContentEnd,
-                child->fLineCount, fParent, '\0');
-        Definition* globalMarkupChild = &fGlobals.back();
-        string globalUniqueName = this->uniqueName(fITypedefMap, nameStr);
-        globalMarkupChild->fName = globalUniqueName;
-        if (!this->findComments(*child, globalMarkupChild)) {
-            return false;
-        }
-        if (globalMarkupChild->fUndocumented) {
-            child->fUndocumented = true;
-        } else {
-            fITypedefMap[globalUniqueName] = globalMarkupChild;
-        }
-        child->fName = nameStr;
-        return true;
-    }
-    markupDef->fTokens.emplace_back(MarkType::kTypedef, child->fContentStart, child->fContentEnd,
-        child->fLineCount, markupDef, '\0');
-    Definition* markupChild = &markupDef->fTokens.back();
-    markupChild->fName = nameStr;
-    this->checkName(markupChild);
-    markupChild->fTerminator = markupChild->fContentEnd;
-    IClassDefinition& classDef = fIClassMap[markupDef->fName];
-    classDef.fTypedefs[nameStr] = markupChild;
-    child->fName = markupDef->fName + "::" + nameStr;
-    this->checkName(child);
-    fITypedefMap[child->fName] = markupChild;
-    return true;
-}
-
-bool IncludeParser::parseUnion() {
-    // incomplete
-    return true;
-}
-
-bool IncludeParser::parseUsing() {
-    // incomplete
-    return true;
-}
-
-bool IncludeParser::parseChar() {
-    char test = *fChar;
-    if ('\\' == fPrev) {
-        if ('\n' == test) {
-//            ++fLineCount;
-            fLine = fChar + 1;
-        }
-        goto done;
-    }
-    switch (test) {
-        case '\n':
-//            ++fLineCount;
-            fLine = fChar + 1;
-            if (fInChar) {
-                return reportError<bool>("malformed char");
-            }
-            if (fInString) {
-                return reportError<bool>("malformed string");
-            }
-            if (!this->checkForWord()) {
-                return false;
-            }
-            if (Bracket::kPound == this->topBracket()) {
-                KeyWord keyWord = fParent->fKeyWord;
-                if (KeyWord::kNone == keyWord) {
-                    return this->reportError<bool>("unhandled preprocessor directive");
-                }
-                if (fInDefine) {
-                    SkASSERT(KeyWord::kDefine == keyWord);
-                    fInDefine = false;
-                }
-                if (KeyWord::kInclude == keyWord || KeyWord::kDefine == keyWord || KeyWord::kError == keyWord) {
-                    this->popBracket();
-                }
-                if (fInBrace) {
-                    SkASSERT(KeyWord::kDefine == fInBrace->fKeyWord);
-                    fInBrace = nullptr;
-                }
-            } else if (Bracket::kSlashSlash == this->topBracket()) {
-                this->popBracket();
-            }
-            break;
-        case '*':
-            if (!fInCharCommentString && '/' == fPrev) {
-                this->pushBracket(Bracket::kSlashStar);
-            }
-            if (!this->checkForWord()) {
-                return false;
-            }
-            if (!fInCharCommentString) {
-                this->addPunctuation(Punctuation::kAsterisk);
-            }
-            break;
-        case '/':
-            if ('*' == fPrev) {
-                if (!fInCharCommentString) {
-                    return reportError<bool>("malformed closing comment");
-                }
-                if (Bracket::kSlashStar == this->topBracket()) {
-                    TextParserSave save(this);
-                    this->next();  // include close in bracket
-                    this->popBracket();
-                    save.restore(); // put things back so nothing is skipped
-                }
-                break;
-            }
-            if (!fInCharCommentString && '/' == fPrev) {
-                this->pushBracket(Bracket::kSlashSlash);
-                break;
-            }
-            if (!this->checkForWord()) {
-                return false;
-            }
-            break;
-        case '\'':
-            if (Bracket::kChar == this->topBracket()) {
-                this->popBracket();
-            } else if (!fInComment && !fInString) {
-                if (fIncludeWord) {
-                    return this->reportError<bool>("word then single-quote");
-                }
-                this->pushBracket(Bracket::kChar);
-            }
-            break;
-        case '\"':
-            if (Bracket::kString == this->topBracket()) {
-                this->popBracket();
-            } else if (!fInComment && !fInChar) {
-                if (fIncludeWord) {
-                    return this->reportError<bool>("word then double-quote");
-                }
-                this->pushBracket(Bracket::kString);
-            }
-            break;
-        case '(':
-            if (fIncludeWord && fChar - fIncludeWord >= 10 &&
-                    !strncmp("SkDEBUGCODE", fIncludeWord, 10)) {
-                this->pushBracket(Bracket::kDebugCode);
-                break;
-            }
-        case ':':
-        case '[':
-        case '{': {
-            if (fInCharCommentString) {
-                break;
-            }
-            if (fInDefine && fInBrace) {
-                break;
-            }
-            if (':' == test && (fInBrace || ':' == fChar[-1] || ':' == fChar[1])) {
-                break;
-            }
-            if (fConstExpr) {
-                fConstExpr->fContentEnd = fParent->fTokens.back().fContentEnd;
-                fConstExpr = nullptr;
-            }
-            if (!fInBrace) {
-                if (!this->checkForWord()) {
-                    return false;
-                }
-                if (':' == test && !fInFunction) {
-                    break;
-                }
-                if ('{' == test) {
-                    this->addPunctuation(Punctuation::kLeftBrace);
-                } else if (':' == test) {
-                    this->addPunctuation(Punctuation::kColon);
-                }
-            }
-            if (fInBrace && '{' == test && Definition::Type::kBracket == fInBrace->fType
-                    && Bracket::kColon == fInBrace->fBracket) {
-                Definition* braceParent = fParent->fParent;
-                braceParent->fChildren.pop_back();
-                braceParent->fTokens.pop_back();
-                fParent = braceParent;
-                fInBrace = nullptr;
-            }
-            this->pushBracket(
-                    '(' == test ? Bracket::kParen :
-                    '[' == test ? Bracket::kSquare :
-                    '{' == test ? Bracket::kBrace :
-                                  Bracket::kColon);
-            if (!fInBrace
-                    && ('{' == test || (':' == test && ' ' >= fChar[1]))
-                    && fInFunction) {
-                fInBrace = fParent;
-            }
-            } break;
-        case '<':
-            if (fInCharCommentString || fInBrace) {
-                break;
-            }
-            if (!this->checkForWord()) {
-                return false;
-            }
-            if (fInEnum) {
-                break;
-            }
-            this->pushBracket(Bracket::kAngle);
-            // this angle bracket may be an operator or may be a bracket
-            // wait for balancing close angle, if any, to decide
-            break;
-        case ')':
-        case ']':
-        case '}': {
-            if (fInCharCommentString) {
-                break;
-            }
-            if (fInDefine && fInBrace) {
-                break;
-            }
-            if (!fInBrace) {
-                if (!this->checkForWord()) {
-                    return false;
-                }
-            }
-            bool popBraceParent = fInBrace == fParent;
-            Bracket match = ')' == test ? Bracket::kParen :
-                    ']' == test ? Bracket::kSquare : Bracket::kBrace;
-            if (match == this->topBracket()) {
-                this->popBracket();
-                if (!fInFunction) {
-                    fInFunction = ')' == test && !this->inAlignAs();
-                } else {
-                    fInFunction = '}' != test;
-                }
-            } else if (')' == test && Bracket::kDebugCode == this->topBracket()) {
-                this->popBracket();
-            } else if (Bracket::kAngle == this->topBracket()
-                    && match == this->grandParentBracket()) {
-                this->popBracket();
-                this->popBracket();
-            } else {
-                return reportError<bool>("malformed close bracket");
-            }
-            if (popBraceParent) {
-                Definition* braceParent = fInBrace->fParent;
-                braceParent->fChildren.pop_back();
-                braceParent->fTokens.pop_back();
-                fInBrace = nullptr;
-            }
-            } break;
-        case '>':
-            if (fInCharCommentString || fInBrace) {
-                break;
-            }
-            if (!this->checkForWord()) {
-                return false;
-            }
-            if (fInEnum) {
-                break;
-            }
-            if (Bracket::kPound == this->topBracket()) {
-                break;
-            }
-            if (Bracket::kAngle == this->topBracket()) {
-                // looks like angle pair are braces, not operators
-                this->popBracket();
-            } else {
-                return reportError<bool>("malformed close angle bracket");
-            }
-            break;
-        case '#': {
-            if (fInCharCommentString || fInBrace) {
-                break;
-            }
-            SkASSERT(!fIncludeWord);  // don't expect this, curious if it is triggered
-            this->pushBracket(Bracket::kPound);
-            break;
-        }
-        case ' ':
-            if (fInDefine && !fInBrace && Bracket::kPound == this->topBracket()) {
-                SkASSERT(KeyWord::kDefine == fParent->fKeyWord);
-                fInBrace = fParent;
-                // delimiting brackets are space ... unescaped-linefeed
-            }
-        case '&':
-        case ',':
-        case '+':
-        case '-':
-        case '!':
-            if (fInCharCommentString || fInBrace) {
-                break;
-            }
-            if (!this->checkForWord()) {
-                return false;
-            }
-            break;
-        case '=':
-            if (fInCharCommentString || fInBrace) {
-                break;
-            }
-            if (!this->checkForWord()) {
-                return false;
-            }
-            if (!fParent->fTokens.size()) {
-                break;
-            }
-            {
-                const Definition& lastToken = fParent->fTokens.back();
-                if (lastToken.fType != Definition::Type::kWord) {
-                    break;
-                }
-                string name(lastToken.fContentStart, lastToken.length());
-                if ("SK_" != name.substr(0, 3) && 'k' != name[0]) {
-                    break;
-                }
-                // find token on start of line
-                auto lineIter = fParent->fTokens.end();
-                do {
-                    if (fParent->fTokens.begin() == lineIter) {
-                        break;
-                    }
-                    --lineIter;
-                } while (lineIter->fContentStart > fLine);
-                if (lineIter->fContentStart < fLine && fParent->fTokens.end() != lineIter) {
-                    ++lineIter;
-                }
-                Definition* lineStart = &*lineIter;
-                // walk tokens looking for [template <typename T>] [static] [const | constexpr]
-                bool sawConst = false;
-                bool sawStatic = false;
-                bool sawTemplate = false;
-                bool sawType = false;
-                while (&lastToken != &*lineIter) {
-                    if (KeyWord::kTemplate == lineIter->fKeyWord) {
-                        if (sawConst || sawStatic || sawTemplate) {
-                            sawConst = false;
-                            break;
-                        }
-                        if (&lastToken == &*++lineIter) {
-                            break;
-                        }
-                        if (KeyWord::kTypename != lineIter->fKeyWord) {
-                            break;
-                        }
-                        if (&lastToken == &*++lineIter) {
-                            break;
-                        }
-                        if (Definition::Type::kWord != lineIter->fType) {
-                            break;
-                        }
-                        sawTemplate = true;
-                    } else if (KeyWord::kStatic == lineIter->fKeyWord) {
-                        if (sawConst || sawStatic) {
-                            sawConst = false;
-                            break;
-                        }
-                        sawStatic = true;
-                    } else if (KeyWord::kConst == lineIter->fKeyWord
-                            || KeyWord::kConstExpr == lineIter->fKeyWord) {
-                        if (sawConst) {
-                            sawConst = false;
-                            break;
-                        }
-                        sawConst = true;
-                    } else {
-                        if (sawType) {
-                            sawType = false;
-                            break;
-                        }
-                        if (Definition::Type::kKeyWord == lineIter->fType
-                                && KeyProperty::kNumber
-                                == kKeyWords[(int) lineIter->fKeyWord].fProperty) {
-                            sawType = true;
-                        } else if (Definition::Type::kWord == lineIter->fType) {
-                            string typeName(lineIter->fContentStart, lineIter->length());
-                            if ("Sk" != name.substr(0, 2)) {
-                                sawType = true;
-                            }
-                        }
-                    }
-                    ++lineIter;
-                }
-                if (sawType && sawConst) {
-                    // if found, name first
-                    lineStart->fName = name;
-                    lineStart->fMarkType = MarkType::kConst;
-                    this->checkName(lineStart);
-                    fParent->fChildren.emplace_back(lineStart);
-                    fConstExpr = lineStart;
-                }
-            }
-            break;
-        case ';':
-            if (fInCharCommentString || fInBrace) {
-                break;
-            }
-            if (!this->checkForWord()) {
-                return false;
-            }
-            if (fConstExpr) {
-                fConstExpr->fContentEnd = fParent->fTokens.back().fContentEnd;
-                fConstExpr = nullptr;
-            }
-            if (Definition::Type::kKeyWord == fParent->fType
-                    && KeyProperty::kObject == (kKeyWords[(int) fParent->fKeyWord].fProperty)) {
-                bool parentIsClass = KeyWord::kClass == fParent->fKeyWord;
-                if (parentIsClass && fParent->fParent &&
-                        KeyWord::kEnum == fParent->fParent->fKeyWord) {
-                    this->popObject();
-                }
-                if (KeyWord::kEnum == fParent->fKeyWord) {
-                    fInEnum = false;
-                }
-                parentIsClass |= KeyWord::kStruct == fParent->fKeyWord;
-                this->popObject();
-                if (parentIsClass && fParent && KeyWord::kTemplate == fParent->fKeyWord) {
-                    this->popObject();
-                }
-                fPriorEnum = nullptr;
-            } else if (Definition::Type::kBracket == fParent->fType
-                    && fParent->fParent && Definition::Type::kKeyWord == fParent->fParent->fType
-                    && KeyWord::kStruct == fParent->fParent->fKeyWord) {
-                list<Definition>::iterator baseIter = fParent->fTokens.end();
-                list<Definition>::iterator namedIter  = fParent->fTokens.end();
-                for (auto tokenIter = fParent->fTokens.end();
-                        fParent->fTokens.begin() != tokenIter; ) {
-                    --tokenIter;
-                    if (tokenIter->fLineCount == fLineCount) {
-                        if (this->isMember(*tokenIter)) {
-                            if (namedIter != fParent->fTokens.end()) {
-                                return reportError<bool>("found two named member tokens");
-                            }
-                            namedIter = tokenIter;
-                        }
-                        baseIter = tokenIter;
-                    } else {
-                        break;
-                    }
-                }
-                // FIXME: if a member definition spans multiple lines, this won't work
-                if (namedIter != fParent->fTokens.end()) {
-                    if (baseIter == namedIter) {
-                        return this->reportError<bool>("expected type before named token");
-                    }
-                    Definition* member = &*namedIter;
-                    member->fMarkType = MarkType::kMember;
-                    if (!member->fTerminator) {
-                        member->fTerminator = member->fContentEnd;
-                    }
-                    fParent->fChildren.push_back(member);
-                    for (auto nameType = baseIter; nameType != namedIter; ++nameType) {
-                        member->fChildren.push_back(&*nameType);
-                    }
-                }
-                fPriorEnum = nullptr;
-            } else if (fParent->fChildren.size() > 0) {
-                auto lastIter = fParent->fChildren.end();
-                Definition* priorEnum = fPriorEnum;
-                fPriorEnum = nullptr;
-                if (!priorEnum) {
-                    while (fParent->fChildren.begin() != lastIter) {
-                        std::advance(lastIter, -1);
-                        priorEnum = *lastIter;
-                        if (Definition::Type::kBracket != priorEnum->fType ||
-                                (Bracket::kSlashSlash != priorEnum->fBracket
-                                && Bracket::kSlashStar != priorEnum->fBracket)) {
-                            break;
-                        }
-                    }
-                    fPriorIndex = priorEnum->fParentIndex;
-                }
-                if (Definition::Type::kKeyWord == priorEnum->fType
-                        && KeyWord::kEnum == priorEnum->fKeyWord) {
-                    auto tokenWalker = fParent->fTokens.begin();
-                    std::advance(tokenWalker, fPriorIndex);
-                    while (tokenWalker != fParent->fTokens.end()) {
-                        std::advance(tokenWalker, 1);
-                        ++fPriorIndex;
-                        if (Punctuation::kSemicolon == tokenWalker->fPunctuation) {
-                            break;
-                        }
-                    }
-                    while (tokenWalker != fParent->fTokens.end()) {
-                        std::advance(tokenWalker, 1);
-                        const Definition* test = &*tokenWalker;
-                        if (Definition::Type::kBracket != test->fType ||
-                                (Bracket::kSlashSlash != test->fBracket
-                                && Bracket::kSlashStar != test->fBracket)) {
-                            break;
-                        }
-                    }
-                    auto saveTokenWalker = tokenWalker;
-                    Definition* start = &*tokenWalker;
-                    bool foundExpected = true;
-                    for (KeyWord expected : {KeyWord::kStatic, KeyWord::kConstExpr, KeyWord::kInt}){
-                        const Definition* test = &*tokenWalker;
-                        if (expected != test->fKeyWord) {
-                            foundExpected = false;
-                            break;
-                        }
-                        if (tokenWalker == fParent->fTokens.end()) {
-                            break;
-                        }
-                        std::advance(tokenWalker, 1);
-                    }
-                    if (!foundExpected) {
-                        foundExpected = true;
-                        tokenWalker = saveTokenWalker;
-                        for (KeyWord expected : {KeyWord::kStatic, KeyWord::kConst, KeyWord::kNone}){
-                            const Definition* test = &*tokenWalker;
-                            if (expected != test->fKeyWord) {
-                                foundExpected = false;
-                                break;
-                            }
-                            if (tokenWalker == fParent->fTokens.end()) {
-                                break;
-                            }
-                            if (KeyWord::kNone != expected) {
-                                std::advance(tokenWalker, 1);
-                            }
-                        }
-                        if (foundExpected) {
-                            auto nameToken = priorEnum->fTokens.begin();
-                            string enumName = string(nameToken->fContentStart,
-                                    nameToken->fContentEnd - nameToken->fContentStart);
-                            const Definition* test = &*tokenWalker;
-                            string constType = string(test->fContentStart,
-                                    test->fContentEnd - test->fContentStart);
-                            if (enumName != constType) {
-                                foundExpected = false;
-                            } else {
-                                std::advance(tokenWalker, 1);
-                            }
-                        }
-                    }
-                    if (foundExpected && tokenWalker != fParent->fTokens.end()) {
-                        const char* nameStart = tokenWalker->fStart;
-                        std::advance(tokenWalker, 1);
-                        if (tokenWalker != fParent->fTokens.end()) {
-                            TextParser tp(fFileName, nameStart, tokenWalker->fStart, fLineCount);
-                            tp.skipToNonName();
-                            start->fName = string(nameStart, tp.fChar - nameStart);
-                            this->checkName(start);
-                            start->fContentEnd = fChar;
-                            priorEnum->fChildren.emplace_back(start);
-                            fPriorEnum = priorEnum;
-                        }
-                    }
-                }
-            }
-            this->addPunctuation(Punctuation::kSemicolon);
-            fInFunction = false;
-            break;
-        case '~':
-            if (fInEnum) {
-                break;
-            }
-        case '0': case '1': case '2': case '3': case '4':
-        case '5': case '6': case '7': case '8': case '9':
-            // TODO: don't want to parse numbers, but do need to track for enum defs
-        //    break;
-        case 'A': case 'B': case 'C': case 'D': case 'E':
-        case 'F': case 'G': case 'H': case 'I': case 'J':
-        case 'K': case 'L': case 'M': case 'N': case 'O':
-        case 'P': case 'Q': case 'R': case 'S': case 'T':
-        case 'U': case 'V': case 'W': case 'X': case 'Y':
-        case 'Z': case '_':
-        case 'a': case 'b': case 'c': case 'd': case 'e':
-        case 'f': case 'g': case 'h': case 'i': case 'j':
-        case 'k': case 'l': case 'm': case 'n': case 'o':
-        case 'p': case 'q': case 'r': case 's': case 't':
-        case 'u': case 'v': case 'w': case 'x': case 'y':
-        case 'z':
-            if (fInCharCommentString || fInBrace) {
-                break;
-            }
-            if (!fIncludeWord) {
-                fIncludeWord = fChar;
-            }
-            break;
-    }
-done:
-    fPrev = test;
-    this->next();
-    return true;
-}
-
-void IncludeParser::validate() const {
-    IncludeParser::ValidateKeyWords();
-}
-
-bool IncludeParser::references(const SkString& file) const {
-    // if includes weren't passed one at a time, assume all references are valid
-    if (fIncludeMap.empty()) {
-        return true;
-    }
-    SkASSERT(file.endsWith(".bmh") );
-    string root(file.c_str(), file.size() - 4);
-    string kReference("_Reference");
-    if (string::npos != root.find(kReference)) {
-        root = root.substr(0, root.length() - kReference.length());
-    }
-    if (fIClassMap.end() != fIClassMap.find(root)) {
-        return true;
-    }
-    if (fIStructMap.end() != fIStructMap.find(root)) {
-        return true;
-    }
-    if (fIEnumMap.end() != fIEnumMap.find(root)) {
-        return true;
-    }
-    if (fITypedefMap.end() != fITypedefMap.find(root)) {
-        return true;
-    }
-    if (fIFunctionMap.end() != fIFunctionMap.find(root)) {
-        return true;
-    }
-    return false;
-}
-
-void IncludeParser::RemoveFile(const char* docs, const char* includes) {
-    if (!sk_isdir(includes)) {
-        IncludeParser::RemoveOneFile(docs, includes);
-    } else {
-        SkOSFile::Iter it(includes, ".h");
-        for (SkString file; it.next(&file); ) {
-            SkString p = SkOSPath::Join(includes, file.c_str());
-            const char* hunk = p.c_str();
-            if (!SkStrEndsWith(hunk, ".h")) {
-                continue;
-            }
-            IncludeParser::RemoveOneFile(docs, hunk);
-        }
-    }
-}
-
-void IncludeParser::RemoveOneFile(const char* docs, const char* includesFile) {
-    const char* lastForward = strrchr(includesFile, '/');
-    const char* lastBackward = strrchr(includesFile, '\\');
-    const char* last = lastForward > lastBackward ? lastForward : lastBackward;
-    if (!last) {
-        last = includesFile;
-    } else {
-        last += 1;
-    }
-    SkString baseName(last);
-    SkASSERT(baseName.endsWith(".h"));
-    baseName.remove(baseName.size() - 2, 2);
-    baseName.append("_Reference.bmh");
-    SkString fullName = docs ? SkOSPath::Join(docs, baseName.c_str()) : baseName;
-    remove(fullName.c_str());
-}
-
-static const char kMethodMissingStr[] =
-    "If the method requires documentation, add to "
-    "%s at minimum:\n"  // path to bmh file
-    "\n"
-    "#Method %s\n" // method declaration less implementation details
-    "#In  SomeSubtopicName\n"
-    "#Line # add a one line description here ##\n"
-    "#Populate\n"
-    "#NoExample\n"
-    "// or better yet, use #Example and put C++ code here\n"
-    "##\n"
-    "#SeeAlso optional related symbols\n"
-    "#Method ##\n"
-    "\n"
-    "Add to %s, at minimum:\n"  // path to include
-    "\n"
-    "/** (description) Starts with present tense action verb\n"
-    "    and end with a period.\n"
-    "%s"   // @param, @return if needed go here
-    "*/\n"
-    "%s ...\n" // method declaration
-    "\n"
-    "If the method does not require documentation,\n"
-    "add \"private\" or \"experimental\", as in:\n"
-    "\n"
-    "/** Experimental, do not use. And so on...\n"
-    "*/\n"
-    "%s ...\n" // method declaration
-    "\n"
-    ;
-
-// bDef does not have #Populate
-static const char kMethodDiffersNoPopStr[] =
-    "In %s:\n"              // path to bmh file
-    "#Method %s\n"          // method declaration less implementation details
-    "does not match doxygen comment of:\n"
-    "%s.\n"                 // method declaration
-    "\n"
-    ;
-
-static const char kMethodDiffersStr[] =
-    "In %s:\n"                        // path to include
-    "%s\n"                            // method declaration
-    "does not match doxygen comment.\n"
-    "\n"
-    ;
-
-void IncludeParser::suggestFix(Suggest suggest, const Definition& iDef,
-        const RootDefinition* root, const Definition* bDef) {
-    string methodNameStr(iDef.fContentStart, iDef.length());
-    const char* methodName = methodNameStr.c_str();
-    TextParser lessImplParser(&iDef);
-    if (lessImplParser.skipExact("static")) {
-        lessImplParser.skipWhiteSpace();
-    }
-    // TODO : handle debug wrapper
-    /* bool inDebugWrapper = */ Definition::SkipImplementationWords(lessImplParser);
-    string lessImplStr(lessImplParser.fChar, lessImplParser.fEnd - lessImplParser.fChar);
-    const char* methodNameLessImpl = lessImplStr.c_str();
-    // return result, if any is substr from 0 to location of iDef.fName
-    size_t namePos = methodNameStr.find(iDef.fName);
-    SkASSERT(string::npos != namePos);
-    size_t funcEnd = namePos;
-    while (funcEnd > 0 && ' ' >= methodNameStr[funcEnd - 1]) {
-        funcEnd -= 1;
-    }
-    string funcRet = methodNameStr.substr(0, funcEnd);
-// parameters, if any, are delimited by () and separate by ,
-    TextParser parser(&iDef);
-    parser.fChar += namePos + iDef.fName.length();
-    const char* start = parser.fChar;
-    vector<string> paramStrs;
-    if ('(' == start[0]) {
-        parser.skipToBalancedEndBracket('(', ')');
-        TextParser params(&iDef);
-        params.fChar = start + 1;
-        params.fEnd = parser.fChar;
-        while (!params.eof()) {
-            const char* paramEnd = params.anyOf("=,)");
-            const char* paramStart = paramEnd;
-            while (paramStart > params.fChar && ' ' >= paramStart[-1]) {
-                paramStart -= 1;
-            }
-            while (paramStart > params.fChar && (isalnum(paramStart[-1])
-                    || '_' == paramStart[-1])) {
-                paramStart -= 1;
-            }
-            string param(paramStart, paramEnd - paramStart);
-            paramStrs.push_back(param);
-            params.fChar = params.anyOf(",)") + 1;
-        }
-    }
-    string bmhFile = root ? root->fFileName : bDef ? bDef->fFileName : "a *.bmh file";
-    bool hasFuncReturn = "" != funcRet && "void" != funcRet;
-    switch(suggest) {
-        case Suggest::kMethodMissing: {
-            // if include @param, @return are missing, request them as well
-            string paramDox;
-            bool firstParam = true;
-            for (auto paramStr : paramStrs) {
-                if (firstParam) {
-                    paramDox += "\n";
-                    firstParam = false;
-                }
-                paramDox += "    @param " + paramStr + "  descriptive phrase\n";
-            }
-            if (hasFuncReturn) {
-                paramDox += "\n";
-                paramDox += "    @return descriptive phrase\n";
-            }
-            SkDebugf(kMethodMissingStr, bmhFile.c_str(), methodNameLessImpl, iDef.fFileName.c_str(),
-                    paramDox.c_str(), methodName, methodName);
-            } break;
-        case Suggest::kMethodDiffers: {
-            bool hasPop = std::any_of(bDef->fChildren.begin(), bDef->fChildren.end(),
-                    [](Definition* def) { return MarkType::kPopulate == def->fMarkType; });
-            if (!hasPop) {
-                SkDebugf(kMethodDiffersNoPopStr, bmhFile.c_str(), methodNameLessImpl, methodName);
-            }
-            SkDebugf(kMethodDiffersStr, iDef.fFileName.c_str(), methodName);
-            } break;
-        default:
-            SkASSERT(0);
-    }
-}
-
-Bracket IncludeParser::topBracket() const {
-    Definition* parent = this->parentBracket(fParent);
-    return parent ? parent->fBracket : Bracket::kNone;
-}
diff --git a/tools/bookmaker/includeParser.h b/tools/bookmaker/includeParser.h
deleted file mode 100644
index 335b4e7..0000000
--- a/tools/bookmaker/includeParser.h
+++ /dev/null
@@ -1,469 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef includeParser_DEFINED
-#define includeParser_DEFINED
-
-#include "SkString.h"
-
-#include "parserCommon.h"
-
-class BmhParser;
-
-struct IClassDefinition : public Definition {
-    unordered_map<string, Definition*> fConsts;
-    unordered_map<string, Definition*> fDefines;
-    unordered_map<string, Definition*> fEnums;
-    unordered_map<string, Definition*> fMembers;
-    unordered_map<string, Definition*> fMethods;
-    unordered_map<string, Definition*> fStructs;
-    unordered_map<string, Definition*> fTypedefs;
-};
-
-class IncludeParser : public ParserCommon {
-public:
-    enum class IsStruct {
-        kNo,
-        kYes,
-    };
-
-    enum class Elided {
-        kNo,
-        kYes,
-    };
-
-    enum class Suggest {
-        kMethodMissing,
-        kMethodDiffers,
-    };
-
-    struct CheckCode {
-        enum class State {
-            kNone,
-            kClassDeclaration,
-            kConstructor,
-            kForwardDeclaration,
-            kMethod,
-        };
-
-        void reset() {
-            fInDebugCode = nullptr;
-            fPrivateBrace = 0;
-            fBraceCount = 0;
-            fIndent = 0;
-            fDoubleReturn = 0;
-            fState = State::kNone;
-            fPrivateProtected = false;
-            fTypedefReturn = false;
-            fSkipAPI = false;
-            fSkipInline = false;
-            fSkipWarnUnused = false;
-            fWriteReturn = false;
-        }
-
-        const char* fInDebugCode;
-        int fPrivateBrace;
-        int fBraceCount;
-        int fIndent;
-        int fDoubleReturn;
-        State fState;
-        bool fPrivateProtected;
-        bool fTypedefReturn;
-        bool fSkipAPI;
-        bool fSkipInline;
-        bool fSkipWarnUnused;
-        bool fWriteReturn;
-    };
-
-    IncludeParser() : ParserCommon()
-        , fMaps {
-          { &fIConstMap,    MarkType::kConst }
-        , { &fIDefineMap,   MarkType::kDefine }
-        , { &fIEnumMap,     MarkType::kEnum }
-        , { &fIEnumMap,     MarkType::kEnumClass }
-        , { &fIStructMap,   MarkType::kStruct }
-        , { &fITemplateMap, MarkType::kTemplate }
-        , { &fITypedefMap,  MarkType::kTypedef }
-        , { &fIUnionMap,    MarkType::kUnion }
-        }
-    {
-        this->reset();
-    }
-
-    ~IncludeParser() override {}
-
-    void addKeyword(KeyWord keyWord);
-
-    void addPunctuation(Punctuation punctuation) {
-        fParent->fTokens.emplace_back(punctuation, fChar, fLineCount, fParent, '\0');
-    }
-
-    void addWord() {
-        fParent->fTokens.emplace_back(fIncludeWord, fChar, fLineCount, fParent, '\0');
-        fIncludeWord = nullptr;
-    }
-
-    bool advanceInclude(TextParser& i);
-    bool inAlignAs() const;
-    void checkForMissingParams(const vector<string>& methodParams,
-                               const vector<string>& foundParams);
-    bool checkForWord();
-    void checkName(Definition* );
-    void checkTokens(list<Definition>& tokens, string key, string className,
-            RootDefinition* root, BmhParser& bmhParser);
-    string className() const;
-
-    string codeBlock(const Definition& def, bool inProgress) const {
-        return codeBlock(def.fMarkType, def.fName, inProgress);
-    }
-
-    string codeBlock(MarkType markType, string name, bool inProgress) const {
-        if (MarkType::kClass == markType || MarkType::kStruct == markType) {
-            auto map = fIClassMap.find(name);
-            SkASSERT(fIClassMap.end() != map || inProgress);
-            return fIClassMap.end() != map ? map->second.fCode : "";
-        }
-        if (MarkType::kConst == markType) {
-            auto map = fIConstMap.find(name);
-            SkASSERT(fIConstMap.end() != map);
-            return map->second->fCode;
-        }
-        if (MarkType::kDefine == markType) {
-            auto map = fIDefineMap.find(name);
-            SkASSERT(fIDefineMap.end() != map);
-            return map->second->fCode;
-        }
-        if (MarkType::kEnum == markType || MarkType::kEnumClass == markType) {
-            auto map = fIEnumMap.find(name);
-            SkASSERT(fIEnumMap.end() != map);
-            return map->second->fCode;
-        }
-        if (MarkType::kTypedef == markType) {
-            auto map = fITypedefMap.find(name);
-            SkASSERT(fITypedefMap.end() != map);
-            return map->second->fCode;
-        }
-        SkASSERT(0);
-        return "";
-    }
-
-    void codeBlockAppend(string& result, string ) const;
-    void codeBlockAppend(string& result, char ch) const;
-    void codeBlockSpaces(string& result, int indent) const;
-
-    bool crossCheck(BmhParser& );
-    IClassDefinition* defineClass(const Definition& includeDef, string className);
-    void dumpClassTokens(IClassDefinition& classDef);
-    void dumpComment(const Definition& );
-    void dumpCommonTail(const Definition& );
-    void dumpConst(const Definition& , string className);
-    void dumpDefine(const Definition& );
-    void dumpEnum(const Definition& , string name);
-    bool dumpGlobals(string* globalFileName, long int* globalTell);
-    bool dumpMethod(const Definition& , string className);
-    void dumpMember(const Definition& );
-    bool dumpTokens();
-    bool dumpTokens(string skClassName, string globalFileName, long int* globalTell);
-    void dumpTypedef(const Definition& , string className);
-
-    string elidedCodeBlock(const Definition& );
-    string filteredBlock(string inContents, string filterContents);
-    bool findCommentAfter(const Definition& includeDef, Definition* markupDef);
-    bool findComments(const Definition& includeDef, Definition* markupDef);
-    Definition* findIncludeObject(const Definition& includeDef, MarkType markType,
-                                  string typeName);
-    static KeyWord FindKey(const char* start, const char* end);
-    Definition* findMethod(const Definition& bmhDef);
-    Bracket grandParentBracket() const;
-    const Definition* include(string ) const;
-    bool isClone(const Definition& token);
-    bool isConstructor(const Definition& token, string className);
-    bool isInternalName(const Definition& token);
-    bool isMember(const Definition& token) const;
-    bool isOperator(const Definition& token);
-    bool isUndocumentable(string filename, const char* start, const char* end, int lineCount);
-    Definition* parentBracket(Definition* parent) const;
-    bool parseChar();
-    bool parseComment(string filename, const char* start, const char* end, int lineCount,
-            Definition* markupDef, bool* undocumentedPtr);
-    bool parseClass(Definition* def, IsStruct);
-    bool parseConst(Definition* child, Definition* markupDef);
-    bool parseDefine(Definition* child, Definition* markupDef);
-    bool parseEnum(Definition* child, Definition* markupDef);
-    bool parseEnumConst(list<Definition>::iterator& tokenIter,
-            const list<Definition>::iterator& tokenEnd, Definition* markupChild);
-
-    bool parseFromFile(const char* path) override {
-        this->reset();
-        if (!INHERITED::parseSetup(path)) {
-            return false;
-        }
-        string name(path);
-        return this->parseInclude(name);
-    }
-
-    bool parseInclude(string name);
-    bool parseMember(Definition* child, Definition* markupDef);
-    bool parseMethod(Definition* child, Definition* markupDef);
-    bool parseObject(Definition* child, Definition* markupDef);
-    bool parseObjects(Definition* parent, Definition* markupDef);
-    bool parseOneEnumConst(list<Definition>& constList, Definition* markupChild, bool skipWord);
-    bool parseTemplate(Definition* child, Definition* markupDef);
-    bool parseTypedef(Definition* child, Definition* markupDef);
-    bool parseUsing();
-    bool parseUnion();
-
-    void popBracket() {
-        if (Definition::Type::kKeyWord == fParent->fType
-                && KeyWord::kTypename == fParent->fKeyWord) {
-            this->popObject();
-        }
-        SkASSERT(Definition::Type::kBracket == fParent->fType);
-        this->popObject();
-        Bracket bracket = this->topBracket();
-        this->setBracketShortCuts(bracket);
-    }
-
-    void pushBracket(Bracket bracket) {
-        this->setBracketShortCuts(bracket);
-        fParent->fTokens.emplace_back(bracket, fChar, fLineCount, fParent, '\0');
-        Definition* container = &fParent->fTokens.back();
-        this->addDefinition(container);
-    }
-
-    bool references(const SkString& file) const;
-
-    static void RemoveFile(const char* docs, const char* includes);
-    static void RemoveOneFile(const char* docs, const char* includesFileOrPath);
-
-    void reset() override {
-        INHERITED::resetCommon();
-        fRootTopic = nullptr;
-        fConstExpr = nullptr;
-        fInBrace = nullptr;
-        fIncludeWord = nullptr;
-        fLastObject = nullptr;
-        fPriorEnum = nullptr;
-        fPriorObject = nullptr;
-        fPrev = '\0';
-        fInChar = false;
-        fInCharCommentString = false;
-        fInComment = false;
-        fInDefine = false;
-        fInEnum = false;
-        fInFunction = false;
-        fInString = false;
-        fFailed = false;
-    }
-
-    void setBracketShortCuts(Bracket bracket) {
-        fInComment = Bracket::kSlashSlash == bracket || Bracket::kSlashStar == bracket;
-        fInString = Bracket::kString == bracket;
-        fInChar = Bracket::kChar == bracket;
-        fInCharCommentString = fInChar || fInComment || fInString;
-    }
-
-    void suggestFix(Suggest suggest, const Definition& iDef, const RootDefinition* root,
-            const Definition* bDef);
-    Bracket topBracket() const;
-
-    template <typename T>
-    string uniqueName(const unordered_map<string, T>& m, string typeName) {
-        string base(typeName.size() > 0 ? typeName : "_anonymous");
-        string name(base);
-        int anonCount = 1;
-        do {
-            auto iter = m.find(name);
-            if (iter == m.end()) {
-                return name;
-            }
-            name = base + '_';
-            name += to_string(++anonCount);
-        } while (true);
-        // should never get here
-        return string();
-    }
-
-    void validate() const;
-    void writeCodeBlock();
-    string writeCodeBlock(const Definition& );
-    string writeCodeBlock(TextParser& i, MarkType , int indent);
-
-    void writeDefinition(const Definition& def) {
-        if (def.length() > 1) {
-            this->writeBlock((int) (def.fContentEnd - def.fContentStart), def.fContentStart);
-            this->lf(1);
-        }
-    }
-
-    void writeDefinition(const Definition& def, string name, int spaces) {
-        this->writeBlock((int) (def.fContentEnd - def.fContentStart), def.fContentStart);
-        this->writeSpace(spaces);
-        this->writeString(name);
-        this->lf(1);
-    }
-
-    void writeEndTag() {
-        this->lf(1);
-        this->writeString("##");
-        this->lf(1);
-    }
-
-    void writeEndTag(const char* tagType) {
-        this->lf(1);
-        this->writeString(string("#") + tagType + " ##");
-        this->lf(1);
-    }
-
-    void writeEndTag(const char* tagType, const char* tagID, int spaces = 1) {
-        this->lf(1);
-        this->writeString(string("#") + tagType + " " + tagID);
-        this->writeSpace(spaces);
-        this->writeString("##");
-        this->lf(1);
-    }
-
-    void writeEndTag(const char* tagType, string tagID, int spaces = 1) {
-        this->writeEndTag(tagType, tagID.c_str(), spaces);
-    }
-
-    void writeIncompleteTag(const char* tagType, string tagID, int spaces = 1) {
-        this->writeString(string("#") + tagType + " " + tagID);
-        this->writeSpace(spaces);
-        this->writeString("incomplete");
-        this->writeSpace();
-        this->writeString("##");
-        this->lf(1);
-    }
-
-    void writeIncompleteTag(const char* tagType) {
-        this->writeString(string("#") + tagType + " incomplete ##");
-        this->lf(1);
-    }
-
-    void writeTableHeader(const char* col1, size_t pad, const char* col2) {
-        this->lf(1);
-        this->writeString("#Table");
-        this->lf(1);
-        this->writeString("#Legend");
-        this->lf(1);
-        string legend = "# ";
-        legend += col1;
-        if (pad > strlen(col1)) {
-            legend += string(pad - strlen(col1), ' ');
-        }
-        legend += " # ";
-        legend += col2;
-        legend += " ##";
-        this->writeString(legend);
-        this->lf(1);
-        this->writeString("#Legend ##");
-        this->lf(1);
-    }
-
-    void writeTableRow(size_t pad, string col1) {
-        this->lf(1);
-        string row = "# " + col1 + string(pad - col1.length(), ' ') + " # ##";
-        this->writeString(row);
-        this->lf(1);
-    }
-
-    void writeTableRow(size_t pad1, string col1, size_t pad2, string col2) {
-        this->lf(1);
-        string row = "# " + col1 + string(pad1 - col1.length(), ' ') + " # " +
-                col2 + string(pad2 - col2.length(), ' ') + " ##";
-        this->writeString(row);
-        this->lf(1);
-    }
-
-    void writeTableTrailer() {
-        this->lf(1);
-        this->writeString("#Table ##");
-        this->lf(1);
-    }
-
-    void writeTag(const char* tagType) {
-        this->lf(1);
-        this->writeString("#");
-        this->writeString(tagType);
-    }
-
-    void writeTagNoLF(const char* tagType, const char* tagID) {
-        this->writeString("#");
-        this->writeString(tagType);
-        this->writeSpace();
-        this->writeString(tagID);
-    }
-
-    void writeTagNoLF(const char* tagType, string tagID) {
-        this->writeTagNoLF(tagType, tagID.c_str());
-    }
-
-    void writeTag(const char* tagType, const char* tagID) {
-        this->lf(1);
-        this->writeTagNoLF(tagType, tagID);
-    }
-
-    void writeTag(const char* tagType, string tagID) {
-        this->writeTag(tagType, tagID.c_str());
-    }
-
-    void writeTagTable(string tagType, string body) {
-        this->writeTag(tagType.c_str());
-        this->writeSpace(1);
-        this->writeString("#");
-        this->writeSpace(1);
-        this->writeString(body);
-        this->writeSpace(1);
-        this->writeString("##");
-    }
-
-protected:
-    static void ValidateKeyWords();
-
-    struct DefinitionMap {
-        unordered_map<string, Definition*>* fInclude;
-        MarkType fMarkType;
-    };
-
-    vector<DefinitionMap> fMaps;
-    unordered_map<string, Definition> fIncludeMap;
-    list<Definition> fGlobals;
-    unordered_map<string, IClassDefinition> fIClassMap;
-    unordered_map<string, Definition*> fIConstMap;
-    unordered_map<string, Definition*> fIDefineMap;
-    unordered_map<string, Definition*> fIEnumMap;
-    unordered_map<string, Definition*> fIFunctionMap;
-    unordered_map<string, Definition*> fIStructMap;
-    unordered_map<string, Definition*> fITemplateMap;
-    unordered_map<string, Definition*> fITypedefMap;
-    unordered_map<string, Definition*> fIUnionMap;
-    CheckCode fCheck;
-    Definition* fRootTopic;
-    Definition* fConstExpr;
-    Definition* fInBrace;
-    Definition* fLastObject;
-    Definition* fPriorEnum;
-    Definition* fPriorObject;
-    const Definition* fPreviousDef;
-    int fPriorIndex;
-    const char* fIncludeWord;
-    Elided fElided;
-    MarkType fPreviousMarkType;
-    char fPrev;
-    bool fInChar;
-    bool fInCharCommentString;
-    bool fInComment;
-    bool fInDefine;
-    bool fInEnum;
-    bool fInFunction;
-    bool fInString;
-    bool fFailed;
-    typedef ParserCommon INHERITED;
-};
-
-#endif
diff --git a/tools/bookmaker/includeWriter.cpp b/tools/bookmaker/includeWriter.cpp
deleted file mode 100644
index 34f6f08..0000000
--- a/tools/bookmaker/includeWriter.cpp
+++ /dev/null
@@ -1,2749 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include <chrono>
-#include <ctime>
-
-#include "bmhParser.h"
-#include "includeWriter.h"
-
-bool IncludeWriter::checkChildCommentLength(const Definition* parent, MarkType childType) const {
-    bool oneMember = false;
-    for (auto& item : parent->fChildren) {
-        if (childType != item->fMarkType) {
-            continue;
-        }
-        oneMember = true;
-        int lineLen = 0;
-        for (auto& itemChild : item->fChildren) {
-            if (MarkType::kLine == itemChild->fMarkType) {
-                lineLen = itemChild->length();
-                break;
-            }
-        }
-        if (!lineLen) {
-            item->reportError<void>("missing #Line");
-        }
-        if (fEnumItemCommentTab + lineLen >= 100) {
-// if too long, remove spaces until it fits, or wrap
-//            item->reportError<void>("#Line comment too long");
-        }
-    }
-    return oneMember;
-}
-
-void IncludeWriter::checkEnumLengths(const Definition& child, string enumName, ItemLength* length) const {
-    const Definition* enumItem = this->matchMemberName(enumName, child);
-    if (std::any_of(enumItem->fChildren.begin(), enumItem->fChildren.end(),
-            [](Definition* child){return MarkType::kNoJustify == child->fMarkType;})) {
-        return;
-    }
-    string comment = this->enumMemberComment(enumItem, child);
-    int lineLimit = 100 - fIndent - 7; // 7: , space //!< space
-    if (length->fCurValue) {
-        lineLimit -= 3; // space = space
-    }
-    if (length->fCurName + length->fCurValue + (int) comment.length() < lineLimit) {
-        length->fLongestName = SkTMax(length->fLongestName, length->fCurName);
-        length->fLongestValue = SkTMax(length->fLongestValue, length->fCurValue);
-    }
-}
-
-void IncludeWriter::constOut(const Definition* memberStart, const Definition* bmhConst) {
-    const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
-        memberStart->fContentStart;
-    this->firstBlockTrim((int) (bodyEnd - fStart), fStart);  // may write nothing
-    this->lf(2);
-    this->indentDeferred(IndentKind::kConstOut);
-    if (fStructEnded) {
-        fIndent = fICSStack.size() * 4;
-        fStructEnded = false;
-    }
-    // comment may be legitimately empty; typedef may not have separate comment (for now)
-    fReturnOnWrite = true;
-    bool commentHasLength = this->descriptionOut(bmhConst, SkipFirstLine::kYes, Phrase::kNo);
-    fReturnOnWrite = false;
-    if (commentHasLength) {
-        this->writeCommentHeader();
-        fIndent += 4;
-        if (!this->descriptionOut(bmhConst, SkipFirstLine::kYes, Phrase::kNo)) {
-            return memberStart->reportError<void>("expected description for const");
-        }
-        fIndent -= 4;
-        this->writeCommentTrailer(OneLine::kNo);
-    }
-    this->setStart(memberStart->fContentStart, memberStart);
-}
-
-bool IncludeWriter::descriptionOut(const Definition* def, SkipFirstLine skipFirstLine,
-            Phrase phrase) {
-    bool wroteSomething = false;
-    const char* commentStart = def->fContentStart;
-    if (SkipFirstLine::kYes == skipFirstLine) {
-        TextParser parser(def);
-        SkAssertResult(parser.skipLine());
-        commentStart = parser.fChar;
-    }
-    int commentLen = (int) (def->fContentEnd - commentStart);
-    bool breakOut = false;
-    SkDEBUGCODE(bool wroteCode = false);
-    const Definition* lastDescription = def;
-    for (auto prop : def->fChildren) {
-        fLastDescription = lastDescription;
-        lastDescription = prop;
-        switch (prop->fMarkType) {
-            case MarkType::kCode: {
-                bool literal = false;
-                bool literalOutdent = false;
-                commentLen = (int) (prop->fStart - commentStart);
-                if (commentLen > 0) {
-                    SkASSERT(commentLen < 1000);
-                    if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, Phrase::kNo)) {
-                        if (fReturnOnWrite) {
-                            return true;
-                        }
-                        this->lf(2);
-                        wroteSomething = true;
-                    }
-                }
-                size_t childSize = prop->fChildren.size();
-                if (childSize) {
-                    if (MarkType::kLiteral == prop->fChildren[0]->fMarkType) {
-                        SkASSERT(1 == childSize || 2 == childSize);  // incomplete
-                        SkASSERT(1 == childSize || MarkType::kOutdent == prop->fChildren[1]->fMarkType);
-                        commentStart = prop->fChildren[childSize - 1]->fContentStart;
-                        literal = true;
-                        literalOutdent = 2 == childSize &&
-                                MarkType::kOutdent == prop->fChildren[1]->fMarkType;
-                    }
-                }
-                commentLen = (int) (prop->fContentEnd - commentStart);
-                SkASSERT(commentLen > 0);
-                if (literal) {
-                    if (!fReturnOnWrite && !literalOutdent) {
-                        fIndent += 4;
-                    }
-                    wroteSomething |= this->writeBlockIndent(commentLen, commentStart, false);
-                    if (fReturnOnWrite) {
-                        return true;
-                    }
-                    if (!fReturnOnWrite) {
-                        this->lf(2);
-                        if (!literalOutdent) {
-                            fIndent -= 4;
-                        }
-                    }
-                    SkDEBUGCODE(wroteCode = true);
-                }
-                commentStart = prop->fTerminator;
-                } break;
-            case MarkType::kBug: {
-                if (fReturnOnWrite) {
-                    return true;
-                }
-                string bugstr("(see skbug.com/" + string(prop->fContentStart,
-                    prop->fContentEnd - prop->fContentStart) + ')');
-                this->writeString(bugstr);
-                this->lfcr();
-                wroteSomething = true;
-            }
-            case MarkType::kFormula: {
-                commentLen = prop->fStart - commentStart;
-                if (commentLen > 0) {
-                    if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, Phrase::kNo)) {
-                        if (fReturnOnWrite) {
-                            return true;
-                        }
-                        if (commentLen > 1 && '\n' == prop->fStart[-1]) {
-                            this->lf(1);
-                        } else {
-                            this->writeSpace();
-                        }
-                        wroteSomething = true;
-                    }
-                }
-                int saveIndent = fIndent;
-                if (fIndent < fColumn + 1) {
-                    fIndent = fColumn + 1;
-                }
-                wroteSomething |= this->writeBlockIndent(prop->length(), prop->fContentStart, true);
-                fIndent = saveIndent;
-                if (wroteSomething && fReturnOnWrite) {
-                    return true;
-                }
-                commentStart = prop->fTerminator;
-                commentLen = (int) (def->fContentEnd - commentStart);
-                if (!fReturnOnWrite) {
-                    if (commentLen > 1 && ' ' == commentStart[0] && !fLinefeeds) {
-                        this->writeSpace();
-                    }
-                }
-                } break;
-            case MarkType::kDetails:
-            case MarkType::kIn:
-            case MarkType::kLine:
-            case MarkType::kToDo:
-                commentLen = (int) (prop->fStart - commentStart);
-                if (commentLen > 0) {
-                    SkASSERT(commentLen < 1000);
-                    if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, Phrase::kNo)) {
-                        if (fReturnOnWrite) {
-                            return true;
-                        }
-                        this->lfcr();
-                        wroteSomething = true;
-                    }
-                }
-                commentStart = prop->fTerminator;
-                commentLen = (int) (def->fContentEnd - commentStart);
-                break;
-            case MarkType::kList:
-                commentLen = prop->fStart - commentStart;
-                if (commentLen > 0) {
-                    if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart,
-                            Phrase::kNo)) {
-                        if (fReturnOnWrite) {
-                            return true;
-                        }
-                        this->lfcr();
-                        wroteSomething = true;
-                    }
-                }
-                for (auto row : prop->fChildren) {
-                    SkASSERT(MarkType::kRow == row->fMarkType);
-                    for (auto column : row->fChildren) {
-                        SkASSERT(MarkType::kColumn == column->fMarkType);
-                        if (fReturnOnWrite) {
-                            return true;
-                        }
-                        this->writeString("-");
-                        this->writeSpace();
-                        wroteSomething |= this->descriptionOut(column, SkipFirstLine::kNo, Phrase::kNo);
-                        this->lf(1);
-                    }
-                }
-                commentStart = prop->fTerminator;
-                commentLen = (int) (def->fContentEnd - commentStart);
-                if ('\n' == commentStart[0] && '\n' == commentStart[1]) {
-                    this->lf(2);
-                }
-                break;
-            case MarkType::kPhraseRef: {
-                commentLen = prop->fStart - commentStart;
-                if (commentLen > 0) {
-                    if (fReturnOnWrite) {
-                        return true;
-                    }
-                    this->rewriteBlock(commentLen, commentStart, Phrase::kNo);
-                    // ince we don't do line wrapping, always insert LF before phrase
-                    this->lfcr();   // TODO: remove this once rewriteBlock rewraps paragraphs
-                    wroteSomething = true;
-                }
-                auto iter = fBmhParser->fPhraseMap.find(prop->fName);
-                if (fBmhParser->fPhraseMap.end() == iter) {
-                    return this->reportError<bool>("missing phrase definition");
-                }
-                Definition* phraseDef = iter->second;
-                // TODO: given TextParser(commentStart, prop->fStart + up to #) return if
-                // it ends with two of more linefeeds, ignoring other whitespace
-                Phrase defIsPhrase = '\n' == prop->fStart[0] && '\n' == prop->fStart[-1] ?
-                        Phrase::kNo : Phrase::kYes;
-                if (Phrase::kNo == defIsPhrase) {
-                    this->lf(2);
-                }
-                const char* start = phraseDef->fContentStart;
-                int length = phraseDef->length();
-                auto propParams = prop->fChildren.begin();
-                // can this share code or logic with mdout somehow?
-                for (auto child : phraseDef->fChildren) {
-                    if (MarkType::kPhraseParam == child->fMarkType) {
-                        continue;
-                    }
-                    int localLength = child->fStart - start;
-                    if (fReturnOnWrite) {
-                        return true;
-                    }
-                    this->rewriteBlock(localLength, start, defIsPhrase);
-                    start += localLength;
-                    length -= localLength;
-                    SkASSERT(propParams != prop->fChildren.end());
-                    if (fColumn > 0) {
-                        this->writeSpace();
-                    }
-                    this->writeString((*propParams)->fName);
-                    localLength = child->fContentEnd - child->fStart;
-                    start += localLength;
-                    length -= localLength;
-                    if (isspace(start[0])) {
-                        this->writeSpace();
-                    }
-                    defIsPhrase = Phrase::kYes;
-                    wroteSomething = true;
-                }
-                if (length > 0) {
-                    if (fReturnOnWrite) {
-                        return true;
-                    }
-                    this->rewriteBlock(length, start, defIsPhrase);
-                }
-                commentStart = prop->fContentStart;
-                commentLen = (int) (def->fContentEnd - commentStart);
-                if (!fReturnOnWrite) {
-                    if ('\n' == commentStart[0] && '\n' == commentStart[1]) {
-                        this->lf(2);
-                    }
-                }
-                } break;
-            default:
-                commentLen = (int) (prop->fStart - commentStart);
-                breakOut = true;
-        }
-        if (breakOut) {
-            break;
-        }
-    }
-    if (!breakOut) {
-        commentLen = (int) (def->fContentEnd - commentStart);
-    }
-    SkASSERT(wroteCode || (commentLen > 0 && commentLen < 1500));
-    if (commentLen > 0) {
-        if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, phrase)) {
-            if (fReturnOnWrite) {
-                return true;
-            }
-            wroteSomething = true;
-        }
-    }
-    SkASSERT(!fReturnOnWrite || !wroteSomething);
-    return wroteSomething;
-}
-
-void IncludeWriter::enumHeaderOut(RootDefinition* root, const Definition& child) {
-    const Definition* enumDef = nullptr;
-    const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
-            child.fContentStart;
-    this->firstBlockTrim((int) (bodyEnd - fStart), fStart);  // may write nothing
-    this->lf(2);
-    this->indentDeferred(IndentKind::kEnumHeader);
-    fDeferComment = nullptr;
-    this->setStart(child.fContentStart, &child);
-    const auto& nameDef = child.fTokens.front();
-    string fullName;
-    if (nullptr != nameDef.fContentEnd) {
-        TextParser enumClassCheck(&nameDef);
-        const char* start = enumClassCheck.fStart;
-        size_t len = (size_t) (enumClassCheck.fEnd - start);
-        bool enumClass = enumClassCheck.skipExact("class ");
-        if (enumClass) {
-            start = enumClassCheck.fChar;
-            const char* end = enumClassCheck.anyOf(" \n;{");
-            len = (size_t) (end - start);
-        }
-        string enumName(start, len);
-        if (enumClass) {
-            child.fChildren[0]->fName = enumName;
-        }
-        fullName = root->fName + "::" + enumName;
-        enumDef = root->find(enumName, RootDefinition::AllowParens::kNo);
-        if (!enumDef) {
-            enumDef = root->find(fullName, RootDefinition::AllowParens::kNo);
-        }
-        if (!enumDef) {
-            auto mapEntry = fBmhParser->fEnumMap.find(enumName);
-            if (fBmhParser->fEnumMap.end() != mapEntry) {
-                enumDef = &mapEntry->second;
-            }
-        }
-        if (!enumDef && enumName == root->fName) {
-            enumDef = root;
-        }
-        SkASSERT(enumDef);
-        // child[0] should be #Code comment starts at child[0].fTerminator
-            // though skip until #Code is found (in case there's a #ToDo, etc)
-        // child[1] should be #Const comment ends at child[1].fStart
-        // comment becomes enum header (if any)
-    } else {
-        string enumName(root->fName);
-        enumName += "::_anonymous";
-        if (fAnonymousEnumCount > 1) {
-            enumName += '_' + to_string(fAnonymousEnumCount);
-        }
-        enumDef = root->find(enumName, RootDefinition::AllowParens::kNo);
-        SkASSERT(enumDef);
-        ++fAnonymousEnumCount;
-    }
-    Definition* codeBlock = nullptr;
-    const char* commentStart = nullptr;
-    bool firstCodeBlocks = true;
-    bool wroteHeader = false;
-    bool lastAnchor = false;
-//    SkDEBUGCODE(bool foundConst = false);
-    for (auto test : enumDef->fChildren) {
-        if (MarkType::kCode == test->fMarkType && firstCodeBlocks) {
-            codeBlock = test;
-            commentStart = codeBlock->fTerminator;
-            continue;
-        } else if (codeBlock) {
-            firstCodeBlocks = false;
-        }
-        if (!codeBlock) {
-            continue;
-        }
-        const char* commentEnd = test->fStart;
-        if (!wroteHeader &&
-                !this->contentFree((int) (commentEnd - commentStart), commentStart)) {
-            if (fIndentNext) {
-                // FIXME: how can I tell where fIdentNext gets cleared?
-                this->indentIn(IndentKind::kEnumChild);
-            }
-            this->writeCommentHeader();
-            this->writeString("\\enum");
-            if (fullName.length() > 0) {
-                this->writeSpace();
-                this->writeString(fullName.c_str());
-            }
-            this->indentIn(IndentKind::kEnumChild2);
-            this->lfcr();
-            wroteHeader = true;
-        }
-        if (lastAnchor) {
-            if (commentEnd - commentStart > 1) {
-                SkASSERT('\n' == commentStart[0]);
-                if (' ' == commentStart[1]) {
-                    this->writeSpace();
-                }
-            }
-            lastAnchor = false;
-        }
-        this->rewriteBlock((int) (commentEnd - commentStart), commentStart, Phrase::kNo);
-        if (MarkType::kAnchor == test->fMarkType || MarkType::kCode == test->fMarkType) {
-            bool newLine = commentEnd - commentStart > 1 &&
-                '\n' == commentEnd[-1] && '\n' == commentEnd[-2];
-            commentStart = test->fContentStart;
-            commentEnd = MarkType::kAnchor == test->fMarkType ? test->fChildren[0]->fStart :
-                    test->fContentEnd;
-            if (newLine) {
-                this->lf(2);
-            } else {
-                this->writeSpace();
-            }
-            if (MarkType::kAnchor == test->fMarkType) {
-                this->rewriteBlock((int) (commentEnd - commentStart), commentStart, Phrase::kNo);
-            } else {
-                this->writeBlock((int) (commentEnd - commentStart), commentStart);
-                this->lf(2);
-            }
-            lastAnchor = true;   // this->writeSpace();
-        }
-        commentStart = test->fTerminator;
-        if (MarkType::kConst == test->fMarkType) {
-            SkASSERT(codeBlock);  // FIXME: check enum for correct order earlier
-//            SkDEBUGCODE(foundConst = true);
-            break;
-        }
-    }
- //   SkASSERT(codeBlock);
- //   SkASSERT(foundConst);
-    if (wroteHeader) {
-        this->indentOut();
-        this->lfcr();
-        this->writeCommentTrailer(OneLine::kNo);
-    }
-    Definition* braceHolder = child.fChildren[0];
-    if (KeyWord::kClass == braceHolder->fKeyWord) {
-        braceHolder = braceHolder->fChildren[0];
-    }
-    bodyEnd = braceHolder->fContentStart;
-    SkASSERT('{' == bodyEnd[0]);
-    ++bodyEnd;
-    this->lfcr();
-    this->writeBlock((int) (bodyEnd - fStart), fStart); // write include "enum Name {"
-    this->indentIn(IndentKind::kEnumHeader2);
-    this->singleLF();
-    this->setStart(bodyEnd, braceHolder);
-    fEnumDef = enumDef;
-}
-
-const Definition* IncludeWriter::enumMemberForComment(const Definition* currentEnumItem) const {
-    for (auto constItem : currentEnumItem->fChildren) {
-        if (MarkType::kLine == constItem->fMarkType) {
-            return constItem;
-        }
-    }
-    SkASSERT(0);
-    return nullptr;
-}
-
-string IncludeWriter::enumMemberComment(const Definition* currentEnumItem,
-        const Definition& child) const {
-    // #Const should always be followed by #Line, so description follows that
-    string shortComment;
-    for (auto constItem : currentEnumItem->fChildren) {
-        if (MarkType::kLine == constItem->fMarkType) {
-            shortComment = string(constItem->fContentStart, constItem->length());
-            break;
-        }
-    }
-    if (!shortComment.length()) {
-        currentEnumItem->reportError<void>("missing #Line or #Deprecated or #Experimental");
-    }
-    return shortComment;
-}
-
-IncludeWriter::ItemState IncludeWriter::enumMemberName(
-        const Definition& child, const Definition* token, Item* item, LastItem* last,
-        const Definition** currentEnumItem) {
-    TextParser parser(fFileName, last->fStart, last->fEnd, fLineCount);
-    parser.skipWhiteSpace();
-    item->fName = string(parser.fChar, (int) (last->fEnd - parser.fChar));
-    *currentEnumItem = this->matchMemberName(item->fName, child);
-    if (token) {
-        this->setStart(token->fContentEnd, token);
-        TextParser enumLine(token->fFileName, last->fEnd, token->fContentStart, token->fLineCount);
-        const char* end = enumLine.anyOf(",}=");
-        SkASSERT(end);
-        if ('=' == *end) {  // write enum value
-            last->fEnd = token->fContentEnd;
-            item->fValue = string(token->fContentStart, (int) (last->fEnd - token->fContentStart));
-            return ItemState::kValue;
-        }
-    }
-    return ItemState::kComment;
-}
-
-void IncludeWriter::enumMemberOut(const Definition* currentEnumItem, const Definition& child,
-        const Item& item, Preprocessor& preprocessor) {
-    SkASSERT(currentEnumItem);
-    string shortComment = this->enumMemberComment(currentEnumItem, child);
-    int enumItemValueTab =
-            SkTMax((int) item.fName.length() + fIndent + 1, fEnumItemValueTab); // 1: ,
-    int valueLength = item.fValue.length();
-    int assignLength = valueLength ? valueLength + 3 : 0; // 3: space = space
-    int enumItemCommentTab = SkTMax(enumItemValueTab + assignLength, fEnumItemCommentTab);
-    int trimNeeded = enumItemCommentTab + shortComment.length() - (100 - (sizeof("//!< ") - 1));
-    bool crAfterName = false;
-    if (trimNeeded > 0) {
-        if (item.fValue.length()) {
-            int valueSpare = SkTMin(trimNeeded,                  // 3 below: space = space
-                    (int) (enumItemCommentTab - enumItemValueTab - item.fValue.length() - 3));
-            SkASSERT(valueSpare >= 0);
-            trimNeeded -= valueSpare;
-            enumItemCommentTab -= valueSpare;
-        }
-        if (trimNeeded > 0) {
-            int nameSpare = SkTMin(trimNeeded, (int) (enumItemValueTab - item.fName.length()
-                    - fIndent - 1));  // 1: ,
-            SkASSERT(nameSpare >= 0);
-            trimNeeded -= nameSpare;
-            enumItemValueTab -= nameSpare;
-            enumItemCommentTab -= nameSpare;
-        }
-        if (trimNeeded > 0) {
-            crAfterName = true;
-            if (!valueLength) {
-                this->enumMemberForComment(currentEnumItem)->reportError<void>("comment too long");
-            } else if (valueLength + fIndent + 8 + shortComment.length() > // 8: addtional indent
-                    100 - (sizeof(", //!< ") - 1)) { // -1: zero-terminated string
-                this->enumMemberForComment(currentEnumItem)->reportError<void>("comment 2 long");
-            }                                    // 2: = space
-            enumItemValueTab = fEnumItemValueTab +  2                 // 2: , space
-                    - SkTMax(0, fEnumItemValueTab + 2 + valueLength +    2 - fEnumItemCommentTab);
-            enumItemCommentTab = SkTMax(enumItemValueTab + valueLength + 2, fEnumItemCommentTab);
-        }
-    }
-    this->lfcr();
-    this->writeString(item.fName);
-    int saveIndent = fIndent;
-    if (item.fValue.length()) {
-        if (!crAfterName) {
-            this->indentToColumn(enumItemValueTab);
-        } else {
-            this->writeSpace();
-        }
-        this->writeString("=");
-        if (crAfterName) {
-            this->lfcr();
-            fIndent = enumItemValueTab;
-        } else {
-            this->writeSpace();
-        }
-        this->writeString(item.fValue);
-    }
-    this->writeString(",");
-    this->indentToColumn(enumItemCommentTab);
-    this->writeString("//!<");
-    this->writeSpace();
-    this->rewriteBlock(shortComment.length(), shortComment.c_str(), Phrase::kYes);
-    this->lfcr();
-    fIndent = saveIndent;
-    if (preprocessor.fStart) {
-        SkASSERT(preprocessor.fEnd);
-        int saveIndent = fIndent;
-        fIndent = SkTMax(0, fIndent - 8);
-        this->lf(2);
-        this->writeBlock(
-                (int) (preprocessor.fEnd - preprocessor.fStart), preprocessor.fStart);
-        this->lfcr();
-        fIndent = saveIndent;
-        preprocessor.reset();
-    }
-}
-
-// iterate through include tokens and find how much remains for 1 line comments
-// put ones that fit on same line, ones that are too big wrap
-void IncludeWriter::enumMembersOut(Definition& child) {
-    ItemState state = ItemState::kNone;
-    const Definition* currentEnumItem = nullptr;
-    LastItem last = { nullptr, nullptr };
-    auto brace = child.fChildren[0];
-    if (KeyWord::kClass == brace->fKeyWord) {
-        brace = brace->fChildren[0];
-    }
-    SkASSERT(Bracket::kBrace == brace->fBracket);
-    vector<IterState> iterStack;
-    iterStack.emplace_back(brace->fTokens.begin(), brace->fTokens.end());
-    IterState* iterState = &iterStack[0];
-    Preprocessor preprocessor;
-    Item item;
-    while (iterState->fDefIter != iterState->fDefEnd) {
-        auto& token = *iterState->fDefIter++;
-        if (this->enumPreprocessor(&token, MemberPass::kOut, iterStack, &iterState,
-                &preprocessor)) {
-            continue;
-        }
-        if (ItemState::kName == state) {
-            state = this->enumMemberName(child, &token, &item, &last, &currentEnumItem);
-        }
-        if (ItemState::kValue == state) {
-            TextParser valueEnd(token.fFileName, last.fEnd, token.fContentStart, token.fLineCount);
-            const char* end = valueEnd.anyOf(",}");
-            if (!end) {  // write expression continuation
-                item.fValue += string(last.fEnd, (int) (token.fContentEnd - last.fEnd));
-                continue;
-            }
-        }
-        if (ItemState::kNone != state && currentEnumItem) {
-            this->enumMemberOut(currentEnumItem, child, item, preprocessor);
-            item.reset();
-            this->setStartBack(token.fContentStart, &token);
-            state = ItemState::kNone;
-            last.fStart = nullptr;
-        }
-        SkASSERT(ItemState::kNone == state || !currentEnumItem);
-        if (!last.fStart) {
-            last.fStart = fStart;
-        }
-        last.fEnd = token.fContentEnd;
-        state = ItemState::kName;
-    }
-    if (ItemState::kName == state) {
-        state = this->enumMemberName(child, nullptr, &item, &last, &currentEnumItem);
-    }
-    if ((ItemState::kValue == state || ItemState::kComment == state) && currentEnumItem) {
-        this->enumMemberOut(currentEnumItem, child, item, preprocessor);
-    }
-    this->indentOut();
-}
-
-bool IncludeWriter::enumPreprocessor(Definition* token, MemberPass pass,
-        vector<IterState>& iterStack, IterState** iterState, Preprocessor* preprocessor) {
-    if (token && Definition::Type::kBracket == token->fType) {
-        if (Bracket::kSlashSlash == token->fBracket) {
-            if (MemberPass::kOut == pass) {
-                this->setStart(token->fContentEnd, token);
-            }
-            return true;  // ignore old inline comments
-        }
-        if (Bracket::kSlashStar == token->fBracket) {
-            if (MemberPass::kOut == pass) {
-                this->setStart(token->fContentEnd + 1, token);
-            }
-            return true;  // ignore old inline comments
-        }
-        if (Bracket::kPound == token->fBracket) {  // preprocessor wraps member
-            preprocessor->fDefinition = token;
-            preprocessor->fStart = token->fContentStart;
-            if (KeyWord::kIf == token->fKeyWord || KeyWord::kIfdef == token->fKeyWord) {
-                iterStack.emplace_back(token->fTokens.begin(), token->fTokens.end());
-                *iterState = &iterStack.back();
-                preprocessor->fWord = true;
-            } else if (KeyWord::kEndif == token->fKeyWord || KeyWord::kElif == token->fKeyWord
-                    || KeyWord::kElse == token->fKeyWord) {
-                iterStack.pop_back();
-                *iterState = &iterStack.back();
-                preprocessor->fEnd = token->fContentEnd;
-                if (KeyWord::kElif == token->fKeyWord) {
-                    iterStack.emplace_back(token->fTokens.begin(), token->fTokens.end());
-                    *iterState = &iterStack.back();
-                    preprocessor->fWord = true;
-                }
-            } else {
-                SkASSERT(0); // incomplete
-            }
-            return true;
-        }
-        if (preprocessor->fDefinition) {
-            if (Bracket::kParen == token->fBracket) {
-                preprocessor->fEnd = token->fContentEnd;
-                SkASSERT(')' == *preprocessor->fEnd);
-                ++preprocessor->fEnd;
-                return true;
-            }
-            SkASSERT(0);  // incomplete
-        }
-        return true;
-    }
-    if (token && Definition::Type::kWord != token->fType) {
-        SkASSERT(0); // incomplete
-    }
-    if (preprocessor->fWord) {
-        preprocessor->fWord = false;
-        preprocessor->fEnd = token->fContentEnd;
-        return true;
-    }
-    return false;
-}
-
-void IncludeWriter::enumSizeItems(const Definition& child) {
-    ItemState state = ItemState::kNone;
-    ItemLength lengths = { 0, 0, 0, 0 };
-    const char* lastEnd = nullptr;
-    auto brace = child.fChildren[0];
-    if (KeyWord::kClass == brace->fKeyWord) {
-        brace = brace->fChildren[0];
-    }
-    SkASSERT(Bracket::kBrace == brace->fBracket);
-    vector<IterState> iterStack;
-    iterStack.emplace_back(brace->fTokens.begin(), brace->fTokens.end());
-    IterState* iterState = &iterStack[0];
-    Preprocessor preprocessor;
-    string enumName;
-    bool undocumented = false;
-    while (iterState->fDefIter != iterState->fDefEnd) {
-        auto& token = *iterState->fDefIter++;
-        if (this->enumPreprocessor(&token, MemberPass::kCount, iterStack, &iterState,
-                &preprocessor)) {
-            continue;
-        }
-        if (ItemState::kName == state) {
-            TextParser enumLine(token.fFileName, lastEnd, token.fContentStart, token.fLineCount);
-            const char* end = enumLine.anyOf(",}=");
-            SkASSERT(end);
-            state = '=' == *end ? ItemState::kValue : ItemState::kComment;
-            if (ItemState::kValue == state) {
-                lastEnd = token.fContentEnd;
-                lengths.fCurValue = (int) (lastEnd - token.fContentStart);
-                continue;
-            }
-        }
-        if (ItemState::kValue == state) {
-            TextParser valueEnd(token.fFileName, lastEnd, token.fContentStart, token.fLineCount);
-            const char* end = valueEnd.anyOf(",}");
-            if (!end) {  // write expression continuation
-                lengths.fCurValue += (int) (token.fContentEnd - lastEnd);
-                continue;
-            }
-        }
-        if (ItemState::kNone != state) {
-            if (!undocumented) {
-                this->checkEnumLengths(child, enumName, &lengths);
-            }
-            lengths.fCurValue = 0;
-            state = ItemState::kNone;
-        }
-        SkASSERT(ItemState::kNone == state);
-        lastEnd = token.fContentEnd;
-        lengths.fCurName = (int) (lastEnd - token.fContentStart);
-        enumName = string(token.fContentStart, lengths.fCurName);
-        undocumented = token.fUndocumented;
-        state = ItemState::kName;
-    }
-    if (ItemState::kNone != state && !undocumented) {
-        this->checkEnumLengths(child, enumName, &lengths);
-    }
-    fEnumItemValueTab = lengths.fLongestName + fIndent + 1 /* 1: , */ ;
-    if (lengths.fLongestValue) {
-        lengths.fLongestValue += 3; // 3: space = space
-    }
-    fEnumItemCommentTab = fEnumItemValueTab + lengths.fLongestValue + 1 ; // 1: space before //!<
-    // iterate through bmh children and see which comments fit on include lines
-    if (!this->checkChildCommentLength(fEnumDef, MarkType::kConst)) {
-        fEnumDef->reportError<void>("expected at least one #Const in #Enum");
-    }
-}
-
-const Definition* IncludeWriter::matchMemberName(string matchName, const Definition& child) const {
-    const Definition* parent = &child;
-    if (KeyWord::kEnum == child.fKeyWord && child.fChildren.size() > 0
-            && KeyWord::kClass == child.fChildren[0]->fKeyWord) {
-        matchName = child.fChildren[0]->fName + "::" + matchName;
-    }
-    do {
-        if (KeyWord::kStruct == parent->fKeyWord || KeyWord::kClass == parent->fKeyWord) {
-            matchName = parent->fName + "::" + matchName;
-        }
-    } while ((parent = parent->fParent));
-    const Definition* enumItem = nullptr;
-    for (auto testItem : fEnumDef->fChildren) {
-        if (MarkType::kConst != testItem->fMarkType) {
-            continue;
-        }
-        if (matchName != testItem->fName) {
-            continue;
-        }
-        enumItem = testItem;
-        break;
-    }
-    return enumItem;    // returns nullptr if matchName is undocumented
-}
-
-// walk children and output complete method doxygen description
-void IncludeWriter::methodOut(Definition* method, const Definition& child) {
-    if (fPendingMethod) {
-        this->indentOut();
-        fPendingMethod = false;
-    }
-    fBmhMethod = method;
-    fMethodDef = &child;
-    fContinuation = nullptr;
-    fDeferComment = nullptr;
-    Definition* csParent = method->csParent();
-    if (csParent && (0 == fIndent || fIndentNext)) {
-        this->indentIn(IndentKind::kMethodOut);
-        fIndentNext = false;
-    }
-    if (method->fChildren.end() != std::find_if(method->fChildren.begin(), method->fChildren.end(),
-            [](const Definition* def) { return MarkType::kPopulate == def->fMarkType; } )) {
-        std::list<Definition>::iterator iter;
-        const Definition* childPtr = &child;
-        SkDEBUGCODE(bool sawMethod = false);
-        do {
-            int commentIndex = childPtr->fParentIndex;
-            iter = childPtr->fParent->fTokens.begin();
-            std::advance(iter, commentIndex);
-            SkDEBUGCODE(sawMethod |= MarkType::kMethod == iter->fMarkType);
-            while (--commentIndex >= 0) {
-                std::advance(iter, -1);
-                if (Bracket::kSlashStar == iter->fBracket) {
-                    SkASSERT(sawMethod);
-                    break;
-                }
-                SkASSERT(!sawMethod);
-                SkDEBUGCODE(sawMethod |= MarkType::kMethod == iter->fMarkType);
-            }
-            if (MarkType::kMethod != iter->fMarkType) {
-                break;
-            }
-            childPtr = childPtr->fParent;
-            SkDEBUGCODE(sawMethod = true);
-        } while (true);
-        SkASSERT(Bracket::kSlashSlash == iter->fBracket || Bracket::kSlashStar == iter->fBracket);
-        this->lf(2);
-        this->writeString("/");
-        this->writeBlock(iter->length(), iter->fContentStart);
-        this->lfcr();
-    } else {
-        this->writeCommentHeader();
-        fIndent += 4;
-        this->descriptionOut(method, SkipFirstLine::kNo, Phrase::kNo);
-        // compute indention column
-        size_t column = 0;
-        bool hasParmReturn = false;
-        for (auto methodPart : method->fChildren) {
-            if (MarkType::kParam == methodPart->fMarkType) {
-                column = SkTMax(column, methodPart->fName.length());
-                hasParmReturn = true;
-            } else if (MarkType::kReturn == methodPart->fMarkType) {
-                hasParmReturn = true;
-            }
-        }
-        if (hasParmReturn) {
-            this->lf(2);
-            column += fIndent + sizeof("@return ");
-            int saveIndent = fIndent;
-            for (auto methodPart : method->fChildren) {
-                if (MarkType::kParam == methodPart->fMarkType) {
-                    this->writeString("@param");
-                    this->writeSpace();
-                    this->writeString(methodPart->fName.c_str());
-                } else if (MarkType::kReturn == methodPart->fMarkType) {
-                    this->writeString("@return");
-                } else {
-                    continue;
-                }
-                this->indentToColumn(column);
-                fIndent = column;
-                this->descriptionOut(methodPart, SkipFirstLine::kNo, Phrase::kYes);
-                fIndent = saveIndent;
-                this->lfcr();
-            }
-        } else {
-            this->lfcr();
-        }
-        fIndent -= 4;
-        this->lfcr();
-        this->writeCommentTrailer(OneLine::kNo);
-    }
-    fBmhMethod = nullptr;
-    fMethodDef = nullptr;
-    fEnumDef = nullptr;
-    fWroteMethod = true;
-}
-
-void IncludeWriter::structOut(const Definition* root, const Definition& child,
-        const char* commentStart, const char* commentEnd) {
-    this->writeCommentHeader();
-    this->writeString("\\");
-    SkASSERT(MarkType::kClass == child.fMarkType || MarkType::kStruct == child.fMarkType);
-    this->writeString(MarkType::kClass == child.fMarkType ? "class" : "struct");
-    this->writeSpace();
-    this->writeString(child.fName.c_str());
-    fIndent += 4;
-    this->lfcr();
-    this->rewriteBlock((int)(commentEnd - commentStart), commentStart, Phrase::kNo);
-    fIndent -= 4;
-    this->lfcr();
-    this->writeCommentTrailer(OneLine::kNo);
-}
-
-bool IncludeWriter::findEnumSubtopic(string undername, const Definition** rootDefPtr) const {
-    const Definition* subtopic = fEnumDef->fParent;
-    string subcheck = subtopic->fFiddle + '_' + undername;
-    auto iter = fBmhParser->fTopicMap.find(subcheck);
-    if (iter == fBmhParser->fTopicMap.end()) {
-        return false;
-    }
-    *rootDefPtr = iter->second;
-    return true;
-}
-
-Definition* IncludeWriter::findMemberCommentBlock(const vector<Definition*>& bmhChildren,
-        string name) const {
-    for (auto memberDef : bmhChildren) {
-        if (MarkType::kMember != memberDef->fMarkType) {
-            continue;
-        }
-        string match = memberDef->fName;
-        // if match.endsWith(name) ...
-        if (match.length() >= name.length() &&
-                0 == match.compare(match.length() - name.length(), name.length(), name)) {
-            return memberDef;
-        }
-    }
-    for (auto memberDef : bmhChildren) {
-        if (MarkType::kSubtopic != memberDef->fMarkType && MarkType::kTopic != memberDef->fMarkType) {
-            continue;
-        }
-        Definition* result = this->findMemberCommentBlock(memberDef->fChildren, name);
-        if (result) {
-            return result;
-        }
-    }
-    return nullptr;
-}
-
-Definition* IncludeWriter::findMethod(string name, RootDefinition* root) const {
-    if (root) {
-        return root->find(name, RootDefinition::AllowParens::kNo);
-    }
-    auto methodIter = fBmhParser->fMethodMap.find(name);
-    if (fBmhParser->fMethodMap.end() == methodIter) {
-        return nullptr;
-    }
-    return &methodIter->second;
-}
-
-
-void IncludeWriter::firstBlock(int size, const char* data) {
-     SkAssertResult(this->firstBlockTrim(size, data));
-}
-
-bool IncludeWriter::firstBlockTrim(int size, const char* data) {
-    bool result = this->writeBlockTrim(size, data);
-    if (fFirstWrite) {
-        auto fileInfo = std::find_if(fRootTopic->fChildren.begin(), fRootTopic->fChildren.end(),
-                [](const Definition* def){ return MarkType::kFile == def->fMarkType; } );
-        if (fRootTopic->fChildren.end() != fileInfo) {
-            this->writeCommentHeader();
-            this->writeString("\\file");
-            this->writeSpace();
-            size_t lastSlash = fFileName.rfind('/');
-            if (string::npos == lastSlash) {
-                lastSlash = fFileName.rfind('\\');
-            }
-            string fileName = fFileName.substr(lastSlash + 1);
-            this->writeString(fileName);
-            this->lf(2);
-            fIndent += 4;
-            this->descriptionOut(*fileInfo, SkipFirstLine::kNo, Phrase::kNo);
-            fIndent -= 4;
-            this->writeCommentTrailer(OneLine::kNo);
-        }
-        fFirstWrite = false;
-    }
-    return result;
-}
-
-void IncludeWriter::setStart(const char* start, const Definition* def) {
-    SkASSERT(start >= fStart);
-    this->setStartBack(start, def);
-}
-
-void IncludeWriter::setStartBack(const char* start, const Definition* def) {
-    fStartSetter = def;
-    fStart = start;
-}
-
-Definition* IncludeWriter::structMemberOut(const Definition* memberStart, const Definition& child) {
-    const char* blockStart = !fWroteMethod && fDeferComment ? fDeferComment->fContentEnd : fStart;
-    const char* blockEnd = fWroteMethod && fDeferComment ? fDeferComment->fStart - 1 :
-            memberStart->fStart;
-    this->firstBlockTrim((int) (blockEnd - blockStart), blockStart);
-    this->indentDeferred(IndentKind::kStructMember);
-    fWroteMethod = false;
-    string name(child.fContentStart, (int) (child.fContentEnd - child.fContentStart));
-    Definition* commentBlock = this->findMemberCommentBlock(fBmhStructDef->fChildren, name);
-    if (!commentBlock) {
-        return memberStart->reportError<Definition*>("member missing comment block 2");
-    }
-    auto lineIter = std::find_if(commentBlock->fChildren.begin(), commentBlock->fChildren.end(),
-        [](const Definition* def){ return MarkType::kLine == def->fMarkType; } );
-    SkASSERT(commentBlock->fChildren.end() != lineIter);
-    const Definition* lineDef = *lineIter;
-    if (fStructMemberLength > 100) {
-        this->writeCommentHeader();
-        this->writeSpace();
-        this->rewriteBlock(lineDef->length(), lineDef->fContentStart, Phrase::kYes);
-        this->writeCommentTrailer(OneLine::kYes);
-    }
-    this->lfcr();
-    this->writeBlock((int) (child.fStart - memberStart->fContentStart),
-            memberStart->fContentStart);
-    this->indentToColumn(fStructMemberTab);
-    this->writeString(name.c_str());
-    auto tokenIter = child.fParent->fTokens.begin();
-    std::advance(tokenIter, child.fParentIndex + 1);
-    Definition* valueStart = &*tokenIter;
-    while (Definition::Type::kPunctuation != tokenIter->fType) {
-        std::advance(tokenIter, 1);
-        SkASSERT(child.fParent->fTokens.end() != tokenIter);
-    }
-    Definition* valueEnd = &*tokenIter;
-    if (valueStart != valueEnd) {
-        this->indentToColumn(fStructValueTab);
-        this->writeString("=");
-        this->writeSpace();
-        this->writeBlock((int) (valueEnd->fStart - valueStart->fContentStart),
-                valueStart->fContentStart);
-    }
-    this->writeString(";");
-    if (fStructMemberLength <= 100) {
-        this->indentToColumn(fStructCommentTab);
-        this->writeString("//!<");
-        this->writeSpace();
-        this->rewriteBlock(lineDef->length(), lineDef->fContentStart, Phrase::kYes);
-    }
-    this->lf(1);
-    return valueEnd;
-}
-
-// const and constexpr and #define aren't contained in a braces like struct and enum.
-// use a bmh subtopic to group like ones together, then measure them in the include as if
-// they were formally linked together
-void IncludeWriter::constSizeMembers(const RootDefinition* root) {
-    // fBmhConst->fParent is subtopic containing all grouped const expressions
-    // fConstDef is token of const include name, hopefully on same line as const start
-    string rootPrefix = root ? root->fName + "::" : "";
-    const Definition* test = fConstDef;
-    int tokenIndex = test->fParentIndex;
-    int longestName = 0;
-    int longestValue = 0;
-    int longestComment = 0;
-    const Definition* subtopic = fBmhConst->fParent;
-    SkASSERT(subtopic);
-    SkASSERT(MarkType::kSubtopic == subtopic->fMarkType);
-    // back up to first token on line
-    size_t lineCount = test->fLineCount;
-    const Definition* last;
-    auto tokenIter = test->fParent->fTokens.begin();
-    std::advance(tokenIter, tokenIndex);
-    do {
-        last = test;
-        std::advance(tokenIter, -1);
-        test = &*tokenIter;
-        SkASSERT(test->fParentIndex == --tokenIndex);
-    } while (lineCount == test->fLineCount);
-    test = last;
-    for (auto child : subtopic->fChildren) {
-        if (MarkType::kConst != child->fMarkType) {
-            continue;
-        }
-        // expect found name to be on the left of assign
-        // expect assign
-        // expect semicolon
-        // no parens, no braces
-        while (rootPrefix + test->fName != child->fName) {
-            std::advance(tokenIter, 1);
-            test = &*tokenIter;
-            SkASSERT(lineCount >= test->fLineCount);
-        }
-        ++lineCount;
-        TextParser constText(test);
-        const char* nameEnd = constText.trimmedBracketEnd('=');
-        SkAssertResult(constText.skipToEndBracket('='));
-        const char* valueEnd = constText.trimmedBracketEnd(';');
-        auto lineIter = std::find_if(child->fChildren.begin(), child->fChildren.end(),
-                [](const Definition* def){ return MarkType::kLine == def->fMarkType; });
-        SkASSERT(child->fChildren.end() != lineIter);
-        longestName = SkTMax(longestName, (int) (nameEnd - constText.fStart));
-        longestValue = SkTMax(longestValue, (int) (valueEnd - constText.fChar));
-        longestComment = SkTMax(longestComment, (*lineIter)->length());
-    }
-    // write fStructValueTab, fStructCommentTab
-    fConstValueTab = longestName + fIndent + 1;
-    fConstCommentTab = fConstValueTab + longestValue + 2;
-    fConstLength = fConstCommentTab + longestComment + (int) sizeof("//!<");
-}
-
-bool IncludeWriter::defineOut(const Definition& def) {
-    if (def.fTokens.size() < 1) {
-        return false;
-    }
-    auto& child = def.fTokens.front();
-    string name(child.fContentStart, child.length());
-    auto defIter = fBmhParser->fDefineMap.find(name);
-    if (fBmhParser->fDefineMap.end() == defIter) {
-        return false;
-    }
-    const Definition& bmhDef = defIter->second;
-    this->constOut(&def, &bmhDef);
-    return true;
-}
-
-void IncludeWriter::structSizeMembers(const Definition& child) {
-    int longestType = 0;
-    Definition* typeStart = nullptr;
-    int longestName = 0;
-    int longestValue = 0;
-    int longestComment = 0;
-    SkASSERT(child.fChildren.size() == 1 || child.fChildren.size() == 2);
-    bool inEnum = false;
-    bool inMethod = false;
-    bool inMember = false;
-    auto brace = child.fChildren[0];
-    SkASSERT(Bracket::kBrace == brace->fBracket);
-    for (auto& token : brace->fTokens) {
-        if (Definition::Type::kBracket == token.fType) {
-            if (Bracket::kSlashSlash == token.fBracket) {
-                continue;  // ignore old inline comments
-            }
-            if (Bracket::kSlashStar == token.fBracket) {
-                continue;  // ignore old inline comments
-            }
-            if (Bracket::kParen == token.fBracket) {
-                if (inMethod) {
-                    continue;
-                }
-                break;
-            }
-            if (Bracket::kAngle == token.fBracket) {
-                // in template param
-                continue;
-            }
-            SkASSERT(0); // incomplete
-        }
-        if (Definition::Type::kKeyWord == token.fType) {
-            switch (token.fKeyWord) {
-                case KeyWord::kEnum:
-                    inEnum = true;
-                    break;
-                case KeyWord::kConst:
-                case KeyWord::kConstExpr:
-                case KeyWord::kStatic:
-                case KeyWord::kInt:
-                case KeyWord::kUint8_t:
-                case KeyWord::kUint16_t:
-                case KeyWord::kUint32_t:
-                case KeyWord::kUint64_t:
-                case KeyWord::kUintPtr_t:
-                case KeyWord::kUnsigned:
-                case KeyWord::kSize_t:
-                case KeyWord::kFloat:
-                case KeyWord::kBool:
-                case KeyWord::kChar:
-                case KeyWord::kVoid:
-                    if (!typeStart) {
-                        typeStart = &token;
-                    }
-                    break;
-                default:
-                    break;
-            }
-            continue;
-        }
-        if (Definition::Type::kPunctuation == token.fType) {
-            if (inEnum) {
-                SkASSERT(Punctuation::kSemicolon == token.fPunctuation);
-                inEnum = false;
-            }
-            if (inMethod) {
-                if (Punctuation::kColon == token.fPunctuation) {
-                    inMethod = false;
-                } else if (Punctuation::kLeftBrace == token.fPunctuation) {
-                    inMethod = false;
-                } else if (Punctuation::kSemicolon == token.fPunctuation) {
-                    inMethod = false;
-                } else if (Punctuation::kAsterisk == token.fPunctuation) {
-                    inMethod = false;
-                } else {
-                    SkASSERT(0);  // incomplete
-                }
-            }
-            if (inMember) {
-                SkASSERT(Punctuation::kSemicolon == token.fPunctuation);
-                typeStart = nullptr;
-                inMember = false;
-            }
-            continue;
-        }
-        if (Definition::Type::kWord != token.fType) {
-            SkASSERT(0); // incomplete
-        }
-        if (MarkType::kMember == token.fMarkType) {
-            TextParser typeStr(token.fFileName, typeStart->fContentStart, token.fContentStart,
-                    token.fLineCount);
-            typeStr.trimEnd();
-            longestType = SkTMax(longestType, (int) (typeStr.fEnd - typeStr.fStart));
-            longestName = SkTMax(longestName, (int) (token.fContentEnd - token.fContentStart));
-            typeStart->fMemberStart = true;
-            inMember = true;
-            string tokenName(token.fContentStart, (int) (token.fContentEnd - token.fContentStart));
-            Definition* commentBlock = this->findMemberCommentBlock(fBmhStructDef->fChildren,
-                    tokenName);
-            if (!commentBlock) {
-                return token.reportError<void>("member missing comment block 1");
-            }
-            auto lineIter = std::find_if(commentBlock->fChildren.begin(),
-                    commentBlock->fChildren.end(),
-                    [](const Definition* def){ return MarkType::kLine == def->fMarkType; } );
-            SkASSERT(commentBlock->fChildren.end() != lineIter);
-            const Definition* lineDef = *lineIter;
-            longestComment = SkTMax(longestComment, lineDef->length());
-            continue;
-        }
-        if (MarkType::kMethod == token.fMarkType) {
-            inMethod = true;
-            continue;
-        }
-        SkASSERT(MarkType::kNone == token.fMarkType);
-        if (typeStart) {
-            if (inMember) {
-                longestValue =
-                        SkTMax(longestValue, (int) (token.fContentEnd - token.fContentStart));
-            }
-        } else {
-            typeStart = &token;
-        }
-    }
-    fStructMemberTab = longestType + fIndent + 1 /* space before name */ ;
-    fStructValueTab = fStructMemberTab + longestName + 2 /* space ; */ ;
-    fStructCommentTab = fStructValueTab;
-    if (longestValue) {
-        fStructCommentTab += longestValue + 3 /* space = space */ ;
-        fStructValueTab -= 1 /* ; */ ;
-    }
-    fStructMemberLength = fStructCommentTab + longestComment;
-    // iterate through struct to ensure that members' comments fit on line
-    // struct or class may not have any members
-    (void) this->checkChildCommentLength(fBmhStructDef, MarkType::kMember);
-}
-
-static bool find_start(const Definition* startDef, const char* start) {
-    for (const auto& child : startDef->fTokens) {
-        if (child.fContentStart == start) {
-            return MarkType::kMethod == child.fMarkType;
-        }
-        if (child.fContentStart >= start) {
-            break;
-        }
-        if (find_start(&child, start)) {
-            return true;
-        }
-    }
-    return false;
-}
-
-bool IncludeWriter::populate(Definition* def, ParentPair* prevPair, RootDefinition* root) {
-    if (!def->fTokens.size()) {
-        return true;
-    }
-    ParentPair pair = { def, prevPair };
-    // write bulk of original include up to class, method, enum, etc., excepting preceding comment
-    // find associated bmh object
-    // write any associated comments in Doxygen form
-    // skip include comment
-    // if there is a series of same named methods, write one set of comments, then write all methods
-    string methodName;
-    Definition* method = nullptr;
-    Definition* clonedMethod = nullptr;
-    const Definition* memberStart = nullptr;
-    const Definition* memberEnd = nullptr;
-    fContinuation = nullptr;
-    bool inStruct = false;
-    bool inConstructor = false;
-    bool inInline = false;
-    bool eatOperator = false;
-    bool sawConst = false;
-    bool staticOnly = false;
-    bool sawTypedef = false;
-    Definition* deferredTypedefComment = nullptr;
-    const Definition* requireDense = nullptr;
-    const Definition* startDef = nullptr;
-    for (auto& child : def->fTokens) {
-        if (KeyWord::kInline == child.fKeyWord) {
-            continue;
-        }
-        if (KeyWord::kOperator == child.fKeyWord && method &&
-                Definition::MethodType::kOperator == method->fMethodType) {
-            eatOperator = true;
-            continue;
-        }
-        if (eatOperator) {
-            if (Bracket::kSquare == child.fBracket || Bracket::kParen == child.fBracket) {
-                continue;
-            }
-            eatOperator = false;
-            fContinuation = nullptr;
-            if (KeyWord::kConst == child.fKeyWord) {
-                continue;
-            }
-        }
-        if (memberEnd) {
-            if (memberEnd != &child) {
-                continue;
-            }
-            startDef = &child;
-            this->setStart(child.fContentStart + 1, &child);
-            memberEnd = nullptr;
-        }
-        if (child.fPrivate) {
-            if (MarkType::kMethod == child.fMarkType) {
-                inInline = true;
-            }
-            continue;
-        }
-        if (inInline) {
-            if (Definition::Type::kKeyWord == child.fType) {
-                SkASSERT(MarkType::kMethod != child.fMarkType);
-                continue;
-            }
-            if (Definition::Type::kPunctuation == child.fType) {
-                if (Punctuation::kLeftBrace == child.fPunctuation) {
-                    inInline = false;
-                } else {
-                    SkASSERT(Punctuation::kAsterisk == child.fPunctuation);
-                }
-                continue;
-            }
-            if (Definition::Type::kWord == child.fType) {
-                string name(child.fContentStart, child.fContentEnd - child.fContentStart);
-                SkASSERT(string::npos != name.find("::"));
-                continue;
-            }
-            if (Definition::Type::kBracket == child.fType) {
-                SkASSERT(Bracket::kParen == child.fBracket);
-                continue;
-            }
-        }
-        if (fContinuation) {
-            if (Definition::Type::kKeyWord == child.fType) {
-                if (KeyWord::kFriend == child.fKeyWord ||
-                        KeyWord::kSK_API == child.fKeyWord) {
-                    continue;
-                }
-                const IncludeKey& includeKey = kKeyWords[(int) child.fKeyWord];
-                if (KeyProperty::kNumber == includeKey.fProperty) {
-                    continue;
-                }
-            }
-            if (Definition::Type::kBracket == child.fType) {
-                if (Bracket::kAngle == child.fBracket) {
-                    continue;
-                }
-                if (Bracket::kParen == child.fBracket) {
-                    if (!clonedMethod) {
-                        if (inConstructor) {
-                            fContinuation = child.fContentStart;
-                        }
-                        continue;
-                    }
-                    int alternate = 1;
-                    ptrdiff_t childLen = child.fContentEnd - child.fContentStart;
-                    SkASSERT(')' == child.fContentStart[childLen]);
-                    ++childLen;
-                    do {
-                        TextParser params(clonedMethod->fFileName, clonedMethod->fStart,
-                            clonedMethod->fContentStart, clonedMethod->fLineCount);
-                        params.skipToEndBracket('(');
-                        if (params.startsWith(child.fContentStart, childLen)) {
-                            this->methodOut(clonedMethod, child);
-                            sawConst = false;
-                            break;
-                        }
-                        ++alternate;
-                        string alternateMethod = methodName + '_' + to_string(alternate);
-                       clonedMethod = this->findMethod(alternateMethod, root);
-                    } while (clonedMethod);
-                    if (!clonedMethod) {
-                        return child.reportError<bool>("cloned method not found");
-                    }
-                    clonedMethod = nullptr;
-                    continue;
-                }
-            }
-            if (Definition::Type::kWord == child.fType) {
-                if (clonedMethod) {
-                    continue;
-                }
-                size_t len = (size_t) (child.fContentEnd - child.fContentStart);
-                const char operatorStr[] = "operator";
-                size_t operatorLen = sizeof(operatorStr) - 1;
-                if (len >= operatorLen && !strncmp(child.fContentStart, operatorStr, operatorLen)) {
-                    fContinuation = child.fContentEnd;
-                    continue;
-                }
-            }
-            if (Definition::Type::kPunctuation == child.fType &&
-                    (Punctuation::kSemicolon == child.fPunctuation ||
-                    Punctuation::kLeftBrace == child.fPunctuation ||
-                    (Punctuation::kColon == child.fPunctuation && inConstructor))) {
-                SkASSERT(fContinuation[0] == '(');
-                const char* continueEnd = child.fContentStart;
-                while (continueEnd > fContinuation && isspace(continueEnd[-1])) {
-                    --continueEnd;
-                }
-                const char defaultTag[] = " = default";
-                size_t tagSize = sizeof(defaultTag) - 1;
-                const char* tokenEnd = continueEnd - tagSize;
-                if (tokenEnd <= fContinuation || strncmp(tokenEnd, defaultTag, tagSize)) {
-                    tokenEnd = continueEnd;
-                }
-                methodName += string(fContinuation, tokenEnd - fContinuation);
-                if (string::npos != methodName.find('\n')) {
-                    methodName.erase(std::remove(methodName.begin(), methodName.end(), '\n'),
-                                    methodName.end());
-                }
-                method = this->findMethod(methodName, root);
-                if (!method) {
-                    return child.reportError<bool>("method not found");
-                }
-                this->methodOut(method, child);
-                sawConst = false;
-                continue;
-            }
-            if (Definition::Type::kPunctuation == child.fType &&
-                    Punctuation::kAsterisk == child.fPunctuation &&
-                    clonedMethod) {
-                continue;
-            }
-            if (inConstructor) {
-                continue;
-            }
-            method = this->findMethod(methodName + "()", root);
-            if (method) {
-                if (method->fCloned) {
-                    clonedMethod = method;
-                    continue;
-                }
-                this->methodOut(method, child);
-                sawConst = false;
-                continue;
-            }
-            if (KeyWord::kTemplate == child.fParent->fKeyWord) {
-                // incomplete; no support to template specialization in public includes
-                fContinuation = nullptr;
-                continue;
-            }
-            return child.reportError<bool>("method not found");
-        }
-        if (Bracket::kSlashSlash == child.fBracket || Bracket::kSlashStar == child.fBracket) {
-            if (!fDeferComment) {
-                fDeferComment = &child;
-            }
-            continue;
-        }
-        if (MarkType::kMethod == child.fMarkType) {
-            if (this->isInternalName(child)) {
-                continue;
-            }
-            if (child.fUndocumented) {
-                continue;
-            }
-            if (KeyWord::kTemplate == child.fParent->fKeyWord) {
-                // todo: support template specializations
-                continue;
-            }
-            const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
-                    child.fContentStart;
-            if (Definition::Type::kBracket == def->fType && Bracket::kDebugCode == def->fBracket) {
-                auto tokenIter = def->fParent->fTokens.begin();
-                std::advance(tokenIter, def->fParentIndex - 1);
-                Definition* prior = &*tokenIter;
-                if (Definition::Type::kBracket == def->fType &&
-                        Bracket::kSlashStar == prior->fBracket) {
-                    bodyEnd = prior->fContentStart - 1;
-                }
-            }
-            // FIXME: roll end-trimming into writeBlockTrim call
-            while (fStart < bodyEnd && ' ' >= bodyEnd[-1]) {
-                --bodyEnd;
-            }
-            int blockSize = (int) (bodyEnd - fStart);
-            SkASSERT(blockSize >= 0);
-            if (blockSize) {
-                string debugstr(fStart, blockSize);
-                this->writeBlock(blockSize, fStart);
-            }
-            startDef = &child;
-            this->setStart(child.fContentStart, &child);
-            auto mapFind = fBmhParser->fMethodMap.find(child.fName);
-            if (fBmhParser->fMethodMap.end() != mapFind) {
-                inConstructor = false;
-                method = &mapFind->second;
-                methodName = child.fName;
-            } else if (root) {
-                methodName = root->fName + "::" + child.fName;
-                size_t lastName = root->fName.rfind(':');
-                lastName = string::npos == lastName ? 0 : lastName + 1;
-                inConstructor = root->fName.substr(lastName) == child.fName;
-                method = root->find(methodName, RootDefinition::AllowParens::kNo);
-            }
-            fContinuation = child.fContentEnd;
-            if (!method) {
-                continue;
-            }
-            if (method->fCloned) {
-                clonedMethod = method;
-                continue;
-            }
-            this->methodOut(method, child);
-            sawConst = false;
-            continue;
-        }
-        if (Definition::Type::kKeyWord == child.fType) {
-            if (child.fUndocumented) {
-                continue;
-            }
-            switch (child.fKeyWord) {
-                case KeyWord::kStruct:
-                case KeyWord::kClass:
-                    fICSStack.push_back(&child);
-                    fStructEnded = false;
-                    fStructMemberTab = 0;
-                    // if struct contains members, compute their name and comment tabs
-                    if (child.fChildren.size() > 0) {
-                        const ParentPair* testPair = &pair;
-                        while ((testPair = testPair->fPrev)) {
-                            if (KeyWord::kClass == testPair->fParent->fKeyWord) {
-                                inStruct = fInStruct = true;
-                                break;
-                            }
-                        }
-                    }
-                    if (fInStruct) {
-                        // try child; root+child; root->parent+child; etc.
-                        int trial = 0;
-                        RootDefinition* search = root;
-                        Definition* parent = search->fParent;
-                        do {
-                            string name;
-                            if (0 == trial) {
-                                name = child.fName;
-                            } else if (1 == trial) {
-                                name = root->fName + "::" + child.fName;
-                            } else if (2 == trial) {
-                                name = root->fName;
-                            } else {
-                                SkASSERT(parent);
-                                name = parent->fName + "::" + child.fName;
-                                search = parent->asRoot();
-                                parent = search->fParent;
-                            }
-                            fBmhStructDef = search->find(name, RootDefinition::AllowParens::kNo);
-                        } while (!fBmhStructDef && ++trial);
-                        root = fBmhStructDef->asRoot();
-                        SkASSERT(root);
-                        fIndent += 4;
-                        this->structSizeMembers(child);
-                        fIndent -= 4;
-                        SkASSERT(!fIndentNext);
-                        fIndentNext = true;
-                    }
-                    if (child.fChildren.size() > 0) {
-                        const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
-                                child.fContentStart;
-                        this->writeBlockTrim((int) (bodyEnd - fStart), fStart);
-                        if (fPendingMethod) {
-                            if (fIndent >= 4) {
-                                this->indentOut();
-                            }
-                            fPendingMethod = false;
-                        }
-                        startDef = requireDense ? requireDense : &child;
-                        if (requireDense) {
-                            startDef = requireDense;
-                            this->setStart(requireDense->fContentStart, requireDense);
-                        } else {
-                            startDef = &child;
-                            this->setStart(child.fContentStart, &child);
-                        }
-                        requireDense = nullptr;
-                        if (!fInStruct && (!root || child.fName != root->fName)) {
-                            root = &fBmhParser->fClassMap[child.fName];
-                            fRootTopic = root->fParent;
-                            SkASSERT(!root->fVisited);
-                            root->clearVisited();
-#if 0
-    // this seems better balanced; but real problem is probably fInStruct
-                            if (fIndentStack.size() > 0) {
-                                this->indentOut();
-                            }
-                            SkASSERT(!fIndent);
-#else
-                            fIndent = 0;
-#endif
-                            fBmhStructDef = root;
-                        }
-                        if (child.fName == root->fName) {
-                            if (Definition* parent = root->fParent) {
-                                if (MarkType::kTopic == parent->fMarkType ||
-                                        MarkType::kSubtopic == parent->fMarkType) {
-                                    const char* commentStart = root->fContentStart;
-                                    unsigned index = 0;
-                                    const char* commentEnd = root->fChildren[0]->fStart;
-                                    int line = 1;
-                                    do {
-                                        TextParser parser(root->fFileName, commentStart, commentEnd, line);
-                                        if (!parser.eof()) {
-                                            parser.skipWhiteSpace();
-                                        }
-                                        if (!parser.eof()) {
-                                            break;
-                                        }
-                                        commentStart = root->fChildren[index]->fTerminator;
-                                        ++index;
-                                        SkASSERT(index < root->fChildren.size());
-                                        commentEnd = root->fChildren[index]->fStart;
-                                    } while (true);
-                                    this->structOut(root, *root, commentStart, commentEnd);
-                                } else {
-                                    SkASSERT(0); // incomplete
-                                }
-                            } else {
-                                SkASSERT(0); // incomplete
-                            }
-                        } else {
-                            SkASSERT(fInStruct);
-                            Definition* priorBlock = fBmhStructDef;
-                            Definition* codeBlock = nullptr;
-                            Definition* nextBlock = nullptr;
-                            for (auto test : fBmhStructDef->fChildren) {
-                                if (MarkType::kCode == test->fMarkType) {
-                                    SkASSERT(!codeBlock);  // FIXME: check enum earlier
-                                    codeBlock = test;
-                                    continue;
-                                }
-                                if (codeBlock) {
-                                    nextBlock = test;
-                                    break;
-                                }
-                                priorBlock = test;
-                            }
-                      // FIXME: trigger error earlier if inner #Struct or #Class is missing #Code
-                            SkASSERT(codeBlock);
-                            SkASSERT(nextBlock);  // FIXME: check enum for correct order earlier
-                            const char* commentStart = codeBlock->fTerminator;
-                            const char* commentEnd = nextBlock->fStart;
-                    // FIXME: trigger error if #Code is present but comment is before it earlier
-                            SkASSERT(priorBlock); // code always preceded by #Line (I think)
-                            TextParser priorComment(priorBlock->fFileName,
-                                    priorBlock->fTerminator, codeBlock->fStart,
-                                    priorBlock->fLineCount);
-                            priorComment.trimEnd();
-                            if (!priorComment.eof()) {
-                                return priorBlock->reportError<bool>(
-                                        "expect no comment before #Code");
-                            }
-                            TextParser nextComment(codeBlock->fFileName, commentStart,
-                                    commentEnd, codeBlock->fLineCount);
-                            nextComment.trimEnd();
-                            if (!priorComment.eof()) {
-                                return priorBlock->reportError<bool>(
-                                        "expect comment after #Code");
-                            }
-                            if (!nextComment.eof()) {
-
-                            }
-                            fIndentNext = true;
-                            this->structOut(root, *fBmhStructDef, commentStart, commentEnd);
-                        }
-                        fDeferComment = nullptr;
-                    } else {
-                       // empty forward reference
-                        bool writeTwo = '\n' == child.fContentStart[-1]
-                                && '\n' == child.fContentStart[-2];
-                        if (writeTwo) {
-                            const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
-                                    child.fContentStart;
-                            this->writeBlockTrim((int) (bodyEnd - fStart), fStart);
-                            this->lf(writeTwo ? 2 : 1);
-                            fIndent = 0;
-                            this->writeBlockTrim(child.length() + 1, child.fContentStart);
-                            writeTwo = '\n' == child.fContentEnd[1]
-                                    && '\n' == child.fContentStart[2];
-                            this->lf(writeTwo ? 2 : 1);
-                            fStart = child.fContentEnd + 1;
-                            fDeferComment = nullptr;
-                        }
-                    }
-                    break;
-                case KeyWord::kEnum: {
-                    fInEnum = true;
-                    this->enumHeaderOut(root, child);
-                    this->enumSizeItems(child);
-                } break;
-                case KeyWord::kConst:
-                case KeyWord::kConstExpr:
-                    sawConst = !memberStart || staticOnly;
-                    if (!memberStart) {
-                        memberStart = &child;
-                        staticOnly = true;
-                    }
-                    if (MarkType::kConst == child.fMarkType) {
-                        auto constIter = fBmhParser->fConstMap.find(child.fName);
-                        if (fBmhParser->fConstMap.end() != constIter) {
-                            const RootDefinition& bmhConst = constIter->second;
-                            this->constOut(&child, &bmhConst);
-                            fDeferComment = nullptr;
-                        }
-                    }
-                    break;
-                case KeyWord::kStatic:
-                    if (!memberStart) {
-                        memberStart = &child;
-                        staticOnly = true;
-                    }
-                    break;
-                case KeyWord::kInt:
-                case KeyWord::kUint8_t:
-                case KeyWord::kUint16_t:
-                case KeyWord::kUint32_t:
-                case KeyWord::kUint64_t:
-                case KeyWord::kUintPtr_t:
-                case KeyWord::kUnsigned:
-                case KeyWord::kSize_t:
-                case KeyWord::kFloat:
-                case KeyWord::kBool:
-                case KeyWord::kChar:
-                case KeyWord::kVoid:
-                    staticOnly = false;
-                    if (!memberStart) {
-                        memberStart = &child;
-                    }
-                    break;
-                case KeyWord::kAlignAs:
-                case KeyWord::kPublic:
-                case KeyWord::kPrivate:
-                case KeyWord::kProtected:
-                case KeyWord::kFriend:
-                case KeyWord::kInline:
-                case KeyWord::kSK_API:
-                case KeyWord::kTemplate:
-                case KeyWord::kUsing:
-                    break;
-                case KeyWord::kTypedef:
-                    SkASSERT(!memberStart);
-                    memberStart = &child;
-                    deferredTypedefComment = fDeferComment;
-                    sawTypedef = true;
-                    break;
-                case KeyWord::kSK_BEGIN_REQUIRE_DENSE:
-                    requireDense = &child;
-                    break;
-                default:
-                    SkASSERT(0);
-            }
-            if (KeyWord::kUint8_t == child.fKeyWord || KeyWord::kUint32_t == child.fKeyWord) {
-                continue;
-            } else {
-                if (fInEnum && child.fChildren.size() > 0
-                        && KeyWord::kClass == child.fChildren[0]->fKeyWord) {
-                    if (!this->populate(child.fChildren[0], &pair, root)) {
-                        return false;
-                    }
-                } else {
-                    if (!this->populate(&child, &pair, root)) {
-                        return false;
-                    }
-                    if (KeyWord::kClass == child.fKeyWord || KeyWord::kStruct == child.fKeyWord) {
-                        fICSStack.pop_back();
-                        fStructEnded = true;
-                        if (fInStruct) {
-                            fInStruct = false;
-                            do {
-                                SkASSERT(root);
-                                root = const_cast<RootDefinition*>(root->fParent->asRoot());
-                            } while (MarkType::kTopic == root->fMarkType ||
-                                    MarkType::kSubtopic == root->fMarkType);
-#if 0
-                        }
-                        if (MarkType::kStruct == root->fMarkType ||
-                                MarkType::kClass == root->fMarkType) {
-#else
-                            SkASSERT(MarkType::kStruct == root->fMarkType ||
-                                    MarkType::kClass == root->fMarkType);
-#endif
-                            fPendingMethod = false;
-                            if (startDef) {
-                                fPendingMethod = find_start(startDef, fStart);
-                            }
-                            fOutdentNext = !fPendingMethod;
-                        }
-                    }
-                }
-            }
-            continue;
-        }
-        if (Definition::Type::kBracket == child.fType) {
-            if (KeyWord::kEnum == child.fParent->fKeyWord ||
-                    (KeyWord::kClass == child.fParent->fKeyWord && child.fParent->fParent &&
-                    KeyWord::kEnum == child.fParent->fParent->fKeyWord)) {
-                SkASSERT(Bracket::kBrace == child.fBracket);
-                this->enumMembersOut(*child.fParent);
-                this->writeString("};");
-                this->lf(2);
-                startDef = child.fParent;
-                this->setStart(child.fParent->fContentEnd, child.fParent);
-                SkASSERT(';' == fStart[0]);
-                ++fStart;
-                fDeferComment = nullptr;
-                fInEnum = false;
-                if (fIndentNext) {
-//                    fIndent -= 4;
-                    fIndentNext = false;
-                }
-                continue;
-            }
-            if (KeyWord::kDefine == child.fKeyWord && this->defineOut(child)) {
-                fDeferComment = nullptr;
-                continue;
-            }
-            fDeferComment = nullptr;
-            if (KeyWord::kClass == def->fKeyWord || KeyWord::kStruct == def->fKeyWord) {
-                fIndentNext = true;
-            }
-            if (!this->populate(&child, &pair, root)) {
-                return false;
-            }
-            if (KeyWord::kClass == def->fKeyWord || KeyWord::kStruct == def->fKeyWord) {
-                if (def->iRootParent() && (!fStartSetter
-                        || MarkType::kMethod != fStartSetter->fMarkType)) {
-                    this->setStart(child.fContentEnd, &child);
-                    fDeferComment = nullptr;
-                }
-            }
-            continue;
-        }
-        if (Definition::Type::kWord == child.fType) {
-            if (MarkType::kMember == child.fMarkType) {
-                if (!memberStart) {
-                    auto iter = def->fTokens.begin();
-                    std::advance(iter, child.fParentIndex - 1);
-                    memberStart = &*iter;
-                    staticOnly = false;
-                }
-                if (!fStructMemberTab) {
-                    SkASSERT(KeyWord::kStruct == def->fParent->fKeyWord);
-                    fIndent += 4;
-                    this->structSizeMembers(*def->fParent);
-                    fIndent -= 4;
-                    fIndentNext = true;
-                }
-                SkASSERT(fBmhStructDef);
-                memberEnd = this->structMemberOut(memberStart, child);
-                startDef = &child;
-                this->setStart(child.fContentEnd + 1, &child);
-                fDeferComment = nullptr;
-            } else if (MarkType::kNone == child.fMarkType && sawConst && fEnumDef) {
-                const Definition* bmhConst = nullptr;
-                string match;
-                if (root) {
-                    match = root->fName + "::";
-                }
-                match += string(child.fContentStart, child.fContentEnd - child.fContentStart);
-                for (auto enumChild : fEnumDef->fChildren) {
-                    if (MarkType::kConst == enumChild->fMarkType && enumChild->fName == match) {
-                        bmhConst = enumChild;
-                        break;
-                    }
-                }
-                if (bmhConst) {
-                    this->constOut(memberStart, bmhConst);
-                    fDeferComment = nullptr;
-                    sawConst = false;
-                }
-            } else if (MarkType::kNone == child.fMarkType && sawConst && !fEnumDef) {
-                string match;
-                if (root) {
-                    match = root->fName + "::";
-                    match += string(child.fContentStart, child.fContentEnd - child.fContentStart);
-                    auto bmhClassIter = fBmhParser->fClassMap.find(root->fName);
-                    if (fBmhParser->fClassMap.end() != bmhClassIter) {
-                        RootDefinition& bmhClass = bmhClassIter->second;
-                        auto constIter = std::find_if(bmhClass.fLeaves.begin(), bmhClass.fLeaves.end(),
-                                [match](std::pair<const string, Definition>& leaf){ return match == leaf.second.fName; } );
-                        if (bmhClass.fLeaves.end() != constIter) {
-                            const Definition& bmhConst = constIter->second;
-                            if (MarkType::kConst == bmhConst.fMarkType
-                                    && MarkType::kSubtopic == bmhConst.fParent->fMarkType) {
-                                fBmhConst = &bmhConst;
-                                fConstDef = &child;
-                            }
-                        }
-                    }
-                }
-            }
-            if (child.fMemberStart) {
-                memberStart = &child;
-                staticOnly = false;
-            }
-            continue;
-        }
-        if (Definition::Type::kPunctuation == child.fType) {
-            if (Punctuation::kSemicolon == child.fPunctuation) {
-                if (sawConst && fBmhConst) {  // find bmh documentation. Parent must be subtopic.
-                    const Definition* subtopic = fBmhConst->fParent;
-                    SkASSERT(subtopic);
-                    SkASSERT(MarkType::kSubtopic == subtopic->fMarkType);
-                    auto firstConst = std::find_if(subtopic->fChildren.begin(),
-                            subtopic->fChildren.end(),
-                            [](const Definition* def){ return MarkType::kConst == def->fMarkType;});
-                    SkASSERT(firstConst != subtopic->fChildren.end());
-                    bool constIsFirst = *firstConst == fBmhConst;
-                    if (constIsFirst) {  // If first #Const child, output subtopic description.
-                        this->constOut(memberStart, subtopic);
-                        // find member / value / comment tabs
-                        // look for a one-to-one correspondence between bmh and include
-                        this->constSizeMembers(root);
-                        fDeferComment = nullptr;
-                    }
-                    // after const code, output #Line description as short comment
-                    auto lineIter = std::find_if(fBmhConst->fChildren.begin(),
-                            fBmhConst->fChildren.end(),
-                            [](const Definition* def){ return MarkType::kLine == def->fMarkType; });
-                    SkASSERT(fBmhConst->fChildren.end() != lineIter);
-                    const Definition* lineDef = *lineIter;
-                    if (fConstLength > 100) {
-                        this->writeCommentHeader();
-                        this->writeSpace();
-                        this->rewriteBlock(lineDef->length(), lineDef->fContentStart, Phrase::kYes);
-                        this->writeCommentTrailer(OneLine::kYes);
-                    }
-                    this->lfcr();
-                    TextParser constText(memberStart);
-                    const char* nameEnd = constText.trimmedBracketEnd('=');
-                    SkAssertResult(constText.skipToEndBracket('='));
-                    const char* valueEnd = constText.trimmedBracketEnd(';');
-                    this->writeBlock((int) (nameEnd - memberStart->fContentStart),
-                            memberStart->fContentStart);
-                    this->indentToColumn(fConstValueTab);
-                    this->writeBlock((int) (valueEnd - constText.fChar), constText.fChar);
-                    this->writeString(";");
-                    if (fConstLength <= 100) {
-                        this->indentToColumn(fConstCommentTab);
-                        this->writeString("//!<");
-                        this->writeSpace();
-                        this->rewriteBlock(lineDef->length(), lineDef->fContentStart, Phrase::kYes);
-                    }
-                    this->setStart(child.fContentStart + 1, &child);
-                    fDeferComment = nullptr;
-                    fBmhConst = nullptr;
-                    sawConst = false;
-                } else if (sawTypedef) {
-                    const Definition* bmhTypedef = nullptr;
-                    if (root) {
-                        SkDEBUGCODE(auto classIter = fBmhParser->fClassMap.find(root->fName));
-                        SkASSERT(fBmhParser->fClassMap.end() != classIter);
-                        RootDefinition& classDef = fBmhParser->fClassMap[root->fName];
-                        auto leafIter = classDef.fLeaves.find(memberStart->fName);
-                        if (classDef.fLeaves.end() != leafIter) {
-                            bmhTypedef = &leafIter->second;
-                        }
-                    }
-                    if (!bmhTypedef) {
-                        auto typedefIter = fBmhParser->fTypedefMap.find(memberStart->fName);
-                        SkASSERT(fBmhParser->fTypedefMap.end() != typedefIter);
-                        bmhTypedef = &typedefIter->second;
-                    }
-                    fDeferComment = deferredTypedefComment;
-                    this->constOut(memberStart, bmhTypedef);
-                    fDeferComment = nullptr;
-                    sawTypedef = false;
-                }
-                memberStart = nullptr;
-                staticOnly = false;
-                if (inStruct) {
-                    fInStruct = false;
-                }
-                continue;
-            }
-            if (Punctuation::kLeftBrace == child.fPunctuation ||
-                    Punctuation::kColon == child.fPunctuation ||
-                    Punctuation::kAsterisk == child.fPunctuation
-                ) {
-                continue;
-            }
-        }
-    }
-    return true;
-}
-
-bool IncludeWriter::populate(BmhParser& bmhParser) {
-    bool allPassed = true;
-    for (auto& includeMapper : fIncludeMap) {
-        size_t lastSlash = includeMapper.first.rfind('/');
-        if (string::npos == lastSlash) {
-            lastSlash = includeMapper.first.rfind('\\');
-        }
-        if (string::npos == lastSlash || lastSlash >= includeMapper.first.length() - 1) {
-            return this->reportError<bool>("malformed include name");
-        }
-        string fileName = includeMapper.first.substr(lastSlash + 1);
-        if (".h" != fileName.substr(fileName.length() - 2)) {
-            return this->reportError<bool>("expected fileName.h");
-        }
-        string skClassName = fileName.substr(0, fileName.length() - 2);
-        this->reset();
-        fOut = fopen(fileName.c_str(), "wb");
-        if (!fOut) {
-            SkDebugf("could not open output file %s\n", fileName.c_str());
-            return false;
-        }
-        RootDefinition* root =
-                bmhParser.fClassMap.end() == bmhParser.fClassMap.find(skClassName) ?
-                nullptr : &bmhParser.fClassMap[skClassName];
-        fBmhParser = &bmhParser;
-        if (root) {
-            fRootTopic = root->fParent;
-            root->clearVisited();
-        } else {
-            SkASSERT("Sk" == skClassName.substr(0, 2));
-            string topicName = skClassName.substr(2);
-            auto topicIter = bmhParser.fTopicMap.find(topicName);
-            SkASSERT(bmhParser.fTopicMap.end() != topicIter);
-            fRootTopic = topicIter->second->asRoot();
-            fFirstWrite = true;   // write file information after includes
-        }
-        fFileName = includeMapper.second.fFileName;
-        this->setStartBack(includeMapper.second.fContentStart, &includeMapper.second);
-        fEnd = includeMapper.second.fContentEnd;
-        fAnonymousEnumCount = 1;
-        this->writeHeader(includeMapper);
-        allPassed &= this->populate(&includeMapper.second, nullptr, root);
-        this->writeBlock((int) (fEnd - fStart), fStart);
-#if 0
-        if (fIndentStack.size() > 0) {
-            this->indentOut();
-        }
-        SkASSERT(!fIndent);
-#else
-        fIndent = 0;
-#endif
-        this->lfcr();
-        this->writePending();
-        fclose(fOut);
-        fflush(fOut);
-        size_t slash = fFileName.find_last_of('/');
-        if (string::npos == slash) {
-            slash = 0;
-        }
-        size_t back = fFileName.find_last_of('\\');
-        if (string::npos == back) {
-            back = 0;
-        }
-        string dir = fFileName.substr(0, SkTMax(slash, back) + 1);
-        string readname = dir + fileName;
-        if (ParserCommon::WrittenFileDiffers(fileName, readname)) {
-            SkDebugf("wrote updated %s\n", fileName.c_str());
-        } else {
-            remove(fileName.c_str());
-        }
-    }
-    return allPassed;
-}
-
-string IncludeWriter::resolveMethod(const char* start, const char* end, bool first) {
-    string methodname(start, end - start);
-    if (string::npos != methodname.find("()")) {
-        return "";
-    }
-    string substitute;
-    auto rootDefIter = fBmhParser->fMethodMap.find(methodname);
-    if (fBmhParser->fMethodMap.end() != rootDefIter) {
-        substitute = methodname + "()";
-    } else {
-        RootDefinition* parent = nullptr;
-        for (auto candidate : fRootTopic->fChildren) {
-            if (MarkType::kClass == candidate->fMarkType
-                    || MarkType::kStruct == candidate->fMarkType) {
-                parent = candidate->asRoot();
-                break;
-            }
-        }
-        if (parent) {
-            auto defRef = parent->find(parent->fName + "::" + methodname,
-                    RootDefinition::AllowParens::kNo);
-            if (defRef && MarkType::kMethod == defRef->fMarkType) {
-                substitute = methodname + "()";
-            } else {
-                auto defineIter = fBmhParser->fDefineMap.find(methodname);
-                if (fBmhParser->fDefineMap.end() != defineIter) {
-                    const RootDefinition& defineDef = defineIter->second;
-                    auto codeIter = std::find_if(defineDef.fChildren.begin(),
-                            defineDef.fChildren.end(),
-                            [](Definition* child){ return MarkType::kCode == child->fMarkType; } );
-                    if (defineDef.fChildren.end() != codeIter) {
-                        const Definition* codeDef = *codeIter;
-                        string codeContents(codeDef->fContentStart, codeDef->length());
-                        size_t namePos = codeContents.find(methodname);
-                        if (string::npos != namePos) {
-                            size_t parenPos = namePos + methodname.length();
-                            if (parenPos < codeContents.length() && '(' == codeContents[parenPos]) {
-                                substitute = methodname + "()";
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-    if (fMethodDef && methodname == fMethodDef->fName) {
-        TextParser report(fBmhMethod);
-        report.reportError("method should not include references to itself");
-        return "";
-    }
-    if (fBmhMethod) {
-        for (auto child : fBmhMethod->fChildren) {
-            if (MarkType::kParam != child->fMarkType) {
-                continue;
-            }
-            if (methodname == child->fName) {
-                return "";
-            }
-        }
-    }
-    return substitute;
-}
-
-string IncludeWriter::resolveAlias(const Definition* def) {
-    for (auto child : def->fChildren) {
-        if (MarkType::kSubstitute == child->fMarkType) {
-            return string(child->fContentStart, (int) (child->fContentEnd - child->fContentStart));
-        }
-        if (MarkType::kAlias == child->fMarkType && def->fName == child->fName) {
-            return this->resolveAlias(child);
-        }
-    }
-    return "";
-}
-
-string IncludeWriter::resolveRef(const char* start, const char* end, bool first,
-        RefType* refType) {
-        // look up Xxx_Xxx
-    string undername(start, end - start);
-    for (const auto& external : fBmhParser->fExternals) {
-        if (external.fName == undername) {
-            *refType = RefType::kExternal;
-            return external.fName;
-        }
-    }
-    *refType = RefType::kNormal;
-    SkASSERT(string::npos == undername.find(' '));
-    const Definition* rootDef = nullptr;
-    string substitute;
-    {
-        auto rootDefIter = fBmhParser->fTopicMap.find(undername);
-        if (fBmhParser->fTopicMap.end() != rootDefIter) {
-            rootDef = rootDefIter->second;
-        } else {
-            string prefixedName = fRootTopic->fName + '_' + undername;
-            rootDefIter = fBmhParser->fTopicMap.find(prefixedName);
-            if (fBmhParser->fTopicMap.end() != rootDefIter) {
-                rootDef = rootDefIter->second;
-            } else if (fBmhStructDef) {
-                string localPrefix = fBmhStructDef->fFiddle + '_' + undername;
-                rootDefIter = fBmhParser->fTopicMap.find(localPrefix);
-                if (fBmhParser->fTopicMap.end() != rootDefIter) {
-                    rootDef = rootDefIter->second;
-                }
-                if (!rootDef) {
-                    size_t doubleColon = fBmhStructDef->fName.rfind("::");
-                    if (string::npos != doubleColon && undername
-                            == fBmhStructDef->fName.substr(doubleColon + 2)) {
-                        substitute = fBmhStructDef->fName;
-                    }
-                }
-            }
-            if (!rootDef && fEnumDef && "Sk" + prefixedName == fEnumDef->fFiddle) {
-                rootDef = fEnumDef;
-            }
-            if (!rootDef && !substitute.length()) {
-                auto aliasIter = fBmhParser->fAliasMap.find(undername);
-                if (fBmhParser->fAliasMap.end() != aliasIter) {
-                    rootDef = aliasIter->second;
-                } else if (fInEnum && fEnumDef && this->findEnumSubtopic(undername, &rootDef)) {
-                } else if (!first) {
-                    this->fChar = start;
-                    this->fLine = start;
-                    this->fEnd = end;
-                    this->reportError("reference unfound");
-                    return "";
-                }
-            }
-        }
-    }
-    if (rootDef) {
-        MarkType rootType = rootDef->fMarkType;
-        if (MarkType::kSubtopic == rootType || MarkType::kTopic == rootType
-                || MarkType::kAlias == rootType) {
-            substitute = this->resolveAlias(rootDef);
-        }
-        if (!substitute.length()) {
-            string match = rootDef->fName;
-            size_t index;
-            while (string::npos != (index = match.find('_'))) {
-                match.erase(index, 1);
-            }
-            string skmatch = "Sk" + match;
-            auto parent = MarkType::kAlias == rootType ? rootDef->fParent : rootDef;
-            for (auto child : parent->fChildren) {
-                // there may be more than one
-                // prefer the one mostly closely matching in text
-                if ((MarkType::kClass == child->fMarkType ||
-                    MarkType::kStruct == child->fMarkType ||
-                    MarkType::kTypedef == child->fMarkType ||
-                    (MarkType::kEnum == child->fMarkType && !child->fAnonymous) ||
-                    MarkType::kEnumClass == child->fMarkType) && (match == child->fName ||
-                    skmatch == child->fName)) {
-                    substitute = child->fName;
-                    break;
-                }
-            }
-        }
-        if (!substitute.length()) {
-            for (auto child : rootDef->fChildren) {
-                if (MarkType::kSubstitute == child->fMarkType) {
-                    substitute = string(child->fContentStart, child->length());
-                    break;
-                }
-                // there may be more than one
-                // if so, it's a bug since it's unknown which is the right one
-                if (MarkType::kClass == child->fMarkType ||
-                        MarkType::kStruct == child->fMarkType ||
-                        (MarkType::kEnum == child->fMarkType && !child->fAnonymous) ||
-                        MarkType::kEnumClass == child->fMarkType) {
-                    SkASSERT("" == substitute);
-                    substitute = child->fName;
-                    if (MarkType::kEnum == child->fMarkType) {
-                        size_t parentClassEnd = substitute.find("::");
-                        SkASSERT(string::npos != parentClassEnd);
-                        string subEnd = substitute.substr(parentClassEnd + 2);
-                        if (fInEnum) {
-                            substitute = subEnd;
-                        }
-                        if (subEnd == undername) {
-                            break;
-                        }
-                    }
-                }
-            }
-        }
-        if (!substitute.length()) {
-            const Definition* parent = rootDef;
-            do {
-                parent = parent->fParent;
-            } while (parent && (MarkType::kSubtopic == parent->fMarkType
-                        || MarkType::kTopic == parent->fMarkType));
-            if (parent) {
-                if (MarkType::kClass == parent->fMarkType ||
-                        MarkType::kStruct == parent->fMarkType ||
-                        (MarkType::kEnum == parent->fMarkType && !parent->fAnonymous) ||
-                        MarkType::kEnumClass == parent->fMarkType) {
-                    if (parent->fParent != fRootTopic) {
-                        substitute = parent->fName;
-                        substitute += ' ';
-                        substitute += ParserCommon::ConvertRef(rootDef->fName, false);
-                    } else {
-                        size_t underpos = undername.find('_');
-                        if (string::npos != underpos) {
-                            string parentName = undername.substr(0, underpos);
-                            string skName = "Sk" + parentName;
-                            if (skName == parent->fName) {
-                                SkASSERT(start >= fLastDescription->fContentStart);
-                                string lastDescription = string(fLastDescription->fContentStart,
-                                        (int) (start - fLastDescription->fContentStart));
-                                size_t lineStart = lastDescription.rfind('\n');
-                                SkASSERT(string::npos != lineStart);
-                                fLine = fLastDescription->fContentStart + lineStart + 1;
-                                fChar = start;
-                                fEnd = end;
-                                return this->reportError<string>("remove underline");
-                            }
-                        }
-                        substitute += ParserCommon::ConvertRef(undername, first);
-                    }
-                }
-            }
-        }
-    }
-    // Ensure first word after period is capitalized if substitute is lower cased.
-    if (first && isupper(start[0]) && substitute.length() > 0 && islower(substitute[0])) {
-        substitute[0] = start[0];
-    }
-    return substitute;
-}
-
-int IncludeWriter::lookupMethod(const PunctuationState punctuation, const Word word,
-        const int lastSpace, const int run, int lastWrite, const char* data,
-        bool hasIndirection) {
-    int wordStart = lastSpace;
-    while (' ' >= data[wordStart]) {
-        ++wordStart;
-    }
-    const int wordEnd = PunctuationState::kDelimiter == punctuation ||
-            PunctuationState::kParen == punctuation ||
-            PunctuationState::kPeriod == punctuation ? run - 1 : run;
-    string temp;
-    if (hasIndirection && '(' != data[wordEnd - 1] && ')' != data[wordEnd - 1]) {
-        // FIXME: hard-coded to assume a.b or a->b is a.b() or a->b().
-        // need to check class a for member b to see if this is so
-        TextParser parser(fFileName, &data[wordStart], &data[wordEnd], fLineCount);
-        const char* indirection = parser.anyOf(".>");
-        if (&data[wordEnd] <= &indirection[2] || 'f' != indirection[1] ||
-                !isupper(indirection[2])) {
-            temp = string(&data[wordStart], wordEnd - wordStart) + "()";
-        }
-    } else {
-        temp = this->resolveMethod(&data[wordStart], &data[wordEnd], Word::kFirst == word);
-    }
-    if (temp.length()) {
-        if (wordStart > lastWrite) {
-            SkASSERT(data[wordStart - 1] >= ' ');
-            if (' ' == data[lastWrite]) {
-                this->writeSpace();
-            }
-            this->firstBlockTrim(wordStart - lastWrite, &data[lastWrite]);
-            if (' ' == data[wordStart - 1]) {
-                this->writeSpace();
-            }
-        }
-        SkASSERT(temp[temp.length() - 1] > ' ');
-        this->writeString(temp.c_str());
-        lastWrite = wordEnd;
-    }
-    return lastWrite;
-}
-
-int IncludeWriter::lookupReference(const PunctuationState punctuation, const Word word,
-        const int start, const int run, int lastWrite, const char last, const char* data) {
-    const int end = PunctuationState::kDelimiter == punctuation ||
-            PunctuationState::kParen == punctuation ||
-            PunctuationState::kPeriod == punctuation ? run - 1 : run;
-    RefType refType = RefType::kUndefined;
-    string resolved = string(&data[start], (size_t) (end - start));
-    string temp = this->resolveRef(&data[start], &data[end], Word::kFirst == word, &refType);
-    if (!temp.length()) {
-        if (Word::kFirst != word && '_' != last) {
-            temp = ParserCommon::ConvertRef(resolved, false);
-        }
-    }
-    if (temp.length()) {
-        if (start > lastWrite) {
-            SkASSERT(data[start - 1] >= ' ');
-            if (' ' == data[lastWrite]) {
-                this->writeSpace();
-            }
-            this->firstBlockTrim(start - lastWrite, &data[lastWrite]);
-            if (' ' == data[start - 1]) {
-                this->writeSpace();
-            }
-        }
-        SkASSERT(temp[temp.length() - 1] > ' ');
-        this->writeString(temp.c_str());
-        lastWrite = end;
-    }
-    return lastWrite;
-}
-
-/* returns true if rewriteBlock wrote linefeeds */
-IncludeWriter::Wrote IncludeWriter::rewriteBlock(int size, const char* data, Phrase phrase) {
-    bool wroteLineFeeds = false;
-    while (size > 0 && data[0] <= ' ') {
-        --size;
-        ++data;
-    }
-    while (size > 0 && data[size - 1] <= ' ') {
-        --size;
-    }
-    if (0 == size) {
-        return Wrote::kNone;
-    }
-    if (fReturnOnWrite) {
-        return Wrote::kChars;
-    }
-    int run = 0;
-    Word word = Word::kStart;
-    PunctuationState punctuation = Phrase::kNo == phrase ?
-            PunctuationState::kStart : PunctuationState::kSpace;
-    int start = 0;
-    int lastWrite = 0;
-    int lineFeeds = 0;
-    int lastPrintable = 0;
-    int lastSpace = -1;
-    char c = 0;
-    char last = 0;
-    bool embeddedIndirection = false;
-    bool embeddedSymbol = false;
-    bool hasLower = false;
-    bool hasUpper = false;
-    bool hasIndirection = false;
-    bool hasSymbol = false;
-    while (run < size) {
-        last = c;
-        c = data[run];
-        SkASSERT(' ' <= c || '\n' == c);
-        if (lineFeeds && ' ' < c) {
-            if (lastPrintable >= lastWrite) {
-                if (' ' == data[lastWrite]) {
-                    this->writeSpace();
-                    lastWrite++;
-                }
-                this->writeBlock(lastPrintable - lastWrite + 1, &data[lastWrite]);
-            }
-            if (lineFeeds > 1) {
-                this->lf(2);
-            }
-            this->lfcr(); // defer the indent until non-whitespace is seen
-            lastWrite = run;
-            lineFeeds = 0;
-        }
-        if (' ' < c) {
-            lastPrintable = run;
-        }
-        switch (c) {
-            case '\n':
-                ++lineFeeds;
-                wroteLineFeeds = true;
-            case ' ':
-                switch (word) {
-                    case Word::kStart:
-                        break;
-                    case Word::kUnderline:
-                    case Word::kCap:
-                    case Word::kFirst:
-                        if (!hasLower) {
-                            break;
-                        }
-                        lastWrite = this->lookupReference(punctuation, word, start, run,
-                                lastWrite, last, data);
-                        break;
-                    case Word::kMixed:
-                        if (hasUpper && hasLower && !hasSymbol && lastSpace > 0) {
-                            lastWrite = this->lookupMethod(punctuation, word, lastSpace, run,
-                                    lastWrite, data, hasIndirection);
-                        }
-                        break;
-                    default:
-                        SkASSERT(0);
-                }
-                punctuation = PunctuationState::kPeriod == punctuation ||
-                        (PunctuationState::kStart == punctuation && ' ' >= last) ?
-                        PunctuationState::kStart : PunctuationState::kSpace;
-                word = Word::kStart;
-                embeddedIndirection = false;
-                embeddedSymbol = false;
-                hasLower = false;
-                hasUpper = false;
-                hasIndirection = false;
-                hasSymbol = false;
-                lastSpace = run;
-                break;
-            case '.': case ',': case ';': case ':': case ')':
-                switch (word) {
-                    case Word::kStart:
-                        punctuation = PunctuationState::kDelimiter;
-                    case Word::kCap:
-                    case Word::kFirst:
-                    case Word::kUnderline:
-                    case Word::kMixed:
-                        if (PunctuationState::kDelimiter == punctuation ||
-                                PunctuationState::kPeriod == punctuation) {
-                            word = Word::kMixed;
-                        }
-                        punctuation = '.' == c ? PunctuationState::kPeriod :
-                                PunctuationState::kDelimiter;
-                        break;
-                    default:
-                        SkASSERT(0);
-                }
-                ('.' == c ? embeddedIndirection : embeddedSymbol) = true;
-                break;
-            case '>':
-                if ('-' == last) {
-                    embeddedIndirection = true;
-                    break;
-                }
-            case '\'': // possessive apostrophe isn't treated as delimiting punctation
-            case '\"': // quote is passed straight through
-            case '=':
-            case '!':  // assumed not to be punctuation, but a programming symbol
-            case '&': case '<': case '{': case '}': case '/': case '*': case '[': case ']':
-                word = Word::kMixed;
-                embeddedSymbol = true;
-                break;
-            case '(':
-                if (' ' == last) {
-                    punctuation = PunctuationState::kParen;
-                } else {
-                    word = Word::kMixed;
-                }
-                embeddedSymbol = true;
-                break;
-            case '_':
-                switch (word) {
-                    case Word::kStart:
-                        word = Word::kMixed;
-                        break;
-                    case Word::kCap:
-                    case Word::kFirst:
-                    case Word::kUnderline:
-                        word = Word::kUnderline;
-                        break;
-                    case Word::kMixed:
-                        break;
-                    default:
-                        SkASSERT(0);
-                }
-                hasSymbol |= embeddedSymbol;
-                break;
-            case '+':
-                // hackery to allow C++
-                SkASSERT('C' == last || '+' == last);  // FIXME: don't allow + outside of #Formula
-                break;
-            case 'A': case 'B': case 'C': case 'D': case 'E':
-            case 'F': case 'G': case 'H': case 'I': case 'J':
-            case 'K': case 'L': case 'M': case 'N': case 'O':
-            case 'P': case 'Q': case 'R': case 'S': case 'T':
-            case 'U': case 'V': case 'W': case 'X': case 'Y':
-            case 'Z':
-                switch (word) {
-                    case Word::kStart:
-                        word = PunctuationState::kStart == punctuation ? Word::kFirst : Word::kCap;
-                        start = run;
-                        break;
-                    case Word::kCap:
-                    case Word::kFirst:
-                        if (!isupper(last) && '~' != last) {
-                            word = Word::kMixed;
-                        }
-                        break;
-                    case Word::kUnderline:
-                        // some word in Xxx_XXX_Xxx can be all upper, but all can't: XXX_XXX
-                        if ('_' != last && !isupper(last)) {
-                            word = Word::kMixed;
-                        }
-                        break;
-                    case Word::kMixed:
-                        break;
-                    default:
-                        SkASSERT(0);
-                }
-                hasUpper = true;
-                if (PunctuationState::kPeriod == punctuation ||
-                        PunctuationState::kDelimiter == punctuation) {
-                    word = Word::kMixed;
-                }
-                hasIndirection |= embeddedIndirection;
-                hasSymbol |= embeddedSymbol;
-                break;
-            case 'a': case 'b': case 'c': case 'd': case 'e':
-            case 'f': case 'g': case 'h': case 'i': case 'j':
-            case 'k': case 'l': case 'm': case 'n': case 'o':
-            case 'p': case 'q': case 'r': case 's': case 't':
-            case 'u': case 'v': case 'w': case 'x': case 'y':
-            case 'z':
-            case '0': case '1': case '2': case '3': case '4':
-            case '5': case '6': case '7': case '8': case '9':
-            case '%':  // to do : ensure that preceding is a number
-            case '-':
-                switch (word) {
-                    case Word::kStart:
-                        word = Word::kMixed;
-                        break;
-                    case Word::kMixed:
-                    case Word::kCap:
-                    case Word::kFirst:
-                    case Word::kUnderline:
-                        break;
-                    default:
-                        SkASSERT(0);
-                }
-                hasLower = true;
-                punctuation = PunctuationState::kStart;
-                hasIndirection |= embeddedIndirection;
-                hasSymbol |= embeddedSymbol;
-                break;
-            case '~':
-                SkASSERT(Word::kStart == word);
-                word = PunctuationState::kStart == punctuation ? Word::kFirst : Word::kCap;
-                start = run;
-                hasUpper = true;
-                hasIndirection |= embeddedIndirection;
-                hasSymbol |= embeddedSymbol;
-                break;
-            default:
-                SkASSERT(0);
-        }
-        ++run;
-    }
-    if ((word == Word::kCap || word == Word::kFirst || word == Word::kUnderline) && hasLower) {
-        lastWrite = this->lookupReference(punctuation, word, start, run, lastWrite, last, data);
-    } else if (word == Word::kMixed && hasUpper && hasLower && !hasSymbol && lastSpace > 0) {
-        lastWrite = this->lookupMethod(punctuation, word, lastSpace, run, lastWrite, data,
-                hasIndirection && !hasSymbol);
-    }
-    if (run > lastWrite) {
-        if (' ' == data[lastWrite]) {
-            this->writeSpace();
-        }
-        this->writeBlock(run - lastWrite, &data[lastWrite]);
-    }
-    return wroteLineFeeds ? Wrote::kLF : Wrote::kChars;
-}
-
-static string paddedString(int num) {
-    auto padded = std::to_string(num);
-    padded.insert(0, 2U - std::min(string::size_type(2), padded.length()), '0');
-    return padded;
-}
-
-bool IncludeWriter::writeHeader(std::pair<const string, Definition>& include) {
-    std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
-    time_t tt = std::chrono::system_clock::to_time_t(now);
-    tm local_tm = *localtime(&tt);
-
-    // find end of copyright header
-    fChar = fStart;
-    this->skipWhiteSpace();
-    if (!this->skipExact(
-            "/*\n"
-            " * Copyright ")) {
-        return this->reportError<bool>("copyright mismatch 1");
-    }
-    const char* date = fChar;
-    this->skipToSpace();
-    string yearStr(date, fChar - date);
-    int year = stoi(yearStr);
-    if (year < 2005 || year > local_tm.tm_year + 1900) {
-        return this->reportError<bool>("copyright year out of range");
-    }
-    this->skipSpace();
-    const char android[] = "The Android Open Source Project";
-    const char google[] = "Google Inc.";
-    if (this->startsWith(android)) {
-        this->skipExact(android);
-    } else if (!this->skipExact(google)) {
-        return this->reportError<bool>("copyright mismatch 2");
-    }
-    if (!this->skipExact(
-            "\n"
-            " *\n"
-            " * Use of this source code is governed by a BSD-style license that can be\n"
-            " * found in the LICENSE file.\n"
-            " */\n"
-            "\n"
-            )) {
-        return this->reportError<bool>("copyright mismatch 2");
-    }
-    this->writeBlock(fChar - fStart, fStart);
-    this->lf(2);
-    this->writeString("/* Generated by tools/bookmaker from");
-    this->writeSpace();
-    string includeName = include.first;
-    std::replace(includeName.begin(), includeName.end(), '\\', '/');
-    this->writeString(includeName);
-    this->writeSpace();
-    this->writeString("and");
-    this->writeSpace();
-    string bmhName = fRootTopic->fFileName;
-    std::replace(bmhName.begin(), bmhName.end(), '\\', '/');
-    this->writeString(bmhName);
-    this->lfcr();
-    fIndent = 3;
-    string dateTimeStr = std::to_string(local_tm.tm_year + 1900) + "-"
-            + paddedString(local_tm.tm_mon + 1) + "-"
-            + paddedString(local_tm.tm_mday) + " "
-            + paddedString(local_tm.tm_hour) + ":"
-            + paddedString(local_tm.tm_min) + ":"
-            + paddedString(local_tm.tm_sec);
-    this->writeString("on");
-    this->writeSpace();
-    this->writeString(dateTimeStr);
-    this->writeString(". Additional documentation and examples can be found at:");
-    this->lfcr();
-    this->writeString("https://skia.org/user/api/");
-    size_t bmhPageStart = bmhName.rfind('/');
-    size_t bmhPageEnd = bmhName.rfind('.');
-    if (string::npos == bmhPageStart || string::npos == bmhPageEnd) {
-        return this->reportError<bool>("badly formed bmh page name");
-    }
-    ++bmhPageStart;
-    string bmhPage = bmhName.substr(bmhPageStart, bmhPageEnd - bmhPageStart);
-    this->writeString(bmhPage);
-    this->lf(2);
-    this->writeString("You may edit either file directly. Structural changes to public interfaces require");
-    this->lfcr();
-    this->writeString("editing both files. After editing");
-    this->writeSpace();
-    this->writeString(bmhName);
-    this->writeSpace();
-    this->writeString(", run:");
-    this->lfcr();
-    fIndent += 4;
-    this->writeString("bookmaker -b docs -i");
-    this->writeSpace();
-    this->writeString(includeName);
-    this->writeSpace();
-    this->writeString("-p");
-    this->lfcr();
-    fIndent -= 4;
-    this->writeString("to create an updated version of this file.");
-    this->lfcr();
-    fIndent = 1;
-    this->writeString("*/");
-    this->lf(2);
-    fIndent = 0;
-    if (this->startsWith("/* Generated by tools/bookmaker from")) {
-        this->skipToEndBracket("*/");
-        if (!this->skipExact("*/\n\n")) {
-            return this->reportError<bool>("malformed generated comment");
-        }
-    }
-    fStart = fChar;
-
-    return true;
-}
diff --git a/tools/bookmaker/includeWriter.h b/tools/bookmaker/includeWriter.h
deleted file mode 100644
index 6271ac7..0000000
--- a/tools/bookmaker/includeWriter.h
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef includeWriter_DEFINED
-#define includeWriter_DEFINED
-
-#include "includeParser.h"
-
-class IncludeWriter : public IncludeParser {
-public:
-    enum class Word {
-        kStart,
-        kCap,
-        kFirst,
-        kUnderline,
-        kMixed,
-    };
-
-    enum class Phrase {
-        kNo,
-        kYes,
-    };
-
-    enum class PunctuationState {
-        kStart,
-        kDelimiter,
-        kParen,     // treated as a delimiter unless following a space, and followed by word
-        kPeriod,
-        kSpace,
-    };
-
-    enum class RefType {
-        kUndefined,
-        kNormal,
-        kExternal,
-    };
-
-	enum class SkipFirstLine {
-		kNo,
-		kYes,
-	};
-
-    enum class Wrote {
-        kNone,
-        kLF,
-        kChars,
-    };
-
-    enum class MemberPass {
-        kCount,
-        kOut,
-    };
-
-    enum class ItemState {
-        kNone,
-        kName,
-        kValue,
-        kComment,
-    };
-
-    struct IterState {
-        IterState (list<Definition>::iterator tIter, list<Definition>::iterator tIterEnd)
-            : fDefIter(tIter)
-            , fDefEnd(tIterEnd) {
-        }
-        list<Definition>::iterator fDefIter;
-        list<Definition>::iterator fDefEnd;
-    };
-
-    struct ParentPair {
-        const Definition* fParent;
-        const ParentPair* fPrev;
-    };
-
-    struct Preprocessor {
-        Preprocessor() {
-            reset();
-        }
-
-        void reset() {
-            fDefinition = nullptr;
-            fStart = nullptr;
-            fEnd = nullptr;
-            fWord = false;
-        }
-
-        const Definition* fDefinition;
-        const char* fStart;
-        const char* fEnd;
-        bool fWord;
-    };
-
-    struct Item {
-        void reset() {
-            fName = "";
-            fValue = "";
-        }
-
-        string fName;
-        string fValue;
-    };
-
-    struct LastItem {
-        const char* fStart;
-        const char* fEnd;
-    };
-
-    struct ItemLength {
-        int fCurName;
-        int fCurValue;
-        int fLongestName;
-        int fLongestValue;
-    };
-
-    IncludeWriter() : IncludeParser() {
-        this->reset();
-    }
-
-    ~IncludeWriter() override {}
-
-    bool contentFree(int size, const char* data) const {
-        while (size > 0 && data[0] <= ' ') {
-            --size;
-            ++data;
-        }
-        while (size > 0 && data[size - 1] <= ' ') {
-            --size;
-        }
-        return 0 == size;
-    }
-
-    bool checkChildCommentLength(const Definition* parent, MarkType childType) const;
-    void checkEnumLengths(const Definition& child, string enumName, ItemLength* length) const;
-	void constOut(const Definition* memberStart, const Definition* bmhConst);
-    void constSizeMembers(const RootDefinition* root);
-    bool defineOut(const Definition& );
-    bool descriptionOut(const Definition* def, SkipFirstLine , Phrase );
-    void enumHeaderOut(RootDefinition* root, const Definition& child);
-    string enumMemberComment(const Definition* currentEnumItem, const Definition& child) const;
-    const Definition* enumMemberForComment(const Definition* currentEnumItem) const;
-    ItemState enumMemberName(const Definition& child,
-            const Definition* token, Item* , LastItem* , const Definition** enumItem);
-    void enumMemberOut(const Definition* currentEnumItem, const Definition& child,
-            const Item& , Preprocessor& );
-    void enumMembersOut(Definition& child);
-    bool enumPreprocessor(Definition* token, MemberPass pass,
-        vector<IterState>& iterStack, IterState** iterState, Preprocessor* );
-    void enumSizeItems(const Definition& child);
-    bool findEnumSubtopic(string undername, const Definition** ) const;
-    void firstBlock(int size, const char* data);
-    bool firstBlockTrim(int size, const char* data);
-	Definition* findMemberCommentBlock(const vector<Definition*>& bmhChildren, string name) const;
-    Definition* findMethod(string name, RootDefinition* ) const;
-
-    void indentDeferred(IndentKind kind) {
-        if (fIndentNext) {
-            this->indentIn(kind);
-            fIndentNext = false;
-        }
-    }
-
-    int lookupMethod(const PunctuationState punctuation, const Word word,
-            const int start, const int run, int lastWrite,
-            const char* data, bool hasIndirection);
-    int lookupReference(const PunctuationState punctuation, const Word word,
-            const int start, const int run, int lastWrite, const char last,
-            const char* data);
-    const Definition* matchMemberName(string matchName, const Definition& child) const;
-    void methodOut(Definition* method, const Definition& child);
-    bool populate(Definition* def, ParentPair* parentPair, RootDefinition* root);
-    bool populate(BmhParser& bmhParser);
-
-    void reset() override {
-        INHERITED::resetCommon();
-        fBmhParser = nullptr;
-        fDeferComment = nullptr;
-        fBmhMethod = nullptr;
-        fEnumDef = nullptr;
-        fMethodDef = nullptr;
-        fBmhConst = nullptr;
-        fConstDef = nullptr;
-        fLastDescription = nullptr;
-        fStartSetter = nullptr;
-        fBmhStructDef = nullptr;
-        fContinuation = nullptr;
-        fInStruct = false;
-        fWroteMethod = false;
-        fIndentNext = false;
-        fPendingMethod = false;
-        fFirstWrite = false;
-        fStructEnded = false;
-        fWritingIncludes = true;
-   }
-
-    string resolveAlias(const Definition* );
-    string resolveMethod(const char* start, const char* end, bool first);
-    string resolveRef(const char* start, const char* end, bool first, RefType* refType);
-    Wrote rewriteBlock(int size, const char* data, Phrase phrase);
-    void setStart(const char* start, const Definition * );
-    void setStartBack(const char* start, const Definition * );
-    Definition* structMemberOut(const Definition* memberStart, const Definition& child);
-    void structOut(const Definition* root, const Definition& child,
-            const char* commentStart, const char* commentEnd);
-    void structSizeMembers(const Definition& child);
-    bool writeHeader(std::pair<const string, Definition>& );
-private:
-    vector<const Definition* > fICSStack;
-    BmhParser* fBmhParser;
-    Definition* fDeferComment;
-    const Definition* fBmhMethod;
-    const Definition* fEnumDef;
-    const Definition* fMethodDef;
-    const Definition* fBmhConst;
-    const Definition* fConstDef;
-    const Definition* fLastDescription;
-    const Definition* fStartSetter;
-    Definition* fBmhStructDef;
-    const char* fContinuation;  // used to construct paren-qualified method name
-    int fAnonymousEnumCount;
-    int fEnumItemValueTab;
-    int fEnumItemCommentTab;
-    int fStructMemberTab;
-    int fStructValueTab;
-    int fStructCommentTab;
-    int fStructMemberLength;
-    int fConstValueTab;
-    int fConstCommentTab;
-    int fConstLength;
-    bool fInStruct;  // set if struct is inside class
-    bool fWroteMethod;
-    bool fIndentNext;
-    bool fPendingMethod;
-    bool fFirstWrite;  // set to write file information just after includes
-    bool fStructEnded;  // allow resetting indent after struct is complete
-
-    typedef IncludeParser INHERITED;
-};
-
-#endif
diff --git a/tools/bookmaker/mdOut.cpp b/tools/bookmaker/mdOut.cpp
deleted file mode 100644
index 366135f..0000000
--- a/tools/bookmaker/mdOut.cpp
+++ /dev/null
@@ -1,2435 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "bmhParser.h"
-#include "includeParser.h"
-#include "mdOut.h"
-
-#include "SkOSFile.h"
-#include "SkOSPath.h"
-#include <map>
-
-class SubtopicKeys {
-public:
-    static constexpr const char* kClasses = "Classes";
-    static constexpr const char* kConstants = "Constants";
-    static constexpr const char* kConstructors = "Constructors";
-    static constexpr const char* kDefines = "Defines";
-    static constexpr const char* kMemberFunctions = "Member_Functions";
-    static constexpr const char* kMembers = "Members";
-    static constexpr const char* kOperators = "Operators";
-    static constexpr const char* kOverview = "Overview";
-    static constexpr const char* kRelatedFunctions = "Related_Functions";
-    static constexpr const char* kStructs = "Structs";
-    static constexpr const char* kTypedefs = "Typedefs";
-
-    static const char* kGeneratedSubtopics[];
-};
-
-const char* SubtopicKeys::kGeneratedSubtopics[] = {
-    kConstants, kDefines, kTypedefs, kMembers, kClasses, kStructs, kConstructors,
-    kOperators, kMemberFunctions, kRelatedFunctions
-};
-
-const char* kConstTableStyle =
-"<style>"                                                                                      "\n"
-    ".td_const td, th { border: 2px solid #dddddd; text-align: left; padding: 8px; }"          "\n"
-    ".tr_const tr:nth-child(even) { background-color: #f0f0f0; }"                              "\n"
-    ".td2_const td:first-child + td { text-align: center; }"                                   "\n"
-"</style>"                                                                                     "\n";
-
-const char* kTableDeclaration = "<table style='border-collapse: collapse; width: 62.5em'>";
-
-#define kTD_Base         "border: 2px solid #dddddd; padding: 8px; "
-#define kTH_Left         "<th style='text-align: left; "   kTD_Base "'>"
-#define kTH_Center       "<th style='text-align: center; " kTD_Base "'>"
-
-string kTD_Left    = "    <td style='text-align: left; "   kTD_Base "'>";
-string kTD_Center  = "    <td style='text-align: center; " kTD_Base "'>";
-string kTR_Dark    =   "  <tr style='background-color: #f0f0f0; '>";
-
-const char* kAllConstTableHeader =  "  <tr>" kTH_Left   "Const</th>"                            "\n"
-                                             kTH_Center "Value</th>"                            "\n"
-                                             kTH_Left   "Description</th>" "</tr>";
-const char* kSubConstTableHeader =  "  <tr>" kTH_Left   "Const</th>"                            "\n"
-                                             kTH_Center "Value</th>"                            "\n"
-                                             kTH_Left   "Details</th>"                          "\n"
-                                             kTH_Left   "Description</th>" "</tr>";
-const char* kAllMemberTableHeader = "  <tr>" kTH_Left   "Type</th>"                             "\n"
-                                             kTH_Left   "Member</th>"                           "\n"
-                                             kTH_Left   "Description</th>" "</tr>";
-const char* kSubMemberTableHeader = "  <tr>" kTH_Left   "Type</th>"                             "\n"
-                                             kTH_Left   "Member</th>"                           "\n"
-                                             kTH_Left   "Details</th>"                          "\n"
-                                             kTH_Left   "Description</th>" "</tr>";
-const char* kTopicsTableHeader    = "  <tr>" kTH_Left   "Topic</th>"                            "\n"
-                                             kTH_Left   "Description</th>" "</tr>";
-
-string MdOut::anchorDef(string str, string name) {
-    if (fValidate) {
-        string htmlName = ParserCommon::HtmlFileName(fFileName);
-        vector<AnchorDef>& allDefs = fAllAnchorDefs[htmlName];
-        if (!std::any_of(allDefs.begin(), allDefs.end(),
-                [str](AnchorDef compare) { return compare.fDef == str; } )) {
-            MarkType markType = fLastDef->fMarkType;
-            if (MarkType::kMethod == markType && fLastDef->fClone) {
-                SkASSERT(0);  // incomplete
-            }
-            allDefs.push_back( { str, markType } );
-        }
-    }
-    return "<a name='" + str + "'>" + name + "</a>";
-}
-
-string MdOut::anchorRef(string ref, string name) {
-    if (fValidate) {
-        string htmlName;
-        size_t hashIndex = ref.find('#');
-        if (string::npos != hashIndex && "https://" != ref.substr(0, 8)) {
-            if (0 == hashIndex) {
-                htmlName = ParserCommon::HtmlFileName(fFileName);
-            } else {
-                htmlName = ref.substr(0, hashIndex);
-            }
-            vector<string>& allRefs = fAllAnchorRefs[htmlName];
-            string refPart = ref.substr(hashIndex + 1);
-            if (allRefs.end() == std::find(allRefs.begin(), allRefs.end(), refPart)) {
-                allRefs.push_back(refPart);
-            }
-        }
-    }
-    SkASSERT(string::npos != ref.find('#') || string::npos != ref.find("https://"));
-    return "<a href='" + ref + "'>" + name + "</a>";
-}
-
-string MdOut::anchorLocalRef(string ref, string name) {
-    return this->anchorRef("#" + ref, name);
-}
-
-string MdOut::tableDataCodeRef(string ref, string name) {
-    return kTD_Left + this->anchorRef(ref, "<code>" + name + "</code>") + "</td>";
-}
-
-string MdOut::tableDataCodeLocalRef(string ref, string name) {
-    return this->tableDataCodeRef("#" + ref, name);
-}
-
-string MdOut::tableDataCodeLocalRef(string name) {
-    return this->tableDataCodeLocalRef(name, name);
-}
-
-string MdOut::tableDataCodeRef(const Definition* ref) {
-    return this->tableDataCodeLocalRef(ref->fFiddle, ref->fName);
-}
-
-string MdOut::tableDataCodeDef(string def, string name) {
-    return kTD_Left + this->anchorDef(def, "<code>" + name + "</code>") + "</td>";
-}
-
-string MdOut::tableDataCodeDef(const Definition* def) {
-    return this->tableDataCodeDef(def->fFiddle, def->fName);
-}
-
-static string table_data_const(const Definition* def, const char** textStartPtr) {
-    TextParser parser(def);
-    SkAssertResult(parser.skipToEndBracket('\n'));
-    string constant = string(def->fContentStart, (int) (parser.fChar - def->fContentStart));
-    if (textStartPtr) {
-        *textStartPtr = parser.fChar;
-    }
-    return kTD_Center + constant + "</td>";
-}
-
-static string out_table_data_description_start() {
-    return kTD_Left;
-}
-
-static string out_table_data_description(string str) {
-    return kTD_Left + str + "</td>";
-}
-
-static string out_table_data_description(const Definition* def) {
-    return out_table_data_description(string(def->fContentStart,
-            (int) (def->fContentEnd - def->fContentStart)));
-}
-
-static string out_table_data_details(string details) {
-    return kTD_Left + details + "</td>";
-}
-
-#undef kConstTDBase
-#undef kTH_Center
-
-static string preformat(string orig) {
-    string result;
-    for (auto c : orig) {
-        if ('<' == c) {
-          result += "&lt;";
-        } else if ('>' == c) {
-          result += "&gt;";
-        } else {
-            result += c;
-        }
-    }
-    return result;
-}
-
-// from https://stackoverflow.com/questions/3418231/replace-part-of-a-string-with-another-string
-void replace_all(string& str, const string& from, const string& to) {
-    SkASSERT(!from.empty());
-    size_t start_pos = 0;
-    while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
-        str.replace(start_pos, from.length(), to);
-        start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
-    }
-}
-
-// detail strings are preceded by an example comment to check readability
-void MdOut::addPopulators() {
-    auto populator = [this](string key, string singular, string plural, string oneLiner,
-            string details) -> void {
-        fPopulators[key].fSingular = singular;
-        fPopulators[key].fPlural = plural;
-        fPopulators[key].fOneLiner = oneLiner;
-        fPopulators[key].fDetails = details;
-    };
-    populator(SubtopicKeys::kClasses, "Class", "Class Declarations",
-            "embedded class members",
-            /* SkImageInfo */ "uses <code>class</code> to declare the public data structures"
-                              " and interfaces.");
-    populator(SubtopicKeys::kConstants, "Constant", "Constants",
-            "enum and enum class, and their const values",
-            /* SkImageInfo */ "defines related constants are using <code>enum</code>,"
-                              " <code>enum class</code>,  <code>#define</code>,"
-                              " <code>const</code>, and <code>constexpr</code>.");
-    populator(SubtopicKeys::kConstructors, "Constructor", "Constructors",
-            "functions that construct",
-            /* SkImageInfo */ "can be constructed or initialized by these functions,"
-                              " including <code>class</code> constructors.");
-    populator(SubtopicKeys::kDefines, "Define", "Defines",
-            "preprocessor definitions of functions, values",
-            /* SkImageInfo */ "uses preprocessor definitions to inline code and constants,"
-                              " and to abstract platform-specific functionality.");
-    populator(SubtopicKeys::kMemberFunctions, "Member Function", "Member Functions",
-            "static and local functions",
-            /* SkImageInfo */ "uses member functions to read and modify structure properties.");
-    populator(SubtopicKeys::kMembers, "Member", "Members",
-            "member values",
-            /* SkImageInfo */ "contains members that may be read and written directly without using"
-                              " a member function.");
-    populator(SubtopicKeys::kOperators, "Operator", "Operators",
-            "operator overloading functions",
-            /* SkImageInfo */ "defines member functions with arithmetic equivalents.");
-    populator(SubtopicKeys::kRelatedFunctions, "Related Function", "Related Functions",
-            "similar functions grouped together",
-            /* SkImageInfo */ "defines related functions that share a topic.");
-    populator(SubtopicKeys::kStructs, "Struct", "Struct Declarations",
-            "embedded struct members",
-            /* SkImageInfo */ "uses <code>struct</code> to declare the public data"
-                              " structures and interfaces.");
-    populator(SubtopicKeys::kTypedefs, "Typedef", "Typedef Declarations",
-            "types defined in terms of other types",
-            /* SkImageInfo */ "uses <code>typedef</code> to define a data type.");
-}
-
-Definition* MdOut::checkParentsForMatch(Definition* test, string ref) const {
-    bool isSubtopic = MarkType::kSubtopic == test->fMarkType
-            || MarkType::kTopic == test->fMarkType;
-    do {
-        if (!test->isRoot()) {
-            continue;
-        }
-        bool localTopic = MarkType::kSubtopic == test->fMarkType
-                || MarkType::kTopic == test->fMarkType;
-        if (localTopic != isSubtopic) {
-            continue;
-        }
-        string prefix(isSubtopic ? "_" : "::");
-        RootDefinition* root = test->asRoot();
-        string prefixed = root->fName + prefix + ref;
-        if (Definition* def = root->find(prefixed, RootDefinition::AllowParens::kYes)) {
-            return def;
-        }
-    } while ((test = test->fParent));
-    return nullptr;
-}
-
-struct BraceState {
-    BraceState(RootDefinition* root, string name, const char* ch, KeyWord last, KeyWord keyWord,
-            int count)
-        : fRoot(root)
-        , fName(name)
-        , fChar(ch)
-        , fLastKey(last)
-        , fKeyWord(keyWord)
-        , fBraceCount(count) {
-    }
-
-    RootDefinition* fRoot;
-    string fName;
-    const char* fChar;
-    KeyWord fLastKey;
-    KeyWord fKeyWord;
-    int fBraceCount;
-};
-
-bool MdOut::DefinedState::hasWordSpace(string wordSpace) const {
-    if (!fNames->fRefMap.size()) {
-        return false;
-    }
-    for (const NameMap* names = fNames; names; names = names->fParent) {
-        if (names->fRefMap.end() != names->fRefMap.find(wordSpace)) {
-            return true;
-        }
-    }
-    return false;
-}
-
-bool MdOut::DefinedState::phraseContinues(string phrase, string* priorWord,
-        string* priorLink) const {
-    for (const NameMap* names = fNames; names; names = names->fParent) {
-        if (names->fRefMap.end() != names->fRefMap.find(phrase + ' ')) {
-            *priorWord = phrase;
-            return true;
-        }
-        if (names->fRefMap.end() != names->fRefMap.find(phrase)) {
-            *priorWord = phrase;
-            auto linkIter = names->fLinkMap.find(phrase);
-            *priorLink = names->fLinkMap.end() == linkIter ? "" : linkIter->second;
-            return true;
-        }
-    }
-    return false;
-}
-
-void MdOut::DefinedState::setLink() {
-    fLink = "";
-    fPriorDef = nullptr;
-    // TODO: operators have complicated parsing possibilities; handle the easiest for now
-    // TODO: constructors also have complicated parsing possibilities; handle the easiest
-    bool isOperator = "operator" == fPriorWord;
-    if (((fRoot && fRoot->isStructOrClass() && fRoot->fName == fPriorWord) || isOperator)
-            && '(' == fSeparator.back()) {
-        SkASSERT(fSubtopic);
-        TextParser parser(fSubtopic->fFileName, fSeparatorStart, fRefEnd, fSubtopic->fLineCount);
-        parser.skipToEndBracket('(');
-        const char* parenStart = parser.fChar;
-        parser.skipToBalancedEndBracket('(', ')');
-        (void) parser.skipExact(" const");
-        string methodName = fPriorWord + fSeparator
-                + string(parenStart + 1, parser.fChar - parenStart - 1);
-        string testLink;
-        if (this->findLink(methodName, &testLink, false)) {
-            // consume only if we find it
-            if (isOperator) {
-                fPriorWord += fSeparator.substr(0, fSeparator.length() - 1);  // strip paren
-                fPriorSeparator = "(";
-            }
-            fWord = "";
-            fPriorLink = testLink;
-            fEnd = parenStart + 1;
-            return;
-        }
-    }
-    // look to see if text following ref is method qualifier
-    else if ((Resolvable::kYes == fResolvable || Resolvable::kClone == fResolvable)
-            && "(" == fSeparator && "" != fPriorLink) {
-        TextParser parser(fLastDef->fFileName, fSeparatorStart, fRefEnd, fLastDef->fLineCount);
-        parser.skipToBalancedEndBracket('(', ')');
-        string fullMethod = fPriorWord + string(parser.fStart, parser.fChar - parser.fStart);
-        string trimmed = trim_inline_spaces(fullMethod);
-        string testLink;
-        if (findLink(trimmed, &testLink, false)) {
-            fMethodName = fullMethod;
-            fWord = trimmed;
-            fLink = testLink;
-            fEnd = parser.fChar;
-            this->backup();
-            return;
-        }
-    }
-    if ("." == fSeparator || "->" == fSeparator || "()." == fSeparator || "()->" == fSeparator) {
-        bool foundField = fWord.length() >= 2 && (('f' == fWord[0] && isupper(fWord[1]))
-                || "()" == fWord.substr(fWord.length() - 2)
-                || (fEnd + 2 <= fRefEnd && "()" == string(fEnd, 2)));
-        if (foundField) {
-            if (fMethod && fNames->fRefMap.end() != fNames->fRefMap.find(fPriorWord)) {
-        // find prior fWord's type in fMethod
-                TextParser parser(fMethod);
-                SkAssertResult(parser.containsWord(fPriorWord.c_str(), parser.fEnd,
-                        &parser.fChar));
-        // look up class or struct; trival lookup only class/struct [& * const]
-                while (parser.back(" ") || parser.back("&") || parser.back("*")
-                        || parser.back("const"))
-                    ;
-                const char* structEnd = parser.fChar;
-                parser.backupWord();
-                if (structEnd != parser.fChar) {
-                    string structName(parser.fChar, structEnd - parser.fChar);
-                    if ("SkVector" == structName) {
-                        // TODO: populate global refmap with typedefs as well as structs
-                        structName = "SkPoint";
-                    } else if ("SkIVector" == structName) {
-                        structName = "SkIPoint";
-                    }
-                    structName += "::" + fWord;
-        // look for fWord as member of class or struct
-                    auto defIter = fGlobals->fRefMap.find(structName);
-                    if (fGlobals->fRefMap.end() == defIter) {
-                        structName += "()";
-                        defIter = fGlobals->fRefMap.find(structName);
-                    }
-                    if (fGlobals->fRefMap.end() != defIter) {
-                        // example: dstInfo.width()
-                        auto structIter = fGlobals->fLinkMap.find(structName);
-                        SkASSERT(fGlobals->fLinkMap.end() != structIter);
-                        fLink = structIter->second;
-                        fPriorDef = defIter->second;
-                        return;
-                    } else {
-                        SkDebugf("probably missing struct or class member in bmh: ");
-                        SkDebugf("%s\n", structName.c_str());
-                    }
-                }
-            }
-            auto& parentRefMap = fNames->fParent->fRefMap;
-            auto priorIter = parentRefMap.find(fPriorWord);
-            if (parentRefMap.end() == priorIter) {
-                priorIter = parentRefMap.find(fPriorWord + "()");
-            }
-            if (parentRefMap.end() != priorIter) {
-                Definition* priorDef = priorIter->second;
-                if (priorDef) {
-                    TextParser parser(priorDef->fFileName, priorDef->fStart,
-                            priorDef->fContentStart, priorDef->fLineCount);
-                    parser.skipExact("#Method ");
-                    parser.skipSpace();
-                    parser.skipExact("const ");  // optional
-                    parser.skipSpace();
-                    const char* start = parser.fChar;
-                    parser.skipToNonAlphaNum();
-                    string structName(start, parser.fChar - start);
-                    structName += "::" + fWord;
-                    auto defIter = fGlobals->fRefMap.find(structName);
-                    if (fGlobals->fRefMap.end() != defIter) {
-                        // example: imageInfo().width()
-                        auto globalIter = fGlobals->fLinkMap.find(structName);
-                        SkASSERT(fGlobals->fLinkMap.end() != globalIter);
-                        fLink = globalIter->second;
-                        fPriorDef = defIter->second;
-                        return;
-                    }
-                }
-            }
-        } else {
-            string fullRef = fPriorWord + fSeparator + fWord;
-            if (this->findLink(fullRef, &fLink, false)) {
-                return;
-            }
-            if (Resolvable::kCode != fResolvable) {
-                SkDebugf("probably missing () after function:");
-                const char* debugStart = fEnd - 20 < fRefStart ? fRefStart : fEnd - 20;
-                const char* debugEnd = fEnd + 10 > fRefEnd ? fRefEnd : fEnd + 10;
-                SkDebugf("%.*s\n", debugEnd - debugStart, debugStart);
-                SkDebugf(""); // convenient place to set a breakpoint
-            }
-        }
-    }
-    // example: SkCanvas::restoreToCount
-    if ("::" == fSeparator) {
-        string fullRef = fPriorWord + "::" + fWord;
-        if (this->findLink(fullRef, &fLink, fAddParens)) {
-            return;
-        }
-    }
-    // look in parent fNames and above for match
-    if (fNames) {
-        if (this->findLink(fWord, &fLink, (Resolvable::kClone == fResolvable && fAddParens)
-                || (Resolvable::kCode == fResolvable && '(' == fEnd[0]))) {
-            return;
-        }
-    }
-    // example : sqrt as in "sqrt(x * x + y * y)"
-    // example : erase in seeAlso
-    if (Resolvable::kClone == fResolvable || (fEnd + 1 < fRefEnd && '(' == fEnd[0])) {
-        if ((fAddParens || '~' == fWord.front()) && this->findLink(fWord + "()", &fLink, false)) {
-            return;
-        }
-    }
-    // example: Color_Type
-    if (this->findLink(fWord, &fLink, fBmhParser->fAliasMap)) {
-        return;
-    }
-    if (Resolvable::kInclude != fResolvable && string::npos != fWord.find('_')) {
-        // example: Blend_Mode
-        if (this->findLink(fWord, &fLink, fBmhParser->fTopicMap)) {
-            return;
-        }
-        if (fSubtopic) {
-            // example: Fake_Bold
-            if (fSubtopic->fName == fWord) {
-                fLink = '#' + fSubtopic->fFiddle;
-                fPriorDef = fSubtopic;
-                return;
-            }
-            const Definition* rootTopic = fSubtopic->subtopicParent();
-            if (rootTopic) {
-                if (rootTopic->fFiddle == fWord) {
-                    fLink = '#' + rootTopic->fFiddle;
-                    fPriorDef = rootTopic;
-                    return;
-                }
-                string globName = rootTopic->fFiddle + '_' + fWord;
-                if (this->findLink(globName, &fLink, fBmhParser->fTopicMap)) {
-                    return;
-                }
-            }
-        }
-        if (fRoot) {
-            string test = fRoot->fName + "::" + fWord;
-            auto rootIter = fRoot->fLeaves.find(test);
-            // example: restoreToCount in subtopic State_Stack
-            if (fRoot->fLeaves.end() != rootIter) {
-                fLink = '#' + rootIter->second.fFiddle;
-                fPriorDef = &rootIter->second;
-                return;
-            }
-        }
-    }
-    if (isupper(fWord[0]) && string::npos != fWord.find('_')) {
-        const Definition* topical = fSubtopic;
-        do {
-            string subtopic = topical->fName + '_' + fWord;
-            // example: Stroke_Width
-            if (this->findLink(subtopic, &fLink, fBmhParser->fTopicMap)) {
-                return;
-            }
-        } while ((topical = topical->topicParent()));
-    }
-    // treat hex constants as known words
-    if (fSeparator.size() > 0 && '0' == fSeparator.back() && 'x' == fWord[0]) {
-        bool allHex = true;
-        for (size_t index = 1; index < fWord.size(); ++index) {
-            char c = fWord[index];
-            if (('0' > c || '9' < c) && ('A' > c || 'F' < c)) {
-                allHex = false;
-                break;
-            }
-        }
-        if (allHex) {
-            return;
-        }
-    }
-    // treat floating constants as known words
-    if ("e" == fWord) {
-        if (std::all_of(fSeparator.begin(), fSeparator.end(), [](char c) {
-            return isdigit(c) || '.' == c || '-' == c || ' ' >= c;
-        })) {
-            return;
-        }
-    }
-    // stop short of parsing example; just look to see if it contains fWord in description
-    if (fLastDef && MarkType::kDescription == fLastDef->fMarkType) {
-        Definition* example = fLastDef->fParent;
-        if (MarkType::kExample == example->fMarkType) {
-            // example text is blocked by last child before std out, if it exists
-            const char* exStart = example->fChildren.back()->fContentEnd;
-            const char* exEnd = example->fContentEnd;
-            if (MarkType::kStdOut == example->fChildren.back()->fMarkType) {
-                exStart = example->fChildren[example->fChildren.size() - 2]->fContentEnd;
-                exEnd = example->fChildren.back()->fContentStart;
-            }
-            // maybe need a general function that searches block text excluding children
-            TextParser exParse(example->fFileName, exStart, exEnd, example->fLineCount);
-            if (exParse.containsWord(fWord.c_str(), exParse.fEnd, nullptr)) {
-                return;
-            }
-        }
-    }
-    // example: (x1, y1) after arcTo(SkScalar x1, ...
-    if (Resolvable::kYes == fResolvable && "" != fSeparator
-            && ('(' == fSeparator.back() || ',' == fSeparator[0])
-            && string::npos != fMethodName.find(fWord)) {
-        return;
-    }
-    // example: <sup>  (skip html)
-    if (Resolvable::kYes == fResolvable && fEnd + 1 < fRefEnd && '>' == fEnd[0] && "" != fSeparator
-            && ('<' == fSeparator.back() || (fSeparator.size() >= 2
-            && "</" == fSeparator.substr(fSeparator.size() - 2)))) {
-        return;
-    }
-    bool paramName = islower(fWord[0]) && (Resolvable::kCode == fResolvable
-            || Resolvable::kClone == fResolvable);
-    // TODO: can probably resolve formulae, but need a way for formula to define new reference
-    // for example: Given: #Formula # Sa ## as source Alpha,
-    // for example: where #Formula # m = Da > 0 ? Dc / Da : 0 ##;
-    if (!fInProgress && Resolvable::kSimple != fResolvable
-            && !paramName && Resolvable::kFormula != fResolvable) {
-        // example: Coons as in "Coons patch"
-        bool withSpace = fEnd + 1 < fRefEnd && ' ' == fEnd[0]
-                && fGlobals->fRefMap.end() != fGlobals->fRefMap.find(fWord + ' ');
-        if (!withSpace && (Resolvable::kInclude == fResolvable ? !fInMatrix :
-                '"' != fPriorSeparator.back() || '"' != fSeparator.back())) {
-            SkDebugf("word %s not found\n", fWord.c_str());
-            fBmhParser->fGlobalNames.fRefMap[fWord] = nullptr;
-        }
-    }
-}
-
-
-string MdOut::addReferences(const char* refStart, const char* refEnd, Resolvable resolvable) {
-    DefinedState s(*this, refStart, refEnd, resolvable);
-    string result;
-    const char* start = refStart;
-    do {
-        s.fSeparatorStart = start;
-        start = s.skipWhiteSpace();
-        s.skipParens();
-        string separator = s.nextSeparator(start);
-        if (fDebugWriteCodeBlock) {
-            SkDebugf("%s", separator.c_str());
-        }
-        result += separator;
-        if (s.findEnd(start)) {
-            break;
-        }
-        s.fWord = string(start, s.fEnd - start);
-        if ("TODO" == s.fWord) {
-            while('\n' != *s.fEnd++)
-                ;
-            start = s.fEnd;
-            continue;
-        }
-        s.setLower();
-        if (s.setPriorSpaceWord(&start)) {
-            continue;
-        }
-        s.setLink();
-        string link = "" == s.fPriorLink ? s.fPriorWord :
-                this->anchorRef(s.fPriorLink, s.fPriorWord);
-        if (fDebugWriteCodeBlock) {
-            SkDebugf("%s", link.c_str());
-        }
-        result += link;
-        start = s.nextWord();
-    } while (true);
-    string finalLink = "" == s.fPriorLink ? s.fPriorWord :
-            this->anchorRef(s.fPriorLink, s.fPriorWord);
-    if (fDebugWriteCodeBlock) {
-        SkDebugf("%s", finalLink.c_str());
-    }
-    result += finalLink;
-    if (fDebugWriteCodeBlock) {
-        SkDebugf("%s", s.fPriorSeparator.c_str());
-    }
-    result += s.fPriorSeparator;
-    return result;
-}
-
-bool MdOut::buildReferences(const char* docDir, const char* mdFileOrPath) {
-    if (!sk_isdir(mdFileOrPath)) {
-        SkDebugf("must pass directory %s\n", mdFileOrPath);
-        SkDebugf("pass -i SkXXX.h to build references for a single include\n");
-        return false;
-    }
-    fInProgress = true;
-    SkOSFile::Iter it(docDir, ".bmh");
-    for (SkString file; it.next(&file); ) {
-        if (!fIncludeParser.references(file)) {
-            continue;
-        }
-        SkString p = SkOSPath::Join(docDir, file.c_str());
-        if (!this->buildRefFromFile(p.c_str(), mdFileOrPath)) {
-            SkDebugf("failed to parse %s\n", p.c_str());
-            return false;
-        }
-    }
-    return true;
-}
-
-bool MdOut::buildStatus(const char* statusFile, const char* outDir) {
-    StatusIter iter(statusFile, ".bmh", StatusFilter::kInProgress);
-    StatusFilter filter;
-    for (string file; iter.next(&file, &filter); ) {
-        SkString p = SkOSPath::Join(iter.baseDir().c_str(), file.c_str());
-        const char* hunk = p.c_str();
-        fInProgress = StatusFilter::kInProgress == filter;
-        if (!this->buildRefFromFile(hunk, outDir)) {
-            SkDebugf("failed to parse %s\n", hunk);
-            return false;
-        }
-    }
-    return true;
-}
-
-bool MdOut::buildRefFromFile(const char* name, const char* outDir) {
-    if (!SkStrEndsWith(name, ".bmh")) {
-        return true;
-    }
-    if (SkStrEndsWith(name, "markup.bmh")) {  // don't look inside this for now
-        return true;
-    }
-    if (SkStrEndsWith(name, "illustrations.bmh")) {  // don't look inside this for now
-        return true;
-    }
-    if (SkStrEndsWith(name, "undocumented.bmh")) {  // don't look inside this for now
-        return true;
-    }
-    fFileName = string(name);
-    string filename(name);
-    if (filename.substr(filename.length() - 4) == ".bmh") {
-        filename = filename.substr(0, filename.length() - 4);
-    }
-    size_t start = filename.length();
-    while (start > 0 && (isalnum(filename[start - 1]) || '_' == filename[start - 1])) {
-        --start;
-    }
-    string match = filename.substr(start);
-    string header = match;
-    filename = match + ".md";
-    match += ".bmh";
-    fOut = nullptr;
-    string fullName;
-
-    vector<string> keys;
-    keys.reserve(fBmhParser.fTopicMap.size());
-    for (const auto& it : fBmhParser.fTopicMap) {
-        keys.push_back(it.first);
-    }
-    std::sort(keys.begin(), keys.end());
-    for (auto key : keys) {
-        string s(key);
-        auto topicDef = fBmhParser.fTopicMap.at(s);
-        if (topicDef->fParent) {
-            continue;
-        }
-        if (string::npos == topicDef->fFileName.rfind(match)) {
-            continue;
-        }
-        if (!fOut) {
-            fullName = outDir;
-            if ('/' != fullName.back()) {
-                fullName += '/';
-            }
-            fullName += filename;
-            fOut = fopen(filename.c_str(), "wb");
-            if (!fOut) {
-                SkDebugf("could not open output file %s\n", fullName.c_str());
-                return false;
-            }
-            if (false) {    // try inlining the style
-                FPRINTF("%s", kConstTableStyle);
-            }
-            size_t underscorePos = header.find('_');
-            if (string::npos != underscorePos) {
-                header.replace(underscorePos, 1, " ");
-            }
-            SkASSERT(string::npos == header.find('_'));
-            this->writeString(header);
-            this->lfAlways(1);
-            this->writeString("===");
-            this->lfAlways(1);
-        }
-        const Definition* prior = nullptr;
-        this->markTypeOut(topicDef, &prior);
-    }
-    if (fOut) {
-        this->writePending();
-        fclose(fOut);
-        fflush(fOut);
-        if (ParserCommon::WrittenFileDiffers(fullName, filename)) {
-            ParserCommon::CopyToFile(fullName, filename);
-            SkDebugf("wrote %s\n", fullName.c_str());
-        } else {
-            remove(filename.c_str());
-        }
-        fOut = nullptr;
-    }
-    return !fAddRefFailed;
-}
-
-static bool contains_referenced_child(const Definition* found, const vector<string>& refs) {
-    for (auto child : found->fChildren) {
-        if (refs.end() != std::find_if(refs.begin(), refs.end(),
-                    [child](string def) { return child->fName == def; } )) {
-            return true;
-        }
-        if (contains_referenced_child(child, refs)) {
-            return true;
-        }
-    }
-    return false;
-}
-
-void MdOut::checkAnchors() {
-    int missing = 0;
-    for (auto bmhFile : fAllAnchorRefs) {
-        auto defIter = fAllAnchorDefs.find(bmhFile.first);
-        SkASSERT(fAllAnchorDefs.end() != defIter);
-        vector<AnchorDef>& allDefs = defIter->second;
-        std::sort(allDefs.begin(), allDefs.end(),
-                [](const AnchorDef& a, const AnchorDef& b) { return a.fDef < b.fDef; } );
-        std::sort(bmhFile.second.begin(), bmhFile.second.end());
-        auto allDefsIter = allDefs.begin();
-        auto allRefsIter = bmhFile.second.begin();
-        for (;;) {
-            bool allDefsEnded = allDefsIter == allDefs.end();
-            bool allRefsEnded = allRefsIter == bmhFile.second.end();
-            if (allDefsEnded && allRefsEnded) {
-                break;
-            }
-            if (allRefsEnded || (!allDefsEnded && allDefsIter->fDef < *allRefsIter)) {
-                if (MarkType::kParam != allDefsIter->fMarkType) {
-                    // If undocumented but parent or child is referred to: good enough for now
-                    bool goodEnough = false;
-                    if ("undocumented" == defIter->first) {
-                        auto iter = fBmhParser.fTopicMap.find(allDefsIter->fDef);
-                        if (fBmhParser.fTopicMap.end() != iter) {
-                            const Definition* found = iter->second;
-                            if (string::npos != found->fFileName.find("undocumented")) {
-                                const Definition* parent = found;
-                                while ((parent = parent->fParent)) {
-                                    if (bmhFile.second.end() != std::find_if(bmhFile.second.begin(),
-                                            bmhFile.second.end(),
-                                            [parent](string def) {
-                                            return parent->fName == def; } )) {
-                                        goodEnough = true;
-                                        break;
-                                    }
-                                }
-                                if (!goodEnough) {
-                                    goodEnough = contains_referenced_child(found, bmhFile.second);
-                                }
-                            }
-                        }
-                    }
-                    if (!goodEnough) {
-                        SkDebugf("missing ref %s %s\n", defIter->first.c_str(),
-                                allDefsIter->fDef.c_str());
-                        missing++;
-                    }
-                }
-                allDefsIter++;
-            } else if (allDefsEnded || (!allRefsEnded && allDefsIter->fDef > *allRefsIter)) {
-                if (fBmhParser.fExternals.end() == std::find_if(fBmhParser.fExternals.begin(),
-                        fBmhParser.fExternals.end(), [allRefsIter](const RootDefinition& root) {
-                        return *allRefsIter != root.fName; } )) {
-                    SkDebugf("missing def %s %s\n", bmhFile.first.c_str(), allRefsIter->c_str());
-                    missing++;
-                }
-                allRefsIter++;
-            } else {
-                SkASSERT(!allDefsEnded);
-                SkASSERT(!allRefsEnded);
-                SkASSERT(allDefsIter->fDef == *allRefsIter);
-                allDefsIter++;
-                allRefsIter++;
-            }
-            if (missing >= 10) {
-                missing = 0;
-            }
-        }
-    }
-}
-
-bool MdOut::checkParamReturnBody(const Definition* def) {
-    TextParser paramBody(def);
-    const char* descriptionStart = paramBody.fChar;
-    if (!islower(descriptionStart[0]) && !isdigit(descriptionStart[0])) {
-        paramBody.skipToNonName();
-        string ref = string(descriptionStart, paramBody.fChar - descriptionStart);
-        if (!std::all_of(ref.begin(), ref.end(), [](char c) { return isupper(c); })
-                && !this->isDefined(paramBody, Resolvable::kYes)) {
-            string errorStr = MarkType::kReturn == def->fMarkType ? "return" : "param";
-            errorStr += " description must start with lower case";
-            paramBody.reportError(errorStr.c_str());
-            fAddRefFailed = true;
-            return false;
-        }
-    }
-    if ('.' == paramBody.fEnd[-1]) {
-        paramBody.reportError("make param description a phrase; should not end with period");
-        fAddRefFailed = true;
-        return false;
-    }
-    return true;
-}
-
-void MdOut::childrenOut(Definition* def, const char* start) {
-    const char* end;
-    fLineCount = def->fLineCount;
-    if (MarkType::kEnumClass == def->fMarkType) {
-        fEnumClass = def;
-    }
-    Resolvable resolvable = this->resolvable(def);
-    const Definition* prior = nullptr;
-    for (auto& child : def->fChildren) {
-        if (MarkType::kPhraseParam == child->fMarkType) {
-            continue;
-        }
-        end = child->fStart;
-        if (Resolvable::kNo != resolvable) {
-            if (def->isStructOrClass() || MarkType::kEnumClass == def->fMarkType) {
-                fNames = &def->asRoot()->fNames;
-            }
-            this->resolveOut(start, end, resolvable);
-        }
-        this->markTypeOut(child, &prior);
-        start = child->fTerminator;
-    }
-    if (Resolvable::kNo != resolvable) {
-        end = def->fContentEnd;
-        if (MarkType::kFormula == def->fMarkType && ' ' == start[0]) {
-            this->writeSpace();
-        }
-        this->resolveOut(start, end, resolvable);
-    }
-    if (MarkType::kEnumClass == def->fMarkType) {
-        fEnumClass = nullptr;
-    }
-}
-
-// output header for subtopic for all consts: name, value, short descriptions (#Line)
-// output link to in context #Const with moderate description
-void MdOut::summaryOut(const Definition* def, MarkType markType, string name) {
-    this->writePending();
-    SkASSERT(TableState::kNone == fTableState);
-    this->mdHeaderOut(3);
-    FPRINTF("%s", name.c_str());
-    this->lfAlways(2);
-    FPRINTF("%s", kTableDeclaration);  // <table> with style info
-    this->lfAlways(1);
-    FPRINTF("%s", MarkType::kConst == markType ? kAllConstTableHeader : kAllMemberTableHeader);
-    this->lfAlways(1);
-    bool odd = true;
-    for (auto child : def->fChildren) {
-        if (markType != child->fMarkType) {
-            continue;
-        }
-        auto oneLiner = std::find_if(child->fChildren.begin(), child->fChildren.end(),
-                [](const Definition* test){ return MarkType::kLine == test->fMarkType; } );
-        if (child->fChildren.end() == oneLiner) {
-            child->reportError<void>("missing #Line");
-            continue;
-        }
-        FPRINTF("%s", odd ? kTR_Dark.c_str() : "  <tr>");
-        this->lfAlways(1);
-        if (MarkType::kConst == markType) {
-            FPRINTF("%s", tableDataCodeRef(child).c_str());
-            this->lfAlways(1);
-            FPRINTF("%s", table_data_const(child, nullptr).c_str());
-        } else {
-            string memberType;
-            string memberName = this->getMemberTypeName(child, &memberType);
-            SkASSERT(MarkType::kMember == markType);
-            FPRINTF("%s", out_table_data_description(memberType).c_str());
-            this->lfAlways(1);
-            FPRINTF("%s", tableDataCodeLocalRef(memberName).c_str());
-        }
-        this->lfAlways(1);
-        FPRINTF("%s", out_table_data_description(*oneLiner).c_str());
-        this->lfAlways(1);
-        FPRINTF("%s", "  </tr>");
-        this->lfAlways(1);
-        odd = !odd;
-    }
-    FPRINTF("</table>");
-    this->lfAlways(1);
-}
-
-Definition* MdOut::csParent() {
-    if (!fRoot) {
-        return nullptr;
-    }
-    Definition* csParent = fRoot->csParent();
-    if (!csParent) {
-        const Definition* topic = fRoot;
-        while (topic && MarkType::kTopic != topic->fMarkType) {
-            topic = topic->fParent;
-        }
-        for (auto child : topic->fChildren) {
-            if (child->isStructOrClass() || MarkType::kTypedef == child->fMarkType) {
-                csParent = child;
-                break;
-            }
-        }
-        SkASSERT(csParent || string::npos == fRoot->fFileName.find("Sk")
-                || string::npos != fRoot->fFileName.find("SkBlendMode_Reference.bmh"));
-    }
-    return csParent;
-}
-
-bool MdOut::DefinedState::findLink(string word, string* linkPtr, bool addParens) {
-    const NameMap* names = fNames;
-    do {
-        auto localIter = names->fRefMap.find(word);
-        if (names->fRefMap.end() != localIter) {
-            if ((fPriorDef = localIter->second)) {
-                auto linkIter = names->fLinkMap.find(word);
-                SkAssertResult(names->fLinkMap.end() != linkIter);
-                *linkPtr = linkIter->second;
-            }
-            return true;
-        }
-        if (!names->fParent && isupper(word[0])) {
-            SkASSERT(names == &fBmhParser->fGlobalNames);
-            string lower = (char) tolower(word[0]) + word.substr(1);
-            auto globalIter = names->fRefMap.find(lower);
-            if (names->fRefMap.end() != globalIter) {
-                if ((fPriorDef = globalIter->second)) {
-                    auto lowerIter = names->fLinkMap.find(lower);
-                    SkAssertResult(names->fLinkMap.end() != lowerIter);
-                    *linkPtr = lowerIter->second;
-                }
-                return true;
-            }
-        }
-        if (addParens) {
-            string parenWord = word + "()";
-            auto paramIter = names->fRefMap.find(parenWord);
-            if (names->fRefMap.end() != paramIter) {
-                if ((fPriorDef = paramIter->second)) {
-                    auto parenIter = names->fLinkMap.find(parenWord);
-                    SkAssertResult(names->fLinkMap.end() != parenIter);
-                    *linkPtr = parenIter->second;
-                }
-                return true;
-            }
-        }
-    } while ((names = names->fParent));
-    return false;
-}
-
-bool MdOut::DefinedState::findLink(string word, string* linkPtr,
-        unordered_map<string, Definition*>& map) {
-    auto mapIter = map.find(word);
-    if (map.end() != mapIter) {
-        if ((fPriorDef = mapIter->second)) {
-            *linkPtr = '#' + mapIter->second->fFiddle;
-        }
-        return true;
-    }
-    return false;
-}
-
-const Definition* MdOut::findParamType() {
-    SkASSERT(fMethod);
-    TextParser parser(fMethod->fFileName, fMethod->fStart, fMethod->fContentStart,
-            fMethod->fLineCount);
-    string lastFull;
-    do {
-        parser.skipToAlpha();
-        if (parser.eof()) {
-            return nullptr;
-        }
-        const char* word = parser.fChar;
-        parser.skipFullName();
-        SkASSERT(!parser.eof());
-        string name = string(word, parser.fChar - word);
-        if (fLastParam->fName == name) {
-            const Definition* paramType = this->isDefined(parser, Resolvable::kOut);
-            return paramType;
-        }
-        if (isupper(name[0])) {
-            lastFull = name;
-        }
-    } while (true);
-    return nullptr;
-}
-
-string MdOut::getMemberTypeName(const Definition* def, string* memberType) {
-    TextParser parser(def->fFileName, def->fStart, def->fContentStart,
-            def->fLineCount);
-    parser.skipExact("#Member");
-    parser.skipWhiteSpace();
-    const char* typeStart = parser.fChar;
-    const char* typeEnd = nullptr;
-    const char* nameStart = nullptr;
-    const char* nameEnd = nullptr;
-    do {
-        parser.skipToWhiteSpace();
-        if (nameStart) {
-            nameEnd = parser.fChar;
-        }
-        if (parser.eof()) {
-            break;
-        }
-        const char* spaceLoc = parser.fChar;
-        if (parser.skipWhiteSpace()) {
-            typeEnd = spaceLoc;
-            nameStart = parser.fChar;
-        }
-    } while (!parser.eof());
-    SkASSERT(typeEnd);
-    *memberType = string(typeStart, (int) (typeEnd - typeStart));
-    replace_all(*memberType, " ", "&nbsp;");
-    SkASSERT(nameStart);
-    SkASSERT(nameEnd);
-    return string(nameStart, (int) (nameEnd - nameStart));
-}
-
-bool MdOut::HasDetails(const Definition* def) {
-    for (auto child : def->fChildren) {
-        if (MarkType::kDetails == child->fMarkType) {
-            return true;
-        }
-        if (MdOut::HasDetails(child)) {
-            return true;
-        }
-    }
-    return false;
-}
-
-void MdOut::htmlOut(string s) {
-    SkASSERT(string::npos != s.find('<'));
-    FPRINTF("%s", s.c_str());
-}
-
-const Definition* MdOut::isDefined(const TextParser& parser, Resolvable resolvable) {
-    DefinedState s(*this, parser.fStart, parser.fEnd, resolvable);
-    const char* start = parser.fStart;
-    do {
-        s.fSeparatorStart = start;
-        start = s.skipWhiteSpace();
-        s.skipParens();
-        (void) s.nextSeparator(start);
-        if (s.findEnd(start)) {
-            return nullptr;
-        }
-        s.fWord = string(start, s.fEnd - start);
-        s.setLower();
-    } while (s.setPriorSpaceWord(&start));
-    s.setLink();
-    return s.fPriorDef;
-}
-
-string MdOut::linkName(const Definition* ref) const {
-    string result = ref->fName;
-    size_t under = result.find('_');
-    if (string::npos != under) {
-        string classPart = result.substr(0, under);
-        string namePart = result.substr(under + 1, result.length());
-        if (fRoot && (fRoot->fName == classPart
-                || (fRoot->fParent && fRoot->fParent->fName == classPart))) {
-            result = namePart;
-        }
-    }
-    replace_all(result, "::", "_");
-    return result;
-}
-
-static bool writeTableEnd(MarkType markType, Definition* def, const Definition** prior) {
-    return markType != def->fMarkType && *prior && markType == (*prior)->fMarkType;
-}
-
-// Recursively build string with declarative code. Skip structs, classes, that
-// have been built directly.
-void MdOut::addCodeBlock(const Definition* def, string& result) const {
-    const Definition* last = nullptr;
-    bool wroteFunction = false;
-    for (auto member : def->fChildren) {
-        const Definition* prior = last;
-        const char* priorTerminator = nullptr;
-        if (prior) {
-            priorTerminator = prior->fTerminator ? prior->fTerminator : prior->fContentEnd;
-        }
-        last = member;
-        if (KeyWord::kIfndef == member->fKeyWord) {
-            this->addCodeBlock(member, result);
-            continue;
-        }
-        if (KeyWord::kClass == member->fKeyWord || KeyWord::kStruct == member->fKeyWord
-                || KeyWord::kTemplate == member->fKeyWord) {
-            if (!member->fChildren.size()) {
-                continue;
-            }
-            // todo: Make sure this was written non-elided somewhere else
-            // todo: provide indent value?
-            string block = fIncludeParser.elidedCodeBlock(*member);
-            // add italic link for elided body
-            size_t brace = block.find('{');
-            if (string::npos != brace) {
-                string name = member->fName;
-                if ("" == name) {
-                    for (auto child : member->fChildren) {
-                        if ("" != (name = child->fName)) {
-                            break;
-                        }
-                    }
-                }
-                SkASSERT("" != name);
-                string body = "\n    // <i>" + name + " interface</i>";
-                block = block.substr(0, brace + 1) + body + block.substr(brace + 1);
-            }
-            this->stringAppend(result, block);
-            continue;
-        }
-        if (KeyWord::kEnum == member->fKeyWord) {
-            if (member->fChildren.empty()) {
-                continue;
-            }
-            auto tokenIter = member->fTokens.begin();
-            if (KeyWord::kEnum == member->fKeyWord && KeyWord::kClass == tokenIter->fKeyWord) {
-                tokenIter = tokenIter->fTokens.begin();
-            }
-            while (Definition::Type::kWord != tokenIter->fType) {
-                std::advance(tokenIter, 1);
-            }
-            const auto& token = *tokenIter;
-            string name = string(token.fContentStart, token.length());
-            SkASSERT(name.length() > 0);
-            MarkType markType = KeyWord::kClass == member->fKeyWord
-                    || KeyWord::kStruct == member->fKeyWord ? MarkType::kClass : MarkType::kEnum;
-            // find bmh def or just find name of class / struct / enum ? (what if enum is nameless?)
-            if (wroteFunction) {
-                this->stringAppend(result, '\n');
-                wroteFunction = false;
-            }
-            this->stringAppend(result,
-                    fIncludeParser.codeBlock(markType, name, fInProgress));
-            this->stringAppend(result, '\n');
-            continue;
-        }
-        // Global function declarations are not preparsed very well;
-        // make do by using the prior position to find the start
-        if (Bracket::kParen == member->fBracket && prior) {
-            TextParser function(member->fFileName, priorTerminator, member->fTerminator + 1,
-                    member->fLineCount);
-            this->stringAppend(result,
-                    fIncludeParser.writeCodeBlock(function, MarkType::kFunction, 0));
-            this->stringAppend(result, ";\n");
-            wroteFunction = true;
-            continue;
-        }
-        if (KeyWord::kTypedef == member->fKeyWord) {
-            this->stringAppend(result, member);
-            this->stringAppend(result, ";\n");
-            continue;
-        }
-        if (KeyWord::kDefine == member->fKeyWord) {
-            string body(member->fContentStart, member->length());
-            if (string::npos != body.find('(')) {
-                this->stringAppend(result, body);
-                this->stringAppend(result, '\n');
-            }
-            continue;
-        }
-        if (KeyWord::kConstExpr == member->fKeyWord) {
-            this->stringAppend(result, member);
-            auto nextMember = def->fTokens.begin();
-            unsigned tokenPos = member->fParentIndex + 1;
-            SkASSERT(tokenPos < def->fTokens.size());
-            std::advance(nextMember, tokenPos);
-            while (member->fContentEnd >= nextMember->fContentStart) {
-                std::advance(nextMember, 1);
-                SkASSERT(++tokenPos < def->fTokens.size());
-            }
-            while (Punctuation::kSemicolon != nextMember->fPunctuation) {
-                std::advance(nextMember, 1);
-                SkASSERT(++tokenPos < def->fTokens.size());
-            }
-            TextParser between(member->fFileName, member->fContentEnd,
-                    nextMember->fContentStart, member->fLineCount);
-            between.skipWhiteSpace();
-            if ('=' == between.peek()) {
-                this->stringAppend(result, ' ');
-                string middle(between.fChar, nextMember->fContentStart);
-                this->stringAppend(result, middle);
-                last = nullptr;
-            } else {
-                SkAssertResult(';' == between.peek());
-            }
-            this->stringAppend(result, ';');
-            this->stringAppend(result, '\n');
-            continue;
-        }
-    }
-}
-
-void MdOut::markTypeOut(Definition* def, const Definition** prior) {
-    string printable = def->printableName();
-    const char* textStart = def->fContentStart;
-    bool lookForOneLiner = false;
-    // #Param and #Const don't have markers to say when the last is seen, so detect that by looking
-    // for a change in type.
-    if (writeTableEnd(MarkType::kParam, def, prior) || writeTableEnd(MarkType::kConst, def, prior)
-                || writeTableEnd(MarkType::kMember, def, prior)) {
-        this->writePending();
-        FPRINTF("</table>");
-        this->lf(2);
-        fTableState = TableState::kNone;
-    }
-    fLastDef = def;
-    NameMap paramMap;
-    switch (def->fMarkType) {
-        case MarkType::kAlias:
-            break;
-        case MarkType::kAnchor: {
-            if (fColumn > 0) {
-                this->writeSpace();
-            }
-            this->writePending();
-            TextParser parser(def);
-            const char* start = parser.fChar;
-            parser.skipToEndBracket((string(" ") + def->fMC + " ").c_str());
-            string anchorText(start, parser.fChar - start);
-            parser.skipExact((string(" ") + def->fMC + " ").c_str());
-            string anchorLink(parser.fChar, parser.fEnd - parser.fChar);
-            this->htmlOut(anchorRef(anchorLink, anchorText));
-            } break;
-        case MarkType::kBug:
-            break;
-        case MarkType::kClass:
-        case MarkType::kStruct:
-            fRoot = def->asRoot();
-            this->lfAlways(2);
-            if (MarkType::kStruct == def->fMarkType) {
-                this->htmlOut(anchorDef(def->fFiddle, ""));
-            } else {
-                this->htmlOut(anchorDef(this->linkName(def), ""));
-            }
-            this->lfAlways(2);
-            FPRINTF("---");
-            this->lf(2);
-            break;
-        case MarkType::kCode:
-            this->lfAlways(2);
-            FPRINTF("<pre style=\"padding: 1em 1em 1em 1em;"
-                    "width: 62.5em; background-color: #f0f0f0\">");
-            this->lf(1);
-            fResolveAndIndent = true;
-            break;
-        case MarkType::kColumn:
-            this->writePending();
-            if (fInList) {
-                FPRINTF("    <td>");
-            } else {
-                FPRINTF("| ");
-            }
-            break;
-        case MarkType::kComment:
-            break;
-        case MarkType::kMember:
-        case MarkType::kConst: {
-            bool isConst = MarkType::kConst == def->fMarkType;
-            lookForOneLiner = false;
-            fWroteSomething = false;
-        // output consts for one parent with moderate descriptions
-        // optional link to subtopic with longer descriptions, examples
-            if (TableState::kNone == fTableState) {
-                SkASSERT(!*prior || (isConst && MarkType::kConst != (*prior)->fMarkType)
-                        || (!isConst && MarkType::kMember != (*prior)->fMarkType));
-                if (isConst) {
-                    this->mdHeaderOut(3);
-                    this->writeString(this->fPopulators[SubtopicKeys::kConstants].fPlural);
-                    this->lfAlways(2);
-                }
-                FPRINTF("%s", kTableDeclaration);
-                fTableState = TableState::kRow;
-                fOddRow = true;
-                this->lfAlways(1);
-                // look ahead to see if the details column has data or not
-                fHasDetails = MdOut::HasDetails(def->fParent);
-                FPRINTF("%s", fHasDetails ? \
-                        (isConst ? kSubConstTableHeader : kSubMemberTableHeader) : \
-                        (isConst ? kAllConstTableHeader : kAllMemberTableHeader));
-                this->lfAlways(1);
-            }
-            if (TableState::kRow == fTableState) {
-                this->writePending();
-                FPRINTF("%s", fOddRow ? kTR_Dark.c_str() : "  <tr>");
-                fOddRow = !fOddRow;
-                this->lfAlways(1);
-                fTableState = TableState::kColumn;
-            }
-            this->writePending();
-            if (isConst) {
-                // TODO: if fHasDetails is true, could defer def and issue a ref instead
-                // unclear if this is a good idea or not
-                FPRINTF("%s", this->tableDataCodeDef(def).c_str());
-                this->lfAlways(1);
-                FPRINTF("%s", table_data_const(def, &textStart).c_str());
-            } else {
-                string memberType;
-                string memberName = this->getMemberTypeName(def, &memberType);
-                FPRINTF("%s", out_table_data_description(memberType).c_str());
-                this->lfAlways(1);
-                FPRINTF("%s", tableDataCodeDef(def->fFiddle, memberName).c_str());
-            }
-            this->lfAlways(1);
-            if (fHasDetails) {
-                string details;
-                auto subtopic = std::find_if(def->fChildren.begin(), def->fChildren.end(),
-                        [](const Definition* test){
-                        return MarkType::kDetails == test->fMarkType; } );
-                if (def->fChildren.end() != subtopic) {
-                    string subtopicName = string((*subtopic)->fContentStart,
-                            (int) ((*subtopic)->fContentEnd - (*subtopic)->fContentStart));
-                    const Definition* parentSubtopic = def->subtopicParent();
-                    SkASSERT(parentSubtopic);
-                    string fullName = parentSubtopic->fFiddle + '_' + subtopicName;
-                    if (fBmhParser.fTopicMap.end() == fBmhParser.fTopicMap.find(fullName)) {
-                        (*subtopic)->reportError<void>("missing #Details subtopic");
-                    }
-             //       subtopicName = parentSubtopic->fName + '_' + subtopicName;
-                    string noUnderscores = subtopicName;
-                    replace_all(noUnderscores, "_", "&nbsp;");
-                    details = this->anchorLocalRef(subtopicName, noUnderscores) + "&nbsp;";
-                }
-                FPRINTF("%s", out_table_data_details(details).c_str());
-                this->lfAlways(1);
-            }
-            lookForOneLiner = true;  // if description is empty, use oneLiner data
-            FPRINTF("%s", out_table_data_description_start().c_str()); // start of Description
-            this->lfAlways(1);
-        } break;
-        case MarkType::kDescription:
-            fInDescription = true;
-            this->writePending();
-            FPRINTF("%s", "<div>");
-            break;
-        case MarkType::kDetails:
-            break;
-        case MarkType::kDuration:
-            break;
-        case MarkType::kDefine:
-        case MarkType::kEnum:
-        case MarkType::kEnumClass:
-            this->lfAlways(2);
-            this->htmlOut(anchorDef(def->fFiddle, ""));
-            this->lfAlways(2);
-            FPRINTF("---");
-            this->lf(2);
-            break;
-        case MarkType::kExample: {
-            this->mdHeaderOut(3);
-            FPRINTF("%s", "Example\n"
-                            "\n");
-            fHasFiddle = true;
-            bool showGpu = false;
-            bool gpuAndCpu = false;
-            const Definition* platform = def->hasChild(MarkType::kPlatform);
-            if (platform) {
-                TextParser platParse(platform);
-                fHasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd);
-                showGpu = platParse.strnstr("gpu", platParse.fEnd);
-                if (showGpu) {
-                    gpuAndCpu = platParse.strnstr("cpu", platParse.fEnd);
-                }
-            }
-            if (fHasFiddle) {
-                SkASSERT(def->fHash.length() > 0);
-                FPRINTF("<div><fiddle-embed name=\"%s\"", def->fHash.c_str());
-                if (showGpu) {
-                    FPRINTF("%s", " gpu=\"true\"");
-                    if (gpuAndCpu) {
-                        FPRINTF("%s", " cpu=\"true\"");
-                    }
-                }
-                FPRINTF("%s", ">");
-            } else {
-                SkASSERT(def->fHash.length() == 0);
-                FPRINTF("%s", "<pre style=\"padding: 1em 1em 1em 1em; font-size: 13px"
-                        " width: 62.5em; background-color: #f0f0f0\">");
-                this->lfAlways(1);
-                if (def->fWrapper.length() > 0) {
-                    FPRINTF("%s", def->fWrapper.c_str());
-                }
-                fLiteralAndIndent = true;
-            }
-            } break;
-        case MarkType::kExternal:
-            break;
-        case MarkType::kFile:
-            break;
-        case MarkType::kFilter:
-            break;
-        case MarkType::kFormula:
-            break;
-        case MarkType::kFunction:
-            break;
-        case MarkType::kHeight:
-            break;
-        case MarkType::kIllustration: {
-            string illustName = "Illustrations_" + def->fParent->fFiddle;
-            string number = string(def->fContentStart, def->length());
-            if (number.length() && "1" != number) {
-                illustName += "_" + number;
-            }
-            auto illustIter = fBmhParser.fTopicMap.find(illustName);
-            SkASSERT(fBmhParser.fTopicMap.end() != illustIter);
-            Definition* illustDef = illustIter->second;
-            SkASSERT(MarkType::kSubtopic == illustDef->fMarkType);
-            SkASSERT(1 == illustDef->fChildren.size());
-            Definition* illustExample = illustDef->fChildren[0];
-            SkASSERT(MarkType::kExample == illustExample->fMarkType);
-            string hash = illustExample->fHash;
-            SkASSERT("" != hash);
-            string title;
-            this->writePending();
-            FPRINTF("![%s](https://fiddle.skia.org/i/%s_raster.png \"%s\")",
-                    def->fName.c_str(), hash.c_str(), title.c_str());
-            this->lf(2);
-        } break;
-        case MarkType::kImage:
-            break;
-        case MarkType::kIn:
-            break;
-        case MarkType::kLegend:
-            break;
-        case MarkType::kLine:
-            break;
-        case MarkType::kLink:
-            break;
-        case MarkType::kList:
-            fInList = true;
-            fTableState = TableState::kRow;
-            this->lfAlways(2);
-            FPRINTF("%s", "<table>");
-            this->lf(1);
-            break;
-        case MarkType::kLiteral:
-            break;
-        case MarkType::kMarkChar:
-            fBmhParser.fMC = def->fContentStart[0];
-            break;
-        case MarkType::kMethod: {
-            this->lfAlways(2);
-			if (false && !def->isClone()) {
-                string method_name = def->methodName();
-                this->mdHeaderOutLF(2, 1);
-                this->htmlOut(this->anchorDef(def->fFiddle, method_name));
-			} else {
-                this->htmlOut(this->anchorDef(def->fFiddle, ""));
-            }
-            this->lfAlways(2);
-            FPRINTF("---");
-			this->lf(2);
-
-            // TODO: put in css spec that we can define somewhere else (if markup supports that)
-            // TODO: 50em below should match limit = 80 in formatFunction()
-            this->writePending();
-            string formattedStr = def->formatFunction(Definition::Format::kIncludeReturn);
-            string preformattedStr = preformat(formattedStr);
-            string references = this->addReferences(&preformattedStr.front(),
-                    &preformattedStr.back() + 1, Resolvable::kSimple);
-            preformattedStr = references;
-            this->htmlOut("<pre style=\"padding: 1em 1em 1em 1em; width: 62.5em;"
-                    "background-color: #f0f0f0\">\n" + preformattedStr + "\n" + "</pre>");
-            this->lf(2);
-            fTableState = TableState::kNone;
-            fMethod = def;
-            Definition* iMethod = fIncludeParser.findMethod(*def);
-            if (iMethod) {
-                fMethod = iMethod;
-                paramMap.fParent = &fBmhParser.fGlobalNames;
-                paramMap.setParams(def, iMethod);
-                fNames = &paramMap;
-            }
-            } break;
-        case MarkType::kNoExample:
-            break;
-        case MarkType::kNoJustify:
-            break;
-        case MarkType::kOutdent:
-            break;
-        case MarkType::kParam: {
-            TextParser paramParser(def->fFileName, def->fStart, def->fContentStart,
-                    def->fLineCount);
-            paramParser.skipWhiteSpace();
-            SkASSERT(paramParser.startsWith("#Param"));
-            paramParser.next(); // skip hash
-            paramParser.skipToNonName(); // skip Param
-            this->parameterHeaderOut(paramParser, prior, def);
-        } break;
-        case MarkType::kPhraseDef:
-            // skip text and children
-            *prior = def;
-            return;
-        case MarkType::kPhraseParam:
-            SkDebugf(""); // convenient place to set a breakpoint
-            break;
-        case MarkType::kPhraseRef:
-            if (fPhraseParams.end() != fPhraseParams.find(def->fName)) {
-                if (fColumn > 0) {
-                    this->writeSpace();
-                }
-                this->writeString(fPhraseParams[def->fName]);
-                if (isspace(def->fContentStart[0])) {
-                    this->writeSpace();
-                }
-            } else if (fBmhParser.fPhraseMap.end() == fBmhParser.fPhraseMap.find(def->fName)) {
-                def->reportError<void>("missing phrase definition");
-                fAddRefFailed = true;
-            } else {
-                if (fColumn) {
-                    SkASSERT(' ' >= def->fStart[0]);
-                    this->writeSpace();
-                }
-                Definition* phraseRef = fBmhParser.fPhraseMap.find(def->fName)->second;
-                // def->fChildren are parameters to substitute phraseRef->fChildren,
-                // phraseRef->fChildren has both param defines and references
-                // def->fChildren must have the same number of entries as phaseRef->fChildren
-                // which are kPhraseParam, and substitute one for one
-                // Then, each kPhraseRef in phaseRef looks up the key and value
-                fPhraseParams.clear();
-                auto refKidsIter = phraseRef->fChildren.begin();
-                for (auto child : def->fChildren) {
-                    if (MarkType::kPhraseParam != child->fMarkType) {
-                        // more work to do to support other types
-                        this->reportError("phrase ref child must be param");
-                    }
-                    do {
-                        if (refKidsIter == phraseRef->fChildren.end()) {
-                            this->reportError("phrase def missing param");
-                            break;
-                        }
-                        if (MarkType::kPhraseRef == (*refKidsIter)->fMarkType) {
-                            continue;
-                        }
-                        if (MarkType::kPhraseParam != (*refKidsIter)->fMarkType) {
-                            this->reportError("unexpected type in phrase def children");
-                            break;
-                        }
-                        fPhraseParams[(*refKidsIter)->fName] = child->fName;
-                        break;
-                    } while (true);
-                }
-                this->childrenOut(phraseRef, phraseRef->fContentStart);
-                fPhraseParams.clear();
-                if (' ' >= def->fContentStart[0] && !fPendingLF) {
-                    this->writeSpace();
-                }
-            }
-            break;
-        case MarkType::kPlatform:
-            break;
-        case MarkType::kPopulate: {
-            Definition* parent = def->fParent;
-            SkASSERT(parent);
-            if (MarkType::kCode == parent->fMarkType) {
-                auto inDef = std::find_if(parent->fChildren.begin(), parent->fChildren.end(),
-                        [](const Definition* child) { return MarkType::kIn == child->fMarkType; });
-                if (parent->fChildren.end() != inDef) {
-                    auto filterDef = std::find_if(parent->fChildren.begin(),
-                            parent->fChildren.end(), [](const Definition* child) {
-                            return MarkType::kFilter == child->fMarkType; });
-                    SkASSERT(parent->fChildren.end() != filterDef);
-                    string codeBlock = fIncludeParser.filteredBlock(
-                            string((*inDef)->fContentStart, (*inDef)->length()),
-                            string((*filterDef)->fContentStart, (*filterDef)->length()));
-                    this->resolveOut(codeBlock.c_str(), codeBlock.c_str() + codeBlock.length(),
-                            this->resolvable(parent));
-                    break;
-                }
-                // find include matching code parent
-                Definition* grand = parent->fParent;
-                SkASSERT(grand);
-                if (MarkType::kClass == grand->fMarkType
-                        || MarkType::kStruct == grand->fMarkType
-                        || MarkType::kEnum == grand->fMarkType
-                        || MarkType::kEnumClass == grand->fMarkType
-                        || MarkType::kTypedef == grand->fMarkType
-                        || MarkType::kDefine == grand->fMarkType) {
-                    string codeBlock = fIncludeParser.codeBlock(*grand, fInProgress);
-                    this->resolveOut(codeBlock.c_str(), codeBlock.c_str() + codeBlock.length(),
-                            this->resolvable(parent));
-                } else if (MarkType::kTopic == grand->fMarkType) {
-                    // use bmh file name to find include file name
-                    size_t start = grand->fFileName.rfind("Sk");
-                    SkASSERT(start != string::npos);
-                    size_t end = grand->fFileName.rfind("_Reference");
-                    SkASSERT(end != string::npos && end > start);
-                    string incName(grand->fFileName.substr(start, end - start));
-                    const Definition* includeDef = fIncludeParser.include(incName + ".h");
-                    SkASSERT(includeDef);
-                    string codeBlock;
-                    this->addCodeBlock(includeDef, codeBlock);
-                    this->resolveOut(codeBlock.c_str(), codeBlock.c_str() + codeBlock.length(),
-                            this->resolvable(parent));
-                } else {
-                    SkASSERT(MarkType::kSubtopic == grand->fMarkType);
-                    auto inTag = std::find_if(grand->fChildren.begin(), grand->fChildren.end(),
-                            [](Definition* child){return MarkType::kIn == child->fMarkType;});
-                    SkASSERT(grand->fChildren.end() != inTag);
-                    auto filterTag = std::find_if(grand->fChildren.begin(), grand->fChildren.end(),
-                            [](Definition* child){return MarkType::kFilter == child->fMarkType;});
-                    SkASSERT(grand->fChildren.end() != filterTag);
-                    string inContents((*inTag)->fContentStart, (*inTag)->length());
-                    string filterContents((*filterTag)->fContentStart, (*filterTag)->length());
-                    string filteredBlock = fIncludeParser.filteredBlock(inContents, filterContents);
-                    this->resolveOut(filteredBlock.c_str(), filteredBlock.c_str()
-                            + filteredBlock.length(), this->resolvable(parent));
-                }
-            } else {
-                SkASSERT(MarkType::kMethod == parent->fMarkType);
-                // retrieve parameters, return, description from include
-                Definition* iMethod = fIncludeParser.findMethod(*parent);
-                if (!iMethod) {  // deprecated or 'in progress' functions should not include populate
-                    SkDebugf("#Populate found in deprecated or missing method %s\n", def->fName.c_str());
-                    def->fParent->reportError<void>("Remove #Method");
-                }
-                bool wroteParam = false;
-                SkASSERT(fMethod == iMethod);
-                for (auto& entry : iMethod->fTokens) {
-                    if (MarkType::kComment != entry.fMarkType) {
-                        continue;
-                    }
-                    TextParser parser(&entry);
-                    if (parser.skipExact("@param ")) { // write parameters, if any
-                        this->parameterHeaderOut(parser, prior, def);
-                        this->resolveOut(parser.fChar, parser.fEnd,
-                                Resolvable::kInclude);
-                        this->parameterTrailerOut();
-                        wroteParam = true;
-                        continue;
-                    }
-                    if (wroteParam) {
-                        this->writePending();
-                        FPRINTF("</table>");
-                        this->lf(2);
-                        fTableState = TableState::kNone;
-                        wroteParam = false;
-                    }
-                    if (parser.skipExact("@return ")) { // write return, if any
-                        this->returnHeaderOut(prior, def);
-                        this->resolveOut(parser.fChar, parser.fEnd,
-                                Resolvable::kInclude);
-                        this->lf(2);
-                        continue;
-                    }
-                    if (1 == entry.length() && '/' == entry.fContentStart[0]) {
-                        continue;
-                    }
-                    if ("/!< " == string(entry.fContentStart, entry.length()).substr(0, 4)) {
-                        continue;
-                    }
-                    const char* backwards = entry.fContentStart;
-                    while (' ' == *--backwards)
-                        ;
-                    if ('\n' == backwards[0] && '\n' == backwards[-1]) {
-                        this->lf(2);
-                    }
-                    this->resolveOut(entry.fContentStart, entry.fContentEnd,
-                            Resolvable::kInclude);  // write description
-                    this->lf(1);
-                }
-            }
-            } break;
-        case MarkType::kReturn:
-            this->returnHeaderOut(prior, def);
-            break;
-        case MarkType::kRow:
-            if (fInList) {
-                FPRINTF("  <tr>");
-                this->lf(1);
-            }
-            break;
-        case MarkType::kSeeAlso:
-            this->mdHeaderOut(3);
-            FPRINTF("See Also");
-            this->lf(2);
-            break;
-        case MarkType::kSet:
-            break;
-        case MarkType::kStdOut: {
-            TextParser code(def);
-            this->mdHeaderOut(4);
-            FPRINTF(
-                    "Example Output\n"
-                    "\n"
-                    "~~~~");
-            this->lfAlways(1);
-            code.skipSpace();
-            while (!code.eof()) {
-                const char* end = code.trimmedLineEnd();
-                FPRINTF("%.*s\n", (int) (end - code.fChar), code.fChar);
-                code.skipToLineStart();
-            }
-            FPRINTF("~~~~");
-            this->lf(2);
-            } break;
-        case MarkType::kSubstitute:
-            break;
-        case MarkType::kSubtopic:
-            fSubtopic = def->asRoot();
-            if (false && SubtopicKeys::kOverview == def->fName) {
-                this->writeString(def->fName);
-            } else {
-                this->lfAlways(2);
-                this->htmlOut(anchorDef(def->fName, ""));
-            }
-            if (std::any_of(def->fChildren.begin(), def->fChildren.end(),
-                    [](Definition* child) {
-                    return MarkType::kSeeAlso == child->fMarkType
-                    || MarkType::kExample == child->fMarkType
-                    || MarkType::kNoExample == child->fMarkType;
-            })) {
-                this->lfAlways(2);
-                FPRINTF("---");
-            }
-            this->lf(2);
-#if 0
-            // if a subtopic child is const, generate short table of const name, value, line desc
-            if (std::any_of(def->fChildren.begin(), def->fChildren.end(),
-                    [](Definition* child){return MarkType::kConst == child->fMarkType;})) {
-                this->summaryOut(def, MarkType::kConst, fPopulators[SubtopicKeys::kConstants].fPlural);
-            }
-#endif
-            // if a subtopic child is member, generate short table of const name, value, line desc
-            if (std::any_of(def->fChildren.begin(), def->fChildren.end(),
-                    [](Definition* child){return MarkType::kMember == child->fMarkType;})) {
-                this->summaryOut(def, MarkType::kMember, fPopulators[SubtopicKeys::kMembers].fPlural);
-            }
-            break;
-        case MarkType::kTable:
-            this->lf(2);
-            break;
-        case MarkType::kTemplate:
-            break;
-        case MarkType::kText:
-            if (def->fParent && MarkType::kFormula == def->fParent->fMarkType) {
-                if (fColumn > 0) {
-                    this->writeSpace();
-                }
-                this->writePending();
-                this->htmlOut("<code>");
-                this->resolveOut(def->fContentStart, def->fContentEnd,
-                        Resolvable::kFormula);
-                this->htmlOut("</code>");
-            }
-            break;
-        case MarkType::kToDo:
-            break;
-        case MarkType::kTopic: {
-            auto found = std::find_if(def->fChildren.begin(), def->fChildren.end(),
-                    [](Definition* test) { return test->isStructOrClass(); } );
-            bool hasClassOrStruct = def->fChildren.end() != found;
-            fRoot = hasClassOrStruct ? (*found)->asRoot() : def->asRoot();
-            fSubtopic = def->asRoot();
-            bool isUndocumented = string::npos != def->fFileName.find("undocumented");
-            if (!isUndocumented) {
-                this->populateTables(def, fRoot);
-            }
-//            this->mdHeaderOut(1);
-//            this->htmlOut(anchorDef(this->linkName(def), printable));
-//            this->lf(1);
-            } break;
-        case MarkType::kTypedef:
-            this->lfAlways(2);
-            this->htmlOut(anchorDef(def->fFiddle, ""));
-            this->lfAlways(2);
-            FPRINTF("---");
-            this->lf(2);
-            break;
-        case MarkType::kUnion:
-            break;
-        case MarkType::kVolatile:
-            break;
-        case MarkType::kWidth:
-            break;
-        default:
-            SkDebugf("fatal error: MarkType::k%s unhandled in %s()\n",
-                    BmhParser::kMarkProps[(int) def->fMarkType].fName, __func__);
-            SkASSERT(0); // handle everything
-            break;
-    }
-    this->childrenOut(def, textStart);
-    switch (def->fMarkType) {  // post child work, at least for tables
-        case MarkType::kAnchor:
-            if (fColumn > 0) {
-                this->writeSpace();
-            }
-            break;
-        case MarkType::kClass:
-        case MarkType::kStruct:
-            if (TableState::kNone != fTableState) {
-                this->writePending();
-                FPRINTF("</table>");
-                this->lf(2);
-                fTableState = TableState::kNone;
-            }
-            if (def->csParent()) {
-                fRoot = def->csParent()->asRoot();
-            }
-            break;
-        case MarkType::kCode:
-            fIndent = 0;
-            this->lf(1);
-            this->writePending();
-            FPRINTF("</pre>");
-            this->lf(2);
-            fResolveAndIndent = false;
-            break;
-        case MarkType::kColumn:
-            if (fInList) {
-                this->writePending();
-                FPRINTF("</td>");
-                this->lfAlways(1);
-            } else {
-                FPRINTF(" ");
-            }
-            break;
-        case MarkType::kDescription:
-            this->writePending();
-            FPRINTF("</div>");
-            fInDescription = false;
-            break;
-        case MarkType::kEnum:
-        case MarkType::kEnumClass:
-            if (TableState::kNone != fTableState) {
-                this->writePending();
-                FPRINTF("</table>");
-                this->lf(2);
-                fTableState = TableState::kNone;
-            }
-            break;
-        case MarkType::kExample:
-            this->writePending();
-            if (fHasFiddle) {
-                FPRINTF("</fiddle-embed></div>");
-            } else {
-                this->lfAlways(1);
-                if (def->fWrapper.length() > 0) {
-                    FPRINTF("}");
-                    this->lfAlways(1);
-                }
-                FPRINTF("</pre>");
-            }
-            this->lf(2);
-            fLiteralAndIndent = false;
-            break;
-        case MarkType::kLink:
-            this->writeString("</a>");
-            this->writeSpace();
-            break;
-        case MarkType::kList:
-            fInList = false;
-            this->writePending();
-            SkASSERT(TableState::kNone != fTableState);
-            FPRINTF("</table>");
-            this->lf(2);
-            fTableState = TableState::kNone;
-            break;
-        case MarkType::kLegend: {
-            SkASSERT(def->fChildren.size() == 1);
-            const Definition* row = def->fChildren[0];
-            SkASSERT(MarkType::kRow == row->fMarkType);
-            size_t columnCount = row->fChildren.size();
-            SkASSERT(columnCount > 0);
-            this->writePending();
-            for (size_t index = 0; index < columnCount; ++index) {
-                FPRINTF("| --- ");
-            }
-            FPRINTF(" |");
-            this->lf(1);
-            } break;
-        case MarkType::kMethod:
-            fMethod = nullptr;
-            fNames = fNames->fParent;
-            break;
-        case MarkType::kConst:
-        case MarkType::kMember:
-            if (lookForOneLiner && !fWroteSomething) {
-                auto oneLiner = std::find_if(def->fChildren.begin(), def->fChildren.end(),
-                        [](const Definition* test){ return MarkType::kLine == test->fMarkType; } );
-                if (def->fChildren.end() != oneLiner) {
-                    TextParser parser(*oneLiner);
-                    parser.skipWhiteSpace();
-                    parser.trimEnd();
-                    FPRINTF("%.*s", (int) (parser.fEnd - parser.fChar), parser.fChar);
-                }
-                lookForOneLiner = false;
-            }
-        case MarkType::kParam:
-            this->parameterTrailerOut();
-            break;
-        case MarkType::kReturn:
-        case MarkType::kSeeAlso:
-            this->lf(2);
-            break;
-        case MarkType::kRow:
-            if (fInList) {
-                FPRINTF("  </tr>");
-            } else {
-                FPRINTF("|");
-            }
-            this->lf(1);
-            break;
-        case MarkType::kTable:
-            this->lf(2);
-            break;
-        case MarkType::kPhraseDef:
-            break;
-        case MarkType::kSubtopic:
-            SkASSERT(def);
-            do {
-                def = def->fParent;
-            } while (def && MarkType::kTopic != def->fMarkType
-                    && MarkType::kSubtopic != def->fMarkType);
-            SkASSERT(def);
-            fSubtopic = def->asRoot();
-            break;
-        case MarkType::kTopic:
-            fSubtopic = nullptr;
-            break;
-        default:
-            break;
-    }
-    *prior = def;
-}
-
-void MdOut::mdHeaderOutLF(int depth, int lf) {
-    this->lfAlways(lf);
-    for (int index = 0; index < depth; ++index) {
-        FPRINTF("#");
-    }
-    FPRINTF(" ");
-}
-
-void MdOut::parameterHeaderOut(TextParser& paramParser, const Definition** prior, Definition* def) {
-    if (TableState::kNone == fTableState) {
-        SkASSERT(!*prior || MarkType::kParam != (*prior)->fMarkType);
-        this->mdHeaderOut(3);
-        this->htmlOut(
-                "Parameters\n"
-                "\n"
-                "<table>"
-                );
-        this->lf(1);
-        fTableState = TableState::kRow;
-    }
-    if (TableState::kRow == fTableState) {
-        FPRINTF("  <tr>");
-        this->lf(1);
-        fTableState = TableState::kColumn;
-    }
-    paramParser.skipSpace();
-    const char* paramName = paramParser.fChar;
-    paramParser.skipToSpace();
-    string paramNameStr(paramName, (int) (paramParser.fChar - paramName));
-    if (MarkType::kPopulate != def->fMarkType && !this->checkParamReturnBody(def)) {
-        *prior = def;
-        return;
-    }
-    string refNameStr = def->fParent->fFiddle + "_" + paramNameStr;
-    this->htmlOut("    <td>" + this->anchorDef(refNameStr,
-            "<code><strong>" + paramNameStr + "</strong></code>") + "</td>");
-    this->lfAlways(1);
-    FPRINTF("    <td>");
-}
-
-void MdOut::parameterTrailerOut() {
-    SkASSERT(TableState::kColumn == fTableState);
-    fTableState = TableState::kRow;
-    this->writePending();
-    FPRINTF("</td>");
-    this->lfAlways(1);
-    FPRINTF("  </tr>");
-    this->lfAlways(1);
-}
-
-void MdOut::populateOne(Definition* def,
-        unordered_map<string, RootDefinition::SubtopicContents>& populator) {
-    if (MarkType::kConst == def->fMarkType) {
-        populator[SubtopicKeys::kConstants].fMembers.push_back(def);
-        return;
-    }
-    if (MarkType::kEnum == def->fMarkType || MarkType::kEnumClass == def->fMarkType) {
-        populator[SubtopicKeys::kConstants].fMembers.push_back(def);
-        return;
-    }
-    if (MarkType::kDefine == def->fMarkType) {
-        populator[SubtopicKeys::kDefines].fMembers.push_back(def);
-        return;
-    }
-    if (MarkType::kMember == def->fMarkType) {
-        populator[SubtopicKeys::kMembers].fMembers.push_back(def);
-        return;
-    }
-    if (MarkType::kTypedef == def->fMarkType) {
-        populator[SubtopicKeys::kTypedefs].fMembers.push_back(def);
-        return;
-    }
-    if (MarkType::kMethod != def->fMarkType) {
-        return;
-    }
-    if (def->fClone) {
-        return;
-    }
-    if (Definition::MethodType::kConstructor == def->fMethodType
-            || Definition::MethodType::kDestructor == def->fMethodType) {
-        populator[SubtopicKeys::kConstructors].fMembers.push_back(def);
-        return;
-    }
-    if (Definition::MethodType::kOperator == def->fMethodType) {
-        populator[SubtopicKeys::kOperators].fMembers.push_back(def);
-        return;
-    }
-    populator[SubtopicKeys::kMemberFunctions].fMembers.push_back(def);
-    const Definition* csParent = this->csParent();
-    if (csParent) {
-        if (0 == def->fName.find(csParent->fName + "::Make")
-                || 0 == def->fName.find(csParent->fName + "::make")) {
-            populator[SubtopicKeys::kConstructors].fMembers.push_back(def);
-            return;
-        }
-    }
-    for (auto item : def->fChildren) {
-        if (MarkType::kIn == item->fMarkType) {
-            string name(item->fContentStart, item->fContentEnd - item->fContentStart);
-            populator[name].fMembers.push_back(def);
-            populator[name].fShowClones = true;
-            break;
-        }
-    }
-}
-
-void MdOut::populateTables(const Definition* def, RootDefinition* root) {
-    for (auto child : def->fChildren) {
-        if (MarkType::kSubtopic == child->fMarkType) {
-            string name = child->fName;
-            bool builtInTopic = name == SubtopicKeys::kOverview;
-            for (auto item : SubtopicKeys::kGeneratedSubtopics) {
-                builtInTopic |= name == item;
-            }
-            if (!builtInTopic) {
-                string subname;
-                const Definition* subtopic = child->subtopicParent();
-                if (subtopic) {
-                    subname = subtopic->fName + '_';
-                }
-                builtInTopic = name == subname + SubtopicKeys::kOverview;
-                for (auto item : SubtopicKeys::kGeneratedSubtopics) {
-                    builtInTopic |= name == subname + item;
-                }
-                if (!builtInTopic) {
-                    root->populator(SubtopicKeys::kRelatedFunctions).fMembers.push_back(child);
-                }
-            }
-            this->populateTables(child, root);
-            continue;
-        }
-        if (child->isStructOrClass()) {
-            if (fClassStack.size() > 0) {
-                root->populator(MarkType::kStruct != child->fMarkType ? SubtopicKeys::kClasses :
-                        SubtopicKeys::kStructs).fMembers.push_back(child);
-            }
-            fClassStack.push_back(child);
-            this->populateTables(child, child->asRoot());
-            fClassStack.pop_back();
-            continue;
-        }
-        if (MarkType::kEnum == child->fMarkType || MarkType::kEnumClass == child->fMarkType) {
-            this->populateTables(child, root);
-        }
-        this->populateOne(child, root->fPopulators);
-    }
-}
-
-void MdOut::resolveOut(const char* start, const char* end, Resolvable resolvable) {
-    if ((Resolvable::kLiteral == resolvable || fLiteralAndIndent ||
-            fResolveAndIndent) && end > start) {
-        int linefeeds = 0;
-        while ('\n' == *start) {
-            ++linefeeds;
-            ++start;
-        }
-        if (fResolveAndIndent && linefeeds) {
-            this->lf(linefeeds);
-        }
-        const char* spaceStart = start;
-        while (' ' == *start) {
-            ++start;
-        }
-        if (start > spaceStart) {
-            fIndent = start - spaceStart;
-        }
-    }
-    if (Resolvable::kLiteral == resolvable || fLiteralAndIndent) {
-        this->writeBlockTrim(end - start, start);
-        if ('\n' == end[-1]) {
-            this->lf(1);
-        }
-        fIndent = 0;
-        return;
-    }
-    // FIXME: this needs the markdown character present when the def was defined,
-    // not the last markdown character the parser would have seen...
-    while (fBmhParser.fMC == end[-1]) {
-        --end;
-    }
-    if (start >= end) {
-        return;
-    }
-    string resolved = this->addReferences(start, end, resolvable);
-    trim_end_spaces(resolved);
-    if (resolved.length()) {
-        TextParser paragraph(fFileName, &*resolved.begin(), &*resolved.end(), fLineCount);
-        while (!paragraph.eof()) {
-            while ('\n' == paragraph.peek()) {
-                paragraph.next();
-                if (paragraph.eof()) {
-                    return;
-                }
-            }
-            const char* lineStart = paragraph.fChar;
-            paragraph.skipWhiteSpace();
-            const char* contentStart = paragraph.fChar;
-            if (fResolveAndIndent && contentStart > lineStart) {
-                this->writePending();
-                this->indentToColumn(contentStart - lineStart);
-            }
-            paragraph.skipToEndBracket('\n');
-            ptrdiff_t lineLength = paragraph.fChar - contentStart;
-            if (lineLength) {
-                while (lineLength && contentStart[lineLength - 1] <= ' ') {
-                    --lineLength;
-                }
-                string str(contentStart, lineLength);
-                this->writeString(str.c_str());
-                fWroteSomething = !!lineLength;
-            }
-            if (paragraph.eof()) {
-                break;
-            }
-            if ('\n' == paragraph.next()) {
-                int linefeeds = 1;
-                if (!paragraph.eof() && '\n' == paragraph.peek()) {
-                    linefeeds = 2;
-                }
-                this->lf(linefeeds);
-            }
-        }
-    }
-}
-
-void MdOut::returnHeaderOut(const Definition** prior, Definition* def) {
-    this->mdHeaderOut(3);
-    FPRINTF("Return Value");
-    if (MarkType::kPopulate != def->fMarkType && !this->checkParamReturnBody(def)) {
-        *prior = def;
-        return;
-    }
-    this->lf(2);
-}
-
-void MdOut::rowOut(string col1, const Definition* col2) {
-    FPRINTF("%s", fOddRow ? kTR_Dark.c_str() : "  <tr>");
-    this->lfAlways(1);
-    FPRINTF("%s", kTD_Left.c_str());
-    if ("" != col1) {
-        this->writeString(col1);
-    }
-    FPRINTF("</td>");
-    this->lfAlways(1);
-    FPRINTF("%s", kTD_Left.c_str());
-    TextParser parser(col2->fFileName, col2->fStart, col2->fContentStart, col2->fLineCount);
-    parser.skipExact("#Method");
-    parser.skipSpace();
-    parser.trimEnd();
-    string methodName(parser.fChar, parser.fEnd - parser.fChar);
-    this->htmlOut(this->anchorRef("#" + col2->fFiddle, methodName));
-    this->htmlOut("</td>");
-    this->lfAlways(1);
-    FPRINTF("  </tr>");
-    this->lfAlways(1);
-    fOddRow = !fOddRow;
-}
-
-void MdOut::rowOut(const char* name, string description, bool literalName) {
-    FPRINTF("%s", fOddRow ? kTR_Dark.c_str() : "  <tr>");
-    this->lfAlways(1);
-    FPRINTF("%s", kTD_Left.c_str());
-    if (literalName) {
-        if (strlen(name)) {
-            this->writeString(name);
-        }
-    } else {
-        this->resolveOut(name, name + strlen(name), Resolvable::kYes);
-    }
-    FPRINTF("</td>");
-    this->lfAlways(1);
-    FPRINTF("%s", kTD_Left.c_str());
-    this->resolveOut(&description.front(), &description.back() + 1, Resolvable::kYes);
-    FPRINTF("</td>");
-    this->lfAlways(1);
-    FPRINTF("  </tr>");
-    this->lfAlways(1);
-    fOddRow = !fOddRow;
-}
-
-void MdOut::subtopicsOut(Definition* def) {
-    Definition* csParent = def->csParent();
-    const Definition* subtopicParent = def->subtopicParent();
-    const Definition* topicParent = def->topicParent();
-    SkASSERT(subtopicParent);
-    this->lfAlways(1);
-    FPRINTF("%s", kTableDeclaration);
-    this->lfAlways(1);
-    FPRINTF("%s", kTopicsTableHeader);
-    this->lfAlways(1);
-    fOddRow = true;
-    for (auto item : SubtopicKeys::kGeneratedSubtopics) {
-        if (SubtopicKeys::kMemberFunctions == item) {
-            continue;
-        }
-        for (auto entry : fRoot->populator(item).fMembers) {
-            if ((csParent && entry->csParent() == csParent)
-                    || entry->subtopicParent() == subtopicParent) {
-                if (SubtopicKeys::kRelatedFunctions == item) {
-                    (void) subtopicRowOut(entry->fName, entry); // report all errors
-                    continue;
-                }
-                auto popItem = fPopulators.find(item);
-                string description = popItem->second.fOneLiner;
-                if (SubtopicKeys::kConstructors == item) {
-                    description += " " + fRoot->fName;
-                }
-                string subtopic;
-                if (subtopicParent != topicParent) {
-                    subtopic = subtopicParent->fName + '_';
-                }
-                string link = this->anchorLocalRef(subtopic + item, popItem->second.fPlural);
-                this->rowOut(link.c_str(), description, true);
-                break;
-            }
-        }
-    }
-    FPRINTF("</table>");
-    this->lfAlways(1);
-}
-
-void MdOut::subtopicOut(string name) {
-    const Definition* topicParent = fSubtopic ? fSubtopic->topicParent() : nullptr;
-    Definition* csParent = fRoot && fRoot->isStructOrClass() ? fRoot : this->csParent();
-    if (!csParent) {
-        auto csIter = std::find_if(topicParent->fChildren.begin(), topicParent->fChildren.end(),
-                [](const Definition* def){ return MarkType::kEnum == def->fMarkType
-                || MarkType::kEnumClass == def->fMarkType; } );
-        SkASSERT(topicParent->fChildren.end() != csIter);
-        csParent = *csIter;
-    }
-    SkASSERT(csParent);
-    this->lfAlways(1);
-    if (fPopulators.end() != fPopulators.find(name)) {
-        const SubtopicDescriptions& tableDescriptions = this->populator(name);
-        this->anchorDef(name, tableDescriptions.fPlural);
-        this->lfAlways(1);
-        if (tableDescriptions.fDetails.length()) {
-            string details = csParent->fName;
-            details += " " + tableDescriptions.fDetails;
-            this->writeString(details);
-            this->lfAlways(1);
-        }
-    } else {
-        this->anchorDef(name, name);
-        this->lfAlways(1);
-    }
-    if (SubtopicKeys::kMembers == name) {
-        return; // members output their own table
-    }
-    const RootDefinition::SubtopicContents& tableContents = fRoot->populator(name.c_str());
-    if (SubtopicKeys::kTypedefs == name && fSubtopic && MarkType::kTopic == fSubtopic->fMarkType) {
-        topicParent = fSubtopic;
-    }
-    this->subtopicOut(name, tableContents.fMembers, csParent, topicParent,
-            tableContents.fShowClones);
-}
-
-void MdOut::subtopicOut(string key, const vector<Definition*>& data, const Definition* csParent,
-        const Definition* topicParent, bool showClones) {
-    this->writeString(kTableDeclaration);
-    this->lfAlways(1);
-    this->writeSubtopicTableHeader(key);
-    this->lfAlways(1);
-    fOddRow = true;
-    std::map<string, const Definition*> items;
-    for (auto entry : data) {
-        if (!BmhParser::IsExemplary(entry)) {
-            continue;
-        }
-        if (entry->csParent() != csParent && entry->topicParent() != topicParent) {
-            continue;
-        }
-        size_t start = entry->fName.find_last_of("::");
-        if (MarkType::kConst == entry->fMarkType && entry->fParent
-                && MarkType::kEnumClass == entry->fParent->fMarkType
-                && string::npos != start && start > 1) {
-            start = entry->fName.substr(0, start - 1).rfind("::");
-        }
-        string entryName = entry->fName.substr(string::npos == start ? 0 : start + 1);
-        items[entryName] = entry;
-    }
-    for (auto entry : items) {
-        if (!this->subtopicRowOut(entry.first, entry.second)) {
-            return;
-        }
-        if (showClones && entry.second->fCloned) {
-            int cloneNo = 2;
-            string builder = entry.second->fName;
-            if ("()" == builder.substr(builder.length() - 2)) {
-                builder = builder.substr(0, builder.length() - 2);
-            }
-            builder += '_';
-            this->rowOut("overloads", entry.second);
-            do {
-                string match = builder + to_string(cloneNo);
-                auto child = csParent->findClone(match);
-                if (!child) {
-                    break;
-                }
-                this->rowOut("", child);
-            } while (++cloneNo);
-        }
-    }
-    FPRINTF("</table>");
-    this->lf(2);
-}
-
-bool MdOut::subtopicRowOut(string keyName, const Definition* entry) {
-    const Definition* oneLiner = nullptr;
-    for (auto child : entry->fChildren) {
-        if (MarkType::kLine == child->fMarkType) {
-            oneLiner = child;
-            break;
-        }
-    }
-    if (!oneLiner) {
-        TextParser parser(entry->fFileName, entry->fStart,
-                entry->fContentStart, entry->fLineCount);
-        return parser.reportError<bool>("missing #Line");
-    }
-    TextParser dummy(entry); // for reporting errors, which we won't do
-    if (!this->isDefined(dummy, Resolvable::kOut)) {
-        keyName = entry->fName;
-        size_t doubleColon = keyName.find("::");
-        SkASSERT(string::npos != doubleColon);
-        keyName = keyName.substr(doubleColon + 2);
-    }
-    this->rowOut(keyName.c_str(), string(oneLiner->fContentStart,
-            oneLiner->fContentEnd - oneLiner->fContentStart), false);
-    return true;
-}
-
-void MdOut::writeSubtopicTableHeader(string key) {
-    this->htmlOut("<tr>");
-    this->htmlOut(kTH_Left);
-    if (fPopulators.end() != fPopulators.find(key)) {
-        this->writeString(fPopulators[key].fSingular);
-    } else {
-        this->writeString("Function");
-    }
-    this->htmlOut("</th>");
-    this->lf(1);
-    this->htmlOut(kTH_Left);
-    this->writeString("Description");
-    this->htmlOut("</th>");
-    this->htmlOut("</tr>");
-}
-
-#undef kTH_Left
diff --git a/tools/bookmaker/mdOut.h b/tools/bookmaker/mdOut.h
deleted file mode 100644
index 434c802..0000000
--- a/tools/bookmaker/mdOut.h
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef mdOut_DEFINED
-#define mdOut_DEFINED
-
-#include "parserCommon.h"
-
-class IncludeParser;
-
-class MdOut : public ParserCommon {
-public:
-    struct SubtopicDescriptions {
-        string fSingular;
-        string fPlural;
-        string fOneLiner;
-        string fDetails;
-    };
-
-    MdOut(BmhParser& bmh, IncludeParser& inc) : ParserCommon()
-        , fBmhParser(bmh)
-        , fIncludeParser(inc) {
-        this->reset();
-        this->addPopulators();
-        fBmhParser.setUpGlobalSubstitutes();
-        fNames = &fBmhParser.fGlobalNames;
-    }
-
-    bool buildReferences(const char* docDir, const char* mdOutDirOrFile);
-    bool buildStatus(const char* docDir, const char* mdOutDir);
-    void checkAnchors();
-
-private:
-    enum class TableState {
-        kNone,
-        kRow,
-        kColumn,
-    };
-
-    struct AnchorDef {
-        string fDef;
-        MarkType fMarkType;
-    };
-
-    struct DefinedState {
-        DefinedState(const MdOut& mdOut, const char* refStart, const char* refEnd,
-                Resolvable resolvable)
-            : fBmhParser(&mdOut.fBmhParser)
-            , fNames(mdOut.fNames)
-            , fGlobals(&mdOut.fBmhParser.fGlobalNames)
-            , fLastDef(mdOut.fLastDef)
-            , fMethod(mdOut.fMethod)
-            , fSubtopic(mdOut.fSubtopic)
-            , fRoot(mdOut.fRoot)
-            , fRefStart(refStart)
-            , fRefEnd(refEnd)
-            , fResolvable(resolvable)
-            , fInProgress(mdOut.fInProgress) {
-            TextParser matrixParser(fLastDef->fFileName, refStart, refEnd, fLastDef->fLineCount);
-            const char* bracket = matrixParser.anyOf("|=\n");
-            fInMatrix = bracket && ('|' == bracket[0] || '=' == bracket[0]);
-        }
-
-        void backup() {
-            fPriorWord = fBack2Word;
-            fPriorLink = "";
-            fPriorSeparator = "";
-            fSeparator = fBack2Separator;
-        }
-
-        bool findEnd(const char* start) {
-            if (fEnd < fRefEnd && '~' == fEnd[0]) {
-                ++fEnd;
-            }
-            do {
-                while (fEnd < fRefEnd && (isalnum(fEnd[0]) || '-' == fEnd[0] || '_' == fEnd[0])) {
-                    ++fEnd;
-                }
-                if (fEnd + 1 >= fRefEnd || '/' != fEnd[0] || start == fEnd || !isalpha(fEnd[-1])
-                        || !isalpha(fEnd[1])) {
-                    break;  // stop unless pattern is xxx/xxx as in I/O
-                }
-                ++fEnd; // skip slash
-            } while (true);
-            while (start != fEnd && '-' == fEnd[-1]) {
-                --fEnd;
-            }
-            return start == fEnd;
-        }
-
-        bool findLink(string ref, string* linkPtr, bool addParens);
-        bool findLink(string ref, string* linkPtr, unordered_map<string, Definition*>& map);
-        bool hasWordSpace(string wordSpace) const;
-        void setLink();
-
-        string nextSeparator(const char* start) {
-            fBack2Separator = fPriorSeparator;
-            fPriorSeparator = fSeparator;
-            fEnd = start;
-            return fBack2Separator;
-        }
-
-        const char* nextWord() {
-            fBack2Word = fPriorWord;
-            fPriorWord = fWord;
-            fPriorLink = fLink;
-            return fEnd;
-        }
-
-        bool phraseContinues(string phrase, string* priorWord, string* priorLink) const;
-
-        void setLower() {
-            fAddParens = false;
-            bool allLower = std::all_of(fWord.begin(), fWord.end(), [](char c) {
-                return islower(c);
-            });
-            bool hasParens = fEnd + 2 <= fRefEnd && "()" == string(fEnd, 2);
-            if (hasParens) {
-                if (allLower) {
-                    fWord += "()";
-                    fEnd += 2;
-                }
-            } else if (allLower) {
-                fAddParens = true;
-            }
-        }
-
-        bool setPriorSpaceWord(const char** startPtr) {
-            if (!fPriorSpace) {
-                return false;
-            }
-            string phrase = fPriorWord + fWord;
-            if (this->phraseContinues(phrase, &fPriorWord, &fPriorLink)) {
-                *startPtr = fEnd;
-                return true;
-            }
-            fPriorWord = fPriorWord.substr(0, fPriorWord.length() - 1);
-            return false;
-        }
-
-        void skipParens() {
-            if ("()" == fSeparator.substr(0, 2)) {
-                string funcRef = fPriorWord + "()";
-                if (this->findLink(funcRef, &fPriorLink, false)) {
-                    fPriorWord = funcRef;
-                    fSeparator = fSeparator.substr(2);
-                }
-            }
-        }
-
-        const char* skipWhiteSpace() {
-            const char* start = fSeparatorStart;
-            bool whiteSpace = start < fRefEnd && ' ' >= start[0];
-            while (start < fRefEnd && !isalpha(start[0]) && '~' != start[0]) {
-                whiteSpace &= ' ' >= start[0];
-                ++start;
-            }
-            fPriorSpace = false;
-            fSeparator = string(fSeparatorStart, start - fSeparatorStart);
-            if ("" != fPriorWord && whiteSpace) {
-                string wordSpace = fPriorWord + ' ';
-                if (this->hasWordSpace(wordSpace)) {
-                    fPriorWord = wordSpace;
-                    fPriorSpace = true;
-                }
-            }
-            return start;
-        }
-
-        string fRef;
-        string fBack2Word;
-        string fBack2Separator;
-        string fPriorWord;
-        string fPriorLink;
-        string fPriorSeparator;
-        string fWord;
-        string fLink;
-        string fSeparator;
-        string fMethodName;
-        BmhParser* fBmhParser;
-        const NameMap* fNames;
-        const NameMap* fGlobals;
-        const Definition* fLastDef;
-        const Definition* fMethod;
-        const Definition* fSubtopic;
-        const Definition* fPriorDef;
-        const RootDefinition* fRoot;
-        const char* fSeparatorStart;
-        const char* fRefStart;
-        const char* fRefEnd;
-        const char* fEnd;
-        Resolvable fResolvable;
-        bool fAddParens;
-        bool fInMatrix;
-        bool fInProgress;
-        bool fPriorSpace;
-    };
-
-    void addCodeBlock(const Definition* def, string& str) const;
-    void addPopulators();
-    string addReferences(const char* start, const char* end, Resolvable );
-    string anchorDef(string def, string name);
-    string anchorLocalRef(string ref, string name);
-    string anchorRef(string def, string name);
-    bool buildRefFromFile(const char* fileName, const char* outDir);
-    bool checkParamReturnBody(const Definition* def);
-    Definition* checkParentsForMatch(Definition* test, string ref) const;
-    void childrenOut(Definition* def, const char* contentStart);
-    Definition* csParent();
-    const Definition* findParamType();
-    string getMemberTypeName(const Definition* def, string* memberType);
-    static bool HasDetails(const Definition* def);
-    void htmlOut(string );
-    bool isDefined(DefinedState& s);
-    const Definition* isDefined(const TextParser& , Resolvable );
-    string linkName(const Definition* ) const;
-    void markTypeOut(Definition* , const Definition** prior);
-    void mdHeaderOut(int depth) { mdHeaderOutLF(depth, 2); }
-    void mdHeaderOutLF(int depth, int lf);
-    void parameterHeaderOut(TextParser& paramParser, const Definition** prior, Definition* def);
-    void parameterTrailerOut();
-    bool parseFromFile(const char* path) override { return true; }
-    void populateOne(Definition* def,
-            unordered_map<string, RootDefinition::SubtopicContents>& populator);
-    void populateTables(const Definition* def, RootDefinition* );
-
-    SubtopicDescriptions& populator(string key) {
-        auto entry = fPopulators.find(key);
-        // FIXME: this should have been detected earlier
-        SkASSERT(fPopulators.end() != entry);
-        return entry->second;
-    }
-
-    void reset() override {
-        INHERITED::resetCommon();
-        fNames = nullptr;
-        fEnumClass = nullptr;
-        fMethod = nullptr;
-        fRoot = nullptr;
-        fSubtopic = nullptr;
-        fLastParam = nullptr;
-        fTableState = TableState::kNone;
-        fAddRefFailed = false;
-        fHasFiddle = false;
-        fInDescription = false;
-        fInList = false;
-        fResolveAndIndent = false;
-        fLiteralAndIndent = false;
-        fLastDef = nullptr;
-        fParamEnd = nullptr;
-        fInProgress = false;
-    }
-
-    Resolvable resolvable(const Definition* definition) const {
-        MarkType markType = definition->fMarkType;
-        if (MarkType::kCode == markType) {
-            for (auto child : definition->fChildren) {
-                if (MarkType::kLiteral == child->fMarkType) {
-                    return Resolvable::kLiteral;
-                }
-            }
-        }
-        if ((MarkType::kExample == markType
-                || MarkType::kFunction == markType) && fHasFiddle) {
-            return Resolvable::kNo;
-        }
-        return BmhParser::kMarkProps[(int) markType].fResolve;
-    }
-
-    void resolveOut(const char* start, const char* end, Resolvable );
-    void returnHeaderOut(const Definition** prior, Definition* def);
-    void rowOut(string col1, const Definition* col2);
-    void rowOut(const char * name, string description, bool literalName);
-
-    void subtopicOut(string name);
-    void subtopicsOut(Definition* def);
-    void subtopicOut(string key, const vector<Definition*>& data, const Definition* csParent,
-        const Definition* topicParent, bool showClones);
-    bool subtopicRowOut(string keyName, const Definition* entry);
-    void summaryOut(const Definition* def, MarkType , string name);
-    string tableDataCodeDef(const Definition* def);
-    string tableDataCodeDef(string def, string name);
-    string tableDataCodeLocalRef(string name);
-    string tableDataCodeLocalRef(string ref, string name);
-    string tableDataCodeRef(const Definition* ref);
-    string tableDataCodeRef(string ref, string name);
-    void writeSubtopicTableHeader(string key);
-
-    vector<const Definition*> fClassStack;
-    unordered_map<string, vector<AnchorDef> > fAllAnchorDefs;
-    unordered_map<string, vector<string> > fAllAnchorRefs;
-    NameMap* fNames;
-    BmhParser& fBmhParser;
-    IncludeParser& fIncludeParser;
-    const Definition* fEnumClass;
-    const Definition* fLastDef;
-    Definition* fMethod;
-    RootDefinition* fRoot;  // used in generating populated tables; always struct or class
-    RootDefinition* fSubtopic; // used in resolving symbols
-    const Definition* fLastParam;
-    TableState fTableState;
-    unordered_map<string, SubtopicDescriptions> fPopulators;
-    unordered_map<string, string> fPhraseParams;
-    const char* fParamEnd;
-    bool fAddRefFailed;
-    bool fHasFiddle;
-    bool fInDescription;   // FIXME: for now, ignore unfound camelCase in description since it may
-                           // be defined in example which at present cannot be linked to
-    bool fInList;
-    bool fLiteralAndIndent;
-    bool fResolveAndIndent;
-    bool fOddRow;
-    bool fHasDetails;
-    bool fInProgress;
-    typedef ParserCommon INHERITED;
-};
-
-#endif
diff --git a/tools/bookmaker/parserCommon.cpp b/tools/bookmaker/parserCommon.cpp
deleted file mode 100644
index 5a8867a..0000000
--- a/tools/bookmaker/parserCommon.cpp
+++ /dev/null
@@ -1,509 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkOSFile.h"
-#include "SkOSPath.h"
-
-#include "parserCommon.h"
-
-void ParserCommon::checkLineLength(size_t len, const char* str) {
-    if (!fWritingIncludes) {
-        return;
-    }
-    int column = fColumn;
-    const char* lineStart = str;
-    for (size_t index = 0; index < len; ++index) {
-        if ('\n' == str[index]) {
-            if (column > 100) {
-                SkDebugf("> 100 columns in %s line %d\n", fFileName.c_str(), fLinesWritten);
-                SkDebugf("%.*s\n", &str[index + 1] - lineStart, lineStart);
-                SkDebugf("");  // convenient place to set breakpoints
-            }
-            fLinesWritten++;
-            column = 0;
-            lineStart = &str[index + 1];
-        } else {
-            column++;
-        }
-    }
-}
-
-// change Xxx_Xxx to xxx xxx
-string ParserCommon::ConvertRef(const string str, bool first) {
-    string substitute;
-    for (char c : str) {
-        if ('_' == c) {
-            c = ' ';
-        } else if (isupper(c) && !first) {
-            c = tolower(c);
-        }
-        substitute += c;
-        first = false;
-    }
-    return substitute;
-}
-
-void ParserCommon::CopyToFile(string oldFile, string newFile) {
-    int bufferSize;
-    char* buffer = ParserCommon::ReadToBuffer(newFile, &bufferSize);
-    FILE* oldOut = fopen(oldFile.c_str(), "wb");
-    if (!oldOut) {
-        SkDebugf("could not open file %s\n", oldFile.c_str());
-        return;
-    }
-    fwrite(buffer, 1, bufferSize, oldOut);
-    fclose(oldOut);
-    remove(newFile.c_str());
-}
-
-string ParserCommon::HtmlFileName(string bmhFileName) {
-    SkASSERT("docs" == bmhFileName.substr(0, 4));
-    SkASSERT('\\' == bmhFileName[4] || '/' == bmhFileName[4]);
-    SkASSERT(".bmh" == bmhFileName.substr(bmhFileName.length() - 4));
-    string result = bmhFileName.substr(5, bmhFileName.length() - 4 - 5);
-    return result;
-}
-
-bool ParserCommon::parseFile(const char* fileOrPath, const char* suffix, OneFile oneFile) {
-    fRawFilePathDir = string(fileOrPath);
-    if (!sk_isdir(fileOrPath)) {
-        if (!this->parseFromFile(fileOrPath)) {
-            SkDebugf("failed to parse %s\n", fileOrPath);
-            return false;
-        }
-    } else if (OneFile::kNo == oneFile) {
-        SkOSFile::Iter it(fileOrPath, suffix);
-        for (SkString file; it.next(&file); ) {
-            // FIXME: skip difficult file for now
-            if (string::npos != string(file.c_str()).find("SkFontArguments")) {
-                continue;
-            }
-            if (string::npos != string(file.c_str()).find("SkFontStyle")) {
-                continue;
-            }
-            SkString p = SkOSPath::Join(fileOrPath, file.c_str());
-            const char* hunk = p.c_str();
-            if (!SkStrEndsWith(hunk, suffix)) {
-                continue;
-            }
-            if (!this->parseFromFile(hunk)) {
-                SkDebugf("failed to parse %s\n", hunk);
-                return false;
-            }
-        }
-    }
-    return true;
-}
-
-bool ParserCommon::parseStatus(const char* statusFile, const char* suffix, StatusFilter filter) {
-    fRawFilePathDir = string(statusFile);
-    StatusIter iter(statusFile, suffix, filter);
-    if (iter.empty()) {
-        return false;
-    }
-    for (string file; iter.next(&file, nullptr); ) {
-        SkString p = SkOSPath::Join(iter.baseDir().c_str(), file.c_str());
-        const char* hunk = p.c_str();
-        if (!this->parseFromFile(hunk)) {
-            SkDebugf("failed to parse %s\n", hunk);
-            return false;
-        }
-    }
-    return true;
-}
-
-bool ParserCommon::parseSetup(const char* path) {
-    sk_sp<SkData> data = SkData::MakeFromFileName(path);
-    if (nullptr == data.get()) {
-        SkDebugf("%s missing\n", path);
-        return false;
-    }
-    const char* rawText = (const char*) data->data();
-    bool hasCR = false;
-    size_t dataSize = data->size();
-    for (size_t index = 0; index < dataSize; ++index) {
-        if ('\r' == rawText[index]) {
-            hasCR = true;
-            break;
-        }
-    }
-    string name(path);
-    if (hasCR) {
-        vector<char> lfOnly;
-        for (size_t index = 0; index < dataSize; ++index) {
-            char ch = rawText[index];
-            if ('\r' == rawText[index]) {
-                ch = '\n';
-                if ('\n' == rawText[index + 1]) {
-                    ++index;
-                }
-            }
-            lfOnly.push_back(ch);
-        }
-        fLFOnly[name] = lfOnly;
-        dataSize = lfOnly.size();
-        rawText = &fLFOnly[name].front();
-    }
-    fRawData[name] = data;
-    fStart = rawText;
-    fLine = rawText;
-    fChar = rawText;
-    fEnd = rawText + dataSize;
-    fFileName = string(path);
-    fLineCount = 1;
-    return true;
-}
-
-void ParserCommon::stringAppend(string& result, char ch) const {
-    if (fDebugWriteCodeBlock) {
-        SkDebugf("%c", ch);
-    }
-    result += ch;
-}
-
-void ParserCommon::stringAppend(string& result, string str) const {
-    string condense;
-    char last = result.size() ? result.back() : '\n';
-    for (auto c : str) {
-        if (' ' == c && ' ' == last) {
-            continue;
-        }
-        condense += c;
-        if ('\n' != last || ' ' != c) {
-            last = c;
-        }
-    }
-    if (fDebugWriteCodeBlock) {
-        SkDebugf("%s", condense.c_str());
-    }
-    result += condense;
-}
-
-void ParserCommon::stringAppend(string& result, const Definition* def) const {
-    this->stringAppend(result, string(def->fContentStart, def->length()));
-}
-
-bool ParserCommon::writeBlockIndent(int size, const char* data, bool ignoreIdent) {
-    bool wroteSomething = false;
-    while (size && ' ' >= data[size - 1]) {
-        --size;
-    }
-    bool newLine = false;
-    char firstCh = 0;
-    while (size) {
-        while (size && (' ' > data[0] || (' ' == data[0] && ignoreIdent))) {
-            ++data;
-            --size;
-        }
-        if (!size) {
-            return wroteSomething;
-        }
-        if (fReturnOnWrite) {
-            return true;
-        }
-        if (newLine) {
-            this->lf(1);
-        }
-        int indent = fIndent;
-        if (!firstCh) {
-            firstCh = data[0];
-        } else if ('(' == firstCh) {
-            indent += 1;
-        }
-        TextParser parser(fFileName, data, data + size, fLineCount);
-        const char* lineEnd = parser.strnchr('\n', data + size);
-        int len = lineEnd ? (int) (lineEnd - data) : size;
-        this->writePending();
-        this->indentToColumn(indent);
-        FPRINTF("%.*s", len, data);
-        checkLineLength(len, data);
-        size -= len;
-        data += len;
-        newLine = true;
-        wroteSomething = true;
-    }
-    return wroteSomething;
-}
-
-bool ParserCommon::writeBlockTrim(int size, const char* data) {
-    SkASSERT(size >= 0);
-    if (!fReturnOnWrite && fOutdentNext) {
-        fIndent -= 4;
-        fOutdentNext = false;
-    }
-    while (size && ' ' >= data[0]) {
-        ++data;
-        --size;
-    }
-    while (size && ' ' >= data[size - 1]) {
-        --size;
-    }
-    if (size <= 0) {
-        if (!fReturnOnWrite) {
-            fLastChar = '\0';
-        }
-        return false;
-    }
-    if (fReturnOnWrite) {
-        return true;
-    }
-    SkASSERT(size < 20000);
-    if (size > 3 && !strncmp("#end", data, 4)) {
-        fMaxLF = 1;
-    }
-    if (this->leadingPunctuation(data, (size_t) size)) {
-        fPendingSpace = 0;
-    }
-    this->writePending();
-    FPRINTF("%.*s", size, data);
-    checkLineLength(size, data);
-    fWroteSomething = true;
-    int added = 0;
-    fLastChar = data[size - 1];
-    while (size > 0 && '\n' != data[--size]) {
-        ++added;
-    }
-    fColumn = size ? added : fColumn + added;
-    fSpaces = 0;
-    fLinefeeds = 0;
-    fMaxLF = added > 2 && !strncmp("#if", &data[size + (size > 0)], 3) ? 1 : 2;
-    if (fOutdentNext) {
-        fIndent -= 4;
-        fOutdentNext = false;
-    }
-    return true;
-}
-
-void ParserCommon::writePending() {
-    SkASSERT(!fReturnOnWrite);
-    fPendingLF = SkTMin(fPendingLF, fMaxLF);
-    bool wroteLF = false;
-    while (fLinefeeds < fPendingLF) {
-        if (fDebugOut) {
-            SkDebugf("\n");
-        }
-        fprintf(fOut, "\n");
-        checkLineLength(1, "\n");
-        ++fLinefeeds;
-        wroteLF = true;
-    }
-    fPendingLF = 0;
-    if (wroteLF) {
-        SkASSERT(0 == fColumn);
-        SkASSERT(fIndent >= fSpaces);
-        SkASSERT(fIndent - fSpaces < 80);
-        if (fDebugOut) {
-            SkDebugf("%*s", fIndent - fSpaces, "");
-        }
-        fprintf(fOut, "%*s", fIndent - fSpaces, "");
-        fColumn = fIndent;
-        fSpaces = fIndent;
-    }
-    SkASSERT(!fWritingIncludes || fColumn + fPendingSpace < 100);
-    for (int index = 0; index < fPendingSpace; ++index) {
-        if (fDebugOut) {
-            SkDebugf(" ");
-        }
-        fprintf(fOut, " ");
-        ++fColumn;
-    }
-    fPendingSpace = 0;
-}
-
-void ParserCommon::writeString(const char* str) {
-    SkASSERT(!fReturnOnWrite);
-    const size_t len = strlen(str);
-    SkASSERT(len > 0);
-    SkASSERT(' ' < str[0]);
-    fLastChar = str[len - 1];
-    SkASSERT(' ' < fLastChar);
-    SkASSERT(!strchr(str, '\n'));
-    if (this->leadingPunctuation(str, strlen(str))) {
-        fPendingSpace = 0;
-    }
-    this->writePending();
-    FPRINTF("%s", str);
-    checkLineLength(strlen(str), str);
-    fColumn += len;
-    fSpaces = 0;
-    fLinefeeds = 0;
-    fMaxLF = 2;
-}
-
-char* ParserCommon::ReadToBuffer(string filename, int* size) {
-    FILE* file = fopen(filename.c_str(), "rb");
-    if (!file) {
-        return nullptr;
-    }
-    fseek(file, 0L, SEEK_END);
-    *size = (int) ftell(file);
-    rewind(file);
-    char* buffer = new char[*size];
-    memset(buffer, ' ', *size);
-    SkAssertResult(*size == (int)fread(buffer, 1, *size, file));
-    fclose(file);
-    fflush(file);
-    return buffer;
-}
-
-char* ParserCommon::FindDateTime(char* buffer, int size) {
-    int index = -1;
-    int lineCount = 8;
-    while (++index < size && ('\n' != buffer[index] || --lineCount))
-        ;
-    if (lineCount) {
-        return nullptr;
-    }
-    if (strncmp("\n   on 20", &buffer[index], 9)) {
-        return nullptr;
-    }
-    return &buffer[index];
-}
-
-bool ParserCommon::WrittenFileDiffers(string filename, string readname) {
-    int writtenSize, readSize;
-    char* written = ParserCommon::ReadToBuffer(filename, &writtenSize);
-    if (!written) {
-        return true;
-    }
-    char* read = ParserCommon::ReadToBuffer(readname, &readSize);
-    if (!read) {
-        delete[] written;
-        return true;
-    }
-#if 0  // enable for debugging this
-    int smaller = SkTMin(writtenSize, readSize);
-    for (int index = 0; index < smaller; ++index) {
-        if (written[index] != read[index]) {
-            SkDebugf("%.*s\n", 40, &written[index]);
-            SkDebugf("%.*s\n", 40, &read[index]);
-            break;
-        }
-    }
-#endif
-    if (readSize != writtenSize) {
-        return true;
-    }
-    // force the date/time to be the same, if present in both
-    const char* newDateTime = FindDateTime(written, writtenSize);
-    char* oldDateTime = FindDateTime(read, readSize);
-    if (newDateTime && oldDateTime) {
-        memcpy(oldDateTime, newDateTime, 26);
-    }
-    bool result = !!memcmp(written, read, writtenSize);
-    delete[] written;
-    delete[] read;
-    return result;
-}
-
-StatusIter::StatusIter(const char* statusFile, const char* suffix, StatusFilter filter)
-    : fSuffix(suffix)
-    , fFilter(filter) {
-    if (!this->parseFromFile(statusFile)) {
-        return;
-    }
-}
-
-static const char* block_names[] = {
-    "Completed",
-    "InProgress",
-};
-
-string StatusIter::baseDir() {
-    SkASSERT(fStack.back().fArray);
-    SkASSERT(fStack.size() > 2);
-    string dir;
-    for (unsigned index = 2; index < fStack.size(); ++index) {
-        dir += fStack[index].fName;
-        if (index < fStack.size() - 1) {
-            dir += SkOSPath::SEPARATOR;
-        }
-    }
-    return dir;
-}
-
-// FIXME: need to compare fBlockName against fFilter
-// need to compare fSuffix against next value returned
-bool StatusIter::next(string* strPtr, StatusFilter *filter) {
-    string str;
-    JsonStatus* status;
-    StatusFilter blockType = StatusFilter::kCompleted;
-    do {
-        do {
-            if (fStack.empty()) {
-                return false;
-            }
-            status = &fStack.back();
-            if (!status->atEnd()) {
-                break;
-            }
-            fStack.pop_back();
-        } while (true);
-        if (1 == fStack.size()) {
-            do {
-                blockType = StatusFilter::kUnknown;
-                SkASSERT(status->fObject);
-                for (unsigned index = 0; index < SK_ARRAY_COUNT(block_names); ++index) {
-                    if (!strcmp(status->fObjectIter->fKey.begin(), block_names[index])) {
-                        blockType = (StatusFilter) index;
-                        break;
-                    }
-                }
-                if (blockType <= fFilter) {
-                    break;
-                }
-                status->advance();
-            } while (!status->atEnd());
-            if (status->atEnd()) {
-                continue;
-            }
-        }
-        if (!status->fArray) {
-            SkASSERT(status->fObjectIter != status->fObject->end());
-            JsonStatus block = JsonStatus::Make(status->current(),
-                                                status->fObjectIter->fKey.begin(),
-                                                blockType);
-            fStack.emplace_back(block);
-            status = &(&fStack.back())[-1];
-            status->advance();
-            status = &fStack.back();
-            continue;
-        }
-        if (const skjson::StringValue* sv = status->current()) {
-            str = sv->begin();
-        } else {
-            str = status->current().toString().c_str();
-        }
-        if (strPtr) {
-            *strPtr = str;
-        }
-        if (filter) {
-            *filter = status->fStatusFilter;
-        }
-        status->advance();
-        if (str.length() - strlen(fSuffix) == str.find(fSuffix)) {
-            return true;
-        }
-    } while (true);
-    return true;
-}
-
-bool JsonCommon::parseFromFile(const char* path) {
-    sk_sp<SkData> json(SkData::MakeFromFileName(path));
-    if (!json) {
-        SkDebugf("file %s:\n", path);
-        return this->reportError<bool>("file not readable");
-    }
-    fDom.reset(new skjson::DOM((const char*)json->data(), json->size()));
-    if (!fDom->root().is<skjson::ObjectValue>()) {
-        SkDebugf("file %s:\n", path);
-        return this->reportError<bool>("file not parsable");
-    }
-    JsonStatus block = JsonStatus::Make(fDom->root(), "", StatusFilter::kUnknown);
-    fStack.emplace_back(block);
-    return true;
-}
diff --git a/tools/bookmaker/parserCommon.h b/tools/bookmaker/parserCommon.h
deleted file mode 100644
index ecf5c2f..0000000
--- a/tools/bookmaker/parserCommon.h
+++ /dev/null
@@ -1,361 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef parserCommon_DEFINED
-#define parserCommon_DEFINED
-
-#include "SkData.h"
-#include "SkJSON.h"
-
-#include "definition.h"
-#include "textParser.h"
-
-#include <memory>
-
-enum class StatusFilter {
-    kCompleted,
-    kInProgress,
-    kUnknown,
-};
-
-class ParserCommon : public TextParser {
-public:
-    enum class OneFile {
-        kNo,
-        kYes,
-    };
-
-    enum class OneLine {
-        kNo,
-        kYes,
-    };
-
-    enum class IndentKind {
-        kConstOut,
-        kEnumChild,
-        kEnumChild2,
-        kEnumHeader,
-        kEnumHeader2,
-        kMethodOut,
-        kStructMember,
-    };
-
-    struct IndentState {
-        IndentState(IndentKind kind, int indent)
-            : fKind(kind)
-            , fIndent(indent) {
-        }
-
-        IndentKind fKind;
-        int fIndent;
-    };
-
-    ParserCommon() : TextParser()
-        , fParent(nullptr)
-        , fDebugOut(false)
-        , fValidate(false)
-        , fReturnOnWrite(false)
-    {
-    }
-
-    ~ParserCommon() override {
-    }
-
-    void addDefinition(Definition* def) {
-        fParent->fChildren.push_back(def);
-        fParent = def;
-    }
-
-    void checkLineLength(size_t len, const char* str);
-    static string ConvertRef(const string str, bool first);
-    static void CopyToFile(string oldFile, string newFile);
-    static char* FindDateTime(char* buffer, int size);
-    static string HtmlFileName(string bmhFileName);
-
-    void indentIn(IndentKind kind) {
-        fIndent += 4;
-        fIndentStack.emplace_back(kind, fIndent);
-    }
-
-    void indentOut() {
-        SkASSERT(fIndent >= 4);
-        SkASSERT(fIndentStack.back().fIndent == fIndent);
-        fIndent -= 4;
-        fIndentStack.pop_back();
-    }
-
-    void indentToColumn(int column) {
-        SkASSERT(column >= fColumn);
-        SkASSERT(!fReturnOnWrite);
-        SkASSERT(column < 80);
-        FPRINTF("%*s", column - fColumn, "");
-        fColumn = column;
-        fSpaces += column - fColumn;
-    }
-
-    bool leadingPunctuation(const char* str, size_t len) const {
-        if (!fPendingSpace) {
-            return false;
-        }
-        if (len < 2) {
-            return false;
-        }
-        if ('.' != str[0] && ',' != str[0] && ';' != str[0] && ':' != str[0]) {
-            return false;
-        }
-        return ' ' >= str[1];
-    }
-
-    void lf(int count) {
-        fPendingLF = SkTMax(fPendingLF, count);
-        this->nl();
-    }
-
-    void lfAlways(int count) {
-        this->lf(count);
-        this->writePending();
-    }
-
-    void lfcr() {
-        this->lf(1);
-    }
-
-    void nl() {
-        SkASSERT(!fReturnOnWrite);
-        fLinefeeds = 0;
-        fSpaces = 0;
-        fColumn = 0;
-        fPendingSpace = 0;
-    }
-
-    bool parseFile(const char* file, const char* suffix, OneFile );
-    bool parseStatus(const char* file, const char* suffix, StatusFilter filter);
-    virtual bool parseFromFile(const char* path) = 0;
-    bool parseSetup(const char* path);
-
-    void popObject() {
-        fParent->fContentEnd = fParent->fTerminator = fChar;
-        fParent = fParent->fParent;
-    }
-
-    static char* ReadToBuffer(string filename, int* size);
-
-    virtual void reset() = 0;
-
-    void resetCommon() {
-        fLine = fChar = fStart;
-        fLineCount = 0;
-        fLinesWritten = 1;
-        fParent = nullptr;
-        fIndent = 0;
-        fOut = nullptr;
-        fMaxLF = 2;
-        fPendingLF = 0;
-        fPendingSpace = 0;
-        fOutdentNext = false;
-        fWritingIncludes = false;
-        fDebugWriteCodeBlock = false;
-        nl();
-   }
-
-    void setAsParent(Definition* definition) {
-        if (fParent) {
-            fParent->fChildren.push_back(definition);
-            definition->fParent = fParent;
-        }
-        fParent = definition;
-    }
-
-    void singleLF() {
-        fMaxLF = 1;
-    }
-
-    void stringAppend(string& result, char ch) const;
-    void stringAppend(string& result, string str) const;
-    void stringAppend(string& result, const Definition* ) const;
-
-    void writeBlock(int size, const char* data) {
-        SkAssertResult(writeBlockTrim(size, data));
-    }
-
-    bool writeBlockIndent(int size, const char* data, bool ignoreIndent);
-
-    void writeBlockSeparator() {
-            this->writeString(
-              "# ------------------------------------------------------------------------------");
-            this->lf(2);
-    }
-
-    bool writeBlockTrim(int size, const char* data);
-
-    void writeCommentHeader() {
-        this->lf(2);
-        this->writeString("/**");
-        this->writeSpace();
-    }
-
-    void writeCommentTrailer(OneLine oneLine) {
-        if (OneLine::kNo == oneLine) {
-            this->lf(1);
-        } else {
-            this->writeSpace();
-        }
-        this->writeString("*/");
-        this->lfcr();
-    }
-
-    void writePending();
-
-    // write a pending space, so that two consecutive calls
-    // don't double write, and trailing spaces on lines aren't written
-    void writeSpace(int count = 1) {
-        SkASSERT(!fReturnOnWrite);
-        SkASSERT(!fPendingLF);
-        SkASSERT(!fLinefeeds);
-        SkASSERT(fColumn > 0);
-        SkASSERT(!fSpaces);
-        fPendingSpace = count;
-    }
-
-    void writeString(const char* str);
-
-    void writeString(string str) {
-        this->writeString(str.c_str());
-    }
-
-    static bool WrittenFileDiffers(string filename, string readname);
-
-    unordered_map<string, sk_sp<SkData>> fRawData;
-    unordered_map<string, vector<char>> fLFOnly;
-    vector<IndentState> fIndentStack;
-    Definition* fParent;
-    FILE* fOut;
-    string fRawFilePathDir;
-    int fLinefeeds;    // number of linefeeds last written, zeroed on non-space
-    int fMaxLF;        // number of linefeeds allowed
-    int fPendingLF;    // number of linefeeds to write (can be suppressed)
-    int fSpaces;       // number of spaces (indent) last written, zeroed on non-space
-    int fColumn;       // current column; number of chars past last linefeed
-    int fIndent;       // desired indention
-    int fPendingSpace; // one or two spaces should preceed the next string or block
-    size_t fLinesWritten; // as opposed to fLineCount, number of lines read
-    char fLastChar;    // last written
-    bool fDebugOut;    // set true to write to std out
-    bool fValidate;    // set true to check anchor defs and refs
-    bool fOutdentNext; // set at end of embedded struct to prevent premature outdent
-    bool fWroteSomething; // used to detect empty content; an alternative source is preferable
-    bool fReturnOnWrite; // used to detect non-empty content; allowing early return
-    bool fWritingIncludes; // set true when writing includes to check >100 columns
-    mutable bool fDebugWriteCodeBlock;
-
-private:
-    typedef TextParser INHERITED;
-};
-
-struct JsonStatus {
-    const skjson::ArrayValue* fArray;
-    const skjson::Value* fArrayIter;
-    const skjson::ObjectValue* fObject;
-    const skjson::Member* fObjectIter;
-    string fName;
-    StatusFilter fStatusFilter;
-
-    static JsonStatus Make(const skjson::Value& value, string name, StatusFilter filter) {
-        JsonStatus status = { value, nullptr, value, nullptr, name, filter };
-        status.reset();
-        return status;
-    }
-
-    void reset() {
-        fArrayIter = fArray ? fArray->begin() : nullptr;
-        fObjectIter = fObject ? fObject->begin() : nullptr;
-    }
-
-    bool atEnd() const {
-        if (fArray) {
-            return fArrayIter == fArray->end();
-        } else if (fObject) {
-            return fObjectIter == fObject->end();
-        } else {
-            return true;
-        }
-    }
-
-    void advance() {
-        if (fArrayIter) {
-            fArrayIter++;
-        } else if (fObjectIter) {
-            fObjectIter++;
-        }
-    }
-
-    const skjson::Value& current() const {
-        if (fArrayIter) {
-            return *fArrayIter;
-        } else {
-            SkASSERT(fObjectIter);
-            return fObjectIter->fValue;
-        }
-    }
-};
-
-class JsonCommon : public ParserCommon {
-public:
-    bool empty() { return fStack.empty(); }
-    bool parseFromFile(const char* path) override;
-
-    void reset() override {
-        fStack.clear();
-        INHERITED::resetCommon();
-    }
-
-    vector<JsonStatus> fStack;
-    std::unique_ptr<skjson::DOM> fDom;
-private:
-    typedef ParserCommon INHERITED;
-};
-
-class StatusIter : public JsonCommon {
-public:
-    StatusIter(const char* statusFile, const char* suffix, StatusFilter);
-    ~StatusIter() override {}
-    string baseDir();
-    bool next(string* file, StatusFilter* filter);
-private:
-    const char* fSuffix;
-    StatusFilter fFilter;
-};
-
-class HackParser : public ParserCommon {
-public:
-    HackParser(const BmhParser& bmhParser)
-        : ParserCommon()
-        , fBmhParser(bmhParser) {
-        this->reset();
-    }
-
-    bool parseFromFile(const char* path) override {
-        if (!INHERITED::parseSetup(path)) {
-            return false;
-        }
-        return hackFiles();
-    }
-
-    void reset() override {
-        INHERITED::resetCommon();
-    }
-
-    void replaceWithPop(const Definition* );
-
-private:
-    const BmhParser& fBmhParser;
-    bool hackFiles();
-
-    typedef ParserCommon INHERITED;
-};
-
-#endif
diff --git a/tools/bookmaker/selfCheck.cpp b/tools/bookmaker/selfCheck.cpp
deleted file mode 100644
index 341b9e3..0000000
--- a/tools/bookmaker/selfCheck.cpp
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "bmhParser.h"
-#include "selfCheck.h"
-
-#ifdef SK_BUILD_FOR_WIN
-#include <windows.h>
-#endif
-
-
- /* SkDebugf works in both visual studio and git shell, but
- in git shell output is not piped to grep.
- printf does not generate output in visual studio, but
- does in git shell and can be piped.
- */
-#ifdef SK_BUILD_FOR_WIN
-#define PRINTF(...)                 \
-do {                                \
-    if (IsDebuggerPresent()) {      \
-        SkDebugf(__VA_ARGS__);      \
-    } else {                        \
-        printf(__VA_ARGS__);        \
-    }                               \
-} while (false)
-#else
-#define PRINTF(...)                 \
-        printf(__VA_ARGS__)
-#endif
-
-
-// Check that mutiple like-named methods are under one Subtopic
-
-// Check that SeeAlso reference each other
-
-// Would be nice to check if other classes have 'create' methods that are included
-//          SkSurface::makeImageSnapShot should be referenced under SkImage 'creators'
-
-class SelfChecker {
-public:
-    SelfChecker(const BmhParser& bmh)
-        : fBmhParser(bmh)
-        {}
-
-    bool check() {
-        for (const auto& topic : fBmhParser.fTopicMap) {
-            Definition* topicDef = topic.second;
-            if (topicDef->fParent) {
-                continue;
-            }
-            if (!topicDef->isRoot()) {
-                return fBmhParser.reportError<bool>("expected root topic");
-            }
-            fRoot = topicDef->asRoot();
-            if (!this->checkSeeAlso()) {
-                return false;
-            }
-            // report functions that are not covered by related hierarchy
-			if (!this->checkRelatedFunctions()) {
-				return false;
-			}
-        }
-        return true;
-    }
-
-protected:
-
-    void checkMethod(string topic, const Definition* csChild, vector<string>* reported) {
-        if (MarkType::kSubtopic == csChild->fMarkType) {
-            for (auto child : csChild->fChildren) {
-                checkMethod(topic, child, reported);
-            }
-            return;
-        } else if (MarkType::kMethod != csChild->fMarkType) {
-            // only check methods for now
-            return;
-        }
-        bool containsMarkTypeIn =
-                   Definition::MethodType::kConstructor == csChild->fMethodType
-                || Definition::MethodType::kDestructor == csChild->fMethodType
-                || Definition::MethodType::kOperator == csChild->fMethodType
-                || csChild->fClone;
-        for (auto child : csChild->fChildren) {
-            if (MarkType::kIn == child->fMarkType) {
-                containsMarkTypeIn = true;
-                string subtopic(child->fContentStart,
-                    child->fContentEnd - child->fContentStart);
-                string fullname = topic + '_' + subtopic;
-                auto topEnd = fBmhParser.fTopicMap.end();
-                auto topFind = fBmhParser.fTopicMap.find(fullname);
-                auto reportEnd = reported->end();
-                auto reportFind = std::find(reported->begin(), reported->end(), subtopic);
-                if (topEnd == topFind) {
-                    if (reportEnd == reportFind) {
-                        reported->push_back(subtopic);
-                    }
-                }
-            }
-        }
-        if (!containsMarkTypeIn) {
-            PRINTF("No #In: %s\n", csChild->fName.c_str());
-        }
-    }
-
-	bool checkRelatedFunctions() {
-		const Definition* cs = this->classOrStruct();
-        if (!cs) {
-            return true;
-        }
-        const Definition* topic = cs->fParent;
-        SkASSERT(topic);
-        SkASSERT(MarkType::kTopic == topic->fMarkType);
-        string topicName = topic->fName;
-        vector<string> methodNames;
-        vector<string> reported;
-		string prefix = cs->fName + "::";
-		for (auto& csChild : cs->fChildren) {
-            checkMethod(topicName, csChild, &reported);
-		}
-        for (auto missing : reported) {
-            string fullname = topicName + '_' + missing;
-            PRINTF("No #Subtopic: %s\n", fullname.c_str());
-        }
-		return true;
-	}
-
-    bool checkSeeAlso() {
-        return true;
-    }
-
-	const Definition* classOrStruct() {
-		for (auto& rootChild : fRoot->fChildren) {
-			if (rootChild->isStructOrClass()) {
-				return rootChild;
-			}
-		}
-		return nullptr;
-	}
-
-	enum class Optional {
-		kNo,
-		kYes,
-	};
-
-private:
-    const BmhParser& fBmhParser;
-    RootDefinition* fRoot;
-};
-
-bool SelfCheck(const BmhParser& bmh) {
-    SelfChecker checker(bmh);
-    return checker.check();
-}
diff --git a/tools/bookmaker/selfCheck.h b/tools/bookmaker/selfCheck.h
deleted file mode 100644
index 6e7bcf0..0000000
--- a/tools/bookmaker/selfCheck.h
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef selfCheck_DEFINED
-#define selfCheck_DEFINED
-
-class BmhParser;
-
-bool SelfCheck(const BmhParser& );
-
-#endif
diff --git a/tools/bookmaker/spellCheck.cpp b/tools/bookmaker/spellCheck.cpp
deleted file mode 100644
index c8de4a3..0000000
--- a/tools/bookmaker/spellCheck.cpp
+++ /dev/null
@@ -1,682 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "bmhParser.h"
-
-#include "SkCommandLineFlags.h"
-#include "SkOSFile.h"
-#include "SkOSPath.h"
-
-/*
-things to do
-if cap word is beginning of sentence, add it to table as lower-case
-   word must have only a single initial capital
-
-if word is camel cased, look for :: matches on suffix
-
-when function crosses lines, whole thing isn't seen as a 'word' e.g., search for largeArc in path
-
-words in external not seen
-
-look for x-bit but allow x bits
-
-don't treat 'pos' or 'glyphs' as spell-checkable as in 'RunBuffer.pos' or 'RunBuffer.glyphs'
- */
-
-struct CheckEntry {
-    string fFile;
-    int fLine;
-    int fCount;
-    bool fOverride;
-};
-
-class SpellCheck : public ParserCommon {
-public:
-    SpellCheck(const BmhParser& bmh) : ParserCommon()
-        , fBmhParser(bmh) {
-        this->reset();
-    }
-    bool check(const char* match);
-    void report(SkCommandLineFlags::StringArray report);
-private:
-    enum class TableState {
-        kNone,
-        kRow,
-        kColumn,
-    };
-
-    enum class PrintCheck {
-        kWordsOnly,
-        kAllowNumbers,
-    };
-
-    bool check(Definition* );
-    bool checkable(MarkType markType);
-    void childCheck(Definition* def, const char* start);
-    void leafCheck(const char* start, const char* end);
-    bool parseFromFile(const char* path) override { return true; }
-    void printCheck(string str, PrintCheck);
-
-    void reset() override {
-        INHERITED::resetCommon();
-        fMethod = nullptr;
-        fRoot = nullptr;
-        fInCode = false;
-        fInConst = false;
-        fInFormula = false;
-        fInDescription = false;
-        fInStdOut = false;
-        fOverride = false;
-    }
-
-    void wordCheck(string str);
-    void wordCheck(ptrdiff_t len, const char* ch);
-
-    unordered_map<string, CheckEntry> fCode;
-    unordered_map<string, CheckEntry> fColons;
-    unordered_map<string, CheckEntry> fDigits;
-    unordered_map<string, CheckEntry> fDots;
-    unordered_map<string, CheckEntry> fParens;  // also hold destructors, operators
-    unordered_map<string, CheckEntry> fUnderscores;
-    unordered_map<string, CheckEntry> fWords;
-    const BmhParser& fBmhParser;
-    Definition* fMethod;
-    RootDefinition* fRoot;
-    int fLocalLine;
-    bool fInCode;
-    bool fInConst;
-    bool fInDescription;
-    bool fInFormula;
-    bool fInStdOut;
-    bool fOverride;
-    typedef ParserCommon INHERITED;
-};
-
-/* This doesn't perform a traditional spell or grammar check, although
-   maybe it should. Instead it looks for words used uncommonly and lower
-   case words that match capitalized words that are not sentence starters.
-   It also looks for articles preceeding capitalized words and their
-   modifiers to try to maintain a consistent voice.
-   Maybe also look for passive verbs (e.g. 'is') and suggest active ones?
- */
-void BmhParser::spellCheck(const char* match, SkCommandLineFlags::StringArray report) const {
-    SpellCheck checker(*this);
-    checker.check(match);
-    checker.report(report);
-}
-
-void BmhParser::spellStatus(const char* statusFile, SkCommandLineFlags::StringArray report) const {
-    SpellCheck checker(*this);
-    StatusIter iter(statusFile, ".bmh", StatusFilter::kInProgress);
-    string file;
-    iter.next(&file, nullptr);
-    string match = iter.baseDir();
-    checker.check(match.c_str());
-    checker.report(report);
-}
-
-bool SpellCheck::check(const char* match) {
-    for (const auto& topic : fBmhParser.fTopicMap) {
-        Definition* topicDef = topic.second;
-        if (topicDef->fParent) {
-            continue;
-        }
-        if (!topicDef->isRoot()) {
-            return this->reportError<bool>("expected root topic");
-        }
-        fRoot = topicDef->asRoot();
-        if (string::npos == fRoot->fFileName.rfind(match)) {
-            continue;
-        }
-        fOverride = string::npos != fRoot->fFileName.rfind("undocumented.bmh")
-                || string::npos != fRoot->fFileName.rfind("markup.bmh")
-                || string::npos != fRoot->fFileName.rfind("usingBookmaker.bmh");
-        this->check(topicDef);
-    }
-    return true;
-}
-
-static bool all_lower(string str) {
-    for (auto c : str) {
-        if (!islower(c)) {
-            return false;
-        }
-    }
-    return true;
-}
-
-bool SpellCheck::check(Definition* def) {
-    fFileName = def->fFileName;
-    fLineCount = def->fLineCount;
-    string printable = def->printableName();
-    const char* textStart = def->fContentStart;
-    switch (def->fMarkType) {
-        case MarkType::kAlias:
-            break;
-        case MarkType::kAnchor:
-            break;
-        case MarkType::kBug:
-            break;
-        case MarkType::kClass:
-            this->wordCheck(def->fName);
-            break;
-        case MarkType::kCode:
-            fInCode = true;
-            break;
-        case MarkType::kColumn:
-            break;
-        case MarkType::kComment:
-            break;
-        case MarkType::kConst: {
-            fInConst = true;
-            this->wordCheck(def->fName);
-            const char* lineEnd = strchr(textStart, '\n');
-            this->wordCheck(lineEnd - textStart, textStart);
-            textStart = lineEnd;
-        } break;
-        case MarkType::kDefine:
-            break;
-        case MarkType::kDescription:
-            fInDescription = true;
-            break;
-        case MarkType::kDetails:
-            break;
-        case MarkType::kDuration:
-            break;
-        case MarkType::kEnum:
-        case MarkType::kEnumClass:
-            this->wordCheck(def->fName);
-            break;
-        case MarkType::kExample:
-            break;
-        case MarkType::kExternal:
-            break;
-        case MarkType::kFile:
-            break;
-        case MarkType::kFilter:
-            break;
-        case MarkType::kFormula:
-            fInFormula = true;
-            break;
-        case MarkType::kFunction:
-            break;
-        case MarkType::kHeight:
-            break;
-        case MarkType::kIllustration:
-            break;
-        case MarkType::kImage:
-            break;
-        case MarkType::kIn:
-            break;
-        case MarkType::kLegend:
-            break;
-        case MarkType::kLine:
-            break;
-        case MarkType::kLink:
-            break;
-        case MarkType::kList:
-            break;
-        case MarkType::kLiteral:
-            break;
-        case MarkType::kMarkChar:
-            break;
-        case MarkType::kMember:
-            break;
-        case MarkType::kMethod: {
-            string method_name = def->methodName();
-            if (all_lower(method_name)) {
-                method_name += "()";
-            }
-            if (!def->isClone() && Definition::MethodType::kOperator != def->fMethodType) {
-                this->wordCheck(method_name);
-            }
-            fMethod = def;
-            } break;
-        case MarkType::kNoExample:
-            break;
-        case MarkType::kNoJustify:
-            break;
-        case MarkType::kOutdent:
-            break;
-        case MarkType::kParam: {
-            TextParser paramParser(def->fFileName, def->fStart, def->fContentStart,
-                    def->fLineCount);
-            paramParser.skipWhiteSpace();
-            SkASSERT(paramParser.startsWith("#Param"));
-            paramParser.next(); // skip hash
-            paramParser.skipToNonName(); // skip Param
-            paramParser.skipSpace();
-            const char* paramName = paramParser.fChar;
-            paramParser.skipToSpace();
-            fInCode = true;
-            this->wordCheck(paramParser.fChar - paramName, paramName);
-            fInCode = false;
-        } break;
-        case MarkType::kPhraseDef:
-            break;
-        case MarkType::kPhraseParam:
-            break;
-        case MarkType::kPhraseRef:
-            break;
-        case MarkType::kPlatform:
-            break;
-        case MarkType::kPopulate:
-            break;
-        case MarkType::kReturn:
-            break;
-        case MarkType::kRow:
-            break;
-        case MarkType::kSeeAlso:
-            break;
-        case MarkType::kSet:
-            break;
-        case MarkType::kStdOut: {
-            fInStdOut = true;
-            TextParser code(def);
-            code.skipSpace();
-            while (!code.eof()) {
-                const char* end = code.trimmedLineEnd();
-                this->wordCheck(end - code.fChar, code.fChar);
-                code.skipToLineStart();
-            }
-            fInStdOut = false;
-            } break;
-        case MarkType::kStruct:
-            fRoot = def->asRoot();
-            this->wordCheck(def->fName);
-            break;
-        case MarkType::kSubstitute:
-            break;
-        case MarkType::kSubtopic:
-            // TODO: add a tag that allows subtopic labels in illustrations to skip spellcheck?
-            if (string::npos == fFileName.find("illustrations.bmh")) {
-                this->printCheck(printable, PrintCheck::kAllowNumbers);
-            }
-            break;
-        case MarkType::kTable:
-            break;
-        case MarkType::kTemplate:
-            break;
-        case MarkType::kText:
-            break;
-        case MarkType::kToDo:
-            break;
-        case MarkType::kTopic:
-            this->printCheck(printable, PrintCheck::kWordsOnly);
-            break;
-        case MarkType::kTypedef:
-            break;
-        case MarkType::kUnion:
-            break;
-        case MarkType::kVolatile:
-            break;
-        case MarkType::kWidth:
-            break;
-        default:
-            SkASSERT(0); // handle everything
-            break;
-    }
-    this->childCheck(def, textStart);
-    switch (def->fMarkType) {  // post child work, at least for tables
-        case MarkType::kCode:
-            fInCode = false;
-            break;
-        case MarkType::kColumn:
-            break;
-        case MarkType::kDescription:
-            fInDescription = false;
-            break;
-        case MarkType::kEnum:
-        case MarkType::kEnumClass:
-            break;
-        case MarkType::kExample:
-            break;
-        case MarkType::kFormula:
-            fInFormula = false;
-            break;
-        case MarkType::kLegend:
-            break;
-        case MarkType::kMethod:
-            fMethod = nullptr;
-            break;
-        case MarkType::kConst:
-            fInConst = false;
-        case MarkType::kParam:
-            break;
-        case MarkType::kReturn:
-        case MarkType::kSeeAlso:
-            break;
-        case MarkType::kRow:
-            break;
-        case MarkType::kStruct:
-            fRoot = fRoot->rootParent();
-            break;
-        case MarkType::kTable:
-            break;
-        default:
-            break;
-    }
-    return true;
-}
-
-bool SpellCheck::checkable(MarkType markType) {
-    return Resolvable::kYes == fBmhParser.kMarkProps[(int) markType].fResolve;
-}
-
-void SpellCheck::childCheck(Definition* def, const char* start) {
-    const char* end;
-    fLineCount = def->fLineCount;
-    if (def->isRoot()) {
-        fRoot = def->asRoot();
-    }
-    for (auto& child : def->fChildren) {
-        end = child->fStart;
-        if (this->checkable(def->fMarkType)) {
-            this->leafCheck(start, end);
-        }
-        this->check(child);
-        start = child->fTerminator;
-    }
-    if (this->checkable(def->fMarkType)) {
-        end = def->fContentEnd;
-        this->leafCheck(start, end);
-    }
-}
-
-void SpellCheck::leafCheck(const char* start, const char* end) {
-    const char* chPtr = start;
-    int inAngles = 0;
-    int inParens = 0;
-    bool inQuotes = false;
-    bool allLower = true;
-    char prePriorCh = 0;
-    char priorCh = 0;
-    char lastCh = 0;
-    const char* wordStart = nullptr;
-    const char* wordEnd = nullptr;
-    const char* possibleEnd = nullptr;
-    fLocalLine = 0;
-    do {
-        if (wordStart && wordEnd) {
-            if (!allLower || (!inQuotes && '\"' != lastCh && !inParens
-                    && ')' != lastCh && !inAngles && '>' != lastCh)) {
-                string word(wordStart, (possibleEnd ? possibleEnd : wordEnd) - wordStart);
-                if ("e" != word || !isdigit(prePriorCh) || ('+' != lastCh &&
-                        '-' != lastCh && !isdigit(lastCh))) {
-                    this->wordCheck(word);
-                }
-            }
-            wordStart = nullptr;
-        }
-        if (chPtr == end) {
-            break;
-        }
-        switch (*chPtr) {
-            case '>':
-                if (isalpha(lastCh)) {
-                    --inAngles;
-                    SkASSERT(inAngles >= 0);
-                }
-                wordEnd = chPtr;
-                break;
-            case '(':
-                ++inParens;
-                possibleEnd = chPtr;
-                break;
-            case ')':
-                --inParens;
-                if ('(' == lastCh) {
-                    wordEnd = chPtr + 1;
-                } else {
-                    wordEnd = chPtr;
-                }
-                SkASSERT(inParens >= 0 || fInStdOut);
-                break;
-            case '\"':
-                inQuotes = !inQuotes;
-                wordEnd = chPtr;
-                SkASSERT(inQuotes == !wordStart);
-                break;
-            case 'A': case 'B': case 'C': case 'D': case 'E':
-            case 'F': case 'G': case 'H': case 'I': case 'J':
-            case 'K': case 'L': case 'M': case 'N': case 'O':
-            case 'P': case 'Q': case 'R': case 'S': case 'T':
-            case 'U': case 'V': case 'W': case 'X': case 'Y':
-            case 'Z':
-                allLower = false;
-            case 'a': case 'b': case 'c': case 'd': case 'e':
-            case 'f': case 'g': case 'h': case 'i': case 'j':
-            case 'k': case 'l': case 'm': case 'n': case 'o':
-            case 'p': case 'q': case 'r': case 's': case 't':
-            case 'u': case 'v': case 'w': case 'x': case 'y':
-            case 'z':
-                if (!wordStart) {
-                    wordStart = chPtr;
-                    wordEnd = nullptr;
-                    possibleEnd = nullptr;
-                    allLower = 'a' <= *chPtr;
-                    if ('<' == lastCh || ('<' == priorCh && '/' == lastCh)) {
-                        ++inAngles;
-                    }
-                }
-                break;
-            case '0': case '1': case '2': case '3': case '4':
-            case '5': case '6': case '7': case '8': case '9':
-            case '_':
-                allLower = false;
-            case '-':  // note that dash doesn't clear allLower
-                break;
-            case '!':
-                if (!inQuotes) {
-                    wordEnd = chPtr;
-                }
-                break;
-            case '\n':
-                ++fLocalLine;
-                // fall through
-            default:
-                wordEnd = chPtr;
-                break;
-        }
-        prePriorCh = priorCh;
-        priorCh = lastCh;
-        lastCh = *chPtr;
-    } while (++chPtr <= end);
-}
-
-void SpellCheck::printCheck(string str, PrintCheck allowed) {
-    string word;
-    for (std::stringstream stream(str); stream >> word; ) {
-        if (PrintCheck::kAllowNumbers == allowed && (std::isdigit(word.back()) || 'x' == word.back())) {
-            // allow ###x for RGB_888x
-            if ((size_t) std::count_if(word.begin(), word.end() - 1,
-                    [](unsigned char c){ return std::isdigit(c); } ) == word.length() - 1) {
-                continue;
-            }
-        }
-        wordCheck(word);
-    }
-}
-
-static bool stringCompare(const std::pair<string, CheckEntry>& i, const std::pair<string, CheckEntry>& j) {
-    return i.first.compare(j.first) < 0;
-}
-
-void SpellCheck::report(SkCommandLineFlags::StringArray report) {
-    vector<std::pair<string, CheckEntry>> elems(fWords.begin(), fWords.end());
-    std::sort(elems.begin(), elems.end(), stringCompare);
-    if (report.contains("once")) {
-        for (auto iter : elems) {
-            if (iter.second.fOverride) {
-                continue;
-            }
-            if (iter.second.fCount == 1) {
-                string fullName = this->ReportFilename(iter.second.fFile);
-                SkDebugf("%s(%d): %s\n", fullName.c_str(), iter.second.fLine,
-                        iter.first.c_str());
-            }
-        }
-        SkDebugf("\n");
-        return;
-    }
-    if (report.contains("all")) {
-        int column = 0;
-        char lastInitial = 'a';
-        int count = 0;
-        for (auto iter : elems) {
-            if (iter.second.fOverride) {
-                continue;
-            }
-            string check = iter.first.c_str();
-            bool allLower = true;
-            for (auto c : check) {
-                if (isupper(c)) {
-                    allLower = false;
-                    break;
-                }
-            }
-            if (!allLower) {
-                continue;
-            }
-            if (column + check.length() > 100 || check[0] != lastInitial) {
-                SkDebugf("\n");
-                column = 0;
-            }
-            if (check[0] != lastInitial) {
-                SkDebugf("\n");
-                lastInitial = check[0];
-            }
-            SkDebugf("%s ", check.c_str());
-            column += check.length();
-            ++count;
-        }
-        SkDebugf("\n\ncount = %d\n", count);
-        return;
-    }
-    int index = 0;
-    const char* mispelled = report[0];
-    for (auto iter : elems) {
-        if (iter.second.fOverride) {
-            continue;
-        }
-        string check = iter.first.c_str();
-        while (check.compare(mispelled) > 0) {
-            SkDebugf("%s not found\n", mispelled);
-            if (report.count() == ++index) {
-                break;
-            }
-        }
-        if (report.count() == index) {
-            break;
-        }
-        if (check.compare(mispelled) == 0) {
-            string fullName = this->ReportFilename(iter.second.fFile);
-            SkDebugf("%s(%d): %s\n", fullName.c_str(), iter.second.fLine,
-                    iter.first.c_str());
-            if (report.count() == ++index) {
-                break;
-            }
-        }
-    }
-}
-
-void SpellCheck::wordCheck(string str) {
-    if ("nullptr" == str) {
-        return;  // doesn't seem worth it, treating nullptr as a word in need of correction
-    }
-    bool hasColon = false;
-    bool hasDot = false;
-    bool hasParen = false;
-    bool hasUnderscore = false;
-    bool sawDash = false;
-    bool sawDigit = false;
-    bool sawSpecial = false;
-    SkASSERT(str.length() > 0);
-    SkASSERT(isalpha(str[0]) || '~' == str[0]);
-    for (char ch : str) {
-        if (isalpha(ch) || '-' == ch) {
-            sawDash |= '-' == ch;
-            continue;
-        }
-        bool isColon = ':' == ch;
-        hasColon |= isColon;
-        bool isDot = '.' == ch;
-        hasDot |= isDot;
-        bool isParen = '(' == ch || ')' == ch || '~' == ch || '=' == ch || '!' == ch ||
-                '[' == ch || ']' == ch;
-        hasParen |= isParen;
-        bool isUnderscore = '_' == ch;
-        hasUnderscore |= isUnderscore;
-        if (isColon || isDot || isUnderscore || isParen) {
-            continue;
-        }
-        if (isdigit(ch)) {
-            sawDigit = true;
-            continue;
-        }
-        if ('&' == ch || ',' == ch || ' ' == ch) {
-            sawSpecial = true;
-            continue;
-        }
-        SkASSERT(0);
-    }
-    if (sawSpecial && !hasParen) {
-        SkASSERT(0);
-    }
-    bool inCode = fInCode;
-    if (hasUnderscore && isupper(str[0]) && ('S' != str[0] || 'K' != str[1])
-            && !hasColon && !hasDot && !hasParen && !fInStdOut && !inCode && !fInConst
-            && !sawDigit && !sawSpecial && !sawDash) {
-        std::istringstream ss(str);
-        string token;
-        while (std::getline(ss, token, '_')) {
-            if (token.length()) {
-                this->wordCheck(token);
-            }
-        }
-        return;
-    }
-    if (!hasColon && !hasDot && !hasParen && !hasUnderscore
-            && !fInStdOut && !inCode && !fInConst && !sawDigit
-            && islower(str[0]) && isupper(str[1])) {
-        inCode = true;
-    }
-    bool methodParam = false;
-    if (fMethod) {
-        for (auto child : fMethod->fChildren) {
-            if (MarkType::kParam == child->fMarkType && str == child->fName) {
-                methodParam = true;
-                break;
-            }
-        }
-    }
-    auto& mappy = hasColon ? fColons :
-                  hasDot ? fDots :
-                  hasParen ? fParens :
-                  hasUnderscore ? fUnderscores :
-                  fInStdOut || fInFormula || inCode || fInConst || methodParam ? fCode :
-                  sawDigit ? fDigits : fWords;
-    auto iter = mappy.find(str);
-    if (mappy.end() != iter) {
-        if (iter->second.fOverride && !fOverride) {
-            iter->second.fFile = fFileName;
-            iter->second.fLine = fLineCount + fLocalLine;
-            iter->second.fOverride = false;
-        }
-        iter->second.fCount += 1;
-    } else {
-        CheckEntry* entry = &mappy[str];
-        entry->fFile = fFileName;
-        entry->fLine = fLineCount + fLocalLine;
-        entry->fCount = 1;
-        entry->fOverride = fOverride;
-    }
-}
-
-void SpellCheck::wordCheck(ptrdiff_t len, const char* ch) {
-    leafCheck(ch, ch + len);
-}
diff --git a/tools/bookmaker/textParser.cpp b/tools/bookmaker/textParser.cpp
deleted file mode 100644
index f53bdaf..0000000
--- a/tools/bookmaker/textParser.cpp
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "definition.h"
-#include "textParser.h"
-
-#ifdef SK_BUILD_FOR_WIN
-#include <Windows.h>
-#endif
-
-TextParser::TextParser(const Definition* definition) :
-    TextParser(definition->fFileName, definition->fContentStart, definition->fContentEnd,
-        definition->fLineCount) {
-}
-
-string TextParser::ReportFilename(string file) {
-	string fullName;
-#ifdef SK_BUILD_FOR_WIN
-	TCHAR pathChars[MAX_PATH];
-	DWORD pathLen = GetCurrentDirectory(MAX_PATH, pathChars);
-	for (DWORD index = 0; index < pathLen; ++index) {
-		fullName += pathChars[index] == (char)pathChars[index] ? (char)pathChars[index] : '?';
-	}
-	fullName += '\\';
-#endif
-	fullName += file;
-    return fullName;
-}
-
-void TextParser::reportError(const char* errorStr) const {
-    this->reportWarning(errorStr);
-    SkDebugf("");  // convenient place to set a breakpoint
-}
-
-void TextParser::reportWarning(const char* errorStr) const {
-    const char* lineStart = fLine;
-    if (lineStart >= fEnd) {
-        lineStart = fChar;
-    }
-    SkASSERT(lineStart < fEnd);
-    TextParser err(fFileName, lineStart, fEnd, fLineCount);
-    size_t lineLen = this->lineLength();
-    ptrdiff_t spaces = fChar - lineStart;
-    while (spaces > 0 && (size_t) spaces > lineLen) {
-        ++err.fLineCount;
-        err.fLine += lineLen;
-        spaces -= lineLen;
-        lineLen = err.lineLength();
-    }
-	string fullName = this->ReportFilename(fFileName);
-    SkDebugf("\n%s(%zd): error: %s\n", fullName.c_str(), err.fLineCount, errorStr);
-    if (0 == lineLen) {
-        SkDebugf("[blank line]\n");
-    } else {
-        while (lineLen > 0 && '\n' == err.fLine[lineLen - 1]) {
-            --lineLen;
-        }
-        SkDebugf("%.*s\n", (int) lineLen, err.fLine);
-        SkDebugf("%*s^\n", (int) spaces, "");
-    }
-}
-
-void TextParser::setForErrorReporting(const Definition* definition, const char* str) {
-    fFileName = definition->fFileName;
-    fStart = definition->fContentStart;
-    fLine = str;
-    while (fLine > fStart && fLine[-1] != '\n') {
-        --fLine;
-    }
-    fChar = str;
-    fEnd = definition->fContentEnd;
-    fLineCount = definition->fLineCount;
-    const char* lineInc = fStart;
-    while (lineInc < str) {
-        fLineCount += '\n' == *lineInc++;
-    }
-}
-
-string TextParser::typedefName() {
-    // look for typedef as one of three forms:
-    // typedef return-type (*NAME)(params);
-    // typedef alias NAME;
-    // typedef std::function<alias> NAME;
-    string builder;
-    const char* end = this->doubleLF();
-    if (!end) {
-       end = fEnd;
-    }
-    const char* altEnd = this->strnstr("#Typedef ##", end);
-    if (altEnd) {
-        end = this->strnchr('\n', end);
-    }
-    if (!end) {
-        return this->reportError<string>("missing typedef std::function end bracket >");
-    }
-    bool stdFunction = this->startsWith("std::function");
-    if (stdFunction) {
-        if (!this->skipToEndBracket('>')) {
-            return this->reportError<string>("missing typedef std::function end bracket >");
-        }
-        this->next();
-        this->skipWhiteSpace();
-        builder += string(fChar, end - fChar);
-    } else {
-        const char* paren = this->strnchr('(', end);
-        if (!paren) {
-            const char* lastWord = nullptr;
-            do {
-                this->skipToWhiteSpace();
-                if (fChar < end && isspace(fChar[0])) {
-                    const char* whiteStart = fChar;
-                    this->skipWhiteSpace();
-                    // FIXME: test should be for fMC
-                    if ('#' == fChar[0]) {
-                        end = whiteStart;
-                        break;
-                    }
-                    lastWord = fChar;
-                } else {
-                    break;
-                }
-            } while (true);
-            if (!lastWord) {
-                return this->reportError<string>("missing typedef name");
-            }
-            builder += string(lastWord, end - lastWord);
-        } else {
-            this->skipTo(paren);
-            this->next();
-            if ('*' != this->next()) {
-                return this->reportError<string>("missing typedef function asterisk");
-            }
-            const char* nameStart = fChar;
-            if (!this->skipToEndBracket(')')) {
-                return this->reportError<string>("missing typedef function )");
-            }
-            builder += string(nameStart, fChar - nameStart);
-            if (!this->skipToEndBracket('(')) {
-                return this->reportError<string>("missing typedef params (");
-            }
-            if (! this->skipToEndBracket(')')) {
-                return this->reportError<string>("missing typedef params )");
-            }
-            this->skipTo(end);
-        }
-    }
-    return builder;
-}
-
-void MethodParser::skipToMethodEnd(Resolvable resolvable) {
-    if (this->eof()) {
-        return;
-    }
-    string name = fLocalName.length() ? fLocalName : fClassName;
-    if ('~' == this->peek()) {
-        this->next();
-        if (!this->startsWith(name.c_str())) {
-            --fChar;
-            return;
-        }
-    }
-    if (Resolvable::kSimple != resolvable
-            && Resolvable::kInclude != resolvable
-            && (this->startsWith(name.c_str()) || this->startsWith("operator"))) {
-        const char* ptr = this->anyOf("\n (");
-        if (ptr && '(' ==  *ptr && strncmp(ptr, "(...", 4)) {
-            this->skipToEndBracket(')');
-            SkAssertResult(')' == this->next());
-            Resolvable::kCode == resolvable && this->skipExact(" const");
-            return;
-        }
-    }
-    if (this->startsWith("Sk") && this->wordEndsWith(".h")) {  // allow include refs
-        this->skipToNonName();
-    } else {
-        this->skipFullName();
-        if (this->endsWith("operator")) {
-            const char* ptr = this->anyOf("\n (");
-            if (ptr && '(' ==  *ptr) {
-                this->skipToEndBracket(')');
-                SkAssertResult(')' == this->next());
-                this->skipExact(" const");
-            }
-        }
-    }
-}
diff --git a/tools/bookmaker/textParser.h b/tools/bookmaker/textParser.h
deleted file mode 100644
index c3c218c..0000000
--- a/tools/bookmaker/textParser.h
+++ /dev/null
@@ -1,793 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef textParser_DEFINED
-#define textParser_DEFINED
-
-#include <functional>
-
-#include "bookmaker.h"
-
-class BmhParser;
-class Definition;
-
-class TextParser : public NonAssignable {
-    TextParser() {}  // only for ParserCommon, TextParserSave
-    friend class ParserCommon;
-    friend class TextParserSave;
-public:
-    virtual ~TextParser() {}
-
-    TextParser(string fileName, const char* start, const char* end, int lineCount)
-        : fFileName(fileName)
-        , fStart(start)
-        , fLine(start)
-        , fChar(start)
-        , fEnd(end)
-        , fLineCount(lineCount)
-    {
-    }
-
-    TextParser(const Definition* );
-
-    const char* anyOf(const char* str) const {
-        const char* ptr = fChar;
-        while (ptr < fEnd) {
-            if (strchr(str, ptr[0])) {
-                return ptr;
-            }
-            ++ptr;
-        }
-        return nullptr;
-    }
-
-    const char* anyOf(const char* wordStart, const char* wordList[], size_t wordListCount) const {
-        const char** wordPtr = wordList;
-        const char** wordEnd = wordPtr + wordListCount;
-        const size_t matchLen = fChar - wordStart;
-        while (wordPtr < wordEnd) {
-            const char* word = *wordPtr++;
-            if (strlen(word) == matchLen && !strncmp(wordStart, word, matchLen)) {
-                return word;
-            }
-        }
-        return nullptr;
-    }
-
-    // words must be alpha only
-    string anyWord(const vector<string>& wordList, int spaces) const {
-        const char* matchStart = fChar;
-        do {
-            int count = spaces;
-            while (matchStart < fEnd && !isalpha(matchStart[0])) {
-                ++matchStart;
-            }
-            const char* matchEnd = matchStart;
-            const char* nextWord = nullptr;
-            while (matchEnd < fEnd) {
-                if (isalpha(matchEnd[0])) {
-                } else if (' ' == matchEnd[0] && --count >= 0) {
-                    if (!nextWord) {
-                        nextWord = matchEnd;
-                    }
-                } else {
-                    break;
-                }
-                ++matchEnd;
-            }
-            size_t matchLen = matchEnd - matchStart;
-            for (auto word : wordList) {
-                if (word.length() != matchLen) {
-                    continue;
-                }
-                for (unsigned index = 0; index < matchLen; ++index) {
-                    if (tolower(matchStart[index]) != word[index]) {
-                        goto nextWord;
-                    }
-                }
-                return word;
-        nextWord: ;
-            }
-            matchStart = nextWord ? nextWord : matchEnd;
-        } while (matchStart < fEnd);
-        return "";
-    }
-
-    bool back(const char* pattern) {
-        size_t len = strlen(pattern);
-        const char* start = fChar - len;
-        if (start <= fStart) {
-            return false;
-        }
-        if (strncmp(start, pattern, len)) {
-            return false;
-        }
-        fChar = start;
-        return true;
-    }
-
-    char backup(const char* pattern) const {
-        size_t len = strlen(pattern);
-        const char* start = fChar - len;
-        if (start <= fStart) {
-            return '\0';
-        }
-        if (strncmp(start, pattern, len)) {
-            return '\0';
-        }
-        return start[-1];
-    }
-
-    void backupWord() {
-        while (fChar > fStart && isalpha(fChar[-1])) {
-            --fChar;
-        }
-    }
-
-    bool contains(const char* match, const char* lineEnd, const char** loc) const {
-        const char* result = this->strnstr(match, lineEnd);
-        if (loc) {
-            *loc = result;
-        }
-        return result;
-    }
-
-    bool containsWord(const char* match, const char* lineEnd, const char** loc) {
-        size_t len = strlen(match);
-        do {
-            const char* result = this->strnstr(match, lineEnd);
-            if (!result) {
-                return false;
-            }
-            if ((result > fStart && isalnum(result[-1])) || (result + len < fEnd
-                    && isalnum(result[len]))) {
-                fChar = result + len;
-                continue;
-            }
-            if (loc) {
-                *loc = result;
-            }
-            return true;
-        } while (true);
-    }
-
-    // either /n/n or /n# will stop parsing a typedef
-    const char* doubleLF() const {
-        const char* ptr = fChar - 1;
-        const char* doubleStart = nullptr;
-        while (++ptr < fEnd) {
-            if (!doubleStart) {
-                if ('\n' == ptr[0]) {
-                    doubleStart = ptr;
-                }
-                continue;
-            }
-            if ('\n' == ptr[0] || '#' == ptr[0]) {
-                return doubleStart;
-            }
-            if (' ' < ptr[0]) {
-                doubleStart = nullptr;
-            }
-        }
-        return nullptr;
-    }
-
-    bool endsWith(const char* match) {
-        int matchLen = strlen(match);
-        if (matchLen > fChar - fLine) {
-            return false;
-        }
-        return !strncmp(fChar - matchLen, match, matchLen);
-    }
-
-    bool eof() const { return fChar >= fEnd; }
-
-    const char* lineEnd() const {
-        const char* ptr = fChar;
-        do {
-            if (ptr >= fEnd) {
-                return ptr;
-            }
-            char test = *ptr++;
-            if (test == '\n' || test == '\0') {
-                break;
-            }
-        } while (true);
-        return ptr;
-    }
-
-    ptrdiff_t lineLength() const {
-        return this->lineEnd() - fLine;
-    }
-
-    bool match(TextParser* );
-
-    char next() {
-        SkASSERT(fChar < fEnd);
-        char result = *fChar++;
-        if ('\n' == result) {
-            ++fLineCount;
-            fLine = fChar;
-        }
-        return result;
-    }
-
-    char peek() const { SkASSERT(fChar < fEnd); return *fChar; }
-
-    void restorePlace(const TextParser& save) {
-        fChar = save.fChar;
-        fLine = save.fLine;
-        fLineCount = save.fLineCount;
-    }
-
-    void savePlace(TextParser* save) {
-        save->fChar = fChar;
-        save->fLine = fLine;
-        save->fLineCount = fLineCount;
-    }
-
-    void reportError(const char* errorStr) const;
-    static string ReportFilename(string file);
-    void reportWarning(const char* errorStr) const;
-
-    template <typename T> T reportError(const char* errorStr) const {
-        this->reportError(errorStr);
-        return T();
-    }
-
-    bool sentenceEnd(const char* check) const {
-        while (check > fStart) {
-            --check;
-            if (' ' < check[0] && '.' != check[0]) {
-                return false;
-            }
-            if ('.' == check[0]) {
-                return ' ' >= check[1];
-            }
-            if ('\n' == check[0] && '\n' == check[1]) {
-                return true;
-            }
-        }
-        return true;
-    }
-
-    void setForErrorReporting(const Definition* , const char* );
-
-    bool skipToBalancedEndBracket(char startB, char endB) {
-        SkASSERT(fChar < fEnd);
-        SkASSERT(startB == fChar[0]);
-        int startCount = 0;
-        do {
-            char test = this->next();
-            startCount += startB == test;
-            startCount -= endB  == test;
-        } while (startCount && fChar < fEnd);
-        return !startCount;
-    }
-
-    bool skipToEndBracket(char endBracket, const char* end = nullptr) {
-        if (nullptr == end) {
-            end = fEnd;
-        }
-        while (fChar[0] != endBracket) {
-            if (fChar >= end) {
-                return false;
-            }
-            (void) this->next();
-        }
-        return true;
-    }
-
-    bool skipToEndBracket(const char* endBracket) {
-        size_t len = strlen(endBracket);
-        while (strncmp(fChar, endBracket, len)) {
-            if (fChar >= fEnd) {
-                return false;
-            }
-            (void) this->next();
-        }
-        return true;
-    }
-
-    bool skipLine() {
-        return skipToEndBracket('\n');
-    }
-
-    void skipTo(const char* skip) {
-       while (fChar < skip) {
-           this->next();
-       }
-    }
-
-    void skipToAlpha() {
-        while (fChar < fEnd && !isalpha(fChar[0])) {
-            fChar++;
-        }
-    }
-
-    // returns true if saw close brace
-    bool skipToAlphaNum() {
-        bool sawCloseBrace = false;
-        while (fChar < fEnd && !isalnum(fChar[0])) {
-            sawCloseBrace |= '}' == *fChar++;
-        }
-        return sawCloseBrace;
-    }
-
-    bool skipExact(const char* pattern) {
-        if (!this->startsWith(pattern)) {
-            return false;
-        }
-        this->skipName(pattern);
-        return true;
-    }
-
-    // differs from skipToNonAlphaNum in that a.b isn't considered a full name,
-    // since a.b can't be found as a named definition
-    void skipFullName() {
-        do {
-            char last = '\0';
-            while (fChar < fEnd && (isalnum(fChar[0])
-                    || '_' == fChar[0]  /* || '-' == fChar[0] */
-                    || (':' == fChar[0] && fChar + 1 < fEnd && ':' == fChar[1]))) {
-                if (':' == fChar[0] && fChar + 1 < fEnd && ':' == fChar[1]) {
-                    fChar++;
-                }
-                last = fChar[0];
-                fChar++;
-            }
-            if (fChar + 1 >= fEnd || '/' != fChar[0] || !isalpha(last) || !isalpha(fChar[1])) {
-                break;  // stop unless pattern is xxx/xxx as in I/O
-            }
-            fChar++; // skip slash
-        } while (true);
-    }
-
-    int skipToLineBalance(char open, char close) {
-        int match = 0;
-        while (!this->eof() && '\n' != fChar[0]) {
-            match += open == this->peek();
-            match -= close == this->next();
-        }
-        return match;
-    }
-
-    bool skipToLineStart() {
-        if (!this->skipLine()) {
-            return false;
-        }
-        if (!this->eof()) {
-            return this->skipWhiteSpace();
-        }
-        return true;
-    }
-
-    void skipToLineStart(int* indent, bool* sawReturn) {
-        SkAssertResult(this->skipLine());
-        this->skipWhiteSpace(indent, sawReturn);
-    }
-
-    void skipLower() {
-        while (fChar < fEnd && (islower(fChar[0]) || '_' == fChar[0])) {
-            fChar++;
-        }
-    }
-
-    void skipToNonAlphaNum() {
-        while (fChar < fEnd && (isalnum(fChar[0]) || '_' == fChar[0])) {
-            fChar++;
-        }
-    }
-
-    void skipToNonName() {
-        while (fChar < fEnd && (isalnum(fChar[0])
-                || '_' == fChar[0] || '-' == fChar[0]
-                || (':' == fChar[0] && fChar + 1 < fEnd && ':' == fChar[1])
-                || ('.' == fChar[0] && fChar + 1 < fEnd && isalpha(fChar[1])))) {
-            if (':' == fChar[0] && fChar +1 < fEnd && ':' == fChar[1]) {
-                fChar++;
-            }
-            fChar++;
-        }
-    }
-
-    void skipPhraseName() {
-        while (fChar < fEnd && (islower(fChar[0]) || '_' == fChar[0])) {
-            fChar++;
-        }
-    }
-
-    void skipToSpace() {
-        while (fChar < fEnd && ' ' != fChar[0]) {
-            fChar++;
-        }
-    }
-
-    void skipToWhiteSpace() {
-        while (fChar < fEnd && ' ' < fChar[0]) {
-            fChar++;
-        }
-    }
-
-    bool skipName(const char* word) {
-        size_t len = strlen(word);
-        if (len <= (size_t) (fEnd - fChar) && !strncmp(word, fChar, len)) {
-            for (size_t i = 0; i < len; ++i) {
-                this->next();
-            }
-        }
-        return this->eof() || ' ' >= fChar[0];
-    }
-
-    bool skipSpace() {
-        while (' ' == this->peek()) {
-            (void) this->next();
-            if (fChar >= fEnd) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    bool skipWord(const char* word) {
-        if (!this->skipWhiteSpace()) {
-            return false;
-        }
-        const char* save = fChar;
-        if (!this->skipName(word)) {
-            fChar = save;
-            return false;
-        }
-        if (!this->skipWhiteSpace()) {
-            return false;
-        }
-        return true;
-    }
-
-    bool skipWhiteSpace() {
-        while (' ' >= this->peek()) {
-            (void) this->next();
-            if (fChar >= fEnd) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    bool skipWhiteSpace(int* indent, bool* skippedReturn) {
-        while (' ' >= this->peek()) {
-            *indent = *skippedReturn ? *indent + 1 : 1;
-            if ('\n' == this->peek()) {
-                *skippedReturn |= true;
-                *indent = 0;
-            }
-            (void) this->next();
-            if (fChar >= fEnd) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    bool startsWith(const char* str) const {
-        size_t len = strlen(str);
-        ptrdiff_t lineLen = fEnd - fChar;
-        return len <= (size_t) lineLen && 0 == strncmp(str, fChar, len);
-    }
-
-    // ignores minor white space differences
-    bool startsWith(const char* str, size_t oLen) const {
-        size_t tIndex = 0;
-        size_t tLen = fEnd - fChar;
-        size_t oIndex = 0;
-        while (oIndex < oLen && tIndex < tLen) {
-            bool tSpace = ' ' >= fChar[tIndex];
-            bool oSpace = ' ' >= str[oIndex];
-            if (tSpace != oSpace) {
-                break;
-            }
-            if (tSpace) {
-                do {
-                    ++tIndex;
-                } while (tIndex < tLen && ' ' >= fChar[tIndex]);
-                do {
-                    ++oIndex;
-                } while (oIndex < oLen && ' ' >= str[oIndex]);
-                continue;
-            }
-            if (fChar[tIndex] != str[oIndex]) {
-                break;
-            }
-            ++tIndex;
-            ++oIndex;
-        }
-        return oIndex >= oLen;
-    }
-
-    const char* strnchr(char ch, const char* end) const {
-        const char* ptr = fChar;
-        while (ptr < end) {
-            if (ptr[0] == ch) {
-                return ptr;
-            }
-            ++ptr;
-        }
-        return nullptr;
-    }
-
-    const char* strnstr(const char *match, const char* end) const {
-        size_t matchLen = strlen(match);
-        SkASSERT(matchLen > 0);
-        ptrdiff_t len = end - fChar;
-        SkASSERT(len >= 0);
-        if ((size_t) len < matchLen ) {
-            return nullptr;
-        }
-        size_t count = len - matchLen;
-        for (size_t index = 0; index <= count; index++) {
-            if (0 == strncmp(&fChar[index], match, matchLen)) {
-                return &fChar[index];
-            }
-        }
-        return nullptr;
-    }
-
-    const char* trimmedBracketEnd(const char bracket) const {
-        int max = (int) (this->lineLength());
-        int index = 0;
-        while (index < max && bracket != fChar[index]) {
-            ++index;
-        }
-        SkASSERT(index < max);
-        while (index > 0 && ' ' >= fChar[index - 1]) {
-            --index;
-        }
-        return fChar + index;
-    }
-
-    const char* trimmedBracketEnd(string bracket) const {
-        size_t max = (size_t) (this->lineLength());
-        string line(fChar, max);
-        size_t index = line.find(bracket);
-        SkASSERT(index < max);
-        while (index > 0 && ' ' >= fChar[index - 1]) {
-            --index;
-        }
-        return fChar + index;
-    }
-
-    const char* trimmedBracketNoEnd(const char bracket) const {
-        int max = (int) (fEnd - fChar);
-        int index = 0;
-        while (index < max && bracket != fChar[index]) {
-            ++index;
-        }
-        SkASSERT(index < max);
-        while (index > 0 && ' ' >= fChar[index - 1]) {
-            --index;
-        }
-        return fChar + index;
-    }
-
-    const char* trimmedLineEnd() const {
-        const char* result = this->lineEnd();
-        while (result > fChar && ' ' >= result[-1]) {
-            --result;
-        }
-        return result;
-    }
-
-    void trimEnd() {
-        while (fEnd > fStart && ' ' >= fEnd[-1]) {
-            --fEnd;
-        }
-    }
-
-    // FIXME: nothing else in TextParser knows from C++ --
-    // there could be a class between TextParser and ParserCommon
-    virtual string typedefName();
-
-    const char* wordEnd() const {
-        const char* end = fChar;
-        while (isalnum(end[0]) || '_' == end[0] || '-' == end[0]) {
-            ++end;
-        }
-        return end;
-    }
-
-    string fFileName;
-    const char* fStart;
-    const char* fLine;
-    const char* fChar;
-    const char* fEnd;
-    size_t fLineCount;
-};
-
-class TextParserSave {
-public:
-    TextParserSave(TextParser* parser) {
-        fParser = parser;
-        fSave.fFileName = parser->fFileName;
-        fSave.fStart = parser->fStart;
-        fSave.fLine = parser->fLine;
-        fSave.fChar = parser->fChar;
-        fSave.fEnd = parser->fEnd;
-        fSave.fLineCount = parser->fLineCount;
-    }
-
-    void restore() const {
-        fParser->fFileName = fSave.fFileName;
-        fParser->fStart = fSave.fStart;
-        fParser->fLine = fSave.fLine;
-        fParser->fChar = fSave.fChar;
-        fParser->fEnd = fSave.fEnd;
-        fParser->fLineCount = fSave.fLineCount;
-    }
-
-private:
-    TextParser* fParser;
-    TextParser fSave;
-};
-
-static inline bool has_nonwhitespace(string s) {
-    bool nonwhite = false;
-    for (const char& c : s) {
-        if (' ' < c) {
-            nonwhite = true;
-            break;
-        }
-    }
-    return nonwhite;
-}
-
-static inline void trim_end(string &s) {
-    s.erase(std::find_if(s.rbegin(), s.rend(),
-            std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
-}
-
-static inline void trim_end_spaces(string &s) {
-    while (s.length() > 0 && ' ' == s.back()) {
-        s.pop_back();
-    }
-}
-
-static inline void trim_start(string &s) {
-    s.erase(s.begin(), std::find_if(s.begin(), s.end(),
-            std::not1(std::ptr_fun<int, int>(std::isspace))));
-}
-
-static inline void trim_start_end(string& s) {
-    trim_start(s);
-    trim_end(s);
-}
-
-static inline string trim_inline_spaces(string s) {
-    bool lastSpace = false;
-    string trimmed;
-    for (const char* ptr = &s.front(); ptr <= &s.back(); ++ptr) {
-        char c = *ptr;
-        if (' ' >= c) {
-            if (!lastSpace) {
-                trimmed += ' ';
-            }
-            lastSpace = true;
-            continue;
-        }
-        lastSpace = false;
-        trimmed += c;
-    }
-    return trimmed;
-}
-
-class EscapeParser : public TextParser {
-public:
-    EscapeParser(const char* start, const char* end) :
-            TextParser("", start, end, 0) {
-        const char* reader = fStart;
-        fStorage = new char[end - start];
-        char* writer = fStorage;
-        while (reader < fEnd) {
-            char ch = *reader++;
-            if (ch != '\\') {
-                *writer++ = ch;
-            } else {
-                char ctrl = *reader++;
-                if (ctrl == 'u') {
-                    unsigned unicode = 0;
-                    for (int i = 0; i < 4; ++i) {
-                        unicode <<= 4;
-                        SkASSERT((reader[0] >= '0' && reader[0] <= '9') ||
-                            (reader[0] >= 'A' && reader[0] <= 'F') ||
-                            (reader[0] >= 'a' && reader[0] <= 'f'));
-                        int nibble = *reader++ - '0';
-                        if (nibble > 9) {
-                            nibble = (nibble & ~('a' - 'A')) - 'A' + '9' + 1;
-                        }
-                        unicode |= nibble;
-                    }
-                    SkASSERT(unicode < 256);
-                    *writer++ = (unsigned char) unicode;
-                } else {
-                    SkASSERT(ctrl == 'n');
-                    *writer++ = '\n';
-                }
-            }
-        }
-        fStart = fLine = fChar = fStorage;
-        fEnd = writer;
-    }
-
-    ~EscapeParser() override {
-        delete fStorage;
-    }
-private:
-    char* fStorage;
-};
-
-// some methods cannot be trivially parsed; look for class-name / ~ / operator
-class MethodParser : public TextParser {
-public:
-    MethodParser(string className, string fileName,
-            const char* start, const char* end, int lineCount)
-        : TextParser(fileName, start, end, lineCount)
-        , fClassName(className) {
-        size_t doubleColons = className.find_last_of("::");
-        if (string::npos != doubleColons) {
-            fLocalName = className.substr(doubleColons + 1);
-            SkASSERT(fLocalName.length() > 0);
-        }
-    }
-
-    ~MethodParser() override {}
-
-    string localName() const {
-        return fLocalName;
-    }
-
-    void setLocalName(string name) {
-        if (name == fClassName) {
-            fLocalName = "";
-        } else {
-            fLocalName = name;
-        }
-    }
-
-    // returns true if close brace was skipped
-    int skipToMethodStart() {
-        if (!fClassName.length()) {
-            return this->skipToAlphaNum();
-        }
-        int braceCount = 0;
-        while (!this->eof() && !isalnum(this->peek()) && '~' != this->peek()) {
-            braceCount += '{' == this->peek();
-            braceCount -= '}' == this->peek();
-            this->next();
-        }
-        return braceCount;
-    }
-
-    void skipToMethodEnd(Resolvable resolvable);
-
-    bool wordEndsWith(const char* str) const {
-        const char* space = this->strnchr(' ', fEnd);
-        if (!space) {
-            return false;
-        }
-        size_t len = strlen(str);
-        if (space < fChar + len) {
-            return false;
-        }
-        return !strncmp(str, space - len, len);
-    }
-
-private:
-    string fClassName;
-    string fLocalName;
-    typedef TextParser INHERITED;
-};
-
-#endif
diff --git a/tools/debugger/DebugCanvas.cpp b/tools/debugger/DebugCanvas.cpp
new file mode 100644
index 0000000..5bf4662
--- /dev/null
+++ b/tools/debugger/DebugCanvas.cpp
@@ -0,0 +1,522 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "DebugCanvas.h"
+#include "DrawCommand.h"
+#include "SkCanvasPriv.h"
+#include "SkClipOpPriv.h"
+#include "SkJSONWriter.h"
+#include "SkPaintFilterCanvas.h"
+#include "SkPicture.h"
+#include "SkRectPriv.h"
+#include "SkTextBlob.h"
+
+#include "GrAuditTrail.h"
+#include "GrContext.h"
+#include "GrContextPriv.h"
+#include "GrRenderTargetContext.h"
+
+#define SKDEBUGCANVAS_VERSION 1
+#define SKDEBUGCANVAS_ATTRIBUTE_VERSION "version"
+#define SKDEBUGCANVAS_ATTRIBUTE_COMMANDS "commands"
+#define SKDEBUGCANVAS_ATTRIBUTE_AUDITTRAIL "auditTrail"
+
+class DebugPaintFilterCanvas : public SkPaintFilterCanvas {
+public:
+    DebugPaintFilterCanvas(SkCanvas* canvas, bool overdrawViz)
+            : INHERITED(canvas), fOverdrawViz(overdrawViz) {}
+
+protected:
+    bool onFilter(SkTCopyOnFirstWrite<SkPaint>* paint, Type) const override {
+        if (*paint) {
+            if (fOverdrawViz) {
+                paint->writable()->setColor(SK_ColorRED);
+                paint->writable()->setAlpha(0x08);
+                paint->writable()->setBlendMode(SkBlendMode::kSrcOver);
+            }
+        }
+        return true;
+    }
+
+    void onDrawPicture(const SkPicture* picture,
+                       const SkMatrix*  matrix,
+                       const SkPaint*   paint) override {
+        // We need to replay the picture onto this canvas in order to filter its internal paints.
+        this->SkCanvas::onDrawPicture(picture, matrix, paint);
+    }
+
+private:
+    bool fOverdrawViz;
+
+    typedef SkPaintFilterCanvas INHERITED;
+};
+
+DebugCanvas::DebugCanvas(int width, int height)
+        : INHERITED(width, height)
+        , fOverdrawViz(false)
+        , fClipVizColor(SK_ColorTRANSPARENT)
+        , fDrawGpuOpBounds(false) {
+    // SkPicturePlayback uses the base-class' quickReject calls to cull clipped
+    // operations. This can lead to problems in the debugger which expects all
+    // the operations in the captured skp to appear in the debug canvas. To
+    // circumvent this we create a wide open clip here (an empty clip rect
+    // is not sufficient).
+    // Internally, the SkRect passed to clipRect is converted to an SkIRect and
+    // rounded out. The following code creates a nearly maximal rect that will
+    // not get collapsed by the coming conversions (Due to precision loss the
+    // inset has to be surprisingly large).
+    SkIRect largeIRect = SkRectPriv::MakeILarge();
+    largeIRect.inset(1024, 1024);
+    SkRect large = SkRect::Make(largeIRect);
+#ifdef SK_DEBUG
+    SkASSERT(!large.roundOut().isEmpty());
+#endif
+    // call the base class' version to avoid adding a draw command
+    this->INHERITED::onClipRect(large, kReplace_SkClipOp, kHard_ClipEdgeStyle);
+}
+
+DebugCanvas::DebugCanvas(SkIRect bounds) { DebugCanvas(bounds.width(), bounds.height()); }
+
+DebugCanvas::~DebugCanvas() { fCommandVector.deleteAll(); }
+
+void DebugCanvas::addDrawCommand(DrawCommand* command) { fCommandVector.push_back(command); }
+
+void DebugCanvas::draw(SkCanvas* canvas) {
+    if (!fCommandVector.isEmpty()) {
+        this->drawTo(canvas, fCommandVector.count() - 1);
+    }
+}
+
+void DebugCanvas::drawTo(SkCanvas* originalCanvas, int index, int m) {
+    SkASSERT(!fCommandVector.isEmpty());
+    SkASSERT(index < fCommandVector.count());
+
+    int saveCount = originalCanvas->save();
+
+    SkRect windowRect = SkRect::MakeWH(SkIntToScalar(originalCanvas->getBaseLayerSize().width()),
+                                       SkIntToScalar(originalCanvas->getBaseLayerSize().height()));
+
+    originalCanvas->clear(SK_ColorTRANSPARENT);
+    originalCanvas->resetMatrix();
+    if (!windowRect.isEmpty()) {
+        originalCanvas->clipRect(windowRect, kReplace_SkClipOp);
+    }
+
+    DebugPaintFilterCanvas filterCanvas(originalCanvas, fOverdrawViz);
+
+    // If we have a GPU backend we can also visualize the op information
+    GrAuditTrail* at = nullptr;
+    if (fDrawGpuOpBounds || m != -1) {
+        // The audit trail must be obtained from the original canvas.
+        at = this->getAuditTrail(originalCanvas);
+    }
+
+    for (int i = 0; i <= index; i++) {
+        // We need to flush any pending operations, or they might combine with commands below.
+        // Previous operations were not registered with the audit trail when they were
+        // created, so if we allow them to combine, the audit trail will fail to find them.
+        filterCanvas.flush();
+
+        GrAuditTrail::AutoCollectOps* acb = nullptr;
+        if (at) {
+            acb = new GrAuditTrail::AutoCollectOps(at, i);
+        }
+
+        if (fCommandVector[i]->isVisible()) {
+            fCommandVector[i]->execute(&filterCanvas);
+        }
+        if (at && acb) {
+            delete acb;
+        }
+    }
+
+    if (SkColorGetA(fClipVizColor) != 0) {
+        filterCanvas.save();
+#define LARGE_COORD 1000000000
+        filterCanvas.clipRect(
+                SkRect::MakeLTRB(-LARGE_COORD, -LARGE_COORD, LARGE_COORD, LARGE_COORD),
+                kReverseDifference_SkClipOp);
+        SkPaint clipPaint;
+        clipPaint.setColor(fClipVizColor);
+        filterCanvas.drawPaint(clipPaint);
+        filterCanvas.restore();
+    }
+
+    fMatrix = filterCanvas.getTotalMatrix();
+    fClip   = filterCanvas.getDeviceClipBounds();
+    filterCanvas.restoreToCount(saveCount);
+
+    // draw any ops if required and issue a full reset onto GrAuditTrail
+    if (at) {
+        // just in case there is global reordering, we flush the canvas before querying
+        // GrAuditTrail
+        GrAuditTrail::AutoEnable ae(at);
+        filterCanvas.flush();
+
+        // we pick three colorblind-safe colors, 75% alpha
+        static const SkColor kTotalBounds     = SkColorSetARGB(0xC0, 0x6A, 0x3D, 0x9A);
+        static const SkColor kCommandOpBounds = SkColorSetARGB(0xC0, 0xE3, 0x1A, 0x1C);
+        static const SkColor kOtherOpBounds   = SkColorSetARGB(0xC0, 0xFF, 0x7F, 0x00);
+
+        // get the render target of the top device (from the original canvas) so we can ignore ops
+        // drawn offscreen
+        GrRenderTargetContext* rtc =
+                originalCanvas->internal_private_accessTopLayerRenderTargetContext();
+        GrSurfaceProxy::UniqueID proxyID = rtc->asSurfaceProxy()->uniqueID();
+
+        // get the bounding boxes to draw
+        SkTArray<GrAuditTrail::OpInfo> childrenBounds;
+        if (m == -1) {
+            at->getBoundsByClientID(&childrenBounds, index);
+        } else {
+            // the client wants us to draw the mth op
+            at->getBoundsByOpListID(&childrenBounds.push_back(), m);
+        }
+        SkPaint paint;
+        paint.setStyle(SkPaint::kStroke_Style);
+        paint.setStrokeWidth(1);
+        for (int i = 0; i < childrenBounds.count(); i++) {
+            if (childrenBounds[i].fProxyUniqueID != proxyID) {
+                // offscreen draw, ignore for now
+                continue;
+            }
+            paint.setColor(kTotalBounds);
+            filterCanvas.drawRect(childrenBounds[i].fBounds, paint);
+            for (int j = 0; j < childrenBounds[i].fOps.count(); j++) {
+                const GrAuditTrail::OpInfo::Op& op = childrenBounds[i].fOps[j];
+                if (op.fClientID != index) {
+                    paint.setColor(kOtherOpBounds);
+                } else {
+                    paint.setColor(kCommandOpBounds);
+                }
+                filterCanvas.drawRect(op.fBounds, paint);
+            }
+        }
+    }
+    this->cleanupAuditTrail(originalCanvas);
+}
+
+void DebugCanvas::deleteDrawCommandAt(int index) {
+    SkASSERT(index < fCommandVector.count());
+    delete fCommandVector[index];
+    fCommandVector.remove(index);
+}
+
+DrawCommand* DebugCanvas::getDrawCommandAt(int index) {
+    SkASSERT(index < fCommandVector.count());
+    return fCommandVector[index];
+}
+
+GrAuditTrail* DebugCanvas::getAuditTrail(SkCanvas* canvas) {
+    GrAuditTrail* at  = nullptr;
+    GrContext*    ctx = canvas->getGrContext();
+    if (ctx) {
+        at = ctx->priv().auditTrail();
+    }
+    return at;
+}
+
+void DebugCanvas::drawAndCollectOps(int n, SkCanvas* canvas) {
+    GrAuditTrail* at = this->getAuditTrail(canvas);
+    if (at) {
+        // loop over all of the commands and draw them, this is to collect reordering
+        // information
+        for (int i = 0; i < this->getSize() && i <= n; i++) {
+            GrAuditTrail::AutoCollectOps enable(at, i);
+            fCommandVector[i]->execute(canvas);
+        }
+
+        // in case there is some kind of global reordering
+        {
+            GrAuditTrail::AutoEnable ae(at);
+            canvas->flush();
+        }
+    }
+}
+
+void DebugCanvas::cleanupAuditTrail(SkCanvas* canvas) {
+    GrAuditTrail* at = this->getAuditTrail(canvas);
+    if (at) {
+        GrAuditTrail::AutoEnable ae(at);
+        at->fullReset();
+    }
+}
+
+void DebugCanvas::toJSON(SkJSONWriter&   writer,
+                         UrlDataManager& urlDataManager,
+                         int             n,
+                         SkCanvas*       canvas) {
+    this->drawAndCollectOps(n, canvas);
+
+    // now collect json
+    GrAuditTrail* at = this->getAuditTrail(canvas);
+    writer.appendS32(SKDEBUGCANVAS_ATTRIBUTE_VERSION, SKDEBUGCANVAS_VERSION);
+    writer.beginArray(SKDEBUGCANVAS_ATTRIBUTE_COMMANDS);
+
+    for (int i = 0; i < this->getSize() && i <= n; i++) {
+        writer.beginObject();  // command
+        this->getDrawCommandAt(i)->toJSON(writer, urlDataManager);
+
+        if (at) {
+            writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_AUDITTRAIL);
+            at->toJson(writer, i);
+        }
+        writer.endObject();  // command
+    }
+
+    writer.endArray();  // commands
+    this->cleanupAuditTrail(canvas);
+}
+
+void DebugCanvas::toJSONOpList(SkJSONWriter& writer, int n, SkCanvas* canvas) {
+    this->drawAndCollectOps(n, canvas);
+
+    GrAuditTrail* at = this->getAuditTrail(canvas);
+    if (at) {
+        GrAuditTrail::AutoManageOpList enable(at);
+        at->toJson(writer);
+    } else {
+        writer.beginObject();
+        writer.endObject();
+    }
+    this->cleanupAuditTrail(canvas);
+}
+
+void DebugCanvas::setOverdrawViz(bool overdrawViz) { fOverdrawViz = overdrawViz; }
+
+void DebugCanvas::onClipPath(const SkPath& path, SkClipOp op, ClipEdgeStyle edgeStyle) {
+    this->addDrawCommand(new ClipPathCommand(path, op, kSoft_ClipEdgeStyle == edgeStyle));
+}
+
+void DebugCanvas::onClipRect(const SkRect& rect, SkClipOp op, ClipEdgeStyle edgeStyle) {
+    this->addDrawCommand(new ClipRectCommand(rect, op, kSoft_ClipEdgeStyle == edgeStyle));
+}
+
+void DebugCanvas::onClipRRect(const SkRRect& rrect, SkClipOp op, ClipEdgeStyle edgeStyle) {
+    this->addDrawCommand(new ClipRRectCommand(rrect, op, kSoft_ClipEdgeStyle == edgeStyle));
+}
+
+void DebugCanvas::onClipRegion(const SkRegion& region, SkClipOp op) {
+    this->addDrawCommand(new ClipRegionCommand(region, op));
+}
+
+void DebugCanvas::didConcat(const SkMatrix& matrix) {
+    this->addDrawCommand(new ConcatCommand(matrix));
+    this->INHERITED::didConcat(matrix);
+}
+
+void DebugCanvas::onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) {
+    this->addDrawCommand(new DrawAnnotationCommand(rect, key, sk_ref_sp(value)));
+}
+
+void DebugCanvas::onDrawBitmap(const SkBitmap& bitmap,
+                               SkScalar        left,
+                               SkScalar        top,
+                               const SkPaint*  paint) {
+    this->addDrawCommand(new DrawBitmapCommand(bitmap, left, top, paint));
+}
+
+void DebugCanvas::onDrawBitmapLattice(const SkBitmap& bitmap,
+                                      const Lattice&  lattice,
+                                      const SkRect&   dst,
+                                      const SkPaint*  paint) {
+    this->addDrawCommand(new DrawBitmapLatticeCommand(bitmap, lattice, dst, paint));
+}
+
+void DebugCanvas::onDrawBitmapRect(const SkBitmap&   bitmap,
+                                   const SkRect*     src,
+                                   const SkRect&     dst,
+                                   const SkPaint*    paint,
+                                   SrcRectConstraint constraint) {
+    this->addDrawCommand(
+            new DrawBitmapRectCommand(bitmap, src, dst, paint, (SrcRectConstraint)constraint));
+}
+
+void DebugCanvas::onDrawBitmapNine(const SkBitmap& bitmap,
+                                   const SkIRect&  center,
+                                   const SkRect&   dst,
+                                   const SkPaint*  paint) {
+    this->addDrawCommand(new DrawBitmapNineCommand(bitmap, center, dst, paint));
+}
+
+void DebugCanvas::onDrawImage(const SkImage* image,
+                              SkScalar       left,
+                              SkScalar       top,
+                              const SkPaint* paint) {
+    this->addDrawCommand(new DrawImageCommand(image, left, top, paint));
+}
+
+void DebugCanvas::onDrawImageLattice(const SkImage* image,
+                                     const Lattice& lattice,
+                                     const SkRect&  dst,
+                                     const SkPaint* paint) {
+    this->addDrawCommand(new DrawImageLatticeCommand(image, lattice, dst, paint));
+}
+
+void DebugCanvas::onDrawImageRect(const SkImage*    image,
+                                  const SkRect*     src,
+                                  const SkRect&     dst,
+                                  const SkPaint*    paint,
+                                  SrcRectConstraint constraint) {
+    this->addDrawCommand(new DrawImageRectCommand(image, src, dst, paint, constraint));
+}
+
+void DebugCanvas::onDrawImageNine(const SkImage* image,
+                                  const SkIRect& center,
+                                  const SkRect&  dst,
+                                  const SkPaint* paint) {
+    this->addDrawCommand(new DrawImageNineCommand(image, center, dst, paint));
+}
+
+void DebugCanvas::onDrawOval(const SkRect& oval, const SkPaint& paint) {
+    this->addDrawCommand(new DrawOvalCommand(oval, paint));
+}
+
+void DebugCanvas::onDrawArc(const SkRect&  oval,
+                            SkScalar       startAngle,
+                            SkScalar       sweepAngle,
+                            bool           useCenter,
+                            const SkPaint& paint) {
+    this->addDrawCommand(new DrawArcCommand(oval, startAngle, sweepAngle, useCenter, paint));
+}
+
+void DebugCanvas::onDrawPaint(const SkPaint& paint) {
+    this->addDrawCommand(new DrawPaintCommand(paint));
+}
+
+void DebugCanvas::onDrawPath(const SkPath& path, const SkPaint& paint) {
+    this->addDrawCommand(new DrawPathCommand(path, paint));
+}
+
+void DebugCanvas::onDrawRegion(const SkRegion& region, const SkPaint& paint) {
+    this->addDrawCommand(new DrawRegionCommand(region, paint));
+}
+
+void DebugCanvas::onDrawPicture(const SkPicture* picture,
+                                const SkMatrix*  matrix,
+                                const SkPaint*   paint) {
+    this->addDrawCommand(new BeginDrawPictureCommand(picture, matrix, paint));
+    SkAutoCanvasMatrixPaint acmp(this, matrix, paint, picture->cullRect());
+    picture->playback(this);
+    this->addDrawCommand(new EndDrawPictureCommand(SkToBool(matrix) || SkToBool(paint)));
+}
+
+void DebugCanvas::onDrawPoints(PointMode      mode,
+                               size_t         count,
+                               const SkPoint  pts[],
+                               const SkPaint& paint) {
+    this->addDrawCommand(new DrawPointsCommand(mode, count, pts, paint));
+}
+
+void DebugCanvas::onDrawRect(const SkRect& rect, const SkPaint& paint) {
+    // NOTE(chudy): Messing up when renamed to DrawRect... Why?
+    addDrawCommand(new DrawRectCommand(rect, paint));
+}
+
+void DebugCanvas::onDrawRRect(const SkRRect& rrect, const SkPaint& paint) {
+    this->addDrawCommand(new DrawRRectCommand(rrect, paint));
+}
+
+void DebugCanvas::onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) {
+    this->addDrawCommand(new DrawDRRectCommand(outer, inner, paint));
+}
+
+void DebugCanvas::onDrawTextBlob(const SkTextBlob* blob,
+                                 SkScalar          x,
+                                 SkScalar          y,
+                                 const SkPaint&    paint) {
+    this->addDrawCommand(
+            new DrawTextBlobCommand(sk_ref_sp(const_cast<SkTextBlob*>(blob)), x, y, paint));
+}
+
+void DebugCanvas::onDrawPatch(const SkPoint  cubics[12],
+                              const SkColor  colors[4],
+                              const SkPoint  texCoords[4],
+                              SkBlendMode    bmode,
+                              const SkPaint& paint) {
+    this->addDrawCommand(new DrawPatchCommand(cubics, colors, texCoords, bmode, paint));
+}
+
+void DebugCanvas::onDrawVerticesObject(const SkVertices*      vertices,
+                                       const SkVertices::Bone bones[],
+                                       int                    boneCount,
+                                       SkBlendMode            bmode,
+                                       const SkPaint&         paint) {
+    // TODO: ANIMATION NOT LOGGED
+    this->addDrawCommand(
+            new DrawVerticesCommand(sk_ref_sp(const_cast<SkVertices*>(vertices)), bmode, paint));
+}
+
+void DebugCanvas::onDrawAtlas(const SkImage*  image,
+                              const SkRSXform xform[],
+                              const SkRect    tex[],
+                              const SkColor   colors[],
+                              int             count,
+                              SkBlendMode     bmode,
+                              const SkRect*   cull,
+                              const SkPaint*  paint) {
+    this->addDrawCommand(
+            new DrawAtlasCommand(image, xform, tex, colors, count, bmode, cull, paint));
+}
+
+void DebugCanvas::onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) {
+    this->addDrawCommand(new DrawShadowCommand(path, rec));
+}
+
+void DebugCanvas::onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) {
+    this->addDrawCommand(new DrawDrawableCommand(drawable, matrix));
+}
+
+void DebugCanvas::onDrawEdgeAAQuad(const SkRect& rect,
+                                   const SkPoint clip[4],
+                                   QuadAAFlags   aa,
+                                   SkColor       color,
+                                   SkBlendMode   mode) {
+    this->addDrawCommand(new DrawEdgeAAQuadCommand(rect, clip, aa, color, mode));
+}
+
+void DebugCanvas::onDrawEdgeAAImageSet(const ImageSetEntry set[],
+                                       int                 count,
+                                       const SkPoint       dstClips[],
+                                       const SkMatrix      preViewMatrices[],
+                                       const SkPaint*      paint,
+                                       SrcRectConstraint   constraint) {
+    this->addDrawCommand(new DrawEdgeAAImageSetCommand(
+            set, count, dstClips, preViewMatrices, paint, constraint));
+}
+
+void DebugCanvas::willRestore() {
+    this->addDrawCommand(new RestoreCommand());
+    this->INHERITED::willRestore();
+}
+
+void DebugCanvas::willSave() {
+    this->addDrawCommand(new SaveCommand());
+    this->INHERITED::willSave();
+}
+
+SkCanvas::SaveLayerStrategy DebugCanvas::getSaveLayerStrategy(const SaveLayerRec& rec) {
+    this->addDrawCommand(new SaveLayerCommand(rec));
+    (void)this->INHERITED::getSaveLayerStrategy(rec);
+    // No need for a full layer.
+    return kNoLayer_SaveLayerStrategy;
+}
+
+bool DebugCanvas::onDoSaveBehind(const SkRect* subset) {
+    // TODO
+    return false;
+}
+
+void DebugCanvas::didSetMatrix(const SkMatrix& matrix) {
+    this->addDrawCommand(new SetMatrixCommand(matrix));
+    this->INHERITED::didSetMatrix(matrix);
+}
+
+void DebugCanvas::toggleCommand(int index, bool toggle) {
+    SkASSERT(index < fCommandVector.count());
+    fCommandVector[index]->setVisible(toggle);
+}
diff --git a/tools/debugger/DebugCanvas.h b/tools/debugger/DebugCanvas.h
new file mode 100644
index 0000000..6f01459
--- /dev/null
+++ b/tools/debugger/DebugCanvas.h
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKDEBUGCANVAS_H_
+#define SKDEBUGCANVAS_H_
+
+#include "DrawCommand.h"
+#include "SkCanvas.h"
+#include "SkCanvasVirtualEnforcer.h"
+#include "SkPath.h"
+#include "SkPathOps.h"
+#include "SkString.h"
+#include "SkTArray.h"
+#include "SkVertices.h"
+#include "UrlDataManager.h"
+
+class GrAuditTrail;
+class SkNWayCanvas;
+class SkPicture;
+
+class DebugCanvas : public SkCanvasVirtualEnforcer<SkCanvas> {
+public:
+    DebugCanvas(int width, int height);
+
+    DebugCanvas(SkIRect bounds);
+
+    ~DebugCanvas() override;
+
+    /**
+     * Enable or disable overdraw visualization
+     */
+    void setOverdrawViz(bool overdrawViz);
+
+    bool getOverdrawViz() const { return fOverdrawViz; }
+
+    /**
+     * Set the color of the clip visualization. An alpha of zero renders the clip invisible.
+     */
+    void setClipVizColor(SkColor clipVizColor) { this->fClipVizColor = clipVizColor; }
+
+    void setDrawGpuOpBounds(bool drawGpuOpBounds) { fDrawGpuOpBounds = drawGpuOpBounds; }
+
+    bool getDrawGpuOpBounds() const { return fDrawGpuOpBounds; }
+
+    /**
+        Executes all draw calls to the canvas.
+        @param canvas  The canvas being drawn to
+     */
+    void draw(SkCanvas* canvas);
+
+    /**
+        Executes the draw calls up to the specified index.
+        @param canvas  The canvas being drawn to
+        @param index  The index of the final command being executed
+        @param m an optional Mth gpu op to highlight, or -1
+     */
+    void drawTo(SkCanvas* canvas, int index, int m = -1);
+
+    /**
+        Returns the most recently calculated transformation matrix
+     */
+    const SkMatrix& getCurrentMatrix() { return fMatrix; }
+
+    /**
+        Returns the most recently calculated clip
+     */
+    const SkIRect& getCurrentClip() { return fClip; }
+
+    /**
+        Removes the command at the specified index
+        @param index  The index of the command to delete
+     */
+    void deleteDrawCommandAt(int index);
+
+    /**
+        Returns the draw command at the given index.
+        @param index  The index of the command
+     */
+    DrawCommand* getDrawCommandAt(int index);
+
+    /**
+        Returns length of draw command vector.
+     */
+    int getSize() const { return fCommandVector.count(); }
+
+    /**
+        Toggles the visibility / execution of the draw command at index i with
+        the value of toggle.
+     */
+    void toggleCommand(int index, bool toggle);
+
+    /**
+        Returns a JSON object representing up to the Nth draw, where N is less than
+        DebugCanvas::getSize(). The encoder may use the UrlDataManager to store binary data such
+        as images, referring to them via URLs embedded in the JSON.
+     */
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager, int n, SkCanvas*);
+
+    void toJSONOpList(SkJSONWriter& writer, int n, SkCanvas*);
+
+    void detachCommands(SkTDArray<DrawCommand*>* dst) { fCommandVector.swap(*dst); }
+
+protected:
+    void              willSave() override;
+    SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override;
+    bool              onDoSaveBehind(const SkRect*) override;
+    void              willRestore() override;
+
+    void didConcat(const SkMatrix&) override;
+
+    void didSetMatrix(const SkMatrix&) override;
+
+    void onDrawAnnotation(const SkRect&, const char[], SkData*) override;
+    void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override;
+    void onDrawTextBlob(const SkTextBlob* blob,
+                        SkScalar          x,
+                        SkScalar          y,
+                        const SkPaint&    paint) override;
+
+    void onDrawPatch(const SkPoint cubics[12],
+                     const SkColor colors[4],
+                     const SkPoint texCoords[4],
+                     SkBlendMode,
+                     const SkPaint& paint) override;
+    void onDrawPaint(const SkPaint&) override;
+
+    void onDrawRect(const SkRect&, const SkPaint&) override;
+    void onDrawOval(const SkRect&, const SkPaint&) override;
+    void onDrawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&) override;
+    void onDrawRRect(const SkRRect&, const SkPaint&) override;
+    void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) override;
+    void onDrawVerticesObject(const SkVertices*,
+                              const SkVertices::Bone bones[],
+                              int                    boneCount,
+                              SkBlendMode,
+                              const SkPaint&) override;
+    void onDrawPath(const SkPath&, const SkPaint&) override;
+    void onDrawRegion(const SkRegion&, const SkPaint&) override;
+    void onDrawBitmap(const SkBitmap&, SkScalar left, SkScalar top, const SkPaint*) override;
+    void onDrawBitmapLattice(const SkBitmap&,
+                             const Lattice&,
+                             const SkRect&,
+                             const SkPaint*) override;
+    void onDrawBitmapRect(const SkBitmap&,
+                          const SkRect* src,
+                          const SkRect& dst,
+                          const SkPaint*,
+                          SrcRectConstraint) override;
+    void onDrawImage(const SkImage*, SkScalar left, SkScalar top, const SkPaint*) override;
+    void onDrawImageLattice(const SkImage* image,
+                            const Lattice& lattice,
+                            const SkRect&  dst,
+                            const SkPaint* paint) override;
+    void onDrawImageRect(const SkImage*,
+                         const SkRect* src,
+                         const SkRect& dst,
+                         const SkPaint*,
+                         SrcRectConstraint) override;
+    void onDrawBitmapNine(const SkBitmap&,
+                          const SkIRect& center,
+                          const SkRect&  dst,
+                          const SkPaint*) override;
+    void onDrawImageNine(const SkImage*,
+                         const SkIRect& center,
+                         const SkRect&  dst,
+                         const SkPaint*) override;
+    void onDrawAtlas(const SkImage*,
+                     const SkRSXform[],
+                     const SkRect[],
+                     const SkColor[],
+                     int,
+                     SkBlendMode,
+                     const SkRect*,
+                     const SkPaint*) override;
+    void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override;
+    void onClipRRect(const SkRRect&, SkClipOp, ClipEdgeStyle) override;
+    void onClipPath(const SkPath&, SkClipOp, ClipEdgeStyle) override;
+    void onClipRegion(const SkRegion& region, SkClipOp) override;
+    void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override;
+
+    void onDrawDrawable(SkDrawable*, const SkMatrix*) override;
+    void onDrawPicture(const SkPicture*, const SkMatrix*, const SkPaint*) override;
+
+    void onDrawEdgeAAQuad(const SkRect&,
+                          const SkPoint[4],
+                          QuadAAFlags,
+                          SkColor,
+                          SkBlendMode) override;
+    void onDrawEdgeAAImageSet(const ImageSetEntry[],
+                              int count,
+                              const SkPoint[],
+                              const SkMatrix[],
+                              const SkPaint*,
+                              SrcRectConstraint) override;
+
+private:
+    SkTDArray<DrawCommand*> fCommandVector;
+    SkMatrix                fMatrix;
+    SkIRect                 fClip;
+
+    bool    fOverdrawViz;
+    SkColor fClipVizColor;
+    bool    fDrawGpuOpBounds;
+
+    /**
+        Adds the command to the class' vector of commands.
+        @param command  The draw command for execution
+     */
+    void addDrawCommand(DrawCommand* command);
+
+    GrAuditTrail* getAuditTrail(SkCanvas*);
+
+    void drawAndCollectOps(int n, SkCanvas*);
+    void cleanupAuditTrail(SkCanvas*);
+
+    typedef SkCanvasVirtualEnforcer<SkCanvas> INHERITED;
+};
+
+#endif
diff --git a/tools/debugger/DrawCommand.cpp b/tools/debugger/DrawCommand.cpp
new file mode 100644
index 0000000..f8dd5e7
--- /dev/null
+++ b/tools/debugger/DrawCommand.cpp
@@ -0,0 +1,2107 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "DrawCommand.h"
+
+#include <algorithm>
+#include "JsonWriteBuffer.h"
+#include "SkAutoMalloc.h"
+#include "SkCanvasPriv.h"
+#include "SkClipOpPriv.h"
+#include "SkColorFilter.h"
+#include "SkDashPathEffect.h"
+#include "SkDrawable.h"
+#include "SkImageFilter.h"
+#include "SkLatticeIter.h"
+#include "SkMaskFilterBase.h"
+#include "SkPaintDefaults.h"
+#include "SkPathEffect.h"
+#include "SkPicture.h"
+#include "SkPngEncoder.h"
+#include "SkReadBuffer.h"
+#include "SkRectPriv.h"
+#include "SkShadowFlags.h"
+#include "SkTHash.h"
+#include "SkTextBlobPriv.h"
+#include "SkTypeface.h"
+#include "SkWriteBuffer.h"
+
+#define DEBUGCANVAS_ATTRIBUTE_COMMAND "command"
+#define DEBUGCANVAS_ATTRIBUTE_VISIBLE "visible"
+#define DEBUGCANVAS_ATTRIBUTE_MATRIX "matrix"
+#define DEBUGCANVAS_ATTRIBUTE_DRAWDEPTHTRANS "drawDepthTranslation"
+#define DEBUGCANVAS_ATTRIBUTE_COORDS "coords"
+#define DEBUGCANVAS_ATTRIBUTE_EDGING "edging"
+#define DEBUGCANVAS_ATTRIBUTE_HINTING "hinting"
+#define DEBUGCANVAS_ATTRIBUTE_BOUNDS "bounds"
+#define DEBUGCANVAS_ATTRIBUTE_PAINT "paint"
+#define DEBUGCANVAS_ATTRIBUTE_OUTER "outer"
+#define DEBUGCANVAS_ATTRIBUTE_INNER "inner"
+#define DEBUGCANVAS_ATTRIBUTE_MODE "mode"
+#define DEBUGCANVAS_ATTRIBUTE_POINTS "points"
+#define DEBUGCANVAS_ATTRIBUTE_PATH "path"
+#define DEBUGCANVAS_ATTRIBUTE_TEXT "text"
+#define DEBUGCANVAS_ATTRIBUTE_COLOR "color"
+#define DEBUGCANVAS_ATTRIBUTE_ALPHA "alpha"
+#define DEBUGCANVAS_ATTRIBUTE_BLENDMODE "blendMode"
+#define DEBUGCANVAS_ATTRIBUTE_STYLE "style"
+#define DEBUGCANVAS_ATTRIBUTE_STROKEWIDTH "strokeWidth"
+#define DEBUGCANVAS_ATTRIBUTE_STROKEMITER "strokeMiter"
+#define DEBUGCANVAS_ATTRIBUTE_STROKEJOIN "strokeJoin"
+#define DEBUGCANVAS_ATTRIBUTE_CAP "cap"
+#define DEBUGCANVAS_ATTRIBUTE_ANTIALIAS "antiAlias"
+#define DEBUGCANVAS_ATTRIBUTE_DITHER "dither"
+#define DEBUGCANVAS_ATTRIBUTE_FAKEBOLDTEXT "fakeBoldText"
+#define DEBUGCANVAS_ATTRIBUTE_LINEARTEXT "linearText"
+#define DEBUGCANVAS_ATTRIBUTE_SUBPIXELTEXT "subpixelText"
+#define DEBUGCANVAS_ATTRIBUTE_DEVKERNTEXT "devKernText"
+#define DEBUGCANVAS_ATTRIBUTE_LCDRENDERTEXT "lcdRenderText"
+#define DEBUGCANVAS_ATTRIBUTE_EMBEDDEDBITMAPTEXT "embeddedBitmapText"
+#define DEBUGCANVAS_ATTRIBUTE_AUTOHINTING "forceAutoHinting"
+#define DEBUGCANVAS_ATTRIBUTE_REGION "region"
+#define DEBUGCANVAS_ATTRIBUTE_REGIONOP "op"
+#define DEBUGCANVAS_ATTRIBUTE_EDGESTYLE "edgeStyle"
+#define DEBUGCANVAS_ATTRIBUTE_DEVICEREGION "deviceRegion"
+#define DEBUGCANVAS_ATTRIBUTE_BLUR "blur"
+#define DEBUGCANVAS_ATTRIBUTE_SIGMA "sigma"
+#define DEBUGCANVAS_ATTRIBUTE_QUALITY "quality"
+#define DEBUGCANVAS_ATTRIBUTE_TEXTSIZE "textSize"
+#define DEBUGCANVAS_ATTRIBUTE_TEXTSCALEX "textScaleX"
+#define DEBUGCANVAS_ATTRIBUTE_TEXTSKEWX "textSkewX"
+#define DEBUGCANVAS_ATTRIBUTE_DASHING "dashing"
+#define DEBUGCANVAS_ATTRIBUTE_INTERVALS "intervals"
+#define DEBUGCANVAS_ATTRIBUTE_PHASE "phase"
+#define DEBUGCANVAS_ATTRIBUTE_FILLTYPE "fillType"
+#define DEBUGCANVAS_ATTRIBUTE_VERBS "verbs"
+#define DEBUGCANVAS_ATTRIBUTE_NAME "name"
+#define DEBUGCANVAS_ATTRIBUTE_DATA "data"
+#define DEBUGCANVAS_ATTRIBUTE_VALUES "values"
+#define DEBUGCANVAS_ATTRIBUTE_SHADER "shader"
+#define DEBUGCANVAS_ATTRIBUTE_PATHEFFECT "pathEffect"
+#define DEBUGCANVAS_ATTRIBUTE_MASKFILTER "maskFilter"
+#define DEBUGCANVAS_ATTRIBUTE_XFERMODE "xfermode"
+#define DEBUGCANVAS_ATTRIBUTE_LOOPER "looper"
+#define DEBUGCANVAS_ATTRIBUTE_BACKDROP "backdrop"
+#define DEBUGCANVAS_ATTRIBUTE_COLORFILTER "colorfilter"
+#define DEBUGCANVAS_ATTRIBUTE_IMAGEFILTER "imagefilter"
+#define DEBUGCANVAS_ATTRIBUTE_IMAGE "image"
+#define DEBUGCANVAS_ATTRIBUTE_BITMAP "bitmap"
+#define DEBUGCANVAS_ATTRIBUTE_SRC "src"
+#define DEBUGCANVAS_ATTRIBUTE_DST "dst"
+#define DEBUGCANVAS_ATTRIBUTE_CENTER "center"
+#define DEBUGCANVAS_ATTRIBUTE_STRICT "strict"
+#define DEBUGCANVAS_ATTRIBUTE_DESCRIPTION "description"
+#define DEBUGCANVAS_ATTRIBUTE_X "x"
+#define DEBUGCANVAS_ATTRIBUTE_Y "y"
+#define DEBUGCANVAS_ATTRIBUTE_RUNS "runs"
+#define DEBUGCANVAS_ATTRIBUTE_POSITIONS "positions"
+#define DEBUGCANVAS_ATTRIBUTE_GLYPHS "glyphs"
+#define DEBUGCANVAS_ATTRIBUTE_FONT "font"
+#define DEBUGCANVAS_ATTRIBUTE_TYPEFACE "typeface"
+#define DEBUGCANVAS_ATTRIBUTE_CUBICS "cubics"
+#define DEBUGCANVAS_ATTRIBUTE_COLORS "colors"
+#define DEBUGCANVAS_ATTRIBUTE_TEXTURECOORDS "textureCoords"
+#define DEBUGCANVAS_ATTRIBUTE_FILTERQUALITY "filterQuality"
+#define DEBUGCANVAS_ATTRIBUTE_STARTANGLE "startAngle"
+#define DEBUGCANVAS_ATTRIBUTE_SWEEPANGLE "sweepAngle"
+#define DEBUGCANVAS_ATTRIBUTE_USECENTER "useCenter"
+#define DEBUGCANVAS_ATTRIBUTE_SHORTDESC "shortDesc"
+#define DEBUGCANVAS_ATTRIBUTE_UNIQUE_ID "uniqueID"
+#define DEBUGCANVAS_ATTRIBUTE_WIDTH "width"
+#define DEBUGCANVAS_ATTRIBUTE_HEIGHT "height"
+#define DEBUGCANVAS_ATTRIBUTE_ALPHA "alpha"
+#define DEBUGCANVAS_ATTRIBUTE_LATTICE "lattice"
+#define DEBUGCANVAS_ATTRIBUTE_LATTICEXCOUNT "xCount"
+#define DEBUGCANVAS_ATTRIBUTE_LATTICEYCOUNT "yCount"
+#define DEBUGCANVAS_ATTRIBUTE_LATTICEXDIVS "xDivs"
+#define DEBUGCANVAS_ATTRIBUTE_LATTICEYDIVS "yDivs"
+#define DEBUGCANVAS_ATTRIBUTE_LATTICEFLAGS "flags"
+#define DEBUGCANVAS_ATTRIBUTE_ZPLANE "zPlane"
+#define DEBUGCANVAS_ATTRIBUTE_LIGHTPOSITION "lightPositions"
+#define DEBUGCANVAS_ATTRIBUTE_AMBIENTCOLOR "ambientColor"
+#define DEBUGCANVAS_ATTRIBUTE_SPOTCOLOR "spotColor"
+#define DEBUGCANVAS_ATTRIBUTE_LIGHTRADIUS "lightRadius"
+
+#define DEBUGCANVAS_VERB_MOVE "move"
+#define DEBUGCANVAS_VERB_LINE "line"
+#define DEBUGCANVAS_VERB_QUAD "quad"
+#define DEBUGCANVAS_VERB_CUBIC "cubic"
+#define DEBUGCANVAS_VERB_CONIC "conic"
+#define DEBUGCANVAS_VERB_CLOSE "close"
+
+#define DEBUGCANVAS_STYLE_FILL "fill"
+#define DEBUGCANVAS_STYLE_STROKE "stroke"
+#define DEBUGCANVAS_STYLE_STROKEANDFILL "strokeAndFill"
+
+#define DEBUGCANVAS_POINTMODE_POINTS "points"
+#define DEBUGCANVAS_POINTMODE_LINES "lines"
+#define DEBUGCANVAS_POINTMODE_POLYGON "polygon"
+
+#define DEBUGCANVAS_REGIONOP_DIFFERENCE "difference"
+#define DEBUGCANVAS_REGIONOP_INTERSECT "intersect"
+#define DEBUGCANVAS_REGIONOP_UNION "union"
+#define DEBUGCANVAS_REGIONOP_XOR "xor"
+#define DEBUGCANVAS_REGIONOP_REVERSE_DIFFERENCE "reverseDifference"
+#define DEBUGCANVAS_REGIONOP_REPLACE "replace"
+
+#define DEBUGCANVAS_BLURSTYLE_NORMAL "normal"
+#define DEBUGCANVAS_BLURSTYLE_SOLID "solid"
+#define DEBUGCANVAS_BLURSTYLE_OUTER "outer"
+#define DEBUGCANVAS_BLURSTYLE_INNER "inner"
+
+#define DEBUGCANVAS_BLURQUALITY_LOW "low"
+#define DEBUGCANVAS_BLURQUALITY_HIGH "high"
+
+#define DEBUGCANVAS_FILLTYPE_WINDING "winding"
+#define DEBUGCANVAS_FILLTYPE_EVENODD "evenOdd"
+#define DEBUGCANVAS_FILLTYPE_INVERSEWINDING "inverseWinding"
+#define DEBUGCANVAS_FILLTYPE_INVERSEEVENODD "inverseEvenOdd"
+
+#define DEBUGCANVAS_CAP_BUTT "butt"
+#define DEBUGCANVAS_CAP_ROUND "round"
+#define DEBUGCANVAS_CAP_SQUARE "square"
+
+#define DEBUGCANVAS_MITER_JOIN "miter"
+#define DEBUGCANVAS_ROUND_JOIN "round"
+#define DEBUGCANVAS_BEVEL_JOIN "bevel"
+
+#define DEBUGCANVAS_COLORTYPE_ARGB4444 "ARGB4444"
+#define DEBUGCANVAS_COLORTYPE_RGBA8888 "RGBA8888"
+#define DEBUGCANVAS_COLORTYPE_BGRA8888 "BGRA8888"
+#define DEBUGCANVAS_COLORTYPE_565 "565"
+#define DEBUGCANVAS_COLORTYPE_GRAY8 "Gray8"
+#define DEBUGCANVAS_COLORTYPE_INDEX8 "Index8"
+#define DEBUGCANVAS_COLORTYPE_ALPHA8 "Alpha8"
+
+#define DEBUGCANVAS_ALPHATYPE_OPAQUE "opaque"
+#define DEBUGCANVAS_ALPHATYPE_PREMUL "premul"
+#define DEBUGCANVAS_ALPHATYPE_UNPREMUL "unpremul"
+#define DEBUGCANVAS_ALPHATYPE_UNKNOWN "unknown"
+
+#define DEBUGCANVAS_FILTERQUALITY_NONE "none"
+#define DEBUGCANVAS_FILTERQUALITY_LOW "low"
+#define DEBUGCANVAS_FILTERQUALITY_MEDIUM "medium"
+#define DEBUGCANVAS_FILTERQUALITY_HIGH "high"
+
+#define DEBUGCANVAS_HINTING_NONE "none"
+#define DEBUGCANVAS_HINTING_SLIGHT "slight"
+#define DEBUGCANVAS_HINTING_NORMAL "normal"
+#define DEBUGCANVAS_HINTING_FULL "full"
+
+#define DEBUGCANVAS_EDGING_ALIAS "alias"
+#define DEBUGCANVAS_EDGING_ANTIALIAS "antialias"
+#define DEBUGCANVAS_EDGING_SUBPIXELANTIALIAS "subpixelantialias"
+
+#define DEBUGCANVAS_SHADOWFLAG_TRANSPARENT_OCC "transparentOccluder"
+#define DEBUGCANVAS_SHADOWFLAG_GEOMETRIC_ONLY "geometricOnly"
+
+static SkString* str_append(SkString* str, const SkRect& r) {
+    str->appendf(" [%g %g %g %g]", r.left(), r.top(), r.right(), r.bottom());
+    return str;
+}
+
+DrawCommand::DrawCommand(OpType type) : fOpType(type), fVisible(true) {}
+
+const char* DrawCommand::GetCommandString(OpType type) {
+    switch (type) {
+        case kBeginDrawPicture_OpType: return "BeginDrawPicture";
+        case kClear_OpType: return "DrawClear";
+        case kClipPath_OpType: return "ClipPath";
+        case kClipRegion_OpType: return "ClipRegion";
+        case kClipRect_OpType: return "ClipRect";
+        case kClipRRect_OpType: return "ClipRRect";
+        case kConcat_OpType: return "Concat";
+        case kDrawAnnotation_OpType: return "DrawAnnotation";
+        case kDrawBitmap_OpType: return "DrawBitmap";
+        case kDrawBitmapLattice_OpType: return "DrawBitmapLattice";
+        case kDrawBitmapNine_OpType: return "DrawBitmapNine";
+        case kDrawBitmapRect_OpType: return "DrawBitmapRect";
+        case kDrawDRRect_OpType: return "DrawDRRect";
+        case kDrawImage_OpType: return "DrawImage";
+        case kDrawImageLattice_OpType: return "DrawImageLattice";
+        case kDrawImageNine_OpType: return "DrawImageNine";
+        case kDrawImageRect_OpType: return "DrawImageRect";
+        case kDrawOval_OpType: return "DrawOval";
+        case kDrawPaint_OpType: return "DrawPaint";
+        case kDrawPatch_OpType: return "DrawPatch";
+        case kDrawPath_OpType: return "DrawPath";
+        case kDrawArc_OpType: return "DrawArc";
+        case kDrawPoints_OpType: return "DrawPoints";
+        case kDrawRect_OpType: return "DrawRect";
+        case kDrawRRect_OpType: return "DrawRRect";
+        case kDrawRegion_OpType: return "DrawRegion";
+        case kDrawShadow_OpType: return "DrawShadow";
+        case kDrawTextBlob_OpType: return "DrawTextBlob";
+        case kDrawVertices_OpType: return "DrawVertices";
+        case kDrawAtlas_OpType: return "DrawAtlas";
+        case kDrawDrawable_OpType: return "DrawDrawable";
+        case kDrawEdgeAAQuad_OpType: return "DrawEdgeAAQuad";
+        case kDrawEdgeAAImageSet_OpType: return "DrawEdgeAAImageSet";
+        case kEndDrawPicture_OpType: return "EndDrawPicture";
+        case kRestore_OpType: return "Restore";
+        case kSave_OpType: return "Save";
+        case kSaveLayer_OpType: return "SaveLayer";
+        case kSetMatrix_OpType: return "SetMatrix";
+        default:
+            SkDebugf("OpType error 0x%08x\n", type);
+            SkASSERT(0);
+            break;
+    }
+    SkDEBUGFAIL("DrawType UNUSED\n");
+    return nullptr;
+}
+
+void DrawCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    writer.appendString(DEBUGCANVAS_ATTRIBUTE_COMMAND, this->GetCommandString(fOpType));
+    writer.appendBool(DEBUGCANVAS_ATTRIBUTE_VISIBLE, this->isVisible());
+}
+
+namespace {
+
+void xlate_and_scale_to_bounds(SkCanvas* canvas, const SkRect& bounds) {
+    const SkISize& size = canvas->getBaseLayerSize();
+
+    static const SkScalar kInsetFrac = 0.9f;  // Leave a border around object
+
+    canvas->translate(size.fWidth / 2.0f, size.fHeight / 2.0f);
+    if (bounds.width() > bounds.height()) {
+        canvas->scale(SkDoubleToScalar((kInsetFrac * size.fWidth) / bounds.width()),
+                      SkDoubleToScalar((kInsetFrac * size.fHeight) / bounds.width()));
+    } else {
+        canvas->scale(SkDoubleToScalar((kInsetFrac * size.fWidth) / bounds.height()),
+                      SkDoubleToScalar((kInsetFrac * size.fHeight) / bounds.height()));
+    }
+    canvas->translate(-bounds.centerX(), -bounds.centerY());
+}
+
+void render_path(SkCanvas* canvas, const SkPath& path) {
+    canvas->clear(0xFFFFFFFF);
+
+    const SkRect& bounds = path.getBounds();
+    if (bounds.isEmpty()) {
+        return;
+    }
+
+    SkAutoCanvasRestore acr(canvas, true);
+    xlate_and_scale_to_bounds(canvas, bounds);
+
+    SkPaint p;
+    p.setColor(SK_ColorBLACK);
+    p.setStyle(SkPaint::kStroke_Style);
+
+    canvas->drawPath(path, p);
+}
+
+void render_region(SkCanvas* canvas, const SkRegion& region) {
+    canvas->clear(0xFFFFFFFF);
+
+    const SkIRect& bounds = region.getBounds();
+    if (bounds.isEmpty()) {
+        return;
+    }
+
+    SkAutoCanvasRestore acr(canvas, true);
+    xlate_and_scale_to_bounds(canvas, SkRect::Make(bounds));
+
+    SkPaint p;
+    p.setColor(SK_ColorBLACK);
+    p.setStyle(SkPaint::kStroke_Style);
+
+    canvas->drawRegion(region, p);
+}
+
+void render_bitmap(SkCanvas* canvas, const SkBitmap& input, const SkRect* srcRect = nullptr) {
+    const SkISize& size = canvas->getBaseLayerSize();
+
+    SkScalar xScale = SkIntToScalar(size.fWidth - 2) / input.width();
+    SkScalar yScale = SkIntToScalar(size.fHeight - 2) / input.height();
+
+    if (input.width() > input.height()) {
+        yScale *= input.height() / (float)input.width();
+    } else {
+        xScale *= input.width() / (float)input.height();
+    }
+
+    SkRect dst = SkRect::MakeXYWH(
+            SK_Scalar1, SK_Scalar1, xScale * input.width(), yScale * input.height());
+
+    static const int kNumBlocks = 8;
+
+    canvas->clear(0xFFFFFFFF);
+    SkISize block = {canvas->imageInfo().width() / kNumBlocks,
+                     canvas->imageInfo().height() / kNumBlocks};
+    for (int y = 0; y < kNumBlocks; ++y) {
+        for (int x = 0; x < kNumBlocks; ++x) {
+            SkPaint paint;
+            paint.setColor((x + y) % 2 ? SK_ColorLTGRAY : SK_ColorDKGRAY);
+            SkRect r = SkRect::MakeXYWH(SkIntToScalar(x * block.width()),
+                                        SkIntToScalar(y * block.height()),
+                                        SkIntToScalar(block.width()),
+                                        SkIntToScalar(block.height()));
+            canvas->drawRect(r, paint);
+        }
+    }
+
+    canvas->drawBitmapRect(input, dst, nullptr);
+
+    if (srcRect) {
+        SkRect  r = SkRect::MakeLTRB(srcRect->fLeft * xScale + SK_Scalar1,
+                                    srcRect->fTop * yScale + SK_Scalar1,
+                                    srcRect->fRight * xScale + SK_Scalar1,
+                                    srcRect->fBottom * yScale + SK_Scalar1);
+        SkPaint p;
+        p.setColor(SK_ColorRED);
+        p.setStyle(SkPaint::kStroke_Style);
+
+        canvas->drawRect(r, p);
+    }
+}
+
+void render_rrect(SkCanvas* canvas, const SkRRect& rrect) {
+    canvas->clear(0xFFFFFFFF);
+    canvas->save();
+
+    const SkRect& bounds = rrect.getBounds();
+
+    xlate_and_scale_to_bounds(canvas, bounds);
+
+    SkPaint p;
+    p.setColor(SK_ColorBLACK);
+    p.setStyle(SkPaint::kStroke_Style);
+
+    canvas->drawRRect(rrect, p);
+    canvas->restore();
+}
+
+void render_drrect(SkCanvas* canvas, const SkRRect& outer, const SkRRect& inner) {
+    canvas->clear(0xFFFFFFFF);
+    canvas->save();
+
+    const SkRect& bounds = outer.getBounds();
+
+    xlate_and_scale_to_bounds(canvas, bounds);
+
+    SkPaint p;
+    p.setColor(SK_ColorBLACK);
+    p.setStyle(SkPaint::kStroke_Style);
+
+    canvas->drawDRRect(outer, inner, p);
+    canvas->restore();
+}
+
+void render_shadow(SkCanvas* canvas, const SkPath& path, SkDrawShadowRec rec) {
+    canvas->clear(0xFFFFFFFF);
+
+    const SkRect& bounds = path.getBounds();
+    if (bounds.isEmpty()) {
+        return;
+    }
+
+    SkAutoCanvasRestore acr(canvas, true);
+    xlate_and_scale_to_bounds(canvas, bounds);
+
+    rec.fAmbientColor = SK_ColorBLACK;
+    rec.fSpotColor    = SK_ColorBLACK;
+    canvas->private_draw_shadow_rec(path, rec);
+}
+
+static const char* const gBlendModeMap[] = {
+        "clear",      "src",        "dst",      "srcOver",    "dstOver",   "srcIn",     "dstIn",
+        "srcOut",     "dstOut",     "srcATop",  "dstATop",    "xor",       "plus",      "modulate",
+
+        "screen",
+
+        "overlay",    "darken",     "lighten",  "colorDodge", "colorBurn", "hardLight", "softLight",
+        "difference", "exclusion",  "multiply",
+
+        "hue",        "saturation", "color",    "luminosity",
+};
+
+static_assert(SK_ARRAY_COUNT(gBlendModeMap) == static_cast<size_t>(SkBlendMode::kLastMode) + 1,
+              "blendMode mismatch");
+static_assert(SK_ARRAY_COUNT(gBlendModeMap) == static_cast<size_t>(SkBlendMode::kLuminosity) + 1,
+              "blendMode mismatch");
+
+void apply_paint_blend_mode(const SkPaint& paint, SkJSONWriter& writer) {
+    const auto mode = paint.getBlendMode();
+    if (mode != SkBlendMode::kSrcOver) {
+        SkASSERT(static_cast<size_t>(mode) < SK_ARRAY_COUNT(gBlendModeMap));
+        writer.appendString(DEBUGCANVAS_ATTRIBUTE_BLENDMODE,
+                            gBlendModeMap[static_cast<size_t>(mode)]);
+    }
+}
+
+};  // namespace
+
+void DrawCommand::MakeJsonColor(SkJSONWriter& writer, const SkColor color) {
+    writer.beginArray(nullptr, false);
+    writer.appendS32(SkColorGetA(color));
+    writer.appendS32(SkColorGetR(color));
+    writer.appendS32(SkColorGetG(color));
+    writer.appendS32(SkColorGetB(color));
+    writer.endArray();
+}
+
+void DrawCommand::MakeJsonColor4f(SkJSONWriter& writer, const SkColor4f& color) {
+    writer.beginArray(nullptr, false);
+    writer.appendFloat(color.fA);
+    writer.appendFloat(color.fR);
+    writer.appendFloat(color.fG);
+    writer.appendFloat(color.fB);
+    writer.endArray();
+}
+
+void DrawCommand::MakeJsonPoint(SkJSONWriter& writer, const SkPoint& point) {
+    writer.beginArray(nullptr, false);
+    writer.appendFloat(point.x());
+    writer.appendFloat(point.y());
+    writer.endArray();
+}
+
+void DrawCommand::MakeJsonPoint(SkJSONWriter& writer, SkScalar x, SkScalar y) {
+    writer.beginArray(nullptr, false);
+    writer.appendFloat(x);
+    writer.appendFloat(y);
+    writer.endArray();
+}
+
+void DrawCommand::MakeJsonPoint3(SkJSONWriter& writer, const SkPoint3& point) {
+    writer.beginArray(nullptr, false);
+    writer.appendFloat(point.x());
+    writer.appendFloat(point.y());
+    writer.appendFloat(point.z());
+    writer.endArray();
+}
+
+void DrawCommand::MakeJsonRect(SkJSONWriter& writer, const SkRect& rect) {
+    writer.beginArray(nullptr, false);
+    writer.appendFloat(rect.left());
+    writer.appendFloat(rect.top());
+    writer.appendFloat(rect.right());
+    writer.appendFloat(rect.bottom());
+    writer.endArray();
+}
+
+void DrawCommand::MakeJsonIRect(SkJSONWriter& writer, const SkIRect& rect) {
+    writer.beginArray(nullptr, false);
+    writer.appendS32(rect.left());
+    writer.appendS32(rect.top());
+    writer.appendS32(rect.right());
+    writer.appendS32(rect.bottom());
+    writer.endArray();
+}
+
+static void make_json_rrect(SkJSONWriter& writer, const SkRRect& rrect) {
+    writer.beginArray(nullptr, false);
+    DrawCommand::MakeJsonRect(writer, rrect.rect());
+    DrawCommand::MakeJsonPoint(writer, rrect.radii(SkRRect::kUpperLeft_Corner));
+    DrawCommand::MakeJsonPoint(writer, rrect.radii(SkRRect::kUpperRight_Corner));
+    DrawCommand::MakeJsonPoint(writer, rrect.radii(SkRRect::kLowerRight_Corner));
+    DrawCommand::MakeJsonPoint(writer, rrect.radii(SkRRect::kLowerLeft_Corner));
+    writer.endArray();
+}
+
+void DrawCommand::MakeJsonMatrix(SkJSONWriter& writer, const SkMatrix& matrix) {
+    writer.beginArray();
+    for (int r = 0; r < 3; ++r) {
+        writer.beginArray(nullptr, false);
+        for (int c = 0; c < 3; ++c) {
+            writer.appendFloat(matrix[r * 3 + c]);
+        }
+        writer.endArray();
+    }
+    writer.endArray();
+}
+
+void DrawCommand::MakeJsonPath(SkJSONWriter& writer, const SkPath& path) {
+    writer.beginObject();
+    switch (path.getFillType()) {
+        case SkPath::kWinding_FillType:
+            writer.appendString(DEBUGCANVAS_ATTRIBUTE_FILLTYPE, DEBUGCANVAS_FILLTYPE_WINDING);
+            break;
+        case SkPath::kEvenOdd_FillType:
+            writer.appendString(DEBUGCANVAS_ATTRIBUTE_FILLTYPE, DEBUGCANVAS_FILLTYPE_EVENODD);
+            break;
+        case SkPath::kInverseWinding_FillType:
+            writer.appendString(DEBUGCANVAS_ATTRIBUTE_FILLTYPE,
+                                DEBUGCANVAS_FILLTYPE_INVERSEWINDING);
+            break;
+        case SkPath::kInverseEvenOdd_FillType:
+            writer.appendString(DEBUGCANVAS_ATTRIBUTE_FILLTYPE,
+                                DEBUGCANVAS_FILLTYPE_INVERSEEVENODD);
+            break;
+    }
+    writer.beginArray(DEBUGCANVAS_ATTRIBUTE_VERBS);
+    SkPath::Iter iter(path, false);
+    SkPoint      pts[4];
+    SkPath::Verb verb;
+    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+        if (verb == SkPath::kClose_Verb) {
+            writer.appendString(DEBUGCANVAS_VERB_CLOSE);
+            continue;
+        }
+        writer.beginObject();  // verb
+        switch (verb) {
+            case SkPath::kLine_Verb: {
+                writer.appendName(DEBUGCANVAS_VERB_LINE);
+                MakeJsonPoint(writer, pts[1]);
+                break;
+            }
+            case SkPath::kQuad_Verb: {
+                writer.beginArray(DEBUGCANVAS_VERB_QUAD);
+                MakeJsonPoint(writer, pts[1]);
+                MakeJsonPoint(writer, pts[2]);
+                writer.endArray();  // quad coords
+                break;
+            }
+            case SkPath::kCubic_Verb: {
+                writer.beginArray(DEBUGCANVAS_VERB_CUBIC);
+                MakeJsonPoint(writer, pts[1]);
+                MakeJsonPoint(writer, pts[2]);
+                MakeJsonPoint(writer, pts[3]);
+                writer.endArray();  // cubic coords
+                break;
+            }
+            case SkPath::kConic_Verb: {
+                writer.beginArray(DEBUGCANVAS_VERB_CONIC);
+                MakeJsonPoint(writer, pts[1]);
+                MakeJsonPoint(writer, pts[2]);
+                writer.appendFloat(iter.conicWeight());
+                writer.endArray();  // conic coords
+                break;
+            }
+            case SkPath::kMove_Verb: {
+                writer.appendName(DEBUGCANVAS_VERB_MOVE);
+                MakeJsonPoint(writer, pts[0]);
+                break;
+            }
+            case SkPath::kClose_Verb:
+            case SkPath::kDone_Verb:
+                // Unreachable
+                break;
+        }
+        writer.endObject();  // verb
+    }
+    writer.endArray();   // verbs
+    writer.endObject();  // path
+}
+
+void DrawCommand::MakeJsonRegion(SkJSONWriter& writer, const SkRegion& region) {
+    // TODO: Actually serialize the rectangles, rather than just devolving to path
+    SkPath path;
+    region.getBoundaryPath(&path);
+    MakeJsonPath(writer, path);
+}
+
+static const char* regionop_name(SkClipOp op) {
+    switch (op) {
+        case kDifference_SkClipOp: return DEBUGCANVAS_REGIONOP_DIFFERENCE;
+        case kIntersect_SkClipOp: return DEBUGCANVAS_REGIONOP_INTERSECT;
+        case kUnion_SkClipOp: return DEBUGCANVAS_REGIONOP_UNION;
+        case kXOR_SkClipOp: return DEBUGCANVAS_REGIONOP_XOR;
+        case kReverseDifference_SkClipOp: return DEBUGCANVAS_REGIONOP_REVERSE_DIFFERENCE;
+        case kReplace_SkClipOp: return DEBUGCANVAS_REGIONOP_REPLACE;
+        default: SkASSERT(false); return "<invalid region op>";
+    }
+}
+
+static const char* pointmode_name(SkCanvas::PointMode mode) {
+    switch (mode) {
+        case SkCanvas::kPoints_PointMode: return DEBUGCANVAS_POINTMODE_POINTS;
+        case SkCanvas::kLines_PointMode: return DEBUGCANVAS_POINTMODE_LINES;
+        case SkCanvas::kPolygon_PointMode: return DEBUGCANVAS_POINTMODE_POLYGON;
+        default: SkASSERT(false); return "<invalid point mode>";
+    }
+}
+
+static void store_scalar(SkJSONWriter& writer,
+                         const char*   key,
+                         SkScalar      value,
+                         SkScalar      defaultValue) {
+    if (value != defaultValue) {
+        writer.appendFloat(key, value);
+    }
+}
+
+static void store_bool(SkJSONWriter& writer, const char* key, bool value, bool defaultValue) {
+    if (value != defaultValue) {
+        writer.appendBool(key, value);
+    }
+}
+
+static SkString encode_data(const void*     bytes,
+                            size_t          count,
+                            const char*     contentType,
+                            UrlDataManager& urlDataManager) {
+    sk_sp<SkData> data(SkData::MakeWithCopy(bytes, count));
+    return urlDataManager.addData(data.get(), contentType);
+}
+
+void DrawCommand::flatten(const SkFlattenable* flattenable,
+                          SkJSONWriter&        writer,
+                          UrlDataManager&      urlDataManager) {
+    SkBinaryWriteBuffer buffer;
+    flattenable->flatten(buffer);
+    void* data = sk_malloc_throw(buffer.bytesWritten());
+    buffer.writeToMemory(data);
+    SkString url =
+            encode_data(data, buffer.bytesWritten(), "application/octet-stream", urlDataManager);
+    writer.appendString(DEBUGCANVAS_ATTRIBUTE_NAME, flattenable->getTypeName());
+    writer.appendString(DEBUGCANVAS_ATTRIBUTE_DATA, url.c_str());
+
+    writer.beginObject(DEBUGCANVAS_ATTRIBUTE_VALUES);
+    JsonWriteBuffer jsonBuffer(&writer, &urlDataManager);
+    flattenable->flatten(jsonBuffer);
+    writer.endObject();  // values
+
+    sk_free(data);
+}
+
+void DrawCommand::WritePNG(SkBitmap bitmap, SkWStream& out) {
+    SkPixmap pm;
+    SkAssertResult(bitmap.peekPixels(&pm));
+
+    SkPngEncoder::Options options;
+    options.fZLibLevel   = 1;
+    options.fFilterFlags = SkPngEncoder::FilterFlag::kNone;
+    SkPngEncoder::Encode(&out, pm, options);
+}
+
+bool DrawCommand::flatten(const SkImage&  image,
+                          SkJSONWriter&   writer,
+                          UrlDataManager& urlDataManager) {
+    size_t       rowBytes = 4 * image.width();
+    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)) {
+        SkDebugf("readPixels failed\n");
+        return false;
+    }
+
+    SkBitmap bm;
+    bm.installPixels(dstInfo, buffer.get(), rowBytes);
+
+    SkDynamicMemoryWStream out;
+    DrawCommand::WritePNG(bm, out);
+    sk_sp<SkData> encoded = out.detachAsData();
+    SkString      url = encode_data(encoded->data(), encoded->size(), "image/png", urlDataManager);
+    writer.appendString(DEBUGCANVAS_ATTRIBUTE_DATA, url.c_str());
+    return true;
+}
+
+static const char* color_type_name(SkColorType colorType) {
+    switch (colorType) {
+        case kARGB_4444_SkColorType: return DEBUGCANVAS_COLORTYPE_ARGB4444;
+        case kRGBA_8888_SkColorType: return DEBUGCANVAS_COLORTYPE_RGBA8888;
+        case kBGRA_8888_SkColorType: return DEBUGCANVAS_COLORTYPE_BGRA8888;
+        case kRGB_565_SkColorType: return DEBUGCANVAS_COLORTYPE_565;
+        case kGray_8_SkColorType: return DEBUGCANVAS_COLORTYPE_GRAY8;
+        case kAlpha_8_SkColorType: return DEBUGCANVAS_COLORTYPE_ALPHA8;
+        default: SkASSERT(false); return DEBUGCANVAS_COLORTYPE_RGBA8888;
+    }
+}
+
+static const char* alpha_type_name(SkAlphaType alphaType) {
+    switch (alphaType) {
+        case kOpaque_SkAlphaType: return DEBUGCANVAS_ALPHATYPE_OPAQUE;
+        case kPremul_SkAlphaType: return DEBUGCANVAS_ALPHATYPE_PREMUL;
+        case kUnpremul_SkAlphaType: return DEBUGCANVAS_ALPHATYPE_UNPREMUL;
+        default: SkASSERT(false); return DEBUGCANVAS_ALPHATYPE_OPAQUE;
+    }
+}
+
+bool DrawCommand::flatten(const SkBitmap& bitmap,
+                          SkJSONWriter&   writer,
+                          UrlDataManager& urlDataManager) {
+    sk_sp<SkImage> image(SkImage::MakeFromBitmap(bitmap));
+    writer.appendString(DEBUGCANVAS_ATTRIBUTE_COLOR, color_type_name(bitmap.colorType()));
+    writer.appendString(DEBUGCANVAS_ATTRIBUTE_ALPHA, alpha_type_name(bitmap.alphaType()));
+    bool success = flatten(*image, writer, urlDataManager);
+    return success;
+}
+
+static void apply_font_hinting(const SkFont& font, SkJSONWriter& writer) {
+    SkFontHinting hinting = font.getHinting();
+    if (hinting != SkPaintDefaults_Hinting) {
+        switch (hinting) {
+            case kNo_SkFontHinting:
+                writer.appendString(DEBUGCANVAS_ATTRIBUTE_HINTING, DEBUGCANVAS_HINTING_NONE);
+                break;
+            case kSlight_SkFontHinting:
+                writer.appendString(DEBUGCANVAS_ATTRIBUTE_HINTING, DEBUGCANVAS_HINTING_SLIGHT);
+                break;
+            case kNormal_SkFontHinting:
+                writer.appendString(DEBUGCANVAS_ATTRIBUTE_HINTING, DEBUGCANVAS_HINTING_NORMAL);
+                break;
+            case kFull_SkFontHinting:
+                writer.appendString(DEBUGCANVAS_ATTRIBUTE_HINTING, DEBUGCANVAS_HINTING_FULL);
+                break;
+        }
+    }
+}
+
+static void apply_font_edging(const SkFont& font, SkJSONWriter& writer) {
+    switch (font.getEdging()) {
+        case SkFont::Edging::kAlias:
+            writer.appendString(DEBUGCANVAS_ATTRIBUTE_EDGING, DEBUGCANVAS_EDGING_ALIAS);
+            break;
+        case SkFont::Edging::kAntiAlias:
+            writer.appendString(DEBUGCANVAS_ATTRIBUTE_EDGING, DEBUGCANVAS_EDGING_ANTIALIAS);
+            break;
+        case SkFont::Edging::kSubpixelAntiAlias:
+            writer.appendString(DEBUGCANVAS_ATTRIBUTE_EDGING, DEBUGCANVAS_EDGING_SUBPIXELANTIALIAS);
+            break;
+    }
+}
+
+static void apply_paint_color(const SkPaint& paint, SkJSONWriter& writer) {
+    SkColor color = paint.getColor();
+    if (color != SK_ColorBLACK) {
+        writer.appendName(DEBUGCANVAS_ATTRIBUTE_COLOR);
+        DrawCommand::MakeJsonColor(writer, color);
+    }
+}
+
+static void apply_paint_style(const SkPaint& paint, SkJSONWriter& writer) {
+    SkPaint::Style style = paint.getStyle();
+    if (style != SkPaint::kFill_Style) {
+        switch (style) {
+            case SkPaint::kStroke_Style: {
+                writer.appendString(DEBUGCANVAS_ATTRIBUTE_STYLE, DEBUGCANVAS_STYLE_STROKE);
+                break;
+            }
+            case SkPaint::kStrokeAndFill_Style: {
+                writer.appendString(DEBUGCANVAS_ATTRIBUTE_STYLE, DEBUGCANVAS_STYLE_STROKEANDFILL);
+                break;
+            }
+            default: SkASSERT(false);
+        }
+    }
+}
+
+static void apply_paint_cap(const SkPaint& paint, SkJSONWriter& writer) {
+    SkPaint::Cap cap = paint.getStrokeCap();
+    if (cap != SkPaint::kDefault_Cap) {
+        switch (cap) {
+            case SkPaint::kButt_Cap:
+                writer.appendString(DEBUGCANVAS_ATTRIBUTE_CAP, DEBUGCANVAS_CAP_BUTT);
+                break;
+            case SkPaint::kRound_Cap:
+                writer.appendString(DEBUGCANVAS_ATTRIBUTE_CAP, DEBUGCANVAS_CAP_ROUND);
+                break;
+            case SkPaint::kSquare_Cap:
+                writer.appendString(DEBUGCANVAS_ATTRIBUTE_CAP, DEBUGCANVAS_CAP_SQUARE);
+                break;
+            default: SkASSERT(false);
+        }
+    }
+}
+
+static void apply_paint_join(const SkPaint& paint, SkJSONWriter& writer) {
+    SkPaint::Join join = paint.getStrokeJoin();
+    if (join != SkPaint::kDefault_Join) {
+        switch (join) {
+            case SkPaint::kMiter_Join:
+                writer.appendString(DEBUGCANVAS_ATTRIBUTE_STROKEJOIN, DEBUGCANVAS_MITER_JOIN);
+                break;
+            case SkPaint::kRound_Join:
+                writer.appendString(DEBUGCANVAS_ATTRIBUTE_STROKEJOIN, DEBUGCANVAS_ROUND_JOIN);
+                break;
+            case SkPaint::kBevel_Join:
+                writer.appendString(DEBUGCANVAS_ATTRIBUTE_STROKEJOIN, DEBUGCANVAS_BEVEL_JOIN);
+                break;
+            default: SkASSERT(false);
+        }
+    }
+}
+
+static void apply_paint_filterquality(const SkPaint& paint, SkJSONWriter& writer) {
+    SkFilterQuality quality = paint.getFilterQuality();
+    switch (quality) {
+        case kNone_SkFilterQuality: break;
+        case kLow_SkFilterQuality:
+            writer.appendString(DEBUGCANVAS_ATTRIBUTE_FILTERQUALITY, DEBUGCANVAS_FILTERQUALITY_LOW);
+            break;
+        case kMedium_SkFilterQuality:
+            writer.appendString(DEBUGCANVAS_ATTRIBUTE_FILTERQUALITY,
+                                DEBUGCANVAS_FILTERQUALITY_MEDIUM);
+            break;
+        case kHigh_SkFilterQuality:
+            writer.appendString(DEBUGCANVAS_ATTRIBUTE_FILTERQUALITY,
+                                DEBUGCANVAS_FILTERQUALITY_HIGH);
+            break;
+    }
+}
+
+static void apply_paint_maskfilter(const SkPaint&  paint,
+                                   SkJSONWriter&   writer,
+                                   UrlDataManager& urlDataManager) {
+    SkMaskFilter* maskFilter = paint.getMaskFilter();
+    if (maskFilter != nullptr) {
+        SkMaskFilterBase::BlurRec blurRec;
+        if (as_MFB(maskFilter)->asABlur(&blurRec)) {
+            writer.beginObject(DEBUGCANVAS_ATTRIBUTE_BLUR);
+            writer.appendFloat(DEBUGCANVAS_ATTRIBUTE_SIGMA, blurRec.fSigma);
+            switch (blurRec.fStyle) {
+                case SkBlurStyle::kNormal_SkBlurStyle:
+                    writer.appendString(DEBUGCANVAS_ATTRIBUTE_STYLE, DEBUGCANVAS_BLURSTYLE_NORMAL);
+                    break;
+                case SkBlurStyle::kSolid_SkBlurStyle:
+                    writer.appendString(DEBUGCANVAS_ATTRIBUTE_STYLE, DEBUGCANVAS_BLURSTYLE_SOLID);
+                    break;
+                case SkBlurStyle::kOuter_SkBlurStyle:
+                    writer.appendString(DEBUGCANVAS_ATTRIBUTE_STYLE, DEBUGCANVAS_BLURSTYLE_OUTER);
+                    break;
+                case SkBlurStyle::kInner_SkBlurStyle:
+                    writer.appendString(DEBUGCANVAS_ATTRIBUTE_STYLE, DEBUGCANVAS_BLURSTYLE_INNER);
+                    break;
+                default: SkASSERT(false);
+            }
+            writer.endObject();  // blur
+        } else {
+            writer.beginObject(DEBUGCANVAS_ATTRIBUTE_MASKFILTER);
+            DrawCommand::flatten(maskFilter, writer, urlDataManager);
+            writer.endObject();  // maskFilter
+        }
+    }
+}
+
+static void apply_paint_patheffect(const SkPaint&  paint,
+                                   SkJSONWriter&   writer,
+                                   UrlDataManager& urlDataManager) {
+    SkPathEffect* pathEffect = paint.getPathEffect();
+    if (pathEffect != nullptr) {
+        SkPathEffect::DashInfo dashInfo;
+        SkPathEffect::DashType dashType = pathEffect->asADash(&dashInfo);
+        if (dashType == SkPathEffect::kDash_DashType) {
+            dashInfo.fIntervals = (SkScalar*)sk_malloc_throw(dashInfo.fCount * sizeof(SkScalar));
+            pathEffect->asADash(&dashInfo);
+            writer.beginObject(DEBUGCANVAS_ATTRIBUTE_DASHING);
+            writer.beginArray(DEBUGCANVAS_ATTRIBUTE_INTERVALS, false);
+            for (int32_t i = 0; i < dashInfo.fCount; i++) {
+                writer.appendFloat(dashInfo.fIntervals[i]);
+            }
+            writer.endArray();  // intervals
+            sk_free(dashInfo.fIntervals);
+            writer.appendFloat(DEBUGCANVAS_ATTRIBUTE_PHASE, dashInfo.fPhase);
+            writer.endObject();  // dashing
+        } else {
+            writer.beginObject(DEBUGCANVAS_ATTRIBUTE_PATHEFFECT);
+            DrawCommand::flatten(pathEffect, writer, urlDataManager);
+            writer.endObject();  // pathEffect
+        }
+    }
+}
+
+static void apply_font_typeface(const SkFont&   font,
+                                SkJSONWriter&   writer,
+                                UrlDataManager& urlDataManager) {
+    SkTypeface* typeface = font.getTypefaceOrDefault();
+    if (typeface != nullptr) {
+        writer.beginObject(DEBUGCANVAS_ATTRIBUTE_TYPEFACE);
+        SkDynamicMemoryWStream buffer;
+        typeface->serialize(&buffer);
+        void* data = sk_malloc_throw(buffer.bytesWritten());
+        buffer.copyTo(data);
+        SkString url = encode_data(
+                data, buffer.bytesWritten(), "application/octet-stream", urlDataManager);
+        writer.appendString(DEBUGCANVAS_ATTRIBUTE_DATA, url.c_str());
+        sk_free(data);
+        writer.endObject();
+    }
+}
+
+static void apply_flattenable(const char*     key,
+                              SkFlattenable*  flattenable,
+                              SkJSONWriter&   writer,
+                              UrlDataManager& urlDataManager) {
+    if (flattenable != nullptr) {
+        writer.beginObject(key);
+        DrawCommand::flatten(flattenable, writer, urlDataManager);
+        writer.endObject();
+    }
+}
+
+void DrawCommand::MakeJsonPaint(SkJSONWriter&   writer,
+                                const SkPaint&  paint,
+                                UrlDataManager& urlDataManager) {
+    writer.beginObject();
+    store_scalar(writer, DEBUGCANVAS_ATTRIBUTE_STROKEWIDTH, paint.getStrokeWidth(), 0.0f);
+    store_scalar(writer,
+                 DEBUGCANVAS_ATTRIBUTE_STROKEMITER,
+                 paint.getStrokeMiter(),
+                 SkPaintDefaults_MiterLimit);
+    store_bool(writer, DEBUGCANVAS_ATTRIBUTE_ANTIALIAS, paint.isAntiAlias(), false);
+    store_bool(writer, DEBUGCANVAS_ATTRIBUTE_DITHER, paint.isDither(), false);
+
+    apply_paint_color(paint, writer);
+    apply_paint_style(paint, writer);
+    apply_paint_blend_mode(paint, writer);
+    apply_paint_cap(paint, writer);
+    apply_paint_join(paint, writer);
+    apply_paint_filterquality(paint, writer);
+    apply_paint_patheffect(paint, writer, urlDataManager);
+    apply_paint_maskfilter(paint, writer, urlDataManager);
+    apply_flattenable(DEBUGCANVAS_ATTRIBUTE_SHADER, paint.getShader(), writer, urlDataManager);
+    apply_flattenable(DEBUGCANVAS_ATTRIBUTE_LOOPER, paint.getLooper(), writer, urlDataManager);
+    apply_flattenable(
+            DEBUGCANVAS_ATTRIBUTE_IMAGEFILTER, paint.getImageFilter(), writer, urlDataManager);
+    apply_flattenable(
+            DEBUGCANVAS_ATTRIBUTE_COLORFILTER, paint.getColorFilter(), writer, urlDataManager);
+    writer.endObject();  // paint
+}
+
+static void MakeJsonFont(const SkFont& font, SkJSONWriter& writer, UrlDataManager& urlDataManager) {
+    writer.beginObject();
+    store_bool(writer, DEBUGCANVAS_ATTRIBUTE_FAKEBOLDTEXT, font.isEmbolden(), false);
+    store_bool(writer, DEBUGCANVAS_ATTRIBUTE_LINEARTEXT, font.isLinearMetrics(), false);
+    store_bool(writer, DEBUGCANVAS_ATTRIBUTE_SUBPIXELTEXT, font.isSubpixel(), false);
+    store_bool(writer, DEBUGCANVAS_ATTRIBUTE_EMBEDDEDBITMAPTEXT, font.isEmbeddedBitmaps(), false);
+    store_bool(writer, DEBUGCANVAS_ATTRIBUTE_AUTOHINTING, font.isForceAutoHinting(), false);
+
+    store_scalar(writer, DEBUGCANVAS_ATTRIBUTE_TEXTSIZE, font.getSize(), SkPaintDefaults_TextSize);
+    store_scalar(writer, DEBUGCANVAS_ATTRIBUTE_TEXTSCALEX, font.getScaleX(), SK_Scalar1);
+    store_scalar(writer, DEBUGCANVAS_ATTRIBUTE_TEXTSCALEX, font.getSkewX(), 0.0f);
+    apply_font_edging(font, writer);
+    apply_font_hinting(font, writer);
+    apply_font_typeface(font, writer, urlDataManager);
+    writer.endObject();  // font
+}
+
+void DrawCommand::MakeJsonLattice(SkJSONWriter& writer, const SkCanvas::Lattice& lattice) {
+    writer.beginObject();
+    writer.appendS32(DEBUGCANVAS_ATTRIBUTE_LATTICEXCOUNT, lattice.fXCount);
+    writer.appendS32(DEBUGCANVAS_ATTRIBUTE_LATTICEYCOUNT, lattice.fYCount);
+    if (nullptr != lattice.fBounds) {
+        writer.appendName(DEBUGCANVAS_ATTRIBUTE_BOUNDS);
+        MakeJsonIRect(writer, *lattice.fBounds);
+    }
+    writer.beginArray(DEBUGCANVAS_ATTRIBUTE_LATTICEXDIVS);
+    for (int i = 0; i < lattice.fXCount; i++) {
+        writer.appendS32(lattice.fXDivs[i]);
+    }
+    writer.endArray();  // xdivs
+    writer.beginArray(DEBUGCANVAS_ATTRIBUTE_LATTICEYDIVS);
+    for (int i = 0; i < lattice.fYCount; i++) {
+        writer.appendS32(lattice.fYDivs[i]);
+    }
+    writer.endArray();  // ydivs
+    if (nullptr != lattice.fRectTypes) {
+        writer.beginArray(DEBUGCANVAS_ATTRIBUTE_LATTICEFLAGS);
+        int flagCount = 0;
+        for (int row = 0; row < lattice.fYCount + 1; row++) {
+            writer.beginArray();
+            for (int column = 0; column < lattice.fXCount + 1; column++) {
+                writer.appendS32(lattice.fRectTypes[flagCount++]);
+            }
+            writer.endArray();  // row
+        }
+        writer.endArray();
+    }
+    writer.endObject();
+}
+
+ClearCommand::ClearCommand(SkColor color) : INHERITED(kClear_OpType) { fColor = color; }
+
+void ClearCommand::execute(SkCanvas* canvas) const { canvas->clear(fColor); }
+
+void ClearCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_COLOR);
+    MakeJsonColor(writer, fColor);
+}
+
+ClipPathCommand::ClipPathCommand(const SkPath& path, SkClipOp op, bool doAA)
+        : INHERITED(kClipPath_OpType) {
+    fPath = path;
+    fOp   = op;
+    fDoAA = doAA;
+}
+
+void ClipPathCommand::execute(SkCanvas* canvas) const { canvas->clipPath(fPath, fOp, fDoAA); }
+
+bool ClipPathCommand::render(SkCanvas* canvas) const {
+    render_path(canvas, fPath);
+    return true;
+}
+
+void ClipPathCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_PATH);
+    MakeJsonPath(writer, fPath);
+    writer.appendString(DEBUGCANVAS_ATTRIBUTE_REGIONOP, regionop_name(fOp));
+    writer.appendBool(DEBUGCANVAS_ATTRIBUTE_ANTIALIAS, fDoAA);
+}
+
+ClipRegionCommand::ClipRegionCommand(const SkRegion& region, SkClipOp op)
+        : INHERITED(kClipRegion_OpType) {
+    fRegion = region;
+    fOp     = op;
+}
+
+void ClipRegionCommand::execute(SkCanvas* canvas) const { canvas->clipRegion(fRegion, fOp); }
+
+void ClipRegionCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_REGION);
+    MakeJsonRegion(writer, fRegion);
+    writer.appendString(DEBUGCANVAS_ATTRIBUTE_REGIONOP, regionop_name(fOp));
+}
+
+ClipRectCommand::ClipRectCommand(const SkRect& rect, SkClipOp op, bool doAA)
+        : INHERITED(kClipRect_OpType) {
+    fRect = rect;
+    fOp   = op;
+    fDoAA = doAA;
+}
+
+void ClipRectCommand::execute(SkCanvas* canvas) const { canvas->clipRect(fRect, fOp, fDoAA); }
+
+void ClipRectCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_COORDS);
+    MakeJsonRect(writer, fRect);
+    writer.appendString(DEBUGCANVAS_ATTRIBUTE_REGIONOP, regionop_name(fOp));
+    writer.appendBool(DEBUGCANVAS_ATTRIBUTE_ANTIALIAS, fDoAA);
+
+    SkString desc;
+    writer.appendString(DEBUGCANVAS_ATTRIBUTE_SHORTDESC, str_append(&desc, fRect)->c_str());
+}
+
+ClipRRectCommand::ClipRRectCommand(const SkRRect& rrect, SkClipOp op, bool doAA)
+        : INHERITED(kClipRRect_OpType) {
+    fRRect = rrect;
+    fOp    = op;
+    fDoAA  = doAA;
+}
+
+void ClipRRectCommand::execute(SkCanvas* canvas) const { canvas->clipRRect(fRRect, fOp, fDoAA); }
+
+bool ClipRRectCommand::render(SkCanvas* canvas) const {
+    render_rrect(canvas, fRRect);
+    return true;
+}
+
+void ClipRRectCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_COORDS);
+    make_json_rrect(writer, fRRect);
+    writer.appendString(DEBUGCANVAS_ATTRIBUTE_REGIONOP, regionop_name(fOp));
+    writer.appendBool(DEBUGCANVAS_ATTRIBUTE_ANTIALIAS, fDoAA);
+}
+
+ConcatCommand::ConcatCommand(const SkMatrix& matrix) : INHERITED(kConcat_OpType) {
+    fMatrix = matrix;
+}
+
+void ConcatCommand::execute(SkCanvas* canvas) const { canvas->concat(fMatrix); }
+
+void ConcatCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_MATRIX);
+    MakeJsonMatrix(writer, fMatrix);
+}
+
+////
+
+DrawAnnotationCommand::DrawAnnotationCommand(const SkRect& rect,
+                                             const char    key[],
+                                             sk_sp<SkData> value)
+        : INHERITED(kDrawAnnotation_OpType), fRect(rect), fKey(key), fValue(std::move(value)) {}
+
+void DrawAnnotationCommand::execute(SkCanvas* canvas) const {
+    canvas->drawAnnotation(fRect, fKey.c_str(), fValue);
+}
+
+void DrawAnnotationCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_COORDS);
+    MakeJsonRect(writer, fRect);
+    writer.appendString("key", fKey.c_str());
+    if (fValue.get()) {
+        // TODO: dump out the "value"
+    }
+
+    SkString desc;
+    str_append(&desc, fRect)->appendf(" %s", fKey.c_str());
+    writer.appendString(DEBUGCANVAS_ATTRIBUTE_SHORTDESC, desc.c_str());
+}
+
+////
+
+DrawBitmapCommand::DrawBitmapCommand(const SkBitmap& bitmap,
+                                     SkScalar        left,
+                                     SkScalar        top,
+                                     const SkPaint*  paint)
+        : INHERITED(kDrawBitmap_OpType), fBitmap(bitmap), fLeft(left), fTop(top), fPaint(paint) {}
+
+void DrawBitmapCommand::execute(SkCanvas* canvas) const {
+    canvas->drawBitmap(fBitmap, fLeft, fTop, fPaint.getMaybeNull());
+}
+
+bool DrawBitmapCommand::render(SkCanvas* canvas) const {
+    render_bitmap(canvas, fBitmap);
+    return true;
+}
+
+void DrawBitmapCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.beginObject(DEBUGCANVAS_ATTRIBUTE_BITMAP);
+    flatten(fBitmap, writer, urlDataManager);
+    writer.endObject();
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_COORDS);
+    MakeJsonPoint(writer, fLeft, fTop);
+    if (fPaint.isValid()) {
+        writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
+        MakeJsonPaint(writer, *fPaint, urlDataManager);
+    }
+}
+
+DrawBitmapLatticeCommand::DrawBitmapLatticeCommand(const SkBitmap&          bitmap,
+                                                   const SkCanvas::Lattice& lattice,
+                                                   const SkRect&            dst,
+                                                   const SkPaint*           paint)
+        : INHERITED(kDrawBitmapLattice_OpType)
+        , fBitmap(bitmap)
+        , fLattice(lattice)
+        , fDst(dst)
+        , fPaint(paint) {}
+
+void DrawBitmapLatticeCommand::execute(SkCanvas* canvas) const {
+    canvas->drawBitmapLattice(fBitmap, fLattice, fDst, fPaint.getMaybeNull());
+}
+
+bool DrawBitmapLatticeCommand::render(SkCanvas* canvas) const {
+    SkAutoCanvasRestore acr(canvas, true);
+    canvas->clear(0xFFFFFFFF);
+
+    xlate_and_scale_to_bounds(canvas, fDst);
+
+    this->execute(canvas);
+    return true;
+}
+
+void DrawBitmapLatticeCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.beginObject(DEBUGCANVAS_ATTRIBUTE_BITMAP);
+    flatten(fBitmap, writer, urlDataManager);
+    writer.endObject();  // bitmap
+
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_LATTICE);
+    MakeJsonLattice(writer, fLattice);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_DST);
+    MakeJsonRect(writer, fDst);
+    if (fPaint.isValid()) {
+        writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
+        MakeJsonPaint(writer, *fPaint, urlDataManager);
+    }
+
+    SkString desc;
+    writer.appendString(DEBUGCANVAS_ATTRIBUTE_SHORTDESC, str_append(&desc, fDst)->c_str());
+}
+
+DrawBitmapNineCommand::DrawBitmapNineCommand(const SkBitmap& bitmap,
+                                             const SkIRect&  center,
+                                             const SkRect&   dst,
+                                             const SkPaint*  paint)
+        : INHERITED(kDrawBitmapNine_OpType)
+        , fBitmap(bitmap)
+        , fCenter(center)
+        , fDst(dst)
+        , fPaint(paint) {}
+
+void DrawBitmapNineCommand::execute(SkCanvas* canvas) const {
+    canvas->drawBitmapNine(fBitmap, fCenter, fDst, fPaint.getMaybeNull());
+}
+
+bool DrawBitmapNineCommand::render(SkCanvas* canvas) const {
+    SkRect tmp = SkRect::Make(fCenter);
+    render_bitmap(canvas, fBitmap, &tmp);
+    return true;
+}
+
+void DrawBitmapNineCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.beginObject(DEBUGCANVAS_ATTRIBUTE_BITMAP);
+    flatten(fBitmap, writer, urlDataManager);
+    writer.endObject();  // bitmap
+
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_CENTER);
+    MakeJsonIRect(writer, fCenter);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_DST);
+    MakeJsonRect(writer, fDst);
+    if (fPaint.isValid()) {
+        writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
+        MakeJsonPaint(writer, *fPaint, urlDataManager);
+    }
+}
+
+DrawBitmapRectCommand::DrawBitmapRectCommand(const SkBitmap&             bitmap,
+                                             const SkRect*               src,
+                                             const SkRect&               dst,
+                                             const SkPaint*              paint,
+                                             SkCanvas::SrcRectConstraint constraint)
+        : INHERITED(kDrawBitmapRect_OpType)
+        , fBitmap(bitmap)
+        , fSrc(src)
+        , fDst(dst)
+        , fPaint(paint)
+        , fConstraint(constraint) {}
+
+void DrawBitmapRectCommand::execute(SkCanvas* canvas) const {
+    canvas->legacy_drawBitmapRect(
+            fBitmap, fSrc.getMaybeNull(), fDst, fPaint.getMaybeNull(), fConstraint);
+}
+
+bool DrawBitmapRectCommand::render(SkCanvas* canvas) const {
+    render_bitmap(canvas, fBitmap, fSrc.getMaybeNull());
+    return true;
+}
+
+void DrawBitmapRectCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.beginObject(DEBUGCANVAS_ATTRIBUTE_BITMAP);
+    flatten(fBitmap, writer, urlDataManager);
+    writer.endObject();  // bitmap
+
+    if (fSrc.isValid()) {
+        writer.appendName(DEBUGCANVAS_ATTRIBUTE_SRC);
+        MakeJsonRect(writer, *fSrc);
+    }
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_DST);
+    MakeJsonRect(writer, fDst);
+    if (fPaint.isValid()) {
+        writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
+        MakeJsonPaint(writer, *fPaint, urlDataManager);
+    }
+    if (fConstraint == SkCanvas::kStrict_SrcRectConstraint) {
+        writer.appendBool(DEBUGCANVAS_ATTRIBUTE_STRICT, true);
+    }
+
+    SkString desc;
+    writer.appendString(DEBUGCANVAS_ATTRIBUTE_SHORTDESC, str_append(&desc, fDst)->c_str());
+}
+
+DrawImageCommand::DrawImageCommand(const SkImage* image,
+                                   SkScalar       left,
+                                   SkScalar       top,
+                                   const SkPaint* paint)
+        : INHERITED(kDrawImage_OpType)
+        , fImage(SkRef(image))
+        , fLeft(left)
+        , fTop(top)
+        , fPaint(paint) {}
+
+void DrawImageCommand::execute(SkCanvas* canvas) const {
+    canvas->drawImage(fImage.get(), fLeft, fTop, fPaint.getMaybeNull());
+}
+
+bool DrawImageCommand::render(SkCanvas* canvas) const {
+    SkAutoCanvasRestore acr(canvas, true);
+    canvas->clear(0xFFFFFFFF);
+
+    xlate_and_scale_to_bounds(
+            canvas,
+            SkRect::MakeXYWH(
+                    fLeft, fTop, SkIntToScalar(fImage->width()), SkIntToScalar(fImage->height())));
+    this->execute(canvas);
+    return true;
+}
+
+void DrawImageCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.beginObject(DEBUGCANVAS_ATTRIBUTE_IMAGE);
+    flatten(*fImage, writer, urlDataManager);
+    writer.endObject();  // image
+
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_COORDS);
+    MakeJsonPoint(writer, fLeft, fTop);
+    if (fPaint.isValid()) {
+        writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
+        MakeJsonPaint(writer, *fPaint, urlDataManager);
+    }
+
+    writer.appendU32(DEBUGCANVAS_ATTRIBUTE_UNIQUE_ID, fImage->uniqueID());
+    writer.appendS32(DEBUGCANVAS_ATTRIBUTE_WIDTH, fImage->width());
+    writer.appendS32(DEBUGCANVAS_ATTRIBUTE_HEIGHT, fImage->height());
+    switch (fImage->alphaType()) {
+        case kOpaque_SkAlphaType:
+            writer.appendString(DEBUGCANVAS_ATTRIBUTE_ALPHA, DEBUGCANVAS_ALPHATYPE_OPAQUE);
+            break;
+        case kPremul_SkAlphaType:
+            writer.appendString(DEBUGCANVAS_ATTRIBUTE_ALPHA, DEBUGCANVAS_ALPHATYPE_PREMUL);
+            break;
+        case kUnpremul_SkAlphaType:
+            writer.appendString(DEBUGCANVAS_ATTRIBUTE_ALPHA, DEBUGCANVAS_ALPHATYPE_UNPREMUL);
+            break;
+        default:
+            writer.appendString(DEBUGCANVAS_ATTRIBUTE_ALPHA, DEBUGCANVAS_ALPHATYPE_UNKNOWN);
+            break;
+    }
+}
+
+DrawImageLatticeCommand::DrawImageLatticeCommand(const SkImage*           image,
+                                                 const SkCanvas::Lattice& lattice,
+                                                 const SkRect&            dst,
+                                                 const SkPaint*           paint)
+        : INHERITED(kDrawImageLattice_OpType)
+        , fImage(SkRef(image))
+        , fLattice(lattice)
+        , fDst(dst)
+        , fPaint(paint) {}
+
+void DrawImageLatticeCommand::execute(SkCanvas* canvas) const {
+    canvas->drawImageLattice(fImage.get(), fLattice, fDst, fPaint.getMaybeNull());
+}
+
+bool DrawImageLatticeCommand::render(SkCanvas* canvas) const {
+    SkAutoCanvasRestore acr(canvas, true);
+    canvas->clear(0xFFFFFFFF);
+
+    xlate_and_scale_to_bounds(canvas, fDst);
+
+    this->execute(canvas);
+    return true;
+}
+
+void DrawImageLatticeCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.beginObject(DEBUGCANVAS_ATTRIBUTE_IMAGE);
+    flatten(*fImage, writer, urlDataManager);
+    writer.endObject();  // image
+
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_LATTICE);
+    MakeJsonLattice(writer, fLattice);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_DST);
+    MakeJsonRect(writer, fDst);
+    if (fPaint.isValid()) {
+        writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
+        MakeJsonPaint(writer, *fPaint, urlDataManager);
+    }
+
+    SkString desc;
+    writer.appendString(DEBUGCANVAS_ATTRIBUTE_SHORTDESC, str_append(&desc, fDst)->c_str());
+}
+
+DrawImageRectCommand::DrawImageRectCommand(const SkImage*              image,
+                                           const SkRect*               src,
+                                           const SkRect&               dst,
+                                           const SkPaint*              paint,
+                                           SkCanvas::SrcRectConstraint constraint)
+        : INHERITED(kDrawImageRect_OpType)
+        , fImage(SkRef(image))
+        , fSrc(src)
+        , fDst(dst)
+        , fPaint(paint)
+        , fConstraint(constraint) {}
+
+void DrawImageRectCommand::execute(SkCanvas* canvas) const {
+    canvas->legacy_drawImageRect(
+            fImage.get(), fSrc.getMaybeNull(), fDst, fPaint.getMaybeNull(), fConstraint);
+}
+
+bool DrawImageRectCommand::render(SkCanvas* canvas) const {
+    SkAutoCanvasRestore acr(canvas, true);
+    canvas->clear(0xFFFFFFFF);
+
+    xlate_and_scale_to_bounds(canvas, fDst);
+
+    this->execute(canvas);
+    return true;
+}
+
+void DrawImageRectCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.beginObject(DEBUGCANVAS_ATTRIBUTE_IMAGE);
+    flatten(*fImage, writer, urlDataManager);
+    writer.endObject();  // image
+
+    if (fSrc.isValid()) {
+        writer.appendName(DEBUGCANVAS_ATTRIBUTE_SRC);
+        MakeJsonRect(writer, *fSrc);
+    }
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_DST);
+    MakeJsonRect(writer, fDst);
+    if (fPaint.isValid()) {
+        writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
+        MakeJsonPaint(writer, *fPaint, urlDataManager);
+    }
+    if (fConstraint == SkCanvas::kStrict_SrcRectConstraint) {
+        writer.appendBool(DEBUGCANVAS_ATTRIBUTE_STRICT, true);
+    }
+
+    SkString desc;
+    writer.appendString(DEBUGCANVAS_ATTRIBUTE_SHORTDESC, str_append(&desc, fDst)->c_str());
+}
+
+DrawImageNineCommand::DrawImageNineCommand(const SkImage* image,
+                                           const SkIRect& center,
+                                           const SkRect&  dst,
+                                           const SkPaint* paint)
+        : INHERITED(kDrawImageNine_OpType)
+        , fImage(SkRef(image))
+        , fCenter(center)
+        , fDst(dst)
+        , fPaint(paint) {}
+
+void DrawImageNineCommand::execute(SkCanvas* canvas) const {
+    canvas->drawImageNine(fImage.get(), fCenter, fDst, fPaint.getMaybeNull());
+}
+
+bool DrawImageNineCommand::render(SkCanvas* canvas) const {
+    SkAutoCanvasRestore acr(canvas, true);
+    canvas->clear(0xFFFFFFFF);
+
+    xlate_and_scale_to_bounds(canvas, fDst);
+
+    this->execute(canvas);
+    return true;
+}
+
+void DrawImageNineCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.beginObject(DEBUGCANVAS_ATTRIBUTE_IMAGE);
+    flatten(*fImage, writer, urlDataManager);
+    writer.endObject();  // image
+
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_CENTER);
+    MakeJsonIRect(writer, fCenter);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_DST);
+    MakeJsonRect(writer, fDst);
+    if (fPaint.isValid()) {
+        writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
+        MakeJsonPaint(writer, *fPaint, urlDataManager);
+    }
+}
+
+DrawOvalCommand::DrawOvalCommand(const SkRect& oval, const SkPaint& paint)
+        : INHERITED(kDrawOval_OpType) {
+    fOval  = oval;
+    fPaint = paint;
+}
+
+void DrawOvalCommand::execute(SkCanvas* canvas) const { canvas->drawOval(fOval, fPaint); }
+
+bool DrawOvalCommand::render(SkCanvas* canvas) const {
+    canvas->clear(0xFFFFFFFF);
+    canvas->save();
+
+    xlate_and_scale_to_bounds(canvas, fOval);
+
+    SkPaint p;
+    p.setColor(SK_ColorBLACK);
+    p.setStyle(SkPaint::kStroke_Style);
+
+    canvas->drawOval(fOval, p);
+    canvas->restore();
+
+    return true;
+}
+
+void DrawOvalCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_COORDS);
+    MakeJsonRect(writer, fOval);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
+    MakeJsonPaint(writer, fPaint, urlDataManager);
+}
+
+DrawArcCommand::DrawArcCommand(const SkRect&  oval,
+                               SkScalar       startAngle,
+                               SkScalar       sweepAngle,
+                               bool           useCenter,
+                               const SkPaint& paint)
+        : INHERITED(kDrawArc_OpType) {
+    fOval       = oval;
+    fStartAngle = startAngle;
+    fSweepAngle = sweepAngle;
+    fUseCenter  = useCenter;
+    fPaint      = paint;
+}
+
+void DrawArcCommand::execute(SkCanvas* canvas) const {
+    canvas->drawArc(fOval, fStartAngle, fSweepAngle, fUseCenter, fPaint);
+}
+
+bool DrawArcCommand::render(SkCanvas* canvas) const {
+    canvas->clear(0xFFFFFFFF);
+    canvas->save();
+
+    xlate_and_scale_to_bounds(canvas, fOval);
+
+    SkPaint p;
+    p.setColor(SK_ColorBLACK);
+    p.setStyle(SkPaint::kStroke_Style);
+
+    canvas->drawArc(fOval, fStartAngle, fSweepAngle, fUseCenter, p);
+    canvas->restore();
+
+    return true;
+}
+
+void DrawArcCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_COORDS);
+    MakeJsonRect(writer, fOval);
+    writer.appendFloat(DEBUGCANVAS_ATTRIBUTE_STARTANGLE, fStartAngle);
+    writer.appendFloat(DEBUGCANVAS_ATTRIBUTE_SWEEPANGLE, fSweepAngle);
+    writer.appendBool(DEBUGCANVAS_ATTRIBUTE_USECENTER, fUseCenter);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
+    MakeJsonPaint(writer, fPaint, urlDataManager);
+}
+
+DrawPaintCommand::DrawPaintCommand(const SkPaint& paint) : INHERITED(kDrawPaint_OpType) {
+    fPaint = paint;
+}
+
+void DrawPaintCommand::execute(SkCanvas* canvas) const { canvas->drawPaint(fPaint); }
+
+bool DrawPaintCommand::render(SkCanvas* canvas) const {
+    canvas->clear(0xFFFFFFFF);
+    canvas->drawPaint(fPaint);
+    return true;
+}
+
+void DrawPaintCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
+    MakeJsonPaint(writer, fPaint, urlDataManager);
+}
+
+DrawPathCommand::DrawPathCommand(const SkPath& path, const SkPaint& paint)
+        : INHERITED(kDrawPath_OpType) {
+    fPath  = path;
+    fPaint = paint;
+}
+
+void DrawPathCommand::execute(SkCanvas* canvas) const { canvas->drawPath(fPath, fPaint); }
+
+bool DrawPathCommand::render(SkCanvas* canvas) const {
+    render_path(canvas, fPath);
+    return true;
+}
+
+void DrawPathCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_PATH);
+    MakeJsonPath(writer, fPath);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
+    MakeJsonPaint(writer, fPaint, urlDataManager);
+}
+
+DrawRegionCommand::DrawRegionCommand(const SkRegion& region, const SkPaint& paint)
+        : INHERITED(kDrawRegion_OpType) {
+    fRegion = region;
+    fPaint  = paint;
+}
+
+void DrawRegionCommand::execute(SkCanvas* canvas) const { canvas->drawRegion(fRegion, fPaint); }
+
+bool DrawRegionCommand::render(SkCanvas* canvas) const {
+    render_region(canvas, fRegion);
+    return true;
+}
+
+void DrawRegionCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_REGION);
+    MakeJsonRegion(writer, fRegion);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
+    MakeJsonPaint(writer, fPaint, urlDataManager);
+}
+
+BeginDrawPictureCommand::BeginDrawPictureCommand(const SkPicture* picture,
+                                                 const SkMatrix*  matrix,
+                                                 const SkPaint*   paint)
+        : INHERITED(kBeginDrawPicture_OpType)
+        , fPicture(SkRef(picture))
+        , fMatrix(matrix)
+        , fPaint(paint) {}
+
+void BeginDrawPictureCommand::execute(SkCanvas* canvas) const {
+    if (fPaint.isValid()) {
+        SkRect bounds = fPicture->cullRect();
+        if (fMatrix.isValid()) {
+            fMatrix->mapRect(&bounds);
+        }
+        canvas->saveLayer(&bounds, fPaint.get());
+    }
+
+    if (fMatrix.isValid()) {
+        if (!fPaint.isValid()) {
+            canvas->save();
+        }
+        canvas->concat(*fMatrix);
+    }
+}
+
+bool BeginDrawPictureCommand::render(SkCanvas* canvas) const {
+    canvas->clear(0xFFFFFFFF);
+    canvas->save();
+
+    xlate_and_scale_to_bounds(canvas, fPicture->cullRect());
+
+    canvas->drawPicture(fPicture.get());
+
+    canvas->restore();
+
+    return true;
+}
+
+EndDrawPictureCommand::EndDrawPictureCommand(bool restore)
+        : INHERITED(kEndDrawPicture_OpType), fRestore(restore) {}
+
+void EndDrawPictureCommand::execute(SkCanvas* canvas) const {
+    if (fRestore) {
+        canvas->restore();
+    }
+}
+
+DrawPointsCommand::DrawPointsCommand(SkCanvas::PointMode mode,
+                                     size_t              count,
+                                     const SkPoint       pts[],
+                                     const SkPaint&      paint)
+        : INHERITED(kDrawPoints_OpType), fMode(mode), fPts(pts, count), fPaint(paint) {}
+
+void DrawPointsCommand::execute(SkCanvas* canvas) const {
+    canvas->drawPoints(fMode, fPts.count(), fPts.begin(), fPaint);
+}
+
+bool DrawPointsCommand::render(SkCanvas* canvas) const {
+    canvas->clear(0xFFFFFFFF);
+    canvas->save();
+
+    SkRect bounds;
+
+    bounds.setEmpty();
+    for (int i = 0; i < fPts.count(); ++i) {
+        SkRectPriv::GrowToInclude(&bounds, fPts[i]);
+    }
+
+    xlate_and_scale_to_bounds(canvas, bounds);
+
+    SkPaint p;
+    p.setColor(SK_ColorBLACK);
+    p.setStyle(SkPaint::kStroke_Style);
+
+    canvas->drawPoints(fMode, fPts.count(), fPts.begin(), p);
+    canvas->restore();
+
+    return true;
+}
+
+void DrawPointsCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.appendString(DEBUGCANVAS_ATTRIBUTE_MODE, pointmode_name(fMode));
+    writer.beginArray(DEBUGCANVAS_ATTRIBUTE_POINTS);
+    for (int i = 0; i < fPts.count(); i++) {
+        MakeJsonPoint(writer, fPts[i]);
+    }
+    writer.endArray();  // points
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
+    MakeJsonPaint(writer, fPaint, urlDataManager);
+}
+
+DrawTextBlobCommand::DrawTextBlobCommand(sk_sp<SkTextBlob> blob,
+                                         SkScalar          x,
+                                         SkScalar          y,
+                                         const SkPaint&    paint)
+        : INHERITED(kDrawTextBlob_OpType)
+        , fBlob(std::move(blob))
+        , fXPos(x)
+        , fYPos(y)
+        , fPaint(paint) {}
+
+void DrawTextBlobCommand::execute(SkCanvas* canvas) const {
+    canvas->drawTextBlob(fBlob, fXPos, fYPos, fPaint);
+}
+
+bool DrawTextBlobCommand::render(SkCanvas* canvas) const {
+    canvas->clear(SK_ColorWHITE);
+    canvas->save();
+
+    SkRect bounds = fBlob->bounds().makeOffset(fXPos, fYPos);
+    xlate_and_scale_to_bounds(canvas, bounds);
+
+    canvas->drawTextBlob(fBlob, fXPos, fYPos, fPaint);
+
+    canvas->restore();
+
+    return true;
+}
+
+void DrawTextBlobCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.beginArray(DEBUGCANVAS_ATTRIBUTE_RUNS);
+    SkTextBlobRunIterator iter(fBlob.get());
+    while (!iter.done()) {
+        writer.beginObject();  // run
+        writer.beginArray(DEBUGCANVAS_ATTRIBUTE_GLYPHS);
+        for (uint32_t i = 0; i < iter.glyphCount(); i++) {
+            writer.appendU32(iter.glyphs()[i]);
+        }
+        writer.endArray();  // glyphs
+        if (iter.positioning() != SkTextBlobRunIterator::kDefault_Positioning) {
+            writer.beginArray(DEBUGCANVAS_ATTRIBUTE_POSITIONS);
+            const SkScalar* iterPositions = iter.pos();
+            for (uint32_t i = 0; i < iter.glyphCount(); i++) {
+                switch (iter.positioning()) {
+                    case SkTextBlobRunIterator::kFull_Positioning:
+                        MakeJsonPoint(writer, iterPositions[i * 2], iterPositions[i * 2 + 1]);
+                        break;
+                    case SkTextBlobRunIterator::kHorizontal_Positioning:
+                        writer.appendFloat(iterPositions[i]);
+                        break;
+                    case SkTextBlobRunIterator::kDefault_Positioning: break;
+                    case SkTextBlobRunIterator::kRSXform_Positioning:
+                        // TODO_RSXFORM_BLOB
+                        break;
+                }
+            }
+            writer.endArray();  // positions
+        }
+        writer.appendName(DEBUGCANVAS_ATTRIBUTE_FONT);
+        MakeJsonFont(iter.font(), writer, urlDataManager);
+        writer.appendName(DEBUGCANVAS_ATTRIBUTE_COORDS);
+        MakeJsonPoint(writer, iter.offset());
+
+        writer.endObject();  // run
+        iter.next();
+    }
+    writer.endArray();  // runs
+    writer.appendFloat(DEBUGCANVAS_ATTRIBUTE_X, fXPos);
+    writer.appendFloat(DEBUGCANVAS_ATTRIBUTE_Y, fYPos);
+    SkRect bounds = fBlob->bounds();
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_COORDS);
+    MakeJsonRect(writer, bounds);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
+    MakeJsonPaint(writer, fPaint, urlDataManager);
+
+    SkString desc;
+    // make the bounds local by applying the x,y
+    bounds.offset(fXPos, fYPos);
+    writer.appendString(DEBUGCANVAS_ATTRIBUTE_SHORTDESC, str_append(&desc, bounds)->c_str());
+}
+
+DrawPatchCommand::DrawPatchCommand(const SkPoint  cubics[12],
+                                   const SkColor  colors[4],
+                                   const SkPoint  texCoords[4],
+                                   SkBlendMode    bmode,
+                                   const SkPaint& paint)
+        : INHERITED(kDrawPatch_OpType), fBlendMode(bmode) {
+    memcpy(fCubics, cubics, sizeof(fCubics));
+    if (colors != nullptr) {
+        memcpy(fColors, colors, sizeof(fColors));
+        fColorsPtr = fColors;
+    } else {
+        fColorsPtr = nullptr;
+    }
+    if (texCoords != nullptr) {
+        memcpy(fTexCoords, texCoords, sizeof(fTexCoords));
+        fTexCoordsPtr = fTexCoords;
+    } else {
+        fTexCoordsPtr = nullptr;
+    }
+    fPaint = paint;
+}
+
+void DrawPatchCommand::execute(SkCanvas* canvas) const {
+    canvas->drawPatch(fCubics, fColorsPtr, fTexCoordsPtr, fBlendMode, fPaint);
+}
+
+void DrawPatchCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.beginArray(DEBUGCANVAS_ATTRIBUTE_CUBICS);
+    for (int i = 0; i < 12; i++) {
+        MakeJsonPoint(writer, fCubics[i]);
+    }
+    writer.endArray();  // cubics
+    if (fColorsPtr != nullptr) {
+        writer.beginArray(DEBUGCANVAS_ATTRIBUTE_COLORS);
+        for (int i = 0; i < 4; i++) {
+            MakeJsonColor(writer, fColorsPtr[i]);
+        }
+        writer.endArray();  // colors
+    }
+    if (fTexCoordsPtr != nullptr) {
+        writer.beginArray(DEBUGCANVAS_ATTRIBUTE_TEXTURECOORDS);
+        for (int i = 0; i < 4; i++) {
+            MakeJsonPoint(writer, fTexCoords[i]);
+        }
+        writer.endArray();  // texCoords
+    }
+    // fBlendMode
+}
+
+DrawRectCommand::DrawRectCommand(const SkRect& rect, const SkPaint& paint)
+        : INHERITED(kDrawRect_OpType) {
+    fRect  = rect;
+    fPaint = paint;
+}
+
+void DrawRectCommand::execute(SkCanvas* canvas) const { canvas->drawRect(fRect, fPaint); }
+
+void DrawRectCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_COORDS);
+    MakeJsonRect(writer, fRect);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
+    MakeJsonPaint(writer, fPaint, urlDataManager);
+
+    SkString desc;
+    writer.appendString(DEBUGCANVAS_ATTRIBUTE_SHORTDESC, str_append(&desc, fRect)->c_str());
+}
+
+DrawRRectCommand::DrawRRectCommand(const SkRRect& rrect, const SkPaint& paint)
+        : INHERITED(kDrawRRect_OpType) {
+    fRRect = rrect;
+    fPaint = paint;
+}
+
+void DrawRRectCommand::execute(SkCanvas* canvas) const { canvas->drawRRect(fRRect, fPaint); }
+
+bool DrawRRectCommand::render(SkCanvas* canvas) const {
+    render_rrect(canvas, fRRect);
+    return true;
+}
+
+void DrawRRectCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_COORDS);
+    make_json_rrect(writer, fRRect);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
+    MakeJsonPaint(writer, fPaint, urlDataManager);
+}
+
+DrawDRRectCommand::DrawDRRectCommand(const SkRRect& outer,
+                                     const SkRRect& inner,
+                                     const SkPaint& paint)
+        : INHERITED(kDrawDRRect_OpType) {
+    fOuter = outer;
+    fInner = inner;
+    fPaint = paint;
+}
+
+void DrawDRRectCommand::execute(SkCanvas* canvas) const {
+    canvas->drawDRRect(fOuter, fInner, fPaint);
+}
+
+bool DrawDRRectCommand::render(SkCanvas* canvas) const {
+    render_drrect(canvas, fOuter, fInner);
+    return true;
+}
+
+void DrawDRRectCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_OUTER);
+    make_json_rrect(writer, fOuter);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_INNER);
+    make_json_rrect(writer, fInner);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
+    MakeJsonPaint(writer, fPaint, urlDataManager);
+}
+
+DrawShadowCommand::DrawShadowCommand(const SkPath& path, const SkDrawShadowRec& rec)
+        : INHERITED(kDrawShadow_OpType) {
+    fPath      = path;
+    fShadowRec = rec;
+}
+
+void DrawShadowCommand::execute(SkCanvas* canvas) const {
+    canvas->private_draw_shadow_rec(fPath, fShadowRec);
+}
+
+bool DrawShadowCommand::render(SkCanvas* canvas) const {
+    render_shadow(canvas, fPath, fShadowRec);
+    return true;
+}
+
+void DrawShadowCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+
+    bool geometricOnly = SkToBool(fShadowRec.fFlags & SkShadowFlags::kGeometricOnly_ShadowFlag);
+    bool transparentOccluder =
+            SkToBool(fShadowRec.fFlags & SkShadowFlags::kTransparentOccluder_ShadowFlag);
+
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_PATH);
+    MakeJsonPath(writer, fPath);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_ZPLANE);
+    MakeJsonPoint3(writer, fShadowRec.fZPlaneParams);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_LIGHTPOSITION);
+    MakeJsonPoint3(writer, fShadowRec.fLightPos);
+    writer.appendFloat(DEBUGCANVAS_ATTRIBUTE_LIGHTRADIUS, fShadowRec.fLightRadius);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_AMBIENTCOLOR);
+    MakeJsonColor(writer, fShadowRec.fAmbientColor);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_SPOTCOLOR);
+    MakeJsonColor(writer, fShadowRec.fSpotColor);
+    store_bool(writer, DEBUGCANVAS_SHADOWFLAG_TRANSPARENT_OCC, transparentOccluder, false);
+    store_bool(writer, DEBUGCANVAS_SHADOWFLAG_GEOMETRIC_ONLY, geometricOnly, false);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+DrawEdgeAAQuadCommand::DrawEdgeAAQuadCommand(const SkRect&         rect,
+                                             const SkPoint         clip[],
+                                             SkCanvas::QuadAAFlags aa,
+                                             SkColor               color,
+                                             SkBlendMode           mode)
+        : INHERITED(kDrawEdgeAAQuad_OpType)
+        , fRect(rect)
+        , fHasClip(clip != nullptr)
+        , fAA(aa)
+        , fColor(color)
+        , fMode(mode) {
+    if (clip) {
+        for (int i = 0; i < 4; ++i) {
+            fClip[i] = clip[i];
+        }
+    }
+}
+
+void DrawEdgeAAQuadCommand::execute(SkCanvas* canvas) const {
+    canvas->experimental_DrawEdgeAAQuad(fRect, fHasClip ? fClip : nullptr, fAA, fColor, fMode);
+}
+
+DrawEdgeAAImageSetCommand::DrawEdgeAAImageSetCommand(const SkCanvas::ImageSetEntry set[],
+                                                     int                           count,
+                                                     const SkPoint                 dstClips[],
+                                                     const SkMatrix              preViewMatrices[],
+                                                     const SkPaint*              paint,
+                                                     SkCanvas::SrcRectConstraint constraint)
+        : INHERITED(kDrawEdgeAAImageSet_OpType)
+        , fSet(count)
+        , fCount(count)
+        , fPaint(paint)
+        , fConstraint(constraint) {
+    int totalDstClipCount, totalMatrixCount;
+    SkCanvasPriv::GetDstClipAndMatrixCounts(set, count, &totalDstClipCount, &totalMatrixCount);
+
+    std::copy_n(set, count, fSet.get());
+    fDstClips.reset(totalDstClipCount);
+    std::copy_n(dstClips, totalDstClipCount, fDstClips.get());
+    fPreViewMatrices.reset(totalMatrixCount);
+    std::copy_n(preViewMatrices, totalMatrixCount, fPreViewMatrices.get());
+}
+
+void DrawEdgeAAImageSetCommand::execute(SkCanvas* canvas) const {
+    canvas->experimental_DrawEdgeAAImageSet(fSet.get(),
+                                            fCount,
+                                            fDstClips.get(),
+                                            fPreViewMatrices.get(),
+                                            fPaint.getMaybeNull(),
+                                            fConstraint);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+DrawDrawableCommand::DrawDrawableCommand(SkDrawable* drawable, const SkMatrix* matrix)
+        : INHERITED(kDrawDrawable_OpType), fDrawable(SkRef(drawable)), fMatrix(matrix) {}
+
+void DrawDrawableCommand::execute(SkCanvas* canvas) const {
+    canvas->drawDrawable(fDrawable.get(), fMatrix.getMaybeNull());
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+DrawVerticesCommand::DrawVerticesCommand(sk_sp<SkVertices> vertices,
+                                         SkBlendMode       bmode,
+                                         const SkPaint&    paint)
+        : INHERITED(kDrawVertices_OpType)
+        , fVertices(std::move(vertices))
+        , fBlendMode(bmode)
+        , fPaint(paint) {}
+
+void DrawVerticesCommand::execute(SkCanvas* canvas) const {
+    canvas->drawVertices(fVertices, fBlendMode, fPaint);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+DrawAtlasCommand::DrawAtlasCommand(const SkImage*  image,
+                                   const SkRSXform xform[],
+                                   const SkRect    tex[],
+                                   const SkColor   colors[],
+                                   int             count,
+                                   SkBlendMode     bmode,
+                                   const SkRect*   cull,
+                                   const SkPaint*  paint)
+        : INHERITED(kDrawAtlas_OpType)
+        , fImage(SkRef(image))
+        , fXform(xform, count)
+        , fTex(tex, count)
+        , fColors(colors, colors ? count : 0)
+        , fBlendMode(bmode)
+        , fCull(cull)
+        , fPaint(paint) {}
+
+void DrawAtlasCommand::execute(SkCanvas* canvas) const {
+    canvas->drawAtlas(fImage.get(),
+                      fXform.begin(),
+                      fTex.begin(),
+                      fColors.isEmpty() ? nullptr : fColors.begin(),
+                      fXform.count(),
+                      fBlendMode,
+                      fCull.getMaybeNull(),
+                      fPaint.getMaybeNull());
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+RestoreCommand::RestoreCommand() : INHERITED(kRestore_OpType) {}
+
+void RestoreCommand::execute(SkCanvas* canvas) const { canvas->restore(); }
+
+SaveCommand::SaveCommand() : INHERITED(kSave_OpType) {}
+
+void SaveCommand::execute(SkCanvas* canvas) const { canvas->save(); }
+
+SaveLayerCommand::SaveLayerCommand(const SkCanvas::SaveLayerRec& rec)
+        : INHERITED(kSaveLayer_OpType)
+        , fBounds(rec.fBounds)
+        , fPaint(rec.fPaint)
+        , fBackdrop(SkSafeRef(rec.fBackdrop))
+        , fSaveLayerFlags(rec.fSaveLayerFlags) {}
+
+void SaveLayerCommand::execute(SkCanvas* canvas) const {
+    canvas->saveLayer(
+            SkCanvas::SaveLayerRec(fBounds.getMaybeNull(), fPaint.getMaybeNull(), fSaveLayerFlags));
+}
+
+void SaveLayerCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    if (fBounds.isValid()) {
+        writer.appendName(DEBUGCANVAS_ATTRIBUTE_BOUNDS);
+        MakeJsonRect(writer, *fBounds);
+    }
+    if (fPaint.isValid()) {
+        writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
+        MakeJsonPaint(writer, *fPaint, urlDataManager);
+    }
+    if (fBackdrop != nullptr) {
+        writer.beginObject(DEBUGCANVAS_ATTRIBUTE_BACKDROP);
+        flatten(fBackdrop.get(), writer, urlDataManager);
+        writer.endObject();  // backdrop
+    }
+    if (fSaveLayerFlags != 0) {
+        SkDebugf("unsupported: saveLayer flags\n");
+        SkASSERT(false);
+    }
+}
+
+SetMatrixCommand::SetMatrixCommand(const SkMatrix& matrix) : INHERITED(kSetMatrix_OpType) {
+    fMatrix = matrix;
+}
+
+void SetMatrixCommand::execute(SkCanvas* canvas) const { canvas->setMatrix(fMatrix); }
+
+void SetMatrixCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
+    INHERITED::toJSON(writer, urlDataManager);
+    writer.appendName(DEBUGCANVAS_ATTRIBUTE_MATRIX);
+    MakeJsonMatrix(writer, fMatrix);
+}
diff --git a/tools/debugger/DrawCommand.h b/tools/debugger/DrawCommand.h
new file mode 100644
index 0000000..30030d4
--- /dev/null
+++ b/tools/debugger/DrawCommand.h
@@ -0,0 +1,715 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKDRAWCOMMAND_H_
+#define SKDRAWCOMMAND_H_
+
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkDrawShadowInfo.h"
+#include "SkFlattenable.h"
+#include "SkJSONWriter.h"
+#include "SkPath.h"
+#include "SkRRect.h"
+#include "SkRSXform.h"
+#include "SkRegion.h"
+#include "SkString.h"
+#include "SkTDArray.h"
+#include "SkTLazy.h"
+#include "SkVertices.h"
+#include "UrlDataManager.h"
+
+class DrawCommand {
+public:
+    enum OpType {
+        kBeginDrawPicture_OpType,
+        kClear_OpType,
+        kClipPath_OpType,
+        kClipRegion_OpType,
+        kClipRect_OpType,
+        kClipRRect_OpType,
+        kConcat_OpType,
+        kDrawAnnotation_OpType,
+        kDrawBitmap_OpType,
+        kDrawBitmapLattice_OpType,
+        kDrawBitmapNine_OpType,
+        kDrawBitmapRect_OpType,
+        kDrawDRRect_OpType,
+        kDrawImage_OpType,
+        kDrawImageLattice_OpType,
+        kDrawImageNine_OpType,
+        kDrawImageRect_OpType,
+        kDrawOval_OpType,
+        kDrawArc_OpType,
+        kDrawPaint_OpType,
+        kDrawPatch_OpType,
+        kDrawPath_OpType,
+        kDrawPoints_OpType,
+        kDrawRect_OpType,
+        kDrawRRect_OpType,
+        kDrawRegion_OpType,
+        kDrawShadow_OpType,
+        kDrawTextBlob_OpType,
+        kDrawVertices_OpType,
+        kDrawAtlas_OpType,
+        kDrawDrawable_OpType,
+        kDrawEdgeAAQuad_OpType,
+        kDrawEdgeAAImageSet_OpType,
+        kEndDrawPicture_OpType,
+        kRestore_OpType,
+        kSave_OpType,
+        kSaveLayer_OpType,
+        kSetMatrix_OpType,
+
+        kLast_OpType = kSetMatrix_OpType
+    };
+
+    static const int kOpTypeCount = kLast_OpType + 1;
+
+    static void WritePNG(SkBitmap bitmap, SkWStream& out);
+
+    DrawCommand(OpType opType);
+
+    virtual ~DrawCommand() {}
+
+    bool isVisible() const { return fVisible; }
+
+    void setVisible(bool toggle) { fVisible = toggle; }
+
+    virtual void execute(SkCanvas*) const = 0;
+
+    virtual bool render(SkCanvas* canvas) const { return false; }
+
+    virtual void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const;
+
+    static const char* GetCommandString(OpType type);
+
+    // Helper methods for converting things to JSON
+    static void MakeJsonColor(SkJSONWriter&, const SkColor color);
+    static void MakeJsonColor4f(SkJSONWriter&, const SkColor4f& color);
+    static void MakeJsonPoint(SkJSONWriter&, const SkPoint& point);
+    static void MakeJsonPoint(SkJSONWriter&, SkScalar x, SkScalar y);
+    static void MakeJsonPoint3(SkJSONWriter&, const SkPoint3& point);
+    static void MakeJsonRect(SkJSONWriter&, const SkRect& rect);
+    static void MakeJsonIRect(SkJSONWriter&, const SkIRect&);
+    static void MakeJsonMatrix(SkJSONWriter&, const SkMatrix&);
+    static void MakeJsonPath(SkJSONWriter&, const SkPath& path);
+    static void MakeJsonRegion(SkJSONWriter&, const SkRegion& region);
+    static void MakeJsonPaint(SkJSONWriter&, const SkPaint& paint, UrlDataManager& urlDataManager);
+    static void MakeJsonLattice(SkJSONWriter&, const SkCanvas::Lattice& lattice);
+
+    static void flatten(const SkFlattenable* flattenable,
+                        SkJSONWriter&        writer,
+                        UrlDataManager&      urlDataManager);
+    static bool flatten(const SkImage& image, SkJSONWriter& writer, UrlDataManager& urlDataManager);
+    static bool flatten(const SkBitmap& bitmap,
+                        SkJSONWriter&   writer,
+                        UrlDataManager& urlDataManager);
+
+private:
+    OpType fOpType;
+    bool   fVisible;
+};
+
+class RestoreCommand : public DrawCommand {
+public:
+    RestoreCommand();
+    void execute(SkCanvas* canvas) const override;
+
+private:
+    typedef DrawCommand INHERITED;
+};
+
+class ClearCommand : public DrawCommand {
+public:
+    ClearCommand(SkColor color);
+    void execute(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkColor fColor;
+
+    typedef DrawCommand INHERITED;
+};
+
+class ClipPathCommand : public DrawCommand {
+public:
+    ClipPathCommand(const SkPath& path, SkClipOp op, bool doAA);
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkPath   fPath;
+    SkClipOp fOp;
+    bool     fDoAA;
+
+    typedef DrawCommand INHERITED;
+};
+
+class ClipRegionCommand : public DrawCommand {
+public:
+    ClipRegionCommand(const SkRegion& region, SkClipOp op);
+    void execute(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkRegion fRegion;
+    SkClipOp fOp;
+
+    typedef DrawCommand INHERITED;
+};
+
+class ClipRectCommand : public DrawCommand {
+public:
+    ClipRectCommand(const SkRect& rect, SkClipOp op, bool doAA);
+    void execute(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkRect   fRect;
+    SkClipOp fOp;
+    bool     fDoAA;
+
+    typedef DrawCommand INHERITED;
+};
+
+class ClipRRectCommand : public DrawCommand {
+public:
+    ClipRRectCommand(const SkRRect& rrect, SkClipOp op, bool doAA);
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkRRect  fRRect;
+    SkClipOp fOp;
+    bool     fDoAA;
+
+    typedef DrawCommand INHERITED;
+};
+
+class ConcatCommand : public DrawCommand {
+public:
+    ConcatCommand(const SkMatrix& matrix);
+    void execute(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkMatrix fMatrix;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawAnnotationCommand : public DrawCommand {
+public:
+    DrawAnnotationCommand(const SkRect&, const char key[], sk_sp<SkData> value);
+    void execute(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkRect        fRect;
+    SkString      fKey;
+    sk_sp<SkData> fValue;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawBitmapCommand : public DrawCommand {
+public:
+    DrawBitmapCommand(const SkBitmap& bitmap, SkScalar left, SkScalar top, const SkPaint* paint);
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkBitmap         fBitmap;
+    SkScalar         fLeft;
+    SkScalar         fTop;
+    SkTLazy<SkPaint> fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawBitmapLatticeCommand : public DrawCommand {
+public:
+    DrawBitmapLatticeCommand(const SkBitmap&          bitmap,
+                             const SkCanvas::Lattice& lattice,
+                             const SkRect&            dst,
+                             const SkPaint*           paint);
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkBitmap          fBitmap;
+    SkCanvas::Lattice fLattice;
+    SkRect            fDst;
+    SkTLazy<SkPaint>  fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawBitmapNineCommand : public DrawCommand {
+public:
+    DrawBitmapNineCommand(const SkBitmap& bitmap,
+                          const SkIRect&  center,
+                          const SkRect&   dst,
+                          const SkPaint*  paint);
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkBitmap         fBitmap;
+    SkIRect          fCenter;
+    SkRect           fDst;
+    SkTLazy<SkPaint> fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawBitmapRectCommand : public DrawCommand {
+public:
+    DrawBitmapRectCommand(const SkBitmap& bitmap,
+                          const SkRect*   src,
+                          const SkRect&   dst,
+                          const SkPaint*  paint,
+                          SkCanvas::SrcRectConstraint);
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkBitmap                    fBitmap;
+    SkTLazy<SkRect>             fSrc;
+    SkRect                      fDst;
+    SkTLazy<SkPaint>            fPaint;
+    SkCanvas::SrcRectConstraint fConstraint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawImageCommand : public DrawCommand {
+public:
+    DrawImageCommand(const SkImage* image, SkScalar left, SkScalar top, const SkPaint* paint);
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    sk_sp<const SkImage> fImage;
+    SkScalar             fLeft;
+    SkScalar             fTop;
+    SkTLazy<SkPaint>     fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawImageLatticeCommand : public DrawCommand {
+public:
+    DrawImageLatticeCommand(const SkImage*           image,
+                            const SkCanvas::Lattice& lattice,
+                            const SkRect&            dst,
+                            const SkPaint*           paint);
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    sk_sp<const SkImage> fImage;
+    SkCanvas::Lattice    fLattice;
+    SkRect               fDst;
+    SkTLazy<SkPaint>     fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawImageNineCommand : public DrawCommand {
+public:
+    DrawImageNineCommand(const SkImage* image,
+                         const SkIRect& center,
+                         const SkRect&  dst,
+                         const SkPaint* paint);
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    sk_sp<const SkImage> fImage;
+    SkIRect              fCenter;
+    SkRect               fDst;
+    SkTLazy<SkPaint>     fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawImageRectCommand : public DrawCommand {
+public:
+    DrawImageRectCommand(const SkImage*              image,
+                         const SkRect*               src,
+                         const SkRect&               dst,
+                         const SkPaint*              paint,
+                         SkCanvas::SrcRectConstraint constraint);
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    sk_sp<const SkImage>        fImage;
+    SkTLazy<SkRect>             fSrc;
+    SkRect                      fDst;
+    SkTLazy<SkPaint>            fPaint;
+    SkCanvas::SrcRectConstraint fConstraint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawOvalCommand : public DrawCommand {
+public:
+    DrawOvalCommand(const SkRect& oval, const SkPaint& paint);
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkRect  fOval;
+    SkPaint fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawArcCommand : public DrawCommand {
+public:
+    DrawArcCommand(const SkRect&  oval,
+                   SkScalar       startAngle,
+                   SkScalar       sweepAngle,
+                   bool           useCenter,
+                   const SkPaint& paint);
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkRect   fOval;
+    SkScalar fStartAngle;
+    SkScalar fSweepAngle;
+    bool     fUseCenter;
+    SkPaint  fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawPaintCommand : public DrawCommand {
+public:
+    DrawPaintCommand(const SkPaint& paint);
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkPaint fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawPathCommand : public DrawCommand {
+public:
+    DrawPathCommand(const SkPath& path, const SkPaint& paint);
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkPath  fPath;
+    SkPaint fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class BeginDrawPictureCommand : public DrawCommand {
+public:
+    BeginDrawPictureCommand(const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint);
+
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+
+private:
+    sk_sp<const SkPicture> fPicture;
+    SkTLazy<SkMatrix>      fMatrix;
+    SkTLazy<SkPaint>       fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class EndDrawPictureCommand : public DrawCommand {
+public:
+    EndDrawPictureCommand(bool restore);
+
+    void execute(SkCanvas* canvas) const override;
+
+private:
+    bool fRestore;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawPointsCommand : public DrawCommand {
+public:
+    DrawPointsCommand(SkCanvas::PointMode mode,
+                      size_t              count,
+                      const SkPoint       pts[],
+                      const SkPaint&      paint);
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkCanvas::PointMode fMode;
+    SkTDArray<SkPoint>  fPts;
+    SkPaint             fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawRegionCommand : public DrawCommand {
+public:
+    DrawRegionCommand(const SkRegion& region, const SkPaint& paint);
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkRegion fRegion;
+    SkPaint  fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawTextBlobCommand : public DrawCommand {
+public:
+    DrawTextBlobCommand(sk_sp<SkTextBlob> blob, SkScalar x, SkScalar y, const SkPaint& paint);
+
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    sk_sp<SkTextBlob> fBlob;
+    SkScalar          fXPos;
+    SkScalar          fYPos;
+    SkPaint           fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawPatchCommand : public DrawCommand {
+public:
+    DrawPatchCommand(const SkPoint  cubics[12],
+                     const SkColor  colors[4],
+                     const SkPoint  texCoords[4],
+                     SkBlendMode    bmode,
+                     const SkPaint& paint);
+    void execute(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkPoint     fCubics[12];
+    SkColor*    fColorsPtr;
+    SkColor     fColors[4];
+    SkPoint*    fTexCoordsPtr;
+    SkPoint     fTexCoords[4];
+    SkBlendMode fBlendMode;
+    SkPaint     fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawRectCommand : public DrawCommand {
+public:
+    DrawRectCommand(const SkRect& rect, const SkPaint& paint);
+    void execute(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkRect  fRect;
+    SkPaint fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawRRectCommand : public DrawCommand {
+public:
+    DrawRRectCommand(const SkRRect& rrect, const SkPaint& paint);
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkRRect fRRect;
+    SkPaint fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawDRRectCommand : public DrawCommand {
+public:
+    DrawDRRectCommand(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint);
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkRRect fOuter;
+    SkRRect fInner;
+    SkPaint fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawVerticesCommand : public DrawCommand {
+public:
+    DrawVerticesCommand(sk_sp<SkVertices>, SkBlendMode, const SkPaint&);
+
+    void execute(SkCanvas* canvas) const override;
+
+private:
+    sk_sp<SkVertices> fVertices;
+    SkBlendMode       fBlendMode;
+    SkPaint           fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawAtlasCommand : public DrawCommand {
+public:
+    DrawAtlasCommand(const SkImage*,
+                     const SkRSXform[],
+                     const SkRect[],
+                     const SkColor[],
+                     int,
+                     SkBlendMode,
+                     const SkRect*,
+                     const SkPaint*);
+
+    void execute(SkCanvas* canvas) const override;
+
+private:
+    sk_sp<const SkImage> fImage;
+    SkTDArray<SkRSXform> fXform;
+    SkTDArray<SkRect>    fTex;
+    SkTDArray<SkColor>   fColors;
+    SkBlendMode          fBlendMode;
+    SkTLazy<SkRect>      fCull;
+    SkTLazy<SkPaint>     fPaint;
+
+    typedef DrawCommand INHERITED;
+};
+
+class SaveCommand : public DrawCommand {
+public:
+    SaveCommand();
+    void execute(SkCanvas* canvas) const override;
+
+private:
+    typedef DrawCommand INHERITED;
+};
+
+class SaveLayerCommand : public DrawCommand {
+public:
+    SaveLayerCommand(const SkCanvas::SaveLayerRec&);
+    void execute(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkTLazy<SkRect>            fBounds;
+    SkTLazy<SkPaint>           fPaint;
+    sk_sp<const SkImageFilter> fBackdrop;
+    uint32_t                   fSaveLayerFlags;
+
+    typedef DrawCommand INHERITED;
+};
+
+class SetMatrixCommand : public DrawCommand {
+public:
+    SetMatrixCommand(const SkMatrix& matrix);
+    void execute(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkMatrix fMatrix;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawShadowCommand : public DrawCommand {
+public:
+    DrawShadowCommand(const SkPath& path, const SkDrawShadowRec& rec);
+    void execute(SkCanvas* canvas) const override;
+    bool render(SkCanvas* canvas) const override;
+    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
+
+private:
+    SkPath          fPath;
+    SkDrawShadowRec fShadowRec;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawDrawableCommand : public DrawCommand {
+public:
+    DrawDrawableCommand(SkDrawable*, const SkMatrix*);
+    void execute(SkCanvas* canvas) const override;
+
+private:
+    sk_sp<SkDrawable> fDrawable;
+    SkTLazy<SkMatrix> fMatrix;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawEdgeAAQuadCommand : public DrawCommand {
+public:
+    DrawEdgeAAQuadCommand(const SkRect&         rect,
+                          const SkPoint         clip[4],
+                          SkCanvas::QuadAAFlags aa,
+                          SkColor               color,
+                          SkBlendMode           mode);
+    void execute(SkCanvas* canvas) const override;
+
+private:
+    SkRect                fRect;
+    SkPoint               fClip[4];
+    int                   fHasClip;
+    SkCanvas::QuadAAFlags fAA;
+    SkColor               fColor;
+    SkBlendMode           fMode;
+
+    typedef DrawCommand INHERITED;
+};
+
+class DrawEdgeAAImageSetCommand : public DrawCommand {
+public:
+    DrawEdgeAAImageSetCommand(const SkCanvas::ImageSetEntry[],
+                              int count,
+                              const SkPoint[],
+                              const SkMatrix[],
+                              const SkPaint*,
+                              SkCanvas::SrcRectConstraint);
+    void execute(SkCanvas* canvas) const override;
+
+private:
+    SkAutoTArray<SkCanvas::ImageSetEntry> fSet;
+    int                                   fCount;
+    SkAutoTArray<SkPoint>                 fDstClips;
+    SkAutoTArray<SkMatrix>                fPreViewMatrices;
+    SkTLazy<SkPaint>                      fPaint;
+    SkCanvas::SrcRectConstraint           fConstraint;
+
+    typedef DrawCommand INHERITED;
+};
+#endif
diff --git a/tools/debugger/JsonWriteBuffer.cpp b/tools/debugger/JsonWriteBuffer.cpp
new file mode 100644
index 0000000..5e37806
--- /dev/null
+++ b/tools/debugger/JsonWriteBuffer.cpp
@@ -0,0 +1,190 @@
+/*
+ * 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 "JsonWriteBuffer.h"
+
+#include "DrawCommand.h"
+
+void JsonWriteBuffer::append(const char* type) {
+    SkString fullName = SkStringPrintf("%02d_%s", fCount++, type);
+    fWriter->appendName(fullName.c_str());
+}
+
+void JsonWriteBuffer::writePad32(const void* data, size_t size) {
+    this->append("rawBytes");
+    fWriter->beginArray();
+    const uint8_t* bytes = reinterpret_cast<const uint8_t*>(data);
+    for (size_t i = 0; i < size; ++i) {
+        SkString hexByte = SkStringPrintf("%02x", bytes[i]);
+        fWriter->appendString(hexByte.c_str());
+    }
+    fWriter->endArray();
+}
+
+void JsonWriteBuffer::writeByteArray(const void* data, size_t size) {
+    this->append("byteArray");
+    fWriter->beginArray();
+    const uint8_t* bytes = reinterpret_cast<const uint8_t*>(data);
+    for (size_t i = 0; i < size; ++i) {
+        SkString hexByte = SkStringPrintf("%02x", bytes[i]);
+        fWriter->appendString(hexByte.c_str());
+    }
+    fWriter->endArray();
+}
+
+void JsonWriteBuffer::writeBool(bool value) {
+    this->append("bool");
+    fWriter->appendBool(value);
+}
+
+void JsonWriteBuffer::writeScalar(SkScalar value) {
+    this->append("scalar");
+    fWriter->appendFloat(value);
+}
+
+void JsonWriteBuffer::writeScalarArray(const SkScalar* value, uint32_t count) {
+    this->append("scalarArray");
+    fWriter->beginArray();
+    for (uint32_t i = 0; i < count; ++i) {
+        fWriter->appendFloat(value[i]);
+    }
+    fWriter->endArray();
+}
+
+void JsonWriteBuffer::writeInt(int32_t value) {
+    this->append("int");
+    fWriter->appendS32(value);
+}
+
+void JsonWriteBuffer::writeIntArray(const int32_t* value, uint32_t count) {
+    this->append("intArray");
+    fWriter->beginArray();
+    for (uint32_t i = 0; i < count; ++i) {
+        fWriter->appendS32(value[i]);
+    }
+    fWriter->endArray();
+}
+
+void JsonWriteBuffer::writeUInt(uint32_t value) {
+    this->append("uint");
+    fWriter->appendU32(value);
+}
+
+void JsonWriteBuffer::writeString(const char* value) {
+    this->append("string");
+    fWriter->appendString(value);
+}
+
+void JsonWriteBuffer::writeFlattenable(const SkFlattenable* flattenable) {
+    if (flattenable) {
+        this->append(flattenable->getTypeName());
+        fWriter->beginObject();
+        JsonWriteBuffer flattenableBuffer(fWriter, fUrlDataManager);
+        flattenable->flatten(flattenableBuffer);
+        fWriter->endObject();
+    } else {
+        this->append("flattenable");
+        fWriter->appendPointer(nullptr);
+    }
+}
+
+void JsonWriteBuffer::writeColor(SkColor color) {
+    this->append("color");
+    DrawCommand::MakeJsonColor(*fWriter, color);
+}
+
+void JsonWriteBuffer::writeColorArray(const SkColor* color, uint32_t count) {
+    this->append("colorArray");
+    fWriter->beginArray();
+    for (uint32_t i = 0; i < count; ++i) {
+        DrawCommand::MakeJsonColor(*fWriter, color[i]);
+    }
+    fWriter->endArray();
+}
+
+void JsonWriteBuffer::writeColor4f(const SkColor4f& color) {
+    this->append("color");
+    DrawCommand::MakeJsonColor4f(*fWriter, color);
+}
+
+void JsonWriteBuffer::writeColor4fArray(const SkColor4f* color, uint32_t count) {
+    this->append("colorArray");
+    fWriter->beginArray();
+    for (uint32_t i = 0; i < count; ++i) {
+        DrawCommand::MakeJsonColor4f(*fWriter, color[i]);
+    }
+    fWriter->endArray();
+}
+
+void JsonWriteBuffer::writePoint(const SkPoint& point) {
+    this->append("point");
+    DrawCommand::MakeJsonPoint(*fWriter, point);
+}
+
+void JsonWriteBuffer::writePoint3(const SkPoint3& point) {
+    this->append("point3");
+    DrawCommand::MakeJsonPoint3(*fWriter, point);
+}
+
+void JsonWriteBuffer::writePointArray(const SkPoint* point, uint32_t count) {
+    this->append("pointArray");
+    fWriter->beginArray();
+    for (uint32_t i = 0; i < count; ++i) {
+        DrawCommand::MakeJsonPoint(*fWriter, point[i]);
+    }
+    fWriter->endArray();
+}
+
+void JsonWriteBuffer::writeMatrix(const SkMatrix& matrix) {
+    this->append("matrix");
+    DrawCommand::MakeJsonMatrix(*fWriter, matrix);
+}
+
+void JsonWriteBuffer::writeIRect(const SkIRect& rect) {
+    this->append("irect");
+    DrawCommand::MakeJsonIRect(*fWriter, rect);
+}
+
+void JsonWriteBuffer::writeRect(const SkRect& rect) {
+    this->append("rect");
+    DrawCommand::MakeJsonRect(*fWriter, rect);
+}
+
+void JsonWriteBuffer::writeRegion(const SkRegion& region) {
+    this->append("region");
+    DrawCommand::MakeJsonRegion(*fWriter, region);
+}
+
+void JsonWriteBuffer::writePath(const SkPath& path) {
+    this->append("path");
+    DrawCommand::MakeJsonPath(*fWriter, path);
+}
+
+size_t JsonWriteBuffer::writeStream(SkStream* stream, size_t length) {
+    // Contents not supported
+    this->append("stream");
+    fWriter->appendU64(static_cast<uint64_t>(length));
+    return 0;
+}
+
+void JsonWriteBuffer::writeImage(const SkImage* image) {
+    this->append("image");
+    fWriter->beginObject();
+    DrawCommand::flatten(*image, *fWriter, *fUrlDataManager);
+    fWriter->endObject();
+}
+
+void JsonWriteBuffer::writeTypeface(SkTypeface* typeface) {
+    // Unsupported
+    this->append("typeface");
+    fWriter->appendPointer(typeface);
+}
+
+void JsonWriteBuffer::writePaint(const SkPaint& paint) {
+    this->append("paint");
+    DrawCommand::MakeJsonPaint(*fWriter, paint, *fUrlDataManager);
+}
diff --git a/tools/debugger/JsonWriteBuffer.h b/tools/debugger/JsonWriteBuffer.h
new file mode 100644
index 0000000..6fdd789
--- /dev/null
+++ b/tools/debugger/JsonWriteBuffer.h
@@ -0,0 +1,58 @@
+/*
+ * 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 JsonWriteBuffer_DEFINED
+#define JsonWriteBuffer_DEFINED
+
+#include "SkWriteBuffer.h"
+
+class SkJSONWriter;
+class SkPath;
+class UrlDataManager;
+
+class JsonWriteBuffer final : public SkWriteBuffer {
+public:
+    JsonWriteBuffer(SkJSONWriter* writer, UrlDataManager* urlDataManager)
+            : fUrlDataManager(urlDataManager), fWriter(writer), fCount(0) {}
+
+    void writePad32(const void* buffer, size_t bytes) override;
+    void writeByteArray(const void* data, size_t size) override;
+    void writeBool(bool value) override;
+    void writeScalar(SkScalar value) override;
+    void writeScalarArray(const SkScalar* value, uint32_t count) override;
+    void writeInt(int32_t value) override;
+    void writeIntArray(const int32_t* value, uint32_t count) override;
+    void writeUInt(uint32_t value) override;
+    void writeString(const char* value) override;
+
+    void   writeFlattenable(const SkFlattenable* flattenable) override;
+    void   writeColor(SkColor color) override;
+    void   writeColorArray(const SkColor* color, uint32_t count) override;
+    void   writeColor4f(const SkColor4f& color) override;
+    void   writeColor4fArray(const SkColor4f* color, uint32_t count) override;
+    void   writePoint(const SkPoint& point) override;
+    void   writePointArray(const SkPoint* point, uint32_t count) override;
+    void   writePoint3(const SkPoint3& point) override;
+    void   writeMatrix(const SkMatrix& matrix) override;
+    void   writeIRect(const SkIRect& rect) override;
+    void   writeRect(const SkRect& rect) override;
+    void   writeRegion(const SkRegion& region) override;
+    void   writePath(const SkPath& path) override;
+    size_t writeStream(SkStream* stream, size_t length) override;
+    void   writeImage(const SkImage*) override;
+    void   writeTypeface(SkTypeface* typeface) override;
+    void   writePaint(const SkPaint& paint) override;
+
+private:
+    void append(const char* type);
+
+    UrlDataManager* fUrlDataManager;
+    SkJSONWriter*   fWriter;
+    int             fCount;
+};
+
+#endif
diff --git a/tools/debugger/SkDebugCanvas.cpp b/tools/debugger/SkDebugCanvas.cpp
deleted file mode 100644
index cbb420c..0000000
--- a/tools/debugger/SkDebugCanvas.cpp
+++ /dev/null
@@ -1,483 +0,0 @@
-/*
- * Copyright 2012 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkCanvasPriv.h"
-#include "SkDebugCanvas.h"
-#include "SkDrawCommand.h"
-#include "SkJSONWriter.h"
-#include "SkPaintFilterCanvas.h"
-#include "SkPicture.h"
-#include "SkRectPriv.h"
-#include "SkTextBlob.h"
-#include "SkClipOpPriv.h"
-
-#include "GrAuditTrail.h"
-#include "GrContext.h"
-#include "GrRenderTargetContext.h"
-
-#define SKDEBUGCANVAS_VERSION                     1
-#define SKDEBUGCANVAS_ATTRIBUTE_VERSION           "version"
-#define SKDEBUGCANVAS_ATTRIBUTE_COMMANDS          "commands"
-#define SKDEBUGCANVAS_ATTRIBUTE_AUDITTRAIL        "auditTrail"
-
-class DebugPaintFilterCanvas : public SkPaintFilterCanvas {
-public:
-    DebugPaintFilterCanvas(SkCanvas* canvas,
-                           bool overdrawViz)
-        : INHERITED(canvas)
-        , fOverdrawViz(overdrawViz) {}
-
-protected:
-    bool onFilter(SkTCopyOnFirstWrite<SkPaint>* paint, Type) const override {
-        if (*paint) {
-            if (fOverdrawViz) {
-                paint->writable()->setColor(SK_ColorRED);
-                paint->writable()->setAlpha(0x08);
-                paint->writable()->setBlendMode(SkBlendMode::kSrcOver);
-            }
-        }
-        return true;
-    }
-
-    void onDrawPicture(const SkPicture* picture,
-                       const SkMatrix* matrix,
-                       const SkPaint* paint) override {
-        // We need to replay the picture onto this canvas in order to filter its internal paints.
-        this->SkCanvas::onDrawPicture(picture, matrix, paint);
-    }
-
-private:
-    bool fOverdrawViz;
-
-    typedef SkPaintFilterCanvas INHERITED;
-};
-
-SkDebugCanvas::SkDebugCanvas(int width, int height)
-        : INHERITED(width, height)
-        , fOverdrawViz(false)
-        , fClipVizColor(SK_ColorTRANSPARENT)
-        , fDrawGpuOpBounds(false) {
-    // SkPicturePlayback uses the base-class' quickReject calls to cull clipped
-    // operations. This can lead to problems in the debugger which expects all
-    // the operations in the captured skp to appear in the debug canvas. To
-    // circumvent this we create a wide open clip here (an empty clip rect
-    // is not sufficient).
-    // Internally, the SkRect passed to clipRect is converted to an SkIRect and
-    // rounded out. The following code creates a nearly maximal rect that will
-    // not get collapsed by the coming conversions (Due to precision loss the
-    // inset has to be surprisingly large).
-    SkIRect largeIRect = SkRectPriv::MakeILarge();
-    largeIRect.inset(1024, 1024);
-    SkRect large = SkRect::Make(largeIRect);
-#ifdef SK_DEBUG
-    SkASSERT(!large.roundOut().isEmpty());
-#endif
-    // call the base class' version to avoid adding a draw command
-    this->INHERITED::onClipRect(large, kReplace_SkClipOp, kHard_ClipEdgeStyle);
-}
-
-SkDebugCanvas::~SkDebugCanvas() {
-    fCommandVector.deleteAll();
-}
-
-void SkDebugCanvas::addDrawCommand(SkDrawCommand* command) {
-    fCommandVector.push_back(command);
-}
-
-void SkDebugCanvas::draw(SkCanvas* canvas) {
-    if (!fCommandVector.isEmpty()) {
-        this->drawTo(canvas, fCommandVector.count() - 1);
-    }
-}
-
-void SkDebugCanvas::drawTo(SkCanvas* originalCanvas, int index, int m) {
-    SkASSERT(!fCommandVector.isEmpty());
-    SkASSERT(index < fCommandVector.count());
-
-    int saveCount = originalCanvas->save();
-
-    SkRect windowRect = SkRect::MakeWH(SkIntToScalar(originalCanvas->getBaseLayerSize().width()),
-                                       SkIntToScalar(originalCanvas->getBaseLayerSize().height()));
-
-    originalCanvas->clear(SK_ColorWHITE);
-    originalCanvas->resetMatrix();
-    if (!windowRect.isEmpty()) {
-        originalCanvas->clipRect(windowRect, kReplace_SkClipOp);
-    }
-
-    DebugPaintFilterCanvas filterCanvas(originalCanvas, fOverdrawViz);
-
-    // If we have a GPU backend we can also visualize the op information
-    GrAuditTrail* at = nullptr;
-    if (fDrawGpuOpBounds || m != -1) {
-        // The audit trail must be obtained from the original canvas.
-        at = this->getAuditTrail(originalCanvas);
-    }
-
-    for (int i = 0; i <= index; i++) {
-        // We need to flush any pending operations, or they might combine with commands below.
-        // Previous operations were not registered with the audit trail when they were
-        // created, so if we allow them to combine, the audit trail will fail to find them.
-        filterCanvas.flush();
-
-        GrAuditTrail::AutoCollectOps* acb = nullptr;
-        if (at) {
-            acb = new GrAuditTrail::AutoCollectOps(at, i);
-        }
-
-        if (fCommandVector[i]->isVisible()) {
-            fCommandVector[i]->execute(&filterCanvas);
-        }
-        if (at && acb) {
-            delete acb;
-        }
-    }
-
-    if (SkColorGetA(fClipVizColor) != 0) {
-        filterCanvas.save();
-        #define LARGE_COORD 1000000000
-        filterCanvas.clipRect(
-                SkRect::MakeLTRB(-LARGE_COORD, -LARGE_COORD, LARGE_COORD, LARGE_COORD),
-                kReverseDifference_SkClipOp);
-        SkPaint clipPaint;
-        clipPaint.setColor(fClipVizColor);
-        filterCanvas.drawPaint(clipPaint);
-        filterCanvas.restore();
-    }
-
-    fMatrix = filterCanvas.getTotalMatrix();
-    fClip = filterCanvas.getDeviceClipBounds();
-    filterCanvas.restoreToCount(saveCount);
-
-    // draw any ops if required and issue a full reset onto GrAuditTrail
-    if (at) {
-        // just in case there is global reordering, we flush the canvas before querying
-        // GrAuditTrail
-        GrAuditTrail::AutoEnable ae(at);
-        filterCanvas.flush();
-
-        // we pick three colorblind-safe colors, 75% alpha
-        static const SkColor kTotalBounds = SkColorSetARGB(0xC0, 0x6A, 0x3D, 0x9A);
-        static const SkColor kCommandOpBounds = SkColorSetARGB(0xC0, 0xE3, 0x1A, 0x1C);
-        static const SkColor kOtherOpBounds = SkColorSetARGB(0xC0, 0xFF, 0x7F, 0x00);
-
-        // get the render target of the top device (from the original canvas) so we can ignore ops
-        // drawn offscreen
-        GrRenderTargetContext* rtc =
-                originalCanvas->internal_private_accessTopLayerRenderTargetContext();
-        GrSurfaceProxy::UniqueID proxyID = rtc->asSurfaceProxy()->uniqueID();
-
-        // get the bounding boxes to draw
-        SkTArray<GrAuditTrail::OpInfo> childrenBounds;
-        if (m == -1) {
-            at->getBoundsByClientID(&childrenBounds, index);
-        } else {
-            // the client wants us to draw the mth op
-            at->getBoundsByOpListID(&childrenBounds.push_back(), m);
-        }
-        SkPaint paint;
-        paint.setStyle(SkPaint::kStroke_Style);
-        paint.setStrokeWidth(1);
-        for (int i = 0; i < childrenBounds.count(); i++) {
-            if (childrenBounds[i].fProxyUniqueID != proxyID) {
-                // offscreen draw, ignore for now
-                continue;
-            }
-            paint.setColor(kTotalBounds);
-            filterCanvas.drawRect(childrenBounds[i].fBounds, paint);
-            for (int j = 0; j < childrenBounds[i].fOps.count(); j++) {
-                const GrAuditTrail::OpInfo::Op& op = childrenBounds[i].fOps[j];
-                if (op.fClientID != index) {
-                    paint.setColor(kOtherOpBounds);
-                } else {
-                    paint.setColor(kCommandOpBounds);
-                }
-                filterCanvas.drawRect(op.fBounds, paint);
-            }
-        }
-    }
-    this->cleanupAuditTrail(originalCanvas);
-}
-
-void SkDebugCanvas::deleteDrawCommandAt(int index) {
-    SkASSERT(index < fCommandVector.count());
-    delete fCommandVector[index];
-    fCommandVector.remove(index);
-}
-
-SkDrawCommand* SkDebugCanvas::getDrawCommandAt(int index) {
-    SkASSERT(index < fCommandVector.count());
-    return fCommandVector[index];
-}
-
-GrAuditTrail* SkDebugCanvas::getAuditTrail(SkCanvas* canvas) {
-    GrAuditTrail* at = nullptr;
-    GrContext* ctx = canvas->getGrContext();
-    if (ctx) {
-        at = ctx->priv().getAuditTrail();
-    }
-    return at;
-}
-
-void SkDebugCanvas::drawAndCollectOps(int n, SkCanvas* canvas) {
-    GrAuditTrail* at = this->getAuditTrail(canvas);
-    if (at) {
-        // loop over all of the commands and draw them, this is to collect reordering
-        // information
-        for (int i = 0; i < this->getSize() && i <= n; i++) {
-            GrAuditTrail::AutoCollectOps enable(at, i);
-            fCommandVector[i]->execute(canvas);
-        }
-
-        // in case there is some kind of global reordering
-        {
-            GrAuditTrail::AutoEnable ae(at);
-            canvas->flush();
-        }
-    }
-}
-
-void SkDebugCanvas::cleanupAuditTrail(SkCanvas* canvas) {
-    GrAuditTrail* at = this->getAuditTrail(canvas);
-    if (at) {
-        GrAuditTrail::AutoEnable ae(at);
-        at->fullReset();
-    }
-}
-
-void SkDebugCanvas::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager, int n,
-                           SkCanvas* canvas) {
-    this->drawAndCollectOps(n, canvas);
-
-    // now collect json
-    GrAuditTrail* at = this->getAuditTrail(canvas);
-    writer.appendS32(SKDEBUGCANVAS_ATTRIBUTE_VERSION, SKDEBUGCANVAS_VERSION);
-    writer.beginArray(SKDEBUGCANVAS_ATTRIBUTE_COMMANDS);
-
-    for (int i = 0; i < this->getSize() && i <= n; i++) {
-        writer.beginObject(); // command
-        this->getDrawCommandAt(i)->toJSON(writer, urlDataManager);
-
-        if (at) {
-            writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_AUDITTRAIL);
-            at->toJson(writer, i);
-        }
-        writer.endObject(); // command
-    }
-
-    writer.endArray(); // commands
-    this->cleanupAuditTrail(canvas);
-}
-
-void SkDebugCanvas::toJSONOpList(SkJSONWriter& writer, int n, SkCanvas* canvas) {
-    this->drawAndCollectOps(n, canvas);
-
-    GrAuditTrail* at = this->getAuditTrail(canvas);
-    if (at) {
-        GrAuditTrail::AutoManageOpList enable(at);
-        at->toJson(writer);
-    } else {
-        writer.beginObject();
-        writer.endObject();
-    }
-    this->cleanupAuditTrail(canvas);
-}
-
-void SkDebugCanvas::setOverdrawViz(bool overdrawViz) {
-    fOverdrawViz = overdrawViz;
-}
-
-void SkDebugCanvas::onClipPath(const SkPath& path, SkClipOp op, ClipEdgeStyle edgeStyle) {
-    this->addDrawCommand(new SkClipPathCommand(path, op, kSoft_ClipEdgeStyle == edgeStyle));
-}
-
-void SkDebugCanvas::onClipRect(const SkRect& rect, SkClipOp op, ClipEdgeStyle edgeStyle) {
-    this->addDrawCommand(new SkClipRectCommand(rect, op, kSoft_ClipEdgeStyle == edgeStyle));
-}
-
-void SkDebugCanvas::onClipRRect(const SkRRect& rrect, SkClipOp op, ClipEdgeStyle edgeStyle) {
-    this->addDrawCommand(new SkClipRRectCommand(rrect, op, kSoft_ClipEdgeStyle == edgeStyle));
-}
-
-void SkDebugCanvas::onClipRegion(const SkRegion& region, SkClipOp op) {
-    this->addDrawCommand(new SkClipRegionCommand(region, op));
-}
-
-void SkDebugCanvas::didConcat(const SkMatrix& matrix) {
-    this->addDrawCommand(new SkConcatCommand(matrix));
-    this->INHERITED::didConcat(matrix);
-}
-
-void SkDebugCanvas::onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) {
-    this->addDrawCommand(new SkDrawAnnotationCommand(rect, key, sk_ref_sp(value)));
-}
-
-void SkDebugCanvas::onDrawBitmap(const SkBitmap& bitmap, SkScalar left,
-                                 SkScalar top, const SkPaint* paint) {
-    this->addDrawCommand(new SkDrawBitmapCommand(bitmap, left, top, paint));
-}
-
-void SkDebugCanvas::onDrawBitmapLattice(const SkBitmap& bitmap, const Lattice& lattice,
-                                        const SkRect& dst, const SkPaint* paint) {
-    this->addDrawCommand(new SkDrawBitmapLatticeCommand(bitmap, lattice, dst, paint));
-}
-
-void SkDebugCanvas::onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* src, const SkRect& dst,
-                                     const SkPaint* paint, SrcRectConstraint constraint) {
-    this->addDrawCommand(new SkDrawBitmapRectCommand(bitmap, src, dst, paint,
-                                                     (SrcRectConstraint)constraint));
-}
-
-void SkDebugCanvas::onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center,
-                                     const SkRect& dst, const SkPaint* paint) {
-    this->addDrawCommand(new SkDrawBitmapNineCommand(bitmap, center, dst, paint));
-}
-
-void SkDebugCanvas::onDrawImage(const SkImage* image, SkScalar left, SkScalar top,
-                                const SkPaint* paint) {
-    this->addDrawCommand(new SkDrawImageCommand(image, left, top, paint));
-}
-
-void SkDebugCanvas::onDrawImageLattice(const SkImage* image, const Lattice& lattice,
-                                       const SkRect& dst, const SkPaint* paint) {
-    this->addDrawCommand(new SkDrawImageLatticeCommand(image, lattice, dst, paint));
-}
-
-void SkDebugCanvas::onDrawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst,
-                                    const SkPaint* paint, SrcRectConstraint constraint) {
-    this->addDrawCommand(new SkDrawImageRectCommand(image, src, dst, paint, constraint));
-}
-
-void SkDebugCanvas::onDrawImageNine(const SkImage* image, const SkIRect& center,
-                                    const SkRect& dst, const SkPaint* paint) {
-    this->addDrawCommand(new SkDrawImageNineCommand(image, center, dst, paint));
-}
-
-void SkDebugCanvas::onDrawImageSet(const SkCanvas::ImageSetEntry set[], int count,
-                                   SkFilterQuality filterQuality, SkBlendMode mode) {
-    this->addDrawCommand(new SkDrawImageSetCommand(set, count, filterQuality, mode));
-}
-
-void SkDebugCanvas::onDrawOval(const SkRect& oval, const SkPaint& paint) {
-    this->addDrawCommand(new SkDrawOvalCommand(oval, paint));
-}
-
-void SkDebugCanvas::onDrawArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
-                               bool useCenter, const SkPaint& paint) {
-    this->addDrawCommand(new SkDrawArcCommand(oval, startAngle, sweepAngle, useCenter, paint));
-}
-
-void SkDebugCanvas::onDrawPaint(const SkPaint& paint) {
-    this->addDrawCommand(new SkDrawPaintCommand(paint));
-}
-
-void SkDebugCanvas::onDrawPath(const SkPath& path, const SkPaint& paint) {
-    this->addDrawCommand(new SkDrawPathCommand(path, paint));
-}
-
-void SkDebugCanvas::onDrawRegion(const SkRegion& region, const SkPaint& paint) {
-    this->addDrawCommand(new SkDrawRegionCommand(region, paint));
-}
-
-void SkDebugCanvas::onDrawPicture(const SkPicture* picture,
-                                  const SkMatrix* matrix,
-                                  const SkPaint* paint) {
-    this->addDrawCommand(new SkBeginDrawPictureCommand(picture, matrix, paint));
-    SkAutoCanvasMatrixPaint acmp(this, matrix, paint, picture->cullRect());
-    picture->playback(this);
-    this->addDrawCommand(new SkEndDrawPictureCommand(SkToBool(matrix) || SkToBool(paint)));
-}
-
-void SkDebugCanvas::onDrawPoints(PointMode mode, size_t count,
-                                 const SkPoint pts[], const SkPaint& paint) {
-    this->addDrawCommand(new SkDrawPointsCommand(mode, count, pts, paint));
-}
-
-void SkDebugCanvas::onDrawRect(const SkRect& rect, const SkPaint& paint) {
-    // NOTE(chudy): Messing up when renamed to DrawRect... Why?
-    addDrawCommand(new SkDrawRectCommand(rect, paint));
-}
-
-void SkDebugCanvas::onDrawEdgeAARect(const SkRect& rect, SkCanvas::QuadAAFlags aa, SkColor color,
-                                     SkBlendMode mode) {
-    this->addDrawCommand(new SkDrawEdgeAARectCommand(rect, aa, color, mode));
-}
-
-void SkDebugCanvas::onDrawRRect(const SkRRect& rrect, const SkPaint& paint) {
-    this->addDrawCommand(new SkDrawRRectCommand(rrect, paint));
-}
-
-void SkDebugCanvas::onDrawDRRect(const SkRRect& outer, const SkRRect& inner,
-                                 const SkPaint& paint) {
-    this->addDrawCommand(new SkDrawDRRectCommand(outer, inner, paint));
-}
-
-void SkDebugCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
-                                   const SkPaint& paint) {
-    this->addDrawCommand(new SkDrawTextBlobCommand(sk_ref_sp(const_cast<SkTextBlob*>(blob)),
-                                                   x, y, paint));
-}
-
-void SkDebugCanvas::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
-                                const SkPoint texCoords[4], SkBlendMode bmode,
-                                const SkPaint& paint) {
-    this->addDrawCommand(new SkDrawPatchCommand(cubics, colors, texCoords, bmode, paint));
-}
-
-void SkDebugCanvas::onDrawVerticesObject(const SkVertices* vertices, const SkVertices::Bone bones[],
-                                         int boneCount, SkBlendMode bmode, const SkPaint& paint) {
-    // TODO: ANIMATION NOT LOGGED
-    this->addDrawCommand(new SkDrawVerticesCommand(sk_ref_sp(const_cast<SkVertices*>(vertices)),
-                                                   bmode, paint));
-}
-
-void SkDebugCanvas::onDrawAtlas(const SkImage* image, const SkRSXform xform[], const SkRect tex[],
-                                const SkColor colors[], int count, SkBlendMode bmode,
-                                const SkRect* cull, const SkPaint* paint) {
-    this->addDrawCommand(new SkDrawAtlasCommand(image, xform, tex, colors, count, bmode, cull,
-                                                paint));
-}
-
-void SkDebugCanvas::onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) {
-    this->addDrawCommand(new SkDrawShadowCommand(path, rec));
-}
-
-void SkDebugCanvas::onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) {
-    this->addDrawCommand(new SkDrawDrawableCommand(drawable, matrix));
-}
-
-void SkDebugCanvas::willRestore() {
-    this->addDrawCommand(new SkRestoreCommand());
-    this->INHERITED::willRestore();
-}
-
-void SkDebugCanvas::willSave() {
-    this->addDrawCommand(new SkSaveCommand());
-    this->INHERITED::willSave();
-}
-
-SkCanvas::SaveLayerStrategy SkDebugCanvas::getSaveLayerStrategy(const SaveLayerRec& rec) {
-    this->addDrawCommand(new SkSaveLayerCommand(rec));
-    (void)this->INHERITED::getSaveLayerStrategy(rec);
-    // No need for a full layer.
-    return kNoLayer_SaveLayerStrategy;
-}
-
-bool SkDebugCanvas::onDoSaveBehind(const SkRect* subset) {
-    // TODO
-    return false;
-}
-
-void SkDebugCanvas::didSetMatrix(const SkMatrix& matrix) {
-    this->addDrawCommand(new SkSetMatrixCommand(matrix));
-    this->INHERITED::didSetMatrix(matrix);
-}
-
-void SkDebugCanvas::toggleCommand(int index, bool toggle) {
-    SkASSERT(index < fCommandVector.count());
-    fCommandVector[index]->setVisible(toggle);
-}
diff --git a/tools/debugger/SkDebugCanvas.h b/tools/debugger/SkDebugCanvas.h
deleted file mode 100644
index 194ce11..0000000
--- a/tools/debugger/SkDebugCanvas.h
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright 2012 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-
-#ifndef SKDEBUGCANVAS_H_
-#define SKDEBUGCANVAS_H_
-
-#include "SkCanvas.h"
-#include "SkCanvasVirtualEnforcer.h"
-#include "SkDrawCommand.h"
-#include "SkPath.h"
-#include "SkPathOps.h"
-#include "SkString.h"
-#include "SkTArray.h"
-#include "SkVertices.h"
-#include "UrlDataManager.h"
-
-class GrAuditTrail;
-class SkNWayCanvas;
-class SkPicture;
-
-class SkDebugCanvas : public SkCanvasVirtualEnforcer<SkCanvas> {
-public:
-    SkDebugCanvas(int width, int height);
-
-    ~SkDebugCanvas() override;
-
-    /**
-     * Enable or disable overdraw visualization
-     */
-    void setOverdrawViz(bool overdrawViz);
-
-    bool getOverdrawViz() const { return fOverdrawViz; }
-
-    /**
-     * Set the color of the clip visualization. An alpha of zero renders the clip invisible.
-     */
-    void setClipVizColor(SkColor clipVizColor) { this->fClipVizColor = clipVizColor; }
-
-    void setDrawGpuOpBounds(bool drawGpuOpBounds) { fDrawGpuOpBounds = drawGpuOpBounds; }
-
-    bool getDrawGpuOpBounds() const { return fDrawGpuOpBounds; }
-
-    /**
-        Executes all draw calls to the canvas.
-        @param canvas  The canvas being drawn to
-     */
-    void draw(SkCanvas *canvas);
-
-    /**
-        Executes the draw calls up to the specified index.
-        @param canvas  The canvas being drawn to
-        @param index  The index of the final command being executed
-        @param m an optional Mth gpu op to highlight, or -1
-     */
-    void drawTo(SkCanvas *canvas, int index, int m = -1);
-
-    /**
-        Returns the most recently calculated transformation matrix
-     */
-    const SkMatrix &getCurrentMatrix() {
-        return fMatrix;
-    }
-
-    /**
-        Returns the most recently calculated clip
-     */
-    const SkIRect &getCurrentClip() {
-        return fClip;
-    }
-
-    /**
-        Removes the command at the specified index
-        @param index  The index of the command to delete
-     */
-    void deleteDrawCommandAt(int index);
-
-    /**
-        Returns the draw command at the given index.
-        @param index  The index of the command
-     */
-    SkDrawCommand *getDrawCommandAt(int index);
-
-    /**
-        Returns length of draw command vector.
-     */
-    int getSize() const {
-        return fCommandVector.count();
-    }
-
-    /**
-        Toggles the visibility / execution of the draw command at index i with
-        the value of toggle.
-     */
-    void toggleCommand(int index, bool toggle);
-
-    /**
-        Returns a JSON object representing up to the Nth draw, where N is less than
-        SkDebugCanvas::getSize(). The encoder may use the UrlDataManager to store binary data such
-        as images, referring to them via URLs embedded in the JSON.
-     */
-    void toJSON(SkJSONWriter& writer, UrlDataManager &urlDataManager, int n, SkCanvas *);
-
-    void toJSONOpList(SkJSONWriter& writer, int n, SkCanvas*);
-
-    void detachCommands(SkTDArray<SkDrawCommand*>* dst) {
-        fCommandVector.swap(*dst);
-    }
-
-protected:
-    void willSave() override;
-    SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec &) override;
-    bool onDoSaveBehind(const SkRect*) override;
-    void willRestore() override;
-
-    void didConcat(const SkMatrix &) override;
-
-    void didSetMatrix(const SkMatrix &) override;
-
-    void onDrawAnnotation(const SkRect&, const char[], SkData*) override;
-    void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override;
-    void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
-                        const SkPaint& paint) override;
-
-    void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
-                     const SkPoint texCoords[4], SkBlendMode, const SkPaint& paint) override;
-    void onDrawPaint(const SkPaint&) override;
-
-    void onDrawRect(const SkRect&, const SkPaint&) override;
-    void onDrawEdgeAARect(const SkRect&, SkCanvas::QuadAAFlags, SkColor, SkBlendMode) override;
-    void onDrawOval(const SkRect&, const SkPaint&) override;
-    void onDrawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&) override;
-    void onDrawRRect(const SkRRect&, const SkPaint&) override;
-    void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) override;
-    void onDrawVerticesObject(const SkVertices*, const SkVertices::Bone bones[], int boneCount,
-                              SkBlendMode, const SkPaint&) override;
-    void onDrawPath(const SkPath&, const SkPaint&) override;
-    void onDrawRegion(const SkRegion&, const SkPaint&) override;
-    void onDrawBitmap(const SkBitmap&, SkScalar left, SkScalar top, const SkPaint*) override;
-    void onDrawBitmapLattice(const SkBitmap&, const Lattice&, const SkRect&,
-                             const SkPaint*) override;
-    void onDrawBitmapRect(const SkBitmap&, const SkRect* src, const SkRect& dst, const SkPaint*,
-                          SrcRectConstraint) override;
-    void onDrawImage(const SkImage*, SkScalar left, SkScalar top, const SkPaint*) override;
-    void onDrawImageLattice(const SkImage* image, const Lattice& lattice,
-                            const SkRect& dst, const SkPaint* paint) override;
-    void onDrawImageRect(const SkImage*, const SkRect* src, const SkRect& dst,
-                         const SkPaint*, SrcRectConstraint) override;
-    void onDrawImageSet(const ImageSetEntry[], int count, SkFilterQuality, SkBlendMode) override;
-    void onDrawBitmapNine(const SkBitmap&, const SkIRect& center, const SkRect& dst,
-                          const SkPaint*) override;
-    void onDrawImageNine(const SkImage*, const SkIRect& center, const SkRect& dst,
-                         const SkPaint*) override;
-    void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[],
-                     int, SkBlendMode, const SkRect*, const SkPaint*) override;
-    void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override;
-    void onClipRRect(const SkRRect&, SkClipOp, ClipEdgeStyle) override;
-    void onClipPath(const SkPath&, SkClipOp, ClipEdgeStyle) override;
-    void onClipRegion(const SkRegion& region, SkClipOp) override;
-    void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override;
-
-    void onDrawDrawable(SkDrawable*, const SkMatrix*) override;
-    void onDrawPicture(const SkPicture*, const SkMatrix*, const SkPaint*) override;
-
-private:
-    SkTDArray<SkDrawCommand*> fCommandVector;
-    SkMatrix fMatrix;
-    SkIRect fClip;
-
-    bool fOverdrawViz;
-    SkColor fClipVizColor;
-    bool fDrawGpuOpBounds;
-
-    /**
-        Adds the command to the class' vector of commands.
-        @param command  The draw command for execution
-     */
-    void addDrawCommand(SkDrawCommand* command);
-
-    GrAuditTrail* getAuditTrail(SkCanvas*);
-
-    void drawAndCollectOps(int n, SkCanvas*);
-    void cleanupAuditTrail(SkCanvas*);
-
-    typedef SkCanvasVirtualEnforcer<SkCanvas> INHERITED;
-};
-
-#endif
diff --git a/tools/debugger/SkDrawCommand.cpp b/tools/debugger/SkDrawCommand.cpp
deleted file mode 100644
index d9723ac..0000000
--- a/tools/debugger/SkDrawCommand.cpp
+++ /dev/null
@@ -1,2103 +0,0 @@
-/*
- * Copyright 2012 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkDrawCommand.h"
-
-#include <algorithm>
-#include "SkAutoMalloc.h"
-#include "SkClipOpPriv.h"
-#include "SkColorFilter.h"
-#include "SkDashPathEffect.h"
-#include "SkDrawable.h"
-#include "SkImageFilter.h"
-#include "SkJsonWriteBuffer.h"
-#include "SkLatticeIter.h"
-#include "SkMaskFilterBase.h"
-#include "SkPaintDefaults.h"
-#include "SkPathEffect.h"
-#include "SkPicture.h"
-#include "SkPngEncoder.h"
-#include "SkReadBuffer.h"
-#include "SkRectPriv.h"
-#include "SkShadowFlags.h"
-#include "SkTHash.h"
-#include "SkTextBlobPriv.h"
-#include "SkTypeface.h"
-#include "SkWriteBuffer.h"
-
-#define SKDEBUGCANVAS_ATTRIBUTE_COMMAND           "command"
-#define SKDEBUGCANVAS_ATTRIBUTE_VISIBLE           "visible"
-#define SKDEBUGCANVAS_ATTRIBUTE_MATRIX            "matrix"
-#define SKDEBUGCANVAS_ATTRIBUTE_DRAWDEPTHTRANS    "drawDepthTranslation"
-#define SKDEBUGCANVAS_ATTRIBUTE_COORDS            "coords"
-#define SKDEBUGCANVAS_ATTRIBUTE_EDGING            "edging"
-#define SKDEBUGCANVAS_ATTRIBUTE_HINTING           "hinting"
-#define SKDEBUGCANVAS_ATTRIBUTE_BOUNDS            "bounds"
-#define SKDEBUGCANVAS_ATTRIBUTE_PAINT             "paint"
-#define SKDEBUGCANVAS_ATTRIBUTE_OUTER             "outer"
-#define SKDEBUGCANVAS_ATTRIBUTE_INNER             "inner"
-#define SKDEBUGCANVAS_ATTRIBUTE_MODE              "mode"
-#define SKDEBUGCANVAS_ATTRIBUTE_POINTS            "points"
-#define SKDEBUGCANVAS_ATTRIBUTE_PATH              "path"
-#define SKDEBUGCANVAS_ATTRIBUTE_TEXT              "text"
-#define SKDEBUGCANVAS_ATTRIBUTE_COLOR             "color"
-#define SKDEBUGCANVAS_ATTRIBUTE_ALPHA             "alpha"
-#define SKDEBUGCANVAS_ATTRIBUTE_BLENDMODE         "blendMode"
-#define SKDEBUGCANVAS_ATTRIBUTE_STYLE             "style"
-#define SKDEBUGCANVAS_ATTRIBUTE_STROKEWIDTH       "strokeWidth"
-#define SKDEBUGCANVAS_ATTRIBUTE_STROKEMITER       "strokeMiter"
-#define SKDEBUGCANVAS_ATTRIBUTE_STROKEJOIN        "strokeJoin"
-#define SKDEBUGCANVAS_ATTRIBUTE_CAP               "cap"
-#define SKDEBUGCANVAS_ATTRIBUTE_ANTIALIAS         "antiAlias"
-#define SKDEBUGCANVAS_ATTRIBUTE_DITHER            "dither"
-#define SKDEBUGCANVAS_ATTRIBUTE_FAKEBOLDTEXT      "fakeBoldText"
-#define SKDEBUGCANVAS_ATTRIBUTE_LINEARTEXT        "linearText"
-#define SKDEBUGCANVAS_ATTRIBUTE_SUBPIXELTEXT      "subpixelText"
-#define SKDEBUGCANVAS_ATTRIBUTE_DEVKERNTEXT       "devKernText"
-#define SKDEBUGCANVAS_ATTRIBUTE_LCDRENDERTEXT     "lcdRenderText"
-#define SKDEBUGCANVAS_ATTRIBUTE_EMBEDDEDBITMAPTEXT "embeddedBitmapText"
-#define SKDEBUGCANVAS_ATTRIBUTE_AUTOHINTING       "forceAutoHinting"
-#define SKDEBUGCANVAS_ATTRIBUTE_REGION            "region"
-#define SKDEBUGCANVAS_ATTRIBUTE_REGIONOP          "op"
-#define SKDEBUGCANVAS_ATTRIBUTE_EDGESTYLE         "edgeStyle"
-#define SKDEBUGCANVAS_ATTRIBUTE_DEVICEREGION      "deviceRegion"
-#define SKDEBUGCANVAS_ATTRIBUTE_BLUR              "blur"
-#define SKDEBUGCANVAS_ATTRIBUTE_SIGMA             "sigma"
-#define SKDEBUGCANVAS_ATTRIBUTE_QUALITY           "quality"
-#define SKDEBUGCANVAS_ATTRIBUTE_TEXTSIZE          "textSize"
-#define SKDEBUGCANVAS_ATTRIBUTE_TEXTSCALEX        "textScaleX"
-#define SKDEBUGCANVAS_ATTRIBUTE_TEXTSKEWX         "textSkewX"
-#define SKDEBUGCANVAS_ATTRIBUTE_DASHING           "dashing"
-#define SKDEBUGCANVAS_ATTRIBUTE_INTERVALS         "intervals"
-#define SKDEBUGCANVAS_ATTRIBUTE_PHASE             "phase"
-#define SKDEBUGCANVAS_ATTRIBUTE_FILLTYPE          "fillType"
-#define SKDEBUGCANVAS_ATTRIBUTE_VERBS             "verbs"
-#define SKDEBUGCANVAS_ATTRIBUTE_NAME              "name"
-#define SKDEBUGCANVAS_ATTRIBUTE_DATA              "data"
-#define SKDEBUGCANVAS_ATTRIBUTE_VALUES            "values"
-#define SKDEBUGCANVAS_ATTRIBUTE_SHADER            "shader"
-#define SKDEBUGCANVAS_ATTRIBUTE_PATHEFFECT        "pathEffect"
-#define SKDEBUGCANVAS_ATTRIBUTE_MASKFILTER        "maskFilter"
-#define SKDEBUGCANVAS_ATTRIBUTE_XFERMODE          "xfermode"
-#define SKDEBUGCANVAS_ATTRIBUTE_LOOPER            "looper"
-#define SKDEBUGCANVAS_ATTRIBUTE_BACKDROP          "backdrop"
-#define SKDEBUGCANVAS_ATTRIBUTE_COLORFILTER       "colorfilter"
-#define SKDEBUGCANVAS_ATTRIBUTE_IMAGEFILTER       "imagefilter"
-#define SKDEBUGCANVAS_ATTRIBUTE_IMAGE             "image"
-#define SKDEBUGCANVAS_ATTRIBUTE_BITMAP            "bitmap"
-#define SKDEBUGCANVAS_ATTRIBUTE_SRC               "src"
-#define SKDEBUGCANVAS_ATTRIBUTE_DST               "dst"
-#define SKDEBUGCANVAS_ATTRIBUTE_CENTER            "center"
-#define SKDEBUGCANVAS_ATTRIBUTE_STRICT            "strict"
-#define SKDEBUGCANVAS_ATTRIBUTE_DESCRIPTION       "description"
-#define SKDEBUGCANVAS_ATTRIBUTE_X                 "x"
-#define SKDEBUGCANVAS_ATTRIBUTE_Y                 "y"
-#define SKDEBUGCANVAS_ATTRIBUTE_RUNS              "runs"
-#define SKDEBUGCANVAS_ATTRIBUTE_POSITIONS         "positions"
-#define SKDEBUGCANVAS_ATTRIBUTE_GLYPHS            "glyphs"
-#define SKDEBUGCANVAS_ATTRIBUTE_FONT              "font"
-#define SKDEBUGCANVAS_ATTRIBUTE_TYPEFACE          "typeface"
-#define SKDEBUGCANVAS_ATTRIBUTE_CUBICS            "cubics"
-#define SKDEBUGCANVAS_ATTRIBUTE_COLORS            "colors"
-#define SKDEBUGCANVAS_ATTRIBUTE_TEXTURECOORDS     "textureCoords"
-#define SKDEBUGCANVAS_ATTRIBUTE_FILTERQUALITY     "filterQuality"
-#define SKDEBUGCANVAS_ATTRIBUTE_STARTANGLE        "startAngle"
-#define SKDEBUGCANVAS_ATTRIBUTE_SWEEPANGLE        "sweepAngle"
-#define SKDEBUGCANVAS_ATTRIBUTE_USECENTER         "useCenter"
-#define SKDEBUGCANVAS_ATTRIBUTE_SHORTDESC         "shortDesc"
-#define SKDEBUGCANVAS_ATTRIBUTE_UNIQUE_ID         "uniqueID"
-#define SKDEBUGCANVAS_ATTRIBUTE_WIDTH             "width"
-#define SKDEBUGCANVAS_ATTRIBUTE_HEIGHT            "height"
-#define SKDEBUGCANVAS_ATTRIBUTE_ALPHA             "alpha"
-#define SKDEBUGCANVAS_ATTRIBUTE_LATTICE           "lattice"
-#define SKDEBUGCANVAS_ATTRIBUTE_LATTICEXCOUNT     "xCount"
-#define SKDEBUGCANVAS_ATTRIBUTE_LATTICEYCOUNT     "yCount"
-#define SKDEBUGCANVAS_ATTRIBUTE_LATTICEXDIVS      "xDivs"
-#define SKDEBUGCANVAS_ATTRIBUTE_LATTICEYDIVS      "yDivs"
-#define SKDEBUGCANVAS_ATTRIBUTE_LATTICEFLAGS      "flags"
-#define SKDEBUGCANVAS_ATTRIBUTE_ZPLANE            "zPlane"
-#define SKDEBUGCANVAS_ATTRIBUTE_LIGHTPOSITION     "lightPositions"
-#define SKDEBUGCANVAS_ATTRIBUTE_AMBIENTCOLOR      "ambientColor"
-#define SKDEBUGCANVAS_ATTRIBUTE_SPOTCOLOR         "spotColor"
-#define SKDEBUGCANVAS_ATTRIBUTE_LIGHTRADIUS       "lightRadius"
-
-#define SKDEBUGCANVAS_VERB_MOVE                   "move"
-#define SKDEBUGCANVAS_VERB_LINE                   "line"
-#define SKDEBUGCANVAS_VERB_QUAD                   "quad"
-#define SKDEBUGCANVAS_VERB_CUBIC                  "cubic"
-#define SKDEBUGCANVAS_VERB_CONIC                  "conic"
-#define SKDEBUGCANVAS_VERB_CLOSE                  "close"
-
-#define SKDEBUGCANVAS_STYLE_FILL                  "fill"
-#define SKDEBUGCANVAS_STYLE_STROKE                "stroke"
-#define SKDEBUGCANVAS_STYLE_STROKEANDFILL         "strokeAndFill"
-
-#define SKDEBUGCANVAS_POINTMODE_POINTS            "points"
-#define SKDEBUGCANVAS_POINTMODE_LINES             "lines"
-#define SKDEBUGCANVAS_POINTMODE_POLYGON           "polygon"
-
-#define SKDEBUGCANVAS_REGIONOP_DIFFERENCE         "difference"
-#define SKDEBUGCANVAS_REGIONOP_INTERSECT          "intersect"
-#define SKDEBUGCANVAS_REGIONOP_UNION              "union"
-#define SKDEBUGCANVAS_REGIONOP_XOR                "xor"
-#define SKDEBUGCANVAS_REGIONOP_REVERSE_DIFFERENCE "reverseDifference"
-#define SKDEBUGCANVAS_REGIONOP_REPLACE            "replace"
-
-#define SKDEBUGCANVAS_BLURSTYLE_NORMAL            "normal"
-#define SKDEBUGCANVAS_BLURSTYLE_SOLID             "solid"
-#define SKDEBUGCANVAS_BLURSTYLE_OUTER             "outer"
-#define SKDEBUGCANVAS_BLURSTYLE_INNER             "inner"
-
-#define SKDEBUGCANVAS_BLURQUALITY_LOW             "low"
-#define SKDEBUGCANVAS_BLURQUALITY_HIGH            "high"
-
-#define SKDEBUGCANVAS_FILLTYPE_WINDING            "winding"
-#define SKDEBUGCANVAS_FILLTYPE_EVENODD            "evenOdd"
-#define SKDEBUGCANVAS_FILLTYPE_INVERSEWINDING     "inverseWinding"
-#define SKDEBUGCANVAS_FILLTYPE_INVERSEEVENODD     "inverseEvenOdd"
-
-#define SKDEBUGCANVAS_CAP_BUTT                    "butt"
-#define SKDEBUGCANVAS_CAP_ROUND                   "round"
-#define SKDEBUGCANVAS_CAP_SQUARE                  "square"
-
-#define SKDEBUGCANVAS_MITER_JOIN                  "miter"
-#define SKDEBUGCANVAS_ROUND_JOIN                  "round"
-#define SKDEBUGCANVAS_BEVEL_JOIN                  "bevel"
-
-#define SKDEBUGCANVAS_COLORTYPE_ARGB4444          "ARGB4444"
-#define SKDEBUGCANVAS_COLORTYPE_RGBA8888          "RGBA8888"
-#define SKDEBUGCANVAS_COLORTYPE_BGRA8888          "BGRA8888"
-#define SKDEBUGCANVAS_COLORTYPE_565               "565"
-#define SKDEBUGCANVAS_COLORTYPE_GRAY8             "Gray8"
-#define SKDEBUGCANVAS_COLORTYPE_INDEX8            "Index8"
-#define SKDEBUGCANVAS_COLORTYPE_ALPHA8            "Alpha8"
-
-#define SKDEBUGCANVAS_ALPHATYPE_OPAQUE            "opaque"
-#define SKDEBUGCANVAS_ALPHATYPE_PREMUL            "premul"
-#define SKDEBUGCANVAS_ALPHATYPE_UNPREMUL          "unpremul"
-#define SKDEBUGCANVAS_ALPHATYPE_UNKNOWN           "unknown"
-
-#define SKDEBUGCANVAS_FILTERQUALITY_NONE          "none"
-#define SKDEBUGCANVAS_FILTERQUALITY_LOW           "low"
-#define SKDEBUGCANVAS_FILTERQUALITY_MEDIUM        "medium"
-#define SKDEBUGCANVAS_FILTERQUALITY_HIGH          "high"
-
-#define SKDEBUGCANVAS_HINTING_NONE                "none"
-#define SKDEBUGCANVAS_HINTING_SLIGHT              "slight"
-#define SKDEBUGCANVAS_HINTING_NORMAL              "normal"
-#define SKDEBUGCANVAS_HINTING_FULL                "full"
-
-#define SKDEBUGCANVAS_EDGING_ALIAS                "alias"
-#define SKDEBUGCANVAS_EDGING_ANTIALIAS            "antialias"
-#define SKDEBUGCANVAS_EDGING_SUBPIXELANTIALIAS    "subpixelantialias"
-
-#define SKDEBUGCANVAS_SHADOWFLAG_TRANSPARENT_OCC  "transparentOccluder"
-#define SKDEBUGCANVAS_SHADOWFLAG_GEOMETRIC_ONLY   "geometricOnly"
-
-static SkString* str_append(SkString* str, const SkRect& r) {
-    str->appendf(" [%g %g %g %g]", r.left(), r.top(), r.right(), r.bottom());
-    return str;
-}
-
-SkDrawCommand::SkDrawCommand(OpType type)
-    : fOpType(type)
-    , fVisible(true) {
-}
-
-const char* SkDrawCommand::GetCommandString(OpType type) {
-    switch (type) {
-        case kBeginDrawPicture_OpType: return "BeginDrawPicture";
-        case kClear_OpType: return "DrawClear";
-        case kClipPath_OpType: return "ClipPath";
-        case kClipRegion_OpType: return "ClipRegion";
-        case kClipRect_OpType: return "ClipRect";
-        case kClipRRect_OpType: return "ClipRRect";
-        case kConcat_OpType: return "Concat";
-        case kDrawAnnotation_OpType: return "DrawAnnotation";
-        case kDrawBitmap_OpType: return "DrawBitmap";
-        case kDrawBitmapLattice_OpType: return "DrawBitmapLattice";
-        case kDrawBitmapNine_OpType: return "DrawBitmapNine";
-        case kDrawBitmapRect_OpType: return "DrawBitmapRect";
-        case kDrawDRRect_OpType: return "DrawDRRect";
-        case kDrawImage_OpType: return "DrawImage";
-        case kDrawImageLattice_OpType: return "DrawImageLattice";
-        case kDrawImageNine_OpType: return "DrawImageNine";
-        case kDrawImageRect_OpType: return "DrawImageRect";
-        case kDrawImageSet_OpType: return "DrawImageSet";
-        case kDrawOval_OpType: return "DrawOval";
-        case kDrawPaint_OpType: return "DrawPaint";
-        case kDrawPatch_OpType: return "DrawPatch";
-        case kDrawPath_OpType: return "DrawPath";
-        case kDrawArc_OpType: return "DrawArc";
-        case kDrawPoints_OpType: return "DrawPoints";
-        case kDrawRect_OpType: return "DrawRect";
-        case kDrawRRect_OpType: return "DrawRRect";
-        case kDrawRegion_OpType: return "DrawRegion";
-        case kDrawShadow_OpType: return "DrawShadow";
-        case kDrawTextBlob_OpType: return "DrawTextBlob";
-        case kDrawVertices_OpType: return "DrawVertices";
-        case kDrawAtlas_OpType: return "DrawAtlas";
-        case kDrawDrawable_OpType: return "DrawDrawable";
-        case kEndDrawPicture_OpType: return "EndDrawPicture";
-        case kRestore_OpType: return "Restore";
-        case kSave_OpType: return "Save";
-        case kSaveLayer_OpType: return "SaveLayer";
-        case kSetMatrix_OpType: return "SetMatrix";
-        default:
-            SkDebugf("OpType error 0x%08x\n", type);
-            SkASSERT(0);
-            break;
-    }
-    SkDEBUGFAIL("DrawType UNUSED\n");
-    return nullptr;
-}
-
-void SkDrawCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_COMMAND, this->GetCommandString(fOpType));
-    writer.appendBool(SKDEBUGCANVAS_ATTRIBUTE_VISIBLE, this->isVisible());
-}
-
-namespace {
-
-void xlate_and_scale_to_bounds(SkCanvas* canvas, const SkRect& bounds) {
-    const SkISize& size = canvas->getBaseLayerSize();
-
-    static const SkScalar kInsetFrac = 0.9f; // Leave a border around object
-
-    canvas->translate(size.fWidth/2.0f, size.fHeight/2.0f);
-    if (bounds.width() > bounds.height()) {
-        canvas->scale(SkDoubleToScalar((kInsetFrac*size.fWidth)/bounds.width()),
-                      SkDoubleToScalar((kInsetFrac*size.fHeight)/bounds.width()));
-    } else {
-        canvas->scale(SkDoubleToScalar((kInsetFrac*size.fWidth)/bounds.height()),
-                      SkDoubleToScalar((kInsetFrac*size.fHeight)/bounds.height()));
-    }
-    canvas->translate(-bounds.centerX(), -bounds.centerY());
-}
-
-
-void render_path(SkCanvas* canvas, const SkPath& path) {
-    canvas->clear(0xFFFFFFFF);
-
-    const SkRect& bounds = path.getBounds();
-    if (bounds.isEmpty()) {
-        return;
-    }
-
-    SkAutoCanvasRestore acr(canvas, true);
-    xlate_and_scale_to_bounds(canvas, bounds);
-
-    SkPaint p;
-    p.setColor(SK_ColorBLACK);
-    p.setStyle(SkPaint::kStroke_Style);
-
-    canvas->drawPath(path, p);
-}
-
-void render_region(SkCanvas* canvas, const SkRegion& region) {
-    canvas->clear(0xFFFFFFFF);
-
-    const SkIRect& bounds = region.getBounds();
-    if (bounds.isEmpty()) {
-        return;
-    }
-
-    SkAutoCanvasRestore acr(canvas, true);
-    xlate_and_scale_to_bounds(canvas, SkRect::Make(bounds));
-
-    SkPaint p;
-    p.setColor(SK_ColorBLACK);
-    p.setStyle(SkPaint::kStroke_Style);
-
-    canvas->drawRegion(region, p);
-}
-
-void render_bitmap(SkCanvas* canvas, const SkBitmap& input, const SkRect* srcRect = nullptr) {
-    const SkISize& size = canvas->getBaseLayerSize();
-
-    SkScalar xScale = SkIntToScalar(size.fWidth-2) / input.width();
-    SkScalar yScale = SkIntToScalar(size.fHeight-2) / input.height();
-
-    if (input.width() > input.height()) {
-        yScale *= input.height() / (float) input.width();
-    } else {
-        xScale *= input.width() / (float) input.height();
-    }
-
-    SkRect dst = SkRect::MakeXYWH(SK_Scalar1, SK_Scalar1,
-                                  xScale * input.width(),
-                                  yScale * input.height());
-
-    static const int kNumBlocks = 8;
-
-    canvas->clear(0xFFFFFFFF);
-    SkISize block = {
-        canvas->imageInfo().width()/kNumBlocks,
-        canvas->imageInfo().height()/kNumBlocks
-    };
-    for (int y = 0; y < kNumBlocks; ++y) {
-        for (int x = 0; x < kNumBlocks; ++x) {
-            SkPaint paint;
-            paint.setColor((x+y)%2 ? SK_ColorLTGRAY : SK_ColorDKGRAY);
-            SkRect r = SkRect::MakeXYWH(SkIntToScalar(x*block.width()),
-                                        SkIntToScalar(y*block.height()),
-                                        SkIntToScalar(block.width()),
-                                        SkIntToScalar(block.height()));
-            canvas->drawRect(r, paint);
-        }
-    }
-
-    canvas->drawBitmapRect(input, dst, nullptr);
-
-    if (srcRect) {
-        SkRect r = SkRect::MakeLTRB(srcRect->fLeft * xScale + SK_Scalar1,
-                                    srcRect->fTop * yScale + SK_Scalar1,
-                                    srcRect->fRight * xScale + SK_Scalar1,
-                                    srcRect->fBottom * yScale + SK_Scalar1);
-        SkPaint p;
-        p.setColor(SK_ColorRED);
-        p.setStyle(SkPaint::kStroke_Style);
-
-        canvas->drawRect(r, p);
-    }
-}
-
-void render_rrect(SkCanvas* canvas, const SkRRect& rrect) {
-    canvas->clear(0xFFFFFFFF);
-    canvas->save();
-
-    const SkRect& bounds = rrect.getBounds();
-
-    xlate_and_scale_to_bounds(canvas, bounds);
-
-    SkPaint p;
-    p.setColor(SK_ColorBLACK);
-    p.setStyle(SkPaint::kStroke_Style);
-
-    canvas->drawRRect(rrect, p);
-    canvas->restore();
-}
-
-void render_drrect(SkCanvas* canvas, const SkRRect& outer, const SkRRect& inner) {
-    canvas->clear(0xFFFFFFFF);
-    canvas->save();
-
-    const SkRect& bounds = outer.getBounds();
-
-    xlate_and_scale_to_bounds(canvas, bounds);
-
-    SkPaint p;
-    p.setColor(SK_ColorBLACK);
-    p.setStyle(SkPaint::kStroke_Style);
-
-    canvas->drawDRRect(outer, inner, p);
-    canvas->restore();
-}
-
-void render_shadow(SkCanvas* canvas, const SkPath& path, SkDrawShadowRec rec) {
-    canvas->clear(0xFFFFFFFF);
-
-    const SkRect& bounds = path.getBounds();
-    if (bounds.isEmpty()) {
-        return;
-    }
-
-    SkAutoCanvasRestore acr(canvas, true);
-    xlate_and_scale_to_bounds(canvas, bounds);
-
-    rec.fAmbientColor = SK_ColorBLACK;
-    rec.fSpotColor = SK_ColorBLACK;
-    canvas->private_draw_shadow_rec(path, rec);
-}
-
-static const char* const gBlendModeMap[] = {
-    "clear",
-    "src",
-    "dst",
-    "srcOver",
-    "dstOver",
-    "srcIn",
-    "dstIn",
-    "srcOut",
-    "dstOut",
-    "srcATop",
-    "dstATop",
-    "xor",
-    "plus",
-    "modulate",
-
-    "screen",
-
-    "overlay",
-    "darken",
-    "lighten",
-    "colorDodge",
-    "colorBurn",
-    "hardLight",
-    "softLight",
-    "difference",
-    "exclusion",
-    "multiply",
-
-    "hue",
-    "saturation",
-    "color",
-    "luminosity",
-};
-
-static_assert(SK_ARRAY_COUNT(gBlendModeMap) == static_cast<size_t>(SkBlendMode::kLastMode) + 1,
-              "blendMode mismatch");
-static_assert(SK_ARRAY_COUNT(gBlendModeMap) == static_cast<size_t>(SkBlendMode::kLuminosity) + 1,
-              "blendMode mismatch");
-
-void apply_paint_blend_mode(const SkPaint& paint, SkJSONWriter& writer) {
-    const auto mode = paint.getBlendMode();
-    if (mode != SkBlendMode::kSrcOver) {
-        SkASSERT(static_cast<size_t>(mode) < SK_ARRAY_COUNT(gBlendModeMap));
-        writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_BLENDMODE,
-                            gBlendModeMap[static_cast<size_t>(mode)]);
-    }
-}
-
-};
-
-void SkDrawCommand::MakeJsonColor(SkJSONWriter& writer, const SkColor color) {
-    writer.beginArray(nullptr, false);
-    writer.appendS32(SkColorGetA(color));
-    writer.appendS32(SkColorGetR(color));
-    writer.appendS32(SkColorGetG(color));
-    writer.appendS32(SkColorGetB(color));
-    writer.endArray();
-}
-
-void SkDrawCommand::MakeJsonColor4f(SkJSONWriter& writer, const SkColor4f& color) {
-    writer.beginArray(nullptr, false);
-    writer.appendFloat(color.fA);
-    writer.appendFloat(color.fR);
-    writer.appendFloat(color.fG);
-    writer.appendFloat(color.fB);
-    writer.endArray();
-}
-
-void SkDrawCommand::MakeJsonPoint(SkJSONWriter& writer, const SkPoint& point) {
-    writer.beginArray(nullptr, false);
-    writer.appendFloat(point.x());
-    writer.appendFloat(point.y());
-    writer.endArray();
-}
-
-void SkDrawCommand::MakeJsonPoint(SkJSONWriter& writer, SkScalar x, SkScalar y) {
-    writer.beginArray(nullptr, false);
-    writer.appendFloat(x);
-    writer.appendFloat(y);
-    writer.endArray();
-}
-
-void SkDrawCommand::MakeJsonPoint3(SkJSONWriter& writer, const SkPoint3& point) {
-    writer.beginArray(nullptr, false);
-    writer.appendFloat(point.x());
-    writer.appendFloat(point.y());
-    writer.appendFloat(point.z());
-    writer.endArray();
-}
-
-void SkDrawCommand::MakeJsonRect(SkJSONWriter& writer, const SkRect& rect) {
-    writer.beginArray(nullptr, false);
-    writer.appendFloat(rect.left());
-    writer.appendFloat(rect.top());
-    writer.appendFloat(rect.right());
-    writer.appendFloat(rect.bottom());
-    writer.endArray();
-}
-
-void SkDrawCommand::MakeJsonIRect(SkJSONWriter& writer, const SkIRect& rect) {
-    writer.beginArray(nullptr, false);
-    writer.appendS32(rect.left());
-    writer.appendS32(rect.top());
-    writer.appendS32(rect.right());
-    writer.appendS32(rect.bottom());
-    writer.endArray();
-}
-
-static void make_json_rrect(SkJSONWriter& writer, const SkRRect& rrect) {
-    writer.beginArray(nullptr, false);
-    SkDrawCommand::MakeJsonRect(writer, rrect.rect());
-    SkDrawCommand::MakeJsonPoint(writer, rrect.radii(SkRRect::kUpperLeft_Corner));
-    SkDrawCommand::MakeJsonPoint(writer, rrect.radii(SkRRect::kUpperRight_Corner));
-    SkDrawCommand::MakeJsonPoint(writer, rrect.radii(SkRRect::kLowerRight_Corner));
-    SkDrawCommand::MakeJsonPoint(writer, rrect.radii(SkRRect::kLowerLeft_Corner));
-    writer.endArray();
-}
-
-void SkDrawCommand::MakeJsonMatrix(SkJSONWriter& writer, const SkMatrix& matrix) {
-    writer.beginArray();
-    for (int r = 0; r < 3; ++r) {
-        writer.beginArray(nullptr, false);
-        for (int c = 0; c < 3; ++c) {
-            writer.appendFloat(matrix[r * 3 + c]);
-        }
-        writer.endArray();
-    }
-    writer.endArray();
-}
-
-void SkDrawCommand::MakeJsonPath(SkJSONWriter& writer, const SkPath& path) {
-    writer.beginObject();
-    switch (path.getFillType()) {
-        case SkPath::kWinding_FillType:
-            writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_FILLTYPE, SKDEBUGCANVAS_FILLTYPE_WINDING);
-            break;
-        case SkPath::kEvenOdd_FillType:
-            writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_FILLTYPE, SKDEBUGCANVAS_FILLTYPE_EVENODD);
-            break;
-        case SkPath::kInverseWinding_FillType:
-            writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_FILLTYPE, SKDEBUGCANVAS_FILLTYPE_INVERSEWINDING);
-            break;
-        case SkPath::kInverseEvenOdd_FillType:
-            writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_FILLTYPE, SKDEBUGCANVAS_FILLTYPE_INVERSEEVENODD);
-            break;
-    }
-    writer.beginArray(SKDEBUGCANVAS_ATTRIBUTE_VERBS);
-    SkPath::Iter iter(path, false);
-    SkPoint pts[4];
-    SkPath::Verb verb;
-    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
-        if (verb == SkPath::kClose_Verb) {
-            writer.appendString(SKDEBUGCANVAS_VERB_CLOSE);
-            continue;
-        }
-        writer.beginObject(); // verb
-        switch (verb) {
-            case SkPath::kLine_Verb: {
-                writer.appendName(SKDEBUGCANVAS_VERB_LINE);
-                MakeJsonPoint(writer, pts[1]);
-                break;
-            }
-            case SkPath::kQuad_Verb: {
-                writer.beginArray(SKDEBUGCANVAS_VERB_QUAD);
-                MakeJsonPoint(writer, pts[1]);
-                MakeJsonPoint(writer, pts[2]);
-                writer.endArray(); // quad coords
-                break;
-            }
-            case SkPath::kCubic_Verb: {
-                writer.beginArray(SKDEBUGCANVAS_VERB_CUBIC);
-                MakeJsonPoint(writer, pts[1]);
-                MakeJsonPoint(writer, pts[2]);
-                MakeJsonPoint(writer, pts[3]);
-                writer.endArray(); // cubic coords
-                break;
-            }
-            case SkPath::kConic_Verb: {
-                writer.beginArray(SKDEBUGCANVAS_VERB_CONIC);
-                MakeJsonPoint(writer, pts[1]);
-                MakeJsonPoint(writer, pts[2]);
-                writer.appendFloat(iter.conicWeight());
-                writer.endArray(); // conic coords
-                break;
-            }
-            case SkPath::kMove_Verb: {
-                writer.appendName(SKDEBUGCANVAS_VERB_MOVE);
-                MakeJsonPoint(writer, pts[0]);
-                break;
-            }
-            case SkPath::kClose_Verb:
-            case SkPath::kDone_Verb:
-                // Unreachable
-                break;
-        }
-        writer.endObject(); // verb
-    }
-    writer.endArray(); // verbs
-    writer.endObject(); // path
-}
-
-void SkDrawCommand::MakeJsonRegion(SkJSONWriter& writer, const SkRegion& region) {
-    // TODO: Actually serialize the rectangles, rather than just devolving to path
-    SkPath path;
-    region.getBoundaryPath(&path);
-    MakeJsonPath(writer, path);
-}
-
-static const char* regionop_name(SkClipOp op) {
-    switch (op) {
-        case kDifference_SkClipOp:
-            return SKDEBUGCANVAS_REGIONOP_DIFFERENCE;
-        case kIntersect_SkClipOp:
-            return SKDEBUGCANVAS_REGIONOP_INTERSECT;
-        case kUnion_SkClipOp:
-            return SKDEBUGCANVAS_REGIONOP_UNION;
-        case kXOR_SkClipOp:
-            return SKDEBUGCANVAS_REGIONOP_XOR;
-        case kReverseDifference_SkClipOp:
-            return SKDEBUGCANVAS_REGIONOP_REVERSE_DIFFERENCE;
-        case kReplace_SkClipOp:
-            return SKDEBUGCANVAS_REGIONOP_REPLACE;
-        default:
-            SkASSERT(false);
-            return "<invalid region op>";
-    }
-}
-
-static const char* pointmode_name(SkCanvas::PointMode mode) {
-    switch (mode) {
-        case SkCanvas::kPoints_PointMode:
-            return SKDEBUGCANVAS_POINTMODE_POINTS;
-        case SkCanvas::kLines_PointMode:
-            return SKDEBUGCANVAS_POINTMODE_LINES;
-        case SkCanvas::kPolygon_PointMode:
-            return SKDEBUGCANVAS_POINTMODE_POLYGON;
-        default:
-            SkASSERT(false);
-            return "<invalid point mode>";
-    }
-}
-
-static void store_scalar(SkJSONWriter& writer, const char* key, SkScalar value,
-                         SkScalar defaultValue) {
-    if (value != defaultValue) {
-        writer.appendFloat(key, value);
-    }
-}
-
-static void store_bool(SkJSONWriter& writer,const char* key, bool value, bool defaultValue) {
-    if (value != defaultValue) {
-        writer.appendBool(key, value);
-    }
-}
-
-static SkString encode_data(const void* bytes, size_t count, const char* contentType,
-                            UrlDataManager& urlDataManager) {
-    sk_sp<SkData> data(SkData::MakeWithCopy(bytes, count));
-    return urlDataManager.addData(data.get(), contentType);
-}
-
-void SkDrawCommand::flatten(const SkFlattenable* flattenable, SkJSONWriter& writer,
-                            UrlDataManager& urlDataManager) {
-    SkBinaryWriteBuffer buffer;
-    flattenable->flatten(buffer);
-    void* data = sk_malloc_throw(buffer.bytesWritten());
-    buffer.writeToMemory(data);
-    SkString url = encode_data(data, buffer.bytesWritten(), "application/octet-stream",
-                               urlDataManager);
-    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_NAME, flattenable->getTypeName());
-    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_DATA, url.c_str());
-
-    writer.beginObject(SKDEBUGCANVAS_ATTRIBUTE_VALUES);
-    SkJsonWriteBuffer jsonBuffer(&writer, &urlDataManager);
-    flattenable->flatten(jsonBuffer);
-    writer.endObject(); // values
-
-    sk_free(data);
-}
-
-void SkDrawCommand::WritePNG(SkBitmap bitmap, SkWStream& out) {
-    SkPixmap pm;
-    SkAssertResult(bitmap.peekPixels(&pm));
-
-    SkPngEncoder::Options options;
-    options.fZLibLevel = 1;
-    options.fFilterFlags = SkPngEncoder::FilterFlag::kNone;
-    SkPngEncoder::Encode(&out, pm, options);
-}
-
-bool SkDrawCommand::flatten(const SkImage& image, SkJSONWriter& writer,
-                            UrlDataManager& urlDataManager) {
-    size_t rowBytes = 4 * image.width();
-    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)) {
-        SkDebugf("readPixels failed\n");
-        return false;
-    }
-
-    SkBitmap bm;
-    bm.installPixels(dstInfo, buffer.get(), rowBytes);
-
-    SkDynamicMemoryWStream out;
-    SkDrawCommand::WritePNG(bm, out);
-    sk_sp<SkData> encoded = out.detachAsData();
-    SkString url = encode_data(encoded->data(), encoded->size(), "image/png", urlDataManager);
-    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_DATA, url.c_str());
-    return true;
-}
-
-static const char* color_type_name(SkColorType colorType) {
-    switch (colorType) {
-        case kARGB_4444_SkColorType:
-            return SKDEBUGCANVAS_COLORTYPE_ARGB4444;
-        case kRGBA_8888_SkColorType:
-            return SKDEBUGCANVAS_COLORTYPE_RGBA8888;
-        case kBGRA_8888_SkColorType:
-            return SKDEBUGCANVAS_COLORTYPE_BGRA8888;
-        case kRGB_565_SkColorType:
-            return SKDEBUGCANVAS_COLORTYPE_565;
-        case kGray_8_SkColorType:
-            return SKDEBUGCANVAS_COLORTYPE_GRAY8;
-        case kAlpha_8_SkColorType:
-            return SKDEBUGCANVAS_COLORTYPE_ALPHA8;
-        default:
-            SkASSERT(false);
-            return SKDEBUGCANVAS_COLORTYPE_RGBA8888;
-    }
-}
-
-static const char* alpha_type_name(SkAlphaType alphaType) {
-    switch (alphaType) {
-        case kOpaque_SkAlphaType:
-            return SKDEBUGCANVAS_ALPHATYPE_OPAQUE;
-        case kPremul_SkAlphaType:
-            return SKDEBUGCANVAS_ALPHATYPE_PREMUL;
-        case kUnpremul_SkAlphaType:
-            return SKDEBUGCANVAS_ALPHATYPE_UNPREMUL;
-        default:
-            SkASSERT(false);
-            return SKDEBUGCANVAS_ALPHATYPE_OPAQUE;
-    }
-}
-
-bool SkDrawCommand::flatten(const SkBitmap& bitmap, SkJSONWriter& writer,
-                            UrlDataManager& urlDataManager) {
-    sk_sp<SkImage> image(SkImage::MakeFromBitmap(bitmap));
-    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_COLOR, color_type_name(bitmap.colorType()));
-    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_ALPHA, alpha_type_name(bitmap.alphaType()));
-    bool success = flatten(*image, writer, urlDataManager);
-    return success;
-}
-
-static void apply_font_hinting(const SkFont& font, SkJSONWriter& writer) {
-    SkFontHinting hinting = font.getHinting();
-    if (hinting != SkPaintDefaults_Hinting) {
-        switch (hinting) {
-            case kNo_SkFontHinting:
-                writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_HINTING, SKDEBUGCANVAS_HINTING_NONE);
-                break;
-            case kSlight_SkFontHinting:
-                writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_HINTING, SKDEBUGCANVAS_HINTING_SLIGHT);
-                break;
-            case kNormal_SkFontHinting:
-                writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_HINTING, SKDEBUGCANVAS_HINTING_NORMAL);
-                break;
-            case kFull_SkFontHinting:
-                writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_HINTING, SKDEBUGCANVAS_HINTING_FULL);
-                break;
-        }
-    }
-}
-
-static void apply_font_edging(const SkFont& font, SkJSONWriter& writer) {
-    switch (font.getEdging()) {
-        case SkFont::Edging::kAlias:
-            writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_EDGING, SKDEBUGCANVAS_EDGING_ALIAS);
-            break;
-        case SkFont::Edging::kAntiAlias:
-            writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_EDGING, SKDEBUGCANVAS_EDGING_ANTIALIAS);
-            break;
-        case SkFont::Edging::kSubpixelAntiAlias:
-            writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_EDGING, SKDEBUGCANVAS_EDGING_SUBPIXELANTIALIAS);
-            break;
-    }
-}
-
-static void apply_paint_color(const SkPaint& paint, SkJSONWriter& writer) {
-    SkColor color = paint.getColor();
-    if (color != SK_ColorBLACK) {
-        writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_COLOR);
-        SkDrawCommand::MakeJsonColor(writer, color);
-    }
-}
-
-static void apply_paint_style(const SkPaint& paint, SkJSONWriter& writer) {
-    SkPaint::Style style = paint.getStyle();
-    if (style != SkPaint::kFill_Style) {
-        switch (style) {
-            case SkPaint::kStroke_Style: {
-                writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_STYLE, SKDEBUGCANVAS_STYLE_STROKE);
-                break;
-            }
-            case SkPaint::kStrokeAndFill_Style: {
-                writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_STYLE, SKDEBUGCANVAS_STYLE_STROKEANDFILL);
-                break;
-            }
-            default: SkASSERT(false);
-        }
-    }
-}
-
-static void apply_paint_cap(const SkPaint& paint, SkJSONWriter& writer) {
-    SkPaint::Cap cap = paint.getStrokeCap();
-    if (cap != SkPaint::kDefault_Cap) {
-        switch (cap) {
-            case SkPaint::kButt_Cap:
-                writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_CAP, SKDEBUGCANVAS_CAP_BUTT);
-                break;
-            case SkPaint::kRound_Cap:
-                writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_CAP, SKDEBUGCANVAS_CAP_ROUND);
-                break;
-            case SkPaint::kSquare_Cap:
-                writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_CAP, SKDEBUGCANVAS_CAP_SQUARE);
-                break;
-            default: SkASSERT(false);
-        }
-    }
-}
-
-static void apply_paint_join(const SkPaint& paint, SkJSONWriter& writer) {
-    SkPaint::Join join = paint.getStrokeJoin();
-    if (join != SkPaint::kDefault_Join) {
-        switch (join) {
-            case SkPaint::kMiter_Join:
-                writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_STROKEJOIN, SKDEBUGCANVAS_MITER_JOIN);
-                break;
-            case SkPaint::kRound_Join:
-                writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_STROKEJOIN, SKDEBUGCANVAS_ROUND_JOIN);
-                break;
-            case SkPaint::kBevel_Join:
-                writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_STROKEJOIN, SKDEBUGCANVAS_BEVEL_JOIN);
-                break;
-            default: SkASSERT(false);
-        }
-    }
-}
-
-static void apply_paint_filterquality(const SkPaint& paint, SkJSONWriter& writer) {
-    SkFilterQuality quality = paint.getFilterQuality();
-    switch (quality) {
-        case kNone_SkFilterQuality:
-            break;
-        case kLow_SkFilterQuality:
-            writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_FILTERQUALITY,
-                                SKDEBUGCANVAS_FILTERQUALITY_LOW);
-            break;
-        case kMedium_SkFilterQuality:
-            writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_FILTERQUALITY,
-                                SKDEBUGCANVAS_FILTERQUALITY_MEDIUM);
-            break;
-        case kHigh_SkFilterQuality:
-            writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_FILTERQUALITY,
-                                SKDEBUGCANVAS_FILTERQUALITY_HIGH);
-            break;
-    }
-}
-
-static void apply_paint_maskfilter(const SkPaint& paint, SkJSONWriter& writer,
-                                   UrlDataManager& urlDataManager) {
-    SkMaskFilter* maskFilter = paint.getMaskFilter();
-    if (maskFilter != nullptr) {
-        SkMaskFilterBase::BlurRec blurRec;
-        if (as_MFB(maskFilter)->asABlur(&blurRec)) {
-            writer.beginObject(SKDEBUGCANVAS_ATTRIBUTE_BLUR);
-            writer.appendFloat(SKDEBUGCANVAS_ATTRIBUTE_SIGMA, blurRec.fSigma);
-            switch (blurRec.fStyle) {
-                case SkBlurStyle::kNormal_SkBlurStyle:
-                    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_STYLE,
-                                        SKDEBUGCANVAS_BLURSTYLE_NORMAL);
-                    break;
-                case SkBlurStyle::kSolid_SkBlurStyle:
-                    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_STYLE,
-                                        SKDEBUGCANVAS_BLURSTYLE_SOLID);
-                    break;
-                case SkBlurStyle::kOuter_SkBlurStyle:
-                    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_STYLE,
-                                        SKDEBUGCANVAS_BLURSTYLE_OUTER);
-                    break;
-                case SkBlurStyle::kInner_SkBlurStyle:
-                    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_STYLE,
-                                        SKDEBUGCANVAS_BLURSTYLE_INNER);
-                    break;
-                default:
-                    SkASSERT(false);
-            }
-            writer.endObject(); // blur
-        } else {
-            writer.beginObject(SKDEBUGCANVAS_ATTRIBUTE_MASKFILTER);
-            SkDrawCommand::flatten(maskFilter, writer, urlDataManager);
-            writer.endObject(); // maskFilter
-        }
-    }
-}
-
-static void apply_paint_patheffect(const SkPaint& paint, SkJSONWriter& writer,
-                                   UrlDataManager& urlDataManager) {
-    SkPathEffect* pathEffect = paint.getPathEffect();
-    if (pathEffect != nullptr) {
-        SkPathEffect::DashInfo dashInfo;
-        SkPathEffect::DashType dashType = pathEffect->asADash(&dashInfo);
-        if (dashType == SkPathEffect::kDash_DashType) {
-            dashInfo.fIntervals = (SkScalar*) sk_malloc_throw(dashInfo.fCount * sizeof(SkScalar));
-            pathEffect->asADash(&dashInfo);
-            writer.beginObject(SKDEBUGCANVAS_ATTRIBUTE_DASHING);
-            writer.beginArray(SKDEBUGCANVAS_ATTRIBUTE_INTERVALS, false);
-            for (int32_t i = 0; i < dashInfo.fCount; i++) {
-                writer.appendFloat(dashInfo.fIntervals[i]);
-            }
-            writer.endArray(); // intervals
-            sk_free(dashInfo.fIntervals);
-            writer.appendFloat(SKDEBUGCANVAS_ATTRIBUTE_PHASE, dashInfo.fPhase);
-            writer.endObject(); // dashing
-        } else {
-            writer.beginObject(SKDEBUGCANVAS_ATTRIBUTE_PATHEFFECT);
-            SkDrawCommand::flatten(pathEffect, writer, urlDataManager);
-            writer.endObject(); // pathEffect
-        }
-    }
-}
-
-static void apply_font_typeface(const SkFont& font, SkJSONWriter& writer,
-                                 UrlDataManager& urlDataManager) {
-    SkTypeface* typeface = font.getTypefaceOrDefault();
-    if (typeface != nullptr) {
-        writer.beginObject(SKDEBUGCANVAS_ATTRIBUTE_TYPEFACE);
-        SkDynamicMemoryWStream buffer;
-        typeface->serialize(&buffer);
-        void* data = sk_malloc_throw(buffer.bytesWritten());
-        buffer.copyTo(data);
-        SkString url = encode_data(data, buffer.bytesWritten(), "application/octet-stream",
-                                   urlDataManager);
-        writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_DATA, url.c_str());
-        sk_free(data);
-        writer.endObject();
-    }
-}
-
-static void apply_flattenable(const char* key, SkFlattenable* flattenable, SkJSONWriter& writer,
-                              UrlDataManager& urlDataManager) {
-    if (flattenable != nullptr) {
-        writer.beginObject(key);
-        SkDrawCommand::flatten(flattenable, writer, urlDataManager);
-        writer.endObject();
-    }
-}
-
-void SkDrawCommand::MakeJsonPaint(SkJSONWriter& writer, const SkPaint& paint,
-                                  UrlDataManager& urlDataManager) {
-    writer.beginObject();
-    store_scalar(writer, SKDEBUGCANVAS_ATTRIBUTE_STROKEWIDTH, paint.getStrokeWidth(), 0.0f);
-    store_scalar(writer, SKDEBUGCANVAS_ATTRIBUTE_STROKEMITER, paint.getStrokeMiter(),
-                 SkPaintDefaults_MiterLimit);
-    store_bool(writer, SKDEBUGCANVAS_ATTRIBUTE_ANTIALIAS, paint.isAntiAlias(), false);
-    store_bool(writer, SKDEBUGCANVAS_ATTRIBUTE_DITHER, paint.isDither(), false);
-
-    apply_paint_color(paint, writer);
-    apply_paint_style(paint, writer);
-    apply_paint_blend_mode(paint, writer);
-    apply_paint_cap(paint, writer);
-    apply_paint_join(paint, writer);
-    apply_paint_filterquality(paint, writer);
-    apply_paint_patheffect(paint, writer, urlDataManager);
-    apply_paint_maskfilter(paint, writer, urlDataManager);
-    apply_flattenable(SKDEBUGCANVAS_ATTRIBUTE_SHADER, paint.getShader(), writer, urlDataManager);
-    apply_flattenable(SKDEBUGCANVAS_ATTRIBUTE_LOOPER, paint.getLooper(), writer, urlDataManager);
-    apply_flattenable(SKDEBUGCANVAS_ATTRIBUTE_IMAGEFILTER, paint.getImageFilter(), writer,
-                      urlDataManager);
-    apply_flattenable(SKDEBUGCANVAS_ATTRIBUTE_COLORFILTER, paint.getColorFilter(), writer,
-                      urlDataManager);
-    writer.endObject(); // paint
-}
-
-static void MakeJsonFont(const SkFont& font, SkJSONWriter& writer, UrlDataManager& urlDataManager) {
-    writer.beginObject();
-    store_bool(writer, SKDEBUGCANVAS_ATTRIBUTE_FAKEBOLDTEXT, font.isEmbolden(), false);
-    store_bool(writer, SKDEBUGCANVAS_ATTRIBUTE_LINEARTEXT, font.isLinearMetrics(), false);
-    store_bool(writer, SKDEBUGCANVAS_ATTRIBUTE_SUBPIXELTEXT, font.isSubpixel(), false);
-    store_bool(writer, SKDEBUGCANVAS_ATTRIBUTE_EMBEDDEDBITMAPTEXT, font.isEmbeddedBitmaps(), false);
-    store_bool(writer, SKDEBUGCANVAS_ATTRIBUTE_AUTOHINTING, font.isForceAutoHinting(), false);
-
-    store_scalar(writer, SKDEBUGCANVAS_ATTRIBUTE_TEXTSIZE, font.getSize(),
-                 SkPaintDefaults_TextSize);
-    store_scalar(writer, SKDEBUGCANVAS_ATTRIBUTE_TEXTSCALEX, font.getScaleX(), SK_Scalar1);
-    store_scalar(writer, SKDEBUGCANVAS_ATTRIBUTE_TEXTSCALEX, font.getSkewX(), 0.0f);
-    apply_font_edging(font, writer);
-    apply_font_hinting(font, writer);
-    apply_font_typeface(font, writer, urlDataManager);
-    writer.endObject(); // font
-}
-
-void SkDrawCommand::MakeJsonLattice(SkJSONWriter& writer, const SkCanvas::Lattice& lattice) {
-    writer.beginObject();
-    writer.appendS32(SKDEBUGCANVAS_ATTRIBUTE_LATTICEXCOUNT, lattice.fXCount);
-    writer.appendS32(SKDEBUGCANVAS_ATTRIBUTE_LATTICEYCOUNT, lattice.fYCount);
-    if (nullptr != lattice.fBounds) {
-        writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_BOUNDS);
-        MakeJsonIRect(writer, *lattice.fBounds);
-    }
-    writer.beginArray(SKDEBUGCANVAS_ATTRIBUTE_LATTICEXDIVS);
-    for (int i = 0; i < lattice.fXCount; i++) {
-        writer.appendS32(lattice.fXDivs[i]);
-    }
-    writer.endArray(); // xdivs
-    writer.beginArray(SKDEBUGCANVAS_ATTRIBUTE_LATTICEYDIVS);
-    for (int i = 0; i < lattice.fYCount; i++) {
-        writer.appendS32(lattice.fYDivs[i]);
-    }
-    writer.endArray(); // ydivs
-    if (nullptr != lattice.fRectTypes) {
-        writer.beginArray(SKDEBUGCANVAS_ATTRIBUTE_LATTICEFLAGS);
-        int flagCount = 0;
-        for (int row = 0; row < lattice.fYCount+1; row++) {
-            writer.beginArray();
-            for (int column = 0; column < lattice.fXCount+1; column++) {
-                writer.appendS32(lattice.fRectTypes[flagCount++]);
-            }
-            writer.endArray(); // row
-        }
-        writer.endArray();
-    }
-    writer.endObject();
-}
-
-SkClearCommand::SkClearCommand(SkColor color) : INHERITED(kClear_OpType) {
-    fColor = color;
-}
-
-void SkClearCommand::execute(SkCanvas* canvas) const {
-    canvas->clear(fColor);
-}
-
-void SkClearCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_COLOR);
-    MakeJsonColor(writer, fColor);
-}
-
-SkClipPathCommand::SkClipPathCommand(const SkPath& path, SkClipOp op, bool doAA)
-    : INHERITED(kClipPath_OpType) {
-    fPath = path;
-    fOp = op;
-    fDoAA = doAA;
-}
-
-void SkClipPathCommand::execute(SkCanvas* canvas) const {
-    canvas->clipPath(fPath, fOp, fDoAA);
-}
-
-bool SkClipPathCommand::render(SkCanvas* canvas) const {
-    render_path(canvas, fPath);
-    return true;
-}
-
-void SkClipPathCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PATH); MakeJsonPath(writer, fPath);
-    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_REGIONOP, regionop_name(fOp));
-    writer.appendBool(SKDEBUGCANVAS_ATTRIBUTE_ANTIALIAS, fDoAA);
-}
-
-SkClipRegionCommand::SkClipRegionCommand(const SkRegion& region, SkClipOp op)
-    : INHERITED(kClipRegion_OpType) {
-    fRegion = region;
-    fOp = op;
-}
-
-void SkClipRegionCommand::execute(SkCanvas* canvas) const {
-    canvas->clipRegion(fRegion, fOp);
-}
-
-void SkClipRegionCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_REGION); MakeJsonRegion(writer, fRegion);
-    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_REGIONOP, regionop_name(fOp));
-}
-
-SkClipRectCommand::SkClipRectCommand(const SkRect& rect, SkClipOp op, bool doAA)
-    : INHERITED(kClipRect_OpType) {
-    fRect = rect;
-    fOp = op;
-    fDoAA = doAA;
-}
-
-void SkClipRectCommand::execute(SkCanvas* canvas) const {
-    canvas->clipRect(fRect, fOp, fDoAA);
-}
-
-void SkClipRectCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_COORDS); MakeJsonRect(writer, fRect);
-    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_REGIONOP, regionop_name(fOp));
-    writer.appendBool(SKDEBUGCANVAS_ATTRIBUTE_ANTIALIAS, fDoAA);
-
-    SkString desc;
-    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_SHORTDESC, str_append(&desc, fRect)->c_str());
-}
-
-SkClipRRectCommand::SkClipRRectCommand(const SkRRect& rrect, SkClipOp op, bool doAA)
-    : INHERITED(kClipRRect_OpType) {
-    fRRect = rrect;
-    fOp = op;
-    fDoAA = doAA;
-}
-
-void SkClipRRectCommand::execute(SkCanvas* canvas) const {
-    canvas->clipRRect(fRRect, fOp, fDoAA);
-}
-
-bool SkClipRRectCommand::render(SkCanvas* canvas) const {
-    render_rrect(canvas, fRRect);
-    return true;
-}
-
-void SkClipRRectCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_COORDS); make_json_rrect(writer, fRRect);
-    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_REGIONOP, regionop_name(fOp));
-    writer.appendBool(SKDEBUGCANVAS_ATTRIBUTE_ANTIALIAS, fDoAA);
-}
-
-SkConcatCommand::SkConcatCommand(const SkMatrix& matrix)
-    : INHERITED(kConcat_OpType) {
-    fMatrix = matrix;
-}
-
-void SkConcatCommand::execute(SkCanvas* canvas) const {
-    canvas->concat(fMatrix);
-}
-
-void SkConcatCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_MATRIX); MakeJsonMatrix(writer, fMatrix);
-}
-
-////
-
-SkDrawAnnotationCommand::SkDrawAnnotationCommand(const SkRect& rect, const char key[],
-                                                 sk_sp<SkData> value)
-    : INHERITED(kDrawAnnotation_OpType)
-    , fRect(rect)
-    , fKey(key)
-    , fValue(std::move(value))
-{
-}
-
-void SkDrawAnnotationCommand::execute(SkCanvas* canvas) const {
-    canvas->drawAnnotation(fRect, fKey.c_str(), fValue);
-}
-
-void SkDrawAnnotationCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_COORDS); MakeJsonRect(writer, fRect);
-    writer.appendString("key", fKey.c_str());
-    if (fValue.get()) {
-        // TODO: dump out the "value"
-    }
-
-    SkString desc;
-    str_append(&desc, fRect)->appendf(" %s", fKey.c_str());
-    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_SHORTDESC, desc.c_str());
-}
-
-////
-
-SkDrawBitmapCommand::SkDrawBitmapCommand(const SkBitmap& bitmap, SkScalar left, SkScalar top,
-                                         const SkPaint* paint)
-    : INHERITED(kDrawBitmap_OpType)
-    , fBitmap(bitmap)
-    , fLeft(left)
-    , fTop(top)
-    , fPaint(paint) {}
-
-void SkDrawBitmapCommand::execute(SkCanvas* canvas) const {
-    canvas->drawBitmap(fBitmap, fLeft, fTop, fPaint.getMaybeNull());
-}
-
-bool SkDrawBitmapCommand::render(SkCanvas* canvas) const {
-    render_bitmap(canvas, fBitmap);
-    return true;
-}
-
-void SkDrawBitmapCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.beginObject(SKDEBUGCANVAS_ATTRIBUTE_BITMAP);
-    flatten(fBitmap, writer, urlDataManager);
-    writer.endObject();
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_COORDS); MakeJsonPoint(writer, fLeft, fTop);
-    if (fPaint.isValid()) {
-        writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PAINT);
-        MakeJsonPaint(writer, *fPaint, urlDataManager);
-    }
-}
-
-SkDrawBitmapLatticeCommand::SkDrawBitmapLatticeCommand(const SkBitmap& bitmap,
-                                                       const SkCanvas::Lattice& lattice,
-                                                       const SkRect& dst, const SkPaint* paint)
-    : INHERITED(kDrawBitmapLattice_OpType)
-    , fBitmap(bitmap)
-    , fLattice(lattice)
-    , fDst(dst)
-    , fPaint(paint) {}
-
-void SkDrawBitmapLatticeCommand::execute(SkCanvas* canvas) const {
-    canvas->drawBitmapLattice(fBitmap, fLattice, fDst, fPaint.getMaybeNull());
-}
-
-bool SkDrawBitmapLatticeCommand::render(SkCanvas* canvas) const {
-    SkAutoCanvasRestore acr(canvas, true);
-    canvas->clear(0xFFFFFFFF);
-
-    xlate_and_scale_to_bounds(canvas, fDst);
-
-    this->execute(canvas);
-    return true;
-}
-
-void SkDrawBitmapLatticeCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.beginObject(SKDEBUGCANVAS_ATTRIBUTE_BITMAP);
-    flatten(fBitmap, writer, urlDataManager);
-    writer.endObject(); // bitmap
-
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_LATTICE); MakeJsonLattice(writer, fLattice);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_DST); MakeJsonRect(writer, fDst);
-    if (fPaint.isValid()) {
-        writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PAINT);
-        MakeJsonPaint(writer, *fPaint, urlDataManager);
-    }
-
-    SkString desc;
-    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_SHORTDESC, str_append(&desc, fDst)->c_str());
-}
-
-SkDrawBitmapNineCommand::SkDrawBitmapNineCommand(const SkBitmap& bitmap, const SkIRect& center,
-                                                 const SkRect& dst, const SkPaint* paint)
-    : INHERITED(kDrawBitmapNine_OpType)
-    , fBitmap(bitmap)
-    , fCenter(center)
-    , fDst(dst)
-    , fPaint(paint) {}
-
-void SkDrawBitmapNineCommand::execute(SkCanvas* canvas) const {
-    canvas->drawBitmapNine(fBitmap, fCenter, fDst, fPaint.getMaybeNull());
-}
-
-bool SkDrawBitmapNineCommand::render(SkCanvas* canvas) const {
-    SkRect tmp = SkRect::Make(fCenter);
-    render_bitmap(canvas, fBitmap, &tmp);
-    return true;
-}
-
-void SkDrawBitmapNineCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.beginObject(SKDEBUGCANVAS_ATTRIBUTE_BITMAP);
-    flatten(fBitmap, writer, urlDataManager);
-    writer.endObject(); // bitmap
-
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_CENTER); MakeJsonIRect(writer, fCenter);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_DST); MakeJsonRect(writer, fDst);
-    if (fPaint.isValid()) {
-        writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PAINT);
-        MakeJsonPaint(writer, *fPaint, urlDataManager);
-    }
-}
-
-SkDrawBitmapRectCommand::SkDrawBitmapRectCommand(const SkBitmap& bitmap, const SkRect* src,
-                                                 const SkRect& dst, const SkPaint* paint,
-                                                 SkCanvas::SrcRectConstraint constraint)
-    : INHERITED(kDrawBitmapRect_OpType)
-    , fBitmap(bitmap)
-    , fSrc(src)
-    , fDst(dst)
-    , fPaint(paint)
-    , fConstraint(constraint) {}
-
-void SkDrawBitmapRectCommand::execute(SkCanvas* canvas) const {
-    canvas->legacy_drawBitmapRect(fBitmap, fSrc.getMaybeNull(), fDst, fPaint.getMaybeNull(),
-                                  fConstraint);
-}
-
-bool SkDrawBitmapRectCommand::render(SkCanvas* canvas) const {
-    render_bitmap(canvas, fBitmap, fSrc.getMaybeNull());
-    return true;
-}
-
-void SkDrawBitmapRectCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.beginObject(SKDEBUGCANVAS_ATTRIBUTE_BITMAP);
-    flatten(fBitmap, writer, urlDataManager);
-    writer.endObject(); // bitmap
-
-    if (fSrc.isValid()) {
-        writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_SRC); MakeJsonRect(writer, *fSrc);
-    }
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_DST); MakeJsonRect(writer, fDst);
-    if (fPaint.isValid()) {
-        writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PAINT);
-        MakeJsonPaint(writer, *fPaint, urlDataManager);
-    }
-    if (fConstraint == SkCanvas::kStrict_SrcRectConstraint) {
-        writer.appendBool(SKDEBUGCANVAS_ATTRIBUTE_STRICT, true);
-    }
-
-    SkString desc;
-    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_SHORTDESC, str_append(&desc, fDst)->c_str());
-}
-
-SkDrawImageCommand::SkDrawImageCommand(const SkImage* image, SkScalar left, SkScalar top,
-                                       const SkPaint* paint)
-    : INHERITED(kDrawImage_OpType)
-    , fImage(SkRef(image))
-    , fLeft(left)
-    , fTop(top)
-    , fPaint(paint) {}
-
-void SkDrawImageCommand::execute(SkCanvas* canvas) const {
-    canvas->drawImage(fImage.get(), fLeft, fTop, fPaint.getMaybeNull());
-}
-
-bool SkDrawImageCommand::render(SkCanvas* canvas) const {
-    SkAutoCanvasRestore acr(canvas, true);
-    canvas->clear(0xFFFFFFFF);
-
-    xlate_and_scale_to_bounds(canvas, SkRect::MakeXYWH(fLeft, fTop,
-                                                       SkIntToScalar(fImage->width()),
-                                                       SkIntToScalar(fImage->height())));
-    this->execute(canvas);
-    return true;
-}
-
-void SkDrawImageCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.beginObject(SKDEBUGCANVAS_ATTRIBUTE_IMAGE);
-    flatten(*fImage, writer, urlDataManager);
-    writer.endObject(); // image
-
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_COORDS); MakeJsonPoint(writer, fLeft, fTop);
-    if (fPaint.isValid()) {
-        writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PAINT);
-        MakeJsonPaint(writer, *fPaint, urlDataManager);
-    }
-
-    writer.appendU32(SKDEBUGCANVAS_ATTRIBUTE_UNIQUE_ID, fImage->uniqueID());
-    writer.appendS32(SKDEBUGCANVAS_ATTRIBUTE_WIDTH, fImage->width());
-    writer.appendS32(SKDEBUGCANVAS_ATTRIBUTE_HEIGHT, fImage->height());
-    switch (fImage->alphaType()) {
-        case kOpaque_SkAlphaType:
-            writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_ALPHA, SKDEBUGCANVAS_ALPHATYPE_OPAQUE);
-            break;
-        case kPremul_SkAlphaType:
-            writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_ALPHA, SKDEBUGCANVAS_ALPHATYPE_PREMUL);
-            break;
-        case kUnpremul_SkAlphaType:
-            writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_ALPHA, SKDEBUGCANVAS_ALPHATYPE_UNPREMUL);
-            break;
-        default:
-            writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_ALPHA, SKDEBUGCANVAS_ALPHATYPE_UNKNOWN);
-            break;
-    }
-}
-
-SkDrawImageLatticeCommand::SkDrawImageLatticeCommand(const SkImage* image,
-                                                     const SkCanvas::Lattice& lattice,
-                                                     const SkRect& dst, const SkPaint* paint)
-    : INHERITED(kDrawImageLattice_OpType)
-    , fImage(SkRef(image))
-    , fLattice(lattice)
-    , fDst(dst)
-    , fPaint(paint) {}
-
-void SkDrawImageLatticeCommand::execute(SkCanvas* canvas) const {
-    canvas->drawImageLattice(fImage.get(), fLattice, fDst, fPaint.getMaybeNull());
-}
-
-bool SkDrawImageLatticeCommand::render(SkCanvas* canvas) const {
-    SkAutoCanvasRestore acr(canvas, true);
-    canvas->clear(0xFFFFFFFF);
-
-    xlate_and_scale_to_bounds(canvas, fDst);
-
-    this->execute(canvas);
-    return true;
-}
-
-void SkDrawImageLatticeCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.beginObject(SKDEBUGCANVAS_ATTRIBUTE_IMAGE);
-    flatten(*fImage, writer, urlDataManager);
-    writer.endObject(); // image
-
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_LATTICE); MakeJsonLattice(writer, fLattice);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_DST); MakeJsonRect(writer, fDst);
-    if (fPaint.isValid()) {
-        writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PAINT);
-        MakeJsonPaint(writer, *fPaint, urlDataManager);
-    }
-
-    SkString desc;
-    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_SHORTDESC, str_append(&desc, fDst)->c_str());
-}
-
-SkDrawImageRectCommand::SkDrawImageRectCommand(const SkImage* image, const SkRect* src,
-                                               const SkRect& dst, const SkPaint* paint,
-                                               SkCanvas::SrcRectConstraint constraint)
-    : INHERITED(kDrawImageRect_OpType)
-    , fImage(SkRef(image))
-    , fSrc(src)
-    , fDst(dst)
-    , fPaint(paint)
-    , fConstraint(constraint) {}
-
-void SkDrawImageRectCommand::execute(SkCanvas* canvas) const {
-    canvas->legacy_drawImageRect(fImage.get(), fSrc.getMaybeNull(), fDst,
-                                 fPaint.getMaybeNull(), fConstraint);
-}
-
-bool SkDrawImageRectCommand::render(SkCanvas* canvas) const {
-    SkAutoCanvasRestore acr(canvas, true);
-    canvas->clear(0xFFFFFFFF);
-
-    xlate_and_scale_to_bounds(canvas, fDst);
-
-    this->execute(canvas);
-    return true;
-}
-
-void SkDrawImageRectCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.beginObject(SKDEBUGCANVAS_ATTRIBUTE_IMAGE);
-    flatten(*fImage, writer, urlDataManager);
-    writer.endObject(); // image
-
-    if (fSrc.isValid()) {
-        writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_SRC); MakeJsonRect(writer, *fSrc);
-    }
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_DST); MakeJsonRect(writer, fDst);
-    if (fPaint.isValid()) {
-        writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PAINT);
-        MakeJsonPaint(writer, *fPaint, urlDataManager);
-    }
-    if (fConstraint == SkCanvas::kStrict_SrcRectConstraint) {
-        writer.appendBool(SKDEBUGCANVAS_ATTRIBUTE_STRICT, true);
-    }
-
-    SkString desc;
-    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_SHORTDESC, str_append(&desc, fDst)->c_str());
-}
-
-SkDrawImageSetCommand::SkDrawImageSetCommand(const SkCanvas::ImageSetEntry set[], int count,
-                                             SkFilterQuality filterQuality, SkBlendMode mode)
-        : INHERITED(kDrawImageSet_OpType)
-        , fSet(count)
-        , fCount(count)
-        , fFilterQuality(filterQuality)
-        , fMode(mode) {
-    std::copy_n(set, count, fSet.get());
-}
-
-void SkDrawImageSetCommand::execute(SkCanvas* canvas) const {
-    canvas->experimental_DrawImageSetV1(fSet.get(), fCount, fFilterQuality, fMode);
-}
-
-SkDrawImageNineCommand::SkDrawImageNineCommand(const SkImage* image, const SkIRect& center,
-                                               const SkRect& dst, const SkPaint* paint)
-    : INHERITED(kDrawImageNine_OpType)
-    , fImage(SkRef(image))
-    , fCenter(center)
-    , fDst(dst)
-    , fPaint(paint) {}
-
-void SkDrawImageNineCommand::execute(SkCanvas* canvas) const {
-    canvas->drawImageNine(fImage.get(), fCenter, fDst, fPaint.getMaybeNull());
-}
-
-bool SkDrawImageNineCommand::render(SkCanvas* canvas) const {
-    SkAutoCanvasRestore acr(canvas, true);
-    canvas->clear(0xFFFFFFFF);
-
-    xlate_and_scale_to_bounds(canvas, fDst);
-
-    this->execute(canvas);
-    return true;
-}
-
-void SkDrawImageNineCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.beginObject(SKDEBUGCANVAS_ATTRIBUTE_IMAGE);
-    flatten(*fImage, writer, urlDataManager);
-    writer.endObject(); // image
-
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_CENTER); MakeJsonIRect(writer, fCenter);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_DST); MakeJsonRect(writer, fDst);
-    if (fPaint.isValid()) {
-        writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PAINT);
-        MakeJsonPaint(writer, *fPaint, urlDataManager);
-    }
-}
-
-SkDrawOvalCommand::SkDrawOvalCommand(const SkRect& oval, const SkPaint& paint)
-    : INHERITED(kDrawOval_OpType) {
-    fOval = oval;
-    fPaint = paint;
-}
-
-void SkDrawOvalCommand::execute(SkCanvas* canvas) const {
-    canvas->drawOval(fOval, fPaint);
-}
-
-bool SkDrawOvalCommand::render(SkCanvas* canvas) const {
-    canvas->clear(0xFFFFFFFF);
-    canvas->save();
-
-    xlate_and_scale_to_bounds(canvas, fOval);
-
-    SkPaint p;
-    p.setColor(SK_ColorBLACK);
-    p.setStyle(SkPaint::kStroke_Style);
-
-    canvas->drawOval(fOval, p);
-    canvas->restore();
-
-    return true;
-}
-
-void SkDrawOvalCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_COORDS); MakeJsonRect(writer, fOval);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PAINT); MakeJsonPaint(writer, fPaint, urlDataManager);
-}
-
-SkDrawArcCommand::SkDrawArcCommand(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
-                                   bool useCenter, const SkPaint& paint)
-        : INHERITED(kDrawArc_OpType) {
-    fOval = oval;
-    fStartAngle = startAngle;
-    fSweepAngle = sweepAngle;
-    fUseCenter = useCenter;
-    fPaint = paint;
-}
-
-void SkDrawArcCommand::execute(SkCanvas* canvas) const {
-    canvas->drawArc(fOval, fStartAngle, fSweepAngle, fUseCenter, fPaint);
-}
-
-bool SkDrawArcCommand::render(SkCanvas* canvas) const {
-    canvas->clear(0xFFFFFFFF);
-    canvas->save();
-
-    xlate_and_scale_to_bounds(canvas, fOval);
-
-    SkPaint p;
-    p.setColor(SK_ColorBLACK);
-    p.setStyle(SkPaint::kStroke_Style);
-
-    canvas->drawArc(fOval, fStartAngle, fSweepAngle, fUseCenter, p);
-    canvas->restore();
-
-    return true;
-}
-
-void SkDrawArcCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_COORDS); MakeJsonRect(writer, fOval);
-    writer.appendFloat(SKDEBUGCANVAS_ATTRIBUTE_STARTANGLE, fStartAngle);
-    writer.appendFloat(SKDEBUGCANVAS_ATTRIBUTE_SWEEPANGLE, fSweepAngle);
-    writer.appendBool(SKDEBUGCANVAS_ATTRIBUTE_USECENTER, fUseCenter);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PAINT); MakeJsonPaint(writer, fPaint, urlDataManager);
-}
-
-SkDrawPaintCommand::SkDrawPaintCommand(const SkPaint& paint)
-    : INHERITED(kDrawPaint_OpType) {
-    fPaint = paint;
-}
-
-void SkDrawPaintCommand::execute(SkCanvas* canvas) const {
-    canvas->drawPaint(fPaint);
-}
-
-bool SkDrawPaintCommand::render(SkCanvas* canvas) const {
-    canvas->clear(0xFFFFFFFF);
-    canvas->drawPaint(fPaint);
-    return true;
-}
-
-void SkDrawPaintCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PAINT); MakeJsonPaint(writer, fPaint, urlDataManager);
-}
-
-SkDrawPathCommand::SkDrawPathCommand(const SkPath& path, const SkPaint& paint)
-    : INHERITED(kDrawPath_OpType) {
-    fPath = path;
-    fPaint = paint;
-}
-
-void SkDrawPathCommand::execute(SkCanvas* canvas) const {
-    canvas->drawPath(fPath, fPaint);
-}
-
-bool SkDrawPathCommand::render(SkCanvas* canvas) const {
-    render_path(canvas, fPath);
-    return true;
-}
-
-void SkDrawPathCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PATH); MakeJsonPath(writer, fPath);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PAINT); MakeJsonPaint(writer, fPaint, urlDataManager);
-}
-
-SkDrawRegionCommand::SkDrawRegionCommand(const SkRegion& region, const SkPaint& paint)
-    : INHERITED(kDrawRegion_OpType) {
-    fRegion = region;
-    fPaint = paint;
-}
-
-void SkDrawRegionCommand::execute(SkCanvas* canvas) const {
-    canvas->drawRegion(fRegion, fPaint);
-}
-
-bool SkDrawRegionCommand::render(SkCanvas* canvas) const {
-    render_region(canvas, fRegion);
-    return true;
-}
-
-void SkDrawRegionCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_REGION); MakeJsonRegion(writer, fRegion);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PAINT); MakeJsonPaint(writer, fPaint, urlDataManager);
-}
-
-SkBeginDrawPictureCommand::SkBeginDrawPictureCommand(const SkPicture* picture,
-                                                     const SkMatrix* matrix,
-                                                     const SkPaint* paint)
-    : INHERITED(kBeginDrawPicture_OpType)
-    , fPicture(SkRef(picture))
-    , fMatrix(matrix)
-    , fPaint(paint) {}
-
-void SkBeginDrawPictureCommand::execute(SkCanvas* canvas) const {
-    if (fPaint.isValid()) {
-        SkRect bounds = fPicture->cullRect();
-        if (fMatrix.isValid()) {
-            fMatrix->mapRect(&bounds);
-        }
-        canvas->saveLayer(&bounds, fPaint.get());
-    }
-
-    if (fMatrix.isValid()) {
-        if (!fPaint.isValid()) {
-            canvas->save();
-        }
-        canvas->concat(*fMatrix);
-    }
-}
-
-bool SkBeginDrawPictureCommand::render(SkCanvas* canvas) const {
-    canvas->clear(0xFFFFFFFF);
-    canvas->save();
-
-    xlate_and_scale_to_bounds(canvas, fPicture->cullRect());
-
-    canvas->drawPicture(fPicture.get());
-
-    canvas->restore();
-
-    return true;
-}
-
-SkEndDrawPictureCommand::SkEndDrawPictureCommand(bool restore)
-    : INHERITED(kEndDrawPicture_OpType) , fRestore(restore) { }
-
-void SkEndDrawPictureCommand::execute(SkCanvas* canvas) const {
-    if (fRestore) {
-        canvas->restore();
-    }
-}
-
-SkDrawPointsCommand::SkDrawPointsCommand(SkCanvas::PointMode mode, size_t count,
-                                         const SkPoint pts[], const SkPaint& paint)
-    : INHERITED(kDrawPoints_OpType)
-    , fMode(mode)
-    , fPts(pts, count)
-    , fPaint(paint) {}
-
-void SkDrawPointsCommand::execute(SkCanvas* canvas) const {
-    canvas->drawPoints(fMode, fPts.count(), fPts.begin(), fPaint);
-}
-
-bool SkDrawPointsCommand::render(SkCanvas* canvas) const {
-    canvas->clear(0xFFFFFFFF);
-    canvas->save();
-
-    SkRect bounds;
-
-    bounds.setEmpty();
-    for (int i = 0; i < fPts.count(); ++i) {
-        SkRectPriv::GrowToInclude(&bounds, fPts[i]);
-    }
-
-    xlate_and_scale_to_bounds(canvas, bounds);
-
-    SkPaint p;
-    p.setColor(SK_ColorBLACK);
-    p.setStyle(SkPaint::kStroke_Style);
-
-    canvas->drawPoints(fMode, fPts.count(), fPts.begin(), p);
-    canvas->restore();
-
-    return true;
-}
-
-void SkDrawPointsCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_MODE, pointmode_name(fMode));
-    writer.beginArray(SKDEBUGCANVAS_ATTRIBUTE_POINTS);
-    for (int i = 0; i < fPts.count(); i++) {
-        MakeJsonPoint(writer, fPts[i]);
-    }
-    writer.endArray(); // points
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PAINT); MakeJsonPaint(writer, fPaint, urlDataManager);
-}
-
-SkDrawTextBlobCommand::SkDrawTextBlobCommand(sk_sp<SkTextBlob> blob, SkScalar x, SkScalar y,
-                                             const SkPaint& paint)
-    : INHERITED(kDrawTextBlob_OpType)
-    , fBlob(std::move(blob))
-    , fXPos(x)
-    , fYPos(y)
-    , fPaint(paint) {}
-
-void SkDrawTextBlobCommand::execute(SkCanvas* canvas) const {
-    canvas->drawTextBlob(fBlob, fXPos, fYPos, fPaint);
-}
-
-bool SkDrawTextBlobCommand::render(SkCanvas* canvas) const {
-    canvas->clear(SK_ColorWHITE);
-    canvas->save();
-
-    SkRect bounds = fBlob->bounds().makeOffset(fXPos, fYPos);
-    xlate_and_scale_to_bounds(canvas, bounds);
-
-    canvas->drawTextBlob(fBlob, fXPos, fYPos, fPaint);
-
-    canvas->restore();
-
-    return true;
-}
-
-void SkDrawTextBlobCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.beginArray(SKDEBUGCANVAS_ATTRIBUTE_RUNS);
-    SkTextBlobRunIterator iter(fBlob.get());
-    while (!iter.done()) {
-        writer.beginObject(); // run
-        writer.beginArray(SKDEBUGCANVAS_ATTRIBUTE_GLYPHS);
-        for (uint32_t i = 0; i < iter.glyphCount(); i++) {
-            writer.appendU32(iter.glyphs()[i]);
-        }
-        writer.endArray(); // glyphs
-        if (iter.positioning() != SkTextBlobRunIterator::kDefault_Positioning) {
-            writer.beginArray(SKDEBUGCANVAS_ATTRIBUTE_POSITIONS);
-            const SkScalar* iterPositions = iter.pos();
-            for (uint32_t i = 0; i < iter.glyphCount(); i++) {
-                switch (iter.positioning()) {
-                case SkTextBlobRunIterator::kFull_Positioning:
-                    MakeJsonPoint(writer, iterPositions[i * 2], iterPositions[i * 2 + 1]);
-                    break;
-                case SkTextBlobRunIterator::kHorizontal_Positioning:
-                    writer.appendFloat(iterPositions[i]);
-                    break;
-                case SkTextBlobRunIterator::kDefault_Positioning:
-                    break;
-                case SkTextBlobRunIterator::kRSXform_Positioning:
-                    // TODO_RSXFORM_BLOB
-                    break;
-                }
-            }
-            writer.endArray(); // positions
-        }
-        writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_FONT);
-            MakeJsonFont(iter.font(), writer, urlDataManager);
-        writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_COORDS);
-            MakeJsonPoint(writer, iter.offset());
-
-        writer.endObject(); // run
-        iter.next();
-    }
-    writer.endArray(); // runs
-    writer.appendFloat(SKDEBUGCANVAS_ATTRIBUTE_X, fXPos);
-    writer.appendFloat(SKDEBUGCANVAS_ATTRIBUTE_Y, fYPos);
-    SkRect bounds = fBlob->bounds();
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_COORDS); MakeJsonRect(writer, bounds);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PAINT); MakeJsonPaint(writer, fPaint, urlDataManager);
-
-    SkString desc;
-    // make the bounds local by applying the x,y
-    bounds.offset(fXPos, fYPos);
-    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_SHORTDESC, str_append(&desc, bounds)->c_str());
-}
-
-SkDrawPatchCommand::SkDrawPatchCommand(const SkPoint cubics[12], const SkColor colors[4],
-                                       const SkPoint texCoords[4], SkBlendMode bmode,
-                                       const SkPaint& paint)
-    : INHERITED(kDrawPatch_OpType)
-    , fBlendMode(bmode)
-{
-    memcpy(fCubics, cubics, sizeof(fCubics));
-    if (colors != nullptr) {
-        memcpy(fColors, colors, sizeof(fColors));
-        fColorsPtr = fColors;
-    } else {
-        fColorsPtr = nullptr;
-    }
-    if (texCoords != nullptr) {
-        memcpy(fTexCoords, texCoords, sizeof(fTexCoords));
-        fTexCoordsPtr = fTexCoords;
-    } else {
-        fTexCoordsPtr = nullptr;
-    }
-    fPaint = paint;
-}
-
-void SkDrawPatchCommand::execute(SkCanvas* canvas) const {
-    canvas->drawPatch(fCubics, fColorsPtr, fTexCoordsPtr, fBlendMode, fPaint);
-}
-
-void SkDrawPatchCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.beginArray(SKDEBUGCANVAS_ATTRIBUTE_CUBICS);
-    for (int i = 0; i < 12; i++) {
-        MakeJsonPoint(writer, fCubics[i]);
-    }
-    writer.endArray(); // cubics
-    if (fColorsPtr != nullptr) {
-        writer.beginArray(SKDEBUGCANVAS_ATTRIBUTE_COLORS);
-        for (int i = 0; i < 4; i++) {
-            MakeJsonColor(writer, fColorsPtr[i]);
-        }
-        writer.endArray(); // colors
-    }
-    if (fTexCoordsPtr != nullptr) {
-        writer.beginArray(SKDEBUGCANVAS_ATTRIBUTE_TEXTURECOORDS);
-        for (int i = 0; i < 4; i++) {
-            MakeJsonPoint(writer, fTexCoords[i]);
-        }
-        writer.endArray(); // texCoords
-    }
-    // fBlendMode
-}
-
-SkDrawRectCommand::SkDrawRectCommand(const SkRect& rect, const SkPaint& paint)
-    : INHERITED(kDrawRect_OpType) {
-    fRect = rect;
-    fPaint = paint;
-}
-
-void SkDrawRectCommand::execute(SkCanvas* canvas) const {
-    canvas->drawRect(fRect, fPaint);
-}
-
-void SkDrawRectCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_COORDS); MakeJsonRect(writer, fRect);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PAINT); MakeJsonPaint(writer, fPaint, urlDataManager);
-
-    SkString desc;
-    writer.appendString(SKDEBUGCANVAS_ATTRIBUTE_SHORTDESC, str_append(&desc, fRect)->c_str());
-}
-
-SkDrawEdgeAARectCommand::SkDrawEdgeAARectCommand(const SkRect& rect, SkCanvas::QuadAAFlags aa,
-                                                 SkColor color, SkBlendMode mode)
-    : INHERITED(kDrawEdgeAARect_OpType) {
-    fRect = rect;
-    fAA = aa;
-    fColor = color;
-    fMode = mode;
-}
-
-void SkDrawEdgeAARectCommand::execute(SkCanvas* canvas) const {
-    canvas->experimental_DrawEdgeAARectV1(fRect, fAA, fColor, fMode);
-}
-
-SkDrawRRectCommand::SkDrawRRectCommand(const SkRRect& rrect, const SkPaint& paint)
-    : INHERITED(kDrawRRect_OpType) {
-    fRRect = rrect;
-    fPaint = paint;
-}
-
-void SkDrawRRectCommand::execute(SkCanvas* canvas) const {
-    canvas->drawRRect(fRRect, fPaint);
-}
-
-bool SkDrawRRectCommand::render(SkCanvas* canvas) const {
-    render_rrect(canvas, fRRect);
-    return true;
-}
-
-void SkDrawRRectCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_COORDS); make_json_rrect(writer, fRRect);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PAINT); MakeJsonPaint(writer, fPaint, urlDataManager);
-}
-
-SkDrawDRRectCommand::SkDrawDRRectCommand(const SkRRect& outer,
-                                         const SkRRect& inner,
-                                         const SkPaint& paint)
-    : INHERITED(kDrawDRRect_OpType) {
-    fOuter = outer;
-    fInner = inner;
-    fPaint = paint;
-}
-
-void SkDrawDRRectCommand::execute(SkCanvas* canvas) const {
-    canvas->drawDRRect(fOuter, fInner, fPaint);
-}
-
-bool SkDrawDRRectCommand::render(SkCanvas* canvas) const {
-    render_drrect(canvas, fOuter, fInner);
-    return true;
-}
-
-void SkDrawDRRectCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_OUTER); make_json_rrect(writer, fOuter);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_INNER); make_json_rrect(writer, fInner);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PAINT); MakeJsonPaint(writer, fPaint, urlDataManager);
-}
-
-SkDrawShadowCommand::SkDrawShadowCommand(const SkPath& path, const SkDrawShadowRec& rec)
-        : INHERITED(kDrawShadow_OpType) {
-    fPath = path;
-    fShadowRec = rec;
-}
-
-void SkDrawShadowCommand::execute(SkCanvas* canvas) const {
-    canvas->private_draw_shadow_rec(fPath, fShadowRec);
-}
-
-bool SkDrawShadowCommand::render(SkCanvas* canvas) const {
-    render_shadow(canvas, fPath, fShadowRec);
-    return true;
-}
-
-void SkDrawShadowCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-
-    bool geometricOnly = SkToBool(fShadowRec.fFlags & SkShadowFlags::kGeometricOnly_ShadowFlag);
-    bool transparentOccluder =
-            SkToBool(fShadowRec.fFlags & SkShadowFlags::kTransparentOccluder_ShadowFlag);
-
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PATH); MakeJsonPath(writer, fPath);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_ZPLANE);
-        MakeJsonPoint3(writer, fShadowRec.fZPlaneParams);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_LIGHTPOSITION);
-        MakeJsonPoint3(writer, fShadowRec.fLightPos);
-    writer.appendFloat(SKDEBUGCANVAS_ATTRIBUTE_LIGHTRADIUS, fShadowRec.fLightRadius);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_AMBIENTCOLOR);
-        MakeJsonColor(writer, fShadowRec.fAmbientColor);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_SPOTCOLOR);
-        MakeJsonColor(writer, fShadowRec.fSpotColor);
-    store_bool(writer, SKDEBUGCANVAS_SHADOWFLAG_TRANSPARENT_OCC, transparentOccluder, false);
-    store_bool(writer, SKDEBUGCANVAS_SHADOWFLAG_GEOMETRIC_ONLY, geometricOnly, false);
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-SkDrawDrawableCommand::SkDrawDrawableCommand(SkDrawable* drawable, const SkMatrix* matrix)
-    : INHERITED(kDrawDrawable_OpType)
-    , fDrawable(SkRef(drawable))
-    , fMatrix(matrix) {}
-
-void SkDrawDrawableCommand::execute(SkCanvas* canvas) const {
-    canvas->drawDrawable(fDrawable.get(), fMatrix.getMaybeNull());
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-SkDrawVerticesCommand::SkDrawVerticesCommand(sk_sp<SkVertices> vertices, SkBlendMode bmode,
-                                             const SkPaint& paint)
-    : INHERITED(kDrawVertices_OpType)
-    , fVertices(std::move(vertices))
-    , fBlendMode(bmode)
-    , fPaint(paint) {}
-
-void SkDrawVerticesCommand::execute(SkCanvas* canvas) const {
-    canvas->drawVertices(fVertices, fBlendMode, fPaint);
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-SkDrawAtlasCommand::SkDrawAtlasCommand(const SkImage* image, const SkRSXform xform[],
-                                       const SkRect tex[], const SkColor colors[], int count,
-                                       SkBlendMode bmode, const SkRect* cull,
-                                       const SkPaint* paint)
-    : INHERITED(kDrawAtlas_OpType)
-    , fImage(SkRef(image))
-    , fXform(xform, count)
-    , fTex(tex, count)
-    , fColors(colors, colors ? count : 0)
-    , fBlendMode(bmode)
-    , fCull(cull)
-    , fPaint(paint) {}
-
-void SkDrawAtlasCommand::execute(SkCanvas* canvas) const {
-    canvas->drawAtlas(fImage.get(), fXform.begin(), fTex.begin(),
-                      fColors.isEmpty() ? nullptr : fColors.begin(), fXform.count(), fBlendMode,
-                      fCull.getMaybeNull(), fPaint.getMaybeNull());
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-SkRestoreCommand::SkRestoreCommand()
-    : INHERITED(kRestore_OpType) {}
-
-void SkRestoreCommand::execute(SkCanvas* canvas) const {
-    canvas->restore();
-}
-
-SkSaveCommand::SkSaveCommand()
-    : INHERITED(kSave_OpType) {
-}
-
-void SkSaveCommand::execute(SkCanvas* canvas) const {
-    canvas->save();
-}
-
-SkSaveLayerCommand::SkSaveLayerCommand(const SkCanvas::SaveLayerRec& rec)
-    : INHERITED(kSaveLayer_OpType)
-    , fBounds(rec.fBounds)
-    , fPaint(rec.fPaint)
-    , fBackdrop(SkSafeRef(rec.fBackdrop))
-    , fSaveLayerFlags(rec.fSaveLayerFlags) {}
-
-void SkSaveLayerCommand::execute(SkCanvas* canvas) const {
-    canvas->saveLayer(SkCanvas::SaveLayerRec(fBounds.getMaybeNull(), fPaint.getMaybeNull(),
-                                             fSaveLayerFlags));
-}
-
-void SkSaveLayerCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    if (fBounds.isValid()) {
-        writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_BOUNDS); MakeJsonRect(writer, *fBounds);
-    }
-    if (fPaint.isValid()) {
-        writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_PAINT);
-        MakeJsonPaint(writer, *fPaint, urlDataManager);
-    }
-    if (fBackdrop != nullptr) {
-        writer.beginObject(SKDEBUGCANVAS_ATTRIBUTE_BACKDROP);
-        flatten(fBackdrop.get(), writer, urlDataManager);
-        writer.endObject(); // backdrop
-    }
-    if (fSaveLayerFlags != 0) {
-        SkDebugf("unsupported: saveLayer flags\n");
-        SkASSERT(false);
-    }
-}
-
-SkSetMatrixCommand::SkSetMatrixCommand(const SkMatrix& matrix)
-    : INHERITED(kSetMatrix_OpType) {
-    fMatrix = matrix;
-}
-
-void SkSetMatrixCommand::execute(SkCanvas* canvas) const {
-    canvas->setMatrix(fMatrix);
-}
-
-void SkSetMatrixCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
-    INHERITED::toJSON(writer, urlDataManager);
-    writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_MATRIX); MakeJsonMatrix(writer, fMatrix);
-}
diff --git a/tools/debugger/SkDrawCommand.h b/tools/debugger/SkDrawCommand.h
deleted file mode 100644
index 7c386bc..0000000
--- a/tools/debugger/SkDrawCommand.h
+++ /dev/null
@@ -1,685 +0,0 @@
-/*
- * Copyright 2012 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKDRAWCOMMAND_H_
-#define SKDRAWCOMMAND_H_
-
-#include "SkBitmap.h"
-#include "SkCanvas.h"
-#include "SkDrawShadowInfo.h"
-#include "SkFlattenable.h"
-#include "SkJSONWriter.h"
-#include "SkTLazy.h"
-#include "SkPath.h"
-#include "SkRegion.h"
-#include "SkRRect.h"
-#include "SkRSXform.h"
-#include "SkString.h"
-#include "SkTDArray.h"
-#include "SkVertices.h"
-#include "UrlDataManager.h"
-
-class SkDrawCommand {
-public:
-    enum OpType {
-        kBeginDrawPicture_OpType,
-        kClear_OpType,
-        kClipPath_OpType,
-        kClipRegion_OpType,
-        kClipRect_OpType,
-        kClipRRect_OpType,
-        kConcat_OpType,
-        kDrawAnnotation_OpType,
-        kDrawBitmap_OpType,
-        kDrawBitmapLattice_OpType,
-        kDrawBitmapNine_OpType,
-        kDrawBitmapRect_OpType,
-        kDrawDRRect_OpType,
-        kDrawImage_OpType,
-        kDrawImageLattice_OpType,
-        kDrawImageNine_OpType,
-        kDrawImageRect_OpType,
-        kDrawImageSet_OpType,
-        kDrawOval_OpType,
-        kDrawArc_OpType,
-        kDrawPaint_OpType,
-        kDrawPatch_OpType,
-        kDrawPath_OpType,
-        kDrawPoints_OpType,
-        kDrawRect_OpType,
-        kDrawEdgeAARect_OpType,
-        kDrawRRect_OpType,
-        kDrawRegion_OpType,
-        kDrawShadow_OpType,
-        kDrawTextBlob_OpType,
-        kDrawVertices_OpType,
-        kDrawAtlas_OpType,
-        kDrawDrawable_OpType,
-        kEndDrawPicture_OpType,
-        kRestore_OpType,
-        kSave_OpType,
-        kSaveLayer_OpType,
-        kSetMatrix_OpType,
-
-        kLast_OpType = kSetMatrix_OpType
-    };
-
-    static const int kOpTypeCount = kLast_OpType + 1;
-
-    static void WritePNG(SkBitmap bitmap, SkWStream& out);
-
-    SkDrawCommand(OpType opType);
-
-    virtual ~SkDrawCommand() {}
-
-    bool isVisible() const {
-        return fVisible;
-    }
-
-    void setVisible(bool toggle) {
-        fVisible = toggle;
-    }
-
-    virtual void execute(SkCanvas*) const = 0;
-
-    virtual bool render(SkCanvas* canvas) const { return false; }
-
-    virtual void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const;
-
-    static const char* GetCommandString(OpType type);
-
-    // Helper methods for converting things to JSON
-    static void MakeJsonColor(SkJSONWriter&, const SkColor color);
-    static void MakeJsonColor4f(SkJSONWriter&, const SkColor4f& color);
-    static void MakeJsonPoint(SkJSONWriter&, const SkPoint& point);
-    static void MakeJsonPoint(SkJSONWriter&, SkScalar x, SkScalar y);
-    static void MakeJsonPoint3(SkJSONWriter&, const SkPoint3& point);
-    static void MakeJsonRect(SkJSONWriter&, const SkRect& rect);
-    static void MakeJsonIRect(SkJSONWriter&, const SkIRect&);
-    static void MakeJsonMatrix(SkJSONWriter&, const SkMatrix&);
-    static void MakeJsonPath(SkJSONWriter&, const SkPath& path);
-    static void MakeJsonRegion(SkJSONWriter&, const SkRegion& region);
-    static void MakeJsonPaint(SkJSONWriter&, const SkPaint& paint, UrlDataManager& urlDataManager);
-    static void MakeJsonLattice(SkJSONWriter&, const SkCanvas::Lattice& lattice);
-
-    static void flatten(const SkFlattenable* flattenable, SkJSONWriter& writer,
-                        UrlDataManager& urlDataManager);
-    static bool flatten(const SkImage& image, SkJSONWriter& writer,
-                        UrlDataManager& urlDataManager);
-    static bool flatten(const SkBitmap& bitmap, SkJSONWriter& writer,
-                        UrlDataManager& urlDataManager);
-
-private:
-    OpType fOpType;
-    bool   fVisible;
-};
-
-class SkRestoreCommand : public SkDrawCommand {
-public:
-    SkRestoreCommand();
-    void execute(SkCanvas* canvas) const override;
-
-private:
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkClearCommand : public SkDrawCommand {
-public:
-    SkClearCommand(SkColor color);
-    void execute(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkColor fColor;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkClipPathCommand : public SkDrawCommand {
-public:
-    SkClipPathCommand(const SkPath& path, SkClipOp op, bool doAA);
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkPath   fPath;
-    SkClipOp fOp;
-    bool     fDoAA;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkClipRegionCommand : public SkDrawCommand {
-public:
-    SkClipRegionCommand(const SkRegion& region, SkClipOp op);
-    void execute(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkRegion fRegion;
-    SkClipOp fOp;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkClipRectCommand : public SkDrawCommand {
-public:
-    SkClipRectCommand(const SkRect& rect, SkClipOp op, bool doAA);
-    void execute(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkRect   fRect;
-    SkClipOp fOp;
-    bool     fDoAA;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkClipRRectCommand : public SkDrawCommand {
-public:
-    SkClipRRectCommand(const SkRRect& rrect, SkClipOp op, bool doAA);
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkRRect  fRRect;
-    SkClipOp fOp;
-    bool     fDoAA;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkConcatCommand : public SkDrawCommand {
-public:
-    SkConcatCommand(const SkMatrix& matrix);
-    void execute(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkMatrix fMatrix;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawAnnotationCommand : public SkDrawCommand {
-public:
-    SkDrawAnnotationCommand(const SkRect&, const char key[], sk_sp<SkData> value);
-    void execute(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkRect          fRect;
-    SkString        fKey;
-    sk_sp<SkData>   fValue;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawBitmapCommand : public SkDrawCommand {
-public:
-    SkDrawBitmapCommand(const SkBitmap& bitmap, SkScalar left, SkScalar top,
-                        const SkPaint* paint);
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkBitmap         fBitmap;
-    SkScalar         fLeft;
-    SkScalar         fTop;
-    SkTLazy<SkPaint> fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawBitmapLatticeCommand : public SkDrawCommand {
-public:
-    SkDrawBitmapLatticeCommand(const SkBitmap& bitmap, const SkCanvas::Lattice& lattice,
-                               const SkRect& dst, const SkPaint* paint);
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkBitmap          fBitmap;
-    SkCanvas::Lattice fLattice;
-    SkRect            fDst;
-    SkTLazy<SkPaint>  fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawBitmapNineCommand : public SkDrawCommand {
-public:
-    SkDrawBitmapNineCommand(const SkBitmap& bitmap, const SkIRect& center,
-                            const SkRect& dst, const SkPaint* paint);
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkBitmap         fBitmap;
-    SkIRect          fCenter;
-    SkRect           fDst;
-    SkTLazy<SkPaint> fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawBitmapRectCommand : public SkDrawCommand {
-public:
-    SkDrawBitmapRectCommand(const SkBitmap& bitmap, const SkRect* src,
-                            const SkRect& dst, const SkPaint* paint,
-                            SkCanvas::SrcRectConstraint);
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkBitmap                      fBitmap;
-    SkTLazy<SkRect>               fSrc;
-    SkRect                        fDst;
-    SkTLazy<SkPaint>              fPaint;
-    SkCanvas::SrcRectConstraint   fConstraint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawImageCommand : public SkDrawCommand {
-public:
-    SkDrawImageCommand(const SkImage* image, SkScalar left, SkScalar top, const SkPaint* paint);
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    sk_sp<const SkImage> fImage;
-    SkScalar             fLeft;
-    SkScalar             fTop;
-    SkTLazy<SkPaint>     fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawImageLatticeCommand : public SkDrawCommand {
-public:
-    SkDrawImageLatticeCommand(const SkImage* image, const SkCanvas::Lattice& lattice,
-                              const SkRect& dst, const SkPaint* paint);
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    sk_sp<const SkImage>        fImage;
-    SkCanvas::Lattice           fLattice;
-    SkRect                      fDst;
-    SkTLazy<SkPaint>            fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawImageNineCommand : public SkDrawCommand {
-public:
-    SkDrawImageNineCommand(const SkImage* image, const SkIRect& center,
-                           const SkRect& dst, const SkPaint* paint);
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    sk_sp<const SkImage> fImage;
-    SkIRect              fCenter;
-    SkRect               fDst;
-    SkTLazy<SkPaint>     fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawImageRectCommand : public SkDrawCommand {
-public:
-    SkDrawImageRectCommand(const SkImage* image, const SkRect* src, const SkRect& dst,
-                           const SkPaint* paint, SkCanvas::SrcRectConstraint constraint);
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    sk_sp<const SkImage>        fImage;
-    SkTLazy<SkRect>             fSrc;
-    SkRect                      fDst;
-    SkTLazy<SkPaint>            fPaint;
-    SkCanvas::SrcRectConstraint fConstraint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawImageSetCommand : public SkDrawCommand {
-public:
-    SkDrawImageSetCommand(const SkCanvas::ImageSetEntry[], int count, SkFilterQuality, SkBlendMode);
-    void execute(SkCanvas* canvas) const override;
-
-private:
-    SkAutoTArray<SkCanvas::ImageSetEntry> fSet;
-    int fCount;
-    SkFilterQuality fFilterQuality;
-    SkBlendMode fMode;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawOvalCommand : public SkDrawCommand {
-public:
-    SkDrawOvalCommand(const SkRect& oval, const SkPaint& paint);
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkRect  fOval;
-    SkPaint fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawArcCommand : public SkDrawCommand {
-public:
-    SkDrawArcCommand(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter,
-                     const SkPaint& paint);
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkRect   fOval;
-    SkScalar fStartAngle;
-    SkScalar fSweepAngle;
-    bool     fUseCenter;
-    SkPaint  fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawPaintCommand : public SkDrawCommand {
-public:
-    SkDrawPaintCommand(const SkPaint& paint);
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkPaint fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawPathCommand : public SkDrawCommand {
-public:
-    SkDrawPathCommand(const SkPath& path, const SkPaint& paint);
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkPath   fPath;
-    SkPaint  fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkBeginDrawPictureCommand : public SkDrawCommand {
-public:
-    SkBeginDrawPictureCommand(const SkPicture* picture,
-                              const SkMatrix* matrix,
-                              const SkPaint* paint);
-
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-
-private:
-    sk_sp<const SkPicture> fPicture;
-    SkTLazy<SkMatrix>      fMatrix;
-    SkTLazy<SkPaint>       fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkEndDrawPictureCommand : public SkDrawCommand {
-public:
-    SkEndDrawPictureCommand(bool restore);
-
-    void execute(SkCanvas* canvas) const override;
-
-private:
-    bool fRestore;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawPointsCommand : public SkDrawCommand {
-public:
-    SkDrawPointsCommand(SkCanvas::PointMode mode, size_t count, const SkPoint pts[],
-                        const SkPaint& paint);
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkCanvas::PointMode fMode;
-    SkTDArray<SkPoint>  fPts;
-    SkPaint             fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawRegionCommand : public SkDrawCommand {
-public:
-    SkDrawRegionCommand(const SkRegion& region, const SkPaint& paint);
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkRegion fRegion;
-    SkPaint  fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawTextBlobCommand : public SkDrawCommand {
-public:
-    SkDrawTextBlobCommand(sk_sp<SkTextBlob> blob, SkScalar x, SkScalar y, const SkPaint& paint);
-
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    sk_sp<SkTextBlob> fBlob;
-    SkScalar          fXPos;
-    SkScalar          fYPos;
-    SkPaint           fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawPatchCommand : public SkDrawCommand {
-public:
-    SkDrawPatchCommand(const SkPoint cubics[12], const SkColor colors[4],
-                       const SkPoint texCoords[4], SkBlendMode bmode,
-                       const SkPaint& paint);
-    void execute(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkPoint fCubics[12];
-    SkColor* fColorsPtr;
-    SkColor  fColors[4];
-    SkPoint* fTexCoordsPtr;
-    SkPoint  fTexCoords[4];
-    SkBlendMode fBlendMode;
-    SkPaint fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-
-class SkDrawRectCommand : public SkDrawCommand {
-public:
-    SkDrawRectCommand(const SkRect& rect, const SkPaint& paint);
-    void execute(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkRect  fRect;
-    SkPaint fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawEdgeAARectCommand : public SkDrawCommand {
-public:
-    SkDrawEdgeAARectCommand(const SkRect& rect, SkCanvas::QuadAAFlags aa, SkColor color,
-                            SkBlendMode mode);
-    void execute(SkCanvas* canvas) const override;
-
-private:
-    SkRect  fRect;
-    SkCanvas::QuadAAFlags fAA;
-    SkColor fColor;
-    SkBlendMode fMode;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawRRectCommand : public SkDrawCommand {
-public:
-    SkDrawRRectCommand(const SkRRect& rrect, const SkPaint& paint);
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkRRect fRRect;
-    SkPaint fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawDRRectCommand : public SkDrawCommand {
-public:
-    SkDrawDRRectCommand(const SkRRect& outer, const SkRRect& inner,
-                        const SkPaint& paint);
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkRRect fOuter;
-    SkRRect fInner;
-    SkPaint fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawVerticesCommand : public SkDrawCommand {
-public:
-    SkDrawVerticesCommand(sk_sp<SkVertices>, SkBlendMode, const SkPaint&);
-
-    void execute(SkCanvas* canvas) const override;
-
-private:
-    sk_sp<SkVertices>   fVertices;
-    SkBlendMode         fBlendMode;
-    SkPaint             fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawAtlasCommand : public SkDrawCommand {
-public:
-    SkDrawAtlasCommand(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], int,
-                       SkBlendMode, const SkRect*, const SkPaint*);
-
-    void execute(SkCanvas* canvas) const override;
-
-private:
-    sk_sp<const SkImage> fImage;
-    SkTDArray<SkRSXform> fXform;
-    SkTDArray<SkRect>    fTex;
-    SkTDArray<SkColor>   fColors;
-    SkBlendMode          fBlendMode;
-    SkTLazy<SkRect>      fCull;
-    SkTLazy<SkPaint>     fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkSaveCommand : public SkDrawCommand {
-public:
-    SkSaveCommand();
-    void execute(SkCanvas* canvas) const override;
-
-private:
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkSaveLayerCommand : public SkDrawCommand {
-public:
-    SkSaveLayerCommand(const SkCanvas::SaveLayerRec&);
-    void execute(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkTLazy<SkRect>            fBounds;
-    SkTLazy<SkPaint>           fPaint;
-    sk_sp<const SkImageFilter> fBackdrop;
-    uint32_t                   fSaveLayerFlags;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkSetMatrixCommand : public SkDrawCommand {
-public:
-    SkSetMatrixCommand(const SkMatrix& matrix);
-    void execute(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkMatrix fMatrix;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawShadowCommand : public SkDrawCommand {
-public:
-    SkDrawShadowCommand(const SkPath& path, const SkDrawShadowRec& rec);
-    void execute(SkCanvas* canvas) const override;
-    bool render(SkCanvas* canvas) const override;
-    void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
-
-private:
-    SkPath           fPath;
-    SkDrawShadowRec  fShadowRec;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawDrawableCommand : public SkDrawCommand {
-public:
-    SkDrawDrawableCommand(SkDrawable*, const SkMatrix*);
-    void execute(SkCanvas* canvas) const override;
-
-private:
-    sk_sp<SkDrawable> fDrawable;
-    SkTLazy<SkMatrix> fMatrix;
-
-    typedef SkDrawCommand INHERITED;
-};
-#endif
diff --git a/tools/debugger/SkJsonWriteBuffer.cpp b/tools/debugger/SkJsonWriteBuffer.cpp
deleted file mode 100644
index f443b04..0000000
--- a/tools/debugger/SkJsonWriteBuffer.cpp
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * 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 "SkJsonWriteBuffer.h"
-
-#include "SkDrawCommand.h"
-
-void SkJsonWriteBuffer::append(const char* type) {
-    SkString fullName = SkStringPrintf("%02d_%s", fCount++, type);
-    fWriter->appendName(fullName.c_str());
-}
-
-void SkJsonWriteBuffer::writePad32(const void* data, size_t size) {
-    this->append("rawBytes");
-    fWriter->beginArray();
-    const uint8_t* bytes = reinterpret_cast<const uint8_t*>(data);
-    for (size_t i = 0; i < size; ++i) {
-        SkString hexByte = SkStringPrintf("%02x", bytes[i]);
-        fWriter->appendString(hexByte.c_str());
-    }
-    fWriter->endArray();
-}
-
-void SkJsonWriteBuffer::writeByteArray(const void* data, size_t size) {
-    this->append("byteArray");
-    fWriter->beginArray();
-    const uint8_t* bytes = reinterpret_cast<const uint8_t*>(data);
-    for (size_t i = 0; i < size; ++i) {
-        SkString hexByte = SkStringPrintf("%02x", bytes[i]);
-        fWriter->appendString(hexByte.c_str());
-    }
-    fWriter->endArray();
-}
-
-void SkJsonWriteBuffer::writeBool(bool value) {
-    this->append("bool"); fWriter->appendBool(value);
-}
-
-void SkJsonWriteBuffer::writeScalar(SkScalar value) {
-    this->append("scalar"); fWriter->appendFloat(value);
-}
-
-void SkJsonWriteBuffer::writeScalarArray(const SkScalar* value, uint32_t count) {
-    this->append("scalarArray");
-    fWriter->beginArray();
-    for (uint32_t i = 0; i < count; ++i) {
-        fWriter->appendFloat(value[i]);
-    }
-    fWriter->endArray();
-}
-
-void SkJsonWriteBuffer::writeInt(int32_t value) {
-    this->append("int"); fWriter->appendS32(value);
-}
-
-void SkJsonWriteBuffer::writeIntArray(const int32_t* value, uint32_t count) {
-    this->append("intArray");
-    fWriter->beginArray();
-    for (uint32_t i = 0; i < count; ++i) {
-        fWriter->appendS32(value[i]);
-    }
-    fWriter->endArray();
-}
-
-void SkJsonWriteBuffer::writeUInt(uint32_t value) {
-    this->append("uint"); fWriter->appendU32(value);
-}
-
-void SkJsonWriteBuffer::writeString(const char* value) {
-    this->append("string"); fWriter->appendString(value);
-}
-
-void SkJsonWriteBuffer::writeFlattenable(const SkFlattenable* flattenable) {
-    if (flattenable) {
-        this->append(flattenable->getTypeName());
-        fWriter->beginObject();
-        SkJsonWriteBuffer flattenableBuffer(fWriter, fUrlDataManager);
-        flattenable->flatten(flattenableBuffer);
-        fWriter->endObject();
-    } else {
-        this->append("flattenable"); fWriter->appendPointer(nullptr);
-    }
-}
-
-void SkJsonWriteBuffer::writeColor(SkColor color) {
-    this->append("color"); SkDrawCommand::MakeJsonColor(*fWriter, color);
-}
-
-void SkJsonWriteBuffer::writeColorArray(const SkColor* color, uint32_t count) {
-    this->append("colorArray");
-    fWriter->beginArray();
-    for (uint32_t i = 0; i < count; ++i) {
-        SkDrawCommand::MakeJsonColor(*fWriter, color[i]);
-    }
-    fWriter->endArray();
-}
-
-void SkJsonWriteBuffer::writeColor4f(const SkColor4f& color) {
-    this->append("color"); SkDrawCommand::MakeJsonColor4f(*fWriter, color);
-}
-
-void SkJsonWriteBuffer::writeColor4fArray(const SkColor4f* color, uint32_t count) {
-    this->append("colorArray");
-    fWriter->beginArray();
-    for (uint32_t i = 0; i < count; ++i) {
-        SkDrawCommand::MakeJsonColor4f(*fWriter, color[i]);
-    }
-    fWriter->endArray();
-}
-
-void SkJsonWriteBuffer::writePoint(const SkPoint& point) {
-    this->append("point"); SkDrawCommand::MakeJsonPoint(*fWriter, point);
-}
-
-void SkJsonWriteBuffer::writePoint3(const SkPoint3& point) {
-    this->append("point3"); SkDrawCommand::MakeJsonPoint3(*fWriter, point);
-}
-
-void SkJsonWriteBuffer::writePointArray(const SkPoint* point, uint32_t count) {
-    this->append("pointArray");
-    fWriter->beginArray();
-    for (uint32_t i = 0; i < count; ++i) {
-        SkDrawCommand::MakeJsonPoint(*fWriter, point[i]);
-    }
-    fWriter->endArray();
-}
-
-void SkJsonWriteBuffer::writeMatrix(const SkMatrix& matrix) {
-    this->append("matrix"); SkDrawCommand::MakeJsonMatrix(*fWriter, matrix);
-}
-
-void SkJsonWriteBuffer::writeIRect(const SkIRect& rect) {
-    this->append("irect"); SkDrawCommand::MakeJsonIRect(*fWriter, rect);
-}
-
-void SkJsonWriteBuffer::writeRect(const SkRect& rect) {
-    this->append("rect"); SkDrawCommand::MakeJsonRect(*fWriter, rect);
-}
-
-void SkJsonWriteBuffer::writeRegion(const SkRegion& region) {
-    this->append("region"); SkDrawCommand::MakeJsonRegion(*fWriter, region);
-}
-
-void SkJsonWriteBuffer::writePath(const SkPath& path) {
-    this->append("path"); SkDrawCommand::MakeJsonPath(*fWriter, path);
-}
-
-size_t SkJsonWriteBuffer::writeStream(SkStream* stream, size_t length) {
-    // Contents not supported
-    this->append("stream"); fWriter->appendU64(static_cast<uint64_t>(length));
-    return 0;
-}
-
-void SkJsonWriteBuffer::writeImage(const SkImage* image) {
-    this->append("image");
-    fWriter->beginObject();
-    SkDrawCommand::flatten(*image, *fWriter, *fUrlDataManager);
-    fWriter->endObject();
-}
-
-void SkJsonWriteBuffer::writeTypeface(SkTypeface* typeface) {
-    // Unsupported
-    this->append("typeface"); fWriter->appendPointer(typeface);
-}
-
-void SkJsonWriteBuffer::writePaint(const SkPaint& paint) {
-    this->append("paint"); SkDrawCommand::MakeJsonPaint(*fWriter, paint, *fUrlDataManager);
-}
diff --git a/tools/debugger/SkJsonWriteBuffer.h b/tools/debugger/SkJsonWriteBuffer.h
deleted file mode 100644
index f0ecac4..0000000
--- a/tools/debugger/SkJsonWriteBuffer.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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 SkJsonWriteBuffer_DEFINED
-#define SkJsonWriteBuffer_DEFINED
-
-#include "SkWriteBuffer.h"
-
-class SkJSONWriter;
-class SkPath;
-class UrlDataManager;
-
-class SkJsonWriteBuffer final : public SkWriteBuffer {
-public:
-    SkJsonWriteBuffer(SkJSONWriter* writer, UrlDataManager* urlDataManager)
-        : fUrlDataManager(urlDataManager)
-        , fWriter(writer)
-        , fCount(0) {}
-
-    void writePad32(const void* buffer, size_t bytes) override;
-    void writeByteArray(const void* data, size_t size) override;
-    void writeBool(bool value) override;
-    void writeScalar(SkScalar value) override;
-    void writeScalarArray(const SkScalar* value, uint32_t count) override;
-    void writeInt(int32_t value) override;
-    void writeIntArray(const int32_t* value, uint32_t count) override;
-    void writeUInt(uint32_t value) override;
-    void writeString(const char* value) override;
-
-    void writeFlattenable(const SkFlattenable* flattenable) override;
-    void writeColor(SkColor color) override;
-    void writeColorArray(const SkColor* color, uint32_t count) override;
-    void writeColor4f(const SkColor4f& color) override;
-    void writeColor4fArray(const SkColor4f* color, uint32_t count) override;
-    void writePoint(const SkPoint& point) override;
-    void writePointArray(const SkPoint* point, uint32_t count) override;
-    void writePoint3(const SkPoint3& point) override;
-    void writeMatrix(const SkMatrix& matrix) override;
-    void writeIRect(const SkIRect& rect) override;
-    void writeRect(const SkRect& rect) override;
-    void writeRegion(const SkRegion& region) override;
-    void writePath(const SkPath& path) override;
-    size_t writeStream(SkStream* stream, size_t length) override;
-    void writeImage(const SkImage*) override;
-    void writeTypeface(SkTypeface* typeface) override;
-    void writePaint(const SkPaint& paint) override;
-
-private:
-    void append(const char* type);
-
-    UrlDataManager* fUrlDataManager;
-    SkJSONWriter* fWriter;
-    int fCount;
-};
-
-#endif
diff --git a/tools/doxygen/Doxyfile b/tools/doxygen/Doxyfile
new file mode 100644
index 0000000..92bd12b
--- /dev/null
+++ b/tools/doxygen/Doxyfile
@@ -0,0 +1,2495 @@
+# Doxyfile 1.8.13
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
+# for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME           = "Skia"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER         =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "2D Graphics Library"
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO = logo.png
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = /tmp/doxygen
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS         = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES    = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF       = "The $name class" \
+                         "The $name widget" \
+                         "The $name file" \
+                         is \
+                         provides \
+                         specifies \
+                         contains \
+                         represents \
+                         a \
+                         an \
+                         the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC    = YES
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB  = YES
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES        = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF      = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = YES
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines.
+
+ALIASES                = "copy{1}=\htmlonly <script>document.write('<div style=\'display:none;visibility:hidden;\'>');</script> \endhtmlonly \image html \1 \n \htmlonly <script>document.write('</div>');</script> \endhtmlonly"
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST              =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
+# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
+# Fortran. In the later case the parser tries to guess whether the code is fixed
+# or free formatted code, this is the default for Fortran type files), VHDL. For
+# instance to make doxygen treat .inc files as Fortran files (default is PHP),
+# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT       = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 0.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS   = 2
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE      = 8
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL            = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC         = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO, these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES, upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC  = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES         =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS               = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC       = NO
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered.
+# The default value is: NO.
+
+WARN_AS_ERROR          = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT = ../../include/core ../../include/effects ../../include/gpu ../../include/pathops ../../third_party/skcms ./mainpage
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: http://www.gnu.org/software/libiconv) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
+# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
+# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
+
+FILE_PATTERNS          = *.c \
+                         *.cc \
+                         *.cxx \
+                         *.cpp \
+                         *.c++ \
+                         *.java \
+                         *.ii \
+                         *.ixx \
+                         *.ipp \
+                         *.i++ \
+                         *.inl \
+                         *.idl \
+                         *.ddl \
+                         *.odl \
+                         *.h \
+                         *.hh \
+                         *.hxx \
+                         *.hpp \
+                         *.h++ \
+                         *.cs \
+                         *.d \
+                         *.php \
+                         *.php4 \
+                         *.php5 \
+                         *.phtml \
+                         *.inc \
+                         *.m \
+                         *.markdown \
+                         *.md \
+                         *.mm \
+                         *.dox \
+                         *.py \
+                         *.pyw \
+                         *.f90 \
+                         *.f95 \
+                         *.f03 \
+                         *.f08 \
+                         *.f \
+                         *.for \
+                         *.tcl \
+                         *.vhd \
+                         *.vhdl \
+                         *.ucf \
+                         *.qsf
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE              = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH           = ./mainpage/markdeep
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS       = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS        = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see http://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS       = YES
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
+# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
+# cost of reduced performance. This can be particularly helpful with template
+# rich C++ code for which doxygen's built-in parser lacks the necessary type
+# information.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse-libclang=ON option for CMake.
+# The default value is: NO.
+
+CLANG_ASSISTED_PARSING = NO
+
+# If clang assisted parsing is enabled you can provide the compiler with command
+# line options that you would normally use when invoking the compiler. Note that
+# the include paths will already be set by doxygen for the files and directories
+# specified with INPUT and INCLUDE_PATH.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_OPTIONS          =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX     = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER            = ./footer.html
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET  = ./customdoxygen.css
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP         = NO
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: http://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET        = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP      = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE               =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION           =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the master .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI           = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING     =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW      = YES
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH         = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# http://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT         = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from http://www.mathjax.org before deployment.
+# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS     =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE       =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH    = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH        = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL       =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE        = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID     =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX         =  NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER           =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES      =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES, to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE        = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES     = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE      = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE        = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP        = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE    =
+
+# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
+# with syntax highlighting in the RTF output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_SOURCE_CODE        = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION          = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR             =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT             = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK       = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT         = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sf.net) file that captures the
+# structure of the code including all documentation. Note that this feature is
+# still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION        = YES
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF     = YES
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED = SkDEBUGCODE
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS        = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES         = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH               =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: YES.
+
+HAVE_DOT               = YES
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS        = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH    =  NO
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH          = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
+# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
+# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG        = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS           =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS           =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH      =
+
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE      =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH  =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP            = YES
+
diff --git a/tools/doxygen/ProdDoxyfile b/tools/doxygen/ProdDoxyfile
new file mode 100644
index 0000000..afcacff
--- /dev/null
+++ b/tools/doxygen/ProdDoxyfile
@@ -0,0 +1,5 @@
+# This config is used to generate the docs under continuous integration.
+
+@INCLUDE = Doxyfile
+
+OUTPUT_DIRECTORY       = /workspace/__doxygen_staging
diff --git a/tools/doxygen/README.md b/tools/doxygen/README.md
new file mode 100644
index 0000000..d01cecb
--- /dev/null
+++ b/tools/doxygen/README.md
@@ -0,0 +1,31 @@
+Doxygen
+=======
+
+To generate all the documentation run the following
+from this directory:
+
+    doxygen Doxyfile
+
+The resulting output goes to
+
+    /tmp/doxygen
+
+To view those file locally in your browser run:
+
+    cd /tmp/doxygen/html; python -m SimpleHTTPServer 8000
+
+and visit
+
+    http://localhost:8000
+
+If you want to have the documentation regenerated on every save then
+you can install `entr` and run the following from this directory:
+
+    find  ../../include/ ../../src/ . | entr doxygen ./Doxyfile
+
+Install
+-------
+
+For a linux desktop you can install the doxygen tool via:
+
+    sudo apt install doxygen
diff --git a/tools/doxygen/customdoxygen.css b/tools/doxygen/customdoxygen.css
new file mode 100644
index 0000000..749cfb1
--- /dev/null
+++ b/tools/doxygen/customdoxygen.css
@@ -0,0 +1,182 @@
+/* Skia overrides for doxygen CSS. */
+
+html {
+  --blue:          rgb(0,114,178);
+  --green:         rgb(0,158,115);
+  --red:           rgb(213,94,0);
+  --orange:        rgb(230,159,0);
+  --purple:        rgb(204,121,167);
+  --brown:         rgb(177,89,40);
+  --gray:          rgb(79,79,79);
+  --light-blue:    rgb(128,185,217);
+  --light-green:   rgb(128,207,185);
+  --light-red:     rgb(234,175,128);
+  --light-orange:  rgb(243,207,128);
+  --light-purple:  rgb(230,188,211);
+  --light-brown:   rgb(216,172,148);
+  --light-gray:    rgb(168,168,168);
+
+  --dark-blue:     rgb(0,65,101);
+  --dark-red:      rgb(156,44,8);
+
+  --white:         rgb(254,254,254);
+  --dark-white:    rgb(240,240,240);
+  --black:         rgb(10,10,10);
+}
+
+#titlearea {
+  /* background matches Skia logo. */
+  background: rgb(248,248,248);
+  color: var(--blue);
+}
+
+#main-nav .sm {
+  background-image: none;
+}
+
+h2.groupheader {
+  border-bottom: var(--gray);
+  color: var(--dark-blue);
+}
+
+div.qindex, div.navtab{
+  background-color: var(--light-gray);
+  border: 1px solid var(--light-blue);
+}
+
+a {
+  color: var(--blue);
+}
+
+.contents a:visited {
+  color: var(--blue);
+}
+
+a.qindexHL {
+  background-color: var(--light-gray);
+  color: var(--white);
+  border: 1px double var(--gray);
+}
+
+.contents a.qindexHL:visited {
+  color: var(--white);
+}
+
+a.code, a.code:visited, a.line, a.line:visited {
+  color: var(--blue);
+}
+
+a.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited {
+  color: var(--blue);
+}
+
+pre.fragment {
+  border: 1px solid var(--orange);
+  background-color: var(--dark-white);
+}
+
+div.fragment {
+  background-color: var(--dark-white);
+  border: 1px solid var(--orange);
+}
+
+span.lineno {
+  border-right: 2px solid var(--green);
+  background-color: var(-light-gray);
+}
+span.lineno a {
+  background-color: var(--light-gray);
+}
+
+span.lineno a:hover {
+  background-color: var(--light-gray);
+  color: var(--blue);
+}
+
+div.ah, span.ah {
+  background-color: var(--black);
+  color: var(--white);
+  border: solid thin var(--gray);
+  box-shadow: 2px 2px 3px var(light-gray);
+  background-image: none;
+}
+
+td.indexkey {
+  background-color: var(--light-gray);
+  border: 1px solid var(--orange);
+}
+
+td.indexvalue {
+  background-color: var(--light-gray);
+  border: 1px solid var(--orange);
+}
+
+tr.memlist {
+  background-color: var(--light-gray);
+}
+
+span.keyword {
+  color: var(--green);
+}
+
+span.keywordtype {
+  color: var(--brown);
+}
+
+span.keywordflow {
+  color: var(--brown);
+}
+
+span.comment {
+  color: var(--brown);
+}
+
+span.charliteral {
+  color: var(--green);
+}
+
+span.vhdldigit { 
+  color: var(--purple);
+}
+
+span.vhdlchar {
+  color: var(--black);
+}
+
+blockquote {
+  background-color: var(--light-gray);
+  border-left: 2px solid var(--gray);
+}
+
+.memtitle {
+  background-image: none;
+}
+
+.memdoc, dl.reflist dd {
+  background-image: none;
+}
+
+.paramname {
+  color: var(--dark-red);
+}
+
+.tabsearch {
+  background-image: none;
+}
+
+.navpath ul {
+  background-image: none;
+}
+
+.navpath li {
+  background-image: none;
+}
+
+.navpath li.navelem a:hover {
+  color: var(--blue)
+}
+
+.navpath li.footer {
+  background-image:none;
+}
+
diff --git a/tools/doxygen/footer.html b/tools/doxygen/footer.html
new file mode 100644
index 0000000..4997206
--- /dev/null
+++ b/tools/doxygen/footer.html
@@ -0,0 +1,24 @@
+<!-- HTML footer for doxygen 1.8.13-->
+<!-- start footer part -->
+<!--BEGIN GENERATE_TREEVIEW-->
+<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
+  <ul>
+    $navpath
+    <li class="footer">$generatedby
+    <a href="http://www.doxygen.org/index.html">
+    Doxygen</a> $doxygenversion on $date</li>
+  </ul>
+</div>
+<!--END GENERATE_TREEVIEW-->
+<!--BEGIN !GENERATE_TREEVIEW-->
+<hr class="footer"/><address class="footer"><small>
+$generatedby &#160;<a href="http://www.doxygen.org/index.html">
+  Doxygen
+</a> $doxygenversion on $date
+</small></address>
+<!--END !GENERATE_TREEVIEW-->
+
+<script>window.markdeepOptions = {mode: 'doxygen'};</script>
+<script src="https://casual-effects.com/markdeep/latest/markdeep.min.js"></script><script src="markdeep.min.js"></script>
+</body>
+</html>
diff --git a/tools/doxygen/logo.png b/tools/doxygen/logo.png
new file mode 100644
index 0000000..b250f7d
--- /dev/null
+++ b/tools/doxygen/logo.png
Binary files differ
diff --git a/tools/doxygen/mainpage/mainpage.dox b/tools/doxygen/mainpage/mainpage.dox
new file mode 100644
index 0000000..4748d8c
--- /dev/null
+++ b/tools/doxygen/mainpage/mainpage.dox
@@ -0,0 +1,24 @@
+/** \mainpage
+
+Skia is an open source 2D graphics library which provides common APIs that work
+across a variety of hardware and software platforms.  It serves as the graphics
+engine for Google Chrome and Chrome OS, Android, Mozilla Firefox and Firefox
+OS, and many other products.
+
+Skia is sponsored and managed by Google, but is available for use by anyone
+under the BSD Free Software License.  While engineering of the core components
+is done by the Skia development team, we consider contributions from any
+source.
+
+The site you are at is the API documentation for the Skia library. Other
+resources are:
+
+  - Canonical source tree:
+    [skia.googlesource.com/skia](https://skia.googlesource.com/skia).
+  - Issue tracker:
+    [bug.skia.org](https://bug.skia.org/).
+  - Discussion forum:
+    [skia-discuss@googlegroups.com](https://groups.google.com/forum/#!forum/skia-discuss).
+  - Skia Fiddle: [fiddle.skia.org](https://fiddle.skia.org/c/@skcanvas_paint).
+
+*/
diff --git a/tools/dump_record.cpp b/tools/dump_record.cpp
index 6e85e05..1c8ed0c 100644
--- a/tools/dump_record.cpp
+++ b/tools/dump_record.cpp
@@ -5,9 +5,9 @@
  * found in the LICENSE file.
  */
 
+#include "CommandLineFlags.h"
 #include "DumpRecord.h"
 #include "SkBitmap.h"
-#include "SkCommandLineFlags.h"
 #include "SkPicture.h"
 #include "SkPictureRecorder.h"
 #include "SkRecordDraw.h"
@@ -17,13 +17,14 @@
 
 #include <stdio.h>
 
-DEFINE_string2(skps, r, "", ".SKPs to dump.");
-DEFINE_string(match, "", "The usual filters on file names to dump.");
-DEFINE_bool2(optimize, O, false, "Run SkRecordOptimize before dumping.");
-DEFINE_bool(optimize2, false, "Run SkRecordOptimize2 before dumping.");
-DEFINE_int32(tile, 1000000000, "Simulated tile size.");
-DEFINE_bool(timeWithCommand, false, "If true, print time next to command, else in first column.");
-DEFINE_string2(write, w, "", "Write the (optimized) picture to the named file.");
+static DEFINE_string2(skps, r, "", ".SKPs to dump.");
+static DEFINE_string(match, "", "The usual filters on file names to dump.");
+static DEFINE_bool2(optimize, O, false, "Run SkRecordOptimize before dumping.");
+static DEFINE_bool(optimize2, false, "Run SkRecordOptimize2 before dumping.");
+static DEFINE_int(tile, 1000000000, "Simulated tile size.");
+static DEFINE_bool(timeWithCommand, false,
+                   "If true, print time next to command, else in first column.");
+static DEFINE_string2(write, w, "", "Write the (optimized) picture to the named file.");
 
 static void dump(const char* name, int w, int h, const SkRecord& record) {
     SkBitmap bitmap;
@@ -38,10 +39,10 @@
 }
 
 int main(int argc, char** argv) {
-    SkCommandLineFlags::Parse(argc, argv);
+    CommandLineFlags::Parse(argc, argv);
 
     for (int i = 0; i < FLAGS_skps.count(); i++) {
-        if (SkCommandLineFlags::ShouldSkip(FLAGS_match, FLAGS_skps[i])) {
+        if (CommandLineFlags::ShouldSkip(FLAGS_match, FLAGS_skps[i])) {
             continue;
         }
 
diff --git a/tools/fiddle/all_examples.cpp b/tools/fiddle/all_examples.cpp
new file mode 100644
index 0000000..e3966e8
--- /dev/null
+++ b/tools/fiddle/all_examples.cpp
@@ -0,0 +1,1085 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#include "../../docs/examples/Alpha_Constants_a.cpp"
+#include "../../docs/examples/Alpha_Constants_b.cpp"
+#include "../../docs/examples/Alpha_Type_Opaque.cpp"
+#include "../../docs/examples/Alpha_Type_Premul.cpp"
+#include "../../docs/examples/Alpha_Type_Unpremul.cpp"
+#include "../../docs/examples/Anti_Alias.cpp"
+#include "../../docs/examples/Arc.cpp"
+#include "../../docs/examples/AutoCanvasRestore_SkCanvas_star.cpp"
+#include "../../docs/examples/AutoCanvasRestore_restore.cpp"
+#include "../../docs/examples/Bitmap_012.cpp"
+#include "../../docs/examples/Bitmap_ComputeIsOpaque.cpp"
+#include "../../docs/examples/Bitmap_HeapAllocator_allocPixelRef.cpp"
+#include "../../docs/examples/Bitmap_allocN32Pixels.cpp"
+#include "../../docs/examples/Bitmap_allocPixels.cpp"
+#include "../../docs/examples/Bitmap_allocPixelsFlags.cpp"
+#include "../../docs/examples/Bitmap_allocPixels_2.cpp"
+#include "../../docs/examples/Bitmap_allocPixels_3.cpp"
+#include "../../docs/examples/Bitmap_allocPixels_4.cpp"
+#include "../../docs/examples/Bitmap_bounds.cpp"
+#include "../../docs/examples/Bitmap_bytesPerPixel.cpp"
+#include "../../docs/examples/Bitmap_colorSpace.cpp"
+#include "../../docs/examples/Bitmap_colorType.cpp"
+#include "../../docs/examples/Bitmap_computeByteSize.cpp"
+#include "../../docs/examples/Bitmap_copy_const_SkBitmap.cpp"
+#include "../../docs/examples/Bitmap_copy_operator.cpp"
+#include "../../docs/examples/Bitmap_dimensions.cpp"
+#include "../../docs/examples/Bitmap_drawsNothing.cpp"
+#include "../../docs/examples/Bitmap_empty.cpp"
+#include "../../docs/examples/Bitmap_empty_constructor.cpp"
+#include "../../docs/examples/Bitmap_erase.cpp"
+#include "../../docs/examples/Bitmap_eraseARGB.cpp"
+#include "../../docs/examples/Bitmap_eraseColor.cpp"
+#include "../../docs/examples/Bitmap_extractAlpha.cpp"
+#include "../../docs/examples/Bitmap_extractAlpha_2.cpp"
+#include "../../docs/examples/Bitmap_extractAlpha_3.cpp"
+#include "../../docs/examples/Bitmap_extractSubset.cpp"
+#include "../../docs/examples/Bitmap_getAddr.cpp"
+#include "../../docs/examples/Bitmap_getAddr16.cpp"
+#include "../../docs/examples/Bitmap_getAddr32.cpp"
+#include "../../docs/examples/Bitmap_getAddr8.cpp"
+#include "../../docs/examples/Bitmap_getBounds.cpp"
+#include "../../docs/examples/Bitmap_getBounds_2.cpp"
+#include "../../docs/examples/Bitmap_getColor.cpp"
+#include "../../docs/examples/Bitmap_getGenerationID.cpp"
+#include "../../docs/examples/Bitmap_getPixels.cpp"
+#include "../../docs/examples/Bitmap_getSubset.cpp"
+#include "../../docs/examples/Bitmap_height.cpp"
+#include "../../docs/examples/Bitmap_info.cpp"
+#include "../../docs/examples/Bitmap_installPixels.cpp"
+#include "../../docs/examples/Bitmap_installPixels_2.cpp"
+#include "../../docs/examples/Bitmap_installPixels_3.cpp"
+#include "../../docs/examples/Bitmap_isImmutable.cpp"
+#include "../../docs/examples/Bitmap_isNull.cpp"
+#include "../../docs/examples/Bitmap_isOpaque.cpp"
+#include "../../docs/examples/Bitmap_isVolatile.cpp"
+#include "../../docs/examples/Bitmap_move_SkBitmap.cpp"
+#include "../../docs/examples/Bitmap_move_operator.cpp"
+#include "../../docs/examples/Bitmap_notifyPixelsChanged.cpp"
+#include "../../docs/examples/Bitmap_peekPixels.cpp"
+#include "../../docs/examples/Bitmap_pixelRef.cpp"
+#include "../../docs/examples/Bitmap_pixelRefOrigin.cpp"
+#include "../../docs/examples/Bitmap_pixmap.cpp"
+#include "../../docs/examples/Bitmap_readPixels.cpp"
+#include "../../docs/examples/Bitmap_readPixels_2.cpp"
+#include "../../docs/examples/Bitmap_readPixels_3.cpp"
+#include "../../docs/examples/Bitmap_readyToDraw.cpp"
+#include "../../docs/examples/Bitmap_refColorSpace.cpp"
+#include "../../docs/examples/Bitmap_reset.cpp"
+#include "../../docs/examples/Bitmap_rowBytes.cpp"
+#include "../../docs/examples/Bitmap_rowBytesAsPixels.cpp"
+#include "../../docs/examples/Bitmap_setAlphaType.cpp"
+#include "../../docs/examples/Bitmap_setImmutable.cpp"
+#include "../../docs/examples/Bitmap_setInfo.cpp"
+#include "../../docs/examples/Bitmap_setIsVolatile.cpp"
+#include "../../docs/examples/Bitmap_setPixelRef.cpp"
+#include "../../docs/examples/Bitmap_setPixels.cpp"
+#include "../../docs/examples/Bitmap_shiftPerPixel.cpp"
+#include "../../docs/examples/Bitmap_swap.cpp"
+#include "../../docs/examples/Bitmap_tryAllocN32Pixels.cpp"
+#include "../../docs/examples/Bitmap_tryAllocPixels.cpp"
+#include "../../docs/examples/Bitmap_tryAllocPixelsFlags.cpp"
+#include "../../docs/examples/Bitmap_tryAllocPixels_2.cpp"
+#include "../../docs/examples/Bitmap_tryAllocPixels_3.cpp"
+#include "../../docs/examples/Bitmap_tryAllocPixels_4.cpp"
+#include "../../docs/examples/Bitmap_width.cpp"
+#include "../../docs/examples/Bitmap_writePixels.cpp"
+#include "../../docs/examples/Bitmap_writePixels_2.cpp"
+#include "../../docs/examples/BlendMode_Name.cpp"
+#include "../../docs/examples/Blend_Mode_Methods.cpp"
+#include "../../docs/examples/Canvas_129.cpp"
+#include "../../docs/examples/Canvas_MakeRasterDirect.cpp"
+#include "../../docs/examples/Canvas_MakeRasterDirectN32.cpp"
+#include "../../docs/examples/Canvas_PointMode.cpp"
+#include "../../docs/examples/Canvas_SaveLayerRec.cpp"
+#include "../../docs/examples/Canvas_SaveLayerRec_SaveLayerRec.cpp"
+#include "../../docs/examples/Canvas_SaveLayerRec_const_SkRect_star_const_SkPaint_star.cpp"
+#include "../../docs/examples/Canvas_SaveLayerRec_const_SkRect_star_const_SkPaint_star_const_SkImageFilter_star.cpp"
+#include "../../docs/examples/Canvas_SrcRectConstraint.cpp"
+#include "../../docs/examples/Canvas_accessTopLayerPixels_a.cpp"
+#include "../../docs/examples/Canvas_accessTopLayerPixels_b.cpp"
+#include "../../docs/examples/Canvas_accessTopRasterHandle.cpp"
+#include "../../docs/examples/Canvas_clear.cpp"
+#include "../../docs/examples/Canvas_clipPath.cpp"
+#include "../../docs/examples/Canvas_clipPath_2.cpp"
+#include "../../docs/examples/Canvas_clipPath_3.cpp"
+#include "../../docs/examples/Canvas_clipRRect.cpp"
+#include "../../docs/examples/Canvas_clipRRect_2.cpp"
+#include "../../docs/examples/Canvas_clipRRect_3.cpp"
+#include "../../docs/examples/Canvas_clipRect.cpp"
+#include "../../docs/examples/Canvas_clipRect_2.cpp"
+#include "../../docs/examples/Canvas_clipRect_3.cpp"
+#include "../../docs/examples/Canvas_clipRegion.cpp"
+#include "../../docs/examples/Canvas_concat.cpp"
+#include "../../docs/examples/Canvas_const_SkBitmap_const_SkSurfaceProps.cpp"
+#include "../../docs/examples/Canvas_copy_const_SkBitmap.cpp"
+#include "../../docs/examples/Canvas_destructor.cpp"
+#include "../../docs/examples/Canvas_drawAnnotation_2.cpp"
+#include "../../docs/examples/Canvas_drawArc_a.cpp"
+#include "../../docs/examples/Canvas_drawArc_b.cpp"
+#include "../../docs/examples/Canvas_drawAtlas.cpp"
+#include "../../docs/examples/Canvas_drawAtlas_2.cpp"
+#include "../../docs/examples/Canvas_drawAtlas_3.cpp"
+#include "../../docs/examples/Canvas_drawAtlas_4.cpp"
+#include "../../docs/examples/Canvas_drawBitmap.cpp"
+#include "../../docs/examples/Canvas_drawBitmapLattice.cpp"
+#include "../../docs/examples/Canvas_drawBitmapNine.cpp"
+#include "../../docs/examples/Canvas_drawBitmapRect.cpp"
+#include "../../docs/examples/Canvas_drawBitmapRect_2.cpp"
+#include "../../docs/examples/Canvas_drawBitmapRect_3.cpp"
+#include "../../docs/examples/Canvas_drawCircle.cpp"
+#include "../../docs/examples/Canvas_drawCircle_2.cpp"
+#include "../../docs/examples/Canvas_drawColor.cpp"
+#include "../../docs/examples/Canvas_drawDRRect_a.cpp"
+#include "../../docs/examples/Canvas_drawDRRect_b.cpp"
+#include "../../docs/examples/Canvas_drawDrawable.cpp"
+#include "../../docs/examples/Canvas_drawDrawable_2.cpp"
+#include "../../docs/examples/Canvas_drawIRect.cpp"
+#include "../../docs/examples/Canvas_drawImage.cpp"
+#include "../../docs/examples/Canvas_drawImageNine.cpp"
+#include "../../docs/examples/Canvas_drawImageNine_2.cpp"
+#include "../../docs/examples/Canvas_drawImageRect.cpp"
+#include "../../docs/examples/Canvas_drawImageRect_2.cpp"
+#include "../../docs/examples/Canvas_drawImageRect_3.cpp"
+#include "../../docs/examples/Canvas_drawImageRect_4.cpp"
+#include "../../docs/examples/Canvas_drawImageRect_5.cpp"
+#include "../../docs/examples/Canvas_drawImageRect_6.cpp"
+#include "../../docs/examples/Canvas_drawImage_2.cpp"
+#include "../../docs/examples/Canvas_drawLine.cpp"
+#include "../../docs/examples/Canvas_drawLine_2.cpp"
+#include "../../docs/examples/Canvas_drawOval.cpp"
+#include "../../docs/examples/Canvas_drawPaint.cpp"
+#include "../../docs/examples/Canvas_drawPatch.cpp"
+#include "../../docs/examples/Canvas_drawPatch_2_a.cpp"
+#include "../../docs/examples/Canvas_drawPatch_2_b.cpp"
+#include "../../docs/examples/Canvas_drawPath.cpp"
+#include "../../docs/examples/Canvas_drawPicture_2.cpp"
+#include "../../docs/examples/Canvas_drawPicture_3.cpp"
+#include "../../docs/examples/Canvas_drawPicture_4.cpp"
+#include "../../docs/examples/Canvas_drawPoint.cpp"
+#include "../../docs/examples/Canvas_drawPoint_2.cpp"
+#include "../../docs/examples/Canvas_drawPoints.cpp"
+#include "../../docs/examples/Canvas_drawPosText.cpp"
+#include "../../docs/examples/Canvas_drawPosTextH.cpp"
+#include "../../docs/examples/Canvas_drawRRect.cpp"
+#include "../../docs/examples/Canvas_drawRect.cpp"
+#include "../../docs/examples/Canvas_drawRegion.cpp"
+#include "../../docs/examples/Canvas_drawRoundRect.cpp"
+#include "../../docs/examples/Canvas_drawString.cpp"
+#include "../../docs/examples/Canvas_drawString_2.cpp"
+#include "../../docs/examples/Canvas_drawText.cpp"
+#include "../../docs/examples/Canvas_drawTextBlob.cpp"
+#include "../../docs/examples/Canvas_drawTextBlob_2.cpp"
+#include "../../docs/examples/Canvas_drawTextRSXform.cpp"
+#include "../../docs/examples/Canvas_drawVertices.cpp"
+#include "../../docs/examples/Canvas_drawVertices_2.cpp"
+#include "../../docs/examples/Canvas_empty_constructor.cpp"
+#include "../../docs/examples/Canvas_getBaseLayerSize.cpp"
+#include "../../docs/examples/Canvas_getDeviceClipBounds.cpp"
+#include "../../docs/examples/Canvas_getDeviceClipBounds_2.cpp"
+#include "../../docs/examples/Canvas_getGrContext.cpp"
+#include "../../docs/examples/Canvas_getLocalClipBounds.cpp"
+#include "../../docs/examples/Canvas_getLocalClipBounds_2.cpp"
+#include "../../docs/examples/Canvas_getProps.cpp"
+#include "../../docs/examples/Canvas_getSaveCount.cpp"
+#include "../../docs/examples/Canvas_getTotalMatrix.cpp"
+#include "../../docs/examples/Canvas_imageInfo.cpp"
+#include "../../docs/examples/Canvas_int_int_const_SkSurfaceProps_star.cpp"
+#include "../../docs/examples/Canvas_isClipEmpty.cpp"
+#include "../../docs/examples/Canvas_isClipRect.cpp"
+#include "../../docs/examples/Canvas_kInitWithPrevious_SaveLayerFlag.cpp"
+#include "../../docs/examples/Canvas_makeSurface.cpp"
+#include "../../docs/examples/Canvas_peekPixels.cpp"
+#include "../../docs/examples/Canvas_quickReject.cpp"
+#include "../../docs/examples/Canvas_quickReject_2.cpp"
+#include "../../docs/examples/Canvas_readPixels_2.cpp"
+#include "../../docs/examples/Canvas_readPixels_3.cpp"
+#include "../../docs/examples/Canvas_readPixels_a.cpp"
+#include "../../docs/examples/Canvas_readPixels_b.cpp"
+#include "../../docs/examples/Canvas_resetMatrix.cpp"
+#include "../../docs/examples/Canvas_restore.cpp"
+#include "../../docs/examples/Canvas_restoreToCount.cpp"
+#include "../../docs/examples/Canvas_rotate.cpp"
+#include "../../docs/examples/Canvas_rotate_2.cpp"
+#include "../../docs/examples/Canvas_save.cpp"
+#include "../../docs/examples/Canvas_saveLayer.cpp"
+#include "../../docs/examples/Canvas_saveLayerAlpha.cpp"
+#include "../../docs/examples/Canvas_saveLayerPreserveLCDTextRequests.cpp"
+#include "../../docs/examples/Canvas_saveLayer_2.cpp"
+#include "../../docs/examples/Canvas_saveLayer_3.cpp"
+#include "../../docs/examples/Canvas_scale.cpp"
+#include "../../docs/examples/Canvas_setMatrix.cpp"
+#include "../../docs/examples/Canvas_skew.cpp"
+#include "../../docs/examples/Canvas_translate.cpp"
+#include "../../docs/examples/Canvas_writePixels.cpp"
+#include "../../docs/examples/Canvas_writePixels_2.cpp"
+#include "../../docs/examples/Clear.cpp"
+#include "../../docs/examples/Clip.cpp"
+#include "../../docs/examples/Color.cpp"
+#include "../../docs/examples/ColorGetA.cpp"
+#include "../../docs/examples/ColorGetB.cpp"
+#include "../../docs/examples/ColorGetG.cpp"
+#include "../../docs/examples/ColorGetR.cpp"
+#include "../../docs/examples/ColorSetA.cpp"
+#include "../../docs/examples/ColorSetARGB.cpp"
+#include "../../docs/examples/ColorSetRGB.cpp"
+#include "../../docs/examples/ColorToHSV.cpp"
+#include "../../docs/examples/ColorTypeBytesPerPixel.cpp"
+#include "../../docs/examples/ColorTypeIsAlwaysOpaque.cpp"
+#include "../../docs/examples/ColorTypeValidateAlphaType.cpp"
+#include "../../docs/examples/Color_Burn.cpp"
+#include "../../docs/examples/Color_Constants_a.cpp"
+#include "../../docs/examples/Color_Constants_b.cpp"
+#include "../../docs/examples/Color_Constants_c.cpp"
+#include "../../docs/examples/Color_Constants_d.cpp"
+#include "../../docs/examples/Color_Dodge.cpp"
+#include "../../docs/examples/Color_Filter_Methods.cpp"
+#include "../../docs/examples/Color_Methods.cpp"
+#include "../../docs/examples/Color_Type_ARGB_4444.cpp"
+#include "../../docs/examples/Color_Type_Alpha_8.cpp"
+#include "../../docs/examples/Color_Type_BGRA_8888.cpp"
+#include "../../docs/examples/Color_Type_Gray_8.cpp"
+#include "../../docs/examples/Color_Type_RGBA_1010102.cpp"
+#include "../../docs/examples/Color_Type_RGBA_8888.cpp"
+#include "../../docs/examples/Color_Type_RGBA_F16.cpp"
+#include "../../docs/examples/Color_Type_RGB_101010.cpp"
+#include "../../docs/examples/Color_Type_RGB_565.cpp"
+#include "../../docs/examples/Color_Type_RGB_888.cpp"
+#include "../../docs/examples/Conic_Weight_a.cpp"
+#include "../../docs/examples/Conic_Weight_b.cpp"
+#include "../../docs/examples/Conic_Weight_c.cpp"
+#include "../../docs/examples/Cubic.cpp"
+#include "../../docs/examples/Darken.cpp"
+#include "../../docs/examples/Device_Text.cpp"
+#include "../../docs/examples/Difference.cpp"
+#include "../../docs/examples/Dither_a.cpp"
+#include "../../docs/examples/Dither_b.cpp"
+#include "../../docs/examples/Draw_Looper_Methods.cpp"
+#include "../../docs/examples/Dst.cpp"
+#include "../../docs/examples/Dst_Atop.cpp"
+#include "../../docs/examples/Dst_In.cpp"
+#include "../../docs/examples/Dst_Out.cpp"
+#include "../../docs/examples/Dst_Over.cpp"
+#include "../../docs/examples/Exclusion.cpp"
+#include "../../docs/examples/Fake_Bold.cpp"
+#include "../../docs/examples/Filter_Quality_Methods.cpp"
+#include "../../docs/examples/Font_breakText.cpp"
+#include "../../docs/examples/HSVToColor.cpp"
+#include "../../docs/examples/HSVToColor_2.cpp"
+#include "../../docs/examples/Hard_Light.cpp"
+#include "../../docs/examples/Hue.cpp"
+#include "../../docs/examples/IPoint_Make.cpp"
+#include "../../docs/examples/IPoint_add_operator.cpp"
+#include "../../docs/examples/IPoint_addto_operator.cpp"
+#include "../../docs/examples/IPoint_equal_operator.cpp"
+#include "../../docs/examples/IPoint_equals.cpp"
+#include "../../docs/examples/IPoint_isZero.cpp"
+#include "../../docs/examples/IPoint_minus_operator.cpp"
+#include "../../docs/examples/IPoint_notequal_operator.cpp"
+#include "../../docs/examples/IPoint_set.cpp"
+#include "../../docs/examples/IPoint_subtract_operator.cpp"
+#include "../../docs/examples/IPoint_subtractfrom_operator.cpp"
+#include "../../docs/examples/IPoint_x.cpp"
+#include "../../docs/examples/IPoint_y.cpp"
+#include "../../docs/examples/IRect_EmptyIRect.cpp"
+#include "../../docs/examples/IRect_Intersects.cpp"
+#include "../../docs/examples/IRect_IntersectsNoEmptyCheck.cpp"
+#include "../../docs/examples/IRect_MakeEmpty.cpp"
+#include "../../docs/examples/IRect_MakeLTRB.cpp"
+#include "../../docs/examples/IRect_MakeSize.cpp"
+#include "../../docs/examples/IRect_MakeWH.cpp"
+#include "../../docs/examples/IRect_MakeXYWH.cpp"
+#include "../../docs/examples/IRect_adjust.cpp"
+#include "../../docs/examples/IRect_bottom.cpp"
+#include "../../docs/examples/IRect_contains.cpp"
+#include "../../docs/examples/IRect_containsNoEmptyCheck.cpp"
+#include "../../docs/examples/IRect_containsNoEmptyCheck_2.cpp"
+#include "../../docs/examples/IRect_contains_2.cpp"
+#include "../../docs/examples/IRect_contains_3.cpp"
+#include "../../docs/examples/IRect_contains_4.cpp"
+#include "../../docs/examples/IRect_equal_operator.cpp"
+#include "../../docs/examples/IRect_height.cpp"
+#include "../../docs/examples/IRect_height64.cpp"
+#include "../../docs/examples/IRect_inset.cpp"
+#include "../../docs/examples/IRect_intersect.cpp"
+#include "../../docs/examples/IRect_intersectNoEmptyCheck.cpp"
+#include "../../docs/examples/IRect_intersect_2.cpp"
+#include "../../docs/examples/IRect_intersect_3.cpp"
+#include "../../docs/examples/IRect_isEmpty.cpp"
+#include "../../docs/examples/IRect_isEmpty64.cpp"
+#include "../../docs/examples/IRect_join.cpp"
+#include "../../docs/examples/IRect_join_2.cpp"
+#include "../../docs/examples/IRect_left.cpp"
+#include "../../docs/examples/IRect_makeInset.cpp"
+#include "../../docs/examples/IRect_makeOffset.cpp"
+#include "../../docs/examples/IRect_makeOutset.cpp"
+#include "../../docs/examples/IRect_makeSorted.cpp"
+#include "../../docs/examples/IRect_notequal_operator.cpp"
+#include "../../docs/examples/IRect_offset.cpp"
+#include "../../docs/examples/IRect_offsetTo.cpp"
+#include "../../docs/examples/IRect_offset_2.cpp"
+#include "../../docs/examples/IRect_outset.cpp"
+#include "../../docs/examples/IRect_right.cpp"
+#include "../../docs/examples/IRect_set.cpp"
+#include "../../docs/examples/IRect_setEmpty.cpp"
+#include "../../docs/examples/IRect_setLTRB.cpp"
+#include "../../docs/examples/IRect_setXYWH.cpp"
+#include "../../docs/examples/IRect_size.cpp"
+#include "../../docs/examples/IRect_sort.cpp"
+#include "../../docs/examples/IRect_top.cpp"
+#include "../../docs/examples/IRect_width.cpp"
+#include "../../docs/examples/IRect_width64.cpp"
+#include "../../docs/examples/IRect_x.cpp"
+#include "../../docs/examples/IRect_y.cpp"
+#include "../../docs/examples/ImageInfo_ByteSizeOverflowed.cpp"
+#include "../../docs/examples/ImageInfo_Make.cpp"
+#include "../../docs/examples/ImageInfo_MakeA8.cpp"
+#include "../../docs/examples/ImageInfo_MakeN32.cpp"
+#include "../../docs/examples/ImageInfo_MakeN32Premul.cpp"
+#include "../../docs/examples/ImageInfo_MakeN32Premul_2.cpp"
+#include "../../docs/examples/ImageInfo_MakeS32.cpp"
+#include "../../docs/examples/ImageInfo_MakeUnknown.cpp"
+#include "../../docs/examples/ImageInfo_MakeUnknown_2.cpp"
+#include "../../docs/examples/ImageInfo_alphaType.cpp"
+#include "../../docs/examples/ImageInfo_bounds.cpp"
+#include "../../docs/examples/ImageInfo_bytesPerPixel.cpp"
+#include "../../docs/examples/ImageInfo_colorSpace.cpp"
+#include "../../docs/examples/ImageInfo_colorType.cpp"
+#include "../../docs/examples/ImageInfo_computeByteSize.cpp"
+#include "../../docs/examples/ImageInfo_computeMinByteSize.cpp"
+#include "../../docs/examples/ImageInfo_computeOffset.cpp"
+#include "../../docs/examples/ImageInfo_dimensions.cpp"
+#include "../../docs/examples/ImageInfo_empty_constructor.cpp"
+#include "../../docs/examples/ImageInfo_equal1_operator.cpp"
+#include "../../docs/examples/ImageInfo_gammaCloseToSRGB.cpp"
+#include "../../docs/examples/ImageInfo_height.cpp"
+#include "../../docs/examples/ImageInfo_isEmpty.cpp"
+#include "../../docs/examples/ImageInfo_isOpaque.cpp"
+#include "../../docs/examples/ImageInfo_makeAlphaType.cpp"
+#include "../../docs/examples/ImageInfo_makeColorSpace.cpp"
+#include "../../docs/examples/ImageInfo_makeColorType.cpp"
+#include "../../docs/examples/ImageInfo_makeWH.cpp"
+#include "../../docs/examples/ImageInfo_minRowBytes.cpp"
+#include "../../docs/examples/ImageInfo_minRowBytes64.cpp"
+#include "../../docs/examples/ImageInfo_notequal1_operator.cpp"
+#include "../../docs/examples/ImageInfo_refColorSpace.cpp"
+#include "../../docs/examples/ImageInfo_reset.cpp"
+#include "../../docs/examples/ImageInfo_shiftPerPixel.cpp"
+#include "../../docs/examples/ImageInfo_validRowBytes.cpp"
+#include "../../docs/examples/ImageInfo_width.cpp"
+#include "../../docs/examples/Image_Filter_Methods.cpp"
+#include "../../docs/examples/Image_MakeBackendTextureFromSkImage.cpp"
+#include "../../docs/examples/Image_MakeCrossContextFromEncoded.cpp"
+#include "../../docs/examples/Image_MakeCrossContextFromPixmap.cpp"
+#include "../../docs/examples/Image_MakeFromAdoptedTexture.cpp"
+#include "../../docs/examples/Image_MakeFromBitmap.cpp"
+#include "../../docs/examples/Image_MakeFromEncoded.cpp"
+#include "../../docs/examples/Image_MakeFromGenerator.cpp"
+#include "../../docs/examples/Image_MakeFromPicture.cpp"
+#include "../../docs/examples/Image_MakeFromRaster.cpp"
+#include "../../docs/examples/Image_MakeFromTexture.cpp"
+#include "../../docs/examples/Image_MakeFromTexture_2.cpp"
+#include "../../docs/examples/Image_MakeRasterCopy.cpp"
+#include "../../docs/examples/Image_MakeRasterData.cpp"
+#include "../../docs/examples/Image_alphaType.cpp"
+#include "../../docs/examples/Image_bounds.cpp"
+#include "../../docs/examples/Image_colorSpace.cpp"
+#include "../../docs/examples/Image_colorType.cpp"
+#include "../../docs/examples/Image_dimensions.cpp"
+#include "../../docs/examples/Image_encodeToData.cpp"
+#include "../../docs/examples/Image_encodeToData_2.cpp"
+#include "../../docs/examples/Image_getBackendTexture.cpp"
+#include "../../docs/examples/Image_height.cpp"
+#include "../../docs/examples/Image_isAlphaOnly.cpp"
+#include "../../docs/examples/Image_isLazyGenerated_a.cpp"
+#include "../../docs/examples/Image_isLazyGenerated_b.cpp"
+#include "../../docs/examples/Image_isOpaque.cpp"
+#include "../../docs/examples/Image_isTextureBacked.cpp"
+#include "../../docs/examples/Image_isValid.cpp"
+#include "../../docs/examples/Image_makeColorSpace.cpp"
+#include "../../docs/examples/Image_makeNonTextureImage.cpp"
+#include "../../docs/examples/Image_makeRasterImage.cpp"
+#include "../../docs/examples/Image_makeShader.cpp"
+#include "../../docs/examples/Image_makeShader_2.cpp"
+#include "../../docs/examples/Image_makeSubset.cpp"
+#include "../../docs/examples/Image_makeTextureImage.cpp"
+#include "../../docs/examples/Image_makeWithFilter.cpp"
+#include "../../docs/examples/Image_peekPixels.cpp"
+#include "../../docs/examples/Image_readPixels.cpp"
+#include "../../docs/examples/Image_readPixels_2.cpp"
+#include "../../docs/examples/Image_refColorSpace.cpp"
+#include "../../docs/examples/Image_refEncodedData.cpp"
+#include "../../docs/examples/Image_scalePixels.cpp"
+#include "../../docs/examples/Image_uniqueID.cpp"
+#include "../../docs/examples/Image_width.cpp"
+#include "../../docs/examples/Lighten.cpp"
+#include "../../docs/examples/Luminosity.cpp"
+#include "../../docs/examples/Mask_Filter_Methods.cpp"
+#include "../../docs/examples/Matrix_063.cpp"
+#include "../../docs/examples/Matrix_Concat.cpp"
+#include "../../docs/examples/Matrix_I.cpp"
+#include "../../docs/examples/Matrix_InvalidMatrix.cpp"
+#include "../../docs/examples/Matrix_MakeAll.cpp"
+#include "../../docs/examples/Matrix_MakeRectToRect.cpp"
+#include "../../docs/examples/Matrix_MakeScale.cpp"
+#include "../../docs/examples/Matrix_MakeScale_2.cpp"
+#include "../../docs/examples/Matrix_MakeTrans.cpp"
+#include "../../docs/examples/Matrix_ScaleToFit.cpp"
+#include "../../docs/examples/Matrix_SetAffineIdentity.cpp"
+#include "../../docs/examples/Matrix_TypeMask.cpp"
+#include "../../docs/examples/Matrix_array_operator.cpp"
+#include "../../docs/examples/Matrix_asAffine.cpp"
+#include "../../docs/examples/Matrix_cheapEqualTo.cpp"
+#include "../../docs/examples/Matrix_decomposeScale.cpp"
+#include "../../docs/examples/Matrix_dirtyMatrixTypeCache.cpp"
+#include "../../docs/examples/Matrix_dump.cpp"
+#include "../../docs/examples/Matrix_equal_operator.cpp"
+#include "../../docs/examples/Matrix_fixedStepInX.cpp"
+#include "../../docs/examples/Matrix_get.cpp"
+#include "../../docs/examples/Matrix_get9.cpp"
+#include "../../docs/examples/Matrix_getMaxScale.cpp"
+#include "../../docs/examples/Matrix_getMinMaxScales.cpp"
+#include "../../docs/examples/Matrix_getMinScale.cpp"
+#include "../../docs/examples/Matrix_getPerspX.cpp"
+#include "../../docs/examples/Matrix_getPerspY.cpp"
+#include "../../docs/examples/Matrix_getScaleX.cpp"
+#include "../../docs/examples/Matrix_getScaleY.cpp"
+#include "../../docs/examples/Matrix_getSkewX.cpp"
+#include "../../docs/examples/Matrix_getSkewY.cpp"
+#include "../../docs/examples/Matrix_getTranslateX.cpp"
+#include "../../docs/examples/Matrix_getTranslateY.cpp"
+#include "../../docs/examples/Matrix_getType.cpp"
+#include "../../docs/examples/Matrix_hasPerspective.cpp"
+#include "../../docs/examples/Matrix_invert.cpp"
+#include "../../docs/examples/Matrix_isFinite.cpp"
+#include "../../docs/examples/Matrix_isFixedStepInX.cpp"
+#include "../../docs/examples/Matrix_isIdentity.cpp"
+#include "../../docs/examples/Matrix_isScaleTranslate.cpp"
+#include "../../docs/examples/Matrix_isSimilarity.cpp"
+#include "../../docs/examples/Matrix_isTranslate.cpp"
+#include "../../docs/examples/Matrix_mapHomogeneousPoints.cpp"
+#include "../../docs/examples/Matrix_mapPoints.cpp"
+#include "../../docs/examples/Matrix_mapPoints_2.cpp"
+#include "../../docs/examples/Matrix_mapRadius.cpp"
+#include "../../docs/examples/Matrix_mapRect.cpp"
+#include "../../docs/examples/Matrix_mapRectScaleTranslate.cpp"
+#include "../../docs/examples/Matrix_mapRectToQuad.cpp"
+#include "../../docs/examples/Matrix_mapRect_2.cpp"
+#include "../../docs/examples/Matrix_mapRect_3.cpp"
+#include "../../docs/examples/Matrix_mapVector.cpp"
+#include "../../docs/examples/Matrix_mapVector_2.cpp"
+#include "../../docs/examples/Matrix_mapVectors.cpp"
+#include "../../docs/examples/Matrix_mapVectors_2.cpp"
+#include "../../docs/examples/Matrix_mapXY.cpp"
+#include "../../docs/examples/Matrix_mapXY_2.cpp"
+#include "../../docs/examples/Matrix_notequal_operator.cpp"
+#include "../../docs/examples/Matrix_postConcat.cpp"
+#include "../../docs/examples/Matrix_postRotate.cpp"
+#include "../../docs/examples/Matrix_postRotate_2.cpp"
+#include "../../docs/examples/Matrix_postScale.cpp"
+#include "../../docs/examples/Matrix_postScale_2.cpp"
+#include "../../docs/examples/Matrix_postSkew.cpp"
+#include "../../docs/examples/Matrix_postSkew_2.cpp"
+#include "../../docs/examples/Matrix_postTranslate.cpp"
+#include "../../docs/examples/Matrix_preConcat.cpp"
+#include "../../docs/examples/Matrix_preRotate.cpp"
+#include "../../docs/examples/Matrix_preRotate_2.cpp"
+#include "../../docs/examples/Matrix_preScale.cpp"
+#include "../../docs/examples/Matrix_preScale_2.cpp"
+#include "../../docs/examples/Matrix_preSkew.cpp"
+#include "../../docs/examples/Matrix_preSkew_2.cpp"
+#include "../../docs/examples/Matrix_preTranslate.cpp"
+#include "../../docs/examples/Matrix_preservesAxisAlignment.cpp"
+#include "../../docs/examples/Matrix_preservesRightAngles.cpp"
+#include "../../docs/examples/Matrix_rectStaysRect.cpp"
+#include "../../docs/examples/Matrix_reset.cpp"
+#include "../../docs/examples/Matrix_set.cpp"
+#include "../../docs/examples/Matrix_set9.cpp"
+#include "../../docs/examples/Matrix_setAffine.cpp"
+#include "../../docs/examples/Matrix_setAll.cpp"
+#include "../../docs/examples/Matrix_setConcat.cpp"
+#include "../../docs/examples/Matrix_setIdentity.cpp"
+#include "../../docs/examples/Matrix_setPerspX.cpp"
+#include "../../docs/examples/Matrix_setPerspY.cpp"
+#include "../../docs/examples/Matrix_setPolyToPoly.cpp"
+#include "../../docs/examples/Matrix_setRSXform.cpp"
+#include "../../docs/examples/Matrix_setRectToRect.cpp"
+#include "../../docs/examples/Matrix_setRotate.cpp"
+#include "../../docs/examples/Matrix_setRotate_2.cpp"
+#include "../../docs/examples/Matrix_setScale.cpp"
+#include "../../docs/examples/Matrix_setScaleTranslate.cpp"
+#include "../../docs/examples/Matrix_setScaleX.cpp"
+#include "../../docs/examples/Matrix_setScaleY.cpp"
+#include "../../docs/examples/Matrix_setScale_2.cpp"
+#include "../../docs/examples/Matrix_setSinCos.cpp"
+#include "../../docs/examples/Matrix_setSinCos_2.cpp"
+#include "../../docs/examples/Matrix_setSkew.cpp"
+#include "../../docs/examples/Matrix_setSkewX.cpp"
+#include "../../docs/examples/Matrix_setSkewY.cpp"
+#include "../../docs/examples/Matrix_setSkew_2.cpp"
+#include "../../docs/examples/Matrix_setTranslate.cpp"
+#include "../../docs/examples/Matrix_setTranslateX.cpp"
+#include "../../docs/examples/Matrix_setTranslateY.cpp"
+#include "../../docs/examples/Matrix_setTranslate_2.cpp"
+#include "../../docs/examples/MemberIndex.cpp"
+#include "../../docs/examples/Miter_Limit.cpp"
+#include "../../docs/examples/Modulate.cpp"
+#include "../../docs/examples/Multiply.cpp"
+#include "../../docs/examples/Overlay.cpp"
+#include "../../docs/examples/Paint_053.cpp"
+#include "../../docs/examples/Paint_057.cpp"
+#include "../../docs/examples/Paint_containsText.cpp"
+#include "../../docs/examples/Paint_copy_const_SkPaint.cpp"
+#include "../../docs/examples/Paint_copy_operator.cpp"
+#include "../../docs/examples/Paint_countText.cpp"
+#include "../../docs/examples/Paint_empty_constructor.cpp"
+#include "../../docs/examples/Paint_equal_operator.cpp"
+#include "../../docs/examples/Paint_getAlpha.cpp"
+#include "../../docs/examples/Paint_getBlendMode.cpp"
+#include "../../docs/examples/Paint_getColor.cpp"
+#include "../../docs/examples/Paint_getColor4f.cpp"
+#include "../../docs/examples/Paint_getColorFilter.cpp"
+#include "../../docs/examples/Paint_getDrawLooper.cpp"
+#include "../../docs/examples/Paint_getFillPath.cpp"
+#include "../../docs/examples/Paint_getFillPath_2.cpp"
+#include "../../docs/examples/Paint_getFilterQuality.cpp"
+#include "../../docs/examples/Paint_getFlags.cpp"
+#include "../../docs/examples/Paint_getFontMetrics.cpp"
+#include "../../docs/examples/Paint_getFontSpacing.cpp"
+#include "../../docs/examples/Paint_getHash.cpp"
+#include "../../docs/examples/Paint_getHinting.cpp"
+#include "../../docs/examples/Paint_getImageFilter.cpp"
+#include "../../docs/examples/Paint_getMaskFilter.cpp"
+#include "../../docs/examples/Paint_getPathEffect.cpp"
+#include "../../docs/examples/Paint_getPosTextPath.cpp"
+#include "../../docs/examples/Paint_getShader.cpp"
+#include "../../docs/examples/Paint_getStrokeCap.cpp"
+#include "../../docs/examples/Paint_getStrokeJoin.cpp"
+#include "../../docs/examples/Paint_getStrokeMiter.cpp"
+#include "../../docs/examples/Paint_getStrokeWidth.cpp"
+#include "../../docs/examples/Paint_getStyle.cpp"
+#include "../../docs/examples/Paint_getTextEncoding.cpp"
+#include "../../docs/examples/Paint_getTextPath.cpp"
+#include "../../docs/examples/Paint_getTextScaleX.cpp"
+#include "../../docs/examples/Paint_getTextSize.cpp"
+#include "../../docs/examples/Paint_getTextSkewX.cpp"
+#include "../../docs/examples/Paint_getTextWidths.cpp"
+#include "../../docs/examples/Paint_getTypeface.cpp"
+#include "../../docs/examples/Paint_isAntiAlias.cpp"
+#include "../../docs/examples/Paint_isAutohinted.cpp"
+#include "../../docs/examples/Paint_isDither.cpp"
+#include "../../docs/examples/Paint_isEmbeddedBitmapText.cpp"
+#include "../../docs/examples/Paint_isFakeBoldText.cpp"
+#include "../../docs/examples/Paint_isLCDRenderText.cpp"
+#include "../../docs/examples/Paint_isLinearText.cpp"
+#include "../../docs/examples/Paint_isSubpixelText.cpp"
+#include "../../docs/examples/Paint_measureText.cpp"
+#include "../../docs/examples/Paint_measureText_2.cpp"
+#include "../../docs/examples/Paint_move_SkPaint.cpp"
+#include "../../docs/examples/Paint_move_operator.cpp"
+#include "../../docs/examples/Paint_notequal_operator.cpp"
+#include "../../docs/examples/Paint_nothingToDraw.cpp"
+#include "../../docs/examples/Paint_refColorFilter.cpp"
+#include "../../docs/examples/Paint_refDrawLooper.cpp"
+#include "../../docs/examples/Paint_refImageFilter.cpp"
+#include "../../docs/examples/Paint_refMaskFilter.cpp"
+#include "../../docs/examples/Paint_refPathEffect.cpp"
+#include "../../docs/examples/Paint_refShader.cpp"
+#include "../../docs/examples/Paint_refTypeface.cpp"
+#include "../../docs/examples/Paint_reset.cpp"
+#include "../../docs/examples/Paint_setARGB.cpp"
+#include "../../docs/examples/Paint_setAlpha.cpp"
+#include "../../docs/examples/Paint_setAntiAlias.cpp"
+#include "../../docs/examples/Paint_setAutohinted.cpp"
+#include "../../docs/examples/Paint_setBlendMode.cpp"
+#include "../../docs/examples/Paint_setColor.cpp"
+#include "../../docs/examples/Paint_setColor4f.cpp"
+#include "../../docs/examples/Paint_setColorFilter.cpp"
+#include "../../docs/examples/Paint_setDither.cpp"
+#include "../../docs/examples/Paint_setDrawLooper.cpp"
+#include "../../docs/examples/Paint_setEmbeddedBitmapText.cpp"
+#include "../../docs/examples/Paint_setFakeBoldText.cpp"
+#include "../../docs/examples/Paint_setFilterQuality.cpp"
+#include "../../docs/examples/Paint_setFlags.cpp"
+#include "../../docs/examples/Paint_setHinting.cpp"
+#include "../../docs/examples/Paint_setImageFilter.cpp"
+#include "../../docs/examples/Paint_setLCDRenderText.cpp"
+#include "../../docs/examples/Paint_setLinearText.cpp"
+#include "../../docs/examples/Paint_setMaskFilter.cpp"
+#include "../../docs/examples/Paint_setPathEffect.cpp"
+#include "../../docs/examples/Paint_setShader.cpp"
+#include "../../docs/examples/Paint_setStrokeCap_a.cpp"
+#include "../../docs/examples/Paint_setStrokeCap_b.cpp"
+#include "../../docs/examples/Paint_setStrokeJoin.cpp"
+#include "../../docs/examples/Paint_setStrokeMiter.cpp"
+#include "../../docs/examples/Paint_setStrokeWidth.cpp"
+#include "../../docs/examples/Paint_setStyle.cpp"
+#include "../../docs/examples/Paint_setSubpixelText.cpp"
+#include "../../docs/examples/Paint_setTextEncoding.cpp"
+#include "../../docs/examples/Paint_setTextScaleX.cpp"
+#include "../../docs/examples/Paint_setTextSize.cpp"
+#include "../../docs/examples/Paint_setTextSkewX.cpp"
+#include "../../docs/examples/Paint_setTypeface.cpp"
+#include "../../docs/examples/Paint_textToGlyphs.cpp"
+#include "../../docs/examples/Path_AddPathMode.cpp"
+#include "../../docs/examples/Path_ArcSize.cpp"
+#include "../../docs/examples/Path_ConvertConicToQuads.cpp"
+#include "../../docs/examples/Path_ConvertToNonInverseFillType.cpp"
+#include "../../docs/examples/Path_Convexity.cpp"
+#include "../../docs/examples/Path_Direction.cpp"
+#include "../../docs/examples/Path_Effect_Methods.cpp"
+#include "../../docs/examples/Path_FillType_a.cpp"
+#include "../../docs/examples/Path_FillType_b.cpp"
+#include "../../docs/examples/Path_IsCubicDegenerate.cpp"
+#include "../../docs/examples/Path_IsInverseFillType.cpp"
+#include "../../docs/examples/Path_IsLineDegenerate.cpp"
+#include "../../docs/examples/Path_IsQuadDegenerate.cpp"
+#include "../../docs/examples/Path_Iter.cpp"
+#include "../../docs/examples/Path_Iter_Iter.cpp"
+#include "../../docs/examples/Path_Iter_conicWeight.cpp"
+#include "../../docs/examples/Path_Iter_const_SkPath.cpp"
+#include "../../docs/examples/Path_Iter_isCloseLine.cpp"
+#include "../../docs/examples/Path_Iter_isClosedContour.cpp"
+#include "../../docs/examples/Path_Iter_next.cpp"
+#include "../../docs/examples/Path_Iter_setPath.cpp"
+#include "../../docs/examples/Path_RawIter_conicWeight.cpp"
+#include "../../docs/examples/Path_RawIter_next.cpp"
+#include "../../docs/examples/Path_RawIter_peek.cpp"
+#include "../../docs/examples/Path_SegmentMask.cpp"
+#include "../../docs/examples/Path_Verb.cpp"
+#include "../../docs/examples/Path_addArc.cpp"
+#include "../../docs/examples/Path_addCircle.cpp"
+#include "../../docs/examples/Path_addOval.cpp"
+#include "../../docs/examples/Path_addOval_2.cpp"
+#include "../../docs/examples/Path_addPath.cpp"
+#include "../../docs/examples/Path_addPath_2.cpp"
+#include "../../docs/examples/Path_addPath_3.cpp"
+#include "../../docs/examples/Path_addPoly.cpp"
+#include "../../docs/examples/Path_addPoly_2.cpp"
+#include "../../docs/examples/Path_addRRect.cpp"
+#include "../../docs/examples/Path_addRRect_2.cpp"
+#include "../../docs/examples/Path_addRect.cpp"
+#include "../../docs/examples/Path_addRect_2.cpp"
+#include "../../docs/examples/Path_addRect_3.cpp"
+#include "../../docs/examples/Path_addRoundRect.cpp"
+#include "../../docs/examples/Path_addRoundRect_2.cpp"
+#include "../../docs/examples/Path_arcTo.cpp"
+#include "../../docs/examples/Path_arcTo_2_a.cpp"
+#include "../../docs/examples/Path_arcTo_2_b.cpp"
+#include "../../docs/examples/Path_arcTo_2_c.cpp"
+#include "../../docs/examples/Path_arcTo_3.cpp"
+#include "../../docs/examples/Path_arcTo_4.cpp"
+#include "../../docs/examples/Path_close.cpp"
+#include "../../docs/examples/Path_computeTightBounds.cpp"
+#include "../../docs/examples/Path_conicTo.cpp"
+#include "../../docs/examples/Path_conicTo_2.cpp"
+#include "../../docs/examples/Path_conservativelyContainsRect.cpp"
+#include "../../docs/examples/Path_contains.cpp"
+#include "../../docs/examples/Path_copy_const_SkPath.cpp"
+#include "../../docs/examples/Path_copy_operator.cpp"
+#include "../../docs/examples/Path_countPoints.cpp"
+#include "../../docs/examples/Path_countVerbs.cpp"
+#include "../../docs/examples/Path_cubicTo.cpp"
+#include "../../docs/examples/Path_cubicTo_2.cpp"
+#include "../../docs/examples/Path_destructor.cpp"
+#include "../../docs/examples/Path_dump.cpp"
+#include "../../docs/examples/Path_dumpHex.cpp"
+#include "../../docs/examples/Path_dump_2.cpp"
+#include "../../docs/examples/Path_empty_constructor.cpp"
+#include "../../docs/examples/Path_equal_operator.cpp"
+#include "../../docs/examples/Path_getBounds.cpp"
+#include "../../docs/examples/Path_getConvexity.cpp"
+#include "../../docs/examples/Path_getConvexityOrUnknown.cpp"
+#include "../../docs/examples/Path_getFillType.cpp"
+#include "../../docs/examples/Path_getGenerationID.cpp"
+#include "../../docs/examples/Path_getLastPt.cpp"
+#include "../../docs/examples/Path_getPoint.cpp"
+#include "../../docs/examples/Path_getPoints.cpp"
+#include "../../docs/examples/Path_getSegmentMasks.cpp"
+#include "../../docs/examples/Path_getVerbs.cpp"
+#include "../../docs/examples/Path_incReserve.cpp"
+#include "../../docs/examples/Path_interpolate.cpp"
+#include "../../docs/examples/Path_isConvex.cpp"
+#include "../../docs/examples/Path_isEmpty.cpp"
+#include "../../docs/examples/Path_isFinite.cpp"
+#include "../../docs/examples/Path_isInterpolatable.cpp"
+#include "../../docs/examples/Path_isInverseFillType_2.cpp"
+#include "../../docs/examples/Path_isLastContourClosed.cpp"
+#include "../../docs/examples/Path_isLine.cpp"
+#include "../../docs/examples/Path_isNestedFillRects.cpp"
+#include "../../docs/examples/Path_isOval.cpp"
+#include "../../docs/examples/Path_isRRect.cpp"
+#include "../../docs/examples/Path_isRect.cpp"
+#include "../../docs/examples/Path_isVolatile.cpp"
+#include "../../docs/examples/Path_lineTo.cpp"
+#include "../../docs/examples/Path_lineTo_2.cpp"
+#include "../../docs/examples/Path_moveTo.cpp"
+#include "../../docs/examples/Path_moveTo_2.cpp"
+#include "../../docs/examples/Path_notequal_operator.cpp"
+#include "../../docs/examples/Path_offset.cpp"
+#include "../../docs/examples/Path_offset_2.cpp"
+#include "../../docs/examples/Path_quadTo.cpp"
+#include "../../docs/examples/Path_quadTo_2.cpp"
+#include "../../docs/examples/Path_rArcTo.cpp"
+#include "../../docs/examples/Path_rConicTo.cpp"
+#include "../../docs/examples/Path_rCubicTo.cpp"
+#include "../../docs/examples/Path_rLineTo.cpp"
+#include "../../docs/examples/Path_rMoveTo.cpp"
+#include "../../docs/examples/Path_rQuadTo.cpp"
+#include "../../docs/examples/Path_readFromMemory.cpp"
+#include "../../docs/examples/Path_reset.cpp"
+#include "../../docs/examples/Path_reverseAddPath.cpp"
+#include "../../docs/examples/Path_rewind.cpp"
+#include "../../docs/examples/Path_serialize.cpp"
+#include "../../docs/examples/Path_setConvexity.cpp"
+#include "../../docs/examples/Path_setFillType.cpp"
+#include "../../docs/examples/Path_setIsVolatile.cpp"
+#include "../../docs/examples/Path_setLastPt.cpp"
+#include "../../docs/examples/Path_setLastPt_2.cpp"
+#include "../../docs/examples/Path_swap.cpp"
+#include "../../docs/examples/Path_toggleInverseFillType.cpp"
+#include "../../docs/examples/Path_transform.cpp"
+#include "../../docs/examples/Path_transform_2.cpp"
+#include "../../docs/examples/Path_updateBoundsCache.cpp"
+#include "../../docs/examples/Path_writeToMemory.cpp"
+#include "../../docs/examples/Picture_008.cpp"
+#include "../../docs/examples/Picture_AbortCallback_abort.cpp"
+#include "../../docs/examples/Picture_MakeFromData.cpp"
+#include "../../docs/examples/Picture_MakeFromStream.cpp"
+#include "../../docs/examples/Picture_MakePlaceholder.cpp"
+#include "../../docs/examples/Picture_approximateBytesUsed.cpp"
+#include "../../docs/examples/Picture_approximateOpCount.cpp"
+#include "../../docs/examples/Picture_cullRect.cpp"
+#include "../../docs/examples/Picture_playback.cpp"
+#include "../../docs/examples/Picture_serialize.cpp"
+#include "../../docs/examples/Picture_serialize_2.cpp"
+#include "../../docs/examples/Picture_uniqueID.cpp"
+#include "../../docs/examples/Pixmap_addr.cpp"
+#include "../../docs/examples/Pixmap_addr16.cpp"
+#include "../../docs/examples/Pixmap_addr16_2.cpp"
+#include "../../docs/examples/Pixmap_addr32.cpp"
+#include "../../docs/examples/Pixmap_addr32_2.cpp"
+#include "../../docs/examples/Pixmap_addr64.cpp"
+#include "../../docs/examples/Pixmap_addr64_2.cpp"
+#include "../../docs/examples/Pixmap_addr8.cpp"
+#include "../../docs/examples/Pixmap_addr8_2.cpp"
+#include "../../docs/examples/Pixmap_addrF16.cpp"
+#include "../../docs/examples/Pixmap_addrF16_2.cpp"
+#include "../../docs/examples/Pixmap_addr_2.cpp"
+#include "../../docs/examples/Pixmap_alphaType.cpp"
+#include "../../docs/examples/Pixmap_bounds.cpp"
+#include "../../docs/examples/Pixmap_colorSpace.cpp"
+#include "../../docs/examples/Pixmap_colorType.cpp"
+#include "../../docs/examples/Pixmap_computeByteSize.cpp"
+#include "../../docs/examples/Pixmap_computeIsOpaque.cpp"
+#include "../../docs/examples/Pixmap_const_SkImageInfo_const_star.cpp"
+#include "../../docs/examples/Pixmap_empty_constructor.cpp"
+#include "../../docs/examples/Pixmap_erase.cpp"
+#include "../../docs/examples/Pixmap_erase_2.cpp"
+#include "../../docs/examples/Pixmap_erase_3.cpp"
+#include "../../docs/examples/Pixmap_extractSubset.cpp"
+#include "../../docs/examples/Pixmap_getColor.cpp"
+#include "../../docs/examples/Pixmap_height.cpp"
+#include "../../docs/examples/Pixmap_info.cpp"
+#include "../../docs/examples/Pixmap_isOpaque.cpp"
+#include "../../docs/examples/Pixmap_readPixels.cpp"
+#include "../../docs/examples/Pixmap_readPixels_2.cpp"
+#include "../../docs/examples/Pixmap_readPixels_3.cpp"
+#include "../../docs/examples/Pixmap_readPixels_4.cpp"
+#include "../../docs/examples/Pixmap_reset.cpp"
+#include "../../docs/examples/Pixmap_reset_2.cpp"
+#include "../../docs/examples/Pixmap_rowBytes.cpp"
+#include "../../docs/examples/Pixmap_rowBytesAsPixels.cpp"
+#include "../../docs/examples/Pixmap_scalePixels.cpp"
+#include "../../docs/examples/Pixmap_setColorSpace.cpp"
+#include "../../docs/examples/Pixmap_shiftPerPixel.cpp"
+#include "../../docs/examples/Pixmap_width.cpp"
+#include "../../docs/examples/Pixmap_writable_addr.cpp"
+#include "../../docs/examples/Pixmap_writable_addr16.cpp"
+#include "../../docs/examples/Pixmap_writable_addr32.cpp"
+#include "../../docs/examples/Pixmap_writable_addr64.cpp"
+#include "../../docs/examples/Pixmap_writable_addr8.cpp"
+#include "../../docs/examples/Pixmap_writable_addrF16.cpp"
+#include "../../docs/examples/Pixmap_writable_addr_2.cpp"
+#include "../../docs/examples/Plus.cpp"
+#include "../../docs/examples/Point_CrossProduct.cpp"
+#include "../../docs/examples/Point_Distance.cpp"
+#include "../../docs/examples/Point_DotProduct.cpp"
+#include "../../docs/examples/Point_Length.cpp"
+#include "../../docs/examples/Point_Make.cpp"
+#include "../../docs/examples/Point_Normalize.cpp"
+#include "../../docs/examples/Point_Offset.cpp"
+#include "../../docs/examples/Point_Offset_2.cpp"
+#include "../../docs/examples/Point_add_operator.cpp"
+#include "../../docs/examples/Point_addto_operator.cpp"
+#include "../../docs/examples/Point_cross.cpp"
+#include "../../docs/examples/Point_distanceToOrigin.cpp"
+#include "../../docs/examples/Point_dot.cpp"
+#include "../../docs/examples/Point_equal_operator.cpp"
+#include "../../docs/examples/Point_equals.cpp"
+#include "../../docs/examples/Point_isFinite.cpp"
+#include "../../docs/examples/Point_isZero.cpp"
+#include "../../docs/examples/Point_iset.cpp"
+#include "../../docs/examples/Point_iset_2.cpp"
+#include "../../docs/examples/Point_length_2.cpp"
+#include "../../docs/examples/Point_minus_operator.cpp"
+#include "../../docs/examples/Point_multiply_operator.cpp"
+#include "../../docs/examples/Point_multiplyby_operator.cpp"
+#include "../../docs/examples/Point_negate.cpp"
+#include "../../docs/examples/Point_normalize_2.cpp"
+#include "../../docs/examples/Point_notequal_operator.cpp"
+#include "../../docs/examples/Point_offset_3.cpp"
+#include "../../docs/examples/Point_scale.cpp"
+#include "../../docs/examples/Point_scale_2.cpp"
+#include "../../docs/examples/Point_set.cpp"
+#include "../../docs/examples/Point_setAbs.cpp"
+#include "../../docs/examples/Point_setLength.cpp"
+#include "../../docs/examples/Point_setLength_2.cpp"
+#include "../../docs/examples/Point_setNormalize.cpp"
+#include "../../docs/examples/Point_subtract_operator.cpp"
+#include "../../docs/examples/Point_subtractfrom_operator.cpp"
+#include "../../docs/examples/Point_x.cpp"
+#include "../../docs/examples/Point_y.cpp"
+#include "../../docs/examples/PreMultiplyARGB.cpp"
+#include "../../docs/examples/PreMultiplyColor.cpp"
+#include "../../docs/examples/Quad_a.cpp"
+#include "../../docs/examples/Quad_b.cpp"
+#include "../../docs/examples/RGBA4f_FromColor.cpp"
+#include "../../docs/examples/RGBA4f_equal1_operator.cpp"
+#include "../../docs/examples/RGBA4f_notequal1_operator.cpp"
+#include "../../docs/examples/RGBA4f_toSkColor.cpp"
+#include "../../docs/examples/RGBA4f_vec.cpp"
+#include "../../docs/examples/RGBA4f_vec_2.cpp"
+#include "../../docs/examples/RGBToHSV.cpp"
+#include "../../docs/examples/RRect_Corner.cpp"
+#include "../../docs/examples/RRect_MakeEmpty.cpp"
+#include "../../docs/examples/RRect_MakeOval.cpp"
+#include "../../docs/examples/RRect_MakeRect.cpp"
+#include "../../docs/examples/RRect_MakeRectXY.cpp"
+#include "../../docs/examples/RRect_Type.cpp"
+#include "../../docs/examples/RRect_contains.cpp"
+#include "../../docs/examples/RRect_copy_const_SkRRect.cpp"
+#include "../../docs/examples/RRect_copy_operator.cpp"
+#include "../../docs/examples/RRect_dump.cpp"
+#include "../../docs/examples/RRect_dumpHex.cpp"
+#include "../../docs/examples/RRect_dump_2.cpp"
+#include "../../docs/examples/RRect_empty_constructor.cpp"
+#include "../../docs/examples/RRect_equal_operator.cpp"
+#include "../../docs/examples/RRect_getBounds.cpp"
+#include "../../docs/examples/RRect_getSimpleRadii.cpp"
+#include "../../docs/examples/RRect_getType.cpp"
+#include "../../docs/examples/RRect_height.cpp"
+#include "../../docs/examples/RRect_inset.cpp"
+#include "../../docs/examples/RRect_inset_2.cpp"
+#include "../../docs/examples/RRect_isComplex.cpp"
+#include "../../docs/examples/RRect_isEmpty.cpp"
+#include "../../docs/examples/RRect_isNinePatch.cpp"
+#include "../../docs/examples/RRect_isOval.cpp"
+#include "../../docs/examples/RRect_isRect.cpp"
+#include "../../docs/examples/RRect_isSimple.cpp"
+#include "../../docs/examples/RRect_isValid.cpp"
+#include "../../docs/examples/RRect_makeOffset.cpp"
+#include "../../docs/examples/RRect_notequal_operator.cpp"
+#include "../../docs/examples/RRect_offset.cpp"
+#include "../../docs/examples/RRect_outset.cpp"
+#include "../../docs/examples/RRect_outset_2.cpp"
+#include "../../docs/examples/RRect_radii.cpp"
+#include "../../docs/examples/RRect_readFromMemory.cpp"
+#include "../../docs/examples/RRect_rect.cpp"
+#include "../../docs/examples/RRect_setEmpty.cpp"
+#include "../../docs/examples/RRect_setNinePatch.cpp"
+#include "../../docs/examples/RRect_setOval.cpp"
+#include "../../docs/examples/RRect_setRect.cpp"
+#include "../../docs/examples/RRect_setRectRadii.cpp"
+#include "../../docs/examples/RRect_setRectXY.cpp"
+#include "../../docs/examples/RRect_transform.cpp"
+#include "../../docs/examples/RRect_type_2.cpp"
+#include "../../docs/examples/RRect_width.cpp"
+#include "../../docs/examples/RRect_writeToMemory.cpp"
+#include "../../docs/examples/Rect_Intersects.cpp"
+#include "../../docs/examples/Rect_Make.cpp"
+#include "../../docs/examples/Rect_MakeEmpty.cpp"
+#include "../../docs/examples/Rect_MakeIWH.cpp"
+#include "../../docs/examples/Rect_MakeLTRB.cpp"
+#include "../../docs/examples/Rect_MakeSize.cpp"
+#include "../../docs/examples/Rect_MakeWH.cpp"
+#include "../../docs/examples/Rect_MakeXYWH.cpp"
+#include "../../docs/examples/Rect_Make_2.cpp"
+#include "../../docs/examples/Rect_asScalars.cpp"
+#include "../../docs/examples/Rect_bottom.cpp"
+#include "../../docs/examples/Rect_centerX.cpp"
+#include "../../docs/examples/Rect_centerY.cpp"
+#include "../../docs/examples/Rect_contains.cpp"
+#include "../../docs/examples/Rect_contains_2.cpp"
+#include "../../docs/examples/Rect_contains_3.cpp"
+#include "../../docs/examples/Rect_dump.cpp"
+#include "../../docs/examples/Rect_dumpHex.cpp"
+#include "../../docs/examples/Rect_dump_2.cpp"
+#include "../../docs/examples/Rect_equal_operator.cpp"
+#include "../../docs/examples/Rect_height.cpp"
+#include "../../docs/examples/Rect_inset.cpp"
+#include "../../docs/examples/Rect_intersect.cpp"
+#include "../../docs/examples/Rect_intersect_2.cpp"
+#include "../../docs/examples/Rect_intersect_3.cpp"
+#include "../../docs/examples/Rect_intersects_2.cpp"
+#include "../../docs/examples/Rect_intersects_3.cpp"
+#include "../../docs/examples/Rect_isEmpty.cpp"
+#include "../../docs/examples/Rect_isFinite.cpp"
+#include "../../docs/examples/Rect_isSorted.cpp"
+#include "../../docs/examples/Rect_iset.cpp"
+#include "../../docs/examples/Rect_isetWH.cpp"
+#include "../../docs/examples/Rect_join.cpp"
+#include "../../docs/examples/Rect_joinNonEmptyArg.cpp"
+#include "../../docs/examples/Rect_joinPossiblyEmptyRect.cpp"
+#include "../../docs/examples/Rect_join_2.cpp"
+#include "../../docs/examples/Rect_left.cpp"
+#include "../../docs/examples/Rect_makeInset.cpp"
+#include "../../docs/examples/Rect_makeOffset.cpp"
+#include "../../docs/examples/Rect_makeOutset.cpp"
+#include "../../docs/examples/Rect_makeSorted.cpp"
+#include "../../docs/examples/Rect_notequal_operator.cpp"
+#include "../../docs/examples/Rect_offset.cpp"
+#include "../../docs/examples/Rect_offsetTo.cpp"
+#include "../../docs/examples/Rect_offset_2.cpp"
+#include "../../docs/examples/Rect_outset.cpp"
+#include "../../docs/examples/Rect_right.cpp"
+#include "../../docs/examples/Rect_round.cpp"
+#include "../../docs/examples/Rect_roundIn.cpp"
+#include "../../docs/examples/Rect_roundOut.cpp"
+#include "../../docs/examples/Rect_roundOut_2.cpp"
+#include "../../docs/examples/Rect_roundOut_3.cpp"
+#include "../../docs/examples/Rect_round_2.cpp"
+#include "../../docs/examples/Rect_set.cpp"
+#include "../../docs/examples/Rect_setBounds.cpp"
+#include "../../docs/examples/Rect_setBoundsCheck.cpp"
+#include "../../docs/examples/Rect_setBoundsNoCheck.cpp"
+#include "../../docs/examples/Rect_setEmpty.cpp"
+#include "../../docs/examples/Rect_setLTRB.cpp"
+#include "../../docs/examples/Rect_setWH.cpp"
+#include "../../docs/examples/Rect_setXYWH.cpp"
+#include "../../docs/examples/Rect_set_2.cpp"
+#include "../../docs/examples/Rect_set_3.cpp"
+#include "../../docs/examples/Rect_set_4.cpp"
+#include "../../docs/examples/Rect_sort.cpp"
+#include "../../docs/examples/Rect_toQuad.cpp"
+#include "../../docs/examples/Rect_top.cpp"
+#include "../../docs/examples/Rect_width.cpp"
+#include "../../docs/examples/Rect_x.cpp"
+#include "../../docs/examples/Rect_y.cpp"
+#include "../../docs/examples/Region_Cliperator_const_SkRegion_const_SkIRect.cpp"
+#include "../../docs/examples/Region_Cliperator_done.cpp"
+#include "../../docs/examples/Region_Cliperator_next.cpp"
+#include "../../docs/examples/Region_Cliperator_rect.cpp"
+#include "../../docs/examples/Region_Iterator_Iterator.cpp"
+#include "../../docs/examples/Region_Iterator_copy_const_SkRegion.cpp"
+#include "../../docs/examples/Region_Iterator_done.cpp"
+#include "../../docs/examples/Region_Iterator_next.cpp"
+#include "../../docs/examples/Region_Iterator_rect.cpp"
+#include "../../docs/examples/Region_Iterator_reset.cpp"
+#include "../../docs/examples/Region_Iterator_rewind.cpp"
+#include "../../docs/examples/Region_Iterator_rgn.cpp"
+#include "../../docs/examples/Region_Op.cpp"
+#include "../../docs/examples/Region_Spanerator_const_SkRegion_int_int_int.cpp"
+#include "../../docs/examples/Region_Spanerator_next.cpp"
+#include "../../docs/examples/Region_computeRegionComplexity.cpp"
+#include "../../docs/examples/Region_contains.cpp"
+#include "../../docs/examples/Region_contains_2.cpp"
+#include "../../docs/examples/Region_contains_3.cpp"
+#include "../../docs/examples/Region_copy_const_SkIRect.cpp"
+#include "../../docs/examples/Region_copy_const_SkRegion.cpp"
+#include "../../docs/examples/Region_copy_operator.cpp"
+#include "../../docs/examples/Region_destructor.cpp"
+#include "../../docs/examples/Region_empty_constructor.cpp"
+#include "../../docs/examples/Region_equal1_operator.cpp"
+#include "../../docs/examples/Region_getBoundaryPath.cpp"
+#include "../../docs/examples/Region_getBounds.cpp"
+#include "../../docs/examples/Region_intersects.cpp"
+#include "../../docs/examples/Region_intersects_2.cpp"
+#include "../../docs/examples/Region_isComplex.cpp"
+#include "../../docs/examples/Region_isEmpty.cpp"
+#include "../../docs/examples/Region_isRect.cpp"
+#include "../../docs/examples/Region_notequal1_operator.cpp"
+#include "../../docs/examples/Region_op_1.cpp"
+#include "../../docs/examples/Region_op_2.cpp"
+#include "../../docs/examples/Region_op_3.cpp"
+#include "../../docs/examples/Region_op_4.cpp"
+#include "../../docs/examples/Region_op_5.cpp"
+#include "../../docs/examples/Region_op_6.cpp"
+#include "../../docs/examples/Region_quickContains.cpp"
+#include "../../docs/examples/Region_quickContains_2.cpp"
+#include "../../docs/examples/Region_quickReject.cpp"
+#include "../../docs/examples/Region_quickReject_2.cpp"
+#include "../../docs/examples/Region_readFromMemory.cpp"
+#include "../../docs/examples/Region_set.cpp"
+#include "../../docs/examples/Region_setEmpty.cpp"
+#include "../../docs/examples/Region_setPath.cpp"
+#include "../../docs/examples/Region_setRect.cpp"
+#include "../../docs/examples/Region_setRect_2.cpp"
+#include "../../docs/examples/Region_setRects.cpp"
+#include "../../docs/examples/Region_setRegion.cpp"
+#include "../../docs/examples/Region_swap.cpp"
+#include "../../docs/examples/Region_translate.cpp"
+#include "../../docs/examples/Region_translate_2.cpp"
+#include "../../docs/examples/Region_writeToMemory.cpp"
+#include "../../docs/examples/Saturation.cpp"
+#include "../../docs/examples/Screen.cpp"
+#include "../../docs/examples/Shader_Methods_a.cpp"
+#include "../../docs/examples/Shader_Methods_b.cpp"
+#include "../../docs/examples/Soft_Light.cpp"
+#include "../../docs/examples/Src.cpp"
+#include "../../docs/examples/Src_Atop.cpp"
+#include "../../docs/examples/Src_In.cpp"
+#include "../../docs/examples/Src_Out.cpp"
+#include "../../docs/examples/Src_Over.cpp"
+#include "../../docs/examples/State_Stack_a.cpp"
+#include "../../docs/examples/State_Stack_b.cpp"
+#include "../../docs/examples/Stroke_Width.cpp"
+#include "../../docs/examples/Surface_MakeFromBackendTexture.cpp"
+#include "../../docs/examples/Surface_MakeFromBackendTextureAsRenderTarget.cpp"
+#include "../../docs/examples/Surface_MakeNull.cpp"
+#include "../../docs/examples/Surface_MakeRaster.cpp"
+#include "../../docs/examples/Surface_MakeRasterDirect.cpp"
+#include "../../docs/examples/Surface_MakeRasterDirectReleaseProc.cpp"
+#include "../../docs/examples/Surface_MakeRasterN32Premul.cpp"
+#include "../../docs/examples/Surface_MakeRaster_2.cpp"
+#include "../../docs/examples/Surface_MakeRenderTarget.cpp"
+#include "../../docs/examples/Surface_MakeRenderTarget_2.cpp"
+#include "../../docs/examples/Surface_MakeRenderTarget_3.cpp"
+#include "../../docs/examples/Surface_characterize.cpp"
+#include "../../docs/examples/Surface_draw.cpp"
+#include "../../docs/examples/Surface_draw_2.cpp"
+#include "../../docs/examples/Surface_getCanvas.cpp"
+#include "../../docs/examples/Surface_height.cpp"
+#include "../../docs/examples/Surface_makeImageSnapshot.cpp"
+#include "../../docs/examples/Surface_makeImageSnapshot_2.cpp"
+#include "../../docs/examples/Surface_makeSurface.cpp"
+#include "../../docs/examples/Surface_notifyContentWillChange.cpp"
+#include "../../docs/examples/Surface_peekPixels.cpp"
+#include "../../docs/examples/Surface_props.cpp"
+#include "../../docs/examples/Surface_readPixels.cpp"
+#include "../../docs/examples/Surface_readPixels_2.cpp"
+#include "../../docs/examples/Surface_readPixels_3.cpp"
+#include "../../docs/examples/Surface_width.cpp"
+#include "../../docs/examples/Surface_writePixels.cpp"
+#include "../../docs/examples/Surface_writePixels_2.cpp"
+#include "../../docs/examples/TextBlobBuilder_allocRun.cpp"
+#include "../../docs/examples/TextBlobBuilder_allocRunPos.cpp"
+#include "../../docs/examples/TextBlobBuilder_allocRunPosH.cpp"
+#include "../../docs/examples/TextBlobBuilder_empty_constructor.cpp"
+#include "../../docs/examples/TextBlobBuilder_make.cpp"
+#include "../../docs/examples/TextBlob_Deserialize.cpp"
+#include "../../docs/examples/TextBlob_MakeFromString.cpp"
+#include "../../docs/examples/TextBlob_MakeFromText.cpp"
+#include "../../docs/examples/TextBlob_bounds.cpp"
+#include "../../docs/examples/TextBlob_getIntercepts.cpp"
+#include "../../docs/examples/TextBlob_serialize.cpp"
+#include "../../docs/examples/TextBlob_serialize_2.cpp"
+#include "../../docs/examples/TextBlob_uniqueID.cpp"
+#include "../../docs/examples/Text_Encoding.cpp"
+#include "../../docs/examples/Text_Scale_X.cpp"
+#include "../../docs/examples/Text_Size.cpp"
+#include "../../docs/examples/Text_Skew_X.cpp"
+#include "../../docs/examples/Typeface_Methods.cpp"
+#include "../../docs/examples/Xor.cpp"
diff --git a/tools/fiddle/disabled_examples.txt b/tools/fiddle/disabled_examples.txt
new file mode 100644
index 0000000..3970425
--- /dev/null
+++ b/tools/fiddle/disabled_examples.txt
@@ -0,0 +1,202 @@
+docs/examples/Alpha_Type_Opaque.cpp
+docs/examples/Alpha_Type_Premul.cpp
+docs/examples/Alpha_Type_Unpremul.cpp
+docs/examples/Arc.cpp
+docs/examples/Canvas_129.cpp
+docs/examples/Canvas_SaveLayerRec_SaveLayerRec.cpp
+docs/examples/Canvas_drawAnnotation_2.cpp
+docs/examples/Canvas_drawPatch_2_a.cpp
+docs/examples/Canvas_drawPosText.cpp
+docs/examples/Canvas_drawPosTextH.cpp
+docs/examples/Canvas_drawTextBlob.cpp
+docs/examples/Canvas_saveLayerPreserveLCDTextRequests.cpp
+docs/examples/ColorGetB.cpp
+docs/examples/ColorGetG.cpp
+docs/examples/ColorGetR.cpp
+docs/examples/Color_Constants_a.cpp
+docs/examples/Device_Text.cpp
+docs/examples/Fake_Bold.cpp
+docs/examples/Font_breakText.cpp
+docs/examples/ImageInfo_MakeN32.cpp
+docs/examples/ImageInfo_MakeN32Premul.cpp
+docs/examples/ImageInfo_MakeN32Premul_2.cpp
+docs/examples/ImageInfo_MakeUnknown.cpp
+docs/examples/ImageInfo_MakeUnknown_2.cpp
+docs/examples/ImageInfo_empty_constructor.cpp
+docs/examples/ImageInfo_gammaCloseToSRGB.cpp
+docs/examples/ImageInfo_height.cpp
+docs/examples/ImageInfo_makeColorSpace.cpp
+docs/examples/ImageInfo_width.cpp
+docs/examples/Image_alphaType.cpp
+docs/examples/Image_colorSpace.cpp
+docs/examples/Image_getBackendTexture.cpp
+docs/examples/Image_height.cpp
+docs/examples/Image_isLazyGenerated_a.cpp
+docs/examples/Image_isLazyGenerated_b.cpp
+docs/examples/Image_isTextureBacked.cpp
+docs/examples/Image_isValid.cpp
+docs/examples/Image_makeColorSpace.cpp
+docs/examples/Image_makeNonTextureImage.cpp
+docs/examples/Image_makeRasterImage.cpp
+docs/examples/Image_makeTextureImage.cpp
+docs/examples/Image_peekPixels.cpp
+docs/examples/Image_refColorSpace.cpp
+docs/examples/Image_refEncodedData.cpp
+docs/examples/Image_uniqueID.cpp
+docs/examples/Image_width.cpp
+docs/examples/Matrix_MakeAll.cpp
+docs/examples/Matrix_ScaleToFit.cpp
+docs/examples/Matrix_getPerspX.cpp
+docs/examples/Matrix_getPerspY.cpp
+docs/examples/Matrix_hasPerspective.cpp
+docs/examples/Matrix_isSimilarity.cpp
+docs/examples/Matrix_mapRadius.cpp
+docs/examples/Matrix_mapVector.cpp
+docs/examples/Matrix_mapVector_2.cpp
+docs/examples/Matrix_preservesRightAngles.cpp
+docs/examples/Matrix_setAll.cpp
+docs/examples/Matrix_setPolyToPoly.cpp
+docs/examples/Matrix_setScale.cpp
+docs/examples/Matrix_setScaleX.cpp
+docs/examples/Matrix_setScaleY.cpp
+docs/examples/Matrix_setScale_2.cpp
+docs/examples/Matrix_setSkew.cpp
+docs/examples/Matrix_setSkewX.cpp
+docs/examples/Matrix_setSkewY.cpp
+docs/examples/Matrix_setSkew_2.cpp
+docs/examples/Matrix_setTranslate.cpp
+docs/examples/Matrix_setTranslateX.cpp
+docs/examples/Matrix_setTranslateY.cpp
+docs/examples/Matrix_setTranslate_2.cpp
+docs/examples/MemberIndex.cpp
+docs/examples/Paint_containsText.cpp
+docs/examples/Paint_countText.cpp
+docs/examples/Paint_getFlags.cpp
+docs/examples/Paint_getFontMetrics.cpp
+docs/examples/Paint_getFontSpacing.cpp
+docs/examples/Paint_getHinting.cpp
+docs/examples/Paint_getPosTextPath.cpp
+docs/examples/Paint_getTextEncoding.cpp
+docs/examples/Paint_getTextPath.cpp
+docs/examples/Paint_getTextScaleX.cpp
+docs/examples/Paint_getTextSize.cpp
+docs/examples/Paint_getTextSkewX.cpp
+docs/examples/Paint_getTextWidths.cpp
+docs/examples/Paint_getTypeface.cpp
+docs/examples/Paint_isAntiAlias.cpp
+docs/examples/Paint_isAutohinted.cpp
+docs/examples/Paint_isDither.cpp
+docs/examples/Paint_isEmbeddedBitmapText.cpp
+docs/examples/Paint_isFakeBoldText.cpp
+docs/examples/Paint_isLCDRenderText.cpp
+docs/examples/Paint_isLinearText.cpp
+docs/examples/Paint_isSubpixelText.cpp
+docs/examples/Paint_measureText.cpp
+docs/examples/Paint_measureText_2.cpp
+docs/examples/Paint_refTypeface.cpp
+docs/examples/Paint_setAntiAlias.cpp
+docs/examples/Paint_setAutohinted.cpp
+docs/examples/Paint_setDither.cpp
+docs/examples/Paint_setEmbeddedBitmapText.cpp
+docs/examples/Paint_setFakeBoldText.cpp
+docs/examples/Paint_setFlags.cpp
+docs/examples/Paint_setHinting.cpp
+docs/examples/Paint_setImageFilter.cpp
+docs/examples/Paint_setLCDRenderText.cpp
+docs/examples/Paint_setLinearText.cpp
+docs/examples/Paint_setSubpixelText.cpp
+docs/examples/Paint_setTextEncoding.cpp
+docs/examples/Paint_setTextScaleX.cpp
+docs/examples/Paint_setTextSize.cpp
+docs/examples/Paint_setTextSkewX.cpp
+docs/examples/Paint_setTypeface.cpp
+docs/examples/Paint_textToGlyphs.cpp
+docs/examples/Path_Convexity.cpp
+docs/examples/Path_Direction.cpp
+docs/examples/Path_FillType_b.cpp
+docs/examples/Path_Iter.cpp
+docs/examples/Path_addOval_2.cpp
+docs/examples/Path_addPath.cpp
+docs/examples/Path_arcTo_2_a.cpp
+docs/examples/Path_arcTo_2_b.cpp
+docs/examples/Path_contains.cpp
+docs/examples/Path_isConvex.cpp
+docs/examples/Path_lineTo.cpp
+docs/examples/Path_rMoveTo.cpp
+docs/examples/Path_setLastPt.cpp
+docs/examples/Path_setLastPt_2.cpp
+docs/examples/Path_toggleInverseFillType.cpp
+docs/examples/Picture_approximateBytesUsed.cpp
+docs/examples/Picture_approximateOpCount.cpp
+docs/examples/Pixmap_setColorSpace.cpp
+docs/examples/Point_CrossProduct.cpp
+docs/examples/Point_Distance.cpp
+docs/examples/Point_DotProduct.cpp
+docs/examples/Point_Length.cpp
+docs/examples/Point_Normalize.cpp
+docs/examples/Point_cross.cpp
+docs/examples/Point_distanceToOrigin.cpp
+docs/examples/Point_dot.cpp
+docs/examples/Point_length_2.cpp
+docs/examples/PreMultiplyARGB.cpp
+docs/examples/PreMultiplyColor.cpp
+docs/examples/RGBToHSV.cpp
+docs/examples/RRect_MakeEmpty.cpp
+docs/examples/RRect_Type.cpp
+docs/examples/RRect_contains.cpp
+docs/examples/RRect_equal_operator.cpp
+docs/examples/RRect_getSimpleRadii.cpp
+docs/examples/RRect_getType.cpp
+docs/examples/RRect_isComplex.cpp
+docs/examples/RRect_isEmpty.cpp
+docs/examples/RRect_isNinePatch.cpp
+docs/examples/RRect_isOval.cpp
+docs/examples/RRect_isRect.cpp
+docs/examples/RRect_isSimple.cpp
+docs/examples/RRect_isValid.cpp
+docs/examples/RRect_notequal_operator.cpp
+docs/examples/RRect_readFromMemory.cpp
+docs/examples/RRect_transform.cpp
+docs/examples/RRect_type_2.cpp
+docs/examples/RRect_writeToMemory.cpp
+docs/examples/Region_Op.cpp
+docs/examples/Region_contains.cpp
+docs/examples/Region_contains_2.cpp
+docs/examples/Region_contains_3.cpp
+docs/examples/Region_intersects.cpp
+docs/examples/Region_intersects_2.cpp
+docs/examples/Region_op_1.cpp
+docs/examples/Region_op_2.cpp
+docs/examples/Region_op_3.cpp
+docs/examples/Region_op_4.cpp
+docs/examples/Region_op_5.cpp
+docs/examples/Region_op_6.cpp
+docs/examples/Region_setPath.cpp
+docs/examples/Region_writeToMemory.cpp
+docs/examples/Surface_MakeFromBackendTexture.cpp
+docs/examples/Surface_MakeFromBackendTextureAsRenderTarget.cpp
+docs/examples/Surface_MakeRenderTarget.cpp
+docs/examples/Surface_MakeRenderTarget_2.cpp
+docs/examples/Surface_MakeRenderTarget_3.cpp
+docs/examples/Surface_characterize.cpp
+docs/examples/Surface_draw_2.cpp
+docs/examples/Surface_getCanvas.cpp
+docs/examples/Surface_peekPixels.cpp
+docs/examples/Surface_readPixels.cpp
+docs/examples/Surface_writePixels.cpp
+docs/examples/TextBlobBuilder_allocRun.cpp
+docs/examples/TextBlobBuilder_allocRunPos.cpp
+docs/examples/TextBlobBuilder_allocRunPosH.cpp
+docs/examples/TextBlobBuilder_make.cpp
+docs/examples/TextBlob_Deserialize.cpp
+docs/examples/TextBlob_MakeFromString.cpp
+docs/examples/TextBlob_MakeFromText.cpp
+docs/examples/TextBlob_bounds.cpp
+docs/examples/TextBlob_getIntercepts.cpp
+docs/examples/TextBlob_serialize.cpp
+docs/examples/TextBlob_uniqueID.cpp
+docs/examples/Text_Encoding.cpp
+docs/examples/Text_Scale_X.cpp
+docs/examples/Text_Size.cpp
+docs/examples/Text_Skew_X.cpp
+docs/examples/Typeface_Methods.cpp
diff --git a/tools/fiddle/documumentation_examples_map.txt b/tools/fiddle/documumentation_examples_map.txt
new file mode 100644
index 0000000..af49342
--- /dev/null
+++ b/tools/fiddle/documumentation_examples_map.txt
@@ -0,0 +1,4107 @@
+
+This file contains a mapping from where these documumentation examples
+should be inserted into the headers.
+
+###########################################################################
+
+[AutoCanvasRestore_SkCanvas_star]
+SkAutoCanvasRestore
+SkAutoCanvasRestore(SkCanvas* canvas, bool doSave);
+
+[AutoCanvasRestore_restore]
+SkAutoCanvasRestore
+void restore();
+
+[Bitmap_ComputeIsOpaque]
+SkBitmap
+static bool ComputeIsOpaque(const SkBitmap& bm);
+
+[Bitmap_empty_constructor]
+SkBitmap
+SkBitmap();
+
+[Bitmap_move_SkBitmap]
+SkBitmap
+SkBitmap(SkBitmap&& src);
+
+[Bitmap_copy_const_SkBitmap]
+SkBitmap
+SkBitmap(const SkBitmap& src);
+
+[Bitmap_allocN32Pixels]
+SkBitmap
+void allocN32Pixels(int width, int height, bool isOpaque = false);
+
+[Bitmap_HeapAllocator_allocPixelRef]
+SkBitmap
+bool allocPixelRef(SkBitmap* bitmap) override;
+
+[Bitmap_allocPixels_3]
+SkBitmap
+void allocPixels();
+
+[Bitmap_allocPixels_4]
+SkBitmap
+void allocPixels(Allocator* allocator);
+
+[Bitmap_allocPixels_2]
+SkBitmap
+void allocPixels(const SkImageInfo& info);
+
+[Bitmap_allocPixels]
+SkBitmap
+void allocPixels(const SkImageInfo& info, size_t rowBytes);
+
+[Bitmap_allocPixelsFlags]
+SkBitmap
+void allocPixelsFlags(const SkImageInfo& info, uint32_t flags);
+
+[Pixmap_alphaType]
+SkBitmap
+SkAlphaType alphaType() const;
+
+[Bitmap_bounds]
+SkBitmap
+SkIRect bounds() const;
+
+[Bitmap_bytesPerPixel]
+SkBitmap
+int bytesPerPixel() const;
+
+[Bitmap_colorSpace]
+SkBitmap
+SkColorSpace* colorSpace() const;
+
+[Bitmap_colorType]
+SkBitmap
+SkColorType colorType() const;
+
+[Bitmap_computeByteSize]
+SkBitmap
+size_t computeByteSize() const;
+
+[Bitmap_dimensions]
+SkBitmap
+SkISize dimensions() const;
+
+[Bitmap_drawsNothing]
+SkBitmap
+bool drawsNothing() const;
+
+[Bitmap_empty]
+SkBitmap
+bool empty() const;
+
+[Bitmap_erase]
+SkBitmap
+void erase(SkColor c, const SkIRect& area) const;
+
+[Bitmap_eraseARGB]
+SkBitmap
+void eraseARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b) const;
+
+[Bitmap_eraseColor]
+SkBitmap
+void eraseColor(SkColor c) const;
+
+[Bitmap_extractAlpha]
+SkBitmap
+bool extractAlpha(SkBitmap* dst) const;
+
+[Bitmap_extractAlpha_3]
+SkBitmap
+bool extractAlpha(SkBitmap* dst, const SkPaint* paint, Allocator* allocator, SkIPoint* offset) const;
+
+[Bitmap_extractAlpha_2]
+SkBitmap
+bool extractAlpha(SkBitmap* dst, const SkPaint* paint, SkIPoint* offset) const;
+
+[Bitmap_extractSubset]
+SkBitmap
+bool extractSubset(SkBitmap* dst, const SkIRect& subset) const;
+
+[Bitmap_getAddr]
+SkBitmap
+void* getAddr(int x, int y) const;
+
+[Bitmap_getAddr16]
+SkBitmap
+uint16_t* getAddr16(int x, int y) const;
+
+[Bitmap_getAddr32]
+SkBitmap
+uint32_t* getAddr32(int x, int y) const;
+
+[Bitmap_getAddr8]
+SkBitmap
+uint8_t* getAddr8(int x, int y) const;
+
+[Bitmap_getBounds_2]
+SkBitmap
+void getBounds(SkIRect* bounds) const;
+
+[Bitmap_getBounds]
+SkBitmap
+void getBounds(SkRect* bounds) const;
+
+[Bitmap_getColor]
+SkBitmap
+SkColor getColor(int x, int y) const;
+
+[Bitmap_getGenerationID]
+SkBitmap
+uint32_t getGenerationID() const;
+
+[Bitmap_getPixels]
+SkBitmap
+void* getPixels() const;
+
+[Bitmap_getSubset]
+SkBitmap
+SkIRect getSubset() const;
+
+[Bitmap_height]
+SkBitmap
+int height() const;
+
+[Bitmap_info]
+SkBitmap
+const SkImageInfo& info() const;
+
+[Bitmap_installPixels_2]
+SkBitmap
+bool installPixels(const SkImageInfo& info, void* pixels, size_t rowBytes);
+
+[Bitmap_installPixels]
+SkBitmap
+bool installPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, void (*releaseProc) (void* addr, void* context) , void* context);
+
+[Bitmap_installPixels_3]
+SkBitmap
+bool installPixels(const SkPixmap& pixmap);
+
+[Bitmap_isImmutable]
+SkBitmap
+bool isImmutable() const;
+
+[Bitmap_isNull]
+SkBitmap
+bool isNull() const;
+
+[Bitmap_isOpaque]
+SkBitmap
+bool isOpaque() const;
+
+[Bitmap_isVolatile]
+SkBitmap
+bool isVolatile() const;
+
+[Bitmap_notifyPixelsChanged]
+SkBitmap
+void notifyPixelsChanged() const;
+
+[Bitmap_move_operator]
+SkBitmap
+SkBitmap& operator=(SkBitmap&& src);
+
+[Bitmap_copy_operator]
+SkBitmap
+SkBitmap& operator=(const SkBitmap& src);
+
+[Bitmap_peekPixels]
+SkBitmap
+bool peekPixels(SkPixmap* pixmap) const;
+
+[Bitmap_pixelRef]
+SkBitmap
+SkPixelRef* pixelRef() const;
+
+[Bitmap_pixelRefOrigin]
+SkBitmap
+SkIPoint pixelRefOrigin() const;
+
+[Bitmap_pixmap]
+SkBitmap
+const SkPixmap& pixmap() const;
+
+[Bitmap_readPixels]
+SkBitmap
+bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, int srcX, int srcY) const;
+
+[Bitmap_readPixels_3]
+SkBitmap
+bool readPixels(const SkPixmap& dst) const;
+
+[Bitmap_readPixels_2]
+SkBitmap
+bool readPixels(const SkPixmap& dst, int srcX, int srcY) const;
+
+[Bitmap_readyToDraw]
+SkBitmap
+bool readyToDraw() const;
+
+[Bitmap_refColorSpace]
+SkBitmap
+sk_sp<SkColorSpace> refColorSpace() const;
+
+[Bitmap_reset]
+SkBitmap
+void reset();
+
+[Bitmap_rowBytes]
+SkBitmap
+size_t rowBytes() const;
+
+[Bitmap_rowBytesAsPixels]
+SkBitmap
+int rowBytesAsPixels() const;
+
+[Bitmap_setAlphaType]
+SkBitmap
+bool setAlphaType(SkAlphaType alphaType);
+
+[Bitmap_setImmutable]
+SkBitmap
+void setImmutable();
+
+[Bitmap_setInfo]
+SkBitmap
+bool setInfo(const SkImageInfo& imageInfo, size_t rowBytes = 0);
+
+[Bitmap_setIsVolatile]
+SkBitmap
+void setIsVolatile(bool isVolatile);
+
+[Bitmap_setPixelRef]
+SkBitmap
+void setPixelRef(sk_sp<SkPixelRef> pixelRef, int dx, int dy);
+
+[Bitmap_setPixels]
+SkBitmap
+void setPixels(void* pixels);
+
+[Bitmap_shiftPerPixel]
+SkBitmap
+int shiftPerPixel() const;
+
+[Bitmap_swap]
+SkBitmap
+void swap(SkBitmap& other);
+
+[Bitmap_tryAllocN32Pixels]
+SkBitmap
+bool tryAllocN32Pixels(int width, int height, bool isOpaque = false);
+
+[Bitmap_tryAllocPixels_3]
+SkBitmap
+bool tryAllocPixels();
+
+[Bitmap_tryAllocPixels_4]
+SkBitmap
+bool tryAllocPixels(Allocator* allocator);
+
+[Bitmap_tryAllocPixels_2]
+SkBitmap
+bool tryAllocPixels(const SkImageInfo& info);
+
+[Bitmap_tryAllocPixels]
+SkBitmap
+bool tryAllocPixels(const SkImageInfo& info, size_t rowBytes);
+
+[Bitmap_tryAllocPixelsFlags]
+SkBitmap
+bool tryAllocPixelsFlags(const SkImageInfo& info, uint32_t flags);
+
+[Bitmap_width]
+SkBitmap
+int width() const;
+
+[Bitmap_writePixels_2]
+SkBitmap
+bool writePixels(const SkPixmap& src);
+
+[Bitmap_writePixels]
+SkBitmap
+bool writePixels(const SkPixmap& src, int dstX, int dstY);
+
+[BlendMode_Name]
+SkBlendMode
+const char* SkBlendMode_Name(SkBlendMode blendMode);
+
+[Clear]
+[Color]
+[Color_Burn]
+[Color_Dodge]
+[Darken]
+[Difference]
+[Dst]
+[Dst_Atop]
+[Dst_In]
+[Dst_Out]
+[Dst_Over]
+[Exclusion]
+[Hard_Light]
+[Hue]
+[Lighten]
+[Luminosity]
+[Modulate]
+[Multiply]
+[Overlay]
+[Plus]
+[Saturation]
+[Screen]
+[Soft_Light]
+[Src]
+[Src_Atop]
+[Src_In]
+[Src_Out]
+[Src_Over]
+[Xor]
+SkBlendMode
+enum class SkBlendMode { kClear, kSrc, kDst, kSrcOver, kDstOver, kSrcIn, kDstIn, kSrcOut, kDstOut, kSrcATop, kDstATop, kXor, kPlus, kModulate, kScreen, kLastCoeffMode = kScreen, kOverlay, kDarken, kLighten, kColorDodge, kColorBurn, kHardLight, kSoftLight, kDifference, kExclusion, kMultiply, kLastSeparableMode = kMultiply, kHue, kSaturation, kColor, kLuminosity, kLastMode = kLuminosity, }; const char* SkBlendMode_Name(SkBlendMode blendMode);
+
+[Canvas_MakeRasterDirect]
+SkCanvas
+static std::unique_ptr<SkCanvas> MakeRasterDirect(const SkImageInfo& info, void* pixels, size_t rowBytes, const SkSurfaceProps* props = nullptr);
+
+[Canvas_MakeRasterDirectN32]
+SkCanvas
+static std::unique_ptr<SkCanvas> MakeRasterDirectN32(int width, int height, SkPMColor* pixels, size_t rowBytes);
+
+[Canvas_SaveLayerRec]
+SkCanvas
+struct SaveLayerRec { SaveLayerRec(); SaveLayerRec(const SkRect* bounds, const SkPaint* paint, SaveLayerFlags saveLayerFlags = 0); SaveLayerRec(const SkRect* bounds, const SkPaint* paint, const SkImageFilter* backdrop, SaveLayerFlags saveLayerFlags); const SkRect* fBounds = nullptr; const SkPaint* fPaint = nullptr; const SkImageFilter* fBackdrop = nullptr; const SkImage* fClipMask = nullptr; const SkMatrix* fClipMatrix = nullptr; SaveLayerFlags fSaveLayerFlags = 0; };
+
+[Canvas_SaveLayerRec_SaveLayerRec]
+SkCanvas
+SaveLayerRec();
+
+[Canvas_SaveLayerRec_const_SkRect_star_const_SkPaint_star]
+SkCanvas
+SaveLayerRec(const SkRect* bounds, const SkPaint* paint, SaveLayerFlags saveLayerFlags = 0);
+
+[Canvas_SaveLayerRec_const_SkRect_star_const_SkPaint_star_const_SkImageFilter_star]
+SkCanvas
+SaveLayerRec(const SkRect* bounds, const SkPaint* paint, const SkImageFilter* backdrop, SaveLayerFlags saveLayerFlags);
+
+[Canvas_copy_const_SkBitmap]
+SkCanvas
+explicit SkCanvas(const SkBitmap& bitmap);
+
+[Canvas_empty_constructor]
+SkCanvas
+SkCanvas();
+
+[Canvas_const_SkBitmap_const_SkSurfaceProps]
+SkCanvas
+SkCanvas(const SkBitmap& bitmap, const SkSurfaceProps& props);
+
+[Canvas_int_int_const_SkSurfaceProps_star]
+SkCanvas
+SkCanvas(int width, int height, const SkSurfaceProps* props = nullptr);
+
+[Canvas_accessTopLayerPixels_a]
+[Canvas_accessTopLayerPixels_b]
+SkCanvas
+void* accessTopLayerPixels(SkImageInfo* info, size_t* rowBytes, SkIPoint* origin = nullptr);
+
+[Canvas_accessTopRasterHandle]
+SkCanvas
+SkRasterHandleAllocator::Handle accessTopRasterHandle() const;
+
+[Canvas_clear]
+SkCanvas
+void clear(SkColor color);
+
+[Canvas_clipPath_2]
+SkCanvas
+void clipPath(const SkPath& path, SkClipOp op);
+
+[Canvas_clipPath]
+SkCanvas
+void clipPath(const SkPath& path, SkClipOp op, bool doAntiAlias);
+
+[Canvas_clipPath_3]
+SkCanvas
+void clipPath(const SkPath& path, bool doAntiAlias = false);
+
+[Canvas_clipRRect_2]
+SkCanvas
+void clipRRect(const SkRRect& rrect, SkClipOp op);
+
+[Canvas_clipRRect]
+SkCanvas
+void clipRRect(const SkRRect& rrect, SkClipOp op, bool doAntiAlias);
+
+[Canvas_clipRRect_3]
+SkCanvas
+void clipRRect(const SkRRect& rrect, bool doAntiAlias = false);
+
+[Canvas_clipRect_2]
+SkCanvas
+void clipRect(const SkRect& rect, SkClipOp op);
+
+[Canvas_clipRect]
+SkCanvas
+void clipRect(const SkRect& rect, SkClipOp op, bool doAntiAlias);
+
+[Canvas_clipRect_3]
+SkCanvas
+void clipRect(const SkRect& rect, bool doAntiAlias = false);
+
+[Canvas_clipRegion]
+SkCanvas
+void clipRegion(const SkRegion& deviceRgn, SkClipOp op = SkClipOp::kIntersect);
+
+[Canvas_concat]
+SkCanvas
+void concat(const SkMatrix& matrix);
+
+[Canvas_drawAnnotation_2]
+SkCanvas
+void drawAnnotation(const SkRect& rect, const char key[], SkData* value);
+
+[Canvas_drawAnnotation_2]
+SkCanvas
+void drawAnnotation(const SkRect& rect, const char key[], const sk_sp<SkData>& value);
+
+[Canvas_drawArc_a]
+[Canvas_drawArc_b]
+SkCanvas
+void drawArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter, const SkPaint& paint);
+
+[Canvas_drawAtlas]
+SkCanvas
+void drawAtlas(const SkImage* atlas, const SkRSXform xform[], const SkRect tex[], const SkColor colors[], int count, SkBlendMode mode, const SkRect* cullRect, const SkPaint* paint);
+
+[Canvas_drawAtlas_3]
+SkCanvas
+void drawAtlas(const SkImage* atlas, const SkRSXform xform[], const SkRect tex[], int count, const SkRect* cullRect, const SkPaint* paint);
+
+[Canvas_drawAtlas_2]
+SkCanvas
+void drawAtlas(const sk_sp<SkImage>& atlas, const SkRSXform xform[], const SkRect tex[], const SkColor colors[], int count, SkBlendMode mode, const SkRect* cullRect, const SkPaint* paint);
+
+[Canvas_drawAtlas_4]
+SkCanvas
+void drawAtlas(const sk_sp<SkImage>& atlas, const SkRSXform xform[], const SkRect tex[], int count, const SkRect* cullRect, const SkPaint* paint);
+
+[Canvas_drawBitmap]
+SkCanvas
+void drawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top, const SkPaint* paint = nullptr);
+
+[Canvas_drawBitmapLattice]
+SkCanvas
+void drawBitmapLattice(const SkBitmap& bitmap, const Lattice& lattice, const SkRect& dst, const SkPaint* paint = nullptr);
+
+[Canvas_drawBitmapNine]
+SkCanvas
+void drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, const SkRect& dst, const SkPaint* paint = nullptr);
+
+[Canvas_drawBitmapRect_2]
+SkCanvas
+void drawBitmapRect(const SkBitmap& bitmap, const SkIRect& isrc, const SkRect& dst, const SkPaint* paint, SrcRectConstraint constraint = kStrict_SrcRectConstraint);
+
+[Canvas_drawBitmapRect_3]
+SkCanvas
+void drawBitmapRect(const SkBitmap& bitmap, const SkRect& dst, const SkPaint* paint, SrcRectConstraint constraint = kStrict_SrcRectConstraint);
+
+[Canvas_drawBitmapRect]
+SkCanvas
+void drawBitmapRect(const SkBitmap& bitmap, const SkRect& src, const SkRect& dst, const SkPaint* paint, SrcRectConstraint constraint = kStrict_SrcRectConstraint);
+
+[Canvas_drawCircle_2]
+SkCanvas
+void drawCircle(SkPoint center, SkScalar radius, const SkPaint& paint);
+
+[Canvas_drawCircle]
+SkCanvas
+void drawCircle(SkScalar cx, SkScalar cy, SkScalar radius, const SkPaint& paint);
+
+[Canvas_drawColor]
+SkCanvas
+void drawColor(SkColor color, SkBlendMode mode = SkBlendMode::kSrcOver);
+
+[Canvas_drawDRRect_a]
+[Canvas_drawDRRect_b]
+SkCanvas
+void drawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint);
+
+[Canvas_drawDrawable_2]
+SkCanvas
+void drawDrawable(SkDrawable* drawable, SkScalar x, SkScalar y);
+
+[Canvas_drawDrawable]
+SkCanvas
+void drawDrawable(SkDrawable* drawable, const SkMatrix* matrix = nullptr);
+
+[Canvas_drawIRect]
+SkCanvas
+void drawIRect(const SkIRect& rect, const SkPaint& paint);
+
+[Canvas_drawImage]
+SkCanvas
+void drawImage(const SkImage* image, SkScalar left, SkScalar top, const SkPaint* paint = nullptr);
+
+[Canvas_drawImage_2]
+SkCanvas
+void drawImage(const sk_sp<SkImage>& image, SkScalar left, SkScalar top, const SkPaint* paint = nullptr);
+
+[Canvas_drawImageNine]
+SkCanvas
+void drawImageLattice(const SkImage* image, const Lattice& lattice, const SkRect& dst, const SkPaint* paint = nullptr);
+
+[Canvas_drawImageNine]
+SkCanvas
+void drawImageNine(const SkImage* image, const SkIRect& center, const SkRect& dst, const SkPaint* paint = nullptr);
+
+[Canvas_drawImageNine_2]
+SkCanvas
+void drawImageNine(const sk_sp<SkImage>& image, const SkIRect& center, const SkRect& dst, const SkPaint* paint = nullptr);
+
+[Canvas_drawImageRect_2]
+SkCanvas
+void drawImageRect(const SkImage* image, const SkIRect& isrc, const SkRect& dst, const SkPaint* paint, SrcRectConstraint constraint = kStrict_SrcRectConstraint);
+
+[Canvas_drawImageRect_3]
+SkCanvas
+void drawImageRect(const SkImage* image, const SkRect& dst, const SkPaint* paint);
+
+[Canvas_drawImageRect]
+SkCanvas
+void drawImageRect(const SkImage* image, const SkRect& src, const SkRect& dst, const SkPaint* paint, SrcRectConstraint constraint = kStrict_SrcRectConstraint);
+
+[Canvas_drawImageRect_5]
+SkCanvas
+void drawImageRect(const sk_sp<SkImage>& image, const SkIRect& isrc, const SkRect& dst, const SkPaint* paint, SrcRectConstraint constraint = kStrict_SrcRectConstraint);
+
+[Canvas_drawImageRect_6]
+SkCanvas
+void drawImageRect(const sk_sp<SkImage>& image, const SkRect& dst, const SkPaint* paint);
+
+[Canvas_drawImageRect_4]
+SkCanvas
+void drawImageRect(const sk_sp<SkImage>& image, const SkRect& src, const SkRect& dst, const SkPaint* paint, SrcRectConstraint constraint = kStrict_SrcRectConstraint);
+
+[Canvas_drawLine_2]
+SkCanvas
+void drawLine(SkPoint p0, SkPoint p1, const SkPaint& paint);
+
+[Canvas_drawLine]
+SkCanvas
+void drawLine(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1, const SkPaint& paint);
+
+[Canvas_drawOval]
+SkCanvas
+void drawOval(const SkRect& oval, const SkPaint& paint);
+
+[Canvas_drawPaint]
+SkCanvas
+void drawPaint(const SkPaint& paint);
+
+[Canvas_drawPatch]
+SkCanvas
+void drawPatch(const SkPoint cubics[12], const SkColor colors[4], const SkPoint texCoords[4], SkBlendMode mode, const SkPaint& paint);
+
+[Canvas_drawPatch_2_a]
+[Canvas_drawPatch_2_b]
+SkCanvas
+void drawPatch(const SkPoint cubics[12], const SkColor colors[4], const SkPoint texCoords[4], const SkPaint& paint);
+
+[Canvas_drawPath]
+SkCanvas
+void drawPath(const SkPath& path, const SkPaint& paint);
+
+[Canvas_drawPicture_2]
+SkCanvas
+void drawPicture(const SkPicture* picture);
+
+[Canvas_drawPicture_3]
+SkCanvas
+void drawPicture(const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint);
+
+[Canvas_drawPicture_2]
+SkCanvas
+void drawPicture(const sk_sp<SkPicture>& picture);
+
+[Canvas_drawPicture_4]
+SkCanvas
+void drawPicture(const sk_sp<SkPicture>& picture, const SkMatrix* matrix, const SkPaint* paint);
+
+[Canvas_drawPoint_2]
+SkCanvas
+void drawPoint(SkPoint p, const SkPaint& paint);
+
+[Canvas_drawPoint]
+SkCanvas
+void drawPoint(SkScalar x, SkScalar y, const SkPaint& paint);
+
+[Canvas_drawPoints]
+SkCanvas
+void drawPoints(PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint);
+
+[Canvas_drawPosText]
+SkCanvas
+void drawPosText(const void* text, size_t byteLength, const SkPoint pos[], const SkPaint& paint);
+
+[Canvas_drawPosTextH]
+SkCanvas
+void drawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], SkScalar constY, const SkPaint& paint);
+
+[Canvas_drawRRect]
+SkCanvas
+void drawRRect(const SkRRect& rrect, const SkPaint& paint);
+
+[Canvas_drawRect]
+SkCanvas
+void drawRect(const SkRect& rect, const SkPaint& paint);
+
+[Canvas_drawRegion]
+SkCanvas
+void drawRegion(const SkRegion& region, const SkPaint& paint);
+
+[Canvas_drawRoundRect]
+SkCanvas
+void drawRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry, const SkPaint& paint);
+
+[Canvas_drawString_2]
+SkCanvas
+void drawString(const SkString& string, SkScalar x, SkScalar y, const SkPaint& paint);
+
+[Canvas_drawString]
+SkCanvas
+void drawString(const char* string, SkScalar x, SkScalar y, const SkPaint& paint);
+
+[Canvas_drawText]
+SkCanvas
+void drawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, const SkPaint& paint);
+
+[Canvas_drawTextBlob]
+SkCanvas
+void drawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint);
+
+[Canvas_drawTextBlob_2]
+SkCanvas
+void drawTextBlob(const sk_sp<SkTextBlob>& blob, SkScalar x, SkScalar y, const SkPaint& paint);
+
+[Canvas_drawTextRSXform]
+SkCanvas
+void drawTextRSXform(const void* text, size_t byteLength, const SkRSXform xform[], const SkRect* cullRect, const SkPaint& paint);
+
+[Canvas_drawVertices]
+SkCanvas
+void drawVertices(const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint);
+
+[Canvas_drawVertices_2]
+SkCanvas
+void drawVertices(const sk_sp<SkVertices>& vertices, SkBlendMode mode, const SkPaint& paint);
+
+[Canvas_PointMode]
+SkCanvas
+enum PointMode { kPoints_PointMode, kLines_PointMode, kPolygon_PointMode, };
+
+[Canvas_kInitWithPrevious_SaveLayerFlag]
+SkCanvas
+enum SaveLayerFlagsSet { kPreserveLCDText_SaveLayerFlag = 1 << 1, kInitWithPrevious_SaveLayerFlag = 1 << 2, };
+
+[Canvas_SrcRectConstraint]
+SkCanvas
+enum SrcRectConstraint { kStrict_SrcRectConstraint, kFast_SrcRectConstraint, };
+
+[Canvas_getBaseLayerSize]
+SkCanvas
+virtual SkISize getBaseLayerSize() const;
+
+[Canvas_getDeviceClipBounds]
+SkCanvas
+SkIRect getDeviceClipBounds() const;
+
+[Canvas_getDeviceClipBounds_2]
+SkCanvas
+bool getDeviceClipBounds(SkIRect* bounds) const;
+
+[Canvas_getGrContext]
+SkCanvas
+virtual GrContext* getGrContext();
+
+[Canvas_getLocalClipBounds]
+SkCanvas
+SkRect getLocalClipBounds() const;
+
+[Canvas_getLocalClipBounds_2]
+SkCanvas
+bool getLocalClipBounds(SkRect* bounds) const;
+
+[Canvas_getProps]
+SkCanvas
+bool getProps(SkSurfaceProps* props) const;
+
+[Canvas_getSaveCount]
+SkCanvas
+int getSaveCount() const;
+
+[Canvas_getTotalMatrix]
+[Clip]
+SkCanvas
+const SkMatrix& getTotalMatrix() const;
+
+[Canvas_imageInfo]
+SkCanvas
+SkImageInfo imageInfo() const;
+
+[Canvas_isClipEmpty]
+SkCanvas
+virtual bool isClipEmpty() const;
+
+[Canvas_isClipRect]
+SkCanvas
+virtual bool isClipRect() const;
+
+[Canvas_makeSurface]
+SkCanvas
+sk_sp<SkSurface> makeSurface(const SkImageInfo& info, const SkSurfaceProps* props = nullptr);
+
+[Canvas_peekPixels]
+SkCanvas
+bool peekPixels(SkPixmap* pixmap);
+
+[Canvas_quickReject_2]
+SkCanvas
+bool quickReject(const SkPath& path) const;
+
+[Canvas_quickReject]
+SkCanvas
+bool quickReject(const SkRect& rect) const;
+
+[Canvas_readPixels_3]
+SkCanvas
+bool readPixels(const SkBitmap& bitmap, int srcX, int srcY);
+
+[Canvas_readPixels_a]
+[Canvas_readPixels_b]
+SkCanvas
+bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, int srcX, int srcY);
+
+[Canvas_readPixels_2]
+SkCanvas
+bool readPixels(const SkPixmap& pixmap, int srcX, int srcY);
+
+[Canvas_resetMatrix]
+SkCanvas
+void resetMatrix();
+
+[Canvas_restore]
+SkCanvas
+void restore();
+
+[Canvas_restoreToCount]
+SkCanvas
+void restoreToCount(int saveCount);
+
+[Canvas_rotate]
+SkCanvas
+void rotate(SkScalar degrees);
+
+[Canvas_rotate_2]
+SkCanvas
+void rotate(SkScalar degrees, SkScalar px, SkScalar py);
+
+[Canvas_save]
+SkCanvas
+int save();
+
+[Canvas_saveLayer_3]
+SkCanvas
+int saveLayer(const SaveLayerRec& layerRec);
+
+[Canvas_saveLayer_2]
+SkCanvas
+int saveLayer(const SkRect& bounds, const SkPaint* paint);
+
+[Canvas_saveLayer]
+SkCanvas
+int saveLayer(const SkRect* bounds, const SkPaint* paint);
+
+[Canvas_saveLayerAlpha]
+SkCanvas
+int saveLayerAlpha(const SkRect* bounds, U8CPU alpha);
+
+[Canvas_saveLayerPreserveLCDTextRequests]
+SkCanvas
+int saveLayerPreserveLCDTextRequests(const SkRect* bounds, const SkPaint* paint);
+
+[Canvas_scale]
+SkCanvas
+void scale(SkScalar sx, SkScalar sy);
+
+[Canvas_setMatrix]
+SkCanvas
+void setMatrix(const SkMatrix& matrix);
+
+[Canvas_skew]
+SkCanvas
+void skew(SkScalar sx, SkScalar sy);
+
+[Canvas_translate]
+SkCanvas
+void translate(SkScalar dx, SkScalar dy);
+
+[Canvas_writePixels_2]
+[State_Stack_a]
+[State_Stack_b]
+SkCanvas
+bool writePixels(const SkBitmap& bitmap, int x, int y);
+
+[Canvas_writePixels]
+SkCanvas
+bool writePixels(const SkImageInfo& info, const void* pixels, size_t rowBytes, int x, int y);
+
+[Canvas_destructor]
+SkCanvas
+virtual ~SkCanvas();
+
+[ColorGetA]
+SkColor
+#define SkColorGetA(color) (((color) >> 24) & 0xFF) color;
+
+[ColorGetB]
+SkColor
+#define SkColorGetB(color) (((color) >> 0) & 0xFF) color;
+
+[ColorGetG]
+SkColor
+#define SkColorGetG(color) (((color) >> 8) & 0xFF) color;
+
+[ColorGetR]
+SkColor
+#define SkColorGetR(color) (((color) >> 16) & 0xFF) color;
+
+[ColorSetA]
+SkColor
+static constexpr inline SkColor SkColorSetA(SkColor c, U8CPU a);
+
+[ColorSetRGB]
+SkColor
+#define SkColorSetRGB(r, g, b) SkColorSetARGB(0xFF, r, g, b) r g b;
+
+[ColorSetARGB]
+SkColor
+static constexpr inline SkColor SkColorSetARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
+
+[ColorToHSV]
+SkColor
+static void SkColorToHSV(SkColor color, SkScalar hsv[3]);
+
+[HSVToColor]
+SkColor
+SkColor SkHSVToColor(U8CPU alpha, const SkScalar hsv[3]);
+
+[HSVToColor_2]
+SkColor
+static SkColor SkHSVToColor(const SkScalar hsv[3]);
+
+[PreMultiplyARGB]
+SkColor
+SkPMColor SkPreMultiplyARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
+
+[PreMultiplyColor]
+SkColor
+SkPMColor SkPreMultiplyColor(SkColor c);
+
+[RGBToHSV]
+SkColor
+void SkRGBToHSV(U8CPU red, U8CPU green, U8CPU blue, SkScalar hsv[3]);
+
+[Alpha_Constants_a]
+[Alpha_Constants_b]
+SkColor
+constexpr SkAlpha SK_AlphaTRANSPARENT = 0x00; constexpr SkAlpha SK_AlphaOPAQUE = 0xFF;
+
+[Color_Constants_a]
+[Color_Constants_b]
+[Color_Constants_c]
+[Color_Constants_d]
+SkColor
+constexpr SkColor SK_ColorTRANSPARENT; constexpr SkColor SK_ColorBLACK; constexpr SkColor SK_ColorDKGRAY; constexpr SkColor SK_ColorGRAY; constexpr SkColor SK_ColorLTGRAY; constexpr SkColor SK_ColorWHITE; constexpr SkColor SK_ColorRED; constexpr SkColor SK_ColorGREEN; constexpr SkColor SK_ColorBLUE; constexpr SkColor SK_ColorYELLOW; constexpr SkColor SK_ColorCYAN; constexpr SkColor SK_ColorMAGENTA;
+
+[RGBA4f_FromColor]
+SkColor4f
+static SkRGBA4f FromColor(SkColor color);
+
+[RGBA4f_notequal1_operator]
+SkColor4f
+bool operator!=(const SkRGBA4f& other) const;
+
+[RGBA4f_equal1_operator]
+SkColor4f
+bool operator==(const SkRGBA4f& other) const;
+
+[RGBA4f_toSkColor]
+SkColor4f
+SkColor toSkColor() const;
+
+[RGBA4f_vec]
+SkColor4f
+const float* vec() const;
+
+[RGBA4f_vec_2]
+SkColor4f
+float* vec();
+
+[Font_breakText]
+SkFont
+size_t breakText(const void* text, size_t length, SkTextEncoding encoding, SkScalar maxWidth, SkScalar* measuredWidth = nullptr) const;
+
+[IPoint_Make]
+SkIPoint
+static constexpr SkIPoint Make(int32_t x, int32_t y);
+
+[IPoint_equals]
+SkIPoint
+bool equals(int32_t x, int32_t y) const;
+
+[IPoint_isZero]
+SkIPoint
+bool isZero() const;
+
+[IPoint_notequal_operator]
+SkIPoint
+bool operator!=(const SkIPoint& a, const SkIPoint& b);
+
+[IPoint_add_operator]
+SkIPoint
+SkIPoint operator+(const SkIPoint& a, const SkIVector& b);
+
+[IPoint_addto_operator]
+SkIPoint
+void operator+=(const SkIVector& v);
+
+[IPoint_minus_operator]
+SkIPoint
+SkIPoint operator-() const;
+
+[IPoint_subtract_operator]
+SkIPoint
+SkIVector operator-(const SkIPoint& a, const SkIPoint& b);
+
+[IPoint_subtractfrom_operator]
+SkIPoint
+void operator-=(const SkIVector& v);
+
+[IPoint_equal_operator]
+SkIPoint
+bool operator==(const SkIPoint& a, const SkIPoint& b);
+
+[IPoint_set]
+SkIPoint
+void set(int32_t x, int32_t y);
+
+[IPoint_x]
+SkIPoint
+int32_t x() const;
+
+[IPoint_y]
+SkIPoint
+int32_t y() const;
+
+[IRect_EmptyIRect]
+SkIRect
+static const SkIRect& EmptyIRect();
+
+[IRect_Intersects]
+SkIRect
+static bool Intersects(const SkIRect& a, const SkIRect& b);
+
+[IRect_IntersectsNoEmptyCheck]
+SkIRect
+static bool IntersectsNoEmptyCheck(const SkIRect& a, const SkIRect& b);
+
+[IRect_MakeEmpty]
+SkIRect
+static constexpr SkIRect MakeEmpty();
+
+[IRect_MakeLTRB]
+SkIRect
+static constexpr SkIRect MakeLTRB(int32_t l, int32_t t, int32_t r, int32_t b);
+
+[IRect_MakeSize]
+SkIRect
+static constexpr SkIRect MakeSize(const SkISize& size);
+
+[IRect_MakeWH]
+SkIRect
+static constexpr SkIRect MakeWH(int32_t w, int32_t h);
+
+[IRect_MakeXYWH]
+SkIRect
+static constexpr SkIRect MakeXYWH(int32_t x, int32_t y, int32_t w, int32_t h);
+
+[IRect_adjust]
+SkIRect
+void adjust(int32_t dL, int32_t dT, int32_t dR, int32_t dB);
+
+[IRect_bottom]
+SkIRect
+int32_t bottom() const;
+
+[IRect_contains_3]
+SkIRect
+bool contains(const SkIRect& r) const;
+
+[IRect_contains_4]
+SkIRect
+bool contains(const SkRect& r) const;
+
+[IRect_contains_2]
+SkIRect
+bool contains(int32_t left, int32_t top, int32_t right, int32_t bottom) const;
+
+[IRect_contains]
+SkIRect
+bool contains(int32_t x, int32_t y) const;
+
+[IRect_containsNoEmptyCheck_2]
+SkIRect
+bool containsNoEmptyCheck(const SkIRect& r) const;
+
+[IRect_containsNoEmptyCheck]
+SkIRect
+bool containsNoEmptyCheck(int32_t left, int32_t top, int32_t right, int32_t bottom) const;
+
+[IRect_height]
+SkIRect
+int32_t height() const;
+
+[IRect_height64]
+SkIRect
+int64_t height64() const;
+
+[IRect_inset]
+SkIRect
+void inset(int32_t dx, int32_t dy);
+
+[IRect_intersect_2]
+SkIRect
+bool intersect(const SkIRect& a, const SkIRect& b);
+
+[IRect_intersect]
+SkIRect
+bool intersect(const SkIRect& r);
+
+[IRect_intersect_3]
+SkIRect
+bool intersect(int32_t left, int32_t top, int32_t right, int32_t bottom);
+
+[IRect_intersectNoEmptyCheck]
+SkIRect
+bool intersectNoEmptyCheck(const SkIRect& a, const SkIRect& b);
+
+[IRect_isEmpty]
+SkIRect
+bool isEmpty() const;
+
+[IRect_isEmpty64]
+SkIRect
+bool isEmpty64() const;
+
+[IRect_join_2]
+SkIRect
+void join(const SkIRect& r);
+
+[IRect_join]
+SkIRect
+void join(int32_t left, int32_t top, int32_t right, int32_t bottom);
+
+[IRect_left]
+SkIRect
+int32_t left() const;
+
+[IRect_makeInset]
+SkIRect
+SkIRect makeInset(int32_t dx, int32_t dy) const;
+
+[IRect_makeOffset]
+SkIRect
+SkIRect makeOffset(int32_t dx, int32_t dy) const;
+
+[IRect_makeOutset]
+SkIRect
+SkIRect makeOutset(int32_t dx, int32_t dy) const;
+
+[IRect_makeSorted]
+SkIRect
+SkIRect makeSorted() const;
+
+[IRect_offset_2]
+SkIRect
+void offset(const SkIPoint& delta);
+
+[IRect_offset]
+SkIRect
+void offset(int32_t dx, int32_t dy);
+
+[IRect_offsetTo]
+SkIRect
+void offsetTo(int32_t newX, int32_t newY);
+
+[IRect_notequal_operator]
+SkIRect
+bool operator!=(const SkIRect& a, const SkIRect& b);
+
+[IRect_equal_operator]
+SkIRect
+bool operator==(const SkIRect& a, const SkIRect& b);
+
+[IRect_outset]
+SkIRect
+void outset(int32_t dx, int32_t dy);
+
+[IRect_right]
+SkIRect
+int32_t right() const;
+
+[IRect_set]
+SkIRect
+void set(int32_t left, int32_t top, int32_t right, int32_t bottom);
+
+[IRect_setEmpty]
+SkIRect
+void setEmpty();
+
+[IRect_setLTRB]
+SkIRect
+void setLTRB(int32_t left, int32_t top, int32_t right, int32_t bottom);
+
+[IRect_setXYWH]
+SkIRect
+void setXYWH(int32_t x, int32_t y, int32_t width, int32_t height);
+
+[IRect_size]
+SkIRect
+SkISize size() const;
+
+[IRect_sort]
+SkIRect
+void sort();
+
+[IRect_top]
+SkIRect
+int32_t top() const;
+
+[IRect_width]
+SkIRect
+int32_t width() const;
+
+[IRect_width64]
+SkIRect
+int64_t width64() const;
+
+[IRect_x]
+SkIRect
+int32_t x() const;
+
+[IRect_y]
+SkIRect
+int32_t y() const;
+
+[Image_MakeBackendTextureFromSkImage]
+SkImage
+static bool MakeBackendTextureFromSkImage(GrContext* context, sk_sp<SkImage> image, GrBackendTexture* backendTexture, BackendTextureReleaseProc* backendTextureReleaseProc);
+
+[Image_MakeCrossContextFromEncoded]
+SkImage
+static sk_sp<SkImage> MakeCrossContextFromEncoded(GrContext* context, sk_sp<SkData> data, bool buildMips, SkColorSpace* dstColorSpace, bool limitToMaxTextureSize = false);
+
+[Image_MakeCrossContextFromPixmap]
+SkImage
+static sk_sp<SkImage> MakeCrossContextFromPixmap(GrContext* context, const SkPixmap& pixmap, bool buildMips, SkColorSpace* dstColorSpace, bool limitToMaxTextureSize = false);
+
+[Image_MakeFromAdoptedTexture]
+SkImage
+static sk_sp<SkImage> MakeFromAdoptedTexture(GrContext* context, const GrBackendTexture& backendTexture, GrSurfaceOrigin surfaceOrigin, SkColorType colorType, SkAlphaType alphaType = kPremul_SkAlphaType, sk_sp<SkColorSpace> colorSpace = nullptr);
+
+[Image_MakeFromBitmap]
+SkImage
+static sk_sp<SkImage> MakeFromBitmap(const SkBitmap& bitmap);
+
+[Image_MakeFromEncoded]
+SkImage
+static sk_sp<SkImage> MakeFromEncoded(sk_sp<SkData> encoded, const SkIRect* subset = nullptr);
+
+[Image_MakeFromGenerator]
+SkImage
+static sk_sp<SkImage> MakeFromGenerator(std::unique_ptr<SkImageGenerator> imageGenerator, const SkIRect* subset = nullptr);
+
+[Image_MakeFromPicture]
+SkImage
+static sk_sp<SkImage> MakeFromPicture(sk_sp<SkPicture> picture, const SkISize& dimensions, const SkMatrix* matrix, const SkPaint* paint, BitDepth bitDepth, sk_sp<SkColorSpace> colorSpace);
+
+[Image_MakeFromRaster]
+SkImage
+static sk_sp<SkImage> MakeFromRaster(const SkPixmap& pixmap, RasterReleaseProc rasterReleaseProc, ReleaseContext releaseContext);
+
+[Image_MakeFromTexture]
+SkImage
+static sk_sp<SkImage> MakeFromTexture(GrContext* context, const GrBackendTexture& backendTexture, GrSurfaceOrigin origin, SkColorType colorType, SkAlphaType alphaType, sk_sp<SkColorSpace> colorSpace);
+
+[Image_MakeFromTexture_2]
+SkImage
+static sk_sp<SkImage> MakeFromTexture(GrContext* context, const GrBackendTexture& backendTexture, GrSurfaceOrigin origin, SkColorType colorType, SkAlphaType alphaType, sk_sp<SkColorSpace> colorSpace, TextureReleaseProc textureReleaseProc, ReleaseContext releaseContext);
+
+[Image_MakeRasterCopy]
+SkImage
+static sk_sp<SkImage> MakeRasterCopy(const SkPixmap& pixmap);
+
+[Image_MakeRasterData]
+SkImage
+static sk_sp<SkImage> MakeRasterData(const SkImageInfo& info, sk_sp<SkData> pixels, size_t rowBytes);
+
+[Image_alphaType]
+SkImage
+SkAlphaType alphaType() const;
+
+[Image_bounds]
+SkImage
+SkIRect bounds() const;
+
+[Image_colorSpace]
+SkImage
+SkColorSpace* colorSpace() const;
+
+[Image_colorType]
+SkImage
+SkColorType colorType() const;
+
+[Image_dimensions]
+SkImage
+SkISize dimensions() const;
+
+[Image_encodeToData_2]
+SkImage
+sk_sp<SkData> encodeToData() const;
+
+[Image_encodeToData]
+SkImage
+sk_sp<SkData> encodeToData(SkEncodedImageFormat encodedImageFormat, int quality) const;
+
+[Image_getBackendTexture]
+SkImage
+GrBackendTexture getBackendTexture(bool flushPendingGrContextIO, GrSurfaceOrigin* origin = nullptr) const;
+
+[Image_height]
+SkImage
+int height() const;
+
+[Image_isAlphaOnly]
+SkImage
+bool isAlphaOnly() const;
+
+[Image_isLazyGenerated_a]
+[Image_isLazyGenerated_b]
+SkImage
+bool isLazyGenerated() const;
+
+[Image_isOpaque]
+SkImage
+bool isOpaque() const;
+
+[Image_isTextureBacked]
+SkImage
+bool isTextureBacked() const;
+
+[Image_isValid]
+SkImage
+bool isValid(GrContext* context) const;
+
+[Image_makeColorSpace]
+SkImage
+sk_sp<SkImage> makeColorSpace(sk_sp<SkColorSpace> target) const;
+
+[Image_makeNonTextureImage]
+SkImage
+sk_sp<SkImage> makeNonTextureImage() const;
+
+[Image_makeRasterImage]
+SkImage
+sk_sp<SkImage> makeRasterImage() const;
+
+[Image_makeShader]
+SkImage
+sk_sp<SkShader> makeShader(SkShader::TileMode tileMode1, SkShader::TileMode tileMode2, const SkMatrix* localMatrix = nullptr) const;
+
+[Image_makeShader_2]
+SkImage
+sk_sp<SkShader> makeShader(const SkMatrix* localMatrix = nullptr) const;
+
+[Image_makeSubset]
+SkImage
+sk_sp<SkImage> makeSubset(const SkIRect& subset) const;
+
+[Image_makeTextureImage]
+SkImage
+sk_sp<SkImage> makeTextureImage(GrContext* context, SkColorSpace* dstColorSpace, GrMipMapped mipMapped = GrMipMapped::kNo) const;
+
+[Image_makeWithFilter]
+SkImage
+sk_sp<SkImage> makeWithFilter(const SkImageFilter* filter, const SkIRect& subset, const SkIRect& clipBounds, SkIRect* outSubset, SkIPoint* offset) const;
+
+[Image_peekPixels]
+SkImage
+bool peekPixels(SkPixmap* pixmap) const;
+
+[Image_readPixels]
+SkImage
+bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, int srcX, int srcY, CachingHint cachingHint = kAllow_CachingHint) const;
+
+[Image_readPixels_2]
+SkImage
+bool readPixels(const SkPixmap& dst, int srcX, int srcY, CachingHint cachingHint = kAllow_CachingHint) const;
+
+[Image_refColorSpace]
+SkImage
+sk_sp<SkColorSpace> refColorSpace() const;
+
+[Image_refEncodedData]
+SkImage
+sk_sp<SkData> refEncodedData() const;
+
+[Image_scalePixels]
+SkImage
+bool scalePixels(const SkPixmap& dst, SkFilterQuality filterQuality, CachingHint cachingHint = kAllow_CachingHint) const;
+
+[Image_uniqueID]
+SkImage
+uint32_t uniqueID() const;
+
+[Image_width]
+SkImage
+int width() const;
+
+[ImageInfo_ByteSizeOverflowed]
+SkImageInfo
+static bool ByteSizeOverflowed(size_t byteSize);
+
+[ImageInfo_Make]
+SkImageInfo
+static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs = nullptr);
+
+[ImageInfo_MakeA8]
+SkImageInfo
+static SkImageInfo MakeA8(int width, int height);
+
+[ImageInfo_MakeN32]
+SkImageInfo
+static SkImageInfo MakeN32(int width, int height, SkAlphaType at, sk_sp<SkColorSpace> cs = nullptr);
+
+[ImageInfo_MakeN32Premul_2]
+SkImageInfo
+static SkImageInfo MakeN32Premul(const SkISize& size);
+
+[ImageInfo_MakeN32Premul]
+SkImageInfo
+static SkImageInfo MakeN32Premul(int width, int height, sk_sp<SkColorSpace> cs = nullptr);
+
+[ImageInfo_MakeS32]
+SkImageInfo
+static SkImageInfo MakeS32(int width, int height, SkAlphaType at);
+
+[ImageInfo_MakeUnknown_2]
+SkImageInfo
+static SkImageInfo MakeUnknown();
+
+[ImageInfo_MakeUnknown]
+SkImageInfo
+static SkImageInfo MakeUnknown(int width, int height);
+
+[ColorTypeBytesPerPixel]
+SkImageInfo
+int SkColorTypeBytesPerPixel(SkColorType ct);
+
+[ColorTypeIsAlwaysOpaque]
+SkImageInfo
+bool SkColorTypeIsAlwaysOpaque(SkColorType ct);
+
+[ColorTypeValidateAlphaType]
+SkImageInfo
+bool SkColorTypeValidateAlphaType(SkColorType colorType, SkAlphaType alphaType, SkAlphaType* canonical = nullptr);
+
+[ImageInfo_empty_constructor]
+SkImageInfo
+SkImageInfo();
+
+[ImageInfo_alphaType]
+SkImageInfo
+SkAlphaType alphaType() const;
+
+[ImageInfo_bounds]
+SkImageInfo
+SkIRect bounds() const;
+
+[ImageInfo_bytesPerPixel]
+SkImageInfo
+int bytesPerPixel() const;
+
+[ImageInfo_colorSpace]
+SkImageInfo
+SkColorSpace* colorSpace() const;
+
+[ImageInfo_colorType]
+SkImageInfo
+SkColorType colorType() const;
+
+[ImageInfo_computeByteSize]
+SkImageInfo
+size_t computeByteSize(size_t rowBytes) const;
+
+[ImageInfo_computeMinByteSize]
+SkImageInfo
+size_t computeMinByteSize() const;
+
+[ImageInfo_computeOffset]
+SkImageInfo
+size_t computeOffset(int x, int y, size_t rowBytes) const;
+
+[ImageInfo_dimensions]
+SkImageInfo
+SkISize dimensions() const;
+
+[Alpha_Type_Opaque]
+SkImageInfo
+enum SkAlphaType { kUnknown_SkAlphaType, kOpaque_SkAlphaType, kPremul_SkAlphaType, kUnpremul_SkAlphaType, kLastEnum_SkAlphaType = kUnpremul_SkAlphaType, };
+
+[Color_Type_ARGB_4444]
+[Color_Type_Alpha_8]
+[Color_Type_BGRA_8888]
+[Color_Type_Gray_8]
+[Color_Type_RGBA_1010102]
+[Color_Type_RGBA_8888]
+[Color_Type_RGBA_F16]
+[Color_Type_RGB_101010]
+[Color_Type_RGB_565]
+[Color_Type_RGB_888]
+SkImageInfo
+enum SkColorType { kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType, kARGB_4444_SkColorType, kRGBA_8888_SkColorType, kRGB_888x_SkColorType, kBGRA_8888_SkColorType, kRGBA_1010102_SkColorType, kRGB_101010x_SkColorType, kGray_8_SkColorType, kRGBA_F16_SkColorType, kRGBA_F32_SkColorType, kLastEnum_SkColorType = kRGBA_F32_SkColorType, kN32_SkColorType = kBGRA_8888_SkColorType, kN32_SkColorType = kRGBA_8888_SkColorType, };
+
+[ImageInfo_gammaCloseToSRGB]
+SkImageInfo
+bool gammaCloseToSRGB() const;
+
+[ImageInfo_height]
+SkImageInfo
+int height() const;
+
+[ImageInfo_isEmpty]
+SkImageInfo
+bool isEmpty() const;
+
+[ImageInfo_isOpaque]
+SkImageInfo
+bool isOpaque() const;
+
+[ImageInfo_makeAlphaType]
+SkImageInfo
+SkImageInfo makeAlphaType(SkAlphaType newAlphaType) const;
+
+[ImageInfo_makeColorSpace]
+SkImageInfo
+SkImageInfo makeColorSpace(sk_sp<SkColorSpace> cs) const;
+
+[ImageInfo_makeColorType]
+SkImageInfo
+SkImageInfo makeColorType(SkColorType newColorType) const;
+
+[ImageInfo_makeWH]
+SkImageInfo
+SkImageInfo makeWH(int newWidth, int newHeight) const;
+
+[ImageInfo_minRowBytes]
+SkImageInfo
+size_t minRowBytes() const;
+
+[ImageInfo_minRowBytes64]
+SkImageInfo
+uint64_t minRowBytes64() const;
+
+[ImageInfo_notequal1_operator]
+SkImageInfo
+bool operator!=(const SkImageInfo& other) const;
+
+[ImageInfo_equal1_operator]
+SkImageInfo
+bool operator==(const SkImageInfo& other) const;
+
+[ImageInfo_refColorSpace]
+SkImageInfo
+sk_sp<SkColorSpace> refColorSpace() const;
+
+[ImageInfo_reset]
+SkImageInfo
+void reset();
+
+[ImageInfo_shiftPerPixel]
+SkImageInfo
+int shiftPerPixel() const;
+
+[Alpha_Type_Premul]
+SkImageInfo
+stored color = original color * alpha / max alpha;
+
+[Alpha_Type_Unpremul]
+SkImageInfo
+stored color = original color * alpha / max alpha;
+
+[ImageInfo_validRowBytes]
+SkImageInfo
+bool validRowBytes(size_t rowBytes) const;
+
+[ImageInfo_width]
+SkImageInfo
+int width() const;
+
+[Matrix_Concat]
+SkMatrix
+static SkMatrix Concat(const SkMatrix& a, const SkMatrix& b);
+
+[Matrix_I]
+SkMatrix
+static const SkMatrix& I();
+
+[Matrix_063]
+SkMatrix
+| sx 0 0 | | J K L | | sx*J sx*K sx*L | I(divx, divy) * Matrix = | 0 sy 0 | | M N O | = | sy*M sy*N sy*O | | 0 0 1 | | P Q R | | P Q R |;
+
+[Matrix_InvalidMatrix]
+SkMatrix
+static const SkMatrix& InvalidMatrix();
+
+[Matrix_MakeAll]
+SkMatrix
+static SkMatrix MakeAll(SkScalar scaleX, SkScalar skewX, SkScalar transX, SkScalar skewY, SkScalar scaleY, SkScalar transY, SkScalar pers0, SkScalar pers1, SkScalar pers2);
+
+[Matrix_MakeRectToRect]
+SkMatrix
+static SkMatrix MakeRectToRect(const SkRect& src, const SkRect& dst, ScaleToFit stf);
+
+[Matrix_MakeScale_2]
+SkMatrix
+static SkMatrix MakeScale(SkScalar scale);
+
+[Matrix_MakeScale]
+SkMatrix
+static SkMatrix MakeScale(SkScalar sx, SkScalar sy);
+
+[Matrix_MakeTrans]
+SkMatrix
+static SkMatrix MakeTrans(SkScalar dx, SkScalar dy);
+
+[Matrix_SetAffineIdentity]
+SkMatrix
+static void SetAffineIdentity(SkScalar affine[6]);
+
+[Matrix_asAffine]
+SkMatrix
+bool asAffine(SkScalar affine[6]) const;
+
+[Matrix_cheapEqualTo]
+SkMatrix
+bool cheapEqualTo(const SkMatrix& m) const;
+
+[Matrix_decomposeScale]
+SkMatrix
+bool decomposeScale(SkSize* scale, SkMatrix* remaining = nullptr) const;
+
+[Matrix_dirtyMatrixTypeCache]
+SkMatrix
+void dirtyMatrixTypeCache();
+
+[Matrix_dump]
+SkMatrix
+void dump() const;
+
+[Matrix_ScaleToFit]
+SkMatrix
+enum ScaleToFit { kFill_ScaleToFit, kStart_ScaleToFit, kCenter_ScaleToFit, kEnd_ScaleToFit, };
+
+[Matrix_TypeMask]
+SkMatrix
+enum TypeMask { kIdentity_Mask = 0, kTranslate_Mask = 0x01, kScale_Mask = 0x02, kAffine_Mask = 0x04, kPerspective_Mask = 0x08, };
+
+[Matrix_fixedStepInX]
+SkMatrix
+SkVector fixedStepInX(SkScalar y) const;
+
+[Matrix_get]
+SkMatrix
+SkScalar get(int index) const;
+
+[Matrix_get9]
+SkMatrix
+void get9(SkScalar buffer[9]) const;
+
+[Matrix_getMaxScale]
+SkMatrix
+SkScalar getMaxScale() const;
+
+[Matrix_getMinMaxScales]
+SkMatrix
+bool getMinMaxScales(SkScalar scaleFactors[2]) const;
+
+[Matrix_getMinScale]
+SkMatrix
+SkScalar getMinScale() const;
+
+[Matrix_getPerspX]
+SkMatrix
+SkScalar getPerspX() const;
+
+[Matrix_getPerspY]
+SkMatrix
+SkScalar getPerspY() const;
+
+[Matrix_getScaleX]
+SkMatrix
+SkScalar getScaleX() const;
+
+[Matrix_getScaleY]
+SkMatrix
+SkScalar getScaleY() const;
+
+[Matrix_getSkewX]
+SkMatrix
+SkScalar getSkewX() const;
+
+[Matrix_getSkewY]
+SkMatrix
+SkScalar getSkewY() const;
+
+[Matrix_getTranslateX]
+SkMatrix
+SkScalar getTranslateX() const;
+
+[Matrix_getTranslateY]
+SkMatrix
+SkScalar getTranslateY() const;
+
+[Matrix_getType]
+SkMatrix
+TypeMask getType() const;
+
+[Matrix_hasPerspective]
+SkMatrix
+bool hasPerspective() const;
+
+[Matrix_invert]
+SkMatrix
+bool invert(SkMatrix* inverse) const;
+
+[Matrix_isFinite]
+SkMatrix
+bool isFinite() const;
+
+[Matrix_isFixedStepInX]
+SkMatrix
+bool isFixedStepInX() const;
+
+[Matrix_isIdentity]
+SkMatrix
+bool isIdentity() const;
+
+[Matrix_isScaleTranslate]
+SkMatrix
+bool isScaleTranslate() const;
+
+[Matrix_isSimilarity]
+SkMatrix
+bool isSimilarity(SkScalar tol = SK_ScalarNearlyZero) const;
+
+[Matrix_isTranslate]
+SkMatrix
+bool isTranslate() const;
+
+[Matrix_mapHomogeneousPoints]
+SkMatrix
+void mapHomogeneousPoints(SkPoint3 dst[], const SkPoint3 src[], int count) const;
+
+[Matrix_mapPoints]
+SkMatrix
+void mapPoints(SkPoint dst[], const SkPoint src[], int count) const;
+
+[Matrix_mapPoints_2]
+SkMatrix
+void mapPoints(SkPoint pts[], int count) const;
+
+[Matrix_mapRadius]
+SkMatrix
+SkScalar mapRadius(SkScalar radius) const;
+
+[Matrix_mapRect_3]
+SkMatrix
+SkRect mapRect(const SkRect& src) const;
+
+[Matrix_mapRect]
+SkMatrix
+bool mapRect(SkRect* dst, const SkRect& src) const;
+
+[Matrix_mapRect_2]
+SkMatrix
+bool mapRect(SkRect* rect) const;
+
+[Matrix_mapRectScaleTranslate]
+SkMatrix
+void mapRectScaleTranslate(SkRect* dst, const SkRect& src) const;
+
+[Matrix_mapRectToQuad]
+SkMatrix
+void mapRectToQuad(SkPoint dst[4], const SkRect& rect) const;
+
+[Matrix_mapVector_2]
+SkMatrix
+SkVector mapVector(SkScalar dx, SkScalar dy) const;
+
+[Matrix_mapVector]
+SkMatrix
+void mapVector(SkScalar dx, SkScalar dy, SkVector* result) const;
+
+[Matrix_mapVectors]
+SkMatrix
+void mapVectors(SkVector dst[], const SkVector src[], int count) const;
+
+[Matrix_mapVectors_2]
+SkMatrix
+void mapVectors(SkVector vecs[], int count) const;
+
+[Matrix_mapXY_2]
+SkMatrix
+SkPoint mapXY(SkScalar x, SkScalar y) const;
+
+[Matrix_mapXY]
+SkMatrix
+void mapXY(SkScalar x, SkScalar y, SkPoint* result) const;
+
+[Matrix_notequal_operator]
+SkMatrix
+bool operator!=(const SkMatrix& a, const SkMatrix& b);
+
+[Matrix_equal_operator]
+SkMatrix
+bool operator==(const SkMatrix& a, const SkMatrix& b);
+
+[Matrix_array_operator]
+SkMatrix
+SkScalar operator[](int index) const;
+
+[Matrix_dirtyMatrixTypeCache]
+SkMatrix
+SkScalar& operator[](int index);
+
+[Matrix_postConcat]
+SkMatrix
+void postConcat(const SkMatrix& other);
+
+[Matrix_postRotate_2]
+SkMatrix
+void postRotate(SkScalar degrees);
+
+[Matrix_postRotate]
+SkMatrix
+void postRotate(SkScalar degrees, SkScalar px, SkScalar py);
+
+[Matrix_postScale_2]
+SkMatrix
+void postScale(SkScalar sx, SkScalar sy);
+
+[Matrix_postScale]
+SkMatrix
+void postScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py);
+
+[Matrix_postSkew_2]
+SkMatrix
+void postSkew(SkScalar kx, SkScalar ky);
+
+[Matrix_postSkew]
+SkMatrix
+void postSkew(SkScalar kx, SkScalar ky, SkScalar px, SkScalar py);
+
+[Matrix_postTranslate]
+SkMatrix
+void postTranslate(SkScalar dx, SkScalar dy);
+
+[Matrix_preConcat]
+SkMatrix
+void preConcat(const SkMatrix& other);
+
+[Matrix_preRotate_2]
+SkMatrix
+void preRotate(SkScalar degrees);
+
+[Matrix_preRotate]
+SkMatrix
+void preRotate(SkScalar degrees, SkScalar px, SkScalar py);
+
+[Matrix_preScale_2]
+SkMatrix
+void preScale(SkScalar sx, SkScalar sy);
+
+[Matrix_preScale]
+SkMatrix
+void preScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py);
+
+[Matrix_preSkew_2]
+SkMatrix
+void preSkew(SkScalar kx, SkScalar ky);
+
+[Matrix_preSkew]
+SkMatrix
+void preSkew(SkScalar kx, SkScalar ky, SkScalar px, SkScalar py);
+
+[Matrix_preTranslate]
+SkMatrix
+void preTranslate(SkScalar dx, SkScalar dy);
+
+[Matrix_preservesAxisAlignment]
+SkMatrix
+bool preservesAxisAlignment() const;
+
+[Matrix_preservesRightAngles]
+SkMatrix
+bool preservesRightAngles(SkScalar tol = SK_ScalarNearlyZero) const;
+
+[Matrix_rectStaysRect]
+SkMatrix
+bool rectStaysRect() const;
+
+[Matrix_reset]
+SkMatrix
+void reset();
+
+[Matrix_set]
+SkMatrix
+void set(int index, SkScalar value);
+
+[Matrix_set9]
+SkMatrix
+void set9(const SkScalar buffer[9]);
+
+[Matrix_setAffine]
+SkMatrix
+void setAffine(const SkScalar affine[6]);
+
+[Matrix_setAll]
+SkMatrix
+void setAll(SkScalar scaleX, SkScalar skewX, SkScalar transX, SkScalar skewY, SkScalar scaleY, SkScalar transY, SkScalar persp0, SkScalar persp1, SkScalar persp2);
+
+[Matrix_setConcat]
+SkMatrix
+void setConcat(const SkMatrix& a, const SkMatrix& b);
+
+[Matrix_setIdentity]
+SkMatrix
+void setIdentity();
+
+[Matrix_setPerspX]
+SkMatrix
+void setPerspX(SkScalar v);
+
+[Matrix_setPerspY]
+SkMatrix
+void setPerspY(SkScalar v);
+
+[Matrix_setPolyToPoly]
+SkMatrix
+bool setPolyToPoly(const SkPoint src[], const SkPoint dst[], int count);
+
+[Matrix_setRSXform]
+SkMatrix
+SkMatrix& setRSXform(const SkRSXform& rsxForm);
+
+[Matrix_setRectToRect]
+SkMatrix
+bool setRectToRect(const SkRect& src, const SkRect& dst, ScaleToFit stf);
+
+[Matrix_setRotate_2]
+SkMatrix
+void setRotate(SkScalar degrees);
+
+[Matrix_setRotate]
+SkMatrix
+void setRotate(SkScalar degrees, SkScalar px, SkScalar py);
+
+[Matrix_setScale_2]
+SkMatrix
+void setScale(SkScalar sx, SkScalar sy);
+
+[Matrix_setScale]
+SkMatrix
+void setScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py);
+
+[Matrix_setScaleTranslate]
+SkMatrix
+void setScaleTranslate(SkScalar sx, SkScalar sy, SkScalar tx, SkScalar ty);
+
+[Matrix_setScaleX]
+SkMatrix
+void setScaleX(SkScalar v);
+
+[Matrix_setScaleY]
+SkMatrix
+void setScaleY(SkScalar v);
+
+[Matrix_setSinCos_2]
+SkMatrix
+void setSinCos(SkScalar sinValue, SkScalar cosValue);
+
+[Matrix_setSinCos]
+SkMatrix
+void setSinCos(SkScalar sinValue, SkScalar cosValue, SkScalar px, SkScalar py);
+
+[Matrix_setSkew_2]
+SkMatrix
+void setSkew(SkScalar kx, SkScalar ky);
+
+[Matrix_setSkew]
+SkMatrix
+void setSkew(SkScalar kx, SkScalar ky, SkScalar px, SkScalar py);
+
+[Matrix_setSkewX]
+SkMatrix
+void setSkewX(SkScalar v);
+
+[Matrix_setSkewY]
+SkMatrix
+void setSkewY(SkScalar v);
+
+[Matrix_setTranslate]
+SkMatrix
+void setTranslate(SkScalar dx, SkScalar dy);
+
+[Matrix_setTranslate_2]
+SkMatrix
+void setTranslate(const SkVector& v);
+
+[Matrix_setTranslateX]
+SkMatrix
+void setTranslateX(SkScalar v);
+
+[Matrix_setTranslateY]
+SkMatrix
+void setTranslateY(SkScalar v);
+
+[MemberIndex]
+SkMatrix
+static constexpr int kMScaleX = 0; static constexpr int kMSkewX = 1; static constexpr int kMTransX = 2; static constexpr int kMSkewY = 3; static constexpr int kMScaleY = 4; static constexpr int kMTransY = 5; static constexpr int kMPersp0 = 6; static constexpr int kMPersp1 = 7; static constexpr int kMPersp2 = 8;
+
+[Paint_empty_constructor]
+SkPaint
+SkPaint();
+
+[Paint_move_SkPaint]
+SkPaint
+SkPaint(SkPaint&& paint);
+
+[Paint_copy_const_SkPaint]
+SkPaint
+SkPaint(const SkPaint& paint);
+
+[Paint_containsText]
+SkPaint
+bool containsText(const void* text, size_t byteLength) const;
+
+[Paint_countText]
+SkPaint
+int countText(const void* text, size_t byteLength) const;
+
+[Paint_getAlpha]
+SkPaint
+uint8_t getAlpha() const;
+
+[Paint_getBlendMode]
+SkPaint
+SkBlendMode getBlendMode() const;
+
+[Paint_getColor]
+SkPaint
+SkColor getColor() const;
+
+[Paint_getColor4f]
+SkPaint
+SkColor4f getColor4f() const;
+
+[Paint_getColorFilter]
+SkPaint
+SkColorFilter* getColorFilter() const;
+
+[Paint_getDrawLooper]
+SkPaint
+SkDrawLooper* getDrawLooper() const;
+
+[Paint_getFillPath_2]
+[Shader_Methods_a]
+[Shader_Methods_b]
+SkPaint
+bool getFillPath(const SkPath& src, SkPath* dst) const;
+
+[Paint_getFillPath]
+SkPaint
+bool getFillPath(const SkPath& src, SkPath* dst, const SkRect* cullRect, SkScalar resScale = 1) const;
+
+[Paint_getFilterQuality]
+SkPaint
+SkFilterQuality getFilterQuality() const;
+
+[Paint_getFlags]
+SkPaint
+uint32_t getFlags() const;
+
+[Paint_getFontMetrics]
+SkPaint
+SkScalar getFontMetrics(SkFontMetrics* metrics) const;
+
+[Paint_getFontSpacing]
+SkPaint
+SkScalar getFontSpacing() const;
+
+[Paint_getHash]
+SkPaint
+uint32_t getHash() const;
+
+[Paint_getHinting]
+SkPaint
+SkFontHinting getHinting() const;
+
+[Paint_getImageFilter]
+SkPaint
+SkImageFilter* getImageFilter() const;
+
+[Paint_getMaskFilter]
+SkPaint
+SkMaskFilter* getMaskFilter() const;
+
+[Paint_getPathEffect]
+SkPaint
+SkPathEffect* getPathEffect() const;
+
+[Paint_getPosTextPath]
+SkPaint
+void getPosTextPath(const void* text, size_t length, const SkPoint pos[], SkPath* path) const;
+
+[Paint_getShader]
+SkPaint
+SkShader* getShader() const;
+
+[Paint_getStrokeCap]
+SkPaint
+Cap getStrokeCap() const;
+
+[Paint_getStrokeJoin]
+SkPaint
+Join getStrokeJoin() const;
+
+[Paint_getStrokeMiter]
+SkPaint
+SkScalar getStrokeMiter() const;
+
+[Paint_getStrokeWidth]
+SkPaint
+SkScalar getStrokeWidth() const;
+
+[Paint_getStyle]
+SkPaint
+Style getStyle() const;
+
+[Paint_getTextEncoding]
+SkPaint
+SkTextEncoding getTextEncoding() const;
+
+[Paint_getTextPath]
+SkPaint
+void getTextPath(const void* text, size_t length, SkScalar x, SkScalar y, SkPath* path) const;
+
+[Paint_getTextScaleX]
+SkPaint
+SkScalar getTextScaleX() const;
+
+[Paint_getTextSize]
+SkPaint
+SkScalar getTextSize() const;
+
+[Paint_getTextSkewX]
+SkPaint
+SkScalar getTextSkewX() const;
+
+[Paint_getTextWidths]
+SkPaint
+int getTextWidths(const void* text, size_t byteLength, SkScalar widths[], SkRect bounds[] = nullptr) const;
+
+[Paint_getTypeface]
+SkPaint
+SkTypeface* getTypeface() const;
+
+[Paint_isAntiAlias]
+SkPaint
+bool isAntiAlias() const;
+
+[Paint_isAutohinted]
+SkPaint
+bool isAutohinted() const;
+
+[Paint_isDither]
+SkPaint
+bool isDither() const;
+
+[Paint_isEmbeddedBitmapText]
+SkPaint
+bool isEmbeddedBitmapText() const;
+
+[Paint_isFakeBoldText]
+SkPaint
+bool isFakeBoldText() const;
+
+[Paint_isLCDRenderText]
+SkPaint
+bool isLCDRenderText() const;
+
+[Paint_isLinearText]
+SkPaint
+bool isLinearText() const;
+
+[Paint_setBlendMode]
+SkPaint
+bool isSrcOver() const;
+
+[Paint_isSubpixelText]
+SkPaint
+bool isSubpixelText() const;
+
+[Paint_measureText_2]
+SkPaint
+SkScalar measureText(const void* text, size_t length) const;
+
+[Paint_measureText]
+SkPaint
+SkScalar measureText(const void* text, size_t length, SkRect* bounds) const;
+
+[Paint_nothingToDraw]
+SkPaint
+bool nothingToDraw() const;
+
+[Paint_notequal_operator]
+SkPaint
+bool operator!=(const SkPaint& a, const SkPaint& b);
+
+[Paint_move_operator]
+SkPaint
+SkPaint& operator=(SkPaint&& paint);
+
+[Paint_copy_operator]
+SkPaint
+SkPaint& operator=(const SkPaint& paint);
+
+[Paint_equal_operator]
+SkPaint
+bool operator==(const SkPaint& a, const SkPaint& b);
+
+[Paint_refColorFilter]
+SkPaint
+sk_sp<SkColorFilter> refColorFilter() const;
+
+[Paint_refDrawLooper]
+SkPaint
+sk_sp<SkDrawLooper> refDrawLooper() const;
+
+[Paint_refImageFilter]
+SkPaint
+sk_sp<SkImageFilter> refImageFilter() const;
+
+[Paint_refMaskFilter]
+SkPaint
+sk_sp<SkMaskFilter> refMaskFilter() const;
+
+[Paint_refPathEffect]
+SkPaint
+sk_sp<SkPathEffect> refPathEffect() const;
+
+[Paint_refShader]
+SkPaint
+sk_sp<SkShader> refShader() const;
+
+[Paint_refTypeface]
+SkPaint
+sk_sp<SkTypeface> refTypeface() const;
+
+[Paint_reset]
+SkPaint
+void reset();
+
+[Paint_setARGB]
+SkPaint
+void setARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
+
+[Paint_setAlpha]
+SkPaint
+void setAlpha(U8CPU a);
+
+[Dither_a]
+[Dither_b]
+[Paint_setAntiAlias]
+SkPaint
+void setAntiAlias(bool aa);
+
+[Fake_Bold]
+[Paint_setAutohinted]
+SkPaint
+void setAutohinted(bool useAutohinter);
+
+[Paint_setBlendMode]
+[Path_Effect_Methods]
+SkPaint
+void setBlendMode(SkBlendMode mode);
+
+[Paint_setColor]
+SkPaint
+void setColor(SkColor color);
+
+[Paint_setColor4f]
+SkPaint
+void setColor4f(const SkColor4f& color, SkColorSpace* colorSpace);
+
+[Blend_Mode_Methods]
+[Paint_setColorFilter]
+SkPaint
+void setColorFilter(sk_sp<SkColorFilter> colorFilter);
+
+[Device_Text]
+[Paint_setDither]
+SkPaint
+void setDither(bool dither);
+
+[Paint_setDrawLooper]
+[Text_Size]
+SkPaint
+void setDrawLooper(sk_sp<SkDrawLooper> drawLooper);
+
+[Paint_setEmbeddedBitmapText]
+SkPaint
+void setEmbeddedBitmapText(bool useEmbeddedBitmapText);
+
+[Filter_Quality_Methods]
+[Paint_setFakeBoldText]
+SkPaint
+void setFakeBoldText(bool fakeBoldText);
+
+[Color_Methods]
+[Paint_setFilterQuality]
+SkPaint
+void setFilterQuality(SkFilterQuality quality);
+
+[Anti_Alias]
+[Paint_setFlags]
+SkPaint
+void setFlags(uint32_t flags);
+
+[Paint_setHinting]
+SkPaint
+void setHinting(SkFontHinting hintingLevel);
+
+[Draw_Looper_Methods]
+[Paint_setImageFilter]
+SkPaint
+void setImageFilter(sk_sp<SkImageFilter> imageFilter);
+
+[Paint_setLCDRenderText]
+SkPaint
+void setLCDRenderText(bool lcdText);
+
+[Paint_setLinearText]
+SkPaint
+void setLinearText(bool linearText);
+
+[Paint_setMaskFilter]
+[Typeface_Methods]
+SkPaint
+void setMaskFilter(sk_sp<SkMaskFilter> maskFilter);
+
+[Mask_Filter_Methods]
+[Paint_setPathEffect]
+SkPaint
+void setPathEffect(sk_sp<SkPathEffect> pathEffect);
+
+[Color_Filter_Methods]
+[Paint_setShader]
+SkPaint
+void setShader(sk_sp<SkShader> shader);
+
+[Paint_setStrokeCap_a]
+[Paint_setStrokeCap_b]
+SkPaint
+void setStrokeCap(Cap cap);
+
+[Paint_setStrokeJoin]
+SkPaint
+void setStrokeJoin(Join join);
+
+[Paint_setStrokeMiter]
+SkPaint
+void setStrokeMiter(SkScalar miter);
+
+[Miter_Limit]
+[Paint_setStrokeWidth]
+SkPaint
+void setStrokeWidth(SkScalar width);
+
+[Paint_setStyle]
+[Stroke_Width]
+SkPaint
+void setStyle(Style style);
+
+[Paint_setSubpixelText]
+SkPaint
+void setSubpixelText(bool subpixelText);
+
+[Paint_setTextEncoding]
+SkPaint
+void setTextEncoding(SkTextEncoding encoding);
+
+[Paint_setTextScaleX]
+[Text_Skew_X]
+SkPaint
+void setTextScaleX(SkScalar scaleX);
+
+[Paint_setTextSize]
+[Text_Scale_X]
+SkPaint
+void setTextSize(SkScalar textSize);
+
+[Paint_setTextSkewX]
+[Text_Encoding]
+SkPaint
+void setTextSkewX(SkScalar skewX);
+
+[Image_Filter_Methods]
+[Paint_setTypeface]
+SkPaint
+void setTypeface(sk_sp<SkTypeface> typeface);
+
+[Paint_053]
+SkPaint
+static constexpr int kCapCount = kLast_Cap + 1;
+
+[Paint_057]
+SkPaint
+static constexpr int kJoinCount = kLast_Join + 1;
+
+[Paint_textToGlyphs]
+SkPaint
+int textToGlyphs(const void* text, size_t byteLength, SkGlyphID glyphs[]) const;
+
+[Path_ConvertConicToQuads]
+SkPath
+static int ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2, SkScalar w, SkPoint pts[], int pow2);
+
+[Path_ConvertToNonInverseFillType]
+SkPath
+static FillType ConvertToNonInverseFillType(FillType fill);
+
+[Path_IsCubicDegenerate]
+SkPath
+static bool IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2, const SkPoint& p3, const SkPoint& p4, bool exact);
+
+[Path_IsInverseFillType]
+SkPath
+static bool IsInverseFillType(FillType fill);
+
+[Path_IsLineDegenerate]
+SkPath
+static bool IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact);
+
+[Path_IsQuadDegenerate]
+SkPath
+static bool IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2, const SkPoint& p3, bool exact);
+
+[Path_Iter_Iter]
+SkPath
+Iter();
+
+[Path_Iter_const_SkPath]
+SkPath
+Iter(const SkPath& path, bool forceClose);
+
+[Path_empty_constructor]
+SkPath
+SkPath();
+
+[Path_copy_const_SkPath]
+SkPath
+SkPath(const SkPath& path);
+
+[Path_addArc]
+SkPath
+SkPath& addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle);
+
+[Path_addCircle]
+SkPath
+SkPath& addCircle(SkScalar x, SkScalar y, SkScalar radius, Direction dir = kCW_Direction);
+
+[Path_addOval]
+SkPath
+SkPath& addOval(const SkRect& oval, Direction dir = kCW_Direction);
+
+[Path_addOval_2]
+SkPath
+SkPath& addOval(const SkRect& oval, Direction dir, unsigned start);
+
+[Path_addPath_2]
+SkPath
+SkPath& addPath(const SkPath& src, AddPathMode mode = kAppend_AddPathMode);
+
+[Path_addPath]
+SkPath
+SkPath& addPath(const SkPath& src, SkScalar dx, SkScalar dy, AddPathMode mode = kAppend_AddPathMode);
+
+[Path_addPath_3]
+SkPath
+SkPath& addPath(const SkPath& src, const SkMatrix& matrix, AddPathMode mode = kAppend_AddPathMode);
+
+[Path_addPoly]
+SkPath
+SkPath& addPoly(const SkPoint pts[], int count, bool close);
+
+[Path_addPoly_2]
+SkPath
+SkPath& addPoly(const std::initializer_list<SkPoint>& list, bool close);
+
+[Path_addRRect]
+SkPath
+SkPath& addRRect(const SkRRect& rrect, Direction dir = kCW_Direction);
+
+[Path_addRRect_2]
+SkPath
+SkPath& addRRect(const SkRRect& rrect, Direction dir, unsigned start);
+
+[Path_addRect_3]
+SkPath
+SkPath& addRect(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom, Direction dir = kCW_Direction);
+
+[Path_addRect]
+SkPath
+SkPath& addRect(const SkRect& rect, Direction dir = kCW_Direction);
+
+[Path_addRect_2]
+SkPath
+SkPath& addRect(const SkRect& rect, Direction dir, unsigned start);
+
+[Path_addRoundRect]
+SkPath
+SkPath& addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry, Direction dir = kCW_Direction);
+
+[Path_addRoundRect_2]
+SkPath
+SkPath& addRoundRect(const SkRect& rect, const SkScalar radii[], Direction dir = kCW_Direction);
+
+[Path_arcTo_4]
+SkPath
+SkPath& arcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, ArcSize largeArc, Direction sweep, SkScalar x, SkScalar y);
+
+[Path_arcTo_2_a]
+[Path_arcTo_2_b]
+[Path_arcTo_2_c]
+SkPath
+SkPath& arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius);
+
+[Path_arcTo_3]
+SkPath
+SkPath& arcTo(const SkPoint p1, const SkPoint p2, SkScalar radius);
+
+[Path_rArcTo]
+SkPath
+SkPath& arcTo(const SkPoint r, SkScalar xAxisRotate, ArcSize largeArc, Direction sweep, const SkPoint xy);
+
+[Path_arcTo]
+SkPath
+SkPath& arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool forceMoveTo);
+
+[Path_close]
+SkPath
+SkPath& close();
+
+[Path_computeTightBounds]
+SkPath
+SkRect computeTightBounds() const;
+
+[Path_conicTo]
+SkPath
+SkPath& conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar w);
+
+[Path_conicTo_2]
+SkPath
+SkPath& conicTo(const SkPoint& p1, const SkPoint& p2, SkScalar w);
+
+[Path_Iter_conicWeight]
+[Path_RawIter_conicWeight]
+SkPath
+SkScalar conicWeight() const;
+
+[Path_conservativelyContainsRect]
+SkPath
+bool conservativelyContainsRect(const SkRect& rect) const;
+
+[Path_contains]
+SkPath
+bool contains(SkScalar x, SkScalar y) const;
+
+[Path_countPoints]
+SkPath
+int countPoints() const;
+
+[Path_countVerbs]
+SkPath
+int countVerbs() const;
+
+[Path_cubicTo]
+SkPath
+SkPath& cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar x3, SkScalar y3);
+
+[Path_cubicTo_2]
+SkPath
+SkPath& cubicTo(const SkPoint& p1, const SkPoint& p2, const SkPoint& p3);
+
+[Path_dump_2]
+SkPath
+void dump() const;
+
+[Path_dump]
+SkPath
+void dump(SkWStream* stream, bool forceClose, bool dumpAsHex) const;
+
+[Path_dumpHex]
+SkPath
+void dumpHex() const;
+
+[Path_AddPathMode]
+SkPath
+enum AddPathMode { kAppend_AddPathMode, kExtend_AddPathMode, };
+
+[Path_ArcSize]
+SkPath
+enum ArcSize { kSmall_ArcSize, kLarge_ArcSize, };
+
+[Path_Convexity]
+SkPath
+enum Convexity : uint8_t { kUnknown_Convexity, kConvex_Convexity, kConcave_Convexity, };
+
+[Path_Direction]
+SkPath
+enum Direction : int { kCW_Direction, kCCW_Direction, };
+
+[Path_FillType_a]
+[Path_FillType_b]
+SkPath
+enum FillType { kWinding_FillType, kEvenOdd_FillType, kInverseWinding_FillType, kInverseEvenOdd_FillType, };
+
+[Path_SegmentMask]
+SkPath
+enum SegmentMask { kLine_SegmentMask = 1 << 0, kQuad_SegmentMask = 1 << 1, kConic_SegmentMask = 1 << 2, kCubic_SegmentMask = 1 << 3, };
+
+[Path_Verb]
+SkPath
+enum Verb { kMove_Verb, kLine_Verb, kQuad_Verb, kConic_Verb, kCubic_Verb, kClose_Verb, kDone_Verb, };
+
+[Path_getBounds]
+SkPath
+const SkRect& getBounds() const;
+
+[Path_getConvexity]
+SkPath
+Convexity getConvexity() const;
+
+[Path_getConvexityOrUnknown]
+SkPath
+Convexity getConvexityOrUnknown() const;
+
+[Path_getFillType]
+SkPath
+FillType getFillType() const;
+
+[Path_getGenerationID]
+SkPath
+uint32_t getGenerationID() const;
+
+[Path_getLastPt]
+SkPath
+bool getLastPt(SkPoint* lastPt) const;
+
+[Path_getPoint]
+SkPath
+SkPoint getPoint(int index) const;
+
+[Path_getPoints]
+SkPath
+int getPoints(SkPoint points[], int max) const;
+
+[Path_getSegmentMasks]
+SkPath
+uint32_t getSegmentMasks() const;
+
+[Path_getVerbs]
+SkPath
+int getVerbs(uint8_t verbs[], int max) const;
+
+[Path_incReserve]
+SkPath
+void incReserve(int extraPtCount);
+
+[Path_interpolate]
+SkPath
+bool interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const;
+
+[Path_Iter_isCloseLine]
+SkPath
+bool isCloseLine() const;
+
+[Path_Iter_isClosedContour]
+SkPath
+bool isClosedContour() const;
+
+[Path_Iter]
+SkPath
+class Iter { Iter(); Iter(const SkPath& path, bool forceClose); void setPath(const SkPath& path, bool forceClose); Verb next(SkPoint pts[4], bool doConsumeDegenerates = true, bool exact = false); SkScalar conicWeight() const; bool isCloseLine() const; bool isClosedContour() const; };
+
+[Path_isConvex]
+SkPath
+bool isConvex() const;
+
+[Path_isEmpty]
+SkPath
+bool isEmpty() const;
+
+[Path_isFinite]
+SkPath
+bool isFinite() const;
+
+[Path_isInterpolatable]
+SkPath
+bool isInterpolatable(const SkPath& compare) const;
+
+[Path_isInverseFillType_2]
+SkPath
+bool isInverseFillType() const;
+
+[Path_isLastContourClosed]
+SkPath
+bool isLastContourClosed() const;
+
+[Path_isLine]
+SkPath
+bool isLine(SkPoint line[2]) const;
+
+[Path_isNestedFillRects]
+SkPath
+bool isNestedFillRects(SkRect rect[2], Direction dirs[2] = nullptr) const;
+
+[Path_isOval]
+SkPath
+bool isOval(SkRect* bounds) const;
+
+[Path_isRRect]
+SkPath
+bool isRRect(SkRRect* rrect) const;
+
+[Path_isRect]
+SkPath
+bool isRect(SkRect* rect, bool* isClosed = nullptr, Direction* direction = nullptr) const;
+
+[Path_isVolatile]
+SkPath
+bool isVolatile() const;
+
+[Path_lineTo]
+SkPath
+SkPath& lineTo(SkScalar x, SkScalar y);
+
+[Path_lineTo_2]
+SkPath
+SkPath& lineTo(const SkPoint& p);
+
+[Path_moveTo]
+SkPath
+SkPath& moveTo(SkScalar x, SkScalar y);
+
+[Path_moveTo_2]
+SkPath
+SkPath& moveTo(const SkPoint& p);
+
+[Path_RawIter_next]
+SkPath
+Verb next(SkPoint pts[4]);
+
+[Path_Iter_next]
+SkPath
+Verb next(SkPoint pts[4], bool doConsumeDegenerates = true, bool exact = false);
+
+[Path_offset_2]
+SkPath
+void offset(SkScalar dx, SkScalar dy);
+
+[Path_offset]
+SkPath
+void offset(SkScalar dx, SkScalar dy, SkPath* dst) const;
+
+[Path_notequal_operator]
+SkPath
+bool operator!=(const SkPath& a, const SkPath& b);
+
+[Path_copy_operator]
+SkPath
+SkPath& operator=(const SkPath& path);
+
+[Path_equal_operator]
+SkPath
+bool operator==(const SkPath& a, const SkPath& b);
+
+[Path_RawIter_peek]
+SkPath
+Verb peek() const;
+
+[Path_quadTo]
+SkPath
+SkPath& quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2);
+
+[Path_quadTo_2]
+SkPath
+SkPath& quadTo(const SkPoint& p1, const SkPoint& p2);
+
+[Path_rArcTo]
+SkPath
+SkPath& rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, ArcSize largeArc, Direction sweep, SkScalar dx, SkScalar dy);
+
+[Cubic]
+[Path_rConicTo]
+SkPath
+SkPath& rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2, SkScalar w);
+
+[Arc]
+[Path_rCubicTo]
+SkPath
+SkPath& rCubicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2, SkScalar dx3, SkScalar dy3);
+
+[Path_rLineTo]
+[Quad_a]
+[Quad_b]
+SkPath
+SkPath& rLineTo(SkScalar dx, SkScalar dy);
+
+[Path_rMoveTo]
+SkPath
+SkPath& rMoveTo(SkScalar dx, SkScalar dy);
+
+[Conic_Weight_a]
+[Conic_Weight_b]
+[Conic_Weight_c]
+[Path_rQuadTo]
+SkPath
+SkPath& rQuadTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2);
+
+[Path_readFromMemory]
+SkPath
+size_t readFromMemory(const void* buffer, size_t length);
+
+[Path_reset]
+SkPath
+SkPath& reset();
+
+[Path_reverseAddPath]
+SkPath
+SkPath& reverseAddPath(const SkPath& src);
+
+[Path_rewind]
+SkPath
+SkPath& rewind();
+
+[Path_serialize]
+SkPath
+sk_sp<SkData> serialize() const;
+
+[Path_setConvexity]
+SkPath
+void setConvexity(Convexity convexity);
+
+[Path_setFillType]
+SkPath
+void setFillType(FillType ft);
+
+[Path_setIsVolatile]
+SkPath
+void setIsVolatile(bool isVolatile);
+
+[Path_setLastPt]
+SkPath
+void setLastPt(SkScalar x, SkScalar y);
+
+[Path_setLastPt_2]
+SkPath
+void setLastPt(const SkPoint& p);
+
+[Path_Iter_setPath]
+SkPath
+void setPath(const SkPath& path, bool forceClose);
+
+[Path_swap]
+SkPath
+void swap(SkPath& other);
+
+[Path_toggleInverseFillType]
+SkPath
+void toggleInverseFillType();
+
+[Path_transform_2]
+SkPath
+void transform(const SkMatrix& matrix);
+
+[Path_transform]
+SkPath
+void transform(const SkMatrix& matrix, SkPath* dst) const;
+
+[Path_updateBoundsCache]
+SkPath
+void updateBoundsCache() const;
+
+[Path_writeToMemory]
+SkPath
+size_t writeToMemory(void* buffer) const;
+
+[Path_destructor]
+SkPath
+~SkPath();
+
+[Picture_MakeFromData]
+SkPicture
+static sk_sp<SkPicture> MakeFromData(const SkData* data, const SkDeserialProcs* procs = nullptr);
+
+[Picture_serialize_2]
+SkPicture
+static sk_sp<SkPicture> MakeFromData(const void* data, size_t size, const SkDeserialProcs* procs = nullptr);
+
+[Picture_MakeFromStream]
+SkPicture
+static sk_sp<SkPicture> MakeFromStream(SkStream* stream, const SkDeserialProcs* procs = nullptr);
+
+[Picture_MakePlaceholder]
+SkPicture
+static sk_sp<SkPicture> MakePlaceholder(SkRect cull);
+
+[Picture_AbortCallback_abort]
+SkPicture
+virtual bool abort() = 0;
+
+[Picture_approximateBytesUsed]
+SkPicture
+virtual size_t approximateBytesUsed() const = 0;
+
+[Picture_approximateOpCount]
+SkPicture
+virtual int approximateOpCount() const = 0;
+
+[Picture_cullRect]
+SkPicture
+virtual SkRect cullRect() const = 0;
+
+[Picture_playback]
+SkPicture
+virtual void playback(SkCanvas* canvas, AbortCallback* callback = nullptr) const = 0;
+
+[Picture_serialize]
+SkPicture
+sk_sp<SkData> serialize(const SkSerialProcs* procs = nullptr) const;
+
+[Picture_serialize_2]
+SkPicture
+void serialize(SkWStream* stream, const SkSerialProcs* procs = nullptr) const;
+
+[Picture_uniqueID]
+SkPicture
+uint32_t uniqueID() const;
+
+[Pixmap_empty_constructor]
+SkPixmap
+SkPixmap();
+
+[Pixmap_const_SkImageInfo_const_star]
+SkPixmap
+SkPixmap(const SkImageInfo& info, const void* addr, size_t rowBytes);
+
+[Pixmap_addr]
+SkPixmap
+const void* addr() const;
+
+[Pixmap_addr_2]
+SkPixmap
+const void* addr(int x, int y) const;
+
+[Pixmap_addr16]
+SkPixmap
+const uint16_t* addr16() const;
+
+[Pixmap_addr16_2]
+SkPixmap
+const uint16_t* addr16(int x, int y) const;
+
+[Pixmap_addr32]
+SkPixmap
+const uint32_t* addr32() const;
+
+[Pixmap_addr32_2]
+SkPixmap
+const uint32_t* addr32(int x, int y) const;
+
+[Pixmap_addr64]
+SkPixmap
+const uint64_t* addr64() const;
+
+[Pixmap_addr64_2]
+SkPixmap
+const uint64_t* addr64(int x, int y) const;
+
+[Pixmap_addr8]
+SkPixmap
+const uint8_t* addr8() const;
+
+[Pixmap_addr8_2]
+SkPixmap
+const uint8_t* addr8(int x, int y) const;
+
+[Pixmap_addrF16]
+SkPixmap
+const uint16_t* addrF16() const;
+
+[Pixmap_addrF16_2]
+SkPixmap
+const uint16_t* addrF16(int x, int y) const;
+
+[Pixmap_alphaType]
+SkPixmap
+SkAlphaType alphaType() const;
+
+[Pixmap_bounds]
+SkPixmap
+SkIRect bounds() const;
+
+[Pixmap_colorSpace]
+SkPixmap
+SkColorSpace* colorSpace() const;
+
+[Pixmap_colorType]
+SkPixmap
+SkColorType colorType() const;
+
+[Pixmap_computeByteSize]
+SkPixmap
+size_t computeByteSize() const;
+
+[Pixmap_computeIsOpaque]
+SkPixmap
+bool computeIsOpaque() const;
+
+[Pixmap_erase_2]
+SkPixmap
+bool erase(SkColor color) const;
+
+[Pixmap_erase]
+SkPixmap
+bool erase(SkColor color, const SkIRect& subset) const;
+
+[Pixmap_erase_3]
+SkPixmap
+bool erase(const SkColor4f& color, const SkIRect* subset = nullptr) const;
+
+[Pixmap_extractSubset]
+SkPixmap
+bool extractSubset(SkPixmap* subset, const SkIRect& area) const;
+
+[Pixmap_getColor]
+SkPixmap
+SkColor getColor(int x, int y) const;
+
+[Pixmap_height]
+SkPixmap
+int height() const;
+
+[Pixmap_info]
+SkPixmap
+const SkImageInfo& info() const;
+
+[Pixmap_isOpaque]
+SkPixmap
+bool isOpaque() const;
+
+[Pixmap_readPixels]
+SkPixmap
+bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes) const;
+
+[Pixmap_readPixels_2]
+SkPixmap
+bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, int srcX, int srcY) const;
+
+[Pixmap_readPixels_4]
+SkPixmap
+bool readPixels(const SkPixmap& dst) const;
+
+[Pixmap_readPixels_3]
+SkPixmap
+bool readPixels(const SkPixmap& dst, int srcX, int srcY) const;
+
+[Pixmap_reset]
+SkPixmap
+void reset();
+
+[Pixmap_reset_2]
+SkPixmap
+void reset(const SkImageInfo& info, const void* addr, size_t rowBytes);
+
+[Pixmap_rowBytes]
+SkPixmap
+size_t rowBytes() const;
+
+[Pixmap_rowBytesAsPixels]
+SkPixmap
+int rowBytesAsPixels() const;
+
+[Pixmap_scalePixels]
+SkPixmap
+bool scalePixels(const SkPixmap& dst, SkFilterQuality filterQuality) const;
+
+[Pixmap_setColorSpace]
+SkPixmap
+void setColorSpace(sk_sp<SkColorSpace> colorSpace);
+
+[Pixmap_shiftPerPixel]
+SkPixmap
+int shiftPerPixel() const;
+
+[Pixmap_width]
+SkPixmap
+int width() const;
+
+[Pixmap_writable_addr]
+SkPixmap
+void* writable_addr() const;
+
+[Pixmap_writable_addr_2]
+SkPixmap
+void* writable_addr(int x, int y) const;
+
+[Pixmap_writable_addr16]
+SkPixmap
+uint16_t* writable_addr16(int x, int y) const;
+
+[Pixmap_writable_addr32]
+SkPixmap
+uint32_t* writable_addr32(int x, int y) const;
+
+[Pixmap_writable_addr64]
+SkPixmap
+uint64_t* writable_addr64(int x, int y) const;
+
+[Pixmap_writable_addr8]
+SkPixmap
+uint8_t* writable_addr8(int x, int y) const;
+
+[Pixmap_writable_addrF16]
+SkPixmap
+uint16_t* writable_addrF16(int x, int y) const;
+
+[Point_CrossProduct]
+SkPoint
+static SkScalar CrossProduct(const SkVector& a, const SkVector& b);
+
+[Point_Distance]
+SkPoint
+static SkScalar Distance(const SkPoint& a, const SkPoint& b);
+
+[Point_DotProduct]
+SkPoint
+static SkScalar DotProduct(const SkVector& a, const SkVector& b);
+
+[Point_Length]
+SkPoint
+static SkScalar Length(SkScalar x, SkScalar y);
+
+[Point_Make]
+SkPoint
+static constexpr SkPoint Make(SkScalar x, SkScalar y);
+
+[Point_Normalize]
+SkPoint
+static SkScalar Normalize(SkVector* vec);
+
+[Point_Offset_2]
+SkPoint
+static void Offset(SkPoint points[], int count, SkScalar dx, SkScalar dy);
+
+[Point_Offset]
+SkPoint
+static void Offset(SkPoint points[], int count, const SkVector& offset);
+
+[Point_cross]
+SkPoint
+SkScalar cross(const SkVector& vec) const;
+
+[Point_distanceToOrigin]
+SkPoint
+SkScalar distanceToOrigin() const;
+
+[Point_dot]
+SkPoint
+SkScalar dot(const SkVector& vec) const;
+
+[Point_equals]
+SkPoint
+bool equals(SkScalar x, SkScalar y) const;
+
+[Point_isFinite]
+SkPoint
+bool isFinite() const;
+
+[Point_isZero]
+SkPoint
+bool isZero() const;
+
+[Point_iset_2]
+SkPoint
+void iset(const SkIPoint& p);
+
+[Point_iset]
+SkPoint
+void iset(int32_t x, int32_t y);
+
+[Point_length_2]
+SkPoint
+SkScalar length() const;
+
+[Point_negate]
+SkPoint
+void negate();
+
+[Point_normalize_2]
+SkPoint
+bool normalize();
+
+[Point_offset_3]
+SkPoint
+void offset(SkScalar dx, SkScalar dy);
+
+[Point_notequal_operator]
+SkPoint
+bool operator!=(const SkPoint& a, const SkPoint& b);
+
+[Point_multiply_operator]
+SkPoint
+SkPoint operator*(SkScalar scale) const;
+
+[Point_multiplyby_operator]
+SkPoint
+SkPoint& operator*=(SkScalar scale);
+
+[Point_add_operator]
+SkPoint
+SkPoint operator+(const SkPoint& a, const SkVector& b);
+
+[Point_addto_operator]
+SkPoint
+void operator+=(const SkVector& v);
+
+[Point_minus_operator]
+SkPoint
+SkPoint operator-() const;
+
+[Point_subtract_operator]
+SkPoint
+SkVector operator-(const SkPoint& a, const SkPoint& b);
+
+[Point_subtractfrom_operator]
+SkPoint
+void operator-=(const SkVector& v);
+
+[Point_equal_operator]
+SkPoint
+bool operator==(const SkPoint& a, const SkPoint& b);
+
+[Point_scale]
+SkPoint
+void scale(SkScalar scale, SkPoint* dst) const;
+
+[Point_scale_2]
+SkPoint
+void scale(SkScalar value);
+
+[Point_set]
+SkPoint
+void set(SkScalar x, SkScalar y);
+
+[Point_setAbs]
+SkPoint
+void setAbs(const SkPoint& pt);
+
+[Point_setLength]
+SkPoint
+bool setLength(SkScalar length);
+
+[Point_setLength_2]
+SkPoint
+bool setLength(SkScalar x, SkScalar y, SkScalar length);
+
+[Point_setNormalize]
+SkPoint
+bool setNormalize(SkScalar x, SkScalar y);
+
+[Point_x]
+SkPoint
+SkScalar x() const;
+
+[Point_y]
+SkPoint
+SkScalar y() const;
+
+[RRect_MakeEmpty]
+SkRRect
+static SkRRect MakeEmpty();
+
+[RRect_MakeOval]
+SkRRect
+static SkRRect MakeOval(const SkRect& oval);
+
+[RRect_MakeRect]
+SkRRect
+static SkRRect MakeRect(const SkRect& r);
+
+[RRect_MakeRectXY]
+SkRRect
+static SkRRect MakeRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad);
+
+[RRect_empty_constructor]
+SkRRect
+SkRRect();
+
+[RRect_copy_const_SkRRect]
+SkRRect
+SkRRect(const SkRRect& rrect);
+
+[RRect_contains]
+SkRRect
+bool contains(const SkRect& rect) const;
+
+[RRect_dump_2]
+SkRRect
+void dump() const;
+
+[RRect_dump]
+SkRRect
+void dump(bool asHex) const;
+
+[RRect_dumpHex]
+SkRRect
+void dumpHex() const;
+
+[RRect_Corner]
+SkRRect
+enum Corner { kUpperLeft_Corner, kUpperRight_Corner, kLowerRight_Corner, kLowerLeft_Corner, };
+
+[RRect_Type]
+SkRRect
+enum Type { kEmpty_Type, kRect_Type, kOval_Type, kSimple_Type, kNinePatch_Type, kComplex_Type, kLastType = kComplex_Type, };
+
+[RRect_getBounds]
+SkRRect
+const SkRect& getBounds() const;
+
+[RRect_getSimpleRadii]
+SkRRect
+SkVector getSimpleRadii() const;
+
+[RRect_getType]
+SkRRect
+Type getType() const;
+
+[RRect_height]
+SkRRect
+SkScalar height() const;
+
+[RRect_inset_2]
+SkRRect
+void inset(SkScalar dx, SkScalar dy);
+
+[RRect_inset]
+SkRRect
+void inset(SkScalar dx, SkScalar dy, SkRRect* dst) const;
+
+[RRect_isComplex]
+SkRRect
+bool isComplex() const;
+
+[RRect_isEmpty]
+SkRRect
+bool isEmpty() const;
+
+[RRect_isNinePatch]
+SkRRect
+bool isNinePatch() const;
+
+[RRect_isOval]
+SkRRect
+bool isOval() const;
+
+[RRect_isRect]
+SkRRect
+bool isRect() const;
+
+[RRect_isSimple]
+SkRRect
+bool isSimple() const;
+
+[RRect_isValid]
+SkRRect
+bool isValid() const;
+
+[RRect_makeOffset]
+SkRRect
+SkRRect makeOffset(SkScalar dx, SkScalar dy) const;
+
+[RRect_offset]
+SkRRect
+void offset(SkScalar dx, SkScalar dy);
+
+[RRect_notequal_operator]
+SkRRect
+bool operator!=(const SkRRect& a, const SkRRect& b);
+
+[RRect_copy_operator]
+SkRRect
+SkRRect& operator=(const SkRRect& rrect);
+
+[RRect_equal_operator]
+SkRRect
+bool operator==(const SkRRect& a, const SkRRect& b);
+
+[RRect_outset_2]
+SkRRect
+void outset(SkScalar dx, SkScalar dy);
+
+[RRect_outset]
+SkRRect
+void outset(SkScalar dx, SkScalar dy, SkRRect* dst) const;
+
+[RRect_radii]
+SkRRect
+SkVector radii(Corner corner) const;
+
+[RRect_readFromMemory]
+SkRRect
+size_t readFromMemory(const void* buffer, size_t length);
+
+[RRect_rect]
+SkRRect
+const SkRect& rect() const;
+
+[RRect_setEmpty]
+SkRRect
+void setEmpty();
+
+[RRect_setNinePatch]
+SkRRect
+void setNinePatch(const SkRect& rect, SkScalar leftRad, SkScalar topRad, SkScalar rightRad, SkScalar bottomRad);
+
+[RRect_setOval]
+SkRRect
+void setOval(const SkRect& oval);
+
+[RRect_setRect]
+SkRRect
+void setRect(const SkRect& rect);
+
+[RRect_setRectRadii]
+SkRRect
+void setRectRadii(const SkRect& rect, const SkVector radii[4]);
+
+[RRect_setRectXY]
+SkRRect
+void setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad);
+
+[RRect_transform]
+SkRRect
+bool transform(const SkMatrix& matrix, SkRRect* dst) const;
+
+[RRect_type_2]
+SkRRect
+Type type() const;
+
+[RRect_width]
+SkRRect
+SkScalar width() const;
+
+[RRect_writeToMemory]
+SkRRect
+size_t writeToMemory(void* buffer) const;
+
+[Rect_Intersects]
+SkRect
+static bool Intersects(const SkRect& a, const SkRect& b);
+
+[Rect_Make_2]
+SkRect
+static SkRect Make(const SkIRect& irect);
+
+[Rect_Make]
+SkRect
+static SkRect Make(const SkISize& size);
+
+[Rect_MakeEmpty]
+SkRect
+static constexpr SkRect MakeEmpty();
+
+[Rect_MakeIWH]
+SkRect
+static SkRect MakeIWH(int w, int h);
+
+[Rect_MakeLTRB]
+SkRect
+static constexpr SkRect MakeLTRB(SkScalar l, SkScalar t, SkScalar r, SkScalar b);
+
+[Rect_MakeSize]
+SkRect
+static constexpr SkRect MakeSize(const SkSize& size);
+
+[Rect_MakeWH]
+SkRect
+static constexpr SkRect MakeWH(SkScalar w, SkScalar h);
+
+[Rect_MakeXYWH]
+SkRect
+static constexpr SkRect MakeXYWH(SkScalar x, SkScalar y, SkScalar w, SkScalar h);
+
+[Rect_asScalars]
+SkRect
+const SkScalar* asScalars() const;
+
+[Rect_bottom]
+SkRect
+SkScalar    bottom() const;
+
+[Rect_centerX]
+SkRect
+SkScalar    centerX() const;
+
+[Rect_centerY]
+SkRect
+SkScalar    centerY() const;
+
+[Rect_contains]
+SkRect
+bool contains(SkScalar x, SkScalar y) const;
+
+[Rect_contains_3]
+SkRect
+bool contains(const SkIRect& r) const;
+
+[Rect_contains_2]
+SkRect
+bool contains(const SkRect& r) const;
+
+[Rect_dump_2]
+SkRect
+void dump() const;
+
+[Rect_dump]
+SkRect
+void dump(bool asHex) const;
+
+[Rect_dumpHex]
+SkRect
+void dumpHex() const;
+
+[Rect_height]
+SkRect
+SkScalar    height() const;
+
+[Rect_inset]
+SkRect
+void inset(SkScalar dx, SkScalar dy);
+
+[Rect_intersect_2]
+SkRect
+bool intersect(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom);
+
+[Rect_intersect_3]
+SkRect
+bool intersect(const SkRect& a, const SkRect& b);
+
+[Rect_intersect]
+SkRect
+bool intersect(const SkRect& r);
+
+[Rect_intersects_3]
+SkRect
+bool intersects(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom) const;
+
+[Rect_intersects_2]
+SkRect
+bool intersects(const SkRect& r) const;
+
+[Rect_isEmpty]
+SkRect
+bool isEmpty() const;
+
+[Rect_isFinite]
+SkRect
+bool isFinite() const;
+
+[Rect_isSorted]
+SkRect
+bool isSorted() const;
+
+[Rect_iset]
+SkRect
+void iset(int left, int top, int right, int bottom);
+
+[Rect_isetWH]
+SkRect
+void isetWH(int width, int height);
+
+[Rect_join]
+SkRect
+void join(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom);
+
+[Rect_join_2]
+SkRect
+void join(const SkRect& r);
+
+[Rect_joinNonEmptyArg]
+SkRect
+void joinNonEmptyArg(const SkRect& r);
+
+[Rect_joinPossiblyEmptyRect]
+SkRect
+void joinPossiblyEmptyRect(const SkRect& r);
+
+[Rect_left]
+SkRect
+SkScalar    left() const;
+
+[Rect_makeInset]
+SkRect
+SkRect makeInset(SkScalar dx, SkScalar dy) const;
+
+[Rect_makeOffset]
+SkRect
+SkRect makeOffset(SkScalar dx, SkScalar dy) const;
+
+[Rect_makeOutset]
+SkRect
+SkRect makeOutset(SkScalar dx, SkScalar dy) const;
+
+[Rect_makeSorted]
+SkRect
+SkRect makeSorted() const;
+
+[Rect_offset]
+SkRect
+void offset(SkScalar dx, SkScalar dy);
+
+[Rect_offset_2]
+SkRect
+void offset(const SkPoint& delta);
+
+[Rect_offsetTo]
+SkRect
+void offsetTo(SkScalar newX, SkScalar newY);
+
+[Rect_notequal_operator]
+SkRect
+bool operator!=(const SkRect& a, const SkRect& b);
+
+[Rect_equal_operator]
+SkRect
+bool operator==(const SkRect& a, const SkRect& b);
+
+[Rect_outset]
+SkRect
+void outset(SkScalar dx, SkScalar dy);
+
+[Rect_right]
+SkRect
+SkScalar    right() const;
+
+[Rect_round_2]
+SkRect
+SkIRect round() const;
+
+[Rect_round]
+SkRect
+void round(SkIRect* dst) const;
+
+[Rect_roundIn]
+SkRect
+void roundIn(SkIRect* dst) const;
+
+[Rect_roundOut_3]
+SkRect
+SkIRect roundOut() const;
+
+[Rect_roundOut]
+SkRect
+void roundOut(SkIRect* dst) const;
+
+[Rect_roundOut_2]
+SkRect
+void roundOut(SkRect* dst) const;
+
+[Rect_set_2]
+SkRect
+void set(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom);
+
+[Rect_set]
+SkRect
+void set(const SkIRect& src);
+
+[Rect_set_3]
+SkRect
+void set(const SkPoint pts[], int count);
+
+[Rect_set_4]
+SkRect
+void set(const SkPoint& p0, const SkPoint& p1);
+
+[Rect_setBounds]
+SkRect
+void setBounds(const SkPoint pts[], int count);
+
+[Rect_setBoundsCheck]
+SkRect
+bool setBoundsCheck(const SkPoint pts[], int count);
+
+[Rect_setBoundsNoCheck]
+SkRect
+void setBoundsNoCheck(const SkPoint pts[], int count);
+
+[Rect_setEmpty]
+SkRect
+void setEmpty();
+
+[Rect_setLTRB]
+SkRect
+void setLTRB(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom);
+
+[Rect_setWH]
+SkRect
+void setWH(SkScalar width, SkScalar height);
+
+[Rect_setXYWH]
+SkRect
+void setXYWH(SkScalar x, SkScalar y, SkScalar width, SkScalar height);
+
+[Rect_sort]
+SkRect
+void sort();
+
+[Rect_toQuad]
+SkRect
+void toQuad(SkPoint quad[4]) const;
+
+[Rect_top]
+SkRect
+SkScalar    top() const;
+
+[Rect_width]
+SkRect
+SkScalar    width() const;
+
+[Rect_x]
+SkRect
+SkScalar    x() const;
+
+[Rect_y]
+SkRect
+SkScalar    y() const;
+
+[Region_Cliperator_const_SkRegion_const_SkIRect]
+SkRegion
+Cliperator(const SkRegion& region, const SkIRect& clip);
+
+[Region_Iterator_Iterator]
+SkRegion
+Iterator();
+
+[Region_Iterator_copy_const_SkRegion]
+SkRegion
+Iterator(const SkRegion& region);
+
+[Region_copy_const_SkIRect]
+SkRegion
+explicit SkRegion(const SkIRect& rect);
+
+[Region_empty_constructor]
+SkRegion
+SkRegion();
+
+[Region_copy_const_SkRegion]
+SkRegion
+SkRegion(const SkRegion& region);
+
+[Region_Spanerator_const_SkRegion_int_int_int]
+SkRegion
+Spanerator(const SkRegion& region, int y, int left, int right);
+
+[Region_computeRegionComplexity]
+SkRegion
+int computeRegionComplexity() const;
+
+[Region_contains_2]
+SkRegion
+bool contains(const SkIRect& other) const;
+
+[Region_contains_3]
+SkRegion
+bool contains(const SkRegion& other) const;
+
+[Region_contains]
+SkRegion
+bool contains(int32_t x, int32_t y) const;
+
+[Region_Iterator_done]
+SkRegion
+bool done() const;
+
+[Region_Cliperator_done]
+SkRegion
+bool done();
+
+[Region_Op]
+SkRegion
+enum Op { kDifference_Op, kIntersect_Op, kUnion_Op, kXOR_Op, kReverseDifference_Op, kReplace_Op, kLastOp = kReplace_Op, };
+
+[Region_getBoundaryPath]
+SkRegion
+bool getBoundaryPath(SkPath* path) const;
+
+[Region_getBounds]
+SkRegion
+const SkIRect& getBounds() const;
+
+[Region_intersects]
+SkRegion
+bool intersects(const SkIRect& rect) const;
+
+[Region_intersects_2]
+SkRegion
+bool intersects(const SkRegion& other) const;
+
+[Region_isComplex]
+SkRegion
+bool isComplex() const;
+
+[Region_isEmpty]
+SkRegion
+bool isEmpty() const;
+
+[Region_isRect]
+SkRegion
+bool isRect() const;
+
+[Region_Spanerator_next]
+SkRegion
+bool next(int* left, int* right);
+
+[Region_Cliperator_next]
+SkRegion
+void  next();
+
+[Region_Iterator_next]
+SkRegion
+void next();
+
+[Region_op_1]
+SkRegion
+bool op(const SkIRect& rect, Op op);
+
+[Region_op_4]
+SkRegion
+bool op(const SkIRect& rect, const SkRegion& rgn, Op op);
+
+[Region_op_3]
+SkRegion
+bool op(const SkRegion& rgn, Op op);
+
+[Region_op_5]
+SkRegion
+bool op(const SkRegion& rgn, const SkIRect& rect, Op op);
+
+[Region_op_6]
+SkRegion
+bool op(const SkRegion& rgna, const SkRegion& rgnb, Op op);
+
+[Region_op_2]
+SkRegion
+bool op(int left, int top, int right, int bottom, Op op);
+
+[Region_notequal1_operator]
+SkRegion
+bool operator!=(const SkRegion& other) const;
+
+[Region_copy_operator]
+SkRegion
+SkRegion& operator=(const SkRegion& region);
+
+[Region_equal1_operator]
+SkRegion
+bool operator==(const SkRegion& other) const;
+
+[Region_quickContains]
+SkRegion
+bool quickContains(const SkIRect& r) const;
+
+[Region_quickContains_2]
+SkRegion
+bool quickContains(int32_t left, int32_t top, int32_t right, int32_t bottom) const;
+
+[Region_quickReject]
+SkRegion
+bool quickReject(const SkIRect& rect) const;
+
+[Region_quickReject_2]
+SkRegion
+bool quickReject(const SkRegion& rgn) const;
+
+[Region_readFromMemory]
+SkRegion
+size_t readFromMemory(const void* buffer, size_t length);
+
+[Region_Cliperator_rect]
+[Region_Iterator_rect]
+SkRegion
+const SkIRect& rect() const;
+
+[Region_Iterator_reset]
+SkRegion
+void reset(const SkRegion& region);
+
+[Region_Iterator_rewind]
+SkRegion
+bool rewind();
+
+[Region_Iterator_rgn]
+SkRegion
+const SkRegion* rgn() const;
+
+[Region_set]
+SkRegion
+bool set(const SkRegion& src);
+
+[Region_setEmpty]
+SkRegion
+bool setEmpty();
+
+[Region_setPath]
+SkRegion
+bool setPath(const SkPath& path, const SkRegion& clip);
+
+[Region_setRect]
+SkRegion
+bool setRect(const SkIRect& rect);
+
+[Region_setRect_2]
+SkRegion
+bool setRect(int32_t left, int32_t top, int32_t right, int32_t bottom);
+
+[Region_setRects]
+SkRegion
+bool setRects(const SkIRect rects[], int count);
+
+[Region_setRegion]
+SkRegion
+bool setRegion(const SkRegion& region);
+
+[Region_swap]
+SkRegion
+void swap(SkRegion& other);
+
+[Region_translate]
+SkRegion
+void translate(int dx, int dy);
+
+[Region_translate_2]
+SkRegion
+void translate(int dx, int dy, SkRegion* dst) const;
+
+[Region_writeToMemory]
+SkRegion
+size_t writeToMemory(void* buffer) const;
+
+[Region_destructor]
+SkRegion
+~SkRegion();
+
+[Surface_MakeFromBackendTexture]
+SkSurface
+static sk_sp<SkSurface> MakeFromBackendTexture(GrContext* context, const GrBackendTexture& backendTexture, GrSurfaceOrigin origin, int sampleCnt, SkColorType colorType, sk_sp<SkColorSpace> colorSpace, const SkSurfaceProps* surfaceProps);
+
+[Surface_MakeFromBackendTextureAsRenderTarget]
+SkSurface
+static sk_sp<SkSurface> MakeFromBackendTextureAsRenderTarget(GrContext* context, const GrBackendTexture& backendTexture, GrSurfaceOrigin origin, int sampleCnt, SkColorType colorType, sk_sp<SkColorSpace> colorSpace, const SkSurfaceProps* surfaceProps);
+
+[Surface_MakeNull]
+SkSurface
+static sk_sp<SkSurface> MakeNull(int width, int height);
+
+[Surface_MakeRaster_2]
+SkSurface
+static sk_sp<SkSurface> MakeRaster(const SkImageInfo& imageInfo, const SkSurfaceProps* props = nullptr);
+
+[Surface_MakeRaster]
+SkSurface
+static sk_sp<SkSurface> MakeRaster(const SkImageInfo& imageInfo, size_t rowBytes, const SkSurfaceProps* surfaceProps);
+
+[Surface_MakeRasterDirect]
+SkSurface
+static sk_sp<SkSurface> MakeRasterDirect(const SkImageInfo& imageInfo, void* pixels, size_t rowBytes, const SkSurfaceProps* surfaceProps = nullptr);
+
+[Surface_MakeRasterDirectReleaseProc]
+SkSurface
+static sk_sp<SkSurface> MakeRasterDirectReleaseProc(const SkImageInfo& imageInfo, void* pixels, size_t rowBytes, void (*releaseProc) (void* pixels, void* context) , void* context, const SkSurfaceProps* surfaceProps = nullptr);
+
+[Surface_MakeRasterN32Premul]
+SkSurface
+static sk_sp<SkSurface> MakeRasterN32Premul(int width, int height, const SkSurfaceProps* surfaceProps = nullptr);
+
+[Surface_MakeRenderTarget_3]
+SkSurface
+static sk_sp<SkSurface> MakeRenderTarget(GrContext* context, SkBudgeted budgeted, const SkImageInfo& imageInfo);
+
+[Surface_MakeRenderTarget]
+SkSurface
+static sk_sp<SkSurface> MakeRenderTarget(GrContext* context, SkBudgeted budgeted, const SkImageInfo& imageInfo, int sampleCount, GrSurfaceOrigin surfaceOrigin, const SkSurfaceProps* surfaceProps, bool shouldCreateWithMips = false);
+
+[Surface_MakeRenderTarget_2]
+SkSurface
+static sk_sp<SkSurface> MakeRenderTarget(GrContext* context, SkBudgeted budgeted, const SkImageInfo& imageInfo, int sampleCount, const SkSurfaceProps* props);
+
+[Surface_characterize]
+SkSurface
+bool characterize(SkSurfaceCharacterization* characterization) const;
+
+[Surface_draw_2]
+SkSurface
+bool draw(SkDeferredDisplayList* deferredDisplayList);
+
+[Surface_draw]
+SkSurface
+void draw(SkCanvas* canvas, SkScalar x, SkScalar y, const SkPaint* paint);
+
+[Surface_notifyContentWillChange]
+SkSurface
+uint32_t generationID();
+
+[Surface_getCanvas]
+SkSurface
+SkCanvas* getCanvas();
+
+[Surface_height]
+SkSurface
+int height() const;
+
+[Surface_makeImageSnapshot]
+SkSurface
+sk_sp<SkImage> makeImageSnapshot();
+
+[Surface_makeImageSnapshot_2]
+SkSurface
+sk_sp<SkImage> makeImageSnapshot(const SkIRect& bounds);
+
+[Surface_makeSurface]
+SkSurface
+sk_sp<SkSurface> makeSurface(const SkImageInfo& imageInfo);
+
+[Surface_notifyContentWillChange]
+SkSurface
+void notifyContentWillChange(ContentChangeMode mode);
+
+[Surface_peekPixels]
+SkSurface
+bool peekPixels(SkPixmap* pixmap);
+
+[Surface_props]
+SkSurface
+const SkSurfaceProps& props() const;
+
+[Surface_readPixels_3]
+SkSurface
+bool readPixels(const SkBitmap& dst, int srcX, int srcY);
+
+[Surface_readPixels_2]
+SkSurface
+bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, int srcX, int srcY);
+
+[Surface_readPixels]
+SkSurface
+bool readPixels(const SkPixmap& dst, int srcX, int srcY);
+
+[Surface_width]
+SkSurface
+int width() const;
+
+[Surface_writePixels_2]
+SkSurface
+void writePixels(const SkBitmap& src, int dstX, int dstY);
+
+[Surface_writePixels]
+SkSurface
+void writePixels(const SkPixmap& src, int dstX, int dstY);
+
+[TextBlob_Deserialize]
+SkTextBlob
+static sk_sp<SkTextBlob> Deserialize(const void* data, size_t size, const SkDeserialProcs& procs);
+
+[TextBlob_MakeFromString]
+SkTextBlob
+static sk_sp<SkTextBlob> MakeFromString(const char* string, const SkFont& font, SkTextEncoding encoding = kUTF8_SkTextEncoding);
+
+[TextBlob_MakeFromText]
+SkTextBlob
+static sk_sp<SkTextBlob> MakeFromText(const void* text, size_t byteLength, const SkFont& font, SkTextEncoding encoding = kUTF8_SkTextEncoding);
+
+[TextBlob_bounds]
+SkTextBlob
+const SkRect& bounds() const;
+
+[TextBlob_getIntercepts]
+SkTextBlob
+int getIntercepts(const SkScalar bounds[2], SkScalar intervals[], const SkPaint* paint = nullptr) const;
+
+[TextBlob_serialize]
+SkTextBlob
+size_t serialize(const SkSerialProcs& procs, void* memory, size_t memory_size) const;
+
+[TextBlob_serialize_2]
+SkTextBlob
+sk_sp<SkData> serialize(const SkSerialProcs& procs) const;
+
+[TextBlob_uniqueID]
+SkTextBlob
+uint32_t uniqueID() const;
+
+[TextBlobBuilder_empty_constructor]
+SkTextBlobBuilder
+SkTextBlobBuilder();
+
+[TextBlobBuilder_allocRun]
+SkTextBlobBuilder
+const RunBuffer& allocRun(const SkFont& font, int count, SkScalar x, SkScalar y, const SkRect* bounds = nullptr);
+
+[TextBlobBuilder_allocRunPos]
+SkTextBlobBuilder
+const RunBuffer& allocRunPos(const SkFont& font, int count, const SkRect* bounds = nullptr);
+
+[TextBlobBuilder_allocRunPosH]
+SkTextBlobBuilder
+const RunBuffer& allocRunPosH(const SkFont& font, int count, SkScalar y, const SkRect* bounds = nullptr);
+
+[TextBlobBuilder_make]
+SkTextBlobBuilder
+sk_sp<SkTextBlob> make();
+
diff --git a/tools/fiddle/draw.cpp b/tools/fiddle/draw.cpp
index bbc463c..f17380a 100644
--- a/tools/fiddle/draw.cpp
+++ b/tools/fiddle/draw.cpp
@@ -22,9 +22,7 @@
     matrix.setScale(0.75f, 0.75f);
     matrix.preRotate(frame * 30.0f * duration); // If an animation, rotate at 30 deg/s.
     SkPaint paint;
-    paint.setShader(image->makeShader(SkShader::kRepeat_TileMode,
-                                      SkShader::kRepeat_TileMode,
-                                      &matrix));
+    paint.setShader(image->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, &matrix));
     canvas->drawPaint(paint);
     SkDebugf("This is text output: %d", 2);
 
diff --git a/tools/fiddle/examples.cpp b/tools/fiddle/examples.cpp
new file mode 100644
index 0000000..d5cd067
--- /dev/null
+++ b/tools/fiddle/examples.cpp
@@ -0,0 +1,37 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+
+#include "examples.h"
+
+template sk_tools::Registry<fiddle::Example>* sk_tools::Registry<fiddle::Example>::gHead;
+
+// These globals are needed by fiddles:
+GrBackendTexture backEndTexture;
+GrBackendRenderTarget backEndRenderTarget;
+GrBackendTexture backEndTextureRenderTarget;
+SkBitmap source;
+sk_sp<SkImage> image;
+double duration = 1.0;
+double frame = 1.0;
+
+int main() {
+    constexpr int kImgCount = 7;
+    sk_sp<SkImage> images[kImgCount];
+    SkBitmap bitmaps[kImgCount];
+    for (int i = 1; i < kImgCount; ++i) {
+        SkString path = SkStringPrintf("resources/images/example_%d.png", i);
+        images[i] = SkImage::MakeFromEncoded(SkData::MakeFromFileName(path.c_str()));
+        SkAssertResult(images[i] && images[i]->asLegacyBitmap(&bitmaps[i]));
+    }
+    for (const fiddle::Example& example : sk_tools::Registry<fiddle::Example>::Range()) {
+        SkASSERT((unsigned)example.fImageIndex < (unsigned)kImgCount);
+        image = images[example.fImageIndex];
+        source = bitmaps[example.fImageIndex];
+        SkBitmap bmp;
+        bmp.allocN32Pixels(example.fWidth, example.fHeight);
+        bmp.eraseColor(SK_ColorWHITE);
+        SkCanvas canvas(bmp);
+        SkDebugf("==> %s\n", example.fName);
+        example.fFunc(&canvas);
+    }
+}
diff --git a/tools/fiddle/examples.h b/tools/fiddle/examples.h
new file mode 100644
index 0000000..70927b8
--- /dev/null
+++ b/tools/fiddle/examples.h
@@ -0,0 +1,41 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#ifndef examples_DEFINED
+#define examples_DEFINED
+
+#include "Registry.h"
+#include "skia.h"
+
+#include <cmath>
+#include <string>
+
+namespace fiddle {
+struct Example {
+    void (*fFunc)(SkCanvas*);
+    const char* fName;
+    int fImageIndex;
+    int fWidth;
+    int fHeight;
+    bool fText;
+    bool fSRGB;
+    bool fF16;
+    bool fAnimation;
+    bool fOffscreen;
+};
+}
+
+extern GrBackendTexture backEndTexture;
+extern GrBackendRenderTarget backEndRenderTarget;
+extern GrBackendTexture backEndTextureRenderTarget;
+extern SkBitmap source;
+extern sk_sp<SkImage> image;
+extern double duration; // The total duration of the animation in seconds.
+extern double frame;    // A value in [0, 1] of where we are in the animation.
+
+#define REG_FIDDLE(NAME, W, H, TEXT, I)                                            \
+    namespace example_##NAME { void draw(SkCanvas*);  }                            \
+    sk_tools::Registry<fiddle::Example> reg_##NAME(                                \
+        fiddle::Example{&example_##NAME::draw, #NAME, I, W, H, TEXT, false, false, false, false}); \
+    namespace example_##NAME
+
+#endif  // examples_DEFINED
diff --git a/tools/fiddle/fiddle_main.cpp b/tools/fiddle/fiddle_main.cpp
index e8d258d..97e647b 100644
--- a/tools/fiddle/fiddle_main.cpp
+++ b/tools/fiddle/fiddle_main.cpp
@@ -10,15 +10,17 @@
 #include <sstream>
 #include <string>
 
+#include "CommandLineFlags.h"
 #include "SkAutoPixmapStorage.h"
-#include "SkCommandLineFlags.h"
 #include "SkMipMap.h"
 #include "SkUtils.h"
 
 #include "fiddle_main.h"
 
-DEFINE_double(duration, 1.0, "The total duration, in seconds, of the animation we are drawing.");
-DEFINE_double(frame, 1.0, "A double value in [0, 1] that specifies the point in animation to draw.");
+static DEFINE_double(duration, 1.0,
+                     "The total duration, in seconds, of the animation we are drawing.");
+static DEFINE_double(frame, 1.0,
+                     "A double value in [0, 1] that specifies the point in animation to draw.");
 
 #include "GrBackendSurface.h"
 #include "GrContextPriv.h"
@@ -234,7 +236,7 @@
 }
 
 int main(int argc, char** argv) {
-    SkCommandLineFlags::Parse(argc, argv);
+    CommandLineFlags::Parse(argc, argv);
     duration = FLAGS_duration;
     frame = FLAGS_frame;
     DrawOptions options = GetDrawOptions();
diff --git a/tools/fiddle/make_all_examples_cpp.py b/tools/fiddle/make_all_examples_cpp.py
new file mode 100755
index 0000000..4c8dcd2
--- /dev/null
+++ b/tools/fiddle/make_all_examples_cpp.py
@@ -0,0 +1,15 @@
+#! /usr/bin/env python
+# Copyright 2019 Google LLC.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+''' Run this script to re-generate the `all_examples.cpp` file after adding or
+    deleting example fiddles. '''
+import glob
+import os
+os.chdir(os.path.dirname(__file__))
+with open('all_examples.cpp', 'w') as o:
+    o.write('// Copyright 2019 Google LLC.\n// Use of this source code is '
+            'governed by a BSD-style license that can be found in the '
+            'LICENSE file.\n')
+    for path in sorted(glob.glob('../../docs/examples/*.cpp')):
+        o.write('#include "%s"\n' % path)
diff --git a/tools/flags/CommandLineFlags.cpp b/tools/flags/CommandLineFlags.cpp
new file mode 100644
index 0000000..9015531
--- /dev/null
+++ b/tools/flags/CommandLineFlags.cpp
@@ -0,0 +1,400 @@
+/*
+ * 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 "CommandLineFlags.h"
+#include "SkTDArray.h"
+#include "SkTSort.h"
+
+#include <stdlib.h>
+
+template <typename T> static void ignore_result(const T&) {}
+
+bool SkFlagInfo::CreateStringFlag(const char*                    name,
+                                  const char*                    shortName,
+                                  CommandLineFlags::StringArray* pStrings,
+                                  const char*                    defaultValue,
+                                  const char*                    helpString,
+                                  const char*                    extendedHelpString) {
+    SkFlagInfo* info =
+            new SkFlagInfo(name, shortName, kString_FlagType, helpString, extendedHelpString);
+    info->fDefaultString.set(defaultValue);
+
+    info->fStrings = pStrings;
+    SetDefaultStrings(pStrings, defaultValue);
+    return true;
+}
+
+void SkFlagInfo::SetDefaultStrings(CommandLineFlags::StringArray* pStrings,
+                                   const char*                    defaultValue) {
+    pStrings->reset();
+    if (nullptr == defaultValue) {
+        return;
+    }
+    // If default is "", leave the array empty.
+    size_t defaultLength = strlen(defaultValue);
+    if (defaultLength > 0) {
+        const char* const defaultEnd = defaultValue + defaultLength;
+        const char*       begin      = defaultValue;
+        while (true) {
+            while (begin < defaultEnd && ' ' == *begin) {
+                begin++;
+            }
+            if (begin < defaultEnd) {
+                const char* end = begin + 1;
+                while (end < defaultEnd && ' ' != *end) {
+                    end++;
+                }
+                size_t length = end - begin;
+                pStrings->append(begin, length);
+                begin = end + 1;
+            } else {
+                break;
+            }
+        }
+    }
+}
+
+static bool string_is_in(const char* target, const char* set[], size_t len) {
+    for (size_t i = 0; i < len; i++) {
+        if (0 == strcmp(target, set[i])) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/**
+ *  Check to see whether string represents a boolean value.
+ *  @param string C style string to parse.
+ *  @param result Pointer to a boolean which will be set to the value in the string, if the
+ *      string represents a boolean.
+ *  @param boolean True if the string represents a boolean, false otherwise.
+ */
+static bool parse_bool_arg(const char* string, bool* result) {
+    static const char* trueValues[] = {"1", "TRUE", "true"};
+    if (string_is_in(string, trueValues, SK_ARRAY_COUNT(trueValues))) {
+        *result = true;
+        return true;
+    }
+    static const char* falseValues[] = {"0", "FALSE", "false"};
+    if (string_is_in(string, falseValues, SK_ARRAY_COUNT(falseValues))) {
+        *result = false;
+        return true;
+    }
+    SkDebugf("Parameter \"%s\" not supported.\n", string);
+    return false;
+}
+
+bool SkFlagInfo::match(const char* string) {
+    if (SkStrStartsWith(string, '-') && strlen(string) > 1) {
+        string++;
+        const SkString* compareName;
+        if (SkStrStartsWith(string, '-') && strlen(string) > 1) {
+            string++;
+            // There were two dashes. Compare against full name.
+            compareName = &fName;
+        } else {
+            // One dash. Compare against the short name.
+            compareName = &fShortName;
+        }
+        if (kBool_FlagType == fFlagType) {
+            // In this case, go ahead and set the value.
+            if (compareName->equals(string)) {
+                *fBoolValue = true;
+                return true;
+            }
+            if (SkStrStartsWith(string, "no") && strlen(string) > 2) {
+                string += 2;
+                // Only allow "no" to be prepended to the full name.
+                if (fName.equals(string)) {
+                    *fBoolValue = false;
+                    return true;
+                }
+                return false;
+            }
+            int equalIndex = SkStrFind(string, "=");
+            if (equalIndex > 0) {
+                // The string has an equal sign. Check to see if the string matches.
+                SkString flag(string, equalIndex);
+                if (flag.equals(*compareName)) {
+                    // Check to see if the remainder beyond the equal sign is true or false:
+                    string += equalIndex + 1;
+                    parse_bool_arg(string, fBoolValue);
+                    return true;
+                } else {
+                    return false;
+                }
+            }
+        }
+        return compareName->equals(string);
+    } else {
+        // Has no dash
+        return false;
+    }
+    return false;
+}
+
+SkFlagInfo* CommandLineFlags::gHead;
+SkString    CommandLineFlags::gUsage;
+
+void CommandLineFlags::SetUsage(const char* usage) { gUsage.set(usage); }
+
+void CommandLineFlags::PrintUsage() { SkDebugf("%s", gUsage.c_str()); }
+
+// Maximum line length for the help message.
+#define LINE_LENGTH 72
+
+static void print_indented(const SkString& text) {
+    size_t      length   = text.size();
+    const char* currLine = text.c_str();
+    const char* stop     = currLine + length;
+    while (currLine < stop) {
+        int lineBreak = SkStrFind(currLine, "\n");
+        if (lineBreak < 0) {
+            lineBreak = static_cast<int>(strlen(currLine));
+        }
+        if (lineBreak > LINE_LENGTH) {
+            // No line break within line length. Will need to insert one.
+            // Find a space before the line break.
+            int spaceIndex = LINE_LENGTH - 1;
+            while (spaceIndex > 0 && currLine[spaceIndex] != ' ') {
+                spaceIndex--;
+            }
+            int gap;
+            if (0 == spaceIndex) {
+                // No spaces on the entire line. Go ahead and break mid word.
+                spaceIndex = LINE_LENGTH;
+                gap        = 0;
+            } else {
+                // Skip the space on the next line
+                gap = 1;
+            }
+            SkDebugf("        %.*s\n", spaceIndex, currLine);
+            currLine += spaceIndex + gap;
+        } else {
+            // the line break is within the limit. Break there.
+            lineBreak++;
+            SkDebugf("        %.*s", lineBreak, currLine);
+            currLine += lineBreak;
+        }
+    }
+}
+
+static void print_help_for_flag(const SkFlagInfo* flag) {
+    SkDebugf("    --%s", flag->name().c_str());
+    const SkString& shortName = flag->shortName();
+    if (shortName.size() > 0) {
+        SkDebugf(" or -%s", shortName.c_str());
+    }
+    SkDebugf(":\ttype: %s", flag->typeAsString().c_str());
+    if (flag->defaultValue().size() > 0) {
+        SkDebugf("\tdefault: %s", flag->defaultValue().c_str());
+    }
+    SkDebugf("\n");
+    const SkString& help = flag->help();
+    print_indented(help);
+    SkDebugf("\n");
+}
+static void print_extended_help_for_flag(const SkFlagInfo* flag) {
+    print_help_for_flag(flag);
+    print_indented(flag->extendedHelp());
+    SkDebugf("\n");
+}
+
+namespace {
+struct CompareFlagsByName {
+    bool operator()(SkFlagInfo* a, SkFlagInfo* b) const {
+        return strcmp(a->name().c_str(), b->name().c_str()) < 0;
+    }
+};
+}  // namespace
+
+void CommandLineFlags::Parse(int argc, const char* const* argv) {
+    // Only allow calling this function once.
+    static bool gOnce;
+    if (gOnce) {
+        SkDebugf("Parse should only be called once at the beginning of main!\n");
+        SkASSERT(false);
+        return;
+    }
+    gOnce = true;
+
+    bool helpPrinted  = false;
+    bool flagsPrinted = false;
+    // Loop over argv, starting with 1, since the first is just the name of the program.
+    for (int i = 1; i < argc; i++) {
+        if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
+            // Print help message.
+            SkTDArray<const char*> helpFlags;
+            for (int j = i + 1; j < argc; j++) {
+                if (SkStrStartsWith(argv[j], '-')) {
+                    break;
+                }
+                helpFlags.append(1, &argv[j]);
+            }
+            if (0 == helpFlags.count()) {
+                // Only print general help message if help for specific flags is not requested.
+                SkDebugf("%s\n%s\n", argv[0], gUsage.c_str());
+            }
+            if (!flagsPrinted) {
+                SkDebugf("Flags:\n");
+                flagsPrinted = true;
+            }
+            if (0 == helpFlags.count()) {
+                // If no flags followed --help, print them all
+                SkTDArray<SkFlagInfo*> allFlags;
+                for (SkFlagInfo* flag = CommandLineFlags::gHead; flag; flag = flag->next()) {
+                    allFlags.push_back(flag);
+                }
+                SkTQSort(&allFlags[0], &allFlags[allFlags.count() - 1], CompareFlagsByName());
+                for (int i = 0; i < allFlags.count(); ++i) {
+                    print_help_for_flag(allFlags[i]);
+                    if (allFlags[i]->extendedHelp().size() > 0) {
+                        SkDebugf("        Use '--help %s' for more information.\n",
+                                 allFlags[i]->name().c_str());
+                    }
+                }
+            } else {
+                for (SkFlagInfo* flag = CommandLineFlags::gHead; flag; flag = flag->next()) {
+                    for (int k = 0; k < helpFlags.count(); k++) {
+                        if (flag->name().equals(helpFlags[k]) ||
+                            flag->shortName().equals(helpFlags[k])) {
+                            print_extended_help_for_flag(flag);
+                            helpFlags.remove(k);
+                            break;
+                        }
+                    }
+                }
+            }
+            if (helpFlags.count() > 0) {
+                SkDebugf("Requested help for unrecognized flags:\n");
+                for (int k = 0; k < helpFlags.count(); k++) {
+                    SkDebugf("    --%s\n", helpFlags[k]);
+                }
+            }
+            helpPrinted = true;
+        }
+        if (!helpPrinted) {
+            SkFlagInfo* matchedFlag = nullptr;
+            SkFlagInfo* flag        = gHead;
+            int         startI      = i;
+            while (flag != nullptr) {
+                if (flag->match(argv[startI])) {
+                    i = startI;
+                    if (matchedFlag) {
+                        // Don't redefine the same flag with different types.
+                        SkASSERT(matchedFlag->getFlagType() == flag->getFlagType());
+                    } else {
+                        matchedFlag = flag;
+                    }
+                    switch (flag->getFlagType()) {
+                        case SkFlagInfo::kBool_FlagType:
+                            // Can be handled by match, above, but can also be set by the next
+                            // string.
+                            if (i + 1 < argc && !SkStrStartsWith(argv[i + 1], '-')) {
+                                i++;
+                                bool value;
+                                if (parse_bool_arg(argv[i], &value)) {
+                                    flag->setBool(value);
+                                }
+                            }
+                            break;
+                        case SkFlagInfo::kString_FlagType:
+                            flag->resetStrings();
+                            // Add all arguments until another flag is reached.
+                            while (i + 1 < argc) {
+                                char* end = nullptr;
+                                // Negative numbers aren't flags.
+                                ignore_result(strtod(argv[i + 1], &end));
+                                if (end == argv[i + 1] && SkStrStartsWith(argv[i + 1], '-')) {
+                                    break;
+                                }
+                                i++;
+                                flag->append(argv[i]);
+                            }
+                            break;
+                        case SkFlagInfo::kInt_FlagType:
+                            i++;
+                            flag->setInt(atoi(argv[i]));
+                            break;
+                        case SkFlagInfo::kDouble_FlagType:
+                            i++;
+                            flag->setDouble(atof(argv[i]));
+                            break;
+                        default: SkDEBUGFAIL("Invalid flag type");
+                    }
+                }
+                flag = flag->next();
+            }
+            if (!matchedFlag) {
+#if defined(SK_BUILD_FOR_MAC)
+                if (SkStrStartsWith(argv[i], "NSDocumentRevisions") ||
+                    SkStrStartsWith(argv[i], "-NSDocumentRevisions")) {
+                    i++;  // skip YES
+                } else
+#endif
+                    SkDebugf("Got unknown flag '%s'. Exiting.\n", argv[i]);
+                exit(-1);
+            }
+        }
+    }
+    // Since all of the flags have been set, release the memory used by each
+    // flag. FLAGS_x can still be used after this.
+    SkFlagInfo* flag = gHead;
+    gHead            = nullptr;
+    while (flag != nullptr) {
+        SkFlagInfo* next = flag->next();
+        delete flag;
+        flag = next;
+    }
+    if (helpPrinted) {
+        exit(0);
+    }
+}
+
+namespace {
+
+template <typename Strings> bool ShouldSkipImpl(const Strings& strings, const char* name) {
+    int    count      = strings.count();
+    size_t testLen    = strlen(name);
+    bool   anyExclude = count == 0;
+    for (int i = 0; i < strings.count(); ++i) {
+        const char* matchName = strings[i];
+        size_t      matchLen  = strlen(matchName);
+        bool        matchExclude, matchStart, matchEnd;
+        if ((matchExclude = matchName[0] == '~')) {
+            anyExclude = true;
+            matchName++;
+            matchLen--;
+        }
+        if ((matchStart = matchName[0] == '^')) {
+            matchName++;
+            matchLen--;
+        }
+        if ((matchEnd = matchName[matchLen - 1] == '$')) {
+            matchLen--;
+        }
+        if (matchStart
+                    ? (!matchEnd || matchLen == testLen) && strncmp(name, matchName, matchLen) == 0
+                    : matchEnd
+                              ? matchLen <= testLen &&
+                                        strncmp(name + testLen - matchLen, matchName, matchLen) == 0
+                              : strstr(name, matchName) != nullptr) {
+            return matchExclude;
+        }
+    }
+    return !anyExclude;
+}
+
+}  // namespace
+
+bool CommandLineFlags::ShouldSkip(const SkTDArray<const char*>& strings, const char* name) {
+    return ShouldSkipImpl(strings, name);
+}
+bool CommandLineFlags::ShouldSkip(const StringArray& strings, const char* name) {
+    return ShouldSkipImpl(strings, name);
+}
diff --git a/tools/flags/CommandLineFlags.h b/tools/flags/CommandLineFlags.h
new file mode 100644
index 0000000..720fa39
--- /dev/null
+++ b/tools/flags/CommandLineFlags.h
@@ -0,0 +1,475 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SK_COMMAND_LINE_FLAGS_H
+#define SK_COMMAND_LINE_FLAGS_H
+
+#include "../private/SkTArray.h"
+#include "../private/SkTDArray.h"
+#include "SkString.h"
+
+/**
+ *  Including this file (and compiling CommandLineFlags.cpp) provides command line
+ *  parsing. In order to use it, use the following macros in global
+ *  namespace:
+ *
+ *  DEFINE_bool(name, defaultValue, helpString);
+ *  DEFINE_string(name, defaultValue, helpString);
+ *  DEFINE_int(name, defaultValue, helpString);
+ *  DEFINE_double(name, defaultValue, helpString);
+ *
+ *  Then, in main, call CommandLineFlags::SetUsage() to describe usage and call
+ *  CommandLineFlags::Parse() to parse the flags. Henceforth, each flag can
+ *  be referenced using
+ *
+ *  FLAGS_name
+ *
+ *  For example, the line
+ *
+ *  DEFINE_bool(boolean, false, "The variable boolean does such and such");
+ *
+ *  will create the following variable:
+ *
+ *  bool FLAGS_boolean;
+ *
+ *  which will initially be set to false, and can be set to true by using the
+ *  flag "--boolean" on the commandline. "--noboolean" will set FLAGS_boolean
+ *  to false. FLAGS_boolean can also be set using "--boolean=true" or
+ *  "--boolean true" (where "true" can be replaced by "false", "TRUE", "FALSE",
+ *  "1" or "0").
+ *
+ *  The helpString will be printed if the help flag (-h or -help) is used.
+ *
+ *  Similarly, the line
+ *
+ *  DEFINE_int(integer, .., ..);
+ *
+ *  will create
+ *
+ *  int FLAGS_integer;
+ *
+ *  and
+ *
+ *  DEFINE_double(real, .., ..);
+ *
+ *  will create
+ *
+ *  double FLAGS_real;
+ *
+ *  These flags can be set by specifying, for example, "--integer 7" and
+ *  "--real 3.14" on the command line. Unsigned integers are parsed from the
+ *  command line using strtoul() so will detect the base (0 for octal, and
+ *  0x or 0X for hex, otherwise assumes decimal).
+ *
+ *  Unlike the others, the line
+ *
+ *  DEFINE_string(args, .., ..);
+ *
+ *  creates an array:
+ *
+ *  CommandLineFlags::StringArray FLAGS_args;
+ *
+ *  If the default value is the empty string, FLAGS_args will default to a size
+ *  of zero. Otherwise it will default to a size of 1 with the default string
+ *  as its value. All strings that follow the flag on the command line (until
+ *  a string that begins with '-') will be entries in the array.
+ *
+ *  DEFINE_extended_string(args, .., .., extendedHelpString);
+ *
+ *  creates a similar string array flag as DEFINE_string. The flag will have extended help text
+ *  (extendedHelpString) that can the user can see with '--help <args>' flag.
+ *
+ *  Any flag can be referenced from another file after using the following:
+ *
+ *  DECLARE_x(name);
+ *
+ *  (where 'x' is the type specified in the DEFINE).
+ *
+ *  Inspired by gflags (https://code.google.com/p/gflags/). Is not quite as
+ *  robust as gflags, but suits our purposes. For example, allows creating
+ *  a flag -h or -help which will never be used, since CommandLineFlags handles it.
+ *  CommandLineFlags will also allow creating --flag and --noflag. Uses the same input
+ *  format as gflags and creates similarly named variables (i.e. FLAGS_name).
+ *  Strings are handled differently (resulting variable will be an array of
+ *  strings) so that a flag can be followed by multiple parameters.
+ */
+
+class SkFlagInfo;
+
+class CommandLineFlags {
+public:
+    /**
+     *  Call to set the help message to be displayed. Should be called before
+     *  Parse.
+     */
+    static void SetUsage(const char* usage);
+
+    /**
+     *  Call this to display the help message. Should be called after SetUsage.
+     */
+    static void PrintUsage();
+
+    /**
+     *  Call at the beginning of main to parse flags created by DEFINE_x, above.
+     *  Must only be called once.
+     */
+    static void Parse(int argc, const char* const* argv);
+
+    /**
+     *  Custom class for holding the arguments for a string flag.
+     *  Publicly only has accessors so the strings cannot be modified.
+     */
+    class StringArray {
+    public:
+        StringArray() {}
+        explicit StringArray(const SkTArray<SkString>& strings) : fStrings(strings) {}
+        const char* operator[](int i) const {
+            SkASSERT(i >= 0 && i < fStrings.count());
+            return fStrings[i].c_str();
+        }
+
+        int count() const { return fStrings.count(); }
+
+        bool isEmpty() const { return this->count() == 0; }
+
+        /**
+         * Returns true iff string is equal to one of the strings in this array.
+         */
+        bool contains(const char* string) const {
+            for (int i = 0; i < fStrings.count(); i++) {
+                if (fStrings[i].equals(string)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        void set(int i, const char* str) {
+            if (i >= fStrings.count()) {
+                this->append(str);
+                return;
+            }
+            fStrings[i].set(str);
+        }
+
+        const SkString* begin() const { return fStrings.begin(); }
+        const SkString* end() const { return fStrings.end(); }
+
+    private:
+        void reset() { fStrings.reset(); }
+
+        void append(const char* string) { fStrings.push_back().set(string); }
+
+        void append(const char* string, size_t length) { fStrings.push_back().set(string, length); }
+
+        SkTArray<SkString> fStrings;
+
+        friend class SkFlagInfo;
+    };
+
+    /* Takes a list of the form [~][^]match[$]
+     ~ causes a matching test to always be skipped
+     ^ requires the start of the test to match
+     $ requires the end of the test to match
+     ^ and $ requires an exact match
+     If a test does not match any list entry, it is skipped unless some list entry starts with ~
+    */
+    static bool ShouldSkip(const SkTDArray<const char*>& strings, const char* name);
+    static bool ShouldSkip(const StringArray& strings, const char* name);
+
+private:
+    static SkFlagInfo* gHead;
+    static SkString    gUsage;
+
+    // For access to gHead.
+    friend class SkFlagInfo;
+};
+
+#define TO_STRING2(s) #s
+#define TO_STRING(s) TO_STRING2(s)
+
+#define DEFINE_bool(name, defaultValue, helpString)                   \
+    bool                  FLAGS_##name;                               \
+    SK_UNUSED static bool unused_##name = SkFlagInfo::CreateBoolFlag( \
+            TO_STRING(name), nullptr, &FLAGS_##name, defaultValue, helpString)
+
+// bool 2 allows specifying a short name. No check is done to ensure that shortName
+// is actually shorter than name.
+#define DEFINE_bool2(name, shortName, defaultValue, helpString)       \
+    bool                  FLAGS_##name;                               \
+    SK_UNUSED static bool unused_##name = SkFlagInfo::CreateBoolFlag( \
+            TO_STRING(name), TO_STRING(shortName), &FLAGS_##name, defaultValue, helpString)
+
+#define DECLARE_bool(name) extern bool FLAGS_##name;
+
+#define DEFINE_string(name, defaultValue, helpString)                           \
+    CommandLineFlags::StringArray FLAGS_##name;                                 \
+    SK_UNUSED static bool         unused_##name = SkFlagInfo::CreateStringFlag( \
+            TO_STRING(name), nullptr, &FLAGS_##name, defaultValue, helpString, nullptr)
+#define DEFINE_extended_string(name, defaultValue, helpString, extendedHelpString) \
+    CommandLineFlags::StringArray FLAGS_##name;                                    \
+    SK_UNUSED static bool         unused_##name = SkFlagInfo::CreateStringFlag(    \
+            TO_STRING(name), nullptr, &FLAGS_##name, defaultValue, helpString, extendedHelpString)
+
+// string2 allows specifying a short name. There is an assert that shortName
+// is only 1 character.
+#define DEFINE_string2(name, shortName, defaultValue, helpString)                                    \
+    CommandLineFlags::StringArray FLAGS_##name;                                                      \
+    SK_UNUSED static bool         unused_##name = SkFlagInfo::CreateStringFlag(TO_STRING(name),      \
+                                                                       TO_STRING(shortName), \
+                                                                       &FLAGS_##name,        \
+                                                                       defaultValue,         \
+                                                                       helpString,           \
+                                                                       nullptr)
+
+#define DECLARE_string(name) extern CommandLineFlags::StringArray FLAGS_##name;
+
+#define DEFINE_int(name, defaultValue, helpString) \
+    int               FLAGS_##name;                \
+    SK_UNUSED static bool unused_##name =          \
+            SkFlagInfo::CreateIntFlag(TO_STRING(name), &FLAGS_##name, defaultValue, helpString)
+
+#define DEFINE_int_2(name, shortName, defaultValue, helpString)      \
+    int               FLAGS_##name;                                  \
+    SK_UNUSED static bool unused_##name = SkFlagInfo::CreateIntFlag( \
+            TO_STRING(name), TO_STRING(shortName), &FLAGS_##name, defaultValue, helpString)
+
+#define DECLARE_int(name) extern int FLAGS_##name;
+
+#define DEFINE_double(name, defaultValue, helpString) \
+    double                FLAGS_##name;               \
+    SK_UNUSED static bool unused_##name =             \
+            SkFlagInfo::CreateDoubleFlag(TO_STRING(name), &FLAGS_##name, defaultValue, helpString)
+
+#define DECLARE_double(name) extern double FLAGS_##name;
+
+class SkFlagInfo {
+public:
+    enum FlagTypes {
+        kBool_FlagType,
+        kString_FlagType,
+        kInt_FlagType,
+        kDouble_FlagType,
+    };
+
+    /**
+     *  Each Create<Type>Flag function creates an SkFlagInfo of the specified type. The SkFlagInfo
+     *  object is appended to a list, which is deleted when CommandLineFlags::Parse is called.
+     *  Therefore, each call should be made before the call to ::Parse. They are not intended
+     *  to be called directly. Instead, use the macros described above.
+     *  @param name Long version (at least 2 characters) of the name of the flag. This name can
+     *      be referenced on the command line as "--name" to set the value of this flag.
+     *  @param shortName Short version (one character) of the name of the flag. This name can
+     *      be referenced on the command line as "-shortName" to set the value of this flag.
+     *  @param p<Type> Pointer to a global variable which holds the value set by CommandLineFlags.
+     *  @param defaultValue The default value of this flag. The variable pointed to by p<Type> will
+     *      be set to this value initially. This is also displayed as part of the help output.
+     *  @param helpString Explanation of what this flag changes in the program.
+     */
+    static bool CreateBoolFlag(const char* name,
+                               const char* shortName,
+                               bool*       pBool,
+                               bool        defaultValue,
+                               const char* helpString) {
+        SkFlagInfo* info  = new SkFlagInfo(name, shortName, kBool_FlagType, helpString, nullptr);
+        info->fBoolValue  = pBool;
+        *info->fBoolValue = info->fDefaultBool = defaultValue;
+        return true;
+    }
+
+    /**
+     *  See comments for CreateBoolFlag.
+     *  @param pStrings Unlike the others, this is a pointer to an array of values.
+     *  @param defaultValue Thise default will be parsed so that strings separated by spaces
+     *      will be added to pStrings.
+     */
+    static bool CreateStringFlag(const char*                    name,
+                                 const char*                    shortName,
+                                 CommandLineFlags::StringArray* pStrings,
+                                 const char*                    defaultValue,
+                                 const char*                    helpString,
+                                 const char*                    extendedHelpString);
+
+    /**
+     *  See comments for CreateBoolFlag.
+     */
+    static bool CreateIntFlag(const char* name,
+                              int*    pInt,
+                              int     defaultValue,
+                              const char* helpString) {
+        SkFlagInfo* info = new SkFlagInfo(name, nullptr, kInt_FlagType, helpString, nullptr);
+        info->fIntValue  = pInt;
+        *info->fIntValue = info->fDefaultInt = defaultValue;
+        return true;
+    }
+
+    static bool CreateIntFlag(const char* name,
+                              const char* shortName,
+                              int*    pInt,
+                              int     defaultValue,
+                              const char* helpString) {
+        SkFlagInfo* info = new SkFlagInfo(name, shortName, kInt_FlagType, helpString, nullptr);
+        info->fIntValue  = pInt;
+        *info->fIntValue = info->fDefaultInt = defaultValue;
+        return true;
+    }
+
+    /**
+     *  See comments for CreateBoolFlag.
+     */
+    static bool CreateDoubleFlag(const char* name,
+                                 double*     pDouble,
+                                 double      defaultValue,
+                                 const char* helpString) {
+        SkFlagInfo* info    = new SkFlagInfo(name, nullptr, kDouble_FlagType, helpString, nullptr);
+        info->fDoubleValue  = pDouble;
+        *info->fDoubleValue = info->fDefaultDouble = defaultValue;
+        return true;
+    }
+
+    /**
+     *  Returns true if the string matches this flag.
+     *  For a boolean flag, also sets the value, since a boolean flag can be set in a number of ways
+     *  without looking at the following string:
+     *      --name
+     *      --noname
+     *      --name=true
+     *      --name=false
+     *      --name=1
+     *      --name=0
+     *      --name=TRUE
+     *      --name=FALSE
+     */
+    bool match(const char* string);
+
+    FlagTypes getFlagType() const { return fFlagType; }
+
+    void resetStrings() {
+        if (kString_FlagType == fFlagType) {
+            fStrings->reset();
+        } else {
+            SkDEBUGFAIL("Can only call resetStrings on kString_FlagType");
+        }
+    }
+
+    void append(const char* string) {
+        if (kString_FlagType == fFlagType) {
+            fStrings->append(string);
+        } else {
+            SkDEBUGFAIL("Can only append to kString_FlagType");
+        }
+    }
+
+    void setInt(int value) {
+        if (kInt_FlagType == fFlagType) {
+            *fIntValue = value;
+        } else {
+            SkDEBUGFAIL("Can only call setInt on kInt_FlagType");
+        }
+    }
+
+    void setDouble(double value) {
+        if (kDouble_FlagType == fFlagType) {
+            *fDoubleValue = value;
+        } else {
+            SkDEBUGFAIL("Can only call setDouble on kDouble_FlagType");
+        }
+    }
+
+    void setBool(bool value) {
+        if (kBool_FlagType == fFlagType) {
+            *fBoolValue = value;
+        } else {
+            SkDEBUGFAIL("Can only call setBool on kBool_FlagType");
+        }
+    }
+
+    SkFlagInfo* next() { return fNext; }
+
+    const SkString& name() const { return fName; }
+
+    const SkString& shortName() const { return fShortName; }
+
+    const SkString& help() const { return fHelpString; }
+    const SkString& extendedHelp() const { return fExtendedHelpString; }
+
+    SkString defaultValue() const {
+        SkString result;
+        switch (fFlagType) {
+            case SkFlagInfo::kBool_FlagType:
+                result.printf("%s", fDefaultBool ? "true" : "false");
+                break;
+            case SkFlagInfo::kString_FlagType: return fDefaultString;
+            case SkFlagInfo::kInt_FlagType: result.printf("%i", fDefaultInt); break;
+            case SkFlagInfo::kDouble_FlagType: result.printf("%2.2f", fDefaultDouble); break;
+            default: SkDEBUGFAIL("Invalid flag type");
+        }
+        return result;
+    }
+
+    SkString typeAsString() const {
+        switch (fFlagType) {
+            case SkFlagInfo::kBool_FlagType: return SkString("bool");
+            case SkFlagInfo::kString_FlagType: return SkString("string");
+            case SkFlagInfo::kInt_FlagType: return SkString("int");
+            case SkFlagInfo::kDouble_FlagType: return SkString("double");
+            default: SkDEBUGFAIL("Invalid flag type"); return SkString();
+        }
+    }
+
+private:
+    SkFlagInfo(const char* name,
+               const char* shortName,
+               FlagTypes   type,
+               const char* helpString,
+               const char* extendedHelpString)
+            : fName(name)
+            , fShortName(shortName)
+            , fFlagType(type)
+            , fHelpString(helpString)
+            , fExtendedHelpString(extendedHelpString)
+            , fBoolValue(nullptr)
+            , fDefaultBool(false)
+            , fIntValue(nullptr)
+            , fDefaultInt(0)
+            , fDoubleValue(nullptr)
+            , fDefaultDouble(0)
+            , fStrings(nullptr) {
+        fNext                   = CommandLineFlags::gHead;
+        CommandLineFlags::gHead = this;
+        SkASSERT(name && strlen(name) > 1);
+        SkASSERT(nullptr == shortName || 1 == strlen(shortName));
+    }
+
+    /**
+     *  Set a StringArray to hold the values stored in defaultStrings.
+     *  @param array The StringArray to modify.
+     *  @param defaultStrings Space separated list of strings that should be inserted into array
+     *      individually.
+     */
+    static void SetDefaultStrings(CommandLineFlags::StringArray* array, const char* defaultStrings);
+
+    // Name of the flag, without initial dashes
+    SkString                       fName;
+    SkString                       fShortName;
+    FlagTypes                      fFlagType;
+    SkString                       fHelpString;
+    SkString                       fExtendedHelpString;
+    bool*                          fBoolValue;
+    bool                           fDefaultBool;
+    int*                           fIntValue;
+    int                            fDefaultInt;
+    double*                        fDoubleValue;
+    double                         fDefaultDouble;
+    CommandLineFlags::StringArray* fStrings;
+    // Both for the help string and in case fStrings is empty.
+    SkString fDefaultString;
+
+    // In order to keep a linked list.
+    SkFlagInfo* fNext;
+};
+#endif  // SK_COMMAND_LINE_FLAGS_H
diff --git a/tools/flags/CommonFlags.h b/tools/flags/CommonFlags.h
new file mode 100644
index 0000000..df6f218
--- /dev/null
+++ b/tools/flags/CommonFlags.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#pragma once
+
+#include "../private/SkTArray.h"
+#include "CommandLineFlags.h"
+#include "SkString.h"
+
+/**
+ *  Helper to assist in collecting image paths from |dir| specified through a command line
+ * flag.
+ *
+ *  Populates |output|, an array of strings with paths to images to test.
+ *
+ *  Returns true if each argument to the images flag is meaningful:
+ *  - If the file/directory does not exist, return false.
+ *  - If |dir| does not have any supported images (based on file type), return false.
+ *  - If |dir| is a single file, assume the user is deliberately testing this image,
+ *    regardless of file type.
+ */
+bool CollectImages(CommandLineFlags::StringArray dir, SkTArray<SkString>* output);
+
+/**
+ *  Helper to set GrContextOptions from common GPU flags, including
+ *     --gpuThreads
+ *     --cachePathMasks
+ *     --noGS
+ *     --pr
+ *     --disableDriverCorrectnessWorkarounds
+ *     --reduceOpListSplitting
+ */
+void SetCtxOptionsFromCommonFlags(struct GrContextOptions*);
+
+/**
+ *  Enable, disable, or force analytic anti-aliasing using --analyticAA and --forceAnalyticAA.
+ */
+void SetAnalyticAAFromCommonFlags();
diff --git a/tools/flags/CommonFlagsAA.cpp b/tools/flags/CommonFlagsAA.cpp
new file mode 100644
index 0000000..651fecf
--- /dev/null
+++ b/tools/flags/CommonFlagsAA.cpp
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+
+#include "CommonFlags.h"
+#include "SkScan.h"
+
+static DEFINE_bool(analyticAA, true, "If false, disable analytic anti-aliasing");
+static DEFINE_bool(forceAnalyticAA, false,
+            "Force analytic anti-aliasing even if the path is complicated: "
+            "whether it's concave or convex, we consider a path complicated"
+            "if its number of points is comparable to its resolution.");
+
+void SetAnalyticAAFromCommonFlags() {
+    gSkUseAnalyticAA   = FLAGS_analyticAA;
+    gSkForceAnalyticAA = FLAGS_forceAnalyticAA;
+}
diff --git a/tools/flags/CommonFlagsConfig.cpp b/tools/flags/CommonFlagsConfig.cpp
new file mode 100644
index 0000000..ee41b60
--- /dev/null
+++ b/tools/flags/CommonFlagsConfig.cpp
@@ -0,0 +1,601 @@
+/*
+ * 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 "CommonFlagsConfig.h"
+#include "SkColorSpacePriv.h"
+#include "SkImageInfo.h"
+#include "SkTHash.h"
+
+#include <stdlib.h>
+
+using sk_gpu_test::GrContextFactory;
+
+#if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_IOS)
+#define DEFAULT_GPU_CONFIG "gles"
+#else
+#define DEFAULT_GPU_CONFIG "gl"
+#endif
+
+static const char defaultConfigs[] = "8888 " DEFAULT_GPU_CONFIG
+                                     " nonrendering "
+#if defined(SK_BUILD_FOR_WIN)
+                                     " angle_d3d11_es2"
+#endif
+        ;
+
+#undef DEFAULT_GPU_CONFIG
+
+// clang-format off
+static const struct {
+    const char* predefinedConfig;
+    const char* backend;
+    const char* options;
+} gPredefinedConfigs[] = {
+    { "gl",                    "gpu", "api=gl" },
+    { "gles",                  "gpu", "api=gles" },
+    { "glmsaa4",               "gpu", "api=gl,samples=4" },
+    { "glmsaa8" ,              "gpu", "api=gl,samples=8" },
+    { "glesmsaa4",             "gpu", "api=gles,samples=4" },
+    { "glnvpr4",               "gpu", "api=gl,nvpr=true,samples=4" },
+    { "glnvpr8" ,              "gpu", "api=gl,nvpr=true,samples=8" },
+    { "glesnvpr4",             "gpu", "api=gles,nvpr=true,samples=4" },
+    { "glbetex",               "gpu", "api=gl,surf=betex" },
+    { "glesbetex",             "gpu", "api=gles,surf=betex" },
+    { "glbert",                "gpu", "api=gl,surf=bert" },
+    { "glesbert",              "gpu", "api=gles,surf=bert" },
+    { "gl4444",                "gpu", "api=gl,color=4444" },
+    { "gles4444",              "gpu", "api=gles,color=4444" },
+    { "gl565",                 "gpu", "api=gl,color=565" },
+    { "gl888x",                "gpu", "api=gl,color=888x" },
+    { "gles888x",              "gpu", "api=gles,color=888x" },
+    { "gl1010102",             "gpu", "api=gl,color=1010102" },
+    { "gles1010102",           "gpu", "api=gles,color=1010102" },
+    { "glsrgb",                "gpu", "api=gl,color=srgb" },
+    { "glp3",                  "gpu", "api=gl,color=p3" },
+    { "glesrgb",               "gpu", "api=gl,color=esrgb" },
+    { "glnarrow",              "gpu", "api=gl,color=narrow" },
+    { "glenarrow",             "gpu", "api=gl,color=enarrow" },
+    { "glf16",                 "gpu", "api=gl,color=f16" },
+    { "glf16norm",             "gpu", "api=gl,color=f16norm" },
+    { "glessrgb",              "gpu", "api=gles,color=srgb" },
+    { "glesesrgb",             "gpu", "api=gles,color=esrgb" },
+    { "glesnarrow",            "gpu", "api=gles,color=narrow" },
+    { "glesenarrow",           "gpu", "api=gles,color=enarrow" },
+    { "glesf16",               "gpu", "api=gles,color=f16" },
+    { "glnostencils",          "gpu", "api=gl,stencils=false" },
+    { "gldft",                 "gpu", "api=gl,dit=true" },
+    { "glesdft",               "gpu", "api=gles,dit=true" },
+    { "gltestthreading",       "gpu", "api=gl,testThreading=true" },
+    { "gltestpersistentcache", "gpu", "api=gl,testPersistentCache=1" },
+    { "gltestglslcache",       "gpu", "api=gl,testPersistentCache=2" },
+    { "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_d3d11_es2_msaa8", "gpu", "api=angle_d3d11_es2,samples=8" },
+    { "angle_d3d11_es3_msaa4", "gpu", "api=angle_d3d11_es3,samples=4" },
+    { "angle_d3d11_es3_msaa8", "gpu", "api=angle_d3d11_es3,samples=8" },
+    { "angle_gl_es2",          "gpu", "api=angle_gl_es2" },
+    { "angle_gl_es3",          "gpu", "api=angle_gl_es3" },
+    { "angle_gl_es2_msaa8",    "gpu", "api=angle_gl_es2,samples=8" },
+    { "angle_gl_es3_msaa8",    "gpu", "api=angle_gl_es3,samples=8" },
+    { "commandbuffer",         "gpu", "api=commandbuffer" },
+    { "mock",                  "gpu", "api=mock" },
+#ifdef SK_VULKAN
+    { "vk",                    "gpu", "api=vulkan" },
+    { "vknostencils",          "gpu", "api=vulkan,stencils=false" },
+    { "vk1010102",             "gpu", "api=vulkan,color=1010102" },
+    { "vksrgb",                "gpu", "api=vulkan,color=srgb" },
+    { "vkesrgb",               "gpu", "api=vulkan,color=esrgb" },
+    { "vknarrow",              "gpu", "api=vulkan,color=narrow" },
+    { "vkenarrow",             "gpu", "api=vulkan,color=enarrow" },
+    { "vkf16",                 "gpu", "api=vulkan,color=f16" },
+    { "vkmsaa4",               "gpu", "api=vulkan,samples=4" },
+    { "vkmsaa8",               "gpu", "api=vulkan,samples=8" },
+    { "vkbetex",               "gpu", "api=vulkan,surf=betex" },
+    { "vkbert",                "gpu", "api=vulkan,surf=bert" },
+    { "vktestpersistentcache", "gpu", "api=vulkan,testPersistentCache=1" },
+#endif
+#ifdef SK_METAL
+    { "mtl",                   "gpu", "api=metal" },
+    { "mtl1010102",            "gpu", "api=metal,color=1010102" },
+    { "mtlmsaa4",              "gpu", "api=metal,samples=4" },
+    { "mtlmsaa8",              "gpu", "api=metal,samples=8" },
+#endif
+};
+// clang-format on
+
+static const char configHelp[] =
+        "Options: 565 8888 srgb f16 nonrendering null pdf pdfa skp pipe svg xps";
+
+static const char* config_help_fn() {
+    static SkString helpString;
+    helpString.set(configHelp);
+    for (const auto& config : gPredefinedConfigs) {
+        helpString.appendf(" %s", config.predefinedConfig);
+    }
+    helpString.append(" or use extended form 'backend[option=value,...]'.\n");
+    return helpString.c_str();
+}
+
+static const char configExtendedHelp[] =
+        "Extended form: 'backend(option=value,...)'\n\n"
+        "Possible backends and options:\n"
+        "\n"
+        "gpu[api=string,color=string,dit=bool,nvpr=bool,inst=bool,samples=int]\n"
+        "\tapi\ttype: string\trequired\n"
+        "\t    Select graphics API to use with gpu backend.\n"
+        "\t    Options:\n"
+        "\t\tgl    \t\t\tUse OpenGL.\n"
+        "\t\tgles  \t\t\tUse OpenGL ES.\n"
+        "\t\tnullgl \t\t\tUse null OpenGL.\n"
+        "\t\tangle_d3d9_es2\t\tUse OpenGL ES2 on the ANGLE Direct3D9 backend.\n"
+        "\t\tangle_d3d11_es2\t\tUse OpenGL ES2 on the ANGLE Direct3D11 backend.\n"
+        "\t\tangle_d3d11_es3\t\tUse OpenGL ES3 on the ANGLE Direct3D11 backend.\n"
+        "\t\tangle_gl_es2\t\tUse OpenGL ES2 on the ANGLE OpenGL backend.\n"
+        "\t\tangle_gl_es3\t\tUse OpenGL ES3 on the ANGLE OpenGL backend.\n"
+        "\t\tcommandbuffer\t\tUse command buffer.\n"
+        "\t\tmock\t\t\tUse mock context.\n"
+#ifdef SK_VULKAN
+        "\t\tvulkan\t\t\tUse Vulkan.\n"
+#endif
+#ifdef SK_METAL
+        "\t\tmetal\t\t\tUse Metal.\n"
+#endif
+        "\tcolor\ttype: string\tdefault: 8888.\n"
+        "\t    Select framebuffer color format.\n"
+        "\t    Options:\n"
+        "\t\t8888\t\t\tLinear 8888.\n"
+        "\t\t888x\t\t\tLinear 888x.\n"
+        "\t\t4444\t\t\tLinear 4444.\n"
+        "\t\t565\t\t\tLinear 565.\n"
+        "\t\t1010102\t\t\tLinear 1010102.\n"
+        "\t\tsrgb\t\t\tsRGB 8888.\n"
+        "\t\tesrgb\t\t\tsRGB 16-bit floating point.\n"
+        "\t\tnarrow\t\t\tNarrow gamut 8888.\n"
+        "\t\tenarrow\t\t\tNarrow gamut 16-bit floating point.\n"
+        "\t\tf16\t\t\tLinearly blended 16-bit floating point.\n"
+        "\tdit\ttype: bool\tdefault: false.\n"
+        "\t    Use device independent text.\n"
+        "\tnvpr\ttype: bool\tdefault: false.\n"
+        "\t    Use NV_path_rendering OpenGL and OpenGL ES extension.\n"
+        "\tsamples\ttype: int\tdefault: 0.\n"
+        "\t    Use multisampling with N samples.\n"
+        "\tstencils\ttype: bool\tdefault: true.\n"
+        "\t    Allow the use of stencil buffers.\n"
+        "\ttestThreading\ttype: bool\tdefault: false.\n"
+        "\t    Run with and without worker threads, check that results match.\n"
+        "\ttestPersistentCache\ttype: int\tdefault: 0.\n"
+        "\t    1: Run using a pre-warmed binary GrContextOptions::fPersistentCache.\n"
+        "\t    2: Run using a pre-warmed GLSL GrContextOptions::fPersistentCache.\n"
+        "\tsurf\ttype: string\tdefault: default.\n"
+        "\t    Controls the type of backing store for SkSurfaces.\n"
+        "\t    Options:\n"
+        "\t\tdefault\t\t\tA renderable texture created in Skia's resource cache.\n"
+        "\t\tbetex\t\t\tA wrapped backend texture.\n"
+        "\t\tbert\t\t\tA wrapped backend render target\n"
+        "\n"
+        "Predefined configs:\n\n"
+        // Help text for pre-defined configs is auto-generated from gPredefinedConfigs
+        ;
+
+static const char* config_extended_help_fn() {
+    static SkString helpString;
+    helpString.set(configExtendedHelp);
+    for (const auto& config : gPredefinedConfigs) {
+        helpString.appendf("\t%-10s\t= gpu(%s)\n", config.predefinedConfig, config.options);
+    }
+    return helpString.c_str();
+}
+
+DEFINE_extended_string(config, defaultConfigs, config_help_fn(), config_extended_help_fn());
+
+SkCommandLineConfig::SkCommandLineConfig(const SkString&           tag,
+                                         const SkString&           backend,
+                                         const SkTArray<SkString>& viaParts)
+        : fTag(tag), fBackend(backend), fViaParts(viaParts) {}
+SkCommandLineConfig::~SkCommandLineConfig() {}
+
+static bool parse_option_int(const SkString& value, int* outInt) {
+    if (value.isEmpty()) {
+        return false;
+    }
+    char* endptr   = nullptr;
+    long  intValue = strtol(value.c_str(), &endptr, 10);
+    if (*endptr != '\0') {
+        return false;
+    }
+    *outInt = static_cast<int>(intValue);
+    return true;
+}
+static bool parse_option_bool(const SkString& value, bool* outBool) {
+    if (value.equals("true")) {
+        *outBool = true;
+        return true;
+    }
+    if (value.equals("false")) {
+        *outBool = false;
+        return true;
+    }
+    return false;
+}
+static bool parse_option_gpu_api(const SkString&                      value,
+                                 SkCommandLineConfigGpu::ContextType* outContextType) {
+    if (value.equals("gl")) {
+        *outContextType = GrContextFactory::kGL_ContextType;
+        return true;
+    }
+    if (value.equals("gles")) {
+        *outContextType = GrContextFactory::kGLES_ContextType;
+        return true;
+    }
+    if (value.equals("angle_d3d9_es2")) {
+        *outContextType = GrContextFactory::kANGLE_D3D9_ES2_ContextType;
+        return true;
+    }
+    if (value.equals("angle_d3d11_es2")) {
+        *outContextType = GrContextFactory::kANGLE_D3D11_ES2_ContextType;
+        return true;
+    }
+    if (value.equals("angle_d3d11_es3")) {
+        *outContextType = GrContextFactory::kANGLE_D3D11_ES3_ContextType;
+        return true;
+    }
+    if (value.equals("angle_gl_es2")) {
+        *outContextType = GrContextFactory::kANGLE_GL_ES2_ContextType;
+        return true;
+    }
+    if (value.equals("angle_gl_es3")) {
+        *outContextType = GrContextFactory::kANGLE_GL_ES3_ContextType;
+        return true;
+    }
+    if (value.equals("commandbuffer")) {
+        *outContextType = GrContextFactory::kCommandBuffer_ContextType;
+        return true;
+    }
+    if (value.equals("mock")) {
+        *outContextType = GrContextFactory::kMock_ContextType;
+        return true;
+    }
+#ifdef SK_VULKAN
+    if (value.equals("vulkan")) {
+        *outContextType = GrContextFactory::kVulkan_ContextType;
+        return true;
+    }
+#endif
+#ifdef SK_METAL
+    if (value.equals("metal")) {
+        *outContextType = GrContextFactory::kMetal_ContextType;
+        return true;
+    }
+#endif
+    return false;
+}
+
+static bool parse_option_gpu_color(const SkString&      value,
+                                   SkColorType*         outColorType,
+                                   SkAlphaType*         alphaType,
+                                   sk_sp<SkColorSpace>* outColorSpace) {
+    // We always use premul unless the color type is 565.
+    *alphaType = kPremul_SkAlphaType;
+
+    if (value.equals("8888")) {
+        *outColorType  = kRGBA_8888_SkColorType;
+        *outColorSpace = nullptr;
+    } else if (value.equals("888x")) {
+        *outColorType  = kRGB_888x_SkColorType;
+        *outColorSpace = nullptr;
+    } else if (value.equals("8888s")) {
+        *outColorType  = kRGBA_8888_SkColorType;
+        *outColorSpace = SkColorSpace::MakeSRGB();
+    } else if (value.equals("bgra8")) {
+        *outColorType  = kBGRA_8888_SkColorType;
+        *outColorSpace = nullptr;
+    } else if (value.equals("bgra8s")) {
+        *outColorType  = kBGRA_8888_SkColorType;
+        *outColorSpace = SkColorSpace::MakeSRGB();
+    } else if (value.equals("4444")) {
+        *outColorType  = kARGB_4444_SkColorType;
+        *outColorSpace = nullptr;
+    } else if (value.equals("565")) {
+        *outColorType  = kRGB_565_SkColorType;
+        *alphaType     = kOpaque_SkAlphaType;
+        *outColorSpace = nullptr;
+    } else if (value.equals("1010102")) {
+        *outColorType  = kRGBA_1010102_SkColorType;
+        *outColorSpace = nullptr;
+    } else if (value.equals("srgb")) {
+        *outColorType  = kRGBA_8888_SkColorType;
+        *outColorSpace = SkColorSpace::MakeSRGB();
+    } else if (value.equals("p3")) {
+        *outColorType  = kRGBA_8888_SkColorType;
+        *outColorSpace = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
+    } else if (value.equals("esrgb")) {
+        *outColorType  = kRGBA_F16_SkColorType;
+        *outColorSpace = SkColorSpace::MakeSRGB();
+    } else if (value.equals("narrow") || value.equals("enarrow")) {
+        *outColorType  = value.equals("narrow") ? kRGBA_8888_SkColorType : kRGBA_F16_SkColorType;
+        *outColorSpace = SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, gNarrow_toXYZD50);
+    } else if (value.equals("f16")) {
+        *outColorType  = kRGBA_F16_SkColorType;
+        *outColorSpace = SkColorSpace::MakeSRGBLinear();
+    } else if (value.equals("f16norm")) {
+        *outColorType  = kRGBA_F16Norm_SkColorType;
+        *outColorSpace = SkColorSpace::MakeSRGB();
+    } else {
+        return false;
+    }
+    return true;
+}
+
+static bool parse_option_gpu_surf_type(const SkString&                   value,
+                                       SkCommandLineConfigGpu::SurfType* surfType) {
+    if (value.equals("default")) {
+        *surfType = SkCommandLineConfigGpu::SurfType::kDefault;
+        return true;
+    }
+    if (value.equals("betex")) {
+        *surfType = SkCommandLineConfigGpu::SurfType::kBackendTexture;
+        return true;
+    }
+    if (value.equals("bert")) {
+        *surfType = SkCommandLineConfigGpu::SurfType::kBackendRenderTarget;
+        return true;
+    }
+    return false;
+}
+
+// Extended options take form --config item[key1=value1,key2=value2,...]
+// Example: --config gpu[api=gl,color=8888]
+class ExtendedOptions {
+public:
+    ExtendedOptions(const SkString& optionsString, bool* outParseSucceeded) {
+        SkTArray<SkString> optionParts;
+        SkStrSplit(optionsString.c_str(), ",", kStrict_SkStrSplitMode, &optionParts);
+        for (int i = 0; i < optionParts.count(); ++i) {
+            SkTArray<SkString> keyValueParts;
+            SkStrSplit(optionParts[i].c_str(), "=", kStrict_SkStrSplitMode, &keyValueParts);
+            if (keyValueParts.count() != 2) {
+                *outParseSucceeded = false;
+                return;
+            }
+            const SkString& key   = keyValueParts[0];
+            const SkString& value = keyValueParts[1];
+            if (fOptionsMap.find(key) == nullptr) {
+                fOptionsMap.set(key, value);
+            } else {
+                // Duplicate values are not allowed.
+                *outParseSucceeded = false;
+                return;
+            }
+        }
+        *outParseSucceeded = true;
+    }
+
+    bool get_option_gpu_color(const char*          optionKey,
+                              SkColorType*         outColorType,
+                              SkAlphaType*         alphaType,
+                              sk_sp<SkColorSpace>* outColorSpace,
+                              bool                 optional = true) const {
+        SkString* optionValue = fOptionsMap.find(SkString(optionKey));
+        if (optionValue == nullptr) {
+            return optional;
+        }
+        return parse_option_gpu_color(*optionValue, outColorType, alphaType, outColorSpace);
+    }
+
+    bool get_option_gpu_api(const char*                          optionKey,
+                            SkCommandLineConfigGpu::ContextType* outContextType,
+                            bool                                 optional = true) const {
+        SkString* optionValue = fOptionsMap.find(SkString(optionKey));
+        if (optionValue == nullptr) {
+            return optional;
+        }
+        return parse_option_gpu_api(*optionValue, outContextType);
+    }
+
+    bool get_option_gpu_surf_type(const char*                       optionKey,
+                                  SkCommandLineConfigGpu::SurfType* outSurfType,
+                                  bool                              optional = true) const {
+        SkString* optionValue = fOptionsMap.find(SkString(optionKey));
+        if (optionValue == nullptr) {
+            return optional;
+        }
+        return parse_option_gpu_surf_type(*optionValue, outSurfType);
+    }
+
+    bool get_option_int(const char* optionKey, int* outInt, bool optional = true) const {
+        SkString* optionValue = fOptionsMap.find(SkString(optionKey));
+        if (optionValue == nullptr) {
+            return optional;
+        }
+        return parse_option_int(*optionValue, outInt);
+    }
+
+    bool get_option_bool(const char* optionKey, bool* outBool, bool optional = true) const {
+        SkString* optionValue = fOptionsMap.find(SkString(optionKey));
+        if (optionValue == nullptr) {
+            return optional;
+        }
+        return parse_option_bool(*optionValue, outBool);
+    }
+
+private:
+    SkTHashMap<SkString, SkString> fOptionsMap;
+};
+
+SkCommandLineConfigGpu::SkCommandLineConfigGpu(const SkString&           tag,
+                                               const SkTArray<SkString>& viaParts,
+                                               ContextType               contextType,
+                                               bool                      useNVPR,
+                                               bool                      useDIText,
+                                               int                       samples,
+                                               SkColorType               colorType,
+                                               SkAlphaType               alphaType,
+                                               sk_sp<SkColorSpace>       colorSpace,
+                                               bool                      useStencilBuffers,
+                                               bool                      testThreading,
+                                               int                       testPersistentCache,
+                                               SurfType                  surfType)
+        : SkCommandLineConfig(tag, SkString("gpu"), viaParts)
+        , fContextType(contextType)
+        , fContextOverrides(ContextOverrides::kNone)
+        , fUseDIText(useDIText)
+        , fSamples(samples)
+        , fColorType(colorType)
+        , fAlphaType(alphaType)
+        , fColorSpace(std::move(colorSpace))
+        , fTestThreading(testThreading)
+        , fTestPersistentCache(testPersistentCache)
+        , fSurfType(surfType) {
+    if (useNVPR) {
+        fContextOverrides |= ContextOverrides::kRequireNVPRSupport;
+    } else {
+        // We don't disable NVPR for instanced configs. Otherwise the caps wouldn't use mixed
+        // samples and we couldn't test the mixed samples backend for simple shapes.
+        fContextOverrides |= ContextOverrides::kDisableNVPR;
+    }
+    if (!useStencilBuffers) {
+        fContextOverrides |= ContextOverrides::kAvoidStencilBuffers;
+    }
+}
+
+SkCommandLineConfigGpu* parse_command_line_config_gpu(const SkString&           tag,
+                                                      const SkTArray<SkString>& vias,
+                                                      const SkString&           options) {
+    // Defaults for GPU backend.
+    SkCommandLineConfigGpu::ContextType contextType         = GrContextFactory::kGL_ContextType;
+    bool                                useNVPR             = false;
+    bool                                useDIText           = false;
+    int                                 samples             = 1;
+    SkColorType                         colorType           = kRGBA_8888_SkColorType;
+    SkAlphaType                         alphaType           = kPremul_SkAlphaType;
+    sk_sp<SkColorSpace>                 colorSpace          = nullptr;
+    bool                                useStencils         = true;
+    bool                                testThreading       = false;
+    int                                 testPersistentCache = 0;
+    SkCommandLineConfigGpu::SurfType    surfType = SkCommandLineConfigGpu::SurfType::kDefault;
+
+    bool            parseSucceeded = false;
+    ExtendedOptions extendedOptions(options, &parseSucceeded);
+    if (!parseSucceeded) {
+        return nullptr;
+    }
+
+    bool validOptions =
+            extendedOptions.get_option_gpu_api("api", &contextType, false) &&
+            extendedOptions.get_option_bool("nvpr", &useNVPR) &&
+            extendedOptions.get_option_bool("dit", &useDIText) &&
+            extendedOptions.get_option_int("samples", &samples) &&
+            extendedOptions.get_option_gpu_color("color", &colorType, &alphaType, &colorSpace) &&
+            extendedOptions.get_option_bool("stencils", &useStencils) &&
+            extendedOptions.get_option_bool("testThreading", &testThreading) &&
+            extendedOptions.get_option_int("testPersistentCache", &testPersistentCache) &&
+            extendedOptions.get_option_gpu_surf_type("surf", &surfType);
+
+    // testing threading and the persistent cache are mutually exclusive.
+    if (!validOptions || (testThreading && (testPersistentCache != 0))) {
+        return nullptr;
+    }
+
+    return new SkCommandLineConfigGpu(tag,
+                                      vias,
+                                      contextType,
+                                      useNVPR,
+                                      useDIText,
+                                      samples,
+                                      colorType,
+                                      alphaType,
+                                      colorSpace,
+                                      useStencils,
+                                      testThreading,
+                                      testPersistentCache,
+                                      surfType);
+}
+
+SkCommandLineConfigSvg::SkCommandLineConfigSvg(const SkString&           tag,
+                                               const SkTArray<SkString>& viaParts,
+                                               int                       pageIndex)
+        : SkCommandLineConfig(tag, SkString("svg"), viaParts), fPageIndex(pageIndex) {}
+
+SkCommandLineConfigSvg* parse_command_line_config_svg(const SkString&           tag,
+                                                      const SkTArray<SkString>& vias,
+                                                      const SkString&           options) {
+    // Defaults for SVG backend.
+    int pageIndex = 0;
+
+    bool            parseSucceeded = false;
+    ExtendedOptions extendedOptions(options, &parseSucceeded);
+    if (!parseSucceeded) {
+        return nullptr;
+    }
+
+    bool validOptions = extendedOptions.get_option_int("page", &pageIndex);
+
+    if (!validOptions) {
+        return nullptr;
+    }
+
+    return new SkCommandLineConfigSvg(tag, vias, pageIndex);
+}
+
+void ParseConfigs(const CommandLineFlags::StringArray& configs,
+                  SkCommandLineConfigArray*            outResult) {
+    outResult->reset();
+    for (int i = 0; i < configs.count(); ++i) {
+        SkString           extendedBackend;
+        SkString           extendedOptions;
+        SkString           simpleBackend;
+        SkTArray<SkString> vias;
+
+        SkString           tag(configs[i]);
+        SkTArray<SkString> parts;
+        SkStrSplit(tag.c_str(), "[", kStrict_SkStrSplitMode, &parts);
+        if (parts.count() == 2) {
+            SkTArray<SkString> parts2;
+            SkStrSplit(parts[1].c_str(), "]", kStrict_SkStrSplitMode, &parts2);
+            if (parts2.count() == 2 && parts2[1].isEmpty()) {
+                SkStrSplit(parts[0].c_str(), "-", kStrict_SkStrSplitMode, &vias);
+                if (vias.count()) {
+                    extendedBackend = vias[vias.count() - 1];
+                    vias.pop_back();
+                } else {
+                    extendedBackend = parts[0];
+                }
+                extendedOptions = parts2[0];
+                simpleBackend.printf("%s[%s]", extendedBackend.c_str(), extendedOptions.c_str());
+            }
+        }
+
+        if (extendedBackend.isEmpty()) {
+            simpleBackend = tag;
+            SkStrSplit(tag.c_str(), "-", kStrict_SkStrSplitMode, &vias);
+            if (vias.count()) {
+                simpleBackend = vias[vias.count() - 1];
+                vias.pop_back();
+            }
+            for (auto& predefinedConfig : gPredefinedConfigs) {
+                if (simpleBackend.equals(predefinedConfig.predefinedConfig)) {
+                    extendedBackend = predefinedConfig.backend;
+                    extendedOptions = predefinedConfig.options;
+                    break;
+                }
+            }
+        }
+        SkCommandLineConfig* parsedConfig = nullptr;
+        if (extendedBackend.equals("gpu")) {
+            parsedConfig = parse_command_line_config_gpu(tag, vias, extendedOptions);
+        }
+        if (extendedBackend.equals("svg")) {
+            parsedConfig = parse_command_line_config_svg(tag, vias, extendedOptions);
+        }
+        if (!parsedConfig) {
+            parsedConfig = new SkCommandLineConfig(tag, simpleBackend, vias);
+        }
+        outResult->emplace_back(parsedConfig);
+    }
+}
diff --git a/tools/flags/CommonFlagsConfig.h b/tools/flags/CommonFlagsConfig.h
new file mode 100644
index 0000000..63b18ce
--- /dev/null
+++ b/tools/flags/CommonFlagsConfig.h
@@ -0,0 +1,116 @@
+/*
+ * 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 SK_COMMON_FLAGS_CONFIG_H
+#define SK_COMMON_FLAGS_CONFIG_H
+
+#include "CommandLineFlags.h"
+#include "GrContextFactory.h"
+
+DECLARE_string(config);
+
+class SkCommandLineConfigGpu;
+class SkCommandLineConfigSvg;
+
+// SkCommandLineConfig represents a Skia rendering configuration string.
+// The string has following form:
+// tag:
+//   [via-]*backend
+// where 'backend' consists of chars excluding hyphen
+// and each 'via' consists of chars excluding hyphen.
+class SkCommandLineConfig {
+public:
+    SkCommandLineConfig(const SkString&           tag,
+                        const SkString&           backend,
+                        const SkTArray<SkString>& viaParts);
+    virtual ~SkCommandLineConfig();
+    virtual const SkCommandLineConfigGpu* asConfigGpu() const { return nullptr; }
+    virtual const SkCommandLineConfigSvg* asConfigSvg() const { return nullptr; }
+    const SkString&                       getTag() const { return fTag; }
+    const SkString&                       getBackend() const { return fBackend; }
+    const SkTArray<SkString>&             getViaParts() const { return fViaParts; }
+
+private:
+    SkString           fTag;
+    SkString           fBackend;
+    SkTArray<SkString> fViaParts;
+};
+
+// SkCommandLineConfigGpu is a SkCommandLineConfig that extracts information out of the backend
+// part of the tag. It is constructed tags that have:
+// * backends of form "gpu[option=value,option2=value,...]"
+// * backends that represent a shorthand of above (such as "glmsaa16" representing
+// "gpu(api=gl,samples=16)")
+class SkCommandLineConfigGpu : public SkCommandLineConfig {
+public:
+    enum class SurfType { kDefault, kBackendTexture, kBackendRenderTarget };
+    typedef sk_gpu_test::GrContextFactory::ContextType      ContextType;
+    typedef sk_gpu_test::GrContextFactory::ContextOverrides ContextOverrides;
+
+    SkCommandLineConfigGpu(const SkString&           tag,
+                           const SkTArray<SkString>& viaParts,
+                           ContextType               contextType,
+                           bool                      useNVPR,
+                           bool                      useDIText,
+                           int                       samples,
+                           SkColorType               colorType,
+                           SkAlphaType               alphaType,
+                           sk_sp<SkColorSpace>       colorSpace,
+                           bool                      useStencilBuffers,
+                           bool                      testThreading,
+                           int                       testPersistentCache,
+                           SurfType);
+
+    const SkCommandLineConfigGpu* asConfigGpu() const override { return this; }
+    ContextType                   getContextType() const { return fContextType; }
+    ContextOverrides              getContextOverrides() const { return fContextOverrides; }
+    bool                          getUseNVPR() const {
+        SkASSERT(!(fContextOverrides & ContextOverrides::kRequireNVPRSupport) ||
+                 !(fContextOverrides & ContextOverrides::kDisableNVPR));
+        return fContextOverrides & ContextOverrides::kRequireNVPRSupport;
+    }
+    bool          getUseDIText() const { return fUseDIText; }
+    int           getSamples() const { return fSamples; }
+    SkColorType   getColorType() const { return fColorType; }
+    SkAlphaType   getAlphaType() const { return fAlphaType; }
+    SkColorSpace* getColorSpace() const { return fColorSpace.get(); }
+    bool          getTestThreading() const { return fTestThreading; }
+    int           getTestPersistentCache() const { return fTestPersistentCache; }
+    SurfType      getSurfType() const { return fSurfType; }
+
+private:
+    ContextType         fContextType;
+    ContextOverrides    fContextOverrides;
+    bool                fUseDIText;
+    int                 fSamples;
+    SkColorType         fColorType;
+    SkAlphaType         fAlphaType;
+    sk_sp<SkColorSpace> fColorSpace;
+    bool                fTestThreading;
+    int                 fTestPersistentCache;
+    SurfType            fSurfType;
+};
+
+// SkCommandLineConfigSvg is a SkCommandLineConfig that extracts information out of the backend
+// part of the tag. It is constructed tags that have:
+// * backends of form "svg[option=value,option2=value,...]"
+class SkCommandLineConfigSvg : public SkCommandLineConfig {
+public:
+    SkCommandLineConfigSvg(const SkString& tag, const SkTArray<SkString>& viaParts, int pageIndex);
+    const SkCommandLineConfigSvg* asConfigSvg() const override { return this; }
+
+    int getPageIndex() const { return fPageIndex; }
+
+private:
+    int fPageIndex;
+};
+
+typedef SkTArray<std::unique_ptr<SkCommandLineConfig>, true> SkCommandLineConfigArray;
+void ParseConfigs(const CommandLineFlags::StringArray& configList,
+                  SkCommandLineConfigArray*            outResult);
+
+#endif
diff --git a/tools/flags/CommonFlagsGpu.cpp b/tools/flags/CommonFlagsGpu.cpp
new file mode 100644
index 0000000..5a90789
--- /dev/null
+++ b/tools/flags/CommonFlagsGpu.cpp
@@ -0,0 +1,97 @@
+/*
+ * 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 "CommonFlags.h"
+#include "GrContextOptions.h"
+#include "SkExecutor.h"
+
+
+DEFINE_int(gpuThreads,
+             2,
+             "Create this many extra threads to assist with GPU work, "
+             "including software path rendering. Defaults to two.");
+
+static DEFINE_bool(cachePathMasks, true,
+                   "Allows path mask textures to be cached in GPU configs.");
+
+static DEFINE_bool(noGS, false, "Disables support for geometry shaders.");
+
+static DEFINE_bool(cc, false, "Allow coverage counting shortcuts to render paths?");
+
+static DEFINE_string(pr, "",
+              "Set of enabled gpu path renderers. Defined as a list of: "
+              "[~]none [~]dashline [~]nvpr [~]ccpr [~]aahairline [~]aaconvex [~]aalinearizing "
+              "[~]small [~]tess] [~]all");
+
+static DEFINE_bool(disableDriverCorrectnessWorkarounds, false,
+                   "Disables all GPU driver correctness workarounds");
+
+static DEFINE_bool(reduceOpListSplitting, false, "Improve opList sorting");
+
+
+static GpuPathRenderers get_named_pathrenderers_flags(const char* name) {
+    if (!strcmp(name, "none")) {
+        return GpuPathRenderers::kNone;
+    } else if (!strcmp(name, "dashline")) {
+        return GpuPathRenderers::kDashLine;
+    } else if (!strcmp(name, "nvpr")) {
+        return GpuPathRenderers::kStencilAndCover;
+    } else if (!strcmp(name, "ccpr")) {
+        return GpuPathRenderers::kCoverageCounting;
+    } else if (!strcmp(name, "aahairline")) {
+        return GpuPathRenderers::kAAHairline;
+    } else if (!strcmp(name, "aaconvex")) {
+        return GpuPathRenderers::kAAConvex;
+    } else if (!strcmp(name, "aalinearizing")) {
+        return GpuPathRenderers::kAALinearizing;
+    } else if (!strcmp(name, "small")) {
+        return GpuPathRenderers::kSmall;
+    } else if (!strcmp(name, "tess")) {
+        return GpuPathRenderers::kTessellating;
+    } else if (!strcmp(name, "all")) {
+        return GpuPathRenderers::kAll;
+    }
+    SK_ABORT(SkStringPrintf("error: unknown named path renderer \"%s\"\n", name).c_str());
+    return GpuPathRenderers::kNone;
+}
+
+static GpuPathRenderers collect_gpu_path_renderers_from_flags() {
+    if (FLAGS_pr.isEmpty()) {
+        return GpuPathRenderers::kAll;
+    }
+
+    GpuPathRenderers gpuPathRenderers = ('~' == FLAGS_pr[0][0])
+            ? GpuPathRenderers::kAll
+            : GpuPathRenderers::kNone;
+
+    for (int i = 0; i < FLAGS_pr.count(); ++i) {
+        const char* name = FLAGS_pr[i];
+        if (name[0] == '~') {
+            gpuPathRenderers &= ~get_named_pathrenderers_flags(&name[1]);
+        } else {
+            gpuPathRenderers |= get_named_pathrenderers_flags(name);
+        }
+    }
+    return gpuPathRenderers;
+}
+
+void SetCtxOptionsFromCommonFlags(GrContextOptions* ctxOptions) {
+    static std::unique_ptr<SkExecutor> gGpuExecutor = (0 != FLAGS_gpuThreads)
+        ? SkExecutor::MakeFIFOThreadPool(FLAGS_gpuThreads)
+        : nullptr;
+
+    ctxOptions->fExecutor                            = gGpuExecutor.get();
+    ctxOptions->fDisableCoverageCountingPaths        = !FLAGS_cc;
+    ctxOptions->fAllowPathMaskCaching                = FLAGS_cachePathMasks;
+    ctxOptions->fSuppressGeometryShaders             = FLAGS_noGS;
+    ctxOptions->fGpuPathRenderers                    = collect_gpu_path_renderers_from_flags();
+    ctxOptions->fDisableDriverCorrectnessWorkarounds = FLAGS_disableDriverCorrectnessWorkarounds;
+
+    if (FLAGS_reduceOpListSplitting) {
+        ctxOptions->fReduceOpListSplitting = GrContextOptions::Enable::kYes;
+    }
+}
diff --git a/tools/flags/CommonFlagsImages.cpp b/tools/flags/CommonFlagsImages.cpp
new file mode 100644
index 0000000..a89c989
--- /dev/null
+++ b/tools/flags/CommonFlagsImages.cpp
@@ -0,0 +1,94 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+
+#include "CommonFlags.h"
+#include "SkOSFile.h"
+#include "SkOSPath.h"
+
+bool CollectImages(CommandLineFlags::StringArray images, SkTArray<SkString>* output) {
+    SkASSERT(output);
+
+    static const char* const exts[] = {
+        "bmp",
+        "gif",
+        "jpg",
+        "jpeg",
+        "png",
+        "webp",
+        "ktx",
+        "astc",
+        "wbmp",
+        "ico",
+#if !defined(SK_BUILD_FOR_WIN)
+        "BMP",
+        "GIF",
+        "JPG",
+        "JPEG",
+        "PNG",
+        "WEBP",
+        "KTX",
+        "ASTC",
+        "WBMP",
+        "ICO",
+#endif
+#ifdef SK_HAS_HEIF_LIBRARY
+        "heic",
+#if !defined(SK_BUILD_FOR_WIN)
+        "HEIC",
+#endif
+#endif
+#ifdef SK_CODEC_DECODES_RAW
+        "arw",
+        "cr2",
+        "dng",
+        "nef",
+        "nrw",
+        "orf",
+        "raf",
+        "rw2",
+        "pef",
+        "srw",
+#if !defined(SK_BUILD_FOR_WIN)
+        "ARW",
+        "CR2",
+        "DNG",
+        "NEF",
+        "NRW",
+        "ORF",
+        "RAF",
+        "RW2",
+        "PEF",
+        "SRW",
+#endif
+#endif
+    };
+
+    for (int i = 0; i < images.count(); ++i) {
+        const char* flag = images[i];
+        if (!sk_exists(flag)) {
+            SkDebugf("%s does not exist!\n", flag);
+            return false;
+        }
+
+        if (sk_isdir(flag)) {
+            // If the value passed in is a directory, add all the images
+            bool foundAnImage = false;
+            for (const char* ext : exts) {
+                SkOSFile::Iter it(flag, ext);
+                SkString       file;
+                while (it.next(&file)) {
+                    foundAnImage        = true;
+                    output->push_back() = SkOSPath::Join(flag, file.c_str());
+                }
+            }
+            if (!foundAnImage) {
+                SkDebugf("No supported images found in %s!\n", flag);
+                return false;
+            }
+        } else {
+            // Also add the value if it is a single image
+            output->push_back() = flag;
+        }
+    }
+    return true;
+}
diff --git a/tools/flags/SkCommandLineFlags.cpp b/tools/flags/SkCommandLineFlags.cpp
deleted file mode 100644
index 5be6d85..0000000
--- a/tools/flags/SkCommandLineFlags.cpp
+++ /dev/null
@@ -1,410 +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 "SkCommandLineFlags.h"
-#include "SkTDArray.h"
-#include "SkTSort.h"
-
-#include <stdlib.h>
-
-template <typename T> static void ignore_result(const T&) {}
-
-bool SkFlagInfo::CreateStringFlag(const char* name, const char* shortName,
-                                  SkCommandLineFlags::StringArray* pStrings,
-                                  const char* defaultValue, const char* helpString,
-                                  const char* extendedHelpString) {
-    SkFlagInfo* info = new SkFlagInfo(name, shortName, kString_FlagType, helpString,
-                                      extendedHelpString);
-    info->fDefaultString.set(defaultValue);
-
-    info->fStrings = pStrings;
-    SetDefaultStrings(pStrings, defaultValue);
-    return true;
-}
-
-void SkFlagInfo::SetDefaultStrings(SkCommandLineFlags::StringArray* pStrings,
-                                   const char* defaultValue) {
-    pStrings->reset();
-    if (nullptr == defaultValue) {
-        return;
-    }
-    // If default is "", leave the array empty.
-    size_t defaultLength = strlen(defaultValue);
-    if (defaultLength > 0) {
-        const char* const defaultEnd = defaultValue + defaultLength;
-        const char* begin = defaultValue;
-        while (true) {
-            while (begin < defaultEnd && ' ' == *begin) {
-                begin++;
-            }
-            if (begin < defaultEnd) {
-                const char* end = begin + 1;
-                while (end < defaultEnd && ' ' != *end) {
-                    end++;
-                }
-                size_t length = end - begin;
-                pStrings->append(begin, length);
-                begin = end + 1;
-            } else {
-                break;
-            }
-        }
-    }
-}
-
-static bool string_is_in(const char* target, const char* set[], size_t len) {
-    for (size_t i = 0; i < len; i++) {
-        if (0 == strcmp(target, set[i])) {
-            return true;
-        }
-    }
-    return false;
-}
-
-/**
- *  Check to see whether string represents a boolean value.
- *  @param string C style string to parse.
- *  @param result Pointer to a boolean which will be set to the value in the string, if the
- *      string represents a boolean.
- *  @param boolean True if the string represents a boolean, false otherwise.
- */
-static bool parse_bool_arg(const char* string, bool* result) {
-    static const char* trueValues[] = { "1", "TRUE", "true" };
-    if (string_is_in(string, trueValues, SK_ARRAY_COUNT(trueValues))) {
-        *result = true;
-        return true;
-    }
-    static const char* falseValues[] = { "0", "FALSE", "false" };
-    if (string_is_in(string, falseValues, SK_ARRAY_COUNT(falseValues))) {
-        *result = false;
-        return true;
-    }
-    SkDebugf("Parameter \"%s\" not supported.\n", string);
-    return false;
-}
-
-bool SkFlagInfo::match(const char* string) {
-    if (SkStrStartsWith(string, '-') && strlen(string) > 1) {
-        string++;
-        const SkString* compareName;
-        if (SkStrStartsWith(string, '-') && strlen(string) > 1) {
-            string++;
-            // There were two dashes. Compare against full name.
-            compareName = &fName;
-        } else {
-            // One dash. Compare against the short name.
-            compareName = &fShortName;
-        }
-        if (kBool_FlagType == fFlagType) {
-            // In this case, go ahead and set the value.
-            if (compareName->equals(string)) {
-                *fBoolValue = true;
-                return true;
-            }
-            if (SkStrStartsWith(string, "no") && strlen(string) > 2) {
-                string += 2;
-                // Only allow "no" to be prepended to the full name.
-                if (fName.equals(string)) {
-                    *fBoolValue = false;
-                    return true;
-                }
-                return false;
-            }
-            int equalIndex = SkStrFind(string, "=");
-            if (equalIndex > 0) {
-                // The string has an equal sign. Check to see if the string matches.
-                SkString flag(string, equalIndex);
-                if (flag.equals(*compareName)) {
-                    // Check to see if the remainder beyond the equal sign is true or false:
-                    string += equalIndex + 1;
-                    parse_bool_arg(string, fBoolValue);
-                    return true;
-                } else {
-                    return false;
-                }
-            }
-        }
-        return compareName->equals(string);
-    } else {
-        // Has no dash
-        return false;
-    }
-    return false;
-}
-
-SkFlagInfo* SkCommandLineFlags::gHead;
-SkString SkCommandLineFlags::gUsage;
-
-void SkCommandLineFlags::SetUsage(const char* usage) {
-    gUsage.set(usage);
-}
-
-void SkCommandLineFlags::PrintUsage() {
-    SkDebugf("%s", gUsage.c_str());
-}
-
-// Maximum line length for the help message.
-#define LINE_LENGTH 72
-
-static void print_indented(const SkString& text) {
-    size_t length = text.size();
-    const char* currLine = text.c_str();
-    const char* stop = currLine + length;
-    while (currLine < stop) {
-        int lineBreak = SkStrFind(currLine, "\n");
-        if (lineBreak < 0) {
-            lineBreak = static_cast<int>(strlen(currLine));
-        }
-        if (lineBreak > LINE_LENGTH) {
-            // No line break within line length. Will need to insert one.
-            // Find a space before the line break.
-            int spaceIndex = LINE_LENGTH - 1;
-            while (spaceIndex > 0 && currLine[spaceIndex] != ' ') {
-                spaceIndex--;
-            }
-            int gap;
-            if (0 == spaceIndex) {
-                // No spaces on the entire line. Go ahead and break mid word.
-                spaceIndex = LINE_LENGTH;
-                gap = 0;
-            } else {
-                // Skip the space on the next line
-                gap = 1;
-            }
-            SkDebugf("        %.*s\n", spaceIndex, currLine);
-            currLine += spaceIndex + gap;
-        } else {
-            // the line break is within the limit. Break there.
-            lineBreak++;
-            SkDebugf("        %.*s", lineBreak, currLine);
-            currLine += lineBreak;
-        }
-    }
-}
-
-static void print_help_for_flag(const SkFlagInfo* flag) {
-    SkDebugf("    --%s", flag->name().c_str());
-    const SkString& shortName = flag->shortName();
-    if (shortName.size() > 0) {
-        SkDebugf(" or -%s", shortName.c_str());
-    }
-    SkDebugf(":\ttype: %s", flag->typeAsString().c_str());
-    if (flag->defaultValue().size() > 0) {
-        SkDebugf("\tdefault: %s", flag->defaultValue().c_str());
-    }
-    SkDebugf("\n");
-    const SkString& help = flag->help();
-    print_indented(help);
-    SkDebugf("\n");
-}
-static void print_extended_help_for_flag(const SkFlagInfo* flag) {
-    print_help_for_flag(flag);
-    print_indented(flag->extendedHelp());
-    SkDebugf("\n");
-}
-
-namespace {
-struct CompareFlagsByName {
-    bool operator()(SkFlagInfo* a, SkFlagInfo* b) const {
-        return strcmp(a->name().c_str(), b->name().c_str()) < 0;
-    }
-};
-}  // namespace
-
-void SkCommandLineFlags::Parse(int argc, const char* const * argv) {
-    // Only allow calling this function once.
-    static bool gOnce;
-    if (gOnce) {
-        SkDebugf("Parse should only be called once at the beginning of main!\n");
-        SkASSERT(false);
-        return;
-    }
-    gOnce = true;
-
-    bool helpPrinted = false;
-    bool flagsPrinted = false;
-    // Loop over argv, starting with 1, since the first is just the name of the program.
-    for (int i = 1; i < argc; i++) {
-        if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
-            // Print help message.
-            SkTDArray<const char*> helpFlags;
-            for (int j = i + 1; j < argc; j++) {
-                if (SkStrStartsWith(argv[j], '-')) {
-                    break;
-                }
-                helpFlags.append(1, &argv[j]);
-            }
-            if (0 == helpFlags.count()) {
-                // Only print general help message if help for specific flags is not requested.
-                SkDebugf("%s\n%s\n", argv[0], gUsage.c_str());
-            }
-            if (!flagsPrinted) {
-                SkDebugf("Flags:\n");
-                flagsPrinted = true;
-            }
-            if (0 == helpFlags.count()) {
-                // If no flags followed --help, print them all
-                SkTDArray<SkFlagInfo*> allFlags;
-                for (SkFlagInfo* flag = SkCommandLineFlags::gHead; flag;
-                     flag = flag->next()) {
-                    allFlags.push_back(flag);
-                }
-                SkTQSort(&allFlags[0], &allFlags[allFlags.count() - 1],
-                         CompareFlagsByName());
-                for (int i = 0; i < allFlags.count(); ++i) {
-                    print_help_for_flag(allFlags[i]);
-                    if (allFlags[i]->extendedHelp().size() > 0) {
-                        SkDebugf("        Use '--help %s' for more information.\n",
-                                 allFlags[i]->name().c_str());
-                    }
-                }
-            } else {
-                for (SkFlagInfo* flag = SkCommandLineFlags::gHead; flag;
-                     flag = flag->next()) {
-                    for (int k = 0; k < helpFlags.count(); k++) {
-                        if (flag->name().equals(helpFlags[k]) ||
-                            flag->shortName().equals(helpFlags[k])) {
-                            print_extended_help_for_flag(flag);
-                            helpFlags.remove(k);
-                            break;
-                        }
-                    }
-                }
-            }
-            if (helpFlags.count() > 0) {
-                SkDebugf("Requested help for unrecognized flags:\n");
-                for (int k = 0; k < helpFlags.count(); k++) {
-                    SkDebugf("    --%s\n", helpFlags[k]);
-                }
-            }
-            helpPrinted = true;
-        }
-        if (!helpPrinted) {
-            SkFlagInfo* matchedFlag = nullptr;
-            SkFlagInfo* flag = gHead;
-            int startI = i;
-            while (flag != nullptr) {
-                if (flag->match(argv[startI])) {
-                    i = startI;
-                    if (matchedFlag) {
-                        // Don't redefine the same flag with different types.
-                        SkASSERT(matchedFlag->getFlagType() == flag->getFlagType());
-                    } else {
-                        matchedFlag = flag;
-                    }
-                    switch (flag->getFlagType()) {
-                        case SkFlagInfo::kBool_FlagType:
-                            // Can be handled by match, above, but can also be set by the next
-                            // string.
-                            if (i+1 < argc && !SkStrStartsWith(argv[i+1], '-')) {
-                                i++;
-                                bool value;
-                                if (parse_bool_arg(argv[i], &value)) {
-                                    flag->setBool(value);
-                                }
-                            }
-                            break;
-                        case SkFlagInfo::kString_FlagType:
-                            flag->resetStrings();
-                            // Add all arguments until another flag is reached.
-                            while (i+1 < argc) {
-                                char* end = nullptr;
-                                // Negative numbers aren't flags.
-                                ignore_result(strtod(argv[i+1], &end));
-                                if (end == argv[i+1] && SkStrStartsWith(argv[i+1], '-')) {
-                                    break;
-                                }
-                                i++;
-                                flag->append(argv[i]);
-                            }
-                            break;
-                        case SkFlagInfo::kInt_FlagType:
-                            i++;
-                            flag->setInt(atoi(argv[i]));
-                            break;
-                        case SkFlagInfo::kUint_FlagType:
-                            i++;
-                            flag->setUint(strtoul(argv[i], nullptr, 0));
-                            break;
-                        case SkFlagInfo::kDouble_FlagType:
-                            i++;
-                            flag->setDouble(atof(argv[i]));
-                            break;
-                        default:
-                            SkDEBUGFAIL("Invalid flag type");
-                    }
-                }
-                flag = flag->next();
-            }
-            if (!matchedFlag) {
-#if defined(SK_BUILD_FOR_MAC)
-                if (SkStrStartsWith(argv[i], "NSDocumentRevisions")
-                        || SkStrStartsWith(argv[i], "-NSDocumentRevisions")) {
-                    i++;  // skip YES
-                } else
-#endif
-                SkDebugf("Got unknown flag '%s'. Exiting.\n", argv[i]);
-                exit(-1);
-            }
-        }
-    }
-    // Since all of the flags have been set, release the memory used by each
-    // flag. FLAGS_x can still be used after this.
-    SkFlagInfo* flag = gHead;
-    gHead = nullptr;
-    while (flag != nullptr) {
-        SkFlagInfo* next = flag->next();
-        delete flag;
-        flag = next;
-    }
-    if (helpPrinted) {
-        exit(0);
-    }
-}
-
-namespace {
-
-template <typename Strings>
-bool ShouldSkipImpl(const Strings& strings, const char* name) {
-    int count = strings.count();
-    size_t testLen = strlen(name);
-    bool anyExclude = count == 0;
-    for (int i = 0; i < strings.count(); ++i) {
-        const char* matchName = strings[i];
-        size_t matchLen = strlen(matchName);
-        bool matchExclude, matchStart, matchEnd;
-        if ((matchExclude = matchName[0] == '~')) {
-            anyExclude = true;
-            matchName++;
-            matchLen--;
-        }
-        if ((matchStart = matchName[0] == '^')) {
-            matchName++;
-            matchLen--;
-        }
-        if ((matchEnd = matchName[matchLen - 1] == '$')) {
-            matchLen--;
-        }
-        if (matchStart ? (!matchEnd || matchLen == testLen)
-                && strncmp(name, matchName, matchLen) == 0
-                : matchEnd ? matchLen <= testLen
-                && strncmp(name + testLen - matchLen, matchName, matchLen) == 0
-                : strstr(name, matchName) != nullptr) {
-            return matchExclude;
-        }
-    }
-    return !anyExclude;
-}
-
-}  // namespace
-
-bool SkCommandLineFlags::ShouldSkip(const SkTDArray<const char*>& strings, const char* name) {
-    return ShouldSkipImpl(strings, name);
-}
-bool SkCommandLineFlags::ShouldSkip(const StringArray& strings, const char* name) {
-    return ShouldSkipImpl(strings, name);
-}
diff --git a/tools/flags/SkCommandLineFlags.h b/tools/flags/SkCommandLineFlags.h
deleted file mode 100644
index 693a5db..0000000
--- a/tools/flags/SkCommandLineFlags.h
+++ /dev/null
@@ -1,569 +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.
- */
-
-#ifndef SK_COMMAND_LINE_FLAGS_H
-#define SK_COMMAND_LINE_FLAGS_H
-
-#include "../private/SkTArray.h"
-#include "../private/SkTDArray.h"
-#include "SkString.h"
-
-/**
- *  Including this file (and compiling SkCommandLineFlags.cpp) provides command line
- *  parsing. In order to use it, use the following macros in global
- *  namespace:
- *
- *  DEFINE_bool(name, defaultValue, helpString);
- *  DEFINE_string(name, defaultValue, helpString);
- *  DEFINE_int32(name, defaultValue, helpString);
- *  DEFINE_double(name, defaultValue, helpString);
- *
- *  Then, in main, call SkCommandLineFlags::SetUsage() to describe usage and call
- *  SkCommandLineFlags::Parse() to parse the flags. Henceforth, each flag can
- *  be referenced using
- *
- *  FLAGS_name
- *
- *  For example, the line
- *
- *  DEFINE_bool(boolean, false, "The variable boolean does such and such");
- *
- *  will create the following variable:
- *
- *  bool FLAGS_boolean;
- *
- *  which will initially be set to false, and can be set to true by using the
- *  flag "--boolean" on the commandline. "--noboolean" will set FLAGS_boolean
- *  to false. FLAGS_boolean can also be set using "--boolean=true" or
- *  "--boolean true" (where "true" can be replaced by "false", "TRUE", "FALSE",
- *  "1" or "0").
- *
- *  The helpString will be printed if the help flag (-h or -help) is used.
- *
- *  Similarly, the line
- *
- *  DEFINE_int32(integer, .., ..);
- *
- *  will create
- *
- *  int32_t FLAGS_integer;
- *
- *  and
- *
- *  DEFINE_double(real, .., ..);
- *
- *  will create
- *
- *  double FLAGS_real;
- *
- *  and
- *
- *  DEFINE_uint32(unsigned, ...);
- *
- *  will create
- *
- *  uint32_t FLAGS_unsigned;
- *
- *  These flags can be set by specifying, for example, "--integer 7" and
- *  "--real 3.14" on the command line. Unsigned integers are parsed from the
- *  command line using strtoul() so will detect the base (0 for octal, and
- *  0x or 0X for hex, otherwise assumes decimal).
- *
- *  Unlike the others, the line
- *
- *  DEFINE_string(args, .., ..);
- *
- *  creates an array:
- *
- *  SkCommandLineFlags::StringArray FLAGS_args;
- *
- *  If the default value is the empty string, FLAGS_args will default to a size
- *  of zero. Otherwise it will default to a size of 1 with the default string
- *  as its value. All strings that follow the flag on the command line (until
- *  a string that begins with '-') will be entries in the array.
- *
- *  DEFINE_extended_string(args, .., .., extendedHelpString);
- *
- *  creates a similar string array flag as DEFINE_string. The flag will have extended help text
- *  (extendedHelpString) that can the user can see with '--help <args>' flag.
- *
- *  Any flag can be referenced from another file after using the following:
- *
- *  DECLARE_x(name);
- *
- *  (where 'x' is the type specified in the DEFINE).
- *
- *  Inspired by gflags (https://code.google.com/p/gflags/). Is not quite as
- *  robust as gflags, but suits our purposes. For example, allows creating
- *  a flag -h or -help which will never be used, since SkCommandLineFlags handles it.
- *  SkCommandLineFlags will also allow creating --flag and --noflag. Uses the same input
- *  format as gflags and creates similarly named variables (i.e. FLAGS_name).
- *  Strings are handled differently (resulting variable will be an array of
- *  strings) so that a flag can be followed by multiple parameters.
- */
-
-class SkFlagInfo;
-
-class SkCommandLineFlags {
-
-public:
-    /**
-     *  Call to set the help message to be displayed. Should be called before
-     *  Parse.
-     */
-    static void SetUsage(const char* usage);
-
-    /**
-     *  Call this to display the help message. Should be called after SetUsage.
-     */
-    static void PrintUsage();
-
-    /**
-     *  Call at the beginning of main to parse flags created by DEFINE_x, above.
-     *  Must only be called once.
-     */
-    static void Parse(int argc, const char* const * argv);
-
-    /**
-     *  Custom class for holding the arguments for a string flag.
-     *  Publicly only has accessors so the strings cannot be modified.
-     */
-    class StringArray {
-    public:
-        StringArray() { }
-        explicit StringArray(const SkTArray<SkString>& strings)
-            : fStrings(strings) {
-        }
-        const char* operator[](int i) const {
-            SkASSERT(i >= 0 && i < fStrings.count());
-            return fStrings[i].c_str();
-        }
-
-        int count() const {
-            return fStrings.count();
-        }
-
-        bool isEmpty() const { return this->count() == 0; }
-
-        /**
-         * Returns true iff string is equal to one of the strings in this array.
-         */
-        bool contains(const char* string) const {
-            for (int i = 0; i < fStrings.count(); i++) {
-                if (fStrings[i].equals(string)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        void set(int i, const char* str) {
-            if (i >= fStrings.count()) {
-                this->append(str);
-                return;
-            }
-            fStrings[i].set(str);
-        }
-
-        const SkString* begin() const { return fStrings.begin(); }
-        const SkString*   end() const { return fStrings.end(); }
-
-    private:
-        void reset() { fStrings.reset(); }
-
-        void append(const char* string) {
-            fStrings.push_back().set(string);
-        }
-
-        void append(const char* string, size_t length) {
-            fStrings.push_back().set(string, length);
-        }
-
-        SkTArray<SkString> fStrings;
-
-        friend class SkFlagInfo;
-    };
-
-    /* Takes a list of the form [~][^]match[$]
-     ~ causes a matching test to always be skipped
-     ^ requires the start of the test to match
-     $ requires the end of the test to match
-     ^ and $ requires an exact match
-     If a test does not match any list entry, it is skipped unless some list entry starts with ~
-    */
-    static bool ShouldSkip(const SkTDArray<const char*>& strings, const char* name);
-    static bool ShouldSkip(const StringArray& strings, const char* name);
-
-private:
-    static SkFlagInfo* gHead;
-    static SkString    gUsage;
-
-    // For access to gHead.
-    friend class SkFlagInfo;
-};
-
-#define TO_STRING2(s) #s
-#define TO_STRING(s) TO_STRING2(s)
-
-#define DEFINE_bool(name, defaultValue, helpString)                         \
-bool FLAGS_##name;                                                          \
-SK_UNUSED static bool unused_##name = SkFlagInfo::CreateBoolFlag(TO_STRING(name),     \
-                                                                 nullptr,                \
-                                                                 &FLAGS_##name,       \
-                                                                 defaultValue,        \
-                                                                 helpString)
-
-// bool 2 allows specifying a short name. No check is done to ensure that shortName
-// is actually shorter than name.
-#define DEFINE_bool2(name, shortName, defaultValue, helpString)             \
-bool FLAGS_##name;                                                          \
-SK_UNUSED static bool unused_##name = SkFlagInfo::CreateBoolFlag(TO_STRING(name),     \
-                                                                 TO_STRING(shortName),\
-                                                                 &FLAGS_##name,       \
-                                                                 defaultValue,        \
-                                                                 helpString)
-
-#define DECLARE_bool(name) extern bool FLAGS_##name;
-
-#define DEFINE_string(name, defaultValue, helpString)                       \
-SkCommandLineFlags::StringArray FLAGS_##name;                               \
-SK_UNUSED static bool unused_##name = SkFlagInfo::CreateStringFlag(TO_STRING(name),   \
-                                                                   nullptr,              \
-                                                                   &FLAGS_##name,     \
-                                                                   defaultValue,      \
-                                                                   helpString, nullptr)
-#define DEFINE_extended_string(name, defaultValue, helpString, extendedHelpString) \
-SkCommandLineFlags::StringArray FLAGS_##name;                                      \
-SK_UNUSED static bool unused_##name = SkFlagInfo::CreateStringFlag(TO_STRING(name),   \
-                                                                   nullptr, \
-                                                                   &FLAGS_##name, \
-                                                                   defaultValue, \
-                                                                   helpString, \
-                                                                   extendedHelpString)
-
-// string2 allows specifying a short name. There is an assert that shortName
-// is only 1 character.
-#define DEFINE_string2(name, shortName, defaultValue, helpString)               \
-SkCommandLineFlags::StringArray FLAGS_##name;                                   \
-SK_UNUSED static bool unused_##name = SkFlagInfo::CreateStringFlag(TO_STRING(name),       \
-                                                                   TO_STRING(shortName),  \
-                                                                   &FLAGS_##name,         \
-                                                                   defaultValue,          \
-                                                                   helpString, nullptr)
-
-#define DECLARE_string(name) extern SkCommandLineFlags::StringArray FLAGS_##name;
-
-
-
-
-#define DEFINE_int32(name, defaultValue, helpString)                        \
-int32_t FLAGS_##name;                                                       \
-SK_UNUSED static bool unused_##name = SkFlagInfo::CreateIntFlag(TO_STRING(name),      \
-                                                                &FLAGS_##name,        \
-                                                                defaultValue,         \
-                                                                helpString)
-
-#define DEFINE_int32_2(name, shortName, defaultValue, helpString)                     \
-int32_t FLAGS_##name;                                                                 \
-SK_UNUSED static bool unused_##name = SkFlagInfo::CreateIntFlag(TO_STRING(name),      \
-                                                                TO_STRING(shortName), \
-                                                                &FLAGS_##name,        \
-                                                                defaultValue,         \
-                                                                helpString)
-
-#define DECLARE_int32(name) extern int32_t FLAGS_##name;
-
-
-#define DEFINE_uint32(name, defaultValue, helpString)                                 \
-uint32_t FLAGS_##name;                                                                \
-SK_UNUSED static bool unused_##name = SkFlagInfo::CreateUintFlag(TO_STRING(name),     \
-                                                                 &FLAGS_##name,       \
-                                                                 defaultValue,        \
-                                                                 helpString)
-
-#define DEFINE_uint32_2(name, shortName, defaultValue, helpString)                    \
-uint32_t FLAGS_##name;                                                                \
-SK_UNUSED static bool unused_##name = SkFlagInfo::CreateUintFlag(TO_STRING(name),     \
-                                                                 TO_STRING(shortName),\
-                                                                 &FLAGS_##name,       \
-                                                                 defaultValue,        \
-                                                                 helpString)
-
-#define DECLARE_uint32(name) extern uint32_t FLAGS_##name;
-
-
-#define DEFINE_double(name, defaultValue, helpString)                       \
-double FLAGS_##name;                                                        \
-SK_UNUSED static bool unused_##name = SkFlagInfo::CreateDoubleFlag(TO_STRING(name),   \
-                                                                   &FLAGS_##name,     \
-                                                                   defaultValue,      \
-                                                                   helpString)
-
-#define DECLARE_double(name) extern double FLAGS_##name;
-
-class SkFlagInfo {
-
-public:
-    enum FlagTypes {
-        kBool_FlagType,
-        kString_FlagType,
-        kInt_FlagType,
-        kUint_FlagType,
-        kDouble_FlagType,
-    };
-
-    /**
-     *  Each Create<Type>Flag function creates an SkFlagInfo of the specified type. The SkFlagInfo
-     *  object is appended to a list, which is deleted when SkCommandLineFlags::Parse is called.
-     *  Therefore, each call should be made before the call to ::Parse. They are not intended
-     *  to be called directly. Instead, use the macros described above.
-     *  @param name Long version (at least 2 characters) of the name of the flag. This name can
-     *      be referenced on the command line as "--name" to set the value of this flag.
-     *  @param shortName Short version (one character) of the name of the flag. This name can
-     *      be referenced on the command line as "-shortName" to set the value of this flag.
-     *  @param p<Type> Pointer to a global variable which holds the value set by SkCommandLineFlags.
-     *  @param defaultValue The default value of this flag. The variable pointed to by p<Type> will
-     *      be set to this value initially. This is also displayed as part of the help output.
-     *  @param helpString Explanation of what this flag changes in the program.
-     */
-    static bool CreateBoolFlag(const char* name, const char* shortName, bool* pBool,
-                               bool defaultValue, const char* helpString) {
-        SkFlagInfo* info = new SkFlagInfo(name, shortName, kBool_FlagType, helpString, nullptr);
-        info->fBoolValue = pBool;
-        *info->fBoolValue = info->fDefaultBool = defaultValue;
-        return true;
-    }
-
-    /**
-     *  See comments for CreateBoolFlag.
-     *  @param pStrings Unlike the others, this is a pointer to an array of values.
-     *  @param defaultValue Thise default will be parsed so that strings separated by spaces
-     *      will be added to pStrings.
-     */
-    static bool CreateStringFlag(const char* name, const char* shortName,
-                                 SkCommandLineFlags::StringArray* pStrings,
-                                 const char* defaultValue, const char* helpString,
-                                 const char* extendedHelpString);
-
-    /**
-     *  See comments for CreateBoolFlag.
-     */
-    static bool CreateIntFlag(const char* name, int32_t* pInt,
-                              int32_t defaultValue, const char* helpString) {
-        SkFlagInfo* info = new SkFlagInfo(name, nullptr, kInt_FlagType, helpString, nullptr);
-        info->fIntValue = pInt;
-        *info->fIntValue = info->fDefaultInt = defaultValue;
-        return true;
-    }
-
-    static bool CreateIntFlag(const char* name, const char* shortName, int32_t* pInt,
-                              int32_t defaultValue, const char* helpString) {
-        SkFlagInfo* info = new SkFlagInfo(name, shortName, kInt_FlagType, helpString, nullptr);
-        info->fIntValue = pInt;
-        *info->fIntValue = info->fDefaultInt = defaultValue;
-        return true;
-    }
-
-    /**
-     *  See comments for CreateBoolFlag.
-     */
-    static bool CreateUintFlag(const char* name, uint32_t* pUint,
-                               uint32_t defaultValue, const char* helpString) {
-        SkFlagInfo* info = new SkFlagInfo(name, nullptr, kUint_FlagType, helpString, nullptr);
-        info->fUintValue = pUint;
-        *info->fUintValue = info->fDefaultUint = defaultValue;
-        return true;
-    }
-
-    static bool CreateUintFlag(const char* name, const char* shortName, uint32_t* pUint,
-                               uint32_t defaultValue, const char* helpString) {
-        SkFlagInfo* info = new SkFlagInfo(name, shortName, kUint_FlagType, helpString, nullptr);
-        info->fUintValue = pUint;
-        *info->fUintValue = info->fDefaultUint = defaultValue;
-        return true;
-    }
-
-    /**
-     *  See comments for CreateBoolFlag.
-     */
-    static bool CreateDoubleFlag(const char* name, double* pDouble,
-                                 double defaultValue, const char* helpString) {
-        SkFlagInfo* info = new SkFlagInfo(name, nullptr, kDouble_FlagType, helpString, nullptr);
-        info->fDoubleValue = pDouble;
-        *info->fDoubleValue = info->fDefaultDouble = defaultValue;
-        return true;
-    }
-
-    /**
-     *  Returns true if the string matches this flag.
-     *  For a boolean flag, also sets the value, since a boolean flag can be set in a number of ways
-     *  without looking at the following string:
-     *      --name
-     *      --noname
-     *      --name=true
-     *      --name=false
-     *      --name=1
-     *      --name=0
-     *      --name=TRUE
-     *      --name=FALSE
-     */
-    bool match(const char* string);
-
-    FlagTypes getFlagType() const { return fFlagType; }
-
-    void resetStrings() {
-        if (kString_FlagType == fFlagType) {
-            fStrings->reset();
-        } else {
-            SkDEBUGFAIL("Can only call resetStrings on kString_FlagType");
-        }
-    }
-
-    void append(const char* string) {
-        if (kString_FlagType == fFlagType) {
-            fStrings->append(string);
-        } else {
-            SkDEBUGFAIL("Can only append to kString_FlagType");
-        }
-    }
-
-    void setInt(int32_t value) {
-        if (kInt_FlagType == fFlagType) {
-            *fIntValue = value;
-        } else {
-            SkDEBUGFAIL("Can only call setInt on kInt_FlagType");
-        }
-    }
-
-    void setUint(uint32_t value) {
-        if (kUint_FlagType == fFlagType) {
-            *fUintValue = value;
-        } else {
-            SkDEBUGFAIL("Can only call setUint on kUint_FlagType");
-        }
-    }
-
-    void setDouble(double value) {
-        if (kDouble_FlagType == fFlagType) {
-            *fDoubleValue = value;
-        } else {
-            SkDEBUGFAIL("Can only call setDouble on kDouble_FlagType");
-        }
-    }
-
-    void setBool(bool value) {
-        if (kBool_FlagType == fFlagType) {
-            *fBoolValue = value;
-        } else {
-            SkDEBUGFAIL("Can only call setBool on kBool_FlagType");
-        }
-    }
-
-    SkFlagInfo* next() { return fNext; }
-
-    const SkString& name() const { return fName; }
-
-    const SkString& shortName() const { return fShortName; }
-
-    const SkString& help() const { return fHelpString; }
-    const SkString& extendedHelp() const { return fExtendedHelpString; }
-
-    SkString defaultValue() const {
-        SkString result;
-        switch (fFlagType) {
-            case SkFlagInfo::kBool_FlagType:
-                result.printf("%s", fDefaultBool ? "true" : "false");
-                break;
-            case SkFlagInfo::kString_FlagType:
-                return fDefaultString;
-            case SkFlagInfo::kInt_FlagType:
-                result.printf("%i", fDefaultInt);
-                break;
-            case SkFlagInfo::kUint_FlagType:
-                result.printf("0x%08x", fDefaultUint);
-                break;
-            case SkFlagInfo::kDouble_FlagType:
-                result.printf("%2.2f", fDefaultDouble);
-                break;
-            default:
-                SkDEBUGFAIL("Invalid flag type");
-        }
-        return result;
-    }
-
-    SkString typeAsString() const {
-        switch (fFlagType) {
-            case SkFlagInfo::kBool_FlagType:
-                return SkString("bool");
-            case SkFlagInfo::kString_FlagType:
-                return SkString("string");
-            case SkFlagInfo::kInt_FlagType:
-                return SkString("int");
-            case SkFlagInfo::kUint_FlagType:
-                return SkString("uint");
-            case SkFlagInfo::kDouble_FlagType:
-                return SkString("double");
-            default:
-                SkDEBUGFAIL("Invalid flag type");
-                return SkString();
-        }
-    }
-
-private:
-    SkFlagInfo(const char* name, const char* shortName, FlagTypes type, const char* helpString,
-               const char* extendedHelpString)
-        : fName(name)
-        , fShortName(shortName)
-        , fFlagType(type)
-        , fHelpString(helpString)
-        , fExtendedHelpString(extendedHelpString)
-        , fBoolValue(nullptr)
-        , fDefaultBool(false)
-        , fIntValue(nullptr)
-        , fDefaultInt(0)
-        , fUintValue(nullptr)
-        , fDefaultUint(0)
-        , fDoubleValue(nullptr)
-        , fDefaultDouble(0)
-        , fStrings(nullptr) {
-        fNext = SkCommandLineFlags::gHead;
-        SkCommandLineFlags::gHead = this;
-        SkASSERT(name && strlen(name) > 1);
-        SkASSERT(nullptr == shortName || 1 == strlen(shortName));
-    }
-
-    /**
-     *  Set a StringArray to hold the values stored in defaultStrings.
-     *  @param array The StringArray to modify.
-     *  @param defaultStrings Space separated list of strings that should be inserted into array
-     *      individually.
-     */
-    static void SetDefaultStrings(SkCommandLineFlags::StringArray* array,
-                                  const char* defaultStrings);
-
-    // Name of the flag, without initial dashes
-    SkString             fName;
-    SkString             fShortName;
-    FlagTypes            fFlagType;
-    SkString             fHelpString;
-    SkString             fExtendedHelpString;
-    bool*                fBoolValue;
-    bool                 fDefaultBool;
-    int32_t*             fIntValue;
-    int32_t              fDefaultInt;
-    uint32_t*            fUintValue;
-    uint32_t             fDefaultUint;
-    double*              fDoubleValue;
-    double               fDefaultDouble;
-    SkCommandLineFlags::StringArray* fStrings;
-    // Both for the help string and in case fStrings is empty.
-    SkString             fDefaultString;
-
-    // In order to keep a linked list.
-    SkFlagInfo*          fNext;
-};
-#endif // SK_COMMAND_LINE_FLAGS_H
diff --git a/tools/flags/SkCommonFlags.cpp b/tools/flags/SkCommonFlags.cpp
deleted file mode 100644
index 1e7b962..0000000
--- a/tools/flags/SkCommonFlags.cpp
+++ /dev/null
@@ -1,190 +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 "GrContextOptions.h"
-#include "SkCommonFlags.h"
-#include "SkExecutor.h"
-#include "SkOnce.h"
-#include "SkOSFile.h"
-#include "SkOSPath.h"
-
-DEFINE_bool(cpu, true, "master switch for running CPU-bound work.");
-
-DEFINE_bool(dryRun, false,
-            "just print the tests that would be run, without actually running them.");
-
-DEFINE_bool(gpu, true, "master switch for running GPU-bound work.");
-
-DEFINE_string(images, "", "List of images and/or directories to decode. A directory with no images"
-                          " is treated as a fatal error.");
-
-DEFINE_string(colorImages, "", "List of images and/or directories to decode with color correction. "
-                               "A directory with no images is treated as a fatal error.");
-
-DEFINE_bool(simpleCodec, false, "Runs of a subset of the codec tests.  "
-                                "For DM, this means no scaling or subsetting, always using the "
-                                "canvas color type.  "
-                                "For nanobench, this means always N32, Premul or Opaque.");
-
-DEFINE_string2(match, m, nullptr,
-               "[~][^]substring[$] [...] of name to run.\n"
-               "Multiple matches may be separated by spaces.\n"
-               "~ causes a matching name to always be skipped\n"
-               "^ requires the start of the name to match\n"
-               "$ requires the end of the name to match\n"
-               "^ and $ requires an exact match\n"
-               "If a name does not match any list entry,\n"
-               "it is skipped unless some list entry starts with ~");
-
-DEFINE_bool2(quiet, q, false, "if true, don't print status updates.");
-
-DEFINE_bool(preAbandonGpuContext, false, "Test abandoning the GrContext before running the test.");
-
-DEFINE_bool(abandonGpuContext, false, "Test abandoning the GrContext after running each test.");
-
-DEFINE_bool(releaseAndAbandonGpuContext, false,
-            "Test releasing all gpu resources and abandoning the GrContext after running each "
-            "test");
-
-DEFINE_bool(disableDriverCorrectnessWorkarounds, false, "Disables all GPU driver correctness "
-            "workarounds");
-
-#ifdef SK_BUILD_FOR_ANDROID
-DEFINE_string(skps, "/data/local/tmp/skps", "Directory to read skps from.");
-DEFINE_string(jpgs, "/data/local/tmp/resources", "Directory to read jpgs from.");
-DEFINE_string(lotties, "/data/local/tmp/lotties", "Directory to read (Bodymovin) jsons from.");
-DEFINE_string(nimas, "/data/local/tmp/nimas", "Directory to read NIMA animations from.");
-#else
-DEFINE_string(skps, "skps", "Directory to read skps from.");
-DEFINE_string(jpgs, "jpgs", "Directory to read jpgs from.");
-DEFINE_string(lotties, "lotties", "Directory to read (Bodymovin) jsons from.");
-DEFINE_string(nimas, "nimas", "Directory to read NIMA animations from.");
-#endif
-
-DEFINE_int32(skpViewportSize, 1000, "Width & height of the viewport used to crop skp rendering.");
-
-DEFINE_bool(nativeFonts, true, "If true, use native font manager and rendering. "
-                               "If false, fonts will draw as portably as possible.");
-
-DEFINE_string(svgs, "", "Directory to read SVGs from, or a single SVG file.");
-
-DEFINE_int32_2(threads, j, -1, "Run threadsafe tests on a threadpool with this many extra threads, "
-                               "defaulting to one extra thread per core.");
-
-DEFINE_bool2(verbose, v, false, "enable verbose output from the test driver.");
-
-DEFINE_bool2(veryVerbose, V, false, "tell individual tests to be verbose.");
-
-DEFINE_string2(writePath, w, "", "If set, write bitmaps here as .pngs.");
-
-DEFINE_string(key, "",
-              "Space-separated key/value pairs to add to JSON identifying this builder.");
-DEFINE_string(properties, "",
-              "Space-separated key/value pairs to add to JSON identifying this run.");
-DEFINE_bool2(pre_log, p, false, "Log before running each test. May be incomprehensible when threading");
-
-DEFINE_bool(analyticAA, true, "If false, disable analytic anti-aliasing");
-
-DEFINE_bool(forceAnalyticAA, false, "Force analytic anti-aliasing even if the path is complicated: "
-                                    "whether it's concave or convex, we consider a path complicated"
-                                    "if its number of points is comparable to its resolution.");
-
-DEFINE_bool(deltaAA, false,
-            "If true, use delta anti-aliasing in suitable cases (it overrides forceAnalyticAA.");
-DEFINE_bool(forceDeltaAA, false, "Force delta anti-aliasing for all paths.");
-
-DEFINE_int32(backendTiles, 3, "Number of tiles in the experimental threaded backend.");
-DEFINE_int32(backendThreads, 2, "Number of threads in the experimental threaded backend.");
-
-bool CollectImages(SkCommandLineFlags::StringArray images, SkTArray<SkString>* output) {
-    SkASSERT(output);
-
-    static const char* const exts[] = {
-        "bmp", "gif", "jpg", "jpeg", "png", "webp", "ktx", "astc", "wbmp", "ico",
-#if !defined(SK_BUILD_FOR_WIN)
-        "BMP", "GIF", "JPG", "JPEG", "PNG", "WEBP", "KTX", "ASTC", "WBMP", "ICO",
-#endif
-#ifdef SK_HAS_HEIF_LIBRARY
-        "heic",
-#if !defined(SK_BUILD_FOR_WIN)
-        "HEIC",
-#endif
-#endif
-#ifdef SK_CODEC_DECODES_RAW
-        "arw", "cr2", "dng", "nef", "nrw", "orf", "raf", "rw2", "pef", "srw",
-#if !defined(SK_BUILD_FOR_WIN)
-        "ARW", "CR2", "DNG", "NEF", "NRW", "ORF", "RAF", "RW2", "PEF", "SRW",
-#endif
-#endif
-    };
-
-    for (int i = 0; i < images.count(); ++i) {
-        const char* flag = images[i];
-        if (!sk_exists(flag)) {
-            SkDebugf("%s does not exist!\n", flag);
-            return false;
-        }
-
-        if (sk_isdir(flag)) {
-            // If the value passed in is a directory, add all the images
-            bool foundAnImage = false;
-            for (const char* ext : exts) {
-                SkOSFile::Iter it(flag, ext);
-                SkString file;
-                while (it.next(&file)) {
-                    foundAnImage = true;
-                    output->push_back() = SkOSPath::Join(flag, file.c_str());
-                }
-            }
-            if (!foundAnImage) {
-                SkDebugf("No supported images found in %s!\n", flag);
-                return false;
-            }
-        } else {
-            // Also add the value if it is a single image
-            output->push_back() = flag;
-        }
-    }
-    return true;
-}
-
-#include "SkCommonFlagsGpu.h"
-
-DEFINE_int32(gpuThreads, 2, "Create this many extra threads to assist with GPU work, "
-                            "including software path rendering. Defaults to two.");
-
-DEFINE_bool(cachePathMasks, true, "Allows path mask textures to be cached in GPU configs.");
-
-DEFINE_bool(noGS, false, "Disables support for geometry shaders.");
-
-DEFINE_string(pr, "all",
-              "Set of enabled gpu path renderers. Defined as a list of: "
-              "[~]none [~]dashline [~]nvpr [~]ccpr [~]aahairline [~]aaconvex [~]aalinearizing "
-              "[~]small [~]tess] [~]all");
-
-DEFINE_bool(disableExplicitAlloc, false, "Disable explicit allocation of GPU resources");
-DEFINE_bool(reduceOpListSplitting, false, "Improve opList sorting");
-
-void SetCtxOptionsFromCommonFlags(GrContextOptions* ctxOptions) {
-    static std::unique_ptr<SkExecutor> gGpuExecutor = (0 != FLAGS_gpuThreads)
-        ? SkExecutor::MakeFIFOThreadPool(FLAGS_gpuThreads) : nullptr;
-    ctxOptions->fExecutor = gGpuExecutor.get();
-    ctxOptions->fAllowPathMaskCaching = FLAGS_cachePathMasks;
-    ctxOptions->fSuppressGeometryShaders = FLAGS_noGS;
-    ctxOptions->fGpuPathRenderers = CollectGpuPathRenderersFromFlags();
-    ctxOptions->fDisableDriverCorrectnessWorkarounds = FLAGS_disableDriverCorrectnessWorkarounds;
-
-    if (FLAGS_disableExplicitAlloc) {
-        ctxOptions->fExplicitlyAllocateGPUResources = GrContextOptions::Enable::kNo;
-        // Can't have sorting enabled when explicit allocation is disabled.
-        ctxOptions->fSortRenderTargets = GrContextOptions::Enable::kNo;
-    }
-
-    if (FLAGS_reduceOpListSplitting) {
-        ctxOptions->fReduceOpListSplitting = GrContextOptions::Enable::kYes;
-    }
-}
diff --git a/tools/flags/SkCommonFlags.h b/tools/flags/SkCommonFlags.h
deleted file mode 100644
index 1bd4517..0000000
--- a/tools/flags/SkCommonFlags.h
+++ /dev/null
@@ -1,61 +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.
- */
-
-#ifndef SK_COMMON_FLAGS_H
-#define SK_COMMON_FLAGS_H
-
-#include "../private/SkTArray.h"
-#include "SkCommandLineFlags.h"
-#include "SkString.h"
-
-DECLARE_bool(cpu);
-DECLARE_bool(dryRun);
-DECLARE_bool(gpu);
-DECLARE_string(images);
-DECLARE_string(colorImages);
-DECLARE_bool(simpleCodec);
-DECLARE_string(match);
-DECLARE_bool(quiet);
-DECLARE_bool(preAbandonGpuContext);
-DECLARE_bool(abandonGpuContext);
-DECLARE_bool(releaseAndAbandonGpuContext);
-DECLARE_string(skps);
-DECLARE_int32(skpViewportSize);
-DECLARE_string(jpgs);
-DECLARE_string(lotties);
-DECLARE_string(svgs);
-DECLARE_string(nimas);
-DECLARE_bool(nativeFonts);
-DECLARE_int32(threads);
-DECLARE_string(resourcePath);
-DECLARE_bool(verbose);
-DECLARE_bool(veryVerbose);
-DECLARE_string(writePath);
-DECLARE_bool(pre_log);
-DECLARE_bool(analyticAA);
-DECLARE_bool(forceAnalyticAA);
-DECLARE_bool(deltaAA);
-DECLARE_bool(forceDeltaAA);
-DECLARE_string(key);
-DECLARE_string(properties);
-DECLARE_int32(backendTiles);
-DECLARE_int32(backendThreads)
-
-/**
- *  Helper to assist in collecting image paths from |dir| specified through a command line flag.
- *
- *  Populates |output|, an array of strings with paths to images to test.
- *
- *  Returns true if each argument to the images flag is meaningful:
- *  - If the file/directory does not exist, return false.
- *  - If |dir| does not have any supported images (based on file type), return false.
- *  - If |dir| is a single file, assume the user is deliberately testing this image,
- *    regardless of file type.
- */
-bool CollectImages(SkCommandLineFlags::StringArray dir, SkTArray<SkString>* output);
-
-#endif
diff --git a/tools/flags/SkCommonFlagsConfig.cpp b/tools/flags/SkCommonFlagsConfig.cpp
deleted file mode 100644
index 44e79ac..0000000
--- a/tools/flags/SkCommonFlagsConfig.cpp
+++ /dev/null
@@ -1,575 +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 "SkColorSpacePriv.h"
-#include "SkCommonFlagsConfig.h"
-#include "SkImageInfo.h"
-#include "SkTHash.h"
-
-#include <stdlib.h>
-
-using sk_gpu_test::GrContextFactory;
-
-#if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_IOS)
-#    define DEFAULT_GPU_CONFIG "gles"
-#else
-#    define DEFAULT_GPU_CONFIG "gl"
-#endif
-
-static const char defaultConfigs[] =
-    "8888 " DEFAULT_GPU_CONFIG " nonrendering "
-#if defined(SK_BUILD_FOR_WIN)
-    " angle_d3d11_es2"
-#endif
-    ;
-
-#undef DEFAULT_GPU_CONFIG
-
-// clang-format off
-static const struct {
-    const char* predefinedConfig;
-    const char* backend;
-    const char* options;
-} gPredefinedConfigs[] = {
-    { "gl",                    "gpu", "api=gl" },
-    { "gles",                  "gpu", "api=gles" },
-    { "glmsaa4",               "gpu", "api=gl,samples=4" },
-    { "glmsaa8" ,              "gpu", "api=gl,samples=8" },
-    { "glesmsaa4",             "gpu", "api=gles,samples=4" },
-    { "glnvpr4",               "gpu", "api=gl,nvpr=true,samples=4" },
-    { "glnvpr8" ,              "gpu", "api=gl,nvpr=true,samples=8" },
-    { "glesnvpr4",             "gpu", "api=gles,nvpr=true,samples=4" },
-    { "glbetex",               "gpu", "api=gl,surf=betex" },
-    { "glesbetex",             "gpu", "api=gles,surf=betex" },
-    { "glbert",                "gpu", "api=gl,surf=bert" },
-    { "glesbert",              "gpu", "api=gles,surf=bert" },
-    { "gl4444",                "gpu", "api=gl,color=4444" },
-    { "gles4444",              "gpu", "api=gles,color=4444" },
-    { "gl565",                 "gpu", "api=gl,color=565" },
-    { "gl888x",                "gpu", "api=gl,color=888x" },
-    { "gles888x",              "gpu", "api=gles,color=888x" },
-    { "gl1010102",             "gpu", "api=gl,color=1010102" },
-    { "gles1010102",           "gpu", "api=gles,color=1010102" },
-    { "glsrgb",                "gpu", "api=gl,color=srgb" },
-    { "glp3",                  "gpu", "api=gl,color=p3" },
-    { "glesrgb",               "gpu", "api=gl,color=esrgb" },
-    { "glnarrow",              "gpu", "api=gl,color=narrow" },
-    { "glenarrow",             "gpu", "api=gl,color=enarrow" },
-    { "glf16",                 "gpu", "api=gl,color=f16" },
-    { "glessrgb",              "gpu", "api=gles,color=srgb" },
-    { "glesesrgb",             "gpu", "api=gles,color=esrgb" },
-    { "glesnarrow",            "gpu", "api=gles,color=narrow" },
-    { "glesenarrow",           "gpu", "api=gles,color=enarrow" },
-    { "glesf16",               "gpu", "api=gles,color=f16" },
-    { "glnostencils",          "gpu", "api=gl,stencils=false" },
-    { "gldft",                 "gpu", "api=gl,dit=true" },
-    { "glesdft",               "gpu", "api=gles,dit=true" },
-    { "gltestthreading",       "gpu", "api=gl,testThreading=true" },
-    { "gltestpersistentcache", "gpu", "api=gl,testPersistentCache=true" },
-    { "nullgl",                "gpu", "api=nullgl" },
-    { "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_d3d11_es2_msaa8", "gpu", "api=angle_d3d11_es2,samples=8" },
-    { "angle_d3d11_es3_msaa4", "gpu", "api=angle_d3d11_es3,samples=4" },
-    { "angle_d3d11_es3_msaa8", "gpu", "api=angle_d3d11_es3,samples=8" },
-    { "angle_gl_es2",          "gpu", "api=angle_gl_es2" },
-    { "angle_gl_es3",          "gpu", "api=angle_gl_es3" },
-    { "angle_gl_es2_msaa8",    "gpu", "api=angle_gl_es2,samples=8" },
-    { "angle_gl_es3_msaa8",    "gpu", "api=angle_gl_es3,samples=8" },
-    { "commandbuffer",         "gpu", "api=commandbuffer" },
-    { "mock",                  "gpu", "api=mock" },
-#ifdef SK_VULKAN
-    { "vk",                    "gpu", "api=vulkan" },
-    { "vknostencils",          "gpu", "api=vulkan,stencils=false" },
-    { "vk1010102",             "gpu", "api=vulkan,color=1010102" },
-    { "vksrgb",                "gpu", "api=vulkan,color=srgb" },
-    { "vkesrgb",               "gpu", "api=vulkan,color=esrgb" },
-    { "vknarrow",              "gpu", "api=vulkan,color=narrow" },
-    { "vkenarrow",             "gpu", "api=vulkan,color=enarrow" },
-    { "vkf16",                 "gpu", "api=vulkan,color=f16" },
-    { "vkmsaa4",               "gpu", "api=vulkan,samples=4" },
-    { "vkmsaa8",               "gpu", "api=vulkan,samples=8" },
-    { "vkbetex",               "gpu", "api=vulkan,surf=betex" },
-    { "vkbert",                "gpu", "api=vulkan,surf=bert" },
-    { "vktestpersistentcache", "gpu", "api=vulkan,testPersistentCache=true" },
-#endif
-#ifdef SK_METAL
-    { "mtl",                   "gpu", "api=metal" },
-    { "mtl1010102",            "gpu", "api=metal,color=1010102" },
-    { "mtlmsaa4",              "gpu", "api=metal,samples=4" },
-    { "mtlmsaa8",              "gpu", "api=metal,samples=8" },
-#endif
-};
-// clang-format on
-
-static const char configHelp[] =
-    "Options: 565 8888 srgb f16 nonrendering null pdf pdfa skp pipe svg xps";
-
-static const char* config_help_fn() {
-    static SkString helpString;
-    helpString.set(configHelp);
-    for (const auto& config : gPredefinedConfigs) {
-        helpString.appendf(" %s", config.predefinedConfig);
-    }
-    helpString.append(" or use extended form 'backend[option=value,...]'.\n");
-    return helpString.c_str();
-}
-
-static const char configExtendedHelp[] =
-        "Extended form: 'backend(option=value,...)'\n\n"
-        "Possible backends and options:\n"
-        "\n"
-        "gpu[api=string,color=string,dit=bool,nvpr=bool,inst=bool,samples=int]\n"
-        "\tapi\ttype: string\trequired\n"
-        "\t    Select graphics API to use with gpu backend.\n"
-        "\t    Options:\n"
-        "\t\tgl    \t\t\tUse OpenGL.\n"
-        "\t\tgles  \t\t\tUse OpenGL ES.\n"
-        "\t\tnullgl \t\t\tUse null OpenGL.\n"
-        "\t\tangle_d3d9_es2\t\tUse OpenGL ES2 on the ANGLE Direct3D9 backend.\n"
-        "\t\tangle_d3d11_es2\t\tUse OpenGL ES2 on the ANGLE Direct3D11 backend.\n"
-        "\t\tangle_d3d11_es3\t\tUse OpenGL ES3 on the ANGLE Direct3D11 backend.\n"
-        "\t\tangle_gl_es2\t\tUse OpenGL ES2 on the ANGLE OpenGL backend.\n"
-        "\t\tangle_gl_es3\t\tUse OpenGL ES3 on the ANGLE OpenGL backend.\n"
-        "\t\tcommandbuffer\t\tUse command buffer.\n"
-        "\t\tmock\t\t\tUse mock context.\n"
-#ifdef SK_VULKAN
-        "\t\tvulkan\t\t\tUse Vulkan.\n"
-#endif
-#ifdef SK_METAL
-        "\t\tmetal\t\t\tUse Metal.\n"
-#endif
-        "\tcolor\ttype: string\tdefault: 8888.\n"
-        "\t    Select framebuffer color format.\n"
-        "\t    Options:\n"
-        "\t\t8888\t\t\tLinear 8888.\n"
-        "\t\t888x\t\t\tLinear 888x.\n"
-        "\t\t4444\t\t\tLinear 4444.\n"
-        "\t\t565\t\t\tLinear 565.\n"
-        "\t\t1010102\t\t\tLinear 1010102.\n"
-        "\t\tsrgb\t\t\tsRGB 8888.\n"
-        "\t\tesrgb\t\t\tsRGB 16-bit floating point.\n"
-        "\t\tnarrow\t\t\tNarrow gamut 8888.\n"
-        "\t\tenarrow\t\t\tNarrow gamut 16-bit floating point.\n"
-        "\t\tf16\t\t\tLinearly blended 16-bit floating point.\n"
-        "\tdit\ttype: bool\tdefault: false.\n"
-        "\t    Use device independent text.\n"
-        "\tnvpr\ttype: bool\tdefault: false.\n"
-        "\t    Use NV_path_rendering OpenGL and OpenGL ES extension.\n"
-        "\tsamples\ttype: int\tdefault: 0.\n"
-        "\t    Use multisampling with N samples.\n"
-        "\tstencils\ttype: bool\tdefault: true.\n"
-        "\t    Allow the use of stencil buffers.\n"
-        "\ttestThreading\ttype: bool\tdefault: false.\n"
-        "\t    Run with and without worker threads, check that results match.\n"
-        "\ttestPersistentCache\ttype: bool\tdefault: false.\n"
-        "\t    Run using a pre-warmed GrContextOption::fPersistentCache.\n"
-        "\tsurf\ttype: string\tdefault: default.\n"
-        "\t    Controls the type of backing store for SkSurfaces.\n"
-        "\t    Options:\n"
-        "\t\tdefault\t\t\tA renderable texture created in Skia's resource cache.\n"
-        "\t\tbetex\t\t\tA wrapped backend texture.\n"
-        "\t\tbert\t\t\tA wrapped backend render target\n"
-        "\n"
-        "Predefined configs:\n\n"
-        // Help text for pre-defined configs is auto-generated from gPredefinedConfigs
-        ;
-
-static const char* config_extended_help_fn() {
-    static SkString helpString;
-    helpString.set(configExtendedHelp);
-    for (const auto& config : gPredefinedConfigs) {
-        helpString.appendf("\t%-10s\t= gpu(%s)\n", config.predefinedConfig, config.options);
-    }
-    return helpString.c_str();
-}
-
-DEFINE_extended_string(config, defaultConfigs, config_help_fn(), config_extended_help_fn());
-
-SkCommandLineConfig::SkCommandLineConfig(const SkString& tag, const SkString& backend,
-                                         const SkTArray<SkString>& viaParts)
-        : fTag(tag)
-        , fBackend(backend)
-        , fViaParts(viaParts) {
-}
-SkCommandLineConfig::~SkCommandLineConfig() {
-}
-
-static bool parse_option_int(const SkString& value, int* outInt) {
-    if (value.isEmpty()) {
-        return false;
-    }
-    char* endptr = nullptr;
-    long intValue = strtol(value.c_str(), &endptr, 10);
-    if (*endptr != '\0') {
-        return false;
-    }
-    *outInt = static_cast<int>(intValue);
-    return true;
-}
-static bool parse_option_bool(const SkString& value, bool* outBool) {
-    if (value.equals("true")) {
-        *outBool = true;
-        return true;
-    }
-    if (value.equals("false")) {
-        *outBool = false;
-        return true;
-    }
-    return false;
-}
-static bool parse_option_gpu_api(const SkString& value,
-                                 SkCommandLineConfigGpu::ContextType* outContextType) {
-    if (value.equals("gl")) {
-        *outContextType = GrContextFactory::kGL_ContextType;
-        return true;
-    }
-    if (value.equals("gles")) {
-        *outContextType = GrContextFactory::kGLES_ContextType;
-        return true;
-    }
-    if (value.equals("nullgl")) {
-        *outContextType = GrContextFactory::kNullGL_ContextType;
-        return true;
-    }
-    if (value.equals("angle_d3d9_es2")) {
-        *outContextType = GrContextFactory::kANGLE_D3D9_ES2_ContextType;
-        return true;
-    }
-    if (value.equals("angle_d3d11_es2")) {
-        *outContextType = GrContextFactory::kANGLE_D3D11_ES2_ContextType;
-        return true;
-    }
-    if (value.equals("angle_d3d11_es3")) {
-        *outContextType = GrContextFactory::kANGLE_D3D11_ES3_ContextType;
-        return true;
-    }
-    if (value.equals("angle_gl_es2")) {
-        *outContextType = GrContextFactory::kANGLE_GL_ES2_ContextType;
-        return true;
-    }
-    if (value.equals("angle_gl_es3")) {
-        *outContextType = GrContextFactory::kANGLE_GL_ES3_ContextType;
-        return true;
-    }
-    if (value.equals("commandbuffer")) {
-        *outContextType = GrContextFactory::kCommandBuffer_ContextType;
-        return true;
-    }
-    if (value.equals("mock")) {
-        *outContextType = GrContextFactory::kMock_ContextType;
-        return true;
-    }
-#ifdef SK_VULKAN
-    if (value.equals("vulkan")) {
-        *outContextType = GrContextFactory::kVulkan_ContextType;
-        return true;
-    }
-#endif
-#ifdef SK_METAL
-    if (value.equals("metal")) {
-        *outContextType = GrContextFactory::kMetal_ContextType;
-        return true;
-    }
-#endif
-    return false;
-}
-
-static bool parse_option_gpu_color(const SkString& value,
-                                   SkColorType* outColorType,
-                                   SkAlphaType* alphaType,
-                                   sk_sp<SkColorSpace>* outColorSpace) {
-    // We always use premul unless the color type is 565.
-    *alphaType = kPremul_SkAlphaType;
-
-    if (value.equals("8888")) {
-        *outColorType = kRGBA_8888_SkColorType;
-        *outColorSpace = nullptr;
-    } else if (value.equals("888x")) {
-        *outColorType = kRGB_888x_SkColorType;
-        *outColorSpace = nullptr;
-    } else if (value.equals("4444")) {
-        *outColorType = kARGB_4444_SkColorType;
-        *outColorSpace = nullptr;
-    } else if (value.equals("565")) {
-        *outColorType = kRGB_565_SkColorType;
-        *alphaType = kOpaque_SkAlphaType;
-        *outColorSpace = nullptr;
-    } else if (value.equals("1010102")) {
-        *outColorType = kRGBA_1010102_SkColorType;
-        *outColorSpace = nullptr;
-    } else if (value.equals("srgb")) {
-        *outColorType = kRGBA_8888_SkColorType;
-        *outColorSpace = SkColorSpace::MakeSRGB();
-    } else if (value.equals("p3")) {
-        *outColorType = kRGBA_8888_SkColorType;
-        *outColorSpace = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
-    } else if (value.equals("esrgb")) {
-        *outColorType = kRGBA_F16_SkColorType;
-        *outColorSpace = SkColorSpace::MakeSRGB();
-    } else if (value.equals("narrow") || value.equals("enarrow")) {
-        *outColorType = value.equals("narrow") ? kRGBA_8888_SkColorType : kRGBA_F16_SkColorType;
-        *outColorSpace = SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, gNarrow_toXYZD50);
-    } else if (value.equals("f16")) {
-        *outColorType = kRGBA_F16_SkColorType;
-        *outColorSpace = SkColorSpace::MakeSRGBLinear();
-    } else {
-        return false;
-    }
-    return true;
-}
-
-static bool parse_option_gpu_surf_type(const SkString& value,
-                                       SkCommandLineConfigGpu::SurfType* surfType) {
-    if (value.equals("default")) {
-        *surfType = SkCommandLineConfigGpu::SurfType::kDefault;
-        return true;
-    }
-    if (value.equals("betex")) {
-        *surfType = SkCommandLineConfigGpu::SurfType::kBackendTexture;
-        return true;
-    }
-    if (value.equals("bert")) {
-        *surfType = SkCommandLineConfigGpu::SurfType::kBackendRenderTarget;
-        return true;
-    }
-    return false;
-}
-
-// Extended options take form --config item[key1=value1,key2=value2,...]
-// Example: --config gpu[api=gl,color=8888]
-class ExtendedOptions {
-public:
-    ExtendedOptions(const SkString& optionsString, bool* outParseSucceeded) {
-        SkTArray<SkString> optionParts;
-        SkStrSplit(optionsString.c_str(), ",", kStrict_SkStrSplitMode, &optionParts);
-        for (int i = 0; i < optionParts.count(); ++i) {
-            SkTArray<SkString> keyValueParts;
-            SkStrSplit(optionParts[i].c_str(), "=", kStrict_SkStrSplitMode, &keyValueParts);
-            if (keyValueParts.count() != 2) {
-                *outParseSucceeded = false;
-                return;
-            }
-            const SkString& key = keyValueParts[0];
-            const SkString& value = keyValueParts[1];
-            if (fOptionsMap.find(key) == nullptr) {
-                fOptionsMap.set(key, value);
-            } else {
-                // Duplicate values are not allowed.
-                *outParseSucceeded = false;
-                return;
-            }
-        }
-        *outParseSucceeded = true;
-    }
-
-    bool get_option_gpu_color(const char* optionKey,
-                              SkColorType* outColorType,
-                              SkAlphaType* alphaType,
-                              sk_sp<SkColorSpace>* outColorSpace,
-                              bool optional = true) const {
-        SkString* optionValue = fOptionsMap.find(SkString(optionKey));
-        if (optionValue == nullptr) {
-            return optional;
-        }
-        return parse_option_gpu_color(*optionValue, outColorType, alphaType, outColorSpace);
-    }
-
-    bool get_option_gpu_api(const char* optionKey,
-                            SkCommandLineConfigGpu::ContextType* outContextType,
-                            bool optional = true) const {
-        SkString* optionValue = fOptionsMap.find(SkString(optionKey));
-        if (optionValue == nullptr) {
-            return optional;
-        }
-        return parse_option_gpu_api(*optionValue, outContextType);
-    }
-
-    bool get_option_gpu_surf_type(const char* optionKey,
-                                  SkCommandLineConfigGpu::SurfType* outSurfType,
-                                  bool optional = true) const {
-        SkString* optionValue = fOptionsMap.find(SkString(optionKey));
-        if (optionValue == nullptr) {
-            return optional;
-        }
-        return parse_option_gpu_surf_type(*optionValue, outSurfType);
-    }
-
-    bool get_option_int(const char* optionKey, int* outInt, bool optional = true) const {
-        SkString* optionValue = fOptionsMap.find(SkString(optionKey));
-        if (optionValue == nullptr) {
-            return optional;
-        }
-        return parse_option_int(*optionValue, outInt);
-    }
-
-    bool get_option_bool(const char* optionKey, bool* outBool, bool optional = true) const {
-        SkString* optionValue = fOptionsMap.find(SkString(optionKey));
-        if (optionValue == nullptr) {
-            return optional;
-        }
-        return parse_option_bool(*optionValue, outBool);
-    }
-
-private:
-    SkTHashMap<SkString, SkString> fOptionsMap;
-};
-
-SkCommandLineConfigGpu::SkCommandLineConfigGpu(
-        const SkString& tag, const SkTArray<SkString>& viaParts, ContextType contextType,
-        bool useNVPR, bool useDIText, int samples, SkColorType colorType, SkAlphaType alphaType,
-        sk_sp<SkColorSpace> colorSpace, bool useStencilBuffers, bool testThreading,
-        bool testPersistentCache, SurfType surfType)
-        : SkCommandLineConfig(tag, SkString("gpu"), viaParts)
-        , fContextType(contextType)
-        , fContextOverrides(ContextOverrides::kNone)
-        , fUseDIText(useDIText)
-        , fSamples(samples)
-        , fColorType(colorType)
-        , fAlphaType(alphaType)
-        , fColorSpace(std::move(colorSpace))
-        , fTestThreading(testThreading)
-        , fTestPersistentCache(testPersistentCache)
-        , fSurfType(surfType) {
-    if (useNVPR) {
-        fContextOverrides |= ContextOverrides::kRequireNVPRSupport;
-    } else {
-        // We don't disable NVPR for instanced configs. Otherwise the caps wouldn't use mixed
-        // samples and we couldn't test the mixed samples backend for simple shapes.
-        fContextOverrides |= ContextOverrides::kDisableNVPR;
-    }
-    if (!useStencilBuffers) {
-        fContextOverrides |= ContextOverrides::kAvoidStencilBuffers;
-    }
-}
-
-SkCommandLineConfigGpu* parse_command_line_config_gpu(const SkString& tag,
-                                                      const SkTArray<SkString>& vias,
-                                                      const SkString& options) {
-    // Defaults for GPU backend.
-    SkCommandLineConfigGpu::ContextType contextType = GrContextFactory::kGL_ContextType;
-    bool useNVPR = false;
-    bool useDIText = false;
-    int samples = 1;
-    SkColorType colorType = kRGBA_8888_SkColorType;
-    SkAlphaType alphaType = kPremul_SkAlphaType;
-    sk_sp<SkColorSpace> colorSpace = nullptr;
-    bool useStencils = true;
-    bool testThreading = false;
-    bool testPersistentCache = false;
-    SkCommandLineConfigGpu::SurfType surfType = SkCommandLineConfigGpu::SurfType::kDefault;
-
-    bool parseSucceeded = false;
-    ExtendedOptions extendedOptions(options, &parseSucceeded);
-    if (!parseSucceeded) {
-        return nullptr;
-    }
-
-    bool validOptions =
-            extendedOptions.get_option_gpu_api("api", &contextType, false) &&
-            extendedOptions.get_option_bool("nvpr", &useNVPR) &&
-            extendedOptions.get_option_bool("dit", &useDIText) &&
-            extendedOptions.get_option_int("samples", &samples) &&
-            extendedOptions.get_option_gpu_color("color", &colorType, &alphaType, &colorSpace) &&
-            extendedOptions.get_option_bool("stencils", &useStencils) &&
-            extendedOptions.get_option_bool("testThreading", &testThreading) &&
-            extendedOptions.get_option_bool("testPersistentCache", &testPersistentCache) &&
-            extendedOptions.get_option_gpu_surf_type("surf", &surfType);
-
-    // testing threading and the persistent cache are mutually exclusive.
-    if (!validOptions || (testThreading && testPersistentCache)) {
-        return nullptr;
-    }
-
-    return new SkCommandLineConfigGpu(tag, vias, contextType, useNVPR, useDIText, samples,
-                                      colorType, alphaType, colorSpace, useStencils, testThreading,
-                                      testPersistentCache, surfType);
-}
-
-SkCommandLineConfigSvg::SkCommandLineConfigSvg(const SkString& tag,
-                                               const SkTArray<SkString>& viaParts, int pageIndex)
-        : SkCommandLineConfig(tag, SkString("svg"), viaParts), fPageIndex(pageIndex) {}
-
-SkCommandLineConfigSvg* parse_command_line_config_svg(const SkString& tag,
-                                                      const SkTArray<SkString>& vias,
-                                                      const SkString& options) {
-    // Defaults for SVG backend.
-    int pageIndex = 0;
-
-    bool parseSucceeded = false;
-    ExtendedOptions extendedOptions(options, &parseSucceeded);
-    if (!parseSucceeded) {
-        return nullptr;
-    }
-
-    bool validOptions = extendedOptions.get_option_int("page", &pageIndex);
-
-    if (!validOptions) {
-        return nullptr;
-    }
-
-    return new SkCommandLineConfigSvg(tag, vias, pageIndex);
-}
-
-void ParseConfigs(const SkCommandLineFlags::StringArray& configs,
-                  SkCommandLineConfigArray* outResult) {
-    outResult->reset();
-    for (int i = 0; i < configs.count(); ++i) {
-        SkString extendedBackend;
-        SkString extendedOptions;
-        SkString simpleBackend;
-        SkTArray<SkString> vias;
-
-        SkString tag(configs[i]);
-        SkTArray<SkString> parts;
-        SkStrSplit(tag.c_str(), "[", kStrict_SkStrSplitMode, &parts);
-        if (parts.count() == 2) {
-            SkTArray<SkString> parts2;
-            SkStrSplit(parts[1].c_str(), "]", kStrict_SkStrSplitMode, &parts2);
-            if (parts2.count() == 2 && parts2[1].isEmpty()) {
-                SkStrSplit(parts[0].c_str(), "-", kStrict_SkStrSplitMode, &vias);
-                if (vias.count()) {
-                    extendedBackend = vias[vias.count() - 1];
-                    vias.pop_back();
-                } else {
-                    extendedBackend = parts[0];
-                }
-                extendedOptions = parts2[0];
-                simpleBackend.printf("%s[%s]", extendedBackend.c_str(), extendedOptions.c_str());
-            }
-        }
-
-        if (extendedBackend.isEmpty()) {
-            simpleBackend = tag;
-            SkStrSplit(tag.c_str(), "-", kStrict_SkStrSplitMode, &vias);
-            if (vias.count()) {
-                simpleBackend = vias[vias.count() - 1];
-                vias.pop_back();
-            }
-            for (auto& predefinedConfig : gPredefinedConfigs) {
-                if (simpleBackend.equals(predefinedConfig.predefinedConfig)) {
-                    extendedBackend = predefinedConfig.backend;
-                    extendedOptions = predefinedConfig.options;
-                    break;
-                }
-            }
-        }
-        SkCommandLineConfig* parsedConfig = nullptr;
-        if (extendedBackend.equals("gpu")) {
-            parsedConfig = parse_command_line_config_gpu(tag, vias, extendedOptions);
-        }
-        if (extendedBackend.equals("svg")) {
-            parsedConfig = parse_command_line_config_svg(tag, vias, extendedOptions);
-        }
-        if (!parsedConfig) {
-            parsedConfig = new SkCommandLineConfig(tag, simpleBackend, vias);
-        }
-        outResult->emplace_back(parsedConfig);
-    }
-}
diff --git a/tools/flags/SkCommonFlagsConfig.h b/tools/flags/SkCommonFlagsConfig.h
deleted file mode 100644
index fa11cc6..0000000
--- a/tools/flags/SkCommonFlagsConfig.h
+++ /dev/null
@@ -1,110 +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 SK_COMMON_FLAGS_CONFIG_H
-#define SK_COMMON_FLAGS_CONFIG_H
-
-#include "SkCommandLineFlags.h"
-#include "GrContextFactory.h"
-
-DECLARE_string(config);
-
-class SkCommandLineConfigGpu;
-class SkCommandLineConfigSvg;
-
-// SkCommandLineConfig represents a Skia rendering configuration string.
-// The string has following form:
-// tag:
-//   [via-]*backend
-// where 'backend' consists of chars excluding hyphen
-// and each 'via' consists of chars excluding hyphen.
-class SkCommandLineConfig {
-  public:
-    SkCommandLineConfig(const SkString& tag, const SkString& backend,
-                        const SkTArray<SkString>& viaParts);
-    virtual ~SkCommandLineConfig();
-    virtual const SkCommandLineConfigGpu* asConfigGpu() const { return nullptr; }
-    virtual const SkCommandLineConfigSvg* asConfigSvg() const { return nullptr; }
-    const SkString& getTag() const { return fTag; }
-    const SkString& getBackend() const { return fBackend; }
-    const SkTArray<SkString>& getViaParts() const { return fViaParts; }
-  private:
-    SkString fTag;
-    SkString fBackend;
-    SkTArray<SkString> fViaParts;
-};
-
-// SkCommandLineConfigGpu is a SkCommandLineConfig that extracts information out of the backend
-// part of the tag. It is constructed tags that have:
-// * backends of form "gpu[option=value,option2=value,...]"
-// * backends that represent a shorthand of above (such as "glmsaa16" representing
-// "gpu(api=gl,samples=16)")
-class SkCommandLineConfigGpu : public SkCommandLineConfig {
-public:
-    enum class SurfType {
-        kDefault,
-        kBackendTexture,
-        kBackendRenderTarget
-    };
-    typedef sk_gpu_test::GrContextFactory::ContextType ContextType;
-    typedef sk_gpu_test::GrContextFactory::ContextOverrides ContextOverrides;
-
-    SkCommandLineConfigGpu(const SkString& tag, const SkTArray<SkString>& viaParts,
-                           ContextType contextType, bool useNVPR, bool useDIText, int samples,
-                           SkColorType colorType, SkAlphaType alphaType,
-                           sk_sp<SkColorSpace> colorSpace, bool useStencilBuffers,
-                           bool testThreading, bool testPersistentCache, SurfType);
-
-    const SkCommandLineConfigGpu* asConfigGpu() const override { return this; }
-    ContextType getContextType() const { return fContextType; }
-    ContextOverrides getContextOverrides() const { return fContextOverrides; }
-    bool getUseNVPR() const {
-        SkASSERT(!(fContextOverrides & ContextOverrides::kRequireNVPRSupport) ||
-                 !(fContextOverrides & ContextOverrides::kDisableNVPR));
-        return fContextOverrides & ContextOverrides::kRequireNVPRSupport;
-    }
-    bool getUseDIText() const { return fUseDIText; }
-    int getSamples() const { return fSamples; }
-    SkColorType getColorType() const { return fColorType; }
-    SkAlphaType getAlphaType() const { return fAlphaType; }
-    SkColorSpace* getColorSpace() const { return fColorSpace.get(); }
-    bool getTestThreading() const { return fTestThreading; }
-    bool getTestPersistentCache() const { return fTestPersistentCache; }
-    SurfType getSurfType() const { return fSurfType; }
-
-private:
-    ContextType fContextType;
-    ContextOverrides fContextOverrides;
-    bool fUseDIText;
-    int fSamples;
-    SkColorType fColorType;
-    SkAlphaType fAlphaType;
-    sk_sp<SkColorSpace> fColorSpace;
-    bool fTestThreading;
-    bool fTestPersistentCache;
-    SurfType fSurfType;
-};
-
-// SkCommandLineConfigSvg is a SkCommandLineConfig that extracts information out of the backend
-// part of the tag. It is constructed tags that have:
-// * backends of form "svg[option=value,option2=value,...]"
-class SkCommandLineConfigSvg : public SkCommandLineConfig {
-public:
-    SkCommandLineConfigSvg(const SkString& tag, const SkTArray<SkString>& viaParts, int pageIndex);
-    const SkCommandLineConfigSvg* asConfigSvg() const override { return this; }
-
-    int getPageIndex() const { return fPageIndex; }
-
-private:
-    int fPageIndex;
-};
-
-typedef SkTArray<std::unique_ptr<SkCommandLineConfig>, true> SkCommandLineConfigArray;
-void ParseConfigs(const SkCommandLineFlags::StringArray& configList,
-                  SkCommandLineConfigArray* outResult);
-
-#endif
diff --git a/tools/flags/SkCommonFlagsGpu.h b/tools/flags/SkCommonFlagsGpu.h
deleted file mode 100644
index 23672dd..0000000
--- a/tools/flags/SkCommonFlagsGpu.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SK_COMMON_FLAGS_GPU_H
-#define SK_COMMON_FLAGS_GPU_H
-
-#include "GrTypesPriv.h"
-#include "SkCommandLineFlags.h"
-#include "SkTypes.h"
-
-DECLARE_int32(gpuThreads);
-DECLARE_bool(cachePathMasks);
-DECLARE_bool(noGS);
-DECLARE_string(pr);
-DECLARE_bool(disableExplicitAlloc);
-DECLARE_bool(reduceOpListSplitting);
-
-inline GpuPathRenderers get_named_pathrenderers_flags(const char* name) {
-    if (!strcmp(name, "none")) {
-        return GpuPathRenderers::kNone;
-    } else if (!strcmp(name, "dashline")) {
-        return GpuPathRenderers::kDashLine;
-    } else if (!strcmp(name, "nvpr")) {
-        return GpuPathRenderers::kStencilAndCover;
-    } else if (!strcmp(name, "ccpr")) {
-        return GpuPathRenderers::kCoverageCounting;
-    } else if (!strcmp(name, "aahairline")) {
-        return GpuPathRenderers::kAAHairline;
-    } else if (!strcmp(name, "aaconvex")) {
-        return GpuPathRenderers::kAAConvex;
-    } else if (!strcmp(name, "aalinearizing")) {
-        return GpuPathRenderers::kAALinearizing;
-    } else if (!strcmp(name, "small")) {
-        return GpuPathRenderers::kSmall;
-    } else if (!strcmp(name, "tess")) {
-        return GpuPathRenderers::kTessellating;
-    } else if (!strcmp(name, "all")) {
-        return GpuPathRenderers::kAll;
-    }
-    SK_ABORT(SkStringPrintf("error: unknown named path renderer \"%s\"\n", name).c_str());
-    return GpuPathRenderers::kNone;
-}
-
-inline GpuPathRenderers CollectGpuPathRenderersFromFlags() {
-    if (FLAGS_pr.isEmpty()) {
-        return GpuPathRenderers::kAll;
-    }
-    GpuPathRenderers gpuPathRenderers = '~' == FLAGS_pr[0][0]
-            ? GpuPathRenderers::kAll : GpuPathRenderers::kNone;
-    for (int i = 0; i < FLAGS_pr.count(); ++i) {
-        const char* name = FLAGS_pr[i];
-        if (name[0] == '~') {
-            gpuPathRenderers &= ~get_named_pathrenderers_flags(&name[1]);
-        } else {
-            gpuPathRenderers |= get_named_pathrenderers_flags(name);
-        }
-    }
-    return gpuPathRenderers;
-}
-
-/**
- *  Helper to set GrContextOptions from common GPU flags.
- */
-void SetCtxOptionsFromCommonFlags(struct GrContextOptions*);
-
-#endif
diff --git a/tools/fm/fm.cpp b/tools/fm/fm.cpp
new file mode 100644
index 0000000..2a918ab
--- /dev/null
+++ b/tools/fm/fm.cpp
@@ -0,0 +1,604 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+
+#include "CommandLineFlags.h"
+#include "CommonFlags.h"
+#include "EventTracingPriv.h"
+#include "GrContextFactory.h"
+#include "GrContextOptions.h"
+#include "GrContextPriv.h"
+#include "GrGpu.h"
+#include "GrPersistentCacheUtils.h"
+#include "HashAndEncode.h"
+#include "MemoryCache.h"
+#include "SkCodec.h"
+#include "SkColorSpace.h"
+#include "SkColorSpacePriv.h"
+#include "SkGraphics.h"
+#include "SkMD5.h"
+#include "SkOSFile.h"
+#include "SkOSPath.h"
+#include "SkPDFDocument.h"
+#include "SkPicture.h"
+#include "SkPictureRecorder.h"
+#include "SkSVGDOM.h"
+#include "SkTHash.h"
+#include "ToolUtils.h"
+#include "gm.h"
+#include <chrono>
+#include <functional>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if defined(SK_ENABLE_SKOTTIE)
+    #include "Skottie.h"
+    #include "SkottieUtils.h"
+#endif
+
+using sk_gpu_test::GrContextFactory;
+
+static DEFINE_string2(sources, s, "", "Which GMs, .skps, or images to draw.");
+static DEFINE_string2(backend, b, "", "Backend used to create a canvas to draw into.");
+
+static DEFINE_string(ct    ,   "8888", "The color type for any raster backend.");
+static DEFINE_string(at    , "premul", "The alpha type for any raster backend.");
+static DEFINE_string(gamut ,   "srgb", "The color gamut for any raster backend.");
+static DEFINE_string(tf    ,   "srgb", "The transfer function for any raster backend.");
+static DEFINE_bool  (legacy,    false, "Use a null SkColorSpace instead of --gamut and --tf?");
+
+static DEFINE_int   (samples ,         0, "Samples per pixel in GPU backends.");
+static DEFINE_bool  (nvpr    ,     false, "Use NV_path_rendering in GPU backends?");
+static DEFINE_bool  (stencils,      true, "If false, avoid stencil buffers in GPU backends.");
+static DEFINE_bool  (dit     ,     false, "Use device-independent text in GPU backends.");
+static DEFINE_string(surf    , "default", "Backing store for GPU backend surfaces.");
+
+static DEFINE_bool(       preAbandonGpuContext, false, "Abandon the GrContext before drawing.");
+static DEFINE_bool(          abandonGpuContext, false, "Abandon the GrContext after drawing.");
+static DEFINE_bool(releaseAndAbandonGpuContext, false,
+                   "Release all GPU resources and abandon the GrContext after drawing.");
+
+static DEFINE_bool(decodeToDst, false,
+                   "Decode images to destination format rather than suggested natural format.");
+
+static DEFINE_double(rasterDPI, SK_ScalarDefaultRasterDPI,
+                     "DPI for rasterized content in vector backends like --backend pdf.");
+static DEFINE_bool(PDFA, false, "Create PDF/A with --backend pdf?");
+
+static DEFINE_bool   (cpuDetect, true, "Detect CPU features for runtime optimizations?");
+static DEFINE_string2(writePath, w, "", "Write .pngs to this directory if set.");
+
+static DEFINE_string(writeShaders, "", "Write GLSL shaders to this directory if set.");
+
+static DEFINE_string(key,        "", "Metadata passed through to .png encoder and .json output.");
+static DEFINE_string(properties, "", "Metadata passed through to .png encoder and .json output.");
+
+template <typename T>
+struct FlagOption {
+    const char* label;
+    T           value;
+};
+
+template <typename T, int N>
+static bool parse_flag(const CommandLineFlags::StringArray& flag,
+                       const char* flag_name,
+                       const FlagOption<T> (&array)[N],
+                       T* value) {
+    for (auto entry : array) {
+        if (flag.contains(entry.label)) {
+            *value = entry.value;
+            return true;
+        }
+    }
+    fprintf(stderr, "Known values for --%s:\n", flag_name);
+    for (auto entry : array) {
+        fprintf(stderr, "    --%s %s\n", flag_name, entry.label);
+    }
+    return false;
+}
+
+static void exit_with_failure() {
+    // TODO: dump stack trace, debug trap, print currently running job, etc?
+    exit(1);
+}
+
+struct Result {
+    enum { Ok, Skip, Fail} status;
+    SkString               failure;
+};
+static const Result ok = {Result::Ok,   {}},
+                  skip = {Result::Skip, {}};
+
+template <typename... Args>
+static Result fail(const char* why, Args... args) {
+    return { Result::Fail, SkStringPrintf(why, args...) };
+}
+
+
+struct Source {
+    SkString                               name;
+    SkISize                                size;
+    std::function<Result(SkCanvas*)>       draw;
+    std::function<void(GrContextOptions*)> tweak = [](GrContextOptions*){};
+};
+
+static void init(Source* source, std::shared_ptr<skiagm::GM> gm) {
+    source->size  = gm->getISize();
+    source->tweak = [gm](GrContextOptions* options) { gm->modifyGrContextOptions(options); };
+    source->draw  = [gm](SkCanvas* canvas) {
+        SkString err;
+        switch (gm->draw(canvas, &err)) {
+            case skiagm::DrawResult::kOk:   break;
+            case skiagm::DrawResult::kSkip: return skip;
+            case skiagm::DrawResult::kFail: return fail(err.c_str());
+        }
+        return ok;
+    };
+}
+
+static void init(Source* source, sk_sp<SkPicture> pic) {
+    source->size = pic->cullRect().roundOut().size();
+    source->draw = [pic](SkCanvas* canvas) {
+        canvas->drawPicture(pic);
+        return ok;
+    };
+}
+
+static void init(Source* source, std::shared_ptr<SkCodec> codec) {
+    source->size = codec->dimensions();
+    source->draw = [codec](SkCanvas* canvas) {
+        SkImageInfo info = codec->getInfo();
+        if (FLAGS_decodeToDst) {
+            info = canvas->imageInfo().makeWH(info.width(),
+                                              info.height());
+        }
+
+        SkBitmap bm;
+        bm.allocPixels(info);
+        switch (SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes())) {
+            case SkCodec::kSuccess:
+            case SkCodec::kErrorInInput:
+            case SkCodec::kIncompleteInput: canvas->drawBitmap(bm, 0,0);
+                                            break;
+            default: return fail("codec->getPixels() failed: %d\n", result);
+        }
+        return ok;
+    };
+}
+
+static void init(Source* source, sk_sp<SkSVGDOM> svg) {
+    source->size = svg->containerSize().isEmpty() ? SkISize{1000,1000}
+                                                  : svg->containerSize().toCeil();
+    source->draw = [svg](SkCanvas* canvas) {
+        svg->render(canvas);
+        return ok;
+    };
+}
+
+#if defined(SK_ENABLE_SKOTTIE)
+static void init(Source* source, sk_sp<skottie::Animation> animation) {
+    source->size = {1000,1000};
+    source->draw = [animation](SkCanvas* canvas) {
+        canvas->clear(SK_ColorWHITE);
+
+        // Draw frames in a shuffled order to exercise nonlinear frame progression.
+        // The film strip will still be in time order, just drawn out of order.
+        const int order[] = { 4, 0, 3, 1, 2 };
+        const int tiles = SK_ARRAY_COUNT(order);
+        const float dim = 1000.0f / tiles;
+
+        const float dt = 1.0f / (tiles*tiles - 1);
+
+        for (int y : order)
+        for (int x : order) {
+            SkRect dst = {x*dim, y*dim, (x+1)*dim, (y+1)*dim};
+
+            SkAutoCanvasRestore _(canvas, true/*save now*/);
+            canvas->clipRect(dst, /*aa=*/true);
+            canvas->concat(SkMatrix::MakeRectToRect(SkRect::MakeSize(animation->size()),
+                                                    dst,
+                                                    SkMatrix::kCenter_ScaleToFit));
+            float t = (y*tiles + x) * dt;
+            animation->seek(t);
+            animation->render(canvas);
+        }
+        return ok;
+    };
+}
+#endif
+
+static sk_sp<SkImage> draw_with_cpu(std::function<bool(SkCanvas*)> draw,
+                                    SkImageInfo info) {
+    if (sk_sp<SkSurface> surface = SkSurface::MakeRaster(info)) {
+        if (draw(surface->getCanvas())) {
+            return surface->makeImageSnapshot();
+        }
+    }
+    return nullptr;
+}
+
+static sk_sp<SkData> draw_as_skp(std::function<bool(SkCanvas*)> draw,
+                                 SkImageInfo info) {
+    SkPictureRecorder recorder;
+    if (draw(recorder.beginRecording(info.width(), info.height()))) {
+        return recorder.finishRecordingAsPicture()->serialize();
+    }
+    return nullptr;
+}
+
+static sk_sp<SkData> draw_as_pdf(std::function<bool(SkCanvas*)> draw,
+                                 SkImageInfo info,
+                                 SkString name) {
+    SkPDF::Metadata metadata;
+    metadata.fTitle     = name;
+    metadata.fCreator   = "Skia/FM";
+    metadata.fRasterDPI = FLAGS_rasterDPI;
+    metadata.fPDFA      = FLAGS_PDFA;
+
+    SkDynamicMemoryWStream stream;
+    if (sk_sp<SkDocument> doc = SkPDF::MakeDocument(&stream, metadata)) {
+        if (draw(doc->beginPage(info.width(), info.height()))) {
+            doc->endPage();
+            doc->close();
+            return stream.detachAsData();
+        }
+    }
+    return nullptr;
+}
+
+static sk_sp<SkImage> draw_with_gpu(std::function<bool(SkCanvas*)> draw,
+                                    SkImageInfo info,
+                                    GrContextFactory::ContextType api,
+                                    GrContextFactory* factory) {
+    enum class SurfaceType { kDefault, kBackendTexture, kBackendRenderTarget };
+    const FlagOption<SurfaceType> kSurfaceTypes[] = {
+        { "default", SurfaceType::kDefault },
+        { "betex"  , SurfaceType::kBackendTexture },
+        { "bert"   , SurfaceType::kBackendRenderTarget },
+    };
+    SurfaceType surfaceType;
+    if (!parse_flag(FLAGS_surf, "surf", kSurfaceTypes, &surfaceType)) {
+        return nullptr;
+    }
+
+    auto overrides = FLAGS_nvpr ? GrContextFactory::ContextOverrides::kRequireNVPRSupport
+                                : GrContextFactory::ContextOverrides::kDisableNVPR;
+    if (!FLAGS_stencils) { overrides |= GrContextFactory::ContextOverrides::kAvoidStencilBuffers; }
+
+    GrContext* context = factory->getContextInfo(api, overrides)
+                                 .grContext();
+
+    uint32_t flags = FLAGS_dit ? SkSurfaceProps::kUseDeviceIndependentFonts_Flag
+                               : 0;
+    SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType);
+
+    sk_sp<SkSurface> surface;
+    GrBackendTexture backendTexture;
+    GrBackendRenderTarget backendRT;
+
+    switch (surfaceType) {
+        case SurfaceType::kDefault:
+            surface = SkSurface::MakeRenderTarget(context,
+                                                  SkBudgeted::kNo,
+                                                  info,
+                                                  FLAGS_samples,
+                                                  &props);
+            break;
+
+        case SurfaceType::kBackendTexture:
+            backendTexture = context->priv().getGpu()
+                ->createTestingOnlyBackendTexture(nullptr,
+                                                  info.width(),
+                                                  info.height(),
+                                                  info.colorType(),
+                                                  true,
+                                                  GrMipMapped::kNo);
+            surface = SkSurface::MakeFromBackendTexture(context,
+                                                        backendTexture,
+                                                        kTopLeft_GrSurfaceOrigin,
+                                                        FLAGS_samples,
+                                                        info.colorType(),
+                                                        info.refColorSpace(),
+                                                        &props);
+            break;
+
+        case SurfaceType::kBackendRenderTarget:
+            backendRT = context->priv().getGpu()
+                ->createTestingOnlyBackendRenderTarget(info.width(),
+                                                       info.height(),
+                                                       SkColorTypeToGrColorType(info.colorType()));
+            surface = SkSurface::MakeFromBackendRenderTarget(context,
+                                                             backendRT,
+                                                             kBottomLeft_GrSurfaceOrigin,
+                                                             info.colorType(),
+                                                             info.refColorSpace(),
+                                                             &props);
+            break;
+    }
+
+    if (!surface) {
+        fprintf(stderr, "Could not create GPU surface.\n");
+        return nullptr;
+    }
+
+    if (FLAGS_preAbandonGpuContext) {
+        factory->abandonContexts();
+    }
+
+    sk_sp<SkImage> image;
+    if (draw(surface->getCanvas())) {
+        image = surface->makeImageSnapshot();
+    }
+
+    if (FLAGS_abandonGpuContext) {
+        factory->abandonContexts();
+    } else if (FLAGS_releaseAndAbandonGpuContext) {
+        factory->releaseResourcesAndAbandonContexts();
+    }
+
+    if (!context->abandoned()) {
+        surface.reset();
+        if (backendTexture.isValid()) {
+            context->priv().getGpu()->deleteTestingOnlyBackendTexture(backendTexture);
+        }
+        if (backendRT.isValid()) {
+            context->priv().getGpu()->deleteTestingOnlyBackendRenderTarget(backendRT);
+        }
+    }
+
+    return image;
+}
+
+int main(int argc, char** argv) {
+    CommandLineFlags::Parse(argc, argv);
+
+    if (FLAGS_cpuDetect) {
+        SkGraphics::Init();
+    }
+    initializeEventTracingForTools();
+    ToolUtils::SetDefaultFontMgr();
+    SetAnalyticAAFromCommonFlags();
+
+    GrContextOptions baseOptions;
+    SetCtxOptionsFromCommonFlags(&baseOptions);
+
+    sk_gpu_test::MemoryCache memoryCache;
+    if (!FLAGS_writeShaders.isEmpty()) {
+        baseOptions.fPersistentCache = &memoryCache;
+        baseOptions.fDisallowGLSLBinaryCaching = true;
+    }
+
+    SkTHashMap<SkString, skiagm::GMFactory> gm_factories;
+    for (skiagm::GMFactory factory : skiagm::GMRegistry::Range()) {
+        std::unique_ptr<skiagm::GM> gm{factory(nullptr)};
+        if (FLAGS_sources.isEmpty()) {
+            fprintf(stdout, "%s\n", gm->getName());
+        } else {
+            gm_factories.set(SkString{gm->getName()}, factory);
+        }
+    }
+    if (FLAGS_sources.isEmpty()) {
+        return 0;
+    }
+
+    SkTArray<Source> sources;
+    for (const SkString& name : FLAGS_sources) {
+        Source* source = &sources.push_back();
+
+        if (skiagm::GMFactory* factory = gm_factories.find(name)) {
+            std::shared_ptr<skiagm::GM> gm{(*factory)(nullptr)};
+            source->name = name;
+            init(source, gm);
+            continue;
+        }
+
+        if (sk_sp<SkData> blob = SkData::MakeFromFileName(name.c_str())) {
+            source->name = SkOSPath::Basename(name.c_str());
+
+            if (name.endsWith(".skp")) {
+                if (sk_sp<SkPicture> pic = SkPicture::MakeFromData(blob.get())) {
+                    init(source, pic);
+                    continue;
+                }
+            } else if (name.endsWith(".svg")) {
+                SkMemoryStream stream{blob};
+                if (sk_sp<SkSVGDOM> svg = SkSVGDOM::MakeFromStream(stream)) {
+                    init(source, svg);
+                    continue;
+                }
+            }
+#if defined(SK_ENABLE_SKOTTIE)
+            else if (name.endsWith(".json")) {
+                const SkString dir  = SkOSPath::Dirname(name.c_str());
+                if (sk_sp<skottie::Animation> animation = skottie::Animation::Builder()
+                        .setResourceProvider(skottie_utils::FileResourceProvider::Make(dir))
+                        .make((const char*)blob->data(), blob->size())) {
+                    init(source, animation);
+                    continue;
+                }
+            }
+#endif
+            else if (std::shared_ptr<SkCodec> codec = SkCodec::MakeFromData(blob)) {
+                init(source, codec);
+                continue;
+            }
+        }
+
+        fprintf(stderr, "Don't understand source '%s'... bailing out.\n", name.c_str());
+        return 1;
+    }
+
+    enum NonGpuBackends {
+        kCPU_Backend = -1,
+        kSKP_Backend = -2,
+        kPDF_Backend = -3,
+    };
+    const FlagOption<int> kBackends[] = {
+        { "cpu"            , kCPU_Backend },
+        { "skp"            , kSKP_Backend },
+        { "pdf"            , kPDF_Backend },
+        { "gl"             , GrContextFactory::kGL_ContextType },
+        { "gles"           , GrContextFactory::kGLES_ContextType },
+        { "angle_d3d9_es2" , GrContextFactory::kANGLE_D3D9_ES2_ContextType },
+        { "angle_d3d11_es2", GrContextFactory::kANGLE_D3D11_ES2_ContextType },
+        { "angle_d3d11_es3", GrContextFactory::kANGLE_D3D11_ES3_ContextType },
+        { "angle_gl_es2"   , GrContextFactory::kANGLE_GL_ES2_ContextType },
+        { "angle_gl_es3"   , GrContextFactory::kANGLE_GL_ES3_ContextType },
+        { "commandbuffer"  , GrContextFactory::kCommandBuffer_ContextType },
+        { "vk"             , GrContextFactory::kVulkan_ContextType },
+        { "mtl"            , GrContextFactory::kMetal_ContextType },
+        { "mock"           , GrContextFactory::kMock_ContextType },
+    };
+    const FlagOption<SkColorType> kColorTypes[] = {
+        { "a8",           kAlpha_8_SkColorType },
+        { "g8",            kGray_8_SkColorType },
+        { "565",          kRGB_565_SkColorType },
+        { "4444",       kARGB_4444_SkColorType },
+        { "8888",             kN32_SkColorType },
+        { "888x",        kRGB_888x_SkColorType },
+        { "1010102", kRGBA_1010102_SkColorType },
+        { "101010x",  kRGB_101010x_SkColorType },
+        { "f16norm", kRGBA_F16Norm_SkColorType },
+        { "f16",         kRGBA_F16_SkColorType },
+        { "f32",         kRGBA_F32_SkColorType },
+        { "rgba",       kRGBA_8888_SkColorType },
+        { "bgra",       kBGRA_8888_SkColorType },
+    };
+    const FlagOption<SkAlphaType> kAlphaTypes[] = {
+        {   "premul",   kPremul_SkAlphaType },
+        { "unpremul", kUnpremul_SkAlphaType },
+    };
+    const FlagOption<skcms_Matrix3x3> kGamuts[] = {
+        { "srgb",    SkNamedGamut::kSRGB },
+        { "p3",      SkNamedGamut::kDCIP3 },
+        { "rec2020", SkNamedGamut::kRec2020 },
+        { "adobe",   SkNamedGamut::kAdobeRGB },
+        { "narrow",  gNarrow_toXYZD50},
+    };
+    const FlagOption<skcms_TransferFunction> kTransferFunctions[] = {
+        { "srgb"   , SkNamedTransferFn::kSRGB },
+        { "rec2020", {2.22222f, 0.909672f, 0.0903276f, 0.222222f, 0.0812429f, 0, 0} },
+        { "2.2"    , SkNamedTransferFn::k2Dot2 },
+        { "linear" , SkNamedTransferFn::kLinear },
+    };
+
+
+    int                      backend;
+    SkColorType              ct;
+    SkAlphaType              at;
+    skcms_Matrix3x3          gamut;
+    skcms_TransferFunction   tf;
+
+    if (!parse_flag(FLAGS_backend, "backend", kBackends         , &backend) ||
+        !parse_flag(FLAGS_ct     , "ct"     , kColorTypes       , &ct)      ||
+        !parse_flag(FLAGS_at     , "at"     , kAlphaTypes       , &at)      ||
+        !parse_flag(FLAGS_gamut  , "gamut"  , kGamuts           , &gamut)   ||
+        !parse_flag(FLAGS_tf     , "tf"     , kTransferFunctions, &tf)) {
+        return 1;
+    }
+
+    sk_sp<SkColorSpace> cs = FLAGS_legacy ? nullptr
+                                          : SkColorSpace::MakeRGB(tf,gamut);
+    const SkImageInfo unsized_info = SkImageInfo::Make(0,0, ct,at,cs);
+
+    for (auto source : sources) {
+        const auto start = std::chrono::steady_clock::now();
+        fprintf(stdout, "%50s", source.name.c_str());
+
+        const SkImageInfo info = unsized_info.makeWH(source.size.width(),
+                                                     source.size.height());
+
+        auto draw = [&source](SkCanvas* canvas) {
+            Result result = source.draw(canvas);
+            switch (result.status) {
+                case Result::Ok:   break;
+                case Result::Skip: return false;
+                case Result::Fail:
+                    fprintf(stderr, "%s failed: %s\n", source.name.c_str(), result.failure.c_str());
+                    exit_with_failure();
+            }
+            return true;
+        };
+
+        GrContextOptions options = baseOptions;
+        source.tweak(&options);
+        GrContextFactory factory(options);  // N.B. factory must outlive image
+
+        sk_sp<SkImage> image;
+        sk_sp<SkData>  blob;
+        const char*    ext = ".png";
+        switch (backend) {
+            case kCPU_Backend:
+                image = draw_with_cpu(draw, info);
+                break;
+            case kSKP_Backend:
+                blob = draw_as_skp(draw, info);
+                ext  = ".skp";
+                break;
+            case kPDF_Backend:
+                blob = draw_as_pdf(draw, info, source.name);
+                ext  = ".pdf";
+                break;
+            default:
+                image = draw_with_gpu(draw, info, (GrContextFactory::ContextType)backend, &factory);
+                break;
+        }
+
+        if (!image && !blob) {
+            fprintf(stdout, "\tskipped\n");
+            continue;
+        }
+
+        SkBitmap bitmap;
+        if (image && !image->asLegacyBitmap(&bitmap)) {
+            fprintf(stderr, "SkImage::asLegacyBitmap() failed.\n");
+            exit_with_failure();
+        }
+
+        HashAndEncode hashAndEncode{bitmap};
+        SkString md5;
+        {
+            SkMD5 hash;
+            if (image) {
+                hashAndEncode.write(&hash);
+            } else {
+                hash.write(blob->data(), blob->size());
+            }
+
+            SkMD5::Digest digest = hash.finish();
+            for (int i = 0; i < 16; i++) {
+                md5.appendf("%02x", digest.data[i]);
+            }
+        }
+
+        if (!FLAGS_writePath.isEmpty()) {
+            sk_mkdir(FLAGS_writePath[0]);
+            SkString path = SkStringPrintf("%s/%s%s", FLAGS_writePath[0], source.name.c_str(), ext);
+
+            if (image) {
+                if (!hashAndEncode.writePngTo(path.c_str(), md5.c_str(),
+                                              FLAGS_key, FLAGS_properties)) {
+                    fprintf(stderr, "Could not write to %s.\n", path.c_str());
+                    exit_with_failure();
+                }
+            } else {
+                SkFILEWStream file(path.c_str());
+                file.write(blob->data(), blob->size());
+            }
+        }
+
+        const auto elapsed = std::chrono::steady_clock::now() - start;
+        fprintf(stdout, "\t%s\t%7dms\n",
+                md5.c_str(),
+                (int)std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count());
+    }
+
+    if (!FLAGS_writeShaders.isEmpty()) {
+        sk_mkdir(FLAGS_writeShaders[0]);
+        GrBackendApi api =
+                GrContextFactory::ContextTypeBackend((GrContextFactory::ContextType)backend);
+        memoryCache.writeShadersToDisk(FLAGS_writeShaders[0], api);
+
+    }
+
+    return 0;
+}
diff --git a/tools/fm/fm_bot.go b/tools/fm/fm_bot.go
new file mode 100644
index 0000000..808a432
--- /dev/null
+++ b/tools/fm/fm_bot.go
@@ -0,0 +1,296 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+package main
+
+import (
+	"bufio"
+	"bytes"
+	"flag"
+	"fmt"
+	"log"
+	"math/rand"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"strings"
+	"time"
+)
+
+// Too many GPU processes and we'll start to overwhelm your GPU,
+// even hanging your machine in the worst case.  Here's a reasonable default.
+func defaultGpuLimit() int {
+	limit := 8
+	if n := runtime.NumCPU(); n < limit {
+		return n
+	}
+	return limit
+}
+
+var script = flag.String("script", "", "A file with jobs to run, one per line. - for stdin.")
+var random = flag.Bool("random", true, "Assign sources into job batches randomly?")
+var quiet = flag.Bool("quiet", false, "Print only failures?")
+var exact = flag.Bool("exact", false, "Match GM names only exactly.")
+var cpuLimit = flag.Int("cpuLimit", runtime.NumCPU(),
+	"Maximum number of concurrent processes for CPU-bound work.")
+var gpuLimit = flag.Int("gpuLimit", defaultGpuLimit(),
+	"Maximum number of concurrent processes for GPU-bound work.")
+
+func init() {
+	flag.StringVar(script, "s", *script, "Alias for --script.")
+	flag.BoolVar(random, "r", *random, "Alias for --random.")
+	flag.BoolVar(quiet, "q", *quiet, "Alias for --quiet.")
+	flag.BoolVar(exact, "e", *exact, "Alias for --exact.")
+	flag.IntVar(cpuLimit, "c", *cpuLimit, "Alias for --cpuLimit.")
+	flag.IntVar(gpuLimit, "g", *gpuLimit, "Alias for --gpuLimit.")
+}
+
+func listAllGMs(fm string) (gms []string, err error) {
+	// Query fm binary for list of all available GMs by running with no arguments.
+	cmd := exec.Command(fm)
+	stdout, err := cmd.Output()
+	if err != nil {
+		return
+	}
+	// GM names are listed line-by-line.
+	scanner := bufio.NewScanner(bytes.NewReader(stdout))
+	for scanner.Scan() {
+		gms = append(gms, scanner.Text())
+	}
+	err = scanner.Err()
+	return
+}
+
+func callFM(fm string, sources []string, flags []string) bool {
+	start := time.Now()
+
+	args := flags[:]
+	args = append(args, "-s")
+	args = append(args, sources...)
+
+	cmd := exec.Command(fm, args...)
+	output, err := cmd.CombinedOutput()
+
+	if err != nil {
+		log.Printf("\n%v #failed (%v):\n%s\n", strings.Join(cmd.Args, " "), err, output)
+		return false
+	} else if !*quiet {
+		log.Printf("\n%v #done in %v:\n%s", strings.Join(cmd.Args, " "), time.Since(start), output)
+	}
+	return true
+}
+
+func sourcesAndFlags(args []string, gms []string) ([]string, []string, error) {
+	sources := []string{}
+	flags := []string{}
+	for _, arg := range args {
+		// I wish we could parse flags here too, but it's too late.
+		if strings.HasPrefix(arg, "-") {
+			msg := "Is '%s' an fm flag? If so please pass it using flag=value syntax."
+			if flag.Lookup(arg[1:]) != nil {
+				msg = "Please pass fm_bot flags like '%s' on the command line before the FM binary."
+			}
+			return nil, nil, fmt.Errorf(msg, arg)
+		}
+
+		// Everything after a # is a comment.
+		if strings.HasPrefix(arg, "#") {
+			break
+		}
+
+		// Treat "gm" or "gms" as a shortcut for all known GMs.
+		if arg == "gm" || arg == "gms" {
+			sources = append(sources, gms...)
+			continue
+		}
+
+		// Is this an option to pass through to fm?
+		if parts := strings.Split(arg, "="); len(parts) == 2 {
+			f := "-"
+			if len(parts[0]) > 1 {
+				f += "-"
+			}
+			f += parts[0]
+
+			flags = append(flags, f, parts[1])
+			continue
+		}
+
+		// Is this argument naming a GM?
+		matchedAnyGM := false
+		for _, gm := range gms {
+			if (*exact && gm == arg) || (!*exact && strings.Contains(gm, arg)) {
+				sources = append(sources, gm)
+				matchedAnyGM = true
+			}
+		}
+		if matchedAnyGM {
+			continue
+		}
+
+		// Anything left ought to be on the file system: a file, a directory, or a glob.
+		// Not all shells expand globs, so we'll do it here just in case.
+		matches, err := filepath.Glob(arg)
+		if err != nil {
+			return nil, nil, err
+		}
+		if len(matches) == 0 {
+			return nil, nil, fmt.Errorf("Don't understand '%s'.", arg)
+		}
+
+		for _, match := range matches {
+			err := filepath.Walk(match, func(path string, info os.FileInfo, err error) error {
+				if !info.IsDir() {
+					sources = append(sources, path)
+				}
+				return err
+			})
+			if err != nil {
+				return nil, nil, err
+			}
+		}
+	}
+	return sources, flags, nil
+}
+
+type work struct {
+	Sources []string
+	Flags   []string
+}
+
+func main() {
+	flag.Parse()
+
+	if flag.NArg() < 1 {
+		log.Fatal("Please pass an fm binary as the first argument.")
+	}
+	fm := flag.Args()[0]
+
+	gms, err := listAllGMs(fm)
+	if err != nil {
+		log.Fatalln("Could not query", fm, "for GMs:", err)
+	}
+
+	// One job can comes right on the command line,
+	// and any number can come one per line from -script.
+	jobs := [][]string{flag.Args()[1:]}
+	if *script != "" {
+		file := os.Stdin
+		if *script != "-" {
+			file, err = os.Open(*script)
+			if err != nil {
+				log.Fatal(err)
+			}
+			defer file.Close()
+		}
+
+		scanner := bufio.NewScanner(file)
+		for scanner.Scan() {
+			jobs = append(jobs, strings.Fields(scanner.Text()))
+		}
+		if err = scanner.Err(); err != nil {
+			log.Fatal(err)
+		}
+	}
+
+	// The buffer size of main->worker channels isn't super important...
+	// presumably we'll have many hungry goroutines snapping up work as quick
+	// as they can, and if things get backed up, no real reason for main to do
+	// anything but block.
+	cpu := make(chan work, *cpuLimit)
+	gpu := make(chan work, *gpuLimit)
+
+	// The buffer size of this worker->main results channel is much more
+	// sensitive.  Since it's a many->one funnel, it's easy for the workers to
+	// produce lots of results that main can't keep up with.
+	//
+	// This needlessly throttles our progress, and we can even deadlock if
+	// the buffer fills up before main has finished enqueueing all the work.
+	//
+	// So we set the buffer size here large enough to hold a result for every
+	// item we might possibly enqueue.
+	results := make(chan bool, (*cpuLimit+*gpuLimit)*len(jobs))
+
+	for i := 0; i < *cpuLimit; i++ {
+		go func() {
+			for w := range cpu {
+				results <- callFM(fm, w.Sources, w.Flags)
+			}
+		}()
+	}
+	for i := 0; i < *gpuLimit; i++ {
+		go func() {
+			for w := range gpu {
+				results <- callFM(fm, w.Sources, w.Flags)
+			}
+		}()
+	}
+
+	sent := 0
+	for _, job := range jobs {
+		// Skip blank lines, empty command lines.
+		if len(job) == 0 {
+			continue
+		}
+		sources, flags, err := sourcesAndFlags(job, gms)
+		if err != nil {
+			log.Fatal(err)
+		}
+
+		// Determine if this is CPU-bound or GPU-bound work, conservatively assuming GPU.
+		queue, limit := gpu, *gpuLimit
+		backend := ""
+		for i, flag := range flags {
+			if flag == "-b" || flag == "--backend" {
+				backend = flags[i+1]
+			}
+		}
+		whitelisted := map[string]bool{
+			"cpu": true,
+			"skp": true,
+			"pdf": true,
+		}
+		if whitelisted[backend] {
+			queue, limit = cpu, *cpuLimit
+		}
+
+		if *random {
+			rand.Shuffle(len(sources), func(i, j int) {
+				sources[i], sources[j] = sources[j], sources[i]
+			})
+		}
+
+		// Round up so there's at least one source per batch.
+		// This math also helps guarantee that sent stays <= cap(results).
+		sourcesPerBatch := (len(sources) + limit - 1) / limit
+
+		for i := 0; i < len(sources); i += sourcesPerBatch {
+			end := i + sourcesPerBatch
+			if end > len(sources) {
+				end = len(sources)
+			}
+			batch := sources[i:end]
+
+			queue <- work{batch, flags}
+
+			sent += 1
+		}
+	}
+	close(cpu)
+	close(gpu)
+
+	if sent > cap(results) {
+		log.Fatalf("Oops, we sent %d but cap(results) is only %d.  "+
+			"This could lead to deadlock and is a bug.", sent, cap(results))
+	}
+
+	failures := 0
+	for i := 0; i < sent; i++ {
+		if !<-results {
+			failures += 1
+		}
+	}
+	if failures > 0 {
+		log.Fatalln(failures, "invocations of", fm, "failed")
+	}
+}
diff --git a/tools/fonts/RandomScalerContext.cpp b/tools/fonts/RandomScalerContext.cpp
new file mode 100644
index 0000000..d20834c
--- /dev/null
+++ b/tools/fonts/RandomScalerContext.cpp
@@ -0,0 +1,229 @@
+/*
+ * 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 "RandomScalerContext.h"
+#include "SkAdvancedTypefaceMetrics.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkGlyph.h"
+#include "SkMakeUnique.h"
+#include "SkPath.h"
+#include "SkRectPriv.h"
+
+class SkDescriptor;
+
+class RandomScalerContext : public SkScalerContext {
+public:
+    RandomScalerContext(sk_sp<SkRandomTypeface>,
+                        const SkScalerContextEffects&,
+                        const SkDescriptor*,
+                        bool fFakeIt);
+
+protected:
+    unsigned generateGlyphCount() override;
+    uint16_t generateCharToGlyph(SkUnichar) override;
+    bool     generateAdvance(SkGlyph*) override;
+    void     generateMetrics(SkGlyph*) override;
+    void     generateImage(const SkGlyph&) override;
+    bool     generatePath(SkGlyphID, SkPath*) override;
+    void     generateFontMetrics(SkFontMetrics*) override;
+
+private:
+    SkRandomTypeface* getRandomTypeface() const {
+        return static_cast<SkRandomTypeface*>(this->getTypeface());
+    }
+    std::unique_ptr<SkScalerContext> fProxy;
+    bool                             fFakeIt;
+};
+
+RandomScalerContext::RandomScalerContext(sk_sp<SkRandomTypeface>       face,
+                                         const SkScalerContextEffects& effects,
+                                         const SkDescriptor*           desc,
+                                         bool                          fakeIt)
+        : SkScalerContext(std::move(face), effects, desc)
+        , fProxy(getRandomTypeface()->proxy()->createScalerContext(SkScalerContextEffects(), desc))
+        , fFakeIt(fakeIt) {
+    fProxy->forceGenerateImageFromPath();
+}
+
+unsigned RandomScalerContext::generateGlyphCount() { return fProxy->getGlyphCount(); }
+
+uint16_t RandomScalerContext::generateCharToGlyph(SkUnichar uni) {
+    return fProxy->charToGlyphID(uni);
+}
+
+bool RandomScalerContext::generateAdvance(SkGlyph* glyph) { return fProxy->generateAdvance(glyph); }
+
+void RandomScalerContext::generateMetrics(SkGlyph* glyph) {
+    // Here we will change the mask format of the glyph
+    // NOTE: this may be overridden by the base class (e.g. if a mask filter is applied).
+    switch (glyph->getGlyphID() % 4) {
+        case 0: glyph->fMaskFormat = SkMask::kLCD16_Format; break;
+        case 1: glyph->fMaskFormat = SkMask::kA8_Format; break;
+        case 2: glyph->fMaskFormat = SkMask::kARGB32_Format; break;
+        case 3: glyph->fMaskFormat = SkMask::kBW_Format; break;
+    }
+
+    fProxy->getMetrics(glyph);
+
+    if (fFakeIt || (glyph->getGlyphID() % 4) != 2) {
+        return;
+    }
+
+    SkPath path;
+    if (!fProxy->getPath(glyph->getPackedID(), &path)) {
+        return;
+    }
+    glyph->fMaskFormat = SkMask::kARGB32_Format;
+
+    SkRect         storage;
+    const SkPaint& paint = this->getRandomTypeface()->paint();
+    const SkRect&  newBounds =
+            paint.doComputeFastBounds(path.getBounds(), &storage, SkPaint::kFill_Style);
+    SkIRect ibounds;
+    newBounds.roundOut(&ibounds);
+    glyph->fLeft   = ibounds.fLeft;
+    glyph->fTop    = ibounds.fTop;
+    glyph->fWidth  = ibounds.width();
+    glyph->fHeight = ibounds.height();
+}
+
+void RandomScalerContext::generateImage(const SkGlyph& glyph) {
+    // TODO: can force down but not up
+    /*
+    SkMask::Format format = (SkMask::Format)glyph.fMaskFormat;
+    switch (glyph.getGlyphID() % 4) {
+        case 0: format = SkMask::kLCD16_Format; break;
+        case 1: format = SkMask::kA8_Format; break;
+        case 2: format = SkMask::kARGB32_Format; break;
+        case 3: format = SkMask::kBW_Format; break;
+    }
+    const_cast<SkGlyph&>(glyph).fMaskFormat = format;
+    */
+
+    if (fFakeIt) {
+        sk_bzero(glyph.fImage, glyph.computeImageSize());
+        return;
+    }
+
+    if (SkMask::kARGB32_Format != glyph.fMaskFormat) {
+        fProxy->getImage(glyph);
+        return;
+    }
+
+    // If the format is ARGB, just draw the glyph from path.
+    SkPath path;
+    if (!fProxy->getPath(glyph.getPackedID(), &path)) {
+        fProxy->getImage(glyph);
+        return;
+    }
+
+    SkBitmap bm;
+    bm.installPixels(SkImageInfo::MakeN32Premul(glyph.fWidth, glyph.fHeight),
+                     glyph.fImage,
+                     glyph.rowBytes());
+    bm.eraseColor(0);
+
+    SkCanvas canvas(bm);
+    canvas.translate(-SkIntToScalar(glyph.fLeft), -SkIntToScalar(glyph.fTop));
+    canvas.drawPath(path, this->getRandomTypeface()->paint());
+}
+
+bool RandomScalerContext::generatePath(SkGlyphID glyph, SkPath* path) {
+    return fProxy->generatePath(glyph, path);
+}
+
+void RandomScalerContext::generateFontMetrics(SkFontMetrics* metrics) {
+    fProxy->getFontMetrics(metrics);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkRandomTypeface::SkRandomTypeface(sk_sp<SkTypeface> proxy, const SkPaint& paint, bool fakeIt)
+        : SkTypeface(proxy->fontStyle(), false)
+        , fProxy(std::move(proxy))
+        , fPaint(paint)
+        , fFakeIt(fakeIt) {}
+
+SkScalerContext* SkRandomTypeface::onCreateScalerContext(const SkScalerContextEffects& effects,
+                                                         const SkDescriptor*           desc) const {
+    return new RandomScalerContext(
+            sk_ref_sp(const_cast<SkRandomTypeface*>(this)), effects, desc, fFakeIt);
+}
+
+void SkRandomTypeface::onFilterRec(SkScalerContextRec* rec) const {
+    fProxy->filterRec(rec);
+    rec->setHinting(kNo_SkFontHinting);
+    rec->fMaskFormat = SkMask::kARGB32_Format;
+}
+
+void SkRandomTypeface::getGlyphToUnicodeMap(SkUnichar* glyphToUnicode) const {
+    fProxy->getGlyphToUnicodeMap(glyphToUnicode);
+}
+
+std::unique_ptr<SkAdvancedTypefaceMetrics> SkRandomTypeface::onGetAdvancedMetrics() const {
+    return fProxy->getAdvancedMetrics();
+}
+
+std::unique_ptr<SkStreamAsset> SkRandomTypeface::onOpenStream(int* ttcIndex) const {
+    return fProxy->openStream(ttcIndex);
+}
+
+sk_sp<SkTypeface> SkRandomTypeface::onMakeClone(const SkFontArguments& args) const {
+    sk_sp<SkTypeface> proxy = fProxy->makeClone(args);
+    if (!proxy) {
+        return nullptr;
+    }
+    return sk_make_sp<SkRandomTypeface>(proxy, fPaint, fFakeIt);
+}
+
+void SkRandomTypeface::onGetFontDescriptor(SkFontDescriptor* desc, bool* isLocal) const {
+    // TODO: anything that uses this typeface isn't correctly serializable, since this typeface
+    // cannot be deserialized.
+    fProxy->getFontDescriptor(desc, isLocal);
+}
+
+int SkRandomTypeface::onCharsToGlyphs(const void* chars,
+                                      Encoding    encoding,
+                                      uint16_t    glyphs[],
+                                      int         glyphCount) const {
+    return fProxy->charsToGlyphs(chars, encoding, glyphs, glyphCount);
+}
+
+int SkRandomTypeface::onCountGlyphs() const { return fProxy->countGlyphs(); }
+
+int SkRandomTypeface::onGetUPEM() const { return fProxy->getUnitsPerEm(); }
+
+void SkRandomTypeface::onGetFamilyName(SkString* familyName) const {
+    fProxy->getFamilyName(familyName);
+}
+
+SkTypeface::LocalizedStrings* SkRandomTypeface::onCreateFamilyNameIterator() const {
+    return fProxy->createFamilyNameIterator();
+}
+
+int SkRandomTypeface::onGetVariationDesignPosition(
+        SkFontArguments::VariationPosition::Coordinate coordinates[],
+        int                                            coordinateCount) const {
+    return fProxy->onGetVariationDesignPosition(coordinates, coordinateCount);
+}
+
+int SkRandomTypeface::onGetVariationDesignParameters(SkFontParameters::Variation::Axis parameters[],
+                                                     int parameterCount) const {
+    return fProxy->onGetVariationDesignParameters(parameters, parameterCount);
+}
+
+int SkRandomTypeface::onGetTableTags(SkFontTableTag tags[]) const {
+    return fProxy->getTableTags(tags);
+}
+
+size_t SkRandomTypeface::onGetTableData(SkFontTableTag tag,
+                                        size_t         offset,
+                                        size_t         length,
+                                        void*          data) const {
+    return fProxy->getTableData(tag, offset, length, data);
+}
diff --git a/tools/fonts/RandomScalerContext.h b/tools/fonts/RandomScalerContext.h
new file mode 100644
index 0000000..ea1b655
--- /dev/null
+++ b/tools/fonts/RandomScalerContext.h
@@ -0,0 +1,59 @@
+/*
+ * 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 RandomScalerContext_DEFINED
+#define RandomScalerContext_DEFINED
+
+#include "SkScalerContext.h"
+#include "SkTypeface.h"
+
+/*
+ * This scaler context is for debug only purposes.  It will 'randomly' but deterministically return
+ * LCD / A8 / BW / RBGA masks based off of the Glyph ID
+ */
+
+class SkRandomTypeface : public SkTypeface {
+public:
+    SkRandomTypeface(sk_sp<SkTypeface> proxy, const SkPaint&, bool fakeit);
+
+    SkTypeface*    proxy() const { return fProxy.get(); }
+    const SkPaint& paint() const { return fPaint; }
+
+protected:
+    SkScalerContext*                           onCreateScalerContext(const SkScalerContextEffects&,
+                                                                     const SkDescriptor*) const override;
+    void                                       onFilterRec(SkScalerContextRec*) const override;
+    void                                       getGlyphToUnicodeMap(SkUnichar*) const override;
+    std::unique_ptr<SkAdvancedTypefaceMetrics> onGetAdvancedMetrics() const override;
+    std::unique_ptr<SkStreamAsset>             onOpenStream(int* ttcIndex) const override;
+    sk_sp<SkTypeface> onMakeClone(const SkFontArguments& args) const override;
+    void              onGetFontDescriptor(SkFontDescriptor*, bool* isLocal) const override;
+
+    int onCharsToGlyphs(const void* chars,
+                        Encoding    encoding,
+                        uint16_t    glyphs[],
+                        int         glyphCount) const override;
+    int onCountGlyphs() const override;
+    int onGetUPEM() const override;
+
+    void                          onGetFamilyName(SkString* familyName) const override;
+    SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const override;
+
+    int onGetVariationDesignPosition(SkFontArguments::VariationPosition::Coordinate coordinates[],
+                                     int coordinateCount) const override;
+    int onGetVariationDesignParameters(SkFontParameters::Variation::Axis parameters[],
+                                       int parameterCount) const override;
+    int onGetTableTags(SkFontTableTag tags[]) const override;
+    size_t onGetTableData(SkFontTableTag, size_t offset, size_t length, void* data) const override;
+
+private:
+    sk_sp<SkTypeface> fProxy;
+    SkPaint           fPaint;
+    bool              fFakeIt;
+};
+
+#endif
diff --git a/tools/fonts/SkRandomScalerContext.cpp b/tools/fonts/SkRandomScalerContext.cpp
deleted file mode 100644
index 7259a83..0000000
--- a/tools/fonts/SkRandomScalerContext.cpp
+++ /dev/null
@@ -1,234 +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 "SkAdvancedTypefaceMetrics.h"
-#include "SkBitmap.h"
-#include "SkCanvas.h"
-#include "SkGlyph.h"
-#include "SkMakeUnique.h"
-#include "SkPath.h"
-#include "SkRandomScalerContext.h"
-#include "SkRectPriv.h"
-
-class SkDescriptor;
-
-class SkRandomScalerContext : public SkScalerContext {
-public:
-    SkRandomScalerContext(sk_sp<SkRandomTypeface>, const SkScalerContextEffects&,
-                          const SkDescriptor*, bool fFakeIt);
-
-protected:
-    unsigned generateGlyphCount() override;
-    uint16_t generateCharToGlyph(SkUnichar) override;
-    bool generateAdvance(SkGlyph*) override;
-    void generateMetrics(SkGlyph*) override;
-    void generateImage(const SkGlyph&) override;
-    bool generatePath(SkGlyphID, SkPath*) override;
-    void generateFontMetrics(SkFontMetrics*) override;
-
-private:
-    SkRandomTypeface* getRandomTypeface() const {
-        return static_cast<SkRandomTypeface*>(this->getTypeface());
-    }
-    std::unique_ptr<SkScalerContext> fProxy;
-    bool fFakeIt;
-};
-
-SkRandomScalerContext::SkRandomScalerContext(sk_sp<SkRandomTypeface> face,
-                                             const SkScalerContextEffects& effects,
-                                             const SkDescriptor* desc,
-                                             bool fakeIt)
-        : SkScalerContext(std::move(face), effects, desc)
-        , fProxy(getRandomTypeface()->proxy()->createScalerContext(SkScalerContextEffects(), desc))
-        , fFakeIt(fakeIt)
-{
-    fProxy->forceGenerateImageFromPath();
-}
-
-unsigned SkRandomScalerContext::generateGlyphCount() {
-    return fProxy->getGlyphCount();
-}
-
-uint16_t SkRandomScalerContext::generateCharToGlyph(SkUnichar uni) {
-    return fProxy->charToGlyphID(uni);
-}
-
-bool SkRandomScalerContext::generateAdvance(SkGlyph* glyph) {
-    return fProxy->generateAdvance(glyph);
-}
-
-void SkRandomScalerContext::generateMetrics(SkGlyph* glyph) {
-    // Here we will change the mask format of the glyph
-    // NOTE: this may be overridden by the base class (e.g. if a mask filter is applied).
-    switch (glyph->getGlyphID() % 4) {
-        case 0: glyph->fMaskFormat = SkMask::kLCD16_Format; break;
-        case 1: glyph->fMaskFormat = SkMask::kA8_Format; break;
-        case 2: glyph->fMaskFormat = SkMask::kARGB32_Format; break;
-        case 3: glyph->fMaskFormat = SkMask::kBW_Format; break;
-    }
-
-    fProxy->getMetrics(glyph);
-
-    if (fFakeIt || (glyph->getGlyphID() % 4) != 2) {
-        return;
-    }
-
-    SkPath path;
-    if (!fProxy->getPath(glyph->getPackedID(), &path)) {
-        return;
-    }
-    glyph->fMaskFormat = SkMask::kARGB32_Format;
-
-    SkRect storage;
-    const SkPaint& paint = this->getRandomTypeface()->paint();
-    const SkRect& newBounds = paint.doComputeFastBounds(path.getBounds(),
-                                                        &storage,
-                                                        SkPaint::kFill_Style);
-    SkIRect ibounds;
-    newBounds.roundOut(&ibounds);
-    glyph->fLeft = ibounds.fLeft;
-    glyph->fTop = ibounds.fTop;
-    glyph->fWidth = ibounds.width();
-    glyph->fHeight = ibounds.height();
-}
-
-void SkRandomScalerContext::generateImage(const SkGlyph& glyph) {
-    // TODO: can force down but not up
-    /*
-    SkMask::Format format = (SkMask::Format)glyph.fMaskFormat;
-    switch (glyph.getGlyphID() % 4) {
-        case 0: format = SkMask::kLCD16_Format; break;
-        case 1: format = SkMask::kA8_Format; break;
-        case 2: format = SkMask::kARGB32_Format; break;
-        case 3: format = SkMask::kBW_Format; break;
-    }
-    const_cast<SkGlyph&>(glyph).fMaskFormat = format;
-    */
-
-    if (fFakeIt) {
-        sk_bzero(glyph.fImage, glyph.computeImageSize());
-        return;
-    }
-
-    if (SkMask::kARGB32_Format != glyph.fMaskFormat) {
-        fProxy->getImage(glyph);
-        return;
-    }
-
-    // If the format is ARGB, just draw the glyph from path.
-    SkPath path;
-    if (!fProxy->getPath(glyph.getPackedID(), &path)) {
-        fProxy->getImage(glyph);
-        return;
-    }
-
-    SkBitmap bm;
-    bm.installPixels(SkImageInfo::MakeN32Premul(glyph.fWidth, glyph.fHeight),
-                     glyph.fImage, glyph.rowBytes());
-    bm.eraseColor(0);
-
-    SkCanvas canvas(bm);
-    canvas.translate(-SkIntToScalar(glyph.fLeft), -SkIntToScalar(glyph.fTop));
-    canvas.drawPath(path, this->getRandomTypeface()->paint());
-}
-
-bool SkRandomScalerContext::generatePath(SkGlyphID glyph, SkPath* path) {
-    return fProxy->generatePath(glyph, path);
-}
-
-void SkRandomScalerContext::generateFontMetrics(SkFontMetrics* metrics) {
-    fProxy->getFontMetrics(metrics);
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-SkRandomTypeface::SkRandomTypeface(sk_sp<SkTypeface> proxy, const SkPaint& paint, bool fakeIt)
-    : SkTypeface(proxy->fontStyle(), false)
-    , fProxy(std::move(proxy))
-    , fPaint(paint)
-    , fFakeIt(fakeIt) {}
-
-SkScalerContext* SkRandomTypeface::onCreateScalerContext(const SkScalerContextEffects& effects,
-                                                         const SkDescriptor* desc) const {
-    return new SkRandomScalerContext(sk_ref_sp(const_cast<SkRandomTypeface*>(this)),
-                                     effects, desc, fFakeIt);
-}
-
-void SkRandomTypeface::onFilterRec(SkScalerContextRec* rec) const {
-    fProxy->filterRec(rec);
-    rec->setHinting(kNo_SkFontHinting);
-    rec->fMaskFormat = SkMask::kARGB32_Format;
-}
-
-void SkRandomTypeface::getGlyphToUnicodeMap(SkUnichar* glyphToUnicode) const {
-    fProxy->getGlyphToUnicodeMap(glyphToUnicode);
-}
-
-std::unique_ptr<SkAdvancedTypefaceMetrics> SkRandomTypeface::onGetAdvancedMetrics() const {
-    return fProxy->getAdvancedMetrics();
-}
-
-SkStreamAsset* SkRandomTypeface::onOpenStream(int* ttcIndex) const {
-    return fProxy->openStream(ttcIndex);
-}
-
-sk_sp<SkTypeface> SkRandomTypeface::onMakeClone(const SkFontArguments& args) const {
-    sk_sp<SkTypeface> proxy = fProxy->makeClone(args);
-    if (!proxy) {
-        return nullptr;
-    }
-    return sk_make_sp<SkRandomTypeface>(proxy, fPaint, fFakeIt);
-}
-
-void SkRandomTypeface::onGetFontDescriptor(SkFontDescriptor* desc, bool* isLocal) const {
-    // TODO: anything that uses this typeface isn't correctly serializable, since this typeface
-    // cannot be deserialized.
-    fProxy->getFontDescriptor(desc, isLocal);
-}
-
-int SkRandomTypeface::onCharsToGlyphs(const void* chars, Encoding encoding,
-                                 uint16_t glyphs[], int glyphCount) const {
-    return fProxy->charsToGlyphs(chars, encoding, glyphs, glyphCount);
-}
-
-int SkRandomTypeface::onCountGlyphs() const {
-    return fProxy->countGlyphs();
-}
-
-int SkRandomTypeface::onGetUPEM() const {
-    return fProxy->getUnitsPerEm();
-}
-
-void SkRandomTypeface::onGetFamilyName(SkString* familyName) const {
-    fProxy->getFamilyName(familyName);
-}
-
-SkTypeface::LocalizedStrings* SkRandomTypeface::onCreateFamilyNameIterator() const {
-    return fProxy->createFamilyNameIterator();
-}
-
-int SkRandomTypeface::onGetVariationDesignPosition(
-        SkFontArguments::VariationPosition::Coordinate coordinates[], int coordinateCount) const
-{
-    return fProxy->onGetVariationDesignPosition(coordinates, coordinateCount);
-}
-
-int SkRandomTypeface::onGetVariationDesignParameters(
-        SkFontParameters::Variation::Axis parameters[], int parameterCount) const
-{
-    return fProxy->onGetVariationDesignParameters(parameters, parameterCount);
-}
-
-int SkRandomTypeface::onGetTableTags(SkFontTableTag tags[]) const {
-    return fProxy->getTableTags(tags);
-}
-
-size_t SkRandomTypeface::onGetTableData(SkFontTableTag tag, size_t offset,
-                                    size_t length, void* data) const {
-    return fProxy->getTableData(tag, offset, length, data);
-}
-
diff --git a/tools/fonts/SkRandomScalerContext.h b/tools/fonts/SkRandomScalerContext.h
deleted file mode 100644
index 42f6095..0000000
--- a/tools/fonts/SkRandomScalerContext.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 SkRandomScalerContext_DEFINED
-#define SkRandomScalerContext_DEFINED
-
-#include "SkScalerContext.h"
-#include "SkTypeface.h"
-
-/*
- * This scaler context is for debug only purposes.  It will 'randomly' but deterministically return
- * LCD / A8 / BW / RBGA masks based off of the Glyph ID
- */
-
-class SkRandomTypeface : public SkTypeface {
-public:
-    SkRandomTypeface(sk_sp<SkTypeface> proxy, const SkPaint&, bool fakeit);
-
-    SkTypeface* proxy() const { return fProxy.get(); }
-    const SkPaint& paint() const { return fPaint; }
-
-protected:
-    SkScalerContext* onCreateScalerContext(const SkScalerContextEffects&,
-                                           const SkDescriptor*) const override;
-    void onFilterRec(SkScalerContextRec*) const override;
-    void getGlyphToUnicodeMap(SkUnichar*) const override;
-    std::unique_ptr<SkAdvancedTypefaceMetrics> onGetAdvancedMetrics() const override;
-    SkStreamAsset* onOpenStream(int* ttcIndex) const override;
-    sk_sp<SkTypeface> onMakeClone(const SkFontArguments& args) const override;
-    void onGetFontDescriptor(SkFontDescriptor*, bool* isLocal) const override;
-
-    int onCharsToGlyphs(const void* chars, Encoding encoding,
-                        uint16_t glyphs[], int glyphCount) const override;
-    int onCountGlyphs() const override;
-    int onGetUPEM() const override;
-
-    void onGetFamilyName(SkString* familyName) const override;
-    SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const override;
-
-    int onGetVariationDesignPosition(SkFontArguments::VariationPosition::Coordinate coordinates[],
-                                     int coordinateCount) const override;
-    int onGetVariationDesignParameters(SkFontParameters::Variation::Axis parameters[],
-                                       int parameterCount) const override;
-    int onGetTableTags(SkFontTableTag tags[]) const override;
-    size_t onGetTableData(SkFontTableTag, size_t offset,
-                          size_t length, void* data) const override;
-
-private:
-    sk_sp<SkTypeface>   fProxy;
-    SkPaint             fPaint;
-    bool                fFakeIt;
-};
-
-#endif
diff --git a/tools/fonts/SkTestEmptyTypeface.h b/tools/fonts/SkTestEmptyTypeface.h
deleted file mode 100644
index 51ce6a6..0000000
--- a/tools/fonts/SkTestEmptyTypeface.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkTestEmptyTypeface_DEFINED
-#define SkTestEmptyTypeface_DEFINED
-
-#include "SkTypeface.h"
-
-class SkTestEmptyTypeface : public SkTypeface {
-public:
-    static sk_sp<SkTypeface> Make() { return sk_sp<SkTypeface>(new SkTestEmptyTypeface); }
-protected:
-    SkTestEmptyTypeface() : SkTypeface(SkFontStyle(), true) { }
-
-    SkStreamAsset* onOpenStream(int* ttcIndex) const override { return nullptr; }
-    sk_sp<SkTypeface> onMakeClone(const SkFontArguments& args) const override {
-        return sk_ref_sp(this);
-    }
-    SkScalerContext* onCreateScalerContext(const SkScalerContextEffects&,
-                                           const SkDescriptor*) const override {
-        return nullptr;
-    }
-    void onFilterRec(SkScalerContextRec*) const override { }
-    std::unique_ptr<SkAdvancedTypefaceMetrics> onGetAdvancedMetrics() const override {
-        return nullptr;
-    }
-    void onGetFontDescriptor(SkFontDescriptor*, bool*) const override { }
-    virtual int onCharsToGlyphs(const void* chars, Encoding encoding,
-                                uint16_t glyphs[], int glyphCount) const override {
-        if (glyphs && glyphCount > 0) {
-            sk_bzero(glyphs, glyphCount * sizeof(glyphs[0]));
-        }
-        return 0;
-    }
-    int onCountGlyphs() const override { return 0; }
-    int onGetUPEM() const override { return 0; }
-    class EmptyLocalizedStrings : public SkTypeface::LocalizedStrings {
-    public:
-        bool next(SkTypeface::LocalizedString*) override { return false; }
-    };
-    void onGetFamilyName(SkString* familyName) const override {
-        familyName->reset();
-    }
-    SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const override {
-        return new EmptyLocalizedStrings;
-    }
-    int onGetVariationDesignPosition(SkFontArguments::VariationPosition::Coordinate coordinates[],
-                                     int coordinateCount) const override
-    {
-        return 0;
-    }
-    int onGetVariationDesignParameters(SkFontParameters::Variation::Axis parameters[],
-                                       int parameterCount) const override
-    {
-        return 0;
-    }
-    int onGetTableTags(SkFontTableTag tags[]) const override { return 0; }
-    size_t onGetTableData(SkFontTableTag, size_t, size_t, void*) const override {
-        return 0;
-    }
-};
-
-
-#endif  // SkTestEmptyTypeface_DEFINED
diff --git a/tools/fonts/SkTestFontMgr.cpp b/tools/fonts/SkTestFontMgr.cpp
deleted file mode 100644
index f902e12..0000000
--- a/tools/fonts/SkTestFontMgr.cpp
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkFontDescriptor.h"
-#include "SkTestFontMgr.h"
-#include "SkTestTypeface.h"
-#include "sk_tool_utils.h"
-
-#ifdef SK_XML
-#include "SkTestSVGTypeface.h"
-#endif
-
-#include <vector>
-
-namespace {
-
-#include "test_font_monospace.inc"
-#include "test_font_sans_serif.inc"
-#include "test_font_serif.inc"
-#include "test_font_index.inc"
-
-class FontStyleSet final : public SkFontStyleSet {
-public:
-    FontStyleSet(const char* familyName) : fFamilyName(familyName) { }
-    struct TypefaceEntry {
-        TypefaceEntry(sk_sp<SkTypeface> typeface, SkFontStyle style, const char* styleName)
-            : fTypeface(std::move(typeface))
-            , fStyle(style)
-            , fStyleName(styleName)
-        {}
-        sk_sp<SkTypeface> fTypeface;
-        SkFontStyle fStyle;
-        const char* fStyleName;
-    };
-
-    int count() override { return fTypefaces.size(); }
-
-    void getStyle(int index, SkFontStyle* style, SkString* name) override {
-        if (style) { *style = fTypefaces[index].fStyle; }
-        if (name) { *name = fTypefaces[index].fStyleName; }
-    }
-
-    SkTypeface* createTypeface(int index) override {
-        return SkRef(fTypefaces[index].fTypeface.get());
-    }
-
-    SkTypeface* matchStyle(const SkFontStyle& pattern) override {
-        return this->matchStyleCSS3(pattern);
-    }
-
-    SkString getFamilyName() { return fFamilyName; }
-
-    std::vector<TypefaceEntry> fTypefaces;
-    SkString fFamilyName;
-};
-
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
-
-class FontMgr final : public SkFontMgr {
-public:
-    FontMgr() {
-        for (const auto& sub : gSubFonts) {
-            sk_sp<SkTestTypeface> typeface =
-                    sk_make_sp<SkTestTypeface>(sk_make_sp<SkTestFont>(sub.fFont), sub.fStyle);
-            bool defaultFamily = false;
-            if (&sub - gSubFonts == gDefaultFontIndex) {
-                defaultFamily = true;
-                fDefaultTypeface = typeface;
-            }
-            bool found = false;
-            for (const auto& family : fFamilies) {
-                if (family->getFamilyName().equals(sub.fFamilyName)) {
-                    family->fTypefaces.emplace_back(std::move(typeface), sub.fStyle, sub.fStyleName);
-                    found = true;
-                    if (defaultFamily) {
-                        fDefaultFamily = family;
-                    }
-                    break;
-                }
-            }
-            if (!found) {
-                fFamilies.emplace_back(sk_make_sp<FontStyleSet>(sub.fFamilyName));
-                // NOLINTNEXTLINE(bugprone-use-after-move)
-                fFamilies.back()->fTypefaces.emplace_back(std::move(typeface), sub.fStyle, sub.fStyleName);
-                if (defaultFamily) {
-                    fDefaultFamily = fFamilies.back();
-                }
-            }
-        }
-        #ifdef SK_XML
-        fFamilies.emplace_back(sk_make_sp<FontStyleSet>("Emoji"));
-        fFamilies.back()->fTypefaces.emplace_back(SkTestSVGTypeface::Default(), SkFontStyle::Normal(), "Normal");
-        #endif
-    }
-
-    int onCountFamilies() const override { return fFamilies.size(); }
-
-    void onGetFamilyName(int index, SkString* familyName) const override {
-        *familyName = fFamilies[index]->getFamilyName();
-    }
-
-    SkFontStyleSet* onCreateStyleSet(int index) const override {
-        sk_sp<SkFontStyleSet> ref = fFamilies[index];
-        return ref.release();
-    }
-
-    SkFontStyleSet* onMatchFamily(const char familyName[]) const override {
-        if (familyName) {
-            if (strstr(familyName,  "ono")) { return this->createStyleSet(0); }
-            if (strstr(familyName,  "ans")) { return this->createStyleSet(1); }
-            if (strstr(familyName, "erif")) { return this->createStyleSet(2); }
-            #ifdef SK_XML
-            if (strstr(familyName,  "oji")) { return this->createStyleSet(6); }
-            #endif
-        }
-        return nullptr;
-    }
-
-
-    SkTypeface* onMatchFamilyStyle(const char familyName[],
-                                   const SkFontStyle& style) const override {
-        sk_sp<SkFontStyleSet> styleSet(this->matchFamily(familyName));
-        return styleSet->matchStyle(style);
-    }
-
-    SkTypeface* onMatchFamilyStyleCharacter(const char familyName[],
-                                            const SkFontStyle& style,
-                                            const char* bcp47[], int bcp47Count,
-                                            SkUnichar character) const override {
-        (void)bcp47;
-        (void)bcp47Count;
-        (void)character;
-        return this->matchFamilyStyle(familyName, style);
-    }
-
-    SkTypeface* onMatchFaceStyle(const SkTypeface* tf,
-                                 const SkFontStyle& style) const override {
-        SkString familyName;
-        tf->getFamilyName(&familyName);
-        return this->matchFamilyStyle(familyName.c_str(), style);
-    }
-
-    sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int ttcIndex) const override {
-        return nullptr;
-    }
-    sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>,
-                                            int ttcIndex) const override {
-        return nullptr;
-    }
-    sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,
-                                           const SkFontArguments&) const override {
-        return nullptr;
-    }
-    sk_sp<SkTypeface> onMakeFromFontData(std::unique_ptr<SkFontData>) const override {
-        return nullptr;
-    }
-    sk_sp<SkTypeface> onMakeFromFile(const char path[], int ttcIndex) const override {
-        return nullptr;
-    }
-
-    sk_sp<SkTypeface> onLegacyMakeTypeface(const char familyName[],
-                                           SkFontStyle style) const override {
-        if (familyName == nullptr) {
-            return sk_sp<SkTypeface>(fDefaultFamily->matchStyle(style));
-        }
-        sk_sp<SkTypeface> typeface = sk_sp<SkTypeface>(this->matchFamilyStyle(familyName, style));
-        if (!typeface) {
-            typeface = fDefaultTypeface;
-        }
-        return typeface;
-    }
-
-private:
-    std::vector<sk_sp<FontStyleSet>> fFamilies;
-    sk_sp<FontStyleSet> fDefaultFamily;
-    sk_sp<SkTypeface> fDefaultTypeface;
-};
-}
-
-namespace sk_tool_utils {
-sk_sp<SkFontMgr> MakePortableFontMgr() { return sk_make_sp<FontMgr>(); }
-} // namespace sk_tool_utils
diff --git a/tools/fonts/SkTestFontMgr.h b/tools/fonts/SkTestFontMgr.h
deleted file mode 100644
index 6f4bb9c..0000000
--- a/tools/fonts/SkTestFontMgr.h
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkTestFontMgr_DEFINED
-#define SkTestFontMgr_DEFINED
-
-#include "SkFontMgr.h"
-
-// An SkFontMgr that always uses sk_tool_utils::create_portable_typeface().
-
-namespace sk_tool_utils {
-    sk_sp<SkFontMgr> MakePortableFontMgr();
-}  // namespace sk_tool_utils
-
-#endif  //SkTestFontMgr_DEFINED
diff --git a/tools/fonts/SkTestSVGTypeface.cpp b/tools/fonts/SkTestSVGTypeface.cpp
deleted file mode 100644
index 4d3da58..0000000
--- a/tools/fonts/SkTestSVGTypeface.cpp
+++ /dev/null
@@ -1,1304 +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 "SkTestSVGTypeface.h"
-
-#ifdef SK_XML
-
-#include "Resources.h"
-#include "SkAdvancedTypefaceMetrics.h"
-#include "SkBitmap.h"
-#include "SkCanvas.h"
-#include "SkColor.h"
-#include "SkData.h"
-#include "SkEncodedImageFormat.h"
-#include "SkFontDescriptor.h"
-#include "SkFontPriv.h"
-#include "SkFontStyle.h"
-#include "SkGeometry.h"
-#include "SkGlyph.h"
-#include "SkImage.h"
-#include "SkImageInfo.h"
-#include "SkMask.h"
-#include "SkMatrix.h"
-#include "SkNoDrawCanvas.h"
-#include "SkOTUtils.h"
-#include "SkPaintPriv.h"
-#include "SkPath.h"
-#include "SkPathPriv.h"
-#include "SkPathEffect.h"
-#include "SkPathOps.h"
-#include "SkPixmap.h"
-#include "SkPointPriv.h"
-#include "SkRRect.h"
-#include "SkSVGDOM.h"
-#include "SkScalerContext.h"
-#include "SkSize.h"
-#include "SkStream.h"
-#include "SkSurface.h"
-#include "SkTDArray.h"
-#include "SkTemplates.h"
-#include "SkUtils.h"
-
-#include <utility>
-
-class SkDescriptor;
-
-SkTestSVGTypeface::SkTestSVGTypeface(const char* name,
-                                     int upem,
-                                     const SkFontMetrics& fontMetrics,
-                                     const SkSVGTestTypefaceGlyphData* data, int dataCount,
-                                     const SkFontStyle& style)
-    : SkTypeface(style, false)
-    , fName(name)
-    , fUpem(upem)
-    , fFontMetrics(fontMetrics)
-    , fGlyphs(new Glyph[dataCount])
-    , fGlyphCount(dataCount)
-{
-    for (int i = 0; i < dataCount; ++i) {
-        const SkSVGTestTypefaceGlyphData& datum = data[i];
-        std::unique_ptr<SkStreamAsset> stream = GetResourceAsStream(datum.fSvgResourcePath);
-        fCMap.set(datum.fUnicode, i);
-        fGlyphs[i].fAdvance = datum.fAdvance;
-        fGlyphs[i].fOrigin = datum.fOrigin;
-        if (!stream) {
-            continue;
-        }
-        sk_sp<SkSVGDOM> svg = SkSVGDOM::MakeFromStream(*stream.get());
-        if (!svg) {
-            continue;
-        }
-
-        const SkSize& sz = svg->containerSize();
-        if (sz.isEmpty()) {
-            continue;
-        }
-
-        fGlyphs[i].fSvg = std::move(svg);
-    }
-}
-
-SkTestSVGTypeface::~SkTestSVGTypeface() {}
-
-SkTestSVGTypeface::Glyph::Glyph() : fOrigin{0,0}, fAdvance(0) {}
-SkTestSVGTypeface::Glyph::~Glyph() {}
-
-void SkTestSVGTypeface::getAdvance(SkGlyph* glyph) const {
-    SkGlyphID glyphID = glyph->getGlyphID();
-    glyphID = glyphID < fGlyphCount ? glyphID : 0;
-
-    glyph->fAdvanceX = fGlyphs[glyphID].fAdvance;
-    glyph->fAdvanceY = 0;
-}
-
-void SkTestSVGTypeface::getFontMetrics(SkFontMetrics* metrics) const {
-    *metrics = fFontMetrics;
-}
-
-void SkTestSVGTypeface::onFilterRec(SkScalerContextRec* rec) const {
-    rec->setHinting(kNo_SkFontHinting);
-}
-
-void SkTestSVGTypeface::getGlyphToUnicodeMap(SkUnichar* glyphToUnicode) const {
-    SkDEBUGCODE(unsigned glyphCount = this->countGlyphs());
-    fCMap.foreach([=](const SkUnichar& c, const SkGlyphID& g) {
-        SkASSERT(g < glyphCount);
-        glyphToUnicode[g] = c;
-    });
-}
-
-std::unique_ptr<SkAdvancedTypefaceMetrics> SkTestSVGTypeface::onGetAdvancedMetrics() const {
-    std::unique_ptr<SkAdvancedTypefaceMetrics> info(new SkAdvancedTypefaceMetrics);
-    info->fFontName = fName;
-    return info;
-}
-
-void SkTestSVGTypeface::onGetFontDescriptor(SkFontDescriptor* desc, bool* isLocal) const {
-    desc->setFamilyName(fName.c_str());
-    desc->setStyle(this->fontStyle());
-    *isLocal = false;
-}
-
-int SkTestSVGTypeface::onCharsToGlyphs(const void* chars, Encoding encoding,
-                                    uint16_t glyphs[], int glyphCount) const {
-    auto utf8  = (const      char*)chars;
-    auto utf16 = (const  uint16_t*)chars;
-    auto utf32 = (const SkUnichar*)chars;
-
-    for (int i = 0; i < glyphCount; i++) {
-        SkUnichar ch;
-        switch (encoding) {
-            case kUTF8_Encoding:  ch =  SkUTF8_NextUnichar(&utf8 ); break;
-            case kUTF16_Encoding: ch = SkUTF16_NextUnichar(&utf16); break;
-            case kUTF32_Encoding: ch =                    *utf32++; break;
-        }
-        if (glyphs) {
-            SkGlyphID* g = fCMap.find(ch);
-            glyphs[i] = g ? *g : 0;
-        }
-    }
-    return glyphCount;
-}
-
-void SkTestSVGTypeface::onGetFamilyName(SkString* familyName) const {
-    *familyName = fName;
-}
-
-SkTypeface::LocalizedStrings* SkTestSVGTypeface::onCreateFamilyNameIterator() const {
-    SkString familyName(fName);
-    SkString language("und"); //undetermined
-    return new SkOTUtils::LocalizedStrings_SingleName(familyName, language);
-}
-
-class SkTestSVGScalerContext : public SkScalerContext {
-public:
-    SkTestSVGScalerContext(sk_sp<SkTestSVGTypeface> face, const SkScalerContextEffects& effects,
-                           const SkDescriptor* desc)
-        : SkScalerContext(std::move(face), effects, desc)
-    {
-        fRec.getSingleMatrix(&fMatrix);
-        SkScalar upem = this->getTestSVGTypeface()->fUpem;
-        fMatrix.preScale(1.f/upem, 1.f/upem);
-    }
-
-protected:
-    SkTestSVGTypeface* getTestSVGTypeface() const {
-        return static_cast<SkTestSVGTypeface*>(this->getTypeface());
-    }
-
-    unsigned generateGlyphCount() override {
-        return this->getTestSVGTypeface()->countGlyphs();
-    }
-
-    uint16_t generateCharToGlyph(SkUnichar u) override {
-        return this->getTestSVGTypeface()->unicharToGlyph(u);
-    }
-
-    bool generateAdvance(SkGlyph* glyph) override {
-        this->getTestSVGTypeface()->getAdvance(glyph);
-
-        const SkVector advance = fMatrix.mapXY(SkFloatToScalar(glyph->fAdvanceX),
-                                               SkFloatToScalar(glyph->fAdvanceY));
-        glyph->fAdvanceX = SkScalarToFloat(advance.fX);
-        glyph->fAdvanceY = SkScalarToFloat(advance.fY);
-        return true;
-    }
-
-    void generateMetrics(SkGlyph* glyph) override {
-        SkGlyphID glyphID = glyph->getGlyphID();
-        glyphID = glyphID < this->getTestSVGTypeface()->fGlyphCount ? glyphID : 0;
-
-        glyph->zeroMetrics();
-        glyph->fMaskFormat = SkMask::kARGB32_Format;
-        this->generateAdvance(glyph);
-
-        SkTestSVGTypeface::Glyph& glyphData = this->getTestSVGTypeface()->fGlyphs[glyphID];
-        if (!glyphData.fSvg) {
-            return;
-        }
-
-        SkSize containerSize = glyphData.fSvg->containerSize();
-        SkRect newBounds = SkRect::MakeXYWH(glyphData.fOrigin.fX, -glyphData.fOrigin.fY,
-                                            containerSize.fWidth, containerSize.fHeight);
-        fMatrix.mapRect(&newBounds);
-        SkScalar dx = SkFixedToScalar(glyph->getSubXFixed());
-        SkScalar dy = SkFixedToScalar(glyph->getSubYFixed());
-        newBounds.offset(dx, dy);
-
-        SkIRect ibounds;
-        newBounds.roundOut(&ibounds);
-        glyph->fLeft = ibounds.fLeft;
-        glyph->fTop = ibounds.fTop;
-        glyph->fWidth = ibounds.width();
-        glyph->fHeight = ibounds.height();
-    }
-
-    void generateImage(const SkGlyph& glyph) override {
-        SkGlyphID glyphID = glyph.getGlyphID();
-        glyphID = glyphID < this->getTestSVGTypeface()->fGlyphCount ? glyphID : 0;
-
-        SkBitmap bm;
-        // TODO: this should be SkImageInfo::MakeS32 when that passes all the tests.
-        bm.installPixels(SkImageInfo::MakeN32(glyph.fWidth, glyph.fHeight, kPremul_SkAlphaType),
-                         glyph.fImage, glyph.rowBytes());
-        bm.eraseColor(0);
-
-        SkTestSVGTypeface::Glyph& glyphData = this->getTestSVGTypeface()->fGlyphs[glyphID];
-
-        SkScalar dx = SkFixedToScalar(glyph.getSubXFixed());
-        SkScalar dy = SkFixedToScalar(glyph.getSubYFixed());
-
-        SkCanvas canvas(bm);
-        canvas.translate(-glyph.fLeft, -glyph.fTop);
-        canvas.translate(dx, dy);
-        canvas.concat(fMatrix);
-        canvas.translate(glyphData.fOrigin.fX, -glyphData.fOrigin.fY);
-
-        if (glyphData.fSvg) {
-            SkAutoExclusive lock(glyphData.fSvgMutex);
-            glyphData.fSvg->render(&canvas);
-        }
-    }
-
-    bool generatePath(SkGlyphID glyph, SkPath* path) override {
-        path->reset();
-        return false;
-    }
-
-    void generateFontMetrics(SkFontMetrics* metrics) override {
-        this->getTestSVGTypeface()->getFontMetrics(metrics);
-        SkFontPriv::ScaleFontMetrics(metrics, fMatrix.getScaleY());
-    }
-
-private:
-    SkMatrix fMatrix;
-};
-
-SkScalerContext* SkTestSVGTypeface::onCreateScalerContext(
-    const SkScalerContextEffects& e, const SkDescriptor* desc) const
-{
-    return new SkTestSVGScalerContext(sk_ref_sp(const_cast<SkTestSVGTypeface*>(this)), e, desc);
-}
-
-// Recommended that the first four be .notdef, .null, CR, space
-constexpr const static SkSVGTestTypefaceGlyphData gGlyphs[] = {
-    {"fonts/svg/notdef.svg", {100,800}, 800, 0x0}, // .notdef
-    {"fonts/svg/empty.svg", {0,0}, 800, 0x0020}, // space
-    {"fonts/svg/diamond.svg", {100, 800}, 800, 0x2662}, // ♢
-    {"fonts/svg/smile.svg", {0,800}, 800, 0x1F600}, // 😀
-};
-
-sk_sp<SkTestSVGTypeface> SkTestSVGTypeface::Default() {
-    SkFontMetrics metrics;
-    metrics.fFlags = SkFontMetrics::kUnderlineThicknessIsValid_Flag |
-                     SkFontMetrics::kUnderlinePositionIsValid_Flag  |
-                     SkFontMetrics::kStrikeoutThicknessIsValid_Flag |
-                     SkFontMetrics::kStrikeoutPositionIsValid_Flag;
-    metrics.fTop = -800;
-    metrics.fAscent = -800;
-    metrics.fDescent = 200;
-    metrics.fBottom = 200;
-    metrics.fLeading = 100;
-    metrics.fAvgCharWidth = 1000;
-    metrics.fMaxCharWidth = 1000;
-    metrics.fXMin = 0;
-    metrics.fXMax = 1000;
-    metrics.fXHeight = 500;
-    metrics.fCapHeight = 700;
-    metrics.fUnderlineThickness = 40;
-    metrics.fUnderlinePosition = 20;
-    metrics.fStrikeoutThickness = 20;
-    metrics.fStrikeoutPosition = -400;
-    return sk_make_sp<SkTestSVGTypeface>("Emoji", 1000, metrics, gGlyphs, SK_ARRAY_COUNT(gGlyphs),
-                                         SkFontStyle::Normal());
-}
-
-void SkTestSVGTypeface::exportTtxCommon(SkWStream* out, const char* type,
-                                        const SkTArray<GlyfInfo>* glyfInfo) const
-{
-    int totalGlyphs = fGlyphCount;
-    out->writeText("  <GlyphOrder>\n");
-    for (int i = 0; i < fGlyphCount; ++i) {
-        out->writeText("    <GlyphID name=\"glyf");
-        out->writeHexAsText(i, 4);
-        out->writeText("\"/>\n");
-    }
-    if (glyfInfo) {
-        for (int i = 0; i < fGlyphCount; ++i) {
-            for (int j = 0; j < (*glyfInfo)[i].fLayers.count(); ++j) {
-                out->writeText("    <GlyphID name=\"glyf");
-                    out->writeHexAsText(i, 4);
-                    out->writeText("l");
-                    out->writeHexAsText(j, 4);
-                    out->writeText("\"/>\n");
-                ++totalGlyphs;
-            }
-        }
-    }
-    out->writeText("  </GlyphOrder>\n");
-
-    out->writeText("  <head>\n");
-    out->writeText("    <tableVersion value=\"1.0\"/>\n");
-    out->writeText("    <fontRevision value=\"1.0\"/>\n");
-    out->writeText("    <checkSumAdjustment value=\"0xa9c3274\"/>\n");
-    out->writeText("    <magicNumber value=\"0x5f0f3cf5\"/>\n");
-    out->writeText("    <flags value=\"00000000 00011011\"/>\n");
-    out->writeText("    <unitsPerEm value=\"");
-        out->writeDecAsText(fUpem);
-        out->writeText("\"/>\n");
-    out->writeText("    <created value=\"Thu Feb 15 12:55:49 2018\"/>\n");
-    out->writeText("    <modified value=\"Thu Feb 15 12:55:49 2018\"/>\n");
-    // TODO: not recalculated for bitmap fonts?
-    out->writeText("    <xMin value=\"");
-        out->writeScalarAsText(fFontMetrics.fXMin);
-        out->writeText("\"/>\n");
-    out->writeText("    <yMin value=\"");
-        out->writeScalarAsText(-fFontMetrics.fBottom);
-        out->writeText("\"/>\n");
-    out->writeText("    <xMax value=\"");
-        out->writeScalarAsText(fFontMetrics.fXMax);
-        out->writeText("\"/>\n");
-    out->writeText("    <yMax value=\"");
-        out->writeScalarAsText(-fFontMetrics.fTop);
-        out->writeText("\"/>\n");
-
-    char macStyle[16] = {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
-    if (this->fontStyle().weight() >= SkFontStyle::Bold().weight()) {
-        macStyle[0xF - 0x0] = '1'; // Bold
-    }
-    switch (this->fontStyle().slant()) {
-        case SkFontStyle::kUpright_Slant:
-            break;
-        case SkFontStyle::kItalic_Slant:
-            macStyle[0xF - 0x1] = '1'; // Italic
-            break;
-        case SkFontStyle::kOblique_Slant:
-            macStyle[0xF - 0x1] = '1'; // Italic
-            break;
-        default:
-            SK_ABORT("Unknown slant.");
-    }
-    if (this->fontStyle().width() <= SkFontStyle::kCondensed_Width) {
-        macStyle[0xF - 0x5] = '1'; // Condensed
-    } else if (this->fontStyle().width() >= SkFontStyle::kExpanded_Width) {
-        macStyle[0xF - 0x6] = '1'; // Extended
-    }
-    out->writeText("    <macStyle value=\"");
-        out->write(macStyle, 8);
-        out->writeText(" ");
-        out->write(macStyle + 8, 8);
-        out->writeText("\"/>\n");
-    out->writeText("    <lowestRecPPEM value=\"8\"/>\n");
-    out->writeText("    <fontDirectionHint value=\"2\"/>\n");
-    out->writeText("    <indexToLocFormat value=\"0\"/>\n");
-    out->writeText("    <glyphDataFormat value=\"0\"/>\n");
-    out->writeText("  </head>\n");
-
-    out->writeText("  <hhea>\n");
-    out->writeText("    <tableVersion value=\"0x00010000\"/>\n");
-    out->writeText("    <ascent value=\"");
-        out->writeDecAsText(-fFontMetrics.fAscent);
-        out->writeText("\"/>\n");
-    out->writeText("    <descent value=\"");
-        out->writeDecAsText(-fFontMetrics.fDescent);
-        out->writeText("\"/>\n");
-    out->writeText("    <lineGap value=\"");
-        out->writeDecAsText(fFontMetrics.fLeading);
-        out->writeText("\"/>\n");
-    out->writeText("    <advanceWidthMax value=\"0\"/>\n");
-    out->writeText("    <minLeftSideBearing value=\"0\"/>\n");
-    out->writeText("    <minRightSideBearing value=\"0\"/>\n");
-    out->writeText("    <xMaxExtent value=\"");
-        out->writeScalarAsText(fFontMetrics.fXMax - fFontMetrics.fXMin);
-        out->writeText("\"/>\n");
-    out->writeText("    <caretSlopeRise value=\"1\"/>\n");
-    out->writeText("    <caretSlopeRun value=\"0\"/>\n");
-    out->writeText("    <caretOffset value=\"0\"/>\n");
-    out->writeText("    <reserved0 value=\"0\"/>\n");
-    out->writeText("    <reserved1 value=\"0\"/>\n");
-    out->writeText("    <reserved2 value=\"0\"/>\n");
-    out->writeText("    <reserved3 value=\"0\"/>\n");
-    out->writeText("    <metricDataFormat value=\"0\"/>\n");
-    out->writeText("    <numberOfHMetrics value=\"0\"/>\n");
-    out->writeText("  </hhea>\n");
-
-    // Some of this table is going to be re-calculated, but we have to write it out anyway.
-    out->writeText("  <maxp>\n");
-    out->writeText("    <tableVersion value=\"0x10000\"/>\n");
-    out->writeText("    <numGlyphs value=\"");
-        out->writeDecAsText(totalGlyphs);
-        out->writeText("\"/>\n");
-    out->writeText("    <maxPoints value=\"4\"/>\n");
-    out->writeText("    <maxContours value=\"1\"/>\n");
-    out->writeText("    <maxCompositePoints value=\"0\"/>\n");
-    out->writeText("    <maxCompositeContours value=\"0\"/>\n");
-    out->writeText("    <maxZones value=\"1\"/>\n");
-    out->writeText("    <maxTwilightPoints value=\"0\"/>\n");
-    out->writeText("    <maxStorage value=\"0\"/>\n");
-    out->writeText("    <maxFunctionDefs value=\"10\"/>\n");
-    out->writeText("    <maxInstructionDefs value=\"0\"/>\n");
-    out->writeText("    <maxStackElements value=\"512\"/>\n");
-    out->writeText("    <maxSizeOfInstructions value=\"24\"/>\n");
-    out->writeText("    <maxComponentElements value=\"0\"/>\n");
-    out->writeText("    <maxComponentDepth value=\"0\"/>\n");
-    out->writeText("  </maxp>\n");
-
-    out->writeText("  <OS_2>\n");
-    out->writeText("    <version value=\"4\"/>\n");
-    out->writeText("    <xAvgCharWidth value=\"");
-        out->writeScalarAsText(fFontMetrics.fAvgCharWidth);
-        out->writeText("\"/>\n");
-    out->writeText("    <usWeightClass value=\"");
-        out->writeDecAsText(this->fontStyle().weight());
-        out->writeText("\"/>\n");
-    out->writeText("    <usWidthClass value=\"");
-        out->writeDecAsText(this->fontStyle().width());
-        out->writeText("\"/>\n");
-    out->writeText("    <fsType value=\"00000000 00000000\"/>\n");
-    out->writeText("    <ySubscriptXSize value=\"665\"/>\n");
-    out->writeText("    <ySubscriptYSize value=\"716\"/>\n");
-    out->writeText("    <ySubscriptXOffset value=\"0\"/>\n");
-    out->writeText("    <ySubscriptYOffset value=\"143\"/>\n");
-    out->writeText("    <ySuperscriptXSize value=\"665\"/>\n");
-    out->writeText("    <ySuperscriptYSize value=\"716\"/>\n");
-    out->writeText("    <ySuperscriptXOffset value=\"0\"/>\n");
-    out->writeText("    <ySuperscriptYOffset value=\"491\"/>\n");
-    out->writeText("    <yStrikeoutSize value=\"");
-        out->writeScalarAsText(fFontMetrics.fStrikeoutThickness);
-        out->writeText("\"/>\n");
-    out->writeText("    <yStrikeoutPosition value=\"");
-        out->writeScalarAsText(-fFontMetrics.fStrikeoutPosition);
-        out->writeText("\"/>\n");
-    out->writeText("    <sFamilyClass value=\"0\"/>\n");
-    out->writeText("    <panose>\n");
-    out->writeText("      <bFamilyType value=\"0\"/>\n");
-    out->writeText("      <bSerifStyle value=\"0\"/>\n");
-    out->writeText("      <bWeight value=\"0\"/>\n");
-    out->writeText("      <bProportion value=\"0\"/>\n");
-    out->writeText("      <bContrast value=\"0\"/>\n");
-    out->writeText("      <bStrokeVariation value=\"0\"/>\n");
-    out->writeText("      <bArmStyle value=\"0\"/>\n");
-    out->writeText("      <bLetterForm value=\"0\"/>\n");
-    out->writeText("      <bMidline value=\"0\"/>\n");
-    out->writeText("      <bXHeight value=\"0\"/>\n");
-    out->writeText("    </panose>\n");
-    out->writeText("    <ulUnicodeRange1 value=\"00000000 00000000 00000000 00000001\"/>\n");
-    out->writeText("    <ulUnicodeRange2 value=\"00010000 00000000 00000000 00000000\"/>\n");
-    out->writeText("    <ulUnicodeRange3 value=\"00000000 00000000 00000000 00000000\"/>\n");
-    out->writeText("    <ulUnicodeRange4 value=\"00000000 00000000 00000000 00000000\"/>\n");
-    out->writeText("    <achVendID value=\"Skia\"/>\n");
-    char fsSelection[16] = {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
-    fsSelection[0xF - 0x7] = '1'; // Use typo metrics
-    if (this->fontStyle().weight() >= SkFontStyle::Bold().weight()) {
-        fsSelection[0xF - 0x5] = '1'; // Bold
-    }
-    switch (this->fontStyle().slant()) {
-        case SkFontStyle::kUpright_Slant:
-            if (this->fontStyle().weight() < SkFontStyle::Bold().weight()) {
-                fsSelection[0xF - 0x6] = '1'; // Not bold or italic, is regular
-            }
-            break;
-        case SkFontStyle::kItalic_Slant:
-            fsSelection[0xF - 0x0] = '1'; // Italic
-            break;
-        case SkFontStyle::kOblique_Slant:
-            fsSelection[0xF - 0x0] = '1'; // Italic
-            fsSelection[0xF - 0x9] = '1'; // Oblique
-            break;
-        default:
-            SK_ABORT("Unknown slant.");
-    }
-    out->writeText("    <fsSelection value=\"");
-        out->write(fsSelection, 8);
-        out->writeText(" ");
-        out->write(fsSelection + 8, 8);
-        out->writeText("\"/>\n");
-    out->writeText("    <usFirstCharIndex value=\"0\"/>\n");
-    out->writeText("    <usLastCharIndex value=\"0\"/>\n");
-    out->writeText("    <sTypoAscender value=\"");
-        out->writeScalarAsText(-fFontMetrics.fAscent);
-        out->writeText("\"/>\n");
-    out->writeText("    <sTypoDescender value=\"");
-        out->writeScalarAsText(-fFontMetrics.fDescent);
-        out->writeText("\"/>\n");
-    out->writeText("    <sTypoLineGap value=\"");
-        out->writeScalarAsText(fFontMetrics.fLeading);
-        out->writeText("\"/>\n");
-    out->writeText("    <usWinAscent value=\"");
-        out->writeScalarAsText(-fFontMetrics.fAscent);
-        out->writeText("\"/>\n");
-    out->writeText("    <usWinDescent value=\"");
-        out->writeScalarAsText(fFontMetrics.fDescent);
-        out->writeText("\"/>\n");
-    out->writeText("    <ulCodePageRange1 value=\"00000000 00000000 00000000 00000000\"/>\n");
-    out->writeText("    <ulCodePageRange2 value=\"00000000 00000000 00000000 00000000\"/>\n");
-    out->writeText("    <sxHeight value=\"");
-        out->writeScalarAsText(fFontMetrics.fXHeight);
-        out->writeText("\"/>\n");
-    out->writeText("    <sCapHeight value=\"");
-        out->writeScalarAsText(fFontMetrics.fCapHeight);
-        out->writeText("\"/>\n");
-    out->writeText("    <usDefaultChar value=\"0\"/>\n");
-    out->writeText("    <usBreakChar value=\"32\"/>\n");
-    out->writeText("    <usMaxContext value=\"0\"/>\n");
-    out->writeText("  </OS_2>\n");
-
-    out->writeText("  <hmtx>\n");
-    for (int i = 0; i < fGlyphCount; ++i) {
-        out->writeText("    <mtx name=\"glyf");
-        out->writeHexAsText(i, 4);
-        out->writeText("\" width=\"");
-        out->writeDecAsText(fGlyphs[i].fAdvance);
-        out->writeText("\" lsb=\"");
-        int lsb = fGlyphs[i].fOrigin.fX;
-        if (glyfInfo) {
-            lsb += (*glyfInfo)[i].fBounds.fLeft;
-        }
-        out->writeDecAsText(lsb);
-        out->writeText("\"/>\n");
-    }
-    if (glyfInfo) {
-        for (int i = 0; i < fGlyphCount; ++i) {
-            for (int j = 0; j < (*glyfInfo)[i].fLayers.count(); ++j) {
-                out->writeText("    <mtx name=\"glyf");
-                    out->writeHexAsText(i, 4);
-                    out->writeText("l");
-                    out->writeHexAsText(j, 4);
-                    out->writeText("\" width=\"");
-                    out->writeDecAsText(fGlyphs[i].fAdvance);
-                    out->writeText("\" lsb=\"");
-                    int32_t lsb = fGlyphs[i].fOrigin.fX + (*glyfInfo)[i].fLayers[j].fBounds.fLeft;
-                    out->writeDecAsText(lsb);
-                    out->writeText("\"/>\n");
-            }
-        }
-    }
-    out->writeText("  </hmtx>\n");
-
-    bool hasNonBMP = false;
-    out->writeText("  <cmap>\n");
-    out->writeText("    <tableVersion version=\"0\"/>\n");
-    out->writeText("    <cmap_format_4 platformID=\"3\" platEncID=\"1\" language=\"0\">\n");
-    fCMap.foreach([&out, &hasNonBMP](const SkUnichar& c, const SkGlyphID& g) {
-        if (0xFFFF < c) {
-            hasNonBMP = true;
-            return;
-        }
-        out->writeText("      <map code=\"0x");
-        out->writeHexAsText(c, 4);
-        out->writeText("\" name=\"glyf");
-        out->writeHexAsText(g, 4);
-        out->writeText("\"/>\n");
-    });
-    out->writeText("    </cmap_format_4>\n");
-    if (hasNonBMP) {
-        out->writeText("    <cmap_format_12 platformID=\"3\" platEncID=\"10\" format=\"12\" reserved=\"0\" length=\"1\" language=\"0\" nGroups=\"0\">\n");
-        fCMap.foreach([&out](const SkUnichar& c, const SkGlyphID& g) {
-            out->writeText("      <map code=\"0x");
-            out->writeHexAsText(c, 6);
-            out->writeText("\" name=\"glyf");
-            out->writeHexAsText(g, 4);
-            out->writeText("\"/>\n");
-        });
-        out->writeText("    </cmap_format_12>\n");
-    }
-    out->writeText("  </cmap>\n");
-
-    out->writeText("  <name>\n");
-    out->writeText("    <namerecord nameID=\"1\" platformID=\"3\" platEncID=\"1\" langID=\"0x409\">\n");
-    out->writeText("      ");
-      out->writeText(fName.c_str());
-      out->writeText(" ");
-      out->writeText(type);
-      out->writeText("\n");
-    out->writeText("    </namerecord>\n");
-    out->writeText("    <namerecord nameID=\"2\" platformID=\"3\" platEncID=\"1\" langID=\"0x409\">\n");
-    out->writeText("      Regular\n");
-    out->writeText("    </namerecord>\n");
-    out->writeText("  </name>\n");
-
-    out->writeText("  <post>\n");
-    out->writeText("    <formatType value=\"3.0\"/>\n");
-    out->writeText("    <italicAngle value=\"0.0\"/>\n");
-    out->writeText("    <underlinePosition value=\"");
-        out->writeScalarAsText(fFontMetrics.fUnderlinePosition);
-        out->writeText("\"/>\n");
-    out->writeText("    <underlineThickness value=\"");
-        out->writeScalarAsText(fFontMetrics.fUnderlineThickness);
-        out->writeText("\"/>\n");
-    out->writeText("    <isFixedPitch value=\"0\"/>\n");
-    out->writeText("    <minMemType42 value=\"0\"/>\n");
-    out->writeText("    <maxMemType42 value=\"0\"/>\n");
-    out->writeText("    <minMemType1 value=\"0\"/>\n");
-    out->writeText("    <maxMemType1 value=\"0\"/>\n");
-    out->writeText("  </post>\n");
-}
-
-void SkTestSVGTypeface::exportTtxCbdt(SkWStream* out) const {
-    out->writeText("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
-    out->writeText("<ttFont sfntVersion=\"\\x00\\x01\\x00\\x00\" ttLibVersion=\"3.19\">\n");
-    this->exportTtxCommon(out, "CBDT");
-
-    int strikeSizes[3] = { 16, 64, 128 };
-
-    SkPaint paint;
-    SkFont font;
-    font.setTypeface(sk_ref_sp(const_cast<SkTestSVGTypeface*>(this)));
-
-    out->writeText("  <CBDT>\n");
-    out->writeText("    <header version=\"2.0\"/>\n");
-    for (size_t strikeIndex = 0; strikeIndex < SK_ARRAY_COUNT(strikeSizes); ++strikeIndex) {
-        font.setSize(strikeSizes[strikeIndex]);
-        out->writeText("    <strikedata index=\"");
-            out->writeDecAsText(strikeIndex);
-            out->writeText("\">\n");
-        for (int i = 0; i < fGlyphCount; ++i) {
-            SkGlyphID gid = i;
-            SkScalar advance;
-            SkRect bounds;
-            font.getWidthsBounds(&gid, 1, &advance, &bounds, nullptr);
-            SkIRect ibounds = bounds.roundOut();
-            if (ibounds.isEmpty()) {
-                continue;
-            }
-            SkImageInfo image_info = SkImageInfo::MakeN32Premul(ibounds.width(), ibounds.height());
-            sk_sp<SkSurface> surface(SkSurface::MakeRaster(image_info));
-            SkASSERT(surface);
-            SkCanvas* canvas = surface->getCanvas();
-            canvas->clear(0);
-            SkPixmap pix;
-            surface->peekPixels(&pix);
-            canvas->drawSimpleText(&gid, sizeof(gid), kGlyphID_SkTextEncoding,
-                                   -bounds.fLeft, -bounds.fTop, font, paint);
-            canvas->flush();
-            sk_sp<SkImage> image = surface->makeImageSnapshot();
-            sk_sp<SkData> data = image->encodeToData(SkEncodedImageFormat::kPNG, 100);
-
-            out->writeText("      <cbdt_bitmap_format_17 name=\"glyf");
-                out->writeHexAsText(i, 4);
-                out->writeText("\">\n");
-            out->writeText("        <SmallGlyphMetrics>\n");
-            out->writeText("          <height value=\"");
-                out->writeDecAsText(image->height());
-                out->writeText("\"/>\n");
-            out->writeText("          <width value=\"");
-                out->writeDecAsText(image->width());
-                out->writeText("\"/>\n");
-            out->writeText("          <BearingX value=\"");
-                out->writeDecAsText(bounds.fLeft);
-                out->writeText("\"/>\n");
-            out->writeText("          <BearingY value=\"");
-                out->writeScalarAsText(-bounds.fTop);
-                out->writeText("\"/>\n");
-            out->writeText("          <Advance value=\"");
-                out->writeScalarAsText(advance);
-                out->writeText("\"/>\n");
-            out->writeText("        </SmallGlyphMetrics>\n");
-            out->writeText("        <rawimagedata>");
-            uint8_t const * bytes = data->bytes();
-            for (size_t i = 0; i < data->size(); ++i) {
-                if ((i % 0x10) == 0x0) {
-                    out->writeText("\n          ");
-                } else if (((i - 1) % 0x4) == 0x3) {
-                    out->writeText(" ");
-                }
-                out->writeHexAsText(bytes[i], 2);
-            }
-            out->writeText("\n");
-            out->writeText("        </rawimagedata>\n");
-            out->writeText("      </cbdt_bitmap_format_17>\n");
-        }
-        out->writeText("    </strikedata>\n");
-    }
-    out->writeText("  </CBDT>\n");
-
-    SkFontMetrics fm;
-    out->writeText("  <CBLC>\n");
-    out->writeText("    <header version=\"2.0\"/>\n");
-    for (size_t strikeIndex = 0; strikeIndex < SK_ARRAY_COUNT(strikeSizes); ++strikeIndex) {
-        font.setSize(strikeSizes[strikeIndex]);
-        font.getMetrics(&fm);
-        out->writeText("    <strike index=\"");
-            out->writeDecAsText(strikeIndex);
-            out->writeText("\">\n");
-        out->writeText("      <bitmapSizeTable>\n");
-        out->writeText("        <sbitLineMetrics direction=\"hori\">\n");
-        out->writeText("          <ascender value=\"");
-            out->writeScalarAsText(-fm.fTop);
-            out->writeText("\"/>\n");
-        out->writeText("          <descender value=\"");
-            out->writeScalarAsText(-fm.fBottom);
-            out->writeText("\"/>\n");
-        out->writeText("          <widthMax value=\"");
-            out->writeScalarAsText(fm.fXMax - fm.fXMin);
-            out->writeText("\"/>\n");
-        out->writeText("          <caretSlopeNumerator value=\"0\"/>\n");
-        out->writeText("          <caretSlopeDenominator value=\"0\"/>\n");
-        out->writeText("          <caretOffset value=\"0\"/>\n");
-        out->writeText("          <minOriginSB value=\"0\"/>\n");
-        out->writeText("          <minAdvanceSB value=\"0\"/>\n");
-        out->writeText("          <maxBeforeBL value=\"0\"/>\n");
-        out->writeText("          <minAfterBL value=\"0\"/>\n");
-        out->writeText("          <pad1 value=\"0\"/>\n");
-        out->writeText("          <pad2 value=\"0\"/>\n");
-        out->writeText("        </sbitLineMetrics>\n");
-        out->writeText("        <sbitLineMetrics direction=\"vert\">\n");
-        out->writeText("          <ascender value=\"");
-            out->writeScalarAsText(-fm.fTop);
-            out->writeText("\"/>\n");
-        out->writeText("          <descender value=\"");
-            out->writeScalarAsText(-fm.fBottom);
-            out->writeText("\"/>\n");
-        out->writeText("          <widthMax value=\"");
-            out->writeScalarAsText(fm.fXMax - fm.fXMin);
-            out->writeText("\"/>\n");
-        out->writeText("          <caretSlopeNumerator value=\"0\"/>\n");
-        out->writeText("          <caretSlopeDenominator value=\"0\"/>\n");
-        out->writeText("          <caretOffset value=\"0\"/>\n");
-        out->writeText("          <minOriginSB value=\"0\"/>\n");
-        out->writeText("          <minAdvanceSB value=\"0\"/>\n");
-        out->writeText("          <maxBeforeBL value=\"0\"/>\n");
-        out->writeText("          <minAfterBL value=\"0\"/>\n");
-        out->writeText("          <pad1 value=\"0\"/>\n");
-        out->writeText("          <pad2 value=\"0\"/>\n");
-        out->writeText("        </sbitLineMetrics>\n");
-        out->writeText("        <colorRef value=\"0\"/>\n");
-        out->writeText("        <startGlyphIndex value=\"1\"/>\n");
-        out->writeText("        <endGlyphIndex value=\"1\"/>\n");
-        out->writeText("        <ppemX value=\"");
-            out->writeDecAsText(strikeSizes[strikeIndex]);
-            out->writeText("\"/>\n");
-        out->writeText("        <ppemY value=\"");
-            out->writeDecAsText(strikeSizes[strikeIndex]);
-            out->writeText("\"/>\n");
-        out->writeText("        <bitDepth value=\"32\"/>\n");
-        out->writeText("        <flags value=\"1\"/>\n");
-        out->writeText("      </bitmapSizeTable>\n");
-        out->writeText("      <eblc_index_sub_table_1 imageFormat=\"17\" firstGlyphIndex=\"1\" lastGlyphIndex=\"1\">\n");
-        for (int i = 0; i < fGlyphCount; ++i) {
-            SkGlyphID gid = i;
-            SkRect bounds;
-            font.getBounds(&gid, 1, &bounds, nullptr);
-            if (bounds.isEmpty()) {
-                continue;
-            }
-            out->writeText("        <glyphLoc name=\"glyf");
-                out->writeHexAsText(i, 4);
-                out->writeText("\"/>\n");
-        }
-        out->writeText("      </eblc_index_sub_table_1>\n");
-        out->writeText("    </strike>\n");
-    }
-    out->writeText("  </CBLC>\n");
-
-    out->writeText("</ttFont>\n");
-}
-
-/**
- * UnitsPerEm is generally 1000 here. Versions of macOS older than 10.13
- * have problems in CoreText determining the glyph bounds of bitmap glyphs
- * with unitsPerEm set to 1024 or numbers not divisible by 100 when the
- * contour is not closed. The bounds of sbix fonts on macOS appear to be those
- * of the outline in the 'glyf' table. If this countour is closed it will be
- * drawn, as the 'glyf' outline is to be drawn on top of any bitmap. (There is
- * a bit which is supposed to control this, but it cannot be relied on.) So
- * make the glyph contour a degenerate line with points at the edge of the
- * bounding box of the glyph.
- */
-void SkTestSVGTypeface::exportTtxSbix(SkWStream* out) const {
-    out->writeText("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
-    out->writeText("<ttFont sfntVersion=\"\\x00\\x01\\x00\\x00\" ttLibVersion=\"3.19\">\n");
-    this->exportTtxCommon(out, "sbix");
-
-    SkPaint paint;
-    SkFont font;
-    font.setTypeface(sk_ref_sp(const_cast<SkTestSVGTypeface*>(this)));
-
-    out->writeText("  <glyf>\n");
-    for (int i = 0; i < fGlyphCount; ++i) {
-        const SkTestSVGTypeface::Glyph& glyphData = this->fGlyphs[i];
-
-        SkSize containerSize = glyphData.fSvg ? glyphData.fSvg->containerSize()
-                                              : SkSize::MakeEmpty();
-        SkRect bounds = SkRect::MakeXYWH(glyphData.fOrigin.fX, -glyphData.fOrigin.fY,
-                                         containerSize.fWidth, containerSize.fHeight);
-        SkIRect ibounds = bounds.roundOut();
-        out->writeText("    <TTGlyph name=\"glyf");
-            out->writeHexAsText(i, 4);
-            out->writeText("\" xMin=\"");
-            out->writeDecAsText(ibounds.fLeft);
-            out->writeText("\" yMin=\"");
-            out->writeDecAsText(-ibounds.fBottom);
-            out->writeText("\" xMax=\"");
-            out->writeDecAsText(ibounds.fRight);
-            out->writeText("\" yMax=\"");
-            out->writeDecAsText(-ibounds.fTop);
-            out->writeText("\">\n");
-        out->writeText("      <contour>\n");
-        out->writeText("        <pt x=\"");
-            out->writeDecAsText(ibounds.fLeft);
-            out->writeText("\" y=\"");
-            out->writeDecAsText(-ibounds.fBottom);
-            out->writeText("\" on=\"1\"/>\n");
-        out->writeText("      </contour>\n");
-        out->writeText("      <contour>\n");
-        out->writeText("        <pt x=\"");
-            out->writeDecAsText(ibounds.fRight);
-            out->writeText("\" y=\"");
-            out->writeDecAsText(-ibounds.fTop);
-            out->writeText("\" on=\"1\"/>\n");
-        out->writeText("      </contour>\n");
-        out->writeText("      <instructions/>\n");
-        out->writeText("    </TTGlyph>\n");
-    }
-    out->writeText("  </glyf>\n");
-
-    // The loca table will be re-calculated, but if we don't write one we don't get one.
-    out->writeText("  <loca/>\n");
-
-    int strikeSizes[3] = { 16, 64, 128 };
-
-    out->writeText("  <sbix>\n");
-    out->writeText("    <version value=\"1\"/>\n");
-    out->writeText("    <flags value=\"00000000 00000001\"/>\n");
-    for (size_t strikeIndex = 0; strikeIndex < SK_ARRAY_COUNT(strikeSizes); ++strikeIndex) {
-        font.setSize(strikeSizes[strikeIndex]);
-        out->writeText("    <strike>\n");
-        out->writeText("      <ppem value=\"");
-            out->writeDecAsText(strikeSizes[strikeIndex]);
-            out->writeText("\"/>\n");
-        out->writeText("      <resolution value=\"72\"/>\n");
-        for (int i = 0; i < fGlyphCount; ++i) {
-            SkGlyphID gid = i;
-            SkScalar advance;
-            SkRect bounds;
-            font.getWidthsBounds(&gid, 1, &advance, &bounds, nullptr);
-            SkIRect ibounds = bounds.roundOut();
-            if (ibounds.isEmpty()) {
-                continue;
-            }
-            SkImageInfo image_info = SkImageInfo::MakeN32Premul(ibounds.width(), ibounds.height());
-            sk_sp<SkSurface> surface(SkSurface::MakeRaster(image_info));
-            SkASSERT(surface);
-            SkCanvas* canvas = surface->getCanvas();
-            canvas->clear(0);
-            SkPixmap pix;
-            surface->peekPixels(&pix);
-            canvas->drawSimpleText(&gid, sizeof(gid), kGlyphID_SkTextEncoding,
-                                   -bounds.fLeft, -bounds.fTop, font, paint);
-            canvas->flush();
-            sk_sp<SkImage> image = surface->makeImageSnapshot();
-            sk_sp<SkData> data = image->encodeToData(SkEncodedImageFormat::kPNG, 100);
-
-            out->writeText("      <glyph name=\"glyf");
-                out->writeHexAsText(i, 4);
-                out->writeText("\" graphicType=\"png \" originOffsetX=\"");
-                out->writeDecAsText(bounds.fLeft);
-                out->writeText("\" originOffsetY=\"");
-                out->writeScalarAsText(bounds.fBottom);
-                out->writeText("\">\n");
-
-            out->writeText("        <hexdata>");
-            uint8_t const * bytes = data->bytes();
-            for (size_t i = 0; i < data->size(); ++i) {
-                if ((i % 0x10) == 0x0) {
-                    out->writeText("\n          ");
-                } else if (((i - 1) % 0x4) == 0x3) {
-                    out->writeText(" ");
-                }
-                out->writeHexAsText(bytes[i], 2);
-            }
-            out->writeText("\n");
-            out->writeText("        </hexdata>\n");
-            out->writeText("      </glyph>\n");
-        }
-        out->writeText("    </strike>\n");
-    }
-    out->writeText("  </sbix>\n");
-    out->writeText("</ttFont>\n");
-}
-
-namespace {
-
-void convert_noninflect_cubic_to_quads(const SkPoint p[4],
-                                       SkScalar toleranceSqd,
-                                       SkTArray<SkPoint, true>* quads,
-                                       int sublevel = 0)
-{
-    // Notation: Point a is always p[0]. Point b is p[1] unless p[1] == p[0], in which case it is
-    // p[2]. Point d is always p[3]. Point c is p[2] unless p[2] == p[3], in which case it is p[1].
-
-    SkVector ab = p[1] - p[0];
-    SkVector dc = p[2] - p[3];
-
-    if (SkPointPriv::LengthSqd(ab) < SK_ScalarNearlyZero) {
-        if (SkPointPriv::LengthSqd(dc) < SK_ScalarNearlyZero) {
-            SkPoint* degQuad = quads->push_back_n(3);
-            degQuad[0] = p[0];
-            degQuad[1] = p[0];
-            degQuad[2] = p[3];
-            return;
-        }
-        ab = p[2] - p[0];
-    }
-    if (SkPointPriv::LengthSqd(dc) < SK_ScalarNearlyZero) {
-        dc = p[1] - p[3];
-    }
-
-    static const SkScalar kLengthScale = 3 * SK_Scalar1 / 2;
-    static const int kMaxSubdivs = 10;
-
-    ab.scale(kLengthScale);
-    dc.scale(kLengthScale);
-
-    // e0 and e1 are extrapolations along vectors ab and dc.
-    SkVector c0 = p[0];
-    c0 += ab;
-    SkVector c1 = p[3];
-    c1 += dc;
-
-    SkScalar dSqd = sublevel > kMaxSubdivs ? 0 : SkPointPriv::DistanceToSqd(c0, c1);
-    if (dSqd < toleranceSqd) {
-        SkPoint cAvg = c0;
-        cAvg += c1;
-        cAvg.scale(SK_ScalarHalf);
-
-        SkPoint* pts = quads->push_back_n(3);
-        pts[0] = p[0];
-        pts[1] = cAvg;
-        pts[2] = p[3];
-        return;
-    }
-    SkPoint choppedPts[7];
-    SkChopCubicAtHalf(p, choppedPts);
-    convert_noninflect_cubic_to_quads(choppedPts + 0, toleranceSqd, quads, sublevel + 1);
-    convert_noninflect_cubic_to_quads(choppedPts + 3, toleranceSqd, quads, sublevel + 1);
-}
-
-void convertCubicToQuads(const SkPoint p[4], SkScalar tolScale, SkTArray<SkPoint, true>* quads) {
-    if (!p[0].isFinite() || !p[1].isFinite() || !p[2].isFinite() || !p[3].isFinite()) {
-        return;
-    }
-    SkPoint chopped[10];
-    int count = SkChopCubicAtInflections(p, chopped);
-
-    const SkScalar tolSqd = SkScalarSquare(tolScale);
-
-    for (int i = 0; i < count; ++i) {
-        SkPoint* cubic = chopped + 3*i;
-        convert_noninflect_cubic_to_quads(cubic, tolSqd, quads);
-    }
-}
-
-void path_to_quads(const SkPath& path, SkPath* quadPath) {
-    quadPath->reset();
-    SkTArray<SkPoint, true> qPts;
-    SkAutoConicToQuads converter;
-    const SkPoint* quadPts;
-    SkPath::RawIter iter(path);
-    uint8_t verb;
-    SkPoint pts[4];
-    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
-        switch (verb) {
-            case SkPath::kMove_Verb:
-                quadPath->moveTo(pts[0].fX, pts[0].fY);
-                break;
-            case SkPath::kLine_Verb:
-                quadPath->lineTo(pts[1].fX, pts[1].fY);
-                break;
-            case SkPath::kQuad_Verb:
-                quadPath->quadTo(pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
-                break;
-            case SkPath::kCubic_Verb:
-                qPts.reset();
-                convertCubicToQuads(pts, SK_Scalar1, &qPts);
-                for (int i = 0; i < qPts.count(); i += 3) {
-                    quadPath->quadTo(qPts[i+1].fX, qPts[i+1].fY, qPts[i+2].fX, qPts[i+2].fY);
-                }
-                break;
-            case SkPath::kConic_Verb:
-                quadPts = converter.computeQuads(pts, iter.conicWeight(), SK_Scalar1);
-                for (int i = 0; i < converter.countQuads(); ++i) {
-                    quadPath->quadTo(quadPts[i*2+1].fX, quadPts[i*2+1].fY,
-                                     quadPts[i*2+2].fX, quadPts[i*2+2].fY);
-                }
-                break;
-            case SkPath::kClose_Verb:
-                 quadPath->close();
-                break;
-            default:
-                SkDEBUGFAIL("bad verb");
-                return;
-        }
-    }
-}
-
-class SkCOLRCanvas : public SkNoDrawCanvas {
-public:
-    SkCOLRCanvas(SkRect glyphBounds, SkGlyphID glyphId,
-                 SkTestSVGTypeface::GlyfInfo* glyf, SkTHashMap<SkColor, int>* colors,
-                 SkWStream* out)
-        : SkNoDrawCanvas(glyphBounds.roundOut().width(), glyphBounds.roundOut().height())
-        , fOut(out)
-        , fGlyphId(glyphId)
-        , fBaselineOffset(glyphBounds.top())
-        , fLayerId(0)
-        , fGlyf(glyf)
-        , fColors(colors)
-    { }
-
-    void writePoint(SkScalar x, SkScalar y, bool on) {
-        fOut->writeText("        <pt x=\"");
-        fOut->writeDecAsText(SkScalarRoundToInt(x));
-        fOut->writeText("\" y=\"");
-        fOut->writeDecAsText(SkScalarRoundToInt(y));
-        fOut->writeText("\" on=\"");
-        fOut->write8(on ? '1' : '0');
-        fOut->writeText("\"/>\n");
-    }
-    SkIRect writePath(const SkPath& path, bool layer) {
-        // Convert to quads.
-        SkPath quads;
-        path_to_quads(path, &quads);
-
-        SkRect bounds = quads.computeTightBounds();
-        SkIRect ibounds = bounds.roundOut();
-        // The bounds will be re-calculated anyway.
-        fOut->writeText("    <TTGlyph name=\"glyf");
-            fOut->writeHexAsText(fGlyphId, 4);
-            if (layer) {
-                fOut->writeText("l");
-                fOut->writeHexAsText(fLayerId, 4);
-            }
-            fOut->writeText("\" xMin=\"");
-            fOut->writeDecAsText(ibounds.fLeft);
-            fOut->writeText("\" yMin=\"");
-            fOut->writeDecAsText(ibounds.fTop);
-            fOut->writeText("\" xMax=\"");
-            fOut->writeDecAsText(ibounds.fRight);
-            fOut->writeText("\" yMax=\"");
-            fOut->writeDecAsText(ibounds.fBottom);
-            fOut->writeText("\">\n");
-
-        SkPath::RawIter iter(quads);
-        uint8_t verb;
-        SkPoint pts[4];
-        bool contourOpen = false;
-        while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
-            switch (verb) {
-                case SkPath::kMove_Verb:
-                    if (contourOpen) {
-                        fOut->writeText("      </contour>\n");
-                        contourOpen = false;
-                    }
-                    break;
-                case SkPath::kLine_Verb:
-                    if (!contourOpen) {
-                        fOut->writeText("      <contour>\n");
-                        this->writePoint(pts[0].fX, pts[0].fY, true);
-                        contourOpen = true;
-                    }
-                    this->writePoint(pts[1].fX, pts[1].fY, true);
-                    break;
-                case SkPath::kQuad_Verb:
-                    if (!contourOpen) {
-                        fOut->writeText("      <contour>\n");
-                        this->writePoint(pts[0].fX, pts[0].fY, true);
-                        contourOpen = true;
-                    }
-                    this->writePoint(pts[1].fX, pts[1].fY, false);
-                    this->writePoint(pts[2].fX, pts[2].fY, true);
-                    break;
-                case SkPath::kClose_Verb:
-                    if (contourOpen) {
-                        fOut->writeText("      </contour>\n");
-                        contourOpen = false;
-                    }
-                    break;
-                default:
-                    SkDEBUGFAIL("bad verb");
-                    return ibounds;
-            }
-        }
-        if (contourOpen) {
-            fOut->writeText("      </contour>\n");
-        }
-
-        // Required to write out an instructions tag.
-        fOut->writeText("      <instructions/>\n");
-        fOut->writeText("    </TTGlyph>\n");
-        return ibounds;
-    }
-
-    void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
-        SkPath path;
-        path.addRect(rect);
-        this->drawPath(path, paint);
-    }
-
-    void onDrawOval(const SkRect& oval, const SkPaint& paint) override {
-        SkPath path;
-        path.addOval(oval);
-        this->drawPath(path, paint);
-    }
-
-    void onDrawArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter,
-                   const SkPaint& paint) override
-    {
-        SkPath path;
-        bool fillNoPathEffect = SkPaint::kFill_Style == paint.getStyle() && !paint.getPathEffect();
-        SkPathPriv::CreateDrawArcPath(&path, oval, startAngle, sweepAngle, useCenter,
-                                    fillNoPathEffect);
-        this->drawPath(path, paint);
-    }
-
-    void onDrawRRect(const SkRRect& rrect, const SkPaint& paint) override {
-        SkPath path;
-        path.addRRect(rrect);
-        this->drawPath(path, paint);
-    }
-
-    void onDrawPath(const SkPath& platonicPath, const SkPaint& originalPaint) override {
-        SkPaint paint = originalPaint;
-        SkPath path = platonicPath;
-
-        // Apply the path effect.
-        if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) {
-            bool fill = paint.getFillPath(path, &path);
-
-            paint.setPathEffect(nullptr);
-            if (fill) {
-                paint.setStyle(SkPaint::kFill_Style);
-            } else {
-                paint.setStyle(SkPaint::kStroke_Style);
-                paint.setStrokeWidth(0);
-            }
-        }
-
-        // Apply the matrix.
-        SkMatrix m = this->getTotalMatrix();
-        // If done to the canvas then everything would get clipped out.
-        m.postTranslate(0, fBaselineOffset); // put the baseline at 0
-        m.postScale(1, -1); // and flip it since OpenType is y-up.
-        path.transform(m);
-
-        // While creating the default glyf, union with dark colors and intersect with bright colors.
-        SkColor color = paint.getColor();
-        if ((SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color)) / 3 > 0x20) {
-            fBasePath.add(path, SkPathOp::kDifference_SkPathOp);
-        } else {
-            fBasePath.add(path, SkPathOp::kUnion_SkPathOp);
-        }
-
-        SkIRect bounds = this->writePath(path, true);
-
-        // The CPAL table has the concept of a 'current color' which is index 0xFFFF.
-        // Mark any layer drawn in 'currentColor' as having this special index.
-        // The value of 'currentColor' here should a color which causes this layer to union into the
-        // default glyf.
-        constexpr SkColor currentColor = 0xFF2B0000;
-
-        int colorIndex;
-        if (color == currentColor) {
-            colorIndex = 0xFFFF;
-        } else {
-            int* colorIndexPtr = fColors->find(color);
-            if (colorIndexPtr) {
-                colorIndex = *colorIndexPtr;
-            } else {
-                colorIndex = fColors->count();
-                fColors->set(color, colorIndex);
-            }
-        }
-        fGlyf->fLayers.emplace_back(colorIndex, bounds);
-
-        ++fLayerId;
-    }
-
-    void finishGlyph() {
-        SkPath baseGlyph;
-        fBasePath.resolve(&baseGlyph);
-        fGlyf->fBounds = this->writePath(baseGlyph, false);
-    }
-
-private:
-    SkWStream * const fOut;
-    SkGlyphID fGlyphId;
-    SkScalar fBaselineOffset;
-    int fLayerId;
-    SkOpBuilder fBasePath;
-    SkTestSVGTypeface::GlyfInfo* fGlyf;
-    SkTHashMap<SkColor, int>* fColors;
-};
-
-} // namespace
-
-void SkTestSVGTypeface::exportTtxColr(SkWStream* out) const {
-    out->writeText("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
-    out->writeText("<ttFont sfntVersion=\"\\x00\\x01\\x00\\x00\" ttLibVersion=\"3.19\">\n");
-
-    SkTHashMap<SkColor, int> colors;
-    SkTArray<GlyfInfo> glyfInfos(fGlyphCount);
-
-    // Need to know all the glyphs up front for the common tables.
-    SkDynamicMemoryWStream glyfOut;
-    glyfOut.writeText("  <glyf>\n");
-    for (int i = 0; i < fGlyphCount; ++i) {
-        const SkTestSVGTypeface::Glyph& glyphData = this->fGlyphs[i];
-
-        SkSize containerSize = glyphData.fSvg ? glyphData.fSvg->containerSize()
-                                              : SkSize::MakeEmpty();
-        SkRect bounds = SkRect::MakeXYWH(glyphData.fOrigin.fX, -glyphData.fOrigin.fY,
-                                         containerSize.fWidth, containerSize.fHeight);
-        SkCOLRCanvas canvas(bounds, i, &glyfInfos.emplace_back(), &colors, &glyfOut);
-        if (glyphData.fSvg) {
-            glyphData.fSvg->render(&canvas);
-        }
-        canvas.finishGlyph();
-    }
-    glyfOut.writeText("  </glyf>\n");
-
-    this->exportTtxCommon(out, "COLR", &glyfInfos);
-
-    // The loca table will be re-calculated, but if we don't write one we don't get one.
-    out->writeText("  <loca/>\n");
-
-    std::unique_ptr<SkStreamAsset> glyfStream = glyfOut.detachAsStream();
-    out->writeStream(glyfStream.get(), glyfStream->getLength());
-
-    out->writeText("  <COLR>\n");
-    out->writeText("    <version value=\"0\"/>\n");
-    for (int i = 0; i < fGlyphCount; ++i) {
-        if (glyfInfos[i].fBounds.isEmpty() || glyfInfos[i].fLayers.empty()) {
-            continue;
-        }
-        out->writeText("    <ColorGlyph name=\"glyf");
-            out->writeHexAsText(i, 4);
-            out->writeText("\">\n");
-        for (int j = 0; j < glyfInfos[i].fLayers.count(); ++j) {
-            const int colorIndex = glyfInfos[i].fLayers[j].fLayerColorIndex;
-            out->writeText("      <layer colorID=\"");
-                out->writeDecAsText(colorIndex);
-                out->writeText("\" name=\"glyf");
-                out->writeHexAsText(i, 4);
-                out->writeText("l");
-                out->writeHexAsText(j, 4);
-                out->writeText("\"/>\n");
-        }
-        out->writeText("    </ColorGlyph>\n");
-    }
-    out->writeText("  </COLR>\n");
-
-    // The colors must be written in order, the 'index' is ignored by ttx.
-    SkAutoTMalloc<SkColor> colorsInOrder(colors.count());
-    colors.foreach([&colorsInOrder](const SkColor& c, const int* i) {
-        colorsInOrder[*i] = c;
-    });
-    out->writeText("  <CPAL>\n");
-    out->writeText("    <version value=\"0\"/>\n");
-    out->writeText("    <numPaletteEntries value=\"");
-        out->writeDecAsText(colors.count());
-        out->writeText("\"/>\n");
-    out->writeText("    <palette index=\"0\">\n");
-    for (int i = 0; i < colors.count(); ++i) {
-        SkColor c = colorsInOrder[i];
-        out->writeText("      <color index=\"");
-            out->writeDecAsText(i);
-            out->writeText("\" value=\"#");
-            out->writeHexAsText(SkColorGetR(c), 2);
-            out->writeHexAsText(SkColorGetG(c), 2);
-            out->writeHexAsText(SkColorGetB(c), 2);
-            out->writeHexAsText(SkColorGetA(c), 2);
-            out->writeText("\"/>\n");
-    }
-    out->writeText("    </palette>\n");
-    out->writeText("  </CPAL>\n");
-
-    out->writeText("</ttFont>\n");
-}
-#endif  // SK_XML
diff --git a/tools/fonts/SkTestSVGTypeface.h b/tools/fonts/SkTestSVGTypeface.h
deleted file mode 100644
index bae3215..0000000
--- a/tools/fonts/SkTestSVGTypeface.h
+++ /dev/null
@@ -1,146 +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.
- */
-
-#ifndef SkTestSVGTypeface_DEFINED
-#define SkTestSVGTypeface_DEFINED
-
-#include "SkFontArguments.h"
-#include "SkFontMetrics.h"
-#include "SkMutex.h"
-#include "SkPaint.h"
-#include "SkPoint.h"
-#include "SkRect.h"
-#include "SkRefCnt.h"
-#include "SkScalar.h"
-#include "SkString.h"
-#include "SkTArray.h"
-#include "SkTHash.h"
-#include "SkTypeface.h"
-#include "SkTypes.h"
-
-#include <memory>
-
-class SkDescriptor;
-class SkFontDescriptor;
-class SkFontStyle;
-class SkGlyph;
-class SkPath;
-class SkScalerContext;
-class SkStreamAsset;
-class SkSVGDOM;
-class SkWStream;
-struct SkAdvancedTypefaceMetrics;
-struct SkScalerContextEffects;
-struct SkScalerContextRec;
-
-struct SkSVGTestTypefaceGlyphData {
-    const char* fSvgResourcePath;
-    SkPoint fOrigin;
-    SkScalar fAdvance;
-    SkUnichar fUnicode; //TODO: this limits to 1:1
-};
-
-class SkTestSVGTypeface : public SkTypeface {
-public:
-    SkTestSVGTypeface(const char* name,
-                      int upem,
-                      const SkFontMetrics& metrics,
-                      const SkSVGTestTypefaceGlyphData* data, int dataCount,
-                      const SkFontStyle& style);
-    ~SkTestSVGTypeface() override;
-    void getAdvance(SkGlyph* glyph) const;
-    void getFontMetrics(SkFontMetrics* metrics) const;
-
-    static sk_sp<SkTestSVGTypeface> Default();
-    void exportTtxCbdt(SkWStream*) const;
-    void exportTtxSbix(SkWStream*) const;
-    void exportTtxColr(SkWStream*) const;
-
-    struct GlyfLayerInfo {
-        GlyfLayerInfo(int layerColorIndex, SkIRect bounds)
-            : fLayerColorIndex(layerColorIndex)
-            , fBounds(bounds) {}
-        int fLayerColorIndex;
-        SkIRect fBounds;
-    };
-    struct GlyfInfo {
-        GlyfInfo() : fBounds(SkIRect::MakeEmpty()) {}
-        SkIRect fBounds;
-        SkTArray<GlyfLayerInfo> fLayers;
-    };
-protected:
-    void exportTtxCommon(SkWStream*, const char* type, const SkTArray<GlyfInfo>* = nullptr) const;
-
-    SkScalerContext* onCreateScalerContext(const SkScalerContextEffects&,
-                                           const SkDescriptor* desc) const override;
-    void onFilterRec(SkScalerContextRec* rec) const override;
-    void getGlyphToUnicodeMap(SkUnichar*) const override;
-    std::unique_ptr<SkAdvancedTypefaceMetrics> onGetAdvancedMetrics() const override;
-
-    SkStreamAsset* onOpenStream(int* ttcIndex) const override {
-        return nullptr;
-    }
-
-    sk_sp<SkTypeface> onMakeClone(const SkFontArguments& args) const override {
-        return sk_ref_sp(this);
-    }
-
-    void onGetFontDescriptor(SkFontDescriptor* desc, bool* isLocal) const override;
-
-    int onCharsToGlyphs(const void* chars, Encoding encoding,
-                        uint16_t glyphs[], int glyphCount) const override;
-
-    int onCountGlyphs() const override {
-        return fGlyphCount;
-    }
-
-    int onGetUPEM() const override {
-        return fUpem;
-    }
-
-    void onGetFamilyName(SkString* familyName) const override;
-    SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const override;
-
-    int onGetVariationDesignPosition(SkFontArguments::VariationPosition::Coordinate coordinates[],
-                                     int coordinateCount) const override
-    {
-        return 0;
-    }
-
-    int onGetVariationDesignParameters(SkFontParameters::Variation::Axis parameters[],
-                                       int parameterCount) const override
-    {
-        return 0;
-    }
-
-    int onGetTableTags(SkFontTableTag tags[]) const override {
-        return 0;
-    }
-
-    size_t onGetTableData(SkFontTableTag tag, size_t offset,
-                          size_t length, void* data) const override {
-        return 0;
-    }
-private:
-    struct Glyph {
-        Glyph();
-        ~Glyph();
-        sk_sp<SkSVGDOM> fSvg;
-        SkMutex fSvgMutex;
-        SkPoint fOrigin;
-        SkScalar fAdvance;
-    };
-    SkString fName;
-    int fUpem;
-    const SkFontMetrics fFontMetrics;
-    std::unique_ptr<Glyph[]> fGlyphs;
-    int fGlyphCount;
-    SkTHashMap<SkUnichar, SkGlyphID> fCMap;
-    friend class SkTestSVGScalerContext;
-};
-
-#endif
diff --git a/tools/fonts/SkTestTypeface.cpp b/tools/fonts/SkTestTypeface.cpp
deleted file mode 100644
index 5b2d6c9..0000000
--- a/tools/fonts/SkTestTypeface.cpp
+++ /dev/null
@@ -1,239 +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 "SkAdvancedTypefaceMetrics.h"
-#include "SkBitmap.h"
-#include "SkCanvas.h"
-#include "SkFontDescriptor.h"
-#include "SkFontMetrics.h"
-#include "SkFontPriv.h"
-#include "SkGlyph.h"
-#include "SkImageInfo.h"
-#include "SkMatrix.h"
-#include "SkOTUtils.h"
-#include "SkPaintPriv.h"
-#include "SkPath.h"
-#include "SkPoint.h"
-#include "SkRect.h"
-#include "SkScalerContext.h"
-#include "SkString.h"
-#include "SkTDArray.h"
-#include "SkTestTypeface.h"
-#include "SkTo.h"
-#include "SkUtils.h"
-
-#include <utility>
-
-class SkDescriptor;
-
-SkTestFont::SkTestFont(const SkTestFontData& fontData)
-    : INHERITED()
-    , fCharCodes(fontData.fCharCodes)
-    , fCharCodesCount(fontData.fCharCodes ? fontData.fCharCodesCount : 0)
-    , fWidths(fontData.fWidths)
-    , fMetrics(fontData.fMetrics)
-    , fName(fontData.fName)
-    , fPaths(nullptr)
-{
-    init(fontData.fPoints, fontData.fVerbs);
-}
-
-SkTestFont::~SkTestFont() {
-    for (unsigned index = 0; index < fCharCodesCount; ++index) {
-        delete fPaths[index];
-    }
-    delete[] fPaths;
-}
-
-SkGlyphID SkTestFont::glyphForUnichar(SkUnichar charCode) const {
-    for (size_t index = 0; index < fCharCodesCount; ++index) {
-        if (fCharCodes[index] == charCode) {
-            return SkTo<SkGlyphID>(index);
-        }
-    }
-    return 0;
-}
-
-void SkTestFont::init(const SkScalar* pts, const unsigned char* verbs) {
-    fPaths = new SkPath* [fCharCodesCount];
-    for (unsigned index = 0; index < fCharCodesCount; ++index) {
-        SkPath* path = new SkPath;
-        SkPath::Verb verb;
-        while ((verb = (SkPath::Verb) *verbs++) != SkPath::kDone_Verb) {
-            switch (verb) {
-                case SkPath::kMove_Verb:
-                    path->moveTo(pts[0], pts[1]);
-                    pts += 2;
-                    break;
-                case SkPath::kLine_Verb:
-                    path->lineTo(pts[0], pts[1]);
-                    pts += 2;
-                    break;
-                case SkPath::kQuad_Verb:
-                    path->quadTo(pts[0], pts[1], pts[2], pts[3]);
-                    pts += 4;
-                    break;
-                case SkPath::kCubic_Verb:
-                    path->cubicTo(pts[0], pts[1], pts[2], pts[3], pts[4], pts[5]);
-                    pts += 6;
-                    break;
-                case SkPath::kClose_Verb:
-                    path->close();
-                    break;
-                default:
-                    SkDEBUGFAIL("bad verb");
-                    return;
-            }
-        }
-        // This should make SkPath::getBounds() queries threadsafe.
-        path->updateBoundsCache();
-        fPaths[index] = path;
-    }
-}
-
-SkTestTypeface::SkTestTypeface(sk_sp<SkTestFont> testFont, const SkFontStyle& style)
-    : SkTypeface(style, false)
-    , fTestFont(std::move(testFont)) {
-}
-
-void SkTestTypeface::getAdvance(SkGlyph* glyph) {
-    SkGlyphID glyphID = glyph->getGlyphID();
-    glyphID = glyphID < fTestFont->fCharCodesCount ? glyphID : 0;
-
-    // TODO(benjaminwagner): Update users to use floats.
-    glyph->fAdvanceX = SkFixedToFloat(fTestFont->fWidths[glyphID]);
-    glyph->fAdvanceY = 0;
-}
-
-void SkTestTypeface::getFontMetrics(SkFontMetrics* metrics) {
-    *metrics = fTestFont->fMetrics;
-}
-
-void SkTestTypeface::getPath(SkGlyphID glyphID, SkPath* path) {
-    glyphID = glyphID < fTestFont->fCharCodesCount ? glyphID : 0;
-    *path = *fTestFont->fPaths[glyphID];
-}
-
-void SkTestTypeface::onFilterRec(SkScalerContextRec* rec) const {
-    rec->setHinting(kNo_SkFontHinting);
-}
-
-void SkTestTypeface::getGlyphToUnicodeMap(SkUnichar* glyphToUnicode) const {
-    unsigned glyphCount = fTestFont->fCharCodesCount;
-    for (unsigned gid = 0; gid < glyphCount; ++gid) {
-        glyphToUnicode[gid] = SkTo<SkUnichar>(fTestFont->fCharCodes[gid]);
-    }
-}
-
-std::unique_ptr<SkAdvancedTypefaceMetrics> SkTestTypeface::onGetAdvancedMetrics() const { // pdf only
-    std::unique_ptr<SkAdvancedTypefaceMetrics> info(new SkAdvancedTypefaceMetrics);
-    info->fFontName.set(fTestFont->fName);
-    return info;
-}
-
-void SkTestTypeface::onGetFontDescriptor(SkFontDescriptor* desc, bool* isLocal) const {
-    desc->setFamilyName(fTestFont->fName);
-    desc->setStyle(this->fontStyle());
-    *isLocal = false;
-}
-
-int SkTestTypeface::onCharsToGlyphs(const void* chars, Encoding encoding,
-                                    SkGlyphID glyphs[], int glyphCount) const {
-    auto utf8  = (const      char*)chars;
-    auto utf16 = (const  uint16_t*)chars;
-    auto utf32 = (const SkUnichar*)chars;
-
-    for (int i = 0; i < glyphCount; ++i) {
-        SkUnichar ch;
-        switch (encoding) {
-            case kUTF8_Encoding:  ch =  SkUTF8_NextUnichar(&utf8 ); break;
-            case kUTF16_Encoding: ch = SkUTF16_NextUnichar(&utf16); break;
-            case kUTF32_Encoding: ch =                    *utf32++; break;
-        }
-        if (glyphs) {
-            glyphs[i] = fTestFont->glyphForUnichar(ch);
-        }
-    }
-    return glyphCount;
-}
-
-void SkTestTypeface::onGetFamilyName(SkString* familyName) const {
-    *familyName = fTestFont->fName;
-}
-
-SkTypeface::LocalizedStrings* SkTestTypeface::onCreateFamilyNameIterator() const {
-    SkString familyName(fTestFont->fName);
-    SkString language("und"); //undetermined
-    return new SkOTUtils::LocalizedStrings_SingleName(familyName, language);
-}
-
-class SkTestScalerContext : public SkScalerContext {
-public:
-    SkTestScalerContext(sk_sp<SkTestTypeface> face, const SkScalerContextEffects& effects,
-                        const SkDescriptor* desc)
-        : SkScalerContext(std::move(face), effects, desc)
-    {
-        fRec.getSingleMatrix(&fMatrix);
-        this->forceGenerateImageFromPath();
-    }
-
-protected:
-    SkTestTypeface* getTestTypeface() const {
-        return static_cast<SkTestTypeface*>(this->getTypeface());
-    }
-
-    unsigned generateGlyphCount() override {
-        return this->getTestTypeface()->onCountGlyphs();
-    }
-
-    uint16_t generateCharToGlyph(SkUnichar uni) override {
-        uint16_t glyph;
-        (void) this->getTestTypeface()->onCharsToGlyphs((const void *) &uni,
-                                                        SkTypeface::kUTF32_Encoding, &glyph, 1);
-        return glyph;
-    }
-
-    bool generateAdvance(SkGlyph* glyph) override {
-        this->getTestTypeface()->getAdvance(glyph);
-
-        const SkVector advance = fMatrix.mapXY(SkFloatToScalar(glyph->fAdvanceX),
-                                               SkFloatToScalar(glyph->fAdvanceY));
-        glyph->fAdvanceX = SkScalarToFloat(advance.fX);
-        glyph->fAdvanceY = SkScalarToFloat(advance.fY);
-        return true;
-    }
-
-    void generateMetrics(SkGlyph* glyph) override {
-        glyph->zeroMetrics();
-        this->generateAdvance(glyph);
-        // Always generates from paths, so SkScalerContext::getMetrics will figure the bounds.
-    }
-
-    void generateImage(const SkGlyph&) override {
-        SK_ABORT("Should have generated from path.");
-    }
-
-    bool generatePath(SkGlyphID glyph, SkPath* path) override {
-        this->getTestTypeface()->getPath(glyph, path);
-        path->transform(fMatrix);
-        return true;
-    }
-
-    void generateFontMetrics(SkFontMetrics* metrics) override {
-        this->getTestTypeface()->getFontMetrics(metrics);
-        SkFontPriv::ScaleFontMetrics(metrics, fMatrix.getScaleY());
-    }
-
-private:
-    SkMatrix         fMatrix;
-};
-
-SkScalerContext* SkTestTypeface::onCreateScalerContext(
-    const SkScalerContextEffects& effects, const SkDescriptor* desc) const
-{
-    return new SkTestScalerContext(sk_ref_sp(const_cast<SkTestTypeface*>(this)), effects, desc);
-}
diff --git a/tools/fonts/SkTestTypeface.h b/tools/fonts/SkTestTypeface.h
deleted file mode 100644
index f2bd3d2..0000000
--- a/tools/fonts/SkTestTypeface.h
+++ /dev/null
@@ -1,126 +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.
- */
-
-#ifndef SkTestTypeface_DEFINED
-#define SkTestTypeface_DEFINED
-
-#include "SkFixed.h"
-#include "SkFontArguments.h"
-#include "SkFontMetrics.h"
-#include "SkFontStyle.h"
-#include "SkPaint.h"
-#include "SkRefCnt.h"
-#include "SkScalar.h"
-#include "SkTypeface.h"
-#include "SkTypes.h"
-
-#include <memory>
-
-class SkDescriptor;
-class SkFontDescriptor;
-class SkGlyph;
-class SkPath;
-class SkScalerContext;
-class SkStreamAsset;
-class SkString;
-class SkTestFont;
-struct SkAdvancedTypefaceMetrics;
-struct SkScalerContextEffects;
-struct SkScalerContextRec;
-
-struct SkTestFontData {
-    const SkScalar* fPoints;
-    const unsigned char* fVerbs;
-    const SkUnichar* fCharCodes;
-    const size_t fCharCodesCount;
-    const SkFixed* fWidths;
-    const SkFontMetrics& fMetrics;
-    const char* fName;
-    SkFontStyle fStyle;
-};
-
-class SkTestFont : public SkRefCnt {
-public:
-    SkTestFont(const SkTestFontData& );
-    virtual ~SkTestFont();
-    SkGlyphID glyphForUnichar(SkUnichar charCode) const;
-    void init(const SkScalar* pts, const unsigned char* verbs);
-private:
-    const SkUnichar* fCharCodes;
-    const size_t fCharCodesCount;
-    const SkFixed* fWidths;
-    const SkFontMetrics& fMetrics;
-    const char* fName;
-    SkPath** fPaths;
-    friend class SkTestTypeface;
-    typedef SkRefCnt INHERITED;
-};
-
-
-class SkTestTypeface : public SkTypeface {
-public:
-    SkTestTypeface(sk_sp<SkTestFont>, const SkFontStyle& style);
-    void getAdvance(SkGlyph* glyph);
-    void getFontMetrics(SkFontMetrics* metrics);
-    void getPath(SkGlyphID glyph, SkPath* path);
-protected:
-    SkScalerContext* onCreateScalerContext(const SkScalerContextEffects&,
-                                           const SkDescriptor* desc) const override;
-    void onFilterRec(SkScalerContextRec* rec) const override;
-    void getGlyphToUnicodeMap(SkUnichar* glyphToUnicode) const override;
-    std::unique_ptr<SkAdvancedTypefaceMetrics> onGetAdvancedMetrics() const override;
-
-    SkStreamAsset* onOpenStream(int* ttcIndex) const override {
-        return nullptr;
-    }
-
-    sk_sp<SkTypeface> onMakeClone(const SkFontArguments& args) const override {
-        return sk_ref_sp(this);
-    }
-
-    void onGetFontDescriptor(SkFontDescriptor* desc, bool* isLocal) const override;
-
-    int onCharsToGlyphs(const void* chars, Encoding encoding,
-                        uint16_t glyphs[], int glyphCount) const override;
-
-    int onCountGlyphs() const override {
-        return (int) fTestFont->fCharCodesCount;
-    }
-
-    int onGetUPEM() const override {
-        return 2048;
-    }
-
-    void onGetFamilyName(SkString* familyName) const override;
-    SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const override;
-
-    int onGetVariationDesignPosition(SkFontArguments::VariationPosition::Coordinate coordinates[],
-                                     int coordinateCount) const override
-    {
-        return 0;
-    }
-
-    int onGetVariationDesignParameters(SkFontParameters::Variation::Axis parameters[],
-                                       int parameterCount) const override
-    {
-        return 0;
-    }
-
-    int onGetTableTags(SkFontTableTag tags[]) const override {
-        return 0;
-    }
-
-    size_t onGetTableData(SkFontTableTag tag, size_t offset,
-                          size_t length, void* data) const override {
-        return 0;
-    }
-private:
-    sk_sp<SkTestFont> fTestFont;
-    friend class SkTestScalerContext;
-};
-
-#endif
diff --git a/tools/fonts/TestEmptyTypeface.h b/tools/fonts/TestEmptyTypeface.h
new file mode 100644
index 0000000..bd630d3
--- /dev/null
+++ b/tools/fonts/TestEmptyTypeface.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef TestEmptyTypeface_DEFINED
+#define TestEmptyTypeface_DEFINED
+
+#include "SkTypeface.h"
+
+class TestEmptyTypeface : public SkTypeface {
+public:
+    static sk_sp<SkTypeface> Make() { return sk_sp<SkTypeface>(new TestEmptyTypeface); }
+
+protected:
+    TestEmptyTypeface() : SkTypeface(SkFontStyle(), true) {}
+
+    std::unique_ptr<SkStreamAsset> onOpenStream(int* ttcIndex) const override { return nullptr; }
+    sk_sp<SkTypeface>              onMakeClone(const SkFontArguments& args) const override {
+        return sk_ref_sp(this);
+    }
+    SkScalerContext* onCreateScalerContext(const SkScalerContextEffects&,
+                                           const SkDescriptor*) const override {
+        return nullptr;
+    }
+    void                                       onFilterRec(SkScalerContextRec*) const override {}
+    std::unique_ptr<SkAdvancedTypefaceMetrics> onGetAdvancedMetrics() const override {
+        return nullptr;
+    }
+    void        onGetFontDescriptor(SkFontDescriptor*, bool*) const override {}
+    virtual int onCharsToGlyphs(const void* chars,
+                                Encoding    encoding,
+                                uint16_t    glyphs[],
+                                int         glyphCount) const override {
+        if (glyphs && glyphCount > 0) {
+            sk_bzero(glyphs, glyphCount * sizeof(glyphs[0]));
+        }
+        return 0;
+    }
+    int onCountGlyphs() const override { return 0; }
+    int onGetUPEM() const override { return 0; }
+    class EmptyLocalizedStrings : public SkTypeface::LocalizedStrings {
+    public:
+        bool next(SkTypeface::LocalizedString*) override { return false; }
+    };
+    void onGetFamilyName(SkString* familyName) const override { familyName->reset(); }
+    SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const override {
+        return new EmptyLocalizedStrings;
+    }
+    int onGetVariationDesignPosition(SkFontArguments::VariationPosition::Coordinate coordinates[],
+                                     int coordinateCount) const override {
+        return 0;
+    }
+    int onGetVariationDesignParameters(SkFontParameters::Variation::Axis parameters[],
+                                       int parameterCount) const override {
+        return 0;
+    }
+    int    onGetTableTags(SkFontTableTag tags[]) const override { return 0; }
+    size_t onGetTableData(SkFontTableTag, size_t, size_t, void*) const override { return 0; }
+};
+
+#endif  // TestEmptyTypeface_DEFINED
diff --git a/tools/fonts/TestFontMgr.cpp b/tools/fonts/TestFontMgr.cpp
new file mode 100644
index 0000000..2b4daa8
--- /dev/null
+++ b/tools/fonts/TestFontMgr.cpp
@@ -0,0 +1,203 @@
+/*
+ * 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 "TestFontMgr.h"
+#include "SkFontDescriptor.h"
+#include "TestTypeface.h"
+#include "ToolUtils.h"
+
+#ifdef SK_XML
+#include "TestSVGTypeface.h"
+#endif
+
+#include <vector>
+
+namespace {
+
+#include "test_font_monospace.inc"
+#include "test_font_sans_serif.inc"
+#include "test_font_serif.inc"
+
+#include "test_font_index.inc"
+
+class FontStyleSet final : public SkFontStyleSet {
+public:
+    FontStyleSet(const char* familyName) : fFamilyName(familyName) {}
+    struct TypefaceEntry {
+        TypefaceEntry(sk_sp<SkTypeface> typeface, SkFontStyle style, const char* styleName)
+                : fTypeface(std::move(typeface)), fStyle(style), fStyleName(styleName) {}
+        sk_sp<SkTypeface> fTypeface;
+        SkFontStyle       fStyle;
+        const char*       fStyleName;
+    };
+
+    int count() override { return fTypefaces.size(); }
+
+    void getStyle(int index, SkFontStyle* style, SkString* name) override {
+        if (style) {
+            *style = fTypefaces[index].fStyle;
+        }
+        if (name) {
+            *name = fTypefaces[index].fStyleName;
+        }
+    }
+
+    SkTypeface* createTypeface(int index) override {
+        return SkRef(fTypefaces[index].fTypeface.get());
+    }
+
+    SkTypeface* matchStyle(const SkFontStyle& pattern) override {
+        return this->matchStyleCSS3(pattern);
+    }
+
+    SkString getFamilyName() { return fFamilyName; }
+
+    std::vector<TypefaceEntry> fTypefaces;
+    SkString                   fFamilyName;
+};
+
+class FontMgr final : public SkFontMgr {
+public:
+    FontMgr() {
+        for (const auto& sub : gSubFonts) {
+            sk_sp<TestTypeface> typeface =
+                    sk_make_sp<TestTypeface>(sk_make_sp<SkTestFont>(sub.fFont), sub.fStyle);
+            bool defaultFamily = false;
+            if (&sub - gSubFonts == gDefaultFontIndex) {
+                defaultFamily    = true;
+                fDefaultTypeface = typeface;
+            }
+            bool found = false;
+            for (const auto& family : fFamilies) {
+                if (family->getFamilyName().equals(sub.fFamilyName)) {
+                    family->fTypefaces.emplace_back(
+                            std::move(typeface), sub.fStyle, sub.fStyleName);
+                    found = true;
+                    if (defaultFamily) {
+                        fDefaultFamily = family;
+                    }
+                    break;
+                }
+            }
+            if (!found) {
+                fFamilies.emplace_back(sk_make_sp<FontStyleSet>(sub.fFamilyName));
+                fFamilies.back()->fTypefaces.emplace_back(
+                        // NOLINTNEXTLINE(bugprone-use-after-move)
+                        std::move(typeface),
+                        sub.fStyle,
+                        sub.fStyleName);
+                if (defaultFamily) {
+                    fDefaultFamily = fFamilies.back();
+                }
+            }
+        }
+#ifdef SK_XML
+        fFamilies.emplace_back(sk_make_sp<FontStyleSet>("Emoji"));
+        fFamilies.back()->fTypefaces.emplace_back(
+                TestSVGTypeface::Default(), SkFontStyle::Normal(), "Normal");
+
+        fFamilies.emplace_back(sk_make_sp<FontStyleSet>("Planet"));
+        fFamilies.back()->fTypefaces.emplace_back(
+                TestSVGTypeface::Planets(), SkFontStyle::Normal(), "Normal");
+#endif
+    }
+
+    int onCountFamilies() const override { return fFamilies.size(); }
+
+    void onGetFamilyName(int index, SkString* familyName) const override {
+        *familyName = fFamilies[index]->getFamilyName();
+    }
+
+    SkFontStyleSet* onCreateStyleSet(int index) const override {
+        sk_sp<SkFontStyleSet> ref = fFamilies[index];
+        return ref.release();
+    }
+
+    SkFontStyleSet* onMatchFamily(const char familyName[]) const override {
+        if (familyName) {
+            if (strstr(familyName, "ono")) {
+                return this->createStyleSet(0);
+            }
+            if (strstr(familyName, "ans")) {
+                return this->createStyleSet(1);
+            }
+            if (strstr(familyName, "erif")) {
+                return this->createStyleSet(2);
+            }
+#ifdef SK_XML
+            if (strstr(familyName, "oji")) {
+                return this->createStyleSet(6);
+            }
+            if (strstr(familyName, "Planet")) {
+                return this->createStyleSet(7);
+            }
+#endif
+        }
+        return nullptr;
+    }
+
+    SkTypeface* onMatchFamilyStyle(const char         familyName[],
+                                   const SkFontStyle& style) const override {
+        sk_sp<SkFontStyleSet> styleSet(this->matchFamily(familyName));
+        return styleSet->matchStyle(style);
+    }
+
+    SkTypeface* onMatchFamilyStyleCharacter(const char         familyName[],
+                                            const SkFontStyle& style,
+                                            const char*        bcp47[],
+                                            int                bcp47Count,
+                                            SkUnichar          character) const override {
+        (void)bcp47;
+        (void)bcp47Count;
+        (void)character;
+        return this->matchFamilyStyle(familyName, style);
+    }
+
+    SkTypeface* onMatchFaceStyle(const SkTypeface* tf, const SkFontStyle& style) const override {
+        SkString familyName;
+        tf->getFamilyName(&familyName);
+        return this->matchFamilyStyle(familyName.c_str(), style);
+    }
+
+    sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int ttcIndex) const override { return nullptr; }
+    sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>,
+                                            int ttcIndex) const override {
+        return nullptr;
+    }
+    sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,
+                                           const SkFontArguments&) const override {
+        return nullptr;
+    }
+    sk_sp<SkTypeface> onMakeFromFontData(std::unique_ptr<SkFontData>) const override {
+        return nullptr;
+    }
+    sk_sp<SkTypeface> onMakeFromFile(const char path[], int ttcIndex) const override {
+        return nullptr;
+    }
+
+    sk_sp<SkTypeface> onLegacyMakeTypeface(const char  familyName[],
+                                           SkFontStyle style) const override {
+        if (familyName == nullptr) {
+            return sk_sp<SkTypeface>(fDefaultFamily->matchStyle(style));
+        }
+        sk_sp<SkTypeface> typeface = sk_sp<SkTypeface>(this->matchFamilyStyle(familyName, style));
+        if (!typeface) {
+            typeface = fDefaultTypeface;
+        }
+        return typeface;
+    }
+
+private:
+    std::vector<sk_sp<FontStyleSet>> fFamilies;
+    sk_sp<FontStyleSet>              fDefaultFamily;
+    sk_sp<SkTypeface>                fDefaultTypeface;
+};
+}  // namespace
+
+namespace ToolUtils {
+sk_sp<SkFontMgr> MakePortableFontMgr() { return sk_make_sp<FontMgr>(); }
+}  // namespace ToolUtils
diff --git a/tools/fonts/TestFontMgr.h b/tools/fonts/TestFontMgr.h
new file mode 100644
index 0000000..f39766b
--- /dev/null
+++ b/tools/fonts/TestFontMgr.h
@@ -0,0 +1,19 @@
+/*
+ * 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 TestFontMgr_DEFINED
+#define TestFontMgr_DEFINED
+
+#include "SkFontMgr.h"
+
+// An SkFontMgr that always uses ToolUtils::create_portable_typeface().
+
+namespace ToolUtils {
+sk_sp<SkFontMgr> MakePortableFontMgr();
+}  // namespace ToolUtils
+
+#endif  // TestFontMgr_DEFINED
diff --git a/tools/fonts/TestSVGTypeface.cpp b/tools/fonts/TestSVGTypeface.cpp
new file mode 100644
index 0000000..4a78084
--- /dev/null
+++ b/tools/fonts/TestSVGTypeface.cpp
@@ -0,0 +1,1454 @@
+/*
+ * 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 "TestSVGTypeface.h"
+
+#ifdef SK_XML
+
+#include "Resources.h"
+#include "SkAdvancedTypefaceMetrics.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkColor.h"
+#include "SkData.h"
+#include "SkEncodedImageFormat.h"
+#include "SkFontDescriptor.h"
+#include "SkFontPriv.h"
+#include "SkFontStyle.h"
+#include "SkGeometry.h"
+#include "SkGlyph.h"
+#include "SkImage.h"
+#include "SkImageInfo.h"
+#include "SkMask.h"
+#include "SkMatrix.h"
+#include "SkNoDrawCanvas.h"
+#include "SkOTUtils.h"
+#include "SkPaintPriv.h"
+#include "SkPath.h"
+#include "SkPathEffect.h"
+#include "SkPathOps.h"
+#include "SkPathPriv.h"
+#include "SkPixmap.h"
+#include "SkPointPriv.h"
+#include "SkRRect.h"
+#include "SkSVGDOM.h"
+#include "SkScalerContext.h"
+#include "SkSize.h"
+#include "SkStream.h"
+#include "SkSurface.h"
+#include "SkTDArray.h"
+#include "SkTemplates.h"
+#include "SkUtils.h"
+
+#include <utility>
+
+class SkDescriptor;
+
+TestSVGTypeface::TestSVGTypeface(const char*                              name,
+                                 int                                      upem,
+                                 const SkFontMetrics&                     fontMetrics,
+                                 SkSpan<const SkSVGTestTypefaceGlyphData> data,
+                                 const SkFontStyle&                       style)
+        : SkTypeface(style, false)
+        , fName(name)
+        , fUpem(upem)
+        , fFontMetrics(fontMetrics)
+        , fGlyphs(new Glyph[data.size()])
+        , fGlyphCount(data.size()) {
+    for (size_t i = 0; i < data.size(); ++i) {
+        const SkSVGTestTypefaceGlyphData& datum  = data[i];
+        fCMap.set(datum.fUnicode, i);
+        fGlyphs[i].fAdvance      = datum.fAdvance;
+        fGlyphs[i].fOrigin       = datum.fOrigin;
+        fGlyphs[i].fResourcePath = datum.fSvgResourcePath;
+    }
+}
+
+template <typename Fn>
+void TestSVGTypeface::Glyph::withSVG(Fn&& fn) const {
+    SkAutoExclusive lock(fSvgMutex);
+
+    if (!fParsedSvg) {
+        fParsedSvg = true;
+
+        std::unique_ptr<SkStreamAsset> stream = GetResourceAsStream(fResourcePath);
+        if (!stream) {
+            return;
+        }
+
+        sk_sp<SkSVGDOM> svg = SkSVGDOM::MakeFromStream(*stream.get());
+        if (!svg) {
+            return;
+        }
+
+        if (svg->containerSize().isEmpty()) {
+            return;
+        }
+
+        fSvg = std::move(svg);
+    }
+
+    if (fSvg) {
+        fn(*fSvg);
+    }
+}
+
+SkSize TestSVGTypeface::Glyph::size() const {
+    SkSize size = SkSize::MakeEmpty();
+    this->withSVG([&](const SkSVGDOM& svg){
+        size = svg.containerSize();
+    });
+    return size;
+}
+
+void TestSVGTypeface::Glyph::render(SkCanvas* canvas) const {
+    this->withSVG([&](const SkSVGDOM& svg){
+        svg.render(canvas);
+    });
+}
+
+TestSVGTypeface::~TestSVGTypeface() {}
+
+TestSVGTypeface::Glyph::Glyph() : fOrigin{0, 0}, fAdvance(0) {}
+TestSVGTypeface::Glyph::~Glyph() {}
+
+void TestSVGTypeface::getAdvance(SkGlyph* glyph) const {
+    SkGlyphID glyphID = glyph->getGlyphID();
+    glyphID           = glyphID < fGlyphCount ? glyphID : 0;
+
+    glyph->fAdvanceX = fGlyphs[glyphID].fAdvance;
+    glyph->fAdvanceY = 0;
+}
+
+void TestSVGTypeface::getFontMetrics(SkFontMetrics* metrics) const { *metrics = fFontMetrics; }
+
+void TestSVGTypeface::onFilterRec(SkScalerContextRec* rec) const {
+    rec->setHinting(kNo_SkFontHinting);
+}
+
+void TestSVGTypeface::getGlyphToUnicodeMap(SkUnichar* glyphToUnicode) const {
+    SkDEBUGCODE(unsigned glyphCount = this->countGlyphs());
+    fCMap.foreach ([=](const SkUnichar& c, const SkGlyphID& g) {
+        SkASSERT(g < glyphCount);
+        glyphToUnicode[g] = c;
+    });
+}
+
+std::unique_ptr<SkAdvancedTypefaceMetrics> TestSVGTypeface::onGetAdvancedMetrics() const {
+    std::unique_ptr<SkAdvancedTypefaceMetrics> info(new SkAdvancedTypefaceMetrics);
+    info->fFontName = fName;
+    return info;
+}
+
+void TestSVGTypeface::onGetFontDescriptor(SkFontDescriptor* desc, bool* isLocal) const {
+    desc->setFamilyName(fName.c_str());
+    desc->setStyle(this->fontStyle());
+    *isLocal = false;
+}
+
+int TestSVGTypeface::onCharsToGlyphs(const void* chars,
+                                     Encoding    encoding,
+                                     uint16_t    glyphs[],
+                                     int         glyphCount) const {
+    auto utf8  = (const char*)chars;
+    auto utf16 = (const uint16_t*)chars;
+    auto utf32 = (const SkUnichar*)chars;
+
+    for (int i = 0; i < glyphCount; i++) {
+        SkUnichar ch;
+        switch (encoding) {
+            case kUTF8_Encoding: ch = SkUTF8_NextUnichar(&utf8); break;
+            case kUTF16_Encoding: ch = SkUTF16_NextUnichar(&utf16); break;
+            case kUTF32_Encoding: ch = *utf32++; break;
+        }
+        if (glyphs) {
+            SkGlyphID* g = fCMap.find(ch);
+            glyphs[i]    = g ? *g : 0;
+        }
+    }
+    return glyphCount;
+}
+
+void TestSVGTypeface::onGetFamilyName(SkString* familyName) const { *familyName = fName; }
+
+SkTypeface::LocalizedStrings* TestSVGTypeface::onCreateFamilyNameIterator() const {
+    SkString familyName(fName);
+    SkString language("und");  // undetermined
+    return new SkOTUtils::LocalizedStrings_SingleName(familyName, language);
+}
+
+class SkTestSVGScalerContext : public SkScalerContext {
+public:
+    SkTestSVGScalerContext(sk_sp<TestSVGTypeface>        face,
+                           const SkScalerContextEffects& effects,
+                           const SkDescriptor*           desc)
+            : SkScalerContext(std::move(face), effects, desc) {
+        fRec.getSingleMatrix(&fMatrix);
+        SkScalar upem = this->getTestSVGTypeface()->fUpem;
+        fMatrix.preScale(1.f / upem, 1.f / upem);
+    }
+
+protected:
+    TestSVGTypeface* getTestSVGTypeface() const {
+        return static_cast<TestSVGTypeface*>(this->getTypeface());
+    }
+
+    unsigned generateGlyphCount() override { return this->getTestSVGTypeface()->countGlyphs(); }
+
+    uint16_t generateCharToGlyph(SkUnichar u) override {
+        return this->getTestSVGTypeface()->unicharToGlyph(u);
+    }
+
+    bool generateAdvance(SkGlyph* glyph) override {
+        this->getTestSVGTypeface()->getAdvance(glyph);
+
+        const SkVector advance =
+                fMatrix.mapXY(SkFloatToScalar(glyph->fAdvanceX), SkFloatToScalar(glyph->fAdvanceY));
+        glyph->fAdvanceX = SkScalarToFloat(advance.fX);
+        glyph->fAdvanceY = SkScalarToFloat(advance.fY);
+        return true;
+    }
+
+    void generateMetrics(SkGlyph* glyph) override {
+        SkGlyphID glyphID = glyph->getGlyphID();
+        glyphID           = glyphID < this->getTestSVGTypeface()->fGlyphCount ? glyphID : 0;
+
+        glyph->zeroMetrics();
+        glyph->fMaskFormat = SkMask::kARGB32_Format;
+        this->generateAdvance(glyph);
+
+        TestSVGTypeface::Glyph& glyphData = this->getTestSVGTypeface()->fGlyphs[glyphID];
+
+        SkSize containerSize = glyphData.size();
+        SkRect newBounds = SkRect::MakeXYWH(glyphData.fOrigin.fX,
+                                           -glyphData.fOrigin.fY,
+                                            containerSize.fWidth,
+                                            containerSize.fHeight);
+        fMatrix.mapRect(&newBounds);
+        SkScalar dx = SkFixedToScalar(glyph->getSubXFixed());
+        SkScalar dy = SkFixedToScalar(glyph->getSubYFixed());
+        newBounds.offset(dx, dy);
+
+        SkIRect ibounds;
+        newBounds.roundOut(&ibounds);
+        glyph->fLeft   = ibounds.fLeft;
+        glyph->fTop    = ibounds.fTop;
+        glyph->fWidth  = ibounds.width();
+        glyph->fHeight = ibounds.height();
+    }
+
+    void generateImage(const SkGlyph& glyph) override {
+        SkGlyphID glyphID = glyph.getGlyphID();
+        glyphID           = glyphID < this->getTestSVGTypeface()->fGlyphCount ? glyphID : 0;
+
+        SkBitmap bm;
+        // TODO: this should be SkImageInfo::MakeS32 when that passes all the tests.
+        bm.installPixels(SkImageInfo::MakeN32(glyph.fWidth, glyph.fHeight, kPremul_SkAlphaType),
+                         glyph.fImage,
+                         glyph.rowBytes());
+        bm.eraseColor(0);
+
+        TestSVGTypeface::Glyph& glyphData = this->getTestSVGTypeface()->fGlyphs[glyphID];
+
+        SkScalar dx = SkFixedToScalar(glyph.getSubXFixed());
+        SkScalar dy = SkFixedToScalar(glyph.getSubYFixed());
+
+        SkCanvas canvas(bm);
+        canvas.translate(-glyph.fLeft, -glyph.fTop);
+        canvas.translate(dx, dy);
+        canvas.concat(fMatrix);
+        canvas.translate(glyphData.fOrigin.fX, -glyphData.fOrigin.fY);
+
+        glyphData.render(&canvas);
+    }
+
+    bool generatePath(SkGlyphID glyph, SkPath* path) override {
+        path->reset();
+        return false;
+    }
+
+    void generateFontMetrics(SkFontMetrics* metrics) override {
+        this->getTestSVGTypeface()->getFontMetrics(metrics);
+        SkFontPriv::ScaleFontMetrics(metrics, fMatrix.getScaleY());
+    }
+
+private:
+    SkMatrix fMatrix;
+};
+
+SkScalerContext* TestSVGTypeface::onCreateScalerContext(const SkScalerContextEffects& e,
+                                                        const SkDescriptor*           desc) const {
+    return new SkTestSVGScalerContext(sk_ref_sp(const_cast<TestSVGTypeface*>(this)), e, desc);
+}
+
+sk_sp<TestSVGTypeface> TestSVGTypeface::Default() {
+    // Recommended that the first four be .notdef, .null, CR, space
+    constexpr const static SkSVGTestTypefaceGlyphData glyphs[] = {
+            {"fonts/svg/notdef.svg", {100, 800}, 800, 0x0},      // .notdef
+            {"fonts/svg/empty.svg", {0, 0}, 800, 0x0020},        // space
+            {"fonts/svg/diamond.svg", {100, 800}, 800, 0x2662},  // ♢
+            {"fonts/svg/smile.svg", {0, 800}, 800, 0x1F600},     // 😀
+    };
+    SkFontMetrics metrics;
+    metrics.fFlags = SkFontMetrics::kUnderlineThicknessIsValid_Flag |
+                     SkFontMetrics::kUnderlinePositionIsValid_Flag |
+                     SkFontMetrics::kStrikeoutThicknessIsValid_Flag |
+                     SkFontMetrics::kStrikeoutPositionIsValid_Flag;
+    metrics.fTop                = -800;
+    metrics.fAscent             = -800;
+    metrics.fDescent            = 200;
+    metrics.fBottom             = 200;
+    metrics.fLeading            = 100;
+    metrics.fAvgCharWidth       = 1000;
+    metrics.fMaxCharWidth       = 1000;
+    metrics.fXMin               = 0;
+    metrics.fXMax               = 1000;
+    metrics.fXHeight            = 500;
+    metrics.fCapHeight          = 700;
+    metrics.fUnderlineThickness = 40;
+    metrics.fUnderlinePosition  = 20;
+    metrics.fStrikeoutThickness = 20;
+    metrics.fStrikeoutPosition  = -400;
+
+    class DefaultTypeface : public TestSVGTypeface {
+        using TestSVGTypeface::TestSVGTypeface;
+
+        bool getPathOp(SkColor color, SkPathOp* op) const override {
+            if ((SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color)) / 3 > 0x20) {
+                *op = SkPathOp::kDifference_SkPathOp;
+            } else {
+                *op = SkPathOp::kUnion_SkPathOp;
+            }
+            return true;
+        }
+    };
+    return sk_make_sp<DefaultTypeface>("Emoji", 1000, metrics, glyphs, SkFontStyle::Normal());
+}
+
+sk_sp<TestSVGTypeface> TestSVGTypeface::Planets() {
+    // Recommended that the first four be .notdef, .null, CR, space
+    constexpr const static SkSVGTestTypefaceGlyphData glyphs[] = {
+            {"fonts/svg/planets/pluto.svg", {0, 20}, 60, 0x0},             // .notdef
+            {"fonts/svg/empty.svg", {0, 0}, 400, 0x0020},                  // space
+            {"fonts/svg/planets/mercury.svg", {0, 45}, 120, 0x263F},       // ☿
+            {"fonts/svg/planets/venus.svg", {0, 100}, 240, 0x2640},        // ♀
+            {"fonts/svg/planets/earth.svg", {0, 100}, 240, 0x2641},        // ♁
+            {"fonts/svg/planets/mars.svg", {0, 50}, 130, 0x2642},          // ♂
+            {"fonts/svg/planets/jupiter.svg", {0, 1000}, 2200, 0x2643},    // ♃
+            {"fonts/svg/planets/saturn.svg", {-300, 1500}, 2600, 0x2644},  // ♄
+            {"fonts/svg/planets/uranus.svg", {0, 375}, 790, 0x2645},       // ♅
+            {"fonts/svg/planets/neptune.svg", {0, 350}, 740, 0x2646},      // ♆
+    };
+    SkFontMetrics metrics;
+    metrics.fFlags = SkFontMetrics::kUnderlineThicknessIsValid_Flag |
+                     SkFontMetrics::kUnderlinePositionIsValid_Flag |
+                     SkFontMetrics::kStrikeoutThicknessIsValid_Flag |
+                     SkFontMetrics::kStrikeoutPositionIsValid_Flag;
+    metrics.fTop                = -1500;
+    metrics.fAscent             = -200;
+    metrics.fDescent            = 50;
+    metrics.fBottom             = 1558;
+    metrics.fLeading            = 10;
+    metrics.fAvgCharWidth       = 200;
+    metrics.fMaxCharWidth       = 200;
+    metrics.fXMin               = -300;
+    metrics.fXMax               = 2566;
+    metrics.fXHeight            = 100;
+    metrics.fCapHeight          = 180;
+    metrics.fUnderlineThickness = 8;
+    metrics.fUnderlinePosition  = 2;
+    metrics.fStrikeoutThickness = 2;
+    metrics.fStrikeoutPosition  = -80;
+
+    class PlanetTypeface : public TestSVGTypeface {
+        using TestSVGTypeface::TestSVGTypeface;
+
+        bool getPathOp(SkColor color, SkPathOp* op) const override {
+            *op = SkPathOp::kUnion_SkPathOp;
+            return true;
+        }
+    };
+    return sk_make_sp<PlanetTypeface>("Planets", 200, metrics, glyphs, SkFontStyle::Normal());
+}
+
+void TestSVGTypeface::exportTtxCommon(SkWStream*                out,
+                                      const char*               type,
+                                      const SkTArray<GlyfInfo>* glyfInfo) const {
+    int totalGlyphs = fGlyphCount;
+    out->writeText("  <GlyphOrder>\n");
+    for (int i = 0; i < fGlyphCount; ++i) {
+        out->writeText("    <GlyphID name=\"glyf");
+        out->writeHexAsText(i, 4);
+        out->writeText("\"/>\n");
+    }
+    if (glyfInfo) {
+        for (int i = 0; i < fGlyphCount; ++i) {
+            for (int j = 0; j < (*glyfInfo)[i].fLayers.count(); ++j) {
+                out->writeText("    <GlyphID name=\"glyf");
+                out->writeHexAsText(i, 4);
+                out->writeText("l");
+                out->writeHexAsText(j, 4);
+                out->writeText("\"/>\n");
+                ++totalGlyphs;
+            }
+        }
+    }
+    out->writeText("  </GlyphOrder>\n");
+
+    out->writeText("  <head>\n");
+    out->writeText("    <tableVersion value=\"1.0\"/>\n");
+    out->writeText("    <fontRevision value=\"1.0\"/>\n");
+    out->writeText("    <checkSumAdjustment value=\"0xa9c3274\"/>\n");
+    out->writeText("    <magicNumber value=\"0x5f0f3cf5\"/>\n");
+    out->writeText("    <flags value=\"00000000 00011011\"/>\n");
+    out->writeText("    <unitsPerEm value=\"");
+    out->writeDecAsText(fUpem);
+    out->writeText("\"/>\n");
+    out->writeText("    <created value=\"Thu Feb 15 12:55:49 2018\"/>\n");
+    out->writeText("    <modified value=\"Thu Feb 15 12:55:49 2018\"/>\n");
+    // TODO: not recalculated for bitmap fonts?
+    out->writeText("    <xMin value=\"");
+    out->writeScalarAsText(fFontMetrics.fXMin);
+    out->writeText("\"/>\n");
+    out->writeText("    <yMin value=\"");
+    out->writeScalarAsText(-fFontMetrics.fBottom);
+    out->writeText("\"/>\n");
+    out->writeText("    <xMax value=\"");
+    out->writeScalarAsText(fFontMetrics.fXMax);
+    out->writeText("\"/>\n");
+    out->writeText("    <yMax value=\"");
+    out->writeScalarAsText(-fFontMetrics.fTop);
+    out->writeText("\"/>\n");
+
+    char macStyle[16] = {
+            '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0'};
+    if (this->fontStyle().weight() >= SkFontStyle::Bold().weight()) {
+        macStyle[0xF - 0x0] = '1';  // Bold
+    }
+    switch (this->fontStyle().slant()) {
+        case SkFontStyle::kUpright_Slant: break;
+        case SkFontStyle::kItalic_Slant:
+            macStyle[0xF - 0x1] = '1';  // Italic
+            break;
+        case SkFontStyle::kOblique_Slant:
+            macStyle[0xF - 0x1] = '1';  // Italic
+            break;
+        default: SK_ABORT("Unknown slant.");
+    }
+    if (this->fontStyle().width() <= SkFontStyle::kCondensed_Width) {
+        macStyle[0xF - 0x5] = '1';  // Condensed
+    } else if (this->fontStyle().width() >= SkFontStyle::kExpanded_Width) {
+        macStyle[0xF - 0x6] = '1';  // Extended
+    }
+    out->writeText("    <macStyle value=\"");
+    out->write(macStyle, 8);
+    out->writeText(" ");
+    out->write(macStyle + 8, 8);
+    out->writeText("\"/>\n");
+    out->writeText("    <lowestRecPPEM value=\"8\"/>\n");
+    out->writeText("    <fontDirectionHint value=\"2\"/>\n");
+    out->writeText("    <indexToLocFormat value=\"0\"/>\n");
+    out->writeText("    <glyphDataFormat value=\"0\"/>\n");
+    out->writeText("  </head>\n");
+
+    out->writeText("  <hhea>\n");
+    out->writeText("    <tableVersion value=\"0x00010000\"/>\n");
+    out->writeText("    <ascent value=\"");
+    out->writeDecAsText(-fFontMetrics.fAscent);
+    out->writeText("\"/>\n");
+    out->writeText("    <descent value=\"");
+    out->writeDecAsText(-fFontMetrics.fDescent);
+    out->writeText("\"/>\n");
+    out->writeText("    <lineGap value=\"");
+    out->writeDecAsText(fFontMetrics.fLeading);
+    out->writeText("\"/>\n");
+    out->writeText("    <advanceWidthMax value=\"0\"/>\n");
+    out->writeText("    <minLeftSideBearing value=\"0\"/>\n");
+    out->writeText("    <minRightSideBearing value=\"0\"/>\n");
+    out->writeText("    <xMaxExtent value=\"");
+    out->writeScalarAsText(fFontMetrics.fXMax - fFontMetrics.fXMin);
+    out->writeText("\"/>\n");
+    out->writeText("    <caretSlopeRise value=\"1\"/>\n");
+    out->writeText("    <caretSlopeRun value=\"0\"/>\n");
+    out->writeText("    <caretOffset value=\"0\"/>\n");
+    out->writeText("    <reserved0 value=\"0\"/>\n");
+    out->writeText("    <reserved1 value=\"0\"/>\n");
+    out->writeText("    <reserved2 value=\"0\"/>\n");
+    out->writeText("    <reserved3 value=\"0\"/>\n");
+    out->writeText("    <metricDataFormat value=\"0\"/>\n");
+    out->writeText("    <numberOfHMetrics value=\"0\"/>\n");
+    out->writeText("  </hhea>\n");
+
+    // Some of this table is going to be re-calculated, but we have to write it out anyway.
+    out->writeText("  <maxp>\n");
+    out->writeText("    <tableVersion value=\"0x10000\"/>\n");
+    out->writeText("    <numGlyphs value=\"");
+    out->writeDecAsText(totalGlyphs);
+    out->writeText("\"/>\n");
+    out->writeText("    <maxPoints value=\"4\"/>\n");
+    out->writeText("    <maxContours value=\"1\"/>\n");
+    out->writeText("    <maxCompositePoints value=\"0\"/>\n");
+    out->writeText("    <maxCompositeContours value=\"0\"/>\n");
+    out->writeText("    <maxZones value=\"1\"/>\n");
+    out->writeText("    <maxTwilightPoints value=\"0\"/>\n");
+    out->writeText("    <maxStorage value=\"0\"/>\n");
+    out->writeText("    <maxFunctionDefs value=\"10\"/>\n");
+    out->writeText("    <maxInstructionDefs value=\"0\"/>\n");
+    out->writeText("    <maxStackElements value=\"512\"/>\n");
+    out->writeText("    <maxSizeOfInstructions value=\"24\"/>\n");
+    out->writeText("    <maxComponentElements value=\"0\"/>\n");
+    out->writeText("    <maxComponentDepth value=\"0\"/>\n");
+    out->writeText("  </maxp>\n");
+
+    out->writeText("  <OS_2>\n");
+    out->writeText("    <version value=\"4\"/>\n");
+    out->writeText("    <xAvgCharWidth value=\"");
+    out->writeScalarAsText(fFontMetrics.fAvgCharWidth);
+    out->writeText("\"/>\n");
+    out->writeText("    <usWeightClass value=\"");
+    out->writeDecAsText(this->fontStyle().weight());
+    out->writeText("\"/>\n");
+    out->writeText("    <usWidthClass value=\"");
+    out->writeDecAsText(this->fontStyle().width());
+    out->writeText("\"/>\n");
+    out->writeText("    <fsType value=\"00000000 00000000\"/>\n");
+    out->writeText("    <ySubscriptXSize value=\"665\"/>\n");
+    out->writeText("    <ySubscriptYSize value=\"716\"/>\n");
+    out->writeText("    <ySubscriptXOffset value=\"0\"/>\n");
+    out->writeText("    <ySubscriptYOffset value=\"143\"/>\n");
+    out->writeText("    <ySuperscriptXSize value=\"665\"/>\n");
+    out->writeText("    <ySuperscriptYSize value=\"716\"/>\n");
+    out->writeText("    <ySuperscriptXOffset value=\"0\"/>\n");
+    out->writeText("    <ySuperscriptYOffset value=\"491\"/>\n");
+    out->writeText("    <yStrikeoutSize value=\"");
+    out->writeScalarAsText(fFontMetrics.fStrikeoutThickness);
+    out->writeText("\"/>\n");
+    out->writeText("    <yStrikeoutPosition value=\"");
+    out->writeScalarAsText(-fFontMetrics.fStrikeoutPosition);
+    out->writeText("\"/>\n");
+    out->writeText("    <sFamilyClass value=\"0\"/>\n");
+    out->writeText("    <panose>\n");
+    out->writeText("      <bFamilyType value=\"0\"/>\n");
+    out->writeText("      <bSerifStyle value=\"0\"/>\n");
+    out->writeText("      <bWeight value=\"0\"/>\n");
+    out->writeText("      <bProportion value=\"0\"/>\n");
+    out->writeText("      <bContrast value=\"0\"/>\n");
+    out->writeText("      <bStrokeVariation value=\"0\"/>\n");
+    out->writeText("      <bArmStyle value=\"0\"/>\n");
+    out->writeText("      <bLetterForm value=\"0\"/>\n");
+    out->writeText("      <bMidline value=\"0\"/>\n");
+    out->writeText("      <bXHeight value=\"0\"/>\n");
+    out->writeText("    </panose>\n");
+    out->writeText("    <ulUnicodeRange1 value=\"00000000 00000000 00000000 00000001\"/>\n");
+    out->writeText("    <ulUnicodeRange2 value=\"00010000 00000000 00000000 00000000\"/>\n");
+    out->writeText("    <ulUnicodeRange3 value=\"00000000 00000000 00000000 00000000\"/>\n");
+    out->writeText("    <ulUnicodeRange4 value=\"00000000 00000000 00000000 00000000\"/>\n");
+    out->writeText("    <achVendID value=\"Skia\"/>\n");
+    char fsSelection[16] = {
+            '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0'};
+    fsSelection[0xF - 0x7] = '1';  // Use typo metrics
+    if (this->fontStyle().weight() >= SkFontStyle::Bold().weight()) {
+        fsSelection[0xF - 0x5] = '1';  // Bold
+    }
+    switch (this->fontStyle().slant()) {
+        case SkFontStyle::kUpright_Slant:
+            if (this->fontStyle().weight() < SkFontStyle::Bold().weight()) {
+                fsSelection[0xF - 0x6] = '1';  // Not bold or italic, is regular
+            }
+            break;
+        case SkFontStyle::kItalic_Slant:
+            fsSelection[0xF - 0x0] = '1';  // Italic
+            break;
+        case SkFontStyle::kOblique_Slant:
+            fsSelection[0xF - 0x0] = '1';  // Italic
+            fsSelection[0xF - 0x9] = '1';  // Oblique
+            break;
+        default: SK_ABORT("Unknown slant.");
+    }
+    out->writeText("    <fsSelection value=\"");
+    out->write(fsSelection, 8);
+    out->writeText(" ");
+    out->write(fsSelection + 8, 8);
+    out->writeText("\"/>\n");
+    out->writeText("    <usFirstCharIndex value=\"0\"/>\n");
+    out->writeText("    <usLastCharIndex value=\"0\"/>\n");
+    out->writeText("    <sTypoAscender value=\"");
+    out->writeScalarAsText(-fFontMetrics.fAscent);
+    out->writeText("\"/>\n");
+    out->writeText("    <sTypoDescender value=\"");
+    out->writeScalarAsText(-fFontMetrics.fDescent);
+    out->writeText("\"/>\n");
+    out->writeText("    <sTypoLineGap value=\"");
+    out->writeScalarAsText(fFontMetrics.fLeading);
+    out->writeText("\"/>\n");
+    out->writeText("    <usWinAscent value=\"");
+    out->writeScalarAsText(-fFontMetrics.fAscent);
+    out->writeText("\"/>\n");
+    out->writeText("    <usWinDescent value=\"");
+    out->writeScalarAsText(fFontMetrics.fDescent);
+    out->writeText("\"/>\n");
+    out->writeText("    <ulCodePageRange1 value=\"00000000 00000000 00000000 00000000\"/>\n");
+    out->writeText("    <ulCodePageRange2 value=\"00000000 00000000 00000000 00000000\"/>\n");
+    out->writeText("    <sxHeight value=\"");
+    out->writeScalarAsText(fFontMetrics.fXHeight);
+    out->writeText("\"/>\n");
+    out->writeText("    <sCapHeight value=\"");
+    out->writeScalarAsText(fFontMetrics.fCapHeight);
+    out->writeText("\"/>\n");
+    out->writeText("    <usDefaultChar value=\"0\"/>\n");
+    out->writeText("    <usBreakChar value=\"32\"/>\n");
+    out->writeText("    <usMaxContext value=\"0\"/>\n");
+    out->writeText("  </OS_2>\n");
+
+    out->writeText("  <hmtx>\n");
+    for (int i = 0; i < fGlyphCount; ++i) {
+        out->writeText("    <mtx name=\"glyf");
+        out->writeHexAsText(i, 4);
+        out->writeText("\" width=\"");
+        out->writeDecAsText(fGlyphs[i].fAdvance);
+        out->writeText("\" lsb=\"");
+        int lsb = fGlyphs[i].fOrigin.fX;
+        if (glyfInfo) {
+            lsb += (*glyfInfo)[i].fBounds.fLeft;
+        }
+        out->writeDecAsText(lsb);
+        out->writeText("\"/>\n");
+    }
+    if (glyfInfo) {
+        for (int i = 0; i < fGlyphCount; ++i) {
+            for (int j = 0; j < (*glyfInfo)[i].fLayers.count(); ++j) {
+                out->writeText("    <mtx name=\"glyf");
+                out->writeHexAsText(i, 4);
+                out->writeText("l");
+                out->writeHexAsText(j, 4);
+                out->writeText("\" width=\"");
+                out->writeDecAsText(fGlyphs[i].fAdvance);
+                out->writeText("\" lsb=\"");
+                int32_t lsb = fGlyphs[i].fOrigin.fX + (*glyfInfo)[i].fLayers[j].fBounds.fLeft;
+                out->writeDecAsText(lsb);
+                out->writeText("\"/>\n");
+            }
+        }
+    }
+    out->writeText("  </hmtx>\n");
+
+    bool hasNonBMP = false;
+    out->writeText("  <cmap>\n");
+    out->writeText("    <tableVersion version=\"0\"/>\n");
+    out->writeText("    <cmap_format_4 platformID=\"3\" platEncID=\"1\" language=\"0\">\n");
+    fCMap.foreach ([&out, &hasNonBMP](const SkUnichar& c, const SkGlyphID& g) {
+        if (0xFFFF < c) {
+            hasNonBMP = true;
+            return;
+        }
+        out->writeText("      <map code=\"0x");
+        out->writeHexAsText(c, 4);
+        out->writeText("\" name=\"glyf");
+        out->writeHexAsText(g, 4);
+        out->writeText("\"/>\n");
+    });
+    out->writeText("    </cmap_format_4>\n");
+    if (hasNonBMP) {
+        out->writeText(
+                "    <cmap_format_12 platformID=\"3\" platEncID=\"10\" format=\"12\" "
+                "reserved=\"0\" length=\"1\" language=\"0\" nGroups=\"0\">\n");
+        fCMap.foreach ([&out](const SkUnichar& c, const SkGlyphID& g) {
+            out->writeText("      <map code=\"0x");
+            out->writeHexAsText(c, 6);
+            out->writeText("\" name=\"glyf");
+            out->writeHexAsText(g, 4);
+            out->writeText("\"/>\n");
+        });
+        out->writeText("    </cmap_format_12>\n");
+    }
+    out->writeText("  </cmap>\n");
+
+    out->writeText("  <name>\n");
+    out->writeText(
+            "    <namerecord nameID=\"1\" platformID=\"3\" platEncID=\"1\" langID=\"0x409\">\n");
+    out->writeText("      ");
+    out->writeText(fName.c_str());
+    out->writeText(" ");
+    out->writeText(type);
+    out->writeText("\n");
+    out->writeText("    </namerecord>\n");
+    out->writeText(
+            "    <namerecord nameID=\"2\" platformID=\"3\" platEncID=\"1\" langID=\"0x409\">\n");
+    out->writeText("      Regular\n");
+    out->writeText("    </namerecord>\n");
+    out->writeText("  </name>\n");
+
+    out->writeText("  <post>\n");
+    out->writeText("    <formatType value=\"3.0\"/>\n");
+    out->writeText("    <italicAngle value=\"0.0\"/>\n");
+    out->writeText("    <underlinePosition value=\"");
+    out->writeScalarAsText(fFontMetrics.fUnderlinePosition);
+    out->writeText("\"/>\n");
+    out->writeText("    <underlineThickness value=\"");
+    out->writeScalarAsText(fFontMetrics.fUnderlineThickness);
+    out->writeText("\"/>\n");
+    out->writeText("    <isFixedPitch value=\"0\"/>\n");
+    out->writeText("    <minMemType42 value=\"0\"/>\n");
+    out->writeText("    <maxMemType42 value=\"0\"/>\n");
+    out->writeText("    <minMemType1 value=\"0\"/>\n");
+    out->writeText("    <maxMemType1 value=\"0\"/>\n");
+    out->writeText("  </post>\n");
+}
+
+void TestSVGTypeface::exportTtxCbdt(SkWStream* out, SkSpan<unsigned> strikeSizes) const {
+    SkPaint paint;
+    SkFont  font;
+    font.setTypeface(sk_ref_sp(const_cast<TestSVGTypeface*>(this)));
+    SkString name;
+    this->getFamilyName(&name);
+
+    // The CBDT/CBLC format is quite restrictive. Only write strikes which fully fit.
+    SkSTArray<8, int> goodStrikeSizes;
+    for (size_t strikeIndex = 0; strikeIndex < strikeSizes.size(); ++strikeIndex) {
+        font.setSize(strikeSizes[strikeIndex]);
+
+        // CBLC limits
+        SkFontMetrics fm;
+        font.getMetrics(&fm);
+        if (!SkTFitsIn<int8_t>((int)(-fm.fTop)) || !SkTFitsIn<int8_t>((int)(-fm.fBottom)) ||
+            !SkTFitsIn<uint8_t>((int)(fm.fXMax - fm.fXMin))) {
+            SkDebugf("Metrics too big cbdt font size %f for %s.\n", font.getSize(), name.c_str());
+            continue;
+        }
+
+        // CBDT limits
+        auto exceedsCbdtLimits = [&]() {
+            for (int i = 0; i < fGlyphCount; ++i) {
+                SkGlyphID gid = i;
+                SkScalar  advance;
+                SkRect    bounds;
+                font.getWidthsBounds(&gid, 1, &advance, &bounds, nullptr);
+                SkIRect ibounds = bounds.roundOut();
+                if (!SkTFitsIn<int8_t>(ibounds.fLeft) || !SkTFitsIn<int8_t>(ibounds.fTop) ||
+                    !SkTFitsIn<uint8_t>(ibounds.width()) || !SkTFitsIn<uint8_t>(ibounds.height()) ||
+                    !SkTFitsIn<uint8_t>((int)advance)) {
+                    return true;
+                }
+            }
+            return false;
+        };
+        if (exceedsCbdtLimits()) {
+            SkDebugf("Glyphs too big cbdt font size %f for %s.\n", font.getSize(), name.c_str());
+            continue;
+        }
+
+        goodStrikeSizes.emplace_back(strikeSizes[strikeIndex]);
+    }
+
+    if (goodStrikeSizes.empty()) {
+        SkDebugf("No strike size fit for cbdt font for %s.\n", name.c_str());
+        return;
+    }
+
+    out->writeText("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+    out->writeText("<ttFont sfntVersion=\"\\x00\\x01\\x00\\x00\" ttLibVersion=\"3.19\">\n");
+    this->exportTtxCommon(out, "CBDT");
+
+    out->writeText("  <CBDT>\n");
+    out->writeText("    <header version=\"2.0\"/>\n");
+    for (size_t strikeIndex = 0; strikeIndex < goodStrikeSizes.size(); ++strikeIndex) {
+        font.setSize(goodStrikeSizes[strikeIndex]);
+
+        out->writeText("    <strikedata index=\"");
+        out->writeDecAsText(strikeIndex);
+        out->writeText("\">\n");
+        for (int i = 0; i < fGlyphCount; ++i) {
+            SkGlyphID gid = i;
+            SkScalar  advance;
+            SkRect    bounds;
+            font.getWidthsBounds(&gid, 1, &advance, &bounds, nullptr);
+            SkIRect ibounds = bounds.roundOut();
+            if (ibounds.isEmpty()) {
+                continue;
+            }
+            SkImageInfo image_info = SkImageInfo::MakeN32Premul(ibounds.width(), ibounds.height());
+            sk_sp<SkSurface> surface(SkSurface::MakeRaster(image_info));
+            SkASSERT(surface);
+            SkCanvas* canvas = surface->getCanvas();
+            canvas->clear(0);
+            SkPixmap pix;
+            surface->peekPixels(&pix);
+            canvas->drawSimpleText(&gid,
+                                   sizeof(gid),
+                                   kGlyphID_SkTextEncoding,
+                                   -bounds.fLeft,
+                                   -bounds.fTop,
+                                   font,
+                                   paint);
+            surface->flush();
+            sk_sp<SkImage> image = surface->makeImageSnapshot();
+            sk_sp<SkData>  data  = image->encodeToData(SkEncodedImageFormat::kPNG, 100);
+
+            out->writeText("      <cbdt_bitmap_format_17 name=\"glyf");
+            out->writeHexAsText(i, 4);
+            out->writeText("\">\n");
+            out->writeText("        <SmallGlyphMetrics>\n");
+            out->writeText("          <height value=\"");
+            out->writeDecAsText(image->height());
+            out->writeText("\"/>\n");
+            out->writeText("          <width value=\"");
+            out->writeDecAsText(image->width());
+            out->writeText("\"/>\n");
+            out->writeText("          <BearingX value=\"");
+            out->writeDecAsText(ibounds.fLeft);
+            out->writeText("\"/>\n");
+            out->writeText("          <BearingY value=\"");
+            out->writeDecAsText(-ibounds.fTop);
+            out->writeText("\"/>\n");
+            out->writeText("          <Advance value=\"");
+            out->writeDecAsText((int)advance);
+            out->writeText("\"/>\n");
+            out->writeText("        </SmallGlyphMetrics>\n");
+            out->writeText("        <rawimagedata>");
+            uint8_t const* bytes = data->bytes();
+            for (size_t i = 0; i < data->size(); ++i) {
+                if ((i % 0x10) == 0x0) {
+                    out->writeText("\n          ");
+                } else if (((i - 1) % 0x4) == 0x3) {
+                    out->writeText(" ");
+                }
+                out->writeHexAsText(bytes[i], 2);
+            }
+            out->writeText("\n");
+            out->writeText("        </rawimagedata>\n");
+            out->writeText("      </cbdt_bitmap_format_17>\n");
+        }
+        out->writeText("    </strikedata>\n");
+    }
+    out->writeText("  </CBDT>\n");
+
+    SkFontMetrics fm;
+    out->writeText("  <CBLC>\n");
+    out->writeText("    <header version=\"2.0\"/>\n");
+    for (size_t strikeIndex = 0; strikeIndex < goodStrikeSizes.size(); ++strikeIndex) {
+        font.setSize(goodStrikeSizes[strikeIndex]);
+        font.getMetrics(&fm);
+        out->writeText("    <strike index=\"");
+        out->writeDecAsText(strikeIndex);
+        out->writeText("\">\n");
+        out->writeText("      <bitmapSizeTable>\n");
+        out->writeText("        <sbitLineMetrics direction=\"hori\">\n");
+        out->writeText("          <ascender value=\"");
+        out->writeDecAsText((int)(-fm.fTop));
+        out->writeText("\"/>\n");
+        out->writeText("          <descender value=\"");
+        out->writeDecAsText((int)(-fm.fBottom));
+        out->writeText("\"/>\n");
+        out->writeText("          <widthMax value=\"");
+        out->writeDecAsText((int)(fm.fXMax - fm.fXMin));
+        out->writeText("\"/>\n");
+        out->writeText("          <caretSlopeNumerator value=\"0\"/>\n");
+        out->writeText("          <caretSlopeDenominator value=\"0\"/>\n");
+        out->writeText("          <caretOffset value=\"0\"/>\n");
+        out->writeText("          <minOriginSB value=\"0\"/>\n");
+        out->writeText("          <minAdvanceSB value=\"0\"/>\n");
+        out->writeText("          <maxBeforeBL value=\"0\"/>\n");
+        out->writeText("          <minAfterBL value=\"0\"/>\n");
+        out->writeText("          <pad1 value=\"0\"/>\n");
+        out->writeText("          <pad2 value=\"0\"/>\n");
+        out->writeText("        </sbitLineMetrics>\n");
+        out->writeText("        <sbitLineMetrics direction=\"vert\">\n");
+        out->writeText("          <ascender value=\"");
+        out->writeDecAsText((int)(-fm.fTop));
+        out->writeText("\"/>\n");
+        out->writeText("          <descender value=\"");
+        out->writeDecAsText((int)(-fm.fBottom));
+        out->writeText("\"/>\n");
+        out->writeText("          <widthMax value=\"");
+        out->writeDecAsText((int)(fm.fXMax - fm.fXMin));
+        out->writeText("\"/>\n");
+        out->writeText("          <caretSlopeNumerator value=\"0\"/>\n");
+        out->writeText("          <caretSlopeDenominator value=\"0\"/>\n");
+        out->writeText("          <caretOffset value=\"0\"/>\n");
+        out->writeText("          <minOriginSB value=\"0\"/>\n");
+        out->writeText("          <minAdvanceSB value=\"0\"/>\n");
+        out->writeText("          <maxBeforeBL value=\"0\"/>\n");
+        out->writeText("          <minAfterBL value=\"0\"/>\n");
+        out->writeText("          <pad1 value=\"0\"/>\n");
+        out->writeText("          <pad2 value=\"0\"/>\n");
+        out->writeText("        </sbitLineMetrics>\n");
+        out->writeText("        <colorRef value=\"0\"/>\n");
+        out->writeText("        <startGlyphIndex value=\"1\"/>\n");
+        out->writeText("        <endGlyphIndex value=\"1\"/>\n");
+        out->writeText("        <ppemX value=\"");
+        out->writeDecAsText(goodStrikeSizes[strikeIndex]);
+        out->writeText("\"/>\n");
+        out->writeText("        <ppemY value=\"");
+        out->writeDecAsText(goodStrikeSizes[strikeIndex]);
+        out->writeText("\"/>\n");
+        out->writeText("        <bitDepth value=\"32\"/>\n");
+        out->writeText("        <flags value=\"1\"/>\n");
+        out->writeText("      </bitmapSizeTable>\n");
+        out->writeText(
+                "      <eblc_index_sub_table_1 imageFormat=\"17\" firstGlyphIndex=\"1\" "
+                "lastGlyphIndex=\"1\">\n");
+        for (int i = 0; i < fGlyphCount; ++i) {
+            SkGlyphID gid = i;
+            SkRect    bounds;
+            font.getBounds(&gid, 1, &bounds, nullptr);
+            if (bounds.isEmpty()) {
+                continue;
+            }
+            out->writeText("        <glyphLoc name=\"glyf");
+            out->writeHexAsText(i, 4);
+            out->writeText("\"/>\n");
+        }
+        out->writeText("      </eblc_index_sub_table_1>\n");
+        out->writeText("    </strike>\n");
+    }
+    out->writeText("  </CBLC>\n");
+
+    out->writeText("</ttFont>\n");
+}
+
+/**
+ * UnitsPerEm is generally 1000 here. Versions of macOS older than 10.13
+ * have problems in CoreText determining the glyph bounds of bitmap glyphs
+ * with unitsPerEm set to 1024 or numbers not divisible by 100 when the
+ * contour is not closed. The bounds of sbix fonts on macOS appear to be those
+ * of the outline in the 'glyf' table. If this countour is closed it will be
+ * drawn, as the 'glyf' outline is to be drawn on top of any bitmap. (There is
+ * a bit which is supposed to control this, but it cannot be relied on.) So
+ * make the glyph contour a degenerate line with points at the edge of the
+ * bounding box of the glyph.
+ */
+void TestSVGTypeface::exportTtxSbix(SkWStream* out, SkSpan<unsigned> strikeSizes) const {
+    out->writeText("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+    out->writeText("<ttFont sfntVersion=\"\\x00\\x01\\x00\\x00\" ttLibVersion=\"3.19\">\n");
+    this->exportTtxCommon(out, "sbix");
+
+    SkPaint paint;
+    SkFont  font;
+    font.setTypeface(sk_ref_sp(const_cast<TestSVGTypeface*>(this)));
+
+    out->writeText("  <glyf>\n");
+    for (int i = 0; i < fGlyphCount; ++i) {
+        const TestSVGTypeface::Glyph& glyphData = this->fGlyphs[i];
+
+        SkSize containerSize = glyphData.size();
+        SkRect  bounds  = SkRect::MakeXYWH(glyphData.fOrigin.fX,
+                                         -glyphData.fOrigin.fY,
+                                         containerSize.fWidth,
+                                         containerSize.fHeight);
+        SkIRect ibounds = bounds.roundOut();
+        out->writeText("    <TTGlyph name=\"glyf");
+        out->writeHexAsText(i, 4);
+        out->writeText("\" xMin=\"");
+        out->writeDecAsText(ibounds.fLeft);
+        out->writeText("\" yMin=\"");
+        out->writeDecAsText(-ibounds.fBottom);
+        out->writeText("\" xMax=\"");
+        out->writeDecAsText(ibounds.fRight);
+        out->writeText("\" yMax=\"");
+        out->writeDecAsText(-ibounds.fTop);
+        out->writeText("\">\n");
+        out->writeText("      <contour>\n");
+        out->writeText("        <pt x=\"");
+        out->writeDecAsText(ibounds.fLeft);
+        out->writeText("\" y=\"");
+        out->writeDecAsText(-ibounds.fBottom);
+        out->writeText("\" on=\"1\"/>\n");
+        out->writeText("      </contour>\n");
+        out->writeText("      <contour>\n");
+        out->writeText("        <pt x=\"");
+        out->writeDecAsText(ibounds.fRight);
+        out->writeText("\" y=\"");
+        out->writeDecAsText(-ibounds.fTop);
+        out->writeText("\" on=\"1\"/>\n");
+        out->writeText("      </contour>\n");
+        out->writeText("      <instructions/>\n");
+        out->writeText("    </TTGlyph>\n");
+    }
+    out->writeText("  </glyf>\n");
+
+    // The loca table will be re-calculated, but if we don't write one we don't get one.
+    out->writeText("  <loca/>\n");
+
+    out->writeText("  <sbix>\n");
+    out->writeText("    <version value=\"1\"/>\n");
+    out->writeText("    <flags value=\"00000000 00000001\"/>\n");
+    for (size_t strikeIndex = 0; strikeIndex < strikeSizes.size(); ++strikeIndex) {
+        font.setSize(strikeSizes[strikeIndex]);
+        out->writeText("    <strike>\n");
+        out->writeText("      <ppem value=\"");
+        out->writeDecAsText(strikeSizes[strikeIndex]);
+        out->writeText("\"/>\n");
+        out->writeText("      <resolution value=\"72\"/>\n");
+        for (int i = 0; i < fGlyphCount; ++i) {
+            SkGlyphID gid = i;
+            SkScalar  advance;
+            SkRect    bounds;
+            font.getWidthsBounds(&gid, 1, &advance, &bounds, nullptr);
+            SkIRect ibounds = bounds.roundOut();
+            if (ibounds.isEmpty()) {
+                continue;
+            }
+            SkImageInfo image_info = SkImageInfo::MakeN32Premul(ibounds.width(), ibounds.height());
+            sk_sp<SkSurface> surface(SkSurface::MakeRaster(image_info));
+            SkASSERT(surface);
+            SkCanvas* canvas = surface->getCanvas();
+            canvas->clear(0);
+            SkPixmap pix;
+            surface->peekPixels(&pix);
+            canvas->drawSimpleText(&gid,
+                                   sizeof(gid),
+                                   kGlyphID_SkTextEncoding,
+                                   -bounds.fLeft,
+                                   -bounds.fTop,
+                                   font,
+                                   paint);
+            surface->flush();
+            sk_sp<SkImage> image = surface->makeImageSnapshot();
+            sk_sp<SkData>  data  = image->encodeToData(SkEncodedImageFormat::kPNG, 100);
+
+            // The originOffset values are difficult to use as DirectWrite and FreeType interpret
+            // the origin to be the initial glyph position on the baseline, but CoreGraphics
+            // interprets the origin to be the lower left of the cbox of the outline in the 'glyf'
+            // table.
+            //#define SK_SBIX_LIKE_FT
+            //#define SK_SBIX_LIKE_DW
+            out->writeText("      <glyph name=\"glyf");
+            out->writeHexAsText(i, 4);
+            out->writeText("\" graphicType=\"png \" originOffsetX=\"");
+#if defined(SK_SBIX_LIKE_FT) || defined(SK_SBIX_LIKE_DW)
+            out->writeDecAsText(bounds.fLeft);
+#else
+            out->writeDecAsText(0);
+#endif
+            // DirectWrite and CoreGraphics use positive values of originOffsetY to push the
+            // image visually up (but from different origins).
+            // FreeType uses positive values to push the image down.
+            out->writeText("\" originOffsetY=\"");
+#if defined(SK_SBIX_LIKE_FT)
+            out->writeScalarAsText(bounds.fBottom);
+#elif defined(SK_SBIX_LIKE_DW)
+            out->writeScalarAsText(-bounds.fBottom);
+#else
+            out->writeDecAsText(0);
+#endif
+            out->writeText("\">\n");
+
+            out->writeText("        <hexdata>");
+            uint8_t const* bytes = data->bytes();
+            for (size_t i = 0; i < data->size(); ++i) {
+                if ((i % 0x10) == 0x0) {
+                    out->writeText("\n          ");
+                } else if (((i - 1) % 0x4) == 0x3) {
+                    out->writeText(" ");
+                }
+                out->writeHexAsText(bytes[i], 2);
+            }
+            out->writeText("\n");
+            out->writeText("        </hexdata>\n");
+            out->writeText("      </glyph>\n");
+        }
+        out->writeText("    </strike>\n");
+    }
+    out->writeText("  </sbix>\n");
+    out->writeText("</ttFont>\n");
+}
+
+namespace {
+
+void convert_noninflect_cubic_to_quads(const SkPoint            p[4],
+                                       SkScalar                 toleranceSqd,
+                                       SkTArray<SkPoint, true>* quads,
+                                       int                      sublevel = 0) {
+    // Notation: Point a is always p[0]. Point b is p[1] unless p[1] == p[0], in which case it is
+    // p[2]. Point d is always p[3]. Point c is p[2] unless p[2] == p[3], in which case it is p[1].
+
+    SkVector ab = p[1] - p[0];
+    SkVector dc = p[2] - p[3];
+
+    if (SkPointPriv::LengthSqd(ab) < SK_ScalarNearlyZero) {
+        if (SkPointPriv::LengthSqd(dc) < SK_ScalarNearlyZero) {
+            SkPoint* degQuad = quads->push_back_n(3);
+            degQuad[0]       = p[0];
+            degQuad[1]       = p[0];
+            degQuad[2]       = p[3];
+            return;
+        }
+        ab = p[2] - p[0];
+    }
+    if (SkPointPriv::LengthSqd(dc) < SK_ScalarNearlyZero) {
+        dc = p[1] - p[3];
+    }
+
+    static const SkScalar kLengthScale = 3 * SK_Scalar1 / 2;
+    static const int      kMaxSubdivs  = 10;
+
+    ab.scale(kLengthScale);
+    dc.scale(kLengthScale);
+
+    // e0 and e1 are extrapolations along vectors ab and dc.
+    SkVector c0 = p[0];
+    c0 += ab;
+    SkVector c1 = p[3];
+    c1 += dc;
+
+    SkScalar dSqd = sublevel > kMaxSubdivs ? 0 : SkPointPriv::DistanceToSqd(c0, c1);
+    if (dSqd < toleranceSqd) {
+        SkPoint cAvg = c0;
+        cAvg += c1;
+        cAvg.scale(SK_ScalarHalf);
+
+        SkPoint* pts = quads->push_back_n(3);
+        pts[0]       = p[0];
+        pts[1]       = cAvg;
+        pts[2]       = p[3];
+        return;
+    }
+    SkPoint choppedPts[7];
+    SkChopCubicAtHalf(p, choppedPts);
+    convert_noninflect_cubic_to_quads(choppedPts + 0, toleranceSqd, quads, sublevel + 1);
+    convert_noninflect_cubic_to_quads(choppedPts + 3, toleranceSqd, quads, sublevel + 1);
+}
+
+void convertCubicToQuads(const SkPoint p[4], SkScalar tolScale, SkTArray<SkPoint, true>* quads) {
+    if (!p[0].isFinite() || !p[1].isFinite() || !p[2].isFinite() || !p[3].isFinite()) {
+        return;
+    }
+    SkPoint chopped[10];
+    int     count = SkChopCubicAtInflections(p, chopped);
+
+    const SkScalar tolSqd = SkScalarSquare(tolScale);
+
+    for (int i = 0; i < count; ++i) {
+        SkPoint* cubic = chopped + 3 * i;
+        convert_noninflect_cubic_to_quads(cubic, tolSqd, quads);
+    }
+}
+
+void path_to_quads(const SkPath& path, SkPath* quadPath) {
+    quadPath->reset();
+    SkTArray<SkPoint, true> qPts;
+    SkAutoConicToQuads      converter;
+    const SkPoint*          quadPts;
+    SkPath::RawIter         iter(path);
+    uint8_t                 verb;
+    SkPoint                 pts[4];
+    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+        switch (verb) {
+            case SkPath::kMove_Verb: quadPath->moveTo(pts[0].fX, pts[0].fY); break;
+            case SkPath::kLine_Verb: quadPath->lineTo(pts[1].fX, pts[1].fY); break;
+            case SkPath::kQuad_Verb:
+                quadPath->quadTo(pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
+                break;
+            case SkPath::kCubic_Verb:
+                qPts.reset();
+                convertCubicToQuads(pts, SK_Scalar1, &qPts);
+                for (int i = 0; i < qPts.count(); i += 3) {
+                    quadPath->quadTo(
+                            qPts[i + 1].fX, qPts[i + 1].fY, qPts[i + 2].fX, qPts[i + 2].fY);
+                }
+                break;
+            case SkPath::kConic_Verb:
+                quadPts = converter.computeQuads(pts, iter.conicWeight(), SK_Scalar1);
+                for (int i = 0; i < converter.countQuads(); ++i) {
+                    quadPath->quadTo(quadPts[i * 2 + 1].fX,
+                                     quadPts[i * 2 + 1].fY,
+                                     quadPts[i * 2 + 2].fX,
+                                     quadPts[i * 2 + 2].fY);
+                }
+                break;
+            case SkPath::kClose_Verb: quadPath->close(); break;
+            default: SkDEBUGFAIL("bad verb"); return;
+        }
+    }
+}
+
+class SkCOLRCanvas : public SkNoDrawCanvas {
+public:
+    SkCOLRCanvas(SkRect                     glyphBounds,
+                 const TestSVGTypeface&     typeface,
+                 SkGlyphID                  glyphId,
+                 TestSVGTypeface::GlyfInfo* glyf,
+                 SkTHashMap<SkColor, int>*  colors,
+                 SkWStream*                 out)
+            : SkNoDrawCanvas(glyphBounds.roundOut().width(), glyphBounds.roundOut().height())
+            , fBaselineOffset(glyphBounds.top())
+            , fTypeface(typeface)
+            , fGlyphId(glyphId)
+            , fGlyf(glyf)
+            , fColors(colors)
+            , fOut(out)
+            , fLayerId(0) {}
+
+    void writePoint(SkScalar x, SkScalar y, bool on) {
+        fOut->writeText("        <pt x=\"");
+        fOut->writeDecAsText(SkScalarRoundToInt(x));
+        fOut->writeText("\" y=\"");
+        fOut->writeDecAsText(SkScalarRoundToInt(y));
+        fOut->writeText("\" on=\"");
+        fOut->write8(on ? '1' : '0');
+        fOut->writeText("\"/>\n");
+    }
+    SkIRect writePath(const SkPath& path, bool layer) {
+        // Convert to quads.
+        SkPath quads;
+        path_to_quads(path, &quads);
+
+        SkRect  bounds  = quads.computeTightBounds();
+        SkIRect ibounds = bounds.roundOut();
+        // The bounds will be re-calculated anyway.
+        fOut->writeText("    <TTGlyph name=\"glyf");
+        fOut->writeHexAsText(fGlyphId, 4);
+        if (layer) {
+            fOut->writeText("l");
+            fOut->writeHexAsText(fLayerId, 4);
+        }
+        fOut->writeText("\" xMin=\"");
+        fOut->writeDecAsText(ibounds.fLeft);
+        fOut->writeText("\" yMin=\"");
+        fOut->writeDecAsText(ibounds.fTop);
+        fOut->writeText("\" xMax=\"");
+        fOut->writeDecAsText(ibounds.fRight);
+        fOut->writeText("\" yMax=\"");
+        fOut->writeDecAsText(ibounds.fBottom);
+        fOut->writeText("\">\n");
+
+        SkPath::RawIter iter(quads);
+        uint8_t         verb;
+        SkPoint         pts[4];
+        bool            contourOpen = false;
+        while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+            switch (verb) {
+                case SkPath::kMove_Verb:
+                    if (contourOpen) {
+                        fOut->writeText("      </contour>\n");
+                        contourOpen = false;
+                    }
+                    break;
+                case SkPath::kLine_Verb:
+                    if (!contourOpen) {
+                        fOut->writeText("      <contour>\n");
+                        this->writePoint(pts[0].fX, pts[0].fY, true);
+                        contourOpen = true;
+                    }
+                    this->writePoint(pts[1].fX, pts[1].fY, true);
+                    break;
+                case SkPath::kQuad_Verb:
+                    if (!contourOpen) {
+                        fOut->writeText("      <contour>\n");
+                        this->writePoint(pts[0].fX, pts[0].fY, true);
+                        contourOpen = true;
+                    }
+                    this->writePoint(pts[1].fX, pts[1].fY, false);
+                    this->writePoint(pts[2].fX, pts[2].fY, true);
+                    break;
+                case SkPath::kClose_Verb:
+                    if (contourOpen) {
+                        fOut->writeText("      </contour>\n");
+                        contourOpen = false;
+                    }
+                    break;
+                default: SkDEBUGFAIL("bad verb"); return ibounds;
+            }
+        }
+        if (contourOpen) {
+            fOut->writeText("      </contour>\n");
+        }
+
+        // Required to write out an instructions tag.
+        fOut->writeText("      <instructions/>\n");
+        fOut->writeText("    </TTGlyph>\n");
+        return ibounds;
+    }
+
+    void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+        SkPath path;
+        path.addRect(rect);
+        this->drawPath(path, paint);
+    }
+
+    void onDrawOval(const SkRect& oval, const SkPaint& paint) override {
+        SkPath path;
+        path.addOval(oval);
+        this->drawPath(path, paint);
+    }
+
+    void onDrawArc(const SkRect&  oval,
+                   SkScalar       startAngle,
+                   SkScalar       sweepAngle,
+                   bool           useCenter,
+                   const SkPaint& paint) override {
+        SkPath path;
+        bool fillNoPathEffect = SkPaint::kFill_Style == paint.getStyle() && !paint.getPathEffect();
+        SkPathPriv::CreateDrawArcPath(
+                &path, oval, startAngle, sweepAngle, useCenter, fillNoPathEffect);
+        this->drawPath(path, paint);
+    }
+
+    void onDrawRRect(const SkRRect& rrect, const SkPaint& paint) override {
+        SkPath path;
+        path.addRRect(rrect);
+        this->drawPath(path, paint);
+    }
+
+    void onDrawPath(const SkPath& platonicPath, const SkPaint& originalPaint) override {
+        SkPaint paint = originalPaint;
+        SkPath  path  = platonicPath;
+
+        // Apply the path effect.
+        if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) {
+            bool fill = paint.getFillPath(path, &path);
+
+            paint.setPathEffect(nullptr);
+            if (fill) {
+                paint.setStyle(SkPaint::kFill_Style);
+            } else {
+                paint.setStyle(SkPaint::kStroke_Style);
+                paint.setStrokeWidth(0);
+            }
+        }
+
+        // Apply the matrix.
+        SkMatrix m = this->getTotalMatrix();
+        // If done to the canvas then everything would get clipped out.
+        m.postTranslate(0, fBaselineOffset);  // put the baseline at 0
+        m.postScale(1, -1);                   // and flip it since OpenType is y-up.
+        path.transform(m);
+
+        // While creating the default glyf, union with dark colors and intersect with bright colors.
+        SkColor  color = paint.getColor();
+        SkPathOp op;
+        if (fTypeface.getPathOp(color, &op)) {
+            fBasePath.add(path, op);
+        }
+        SkIRect bounds = this->writePath(path, true);
+
+        // The CPAL table has the concept of a 'current color' which is index 0xFFFF.
+        // Mark any layer drawn in 'currentColor' as having this special index.
+        // The value of 'currentColor' here should a color which causes this layer to union into the
+        // default glyf.
+        constexpr SkColor currentColor = 0xFF2B0000;
+
+        int colorIndex;
+        if (color == currentColor) {
+            colorIndex = 0xFFFF;
+        } else {
+            int* colorIndexPtr = fColors->find(color);
+            if (colorIndexPtr) {
+                colorIndex = *colorIndexPtr;
+            } else {
+                colorIndex = fColors->count();
+                fColors->set(color, colorIndex);
+            }
+        }
+        fGlyf->fLayers.emplace_back(colorIndex, bounds);
+
+        ++fLayerId;
+    }
+
+    void finishGlyph() {
+        SkPath baseGlyph;
+        fBasePath.resolve(&baseGlyph);
+        fGlyf->fBounds = this->writePath(baseGlyph, false);
+    }
+
+private:
+    SkScalar                   fBaselineOffset;
+    const TestSVGTypeface&     fTypeface;
+    SkGlyphID                  fGlyphId;
+    TestSVGTypeface::GlyfInfo* fGlyf;
+    SkTHashMap<SkColor, int>*  fColors;
+    SkWStream* const           fOut;
+    SkOpBuilder                fBasePath;
+    int                        fLayerId;
+};
+
+}  // namespace
+
+void TestSVGTypeface::exportTtxColr(SkWStream* out) const {
+    out->writeText("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+    out->writeText("<ttFont sfntVersion=\"\\x00\\x01\\x00\\x00\" ttLibVersion=\"3.19\">\n");
+
+    SkTHashMap<SkColor, int> colors;
+    SkTArray<GlyfInfo>       glyfInfos(fGlyphCount);
+
+    // Need to know all the glyphs up front for the common tables.
+    SkDynamicMemoryWStream glyfOut;
+    glyfOut.writeText("  <glyf>\n");
+    for (int i = 0; i < fGlyphCount; ++i) {
+        const TestSVGTypeface::Glyph& glyphData = this->fGlyphs[i];
+
+        SkSize containerSize = glyphData.size();
+        SkRect       bounds = SkRect::MakeXYWH(glyphData.fOrigin.fX,
+                                         -glyphData.fOrigin.fY,
+                                         containerSize.fWidth,
+                                         containerSize.fHeight);
+        SkCOLRCanvas canvas(bounds, *this, i, &glyfInfos.emplace_back(), &colors, &glyfOut);
+        glyphData.render(&canvas);
+        canvas.finishGlyph();
+    }
+    glyfOut.writeText("  </glyf>\n");
+
+    this->exportTtxCommon(out, "COLR", &glyfInfos);
+
+    // The loca table will be re-calculated, but if we don't write one we don't get one.
+    out->writeText("  <loca/>\n");
+
+    std::unique_ptr<SkStreamAsset> glyfStream = glyfOut.detachAsStream();
+    out->writeStream(glyfStream.get(), glyfStream->getLength());
+
+    out->writeText("  <COLR>\n");
+    out->writeText("    <version value=\"0\"/>\n");
+    for (int i = 0; i < fGlyphCount; ++i) {
+        if (glyfInfos[i].fLayers.empty()) {
+            continue;
+        }
+        if (glyfInfos[i].fBounds.isEmpty()) {
+            SkDebugf("Glyph %d is empty but has layers.\n", i);
+        }
+        out->writeText("    <ColorGlyph name=\"glyf");
+        out->writeHexAsText(i, 4);
+        out->writeText("\">\n");
+        for (int j = 0; j < glyfInfos[i].fLayers.count(); ++j) {
+            const int colorIndex = glyfInfos[i].fLayers[j].fLayerColorIndex;
+            out->writeText("      <layer colorID=\"");
+            out->writeDecAsText(colorIndex);
+            out->writeText("\" name=\"glyf");
+            out->writeHexAsText(i, 4);
+            out->writeText("l");
+            out->writeHexAsText(j, 4);
+            out->writeText("\"/>\n");
+        }
+        out->writeText("    </ColorGlyph>\n");
+    }
+    out->writeText("  </COLR>\n");
+
+    // The colors must be written in order, the 'index' is ignored by ttx.
+    SkAutoTMalloc<SkColor> colorsInOrder(colors.count());
+    colors.foreach ([&colorsInOrder](const SkColor& c, const int* i) { colorsInOrder[*i] = c; });
+    out->writeText("  <CPAL>\n");
+    out->writeText("    <version value=\"0\"/>\n");
+    out->writeText("    <numPaletteEntries value=\"");
+    out->writeDecAsText(colors.count());
+    out->writeText("\"/>\n");
+    out->writeText("    <palette index=\"0\">\n");
+    for (int i = 0; i < colors.count(); ++i) {
+        SkColor c = colorsInOrder[i];
+        out->writeText("      <color index=\"");
+        out->writeDecAsText(i);
+        out->writeText("\" value=\"#");
+        out->writeHexAsText(SkColorGetR(c), 2);
+        out->writeHexAsText(SkColorGetG(c), 2);
+        out->writeHexAsText(SkColorGetB(c), 2);
+        out->writeHexAsText(SkColorGetA(c), 2);
+        out->writeText("\"/>\n");
+    }
+    out->writeText("    </palette>\n");
+    out->writeText("  </CPAL>\n");
+
+    out->writeText("</ttFont>\n");
+}
+#endif  // SK_XML
diff --git a/tools/fonts/TestSVGTypeface.h b/tools/fonts/TestSVGTypeface.h
new file mode 100644
index 0000000..ba0d789
--- /dev/null
+++ b/tools/fonts/TestSVGTypeface.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef TestSVGTypeface_DEFINED
+#define TestSVGTypeface_DEFINED
+
+#include "SkFontArguments.h"
+#include "SkFontMetrics.h"
+#include "SkMutex.h"
+#include "SkPaint.h"
+#include "SkPathOps.h"
+#include "SkPoint.h"
+#include "SkRect.h"
+#include "SkRefCnt.h"
+#include "SkScalar.h"
+#include "SkSpan.h"
+#include "SkStream.h"
+#include "SkString.h"
+#include "SkTArray.h"
+#include "SkTHash.h"
+#include "SkTypeface.h"
+#include "SkTypes.h"
+
+#include <memory>
+
+class SkCanvas;
+class SkDescriptor;
+class SkFontDescriptor;
+class SkFontStyle;
+class SkGlyph;
+class SkPath;
+class SkScalerContext;
+class SkSVGDOM;
+class SkWStream;
+struct SkAdvancedTypefaceMetrics;
+struct SkScalerContextEffects;
+struct SkScalerContextRec;
+
+struct SkSVGTestTypefaceGlyphData {
+    const char* fSvgResourcePath;
+    SkPoint     fOrigin;  // y-down
+    SkScalar    fAdvance;
+    SkUnichar   fUnicode;  // TODO: this limits to 1:1
+};
+
+class TestSVGTypeface : public SkTypeface {
+public:
+    TestSVGTypeface(const char*                              name,
+                    int                                      upem,
+                    const SkFontMetrics&                     metrics,
+                    SkSpan<const SkSVGTestTypefaceGlyphData> data,
+                    const SkFontStyle&                       style);
+    ~TestSVGTypeface() override;
+    void getAdvance(SkGlyph* glyph) const;
+    void getFontMetrics(SkFontMetrics* metrics) const;
+
+    static sk_sp<TestSVGTypeface> Default();
+    static sk_sp<TestSVGTypeface> Planets();
+    void                          exportTtxCbdt(SkWStream*, SkSpan<unsigned> strikeSizes) const;
+    void                          exportTtxSbix(SkWStream*, SkSpan<unsigned> strikeSizes) const;
+    void                          exportTtxColr(SkWStream*) const;
+    virtual bool                  getPathOp(SkColor, SkPathOp*) const = 0;
+
+    struct GlyfLayerInfo {
+        GlyfLayerInfo(int layerColorIndex, SkIRect bounds)
+                : fLayerColorIndex(layerColorIndex), fBounds(bounds) {}
+        int     fLayerColorIndex;
+        SkIRect fBounds;
+    };
+    struct GlyfInfo {
+        GlyfInfo() : fBounds(SkIRect::MakeEmpty()) {}
+        SkIRect                 fBounds;
+        SkTArray<GlyfLayerInfo> fLayers;
+    };
+
+protected:
+    void exportTtxCommon(SkWStream*, const char* type, const SkTArray<GlyfInfo>* = nullptr) const;
+
+    SkScalerContext*                           onCreateScalerContext(const SkScalerContextEffects&,
+                                                                     const SkDescriptor* desc) const override;
+    void                                       onFilterRec(SkScalerContextRec* rec) const override;
+    void                                       getGlyphToUnicodeMap(SkUnichar*) const override;
+    std::unique_ptr<SkAdvancedTypefaceMetrics> onGetAdvancedMetrics() const override;
+
+    std::unique_ptr<SkStreamAsset> onOpenStream(int* ttcIndex) const override { return nullptr; }
+
+    sk_sp<SkTypeface> onMakeClone(const SkFontArguments& args) const override {
+        return sk_ref_sp(this);
+    }
+
+    void onGetFontDescriptor(SkFontDescriptor* desc, bool* isLocal) const override;
+
+    int onCharsToGlyphs(const void* chars,
+                        Encoding    encoding,
+                        uint16_t    glyphs[],
+                        int         glyphCount) const override;
+
+    int onCountGlyphs() const override { return fGlyphCount; }
+
+    int onGetUPEM() const override { return fUpem; }
+
+    void                          onGetFamilyName(SkString* familyName) const override;
+    SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const override;
+
+    int onGetVariationDesignPosition(SkFontArguments::VariationPosition::Coordinate coordinates[],
+                                     int coordinateCount) const override {
+        return 0;
+    }
+
+    int onGetVariationDesignParameters(SkFontParameters::Variation::Axis parameters[],
+                                       int parameterCount) const override {
+        return 0;
+    }
+
+    int onGetTableTags(SkFontTableTag tags[]) const override { return 0; }
+
+    size_t onGetTableData(SkFontTableTag tag,
+                          size_t         offset,
+                          size_t         length,
+                          void*          data) const override {
+        return 0;
+    }
+
+private:
+    struct Glyph {
+        Glyph();
+        ~Glyph();
+        SkPoint     fOrigin;
+        SkScalar    fAdvance;
+        const char* fResourcePath;
+
+        SkSize size() const;
+        void render(SkCanvas*) const;
+
+    private:
+        // Lazily parses the SVG from fResourcePath, and manages mutex locking.
+        template <typename Fn> void withSVG(Fn&&) const;
+
+        // The mutex guards lazy parsing of the SVG, but also predates that.
+        // Must be SkSVGDOM::render() is not thread safe?
+        // If not, an SkOnce is enough here.
+        mutable SkMutex         fSvgMutex;
+        mutable bool            fParsedSvg = false;
+        mutable sk_sp<SkSVGDOM> fSvg;
+    };
+
+    SkString                         fName;
+    int                              fUpem;
+    const SkFontMetrics              fFontMetrics;
+    std::unique_ptr<Glyph[]>         fGlyphs;
+    int                              fGlyphCount;
+    SkTHashMap<SkUnichar, SkGlyphID> fCMap;
+    friend class SkTestSVGScalerContext;
+};
+
+#endif
diff --git a/tools/fonts/TestTypeface.cpp b/tools/fonts/TestTypeface.cpp
new file mode 100644
index 0000000..f8427cc
--- /dev/null
+++ b/tools/fonts/TestTypeface.cpp
@@ -0,0 +1,225 @@
+/*
+ * 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 "TestTypeface.h"
+#include "SkAdvancedTypefaceMetrics.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkFontDescriptor.h"
+#include "SkFontMetrics.h"
+#include "SkFontPriv.h"
+#include "SkGlyph.h"
+#include "SkImageInfo.h"
+#include "SkMatrix.h"
+#include "SkOTUtils.h"
+#include "SkPaintPriv.h"
+#include "SkPath.h"
+#include "SkPoint.h"
+#include "SkRect.h"
+#include "SkScalerContext.h"
+#include "SkString.h"
+#include "SkTDArray.h"
+#include "SkTo.h"
+#include "SkUtils.h"
+
+#include <utility>
+
+class SkDescriptor;
+
+SkTestFont::SkTestFont(const SkTestFontData& fontData)
+        : INHERITED()
+        , fCharCodes(fontData.fCharCodes)
+        , fCharCodesCount(fontData.fCharCodes ? fontData.fCharCodesCount : 0)
+        , fWidths(fontData.fWidths)
+        , fMetrics(fontData.fMetrics)
+        , fName(fontData.fName)
+        , fPaths(nullptr) {
+    init(fontData.fPoints, fontData.fVerbs);
+}
+
+SkTestFont::~SkTestFont() {
+    for (unsigned index = 0; index < fCharCodesCount; ++index) {
+        delete fPaths[index];
+    }
+    delete[] fPaths;
+}
+
+SkGlyphID SkTestFont::glyphForUnichar(SkUnichar charCode) const {
+    for (size_t index = 0; index < fCharCodesCount; ++index) {
+        if (fCharCodes[index] == charCode) {
+            return SkTo<SkGlyphID>(index);
+        }
+    }
+    return 0;
+}
+
+void SkTestFont::init(const SkScalar* pts, const unsigned char* verbs) {
+    fPaths = new SkPath*[fCharCodesCount];
+    for (unsigned index = 0; index < fCharCodesCount; ++index) {
+        SkPath*      path = new SkPath;
+        SkPath::Verb verb;
+        while ((verb = (SkPath::Verb)*verbs++) != SkPath::kDone_Verb) {
+            switch (verb) {
+                case SkPath::kMove_Verb:
+                    path->moveTo(pts[0], pts[1]);
+                    pts += 2;
+                    break;
+                case SkPath::kLine_Verb:
+                    path->lineTo(pts[0], pts[1]);
+                    pts += 2;
+                    break;
+                case SkPath::kQuad_Verb:
+                    path->quadTo(pts[0], pts[1], pts[2], pts[3]);
+                    pts += 4;
+                    break;
+                case SkPath::kCubic_Verb:
+                    path->cubicTo(pts[0], pts[1], pts[2], pts[3], pts[4], pts[5]);
+                    pts += 6;
+                    break;
+                case SkPath::kClose_Verb: path->close(); break;
+                default: SkDEBUGFAIL("bad verb"); return;
+            }
+        }
+        // This should make SkPath::getBounds() queries threadsafe.
+        path->updateBoundsCache();
+        fPaths[index] = path;
+    }
+}
+
+TestTypeface::TestTypeface(sk_sp<SkTestFont> testFont, const SkFontStyle& style)
+        : SkTypeface(style, false), fTestFont(std::move(testFont)) {}
+
+void TestTypeface::getAdvance(SkGlyph* glyph) {
+    SkGlyphID glyphID = glyph->getGlyphID();
+    glyphID           = glyphID < fTestFont->fCharCodesCount ? glyphID : 0;
+
+    // TODO(benjaminwagner): Update users to use floats.
+    glyph->fAdvanceX = SkFixedToFloat(fTestFont->fWidths[glyphID]);
+    glyph->fAdvanceY = 0;
+}
+
+void TestTypeface::getFontMetrics(SkFontMetrics* metrics) { *metrics = fTestFont->fMetrics; }
+
+void TestTypeface::getPath(SkGlyphID glyphID, SkPath* path) {
+    glyphID = glyphID < fTestFont->fCharCodesCount ? glyphID : 0;
+    *path   = *fTestFont->fPaths[glyphID];
+}
+
+void TestTypeface::onFilterRec(SkScalerContextRec* rec) const {
+    rec->setHinting(kNo_SkFontHinting);
+}
+
+void TestTypeface::getGlyphToUnicodeMap(SkUnichar* glyphToUnicode) const {
+    unsigned glyphCount = fTestFont->fCharCodesCount;
+    for (unsigned gid = 0; gid < glyphCount; ++gid) {
+        glyphToUnicode[gid] = SkTo<SkUnichar>(fTestFont->fCharCodes[gid]);
+    }
+}
+
+std::unique_ptr<SkAdvancedTypefaceMetrics> TestTypeface::onGetAdvancedMetrics() const {  // pdf only
+    std::unique_ptr<SkAdvancedTypefaceMetrics>info(new SkAdvancedTypefaceMetrics);
+    info->fFontName.set(fTestFont->fName);
+    return info;
+}
+
+void TestTypeface::onGetFontDescriptor(SkFontDescriptor* desc, bool* isLocal) const {
+    desc->setFamilyName(fTestFont->fName);
+    desc->setStyle(this->fontStyle());
+    *isLocal = false;
+}
+
+int TestTypeface::onCharsToGlyphs(const void* chars,
+                                  Encoding    encoding,
+                                  SkGlyphID   glyphs[],
+                                  int         glyphCount) const {
+    auto utf8  = (const char*)chars;
+    auto utf16 = (const uint16_t*)chars;
+    auto utf32 = (const SkUnichar*)chars;
+
+    for (int i = 0; i < glyphCount; ++i) {
+        SkUnichar ch;
+        switch (encoding) {
+            case kUTF8_Encoding: ch = SkUTF8_NextUnichar(&utf8); break;
+            case kUTF16_Encoding: ch = SkUTF16_NextUnichar(&utf16); break;
+            case kUTF32_Encoding: ch = *utf32++; break;
+        }
+        if (glyphs) {
+            glyphs[i] = fTestFont->glyphForUnichar(ch);
+        }
+    }
+    return glyphCount;
+}
+
+void TestTypeface::onGetFamilyName(SkString* familyName) const { *familyName = fTestFont->fName; }
+
+SkTypeface::LocalizedStrings* TestTypeface::onCreateFamilyNameIterator() const {
+    SkString familyName(fTestFont->fName);
+    SkString language("und");  // undetermined
+    return new SkOTUtils::LocalizedStrings_SingleName(familyName, language);
+}
+
+class SkTestScalerContext : public SkScalerContext {
+public:
+    SkTestScalerContext(sk_sp<TestTypeface>           face,
+                        const SkScalerContextEffects& effects,
+                        const SkDescriptor*           desc)
+            : SkScalerContext(std::move(face), effects, desc) {
+        fRec.getSingleMatrix(&fMatrix);
+        this->forceGenerateImageFromPath();
+    }
+
+protected:
+    TestTypeface* getTestTypeface() const {
+        return static_cast<TestTypeface*>(this->getTypeface());
+    }
+
+    unsigned generateGlyphCount() override { return this->getTestTypeface()->onCountGlyphs(); }
+
+    uint16_t generateCharToGlyph(SkUnichar uni) override {
+        uint16_t glyph;
+        (void)this->getTestTypeface()->onCharsToGlyphs(
+                (const void*)&uni, SkTypeface::kUTF32_Encoding, &glyph, 1);
+        return glyph;
+    }
+
+    bool generateAdvance(SkGlyph* glyph) override {
+        this->getTestTypeface()->getAdvance(glyph);
+
+        const SkVector advance =
+                fMatrix.mapXY(SkFloatToScalar(glyph->fAdvanceX), SkFloatToScalar(glyph->fAdvanceY));
+        glyph->fAdvanceX = SkScalarToFloat(advance.fX);
+        glyph->fAdvanceY = SkScalarToFloat(advance.fY);
+        return true;
+    }
+
+    void generateMetrics(SkGlyph* glyph) override {
+        glyph->zeroMetrics();
+        this->generateAdvance(glyph);
+        // Always generates from paths, so SkScalerContext::getMetrics will figure the bounds.
+    }
+
+    void generateImage(const SkGlyph&) override { SK_ABORT("Should have generated from path."); }
+
+    bool generatePath(SkGlyphID glyph, SkPath* path) override {
+        this->getTestTypeface()->getPath(glyph, path);
+        path->transform(fMatrix);
+        return true;
+    }
+
+    void generateFontMetrics(SkFontMetrics* metrics) override {
+        this->getTestTypeface()->getFontMetrics(metrics);
+        SkFontPriv::ScaleFontMetrics(metrics, fMatrix.getScaleY());
+    }
+
+private:
+    SkMatrix fMatrix;
+};
+
+SkScalerContext* TestTypeface::onCreateScalerContext(const SkScalerContextEffects& effects,
+                                                     const SkDescriptor*           desc) const {
+    return new SkTestScalerContext(sk_ref_sp(const_cast<TestTypeface*>(this)), effects, desc);
+}
diff --git a/tools/fonts/TestTypeface.h b/tools/fonts/TestTypeface.h
new file mode 100644
index 0000000..8ea837f
--- /dev/null
+++ b/tools/fonts/TestTypeface.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef TestTypeface_DEFINED
+#define TestTypeface_DEFINED
+
+#include "SkFixed.h"
+#include "SkFontArguments.h"
+#include "SkFontMetrics.h"
+#include "SkFontStyle.h"
+#include "SkPaint.h"
+#include "SkRefCnt.h"
+#include "SkScalar.h"
+#include "SkTypeface.h"
+#include "SkTypes.h"
+
+#include <memory>
+
+class SkDescriptor;
+class SkFontDescriptor;
+class SkGlyph;
+class SkPath;
+class SkScalerContext;
+class SkStreamAsset;
+class SkString;
+class SkTestFont;
+struct SkAdvancedTypefaceMetrics;
+struct SkScalerContextEffects;
+struct SkScalerContextRec;
+
+struct SkTestFontData {
+    const SkScalar*      fPoints;
+    const unsigned char* fVerbs;
+    const SkUnichar*     fCharCodes;
+    const size_t         fCharCodesCount;
+    const SkFixed*       fWidths;
+    const SkFontMetrics& fMetrics;
+    const char*          fName;
+    SkFontStyle          fStyle;
+};
+
+class SkTestFont : public SkRefCnt {
+public:
+    SkTestFont(const SkTestFontData&);
+    virtual ~SkTestFont();
+    SkGlyphID glyphForUnichar(SkUnichar charCode) const;
+    void      init(const SkScalar* pts, const unsigned char* verbs);
+
+private:
+    const SkUnichar*     fCharCodes;
+    const size_t         fCharCodesCount;
+    const SkFixed*       fWidths;
+    const SkFontMetrics& fMetrics;
+    const char*          fName;
+    SkPath**             fPaths;
+    friend class TestTypeface;
+    typedef SkRefCnt INHERITED;
+};
+
+class TestTypeface : public SkTypeface {
+public:
+    TestTypeface(sk_sp<SkTestFont>, const SkFontStyle& style);
+    void getAdvance(SkGlyph* glyph);
+    void getFontMetrics(SkFontMetrics* metrics);
+    void getPath(SkGlyphID glyph, SkPath* path);
+
+protected:
+    SkScalerContext* onCreateScalerContext(const SkScalerContextEffects&,
+                                           const SkDescriptor* desc) const override;
+    void             onFilterRec(SkScalerContextRec* rec) const override;
+    void             getGlyphToUnicodeMap(SkUnichar* glyphToUnicode) const override;
+    std::unique_ptr<SkAdvancedTypefaceMetrics> onGetAdvancedMetrics() const override;
+
+    std::unique_ptr<SkStreamAsset> onOpenStream(int* ttcIndex) const override { return nullptr; }
+
+    sk_sp<SkTypeface> onMakeClone(const SkFontArguments& args) const override {
+        return sk_ref_sp(this);
+    }
+
+    void onGetFontDescriptor(SkFontDescriptor* desc, bool* isLocal) const override;
+
+    int onCharsToGlyphs(const void* chars,
+                        Encoding    encoding,
+                        uint16_t    glyphs[],
+                        int         glyphCount) const override;
+
+    int onCountGlyphs() const override { return (int)fTestFont->fCharCodesCount; }
+
+    int onGetUPEM() const override { return 2048; }
+
+    void                          onGetFamilyName(SkString* familyName) const override;
+    SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const override;
+
+    int onGetVariationDesignPosition(SkFontArguments::VariationPosition::Coordinate coordinates[],
+                                     int coordinateCount) const override {
+        return 0;
+    }
+
+    int onGetVariationDesignParameters(SkFontParameters::Variation::Axis parameters[],
+                                       int parameterCount) const override {
+        return 0;
+    }
+
+    int onGetTableTags(SkFontTableTag tags[]) const override { return 0; }
+
+    size_t onGetTableData(SkFontTableTag tag,
+                          size_t         offset,
+                          size_t         length,
+                          void*          data) const override {
+        return 0;
+    }
+
+private:
+    sk_sp<SkTestFont> fTestFont;
+    friend class SkTestScalerContext;
+};
+
+#endif
diff --git a/tools/fonts/ToolUtilsFont.cpp b/tools/fonts/ToolUtilsFont.cpp
new file mode 100644
index 0000000..7d00682
--- /dev/null
+++ b/tools/fonts/ToolUtilsFont.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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 "ToolUtils.h"
+
+#include "Resources.h"
+#include "SkFontMgr.h"
+#include "SkFontStyle.h"
+#include "SkMutex.h"
+#include "SkOSFile.h"
+#include "SkTypeface.h"
+#include "SkUTF.h"
+#include "TestFontMgr.h"
+
+namespace ToolUtils {
+
+sk_sp<SkTypeface> planet_typeface() {
+    static const sk_sp<SkTypeface> planetTypeface = []() {
+        const char* filename;
+#if defined(SK_BUILD_FOR_WIN)
+        filename = "fonts/planetcolr.ttf";
+#elif defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
+        filename = "fonts/planetsbix.ttf";
+#else
+        filename = "fonts/planetcbdt.ttf";
+#endif
+        sk_sp<SkTypeface> typeface = MakeResourceAsTypeface(filename);
+        if (typeface) {
+            return typeface;
+        }
+        return SkTypeface::MakeFromName("Planet", SkFontStyle());
+    }();
+    return planetTypeface;
+}
+
+sk_sp<SkTypeface> emoji_typeface() {
+    static const sk_sp<SkTypeface> emojiTypeface = []() {
+        const char* filename;
+#if defined(SK_BUILD_FOR_WIN)
+        filename = "fonts/colr.ttf";
+#elif defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
+        filename = "fonts/sbix.ttf";
+#else
+        filename = "fonts/cbdt.ttf";
+#endif
+        sk_sp<SkTypeface> typeface = MakeResourceAsTypeface(filename);
+        if (typeface) {
+            return typeface;
+        }
+        return SkTypeface::MakeFromName("Emoji", SkFontStyle());
+    }();
+    return emojiTypeface;
+}
+
+const char* emoji_sample_text() {
+    return "\xF0\x9F\x98\x80"
+           " "
+           "\xE2\x99\xA2";  // 😀 ♢
+}
+static sk_sp<SkTypeface> create_font(const char* name, SkFontStyle style) {
+    static sk_sp<SkFontMgr> portableFontMgr = MakePortableFontMgr();
+    return portableFontMgr->legacyMakeTypeface(name, style);
+}
+
+sk_sp<SkTypeface> create_portable_typeface(const char* name, SkFontStyle style) {
+    return create_font(name, style);
+}
+}  // namespace ToolUtils
diff --git a/tools/fonts/create_test_font.cpp b/tools/fonts/create_test_font.cpp
index 51a2881..3893137 100644
--- a/tools/fonts/create_test_font.cpp
+++ b/tools/fonts/create_test_font.cpp
@@ -7,7 +7,7 @@
 
 // Running create_test_font generates ./tools/fonts/test_font_index.inc
 // and ./tools/fonts/test_font_<generic name>.inc which are read by
-// ./tools/fonts/SkTestFontMgr.cpp
+// ./tools/fonts/TestFontMgr.cpp
 
 #include "SkFont.h"
 #include "SkFontMetrics.h"
diff --git a/tools/fonts/create_test_font_color.cpp b/tools/fonts/create_test_font_color.cpp
index c3f2e08..04d9e5e 100644
--- a/tools/fonts/create_test_font_color.cpp
+++ b/tools/fonts/create_test_font_color.cpp
@@ -8,30 +8,43 @@
 // running create_test_font_color generates ./<cbdt|sbix|cpal>.ttx
 // which are read by fonttools ttx to produce native fonts.
 
-#include "SkCommandLineFlags.h"
+#include "CommandLineFlags.h"
 #include "SkRefCnt.h"
 #include "SkStream.h"
-#include "SkTestSVGTypeface.h"
+#include "SkString.h"
+#include "TestSVGTypeface.h"
 
-int main(int argc, char** argv) {
-    SkCommandLineFlags::Parse(argc, argv);
-
-    sk_sp<SkTestSVGTypeface> typeface = SkTestSVGTypeface::Default();
-
-    SkFILEWStream cbdt("cbdt.ttx");
-    typeface->exportTtxCbdt(&cbdt);
+static void export_ttx(sk_sp<TestSVGTypeface> typeface,
+                       SkString               prefix,
+                       SkSpan<unsigned>       cbdtStrikeSizes,
+                       SkSpan<unsigned>       sbixStrikeSizes) {
+    SkFILEWStream cbdt((SkString(prefix) += "cbdt.ttx").c_str());
+    typeface->exportTtxCbdt(&cbdt, cbdtStrikeSizes);
     cbdt.flush();
     cbdt.fsync();
 
-    SkFILEWStream sbix("sbix.ttx");
-    typeface->exportTtxSbix(&sbix);
+    SkFILEWStream sbix((SkString(prefix) += "sbix.ttx").c_str());
+    typeface->exportTtxSbix(&sbix, sbixStrikeSizes);
     sbix.flush();
     sbix.fsync();
 
-    SkFILEWStream colr("colr.ttx");
+    SkFILEWStream colr((SkString(prefix) += "colr.ttx").c_str());
     typeface->exportTtxColr(&colr);
     colr.flush();
     colr.fsync();
+}
+
+int main(int argc, char** argv) {
+    CommandLineFlags::Parse(argc, argv);
+
+    // Most of the time use these sizes.
+    unsigned usual[] = { 16, 64, 128 };
+
+    // But the planet font cannot get very big in the size limited cbdt format.
+    unsigned small[] = { 8, 16 };
+
+    export_ttx(TestSVGTypeface::Default(), SkString(), usual, usual);
+    export_ttx(TestSVGTypeface::Planets(), SkString("planet"), small, usual);
 
     return 0;
 }
diff --git a/tools/fonts/sk_tool_utils_font.cpp b/tools/fonts/sk_tool_utils_font.cpp
deleted file mode 100644
index 68c1abc..0000000
--- a/tools/fonts/sk_tool_utils_font.cpp
+++ /dev/null
@@ -1,79 +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 "sk_tool_utils.h"
-
-#include "Resources.h"
-#include "SkCommonFlags.h"
-#include "SkFontMgr.h"
-#include "SkFontStyle.h"
-#include "SkMutex.h"
-#include "SkOSFile.h"
-#include "SkTestFontMgr.h"
-#include "SkTypeface.h"
-#include "SkUTF.h"
-
-namespace sk_tool_utils {
-
-sk_sp<SkTypeface> emoji_typeface() {
-    const char* filename;
-#if defined(SK_BUILD_FOR_WIN)
-    filename = "fonts/colr.ttf";
-#elif defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
-    filename = "fonts/sbix.ttf";
-#else
-    filename = "fonts/cbdt.ttf";
-#endif
-    sk_sp<SkTypeface> typeface = MakeResourceAsTypeface(filename);
-    if (typeface) {
-        return typeface;
-    }
-    return SkTypeface::MakeFromName("Emoji", SkFontStyle());
-}
-
-const char* emoji_sample_text() {
-    return "\xF0\x9F\x98\x80" " " "\xE2\x99\xA2"; // 😀 ♢
-}
-
-static const char* platform_os_name() {
-    for (int index = 0; index < FLAGS_key.count(); index += 2) {
-        if (!strcmp("os", FLAGS_key[index])) {
-            return FLAGS_key[index + 1];
-        }
-    }
-    return "";
-}
-
-static bool extra_config_contains(const char* substring) {
-    for (int index = 0; index < FLAGS_key.count(); index += 2) {
-        if (0 == strcmp("extra_config", FLAGS_key[index])
-                && strstr(FLAGS_key[index + 1], substring)) {
-            return true;
-        }
-    }
-    return false;
-}
-
-const char* platform_font_manager() {
-    if (extra_config_contains("GDI")) {
-        return "GDI";
-    }
-    if (extra_config_contains("NativeFonts")){
-        return platform_os_name();
-    }
-    return "";
-}
-
-static sk_sp<SkTypeface> create_font(const char* name, SkFontStyle style) {
-    static sk_sp<SkFontMgr> portableFontMgr = MakePortableFontMgr();
-    return portableFontMgr->legacyMakeTypeface(name, style);
-}
-
-sk_sp<SkTypeface> create_portable_typeface(const char* name, SkFontStyle style) {
-    return create_font(name, style);
-}
-}
diff --git a/tools/get_images_from_skps.cpp b/tools/get_images_from_skps.cpp
index 4867818..d573843 100644
--- a/tools/get_images_from_skps.cpp
+++ b/tools/get_images_from_skps.cpp
@@ -5,10 +5,10 @@
  * found in the LICENSE file.
  */
 
+#include "CommandLineFlags.h"
 #include "SkBitmap.h"
 #include "SkCodec.h"
 #include "SkColorSpace.h"
-#include "SkCommandLineFlags.h"
 #include "SkData.h"
 #include "SkJSONWriter.h"
 #include "SkMD5.h"
@@ -19,18 +19,18 @@
 #include "SkStream.h"
 #include "SkTHash.h"
 
-
 #include <iostream>
 #include <map>
 
-DEFINE_string2(skps, s, "skps", "A path to a directory of skps or a single skp.");
-DEFINE_string2(out, o, "img-out", "A path to an output directory.");
-DEFINE_bool(testDecode, false, "Indicates if we want to test that the images decode successfully.");
-DEFINE_bool(writeImages, true,
-            "Indicates if we want to write out supported/decoded images.");
-DEFINE_bool(writeFailedImages, false,
-            "Indicates if we want to write out unsupported/failed to decode images.");
-DEFINE_string2(failuresJsonPath, j, "",
+static DEFINE_string2(skps, s, "skps", "A path to a directory of skps or a single skp.");
+static DEFINE_string2(out, o, "img-out", "A path to an output directory.");
+static DEFINE_bool(testDecode, false,
+                   "Indicates if we want to test that the images decode successfully.");
+static DEFINE_bool(writeImages, true,
+                   "Indicates if we want to write out supported/decoded images.");
+static DEFINE_bool(writeFailedImages, false,
+                   "Indicates if we want to write out unsupported/failed to decode images.");
+static DEFINE_string2(failuresJsonPath, j, "",
                "Dump SKP and count of unknown images to the specified JSON file. Will not be "
                "written anywhere if empty.");
 
@@ -52,8 +52,7 @@
     void sniff(const void* ptr, size_t len) {
         SkMD5 md5;
         md5.write(ptr, len);
-        SkMD5::Digest digest;
-        md5.finish(digest);
+        SkMD5::Digest digest = md5.finish();
 
         if (gSeen.contains(digest)) {
             return;
@@ -138,16 +137,16 @@
 }
 
 int main(int argc, char** argv) {
-    SkCommandLineFlags::SetUsage(
+    CommandLineFlags::SetUsage(
             "Usage: get_images_from_skps -s <dir of skps> -o <dir for output images> --testDecode "
             "-j <output JSON path> --writeImages, --writeFailedImages\n");
 
-    SkCommandLineFlags::Parse(argc, argv);
+    CommandLineFlags::Parse(argc, argv);
     const char* inputs = FLAGS_skps[0];
     gOutputDir = FLAGS_out[0];
 
     if (!sk_isdir(gOutputDir)) {
-        SkCommandLineFlags::PrintUsage();
+        CommandLineFlags::PrintUsage();
         return 1;
     }
 
diff --git a/tools/gpu/GrContextFactory.cpp b/tools/gpu/GrContextFactory.cpp
index 42b8322..150a880 100644
--- a/tools/gpu/GrContextFactory.cpp
+++ b/tools/gpu/GrContextFactory.cpp
@@ -20,7 +20,6 @@
 #ifdef SK_METAL
 #include "mtl/MtlTestContext.h"
 #endif
-#include "gl/null/NullGLTestContext.h"
 #include "gl/GrGLGpu.h"
 #include "mock/MockTestContext.h"
 #include "GrCaps.h"
@@ -188,10 +187,6 @@
                     glCtx = CommandBufferGLTestContext::Create(glShareContext);
                     break;
 #endif
-                case kNullGL_ContextType:
-                    glCtx = CreateNullGLTestContext(
-                            ContextOverrides::kRequireNVPRSupport & overrides, glShareContext);
-                    break;
                 default:
                     return ContextInfo();
             }
@@ -228,8 +223,10 @@
 #endif
 #ifdef SK_METAL
         case GrBackendApi::kMetal: {
-            SkASSERT(!masterContext);
-            testCtx.reset(CreatePlatformMtlTestContext(nullptr));
+            MtlTestContext* mtlSharedContext = masterContext
+                    ? static_cast<MtlTestContext*>(masterContext->fTestContext) : nullptr;
+            SkASSERT(kMetal_ContextType == type);
+            testCtx.reset(CreatePlatformMtlTestContext(mtlSharedContext));
             if (!testCtx) {
                 return ContextInfo();
             }
diff --git a/tools/gpu/GrContextFactory.h b/tools/gpu/GrContextFactory.h
index e2e4fe9..4a7927c 100644
--- a/tools/gpu/GrContextFactory.h
+++ b/tools/gpu/GrContextFactory.h
@@ -39,7 +39,6 @@
         kANGLE_GL_ES2_ContextType,   //! ANGLE on OpenGL OpenGL ES 2 context.
         kANGLE_GL_ES3_ContextType,   //! ANGLE on OpenGL OpenGL ES 3 context.
         kCommandBuffer_ContextType,  //! Chromium command buffer OpenGL ES context.
-        kNullGL_ContextType,         //! Non-rendering OpenGL mock context.
         kVulkan_ContextType,         //! Vulkan
         kMetal_ContextType,          //! Metal
         kMock_ContextType,           //! Mock context that does not draw.
@@ -62,7 +61,6 @@
 
     static bool IsRenderingContext(ContextType type) {
         switch (type) {
-            case kNullGL_ContextType:
             case kMock_ContextType:
                 return false;
             default:
@@ -101,8 +99,6 @@
                 return "ANGLE GL ES3";
             case kCommandBuffer_ContextType:
                 return "Command Buffer";
-            case kNullGL_ContextType:
-                return "Null GL";
             case kVulkan_ContextType:
                 return "Vulkan";
             case kMetal_ContextType:
diff --git a/tools/gpu/GrTest.cpp b/tools/gpu/GrTest.cpp
index 7a0cbfe..b8a743f 100644
--- a/tools/gpu/GrTest.cpp
+++ b/tools/gpu/GrTest.cpp
@@ -14,6 +14,8 @@
 #include "GrGpu.h"
 #include "GrGpuResourceCacheAccess.h"
 #include "GrMemoryPool.h"
+#include "GrRecordingContext.h"
+#include "GrRecordingContextPriv.h"
 #include "GrRenderTargetContext.h"
 #include "GrRenderTargetContextPriv.h"
 #include "GrRenderTargetProxy.h"
@@ -65,29 +67,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-sk_sp<GrTextureProxy> GrProxyProvider::testingOnly_createInstantiatedProxy(
-        const GrSurfaceDesc& desc, GrSurfaceOrigin origin, SkBackingFit fit, SkBudgeted budgeted) {
-    sk_sp<GrTexture> tex;
-
-    if (SkBackingFit::kApprox == fit) {
-        tex = fResourceProvider->createApproxTexture(desc, GrResourceProvider::Flags::kNone);
-    } else {
-        tex = fResourceProvider->createTexture(desc, budgeted, GrResourceProvider::Flags::kNone);
-    }
-    if (!tex) {
-        return nullptr;
-    }
-
-    return this->createWrapped(std::move(tex), origin);
-}
-
-sk_sp<GrTextureProxy> GrProxyProvider::testingOnly_createWrapped(sk_sp<GrTexture> tex,
-                                                                 GrSurfaceOrigin origin) {
-    return this->createWrapped(std::move(tex), origin);
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
 #define ASSERT_SINGLE_OWNER \
     SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(fRenderTargetContext->singleOwner());)
 
@@ -105,12 +84,12 @@
         std::unique_ptr<GrDrawOp> op,
         const std::function<GrRenderTargetContext::WillAddOpFn>& willAddFn) {
     ASSERT_SINGLE_OWNER
-    if (fRenderTargetContext->drawingManager()->wasAbandoned()) {
+    if (fRenderTargetContext->fContext->priv().abandoned()) {
         fRenderTargetContext->fContext->priv().opMemoryPool()->release(std::move(op));
         return;
     }
     SkDEBUGCODE(fRenderTargetContext->validate());
-    GR_AUDIT_TRAIL_AUTO_FRAME(fRenderTargetContext->fAuditTrail,
+    GR_AUDIT_TRAIL_AUTO_FRAME(fRenderTargetContext->auditTrail(),
                               "GrRenderTargetContext::testingOnly_addDrawOp");
     fRenderTargetContext->addDrawOp(clip, std::move(op), willAddFn);
 }
@@ -184,7 +163,8 @@
 //////////////////////////////////////////////////////////////////////////////
 
 #define DRAW_OP_TEST_EXTERN(Op) \
-    extern std::unique_ptr<GrDrawOp> Op##__Test(GrPaint&&, SkRandom*, GrContext*, GrFSAAType)
+    extern std::unique_ptr<GrDrawOp> Op##__Test(GrPaint&&, SkRandom*, \
+                                                GrRecordingContext*, GrFSAAType)
 #define DRAW_OP_TEST_ENTRY(Op) Op##__Test
 
 DRAW_OP_TEST_EXTERN(AAConvexPathOp);
@@ -198,8 +178,8 @@
 DRAW_OP_TEST_EXTERN(EllipseOp);
 DRAW_OP_TEST_EXTERN(FillRectOp);
 DRAW_OP_TEST_EXTERN(GrAtlasTextOp);
-DRAW_OP_TEST_EXTERN(GrDrawAtlasOp);
-DRAW_OP_TEST_EXTERN(GrDrawVerticesOp);
+DRAW_OP_TEST_EXTERN(DrawAtlasOp);
+DRAW_OP_TEST_EXTERN(DrawVerticesOp);
 DRAW_OP_TEST_EXTERN(NonAALatticeOp);
 DRAW_OP_TEST_EXTERN(NonAAStrokeRectOp);
 DRAW_OP_TEST_EXTERN(ShadowRRectOp);
@@ -210,8 +190,9 @@
 DRAW_OP_TEST_EXTERN(TextureOp);
 
 void GrDrawRandomOp(SkRandom* random, GrRenderTargetContext* renderTargetContext, GrPaint&& paint) {
-    GrContext* context = renderTargetContext->surfPriv().getContext();
-    using MakeDrawOpFn = std::unique_ptr<GrDrawOp>(GrPaint&&, SkRandom*, GrContext*, GrFSAAType);
+    auto context = renderTargetContext->surfPriv().getContext();
+    using MakeDrawOpFn = std::unique_ptr<GrDrawOp>(GrPaint&&, SkRandom*,
+                                                   GrRecordingContext*, GrFSAAType);
     static constexpr MakeDrawOpFn* gFactories[] = {
             DRAW_OP_TEST_ENTRY(AAConvexPathOp),
             DRAW_OP_TEST_ENTRY(AAFlatteningConvexPathOp),
@@ -224,8 +205,8 @@
             DRAW_OP_TEST_ENTRY(EllipseOp),
             DRAW_OP_TEST_ENTRY(FillRectOp),
             DRAW_OP_TEST_ENTRY(GrAtlasTextOp),
-            DRAW_OP_TEST_ENTRY(GrDrawAtlasOp),
-            DRAW_OP_TEST_ENTRY(GrDrawVerticesOp),
+            DRAW_OP_TEST_ENTRY(DrawAtlasOp),
+            DRAW_OP_TEST_ENTRY(DrawVerticesOp),
             DRAW_OP_TEST_ENTRY(NonAALatticeOp),
             DRAW_OP_TEST_ENTRY(NonAAStrokeRectOp),
             DRAW_OP_TEST_ENTRY(ShadowRRectOp),
diff --git a/tools/gpu/MemoryCache.cpp b/tools/gpu/MemoryCache.cpp
index 7fc3e1b..771d1a1 100644
--- a/tools/gpu/MemoryCache.cpp
+++ b/tools/gpu/MemoryCache.cpp
@@ -5,8 +5,12 @@
  * found in the LICENSE file.
  */
 
+#include "GrPersistentCacheUtils.h"
 #include "MemoryCache.h"
 #include "SkBase64.h"
+#include "SkJSONWriter.h"
+#include "SkMD5.h"
+#include "SkTHash.h"
 
 // Change this to 1 to log cache hits/misses/stores using SkDebugf.
 #define LOG_MEMORY_CACHE 0
@@ -40,9 +44,10 @@
     }
     if (LOG_MEMORY_CACHE) {
         SkDebugf("Load Key: %s\n\tFound Data: %s\n\n", data_to_str(key).c_str(),
-                 data_to_str(*result->second).c_str());
+                 data_to_str(*result->second.fData).c_str());
     }
-    return result->second;
+    result->second.fHitCount++;
+    return result->second.fData;
 }
 
 void MemoryCache::store(const SkData& key, const SkData& data) {
@@ -50,7 +55,52 @@
         SkDebugf("Store Key: %s\n\tData: %s\n\n", data_to_str(key).c_str(),
                  data_to_str(data).c_str());
     }
-    fMap[Key(key)] = SkData::MakeWithCopy(data.data(), data.size());
+    fMap[Key(key)] = Value(data);
+}
+
+void MemoryCache::writeShadersToDisk(const char* path, GrBackendApi api) {
+    if (GrBackendApi::kOpenGL != api) {
+        // TODO: Add SPIRV support, too.
+        return;
+    }
+
+    // Default extensions detected by the Mali Offline Compiler
+    const char* extensions[kGrShaderTypeCount] = { "vert", "geom", "frag" };
+
+    // For now, we only dump fragment shaders. They are the biggest factor in performance, and
+    // the shaders for other stages tend to be heavily reused.
+    SkString jsonPath = SkStringPrintf("%s/%s.json", path, extensions[kFragment_GrShaderType]);
+    SkFILEWStream jsonFile(jsonPath.c_str());
+    SkJSONWriter writer(&jsonFile, SkJSONWriter::Mode::kPretty);
+    writer.beginArray();
+
+    for (auto it = fMap.begin(); it != fMap.end(); ++it) {
+        SkMD5 hash;
+        hash.write(it->first.fKey->bytes(), it->first.fKey->size());
+        SkMD5::Digest digest = hash.finish();
+        SkString md5;
+        for (int i = 0; i < 16; ++i) {
+            md5.appendf("%02x", digest.data[i]);
+        }
+
+        // Write [ hash, hitCount ] to JSON digest
+        writer.beginArray(nullptr, false);
+        writer.appendString(md5.c_str());
+        writer.appendS32(it->second.fHitCount);
+        writer.endArray();
+
+        SkReader32 reader(it->second.fData->data(), it->second.fData->size());
+        SkSL::Program::Inputs inputsIgnored;
+        SkSL::String glsl[kGrShaderTypeCount];
+        GrPersistentCacheUtils::UnpackCachedGLSL(reader, &inputsIgnored, glsl);
+
+        SkString filename = SkStringPrintf("%s/%s.%s", path, md5.c_str(),
+                                           extensions[kFragment_GrShaderType]);
+        SkFILEWStream file(filename.c_str());
+        file.write(glsl[kFragment_GrShaderType].c_str(), glsl[kFragment_GrShaderType].size());
+    }
+
+    writer.endArray();
 }
 
 }  // namespace sk_gpu_test
diff --git a/tools/gpu/MemoryCache.h b/tools/gpu/MemoryCache.h
index dea1ccd..119ea8d 100644
--- a/tools/gpu/MemoryCache.h
+++ b/tools/gpu/MemoryCache.h
@@ -33,6 +33,8 @@
     int numCacheMisses() const { return fCacheMissCnt; }
     void resetNumCacheMisses() { fCacheMissCnt = 0; }
 
+    void writeShadersToDisk(const char* path, GrBackendApi backend);
+
 private:
     struct Key {
         Key() = default;
@@ -46,6 +48,18 @@
         sk_sp<const SkData> fKey;
     };
 
+    struct Value {
+        Value() = default;
+        Value(const SkData& data)
+            : fData(SkData::MakeWithCopy(data.data(), data.size()))
+            , fHitCount(1) {}
+        Value(const Value& that) = default;
+        Value& operator=(const Value&) = default;
+
+        sk_sp<SkData> fData;
+        int fHitCount;
+    };
+
     struct Hash {
         using argument_type = Key;
         using result_type = uint32_t;
@@ -55,7 +69,7 @@
     };
 
     int fCacheMissCnt = 0;
-    std::unordered_map<Key, sk_sp<SkData>, Hash> fMap;
+    std::unordered_map<Key, Value, Hash> fMap;
 };
 
 }  // namespace sk_gpu_test
diff --git a/tools/gpu/ProxyUtils.cpp b/tools/gpu/ProxyUtils.cpp
index 31b9ddd..8167264 100644
--- a/tools/gpu/ProxyUtils.cpp
+++ b/tools/gpu/ProxyUtils.cpp
@@ -14,11 +14,12 @@
 
 namespace sk_gpu_test {
 
-sk_sp<GrTextureProxy> MakeTextureProxyFromData(GrContext* context, bool isRT, int width, int height,
+sk_sp<GrTextureProxy> MakeTextureProxyFromData(GrContext* context, bool isRT,
+                                               int width, int height,
                                                GrColorType colorType, GrSRGBEncoded srgbEncoded,
                                                GrSurfaceOrigin origin, const void* data,
                                                size_t rowBytes) {
-    if (context->abandoned()) {
+    if (context->priv().abandoned()) {
         return nullptr;
     }
 
diff --git a/tools/gpu/ProxyUtils.h b/tools/gpu/ProxyUtils.h
index 6dfcd71..3ad23ae 100644
--- a/tools/gpu/ProxyUtils.h
+++ b/tools/gpu/ProxyUtils.h
@@ -14,12 +14,13 @@
 namespace sk_gpu_test {
 
 /** Makes a texture proxy containing the passed in color data. */
-sk_sp<GrTextureProxy> MakeTextureProxyFromData(GrContext* context, bool isRT, int width, int height,
+sk_sp<GrTextureProxy> MakeTextureProxyFromData(GrContext*, bool isRT, int width, int height,
                                                GrColorType, GrSRGBEncoded, GrSurfaceOrigin,
                                                const void* data, size_t rowBytes);
 
 /** Version that assumes GrSRGBEncoded::kNo. */
-inline sk_sp<GrTextureProxy> MakeTextureProxyFromData(GrContext* context, bool isRT, int width,
+inline sk_sp<GrTextureProxy> MakeTextureProxyFromData(GrContext* context,
+                                                      bool isRT, int width,
                                                       int height, GrColorType ct,
                                                       GrSurfaceOrigin origin, const void* data,
                                                       size_t rowBytes) {
@@ -28,7 +29,8 @@
 }
 
 /** Version that takes SkColorType rather than GrColorType and assumes GrSRGBEncoded::kNo. */
-inline sk_sp<GrTextureProxy> MakeTextureProxyFromData(GrContext* context, bool isRT, int width,
+inline sk_sp<GrTextureProxy> MakeTextureProxyFromData(GrContext* context,
+                                                      bool isRT, int width,
                                                       int height, SkColorType ct,
                                                       GrSurfaceOrigin origin, const void* data,
                                                       size_t rowBytes) {
diff --git a/tools/gpu/YUVUtils.cpp b/tools/gpu/YUVUtils.cpp
new file mode 100644
index 0000000..8f68986
--- /dev/null
+++ b/tools/gpu/YUVUtils.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "YUVUtils.h"
+
+#include "GrContext.h"
+#include "GrContextPriv.h"
+#include "SkCodecImageGenerator.h"
+#include "SkData.h"
+
+namespace sk_gpu_test {
+
+std::unique_ptr<LazyYUVImage> LazyYUVImage::Make(sk_sp<SkData> data) {
+    std::unique_ptr<LazyYUVImage> image(new LazyYUVImage());
+    if (image->reset(std::move(data))) {
+        return image;
+    } else {
+        return nullptr;
+    }
+}
+
+sk_sp<SkImage> LazyYUVImage::refImage(GrContext* context) {
+    if (this->ensureYUVImage(context)) {
+        return fYUVImage;
+    } else {
+        return nullptr;
+    }
+}
+
+const SkImage* LazyYUVImage::getImage(GrContext* context) {
+    if (this->ensureYUVImage(context)) {
+        return fYUVImage.get();
+    } else {
+        return nullptr;
+    }
+}
+
+bool LazyYUVImage::reset(sk_sp<SkData> data) {
+    auto codec = SkCodecImageGenerator::MakeFromEncodedCodec(data);
+    if (!codec) {
+        return false;
+    }
+
+    if (!codec->queryYUVA8(&fSizeInfo, fComponents, &fColorSpace)) {
+        return false;
+    }
+
+    fPlaneData.reset(fSizeInfo.computeTotalBytes());
+    void* planes[SkYUVASizeInfo::kMaxCount];
+    fSizeInfo.computePlanes(fPlaneData.get(), planes);
+    if (!codec->getYUVA8Planes(fSizeInfo, fComponents, planes)) {
+        return false;
+    }
+
+    for (int i = 0; i < SkYUVASizeInfo::kMaxCount; ++i) {
+        if (fSizeInfo.fSizes[i].isEmpty()) {
+            fPlanes[i].reset();
+        } else {
+            SkASSERT(planes[i]);
+            auto planeInfo = SkImageInfo::Make(fSizeInfo.fSizes[i].fWidth,
+                                               fSizeInfo.fSizes[i].fHeight,
+                                               kGray_8_SkColorType, kOpaque_SkAlphaType, nullptr);
+            fPlanes[i].reset(planeInfo, planes[i], fSizeInfo.fWidthBytes[i]);
+        }
+    }
+    // The SkPixmap data is fully configured now for MakeFromYUVAPixmaps once we get a GrContext
+    return true;
+}
+
+bool LazyYUVImage::ensureYUVImage(GrContext* context) {
+    if (!context) {
+        return false; // Cannot make a YUV image from planes
+    }
+    if (context->priv().contextID() == fOwningContextID) {
+        return fYUVImage != nullptr; // Have already made a YUV image (or tried and failed)
+    }
+    // Must make a new YUV image
+    fYUVImage = SkImage::MakeFromYUVAPixmaps(context, fColorSpace, fPlanes, fComponents,
+            fSizeInfo.fSizes[0], kTopLeft_GrSurfaceOrigin, false, false);
+    fOwningContextID = context->priv().contextID();
+    return fYUVImage != nullptr;
+}
+
+} // namespace sk_gpu_test
diff --git a/tools/gpu/YUVUtils.h b/tools/gpu/YUVUtils.h
new file mode 100644
index 0000000..b46b35b
--- /dev/null
+++ b/tools/gpu/YUVUtils.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef YUVUtils_DEFINED
+#define YUVUtils_DEFINED
+
+#include "SkAutoMalloc.h"
+#include "SkImage.h"
+#include "SkYUVAIndex.h"
+#include "SkYUVASizeInfo.h"
+
+class SkData;
+
+namespace sk_gpu_test {
+
+// Utility that decodes a JPEG but preserves the YUVA8 planes in the image, and uses
+// MakeFromYUVAPixmaps to create a GPU multiplane YUVA image for a context. It extracts the planar
+// data once, and lazily creates the actual SkImage when the GrContext is provided (and refreshes
+// the image if the context has changed, as in Viewer)
+class LazyYUVImage {
+public:
+    // Returns null if the data could not be extracted into YUVA8 planes
+    static std::unique_ptr<LazyYUVImage> Make(sk_sp<SkData> data);
+
+    sk_sp<SkImage> refImage(GrContext* context);
+
+    const SkImage* getImage(GrContext* context);
+
+private:
+    // Decoded YUV data
+    SkYUVASizeInfo fSizeInfo;
+    SkYUVColorSpace fColorSpace;
+    SkYUVAIndex fComponents[SkYUVAIndex::kIndexCount];
+    SkAutoMalloc fPlaneData;
+    SkPixmap fPlanes[SkYUVASizeInfo::kMaxCount];
+
+    // Memoized SkImage formed with planes
+    sk_sp<SkImage> fYUVImage;
+    uint32_t fOwningContextID;
+
+    LazyYUVImage() : fOwningContextID(SK_InvalidGenID) {}
+
+    bool reset(sk_sp<SkData> data);
+
+    bool ensureYUVImage(GrContext* context);
+};
+
+} // namespace sk_gpu_test
+
+#endif // YUVUtils_DEFINED
diff --git a/tools/gpu/atlastext/GLTestAtlasTextRenderer.cpp b/tools/gpu/atlastext/GLTestAtlasTextRenderer.cpp
index 677ce5a..0bc3ae8 100644
--- a/tools/gpu/atlastext/GLTestAtlasTextRenderer.cpp
+++ b/tools/gpu/atlastext/GLTestAtlasTextRenderer.cpp
@@ -82,6 +82,7 @@
     auto version = GrGLGetVersionFromString(versionStr);
     auto standard = GrGLGetStandardInUseFromString(versionStr);
     switch (standard) {
+        case kWebGL_GrGLStandard:
         case kNone_GrGLStandard:
             return;
         case kGLES_GrGLStandard:
diff --git a/tools/gpu/gl/GLTestContext.cpp b/tools/gpu/gl/GLTestContext.cpp
index e11f1b0..2e12b47 100644
--- a/tools/gpu/gl/GLTestContext.cpp
+++ b/tools/gpu/gl/GLTestContext.cpp
@@ -314,9 +314,9 @@
     }
 }
 
-GrGLint GLTestContext::createTextureRectangle(int width, int height, GrGLenum internalFormat,
-                                          GrGLenum externalFormat, GrGLenum externalType,
-                                          GrGLvoid* data) {
+GrGLuint GLTestContext::createTextureRectangle(int width, int height, GrGLenum internalFormat,
+                                               GrGLenum externalFormat, GrGLenum externalType,
+                                               GrGLvoid* data) {
     // Should match GrGLCaps check for fRectangleTextureSupport.
     if (kGL_GrGLStandard != fGL->fStandard ||
         (GrGLGetVersion(fGL.get()) < GR_GL_VER(3, 1) &&
diff --git a/tools/gpu/gl/GLTestContext.h b/tools/gpu/gl/GLTestContext.h
index d376ed6..c431664 100644
--- a/tools/gpu/gl/GLTestContext.h
+++ b/tools/gpu/gl/GLTestContext.h
@@ -32,9 +32,8 @@
     virtual void destroyEGLImage(GrEGLImage) const { }
 
     /** Used for testing GL_TEXTURE_RECTANGLE integration. */
-    GrGLint createTextureRectangle(int width, int height, GrGLenum internalFormat,
-                                   GrGLenum externalFormat, GrGLenum externalType,
-                                   GrGLvoid *data);
+    GrGLuint createTextureRectangle(int width, int height, GrGLenum internalFormat,
+                                    GrGLenum externalFormat, GrGLenum externalType, GrGLvoid* data);
 
     /**
      * Used for testing EGLImage integration. Takes a EGLImage and wraps it in a
diff --git a/tools/gpu/gl/interface/Makefile b/tools/gpu/gl/interface/Makefile
new file mode 100644
index 0000000..b7f5ced
--- /dev/null
+++ b/tools/gpu/gl/interface/Makefile
@@ -0,0 +1,5 @@
+generate:
+	go run *.go --in_table "./interface.json5" --out_dir "../../../../src/gpu/gl"
+
+dryrun:
+	go run *.go --in_table "./interface.json5" --out_dir "../../../../src/gpu/gl" --dryrun
\ No newline at end of file
diff --git a/tools/gpu/gl/interface/README.md b/tools/gpu/gl/interface/README.md
new file mode 100644
index 0000000..f4e1788
--- /dev/null
+++ b/tools/gpu/gl/interface/README.md
@@ -0,0 +1,22 @@
+GrGlInterface Autogeneration
+============================
+
+Background
+----------
+
+At a high level, the first three steps of making a GrGLInterface (a generic way to
+interact with a GL-like GPU) are:
+
+  - Assemble: Copy a set of function pointers into the struct
+  - Validate: Make sure the function pointers advertised actually exist.
+  - Capabilities: Compute what fast/slow paths are enabled based on the functions
+        in the struct (GrGLCaps, for short)
+
+Autogeneration
+--------------
+
+The first two steps have been automated with a table-based generation script located
+in this folder. The table is in JSON5 format (like JSON, but with comments). O
+
+Once edited, the Assemble/Validate code can be re-generated by running
+`make generate` in this folder.
\ No newline at end of file
diff --git a/tools/gpu/gl/interface/gen_interface.go b/tools/gpu/gl/interface/gen_interface.go
new file mode 100644
index 0000000..17b8e02
--- /dev/null
+++ b/tools/gpu/gl/interface/gen_interface.go
@@ -0,0 +1,473 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package main
+
+// gen_interface creates the assemble/validate cpp files given the
+// interface.json5 file.
+// See README for more details.
+
+import (
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+
+	"github.com/flynn/json5"
+)
+
+var (
+	outDir  = flag.String("out_dir", "../../src/gpu/gl", "Where to output the GrGlAssembleInterface_* and GrGlInterface.cpp files")
+	inTable = flag.String("in_table", "./interface.json5", "The JSON5 table to read in")
+	dryRun  = flag.Bool("dryrun", false, "Print the outputs, don't write to file")
+)
+
+const (
+	CORE_FEATURE        = "<core>"
+	SPACER              = "    "
+	GLES_FILE_NAME      = "GrGLAssembleGLESInterfaceAutogen.cpp"
+	GL_FILE_NAME        = "GrGLAssembleGLInterfaceAutogen.cpp"
+	WEBGL_FILE_NAME     = "GrGLAssembleWebGLInterfaceAutogen.cpp"
+	INTERFACE_FILE_NAME = "GrGLInterfaceAutogen.cpp"
+)
+
+// FeatureSet represents one set of requirements for each of the GL "standards" that
+// Skia supports.  This is currently OpenGL, OpenGL ES and WebGL.
+// OpenGL is typically abbreviated as just "GL".
+// https://www.khronos.org/registry/OpenGL/index_gl.php
+// https://www.khronos.org/opengles/
+// https://www.khronos.org/registry/webgl/specs/1.0/
+type FeatureSet struct {
+	GLReqs    []Requirement `json:"GL"`
+	GLESReqs  []Requirement `json:"GLES"`
+	WebGLReqs []Requirement `json:"WebGL"`
+
+	Functions         []string           `json:"functions"`
+	HardCodeFunctions []HardCodeFunction `json:"hardcode_functions"`
+	OptionalFunctions []string           `json:"optional"` // not checked in validate
+
+	// only assembled/validated when testing
+	TestOnlyFunctions []string `json:"test_functions"`
+
+	Required bool `json:"required"`
+	EGLProc  bool `json:"egl_proc"`
+}
+
+// Requirement lists how we know if a function exists. Extension is the
+// GL extension (or the string CORE_FEATURE if it's part of the core functionality).
+// MinVersion optionally indicates the minimum version of a standard
+// that has the function.
+// SuffixOverride allows the extension suffix to be manually specified instead
+// of automatically derived from the extension name.
+// (for example, if an NV extension specifies some EXT extensions)
+type Requirement struct {
+	Extension      string     `json:"ext"` // required
+	MinVersion     *GLVersion `json:"min_version"`
+	SuffixOverride *string    `json:"suffix"`
+}
+
+// HardCodeFunction indicates to not use the C++ macro and just directly
+// adds a given function ptr to the struct.
+type HardCodeFunction struct {
+	PtrName  string `json:"ptr_name"`
+	CastName string `json:"cast_name"`
+	GetName  string `json:"get_name"`
+}
+
+var CORE_REQUIREMENT = Requirement{Extension: CORE_FEATURE, MinVersion: nil}
+
+type GLVersion [2]int
+
+// RequirementGetter functions allows us to "iterate" over the requirements
+// of the different standards which are stored as keys in FeatureSet and
+// normally not easily iterable.
+type RequirementGetter func(FeatureSet) []Requirement
+
+func glRequirements(fs FeatureSet) []Requirement {
+	return fs.GLReqs
+}
+
+func glesRequirements(fs FeatureSet) []Requirement {
+	return fs.GLESReqs
+}
+
+func webglRequirements(fs FeatureSet) []Requirement {
+	return fs.WebGLReqs
+}
+
+// generateAssembleInterface creates one GrGLAssembleInterface_[type]_gen.cpp
+// for each of the standards
+func generateAssembleInterface(features []FeatureSet) {
+	gl := fillAssembleTemplate(ASSEMBLE_INTERFACE_GL, features, glRequirements)
+	writeToFile(*outDir, GL_FILE_NAME, gl)
+	gles := fillAssembleTemplate(ASSEMBLE_INTERFACE_GL_ES, features, glesRequirements)
+	writeToFile(*outDir, GLES_FILE_NAME, gles)
+	webgl := fillAssembleTemplate(ASSEMBLE_INTERFACE_WEBGL, features, webglRequirements)
+	writeToFile(*outDir, WEBGL_FILE_NAME, webgl)
+}
+
+// fillAssembleTemplate returns a generated file given a template (for a single standard)
+// to fill out and a slice of features with which to fill it.  getReqs is used to select
+// the requirements for the standard we are working on.
+func fillAssembleTemplate(template string, features []FeatureSet, getReqs RequirementGetter) string {
+	content := ""
+	for _, feature := range features {
+		// For each feature set, we are going to create a series of
+		// if statements, which check for the requirements (e.g. extensions, version)
+		// and inside those if branches, write the code to load the
+		// correct function pointer to the interface. GET_PROC and
+		// GET_PROC_SUFFIX are macros defined in C++ part of the template
+		// to accomplish this (for a core feature and extensions, respectively).
+		reqs := getReqs(feature)
+		if len(reqs) == 0 {
+			continue
+		}
+		isEGL := feature.EGLProc
+		// blocks holds all the if blocks generated - it will be joined with else
+		// after and appended to content
+		blocks := []string{}
+		for i, req := range reqs {
+			block := ""
+			ifExpr := requirementIfExpression(req, true)
+
+			if ifExpr != "" {
+				if strings.HasPrefix(ifExpr, "(") {
+					ifExpr = "if " + ifExpr + " {"
+				} else {
+					ifExpr = "if (" + ifExpr + ") {"
+				}
+				// Indent the first if statement
+				if i == 0 {
+					block = addLine(block, ifExpr)
+				} else {
+					block += ifExpr + "\n"
+				}
+			}
+			// sort for determinism
+			sort.Strings(feature.Functions)
+			for _, function := range feature.Functions {
+				block = assembleFunction(block, ifExpr, function, isEGL, req)
+			}
+			sort.Strings(feature.TestOnlyFunctions)
+			if len(feature.TestOnlyFunctions) > 0 {
+				block += "#if GR_TEST_UTILS\n"
+				for _, function := range feature.TestOnlyFunctions {
+					block = assembleFunction(block, ifExpr, function, isEGL, req)
+				}
+				block += "#endif\n"
+			}
+
+			// a hard code function does not use the C++ macro
+			for _, hcf := range feature.HardCodeFunctions {
+				if ifExpr != "" {
+					// extra tab for being in an if statement
+					block += SPACER
+				}
+				line := fmt.Sprintf(`functions->%s =(%s*)get(ctx, "%s");`, hcf.PtrName, hcf.CastName, hcf.GetName)
+				block = addLine(block, line)
+			}
+			if ifExpr != "" {
+				block += SPACER + "}"
+			}
+			blocks = append(blocks, block)
+		}
+		content += strings.Join(blocks, " else ")
+
+		if feature.Required && reqs[0] != CORE_REQUIREMENT {
+			content += ` else {
+        SkASSERT(false); // Required feature
+        return nullptr;
+    }`
+		}
+
+		if !strings.HasSuffix(content, "\n") {
+			content += "\n"
+		}
+		// Add an extra space between blocks for easier readability
+		content += "\n"
+
+	}
+
+	return strings.Replace(template, "[[content]]", content, 1)
+}
+
+// assembleFunction is a little helper that will add a function to the interface
+// using an appropriate macro (e.g. if the function is added)
+// with an extension.
+func assembleFunction(block, ifExpr, function string, isEGL bool, req Requirement) string {
+	if ifExpr != "" {
+		// extra tab for being in an if statement
+		block += SPACER
+	}
+	suffix := deriveSuffix(req.Extension)
+	// Some ARB extensions don't have ARB suffixes because they were written
+	// for backwards compatibility simultaneous to adding them as required
+	// in a new GL version.
+	if suffix == "ARB" {
+		suffix = ""
+	}
+	if req.SuffixOverride != nil {
+		suffix = *req.SuffixOverride
+	}
+	if isEGL {
+		block = addLine(block, fmt.Sprintf("GET_EGL_PROC_SUFFIX(%s, %s);", function, suffix))
+	} else if req.Extension == CORE_FEATURE || suffix == "" {
+		block = addLine(block, fmt.Sprintf("GET_PROC(%s);", function))
+	} else if req.Extension != "" {
+		block = addLine(block, fmt.Sprintf("GET_PROC_SUFFIX(%s, %s);", function, suffix))
+	}
+	return block
+}
+
+// requirementIfExpression returns a string that is an if expression
+// Notably, there is no if expression if the function is a "core" function
+// on all supported versions.
+// The expressions are wrapped in parentheses so they can be safely
+// joined together with && or ||.
+func requirementIfExpression(req Requirement, isLocal bool) string {
+	mv := req.MinVersion
+	if req == CORE_REQUIREMENT {
+		return ""
+	}
+	if req.Extension == CORE_FEATURE && mv != nil {
+		return fmt.Sprintf("(glVer >= GR_GL_VER(%d,%d))", mv[0], mv[1])
+	}
+	extVar := "fExtensions"
+	if isLocal {
+		extVar = "extensions"
+	}
+	// We know it has an extension
+	if req.Extension != "" {
+		if mv == nil {
+			return fmt.Sprintf("%s.has(%q)", extVar, req.Extension)
+		} else {
+			return fmt.Sprintf("(glVer >= GR_GL_VER(%d,%d) && %s.has(%q))", mv[0], mv[1], extVar, req.Extension)
+		}
+	}
+	abort("ERROR: requirement must have ext")
+	return "ERROR"
+}
+
+// driveSuffix returns the suffix of the function associated with the given
+// extension.
+func deriveSuffix(ext string) string {
+	// Some extensions begin with GL_ or EGL_ and then have the actual
+	// extension like KHR, EXT etc.
+	ext = strings.TrimPrefix(ext, "GL_")
+	ext = strings.TrimPrefix(ext, "EGL_")
+	return strings.Split(ext, "_")[0]
+}
+
+// addLine is a little helper function which handles the newline and tab
+func addLine(str, line string) string {
+	return str + SPACER + line + "\n"
+}
+
+func writeToFile(parent, file, content string) {
+	p := filepath.Join(parent, file)
+	if *dryRun {
+		fmt.Printf("Writing to %s\n", p)
+		fmt.Println(content)
+	} else {
+		if err := ioutil.WriteFile(p, []byte(content), 0644); err != nil {
+			abort("Error while writing to file %s: %s", p, err)
+		}
+	}
+}
+
+// validationEntry is a helper struct that contains anything
+// necessary to make validation code for a given standard.
+type validationEntry struct {
+	StandardCheck string
+	GetReqs       RequirementGetter
+}
+
+func generateValidateInterface(features []FeatureSet) {
+	standards := []validationEntry{
+		{
+			StandardCheck: "GR_IS_GR_GL(fStandard)",
+			GetReqs:       glRequirements,
+		}, {
+			StandardCheck: "GR_IS_GR_GL_ES(fStandard)",
+			GetReqs:       glesRequirements,
+		}, {
+			StandardCheck: "GR_IS_GR_WEBGL(fStandard)",
+			GetReqs:       webglRequirements,
+		},
+	}
+	content := ""
+	// For each feature, we are going to generate a series of
+	// boolean expressions which check that the functions we thought
+	// were gathered during the assemble phase actually were applied to
+	// the interface (functionCheck). This check will be guarded
+	// another set of if statements (one per standard) based
+	// on the same requirements (standardChecks) that were used when
+	// assembling the interface.
+	for _, feature := range features {
+		if allReqsAreCore(feature) {
+			content += functionCheck(feature, 1)
+		} else {
+			content += SPACER
+			standardChecks := []string{}
+			for _, std := range standards {
+				reqs := std.GetReqs(feature)
+				if reqs == nil || len(reqs) == 0 {
+					continue
+				}
+				expr := []string{}
+				for _, r := range reqs {
+					e := requirementIfExpression(r, false)
+					if e != "" {
+						expr = append(expr, e)
+					}
+				}
+				check := ""
+				if len(expr) == 0 {
+					check = fmt.Sprintf("%s", std.StandardCheck)
+				} else {
+					lineBreak := "\n" + SPACER + "      "
+					check = fmt.Sprintf("(%s && (%s%s))", std.StandardCheck, lineBreak, strings.Join(expr, " ||"+lineBreak))
+				}
+				standardChecks = append(standardChecks, check)
+			}
+			content += fmt.Sprintf("if (%s) {\n", strings.Join(standardChecks, " ||\n"+SPACER+"   "))
+			content += functionCheck(feature, 2)
+
+			content += SPACER + "}\n"
+		}
+		// add additional line between each block
+		content += "\n"
+	}
+	content = strings.Replace(VALIDATE_INTERFACE, "[[content]]", content, 1)
+	writeToFile(*outDir, INTERFACE_FILE_NAME, content)
+}
+
+// functionCheck returns an if statement that checks that all functions
+// in the passed in slice are on the interface (that is, they are truthy
+// on the fFunctions struct)
+func functionCheck(feature FeatureSet, indentLevel int) string {
+	// sort for determinism
+	sort.Strings(feature.Functions)
+	indent := strings.Repeat(SPACER, indentLevel)
+
+	checks := []string{}
+	for _, function := range feature.Functions {
+		if in(function, feature.OptionalFunctions) {
+			continue
+		}
+		if feature.EGLProc {
+			checks = append(checks, "!fFunctions.fEGL"+function)
+		} else {
+			checks = append(checks, "!fFunctions.f"+function)
+		}
+	}
+	testOnly := []string{}
+	for _, function := range feature.TestOnlyFunctions {
+		if in(function, feature.OptionalFunctions) {
+			continue
+		}
+		if feature.EGLProc {
+			testOnly = append(testOnly, "!fFunctions.fEGL"+function)
+		} else {
+			testOnly = append(testOnly, "!fFunctions.f"+function)
+		}
+	}
+	for _, hcf := range feature.HardCodeFunctions {
+		checks = append(checks, "!fFunctions."+hcf.PtrName)
+	}
+	preCheck := ""
+	if len(testOnly) != 0 {
+		preCheck = fmt.Sprintf(`#if GR_TEST_UTILS
+%sif (%s) {
+%s%sRETURN_FALSE_INTERFACE;
+%s}
+#endif
+`, indent, strings.Join(testOnly, " ||\n"+indent+"    "), indent, SPACER, indent)
+	}
+
+	if len(checks) == 0 {
+		return preCheck + strings.Repeat(SPACER, indentLevel) + "// all functions were marked optional or test_only\n"
+	}
+
+	return preCheck + fmt.Sprintf(`%sif (%s) {
+%s%sRETURN_FALSE_INTERFACE;
+%s}
+`, indent, strings.Join(checks, " ||\n"+indent+"    "), indent, SPACER, indent)
+}
+
+// allReqsAreCore returns true iff the FeatureSet is part of "core" for
+// all standards
+func allReqsAreCore(feature FeatureSet) bool {
+	if feature.GLReqs == nil || feature.GLESReqs == nil {
+		return false
+	}
+	return feature.GLReqs[0] == CORE_REQUIREMENT && feature.GLESReqs[0] == CORE_REQUIREMENT && feature.WebGLReqs[0] == CORE_REQUIREMENT
+}
+
+func validateFeatures(features []FeatureSet) {
+	seen := map[string]bool{}
+	for _, feature := range features {
+		for _, fn := range feature.Functions {
+			if seen[fn] {
+				abort("ERROR: Duplicate function %s", fn)
+			}
+			seen[fn] = true
+		}
+		for _, fn := range feature.TestOnlyFunctions {
+			if seen[fn] {
+				abort("ERROR: Duplicate function %s\n", fn)
+			}
+			seen[fn] = true
+		}
+	}
+}
+
+// in returns true if |s| is *in* |a| slice.
+func in(s string, a []string) bool {
+	for _, x := range a {
+		if x == s {
+			return true
+		}
+	}
+	return false
+}
+
+func abort(fmtStr string, inputs ...interface{}) {
+	fmt.Printf(fmtStr+"\n", inputs...)
+	os.Exit(1)
+}
+
+func main() {
+	flag.Parse()
+	b, err := ioutil.ReadFile(*inTable)
+	if err != nil {
+		abort("Could not read file %s", err)
+	}
+
+	dir, err := os.Open(*outDir)
+	if err != nil {
+		abort("Could not write to output dir %s", err)
+	}
+	defer dir.Close()
+	if fi, err := dir.Stat(); err != nil {
+		abort("Error getting info about %s: %s", *outDir, err)
+	} else if !fi.IsDir() {
+		abort("%s must be a directory", *outDir)
+	}
+
+	features := []FeatureSet{}
+
+	err = json5.Unmarshal(b, &features)
+	if err != nil {
+		abort("Invalid JSON: %s", err)
+	}
+
+	validateFeatures(features)
+
+	generateAssembleInterface(features)
+	generateValidateInterface(features)
+}
diff --git a/tools/gpu/gl/interface/interface.json5 b/tools/gpu/gl/interface/interface.json5
new file mode 100644
index 0000000..731712e
--- /dev/null
+++ b/tools/gpu/gl/interface/interface.json5
@@ -0,0 +1,678 @@
+// This file specifies which functions should be attached to GrGLInterface
+// for a given standard (OpenGL, OpenGL ES, etc). It allows specifing
+// how and when to attach them (e.g. only if an extension is present).
+// It is used for both the assemble and validate step.
+//
+// Currently it assumes the minimum versions:
+//   - GL: 2.0
+//   - GLES: 2.0
+//   - WebGL: [WIP] 1.0
+//
+// http://web.eecs.umich.edu/~sugih/courses/eecs487/common/notes/APITables.xml
+// is a handy reference comparing GL and GLES API
+[
+  {
+    "GL":    [{"ext": "<core>"}],
+    "GLES":  [{"ext": "<core>"}],
+    "WebGL": [{"ext": "<core>"}],
+
+    "functions": [
+      "ActiveTexture", "AttachShader", "BindAttribLocation", "BindBuffer",
+      "BindTexture", "BlendColor", "BlendEquation", "BlendFunc",
+      "BufferData", "BufferSubData", "Clear", "ClearColor",
+      "ClearStencil", "ColorMask", "CompileShader", "CompressedTexImage2D",
+      "CompressedTexSubImage2D", "CopyTexSubImage2D", "CreateProgram", "CreateShader",
+      "CullFace", "DeleteBuffers", "DeleteProgram",
+      "DeleteShader", "DeleteTextures", "DepthMask", "Disable",
+      "DisableVertexAttribArray", "DrawArrays", "DrawElements", "Enable",
+      "EnableVertexAttribArray", "Finish", "Flush",
+      "FrontFace", "GenBuffers",
+      "GenTextures", "GetBufferParameteriv", "GetError",
+      "GetIntegerv", "GetProgramInfoLog",
+      "GetProgramiv", "GetShaderInfoLog",
+      "GetShaderiv", "GetString",
+      "GetUniformLocation", "IsTexture", "LineWidth", "LinkProgram", "PixelStorei",
+      "ReadPixels", "Scissor", "ShaderSource", "StencilFunc",
+      "StencilFuncSeparate", "StencilMask", "StencilMaskSeparate", "StencilOp",
+      "StencilOpSeparate", "TexImage2D", "TexParameterf", "TexParameterfv", "TexParameteri",
+      "TexParameteriv", "TexSubImage2D", "Uniform1f", "Uniform1fv", "Uniform1i", "Uniform1iv",
+      "Uniform2f", "Uniform2fv", "Uniform2i", "Uniform2iv", "Uniform3f", "Uniform3fv", "Uniform3i",
+      "Uniform3iv", "Uniform4f", "Uniform4fv", "Uniform4i", "Uniform4iv", "UniformMatrix2fv",
+      "UniformMatrix3fv", "UniformMatrix4fv", "UseProgram", "VertexAttrib1f",
+      "VertexAttrib2fv", "VertexAttrib3fv", "VertexAttrib4fv", "VertexAttribPointer",
+      "Viewport",
+    ],
+  },
+  { // GL exclusive core functions
+    "GL":    [{"ext": "<core>"}],
+    "GLES":  null,
+
+    "functions": [
+      "DrawBuffer", "PolygonMode",
+    ],
+  },
+  {
+    "GL":    [{"min_version": [3, 0], "ext": "<core>"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "<core>"}],
+    "WebGL": [{"min_version": [2, 0], "ext": "<core>"}],
+
+    "functions": [
+      "GetStringi",
+    ]
+  },
+
+  {
+    "GL":    [{"ext": "<core>"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_OES_vertex_array_object"}],
+    "WebGL": [{"min_version": [2, 0], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_OES_vertex_array_object"},
+              {/*    else if      */  "ext": "OES_vertex_array_object"}],
+
+    // WebGL uses createVertexArray instead of genVertexArrays, but Emscripten
+    // creates an alias called genVertexArray which papers over this difference.
+    "functions": [
+      "BindVertexArray", "DeleteVertexArrays", "GenVertexArrays",
+    ],
+  },
+
+  {
+    "GL":    [{"min_version": [3, 0], "ext": "<core>"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "GL_EXT_blend_func_extended"}],
+    "WebGL": null,
+
+    "functions": [
+      "BindFragDataLocation",
+    ],
+  },
+  {
+    "GL":    [{"min_version": [3, 3], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_blend_func_extended"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "GL_EXT_blend_func_extended"}],
+    "WebGL": null,
+
+    "functions": [
+      "BindFragDataLocationIndexed",
+    ],
+  },
+
+  {
+    "GL":    [{"ext": "GL_KHR_blend_equation_advanced"},
+              {"ext": "GL_NV_blend_equation_advanced"}],
+    "GLES":  [{"ext": "GL_KHR_blend_equation_advanced"},
+              {"ext": "GL_NV_blend_equation_advanced"}],
+    "WebGL": null,
+
+    "functions": [
+      "BlendBarrier",
+    ],
+  },
+
+  {
+    "GL":    [{"min_version": [4, 4], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_clear_texture"}],
+    "GLES":  [{"ext": "GL_EXT_clear_texture", "suffix": "EXT"}],
+    "WebGL": null,
+
+    "functions": [
+      "ClearTexImage", "ClearTexSubImage",
+    ],
+    // https://bugs.chromium.org/p/skia/issues/detail?id=8913
+    "optional": [
+      "ClearTexImage", "ClearTexSubImage",
+    ]
+  },
+
+  {
+    "GL":    [{"min_version": [3, 1], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_draw_instanced"},
+              {/*    else if      */  "ext": "GL_EXT_draw_instanced"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_EXT_draw_instanced"}],
+    "WebGL": [{"min_version": [2, 0], "ext": "<core>"}],
+
+    "functions": [
+      "DrawArraysInstanced", "DrawElementsInstanced",
+    ]
+  },
+  { // ES 3.0 has glDrawBuffers but not glDrawBuffer
+    "GL":    [{"ext": "<core>"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "<core>"}],
+    "WebGL": [{"min_version": [2, 0], "ext": "<core>"}],
+
+    "functions": [
+      "DrawBuffers", "ReadBuffer",
+    ]
+  },
+
+  {
+    "GL":    [{"min_version": [4, 0], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_draw_indirect"}],
+    "GLES":  [{"min_version": [3, 1], "ext": "<core>"}],
+    "WebGL": null,
+
+    "functions": [
+      "DrawArraysIndirect", "DrawElementsIndirect",
+    ]
+  },
+
+  { // glDrawRangeElements was added to ES in 3.0.
+    "GL":    [{"ext": "<core>"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "<core>"}],
+    "WebGL": [{"min_version": [2, 0], "ext": "<core>"}],
+
+    "functions": [
+      "DrawRangeElements",
+    ]
+  },
+
+  {
+    "GL":    [{"min_version": [3, 2], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_texture_multisample"}],
+    "GLES":  [{"min_version": [3, 1], "ext": "<core>"}],
+    "WebGL": null,
+
+    "functions": [
+      "GetMultisamplefv",
+    ]
+  },
+
+  // glGetTexLevelParameteriv was added to ES in 3.1.
+  {
+    "GL":    [{"ext": "<core>"}],
+    "GLES":  [{"min_version": [3, 1], "ext": "<core>"}],
+    "WebGL": null,
+
+    "functions": [
+      "GetTexLevelParameteriv",
+    ]
+  },
+
+  {
+    "GL":    [{"min_version": [4, 3], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_multi_draw_indirect"}],
+    "GLES":  [{"ext": "GL_EXT_multi_draw_indirect"}],
+    "WebGL": null,
+
+    "functions": [
+      "MultiDrawArraysIndirect", "MultiDrawElementsIndirect",
+    ]
+  },
+
+  {
+    "GL":    [{"min_version": [3, 1], "ext": "<core>"}],
+    "GLES":  [{"min_version": [3, 2], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_OES_texture_buffer"},
+              {/*    else if      */  "ext": "GL_EXT_texture_buffer"}],
+    "WebGL": null,
+
+    "functions": [
+      "TexBuffer",
+    ]
+  },
+  {
+    "GL":    [{"min_version": [4, 3], "ext": "<core>"}],
+    "GLES":  [{"min_version": [3, 2], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_OES_texture_buffer"},
+              {/*    else if      */  "ext": "GL_EXT_texture_buffer"}],
+    "WebGL": null,
+
+    "functions": [
+      "TexBufferRange",
+    ]
+  },
+
+    // GL_EXT_texture_storage is part of desktop 4.2
+    // There is a desktop ARB extension and an ES+desktop EXT extension
+  {
+    "GL":    [{"min_version": [4, 2], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_texture_storage"},
+              {/*    else if      */  "ext": "GL_EXT_texture_storage"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_EXT_texture_storage"}],
+    "WebGL": [{"min_version": [2, 0], "ext": "<core>"}],
+
+    "functions": [
+      "TexStorage2D",
+    ]
+  },
+
+  // glTextureBarrier is part of desktop 4.5. There are also ARB and NV extensions.
+  {
+    "GL":    [{"min_version": [4, 5], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_texture_barrier"},
+              {/*    else if      */  "ext": "GL_NV_texture_barrier"}],
+    "GLES":  [{"ext": "GL_NV_texture_barrier"}],
+    "WebGL": null,
+
+    "functions": [
+      "TextureBarrier",
+    ]
+  },
+
+  {
+    "GL":    null, // Not supported
+    "GLES":  [{"ext": "GL_EXT_discard_framebuffer"}],
+    "WebGL": null,
+
+    "functions": [
+      "DiscardFramebuffer",
+    ]
+  },
+
+  {
+    "GL":    [{"min_version": [3, 2], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_instanced_arrays"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_EXT_instanced_arrays"}],
+    "WebGL": null,
+
+    "functions": [
+      "VertexAttribDivisor",
+    ]
+  },
+  {
+    "GL":    [{"min_version": [3, 0], "ext": "<core>"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "<core>"}],
+    "WebGL": [{"min_version": [2, 0], "ext": "<core>"}],
+
+    "functions": [
+      "VertexAttribIPointer",
+    ]
+  },
+
+  // FrameBuffer Object (FBO) related calls
+  {
+    "GL":    [{"min_version": [3, 0], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_framebuffer_object"},
+              {/*    else if      */  "ext": "GL_EXT_framebuffer_object"}],
+    "GLES":  [{"ext": "<core>"}], // These are all in ES 2.0 and above
+    "WebGL": [{"ext": "<core>"}],
+
+    "functions": [
+      "BindFramebuffer", "BindRenderbuffer", "CheckFramebufferStatus",
+      "DeleteFramebuffers", "DeleteRenderbuffers", "FramebufferRenderbuffer",
+      "FramebufferTexture2D", "GenFramebuffers", "GenRenderbuffers", "GenerateMipmap",
+      "GetFramebufferAttachmentParameteriv", "GetRenderbufferParameteriv",
+      "RenderbufferStorage",
+    ],
+  },
+  {
+    "GL":    [{"min_version": [3, 0], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_framebuffer_object"},
+              {/*    else if      */  "ext": "GL_EXT_framebuffer_blit"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_CHROMIUM_framebuffer_multisample"},
+              {/*    else if      */  "ext": "GL_ANGLE_framebuffer_blit"}],
+    // WebGL 2.0 might have support for blitFramebuffer and related functions.
+
+    "functions": [
+      "BlitFramebuffer",
+    ],
+  },
+  {
+    "GL":    [{"min_version": [3, 0], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_framebuffer_object"},
+              {/*    else if      */  "ext": "GL_EXT_framebuffer_multisample"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_CHROMIUM_framebuffer_multisample"},
+              {/*    else if      */  "ext": "GL_ANGLE_framebuffer_multisample"}],
+    "WebGL": [{"min_version": [2, 0], "ext": "<core>"}],
+
+    "functions": [
+      "RenderbufferStorageMultisample",
+    ],
+  },
+
+  {
+    "GL":    null,
+    "GLES":  [{"ext": "GL_CHROMIUM_map_sub"}],
+    "WebGL": null,
+
+    "functions": [
+      "MapBufferSubData", "MapTexSubImage2D", "UnmapBufferSubData",
+      "UnmapTexSubImage2D"
+    ],
+  },
+
+  {
+    "GL":    null,
+    "GLES":  [{"ext": "GL_EXT_multisampled_render_to_texture"},
+              {"ext": "GL_IMG_multisampled_render_to_texture"}],
+    "WebGL": null,
+
+    "functions": [
+      "FramebufferTexture2DMultisample",
+    ],
+  },
+  {
+    "GL":    null,
+    "GLES":  [{"ext": "GL_EXT_multisampled_render_to_texture"}],
+    "WebGL": null,
+
+    "hardcode_functions" : [
+      {
+        "ptr_name": "fRenderbufferStorageMultisampleES2EXT",
+        "cast_name": "GrGLRenderbufferStorageMultisampleFn",
+        "get_name": "glRenderbufferStorageMultisampleEXT",
+      }
+    ]
+  },
+  {
+    "GL":    null,
+    "GLES":  [{"ext": "GL_IMG_multisampled_render_to_texture"}],
+    "WebGL": null,
+
+    "hardcode_functions" : [
+      {
+        "ptr_name": "fRenderbufferStorageMultisampleES2EXT",
+        "cast_name": "GrGLRenderbufferStorageMultisampleFn",
+        "get_name": "glRenderbufferStorageMultisampleIMG",
+      }
+    ]
+  },
+  {
+    "GL":    null,
+    "GLES":  [{"ext": "GL_APPLE_framebuffer_multisample"}],
+    "WebGL": null,
+
+    "functions" : ["ResolveMultisampleFramebuffer"],
+    "hardcode_functions" : [
+      {
+        "ptr_name": "fRenderbufferStorageMultisampleES2APPLE",
+        "cast_name": "GrGLRenderbufferStorageMultisampleFn",
+        "get_name": "glRenderbufferStorageMultisampleAPPLE",
+      }
+    ]
+  },
+
+    // There are several APIs for buffer mapping:
+    // ES2 + GL_OES_mapbuffer: MapBufferOES and UnmapBufferOES
+    // ES2 + GL_EXT_map_buffer_range: Adds MapBufferRangeEXT and FlushMappedBufferRangeEXT
+    // ES3: MapBufferRange, FlushMappedBufferRange, and UnmapBuffer are core (so no suffix).
+    //
+    // MapBuffer is not part of ES3, but implementations may still report the OES versions of
+    // MapBuffer and UnmapBuffer, per the older GL_OES_mapbuffer extension. Some implementations
+    // let us mix the newer MapBufferRange with the older UnmapBufferOES, but we've hit others that
+    // don't permit it. Note that in GrGLBuffer, we choose which API to use based on version and
+    // extensions. This code is written so that we never mix OES and non-OES functions.
+  {
+    "GL":    [{"ext": "<core>"}],
+    "GLES":  [{"ext": "GL_OES_mapbuffer"}],
+    "WebGL": null,
+
+    "functions": [
+      "MapBuffer",
+    ],
+  },
+  {
+    "GL":    [{"ext": "<core>"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_OES_mapbuffer"}],
+    "WebGL": null, // explicitly removed https://www.khronos.org/registry/webgl/specs/2.0/#5.14
+
+    "functions": [
+      "UnmapBuffer",
+    ],
+  },
+  {
+    "GL":    [{"min_version": [3, 0], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_map_buffer_range"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_EXT_map_buffer_range"}],
+    "WebGL": null, // explicitly removed https://www.khronos.org/registry/webgl/specs/2.0/#5.14
+
+    "functions": [
+      // These functions are added to the 3.0 version of both GLES and GL.
+      "MapBufferRange", "FlushMappedBufferRange",
+    ],
+  },
+
+  {
+    "GL":    [{"ext": "GL_EXT_debug_marker"}],
+    "GLES":  [{"ext": "GL_EXT_debug_marker"}],
+    "WebGL": null,
+
+    "functions": [
+      "InsertEventMarker", "PushGroupMarker", "PopGroupMarker"
+    ],
+  },
+
+  {
+    "GL":    [{"min_version": [4, 3], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_program_interface_query"}],
+    "GLES":  [{"min_version": [3, 1], "ext": "<core>"}],
+    "WebGL": null,
+
+    "functions": [
+      "GetProgramResourceLocation",
+    ],
+  },
+
+  {  // It appears that the GL_NV_path_rendering sometimes provides these
+     // functions under the "EXT" extension instead of "NV".
+    "GL":    [{"ext": "GL_NV_path_rendering", "suffix": "EXT"}],
+
+    "GLES":  [{"ext": "GL_CHROMIUM_path_rendering"},
+              {"ext": "GL_NV_path_rendering", "suffix": "EXT"}],
+    "WebGL": null,
+
+    "functions": [
+      "MatrixLoadIdentity", "MatrixLoadf"
+    ],
+  },
+  {
+    "GL":    [{"ext": "GL_NV_path_rendering"}],
+    "GLES":  [{"ext": "GL_CHROMIUM_path_rendering"},
+              {"ext": "GL_NV_path_rendering"}],
+    "WebGL": null,
+
+    "functions": [
+      "CoverFillPath", "CoverFillPathInstanced", "CoverStrokePath",
+      "CoverStrokePathInstanced", "DeletePaths", "GenPaths",
+      "IsPath", "PathCommands", "PathParameterf", "PathParameteri",
+      "PathStencilFunc", "ProgramPathFragmentInputGen", "StencilFillPath",
+      "StencilFillPathInstanced", "StencilStrokePath", "StencilStrokePathInstanced",
+      "StencilThenCoverFillPath", "StencilThenCoverFillPathInstanced",
+      "StencilThenCoverStrokePath", "StencilThenCoverStrokePathInstanced",
+    ],
+    // List of functions that Skia uses, but which have been added since the initial release
+    // of NV_path_rendering driver. We do not want to fail interface validation due to
+    // missing features, we will just not use the extension.
+    // If one updates this list, then update GrGLCaps::hasPathRenderingSupport too.
+    "optional": [
+      "ProgramPathFragmentInputGen", "StencilThenCoverFillPath",
+      "StencilThenCoverFillPathInstanced", "StencilThenCoverStrokePath",
+      "StencilThenCoverStrokePathInstanced",
+    ],
+  },
+  {
+    "GL":    null,
+    "GLES":  [{"ext": "GL_CHROMIUM_path_rendering"}],
+    "WebGL": null,
+
+    "functions": [
+      "BindFragmentInputLocation",
+    ],
+  },
+  {
+    "GL":    [{"ext": "GL_NV_framebuffer_mixed_samples"}],
+    "GLES":  [{"ext": "GL_CHROMIUM_framebuffer_mixed_samples"},
+              {"ext": "GL_NV_framebuffer_mixed_samples"}],
+    "WebGL": null,
+
+    "functions": [
+      "CoverageModulation",
+    ],
+  },
+
+  {
+    "GL":    [{"min_version": [4, 3], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_KHR_debug", "suffix": ""}],
+    "GLES":  [{"ext": "GL_KHR_debug"}],
+    "WebGL": null,
+
+    // In OpenGL (but not ES), KHR_debug defines these methods to have no suffix.
+    "functions": [
+      "DebugMessageControl", "DebugMessageInsert", "DebugMessageCallback",
+      "GetDebugMessageLog", "PushDebugGroup", "PopDebugGroup", "ObjectLabel",
+    ],
+  },
+
+  {
+    "GL":    null,
+    "GLES":  [{"ext": "GL_CHROMIUM_bind_uniform_location"}],
+    "WebGL": null,
+
+    "functions": [
+      "BindUniformLocation",
+    ],
+  },
+
+  {
+    "GL":    [{"ext": "GL_EXT_window_rectangles"}],
+    "GLES":  [{"ext": "GL_EXT_window_rectangles"}],
+    "WebGL": null,
+
+    "functions": [
+      "WindowRectangles",
+    ],
+  },
+
+  {
+    "GL":    [{"ext": "EGL_KHR_image"},
+              {"ext": "EGL_KHR_image_base"}],
+    "GLES":  [{"ext": "EGL_KHR_image"},
+              {"ext": "EGL_KHR_image_base"}],
+    "WebGL": null,
+
+    "functions": [
+      "CreateImage", "DestroyImage",
+    ],
+    "egl_proc": true
+  },
+
+  {
+    "GL":    [{"min_version": [3, 2], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_sync"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_APPLE_sync"}],
+    "WebGL": [{"min_version": [2, 0], "ext": "<core>"}],
+
+    "functions": [
+      "ClientWaitSync", "DeleteSync", "FenceSync",
+      "IsSync", "WaitSync"
+    ],
+  },
+
+  {  // getInternalformativ was added in GL 4.2, ES 3.0, and with
+     // extension ARB_internalformat_query
+    "GL":    [{"min_version": [4, 2], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_internalformat_query"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "<core>"}],
+    "WebGL": null,
+
+    "functions": [
+      "GetInternalformativ"
+    ],
+  },
+
+  {
+    "GL":    [{"min_version": [4, 1], "ext": "<core>"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "<core>"}],
+    "WebGL": null, // explicitly not supported in WebGL 2.0
+
+    "functions": [
+      "GetProgramBinary", "ProgramBinary", "ProgramParameteri",
+    ],
+  },
+
+  {
+    "GL":    [{"min_version": [3, 2], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_sampler_objects"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "<core>"}],
+    "WebGL":  [{"min_version": [2, 0], "ext": "<core>"}],
+
+    "functions": [
+      "BindSampler", "DeleteSamplers", "GenSamplers",
+      "SamplerParameteri", "SamplerParameteriv",
+    ],
+  },
+
+  {
+    "GL":    [{"ext": "<core>"}],
+    "GLES":  null, // not in ES
+    "WebGL": null,
+
+    "functions": [
+      "GetQueryObjectiv",
+    ],
+  },
+  {
+    "GL":    [{"ext": "<core>"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_EXT_occlusion_query_boolean"}],
+    "WebGL": null,
+
+    // We only use these in our test tools
+    "test_functions": [
+      "GenQueries", "DeleteQueries", "BeginQuery", "EndQuery",
+      "GetQueryObjectuiv", "GetQueryiv",
+    ]
+  },
+  {
+    "GL":    [{"min_version": [3, 3], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_timer_query"},
+              {/*    else if      */  "ext": "GL_EXT_timer_query"}],
+    "GLES":  null,
+    "WebGL": null,
+
+    "functions": [
+      "GetQueryObjecti64v", "GetQueryObjectui64v",
+    ],
+  },
+  {
+    "GL":    [{"min_version": [3, 3], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_timer_query"}],
+    "GLES":  null,
+    "WebGL": null,
+
+    "functions": [
+      "QueryCounter",
+    ],
+  },
+
+  {
+    "GL":    [{"min_version": [4, 3], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_invalidate_subdata"}],
+    "GLES":  null,
+    "WebGL": null,
+
+    "functions": [
+      "InvalidateBufferData", "InvalidateBufferSubData", "InvalidateTexImage",
+      "InvalidateTexSubImage",
+    ],
+  },
+  {  // ES 3.0 adds the framebuffer functions but not the others.
+    "GL":    [{"min_version": [4, 3], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_invalidate_subdata"}],
+    "GLES":  [{"min_version": [3, 0], "ext": "<core>"}],
+    "WebGL": [{"min_version": [2, 0], "ext": "<core>"}],
+
+    "functions": [
+      "InvalidateFramebuffer", "InvalidateSubFramebuffer",
+    ],
+  },
+
+  {
+    "GL":    [{"min_version": [4, 3], "ext": "<core>"},
+              {/*    else if      */  "ext": "GL_ARB_ES2_compatibility"}],
+    "GLES":  [{"ext": "<core>"}],
+    "WebGL": [{"ext": "<core>"}],
+
+    "functions": [
+      "GetShaderPrecisionFormat",
+    ],
+  },
+
+]
diff --git a/tools/gpu/gl/interface/templates.go b/tools/gpu/gl/interface/templates.go
new file mode 100644
index 0000000..53c007e
--- /dev/null
+++ b/tools/gpu/gl/interface/templates.go
@@ -0,0 +1,268 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package main
+
+const ASSEMBLE_INTERFACE_GL_ES = `/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * THIS FILE IS AUTOGENERATED
+ * Make edits to tools/gpu/gl/interface/templates.go or they will
+ * be overwritten.
+ */
+
+#include "gl/GrGLAssembleInterface.h"
+#include "gl/GrGLAssembleHelpers.h"
+#include "gl/GrGLUtil.h"
+
+#define GET_PROC(F) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F)
+#define GET_PROC_SUFFIX(F, S) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F #S)
+#define GET_PROC_LOCAL(F) GrGL##F##Fn* F = (GrGL##F##Fn*)get(ctx, "gl" #F)
+
+#define GET_EGL_PROC_SUFFIX(F, S) functions->fEGL##F = (GrEGL##F##Fn*)get(ctx, "egl" #F #S)
+
+#if SK_DISABLE_GL_ES_INTERFACE
+sk_sp<const GrGLInterface> GrGLMakeAssembledGLESInterface(void *ctx, GrGLGetProc get) {
+    return nullptr;
+}
+#else
+sk_sp<const GrGLInterface> GrGLMakeAssembledGLESInterface(void *ctx, GrGLGetProc get) {
+    GET_PROC_LOCAL(GetString);
+    if (nullptr == GetString) {
+        return nullptr;
+    }
+
+    const char* verStr = reinterpret_cast<const char*>(GetString(GR_GL_VERSION));
+    GrGLVersion glVer = GrGLGetVersionFromString(verStr);
+
+    if (glVer < GR_GL_VER(2,0)) {
+        return nullptr;
+    }
+
+    GET_PROC_LOCAL(GetIntegerv);
+    GET_PROC_LOCAL(GetStringi);
+    GrEGLQueryStringFn* queryString;
+    GrEGLDisplay display;
+    GrGetEGLQueryAndDisplay(&queryString, &display, ctx, get);
+    GrGLExtensions extensions;
+    if (!extensions.init(kGLES_GrGLStandard, GetString, GetStringi, GetIntegerv, queryString,
+                         display)) {
+        return nullptr;
+    }
+
+    sk_sp<GrGLInterface> interface(new GrGLInterface);
+    GrGLInterface::Functions* functions = &interface->fFunctions;
+
+    // Autogenerated content follows
+[[content]]
+    // End autogenerated content
+    // TODO(kjlubick): Do we want a feature that removes the extension if it doesn't have
+    // the function? This is common on some low-end GPUs.
+
+    if (extensions.has("GL_KHR_debug")) {
+        // In general we have a policy against removing extension strings when the driver does
+        // not provide function pointers for an advertised extension. However, because there is a
+        // known device that advertises GL_KHR_debug but fails to provide the functions and this is
+        // a debugging- only extension we've made an exception. This also can happen when using
+        // APITRACE.
+        if (!interface->fFunctions.fDebugMessageControl) {
+            extensions.remove("GL_KHR_debug");
+        }
+    }
+    interface->fStandard = kGLES_GrGLStandard;
+    interface->fExtensions.swap(&extensions);
+
+    return std::move(interface);
+}
+#endif
+`
+
+const ASSEMBLE_INTERFACE_GL = `/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * THIS FILE IS AUTOGENERATED
+ * Make edits to tools/gpu/gl/interface/templates.go or they will
+ * be overwritten.
+ */
+
+#include "gl/GrGLAssembleInterface.h"
+#include "gl/GrGLAssembleHelpers.h"
+#include "gl/GrGLUtil.h"
+
+#define GET_PROC(F) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F)
+#define GET_PROC_SUFFIX(F, S) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F #S)
+#define GET_PROC_LOCAL(F) GrGL##F##Fn* F = (GrGL##F##Fn*)get(ctx, "gl" #F)
+
+#define GET_EGL_PROC_SUFFIX(F, S) functions->fEGL##F = (GrEGL##F##Fn*)get(ctx, "egl" #F #S)
+
+#if SK_DISABLE_GL_INTERFACE
+sk_sp<const GrGLInterface> GrGLMakeAssembledGLInterface(void *ctx, GrGLGetProc get) {
+    return nullptr;
+}
+#else
+sk_sp<const GrGLInterface> GrGLMakeAssembledGLInterface(void *ctx, GrGLGetProc get) {
+    GET_PROC_LOCAL(GetString);
+    GET_PROC_LOCAL(GetStringi);
+    GET_PROC_LOCAL(GetIntegerv);
+
+    // GetStringi may be nullptr depending on the GL version.
+    if (nullptr == GetString || nullptr == GetIntegerv) {
+        return nullptr;
+    }
+
+    const char* versionString = (const char*) GetString(GR_GL_VERSION);
+    GrGLVersion glVer = GrGLGetVersionFromString(versionString);
+
+    if (glVer < GR_GL_VER(2,0) || GR_GL_INVALID_VER == glVer) {
+        // This is our minimum for non-ES GL.
+        return nullptr;
+    }
+
+    GrEGLQueryStringFn* queryString;
+    GrEGLDisplay display;
+    GrGetEGLQueryAndDisplay(&queryString, &display, ctx, get);
+    GrGLExtensions extensions;
+    if (!extensions.init(kGL_GrGLStandard, GetString, GetStringi, GetIntegerv, queryString,
+                         display)) {
+        return nullptr;
+    }
+
+    sk_sp<GrGLInterface> interface(new GrGLInterface());
+    GrGLInterface::Functions* functions = &interface->fFunctions;
+
+    // Autogenerated content follows
+[[content]]
+    // End autogenerated content
+    interface->fStandard = kGL_GrGLStandard;
+    interface->fExtensions.swap(&extensions);
+
+    return std::move(interface);
+}
+#endif
+`
+
+const ASSEMBLE_INTERFACE_WEBGL = `/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * THIS FILE IS AUTOGENERATED
+ * Make edits to tools/gpu/gl/interface/templates.go or they will
+ * be overwritten.
+ */
+
+#include "gl/GrGLAssembleInterface.h"
+#include "gl/GrGLAssembleHelpers.h"
+#include "gl/GrGLUtil.h"
+
+#define GET_PROC(F) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F)
+#define GET_PROC_SUFFIX(F, S) functions->f##F = (GrGL##F##Fn*)get(ctx, "gl" #F #S)
+#define GET_PROC_LOCAL(F) GrGL##F##Fn* F = (GrGL##F##Fn*)get(ctx, "gl" #F)
+
+#define GET_EGL_PROC_SUFFIX(F, S) functions->fEGL##F = (GrEGL##F##Fn*)get(ctx, "egl" #F #S)
+
+#if SK_DISABLE_WEBGL_INTERFACE
+sk_sp<const GrGLInterface> GrGLMakeAssembledWebGLInterface(void *ctx, GrGLGetProc get) {
+    return nullptr;
+}
+#else
+sk_sp<const GrGLInterface> GrGLMakeAssembledWebGLInterface(void *ctx, GrGLGetProc get) {
+    GET_PROC_LOCAL(GetString);
+    if (nullptr == GetString) {
+        return nullptr;
+    }
+
+    const char* verStr = reinterpret_cast<const char*>(GetString(GR_GL_VERSION));
+    GrGLVersion glVer = GrGLGetVersionFromString(verStr);
+
+    if (glVer < GR_GL_VER(1,0)) {
+        return nullptr;
+    }
+
+    GET_PROC_LOCAL(GetIntegerv);
+    GET_PROC_LOCAL(GetStringi);
+    GrEGLQueryStringFn* queryString;
+    GrEGLDisplay display;
+    GrGetEGLQueryAndDisplay(&queryString, &display, ctx, get);
+    GrGLExtensions extensions;
+    if (!extensions.init(kWebGL_GrGLStandard, GetString, GetStringi, GetIntegerv, queryString,
+                         display)) {
+        return nullptr;
+    }
+
+    sk_sp<GrGLInterface> interface(new GrGLInterface);
+    GrGLInterface::Functions* functions = &interface->fFunctions;
+
+    // Autogenerated content follows
+[[content]]
+    // End autogenerated content
+
+    interface->fStandard = kWebGL_GrGLStandard;
+    interface->fExtensions.swap(&extensions);
+
+    return std::move(interface);
+}
+#endif
+`
+
+const VALIDATE_INTERFACE = `/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * THIS FILE IS AUTOGENERATED
+ * Make edits to tools/gpu/gl/interface/templates.go or they will
+ * be overwritten.
+ */
+
+#include "gl/GrGLInterface.h"
+#include "gl/GrGLExtensions.h"
+#include "gl/GrGLUtil.h"
+
+#include <stdio.h>
+
+GrGLInterface::GrGLInterface() {
+    fStandard = kNone_GrGLStandard;
+}
+
+#define RETURN_FALSE_INTERFACE                                                 \
+    SkDEBUGF("%s:%d GrGLInterface::validate() failed.\n", __FILE__, __LINE__); \
+    return false
+
+bool GrGLInterface::validate() const {
+
+    if (kNone_GrGLStandard == fStandard) {
+        RETURN_FALSE_INTERFACE;
+    }
+
+    if (!fExtensions.isInitialized()) {
+        RETURN_FALSE_INTERFACE;
+    }
+
+    GrGLVersion glVer = GrGLGetVersion(this);
+    if (GR_GL_INVALID_VER == glVer) {
+        RETURN_FALSE_INTERFACE;
+    }
+    // Autogenerated content follows
+[[content]]
+    // End autogenerated content
+    return true;
+}
+
+#if GR_TEST_UTILS
+
+void GrGLInterface::abandon() const {
+    const_cast<GrGLInterface*>(this)->fFunctions = GrGLInterface::Functions();
+}
+
+#endif // GR_TEST_UTILS
+`
diff --git a/tools/gpu/gl/null/NullGLTestContext.cpp b/tools/gpu/gl/null/NullGLTestContext.cpp
deleted file mode 100644
index de33a40..0000000
--- a/tools/gpu/gl/null/NullGLTestContext.cpp
+++ /dev/null
@@ -1,47 +0,0 @@
-
-/*
- * Copyright 2011 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "NullGLTestContext.h"
-#include "gl/GrGLTestInterface.h"
-#include "gl/GrGLDefines.h"
-#include "gl/GrGLInterface.h"
-#include "gl/GrGLTypes.h"
-#include "SkMutex.h"
-#include "SkTDArray.h"
-
-namespace {
-class NullGLContext : public sk_gpu_test::GLTestContext {
-public:
-    NullGLContext(bool enableNVPR) {
-        this->init(sk_sp<const GrGLInterface>(GrGLCreateNullInterface(enableNVPR)));
-    }
-    ~NullGLContext() override { this->teardown(); }
-
-private:
-    void onPlatformMakeCurrent() const override {}
-    std::function<void()> onPlatformGetAutoContextRestore() const override { return nullptr; }
-    void onPlatformSwapBuffers() const override {}
-    GrGLFuncPtr onPlatformGetProcAddress(const char*) const override { return nullptr; }
-};
-
-}  // anonymous namespace
-
-namespace sk_gpu_test {
-GLTestContext* CreateNullGLTestContext(bool enableNVPR, GLTestContext* shareContext) {
-    if (shareContext) {
-        return nullptr;
-    }
-    GLTestContext* ctx = new NullGLContext(enableNVPR);
-    if (ctx->isValid()) {
-        return ctx;
-    }
-    delete ctx;
-    return nullptr;
-}
-}  // namespace sk_gpu_test
-
diff --git a/tools/gpu/gl/null/NullGLTestContext.h b/tools/gpu/gl/null/NullGLTestContext.h
deleted file mode 100644
index 9062e88..0000000
--- a/tools/gpu/gl/null/NullGLTestContext.h
+++ /dev/null
@@ -1,17 +0,0 @@
-
-/*
- * Copyright 2011 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-#ifndef NullGLContext_DEFINED
-#define NullGLContext_DEFINED
-
-#include "gl/GLTestContext.h"
-
-namespace sk_gpu_test {
-GLTestContext* CreateNullGLTestContext(bool enableNVPR, GLTestContext* shareContext);
-}  // namespace sk_gpu_test
-
-#endif
diff --git a/tools/gpu/mtl/MtlTestContext.h b/tools/gpu/mtl/MtlTestContext.h
index f703238..a44e27c 100644
--- a/tools/gpu/mtl/MtlTestContext.h
+++ b/tools/gpu/mtl/MtlTestContext.h
@@ -13,10 +13,24 @@
 #ifdef SK_METAL
 
 namespace sk_gpu_test {
-TestContext* CreatePlatformMtlTestContext(TestContext*);
+class MtlTestContext : public TestContext {
+public:
+    GrBackendApi backend() override { return GrBackendApi::kMetal; }
+
+protected:
+    MtlTestContext() {}
+
+private:
+    typedef TestContext INHERITED;
+};
+
+/**
+ * Creates Metal context object bound to the native Metal library.
+ */
+MtlTestContext* CreatePlatformMtlTestContext(MtlTestContext*);
+
 }  // namespace sk_gpu_test
 
 #endif
 
-
 #endif /* MtlTestContext_h */
diff --git a/tools/gpu/mtl/MtlTestContext.mm b/tools/gpu/mtl/MtlTestContext.mm
index 18be792..69aa91e 100644
--- a/tools/gpu/mtl/MtlTestContext.mm
+++ b/tools/gpu/mtl/MtlTestContext.mm
@@ -10,9 +10,13 @@
 #include "GrContext.h"
 #include "GrContextOptions.h"
 
+#ifdef SK_METAL
+
 #import <Metal/Metal.h>
 
-#ifdef SK_METAL
+// Helper macros for autorelease pools
+#define SK_BEGIN_AUTORELEASE_BLOCK @autoreleasepool {
+#define SK_END_AUTORELEASE_BLOCK }
 
 namespace {
 /**
@@ -108,19 +112,26 @@
 GR_STATIC_ASSERT(sizeof(VkFence) <= sizeof(sk_gpu_test::PlatformFence));
 #endif
 
-class MtlTestContext : public sk_gpu_test::TestContext {
+class MtlTestContextImpl : public sk_gpu_test::MtlTestContext {
 public:
-    static MtlTestContext* Create(TestContext* sharedContext) {
-        SkASSERT(!sharedContext);
-        id<MTLDevice> device = MTLCreateSystemDefaultDevice();
-        id<MTLCommandQueue> queue = [device newCommandQueue];
+    static MtlTestContext* Create(MtlTestContext* sharedContext) {
+        id<MTLDevice> device;
+        id<MTLCommandQueue> queue;
+        if (sharedContext) {
+            MtlTestContextImpl* sharedContextImpl = (MtlTestContextImpl*) sharedContext;
+            device = sharedContextImpl->device();
+            queue = sharedContextImpl->queue();
+        } else {
+            SK_BEGIN_AUTORELEASE_BLOCK
+            device = MTLCreateSystemDefaultDevice();
+            queue = [device newCommandQueue];
+            SK_END_AUTORELEASE_BLOCK
+        }
 
-        return new MtlTestContext(device, queue);
+        return new MtlTestContextImpl(device, queue);
     }
 
-    ~MtlTestContext() override { this->teardown(); }
-
-    GrBackendApi backend() override { return GrBackendApi::kMetal; }
+    ~MtlTestContextImpl() override { this->teardown(); }
 
     void testAbandon() override {}
 
@@ -135,9 +146,12 @@
                                     options);
     }
 
+    id<MTLDevice> device() { return fDevice; }
+    id<MTLCommandQueue> queue() { return fQueue; }
+
 private:
-    MtlTestContext(id<MTLDevice> device, id<MTLCommandQueue> queue)
-            : fDevice(device), fQueue(queue) {
+    MtlTestContextImpl(id<MTLDevice> device, id<MTLCommandQueue> queue)
+            : INHERITED(), fDevice(device), fQueue(queue) {
         fFenceSync.reset(nullptr);
     }
 
@@ -145,19 +159,20 @@
     std::function<void()> onPlatformGetAutoContextRestore() const override { return nullptr; }
     void onPlatformSwapBuffers() const override {}
 
-    id<MTLDevice> fDevice;
+    id<MTLDevice>        fDevice;
     id<MTLCommandQueue>  fQueue;
 
-    typedef sk_gpu_test::TestContext INHERITED;
+    typedef sk_gpu_test::MtlTestContext INHERITED;
 };
 
 }  // anonymous namespace
 
 namespace sk_gpu_test {
 
-TestContext* CreatePlatformMtlTestContext(TestContext* sharedContext) {
-    return MtlTestContext::Create(sharedContext);
+MtlTestContext* CreatePlatformMtlTestContext(MtlTestContext* sharedContext) {
+    return MtlTestContextImpl::Create(sharedContext);
 }
+
 }  // namespace sk_gpu_test
 
 
diff --git a/tools/gpu/vk/VkTestUtils.cpp b/tools/gpu/vk/VkTestUtils.cpp
index ed6549c..b7148fa 100644
--- a/tools/gpu/vk/VkTestUtils.cpp
+++ b/tools/gpu/vk/VkTestUtils.cpp
@@ -14,6 +14,10 @@
 #include "vk/GrVkExtensions.h"
 #include "../ports/SkOSLibrary.h"
 
+#if defined(SK_ENABLE_SCOPED_LSAN_SUPPRESSIONS)
+#include <sanitizer/lsan_interface.h>
+#endif
+
 namespace sk_gpu_test {
 
 bool LoadVkLibraryAndGetProcAddrFuncs(PFN_vkGetInstanceProcAddr* instProc,
@@ -661,7 +665,13 @@
         pointerToFeatures ? nullptr : deviceFeatures // ppEnabledFeatures
     };
 
-    err = grVkCreateDevice(physDev, &deviceInfo, nullptr, &device);
+    {
+#if defined(SK_ENABLE_SCOPED_LSAN_SUPPRESSIONS)
+        // skia:8712
+        __lsan::ScopedDisabler lsanDisabler;
+#endif
+        err = grVkCreateDevice(physDev, &deviceInfo, nullptr, &device);
+    }
     if (err) {
         SkDebugf("CreateDevice failed: %d\n", err);
         destroy_instance(getProc, inst, debugCallback, hasDebugExtension);
diff --git a/tools/hello-opencl.cpp b/tools/hello-opencl.cpp
index 7e57f0b..12b51f5 100644
--- a/tools/hello-opencl.cpp
+++ b/tools/hello-opencl.cpp
@@ -7,112 +7,111 @@
 
 // This is a simple OpenCL Hello World that tests you have a functioning OpenCL setup.
 
-#include <CL/cl.hpp>
+#include "cl.hpp"
 #include <initializer_list>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+#include <vector>
 
-extern "C" {
-    #include "cl/assert_cl.h"   // for cl(), cl_ok() macros
-    #include "cl/find_cl.h"     // for clFindIdsByName
+static inline void assert_cl(cl_int rc, const char* file, int line) {
+    if (rc != CL_SUCCESS) {
+        fprintf(stderr, "%s:%d, got OpenCL error code %d\n", file,line,rc);
+        exit(1);
+    }
 }
+#define cl_ok(err) assert_cl(err, __FILE__, __LINE__)
 
-int main(int argc, char** argv) {
-    // Find any OpenCL platform+device with these substrings.
-    const char* platform_match = argc > 1 ? argv[1] : "";
-    const char* device_match   = argc > 2 ? argv[2] : "";
+int main(int, char**) {
+    std::vector<cl::Platform> platforms;
+    cl_ok(cl::Platform::get(&platforms));
 
-    cl_platform_id platform_id;
-    cl_device_id   device_id;
-
-    char device_name[256];
-    size_t device_name_len;
-
-    // clFindIdsByName will narrate what it's doing when this is set.
-    bool verbose = true;
-
-    // The cl() macro prepends cl to its argument, calls it, and asserts that it succeeded,
-    // printing out the file, line, and somewhat readable version of the error code on failure.
-    //
-    // It's generally used to call OpenCL APIs, but here we've written clFindIdsByName to match
-    // the convention, as its error conditions are just going to be passed along from OpenCL.
-    cl(FindIdsByName(platform_match,  device_match,
-                     &platform_id,    &device_id,
-                     sizeof(device_name), device_name, &device_name_len,
-                     verbose));
-
-    printf("picked %.*s\n", (int)device_name_len, device_name);
-
-    // Allan's code is all C using OpenCL's C API,
-    // but we can mix that freely with the C++ API found in cl.hpp.
-    // cl_ok() comes in handy here, which is cl() without the extra cl- prefix.
-
-    cl::Device device(device_id);
-
-    std::string name,
-                vendor,
-                extensions;
-    cl_ok(device.getInfo(CL_DEVICE_NAME,       &name));
-    cl_ok(device.getInfo(CL_DEVICE_VENDOR,     &vendor));
-    cl_ok(device.getInfo(CL_DEVICE_EXTENSIONS, &extensions));
-
-    printf("name %s, vendor %s, extensions:\n%s\n",
-           name.c_str(), vendor.c_str(), extensions.c_str());
-
-    std::vector<cl::Device> devices = { device };
-
-    // Some APIs can't return their cl_int error but might still fail,
-    // so they take a pointer.  cl_ok() is really handy here too.
-    cl_int ok;
-    cl::Context ctx(devices,
-                    nullptr/*optional cl_context_properties*/,
-                    nullptr/*optional error reporting callback*/,
-                    nullptr/*context arguement for error reporting callback*/,
-                    &ok);
-    cl_ok(ok);
-
-    cl::Program program(ctx,
-                        "__kernel void mul(__global const float* a,    "
-                        "                  __global const float* b,    "
-                        "                  __global       float* dst) {"
-                        "    int i = get_global_id(0);                 "
-                        "    dst[i] = a[i] * b[i];                     "
-                        "}                                             ",
-                        /*and build now*/true,
-                        &ok);
-    cl_ok(ok);
-
-    std::vector<float> a,b,p;
-    for (int i = 0; i < 1000; i++) {
-        a.push_back(+i);
-        b.push_back(-i);
-        p.push_back( 0);
+    std::vector<cl::Device> devices;
+    for (cl::Platform platform : platforms) {
+        std::vector<cl::Device> platform_devices;
+        cl_ok(platform.getDevices(CL_DEVICE_TYPE_ALL, &platform_devices));
+        devices.insert(devices.end(), platform_devices.begin(), platform_devices.end());
     }
 
-    cl::Buffer A(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR , sizeof(float)*a.size(), a.data()),
-               B(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR , sizeof(float)*b.size(), b.data()),
-               P(ctx, CL_MEM_WRITE_ONLY| CL_MEM_HOST_READ_ONLY, sizeof(float)*p.size());
+    if (devices.empty()) {
+        fprintf(stderr, "No OpenCL devices available. :(\n");
+        return 1;
+    }
 
-    cl::Kernel mul(program, "mul", &ok);
-    cl_ok(ok);
-    cl_ok(mul.setArg(0, A));
-    cl_ok(mul.setArg(1, B));
-    cl_ok(mul.setArg(2, P));
+    // To keep things simple we'll only create single-device cl::Contexts.
+    for (cl::Device device : devices) {
+        std::string name,
+                    version,
+                    driver,
+                    vendor,
+                    extensions;
+        cl_ok(device.getInfo(CL_DEVICE_NAME,       &name));
+        cl_ok(device.getInfo(CL_DEVICE_VERSION,    &version));
+        cl_ok(device.getInfo(CL_DEVICE_VENDOR,     &vendor));
+        cl_ok(device.getInfo(CL_DEVICE_EXTENSIONS, &extensions));
+        cl_ok(device.getInfo(CL_DRIVER_VERSION,    &driver));
 
-    cl::CommandQueue queue(ctx, device);
+        fprintf(stdout, "Using %s%s, vendor %s, version %s, extensions:\n%s\n",
+                version.c_str(), name.c_str(), vendor.c_str(), driver.c_str(), extensions.c_str());
 
-    cl_ok(queue.enqueueNDRangeKernel(mul, cl::NDRange(0)  /*offset*/
-                                        , cl::NDRange(1000) /*size*/));
+        std::vector<cl::Device> devices = { device };
 
-    cl_ok(queue.enqueueReadBuffer(P, true/*block until read is done*/
-                                   , 0                     /*offset in bytes*/
-                                   , sizeof(float)*p.size() /*size in bytes*/
-                                   , p.data()));
+        // Some APIs can't return their cl_int error but might still fail,
+        // so they take a pointer.  cl_ok() is really handy here too.
+        cl_int ok;
+        cl::Context ctx(devices,
+                        nullptr/*optional cl_context_properties*/,
+                        nullptr/*optional error reporting callback*/,
+                        nullptr/*context argument for error reporting callback*/,
+                        &ok);
+        cl_ok(ok);
 
-    for (int i = 0; i < 1000; i++) {
-        if (p[i] != a[i]*b[i]) {
-            return 1;
+        cl::Program program(ctx,
+                            "__kernel void mul(__global const float* a,    "
+                            "                  __global const float* b,    "
+                            "                  __global       float* dst) {"
+                            "    int i = get_global_id(0);                 "
+                            "    dst[i] = a[i] * b[i];                     "
+                            "}                                             ",
+                            /*and build now*/true,
+                            &ok);
+        cl_ok(ok);
+
+        std::vector<float> a,b,p;
+        for (int i = 0; i < 1000; i++) {
+            a.push_back(+i);
+            b.push_back(-i);
+            p.push_back( 0);
+        }
+
+        cl::Buffer
+            A(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR , sizeof(float)*a.size(), a.data()),
+            B(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR , sizeof(float)*b.size(), b.data()),
+            P(ctx, CL_MEM_WRITE_ONLY| CL_MEM_HOST_READ_ONLY, sizeof(float)*p.size());
+
+        cl::Kernel mul(program, "mul", &ok);
+        cl_ok(ok);
+        cl_ok(mul.setArg(0, A));
+        cl_ok(mul.setArg(1, B));
+        cl_ok(mul.setArg(2, P));
+
+        cl::CommandQueue queue(ctx, device);
+
+        cl_ok(queue.enqueueNDRangeKernel(mul, cl::NDRange(0)  /*offset*/
+                                            , cl::NDRange(1000) /*size*/));
+
+        cl_ok(queue.enqueueReadBuffer(P, true/*block until read is done*/
+                                       , 0                     /*offset in bytes*/
+                                       , sizeof(float)*p.size() /*size in bytes*/
+                                       , p.data()));
+
+        fprintf(stdout, "OpenCL sez: %g x %g = %g\n", a[42], b[42], p[42]);
+        for (int i = 0; i < 1000; i++) {
+            if (p[i] != a[i]*b[i]) {
+                return 1;
+            }
         }
     }
 
-    printf("OpenCL sez: %g x %g = %g\n", a[42], b[42], p[42]);
     return 0;
 }
diff --git a/tools/lua/lua_pictures.cpp b/tools/lua/lua_pictures.cpp
index 468d85b..1afdde38 100644
--- a/tools/lua/lua_pictures.cpp
+++ b/tools/lua/lua_pictures.cpp
@@ -5,15 +5,15 @@
  * found in the LICENSE file.
  */
 
+#include "CommandLineFlags.h"
+#include "SkData.h"
+#include "SkGraphics.h"
 #include "SkLua.h"
 #include "SkLuaCanvas.h"
-#include "SkPicture.h"
-#include "SkCommandLineFlags.h"
-#include "SkGraphics.h"
-#include "SkStream.h"
-#include "SkData.h"
 #include "SkOSFile.h"
 #include "SkOSPath.h"
+#include "SkPicture.h"
+#include "SkStream.h"
 
 #include <stdlib.h>
 
@@ -63,8 +63,8 @@
 }
 
 int main(int argc, char** argv) {
-    SkCommandLineFlags::SetUsage("apply lua script to .skp files.");
-    SkCommandLineFlags::Parse(argc, argv);
+    CommandLineFlags::SetUsage("apply lua script to .skp files.");
+    CommandLineFlags::Parse(argc, argv);
 
     if (FLAGS_skpPath.isEmpty()) {
         SkDebugf(".skp files or directories are required.\n");
diff --git a/tools/malisc/malisc.py b/tools/malisc/malisc.py
new file mode 100644
index 0000000..000405f
--- /dev/null
+++ b/tools/malisc/malisc.py
@@ -0,0 +1,36 @@
+# Copyright 2019 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.
+
+import json
+import os
+import subprocess
+import sys
+
+if len(sys.argv) != 3:
+    print sys.argv[0], ' <compiler> <folder>'
+    sys.exit(1)
+
+compiler = sys.argv[1]
+folder = sys.argv[2]
+
+FRAG_JSON = os.path.join(folder, 'frag.json')
+
+with open(FRAG_JSON) as f:
+    frags = json.load(f)
+
+if not frags:
+    print 'No JSON data'
+    sys.exit(1)
+
+for fragAndCount in frags:
+    source = os.path.join(folder, fragAndCount[0] + '.frag')
+    try:
+        output = subprocess.check_output([compiler, source])
+    except subprocess.CalledProcessError:
+        continue
+    for line in output.splitlines():
+        if line.startswith('Instructions Emitted'):
+            inst = line.split(':')[1].split()
+            print '{0} {1} {2} {3} {4}'.format(
+                fragAndCount[0], fragAndCount[1], inst[0], inst[1], inst[2])
diff --git a/tools/mdbviz/Model.cpp b/tools/mdbviz/Model.cpp
index c292956..ce85117 100644
--- a/tools/mdbviz/Model.cpp
+++ b/tools/mdbviz/Model.cpp
@@ -9,9 +9,9 @@
 
 #include "Model.h"
 
+#include "DebugCanvas.h"
 #include "SkBitmap.h"
 #include "SkCanvas.h"
-#include "SkDebugCanvas.h"
 #include "SkPicture.h"
 #include "SkStream.h"
 
@@ -35,9 +35,9 @@
     }
 
     {
-        std::unique_ptr<SkDebugCanvas> temp(new SkDebugCanvas(
-                                                    SkScalarCeilToInt(pic->cullRect().width()),
-                                                    SkScalarCeilToInt(pic->cullRect().height())));
+        std::unique_ptr<DebugCanvas> temp(
+                new DebugCanvas(SkScalarCeilToInt(pic->cullRect().width()),
+                                SkScalarCeilToInt(pic->cullRect().height())));
 
         temp->setPicture(pic.get());
         pic->playback(temp.get());
@@ -62,22 +62,20 @@
 }
 
 const char* Model::getOpName(int index) const {
-    return SkDrawCommand::GetCommandString(fOps[index]->getType());
+    return DrawCommand::GetCommandString(fOps[index]->getType());
 }
 
 bool Model::isHierarchyPush(int index) const {
-    SkDrawCommand::OpType type = fOps[index]->getType();
+    DrawCommand::OpType type = fOps[index]->getType();
 
-    return SkDrawCommand::kSave_OpType == type ||
-           SkDrawCommand::kSaveLayer_OpType == type ||
-           SkDrawCommand::kBeginDrawPicture_OpType == type;
+    return DrawCommand::kSave_OpType == type || DrawCommand::kSaveLayer_OpType == type ||
+           DrawCommand::kBeginDrawPicture_OpType == type;
 }
 
 bool Model::isHierarchyPop(int index) const {
-    SkDrawCommand::OpType type = fOps[index]->getType();
+    DrawCommand::OpType type = fOps[index]->getType();
 
-    return SkDrawCommand::kRestore_OpType == type ||
-           SkDrawCommand::kEndDrawPicture_OpType == type;
+    return DrawCommand::kRestore_OpType == type || DrawCommand::kEndDrawPicture_OpType == type;
 }
 
 void Model::setCurOp(int curOp) {
diff --git a/tools/mdbviz/Model.h b/tools/mdbviz/Model.h
index 8448701..cd3563c 100644
--- a/tools/mdbviz/Model.h
+++ b/tools/mdbviz/Model.h
@@ -8,7 +8,7 @@
 #include "SkBitmap.h"
 #include "SkTDArray.h"
 
-class SkDrawCommand;
+class DrawCommand;
 
 // This class encapsulates the both the in-memory representation of the draw ops
 // and the state of Skia/Ganesh's rendering. It should never have any Qt intrusions.
@@ -51,7 +51,7 @@
     void resetOpList();
 
 private:
-    SkTDArray<SkDrawCommand*> fOps;
+    SkTDArray<DrawCommand*>   fOps;
     int                       fCurOp;  // The current op the rendering state is at
     SkBitmap                  fBM;
 };
diff --git a/tools/remote_demo.cpp b/tools/remote_demo.cpp
index 105a9b3..8d80e94 100644
--- a/tools/remote_demo.cpp
+++ b/tools/remote_demo.cpp
@@ -132,12 +132,12 @@
     std::chrono::duration<double>                       fElapsedSeconds{0.0};
 };
 
-static bool push_font_data(const SkPicture& pic, SkStrikeServer* strikeServer, int writeFd) {
-    SkMatrix deviceMatrix = SkMatrix::I();
+static bool push_font_data(const SkPicture& pic, SkStrikeServer* strikeServer,
+                           sk_sp<SkColorSpace> colorSpace, int writeFd) {
     const SkIRect bounds = pic.cullRect().round();
     const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
-    SkTextBlobCacheDiffCanvas filter(bounds.width(), bounds.height(), deviceMatrix, props,
-                                     strikeServer);
+    SkTextBlobCacheDiffCanvas filter(bounds.width(), bounds.height(), props,
+                                     strikeServer, std::move(colorSpace));
     pic.playback(&filter);
 
     std::vector<uint8_t> fontData;
@@ -236,6 +236,7 @@
     sk_sp<SkData> stream;
     if (gUseGpu) {
         auto pic = SkPicture::MakeFromData(skpData.get());
+        auto colorSpace = SkColorSpace::MakeSRGB();
         SkSerialProcs procs;
         auto encode = [](SkTypeface* tf, void* ctx) -> sk_sp<SkData> {
             return reinterpret_cast<SkStrikeServer*>(ctx)->serializeTypeface(tf);
@@ -257,7 +258,7 @@
                 return 0;
             }
             if (gPurgeFontCaches) discardableManager.purgeAll();
-            push_font_data(*pic.get(), &server, writeFd);
+            push_font_data(*pic.get(), &server, colorSpace, writeFd);
         }
     } else {
         stream = skpData;
diff --git a/tools/sk_app/DisplayParams.h b/tools/sk_app/DisplayParams.h
index 54e9135..813603c 100644
--- a/tools/sk_app/DisplayParams.h
+++ b/tools/sk_app/DisplayParams.h
@@ -19,6 +19,7 @@
         , fColorSpace(nullptr)
         , fMSAASampleCount(1)
         , fSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType)
+        , fDisableVsync(false)
     {}
 
     SkColorType         fColorType;
@@ -26,6 +27,7 @@
     int                 fMSAASampleCount;
     GrContextOptions    fGrContextOptions;
     SkSurfaceProps      fSurfaceProps;
+    bool                fDisableVsync;
 };
 
 }   // namespace sk_app
diff --git a/tools/sk_app/MetalWindowContext.h b/tools/sk_app/MetalWindowContext.h
new file mode 100644
index 0000000..fc712d7
--- /dev/null
+++ b/tools/sk_app/MetalWindowContext.h
@@ -0,0 +1,58 @@
+
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef MetalWindowContext_DEFINED
+#define MetalWindowContext_DEFINED
+
+#include "SkRefCnt.h"
+#include "SkSurface.h"
+
+#include "WindowContext.h"
+
+#import <Metal/Metal.h>
+#import <MetalKit/MetalKit.h>
+
+class GrContext;
+
+namespace sk_app {
+
+class MetalWindowContext : public WindowContext {
+public:
+    sk_sp<SkSurface> getBackbufferSurface() override;
+
+    bool isValid() override { return fValid; }
+
+    void resize(int w, int h) override;
+    void swapBuffers() override;
+
+    void setDisplayParams(const DisplayParams& params) override;
+
+protected:
+    MetalWindowContext(const DisplayParams&);
+    // This should be called by subclass constructor. It is also called when window/display
+    // parameters change. This will in turn call onInitializeContext().
+    void initializeContext();
+    virtual bool onInitializeContext() = 0;
+
+    // This should be called by subclass destructor. It is also called when window/display
+    // parameters change prior to initializing a new GL context. This will in turn call
+    // onDestroyContext().
+    void destroyContext();
+    virtual void onDestroyContext() = 0;
+
+
+    bool                        fValid;
+    id<MTLDevice>               fDevice;
+    id<MTLCommandQueue>         fQueue;
+    dispatch_semaphore_t        fInFlightSemaphore;
+    MTKView*                    fMTKView;
+    sk_sp<SkSurface>            fSurface;
+};
+
+}   // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/MetalWindowContext.mm b/tools/sk_app/MetalWindowContext.mm
new file mode 100644
index 0000000..1ce4e26
--- /dev/null
+++ b/tools/sk_app/MetalWindowContext.mm
@@ -0,0 +1,115 @@
+
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "MetalWindowContext.h"
+#include "GrBackendSurface.h"
+#include "GrCaps.h"
+#include "GrContext.h"
+#include "GrContextPriv.h"
+#include "SkCanvas.h"
+#include "SkImage_Base.h"
+#include "SkMathPriv.h"
+#include "SkSurface.h"
+#include "mtl/GrMtlTypes.h"
+
+namespace sk_app {
+
+static int kMaxBuffersInFlight = 3;
+
+MetalWindowContext::MetalWindowContext(const DisplayParams& params)
+    : WindowContext(params)
+    , fValid(false)
+    , fSurface(nullptr) {
+    fDisplayParams.fMSAASampleCount = GrNextPow2(fDisplayParams.fMSAASampleCount);
+}
+
+void MetalWindowContext::initializeContext() {
+    SkASSERT(!fContext);
+
+    // The subclass uses these to initialize their view
+    fDevice = MTLCreateSystemDefaultDevice();
+    fQueue = [fDevice newCommandQueue];
+
+    fInFlightSemaphore = dispatch_semaphore_create(kMaxBuffersInFlight);
+
+    fValid = this->onInitializeContext();
+    fContext = GrContext::MakeMetal(fDevice, fQueue, fDisplayParams.fGrContextOptions);
+    if (!fContext && fDisplayParams.fMSAASampleCount > 1) {
+        fDisplayParams.fMSAASampleCount /= 2;
+        this->initializeContext();
+        return;
+    }
+}
+
+void MetalWindowContext::destroyContext() {
+    fSurface.reset(nullptr);
+
+    if (fContext) {
+        // in case we have outstanding refs to this guy (lua?)
+        fContext->abandonContext();
+        fContext.reset();
+    }
+
+    // TODO: Figure out who's releasing this
+    // [fQueue release];
+    [fDevice release];
+
+    this->onDestroyContext();
+}
+
+sk_sp<SkSurface> MetalWindowContext::getBackbufferSurface() {
+    sk_sp<SkSurface> surface;
+    if (fContext) {
+        GrMtlTextureInfo fbInfo;
+        fbInfo.fTexture = [[fMTKView currentDrawable] texture];
+
+        GrBackendRenderTarget backendRT(fWidth,
+                                        fHeight,
+                                        fSampleCount,
+                                        fbInfo);
+
+        surface = SkSurface::MakeFromBackendRenderTarget(fContext.get(), backendRT,
+                                                          kTopLeft_GrSurfaceOrigin,
+                                                          kBGRA_8888_SkColorType,
+                                                          fDisplayParams.fColorSpace,
+                                                          &fDisplayParams.fSurfaceProps);
+    }
+
+    return surface;
+}
+
+void MetalWindowContext::swapBuffers() {
+    // Block to ensure we don't try to render to a frame that hasn't finished presenting
+    dispatch_semaphore_wait(fInFlightSemaphore, DISPATCH_TIME_FOREVER);
+
+    id<MTLCommandBuffer> commandBuffer = [fQueue commandBuffer];
+    commandBuffer.label = @"Present";
+
+    __block dispatch_semaphore_t block_sema = fInFlightSemaphore;
+    [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer)
+     {
+         dispatch_semaphore_signal(block_sema);
+     }];
+
+    id<MTLDrawable> drawable = [fMTKView currentDrawable];
+    [commandBuffer presentDrawable:drawable];
+    [commandBuffer commit];
+}
+
+void MetalWindowContext::resize(int w, int h) {
+    this->destroyContext();
+    this->initializeContext();
+}
+
+void MetalWindowContext::setDisplayParams(const DisplayParams& params) {
+    this->destroyContext();
+    fDisplayParams = params;
+    this->initializeContext();
+}
+
+}   //namespace sk_app
diff --git a/tools/sk_app/VulkanWindowContext.cpp b/tools/sk_app/VulkanWindowContext.cpp
index 2a007d5..564f115 100644
--- a/tools/sk_app/VulkanWindowContext.cpp
+++ b/tools/sk_app/VulkanWindowContext.cpp
@@ -8,6 +8,7 @@
 
 #include "VulkanWindowContext.h"
 
+#include "GrBackendSemaphore.h"
 #include "GrBackendSurface.h"
 #include "GrContext.h"
 #include "SkAutoMalloc.h"
@@ -41,7 +42,6 @@
     , fImages(nullptr)
     , fImageLayouts(nullptr)
     , fSurfaces(nullptr)
-    , fCommandPool(VK_NULL_HANDLE)
     , fBackbuffers(nullptr) {
     fGetInstanceProcAddr = instProc;
     fGetDeviceProcAddr = devProc;
@@ -262,12 +262,18 @@
     // If mailbox mode is available, use it, as it is the lowest-latency non-
     // tearing mode. If not, fall back to FIFO which is always available.
     VkPresentModeKHR mode = VK_PRESENT_MODE_FIFO_KHR;
+    bool hasImmediate = false;
     for (uint32_t i = 0; i < presentModeCount; ++i) {
         // use mailbox
         if (VK_PRESENT_MODE_MAILBOX_KHR == presentModes[i]) {
-            mode = presentModes[i];
-            break;
+            mode = VK_PRESENT_MODE_MAILBOX_KHR;
         }
+        if (VK_PRESENT_MODE_IMMEDIATE_KHR == presentModes[i]) {
+            hasImmediate = true;
+        }
+    }
+    if (params.fDisableVsync && hasImmediate) {
+        mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
     }
 
     VkSwapchainCreateInfoKHR swapchainCreateInfo;
@@ -336,6 +342,7 @@
         info.fImageTiling = VK_IMAGE_TILING_OPTIMAL;
         info.fFormat = format;
         info.fLevelCount = 1;
+        info.fCurrentQueueFamily = fPresentQueueIndex;
 
         GrBackendRenderTarget backendRT(fWidth, fHeight, fSampleCount, info);
 
@@ -347,37 +354,12 @@
                                                               &fDisplayParams.fSurfaceProps);
     }
 
-    // create the command pool for the command buffers
-    if (VK_NULL_HANDLE == fCommandPool) {
-        VkCommandPoolCreateInfo commandPoolInfo;
-        memset(&commandPoolInfo, 0, sizeof(VkCommandPoolCreateInfo));
-        commandPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
-        // this needs to be on the render queue
-        commandPoolInfo.queueFamilyIndex = fGraphicsQueueIndex;
-        commandPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
-        GR_VK_CALL_ERRCHECK(fInterface,
-                            CreateCommandPool(fDevice, &commandPoolInfo,
-                                              nullptr, &fCommandPool));
-    }
-
     // set up the backbuffers
     VkSemaphoreCreateInfo semaphoreInfo;
     memset(&semaphoreInfo, 0, sizeof(VkSemaphoreCreateInfo));
     semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
     semaphoreInfo.pNext = nullptr;
     semaphoreInfo.flags = 0;
-    VkCommandBufferAllocateInfo commandBuffersInfo;
-    memset(&commandBuffersInfo, 0, sizeof(VkCommandBufferAllocateInfo));
-    commandBuffersInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
-    commandBuffersInfo.pNext = nullptr;
-    commandBuffersInfo.commandPool = fCommandPool;
-    commandBuffersInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
-    commandBuffersInfo.commandBufferCount = 2;
-    VkFenceCreateInfo fenceInfo;
-    memset(&fenceInfo, 0, sizeof(VkFenceCreateInfo));
-    fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
-    fenceInfo.pNext = nullptr;
-    fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
 
     // we create one additional backbuffer structure here, because we want to
     // give the command buffers they contain a chance to finish before we cycle back
@@ -386,19 +368,7 @@
         fBackbuffers[i].fImageIndex = -1;
         GR_VK_CALL_ERRCHECK(fInterface,
                             CreateSemaphore(fDevice, &semaphoreInfo,
-                                            nullptr, &fBackbuffers[i].fAcquireSemaphore));
-        GR_VK_CALL_ERRCHECK(fInterface,
-                            CreateSemaphore(fDevice, &semaphoreInfo,
                                             nullptr, &fBackbuffers[i].fRenderSemaphore));
-        GR_VK_CALL_ERRCHECK(fInterface,
-                            AllocateCommandBuffers(fDevice, &commandBuffersInfo,
-                                                   fBackbuffers[i].fTransitionCmdBuffers));
-        GR_VK_CALL_ERRCHECK(fInterface,
-                            CreateFence(fDevice, &fenceInfo, nullptr,
-                                        &fBackbuffers[i].fUsageFences[0]));
-        GR_VK_CALL_ERRCHECK(fInterface,
-                            CreateFence(fDevice, &fenceInfo, nullptr,
-                                        &fBackbuffers[i].fUsageFences[1]));
     }
     fCurrentBackbufferIndex = fImageCount;
 }
@@ -407,26 +377,11 @@
 
     if (fBackbuffers) {
         for (uint32_t i = 0; i < fImageCount + 1; ++i) {
-            GR_VK_CALL_ERRCHECK(fInterface,
-                                WaitForFences(fDevice, 2,
-                                              fBackbuffers[i].fUsageFences,
-                                              true, UINT64_MAX));
             fBackbuffers[i].fImageIndex = -1;
             GR_VK_CALL(fInterface,
                        DestroySemaphore(fDevice,
-                                        fBackbuffers[i].fAcquireSemaphore,
-                                        nullptr));
-            GR_VK_CALL(fInterface,
-                       DestroySemaphore(fDevice,
                                         fBackbuffers[i].fRenderSemaphore,
                                         nullptr));
-            GR_VK_CALL(fInterface,
-                       FreeCommandBuffers(fDevice, fCommandPool, 2,
-                                          fBackbuffers[i].fTransitionCmdBuffers));
-            GR_VK_CALL(fInterface,
-                       DestroyFence(fDevice, fBackbuffers[i].fUsageFences[0], 0));
-            GR_VK_CALL(fInterface,
-                       DestroyFence(fDevice, fBackbuffers[i].fUsageFences[1], 0));
         }
     }
 
@@ -453,11 +408,6 @@
 
         this->destroyBuffers();
 
-        if (VK_NULL_HANDLE != fCommandPool) {
-            GR_VK_CALL(fInterface, DestroyCommandPool(fDevice, fCommandPool, nullptr));
-            fCommandPool = VK_NULL_HANDLE;
-        }
-
         if (VK_NULL_HANDLE != fSwapchain) {
             fDestroySwapchainKHR(fDevice, fSwapchain, nullptr);
             fSwapchain = VK_NULL_HANDLE;
@@ -500,9 +450,6 @@
     }
 
     BackbufferInfo* backbuffer = fBackbuffers + fCurrentBackbufferIndex;
-    GR_VK_CALL_ERRCHECK(fInterface,
-                        WaitForFences(fDevice, 2, backbuffer->fUsageFences,
-                                      true, UINT64_MAX));
     return backbuffer;
 }
 
@@ -510,100 +457,51 @@
     BackbufferInfo* backbuffer = this->getAvailableBackbuffer();
     SkASSERT(backbuffer);
 
-    // reset the fence
-    GR_VK_CALL_ERRCHECK(fInterface,
-                        ResetFences(fDevice, 2, backbuffer->fUsageFences));
     // semaphores should be in unsignaled state
+    VkSemaphoreCreateInfo semaphoreInfo;
+    memset(&semaphoreInfo, 0, sizeof(VkSemaphoreCreateInfo));
+    semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+    semaphoreInfo.pNext = nullptr;
+    semaphoreInfo.flags = 0;
+    VkSemaphore semaphore;
+    GR_VK_CALL_ERRCHECK(fInterface, CreateSemaphore(fDevice, &semaphoreInfo,
+                                                    nullptr, &semaphore));
 
     // acquire the image
     VkResult res = fAcquireNextImageKHR(fDevice, fSwapchain, UINT64_MAX,
-                                        backbuffer->fAcquireSemaphore, VK_NULL_HANDLE,
+                                        semaphore, VK_NULL_HANDLE,
                                         &backbuffer->fImageIndex);
     if (VK_ERROR_SURFACE_LOST_KHR == res) {
         // need to figure out how to create a new vkSurface without the platformData*
         // maybe use attach somehow? but need a Window
+        GR_VK_CALL(fInterface, DestroySemaphore(fDevice, semaphore, nullptr));
         return nullptr;
     }
     if (VK_ERROR_OUT_OF_DATE_KHR == res) {
         // tear swapchain down and try again
         if (!this->createSwapchain(-1, -1, fDisplayParams)) {
+            GR_VK_CALL(fInterface, DestroySemaphore(fDevice, semaphore, nullptr));
             return nullptr;
         }
         backbuffer = this->getAvailableBackbuffer();
-        GR_VK_CALL_ERRCHECK(fInterface,
-                            ResetFences(fDevice, 2, backbuffer->fUsageFences));
 
         // acquire the image
         res = fAcquireNextImageKHR(fDevice, fSwapchain, UINT64_MAX,
-                                   backbuffer->fAcquireSemaphore, VK_NULL_HANDLE,
+                                   semaphore, VK_NULL_HANDLE,
                                    &backbuffer->fImageIndex);
 
         if (VK_SUCCESS != res) {
+            GR_VK_CALL(fInterface, DestroySemaphore(fDevice, semaphore, nullptr));
             return nullptr;
         }
     }
 
-    // set up layout transfer from initial to color attachment
-    VkImageLayout layout = fImageLayouts[backbuffer->fImageIndex];
-    SkASSERT(VK_IMAGE_LAYOUT_UNDEFINED == layout || VK_IMAGE_LAYOUT_PRESENT_SRC_KHR == layout);
-    VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
-    VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
-    VkAccessFlags srcAccessMask = 0;
-    VkAccessFlags dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
-                                  VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
-
-    VkImageMemoryBarrier imageMemoryBarrier = {
-        VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,   // sType
-        NULL,                                     // pNext
-        srcAccessMask,                            // outputMask
-        dstAccessMask,                            // inputMask
-        layout,                                   // oldLayout
-        VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, // newLayout
-        fPresentQueueIndex,                       // srcQueueFamilyIndex
-        fGraphicsQueueIndex,                      // dstQueueFamilyIndex
-        fImages[backbuffer->fImageIndex],         // image
-        { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 } // subresourceRange
-    };
-    GR_VK_CALL_ERRCHECK(fInterface,
-                        ResetCommandBuffer(backbuffer->fTransitionCmdBuffers[0], 0));
-    VkCommandBufferBeginInfo info;
-    memset(&info, 0, sizeof(VkCommandBufferBeginInfo));
-    info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
-    info.flags = 0;
-    GR_VK_CALL_ERRCHECK(fInterface,
-                        BeginCommandBuffer(backbuffer->fTransitionCmdBuffers[0], &info));
-
-    GR_VK_CALL(fInterface,
-               CmdPipelineBarrier(backbuffer->fTransitionCmdBuffers[0],
-                                  srcStageMask, dstStageMask, 0,
-                                  0, nullptr,
-                                  0, nullptr,
-                                  1, &imageMemoryBarrier));
-
-    GR_VK_CALL_ERRCHECK(fInterface,
-                        EndCommandBuffer(backbuffer->fTransitionCmdBuffers[0]));
-
-    VkPipelineStageFlags waitDstStageFlags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
-    // insert the layout transfer into the queue and wait on the acquire
-    VkSubmitInfo submitInfo;
-    memset(&submitInfo, 0, sizeof(VkSubmitInfo));
-    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
-    submitInfo.waitSemaphoreCount = 1;
-    submitInfo.pWaitSemaphores = &backbuffer->fAcquireSemaphore;
-    submitInfo.pWaitDstStageMask = &waitDstStageFlags;
-    submitInfo.commandBufferCount = 1;
-    submitInfo.pCommandBuffers = &backbuffer->fTransitionCmdBuffers[0];
-    submitInfo.signalSemaphoreCount = 0;
-
-    GR_VK_CALL_ERRCHECK(fInterface,
-                        QueueSubmit(fGraphicsQueue, 1, &submitInfo,
-                                    backbuffer->fUsageFences[0]));
-
     SkSurface* surface = fSurfaces[backbuffer->fImageIndex].get();
-    GrBackendRenderTarget backendRT = surface->getBackendRenderTarget(
-                                                        SkSurface::kFlushRead_BackendHandleAccess);
-    backendRT.setVkImageLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
 
+    GrBackendSemaphore beSemaphore;
+    beSemaphore.initVulkan(semaphore);
+
+    surface->wait(1, &beSemaphore);
 
     return sk_ref_sp(surface);
 }
@@ -613,64 +511,11 @@
     BackbufferInfo* backbuffer = fBackbuffers + fCurrentBackbufferIndex;
     SkSurface* surface = fSurfaces[backbuffer->fImageIndex].get();
 
-    GrBackendRenderTarget backendRT = surface->getBackendRenderTarget(
-                                                        SkSurface::kFlushRead_BackendHandleAccess);
-    GrVkImageInfo imageInfo;
-    SkAssertResult(backendRT.getVkImageInfo(&imageInfo));
-    // Check to make sure we never change the actually wrapped image
-    SkASSERT(imageInfo.fImage == fImages[backbuffer->fImageIndex]);
+    GrBackendSemaphore beSemaphore;
+    beSemaphore.initVulkan(backbuffer->fRenderSemaphore);
 
-    VkImageLayout layout = imageInfo.fImageLayout;
-    VkPipelineStageFlags srcStageMask = GrVkImage::LayoutToPipelineSrcStageFlags(layout);
-    VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
-    VkAccessFlags srcAccessMask = GrVkImage::LayoutToSrcAccessMask(layout);
-    VkAccessFlags dstAccessMask = 0;
-
-    VkImageMemoryBarrier imageMemoryBarrier = {
-        VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,   // sType
-        NULL,                                     // pNext
-        srcAccessMask,                            // outputMask
-        dstAccessMask,                            // inputMask
-        layout,                                   // oldLayout
-        VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,          // newLayout
-        fGraphicsQueueIndex,                      // srcQueueFamilyIndex
-        fPresentQueueIndex,                       // dstQueueFamilyIndex
-        fImages[backbuffer->fImageIndex],         // image
-        { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 } // subresourceRange
-    };
-    GR_VK_CALL_ERRCHECK(fInterface,
-                        ResetCommandBuffer(backbuffer->fTransitionCmdBuffers[1], 0));
-    VkCommandBufferBeginInfo info;
-    memset(&info, 0, sizeof(VkCommandBufferBeginInfo));
-    info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
-    info.flags = 0;
-    GR_VK_CALL_ERRCHECK(fInterface,
-                        BeginCommandBuffer(backbuffer->fTransitionCmdBuffers[1], &info));
-    GR_VK_CALL(fInterface,
-               CmdPipelineBarrier(backbuffer->fTransitionCmdBuffers[1],
-                                  srcStageMask, dstStageMask, 0,
-                                  0, nullptr,
-                                  0, nullptr,
-                                  1, &imageMemoryBarrier));
-    GR_VK_CALL_ERRCHECK(fInterface,
-                        EndCommandBuffer(backbuffer->fTransitionCmdBuffers[1]));
-
-    fImageLayouts[backbuffer->fImageIndex] = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
-
-    // insert the layout transfer into the queue and wait on the acquire
-    VkSubmitInfo submitInfo;
-    memset(&submitInfo, 0, sizeof(VkSubmitInfo));
-    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
-    submitInfo.waitSemaphoreCount = 0;
-    submitInfo.pWaitDstStageMask = 0;
-    submitInfo.commandBufferCount = 1;
-    submitInfo.pCommandBuffers = &backbuffer->fTransitionCmdBuffers[1];
-    submitInfo.signalSemaphoreCount = 1;
-    submitInfo.pSignalSemaphores = &backbuffer->fRenderSemaphore;
-
-    GR_VK_CALL_ERRCHECK(fInterface,
-                        QueueSubmit(fGraphicsQueue, 1, &submitInfo,
-                                    backbuffer->fUsageFences[1]));
+    surface->flush(SkSurface::BackendSurfaceAccess::kPresent, kNone_GrFlushFlags,
+                   1, &beSemaphore);
 
     // Submit present operation to present queue
     const VkPresentInfoKHR presentInfo =
diff --git a/tools/sk_app/VulkanWindowContext.h b/tools/sk_app/VulkanWindowContext.h
index 1fbf18d..cb14480 100644
--- a/tools/sk_app/VulkanWindowContext.h
+++ b/tools/sk_app/VulkanWindowContext.h
@@ -56,10 +56,7 @@
 
     struct BackbufferInfo {
         uint32_t        fImageIndex;          // image this is associated with
-        VkSemaphore     fAcquireSemaphore;    // we signal on this for acquisition of image
         VkSemaphore     fRenderSemaphore;     // we wait on this for rendering to be done
-        VkCommandBuffer fTransitionCmdBuffers[2]; // to transition layout between present and render
-        VkFence         fUsageFences[2];      // used to ensure this data is no longer used on GPU
     };
 
     BackbufferInfo* getAvailableBackbuffer();
@@ -113,7 +110,6 @@
     VkImage*               fImages;         // images in the swapchain
     VkImageLayout*         fImageLayouts;   // layouts of these images when not color attachment
     sk_sp<SkSurface>*      fSurfaces;       // surfaces client renders to (may not be based on rts)
-    VkCommandPool          fCommandPool;
     BackbufferInfo*        fBackbuffers;
     uint32_t               fCurrentBackbufferIndex;
 };
diff --git a/tools/sk_app/Window.cpp b/tools/sk_app/Window.cpp
index d167914..c70aea1 100644
--- a/tools/sk_app/Window.cpp
+++ b/tools/sk_app/Window.cpp
@@ -70,15 +70,13 @@
         return;
     }
     markInvalProcessed();
+    this->visitLayers([](Layer* layer) { layer->onPrePaint(); });
     sk_sp<SkSurface> backbuffer = fWindowContext->getBackbufferSurface();
     if (backbuffer) {
         // draw into the canvas of this surface
-        SkCanvas* canvas = backbuffer->getCanvas();
+        this->visitLayers([=](Layer* layer) { layer->onPaint(backbuffer.get()); });
 
-        this->visitLayers([](Layer* layer) { layer->onPrePaint(); });
-        this->visitLayers([=](Layer* layer) { layer->onPaint(canvas); });
-
-        canvas->flush();
+        backbuffer->flush();
 
         fWindowContext->swapBuffers();
     } else {
diff --git a/tools/sk_app/Window.h b/tools/sk_app/Window.h
index 85eea60..5ca9654 100644
--- a/tools/sk_app/Window.h
+++ b/tools/sk_app/Window.h
@@ -49,6 +49,9 @@
 #ifdef SK_VULKAN
         kVulkan_BackendType,
 #endif
+#if SK_METAL && defined(SK_BUILD_FOR_MAC)
+        kMetal_BackendType,
+#endif
         kRaster_BackendType,
 
         kLast_BackendType = kRaster_BackendType
@@ -149,7 +152,7 @@
         virtual bool onTouch(intptr_t owner, InputState state, float x, float y) { return false; }
         virtual void onUIStateChanged(const SkString& stateName, const SkString& stateValue) {}
         virtual void onPrePaint() {}
-        virtual void onPaint(SkCanvas*) {}
+        virtual void onPaint(SkSurface*) {}
         virtual void onResize(int width, int height) {}
 
     private:
diff --git a/tools/sk_app/android/GLWindowContext_android.cpp b/tools/sk_app/android/GLWindowContext_android.cpp
index bdab7da..f7f9e0c 100644
--- a/tools/sk_app/android/GLWindowContext_android.cpp
+++ b/tools/sk_app/android/GLWindowContext_android.cpp
@@ -117,6 +117,8 @@
     eglGetConfigAttrib(fDisplay, surfaceConfig, EGL_SAMPLES, &fSampleCount);
     fSampleCount = SkTMax(fSampleCount, 1);
 
+    eglSwapInterval(fDisplay, fDisplayParams.fDisableVsync ? 0 : 1);
+
     return GrGLMakeNativeInterface();
 }
 
diff --git a/tools/sk_app/ios/RasterWindowContext_ios.cpp b/tools/sk_app/ios/RasterWindowContext_ios.cpp
index fe76b81..c3d9c74 100644
--- a/tools/sk_app/ios/RasterWindowContext_ios.cpp
+++ b/tools/sk_app/ios/RasterWindowContext_ios.cpp
@@ -9,9 +9,9 @@
 #include "../GLWindowContext.h"
 #include "SkCanvas.h"
 #include "SkColorFilter.h"
+#include "ToolUtils.h"
 #include "WindowContextFactory_ios.h"
 #include "gl/GrGLInterface.h"
-#include "sk_tool_utils.h"
 
 #include <OpenGLES/ES2/gl.h>
 
diff --git a/tools/sk_app/mac/GLWindowContext_mac.mm b/tools/sk_app/mac/GLWindowContext_mac.mm
index 8fc9579..f6650f4 100644
--- a/tools/sk_app/mac/GLWindowContext_mac.mm
+++ b/tools/sk_app/mac/GLWindowContext_mac.mm
@@ -17,18 +17,6 @@
 using sk_app::window_context_factory::MacWindowInfo;
 using sk_app::GLWindowContext;
 
-@interface GLView : NSOpenGLView
-@end
-
-@implementation GLView
-
-- (void)drawRect:(NSRect)dirtyRect {
-    // not sure why the parent isn't getting this, but we'll pass it up
-    [[self superview] drawRect:dirtyRect];
-}
-
-@end
-
 namespace {
 
 class GLWindowContext_mac : public GLWindowContext {
@@ -44,7 +32,7 @@
 
 private:
     NSView*              fMainView;
-    GLView*              fGLView;
+    NSOpenGLView*        fGLView;
     NSOpenGLContext*     fGLContext;
     NSOpenGLPixelFormat* fPixelFormat;
 
@@ -111,7 +99,7 @@
 
     // create view
     NSRect rect = fMainView.bounds;
-    fGLView = [[GLView alloc] initWithFrame:rect];
+    fGLView = [[NSOpenGLView alloc] initWithFrame:rect];
     if (nil == fGLView) {
         [fGLContext release];
         fGLContext = nil;
@@ -138,7 +126,7 @@
                                                views:views]];
 
     // make context current
-    GLint swapInterval = 1;
+    GLint swapInterval = fDisplayParams.fDisableVsync ? 0 : 1;
     [fGLContext setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval];
     [fGLView setOpenGLContext:fGLContext];
     [fGLView setPixelFormat:fPixelFormat];
@@ -161,7 +149,7 @@
     fSampleCount = sampleCount;
     fSampleCount = SkTMax(fSampleCount, 1);
 
-    const NSRect viewportRect = [fMainView bounds];
+    const NSRect viewportRect = [fGLView bounds];
     fWidth = viewportRect.size.width;
     fHeight = viewportRect.size.height;
     glViewport(0, 0, fWidth, fHeight);
diff --git a/tools/sk_app/mac/MetalWindowContext_mac.mm b/tools/sk_app/mac/MetalWindowContext_mac.mm
new file mode 100644
index 0000000..3c76d1a
--- /dev/null
+++ b/tools/sk_app/mac/MetalWindowContext_mac.mm
@@ -0,0 +1,121 @@
+
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "../MetalWindowContext.h"
+#include "WindowContextFactory_mac.h"
+
+#import <MetalKit/MetalKit.h>
+
+#include <Cocoa/Cocoa.h>
+
+using sk_app::DisplayParams;
+using sk_app::window_context_factory::MacWindowInfo;
+using sk_app::MetalWindowContext;
+
+namespace {
+
+class MetalWindowContext_mac : public MetalWindowContext {
+public:
+    MetalWindowContext_mac(const MacWindowInfo&, const DisplayParams&);
+
+    ~MetalWindowContext_mac() override;
+
+    bool onInitializeContext() override;
+    void onDestroyContext() override;
+
+private:
+    NSView*              fMainView;
+
+    typedef MetalWindowContext INHERITED;
+};
+
+MetalWindowContext_mac::MetalWindowContext_mac(const MacWindowInfo& info, const DisplayParams& params)
+    : INHERITED(params)
+    , fMainView(info.fMainView) {
+
+    // any config code here (particularly for msaa)?
+
+    this->initializeContext();
+}
+
+MetalWindowContext_mac::~MetalWindowContext_mac() {
+    this->destroyContext();
+}
+
+bool MetalWindowContext_mac::onInitializeContext() {
+    SkASSERT(nil != fMainView);
+
+    // create mtkview
+    NSRect rect = fMainView.bounds;
+    fMTKView = [[MTKView alloc] initWithFrame:rect device:fDevice];
+    if (nil == fMTKView) {
+        return false;
+    }
+
+    fMTKView.autoResizeDrawable = NO;
+    fMTKView.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
+    fMTKView.drawableSize = rect.size;
+
+    if (fDisplayParams.fMSAASampleCount > 1) {
+        if (![fDevice supportsTextureSampleCount:fDisplayParams.fMSAASampleCount]) {
+            return false;
+        }
+    }
+    fMTKView.sampleCount = fDisplayParams.fMSAASampleCount;
+
+    // attach Metal view to main view
+    [fMTKView setTranslatesAutoresizingMaskIntoConstraints:NO];
+
+    [fMainView addSubview:fMTKView];
+    NSDictionary *views = NSDictionaryOfVariableBindings(fMTKView);
+
+    [fMainView addConstraints:
+     [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[fMTKView]|"
+                                             options:0
+                                             metrics:nil
+                                               views:views]];
+
+    [fMainView addConstraints:
+     [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[fMTKView]|"
+                                             options:0
+                                             metrics:nil
+                                               views:views]];
+
+    fSampleCount = [fMTKView sampleCount];
+    fStencilBits = 8;
+
+    CGSize backingSize = [fMTKView drawableSize];
+    fWidth = backingSize.width;
+    fHeight = backingSize.height;
+
+    return true;
+}
+
+void MetalWindowContext_mac::onDestroyContext() {
+    [fMTKView removeFromSuperview];
+    [fMTKView release];
+    fMTKView = nil;
+}
+
+}  // anonymous namespace
+
+
+namespace sk_app {
+namespace window_context_factory {
+
+WindowContext* NewMetalForMac(const MacWindowInfo& info, const DisplayParams& params) {
+    WindowContext* ctx = new MetalWindowContext_mac(info, params);
+    if (!ctx->isValid()) {
+        delete ctx;
+        return nullptr;
+    }
+    return ctx;
+}
+
+}  // namespace window_context_factory
+}  // namespace sk_app
diff --git a/tools/sk_app/mac/RasterWindowContext_mac.mm b/tools/sk_app/mac/RasterWindowContext_mac.mm
index 8eddc82..edbbe33 100644
--- a/tools/sk_app/mac/RasterWindowContext_mac.mm
+++ b/tools/sk_app/mac/RasterWindowContext_mac.mm
@@ -9,9 +9,9 @@
 #include "../GLWindowContext.h"
 #include "SkCanvas.h"
 #include "SkColorFilter.h"
+#include "ToolUtils.h"
 #include "WindowContextFactory_mac.h"
 #include "gl/GrGLInterface.h"
-#include "sk_tool_utils.h"
 
 #include <OpenGL/gl.h>
 
@@ -21,18 +21,6 @@
 using sk_app::window_context_factory::MacWindowInfo;
 using sk_app::GLWindowContext;
 
-@interface RasterView : NSOpenGLView
-@end
-
-@implementation RasterView
-
-- (void)drawRect:(NSRect)dirtyRect {
-    // not sure why the parent isn't getting this, but we'll pass it up
-    [[self superview] drawRect:dirtyRect];
-}
-
-@end
-
 namespace {
 
 // TODO: This still uses GL to handle the update rather than using a purely raster backend,
@@ -53,7 +41,7 @@
 
 private:
     NSView*              fMainView;
-    RasterView*          fRasterView;
+    NSOpenGLView*        fRasterView;
     NSOpenGLContext*     fGLContext;
     NSOpenGLPixelFormat* fPixelFormat;
     sk_sp<SkSurface>     fBackbufferSurface;
@@ -122,7 +110,7 @@
 
     // create view
     NSRect rect = fMainView.bounds;
-    fRasterView = [[RasterView alloc] initWithFrame:rect];
+    fRasterView = [[NSOpenGLView alloc] initWithFrame:rect];
     if (nil == fRasterView) {
         [fGLContext release];
         fGLContext = nil;
@@ -172,7 +160,7 @@
     fSampleCount = sampleCount;
     fSampleCount = SkTMax(fSampleCount, 1);
 
-    const NSRect viewportRect = [fMainView bounds];
+    const NSRect viewportRect = [fRasterView bounds];
     fWidth = viewportRect.size.width;
     fHeight = viewportRect.size.height;
     glViewport(0, 0, fWidth, fHeight);
diff --git a/tools/sk_app/mac/VulkanWindowContext_mac.mm b/tools/sk_app/mac/VulkanWindowContext_mac.mm
new file mode 100644
index 0000000..98e90e8
--- /dev/null
+++ b/tools/sk_app/mac/VulkanWindowContext_mac.mm
@@ -0,0 +1,154 @@
+
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "vk/GrVkVulkan.h"
+
+#include "vk/GrVkInterface.h"
+#include "vk/GrVkUtil.h"
+
+#include "vk/VkTestUtils.h"
+
+#include "WindowContextFactory_mac.h"
+#include "../VulkanWindowContext.h"
+#include "vulkan/vulkan_macos.h"
+
+#import <MetalKit/MetalKit.h>
+
+namespace sk_app {
+
+class VulkanWindowContext_mac : public VulkanWindowContext {
+public:
+    VulkanWindowContext_mac(MTKView* mtkView,
+                            const DisplayParams& params,
+                            CreateVkSurfaceFn createVkSurface,
+                            CanPresentFn canPresent,
+                            PFN_vkGetInstanceProcAddr instProc,
+                            PFN_vkGetDeviceProcAddr devProc);
+
+    ~VulkanWindowContext_mac() override;
+
+    void resize(int w, int h) override;
+
+private:
+    MTKView*              fMTKView;
+
+    typedef VulkanWindowContext INHERITED;
+};
+
+VulkanWindowContext_mac::VulkanWindowContext_mac(MTKView* mtkView,
+                                                 const DisplayParams& params,
+                                                 CreateVkSurfaceFn createVkSurface,
+                                                 CanPresentFn canPresent,
+                                                 PFN_vkGetInstanceProcAddr instProc,
+                                                 PFN_vkGetDeviceProcAddr devProc)
+    : INHERITED(params, createVkSurface, canPresent, instProc, devProc)
+    , fMTKView(mtkView) {
+
+    // any config code here (particularly for msaa)?
+}
+
+VulkanWindowContext_mac::~VulkanWindowContext_mac() {
+    [fMTKView removeFromSuperview];
+    [fMTKView release];
+    fMTKView = nil;
+}
+
+void VulkanWindowContext_mac::resize(int w, int h) {
+    CGSize newSize;
+    newSize.width = w;
+    newSize.height = h;
+    fMTKView.drawableSize = newSize;
+    this->INHERITED::resize(w, h);
+}
+
+namespace window_context_factory {
+
+WindowContext* NewVulkanForMac(const MacWindowInfo& info, const DisplayParams& displayParams) {
+    PFN_vkGetInstanceProcAddr instProc;
+    PFN_vkGetDeviceProcAddr devProc;
+    if (!sk_gpu_test::LoadVkLibraryAndGetProcAddrFuncs(&instProc, &devProc)) {
+        return nullptr;
+    }
+
+    // Create mtkview
+    NSRect rect = info.fMainView.frame;
+    MTKView* mtkView = [[MTKView alloc] initWithFrame:rect device:nil];
+    if (nil == mtkView) {
+        return nullptr;
+    }
+
+    mtkView.autoResizeDrawable = NO;
+    mtkView.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
+    mtkView.drawableSize = rect.size;
+
+//    if (fDisplayParams.fMSAASampleCount > 1) {
+//        if (![fDevice supportsTextureSampleCount:fDisplayParams.fMSAASampleCount]) {
+//            return nullptr;
+//        }
+//    }
+//    mtkView.sampleCount = fDisplayParams.fMSAASampleCount;
+
+    // attach Metal view to main view
+    [mtkView setTranslatesAutoresizingMaskIntoConstraints:NO];
+
+    [info.fMainView addSubview:mtkView];
+    NSDictionary *views = NSDictionaryOfVariableBindings(mtkView);
+
+    [info.fMainView addConstraints:
+     [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[mtkView]|"
+                                             options:0
+                                             metrics:nil
+                                               views:views]];
+
+    [info.fMainView addConstraints:
+     [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[mtkView]|"
+                                             options:0
+                                             metrics:nil
+                                               views:views]];
+
+    auto createVkSurface = [mtkView, instProc](VkInstance instance) -> VkSurfaceKHR {
+        static PFN_vkCreateMacOSSurfaceMVK createMacOSSurfaceMVK = nullptr;
+        if (!createMacOSSurfaceMVK) {
+            createMacOSSurfaceMVK =
+                    (PFN_vkCreateMacOSSurfaceMVK) instProc(instance, "vkCreateMacOSSurfaceMVK");
+        }
+
+        VkSurfaceKHR surface;
+
+        VkMacOSSurfaceCreateInfoMVK surfaceCreateInfo;
+        memset(&surfaceCreateInfo, 0, sizeof(VkMacOSSurfaceCreateInfoMVK));
+        surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
+        surfaceCreateInfo.pNext = nullptr;
+        surfaceCreateInfo.flags = 0;
+        surfaceCreateInfo.pView = (__bridge void*)mtkView;
+
+        VkResult res = createMacOSSurfaceMVK(instance, &surfaceCreateInfo, nullptr, &surface);
+        if (VK_SUCCESS != res) {
+            return VK_NULL_HANDLE;
+        }
+
+        return surface;
+    };
+
+    auto canPresent = [](VkInstance instance, VkPhysicalDevice physDev, uint32_t queueFamilyIndex) {
+        return true;
+    };
+    WindowContext* context = new VulkanWindowContext_mac(mtkView, displayParams, createVkSurface,
+                                                         canPresent, instProc, devProc);
+    if (!context->isValid()) {
+        delete context;
+        [mtkView removeFromSuperview];
+        [mtkView release];
+        return nullptr;
+    }
+    return context;
+}
+
+}  // namespace VulkanWindowContextFactory
+
+}  // namespace sk_app
diff --git a/tools/sk_app/mac/WindowContextFactory_mac.h b/tools/sk_app/mac/WindowContextFactory_mac.h
index 840c711..136581e 100644
--- a/tools/sk_app/mac/WindowContextFactory_mac.h
+++ b/tools/sk_app/mac/WindowContextFactory_mac.h
@@ -22,14 +22,21 @@
     NSView*   fMainView;
 };
 
+#ifdef SK_VULKAN
+WindowContext* NewVulkanForMac(const MacWindowInfo&, const DisplayParams&);
+#else
 inline WindowContext* NewVulkanForMac(const MacWindowInfo&, const DisplayParams&) {
     // No Vulkan support on Mac.
     return nullptr;
 }
+#endif
 
 WindowContext* NewGLForMac(const MacWindowInfo&, const DisplayParams&);
 
 WindowContext* NewRasterForMac(const MacWindowInfo&, const DisplayParams&);
+#ifdef SK_METAL
+WindowContext* NewMetalForMac(const MacWindowInfo&, const DisplayParams&);
+#endif
 
 }  // namespace window_context_factory
 
diff --git a/tools/sk_app/mac/Window_mac.h b/tools/sk_app/mac/Window_mac.h
index cef3546..94f329a 100644
--- a/tools/sk_app/mac/Window_mac.h
+++ b/tools/sk_app/mac/Window_mac.h
@@ -9,6 +9,7 @@
 #define Window_mac_DEFINED
 
 #include "../Window.h"
+#include "SkTDynamicHash.h"
 
 #import <Cocoa/Cocoa.h>
 
@@ -19,7 +20,8 @@
     Window_mac()
             : INHERITED()
             , fWindow(nil)
-            , fMSAASampleCount(1) {}
+            , fMSAASampleCount(1)
+            , fIsMouseDown(false) {}
     ~Window_mac() override {
         this->closeWindow();
     }
@@ -31,14 +33,37 @@
 
     bool attach(BackendType) override;
 
-    void onInval() override;
+    void onInval() override {}
 
-    NSView* view() { return [fWindow contentView]; }
+    static void PaintWindows();
+    static void HandleWindowEvent(const NSEvent* event);
+
+    static const NSInteger& GetKey(const Window_mac& w) {
+        return w.fWindowNumber;
+    }
+
+    static uint32_t Hash(const NSInteger& windowNumber) {
+        return windowNumber;
+    }
+
+    bool needsPaint() { return this->fIsContentInvalidated; }
+
+    NSWindow* window() { return fWindow; }
     void closeWindow();
 
+    void resetMouse();
+
 private:
+    void handleEvent(const NSEvent* event);
+
     NSWindow*    fWindow;
+    NSInteger    fWindowNumber;
     int          fMSAASampleCount;
+    bool         fIsMouseDown;
+    NSPoint      fMouseDownPos;
+    uint32_t     fMouseModifiers;
+
+    static SkTDynamicHash<Window_mac, NSInteger> gWindowMap;
 
     typedef Window INHERITED;
 };
diff --git a/tools/sk_app/mac/Window_mac.mm b/tools/sk_app/mac/Window_mac.mm
index 2c3f175..d52d725 100644
--- a/tools/sk_app/mac/Window_mac.mm
+++ b/tools/sk_app/mac/Window_mac.mm
@@ -9,16 +9,13 @@
 #include "WindowContextFactory_mac.h"
 #include "Window_mac.h"
 
-@interface MainView : NSView
+@interface WindowDelegate : NSObject<NSWindowDelegate>
 
-- (MainView*)initWithWindow:(sk_app::Window*)initWindow;
+- (WindowDelegate*)initWithWindow:(sk_app::Window_mac*)initWindow;
 
 @end
 
-@interface WindowDelegate : NSObject<NSWindowDelegate>
-
-- (WindowDelegate*)initWithWindow:(sk_app::Window*)initWindow;
-
+@interface MainView : NSView
 @end
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -27,7 +24,7 @@
 
 namespace sk_app {
 
-static int gWindowCount = 0;
+SkTDynamicHash<Window_mac, NSInteger> Window_mac::gWindowMap;
 
 Window* Window::CreateNativeWindow(void*) {
     Window_mac* window = new Window_mac();
@@ -36,7 +33,6 @@
         return nullptr;
     }
 
-    ++gWindowCount;
     return window;
 }
 
@@ -62,12 +58,14 @@
     if (nil == fWindow) {
         return false;
     }
+    [fWindow setAcceptsMouseMovedEvents:YES];
+
     WindowDelegate* delegate = [[WindowDelegate alloc] initWithWindow:this];
     [fWindow setDelegate:delegate];
     [delegate release];
 
     // create view
-    MainView* view = [[[MainView alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)] initWithWindow:this];
+    MainView* view = [[MainView alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)] ;
     if (nil == view) {
         [fWindow release];
         fWindow = nil;
@@ -77,12 +75,21 @@
     // attach view to window
     [fWindow setContentView:view];
 
+    fWindowNumber = fWindow.windowNumber;
+    gWindowMap.add(this);
+
     return true;
 }
 
 void Window_mac::closeWindow() {
-    [fWindow release];
-    fWindow = nil;
+    if (nil != fWindow) {
+        gWindowMap.remove(fWindowNumber);
+        if (sk_app::Window_mac::gWindowMap.count() < 1) {
+            [NSApp terminate:fWindow];
+        }
+        [fWindow release];
+        fWindow = nil;
+    }
 }
 
 void Window_mac::setTitle(const char* title) {
@@ -100,12 +107,21 @@
     this->initWindow();
 
     window_context_factory::MacWindowInfo info;
-    info.fMainView = this->view();
+    info.fMainView = [fWindow contentView];
     switch (attachType) {
         case kRaster_BackendType:
             fWindowContext = NewRasterForMac(info, fRequestedDisplayParams);
             break;
-
+#ifdef SK_VULKAN
+        case kVulkan_BackendType:
+            fWindowContext = NewVulkanForMac(info, fRequestedDisplayParams);
+            break;
+#endif
+#ifdef SK_METAL
+        case kMetal_BackendType:
+            fWindowContext = NewMetalForMac(info, fRequestedDisplayParams);
+            break;
+#endif
         case kNativeGL_BackendType:
         default:
             fWindowContext = NewGLForMac(info, fRequestedDisplayParams);
@@ -116,76 +132,6 @@
     return (SkToBool(fWindowContext));
 }
 
-void Window_mac::onInval() {
-    [[fWindow contentView] setNeedsDisplay:YES];
-    // MacOS already queues a single drawRect event for multiple invalidations
-    // so we don't need to use our invalidation method (and it can mess things up
-    // if for some reason MacOS skips a drawRect when we need one).
-    this->markInvalProcessed();
-}
-
-}   // namespace sk_app
-
-///////////////////////////////////////////////////////////////////////////////
-
-@implementation WindowDelegate {
-    sk_app::Window* fWindow;
-}
-
-- (WindowDelegate*)initWithWindow:(sk_app::Window *)initWindow {
-    fWindow = initWindow;
-
-    return self;
-}
-
-- (void)windowDidResize:(NSNotification *)notification {
-    sk_app::Window_mac* macWindow = reinterpret_cast<sk_app::Window_mac*>(fWindow);
-    const NSRect mainRect = [macWindow->view() bounds];
-
-    fWindow->onResize(mainRect.size.width, mainRect.size.height);
-}
-
-- (BOOL)windowShouldClose:(NSWindow*)sender {
-    --sk_app::gWindowCount;
-    if (sk_app::gWindowCount < 1) {
-        [NSApp terminate:self];
-    }
-
-    reinterpret_cast<sk_app::Window_mac*>(fWindow)->closeWindow();
-
-    return FALSE;
-}
-
-@end
-
-///////////////////////////////////////////////////////////////////////////////
-
-@implementation MainView {
-    sk_app::Window* fWindow;
-}
-
-- (MainView*)initWithWindow:(sk_app::Window *)initWindow {
-    fWindow = initWindow;
-
-    return self;
-}
-
-- (BOOL)isOpaque {
-    return YES;
-}
-
-- (BOOL)canBecomeKeyView {
-    return YES;
-}
-
-- (BOOL)acceptsFirstResponder {
-    return YES;
-}
-
-- (void)drawRect:(NSRect)dirtyRect {
-    fWindow->onPaint();
-}
-
 static Window::Key get_key(unsigned short vk) {
     // This will work with an ANSI QWERTY keyboard.
     // Something more robust would be needed to support alternate keyboards.
@@ -250,65 +196,152 @@
     return modifiers;
 }
 
-- (void)keyDown:(NSEvent *)event {
-    Window::Key key = get_key([event keyCode]);
-    if (key != Window::Key::kNONE) {
-        if (!fWindow->onKey(key, Window::kDown_InputState, get_modifiers(event))) {
-            if (Window::Key::kEscape == key) {
-                [NSApp terminate:self];
+void Window_mac::PaintWindows() {
+    SkTDynamicHash<Window_mac, NSInteger>::Iter iter(&gWindowMap);
+    while (!iter.done()) {
+        if ((*iter).fIsContentInvalidated) {
+            (*iter).onPaint();
+        }
+        ++iter;
+    }
+}
+
+void Window_mac::HandleWindowEvent(const NSEvent* event) {
+    Window_mac* win = gWindowMap.find(event.window.windowNumber);
+    if (win) {
+        win->handleEvent(event);
+    }
+}
+
+void Window_mac::handleEvent(const NSEvent* event) {
+    switch (event.type) {
+        case NSEventTypeKeyDown: {
+            Window::Key key = get_key([event keyCode]);
+            if (key != Window::Key::kNONE) {
+                if (!this->onKey(key, Window::kDown_InputState, get_modifiers(event))) {
+                    if (Window::Key::kEscape == key) {
+                        [NSApp terminate:fWindow];
+                    }
+                }
             }
+
+            NSString* characters = [event charactersIgnoringModifiers];
+            NSUInteger len = [characters length];
+            if (len > 0) {
+                unichar* charBuffer = new unichar[len+1];
+                [characters getCharacters:charBuffer range:NSMakeRange(0, len)];
+                for (NSUInteger i = 0; i < len; ++i) {
+                    (void) this->onChar((SkUnichar) charBuffer[i], get_modifiers(event));
+                }
+                delete [] charBuffer;
+            }
+            break;
         }
-    }
-
-    NSString* characters = [event charactersIgnoringModifiers];
-    NSUInteger len = [characters length];
-    if (len > 0) {
-        unichar* charBuffer = new unichar[len+1];
-        [characters getCharacters:charBuffer range:NSMakeRange(0, len)];
-        for (NSUInteger i = 0; i < len; ++i) {
-            (void) fWindow->onChar((SkUnichar) charBuffer[i], get_modifiers(event));
+        case NSEventTypeKeyUp: {
+            Window::Key key = get_key([event keyCode]);
+            if (key != Window::Key::kNONE) {
+                (void) this->onKey(key, Window::kUp_InputState, get_modifiers(event));
+            }
+            break;
         }
-        delete [] charBuffer;
+        case NSEventTypeLeftMouseDown: {
+            const NSPoint pos = [event locationInWindow];
+            const NSRect rect = [fWindow.contentView frame];
+            if (NSPointInRect(pos, rect)) {
+                // This might be a resize event -- until we know we'll store the event
+                // and reset later if need be
+                fIsMouseDown = true;
+                fMouseDownPos = pos;
+                fMouseModifiers = get_modifiers(event);
+                this->onMouse(pos.x, rect.size.height - pos.y, Window::kDown_InputState,
+                              fMouseModifiers);
+            }
+            break;
+        }
+        case NSEventTypeLeftMouseUp: {
+            const NSPoint pos = [event locationInWindow];
+            const NSRect rect = [fWindow.contentView frame];
+            this->onMouse(pos.x, rect.size.height - pos.y, Window::kUp_InputState,
+                          get_modifiers(event));
+            break;
+        }
+        case NSEventTypeMouseMoved:
+        case NSEventTypeLeftMouseDragged: {
+            const NSPoint pos = [event locationInWindow];
+            const NSRect rect = [fWindow.contentView frame];
+            this->onMouse(pos.x, rect.size.height - pos.y, Window::kMove_InputState,
+                          get_modifiers(event));
+            break;
+        }
+        case NSEventTypeScrollWheel:
+            // TODO: support hasPreciseScrollingDeltas?
+            this->onMouseWheel([event scrollingDeltaY], get_modifiers(event));
+            break;
+
+        default:
+            break;
     }
 }
 
-- (void)keyUp:(NSEvent *)event {
-    Window::Key key = get_key([event keyCode]);
-    if (key != Window::Key::kNONE) {
-        (void) fWindow->onKey(key, Window::kUp_InputState, get_modifiers(event));
+void Window_mac::resetMouse() {
+    if (fIsMouseDown) {
+        // We're resizing so just send a mouse up event in the same place
+        const NSRect rect = [fWindow.contentView frame];
+        this->onMouse(fMouseDownPos.x, rect.size.height - fMouseDownPos.y, Window::kUp_InputState,
+                      fMouseModifiers);
+        fIsMouseDown = false;
     }
 }
 
-- (void)mouseDown:(NSEvent *)event {
-    const NSPoint pos = [event locationInWindow];
-    const NSRect rect = [self frame];
-    fWindow->onMouse(pos.x, rect.size.height - pos.y, Window::kDown_InputState,
-                     get_modifiers(event));
+}   // namespace sk_app
+
+///////////////////////////////////////////////////////////////////////////////
+
+@implementation WindowDelegate {
+    sk_app::Window_mac* fWindow;
 }
 
-- (void)mouseDragged:(NSEvent *)event {
-    [self mouseMoved:event];
+- (WindowDelegate*)initWithWindow:(sk_app::Window_mac *)initWindow {
+    fWindow = initWindow;
+
+    return self;
 }
 
-- (void)mouseUp:(NSEvent *)event {
-    const NSPoint pos = [event locationInWindow];
-    const NSRect rect = [self frame];
-    fWindow->onMouse(pos.x, rect.size.height - pos.y, Window::kUp_InputState,
-                     get_modifiers(event));
+- (void)windowDidResize:(NSNotification *)notification {
+    const NSRect mainRect = [fWindow->window().contentView bounds];
+
+    fWindow->onResize(mainRect.size.width, mainRect.size.height);
+    fWindow->resetMouse();
 }
 
-- (void)mouseMoved:(NSEvent *)event {
-    const NSPoint pos = [event locationInWindow];
-    const NSRect rect = [self frame];
-    fWindow->onMouse(pos.x, rect.size.height - pos.y, Window::kMove_InputState,
-                     get_modifiers(event));
-}
+- (BOOL)windowShouldClose:(NSWindow*)sender {
+    fWindow->closeWindow();
 
-- (void)scrollWheel:(NSEvent *)event {
-    // TODO: support hasPreciseScrollingDeltas?
-    fWindow->onMouseWheel([event scrollingDeltaY], get_modifiers(event));
+    return FALSE;
 }
 
-
 @end
 
+///////////////////////////////////////////////////////////////////////////////
+
+@implementation MainView
+- (BOOL)isOpaque {
+    return YES;
+}
+
+- (BOOL)canBecomeKeyView {
+    return YES;
+}
+
+- (BOOL)acceptsFirstResponder {
+    return YES;
+}
+
+// We keep these around to prevent beeping when the system can't determine
+// where the focus for key events is.
+- (void)keyDown:(NSEvent *)event {
+}
+
+- (void)keyUp:(NSEvent *)event {
+}
+@end
diff --git a/tools/sk_app/mac/main_mac.mm b/tools/sk_app/mac/main_mac.mm
index 1cfb8a9..0df5f8e 100644
--- a/tools/sk_app/mac/main_mac.mm
+++ b/tools/sk_app/mac/main_mac.mm
@@ -8,6 +8,7 @@
 #import <Cocoa/Cocoa.h>
 
 #include "../Application.h"
+#include "Window_mac.h"
 
 @interface AppDelegate : NSObject<NSApplicationDelegate, NSWindowDelegate>
 
@@ -39,6 +40,7 @@
 ///////////////////////////////////////////////////////////////////////////////////////////
 
 using sk_app::Application;
+using sk_app::Window_mac;
 
 int main(int argc, char * argv[]) {
 #if MAC_OS_X_VERSION_MAX_ALLOWED < 1070
@@ -79,12 +81,32 @@
 
     // Now we process the events
     while (![appDelegate done]) {
+        // Rather than depending on a Mac event to drive this, we treat our window
+        // invalidation flag as a separate event stream. Window::onPaint() will clear
+        // the invalidation flag, effectively removing it from the stream.
+        Window_mac::PaintWindows();
+
         NSEvent* event;
         do {
             event = [NSApp nextEventMatchingMask:NSAnyEventMask
                                        untilDate:[NSDate distantPast]
                                           inMode:NSDefaultRunLoopMode
                                          dequeue:YES];
+            NSEventType type = event.type;
+            switch (type) {
+                case NSEventTypeKeyDown:
+                case NSEventTypeKeyUp:
+                case NSEventTypeLeftMouseDown:
+                case NSEventTypeLeftMouseUp:
+                case NSEventTypeLeftMouseDragged:
+                case NSEventTypeMouseMoved:
+                case NSEventTypeScrollWheel:
+                    Window_mac::HandleWindowEvent(event);
+                    break;
+                default:
+                    break;
+            }
+            // We send all events through the system to catch window close events, drags, etc.
             [NSApp sendEvent:event];
         } while (event != nil);
 
diff --git a/tools/sk_app/unix/GLWindowContext_unix.cpp b/tools/sk_app/unix/GLWindowContext_unix.cpp
index 6add5ae..cac3f56 100644
--- a/tools/sk_app/unix/GLWindowContext_unix.cpp
+++ b/tools/sk_app/unix/GLWindowContext_unix.cpp
@@ -67,6 +67,7 @@
     SkASSERT(!fGLContext);
     sk_sp<const GrGLInterface> interface;
     bool current = false;
+
     // We attempt to use glXCreateContextAttribsARB as RenderDoc requires that the context be
     // created with this rather than glXCreateContext.
     CreateContextAttribsFn* createContextAttribs = (CreateContextAttribsFn*)glXGetProcAddressARB(
@@ -126,6 +127,17 @@
     if (!current && !glXMakeCurrent(fDisplay, fWindow, fGLContext)) {
         return nullptr;
     }
+
+    const char* glxExtensions = glXQueryExtensionsString(fDisplay, DefaultScreen(fDisplay));
+    if (glxExtensions) {
+        if (strstr(glxExtensions, "GLX_EXT_swap_control")) {
+            PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT =
+                    (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddressARB(
+                            (const GLubyte*)"glXSwapIntervalEXT");
+            glXSwapIntervalEXT(fDisplay, fWindow, fDisplayParams.fDisableVsync ? 0 : 1);
+        }
+    }
+
     glClearStencil(0);
     glClearColor(0, 0, 0, 0);
     glStencilMask(0xffffffff);
diff --git a/tools/sk_app/unix/Window_unix.cpp b/tools/sk_app/unix/Window_unix.cpp
index 09e3c52..d24beca 100644
--- a/tools/sk_app/unix/Window_unix.cpp
+++ b/tools/sk_app/unix/Window_unix.cpp
@@ -333,6 +333,8 @@
 }
 
 bool Window_unix::attach(BackendType attachType) {
+    fBackend = attachType;
+
     this->initWindow(fDisplay);
 
     window_context_factory::XlibWindowInfo winInfo;
@@ -384,4 +386,21 @@
     XSendEvent(fDisplay, fWindow, False, 0, &event);
 }
 
+void Window_unix::setRequestedDisplayParams(const DisplayParams& params, bool allowReattach) {
+#if defined(SK_VULKAN)
+    // Vulkan on unix crashes if we try to reinitialize the vulkan context without remaking the
+    // window.
+    if (fBackend == kVulkan_BackendType && allowReattach) {
+        // Need to change these early, so attach() creates the window context correctly
+        fRequestedDisplayParams = params;
+
+        this->detach();
+        this->attach(fBackend);
+        return;
+    }
+#endif
+
+    INHERITED::setRequestedDisplayParams(params, allowReattach);
+}
+
 }   // namespace sk_app
diff --git a/tools/sk_app/unix/Window_unix.h b/tools/sk_app/unix/Window_unix.h
index 1a644d5..18c9495 100644
--- a/tools/sk_app/unix/Window_unix.h
+++ b/tools/sk_app/unix/Window_unix.h
@@ -74,6 +74,8 @@
         }
     }
 
+    void setRequestedDisplayParams(const DisplayParams&, bool allowReattach) override;
+
 private:
     void closeWindow();
 
@@ -90,6 +92,10 @@
     int      fPendingWidth;
     int      fPendingHeight;
     bool     fPendingResize;
+
+    BackendType fBackend;
+
+    typedef Window INHERITED;
 };
 
 }   // namespace sk_app
diff --git a/tools/sk_app/win/GLWindowContext_win.cpp b/tools/sk_app/win/GLWindowContext_win.cpp
index 2fcd065..a629da6 100644
--- a/tools/sk_app/win/GLWindowContext_win.cpp
+++ b/tools/sk_app/win/GLWindowContext_win.cpp
@@ -72,6 +72,11 @@
         return nullptr;
     }
 
+    SkWGLExtensions extensions;
+    if (extensions.hasExtension(dc, "WGL_EXT_swap_control")) {
+        extensions.swapInterval(fDisplayParams.fDisableVsync ? 0 : 1);
+    }
+
     // Look to see if RenderDoc is attached. If so, re-create the context with a core profile
     if (wglMakeCurrent(dc, fHGLRC)) {
         auto interface = GrGLMakeNativeInterface();
@@ -100,7 +105,6 @@
         fStencilBits = pfd.cStencilBits;
 
         // Get sample count if the MSAA WGL extension is present
-        SkWGLExtensions extensions;
         if (extensions.hasExtension(dc, "WGL_ARB_multisample")) {
             static const int kSampleCountAttr = SK_WGL_SAMPLES;
             extensions.getPixelFormatAttribiv(dc,
diff --git a/tools/sk_pixel_iter.h b/tools/sk_pixel_iter.h
deleted file mode 100644
index 8bf5a55..0000000
--- a/tools/sk_pixel_iter.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef sk_pixel_iter_DEFINED
-#define sk_pixel_iter_DEFINED
-
-#include "SkPixmap.h"
-#include "SkSurface.h"
-
-namespace sk_tool_utils {
-
-    class PixelIter {
-    public:
-        PixelIter();
-        PixelIter(SkSurface* surf) {
-            SkPixmap pm;
-            if (!surf->peekPixels(&pm)) {
-                pm.reset();
-            }
-            this->reset(pm);
-        }
-
-        void reset(const SkPixmap& pm) {
-            fPM = pm;
-            fLoc = { -1, 0 };
-        }
-
-        void* next(SkIPoint* loc = nullptr) {
-            if (!fPM.addr()) {
-                return nullptr;
-            }
-            fLoc.fX += 1;
-            if (fLoc.fX >= fPM.width()) {
-                fLoc.fX = 0;
-                if (++fLoc.fY >= fPM.height()) {
-                    this->setDone();
-                    return nullptr;
-                }
-            }
-            if (loc) {
-                *loc = fLoc;
-            }
-            return fPM.writable_addr(fLoc.fX, fLoc.fY);
-        }
-
-        void setDone() {
-            fPM.reset();
-        }
-
-    private:
-        SkPixmap    fPM;
-        SkIPoint    fLoc;
-    };
-
-}  // namespace sk_tool_utils
-
-#endif  // sk_tool_utils_DEFINED
diff --git a/tools/sk_tool_utils.cpp b/tools/sk_tool_utils.cpp
deleted file mode 100644
index a50dffc..0000000
--- a/tools/sk_tool_utils.cpp
+++ /dev/null
@@ -1,414 +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 "SkBitmap.h"
-#include "SkBlendMode.h"
-#include "SkCanvas.h"
-#include "SkColorData.h"
-#include "SkColorPriv.h"
-#include "SkFontPriv.h"
-#include "SkFloatingPoint.h"
-#include "SkImage.h"
-#include "SkMatrix.h"
-#include "SkPaint.h"
-#include "SkPath.h"
-#include "SkPixelRef.h"
-#include "SkPixmap.h"
-#include "SkPoint3.h"
-#include "SkRRect.h"
-#include "SkShader.h"
-#include "SkSurface.h"
-#include "SkTextBlob.h"
-#include "sk_tool_utils.h"
-
-#include <cmath>
-#include <cstring>
-#include <memory>
-
-namespace sk_tool_utils {
-
-const char* alphatype_name(SkAlphaType at) {
-    switch (at) {
-        case kUnknown_SkAlphaType:  return "Unknown";
-        case kOpaque_SkAlphaType:   return "Opaque";
-        case kPremul_SkAlphaType:   return "Premul";
-        case kUnpremul_SkAlphaType: return "Unpremul";
-    }
-    SkASSERT(false);
-    return "unexpected alphatype";
-}
-
-const char* colortype_name(SkColorType ct) {
-    switch (ct) {
-        case kUnknown_SkColorType:      return "Unknown";
-        case kAlpha_8_SkColorType:      return "Alpha_8";
-        case kRGB_565_SkColorType:      return "RGB_565";
-        case kARGB_4444_SkColorType:    return "ARGB_4444";
-        case kRGBA_8888_SkColorType:    return "RGBA_8888";
-        case kRGB_888x_SkColorType:     return "RGB_888x";
-        case kBGRA_8888_SkColorType:    return "BGRA_8888";
-        case kRGBA_1010102_SkColorType: return "RGBA_1010102";
-        case kRGB_101010x_SkColorType:  return "RGB_101010x";
-        case kGray_8_SkColorType:       return "Gray_8";
-        case kRGBA_F16_SkColorType:     return "RGBA_F16";
-        case kRGBA_F32_SkColorType:     return "RGBA_F32";
-    }
-    SkASSERT(false);
-    return "unexpected colortype";
-}
-
-SkColor color_to_565(SkColor color) {
-    // Not a good idea to use this function for greyscale colors...
-    // it will add an obvious purple or green tint.
-    SkASSERT(SkColorGetR(color) != SkColorGetG(color) ||
-             SkColorGetR(color) != SkColorGetB(color) ||
-             SkColorGetG(color) != SkColorGetB(color));
-
-    SkPMColor pmColor = SkPreMultiplyColor(color);
-    U16CPU color16 = SkPixel32ToPixel16(pmColor);
-    return SkPixel16ToColor(color16);
-}
-
-void write_pixels(SkCanvas* canvas, const SkBitmap& bitmap, int x, int y,
-                  SkColorType colorType, SkAlphaType alphaType) {
-    SkBitmap tmp(bitmap);
-    const SkImageInfo info = SkImageInfo::Make(tmp.width(), tmp.height(), colorType, alphaType);
-
-    canvas->writePixels(info, tmp.getPixels(), tmp.rowBytes(), x, y);
-}
-
-void write_pixels(SkSurface* surface, const SkBitmap& src, int x, int y,
-                  SkColorType colorType, SkAlphaType alphaType) {
-    const SkImageInfo info = SkImageInfo::Make(src.width(), src.height(), colorType, alphaType);
-    surface->writePixels({info, src.getPixels(), src.rowBytes()}, x, y);
-}
-
-sk_sp<SkShader> create_checkerboard_shader(SkColor c1, SkColor c2, int size) {
-    SkBitmap bm;
-    bm.allocPixels(SkImageInfo::MakeS32(2 * size, 2 * size, kPremul_SkAlphaType));
-    bm.eraseColor(c1);
-    bm.eraseArea(SkIRect::MakeLTRB(0, 0, size, size), c2);
-    bm.eraseArea(SkIRect::MakeLTRB(size, size, 2 * size, 2 * size), c2);
-    return SkShader::MakeBitmapShader(
-            bm, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode);
-}
-
-SkBitmap create_checkerboard_bitmap(int w, int h, SkColor c1, SkColor c2, int checkSize) {
-    SkBitmap bitmap;
-    bitmap.allocPixels(SkImageInfo::MakeS32(w, h, kPremul_SkAlphaType));
-    SkCanvas canvas(bitmap);
-
-    sk_tool_utils::draw_checkerboard(&canvas, c1, c2, checkSize);
-    return bitmap;
-}
-
-void draw_checkerboard(SkCanvas* canvas, SkColor c1, SkColor c2, int size) {
-    SkPaint paint;
-    paint.setShader(create_checkerboard_shader(c1, c2, size));
-    paint.setBlendMode(SkBlendMode::kSrc);
-    canvas->drawPaint(paint);
-}
-
-SkBitmap create_string_bitmap(int w, int h, SkColor c, int x, int y,
-                              int textSize, const char* str) {
-    SkBitmap bitmap;
-    bitmap.allocN32Pixels(w, h);
-    SkCanvas canvas(bitmap);
-
-    SkPaint paint;
-    paint.setColor(c);
-
-    SkFont font(sk_tool_utils::create_portable_typeface(), textSize);
-
-    canvas.clear(0x00000000);
-    canvas.drawSimpleText(str, strlen(str), kUTF8_SkTextEncoding,
-                          SkIntToScalar(x), SkIntToScalar(y), font, paint);
-
-    // Tag data as sRGB (without doing any color space conversion). Color-space aware configs
-    // will process this correctly but legacy configs will render as if this returned N32.
-    SkBitmap result;
-    result.setInfo(SkImageInfo::MakeS32(w, h, kPremul_SkAlphaType));
-    result.setPixelRef(sk_ref_sp(bitmap.pixelRef()), 0, 0);
-    return result;
-}
-
-void add_to_text_blob_w_len(SkTextBlobBuilder* builder, const char* text, size_t len,
-                            SkTextEncoding encoding, const SkFont& font, SkScalar x, SkScalar y) {
-    int count = font.countText(text, len, encoding);
-    auto run = builder->allocRun(font, count, x, y);
-    font.textToGlyphs(text, len, encoding, run.glyphs, count);
-}
-
-void add_to_text_blob(SkTextBlobBuilder* builder, const char* text, const SkFont& font,
-                      SkScalar x, SkScalar y) {
-    add_to_text_blob_w_len(builder, text, strlen(text), kUTF8_SkTextEncoding, font, x, y);
-}
-
-void get_text_path(const SkFont& font, const void* text, size_t length, SkTextEncoding encoding,
-                   SkPath* dst, const SkPoint pos[]) {
-    SkAutoToGlyphs atg(font, text, length, encoding);
-    const int count = atg.count();
-    SkAutoTArray<SkPoint> computedPos;
-    if (pos == nullptr) {
-        computedPos.reset(count);
-        font.getPos(atg.glyphs(), count, &computedPos[0]);
-        pos = computedPos.get();
-    }
-
-    struct Rec {
-        SkPath* fDst;
-        const SkPoint* fPos;
-    } rec = { dst, pos };
-    font.getPaths(atg.glyphs(), atg.count(), [](const SkPath* src, const SkMatrix& mx, void* ctx) {
-        Rec* rec = (Rec*)ctx;
-        if (src) {
-            SkMatrix tmp(mx);
-            tmp.postTranslate(rec->fPos->fX, rec->fPos->fY);
-            rec->fDst->addPath(*src, tmp);
-        }
-        rec->fPos += 1;
-    }, &rec);
-}
-
-SkPath make_star(const SkRect& bounds, int numPts, int step) {
-    SkASSERT(numPts != step);
-    SkPath path;
-    path.setFillType(SkPath::kEvenOdd_FillType);
-    path.moveTo(0,-1);
-    for (int i = 1; i < numPts; ++i) {
-        int idx = i*step % numPts;
-        SkScalar theta = idx * 2*SK_ScalarPI/numPts + SK_ScalarPI/2;
-        SkScalar x = SkScalarCos(theta);
-        SkScalar y = -SkScalarSin(theta);
-        path.lineTo(x, y);
-    }
-    path.transform(SkMatrix::MakeRectToRect(path.getBounds(), bounds, SkMatrix::kFill_ScaleToFit));
-    return path;
-}
-
-static inline void norm_to_rgb(SkBitmap* bm, int x, int y, const SkVector3& norm) {
-    SkASSERT(SkScalarNearlyEqual(norm.length(), 1.0f));
-    unsigned char r = static_cast<unsigned char>((0.5f * norm.fX + 0.5f) * 255);
-    unsigned char g = static_cast<unsigned char>((-0.5f * norm.fY + 0.5f) * 255);
-    unsigned char b = static_cast<unsigned char>((0.5f * norm.fZ + 0.5f) * 255);
-    *bm->getAddr32(x, y) = SkPackARGB32(0xFF, r, g, b);
-}
-
-void create_hemi_normal_map(SkBitmap* bm, const SkIRect& dst) {
-    const SkPoint center = SkPoint::Make(dst.fLeft + (dst.width() / 2.0f),
-                                         dst.fTop + (dst.height() / 2.0f));
-    const SkPoint halfSize = SkPoint::Make(dst.width() / 2.0f, dst.height() / 2.0f);
-
-    SkVector3 norm;
-
-    for (int y = dst.fTop; y < dst.fBottom; ++y) {
-        for (int x = dst.fLeft; x < dst.fRight; ++x) {
-            norm.fX = (x + 0.5f - center.fX) / halfSize.fX;
-            norm.fY = (y + 0.5f - center.fY) / halfSize.fY;
-
-            SkScalar tmp = norm.fX * norm.fX + norm.fY * norm.fY;
-            if (tmp >= 1.0f) {
-                norm.set(0.0f, 0.0f, 1.0f);
-            } else {
-                norm.fZ = sqrtf(1.0f - tmp);
-            }
-
-            norm_to_rgb(bm, x, y, norm);
-        }
-    }
-}
-
-void create_frustum_normal_map(SkBitmap* bm, const SkIRect& dst) {
-    const SkPoint center = SkPoint::Make(dst.fLeft + (dst.width() / 2.0f),
-                                         dst.fTop + (dst.height() / 2.0f));
-
-    SkIRect inner = dst;
-    inner.inset(dst.width()/4, dst.height()/4);
-
-    SkPoint3 norm;
-    const SkPoint3 left =  SkPoint3::Make(-SK_ScalarRoot2Over2, 0.0f, SK_ScalarRoot2Over2);
-    const SkPoint3 up =    SkPoint3::Make(0.0f, -SK_ScalarRoot2Over2, SK_ScalarRoot2Over2);
-    const SkPoint3 right = SkPoint3::Make(SK_ScalarRoot2Over2,  0.0f, SK_ScalarRoot2Over2);
-    const SkPoint3 down =  SkPoint3::Make(0.0f,  SK_ScalarRoot2Over2, SK_ScalarRoot2Over2);
-
-    for (int y = dst.fTop; y < dst.fBottom; ++y) {
-        for (int x = dst.fLeft; x < dst.fRight; ++x) {
-            if (inner.contains(x, y)) {
-                norm.set(0.0f, 0.0f, 1.0f);
-            } else {
-                SkScalar locX = x + 0.5f - center.fX;
-                SkScalar locY = y + 0.5f - center.fY;
-
-                if (locX >= 0.0f) {
-                    if (locY > 0.0f) {
-                        norm = locX >= locY ? right : down;   // LR corner
-                    } else {
-                        norm = locX > -locY ? right : up;     // UR corner
-                    }
-                } else {
-                    if (locY > 0.0f) {
-                        norm = -locX > locY ? left : down;    // LL corner
-                    } else {
-                        norm = locX > locY ? up : left;       // UL corner
-                    }
-                }
-            }
-
-            norm_to_rgb(bm, x, y, norm);
-        }
-    }
-}
-
-void create_tetra_normal_map(SkBitmap* bm, const SkIRect& dst) {
-    const SkPoint center = SkPoint::Make(dst.fLeft + (dst.width() / 2.0f),
-                                         dst.fTop + (dst.height() / 2.0f));
-
-    static const SkScalar k1OverRoot3 = 0.5773502692f;
-
-    SkPoint3 norm;
-    const SkPoint3 leftUp =  SkPoint3::Make(-k1OverRoot3, -k1OverRoot3, k1OverRoot3);
-    const SkPoint3 rightUp = SkPoint3::Make(k1OverRoot3,  -k1OverRoot3, k1OverRoot3);
-    const SkPoint3 down =  SkPoint3::Make(0.0f,  SK_ScalarRoot2Over2, SK_ScalarRoot2Over2);
-
-    for (int y = dst.fTop; y < dst.fBottom; ++y) {
-        for (int x = dst.fLeft; x < dst.fRight; ++x) {
-            SkScalar locX = x + 0.5f - center.fX;
-            SkScalar locY = y + 0.5f - center.fY;
-
-            if (locX >= 0.0f) {
-                if (locY > 0.0f) {
-                    norm = locX >= locY ? rightUp : down;   // LR corner
-                } else {
-                    norm = rightUp;
-                }
-            } else {
-                if (locY > 0.0f) {
-                    norm = -locX > locY ? leftUp : down;    // LL corner
-                } else {
-                    norm = leftUp;
-                }
-            }
-
-            norm_to_rgb(bm, x, y, norm);
-        }
-    }
-}
-
-#if !defined(__clang__) && defined(_MSC_VER)
-    // MSVC takes ~2 minutes to compile this function with optimization.
-    // We don't really care to wait that long for this function.
-    #pragma optimize("", off)
-#endif
-void make_big_path(SkPath& path) {
-    #include "BigPathBench.inc" // IWYU pragma: keep
-}
-
-bool copy_to(SkBitmap* dst, SkColorType dstColorType, const SkBitmap& src) {
-    SkPixmap srcPM;
-    if (!src.peekPixels(&srcPM)) {
-        return false;
-    }
-
-    SkBitmap tmpDst;
-    SkImageInfo dstInfo = srcPM.info().makeColorType(dstColorType);
-    if (!tmpDst.setInfo(dstInfo)) {
-        return false;
-    }
-
-    if (!tmpDst.tryAllocPixels()) {
-        return false;
-    }
-
-    SkPixmap dstPM;
-    if (!tmpDst.peekPixels(&dstPM)) {
-        return false;
-    }
-
-    if (!srcPM.readPixels(dstPM)) {
-        return false;
-    }
-
-    dst->swap(tmpDst);
-    return true;
-}
-
-void copy_to_g8(SkBitmap* dst, const SkBitmap& src) {
-    SkASSERT(kBGRA_8888_SkColorType == src.colorType() ||
-             kRGBA_8888_SkColorType == src.colorType());
-
-    SkImageInfo grayInfo = src.info().makeColorType(kGray_8_SkColorType);
-    dst->allocPixels(grayInfo);
-    uint8_t* dst8 = (uint8_t*)dst->getPixels();
-    const uint32_t* src32 = (const uint32_t*)src.getPixels();
-
-    const int w = src.width();
-    const int h = src.height();
-    const bool isBGRA = (kBGRA_8888_SkColorType == src.colorType());
-    for (int y = 0; y < h; ++y) {
-        if (isBGRA) {
-            // BGRA
-            for (int x = 0; x < w; ++x) {
-                uint32_t s = src32[x];
-                dst8[x] = SkComputeLuminance((s >> 16) & 0xFF, (s >> 8) & 0xFF, s & 0xFF);
-            }
-        } else {
-            // RGBA
-            for (int x = 0; x < w; ++x) {
-                uint32_t s = src32[x];
-                dst8[x] = SkComputeLuminance(s & 0xFF, (s >> 8) & 0xFF, (s >> 16) & 0xFF);
-            }
-        }
-        src32 = (const uint32_t*)((const char*)src32 + src.rowBytes());
-        dst8 += dst->rowBytes();
-    }
-}
-
-    //////////////////////////////////////////////////////////////////////////////////////////////
-
-    bool equal_pixels(const SkPixmap& a, const SkPixmap& b) {
-        if (a.width() != b.width() ||
-            a.height() != b.height() ||
-            a.colorType() != b.colorType())
-        {
-            return false;
-        }
-
-        for (int y = 0; y < a.height(); ++y) {
-            const char* aptr = (const char*)a.addr(0, y);
-            const char* bptr = (const char*)b.addr(0, y);
-            if (memcmp(aptr, bptr, a.width() * a.info().bytesPerPixel())) {
-                return false;
-            }
-            aptr += a.rowBytes();
-            bptr += b.rowBytes();
-        }
-        return true;
-    }
-
-    bool equal_pixels(const SkBitmap& bm0, const SkBitmap& bm1) {
-        SkPixmap pm0, pm1;
-        return bm0.peekPixels(&pm0) && bm1.peekPixels(&pm1) && equal_pixels(pm0, pm1);
-    }
-
-    bool equal_pixels(const SkImage* a, const SkImage* b) {
-        // ensure that peekPixels will succeed
-        auto imga = a->makeRasterImage();
-        auto imgb = b->makeRasterImage();
-
-        SkPixmap pm0, pm1;
-        return imga->peekPixels(&pm0) && imgb->peekPixels(&pm1) && equal_pixels(pm0, pm1);
-    }
-
-    sk_sp<SkSurface> makeSurface(SkCanvas* canvas, const SkImageInfo& info,
-                                 const SkSurfaceProps* props) {
-        auto surf = canvas->makeSurface(info, props);
-        if (!surf) {
-            surf = SkSurface::MakeRaster(info, props);
-        }
-        return surf;
-    }
-}  // namespace sk_tool_utils
diff --git a/tools/sk_tool_utils.h b/tools/sk_tool_utils.h
deleted file mode 100644
index 196543a..0000000
--- a/tools/sk_tool_utils.h
+++ /dev/null
@@ -1,240 +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.
- */
-
-#ifndef sk_tool_utils_DEFINED
-#define sk_tool_utils_DEFINED
-
-#include "SkColor.h"
-#include "SkData.h"
-#include "SkEncodedImageFormat.h"
-#include "SkFont.h"
-#include "SkFontStyle.h"
-#include "SkFontTypes.h"
-#include "SkImageEncoder.h"
-#include "SkImageInfo.h"
-#include "SkRandom.h"
-#include "SkRect.h"
-#include "SkRefCnt.h"
-#include "SkScalar.h"
-#include "SkStream.h"
-#include "SkTArray.h"
-#include "SkTDArray.h"
-#include "SkTypeface.h"
-#include "SkTypes.h"
-
-class SkBitmap;
-class SkCanvas;
-class SkFontStyle;
-class SkImage;
-class SkPath;
-class SkPixmap;
-class SkRRect;
-class SkShader;
-class SkSurface;
-class SkSurfaceProps;
-class SkTextBlobBuilder;
-class SkTypeface;
-
-namespace sk_tool_utils {
-
-    const char* alphatype_name(SkAlphaType);
-    const char* colortype_name(SkColorType);
-
-    /**
-     * Map opaque colors from 8888 to 565.
-     */
-    SkColor color_to_565(SkColor color);
-
-    /**
-     * Return a color emoji typeface if available.
-     */
-    sk_sp<SkTypeface> emoji_typeface();
-
-    /**
-     * If the platform supports color emoji, return sample text the emoji can render.
-     */
-    const char* emoji_sample_text();
-
-    /**
-     * Returns a string describing the platform font manager, if we're using one, otherwise "".
-     */
-    const char* platform_font_manager();
-
-    /**
-     * Returns a platform-independent text renderer.
-     */
-    sk_sp<SkTypeface> create_portable_typeface(const char* name, SkFontStyle style);
-
-    static inline sk_sp<SkTypeface> create_portable_typeface() {
-        return create_portable_typeface(nullptr, SkFontStyle());
-    }
-
-    void get_text_path(const SkFont&, const void* text, size_t length, SkTextEncoding, SkPath*,
-                       const SkPoint* positions = nullptr);
-
-    /**
-     *  Call writePixels() by using the pixels from bitmap, but with an info that claims
-     *  the pixels are colorType + alphaType
-     */
-    void write_pixels(SkCanvas*, const SkBitmap&, int x, int y, SkColorType, SkAlphaType);
-    void write_pixels(SkSurface*, const SkBitmap&, int x, int y, SkColorType, SkAlphaType);
-
-    /**
-     *  Returns true iff all of the pixels between the two images are identical.
-     *
-     *  If the configs differ, return false.
-     */
-    bool equal_pixels(const SkPixmap&, const SkPixmap&);
-    bool equal_pixels(const SkBitmap&, const SkBitmap&);
-    bool equal_pixels(const SkImage* a, const SkImage* b);
-
-    /** Returns a newly created CheckerboardShader. */
-    sk_sp<SkShader> create_checkerboard_shader(SkColor c1, SkColor c2, int size);
-
-    /** Draw a checkerboard pattern in the current canvas, restricted to
-        the current clip, using SkXfermode::kSrc_Mode. */
-    void draw_checkerboard(SkCanvas* canvas,
-                           SkColor color1,
-                           SkColor color2,
-                           int checkSize);
-
-    /** Make it easier to create a bitmap-based checkerboard */
-    SkBitmap create_checkerboard_bitmap(int w, int h,
-                                        SkColor c1, SkColor c2,
-                                        int checkSize);
-
-    /** A default checkerboard. */
-    inline void draw_checkerboard(SkCanvas* canvas) {
-        sk_tool_utils::draw_checkerboard(canvas, 0xFF999999, 0xFF666666, 8);
-    }
-
-    SkBitmap create_string_bitmap(int w, int h, SkColor c, int x, int y,
-                                  int textSize, const char* str);
-
-    // If the canvas does't make a surface (e.g. recording), make a raster surface
-    sk_sp<SkSurface> makeSurface(SkCanvas*, const SkImageInfo&, const SkSurfaceProps* = nullptr);
-
-    // A helper for inserting a drawtext call into a SkTextBlobBuilder
-    void add_to_text_blob_w_len(SkTextBlobBuilder*, const char* text, size_t len, SkTextEncoding,
-                                const SkFont&, SkScalar x, SkScalar y);
-
-    void add_to_text_blob(SkTextBlobBuilder*, const char* text, const SkFont&,
-                          SkScalar x, SkScalar y);
-
-    // Constructs a star by walking a 'numPts'-sided regular polygon with even/odd fill:
-    //
-    //   moveTo(pts[0]);
-    //   lineTo(pts[step % numPts]);
-    //   ...
-    //   lineTo(pts[(step * (N - 1)) % numPts]);
-    //
-    // numPts=5, step=2 will produce a classic five-point star.
-    //
-    // numPts and step must be co-prime.
-    SkPath make_star(const SkRect& bounds, int numPts = 5, int step = 2);
-
-    void create_hemi_normal_map(SkBitmap* bm, const SkIRect& dst);
-
-    void create_frustum_normal_map(SkBitmap* bm, const SkIRect& dst);
-
-    void create_tetra_normal_map(SkBitmap* bm, const SkIRect& dst);
-
-    void make_big_path(SkPath& path);
-
-    // A helper object to test the topological sorting code (TopoSortBench.cpp & TopoSortTest.cpp)
-    class TopoTestNode : public SkRefCnt {
-    public:
-        TopoTestNode(int id) : fID(id), fOutputPos(-1), fTempMark(false) { }
-
-        void dependsOn(TopoTestNode* src) {
-            *fDependencies.append() = src;
-        }
-
-        int id() const { return fID; }
-        void reset() { fOutputPos = -1; }
-
-        int outputPos() const { return fOutputPos; }
-
-        // check that the topological sort is valid for this node
-        bool check() {
-            if (-1 == fOutputPos) {
-                return false;
-            }
-
-            for (int i = 0; i < fDependencies.count(); ++i) {
-                if (-1 == fDependencies[i]->outputPos()) {
-                    return false;
-                }
-                // This node should've been output after all the nodes on which it depends
-                if (fOutputPos < fDependencies[i]->outputPos()) {
-                    return false;
-                }
-            }
-
-            return true;
-        }
-
-        // The following 7 methods are needed by the topological sort
-        static void SetTempMark(TopoTestNode* node) { node->fTempMark = true; }
-        static void ResetTempMark(TopoTestNode* node) { node->fTempMark = false; }
-        static bool IsTempMarked(TopoTestNode* node) { return node->fTempMark; }
-        static void Output(TopoTestNode* node, int outputPos) {
-            SkASSERT(-1 != outputPos);
-            node->fOutputPos = outputPos;
-        }
-        static bool WasOutput(TopoTestNode* node) { return (-1 != node->fOutputPos); }
-        static int NumDependencies(TopoTestNode* node) { return node->fDependencies.count(); }
-        static TopoTestNode* Dependency(TopoTestNode* node, int index) {
-            return node->fDependencies[index];
-        }
-
-        // Helper functions for TopoSortBench & TopoSortTest
-        static void AllocNodes(SkTArray<sk_sp<sk_tool_utils::TopoTestNode>>* graph, int num) {
-            graph->reserve(num);
-
-            for (int i = 0; i < num; ++i) {
-                graph->push_back(sk_sp<TopoTestNode>(new TopoTestNode(i)));
-            }
-        }
-
-#ifdef SK_DEBUG
-        static void Print(const SkTArray<TopoTestNode*>& graph) {
-            for (int i = 0; i < graph.count(); ++i) {
-                SkDebugf("%d, ", graph[i]->id());
-            }
-            SkDebugf("\n");
-        }
-#endif
-
-        // randomize the array
-        static void Shuffle(SkTArray<sk_sp<TopoTestNode>>* graph, SkRandom* rand) {
-            for (int i = graph->count()-1; i > 0; --i) {
-                int swap = rand->nextU() % (i+1);
-
-                (*graph)[i].swap((*graph)[swap]);
-            }
-        }
-
-    private:
-        int  fID;
-        int  fOutputPos;
-        bool fTempMark;
-
-        SkTDArray<TopoTestNode*> fDependencies;
-    };
-
-    template <typename T>
-    inline bool EncodeImageToFile(const char* path, const T& src, SkEncodedImageFormat f, int q) {
-        SkFILEWStream file(path);
-        return file.isValid() && SkEncodeImage(&file, src, f, q);
-    }
-
-    bool copy_to(SkBitmap* dst, SkColorType dstCT, const SkBitmap& src);
-    void copy_to_g8(SkBitmap* dst, const SkBitmap& src);
-}  // namespace sk_tool_utils
-
-#endif  // sk_tool_utils_DEFINED
diff --git a/tools/skdiff/skdiff_utils.cpp b/tools/skdiff/skdiff_utils.cpp
index 18682c0..342af99 100644
--- a/tools/skdiff/skdiff_utils.cpp
+++ b/tools/skdiff/skdiff_utils.cpp
@@ -4,15 +4,15 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
-#include "skdiff.h"
 #include "skdiff_utils.h"
-#include "sk_tool_utils.h"
 #include "SkBitmap.h"
 #include "SkCodec.h"
 #include "SkData.h"
 #include "SkImageEncoder.h"
 #include "SkStream.h"
 #include "SkTypes.h"
+#include "ToolUtils.h"
+#include "skdiff.h"
 
 #include <memory>
 
@@ -92,10 +92,9 @@
 
 bool write_bitmap(const SkString& path, const SkBitmap& bitmap) {
     SkBitmap copy;
-    sk_tool_utils::copy_to(&copy, kN32_SkColorType, bitmap);
+    ToolUtils::copy_to(&copy, kN32_SkColorType, bitmap);
     force_all_opaque(copy);
-    return sk_tool_utils::EncodeImageToFile(path.c_str(), copy,
-                                      SkEncodedImageFormat::kPNG, 100);
+    return ToolUtils::EncodeImageToFile(path.c_str(), copy, SkEncodedImageFormat::kPNG, 100);
 }
 
 /// Return a copy of the "input" string, within which we have replaced all instances
diff --git a/tools/skhello.cpp b/tools/skhello.cpp
index ac2ea56..0f39399 100644
--- a/tools/skhello.cpp
+++ b/tools/skhello.cpp
@@ -5,18 +5,18 @@
  * found in the LICENSE file.
  */
 
+#include "CommandLineFlags.h"
 #include "SkCanvas.h"
-#include "SkCommandLineFlags.h"
 #include "SkData.h"
 #include "SkDocument.h"
 #include "SkGraphics.h"
-#include "SkSurface.h"
 #include "SkImage.h"
 #include "SkStream.h"
 #include "SkString.h"
+#include "SkSurface.h"
 
-DEFINE_string2(outFile, o, "skhello", "The filename to write the image.");
-DEFINE_string2(text, t, "Hello", "The string to write.");
+static DEFINE_string2(outFile, o, "skhello", "The filename to write the image.");
+static DEFINE_string2(text, t, "Hello", "The string to write.");
 
 static void doDraw(SkCanvas* canvas, const SkPaint& paint, const char text[]) {
     SkRect bounds = canvas->getLocalClipBounds();
@@ -55,8 +55,8 @@
 }
 
 int main(int argc, char** argv) {
-    SkCommandLineFlags::SetUsage("");
-    SkCommandLineFlags::Parse(argc, argv);
+    CommandLineFlags::SetUsage("");
+    CommandLineFlags::Parse(argc, argv);
 
     SkAutoGraphics ag;
     SkString path("skhello");
diff --git a/tools/skiaserve/Request.cpp b/tools/skiaserve/Request.cpp
index d1286be..d73a29c 100644
--- a/tools/skiaserve/Request.cpp
+++ b/tools/skiaserve/Request.cpp
@@ -9,7 +9,7 @@
 
 #include "SkJSONWriter.h"
 #include "SkPictureRecorder.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 using namespace sk_gpu_test;
 
@@ -44,7 +44,7 @@
 
     // write to an opaque png (black background)
     SkDynamicMemoryWStream buffer;
-    SkDrawCommand::WritePNG(bmp, buffer);
+    DrawCommand::WritePNG(bmp, buffer);
     return buffer.detachAsData();
 }
 
@@ -204,7 +204,7 @@
 
     // pour picture into debug canvas
     SkIRect bounds = this->getBounds();
-    fDebugCanvas.reset(new SkDebugCanvas(bounds.width(), bounds.height()));
+    fDebugCanvas.reset(new DebugCanvas(bounds.width(), bounds.height()));
     fDebugCanvas->drawPicture(fPicture);
 
     // for some reason we need to 'flush' the debug canvas by drawing all of the ops
@@ -257,8 +257,10 @@
     SkIRect clip = fDebugCanvas->getCurrentClip();
 
     writer.beginObject(); // root
-    writer.appendName("ViewMatrix"); SkDrawCommand::MakeJsonMatrix(writer, vm);
-    writer.appendName("ClipRect"); SkDrawCommand::MakeJsonIRect(writer, clip);
+    writer.appendName("ViewMatrix");
+    DrawCommand::MakeJsonMatrix(writer, vm);
+    writer.appendName("ClipRect");
+    DrawCommand::MakeJsonIRect(writer, clip);
     writer.endObject(); // root
 
     // TODO: Old code explicitly avoided the null terminator in the returned data. Important?
diff --git a/tools/skiaserve/Request.h b/tools/skiaserve/Request.h
index 6c970be..3775af1 100644
--- a/tools/skiaserve/Request.h
+++ b/tools/skiaserve/Request.h
@@ -12,7 +12,7 @@
 
 #include "GrContextFactory.h"
 
-#include "SkDebugCanvas.h"
+#include "DebugCanvas.h"
 #include "SkPicture.h"
 #include "SkStream.h"
 #include "SkSurface.h"
@@ -60,7 +60,7 @@
     SkColor getPixel(int x, int y);
 
     UploadContext* fUploadContext;
-    std::unique_ptr<SkDebugCanvas> fDebugCanvas;
+    std::unique_ptr<DebugCanvas> fDebugCanvas;
     UrlDataManager fUrlDataManager;
 
 private:
diff --git a/tools/skiaserve/Response.cpp b/tools/skiaserve/Response.cpp
index 26b137d..f200828 100644
--- a/tools/skiaserve/Response.cpp
+++ b/tools/skiaserve/Response.cpp
@@ -9,13 +9,13 @@
 
 #include "Request.h"
 
-#include "SkCommandLineFlags.h"
+#include "CommandLineFlags.h"
 #include "SkData.h"
 #include "SkString.h"
 
 #include "microhttpd.h"
 
-DEFINE_string(source, "https://debugger-assets.skia.org", "Where to load the web UI from.");
+static DEFINE_string(source, "https://debugger-assets.skia.org", "Where to load the web UI from.");
 
 static SkString generate_template(SkString source) {
     SkString debuggerTemplate;
diff --git a/tools/skiaserve/skiaserve.cpp b/tools/skiaserve/skiaserve.cpp
index 1e265c7..8292777 100644
--- a/tools/skiaserve/skiaserve.cpp
+++ b/tools/skiaserve/skiaserve.cpp
@@ -8,7 +8,7 @@
 #include "Request.h"
 #include "Response.h"
 
-#include "SkCommandLineFlags.h"
+#include "CommandLineFlags.h"
 #include "SkGraphics.h"
 
 #include "urlhandlers/UrlHandler.h"
@@ -24,9 +24,9 @@
 
 using namespace Response;
 
-DEFINE_int32(port, 8888, "The port to listen on.");
-DEFINE_string(address, "127.0.0.1", "The address to bind to.");
-DEFINE_bool(hosted, false, "Running in hosted mode on debugger.skia.org.");
+static DEFINE_int(port, 8888, "The port to listen on.");
+static DEFINE_string(address, "127.0.0.1", "The address to bind to.");
+static DEFINE_bool(hosted, false, "Running in hosted mode on debugger.skia.org.");
 
 class UrlManager {
 public:
@@ -133,7 +133,7 @@
 
 #if !defined SK_BUILD_FOR_IOS
 int main(int argc, char** argv) {
-    SkCommandLineFlags::Parse(argc, argv);
+    CommandLineFlags::Parse(argc, argv);
     return skiaserve_main();
 }
 #endif
diff --git a/tools/skiaserve/urlhandlers/BreakHandler.cpp b/tools/skiaserve/urlhandlers/BreakHandler.cpp
index efb98fa..2d6a547 100644
--- a/tools/skiaserve/urlhandlers/BreakHandler.cpp
+++ b/tools/skiaserve/urlhandlers/BreakHandler.cpp
@@ -52,7 +52,8 @@
     SkJSONWriter writer(&stream, SkJSONWriter::Mode::kFast);
     writer.beginObject(); // root
 
-    writer.appendName("startColor"); SkDrawCommand::MakeJsonColor(writer, target);
+    writer.appendName("startColor");
+    DrawCommand::MakeJsonColor(writer, target);
 
     bool changed = false;
     for (int i = n + 1; i < n + count; ++i) {
@@ -66,14 +67,16 @@
         request->fDebugCanvas->getDrawCommandAt(index)->execute(canvas);
         SkColor current = request->getPixel(x, y);
         if (current != target) {
-            writer.appendName("endColor"); SkDrawCommand::MakeJsonColor(writer, current);
+            writer.appendName("endColor");
+            DrawCommand::MakeJsonColor(writer, current);
             writer.appendS32("endOp", index);
             changed = true;
             break;
         }
     }
     if (!changed) {
-        writer.appendName("endColor"); SkDrawCommand::MakeJsonColor(writer, target);
+        writer.appendName("endColor");
+        DrawCommand::MakeJsonColor(writer, target);
         writer.appendS32("endOp", n);
     }
     canvas->restoreToCount(saveCount);
diff --git a/tools/skp/generate_page_set.py b/tools/skp/generate_page_set.py
new file mode 100644
index 0000000..3ca71ba
--- /dev/null
+++ b/tools/skp/generate_page_set.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+# Copyright (c) 2019 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.
+
+"""Script that generates a page_set for the webpages_playback.py script."""
+
+import jinja2
+import os
+
+
+PAGE_SET_TEMPLATE = 'page_set_template'
+PAGE_SET_DIR = 'page_sets'
+
+
+def main():
+  created_page_sets = []
+  while True:
+    user_agent = raw_input('user agent? (mobile/desktop/tablet): ')
+    url_name = raw_input('URL name? (eg: google): ')
+    url = raw_input('URL? (eg: http://www.google.com): ')
+    comment = raw_input('Reason for adding the URL? (eg: go/skia-skps-3-2019): ')
+
+    with open(PAGE_SET_TEMPLATE) as f:
+      t = jinja2.Template(f.read())
+    subs = {
+      'user_agent': user_agent,
+      'url_name': url_name,
+      'url': url,
+      'comment': comment,
+    }
+
+    page_set_name = 'skia_%s_%s.py' % (url_name, user_agent)
+    page_set_path = os.path.join(PAGE_SET_DIR, page_set_name)
+    with open(page_set_path, 'w') as f:
+      f.write(t.render(**subs))
+    created_page_sets.append(page_set_path)
+    print '\nPage set has been created in %s\n\n' % page_set_path
+
+    keep_going = raw_input('Do you have more page sets to create? (y/n)')
+    if keep_going != 'y':
+      break
+
+  print '\n\nSummarizing all created page sets:'
+  for page_set_path in created_page_sets:
+    print '* %s' % page_set_path
+
+
+if __name__ == '__main__':
+  main()
diff --git a/tools/skp/page_set_template b/tools/skp/page_set_template
new file mode 100644
index 0000000..d0d01cf
--- /dev/null
+++ b/tools/skp/page_set_template
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class Skia{{ user_agent|capitalize }}Page(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(Skia{{ user_agent|capitalize }}Page, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.Shared{{ user_agent|capitalize }}PageState)
+    self.archive_data_file = 'data/skia_{{url_name}}_{{user_agent}}.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class Skia{{ url_name|capitalize }}{{ user_agent|capitalize }}PageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(Skia{{ url_name|capitalize }}{{ user_agent|capitalize }}PageSet, self).__init__(
+      archive_data_file='data/skia_{{url_name}}_{{user_agent}}.json')
+
+    urls_list = [
+      # {{comment}}
+      '{{url}}',
+    ]
+
+    for url in urls_list:
+      self.AddStory(Skia{{ user_agent|capitalize }}Page(url, self))
diff --git a/tools/skp/page_sets/skia_amazon_mobile.py b/tools/skp/page_sets/skia_amazon_mobile.py
new file mode 100644
index 0000000..814bb8a
--- /dev/null
+++ b/tools/skp/page_sets/skia_amazon_mobile.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaBuildbotMobilePage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaBuildbotMobilePage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedMobilePageState)
+    self.archive_data_file = 'data/skia_amazon_mobile.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaAmazonMobilePageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaAmazonMobilePageSet, self).__init__(
+      archive_data_file='data/skia_amazon_mobile.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'https://www.amazon.com/s?k=nicolas+cage&ref=is_box_',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaBuildbotMobilePage(url, self))
diff --git a/tools/skp/page_sets/skia_baidu_mobile.py b/tools/skp/page_sets/skia_baidu_mobile.py
new file mode 100644
index 0000000..58dc713
--- /dev/null
+++ b/tools/skp/page_sets/skia_baidu_mobile.py
@@ -0,0 +1,41 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaMobilePage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaMobilePage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedMobilePageState)
+    self.archive_data_file = 'data/skia_baidu_mobile.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaBaiduMobilePageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaBaiduMobilePageSet, self).__init__(
+      archive_data_file='data/skia_baidu_mobile.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      ('http://www.baidu.com/s?wd=barack+obama&rsv_bp=0&rsv_spt=3&rsv_sug3=9'
+       '&rsv_sug=0&rsv_sug4=3824&rsv_sug1=3&inputT=4920'),
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaMobilePage(url, self))
diff --git a/tools/skp/page_sets/skia_booking_mobile.py b/tools/skp/page_sets/skia_booking_mobile.py
new file mode 100644
index 0000000..d158bcd
--- /dev/null
+++ b/tools/skp/page_sets/skia_booking_mobile.py
@@ -0,0 +1,41 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaMobilePage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaMobilePage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedMobilePageState)
+    self.archive_data_file = 'data/skia_booking_mobile.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(30)
+
+
+class SkiaBookingMobilePageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaBookingMobilePageSet, self).__init__(
+      archive_data_file='data/skia_booking_mobile.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      ('http://www.booking.com/searchresults.html?src=searchresults'
+       '&latitude=65.0500&longitude=25.4667'),
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaMobilePage(url, self))
diff --git a/tools/skp/page_sets/skia_capitalvolkswagen_mobile.py b/tools/skp/page_sets/skia_capitalvolkswagen_mobile.py
new file mode 100644
index 0000000..3db39a4
--- /dev/null
+++ b/tools/skp/page_sets/skia_capitalvolkswagen_mobile.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaMobilePage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaMobilePage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedMobilePageState)
+    self.archive_data_file = 'data/skia_capitalvolkswagen_mobile.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(30)
+
+
+class SkiaCapitalvolkswagenMobilePageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaCapitalvolkswagenMobilePageSet, self).__init__(
+      archive_data_file='data/skia_capitalvolkswagen_mobile.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      ('https://www.capitolvolkswagen.com/new-vehicles/'),
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaMobilePage(url, self))
diff --git a/tools/skp/page_sets/skia_chalkboard_desktop.py b/tools/skp/page_sets/skia_chalkboard_desktop.py
index 77d249d..d5ebd3d 100644
--- a/tools/skp/page_sets/skia_chalkboard_desktop.py
+++ b/tools/skp/page_sets/skia_chalkboard_desktop.py
@@ -30,8 +30,8 @@
 
     urls_list = [
       # Why: from fmalita
-      ('http://ie.microsoft.com/testdrive/Performance/Chalkboard/Images/'
-       'Chalkboard.svg'),
+      ('https://testdrive-archive.azurewebsites.net/performance/chalkboard/'
+       'Images/Chalkboard.svg'),
     ]
 
     for url in urls_list:
diff --git a/tools/skp/page_sets/skia_cnn_desktop.py b/tools/skp/page_sets/skia_cnn_desktop.py
new file mode 100644
index 0000000..06cd8c3
--- /dev/null
+++ b/tools/skp/page_sets/skia_cnn_desktop.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaDesktopPage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaDesktopPage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedDesktopPageState)
+    self.archive_data_file = 'data/skia_cnn_desktop.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaCnnDesktopPageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaCnnDesktopPageSet, self).__init__(
+      archive_data_file='data/skia_cnn_desktop.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'http://www.cnn.com',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaDesktopPage(url, self))
\ No newline at end of file
diff --git a/tools/skp/page_sets/skia_cnn_mobile.py b/tools/skp/page_sets/skia_cnn_mobile.py
new file mode 100644
index 0000000..63d2f17
--- /dev/null
+++ b/tools/skp/page_sets/skia_cnn_mobile.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaMobilePage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaMobilePage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedMobilePageState)
+    self.archive_data_file = 'data/skia_cnn_mobile.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaCnnMobilePageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaCnnMobilePageSet, self).__init__(
+      archive_data_file='data/skia_cnn_mobile.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'http://www.cnn.com',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaMobilePage(url, self))
diff --git a/tools/skp/page_sets/skia_cnnarticle_mobile.py b/tools/skp/page_sets/skia_cnnarticle_mobile.py
new file mode 100644
index 0000000..5499fe4
--- /dev/null
+++ b/tools/skp/page_sets/skia_cnnarticle_mobile.py
@@ -0,0 +1,41 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaMobilePage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaMobilePage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedMobilePageState)
+    self.archive_data_file = 'data/skia_cnnarticle_mobile.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaCnnarticleMobilePageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaCnnarticleMobilePageSet, self).__init__(
+      archive_data_file='data/skia_cnnarticle_mobile.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      ('https://www.cnn.com/2012/10/03/politics/michelle-obama-debate/'
+       'index.html'),
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaMobilePage(url, self))
diff --git a/tools/skp/page_sets/skia_deviantart_mobile.py b/tools/skp/page_sets/skia_deviantart_mobile.py
new file mode 100644
index 0000000..326822d
--- /dev/null
+++ b/tools/skp/page_sets/skia_deviantart_mobile.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaMobilePage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaMobilePage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedMobilePageState)
+    self.archive_data_file = 'data/skia_deviantart_mobile.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(45)
+
+
+class SkiaDeviantartMobilePageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaDeviantartMobilePageSet, self).__init__(
+      archive_data_file='data/skia_deviantart_mobile.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'https://www.deviantart.com/whats-hot/',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaMobilePage(url, self))
diff --git a/tools/skp/page_sets/skia_digg_nexus10.py b/tools/skp/page_sets/skia_digg_nexus10.py
deleted file mode 100644
index bbf4bf5b..0000000
--- a/tools/skp/page_sets/skia_digg_nexus10.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright 2014 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.
-# pylint: disable=W0401,W0614
-
-
-from telemetry import story
-from telemetry.page import page as page_module
-from telemetry.page import shared_page_state
-
-
-class SkiaBuildbotDesktopPage(page_module.Page):
-
-  def __init__(self, url, page_set):
-    super(SkiaBuildbotDesktopPage, self).__init__(
-        url=url,
-        name=url,
-        page_set=page_set,
-        shared_page_state_class=shared_page_state.Shared10InchTabletPageState)
-    self.archive_data_file = 'data/skia_digg_nexus10.json'
-
-  def RunNavigateSteps(self, action_runner):
-    action_runner.Navigate(self.url)
-    action_runner.Wait(5)
-
-class SkiaDiggNexus10PageSet(story.StorySet):
-
-  """ Pages designed to represent the median, not highly optimized web """
-
-  def __init__(self):
-    super(SkiaDiggNexus10PageSet, self).__init__(
-      archive_data_file='data/skia_digg_nexus10.json')
-
-    urls_list = [
-      # Why: from Clank CY.
-      'http://digg.com/',
-    ]
-
-    for url in urls_list:
-      self.AddStory(SkiaBuildbotDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_digg_tablet.py b/tools/skp/page_sets/skia_digg_tablet.py
new file mode 100644
index 0000000..fe64ceb
--- /dev/null
+++ b/tools/skp/page_sets/skia_digg_tablet.py
@@ -0,0 +1,39 @@
+# Copyright 2014 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaBuildbotDesktopPage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaBuildbotDesktopPage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedTabletPageState)
+    self.archive_data_file = 'data/skia_digg_tablet.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(30)
+
+class SkiaDiggTabletPageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaDiggTabletPageSet, self).__init__(
+      archive_data_file='data/skia_digg_tablet.json')
+
+    urls_list = [
+      # Why: from Clank CY.
+      'http://digg.com/',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaBuildbotDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_ebay_desktop.py b/tools/skp/page_sets/skia_ebay_desktop.py
new file mode 100644
index 0000000..80f41cb
--- /dev/null
+++ b/tools/skp/page_sets/skia_ebay_desktop.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaDesktopPage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaDesktopPage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedDesktopPageState)
+    self.archive_data_file = 'data/skia_ebay_desktop.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaEbayDesktopPageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaEbayDesktopPageSet, self).__init__(
+      archive_data_file='data/skia_ebay_desktop.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'http://www.ebay.com',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaDesktopPage(url, self))
\ No newline at end of file
diff --git a/tools/skp/page_sets/skia_facebook_desktop.py b/tools/skp/page_sets/skia_facebook_desktop.py
new file mode 100644
index 0000000..7fcb17d
--- /dev/null
+++ b/tools/skp/page_sets/skia_facebook_desktop.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaDesktopPage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaDesktopPage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedDesktopPageState)
+    self.archive_data_file = 'data/skia_facebook_desktop.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaFacebookDesktopPageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaFacebookDesktopPageSet, self).__init__(
+      archive_data_file='data/skia_facebook_desktop.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'https://www.facebook.com/barackobama',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaDesktopPage(url, self))
\ No newline at end of file
diff --git a/tools/skp/page_sets/skia_facebook_mobile.py b/tools/skp/page_sets/skia_facebook_mobile.py
new file mode 100644
index 0000000..77a63ba
--- /dev/null
+++ b/tools/skp/page_sets/skia_facebook_mobile.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaMobilePage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaMobilePage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedMobilePageState)
+    self.archive_data_file = 'data/skia_facebook_mobile.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(30)
+
+
+class SkiaFacebookMobilePageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaFacebookMobilePageSet, self).__init__(
+      archive_data_file='data/skia_facebook_mobile.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'https://facebook.com/barackobama',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaMobilePage(url, self))
diff --git a/tools/skp/page_sets/skia_forecastio_mobile.py b/tools/skp/page_sets/skia_forecastio_mobile.py
new file mode 100644
index 0000000..03bd4dd
--- /dev/null
+++ b/tools/skp/page_sets/skia_forecastio_mobile.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaMobilePage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaMobilePage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedMobilePageState)
+    self.archive_data_file = 'data/skia_forecastio_mobile.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaForecastioMobilePageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaForecastioMobilePageSet, self).__init__(
+      archive_data_file='data/skia_forecastio_mobile.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'http://forecast.io',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaMobilePage(url, self))
\ No newline at end of file
diff --git a/tools/skp/page_sets/skia_gamedeksiam_nexus10.py b/tools/skp/page_sets/skia_gamedeksiam_nexus10.py
deleted file mode 100644
index 638b752..0000000
--- a/tools/skp/page_sets/skia_gamedeksiam_nexus10.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2014 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.
-# pylint: disable=W0401,W0614
-
-
-from telemetry import story
-from telemetry.page import page as page_module
-from telemetry.page import shared_page_state
-
-
-class SkiaBuildbotDesktopPage(page_module.Page):
-
-  def __init__(self, url, page_set):
-    super(SkiaBuildbotDesktopPage, self).__init__(
-        url=url,
-        name=url,
-        page_set=page_set,
-        shared_page_state_class=shared_page_state.Shared10InchTabletPageState)
-    self.archive_data_file = 'data/skia_gamedeksiam_nexus10.json'
-
-  def RunNavigateSteps(self, action_runner):
-    action_runner.Navigate(self.url)
-    action_runner.Wait(5)
-
-
-class SkiaGamedeksiamNexus10PageSet(story.StorySet):
-
-  """ Pages designed to represent the median, not highly optimized web """
-
-  def __init__(self):
-    super(SkiaGamedeksiamNexus10PageSet, self).__init__(
-      archive_data_file='data/skia_gamedeksiam_nexus10.json')
-
-    urls_list = [
-      # Why: from Tom W's list.
-      'http://game.deksiam.in.th/',
-    ]
-
-    for url in urls_list:
-      self.AddStory(SkiaBuildbotDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_gmail_desktop.py b/tools/skp/page_sets/skia_gmail_desktop.py
new file mode 100644
index 0000000..2e97800
--- /dev/null
+++ b/tools/skp/page_sets/skia_gmail_desktop.py
@@ -0,0 +1,52 @@
+# Copyright 2014 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.
+# pylint: disable=W0401,W0614
+
+import os
+
+from page_sets.login_helpers import google_login
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+from telemetry.util import wpr_modes
+
+
+class SkiaBuildbotDesktopPage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaBuildbotDesktopPage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedDesktopPageState)
+    self.archive_data_file = 'data/skia_gmail_desktop.json'
+
+  def RunSmoothness(self, action_runner):
+    action_runner.ScrollElement()
+
+  def RunNavigateSteps(self, action_runner):
+    if self.wpr_mode != wpr_modes.WPR_REPLAY:
+      credentials_path = os.path.join(
+          os.path.dirname(os.path.abspath(__file__)), 'data/credentials.json')
+      google_login.BaseLoginGoogle(action_runner, 'google', credentials_path)
+      action_runner.Wait(10)
+    action_runner.Navigate(self.url)
+    action_runner.Wait(10)
+
+
+class SkiaGmailDesktopPageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaGmailDesktopPageSet, self).__init__(
+      archive_data_file='data/skia_gmail_desktop.json')
+
+    urls_list = [
+      # Why: productivity, top google properties, long email .
+      'https://mail.google.com/mail/?shva=1#inbox/13ba91194d0b8a2e',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaBuildbotDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_gmail_nexus10.py b/tools/skp/page_sets/skia_gmail_nexus10.py
deleted file mode 100644
index fdc06ac..0000000
--- a/tools/skp/page_sets/skia_gmail_nexus10.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright 2014 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.
-# pylint: disable=W0401,W0614
-
-
-from telemetry import story
-from telemetry.page import page as page_module
-from telemetry.page import shared_page_state
-
-
-class SkiaBuildbotDesktopPage(page_module.Page):
-
-  def __init__(self, url, page_set):
-    super(SkiaBuildbotDesktopPage, self).__init__(
-        url=url,
-        name=url,
-        page_set=page_set,
-        shared_page_state_class=shared_page_state.Shared10InchTabletPageState)
-    self.archive_data_file = 'data/skia_gmail_nexus10.json'
-
-  def RunSmoothness(self, action_runner):
-    action_runner.ScrollElement()
-
-  def RunNavigateSteps(self, action_runner):
-    action_runner.Navigate(self.url)
-    action_runner.Wait(10)
-
-
-class SkiaGmailNexus10PageSet(story.StorySet):
-
-  """ Pages designed to represent the median, not highly optimized web """
-
-  def __init__(self):
-    super(SkiaGmailNexus10PageSet, self).__init__(
-      archive_data_file='data/skia_gmail_nexus10.json')
-
-    urls_list = [
-      # Why: productivity, top google properties
-      'https://mail.google.com/mail/',
-    ]
-
-    for url in urls_list:
-      self.AddStory(SkiaBuildbotDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_gmailthread_desktop.py b/tools/skp/page_sets/skia_gmailthread_desktop.py
deleted file mode 100644
index 83b3eed..0000000
--- a/tools/skp/page_sets/skia_gmailthread_desktop.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright 2014 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.
-# pylint: disable=W0401,W0614
-
-from page_sets.login_helpers import google_login
-
-from telemetry import story
-from telemetry.page import page as page_module
-from telemetry.page import shared_page_state
-
-
-class SkiaBuildbotDesktopPage(page_module.Page):
-
-  def __init__(self, url, page_set):
-    super(SkiaBuildbotDesktopPage, self).__init__(
-        url=url,
-        name=url,
-        page_set=page_set,
-        shared_page_state_class=shared_page_state.SharedDesktopPageState)
-    self.archive_data_file = 'data/skia_gmailthread_desktop.json'
-
-  def RunSmoothness(self, action_runner):
-    action_runner.ScrollElement()
-
-  def RunNavigateSteps(self, action_runner):
-    action_runner.Navigate(self.url)
-    action_runner.Wait(15)
-
-
-class SkiaGmailthreadDesktopPageSet(story.StorySet):
-
-  """ Pages designed to represent the median, not highly optimized web """
-
-  def __init__(self):
-    super(SkiaGmailthreadDesktopPageSet, self).__init__(
-      archive_data_file='data/skia_gmailthread_desktop.json')
-
-    urls_list = [
-      # Why: productivity, top google properties, long email thread.
-      'https://mail.google.com/mail/?shva=1#inbox/13ba91194d0b8a2e',
-    ]
-
-    for url in urls_list:
-      self.AddStory(SkiaBuildbotDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_googlecalendar_desktop.py b/tools/skp/page_sets/skia_googlecalendar_desktop.py
new file mode 100644
index 0000000..a21ab7c
--- /dev/null
+++ b/tools/skp/page_sets/skia_googlecalendar_desktop.py
@@ -0,0 +1,49 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+import os
+
+from page_sets.login_helpers import google_login
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+from telemetry.util import wpr_modes
+
+
+class SkiaDesktopPage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaDesktopPage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedDesktopPageState)
+    self.archive_data_file = 'data/skia_googlecalendar_desktop.json'
+
+  def RunNavigateSteps(self, action_runner):
+    if self.wpr_mode != wpr_modes.WPR_REPLAY:
+      credentials_path=os.path.join(os.path.dirname(os.path.abspath(__file__)),
+                                    'data/credentials.json')
+      google_login.BaseLoginGoogle(action_runner, 'google', credentials_path)
+      action_runner.Wait(15)
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaGooglecalendarDesktopPageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaGooglecalendarDesktopPageSet, self).__init__(
+      archive_data_file='data/skia_googlecalendar_desktop.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'https://www.google.com/calendar/',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_googledocs_desktop.py b/tools/skp/page_sets/skia_googledocs_desktop.py
new file mode 100644
index 0000000..24f2ee2
--- /dev/null
+++ b/tools/skp/page_sets/skia_googledocs_desktop.py
@@ -0,0 +1,50 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+import os
+
+from page_sets.login_helpers import google_login
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+from telemetry.util import wpr_modes
+
+
+class SkiaDesktopPage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaDesktopPage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedDesktopPageState)
+    self.archive_data_file = 'data/skia_googledocs_desktop.json'
+
+  def RunNavigateSteps(self, action_runner):
+    if self.wpr_mode != wpr_modes.WPR_REPLAY:
+      credentials_path=os.path.join(os.path.dirname(os.path.abspath(__file__)),
+                                    'data/credentials.json')
+      google_login.BaseLoginGoogle(action_runner, 'google', credentials_path)
+      action_runner.Wait(15)
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaGoogledocsDesktopPageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaGoogledocsDesktopPageSet, self).__init__(
+      archive_data_file='data/skia_googledocs_desktop.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      ('https://docs.google.com/document/d/'
+       '1X-IKNjtEnx-WW5JIKRLsyhz5sbsat3mfTpAPUSX3_s4/view'),
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_googlehome_desktop.py b/tools/skp/page_sets/skia_googlehome_desktop.py
deleted file mode 100644
index 041a315..0000000
--- a/tools/skp/page_sets/skia_googlehome_desktop.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2014 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.
-# pylint: disable=W0401,W0614
-
-
-from telemetry import story
-from telemetry.page import page as page_module
-from telemetry.page import shared_page_state
-
-
-class SkiaBuildbotDesktopPage(page_module.Page):
-
-  def __init__(self, url, page_set):
-    super(SkiaBuildbotDesktopPage, self).__init__(
-        url=url,
-        name=url,
-        page_set=page_set,
-        shared_page_state_class=shared_page_state.SharedDesktopPageState)
-    self.archive_data_file = 'data/skia_googlehome_desktop.json'
-
-  def RunNavigateSteps(self, action_runner):
-    action_runner.Navigate(self.url)
-    action_runner.Wait(5)
-
-
-class SkiaGooglehomeDesktopPageSet(story.StorySet):
-
-  """ Pages designed to represent the median, not highly optimized web """
-
-  def __init__(self):
-    super(SkiaGooglehomeDesktopPageSet, self).__init__(
-      archive_data_file='data/skia_googlehome_desktop.json')
-
-    urls_list = [
-      # Why: top google property; a google tab is often open
-      'http://www.google.com/',
-    ]
-
-    for url in urls_list:
-      self.AddStory(SkiaBuildbotDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_googleimagesearch_desktop.py b/tools/skp/page_sets/skia_googleimagesearch_desktop.py
new file mode 100644
index 0000000..06599ee
--- /dev/null
+++ b/tools/skp/page_sets/skia_googleimagesearch_desktop.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaDesktopPage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaDesktopPage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedDesktopPageState)
+    self.archive_data_file = 'data/skia_googleimagesearch_desktop.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaGoogleimagesearchDesktopPageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaGoogleimagesearchDesktopPageSet, self).__init__(
+      archive_data_file='data/skia_googleimagesearch_desktop.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'https://www.google.com/search?q=cats&tbm=isch',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaDesktopPage(url, self))
\ No newline at end of file
diff --git a/tools/skp/page_sets/skia_googlenews_mobile.py b/tools/skp/page_sets/skia_googlenews_mobile.py
new file mode 100644
index 0000000..23956e6
--- /dev/null
+++ b/tools/skp/page_sets/skia_googlenews_mobile.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaMobilePage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaMobilePage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedMobilePageState)
+    self.archive_data_file = 'data/skia_googlenews_mobile.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaGooglenewsMobilePageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaGooglenewsMobilePageSet, self).__init__(
+      archive_data_file='data/skia_googlenews_mobile.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'https://news.google.com/',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaMobilePage(url, self))
\ No newline at end of file
diff --git a/tools/skp/page_sets/skia_googlesearch_desktop.py b/tools/skp/page_sets/skia_googlesearch_desktop.py
new file mode 100644
index 0000000..1c828fa
--- /dev/null
+++ b/tools/skp/page_sets/skia_googlesearch_desktop.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaDesktopPage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaDesktopPage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedDesktopPageState)
+    self.archive_data_file = 'data/skia_googlesearch_desktop.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaGooglesearchDesktopPageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaGooglesearchDesktopPageSet, self).__init__(
+      archive_data_file='data/skia_googlesearch_desktop.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'https://www.google.com/#hl=en&q=barack+obama',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaDesktopPage(url, self))
\ No newline at end of file
diff --git a/tools/skp/page_sets/skia_googlesearch_mobile.py b/tools/skp/page_sets/skia_googlesearch_mobile.py
new file mode 100644
index 0000000..5ccb2aa
--- /dev/null
+++ b/tools/skp/page_sets/skia_googlesearch_mobile.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaMobilePage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaMobilePage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedMobilePageState)
+    self.archive_data_file = 'data/skia_googlesearch_mobile.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaGooglesearchMobilePageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaGooglesearchMobilePageSet, self).__init__(
+      archive_data_file='data/skia_googlesearch_mobile.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'https://www.google.co.uk/search?hl=en&q=barack+obama&cad=h',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaMobilePage(url, self))
\ No newline at end of file
diff --git a/tools/skp/page_sets/skia_jsfiddlebigcar_desktop.py b/tools/skp/page_sets/skia_jsfiddlebigcar_desktop.py
deleted file mode 100644
index 49fde18..0000000
--- a/tools/skp/page_sets/skia_jsfiddlebigcar_desktop.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2014 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.
-# pylint: disable=W0401,W0614
-
-
-from telemetry import story
-from telemetry.page import page as page_module
-from telemetry.page import shared_page_state
-
-
-class SkiaBuildbotDesktopPage(page_module.Page):
-
-  def __init__(self, url, page_set):
-    super(SkiaBuildbotDesktopPage, self).__init__(
-        url=url,
-        name=url,
-        page_set=page_set,
-        shared_page_state_class=shared_page_state.SharedDesktopPageState)
-    self.archive_data_file = 'data/skia_jsfiddlebigcar_desktop.json'
-
-  def RunNavigateSteps(self, action_runner):
-    action_runner.Navigate(self.url)
-    action_runner.Wait(5)
-
-
-class SkiaJsfiddlebigcarDesktopPageSet(story.StorySet):
-
-  """ Pages designed to represent the median, not highly optimized web """
-
-  def __init__(self):
-    super(SkiaJsfiddlebigcarDesktopPageSet, self).__init__(
-      archive_data_file='data/skia_jsfiddlebigcar_desktop.json')
-
-    urls_list = [
-      # Why: Page from Chromium's silk test cases
-      'http://jsfiddle.net/vBQHH/3/embedded/result/',
-    ]
-
-    for url in urls_list:
-      self.AddStory(SkiaBuildbotDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_linkedin_desktop.py b/tools/skp/page_sets/skia_linkedin_desktop.py
new file mode 100644
index 0000000..fa7552f
--- /dev/null
+++ b/tools/skp/page_sets/skia_linkedin_desktop.py
@@ -0,0 +1,50 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+import os
+
+from page_sets.login_helpers import linkedin_login
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+from telemetry.util import wpr_modes
+
+
+class SkiaDesktopPage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaDesktopPage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedDesktopPageState)
+    self.archive_data_file = 'data/skia_linkedin_desktop.json'
+
+  def RunNavigateSteps(self, action_runner):
+    if self.wpr_mode != wpr_modes.WPR_REPLAY:
+      credentials_path=os.path.join(os.path.dirname(os.path.abspath(__file__)),
+                                    'data/credentials.json')
+      linkedin_login.LoginDesktopAccount(action_runner, 'linkedin',
+                                         credentials_path)
+      action_runner.Wait(15)
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaLinkedinDesktopPageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaLinkedinDesktopPageSet, self).__init__(
+      archive_data_file='data/skia_linkedin_desktop.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'https://www.linkedin.com/in/linustorvalds',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_mozilla_nexus10.py b/tools/skp/page_sets/skia_mozilla_nexus10.py
deleted file mode 100644
index 4998766..0000000
--- a/tools/skp/page_sets/skia_mozilla_nexus10.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2014 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.
-# pylint: disable=W0401,W0614
-
-
-from telemetry import story
-from telemetry.page import page as page_module
-from telemetry.page import shared_page_state
-
-
-class SkiaBuildbotDesktopPage(page_module.Page):
-
-  def __init__(self, url, page_set):
-    super(SkiaBuildbotDesktopPage, self).__init__(
-        url=url,
-        name=url,
-        page_set=page_set,
-        shared_page_state_class=shared_page_state.SharedDesktopPageState)
-    self.archive_data_file = 'data/skia_mozilla_nexus10.json'
-
-  def RunNavigateSteps(self, action_runner):
-    action_runner.Navigate(self.url)
-    action_runner.Wait(5)
-
-
-class SkiaMozillaNexus10PageSet(story.StorySet):
-
-  """ Pages designed to represent the median, not highly optimized web """
-
-  def __init__(self):
-    super(SkiaMozillaNexus10PageSet, self).__init__(
-      archive_data_file='data/skia_mozilla_nexus10.json')
-
-    urls_list = [
-      # Why:
-      'http://planet.mozilla.org/',
-    ]
-
-    for url in urls_list:
-      self.AddStory(SkiaBuildbotDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_mozilla_tablet.py b/tools/skp/page_sets/skia_mozilla_tablet.py
new file mode 100644
index 0000000..3537deb
--- /dev/null
+++ b/tools/skp/page_sets/skia_mozilla_tablet.py
@@ -0,0 +1,40 @@
+# Copyright 2014 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaBuildbotDesktopPage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaBuildbotDesktopPage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedTabletPageState)
+    self.archive_data_file = 'data/skia_mozilla_tablet.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(5)
+
+
+class SkiaMozillaTabletPageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaMozillaTabletPageSet, self).__init__(
+      archive_data_file='data/skia_mozilla_tablet.json')
+
+    urls_list = [
+      # Why:
+      'http://planet.mozilla.org/',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaBuildbotDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_pokemonwiki_desktop.py b/tools/skp/page_sets/skia_pokemonwiki_desktop.py
index ae9d1e0..3464193 100644
--- a/tools/skp/page_sets/skia_pokemonwiki_desktop.py
+++ b/tools/skp/page_sets/skia_pokemonwiki_desktop.py
@@ -33,8 +33,8 @@
       archive_data_file='data/skia_pokemonwiki_desktop.json')
 
     urls_list = [
-      # Why: http://code.google.com/p/chromium/issues/detail?id=136555
-      'http://en.wikipedia.org/wiki/List_of_Pok%C3%A9mon',
+      # go/skia-skps-3-19
+      'https://pokemondb.net/pokedex/all',
     ]
 
     for url in urls_list:
diff --git a/tools/skp/page_sets/skia_pravda_nexus10.py b/tools/skp/page_sets/skia_pravda_nexus10.py
deleted file mode 100644
index e6560ce..0000000
--- a/tools/skp/page_sets/skia_pravda_nexus10.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2014 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.
-# pylint: disable=W0401,W0614
-
-
-from telemetry import story
-from telemetry.page import page as page_module
-from telemetry.page import shared_page_state
-
-
-class SkiaBuildbotDesktopPage(page_module.Page):
-
-  def __init__(self, url, page_set):
-    super(SkiaBuildbotDesktopPage, self).__init__(
-        url=url,
-        name=url,
-        page_set=page_set,
-        shared_page_state_class=shared_page_state.Shared10InchTabletPageState)
-    self.archive_data_file = 'data/skia_pravda_nexus10.json'
-
-  def RunNavigateSteps(self, action_runner):
-    action_runner.Navigate(self.url)
-    action_runner.Wait(5)
-
-
-class SkiaPravdaNexus10PageSet(story.StorySet):
-
-  """ Pages designed to represent the median, not highly optimized web """
-
-  def __init__(self):
-    super(SkiaPravdaNexus10PageSet, self).__init__(
-      archive_data_file='data/skia_pravda_nexus10.json')
-
-    urls_list = [
-      # Why: cyrillic font test case
-      'http://www.pravda.ru/',
-    ]
-
-    for url in urls_list:
-      self.AddStory(SkiaBuildbotDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_pravda_tablet.py b/tools/skp/page_sets/skia_pravda_tablet.py
new file mode 100644
index 0000000..944b60b
--- /dev/null
+++ b/tools/skp/page_sets/skia_pravda_tablet.py
@@ -0,0 +1,40 @@
+# Copyright 2014 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaBuildbotDesktopPage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaBuildbotDesktopPage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedTabletPageState)
+    self.archive_data_file = 'data/skia_pravda_tablet.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(5)
+
+
+class SkiaPravdaTabletPageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaPravdaTabletPageSet, self).__init__(
+      archive_data_file='data/skia_pravda_tablet.json')
+
+    urls_list = [
+      # Why: cyrillic font test case
+      'http://www.pravda.ru/',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaBuildbotDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_reddit_mobile.py b/tools/skp/page_sets/skia_reddit_mobile.py
new file mode 100644
index 0000000..fe51114
--- /dev/null
+++ b/tools/skp/page_sets/skia_reddit_mobile.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaMobilePage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaMobilePage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedMobilePageState)
+    self.archive_data_file = 'data/skia_reddit_mobile.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(30)
+
+
+class SkiaRedditMobilePageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaRedditMobilePageSet, self).__init__(
+      archive_data_file='data/skia_reddit_mobile.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'https://www.reddit.com/r/programming/comments/1g96ve/',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaMobilePage(url, self))
diff --git a/tools/skp/page_sets/skia_slashdot_mobile.py b/tools/skp/page_sets/skia_slashdot_mobile.py
new file mode 100644
index 0000000..08f1b71
--- /dev/null
+++ b/tools/skp/page_sets/skia_slashdot_mobile.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaMobilePage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaMobilePage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedMobilePageState)
+    self.archive_data_file = 'data/skia_slashdot_mobile.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaSlashdotMobilePageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaSlashdotMobilePageSet, self).__init__(
+      archive_data_file='data/skia_slashdot_mobile.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'http://slashdot.org',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaMobilePage(url, self))
\ No newline at end of file
diff --git a/tools/skp/page_sets/skia_techcrunch_mobile.py b/tools/skp/page_sets/skia_techcrunch_mobile.py
new file mode 100644
index 0000000..16a8937
--- /dev/null
+++ b/tools/skp/page_sets/skia_techcrunch_mobile.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaMobilePage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaMobilePage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedMobilePageState)
+    self.archive_data_file = 'data/skia_techcrunch_mobile.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaTechcrunchMobilePageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaTechcrunchMobilePageSet, self).__init__(
+      archive_data_file='data/skia_techcrunch_mobile.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'http://techcrunch.com',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaMobilePage(url, self))
\ No newline at end of file
diff --git a/tools/skp/page_sets/skia_theverge_mobile.py b/tools/skp/page_sets/skia_theverge_mobile.py
new file mode 100644
index 0000000..9e4159a
--- /dev/null
+++ b/tools/skp/page_sets/skia_theverge_mobile.py
@@ -0,0 +1,41 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaBuildbotMobilePage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaBuildbotMobilePage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedMobilePageState)
+    self.archive_data_file = 'data/skia_theverge_mobile.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaThevergeMobilePageSet(story.StorySet):
+
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaThevergeMobilePageSet, self).__init__(
+      archive_data_file='data/skia_theverge_mobile.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'http://theverge.com/',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaBuildbotMobilePage(url, self))
diff --git a/tools/skp/page_sets/skia_tiger8svg_desktop.py b/tools/skp/page_sets/skia_tiger8svg_desktop.py
index 93e87e0..b7e7877 100644
--- a/tools/skp/page_sets/skia_tiger8svg_desktop.py
+++ b/tools/skp/page_sets/skia_tiger8svg_desktop.py
@@ -34,7 +34,8 @@
 
     urls_list = [
       # Why: from skbug.com/4713
-      'http://www.googledrive.com/host/0B5nDjttF0gt9QzJjdjRNVlNvems',
+      ('https://storage.googleapis.com/skia-infra-testdata/images-for-skps/'
+       'tiger-8.svg'),
     ]
 
     for url in urls_list:
diff --git a/tools/skp/page_sets/skia_twitter_desktop.py b/tools/skp/page_sets/skia_twitter_desktop.py
new file mode 100644
index 0000000..a6f19ae
--- /dev/null
+++ b/tools/skp/page_sets/skia_twitter_desktop.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaDesktopPage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaDesktopPage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedDesktopPageState)
+    self.archive_data_file = 'data/skia_twitter_desktop.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaTwitterDesktopPageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaTwitterDesktopPageSet, self).__init__(
+      archive_data_file='data/skia_twitter_desktop.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'https://twitter.com/katyperry',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaDesktopPage(url, self))
\ No newline at end of file
diff --git a/tools/skp/page_sets/skia_ugamsolutions_desktop.py b/tools/skp/page_sets/skia_ugamsolutions_desktop.py
deleted file mode 100644
index 861a8e6..0000000
--- a/tools/skp/page_sets/skia_ugamsolutions_desktop.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2014 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.
-# pylint: disable=W0401,W0614
-
-
-from telemetry import story
-from telemetry.page import page as page_module
-from telemetry.page import shared_page_state
-
-
-class SkiaBuildbotDesktopPage(page_module.Page):
-
-  def __init__(self, url, page_set):
-    super(SkiaBuildbotDesktopPage, self).__init__(
-        url=url,
-        name=url,
-        page_set=page_set,
-        shared_page_state_class=shared_page_state.SharedDesktopPageState)
-    self.archive_data_file = 'data/skia_ugamsolutions_desktop.json'
-
-  def RunNavigateSteps(self, action_runner):
-    action_runner.Navigate(self.url)
-    action_runner.Wait(15)
-
-
-class SkiaUgamsolutionsDesktopPageSet(story.StorySet):
-
-  """ Pages designed to represent the median, not highly optimized web """
-
-  def __init__(self):
-    super(SkiaUgamsolutionsDesktopPageSet, self).__init__(
-      archive_data_file='data/skia_ugamsolutions_desktop.json')
-
-    urls_list = [
-      # Why: for crbug.com/447291
-      'http://www.ugamsolutions.com',
-    ]
-
-    for url in urls_list:
-      self.AddStory(SkiaBuildbotDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_unicodetable_desktop.py b/tools/skp/page_sets/skia_unicodetable_desktop.py
deleted file mode 100644
index da01e10..0000000
--- a/tools/skp/page_sets/skia_unicodetable_desktop.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright 2015 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.
-# pylint: disable=W0401,W0614
-
-
-from telemetry import story
-from telemetry.page import page as page_module
-from telemetry.page import shared_page_state
-
-
-class SkiaBuildbotDesktopPage(page_module.Page):
-
-  def __init__(self, url, page_set):
-    super(SkiaBuildbotDesktopPage, self).__init__(
-        url=url,
-        name=url,
-        page_set=page_set,
-        shared_page_state_class=shared_page_state.SharedDesktopPageState)
-    self.archive_data_file = 'data/skia_unicodetable_desktop.json'
-
-  def RunNavigateSteps(self, action_runner):
-    action_runner.Navigate(self.url)
-    action_runner.ScrollPage(distance=100000)
-    action_runner.Wait(20)
-
-
-class SkiaUnicodetableDesktopPageSet(story.StorySet):
-
-  """ Pages designed to represent the median, not highly optimized web """
-
-  def __init__(self):
-    super(SkiaUnicodetableDesktopPageSet, self).__init__(
-      archive_data_file='data/skia_unicodetable_desktop.json')
-
-    urls_list = [
-      # Why: stress tests for fonts (from skia:3574).
-      'http://unicode-table.com/en/',
-    ]
-
-    for url in urls_list:
-      self.AddStory(SkiaBuildbotDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_weather_desktop.py b/tools/skp/page_sets/skia_weather_desktop.py
new file mode 100644
index 0000000..5a2ffa3
--- /dev/null
+++ b/tools/skp/page_sets/skia_weather_desktop.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaDesktopPage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaDesktopPage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedDesktopPageState)
+    self.archive_data_file = 'data/skia_weather_desktop.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaWeatherDesktopPageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaWeatherDesktopPageSet, self).__init__(
+      archive_data_file='data/skia_weather_desktop.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'https://weather.com/weather/today/l/94043:4:US',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_wikipedia_mobile.py b/tools/skp/page_sets/skia_wikipedia_mobile.py
new file mode 100644
index 0000000..28076d2
--- /dev/null
+++ b/tools/skp/page_sets/skia_wikipedia_mobile.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaMobilePage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaMobilePage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedMobilePageState)
+    self.archive_data_file = 'data/skia_wikipedia_mobile.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaWikipediaMobilePageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaWikipediaMobilePageSet, self).__init__(
+      archive_data_file='data/skia_wikipedia_mobile.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'https://en.wikipedia.org/wiki/Wikipedia',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaMobilePage(url, self))
\ No newline at end of file
diff --git a/tools/skp/page_sets/skia_worldjournal_nexus10.py b/tools/skp/page_sets/skia_worldjournal_nexus10.py
deleted file mode 100644
index 7d10c3e..0000000
--- a/tools/skp/page_sets/skia_worldjournal_nexus10.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2014 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.
-# pylint: disable=W0401,W0614
-
-
-from telemetry import story
-from telemetry.page import page as page_module
-from telemetry.page import shared_page_state
-
-
-class SkiaBuildbotDesktopPage(page_module.Page):
-
-  def __init__(self, url, page_set):
-    super(SkiaBuildbotDesktopPage, self).__init__(
-        url=url,
-        name=url,
-        page_set=page_set,
-        shared_page_state_class=shared_page_state.Shared10InchTabletPageState)
-    self.archive_data_file = 'data/skia_worldjournal_nexus10.json'
-
-
-class SkiaWorldjournalNexus10PageSet(story.StorySet):
-
-  """ Pages designed to represent the median, not highly optimized web """
-
-  def __init__(self):
-    super(SkiaWorldjournalNexus10PageSet, self).__init__(
-      archive_data_file='data/skia_worldjournal_nexus10.json')
-
-    urls_list = [
-      # Why: Chinese font test case
-      'http://worldjournal.com/',
-    ]
-
-    for url in urls_list:
-      self.AddStory(SkiaBuildbotDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_worldjournal_tablet.py b/tools/skp/page_sets/skia_worldjournal_tablet.py
new file mode 100644
index 0000000..b7d62b8
--- /dev/null
+++ b/tools/skp/page_sets/skia_worldjournal_tablet.py
@@ -0,0 +1,36 @@
+# Copyright 2014 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaBuildbotDesktopPage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaBuildbotDesktopPage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedTabletPageState)
+    self.archive_data_file = 'data/skia_worldjournal_tablet.json'
+
+
+class SkiaWorldjournalTabletPageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaWorldjournalTabletPageSet, self).__init__(
+      archive_data_file='data/skia_worldjournal_tablet.json')
+
+    urls_list = [
+      # Why: Chinese font test case
+      'http://worldjournal.com/',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaBuildbotDesktopPage(url, self))
diff --git a/tools/skp/page_sets/skia_wowwiki_desktop.py b/tools/skp/page_sets/skia_wowwiki_desktop.py
index 0372d1e..6970ad5 100644
--- a/tools/skp/page_sets/skia_wowwiki_desktop.py
+++ b/tools/skp/page_sets/skia_wowwiki_desktop.py
@@ -21,7 +21,8 @@
 
   def RunNavigateSteps(self, action_runner):
     action_runner.Navigate(self.url)
-    action_runner.Wait(25)
+    action_runner.ScrollPage(distance=6000000)
+    action_runner.Wait(60)
 
 
 class SkiaWowwikiDesktopPageSet(story.StorySet):
diff --git a/tools/skp/page_sets/skia_yahooanswers_desktop.py b/tools/skp/page_sets/skia_yahooanswers_desktop.py
new file mode 100644
index 0000000..3204360
--- /dev/null
+++ b/tools/skp/page_sets/skia_yahooanswers_desktop.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaDesktopPage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaDesktopPage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedDesktopPageState)
+    self.archive_data_file = 'data/skia_yahooanswers_desktop.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaYahooanswersDesktopPageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaYahooanswersDesktopPageSet, self).__init__(
+      archive_data_file='data/skia_yahooanswers_desktop.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'http://answers.yahoo.com',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaDesktopPage(url, self))
\ No newline at end of file
diff --git a/tools/skp/page_sets/skia_yahoosports_desktop.py b/tools/skp/page_sets/skia_yahoosports_desktop.py
new file mode 100644
index 0000000..0a7cd79
--- /dev/null
+++ b/tools/skp/page_sets/skia_yahoosports_desktop.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaDesktopPage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaDesktopPage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedDesktopPageState)
+    self.archive_data_file = 'data/skia_yahoosports_desktop.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaYahoosportsDesktopPageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaYahoosportsDesktopPageSet, self).__init__(
+      archive_data_file='data/skia_yahoosports_desktop.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'http://sports.yahoo.com',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaDesktopPage(url, self))
\ No newline at end of file
diff --git a/tools/skp/page_sets/skia_ynevsvg_desktop.py b/tools/skp/page_sets/skia_ynevsvg_desktop.py
index 7950f16..cd63195 100644
--- a/tools/skp/page_sets/skia_ynevsvg_desktop.py
+++ b/tools/skp/page_sets/skia_ynevsvg_desktop.py
@@ -34,7 +34,8 @@
 
     urls_list = [
       # Why: from skbug.com/4713
-      'http://www.googledrive.com/host/0B5nDjttF0gt9QjRKdEZ5MEVYc2c',
+      ('https://storage.googleapis.com/skia-infra-testdata/images-for-skps/'
+       'ynev.svg'),
     ]
 
     for url in urls_list:
diff --git a/tools/skp/page_sets/skia_youtube_desktop.py b/tools/skp/page_sets/skia_youtube_desktop.py
new file mode 100644
index 0000000..513a067
--- /dev/null
+++ b/tools/skp/page_sets/skia_youtube_desktop.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaDesktopPage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaDesktopPage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedDesktopPageState)
+    self.archive_data_file = 'data/skia_youtube_desktop.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaYoutubeDesktopPageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaYoutubeDesktopPageSet, self).__init__(
+      archive_data_file='data/skia_youtube_desktop.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'http://www.youtube.com',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaDesktopPage(url, self))
\ No newline at end of file
diff --git a/tools/skp/page_sets/skia_youtube_mobile.py b/tools/skp/page_sets/skia_youtube_mobile.py
new file mode 100644
index 0000000..4e23040
--- /dev/null
+++ b/tools/skp/page_sets/skia_youtube_mobile.py
@@ -0,0 +1,40 @@
+# Copyright 2019 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.
+# pylint: disable=W0401,W0614
+
+
+from telemetry import story
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+
+
+class SkiaMobilePage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(SkiaMobilePage, self).__init__(
+        url=url,
+        name=url,
+        page_set=page_set,
+        shared_page_state_class=shared_page_state.SharedMobilePageState)
+    self.archive_data_file = 'data/skia_youtube_mobile.json'
+
+  def RunNavigateSteps(self, action_runner):
+    action_runner.Navigate(self.url)
+    action_runner.Wait(15)
+
+
+class SkiaYoutubeMobilePageSet(story.StorySet):
+  """ Pages designed to represent the median, not highly optimized web """
+
+  def __init__(self):
+    super(SkiaYoutubeMobilePageSet, self).__init__(
+      archive_data_file='data/skia_youtube_mobile.json')
+
+    urls_list = [
+      # go/skia-skps-3-2019
+      'https://www.youtube.com/watch?v=9hBpF_Zj4OA',
+    ]
+
+    for url in urls_list:
+      self.AddStory(SkiaMobilePage(url, self))
\ No newline at end of file
diff --git a/tools/skp/webpages_playback.py b/tools/skp/webpages_playback.py
index 805b3f3..faf82f1 100644
--- a/tools/skp/webpages_playback.py
+++ b/tools/skp/webpages_playback.py
@@ -76,6 +76,13 @@
     os.path.abspath(os.path.dirname(__file__)), 'page_sets', 'data')
 TMP_SKP_DIR = tempfile.mkdtemp()
 
+# Location of the credentials.json file and the string that represents missing
+# passwords.
+CREDENTIALS_FILE_PATH = os.path.join(
+    os.path.abspath(os.path.dirname(__file__)), 'page_sets', 'data',
+    'credentials.json'
+)
+
 # Name of the SKP benchmark
 SKP_BENCHMARK = 'skpicture_printer'
 
@@ -85,8 +92,8 @@
 # Dictionary of device to platform prefixes for SKP files.
 DEVICE_TO_PLATFORM_PREFIX = {
     'desktop': 'desk',
-    'galaxynexus': 'mobi',
-    'nexus10': 'tabl'
+    'mobile': 'mobi',
+    'tablet': 'tabl'
 }
 
 # How many times the record_wpr binary should be retried.
@@ -94,6 +101,9 @@
 # How many times the run_benchmark binary should be retried.
 RETRY_RUN_MEASUREMENT_COUNT = 3
 
+# Location of the credentials.json file in Google Storage.
+CREDENTIALS_GS_PATH = 'playback/credentials/credentials.json'
+
 X11_DISPLAY = os.getenv('DISPLAY', ':0')
 
 # Path to Chromium's page sets.
@@ -101,8 +111,6 @@
 
 # Dictionary of supported Chromium page sets to their file prefixes.
 CHROMIUM_PAGE_SETS_TO_PREFIX = {
-    'key_mobile_sites_smooth.py': 'keymobi',
-    'top_25_smooth.py': 'top25desk',
 }
 
 PAGE_SETS_TO_EXCLUSIONS = {
@@ -113,6 +121,11 @@
 }
 
 
+class InvalidSKPException(Exception):
+  """Raised when the created SKP is invalid."""
+  pass
+
+
 def remove_prefix(s, prefix):
   if s.startswith(prefix):
     return s[len(prefix):]
@@ -191,6 +204,21 @@
   def Run(self):
     """Run the SkPicturePlayback BuildStep."""
 
+    # Download the credentials file if it was not previously downloaded.
+    if not os.path.isfile(CREDENTIALS_FILE_PATH):
+      # Download the credentials.json file from Google Storage.
+      self.gs.download_file(CREDENTIALS_GS_PATH, CREDENTIALS_FILE_PATH)
+
+    if not os.path.isfile(CREDENTIALS_FILE_PATH):
+      raise Exception("""Could not locate credentials file in the storage.
+      Please create a credentials file in gs://%s that contains:
+      {
+        "google": {
+          "username": "google_testing_account_username",
+          "password": "google_testing_account_password"
+        }
+      }\n\n""" % CREDENTIALS_GS_PATH)
+
     # Delete any left over data files in the data directory.
     for archive_file in glob.glob(
         os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, 'skia_*')):
@@ -207,8 +235,8 @@
 
       page_set_basename = os.path.basename(page_set).split('.')[0]
       page_set_json_name = page_set_basename + '.json'
-      wpr_data_file = (
-          page_set.split(os.path.sep)[-1].split('.')[0] + '_000.wprgo')
+      wpr_data_file_glob = (
+          page_set.split(os.path.sep)[-1].split('.')[0] + '_*.wprgo')
       page_set_dir = os.path.dirname(page_set)
 
       if self._IsChromiumPageSet(page_set):
@@ -232,9 +260,11 @@
 
             # Copy over the created archive into the local webpages archive
             # directory.
-            shutil.copy(
-              os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, wpr_data_file),
-              self._local_record_webpages_archive_dir)
+            for wpr_data_file in glob.glob(os.path.join(
+                LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, wpr_data_file_glob)):
+              shutil.copy(
+                os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR, wpr_data_file),
+                self._local_record_webpages_archive_dir)
             shutil.copy(
               os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR,
                            page_set_json_name),
@@ -252,7 +282,7 @@
 
       else:
         # Get the webpages archive so that it can be replayed.
-        self._DownloadWebpagesArchive(wpr_data_file, page_set_json_name)
+        self._DownloadWebpagesArchive(wpr_data_file_glob, page_set_json_name)
 
       run_benchmark_cmd = [
           'PYTHONPATH=%s:%s:$PYTHONPATH' % (page_set_dir, self._catapult_dir),
@@ -284,8 +314,16 @@
           time.sleep(10)
           continue
 
-        # Rename generated SKP files into more descriptive names.
-        self._RenameSkpFiles(page_set)
+        try:
+          # Rename generated SKP files into more descriptive names.
+          self._RenameSkpFiles(page_set)
+        except InvalidSKPException:
+          # There was a failure continue with the loop.
+          traceback.print_exc()
+          print '\n\n=======Retrying %s=======\n\n' % page_set
+          time.sleep(10)
+          continue
+
         # Break out of the retry loop since there were no errors.
         break
       else:
@@ -383,6 +421,10 @@
 
     Look into the subdirectory of TMP_SKP_DIR and find the most interesting
     .skp in there to be this page_set's representative .skp.
+
+    Throws InvalidSKPException if the chosen .skp is less than 1KB. This
+    typically happens when there is a 404 or a redirect loop. Anything greater
+    than 1KB seems to have captured at least some useful information.
     """
     subdirs = glob.glob(os.path.join(TMP_SKP_DIR, '*'))
     for site in subdirs:
@@ -403,6 +445,11 @@
       shutil.move(largest_skp, dest)
       self._skp_files.append(filename)
       shutil.rmtree(site)
+      skp_size = os.path.getsize(dest)
+      if skp_size < 1024:
+        raise InvalidSKPException(
+            'Size of %s is only %d. Something is wrong.' % (dest, skp_size))
+
 
   def _CreateLocalStorageDirs(self):
     """Creates required local storage directories for this script."""
@@ -422,9 +469,7 @@
     gs = self.gs
     if (gs.does_storage_object_exist(wpr_source) and
         gs.does_storage_object_exist(page_set_source)):
-      gs.download_file(wpr_source,
-                       os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR,
-                                    wpr_data_file))
+      gs.download_file(wpr_source, LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR)
       gs.download_file(page_set_source,
                        os.path.join(LOCAL_REPLAY_WEBPAGES_ARCHIVE_DIR,
                                     page_set_json_name))
diff --git a/tools/skp_parser.cpp b/tools/skp_parser.cpp
index 25b0ea4..de1caf0 100644
--- a/tools/skp_parser.cpp
+++ b/tools/skp_parser.cpp
@@ -5,7 +5,7 @@
  * found in the LICENSE file.
  */
 
-#include "SkDebugCanvas.h"
+#include "DebugCanvas.h"
 #include "SkNullCanvas.h"
 #include "SkPicture.h"
 #include "SkStream.h"
@@ -51,7 +51,7 @@
         return 3;
     }
     SkISize size = pic->cullRect().roundOut().size();
-    SkDebugCanvas debugCanvas(size.width(), size.height());
+    DebugCanvas debugCanvas(size.width(), size.height());
     pic->playback(&debugCanvas);
     std::unique_ptr<SkCanvas> nullCanvas = SkMakeNullCanvas();
     UrlDataManager dataManager(SkString("data"));
diff --git a/tools/skpbench/skpbench.cpp b/tools/skpbench/skpbench.cpp
index d63ba7e..7b1c478 100644
--- a/tools/skpbench/skpbench.cpp
+++ b/tools/skpbench/skpbench.cpp
@@ -5,6 +5,9 @@
  * found in the LICENSE file.
  */
 
+#include "CommandLineFlags.h"
+#include "CommonFlags.h"
+#include "CommonFlagsConfig.h"
 #include "DDLPromiseImageHelper.h"
 #include "DDLTileHelper.h"
 #include "GpuTimer.h"
@@ -12,11 +15,9 @@
 #include "GrContextFactory.h"
 #include "GrContextPriv.h"
 #include "SkCanvas.h"
-#include "SkCommonFlags.h"
-#include "SkCommonFlagsGpu.h"
 #include "SkDeferredDisplayList.h"
-#include "SkGraphics.h"
 #include "SkGr.h"
+#include "SkGraphics.h"
 #include "SkOSFile.h"
 #include "SkOSPath.h"
 #include "SkPerlinNoiseShader.h"
@@ -26,9 +27,7 @@
 #include "SkSurface.h"
 #include "SkSurfaceProps.h"
 #include "SkTaskGroup.h"
-#include "flags/SkCommandLineFlags.h"
-#include "flags/SkCommonFlagsConfig.h"
-#include "sk_tool_utils.h"
+#include "ToolUtils.h"
 
 #ifdef SK_XML
 #include "SkDOM.h"
@@ -54,19 +53,21 @@
  * Currently, only GPU configs are supported.
  */
 
-DEFINE_bool(ddl, false, "record the skp into DDLs before rendering");
-DEFINE_int32(ddlNumAdditionalThreads, 0, "number of DDL recording threads in addition to main one");
-DEFINE_int32(ddlTilingWidthHeight, 0, "number of tiles along one edge when in DDL mode");
-DEFINE_bool(ddlRecordTime, false, "report just the cpu time spent recording DDLs");
+static DEFINE_bool(ddl, false, "record the skp into DDLs before rendering");
+static DEFINE_int(ddlNumAdditionalThreads, 0,
+                    "number of DDL recording threads in addition to main one");
+static DEFINE_int(ddlTilingWidthHeight, 0, "number of tiles along one edge when in DDL mode");
+static DEFINE_bool(ddlRecordTime, false, "report just the cpu time spent recording DDLs");
 
-DEFINE_int32(duration, 5000, "number of milliseconds to run the benchmark");
-DEFINE_int32(sampleMs, 50, "minimum duration of a sample");
-DEFINE_bool(gpuClock, false, "time on the gpu clock (gpu work only)");
-DEFINE_bool(fps, false, "use fps instead of ms");
-DEFINE_string(src, "", "path to a single .skp or .svg file, or 'warmup' for a builtin warmup run");
-DEFINE_string(png, "", "if set, save a .png proof to disk at this file location");
-DEFINE_int32(verbosity, 4, "level of verbosity (0=none to 5=debug)");
-DEFINE_bool(suppressHeader, false, "don't print a header row before the results");
+static DEFINE_int(duration, 5000, "number of milliseconds to run the benchmark");
+static DEFINE_int(sampleMs, 50, "minimum duration of a sample");
+static DEFINE_bool(gpuClock, false, "time on the gpu clock (gpu work only)");
+static DEFINE_bool(fps, false, "use fps instead of ms");
+static DEFINE_string(src, "",
+                     "path to a single .skp or .svg file, or 'warmup' for a builtin warmup run");
+static DEFINE_string(png, "", "if set, save a .png proof to disk at this file location");
+static DEFINE_int(verbosity, 4, "level of verbosity (0=none to 5=debug)");
+static DEFINE_bool(suppressHeader, false, "don't print a header row before the results");
 
 static const char* header =
 "   accum    median       max       min   stddev  samples  sample_ms  clock  metric  config    bench";
@@ -112,11 +113,11 @@
     kSoftware     = 70
 };
 
-static void draw_skp_and_flush(SkCanvas*, const SkPicture*);
+static void draw_skp_and_flush(SkSurface*, const SkPicture*);
 static sk_sp<SkPicture> create_warmup_skp();
 static sk_sp<SkPicture> create_skp_from_svg(SkStream*, const char* filename);
 static bool mkdir_p(const SkString& name);
-static SkString join(const SkCommandLineFlags::StringArray&);
+static SkString         join(const CommandLineFlags::StringArray&);
 static void exitf(ExitErr, const char* format, ...);
 
 static void ddl_sample(GrContext* context, DDLTileHelper* tiles, GpuSync* gpuSync, Sample* sample,
@@ -193,17 +194,17 @@
     }
 }
 
-static void run_benchmark(const sk_gpu_test::FenceSync* fenceSync, SkCanvas* canvas,
+static void run_benchmark(const sk_gpu_test::FenceSync* fenceSync, SkSurface* surface,
                           const SkPicture* skp, std::vector<Sample>* samples) {
     using clock = std::chrono::high_resolution_clock;
     const Sample::duration sampleDuration = std::chrono::milliseconds(FLAGS_sampleMs);
     const clock::duration benchDuration = std::chrono::milliseconds(FLAGS_duration);
 
-    draw_skp_and_flush(canvas, skp); // draw 1
+    draw_skp_and_flush(surface, skp); // draw 1
     GpuSync gpuSync(fenceSync);
 
     for (int i = 1; i < kNumFlushesToPrimeCache; ++i) {
-        draw_skp_and_flush(canvas, skp); // draw N
+        draw_skp_and_flush(surface, skp); // draw N
         // Waits for draw N-1 to finish (after draw N's cpu work is done).
         gpuSync.syncToPreviousFrame();
     }
@@ -217,7 +218,7 @@
         Sample& sample = samples->back();
 
         do {
-            draw_skp_and_flush(canvas, skp);
+            draw_skp_and_flush(surface, skp);
             gpuSync.syncToPreviousFrame();
 
             now = clock::now();
@@ -228,7 +229,7 @@
 }
 
 static void run_gpu_time_benchmark(sk_gpu_test::GpuTimer* gpuTimer,
-                                   const sk_gpu_test::FenceSync* fenceSync, SkCanvas* canvas,
+                                   const sk_gpu_test::FenceSync* fenceSync, SkSurface* surface,
                                    const SkPicture* skp, std::vector<Sample>* samples) {
     using sk_gpu_test::PlatformTimerQuery;
     using clock = std::chrono::steady_clock;
@@ -240,13 +241,13 @@
                         "results may be unreliable\n");
     }
 
-    draw_skp_and_flush(canvas, skp);
+    draw_skp_and_flush(surface, skp);
     GpuSync gpuSync(fenceSync);
 
     PlatformTimerQuery previousTime = 0;
     for (int i = 1; i < kNumFlushesToPrimeCache; ++i) {
         gpuTimer->queueStart();
-        draw_skp_and_flush(canvas, skp);
+        draw_skp_and_flush(surface, skp);
         previousTime = gpuTimer->queueStop();
         gpuSync.syncToPreviousFrame();
     }
@@ -261,7 +262,7 @@
 
         do {
             gpuTimer->queueStart();
-            draw_skp_and_flush(canvas, skp);
+            draw_skp_and_flush(surface, skp);
             PlatformTimerQuery time = gpuTimer->queueStop();
             gpuSync.syncToPreviousFrame();
 
@@ -323,9 +324,10 @@
 }
 
 int main(int argc, char** argv) {
-    SkCommandLineFlags::SetUsage("Use skpbench.py instead. "
-                                 "You usually don't want to use this program directly.");
-    SkCommandLineFlags::Parse(argc, argv);
+    CommandLineFlags::SetUsage(
+            "Use skpbench.py instead. "
+            "You usually don't want to use this program directly.");
+    CommandLineFlags::Parse(argc, argv);
 
     if (!FLAGS_suppressHeader) {
         printf("%s\n", header);
@@ -448,7 +450,7 @@
         if (FLAGS_ddl) {
             run_ddl_benchmark(testCtx->fenceSync(), ctx, canvas, skp.get(), &samples);
         } else {
-            run_benchmark(testCtx->fenceSync(), canvas, skp.get(), &samples);
+            run_benchmark(testCtx->fenceSync(), surface.get(), skp.get(), &samples);
         }
     } else {
         if (FLAGS_ddl) {
@@ -457,7 +459,7 @@
         if (!testCtx->gpuTimingSupport()) {
             exitf(ExitErr::kUnavailable, "GPU does not support timing");
         }
-        run_gpu_time_benchmark(testCtx->gpuTimer(), testCtx->fenceSync(), canvas, skp.get(),
+        run_gpu_time_benchmark(testCtx->gpuTimer(), testCtx->fenceSync(), surface.get(), skp.get(),
                                &samples);
     }
     print_result(samples, config->getTag().c_str(), srcname.c_str());
@@ -472,7 +474,7 @@
         if (!mkdir_p(SkOSPath::Dirname(FLAGS_png[0]))) {
             exitf(ExitErr::kIO, "failed to create directory for png \"%s\"", FLAGS_png[0]);
         }
-        if (!sk_tool_utils::EncodeImageToFile(FLAGS_png[0], bmp, SkEncodedImageFormat::kPNG, 100)) {
+        if (!ToolUtils::EncodeImageToFile(FLAGS_png[0], bmp, SkEncodedImageFormat::kPNG, 100)) {
             exitf(ExitErr::kIO, "failed to save png to \"%s\"", FLAGS_png[0]);
         }
     }
@@ -480,9 +482,10 @@
     exit(0);
 }
 
-static void draw_skp_and_flush(SkCanvas* canvas, const SkPicture* skp) {
+static void draw_skp_and_flush(SkSurface* surface, const SkPicture* skp) {
+    auto canvas = surface->getCanvas();
     canvas->drawPicture(skp);
-    canvas->flush();
+    surface->flush();
 }
 
 static sk_sp<SkPicture> create_warmup_skp() {
@@ -498,7 +501,7 @@
 
     // Use a big path to (theoretically) warmup the CPU.
     SkPath bigPath;
-    sk_tool_utils::make_big_path(bigPath);
+    ToolUtils::make_big_path(bigPath);
     recording->drawPath(bigPath, stroke);
 
     // Use a perlin shader to warmup the GPU.
@@ -534,13 +537,13 @@
 }
 
 bool mkdir_p(const SkString& dirname) {
-    if (dirname.isEmpty()) {
+    if (dirname.isEmpty() || dirname == SkString("/")) {
         return true;
     }
     return mkdir_p(SkOSPath::Dirname(dirname.c_str())) && sk_mkdir(dirname.c_str());
 }
 
-static SkString join(const SkCommandLineFlags::StringArray& stringArray) {
+static SkString join(const CommandLineFlags::StringArray& stringArray) {
     SkString joined;
     for (int i = 0; i < stringArray.count(); ++i) {
         joined.appendf(i ? " %s" : "%s", stringArray[i]);
diff --git a/tools/skpbench/skpbench.py b/tools/skpbench/skpbench.py
index 9d111f0..df26232 100755
--- a/tools/skpbench/skpbench.py
+++ b/tools/skpbench/skpbench.py
@@ -60,6 +60,8 @@
   help="comma- or space-separated list of GPU path renderers, including: "
        "[[~]all [~]default [~]dashline [~]nvpr [~]msaa [~]aaconvex "
        "[~]aalinearizing [~]small [~]tess]")
+__argparse.add_argument('--cc',
+  action='store_true', help="allow coverage counting shortcuts to render paths")
 __argparse.add_argument('--nocache',
   action='store_true', help="disable caching of path mask textures")
 __argparse.add_argument('-c', '--config',
@@ -131,6 +133,8 @@
     ARGV.extend(['--fps', 'true'])
   if FLAGS.pr:
     ARGV.extend(['--pr'] + re.split(r'[ ,]', FLAGS.pr))
+  if FLAGS.cc:
+    ARGV.extend(['--cc', 'true'])
   if FLAGS.nocache:
     ARGV.extend(['--cachePathMasks', 'false'])
   if FLAGS.gpuThreads != -1:
diff --git a/tools/skpinfo.cpp b/tools/skpinfo.cpp
index 53ea8f3..2316c62 100644
--- a/tools/skpinfo.cpp
+++ b/tools/skpinfo.cpp
@@ -5,7 +5,7 @@
  * found in the LICENSE file.
  */
 
-#include "SkCommandLineFlags.h"
+#include "CommandLineFlags.h"
 #include "SkFontDescriptor.h"
 #include "SkPicture.h"
 #include "SkPictureCommon.h"
@@ -13,12 +13,12 @@
 #include "SkStream.h"
 #include "SkTo.h"
 
-DEFINE_string2(input, i, "", "skp on which to report");
-DEFINE_bool2(version, v, true, "version");
-DEFINE_bool2(cullRect, c, true, "cullRect");
-DEFINE_bool2(flags, f, true, "flags");
-DEFINE_bool2(tags, t, true, "tags");
-DEFINE_bool2(quiet, q, false, "quiet");
+static DEFINE_string2(input, i, "", "skp on which to report");
+static DEFINE_bool2(version, v, true, "version");
+static DEFINE_bool2(cullRect, c, true, "cullRect");
+static DEFINE_bool2(flags, f, true, "flags");
+static DEFINE_bool2(tags, t, true, "tags");
+static DEFINE_bool2(quiet, q, false, "quiet");
 
 // This tool can print simple information about an SKP but its main use
 // is just to check if an SKP has been truncated during the recording
@@ -32,8 +32,8 @@
 static const int kIOError = 5;
 
 int main(int argc, char** argv) {
-    SkCommandLineFlags::SetUsage("Prints information about an skp file");
-    SkCommandLineFlags::Parse(argc, argv);
+    CommandLineFlags::SetUsage("Prints information about an skp file");
+    CommandLineFlags::Parse(argc, argv);
 
     if (FLAGS_input.count() != 1) {
         if (!FLAGS_quiet) {
diff --git a/tools/skqp/bad_gms.txt b/tools/skqp/bad_gms.txt
index dc57054..c812083 100644
--- a/tools/skqp/bad_gms.txt
+++ b/tools/skqp/bad_gms.txt
@@ -1,4 +1,10 @@
+arcs_as_paths
 drawbitmaprect
 drawbitmaprect-imagerect
+etc1
+ovals_as_paths
 p3
+perlinnoise
+radial_gradient_precision
 skbug1719
+zero_length_paths_aa
diff --git a/tools/skqp/find_commit_with_best_gold_results.py b/tools/skqp/find_commit_with_best_gold_results.py
new file mode 100755
index 0000000..49d2a4a
--- /dev/null
+++ b/tools/skqp/find_commit_with_best_gold_results.py
@@ -0,0 +1,122 @@
+#! /usr/bin/env python
+# Copyright 2019 Google LLC.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import json
+import os
+import re
+import subprocess
+import sys
+import threading
+import urllib
+import urllib2
+
+
+assert '/' in [os.sep, os.altsep]
+
+
+skia_directory = os.path.abspath(os.path.dirname(__file__) + '/../..')
+
+
+def get_jobs():
+    path = skia_directory + '/infra/bots/jobs.json'
+    reg = re.compile('Test-(?P<os>[A-Za-z0-9_]+)-'
+                     '(?P<compiler>[A-Za-z0-9_]+)-'
+                     '(?P<model>[A-Za-z0-9_]+)-GPU-'
+                     '(?P<cpu_or_gpu_value>[A-Za-z0-9_]+)-'
+                     '(?P<arch>[A-Za-z0-9_]+)-'
+                     '(?P<configuration>[A-Za-z0-9_]+)-'
+                     'All(-(?P<extra_config>[A-Za-z0-9_]+)|)')
+    keys = ['os', 'compiler', 'model', 'cpu_or_gpu_value', 'arch',
+            'configuration', 'extra_config']
+    def fmt(s):
+        return s.encode('utf-8') if s is not None else ''
+    with open(path) as f:
+        jobs = json.load(f)
+    for job in jobs:
+        m = reg.match(job)
+        if m is not None:
+            yield [(k, fmt(m.group(k))) for k in keys]
+
+
+def gold_export_url(job, config, first_commit, last_commit):
+    qq = [('source_type', 'gm'), ('config', config)] + job
+    query = [
+        ('fbegin', first_commit),
+        ('fend', last_commit),
+        ('query', urllib.urlencode(qq)),
+        ('pos', 'true'),
+        ('neg', 'false'),
+        ('unt', 'false'),
+        ('head', 'true')
+    ]
+    return 'https://public-gold.skia.org/json/export?' + urllib.urlencode(query)
+
+
+def get_results_for_commit(commit, jobs):
+    sys.stderr.write('%s\n' % commit)
+    sys.stderr.flush()
+    CONFIGS = ['gles', 'vk']
+    passing_tests_for_all_jobs = []
+    def process(url):
+        testResults = json.load(urllib2.urlopen(url))
+        sys.stderr.write('.')
+        sys.stderr.flush()
+        passing_tests = 0
+        for t in testResults:
+            assert t['digests']
+            passing_tests += 1
+        passing_tests_for_all_jobs.append(passing_tests)
+    all_urls = [gold_export_url(job, config, commit, commit)
+                for job in jobs for config in CONFIGS]
+    threads = [threading.Thread(target=process, args=(url,)) for url in all_urls]
+    for t in threads:
+        t.start()
+    for t in threads:
+        t.join()
+    result = sum(passing_tests_for_all_jobs)
+    sys.stderr.write('\n%d\n' % result)
+    sys.stderr.flush()
+    return result
+
+
+def find_best_commit(commits):
+    jobs = [j for j in get_jobs()]
+    results = []
+    for commit_name in commits:
+        commit_hash = subprocess.check_output(['git', 'rev-parse', commit_name]).strip()
+        results.append((commit_hash, get_results_for_commit(commit_hash, jobs)))
+
+    best_result = max(r for h, r in results)
+    for h, r in results:
+        if r == best_result:
+            return h
+    return None
+
+
+def generate_commit_list(args):
+    return subprocess.check_output(['git', 'log', '--format=%H'] + args).splitlines()
+
+
+def main(args):
+    os.chdir(skia_directory)
+    subprocess.check_call(['git', 'fetch', 'origin'])
+    sys.stderr.write('%s\n' % ' '.join(args))
+    commits = generate_commit_list(args)
+    sys.stderr.write('%d\n' % len(commits))
+    best = find_best_commit(commits)
+    sys.stderr.write('DONE:\n')
+    sys.stderr.flush()
+    sys.stdout.write('%s\n' % best)
+
+
+usage = '''Example usage:
+    python %s origin/master ^origin/skqp/dev < /dev/null > LOG 2>&1 & disown
+'''
+
+if __name__ == '__main__':
+    if len(sys.argv) < 2:
+        sys.stderr.write(usage % sys.argv[0])
+        sys.exit(1)
+    main(sys.argv[1:])
diff --git a/tools/skqp/generate_gn_args b/tools/skqp/generate_gn_args
index 4dc1aa1..f707fa4 100755
--- a/tools/skqp/generate_gn_args
+++ b/tools/skqp/generate_gn_args
@@ -8,22 +8,13 @@
 import os
 import sys
 
+from skqp_gn_args import SkqpGnArgs
+
 fmt = '''
 target_cpu = "{arch}"
-is_debug = {debug}
 ndk = "{android_ndk_dir}"
+is_debug = {debug}
 ndk_api = {api_level}
-skia_enable_fontmgr_empty = true
-skia_enable_pdf = false
-skia_skqp_global_error_tolerance = 8
-skia_use_dng_sdk = false
-skia_use_expat = false
-skia_use_icu = false
-skia_use_libheif = false
-skia_use_lua = false
-skia_use_piex = false
-skia_tools_require_resources = true
-extra_cflags = [ "-DSK_ENABLE_DUMP_GPU", "-DSK_BUILD_FOR_SKQP" ]
 '''
 
 def parse_args():
@@ -47,11 +38,19 @@
     args.android_ndk_dir = os.path.abspath(args.android_ndk_dir)
     return args
 
+def write_gn(o, args):
+    o.write(fmt.format(**args))
+    for k, v in SkqpGnArgs.iteritems():
+        o.write('%s = %s\n' % (k,v) )
+
 def make_args_gn(out_dir, args):
+    if out_dir == '-':
+        write_gn(sys.stdout, args)
+        return
     if not os.path.exists(out_dir):
         os.makedirs(out_dir)
     with open(os.path.join(out_dir, 'args.gn'), 'w') as o:
-        o.write(fmt.format(**args))
+        write_gn(o, args)
 
 if __name__ == '__main__':
     args = parse_args()
diff --git a/tools/skqp/gn_to_bp.py b/tools/skqp/gn_to_bp.py
old mode 100644
new mode 100755
index 02bd638..8fd614a
--- a/tools/skqp/gn_to_bp.py
+++ b/tools/skqp/gn_to_bp.py
@@ -23,6 +23,8 @@
 
 import gn_to_bp_utils
 
+from skqp_gn_args import SkqpGnArgs
+
 # First we start off with a template for Android.bp,
 # with holes for source lists and include directories.
 bp = string.Template('''// This file is autogenerated by tools/skqp/gn_to_bp.py.
@@ -32,7 +34,6 @@
     sdk_version: "26",
     stl: "libc++_static",
     compile_multilib: "both",
-    tags: ["tests", "optional"],
 
     cflags: [
         $cflags
@@ -49,6 +50,7 @@
     ],
 
     srcs: [
+        "third_party/vulkanmemoryallocator/GrVulkanMemoryAllocator.cpp",
         $srcs
     ],
 
@@ -105,8 +107,8 @@
           "libz",
     ],
     static_libs: [
+          "libexpat",
           "libjpeg_static_ndk",
-          "libjsoncpp_ndk",
           "libpng_ndk",
           "libwebp-decode",
           "libwebp-encode",
@@ -121,31 +123,18 @@
   # setup skqp
   'is_debug':   'false',
   'ndk_api':    '26',
-  'skia_skqp_global_error_tolerance': '8',
-
-  # setup vulkan
-  'skia_use_vulkan':    'true',
-  'skia_vulkan_header': '"Skia_Vulkan_Android.h"',
-
-  # enable/disable skia subsystems
-  'skia_enable_fontmgr_empty': 'true',
-  'skia_enable_pdf':           'false',
-  'skia_enable_skottie':       'false',
-  'skia_use_expat':            'false',
-  'skia_use_dng_sdk':          'false',
-  'skia_use_icu':              'false',
-  'skia_use_lua':              'false',
-  'skia_use_piex':             'false',
 
   # specify that the Android.bp will supply the necessary components
   'skia_use_system_expat':         'true', # removed this when gn is fixed
   'skia_use_system_libpng':        'true',
-  'skia_use_system_jsoncpp':       'true',
   'skia_use_system_libwebp':       'true',
   'skia_use_system_libjpeg_turbo': 'true',
   'skia_use_system_zlib':          'true',
 }
 
+for k, v in SkqpGnArgs.iteritems():
+    gn_args[k] = v
+
 js = gn_to_bp_utils.GenerateJSONFromGN(gn_args)
 
 def strip_slashes(lst):
@@ -157,6 +146,9 @@
 local_includes  = strip_slashes(js['targets']['//:libskqp_app']['include_dirs'])
 defines      = {str(d) for d in js['targets']['//:libskqp_app']['defines']}
 
+defines.update(["SK_ENABLE_DUMP_GPU", "SK_BUILD_FOR_SKQP"])
+cflags_cc.update(['-Wno-extra-semi-stmt'])
+
 gn_to_bp_utils.GrabDependentValues(js, '//:libskqp_app', 'sources', srcs, None)
 gn_to_bp_utils.GrabDependentValues(js, '//:libskqp_app', 'include_dirs',
                                    local_includes, 'freetype')
@@ -205,6 +197,7 @@
                                 defs['ssse3'] +
                                 defs['sse41'] +
                                 defs['sse42'] +
-                                defs['avx'  ]),
+                                defs['avx'] +
+                                defs['hsw']),
   })
 
diff --git a/tools/skqp/release.sh b/tools/skqp/release.sh
new file mode 100755
index 0000000..f96fb9f
--- /dev/null
+++ b/tools/skqp/release.sh
@@ -0,0 +1,85 @@
+#!/bin/sh
+# Copyright 2019 Google LLC.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+cd "$(dirname "$0")/../.."
+
+set -e -x
+
+[ -f platform_tools/android/apps/skqp/src/main/assets/files.checksum       ] || exit 1
+[ -f platform_tools/android/apps/skqp/src/main/assets/skqp/rendertests.txt ] || exit 1
+[ -f platform_tools/android/apps/skqp/src/main/assets/skqp/unittests.txt   ] || exit 1
+
+python tools/skqp/gn_to_bp.py
+python tools/skqp/download_model
+python tools/skqp/setup_resources
+
+touch MODULE_LICENSE_BSD
+
+cat > platform_tools/android/apps/skqp/src/main/Android.mk <<- "EOM"
+	# Copyright 2019 Google LLC.
+	# Use of this source code is governed by a BSD-style license that can be
+	# found in the LICENSE file.
+	LOCAL_PATH:= $(call my-dir)
+	include $(CLEAR_VARS)
+	LOCAL_MODULE_TAGS := tests optional
+	LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+	LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+	LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
+	LOCAL_JNI_SHARED_LIBRARIES := libskqp_app
+	LOCAL_MULTILIB := both
+	LOCAL_USE_AAPT2 := true
+	LOCAL_STATIC_ANDROID_LIBRARIES := android-support-design
+	LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner-axt
+	LOCAL_SRC_FILES := $(call all-java-files-under, java)
+	LOCAL_PACKAGE_NAME := CtsSkQPTestCases
+	LOCAL_SDK_VERSION := test_current
+	include $(BUILD_CTS_PACKAGE)
+EOM
+
+cat > include/config/SkUserConfigManual.h <<- "EOM"
+	// Copyright 2019 Google LLC.
+	// Use of this source code is governed by a BSD-style license that can be
+	// found in the LICENSE file.
+	#ifndef SkUserConfigManual_DEFINED
+	#define SkUserConfigManual_DEFINED
+	// DON'T DEFINE THINGS HERE AS IT WILL RESULT IN DIFFERENCES WITH
+	// THE VERSION OF SKQP PUBLISHED ON SKIA.ORG
+	#endif // SkUserConfigManual_DEFINED
+EOM
+
+cat > platform_tools/android/apps/skqp/src/main/AndroidTest.xml <<- "EOM"
+	<?xml version="1.0" encoding="utf-8"?>
+	<!--
+	Copyright 2019 Google LLC.
+	Use of this source code is governed by a BSD-style license that can be
+	found in the LICENSE file.
+	-->
+	<configuration description="Config for CTS SkQP test cases">
+	<option name="test-suite-tag" value="cts" />
+	<option name="config-descriptor:metadata" key="component" value="uitoolkit" />
+	<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+	<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+	<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+	<option name="cleanup-apks" value="true" />
+	<option name="test-file-name" value="CtsSkQPTestCases.apk" />
+	</target_preparer>
+	<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+	<option name="package" value="org.skia.skqp" />
+	<option name="runtime-hint" value="7m" />
+	</test>
+	</configuration>
+EOM
+
+[ -f platform_tools/android/apps/skqp/src/main/assets/.gitignore ] && \
+    git rm platform_tools/android/apps/skqp/src/main/assets/.gitignore
+
+git add                                                       \
+    Android.bp                                                \
+    MODULE_LICENSE_BSD                                        \
+    include/config/SkUserConfig.h                             \
+    include/config/SkUserConfigManual.h                       \
+    platform_tools/android/apps/skqp/src/main/Android.mk      \
+    platform_tools/android/apps/skqp/src/main/AndroidTest.xml \
+    platform_tools/android/apps/skqp/src/main/assets
diff --git a/tools/skqp/skqp_gn_args.py b/tools/skqp/skqp_gn_args.py
new file mode 100644
index 0000000..1c55194
--- /dev/null
+++ b/tools/skqp/skqp_gn_args.py
@@ -0,0 +1,19 @@
+# Copyright 2019 Google LLC.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+SkqpGnArgs = {
+    'extra_cflags':                     '[ "-DSK_ENABLE_DUMP_GPU", "-DSK_BUILD_FOR_SKQP" ]',
+    'skia_enable_fontmgr_android':      'false',
+    'skia_enable_fontmgr_empty':        'true',
+    'skia_enable_pdf':                  'false',
+    'skia_enable_skottie':              'false',
+    'skia_skqp_global_error_tolerance': '8',
+    'skia_tools_require_resources':     'true',
+    'skia_use_dng_sdk':                 'false',
+    'skia_use_expat':                   'true',
+    'skia_use_icu':                     'false',
+    'skia_use_libheif':                 'false',
+    'skia_use_lua':                     'false',
+    'skia_use_piex':                    'false',
+    'skia_use_vulkan':                  'true',
+}
diff --git a/tools/skqp/src/skqp.cpp b/tools/skqp/src/skqp.cpp
index e2180de..4bf57e2 100644
--- a/tools/skqp/src/skqp.cpp
+++ b/tools/skqp/src/skqp.cpp
@@ -8,7 +8,7 @@
 #include "skqp.h"
 
 #include "../../../src/core/SkStreamPriv.h"
-#include "../../tools/fonts/SkTestFontMgr.h"
+#include "../../tools/fonts/TestFontMgr.h"
 #include "GrContext.h"
 #include "GrContextOptions.h"
 #include "GrContextPriv.h"
@@ -245,7 +245,7 @@
     fReportDirectory = reportDirectory;
 
     SkGraphics::Init();
-    gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
+    gSkFontMgr_DefaultFactory = &ToolUtils::MakePortableFontMgr;
 
     /* If the file "skqp/rendertests.txt" does not exist or is empty, run all
        render tests.  Otherwise only run tests mentioned in that file.  */
@@ -456,7 +456,7 @@
         }
         const char* backendName = SkQP::GetBackendName(run.fBackend);
         std::string gmName = SkQP::GetGMName(run.fGM);
-        SkQP::RenderOutcome outcome;
+        const SkQP::RenderOutcome& outcome = run.fOutcome;
         auto str = SkStringPrintf("\"%s\",\"%s\",%d,%d,%" PRId64, backendName, gmName.c_str(),
                                   outcome.fMaxError, outcome.fBadPixelCount, outcome.fTotalError);
         write(&csvOut, SkStringPrintf("%s\n", str.c_str()));
diff --git a/tools/timer/AnimTimer.h b/tools/timer/AnimTimer.h
new file mode 100644
index 0000000..8517951
--- /dev/null
+++ b/tools/timer/AnimTimer.h
@@ -0,0 +1,166 @@
+/*
+ * 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 "SkScalar.h"
+#include "SkTime.h"
+
+#ifndef AnimTimer_DEFINED
+#define AnimTimer_DEFINED
+
+/**
+ *  Class to track a "timer". It supports 3 states: stopped, paused, and running.
+ *  Playback speed is variable.
+ *
+ *  The caller must call updateTime() to resync with the clock (typically just before
+ *  using the timer). Forcing the caller to do this ensures that the timer's return values
+ *  are consistent if called repeatedly, as they only reflect the time since the last
+ *  calle to updateTimer().
+ */
+class AnimTimer {
+public:
+    enum State { kStopped_State, kPaused_State, kRunning_State };
+
+    /**
+     *  Class begins in the "stopped" state.
+     */
+    AnimTimer() : fPreviousNanos(0), fElapsedNanos(0), fSpeed(1), fState(kStopped_State) {}
+
+    AnimTimer(double elapsed)
+            : fPreviousNanos(0), fElapsedNanos(elapsed), fSpeed(1), fState(kRunning_State) {}
+
+    bool isStopped() const { return kStopped_State == fState; }
+    bool isRunning() const { return kRunning_State == fState; }
+    bool isPaused() const { return kPaused_State == fState; }
+
+    /**
+     *  Stops the timer, and resets it, such that the next call to run or togglePauseResume
+     *  will begin at time 0.
+     */
+    void stop() { this->setState(kStopped_State); }
+
+    /**
+     *  If the timer is paused or stopped, it will resume (or start if it was stopped).
+     */
+    void run() { this->setState(kRunning_State); }
+
+    /**
+     *  Control the rate at which time advances.
+     */
+    float getSpeed() const { return fSpeed; }
+    void  setSpeed(float speed) { fSpeed = speed; }
+
+    /**
+     *  If the timer is stopped, start running, else it toggles between paused and running.
+     */
+    void togglePauseResume() {
+        if (kRunning_State == fState) {
+            this->setState(kPaused_State);
+        } else {
+            this->setState(kRunning_State);
+        }
+    }
+
+    /**
+     *  Call this each time you want to sample the clock for the timer. This is NOT done
+     *  automatically, so that repeated calls to msec() or secs() will always return the
+     *  same value.
+     *
+     *  This may safely be called with the timer in any state.
+     */
+    void updateTime() {
+        if (kRunning_State == fState) {
+            double now = SkTime::GetNSecs();
+            fElapsedNanos += (now - fPreviousNanos) * fSpeed;
+            fPreviousNanos = now;
+        }
+    }
+
+    /**
+     *  Return the time in milliseconds the timer has been in the running state.
+     *  Returns 0 if the timer is stopped. Behavior is undefined if the timer
+     *  has been running longer than SK_MSecMax.
+     */
+    SkMSec msec() const {
+        const double msec = fElapsedNanos * 1e-6;
+        SkASSERT(SK_MSecMax >= msec);
+        return static_cast<SkMSec>(msec);
+    }
+
+    /**
+     *  Return the time in seconds the timer has been in the running state.
+     *  Returns 0 if the timer is stopped.
+     */
+    double secs() const { return fElapsedNanos * 1e-9; }
+
+    /**
+     *  Return the time in seconds the timer has been in the running state,
+     *  scaled by "speed" and (if not zero) mod by period.
+     *  Returns 0 if the timer is stopped.
+     */
+    SkScalar scaled(SkScalar speed, SkScalar period = 0) const {
+        double value = this->secs() * speed;
+        if (period) {
+            value = ::fmod(value, SkScalarToDouble(period));
+        }
+        return SkDoubleToScalar(value);
+    }
+
+    /**
+     * Transitions from ends->mid->ends linearly over period seconds. The phase specifies a phase
+     * shift in seconds.
+     */
+    SkScalar pingPong(SkScalar period, SkScalar phase, SkScalar ends, SkScalar mid) const {
+        return PingPong(this->secs(), period, phase, ends, mid);
+    }
+
+    /** Helper for computing a ping-pong value without a AnimTimer object. */
+    static SkScalar PingPong(double   t,
+                             SkScalar period,
+                             SkScalar phase,
+                             SkScalar ends,
+                             SkScalar mid) {
+        double value = ::fmod(t + phase, period);
+        double half  = period / 2.0;
+        double diff  = ::fabs(value - half);
+        return SkDoubleToScalar(ends + (1.0 - diff / half) * (mid - ends));
+    }
+
+private:
+    double fPreviousNanos;
+    double fElapsedNanos;
+    float  fSpeed;
+    State  fState;
+
+    void setState(State newState) {
+        switch (newState) {
+            case kStopped_State:
+                fPreviousNanos = fElapsedNanos = 0;
+                fState                         = kStopped_State;
+                break;
+            case kPaused_State:
+                if (kRunning_State == fState) {
+                    fState = kPaused_State;
+                }  // else stay stopped or paused
+                break;
+            case kRunning_State:
+                switch (fState) {
+                    case kStopped_State:
+                        fPreviousNanos = SkTime::GetNSecs();
+                        fElapsedNanos  = 0;
+                        break;
+                    case kPaused_State:  // they want "resume"
+                        fPreviousNanos = SkTime::GetNSecs();
+                        break;
+                    case kRunning_State: break;
+                }
+                fState = kRunning_State;
+                break;
+        }
+    }
+};
+
+#endif
diff --git a/tools/timer/SkAnimTimer.h b/tools/timer/SkAnimTimer.h
deleted file mode 100644
index bcec0080..0000000
--- a/tools/timer/SkAnimTimer.h
+++ /dev/null
@@ -1,175 +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 "SkScalar.h"
-#include "SkTime.h"
-
-#ifndef SkAnimTimer_DEFINED
-#define SkAnimTimer_DEFINED
-
-/**
- *  Class to track a "timer". It supports 3 states: stopped, paused, and running.
- *  Playback speed is variable.
- *
- *  The caller must call updateTime() to resync with the clock (typically just before
- *  using the timer). Forcing the caller to do this ensures that the timer's return values
- *  are consistent if called repeatedly, as they only reflect the time since the last
- *  calle to updateTimer().
- */
-class SkAnimTimer {
-public:
-    enum State {
-        kStopped_State,
-        kPaused_State,
-        kRunning_State
-    };
-
-    /**
-     *  Class begins in the "stopped" state.
-     */
-    SkAnimTimer() : fPreviousNanos(0), fElapsedNanos(0), fSpeed(1), fState(kStopped_State) {}
-
-    SkAnimTimer(double elapsed)
-        : fPreviousNanos(0)
-        , fElapsedNanos(elapsed)
-        , fSpeed(1)
-        , fState(kRunning_State) {}
-
-    bool isStopped() const { return kStopped_State == fState; }
-    bool isRunning() const { return kRunning_State == fState; }
-    bool isPaused() const { return kPaused_State == fState; }
-
-    /**
-     *  Stops the timer, and resets it, such that the next call to run or togglePauseResume
-     *  will begin at time 0.
-     */
-    void stop() {
-        this->setState(kStopped_State);
-    }
-
-    /**
-     *  If the timer is paused or stopped, it will resume (or start if it was stopped).
-     */
-    void run() {
-        this->setState(kRunning_State);
-    }
-
-    /**
-     *  Control the rate at which time advances.
-     */
-    float getSpeed() const { return fSpeed; }
-    void setSpeed(float speed) { fSpeed = speed; }
-
-    /**
-     *  If the timer is stopped, start running, else it toggles between paused and running.
-     */
-    void togglePauseResume() {
-        if (kRunning_State == fState) {
-            this->setState(kPaused_State);
-        } else {
-            this->setState(kRunning_State);
-        }
-    }
-
-    /**
-     *  Call this each time you want to sample the clock for the timer. This is NOT done
-     *  automatically, so that repeated calls to msec() or secs() will always return the
-     *  same value.
-     *
-     *  This may safely be called with the timer in any state.
-     */
-    void updateTime() {
-        if (kRunning_State == fState) {
-            double now = SkTime::GetNSecs();
-            fElapsedNanos += (now - fPreviousNanos) * fSpeed;
-            fPreviousNanos = now;
-        }
-    }
-
-    /**
-     *  Return the time in milliseconds the timer has been in the running state.
-     *  Returns 0 if the timer is stopped. Behavior is undefined if the timer
-     *  has been running longer than SK_MSecMax.
-     */
-    SkMSec msec() const {
-        const double msec = fElapsedNanos * 1e-6;
-        SkASSERT(SK_MSecMax >= msec);
-        return static_cast<SkMSec>(msec);
-    }
-
-    /**
-     *  Return the time in seconds the timer has been in the running state.
-     *  Returns 0 if the timer is stopped.
-     */
-    double secs() const { return fElapsedNanos * 1e-9; }
-
-    /**
-     *  Return the time in seconds the timer has been in the running state,
-     *  scaled by "speed" and (if not zero) mod by period.
-     *  Returns 0 if the timer is stopped.
-     */
-    SkScalar scaled(SkScalar speed, SkScalar period = 0) const {
-        double value = this->secs() * speed;
-        if (period) {
-            value = ::fmod(value, SkScalarToDouble(period));
-        }
-        return SkDoubleToScalar(value);
-    }
-
-    /**
-     * Transitions from ends->mid->ends linearly over period seconds. The phase specifies a phase
-     * shift in seconds.
-     */
-    SkScalar pingPong(SkScalar period, SkScalar phase, SkScalar ends, SkScalar mid) const {
-        return PingPong(this->secs(), period, phase, ends, mid);
-    }
-
-    /** Helper for computing a ping-pong value without a SkAnimTimer object. */
-    static SkScalar PingPong(double t, SkScalar period, SkScalar phase, SkScalar ends,
-                             SkScalar mid) {
-        double value = ::fmod(t + phase, period);
-        double half = period / 2.0;
-        double diff = ::fabs(value - half);
-        return SkDoubleToScalar(ends + (1.0 - diff / half) * (mid - ends));
-    }
-
-private:
-    double fPreviousNanos;
-    double fElapsedNanos;
-    float  fSpeed;
-    State  fState;
-
-    void setState(State newState) {
-        switch (newState) {
-            case kStopped_State:
-                fPreviousNanos = fElapsedNanos = 0;
-                fState = kStopped_State;
-                break;
-            case kPaused_State:
-                if (kRunning_State == fState) {
-                    fState = kPaused_State;
-                } // else stay stopped or paused
-                break;
-            case kRunning_State:
-                switch (fState) {
-                    case kStopped_State:
-                        fPreviousNanos = SkTime::GetNSecs();
-                        fElapsedNanos = 0;
-                        break;
-                    case kPaused_State:  // they want "resume"
-                        fPreviousNanos = SkTime::GetNSecs();
-                        break;
-                    case kRunning_State:
-                        break;
-                }
-                fState = kRunning_State;
-                break;
-        }
-    }
-};
-
-#endif
diff --git a/tools/trace/ChromeTracingTracer.cpp b/tools/trace/ChromeTracingTracer.cpp
new file mode 100644
index 0000000..a6e84e4
--- /dev/null
+++ b/tools/trace/ChromeTracingTracer.cpp
@@ -0,0 +1,313 @@
+/*
+ * 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 "ChromeTracingTracer.h"
+#include "SkJSONWriter.h"
+#include "SkOSFile.h"
+#include "SkOSPath.h"
+#include "SkStream.h"
+#include "SkThreadID.h"
+#include "SkTraceEvent.h"
+
+#include <chrono>
+
+namespace {
+
+/**
+ * All events have a fixed block of information (TraceEvent), plus variable length payload:
+ * {TraceEvent} {TraceEventArgs} {Inline Payload}
+ */
+struct TraceEventArg {
+    uint8_t     fArgType;
+    const char* fArgName;
+    uint64_t    fArgValue;
+};
+
+// These fields are ordered to minimize size due to alignment. Argument types could be packed
+// better, but very few events have many arguments, so the net loss is pretty small.
+struct TraceEvent {
+    char     fPhase;
+    uint8_t  fNumArgs;
+    uint32_t fSize;
+
+    const char* fName;
+    // TODO: Merge fID and fClockEnd (never used together)
+    uint64_t   fID;
+    uint64_t   fClockBegin;
+    uint64_t   fClockEnd;
+    SkThreadID fThreadID;
+
+    TraceEvent* next() {
+        return reinterpret_cast<TraceEvent*>(reinterpret_cast<char*>(this) + fSize);
+    }
+    TraceEventArg* args() { return reinterpret_cast<TraceEventArg*>(this + 1); }
+    char*          stringTable() { return reinterpret_cast<char*>(this->args() + fNumArgs); }
+};
+
+}  // namespace
+
+ChromeTracingTracer::ChromeTracingTracer(const char* filename) : fFilename(filename) {
+    this->createBlock();
+}
+
+ChromeTracingTracer::~ChromeTracingTracer() { this->flush(); }
+
+void ChromeTracingTracer::createBlock() {
+    fCurBlock.fBlock         = BlockPtr(new uint8_t[kBlockSize]);
+    fCurBlock.fEventsInBlock = 0;
+    fCurBlockUsed            = 0;
+}
+
+SkEventTracer::Handle ChromeTracingTracer::appendEvent(const void* data, size_t size) {
+    SkASSERT(size > 0 && size <= kBlockSize);
+
+    SkAutoMutexAcquire lock(fMutex);
+    if (fCurBlockUsed + size > kBlockSize) {
+        fBlocks.push_back(std::move(fCurBlock));
+        this->createBlock();
+    }
+    memcpy(fCurBlock.fBlock.get() + fCurBlockUsed, data, size);
+    Handle handle = reinterpret_cast<Handle>(fCurBlock.fBlock.get() + fCurBlockUsed);
+    fCurBlockUsed += size;
+    fCurBlock.fEventsInBlock++;
+    return handle;
+}
+
+SkEventTracer::Handle ChromeTracingTracer::addTraceEvent(char            phase,
+                                                         const uint8_t*  categoryEnabledFlag,
+                                                         const char*     name,
+                                                         uint64_t        id,
+                                                         int             numArgs,
+                                                         const char**    argNames,
+                                                         const uint8_t*  argTypes,
+                                                         const uint64_t* argValues,
+                                                         uint8_t         flags) {
+    // TODO: Respect flags (or assert). INSTANT events encode scope in flags, should be stored
+    // using "s" key in JSON. COPY flag should be supported or rejected.
+
+    // Figure out how much extra storage we need for copied strings
+    int size = static_cast<int>(sizeof(TraceEvent) + numArgs * sizeof(TraceEventArg));
+    for (int i = 0; i < numArgs; ++i) {
+        if (TRACE_VALUE_TYPE_COPY_STRING == argTypes[i]) {
+            skia::tracing_internals::TraceValueUnion value;
+            value.as_uint = argValues[i];
+            size += strlen(value.as_string) + 1;
+        }
+    }
+
+    size = SkAlign8(size);
+
+    SkSTArray<128, uint8_t, true> storage;
+    uint8_t*                      storagePtr = storage.push_back_n(size);
+
+    TraceEvent* traceEvent  = reinterpret_cast<TraceEvent*>(storagePtr);
+    traceEvent->fPhase      = phase;
+    traceEvent->fNumArgs    = numArgs;
+    traceEvent->fSize       = size;
+    traceEvent->fName       = name;
+    traceEvent->fID         = id;
+    traceEvent->fClockBegin = std::chrono::steady_clock::now().time_since_epoch().count();
+    traceEvent->fClockEnd   = 0;
+    traceEvent->fThreadID   = SkGetThreadID();
+
+    TraceEventArg* traceEventArgs  = traceEvent->args();
+    char*          stringTableBase = traceEvent->stringTable();
+    char*          stringTable     = stringTableBase;
+    for (int i = 0; i < numArgs; ++i) {
+        traceEventArgs[i].fArgName = argNames[i];
+        traceEventArgs[i].fArgType = argTypes[i];
+        if (TRACE_VALUE_TYPE_COPY_STRING == argTypes[i]) {
+            // Just write an offset into the arguments array
+            traceEventArgs[i].fArgValue = stringTable - stringTableBase;
+
+            // Copy string into our buffer (and advance)
+            skia::tracing_internals::TraceValueUnion value;
+            value.as_uint = argValues[i];
+            while (*value.as_string) {
+                *stringTable++ = *value.as_string++;
+            }
+            *stringTable++ = 0;
+        } else {
+            traceEventArgs[i].fArgValue = argValues[i];
+        }
+    }
+
+    return this->appendEvent(storagePtr, size);
+}
+
+void ChromeTracingTracer::updateTraceEventDuration(const uint8_t*        categoryEnabledFlag,
+                                                   const char*           name,
+                                                   SkEventTracer::Handle handle) {
+    // We could probably get away with not locking here, but let's be totally safe.
+    SkAutoMutexAcquire lock(fMutex);
+    TraceEvent*        traceEvent = reinterpret_cast<TraceEvent*>(handle);
+    traceEvent->fClockEnd         = std::chrono::steady_clock::now().time_since_epoch().count();
+}
+
+static void trace_value_to_json(SkJSONWriter* writer,
+                                uint64_t      argValue,
+                                uint8_t       argType,
+                                const char*   stringTableBase) {
+    skia::tracing_internals::TraceValueUnion value;
+    value.as_uint = argValue;
+
+    switch (argType) {
+        case TRACE_VALUE_TYPE_BOOL: writer->appendBool(value.as_bool); break;
+        case TRACE_VALUE_TYPE_UINT: writer->appendU64(value.as_uint); break;
+        case TRACE_VALUE_TYPE_INT: writer->appendS64(value.as_int); break;
+        case TRACE_VALUE_TYPE_DOUBLE: writer->appendDouble(value.as_double); break;
+        case TRACE_VALUE_TYPE_POINTER: writer->appendPointer(value.as_pointer); break;
+        case TRACE_VALUE_TYPE_STRING: writer->appendString(value.as_string); break;
+        case TRACE_VALUE_TYPE_COPY_STRING:
+            writer->appendString(stringTableBase + value.as_uint);
+            break;
+        default: writer->appendString("<unknown type>"); break;
+    }
+}
+
+namespace {
+
+struct TraceEventSerializationState {
+    TraceEventSerializationState(uint64_t clockOffset)
+            : fClockOffset(clockOffset), fNextThreadID(0) {}
+
+    int getShortThreadID(SkThreadID id) {
+        if (int* shortIDPtr = fShortThreadIDMap.find(id)) {
+            return *shortIDPtr;
+        }
+        int shortID = fNextThreadID++;
+        fShortThreadIDMap.set(id, shortID);
+        return shortID;
+    }
+
+    uint64_t                          fClockOffset;
+    SkTHashMap<uint64_t, const char*> fBaseTypeResolver;
+    int                               fNextThreadID;
+    SkTHashMap<SkThreadID, int>       fShortThreadIDMap;
+};
+
+}  // namespace
+
+static void trace_event_to_json(SkJSONWriter*                 writer,
+                                TraceEvent*                   traceEvent,
+                                TraceEventSerializationState* serializationState) {
+    // We track the original (creation time) "name" of each currently live object, so we can
+    // automatically insert "base_name" fields in object snapshot events.
+    auto baseTypeResolver = &(serializationState->fBaseTypeResolver);
+    if (TRACE_EVENT_PHASE_CREATE_OBJECT == traceEvent->fPhase) {
+        SkASSERT(nullptr == baseTypeResolver->find(traceEvent->fID));
+        baseTypeResolver->set(traceEvent->fID, traceEvent->fName);
+    } else if (TRACE_EVENT_PHASE_DELETE_OBJECT == traceEvent->fPhase) {
+        SkASSERT(nullptr != baseTypeResolver->find(traceEvent->fID));
+        baseTypeResolver->remove(traceEvent->fID);
+    }
+
+    writer->beginObject();
+
+    char phaseString[2] = {traceEvent->fPhase, 0};
+    writer->appendString("ph", phaseString);
+    writer->appendString("name", traceEvent->fName);
+    if (0 != traceEvent->fID) {
+        // IDs are (almost) always pointers
+        writer->appendPointer("id", reinterpret_cast<void*>(traceEvent->fID));
+    }
+
+    // Offset timestamps to reduce JSON length, then convert nanoseconds to microseconds
+    // (standard time unit for tracing JSON files).
+    uint64_t relativeTimestamp =
+            static_cast<int64_t>(traceEvent->fClockBegin - serializationState->fClockOffset);
+    writer->appendDoubleDigits("ts", static_cast<double>(relativeTimestamp) * 1E-3, 3);
+    if (0 != traceEvent->fClockEnd) {
+        double dur = static_cast<double>(traceEvent->fClockEnd - traceEvent->fClockBegin) * 1E-3;
+        writer->appendDoubleDigits("dur", dur, 3);
+    }
+
+    writer->appendS64("tid", serializationState->getShortThreadID(traceEvent->fThreadID));
+    // Trace events *must* include a process ID, but for internal tools this isn't particularly
+    // important (and certainly not worth adding a cross-platform API to get it).
+    writer->appendS32("pid", 0);
+
+    if (traceEvent->fNumArgs) {
+        writer->beginObject("args");
+        const char* stringTable   = traceEvent->stringTable();
+        bool        addedSnapshot = false;
+        if (TRACE_EVENT_PHASE_SNAPSHOT_OBJECT == traceEvent->fPhase &&
+            baseTypeResolver->find(traceEvent->fID) &&
+            0 != strcmp(*baseTypeResolver->find(traceEvent->fID), traceEvent->fName)) {
+            // Special handling for snapshots where the name differs from creation.
+            writer->beginObject("snapshot");
+            writer->appendString("base_type", *baseTypeResolver->find(traceEvent->fID));
+            addedSnapshot = true;
+        }
+
+        for (int i = 0; i < traceEvent->fNumArgs; ++i) {
+            const TraceEventArg* arg = traceEvent->args() + i;
+            // TODO: Skip '#'
+            writer->appendName(arg->fArgName);
+
+            if (arg->fArgName && '#' == arg->fArgName[0]) {
+                writer->beginObject();
+                writer->appendName("id_ref");
+                trace_value_to_json(writer, arg->fArgValue, arg->fArgType, stringTable);
+                writer->endObject();
+            } else {
+                trace_value_to_json(writer, arg->fArgValue, arg->fArgType, stringTable);
+            }
+        }
+
+        if (addedSnapshot) {
+            writer->endObject();
+        }
+
+        writer->endObject();
+    }
+
+    writer->endObject();
+}
+
+void ChromeTracingTracer::flush() {
+    SkAutoMutexAcquire lock(fMutex);
+
+    SkString dirname = SkOSPath::Dirname(fFilename.c_str());
+    if (!dirname.isEmpty() && !sk_exists(dirname.c_str(), kWrite_SkFILE_Flag)) {
+        if (!sk_mkdir(dirname.c_str())) {
+            SkDebugf("Failed to create directory.");
+        }
+    }
+
+    SkFILEWStream fileStream(fFilename.c_str());
+    SkJSONWriter  writer(&fileStream, SkJSONWriter::Mode::kFast);
+    writer.beginArray();
+
+    uint64_t clockOffset = 0;
+    if (fBlocks.count() > 0) {
+        clockOffset = reinterpret_cast<TraceEvent*>(fBlocks[0].fBlock.get())->fClockBegin;
+    } else if (fCurBlock.fEventsInBlock > 0) {
+        clockOffset = reinterpret_cast<TraceEvent*>(fCurBlock.fBlock.get())->fClockBegin;
+    }
+
+    TraceEventSerializationState serializationState(clockOffset);
+
+    auto event_block_to_json = [](SkJSONWriter*                 writer,
+                                  const TraceEventBlock&        block,
+                                  TraceEventSerializationState* serializationState) {
+        TraceEvent* traceEvent = reinterpret_cast<TraceEvent*>(block.fBlock.get());
+        for (int i = 0; i < block.fEventsInBlock; ++i) {
+            trace_event_to_json(writer, traceEvent, serializationState);
+            traceEvent = traceEvent->next();
+        }
+    };
+
+    for (int i = 0; i < fBlocks.count(); ++i) {
+        event_block_to_json(&writer, fBlocks[i], &serializationState);
+    }
+    event_block_to_json(&writer, fCurBlock, &serializationState);
+
+    writer.endArray();
+    writer.flush();
+    fileStream.flush();
+}
diff --git a/tools/trace/ChromeTracingTracer.h b/tools/trace/ChromeTracingTracer.h
new file mode 100644
index 0000000..266409d
--- /dev/null
+++ b/tools/trace/ChromeTracingTracer.h
@@ -0,0 +1,79 @@
+/*
+ * 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 ChromeTracingTracer_DEFINED
+#define ChromeTracingTracer_DEFINED
+
+#include "EventTracingPriv.h"
+#include "SkEventTracer.h"
+#include "SkSpinlock.h"
+#include "SkString.h"
+#include "SkTHash.h"
+
+class SkJSONWriter;
+
+/**
+ * A SkEventTracer implementation that logs events to JSON for viewing with chrome://tracing.
+ */
+class ChromeTracingTracer : public SkEventTracer {
+public:
+    ChromeTracingTracer(const char* filename);
+    ~ChromeTracingTracer() override;
+
+    SkEventTracer::Handle addTraceEvent(char            phase,
+                                        const uint8_t*  categoryEnabledFlag,
+                                        const char*     name,
+                                        uint64_t        id,
+                                        int             numArgs,
+                                        const char**    argNames,
+                                        const uint8_t*  argTypes,
+                                        const uint64_t* argValues,
+                                        uint8_t         flags) override;
+
+    void updateTraceEventDuration(const uint8_t*        categoryEnabledFlag,
+                                  const char*           name,
+                                  SkEventTracer::Handle handle) override;
+
+    const uint8_t* getCategoryGroupEnabled(const char* name) override {
+        return fCategories.getCategoryGroupEnabled(name);
+    }
+
+    const char* getCategoryGroupName(const uint8_t* categoryEnabledFlag) override {
+        return fCategories.getCategoryGroupName(categoryEnabledFlag);
+    }
+
+private:
+    void flush();
+
+    enum {
+        // Events are variable size, but most commonly 48 bytes, assuming 64-bit pointers and
+        // reasonable packing. This is a first guess at a number that balances memory usage vs.
+        // time overhead of allocating blocks.
+        kBlockSize = 512 * 1024,
+    };
+
+    typedef std::unique_ptr<uint8_t[]> BlockPtr;
+    struct TraceEventBlock {
+        BlockPtr fBlock;
+        int      fEventsInBlock;
+    };
+
+    void createBlock();
+
+    Handle appendEvent(const void* data, size_t size);
+
+    SkString                 fFilename;
+    SkSpinlock               fMutex;
+    SkEventTracingCategories fCategories;
+
+    TraceEventBlock fCurBlock;
+    size_t          fCurBlockUsed;
+
+    SkTArray<TraceEventBlock> fBlocks;
+};
+
+#endif
diff --git a/tools/trace/EventTracingPriv.cpp b/tools/trace/EventTracingPriv.cpp
new file mode 100644
index 0000000..e3d9c2c
--- /dev/null
+++ b/tools/trace/EventTracingPriv.cpp
@@ -0,0 +1,88 @@
+/*
+ * 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 "EventTracingPriv.h"
+
+#include "ChromeTracingTracer.h"
+#include "CommandLineFlags.h"
+#include "SkATrace.h"
+#include "SkDebugfTracer.h"
+#include "SkEventTracer.h"
+#include "SkTraceEvent.h"
+
+static DEFINE_string(trace,
+              "",
+              "Log trace events in one of several modes:\n"
+              "  debugf     : Show events using SkDebugf\n"
+              "  atrace     : Send events to Android ATrace\n"
+              "  <filename> : Any other string is interpreted as a filename. Writes\n"
+              "               trace events to specified file as JSON, for viewing\n"
+              "               with chrome://tracing");
+
+static DEFINE_string(traceMatch,
+              "",
+              "Filter which categories are traced.\n"
+              "Uses same format as --match\n");
+
+void initializeEventTracingForTools(const char* traceFlag) {
+    if (!traceFlag) {
+        if (FLAGS_trace.isEmpty()) {
+            return;
+        }
+        traceFlag = FLAGS_trace[0];
+    }
+
+    SkEventTracer* eventTracer = nullptr;
+    if (0 == strcmp(traceFlag, "atrace")) {
+        eventTracer = new SkATrace();
+    } else if (0 == strcmp(traceFlag, "debugf")) {
+        eventTracer = new SkDebugfTracer();
+    } else {
+        eventTracer = new ChromeTracingTracer(traceFlag);
+    }
+
+    SkAssertResult(SkEventTracer::SetInstance(eventTracer));
+}
+
+uint8_t* SkEventTracingCategories::getCategoryGroupEnabled(const char* name) {
+    static_assert(0 == offsetof(CategoryState, fEnabled), "CategoryState");
+
+    // We ignore the "disabled-by-default-" prefix in our internal tools
+    if (SkStrStartsWith(name, TRACE_CATEGORY_PREFIX)) {
+        name += strlen(TRACE_CATEGORY_PREFIX);
+    }
+
+    // Chrome's implementation of this API does a two-phase lookup (once without a lock, then again
+    // with a lock. But the tracing macros avoid calling these functions more than once per site,
+    // so just do something simple (and easier to reason about):
+    SkAutoMutexAcquire lock(&fMutex);
+    for (int i = 0; i < fNumCategories; ++i) {
+        if (0 == strcmp(name, fCategories[i].fName)) {
+            return reinterpret_cast<uint8_t*>(&fCategories[i]);
+        }
+    }
+
+    if (fNumCategories >= kMaxCategories) {
+        SkDEBUGFAIL("Exhausted event tracing categories. Increase kMaxCategories.");
+        return reinterpret_cast<uint8_t*>(&fCategories[0]);
+    }
+
+    fCategories[fNumCategories].fEnabled =
+            CommandLineFlags::ShouldSkip(FLAGS_traceMatch, name)
+                    ? 0
+                    : SkEventTracer::kEnabledForRecording_CategoryGroupEnabledFlags;
+
+    fCategories[fNumCategories].fName = name;
+    return reinterpret_cast<uint8_t*>(&fCategories[fNumCategories++]);
+}
+
+const char* SkEventTracingCategories::getCategoryGroupName(const uint8_t* categoryEnabledFlag) {
+    if (categoryEnabledFlag) {
+        return reinterpret_cast<const CategoryState*>(categoryEnabledFlag)->fName;
+    }
+    return nullptr;
+}
diff --git a/tools/trace/EventTracingPriv.h b/tools/trace/EventTracingPriv.h
new file mode 100644
index 0000000..6485dc9
--- /dev/null
+++ b/tools/trace/EventTracingPriv.h
@@ -0,0 +1,42 @@
+/*
+ * 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 EventTracingPriv_DEFINED
+#define EventTracingPriv_DEFINED
+
+#include "SkMutex.h"
+
+/**
+ * Construct and install an SkEventTracer, based on the mode,
+ * defaulting to the --trace command line argument.
+ */
+void initializeEventTracingForTools(const char* mode = nullptr);
+
+/**
+ * Helper class used by internal implementations of SkEventTracer to manage categories.
+ */
+class SkEventTracingCategories {
+public:
+    SkEventTracingCategories() : fNumCategories(0) {}
+
+    uint8_t*    getCategoryGroupEnabled(const char* name);
+    const char* getCategoryGroupName(const uint8_t* categoryEnabledFlag);
+
+private:
+    enum { kMaxCategories = 256 };
+
+    struct CategoryState {
+        uint8_t     fEnabled;
+        const char* fName;
+    };
+
+    CategoryState fCategories[kMaxCategories];
+    int           fNumCategories;
+    SkMutex       fMutex;
+};
+
+#endif
diff --git a/tools/trace/SkChromeTracingTracer.cpp b/tools/trace/SkChromeTracingTracer.cpp
deleted file mode 100644
index f1f201f..0000000
--- a/tools/trace/SkChromeTracingTracer.cpp
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkChromeTracingTracer.h"
-#include "SkJSONWriter.h"
-#include "SkThreadID.h"
-#include "SkTraceEvent.h"
-#include "SkOSFile.h"
-#include "SkOSPath.h"
-#include "SkStream.h"
-
-#include <chrono>
-
-namespace {
-
-/**
- * All events have a fixed block of information (TraceEvent), plus variable length payload:
- * {TraceEvent} {TraceEventArgs} {Inline Payload}
- */
-struct TraceEventArg {
-    uint8_t fArgType;
-    const char* fArgName;
-    uint64_t fArgValue;
-};
-
-// These fields are ordered to minimize size due to alignment. Argument types could be packed
-// better, but very few events have many arguments, so the net loss is pretty small.
-struct TraceEvent {
-    char fPhase;
-    uint8_t fNumArgs;
-    uint32_t fSize;
-
-    const char* fName;
-    // TODO: Merge fID and fClockEnd (never used together)
-    uint64_t fID;
-    uint64_t fClockBegin;
-    uint64_t fClockEnd;
-    SkThreadID fThreadID;
-
-    TraceEvent* next() {
-        return reinterpret_cast<TraceEvent*>(reinterpret_cast<char*>(this) + fSize);
-    }
-    TraceEventArg* args() {
-        return reinterpret_cast<TraceEventArg*>(this + 1);
-    }
-    char* stringTable() {
-        return reinterpret_cast<char*>(this->args() + fNumArgs);
-    }
-};
-
-}
-
-SkChromeTracingTracer::SkChromeTracingTracer(const char* filename) : fFilename(filename) {
-    this->createBlock();
-}
-
-SkChromeTracingTracer::~SkChromeTracingTracer() {
-    this->flush();
-}
-
-void SkChromeTracingTracer::createBlock() {
-    fCurBlock.fBlock = BlockPtr(new uint8_t[kBlockSize]);
-    fCurBlock.fEventsInBlock = 0;
-    fCurBlockUsed = 0;
-}
-
-SkEventTracer::Handle SkChromeTracingTracer::appendEvent(const void* data, size_t size) {
-    SkASSERT(size > 0 && size <= kBlockSize);
-
-    SkAutoMutexAcquire lock(fMutex);
-    if (fCurBlockUsed + size > kBlockSize) {
-        fBlocks.push_back(std::move(fCurBlock));
-        this->createBlock();
-    }
-    memcpy(fCurBlock.fBlock.get() + fCurBlockUsed, data, size);
-    Handle handle = reinterpret_cast<Handle>(fCurBlock.fBlock.get() + fCurBlockUsed);
-    fCurBlockUsed += size;
-    fCurBlock.fEventsInBlock++;
-    return handle;
-}
-
-SkEventTracer::Handle SkChromeTracingTracer::addTraceEvent(char phase,
-                                                           const uint8_t* categoryEnabledFlag,
-                                                           const char* name,
-                                                           uint64_t id,
-                                                           int numArgs,
-                                                           const char** argNames,
-                                                           const uint8_t* argTypes,
-                                                           const uint64_t* argValues,
-                                                           uint8_t flags) {
-    // TODO: Respect flags (or assert). INSTANT events encode scope in flags, should be stored
-    // using "s" key in JSON. COPY flag should be supported or rejected.
-
-    // Figure out how much extra storage we need for copied strings
-    int size = static_cast<int>(sizeof(TraceEvent) + numArgs * sizeof(TraceEventArg));
-    for (int i = 0; i < numArgs; ++i) {
-        if (TRACE_VALUE_TYPE_COPY_STRING == argTypes[i]) {
-            skia::tracing_internals::TraceValueUnion value;
-            value.as_uint = argValues[i];
-            size += strlen(value.as_string) + 1;
-        }
-    }
-
-    size = SkAlign8(size);
-
-    SkSTArray<128, uint8_t, true> storage;
-    uint8_t* storagePtr = storage.push_back_n(size);
-
-    TraceEvent* traceEvent = reinterpret_cast<TraceEvent*>(storagePtr);
-    traceEvent->fPhase = phase;
-    traceEvent->fNumArgs = numArgs;
-    traceEvent->fSize = size;
-    traceEvent->fName = name;
-    traceEvent->fID = id;
-    traceEvent->fClockBegin = std::chrono::steady_clock::now().time_since_epoch().count();
-    traceEvent->fClockEnd = 0;
-    traceEvent->fThreadID = SkGetThreadID();
-
-    TraceEventArg* traceEventArgs = traceEvent->args();
-    char* stringTableBase = traceEvent->stringTable();
-    char* stringTable = stringTableBase;
-    for (int i = 0; i < numArgs; ++i) {
-        traceEventArgs[i].fArgName = argNames[i];
-        traceEventArgs[i].fArgType = argTypes[i];
-        if (TRACE_VALUE_TYPE_COPY_STRING == argTypes[i]) {
-            // Just write an offset into the arguments array
-            traceEventArgs[i].fArgValue = stringTable  - stringTableBase;
-
-            // Copy string into our buffer (and advance)
-            skia::tracing_internals::TraceValueUnion value;
-            value.as_uint = argValues[i];
-            while (*value.as_string) {
-                *stringTable++ = *value.as_string++;
-            }
-            *stringTable++ = 0;
-        } else {
-            traceEventArgs[i].fArgValue = argValues[i];
-        }
-    }
-
-    return this->appendEvent(storagePtr, size);
-}
-
-void SkChromeTracingTracer::updateTraceEventDuration(const uint8_t* categoryEnabledFlag,
-                                                     const char* name,
-                                                     SkEventTracer::Handle handle) {
-    // We could probably get away with not locking here, but let's be totally safe.
-    SkAutoMutexAcquire lock(fMutex);
-    TraceEvent* traceEvent = reinterpret_cast<TraceEvent*>(handle);
-    traceEvent->fClockEnd = std::chrono::steady_clock::now().time_since_epoch().count();
-}
-
-static void trace_value_to_json(SkJSONWriter* writer, uint64_t argValue, uint8_t argType,
-                                const char* stringTableBase) {
-    skia::tracing_internals::TraceValueUnion value;
-    value.as_uint = argValue;
-
-    switch (argType) {
-        case TRACE_VALUE_TYPE_BOOL:
-            writer->appendBool(value.as_bool);
-            break;
-        case TRACE_VALUE_TYPE_UINT:
-            writer->appendU64(value.as_uint);
-            break;
-        case TRACE_VALUE_TYPE_INT:
-            writer->appendS64(value.as_int);
-            break;
-        case TRACE_VALUE_TYPE_DOUBLE:
-            writer->appendDouble(value.as_double);
-            break;
-        case TRACE_VALUE_TYPE_POINTER:
-            writer->appendPointer(value.as_pointer);
-            break;
-        case TRACE_VALUE_TYPE_STRING:
-            writer->appendString(value.as_string);
-            break;
-        case TRACE_VALUE_TYPE_COPY_STRING:
-            writer->appendString(stringTableBase + value.as_uint);
-            break;
-        default:
-            writer->appendString("<unknown type>");
-            break;
-    }
-}
-
-namespace {
-
-struct TraceEventSerializationState {
-    TraceEventSerializationState(uint64_t clockOffset)
-            : fClockOffset(clockOffset), fNextThreadID(0) {}
-
-    int getShortThreadID(SkThreadID id) {
-        if (int* shortIDPtr = fShortThreadIDMap.find(id)) {
-            return *shortIDPtr;
-        }
-        int shortID = fNextThreadID++;
-        fShortThreadIDMap.set(id, shortID);
-        return shortID;
-    }
-
-    uint64_t fClockOffset;
-    SkTHashMap<uint64_t, const char*> fBaseTypeResolver;
-    int fNextThreadID;
-    SkTHashMap<SkThreadID, int> fShortThreadIDMap;
-};
-
-}
-
-static void trace_event_to_json(SkJSONWriter* writer, TraceEvent* traceEvent,
-                                TraceEventSerializationState* serializationState) {
-    // We track the original (creation time) "name" of each currently live object, so we can
-    // automatically insert "base_name" fields in object snapshot events.
-    auto baseTypeResolver = &(serializationState->fBaseTypeResolver);
-    if (TRACE_EVENT_PHASE_CREATE_OBJECT == traceEvent->fPhase) {
-        SkASSERT(nullptr == baseTypeResolver->find(traceEvent->fID));
-        baseTypeResolver->set(traceEvent->fID, traceEvent->fName);
-    } else if (TRACE_EVENT_PHASE_DELETE_OBJECT == traceEvent->fPhase) {
-        SkASSERT(nullptr != baseTypeResolver->find(traceEvent->fID));
-        baseTypeResolver->remove(traceEvent->fID);
-    }
-
-    writer->beginObject();
-
-    char phaseString[2] = { traceEvent->fPhase, 0 };
-    writer->appendString("ph", phaseString);
-    writer->appendString("name", traceEvent->fName);
-    if (0 != traceEvent->fID) {
-        // IDs are (almost) always pointers
-        writer->appendPointer("id", reinterpret_cast<void*>(traceEvent->fID));
-    }
-
-    // Offset timestamps to reduce JSON length, then convert nanoseconds to microseconds
-    // (standard time unit for tracing JSON files).
-    uint64_t relativeTimestamp = static_cast<int64_t>(traceEvent->fClockBegin -
-                                                      serializationState->fClockOffset);
-    writer->appendDoubleDigits("ts", static_cast<double>(relativeTimestamp) * 1E-3, 3);
-    if (0 != traceEvent->fClockEnd) {
-        double dur = static_cast<double>(traceEvent->fClockEnd - traceEvent->fClockBegin) * 1E-3;
-        writer->appendDoubleDigits("dur", dur, 3);
-    }
-
-    writer->appendS64("tid", serializationState->getShortThreadID(traceEvent->fThreadID));
-    // Trace events *must* include a process ID, but for internal tools this isn't particularly
-    // important (and certainly not worth adding a cross-platform API to get it).
-    writer->appendS32("pid", 0);
-
-    if (traceEvent->fNumArgs) {
-        writer->beginObject("args");
-        const char* stringTable = traceEvent->stringTable();
-        bool addedSnapshot = false;
-        if (TRACE_EVENT_PHASE_SNAPSHOT_OBJECT == traceEvent->fPhase &&
-                baseTypeResolver->find(traceEvent->fID) &&
-                0 != strcmp(*baseTypeResolver->find(traceEvent->fID), traceEvent->fName)) {
-            // Special handling for snapshots where the name differs from creation.
-            writer->beginObject("snapshot");
-            writer->appendString("base_type", *baseTypeResolver->find(traceEvent->fID));
-            addedSnapshot = true;
-        }
-
-        for (int i = 0; i < traceEvent->fNumArgs; ++i) {
-            const TraceEventArg* arg = traceEvent->args() + i;
-            // TODO: Skip '#'
-            writer->appendName(arg->fArgName);
-
-            if (arg->fArgName && '#' == arg->fArgName[0]) {
-                writer->beginObject();
-                writer->appendName("id_ref");
-                trace_value_to_json(writer, arg->fArgValue, arg->fArgType, stringTable);
-                writer->endObject();
-            } else {
-                trace_value_to_json(writer, arg->fArgValue, arg->fArgType, stringTable);
-            }
-        }
-
-        if (addedSnapshot) {
-            writer->endObject();
-        }
-
-        writer->endObject();
-    }
-
-    writer->endObject();
-}
-
-void SkChromeTracingTracer::flush() {
-    SkAutoMutexAcquire lock(fMutex);
-
-    SkString dirname = SkOSPath::Dirname(fFilename.c_str());
-    if (!dirname.isEmpty() && !sk_exists(dirname.c_str(), kWrite_SkFILE_Flag)) {
-        if (!sk_mkdir(dirname.c_str())) {
-            SkDebugf("Failed to create directory.");
-        }
-    }
-
-    SkFILEWStream fileStream(fFilename.c_str());
-    SkJSONWriter writer(&fileStream, SkJSONWriter::Mode::kFast);
-    writer.beginArray();
-
-    uint64_t clockOffset = 0;
-    if (fBlocks.count() > 0) {
-        clockOffset = reinterpret_cast<TraceEvent*>(fBlocks[0].fBlock.get())->fClockBegin;
-    } else if (fCurBlock.fEventsInBlock > 0) {
-        clockOffset = reinterpret_cast<TraceEvent*>(fCurBlock.fBlock.get())->fClockBegin;
-    }
-
-    TraceEventSerializationState serializationState(clockOffset);
-
-    auto event_block_to_json = [](SkJSONWriter* writer, const TraceEventBlock& block,
-                                  TraceEventSerializationState* serializationState) {
-        TraceEvent* traceEvent = reinterpret_cast<TraceEvent*>(block.fBlock.get());
-        for (int i = 0; i < block.fEventsInBlock; ++i) {
-            trace_event_to_json(writer, traceEvent, serializationState);
-            traceEvent = traceEvent->next();
-        }
-    };
-
-    for (int i = 0; i < fBlocks.count(); ++i) {
-        event_block_to_json(&writer, fBlocks[i], &serializationState);
-    }
-    event_block_to_json(&writer, fCurBlock, &serializationState);
-
-    writer.endArray();
-    writer.flush();
-    fileStream.flush();
-}
diff --git a/tools/trace/SkChromeTracingTracer.h b/tools/trace/SkChromeTracingTracer.h
deleted file mode 100644
index a9e0966..0000000
--- a/tools/trace/SkChromeTracingTracer.h
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkChromeTracingTracer_DEFINED
-#define SkChromeTracingTracer_DEFINED
-
-#include "SkEventTracer.h"
-#include "SkEventTracingPriv.h"
-#include "SkSpinlock.h"
-#include "SkString.h"
-#include "SkTHash.h"
-
-class SkJSONWriter;
-
-/**
- * A SkEventTracer implementation that logs events to JSON for viewing with chrome://tracing.
- */
-class SkChromeTracingTracer : public SkEventTracer {
-public:
-    SkChromeTracingTracer(const char* filename);
-    ~SkChromeTracingTracer() override;
-
-    SkEventTracer::Handle addTraceEvent(char phase,
-                                        const uint8_t* categoryEnabledFlag,
-                                        const char* name,
-                                        uint64_t id,
-                                        int numArgs,
-                                        const char** argNames,
-                                        const uint8_t* argTypes,
-                                        const uint64_t* argValues,
-                                        uint8_t flags) override;
-
-    void updateTraceEventDuration(const uint8_t* categoryEnabledFlag,
-                                  const char* name,
-                                  SkEventTracer::Handle handle) override;
-
-    const uint8_t* getCategoryGroupEnabled(const char* name) override {
-        return fCategories.getCategoryGroupEnabled(name);
-    }
-
-    const char* getCategoryGroupName(const uint8_t* categoryEnabledFlag) override {
-        return fCategories.getCategoryGroupName(categoryEnabledFlag);
-    }
-
-private:
-    void flush();
-
-    enum {
-        // Events are variable size, but most commonly 48 bytes, assuming 64-bit pointers and
-        // reasonable packing. This is a first guess at a number that balances memory usage vs.
-        // time overhead of allocating blocks.
-        kBlockSize = 512 * 1024,
-    };
-
-    typedef std::unique_ptr<uint8_t[]> BlockPtr;
-    struct TraceEventBlock {
-        BlockPtr fBlock;
-        int fEventsInBlock;
-    };
-
-    void createBlock();
-
-    Handle appendEvent(const void* data, size_t size);
-
-    SkString fFilename;
-    SkSpinlock fMutex;
-    SkEventTracingCategories fCategories;
-
-    TraceEventBlock fCurBlock;
-    size_t fCurBlockUsed;
-
-    SkTArray<TraceEventBlock> fBlocks;
-};
-
-#endif
diff --git a/tools/trace/SkDebugfTracer.h b/tools/trace/SkDebugfTracer.h
index ca049d7..9d8753f 100644
--- a/tools/trace/SkDebugfTracer.h
+++ b/tools/trace/SkDebugfTracer.h
@@ -8,8 +8,8 @@
 #ifndef SkDebugfTracer_DEFINED
 #define SkDebugfTracer_DEFINED
 
+#include "EventTracingPriv.h"
 #include "SkEventTracer.h"
-#include "SkEventTracingPriv.h"
 #include "SkString.h"
 
 /**
diff --git a/tools/trace/SkEventTracingPriv.cpp b/tools/trace/SkEventTracingPriv.cpp
deleted file mode 100644
index a7b3a82..0000000
--- a/tools/trace/SkEventTracingPriv.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkEventTracingPriv.h"
-
-#include "SkATrace.h"
-#include "SkChromeTracingTracer.h"
-#include "SkCommandLineFlags.h"
-#include "SkDebugfTracer.h"
-#include "SkEventTracer.h"
-#include "SkTraceEvent.h"
-
-DEFINE_string(trace, "",
-              "Log trace events in one of several modes:\n"
-              "  debugf     : Show events using SkDebugf\n"
-              "  atrace     : Send events to Android ATrace\n"
-              "  <filename> : Any other string is interpreted as a filename. Writes\n"
-              "               trace events to specified file as JSON, for viewing\n"
-              "               with chrome://tracing");
-
-DEFINE_string(traceMatch, "",
-              "Filter which categories are traced.\n"
-              "Uses same format as --match\n");
-
-void initializeEventTracingForTools(const char* traceFlag) {
-    if (!traceFlag) {
-        if (FLAGS_trace.isEmpty()) {
-            return;
-        }
-        traceFlag = FLAGS_trace[0];
-    }
-
-    SkEventTracer* eventTracer = nullptr;
-    if (0 == strcmp(traceFlag, "atrace")) {
-        eventTracer = new SkATrace();
-    } else if (0 == strcmp(traceFlag, "debugf")) {
-        eventTracer = new SkDebugfTracer();
-    } else {
-        eventTracer = new SkChromeTracingTracer(traceFlag);
-    }
-
-    SkAssertResult(SkEventTracer::SetInstance(eventTracer));
-}
-
-uint8_t* SkEventTracingCategories::getCategoryGroupEnabled(const char* name) {
-    static_assert(0 == offsetof(CategoryState, fEnabled), "CategoryState");
-
-    // We ignore the "disabled-by-default-" prefix in our internal tools
-    if (SkStrStartsWith(name, TRACE_CATEGORY_PREFIX)) {
-        name += strlen(TRACE_CATEGORY_PREFIX);
-    }
-
-    // Chrome's implementation of this API does a two-phase lookup (once without a lock, then again
-    // with a lock. But the tracing macros avoid calling these functions more than once per site,
-    // so just do something simple (and easier to reason about):
-    SkAutoMutexAcquire lock(&fMutex);
-    for (int i = 0; i < fNumCategories; ++i) {
-        if (0 == strcmp(name, fCategories[i].fName)) {
-            return reinterpret_cast<uint8_t*>(&fCategories[i]);
-        }
-    }
-
-    if (fNumCategories >= kMaxCategories) {
-        SkDEBUGFAIL("Exhausted event tracing categories. Increase kMaxCategories.");
-        return reinterpret_cast<uint8_t*>(&fCategories[0]);
-    }
-
-    fCategories[fNumCategories].fEnabled = SkCommandLineFlags::ShouldSkip(FLAGS_traceMatch, name)
-            ? 0 : SkEventTracer::kEnabledForRecording_CategoryGroupEnabledFlags;
-
-    fCategories[fNumCategories].fName = name;
-    return reinterpret_cast<uint8_t*>(&fCategories[fNumCategories++]);
-}
-
-const char* SkEventTracingCategories::getCategoryGroupName(const uint8_t* categoryEnabledFlag) {
-    if (categoryEnabledFlag) {
-        return reinterpret_cast<const CategoryState*>(categoryEnabledFlag)->fName;
-    }
-    return nullptr;
-}
diff --git a/tools/trace/SkEventTracingPriv.h b/tools/trace/SkEventTracingPriv.h
deleted file mode 100644
index fb7b2f3..0000000
--- a/tools/trace/SkEventTracingPriv.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkEventTracingPriv_DEFINED
-#define SkEventTracingPriv_DEFINED
-
-#include "SkMutex.h"
-
-/**
- * Construct and install an SkEventTracer, based on the mode,
- * defaulting to the --trace command line argument.
- */
-void initializeEventTracingForTools(const char* mode = nullptr);
-
-/**
- * Helper class used by internal implementations of SkEventTracer to manage categories.
- */
-class SkEventTracingCategories {
-public:
-    SkEventTracingCategories() : fNumCategories(0) {}
-
-    uint8_t* getCategoryGroupEnabled(const char* name);
-    const char* getCategoryGroupName(const uint8_t* categoryEnabledFlag);
-
-private:
-    enum { kMaxCategories = 256 };
-
-    struct CategoryState {
-        uint8_t fEnabled;
-        const char* fName;
-    };
-
-    CategoryState fCategories[kMaxCategories];
-    int fNumCategories;
-    SkMutex fMutex;
-};
-
-#endif
diff --git a/tools/using_skia_and_harfbuzz.cpp b/tools/using_skia_and_harfbuzz.cpp
index b739218..af315f0 100644
--- a/tools/using_skia_and_harfbuzz.cpp
+++ b/tools/using_skia_and_harfbuzz.cpp
@@ -139,10 +139,10 @@
     }
 
     void WriteLine(const SkShaper& shaper, const char *text, size_t textBytes) {
-        SkTextBlobBuilderRunHandler textBlobBuilder;
-        SkPoint endPoint = shaper.shape(&textBlobBuilder, font, text, textBytes, true,
-                                        SkPoint{0, 0},
-                                        config->page_width.value - 2*config->left_margin.value);
+        SkTextBlobBuilderRunHandler textBlobBuilder(text, {0, 0});
+        shaper.shape(text, textBytes, font, true,
+                     config->page_width.value - 2*config->left_margin.value, &textBlobBuilder);
+        SkPoint endPoint = textBlobBuilder.endPoint();
         sk_sp<const SkTextBlob> blob = textBlobBuilder.makeBlob();
         // If we don't have a page, or if we're not at the start of the page and the blob won't fit
         if (!pageCanvas ||
@@ -208,12 +208,12 @@
     if (font_file.size() > 0) {
         typeface = SkTypeface::MakeFromFile(font_file.c_str(), 0 /* index */);
     }
-    SkShaper shaper(typeface);
-    assert(shaper.good());
+    std::unique_ptr<SkShaper> shaper = SkShaper::Make();
+    assert(shaper);
     //SkString line("This is هذا هو الخط a line.");
     //SkString line("⁧This is a line هذا هو الخط.⁩");
     for (std::string line; std::getline(std::cin, line);) {
-        placement.WriteLine(shaper, line.c_str(), line.size());
+        placement.WriteLine(*shaper, line.c_str(), line.size());
     }
 
     doc->close();
diff --git a/tools/viewer/GMSlide.cpp b/tools/viewer/GMSlide.cpp
index cfd4853..07c0392 100644
--- a/tools/viewer/GMSlide.cpp
+++ b/tools/viewer/GMSlide.cpp
@@ -27,9 +27,7 @@
     fGM->drawContent(canvas);
 }
 
-bool GMSlide::animate(const SkAnimTimer& timer) {
-    return fGM->animate(timer);
-}
+bool GMSlide::animate(const AnimTimer& timer) { return fGM->animate(timer); }
 
 bool GMSlide::onChar(SkUnichar c) {
     return fGM->handleKey(c);
diff --git a/tools/viewer/GMSlide.h b/tools/viewer/GMSlide.h
index c03cc8a..fc34fe4 100644
--- a/tools/viewer/GMSlide.h
+++ b/tools/viewer/GMSlide.h
@@ -19,7 +19,7 @@
     SkISize getDimensions() const override { return fGM->getISize(); }
 
     void draw(SkCanvas* canvas) override;
-    bool animate(const SkAnimTimer&) override;
+    bool animate(const AnimTimer&) override;
 
     bool onChar(SkUnichar c) override;
 
diff --git a/tools/viewer/ImGuiLayer.cpp b/tools/viewer/ImGuiLayer.cpp
index c6a95b4..03ba9ed 100644
--- a/tools/viewer/ImGuiLayer.cpp
+++ b/tools/viewer/ImGuiLayer.cpp
@@ -10,7 +10,9 @@
 #include "SkCanvas.h"
 #include "SkImage.h"
 #include "SkPixmap.h"
+#include "SkSurface.h"
 #include "SkSwizzle.h"
+#include "SkTime.h"
 #include "SkVertices.h"
 
 #include "imgui.h"
@@ -95,7 +97,12 @@
 void ImGuiLayer::onPrePaint() {
     // Update ImGui input
     ImGuiIO& io = ImGui::GetIO();
-    io.DeltaTime = 1.0f / 60.0f;
+
+    static double previousTime = 0.0;
+    double currentTime = SkTime::GetSecs();
+    io.DeltaTime = static_cast<float>(currentTime - previousTime);
+    previousTime = currentTime;
+
     io.DisplaySize.x = static_cast<float>(fWindow->width());
     io.DisplaySize.y = static_cast<float>(fWindow->height());
 
@@ -106,7 +113,7 @@
     ImGui::NewFrame();
 }
 
-void ImGuiLayer::onPaint(SkCanvas* canvas) {
+void ImGuiLayer::onPaint(SkSurface* surface) {
     // This causes ImGui to rebuild vertex/index data based on all immediate-mode commands
     // (widgets, etc...) that have been issued
     ImGui::Render();
@@ -117,6 +124,8 @@
     SkTDArray<SkPoint> uv;
     SkTDArray<SkColor> color;
 
+    auto canvas = surface->getCanvas();
+
     for (int i = 0; i < drawData->CmdListsCount; ++i) {
         const ImDrawList* drawList = drawData->CmdLists[i];
 
@@ -166,8 +175,8 @@
                                                          drawCmd->ElemCount,
                                                          drawList->IdxBuffer.begin() + indexOffset);
                     canvas->drawVertices(vertices, SkBlendMode::kModulate, *paint);
-                    indexOffset += drawCmd->ElemCount;
                 }
+                indexOffset += drawCmd->ElemCount;
             }
         }
     }
diff --git a/tools/viewer/ImGuiLayer.h b/tools/viewer/ImGuiLayer.h
index 4b10fc7..f7ee4da 100644
--- a/tools/viewer/ImGuiLayer.h
+++ b/tools/viewer/ImGuiLayer.h
@@ -14,6 +14,104 @@
 
 #include "imgui.h"
 
+namespace ImGui {
+
+// Helper object for drawing in a widget region, with draggable points
+struct DragCanvas {
+    DragCanvas(const void* id, SkPoint tl = { 0.0f, 0.0f }, SkPoint br = { 1.0f, 1.0f },
+               float aspect = -1.0f)
+            : fID(0), fDragging(false) {
+        ImGui::PushID(id);
+        fDrawList = ImGui::GetWindowDrawList();
+
+        // Logical size
+        SkScalar w = SkTAbs(br.fX - tl.fX),
+                 h = SkTAbs(br.fY - tl.fY);
+
+        // Determine aspect ratio automatically by default
+        if (aspect < 0) {
+            aspect = h / w;
+        }
+
+        float availWidth = SkTMax(ImGui::GetContentRegionAvailWidth(), 1.0f);
+        fPos = ImGui::GetCursorScreenPos();
+        fSize = ImVec2(availWidth, availWidth * aspect);
+
+        SkPoint local[4] = {
+            { tl.fX, tl.fY },
+            { br.fX, tl.fY },
+            { tl.fX, br.fY },
+            { br.fX, br.fY },
+        };
+        SkPoint screen[4] = {
+            { fPos.x          , fPos.y           },
+            { fPos.x + fSize.x, fPos.y           },
+            { fPos.x          , fPos.y + fSize.y },
+            { fPos.x + fSize.x, fPos.y + fSize.y },
+        };
+        fLocalToScreen.setPolyToPoly(local, screen, 4);
+        fScreenToLocal.setPolyToPoly(screen, local, 4);
+    }
+
+    ~DragCanvas() {
+        ImGui::SetCursorScreenPos(ImVec2(fPos.x, fPos.y + fSize.y));
+        ImGui::Spacing();
+        ImGui::PopID();
+    }
+
+    void fillColor(ImU32 color) {
+        fDrawList->AddRectFilled(fPos, ImVec2(fPos.x + fSize.x, fPos.y + fSize.y), color);
+    }
+
+    void dragPoint(SkPoint* p, bool tooltip = false, ImU32 color = 0xFFFFFFFF) {
+        // Transform points from logical coordinates to screen coordinates
+        SkPoint center = fLocalToScreen.mapXY(p->fX, p->fY);
+
+        // Invisible 10x10 button
+        ImGui::PushID(fID++);
+        ImGui::SetCursorScreenPos(ImVec2(center.fX - 5, center.fY - 5));
+        ImGui::InvisibleButton("", ImVec2(10, 10));
+
+        if (ImGui::IsItemActive() && ImGui::IsMouseDragging()) {
+            // Update screen position to track mouse, clamped to our area
+            ImGuiIO& io = ImGui::GetIO();
+            center.set(SkTPin(io.MousePos.x, fPos.x, fPos.x + fSize.x),
+                       SkTPin(io.MousePos.y, fPos.y, fPos.y + fSize.y));
+
+            // Update local coordinates for the caller
+            *p = fScreenToLocal.mapXY(center.fX, center.fY);
+            fDragging = true;
+        }
+
+        if (tooltip && ImGui::IsItemHovered()) {
+            ImGui::SetTooltip("x: %.3f\ny: %.3f", p->fX, p->fY);
+        }
+
+        ImGui::PopID();
+
+        fScreenPoints.push_back(ImVec2(center.fX, center.fY));
+        fDrawList->AddCircle(fScreenPoints.back(), 5.0f, color);
+    }
+
+    ImDrawList* fDrawList;
+
+    // Location and dimensions (in screen coordinates)
+    ImVec2 fPos;
+    ImVec2 fSize;
+
+    // Screen coordinates of points (for additional user drawing)
+    SkSTArray<4, ImVec2, true> fScreenPoints;
+
+    // To simplify dragPoint
+    SkMatrix fLocalToScreen;
+    SkMatrix fScreenToLocal;
+
+    int fID;
+    bool fDragging;
+};
+
+}
+
 class ImGuiLayer : public sk_app::Window::Layer {
 public:
     ImGuiLayer();
@@ -24,7 +122,7 @@
 
     void onAttach(sk_app::Window* window) override;
     void onPrePaint() override;
-    void onPaint(SkCanvas* canvas) override;
+    void onPaint(SkSurface*) override;
     bool onMouse(int x, int y, sk_app::Window::InputState state, uint32_t modifiers) override;
     bool onMouseWheel(float delta, uint32_t modifiers) override;
     bool onKey(sk_app::Window::Key key, sk_app::Window::InputState state, uint32_t modifiers) override;
diff --git a/tools/viewer/NIMASlide.cpp b/tools/viewer/NIMASlide.cpp
index bf516a8..c04c82b 100644
--- a/tools/viewer/NIMASlide.cpp
+++ b/tools/viewer/NIMASlide.cpp
@@ -7,8 +7,8 @@
 
 #include "NIMASlide.h"
 
+#include "AnimTimer.h"
 #include "Resources.h"
-#include "SkAnimTimer.h"
 #include "SkOSPath.h"
 #include "imgui.h"
 #include "nima/NimaActor.h"
@@ -88,7 +88,7 @@
     fActor.reset(nullptr);
 }
 
-bool NIMASlide::animate(const SkAnimTimer& timer) {
+bool NIMASlide::animate(const AnimTimer& timer) {
     // Apply the animation.
     if (fActor) {
         float time = std::fmod(timer.secs(), fActor->duration());
diff --git a/tools/viewer/NIMASlide.h b/tools/viewer/NIMASlide.h
index ea2c227..05892b9 100644
--- a/tools/viewer/NIMASlide.h
+++ b/tools/viewer/NIMASlide.h
@@ -24,7 +24,7 @@
     void draw(SkCanvas* canvas) override;
     void load(SkScalar winWidth, SkScalar winHeight) override;
     void unload() override;
-    bool animate(const SkAnimTimer& timer) override;
+    bool animate(const AnimTimer& timer) override;
 
     bool onChar(SkUnichar c) override;
     bool onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state,
diff --git a/tools/viewer/ParticlesSlide.cpp b/tools/viewer/ParticlesSlide.cpp
new file mode 100644
index 0000000..cd41656
--- /dev/null
+++ b/tools/viewer/ParticlesSlide.cpp
@@ -0,0 +1,357 @@
+/*
+* Copyright 2019 Google LLC
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include "ParticlesSlide.h"
+
+#include "AnimTimer.h"
+#include "ImGuiLayer.h"
+#include "Resources.h"
+#include "SkOSFile.h"
+#include "SkOSPath.h"
+#include "SkParticleAffector.h"
+#include "SkParticleDrawable.h"
+#include "SkParticleEffect.h"
+#include "SkParticleSerialization.h"
+#include "SkReflected.h"
+
+#include "imgui.h"
+
+using namespace sk_app;
+
+namespace {
+
+static SkScalar kDragSize = 8.0f;
+static SkTArray<SkPoint*> gDragPoints;
+int gDragIndex = -1;
+
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int InputTextCallback(ImGuiInputTextCallbackData* data) {
+    if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) {
+        SkString* s = (SkString*)data->UserData;
+        SkASSERT(data->Buf == s->writable_str());
+        SkString tmp(data->Buf, data->BufTextLen);
+        s->swap(tmp);
+        data->Buf = s->writable_str();
+    }
+    return 0;
+}
+
+class SkGuiVisitor : public SkFieldVisitor {
+public:
+    SkGuiVisitor() {
+        fTreeStack.push_back(true);
+    }
+
+#define IF_OPEN(WIDGET) if (fTreeStack.back()) { WIDGET; }
+
+    void visit(const char* name, float& f) override {
+        IF_OPEN(ImGui::DragFloat(item(name), &f))
+    }
+    void visit(const char* name, int& i) override {
+        IF_OPEN(ImGui::DragInt(item(name), &i))
+    }
+    void visit(const char* name, bool& b) override {
+        IF_OPEN(ImGui::Checkbox(item(name), &b))
+    }
+    void visit(const char* name, SkString& s) override {
+        if (fTreeStack.back()) {
+            ImGuiInputTextFlags flags = ImGuiInputTextFlags_CallbackResize;
+            ImGui::InputText(item(name), s.writable_str(), s.size() + 1, flags, InputTextCallback,
+                             &s);
+        }
+    }
+    void visit(const char* name, int& i, const EnumStringMapping* map, int count) override {
+        if (fTreeStack.back()) {
+            const char* curStr = EnumToString(i, map, count);
+            if (ImGui::BeginCombo(item(name), curStr ? curStr : "Unknown")) {
+                for (int j = 0; j < count; ++j) {
+                    if (ImGui::Selectable(map[j].fName, i == map[j].fValue)) {
+                        i = map[j].fValue;
+                    }
+                }
+                ImGui::EndCombo();
+            }
+        }
+    }
+
+    void visit(const char* name, SkPoint& p) override {
+        if (fTreeStack.back()) {
+            ImGui::DragFloat2(item(name), &p.fX);
+            gDragPoints.push_back(&p);
+        }
+    }
+    void visit(const char* name, SkColor4f& c) override {
+        IF_OPEN(ImGui::ColorEdit4(item(name), c.vec()))
+    }
+
+#undef IF_OPEN
+
+    void visit(sk_sp<SkReflected>& e, const SkReflected::Type* baseType) override {
+        if (fTreeStack.back()) {
+            const SkReflected::Type* curType = e ? e->getType() : nullptr;
+            if (ImGui::BeginCombo("Type", curType ? curType->fName : "Null")) {
+                auto visitType = [baseType, curType, &e](const SkReflected::Type* t) {
+                    if (t->fFactory && (t == baseType || t->isDerivedFrom(baseType)) &&
+                        ImGui::Selectable(t->fName, curType == t)) {
+                        e = t->fFactory();
+                    }
+                };
+                SkReflected::VisitTypes(visitType);
+                ImGui::EndCombo();
+            }
+        }
+    }
+
+    void enterObject(const char* name) override {
+        if (fTreeStack.back()) {
+            fTreeStack.push_back(ImGui::TreeNodeEx(item(name),
+                                                   ImGuiTreeNodeFlags_AllowItemOverlap));
+        } else {
+            fTreeStack.push_back(false);
+        }
+    }
+    void exitObject() override {
+        if (fTreeStack.back()) {
+            ImGui::TreePop();
+        }
+        fTreeStack.pop_back();
+    }
+
+    int enterArray(const char* name, int oldCount) override {
+        this->enterObject(item(name));
+        fArrayCounterStack.push_back(0);
+        fArrayEditStack.push_back();
+
+        int count = oldCount;
+        if (fTreeStack.back()) {
+            ImGui::SameLine();
+            if (ImGui::Button("+")) {
+                ++count;
+            }
+        }
+        return count;
+    }
+    ArrayEdit exitArray() override {
+        fArrayCounterStack.pop_back();
+        auto edit = fArrayEditStack.back();
+        fArrayEditStack.pop_back();
+        this->exitObject();
+        return edit;
+    }
+
+private:
+    const char* item(const char* name) {
+        if (name) {
+            return name;
+        }
+
+        // We're in an array. Add extra controls and a dynamic label.
+        int index = fArrayCounterStack.back()++;
+        ArrayEdit& edit(fArrayEditStack.back());
+        fScratchLabel = SkStringPrintf("[%d]", index);
+
+        ImGui::PushID(index);
+
+        if (ImGui::Button("X")) {
+            edit.fVerb = ArrayEdit::Verb::kRemove;
+            edit.fIndex = index;
+        }
+        ImGui::SameLine();
+        if (ImGui::Button("^")) {
+            edit.fVerb = ArrayEdit::Verb::kMoveForward;
+            edit.fIndex = index;
+        }
+        ImGui::SameLine();
+        if (ImGui::Button("v")) {
+            edit.fVerb = ArrayEdit::Verb::kMoveForward;
+            edit.fIndex = index + 1;
+        }
+        ImGui::SameLine();
+
+        ImGui::PopID();
+
+        return fScratchLabel.c_str();
+    }
+
+    SkSTArray<16, bool, true> fTreeStack;
+    SkSTArray<16, int, true>  fArrayCounterStack;
+    SkSTArray<16, ArrayEdit, true> fArrayEditStack;
+    SkString fScratchLabel;
+};
+
+ParticlesSlide::ParticlesSlide() {
+    // Register types for serialization
+    REGISTER_REFLECTED(SkReflected);
+    SkParticleAffector::RegisterAffectorTypes();
+    SkParticleDrawable::RegisterDrawableTypes();
+    fName = "Particles";
+    fPlayPosition.set(200.0f, 200.0f);
+}
+
+void ParticlesSlide::loadEffects(const char* dirname) {
+    fLoaded.reset();
+    fRunning.reset();
+    SkOSFile::Iter iter(dirname, ".json");
+    for (SkString file; iter.next(&file); ) {
+        LoadedEffect effect;
+        effect.fName = SkOSPath::Join(dirname, file.c_str());
+        effect.fParams.reset(new SkParticleEffectParams());
+        if (auto fileData = SkData::MakeFromFileName(effect.fName.c_str())) {
+            skjson::DOM dom(static_cast<const char*>(fileData->data()), fileData->size());
+            SkFromJsonVisitor fromJson(dom.root());
+            effect.fParams->visitFields(&fromJson);
+            fLoaded.push_back(effect);
+        }
+    }
+}
+
+void ParticlesSlide::load(SkScalar winWidth, SkScalar winHeight) {
+    this->loadEffects(GetResourcePath("particles").c_str());
+}
+
+void ParticlesSlide::draw(SkCanvas* canvas) {
+    canvas->clear(0);
+
+    gDragPoints.reset();
+    gDragPoints.push_back(&fPlayPosition);
+
+    // Window to show all loaded effects, and allow playing them
+    if (ImGui::Begin("Library", nullptr, ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
+        static bool looped = true;
+        ImGui::Checkbox("Looped", &looped);
+
+        static SkString dirname = GetResourcePath("particles");
+        ImGuiInputTextFlags textFlags = ImGuiInputTextFlags_CallbackResize;
+        ImGui::InputText("Directory", dirname.writable_str(), dirname.size() + 1, textFlags,
+                         InputTextCallback, &dirname);
+
+        if (ImGui::Button("New")) {
+            LoadedEffect effect;
+            effect.fName = SkOSPath::Join(dirname.c_str(), "new.json");
+            effect.fParams.reset(new SkParticleEffectParams());
+            fLoaded.push_back(effect);
+        }
+        ImGui::SameLine();
+
+        if (ImGui::Button("Load")) {
+            this->loadEffects(dirname.c_str());
+        }
+        ImGui::SameLine();
+
+        if (ImGui::Button("Save")) {
+            for (const auto& effect : fLoaded) {
+                SkFILEWStream fileStream(effect.fName.c_str());
+                if (fileStream.isValid()) {
+                    SkJSONWriter writer(&fileStream, SkJSONWriter::Mode::kPretty);
+                    SkToJsonVisitor toJson(writer);
+                    writer.beginObject();
+                    effect.fParams->visitFields(&toJson);
+                    writer.endObject();
+                    writer.flush();
+                    fileStream.flush();
+                } else {
+                    SkDebugf("Failed to open %s\n", effect.fName.c_str());
+                }
+            }
+        }
+
+        SkGuiVisitor gui;
+        for (int i = 0; i < fLoaded.count(); ++i) {
+            ImGui::PushID(i);
+            if (fTimer && ImGui::Button("Play")) {
+                sk_sp<SkParticleEffect> effect(new SkParticleEffect(fLoaded[i].fParams, fRandom));
+                effect->start(fTimer->secs(), looped);
+                fRunning.push_back({ fPlayPosition, fLoaded[i].fName, effect });
+            }
+            ImGui::SameLine();
+
+            ImGui::InputText("##Name", fLoaded[i].fName.writable_str(), fLoaded[i].fName.size() + 1,
+                             textFlags, InputTextCallback, &fLoaded[i].fName);
+
+            if (ImGui::TreeNode("##Details")) {
+                fLoaded[i].fParams->visitFields(&gui);
+                ImGui::TreePop();
+            }
+            ImGui::PopID();
+        }
+    }
+    ImGui::End();
+
+    // Another window to show all the running effects
+    if (ImGui::Begin("Running")) {
+        for (int i = 0; i < fRunning.count(); ++i) {
+            ImGui::PushID(i);
+            bool remove = ImGui::Button("X") || !fRunning[i].fEffect->isAlive();
+            ImGui::SameLine();
+            ImGui::Text("%4g, %4g %5d %s", fRunning[i].fPosition.fX, fRunning[i].fPosition.fY,
+                        fRunning[i].fEffect->getCount(), fRunning[i].fName.c_str());
+            if (remove) {
+                fRunning.removeShuffle(i);
+            }
+            ImGui::PopID();
+        }
+    }
+    ImGui::End();
+
+    SkPaint dragPaint;
+    dragPaint.setColor(SK_ColorLTGRAY);
+    dragPaint.setAntiAlias(true);
+    SkPaint dragHighlight;
+    dragHighlight.setStyle(SkPaint::kStroke_Style);
+    dragHighlight.setColor(SK_ColorGREEN);
+    dragHighlight.setStrokeWidth(2);
+    dragHighlight.setAntiAlias(true);
+    for (int i = 0; i < gDragPoints.count(); ++i) {
+        canvas->drawCircle(*gDragPoints[i], kDragSize, dragPaint);
+        if (gDragIndex == i) {
+            canvas->drawCircle(*gDragPoints[i], kDragSize, dragHighlight);
+        }
+    }
+    for (const auto& effect : fRunning) {
+        canvas->save();
+        canvas->translate(effect.fPosition.fX, effect.fPosition.fY);
+        effect.fEffect->draw(canvas);
+        canvas->restore();
+    }
+}
+
+bool ParticlesSlide::animate(const AnimTimer& timer) {
+    fTimer = &timer;
+    for (const auto& effect : fRunning) {
+        effect.fEffect->update(timer.secs());
+    }
+    return true;
+}
+
+bool ParticlesSlide::onMouse(SkScalar x, SkScalar y, Window::InputState state, uint32_t modifiers) {
+    if (gDragIndex == -1) {
+        if (state == Window::kDown_InputState) {
+            float bestDistance = kDragSize;
+            SkPoint mousePt = { x, y };
+            for (int i = 0; i < gDragPoints.count(); ++i) {
+                float distance = SkPoint::Distance(*gDragPoints[i], mousePt);
+                if (distance < bestDistance) {
+                    gDragIndex = i;
+                    bestDistance = distance;
+                }
+            }
+            return gDragIndex != -1;
+        }
+    } else {
+        // Currently dragging
+        SkASSERT(gDragIndex < gDragPoints.count());
+        gDragPoints[gDragIndex]->set(x, y);
+        if (state == Window::kUp_InputState) {
+            gDragIndex = -1;
+        }
+        return true;
+    }
+    return false;
+}
diff --git a/tools/viewer/ParticlesSlide.h b/tools/viewer/ParticlesSlide.h
new file mode 100644
index 0000000..8211513
--- /dev/null
+++ b/tools/viewer/ParticlesSlide.h
@@ -0,0 +1,56 @@
+/*
+* Copyright 2019 Google LLC
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#ifndef ParticlesSlide_DEFINED
+#define ParticlesSlide_DEFINED
+
+#include "Slide.h"
+
+#include "SkPath.h"
+#include "SkRandom.h"
+#include "SkTArray.h"
+
+class AnimTimer;
+class SkParticleEffect;
+class SkParticleEffectParams;
+
+class ParticlesSlide : public Slide {
+public:
+    ParticlesSlide();
+
+    // TODO: We need a way for primarily interactive slides to always be as large as the window
+    SkISize getDimensions() const override { return SkISize::MakeEmpty(); }
+
+    void load(SkScalar winWidth, SkScalar winHeight) override;
+    void draw(SkCanvas* canvas) override;
+    bool animate(const AnimTimer& timer) override;
+
+    bool onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state,
+                 uint32_t modifiers) override;
+
+private:
+    void loadEffects(const char* dirname);
+
+    SkRandom fRandom;
+    const AnimTimer* fTimer;
+    SkPoint fPlayPosition;
+
+    struct LoadedEffect {
+        SkString fName;
+        sk_sp<SkParticleEffectParams> fParams;
+    };
+    SkTArray<LoadedEffect> fLoaded;
+
+    struct RunningEffect {
+        SkPoint fPosition;
+        SkString fName;
+        sk_sp<SkParticleEffect> fEffect;
+    };
+    SkTArray<RunningEffect> fRunning;
+};
+
+#endif
diff --git a/tools/viewer/SKPSlide.cpp b/tools/viewer/SKPSlide.cpp
index e24e1fd..caf18cd 100644
--- a/tools/viewer/SKPSlide.cpp
+++ b/tools/viewer/SKPSlide.cpp
@@ -8,7 +8,6 @@
 #include "SKPSlide.h"
 
 #include "SkCanvas.h"
-#include "SkCommonFlags.h"
 #include "SkOSFile.h"
 #include "SkStream.h"
 
diff --git a/tools/viewer/SampleSlide.cpp b/tools/viewer/SampleSlide.cpp
index 9b1737a..f48f1dc 100644
--- a/tools/viewer/SampleSlide.cpp
+++ b/tools/viewer/SampleSlide.cpp
@@ -8,7 +8,6 @@
 #include "SampleSlide.h"
 
 #include "SkCanvas.h"
-#include "SkCommonFlags.h"
 #include "SkOSFile.h"
 #include "SkStream.h"
 
diff --git a/tools/viewer/SampleSlide.h b/tools/viewer/SampleSlide.h
index 7cf1d3c..69d48ac 100644
--- a/tools/viewer/SampleSlide.h
+++ b/tools/viewer/SampleSlide.h
@@ -24,9 +24,7 @@
         fSample->setSize(winWidth, winHeight);
     }
     void unload() override;
-    bool animate(const SkAnimTimer& timer) override {
-        return fSample->animate(timer);
-    }
+    bool animate(const AnimTimer& timer) override { return fSample->animate(timer); }
 
     bool onChar(SkUnichar c) override;
     bool onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state,
diff --git a/tools/viewer/SkottieSlide.cpp b/tools/viewer/SkottieSlide.cpp
index 981dc53..6198e47 100644
--- a/tools/viewer/SkottieSlide.cpp
+++ b/tools/viewer/SkottieSlide.cpp
@@ -9,7 +9,7 @@
 
 #if defined(SK_ENABLE_SKOTTIE)
 
-#include "SkAnimTimer.h"
+#include "AnimTimer.h"
 #include "SkCanvas.h"
 #include "SkFont.h"
 #include "SkOSPath.h"
@@ -136,7 +136,7 @@
     }
 }
 
-bool SkottieSlide::animate(const SkAnimTimer& timer) {
+bool SkottieSlide::animate(const AnimTimer& timer) {
     if (fTimeBase == 0) {
         // Reset the animation time.
         fTimeBase = timer.msec();
diff --git a/tools/viewer/SkottieSlide.h b/tools/viewer/SkottieSlide.h
index 9bf6bf5..1a6a075 100644
--- a/tools/viewer/SkottieSlide.h
+++ b/tools/viewer/SkottieSlide.h
@@ -26,7 +26,7 @@
     SkISize getDimensions() const override;
 
     void draw(SkCanvas*) override;
-    bool animate(const SkAnimTimer&) override;
+    bool animate(const AnimTimer&) override;
 
     bool onChar(SkUnichar) override;
     bool onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState, uint32_t modifiers) override;
diff --git a/tools/viewer/Slide.h b/tools/viewer/Slide.h
index a2ee307..10080ce 100644
--- a/tools/viewer/Slide.h
+++ b/tools/viewer/Slide.h
@@ -14,7 +14,7 @@
 #include "sk_app/Window.h"
 
 class SkCanvas;
-class SkAnimTimer;
+class AnimTimer;
 class SkMetaData;
 
 class Slide : public SkRefCnt {
@@ -24,7 +24,7 @@
     virtual SkISize getDimensions() const = 0;
 
     virtual void draw(SkCanvas* canvas) = 0;
-    virtual bool animate(const SkAnimTimer&) { return false;  }
+    virtual bool animate(const AnimTimer&) { return false; }
     virtual void load(SkScalar winWidth, SkScalar winHeight) {}
     virtual void resize(SkScalar winWidth, SkScalar winHeight) {}
     virtual void unload() {}
diff --git a/tools/viewer/SlideDir.cpp b/tools/viewer/SlideDir.cpp
index beb2ec2..ad3be77 100644
--- a/tools/viewer/SlideDir.cpp
+++ b/tools/viewer/SlideDir.cpp
@@ -7,13 +7,13 @@
 
 #include "SlideDir.h"
 
-#include "SkAnimTimer.h"
+#include "AnimTimer.h"
 #include "SkCanvas.h"
 #include "SkCubicMap.h"
 #include "SkMakeUnique.h"
-#include "SkSGColor.h"
 #include "SkSGDraw.h"
 #include "SkSGGroup.h"
+#include "SkSGPaint.h"
 #include "SkSGPlane.h"
 #include "SkSGRect.h"
 #include "SkSGRenderNode.h"
@@ -80,9 +80,11 @@
         fSlide->draw(canvas);
     }
 
+    const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; }
+
 private:
     void tick(SkMSec t) {
-        fSlide->animate(SkAnimTimer(t * 1e6));
+        fSlide->animate(AnimTimer(t * 1e6));
         this->invalidate();
     }
 
@@ -113,9 +115,8 @@
         : fDir(dir)
         , fRect(focusRect)
         , fTarget(nullptr)
+        , fMap(kFocusCtrl1, kFocusCtrl0)
         , fState(State::kIdle) {
-        fMap.setPts(kFocusCtrl1, kFocusCtrl0);
-
         fShadePaint = sksg::Color::Make(kFocusShade);
         fShade = sksg::Draw::Make(sksg::Plane::Make(), fShadePaint);
     }
@@ -352,7 +353,7 @@
     fScene->render(canvas);
 }
 
-bool SlideDir::animate(const SkAnimTimer& timer) {
+bool SlideDir::animate(const AnimTimer& timer) {
     if (fTimeBase == 0) {
         // Reset the animation time.
         fTimeBase = timer.msec();
diff --git a/tools/viewer/SlideDir.h b/tools/viewer/SlideDir.h
index 376aee5..eeb4904 100644
--- a/tools/viewer/SlideDir.h
+++ b/tools/viewer/SlideDir.h
@@ -33,7 +33,7 @@
     SkISize getDimensions() const override;
 
     void draw(SkCanvas*) override;
-    bool animate(const SkAnimTimer&) override;
+    bool animate(const AnimTimer&) override;
 
     bool onChar(SkUnichar) override;
     bool onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState, uint32_t modifiers) override;
diff --git a/tools/viewer/StatsLayer.cpp b/tools/viewer/StatsLayer.cpp
index 8d25e8b..fe82c97 100644
--- a/tools/viewer/StatsLayer.cpp
+++ b/tools/viewer/StatsLayer.cpp
@@ -10,19 +10,25 @@
 #include "SkCanvas.h"
 #include "SkFont.h"
 #include "SkString.h"
+#include "SkSurface.h"
 #include "SkTime.h"
 
 StatsLayer::StatsLayer()
-    : fCurrentMeasurement(0)
+    : fCurrentMeasurement(-1)
+    , fLastTotalBegin(0)
     , fCumulativeMeasurementTime(0)
     , fCumulativeMeasurementCount(0)
-    , fDisplayScale(1.0f) {}
+    , fDisplayScale(1.0f) {
+    memset(fTotalTimes, 0, sizeof(fTotalTimes));
+}
 
 void StatsLayer::resetMeasurements() {
     for (int i = 0; i < fTimers.count(); ++i) {
         memset(fTimers[i].fTimes, 0, sizeof(fTimers[i].fTimes));
     }
-    fCurrentMeasurement = 0;
+    memset(fTotalTimes, 0, sizeof(fTotalTimes));
+    fCurrentMeasurement = -1;
+    fLastTotalBegin = 0;
     fCumulativeMeasurementTime = 0;
     fCumulativeMeasurementCount = 0;
 }
@@ -38,28 +44,32 @@
 }
 
 void StatsLayer::beginTiming(Timer timer) {
-    fTimers[timer].fTimes[fCurrentMeasurement] -= SkTime::GetMSecs();
+    if (fCurrentMeasurement >= 0) {
+        fTimers[timer].fTimes[fCurrentMeasurement] -= SkTime::GetMSecs();
+    }
 }
 
 void StatsLayer::endTiming(Timer timer) {
-    fTimers[timer].fTimes[fCurrentMeasurement] += SkTime::GetMSecs();
-}
-
-double StatsLayer::getLastTime(Timer timer) {
-    int idx = (fCurrentMeasurement + (kMeasurementCount - 1)) & (kMeasurementCount - 1);
-    return fTimers[timer].fTimes[idx];
-}
-
-void StatsLayer::onPaint(SkCanvas* canvas) {
-    // Advance our timing bookkeeping
-    for (int i = 0; i < fTimers.count(); ++i) {
-        fCumulativeMeasurementTime += fTimers[i].fTimes[fCurrentMeasurement];
+    if (fCurrentMeasurement >= 0) {
+        fTimers[timer].fTimes[fCurrentMeasurement] += SkTime::GetMSecs();
     }
-    fCumulativeMeasurementCount++;
+}
+
+void StatsLayer::onPrePaint() {
+    if (fCurrentMeasurement >= 0) {
+        fTotalTimes[fCurrentMeasurement] = SkTime::GetMSecs() - fLastTotalBegin;
+        fCumulativeMeasurementTime += fTotalTimes[fCurrentMeasurement];
+        fCumulativeMeasurementCount++;
+    }
     fCurrentMeasurement = (fCurrentMeasurement + 1) & (kMeasurementCount - 1);
-    SkASSERT(fCurrentMeasurement < kMeasurementCount);
+    SkASSERT(fCurrentMeasurement >= 0 && fCurrentMeasurement < kMeasurementCount);
+    fLastTotalBegin = SkTime::GetMSecs();
+}
+
+void StatsLayer::onPaint(SkSurface* surface) {
+    int nextMeasurement = (fCurrentMeasurement + 1) & (kMeasurementCount - 1);
     for (int i = 0; i < fTimers.count(); ++i) {
-        fTimers[i].fTimes[fCurrentMeasurement] = 0;
+        fTimers[i].fTimes[nextMeasurement] = 0;
     }
 
 #ifdef SK_BUILD_FOR_ANDROID
@@ -79,6 +89,7 @@
     static const int kGraphPadding = 3;
     static const SkScalar kBaseMS = 1000.f / 60.f;  // ms/frame to hit 60 fps
 
+    auto canvas = surface->getCanvas();
     SkISize canvasSize = canvas->getBaseLayerSize();
     SkRect rect = SkRect::MakeXYWH(SkIntToScalar(canvasSize.fWidth-kDisplayWidth-kDisplayPadding),
                                    SkIntToScalar(kDisplayPadding),
@@ -107,12 +118,13 @@
 
     int x = SkScalarTruncToInt(rect.fLeft) + kGraphPadding;
     const int xStep = 3;
-    int i = fCurrentMeasurement;
-    double ms = 0;
+    int i = nextMeasurement;
     SkTDArray<double> sumTimes;
     sumTimes.setCount(fTimers.count());
     memset(sumTimes.begin(), 0, sumTimes.count() * sizeof(double));
     int count = 0;
+    double totalTime = 0;
+    int totalCount = 0;
     do {
         int startY = SkScalarTruncToInt(rect.fBottom);
         double inc = 0;
@@ -127,19 +139,29 @@
             sumTimes[timer] += fTimers[timer].fTimes[i];
         }
 
+        int height = (int)(fTotalTimes[i] * kPixelPerMS + 0.5);
+        height = SkTMax(0, height - (SkScalarTruncToInt(rect.fBottom) - startY));
+        int endY = SkTMax(startY - height, kDisplayPadding + kTextHeight);
+        paint.setColor(SK_ColorWHITE);
+        canvas->drawLine(SkIntToScalar(x), SkIntToScalar(startY),
+                         SkIntToScalar(x), SkIntToScalar(endY), paint);
+        totalTime += fTotalTimes[i];
+        if (fTotalTimes[i] > 0) {
+            ++totalCount;
+        }
+
         if (inc > 0) {
-            ms += inc;
             ++count;
         }
 
         i++;
         i &= (kMeasurementCount - 1);  // fast mod
         x += xStep;
-    } while (i != fCurrentMeasurement);
+    } while (i != nextMeasurement);
 
     SkFont font(nullptr, 16);
     paint.setColor(SK_ColorWHITE);
-    double time = ms / SkTMax(1, count);
+    double time = totalTime / SkTMax(1, totalCount);
     double measure = fCumulativeMeasurementTime / SkTMax(1, fCumulativeMeasurementCount);
     canvas->drawString(SkStringPrintf("%4.3f ms -> %4.3f ms", time, measure),
                        rect.fLeft + 3, rect.fTop + 14, font, paint);
diff --git a/tools/viewer/StatsLayer.h b/tools/viewer/StatsLayer.h
index b888517..1d12f68 100644
--- a/tools/viewer/StatsLayer.h
+++ b/tools/viewer/StatsLayer.h
@@ -22,9 +22,9 @@
     Timer addTimer(const char* label, SkColor color, SkColor labelColor = 0);
     void beginTiming(Timer);
     void endTiming(Timer);
-    double getLastTime(Timer);
 
-    void onPaint(SkCanvas* canvas) override;
+    void onPrePaint() override;
+    void onPaint(SkSurface*) override;
 
     void setDisplayScale(float scale) { fDisplayScale = scale; }
 
@@ -37,7 +37,9 @@
         SkColor fLabelColor;
     };
     SkTArray<TimerData> fTimers;
+    double fTotalTimes[kMeasurementCount];
     int fCurrentMeasurement;
+    double fLastTotalBegin;
     double fCumulativeMeasurementTime;
     int fCumulativeMeasurementCount;
     float fDisplayScale;
diff --git a/tools/viewer/Viewer.cpp b/tools/viewer/Viewer.cpp
index 75730ca..3a610ac 100644
--- a/tools/viewer/Viewer.cpp
+++ b/tools/viewer/Viewer.cpp
@@ -5,21 +5,21 @@
 * found in the LICENSE file.
 */
 
+#include "Viewer.h"
 #include "BisectSlide.h"
+#include "CommandLineFlags.h"
+#include "CommonFlags.h"
+#include "EventTracingPriv.h"
 #include "GMSlide.h"
 #include "GrContext.h"
 #include "GrContextPriv.h"
 #include "ImageSlide.h"
+#include "ParticlesSlide.h"
 #include "Resources.h"
 #include "SKPSlide.h"
 #include "SampleSlide.h"
 #include "SkCanvas.h"
 #include "SkColorSpacePriv.h"
-#include "SkCommandLineFlags.h"
-#include "SkCommonFlags.h"
-#include "SkCommonFlagsGpu.h"
-#include "SkEventTracingPriv.h"
-#include "SkFontMgrPriv.h"
 #include "SkGraphics.h"
 #include "SkImagePriv.h"
 #include "SkJSONWriter.h"
@@ -32,11 +32,10 @@
 #include "SkStream.h"
 #include "SkSurface.h"
 #include "SkTaskGroup.h"
-#include "SkTestFontMgr.h"
 #include "SkTo.h"
 #include "SlideDir.h"
 #include "SvgSlide.h"
-#include "Viewer.h"
+#include "ToolUtils.h"
 #include "ccpr/GrCoverageCountingPathRenderer.h"
 
 #include <stdlib.h>
@@ -65,19 +64,49 @@
 
 #ifdef SK_VULKAN
 #    define BACKENDS_STR "\"sw\", \"gl\", and \"vk\""
+#elif defined(SK_METAL) && defined(SK_BUILD_FOR_MAC)
+#    define BACKENDS_STR "\"sw\", \"gl\", and \"mtl\""
 #else
 #    define BACKENDS_STR "\"sw\" and \"gl\""
 #endif
 
 static DEFINE_string2(backend, b, "sw", "Backend to use. Allowed values are " BACKENDS_STR ".");
 
-static DEFINE_int32(msaa, 1, "Number of subpixel samples. 0 for no HW antialiasing.");
+static DEFINE_int(msaa, 1, "Number of subpixel samples. 0 for no HW antialiasing.");
 
-DEFINE_string(bisect, "", "Path to a .skp or .svg file to bisect.");
+static DEFINE_string(bisect, "", "Path to a .skp or .svg file to bisect.");
 
-DECLARE_int32(threads)
+static DEFINE_string2(file, f, "", "Open a single file for viewing.");
 
-DEFINE_string2(file, f, "", "Open a single file for viewing.");
+static DEFINE_string2(match, m, nullptr,
+               "[~][^]substring[$] [...] of name to run.\n"
+               "Multiple matches may be separated by spaces.\n"
+               "~ causes a matching name to always be skipped\n"
+               "^ requires the start of the name to match\n"
+               "$ requires the end of the name to match\n"
+               "^ and $ requires an exact match\n"
+               "If a name does not match any list entry,\n"
+               "it is skipped unless some list entry starts with ~");
+
+#if defined(SK_BUILD_FOR_ANDROID)
+    static DEFINE_string(jpgs, "/data/local/tmp/resources", "Directory to read jpgs from.");
+    static DEFINE_string(nimas, "/data/local/tmp/nimas", "Directory to read NIMA animations from.");
+    static DEFINE_string(skps, "/data/local/tmp/skps", "Directory to read skps from.");
+    static DEFINE_string(lotties, "/data/local/tmp/lotties",
+                         "Directory to read (Bodymovin) jsons from.");
+#else
+    static DEFINE_string(jpgs, "jpgs", "Directory to read jpgs from.");
+    static DEFINE_string(nimas, "nimas", "Directory to read NIMA animations from.");
+    static DEFINE_string(skps, "skps", "Directory to read skps from.");
+    static DEFINE_string(lotties, "lotties", "Directory to read (Bodymovin) jsons from.");
+#endif
+
+static DEFINE_string(svgs, "", "Directory to read SVGs from, or a single SVG file.");
+
+static DEFINE_int_2(threads, j, -1,
+               "Run threadsafe tests on a threadpool with this many extra threads, "
+               "defaulting to one extra thread per core.");
+
 
 const char* kBackendTypeStrings[sk_app::Window::kBackendTypeCount] = {
     "OpenGL",
@@ -87,6 +116,9 @@
 #ifdef SK_VULKAN
     "Vulkan",
 #endif
+#if defined(SK_METAL) && defined(SK_BUILD_FOR_MAC)
+    "Metal",
+#endif
     "Raster"
 };
 
@@ -101,6 +133,11 @@
         return sk_app::Window::kANGLE_BackendType;
     } else
 #endif
+#if defined(SK_METAL) && defined(SK_BUILD_FOR_MAC)
+        if (0 == strcmp(str, "mtl")) {
+            return sk_app::Window::kMetal_BackendType;
+        } else
+#endif
     if (0 == strcmp(str, "gl")) {
         return sk_app::Window::kNativeGL_BackendType;
     } else if (0 == strcmp(str, "sw")) {
@@ -155,6 +192,16 @@
     return Window::kRaster_BackendType == backendType ? Window::kNativeGL_BackendType : backendType;
 }
 
+class NullSlide : public Slide {
+    SkISize getDimensions() const override {
+        return SkISize::Make(640, 480);
+    }
+
+    void draw(SkCanvas* canvas) override {
+        canvas->clear(0xffff11ff);
+    }
+};
+
 const char* kName = "name";
 const char* kValue = "value";
 const char* kOptions = "options";
@@ -173,6 +220,7 @@
     : fCurrentSlide(-1)
     , fRefresh(false)
     , fSaveToSKP(false)
+    , fShowSlideDimensions(false)
     , fShowImGuiDebugWindow(false)
     , fShowSlidePicker(false)
     , fShowImGuiTestWindow(false)
@@ -210,14 +258,12 @@
     }
     SkDebugf("\n");
 
-    SkCommandLineFlags::Parse(argc, argv);
+    CommandLineFlags::Parse(argc, argv);
 #ifdef SK_BUILD_FOR_ANDROID
     SetResourcePath("/data/local/tmp/resources");
 #endif
 
-    if (!FLAGS_nativeFonts) {
-        gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
-    }
+    ToolUtils::SetDefaultFontMgr();
 
     initializeEventTracingForTools();
     static SkTaskGroup::Enabler kTaskGroupEnabler(FLAGS_threads);
@@ -271,6 +317,13 @@
         this->fZoomWindowFixed = !this->fZoomWindowFixed;
         fWindow->inval();
     });
+    fCommands.addCommand('v', "VSync", "Toggle vsync on/off", [this]() {
+        DisplayParams params = fWindow->getRequestedDisplayParams();
+        params.fDisableVsync = !params.fDisableVsync;
+        fWindow->setRequestedDisplayParams(params);
+        this->updateTitle();
+        fWindow->inval();
+    });
     fCommands.addCommand('s', "Overlays", "Toggle stats display", [this]() {
         fStatsLayer.setActive(!fStatsLayer.getActive());
         fWindow->inval();
@@ -325,6 +378,10 @@
         fSaveToSKP = true;
         fWindow->inval();
     });
+    fCommands.addCommand('&', "Overlays", "Show slide dimensios", [this]() {
+        fShowSlideDimensions = !fShowSlideDimensions;
+        fWindow->inval();
+    });
     fCommands.addCommand('G', "Modes", "Geometry", [this]() {
         DisplayParams params = fWindow->getRequestedDisplayParams();
         uint32_t flags = params.fSurfaceProps.flags();
@@ -385,44 +442,27 @@
             fPaintOverrides.fAntiAlias = true;
             fPaint.setAntiAlias(false);
             gSkUseAnalyticAA = gSkForceAnalyticAA = false;
-            gSkUseDeltaAA = gSkForceDeltaAA = false;
         } else {
             fPaint.setAntiAlias(true);
             switch (fPaintOverrides.fAntiAliasState) {
                 case SkPaintFields::AntiAliasState::Alias:
                     fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::Normal;
                     gSkUseAnalyticAA = gSkForceAnalyticAA = false;
-                    gSkUseDeltaAA = gSkForceDeltaAA = false;
                     break;
                 case SkPaintFields::AntiAliasState::Normal:
                     fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::AnalyticAAEnabled;
                     gSkUseAnalyticAA = true;
                     gSkForceAnalyticAA = false;
-                    gSkUseDeltaAA = gSkForceDeltaAA = false;
                     break;
                 case SkPaintFields::AntiAliasState::AnalyticAAEnabled:
                     fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::AnalyticAAForced;
                     gSkUseAnalyticAA = gSkForceAnalyticAA = true;
-                    gSkUseDeltaAA = gSkForceDeltaAA = false;
                     break;
                 case SkPaintFields::AntiAliasState::AnalyticAAForced:
-                    fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::DeltaAAEnabled;
-                    gSkUseAnalyticAA = gSkForceAnalyticAA = false;
-                    gSkUseDeltaAA = true;
-                    gSkForceDeltaAA = false;
-                    break;
-                case SkPaintFields::AntiAliasState::DeltaAAEnabled:
-                    fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::DeltaAAForced;
-                    gSkUseAnalyticAA = gSkForceAnalyticAA = false;
-                    gSkUseDeltaAA = gSkForceDeltaAA = true;
-                    break;
-                case SkPaintFields::AntiAliasState::DeltaAAForced:
                     fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::Alias;
                     fPaintOverrides.fAntiAlias = false;
                     gSkUseAnalyticAA = fPaintOverrides.fOriginalSkUseAnalyticAA;
                     gSkForceAnalyticAA = fPaintOverrides.fOriginalSkForceAnalyticAA;
-                    gSkUseDeltaAA = fPaintOverrides.fOriginalSkUseDeltaAA;
-                    gSkForceDeltaAA = fPaintOverrides.fOriginalSkForceDeltaAA;
                     break;
             }
         }
@@ -522,7 +562,7 @@
     static const struct {
         const char*                            fExtension;
         const char*                            fDirName;
-        const SkCommandLineFlags::StringArray& fFlags;
+        const CommandLineFlags::StringArray&   fFlags;
         const SlideFactory                     fFactory;
     } gExternalSlidesInfo[] = {
         { ".skp", "skp-dir", FLAGS_skps,
@@ -555,18 +595,17 @@
 
     SkTArray<sk_sp<Slide>> dirSlides;
 
-    const auto addSlide = [&](const SkString& name,
-                              const SkString& path,
-                              const SlideFactory& fact) {
-        if (SkCommandLineFlags::ShouldSkip(FLAGS_match,  name.c_str())) {
-            return;
-        }
+    const auto addSlide =
+            [&](const SkString& name, const SkString& path, const SlideFactory& fact) {
+                if (CommandLineFlags::ShouldSkip(FLAGS_match, name.c_str())) {
+                    return;
+                }
 
-        if (auto slide = fact(name, path)) {
-            dirSlides.push_back(slide);
-            fSlides.push_back(std::move(slide));
-        }
-    };
+                if (auto slide = fact(name, path)) {
+                    dirSlides.push_back(slide);
+                    fSlides.push_back(std::move(slide));
+                }
+            };
 
     if (!FLAGS_file.isEmpty()) {
         // single file mode
@@ -591,7 +630,7 @@
     // Bisect slide.
     if (!FLAGS_bisect.isEmpty()) {
         sk_sp<BisectSlide> bisect = BisectSlide::Create(FLAGS_bisect[0]);
-        if (bisect && !SkCommandLineFlags::ShouldSkip(FLAGS_match, bisect->getName().c_str())) {
+        if (bisect && !CommandLineFlags::ShouldSkip(FLAGS_match, bisect->getName().c_str())) {
             if (FLAGS_bisect.count() >= 2) {
                 for (const char* ch = FLAGS_bisect[1]; *ch; ++ch) {
                     bisect->onChar(*ch);
@@ -605,7 +644,7 @@
     int firstGM = fSlides.count();
     for (skiagm::GMFactory gmFactory : skiagm::GMRegistry::Range()) {
         std::unique_ptr<skiagm::GM> gm(gmFactory(nullptr));
-        if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, gm->getName())) {
+        if (!CommandLineFlags::ShouldSkip(FLAGS_match, gm->getName())) {
             sk_sp<Slide> slide(new GMSlide(gm.release()));
             fSlides.push_back(std::move(slide));
         }
@@ -619,11 +658,20 @@
     // samples
     for (const SampleFactory factory : SampleRegistry::Range()) {
         sk_sp<Slide> slide(new SampleSlide(factory));
-        if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, slide->getName().c_str())) {
+        if (!CommandLineFlags::ShouldSkip(FLAGS_match, slide->getName().c_str())) {
             fSlides.push_back(slide);
         }
     }
 
+    // Particle demo
+    {
+        // TODO: Convert this to a sample
+        sk_sp<Slide> slide(new ParticlesSlide());
+        if (!CommandLineFlags::ShouldSkip(FLAGS_match, slide->getName().c_str())) {
+            fSlides.push_back(std::move(slide));
+        }
+    }
+
     for (const auto& info : gExternalSlidesInfo) {
         for (const auto& flag : info.fFlags) {
             if (SkStrEndsWith(flag.c_str(), info.fExtension)) {
@@ -645,6 +693,11 @@
             }
         }
     }
+
+    if (!fSlides.count()) {
+        sk_sp<Slide> slide(new NullSlide());
+        fSlides.push_back(std::move(slide));
+    }
 }
 
 
@@ -684,13 +737,7 @@
     SkString title("Viewer: ");
     title.append(fSlides[fCurrentSlide]->getName());
 
-    if (gSkUseDeltaAA) {
-        if (gSkForceDeltaAA) {
-            title.append(" <FDAA>");
-        } else {
-            title.append(" <DAA>");
-        }
-    } else if (gSkUseAnalyticAA) {
+    if (gSkUseAnalyticAA) {
         if (gSkForceAnalyticAA) {
             title.append(" <FAAA>");
         } else {
@@ -718,6 +765,22 @@
 
     paintFlag(&SkPaintFields::fAntiAlias, &SkPaint::isAntiAlias, "Antialias", "Alias");
     paintFlag(&SkPaintFields::fDither, &SkPaint::isDither, "DITHER", "No Dither");
+    if (fPaintOverrides.fFilterQuality) {
+        switch (fPaint.getFilterQuality()) {
+            case kNone_SkFilterQuality:
+                paintTitle.append("NoFilter");
+                break;
+            case kLow_SkFilterQuality:
+                paintTitle.append("LowFilter");
+                break;
+            case kMedium_SkFilterQuality:
+                paintTitle.append("MediumFilter");
+                break;
+            case kHigh_SkFilterQuality:
+                paintTitle.append("HighFilter");
+                break;
+        }
+    }
 
     fontFlag(&SkFontFields::fForceAutoHinting, &SkFont::isForceAutoHinting,
              "Force Autohint", "No Force Autohint");
@@ -935,8 +998,8 @@
 
 SkMatrix Viewer::computePreTouchMatrix() {
     SkMatrix m = fDefaultMatrix;
-    SkScalar zoomScale = (fZoomLevel < 0) ? SK_Scalar1 / (SK_Scalar1 - fZoomLevel)
-                                          : SK_Scalar1 + fZoomLevel;
+
+    SkScalar zoomScale = exp(fZoomLevel);
     m.preTranslate((fOffset.x() - 0.5f) * 2.0f, (fOffset.y() - 0.5f) * 2.0f);
     m.preScale(zoomScale, zoomScale);
 
@@ -1028,10 +1091,10 @@
             const SkTextBlobBuilder::RunBuffer& runBuffer
                 = it.positioning() == SkTextBlobRunIterator::kDefault_Positioning
                     ? SkTextBlobBuilderPriv::AllocRunText(&builder, font,
-                        it.offset().x(),it.offset().y(), it.glyphCount(), it.textSize(), SkString())
+                        it.glyphCount(), it.offset().x(),it.offset().y(), it.textSize(), SkString())
                 : it.positioning() == SkTextBlobRunIterator::kHorizontal_Positioning
                     ? SkTextBlobBuilderPriv::AllocRunTextPosH(&builder, font,
-                        it.offset().y(), it.glyphCount(), it.textSize(), SkString())
+                        it.glyphCount(), it.offset().y(), it.textSize(), SkString())
                 : it.positioning() == SkTextBlobRunIterator::kFull_Positioning
                     ? SkTextBlobBuilderPriv::AllocRunTextPos(&builder, font,
                         it.glyphCount(), it.textSize(), SkString())
@@ -1066,9 +1129,15 @@
             this->filterTextBlob(paint, blob, &cache), x, y, paint);
     }
     bool filterFont(SkTCopyOnFirstWrite<SkFont>* font) const {
-        if (fFontOverrides->fTextSize) {
+        if (fFontOverrides->fSize) {
             font->writable()->setSize(fFont->getSize());
         }
+        if (fFontOverrides->fScaleX) {
+            font->writable()->setScaleX(fFont->getScaleX());
+        }
+        if (fFontOverrides->fSkewX) {
+            font->writable()->setSkewX(fFont->getSkewX());
+        }
         if (fFontOverrides->fHinting) {
             font->writable()->setHinting(fFont->getHinting());
         }
@@ -1103,6 +1172,9 @@
         if (fPaintOverrides->fDither) {
             paint->writable()->setDither(fPaint->isDither());
         }
+        if (fPaintOverrides->fFilterQuality) {
+            paint->writable()->setFilterQuality(fPaint->getFilterQuality());
+        }
         return true;
     }
     SkPaint* fPaint;
@@ -1111,11 +1183,16 @@
     Viewer::SkFontFields* fFontOverrides;
 };
 
-void Viewer::drawSlide(SkCanvas* canvas) {
-    SkAutoCanvasRestore autorestore(canvas, false);
+void Viewer::drawSlide(SkSurface* surface) {
+    if (fCurrentSlide < 0) {
+        return;
+    }
+
+    SkAutoCanvasRestore autorestore(surface->getCanvas(), false);
 
     // By default, we render directly into the window's surface/canvas
-    SkCanvas* slideCanvas = canvas;
+    SkSurface* slideSurface = surface;
+    SkCanvas* slideCanvas = surface->getCanvas();
     fLastImage.reset();
 
     // If we're in any of the color managed modes, construct the color space we're going to use
@@ -1140,14 +1217,15 @@
     // Grab some things we'll need to make surfaces (for tiling or general offscreen rendering)
     SkColorType colorType = (ColorMode::kColorManagedF16 == fColorMode) ? kRGBA_F16_SkColorType
                                                                         : kN32_SkColorType;
-    SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
-    canvas->getProps(&props);
 
     auto make_surface = [=](int w, int h) {
+        SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
+        slideCanvas->getProps(&props);
+
         SkImageInfo info = SkImageInfo::Make(w, h, colorType, kPremul_SkAlphaType, colorSpace);
         return Window::kRaster_BackendType == this->fBackendType
                 ? SkSurface::MakeRaster(info, &props)
-                : canvas->makeSurface(info, &props);
+                : slideCanvas->makeSurface(info, &props);
     };
 
     // We need to render offscreen if we're...
@@ -1161,6 +1239,7 @@
         colorSpace != nullptr) {
 
         offscreenSurface = make_surface(fWindow->width(), fWindow->height());
+        slideSurface = offscreenSurface.get();
         slideCanvas = offscreenSurface->getCanvas();
     }
 
@@ -1211,13 +1290,14 @@
 
     // Force a flush so we can time that, too
     fStatsLayer.beginTiming(fFlushTimer);
-    slideCanvas->flush();
+    slideSurface->flush();
     fStatsLayer.endTiming(fFlushTimer);
 
     // If we rendered offscreen, snap an image and push the results to the window's canvas
     if (offscreenSurface) {
         fLastImage = offscreenSurface->makeImageSnapshot();
 
+        SkCanvas* canvas = surface->getCanvas();
         SkPaint paint;
         paint.setBlendMode(SkBlendMode::kSrc);
         int prePerspectiveCount = canvas->save();
@@ -1229,6 +1309,13 @@
         canvas->drawImage(fLastImage, 0, 0, &paint);
         canvas->restoreToCount(prePerspectiveCount);
     }
+
+    if (fShowSlideDimensions) {
+        SkRect r = SkRect::Make(fSlides[fCurrentSlide]->getDimensions());
+        SkPaint paint;
+        paint.setColor(0x40FFFF00);
+        surface->getCanvas()->drawRect(r, paint);
+    }
 }
 
 void Viewer::onBackendCreated() {
@@ -1236,10 +1323,10 @@
     fWindow->show();
 }
 
-void Viewer::onPaint(SkCanvas* canvas) {
-    this->drawSlide(canvas);
+void Viewer::onPaint(SkSurface* surface) {
+    this->drawSlide(surface);
 
-    fCommands.drawHelp(canvas);
+    fCommands.drawHelp(surface->getCanvas());
 
     this->drawImGui();
 
@@ -1345,143 +1432,45 @@
     return true;
 }
 
-static ImVec2 ImGui_DragPrimary(const char* label, float* x, float* y,
-                                const ImVec2& pos, const ImVec2& size) {
-    // Transform primaries ([0, 0] - [0.8, 0.9]) to screen coords (including Y-flip)
-    ImVec2 center(pos.x + (*x / 0.8f) * size.x, pos.y + (1.0f - (*y / 0.9f)) * size.y);
-
-    // Invisible 10x10 button
-    ImGui::SetCursorScreenPos(ImVec2(center.x - 5, center.y - 5));
-    ImGui::InvisibleButton(label, ImVec2(10, 10));
-
-    if (ImGui::IsItemActive() && ImGui::IsMouseDragging()) {
-        ImGuiIO& io = ImGui::GetIO();
-        // Normalized mouse position, relative to our gamut box
-        ImVec2 mousePosXY((io.MousePos.x - pos.x) / size.x, (io.MousePos.y - pos.y) / size.y);
-        // Clamp to edge of box, convert back to primary scale
-        *x = SkTPin(mousePosXY.x, 0.0f, 1.0f) * 0.8f;
-        *y = SkTPin(1 - mousePosXY.y, 0.0f, 1.0f) * 0.9f;
-    }
-
-    if (ImGui::IsItemHovered()) {
-        ImGui::SetTooltip("x: %.3f\ny: %.3f", *x, *y);
-    }
-
-    // Return screen coordinates for the caller. We could just return center here, but we'd have
-    // one frame of lag during drag.
-    return ImVec2(pos.x + (*x / 0.8f) * size.x, pos.y + (1.0f - (*y / 0.9f)) * size.y);
-}
-
 static void ImGui_Primaries(SkColorSpacePrimaries* primaries, SkPaint* gamutPaint) {
-    ImDrawList* drawList = ImGui::GetWindowDrawList();
-
-    // The gamut image covers a (0.8 x 0.9) shaped region, so fit our image/canvas to the available
-    // width, and scale the height to maintain aspect ratio.
-    float canvasWidth = SkTMax(ImGui::GetContentRegionAvailWidth(), 50.0f);
-    ImVec2 size = ImVec2(canvasWidth, canvasWidth * (0.9f / 0.8f));
-    ImVec2 pos = ImGui::GetCursorScreenPos();
+    // The gamut image covers a (0.8 x 0.9) shaped region
+    ImGui::DragCanvas dc(primaries, { 0.0f, 0.9f }, { 0.8f, 0.0f });
 
     // Background image. Only draw a subset of the image, to avoid the regions less than zero.
     // Simplifes re-mapping math, clipping behavior, and increases resolution in the useful area.
     // Magic numbers are pixel locations of the origin and upper-right corner.
-    drawList->AddImage(gamutPaint, pos, ImVec2(pos.x + size.x, pos.y + size.y),
-                       ImVec2(242, 61), ImVec2(1897, 1922));
+    dc.fDrawList->AddImage(gamutPaint, dc.fPos,
+                           ImVec2(dc.fPos.x + dc.fSize.x, dc.fPos.y + dc.fSize.y),
+                           ImVec2(242, 61), ImVec2(1897, 1922));
 
-    // Primary markers
-    ImVec2 r = ImGui_DragPrimary("R", &primaries->fRX, &primaries->fRY, pos, size);
-    ImVec2 g = ImGui_DragPrimary("G", &primaries->fGX, &primaries->fGY, pos, size);
-    ImVec2 b = ImGui_DragPrimary("B", &primaries->fBX, &primaries->fBY, pos, size);
-    ImVec2 w = ImGui_DragPrimary("W", &primaries->fWX, &primaries->fWY, pos, size);
-
-    // Gamut triangle
-    drawList->AddCircle(r, 5.0f, 0xFF000040);
-    drawList->AddCircle(g, 5.0f, 0xFF004000);
-    drawList->AddCircle(b, 5.0f, 0xFF400000);
-    drawList->AddCircle(w, 5.0f, 0xFFFFFFFF);
-    drawList->AddTriangle(r, g, b, 0xFFFFFFFF);
-
-    // Re-position cursor immediate after the diagram for subsequent controls
-    ImGui::SetCursorScreenPos(ImVec2(pos.x, pos.y + size.y));
-}
-
-static ImVec2 ImGui_DragPoint(const char* label, SkPoint* p,
-                              const ImVec2& pos, const ImVec2& size, bool* dragging) {
-    // Transform points ([0, 0] - [1.0, 1.0]) to screen coords
-    ImVec2 center(pos.x + p->fX * size.x, pos.y + p->fY * size.y);
-
-    // Invisible 10x10 button
-    ImGui::SetCursorScreenPos(ImVec2(center.x - 5, center.y - 5));
-    ImGui::InvisibleButton(label, ImVec2(10, 10));
-
-    if (ImGui::IsItemActive() && ImGui::IsMouseDragging()) {
-        ImGuiIO& io = ImGui::GetIO();
-        // Normalized mouse position, relative to our gamut box
-        ImVec2 mousePosXY((io.MousePos.x - pos.x) / size.x, (io.MousePos.y - pos.y) / size.y);
-        // Clamp to edge of box
-        p->fX = SkTPin(mousePosXY.x, 0.0f, 1.0f);
-        p->fY = SkTPin(mousePosXY.y, 0.0f, 1.0f);
-        *dragging = true;
-    }
-
-    // Return screen coordinates for the caller. We could just return center here, but we'd have
-    // one frame of lag during drag.
-    return ImVec2(pos.x + p->fX * size.x, pos.y + p->fY * size.y);
+    dc.dragPoint((SkPoint*)(&primaries->fRX), true, 0xFF000040);
+    dc.dragPoint((SkPoint*)(&primaries->fGX), true, 0xFF004000);
+    dc.dragPoint((SkPoint*)(&primaries->fBX), true, 0xFF400000);
+    dc.dragPoint((SkPoint*)(&primaries->fWX), true);
+    dc.fDrawList->AddPolyline(dc.fScreenPoints.begin(), 3, 0xFFFFFFFF, true, 1.5f);
 }
 
 static bool ImGui_DragLocation(SkPoint* pt) {
-    ImDrawList* drawList = ImGui::GetWindowDrawList();
-
-    // Fit our image/canvas to the available width, and scale the height to maintain aspect ratio.
-    float canvasWidth = SkTMax(ImGui::GetContentRegionAvailWidth(), 50.0f);
-    ImVec2 size = ImVec2(canvasWidth, canvasWidth);
-    ImVec2 pos = ImGui::GetCursorScreenPos();
-
-    // Background rectangle
-    drawList->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), IM_COL32(0, 0, 0, 128));
-
-    // Location marker
-    bool dragging = false;
-    ImVec2 tl = ImGui_DragPoint("SL", pt + 0, pos, size, &dragging);
-    drawList->AddCircle(tl, 5.0f, 0xFFFFFFFF);
-
-    ImGui::SetCursorScreenPos(ImVec2(pos.x, pos.y + size.y));
-    ImGui::Spacing();
-
-    return dragging;
+    ImGui::DragCanvas dc(pt);
+    dc.fillColor(IM_COL32(0, 0, 0, 128));
+    dc.dragPoint(pt);
+    return dc.fDragging;
 }
 
 static bool ImGui_DragQuad(SkPoint* pts) {
-    ImDrawList* drawList = ImGui::GetWindowDrawList();
+    ImGui::DragCanvas dc(pts);
+    dc.fillColor(IM_COL32(0, 0, 0, 128));
 
-    // Fit our image/canvas to the available width, and scale the height to maintain aspect ratio.
-    float canvasWidth = SkTMax(ImGui::GetContentRegionAvailWidth(), 50.0f);
-    ImVec2 size = ImVec2(canvasWidth, canvasWidth);
-    ImVec2 pos = ImGui::GetCursorScreenPos();
+    for (int i = 0; i < 4; ++i) {
+        dc.dragPoint(pts + i);
+    }
 
-    // Background rectangle
-    drawList->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), IM_COL32(0, 0, 0, 128));
+    dc.fDrawList->AddLine(dc.fScreenPoints[0], dc.fScreenPoints[1], 0xFFFFFFFF);
+    dc.fDrawList->AddLine(dc.fScreenPoints[1], dc.fScreenPoints[3], 0xFFFFFFFF);
+    dc.fDrawList->AddLine(dc.fScreenPoints[3], dc.fScreenPoints[2], 0xFFFFFFFF);
+    dc.fDrawList->AddLine(dc.fScreenPoints[2], dc.fScreenPoints[0], 0xFFFFFFFF);
 
-    // Corner markers
-    bool dragging = false;
-    ImVec2 tl = ImGui_DragPoint("TL", pts + 0, pos, size, &dragging);
-    ImVec2 tr = ImGui_DragPoint("TR", pts + 1, pos, size, &dragging);
-    ImVec2 bl = ImGui_DragPoint("BL", pts + 2, pos, size, &dragging);
-    ImVec2 br = ImGui_DragPoint("BR", pts + 3, pos, size, &dragging);
-
-    // Draw markers and quad
-    drawList->AddCircle(tl, 5.0f, 0xFFFFFFFF);
-    drawList->AddCircle(tr, 5.0f, 0xFFFFFFFF);
-    drawList->AddCircle(bl, 5.0f, 0xFFFFFFFF);
-    drawList->AddCircle(br, 5.0f, 0xFFFFFFFF);
-    drawList->AddLine(tl, tr, 0xFFFFFFFF);
-    drawList->AddLine(tr, br, 0xFFFFFFFF);
-    drawList->AddLine(br, bl, 0xFFFFFFFF);
-    drawList->AddLine(bl, tl, 0xFFFFFFFF);
-
-    ImGui::SetCursorScreenPos(ImVec2(pos.x, pos.y + size.y));
-    ImGui::Spacing();
-
-    return dragging;
+    return dc.fDragging;
 }
 
 void Viewer::drawImGui() {
@@ -1511,6 +1500,10 @@
                 ImGui::SameLine();
                 ImGui::RadioButton("Vulkan", &newBackend, sk_app::Window::kVulkan_BackendType);
 #endif
+#if defined(SK_METAL) && defined(SK_BUILD_FOR_MAC)
+                ImGui::SameLine();
+                ImGui::RadioButton("Metal", &newBackend, sk_app::Window::kMetal_BackendType);
+#endif
                 if (newBackend != fBackendType) {
                     fDeferredActions.push_back([=]() {
                         this->setBackend(static_cast<sk_app::Window::BackendType>(newBackend));
@@ -1603,6 +1596,13 @@
                 }
             }
 
+            if (ImGui::CollapsingHeader("Tiling")) {
+                ImGui::Checkbox("Enable", &fTiled);
+                ImGui::Checkbox("Draw Boundaries", &fDrawTileBoundaries);
+                ImGui::SliderFloat("Horizontal", &fTileScale.fWidth, 0.1f, 1.0f);
+                ImGui::SliderFloat("Vertical", &fTileScale.fHeight, 0.1f, 1.0f);
+            }
+
             if (ImGui::CollapsingHeader("Transform")) {
                 float zoom = fZoomLevel;
                 if (ImGui::SliderFloat("Zoom", &zoom, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL)) {
@@ -1626,12 +1626,6 @@
                     paramsChanged = true;
                     fOffset = {0.5f, 0.5f};
                 }
-                if (ImGui::CollapsingHeader("Tiling")) {
-                    ImGui::Checkbox("Enable", &fTiled);
-                    ImGui::Checkbox("Draw Boundaries", &fDrawTileBoundaries);
-                    ImGui::SliderFloat("Horizontal", &fTileScale.fWidth, 0.1f, 1.0f);
-                    ImGui::SliderFloat("Vertical", &fTileScale.fHeight, 0.1f, 1.0f);
-                }
                 int perspectiveMode = static_cast<int>(fPerspectiveMode);
                 if (ImGui::Combo("Perspective", &perspectiveMode, "Off\0Real\0Fake\0\0")) {
                     fPerspectiveMode = static_cast<PerspectiveMode>(perspectiveMode);
@@ -1650,13 +1644,10 @@
                     aliasIdx = SkTo<int>(fPaintOverrides.fAntiAliasState) + 1;
                 }
                 if (ImGui::Combo("Anti-Alias", &aliasIdx,
-                                 "Default\0Alias\0Normal\0AnalyticAAEnabled\0AnalyticAAForced\0"
-                                 "DeltaAAEnabled\0DeltaAAForced\0\0"))
+                                 "Default\0Alias\0Normal\0AnalyticAAEnabled\0AnalyticAAForced\0\0"))
                 {
                     gSkUseAnalyticAA = fPaintOverrides.fOriginalSkUseAnalyticAA;
                     gSkForceAnalyticAA = fPaintOverrides.fOriginalSkForceAnalyticAA;
-                    gSkUseDeltaAA = fPaintOverrides.fOriginalSkUseDeltaAA;
-                    gSkForceDeltaAA = fPaintOverrides.fOriginalSkForceDeltaAA;
                     if (aliasIdx == 0) {
                         fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::Alias;
                         fPaintOverrides.fAntiAlias = false;
@@ -1672,20 +1663,9 @@
                             case SkPaintFields::AntiAliasState::AnalyticAAEnabled:
                                 gSkUseAnalyticAA = true;
                                 gSkForceAnalyticAA = false;
-                                gSkUseDeltaAA = gSkForceDeltaAA = false;
                                 break;
                             case SkPaintFields::AntiAliasState::AnalyticAAForced:
                                 gSkUseAnalyticAA = gSkForceAnalyticAA = true;
-                                gSkUseDeltaAA = gSkForceDeltaAA = false;
-                                break;
-                            case SkPaintFields::AntiAliasState::DeltaAAEnabled:
-                                gSkUseAnalyticAA = gSkForceAnalyticAA = false;
-                                gSkUseDeltaAA = true;
-                                gSkForceDeltaAA = false;
-                                break;
-                            case SkPaintFields::AntiAliasState::DeltaAAForced:
-                                gSkUseAnalyticAA = gSkForceAnalyticAA = false;
-                                gSkUseDeltaAA = gSkForceDeltaAA = true;
                                 break;
                         }
                     }
@@ -1716,6 +1696,23 @@
                           "Default\0No Dither\0Dither\0\0",
                           &SkPaintFields::fDither,
                           &SkPaint::isDither, &SkPaint::setDither);
+
+                int filterQualityIdx = 0;
+                if (fPaintOverrides.fFilterQuality) {
+                    filterQualityIdx = SkTo<int>(fPaint.getFilterQuality()) + 1;
+                }
+                if (ImGui::Combo("Filter Quality", &filterQualityIdx,
+                                 "Default\0None\0Low\0Medium\0High\0\0"))
+                {
+                    if (filterQualityIdx == 0) {
+                        fPaintOverrides.fFilterQuality = false;
+                        fPaint.setFilterQuality(kNone_SkFilterQuality);
+                    } else {
+                        fPaint.setFilterQuality(SkTo<SkFilterQuality>(filterQualityIdx - 1));
+                        fPaintOverrides.fFilterQuality = true;
+                    }
+                    paramsChanged = true;
+                }
             }
 
             if (ImGui::CollapsingHeader("Font")) {
@@ -1798,18 +1795,35 @@
                     paramsChanged = true;
                 }
 
-                ImGui::Checkbox("Override TextSize", &fFontOverrides.fTextSize);
-                if (fFontOverrides.fTextSize) {
-                    ImGui::DragFloat2("TextRange", fFontOverrides.fTextSizeRange,
+                ImGui::Checkbox("Override Size", &fFontOverrides.fSize);
+                if (fFontOverrides.fSize) {
+                    ImGui::DragFloat2("TextRange", fFontOverrides.fSizeRange,
                                       0.001f, -10.0f, 300.0f, "%.6f", 2.0f);
                     float textSize = fFont.getSize();
                     if (ImGui::DragFloat("TextSize", &textSize, 0.001f,
-                                         fFontOverrides.fTextSizeRange[0],
-                                         fFontOverrides.fTextSizeRange[1],
+                                         fFontOverrides.fSizeRange[0],
+                                         fFontOverrides.fSizeRange[1],
                                          "%.6f", 2.0f))
                     {
                         fFont.setSize(textSize);
-                        this->preTouchMatrixChanged();
+                        paramsChanged = true;
+                    }
+                }
+
+                ImGui::Checkbox("Override ScaleX", &fFontOverrides.fScaleX);
+                if (fFontOverrides.fScaleX) {
+                    float scaleX = fFont.getScaleX();
+                    if (ImGui::SliderFloat("ScaleX", &scaleX, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL)) {
+                        fFont.setScaleX(scaleX);
+                        paramsChanged = true;
+                    }
+                }
+
+                ImGui::Checkbox("Override SkewX", &fFontOverrides.fSkewX);
+                if (fFontOverrides.fSkewX) {
+                    float skewX = fFont.getSkewX();
+                    if (ImGui::SliderFloat("SkewX", &skewX, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL)) {
+                        fFont.setSkewX(skewX);
                         paramsChanged = true;
                     }
                 }
@@ -1831,6 +1845,13 @@
                                 if (ImGui::SliderFloat(name, &val[0], val[1], val[2])) {
                                     controls.setScalars(name, 3, val);
                                 }
+                            } else if (type == SkMetaData::kBool_Type) {
+                                bool val;
+                                SkASSERT(count == 1);
+                                controls.findBool(name, &val);
+                                if (ImGui::Checkbox(name, &val)) {
+                                    controls.setBool(name, val);
+                                }
                             }
                         }
                         fSlides[fCurrentSlide]->onSetControls(controls);
@@ -1959,7 +1980,7 @@
             SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
             if (fLastImage->readPixels(info, &pixel, info.minRowBytes(), xInt, yInt)) {
                 ImGui::SameLine();
-                ImGui::Text("(X, Y): %d, %d RGBA: %x %x %x %x",
+                ImGui::Text("(X, Y): %d, %d RGBA: %X %X %X %X",
                             xInt, yInt,
                             SkGetPackedR32(pixel), SkGetPackedG32(pixel),
                             SkGetPackedB32(pixel), SkGetPackedA32(pixel));
diff --git a/tools/viewer/Viewer.h b/tools/viewer/Viewer.h
index ec6a2d5..92c5696 100644
--- a/tools/viewer/Viewer.h
+++ b/tools/viewer/Viewer.h
@@ -8,17 +8,18 @@
 #ifndef Viewer_DEFINED
 #define Viewer_DEFINED
 
-#include "sk_app/Application.h"
-#include "sk_app/CommandSet.h"
-#include "sk_app/Window.h"
-#include "gm.h"
+#include "AnimTimer.h"
 #include "ImGuiLayer.h"
-#include "SkAnimTimer.h"
 #include "SkExecutor.h"
+#include "SkFont.h"
 #include "SkScan.h"
 #include "Slide.h"
 #include "StatsLayer.h"
 #include "TouchGesture.h"
+#include "gm.h"
+#include "sk_app/Application.h"
+#include "sk_app/CommandSet.h"
+#include "sk_app/Window.h"
 
 class SkCanvas;
 
@@ -30,7 +31,7 @@
     void onIdle() override;
 
     void onBackendCreated() override;
-    void onPaint(SkCanvas* canvas) override;
+    void onPaint(SkSurface*) override;
     void onResize(int width, int height) override;
     bool onTouch(intptr_t owner, sk_app::Window::InputState state, float x, float y) override;
     bool onMouse(int x, int y, sk_app::Window::InputState state, uint32_t modifiers) override;
@@ -40,10 +41,10 @@
 
     struct SkFontFields {
         bool fTypeface = false;
-        bool fTextSize = false;
-        SkScalar fTextSizeRange[2] = { 0, 20 };
-        bool fTextScaleX = false;
-        bool fTextSkewX = false;
+        bool fSize = false;
+        SkScalar fSizeRange[2] = { 0, 20 };
+        bool fScaleX = false;
+        bool fSkewX = false;
         bool fHinting = false;
         bool fEdging = false;
         bool fSubpixel = false;
@@ -72,13 +73,9 @@
             Normal,
             AnalyticAAEnabled,
             AnalyticAAForced,
-            DeltaAAEnabled,
-            DeltaAAForced,
         } fAntiAliasState = AntiAliasState::Alias;
         const bool fOriginalSkUseAnalyticAA = gSkUseAnalyticAA;
         const bool fOriginalSkForceAnalyticAA = gSkForceAnalyticAA;
-        const bool fOriginalSkUseDeltaAA = gSkUseDeltaAA;
-        const bool fOriginalSkForceDeltaAA = gSkForceDeltaAA;
 
         bool fCapType = false;
         bool fJoinType = false;
@@ -103,7 +100,7 @@
 
     void updateUIState();
 
-    void drawSlide(SkCanvas* canvs);
+    void drawSlide(SkSurface* surface);
     void drawImGui();
 
     void changeZoomLevel(float delta);
@@ -120,13 +117,14 @@
     StatsLayer::Timer      fFlushTimer;
     StatsLayer::Timer      fAnimateTimer;
 
-    SkAnimTimer            fAnimTimer;
+    AnimTimer              fAnimTimer;
     SkTArray<sk_sp<Slide>> fSlides;
     int                    fCurrentSlide;
 
     bool                   fRefresh; // whether to continuously refresh for measuring render time
 
     bool                   fSaveToSKP;
+    bool                   fShowSlideDimensions;
 
     ImGuiLayer             fImGuiLayer;
     SkPaint                fImGuiGamutPaint;